Repository: cemu-project/Cemu Branch: main Commit: 6312fb936cbe Files: 1285 Total size: 18.7 MB Directory structure: gitextract__32_xijf/ ├── .clang-format ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── config.yml │ │ ├── emulation_bug_report.yaml │ │ └── feature_report.yaml │ └── workflows/ │ ├── build.yml │ ├── build_check.yml │ ├── deploy_release.yml │ ├── determine_release_version.yml │ └── generate_pot.yml ├── .gitignore ├── .gitmodules ├── BUILD.md ├── CMakeLists.txt ├── CMakeSettings.json ├── CODING_STYLE.md ├── LICENSE.txt ├── README.md ├── bin/ │ └── resources/ │ ├── ar/ │ │ └── ‏‏cemu.mo │ ├── ca/ │ │ └── cemu.mo │ ├── de/ │ │ └── cemu.mo │ ├── es/ │ │ └── cemu.mo │ ├── fr/ │ │ └── cemu.mo │ ├── he/ │ │ └── cemu.mo │ ├── hu/ │ │ └── cemu.mo │ ├── it/ │ │ └── cemu.mo │ ├── ja/ │ │ └── cemu.mo │ ├── ko/ │ │ └── cemu.mo │ ├── nb/ │ │ └── cemu.mo │ ├── nl/ │ │ └── cemu.mo │ ├── pl/ │ │ └── cemu.mo │ ├── pt/ │ │ └── cemu.mo │ ├── ru/ │ │ └── cemu.mo │ ├── sv/ │ │ └── cemu.mo │ ├── tr/ │ │ └── cemu.mo │ ├── uk/ │ │ └── cemu.mo │ └── zh/ │ └── cemu.mo ├── boost.natvis ├── cmake/ │ ├── ECMFindModuleHelpers.cmake │ ├── ECMFindModuleHelpersStub.cmake │ ├── FindGTK3.cmake │ ├── FindWayland.cmake │ ├── FindWaylandProtocols.cmake │ ├── FindWaylandScanner.cmake │ ├── FindZArchive.cmake │ ├── Findbluez.cmake │ ├── Findimgui.cmake │ ├── Findlibusb.cmake │ ├── FindwxWidgets.cmake │ └── Findzstd.cmake ├── dependencies/ │ ├── DirectX_2010/ │ │ ├── XAudio2.h │ │ ├── audiodefs.h │ │ ├── comdecl.h │ │ └── xma2defs.h │ ├── gamemode/ │ │ ├── CMakeLists.txt │ │ └── lib/ │ │ ├── client_loader.c │ │ └── gamemode_client.h │ ├── ih264d/ │ │ ├── CHANGES │ │ ├── CMakeLists.txt │ │ ├── CMakeSettings.json │ │ ├── NOTICE │ │ ├── common/ │ │ │ ├── arm/ │ │ │ │ ├── ih264_arm_memory_barrier.s │ │ │ │ ├── ih264_deblk_chroma_a9.s │ │ │ │ ├── ih264_deblk_luma_a9.s │ │ │ │ ├── ih264_default_weighted_pred_a9q.s │ │ │ │ ├── ih264_ihadamard_scaling_a9.s │ │ │ │ ├── ih264_inter_pred_chroma_a9q.s │ │ │ │ ├── ih264_inter_pred_filters_luma_horz_a9q.s │ │ │ │ ├── ih264_inter_pred_filters_luma_vert_a9q.s │ │ │ │ ├── ih264_inter_pred_luma_bilinear_a9q.s │ │ │ │ ├── ih264_inter_pred_luma_copy_a9q.s │ │ │ │ ├── ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q.s │ │ │ │ ├── ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q.s │ │ │ │ ├── ih264_inter_pred_luma_horz_qpel_a9q.s │ │ │ │ ├── ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q.s │ │ │ │ ├── ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q.s │ │ │ │ ├── ih264_inter_pred_luma_vert_qpel_a9q.s │ │ │ │ ├── ih264_intra_pred_chroma_a9q.s │ │ │ │ ├── ih264_intra_pred_luma_16x16_a9q.s │ │ │ │ ├── ih264_intra_pred_luma_4x4_a9q.s │ │ │ │ ├── ih264_intra_pred_luma_8x8_a9q.s │ │ │ │ ├── ih264_iquant_itrans_recon_a9.s │ │ │ │ ├── ih264_iquant_itrans_recon_dc_a9.s │ │ │ │ ├── ih264_mem_fns_neon.s │ │ │ │ ├── ih264_padding_neon.s │ │ │ │ ├── ih264_platform_macros.h │ │ │ │ ├── ih264_resi_trans_quant_a9.s │ │ │ │ ├── ih264_weighted_bi_pred_a9q.s │ │ │ │ └── ih264_weighted_pred_a9q.s │ │ │ ├── armv8/ │ │ │ │ ├── ih264_deblk_chroma_av8.s │ │ │ │ ├── ih264_deblk_luma_av8.s │ │ │ │ ├── ih264_default_weighted_pred_av8.s │ │ │ │ ├── ih264_ihadamard_scaling_av8.s │ │ │ │ ├── ih264_inter_pred_chroma_av8.s │ │ │ │ ├── ih264_inter_pred_filters_luma_horz_av8.s │ │ │ │ ├── ih264_inter_pred_filters_luma_vert_av8.s │ │ │ │ ├── ih264_inter_pred_luma_copy_av8.s │ │ │ │ ├── ih264_inter_pred_luma_horz_hpel_vert_hpel_av8.s │ │ │ │ ├── ih264_inter_pred_luma_horz_hpel_vert_qpel_av8.s │ │ │ │ ├── ih264_inter_pred_luma_horz_qpel_av8.s │ │ │ │ ├── ih264_inter_pred_luma_horz_qpel_vert_hpel_av8.s │ │ │ │ ├── ih264_inter_pred_luma_horz_qpel_vert_qpel_av8.s │ │ │ │ ├── ih264_inter_pred_luma_vert_qpel_av8.s │ │ │ │ ├── ih264_intra_pred_chroma_av8.s │ │ │ │ ├── ih264_intra_pred_luma_16x16_av8.s │ │ │ │ ├── ih264_intra_pred_luma_4x4_av8.s │ │ │ │ ├── ih264_intra_pred_luma_8x8_av8.s │ │ │ │ ├── ih264_iquant_itrans_recon_av8.s │ │ │ │ ├── ih264_iquant_itrans_recon_dc_av8.s │ │ │ │ ├── ih264_mem_fns_neon_av8.s │ │ │ │ ├── ih264_neon_macros.s │ │ │ │ ├── ih264_padding_neon_av8.s │ │ │ │ ├── ih264_platform_macros.h │ │ │ │ ├── ih264_resi_trans_quant_av8.s │ │ │ │ ├── ih264_weighted_bi_pred_av8.s │ │ │ │ ├── ih264_weighted_pred_av8.s │ │ │ │ └── macos_arm_symbol_aliases.s │ │ │ ├── ih264_buf_mgr.c │ │ │ ├── ih264_buf_mgr.h │ │ │ ├── ih264_cabac_tables.c │ │ │ ├── ih264_cabac_tables.h │ │ │ ├── ih264_cavlc_tables.c │ │ │ ├── ih264_cavlc_tables.h │ │ │ ├── ih264_chroma_intra_pred_filters.c │ │ │ ├── ih264_common_tables.c │ │ │ ├── ih264_common_tables.h │ │ │ ├── ih264_deblk_edge_filters.c │ │ │ ├── ih264_deblk_edge_filters.h │ │ │ ├── ih264_deblk_tables.c │ │ │ ├── ih264_deblk_tables.h │ │ │ ├── ih264_debug.h │ │ │ ├── ih264_defs.h │ │ │ ├── ih264_disp_mgr.c │ │ │ ├── ih264_disp_mgr.h │ │ │ ├── ih264_dpb_mgr.c │ │ │ ├── ih264_dpb_mgr.h │ │ │ ├── ih264_error.h │ │ │ ├── ih264_ihadamard_scaling.c │ │ │ ├── ih264_inter_pred_filters.c │ │ │ ├── ih264_inter_pred_filters.h │ │ │ ├── ih264_intra_pred_filters.h │ │ │ ├── ih264_iquant_itrans_recon.c │ │ │ ├── ih264_list.c │ │ │ ├── ih264_list.h │ │ │ ├── ih264_luma_intra_pred_filters.c │ │ │ ├── ih264_macros.h │ │ │ ├── ih264_mem_fns.c │ │ │ ├── ih264_mem_fns.h │ │ │ ├── ih264_padding.c │ │ │ ├── ih264_padding.h │ │ │ ├── ih264_resi_trans.h │ │ │ ├── ih264_resi_trans_quant.c │ │ │ ├── ih264_size_defs.h │ │ │ ├── ih264_structs.h │ │ │ ├── ih264_trans_data.c │ │ │ ├── ih264_trans_data.h │ │ │ ├── ih264_trans_macros.h │ │ │ ├── ih264_trans_quant_itrans_iquant.h │ │ │ ├── ih264_typedefs.h │ │ │ ├── ih264_weighted_pred.c │ │ │ ├── ih264_weighted_pred.h │ │ │ ├── ithread.c │ │ │ ├── ithread.h │ │ │ ├── mips/ │ │ │ │ └── ih264_platform_macros.h │ │ │ └── x86/ │ │ │ ├── ih264_chroma_intra_pred_filters_ssse3.c │ │ │ ├── ih264_deblk_chroma_ssse3.c │ │ │ ├── ih264_deblk_luma_ssse3.c │ │ │ ├── ih264_ihadamard_scaling_sse42.c │ │ │ ├── ih264_ihadamard_scaling_ssse3.c │ │ │ ├── ih264_inter_pred_filters_ssse3.c │ │ │ ├── ih264_iquant_itrans_recon_dc_ssse3.c │ │ │ ├── ih264_iquant_itrans_recon_sse42.c │ │ │ ├── ih264_iquant_itrans_recon_ssse3.c │ │ │ ├── ih264_luma_intra_pred_filters_ssse3.c │ │ │ ├── ih264_mem_fns_ssse3.c │ │ │ ├── ih264_padding_ssse3.c │ │ │ ├── ih264_platform_macros.h │ │ │ ├── ih264_resi_trans_quant_sse42.c │ │ │ └── ih264_weighted_pred_sse42.c │ │ └── decoder/ │ │ ├── arm/ │ │ │ ├── ih264d_function_selector.c │ │ │ ├── ih264d_function_selector_a9q.c │ │ │ └── ih264d_function_selector_av8.c │ │ ├── ih264d.h │ │ ├── ih264d_api.c │ │ ├── ih264d_bitstrm.c │ │ ├── ih264d_bitstrm.h │ │ ├── ih264d_cabac.c │ │ ├── ih264d_cabac.h │ │ ├── ih264d_cabac_init_tables.c │ │ ├── ih264d_compute_bs.c │ │ ├── ih264d_deblocking.c │ │ ├── ih264d_deblocking.h │ │ ├── ih264d_debug.h │ │ ├── ih264d_defs.h │ │ ├── ih264d_dpb_manager.h │ │ ├── ih264d_dpb_mgr.c │ │ ├── ih264d_error_handler.h │ │ ├── ih264d_format_conv.c │ │ ├── ih264d_format_conv.h │ │ ├── ih264d_function_selector.h │ │ ├── ih264d_function_selector_generic.c │ │ ├── ih264d_inter_pred.c │ │ ├── ih264d_inter_pred.h │ │ ├── ih264d_mb_utils.c │ │ ├── ih264d_mb_utils.h │ │ ├── ih264d_mem_request.h │ │ ├── ih264d_mvpred.c │ │ ├── ih264d_mvpred.h │ │ ├── ih264d_nal.c │ │ ├── ih264d_nal.h │ │ ├── ih264d_parse_bslice.c │ │ ├── ih264d_parse_cabac.c │ │ ├── ih264d_parse_cabac.h │ │ ├── ih264d_parse_cavlc.c │ │ ├── ih264d_parse_cavlc.h │ │ ├── ih264d_parse_headers.c │ │ ├── ih264d_parse_headers.h │ │ ├── ih264d_parse_islice.c │ │ ├── ih264d_parse_islice.h │ │ ├── ih264d_parse_mb_header.c │ │ ├── ih264d_parse_mb_header.h │ │ ├── ih264d_parse_pslice.c │ │ ├── ih264d_parse_slice.c │ │ ├── ih264d_parse_slice.h │ │ ├── ih264d_process_bslice.c │ │ ├── ih264d_process_bslice.h │ │ ├── ih264d_process_intra_mb.c │ │ ├── ih264d_process_intra_mb.h │ │ ├── ih264d_process_pslice.c │ │ ├── ih264d_process_pslice.h │ │ ├── ih264d_quant_scaling.c │ │ ├── ih264d_quant_scaling.h │ │ ├── ih264d_sei.c │ │ ├── ih264d_sei.h │ │ ├── ih264d_structs.h │ │ ├── ih264d_tables.c │ │ ├── ih264d_tables.h │ │ ├── ih264d_thread_compute_bs.c │ │ ├── ih264d_thread_compute_bs.h │ │ ├── ih264d_thread_parse_decode.c │ │ ├── ih264d_thread_parse_decode.h │ │ ├── ih264d_transfer_address.h │ │ ├── ih264d_utils.c │ │ ├── ih264d_utils.h │ │ ├── ih264d_vui.c │ │ ├── ih264d_vui.h │ │ ├── iv.h │ │ ├── ivd.h │ │ ├── mips/ │ │ │ └── ih264d_function_selector.c │ │ └── x86/ │ │ ├── ih264d_function_selector.c │ │ ├── ih264d_function_selector_sse42.c │ │ └── ih264d_function_selector_ssse3.c │ ├── vcpkg_overlay_ports/ │ │ ├── .gitkeep │ │ └── wxwidgets/ │ │ ├── example/ │ │ │ └── CMakeLists.txt │ │ ├── fix-libs-export.patch │ │ ├── fix-listctrl-layout.patch │ │ ├── fix-pcre2.patch │ │ ├── gtk3-link-libraries.patch │ │ ├── install-layout.patch │ │ ├── nanosvg-ext-depend.patch │ │ ├── portfile.cmake │ │ ├── relocatable-wx-config.patch │ │ ├── sdl2.patch │ │ ├── setup.h.in │ │ ├── usage │ │ ├── vcpkg-cmake-wrapper.cmake │ │ └── vcpkg.json │ ├── vcpkg_overlay_ports_linux/ │ │ ├── .gitkeep │ │ ├── cairo/ │ │ │ ├── portfile.cmake │ │ │ └── vcpkg.json │ │ ├── glm/ │ │ │ ├── portfile.cmake │ │ │ └── vcpkg.json │ │ ├── gtk3/ │ │ │ ├── portfile.cmake │ │ │ └── vcpkg.json │ │ └── libpng/ │ │ ├── portfile.cmake │ │ └── vcpkg.json │ ├── vcpkg_overlay_ports_mac/ │ │ ├── .gitkeep │ │ └── libusb/ │ │ ├── portfile.cmake │ │ ├── usage │ │ └── vcpkg.json │ └── vcpkg_overlay_ports_win/ │ └── .gitkeep ├── dist/ │ ├── linux/ │ │ ├── appimage.sh │ │ ├── info.cemu.Cemu.desktop │ │ └── info.cemu.Cemu.metainfo.xml │ ├── network_services.xml │ └── windows/ │ └── Cemu.manifest ├── src/ │ ├── CMakeLists.txt │ ├── Cafe/ │ │ ├── Account/ │ │ │ ├── Account.cpp │ │ │ ├── Account.h │ │ │ └── AccountError.h │ │ ├── CMakeLists.txt │ │ ├── CafeSystem.cpp │ │ ├── CafeSystem.h │ │ ├── Filesystem/ │ │ │ ├── FST/ │ │ │ │ ├── FST.cpp │ │ │ │ ├── FST.h │ │ │ │ ├── KeyCache.cpp │ │ │ │ ├── KeyCache.h │ │ │ │ └── fstUtil.h │ │ │ ├── WUD/ │ │ │ │ ├── wud.cpp │ │ │ │ └── wud.h │ │ │ ├── WUHB/ │ │ │ │ ├── RomFSStructs.h │ │ │ │ ├── WUHBReader.cpp │ │ │ │ └── WUHBReader.h │ │ │ ├── fsc.cpp │ │ │ ├── fsc.h │ │ │ ├── fscDeviceHostFS.cpp │ │ │ ├── fscDeviceHostFS.h │ │ │ ├── fscDeviceRedirect.cpp │ │ │ ├── fscDeviceWua.cpp │ │ │ ├── fscDeviceWud.cpp │ │ │ └── fscDeviceWuhb.cpp │ │ ├── GamePatch.cpp │ │ ├── GamePatch.h │ │ ├── GameProfile/ │ │ │ ├── GameProfile.cpp │ │ │ └── GameProfile.h │ │ ├── GraphicPack/ │ │ │ ├── GraphicPack2.cpp │ │ │ ├── GraphicPack2.h │ │ │ ├── GraphicPack2Patches.cpp │ │ │ ├── GraphicPack2Patches.h │ │ │ ├── GraphicPack2PatchesApply.cpp │ │ │ ├── GraphicPack2PatchesParser.cpp │ │ │ └── GraphicPackError.h │ │ ├── HW/ │ │ │ ├── ACR/ │ │ │ │ └── ACR.cpp │ │ │ ├── AI/ │ │ │ │ ├── AI.cpp │ │ │ │ └── AI.h │ │ │ ├── Common/ │ │ │ │ └── HwReg.h │ │ │ ├── Espresso/ │ │ │ │ ├── Const.h │ │ │ │ ├── Debugger/ │ │ │ │ │ ├── DebugSymbolStorage.cpp │ │ │ │ │ ├── DebugSymbolStorage.h │ │ │ │ │ ├── Debugger.cpp │ │ │ │ │ ├── Debugger.h │ │ │ │ │ ├── GDBBreakpoints.cpp │ │ │ │ │ ├── GDBBreakpoints.h │ │ │ │ │ ├── GDBStub.cpp │ │ │ │ │ └── GDBStub.h │ │ │ │ ├── EspressoISA.h │ │ │ │ ├── Interpreter/ │ │ │ │ │ ├── PPCInterpreterALU.hpp │ │ │ │ │ ├── PPCInterpreterFPU.cpp │ │ │ │ │ ├── PPCInterpreterHLE.cpp │ │ │ │ │ ├── PPCInterpreterHelper.h │ │ │ │ │ ├── PPCInterpreterImpl.cpp │ │ │ │ │ ├── PPCInterpreterInternal.h │ │ │ │ │ ├── PPCInterpreterLoadStore.hpp │ │ │ │ │ ├── PPCInterpreterMain.cpp │ │ │ │ │ ├── PPCInterpreterOPC.cpp │ │ │ │ │ ├── PPCInterpreterOPC.hpp │ │ │ │ │ ├── PPCInterpreterPS.cpp │ │ │ │ │ └── PPCInterpreterSPR.hpp │ │ │ │ ├── PPCCallback.h │ │ │ │ ├── PPCScheduler.cpp │ │ │ │ ├── PPCSchedulerLLE.cpp │ │ │ │ ├── PPCState.h │ │ │ │ ├── PPCTimer.cpp │ │ │ │ └── Recompiler/ │ │ │ │ ├── BackendAArch64/ │ │ │ │ │ ├── BackendAArch64.cpp │ │ │ │ │ └── BackendAArch64.h │ │ │ │ ├── BackendX64/ │ │ │ │ │ ├── BackendX64.cpp │ │ │ │ │ ├── BackendX64.h │ │ │ │ │ ├── BackendX64AVX.cpp │ │ │ │ │ ├── BackendX64BMI.cpp │ │ │ │ │ ├── BackendX64FPU.cpp │ │ │ │ │ ├── BackendX64Gen.cpp │ │ │ │ │ ├── BackendX64GenFPU.cpp │ │ │ │ │ ├── X64Emit.hpp │ │ │ │ │ └── x86Emitter.h │ │ │ │ ├── IML/ │ │ │ │ │ ├── IML.h │ │ │ │ │ ├── IMLAnalyzer.cpp │ │ │ │ │ ├── IMLDebug.cpp │ │ │ │ │ ├── IMLInstruction.cpp │ │ │ │ │ ├── IMLInstruction.h │ │ │ │ │ ├── IMLOptimizer.cpp │ │ │ │ │ ├── IMLRegisterAllocator.cpp │ │ │ │ │ ├── IMLRegisterAllocator.h │ │ │ │ │ ├── IMLRegisterAllocatorRanges.cpp │ │ │ │ │ ├── IMLRegisterAllocatorRanges.h │ │ │ │ │ ├── IMLSegment.cpp │ │ │ │ │ └── IMLSegment.h │ │ │ │ ├── PPCFunctionBoundaryTracker.h │ │ │ │ ├── PPCRecompiler.cpp │ │ │ │ ├── PPCRecompiler.h │ │ │ │ ├── PPCRecompilerIml.h │ │ │ │ ├── PPCRecompilerImlGen.cpp │ │ │ │ ├── PPCRecompilerImlGenFPU.cpp │ │ │ │ └── PPCRecompilerIntermediate.cpp │ │ │ ├── Latte/ │ │ │ │ ├── Common/ │ │ │ │ │ ├── RegisterSerializer.cpp │ │ │ │ │ ├── RegisterSerializer.h │ │ │ │ │ ├── ShaderSerializer.cpp │ │ │ │ │ └── ShaderSerializer.h │ │ │ │ ├── Core/ │ │ │ │ │ ├── FetchShader.cpp │ │ │ │ │ ├── FetchShader.h │ │ │ │ │ ├── Latte.h │ │ │ │ │ ├── LatteAsyncCommands.cpp │ │ │ │ │ ├── LatteAsyncCommands.h │ │ │ │ │ ├── LatteBufferCache.cpp │ │ │ │ │ ├── LatteBufferCache.h │ │ │ │ │ ├── LatteBufferData.cpp │ │ │ │ │ ├── LatteCachedFBO.h │ │ │ │ │ ├── LatteCommandProcessor.cpp │ │ │ │ │ ├── LatteConst.h │ │ │ │ │ ├── LatteDefaultShaders.cpp │ │ │ │ │ ├── LatteDefaultShaders.h │ │ │ │ │ ├── LatteDraw.h │ │ │ │ │ ├── LatteGSCopyShaderParser.cpp │ │ │ │ │ ├── LatteIndices.cpp │ │ │ │ │ ├── LatteIndices.h │ │ │ │ │ ├── LatteOverlay.cpp │ │ │ │ │ ├── LatteOverlay.h │ │ │ │ │ ├── LattePM4.h │ │ │ │ │ ├── LattePerformanceMonitor.cpp │ │ │ │ │ ├── LattePerformanceMonitor.h │ │ │ │ │ ├── LatteQuery.cpp │ │ │ │ │ ├── LatteQueryObject.h │ │ │ │ │ ├── LatteRenderTarget.cpp │ │ │ │ │ ├── LatteRingBuffer.cpp │ │ │ │ │ ├── LatteRingBuffer.h │ │ │ │ │ ├── LatteShader.cpp │ │ │ │ │ ├── LatteShader.h │ │ │ │ │ ├── LatteShaderAssembly.h │ │ │ │ │ ├── LatteShaderCache.cpp │ │ │ │ │ ├── LatteShaderCache.h │ │ │ │ │ ├── LatteShaderGL.cpp │ │ │ │ │ ├── LatteSoftware.cpp │ │ │ │ │ ├── LatteSoftware.h │ │ │ │ │ ├── LatteStreamoutGPU.cpp │ │ │ │ │ ├── LatteSurfaceCopy.cpp │ │ │ │ │ ├── LatteTexture.cpp │ │ │ │ │ ├── LatteTexture.h │ │ │ │ │ ├── LatteTextureCache.cpp │ │ │ │ │ ├── LatteTextureLegacy.cpp │ │ │ │ │ ├── LatteTextureLoader.cpp │ │ │ │ │ ├── LatteTextureLoader.h │ │ │ │ │ ├── LatteTextureReadback.cpp │ │ │ │ │ ├── LatteTextureReadbackInfo.h │ │ │ │ │ ├── LatteTextureView.cpp │ │ │ │ │ ├── LatteTextureView.h │ │ │ │ │ ├── LatteThread.cpp │ │ │ │ │ ├── LatteTiming.cpp │ │ │ │ │ └── LatteTiming.h │ │ │ │ ├── ISA/ │ │ │ │ │ ├── LatteInstructions.h │ │ │ │ │ ├── LatteReg.h │ │ │ │ │ └── RegDefines.h │ │ │ │ ├── LatteAddrLib/ │ │ │ │ │ ├── AddrLibFastDecode.h │ │ │ │ │ ├── LatteAddrLib.cpp │ │ │ │ │ ├── LatteAddrLib.h │ │ │ │ │ └── LatteAddrLib_Coord.cpp │ │ │ │ ├── LegacyShaderDecompiler/ │ │ │ │ │ ├── LatteDecompiler.cpp │ │ │ │ │ ├── LatteDecompiler.h │ │ │ │ │ ├── LatteDecompilerAnalyzer.cpp │ │ │ │ │ ├── LatteDecompilerEmitGLSL.cpp │ │ │ │ │ ├── LatteDecompilerEmitGLSLAttrDecoder.cpp │ │ │ │ │ ├── LatteDecompilerEmitGLSLHeader.hpp │ │ │ │ │ ├── LatteDecompilerEmitMSL.cpp │ │ │ │ │ ├── LatteDecompilerEmitMSLAttrDecoder.cpp │ │ │ │ │ ├── LatteDecompilerEmitMSLHeader.hpp │ │ │ │ │ ├── LatteDecompilerInstructions.h │ │ │ │ │ ├── LatteDecompilerInternal.h │ │ │ │ │ └── LatteDecompilerRegisterDataTypeTracker.cpp │ │ │ │ ├── Renderer/ │ │ │ │ │ ├── Metal/ │ │ │ │ │ │ ├── CachedFBOMtl.cpp │ │ │ │ │ │ ├── CachedFBOMtl.h │ │ │ │ │ │ ├── LatteTextureMtl.cpp │ │ │ │ │ │ ├── LatteTextureMtl.h │ │ │ │ │ │ ├── LatteTextureReadbackMtl.cpp │ │ │ │ │ │ ├── LatteTextureReadbackMtl.h │ │ │ │ │ │ ├── LatteTextureViewMtl.cpp │ │ │ │ │ │ ├── LatteTextureViewMtl.h │ │ │ │ │ │ ├── LatteToMtl.cpp │ │ │ │ │ │ ├── LatteToMtl.h │ │ │ │ │ │ ├── MetalAttachmentsInfo.cpp │ │ │ │ │ │ ├── MetalAttachmentsInfo.h │ │ │ │ │ │ ├── MetalBufferAllocator.cpp │ │ │ │ │ │ ├── MetalBufferAllocator.h │ │ │ │ │ │ ├── MetalCommon.h │ │ │ │ │ │ ├── MetalCppImpl.cpp │ │ │ │ │ │ ├── MetalDepthStencilCache.cpp │ │ │ │ │ │ ├── MetalDepthStencilCache.h │ │ │ │ │ │ ├── MetalLayer.h │ │ │ │ │ │ ├── MetalLayer.mm │ │ │ │ │ │ ├── MetalLayerHandle.cpp │ │ │ │ │ │ ├── MetalLayerHandle.h │ │ │ │ │ │ ├── MetalMemoryManager.cpp │ │ │ │ │ │ ├── MetalMemoryManager.h │ │ │ │ │ │ ├── MetalOutputShaderCache.cpp │ │ │ │ │ │ ├── MetalOutputShaderCache.h │ │ │ │ │ │ ├── MetalPerformanceMonitor.h │ │ │ │ │ │ ├── MetalPipelineCache.cpp │ │ │ │ │ │ ├── MetalPipelineCache.h │ │ │ │ │ │ ├── MetalPipelineCompiler.cpp │ │ │ │ │ │ ├── MetalPipelineCompiler.h │ │ │ │ │ │ ├── MetalQuery.cpp │ │ │ │ │ │ ├── MetalQuery.h │ │ │ │ │ │ ├── MetalRenderer.cpp │ │ │ │ │ │ ├── MetalRenderer.h │ │ │ │ │ │ ├── MetalSamplerCache.cpp │ │ │ │ │ │ ├── MetalSamplerCache.h │ │ │ │ │ │ ├── MetalVoidVertexPipeline.cpp │ │ │ │ │ │ ├── MetalVoidVertexPipeline.h │ │ │ │ │ │ ├── RendererShaderMtl.cpp │ │ │ │ │ │ ├── RendererShaderMtl.h │ │ │ │ │ │ └── UtilityShaderSource.h │ │ │ │ │ ├── MetalView.h │ │ │ │ │ ├── MetalView.mm │ │ │ │ │ ├── OpenGL/ │ │ │ │ │ │ ├── CachedFBOGL.h │ │ │ │ │ │ ├── LatteTextureGL.cpp │ │ │ │ │ │ ├── LatteTextureGL.h │ │ │ │ │ │ ├── LatteTextureViewGL.cpp │ │ │ │ │ │ ├── LatteTextureViewGL.h │ │ │ │ │ │ ├── OpenGLQuery.cpp │ │ │ │ │ │ ├── OpenGLRenderer.cpp │ │ │ │ │ │ ├── OpenGLRenderer.h │ │ │ │ │ │ ├── OpenGLRendererCore.cpp │ │ │ │ │ │ ├── OpenGLRendererStreamout.cpp │ │ │ │ │ │ ├── OpenGLRendererUniformData.cpp │ │ │ │ │ │ ├── OpenGLSurfaceCopy.cpp │ │ │ │ │ │ ├── OpenGLTextureReadback.h │ │ │ │ │ │ ├── RendererShaderGL.cpp │ │ │ │ │ │ ├── RendererShaderGL.h │ │ │ │ │ │ └── TextureReadbackGL.cpp │ │ │ │ │ ├── Renderer.cpp │ │ │ │ │ ├── Renderer.h │ │ │ │ │ ├── RendererOuputShader.cpp │ │ │ │ │ ├── RendererOuputShader.h │ │ │ │ │ ├── RendererShader.cpp │ │ │ │ │ ├── RendererShader.h │ │ │ │ │ └── Vulkan/ │ │ │ │ │ ├── CachedFBOVk.cpp │ │ │ │ │ ├── CachedFBOVk.h │ │ │ │ │ ├── CocoaSurface.h │ │ │ │ │ ├── CocoaSurface.mm │ │ │ │ │ ├── LatteTextureViewVk.cpp │ │ │ │ │ ├── LatteTextureViewVk.h │ │ │ │ │ ├── LatteTextureVk.cpp │ │ │ │ │ ├── LatteTextureVk.h │ │ │ │ │ ├── RendererShaderVk.cpp │ │ │ │ │ ├── RendererShaderVk.h │ │ │ │ │ ├── SwapchainInfoVk.cpp │ │ │ │ │ ├── SwapchainInfoVk.h │ │ │ │ │ ├── TextureReadbackVk.cpp │ │ │ │ │ ├── VKRBase.h │ │ │ │ │ ├── VKRMemoryManager.cpp │ │ │ │ │ ├── VKRMemoryManager.h │ │ │ │ │ ├── VKRPipelineInfo.cpp │ │ │ │ │ ├── VsyncDriver.cpp │ │ │ │ │ ├── VsyncDriver.h │ │ │ │ │ ├── VulkanAPI.cpp │ │ │ │ │ ├── VulkanAPI.h │ │ │ │ │ ├── VulkanPipelineCompiler.cpp │ │ │ │ │ ├── VulkanPipelineCompiler.h │ │ │ │ │ ├── VulkanPipelineStableCache.cpp │ │ │ │ │ ├── VulkanPipelineStableCache.h │ │ │ │ │ ├── VulkanQuery.cpp │ │ │ │ │ ├── VulkanRenderer.cpp │ │ │ │ │ ├── VulkanRenderer.h │ │ │ │ │ ├── VulkanRendererCore.cpp │ │ │ │ │ ├── VulkanSurfaceCopy.cpp │ │ │ │ │ └── VulkanTextureReadback.h │ │ │ │ ├── ShaderInfo/ │ │ │ │ │ ├── ShaderDescription.cpp │ │ │ │ │ ├── ShaderInfo.h │ │ │ │ │ └── ShaderInstanceInfo.cpp │ │ │ │ └── Transcompiler/ │ │ │ │ ├── LatteTC.cpp │ │ │ │ ├── LatteTC.h │ │ │ │ └── LatteTCGenIR.cpp │ │ │ ├── MMU/ │ │ │ │ ├── MMU.cpp │ │ │ │ └── MMU.h │ │ │ ├── SI/ │ │ │ │ ├── SI.cpp │ │ │ │ └── si.h │ │ │ └── VI/ │ │ │ └── VI.cpp │ │ ├── IOSU/ │ │ │ ├── ODM/ │ │ │ │ ├── iosu_odm.cpp │ │ │ │ └── iosu_odm.h │ │ │ ├── PDM/ │ │ │ │ ├── iosu_pdm.cpp │ │ │ │ └── iosu_pdm.h │ │ │ ├── ccr_nfc/ │ │ │ │ ├── iosu_ccr_nfc.cpp │ │ │ │ └── iosu_ccr_nfc.h │ │ │ ├── fsa/ │ │ │ │ ├── fsa_types.h │ │ │ │ ├── iosu_fsa.cpp │ │ │ │ └── iosu_fsa.h │ │ │ ├── iosu_ipc_common.h │ │ │ ├── iosu_types_common.h │ │ │ ├── kernel/ │ │ │ │ ├── iosu_kernel.cpp │ │ │ │ └── iosu_kernel.h │ │ │ ├── legacy/ │ │ │ │ ├── iosu_acp.cpp │ │ │ │ ├── iosu_acp.h │ │ │ │ ├── iosu_act.cpp │ │ │ │ ├── iosu_act.h │ │ │ │ ├── iosu_crypto.cpp │ │ │ │ ├── iosu_crypto.h │ │ │ │ ├── iosu_fpd.cpp │ │ │ │ ├── iosu_fpd.h │ │ │ │ ├── iosu_ioctl.cpp │ │ │ │ ├── iosu_ioctl.h │ │ │ │ ├── iosu_mcp.cpp │ │ │ │ ├── iosu_mcp.h │ │ │ │ ├── iosu_nim.cpp │ │ │ │ └── iosu_nim.h │ │ │ └── nn/ │ │ │ ├── boss/ │ │ │ │ ├── boss_common.cpp │ │ │ │ ├── boss_common.h │ │ │ │ ├── boss_service.cpp │ │ │ │ └── boss_service.h │ │ │ ├── iosu_nn_service.cpp │ │ │ └── iosu_nn_service.h │ │ ├── OS/ │ │ │ ├── RPL/ │ │ │ │ ├── COSModule.cpp │ │ │ │ ├── COSModule.h │ │ │ │ ├── elf.cpp │ │ │ │ ├── rpl.cpp │ │ │ │ ├── rpl.h │ │ │ │ ├── rpl_debug_symbols.cpp │ │ │ │ ├── rpl_debug_symbols.h │ │ │ │ ├── rpl_structs.h │ │ │ │ ├── rpl_symbol_storage.cpp │ │ │ │ └── rpl_symbol_storage.h │ │ │ ├── common/ │ │ │ │ ├── OSCommon.cpp │ │ │ │ ├── OSCommon.h │ │ │ │ ├── OSUtil.h │ │ │ │ └── PPCConcurrentQueue.h │ │ │ └── libs/ │ │ │ ├── TCL/ │ │ │ │ ├── TCL.cpp │ │ │ │ └── TCL.h │ │ │ ├── avm/ │ │ │ │ ├── avm.cpp │ │ │ │ └── avm.h │ │ │ ├── camera/ │ │ │ │ ├── camera.cpp │ │ │ │ └── camera.h │ │ │ ├── coreinit/ │ │ │ │ ├── coreinit.cpp │ │ │ │ ├── coreinit.h │ │ │ │ ├── coreinit_Alarm.cpp │ │ │ │ ├── coreinit_Alarm.h │ │ │ │ ├── coreinit_Atomic.cpp │ │ │ │ ├── coreinit_Atomic.h │ │ │ │ ├── coreinit_BSP.cpp │ │ │ │ ├── coreinit_BSP.h │ │ │ │ ├── coreinit_Callbacks.cpp │ │ │ │ ├── coreinit_CodeGen.cpp │ │ │ │ ├── coreinit_CodeGen.h │ │ │ │ ├── coreinit_Coroutine.cpp │ │ │ │ ├── coreinit_Coroutine.h │ │ │ │ ├── coreinit_DynLoad.cpp │ │ │ │ ├── coreinit_DynLoad.h │ │ │ │ ├── coreinit_FG.cpp │ │ │ │ ├── coreinit_FG.h │ │ │ │ ├── coreinit_FS.cpp │ │ │ │ ├── coreinit_FS.h │ │ │ │ ├── coreinit_GHS.cpp │ │ │ │ ├── coreinit_GHS.h │ │ │ │ ├── coreinit_HWInterface.cpp │ │ │ │ ├── coreinit_HWInterface.h │ │ │ │ ├── coreinit_IM.cpp │ │ │ │ ├── coreinit_IM.h │ │ │ │ ├── coreinit_IOS.cpp │ │ │ │ ├── coreinit_IOS.h │ │ │ │ ├── coreinit_IPC.cpp │ │ │ │ ├── coreinit_IPC.h │ │ │ │ ├── coreinit_IPCBuf.cpp │ │ │ │ ├── coreinit_IPCBuf.h │ │ │ │ ├── coreinit_Init.cpp │ │ │ │ ├── coreinit_LockedCache.cpp │ │ │ │ ├── coreinit_LockedCache.h │ │ │ │ ├── coreinit_MCP.cpp │ │ │ │ ├── coreinit_MCP.h │ │ │ │ ├── coreinit_MEM.cpp │ │ │ │ ├── coreinit_MEM.h │ │ │ │ ├── coreinit_MEM_BlockHeap.cpp │ │ │ │ ├── coreinit_MEM_BlockHeap.h │ │ │ │ ├── coreinit_MEM_ExpHeap.cpp │ │ │ │ ├── coreinit_MEM_ExpHeap.h │ │ │ │ ├── coreinit_MEM_FrmHeap.cpp │ │ │ │ ├── coreinit_MEM_FrmHeap.h │ │ │ │ ├── coreinit_MEM_UnitHeap.cpp │ │ │ │ ├── coreinit_MEM_UnitHeap.h │ │ │ │ ├── coreinit_MPQueue.cpp │ │ │ │ ├── coreinit_MPQueue.h │ │ │ │ ├── coreinit_Memory.cpp │ │ │ │ ├── coreinit_Memory.h │ │ │ │ ├── coreinit_MemoryMapping.cpp │ │ │ │ ├── coreinit_MemoryMapping.h │ │ │ │ ├── coreinit_MessageQueue.cpp │ │ │ │ ├── coreinit_MessageQueue.h │ │ │ │ ├── coreinit_Misc.cpp │ │ │ │ ├── coreinit_Misc.h │ │ │ │ ├── coreinit_OSScreen.cpp │ │ │ │ ├── coreinit_OSScreen.h │ │ │ │ ├── coreinit_OSScreen_font.h │ │ │ │ ├── coreinit_OverlayArena.cpp │ │ │ │ ├── coreinit_OverlayArena.h │ │ │ │ ├── coreinit_Scheduler.cpp │ │ │ │ ├── coreinit_Scheduler.h │ │ │ │ ├── coreinit_Spinlock.cpp │ │ │ │ ├── coreinit_Spinlock.h │ │ │ │ ├── coreinit_Synchronization.cpp │ │ │ │ ├── coreinit_SysHeap.cpp │ │ │ │ ├── coreinit_SysHeap.h │ │ │ │ ├── coreinit_SystemInfo.cpp │ │ │ │ ├── coreinit_SystemInfo.h │ │ │ │ ├── coreinit_Thread.cpp │ │ │ │ ├── coreinit_Thread.h │ │ │ │ ├── coreinit_ThreadQueue.cpp │ │ │ │ ├── coreinit_Time.cpp │ │ │ │ └── coreinit_Time.h │ │ │ ├── dmae/ │ │ │ │ ├── dmae.cpp │ │ │ │ └── dmae.h │ │ │ ├── drmapp/ │ │ │ │ ├── drmapp.cpp │ │ │ │ └── drmapp.h │ │ │ ├── erreula/ │ │ │ │ ├── erreula.cpp │ │ │ │ └── erreula.h │ │ │ ├── gx2/ │ │ │ │ ├── GX2.cpp │ │ │ │ ├── GX2.h │ │ │ │ ├── GX2_AddrTest.cpp │ │ │ │ ├── GX2_Blit.cpp │ │ │ │ ├── GX2_Blit.h │ │ │ │ ├── GX2_Command.cpp │ │ │ │ ├── GX2_Command.h │ │ │ │ ├── GX2_ContextState.cpp │ │ │ │ ├── GX2_Draw.cpp │ │ │ │ ├── GX2_Draw.h │ │ │ │ ├── GX2_Event.cpp │ │ │ │ ├── GX2_Event.h │ │ │ │ ├── GX2_Memory.cpp │ │ │ │ ├── GX2_Memory.h │ │ │ │ ├── GX2_Misc.cpp │ │ │ │ ├── GX2_Misc.h │ │ │ │ ├── GX2_Query.cpp │ │ │ │ ├── GX2_Query.h │ │ │ │ ├── GX2_RenderTarget.cpp │ │ │ │ ├── GX2_Resource.cpp │ │ │ │ ├── GX2_Resource.h │ │ │ │ ├── GX2_Shader.cpp │ │ │ │ ├── GX2_Shader.h │ │ │ │ ├── GX2_State.cpp │ │ │ │ ├── GX2_State.h │ │ │ │ ├── GX2_Streamout.cpp │ │ │ │ ├── GX2_Streamout.h │ │ │ │ ├── GX2_Surface.cpp │ │ │ │ ├── GX2_Surface.h │ │ │ │ ├── GX2_Surface_Copy.cpp │ │ │ │ ├── GX2_Surface_Copy.h │ │ │ │ ├── GX2_Texture.cpp │ │ │ │ ├── GX2_Texture.h │ │ │ │ ├── GX2_TilingAperture.cpp │ │ │ │ └── GX2_shader_legacy.cpp │ │ │ ├── h264_avc/ │ │ │ │ ├── H264Dec.cpp │ │ │ │ ├── H264DecBackendAVC.cpp │ │ │ │ ├── H264DecInternal.h │ │ │ │ ├── h264dec.h │ │ │ │ └── parser/ │ │ │ │ ├── H264Parser.cpp │ │ │ │ └── H264Parser.h │ │ │ ├── mic/ │ │ │ │ ├── mic.cpp │ │ │ │ └── mic.h │ │ │ ├── nfc/ │ │ │ │ ├── TLV.cpp │ │ │ │ ├── TLV.h │ │ │ │ ├── TagV0.cpp │ │ │ │ ├── TagV0.h │ │ │ │ ├── ndef.cpp │ │ │ │ ├── ndef.h │ │ │ │ ├── nfc.cpp │ │ │ │ ├── nfc.h │ │ │ │ ├── stream.cpp │ │ │ │ └── stream.h │ │ │ ├── nlibcurl/ │ │ │ │ ├── nlibcurl.cpp │ │ │ │ ├── nlibcurl.h │ │ │ │ └── nlibcurlDebug.hpp │ │ │ ├── nlibnss/ │ │ │ │ ├── nlibnss.cpp │ │ │ │ └── nlibnss.h │ │ │ ├── nn_ac/ │ │ │ │ ├── nn_ac.cpp │ │ │ │ └── nn_ac.h │ │ │ ├── nn_acp/ │ │ │ │ ├── nn_acp.cpp │ │ │ │ └── nn_acp.h │ │ │ ├── nn_act/ │ │ │ │ ├── nn_act.cpp │ │ │ │ └── nn_act.h │ │ │ ├── nn_aoc/ │ │ │ │ ├── nn_aoc.cpp │ │ │ │ └── nn_aoc.h │ │ │ ├── nn_boss/ │ │ │ │ ├── nn_boss.cpp │ │ │ │ └── nn_boss.h │ │ │ ├── nn_ccr/ │ │ │ │ ├── nn_ccr.cpp │ │ │ │ └── nn_ccr.h │ │ │ ├── nn_client_service.h │ │ │ ├── nn_cmpt/ │ │ │ │ ├── nn_cmpt.cpp │ │ │ │ └── nn_cmpt.h │ │ │ ├── nn_common.h │ │ │ ├── nn_ec/ │ │ │ │ ├── nn_ec.cpp │ │ │ │ └── nn_ec.h │ │ │ ├── nn_fp/ │ │ │ │ ├── nn_fp.cpp │ │ │ │ └── nn_fp.h │ │ │ ├── nn_idbe/ │ │ │ │ ├── nn_idbe.cpp │ │ │ │ └── nn_idbe.h │ │ │ ├── nn_ndm/ │ │ │ │ ├── nn_ndm.cpp │ │ │ │ └── nn_ndm.h │ │ │ ├── nn_nfp/ │ │ │ │ ├── AmiiboCrypto.h │ │ │ │ ├── nn_nfp.cpp │ │ │ │ └── nn_nfp.h │ │ │ ├── nn_nim/ │ │ │ │ ├── nn_nim.cpp │ │ │ │ └── nn_nim.h │ │ │ ├── nn_olv/ │ │ │ │ ├── nn_olv.cpp │ │ │ │ ├── nn_olv.h │ │ │ │ ├── nn_olv_Common.cpp │ │ │ │ ├── nn_olv_Common.h │ │ │ │ ├── nn_olv_DownloadCommunityTypes.cpp │ │ │ │ ├── nn_olv_DownloadCommunityTypes.h │ │ │ │ ├── nn_olv_InitializeTypes.cpp │ │ │ │ ├── nn_olv_InitializeTypes.h │ │ │ │ ├── nn_olv_OfflineDB.cpp │ │ │ │ ├── nn_olv_OfflineDB.h │ │ │ │ ├── nn_olv_PostTypes.cpp │ │ │ │ ├── nn_olv_PostTypes.h │ │ │ │ ├── nn_olv_UploadCommunityTypes.cpp │ │ │ │ ├── nn_olv_UploadCommunityTypes.h │ │ │ │ ├── nn_olv_UploadFavoriteTypes.cpp │ │ │ │ └── nn_olv_UploadFavoriteTypes.h │ │ │ ├── nn_pdm/ │ │ │ │ ├── nn_pdm.cpp │ │ │ │ └── nn_pdm.h │ │ │ ├── nn_save/ │ │ │ │ ├── nn_save.cpp │ │ │ │ └── nn_save.h │ │ │ ├── nn_sl/ │ │ │ │ ├── nn_sl.cpp │ │ │ │ └── nn_sl.h │ │ │ ├── nn_spm/ │ │ │ │ ├── nn_spm.cpp │ │ │ │ └── nn_spm.h │ │ │ ├── nn_temp/ │ │ │ │ ├── nn_temp.cpp │ │ │ │ └── nn_temp.h │ │ │ ├── nn_uds/ │ │ │ │ ├── nn_uds.cpp │ │ │ │ └── nn_uds.h │ │ │ ├── nsyshid/ │ │ │ │ ├── AttachDefaultBackends.cpp │ │ │ │ ├── Backend.h │ │ │ │ ├── BackendEmulated.cpp │ │ │ │ ├── BackendEmulated.h │ │ │ │ ├── BackendLibusb.cpp │ │ │ │ ├── BackendLibusb.h │ │ │ │ ├── Dimensions.cpp │ │ │ │ ├── Dimensions.h │ │ │ │ ├── Infinity.cpp │ │ │ │ ├── Infinity.h │ │ │ │ ├── Skylander.cpp │ │ │ │ ├── Skylander.h │ │ │ │ ├── SkylanderXbox360.cpp │ │ │ │ ├── SkylanderXbox360.h │ │ │ │ ├── Whitelist.cpp │ │ │ │ ├── Whitelist.h │ │ │ │ ├── g721/ │ │ │ │ │ ├── g721.cpp │ │ │ │ │ └── g721.h │ │ │ │ ├── nsyshid.cpp │ │ │ │ └── nsyshid.h │ │ │ ├── nsyskbd/ │ │ │ │ ├── nsyskbd.cpp │ │ │ │ └── nsyskbd.h │ │ │ ├── nsysnet/ │ │ │ │ ├── nsysnet.cpp │ │ │ │ └── nsysnet.h │ │ │ ├── ntag/ │ │ │ │ ├── ntag.cpp │ │ │ │ └── ntag.h │ │ │ ├── padscore/ │ │ │ │ ├── padscore.cpp │ │ │ │ └── padscore.h │ │ │ ├── proc_ui/ │ │ │ │ ├── proc_ui.cpp │ │ │ │ └── proc_ui.h │ │ │ ├── snd_core/ │ │ │ │ ├── ax.h │ │ │ │ ├── ax_aux.cpp │ │ │ │ ├── ax_exports.cpp │ │ │ │ ├── ax_internal.h │ │ │ │ ├── ax_ist.cpp │ │ │ │ ├── ax_mix.cpp │ │ │ │ ├── ax_multivoice.cpp │ │ │ │ ├── ax_out.cpp │ │ │ │ └── ax_voice.cpp │ │ │ ├── snd_user/ │ │ │ │ ├── snd_user.cpp │ │ │ │ └── snd_user.h │ │ │ ├── swkbd/ │ │ │ │ ├── swkbd.cpp │ │ │ │ └── swkbd.h │ │ │ ├── sysapp/ │ │ │ │ ├── sysapp.cpp │ │ │ │ └── sysapp.h │ │ │ ├── vpad/ │ │ │ │ ├── vpad.cpp │ │ │ │ └── vpad.h │ │ │ └── zlib125/ │ │ │ ├── zlib125.cpp │ │ │ └── zlib125.h │ │ └── TitleList/ │ │ ├── AppType.h │ │ ├── GameInfo.h │ │ ├── ParsedMetaXml.h │ │ ├── SaveInfo.cpp │ │ ├── SaveInfo.h │ │ ├── SaveList.cpp │ │ ├── SaveList.h │ │ ├── TitleId.h │ │ ├── TitleInfo.cpp │ │ ├── TitleInfo.h │ │ ├── TitleList.cpp │ │ └── TitleList.h │ ├── Cemu/ │ │ ├── CMakeLists.txt │ │ ├── DiscordPresence/ │ │ │ ├── DiscordPresence.cpp │ │ │ ├── DiscordPresence.h │ │ │ ├── DiscordRPCLite.cpp │ │ │ └── DiscordRPCLite.h │ │ ├── ExpressionParser/ │ │ │ ├── ExpressionParser.cpp │ │ │ └── ExpressionParser.h │ │ ├── FileCache/ │ │ │ ├── FileCache.cpp │ │ │ └── FileCache.h │ │ ├── Logging/ │ │ │ ├── CemuDebugLogging.h │ │ │ ├── CemuLogging.cpp │ │ │ └── CemuLogging.h │ │ ├── PPCAssembler/ │ │ │ ├── ppcAssembler.cpp │ │ │ └── ppcAssembler.h │ │ ├── Tools/ │ │ │ └── DownloadManager/ │ │ │ ├── DownloadManager.cpp │ │ │ └── DownloadManager.h │ │ ├── napi/ │ │ │ ├── napi.h │ │ │ ├── napi_act.cpp │ │ │ ├── napi_ec.cpp │ │ │ ├── napi_helper.cpp │ │ │ ├── napi_helper.h │ │ │ ├── napi_idbe.cpp │ │ │ └── napi_version.cpp │ │ ├── ncrypto/ │ │ │ ├── ncrypto.cpp │ │ │ └── ncrypto.h │ │ └── nex/ │ │ ├── nex.cpp │ │ ├── nex.h │ │ ├── nexFriends.cpp │ │ ├── nexFriends.h │ │ ├── nexThread.cpp │ │ ├── nexThread.h │ │ ├── nexTypes.h │ │ ├── prudp.cpp │ │ └── prudp.h │ ├── Common/ │ │ ├── CMakeLists.txt │ │ ├── CafeString.h │ │ ├── ExceptionHandler/ │ │ │ ├── ELFSymbolTable.cpp │ │ │ ├── ELFSymbolTable.h │ │ │ ├── ExceptionHandler.cpp │ │ │ ├── ExceptionHandler.h │ │ │ ├── ExceptionHandler_posix.cpp │ │ │ └── ExceptionHandler_win32.cpp │ │ ├── FileStream.h │ │ ├── GLInclude/ │ │ │ ├── GLInclude.h │ │ │ ├── egl.h │ │ │ ├── glFunctions.h │ │ │ ├── glext.h │ │ │ ├── glxext.h │ │ │ ├── khrplatform.h │ │ │ └── wglext.h │ │ ├── MemPtr.h │ │ ├── StackAllocator.h │ │ ├── SysAllocator.cpp │ │ ├── SysAllocator.h │ │ ├── betype.h │ │ ├── cpu_features.cpp │ │ ├── cpu_features.h │ │ ├── enumFlags.h │ │ ├── platform.h │ │ ├── precompiled.cpp │ │ ├── precompiled.h │ │ ├── socket.h │ │ ├── unix/ │ │ │ ├── FileStream_unix.cpp │ │ │ ├── FileStream_unix.h │ │ │ ├── date.h │ │ │ ├── fast_float.h │ │ │ ├── platform.cpp │ │ │ └── platform.h │ │ ├── version.h │ │ └── windows/ │ │ ├── FileStream_win32.cpp │ │ ├── FileStream_win32.h │ │ ├── platform.cpp │ │ └── platform.h │ ├── audio/ │ │ ├── CMakeLists.txt │ │ ├── CubebAPI.cpp │ │ ├── CubebAPI.h │ │ ├── CubebInputAPI.cpp │ │ ├── CubebInputAPI.h │ │ ├── DirectSoundAPI.cpp │ │ ├── DirectSoundAPI.h │ │ ├── IAudioAPI.cpp │ │ ├── IAudioAPI.h │ │ ├── IAudioInputAPI.cpp │ │ ├── IAudioInputAPI.h │ │ ├── XAudio27API.cpp │ │ ├── XAudio27API.h │ │ ├── XAudio2API.cpp │ │ ├── XAudio2API.h │ │ └── xaudio2_7/ │ │ ├── audiodefs.h │ │ ├── comdecl.h │ │ ├── dxsdkver.h │ │ └── xma2defs.h │ ├── config/ │ │ ├── ActiveSettings.cpp │ │ ├── ActiveSettings.h │ │ ├── CMakeLists.txt │ │ ├── CemuConfig.cpp │ │ ├── CemuConfig.h │ │ ├── ConfigValue.h │ │ ├── LaunchSettings.cpp │ │ ├── LaunchSettings.h │ │ ├── NetworkSettings.cpp │ │ ├── NetworkSettings.h │ │ └── XMLConfig.h │ ├── gui/ │ │ ├── CMakeLists.txt │ │ ├── interface/ │ │ │ └── WindowSystem.h │ │ └── wxgui/ │ │ ├── AudioDebuggerWindow.cpp │ │ ├── AudioDebuggerWindow.h │ │ ├── CMakeLists.txt │ │ ├── CemuApp.cpp │ │ ├── CemuApp.h │ │ ├── CemuUpdateWindow.cpp │ │ ├── CemuUpdateWindow.h │ │ ├── ChecksumTool.cpp │ │ ├── ChecksumTool.h │ │ ├── DownloadGraphicPacksWindow.cpp │ │ ├── DownloadGraphicPacksWindow.h │ │ ├── EmulatedUSBDevices/ │ │ │ ├── EmulatedUSBDeviceFrame.cpp │ │ │ └── EmulatedUSBDeviceFrame.h │ │ ├── GameProfileWindow.cpp │ │ ├── GameProfileWindow.h │ │ ├── GameUpdateWindow.cpp │ │ ├── GameUpdateWindow.h │ │ ├── GeneralSettings2.cpp │ │ ├── GeneralSettings2.h │ │ ├── GettingStartedDialog.cpp │ │ ├── GettingStartedDialog.h │ │ ├── GraphicPacksWindow2.cpp │ │ ├── GraphicPacksWindow2.h │ │ ├── LoggingWindow.cpp │ │ ├── LoggingWindow.h │ │ ├── MainWindow.cpp │ │ ├── MainWindow.h │ │ ├── MemorySearcherTool.cpp │ │ ├── MemorySearcherTool.h │ │ ├── PadViewFrame.cpp │ │ ├── PadViewFrame.h │ │ ├── TitleManager.cpp │ │ ├── TitleManager.h │ │ ├── canvas/ │ │ │ ├── IRenderCanvas.h │ │ │ ├── MetalCanvas.cpp │ │ │ ├── MetalCanvas.h │ │ │ ├── OpenGLCanvas.cpp │ │ │ ├── OpenGLCanvas.h │ │ │ ├── VulkanCanvas.cpp │ │ │ └── VulkanCanvas.h │ │ ├── components/ │ │ │ ├── TextList.cpp │ │ │ ├── TextList.h │ │ │ ├── wxDownloadManagerList.cpp │ │ │ ├── wxDownloadManagerList.h │ │ │ ├── wxGameList.cpp │ │ │ ├── wxGameList.h │ │ │ ├── wxInputDraw.cpp │ │ │ ├── wxInputDraw.h │ │ │ ├── wxLogCtrl.cpp │ │ │ ├── wxLogCtrl.h │ │ │ ├── wxProgressDialogManager.h │ │ │ ├── wxTitleManagerList.cpp │ │ │ └── wxTitleManagerList.h │ │ ├── debugger/ │ │ │ ├── BreakpointWindow.cpp │ │ │ ├── BreakpointWindow.h │ │ │ ├── DebuggerWindow2.cpp │ │ │ ├── DebuggerWindow2.h │ │ │ ├── DisasmCtrl.cpp │ │ │ ├── DisasmCtrl.h │ │ │ ├── DumpCtrl.cpp │ │ │ ├── DumpCtrl.h │ │ │ ├── DumpWindow.cpp │ │ │ ├── DumpWindow.h │ │ │ ├── ModuleWindow.cpp │ │ │ ├── ModuleWindow.h │ │ │ ├── RegisterWindow.cpp │ │ │ ├── RegisterWindow.h │ │ │ ├── SymbolCtrl.cpp │ │ │ ├── SymbolCtrl.h │ │ │ ├── SymbolWindow.cpp │ │ │ └── SymbolWindow.h │ │ ├── dialogs/ │ │ │ ├── CreateAccount/ │ │ │ │ ├── wxCreateAccountDialog.cpp │ │ │ │ └── wxCreateAccountDialog.h │ │ │ └── SaveImport/ │ │ │ ├── SaveImportWindow.cpp │ │ │ ├── SaveImportWindow.h │ │ │ ├── SaveTransfer.cpp │ │ │ └── SaveTransfer.h │ │ ├── helpers/ │ │ │ ├── wxControlObject.h │ │ │ ├── wxCustomData.h │ │ │ ├── wxCustomEvents.cpp │ │ │ ├── wxCustomEvents.h │ │ │ ├── wxHelpers.cpp │ │ │ ├── wxHelpers.h │ │ │ ├── wxLogEvent.h │ │ │ ├── wxWayland.cpp │ │ │ └── wxWayland.h │ │ ├── input/ │ │ │ ├── HotkeySettings.cpp │ │ │ ├── HotkeySettings.h │ │ │ ├── InputAPIAddWindow.cpp │ │ │ ├── InputAPIAddWindow.h │ │ │ ├── InputSettings2.cpp │ │ │ ├── InputSettings2.h │ │ │ ├── PairingDialog.cpp │ │ │ ├── PairingDialog.h │ │ │ ├── panels/ │ │ │ │ ├── ClassicControllerInputPanel.cpp │ │ │ │ ├── ClassicControllerInputPanel.h │ │ │ │ ├── InputPanel.cpp │ │ │ │ ├── InputPanel.h │ │ │ │ ├── ProControllerInputPanel.cpp │ │ │ │ ├── ProControllerInputPanel.h │ │ │ │ ├── VPADInputPanel.cpp │ │ │ │ ├── VPADInputPanel.h │ │ │ │ ├── WiimoteInputPanel.cpp │ │ │ │ └── WiimoteInputPanel.h │ │ │ └── settings/ │ │ │ ├── DefaultControllerSettings.cpp │ │ │ ├── DefaultControllerSettings.h │ │ │ ├── WiimoteControllerSettings.cpp │ │ │ └── WiimoteControllerSettings.h │ │ ├── windows/ │ │ │ ├── PPCThreadsViewer/ │ │ │ │ ├── DebugPPCThreadsWindow.cpp │ │ │ │ └── DebugPPCThreadsWindow.h │ │ │ └── TextureRelationViewer/ │ │ │ ├── TextureRelationWindow.cpp │ │ │ └── TextureRelationWindow.h │ │ ├── wxCemuConfig.cpp │ │ ├── wxCemuConfig.h │ │ ├── wxHelper.h │ │ ├── wxWindowSystem.cpp │ │ ├── wxcomponents/ │ │ │ ├── checktree.cpp │ │ │ └── checktree.h │ │ └── wxgui.h │ ├── imgui/ │ │ ├── CMakeLists.txt │ │ ├── imgui_extension.cpp │ │ ├── imgui_extension.h │ │ ├── imgui_impl_metal.h │ │ ├── imgui_impl_metal.mm │ │ ├── imgui_impl_opengl3.cpp │ │ ├── imgui_impl_opengl3.h │ │ ├── imgui_impl_vulkan.cpp │ │ └── imgui_impl_vulkan.h │ ├── input/ │ │ ├── CMakeLists.txt │ │ ├── ControllerFactory.cpp │ │ ├── ControllerFactory.h │ │ ├── InputManager.cpp │ │ ├── InputManager.h │ │ ├── api/ │ │ │ ├── Controller.cpp │ │ │ ├── Controller.h │ │ │ ├── ControllerProvider.h │ │ │ ├── ControllerState.cpp │ │ │ ├── ControllerState.h │ │ │ ├── DSU/ │ │ │ │ ├── DSUController.cpp │ │ │ │ ├── DSUController.h │ │ │ │ ├── DSUControllerProvider.cpp │ │ │ │ ├── DSUControllerProvider.h │ │ │ │ ├── DSUMessages.cpp │ │ │ │ └── DSUMessages.h │ │ │ ├── DirectInput/ │ │ │ │ ├── DirectInputController.cpp │ │ │ │ ├── DirectInputController.h │ │ │ │ ├── DirectInputControllerProvider.cpp │ │ │ │ └── DirectInputControllerProvider.h │ │ │ ├── GameCube/ │ │ │ │ ├── GameCubeController.cpp │ │ │ │ ├── GameCubeController.h │ │ │ │ ├── GameCubeControllerProvider.cpp │ │ │ │ └── GameCubeControllerProvider.h │ │ │ ├── InputAPI.h │ │ │ ├── Keyboard/ │ │ │ │ ├── KeyboardController.cpp │ │ │ │ ├── KeyboardController.h │ │ │ │ ├── KeyboardControllerProvider.cpp │ │ │ │ └── KeyboardControllerProvider.h │ │ │ ├── SDL/ │ │ │ │ ├── SDLController.cpp │ │ │ │ ├── SDLController.h │ │ │ │ ├── SDLControllerProvider.cpp │ │ │ │ └── SDLControllerProvider.h │ │ │ ├── Wiimote/ │ │ │ │ ├── NativeWiimoteController.cpp │ │ │ │ ├── NativeWiimoteController.h │ │ │ │ ├── WiimoteControllerProvider.cpp │ │ │ │ ├── WiimoteControllerProvider.h │ │ │ │ ├── WiimoteDevice.h │ │ │ │ ├── WiimoteMessages.h │ │ │ │ ├── hidapi/ │ │ │ │ │ ├── HidapiWiimote.cpp │ │ │ │ │ └── HidapiWiimote.h │ │ │ │ └── l2cap/ │ │ │ │ ├── L2CapWiimote.cpp │ │ │ │ └── L2CapWiimote.h │ │ │ └── XInput/ │ │ │ ├── XInputController.cpp │ │ │ ├── XInputController.h │ │ │ ├── XInputControllerProvider.cpp │ │ │ └── XInputControllerProvider.h │ │ ├── emulated/ │ │ │ ├── ClassicController.cpp │ │ │ ├── ClassicController.h │ │ │ ├── EmulatedController.cpp │ │ │ ├── EmulatedController.h │ │ │ ├── ProController.cpp │ │ │ ├── ProController.h │ │ │ ├── VPADController.cpp │ │ │ ├── VPADController.h │ │ │ ├── WPADController.cpp │ │ │ ├── WPADController.h │ │ │ ├── WiimoteController.cpp │ │ │ └── WiimoteController.h │ │ └── motion/ │ │ ├── Mahony.h │ │ ├── MotionHandler.h │ │ └── MotionSample.h │ ├── main.cpp │ ├── mainLLE.cpp │ ├── resource/ │ │ ├── CMakeLists.txt │ │ ├── CafeDefaultFont.cpp │ │ ├── IconsFontAwesome5.h │ │ ├── MacOSXBundleInfo.plist.in │ │ ├── cemu.icns │ │ ├── cemu.rc │ │ ├── embedded/ │ │ │ ├── DEBUGGER_BP.hpng │ │ │ ├── DEBUGGER_BP_RED.hpng │ │ │ ├── DEBUGGER_GOTO.hpng │ │ │ ├── DEBUGGER_PAUSE.hpng │ │ │ ├── DEBUGGER_PLAY.hpng │ │ │ ├── DEBUGGER_STEP_INTO.hpng │ │ │ ├── DEBUGGER_STEP_OUT.hpng │ │ │ ├── DEBUGGER_STEP_OVER.hpng │ │ │ ├── INPUT_CONNECTED.hpng │ │ │ ├── INPUT_DISCONNECTED.hpng │ │ │ ├── INPUT_LOW_BATTERY.hpng │ │ │ ├── M_WND_ICON128.xpm │ │ │ ├── PNG_HELP.hpng │ │ │ ├── PNG_REFRESH.hpng │ │ │ ├── X_BOX.xpm │ │ │ ├── X_GAME_PROFILE.xpm │ │ │ ├── X_HOTKEY_SETTINGS.xpm │ │ │ ├── X_SETTINGS.xpm │ │ │ ├── fontawesome.S │ │ │ ├── fontawesome_macos.S │ │ │ ├── icons8-checkmark-yes-32.hpng │ │ │ ├── icons8-error-32.hpng │ │ │ ├── resources.cpp │ │ │ └── resources.h │ │ ├── installer.nsi │ │ ├── resource.h │ │ └── update.sh │ ├── tools/ │ │ └── ShaderCacheMerger.cpp │ └── util/ │ ├── CMakeLists.txt │ ├── ChunkedHeap/ │ │ └── ChunkedHeap.h │ ├── DXGIWrapper/ │ │ └── DXGIWrapper.h │ ├── EventService.h │ ├── Fiber/ │ │ ├── Fiber.h │ │ ├── FiberUnix.cpp │ │ └── FiberWin.cpp │ ├── ImageWriter/ │ │ ├── bmp.h │ │ └── tga.h │ ├── IniParser/ │ │ ├── IniParser.cpp │ │ └── IniParser.h │ ├── MemMapper/ │ │ ├── MemMapper.h │ │ ├── MemMapperUnix.cpp │ │ └── MemMapperWin.cpp │ ├── ScreenSaver/ │ │ └── ScreenSaver.h │ ├── SystemInfo/ │ │ ├── SystemInfo.cpp │ │ ├── SystemInfo.h │ │ ├── SystemInfoLinux.cpp │ │ ├── SystemInfoMac.cpp │ │ ├── SystemInfoStub.cpp │ │ ├── SystemInfoUnix.cpp │ │ └── SystemInfoWin.cpp │ ├── ThreadPool/ │ │ └── ThreadPool.h │ ├── VirtualHeap/ │ │ ├── VirtualHeap.cpp │ │ └── VirtualHeap.h │ ├── Zir/ │ │ ├── Core/ │ │ │ ├── IR.cpp │ │ │ ├── IR.h │ │ │ ├── ZirUtility.h │ │ │ ├── ZpIRBuilder.h │ │ │ ├── ZpIRDebug.h │ │ │ ├── ZpIRPasses.h │ │ │ └── ZpIRScheduler.h │ │ ├── EmitterGLSL/ │ │ │ ├── ZpIREmitGLSL.cpp │ │ │ └── ZpIREmitGLSL.h │ │ └── Passes/ │ │ ├── RegisterAllocatorForGLSL.cpp │ │ └── ZpIRRegisterAllocator.cpp │ ├── boost/ │ │ └── bluetooth.h │ ├── bootSound/ │ │ ├── BootSoundReader.cpp │ │ └── BootSoundReader.h │ ├── containers/ │ │ ├── IntervalBucketContainer.h │ │ ├── LookupTableL3.h │ │ ├── RangeStore.h │ │ ├── SmallBitset.h │ │ ├── flat_hash_map.hpp │ │ └── robin_hood.h │ ├── crypto/ │ │ ├── aes128.cpp │ │ ├── aes128.h │ │ ├── crc32.cpp │ │ ├── crc32.h │ │ ├── md5.cpp │ │ └── md5.h │ ├── helpers/ │ │ ├── ClassWrapper.h │ │ ├── ConcurrentQueue.h │ │ ├── MapAdaptor.h │ │ ├── MemoryPool.h │ │ ├── Semaphore.h │ │ ├── Serializer.cpp │ │ ├── Serializer.h │ │ ├── Singleton.h │ │ ├── StringBuf.h │ │ ├── StringHelpers.h │ │ ├── StringParser.h │ │ ├── SystemException.h │ │ ├── TempState.h │ │ ├── enum_array.hpp │ │ ├── fixedSizeList.h │ │ ├── fspinlock.h │ │ ├── helpers.cpp │ │ ├── helpers.h │ │ └── ringbuffer.h │ ├── highresolutiontimer/ │ │ ├── HighResolutionTimer.cpp │ │ └── HighResolutionTimer.h │ ├── libusbWrapper/ │ │ ├── libusbWrapper.cpp │ │ └── libusbWrapper.h │ ├── math/ │ │ ├── glm.h │ │ ├── quaternion.h │ │ ├── vector2.h │ │ └── vector3.h │ └── tinyxml2/ │ ├── tinyxml2.cpp │ └── tinyxml2.h └── vcpkg.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: false AlignOperands: true AlignTrailingComments: true AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: false AllowShortEnumsOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLambdasOnASingleLine: Inline AlwaysBreakTemplateDeclarations: true BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: true AfterClass: true AfterControlStatement: Always AfterEnum: true AfterExternBlock: true AfterFunction: true AfterNamespace: true AfterStruct: true AfterUnion: true BeforeElse: true BeforeWhile: true SplitEmptyFunction: false BreakBeforeBraces: Custom BreakBeforeTernaryOperators: true ColumnLimit: 0 ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true IndentWidth: 4 KeepEmptyLinesAtTheStartOfBlocks: false Language: Cpp MaxEmptyLinesToKeep: 1 NamespaceIndentation: All ObjCSpaceAfterProperty: false PointerAlignment: Left ReflowComments: true SortIncludes: false SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceBeforeCtorInitializerColon: true SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceBeforeSquareBrackets: false SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Latest TabWidth: 4 UseTab: Always ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Cemu Discord url: https://discord.com/invite/5psYsup about: If you need technical support with Cemu or have other questions the best place to ask is on the official Cemu Discord linked here ================================================ FILE: .github/ISSUE_TEMPLATE/emulation_bug_report.yaml ================================================ # Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema name: Bug Report description: Report an issue with Cemu emulator title: "Enter a title for the bug report here" labels: bug body: - type: markdown id: md_readme attributes: value: | ## Important: Read First If you discovered a bug you can report it here. Please make sure of the following first: - That you are using the latest version of Cemu - Only report something if you are sure it's a bug and not any technical issue on your end. For troubleshooting help see the [links page](https://github.com/cemu-project/Cemu#links) - Problems specific to a single game should be reported on the [compatibility wiki](https://wiki.cemu.info/wiki/Main_Page) instead - Verify that your problem isn't already mentioned on the [issue tracker](https://github.com/cemu-project/Cemu/issues) Additionally, be aware that graphic packs can also causes issues. There is a separate issue tracker for graphic pack bugs over at the [graphic pack repository](https://github.com/cemu-project/cemu_graphic_packs) - type: textarea id: current_behavior attributes: label: Current Behavior description: "What the bug is, in a brief description" validations: required: true - type: textarea id: expected_behavior attributes: label: Expected Behavior description: "What did you expect to happen?" validations: required: true - type: textarea id: steps_to_reproduce attributes: label: Steps to Reproduce description: "How to reproduce the issue" validations: required: true - type: textarea id: sys_info attributes: label: System Info (Optional) description: "Your PC specifications. Usually only the operating system and graphics card is important. But feel free to add more info." placeholder: | Info OS: Windows 10 GPU: NVIDIA GeForce RTX 4090 value: | OS: GPU: - type: textarea id: emulation_settings attributes: label: Emulation Settings (Optional) description: | Any non-default settings. You can leave this empty if you didn't change anything other than input settings. validations: required: false - type: textarea id: logs_files attributes: label: "Logs (Optional)" description: | "Attach `log.txt` from your Cemu folder (*File > Open Cemu folder*)". validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature_report.yaml ================================================ # Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema name: Feature suggestion description: Suggest a new feature title: "Enter a title for the suggestion here" labels: feature request body: - type: markdown id: md_readme attributes: value: | ## Important: Read First While we appreciate suggestions, it is important to note that we are a very small team and there are already many more ideas than we could ever implement in the near future. Therefore, please only suggest something if you believe it is a great addition and the idea is reasonably unique. *Avoid* to create suggestions for: - Overly obvious features ("Game xyz does not work and should be fixed", "Wiimote support should be improved", "You should add an Android port", "Copy feature xyz from another emulator", "A button to pause/stop emulation") - Niche features which are only interesting to a tiny percentage of users - Large scale features ("Add a Metal backend for MacOS", "Add ARM support", "Add savestates") Note that this doesn't mean we aren't interested in these ideas, but rather we likely have them planned anyway and it's mostly up to finding the time to implement them. If you believe your idea is worthwhile even if it doesn't meet all the criteria above, you can still try suggesting it but we might close it. - type: textarea id: idea_suggestion attributes: label: Your suggestion description: "Describe what your suggestion is in as much detail as possible" validations: required: true ================================================ FILE: .github/workflows/build.yml ================================================ name: Build Cemu on: workflow_call: inputs: next_version_major: required: false type: string next_version_minor: required: false type: string env: VCPKG_ROOT: "${{github.workspace}}/dependencies/vcpkg" VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' VCPKG_FORCE_DOWNLOADED_BINARIES: true jobs: build-ubuntu: continue-on-error: true strategy: fail-fast: false matrix: include: - os: ubuntu-22.04 arch: x64 - os: ubuntu-22.04-arm arch: arm name: build-ubuntu-${{ matrix.arch }} runs-on: ${{ matrix.os }} steps: - name: "Checkout repo" uses: actions/checkout@v6 with: submodules: "recursive" fetch-depth: 0 - name: Setup release mode parameters run: | echo "BUILD_MODE=release" >> $GITHUB_ENV echo "BUILD_FLAGS=" >> $GITHUB_ENV echo "Build mode is release" - name: Setup build flags for version if: ${{ inputs.next_version_major != '' }} run: | echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}" echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV - name: "Install system dependencies" run: | sudo apt update -qq sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build libbluetooth-dev - name: "Bootstrap vcpkg" run: | bash ./dependencies/vcpkg/bootstrap-vcpkg.sh - name: 'Setup NuGet Credentials for vcpkg' shell: 'bash' run: | mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \ sources add \ -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ -storepasswordincleartext \ -name "GitHub" \ -username "${{ github.repository_owner }}" \ -password "${{ secrets.GITHUB_TOKEN }}" mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \ setapikey "${{ secrets.GITHUB_TOKEN }}" \ -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" - name: "cmake" run: | cmake -S . -B build ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja - name: "Build Cemu" run: | cmake --build build - name: Prepare artifact run: mv bin/Cemu_release bin/Cemu - name: Upload artifact uses: actions/upload-artifact@v6 with: name: cemu-bin-linux-${{ matrix.arch }} path: ./bin/Cemu build-appimage: strategy: fail-fast: false matrix: include: - os: ubuntu-22.04 arch: x64 - os: ubuntu-22.04-arm arch: arm name: build-appimage-${{ matrix.arch }} runs-on: ${{ matrix.os }} needs: build-ubuntu steps: - name: Checkout Upstream Repo uses: actions/checkout@v6 - uses: actions/download-artifact@v8 with: name: cemu-bin-linux-${{ matrix.arch }} path: bin - name: "Install system dependencies" run: | sudo apt update -qq sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream libbluetooth-dev - name: "Build AppImage" run: | export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" export DEPLOY_GTK_VERSION=3 dist/linux/appimage.sh ${{ runner.arch }} - name: Upload artifact uses: actions/upload-artifact@v6 with: name: cemu-appimage-${{ matrix.arch }} path: artifacts build-windows: runs-on: windows-2022 steps: - name: "Checkout repo" uses: actions/checkout@v6 with: submodules: "recursive" - name: Setup release mode parameters run: | echo "BUILD_MODE=release" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append echo "Build mode is release" - name: Setup build flags for version if: ${{ inputs.next_version_major != '' }} run: | echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}" echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - name: "Setup cmake" uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: '3.29.0' - name: "Bootstrap vcpkg" run: | ./dependencies/vcpkg/bootstrap-vcpkg.bat - name: 'Setup NuGet Credentials for vcpkg' shell: 'bash' run: | `./dependencies/vcpkg/vcpkg.exe fetch nuget | tail -n 1` \ sources add \ -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ -storepasswordincleartext \ -name "GitHub" \ -username "${{ github.repository_owner }}" \ -password "${{ secrets.GITHUB_TOKEN }}" `./dependencies/vcpkg/vcpkg.exe fetch nuget | tail -n 1` \ setapikey "${{ secrets.GITHUB_TOKEN }}" \ -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" - name: "cmake" run: | mkdir -p build cd build echo "[INFO] BUILD_FLAGS: ${{ env.BUILD_FLAGS }}" echo "[INFO] BUILD_MODE: ${{ env.BUILD_MODE }}" cmake .. ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DVCPKG_INSTALL_OPTIONS="--clean-after-build" - name: "Build Cemu" run: | cd build cmake --build . --config ${{ env.BUILD_MODE }} - name: Prepare artifact run: Rename-Item bin/Cemu_release.exe Cemu.exe - name: Build NSIS Installer shell: cmd run: | cd src\resource makensis /DPRODUCT_VERSION=${{ inputs.next_version_major }}.${{ inputs.next_version_minor }} installer.nsi - name: Upload artifact uses: actions/upload-artifact@v6 with: name: cemu-bin-windows-x64 path: ./bin/Cemu.exe - name: Upload NSIS Installer uses: actions/upload-artifact@v6 with: name: cemu-installer-windows-x64 path: ./src/resource/cemu-${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}-windows-x64-installer.exe build-macos: runs-on: macos-14 strategy: matrix: arch: [x86_64, arm64] steps: - name: "Checkout repo" uses: actions/checkout@v6 with: submodules: "recursive" - name: Setup release mode parameters run: | echo "BUILD_MODE=release" >> $GITHUB_ENV echo "BUILD_FLAGS=" >> $GITHUB_ENV echo "Build mode is release" - name: Setup build flags for version if: ${{ inputs.next_version_major != '' }} run: | echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}" echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV - name: "Install system dependencies" run: | brew update brew install ninja nasm automake libtool - name: "Install molten-vk" run: | curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar tar xf MoltenVK-macos-privateapi.tar sudo mkdir -p /usr/local/lib sudo cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib - name: "Setup cmake" uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: '3.29.0' - name: "Bootstrap vcpkg" run: | bash ./dependencies/vcpkg/bootstrap-vcpkg.sh - name: 'Setup NuGet Credentials for vcpkg' shell: 'bash' run: | mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \ sources add \ -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ -storepasswordincleartext \ -name "GitHub" \ -username "${{ github.repository_owner }}" \ -password "${{ secrets.GITHUB_TOKEN }}" mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \ setapikey "${{ secrets.GITHUB_TOKEN }}" \ -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" - name: "cmake" run: | mkdir build cd build cmake .. ${{ env.BUILD_FLAGS }} \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \ -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \ -DMACOS_BUNDLE=ON \ -G Ninja - name: "Build Cemu" run: | cmake --build build - name: Prepare artifact run: | mkdir bin/Cemu_app mv bin/Cemu_release.app bin/Cemu_app/Cemu.app mv bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu_release bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu sed -i '' 's/Cemu_release/Cemu/g' bin/Cemu_app/Cemu.app/Contents/Info.plist chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/{Cemu,update.sh} ln -s /Applications bin/Cemu_app/Applications hdiutil create ./bin/tmp.dmg -ov -volname "Cemu" -fs HFS+ -srcfolder "./bin/Cemu_app" hdiutil convert ./bin/tmp.dmg -format UDZO -o bin/Cemu.dmg rm bin/tmp.dmg - name: Upload artifact uses: actions/upload-artifact@v6 with: name: cemu-bin-macos-${{ matrix.arch }} path: ./bin/Cemu.dmg ================================================ FILE: .github/workflows/build_check.yml ================================================ name: Build check on: pull_request: paths-ignore: - "*.md" types: - opened - synchronize - reopened push: paths-ignore: - "*.md" branches: - main jobs: build: uses: ./.github/workflows/build.yml ================================================ FILE: .github/workflows/deploy_release.yml ================================================ name: Deploy release on: workflow_dispatch: inputs: changelog0: description: 'Enter the changelog lines for this release. Each line is a feature / bullet point. Do not use dash.' required: true type: string changelog1: description: 'Feature 2' required: false type: string changelog2: description: 'Feature 3' required: false type: string changelog3: description: 'Feature 4' required: false type: string changelog4: description: 'Feature 5' required: false type: string changelog5: description: 'Feature 6' required: false type: string changelog6: description: 'Feature 7' required: false type: string changelog7: description: 'Feature 8' required: false type: string changelog8: description: 'Feature 9' required: false type: string changelog9: description: 'Feature 10' required: false type: string jobs: calculate-version: name: Calculate Version uses: ./.github/workflows/determine_release_version.yml call-release-build: uses: ./.github/workflows/build.yml needs: calculate-version with: next_version_major: ${{ needs.calculate-version.outputs.next_version_major }} next_version_minor: ${{ needs.calculate-version.outputs.next_version_minor }} deploy: name: Deploy release runs-on: ubuntu-22.04 needs: [call-release-build, calculate-version] steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Generate changelog id: generate_changelog run: | CHANGELOG="" if [ -n "${{ github.event.inputs.changelog0 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog0 }}\n"; fi if [ -n "${{ github.event.inputs.changelog1 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog1 }}\n"; fi if [ -n "${{ github.event.inputs.changelog2 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog2 }}\n"; fi if [ -n "${{ github.event.inputs.changelog3 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog3 }}\n"; fi if [ -n "${{ github.event.inputs.changelog4 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog4 }}\n"; fi if [ -n "${{ github.event.inputs.changelog5 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog5 }}\n"; fi if [ -n "${{ github.event.inputs.changelog6 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog6 }}\n"; fi if [ -n "${{ github.event.inputs.changelog7 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog7 }}\n"; fi if [ -n "${{ github.event.inputs.changelog8 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog8 }}\n"; fi if [ -n "${{ github.event.inputs.changelog9 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog9 }}\n"; fi echo -e "$CHANGELOG" echo "RELEASE_BODY=$CHANGELOG" >> $GITHUB_ENV - uses: actions/download-artifact@v8 with: name: cemu-bin-linux-x64 path: cemu-bin-linux-x64 - uses: actions/download-artifact@v8 with: name: cemu-appimage-x64 path: cemu-appimage-x64 - uses: actions/download-artifact@v8 with: name: cemu-bin-windows-x64 path: cemu-bin-windows-x64 - uses: actions/download-artifact@v8 with: name: cemu-installer-windows-x64 path: cemu-installer-windows-x64 - uses: actions/download-artifact@v8 with: pattern: cemu-bin-macos* path: cemu-macos - name: Initialize run: | mkdir upload sudo apt install zip - name: Set version dependent vars run: | echo "Version: ${{ needs.calculate-version.outputs.next_version }}" echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}" echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}" echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV - name: Create release from windows-bin run: | ls ./ ls ./bin/ cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }} mv cemu-bin-windows-x64/Cemu.exe ./${{ env.CEMU_FOLDER_NAME }}/Cemu.exe zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-windows-x64.zip ${{ env.CEMU_FOLDER_NAME }} rm -r ./${{ env.CEMU_FOLDER_NAME }} - name: Create release from windows-installer run: cp cemu-installer-windows-x64/cemu-${{ env.CEMU_VERSION }}-windows-x64-installer.exe upload/cemu-${{ env.CEMU_VERSION }}-windows-x64-installer.exe - name: Create appimage run: | VERSION=${{ env.CEMU_VERSION }} echo "Cemu Version is $VERSION" ls cemu-appimage-x64 mv cemu-appimage-x64/Cemu-*-x86_64.AppImage upload/Cemu-$VERSION-x86_64.AppImage - name: Create release from linux-bin run: | ls ./ ls ./bin/ cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }} mv cemu-bin-linux-x64/Cemu ./${{ env.CEMU_FOLDER_NAME }}/Cemu zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-ubuntu-22.04-x64.zip ${{ env.CEMU_FOLDER_NAME }} rm -r ./${{ env.CEMU_FOLDER_NAME }} - name: Create release from macos-bin run: | cd cemu-macos for bin_dir in cemu-bin-macos-*; do arch="${bin_dir##cemu-bin-macos-}" cp $bin_dir/Cemu.dmg ../upload/cemu-${{ env.CEMU_VERSION }}-macos-12-$arch.dmg done - name: Create release run: | wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz tar xvzf ghr.tar.gz; rm ghr.tar.gz echo "[INFO] Release tag: v${{ env.CEMU_VERSION }}" CHANGELOG_UNESCAPED=$(printf "%s\n" "${{ env.RELEASE_BODY }}" | sed 's/\\n/\n/g') RELEASE_BODY=$(printf "%s\n%s" \ "**Changelog:**" \ "$CHANGELOG_UNESCAPED") ghr_v0.15.0_linux_amd64/ghr -draft -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "$RELEASE_BODY" "v${{ env.CEMU_VERSION }}" ./upload ================================================ FILE: .github/workflows/determine_release_version.yml ================================================ name: Calculate Next Version from release history on: workflow_dispatch: workflow_call: outputs: next_version: description: "The next semantic version" value: ${{ jobs.calculate-version.outputs.next_version }} next_version_major: description: "The next semantic version (major)" value: ${{ jobs.calculate-version.outputs.next_version_major }} next_version_minor: description: "The next semantic version (minor)" value: ${{ jobs.calculate-version.outputs.next_version_minor }} jobs: calculate-version: runs-on: ubuntu-latest outputs: next_version: ${{ steps.calculate_next_version.outputs.next_version }} next_version_major: ${{ steps.calculate_next_version.outputs.next_version_major }} next_version_minor: ${{ steps.calculate_next_version.outputs.next_version_minor }} steps: - name: Checkout code uses: actions/checkout@v6 - name: Get all releases id: get_all_releases run: | # Fetch all releases and check for API errors RESPONSE=$(curl -s -o response.json -w "%{http_code}" "https://api.github.com/repos/${{ github.repository }}/releases?per_page=100") if [ "$RESPONSE" -ne 200 ]; then echo "Failed to fetch releases. HTTP status: $RESPONSE" cat response.json exit 1 fi # Extract and sort tags ALL_TAGS=$(jq -r '.[].tag_name' response.json | grep -E '^v[0-9]+\.[0-9]+(-[0-9]+)?$' | sed 's/-.*//' | sort -V | tail -n 1) # Exit if no tags were found if [ -z "$ALL_TAGS" ]; then echo "No valid tags found." exit 1 fi echo "::set-output name=tag::$ALL_TAGS" # echo "tag=$ALL_TAGS" >> $GITHUB_STATE - name: Calculate next semver minor id: calculate_next_version run: | LATEST_VERSION=${{ steps.get_all_releases.outputs.tag }} # strip 'v' prefix and split into major.minor LATEST_VERSION=${LATEST_VERSION//v/} IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_VERSION" MAJOR=${VERSION_PARTS[0]} MINOR=${VERSION_PARTS[1]} # increment the minor version MINOR=$((MINOR + 1)) NEXT_VERSION="${MAJOR}.${MINOR}" echo "Major: $MAJOR" echo "Minor: $MINOR" echo "Next version: $NEXT_VERSION" echo "::set-output name=next_version::$NEXT_VERSION" echo "::set-output name=next_version_major::$MAJOR" echo "::set-output name=next_version_minor::$MINOR" ================================================ FILE: .github/workflows/generate_pot.yml ================================================ name: Generate translation template on: pull_request: paths-ignore: - "*.md" types: - opened - synchronize - reopened push: paths-ignore: - "*.md" branches: - main jobs: generate-pot: runs-on: ubuntu-latest steps: - name: "Checkout repo" uses: actions/checkout@v6 - name: "Install gettext" run: | sudo apt update -qq sudo apt install -y gettext - name: "Generate POT file using xgettext" run: > find src -name *.cpp -o -name *.hpp -o -name *.h | xargs xgettext --from-code=utf-8 -w 100 --keyword="_" --keyword="wxTRANSLATE" --keyword="wxPLURAL:1,2" --keyword="_tr" --keyword="TR_NOOP" --check=space-ellipsis --omit-header -o cemu.pot - name: Upload artifact uses: actions/upload-artifact@v6 with: name: POT file path: ./cemu.pot if-no-files-found: error ================================================ FILE: .gitignore ================================================ *.slo *.lo *.o *.obj *.gch *.pch *.so *.dylib *.dll *.a *.lib *.exe *.out *.app .vs .vscode .idea/ build/ cmake-build-*/ out/ .cache/ bin/Cemu_* bin/Cemu_*.exe # Cemu bin files bin/otp.bin bin/seeprom.bin bin/log.txt bin/Cemu_*.pdb bin/Cemu_*.ilk bin/Cemu.exe.backup bin/mlc01/* bin/settings.xml bin/network_services.xml bin/title_list_cache.xml bin/debugger/* bin/sdcard/* bin/screenshots/* bin/dump/* bin/cafeLibs/* bin/portable/* bin/keys.txt !bin/shaderCache/info.txt bin/shaderCache/* bin/controllerProfiles/* !bin/gameProfiles/default/* bin/gameProfiles/* bin/graphicPacks/* # Ignore Finder view option files created by OS X .DS_Store ================================================ FILE: .gitmodules ================================================ [submodule "dependencies/ZArchive"] path = dependencies/ZArchive url = https://github.com/Exzap/ZArchive shallow = true [submodule "dependencies/cubeb"] path = dependencies/cubeb url = https://github.com/mozilla/cubeb shallow = true [submodule "dependencies/vcpkg"] path = dependencies/vcpkg url = https://github.com/microsoft/vcpkg shallow = false [submodule "dependencies/Vulkan-Headers"] path = dependencies/Vulkan-Headers url = https://github.com/KhronosGroup/Vulkan-Headers shallow = true [submodule "dependencies/imgui"] path = dependencies/imgui url = https://github.com/ocornut/imgui shallow = true [submodule "dependencies/metal-cpp"] path = dependencies/metal-cpp url = https://github.com/bkaradzic/metal-cpp.git shallow = true [submodule "dependencies/xbyak_aarch64"] path = dependencies/xbyak_aarch64 url = https://github.com/fujitsu/xbyak_aarch64 ================================================ FILE: BUILD.md ================================================ # Build Instructions ## Table of Contents - [Windows](#windows) - [Linux](#linux) - [Dependencies](#dependencies) - [For Arch and derivatives:](#for-arch-and-derivatives) - [For Debian, Ubuntu and derivatives](#for-debian-ubuntu-and-derivatives) - [For Fedora and derivatives:](#for-fedora-and-derivatives) - [Build Cemu](#build-cemu) - [CMake and Clang](#cmake-and-clang) - [GCC](#gcc) - [Debug Build](#debug-build) - [Troubleshooting Steps](#troubleshooting-steps) - [Compiling Errors](#compiling-errors) - [Building Errors](#building-errors) - [macOS](#macos) - [Installing brew](#installing-brew) - [Installing Tool Dependencies](#installing-tool-dependencies) - [Installing Library Dependencies](#installing-library-dependencies) - [Build Cemu using CMake](#build-cemu-using-cmake) - [FreeBSD](#freebsd) - [Installing Dependencies](#installing-dependencies) - [Build Cemu on BSD with CMake](#build-cemu-on-bsd-with-cmake) - [Updating Cemu and source code](#updating-cemu-and-source-code) ## Windows Prerequisites: - git - A recent version of Visual Studio 2022 with the following additional components: - C++ CMake tools for Windows - Windows 10/11 SDK Instructions for Visual Studio 2022: 1. Run `git clone --recursive https://github.com/cemu-project/Cemu` 2. Open the newly created Cemu directory in Visual Studio using the "Open a local folder" option 3. In the menu select Project -> Configure CMake. Wait until it is done, this may take a long time 4. You can now build, run and debug Cemu Any other IDE should also work as long as it has CMake and MSVC support. CLion and Visual Studio Code have been confirmed to work. ## Linux To compile Cemu, a recent enough compiler and STL with C++20 support is required! Clang-15 or higher is what we recommend. ### Dependencies #### For Arch and derivatives: `sudo pacman -S --needed base-devel bluez-libs clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` #### For Debian, Ubuntu and derivatives: `sudo apt install -y cmake curl clang-15 freeglut3-dev git libbluetooth-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package. At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clang), use the following command instead: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` #### For Fedora and derivatives: `sudo dnf install bluez-libs-devel clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel wayland-protocols-devel zlib-devel zlib-static` ### Build Cemu #### CMake and Clang ``` git clone --recursive https://github.com/cemu-project/Cemu cd Cemu cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja cmake --build build ``` #### GCC If you are building using GCC, make sure you have g++ installed: - Installation for Arch and derivatives: `sudo pacman -S gcc` - Installation for Debian, Ubuntu and derivatives: `sudo apt install g++` - Installation for Fedora and derivatives: `sudo dnf install gcc-c++` ``` git clone --recursive https://github.com/cemu-project/Cemu cd Cemu cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja cmake --build build ``` #### Debug Build ``` git clone --recursive https://github.com/cemu-project/Cemu cd Cemu cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja cmake --build build ``` If you are using GCC, replace `cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja` with `cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja` #### Troubleshooting Steps ##### Compiling Errors This section refers to running `cmake -S...` (truncated). * `vcpkg install failed` * Run the following in the root directory and try running the command again (don't forget to change directories afterwards): * `cd dependencies/vcpkg && git fetch --unshallow` * `Please ensure you're using the latest port files with git pull and vcpkg update.` * Either: * Update vcpkg by running by the following command: * `git submodule update --remote dependencies/vcpkg` * If you are sure vcpkg is up to date, check the following logs: * `Cemu/dependencies/vcpkg/buildtrees/wxwidgets/config-x64-linux-out.log` * `Cemu/dependencies/vcpkg/buildtrees/libsystemd/config-x64-linux-dbg-meson-log.txt.log` * `Cemu/dependencies/vcpkg/buildtrees/libsystemd/config-x64-linux-dbg-out.log` * Not able to find Ninja. * Add the following and try running the command again: * `-DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` * Compiling failed during the boost-build dependency. * It means you don't have a working/good standard library installation. Check the integrity of your system headers and making sure that C++ related packages are installed and intact. * Compiling failed during rebuild after `git pull` with an error that mentions RPATH * Add the following and try running the command again: * `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON` * Environment variable `VCPKG_FORCE_SYSTEM_BINARIES` must be set. * Execute the folowing and then try running the command again: * `export VCPKG_FORCE_SYSTEM_BINARIES=1` * If you are getting a random error, read the [package-name-and-platform]-out.log and [package-name-and-platform]-err.log for the actual reason to see if you might be lacking the headers from a dependency. If you are getting a different error than any of the errors listed above, you may either open an issue in this repo or try using [GCC](#gcc). Make sure your standard library and compilers are updated since Cemu uses a lot of modern features! ##### Building Errors This section refers to running `cmake --build build`. * `main.cpp.o: in function 'std::__cxx11::basic_string...` * You likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see [GCC](#gcc). * `fatal error: 'span' file not found` * You're either missing `libstdc++` or are using a version that's too old. Install at least v10 with your package manager, eg `sudo apt install libstdc++-10-dev`. See [#644](https://github.com/cemu-project/Cemu/issues/644). * `undefined libdecor_xx` * You are likely experiencing an issue with sdl2 package that comes with vcpkg. Delete sdl2 from vcpkg.json in source file and recompile. If you are getting a different error than any of the errors listed above, you may either open an issue in this repo or try using [GCC](#gcc). Make sure your standard library and compilers are updated since Cemu uses a lot of modern features! ## macOS To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and below don't support the C++20 feature set required, so either install LLVM from Homebrew or make sure that you have a recent enough version of Xcode. Xcode 15 is known to work. The OpenGL graphics API isn't supported on macOS, so Vulkan must be used through the Molten-VK compatibility layer. ### Installing brew 1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` 2. Set up the Homebrew shell environment: 1. **On an Intel Mac:** `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` 2. **On an Apple Silicon Mac:** eval `"$(/opt/homebrew/bin/brew shellenv)"` ### Installing Tool Dependencies The native versions of these can be used regardless of what type of Mac you have. `brew install git cmake ninja nasm automake libtool` ### Installing Library Dependencies **On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used to install these dependencies:** 1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once 2. `arch -x86_64 zsh` # run an x64 shell 3. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` 4. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` Then install the dependencies: `brew install boost molten-vk` ### Build Cemu using CMake 1. `git clone --recursive https://github.com/cemu-project/Cemu` 2. `cd Cemu` 3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_OSX_ARCHITECTURES=x86_64 -G Ninja` 4. `cmake --build build` 5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`. #### Troubleshooting steps - If step 3 gives you an error about not being able to find ninja, try appending `-DCMAKE_MAKE_PROGRAM=/usr/local/bin/ninja` to the command and running it again. ## FreeBSD The following instructions to build Cemu on FreeBSD are experimental. Some features available on other platforms are not available on FreeBSD (discord rich presence, bluetooth/support for actual Wii U controllers, auto-updates, etc.) To compile Cemu, a recent enough compiler and STL with C++20 support is required! Clang-15 or higher is what we recommend. Any version of FreeBSD 13.3-RELEASE or higher comes bundled with LLVM > version 15 as part of the base system. However, if for whatever reason your system lacks a recent version of LLVM you can install one by executing: `sudo pkg install llvm15` Or a higher version as desired. ### Installing Dependencies `sudo pkg install boost-libs cmake-core curl glslang gtk3 libzip ninja png pkgconf pugixml rapidjson sdl2 wayland wayland-protocols wx32-gtk3 xorg zstd` ### Build Cemu on BSD with CMake ``` git clone --recursive https://github.com/cemu-project/Cemu cd Cemu cmake -B build -DCMAKE_BUILD_TYPE=release -DENABLE_BLUEZ=OFF -DENABLE_DISCORD_RPC=OFF -DENABLE_FERAL_GAMEMODE=OFF -DENABLE_HIDAPI=OFF -DENABLE_VCPKG=OFF -G Ninja cmake --build build cd build && ninja install ``` You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`. ## Updating Cemu and source code 1. To update your Cemu local repository, use the command `git pull --recurse-submodules` (run this command on the Cemu root). - This should update your local copy of Cemu and all of its dependencies. 2. Then, you can rebuild Cemu using the steps listed above, according to whether you use Linux or Windows. If CMake complains about Cemu already being compiled or another similar error, try deleting the `CMakeCache.txt` file inside the `build` folder and retry building. ## CMake configure flags Some flags can be passed during CMake configure to customise which features are enabled on build. Example usage: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DENABLE_SDL=ON -DENABLE_VULKAN=OFF` ### All platforms | Flag | | Description | Default | Note | |--------------------|:--|-----------------------------------------------------------------------------|---------|--------------------| | ALLOW_PORTABLE | | Allow Cemu to use the `portable` directory to store configs and data | ON | | | CEMU_CXX_FLAGS | | Flags passed straight to the compiler, e.g. `-march=native`, `-Wall`, `/W3` | "" | | | ENABLE_CUBEB | | Enable cubeb audio backend | ON | | | ENABLE_DISCORD_RPC | | Enable Discord Rich presence support | ON | | | ENABLE_OPENGL | | Enable OpenGL graphics backend | ON | Currently required | | ENABLE_HIDAPI | | Enable HIDAPI (used for Wiimote controller API) | ON | | | ENABLE_SDL | | Enable SDLController controller API | ON | Currently required | | ENABLE_VCPKG | | Use VCPKG package manager to obtain dependencies | ON | | | ENABLE_VULKAN | | Enable the Vulkan graphics backend | ON | | | ENABLE_WXWIDGETS | | Enable wxWidgets UI | ON | Currently required | ### Windows | Flag | Description | Default | Note | |--------------------|-----------------------------------|---------|--------------------| | ENABLE_DIRECTAUDIO | Enable DirectAudio audio backend | ON | Currently required | | ENABLE_DIRECTINPUT | Enable DirectInput controller API | ON | Currently required | | ENABLE_XAUDIO | Enable XAudio audio backend | ON | | | ENABLE_XINPUT | Enable XInput controller API | ON | | ### Linux | Flag | Description | Default | |-----------------------|----------------------------------------------------|---------| | ENABLE_BLUEZ | Build with Bluez (used for Wiimote controller API) | ON | | ENABLE_FERAL_GAMEMODE | Enable Feral Interactive GameMode support | ON | | ENABLE_WAYLAND | Enable Wayland support | ON | ### macOS | Flag | Description | Default | |--------------|------------------------------------------------|---------| | MACOS_BUNDLE | MacOS executable will be an application bundle | OFF | ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.21.1) option(ENABLE_VCPKG "Enable the vcpkg package manager" ON) option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF) option(ALLOW_PORTABLE "Allow Cemu to be run in portable mode" ON) # used by CI script to set version: set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "") set(EMULATOR_VERSION_MINOR "0" CACHE STRING "") set(EMULATOR_VERSION_PATCH "0" CACHE STRING "") execute_process( COMMAND git log --format=%h -1 WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} OUTPUT_VARIABLE GIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ) add_definitions(-DEMULATOR_HASH=${GIT_HASH}) if (ENABLE_VCPKG) # check if vcpkg is shallow and unshallow it if necessary execute_process( COMMAND git rev-parse --is-shallow-repository WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dependencies/vcpkg OUTPUT_VARIABLE is_vcpkg_shallow OUTPUT_STRIP_TRAILING_WHITESPACE ) if(is_vcpkg_shallow STREQUAL "true") message(STATUS "vcpkg is shallow. Unshallowing it now...") execute_process( COMMAND git fetch --unshallow WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/dependencies/vcpkg" RESULT_VARIABLE result OUTPUT_VARIABLE output ) endif() if(UNIX AND NOT APPLE) set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux;${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") elseif(APPLE) set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_mac;${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") else() set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_win;${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") endif() set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file") # Set this so that all the various find_package() calls don't need an explicit # CONFIG option set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE) if (WIN32) set(VCPKG_TARGET_TRIPLET "x64-windows-static" CACHE STRING "") endif() endif() project(Cemu VERSION 2.0.0) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_compile_definitions($<$:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT add_definitions(-DEMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR}) add_definitions(-DEMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR}) add_definitions(-DEMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}) set_property(GLOBAL PROPERTY USE_FOLDERS ON) # enable link time optimization for release builds set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON) if (MSVC) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT CemuBin) # floating point model: precise, fiber safe optimizations add_compile_options(/EHsc /fp:precise) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # Speeds up static linking (especially helpful in incremental compilation) if((CMAKE_LINKER MATCHES ".*lld-link.*") AND (CMAKE_AR MATCHES ".*llvm-lib.*")) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY STATIC_LIBRARY_OPTIONS /llvmlibthin) endif() else() add_compile_options(/GT) endif() # enable additional optimization flags for release builds add_compile_options($<$:/Oi>) # enable intrinsic functions add_compile_options($<$:/Ot>) # favor speed endif() if (APPLE) enable_language(OBJC OBJCXX) set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4") endif() if (UNIX AND NOT APPLE) option(ENABLE_WAYLAND "Build with Wayland support" ON) option(ENABLE_FERAL_GAMEMODE "Enables Feral Interactive GameMode Support" ON) option(ENABLE_BLUEZ "Build with Bluez support" ON) endif() if (APPLE) set(ENABLE_METAL_DEFAULT ON) else() set(ENABLE_METAL_DEFAULT OFF) endif() option(ENABLE_OPENGL "Enables the OpenGL backend" ON) option(ENABLE_VULKAN "Enables the Vulkan backend" ON) option(ENABLE_METAL "Enables the Metal backend" ${ENABLE_METAL_DEFAULT}) option(ENABLE_DISCORD_RPC "Enables the Discord Rich Presence feature" ON) if (ENABLE_METAL AND NOT APPLE) message(FATAL_ERROR "Metal backend is only supported on Apple platforms") endif() # input backends if (WIN32) option(ENABLE_XINPUT "Enables the usage of XInput" ON) option(ENABLE_DIRECTINPUT "Enables the usage of DirectInput" ON) add_compile_definitions(HAS_DIRECTINPUT) endif() option(ENABLE_HIDAPI "Build with HIDAPI" ON) option(ENABLE_SDL "Enables the SDLController backend" ON) # audio backends if (WIN32) option(ENABLE_DIRECTAUDIO "Enables the directaudio backend" ON) option(ENABLE_XAUDIO "Enables the xaudio backend" ON) endif() option(ENABLE_CUBEB "Enabled cubeb backend" ON) option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON) set(THREADS_PREFER_PTHREAD_FLAG true) find_package(Threads REQUIRED) find_package(SDL2 REQUIRED) find_package(CURL REQUIRED) find_package(pugixml REQUIRED) find_package(RapidJSON REQUIRED) find_package(Boost COMPONENTS program_options filesystem nowide REQUIRED) find_package(libzip REQUIRED) find_package(glslang REQUIRED) find_package(ZLIB REQUIRED) find_package(zstd MODULE REQUIRED) # MODULE so that zstd::zstd is available find_package(OpenSSL COMPONENTS Crypto SSL REQUIRED) find_package(glm REQUIRED) find_package(fmt 9 REQUIRED) find_package(PNG REQUIRED) # glslang versions older than 11.11.0 define targets without a namespace if (NOT TARGET glslang::SPIRV AND TARGET SPIRV) add_library(glslang::SPIRV ALIAS SPIRV) endif() if (UNIX AND NOT APPLE) find_package(X11 REQUIRED) if (ENABLE_WAYLAND) find_package(Wayland REQUIRED Client) find_package(WaylandScanner REQUIRED) find_package(WaylandProtocols 1.15 REQUIRED) ecm_add_wayland_client_protocol(WAYLAND_PROTOCOL_SRCS PROTOCOL "${WaylandProtocols_DATADIR}/stable/viewporter/viewporter.xml" BASENAME viewporter) add_library(CemuWaylandProtocols STATIC ${WAYLAND_PROTOCOL_SRCS}) target_include_directories(CemuWaylandProtocols PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") target_include_directories(CemuWaylandProtocols PRIVATE ${Wayland_INCLUDE_DIRS}) add_compile_definitions(HAS_WAYLAND) endif() find_package(GTK3 REQUIRED) if(ENABLE_BLUEZ) find_package(bluez REQUIRED) set(SUPPORTS_WIIMOTE ON) add_compile_definitions(HAS_BLUEZ) endif() endif() if (ENABLE_VULKAN) include_directories("dependencies/Vulkan-Headers/include") endif() if (ENABLE_OPENGL) find_package(OpenGL REQUIRED) endif() if (ENABLE_METAL) include_directories(${CMAKE_SOURCE_DIR}/dependencies/metal-cpp) add_definitions(-DENABLE_METAL=1) endif() if (ENABLE_DISCORD_RPC) add_compile_definitions(ENABLE_DISCORD_RPC) endif() if (ENABLE_HIDAPI) find_package(hidapi REQUIRED) set(SUPPORTS_WIIMOTE ON) add_compile_definitions(HAS_HIDAPI) endif () if(UNIX AND NOT APPLE) if(ENABLE_FERAL_GAMEMODE) add_compile_definitions(ENABLE_FERAL_GAMEMODE) add_subdirectory(dependencies/gamemode EXCLUDE_FROM_ALL) target_include_directories(gamemode INTERFACE ./dependencies/gamemode/lib) endif() endif() if (ENABLE_WXWIDGETS) find_package(wxWidgets 3.3 REQUIRED COMPONENTS base core gl propgrid xrc) endif() if (ENABLE_CUBEB) if (NOT ENABLE_VCPKG) find_package(cubeb) endif() if (NOT cubeb_FOUND) option(BUILD_TESTS "" OFF) option(BUILD_TOOLS "" OFF) option(BUNDLE_SPEEX "" OFF) set(USE_WINMM OFF CACHE BOOL "") add_subdirectory("dependencies/cubeb" EXCLUDE_FROM_ALL SYSTEM) set_property(TARGET cubeb PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") add_library(cubeb::cubeb ALIAS cubeb) endif() add_compile_definitions("HAS_CUBEB=1") endif() add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL) if (CMAKE_OSX_ARCHITECTURES) set(CEMU_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) else() set(CEMU_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) endif() if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") add_subdirectory("dependencies/xbyak_aarch64" EXCLUDE_FROM_ALL) endif() find_package(ZArchive) if (NOT ZArchive_FOUND) add_subdirectory("dependencies/ZArchive" EXCLUDE_FROM_ALL) endif() add_subdirectory(src) ================================================ FILE: CMakeSettings.json ================================================ { "configurations": [ { "name": "RelWithDebInfo", "configurationType": "RelWithDebInfo", "generator": "Ninja", "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}" }, { "name": "Release", "configurationType": "Release", "generator": "Ninja", "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}" }, { "name": "Debug", "configurationType": "Debug", "generator": "Ninja", "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}" } ] } ================================================ FILE: CODING_STYLE.md ================================================ # Coding style guidelines for Cemu This document describes the latest version of our coding-style guidelines. Since we did not use this style from the beginning, older code may not adhere to these guidelines. Nevertheless, use these rules even if the surrounding code does not match. Cemu comes with a `.clang-format` file which is supported by most IDEs for formatting. Avoid auto-reformatting whole files, PRs with a lot of formatting changes are difficult to review. ## Names for variables, functions and classes - Always prefix class member variables with `m_` - Always prefix static class variables with `s_` - For variable names: Camel case, starting with a lower case letter after the prefix. Examples: `m_option`, `s_audioVolume` - For functions/class names: Use camel case starting with a capital letter. Examples: `MyClass`, `SetActive` - Avoid underscores in variable names after the prefix. Use `m_myVariable` instead of `m_my_variable` ## About types Cemu provides its own set of basic fixed-width types. They are: `uint8`, `sint8`, `uint16`, `sint16`, `uint32`, `sint32`, `uint64`, `sint64`. Always use these types over something like `uint32_t`. Using `size_t` is also acceptable where suitable. Avoid C types like `int` or `long`. The only exception is when interacting with external libraries which expect these types as parameters. ## When and where to put brackets Always put curly-brackets (`{ }`) on their own line. Example: ``` void FooBar() { if (m_hasFoo) { ... } } ``` As an exception, you can put short lambdas onto the same line: ``` SomeFunc([]() { .... }); ``` You can skip brackets for single-statement `if`. Example: ``` if (cond) action(); ``` ## Printing Avoid sprintf and similar C-style formatting API. Use `fmt::format()`. In UI related code you can use `formatWxString`, but be aware that number formatting with this function will be locale dependent! ## Strings and encoding We use UTF-8 encoded `std::string` where possible. Some conversions need special handling and we have helper functions for those: ```cpp // std::filesystem::path <-> std::string (in precompiled.h) std::string _pathToUtf8(const fs::path& path); fs::path _utf8ToPath(std::string_view input); // wxString <-> std::string wxString wxString::FromUTF8(const std::string& s) std::string wxString::utf8_string(); ``` ## Logging If you want to write to log.txt use `cemuLog_log()`. The log type parameter should be mostly self-explanatory. Use `LogType::Force` if you always want to log something. For example: `cemuLog_log(LogType::Force, "The value is {}", 123);` ## HLE and endianness A pretty large part of Cemu's code base are re-implementations of various Cafe OS modules (e.g. `coreinit.rpl`, `gx2.rpl`...). These generally run in the context of the emulated process, thus special care has to be taken to use types with the correct size and endianness when interacting with memory. Keep in mind that the emulated Espresso CPU is 32bit big-endian, while the host architectures targeted by Cemu are 64bit little-endian! To keep code simple and remove the need for manual endian-swapping, Cemu has templates and aliases of the basic types with explicit endian-ness. For big-endian types add the suffix `be`. Example: `uint32be` When you need to store a pointer in the guest's memory. Use `MEMPTR`. It will automatically store any pointer as 32bit big-endian. The pointer you store must point to memory that is within the guest address space. ## HLE interfaces The implementation for each HLE module is inside a namespace with a matching name. E.g. `coreinit.rpl` functions go into `coreinit` namespace. To expose a new function as callable from within the emulated machine, use `cafeExportRegister` or `cafeExportRegisterFunc`. Here is a short example: ```cpp namespace coreinit { uint32 OSGetCoreCount() { return Espresso::CORE_COUNT; } void Init() { cafeExportRegister("coreinit", OSGetCoreCount, LogType::CoreinitThread); } } ``` You may also see some code which uses `osLib_addFunction` directly. This is a deprecated way of registering functions. ================================================ FILE: LICENSE.txt ================================================ Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ================================================ FILE: README.md ================================================ # **Cemu - Wii U emulator** [![Build Process](https://github.com/cemu-project/Cemu/actions/workflows/build.yml/badge.svg)](https://github.com/cemu-project/Cemu/actions/workflows/build.yml) [![Discord](https://img.shields.io/discord/286429969104764928?label=Cemu&logo=discord&logoColor=FFFFFF)](https://discord.gg/5psYsup) [![Matrix Server](https://img.shields.io/matrix/cemu:cemu.info?server_fqdn=matrix.cemu.info&label=cemu:cemu.info&logo=matrix&logoColor=FFFFFF)](https://matrix.to/#/#cemu:cemu.info) This is the code repository of Cemu, a Wii U emulator that is able to run most Wii U games and homebrew in a playable state. It's written in C/C++ and is being actively developed with new features and fixes. Cemu is currently only available for 64-bit Windows, Linux & macOS devices. ### Links: - [Open Source Announcement](https://www.reddit.com/r/cemu/comments/wwa22c/cemu_20_announcement_linux_builds_opensource_and/) - [Official Website](https://cemu.info) - [Compatibility List/Wiki](https://wiki.cemu.info/wiki/Main_Page) - [Official Subreddit](https://reddit.com/r/Cemu) - [Official Discord](https://discord.gg/5psYsup) - [Official Matrix Server](https://matrix.to/#/#cemu:cemu.info) - [Setup Guide](https://cemu.cfw.guide) #### Other relevant repositories: - [Cemu-Language](https://github.com/cemu-project/Cemu-Language) - [Cemu's Community Graphic Packs](https://github.com/cemu-project/cemu_graphic_packs) ## Download You can download the latest Cemu releases for Windows, Linux and Mac from the [GitHub Releases](https://github.com/cemu-project/Cemu/releases/). For Linux you can also find Cemu on [flathub](https://flathub.org/apps/info.cemu.Cemu). On Windows, Cemu is available both as an installer and in a portable format, where no installation is required besides extracting it in a safe place. The native macOS build is currently purely experimental and should not be considered stable or ready for issue-free gameplay. There are also known issues with degraded performance due to the use of MoltenVK and Rosetta for ARM Macs. We appreciate your patience while we improve Cemu for macOS. Pre-2.0 releases can be found on Cemu's [changelog page](https://cemu.info/changelog.html). ## Build Instructions To compile Cemu yourself on Windows, Linux or macOS, view [BUILD.md](/BUILD.md). ## Issues Issues with the emulator should be filed using [GitHub Issues](https://github.com/cemu-project/Cemu/issues). The old bug tracker can be found at [bugs.cemu.info](https://bugs.cemu.info) and still contains relevant issues and feature suggestions. ## Contributing Pull requests are very welcome. For easier coordination you can visit the developer discussion channel on [Discord](https://discord.gg/5psYsup) or alternatively the [Matrix Server](https://matrix.to/#/#cemu:cemu.info). Before submitting a pull request, please read and follow our code style guidelines listed in [CODING_STYLE.md](/CODING_STYLE.md). If coding isn't your thing, testing games and making detailed bug reports or updating the (usually outdated) compatibility wiki is also appreciated! Questions about Cemu's software architecture can also be answered on Discord (or through the Matrix bridge). ## License Cemu is licensed under [Mozilla Public License 2.0](/LICENSE.txt). Exempt from this are all files in the dependencies directory for which the licenses of the original code apply as well as some individual files in the src folder, as specified in those file headers respectively. ================================================ FILE: boost.natvis ================================================ m_holder.m_size m_holder.m_size m_holder.m_start {{ size={m_holder.m_size} }} m_holder.m_size static_capacity m_holder.m_size ($T1*)m_holder.storage.data ================================================ FILE: cmake/ECMFindModuleHelpers.cmake ================================================ # SPDX-FileCopyrightText: 2014 Alex Merry # # SPDX-License-Identifier: BSD-3-Clause #[=======================================================================[.rst: ECMFindModuleHelpers -------------------- Helper macros for find modules: ``ecm_find_package_version_check()``, ``ecm_find_package_parse_components()`` and ``ecm_find_package_handle_library_components()``. :: ecm_find_package_version_check() Prints warnings if the CMake version or the project's required CMake version is older than that required by extra-cmake-modules. :: ecm_find_package_parse_components( RESULT_VAR KNOWN_COMPONENTS [ [...]] [SKIP_DEPENDENCY_HANDLING]) This macro will populate with a list of components found in _FIND_COMPONENTS, after checking that all those components are in the list of ``KNOWN_COMPONENTS``; if there are any unknown components, it will print an error or warning (depending on the value of _FIND_REQUIRED) and call ``return()``. The order of components in is guaranteed to match the order they are listed in the ``KNOWN_COMPONENTS`` argument. If ``SKIP_DEPENDENCY_HANDLING`` is not set, for each component the variable __component_deps will be checked for dependent components. If is listed in _FIND_COMPONENTS, then all its (transitive) dependencies will also be added to . :: ecm_find_package_handle_library_components( COMPONENTS [ [...]] [SKIP_DEPENDENCY_HANDLING]) [SKIP_PKG_CONFIG]) Creates an imported library target for each component. The operation of this macro depends on the presence of a number of CMake variables. The __lib variable should contain the name of this library, and __header variable should contain the name of a header file associated with it (whatever relative path is normally passed to '#include'). __header_subdir variable can be used to specify which subdirectory of the include path the headers will be found in. ``ecm_find_package_components()`` will then search for the library and include directory (creating appropriate cache variables) and create an imported library target named ::. Additional variables can be used to provide additional information: If ``SKIP_PKG_CONFIG``, the __pkg_config variable is set, and pkg-config is found, the pkg-config module given by __pkg_config will be searched for and used to help locate the library and header file. It will also be used to set __VERSION. Note that if version information is found via pkg-config, __FIND_VERSION can be set to require a particular version for each component. If ``SKIP_DEPENDENCY_HANDLING`` is not set, the ``INTERFACE_LINK_LIBRARIES`` property of the imported target for will be set to contain the imported targets for the components listed in __component_deps. _FOUND will also be set to ``FALSE`` if any of the components in __component_deps are not found. This requires the components in __component_deps to be listed before in the ``COMPONENTS`` argument. The following variables will be set: ``_TARGETS`` the imported targets ``_LIBRARIES`` the found libraries ``_INCLUDE_DIRS`` the combined required include directories for the components ``_DEFINITIONS`` the "other" CFLAGS provided by pkg-config, if any ``_VERSION`` the value of ``__VERSION`` for the first component that has this variable set (note that components are searched for in the order they are passed to the macro), although if it is already set, it will not be altered .. note:: These variables are never cleared, so if ``ecm_find_package_handle_library_components()`` is called multiple times with different components (typically because of multiple ``find_package()`` calls) then ``_TARGETS``, for example, will contain all the targets found in any call (although no duplicates). Since pre-1.0.0. #]=======================================================================] include(CMakeParseArguments) macro(ecm_find_package_version_check module_name) if(CMAKE_VERSION VERSION_LESS 3.16.0) message(FATAL_ERROR "CMake 3.16.0 is required by Find${module_name}.cmake") endif() if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 3.16.0) message(AUTHOR_WARNING "Your project should require at least CMake 3.16.0 to use Find${module_name}.cmake") endif() endmacro() macro(ecm_find_package_parse_components module_name) set(ecm_fppc_options SKIP_DEPENDENCY_HANDLING) set(ecm_fppc_oneValueArgs RESULT_VAR) set(ecm_fppc_multiValueArgs KNOWN_COMPONENTS DEFAULT_COMPONENTS) cmake_parse_arguments(ECM_FPPC "${ecm_fppc_options}" "${ecm_fppc_oneValueArgs}" "${ecm_fppc_multiValueArgs}" ${ARGN}) if(ECM_FPPC_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unexpected arguments to ecm_find_package_parse_components: ${ECM_FPPC_UNPARSED_ARGUMENTS}") endif() if(NOT ECM_FPPC_RESULT_VAR) message(FATAL_ERROR "Missing RESULT_VAR argument to ecm_find_package_parse_components") endif() if(NOT ECM_FPPC_KNOWN_COMPONENTS) message(FATAL_ERROR "Missing KNOWN_COMPONENTS argument to ecm_find_package_parse_components") endif() if(NOT ECM_FPPC_DEFAULT_COMPONENTS) set(ECM_FPPC_DEFAULT_COMPONENTS ${ECM_FPPC_KNOWN_COMPONENTS}) endif() if(${module_name}_FIND_COMPONENTS) set(ecm_fppc_requestedComps ${${module_name}_FIND_COMPONENTS}) if(NOT ECM_FPPC_SKIP_DEPENDENCY_HANDLING) # Make sure deps are included foreach(ecm_fppc_comp ${ecm_fppc_requestedComps}) foreach(ecm_fppc_dep_comp ${${module_name}_${ecm_fppc_comp}_component_deps}) list(FIND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}" ecm_fppc_index) if("${ecm_fppc_index}" STREQUAL "-1") if(NOT ${module_name}_FIND_QUIETLY) message(STATUS "${module_name}: ${ecm_fppc_comp} requires ${${module_name}_${ecm_fppc_comp}_component_deps}") endif() list(APPEND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}") endif() endforeach() endforeach() else() message(STATUS "Skipping dependency handling for ${module_name}") endif() list(REMOVE_DUPLICATES ecm_fppc_requestedComps) # This makes sure components are listed in the same order as # KNOWN_COMPONENTS (potentially important for inter-dependencies) set(${ECM_FPPC_RESULT_VAR}) foreach(ecm_fppc_comp ${ECM_FPPC_KNOWN_COMPONENTS}) list(FIND ecm_fppc_requestedComps "${ecm_fppc_comp}" ecm_fppc_index) if(NOT "${ecm_fppc_index}" STREQUAL "-1") list(APPEND ${ECM_FPPC_RESULT_VAR} "${ecm_fppc_comp}") list(REMOVE_AT ecm_fppc_requestedComps ${ecm_fppc_index}) endif() endforeach() # if there are any left, they are unknown components if(ecm_fppc_requestedComps) set(ecm_fppc_msgType STATUS) if(${module_name}_FIND_REQUIRED) set(ecm_fppc_msgType FATAL_ERROR) endif() if(NOT ${module_name}_FIND_QUIETLY) message(${ecm_fppc_msgType} "${module_name}: requested unknown components ${ecm_fppc_requestedComps}") endif() return() endif() else() set(${ECM_FPPC_RESULT_VAR} ${ECM_FPPC_DEFAULT_COMPONENTS}) endif() endmacro() macro(ecm_find_package_handle_library_components module_name) set(ecm_fpwc_options SKIP_PKG_CONFIG SKIP_DEPENDENCY_HANDLING) set(ecm_fpwc_oneValueArgs) set(ecm_fpwc_multiValueArgs COMPONENTS) cmake_parse_arguments(ECM_FPWC "${ecm_fpwc_options}" "${ecm_fpwc_oneValueArgs}" "${ecm_fpwc_multiValueArgs}" ${ARGN}) if(ECM_FPWC_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unexpected arguments to ecm_find_package_handle_components: ${ECM_FPWC_UNPARSED_ARGUMENTS}") endif() if(NOT ECM_FPWC_COMPONENTS) message(FATAL_ERROR "Missing COMPONENTS argument to ecm_find_package_handle_components") endif() include(FindPackageHandleStandardArgs) find_package(PkgConfig QUIET) foreach(ecm_fpwc_comp ${ECM_FPWC_COMPONENTS}) set(ecm_fpwc_dep_vars) set(ecm_fpwc_dep_targets) if(NOT SKIP_DEPENDENCY_HANDLING) foreach(ecm_fpwc_dep ${${module_name}_${ecm_fpwc_comp}_component_deps}) list(APPEND ecm_fpwc_dep_vars "${module_name}_${ecm_fpwc_dep}_FOUND") list(APPEND ecm_fpwc_dep_targets "${module_name}::${ecm_fpwc_dep}") endforeach() endif() if(NOT ECM_FPWC_SKIP_PKG_CONFIG AND ${module_name}_${ecm_fpwc_comp}_pkg_config) pkg_check_modules(PKG_${module_name}_${ecm_fpwc_comp} QUIET ${${module_name}_${ecm_fpwc_comp}_pkg_config}) endif() find_path(${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR NAMES ${${module_name}_${ecm_fpwc_comp}_header} HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_INCLUDE_DIRS} PATH_SUFFIXES ${${module_name}_${ecm_fpwc_comp}_header_subdir} ) find_library(${module_name}_${ecm_fpwc_comp}_LIBRARY NAMES ${${module_name}_${ecm_fpwc_comp}_lib} HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_LIBRARY_DIRS} ) set(${module_name}_${ecm_fpwc_comp}_VERSION "${PKG_${module_name}_${ecm_fpwc_comp}_VERSION}") if(NOT ${module_name}_VERSION) set(${module_name}_VERSION ${${module_name}_${ecm_fpwc_comp}_VERSION}) endif() set(FPHSA_NAME_MISMATCHED 1) find_package_handle_standard_args(${module_name}_${ecm_fpwc_comp} FOUND_VAR ${module_name}_${ecm_fpwc_comp}_FOUND REQUIRED_VARS ${module_name}_${ecm_fpwc_comp}_LIBRARY ${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR ${ecm_fpwc_dep_vars} VERSION_VAR ${module_name}_${ecm_fpwc_comp}_VERSION ) unset(FPHSA_NAME_MISMATCHED) mark_as_advanced( ${module_name}_${ecm_fpwc_comp}_LIBRARY ${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR ) if(${module_name}_${ecm_fpwc_comp}_FOUND) list(APPEND ${module_name}_LIBRARIES "${${module_name}_${ecm_fpwc_comp}_LIBRARY}") list(APPEND ${module_name}_INCLUDE_DIRS "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}") set(${module_name}_DEFINITIONS ${${module_name}_DEFINITIONS} ${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}) if(NOT TARGET ${module_name}::${ecm_fpwc_comp}) add_library(${module_name}::${ecm_fpwc_comp} UNKNOWN IMPORTED) set_target_properties(${module_name}::${ecm_fpwc_comp} PROPERTIES IMPORTED_LOCATION "${${module_name}_${ecm_fpwc_comp}_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}" INTERFACE_INCLUDE_DIRECTORIES "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}" INTERFACE_LINK_LIBRARIES "${ecm_fpwc_dep_targets}" ) endif() list(APPEND ${module_name}_TARGETS "${module_name}::${ecm_fpwc_comp}") endif() endforeach() if(${module_name}_LIBRARIES) list(REMOVE_DUPLICATES ${module_name}_LIBRARIES) endif() if(${module_name}_INCLUDE_DIRS) list(REMOVE_DUPLICATES ${module_name}_INCLUDE_DIRS) endif() if(${module_name}_DEFINITIONS) list(REMOVE_DUPLICATES ${module_name}_DEFINITIONS) endif() if(${module_name}_TARGETS) list(REMOVE_DUPLICATES ${module_name}_TARGETS) endif() endmacro() ================================================ FILE: cmake/ECMFindModuleHelpersStub.cmake ================================================ include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpers.cmake) ================================================ FILE: cmake/FindGTK3.cmake ================================================ # SPDX-FileCopyrightText: 2022 Andrea Pappacoda # SPDX-License-Identifier: ISC include(FindPackageHandleStandardArgs) find_package(PkgConfig) if (PKG_CONFIG_FOUND) pkg_search_module(GTK3 IMPORTED_TARGET gtk+-3.0) if (GTK3_FOUND) add_library(GTK3::gtk ALIAS PkgConfig::GTK3) endif() find_package_handle_standard_args(GTK3 REQUIRED_VARS GTK3_LINK_LIBRARIES VERSION_VAR GTK3_VERSION ) endif() ================================================ FILE: cmake/FindWayland.cmake ================================================ # SPDX-FileCopyrightText: 2014 Alex Merry # SPDX-FileCopyrightText: 2014 Martin Gräßlin # # SPDX-License-Identifier: BSD-3-Clause #[=======================================================================[.rst: FindWayland ----------- Try to find Wayland. This is a component-based find module, which makes use of the COMPONENTS and OPTIONAL_COMPONENTS arguments to find_module. The following components are available:: Client Server Cursor Egl If no components are specified, this module will act as though all components were passed to OPTIONAL_COMPONENTS. This module will define the following variables, independently of the components searched for or found: ``Wayland_FOUND`` TRUE if (the requested version of) Wayland is available ``Wayland_VERSION`` Found Wayland version ``Wayland_TARGETS`` A list of all targets imported by this module (note that there may be more than the components that were requested) ``Wayland_LIBRARIES`` This can be passed to target_link_libraries() instead of the imported targets ``Wayland_INCLUDE_DIRS`` This should be passed to target_include_directories() if the targets are not used for linking ``Wayland_DEFINITIONS`` This should be passed to target_compile_options() if the targets are not used for linking ``Wayland_DATADIR`` The core wayland protocols data directory Since 5.73.0 For each searched-for components, ``Wayland__FOUND`` will be set to TRUE if the corresponding Wayland library was found, and FALSE otherwise. If ``Wayland__FOUND`` is TRUE, the imported target ``Wayland::`` will be defined. This module will also attempt to determine ``Wayland_*_VERSION`` variables for each imported target, although ``Wayland_VERSION`` should normally be sufficient. In general we recommend using the imported targets, as they are easier to use and provide more control. Bear in mind, however, that if any target is in the link interface of an exported library, it must be made available by the package config file. Since pre-1.0.0. #]=======================================================================] include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpersStub.cmake) ecm_find_package_version_check(Wayland) set(Wayland_known_components Client Server Cursor Egl ) foreach(_comp ${Wayland_known_components}) string(TOLOWER "${_comp}" _lc_comp) set(Wayland_${_comp}_component_deps) set(Wayland_${_comp}_pkg_config "wayland-${_lc_comp}") set(Wayland_${_comp}_lib "wayland-${_lc_comp}") set(Wayland_${_comp}_header "wayland-${_lc_comp}.h") endforeach() set(Wayland_Egl_component_deps Client) ecm_find_package_parse_components(Wayland RESULT_VAR Wayland_components KNOWN_COMPONENTS ${Wayland_known_components} ) ecm_find_package_handle_library_components(Wayland COMPONENTS ${Wayland_components} ) # If pkg-config didn't provide us with version information, # try to extract it from wayland-version.h # (Note that the version from wayland-egl.pc will probably be # the Mesa version, rather than the Wayland version, but that # version will be ignored as we always find wayland-client.pc # first). if(NOT Wayland_VERSION) find_file(Wayland_VERSION_HEADER NAMES wayland-version.h HINTS ${Wayland_INCLUDE_DIRS} ) mark_as_advanced(Wayland_VERSION_HEADER) if(Wayland_VERSION_HEADER) file(READ ${Wayland_VERSION_HEADER} _wayland_version_header_contents) string(REGEX REPLACE "^.*[ \t]+WAYLAND_VERSION[ \t]+\"([0-9.]*)\".*$" "\\1" Wayland_VERSION "${_wayland_version_header_contents}" ) unset(_wayland_version_header_contents) endif() endif() find_package_handle_standard_args(Wayland FOUND_VAR Wayland_FOUND REQUIRED_VARS Wayland_LIBRARIES VERSION_VAR Wayland_VERSION HANDLE_COMPONENTS ) pkg_get_variable(Wayland_DATADIR wayland-scanner pkgdatadir) if (CMAKE_CROSSCOMPILING AND (NOT EXISTS "${Wayland_DATADIR}/wayland.xml")) # PKG_CONFIG_SYSROOT_DIR only applies to -I and -L flags, so pkg-config # does not prepend CMAKE_SYSROOT when cross-compiling unless you pass # --define-prefix explicitly. Therefore we have to manually do prepend # it here when cross-compiling. # See https://gitlab.kitware.com/cmake/cmake/-/issues/16647#note_844761 set(Wayland_DATADIR ${CMAKE_SYSROOT}${Wayland_DATADIR}) endif() if (NOT EXISTS "${Wayland_DATADIR}/wayland.xml") message(WARNING "Could not find wayland.xml in ${Wayland_DATADIR}") endif() include(FeatureSummary) set_package_properties(Wayland PROPERTIES URL "https://wayland.freedesktop.org/" DESCRIPTION "C library implementation of the Wayland protocol: a protocol for a compositor to talk to its clients" ) ================================================ FILE: cmake/FindWaylandProtocols.cmake ================================================ # SPDX-FileCopyrightText: 2019 Vlad Zahorodnii # # SPDX-License-Identifier: BSD-3-Clause #[=======================================================================[.rst: FindWaylandProtocols -------------------- Try to find wayland-protocols on a Unix system. This will define the following variables: ``WaylandProtocols_FOUND`` True if (the requested version of) wayland-protocols is available ``WaylandProtocols_VERSION`` The version of wayland-protocols ``WaylandProtocols_DATADIR`` The wayland protocols data directory #]=======================================================================] find_package(PkgConfig QUIET) pkg_check_modules(PKG_wayland_protocols QUIET wayland-protocols) set(WaylandProtocols_VERSION ${PKG_wayland_protocols_VERSION}) pkg_get_variable(WaylandProtocols_DATADIR wayland-protocols pkgdatadir) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(WaylandProtocols FOUND_VAR WaylandProtocols_FOUND REQUIRED_VARS WaylandProtocols_DATADIR VERSION_VAR WaylandProtocols_VERSION ) include(FeatureSummary) set_package_properties(WaylandProtocols PROPERTIES DESCRIPTION "Specifications of extended Wayland protocols" URL "https://wayland.freedesktop.org/" ) ================================================ FILE: cmake/FindWaylandScanner.cmake ================================================ # SPDX-FileCopyrightText: 2012-2014 Pier Luigi Fiorini # # SPDX-License-Identifier: BSD-3-Clause #[=======================================================================[.rst: FindWaylandScanner ------------------ Try to find wayland-scanner. If the wayland-scanner executable is not in your PATH, you can provide an alternative name or full path location with the ``WaylandScanner_EXECUTABLE`` variable. This will define the following variables: ``WaylandScanner_FOUND`` True if wayland-scanner is available. ``WaylandScanner_EXECUTABLE`` The wayland-scanner executable. If ``WaylandScanner_FOUND`` is TRUE, it will also define the following imported target: ``Wayland::Scanner`` The wayland-scanner executable. This module provides the following functions to generate C protocol implementations: - ``ecm_add_wayland_client_protocol`` - ``ecm_add_wayland_server_protocol`` :: ecm_add_wayland_client_protocol( PROTOCOL BASENAME ) ecm_add_wayland_client_protocol( PROTOCOL BASENAME ) Generate Wayland client protocol files from ```` XML definition for the ```` interface and append those files to ```` or ````. :: ecm_add_wayland_server_protocol( PROTOCOL BASENAME ) ecm_add_wayland_server_protocol( PROTOCOL BASENAME ) Generate Wayland server protocol files from ```` XML definition for the ```` interface and append those files to ```` or ````. Since 1.4.0. #]=======================================================================] include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpersStub.cmake) ecm_find_package_version_check(WaylandScanner) # Find wayland-scanner find_program(WaylandScanner_EXECUTABLE NAMES wayland-scanner) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(WaylandScanner FOUND_VAR WaylandScanner_FOUND REQUIRED_VARS WaylandScanner_EXECUTABLE ) mark_as_advanced(WaylandScanner_EXECUTABLE) if(NOT TARGET Wayland::Scanner AND WaylandScanner_FOUND) add_executable(Wayland::Scanner IMPORTED) set_target_properties(Wayland::Scanner PROPERTIES IMPORTED_LOCATION "${WaylandScanner_EXECUTABLE}" ) endif() include(FeatureSummary) set_package_properties(WaylandScanner PROPERTIES URL "https://wayland.freedesktop.org/" DESCRIPTION "Executable that converts XML protocol files to C code" ) include(CMakeParseArguments) function(ecm_add_wayland_client_protocol target_or_sources_var) # Parse arguments set(oneValueArgs PROTOCOL BASENAME) cmake_parse_arguments(ARGS "" "${oneValueArgs}" "" ${ARGN}) if(ARGS_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown keywords given to ecm_add_wayland_client_protocol(): \"${ARGS_UNPARSED_ARGUMENTS}\"") endif() get_filename_component(_infile ${ARGS_PROTOCOL} ABSOLUTE) set(_client_header "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-client-protocol.h") set(_code "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-protocol.c") set_source_files_properties(${_client_header} GENERATED) set_source_files_properties(${_code} GENERATED) set_property(SOURCE ${_client_header} ${_code} PROPERTY SKIP_AUTOMOC ON) add_custom_command(OUTPUT "${_client_header}" COMMAND ${WaylandScanner_EXECUTABLE} client-header ${_infile} ${_client_header} DEPENDS ${_infile} VERBATIM) add_custom_command(OUTPUT "${_code}" COMMAND ${WaylandScanner_EXECUTABLE} public-code ${_infile} ${_code} DEPENDS ${_infile} ${_client_header} VERBATIM) if (TARGET ${target_or_sources_var}) target_sources(${target_or_sources_var} PRIVATE "${_client_header}" "${_code}") else() list(APPEND ${target_or_sources_var} "${_client_header}" "${_code}") set(${target_or_sources_var} ${${target_or_sources_var}} PARENT_SCOPE) endif() endfunction() function(ecm_add_wayland_server_protocol target_or_sources_var) # Parse arguments set(oneValueArgs PROTOCOL BASENAME) cmake_parse_arguments(ARGS "" "${oneValueArgs}" "" ${ARGN}) if(ARGS_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown keywords given to ecm_add_wayland_server_protocol(): \"${ARGS_UNPARSED_ARGUMENTS}\"") endif() ecm_add_wayland_client_protocol(${target_or_sources_var} PROTOCOL ${ARGS_PROTOCOL} BASENAME ${ARGS_BASENAME}) get_filename_component(_infile ${ARGS_PROTOCOL} ABSOLUTE) set(_server_header "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-server-protocol.h") set(_server_code "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-protocol.c") set_property(SOURCE ${_server_header} ${_server_code} PROPERTY SKIP_AUTOMOC ON) set_source_files_properties(${_server_header} GENERATED) add_custom_command(OUTPUT "${_server_header}" COMMAND ${WaylandScanner_EXECUTABLE} server-header ${_infile} ${_server_header} DEPENDS ${_infile} VERBATIM) if (TARGET ${target_or_sources_var}) target_sources(${target_or_sources_var} PRIVATE "${_server_header}") else() list(APPEND ${target_or_sources_var} "${_server_header}") set(${target_or_sources_var} ${${target_or_sources_var}} PARENT_SCOPE) endif() endfunction() ================================================ FILE: cmake/FindZArchive.cmake ================================================ # SPDX-FileCopyrightText: 2022 Andrea Pappacoda # SPDX-License-Identifier: ISC find_package(PkgConfig) if (PKG_CONFIG_FOUND) pkg_search_module(zarchive IMPORTED_TARGET GLOBAL zarchive) if (zarchive_FOUND) add_library(ZArchive::zarchive ALIAS PkgConfig::zarchive) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(ZArchive REQUIRED_VARS zarchive_LINK_LIBRARIES zarchive_FOUND VERSION_VAR zarchive_VERSION ) ================================================ FILE: cmake/Findbluez.cmake ================================================ # SPDX-FileCopyrightText: 2022 Andrea Pappacoda # SPDX-License-Identifier: ISC find_package(bluez CONFIG) if (NOT bluez_FOUND) find_package(PkgConfig) if (PKG_CONFIG_FOUND) pkg_search_module(bluez IMPORTED_TARGET GLOBAL bluez-1.0 bluez) if (bluez_FOUND) add_library(bluez::bluez ALIAS PkgConfig::bluez) endif () endif () endif () find_package_handle_standard_args(bluez REQUIRED_VARS bluez_LINK_LIBRARIES bluez_FOUND VERSION_VAR bluez_VERSION ) ================================================ FILE: cmake/Findimgui.cmake ================================================ # SPDX-FileCopyrightText: 2022 Andrea Pappacoda # SPDX-License-Identifier: ISC include(FindPackageHandleStandardArgs) find_package(imgui CONFIG) if (imgui_FOUND) # Use upstream imguiConfig.cmake if possible find_package_handle_standard_args(imgui CONFIG_MODE) else() # Fallback to pkg-config otherwise find_package(PkgConfig) if (PKG_CONFIG_FOUND) pkg_search_module(imgui IMPORTED_TARGET GLOBAL imgui) if (imgui_FOUND) add_library(imgui::imgui ALIAS PkgConfig::imgui) endif() endif() find_package_handle_standard_args(imgui REQUIRED_VARS imgui_LINK_LIBRARIES imgui_FOUND VERSION_VAR imgui_VERSION ) endif() ================================================ FILE: cmake/Findlibusb.cmake ================================================ # SPDX-FileCopyrightText: 2022 Andrea Pappacoda # SPDX-License-Identifier: ISC find_package(libusb CONFIG) if (NOT libusb_FOUND) find_package(PkgConfig) if (PKG_CONFIG_FOUND) pkg_search_module(libusb IMPORTED_TARGET GLOBAL libusb-1.0 libusb) if (libusb_FOUND) add_library(libusb::libusb ALIAS PkgConfig::libusb) endif () endif () endif () find_package_handle_standard_args(libusb REQUIRED_VARS libusb_LINK_LIBRARIES libusb_FOUND VERSION_VAR libusb_VERSION ) ================================================ FILE: cmake/FindwxWidgets.cmake ================================================ # SPDX-FileCopyrightText: 2022 Andrea Pappacoda # SPDX-License-Identifier: ISC include(FindPackageHandleStandardArgs) find_package(wxWidgets CONFIG COMPONENTS ${wxWidgets_FIND_COMPONENTS}) if (wxWidgets_FOUND) # Use upstream wxWidgetsConfig.cmake if possible find_package_handle_standard_args(wxWidgets CONFIG_MODE) else() # Fall back to CMake's FindwxWidgets # Temporarily unset CMAKE_MODULE_PATH to avoid calling the current find # module recursively set(_tmp_module_path "${CMAKE_MODULE_PATH}") set(CMAKE_MODULE_PATH "") find_package(wxWidgets MODULE QUIET COMPONENTS ${wxWidgets_FIND_COMPONENTS}) set(CMAKE_MODULE_PATH "${_tmp_module_path}") unset(_tmp_module_path) if (wxWidgets_FOUND) add_library(wx::base IMPORTED INTERFACE) target_include_directories(wx::base INTERFACE ${wxWidgets_INCLUDE_DIRS}) target_link_libraries(wx::base INTERFACE ${wxWidgets_LIBRARIES}) target_link_directories(wx::base INTERFACE ${wxWidgets_LIBRARY_DIRS}) target_compile_definitions(wx::base INTERFACE ${wxWidgets_DEFINITIONS}) target_compile_options(wx::base INTERFACE ${wxWidgets_CXX_FLAGS}) # FindwxWidgets sets everything into a single set of variables, so it is # impossible to tell what libraries are required for what component. # To be compatible with wxWidgetsConfig, we create an alias for each # component so that the user can still use target_link_libraries(wx::gl) foreach(component ${wxWidgets_FIND_COMPONENTS}) if (NOT component STREQUAL "base") # don't alias base to itself add_library(wx::${component} ALIAS wx::base) endif() endforeach() endif() find_package_handle_standard_args(wxWidgets REQUIRED_VARS wxWidgets_LIBRARIES wxWidgets_FOUND VERSION_VAR wxWidgets_VERSION_STRING ) endif() ================================================ FILE: cmake/Findzstd.cmake ================================================ # SPDX-FileCopyrightText: 2022 Andrea Pappacoda # SPDX-License-Identifier: ISC include(FindPackageHandleStandardArgs) find_package(zstd CONFIG) if (zstd_FOUND) # Use upstream zstdConfig.cmake if possible if (NOT TARGET zstd::zstd) if (TARGET zstd::libzstd_static) add_library(zstd::zstd ALIAS zstd::libzstd_static) elseif (TARGET zstd::libzstd_shared) add_library(zstd::zstd ALIAS zstd::libzstd_shared) endif() endif() find_package_handle_standard_args(zstd CONFIG_MODE) else() # Fallback to pkg-config otherwise find_package(PkgConfig) if (PKG_CONFIG_FOUND) pkg_search_module(libzstd IMPORTED_TARGET GLOBAL libzstd) if (libzstd_FOUND) add_library(zstd::zstd ALIAS PkgConfig::libzstd) endif() endif() find_package_handle_standard_args(zstd REQUIRED_VARS libzstd_LINK_LIBRARIES libzstd_FOUND VERSION_VAR libzstd_VERSION ) endif() ================================================ FILE: dependencies/DirectX_2010/XAudio2.h ================================================ /************************************************************************** * * Copyright (c) Microsoft Corporation. All rights reserved. * * File: xaudio2.h * Content: Declarations for the XAudio2 game audio API. * **************************************************************************/ #ifndef __XAUDIO2_INCLUDED__ #define __XAUDIO2_INCLUDED__ /************************************************************************** * * XAudio2 COM object class and interface IDs. * **************************************************************************/ #include "comdecl.h" // For DEFINE_CLSID and DEFINE_IID // XAudio 2.0 (March 2008 SDK) //DEFINE_CLSID(XAudio2, fac23f48, 31f5, 45a8, b4, 9b, 52, 25, d6, 14, 01, aa); //DEFINE_CLSID(XAudio2_Debug, fac23f48, 31f5, 45a8, b4, 9b, 52, 25, d6, 14, 01, db); // XAudio 2.1 (June 2008 SDK) //DEFINE_CLSID(XAudio2, e21a7345, eb21, 468e, be, 50, 80, 4d, b9, 7c, f7, 08); //DEFINE_CLSID(XAudio2_Debug, f7a76c21, 53d4, 46bb, ac, 53, 8b, 45, 9c, ae, 46, bd); // XAudio 2.2 (August 2008 SDK) //DEFINE_CLSID(XAudio2, b802058a, 464a, 42db, bc, 10, b6, 50, d6, f2, 58, 6a); //DEFINE_CLSID(XAudio2_Debug, 97dfb7e7, 5161, 4015, 87, a9, c7, 9e, 6a, 19, 52, cc); // XAudio 2.3 (November 2008 SDK) //DEFINE_CLSID(XAudio2, 4c5e637a, 16c7, 4de3, 9c, 46, 5e, d2, 21, 81, 96, 2d); //DEFINE_CLSID(XAudio2_Debug, ef0aa05d, 8075, 4e5d, be, ad, 45, be, 0c, 3c, cb, b3); // XAudio 2.4 (March 2009 SDK) //DEFINE_CLSID(XAudio2, 03219e78, 5bc3, 44d1, b9, 2e, f6, 3d, 89, cc, 65, 26); //DEFINE_CLSID(XAudio2_Debug, 4256535c, 1ea4, 4d4b, 8a, d5, f9, db, 76, 2e, ca, 9e); // XAudio 2.5 (August 2009 SDK) //DEFINE_CLSID(XAudio2, 4c9b6dde, 6809, 46e6, a2, 78, 9b, 6a, 97, 58, 86, 70); //DEFINE_CLSID(XAudio2_Debug, 715bdd1a, aa82, 436b, b0, fa, 6a, ce, a3, 9b, d0, a1); // XAudio 2.6 (February 2010 SDK) //DEFINE_CLSID(XAudio2, 3eda9b49, 2085, 498b, 9b, b2, 39, a6, 77, 84, 93, de); //DEFINE_CLSID(XAudio2_Debug, 47199894, 7cc2, 444d, 98, 73, ce, d2, 56, 2c, c6, 0e); // XAudio 2.7 (June 2010 SDK) #ifdef __clang__ class __declspec(uuid("5a508685-a254-4fba-9b82-9a24b00306af")) XAudio2; extern "C" const GUID CLSID_XAudio2; class __declspec(uuid("db05ea35-0329-4d4b-a53a-6dead03d3852")) XAudio2_Debug; extern "C" const GUID CLSID_XAudio2_Debug; struct __declspec(uuid("8bcf1f58-9fe7-4583-8ac6-e2adc465c8bb")) IXAudio2; extern "C" const GUID IID_IXAudio2; #else DEFINE_CLSID(XAudio2, 5a508685, a254, 4fba, 9b, 82, 9a, 24, b0, 03, 06, af); DEFINE_CLSID(XAudio2_Debug, db05ea35, 0329, 4d4b, a5, 3a, 6d, ea, d0, 3d, 38, 52); DEFINE_IID(IXAudio2, 8bcf1f58, 9fe7, 4583, 8a, c6, e2, ad, c4, 65, c8, bb); #endif // Ignore the rest of this header if only the GUID definitions were requested #ifndef GUID_DEFS_ONLY #ifdef _XBOX #include // Xbox COM declarations (IUnknown, etc) #else #include // Windows COM declarations #endif #include // Markers for documenting API semantics #include "audiodefs.h" // Basic audio data types and constants #include "xma2defs.h" // Data types and constants for XMA2 audio // All structures defined in this file use tight field packing #pragma pack(push, 1) /************************************************************************** * * XAudio2 constants, flags and error codes. * **************************************************************************/ // Numeric boundary values #define XAUDIO2_MAX_BUFFER_BYTES 0x80000000 // Maximum bytes allowed in a source buffer #define XAUDIO2_MAX_QUEUED_BUFFERS 64 // Maximum buffers allowed in a voice queue #define XAUDIO2_MAX_BUFFERS_SYSTEM 2 // Maximum buffers allowed for system threads (Xbox 360 only) #define XAUDIO2_MAX_AUDIO_CHANNELS 64 // Maximum channels in an audio stream #define XAUDIO2_MIN_SAMPLE_RATE 1000 // Minimum audio sample rate supported #define XAUDIO2_MAX_SAMPLE_RATE 200000 // Maximum audio sample rate supported #define XAUDIO2_MAX_VOLUME_LEVEL 16777216.0f // Maximum acceptable volume level (2^24) #define XAUDIO2_MIN_FREQ_RATIO (1/1024.0f) // Minimum SetFrequencyRatio argument #define XAUDIO2_MAX_FREQ_RATIO 1024.0f // Maximum MaxFrequencyRatio argument #define XAUDIO2_DEFAULT_FREQ_RATIO 2.0f // Default MaxFrequencyRatio argument #define XAUDIO2_MAX_FILTER_ONEOVERQ 1.5f // Maximum XAUDIO2_FILTER_PARAMETERS.OneOverQ #define XAUDIO2_MAX_FILTER_FREQUENCY 1.0f // Maximum XAUDIO2_FILTER_PARAMETERS.Frequency #define XAUDIO2_MAX_LOOP_COUNT 254 // Maximum non-infinite XAUDIO2_BUFFER.LoopCount #define XAUDIO2_MAX_INSTANCES 8 // Maximum simultaneous XAudio2 objects on Xbox 360 // For XMA voices on Xbox 360 there is an additional restriction on the MaxFrequencyRatio // argument and the voice's sample rate: the product of these numbers cannot exceed 600000 // for one-channel voices or 300000 for voices with more than one channel. #define XAUDIO2_MAX_RATIO_TIMES_RATE_XMA_MONO 600000 #define XAUDIO2_MAX_RATIO_TIMES_RATE_XMA_MULTICHANNEL 300000 // Numeric values with special meanings #define XAUDIO2_COMMIT_NOW 0 // Used as an OperationSet argument #define XAUDIO2_COMMIT_ALL 0 // Used in IXAudio2::CommitChanges #define XAUDIO2_INVALID_OPSET (UINT32)(-1) // Not allowed for OperationSet arguments #define XAUDIO2_NO_LOOP_REGION 0 // Used in XAUDIO2_BUFFER.LoopCount #define XAUDIO2_LOOP_INFINITE 255 // Used in XAUDIO2_BUFFER.LoopCount #define XAUDIO2_DEFAULT_CHANNELS 0 // Used in CreateMasteringVoice #define XAUDIO2_DEFAULT_SAMPLERATE 0 // Used in CreateMasteringVoice // Flags #define XAUDIO2_DEBUG_ENGINE 0x0001 // Used in XAudio2Create on Windows only #define XAUDIO2_VOICE_NOPITCH 0x0002 // Used in IXAudio2::CreateSourceVoice #define XAUDIO2_VOICE_NOSRC 0x0004 // Used in IXAudio2::CreateSourceVoice #define XAUDIO2_VOICE_USEFILTER 0x0008 // Used in IXAudio2::CreateSource/SubmixVoice #define XAUDIO2_VOICE_MUSIC 0x0010 // Used in IXAudio2::CreateSourceVoice #define XAUDIO2_PLAY_TAILS 0x0020 // Used in IXAudio2SourceVoice::Stop #define XAUDIO2_END_OF_STREAM 0x0040 // Used in XAUDIO2_BUFFER.Flags #define XAUDIO2_SEND_USEFILTER 0x0080 // Used in XAUDIO2_SEND_DESCRIPTOR.Flags // Default parameters for the built-in filter #define XAUDIO2_DEFAULT_FILTER_TYPE LowPassFilter #define XAUDIO2_DEFAULT_FILTER_FREQUENCY XAUDIO2_MAX_FILTER_FREQUENCY #define XAUDIO2_DEFAULT_FILTER_ONEOVERQ 1.0f // Internal XAudio2 constants #ifdef _XBOX #define XAUDIO2_QUANTUM_NUMERATOR 2 // On Xbox 360, XAudio2 processes audio #define XAUDIO2_QUANTUM_DENOMINATOR 375 // in 5.333ms chunks (= 2/375 seconds) #else #define XAUDIO2_QUANTUM_NUMERATOR 1 // On Windows, XAudio2 processes audio #define XAUDIO2_QUANTUM_DENOMINATOR 100 // in 10ms chunks (= 1/100 seconds) #endif #define XAUDIO2_QUANTUM_MS (1000.0f * XAUDIO2_QUANTUM_NUMERATOR / XAUDIO2_QUANTUM_DENOMINATOR) // XAudio2 error codes #define FACILITY_XAUDIO2 0x896 #define XAUDIO2_E_INVALID_CALL 0x88960001 // An API call or one of its arguments was illegal #define XAUDIO2_E_XMA_DECODER_ERROR 0x88960002 // The XMA hardware suffered an unrecoverable error #define XAUDIO2_E_XAPO_CREATION_FAILED 0x88960003 // XAudio2 failed to initialize an XAPO effect #define XAUDIO2_E_DEVICE_INVALIDATED 0x88960004 // An audio device became unusable (unplugged, etc) /************************************************************************** * * Forward declarations for the XAudio2 interfaces. * **************************************************************************/ #ifdef __cplusplus #define FWD_DECLARE(x) interface x #else #define FWD_DECLARE(x) typedef interface x x #endif FWD_DECLARE(IXAudio2); FWD_DECLARE(IXAudio2Voice); FWD_DECLARE(IXAudio2SourceVoice); FWD_DECLARE(IXAudio2SubmixVoice); FWD_DECLARE(IXAudio2MasteringVoice); FWD_DECLARE(IXAudio2EngineCallback); FWD_DECLARE(IXAudio2VoiceCallback); /************************************************************************** * * XAudio2 structures and enumerations. * **************************************************************************/ // Used in IXAudio2::Initialize #ifdef _XBOX typedef enum XAUDIO2_XBOX_HWTHREAD_SPECIFIER { XboxThread0 = 0x01, XboxThread1 = 0x02, XboxThread2 = 0x04, XboxThread3 = 0x08, XboxThread4 = 0x10, XboxThread5 = 0x20, XAUDIO2_ANY_PROCESSOR = XboxThread4, XAUDIO2_DEFAULT_PROCESSOR = XAUDIO2_ANY_PROCESSOR } XAUDIO2_XBOX_HWTHREAD_SPECIFIER, XAUDIO2_PROCESSOR; #else typedef enum XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER { Processor1 = 0x00000001, Processor2 = 0x00000002, Processor3 = 0x00000004, Processor4 = 0x00000008, Processor5 = 0x00000010, Processor6 = 0x00000020, Processor7 = 0x00000040, Processor8 = 0x00000080, Processor9 = 0x00000100, Processor10 = 0x00000200, Processor11 = 0x00000400, Processor12 = 0x00000800, Processor13 = 0x00001000, Processor14 = 0x00002000, Processor15 = 0x00004000, Processor16 = 0x00008000, Processor17 = 0x00010000, Processor18 = 0x00020000, Processor19 = 0x00040000, Processor20 = 0x00080000, Processor21 = 0x00100000, Processor22 = 0x00200000, Processor23 = 0x00400000, Processor24 = 0x00800000, Processor25 = 0x01000000, Processor26 = 0x02000000, Processor27 = 0x04000000, Processor28 = 0x08000000, Processor29 = 0x10000000, Processor30 = 0x20000000, Processor31 = 0x40000000, Processor32 = 0x80000000, XAUDIO2_ANY_PROCESSOR = 0xffffffff, XAUDIO2_DEFAULT_PROCESSOR = XAUDIO2_ANY_PROCESSOR } XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER, XAUDIO2_PROCESSOR; #endif // Used in XAUDIO2_DEVICE_DETAILS below to describe the types of applications // that the user has specified each device as a default for. 0 means that the // device isn't the default for any role. typedef enum XAUDIO2_DEVICE_ROLE { NotDefaultDevice = 0x0, DefaultConsoleDevice = 0x1, DefaultMultimediaDevice = 0x2, DefaultCommunicationsDevice = 0x4, DefaultGameDevice = 0x8, GlobalDefaultDevice = 0xf, InvalidDeviceRole = ~GlobalDefaultDevice } XAUDIO2_DEVICE_ROLE; // Returned by IXAudio2::GetDeviceDetails typedef struct XAUDIO2_DEVICE_DETAILS { WCHAR DeviceID[256]; // String identifier for the audio device. WCHAR DisplayName[256]; // Friendly name suitable for display to a human. XAUDIO2_DEVICE_ROLE Role; // Roles that the device should be used for. WAVEFORMATEXTENSIBLE OutputFormat; // The device's native PCM audio output format. } XAUDIO2_DEVICE_DETAILS; // Returned by IXAudio2Voice::GetVoiceDetails typedef struct XAUDIO2_VOICE_DETAILS { UINT32 CreationFlags; // Flags the voice was created with. UINT32 InputChannels; // Channels in the voice's input audio. UINT32 InputSampleRate; // Sample rate of the voice's input audio. } XAUDIO2_VOICE_DETAILS; // Used in XAUDIO2_VOICE_SENDS below typedef struct XAUDIO2_SEND_DESCRIPTOR { UINT32 Flags; // Either 0 or XAUDIO2_SEND_USEFILTER. IXAudio2Voice* pOutputVoice; // This send's destination voice. } XAUDIO2_SEND_DESCRIPTOR; // Used in the voice creation functions and in IXAudio2Voice::SetOutputVoices typedef struct XAUDIO2_VOICE_SENDS { UINT32 SendCount; // Number of sends from this voice. XAUDIO2_SEND_DESCRIPTOR* pSends; // Array of SendCount send descriptors. } XAUDIO2_VOICE_SENDS; // Used in XAUDIO2_EFFECT_CHAIN below typedef struct XAUDIO2_EFFECT_DESCRIPTOR { IUnknown* pEffect; // Pointer to the effect object's IUnknown interface. BOOL InitialState; // TRUE if the effect should begin in the enabled state. UINT32 OutputChannels; // How many output channels the effect should produce. } XAUDIO2_EFFECT_DESCRIPTOR; // Used in the voice creation functions and in IXAudio2Voice::SetEffectChain typedef struct XAUDIO2_EFFECT_CHAIN { UINT32 EffectCount; // Number of effects in this voice's effect chain. XAUDIO2_EFFECT_DESCRIPTOR* pEffectDescriptors; // Array of effect descriptors. } XAUDIO2_EFFECT_CHAIN; // Used in XAUDIO2_FILTER_PARAMETERS below typedef enum XAUDIO2_FILTER_TYPE { LowPassFilter, // Attenuates frequencies above the cutoff frequency. BandPassFilter, // Attenuates frequencies outside a given range. HighPassFilter, // Attenuates frequencies below the cutoff frequency. NotchFilter // Attenuates frequencies inside a given range. } XAUDIO2_FILTER_TYPE; // Used in IXAudio2Voice::Set/GetFilterParameters and Set/GetOutputFilterParameters typedef struct XAUDIO2_FILTER_PARAMETERS { XAUDIO2_FILTER_TYPE Type; // Low-pass, band-pass or high-pass. float Frequency; // Radian frequency (2 * sin(pi*CutoffFrequency/SampleRate)); // must be >= 0 and <= XAUDIO2_MAX_FILTER_FREQUENCY // (giving a maximum CutoffFrequency of SampleRate/6). float OneOverQ; // Reciprocal of the filter's quality factor Q; // must be > 0 and <= XAUDIO2_MAX_FILTER_ONEOVERQ. } XAUDIO2_FILTER_PARAMETERS; // Used in IXAudio2SourceVoice::SubmitSourceBuffer typedef struct XAUDIO2_BUFFER { UINT32 Flags; // Either 0 or XAUDIO2_END_OF_STREAM. UINT32 AudioBytes; // Size of the audio data buffer in bytes. const BYTE* pAudioData; // Pointer to the audio data buffer. UINT32 PlayBegin; // First sample in this buffer to be played. UINT32 PlayLength; // Length of the region to be played in samples, // or 0 to play the whole buffer. UINT32 LoopBegin; // First sample of the region to be looped. UINT32 LoopLength; // Length of the desired loop region in samples, // or 0 to loop the entire buffer. UINT32 LoopCount; // Number of times to repeat the loop region, // or XAUDIO2_LOOP_INFINITE to loop forever. void* pContext; // Context value to be passed back in callbacks. } XAUDIO2_BUFFER; // Used in IXAudio2SourceVoice::SubmitSourceBuffer when submitting XWMA data. // NOTE: If an XWMA sound is submitted in more than one buffer, each buffer's // pDecodedPacketCumulativeBytes[PacketCount-1] value must be subtracted from // all the entries in the next buffer's pDecodedPacketCumulativeBytes array. // And whether a sound is submitted in more than one buffer or not, the final // buffer of the sound should use the XAUDIO2_END_OF_STREAM flag, or else the // client must call IXAudio2SourceVoice::Discontinuity after submitting it. typedef struct XAUDIO2_BUFFER_WMA { const UINT32* pDecodedPacketCumulativeBytes; // Decoded packet's cumulative size array. // Each element is the number of bytes accumulated // when the corresponding XWMA packet is decoded in // order. The array must have PacketCount elements. UINT32 PacketCount; // Number of XWMA packets submitted. Must be >= 1 and // divide evenly into XAUDIO2_BUFFER.AudioBytes. } XAUDIO2_BUFFER_WMA; // Returned by IXAudio2SourceVoice::GetState typedef struct XAUDIO2_VOICE_STATE { void* pCurrentBufferContext; // The pContext value provided in the XAUDIO2_BUFFER // that is currently being processed, or NULL if // there are no buffers in the queue. UINT32 BuffersQueued; // Number of buffers currently queued on the voice // (including the one that is being processed). UINT64 SamplesPlayed; // Total number of samples produced by the voice since // it began processing the current audio stream. } XAUDIO2_VOICE_STATE; // Returned by IXAudio2::GetPerformanceData typedef struct XAUDIO2_PERFORMANCE_DATA { // CPU usage information UINT64 AudioCyclesSinceLastQuery; // CPU cycles spent on audio processing since the // last call to StartEngine or GetPerformanceData. UINT64 TotalCyclesSinceLastQuery; // Total CPU cycles elapsed since the last call // (only counts the CPU XAudio2 is running on). UINT32 MinimumCyclesPerQuantum; // Fewest CPU cycles spent processing any one // audio quantum since the last call. UINT32 MaximumCyclesPerQuantum; // Most CPU cycles spent processing any one // audio quantum since the last call. // Memory usage information UINT32 MemoryUsageInBytes; // Total heap space currently in use. // Audio latency and glitching information UINT32 CurrentLatencyInSamples; // Minimum delay from when a sample is read from a // source buffer to when it reaches the speakers. UINT32 GlitchesSinceEngineStarted; // Audio dropouts since the engine was started. // Data about XAudio2's current workload UINT32 ActiveSourceVoiceCount; // Source voices currently playing. UINT32 TotalSourceVoiceCount; // Source voices currently existing. UINT32 ActiveSubmixVoiceCount; // Submix voices currently playing/existing. UINT32 ActiveResamplerCount; // Resample xAPOs currently active. UINT32 ActiveMatrixMixCount; // MatrixMix xAPOs currently active. // Usage of the hardware XMA decoder (Xbox 360 only) UINT32 ActiveXmaSourceVoices; // Number of source voices decoding XMA data. UINT32 ActiveXmaStreams; // A voice can use more than one XMA stream. } XAUDIO2_PERFORMANCE_DATA; // Used in IXAudio2::SetDebugConfiguration typedef struct XAUDIO2_DEBUG_CONFIGURATION { UINT32 TraceMask; // Bitmap of enabled debug message types. UINT32 BreakMask; // Message types that will break into the debugger. BOOL LogThreadID; // Whether to log the thread ID with each message. BOOL LogFileline; // Whether to log the source file and line number. BOOL LogFunctionName; // Whether to log the function name. BOOL LogTiming; // Whether to log message timestamps. } XAUDIO2_DEBUG_CONFIGURATION; // Values for the TraceMask and BreakMask bitmaps. Only ERRORS and WARNINGS // are valid in BreakMask. WARNINGS implies ERRORS, DETAIL implies INFO, and // FUNC_CALLS implies API_CALLS. By default, TraceMask is ERRORS and WARNINGS // and all the other settings are zero. #define XAUDIO2_LOG_ERRORS 0x0001 // For handled errors with serious effects. #define XAUDIO2_LOG_WARNINGS 0x0002 // For handled errors that may be recoverable. #define XAUDIO2_LOG_INFO 0x0004 // Informational chit-chat (e.g. state changes). #define XAUDIO2_LOG_DETAIL 0x0008 // More detailed chit-chat. #define XAUDIO2_LOG_API_CALLS 0x0010 // Public API function entries and exits. #define XAUDIO2_LOG_FUNC_CALLS 0x0020 // Internal function entries and exits. #define XAUDIO2_LOG_TIMING 0x0040 // Delays detected and other timing data. #define XAUDIO2_LOG_LOCKS 0x0080 // Usage of critical sections and mutexes. #define XAUDIO2_LOG_MEMORY 0x0100 // Memory heap usage information. #define XAUDIO2_LOG_STREAMING 0x1000 // Audio streaming information. /************************************************************************** * * IXAudio2: Top-level XAudio2 COM interface. * **************************************************************************/ // Use default arguments if compiling as C++ #ifdef __cplusplus #define X2DEFAULT(x) =x #else #define X2DEFAULT(x) #endif #undef INTERFACE #define INTERFACE IXAudio2 DECLARE_INTERFACE_(IXAudio2, IUnknown) { // NAME: IXAudio2::QueryInterface // DESCRIPTION: Queries for a given COM interface on the XAudio2 object. // Only IID_IUnknown and IID_IXAudio2 are supported. // // ARGUMENTS: // riid - IID of the interface to be obtained. // ppvInterface - Returns a pointer to the requested interface. // STDMETHOD(QueryInterface) (THIS_ REFIID riid, __deref_out void** ppvInterface) PURE; // NAME: IXAudio2::AddRef // DESCRIPTION: Adds a reference to the XAudio2 object. // STDMETHOD_(ULONG, AddRef) (THIS) PURE; // NAME: IXAudio2::Release // DESCRIPTION: Releases a reference to the XAudio2 object. // STDMETHOD_(ULONG, Release) (THIS) PURE; // NAME: IXAudio2::GetDeviceCount // DESCRIPTION: Returns the number of audio output devices available. // // ARGUMENTS: // pCount - Returns the device count. // STDMETHOD(GetDeviceCount) (THIS_ __out UINT32* pCount) PURE; // NAME: IXAudio2::GetDeviceDetails // DESCRIPTION: Returns information about the device with the given index. // // ARGUMENTS: // Index - Index of the device to be queried. // pDeviceDetails - Returns the device details. // STDMETHOD(GetDeviceDetails) (THIS_ UINT32 Index, __out XAUDIO2_DEVICE_DETAILS* pDeviceDetails) PURE; // NAME: IXAudio2::Initialize // DESCRIPTION: Sets global XAudio2 parameters and prepares it for use. // // ARGUMENTS: // Flags - Flags specifying the XAudio2 object's behavior. Currently unused. // XAudio2Processor - An XAUDIO2_PROCESSOR enumeration value that specifies // the hardware thread (Xbox) or processor (Windows) that XAudio2 will use. // The enumeration values are platform-specific; platform-independent code // can use XAUDIO2_DEFAULT_PROCESSOR to use the default on each platform. // STDMETHOD(Initialize) (THIS_ UINT32 Flags X2DEFAULT(0), XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) PURE; // NAME: IXAudio2::RegisterForCallbacks // DESCRIPTION: Adds a new client to receive XAudio2's engine callbacks. // // ARGUMENTS: // pCallback - Callback interface to be called during each processing pass. // STDMETHOD(RegisterForCallbacks) (__in IXAudio2EngineCallback* pCallback) PURE; // NAME: IXAudio2::UnregisterForCallbacks // DESCRIPTION: Removes an existing receiver of XAudio2 engine callbacks. // // ARGUMENTS: // pCallback - Previously registered callback interface to be removed. // STDMETHOD_(void, UnregisterForCallbacks) (__in IXAudio2EngineCallback* pCallback) PURE; // NAME: IXAudio2::CreateSourceVoice // DESCRIPTION: Creates and configures a source voice. // // ARGUMENTS: // ppSourceVoice - Returns the new object's IXAudio2SourceVoice interface. // pSourceFormat - Format of the audio that will be fed to the voice. // Flags - XAUDIO2_VOICE flags specifying the source voice's behavior. // MaxFrequencyRatio - Maximum SetFrequencyRatio argument to be allowed. // pCallback - Optional pointer to a client-provided callback interface. // pSendList - Optional list of voices this voice should send audio to. // pEffectChain - Optional list of effects to apply to the audio data. // STDMETHOD(CreateSourceVoice) (THIS_ __deref_out IXAudio2SourceVoice** ppSourceVoice, __in const WAVEFORMATEX* pSourceFormat, UINT32 Flags X2DEFAULT(0), float MaxFrequencyRatio X2DEFAULT(XAUDIO2_DEFAULT_FREQ_RATIO), __in_opt IXAudio2VoiceCallback* pCallback X2DEFAULT(NULL), __in_opt const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL), __in_opt const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; // NAME: IXAudio2::CreateSubmixVoice // DESCRIPTION: Creates and configures a submix voice. // // ARGUMENTS: // ppSubmixVoice - Returns the new object's IXAudio2SubmixVoice interface. // InputChannels - Number of channels in this voice's input audio data. // InputSampleRate - Sample rate of this voice's input audio data. // Flags - XAUDIO2_VOICE flags specifying the submix voice's behavior. // ProcessingStage - Arbitrary number that determines the processing order. // pSendList - Optional list of voices this voice should send audio to. // pEffectChain - Optional list of effects to apply to the audio data. // STDMETHOD(CreateSubmixVoice) (THIS_ __deref_out IXAudio2SubmixVoice** ppSubmixVoice, UINT32 InputChannels, UINT32 InputSampleRate, UINT32 Flags X2DEFAULT(0), UINT32 ProcessingStage X2DEFAULT(0), __in_opt const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL), __in_opt const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; // NAME: IXAudio2::CreateMasteringVoice // DESCRIPTION: Creates and configures a mastering voice. // // ARGUMENTS: // ppMasteringVoice - Returns the new object's IXAudio2MasteringVoice interface. // InputChannels - Number of channels in this voice's input audio data. // InputSampleRate - Sample rate of this voice's input audio data. // Flags - XAUDIO2_VOICE flags specifying the mastering voice's behavior. // DeviceIndex - Identifier of the device to receive the output audio. // pEffectChain - Optional list of effects to apply to the audio data. // STDMETHOD(CreateMasteringVoice) (THIS_ __deref_out IXAudio2MasteringVoice** ppMasteringVoice, UINT32 InputChannels X2DEFAULT(XAUDIO2_DEFAULT_CHANNELS), UINT32 InputSampleRate X2DEFAULT(XAUDIO2_DEFAULT_SAMPLERATE), UINT32 Flags X2DEFAULT(0), UINT32 DeviceIndex X2DEFAULT(0), __in_opt const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; // NAME: IXAudio2::StartEngine // DESCRIPTION: Creates and starts the audio processing thread. // STDMETHOD(StartEngine) (THIS) PURE; // NAME: IXAudio2::StopEngine // DESCRIPTION: Stops and destroys the audio processing thread. // STDMETHOD_(void, StopEngine) (THIS) PURE; // NAME: IXAudio2::CommitChanges // DESCRIPTION: Atomically applies a set of operations previously tagged // with a given identifier. // // ARGUMENTS: // OperationSet - Identifier of the set of operations to be applied. // STDMETHOD(CommitChanges) (THIS_ UINT32 OperationSet) PURE; // NAME: IXAudio2::GetPerformanceData // DESCRIPTION: Returns current resource usage details: memory, CPU, etc. // // ARGUMENTS: // pPerfData - Returns the performance data structure. // STDMETHOD_(void, GetPerformanceData) (THIS_ __out XAUDIO2_PERFORMANCE_DATA* pPerfData) PURE; // NAME: IXAudio2::SetDebugConfiguration // DESCRIPTION: Configures XAudio2's debug output (in debug builds only). // // ARGUMENTS: // pDebugConfiguration - Structure describing the debug output behavior. // pReserved - Optional parameter; must be NULL. // STDMETHOD_(void, SetDebugConfiguration) (THIS_ __in_opt const XAUDIO2_DEBUG_CONFIGURATION* pDebugConfiguration, __in_opt __reserved void* pReserved X2DEFAULT(NULL)) PURE; }; /************************************************************************** * * IXAudio2Voice: Base voice management interface. * **************************************************************************/ #undef INTERFACE #define INTERFACE IXAudio2Voice DECLARE_INTERFACE(IXAudio2Voice) { // These methods are declared in a macro so that the same declarations // can be used in the derived voice types (IXAudio2SourceVoice, etc). #define Declare_IXAudio2Voice_Methods() \ \ /* NAME: IXAudio2Voice::GetVoiceDetails // DESCRIPTION: Returns the basic characteristics of this voice. // // ARGUMENTS: // pVoiceDetails - Returns the voice's details. */\ STDMETHOD_(void, GetVoiceDetails) (THIS_ __out XAUDIO2_VOICE_DETAILS* pVoiceDetails) PURE; \ \ /* NAME: IXAudio2Voice::SetOutputVoices // DESCRIPTION: Replaces the set of submix/mastering voices that receive // this voice's output. // // ARGUMENTS: // pSendList - Optional list of voices this voice should send audio to. */\ STDMETHOD(SetOutputVoices) (THIS_ __in_opt const XAUDIO2_VOICE_SENDS* pSendList) PURE; \ \ /* NAME: IXAudio2Voice::SetEffectChain // DESCRIPTION: Replaces this voice's current effect chain with a new one. // // ARGUMENTS: // pEffectChain - Structure describing the new effect chain to be used. */\ STDMETHOD(SetEffectChain) (THIS_ __in_opt const XAUDIO2_EFFECT_CHAIN* pEffectChain) PURE; \ \ /* NAME: IXAudio2Voice::EnableEffect // DESCRIPTION: Enables an effect in this voice's effect chain. // // ARGUMENTS: // EffectIndex - Index of an effect within this voice's effect chain. // OperationSet - Used to identify this call as part of a deferred batch. */\ STDMETHOD(EnableEffect) (THIS_ UINT32 EffectIndex, \ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ \ /* NAME: IXAudio2Voice::DisableEffect // DESCRIPTION: Disables an effect in this voice's effect chain. // // ARGUMENTS: // EffectIndex - Index of an effect within this voice's effect chain. // OperationSet - Used to identify this call as part of a deferred batch. */\ STDMETHOD(DisableEffect) (THIS_ UINT32 EffectIndex, \ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ \ /* NAME: IXAudio2Voice::GetEffectState // DESCRIPTION: Returns the running state of an effect. // // ARGUMENTS: // EffectIndex - Index of an effect within this voice's effect chain. // pEnabled - Returns the enabled/disabled state of the given effect. */\ STDMETHOD_(void, GetEffectState) (THIS_ UINT32 EffectIndex, __out BOOL* pEnabled) PURE; \ \ /* NAME: IXAudio2Voice::SetEffectParameters // DESCRIPTION: Sets effect-specific parameters. // // REMARKS: Unlike IXAPOParameters::SetParameters, this method may // be called from any thread. XAudio2 implements // appropriate synchronization to copy the parameters to the // realtime audio processing thread. // // ARGUMENTS: // EffectIndex - Index of an effect within this voice's effect chain. // pParameters - Pointer to an effect-specific parameters block. // ParametersByteSize - Size of the pParameters array in bytes. // OperationSet - Used to identify this call as part of a deferred batch. */\ STDMETHOD(SetEffectParameters) (THIS_ UINT32 EffectIndex, \ __in_bcount(ParametersByteSize) const void* pParameters, \ UINT32 ParametersByteSize, \ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ \ /* NAME: IXAudio2Voice::GetEffectParameters // DESCRIPTION: Obtains the current effect-specific parameters. // // ARGUMENTS: // EffectIndex - Index of an effect within this voice's effect chain. // pParameters - Returns the current values of the effect-specific parameters. // ParametersByteSize - Size of the pParameters array in bytes. */\ STDMETHOD(GetEffectParameters) (THIS_ UINT32 EffectIndex, \ __out_bcount(ParametersByteSize) void* pParameters, \ UINT32 ParametersByteSize) PURE; \ \ /* NAME: IXAudio2Voice::SetFilterParameters // DESCRIPTION: Sets this voice's filter parameters. // // ARGUMENTS: // pParameters - Pointer to the filter's parameter structure. // OperationSet - Used to identify this call as part of a deferred batch. */\ STDMETHOD(SetFilterParameters) (THIS_ __in const XAUDIO2_FILTER_PARAMETERS* pParameters, \ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ \ /* NAME: IXAudio2Voice::GetFilterParameters // DESCRIPTION: Returns this voice's current filter parameters. // // ARGUMENTS: // pParameters - Returns the filter parameters. */\ STDMETHOD_(void, GetFilterParameters) (THIS_ __out XAUDIO2_FILTER_PARAMETERS* pParameters) PURE; \ \ /* NAME: IXAudio2Voice::SetOutputFilterParameters // DESCRIPTION: Sets the filter parameters on one of this voice's sends. // // ARGUMENTS: // pDestinationVoice - Destination voice of the send whose filter parameters will be set. // pParameters - Pointer to the filter's parameter structure. // OperationSet - Used to identify this call as part of a deferred batch. */\ STDMETHOD(SetOutputFilterParameters) (THIS_ __in_opt IXAudio2Voice* pDestinationVoice, \ __in const XAUDIO2_FILTER_PARAMETERS* pParameters, \ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ \ /* NAME: IXAudio2Voice::GetOutputFilterParameters // DESCRIPTION: Returns the filter parameters from one of this voice's sends. // // ARGUMENTS: // pDestinationVoice - Destination voice of the send whose filter parameters will be read. // pParameters - Returns the filter parameters. */\ STDMETHOD_(void, GetOutputFilterParameters) (THIS_ __in_opt IXAudio2Voice* pDestinationVoice, \ __out XAUDIO2_FILTER_PARAMETERS* pParameters) PURE; \ \ /* NAME: IXAudio2Voice::SetVolume // DESCRIPTION: Sets this voice's overall volume level. // // ARGUMENTS: // Volume - New overall volume level to be used, as an amplitude factor. // OperationSet - Used to identify this call as part of a deferred batch. */\ STDMETHOD(SetVolume) (THIS_ float Volume, \ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ \ /* NAME: IXAudio2Voice::GetVolume // DESCRIPTION: Obtains this voice's current overall volume level. // // ARGUMENTS: // pVolume: Returns the voice's current overall volume level. */\ STDMETHOD_(void, GetVolume) (THIS_ __out float* pVolume) PURE; \ \ /* NAME: IXAudio2Voice::SetChannelVolumes // DESCRIPTION: Sets this voice's per-channel volume levels. // // ARGUMENTS: // Channels - Used to confirm the voice's channel count. // pVolumes - Array of per-channel volume levels to be used. // OperationSet - Used to identify this call as part of a deferred batch. */\ STDMETHOD(SetChannelVolumes) (THIS_ UINT32 Channels, __in_ecount(Channels) const float* pVolumes, \ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ \ /* NAME: IXAudio2Voice::GetChannelVolumes // DESCRIPTION: Returns this voice's current per-channel volume levels. // // ARGUMENTS: // Channels - Used to confirm the voice's channel count. // pVolumes - Returns an array of the current per-channel volume levels. */\ STDMETHOD_(void, GetChannelVolumes) (THIS_ UINT32 Channels, __out_ecount(Channels) float* pVolumes) PURE; \ \ /* NAME: IXAudio2Voice::SetOutputMatrix // DESCRIPTION: Sets the volume levels used to mix from each channel of this // voice's output audio to each channel of a given destination // voice's input audio. // // ARGUMENTS: // pDestinationVoice - The destination voice whose mix matrix to change. // SourceChannels - Used to confirm this voice's output channel count // (the number of channels produced by the last effect in the chain). // DestinationChannels - Confirms the destination voice's input channels. // pLevelMatrix - Array of [SourceChannels * DestinationChannels] send // levels. The level used to send from source channel S to destination // channel D should be in pLevelMatrix[S + SourceChannels * D]. // OperationSet - Used to identify this call as part of a deferred batch. */\ STDMETHOD(SetOutputMatrix) (THIS_ __in_opt IXAudio2Voice* pDestinationVoice, \ UINT32 SourceChannels, UINT32 DestinationChannels, \ __in_ecount(SourceChannels * DestinationChannels) const float* pLevelMatrix, \ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ \ /* NAME: IXAudio2Voice::GetOutputMatrix // DESCRIPTION: Obtains the volume levels used to send each channel of this // voice's output audio to each channel of a given destination // voice's input audio. // // ARGUMENTS: // pDestinationVoice - The destination voice whose mix matrix to obtain. // SourceChannels - Used to confirm this voice's output channel count // (the number of channels produced by the last effect in the chain). // DestinationChannels - Confirms the destination voice's input channels. // pLevelMatrix - Array of send levels, as above. */\ STDMETHOD_(void, GetOutputMatrix) (THIS_ __in_opt IXAudio2Voice* pDestinationVoice, \ UINT32 SourceChannels, UINT32 DestinationChannels, \ __out_ecount(SourceChannels * DestinationChannels) float* pLevelMatrix) PURE; \ \ /* NAME: IXAudio2Voice::DestroyVoice // DESCRIPTION: Destroys this voice, stopping it if necessary and removing // it from the XAudio2 graph. */\ STDMETHOD_(void, DestroyVoice) (THIS) PURE Declare_IXAudio2Voice_Methods(); }; /************************************************************************** * * IXAudio2SourceVoice: Source voice management interface. * **************************************************************************/ #undef INTERFACE #define INTERFACE IXAudio2SourceVoice DECLARE_INTERFACE_(IXAudio2SourceVoice, IXAudio2Voice) { // Methods from IXAudio2Voice base interface Declare_IXAudio2Voice_Methods(); // NAME: IXAudio2SourceVoice::Start // DESCRIPTION: Makes this voice start consuming and processing audio. // // ARGUMENTS: // Flags - Flags controlling how the voice should be started. // OperationSet - Used to identify this call as part of a deferred batch. // STDMETHOD(Start) (THIS_ UINT32 Flags X2DEFAULT(0), UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; // NAME: IXAudio2SourceVoice::Stop // DESCRIPTION: Makes this voice stop consuming audio. // // ARGUMENTS: // Flags - Flags controlling how the voice should be stopped. // OperationSet - Used to identify this call as part of a deferred batch. // STDMETHOD(Stop) (THIS_ UINT32 Flags X2DEFAULT(0), UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; // NAME: IXAudio2SourceVoice::SubmitSourceBuffer // DESCRIPTION: Adds a new audio buffer to this voice's input queue. // // ARGUMENTS: // pBuffer - Pointer to the buffer structure to be queued. // pBufferWMA - Additional structure used only when submitting XWMA data. // STDMETHOD(SubmitSourceBuffer) (THIS_ __in const XAUDIO2_BUFFER* pBuffer, __in_opt const XAUDIO2_BUFFER_WMA* pBufferWMA X2DEFAULT(NULL)) PURE; // NAME: IXAudio2SourceVoice::FlushSourceBuffers // DESCRIPTION: Removes all pending audio buffers from this voice's queue. // STDMETHOD(FlushSourceBuffers) (THIS) PURE; // NAME: IXAudio2SourceVoice::Discontinuity // DESCRIPTION: Notifies the voice of an intentional break in the stream of // audio buffers (e.g. the end of a sound), to prevent XAudio2 // from interpreting an empty buffer queue as a glitch. // STDMETHOD(Discontinuity) (THIS) PURE; // NAME: IXAudio2SourceVoice::ExitLoop // DESCRIPTION: Breaks out of the current loop when its end is reached. // // ARGUMENTS: // OperationSet - Used to identify this call as part of a deferred batch. // STDMETHOD(ExitLoop) (THIS_ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; // NAME: IXAudio2SourceVoice::GetState // DESCRIPTION: Returns the number of buffers currently queued on this voice, // the pContext value associated with the currently processing // buffer (if any), and other voice state information. // // ARGUMENTS: // pVoiceState - Returns the state information. // STDMETHOD_(void, GetState) (THIS_ __out XAUDIO2_VOICE_STATE* pVoiceState) PURE; // NAME: IXAudio2SourceVoice::SetFrequencyRatio // DESCRIPTION: Sets this voice's frequency adjustment, i.e. its pitch. // // ARGUMENTS: // Ratio - Frequency change, expressed as source frequency / target frequency. // OperationSet - Used to identify this call as part of a deferred batch. // STDMETHOD(SetFrequencyRatio) (THIS_ float Ratio, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; // NAME: IXAudio2SourceVoice::GetFrequencyRatio // DESCRIPTION: Returns this voice's current frequency adjustment ratio. // // ARGUMENTS: // pRatio - Returns the frequency adjustment. // STDMETHOD_(void, GetFrequencyRatio) (THIS_ __out float* pRatio) PURE; // NAME: IXAudio2SourceVoice::SetSourceSampleRate // DESCRIPTION: Reconfigures this voice to treat its source data as being // at a different sample rate than the original one specified // in CreateSourceVoice's pSourceFormat argument. // // ARGUMENTS: // UINT32 - The intended sample rate of further submitted source data. // STDMETHOD(SetSourceSampleRate) (THIS_ UINT32 NewSourceSampleRate) PURE; }; /************************************************************************** * * IXAudio2SubmixVoice: Submixing voice management interface. * **************************************************************************/ #undef INTERFACE #define INTERFACE IXAudio2SubmixVoice DECLARE_INTERFACE_(IXAudio2SubmixVoice, IXAudio2Voice) { // Methods from IXAudio2Voice base interface Declare_IXAudio2Voice_Methods(); // There are currently no methods specific to submix voices. }; /************************************************************************** * * IXAudio2MasteringVoice: Mastering voice management interface. * **************************************************************************/ #undef INTERFACE #define INTERFACE IXAudio2MasteringVoice DECLARE_INTERFACE_(IXAudio2MasteringVoice, IXAudio2Voice) { // Methods from IXAudio2Voice base interface Declare_IXAudio2Voice_Methods(); // There are currently no methods specific to mastering voices. }; /************************************************************************** * * IXAudio2EngineCallback: Client notification interface for engine events. * * REMARKS: Contains methods to notify the client when certain events happen * in the XAudio2 engine. This interface should be implemented by * the client. XAudio2 will call these methods via the interface * pointer provided by the client when it calls XAudio2Create or * IXAudio2::Initialize. * **************************************************************************/ #undef INTERFACE #define INTERFACE IXAudio2EngineCallback DECLARE_INTERFACE(IXAudio2EngineCallback) { // Called by XAudio2 just before an audio processing pass begins. STDMETHOD_(void, OnProcessingPassStart) (THIS) PURE; // Called just after an audio processing pass ends. STDMETHOD_(void, OnProcessingPassEnd) (THIS) PURE; // Called in the event of a critical system error which requires XAudio2 // to be closed down and restarted. The error code is given in Error. STDMETHOD_(void, OnCriticalError) (THIS_ HRESULT Error) PURE; }; /************************************************************************** * * IXAudio2VoiceCallback: Client notification interface for voice events. * * REMARKS: Contains methods to notify the client when certain events happen * in an XAudio2 voice. This interface should be implemented by the * client. XAudio2 will call these methods via an interface pointer * provided by the client in the IXAudio2::CreateSourceVoice call. * **************************************************************************/ #undef INTERFACE #define INTERFACE IXAudio2VoiceCallback DECLARE_INTERFACE(IXAudio2VoiceCallback) { // Called just before this voice's processing pass begins. STDMETHOD_(void, OnVoiceProcessingPassStart) (THIS_ UINT32 BytesRequired) PURE; // Called just after this voice's processing pass ends. STDMETHOD_(void, OnVoiceProcessingPassEnd) (THIS) PURE; // Called when this voice has just finished playing a buffer stream // (as marked with the XAUDIO2_END_OF_STREAM flag on the last buffer). STDMETHOD_(void, OnStreamEnd) (THIS) PURE; // Called when this voice is about to start processing a new buffer. STDMETHOD_(void, OnBufferStart) (THIS_ void* pBufferContext) PURE; // Called when this voice has just finished processing a buffer. // The buffer can now be reused or destroyed. STDMETHOD_(void, OnBufferEnd) (THIS_ void* pBufferContext) PURE; // Called when this voice has just reached the end position of a loop. STDMETHOD_(void, OnLoopEnd) (THIS_ void* pBufferContext) PURE; // Called in the event of a critical error during voice processing, // such as a failing xAPO or an error from the hardware XMA decoder. // The voice may have to be destroyed and re-created to recover from // the error. The callback arguments report which buffer was being // processed when the error occurred, and its HRESULT code. STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) PURE; }; /************************************************************************** * * Macros to make it easier to use the XAudio2 COM interfaces in C code. * **************************************************************************/ #ifndef __cplusplus // IXAudio2 #define IXAudio2_QueryInterface(This,riid,ppvInterface) ((This)->lpVtbl->QueryInterface(This,riid,ppvInterface)) #define IXAudio2_AddRef(This) ((This)->lpVtbl->AddRef(This)) #define IXAudio2_Release(This) ((This)->lpVtbl->Release(This)) #define IXAudio2_GetDeviceCount(This,puCount) ((This)->lpVtbl->GetDeviceCount(This,puCount)) #define IXAudio2_GetDeviceDetails(This,Index,pDeviceDetails) ((This)->lpVtbl->GetDeviceDetails(This,Index,pDeviceDetails)) #define IXAudio2_Initialize(This,Flags,XAudio2Processor) ((This)->lpVtbl->Initialize(This,Flags,XAudio2Processor)) #define IXAudio2_CreateSourceVoice(This,ppSourceVoice,pSourceFormat,Flags,MaxFrequencyRatio,pCallback,pSendList,pEffectChain) ((This)->lpVtbl->CreateSourceVoice(This,ppSourceVoice,pSourceFormat,Flags,MaxFrequencyRatio,pCallback,pSendList,pEffectChain)) #define IXAudio2_CreateSubmixVoice(This,ppSubmixVoice,InputChannels,InputSampleRate,Flags,ProcessingStage,pSendList,pEffectChain) ((This)->lpVtbl->CreateSubmixVoice(This,ppSubmixVoice,InputChannels,InputSampleRate,Flags,ProcessingStage,pSendList,pEffectChain)) #define IXAudio2_CreateMasteringVoice(This,ppMasteringVoice,InputChannels,InputSampleRate,Flags,DeviceIndex,pEffectChain) ((This)->lpVtbl->CreateMasteringVoice(This,ppMasteringVoice,InputChannels,InputSampleRate,Flags,DeviceIndex,pEffectChain)) #define IXAudio2_StartEngine(This) ((This)->lpVtbl->StartEngine(This)) #define IXAudio2_StopEngine(This) ((This)->lpVtbl->StopEngine(This)) #define IXAudio2_CommitChanges(This,OperationSet) ((This)->lpVtbl->CommitChanges(This,OperationSet)) #define IXAudio2_GetPerformanceData(This,pPerfData) ((This)->lpVtbl->GetPerformanceData(This,pPerfData)) #define IXAudio2_SetDebugConfiguration(This,pDebugConfiguration,pReserved) ((This)->lpVtbl->SetDebugConfiguration(This,pDebugConfiguration,pReserved)) // IXAudio2Voice #define IXAudio2Voice_GetVoiceDetails(This,pVoiceDetails) ((This)->lpVtbl->GetVoiceDetails(This,pVoiceDetails)) #define IXAudio2Voice_SetOutputVoices(This,pSendList) ((This)->lpVtbl->SetOutputVoices(This,pSendList)) #define IXAudio2Voice_SetEffectChain(This,pEffectChain) ((This)->lpVtbl->SetEffectChain(This,pEffectChain)) #define IXAudio2Voice_EnableEffect(This,EffectIndex,OperationSet) ((This)->lpVtbl->EnableEffect(This,EffectIndex,OperationSet)) #define IXAudio2Voice_DisableEffect(This,EffectIndex,OperationSet) ((This)->lpVtbl->DisableEffect(This,EffectIndex,OperationSet)) #define IXAudio2Voice_GetEffectState(This,EffectIndex,pEnabled) ((This)->lpVtbl->GetEffectState(This,EffectIndex,pEnabled)) #define IXAudio2Voice_SetEffectParameters(This,EffectIndex,pParameters,ParametersByteSize, OperationSet) ((This)->lpVtbl->SetEffectParameters(This,EffectIndex,pParameters,ParametersByteSize,OperationSet)) #define IXAudio2Voice_GetEffectParameters(This,EffectIndex,pParameters,ParametersByteSize) ((This)->lpVtbl->GetEffectParameters(This,EffectIndex,pParameters,ParametersByteSize)) #define IXAudio2Voice_SetFilterParameters(This,pParameters,OperationSet) ((This)->lpVtbl->SetFilterParameters(This,pParameters,OperationSet)) #define IXAudio2Voice_GetFilterParameters(This,pParameters) ((This)->lpVtbl->GetFilterParameters(This,pParameters)) #define IXAudio2Voice_SetOutputFilterParameters(This,pDestinationVoice,pParameters,OperationSet) ((This)->lpVtbl->SetOutputFilterParameters(This,pDestinationVoice,pParameters,OperationSet)) #define IXAudio2Voice_GetOutputFilterParameters(This,pDestinationVoice,pParameters) ((This)->lpVtbl->GetOutputFilterParameters(This,pDestinationVoice,pParameters)) #define IXAudio2Voice_SetVolume(This,Volume,OperationSet) ((This)->lpVtbl->SetVolume(This,Volume,OperationSet)) #define IXAudio2Voice_GetVolume(This,pVolume) ((This)->lpVtbl->GetVolume(This,pVolume)) #define IXAudio2Voice_SetChannelVolumes(This,Channels,pVolumes,OperationSet) ((This)->lpVtbl->SetChannelVolumes(This,Channels,pVolumes,OperationSet)) #define IXAudio2Voice_GetChannelVolumes(This,Channels,pVolumes) ((This)->lpVtbl->GetChannelVolumes(This,Channels,pVolumes)) #define IXAudio2Voice_SetOutputMatrix(This,pDestinationVoice,SourceChannels,DestinationChannels,pLevelMatrix,OperationSet) ((This)->lpVtbl->SetOutputMatrix(This,pDestinationVoice,SourceChannels,DestinationChannels,pLevelMatrix,OperationSet)) #define IXAudio2Voice_GetOutputMatrix(This,pDestinationVoice,SourceChannels,DestinationChannels,pLevelMatrix) ((This)->lpVtbl->GetOutputMatrix(This,pDestinationVoice,SourceChannels,DestinationChannels,pLevelMatrix)) #define IXAudio2Voice_DestroyVoice(This) ((This)->lpVtbl->DestroyVoice(This)) // IXAudio2SourceVoice #define IXAudio2SourceVoice_GetVoiceDetails IXAudio2Voice_GetVoiceDetails #define IXAudio2SourceVoice_SetOutputVoices IXAudio2Voice_SetOutputVoices #define IXAudio2SourceVoice_SetEffectChain IXAudio2Voice_SetEffectChain #define IXAudio2SourceVoice_EnableEffect IXAudio2Voice_EnableEffect #define IXAudio2SourceVoice_DisableEffect IXAudio2Voice_DisableEffect #define IXAudio2SourceVoice_GetEffectState IXAudio2Voice_GetEffectState #define IXAudio2SourceVoice_SetEffectParameters IXAudio2Voice_SetEffectParameters #define IXAudio2SourceVoice_GetEffectParameters IXAudio2Voice_GetEffectParameters #define IXAudio2SourceVoice_SetFilterParameters IXAudio2Voice_SetFilterParameters #define IXAudio2SourceVoice_GetFilterParameters IXAudio2Voice_GetFilterParameters #define IXAudio2SourceVoice_SetOutputFilterParameters IXAudio2Voice_SetOutputFilterParameters #define IXAudio2SourceVoice_GetOutputFilterParameters IXAudio2Voice_GetOutputFilterParameters #define IXAudio2SourceVoice_SetVolume IXAudio2Voice_SetVolume #define IXAudio2SourceVoice_GetVolume IXAudio2Voice_GetVolume #define IXAudio2SourceVoice_SetChannelVolumes IXAudio2Voice_SetChannelVolumes #define IXAudio2SourceVoice_GetChannelVolumes IXAudio2Voice_GetChannelVolumes #define IXAudio2SourceVoice_SetOutputMatrix IXAudio2Voice_SetOutputMatrix #define IXAudio2SourceVoice_GetOutputMatrix IXAudio2Voice_GetOutputMatrix #define IXAudio2SourceVoice_DestroyVoice IXAudio2Voice_DestroyVoice #define IXAudio2SourceVoice_Start(This,Flags,OperationSet) ((This)->lpVtbl->Start(This,Flags,OperationSet)) #define IXAudio2SourceVoice_Stop(This,Flags,OperationSet) ((This)->lpVtbl->Stop(This,Flags,OperationSet)) #define IXAudio2SourceVoice_SubmitSourceBuffer(This,pBuffer,pBufferWMA) ((This)->lpVtbl->SubmitSourceBuffer(This,pBuffer,pBufferWMA)) #define IXAudio2SourceVoice_FlushSourceBuffers(This) ((This)->lpVtbl->FlushSourceBuffers(This)) #define IXAudio2SourceVoice_Discontinuity(This) ((This)->lpVtbl->Discontinuity(This)) #define IXAudio2SourceVoice_ExitLoop(This,OperationSet) ((This)->lpVtbl->ExitLoop(This,OperationSet)) #define IXAudio2SourceVoice_GetState(This,pVoiceState) ((This)->lpVtbl->GetState(This,pVoiceState)) #define IXAudio2SourceVoice_SetFrequencyRatio(This,Ratio,OperationSet) ((This)->lpVtbl->SetFrequencyRatio(This,Ratio,OperationSet)) #define IXAudio2SourceVoice_GetFrequencyRatio(This,pRatio) ((This)->lpVtbl->GetFrequencyRatio(This,pRatio)) #define IXAudio2SourceVoice_SetSourceSampleRate(This,NewSourceSampleRate) ((This)->lpVtbl->SetSourceSampleRate(This,NewSourceSampleRate)) // IXAudio2SubmixVoice #define IXAudio2SubmixVoice_GetVoiceDetails IXAudio2Voice_GetVoiceDetails #define IXAudio2SubmixVoice_SetOutputVoices IXAudio2Voice_SetOutputVoices #define IXAudio2SubmixVoice_SetEffectChain IXAudio2Voice_SetEffectChain #define IXAudio2SubmixVoice_EnableEffect IXAudio2Voice_EnableEffect #define IXAudio2SubmixVoice_DisableEffect IXAudio2Voice_DisableEffect #define IXAudio2SubmixVoice_GetEffectState IXAudio2Voice_GetEffectState #define IXAudio2SubmixVoice_SetEffectParameters IXAudio2Voice_SetEffectParameters #define IXAudio2SubmixVoice_GetEffectParameters IXAudio2Voice_GetEffectParameters #define IXAudio2SubmixVoice_SetFilterParameters IXAudio2Voice_SetFilterParameters #define IXAudio2SubmixVoice_GetFilterParameters IXAudio2Voice_GetFilterParameters #define IXAudio2SubmixVoice_SetOutputFilterParameters IXAudio2Voice_SetOutputFilterParameters #define IXAudio2SubmixVoice_GetOutputFilterParameters IXAudio2Voice_GetOutputFilterParameters #define IXAudio2SubmixVoice_SetVolume IXAudio2Voice_SetVolume #define IXAudio2SubmixVoice_GetVolume IXAudio2Voice_GetVolume #define IXAudio2SubmixVoice_SetChannelVolumes IXAudio2Voice_SetChannelVolumes #define IXAudio2SubmixVoice_GetChannelVolumes IXAudio2Voice_GetChannelVolumes #define IXAudio2SubmixVoice_SetOutputMatrix IXAudio2Voice_SetOutputMatrix #define IXAudio2SubmixVoice_GetOutputMatrix IXAudio2Voice_GetOutputMatrix #define IXAudio2SubmixVoice_DestroyVoice IXAudio2Voice_DestroyVoice // IXAudio2MasteringVoice #define IXAudio2MasteringVoice_GetVoiceDetails IXAudio2Voice_GetVoiceDetails #define IXAudio2MasteringVoice_SetOutputVoices IXAudio2Voice_SetOutputVoices #define IXAudio2MasteringVoice_SetEffectChain IXAudio2Voice_SetEffectChain #define IXAudio2MasteringVoice_EnableEffect IXAudio2Voice_EnableEffect #define IXAudio2MasteringVoice_DisableEffect IXAudio2Voice_DisableEffect #define IXAudio2MasteringVoice_GetEffectState IXAudio2Voice_GetEffectState #define IXAudio2MasteringVoice_SetEffectParameters IXAudio2Voice_SetEffectParameters #define IXAudio2MasteringVoice_GetEffectParameters IXAudio2Voice_GetEffectParameters #define IXAudio2MasteringVoice_SetFilterParameters IXAudio2Voice_SetFilterParameters #define IXAudio2MasteringVoice_GetFilterParameters IXAudio2Voice_GetFilterParameters #define IXAudio2MasteringVoice_SetOutputFilterParameters IXAudio2Voice_SetOutputFilterParameters #define IXAudio2MasteringVoice_GetOutputFilterParameters IXAudio2Voice_GetOutputFilterParameters #define IXAudio2MasteringVoice_SetVolume IXAudio2Voice_SetVolume #define IXAudio2MasteringVoice_GetVolume IXAudio2Voice_GetVolume #define IXAudio2MasteringVoice_SetChannelVolumes IXAudio2Voice_SetChannelVolumes #define IXAudio2MasteringVoice_GetChannelVolumes IXAudio2Voice_GetChannelVolumes #define IXAudio2MasteringVoice_SetOutputMatrix IXAudio2Voice_SetOutputMatrix #define IXAudio2MasteringVoice_GetOutputMatrix IXAudio2Voice_GetOutputMatrix #define IXAudio2MasteringVoice_DestroyVoice IXAudio2Voice_DestroyVoice #endif // #ifndef __cplusplus /************************************************************************** * * Utility functions used to convert from pitch in semitones and volume * in decibels to the frequency and amplitude ratio units used by XAudio2. * These are only defined if the client #defines XAUDIO2_HELPER_FUNCTIONS * prior to #including xaudio2.h. * **************************************************************************/ #ifdef XAUDIO2_HELPER_FUNCTIONS #define _USE_MATH_DEFINES // Make math.h define M_PI #include // For powf, log10f, sinf and asinf // Calculate the argument to SetVolume from a decibel value __inline float XAudio2DecibelsToAmplitudeRatio(float Decibels) { return powf(10.0f, Decibels / 20.0f); } // Recover a volume in decibels from an amplitude factor __inline float XAudio2AmplitudeRatioToDecibels(float Volume) { if (Volume == 0) { return -3.402823466e+38f; // Smallest float value (-FLT_MAX) } return 20.0f * log10f(Volume); } // Calculate the argument to SetFrequencyRatio from a semitone value __inline float XAudio2SemitonesToFrequencyRatio(float Semitones) { // FrequencyRatio = 2 ^ Octaves // = 2 ^ (Semitones / 12) return powf(2.0f, Semitones / 12.0f); } // Recover a pitch in semitones from a frequency ratio __inline float XAudio2FrequencyRatioToSemitones(float FrequencyRatio) { // Semitones = 12 * log2(FrequencyRatio) // = 12 * log2(10) * log10(FrequencyRatio) return 39.86313713864835f * log10f(FrequencyRatio); } // Convert from filter cutoff frequencies expressed in Hertz to the radian // frequency values used in XAUDIO2_FILTER_PARAMETERS.Frequency. Note that // the highest CutoffFrequency supported is SampleRate/6. Higher values of // CutoffFrequency will return XAUDIO2_MAX_FILTER_FREQUENCY. __inline float XAudio2CutoffFrequencyToRadians(float CutoffFrequency, UINT32 SampleRate) { if ((UINT32)(CutoffFrequency * 6.0f) >= SampleRate) { return XAUDIO2_MAX_FILTER_FREQUENCY; } return 2.0f * sinf((float)M_PI * CutoffFrequency / SampleRate); } // Convert from radian frequencies back to absolute frequencies in Hertz __inline float XAudio2RadiansToCutoffFrequency(float Radians, float SampleRate) { return SampleRate * asinf(Radians / 2.0f) / (float)M_PI; } #endif // #ifdef XAUDIO2_HELPER_FUNCTIONS /************************************************************************** * * XAudio2Create: Top-level function that creates an XAudio2 instance. * * On Windows this is just an inline function that calls CoCreateInstance * and Initialize. The arguments are described above, under Initialize, * except that the XAUDIO2_DEBUG_ENGINE flag can be used here to select * the debug version of XAudio2. * * On Xbox, this function is implemented in the XAudio2 library, and the * XAUDIO2_DEBUG_ENGINE flag has no effect; the client must explicitly * link with the debug version of the library to obtain debug behavior. * **************************************************************************/ #ifdef _XBOX STDAPI XAudio2Create(__deref_out IXAudio2** ppXAudio2, UINT32 Flags X2DEFAULT(0), XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)); #else // Windows __inline HRESULT XAudio2Create(__deref_out IXAudio2** ppXAudio2, UINT32 Flags X2DEFAULT(0), XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) { // Instantiate the appropriate XAudio2 engine IXAudio2* pXAudio2; #ifdef __cplusplus HRESULT hr = CoCreateInstance((Flags & XAUDIO2_DEBUG_ENGINE) ? __uuidof(XAudio2_Debug) : __uuidof(XAudio2), NULL, CLSCTX_INPROC_SERVER, __uuidof(IXAudio2), (void**)&pXAudio2); if (SUCCEEDED(hr)) { hr = pXAudio2->Initialize(Flags, XAudio2Processor); if (SUCCEEDED(hr)) { *ppXAudio2 = pXAudio2; } else { pXAudio2->Release(); } } #else HRESULT hr = CoCreateInstance((Flags & XAUDIO2_DEBUG_ENGINE) ? &CLSID_XAudio2_Debug : &CLSID_XAudio2, NULL, CLSCTX_INPROC_SERVER, &IID_IXAudio2, (void**)&pXAudio2); if (SUCCEEDED(hr)) { hr = pXAudio2->lpVtbl->Initialize(pXAudio2, Flags, XAudio2Processor); if (SUCCEEDED(hr)) { *ppXAudio2 = pXAudio2; } else { pXAudio2->lpVtbl->Release(pXAudio2); } } #endif // #ifdef __cplusplus return hr; } #endif // #ifdef _XBOX // Undo the #pragma pack(push, 1) directive at the top of this file #pragma pack(pop) #endif // #ifndef GUID_DEFS_ONLY #endif // #ifndef __XAUDIO2_INCLUDED__ ================================================ FILE: dependencies/DirectX_2010/audiodefs.h ================================================ /*************************************************************************** * * Copyright (c) Microsoft Corporation. All rights reserved. * * File: audiodefs.h * Content: Basic constants and data types for audio work. * * Remarks: This header file defines all of the audio format constants and * structures required for XAudio2 and XACT work. Providing these * in a single location avoids certain dependency problems in the * legacy audio headers (mmreg.h, mmsystem.h, ksmedia.h). * * NOTE: Including the legacy headers after this one may cause a * compilation error, because they define some of the same types * defined here without preprocessor guards to avoid multiple * definitions. If a source file needs one of the old headers, * it must include it before including audiodefs.h. * ***************************************************************************/ #ifndef __AUDIODEFS_INCLUDED__ #define __AUDIODEFS_INCLUDED__ #include // For WORD, DWORD, etc. #pragma pack(push, 1) // Pack structures to 1-byte boundaries /************************************************************************** * * WAVEFORMATEX: Base structure for many audio formats. Format-specific * extensions can be defined for particular formats by using a non-zero * cbSize value and adding extra fields to the end of this structure. * ***************************************************************************/ #ifndef _WAVEFORMATEX_ #define _WAVEFORMATEX_ typedef struct tWAVEFORMATEX { WORD wFormatTag; // Integer identifier of the format WORD nChannels; // Number of audio channels DWORD nSamplesPerSec; // Audio sample rate DWORD nAvgBytesPerSec; // Bytes per second (possibly approximate) WORD nBlockAlign; // Size in bytes of a sample block (all channels) WORD wBitsPerSample; // Size in bits of a single per-channel sample WORD cbSize; // Bytes of extra data appended to this struct } WAVEFORMATEX; #endif // Defining pointer types outside of the #if block to make sure they are // defined even if mmreg.h or mmsystem.h is #included before this file typedef WAVEFORMATEX *PWAVEFORMATEX, *NPWAVEFORMATEX, *LPWAVEFORMATEX; typedef const WAVEFORMATEX *PCWAVEFORMATEX, *LPCWAVEFORMATEX; /************************************************************************** * * WAVEFORMATEXTENSIBLE: Extended version of WAVEFORMATEX that should be * used as a basis for all new audio formats. The format tag is replaced * with a GUID, allowing new formats to be defined without registering a * format tag with Microsoft. There are also new fields that can be used * to specify the spatial positions for each channel and the bit packing * used for wide samples (e.g. 24-bit PCM samples in 32-bit containers). * ***************************************************************************/ #ifndef _WAVEFORMATEXTENSIBLE_ #define _WAVEFORMATEXTENSIBLE_ typedef struct { WAVEFORMATEX Format; // Base WAVEFORMATEX data union { WORD wValidBitsPerSample; // Valid bits in each sample container WORD wSamplesPerBlock; // Samples per block of audio data; valid // if wBitsPerSample=0 (but rarely used). WORD wReserved; // Zero if neither case above applies. } Samples; DWORD dwChannelMask; // Positions of the audio channels GUID SubFormat; // Format identifier GUID } WAVEFORMATEXTENSIBLE; #endif typedef WAVEFORMATEXTENSIBLE *PWAVEFORMATEXTENSIBLE, *LPWAVEFORMATEXTENSIBLE; typedef const WAVEFORMATEXTENSIBLE *PCWAVEFORMATEXTENSIBLE, *LPCWAVEFORMATEXTENSIBLE; /************************************************************************** * * Define the most common wave format tags used in WAVEFORMATEX formats. * ***************************************************************************/ #ifndef WAVE_FORMAT_PCM // Pulse Code Modulation // If WAVE_FORMAT_PCM is not defined, we need to define some legacy types // for compatibility with the Windows mmreg.h / mmsystem.h header files. // Old general format structure (information common to all formats) typedef struct waveformat_tag { WORD wFormatTag; WORD nChannels; DWORD nSamplesPerSec; DWORD nAvgBytesPerSec; WORD nBlockAlign; } WAVEFORMAT, *PWAVEFORMAT, NEAR *NPWAVEFORMAT, FAR *LPWAVEFORMAT; // Specific format structure for PCM data typedef struct pcmwaveformat_tag { WAVEFORMAT wf; WORD wBitsPerSample; } PCMWAVEFORMAT, *PPCMWAVEFORMAT, NEAR *NPPCMWAVEFORMAT, FAR *LPPCMWAVEFORMAT; #define WAVE_FORMAT_PCM 0x0001 #endif #ifndef WAVE_FORMAT_ADPCM // Microsoft Adaptive Differental PCM // Replicate the Microsoft ADPCM type definitions from mmreg.h. typedef struct adpcmcoef_tag { short iCoef1; short iCoef2; } ADPCMCOEFSET; #pragma warning(push) #pragma warning(disable:4200) // Disable zero-sized array warnings typedef struct adpcmwaveformat_tag { WAVEFORMATEX wfx; WORD wSamplesPerBlock; WORD wNumCoef; ADPCMCOEFSET aCoef[]; // Always 7 coefficient pairs for MS ADPCM } ADPCMWAVEFORMAT; #pragma warning(pop) #define WAVE_FORMAT_ADPCM 0x0002 #endif // Other frequently used format tags #ifndef WAVE_FORMAT_UNKNOWN #define WAVE_FORMAT_UNKNOWN 0x0000 // Unknown or invalid format tag #endif #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 // 32-bit floating-point #endif #ifndef WAVE_FORMAT_MPEGLAYER3 #define WAVE_FORMAT_MPEGLAYER3 0x0055 // ISO/MPEG Layer3 #endif #ifndef WAVE_FORMAT_DOLBY_AC3_SPDIF #define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092 // Dolby Audio Codec 3 over S/PDIF #endif #ifndef WAVE_FORMAT_WMAUDIO2 #define WAVE_FORMAT_WMAUDIO2 0x0161 // Windows Media Audio #endif #ifndef WAVE_FORMAT_WMAUDIO3 #define WAVE_FORMAT_WMAUDIO3 0x0162 // Windows Media Audio Pro #endif #ifndef WAVE_FORMAT_WMASPDIF #define WAVE_FORMAT_WMASPDIF 0x0164 // Windows Media Audio over S/PDIF #endif #ifndef WAVE_FORMAT_EXTENSIBLE #define WAVE_FORMAT_EXTENSIBLE 0xFFFE // All WAVEFORMATEXTENSIBLE formats #endif /************************************************************************** * * Define the most common wave format GUIDs used in WAVEFORMATEXTENSIBLE * formats. Note that including the Windows ksmedia.h header after this * one will cause build problems; this cannot be avoided, since ksmedia.h * defines these macros without preprocessor guards. * ***************************************************************************/ #ifdef __cplusplus // uuid() and __uuidof() are only available in C++ #ifndef KSDATAFORMAT_SUBTYPE_PCM struct __declspec(uuid("00000001-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_PCM_STRUCT; #define KSDATAFORMAT_SUBTYPE_PCM __uuidof(KSDATAFORMAT_SUBTYPE_PCM_STRUCT) #endif #ifndef KSDATAFORMAT_SUBTYPE_ADPCM struct __declspec(uuid("00000002-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_ADPCM_STRUCT; #define KSDATAFORMAT_SUBTYPE_ADPCM __uuidof(KSDATAFORMAT_SUBTYPE_ADPCM_STRUCT) #endif #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT struct __declspec(uuid("00000003-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_STRUCT; #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT __uuidof(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_STRUCT) #endif #endif /************************************************************************** * * Speaker positions used in the WAVEFORMATEXTENSIBLE dwChannelMask field. * ***************************************************************************/ #ifndef SPEAKER_FRONT_LEFT #define SPEAKER_FRONT_LEFT 0x00000001 #define SPEAKER_FRONT_RIGHT 0x00000002 #define SPEAKER_FRONT_CENTER 0x00000004 #define SPEAKER_LOW_FREQUENCY 0x00000008 #define SPEAKER_BACK_LEFT 0x00000010 #define SPEAKER_BACK_RIGHT 0x00000020 #define SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040 #define SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080 #define SPEAKER_BACK_CENTER 0x00000100 #define SPEAKER_SIDE_LEFT 0x00000200 #define SPEAKER_SIDE_RIGHT 0x00000400 #define SPEAKER_TOP_CENTER 0x00000800 #define SPEAKER_TOP_FRONT_LEFT 0x00001000 #define SPEAKER_TOP_FRONT_CENTER 0x00002000 #define SPEAKER_TOP_FRONT_RIGHT 0x00004000 #define SPEAKER_TOP_BACK_LEFT 0x00008000 #define SPEAKER_TOP_BACK_CENTER 0x00010000 #define SPEAKER_TOP_BACK_RIGHT 0x00020000 #define SPEAKER_RESERVED 0x7FFC0000 #define SPEAKER_ALL 0x80000000 #define _SPEAKER_POSITIONS_ #endif #ifndef SPEAKER_STEREO #define SPEAKER_MONO (SPEAKER_FRONT_CENTER) #define SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT) #define SPEAKER_2POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY) #define SPEAKER_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER) #define SPEAKER_QUAD (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) #define SPEAKER_4POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) #define SPEAKER_5POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) #define SPEAKER_7POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER) #define SPEAKER_5POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) #define SPEAKER_7POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) #endif #pragma pack(pop) #endif // #ifndef __AUDIODEFS_INCLUDED__ ================================================ FILE: dependencies/DirectX_2010/comdecl.h ================================================ // comdecl.h: Macros to facilitate COM interface and GUID declarations. // Copyright (c) Microsoft Corporation. All rights reserved. #ifndef _COMDECL_H_ #define _COMDECL_H_ #ifndef _XBOX #include // For standard COM interface macros #else #pragma warning(push) #pragma warning(disable:4061) #include // Required by xobjbase.h #include // Special definitions for Xbox build #pragma warning(pop) #endif // The DEFINE_CLSID() and DEFINE_IID() macros defined below allow COM GUIDs to // be declared and defined in such a way that clients can obtain the GUIDs using // either the __uuidof() extension or the old-style CLSID_Foo / IID_IFoo names. // If using the latter approach, the client can also choose whether to get the // GUID definitions by defining the INITGUID preprocessor constant or by linking // to a GUID library. This works in either C or C++. #ifdef __cplusplus #define DECLSPEC_UUID_WRAPPER(x) __declspec(uuid(#x)) #ifdef INITGUID #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ class DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) className; \ EXTERN_C const GUID DECLSPEC_SELECTANY CLSID_##className = __uuidof(className) #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ interface DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) interfaceName; \ EXTERN_C const GUID DECLSPEC_SELECTANY IID_##interfaceName = __uuidof(interfaceName) #else // INITGUID #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ class DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) className; \ EXTERN_C const GUID CLSID_##className #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ interface DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) interfaceName; \ EXTERN_C const GUID IID_##interfaceName #endif // INITGUID #else // __cplusplus #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ DEFINE_GUID(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ DEFINE_GUID(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) #endif // __cplusplus #endif // #ifndef _COMDECL_H_ ================================================ FILE: dependencies/DirectX_2010/xma2defs.h ================================================ /*************************************************************************** * * Copyright (c) Microsoft Corporation. All rights reserved. * * File: xma2defs.h * Content: Constants, data types and functions for XMA2 compressed audio. * ***************************************************************************/ #ifndef __XMA2DEFS_INCLUDED__ #define __XMA2DEFS_INCLUDED__ #include // Markers for documenting API semantics #include // For S_OK, E_FAIL #include "audiodefs.h" // Basic data types and constants for audio work /*************************************************************************** * Overview ***************************************************************************/ // A typical XMA2 file contains these RIFF chunks: // // 'fmt' or 'XMA2' chunk (or both): A description of the XMA data's structure // and characteristics (length, channels, sample rate, loops, block size, etc). // // 'seek' chunk: A seek table to help navigate the XMA data. // // 'data' chunk: The encoded XMA2 data. // // The encoded XMA2 data is structured as a set of BLOCKS, which contain PACKETS, // which contain FRAMES, which contain SUBFRAMES (roughly speaking). The frames // in a file may also be divided into several subsets, called STREAMS. // // FRAME: A variable-sized segment of XMA data that decodes to exactly 512 mono // or stereo PCM samples. This is the smallest unit of XMA data that can // be decoded in isolation. Frames are an arbitrary number of bits in // length, and need not be byte-aligned. See "XMA frame structure" below. // // SUBFRAME: A region of bits in an XMA frame that decodes to 128 mono or stereo // samples. The XMA decoder cannot decode a subframe in isolation; it needs // a whole frame to work with. However, it can begin emitting the frame's // decoded samples at any one of the four subframe boundaries. Subframes // can be addressed for seeking and looping purposes. // // PACKET: A 2Kb region containing a 32-bit header and some XMA frames. Frames // can (and usually do) span packets. A packet's header includes the offset // in bits of the first frame that begins within that packet. All of the // frames that begin in a given packet belong to the same "stream" (see the // Multichannel Audio section below). // // STREAM: A set of packets within an XMA file that all contain data for the // same mono or stereo component of a PCM file with more than two channels. // The packets comprising a given stream may be interleaved with each other // more or less arbitrarily; see Multichannel Audio. // // BLOCK: An array of XMA packets; or, to break it down differently, a series of // consecutive XMA frames, padded at the end with reserved data. A block // must contain at least one 2Kb packet per stream, and it can hold up to // 4095 packets (8190Kb), but its size is typically in the 32Kb-128Kb range. // (The size chosen involves a trade-off between memory use and efficiency // of reading from permanent storage.) // // XMA frames do not span blocks, so a block is guaranteed to begin with a // set of complete frames, one per stream. Also, a block in a multi-stream // XMA2 file always contains the same number of samples for each stream; // see Multichannel Audio. // // The 'data' chunk in an XMA2 file is an array of XMA2WAVEFORMAT.BlockCount XMA // blocks, all the same size (as specified in XMA2WAVEFORMAT.BlockSizeInBytes) // except for the last one, which may be shorter. // MULTICHANNEL AUDIO: the XMA decoder can only decode raw XMA data into either // mono or stereo PCM data. In order to encode a 6-channel file (say), the file // must be deinterleaved into 3 stereo streams that are encoded independently, // producing 3 encoded XMA data streams. Then the packets in these 3 streams // are interleaved to produce a single XMA2 file, and some information is added // to the file so that the original 6-channel audio can be reconstructed at // decode time. This works using the concept of an XMA stream (see above). // // The frames for all the streams in an XMA file are interleaved in an arbitrary // order. To locate a frame that belongs to a given stream in a given XMA block, // you must examine the first few packets in the block. Here (and only here) the // packets are guaranteed to be presented in stream order, so that all frames // beginning in packet 0 belong to stream 0 (the first stereo pair), etc. // // (This means that when decoding multi-stream XMA files, only entire XMA blocks // should be submitted to the decoder; otherwise it cannot know which frames // belong to which stream.) // // Once you have one frame that belongs to a given stream, you can find the next // one by looking at the frame's 'NextFrameOffsetBits' value (which is stored in // its first 15 bits; see XMAFRAME below). The GetXmaFrameBitPosition function // uses this technique. // SEEKING IN XMA2 FILES: Here is some pseudocode to find the byte position and // subframe in an XMA2 file which will contain sample S when decoded. // // 1. Traverse the seek table to find the XMA2 block containing sample S. The // seek table is an array of big-endian DWORDs, one per block in the file. // The Nth DWORD is the total number of PCM samples that would be obtained // by decoding the entire XMA file up to the end of block N. Hence, the // block we want is the first one whose seek table entry is greater than S. // (See the GetXmaBlockContainingSample helper function.) // // 2. Calculate which frame F within the block found above contains sample S. // Since each frame decodes to 512 samples, this is straightforward. The // first frame in the block produces samples X to X + 512, where X is the // seek table entry for the prior block. So F is (S - X) / 512. // // 3. Find the bit offset within the block where frame F starts. Since frames // are variable-sized, this can only be done by traversing all the frames in // the block until we reach frame F. (See GetXmaFrameBitPosition.) // // 4. Frame F has four 128-sample subframes. To find the subframe containing S, // we can use the formula (S % 512) / 128. // // In the case of multi-stream XMA files, sample S is a multichannel sample with // parts coming from several frames, one per stream. To find all these frames, // steps 2-4 need to be repeated for each stream N, using the knowledge that the // first packets in a block are presented in stream order. The frame traversal // in step 3 must be started at the first frame in the Nth packet of the block, // which will be the first frame for stream N. (And the packet header will tell // you the first frame's start position within the packet.) // // Step 1 can be performed using the GetXmaBlockContainingSample function below, // and steps 2-4 by calling GetXmaDecodePositionForSample once for each stream. /*************************************************************************** * XMA constants ***************************************************************************/ // Size of the PCM samples produced by the XMA decoder #define XMA_OUTPUT_SAMPLE_BYTES 2u #define XMA_OUTPUT_SAMPLE_BITS (XMA_OUTPUT_SAMPLE_BYTES * 8u) // Size of an XMA packet #define XMA_BYTES_PER_PACKET 2048u #define XMA_BITS_PER_PACKET (XMA_BYTES_PER_PACKET * 8u) // Size of an XMA packet header #define XMA_PACKET_HEADER_BYTES 4u #define XMA_PACKET_HEADER_BITS (XMA_PACKET_HEADER_BYTES * 8u) // Sample blocks in a decoded XMA frame #define XMA_SAMPLES_PER_FRAME 512u // Sample blocks in a decoded XMA subframe #define XMA_SAMPLES_PER_SUBFRAME 128u // Maximum encoded data that can be submitted to the XMA decoder at a time #define XMA_READBUFFER_MAX_PACKETS 4095u #define XMA_READBUFFER_MAX_BYTES (XMA_READBUFFER_MAX_PACKETS * XMA_BYTES_PER_PACKET) // Maximum size allowed for the XMA decoder's output buffers #define XMA_WRITEBUFFER_MAX_BYTES (31u * 256u) // Required byte alignment of the XMA decoder's output buffers #define XMA_WRITEBUFFER_BYTE_ALIGNMENT 256u // Decode chunk sizes for the XMA_PLAYBACK_INIT.subframesToDecode field #define XMA_MIN_SUBFRAMES_TO_DECODE 1u #define XMA_MAX_SUBFRAMES_TO_DECODE 8u #define XMA_OPTIMAL_SUBFRAMES_TO_DECODE 4u // LoopCount<255 means finite repetitions; LoopCount=255 means infinite looping #define XMA_MAX_LOOPCOUNT 254u #define XMA_INFINITE_LOOP 255u /*************************************************************************** * XMA format structures ***************************************************************************/ // The currently recommended way to express format information for XMA2 files // is the XMA2WAVEFORMATEX structure. This structure is fully compliant with // the WAVEFORMATEX standard and contains all the information needed to parse // and manage XMA2 files in a compact way. #define WAVE_FORMAT_XMA2 0x166 typedef struct XMA2WAVEFORMATEX { WAVEFORMATEX wfx; // Meaning of the WAVEFORMATEX fields here: // wFormatTag; // Audio format type; always WAVE_FORMAT_XMA2 // nChannels; // Channel count of the decoded audio // nSamplesPerSec; // Sample rate of the decoded audio // nAvgBytesPerSec; // Used internally by the XMA encoder // nBlockAlign; // Decoded sample size; channels * wBitsPerSample / 8 // wBitsPerSample; // Bits per decoded mono sample; always 16 for XMA // cbSize; // Size in bytes of the rest of this structure (34) WORD NumStreams; // Number of audio streams (1 or 2 channels each) DWORD ChannelMask; // Spatial positions of the channels in this file, // stored as SPEAKER_xxx values (see audiodefs.h) DWORD SamplesEncoded; // Total number of PCM samples the file decodes to DWORD BytesPerBlock; // XMA block size (but the last one may be shorter) DWORD PlayBegin; // First valid sample in the decoded audio DWORD PlayLength; // Length of the valid part of the decoded audio DWORD LoopBegin; // Beginning of the loop region in decoded sample terms DWORD LoopLength; // Length of the loop region in decoded sample terms BYTE LoopCount; // Number of loop repetitions; 255 = infinite BYTE EncoderVersion; // Version of XMA encoder that generated the file WORD BlockCount; // XMA blocks in file (and entries in its seek table) } XMA2WAVEFORMATEX, *PXMA2WAVEFORMATEX; // The legacy XMA format structures are described here for reference, but they // should not be used in new content. XMAWAVEFORMAT was the structure used in // XMA version 1 files. XMA2WAVEFORMAT was used in early XMA2 files; it is not // placed in the usual 'fmt' RIFF chunk but in its own 'XMA2' chunk. #ifndef WAVE_FORMAT_XMA #define WAVE_FORMAT_XMA 0x0165 // Values used in the ChannelMask fields below. Similar to the SPEAKER_xxx // values defined in audiodefs.h, but modified to fit in a single byte. #ifndef XMA_SPEAKER_LEFT #define XMA_SPEAKER_LEFT 0x01 #define XMA_SPEAKER_RIGHT 0x02 #define XMA_SPEAKER_CENTER 0x04 #define XMA_SPEAKER_LFE 0x08 #define XMA_SPEAKER_LEFT_SURROUND 0x10 #define XMA_SPEAKER_RIGHT_SURROUND 0x20 #define XMA_SPEAKER_LEFT_BACK 0x40 #define XMA_SPEAKER_RIGHT_BACK 0x80 #endif // Used in XMAWAVEFORMAT for per-stream data typedef struct XMASTREAMFORMAT { DWORD PsuedoBytesPerSec; // Used by the XMA encoder (typo preserved for legacy reasons) DWORD SampleRate; // The stream's decoded sample rate (in XMA2 files, // this is the same for all streams in the file). DWORD LoopStart; // Bit offset of the frame containing the loop start // point, relative to the beginning of the stream. DWORD LoopEnd; // Bit offset of the frame containing the loop end. BYTE SubframeData; // Two 4-bit numbers specifying the exact location of // the loop points within the frames that contain them. // SubframeEnd: Subframe of the loop end frame where // the loop ends. Ranges from 0 to 3. // SubframeSkip: Subframes to skip in the start frame to // reach the loop. Ranges from 0 to 4. BYTE Channels; // Number of channels in the stream (1 or 2) WORD ChannelMask; // Spatial positions of the channels in the stream } XMASTREAMFORMAT; // Legacy XMA1 format structure typedef struct XMAWAVEFORMAT { WORD FormatTag; // Audio format type (always WAVE_FORMAT_XMA) WORD BitsPerSample; // Bit depth (currently required to be 16) WORD EncodeOptions; // Options for XMA encoder/decoder WORD LargestSkip; // Largest skip used in interleaving streams WORD NumStreams; // Number of interleaved audio streams BYTE LoopCount; // Number of loop repetitions; 255 = infinite BYTE Version; // XMA encoder version that generated the file. // Always 3 or higher for XMA2 files. XMASTREAMFORMAT XmaStreams[1]; // Per-stream format information; the actual // array length is in the NumStreams field. } XMAWAVEFORMAT; // Used in XMA2WAVEFORMAT for per-stream data typedef struct XMA2STREAMFORMAT { BYTE Channels; // Number of channels in the stream (1 or 2) BYTE RESERVED; // Reserved for future use WORD ChannelMask; // Spatial positions of the channels in the stream } XMA2STREAMFORMAT; // Legacy XMA2 format structure (big-endian byte ordering) typedef struct XMA2WAVEFORMAT { BYTE Version; // XMA encoder version that generated the file. // Always 3 or higher for XMA2 files. BYTE NumStreams; // Number of interleaved audio streams BYTE RESERVED; // Reserved for future use BYTE LoopCount; // Number of loop repetitions; 255 = infinite DWORD LoopBegin; // Loop begin point, in samples DWORD LoopEnd; // Loop end point, in samples DWORD SampleRate; // The file's decoded sample rate DWORD EncodeOptions; // Options for the XMA encoder/decoder DWORD PsuedoBytesPerSec; // Used internally by the XMA encoder DWORD BlockSizeInBytes; // Size in bytes of this file's XMA blocks (except // possibly the last one). Always a multiple of // 2Kb, since XMA blocks are arrays of 2Kb packets. DWORD SamplesEncoded; // Total number of PCM samples encoded in this file DWORD SamplesInSource; // Actual number of PCM samples in the source // material used to generate this file DWORD BlockCount; // Number of XMA blocks in this file (and hence // also the number of entries in its seek table) XMA2STREAMFORMAT Streams[1]; // Per-stream format information; the actual // array length is in the NumStreams field. } XMA2WAVEFORMAT; #endif // #ifndef WAVE_FORMAT_XMA /*************************************************************************** * XMA packet structure (in big-endian form) ***************************************************************************/ typedef struct XMA2PACKET { int FrameCount : 6; // Number of XMA frames that begin in this packet int FrameOffsetInBits : 15; // Bit of XmaData where the first complete frame begins int PacketMetaData : 3; // Metadata stored in the packet (always 1 for XMA2) int PacketSkipCount : 8; // How many packets belonging to other streams must be // skipped to find the next packet belonging to this one BYTE XmaData[XMA_BYTES_PER_PACKET - sizeof(DWORD)]; // XMA encoded data } XMA2PACKET; // E.g. if the first DWORD of a packet is 0x30107902: // // 001100 000001000001111 001 00000010 // | | | |____ Skip 2 packets to find the next one for this stream // | | |___________ XMA2 signature (always 001) // | |_____________________ First frame starts 527 bits into packet // |________________________________ Packet contains 12 frames // Helper functions to extract the fields above from an XMA packet. (Note that // the bitfields cannot be read directly on little-endian architectures such as // the Intel x86, as they are laid out in big-endian form.) __inline DWORD GetXmaPacketFrameCount(__in_bcount(1) const BYTE* pPacket) { return (DWORD)(pPacket[0] >> 2); } __inline DWORD GetXmaPacketFirstFrameOffsetInBits(__in_bcount(3) const BYTE* pPacket) { return ((DWORD)(pPacket[0] & 0x3) << 13) | ((DWORD)(pPacket[1]) << 5) | ((DWORD)(pPacket[2]) >> 3); } __inline DWORD GetXmaPacketMetadata(__in_bcount(3) const BYTE* pPacket) { return (DWORD)(pPacket[2] & 0x7); } __inline DWORD GetXmaPacketSkipCount(__in_bcount(4) const BYTE* pPacket) { return (DWORD)(pPacket[3]); } /*************************************************************************** * XMA frame structure ***************************************************************************/ // There is no way to represent the XMA frame as a C struct, since it is a // variable-sized string of bits that need not be stored at a byte-aligned // position in memory. This is the layout: // // XMAFRAME // { // LengthInBits: A 15-bit number representing the length of this frame. // XmaData: Encoded XMA data; its size in bits is (LengthInBits - 15). // } // Size in bits of the frame's initial LengthInBits field #define XMA_BITS_IN_FRAME_LENGTH_FIELD 15 // Special LengthInBits value that marks an invalid final frame #define XMA_FINAL_FRAME_MARKER 0x7FFF /*************************************************************************** * XMA helper functions ***************************************************************************/ // We define a local ASSERT macro to equal the global one if it exists. // You can define XMA2DEFS_ASSERT in advance to override this default. #ifndef XMA2DEFS_ASSERT #ifdef ASSERT #define XMA2DEFS_ASSERT ASSERT #else #define XMA2DEFS_ASSERT(a) /* No-op by default */ #endif #endif // GetXmaBlockContainingSample: Use a given seek table to find the XMA block // containing a given decoded sample. Note that the seek table entries in an // XMA file are stored in big-endian form and may need to be converted prior // to calling this function. __inline HRESULT GetXmaBlockContainingSample ( DWORD nBlockCount, // Blocks in the file (= seek table entries) __in_ecount(nBlockCount) const DWORD* pSeekTable, // Pointer to the seek table data DWORD nDesiredSample, // Decoded sample to locate __out DWORD* pnBlockContainingSample, // Index of the block containing the sample __out DWORD* pnSampleOffsetWithinBlock // Position of the sample in this block ) { DWORD nPreviousTotalSamples = 0; DWORD nBlock; DWORD nTotalSamplesSoFar; XMA2DEFS_ASSERT(pSeekTable); XMA2DEFS_ASSERT(pnBlockContainingSample); XMA2DEFS_ASSERT(pnSampleOffsetWithinBlock); for (nBlock = 0; nBlock < nBlockCount; ++nBlock) { nTotalSamplesSoFar = pSeekTable[nBlock]; if (nTotalSamplesSoFar > nDesiredSample) { *pnBlockContainingSample = nBlock; *pnSampleOffsetWithinBlock = nDesiredSample - nPreviousTotalSamples; return S_OK; } nPreviousTotalSamples = nTotalSamplesSoFar; } return E_FAIL; } // GetXmaFrameLengthInBits: Reads a given frame's LengthInBits field. __inline DWORD GetXmaFrameLengthInBits ( __in_bcount(nBitPosition / 8 + 3) __in const BYTE* pPacket, // Pointer to XMA packet[s] containing the frame DWORD nBitPosition // Bit offset of the frame within this packet ) { DWORD nRegion; DWORD nBytePosition = nBitPosition / 8; DWORD nBitOffset = nBitPosition % 8; if (nBitOffset < 2) // Only need to read 2 bytes (and might not be safe to read more) { nRegion = (DWORD)(pPacket[nBytePosition+0]) << 8 | (DWORD)(pPacket[nBytePosition+1]); return (nRegion >> (1 - nBitOffset)) & 0x7FFF; // Last 15 bits } else // Need to read 3 bytes { nRegion = (DWORD)(pPacket[nBytePosition+0]) << 16 | (DWORD)(pPacket[nBytePosition+1]) << 8 | (DWORD)(pPacket[nBytePosition+2]); return (nRegion >> (9 - nBitOffset)) & 0x7FFF; // Last 15 bits } } // GetXmaFrameBitPosition: Calculates the bit offset of a given frame within // an XMA block or set of blocks. Returns 0 on failure. __inline DWORD GetXmaFrameBitPosition ( __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] DWORD nXmaDataBytes, // Size of pXmaData in bytes DWORD nStreamIndex, // Stream within which to seek DWORD nDesiredFrame // Frame sought ) { const BYTE* pCurrentPacket; DWORD nPacketsExamined = 0; DWORD nFrameCountSoFar = 0; DWORD nFramesToSkip; DWORD nFrameBitOffset; XMA2DEFS_ASSERT(pXmaData); XMA2DEFS_ASSERT(nXmaDataBytes % XMA_BYTES_PER_PACKET == 0); // Get the first XMA packet belonging to the desired stream, relying on the // fact that the first packets for each stream are in consecutive order at // the beginning of an XMA block. pCurrentPacket = pXmaData + nStreamIndex * XMA_BYTES_PER_PACKET; for (;;) { // If we have exceeded the size of the XMA data, return failure if (pCurrentPacket + XMA_BYTES_PER_PACKET > pXmaData + nXmaDataBytes) { return 0; } // If the current packet contains the frame we are looking for... if (nFrameCountSoFar + GetXmaPacketFrameCount(pCurrentPacket) > nDesiredFrame) { // See how many frames in this packet we need to skip to get to it XMA2DEFS_ASSERT(nDesiredFrame >= nFrameCountSoFar); nFramesToSkip = nDesiredFrame - nFrameCountSoFar; // Get the bit offset of the first frame in this packet nFrameBitOffset = XMA_PACKET_HEADER_BITS + GetXmaPacketFirstFrameOffsetInBits(pCurrentPacket); // Advance nFrameBitOffset to the frame of interest while (nFramesToSkip--) { nFrameBitOffset += GetXmaFrameLengthInBits(pCurrentPacket, nFrameBitOffset); } // The bit offset to return is the number of bits from pXmaData to // pCurrentPacket plus the bit offset of the frame of interest return (DWORD)(pCurrentPacket - pXmaData) * 8 + nFrameBitOffset; } // If we haven't found the right packet yet, advance our counters ++nPacketsExamined; nFrameCountSoFar += GetXmaPacketFrameCount(pCurrentPacket); // And skip to the next packet belonging to the same stream pCurrentPacket += XMA_BYTES_PER_PACKET * (GetXmaPacketSkipCount(pCurrentPacket) + 1); } } // GetLastXmaFrameBitPosition: Calculates the bit offset of the last complete // frame in an XMA block or set of blocks. __inline DWORD GetLastXmaFrameBitPosition ( __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] DWORD nXmaDataBytes, // Size of pXmaData in bytes DWORD nStreamIndex // Stream within which to seek ) { const BYTE* pLastPacket; DWORD nBytesToNextPacket; DWORD nFrameBitOffset; DWORD nFramesInLastPacket; XMA2DEFS_ASSERT(pXmaData); XMA2DEFS_ASSERT(nXmaDataBytes % XMA_BYTES_PER_PACKET == 0); XMA2DEFS_ASSERT(nXmaDataBytes >= XMA_BYTES_PER_PACKET * (nStreamIndex + 1)); // Get the first XMA packet belonging to the desired stream, relying on the // fact that the first packets for each stream are in consecutive order at // the beginning of an XMA block. pLastPacket = pXmaData + nStreamIndex * XMA_BYTES_PER_PACKET; // Search for the last packet belonging to the desired stream for (;;) { nBytesToNextPacket = XMA_BYTES_PER_PACKET * (GetXmaPacketSkipCount(pLastPacket) + 1); XMA2DEFS_ASSERT(nBytesToNextPacket); if (pLastPacket + nBytesToNextPacket + XMA_BYTES_PER_PACKET > pXmaData + nXmaDataBytes) { break; // The next packet would extend beyond the end of pXmaData } pLastPacket += nBytesToNextPacket; } // The last packet can sometimes have no seekable frames, in which case we // have to use the previous one if (GetXmaPacketFrameCount(pLastPacket) == 0) { pLastPacket -= nBytesToNextPacket; } // Found the last packet. Get the bit offset of its first frame. nFrameBitOffset = XMA_PACKET_HEADER_BITS + GetXmaPacketFirstFrameOffsetInBits(pLastPacket); // Traverse frames until we reach the last one nFramesInLastPacket = GetXmaPacketFrameCount(pLastPacket); while (--nFramesInLastPacket) { nFrameBitOffset += GetXmaFrameLengthInBits(pLastPacket, nFrameBitOffset); } // The bit offset to return is the number of bits from pXmaData to // pLastPacket plus the offset of the last frame in this packet. return (DWORD)(pLastPacket - pXmaData) * 8 + nFrameBitOffset; } // GetXmaDecodePositionForSample: Obtains the information needed to make the // decoder generate audio starting at a given sample position relative to the // beginning of the given XMA block: the bit offset of the appropriate frame, // and the right subframe within that frame. This data can be passed directly // to the XMAPlaybackSetDecodePosition function. __inline HRESULT GetXmaDecodePositionForSample ( __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] DWORD nXmaDataBytes, // Size of pXmaData in bytes DWORD nStreamIndex, // Stream within which to seek DWORD nDesiredSample, // Sample sought __out DWORD* pnBitOffset, // Returns the bit offset within pXmaData of // the frame containing the sample sought __out DWORD* pnSubFrame // Returns the subframe containing the sample ) { DWORD nDesiredFrame = nDesiredSample / XMA_SAMPLES_PER_FRAME; DWORD nSubFrame = (nDesiredSample % XMA_SAMPLES_PER_FRAME) / XMA_SAMPLES_PER_SUBFRAME; DWORD nBitOffset = GetXmaFrameBitPosition(pXmaData, nXmaDataBytes, nStreamIndex, nDesiredFrame); XMA2DEFS_ASSERT(pnBitOffset); XMA2DEFS_ASSERT(pnSubFrame); if (nBitOffset) { *pnBitOffset = nBitOffset; *pnSubFrame = nSubFrame; return S_OK; } else { return E_FAIL; } } // GetXmaSampleRate: Obtains the legal XMA sample rate (24, 32, 44.1 or 48Khz) // corresponding to a generic sample rate. __inline DWORD GetXmaSampleRate(DWORD dwGeneralRate) { DWORD dwXmaRate = 48000; // Default XMA rate for all rates above 44100Hz if (dwGeneralRate <= 24000) dwXmaRate = 24000; else if (dwGeneralRate <= 32000) dwXmaRate = 32000; else if (dwGeneralRate <= 44100) dwXmaRate = 44100; return dwXmaRate; } // Functions to convert between WAVEFORMATEXTENSIBLE channel masks (combinations // of the SPEAKER_xxx flags defined in audiodefs.h) and XMA channel masks (which // are limited to eight possible speaker positions: left, right, center, low // frequency, side left, side right, back left and back right). __inline DWORD GetStandardChannelMaskFromXmaMask(BYTE bXmaMask) { DWORD dwStandardMask = 0; if (bXmaMask & XMA_SPEAKER_LEFT) dwStandardMask |= SPEAKER_FRONT_LEFT; if (bXmaMask & XMA_SPEAKER_RIGHT) dwStandardMask |= SPEAKER_FRONT_RIGHT; if (bXmaMask & XMA_SPEAKER_CENTER) dwStandardMask |= SPEAKER_FRONT_CENTER; if (bXmaMask & XMA_SPEAKER_LFE) dwStandardMask |= SPEAKER_LOW_FREQUENCY; if (bXmaMask & XMA_SPEAKER_LEFT_SURROUND) dwStandardMask |= SPEAKER_SIDE_LEFT; if (bXmaMask & XMA_SPEAKER_RIGHT_SURROUND) dwStandardMask |= SPEAKER_SIDE_RIGHT; if (bXmaMask & XMA_SPEAKER_LEFT_BACK) dwStandardMask |= SPEAKER_BACK_LEFT; if (bXmaMask & XMA_SPEAKER_RIGHT_BACK) dwStandardMask |= SPEAKER_BACK_RIGHT; return dwStandardMask; } __inline BYTE GetXmaChannelMaskFromStandardMask(DWORD dwStandardMask) { BYTE bXmaMask = 0; if (dwStandardMask & SPEAKER_FRONT_LEFT) bXmaMask |= XMA_SPEAKER_LEFT; if (dwStandardMask & SPEAKER_FRONT_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT; if (dwStandardMask & SPEAKER_FRONT_CENTER) bXmaMask |= XMA_SPEAKER_CENTER; if (dwStandardMask & SPEAKER_LOW_FREQUENCY) bXmaMask |= XMA_SPEAKER_LFE; if (dwStandardMask & SPEAKER_SIDE_LEFT) bXmaMask |= XMA_SPEAKER_LEFT_SURROUND; if (dwStandardMask & SPEAKER_SIDE_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT_SURROUND; if (dwStandardMask & SPEAKER_BACK_LEFT) bXmaMask |= XMA_SPEAKER_LEFT_BACK; if (dwStandardMask & SPEAKER_BACK_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT_BACK; return bXmaMask; } // LocalizeXma2Format: Modifies a XMA2WAVEFORMATEX structure in place to comply // with the current platform's byte-ordering rules (little- or big-endian). __inline HRESULT LocalizeXma2Format(__inout XMA2WAVEFORMATEX* pXma2Format) { #define XMASWAP2BYTES(n) ((WORD)(((n) >> 8) | (((n) & 0xff) << 8))) #define XMASWAP4BYTES(n) ((DWORD)((n) >> 24 | (n) << 24 | ((n) & 0xff00) << 8 | ((n) & 0xff0000) >> 8)) if (pXma2Format->wfx.wFormatTag == WAVE_FORMAT_XMA2) { return S_OK; } else if (XMASWAP2BYTES(pXma2Format->wfx.wFormatTag) == WAVE_FORMAT_XMA2) { pXma2Format->wfx.wFormatTag = XMASWAP2BYTES(pXma2Format->wfx.wFormatTag); pXma2Format->wfx.nChannels = XMASWAP2BYTES(pXma2Format->wfx.nChannels); pXma2Format->wfx.nSamplesPerSec = XMASWAP4BYTES(pXma2Format->wfx.nSamplesPerSec); pXma2Format->wfx.nAvgBytesPerSec = XMASWAP4BYTES(pXma2Format->wfx.nAvgBytesPerSec); pXma2Format->wfx.nBlockAlign = XMASWAP2BYTES(pXma2Format->wfx.nBlockAlign); pXma2Format->wfx.wBitsPerSample = XMASWAP2BYTES(pXma2Format->wfx.wBitsPerSample); pXma2Format->wfx.cbSize = XMASWAP2BYTES(pXma2Format->wfx.cbSize); pXma2Format->NumStreams = XMASWAP2BYTES(pXma2Format->NumStreams); pXma2Format->ChannelMask = XMASWAP4BYTES(pXma2Format->ChannelMask); pXma2Format->SamplesEncoded = XMASWAP4BYTES(pXma2Format->SamplesEncoded); pXma2Format->BytesPerBlock = XMASWAP4BYTES(pXma2Format->BytesPerBlock); pXma2Format->PlayBegin = XMASWAP4BYTES(pXma2Format->PlayBegin); pXma2Format->PlayLength = XMASWAP4BYTES(pXma2Format->PlayLength); pXma2Format->LoopBegin = XMASWAP4BYTES(pXma2Format->LoopBegin); pXma2Format->LoopLength = XMASWAP4BYTES(pXma2Format->LoopLength); pXma2Format->BlockCount = XMASWAP2BYTES(pXma2Format->BlockCount); return S_OK; } else { return E_FAIL; // Not a recognizable XMA2 format } #undef XMASWAP2BYTES #undef XMASWAP4BYTES } #endif // #ifndef __XMA2DEFS_INCLUDED__ ================================================ FILE: dependencies/gamemode/CMakeLists.txt ================================================ project( gamemode LANGUAGES C ) add_library (gamemode "lib/gamemode_client.h" "lib/client_loader.c") ================================================ FILE: dependencies/gamemode/lib/client_loader.c ================================================ /* Copyright (c) 2017-2019, Feral Interactive 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. * Neither the name of Feral Interactive nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 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 OWNER 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. */ // Simply include the header with GAMEMODE_AUTO set // This will ensure it calls the functions when it's loaded #define GAMEMODE_AUTO #include "gamemode_client.h" ================================================ FILE: dependencies/gamemode/lib/gamemode_client.h ================================================ /* Copyright (c) 2017-2019, Feral Interactive 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. * Neither the name of Feral Interactive nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 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 OWNER 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. */ #ifndef CLIENT_GAMEMODE_H #define CLIENT_GAMEMODE_H /* * GameMode supports the following client functions * Requests are refcounted in the daemon * * int gamemode_request_start() - Request gamemode starts * 0 if the request was sent successfully * -1 if the request failed * * int gamemode_request_end() - Request gamemode ends * 0 if the request was sent successfully * -1 if the request failed * * GAMEMODE_AUTO can be defined to make the above two functions apply during static init and * destruction, as appropriate. In this configuration, errors will be printed to stderr * * int gamemode_query_status() - Query the current status of gamemode * 0 if gamemode is inactive * 1 if gamemode is active * 2 if gamemode is active and this client is registered * -1 if the query failed * * int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process * 0 if the request was sent successfully * -1 if the request failed * -2 if the request was rejected * * int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process * 0 if the request was sent successfully * -1 if the request failed * -2 if the request was rejected * * int gamemode_query_status_for(pid_t pid) - Query status of gamemode for another process * 0 if gamemode is inactive * 1 if gamemode is active * 2 if gamemode is active and this client is registered * -1 if the query failed * * const char* gamemode_error_string() - Get an error string * returns a string describing any of the above errors * * Note: All the above requests can be blocking - dbus requests can and will block while the daemon * handles the request. It is not recommended to make these calls in performance critical code */ #include #include #include #include #include #include static char internal_gamemode_client_error_string[512] = { 0 }; /** * Load libgamemode dynamically to dislodge us from most dependencies. * This allows clients to link and/or use this regardless of runtime. * See SDL2 for an example of the reasoning behind this in terms of * dynamic versioning as well. */ static volatile int internal_libgamemode_loaded = 1; /* Typedefs for the functions to load */ typedef int (*api_call_return_int)(void); typedef const char *(*api_call_return_cstring)(void); typedef int (*api_call_pid_return_int)(pid_t); /* Storage for functors */ static api_call_return_int REAL_internal_gamemode_request_start = NULL; static api_call_return_int REAL_internal_gamemode_request_end = NULL; static api_call_return_int REAL_internal_gamemode_query_status = NULL; static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL; /** * Internal helper to perform the symbol binding safely. * * Returns 0 on success and -1 on failure */ __attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol( void *handle, const char *name, void **out_func, size_t func_size, bool required) { void *symbol_lookup = NULL; char *dl_error = NULL; /* Safely look up the symbol */ symbol_lookup = dlsym(handle, name); dl_error = dlerror(); if (required && (dl_error || !symbol_lookup)) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "dlsym failed - %s", dl_error); return -1; } /* Have the symbol correctly, copy it to make it usable */ memcpy(out_func, &symbol_lookup, func_size); return 0; } /** * Loads libgamemode and needed functions * * Returns 0 on success and -1 on failure */ __attribute__((always_inline)) static inline int internal_load_libgamemode(void) { /* We start at 1, 0 is a success and -1 is a fail */ if (internal_libgamemode_loaded != 1) { return internal_libgamemode_loaded; } /* Anonymous struct type to define our bindings */ struct binding { const char *name; void **functor; size_t func_size; bool required; } bindings[] = { { "real_gamemode_request_start", (void **)&REAL_internal_gamemode_request_start, sizeof(REAL_internal_gamemode_request_start), true }, { "real_gamemode_request_end", (void **)&REAL_internal_gamemode_request_end, sizeof(REAL_internal_gamemode_request_end), true }, { "real_gamemode_query_status", (void **)&REAL_internal_gamemode_query_status, sizeof(REAL_internal_gamemode_query_status), false }, { "real_gamemode_error_string", (void **)&REAL_internal_gamemode_error_string, sizeof(REAL_internal_gamemode_error_string), true }, { "real_gamemode_request_start_for", (void **)&REAL_internal_gamemode_request_start_for, sizeof(REAL_internal_gamemode_request_start_for), false }, { "real_gamemode_request_end_for", (void **)&REAL_internal_gamemode_request_end_for, sizeof(REAL_internal_gamemode_request_end_for), false }, { "real_gamemode_query_status_for", (void **)&REAL_internal_gamemode_query_status_for, sizeof(REAL_internal_gamemode_query_status_for), false }, }; void *libgamemode = NULL; /* Try and load libgamemode */ libgamemode = dlopen("libgamemode.so.0", RTLD_NOW); if (!libgamemode) { /* Attempt to load unversioned library for compatibility with older * versions (as of writing, there are no ABI changes between the two - * this may need to change if ever ABI-breaking changes are made) */ libgamemode = dlopen("libgamemode.so", RTLD_NOW); if (!libgamemode) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "dlopen failed - %s", dlerror()); internal_libgamemode_loaded = -1; return -1; } } /* Attempt to bind all symbols */ for (size_t i = 0; i < sizeof(bindings) / sizeof(bindings[0]); i++) { struct binding *binder = &bindings[i]; if (internal_bind_libgamemode_symbol(libgamemode, binder->name, binder->functor, binder->func_size, binder->required)) { internal_libgamemode_loaded = -1; return -1; }; } /* Success */ internal_libgamemode_loaded = 0; return 0; } /** * Redirect to the real libgamemode */ __attribute__((always_inline)) static inline const char *gamemode_error_string(void) { /* If we fail to load the system gamemode, or we have an error string already, return our error * string instead of diverting to the system version */ if (internal_load_libgamemode() < 0 || internal_gamemode_client_error_string[0] != '\0') { return internal_gamemode_client_error_string; } /* Assert for static analyser that the function is not NULL */ assert(REAL_internal_gamemode_error_string != NULL); return REAL_internal_gamemode_error_string(); } /** * Redirect to the real libgamemode * Allow automatically requesting game mode * Also prints errors as they happen. */ #ifdef GAMEMODE_AUTO __attribute__((constructor)) #else __attribute__((always_inline)) static inline #endif int gamemode_request_start(void) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { #ifdef GAMEMODE_AUTO fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); #endif return -1; } /* Assert for static analyser that the function is not NULL */ assert(REAL_internal_gamemode_request_start != NULL); if (REAL_internal_gamemode_request_start() < 0) { #ifdef GAMEMODE_AUTO fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); #endif return -1; } return 0; } /* Redirect to the real libgamemode */ #ifdef GAMEMODE_AUTO __attribute__((destructor)) #else __attribute__((always_inline)) static inline #endif int gamemode_request_end(void) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { #ifdef GAMEMODE_AUTO fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); #endif return -1; } /* Assert for static analyser that the function is not NULL */ assert(REAL_internal_gamemode_request_end != NULL); if (REAL_internal_gamemode_request_end() < 0) { #ifdef GAMEMODE_AUTO fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); #endif return -1; } return 0; } /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_query_status(void) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { return -1; } if (REAL_internal_gamemode_query_status == NULL) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "gamemode_query_status missing (older host?)"); return -1; } return REAL_internal_gamemode_query_status(); } /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { return -1; } if (REAL_internal_gamemode_request_start_for == NULL) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "gamemode_request_start_for missing (older host?)"); return -1; } return REAL_internal_gamemode_request_start_for(pid); } /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { return -1; } if (REAL_internal_gamemode_request_end_for == NULL) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "gamemode_request_end_for missing (older host?)"); return -1; } return REAL_internal_gamemode_request_end_for(pid); } /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { return -1; } if (REAL_internal_gamemode_query_status_for == NULL) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "gamemode_query_status_for missing (older host?)"); return -1; } return REAL_internal_gamemode_query_status_for(pid); } #endif // CLIENT_GAMEMODE_H ================================================ FILE: dependencies/ih264d/CHANGES ================================================ Code adjustments made by Team Cemu for Cemu emulator project as of 2022-02-01: - Stripped encoder, tests and fuzzer. We only need the decoder - Extended ih264_platform_macros.h to support MSVC instrinsics - Extended ithread.c to use WinAPI natives instead of requiring pthread.h - The decoder will output images without regarding SPS crop flag and instead pass the crop values in the decode output structure - Modified decoder/x86/ih264d_function_selector.c ih264d_init_arch() to automatically choose the best e_processor_arch for the current CPU ================================================ FILE: dependencies/ih264d/CMakeLists.txt ================================================ cmake_minimum_required (VERSION 3.8) project ("ih264d") add_library (ih264d "common/ih264_buf_mgr.c" "common/ih264_buf_mgr.h" "common/ih264_cabac_tables.c" "common/ih264_cabac_tables.h" "common/ih264_cavlc_tables.c" "common/ih264_cavlc_tables.h" "common/ih264_chroma_intra_pred_filters.c" "common/ih264_common_tables.c" "common/ih264_common_tables.h" "common/ih264_deblk_edge_filters.c" "common/ih264_deblk_edge_filters.h" "common/ih264_deblk_tables.c" "common/ih264_deblk_tables.h" "common/ih264_debug.h" "common/ih264_defs.h" "common/ih264_disp_mgr.c" "common/ih264_disp_mgr.h" "common/ih264_dpb_mgr.c" "common/ih264_dpb_mgr.h" "common/ih264_error.h" "common/ih264_ihadamard_scaling.c" "common/ih264_inter_pred_filters.c" "common/ih264_inter_pred_filters.h" "common/ih264_intra_pred_filters.h" "common/ih264_iquant_itrans_recon.c" "common/ih264_list.c" "common/ih264_list.h" "common/ih264_luma_intra_pred_filters.c" "common/ih264_macros.h" "common/ih264_mem_fns.c" "common/ih264_mem_fns.h" "common/ih264_padding.c" "common/ih264_padding.h" "common/ih264_resi_trans.h" "common/ih264_resi_trans_quant.c" "common/ih264_size_defs.h" "common/ih264_structs.h" "common/ih264_trans_data.c" "common/ih264_trans_data.h" "common/ih264_trans_macros.h" "common/ih264_trans_quant_itrans_iquant.h" "common/ih264_typedefs.h" "common/ih264_weighted_pred.c" "common/ih264_weighted_pred.h" "common/ithread.c" "common/ithread.h" "decoder/ih264d.h" "decoder/ih264d_api.c" "decoder/ih264d_bitstrm.c" "decoder/ih264d_bitstrm.h" "decoder/ih264d_cabac.c" "decoder/ih264d_cabac.h" "decoder/ih264d_cabac_init_tables.c" "decoder/ih264d_compute_bs.c" "decoder/ih264d_deblocking.c" "decoder/ih264d_deblocking.h" "decoder/ih264d_debug.h" "decoder/ih264d_defs.h" "decoder/ih264d_dpb_manager.h" "decoder/ih264d_dpb_mgr.c" "decoder/ih264d_error_handler.h" "decoder/ih264d_format_conv.c" "decoder/ih264d_format_conv.h" "decoder/ih264d_function_selector.h" "decoder/ih264d_function_selector_generic.c" "decoder/ih264d_inter_pred.c" "decoder/ih264d_inter_pred.h" "decoder/ih264d_mb_utils.c" "decoder/ih264d_mb_utils.h" "decoder/ih264d_mem_request.h" "decoder/ih264d_mvpred.c" "decoder/ih264d_mvpred.h" "decoder/ih264d_nal.c" "decoder/ih264d_nal.h" "decoder/ih264d_parse_bslice.c" "decoder/ih264d_parse_cabac.c" "decoder/ih264d_parse_cabac.h" "decoder/ih264d_parse_cavlc.c" "decoder/ih264d_parse_cavlc.h" "decoder/ih264d_parse_headers.c" "decoder/ih264d_parse_headers.h" "decoder/ih264d_parse_islice.c" "decoder/ih264d_parse_islice.h" "decoder/ih264d_parse_mb_header.c" "decoder/ih264d_parse_mb_header.h" "decoder/ih264d_parse_pslice.c" "decoder/ih264d_parse_slice.c" "decoder/ih264d_parse_slice.h" "decoder/ih264d_process_bslice.c" "decoder/ih264d_process_bslice.h" "decoder/ih264d_process_intra_mb.c" "decoder/ih264d_process_intra_mb.h" "decoder/ih264d_process_pslice.c" "decoder/ih264d_process_pslice.h" "decoder/ih264d_quant_scaling.c" "decoder/ih264d_quant_scaling.h" "decoder/ih264d_sei.c" "decoder/ih264d_sei.h" "decoder/ih264d_structs.h" "decoder/ih264d_tables.c" "decoder/ih264d_tables.h" "decoder/ih264d_thread_compute_bs.c" "decoder/ih264d_thread_compute_bs.h" "decoder/ih264d_thread_parse_decode.c" "decoder/ih264d_thread_parse_decode.h" "decoder/ih264d_transfer_address.h" "decoder/ih264d_utils.c" "decoder/ih264d_utils.h" "decoder/ih264d_vui.c" "decoder/ih264d_vui.h" "decoder/iv.h" "decoder/ivd.h" ) if (CMAKE_OSX_ARCHITECTURES) set(IH264D_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) else() set(IH264D_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) endif() if (IH264D_ARCHITECTURE STREQUAL "x86_64" OR IH264D_ARCHITECTURE STREQUAL "amd64" OR IH264D_ARCHITECTURE STREQUAL "AMD64") set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86") include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES}) target_sources(ih264d PRIVATE "common/x86/ih264_chroma_intra_pred_filters_ssse3.c" "common/x86/ih264_deblk_chroma_ssse3.c" "common/x86/ih264_deblk_luma_ssse3.c" "common/x86/ih264_ihadamard_scaling_sse42.c" "common/x86/ih264_ihadamard_scaling_ssse3.c" "common/x86/ih264_inter_pred_filters_ssse3.c" "common/x86/ih264_iquant_itrans_recon_dc_ssse3.c" "common/x86/ih264_iquant_itrans_recon_sse42.c" "common/x86/ih264_iquant_itrans_recon_ssse3.c" "common/x86/ih264_luma_intra_pred_filters_ssse3.c" "common/x86/ih264_mem_fns_ssse3.c" "common/x86/ih264_padding_ssse3.c" "common/x86/ih264_platform_macros.h" "common/x86/ih264_resi_trans_quant_sse42.c" "common/x86/ih264_weighted_pred_sse42.c" "decoder/x86/ih264d_function_selector.c" "decoder/x86/ih264d_function_selector_sse42.c" "decoder/x86/ih264d_function_selector_ssse3.c" ) elseif(IH264D_ARCHITECTURE STREQUAL "aarch64" OR IH264D_ARCHITECTURE STREQUAL "arm64") enable_language( C CXX ASM ) set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm") include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES}) target_sources(ih264d PRIVATE "common/armv8/ih264_deblk_chroma_av8.s" "common/armv8/ih264_deblk_luma_av8.s" "common/armv8/ih264_default_weighted_pred_av8.s" "common/armv8/ih264_ihadamard_scaling_av8.s" "common/armv8/ih264_inter_pred_chroma_av8.s" "common/armv8/ih264_inter_pred_filters_luma_horz_av8.s" "common/armv8/ih264_inter_pred_filters_luma_vert_av8.s" "common/armv8/ih264_inter_pred_luma_copy_av8.s" "common/armv8/ih264_inter_pred_luma_horz_hpel_vert_hpel_av8.s" "common/armv8/ih264_inter_pred_luma_horz_hpel_vert_qpel_av8.s" "common/armv8/ih264_inter_pred_luma_horz_qpel_av8.s" "common/armv8/ih264_inter_pred_luma_horz_qpel_vert_hpel_av8.s" "common/armv8/ih264_inter_pred_luma_horz_qpel_vert_qpel_av8.s" "common/armv8/ih264_inter_pred_luma_vert_qpel_av8.s" "common/armv8/ih264_intra_pred_chroma_av8.s" "common/armv8/ih264_intra_pred_luma_16x16_av8.s" "common/armv8/ih264_intra_pred_luma_4x4_av8.s" "common/armv8/ih264_intra_pred_luma_8x8_av8.s" "common/armv8/ih264_iquant_itrans_recon_av8.s" "common/armv8/ih264_iquant_itrans_recon_dc_av8.s" "common/armv8/ih264_mem_fns_neon_av8.s" "common/armv8/ih264_neon_macros.s" "common/armv8/ih264_padding_neon_av8.s" "common/armv8/ih264_platform_macros.h" "common/armv8/ih264_resi_trans_quant_av8.s" "common/armv8/ih264_weighted_bi_pred_av8.s" "common/armv8/ih264_weighted_pred_av8.s" "decoder/arm/ih264d_function_selector_a9q.c" "decoder/arm/ih264d_function_selector_av8.c" "decoder/arm/ih264d_function_selector.c" ) target_compile_options(ih264d PRIVATE -DARMV8 $<$:-Wno-unused-command-line-argument>) if(NOT MSVC) set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp") endif() if(APPLE) target_sources(ih264d PRIVATE "common/armv8/macos_arm_symbol_aliases.s") endif() else() message(FATAL_ERROR "ih264d unknown architecture: ${IH264D_ARCHITECTURE}") endif() if(MSVC) set_property(TARGET ih264d PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") # tune settings for slightly better performance target_compile_options(ih264d PRIVATE $<$:/Oi>) # enable intrinsic functions target_compile_options(ih264d PRIVATE $<$:/Ot>) # favor speed target_compile_options(ih264d PRIVATE "/GS-") # disable runtime checks endif() ================================================ FILE: dependencies/ih264d/CMakeSettings.json ================================================ { "configurations": [ { "name": "x64-Debug", "generator": "Ninja", "configurationType": "Debug", "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "" }, { "name": "x64-Release", "generator": "Ninja", "configurationType": "Release", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ] } ] } ================================================ FILE: dependencies/ih264d/NOTICE ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ ================================================ FILE: dependencies/ih264d/common/arm/ih264_arm_memory_barrier.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @******************************************************************************* @* @file @* ih264_arm_memory_barrier.s @* @* @brief @* Contains function definitions for data synchronization. @* @* @author @* Ittiam @* @* @par List of Functions: @* @* @* @remarks @* None @* @******************************************************************************* .text .p2align 2 @***************************************************************************** @* @* Function Name : ih264_arm_dsb @* Description : Adds DSB @* Revision History : @* DD MM YYYY Author(s) Changes @* 03 07 2008 100355 First version @* @***************************************************************************** .global ih264_arm_dsb ih264_arm_dsb: dsb bx lr @***************************************************************************** @* @* Function Name : ih264_arm_dmb @* Description : Adds DMB @* Revision History : @* DD MM YYYY Author(s) Changes @* 03 07 2008 100355 First version @* @***************************************************************************** .global ih264_arm_dmb ih264_arm_dmb: dmb bx lr ================================================ FILE: dependencies/ih264d/common/arm/ih264_deblk_chroma_a9.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @/*****************************************************************************/ @/* */ @/* File Name : ih264_deblk_chroma_a9.s */ @/* */ @/* Description : Contains function definitions for deblocking luma */ @/* edge. Functions are coded in NEON assembly and can */ @/* be compiled using ARM RVDS. */ @/* */ @/* List of Functions : ih264_deblk_chroma_vert_bs4_bp_a9() */ @/* ih264_deblk_chroma_vert_bslt4_bp_a9() */ @/* ih264_deblk_chroma_horz_bs4_bp_a9() */ @/* ih264_deblk_chroma_horz_bslt4_bp_a9() */ @/* ih264_deblk_chroma_vert_bs4_mbaff_bp_a9() */ @/* ih264_deblk_chroma_vert_bslt4_mbaff_bp_a9() */ @/* ih264_deblk_chroma_vert_bs4_a9() */ @/* ih264_deblk_chroma_vert_bslt4_a9() */ @/* ih264_deblk_chroma_horz_bs4_a9() */ @/* ih264_deblk_chroma_horz_bslt4_a9() */ @/* ih264_deblk_chroma_vert_bs4_mbaff_a9() */ @/* ih264_deblk_chroma_vert_bslt4_mbaff_a9() */ @/* */ @/* Issues / Problems : None */ @/* */ @/* Revision History : */ @/* */ @/* DD MM YYYY Author(s) Changes (Describe the changes made) */ @/* 28 11 2013 Ittiam Draft */ @/* 05 01 2015 Kaushik Added double-call functions for */ @/* Senthoor vertical deblocking, and high */ @/* profile functions. */ @/* */ @/*****************************************************************************/ .text .p2align 2 @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block horizontal edge when the @* boundary strength is set to 4 @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_horz_bs4_bp_a9 ih264_deblk_chroma_horz_bs4_bp_a9: stmfd sp!, {r4, lr} @ vpush {d8 - d15} sub r0, r0, r1, lsl #1 @R0 = uc_edgePixel pointing to p1 of chroma vld2.8 {d6, d7}, [r0], r1 @D6 = p1u , D7 = p1v mov r4, r0 @Keeping a backup of the pointer p0 of chroma vld2.8 {d4, d5}, [r0], r1 @D4 = p0u , D5 = p0v vdup.8 q10, r2 @Q10 contains alpha vld2.8 {d0, d1}, [r0], r1 @D0 = q0u , D1 = q0v vaddl.u8 q4, d6, d0 @ vaddl.u8 q5, d7, d1 @Q4,Q5 = q0 + p1 vmov.i8 d31, #2 @ vld2.8 {d2, d3}, [r0] @D2 = q1u , D3 = q1v vabd.u8 q13, q3, q2 @Q13 = ABS(p1 - p0) vmlal.u8 q4, d2, d31 @ vmlal.u8 q5, d3, d31 @Q5,Q4 = (X2(q1U) + q0U + p1U) vabd.u8 q11, q2, q0 @Q11 = ABS(p0 - q0) vabd.u8 q12, q1, q0 @Q12 = ABS(q1 - q0) vaddl.u8 q7, d4, d2 @ vaddl.u8 q14, d5, d3 @Q14,Q7 = P0 + Q1 vdup.8 q8, r3 @Q8 contains beta vmlal.u8 q7, d6, d31 @ vmlal.u8 q14, d7, d31 @Q14,Q7 = (X2(p1U) + p0U + q1U) vcge.u8 q9, q11, q10 @Q9 = ( ABS(p0 - q0) >= Alpha ) vcge.u8 q12, q12, q8 @Q12= ( ABS(q1 - q0) >= Beta ) vcge.u8 q13, q13, q8 @Q13= ( ABS(p1 - p0) >= Beta ) vrshrn.u16 d8, q4, #2 @ vrshrn.u16 d9, q5, #2 @Q4 = (X2(q1U) + q0U + p1U + 2) >> 2 vorr q9, q9, q12 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) vrshrn.u16 d10, q7, #2 @ vrshrn.u16 d11, q14, #2 @Q5 = (X2(p1U) + p0U + q1U + 2) >> 2 vorr q9, q9, q13 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) vbit q5, q2, q9 @ vbit q4, q0, q9 @ vst2.8 {d10, d11}, [r4], r1 @ vst2.8 {d8, d9}, [r4] @ vpop {d8 - d15} ldmfd sp!, {r4, pc} @ @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block vertical edge when the @* boundary strength is set to 4 @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_vert_bs4_bp_a9 ih264_deblk_chroma_vert_bs4_bp_a9: stmfd sp!, {r12, r14} vpush {d8 - d15} sub r0, r0, #4 @point r0 to p1u of row0. mov r12, r0 @keep a back up of r0 for buffer write vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 vdup.8 q11, r2 @Q4 = alpha vdup.8 q12, r3 @Q5 = beta vmov.i8 d31, #2 vabd.u8 q4, q1, q2 @|p0-q0| vabd.u8 q5, q3, q2 @|q1-q0| vabd.u8 q6, q0, q1 @|p1-p0| vaddl.u8 q7, d2, d6 vaddl.u8 q8, d3, d7 @(p0 + q1) vclt.u8 q4, q4, q11 @|p0-q0| < alpha ? vclt.u8 q5, q5, q12 @|q1-q0| < beta ? vclt.u8 q6, q6, q12 @|p1-p0| < beta ? vmlal.u8 q7, d0, d31 vmlal.u8 q8, d1, d31 @2*p1 + (p0 + q1) vaddl.u8 q9, d0, d4 vaddl.u8 q10, d1, d5 @(p1 + q0) vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta vmlal.u8 q9, d6, d31 vmlal.u8 q10, d7, d31 @2*q1 + (p1 + q0) vrshrn.i16 d14, q7, #2 vrshrn.i16 d15, q8, #2 @(2*p1 + (p0 + q1) + 2) >> 2 vand.u8 q4, q4, q6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta vrshrn.i16 d18, q9, #2 vrshrn.i16 d19, q10, #2 @(2*q1 + (p1 + q0) + 2) >> 2 vbit q1, q7, q4 vbit q2, q9, q4 vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r12], r1 vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r12], r1 vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r12], r1 vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r12], r1 vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r12], r1 vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r12], r1 vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r12], r1 vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r12], r1 vpop {d8 - d15} ldmfd sp!, {r12, pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block horizontal edge for cases where the @* boundary strength is less than 4 @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @param[in] sp(0) - u4_bs @* Packed Boundary strength array @* @* @param[in] sp(4) - pu1_cliptab @* tc0_table @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_horz_bslt4_bp_a9 ih264_deblk_chroma_horz_bslt4_bp_a9: stmfd sp!, {r4-r6, lr} @ ldrd r4, r5, [sp, #0x10] @r4 = u4_bs , r5 = pu1_cliptab vpush {d8 - d15} sub r0, r0, r1, lsl #1 @R0 = uc_edgePixelU pointing to p2 of chroma U rev r4, r4 @ vmov.32 d12[0], r4 @d12[0] = ui_Bs vld1.32 d16[0], [r5] @D16[0] contains cliptab vld2.8 {d6, d7}, [r0], r1 @Q3=p1 vtbl.8 d14, {d16}, d12 @ vmovl.u8 q6, d12 @q6 = uc_Bs in each 16 bit scalar mov r6, r0 @Keeping a backup of the pointer to chroma U P0 vld2.8 {d4, d5}, [r0], r1 @Q2=p0 vmov.i8 d30, #1 @ vdup.8 q10, r2 @Q10 contains alpha vld2.8 {d0, d1}, [r0], r1 @Q0=q0 vmovl.u8 q7, d14 @ vld2.8 {d2, d3}, [r0] @Q1=q1 vsubl.u8 q5, d1, d5 @ vsubl.u8 q4, d0, d4 @Q5,Q4 = (q0 - p0) vabd.u8 q13, q3, q2 @Q13 = ABS(p1 - p0) vshl.i16 q5, q5, #2 @Q5 = (q0 - p0)<<2 vabd.u8 q11, q2, q0 @Q11 = ABS(p0 - q0) vshl.i16 q4, q4, #2 @Q4 = (q0 - p0)<<2 vsli.16 q7, q7, #8 @ vabd.u8 q12, q1, q0 @Q12 = ABS(q1 - q0) vcge.u8 q9, q11, q10 @Q9 = ( ABS(p0 - q0) >= Alpha ) vsubl.u8 q10, d6, d2 @Q10 = (p1 - q1)L vsubl.u8 q3, d7, d3 @Q3 = (p1 - q1)H vdup.8 q8, r3 @Q8 contains beta vadd.i16 q4, q4, q10 @ vadd.i16 q5, q5, q3 @Q5,Q4 = [ (q0 - p0)<<2 ] + (p1 - q1) vcge.u8 q12, q12, q8 @Q12= ( ABS(q1 - q0) >= Beta ) vcgt.s16 d12, d12, #0 @Q6 = (us_Bs > 0) vqrshrn.s16 d8, q4, #3 @ vqrshrn.s16 d9, q5, #3 @Q4 = i_macro = (((q0 - p0)<<2) + (p1 - q1) + 4)>>3 vadd.i8 d14, d14, d30 @Q7 = C = C0+1 vcge.u8 q13, q13, q8 @Q13= ( ABS(p1 - p0) >= Beta ) vorr q9, q9, q12 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) vabs.s8 q3, q4 @Q4 = ABS (i_macro) vmov.i8 d15, d14 @ vmov.i8 d13, d12 @ vorr q9, q9, q13 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) vmin.u8 q7, q3, q7 @Q7 = delta = (ABS(i_macro) > C) ? C : ABS(i_macro) vbic q6, q6, q9 @final condition vcge.s8 q4, q4, #0 @Q4 = (i_macro >= 0) vand q7, q7, q6 @Making delta zero in places where values shouldn be filterd vqadd.u8 q8, q2, q7 @Q8 = p0 + delta vqsub.u8 q2, q2, q7 @Q2 = p0 - delta vqadd.u8 q9, q0, q7 @Q9 = q0 + delta vqsub.u8 q0, q0, q7 @Q0 = q0 - delta vbif q8, q2, q4 @Q8 = (i_macro >= 0 ) ? (p0+delta) : (p0-delta) vbif q0, q9, q4 @Q0 = (i_macro >= 0 ) ? (q0-delta) : (q0+delta) vst2.8 {d16, d17}, [r6], r1 @ vst2.8 {d0, d1}, [r6] @ vpop {d8 - d15} ldmfd sp!, {r4-r6, pc} @ @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block vertical edge for cases where the @* boundary strength is less than 4 @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @param[in] sp(0) - u4_bs @* Packed Boundary strength array @* @* @param[in] sp(4) - pu1_cliptab @* tc0_table @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_vert_bslt4_bp_a9 ih264_deblk_chroma_vert_bslt4_bp_a9: stmfd sp!, {r10-r12, r14} sub r0, r0, #4 @point r0 to p1u of row0. ldr r11, [sp, #16] @r12 = ui_Bs ldr r10, [sp, #20] @r14 = puc_ClipTab mov r12, r0 @keep a back up of r0 for buffer write vpush {d8 - d15} vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 vdup.8 q11, r2 @Q4 = alpha vabd.u8 q4, q1, q2 @|p0-q0| vdup.8 q12, r3 @Q5 = beta vabd.u8 q5, q3, q2 @|q1-q0| vabd.u8 q6, q0, q1 @|p1-p0| vclt.u8 q4, q4, q11 @|p0-q0| < alpha ? vsubl.u8 q7, d0, d6 vclt.u8 q5, q5, q12 @|q1-q0| < beta ? vsubl.u8 q8, d1, d7 @(p1 - q1) vclt.u8 q6, q6, q12 @|p1-p0| < beta ? vsubl.u8 q9, d4, d2 vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta vsubl.u8 q10, d5, d3 @(q0 - p0) vmov.u16 q14, #4 vld1.32 {d24[0]}, [r10] @Load ClipTable rev r11, r11 @Blocking strengths vand.u8 q4, q4, q6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta vmov.32 d10[0], r11 vmla.s16 q7, q9, q14 vmla.s16 q8, q10, q14 @4*(q0 - p0) + (p1 - q1) vmovl.u8 q5, d10 vsli.u16 d10, d10, #8 vmovl.u16 q5, d10 vsli.u32 q5, q5, #16 vtbl.8 d12, {d24}, d10 vtbl.8 d13, {d24}, d11 @tC0 vmov.u8 q12, #1 vadd.u8 q6, q6, q12 @tC0 + 1 vcge.u8 q5, q5, q12 @u4_bS > 0 ? vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0 @ Q0 - Q3(inputs), @ Q4 (|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0), @ Q6 (tC) vrshr.s16 q7, q7, #3 vrshr.s16 q8, q8, #3 @(((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) vcgt.s16 q9, q7, #0 vcgt.s16 q10, q8, #0 vmovn.i16 d18, q9 vmovn.i16 d19, q10 @Q9 = sign(delta) vabs.s16 q7, q7 vabs.s16 q8, q8 vmovn.u16 d14, q7 vmovn.u16 d15, q8 vmin.u8 q7, q7, q6 @Q7 = |delta| vqadd.u8 q10, q1, q7 @p0+|delta| vqadd.u8 q11, q2, q7 @q0+|delta| vqsub.u8 q12, q1, q7 @p0-|delta| vqsub.u8 q13, q2, q7 @q0-|delta| vbit q12, q10, q9 @p0 + delta vbit q11, q13, q9 @q0 - delta vbit q1, q12, q4 vbit q2, q11, q4 vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r12], r1 vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r12], r1 vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r12], r1 vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r12], r1 vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r12], r1 vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r12], r1 vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r12], r1 vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r12], r1 vpop {d8 - d15} ldmfd sp!, {r10-r12, pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block vertical edge when the @* boundary strength is set to 4 on calling twice @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_vert_bs4_mbaff_bp_a9 ih264_deblk_chroma_vert_bs4_mbaff_bp_a9: stmfd sp!, {r12, r14} vpush {d8 - d15} sub r0, r0, #4 @point r0 to p1u of row0. mov r12, r0 @keep a back up of r0 for buffer write vld4.16 {d0[0], d1[0], d2[0], d3[0]}, [r0], r1 vld4.16 {d0[1], d1[1], d2[1], d3[1]}, [r0], r1 vld4.16 {d0[2], d1[2], d2[2], d3[2]}, [r0], r1 vld4.16 {d0[3], d1[3], d2[3], d3[3]}, [r0], r1 vdup.8 d11, r2 @D11 = alpha vdup.8 d12, r3 @D12 = beta vmov.i8 d31, #2 vabd.u8 d4, d1, d2 @|p0-q0| vabd.u8 d5, d3, d2 @|q1-q0| vabd.u8 d6, d0, d1 @|p1-p0| vaddl.u8 q14, d1, d3 @(p0 + q1) vclt.u8 d4, d4, d11 @|p0-q0| < alpha ? vclt.u8 d5, d5, d12 @|q1-q0| < beta ? vclt.u8 d6, d6, d12 @|p1-p0| < beta ? vmlal.u8 q14, d0, d31 @2*p1 + (p0 + q1) vaddl.u8 q13, d0, d2 @(p1 + q0) vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta vmlal.u8 q13, d3, d31 @2*q1 + (p1 + q0) vrshrn.i16 d7, q14, #2 @(2*p1 + (p0 + q1) + 2) >> 2 vand.u8 d4, d4, d6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta vrshrn.i16 d9, q13, #2 @(2*q1 + (p1 + q0) + 2) >> 2 vbit d1, d7, d4 vbit d2, d9, d4 vst4.16 {d0[0], d1[0], d2[0], d3[0]}, [r12], r1 vst4.16 {d0[1], d1[1], d2[1], d3[1]}, [r12], r1 vst4.16 {d0[2], d1[2], d2[2], d3[2]}, [r12], r1 vst4.16 {d0[3], d1[3], d2[3], d3[3]}, [r12], r1 vpop {d8 - d15} ldmfd sp!, {r12, pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block vertical edge for cases where the @* boundary strength is less than 4 on calling twice @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @param[in] sp(0) - u4_bs @* Packed Boundary strength array @* @* @param[in] sp(4) - pu1_cliptab @* tc0_table @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_vert_bslt4_mbaff_bp_a9 ih264_deblk_chroma_vert_bslt4_mbaff_bp_a9: stmfd sp!, {r10-r12, r14} sub r0, r0, #4 @point r0 to p1u of row0. ldr r11, [sp, #16] @r11 = ui_Bs ldr r10, [sp, #20] @r10 = puc_ClipTab mov r12, r0 @keep a back up of r0 for buffer write vpush {d8 - d15} vld4.16 {d0[0], d1[0], d2[0], d3[0]}, [r0], r1 vld4.16 {d0[1], d1[1], d2[1], d3[1]}, [r0], r1 vld4.16 {d0[2], d1[2], d2[2], d3[2]}, [r0], r1 vld4.16 {d0[3], d1[3], d2[3], d3[3]}, [r0], r1 vdup.8 d11, r2 @D11 = alpha vabd.u8 d4, d1, d2 @|p0-q0| vdup.8 d12, r3 @D12 = beta vabd.u8 d5, d3, d2 @|q1-q0| vabd.u8 d6, d0, d1 @|p1-p0| vclt.u8 d4, d4, d11 @|p0-q0| < alpha ? vclt.u8 d5, d5, d12 @|q1-q0| < beta ? vsubl.u8 q14, d0, d3 @(p1 - q1) vclt.u8 d6, d6, d12 @|p1-p0| < beta ? vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta vsubl.u8 q12, d2, d1 @(q0 - p0) vmov.u16 q10, #4 vld1.32 {d31[0]}, [r10] @Load ClipTable rev r11, r11 @Blocking strengths vand.u8 d4, d4, d6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta vmov.32 d22[0], r11 vmla.s16 q14, q12, q10 @4*(q0 - p0) + (p1 - q1) vmovl.u8 q11, d22 vsli.u16 d22, d22, #8 vtbl.8 d6, {d31}, d22 @tC0 vmov.u8 d12, #1 vadd.u8 d6, d6, d12 @tC0 + 1 vcge.u8 d5, d22, d12 @u4_bS > 0 ? vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0 @ D0 - D3(inputs), @ D4 (|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0), @ D6 (tC) vrshr.s16 q14, q14, #3 @(((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) vcgt.s16 q13, q14, #0 vmovn.i16 d9, q13 @D9 = sign(delta) vabs.s16 q14, q14 vmovn.u16 d7, q14 vmin.u8 d7, d7, d6 @D7 = |delta| vqadd.u8 d10, d1, d7 @p0+|delta| vqadd.u8 d11, d2, d7 @q0+|delta| vqsub.u8 d12, d1, d7 @p0-|delta| vqsub.u8 d13, d2, d7 @q0-|delta| vbit d12, d10, d9 @p0 + delta vbit d11, d13, d9 @q0 - delta vbit d1, d12, d4 vbit d2, d11, d4 vst4.16 {d0[0], d1[0], d2[0], d3[0]}, [r12], r1 vst4.16 {d0[1], d1[1], d2[1], d3[1]}, [r12], r1 vst4.16 {d0[2], d1[2], d2[2], d3[2]}, [r12], r1 vst4.16 {d0[3], d1[3], d2[3], d3[3]}, [r12], r1 vpop {d8 - d15} ldmfd sp!, {r10-r12, pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block horizontal edge when the @* boundary strength is set to 4 in high profile @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha_cb @* Alpha Value for the boundary in U @* @* @param[in] r3 - beta_cb @* Beta Value for the boundary in U @* @* @param[in] sp(0) - alpha_cr @* Alpha Value for the boundary in V @* @* @param[in] sp(4) - beta_cr @* Beta Value for the boundary in V @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_horz_bs4_a9 ih264_deblk_chroma_horz_bs4_a9: stmfd sp!, {r4-r6, lr} @ ldr r5, [sp, #16] @R5 = alpha_cr ldr r6, [sp, #20] @R6 = beta_cr vpush {d8 - d15} sub r0, r0, r1, lsl #1 @R0 = uc_edgePixel pointing to p1 of chroma vld2.8 {d6, d7}, [r0], r1 @D6 = p1u , D7 = p1v mov r4, r0 @Keeping a backup of the pointer p0 of chroma vld2.8 {d4, d5}, [r0], r1 @D4 = p0u , D5 = p0v vdup.8 d20, r2 @D20 contains alpha_cb vdup.8 d21, r5 @D21 contains alpha_cr vld2.8 {d0, d1}, [r0], r1 @D0 = q0u , D1 = q0v vaddl.u8 q4, d6, d0 @ vaddl.u8 q5, d7, d1 @Q4,Q5 = q0 + p1 vmov.i8 d31, #2 @ vld2.8 {d2, d3}, [r0] @D2 = q1u , D3 = q1v vabd.u8 q13, q3, q2 @Q13 = ABS(p1 - p0) vmlal.u8 q4, d2, d31 @ vmlal.u8 q5, d3, d31 @Q5,Q4 = (X2(q1U) + q0U + p1U) vabd.u8 q11, q2, q0 @Q11 = ABS(p0 - q0) vabd.u8 q12, q1, q0 @Q12 = ABS(q1 - q0) vaddl.u8 q7, d4, d2 @ vaddl.u8 q14, d5, d3 @Q14,Q7 = P0 + Q1 vdup.8 d16, r3 @D16 contains beta_cb vdup.8 d17, r6 @D17 contains beta_cr vmlal.u8 q7, d6, d31 @ vmlal.u8 q14, d7, d31 @Q14,Q7 = (X2(p1U) + p0U + q1U) vcge.u8 q9, q11, q10 @Q9 = ( ABS(p0 - q0) >= Alpha ) vcge.u8 q12, q12, q8 @Q12= ( ABS(q1 - q0) >= Beta ) vcge.u8 q13, q13, q8 @Q13= ( ABS(p1 - p0) >= Beta ) vrshrn.u16 d8, q4, #2 @ vrshrn.u16 d9, q5, #2 @Q4 = (X2(q1U) + q0U + p1U + 2) >> 2 vorr q9, q9, q12 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) vrshrn.u16 d10, q7, #2 @ vrshrn.u16 d11, q14, #2 @Q5 = (X2(p1U) + p0U + q1U + 2) >> 2 vorr q9, q9, q13 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) vbit q5, q2, q9 @ vbit q4, q0, q9 @ vst2.8 {d10, d11}, [r4], r1 @ vst2.8 {d8, d9}, [r4] @ vpop {d8 - d15} ldmfd sp!, {r4-r6, pc} @ @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block vertical edge when the @* boundary strength is set to 4 in high profile @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha_cb @* Alpha Value for the boundary in U @* @* @param[in] r3 - beta_cb @* Beta Value for the boundary in U @* @* @param[in] sp(0) - alpha_cr @* Alpha Value for the boundary in V @* @* @param[in] sp(4) - beta_cr @* Beta Value for the boundary in V @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_vert_bs4_a9 ih264_deblk_chroma_vert_bs4_a9: stmfd sp!, {r4, r5, r12, r14} sub r0, r0, #4 @point r0 to p1u of row0. mov r12, r0 @keep a back up of r0 for buffer write ldr r4, [sp, #16] @r4 = alpha_cr ldr r5, [sp, #20] @r5 = beta_cr add r2, r2, r4, lsl #8 @r2 = (alpha_cr,alpha_cb) add r3, r3, r5, lsl #8 @r3 = (beta_cr,beta_cb) vpush {d8 - d15} vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 vdup.16 q11, r2 @Q11 = alpha vdup.16 q12, r3 @Q12 = beta vmov.i8 d31, #2 vabd.u8 q4, q1, q2 @|p0-q0| vabd.u8 q5, q3, q2 @|q1-q0| vabd.u8 q6, q0, q1 @|p1-p0| vaddl.u8 q7, d2, d6 vaddl.u8 q8, d3, d7 @(p0 + q1) vclt.u8 q4, q4, q11 @|p0-q0| < alpha ? vclt.u8 q5, q5, q12 @|q1-q0| < beta ? vclt.u8 q6, q6, q12 @|p1-p0| < beta ? vmlal.u8 q7, d0, d31 vmlal.u8 q8, d1, d31 @2*p1 + (p0 + q1) vaddl.u8 q9, d0, d4 vaddl.u8 q10, d1, d5 @(p1 + q0) vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta vmlal.u8 q9, d6, d31 vmlal.u8 q10, d7, d31 @2*q1 + (p1 + q0) vrshrn.i16 d14, q7, #2 vrshrn.i16 d15, q8, #2 @(2*p1 + (p0 + q1) + 2) >> 2 vand.u8 q4, q4, q6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta vrshrn.i16 d18, q9, #2 vrshrn.i16 d19, q10, #2 @(2*q1 + (p1 + q0) + 2) >> 2 vbit q1, q7, q4 vbit q2, q9, q4 vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r12], r1 vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r12], r1 vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r12], r1 vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r12], r1 vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r12], r1 vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r12], r1 vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r12], r1 vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r12], r1 vpop {d8 - d15} ldmfd sp!, {r4, r5, r12, pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block horizontal edge for cases where the @* boundary strength is less than 4 in high profile @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha_cb @* Alpha Value for the boundary in U @* @* @param[in] r3 - beta_cb @* Beta Value for the boundary in U @* @* @param[in] sp(0) - alpha_cr @* Alpha Value for the boundary in V @* @* @param[in] sp(4) - beta_cr @* Beta Value for the boundary in V @* @* @param[in] sp(8) - u4_bs @* Packed Boundary strength array @* @* @param[in] sp(12) - pu1_cliptab_cb @* tc0_table for U @* @* @param[in] sp(16) - pu1_cliptab_cr @* tc0_table for V @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_horz_bslt4_a9 ih264_deblk_chroma_horz_bslt4_a9: stmfd sp!, {r4-r9, lr} @ ldrd r4, r5, [sp, #28] @R4 = alpha_cr , R5 = beta_cr ldr r7, [sp, #36] @R7 = u4_bs ldrd r8, r9, [sp, #40] @R8 = pu1_cliptab_cb , R9 = pu1_cliptab_cr sub r0, r0, r1, lsl #1 @R0 = uc_edgePixelU pointing to p1 of chroma U vpush {d8 - d15} rev r7, r7 @ vmov.32 d12[0], r7 @D12[0] = ui_Bs vld1.32 d16[0], [r8] @D16[0] contains cliptab_cb vld1.32 d17[0], [r9] @D17[0] contains cliptab_cr vld2.8 {d6, d7}, [r0], r1 @Q3=p1 vtbl.8 d14, {d16}, d12 @Retreiving cliptab values for U vtbl.8 d28, {d17}, d12 @Retrieving cliptab values for V vmovl.u8 q6, d12 @Q6 = uc_Bs in each 16 bit scalar mov r6, r0 @Keeping a backup of the pointer to chroma U P0 vld2.8 {d4, d5}, [r0], r1 @Q2=p0 vmov.i8 d30, #1 @ vdup.8 d20, r2 @D20 contains alpha_cb vdup.8 d21, r4 @D21 contains alpha_cr vld2.8 {d0, d1}, [r0], r1 @Q0=q0 vmovl.u8 q7, d14 @ vmovl.u8 q14, d28 @ vmov.i16 d15, d28 @D14 has cliptab values for U, D15 for V vld2.8 {d2, d3}, [r0] @Q1=q1 vsubl.u8 q5, d1, d5 @ vsubl.u8 q4, d0, d4 @Q5,Q4 = (q0 - p0) vabd.u8 q13, q3, q2 @Q13 = ABS(p1 - p0) vshl.i16 q5, q5, #2 @Q5 = (q0 - p0)<<2 vabd.u8 q11, q2, q0 @Q11 = ABS(p0 - q0) vshl.i16 q4, q4, #2 @Q4 = (q0 - p0)<<2 vsli.16 q7, q7, #8 @ vabd.u8 q12, q1, q0 @Q12 = ABS(q1 - q0) vcge.u8 q9, q11, q10 @Q9 = ( ABS(p0 - q0) >= Alpha ) vsubl.u8 q10, d6, d2 @Q10 = (p1 - q1)L vsubl.u8 q3, d7, d3 @Q3 = (p1 - q1)H vdup.8 d16, r3 @Q8 contains beta_cb vdup.8 d17, r5 @Q8 contains beta_cr vadd.i16 q4, q4, q10 @ vadd.i16 q5, q5, q3 @Q5,Q4 = [ (q0 - p0)<<2 ] + (p1 - q1) vcge.u8 q12, q12, q8 @Q12= ( ABS(q1 - q0) >= Beta ) vcgt.s16 d12, d12, #0 @Q6 = (us_Bs > 0) vqrshrn.s16 d8, q4, #3 @ vqrshrn.s16 d9, q5, #3 @Q4 = i_macro = (((q0 - p0)<<2) + (p1 - q1) + 4)>>3 vadd.i8 d14, d14, d30 @D14 = C = C0+1 for U vcge.u8 q13, q13, q8 @Q13= ( ABS(p1 - p0) >= Beta ) vorr q9, q9, q12 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) vabs.s8 q3, q4 @Q4 = ABS (i_macro) vadd.i8 d15, d15, d30 @D15 = C = C0+1 for V vmov.i8 d13, d12 @ vorr q9, q9, q13 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) vmin.u8 q7, q3, q7 @Q7 = delta = (ABS(i_macro) > C) ? C : ABS(i_macro) vbic q6, q6, q9 @final condition vcge.s8 q4, q4, #0 @Q4 = (i_macro >= 0) vand q7, q7, q6 @Making delta zero in places where values shouldn be filterd vqadd.u8 q8, q2, q7 @Q8 = p0 + delta vqsub.u8 q2, q2, q7 @Q2 = p0 - delta vqadd.u8 q9, q0, q7 @Q9 = q0 + delta vqsub.u8 q0, q0, q7 @Q0 = q0 - delta vbif q8, q2, q4 @Q8 = (i_macro >= 0 ) ? (p0+delta) : (p0-delta) vbif q0, q9, q4 @Q0 = (i_macro >= 0 ) ? (q0-delta) : (q0+delta) vst2.8 {d16, d17}, [r6], r1 @ vst2.8 {d0, d1}, [r6] @ vpop {d8 - d15} ldmfd sp!, {r4-r9, pc} @ @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block vertical edge for cases where the @* boundary strength is less than 4 in high profile @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha_cb @* Alpha Value for the boundary in U @* @* @param[in] r3 - beta_cb @* Beta Value for the boundary in U @* @* @param[in] sp(0) - alpha_cr @* Alpha Value for the boundary in V @* @* @param[in] sp(4) - beta_cr @* Beta Value for the boundary in V @* @* @param[in] sp(8) - u4_bs @* Packed Boundary strength array @* @* @param[in] sp(12) - pu1_cliptab_cb @* tc0_table for U @* @* @param[in] sp(16) - pu1_cliptab_cr @* tc0_table for V @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_vert_bslt4_a9 ih264_deblk_chroma_vert_bslt4_a9: stmfd sp!, {r4-r7, r10-r12, r14} sub r0, r0, #4 @point r0 to p1u of row0. ldrd r4, r5, [sp, #32] @R4 = alpha_cr , R5 = beta_cr add r2, r2, r4, lsl #8 add r3, r3, r5, lsl #8 ldr r6, [sp, #40] @R6 = u4_bs ldrd r10, r11, [sp, #44] @R10 = pu1_cliptab_cb , R11 = pu1_cliptab_cr vpush {d8 - d15} mov r12, r0 @keep a back up of R0 for buffer write vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 vdup.16 q11, r2 @Q11 = alpha vabd.u8 q4, q1, q2 @|p0-q0| vdup.16 q12, r3 @Q12 = beta vabd.u8 q5, q3, q2 @|q1-q0| vabd.u8 q6, q0, q1 @|p1-p0| vclt.u8 q4, q4, q11 @|p0-q0| < alpha ? vsubl.u8 q7, d0, d6 vclt.u8 q5, q5, q12 @|q1-q0| < beta ? vsubl.u8 q8, d1, d7 @(p1 - q1) vclt.u8 q6, q6, q12 @|p1-p0| < beta ? vsubl.u8 q9, d4, d2 vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta vsubl.u8 q10, d5, d3 @(q0 - p0) vmov.u16 q14, #4 vld1.32 {d24[0]}, [r10] @Load ClipTable for U vld1.32 {d25[0]}, [r11] @Load ClipTable for V rev r6, r6 @Blocking strengths vand.u8 q4, q4, q6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta vmov.32 d10[0], r6 vmla.s16 q7, q9, q14 vmla.s16 q8, q10, q14 @4*(q0 - p0) + (p1 - q1) vmovl.u8 q5, d10 vsli.u16 d10, d10, #8 vtbl.8 d12, {d24}, d10 @tC0 for U vtbl.8 d13, {d25}, d10 @tC0 for V vzip.8 d12, d13 vmovl.u16 q5, d10 vsli.u32 q5, q5, #16 vmov.u8 q12, #1 vadd.u8 q6, q6, q12 @tC0 + 1 vcge.u8 q5, q5, q12 @u4_bS > 0 ? vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0 @ Q0 - Q3(inputs), @ Q4 (|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0), @ Q6 (tC) vrshr.s16 q7, q7, #3 vrshr.s16 q8, q8, #3 @(((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) vcgt.s16 q9, q7, #0 vcgt.s16 q10, q8, #0 vmovn.i16 d18, q9 vmovn.i16 d19, q10 @Q9 = sign(delta) vabs.s16 q7, q7 vabs.s16 q8, q8 vmovn.u16 d14, q7 vmovn.u16 d15, q8 vmin.u8 q7, q7, q6 @Q7 = |delta| vqadd.u8 q10, q1, q7 @p0+|delta| vqadd.u8 q11, q2, q7 @q0+|delta| vqsub.u8 q12, q1, q7 @p0-|delta| vqsub.u8 q13, q2, q7 @q0-|delta| vbit q12, q10, q9 @p0 + delta vbit q11, q13, q9 @q0 - delta vbit q1, q12, q4 vbit q2, q11, q4 vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r12], r1 vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r12], r1 vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r12], r1 vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r12], r1 vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r12], r1 vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r12], r1 vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r12], r1 vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r12], r1 vpop {d8 - d15} ldmfd sp!, {r4-r7, r10-r12, pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block vertical edge when the @* boundary strength is set to 4 on calling twice in high profile @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha_cb @* Alpha Value for the boundary in U @* @* @param[in] r3 - beta_cb @* Beta Value for the boundary in U @* @* @param[in] sp(0) - alpha_cr @* Alpha Value for the boundary in V @* @* @param[in] sp(4) - beta_cr @* Beta Value for the boundary in V @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_vert_bs4_mbaff_a9 ih264_deblk_chroma_vert_bs4_mbaff_a9: stmfd sp!, {r4, r5, r12, r14} sub r0, r0, #4 @point r0 to p1u of row0. mov r12, r0 @keep a back up of r0 for buffer write ldrd r4, r5, [sp, #16] @R4 = alpha_cr , R5 = beta_cr add r2, r2, r4, lsl #8 add r3, r3, r5, lsl #8 vpush {d8 - d15} vld4.16 {d0[0], d1[0], d2[0], d3[0]}, [r0], r1 vld4.16 {d0[1], d1[1], d2[1], d3[1]}, [r0], r1 vld4.16 {d0[2], d1[2], d2[2], d3[2]}, [r0], r1 vld4.16 {d0[3], d1[3], d2[3], d3[3]}, [r0], r1 vdup.16 d11, r2 @D11 = alpha vdup.16 d12, r3 @D12 = beta vmov.i8 d31, #2 vabd.u8 d4, d1, d2 @|p0-q0| vabd.u8 d5, d3, d2 @|q1-q0| vabd.u8 d6, d0, d1 @|p1-p0| vaddl.u8 q14, d1, d3 @(p0 + q1) vclt.u8 d4, d4, d11 @|p0-q0| < alpha ? vclt.u8 d5, d5, d12 @|q1-q0| < beta ? vclt.u8 d6, d6, d12 @|p1-p0| < beta ? vmlal.u8 q14, d0, d31 @2*p1 + (p0 + q1) vaddl.u8 q13, d0, d2 @(p1 + q0) vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta vmlal.u8 q13, d3, d31 @2*q1 + (p1 + q0) vrshrn.i16 d7, q14, #2 @(2*p1 + (p0 + q1) + 2) >> 2 vand.u8 d4, d4, d6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta vrshrn.i16 d9, q13, #2 @(2*q1 + (p1 + q0) + 2) >> 2 vbit d1, d7, d4 vbit d2, d9, d4 vst4.16 {d0[0], d1[0], d2[0], d3[0]}, [r12], r1 vst4.16 {d0[1], d1[1], d2[1], d3[1]}, [r12], r1 vst4.16 {d0[2], d1[2], d2[2], d3[2]}, [r12], r1 vst4.16 {d0[3], d1[3], d2[3], d3[3]}, [r12], r1 vpop {d8 - d15} ldmfd sp!, {r4, r5, r12, pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a chroma block vertical edge for cases where the @* boundary strength is less than 4 on calling twice in high profile @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha_cb @* Alpha Value for the boundary in U @* @* @param[in] r3 - beta_cb @* Beta Value for the boundary in U @* @* @param[in] sp(0) - alpha_cr @* Alpha Value for the boundary in V @* @* @param[in] sp(4) - beta_cr @* Beta Value for the boundary in V @* @* @param[in] sp(8) - u4_bs @* Packed Boundary strength array @* @* @param[in] sp(12) - pu1_cliptab_cb @* tc0_table for U @* @* @param[in] sp(16) - pu1_cliptab_cr @* tc0_table for V @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_chroma_vert_bslt4_mbaff_a9 ih264_deblk_chroma_vert_bslt4_mbaff_a9: stmfd sp!, {r4-r6, r10-r12, r14} sub r0, r0, #4 @point r0 to p1u of row0. mov r12, r0 @keep a back up of r0 for buffer write ldrd r4, r5, [sp, #28] @R4 = alpha_cr , R5 = beta_cr add r2, r2, r4, lsl #8 add r3, r3, r5, lsl #8 ldr r6, [sp, #36] @R6 = u4_bs ldrd r10, r11, [sp, #40] @R10 = pu1_cliptab_cb , R11 = pu1_cliptab_cr vpush {d8 - d15} vld4.16 {d0[0], d1[0], d2[0], d3[0]}, [r0], r1 vld4.16 {d0[1], d1[1], d2[1], d3[1]}, [r0], r1 vld4.16 {d0[2], d1[2], d2[2], d3[2]}, [r0], r1 vld4.16 {d0[3], d1[3], d2[3], d3[3]}, [r0], r1 vdup.16 d11, r2 @D11 = alpha vabd.u8 d4, d1, d2 @|p0-q0| vdup.16 d12, r3 @D12 = beta vabd.u8 d5, d3, d2 @|q1-q0| vabd.u8 d6, d0, d1 @|p1-p0| vclt.u8 d4, d4, d11 @|p0-q0| < alpha ? vclt.u8 d5, d5, d12 @|q1-q0| < beta ? vsubl.u8 q14, d0, d3 @(p1 - q1) vclt.u8 d6, d6, d12 @|p1-p0| < beta ? vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta vsubl.u8 q12, d2, d1 @(q0 - p0) vmov.u16 q10, #4 vld1.32 {d31[1]}, [r10] @Load ClipTable for U vld1.32 {d31[0]}, [r11] @Load ClipTable for V rev r6, r6 @Blocking strengths vand.u8 d4, d4, d6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta vmov.32 d22[0], r6 vmla.s16 q14, q12, q10 @4*(q0 - p0) + (p1 - q1) vmovl.u8 q11, d22 vsli.u16 d22, d22, #8 vmov.u16 d13, #4 vadd.u8 d22, d22, d13 vtbl.8 d6, {d31}, d22 @tC0 vmov.u8 d12, #1 vsub.u8 d22, d22, d13 vadd.u8 d6, d6, d12 @tC0 + 1 vcge.u8 d5, d22, d12 @u4_bS > 0 ? vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0 @ D0 - D3(inputs), @ D4 (|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0), @ D6 (tC) vrshr.s16 q14, q14, #3 @(((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) vcgt.s16 q13, q14, #0 vmovn.i16 d9, q13 @D9 = sign(delta) vabs.s16 q14, q14 vmovn.u16 d7, q14 vmin.u8 d7, d7, d6 @D7 = |delta| vqadd.u8 d10, d1, d7 @p0+|delta| vqadd.u8 d11, d2, d7 @q0+|delta| vqsub.u8 d12, d1, d7 @p0-|delta| vqsub.u8 d13, d2, d7 @q0-|delta| vbit d12, d10, d9 @p0 + delta vbit d11, d13, d9 @q0 - delta vbit d1, d12, d4 vbit d2, d11, d4 vst4.16 {d0[0], d1[0], d2[0], d3[0]}, [r12], r1 vst4.16 {d0[1], d1[1], d2[1], d3[1]}, [r12], r1 vst4.16 {d0[2], d1[2], d2[2], d3[2]}, [r12], r1 vst4.16 {d0[3], d1[3], d2[3], d3[3]}, [r12], r1 vpop {d8 - d15} ldmfd sp!, {r4-r6, r10-r12, pc} ================================================ FILE: dependencies/ih264d/common/arm/ih264_deblk_luma_a9.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @/*****************************************************************************/ @/* */ @/* File Name : ih264_deblk_luma_a9.s */ @/* */ @/* Description : Contains function definitions for deblocking luma */ @/* edge. Functions are coded in NEON assembly and can */ @/* be compiled using ARM RVDS. */ @/* */ @/* List of Functions : ih264_deblk_luma_vert_bs4_a9() */ @/* ih264_deblk_luma_vert_bslt4_a9() */ @/* ih264_deblk_luma_horz_bs4_a9() */ @/* ih264_deblk_luma_horz_bslt4_a9() */ @/* ih264_deblk_luma_vert_bs4_mbaff_a9() */ @/* ih264_deblk_luma_vert_bslt4_mbaff_a9() */ @/* */ @/* Issues / Problems : None */ @/* */ @/* Revision History : */ @/* */ @/* DD MM YYYY Author(s) Changes (Describe the changes made) */ @/* 28 11 2013 Ittiam Draft */ @/* 05 01 2015 Kaushik Added double-call functions for */ @/* Senthoor vertical deblocking. */ @/* */ @/*****************************************************************************/ .text .p2align 2 @** @******************************************************************************* @* @* @brief @* Performs filtering of a luma block horizontal edge for cases where the @* boundary strength is less than 4 @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @param[in] sp(0) - u4_bs @* Packed Boundary strength array @* @* @param[in] sp(4) - pu1_cliptab @* tc0_table @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_luma_horz_bslt4_a9 ih264_deblk_luma_horz_bslt4_a9: stmfd sp!, {r4-r7, lr} ldrd r4, r5, [sp, #0x14] @r4 = ui_Bs , r5 = *puc_ClpTab vpush {d8 - d15} sub r0, r0, r1, lsl #1 @R1 = uc_Horizonpad sub r0, r0, r1 @r0 pointer to p2 rev r4, r4 @ vld1.8 {q5}, [r0], r1 @p2 values are loaded into q5 vmov.32 d12[0], r4 @d12[0] = ui_Bs mov r6, r0 @keeping backup of pointer to p1 vld1.8 {q4}, [r0], r1 @p1 values are loaded into q4 mov r7, r0 @keeping backup of pointer to p0 vld1.8 {q3}, [r0], r1 @p0 values are loaded into q3 vmovl.u8 q6, d12 @q6 = uc_Bs in each 16 bt scalar vld1.8 {q0}, [r0], r1 @q0 values are loaded into q0 vabd.u8 q13, q4, q3 @Q13 = ABS(p1 - p0) vld1.8 {q1}, [r0], r1 @q1 values are loaded into q1 vabd.u8 q11, q3, q0 @Q11 = ABS(p0 - q0) vld1.32 d16[0], [r5] @D16[0] contains cliptab vabd.u8 q12, q1, q0 @Q12 = ABS(q1 - q0) vld1.8 {q2}, [r0], r1 @q2 values are loaded into q2 vtbl.8 d14, {d16}, d12 @ vdup.8 q10, r2 @Q10 contains alpha vdup.8 q8, r3 @Q8 contains beta vmovl.u16 q6, d12 @ vmovl.u16 q7, d14 @ vabd.u8 q14, q5, q3 @Q14 = Ap = ABS(p2 - p0) vabd.u8 q15, q2, q0 @Q15 = Aq = ABS(q2 - q0) vcgt.s32 q6, q6, #0 @Q6 = (us_Bs > 0) vsli.32 q7, q7, #8 @ vcge.u8 q9, q11, q10 @Q9 = ( ABS(p0 - q0) >= Alpha ) vcge.u8 q12, q12, q8 @Q12=( ABS(q1 - q0) >= Beta ) vcge.u8 q13, q13, q8 @Q13=( ABS(p1 - p0) >= Beta ) vcgt.u8 q10, q8, q14 @Q10=(Ap= Alpha ) | ( ABS(q1 - q0) >= Beta ) vsubl.u8 q15, d1, d7 @ vsubl.u8 q12, d0, d6 @Q15,Q12 = (q0 - p0) vorr q9, q9, q13 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) vsubl.u8 q14, d8, d2 @Q14 = (p1 - q1)L vshl.i16 q13, q15, #2 @Q13 = (q0 - p0)<<2 vshl.i16 q12, q12, #2 @Q12 = (q0 - p0)<<2 vsubl.u8 q15, d9, d3 @Q15 = (p1 - q1)H vbic q6, q6, q9 @final condition vadd.i16 q12, q12, q14 @ vadd.i16 q13, q13, q15 @Q13,Q12 = [ (q0 - p0)<<2 ] + (p1 - q1) vsub.i8 q9, q7, q10 @Q9 = C0 + (Ap < Beta) vrhadd.u8 q8, q3, q0 @Q8 = ((p0+q0+1) >> 1) vqrshrn.s16 d24, q12, #3 @ vqrshrn.s16 d25, q13, #3 @Q12 = i_macro = (((q0 - p0)<<2) + (p1 - q1) + 4)>>3 vsub.i8 q9, q9, q11 @Q9 = C0 + (Ap < Beta) + (Aq < Beta) vand.i8 q10, q10, q6 @ vand.i8 q11, q11, q6 @ vabs.s8 q13, q12 @Q13 = ABS (i_macro) vaddl.u8 q14, d17, d11 @ vaddl.u8 q5, d16, d10 @Q14,Q5 = p2 + (p0+q0+1)>>1 vaddl.u8 q15, d17, d5 @ vmin.u8 q9, q13, q9 @Q9 = delta = (ABS(i_macro) > C) ? C : ABS(i_macro) vshll.u8 q13, d9, #1 @ vaddl.u8 q2, d16, d4 @Q15,Q2 = q2 + (p0+q0+1)>>1 vshll.u8 q8, d8, #1 @Q13,Q8 = (p1<<1) vand q9, q9, q6 @Making delta zero in places where values shouldn be filterd vsub.i16 q14, q14, q13 @Q14,Q5 = [p2 + (p0+q0+1)>>1] - (p1<<1) vsub.i16 q5, q5, q8 @ vshll.u8 q8, d2, #1 @ vshll.u8 q13, d3, #1 @Q13,Q8 = (q1<<1) vqshrn.s16 d29, q14, #1 @ vqshrn.s16 d28, q5, #1 @Q14 = i_macro_p1 vsub.i16 q2, q2, q8 @ vsub.i16 q15, q15, q13 @Q15,Q2 = [q2 + (p0+q0+1)>>1] - (q1<<1) vneg.s8 q13, q7 @Q13 = -C0 vmin.s8 q14, q14, q7 @Q14 = min(C0,i_macro_p1) vcge.s8 q12, q12, #0 @Q12 = (i_macro >= 0) vqshrn.s16 d31, q15, #1 @ vqshrn.s16 d30, q2, #1 @Q15 = i_macro_q1 vmax.s8 q14, q14, q13 @Q14 = max( - C0 , min(C0, i_macro_p1) ) vqadd.u8 q8, q3, q9 @Q8 = p0 + delta vqsub.u8 q3, q3, q9 @Q3 = p0 - delta vmin.s8 q15, q15, q7 @Q15 = min(C0,i_macro_q1) vand.i8 q14, q10, q14 @condition check Ap= 0 ) ? (p0+delta) : (p0-delta) vbif q0, q7, q12 @Q0 = (i_macro >= 0 ) ? (q0-delta) : (q0+delta) vadd.i8 q14, q14, q4 @ vand.i8 q15, q11, q15 @condition check Aq= Alpha vcge.u8 q7, q7, q1 @ABS(q1 - q0) >= Beta vcge.u8 q8, q8, q1 @ABS(p1 - p0) >= Beta vmov.i8 q10, #2 vorr q9, q9, q7 @ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta vld1.8 {d14, d15}, [r0], r1 @load q2 to Q7, q0 = q0 + src_strd vorr q9, q9, q8 @ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta vsra.u8 q10, q0, #2 @((Alpha >> 2) + 2) vabd.u8 q11, q7, q2 @Aq = ABS(q2 - q0) vaddl.u8 q12, d4, d6 @p0+q0 L vaddl.u8 q13, d5, d7 @p0+q0 H vclt.u8 q11, q11, q1 @Aq < Beta vclt.u8 q10, q6, q10 @(ABS(p0 - q0) <((Alpha >>2) + 2)) @ Deblock Filtering q0', q1', q2' vaddw.u8 q14, q12, d8 @p0+q0+q1 L vaddw.u8 q15, q13, d9 @p0+q0+q1 H vand q11, q11, q10 @(Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) @ q0' if (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) TRUE vadd.i16 q8, q14, q14 @2*(p0+q0+q1)L vadd.i16 q0, q15, q15 @2*(p0+q0+q1)H vaddw.u8 q8, q8, d14 @2*(p0+q0+q1)+q2 L vaddw.u8 q0, q0, d15 @2*(p0+q0+q1)+q2 H vaddw.u8 q8, q8, d10 @2*(p0+q0+q1)+q2 +p1 L vaddw.u8 q0, q0, d11 @2*(p0+q0+q1)+q2 +p1 H vrshrn.u16 d12, q8, #3 @(2*(p0+q0+q1)+q2 +p1 +4)>> 3 L [q0'] vrshrn.u16 d13, q0, #3 @(2*(p0+q0+q1)+q2 +p1 +4)>> 3 H [q0'] @ q0" if (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) FALSE vaddl.u8 q8, d8, d8 @2*q1 L vaddl.u8 q0, d9, d9 @2*q1 H vaddw.u8 q8, q8, d4 @2*q1+q0 L vaddw.u8 q0, q0, d5 @2*q1+q0 H vaddw.u8 q8, q8, d10 @2*q1+q0+p1 L vaddw.u8 q0, q0, d11 @2*q1+q0+p1 H vrshrn.u16 d16, q8, #2 @(2*q1+q0+p1+2)>>2 L [q0"] vrshrn.u16 d17, q0, #2 @(2*q1+q0+p1+2)>>2 H [q0"] @ q1' vaddw.u8 q14, q14, d14 @p0+q0+q1+q2 L vaddw.u8 q15, q15, d15 @p0+q0+q1+q2 H vld1.8 {q0}, [r0], r1 @load q3 to Q0, q0 = q0 + src_strd vbit q8, q6, q11 @choosing between q0' and q0" depending on condn sub r0, r0, r1, lsl #2 @pointer to q0 vbic q11, q11, q9 @((ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) @ && (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) vrshrn.u16 d12, q14, #2 @(p0+q0+q1+q2+2)>>2 L [q1'] vrshrn.u16 d13, q15, #2 @(p0+q0+q1+q2+2)>>2 H [q1'] vbif q2, q8, q9 @choose q0 or filtered q0 @ q2' vaddl.u8 q8, d14, d0 @q2+q3,L vaddl.u8 q0, d15, d1 @q2+q3,H vadd.i16 q14, q14, q8 @p0+q0+q1+2*q2+q3 L vst1.8 {d4, d5}, [r0], r1 @store q0 vadd.i16 q15, q15, q0 @p0+q0+q1+2*q2+q3 H vadd.i16 q14, q14, q8 @p0+q0+q1+3*q2+2*q3 L vadd.i16 q15, q15, q0 @p0+q0+q1+3*q2+2*q3 H vrshrn.u16 d0, q14, #3 @(p0+q0+q1+3*q2+2*q3+4)>>3 L [q2'] vrshrn.u16 d1, q15, #3 @(p0+q0+q1+3*q2+2*q3+4)>>3 H [q2'] vld1.8 {d30, d31}, [r3] @load p2 to Q15 vbif q6, q4, q11 @choose q1 or filtered value of q1 vabd.u8 q8, q15, q3 @Ap,ABS(p2 - p0) vaddw.u8 q12, q12, d10 @p0+q0+p1 L vbif q0, q7, q11 @choose q2 or filtered q2 vaddw.u8 q13, q13, d11 @p0+q0+p1 H vst1.8 {d12, d13}, [r0], r1 @store q1 vclt.u8 q8, q8, q1 @Ap < Beta vadd.i16 q14, q12, q12 @2*(p0+q0+p1) L vadd.i16 q2, q13, q13 @2*(p0+q0+p1) H vst1.8 {d0, d1}, [r0], r1 @store q2 vand q10, q10, q8 @((Ap < Beta) && (ABS(p0 - q0) <((Alpha >>2) + 2))) vaddw.u8 q14, q14, d30 @2*(p0+q0+p1)+p2 l vaddw.u8 q2, q2, d31 @2*(p0+q0+p1)+p2 H vaddw.u8 q14, q14, d8 @2*(p0+q0+p1)+p2+q1 L vaddw.u8 q2, q2, d9 @2*(p0+q0+p1)+p2+q1 H vrshrn.u16 d28, q14, #3 @(2*(p0+q0+p1)+p2+q1+4)>>3 L,p0' vrshrn.u16 d29, q2, #3 @(2*(p0+q0+p1)+p2+q1+4)>>3 H,p0' vmov.i8 d0, #2 vmov.i16 d1, #2 vaddl.u8 q1, d6, d8 @p0+q1 L vmlal.u8 q1, d10, d0 @2*p1+p0+q1 L vaddl.u8 q8, d7, d9 @p0+q1 H vmlal.u8 q8, d11, d0 @2*p1+p0+q1 H vaddw.u8 q6, q12, d30 @(p0+q0+p1) +p2 L vld1.8 {d24, d25}, [r2] @load p3,Q12 vaddw.u8 q2, q13, d31 @(p0+q0+p1) +p2 H vaddl.u8 q4, d30, d24 @p2+p3 L vrshrn.u16 d26, q6, #2 @((p0+q0+p1)+p2 +2)>>2,p1' L vrshrn.u16 d2, q1, #2 @(2*p1+p0+q1+2)>>2,p0"L vrshrn.u16 d27, q2, #2 @((p0+q0+p1)+p2 +2)>>2,p1' H vrshrn.u16 d3, q8, #2 @(2*p1+p0+q1+2)>>2,p0" H vaddl.u8 q8, d31, d25 @p2+p3 H vmla.u16 q6, q4, d1[0] @(p0+q0+p1)+3*p2+2*p3 L vmla.u16 q2, q8, d1[0] @(p0+q0+p1)+3*p2+2*p3 H vbic q8, q10, q9 @((ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) @&& (Ap < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) vbit q1, q14, q10 @choosing between po' and p0" vrshrn.u16 d12, q6, #3 @((p0+q0+p1)+3*p2+2*p3+4)>>3 L p2' vrshrn.u16 d13, q2, #3 @((p0+q0+p1)+3*p2+2*p3+4)>>3 H p2' vbif q3, q1, q9 @choosing between p0 and filtered value of p0 vbit q5, q13, q8 @choosing between p1 and p1' vbit q15, q6, q8 @choosing between p2 and p2' vst1.8 {d6, d7}, [r12] @store p0 vst1.8 {d10, d11}, [r14] @store p1 vst1.8 {d30, d31}, [r3] @store p2 vpop {d8 - d15} ldmfd sp!, {r12, pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a luma block vertical edge for cases where the @* boundary strength is less than 4 @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @param[in] sp(0) - u4_bs @* Packed Boundary strength array @* @* @param[in] sp(4) - pu1_cliptab @* tc0_table @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_luma_vert_bslt4_a9 ih264_deblk_luma_vert_bslt4_a9: stmfd sp!, {r12, lr} sub r0, r0, #4 @pointer uc_edgePixel-4 ldr r12, [sp, #8] @r12 = ui_Bs ldr r14, [sp, #12] @r14 = *puc_ClpTab vpush {d8 - d15} @loading p3:p2:p1:p0:q0:q1:q2:q3 for every row vld1.8 {d0}, [r0], r1 @row1 vld1.8 d2, [r0], r1 @row2 vld1.8 d4, [r0], r1 @row3 rev r12, r12 @reversing ui_bs vld1.8 d6, [r0], r1 @row4 vmov.32 d18[0], r12 @d12[0] = ui_Bs vld1.32 d16[0], [r14] @D16[0] contains cliptab vld1.8 d8, [r0], r1 @row5 vmovl.u8 q9, d18 @q6 = uc_Bs in each 16 bt scalar vld1.8 d10, [r0], r1 @row6 vld1.8 d12, [r0], r1 @row7 vtbl.8 d16, {d16}, d18 @puc_ClipTab[uc_Bs] vld1.8 d14, [r0], r1 @row8 vld1.8 d1, [r0], r1 @row9 vmovl.u16 q8, d16 @ vld1.8 d3, [r0], r1 @row10 vld1.8 d5, [r0], r1 @row11 vld1.8 d7, [r0], r1 @row12 vsli.32 q8, q8, #8 @ vld1.8 d9, [r0], r1 @row13 vld1.8 d11, [r0], r1 @row14 vld1.8 d13, [r0], r1 @row15 vsli.32 q8, q8, #16 @Q8 = C0 vld1.8 d15, [r0], r1 @row16 @taking two 8x8 transposes @2X2 transposes vtrn.8 d0, d2 @row1 &2 vtrn.8 d4, d6 @row3&row4 vtrn.8 d8, d10 @row5&6 vtrn.8 d12, d14 @row7 & 8 vtrn.8 d1, d3 @row9 &10 vtrn.8 d5, d7 @row11 & 12 vtrn.8 d9, d11 @row13 &14 vtrn.8 d13, d15 @row15 & 16 @4x4 transposes vtrn.16 d2, d6 @row2 & row4 vtrn.16 d10, d14 @row6 & row8 vtrn.16 d3, d7 @row10 & 12 vtrn.16 d11, d15 @row14 & row16 vtrn.32 d6, d14 @row4 & 8 vtrn.32 d7, d15 @row 12 & 16 @now Q3 ->p0 and Q7->q3 vtrn.16 d0, d4 @row1 & 3 vtrn.16 d8, d12 @row 5 & 7 vtrn.16 d1, d5 @row9 & row11 vtrn.16 d9, d13 @row13 & row15 vtrn.32 d0, d8 @row1 & row5 vtrn.32 d1, d9 @row9 & 13 @now Q0->p3 & Q4->q0 @starting processing as p0 and q0 are now ready vtrn.32 d2, d10 @row2 &6 vrhadd.u8 q10, q3, q4 @((p0 + q0 + 1) >> 1) vtrn.32 d3, d11 @row10&row14 vmov.i8 d19, #2 @now Q1->p2 & Q5->q1 vtrn.32 d4, d12 @row3 & 7 vabd.u8 q11, q3, q4 @ABS(p0 - q0) vtrn.32 d5, d13 @row11 & row15 vaddl.u8 q12, d20, d2 @(p2 + ((p0 + q0 + 1) >> 1) L @now Q2->p1,Q6->q2 vaddl.u8 q13, d21, d3 @(p2 + ((p0 + q0 + 1) >> 1) H vmlsl.u8 q12, d4, d19 @(p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) L vmlsl.u8 q13, d5, d19 @(p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) H vdup.8 q14, r2 @alpha vcle.u8 q11, q14, q11 @ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) vdup.i8 q14, r3 @beta vabd.u8 q15, q5, q4 @ABS(q1 - q0) vqshrn.s16 d24, q12, #1 @((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1) L vqshrn.s16 d25 , q13, #1 @((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1) H vcge.u8 q15, q15, q14 @ABS(q1 - q0) >= Beta vabd.u8 q13, q2, q3 @ABS(p1 - p0) vmin.s8 q12, q12, q8 @min(deltap1 ,C0) vorr q11, q11, q15 @ABS(q1 - q0) >= Beta ||ABS(p0 - q0) >= Alpha vneg.s8 q15, q8 @-C0 vcge.u8 q13, q13, q14 @ABS(p1 - p0) >= Beta vmax.s8 q12, q12, q15 @max(deltap1,-C0) vorr q11, q11, q13 @ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta) vmovl.u16 q13, d18 @ui_bs vaddl.u8 q9, d20, d12 @q2 + ((p0 + q0 + 1) >> 1) L vceq.u32 q13, q13, #0 @ui_bs == 0 vsubw.u8 q9, q9, d10 @(q2 + ((p0 + q0 + 1) >> 1) - q1) L vaddl.u8 q10, d21, d13 @q2 + ((p0 + q0 + 1) >> 1) H vsubw.u8 q9, q9, d10 @(q2 + ((p0 + q0 + 1) >> 1) - 2*q1)L vsubw.u8 q10, q10, d11 @(q2 + ((p0 + q0 + 1) >> 1) - q1) H vorr q13, q13, q11 @(ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) &&(ui_bs) vsubw.u8 q10, q10, d11 @(q2 + ((p0 + q0 + 1) >> 1) - 2*q1) H vqshrn.s16 d18, q9, #1 @((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1) L vabd.u8 q11, q1, q3 @Ap = ABS(p2 - p0) vqshrn.s16 d19, q10, #1 @((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1) H vabd.u8 q10, q6, q4 @Aq= ABS(q2 - q0) vclt.u8 q11, q11, q14 @Ap < Beta vmin.s8 q9, q9, q8 @min(delatq1,C0) vclt.u8 q10, q10, q14 @Aq > 3); L vrshrn.s16 d29, q15, #3 @delta = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) H vsub.u8 q8, q8, q10 @C0 + (Ap < Beta) + (Aq < Beta) vbic q10, q10, q13 @final condition for q1 vabs.s8 q15, q14 @abs(delta) vand q12, q12, q11 @delatp1 vand q9, q9, q10 @delta q1 vmin.u8 q15, q15, q8 @min((abs(delta),C) vadd.i8 q2, q2, q12 @p1+deltap1 vadd.i8 q5, q5, q9 @q1+deltaq1 vbic q15, q15, q13 @abs(delta) of pixels to be changed only vcge.s8 q14, q14, #0 @sign(delta) vqsub.u8 q11, q3, q15 @clip(p0-delta) vtrn.8 d0, d2 @row1 &2 vqadd.u8 q3, q3, q15 @clip(p0+delta) vtrn.8 d1, d3 @row9 &10 vqadd.u8 q12, q4, q15 @clip(q0+delta) vtrn.8 d12, d14 @row7 & 8 vqsub.u8 q4, q4, q15 @clip(q0-delta) vtrn.8 d13, d15 @row15 & 16 vbif q3, q11, q14 @p0 vbif q4, q12, q14 @q0 vtrn.8 d4, d6 @row3&row4 vtrn.8 d8, d10 @row5&6 vtrn.8 d5, d7 @row11 & 12 vtrn.8 d9, d11 @row13 &14 vtrn.16 d2, d6 @row2 & row4 vtrn.16 d10, d14 @row6 & row8 vtrn.16 d3, d7 @row10 & 12 vtrn.16 d11, d15 @row14 & row16 vtrn.32 d6, d14 @row4 & 8 vtrn.32 d7, d15 @row 12 & 16 @now Q3 ->p0 and Q7->q3 vtrn.16 d0, d4 @row1 & 3 vtrn.16 d8, d12 @row 5 & 7 vtrn.16 d1, d5 @row9 & row11 vtrn.16 d9, d13 @row13 & row15 sub r0, r0, r1, lsl#4 @restore pointer vtrn.32 d0, d8 @row1 & row5 vtrn.32 d1, d9 @row9 & 13 vtrn.32 d2, d10 @row2 &6 vtrn.32 d3, d11 @row10&row14 vtrn.32 d4, d12 @row3 & 7 vtrn.32 d5, d13 @row11 & row15 vst1.8 {d0}, [r0], r1 @row1 vst1.8 d2, [r0], r1 @row2 vst1.8 d4, [r0], r1 @row3 vst1.8 d6, [r0], r1 @row4 vst1.8 d8, [r0], r1 @row5 vst1.8 d10, [r0], r1 @row6 vst1.8 d12, [r0], r1 @row7 vst1.8 d14, [r0], r1 @row8 vst1.8 d1, [r0], r1 @row9 vst1.8 d3, [r0], r1 @row10 vst1.8 d5, [r0], r1 @row11 vst1.8 d7, [r0], r1 @row12 vst1.8 d9, [r0], r1 @row13 vst1.8 d11, [r0], r1 @row14 vst1.8 d13, [r0], r1 @row15 vst1.8 d15, [r0], r1 @row16 vpop {d8 - d15} ldmfd sp!, {r12, pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a luma block vertical edge when the @* boundary strength is set to 4 @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_luma_vert_bs4_a9 ih264_deblk_luma_vert_bs4_a9: stmfd sp!, {r12, lr} vpush {d8 - d15} sub r0, r0, #4 @pointer uc_edgePixel-4 @loading p3:p2:p1:p0:q0:q1:q2:q3 for every row vld1.8 d0, [r0], r1 @row1 vld1.8 d2, [r0], r1 @row2 vld1.8 d4, [r0], r1 @row3 vld1.8 d6, [r0], r1 @row4 vld1.8 d8, [r0], r1 @row5 vld1.8 d10, [r0], r1 @row6 vld1.8 d12, [r0], r1 @row7 vld1.8 d14, [r0], r1 @row8 vld1.8 d1, [r0], r1 @row9 vld1.8 d3, [r0], r1 @row10 vld1.8 d5, [r0], r1 @row11 vld1.8 d7, [r0], r1 @row12 vld1.8 d9, [r0], r1 @row13 vld1.8 d11, [r0], r1 @row14 vld1.8 d13, [r0], r1 @row15 vld1.8 d15, [r0], r1 @row16 @taking two 8x8 transposes @2X2 transposes vtrn.8 d0, d2 @row1 &2 vtrn.8 d4, d6 @row3&row4 vtrn.8 d8, d10 @row5&6 vtrn.8 d12, d14 @row7 & 8 vtrn.8 d1, d3 @row9 &10 vtrn.8 d5, d7 @row11 & 12 vtrn.8 d9, d11 @row13 &14 vtrn.8 d13, d15 @row15 & 16 @4x4 transposes vtrn.16 d2, d6 @row2 & row4 vtrn.16 d10, d14 @row6 & row8 vtrn.16 d3, d7 @row10 & 12 vtrn.16 d11, d15 @row14 & row16 vtrn.32 d6, d14 @row4 & 8 vtrn.32 d7, d15 @row 12 & 16 @now Q3 ->p0 and Q7->q3 vtrn.16 d0, d4 @row1 & 3 vtrn.16 d8, d12 @row 5 & 7 vtrn.16 d1, d5 @row9 & row11 vtrn.16 d9, d13 @row13 & row15 vtrn.32 d0, d8 @row1 & row5 vtrn.32 d1, d9 @row9 & 13 @now Q0->p3 & Q4->q0 @starting processing as p0 and q0 are now ready @now Q1->p2 & Q5->q1 vpush {q7} @saving in stack vtrn.32 d4, d12 @row3 & 7 vmov.i16 q14, #2 vtrn.32 d5, d13 @row11 & row15 vaddl.u8 q8, d6, d8 @p0+q0 L vtrn.32 d2, d10 @row2 &6 vaddl.u8 q9, d7, d9 @p0+q0 H vtrn.32 d3, d11 @row10&row14 vaddw.u8 q10, q8, d4 @p0+q0+p1 L vaddw.u8 q11, q9, d5 @p0+q0+p1 H vaddl.u8 q12, d2, d10 @p2+q1 L vaddl.u8 q13, d3, d11 @p2+q1 H vmla.u16 q12, q10, q14 @p2 + X2(p1) + X2(p0) + X2(q0) + q1 L vmla.u16 q13, q11, q14 @p2 + X2(p1) + X2(p0) + X2(q0) + q1 H vmov.i8 q14, #2 vaddw.u8 q8, q10, d2 @p0+q0+p1+p2 L vaddw.u8 q9, q11, d3 @p0+q0+p1+p2 H vdup.i8 q15, r2 @duplicate alpha vrshrn.u16 d20, q8, #2 @(p2 + p1 + p0 + q0 + 2) >> 2)L p1' vrshrn.u16 d21, q9, #2 @(p2 + p1 + p0 + q0 + 2) >> 2)H p1' vabd.u8 q11, q3, q4 @ABD(p0-q0) vsra.u8 q14, q15, #2 @alpha >>2 +2 vabd.u8 q15, q1, q3 @Ap = ABD(p2-p0) vrshrn.u16 d24, q12, #3 @((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3) L p0' vrshrn.u16 d25, q13, #3 @((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3) H p0' vdup.i8 q13, r3 @beta vcgt.u8 q14, q14, q11 @ABS(p0 - q0) <((Alpha >>2) + 2) vaddl.u8 q11, d6, d10 @p0+q1 L vcgt.u8 q7, q13, q15 @beta>Ap vaddl.u8 q15, d7, d11 @p0+q1 H vaddw.u8 q11, q11, d4 @p0+q1+p1 L vaddw.u8 q15, q15, d5 @p0+q1+p1 H vaddw.u8 q11, q11, d4 @p0+q1+2*p1 L vaddw.u8 q15, q15, d5 @p0+q1+2*p1 H vand q7, q7, q14 @(Ap < Beta && ABS(p0 - q0) <((Alpha >>2) + 2) vrshrn.u16 d22, q11, #2 @((X2(p1) + p0 + q1 + 2) >> 2) L p0" vrshrn.u16 d23, q15, #2 @((X2(p1) + p0 + q1 + 2) >> 2) H p0" vaddl.u8 q15, d2, d0 @p2+p3 L vbif q12, q11, q7 @p0' or p0 " vaddl.u8 q11, d3, d1 @p2+p3 H vadd.u16 q15, q15, q15 @2*(p2+p3) L vadd.u16 q11, q11, q11 @2*(p2+p3)H vadd.u16 q8, q8, q15 @(X2(p3) + X3(p2) + p1 + p0 + q0) L vadd.u16 q9, q9, q11 @(X2(p3) + X3(p2) + p1 + p0 + q0) H vabd.u8 q15, q6, q4 @Aq = abs(q2-q0) vabd.u8 q11, q5, q4 @ABS(Q1-Q0) vrshrn.u16 d16, q8, #3 @((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); L p2' vrshrn.u16 d17, q9, #3 @((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); H p2' vabd.u8 q9, q2, q3 @ABS(p1-p0) vcgt.u8 q15, q13, q15 @Aq < Beta vcge.u8 q11, q11, q13 @ABS(q1 - q0) >= Beta vcge.u8 q9, q9, q13 @ABS(p1 - p0) >= beta vdup.i8 q13, r2 @duplicate alpha vand q15, q15, q14 @(Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) vabd.u8 q14, q3, q4 @abs(p0-q0) vorr q11, q11, q9 @ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta vaddl.u8 q9, d6, d8 @p0+q0 L vcge.u8 q14, q14, q13 @ABS(p0 - q0) >= Alpha vaddl.u8 q13, d7, d9 @p0+q0 H vaddw.u8 q9, q9, d10 @p0+q0+q1 L vorr q11, q11, q14 @ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta||ABS(p0 - q0) >= Alpha vaddw.u8 q13, q13, d11 @p0+q0+q1 H vbic q7, q7, q11 @final condn for p's vmov.i8 q14, #2 vbif q3, q12, q11 @final p0 vbit q1, q8, q7 @final p2 vbif q10, q2, q7 @final p1 vaddl.u8 q12, d8, d4 @q0+p1 L vmlal.u8 q12, d10, d28 @X2(q1) + q0 + p1 L vaddl.u8 q8, d9, d5 @q0+p1 H vmlal.u8 q8, d11, d28 @X2(q1) + q0 + p1 H vmov.i16 q14, #2 vaddl.u8 q7, d4, d12 @p1+q2 L vmla.u16 q7, q9, q14 @p1 + X2(p0) + X2(q0) + X2(q1) + q2L vaddl.u8 q2, d5, d13 @p1+q2H vmla.u16 q2, q13, q14 @p1 + X2(p0) + X2(q0) + X2(q1) + q2H vrshrn.u16 d24, q12, #2 @(X2(q1) + q0 + p1 + 2) >> 2; L q0' vrshrn.u16 d25, q8, #2 @(X2(q1) + q0 + p1 + 2) >> 2; H q0' vaddw.u8 q9, q9, d12 @p0 + q0 + q1 + q2 L vaddw.u8 q13, q13, d13 @p0 + q0 + q1 + q2 H vrshrn.u16 d16, q7, #3 @(p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3 L qo" vpop {q7} vrshrn.u16 d17, q2, #3 @(p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3 H qo" vrshrn.u16 d4, q9, #2 @p0 + q0 + q1 + q2 + 2)>>2 L q1' vrshrn.u16 d5, q13, #2 @p0 + q0 + q1 + q2 + 2)>>2 H q1' vbit q12, q8, q15 @q0' or q0" vbic q15, q15, q11 @final condn for q's vtrn.8 d0, d2 @row1 &2 vbit q5, q2, q15 @final q1 vtrn.8 d1, d3 @row9 &10 vaddl.u8 q8, d12, d14 @q2+q3 L vtrn.8 d20, d6 @row3&row4 vaddl.u8 q2, d13, d15 @q2+q3 H vtrn.8 d21, d7 @row11 & 12 vmla.u16 q9, q8, q14 @X2(q3) + X3(q2) + q1 + q0 + p0 L vtrn.16 d2, d6 @row2 & row4 vmla.u16 q13, q2, q14 @X2(q3) + X3(q2) + q1 + q0 + p0 H vtrn.16 d3, d7 @row10 & 12 vbif q4, q12, q11 @final q0 vtrn.16 d0, d20 @row1 & 3 vrshrn.u16 d18, q9, #3 @(X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; L vtrn.16 d1, d21 @row9 & row11 vrshrn.u16 d19, q13, #3 @(X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; H vtrn.8 d8, d10 @row5&6 vbit q6, q9, q15 @final q2 vtrn.8 d9, d11 @row13 &14 vtrn.8 d12, d14 @row7 & 8 vtrn.8 d13, d15 @row15 & 16 vtrn.16 d10, d14 @row6 & row8 vtrn.16 d11, d15 @row14 & row16 @now Q3 ->p0 and Q7->q3 vtrn.16 d8, d12 @row 5 & 7 vtrn.16 d9, d13 @row13 & row15 sub r0, r0, r1, lsl#4 @restore pointer vtrn.32 d6, d14 @row4 & 8 vtrn.32 d7, d15 @row 12 & 16 vtrn.32 d0, d8 @row1 & row5 vtrn.32 d1, d9 @row9 & 13 vtrn.32 d2, d10 @row2 &6 vtrn.32 d3, d11 @row10&row14 vtrn.32 d20, d12 @row3 & 7 vtrn.32 d21, d13 @row11 & row15 vst1.8 d0, [r0], r1 @row1 vst1.8 d2, [r0], r1 @row2 vst1.8 d20, [r0], r1 @row3 vst1.8 d6, [r0], r1 @row4 vst1.8 d8, [r0], r1 @row5 vst1.8 d10, [r0], r1 @row6 vst1.8 d12, [r0], r1 @row7 vst1.8 d14, [r0], r1 @row8 vst1.8 d1, [r0], r1 @row9 vst1.8 d3, [r0], r1 @row10 vst1.8 d21, [r0], r1 @row11 vst1.8 d7, [r0], r1 @row12 vst1.8 d9, [r0], r1 @row13 vst1.8 d11, [r0], r1 @row14 vst1.8 d13, [r0], r1 @row15 vst1.8 d15, [r0], r1 @row16 vpop {d8 - d15} ldmfd sp!, {r12, pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a luma block vertical edge when the @* boundary strength is set to 4 on calling twice @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_luma_vert_bs4_mbaff_a9 ih264_deblk_luma_vert_bs4_mbaff_a9: stmfd sp!, {lr} sub r0, r0, #4 @pointer uc_edgePixel-4 vpush {d8 - d15} @loading [p3:p2],[p1:p0]:[q0:q1]:[q2:q3] for every row vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 vuzp.8 d0, d1 @D0->p3, D1->p2 vuzp.8 d2, d3 @D2->p1, D3->p0 vuzp.8 d4, d5 @D4->q0, D5->q1 vuzp.8 d6, d7 @D6->q2, D7->q3 vmov.i16 q14, #2 vaddl.u8 q4, d3, d4 @p0+q0 vaddw.u8 q5, q4, d2 @p0+q0+p1 vaddl.u8 q6, d1, d5 @p2+q1 vmla.u16 q6, q5, q14 @p2 + X2(p1) + X2(p0) + X2(q0) + q1 vmov.i8 d14, #2 vaddw.u8 q4, q5, d1 @p0+q0+p1+p2 vdup.i8 d15, r2 @duplicate alpha vrshrn.u16 d10, q4, #2 @(p2 + p1 + p0 + q0 + 2) >> 2) p1' vabd.u8 d11, d3, d4 @ABD(p0-q0) vsra.u8 d14, d15, #2 @alpha >>2 +2 vabd.u8 d15, d1, d3 @Ap = ABD(p2-p0) vrshrn.u16 d12, q6, #3 @((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3) p0' vdup.i8 d13, r3 @beta vcgt.u8 d14, d14, d11 @ABS(p0 - q0) <((Alpha >>2) + 2) vaddl.u8 q8, d3, d5 @p0+q1 vcgt.u8 d26, d13, d15 @beta>Ap vaddw.u8 q8, q8, d2 @p0+q1+p1 vaddw.u8 q8, q8, d2 @p0+q1+2*p1 vand d26, d26, d14 @(Ap < Beta && ABS(p0 - q0) <((Alpha >>2) + 2) vrshrn.u16 d11, q8, #2 @((X2(p1) + p0 + q1 + 2) >> 2) p0" vbif d12, d11, d26 @p0' or p0 " vaddl.u8 q9, d1, d0 @p2+p3 vadd.u16 q9, q9, q9 @2*(p2+p3) vadd.u16 q4, q4, q9 @(X2(p3) + X3(p2) + p1 + p0 + q0) vabd.u8 d15, d6, d4 @Aq = abs(q2-q0) vabd.u8 d11, d5, d4 @ABS(q1-q0) vrshrn.u16 d8, q4, #3 @((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); p2' vabd.u8 d9, d2, d3 @ABS(p1-p0) vcgt.u8 d15, d13, d15 @Aq < Beta vcge.u8 d11, d11, d13 @ABS(q1 - q0) >= Beta vcge.u8 d9, d9, d13 @ABS(p1 - p0) >= beta vdup.i8 d13, r2 @duplicate alpha vand d15, d15, d14 @(Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) vabd.u8 d14, d3, d4 @abs(p0-q0) vorr d11, d11, d9 @ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta vcge.u8 d14, d14, d13 @ABS(p0 - q0) >= Alpha vaddl.u8 q10, d3, d4 @p0+q0 vorr d11, d11, d14 @ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta||ABS(p0 - q0) >= Alpha vaddw.u8 q10, q10, d5 @p0+q0+q1 vbic d26, d26, d11 @final condn for p's vmov.i8 d14, #2 vbif d3, d12, d11 @final p0 vbit d1, d8, d26 @final p2 vbif d10, d2, d26 @final p1 vaddl.u8 q6, d4, d2 @q0+p1 vmlal.u8 q6, d5, d14 @X2(q1) + q0 + p1 vaddl.u8 q11, d2, d6 @p1+q2 vmla.u16 q11, q10, q14 @p1 + X2(p0) + X2(q0) + X2(q1) + q2 vrshrn.u16 d12, q6, #2 @(X2(q1) + q0 + p1 + 2) >> 2; q0' vaddw.u8 q10, q10, d6 @p0 + q0 + q1 + q2 vrshrn.u16 d8, q11, #3 @(p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3 qo" vrshrn.u16 d2, q10, #2 @p0 + q0 + q1 + q2 + 2)>>2 q1' vbit d12, d8, d15 @q0' or q0" vbic d15, d15, d11 @final condn for q's vbit d5, d2, d15 @final q1 vaddl.u8 q12, d6, d7 @q2+q3 vmla.u16 q10, q12, q14 @X2(q3) + X3(q2) + q1 + q0 + p0 vbif d4, d12, d11 @final q0 vrshrn.u16 d9, q10, #3 @(X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; vbit d6, d9, d15 @final q2 vand d2, d10, d10 @D0->p3, D1->p2, D2->p1, D3->p0, D4->q0, D5->q1, D6->q2, D7->q3 vzip.8 d0, d1 @D0,D1 -> [p3:p2] vzip.8 d2, d3 @D2,D3 -> [p1:p0] vzip.8 d4, d5 @D4,D5 -> [q0:q1] vzip.8 d6, d7 @D6,D7 -> [q2:q3] sub r0, r0, r1, lsl#3 @restore pointer @storing [p3:p2],[p1:p0]:[q0:q1]:[q2:q3] in every row vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 vpop {d8 - d15} ldmfd sp!, {pc} @** @******************************************************************************* @* @* @brief @* Performs filtering of a luma block vertical edge for cases where the @* boundary strength is less than 4 on calling twice @* @* @par Description: @* This operation is described in Sec. 8.7.2.4 under the title @* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. @* @* @param[in] r0 - pu1_src @* Pointer to the src sample q0 @* @* @param[in] r1 - src_strd @* Source stride @* @* @param[in] r2 - alpha @* Alpha Value for the boundary @* @* @param[in] r3 - beta @* Beta Value for the boundary @* @* @param[in] sp(0) - u4_bs @* Packed Boundary strength array @* @* @param[in] sp(4) - pu1_cliptab @* tc0_table @* @* @returns @* None @* @* @remarks @* None @* @******************************************************************************* @* .global ih264_deblk_luma_vert_bslt4_mbaff_a9 ih264_deblk_luma_vert_bslt4_mbaff_a9: stmfd sp!, {r12, lr} sub r0, r0, #4 @pointer uc_edgePixel-4 ldr r12, [sp, #8] @r12 = ui_Bs ldr r14, [sp, #12] @r14 = pu1_ClipTab vpush {d8 - d15} @loading [p3:p2],[p1:p0]:[q0:q1]:[q2:q3] for every row vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 vuzp.8 d0, d1 @D0->p3, D1->p2 vuzp.8 d2, d3 @D2->p1, D3->p0 vuzp.8 d4, d5 @D4->q0, D5->q1 vuzp.8 d6, d7 @D6->q2, D7->q3 rev r12, r12 @reversing ui_bs vmov.32 d8[0], r12 @D8[0] = ui_Bs vld1.32 d9[0], [r14] @D9[0] contains cliptab vmovl.u8 q15, d8 @D30 = ui_Bs in each 16 bt scalar vtbl.8 d8, {d9}, d30 @puc_ClipTab[ui_Bs] vsli.16 d8, d8, #8 @D8 = C0 vrhadd.u8 d10, d3, d4 @((p0 + q0 + 1) >> 1) vmov.i8 d31, #2 vabd.u8 d11, d3, d4 @ABS(p0 - q0) vaddl.u8 q6, d10, d1 @(p2 + ((p0 + q0 + 1) >> 1) vmlsl.u8 q6, d2, d31 @(p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) vdup.8 d14, r2 @alpha vcle.u8 d11, d14, d11 @ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) vdup.i8 d14, r3 @beta vabd.u8 d15, d5, d4 @ABS(q1 - q0) vqshrn.s16 d12, q6, #1 @((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1) vcge.u8 d15, d15, d14 @ABS(q1 - q0) >= Beta vabd.u8 d13, d2, d3 @ABS(p1 - p0) vmin.s8 d12, d12, d8 @min(deltap1 ,C0) vorr d11, d11, d15 @ABS(q1 - q0) >= Beta ||ABS(p0 - q0) >= Alpha vneg.s8 d15, d8 @-C0 vcge.u8 d13, d13, d14 @ABS(p1 - p0) >= Beta vmax.s8 d12, d12, d15 @max(deltap1,-C0) vorr d11, d11, d13 @ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta) vceq.u16 d13, d30, #0 @ui_bs == 0 vaddl.u8 q14, d10, d6 @q2 + ((p0 + q0 + 1) >> 1) vsubw.u8 q14, q14, d5 @q2 + ((p0 + q0 + 1) >> 1) - q1 vsubw.u8 q14, q14, d5 @q2 + ((p0 + q0 + 1) >> 1) - 2*q1 vorr d13, d13, d11 @(ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) @|| (ui_bs == 0) vqshrn.s16 d9, q14, #1 @(q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1 vabd.u8 d11, d1, d3 @Ap = ABS(p2 - p0) vabd.u8 d10, d6, d4 @Aq= ABS(q2 - q0) vclt.u8 d11, d11, d14 @Ap < Beta vmin.s8 d9, d9, d8 @min(deltaq1,C0) vclt.u8 d10, d10, d14 @Aq < Beta vmax.s8 d9, d9, d15 @max(deltaq1,-C0) vsubl.u8 q7, d4, d3 @q0 - p0 vshl.s16 q7, q7, #2 @(q0 - p0) << 2 vsub.u8 d8, d8, d11 @C0 + (Ap < Beta) vaddw.u8 q7, q7, d2 @((q0 - p0) << 2) + p1 vsubw.u8 q7, q7, d5 @((q0 - p0) << 2) + (p1 - q1) vbic d11, d11, d13 @final condition for p1 vrshr.s16 q15, q7, #3 @delta = (((q0 - p0) << 2) + (p1 - q1) + 4) >> 3 vsub.u8 d8, d8, d10 @C0 + (Ap < Beta) + (Aq < Beta) vbic d10, d10, d13 @final condition for q1 vabs.s16 q14, q15 vmovn.i16 d15, q14 @abs(delta) vand d12, d12, d11 @delatp1 vand d9, d9, d10 @deltaq1 vmin.u8 d15, d15, d8 @min((abs(delta),C) vadd.i8 d2, d2, d12 @p1+deltap1 vadd.i8 d5, d5, d9 @q1+deltaq1 vbic d15, d15, d13 @abs(delta) of pixels to be changed only vcge.s16 q14, q15, #0 vmovn.i16 d14, q14 @sign(delta) vqsub.u8 d11, d3, d15 @clip(p0-delta) vqadd.u8 d3, d3, d15 @clip(p0+delta) vqadd.u8 d12, d4, d15 @clip(q0+delta) vqsub.u8 d4, d4, d15 @clip(q0-delta) vbif d3, d11, d14 @p0 vbif d4, d12, d14 @q0 sub r0, r0, r1, lsl#3 @restore pointer @D0->p3, D1->p2, D2->p1, D3->p0, D4->q0, D5->q1, D6->q2, D7->q3 vzip.8 d0, d1 @D0,D1 -> [p3:p2] vzip.8 d2, d3 @D2,D3 -> [p1:p0] vzip.8 d4, d5 @D4,D5 -> [q0:q1] vzip.8 d6, d7 @D6,D7 -> [q2:q3] @storing [p3:p2],[p1:p0]:[q0:q1]:[q2:q3] in every row vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 vpop {d8 - d15} ldmfd sp!, {r12, pc} ================================================ FILE: dependencies/ih264d/common/arm/ih264_default_weighted_pred_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_default_weighted_pred_a9q.s @* @* @brief @* Contains function definitions for default weighted prediction. @* @* @author @* Kaushik Senthoor R @* @* @par List of Functions: @* @* - ih264_default_weighted_pred_luma_a9q() @* - ih264_default_weighted_pred_chroma_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @******************************************************************************* @* @function @* ih264_default_weighted_pred_luma_a9q() @* @* @brief @* This routine performs the default weighted prediction as described in sec @* 8.4.2.3.1 titled "Default weighted sample prediction process" for luma. @* @* @par Description: @* This function gets two ht x wd blocks, calculates their rounded-average and @* stores it in the destination block. @* @* @param[in] pu1_src1: @* UWORD8 Pointer to the buffer containing the first input block. @* @* @param[in] pu1_src2: @* UWORD8 Pointer to the buffer containing the second input block. @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination where the output block is stored. @* @* @param[in] src_strd1 @* Stride of the first input buffer @* @* @param[in] src_strd2 @* Stride of the second input buffer @* @* @param[in] dst_strd @* Stride of the destination buffer @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* None @* @* @remarks @* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). @* @******************************************************************************* @* @void ih264_default_weighted_pred_luma_a9q(UWORD8 *pu1_src1, @ UWORD8 *pu1_src2, @ UWORD8 *pu1_dst, @ WORD32 src_strd1, @ WORD32 src_strd2, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd) @ @**************Variables Vs Registers***************************************** @ r0 => pu1_src1 @ r1 => pu1_src2 @ r2 => pu1_dst @ r3 => src_strd1 @ [sp] => src_strd2 (r4) @ [sp+4] => dst_strd (r5) @ [sp+8] => ht (r6) @ [sp+12] => wd (r7) @ .text .p2align 2 .global ih264_default_weighted_pred_luma_a9q ih264_default_weighted_pred_luma_a9q: stmfd sp!, {r4-r7, r14} @stack stores the values of the arguments ldr r7, [sp, #32] @Load wd ldr r4, [sp, #20] @Load src_strd2 ldr r5, [sp, #24] @Load dst_strd cmp r7, #16 ldr r6, [sp, #28] @Load ht vpush {d8-d15} beq loop_16 @branch if wd is 16 cmp r7, #8 beq loop_8 @branch if wd is 8 loop_4: @each iteration processes four rows vld1.32 d0[0], [r0], r3 @load row 1 in source 1 vld1.32 d0[1], [r0], r3 @load row 2 in source 1 vld1.32 d2[0], [r1], r4 @load row 1 in source 2 vld1.32 d2[1], [r1], r4 @load row 2 in source 2 vld1.32 d1[0], [r0], r3 @load row 3 in source 1 vld1.32 d1[1], [r0], r3 @load row 4 in source 1 vrhadd.u8 d0, d0, d2 vld1.32 d3[0], [r1], r4 @load row 3 in source 2 vld1.32 d3[1], [r1], r4 @load row 4 in source 2 subs r6, r6, #4 @decrement ht by 4 vst1.32 d0[0], [r2], r5 @load row 1 in destination vst1.32 d0[1], [r2], r5 @load row 2 in destination vrhadd.u8 d1, d1, d3 vst1.32 d1[0], [r2], r5 @load row 3 in destination vst1.32 d1[1], [r2], r5 @load row 4 in destination bgt loop_4 @if greater than 0 repeat the loop again b end_loops loop_8: @each iteration processes four rows vld1.8 d0, [r0], r3 @load row 1 in source 1 vld1.8 d4, [r1], r4 @load row 1 in source 2 vld1.8 d1, [r0], r3 @load row 2 in source 1 vld1.8 d5, [r1], r4 @load row 2 in source 2 vld1.8 d2, [r0], r3 @load row 3 in source 1 vrhadd.u8 q0, q0, q2 vld1.8 d6, [r1], r4 @load row 3 in source 2 vld1.8 d3, [r0], r3 @load row 4 in source 1 vrhadd.u8 d2, d2, d6 vld1.8 d7, [r1], r4 @load row 4 in source 2 subs r6, r6, #4 @decrement ht by 4 vst1.8 d0, [r2], r5 @load row 1 in destination vrhadd.u8 d3, d3, d7 vst1.8 d1, [r2], r5 @load row 2 in destination vst1.8 d2, [r2], r5 @load row 3 in destination vst1.8 d3, [r2], r5 @load row 4 in destination bgt loop_8 @if greater than 0 repeat the loop again b end_loops loop_16: @each iteration processes eight rows vld1.8 {q0}, [r0], r3 @load row 1 in source 1 vld1.8 {q8}, [r1], r4 @load row 1 in source 2 vld1.8 {q1}, [r0], r3 @load row 2 in source 1 vld1.8 {q9}, [r1], r4 @load row 2 in source 2 vrhadd.u8 q0, q0, q8 vld1.8 {q2}, [r0], r3 @load row 3 in source 1 vld1.8 {q10}, [r1], r4 @load row 3 in source 2 vrhadd.u8 q1, q1, q9 vld1.8 {q3}, [r0], r3 @load row 4 in source 1 vld1.8 {q11}, [r1], r4 @load row 4 in source 2 vrhadd.u8 q2, q2, q10 vld1.8 {q4}, [r0], r3 @load row 5 in source 1 vld1.8 {q12}, [r1], r4 @load row 5 in source 2 vrhadd.u8 q3, q3, q11 vld1.8 {q5}, [r0], r3 @load row 6 in source 1 vld1.8 {q13}, [r1], r4 @load row 6 in source 2 vrhadd.u8 q4, q4, q12 vld1.8 {q6}, [r0], r3 @load row 7 in source 1 vld1.8 {q14}, [r1], r4 @load row 7 in source 2 vrhadd.u8 q5, q5, q13 vld1.8 {q7}, [r0], r3 @load row 8 in source 1 vld1.8 {q15}, [r1], r4 @load row 8 in source 2 vrhadd.u8 q6, q6, q14 vst1.8 {q0}, [r2], r5 @load row 1 in destination vst1.8 {q1}, [r2], r5 @load row 2 in destination vrhadd.u8 q7, q7, q15 vst1.8 {q2}, [r2], r5 @load row 3 in destination vst1.8 {q3}, [r2], r5 @load row 4 in destination subs r6, r6, #8 @decrement ht by 8 vst1.8 {q4}, [r2], r5 @load row 5 in destination vst1.8 {q5}, [r2], r5 @load row 6 in destination vst1.8 {q6}, [r2], r5 @load row 7 in destination vst1.8 {q7}, [r2], r5 @load row 8 in destination bgt loop_16 @if greater than 0 repeat the loop again end_loops: vpop {d8-d15} ldmfd sp!, {r4-r7, r15} @Reload the registers from sp @******************************************************************************* @* @function @* ih264_default_weighted_pred_chroma_a9q() @* @* @brief @* This routine performs the default weighted prediction as described in sec @* 8.4.2.3.1 titled "Default weighted sample prediction process" for chroma. @* @* @par Description: @* This function gets two ht x wd blocks, calculates their rounded-average and @* stores it in the destination block for U and V. @* @* @param[in] pu1_src1: @* UWORD8 Pointer to the buffer containing the first input block. @* @* @param[in] pu1_src2: @* UWORD8 Pointer to the buffer containing the second input block. @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination where the output block is stored. @* @* @param[in] src_strd1 @* Stride of the first input buffer @* @* @param[in] src_strd2 @* Stride of the second input buffer @* @* @param[in] dst_strd @* Stride of the destination buffer @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* None @* @* @remarks @* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). @* @******************************************************************************* @* @void ih264_default_weighted_pred_chroma_a9q(UWORD8 *pu1_src1, @ UWORD8 *pu1_src2, @ UWORD8 *pu1_dst, @ WORD32 src_strd1, @ WORD32 src_strd2, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd) @ @**************Variables Vs Registers***************************************** @ r0 => pu1_src1 @ r1 => pu1_src2 @ r2 => pu1_dst @ r3 => src_strd1 @ [sp] => src_strd2 (r4) @ [sp+4] => dst_strd (r5) @ [sp+8] => ht (r6) @ [sp+12] => wd (r7) @ .global ih264_default_weighted_pred_chroma_a9q ih264_default_weighted_pred_chroma_a9q: stmfd sp!, {r4-r7, r14} @stack stores the values of the arguments ldr r7, [sp, #32] @Load wd ldr r4, [sp, #20] @Load src_strd2 ldr r5, [sp, #24] @Load dst_strd cmp r7, #8 ldr r6, [sp, #28] @Load ht vpush {d8-d15} beq loop_8_uv @branch if wd is 8 cmp r7, #4 beq loop_4_uv @branch if wd is 4 loop_2_uv: @each iteration processes two rows vld1.32 d0[0], [r0], r3 @load row 1 in source 1 vld1.32 d0[1], [r0], r3 @load row 2 in source 1 vld1.32 d1[0], [r1], r4 @load row 1 in source 2 vld1.32 d1[1], [r1], r4 @load row 2 in source 2 vrhadd.u8 d0, d0, d1 subs r6, r6, #2 @decrement ht by 2 vst1.32 d0[0], [r2], r5 @load row 1 in destination vst1.32 d0[1], [r2], r5 @load row 2 in destination bgt loop_2_uv @if greater than 0 repeat the loop again b end_loops_uv loop_4_uv: @each iteration processes two rows vld1.8 d0, [r0], r3 @load row 1 in source 1 vld1.8 d2, [r1], r4 @load row 1 in source 2 vld1.8 d1, [r0], r3 @load row 2 in source 1 vrhadd.u8 d0, d0, d2 vld1.8 d3, [r1], r4 @load row 2 in source 2 vrhadd.u8 d1, d1, d3 vst1.8 d0, [r2], r5 @load row 1 in destination subs r6, r6, #2 @decrement ht by 2 vst1.8 d1, [r2], r5 @load row 2 in destination bgt loop_4_uv @if greater than 0 repeat the loop again b end_loops_uv loop_8_uv: @each iteration processes four rows vld1.8 {q0}, [r0], r3 @load row 1 in source 1 vld1.8 {q4}, [r1], r4 @load row 1 in source 2 vld1.8 {q1}, [r0], r3 @load row 2 in source 1 vrhadd.u8 q0, q0, q4 vld1.8 {q5}, [r1], r4 @load row 2 in source 2 vld1.8 {q2}, [r0], r3 @load row 3 in source 1 vrhadd.u8 q1, q1, q5 vld1.8 {q6}, [r1], r4 @load row 3 in source 2 vld1.8 {q3}, [r0], r3 @load row 4 in source 1 vrhadd.u8 q2, q2, q6 vld1.8 {q7}, [r1], r4 @load row 4 in source 2 vst1.8 {q0}, [r2], r5 @load row 1 in destination vrhadd.u8 q3, q3, q7 vst1.8 {q1}, [r2], r5 @load row 2 in destination subs r6, r6, #4 @decrement ht by 4 vst1.8 {q2}, [r2], r5 @load row 3 in destination vst1.8 {q3}, [r2], r5 @load row 4 in destination bgt loop_8_uv @if greater than 0 repeat the loop again end_loops_uv: vpop {d8-d15} ldmfd sp!, {r4-r7, r15} @Reload the registers from sp ================================================ FILE: dependencies/ih264d/common/arm/ih264_ihadamard_scaling_a9.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @ ******************************************************************************* @ * @file @ * ih264_ihadamard_scaling_a9.s @ * @ * @brief @ * Contains function definitions for inverse hadamard transform on 4x4 DC outputs @ * of 16x16 intra-prediction @ * @ * @author @ * Mohit @ * @ * @par List of Functions: @ * - ih264_ihadamard_scaling_4x4_a9() @ * - ih264_ihadamard_scaling_2x2_uv_a9() @ * @ * @remarks @ * None @ * @ ******************************************************************************* @ * @ * @brief This function performs a 4x4 inverse hadamard transform on the 4x4 DC coefficients @ * of a 16x16 intra prediction macroblock, and then performs scaling. @ * prediction buffer @ * @ * @par Description: @ * The DC coefficients pass through a 2-stage inverse hadamard transform. @ * This inverse transformed content is scaled to based on Qp value. @ * @ * @param[in] pi2_src @ * input 4x4 block of DC coefficients @ * @ * @param[out] pi2_out @ * output 4x4 block @ * @ * @param[in] pu2_iscal_mat @ * pointer to scaling list @ * @ * @param[in] pu2_weigh_mat @ * pointer to weight matrix @ * @ * @param[in] u4_qp_div_6 @ * Floor (qp/6) @ * @ * @param[in] pi4_tmp @ * temporary buffer of size 1*16 @ * @ * @returns none @ * @ * @remarks none @ * @ ******************************************************************************* @ * @ * @ ******************************************************************************* @ * @ void ih264_ihadamard_scaling_4x4(WORD16* pi2_src, @ WORD16* pi2_out, @ const UWORD16 *pu2_iscal_mat, @ const UWORD16 *pu2_weigh_mat, @ UWORD32 u4_qp_div_6, @ WORD32* pi4_tmp) @**************Variables Vs Registers***************************************** @r0 => *pi2_src @r1 => *pi2_out @r2 => *pu2_iscal_mat @r3 => *pu2_weigh_mat @r4 => u4_qp_div_6 .text .p2align 2 .global ih264_ihadamard_scaling_4x4_a9 ih264_ihadamard_scaling_4x4_a9: @VLD4.S16 is used because the pointer is incremented by SUB_BLK_WIDTH_4x4 @If the macro value changes need to change the instruction according to it. @Only one shift is done in horizontal inverse because, @if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value @if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 stmfd sp!, {r4-r12, r14} @ stack stores the values of the arguments ldr r4, [sp, #40] @ Loads u4_qp_div_6 vdup.s32 q10, r4 @ Populate the u4_qp_div_6 in Q10 ldrh r6, [r3] @ load pu2_weight_mat[0] , H for unsigned halfword load ldrh r7, [r2] @ load pu2_iscal_mat[0] , H for unsigned halfword load mul r6, r6, r7 @ pu2_iscal_mat[0]*pu2_weigh_mat[0] vdup.s32 q9, r6 @ Populate pu2_iscal_mat[0]*pu2_weigh_mat[0] 32-bit in Q9 vpush {d8-d15} @=======================INVERSE HADAMARD TRANSFORM================================ vld4.s16 {d0, d1, d2, d3}, [r0] @load x4,x5,x6,x7 vaddl.s16 q12, d0, d3 @x0 = x4 + x7 vaddl.s16 q13, d1, d2 @x1 = x5 + x6 vsubl.s16 q14, d1, d2 @x2 = x5 - x6 vsubl.s16 q15, d0, d3 @x3 = x4 - x7 vadd.s32 q2, q12, q13 @pi4_tmp_ptr[0] = x0 + x1 vadd.s32 q3, q15, q14 @pi4_tmp_ptr[1] = x3 + x2 vsub.s32 q4, q12, q13 @pi4_tmp_ptr[2] = x0 - x1 vsub.s32 q5, q15, q14 @pi4_tmp_ptr[3] = x3 - x2 vtrn.32 q2, q3 @Transpose the register for vertical transform vtrn.32 q4, q5 vswp d5, d8 @Q2 = x4, Q4 = x6 vswp d7, d10 @Q3 = x5, Q5 = x7 vadd.s32 q12, q2, q5 @x0 = x4+x7 vadd.s32 q13, q3, q4 @x1 = x5+x6 vsub.s32 q14, q3, q4 @x2 = x5-x6 vsub.s32 q15, q2, q5 @x3 = x4-x7 vadd.s32 q0, q12, q13 @pi4_tmp_ptr[0] = x0 + x1 vadd.s32 q1, q15, q14 @pi4_tmp_ptr[1] = x3 + x2 vsub.s32 q2, q12, q13 @pi4_tmp_ptr[2] = x0 - x1 vsub.s32 q3, q15, q14 @pi4_tmp_ptr[3] = x3 - x2 vmul.s32 q0, q0, q9 @ Q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 vmul.s32 q1, q1, q9 @ Q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 vmul.s32 q2, q2, q9 @ Q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 vmul.s32 q3, q3, q9 @ Q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 vshl.s32 q0, q0, q10 @ Q0 = q[i] = (p[i] << (qP/6)) where i = 0..3 vshl.s32 q1, q1, q10 @ Q1 = q[i] = (p[i] << (qP/6)) where i = 4..7 vshl.s32 q2, q2, q10 @ Q2 = q[i] = (p[i] << (qP/6)) where i = 8..11 vshl.s32 q3, q3, q10 @ Q3 = q[i] = (p[i] << (qP/6)) where i = 12..15 vqrshrn.s32 d0, q0, #0x6 @ D0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 vqrshrn.s32 d1, q1, #0x6 @ D1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 vqrshrn.s32 d2, q2, #0x6 @ D2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 vqrshrn.s32 d3, q3, #0x6 @ D3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 vst1.s16 {d0, d1, d2, d3}, [r1] @IV row store the value vpop {d8-d15} ldmfd sp!, {r4-r12, r15} @Reload the registers from SP @ ******************************************************************************* @ * @ * @brief This function performs a 2x2 inverse hadamard transform for chroma block @ * @ * @par Description: @ * The DC coefficients pass through a 2-stage inverse hadamard transform. @ * This inverse transformed content is scaled to based on Qp value. @ * Both DC blocks of U and v blocks are processesd @ * @ * @param[in] pi2_src @ * input 1x8 block of ceffs. First 4 are from U and next from V @ * @ * @param[out] pi2_out @ * output 1x8 block @ * @ * @param[in] pu2_iscal_mat @ * pointer to scaling list @ * @ * @param[in] pu2_weigh_mat @ * pointer to weight matrix @ * @ * @param[in] u4_qp_div_6 @ * Floor (qp/6) @ * @ * @returns none @ * @ * @remarks none @ * @ ******************************************************************************* @ * @ * @ ******************************************************************************* @ * @ void ih264_ihadamard_scaling_2x2_uv(WORD16* pi2_src, @ WORD16* pi2_out, @ const UWORD16 *pu2_iscal_mat, @ const UWORD16 *pu2_weigh_mat, @ UWORD32 u4_qp_div_6, .global ih264_ihadamard_scaling_2x2_uv_a9 ih264_ihadamard_scaling_2x2_uv_a9: @Registers used @ r0 : *pi2_src @ r1 : *pi2_out @ r2 : *pu2_iscal_mat @ r3 : *pu2_weigh_mat vld1.u16 d26[0], [r2] vld1.u16 d27[0], [r3] vmull.u16 q15, d26, d27 @pu2_iscal_mat[0] * pu2_weigh_mat[0] vdup.u32 q15, d30[0] vld1.u16 d28[0], [sp] @load qp/6 vpush {d8-d15} vmov.u16 d29, #5 vsubl.u16 q14, d28, d29 @qp\6 - 5 vdup.s32 q14, d28[0] vld2.s16 {d0, d1}, [r0] @load 8 dc coeffs @i2_x4,i2_x6,i2_y4,i1_y6 -> d0 @i2_x5,i2_x7,i2_y5,i1_y6 -> d1 vaddl.s16 q1, d0, d1 @ i4_x0 = i4_x4 + i4_x5;...x2 vsubl.s16 q2, d0, d1 @ i4_x1 = i4_x4 - i4_x5;...x3 vtrn.s32 q1, q2 @i4_x0 i4_x1 -> q1 vadd.s32 q3, q1, q2 @i4_x4 = i4_x0+i4_x2;.. i4_x5 vsub.s32 q1, q1, q2 @i4_x6 = i4_x0-i4_x2;.. i4_x7 vmul.s32 q5, q3, q15 vmul.s32 q6, q1, q15 vshl.s32 q7, q5, q14 vshl.s32 q8, q6, q14 vmovn.s32 d18, q7 @i4_x4 i4_x5 i4_y4 i4_y5 vmovn.s32 d19, q8 @i4_x6 i4_x7 i4_y6 i4_y7 vst2.s32 {d18-d19}, [r1] vpop {d8-d15} bx lr ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_chroma_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_inter_pred_chroma_a9q.s @* @* @brief @* Contains function definitions for inter prediction interpolation. @* @* @author @* Ittaim @* @* @par List of Functions: @* @* - ih264_inter_pred_chroma_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_inter_pred_filters.c @ @** @** @** @ @** @******************************************************************************* @* @* @brief @* Interprediction chroma filter @* @* @par Description: @* Applies filtering to chroma samples as mentioned in @* sec 8.4.2.2.2 titled "chroma sample interpolation process" @* @* @param[in] pu1_src @* UWORD8 pointer to the source containing alternate U and V samples @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in]uc_dx @* dx value where the sample is to be produced(refer sec 8.4.2.2.2 ) @* @* @param[in] uc_dy @* dy value where the sample is to be produced(refer sec 8.4.2.2.2 ) @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @void ih264_inter_pred_chroma(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 u1_dx, @ WORD32 u1_dy, @ WORD32 ht, @ WORD32 wd) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => u1_dx @ r5 => u1_dy @ r6 => height @ r7 => width @ .text .p2align 2 .global ih264_inter_pred_chroma_a9q ih264_inter_pred_chroma_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vstmdb sp!, {d8-d15} @push neon registers to stack ldr r4, [sp, #104] ldr r5, [sp, #108] ldr r6, [sp, #112] ldr r7, [sp, #116] rsb r8, r4, #8 @8-u1_dx rsb r9, r5, #8 @8-u1_dy mul r10, r8, r9 mul r11, r4, r9 vdup.u8 d28, r10 vdup.u8 d29, r11 mul r10, r8, r5 mul r11, r4, r5 vdup.u8 d30, r10 vdup.u8 d31, r11 subs r12, r7, #2 @if wd=4 branch to loop_4 beq loop_2 subs r12, r7, #4 @if wd=8 branch to loop_8 beq loop_4 loop_8: sub r6, #1 vld1.8 {d0, d1, d2}, [r0], r2 @ Load row0 vld1.8 {d5, d6, d7}, [r0], r2 @ Load row1 vext.8 d3, d0, d1, #2 vext.8 d8, d5, d6, #2 vmull.u8 q5, d0, d28 vmlal.u8 q5, d5, d30 vmlal.u8 q5, d3, d29 vmlal.u8 q5, d8, d31 vext.8 d9, d6, d7, #2 vext.8 d4, d1, d2, #2 inner_loop_8: vmull.u8 q6, d6, d30 vmlal.u8 q6, d1, d28 vmlal.u8 q6, d9, d31 vmlal.u8 q6, d4, d29 vmov d0, d5 vmov d3, d8 vqrshrun.s16 d14, q5, #6 vmov d1, d6 vmov d4, d9 vld1.8 {d5, d6, d7}, [r0], r2 @ Load row1 vqrshrun.s16 d15, q6, #6 vext.8 d8, d5, d6, #2 subs r6, #1 vext.8 d9, d6, d7, #2 vst1.8 {q7}, [r1], r3 @ Store dest row vmull.u8 q5, d0, d28 vmlal.u8 q5, d5, d30 vmlal.u8 q5, d3, d29 vmlal.u8 q5, d8, d31 bne inner_loop_8 vmull.u8 q6, d6, d30 vmlal.u8 q6, d1, d28 vmlal.u8 q6, d9, d31 vmlal.u8 q6, d4, d29 vqrshrun.s16 d14, q5, #6 vqrshrun.s16 d15, q6, #6 vst1.8 {q7}, [r1], r3 @ Store dest row b end_func loop_4: sub r6, #1 vld1.8 {d0, d1}, [r0], r2 @ Load row0 vld1.8 {d2, d3}, [r0], r2 @ Load row1 vext.8 d1, d0, d1, #2 vext.8 d3, d2, d3, #2 vmull.u8 q2, d2, d30 vmlal.u8 q2, d0, d28 vmlal.u8 q2, d3, d31 vmlal.u8 q2, d1, d29 inner_loop_4: subs r6, #1 vmov d0, d2 vmov d1, d3 vld1.8 {d2, d3}, [r0], r2 @ Load row1 vqrshrun.s16 d6, q2, #6 vext.8 d3, d2, d3, #2 vst1.8 {d6}, [r1], r3 @ Store dest row vmull.u8 q2, d0, d28 vmlal.u8 q2, d2, d30 vmlal.u8 q2, d1, d29 vmlal.u8 q2, d3, d31 bne inner_loop_4 vqrshrun.s16 d6, q2, #6 vst1.8 {d6}, [r1], r3 @ Store dest row b end_func loop_2: vld1.8 {d0}, [r0], r2 @ Load row0 vext.8 d1, d0, d0, #2 vld1.8 {d2}, [r0], r2 @ Load row1 vext.8 d3, d2, d2, #2 vmull.u8 q2, d0, d28 vmlal.u8 q2, d1, d29 vmlal.u8 q2, d2, d30 vmlal.u8 q2, d3, d31 vld1.8 {d6}, [r0] @ Load row2 vqrshrun.s16 d4, q2, #6 vext.8 d7, d6, d6, #2 vst1.32 d4[0], [r1], r3 @ Store dest row0 vmull.u8 q4, d2, d28 vmlal.u8 q4, d3, d29 vmlal.u8 q4, d6, d30 vmlal.u8 q4, d7, d31 subs r6, #2 vqrshrun.s16 d8, q4, #6 vst1.32 d8[0], [r1], r3 @ Store dest row1 bne loop_2 @ repeat if ht=2 end_func: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, pc} @ Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_filters_luma_horz_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_inter_pred_luma_horz_a9q.s @* @* @brief @* Contains function definitions for inter prediction interpolation. @* @* @author @* Ittiam @* @* @par List of Functions: @* @* - ih264_inter_pred_luma_horz_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_inter_pred_filters.c @ @** @** @******************************************************************************* @* @* @brief @* Interprediction luma filter for horizontal input @* @* @par Description: @* Applies a 6 tap horizontal filter .The output is clipped to 8 bits @* sec 8.4.2.2.1 titled "Luma sample interpolation process" @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* @ @remarks @* None @* @******************************************************************************* @* @void ih264_inter_pred_luma_horz ( @ UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd ) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r5 => ht @ r6 => wd .text .p2align 2 .global ih264_inter_pred_luma_horz_a9q ih264_inter_pred_luma_horz_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vstmdb sp!, {d8-d15} @push neon registers to stack ldr r5, [sp, #104] @Loads ht sub r0, r0, #2 @pu1_src-2 ldr r6, [sp, #108] @Loads wd vmov.i8 d0, #5 @filter coeff subs r12, r6, #8 @if wd=8 branch to loop_8 vmov.i8 d1, #20 @filter coeff beq loop_8 subs r12, r6, #4 @if wd=4 branch to loop_4 beq loop_4 loop_16: @when wd=16 @ Processing row0 and row1 vld1.8 {d2, d3, d4}, [r0], r2 @// Load row0 ;for checking loop vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) vld1.8 {d5, d6, d7}, [r0], r2 @// Load row1 vext.8 d30, d3, d4, #5 @//extract a[5] (column2,row0) vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) vaddl.u8 q5, d30, d3 @// a0 + a5 (column2,row0) vext.8 d27, d6, d7, #5 @//extract a[5] (column2,row1) vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) vext.8 d31, d2, d3, #2 @//extract a[2] (column1,row0) vaddl.u8 q8, d27, d6 @// a0 + a5 (column2,row1) vext.8 d30, d3, d4, #2 @//extract a[2] (column2,row0) vmlal.u8 q4, d31, d1 @// a0 + a5 + 20a2 (column1,row0) vext.8 d28, d5, d6, #2 @//extract a[2] (column1,row1) vmlal.u8 q5, d30, d1 @// a0 + a5 + 20a2 (column2,row0) vext.8 d27, d6, d7, #2 @//extract a[2] (column2,row1) vmlal.u8 q7, d28, d1 @// a0 + a5 + 20a2 (column1,row1) vext.8 d31, d2, d3, #3 @//extract a[3] (column1,row0) vmlal.u8 q8, d27, d1 @// a0 + a5 + 20a2 (column2,row1) vext.8 d30, d3, d4, #3 @//extract a[3] (column2,row0) vmlal.u8 q4, d31, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) vext.8 d28, d5, d6, #3 @//extract a[3] (column1,row1) vmlal.u8 q5, d30, d1 @// a0 + a5 + 20a2 + 20a3 (column2,row0) vext.8 d27, d6, d7, #3 @//extract a[3] (column2,row1) vmlal.u8 q7, d28, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) vext.8 d31, d2, d3, #1 @//extract a[1] (column1,row0) vmlal.u8 q8, d27, d1 @// a0 + a5 + 20a2 + 20a3 (column2,row1) vext.8 d30, d3, d4, #1 @//extract a[1] (column2,row0) vmlsl.u8 q4, d31, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) vext.8 d28, d5, d6, #1 @//extract a[1] (column1,row1) vmlsl.u8 q5, d30, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row0) vext.8 d27, d6, d7, #1 @//extract a[1] (column2,row1) vmlsl.u8 q7, d28, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) vext.8 d31, d2, d3, #4 @//extract a[4] (column1,row0) vmlsl.u8 q8, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row1) vext.8 d30, d3, d4, #4 @//extract a[4] (column2,row0) vmlsl.u8 q4, d31, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) vext.8 d28, d5, d6, #4 @//extract a[4] (column1,row1) vmlsl.u8 q5, d30, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row0) vext.8 d27, d6, d7, #4 @//extract a[4] (column2,row1) vmlsl.u8 q7, d28, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) vmlsl.u8 q8, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row1) vqrshrun.s16 d20, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) vqrshrun.s16 d21, q5, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row0) vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row2) vst1.8 {d20, d21}, [r1], r3 @//Store dest row0 vqrshrun.s16 d23, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) vext.8 d30, d3, d4, #5 @//extract a[5] (column2,row2) vqrshrun.s16 d24, q8, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row1) vst1.8 {d23, d24}, [r1], r3 @//Store dest row1 subs r5, r5, #2 @ 2 rows done, decrement by 2 beq end_func b loop_16 @ loop if height == 8 or 16 loop_8: @ Processing row0 and row1 vld1.8 {d5, d6}, [r0], r2 @// Load row1 vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) vld1.8 {d2, d3}, [r0], r2 @// Load row0 vext.8 d25, d5, d6, #2 @//extract a[2] (column1,row1) vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) vext.8 d24, d5, d6, #3 @//extract a[3] (column1,row1) vext.8 d23, d5, d6, #1 @//extract a[1] (column1,row1) vext.8 d22, d5, d6, #4 @//extract a[4] (column1,row1) vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) vext.8 d29, d2, d3, #3 @//extract a[3] (column1,row0) vmlal.u8 q7, d25, d1 @// a0 + a5 + 20a2 (column1,row1) vmlal.u8 q7, d24, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) vmlsl.u8 q7, d23, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) vmlsl.u8 q7, d22, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) vext.8 d30, d2, d3, #2 @//extract a[2] (column1,row0) vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) vext.8 d27, d2, d3, #1 @//extract a[1] (column1,row0) vext.8 d26, d2, d3, #4 @//extract a[4] (column1,row0) vmlal.u8 q4, d29, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) vmlal.u8 q4, d30, d1 @// a0 + a5 + 20a2 (column1,row0) vmlsl.u8 q4, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) vmlsl.u8 q4, d26, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) vqrshrun.s16 d23, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) vst1.8 {d23}, [r1], r3 @//Store dest row0 vqrshrun.s16 d20, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) vst1.8 {d20}, [r1], r3 @//Store dest row1 subs r5, r5, #2 @ 2 rows done, decrement by 2 beq end_func @ Branch if height==4 b loop_8 @looping if height =8 or 16 loop_4: vld1.8 {d5, d6}, [r0], r2 @// Load row1 vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) vld1.8 {d2, d3}, [r0], r2 @// Load row0 vext.8 d25, d5, d6, #2 @//extract a[2] (column1,row1) vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) vext.8 d24, d5, d6, #3 @//extract a[3] (column1,row1) vext.8 d23, d5, d6, #1 @//extract a[1] (column1,row1) vext.8 d22, d5, d6, #4 @//extract a[4] (column1,row1) vext.8 d29, d2, d3, #3 @//extract a[3] (column1,row0) vmlal.u8 q7, d25, d1 @// a0 + a5 + 20a2 (column1,row1) vmlal.u8 q7, d24, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) vmlsl.u8 q7, d23, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) vmlsl.u8 q7, d22, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) vext.8 d30, d2, d3, #2 @//extract a[2] (column1,row0) vext.8 d27, d2, d3, #1 @//extract a[1] (column1,row0) vext.8 d26, d2, d3, #4 @//extract a[4] (column1,row0) vmlal.u8 q4, d29, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) vmlal.u8 q4, d30, d1 @// a0 + a5 + 20a2 (column1,row0) vmlsl.u8 q4, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) vmlsl.u8 q4, d26, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) vqrshrun.s16 d23, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) vst1.32 d23[0], [r1], r3 @//Store dest row0 vqrshrun.s16 d20, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) vst1.32 d20[0], [r1], r3 @//Store dest row1 subs r5, r5, #2 @ 2 rows done, decrement by 2 beq end_func b loop_4 end_func: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_filters_luma_vert_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_inter_pred_luma_vert_a9q.s @* @* @brief @* Contains function definitions for inter prediction interpolation. @* @* @author @* Ittiam @* @* @par List of Functions: @* @* - ih264_inter_pred_luma_vert_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_inter_pred_filters.c @ @** @** @** @ ******************************************************************************* @ * @ * @brief @ * Interprediction luma filter for vertical input @ * @ * @par Description: @ * Applies a 6 tap vertcal filter.The output is clipped to 8 bits @ * sec 8.4.2.2.1 titled "Luma sample interpolation process" @ * @ * @param[in] pu1_src @ * UWORD8 pointer to the source @ * @ * @param[out] pu1_dst @ * UWORD8 pointer to the destination @ * @ * @param[in] src_strd @ * integer source stride @ * @ * @param[in] dst_strd @ * integer destination stride @ * @ * @param[in] ht @ * integer height of the array @ * @ * @param[in] wd @ * integer width of the array @ * @ * @returns @ * @ * @remarks @ * None @ * @ ******************************************************************************* @void ih264_inter_pred_luma_vert ( @ UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd ) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r5 => ht @ r6 => wd .text .p2align 2 .global ih264_inter_pred_luma_vert_a9q ih264_inter_pred_luma_vert_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vstmdb sp!, {d8-d15} @push neon registers to stack ldr r5, [sp, #104] @Loads ht sub r0, r0, r2, lsl #1 @pu1_src-2*src_strd ldr r6, [sp, #108] @Loads wd vmov.u16 q11, #20 @ Filter coeff 0x14 into Q11 subs r12, r6, #8 @if wd=8 branch to loop_8 vmov.u16 q12, #5 @ Filter coeff 0x5 into Q12 beq loop_8 subs r12, r6, #4 @if wd=4 branch to loop_4 beq loop_4 loop_16: @when wd=16 vld1.u32 {q0}, [r0], r2 @ Vector load from src[0_0] vld1.u32 {q1}, [r0], r2 @ Vector load from src[1_0] vld1.u32 {q2}, [r0], r2 @ Vector load from src[2_0] vld1.u32 {q3}, [r0], r2 @ Vector load from src[3_0] vld1.u32 {q4}, [r0], r2 @ Vector load from src[4_0] vaddl.u8 q6, d4, d6 @ temp1 = src[2_0] + src[3_0] vld1.u32 {q5}, [r0], r2 @ Vector load from src[5_0] vaddl.u8 q7, d0, d10 @ temp = src[0_0] + src[5_0] vaddl.u8 q8, d2, d8 @ temp2 = src[1_0] + src[4_0] vmla.u16 q7, q6, q11 @ temp += temp1 * 20 vaddl.u8 q10, d1, d11 @ temp4 = src[0_8] + src[5_8] vaddl.u8 q9, d5, d7 @ temp3 = src[2_8] + src[3_8] vmla.u16 q10, q9, q11 @ temp4 += temp3 * 20 vld1.u32 {q0}, [r0], r2 vaddl.u8 q13, d3, d9 @ temp5 = src[1_8] + src[4_8] vaddl.u8 q6, d6, d8 vmls.u16 q7, q8, q12 @ temp -= temp2 * 5 vaddl.u8 q8, d2, d0 vaddl.u8 q9, d4, d10 vmla.u16 q8, q6, q11 vmls.u16 q10, q13, q12 @ temp4 -= temp5 * 5 vaddl.u8 q13, d5, d11 vaddl.u8 q6, d7, d9 vqrshrun.s16 d30, q7, #5 @ dst[0_0] = CLIP_U8((temp +16) >> 5) vaddl.u8 q7, d3, d1 vld1.u32 {q1}, [r0], r2 vmla.u16 q7, q6, q11 vmls.u16 q8, q9, q12 vqrshrun.s16 d31, q10, #5 @ dst[0_8] = CLIP_U8((temp4 +16) >> 5) vaddl.u8 q9, d4, d2 vaddl.u8 q6, d8, d10 vst1.u32 {q15}, [r1], r3 @ Vector store to dst[0_0] vmla.u16 q9, q6, q11 vaddl.u8 q10, d6, d0 vmls.u16 q7, q13, q12 vqrshrun.s16 d30, q8, #5 vaddl.u8 q6, d9, d11 vaddl.u8 q8, d5, d3 vaddl.u8 q13, d7, d1 vmla.u16 q8, q6, q11 vmls.u16 q9, q10, q12 vld1.u32 {q2}, [r0], r2 vqrshrun.s16 d31, q7, #5 vaddl.u8 q6, d10, d0 vaddl.u8 q7, d6, d4 vaddl.u8 q10, d8, d2 vmla.u16 q7, q6, q11 vmls.u16 q8, q13, q12 vst1.u32 {q15}, [r1], r3 @store row 1 vqrshrun.s16 d30, q9, #5 vaddl.u8 q9, d7, d5 vaddl.u8 q6, d11, d1 vmla.u16 q9, q6, q11 vaddl.u8 q13, d9, d3 vmls.u16 q7, q10, q12 vqrshrun.s16 d31, q8, #5 vmls.u16 q9, q13, q12 vaddl.u8 q6, d0, d2 @ temp1 = src[2_0] + src[3_0] vst1.u32 {q15}, [r1], r3 @store row 2 vaddl.u8 q8, d10, d4 @ temp2 = src[1_0] + src[4_0] vaddl.u8 q10, d9, d7 @ temp4 = src[0_8] + src[5_8] vqrshrun.s16 d30, q7, #5 vaddl.u8 q13, d5, d11 @ temp5 = src[1_8] + src[4_8] vaddl.u8 q7, d8, d6 @ temp = src[0_0] + src[5_0] vqrshrun.s16 d31, q9, #5 vmla.u16 q7, q6, q11 @ temp += temp1 * 20 vaddl.u8 q9, d1, d3 @ temp3 = src[2_8] + src[3_8] vst1.u32 {q15}, [r1], r3 @store row 3 subs r5, r5, #4 @ 4 rows processed, decrement by 4 subne r0, r0 , r2, lsl #2 subne r0, r0, r2 beq end_func @ Branch if height==4 b loop_16 @ looping if height = 8 or 16 loop_8: @ Processing row0 and row1 vld1.u32 d0, [r0], r2 @ Vector load from src[0_0] vld1.u32 d1, [r0], r2 @ Vector load from src[1_0] vld1.u32 d2, [r0], r2 @ Vector load from src[2_0] vld1.u32 d3, [r0], r2 @ Vector load from src[3_0] vld1.u32 d4, [r0], r2 @ Vector load from src[4_0] vld1.u32 d5, [r0], r2 @ Vector load from src[5_0] vaddl.u8 q3, d2, d3 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q4, d0, d5 @ temp = src[0_0] + src[5_0] vaddl.u8 q5, d1, d4 @ temp2 = src[1_0] + src[4_0] vmla.u16 q4, q3, q11 @ temp += temp1 * 20 vld1.u32 d6, [r0], r2 vaddl.u8 q7, d3, d4 vaddl.u8 q8, d1, d6 vaddl.u8 q9, d2, d5 vmls.u16 q4, q5, q12 @ temp -= temp2 * 5 vmla.u16 q8, q7, q11 vld1.u32 d7, [r0], r2 vaddl.u8 q10, d4, d5 vaddl.u8 q6, d2, d7 vaddl.u8 q5, d3, d6 vmls.u16 q8, q9, q12 vqrshrun.s16 d26, q4, #5 @ dst[0_0] = CLIP_U8( (temp + 16) >> 5) vmla.u16 q6, q10, q11 vld1.u32 d0, [r0], r2 vaddl.u8 q7, d5, d6 vqrshrun.s16 d27, q8, #5 vaddl.u8 q10, d3, d0 vmls.u16 q6, q5, q12 vst1.u32 d26, [r1], r3 @ Vector store to dst[0_0] vaddl.u8 q9, d4, d7 vmla.u16 q10, q7, q11 vst1.u32 d27, [r1], r3 vqrshrun.s16 d28, q6, #5 vst1.u32 d28, [r1], r3 vmls.u16 q10, q9, q12 vqrshrun.s16 d29, q10, #5 vst1.u32 d29, [r1], r3 @store row 3 subs r5, r5, #4 @ 4 rows processed, decrement by 4 subne r0, r0 , r2, lsl #2 subne r0, r0, r2 beq end_func @ Branch if height==4 b loop_8 @looping if height == 8 or 16 loop_4: @ Processing row0 and row1 vld1.u32 d0[0], [r0], r2 @ Vector load from src[0_0] vld1.u32 d1[0], [r0], r2 @ Vector load from src[1_0] vld1.u32 d2[0], [r0], r2 @ Vector load from src[2_0] vld1.u32 d3[0], [r0], r2 @ Vector load from src[3_0] vld1.u32 d4[0], [r0], r2 @ Vector load from src[4_0] vld1.u32 d5[0], [r0], r2 @ Vector load from src[5_0] vaddl.u8 q3, d2, d3 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q4, d0, d5 @ temp = src[0_0] + src[5_0] vaddl.u8 q5, d1, d4 @ temp2 = src[1_0] + src[4_0] vmla.u16 q4, q3, q11 @ temp += temp1 * 20 vld1.u32 d6[0], [r0], r2 vaddl.u8 q7, d3, d4 vaddl.u8 q8, d1, d6 vaddl.u8 q9, d2, d5 vmls.u16 q4, q5, q12 @ temp -= temp2 * 5 vld1.u32 d7[0], [r0], r2 vmla.u16 q8, q7, q11 vaddl.u8 q10, d4, d5 vaddl.u8 q6, d2, d7 vaddl.u8 q5, d3, d6 vmls.u16 q8, q9, q12 vqrshrun.s16 d26, q4, #5 @ dst[0_0] = CLIP_U8( (temp + 16) >> 5) vmla.u16 q6, q10, q11 vld1.u32 d0[0], [r0], r2 vaddl.u8 q7, d5, d6 vqrshrun.s16 d27, q8, #5 vaddl.u8 q10, d3, d0 vmls.u16 q6, q5, q12 vst1.u32 d26[0], [r1], r3 @ Vector store to dst[0_0] vaddl.u8 q9, d4, d7 vmla.u16 q10, q7, q11 vst1.u32 d27[0], [r1], r3 vqrshrun.s16 d28, q6, #5 vst1.u32 d28[0], [r1], r3 vmls.u16 q10, q9, q12 vqrshrun.s16 d29, q10, #5 vst1.u32 d29[0], [r1], r3 @store row 3 subs r5, r5, #8 subeq r0, r0, r2, lsl #2 subeq r0, r0, r2 beq loop_4 @ Loop if height==8 end_func: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_luma_bilinear_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_inter_pred_luma_bilinear_a9q.s @* @* @brief @* Contains function definitions for inter prediction interpolation. @* @* @author @* Ittiam @* @* @par List of Functions: @* @* - ih264_inter_pred_luma_bilinear_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_inter_pred_filters.c @ @** @** @** @ ******************************************************************************* @ * function:ih264_inter_pred_luma_bilinear @ * @* @brief @* This routine applies the bilinear filter to the predictors . @* The filtering operation is described in @* sec 8.4.2.2.1 titled "Luma sample interpolation process" @* @* @par Description: @\note @* This function is called to obtain pixels lying at the following @* locations (1/4,1), (3/4,1),(1,1/4), (1,3/4) ,(1/4,1/2), (3/4,1/2),(1/2,1/4), (1/2,3/4),(3/4,1/4),(1/4,3/4),(3/4,3/4)&& (1/4,1/4) . @* The function averages the two adjacent values from the two input arrays in horizontal direction. @* @* @* @param[in] pu1_src1: @* UWORD8 Pointer to the buffer containing the first input array. @* @* @param[in] pu1_src2: @* UWORD8 Pointer to the buffer containing the second input array. @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination where the output of bilinear filter is stored. @* @* @param[in] src_strd1 @* Stride of the first input buffer @* @* @param[in] src_strd2 @* Stride of the second input buffer @* @* @param[in] dst_strd @* integer destination stride of pu1_dst @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @void ih264_inter_pred_luma_bilinear(UWORD8 *pu1_src1, @ UWORD8 *pu1_src2, @ UWORD8 *pu1_dst, @ WORD32 src_strd1, @ WORD32 src_strd2, @ WORD32 dst_strd, @ WORD32 height, @ WORD32 width) @ @**************Variables Vs Registers***************************************** @ r0 => *pu1_src1 @ r1 => *pu1_src2 @ r2 => *pu1_dst @ r3 => src_strd1 @ r4 => src_strd2 @ r5 => dst_strd @ r6 => height @ r7 => width @ .text .p2align 2 .global ih264_inter_pred_luma_bilinear_a9q ih264_inter_pred_luma_bilinear_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vstmdb sp!, {d8-d15} @push neon registers to stack ldr r4, [sp, #104] ldr r5, [sp, #108] @ ldr r6, [sp, #112] ldr r7, [sp, #116] subs r12, r7, #4 @if wd=4 branch to loop_4 beq loop_4 subs r12, r7, #8 @if wd=8 branch to loop_8 beq loop_8 loop_16: @when wd=16 vld1.8 {q0}, [r0], r3 @// Load row0 ;src1 vld1.8 {q2}, [r1], r4 @// Load row0 ;src2 vld1.8 {q1}, [r0], r3 @// Load row1 ;src1 vaddl.u8 q10, d0, d4 vld1.8 {q3}, [r1], r4 @// Load row1 ;src2 vaddl.u8 q11, d1, d5 vld1.8 {q4}, [r0], r3 @// Load row2 ;src1 vaddl.u8 q12, d2, d6 vld1.8 {q5}, [r0], r3 @// Load row3 ;src1 vaddl.u8 q13, d3, d7 vld1.8 {q6}, [r1], r4 @// Load row2 ;src2 vaddl.u8 q8, d8, d12 vld1.8 {q7}, [r1], r4 @// Load row3 ;src2 vaddl.u8 q9, d9, d13 vqrshrun.s16 d28, q10, #1 vqrshrun.s16 d29, q11, #1 vaddl.u8 q10, d10, d14 vqrshrun.s16 d30, q12, #1 vqrshrun.s16 d31, q13, #1 vst1.8 {q14}, [r2], r5 @//Store dest row0 vaddl.u8 q11, d11, d15 vst1.8 {q15}, [r2], r5 @//Store dest row1 vqrshrun.s16 d28, q8, #1 vld1.8 {q0}, [r0], r3 @// Load row4 ;src1 vqrshrun.s16 d29, q9, #1 vld1.8 {q1}, [r0], r3 @// Load row5 ;src1 vqrshrun.s16 d30, q10, #1 vld1.8 {q2}, [r1], r4 @// Load row4 ;src2 vqrshrun.s16 d31, q11, #1 vld1.8 {q3}, [r1], r4 @// Load row5 ;src2 vaddl.u8 q10, d0, d4 vst1.8 {q14}, [r2], r5 @//Store dest row2 vaddl.u8 q13, d3, d7 vst1.8 {q15}, [r2], r5 @//Store dest row3 vaddl.u8 q11, d1, d5 vld1.8 {q4}, [r0], r3 @// Load row6 ;src1 vaddl.u8 q12, d2, d6 vld1.8 {q5}, [r0], r3 @// Load row7 ;src1 vqrshrun.s16 d28, q10, #1 vld1.8 {q6}, [r1], r4 @// Load row6 ;src2 vqrshrun.s16 d29, q11, #1 vld1.8 {q7}, [r1], r4 @// Load row7 ;src2 vaddl.u8 q8, d8, d12 vaddl.u8 q9, d9, d13 vaddl.u8 q10, d10, d14 vqrshrun.s16 d30, q12, #1 vqrshrun.s16 d31, q13, #1 vst1.8 {q14}, [r2], r5 @//Store dest row4 vaddl.u8 q11, d11, d15 vst1.8 {q15}, [r2], r5 @//Store dest row5 vqrshrun.s16 d28, q8, #1 vqrshrun.s16 d30, q10, #1 vqrshrun.s16 d29, q9, #1 vld1.8 {q2}, [r1], r4 @// Load row8 ;src2 vqrshrun.s16 d31, q11, #1 vst1.8 {q14}, [r2], r5 @//Store dest row6 subs r12, r6, #8 vst1.8 {q15}, [r2], r5 @//Store dest row7 beq end_func @ end function if ht=8 vld1.8 {q0}, [r0], r3 @// Load row8 ;src1 vaddl.u8 q10, d0, d4 vld1.8 {q1}, [r0], r3 @// Load row9 ;src1 vaddl.u8 q11, d1, d5 vld1.8 {q3}, [r1], r4 @// Load row9 ;src2 vqrshrun.s16 d28, q10, #1 vld1.8 {q4}, [r0], r3 @// Load row10 ;src1 vqrshrun.s16 d29, q11, #1 vld1.8 {q5}, [r0], r3 @// Load row11 ;src1 vaddl.u8 q12, d2, d6 vld1.8 {q6}, [r1], r4 @// Load row10 ;src2 vaddl.u8 q13, d3, d7 vld1.8 {q7}, [r1], r4 @// Load row11 ;src2 vaddl.u8 q8, d8, d12 vaddl.u8 q9, d9, d13 vaddl.u8 q10, d10, d14 vqrshrun.s16 d30, q12, #1 vst1.8 {q14}, [r2], r5 @//Store dest row8 vqrshrun.s16 d31, q13, #1 vst1.8 {q15}, [r2], r5 @//Store dest row9 vqrshrun.s16 d28, q8, #1 vld1.8 {q0}, [r0], r3 @// Load row12 ;src1 vaddl.u8 q11, d11, d15 vld1.8 {q1}, [r0], r3 @// Load row13 ;src1 vqrshrun.s16 d29, q9, #1 vld1.8 {q2}, [r1], r4 @// Load row12 ;src2 vqrshrun.s16 d30, q10, #1 vld1.8 {q3}, [r1], r4 @// Load row13 ;src2 vqrshrun.s16 d31, q11, #1 vst1.8 {q14}, [r2], r5 @//Store dest row10 vaddl.u8 q10, d0, d4 vst1.8 {q15}, [r2], r5 @//Store dest row11 vaddl.u8 q11, d1, d5 vld1.8 {q4}, [r0], r3 @// Load row14 ;src1 vaddl.u8 q13, d3, d7 vld1.8 {q5}, [r0], r3 @// Load row15 ;src1 vaddl.u8 q12, d2, d6 vld1.8 {q6}, [r1], r4 @// Load row14 ;src2 vaddl.u8 q8, d8, d12 vld1.8 {q7}, [r1], r4 @// Load row15 ;src2 vaddl.u8 q9, d9, d13 vqrshrun.s16 d28, q10, #1 vqrshrun.s16 d29, q11, #1 vaddl.u8 q10, d10, d14 vst1.8 {q14}, [r2], r5 @//Store dest row12 vqrshrun.s16 d30, q12, #1 vqrshrun.s16 d31, q13, #1 vaddl.u8 q11, d11, d15 vst1.8 {q15}, [r2], r5 @//Store dest row13 vqrshrun.s16 d28, q8, #1 vqrshrun.s16 d29, q9, #1 vqrshrun.s16 d30, q10, #1 vst1.8 {q14}, [r2], r5 @//Store dest row14 vqrshrun.s16 d31, q11, #1 vst1.8 {q15}, [r2], r5 @//Store dest row15 b end_func loop_8: @wd=8; vld1.8 {d0}, [r0], r3 @// Load row0 ;src1 vld1.8 {d4}, [r1], r4 @// Load row0 ;src2 vld1.8 {d1}, [r0], r3 @// Load row1 ;src1 vaddl.u8 q10, d0, d4 vld1.8 {d5}, [r1], r4 @// Load row1 ;src2 vld1.8 {d2}, [r0], r3 @// Load row2 ;src1 vqrshrun.s16 d28, q10, #1 vld1.8 {d6}, [r1], r4 @// Load row2 ;src2 vaddl.u8 q11, d1, d5 vld1.8 {d3}, [r0], r3 @// Load row3 ;src1 vaddl.u8 q12, d2, d6 vst1.8 {d28}, [r2], r5 @//Store dest row0 vqrshrun.s16 d29, q11, #1 vld1.8 {d7}, [r1], r4 @// Load row3 ;src2 vqrshrun.s16 d30, q12, #1 vst1.8 {d29}, [r2], r5 @//Store dest row1 vaddl.u8 q13, d3, d7 vst1.8 {d30}, [r2], r5 @//Store dest row2 vqrshrun.s16 d31, q13, #1 subs r12, r6, #4 vst1.8 {d31}, [r2], r5 @//Store dest row3 beq end_func @ end function if ht=4 vld1.8 {d12}, [r1], r4 @// Load row4 ;src2 vld1.8 {d8}, [r0], r3 @// Load row4 ;src1 vld1.8 {d9}, [r0], r3 @// Load row5 ;src1 vaddl.u8 q8, d8, d12 vld1.8 {d13}, [r1], r4 @// Load row5 ;src2 vld1.8 {d10}, [r0], r3 @// Load row6;src1 vaddl.u8 q9, d9, d13 vld1.8 {d14}, [r1], r4 @// Load row6 ;src2 vqrshrun.s16 d28, q8, #1 vld1.8 {d11}, [r0], r3 @// Load row7 ;src1 vqrshrun.s16 d29, q9, #1 vst1.8 {d28}, [r2], r5 @//Store dest row4 vaddl.u8 q10, d10, d14 vst1.8 {d29}, [r2], r5 @//Store dest row5 vqrshrun.s16 d30, q10, #1 vld1.8 {d15}, [r1], r4 @// Load row7 ;src2 vaddl.u8 q11, d11, d15 vst1.8 {d30}, [r2], r5 @//Store dest row6 vqrshrun.s16 d31, q11, #1 subs r12, r6, #8 vst1.8 {d31}, [r2], r5 @//Store dest row7 beq end_func @ end function if ht=8 vld1.8 {d0}, [r0], r3 @// Load row8 ;src1 vld1.8 {d4}, [r1], r4 @// Load row8 ;src2 vld1.8 {d1}, [r0], r3 @// Load row9 ;src1 vaddl.u8 q10, d0, d4 vld1.8 {d5}, [r1], r4 @// Load row9 ;src2 vld1.8 {d2}, [r0], r3 @// Load row10 ;src1 vaddl.u8 q11, d1, d5 vld1.8 {d6}, [r1], r4 @// Load row10 ;src2 vqrshrun.s16 d28, q10, #1 vld1.8 {d3}, [r0], r3 @// Load row11 ;src1 vaddl.u8 q12, d2, d6 vld1.8 {d7}, [r1], r4 @// Load row11 ;src2 vqrshrun.s16 d29, q11, #1 vld1.8 {d8}, [r0], r3 @// Load row12 ;src1 vaddl.u8 q13, d3, d7 vst1.8 {d28}, [r2], r5 @//Store dest row8 vqrshrun.s16 d30, q12, #1 vld1.8 {d12}, [r1], r4 @// Load row12 ;src2 vqrshrun.s16 d31, q13, #1 vst1.8 {d29}, [r2], r5 @//Store dest row9 vaddl.u8 q8, d8, d12 vld1.8 {d9}, [r0], r3 @// Load row13 ;src1 vqrshrun.s16 d28, q8, #1 vld1.8 {d13}, [r1], r4 @// Load row13 ;src2 vld1.8 {d10}, [r0], r3 @// Load row14;src1 vaddl.u8 q9, d9, d13 vld1.8 {d11}, [r0], r3 @// Load row15 ;src1 vld1.8 {d14}, [r1], r4 @// Load row14 ;src2 vqrshrun.s16 d29, q9, #1 vld1.8 {d15}, [r1], r4 @// Load roW15 ;src2 vaddl.u8 q10, d10, d14 vst1.8 {d30}, [r2], r5 @//Store dest row10 vaddl.u8 q11, d11, d15 vst1.8 {d31}, [r2], r5 @//Store dest row11 vqrshrun.s16 d30, q10, #1 vst1.8 {d28}, [r2], r5 @//Store dest row12 vqrshrun.s16 d31, q11, #1 vst1.8 {d29}, [r2], r5 @//Store dest row13 vst1.8 {d30}, [r2], r5 @//Store dest row14 vst1.8 {d31}, [r2], r5 @//Store dest row15 b end_func loop_4: vld1.32 d0[0], [r0], r3 @// Load row0 ;src1 vld1.32 d4[0], [r1], r4 @// Load row0 ;src2 vld1.32 d1[0], [r0], r3 @// Load row1 ;src1 vaddl.u8 q10, d0, d4 vld1.32 d5[0], [r1], r4 @// Load row1 ;src2 vld1.32 d2[0], [r0], r3 @// Load row2 ;src1 vqrshrun.s16 d28, q10, #1 vld1.32 d6[0], [r1], r4 @// Load row2 ;src2 vaddl.u8 q11, d1, d5 vld1.32 d3[0], [r0], r3 @// Load row3 ;src1 vaddl.u8 q12, d2, d6 vst1.32 d28[0], [r2], r5 @//Store dest row0 vqrshrun.s16 d29, q11, #1 vld1.32 d7[0], [r1], r4 @// Load row3 ;src2 vqrshrun.s16 d30, q12, #1 vst1.32 d29[0], [r2], r5 @//Store dest row1 vaddl.u8 q13, d3, d7 vst1.32 d30[0], [r2], r5 @//Store dest row2 vqrshrun.s16 d31, q13, #1 subs r12, r6, #4 vst1.32 d31[0], [r2], r5 @//Store dest row3 beq end_func @ end function if ht=4 vld1.32 d12[0], [r1], r4 @// Load row4 ;src2 vld1.32 d8[0], [r0], r3 @// Load row4 ;src1 vld1.32 d9[0], [r0], r3 @// Load row5 ;src1 vaddl.u8 q8, d8, d12 vld1.32 d13[0], [r1], r4 @// Load row5 ;src2 vld1.32 d10[0], [r0], r3 @// Load row6;src1 vaddl.u8 q9, d9, d13 vld1.32 d14[0], [r1], r4 @// Load row6 ;src2 vqrshrun.s16 d28, q8, #1 vld1.32 d11[0], [r0], r3 @// Load row7 ;src1 vqrshrun.s16 d29, q9, #1 vst1.32 d28[0], [r2], r5 @//Store dest row4 vaddl.u8 q10, d10, d14 vst1.32 d29[0], [r2], r5 @//Store dest row5 vqrshrun.s16 d30, q10, #1 vld1.32 d15[0], [r1], r4 @// Load row7 ;src2 vaddl.u8 q11, d11, d15 vst1.32 d30[0], [r2], r5 @//Store dest row6 vqrshrun.s16 d31, q11, #1 vst1.32 d31[0], [r2], r5 @//Store dest row7 end_func: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_luma_copy_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @** @******************************************************************************* @* @* @brief @* Interprediction luma function for copy @* @* @par Description: @* Copies the array of width 'wd' and height 'ht' from the location pointed @* by 'src' to the location pointed by 'dst' @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @void ih264_inter_pred_luma_copy ( @ UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd ) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r7 => ht @ r12 => wd .text .p2align 2 .global ih264_inter_pred_luma_copy_a9q ih264_inter_pred_luma_copy_a9q: stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments vstmdb sp!, {d8-d15} @push neon registers to stack ldr r12, [sp, #108] @Loads wd ldr r7, [sp, #104] @Loads ht cmp r7, #0 @checks ht == 0 ble end_loops tst r12, #15 @checks wd for multiples for 4 & 8 beq core_loop_wd_16 tst r12, #7 @checks wd for multiples for 4 & 8 beq core_loop_wd_8 sub r11, r12, #4 outer_loop_wd_4: subs r4, r12, #0 @checks wd == 0 ble end_inner_loop_wd_4 inner_loop_wd_4: vld1.32 {d0[0]}, [r0] @vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) add r5, r0, r2 @pu1_src_tmp += src_strd add r6, r1, r3 @pu1_dst_tmp += dst_strd vst1.32 {d0[0]}, [r1] @vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) vld1.32 {d0[0]}, [r5], r2 @vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) add r0, r0, #4 @pu1_src += 4 vst1.32 {d0[0]}, [r6], r3 @vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) vld1.32 {d0[0]}, [r5], r2 @vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) subs r4, r4, #4 @(wd -4) vst1.32 {d0[0]}, [r6], r3 @vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) vld1.32 {d0[0]}, [r5], r2 @vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) add r1, r1, #4 @pu1_dst += 4 vst1.32 {d0[0]}, [r6], r3 @vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) bgt inner_loop_wd_4 end_inner_loop_wd_4: subs r7, r7, #4 @ht - 4 sub r0, r5, r11 @pu1_src = pu1_src_tmp sub r1, r6, r11 @pu1_dst = pu1_dst_tmp bgt outer_loop_wd_4 end_loops: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, r15} @Reload the registers from SP core_loop_wd_8: sub r11, r12, #8 outer_loop_wd_8: subs r4, r12, #0 @checks wd ble end_inner_loop_wd_8 inner_loop_wd_8: add r5, r0, r2 @pu1_src_tmp += src_strd vld1.8 {d0}, [r0]! @vld1_u8(pu1_src_tmp) add r6, r1, r3 @pu1_dst_tmp += dst_strd vst1.8 {d0}, [r1]! @vst1_u8(pu1_dst_tmp, tmp_src) vld1.8 {d1}, [r5], r2 @vld1_u8(pu1_src_tmp) vst1.8 {d1}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) subs r4, r4, #8 @wd - 8(Loop condition) vld1.8 {d2}, [r5], r2 @vld1_u8(pu1_src_tmp) vst1.8 {d2}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) vld1.8 {d3}, [r5], r2 @vld1_u8(pu1_src_tmp) vst1.8 {d3}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) bgt inner_loop_wd_8 end_inner_loop_wd_8: subs r7, r7, #4 @ht -= 4 sub r0, r5, r11 @pu1_src = pu1_src_tmp sub r1, r6, r11 @pu1_dst = pu1_dst_tmp bgt outer_loop_wd_8 vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, r15} @Reload the registers from SP core_loop_wd_16: sub r11, r12, #16 outer_loop_wd_16: subs r4, r12, #0 @checks wd ble end_inner_loop_wd_16 inner_loop_wd_16: add r5, r0, r2 @pu1_src_tmp += src_strd vld1.8 {q0}, [r0]! @vld1_u8(pu1_src_tmp) add r6, r1, r3 @pu1_dst_tmp += dst_strd vst1.8 {q0}, [r1]! @vst1_u8(pu1_dst_tmp, tmp_src) vld1.8 {q1}, [r5], r2 @vld1_u8(pu1_src_tmp) vst1.8 {q1}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) subs r4, r4, #16 @wd - 8(Loop condition) vld1.8 {q2}, [r5], r2 @vld1_u8(pu1_src_tmp) vst1.8 {q2}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) vld1.8 {q3}, [r5], r2 @vld1_u8(pu1_src_tmp) vst1.8 {q3}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) bgt inner_loop_wd_16 end_inner_loop_wd_16: subs r7, r7, #4 @ht -= 4 sub r0, r5, r11 @pu1_src = pu1_src_tmp sub r1, r6, r11 @pu1_dst = pu1_dst_tmp bgt outer_loop_wd_16 vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, r15} @Reload the registers from SP @ * @ ******************************************************************************** @ * @ * @brief This function copies a 4x4 block to destination @ * @ * @par Description: @ * Copies a 4x4 block to destination, where both src and dst are interleaved @ * @ * @param[in] pi2_src @ * Source @ * @ * @param[in] pu1_out @ * Output pointer @ * @ * @param[in] pred_strd, @ * Prediction buffer stride @ * @ * @param[in] out_strd @ * output buffer buffer Stride @ * @ * @returns none @ * @ * @remarks none @ * Currently wd and height is not used, ie a 4x4 block is always copied @ * @ ******************************************************************************* @ * @ void ih264_interleave_copy(WORD16 *pi2_src, @ UWORD8 *pu1_out, @ WORD32 pred_strd, @ WORD32 out_strd @ WORD32 wd @ WORD32 ht) @ Register Usage @ r0 : pi2_src @ r1 : pu1_out @ r2 : src_strd @ r3 : out_strd @ Neon registers d0-d7, d16-d30 are used @ No need for pushing arm and neon registers .global ih264_interleave_copy_a9 ih264_interleave_copy_a9: vld1.u8 d2, [r0], r2 @load src plane 1 => d2 &pred palne 2 => d3 vld1.u8 d3, [r0], r2 vld1.u8 d4, [r0], r2 vld1.u8 d5, [r0], r2 mov r0, r1 vld1.u8 d18, [r1], r3 @load out [8 bit size) -8 coeffs vld1.u8 d19, [r1], r3 vmov.u16 q15, #0x00ff vld1.u8 d20, [r1], r3 vld1.u8 d21, [r1], r3 vbit.u8 q9, q1, q15 vbit.u8 q10, q2, q15 vst1.u8 d18, [r0], r3 @store out vst1.u8 d19, [r0], r3 vst1.u8 d20, [r0], r3 vst1.u8 d21, [r0], r3 bx lr ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q.s @* @* @brief @* Contains function definitions for inter prediction interpolation. @* @* @author @* Mohit @* @* @par List of Functions: @* @* - ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_inter_pred_filters.c @ @** @** @** @******************************************************************************* @* @* @brief @* This function implements a two stage cascaded six tap filter. It @* applies the six tap filter in the vertical direction on the @* predictor values, followed by applying the same filter in the @* horizontal direction on the output of the first stage. The six tap @* filtering operation is described in sec 8.4.2.2.1 titled "Luma sample @* interpolation process" @* @* @par Description: @* This function is called to obtain pixels lying at the following @* location (1/2,1/2). The function interpolates @* the predictors first in the horizontal direction and then in the @* vertical direction to output the (1/2,1/2). @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @param[in] pu1_tmp: temporary buffer @* @* @param[in] dydx: x and y reference offset for qpel calculations: UNUSED in this function. @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @*; @void ih264_inter_pred_luma_horz_hpel_vert_hpel(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd,, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd, @ UWORD8* pu1_tmp, @ UWORD32 dydx) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r8 => ht @ r9 => wd .text .p2align 2 .global ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vstmdb sp!, {d8-d15} @push neon registers to stack ldr r8, [sp, #104] @ loads ht sub r0, r0, r2, lsl #1 @pu1_src-2*src_strd sub r0, r0, #2 @pu1_src-2 ldr r9, [sp, #108] @ loads wd vmov.s16 d0, #20 @ Filter coeff 20 vmov.s16 d1, #5 @ Filter coeff 5 subs r12, r9, #4 @if wd=4 branch to loop_4 beq loop_4 subs r12, r9, #8 @if wd=8 branch to loop_8 beq loop_8 mov r10, #8 sub r7, r3, r10 @when wd=16 loop_16: vld1.u32 {d2, d3, d4}, [r0], r2 @ Vector load from src[0_0] vld1.u32 {d5, d6, d7}, [r0], r2 @ Vector load from src[1_0] vld1.u32 {d8, d9, d10}, [r0], r2 @ Vector load from src[2_0] vld1.u32 {d11, d12, d13}, [r0], r2 @ Vector load from src[3_0] vld1.u32 {d14, d15, d16}, [r0], r2 @ Vector load from src[4_0] vld1.u32 {d17, d18, d19}, [r0], r2 @ Vector load from src[5_0] @ vERTICAL FILTERING FOR ROW 0 vaddl.u8 q10, d8, d11 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q12, d2, d17 @ temp2 = src[0_0] + src[5_0] vaddl.u8 q11, d5, d14 @ temp = src[1_0] + src[4_0] vaddl.u8 q13, d3, d18 @ temp2 = src[0_0] + src[5_0] vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 vaddl.u8 q10, d6, d15 @ temp = src[1_0] + src[4_0] vaddl.u8 q11, d9, d12 @ temp3 = src[2_0] + src[3_0] vaddl.u8 q14, d4, d19 @ temp2 = src[0_0] + src[5_0] vmla.u16 q13, q11, d0[0] @ temp4 += temp3 * 20 vmls.s16 q13, q10, d1[0] @ temp -= temp2 * 5 vaddl.u8 q11, d10, d13 @ temp3 = src[2_0] + src[3_0] vaddl.u8 q10, d7, d16 @ temp = src[1_0] + src[4_0] vmla.u16 q14, q11, d0[0] @ temp4 += temp3 * 20 vmls.s16 q14, q10, d1[0] @ temp -= temp2 * 5 vext.16 q10, q12, q13, #5 @//extract a[5] (column1) @Q12,Q13,Q14 HAVE VERTICAL FILTERED VALUES @CASCADED FILTERING FOR ROW 0 vext.16 q11, q12, q13, #2 @//extract a[2] (column1) vaddl.s16 q1, d20, d24 @// a0 + a5 (column1) vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) vmlal.s16 q1, d22, d0[0] @// a0 + a5 + 20a2 (column1) vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) vext.16 q11, q12, q13, #1 @//extract a[1] (column1) vext.16 q10, q12, q13, #3 @//extract a[3] (column1) vmlsl.s16 q1, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlal.s16 q1, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vext.16 q11, q12, q13, #4 @//extract a[4] (column1) vext.16 q10, q13, q14, #5 @//extract a[5] (column2) vmlsl.s16 q1, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vqrshrun.s32 d22, q1, #10 vqrshrun.s32 d23, q15, #10 vqmovun.s16 d22, q11 vst1.u8 {d22}, [r1], r10 @//Store dest row0, column 1; (1/2,1/2) vext.16 q11, q13, q14, #2 @//extract a[2] (column2) vaddl.s16 q1, d20, d26 @// a0 + a5 (column2) vaddl.s16 q15, d21, d27 @// a0 + a5 (column2) vmlal.s16 q1, d22, d0[0] @// a0 + a5 + 20a2 (column2) vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column2) vext.16 q10, q13, q14, #3 @//extract a[3] (column2) vext.16 q11, q13, q14, #1 @//extract a[1] (column2) vmlal.s16 q1, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column2) vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column2) vext.16 q10, q13, q14, #4 @//extract a[4] (column2) vmlsl.s16 q1, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2) vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2) vmlsl.s16 q1, d20, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2) vmlsl.s16 q15, d21, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2) vqrshrun.s32 d20, q1, #10 vqrshrun.s32 d21, q15, #10 vld1.u32 {d2, d3, d4}, [r0], r2 @ Vector load from src[6_0] vqmovun.s16 d22, q10 vst1.u8 {d22}, [r1], r7 @//Store dest row0 ,column 2; (1/2,1/2) @ vERTICAL FILTERING FOR ROW 1 vaddl.u8 q10, d11, d14 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q12, d5, d2 @ temp2 = src[0_0] + src[5_0] vaddl.u8 q11, d8, d17 @ temp = src[1_0] + src[4_0] vaddl.u8 q13, d6, d3 @ temp2 = src[0_0] + src[5_0] vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 vaddl.u8 q10, d9, d18 @ temp = src[1_0] + src[4_0] vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 vaddl.u8 q11, d12, d15 @ temp3 = src[2_0] + src[3_0] vaddl.u8 q14, d7, d4 @ temp2 = src[0_0] + src[5_0] vmla.u16 q13, q11, d0[0] @ temp4 += temp3 * 20 vaddl.u8 q11, d13, d16 @ temp3 = src[2_0] + src[3_0] vmls.s16 q13, q10, d1[0] @ temp -= temp2 * 5 vmla.u16 q14, q11, d0[0] @ temp4 += temp3 * 20 vaddl.u8 q10, d10, d19 @ temp = src[1_0] + src[4_0] vmls.s16 q14, q10, d1[0] @ temp -= temp2 * 5 vext.16 q10, q12, q13, #5 @//extract a[5] (column1) @Q12,Q13,Q14 HAVE VERTICAL FILTERED VALUES @CASCADED FILTERING FOR ROW 1 vext.16 q11, q12, q13, #2 @//extract a[2] (column1) vaddl.s16 q3, d20, d24 @// a0 + a5 (column1) vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) vmlal.s16 q3, d22, d0[0] @// a0 + a5 + 20a2 (column1) vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) vext.16 q11, q12, q13, #1 @//extract a[1] (column1) vext.16 q10, q12, q13, #3 @//extract a[3] (column1) vmlsl.s16 q3, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlal.s16 q3, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vext.16 q11, q12, q13, #4 @//extract a[4] (column1) vext.16 q10, q13, q14, #5 @//extract a[5] (column2) vmlsl.s16 q3, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vqrshrun.s32 d22, q3, #10 vqrshrun.s32 d23, q15, #10 vqmovun.s16 d22, q11 vst1.u8 {d22}, [r1], r10 @//Store dest row1, column 1; (1/2,1/2) vext.16 q11, q13, q14, #2 @//extract a[2] (column2) vaddl.s16 q3, d20, d26 @// a0 + a5 (column2) vaddl.s16 q15, d21, d27 @// a0 + a5 (column2) vmlal.s16 q3, d22, d0[0] @// a0 + a5 + 20a2 (column2) vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column2) vext.16 q10, q13, q14, #3 @//extract a[3] (column2) vext.16 q11, q13, q14, #1 @//extract a[1] (column2) vmlal.s16 q3, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column2) vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column2) vext.16 q10, q13, q14, #4 @//extract a[4] (column2) vmlsl.s16 q3, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2) vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2) vmlsl.s16 q3, d20, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2) vmlsl.s16 q15, d21, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2) vqrshrun.s32 d20, q3, #10 vqrshrun.s32 d21, q15, #10 vqmovun.s16 d22, q10 vst1.u8 {d22}, [r1], r7 @//Store dest row1 ,column 2; (1/2,1/2) subs r8, r8, #2 @ 2 rows processed, decrement by 2 subne r0, r0 , r2, lsl #2 subne r0, r0, r2 beq end_func @ Branch if height==4 b loop_16 @ looping if height = 8 or 16 loop_8: vld1.u32 {d2, d3}, [r0], r2 @ Vector load from src[0_0] vld1.u32 {d4, d5}, [r0], r2 @ Vector load from src[1_0] vld1.u32 {d6, d7}, [r0], r2 @ Vector load from src[2_0] vld1.u32 {d8, d9}, [r0], r2 @ Vector load from src[3_0] vld1.u32 {d10, d11}, [r0], r2 @ Vector load from src[4_0] vld1.u32 {d12, d13}, [r0], r2 @ Vector load from src[5_0] @ vERTICAL FILTERING FOR ROW 0 vaddl.u8 q10, d6, d8 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q11, d4, d10 @ temp2 = src[1_0] + src4_0] vaddl.u8 q12, d2, d12 @ temp = src[0_0] + src[5_0] vaddl.u8 q13, d3, d13 @ temp = src[0_0] + src[5_0] vaddl.u8 q14, d7, d9 @ temp1 = src[2_0] + src[3_0] vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 vaddl.u8 q15, d5, d11 @ temp2 = src[1_0] + src4_0] vmla.u16 q13, q14, d0[0] @ temp += temp1 * 20 vmls.s16 q13, q15, d1[0] @ temp -= temp2 * 5 @Q12,Q13 HAVE VERTICAL FILTERED VALUES @CASCADED FILTERING FOR ROW 0 vext.16 q10, q12, q13, #5 @//extract a[5] (column1) vext.16 q11, q12, q13, #2 @//extract a[2] (column1) vaddl.s16 q14, d20, d24 @// a0 + a5 (column1) vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) vext.16 q9, q12, q13, #1 @//extract a[1] (column1) vext.16 q10, q12, q13, #3 @//extract a[3] (column1) vext.16 q1, q12, q13, #4 @//extract a[4] (column1) vmlal.s16 q14, d22, d0[0] @// a0 + a5 + 20a2 (column1) vmlsl.s16 q14, d18, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlal.s16 q14, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vmlsl.s16 q14, d2, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vld1.u32 {d14, d15}, [r0], r2 @ Vector load from src[6_0] vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vmlsl.s16 q15, d19, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlsl.s16 q15, d3, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vaddl.u8 q12, d4, d14 @ temp = src[0_0] + src[5_0] vaddl.u8 q13, d5, d15 @ temp = src[0_0] + src[5_0] vqrshrun.s32 d18, q14, #10 vaddl.u8 q14, d9, d11 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q10, d8, d10 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q11, d6, d12 @ temp2 = src[1_0] + src4_0] vqrshrun.s32 d19, q15, #10 vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 vaddl.u8 q15, d7, d13 @ temp2 = src[1_0] + src4_0] vmla.u16 q13, q14, d0[0] @ temp += temp1 * 20 vmls.s16 q13, q15, d1[0] @ temp -= temp2 * 5 vqmovun.s16 d2, q9 @ vERTICAL FILTERING FOR ROW 1 @Q12,Q13 HAVE VERTICAL FILTERED VALUES @CASCADED FILTERING FOR ROW 1 vext.16 q10, q12, q13, #5 @//extract a[5] (column1) vext.16 q11, q12, q13, #2 @//extract a[2] (column1) vaddl.s16 q14, d20, d24 @// a0 + a5 (column1) vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) vst1.u8 {d2}, [r1], r3 @//Store dest row0, column 1; (1/2,1/2) vext.16 q9, q12, q13, #1 @//extract a[1] (column1) vext.16 q10, q12, q13, #3 @//extract a[3] (column1) vext.16 q2, q12, q13, #4 @//extract a[4] (column1) vmlal.s16 q14, d22, d0[0] @// a0 + a5 + 20a2 (column1) vmlsl.s16 q14, d18, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlal.s16 q14, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vmlsl.s16 q14, d4, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vmlsl.s16 q15, d19, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlsl.s16 q15, d5, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vqrshrun.s32 d18, q14, #10 vqrshrun.s32 d19, q15, #10 vqmovun.s16 d3, q9 vst1.u8 {d3}, [r1], r3 @//Store dest row1, column 1; (1/2,1/2) subs r8, r8, #2 @ 2 rows processed, decrement by 2 subne r0, r0 , r2, lsl #2 subne r0, r0, r2 beq end_func @ Branch if height==4 b loop_8 @looping if height == 8 or 16 loop_4: vld1.u32 {d2, d3}, [r0], r2 @ Vector load from src[0_0] vld1.u32 {d4, d5}, [r0], r2 @ Vector load from src[1_0] vld1.u32 {d6, d7}, [r0], r2 @ Vector load from src[2_0] vld1.u32 {d8, d9}, [r0], r2 @ Vector load from src[3_0] vld1.u32 {d10, d11}, [r0], r2 @ Vector load from src[4_0] vld1.u32 {d12, d13}, [r0], r2 @ Vector load from src[5_0] @ vERTICAL FILTERING FOR ROW 0 vaddl.u8 q10, d6, d8 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q11, d4, d10 @ temp2 = src[1_0] + src4_0] vaddl.u8 q12, d2, d12 @ temp = src[0_0] + src[5_0] vaddl.u8 q13, d3, d13 @ temp = src[0_0] + src[5_0] vaddl.u8 q14, d7, d9 @ temp1 = src[2_0] + src[3_0] vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 vaddl.u8 q15, d5, d11 @ temp2 = src[1_0] + src4_0] vmla.u16 q13, q14, d0[0] @ temp += temp1 * 20 vmls.s16 q13, q15, d1[0] @ temp -= temp2 * 5 @Q12,Q13 HAVE VERTICAL FILTERED VALUES @CASCADED FILTERING FOR ROW 0 vext.16 q10, q12, q13, #5 @//extract a[5] (column1) vext.16 q11, q12, q13, #2 @//extract a[2] (column1) vaddl.s16 q14, d20, d24 @// a0 + a5 (column1) vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) vext.16 q1, q12, q13, #4 @//extract a[4] (column1) vext.16 q9, q12, q13, #1 @//extract a[1] (column1) vext.16 q10, q12, q13, #3 @//extract a[3] (column1) vmlal.s16 q14, d22, d0[0] @// a0 + a5 + 20a2 (column1) vmlsl.s16 q14, d18, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlal.s16 q14, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vmlsl.s16 q14, d2, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vld1.u32 {d14, d15}, [r0], r2 @ Vector load from src[6_0] vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vmlsl.s16 q15, d19, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlsl.s16 q15, d3, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vaddl.u8 q12, d4, d14 @ temp = src[0_0] + src[5_0] vaddl.u8 q13, d5, d15 @ temp = src[0_0] + src[5_0] vqrshrun.s32 d18, q14, #10 vaddl.u8 q14, d9, d11 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q11, d6, d12 @ temp2 = src[1_0] + src4_0] vaddl.u8 q10, d8, d10 @ temp1 = src[2_0] + src[3_0] vqrshrun.s32 d19, q15, #10 vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 vaddl.u8 q15, d7, d13 @ temp2 = src[1_0] + src4_0] vqmovun.s16 d2, q9 vmla.u16 q13, q14, d0[0] @ temp += temp1 * 20 vmls.s16 q13, q15, d1[0] @ temp -= temp2 * 5 @ vERTICAL FILTERING FOR ROW 1 @Q12,Q13 HAVE VERTICAL FILTERED VALUES @CASCADED FILTERING FOR ROW 1 vext.16 q10, q12, q13, #5 @//extract a[5] (column1) vext.16 q11, q12, q13, #2 @//extract a[2] (column1) vst1.u32 {d2[0]}, [r1], r3 @//Store dest row0, column 1; (1/2,1/2) vaddl.s16 q14, d20, d24 @// a0 + a5 (column1) vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) vext.16 q9, q12, q13, #1 @//extract a[1] (column1) vext.16 q10, q12, q13, #3 @//extract a[3] (column1) vext.16 q2, q12, q13, #4 @//extract a[4] (column1) vmlal.s16 q14, d22, d0[0] @// a0 + a5 + 20a2 (column1) vmlsl.s16 q14, d18, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlal.s16 q14, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vmlsl.s16 q14, d4, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) vmlsl.s16 q15, d19, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) vmlsl.s16 q15, d5, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) vqrshrun.s32 d18, q14, #10 vqrshrun.s32 d19, q15, #10 vqmovun.s16 d4, q9 vst1.u32 {d4[0]}, [r1], r3 @//Store dest row1, column 1; (1/2,1/2) subs r8, r8, #2 @ 2 rows processed, decrement by 2 subne r0, r0 , r2, lsl #2 subne r0, r0, r2 beq end_func @ Branch if height==4 b loop_4 @looping if height == 8 or 16 end_func: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q.s @* @* @brief @* Contains function definitions for inter prediction interpolation. @* @* @author @* Mohit @* @* @par List of Functions: @* @* - ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_inter_pred_filters.c @ @** @** @** @******************************************************************************* @* @* @brief @* This function implements a two stage cascaded six tap filter. It @* applies the six tap filter in the horizontal direction on the @* predictor values, followed by applying the same filter in the @* vertical direction on the output of the first stage. It then averages @* the output of the 1st stage and the output of the 2nd stage to obtain @* the quarter pel values. The six tap filtering operation is described @* in sec 8.4.2.2.1 titled "Luma sample interpolation process". @* @* @par Description: @* This function is called to obtain pixels lying at the following @* location (1/2,1/4) or (1/2,3/4). The function interpolates @* the predictors first in the horizontal direction and then in the @* vertical direction to output the (1/2,1/2). It then averages @* the output of the 2nd stage and (1/2,1/2) value to obtain (1/2,1/4) @* or (1/2,3/4) depending on the offset. @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @param[in] pu1_tmp: temporary buffer @* @* @param[in] dydx: x and y reference offset for qpel calculations @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @*; @void ih264_inter_pred_luma_horz_hpel_vert_qpel(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd,, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd, @ UWORD8* pu1_tmp, @ UWORD32 dydx) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ht @ r5 => wd @ r7 => dydx @ r9 => *pu1_tmp .text .p2align 2 .global ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q: stmfd sp!, {r4-r12, r14} @ store register values to stack vstmdb sp!, {d8-d15} @push neon registers to stack ldr r4, [sp, #104] @ loads ht sub r0, r0, r2, lsl #1 @ pu1_src-2*src_strd sub r0, r0, #2 @ pu1_src-2 ldr r5, [sp, #108] @ loads wd ldr r7, [sp, #116] @ loads dydx lsr r7, r7, #3 @ dydx >> 2 followed by dydx & 0x3 and dydx>>1 to obtain the deciding bit ldr r9, [sp, #112] @ pu1_tmp add r7, r7, #2 mov r6, #48 mla r7, r7, r6, r9 subs r12, r5, #4 @if wd=4 branch to loop_4 beq loop_4_start subs r12, r5, #8 @if wd=8 branch to loop_8 beq loop_8_start @when wd=16 vmov.u16 q11, #20 @ Filter coeff 0x14 into Q11 vmov.u16 q12, #5 @ Filter coeff 0x5 into Q12 add r8, r0, #8 add r14, r1, #8 add r10, r9, #8 mov r12, r4 add r11, r7, #8 loop_16_lowhalf_start: vld1.32 {q0}, [r0], r2 @ row -2 load for horizontal filter vext.8 d5, d0, d1, #5 vaddl.u8 q3, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q4, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q3, q4, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q4, d1, d4 vld1.32 {q0}, [r0], r2 @ row -1 load for horizontal filter vmls.u16 q3, q4, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q4, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q5, d2, d3 vst1.32 {q3}, [r9], r6 @ store temp buffer 0 vext.8 d4, d0, d1, #4 vmla.u16 q4, q5, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q5, d1, d4 vld1.32 {q0}, [r0], r2 @ row 0 load for horizontal filter vmls.u16 q4, q5, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q5, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q6, d2, d3 vst1.32 {q4}, [r9], r6 @ store temp buffer 1 vext.8 d4, d0, d1, #4 vmla.u16 q5, q6, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q6, d1, d4 vld1.32 {q0}, [r0], r2 @ row 1 load for horizontal filter vmls.u16 q5, q6, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q6, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q7, d2, d3 vst1.32 {q5}, [r9], r6 @ store temp buffer 2 vext.8 d4, d0, d1, #4 vmla.u16 q6, q7, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q7, d1, d4 vld1.32 {q0}, [r0], r2 @ row 2 load for horizontal filter vmls.u16 q6, q7, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q7, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q8, d2, d3 vst1.32 {q6}, [r9], r6 @ store temp buffer 3 vext.8 d4, d0, d1, #4 vmla.u16 q7, q8, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q8, d1, d4 vmls.u16 q7, q8, q12 loop_16_lowhalf: vld1.32 {q0}, [r0], r2 @ row 3 load for horizontal filter vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q8, d0, d5 vst1.32 {q7}, [r9], r6 @ store temp buffer 4 vaddl.u8 q9, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q8, q9, q11 vext.8 d1, d0, d1, #1 vadd.s16 q14, q4, q7 vaddl.u8 q9, d1, d4 vadd.s16 q15, q5, q6 vmls.u16 q8, q9, q12 vld1.32 {q0}, [r0], r2 @ row 4 load for hoorizontal filter vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q10, d0, d5 vst1.32 {q8}, [r9], r6 @ store temp buffer r5 vaddl.s16 q9, d6, d16 vld1.32 {q13}, [r7], r6 @ load from temp buffer 0 vaddl.s16 q3, d7, d17 vqrshrun.s16 d26, q13, #5 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d28, d24 vmlal.s16 q3, d31, d22 vmlsl.s16 q3, d29, d24 vaddl.u8 q1, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q10, q1, q11 vqrshrun.s32 d18, q9, #10 vext.8 d1, d0, d1, #1 vqrshrun.s32 d19, q3, #10 vadd.s16 q14, q5, q8 vaddl.u8 q1, d1, d4 vadd.s16 q15, q6, q7 vmls.u16 q10, q1, q12 vqmovn.u16 d18, q9 vld1.32 {q0}, [r0], r2 @ row 5 load for horizontal filter vrhadd.u8 d26, d18, d26 vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vst1.32 {q10}, [r9], r6 @ store temp buffer r6 vaddl.s16 q9, d8, d20 vaddl.s16 q3, d9, d21 vld1.32 {q4}, [r7], r6 @load from temp buffer 1 vst1.32 d26, [r1], r3 @ store row 0 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d28, d24 vqrshrun.s16 d28, q4, #5 vmlal.s16 q3, d31, d22 vmlsl.s16 q3, d29, d24 vext.8 d3, d0, d1, #3 vaddl.u8 q4, d0, d5 vaddl.u8 q1, d2, d3 vqrshrun.s32 d18, q9, #10 vext.8 d4, d0, d1, #4 vqrshrun.s32 d19, q3, #10 vmla.u16 q4, q1, q11 vext.8 d1, d0, d1, #1 vadd.s16 q13, q6, q10 vaddl.u8 q1, d1, d4 vqmovn.u16 d18, q9 vadd.s16 q15, q7, q8 vmls.u16 q4, q1, q12 vld1.32 {q0}, [r0], r2 @ row 6 load for horizontal filter vrhadd.u8 d28, d28, d18 vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vst1.32 d28, [r1], r3 @ store row 1 vaddl.u8 q14, d0, d5 vst1.32 {q4}, [r9], r6 @ store temp buffer r7 vaddl.s16 q9, d10, d8 vaddl.s16 q3, d11, d9 vld1.32 {q5}, [r7], r6 @ load from temp buffer 2 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d26, d24 vmlal.s16 q3, d31, d22 vqrshrun.s16 d26, q5, #5 vmlsl.s16 q3, d27, d24 vaddl.u8 q1, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q14, q1, q11 vqrshrun.s32 d18, q9, #10 vext.8 d1, d0, d1, #1 vqrshrun.s32 d19, q3, #10 vadd.s16 q5, q7, q4 vaddl.u8 q1, d1, d4 vadd.s16 q15, q8, q10 vmls.u16 q14, q1, q12 vqmovn.u16 d27, q9 vaddl.s16 q9, d12, d28 vaddl.s16 q3, d13, d29 vrhadd.u8 d26, d26, d27 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d10, d24 vmlal.s16 q3, d31, d22 vmlsl.s16 q3, d11, d24 vst1.32 d26, [r1], r3 @ store row 2 vst1.32 {q14}, [r9] vqrshrun.s32 d18, q9, #10 vmov q5, q10 vld1.32 {q15}, [r7], r6 @ load from temp buffer 3 vqrshrun.s32 d19, q3, #10 subs r4, r4, #4 vqrshrun.s16 d30, q15, #5 vqmovn.u16 d18, q9 vmov q6, q4 vmov q3, q7 vrhadd.u8 d30, d18, d30 vmov q4, q8 vmov q7, q14 vst1.32 d30, [r1], r3 @ store row 3 bgt loop_16_lowhalf @ looping if height =16 loop_16_highhalf_start: vld1.32 {q0}, [r8], r2 vext.8 d5, d0, d1, #5 vaddl.u8 q3, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q4, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q3, q4, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q4, d1, d4 vld1.32 {q0}, [r8], r2 vmls.u16 q3, q4, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q4, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q5, d2, d3 vst1.32 {q3}, [r10], r6 vext.8 d4, d0, d1, #4 vmla.u16 q4, q5, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q5, d1, d4 vld1.32 {q0}, [r8], r2 vmls.u16 q4, q5, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q5, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q6, d2, d3 vst1.32 {q4}, [r10], r6 vext.8 d4, d0, d1, #4 vmla.u16 q5, q6, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q6, d1, d4 vld1.32 {q0}, [r8], r2 vmls.u16 q5, q6, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q6, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q7, d2, d3 vst1.32 {q5}, [r10], r6 vext.8 d4, d0, d1, #4 vmla.u16 q6, q7, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q7, d1, d4 vld1.32 {q0}, [r8], r2 vmls.u16 q6, q7, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q7, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q8, d2, d3 vst1.32 {q6}, [r10], r6 vext.8 d4, d0, d1, #4 vmla.u16 q7, q8, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q8, d1, d4 vmls.u16 q7, q8, q12 loop_16_highhalf: vld1.32 {q0}, [r8], r2 vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q8, d0, d5 vst1.32 {q7}, [r10], r6 vaddl.u8 q9, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q8, q9, q11 vext.8 d1, d0, d1, #1 vadd.s16 q14, q4, q7 vaddl.u8 q9, d1, d4 vadd.s16 q15, q5, q6 vmls.u16 q8, q9, q12 vld1.32 {q0}, [r8], r2 vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q10, d0, d5 vst1.32 {q8}, [r10], r6 vaddl.s16 q9, d6, d16 vld1.32 {q13}, [r11], r6 vaddl.s16 q3, d7, d17 vqrshrun.s16 d26, q13, #5 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d28, d24 vmlal.s16 q3, d31, d22 vmlsl.s16 q3, d29, d24 vaddl.u8 q1, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q10, q1, q11 vqrshrun.s32 d18, q9, #10 vext.8 d1, d0, d1, #1 vqrshrun.s32 d19, q3, #10 vadd.s16 q14, q5, q8 vaddl.u8 q1, d1, d4 vadd.s16 q15, q6, q7 vmls.u16 q10, q1, q12 vqmovn.u16 d18, q9 vld1.32 {q0}, [r8], r2 vrhadd.u8 d26, d18, d26 vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vst1.32 {q10}, [r10], r6 vaddl.s16 q9, d8, d20 vaddl.s16 q3, d9, d21 vld1.32 {q4}, [r11], r6 vst1.32 d26, [r14], r3 @store row 0 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d28, d24 vqrshrun.s16 d28, q4, #5 vmlal.s16 q3, d31, d22 vmlsl.s16 q3, d29, d24 vext.8 d3, d0, d1, #3 vaddl.u8 q4, d0, d5 vaddl.u8 q1, d2, d3 vqrshrun.s32 d18, q9, #10 vext.8 d4, d0, d1, #4 vqrshrun.s32 d19, q3, #10 vmla.u16 q4, q1, q11 vext.8 d1, d0, d1, #1 vadd.s16 q13, q6, q10 vaddl.u8 q1, d1, d4 vqmovn.u16 d18, q9 vadd.s16 q15, q7, q8 vmls.u16 q4, q1, q12 vld1.32 {q0}, [r8], r2 vrhadd.u8 d28, d28, d18 vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vst1.32 d28, [r14], r3 @store row 1 vaddl.u8 q14, d0, d5 vst1.32 {q4}, [r10], r6 vaddl.s16 q9, d10, d8 vaddl.s16 q3, d11, d9 vld1.32 {q5}, [r11], r6 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d26, d24 vmlal.s16 q3, d31, d22 vqrshrun.s16 d26, q5, #5 vmlsl.s16 q3, d27, d24 vaddl.u8 q1, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q14, q1, q11 vqrshrun.s32 d18, q9, #10 vext.8 d1, d0, d1, #1 vqrshrun.s32 d19, q3, #10 vadd.s16 q5, q7, q4 vaddl.u8 q1, d1, d4 vadd.s16 q15, q8, q10 vmls.u16 q14, q1, q12 vqmovn.u16 d27, q9 vaddl.s16 q9, d12, d28 vaddl.s16 q3, d13, d29 vrhadd.u8 d26, d26, d27 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d10, d24 vmlal.s16 q3, d31, d22 vmlsl.s16 q3, d11, d24 vst1.32 d26, [r14], r3 @ store row 2 vst1.32 {q14}, [r10] vqrshrun.s32 d18, q9, #10 vmov q5, q10 vld1.32 {q15}, [r11], r6 vqrshrun.s32 d19, q3, #10 subs r12, r12, #4 vqrshrun.s16 d30, q15, #5 vqmovn.u16 d18, q9 vmov q6, q4 vmov q3, q7 vrhadd.u8 d30, d18, d30 vmov q4, q8 vmov q7, q14 vst1.32 d30, [r14], r3 @ store row 3 bgt loop_16_highhalf @ looping if height = 8 or 16 b end_func loop_8_start: vmov.u16 q11, #20 @ Filter coeff 20 into Q11 vmov.u16 q12, #5 @ Filter coeff 5 into Q12 vld1.32 {q0}, [r0], r2 @ row -2 load for horizontal filter vext.8 d5, d0, d1, #5 vaddl.u8 q3, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q4, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q3, q4, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q4, d1, d4 vld1.32 {q0}, [r0], r2 @ row -1 load for horizontal filter vmls.u16 q3, q4, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q4, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q5, d2, d3 vst1.32 {q3}, [r9], r6 @ store temp buffer 0 vext.8 d4, d0, d1, #4 vmla.u16 q4, q5, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q5, d1, d4 vld1.32 {q0}, [r0], r2 @ row 0 load for horizontal filter vmls.u16 q4, q5, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q5, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q6, d2, d3 vst1.32 {q4}, [r9], r6 @ store temp buffer 1 vext.8 d4, d0, d1, #4 vmla.u16 q5, q6, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q6, d1, d4 vld1.32 {q0}, [r0], r2 @ row 1 load for horizontal filter vmls.u16 q5, q6, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q6, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q7, d2, d3 vst1.32 {q5}, [r9], r6 @ store temp buffer 2 vext.8 d4, d0, d1, #4 vmla.u16 q6, q7, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q7, d1, d4 vld1.32 {q0}, [r0], r2 @ row 2 load for horizontal filter vmls.u16 q6, q7, q12 vext.8 d5, d0, d1, #5 vaddl.u8 q7, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q8, d2, d3 vst1.32 {q6}, [r9], r6 @ store temp buffer 3 vext.8 d4, d0, d1, #4 vmla.u16 q7, q8, q11 vext.8 d1, d0, d1, #1 vaddl.u8 q8, d1, d4 vmls.u16 q7, q8, q12 loop_8: vld1.32 {q0}, [r0], r2 @ row 3 load for horizontal filter vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q8, d0, d5 vst1.32 {q7}, [r9], r6 @ store temp buffer 4 vaddl.u8 q9, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q8, q9, q11 vext.8 d1, d0, d1, #1 vadd.s16 q14, q4, q7 vaddl.u8 q9, d1, d4 vadd.s16 q15, q5, q6 vmls.u16 q8, q9, q12 vld1.32 {q0}, [r0], r2 @ row 4 load for hoorizontal filter vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q10, d0, d5 vst1.32 {q8}, [r9], r6 @ store temp buffer r5 vaddl.s16 q9, d6, d16 vld1.32 {q13}, [r7], r6 @ load from temp buffer 0 vaddl.s16 q3, d7, d17 vqrshrun.s16 d26, q13, #5 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d28, d24 vmlal.s16 q3, d31, d22 vmlsl.s16 q3, d29, d24 vaddl.u8 q1, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q10, q1, q11 vqrshrun.s32 d18, q9, #10 vext.8 d1, d0, d1, #1 vqrshrun.s32 d19, q3, #10 vadd.s16 q14, q5, q8 vaddl.u8 q1, d1, d4 vadd.s16 q15, q6, q7 vmls.u16 q10, q1, q12 vqmovn.u16 d18, q9 vld1.32 {q0}, [r0], r2 @ row 5 load for horizontal filter vrhadd.u8 d26, d18, d26 vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vst1.32 {q10}, [r9], r6 @ store temp buffer r6 vaddl.s16 q9, d8, d20 vaddl.s16 q3, d9, d21 vld1.32 {q4}, [r7], r6 @load from temp buffer 1 vst1.32 d26, [r1], r3 @ store row 0 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d28, d24 vqrshrun.s16 d28, q4, #5 vmlal.s16 q3, d31, d22 vmlsl.s16 q3, d29, d24 vext.8 d3, d0, d1, #3 vaddl.u8 q4, d0, d5 vaddl.u8 q1, d2, d3 vqrshrun.s32 d18, q9, #10 vext.8 d4, d0, d1, #4 vqrshrun.s32 d19, q3, #10 vmla.u16 q4, q1, q11 vext.8 d1, d0, d1, #1 vadd.s16 q13, q6, q10 vaddl.u8 q1, d1, d4 vqmovn.u16 d18, q9 vadd.s16 q15, q7, q8 vmls.u16 q4, q1, q12 vld1.32 {q0}, [r0], r2 @ row 6 load for horizontal filter vrhadd.u8 d28, d28, d18 vext.8 d5, d0, d1, #5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vst1.32 d28, [r1], r3 @ store row 1 vaddl.u8 q14, d0, d5 vst1.32 {q4}, [r9], r6 @ store temp buffer r7 vaddl.s16 q9, d10, d8 vaddl.s16 q3, d11, d9 vld1.32 {q5}, [r7], r6 @ load from temp buffer 2 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d26, d24 vmlal.s16 q3, d31, d22 vqrshrun.s16 d26, q5, #5 vmlsl.s16 q3, d27, d24 vaddl.u8 q1, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 q14, q1, q11 vqrshrun.s32 d18, q9, #10 vext.8 d1, d0, d1, #1 vqrshrun.s32 d19, q3, #10 vadd.s16 q5, q7, q4 vaddl.u8 q1, d1, d4 vadd.s16 q15, q8, q10 vmls.u16 q14, q1, q12 vqmovn.u16 d27, q9 vaddl.s16 q9, d12, d28 vaddl.s16 q3, d13, d29 vrhadd.u8 d26, d26, d27 vmlal.s16 q9, d30, d22 vmlsl.s16 q9, d10, d24 vmlal.s16 q3, d31, d22 vmlsl.s16 q3, d11, d24 vst1.32 d26, [r1], r3 @ store row 2 vst1.32 {q14}, [r9] vqrshrun.s32 d18, q9, #10 vmov q5, q10 vld1.32 {q15}, [r7], r6 @ load from temp buffer 3 vqrshrun.s32 d19, q3, #10 subs r4, r4, #4 vqrshrun.s16 d30, q15, #5 vqmovn.u16 d18, q9 vmov q6, q4 vmov q3, q7 vrhadd.u8 d30, d18, d30 vmov q4, q8 vmov q7, q14 vst1.32 d30, [r1], r3 @ store row 3 bgt loop_8 @if height =8 or 16 loop b end_func loop_4_start: vmov.u16 d22, #20 @ Filter coeff 20 into D22 vmov.u16 d23, #5 @ Filter coeff 5 into D23 vld1.32 {q0}, [r0], r2 @row -2 load vext.8 d5, d0, d1, #5 vaddl.u8 q3, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q4, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 d6, d8, d22 vext.8 d1, d0, d1, #1 vaddl.u8 q4, d1, d4 vld1.32 {q0}, [r0], r2 @ row -1 load vmls.u16 d6, d8, d23 vext.8 d5, d0, d1, #5 vaddl.u8 q4, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q5, d2, d3 vst1.32 d6, [r9], r6 @ store temp buffer 0 vext.8 d4, d0, d1, #4 vmla.u16 d8, d10, d22 vext.8 d1, d0, d1, #1 vaddl.u8 q5, d1, d4 vld1.32 {q0}, [r0], r2 @ row 0 load vmls.u16 d8, d10, d23 vext.8 d5, d0, d1, #5 vaddl.u8 q5, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q6, d2, d3 vst1.32 d8, [r9], r6 @ store temp buffer 1 vext.8 d4, d0, d1, #4 vmla.u16 d10, d12, d22 vext.8 d1, d0, d1, #1 vaddl.u8 q6, d1, d4 vld1.32 {q0}, [r0], r2 @ row 1 load vmls.u16 d10, d12, d23 vext.8 d5, d0, d1, #5 vaddl.u8 q6, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q7, d2, d3 vst1.32 d10, [r9], r6 @ store temp buffer 2 vext.8 d4, d0, d1, #4 vmla.u16 d12, d14, d22 vext.8 d1, d0, d1, #1 vaddl.u8 q7, d1, d4 vld1.32 {q0}, [r0], r2 @ row 2 load vmls.u16 d12, d14, d23 vext.8 d5, d0, d1, #5 vaddl.u8 q7, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q8, d2, d3 vext.8 d4, d0, d1, #4 vmla.u16 d14, d16, d22 vext.8 d1, d0, d1, #1 vaddl.u8 q8, d1, d4 vst1.32 d12, [r9], r6 @ store temp buffer 3 vmls.u16 d14, d16, d23 loop_4: vld1.32 {q0}, [r0], r2 @ row 3 load vext.8 d5, d0, d1, #5 vaddl.u8 q8, d0, d5 vext.8 d2, d0, d1, #2 vext.8 d3, d0, d1, #3 vaddl.u8 q9, d2, d3 vst1.32 d14, [r9], r6 @ store temp buffer 4 vext.8 d4, d0, d1, #4 vmla.u16 d16, d18, d22 vext.8 d1, d0, d1, #1 vaddl.u8 q9, d1, d4 vadd.s16 d2, d10, d12 vmls.u16 d16, d18, d23 vadd.s16 d3, d8, d14 vld1.32 {q9}, [r0], r2 @ row 4 load vext.8 d25, d18, d19, #5 vaddl.u8 q13, d18, d25 vext.8 d20, d18, d19, #2 vst1.32 d16, [r9], r6 @ store temp buffer 5 vaddl.s16 q0, d6, d16 vmlal.s16 q0, d2, d22 vext.8 d21, d18, d19, #3 vaddl.u8 q14, d20, d21 vext.8 d24, d18, d19, #4 vmlsl.s16 q0, d3, d23 vmla.u16 d26, d28, d22 vext.8 d19, d18, d19, #1 vaddl.u8 q14, d19, d24 vadd.s16 d2, d12, d14 vmls.u16 d26, d28, d23 vqrshrun.s32 d0, q0, #0xa vadd.s16 d3, d10, d16 vld1.32 {q9}, [r0], r2 @ row 5 load vext.8 d25, d18, d19, #5 vqmovn.u16 d11, q0 vaddl.u8 q14, d18, d25 vst1.32 d26, [r9], r6 @ store temp buffer 6 @Q3 available here vld1.32 d6, [r7], r6 @ load from temp buffer 0 vld1.32 d7, [r7], r6 @ load from temp buffer 1 vqrshrun.s16 d9, q3, #5 vext.8 d20, d18, d19, #2 vaddl.s16 q0, d8, d26 vmlal.s16 q0, d2, d22 vext.8 d21, d18, d19, #3 vaddl.u8 q3, d20, d21 vext.8 d24, d18, d19, #4 vmlsl.s16 q0, d3, d23 vmla.u16 d28, d6, d22 vext.8 d19, d18, d19, #1 vaddl.u8 q3, d19, d24 vadd.s16 d2, d14, d16 vmls.u16 d28, d6, d23 vqrshrun.s32 d0, q0, #0xa vadd.s16 d3, d12, d26 vld1.32 {q9}, [r0], r2 @ row 6 load vext.8 d25, d18, d19, #5 vqmovn.u16 d13, q0 vtrn.32 d11, d13 vaddl.s16 q0, d10, d28 vrhadd.u8 d9, d9, d11 vst1.32 d28, [r9], r6 @ store temp buffer 7 vmlal.s16 q0, d2, d22 vaddl.u8 q15, d18, d25 vst1.32 d9[0], [r1], r3 @ store row 0 vext.8 d20, d18, d19, #2 vst1.32 d9[1], [r1], r3 @ store row 1 vext.8 d21, d18, d19, #3 vmlsl.s16 q0, d3, d23 vaddl.u8 q4, d20, d21 vext.8 d24, d18, d19, #4 vmla.u16 d30, d8, d22 vext.8 d19, d18, d19, #1 vaddl.u8 q4, d19, d24 vqrshrun.s32 d0, q0, #0xa vadd.s16 d2, d16, d26 vmls.u16 d30, d8, d23 vqmovn.u16 d4, q0 vadd.s16 d3, d14, d28 vaddl.s16 q0, d12, d30 vst1.32 d30, [r9] vmlal.s16 q0, d2, d22 vld1.32 d8, [r7], r6 @ load from temp buffer 2 vld1.32 d9, [r7], r6 @ load from temp buffer 3 vmlsl.s16 q0, d3, d23 subs r4, r4, #4 vqrshrun.s16 d10, q4, #5 vmov d12, d28 vqrshrun.s32 d0, q0, #0xa vmov d6, d14 vmov d8, d16 vqmovn.u16 d5, q0 vtrn.32 d4, d5 vrhadd.u8 d4, d4, d10 vmov d10, d26 vmov d14, d30 vst1.32 d4[0], [r1], r3 @ store row 2 vst1.32 d4[1], [r1], r3 @ store row 3 bgt loop_4 end_func: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_inter_pred_luma_horz_qpel_a9q.s @* @* @brief @* Contains function definitions for inter prediction horizontal quarter pel interpolation. @* @* @author @* Mohit @* @* @par List of Functions: @* @* - ih264_inter_pred_luma_horz_qpel_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_inter_pred_filters.c @ @** @** @******************************************************************************* @* @* @brief @* Quarter pel interprediction luma filter for horizontal input @* @* @par Description: @* Applies a 6 tap horizontal filter .The output is clipped to 8 bits @* sec 8.4.2.2.1 titled "Luma sample interpolation process" @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @ @param[in] pu1_tmp: temporary buffer: UNUSED in this function @* @* @param[in] dydx: x and y reference offset for qpel calculations. @* @returns @* @ @remarks @* None @* @******************************************************************************* @* @void ih264_inter_pred_luma_horz ( @ UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd, @ UWORD8* pu1_tmp, @ UWORD32 dydx) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r5 => ht @ r6 => wd @ r7 => dydx .text .p2align 2 .global ih264_inter_pred_luma_horz_qpel_a9q ih264_inter_pred_luma_horz_qpel_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vstmdb sp!, {d8-d15} @push neon registers to stack ldr r5, [sp, #104] @Loads ht ldr r6, [sp, #108] @Loads wd ldr r7, [sp, #116] @Loads dydx and r7, r7, #3 @Finds x-offset add r7, r0, r7, lsr #1 @pu1_src + (x_offset>>1) sub r0, r0, #2 @pu1_src-2 vmov.i8 d0, #5 @filter coeff subs r12, r6, #8 @if wd=8 branch to loop_8 vmov.i8 d1, #20 @filter coeff beq loop_8 subs r12, r6, #4 @if wd=4 branch to loop_4 beq loop_4 loop_16: @when wd=16 @ Processing row0 and row1 vld1.8 {d2, d3, d4}, [r0], r2 @// Load row0 vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) vld1.8 {d5, d6, d7}, [r0], r2 @// Load row1 vext.8 d30, d3, d4, #5 @//extract a[5] (column2,row0) vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) vaddl.u8 q5, d30, d3 @// a0 + a5 (column2,row0) vext.8 d27, d6, d7, #5 @//extract a[5] (column2,row1) vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) vext.8 d31, d2, d3, #2 @//extract a[2] (column1,row0) vaddl.u8 q8, d27, d6 @// a0 + a5 (column2,row1) vext.8 d30, d3, d4, #2 @//extract a[2] (column2,row0) vmlal.u8 q4, d31, d1 @// a0 + a5 + 20a2 (column1,row0) vext.8 d28, d5, d6, #2 @//extract a[2] (column1,row1) vmlal.u8 q5, d30, d1 @// a0 + a5 + 20a2 (column2,row0) vext.8 d27, d6, d7, #2 @//extract a[2] (column2,row1) vmlal.u8 q7, d28, d1 @// a0 + a5 + 20a2 (column1,row1) vext.8 d31, d2, d3, #3 @//extract a[3] (column1,row0) vmlal.u8 q8, d27, d1 @// a0 + a5 + 20a2 (column2,row1) vext.8 d30, d3, d4, #3 @//extract a[3] (column2,row0) vmlal.u8 q4, d31, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) vext.8 d28, d5, d6, #3 @//extract a[3] (column1,row1) vmlal.u8 q5, d30, d1 @// a0 + a5 + 20a2 + 20a3 (column2,row0) vext.8 d27, d6, d7, #3 @//extract a[3] (column2,row1) vmlal.u8 q7, d28, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) vext.8 d31, d2, d3, #1 @//extract a[1] (column1,row0) vmlal.u8 q8, d27, d1 @// a0 + a5 + 20a2 + 20a3 (column2,row1) vext.8 d30, d3, d4, #1 @//extract a[1] (column2,row0) vmlsl.u8 q4, d31, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) vext.8 d28, d5, d6, #1 @//extract a[1] (column1,row1) vmlsl.u8 q5, d30, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row0) vext.8 d27, d6, d7, #1 @//extract a[1] (column2,row1) vmlsl.u8 q7, d28, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) vext.8 d31, d2, d3, #4 @//extract a[4] (column1,row0) vmlsl.u8 q8, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row1) vext.8 d30, d3, d4, #4 @//extract a[4] (column2,row0) vmlsl.u8 q4, d31, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) vext.8 d28, d5, d6, #4 @//extract a[4] (column1,row1) vmlsl.u8 q5, d30, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row0) vext.8 d27, d6, d7, #4 @//extract a[4] (column2,row1) vmlsl.u8 q7, d28, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) vmlsl.u8 q8, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row1) vld1.32 {d12, d13}, [r7], r2 @Load value for interpolation (column1,row0) vqrshrun.s16 d20, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) vqrshrun.s16 d21, q5, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row0) vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row2) vrhadd.u8 q10, q6, q10 @Interpolation step for qpel calculation vqrshrun.s16 d18, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) vst1.8 {d20, d21}, [r1], r3 @//Store dest row0 vext.8 d30, d3, d4, #5 @//extract a[5] (column2,row2) vqrshrun.s16 d19, q8, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row1) vld1.32 {d12, d13}, [r7], r2 @Load value for interpolation (column1,row1) vrhadd.u8 q9, q6, q9 @Interpolation step for qpel calculation vst1.8 {d18, d19}, [r1], r3 @//Store dest row1 subs r5, r5, #2 @ 2 rows done, decrement by 2 beq end_func b loop_16 loop_8: @ Processing row0 and row1 vld1.8 {d5, d6}, [r0], r2 @// Load row1 vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) vld1.8 {d2, d3}, [r0], r2 @// Load row0 vext.8 d25, d5, d6, #2 @//extract a[2] (column1,row1) vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) vext.8 d24, d5, d6, #3 @//extract a[3] (column1,row1) vext.8 d23, d5, d6, #1 @//extract a[1] (column1,row1) vext.8 d22, d5, d6, #4 @//extract a[4] (column1,row1) vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) vext.8 d29, d2, d3, #3 @//extract a[3] (column1,row0) vmlal.u8 q7, d25, d1 @// a0 + a5 + 20a2 (column1,row1) vmlal.u8 q7, d24, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) vmlsl.u8 q7, d23, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) vmlsl.u8 q7, d22, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) vext.8 d30, d2, d3, #2 @//extract a[2] (column1,row0) vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) vext.8 d27, d2, d3, #1 @//extract a[1] (column1,row0) vext.8 d26, d2, d3, #4 @//extract a[4] (column1,row0) vmlal.u8 q4, d29, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) vmlal.u8 q4, d30, d1 @// a0 + a5 + 20a2 (column1,row0) vmlsl.u8 q4, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) vmlsl.u8 q4, d26, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) vqrshrun.s16 d18, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) vld1.32 d12, [r7], r2 @Load value for interpolation (column1,row0) vld1.32 d13, [r7], r2 @Load value for interpolation (column1,row1) vqrshrun.s16 d19, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) vrhadd.u8 q9, q6, q9 @Interpolation step for qpel calculation vst1.8 {d18}, [r1], r3 @//Store dest row0 vst1.8 {d19}, [r1], r3 @//Store dest row1 subs r5, r5, #2 @ 2 rows done, decrement by 2 beq end_func @ Branch if height==4 b loop_8 @looping if height == 8 or 16 loop_4: vld1.8 {d5, d6}, [r0], r2 @// Load row1 vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) vld1.8 {d2, d3}, [r0], r2 @// Load row0 vext.8 d25, d5, d6, #2 @//extract a[2] (column1,row1) vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) vext.8 d24, d5, d6, #3 @//extract a[3] (column1,row1) vext.8 d23, d5, d6, #1 @//extract a[1] (column1,row1) vext.8 d22, d5, d6, #4 @//extract a[4] (column1,row1) vext.8 d29, d2, d3, #3 @//extract a[3] (column1,row0) vmlal.u8 q7, d25, d1 @// a0 + a5 + 20a2 (column1,row1) vmlal.u8 q7, d24, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) vmlsl.u8 q7, d23, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) vmlsl.u8 q7, d22, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) vext.8 d30, d2, d3, #2 @//extract a[2] (column1,row0) vld1.32 d12, [r7], r2 @Load value for interpolation (column1,row0) vld1.32 d13, [r7], r2 @Load value for interpolation (column1,row1) vext.8 d27, d2, d3, #1 @//extract a[1] (column1,row0) vext.8 d26, d2, d3, #4 @//extract a[4] (column1,row0) vmlal.u8 q4, d29, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) vmlal.u8 q4, d30, d1 @// a0 + a5 + 20a2 (column1,row0) vmlsl.u8 q4, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) vmlsl.u8 q4, d26, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) vqrshrun.s16 d18, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) vqrshrun.s16 d19, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) vrhadd.u8 q9, q6, q9 @Interpolation step for qpel calculation vst1.32 d18[0], [r1], r3 @//Store dest row0 vst1.32 d19[0], [r1], r3 @//Store dest row1 subs r5, r5, #2 @ 2 rows done, decrement by 2 beq end_func b loop_4 end_func: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q.s @* @* @brief @* Contains function definitions for inter prediction interpolation. @* @* @author @* Mohit @* @* @par List of Functions: @* @* - ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_inter_pred_filters.c @ @** @** @** @******************************************************************************* @* @* @brief @* This function implements a two stage cascaded six tap filter. It @* applies the six tap filter in the vertical direction on the @* predictor values, followed by applying the same filter in the @* horizontal direction on the output of the first stage. It then averages @* the output of the 1st stage and the final stage to obtain the quarter @* pel values.The six tap filtering operation is described in sec 8.4.2.2.1 @* titled "Luma sample interpolation process". @* @* @par Description: @* This function is called to obtain pixels lying at the following @* location (1/4,1/2) or (3/4,1/2). The function interpolates @* the predictors first in the verical direction and then in the @* horizontal direction to output the (1/2,1/2). It then averages @* the output of the 2nd stage and (1/2,1/2) value to obtain (1/4,1/2) @* or (3/4,1/2) depending on the offset. @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @param[in] pu1_tmp: temporary buffer @* @* @param[in] dydx: x and y reference offset for qpel calculations @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @*; @void ih264_inter_pred_luma_horz_qpel_vert_hpel(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd,, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd, @ UWORD8* pu1_tmp, @ UWORD32 dydx) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ht @ r5 => wd @ r6 => dydx @ r9 => *pu1_tmp .text .p2align 2 .global ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vstmdb sp!, {d8-d15} @push neon registers to stack ldr r4, [sp, #104] @ loads ht sub r0, r0, r2, lsl #1 @pu1_src-2*src_strd sub r0, r0, #2 @pu1_src-2 ldr r5, [sp, #108] @ loads wd ldr r6, [sp, #116] @ loads dydx and r6, r6, #2 @ dydx & 0x3 followed by dydx>>1 and dydx<<1 ldr r9, [sp, #112] @pu1_tmp add r7, r9, #4 add r6, r7, r6 @ pi16_pred1_temp += (x_offset>>1) vmov.u16 q13, #0x14 @ Filter coeff 20 into Q13 vmov.u16 q12, #0x5 @ Filter coeff 5 into Q12 mov r7, #0x20 mov r8, #0x30 subs r12, r5, #4 @if wd=4 branch to loop_4 beq loop_4 subs r12, r5, #8 @if wd=8 branch to loop_8 beq loop_8 @when wd=16 vmov.u16 q14, #0x14 @ Filter coeff 20 into Q13 vmov.u16 q15, #0x5 @ Filter coeff 5 into Q12 add r14, r2, #0 sub r2, r2, #16 loop_16: vld1.u32 {q0}, [r0]! @ Vector load from src[0_0] vld1.u32 d12, [r0], r2 @ Vector load from src[0_0] vld1.u32 {q1}, [r0]! @ Vector load from src[1_0] vld1.u32 d13, [r0], r2 @ Vector load from src[1_0] vld1.u32 {q2}, [r0]! @ Vector load from src[2_0] vld1.u32 d14, [r0], r2 @ Vector load from src[2_0] vld1.u32 {q3}, [r0]! @ Vector load from src[3_0] vld1.u32 d15, [r0], r2 @ Vector load from src[3_0] vld1.u32 {q4}, [r0]! @ Vector load from src[4_0] vld1.u32 d16, [r0], r2 @ Vector load from src[4_0] vld1.u32 {q5}, [r0]! @ Vector load from src[5_0] vld1.u32 d17, [r0], r2 @ Vector load from src[5_0] vaddl.u8 q10, d4, d6 vaddl.u8 q9, d0, d10 vaddl.u8 q11, d2, d8 vmla.u16 q9, q10, q14 vaddl.u8 q12, d5, d7 vaddl.u8 q10, d1, d11 vaddl.u8 q13, d3, d9 vmla.u16 q10, q12, q14 vaddl.u8 q12, d14, d15 vmls.u16 q9, q11, q15 vaddl.u8 q11, d12, d17 vmls.u16 q10, q13, q15 vaddl.u8 q13, d13, d16 vmla.u16 q11, q12, q14 vmls.u16 q11, q13, q15 vst1.32 {q9}, [r9]! vst1.32 {q10}, [r9]! vext.16 q12, q9, q10, #2 vext.16 q13, q9, q10, #3 vst1.32 {q11}, [r9] vext.16 q11, q9, q10, #5 vadd.s16 q0, q12, q13 vext.16 q12, q9, q10, #1 vext.16 q13, q9, q10, #4 vadd.s16 q12, q12, q13 vaddl.s16 q13, d18, d22 vmlal.s16 q13, d0, d28 vmlsl.s16 q13, d24, d30 vaddl.s16 q11, d19, d23 vmlal.s16 q11, d1, d28 vmlsl.s16 q11, d25, d30 vqrshrun.s32 d18, q13, #10 vqrshrun.s32 d19, q11, #10 vld1.32 {q11}, [r9]! vqmovn.u16 d18, q9 vext.16 q12, q10, q11, #2 vext.16 q13, q10, q11, #3 vext.16 q0, q10, q11, #5 vst1.32 d18, [r1] vadd.s16 q9, q12, q13 vext.16 q12, q10, q11, #1 vext.16 q13, q10, q11, #4 vadd.s16 q12, q12, q13 vaddl.s16 q13, d0, d20 vmlal.s16 q13, d18, d28 vmlsl.s16 q13, d24, d30 vaddl.s16 q11, d1, d21 vmlal.s16 q11, d19, d28 vmlsl.s16 q11, d25, d30 vqrshrun.s32 d18, q13, #10 vqrshrun.s32 d19, q11, #10 vaddl.u8 q12, d7, d9 vld1.32 {q10}, [r6]! vld1.32 {q11}, [r6], r7 vqmovn.u16 d19, q9 vld1.32 d18, [r1] vqrshrun.s16 d20, q10, #5 vqrshrun.s16 d21, q11, #5 vaddl.u8 q11, d4, d10 vld1.u32 {q0}, [r0]! @ Vector load from src[6_0] vrhadd.u8 q9, q9, q10 vld1.u32 d12, [r0], r2 @ Vector load from src[6_0] vaddl.u8 q10, d6, d8 vaddl.u8 q13, d5, d11 vst1.32 {q9}, [r1], r3 @ store row 0 @ROW_2 vaddl.u8 q9, d2, d0 vmla.u16 q9, q10, q14 vaddl.u8 q10, d3, d1 vmla.u16 q10, q12, q14 vaddl.u8 q12, d15, d16 vmls.u16 q9, q11, q15 vaddl.u8 q11, d13, d12 vmls.u16 q10, q13, q15 vaddl.u8 q13, d14, d17 vmla.u16 q11, q12, q14 vmls.u16 q11, q13, q15 vst1.32 {q9}, [r9]! vst1.32 {q10}, [r9]! vext.16 q12, q9, q10, #2 vext.16 q13, q9, q10, #3 vst1.32 {q11}, [r9] vext.16 q11, q9, q10, #5 vadd.s16 q1, q12, q13 vext.16 q12, q9, q10, #1 vext.16 q13, q9, q10, #4 vadd.s16 q12, q12, q13 vaddl.s16 q13, d18, d22 vmlal.s16 q13, d2, d28 vmlsl.s16 q13, d24, d30 vaddl.s16 q11, d19, d23 vmlal.s16 q11, d3, d28 vmlsl.s16 q11, d25, d30 vqrshrun.s32 d18, q13, #10 vqrshrun.s32 d19, q11, #10 vld1.32 {q11}, [r9]! vqmovn.u16 d18, q9 vext.16 q12, q10, q11, #2 vext.16 q13, q10, q11, #3 vext.16 q1, q10, q11, #5 vst1.32 d18, [r1] vadd.s16 q9, q12, q13 vext.16 q12, q10, q11, #1 vext.16 q13, q10, q11, #4 vadd.s16 q12, q12, q13 vaddl.s16 q13, d2, d20 vmlal.s16 q13, d18, d28 vmlsl.s16 q13, d24, d30 vaddl.s16 q11, d3, d21 vmlal.s16 q11, d19, d28 vmlsl.s16 q11, d25, d30 vqrshrun.s32 d18, q13, #10 vqrshrun.s32 d19, q11, #10 vaddl.u8 q12, d9, d11 vld1.32 {q10}, [r6]! vld1.32 {q11}, [r6], r7 vqmovn.u16 d19, q9 vld1.32 d18, [r1] vqrshrun.s16 d20, q10, #5 vqrshrun.s16 d21, q11, #5 vrhadd.u8 q9, q9, q10 vst1.32 {q9}, [r1], r3 @ store row 1 subs r4, r4, #2 subne r0, r0 , r14, lsl #2 subne r0, r0, r14 beq end_func @ Branch if height==4 b loop_16 @ Loop if height==8 loop_8: vld1.u32 {q0}, [r0], r2 @ Vector load from src[0_0] vld1.u32 {q1}, [r0], r2 @ Vector load from src[1_0] vld1.u32 {q2}, [r0], r2 @ Vector load from src[2_0] vld1.u32 {q3}, [r0], r2 @ Vector load from src[3_0] vld1.u32 {q4}, [r0], r2 @ Vector load from src[4_0] vld1.u32 {q5}, [r0], r2 @ Vector load from src[5_0] vaddl.u8 q7, d4, d6 vaddl.u8 q6, d0, d10 vaddl.u8 q8, d2, d8 vmla.u16 q6, q7, q13 vaddl.u8 q9, d5, d7 vaddl.u8 q7, d1, d11 vaddl.u8 q11, d3, d9 vmla.u16 q7, q9, q13 vmls.u16 q6, q8, q12 vld1.32 {q0}, [r0], r2 @ Vector load from src[6_0] vaddl.u8 q8, d6, d8 vmls.u16 q7, q11, q12 vaddl.u8 q14, d2, d0 vst1.32 {q6}, [r9]! @ store row 0 to temp buffer: col 0 vext.16 q11, q6, q7, #5 vaddl.u8 q9, d4, d10 vmla.u16 q14, q8, q13 vaddl.s16 q15, d12, d22 vst1.32 {q7}, [r9], r7 @ store row 0 to temp buffer: col 1 vaddl.s16 q11, d13, d23 vext.16 q8, q6, q7, #2 vmls.u16 q14, q9, q12 vext.16 q9, q6, q7, #3 vext.16 q10, q6, q7, #4 vext.16 q7, q6, q7, #1 vadd.s16 q8, q8, q9 vadd.s16 q9, q7, q10 vaddl.u8 q10, d7, d9 vmlal.s16 q15, d16, d26 vmlsl.s16 q15, d18, d24 vmlal.s16 q11, d17, d26 vmlsl.s16 q11, d19, d24 vaddl.u8 q7, d3, d1 vst1.32 {q14}, [r9]! @ store row 1 to temp buffer: col 0 vmla.u16 q7, q10, q13 vqrshrun.s32 d12, q15, #10 vaddl.u8 q8, d5, d11 vqrshrun.s32 d13, q11, #10 vmls.u16 q7, q8, q12 @ vld1.32 {q1},[r0],r2 ; Vector load from src[7_0] vqmovn.u16 d25, q6 vaddl.u8 q8, d8, d10 vext.16 q11, q14, q7, #5 vaddl.u8 q10, d4, d2 vaddl.s16 q15, d28, d22 vmla.u16 q10, q8, q13 vst1.32 {q7}, [r9], r7 @ store row 1 to temp buffer: col 1 vaddl.s16 q11, d29, d23 vext.16 q8, q14, q7, #2 vext.16 q9, q14, q7, #3 vext.16 q6, q14, q7, #4 vext.16 q7, q14, q7, #1 vadd.s16 q8, q8, q9 vadd.s16 q9, q6, q7 vld1.32 {q7}, [r6], r8 @ load row 0 from temp buffer vmlal.s16 q15, d16, d26 vmlsl.s16 q15, d18, d24 vmlal.s16 q11, d17, d26 vmlsl.s16 q11, d19, d24 vqrshrun.s16 d14, q7, #0x5 vld1.32 {q14}, [r6], r8 @ load row 1 from temp buffer vaddl.u8 q9, d6, d0 vqrshrun.s32 d16, q15, #10 vqrshrun.s16 d15, q14, #0x5 vqrshrun.s32 d17, q11, #10 vmov d12, d25 vmov d25, d24 vqmovn.u16 d13, q8 vrhadd.u8 q6, q6, q7 vst1.32 d12, [r1], r3 @ store row 0 vst1.32 d13, [r1], r3 @ store row 1 subs r4, r4, #2 subne r0, r0 , r2, lsl #2 subne r0, r0, r2 beq end_func @ Branch if height==4 b loop_8 @ Loop if height==8 loop_4: vld1.u32 {q0}, [r0], r2 @ Vector load from src[0_0] vld1.u32 {q1}, [r0], r2 @ Vector load from src[1_0] vld1.u32 {q2}, [r0], r2 @ Vector load from src[2_0] vld1.u32 {q3}, [r0], r2 @ Vector load from src[3_0] vld1.u32 {q4}, [r0], r2 @ Vector load from src[4_0] vld1.u32 {q5}, [r0], r2 @ Vector load from src[5_0] vaddl.u8 q7, d4, d6 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q6, d0, d10 @ temp = src[0_0] + src[5_0] vaddl.u8 q8, d2, d8 @ temp2 = src[1_0] + src[4_0] vmla.u16 q6, q7, q13 @ temp += temp1 * 20 vaddl.u8 q9, d5, d7 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q7, d1, d11 @ temp = src[0_0] + src[5_0] vaddl.u8 q11, d3, d9 @ temp2 = src[1_0] + src[4_0] vmla.u16 q7, q9, q13 @ temp += temp1 * 20 vmls.u16 q6, q8, q12 @ temp -= temp2 * 5 vld1.32 {q0}, [r0], r2 @ Vector load from src[6_0] vaddl.u8 q8, d6, d8 vmls.u16 q7, q11, q12 @ temp -= temp2 * 5 @Q6 and Q7 have filtered values vaddl.u8 q14, d2, d0 vst1.32 {q6}, [r9]! @ store row 0 to temp buffer: col 0 vext.16 q11, q6, q7, #5 vaddl.u8 q9, d4, d10 vmla.u16 q14, q8, q13 vaddl.s16 q15, d12, d22 vst1.32 {q7}, [r9], r7 @ store row 0 to temp buffer: col 1 vaddl.s16 q11, d13, d23 vext.16 q8, q6, q7, #2 vmls.u16 q14, q9, q12 vext.16 q9, q6, q7, #3 vext.16 q10, q6, q7, #4 vext.16 q7, q6, q7, #1 vadd.s16 q8, q8, q9 vadd.s16 q9, q7, q10 vaddl.u8 q10, d7, d9 vmlal.s16 q15, d16, d26 vmlsl.s16 q15, d18, d24 vmlal.s16 q11, d17, d26 vmlsl.s16 q11, d19, d24 vaddl.u8 q7, d3, d1 vst1.32 {q14}, [r9]! @ store row 1 to temp buffer: col 0 vmla.u16 q7, q10, q13 vqrshrun.s32 d12, q15, #10 vaddl.u8 q8, d5, d11 vqrshrun.s32 d13, q11, #10 vmls.u16 q7, q8, q12 vqmovn.u16 d25, q6 vaddl.u8 q8, d8, d10 vext.16 q11, q14, q7, #5 vaddl.u8 q10, d4, d2 vaddl.s16 q15, d28, d22 vmla.u16 q10, q8, q13 vst1.32 {q7}, [r9], r7 @ store row 1 to temp buffer: col 1 vaddl.s16 q11, d29, d23 vext.16 q8, q14, q7, #2 vext.16 q9, q14, q7, #3 vext.16 q6, q14, q7, #4 vext.16 q7, q14, q7, #1 vadd.s16 q8, q8, q9 vadd.s16 q9, q6, q7 vld1.32 d14, [r6], r8 @load row 0 from temp buffer vmlal.s16 q15, d16, d26 vmlsl.s16 q15, d18, d24 vmlal.s16 q11, d17, d26 vmlsl.s16 q11, d19, d24 vqrshrun.s16 d14, q7, #0x5 vld1.32 d28, [r6], r8 @load row 1 from temp buffer vaddl.u8 q9, d6, d0 vqrshrun.s32 d16, q15, #10 vqrshrun.s16 d15, q14, #0x5 vqrshrun.s32 d17, q11, #10 vmov d12, d25 vmov d25, d24 vqmovn.u16 d13, q8 vrhadd.u8 q6, q6, q7 vst1.32 d12[0], [r1], r3 @ store row 0 vst1.32 d13[0], [r1], r3 @store row 1 subs r4, r4, #2 subne r0, r0 , r2, lsl #2 subne r0, r0, r2 beq end_func @ Branch if height==4 b loop_4 @ Loop if height==8 end_func: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q.s @* @* @brief @* Contains function definitions for inter prediction interpolation. @* @* @author @* Mohit @* @* @par List of Functions: @* @* - ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_inter_pred_filters.c @ @******************************************************************************* @* @* @brief @* This function implements two six tap filters. It @* applies the six tap filter in the horizontal direction on the @* predictor values, then applies the same filter in the @* vertical direction on the predictor values. It then averages these @* two outputs to obtain quarter pel values in horizontal and vertical direction. @* The six tap filtering operation is described in sec 8.4.2.2.1 titled @* "Luma sample interpolation process" @* @* @par Description: @* This function is called to obtain pixels lying at the following @* location (1/4,1/4) or (3/4,1/4) or (1/4,3/4) or (3/4,3/4). @* The function interpolates the predictors first in the horizontal direction @* and then in the vertical direction, and then averages these two @* values. @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @param[in] pu1_tmp: temporary buffer @* @* @param[in] dydx: x and y reference offset for qpel calculations @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @*; @void ih264_inter_pred_luma_horz_qpel_vert_qpel(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd,, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd, @ UWORD8* pu1_tmp, @ UWORD32 dydx) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ht @ r5 => wd @ r6 => dydx .text .p2align 2 .global ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vstmdb sp!, {d8-d15} @push neon registers to stack ldr r4, [sp, #104] @ loads ht ldr r5, [sp, #108] @ loads wd ldr r6, [sp, #116] @dydx and r7, r6, #3 add r7, r0, r7, lsr #1 @pu1_pred_vert = pu1_src + (x_offset>>1) and r6, r6, #12 @Finds y-offset lsr r6, r6, #3 @dydx>>3 mul r6, r2, r6 add r6, r0, r6 @pu1_pred_horz = pu1_src + (y_offset>>1)*src_strd sub r7, r7, r2, lsl #1 @pu1_pred_vert-2*src_strd sub r6, r6, #2 @pu1_pred_horz-2 vmov.u8 d30, #20 @ Filter coeff 20 vmov.u8 d31, #5 @ Filter coeff 5 subs r12, r5, #4 @if wd=4 branch to loop_4 beq loop_4 subs r12, r5, #8 @if wd=8 branch to loop_8 beq loop_8 loop_16: vld1.32 {q0}, [r7], r2 @ Vector load from src[0_0] vld1.32 {q1}, [r7], r2 @ Vector load from src[1_0] vld1.32 {q2}, [r7], r2 @ Vector load from src[2_0] vld1.32 {q3}, [r7], r2 @ Vector load from src[3_0] vld1.32 {q4}, [r7], r2 @ Vector load from src[4_0] add r11, r6, #8 vld1.32 {q5}, [r7], r2 @ Vector load from src[5_0] vld1.32 {q9}, [r6], r2 @ horz row0, col 0 vaddl.u8 q12, d0, d10 vmlal.u8 q12, d4, d30 vmlal.u8 q12, d6, d30 vmlsl.u8 q12, d2, d31 vmlsl.u8 q12, d8, d31 vext.8 d23, d18, d19, #5 vext.8 d20, d18, d19, #2 vext.8 d21, d18, d19, #3 vext.8 d22, d18, d19, #4 vext.8 d19, d18, d19, #1 vqrshrun.s16 d26, q12, #5 vaddl.u8 q14, d18, d23 vmlal.u8 q14, d20, d30 vmlal.u8 q14, d21, d30 vmlsl.u8 q14, d19, d31 vmlsl.u8 q14, d22, d31 vld1.32 {q9}, [r11], r2 @ horz row 0, col 1 vaddl.u8 q12, d1, d11 vmlal.u8 q12, d5, d30 vmlal.u8 q12, d7, d30 vmlsl.u8 q12, d3, d31 vmlsl.u8 q12, d9, d31 vqrshrun.s16 d28, q14, #5 vext.8 d23, d18, d19, #5 vext.8 d20, d18, d19, #2 vext.8 d21, d18, d19, #3 vext.8 d22, d18, d19, #4 vext.8 d19, d18, d19, #1 vqrshrun.s16 d27, q12, #5 vld1.32 {q6}, [r7], r2 @ src[6_0] vaddl.u8 q12, d18, d23 vmlal.u8 q12, d20, d30 vmlal.u8 q12, d21, d30 vmlsl.u8 q12, d19, d31 vmlsl.u8 q12, d22, d31 vaddl.u8 q8, d2, d12 vmlal.u8 q8, d6, d30 vmlal.u8 q8, d8, d30 vmlsl.u8 q8, d4, d31 vmlsl.u8 q8, d10, d31 vqrshrun.s16 d29, q12, #5 vld1.32 {q9}, [r6], r2 @ horz row 1, col 0 vaddl.u8 q12, d3, d13 vmlal.u8 q12, d7, d30 vmlal.u8 q12, d9, d30 vmlsl.u8 q12, d5, d31 vmlsl.u8 q12, d11, d31 vrhadd.u8 q14, q14, q13 vqrshrun.s16 d26, q8, #5 vext.8 d23, d18, d19, #5 vext.8 d20, d18, d19, #2 vext.8 d21, d18, d19, #3 vext.8 d22, d18, d19, #4 vst1.32 {q14}, [r1], r3 @ store row 0 vext.8 d19, d18, d19, #1 vqrshrun.s16 d27, q12, #5 vaddl.u8 q14, d18, d23 vmlal.u8 q14, d20, d30 vmlal.u8 q14, d21, d30 vmlsl.u8 q14, d19, d31 vmlsl.u8 q14, d22, d31 vld1.32 {q9}, [r11], r2 @ horz row 1, col 1 vext.8 d23, d18, d19, #5 vext.8 d20, d18, d19, #2 vext.8 d21, d18, d19, #3 vext.8 d22, d18, d19, #4 vext.8 d19, d18, d19, #1 vqrshrun.s16 d28, q14, #5 vaddl.u8 q12, d18, d23 vmlal.u8 q12, d20, d30 vmlal.u8 q12, d21, d30 vmlsl.u8 q12, d19, d31 vmlsl.u8 q12, d22, d31 vqrshrun.s16 d29, q12, #5 vrhadd.u8 q14, q14, q13 vst1.32 {q14}, [r1], r3 @ store row 1 subs r4, r4, #2 @ 2 rows processed, decrement by 2 subne r7, r7 , r2, lsl #2 subne r7, r7, r2 beq end_func @ Branch if height==4 b loop_16 @ looping if height = 8 or 16 loop_8: vld1.32 d0, [r7], r2 @ Vector load from src[0_0] vld1.32 d1, [r7], r2 @ Vector load from src[1_0] vld1.32 d2, [r7], r2 @ Vector load from src[2_0] vld1.32 d3, [r7], r2 @ Vector load from src[3_0] vld1.32 d4, [r7], r2 @ Vector load from src[4_0] vld1.32 d5, [r7], r2 @ Vector load from src[5_0] vaddl.u8 q5, d0, d5 vmlal.u8 q5, d2, d30 vmlal.u8 q5, d3, d30 vmlsl.u8 q5, d1, d31 vmlsl.u8 q5, d4, d31 vld1.32 {q6}, [r6], r2 @horz row 0 vext.8 d17, d12, d13, #5 vext.8 d14, d12, d13, #2 vext.8 d15, d12, d13, #3 vext.8 d16, d12, d13, #4 vext.8 d13, d12, d13, #1 vqrshrun.s16 d26, q5, #5 vld1.32 d6, [r7], r2 @ src[6_0] vaddl.u8 q5, d12, d17 vmlal.u8 q5, d14, d30 vmlal.u8 q5, d15, d30 vmlsl.u8 q5, d13, d31 vmlsl.u8 q5, d16, d31 vld1.32 {q6}, [r6], r2 @ horz row 1 vaddl.u8 q9, d1, d6 vmlal.u8 q9, d3, d30 vmlal.u8 q9, d4, d30 vmlsl.u8 q9, d2, d31 vmlsl.u8 q9, d5, d31 vqrshrun.s16 d28, q5, #5 vext.8 d17, d12, d13, #5 vext.8 d14, d12, d13, #2 vext.8 d15, d12, d13, #3 vext.8 d16, d12, d13, #4 vext.8 d13, d12, d13, #1 vqrshrun.s16 d27, q9, #5 vaddl.u8 q5, d12, d17 vmlal.u8 q5, d14, d30 vmlal.u8 q5, d15, d30 vmlsl.u8 q5, d13, d31 vmlsl.u8 q5, d16, d31 vqrshrun.s16 d29, q5, #5 vrhadd.u8 q13, q13, q14 vst1.32 d26, [r1], r3 vst1.32 d27, [r1], r3 subs r4, r4, #2 @ 2 rows processed, decrement by 2 subne r7, r7 , r2, lsl #2 subne r7, r7, r2 beq end_func @ Branch if height==4 b loop_8 @looping if height == 8 or 16 loop_4: vld1.32 d0[0], [r7], r2 @ Vector load from src[0_0] vld1.32 d1[0], [r7], r2 @ Vector load from src[1_0] vld1.32 d2[0], [r7], r2 @ Vector load from src[2_0] vld1.32 d3[0], [r7], r2 @ Vector load from src[3_0] vld1.32 d4[0], [r7], r2 @ Vector load from src[4_0] vld1.32 d5[0], [r7], r2 @ Vector load from src[5_0] vaddl.u8 q5, d0, d5 vmlal.u8 q5, d2, d30 vmlal.u8 q5, d3, d30 vmlsl.u8 q5, d1, d31 vmlsl.u8 q5, d4, d31 vld1.32 {q6}, [r6], r2 @load for horz filter row 0 vext.8 d17, d12, d13, #5 vext.8 d14, d12, d13, #2 vext.8 d15, d12, d13, #3 vext.8 d16, d12, d13, #4 vext.8 d13, d12, d13, #1 vqrshrun.s16 d26, q5, #5 vld1.32 d6[0], [r7], r2 @ Vector load from src[6_0] vaddl.u8 q5, d12, d17 vmlal.u8 q5, d14, d30 vmlal.u8 q5, d15, d30 vmlsl.u8 q5, d13, d31 vmlsl.u8 q5, d16, d31 vld1.32 {q6}, [r6], r2 @horz row 1 vaddl.u8 q9, d1, d6 vmlal.u8 q9, d3, d30 vmlal.u8 q9, d4, d30 vmlsl.u8 q9, d2, d31 vmlsl.u8 q9, d5, d31 vqrshrun.s16 d28, q5, #5 vext.8 d17, d12, d13, #5 vext.8 d14, d12, d13, #2 vext.8 d15, d12, d13, #3 vext.8 d16, d12, d13, #4 vext.8 d13, d12, d13, #1 vqrshrun.s16 d27, q9, #5 vaddl.u8 q5, d12, d17 vmlal.u8 q5, d14, d30 vmlal.u8 q5, d15, d30 vmlsl.u8 q5, d13, d31 vmlsl.u8 q5, d16, d31 vqrshrun.s16 d29, q5, #5 vrhadd.u8 q13, q13, q14 vst1.32 d26[0], [r1], r3 vst1.32 d27[0], [r1], r3 subs r4, r4, #2 @ 2 rows processed, decrement by 2 subne r7, r7 , r2, lsl #2 subne r7, r7, r2 beq end_func @ Branch if height==4 b loop_4 @ Loop if height==8 end_func: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_inter_pred_luma_vert_qpel_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_inter_pred_luma_vert_qpel_a9q.s @* @* @brief @* Contains function definitions for inter prediction vertical quarter pel interpolation. @* @* @author @* Mohit @* @* @par List of Functions: @* @* - ih264_inter_pred_luma_vert_qpel_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_inter_pred_filters.c @ @******************************************************************************* @* @* @brief @* Quarter pel interprediction luma filter for vertical input @* @* @par Description: @* Applies a 6 tap horizontal filter .The output is clipped to 8 bits @* sec 8.4.2.2.1 titled "Luma sample interpolation process" @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @param[in] pu1_tmp: temporary buffer: UNUSED in this function @* @* @param[in] dydx: x and y reference offset for qpel calculations. @* @returns @* @ @remarks @* None @* @******************************************************************************* @* @void ih264_inter_pred_luma_vert ( @ UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ht, @ WORD32 wd, @ UWORD8* pu1_tmp, @ UWORD32 dydx) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r5 => ht @ r6 => wd @ r7 => dydx .text .p2align 2 .global ih264_inter_pred_luma_vert_qpel_a9q ih264_inter_pred_luma_vert_qpel_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vstmdb sp!, {d8-d15} @push neon registers to stack ldr r5, [sp, #104] @Loads ht ldr r6, [sp, #108] @Loads wd ldr r7, [sp, #116] @Loads dydx and r7, r7, #12 @Finds y-offset lsr r7, r7, #3 @dydx>>3 mul r7, r2, r7 add r7, r0, r7 @pu1_src + (y_offset>>1)*src_strd vmov.u16 q11, #20 @ Filter coeff 0x14 into Q11 sub r0, r0, r2, lsl #1 @pu1_src-2*src_strd subs r12, r6, #8 @if wd=8 branch to loop_8 vmov.u16 q12, #5 @ Filter coeff 0x5 into Q12 beq loop_8 subs r12, r6, #4 @if wd=4 branch to loop_4 beq loop_4 loop_16: @when wd=16 vld1.u32 {q0}, [r0], r2 @ Vector load from src[0_0] vld1.u32 {q1}, [r0], r2 @ Vector load from src[1_0] vld1.u32 {q2}, [r0], r2 @ Vector load from src[2_0] vld1.u32 {q3}, [r0], r2 @ Vector load from src[3_0] vld1.u32 {q4}, [r0], r2 @ Vector load from src[4_0] vaddl.u8 q6, d4, d6 @ temp1 = src[2_0] + src[3_0] vld1.u32 {q5}, [r0], r2 @ Vector load from src[5_0] vaddl.u8 q7, d0, d10 @ temp = src[0_0] + src[5_0] vaddl.u8 q8, d2, d8 @ temp2 = src[1_0] + src[4_0] vmla.u16 q7, q6, q11 @ temp += temp1 * 20 vaddl.u8 q10, d1, d11 @ temp4 = src[0_8] + src[5_8] vaddl.u8 q9, d5, d7 @ temp3 = src[2_8] + src[3_8] vmla.u16 q10, q9, q11 @ temp4 += temp3 * 20 vld1.u32 {q0}, [r0], r2 vaddl.u8 q13, d3, d9 @ temp5 = src[1_8] + src[4_8] vaddl.u8 q6, d6, d8 vmls.u16 q7, q8, q12 @ temp -= temp2 * 5 vaddl.u8 q8, d2, d0 vaddl.u8 q9, d4, d10 vmla.u16 q8, q6, q11 vmls.u16 q10, q13, q12 @ temp4 -= temp5 * 5 vaddl.u8 q13, d5, d11 vaddl.u8 q6, d7, d9 vqrshrun.s16 d30, q7, #5 @ dst[0_0] = CLIP_U8((temp +16) >> 5) vaddl.u8 q7, d3, d1 vld1.u32 {q1}, [r0], r2 vmla.u16 q7, q6, q11 vmls.u16 q8, q9, q12 vqrshrun.s16 d31, q10, #5 @ dst[0_8] = CLIP_U8((temp4 +16) >> 5) vld1.u32 {q10}, [r7], r2 @ Load for interpolation row 0 vrhadd.u8 q15, q10, q15 @ Interpolation to obtain qpel value vaddl.u8 q9, d4, d2 vaddl.u8 q6, d8, d10 vst1.u32 {q15}, [r1], r3 @ Vector store to dst[0_0] vmla.u16 q9, q6, q11 vaddl.u8 q10, d6, d0 vmls.u16 q7, q13, q12 vqrshrun.s16 d30, q8, #5 vaddl.u8 q6, d9, d11 vaddl.u8 q8, d5, d3 vaddl.u8 q13, d7, d1 vmla.u16 q8, q6, q11 vmls.u16 q9, q10, q12 vld1.u32 {q2}, [r0], r2 vqrshrun.s16 d31, q7, #5 vld1.u32 {q7}, [r7], r2 @ Load for interpolation row 1 vaddl.u8 q6, d10, d0 vrhadd.u8 q15, q7, q15 @ Interpolation to obtain qpel value vaddl.u8 q7, d6, d4 vaddl.u8 q10, d8, d2 vmla.u16 q7, q6, q11 vmls.u16 q8, q13, q12 vst1.u32 {q15}, [r1], r3 @store row 1 vqrshrun.s16 d30, q9, #5 vaddl.u8 q9, d7, d5 vaddl.u8 q6, d11, d1 vmla.u16 q9, q6, q11 vaddl.u8 q13, d9, d3 vmls.u16 q7, q10, q12 vqrshrun.s16 d31, q8, #5 vld1.u32 {q8}, [r7], r2 @ Load for interpolation row 2 vmls.u16 q9, q13, q12 vrhadd.u8 q15, q8, q15 @ Interpolation to obtain qpel value vaddl.u8 q6, d0, d2 @ temp1 = src[2_0] + src[3_0] vst1.u32 {q15}, [r1], r3 @store row 2 vaddl.u8 q8, d10, d4 @ temp2 = src[1_0] + src[4_0] vaddl.u8 q10, d9, d7 @ temp4 = src[0_8] + src[5_8] vqrshrun.s16 d30, q7, #5 vaddl.u8 q13, d5, d11 @ temp5 = src[1_8] + src[4_8] vaddl.u8 q7, d8, d6 @ temp = src[0_0] + src[5_0] vqrshrun.s16 d31, q9, #5 vld1.u32 {q9}, [r7], r2 @ Load for interpolation row 3 vmla.u16 q7, q6, q11 @ temp += temp1 * 20 vrhadd.u8 q15, q9, q15 @ Interpolation to obtain qpel value vaddl.u8 q9, d1, d3 @ temp3 = src[2_8] + src[3_8] vst1.u32 {q15}, [r1], r3 @store row 3 subs r5, r5, #4 @ 4 rows processed, decrement by 4 subne r0, r0 , r2, lsl #2 subne r0, r0, r2 beq end_func @ Branch if height==4 b loop_16 @ looping if height = 8 or 16 loop_8: @ Processing row0 and row1 vld1.u32 d0, [r0], r2 @ Vector load from src[0_0] vld1.u32 d1, [r0], r2 @ Vector load from src[1_0] vld1.u32 d2, [r0], r2 @ Vector load from src[2_0] vld1.u32 d3, [r0], r2 @ Vector load from src[3_0] vld1.u32 d4, [r0], r2 @ Vector load from src[4_0] vld1.u32 d5, [r0], r2 @ Vector load from src[5_0] vaddl.u8 q3, d2, d3 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q4, d0, d5 @ temp = src[0_0] + src[5_0] vaddl.u8 q5, d1, d4 @ temp2 = src[1_0] + src[4_0] vmla.u16 q4, q3, q11 @ temp += temp1 * 20 vld1.u32 d6, [r0], r2 vaddl.u8 q7, d3, d4 vaddl.u8 q8, d1, d6 vaddl.u8 q9, d2, d5 vmls.u16 q4, q5, q12 @ temp -= temp2 * 5 vmla.u16 q8, q7, q11 vld1.u32 d7, [r0], r2 vaddl.u8 q10, d4, d5 vaddl.u8 q6, d2, d7 vaddl.u8 q5, d3, d6 vmls.u16 q8, q9, q12 vqrshrun.s16 d26, q4, #5 @ dst[0_0] = CLIP_U8( (temp + 16) >> 5) vmla.u16 q6, q10, q11 vld1.32 d8, [r7], r2 @Load value for interpolation (row0) vld1.32 d9, [r7], r2 @Load value for interpolation (row1) vld1.u32 d0, [r0], r2 vaddl.u8 q7, d5, d6 vqrshrun.s16 d27, q8, #5 vrhadd.u8 q13, q4, q13 @ Interpolation step for qpel calculation vaddl.u8 q10, d3, d0 vmls.u16 q6, q5, q12 vst1.u32 d26, [r1], r3 @ Vector store to dst[0_0] vaddl.u8 q9, d4, d7 vmla.u16 q10, q7, q11 vst1.u32 d27, [r1], r3 @ Vector store to dst[1_0] vqrshrun.s16 d28, q6, #5 vmls.u16 q10, q9, q12 vld1.32 d12, [r7], r2 @Load value for interpolation (row2) vld1.32 d13, [r7], r2 @Load value for interpolation (row3) vqrshrun.s16 d29, q10, #5 subs r9, r5, #4 vrhadd.u8 q14, q6, q14 vst1.u32 d28, [r1], r3 @store row 2 vst1.u32 d29, [r1], r3 @store row 3 subs r5, r5, #4 @ 4 rows processed, decrement by 4 subne r0, r0 , r2, lsl #2 subne r0, r0, r2 beq end_func @ Branch if height==4 b loop_8 @looping if height == 8 or 16 loop_4: @ Processing row0 and row1 vld1.u32 d0[0], [r0], r2 @ Vector load from src[0_0] vld1.u32 d1[0], [r0], r2 @ Vector load from src[1_0] vld1.u32 d2[0], [r0], r2 @ Vector load from src[2_0] vld1.u32 d3[0], [r0], r2 @ Vector load from src[3_0] vld1.u32 d4[0], [r0], r2 @ Vector load from src[4_0] vld1.u32 d5[0], [r0], r2 @ Vector load from src[5_0] vaddl.u8 q3, d2, d3 @ temp1 = src[2_0] + src[3_0] vaddl.u8 q4, d0, d5 @ temp = src[0_0] + src[5_0] vaddl.u8 q5, d1, d4 @ temp2 = src[1_0] + src[4_0] vmla.u16 q4, q3, q11 @ temp += temp1 * 20 vld1.u32 d6, [r0], r2 vaddl.u8 q7, d3, d4 vaddl.u8 q8, d1, d6 vaddl.u8 q9, d2, d5 vmls.u16 q4, q5, q12 @ temp -= temp2 * 5 vld1.u32 d7[0], [r0], r2 vmla.u16 q8, q7, q11 vaddl.u8 q10, d4, d5 vaddl.u8 q6, d2, d7 vaddl.u8 q5, d3, d6 vmls.u16 q8, q9, q12 vqrshrun.s16 d26, q4, #5 @ dst[0_0] = CLIP_U8( (temp + 16) >> 5) vld1.u32 d8[0], [r7], r2 @Load value for interpolation - row 0 vld1.u32 d9[0], [r7], r2 @Load value for interpolation - row 1 vmla.u16 q6, q10, q11 vld1.u32 d0[0], [r0], r2 vaddl.u8 q7, d5, d6 vqrshrun.s16 d27, q8, #5 vaddl.u8 q10, d3, d0 vrhadd.u8 q13, q13, q4 @Interpolation step for qpel calculation vmls.u16 q6, q5, q12 vst1.u32 d26[0], [r1], r3 @ Vector store to dst[0_0] vaddl.u8 q9, d4, d7 vmla.u16 q10, q7, q11 vst1.u32 d27[0], [r1], r3 @ store row 1 vqrshrun.s16 d28, q6, #5 vld1.u32 d12[0], [r7], r2 @Load value for interpolation - row 2 vld1.u32 d13[0], [r7], r2 @Load value for interpolation - row 3 vmls.u16 q10, q9, q12 vqrshrun.s16 d29, q10, #5 vrhadd.u8 q14, q6, q14 @Interpolation step for qpel calculation vst1.u32 d28[0], [r1], r3 @store row 2 vst1.u32 d29[0], [r1], r3 @store row 3 subs r5, r5, #8 subeq r0, r0, r2, lsl #2 subeq r0, r0, r2 beq loop_4 @ Loop if height==8 end_func: vldmia sp!, {d8-d15} @ Restore neon registers that were saved ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_intra_pred_chroma_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_intra_pred_chroma_a9q.s @* @* @brief @* Contains function definitions for intra chroma prediction . @* @* @author @* Ittiam @* @* @par List of Functions: @* @* - ih264_intra_pred_chroma_mode_horz_a9q() @* - ih264_intra_pred_chroma_8x8_mode_vert_a9q() @* - ih264_intra_pred_chroma_mode_dc_a9q() @* - ih264_intra_pred_chroma_mode_plane_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_chroma_intra_pred_filters.c @ .text .p2align 2 .extern ih264_gai1_intrapred_chroma_plane_coeffs1 .hidden ih264_gai1_intrapred_chroma_plane_coeffs1 .extern ih264_gai1_intrapred_chroma_plane_coeffs2 .hidden ih264_gai1_intrapred_chroma_plane_coeffs2 scratch_chroma_intrapred_addr1: .long ih264_gai1_intrapred_chroma_plane_coeffs1 - scrlblc1 - 8 scratch_intrapred_chroma_plane_addr1: .long ih264_gai1_intrapred_chroma_plane_coeffs2 - scrlblc2 - 8 @** @******************************************************************************* @* @*ih264_intra_pred_chroma_8x8_mode_dc @* @* @brief @* Perform Intra prediction for chroma_8x8 mode:DC @* @* @par Description: @* Perform Intra prediction for chroma_8x8 mode:DC ,described in sec 8.3.4.1 @* @* @param[in] pu1_src @* UWORD8 pointer to the source containing alternate U and V samples @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination with alternate U and V samples @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @** @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_chroma_8x8_mode_dc(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_chroma_8x8_mode_dc_a9q ih264_intra_pred_chroma_8x8_mode_dc_a9q: stmfd sp!, {r4, r14} @store register values to stack ldr r4, [sp, #8] @r4 => ui_neighboravailability vpush {d8-d15} ands r2, r4, #0x01 @CHECKING IF LEFT_AVAILABLE ELSE BRANCHING TO ONLY TOP AVAILABLE beq top_available ands r2, r4, #0x04 @CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE beq left_available vld1.u8 {q0}, [r0] @BOTH LEFT AND TOP AVAILABLE add r0, r0, #18 vld1.u8 {q1}, [r0] vaddl.u8 q2, d1, d2 vaddl.u8 q3, d0, d3 vmovl.u8 q1, d3 vmovl.u8 q0, d0 vadd.u16 d12, d4, d5 vadd.u16 d13, d2, d3 vadd.u16 d15, d6, d7 vadd.u16 d14, d0, d1 vpadd.u32 d12, d12, d15 vpadd.u32 d14, d13, d14 vqrshrun.s16 d12, q6, #3 vqrshrun.s16 d14, q7, #2 vdup.u16 d8, d12[0] vdup.u16 d9, d14[0] vdup.u16 d10, d14[1] vdup.u16 d11, d12[1] b str_pred top_available: @ONLY TOP AVAILABLE ands r2, r4, #0x04 @CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE beq none_available add r0, r0, #18 vld1.u8 {q0}, [r0] vmovl.u8 q1, d0 vmovl.u8 q2, d1 vadd.u16 d0, d2, d3 vadd.u16 d1, d4, d5 vpaddl.u32 q0, q0 vqrshrun.s16 d0, q0, #2 vdup.u16 d8, d0[0] vdup.u16 d9, d0[2] vmov q5, q4 b str_pred left_available: @ONLY LEFT AVAILABLE vld1.u8 {q0}, [r0] vmovl.u8 q1, d0 vmovl.u8 q2, d1 vadd.u16 d0, d2, d3 vadd.u16 d1, d4, d5 vpaddl.u32 q0, q0 vqrshrun.s16 d0, q0, #2 vdup.u16 q5, d0[0] vdup.u16 q4, d0[2] b str_pred none_available: @NONE AVAILABLE vmov.u8 q4, #128 vmov.u8 q5, #128 str_pred: vst1.8 {q4}, [r1], r3 vst1.8 {q4}, [r1], r3 vst1.8 {q4}, [r1], r3 vst1.8 {q4}, [r1], r3 vst1.8 {q5}, [r1], r3 vst1.8 {q5}, [r1], r3 vst1.8 {q5}, [r1], r3 vst1.8 {q5}, [r1], r3 vpop {d8-d15} ldmfd sp!, {r4, pc} @Restoring registers from stack @****************************************************************************** @** @******************************************************************************* @* @*ih264_intra_pred_chroma_8x8_mode_horz @* @* @brief @* Perform Intra prediction for chroma_8x8 mode:Horizontal @* @* @par Description: @* Perform Intra prediction for chroma_8x8 mode:Horizontal ,described in sec 8.3.4.2 @* @* @param[in] pu1_src @* UWORD8 pointer to the source containing alternate U and V samples @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination with alternate U and V samples @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels(Not used in this function) @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @void ih264_intra_pred_chroma_8x8_mode_horz(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_chroma_8x8_mode_horz_a9q ih264_intra_pred_chroma_8x8_mode_horz_a9q: stmfd sp!, {r14} @store register values to stack vld1.u8 {q0}, [r0] mov r2, #6 vdup.u16 q1, d1[3] vdup.u16 q2, d1[2] vst1.8 {q1}, [r1], r3 loop_8x8_horz: vext.8 q0, q0, q0, #12 vst1.8 {q2}, [r1], r3 vdup.u16 q1, d1[3] subs r2, #2 vdup.u16 q2, d1[2] vst1.8 {q1}, [r1], r3 bne loop_8x8_horz vext.8 q0, q0, q0, #12 vst1.8 {q2}, [r1], r3 ldmfd sp!, {pc} @restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_chroma_8x8_mode_vert @* @* @brief @* Perform Intra prediction for chroma_8x8 mode:vertical @* @* @par Description: @*Perform Intra prediction for chroma_8x8 mode:vertical ,described in sec 8.3.4.3 @* @* @param[in] pu1_src @* UWORD8 pointer to the source containing alternate U and V samples @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination with alternate U and V samples @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels(Not used in this function) @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_chroma_8x8_mode_vert(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_chroma_8x8_mode_vert_a9q ih264_intra_pred_chroma_8x8_mode_vert_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack add r0, r0, #18 vld1.8 {q0}, [r0] vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @****************************************************************************** @** @******************************************************************************* @* @*ih264_intra_pred_chroma_8x8_mode_plane @* @* @brief @* Perform Intra prediction for chroma_8x8 mode:PLANE @* @* @par Description: @* Perform Intra prediction for chroma_8x8 mode:PLANE ,described in sec 8.3.4.4 @* @* @param[in] pu1_src @* UWORD8 pointer to the source containing alternate U and V samples @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination with alternate U and V samples @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_chroma_8x8_mode_plane(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_chroma_8x8_mode_plane_a9q ih264_intra_pred_chroma_8x8_mode_plane_a9q: stmfd sp!, {r4-r10, r12, lr} vpush {d8-d15} vld1.32 d0, [r0] add r10, r0, #10 vld1.32 d1, [r10] add r10, r10, #6 vrev64.16 d5, d0 vld1.32 d2, [r10]! add r10, r10, #2 vrev64.16 d7, d2 vld1.32 d3, [r10] sub r5, r3, #8 ldr r12, scratch_chroma_intrapred_addr1 scrlblc1: add r12, r12, pc vsubl.u8 q5, d5, d1 vld1.64 {q4}, [r12] @ Load multiplication factors 1 to 8 into D3 vsubl.u8 q6, d3, d7 vmul.s16 q7, q5, q4 vmul.s16 q8, q6, q4 vuzp.16 q7, q8 vpadd.s16 d14, d14 vpadd.s16 d15, d15 vpadd.s16 d16, d16 vpadd.s16 d17, d17 vpadd.s16 d14, d14 vpadd.s16 d15, d15 vpadd.s16 d16, d16 vpadd.s16 d17, d17 mov r6, #34 vdup.16 q9, r6 vmull.s16 q11, d14, d18 vmull.s16 q12, d15, d18 vmull.s16 q13, d16, d18 vmull.s16 q14, d17, d18 vrshrn.s32 d10, q11, #6 vrshrn.s32 d12, q12, #6 vrshrn.s32 d13, q13, #6 vrshrn.s32 d14, q14, #6 ldrb r6, [r0], #1 add r10, r0, #31 ldrb r8, [r0], #1 ldrb r7, [r10], #1 ldrb r9, [r10], #1 add r6, r6, r7 add r8, r8, r9 lsl r6, r6, #4 lsl r8, r8, #4 vdup.16 q0, r6 vdup.16 q1, r8 vdup.16 q2, d12[0] vdup.16 q3, d10[0] vdup.16 q12, d14[0] vdup.16 q13, d13[0] vzip.16 q2, q12 vzip.16 q3, q13 vzip.16 q0, q1 ldr r12, scratch_intrapred_chroma_plane_addr1 scrlblc2: add r12, r12, pc vld1.64 {q4}, [r12] vmov.16 q5, q4 vmov q11, q4 vzip.16 q4, q5 vmul.s16 q6, q2, q4 vmul.s16 q8, q2, q5 vadd.s16 q6, q0, q6 vadd.s16 q8, q0, q8 vdup.16 q10, d22[0] vmul.s16 q2, q3, q10 vdup.16 q15, d22[1] vmul.s16 q9, q3, q10 vmul.s16 q7, q3, q15 vmul.s16 q4, q3, q15 vadd.s16 q12, q6, q2 vadd.s16 q0, q8, q9 vadd.s16 q1, q6, q7 vqrshrun.s16 d28, q12, #5 vadd.s16 q13, q8, q4 vqrshrun.s16 d29, q0, #5 vdup.16 q10, d22[2] vst1.8 {q14}, [r1], r3 vqrshrun.s16 d28, q1, #5 vqrshrun.s16 d29, q13, #5 vmul.s16 q2, q3, q10 vmul.s16 q9, q3, q10 vst1.8 {q14}, [r1], r3 vadd.s16 q12, q6, q2 vadd.s16 q0, q8, q9 vdup.16 q15, d22[3] vqrshrun.s16 d28, q12, #5 vqrshrun.s16 d29, q0, #5 vmul.s16 q7, q3, q15 vmul.s16 q4, q3, q15 vst1.8 {q14}, [r1], r3 vadd.s16 q1, q6, q7 vadd.s16 q13, q8, q4 vdup.16 q10, d23[0] vqrshrun.s16 d28, q1, #5 vqrshrun.s16 d29, q13, #5 vmul.s16 q2, q3, q10 vmul.s16 q9, q3, q10 vst1.8 {q14}, [r1], r3 vadd.s16 q12, q6, q2 vadd.s16 q0, q8, q9 vdup.16 q15, d23[1] vqrshrun.s16 d28, q12, #5 vqrshrun.s16 d29, q0, #5 vmul.s16 q7, q3, q15 vmul.s16 q4, q3, q15 vst1.8 {q14}, [r1], r3 vadd.s16 q1, q6, q7 vadd.s16 q13, q8, q4 vdup.16 q10, d23[2] vqrshrun.s16 d28, q1, #5 vqrshrun.s16 d29, q13, #5 vmul.s16 q2, q3, q10 vmul.s16 q9, q3, q10 vst1.8 {q14}, [r1], r3 vadd.s16 q12, q6, q2 vadd.s16 q0, q8, q9 vdup.16 q15, d23[3] vqrshrun.s16 d28, q12, #5 vqrshrun.s16 d29, q0, #5 vmul.s16 q7, q3, q15 vmul.s16 q4, q3, q15 vst1.8 {q14}, [r1], r3 vadd.s16 q1, q6, q7 vadd.s16 q13, q8, q4 vqrshrun.s16 d28, q1, #5 vqrshrun.s16 d29, q13, #5 vst1.8 {q14}, [r1], r3 end_func_plane: vpop {d8-d15} ldmfd sp!, {r4-r10, r12, pc} ================================================ FILE: dependencies/ih264d/common/arm/ih264_intra_pred_luma_16x16_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_intra_pred_luma_16x16_a9q.s @* @* @brief @* Contains function definitions for intra 16x16 Luma prediction . @* @* @author @* Ittiam @* @* @par List of Functions: @* @* - ih264_intra_pred_luma_16x16_mode_vert_a9q() @* - ih264_intra_pred_luma_16x16_mode_horz_a9q() @* - ih264_intra_pred_luma_16x16_mode_dc_a9q() @* - ih264_intra_pred_luma_16x16_mode_plane_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_intra_pred_filters.c @ @** @** @** @ .text .p2align 2 .extern ih264_gai1_intrapred_luma_plane_coeffs .hidden ih264_gai1_intrapred_luma_plane_coeffs scratch_intrapred_addr1: .long ih264_gai1_intrapred_luma_plane_coeffs - scrlbl1 - 8 @** @******************************************************************************* @* @*ih264_intra_pred_luma_16x16_mode_vert @* @* @brief @* Perform Intra prediction for luma_16x16 mode:vertical @* @* @par Description: @* Perform Intra prediction for luma_16x16 mode:Vertical ,described in sec 8.3.3.1 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels(Not used in this function) @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_16x16_mode_vert(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_16x16_mode_vert_a9q ih264_intra_pred_luma_16x16_mode_vert_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack add r0, r0, #17 vld1.8 {q0}, [r0] vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @****************************************************************************** @** @******************************************************************************* @* @*ih264_intra_pred_luma_16x16_mode_horz @* @* @brief @* Perform Intra prediction for luma_16x16 mode:horizontal @* @* @par Description: @* Perform Intra prediction for luma_16x16 mode:horizontal ,described in sec 8.3.3.2 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels(Not used in this function) @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @void ih264_intra_pred_luma_16x16_mode_horz(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_16x16_mode_horz_a9q ih264_intra_pred_luma_16x16_mode_horz_a9q: stmfd sp!, {r14} @store register values to stack vld1.u8 {q0}, [r0] mov r2, #14 vdup.u8 q1, d1[7] vdup.u8 q2, d1[6] vst1.8 {q1}, [r1], r3 loop_16x16_horz: vext.8 q0, q0, q0, #14 vst1.8 {q2}, [r1], r3 vdup.u8 q1, d1[7] subs r2, #2 vdup.u8 q2, d1[6] vst1.8 {q1}, [r1], r3 bne loop_16x16_horz vext.8 q0, q0, q0, #14 vst1.8 {q2}, [r1], r3 ldmfd sp!, {pc} @Restoring registers from stack @****************************************************************************** @** @******************************************************************************* @* @*ih264_intra_pred_luma_16x16_mode_dc @* @* @brief @* Perform Intra prediction for luma_16x16 mode:DC @* @* @par Description: @* Perform Intra prediction for luma_16x16 mode:DC ,described in sec 8.3.3.3 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_16x16_mode_dc(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_16x16_mode_dc_a9q ih264_intra_pred_luma_16x16_mode_dc_a9q: stmfd sp!, {r4, r14} @store register values to stack ldr r4, [sp, #8] @r4 => ui_neighboravailability ands r2, r4, #0x01 @CHECKING IF LEFT_AVAILABLE ELSE BRANCHING TO ONLY TOP AVAILABLE beq top_available ands r2, r4, #0x04 @CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE beq left_available vld1.u8 {q0}, [r0] @BOTH LEFT AND TOP AVAILABLE add r0, r0, #17 vpaddl.u8 q0, q0 vld1.u8 {q1}, [r0] vpaddl.u8 q1, q1 vadd.u16 q0, q0, q1 vadd.u16 d0, d0, d1 vpaddl.u16 d0, d0 vpaddl.u32 d0, d0 vqrshrun.s16 d0, q0, #5 vdup.u8 q0, d0[0] b str_pred top_available: @ONLY TOP AVAILABLE ands r2, r4, #0x04 @CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE beq none_available add r0, r0, #17 vld1.u8 {q0}, [r0] vpaddl.u8 q0, q0 vadd.u16 d0, d0, d1 vpaddl.u16 d0, d0 vpaddl.u32 d0, d0 vqrshrun.s16 d0, q0, #4 vdup.u8 q0, d0[0] b str_pred left_available: @ONLY LEFT AVAILABLE vld1.u8 {q0}, [r0] vpaddl.u8 q0, q0 vadd.u16 d0, d0, d1 vpaddl.u16 d0, d0 vpaddl.u32 d0, d0 vqrshrun.s16 d0, q0, #4 vdup.u8 q0, d0[0] b str_pred none_available: @NONE AVAILABLE vmov.u8 q0, #128 str_pred: vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 vst1.8 {q0}, [r1], r3 ldmfd sp!, {r4, pc} @Restoring registers from stack @****************************************************************************** @** @******************************************************************************* @* @*ih264_intra_pred_luma_16x16_mode_plane @* @* @brief @* Perform Intra prediction for luma_16x16 mode:PLANE @* @* @par Description: @* Perform Intra prediction for luma_16x16 mode:PLANE ,described in sec 8.3.3.4 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_16x16_mode_plane(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_16x16_mode_plane_a9q ih264_intra_pred_luma_16x16_mode_plane_a9q: stmfd sp!, {r4-r10, r12, lr} mov r2, r1 add r1, r0, #17 add r0, r0, #15 mov r8, #9 sub r1, r1, #1 mov r10, r1 @top_left mov r4, #-1 vld1.32 d2, [r1], r8 ldr r7, scratch_intrapred_addr1 scrlbl1: add r7, r7, pc vld1.32 d0, [r1] vrev64.8 d2, d2 vld1.32 {q3}, [r7] vsubl.u8 q0, d0, d2 vmovl.u8 q8, d6 vmul.s16 q0, q0, q8 vmovl.u8 q9, d7 add r7, r0, r4, lsl #3 sub r0, r7, r4, lsl #1 neg lr, r4 vpadd.s16 d0, d0, d1 ldrb r8, [r7], r4 ldrb r9, [r0], lr vpaddl.s16 d0, d0 sub r12, r8, r9 ldrb r8, [r7], r4 vpaddl.s32 d0, d0 ldrb r9, [r0], lr sub r8, r8, r9 vshl.s32 d2, d0, #2 add r12, r12, r8, lsl #1 vadd.s32 d0, d0, d2 ldrb r8, [r7], r4 ldrb r9, [r0], lr vrshr.s32 d0, d0, #6 @ i_b = D0[0] sub r8, r8, r9 ldrb r5, [r7], r4 add r8, r8, r8, lsl #1 vdup.16 q2, d0[0] add r12, r12, r8 ldrb r9, [r0], lr vmul.s16 q0, q2, q8 sub r5, r5, r9 vmul.s16 q1, q2, q9 add r12, r12, r5, lsl #2 ldrb r8, [r7], r4 ldrb r9, [r0], lr sub r8, r8, r9 ldrb r5, [r7], r4 add r8, r8, r8, lsl #2 ldrb r6, [r0], lr add r12, r12, r8 ldrb r8, [r7], r4 ldrb r9, [r0], lr sub r5, r5, r6 sub r8, r8, r9 add r5, r5, r5, lsl #1 rsb r8, r8, r8, lsl #3 add r12, r12, r5, lsl #1 ldrb r5, [r7], r4 ldrb r6, [r10] @top_left add r12, r12, r8 sub r9, r5, r6 ldrb r6, [r1, #7] add r12, r12, r9, lsl #3 @ i_c = r12 add r8, r5, r6 add r12, r12, r12, lsl #2 lsl r8, r8, #4 @ i_a = r8 add r12, r12, #0x20 lsr r12, r12, #6 vshl.s16 q14, q2, #3 vdup.16 q3, r12 vdup.16 q15, r8 vshl.s16 q13, q3, #3 vsub.s16 q15, q15, q14 vsub.s16 q15, q15, q13 vadd.s16 q14, q15, q3 mov r0, #14 vadd.s16 q13, q14, q0 vadd.s16 q14, q14, q1 vqrshrun.s16 d20, q13, #5 vqrshrun.s16 d21, q14, #5 loop_16x16_plane: vadd.s16 q13, q13, q3 vadd.s16 q14, q14, q3 vqrshrun.s16 d22, q13, #5 vst1.32 {q10}, [r2], r3 vqrshrun.s16 d23, q14, #5 vadd.s16 q13, q13, q3 subs r0, #2 vadd.s16 q14, q14, q3 vqrshrun.s16 d20, q13, #5 vst1.32 {q11}, [r2], r3 vqrshrun.s16 d21, q14, #5 bne loop_16x16_plane vadd.s16 q13, q13, q3 vadd.s16 q14, q14, q3 vqrshrun.s16 d22, q13, #5 vst1.32 {q10}, [r2], r3 vqrshrun.s16 d23, q14, #5 vst1.32 {q11}, [r2], r3 ldmfd sp!, {r4-r10, r12, pc} ================================================ FILE: dependencies/ih264d/common/arm/ih264_intra_pred_luma_4x4_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_intra_pred_luma_4x4_a9q.s @* @* @brief @* Contains function definitions for intra 4x4 Luma prediction . @* @* @author @* Ittiam @* @* @par List of Functions: @* @* -ih264_intra_pred_luma_4x4_mode_vert_a9q @* -ih264_intra_pred_luma_4x4_mode_horz_a9q @* -ih264_intra_pred_luma_4x4_mode_dc_a9q @* -ih264_intra_pred_luma_4x4_mode_diag_dl_a9q @* -ih264_intra_pred_luma_4x4_mode_diag_dr_a9q @* -ih264_intra_pred_luma_4x4_mode_vert_r_a9q @* -ih264_intra_pred_luma_4x4_mode_horz_d_a9q @* -ih264_intra_pred_luma_4x4_mode_vert_l_a9q @* -ih264_intra_pred_luma_4x4_mode_horz_u_a9q @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_intra_pred_filters.c @ .text .p2align 2 @** @******************************************************************************* @* @*ih264_intra_pred_luma_4x4_mode_vert @* @* @brief @* Perform Intra prediction for luma_4x4 mode:vertical @* @* @par Description: @* Perform Intra prediction for luma_4x4 mode:vertical ,described in sec 8.3.1.2.1 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels(Not used in this function) @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_4x4_mode_vert(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_vert_a9q ih264_intra_pred_luma_4x4_mode_vert_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack add r0, r0, #5 vld1.32 d0[0], [r0] vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @****************************************************************************** @** @******************************************************************************* @* @*ih264_intra_pred_luma_4x4_mode_horz @* @* @brief @* Perform Intra prediction for luma_4x4 mode:horizontal @* @* @par Description: @* Perform Intra prediction for luma_4x4 mode:horizontal ,described in sec 8.3.1.2.2 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels(Not used in this function) @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @void ih264_intra_pred_luma_4x4_mode_horz(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_horz_a9q ih264_intra_pred_luma_4x4_mode_horz_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack add r0, r0, #3 mov r2 , #-1 ldrb r5, [r0], r2 vdup.u8 d0, r5 ldrb r6, [r0], r2 vst1.32 d0[0], [r1], r3 vdup.u8 d1, r6 ldrb r7, [r0], r2 vst1.32 d1[0], [r1], r3 vdup.u8 d2, r7 ldrb r8, [r0], r2 vst1.32 d2[0], [r1], r3 vdup.u8 d3, r8 vst1.32 d3[0], [r1], r3 ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @****************************************************************************** @** @******************************************************************************* @* @*ih264_intra_pred_luma_4x4_mode_dc @* @* @brief @* Perform Intra prediction for luma_4x4 mode:DC @* @* @par Description: @* Perform Intra prediction for luma_4x4 mode:DC ,described in sec 8.3.1.2.3 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_4x4_mode_dc(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_dc_a9q ih264_intra_pred_luma_4x4_mode_dc_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack ldr r4, [sp, #40] @ r4 => ui_neighboravailability ands r5, r4, #0x01 beq top_available @LEFT NOT AVAILABLE add r10, r0, #3 mov r2, #-1 ldrb r5, [r10], r2 ldrb r6, [r10], r2 ldrb r7, [r10], r2 add r5, r5, r6 ldrb r8, [r10], r2 add r5, r5, r7 ands r11, r4, #0x04 @ CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE add r5, r5, r8 beq left_available add r10, r0, #5 @ BOTH LEFT AND TOP AVAILABLE ldrb r6, [r10], #1 ldrb r7, [r10], #1 add r5, r5, r6 ldrb r8, [r10], #1 add r5, r5, r7 ldrb r9, [r10], #1 add r5, r5, r8 add r5, r5, r9 add r5, r5, #4 lsr r5, r5, #3 vdup.u8 d0, r5 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 b end_func top_available: @ ONLT TOP AVAILABLE ands r11, r4, #0x04 @ CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE beq none_available add r10, r0, #5 ldrb r6, [r10], #1 ldrb r7, [r10], #1 ldrb r8, [r10], #1 add r5, r6, r7 ldrb r9, [r10], #1 add r5, r5, r8 add r5, r5, r9 add r5, r5, #2 lsr r5, r5, #2 vdup.u8 d0, r5 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 b end_func left_available: @ONLY LEFT AVAILABLE add r5, r5, #2 lsr r5, r5, #2 vdup.u8 d0, r5 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 b end_func none_available: @NONE AVAILABLE mov r5, #128 vdup.u8 d0, r5 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 vst1.32 d0[0], [r1], r3 b end_func end_func: ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_4x4_mode_diag_dl @* @* @brief @* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left @* @* @par Description: @* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left ,described in sec 8.3.1.2.4 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_4x4_mode_diag_dl(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_diag_dl_a9q ih264_intra_pred_luma_4x4_mode_diag_dl_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack add r0, r0, #5 sub r5, r3, #2 add r6, r0, #7 vld1.8 {d0}, [r0] vext.8 d1, d0, d0, #1 vext.8 d2, d0, d0, #2 vld1.8 {d2[6]}, [r6] vaddl.u8 q10, d0, d1 vaddl.u8 q11, d1, d2 vadd.u16 q12, q10, q11 vqrshrun.s16 d3, q12, #2 vst1.32 {d3[0]}, [r1], r3 vext.8 d4, d3, d3, #1 vst1.32 {d4[0]}, [r1], r3 vst1.16 {d3[1]}, [r1]! vst1.16 {d3[2]}, [r1], r5 vst1.16 {d4[1]}, [r1]! vst1.16 {d4[2]}, [r1] end_func_diag_dl: ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_4x4_mode_diag_dr @* @* @brief @* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right @* @* @par Description: @* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right ,described in sec 8.3.1.2.5 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_4x4_mode_diag_dr(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_diag_dr_a9q ih264_intra_pred_luma_4x4_mode_diag_dr_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vld1.u8 {d0}, [r0] add r0, r0, #1 vld1.u8 {d1}, [r0] vext.8 d2, d1, d1, #1 vaddl.u8 q10, d0, d1 vaddl.u8 q11, d1, d2 vadd.u16 q12, q10, q11 vqrshrun.s16 d3, q12, #2 vext.8 d4, d3, d3, #1 sub r5, r3, #2 vst1.16 {d4[1]}, [r1]! vst1.16 {d4[2]}, [r1], r5 vst1.16 {d3[1]}, [r1]! vst1.16 {d3[2]}, [r1], r5 vst1.32 {d4[0]}, [r1], r3 vst1.32 {d3[0]}, [r1], r3 end_func_diag_dr: ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_4x4_mode_vert_r @* @* @brief @* Perform Intra prediction for luma_4x4 mode:Vertical_Right @* @* @par Description: @* Perform Intra prediction for luma_4x4 mode:Vertical_Right ,described in sec 8.3.1.2.6 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_4x4_mode_vert_r(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_vert_r_a9q ih264_intra_pred_luma_4x4_mode_vert_r_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vld1.u8 {d0}, [r0] add r0, r0, #1 vld1.u8 {d1}, [r0] vext.8 d2, d1, d1, #1 vaddl.u8 q10, d0, d1 vaddl.u8 q11, d1, d2 vadd.u16 q12, q10, q11 vqrshrun.s16 d4, q10, #1 vqrshrun.s16 d3, q12, #2 sub r5, r3, #2 vext.8 d5, d3, d3, #3 vst1.32 {d4[1]}, [r1], r3 vst1.32 {d5[0]}, [r1], r3 sub r8, r3, #3 vst1.u8 {d3[2]}, [r1]! vst1.16 {d4[2]}, [r1]! vst1.u8 {d4[6]}, [r1], r8 vst1.u8 {d3[1]}, [r1]! vst1.16 {d5[0]}, [r1]! vst1.u8 {d5[2]}, [r1] end_func_vert_r: ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_4x4_mode_horz_d @* @* @brief @* Perform Intra prediction for luma_4x4 mode:Horizontal_Down @* @* @par Description: @* Perform Intra prediction for luma_4x4 mode:Horizontal_Down ,described in sec 8.3.1.2.7 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_4x4_mode_horz_d(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_horz_d_a9q ih264_intra_pred_luma_4x4_mode_horz_d_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vld1.u8 {d0}, [r0] add r0, r0, #1 vld1.u8 {d1}, [r0] vext.8 d2, d1, d0, #1 vaddl.u8 q10, d0, d1 vaddl.u8 q11, d1, d2 vadd.u16 q12, q10, q11 vqrshrun.s16 d4, q10, #1 vqrshrun.s16 d5, q12, #2 sub r5, r3, #2 vmov.8 d6, d5 vtrn.8 d4, d5 @ vst1.u16 {d5[1]}, [r1]! vst1.16 {d6[2]}, [r1], r5 vst1.u16 {d4[1]}, [r1]! vst1.16 {d5[1]}, [r1], r5 vst1.u16 {d5[0]}, [r1]! vst1.16 {d4[1]}, [r1], r5 vst1.u16 {d4[0]}, [r1]! vst1.16 {d5[0]}, [r1], r5 end_func_horz_d: ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_4x4_mode_vert_l @* @* @brief @* Perform Intra prediction for luma_4x4 mode:Vertical_Left @* @* @par Description: @* Perform Intra prediction for luma_4x4 mode:Vertical_Left ,described in sec 8.3.1.2.8 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_4x4_mode_vert_l(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_vert_l_a9q ih264_intra_pred_luma_4x4_mode_vert_l_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack add r0, r0, #4 vld1.u8 {d0}, [r0] add r0, r0, #1 vld1.u8 {d1}, [r0] vext.8 d2, d1, d0, #1 vaddl.u8 q10, d0, d1 vaddl.u8 q11, d1, d2 vadd.u16 q12, q10, q11 vqrshrun.s16 d4, q10, #1 vqrshrun.s16 d5, q12, #2 vext.8 d6, d4, d4, #1 vext.8 d7, d5, d5, #1 vst1.32 {d6[0]}, [r1], r3 vext.8 d16, d4, d4, #2 vext.8 d17, d5, d5, #2 vst1.32 {d7[0]}, [r1], r3 vst1.32 {d16[0]}, [r1], r3 vst1.32 {d17[0]}, [r1], r3 end_func_vert_l: ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_4x4_mode_horz_u @* @* @brief @* Perform Intra prediction for luma_4x4 mode:Horizontal_Up @* @* @par Description: @* Perform Intra prediction for luma_4x4 mode:Horizontal_Up ,described in sec 8.3.1.2.9 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_4x4_mode_horz_u(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_horz_u_a9q ih264_intra_pred_luma_4x4_mode_horz_u_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack mov r10, r0 vld1.u8 {d0}, [r0] ldrb r9, [r0], #1 vext.8 d1, d0, d0, #1 vld1.u8 {d0[7]}, [r10] vext.8 d2, d1, d1, #1 vaddl.u8 q10, d0, d1 vaddl.u8 q11, d1, d2 vadd.u16 q12, q10, q11 vqrshrun.s16 d4, q10, #1 vqrshrun.s16 d5, q12, #2 vmov d6, d4 vext.8 d6, d5, d4, #1 vst1.8 {d4[2]}, [r1]! vst1.8 {d6[0]}, [r1]! vtrn.8 d6, d5 @ sub r5, r3, #2 vtrn.8 d4, d6 @ vdup.8 d7, r9 vst1.16 {d6[0]}, [r1], r5 vst1.16 {d6[0]}, [r1]! vst1.16 {d5[3]}, [r1], r5 vst1.16 {d5[3]}, [r1]! vst1.16 {d7[3]}, [r1], r5 vst1.32 {d7[0]}, [r1], r3 end_func_horz_u: ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_intra_pred_luma_8x8_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_intra_pred_luma_8x8_a9q.s @* @* @brief @* Contains function definitions for intra 8x8 Luma prediction . @* @* @author @* Ittiam @* @* @par List of Functions: @* @* -ih264_intra_pred_luma_8x8_mode_ref_filtering_a9q @* -ih264_intra_pred_luma_8x8_mode_vert_a9q @* -ih264_intra_pred_luma_8x8_mode_horz_a9q @* -ih264_intra_pred_luma_8x8_mode_dc_a9q @* -ih264_intra_pred_luma_8x8_mode_diag_dl_a9q @* -ih264_intra_pred_luma_8x8_mode_diag_dr_a9q @* -ih264_intra_pred_luma_8x8_mode_vert_r_a9q @* -ih264_intra_pred_luma_8x8_mode_horz_d_a9q @* -ih264_intra_pred_luma_8x8_mode_vert_l_a9q @* -ih264_intra_pred_luma_8x8_mode_horz_u_a9q @* @* @remarks @* None @* @******************************************************************************* @* @* All the functions here are replicated from ih264_intra_pred_filters.c @ .text .p2align 2 .extern ih264_gai1_intrapred_luma_8x8_horz_u .hidden ih264_gai1_intrapred_luma_8x8_horz_u scratch_intrapred_addr_8x8: .long ih264_gai1_intrapred_luma_8x8_horz_u - scrlb8x8l2 - 8 @** @******************************************************************************* @* @*ih264_intra_pred_luma_8x8_mode_ref_filtering @* @* @brief @* Reference sample filtering process for Intra_8x8 sample prediction @* @* @par Description: @* Perform Reference sample filtering process for Intra_8x8 sample prediction ,described in sec 8.3.2.2.1 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride [Not used] @* @* @param[in] dst_strd @* integer destination stride[Not used] @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels[Not used] @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_8x8_mode_ref_filtering(UWORD8 *pu1_src, @ UWORD8 *pu1_dst) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst .global ih264_intra_pred_luma_8x8_mode_ref_filtering_a9q ih264_intra_pred_luma_8x8_mode_ref_filtering_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vpush {d8-d15} vld1.u8 {q0}, [r0]! @ vld1.u8 {q1}, [r0] add r0, r0, #8 @ vext.8 q2, q0, q1, #1 vext.8 q3, q1, q1, #1 vext.8 q4, q2, q3, #1 vext.8 q5, q3, q3, #1 vld1.8 {d10[7]}, [r0] @ LOADING SRC[24] AGIN TO THE END FOR p'[ 15, -1 ] = ( p[ 14, -1 ] + 3 * p[ 15, -1 ] + 2 ) >> 2 vaddl.u8 q10, d0, d4 vaddl.u8 q7, d0, d0 @ SPECIAL CASE FOR p'[ -1 ,7 ] = ( p[ -1, 6 ] + 3 * p[ -1, 7 ] + 2 ) >> 2 vadd.u16 q7, q10, q7 vaddl.u8 q11, d1, d5 vqrshrun.s16 d14, q7, #2 vaddl.u8 q12, d4, d8 vaddl.u8 q13, d5, d9 vst1.8 {d14[0]}, [r1]! vadd.u16 q12, q10, q12 vadd.u16 q13, q11, q13 vaddl.u8 q9, d2, d6 vaddl.u8 q8, d6, d10 vqrshrun.s16 d4, q12, #2 vqrshrun.s16 d5, q13, #2 vadd.u16 q6, q8, q9 vst1.8 {q2}, [r1]! vqrshrun.s16 d6, q6, #2 vst1.8 {d6}, [r1] end_func_ref_filt: vpop {d8-d15} ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_8x8_mode_vert @* @* @brief @* Perform Intra prediction for luma_8x8 mode:vertical @* @* @par Description: @* Perform Intra prediction for luma_8x8 mode:vertical ,described in sec 8.3.2.2.2 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels(Not used in this function) @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_8x8_mode_vert(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_vert_a9q ih264_intra_pred_luma_8x8_mode_vert_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack add r0, r0, #9 vld1.8 d0, [r0] vst1.8 d0, [r1], r3 vst1.8 d0, [r1], r3 vst1.8 d0, [r1], r3 vst1.8 d0, [r1], r3 vst1.8 d0, [r1], r3 vst1.8 d0, [r1], r3 vst1.8 d0, [r1], r3 vst1.8 d0, [r1], r3 ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @****************************************************************************** @** @******************************************************************************* @* @*ih264_intra_pred_luma_8x8_mode_horz @* @* @brief @* Perform Intra prediction for luma_8x8 mode:horizontal @* @* @par Description: @* Perform Intra prediction for luma_8x8 mode:horizontal ,described in sec 8.3.2.2.2 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels(Not used in this function) @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @void ih264_intra_pred_luma_8x8_mode_horz(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_horz_a9q ih264_intra_pred_luma_8x8_mode_horz_a9q: stmfd sp!, {r14} @store register values to stack vld1.u8 {d0}, [r0] mov r2, #6 vdup.u8 d1, d0[7] vdup.u8 d2, d0[6] vst1.8 {d1}, [r1], r3 loop_8x8_horz: vext.8 d0, d0, d0, #6 vst1.8 {d2}, [r1], r3 vdup.u8 d1, d0[7] subs r2, #2 vdup.u8 d2, d0[6] vst1.8 {d1}, [r1], r3 bne loop_8x8_horz vext.8 d0, d0, d0, #6 vst1.8 {d2}, [r1], r3 ldmfd sp!, {pc} @restoring registers from stack @****************************************************************************** @** @******************************************************************************* @* @*ih264_intra_pred_luma_8x8_mode_dc @* @* @brief @* Perform Intra prediction for luma_8x8 mode:DC @* @* @par Description: @* Perform Intra prediction for luma_8x8 mode:DC ,described in sec 8.3.2.2.3 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_8x8_mode_dc(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_dc_a9q ih264_intra_pred_luma_8x8_mode_dc_a9q: stmfd sp!, {r4, r14} @store register values to stack ldr r4, [sp, #8] @r4 => ui_neighboravailability ands r2, r4, #0x01 @CHECKING IF LEFT_AVAILABLE ELSE BRANCHING TO ONLY TOP AVAILABLE beq top_available ands r2, r4, #0x04 @CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE beq left_available vld1.u8 {d0}, [r0] @BOTH LEFT AND TOP AVAILABLE add r0, r0, #9 vld1.u8 {d1}, [r0] vpaddl.u8 q0, q0 vadd.u16 d0, d0, d1 vpaddl.u16 d0, d0 vpaddl.u32 d0, d0 vqrshrun.s16 d0, q0, #4 vdup.u8 d0, d0[0] b str_pred top_available: @ONLY TOP AVAILABLE ands r2, r4, #0x04 @CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE beq none_available add r0, r0, #9 vld1.u8 {d0}, [r0] vpaddl.u8 d0, d0 vpaddl.u16 d0, d0 vpaddl.u32 d0, d0 vqrshrun.s16 d0, q0, #3 vdup.u8 d0, d0[0] b str_pred left_available: @ONLY LEFT AVAILABLE vld1.u8 {d0}, [r0] vpaddl.u8 d0, d0 vpaddl.u16 d0, d0 vpaddl.u32 d0, d0 vqrshrun.s16 d0, q0, #3 vdup.u8 d0, d0[0] b str_pred none_available: @NONE AVAILABLE vmov.u8 q0, #128 str_pred: vst1.8 {d0}, [r1], r3 vst1.8 {d0}, [r1], r3 vst1.8 {d0}, [r1], r3 vst1.8 {d0}, [r1], r3 vst1.8 {d0}, [r1], r3 vst1.8 {d0}, [r1], r3 vst1.8 {d0}, [r1], r3 vst1.8 {d0}, [r1], r3 ldmfd sp!, {r4, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_8x8_mode_diag_dl @* @* @brief @* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left @* @* @par Description: @* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left ,described in sec 8.3.2.2.4 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_8x8_mode_diag_dl(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_diag_dl_a9q ih264_intra_pred_luma_8x8_mode_diag_dl_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack add r0, r0, #9 sub r5, r3, #4 add r6, r0, #15 vld1.8 {q0}, [r0] vext.8 q2, q0, q0, #2 vext.8 q1, q0, q0, #1 vld1.8 {d5[6]}, [r6] @ q1 = q0 shifted to left once @ q2 = q1 shifted to left once vaddl.u8 q10, d0, d2 @Adding for FILT121 vaddl.u8 q11, d1, d3 vaddl.u8 q12, d2, d4 vaddl.u8 q13, d3, d5 vadd.u16 q12, q10, q12 vadd.u16 q13, q11, q13 vqrshrun.s16 d4, q12, #2 vqrshrun.s16 d5, q13, #2 @Q2 has all FILT121 values vst1.8 {d4}, [r1], r3 vext.8 q9, q2, q2, #1 vext.8 q8, q9, q9, #1 vst1.8 {d18}, [r1], r3 vext.8 q15, q8, q8, #1 vst1.8 {d16}, [r1], r3 vst1.8 {d30}, [r1], r3 vst1.32 {d4[1]}, [r1]! vst1.32 {d5[0]}, [r1], r5 vst1.32 {d18[1]}, [r1]! vst1.32 {d19[0]}, [r1], r5 vst1.32 {d16[1]}, [r1]! vst1.32 {d17[0]}, [r1], r5 vst1.32 {d30[1]}, [r1]! vst1.32 {d31[0]}, [r1], r5 end_func_diag_dl: ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_8x8_mode_diag_dr @* @* @brief @* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right @* @* @par Description: @* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right ,described in sec 8.3.2.2.5 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_8x8_mode_diag_dr(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_diag_dr_a9q ih264_intra_pred_luma_8x8_mode_diag_dr_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vld1.u8 {q0}, [r0] add r0, r0, #1 vld1.u8 {q1}, [r0] vext.8 q2, q1, q1, #1 @ q1 = q0 shifted to left once @ q2 = q1 shifted to left once vaddl.u8 q10, d0, d2 @Adding for FILT121 vaddl.u8 q11, d1, d3 vaddl.u8 q12, d2, d4 vaddl.u8 q13, d3, d5 vadd.u16 q12, q10, q12 vadd.u16 q13, q11, q13 vqrshrun.s16 d4, q12, #2 vqrshrun.s16 d5, q13, #2 @Q2 has all FILT121 values sub r5, r3, #4 vext.8 q9, q2, q2, #15 vst1.8 {d19}, [r1], r3 vext.8 q8, q9, q9, #15 vst1.8 {d17}, [r1], r3 vext.8 q15, q8, q8, #15 vst1.8 {d31}, [r1], r3 vst1.32 {d4[1]}, [r1]! vst1.32 {d5[0]}, [r1], r5 vst1.32 {d18[1]}, [r1]! vst1.32 {d19[0]}, [r1], r5 vst1.32 {d16[1]}, [r1]! vst1.32 {d17[0]}, [r1], r5 vst1.32 {d30[1]}, [r1]! vst1.32 {d31[0]}, [r1], r5 vst1.8 {d4}, [r1], r3 end_func_diag_dr: ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_8x8_mode_vert_r @* @* @brief @* Perform Intra prediction for luma_8x8 mode:Vertical_Right @* @* @par Description: @* Perform Intra prediction for luma_8x8 mode:Vertical_Right ,described in sec 8.3.2.2.6 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_8x8_mode_vert_r(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_vert_r_a9q ih264_intra_pred_luma_8x8_mode_vert_r_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vld1.u8 {q0}, [r0] add r0, r0, #1 vld1.u8 {q1}, [r0] vext.8 q2, q1, q1, #1 @ q1 = q0 shifted to left once @ q2 = q1 shifted to left once vaddl.u8 q10, d0, d2 vaddl.u8 q11, d1, d3 vaddl.u8 q12, d2, d4 vaddl.u8 q13, d3, d5 vadd.u16 q12, q10, q12 vadd.u16 q13, q11, q13 vqrshrun.s16 d4, q10, #1 vqrshrun.s16 d5, q11, #1 vqrshrun.s16 d6, q12, #2 vqrshrun.s16 d7, q13, #2 @Q2 has all FILT11 values @Q3 has all FILT121 values sub r5, r3, #6 sub r6, r3, #4 vst1.8 {d5}, [r1], r3 @ row 0 vext.8 q9, q3, q3, #15 vmov.8 q11, q9 vext.8 q8, q2, q2, #1 vst1.8 {d19}, [r1], r3 @row 1 vmov.8 q15, q8 vext.8 q10, q2, q2, #15 vuzp.8 q8, q9 @row 2 vext.8 q14, q8, q8, #1 vst1.8 {d21}, [r1] vst1.8 {d6[6]}, [r1], r3 @row 3 vst1.16 {d29[1]}, [r1]! vst1.32 {d7[0]}, [r1]! vst1.16 {d7[2]}, [r1], r5 @row 4 vst1.16 {d19[1]}, [r1]! vst1.32 {d5[0]}, [r1]! vst1.16 {d5[2]}, [r1], r5 @row 5 vext.8 q13, q9, q9, #1 vst1.16 {d17[1]}, [r1]! vst1.32 {d23[0]}, [r1]! vst1.16 {d23[2]}, [r1], r5 @row 6 vst1.16 {d27[0]}, [r1]! vst1.8 {d27[2]}, [r1]! vst1.8 {d5[0]}, [r1]! vst1.32 {d31[0]}, [r1], r6 @row 7 vst1.32 {d29[0]}, [r1]! vst1.32 {d7[0]}, [r1]! end_func_vert_r: ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_8x8_mode_horz_d @* @* @brief @* Perform Intra prediction for luma_8x8 mode:Horizontal_Down @* @* @par Description: @* Perform Intra prediction for luma_8x8 mode:Horizontal_Down ,described in sec 8.3.2.2.7 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_8x8_mode_horz_d(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_horz_d_a9q ih264_intra_pred_luma_8x8_mode_horz_d_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vpush {d8-d15} vld1.u8 {q0}, [r0] add r0, r0, #1 vld1.u8 {q1}, [r0] vext.8 q2, q1, q1, #1 @ q1 = q0 shifted to left once @ q2 = q1 shifted to left once vaddl.u8 q10, d0, d2 vaddl.u8 q11, d1, d3 vaddl.u8 q12, d2, d4 vaddl.u8 q13, d3, d5 vadd.u16 q12, q10, q12 vadd.u16 q13, q11, q13 vqrshrun.s16 d4, q10, #1 vqrshrun.s16 d5, q11, #1 vqrshrun.s16 d6, q12, #2 vqrshrun.s16 d7, q13, #2 @Q2 has all FILT11 values @Q3 has all FILT121 values vmov.8 q4, q2 vmov.8 q5, q3 sub r6, r3, #6 vtrn.8 q4, q5 @ vmov.8 q6, q4 vmov.8 q7, q5 sub r5, r3, #4 vtrn.16 q6, q7 vext.8 q8, q3, q3, #14 @ROW 0 vst1.8 {d17}, [r1] vst1.16 {d10[3]}, [r1], r3 @ROW 1 vst1.32 {d14[1]}, [r1]! vst1.32 {d7[0]}, [r1], r5 @ROW 2 vst1.16 {d10[2]}, [r1]! vst1.32 {d14[1]}, [r1]! vst1.16 {d7[0]}, [r1], r6 @ROW 3 vst1.32 {d12[1]}, [r1]! vst1.32 {d14[1]}, [r1], r5 @ROW 4 vst1.16 {d14[1]}, [r1]! vst1.32 {d12[1]}, [r1]! vst1.16 {d14[2]}, [r1], r6 @ROW 5 vst1.32 {d14[0]}, [r1]! vst1.32 {d12[1]}, [r1], r5 @ROW 6 vst1.16 {d10[0]}, [r1]! vst1.16 {d8[1]}, [r1]! vst1.16 {d14[1]}, [r1]! vst1.16 {d12[2]}, [r1], r6 @ROW 7 vst1.32 {d12[0]}, [r1]! vst1.32 {d14[0]}, [r1], r5 end_func_horz_d: vpop {d8-d15} ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_8x8_mode_vert_l @* @* @brief @* Perform Intra prediction for luma_8x8 mode:Vertical_Left @* @* @par Description: @* Perform Intra prediction for luma_8x8 mode:Vertical_Left ,described in sec 8.3.2.2.8 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_8x8_mode_vert_l(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_vert_l_a9q ih264_intra_pred_luma_8x8_mode_vert_l_a9q: stmfd sp!, {r4-r12, r14} @Restoring registers from stack vpush {d8-d15} add r0, r0, #9 vld1.u8 {q0}, [r0] add r0, r0, #1 vld1.u8 {q1}, [r0] vext.8 q2, q1, q1, #1 vaddl.u8 q10, d0, d2 vaddl.u8 q11, d1, d3 vaddl.u8 q12, d2, d4 vaddl.u8 q13, d3, d5 vadd.u16 q12, q10, q12 vadd.u16 q13, q11, q13 vqrshrun.s16 d4, q10, #1 vqrshrun.s16 d5, q11, #1 vqrshrun.s16 d6, q12, #2 vext.8 q4, q2, q2, #1 vqrshrun.s16 d7, q13, #2 @Q2 has all FILT11 values @Q3 has all FILT121 values vext.8 q5, q3, q3, #1 @ROW 0,1 vst1.8 {d4}, [r1], r3 vst1.8 {d6}, [r1], r3 vext.8 q6, q4, q4, #1 vext.8 q7, q5, q5, #1 @ROW 2,3 vst1.8 {d8}, [r1], r3 vst1.8 {d10}, [r1], r3 vext.8 q8, q6, q6, #1 vext.8 q9, q7, q7, #1 @ROW 4,5 vst1.8 {d12}, [r1], r3 vst1.8 {d14}, [r1], r3 @ROW 6,7 vst1.8 {d16}, [r1], r3 vst1.8 {d18}, [r1], r3 end_func_vert_l: vpop {d8-d15} ldmfd sp!, {r4-r12, pc} @Restoring registers from stack @** @******************************************************************************* @* @*ih264_intra_pred_luma_8x8_mode_horz_u @* @* @brief @* Perform Intra prediction for luma_8x8 mode:Horizontal_Up @* @* @par Description: @* Perform Intra prediction for luma_8x8 mode:Horizontal_Up ,described in sec 8.3.2.2.9 @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] src_strd @* integer source stride @* @* @param[in] dst_strd @* integer destination stride @* @* @param[in] ui_neighboravailability @* availability of neighbouring pixels @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @void ih264_intra_pred_luma_8x8_mode_horz_u(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 ui_neighboravailability) @**************Variables Vs Registers***************************************** @ r0 => *pu1_src @ r1 => *pu1_dst @ r2 => src_strd @ r3 => dst_strd @ r4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_horz_u_a9q ih264_intra_pred_luma_8x8_mode_horz_u_a9q: stmfd sp!, {r4-r12, r14} @store register values to stack vpush {d8-d15} vld1.u8 {q0}, [r0] vld1.u8 {d1[7]}, [r0] vext.8 q1, q0, q0, #1 vext.8 q2, q1, q1, #1 @ LOADING V TABLE ldr r12, scratch_intrapred_addr_8x8 scrlb8x8l2: add r12, r12, pc vaddl.u8 q10, d0, d2 vaddl.u8 q11, d1, d3 vaddl.u8 q12, d2, d4 vaddl.u8 q13, d3, d5 vadd.u16 q12, q10, q12 vadd.u16 q13, q11, q13 vld1.u8 {q5}, [r12] vqrshrun.s16 d4, q10, #1 vqrshrun.s16 d5, q11, #1 vqrshrun.s16 d6, q12, #2 vqrshrun.s16 d7, q13, #2 @Q2 has all FILT11 values @Q3 has all FILT121 values vtbl.u8 d12, {q2, q3}, d10 vdup.u8 q7, d5[7] @ vtbl.u8 d13, {q2, q3}, d11 vext.8 q8, q6, q7, #2 vext.8 q9, q8, q7, #2 vst1.8 {d12}, [r1], r3 vext.8 q10, q9, q7, #2 vst1.8 {d16}, [r1], r3 vst1.8 {d18}, [r1], r3 vst1.8 {d20}, [r1], r3 vst1.8 {d13}, [r1], r3 vst1.8 {d17}, [r1], r3 vst1.8 {d19}, [r1], r3 vst1.8 {d21}, [r1], r3 end_func_horz_u: vpop {d8-d15} ldmfd sp!, {r4-r12, pc} @Restoring registers from stack ================================================ FILE: dependencies/ih264d/common/arm/ih264_iquant_itrans_recon_a9.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @ ******************************************************************************* @ * @file @ * ih264_iquant_itrans_recon_a9.s @ * @ * @brief @ * Contains function definitions for single stage inverse transform @ * @ * @author @ * Mohit @ * Harinarayanaan @ * @ * @par List of Functions: @ * - ih264_iquant_itrans_recon_4x4_a9() @ * - ih264_iquant_itrans_recon_8x8_a9() @ * - ih264_iquant_itrans_recon_chroma_4x4_a9() @ * @ * @remarks @ * None @ * @ ******************************************************************************* @* @** @ ******************************************************************************* @ * @ * @brief @ * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block @ * @ * @par Description: @ * Performs inverse transform Ci4 and adds the residue to get the @ * reconstructed block @ * @ * @param[in] pi2_src @ * Input 4x4 coefficients @ * @ * @param[in] pu1_pred @ * Prediction 4x4 block @ * @ * @param[out] pu1_out @ * Output 4x4 block @ * @ * @param[in] u4_qp_div_6 @ * QP @ * @ * @param[in] pu2_weigh_mat @ * Pointer to weight matrix @ * @ * @param[in] pred_strd, @ * Prediction stride @ * @ * @param[in] out_strd @ * Output Stride @ * @ *@param[in] pi2_tmp @ * temporary buffer of size 1*16 @ * @ * @param[in] pu2_iscal_mat @ * Pointer to the inverse quantization matrix @ * @ * @returns Void @ * @ * @remarks @ * None @ * @ ******************************************************************************* @ * @void ih264_iquant_itrans_recon_4x4(WORD16 *pi2_src, @ UWORD8 *pu1_pred, @ UWORD8 *pu1_out, @ WORD32 pred_strd, @ WORD32 out_strd, @ const UWORD16 *pu2_iscal_mat, @ const UWORD16 *pu2_weigh_mat, @ UWORD32 u4_qp_div_6, @ WORD32 *pi4_tmp, @ WORD32 iq_start_idx @ WORD16 *pi2_dc_ld_addr) @**************Variables Vs Registers***************************************** @r0 => *pi2_src @r1 => *pu1_pred @r2 => *pu1_out @r3 => pred_strd @r4 => out_strd @r5 => *pu2_iscal_mat @r6 => *pu2_weigh_mat @r7 => u4_qp_div_6 @r8 => iq_start_idx @r10=> pi2_dc_ld_addr .text .syntax unified .p2align 2 .global ih264_iquant_itrans_recon_4x4_a9 ih264_iquant_itrans_recon_4x4_a9: @VLD4.S16 is used because the pointer is incremented by SUB_BLK_WIDTH_4x4 @If the macro value changes need to change the instruction according to it. @Only one shift is done in horizontal inverse because, @if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value @if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments ldr r7, [sp, #52] @Loads u4_qp_div_6 ldr r4, [sp, #40] @Loads out_strd vdup.s32 q15, r7 @Populate the u4_qp_div_6 in Q15 ldr r5, [sp, #44] @Loads *pu2_iscal_mat ldr r6, [sp, #48] @Loads *pu2_weigh_mat ldr r8, [sp, #60] @Loads iq_start_idx ldr r10, [sp, #64] @Load alternate dc address vpush {d8-d15} @=======================DEQUANT FROM HERE=================================== vld4.s16 {d20, d21, d22, d23}, [r5] @Load pu2_iscal_mat[i], i =0..15 vld4.s16 {d26, d27, d28, d29}, [r6] @pu2_weigh_mat[i], i =0..15 vmul.s16 q10, q10, q13 @x[i]=(scale[i] * dequant[i]) where i = 0..7 vld4.s16 {d16, d17, d18, d19}, [r0] @pi2_src_tmp[i], i =0..15 vmul.s16 q11, q11, q14 @x[i]=(scale[i] * dequant[i]) where i = 8..15 subs r8, r8, #1 @ if r8 == 1 => intra case , so result of subtraction is zero and Z flag is set ldrsheq r9, [r10] @ Loads signed halfword pi2_dc_ld_addr[0], if r8==1 vmull.s16 q0, d16, d20 @ Q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 vmull.s16 q1, d17, d21 @ Q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 vmull.s16 q2, d18, d22 @ Q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 vmull.s16 q3, d19, d23 @ Q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 vshl.s32 q0, q0, q15 @ Q0 = q[i] = (p[i] << (qP/6)) where i = 0..3 vshl.s32 q1, q1, q15 @ Q1 = q[i] = (p[i] << (qP/6)) where i = 4..7 vshl.s32 q2, q2, q15 @ Q2 = q[i] = (p[i] << (qP/6)) where i = 8..11 vshl.s32 q3, q3, q15 @ Q3 = q[i] = (p[i] << (qP/6)) where i = 12..15 vqrshrn.s32 d0, q0, #0x4 @ D0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 vqrshrn.s32 d1, q1, #0x4 @ D1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 vqrshrn.s32 d2, q2, #0x4 @ D2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 vqrshrn.s32 d3, q3, #0x4 @ D3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 vmoveq.16 d0[0], r9 @ Restore dc value in case of intra, i.e. r8 == 1 @========= PROCESS IDCT FROM HERE ======= @Steps for Stage 1: @------------------ vld1.32 d30[0], [r1], r3 @I row Load pu1_pred buffer vadd.s16 d4, d0, d2 @x0 = q0 + q1; vsub.s16 d5, d0, d2 @x1 = q0 - q1; vshr.s16 d8, d1, #1 @q0>>1 vshr.s16 d9, d3, #1 @q1>>1 vsub.s16 d6, d8, d3 @x2 = (q0 >> 1) - q1; vadd.s16 d7, d1, d9 @x3 = q0+ (q1 >> 1); vld1.32 d30[1], [r1], r3 @II row Load pu1_pred buffer vswp d6, d7 @Reverse positions of x2 and x3 vsub.s16 q6, q2, q3 @x0-x3 and x1-x2 combined vadd.s16 q5, q2, q3 @x0 + x3 and x1+x2 combined vld1.32 d31[0], [r1], r3 @III row Load pu1_pred buf vswp d12, d13 @Steps for Stage 2: @------------------ vtrn.16 d10, d11 vtrn.16 d12, d13 vtrn.32 d10, d12 vtrn.32 d11, d13 vadd.s16 d14, d10, d12 @x0 = q0 + q1; vsub.s16 d15, d10, d12 @x1 = q0 - q1; vshr.s16 d18, d11, #1 @q0>>1 vshr.s16 d19, d13, #1 @q1>>1 vsub.s16 d16, d18, d13 @x2 = (q0 >> 1) - q1; vadd.s16 d17, d11, d19 @x3 = q0+ (q1 >> 1); vld1.32 d31[1], [r1], r3 @IV row Load pu1_pred buffer vswp d16, d17 @Reverse positions of x2 and x3 vsub.s16 q11, q7, q8 @x0-x3 and x1-x2 combined vadd.s16 q10, q7, q8 @x0 + x3 and x1+x2 combined vswp d22, d23 vrshr.s16 q10, q10, #6 @ vrshr.s16 q11, q11, #6 vaddw.u8 q10, q10, d30 vaddw.u8 q11, q11, d31 vqmovun.s16 d0, q10 vqmovun.s16 d1, q11 vst1.32 d0[0], [r2], r4 @I row store the value vst1.32 d0[1], [r2], r4 @II row store the value vst1.32 d1[0], [r2], r4 @III row store the value vst1.32 d1[1], [r2] @IV row store the value vpop {d8-d15} ldmfd sp!, {r4-r12, r15} @Reload the registers from SP @** @ ******************************************************************************* @ * @ * @brief @ * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block @ * @ * @par Description: @ * Performs inverse transform Ci4 and adds the residue to get the @ * reconstructed block @ * @ * @param[in] pi2_src @ * Input 4x4 coefficients @ * @ * @param[in] pu1_pred @ * Prediction 4x4 block @ * @ * @param[out] pu1_out @ * Output 4x4 block @ * @ * @param[in] u4_qp_div_6 @ * QP @ * @ * @param[in] pu2_weigh_mat @ * Pointer to weight matrix @ * @ * @param[in] pred_strd, @ * Prediction stride @ * @ * @param[in] out_strd @ * Output Stride @ * @ *@param[in] pi2_tmp @ * temporary buffer of size 1*16 @ * @ * @param[in] pu2_iscal_mat @ * Pointer to the inverse quantization matrix @ * @ * @returns Void @ * @ * @remarks @ * None @ * @ ******************************************************************************* @ * @void ih264_iquant_itrans_recon_chroma_4x4(WORD16 *pi2_src, @ UWORD8 *pu1_pred, @ UWORD8 *pu1_out, @ WORD32 pred_strd, @ WORD32 out_strd, @ const UWORD16 *pu2_iscal_mat, @ const UWORD16 *pu2_weigh_mat, @ UWORD32 u4_qp_div_6, @ WORD32 *pi4_tmp @ WORD16 *pi2_dc_src) @**************Variables Vs Registers***************************************** @r0 => *pi2_src @r1 => *pu1_pred @r2 => *pu1_out @r3 => pred_strd @r4 => out_strd @r5 => *pu2_iscal_mat @r6 => *pu2_weigh_mat @r7 => u4_qp_div_6 .global ih264_iquant_itrans_recon_chroma_4x4_a9 ih264_iquant_itrans_recon_chroma_4x4_a9: @VLD4.S16 is used because the pointer is incremented by SUB_BLK_WIDTH_4x4 @If the macro value changes need to change the instruction according to it. @Only one shift is done in horizontal inverse because, @if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value @if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments ldr r7, [sp, #52] @Loads u4_qp_div_6 ldr r4, [sp, #40] @Loads out_strd vdup.s32 q15, r7 @Populate the u4_qp_div_6 in Q15 ldr r5, [sp, #44] @Loads *pu2_iscal_mat ldr r6, [sp, #48] @Loads *pu2_weigh_mat ldr r8, [sp, #60] @loads *pi2_dc_src vpush {d8-d15} @=======================DEQUANT FROM HERE=================================== vld4.s16 {d20, d21, d22, d23}, [r5] @Load pu2_iscal_mat[i], i =0..15 vld4.s16 {d26, d27, d28, d29}, [r6] @pu2_weigh_mat[i], i =0..15 vmul.s16 q10, q10, q13 @x[i]=(scale[i] * dequant[i]) where i = 0..7 vld4.s16 {d16, d17, d18, d19}, [r0] @pi2_src_tmp[i], i =0..15 vmul.s16 q11, q11, q14 @x[i]=(scale[i] * dequant[i]) where i = 8..15 vmull.s16 q0, d16, d20 @ Q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 vmull.s16 q1, d17, d21 @ Q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 vmull.s16 q2, d18, d22 @ Q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 vmull.s16 q3, d19, d23 @ Q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 vshl.s32 q0, q0, q15 @ Q0 = q[i] = (p[i] << (qP/6)) where i = 0..3 vshl.s32 q1, q1, q15 @ Q1 = q[i] = (p[i] << (qP/6)) where i = 4..7 vshl.s32 q2, q2, q15 @ Q2 = q[i] = (p[i] << (qP/6)) where i = 8..11 vshl.s32 q3, q3, q15 @ Q3 = q[i] = (p[i] << (qP/6)) where i = 12..15 vqrshrn.s32 d0, q0, #0x4 @ D0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 vqrshrn.s32 d1, q1, #0x4 @ D1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 vqrshrn.s32 d2, q2, #0x4 @ D2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 vqrshrn.s32 d3, q3, #0x4 @ D3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 ldrsh r9, [r8] @ Loads signed halfword pi2_dc_src[0] vmov.16 d0[0], r9 @ Restore dc value since its chroma iq-it @========= PROCESS IDCT FROM HERE ======= @Steps for Stage 1: @------------------ vld2.8 {d28, d29}, [r1], r3 @I row Load pu1_pred buffer vadd.s16 d4, d0, d2 @x0 = q0 + q1; vsub.s16 d5, d0, d2 @x1 = q0 - q1; vshr.s16 d8, d1, #1 @q0>>1 vshr.s16 d9, d3, #1 @q1>>1 vsub.s16 d6, d8, d3 @x2 = (q0 >> 1) - q1; vadd.s16 d7, d1, d9 @x3 = q0+ (q1 >> 1); vld2.8 {d29, d30}, [r1], r3 @II row Load pu1_pred buffer vswp d6, d7 @Reverse positions of x2 and x3 vsub.s16 q6, q2, q3 @x0-x3 and x1-x2 combined vtrn.32 d28, d29 @ D28 -- row I and II of pu1_pred_buffer vadd.s16 q5, q2, q3 @x0 + x3 and x1+x2 combined vld2.8 {d29, d30}, [r1], r3 @III row Load pu1_pred buf vswp d12, d13 @Steps for Stage 2: @------------------ vtrn.16 d10, d11 vtrn.16 d12, d13 vtrn.32 d10, d12 vtrn.32 d11, d13 vadd.s16 d14, d10, d12 @x0 = q0 + q1; vsub.s16 d15, d10, d12 @x1 = q0 - q1; vshr.s16 d18, d11, #1 @q0>>1 vshr.s16 d19, d13, #1 @q1>>1 vsub.s16 d16, d18, d13 @x2 = (q0 >> 1) - q1; vadd.s16 d17, d11, d19 @x3 = q0+ (q1 >> 1); vld2.8 {d30, d31}, [r1], r3 @IV row Load pu1_pred buffer vswp d16, d17 @Reverse positions of x2 and x3 vsub.s16 q11, q7, q8 @x0-x3 and x1-x2 combined vtrn.32 d29, d30 @ D29 -- row III and IV of pu1_pred_buf vadd.s16 q10, q7, q8 @x0 + x3 and x1+x2 combined vswp d22, d23 vrshr.s16 q10, q10, #6 @ vrshr.s16 q11, q11, #6 vaddw.u8 q10, q10, d28 vaddw.u8 q11, q11, d29 vld1.u8 d0, [r2], r4 @Loading out buffer 16 coeffs vld1.u8 d1, [r2], r4 vld1.u8 d2, [r2], r4 vld1.u8 d3, [r2], r4 sub r2, r2, r4, lsl #2 vqmovun.s16 d20, q10 @Getting quantized coeffs vqmovun.s16 d22, q11 vmovl.u8 q10, d20 @Move the coffs into 16 bit vmovl.u8 q11, d22 @so that we can use vbit to copy vmov.u16 q14, #0x00ff @Copy lsb from qantized(long)coeffs vbit.u8 q0, q10, q14 vbit.u8 q1, q11, q14 vst1.u8 d0, [r2], r4 vst1.u8 d1, [r2], r4 vst1.u8 d2, [r2], r4 vst1.u8 d3, [r2] vpop {d8-d15} ldmfd sp!, {r4-r12, r15} @Reload the registers from SP @* @ ******************************************************************************* @ * @ * @brief @ * This function performs inverse quant and Inverse transform type Ci4 for 8*8 block @ * @ * @par Description: @ * Performs inverse transform Ci8 and adds the residue to get the @ * reconstructed block @ * @ * @param[in] pi2_src @ * Input 4x4 coefficients @ * @ * @param[in] pu1_pred @ * Prediction 4x4 block @ * @ * @param[out] pu1_out @ * Output 4x4 block @ * @ * @param[in] u4_qp_div_6 @ * QP @ * @ * @param[in] pu2_weigh_mat @ * Pointer to weight matrix @ * @ * @param[in] pred_strd, @ * Prediction stride @ * @ * @param[in] out_strd @ * Output Stride @ * @ *@param[in] pi2_tmp @ * temporary buffer of size 1*64 @ * @ * @param[in] pu2_iscal_mat @ * Pointer to the inverse quantization matrix @ * @ * @returns Void @ * @ * @remarks @ * None @ * @ ******************************************************************************* @ * @void ih264_iquant_itrans_recon_8x8(WORD16 *pi2_src, @ UWORD8 *pu1_pred, @ UWORD8 *pu1_out, @ WORD32 pred_strd, @ WORD32 out_strd, @ const UWORD16 *pu2_iscal_mat, @ const UWORD16 *pu2_weigh_mat, @ UWORD32 u4_qp_div_6, @ WORD32 *pi4_tmp, @ WORD32 iq_start_idx) @**************Variables Vs Registers***************************************** @r0 => *pi2_src @r1 => *pu1_pred @r2 => *pu1_out @r3 => pred_strd @r4 => out_strd @r5 => *pu2_iscal_mat @r6 => *pu2_weigh_mat @r7 => u4_qp_div_6 .global ih264_iquant_itrans_recon_8x8_a9 ih264_iquant_itrans_recon_8x8_a9: stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments ldr r7, [sp, #52] @Loads u4_qp_div_6 ldr r4, [sp, #40] @Loads out_strd ldr r5, [sp, #44] @Loads *pu2_iscal_mat ldr r6, [sp, #48] @Loads *pu2_weigh_mat vdup.s32 q15, r7 @Populate the u4_qp_div_6 in Q15 vpush {d8-d15} idct_8x8_begin: @========= DEQUANT FROM HERE =========== vld1.32 {q13}, [r5]! @ Q13 = dequant values row 0 vld1.32 {q10}, [r6]! @ Q10 = scaling factors row 0 vld1.32 {q14}, [r5]! @ Q14 = dequant values row 1 vmul.s16 q10, q10, q13 @ Q10 = x[i] = (scale[i] * dequant[i]) where i = 0..7 vld1.32 {q11}, [r6]! @ Q11 = scaling factors row 1 vld1.32 {q8}, [r0]! @ Q8 = Source row 0 vmul.s16 q11, q11, q14 @ Q11 = x[i] = (scale[i] * dequant[i]) where i = 8..15 vmull.s16 q0, d16, d20 @ Q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 vld1.32 {q9}, [r0]! @ Q8 = Source row 1 vmull.s16 q1, d17, d21 @ Q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 vmull.s16 q2, d18, d22 @ Q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 vld1.32 {q13}, [r6]! @ Scaling factors row 2 vmull.s16 q3, d19, d23 @ Q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 vld1.32 {q14}, [r6]! @ Scaling factors row 3 vshl.s32 q0, q0, q15 @ Q0 = q[i] = (p[i] << (qP/6)) where i = 0..3 vld1.32 {q10}, [r5]! @ Q10 = Dequant values row 2 vshl.s32 q1, q1, q15 @ Q1 = q[i] = (p[i] << (qP/6)) where i = 4..7 vld1.32 {q8}, [r0]! @ Source Row 2 vshl.s32 q2, q2, q15 @ Q2 = q[i] = (p[i] << (qP/6)) where i = 8..11 vld1.32 {q11}, [r5]! @ Q11 = Dequant values row 3 vshl.s32 q3, q3, q15 @ Q3 = q[i] = (p[i] << (qP/6)) where i = 12..15 vld1.32 {q9}, [r0]! @ Source Row 3 vmul.s16 q10, q10, q13 @ Dequant row2*scale matrix row 2 vmul.s16 q11, q11, q14 @ Dequant row 3*scale matrix row 3 vld1.32 {q4}, [r6]! @ Scaling factors row 4 vqrshrn.s32 d0, q0, #0x6 @ D0 = c[i] = ((q[i] + 32) >> 6) where i = 0..3 vqrshrn.s32 d1, q1, #0x6 @ D1 = c[i] = ((q[i] + 32) >> 6) where i = 4..7 vld1.32 {q5}, [r6]! @ Scaling factors row 5 vqrshrn.s32 d2, q2, #0x6 @ D2 = c[i] = ((q[i] + 32) >> 6) where i = 8..11 vqrshrn.s32 d3, q3, #0x6 @ D3 = c[i] = ((q[i] + 32) >> 6) where i = 12..15 vld1.32 {q13}, [r5]! @ Q13 = Dequant values row 4 vmull.s16 q2, d16, d20 @ p[i] = (x[i] * trns_coeff[i]) where i=16..19 vmull.s16 q3, d17, d21 @ p[i] = (x[i] * trns_coeff[i]) where i=20..23 vld1.32 {q12}, [r5]! @ Q12 = Dequant values row 5 vmull.s16 q6, d18, d22 @ p[i] = (x[i] * trns_coeff[i]) where i=24..27 vmull.s16 q7, d19, d23 @ p[i] = (x[i] * trns_coeff[i]) where i=28..31 vld1.32 {q14}, [r0]! @ Source row 4 vmul.s16 q10, q4, q13 @ Dequant row4*scale matrix row 4 vmul.s16 q11, q5, q12 @ Dequant row5*scale matrix row 5 vld1.32 {q9}, [r0]! @ Source row 5 vshl.s32 q2, q2, q15 @ vshl.s32 q3, q3, q15 @ vld1.32 {q13}, [r6]! @ Scaling factors row 6 vshl.s32 q6, q6, q15 @ vshl.s32 q7, q7, q15 @ vmull.s16 q4, d28, d20 @ i = 32..35 vqrshrn.s32 d4, q2, #0x6 @ D4 = c[i] = ((q[i] + 32) >> 6) where i = 16..19 vqrshrn.s32 d5, q3, #0x6 @ D5 = c[i] = ((q[i] + 32) >> 6) where i = 20..23 vmull.s16 q5, d29, d21 @ i =36..39 vld1.32 {q10}, [r5]! @ Dequant values row 6 vqrshrn.s32 d6, q6, #0x6 @ D6 = c[i] = ((q[i] + 32) >> 6) where i = 24..27 vqrshrn.s32 d7, q7, #0x6 @ D7 = c[i] = ((q[i] + 32) >> 6) where i = 28..31 vld1.32 {q14}, [r6]! @ Scaling factors row 7 vmull.s16 q6, d18, d22 @ vld1.32 {q8}, [r0]! @ Source row 6 vmull.s16 q7, d19, d23 @ vld1.32 {q11}, [r5]! @ Dequant values row 7 vshl.s32 q4, q4, q15 @ vld1.32 {q9}, [r0]! @ Source row 7 vshl.s32 q5, q5, q15 @ vshl.s32 q6, q6, q15 @ vshl.s32 q7, q7, q15 @ vmul.s16 q10, q10, q13 @ Dequant*scaling row 6 vmul.s16 q11, q11, q14 @ Dequant*scaling row 7 vqrshrn.s32 d8, q4, #0x6 @ D8 = c[i] = ((q[i] + 32) >> 6) where i = 32..35 vqrshrn.s32 d9, q5, #0x6 @ D9 = c[i] = ((q[i] + 32) >> 6) where i = 36..39 vqrshrn.s32 d10, q6, #0x6 @ D10 = c[i] = ((q[i] + 32) >> 6) where i = 40..43 vqrshrn.s32 d11, q7, #0x6 @ D11 = c[i] = ((q[i] + 32) >> 6) where i = 44..47 vmull.s16 q6, d16, d20 @ i= 48..51 vmull.s16 q7, d17, d21 @ i= 52..55 vmull.s16 q8, d18, d22 @ i=56..59 vmull.s16 q9, d19, d23 @ i=60..63 vshl.s32 q6, q6, q15 @ vzip.s16 q0, q1 @Transpose vshl.s32 q7, q7, q15 @ vshl.s32 q8, q8, q15 @ vzip.s16 q2, q3 @ vshl.s32 q9, q9, q15 @ vqrshrn.s32 d12, q6, #0x6 @ D12 = c[i] = ((q[i] + 32) >> 6) where i = 48..51 vzip.s16 q4, q5 @Transpose vqrshrn.s32 d13, q7, #0x6 @ D13 = c[i] = ((q[i] + 32) >> 6) where i = 52..55 vqrshrn.s32 d14, q8, #0x6 @ D14 = c[i] = ((q[i] + 32) >> 6) where i = 56..59 vzip.s32 q0, q2 @Transpose vqrshrn.s32 d15, q9, #0x6 @ D15 = c[i] = ((q[i] + 32) >> 6) where i = 60..63 @========= PROCESS IDCT FROM HERE ======= @Steps for Stage 2: @------------------ @ TRANSPOSE 8x8 coeffs to actual order vzip.s16 q6, q7 @ vzip.s32 q1, q3 @ vzip.s32 q4, q6 @ vzip.s32 q5, q7 @ vswp d1, d8 @ Q0/Q1 = Row order x0/x1 vswp d3, d10 @ Q2/Q3 = Row order x2/x3 vswp d5, d12 @ Q4/Q5 = Row order x4/x5 vswp d7, d14 @ Q6/Q7 = Row order x6/x7 vswp q1, q4 @ vshr.s16 q10, q2, #0x1 @ vswp q3, q6 @ @Steps for Stage 1: @------------------ vadd.s16 q8, q0, q4 @ Q8 = y0 vsub.s16 q9, q0, q4 @ Q9 = y2 vsra.s16 q2, q6, #0x1 @ Q2 = y6 vsub.s16 q6, q10, q6 @ Q6 = y4 vaddl.s16 q12, d14, d2 @ y3 (0-3) 1+7 vaddl.s16 q13, d15, d3 @ y3 (4-7) 1+7 vsubl.s16 q10, d14, d2 @ y5 (0-3) 7-1 vsubl.s16 q11, d15, d3 @ y5 (4-7) 7-1 vadd.s16 q0, q8, q2 @ Q0 = z0 vsub.s16 q4, q8, q2 @ Q4 = z6 vadd.s16 q8, q9, q6 @ Q8 = z2 vsub.s16 q2, q9, q6 @ Q2 = z4 vsubw.s16 q12, q12, d6 @ y3 (0-3) 1+7-3 vsubw.s16 q13, q13, d7 @ y3 (0-7) 1+7-3 vshr.s16 q6, q3, #0x1 @ vaddw.s16 q10, q10, d10 @ vaddw.s16 q11, q11, d11 @ vshr.s16 q9, q5, #0x1 @ vsubw.s16 q12, q12, d12 @ vsubw.s16 q13, q13, d13 @ vaddw.s16 q10, q10, d18 @ vaddw.s16 q11, q11, d19 @ vqmovn.s32 d12, q12 @ vaddl.s16 q12, d10, d6 @ vqmovn.s32 d13, q13 @ Q6 = y3 vaddl.s16 q13, d11, d7 @ vqmovn.s32 d18, q10 @ vsubl.s16 q10, d10, d6 @ vqmovn.s32 d19, q11 @ Q9 = y5 vsubl.s16 q11, d11, d7 @ vshr.s16 q3, q6, #0x2 @ vsra.s16 q6, q9, #0x2 @ Q6 = z3 vaddw.s16 q12, q12, d2 @ vaddw.s16 q13, q13, d3 @ vshr.s16 q1, #0x1 @ vsub.s16 q5, q3, q9 @ Q5 = z5 vsubw.s16 q10, q10, d14 @ vsubw.s16 q11, q11, d15 @ vshr.s16 q7, #0x1 @ vaddw.s16 q12, q12, d2 @ vaddw.s16 q13, q13, d3 @ vsubw.s16 q10, q10, d14 @ vsubw.s16 q11, q11, d15 @ vqmovn.s32 d14, q12 @ vadd.s16 q1, q8, q5 @ Q1 = x1 vqmovn.s32 d15, q13 @ Q7 = y7 vsub.s16 q3, q8, q5 @ Q3 = x6 vqmovn.s32 d18, q10 @ vsub.s16 q5, q2, q6 @ Q5 = x5 vqmovn.s32 d19, q11 @ Q9 = y1 vadd.s16 q2, q2, q6 @ Q2 = x2 vshr.s16 q12, q9, #0x2 @ vsra.s16 q9, q7, #0x2 @ Q9 = z1 vsub.s16 q11, q7, q12 @ Q11 = z7 vadd.s16 q6, q4, q9 @ Q6 = x3 vsub.s16 q4, q4, q9 @ Q4 = x4 vsub.s16 q7, q0, q11 @ Q7 = x7 vadd.s16 q0, q0, q11 @ Q0 = x0 vswp.s16 q3, q6 @ Q3 = x3, Q6 = x6 @Steps for Stage 2: @------------------ @ TRANSPOSE 8x8 coeffs to actual order vzip.s16 q0, q1 @ vzip.s16 q2, q3 @ vzip.s16 q4, q5 @ vzip.s16 q6, q7 @ vzip.s32 q0, q2 @ vzip.s32 q1, q3 @ vzip.s32 q4, q6 @ vzip.s32 q5, q7 @ vswp d1, d8 @ Q0/Q1 = Row order x0/x1 vswp d3, d10 @ Q2/Q3 = Row order x2/x3 vswp d5, d12 @ Q4/Q5 = Row order x4/x5 vswp d7, d14 @ Q6/Q7 = Row order x6/x7 vswp q1, q4 @ vshr.s16 q10, q2, #0x1 @ vswp q3, q6 @ @Steps for Stage 3: @------------------ @Repeat stage 1 again for vertical transform vadd.s16 q8, q0, q4 @ Q8 = y0 vld1.32 d28, [r1], r3 @ Q12 = 0x070605....0x070605.... vsub.s16 q9, q0, q4 @ Q9 = y2 vsra.s16 q2, q6, #0x1 @ Q2 = y6 vsub.s16 q6, q10, q6 @ Q6 = y4 vaddl.s16 q12, d14, d2 @ vld1.32 d29, [r1], r3 @ Q12 = 0x070605....0x070605.... vaddl.s16 q13, d15, d3 @ vsubl.s16 q10, d14, d2 @ vld1.32 d30, [r1], r3 @ Q12 = 0x070605....0x070605.... vsubl.s16 q11, d15, d3 @ vadd.s16 q0, q8, q2 @ Q0 = z0 vld1.32 d31, [r1], r3 @ Q12 = 0x070605....0x070605.... vsub.s16 q4, q8, q2 @ Q4 = z6 vadd.s16 q8, q9, q6 @ Q8 = z2 vsub.s16 q2, q9, q6 @ Q2 = z4 vsubw.s16 q12, q12, d6 @ vsubw.s16 q13, q13, d7 @ vshr.s16 q6, q3, #0x1 @ vaddw.s16 q10, q10, d10 @ vaddw.s16 q11, q11, d11 @ vshr.s16 q9, q5, #0x1 @ vsubw.s16 q12, q12, d12 @ vsubw.s16 q13, q13, d13 @ vaddw.s16 q10, q10, d18 @ vaddw.s16 q11, q11, d19 @ vqmovn.s32 d12, q12 @ vaddl.s16 q12, d10, d6 @ vqmovn.s32 d13, q13 @ Q6 = y3 vaddl.s16 q13, d11, d7 @ vqmovn.s32 d18, q10 @ vsubl.s16 q10, d10, d6 @ vqmovn.s32 d19, q11 @ Q9 = y5 vsubl.s16 q11, d11, d7 @ vshr.s16 q3, q6, #0x2 @ vsra.s16 q6, q9, #0x2 @ Q6 = z3 vaddw.s16 q12, q12, d2 @ vaddw.s16 q13, q13, d3 @ vshr.s16 q1, #0x1 @ vsub.s16 q5, q3, q9 @ Q5 = z5 vsubw.s16 q10, q10, d14 @ vsubw.s16 q11, q11, d15 @ vshr.s16 q7, #0x1 @ vaddw.s16 q12, q12, d2 @ vaddw.s16 q13, q13, d3 @ vsubw.s16 q10, q10, d14 @ vsubw.s16 q11, q11, d15 @ vqmovn.s32 d14, q12 @ vadd.s16 q1, q8, q5 @ Q1 = x1 vqmovn.s32 d15, q13 @ Q7 = y7 vsub.s16 q3, q8, q5 @ Q3 = x6 vqmovn.s32 d18, q10 @ vsub.s16 q5, q2, q6 @ Q5 = x5 vqmovn.s32 d19, q11 @ Q9 = y1 vadd.s16 q2, q2, q6 @ Q2 = x2 vshr.s16 q12, q9, #0x2 @ vsra.s16 q9, q7, #0x2 @ Q9 = z1 vsub.s16 q11, q7, q12 @ Q11 = z7 vadd.s16 q6, q4, q9 @ Q6 = x3 vsub.s16 q4, q4, q9 @ Q4 = x4 vsub.s16 q7, q0, q11 @ Q7 = x7 vadd.s16 q0, q0, q11 @ Q0 = x0 vswp.s16 q3, q6 @ Q3 <-> Q6 vrshr.s16 q1, q1, #6 @ vld1.32 d16, [r1], r3 @ Q12 = 0x070605....0x070605.... vrshr.s16 q2, q2, #6 @ vrshr.s16 q4, q4, #6 @ vld1.32 d17, [r1], r3 @ Q12 = 0x070605....0x070605.... vrshr.s16 q5, q5, #6 @ vrshr.s16 q7, q7, #6 @ vld1.32 d18, [r1], r3 @ Q12 = 0x070605....0x070605.... vrshr.s16 q0, q0, #6 @ vrshr.s16 q3, q3, #6 @ vld1.32 d19, [r1], r3 @ Q12 = 0x070605....0x070605.... vrshr.s16 q6, q6, #6 @ @ Code Added to pack sign and magnitudes vaddw.u8 q0, q0, d28 vaddw.u8 q1, q1, d29 vaddw.u8 q2, q2, d30 vaddw.u8 q3, q3, d31 vqmovun.s16 d0, q0 vaddw.u8 q4, q4, d16 vqmovun.s16 d1, q1 vaddw.u8 q5, q5, d17 vqmovun.s16 d2, q2 vaddw.u8 q6, q6, d18 vqmovun.s16 d3, q3 vaddw.u8 q7, q7, d19 vqmovun.s16 d4, q4 vst1.32 d0, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vqmovun.s16 d5, q5 vst1.32 d1, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vqmovun.s16 d6, q6 vst1.32 d2, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vqmovun.s16 d7, q7 vst1.32 d3, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vst1.32 d4, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vst1.32 d5, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vst1.32 d6, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vst1.32 d7, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs idct_8x8_end: vpop {d8-d15} ldmfd sp!, {r4-r12, r15} ================================================ FILE: dependencies/ih264d/common/arm/ih264_iquant_itrans_recon_dc_a9.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @ ******************************************************************************* @ * @file @ * ih264_iquant_itrans_recon_dc_a9.s @ * @ * @brief @ * Contains function definitions for single stage inverse transform @ * @ * @author @ * Mohit @ * @ * @par List of Functions: @ * - ih264_iquant_itrans_recon_4x4_dc_a9() @ * - ih264_iquant_itrans_recon_8x8_dc_a9() @ * - ih264_iquant_itrans_recon_chroma_4x4_dc_a9() @ * @ * @remarks @ * None @ * @ ******************************************************************************* @* @** @ ******************************************************************************* @ * @ * @brief @ * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block @ * for dc input pattern only, i.e. only the (0,0) element of the input 4x4 block is @ * non-zero. For complete function, refer ih264_iquant_itrans_recon_a9.s @ * @ * @par Description: @ * Performs inverse transform Ci4 and adds the residue to get the @ * reconstructed block @ * @ * @param[in] pi2_src @ * Input 4x4 coefficients @ * @ * @param[in] pu1_pred @ * Prediction 4x4 block @ * @ * @param[out] pu1_out @ * Output 4x4 block @ * @ * @param[in] u4_qp_div_6 @ * QP @ * @ * @param[in] pu2_weigh_mat @ * Pointer to weight matrix @ * @ * @param[in] pred_strd, @ * Prediction stride @ * @ * @param[in] out_strd @ * Output Stride @ * @ *@param[in] pi2_tmp @ * temporary buffer of size 1*16 @ * @ * @param[in] pu2_iscal_mat @ * Pointer to the inverse quantization matrix @ * @ * @returns Void @ * @ * @remarks @ * None @ * @ ******************************************************************************* @ * @void ih264_iquant_itrans_recon_4x4_dc(WORD16 *pi2_src, @ UWORD8 *pu1_pred, @ UWORD8 *pu1_out, @ WORD32 pred_strd, @ WORD32 out_strd, @ const UWORD16 *pu2_iscal_mat, @ const UWORD16 *pu2_weigh_mat, @ UWORD32 u4_qp_div_6, @ WORD32 *pi4_tmp, @ WORD32 iq_start_idx @ WORD16 *pi2_dc_ld_addr) @**************Variables Vs Registers***************************************** @r0 => *pi2_src @r1 => *pu1_pred @r2 => *pu1_out @r3 => pred_strd @r4 => out_strd @r5 => *pu2_iscal_mat @r6 => *pu2_weigh_mat @r7 => u4_qp_div_6 @r9 => iq_start_idx @unused => pi2_dc_ld_addr .text .syntax unified .p2align 2 .global ih264_iquant_itrans_recon_4x4_dc_a9 ih264_iquant_itrans_recon_4x4_dc_a9: @Only one shift is done in horizontal inverse because, @if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value @if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 stmfd sp!, {r4-r10, r14} @stack stores the values of the arguments ldr r5, [sp, #36] @Loads *pu2_iscal_mat ldr r6, [sp, #40] @Loads *pu2_weigh_mat ldrsh r8, [r0] @load pi2_src[0], SH for signed halfword load ldrh r6, [r6] @load pu2_weight_mat[0] , H for unsigned halfword load ldrh r5, [r5] @load pu2_iscal_mat[0] , H for unsigned halfword load @=======================DEQUANT FROM HERE=================================== mul r6, r6, r5 @pu2_iscal_mat[0]*pu2_weigh_mat[0] ldr r7, [sp, #44] @Loads u4_qp_div_6 mul r6, r6, r8 @pi2_src[0]*pu2_iscal_mat[0]*pu2_weigh_mat[0] ldr r4, [sp, #32] @Loads out_strd ldr r9, [sp, #52] @Loads iq_start_idx lsl r6, r6, r7 @(pi2_src[0]*pu2_iscal_mat[0]*pu2_weigh_mat[0])< intra case , so result of subtraction is zero and Z flag is set ldrsheq r10, [r0] @ Loads signed halfword pi2_src[0], if r9==1 moveq r6, r10 @ Restore dc value in case of intra, i.e. r9 == 1 add r6, r6, #32 @i_macro = q0 + 32 asr r6, r6, #6 @i_macro >>6 = DC output of 2-stage transform vdup.s16 q0, r6 @copy transform output to Q0 vld1.32 d30[0], [r1], r3 @I row Load pu1_pred buffer vld1.32 d30[1], [r1], r3 @II row Load pu1_pred buffer vld1.32 d31[0], [r1], r3 @III row Load pu1_pred buf vld1.32 d31[1], [r1], r3 @IV row Load pu1_pred buffer vaddw.u8 q10, q0, d30 vaddw.u8 q11, q0, d31 vqmovun.s16 d0, q10 vst1.32 d0[0], [r2], r4 @I row store the value vqmovun.s16 d1, q11 vst1.32 d0[1], [r2], r4 @II row store the value vst1.32 d1[0], [r2], r4 @III row store the value vst1.32 d1[1], [r2] @IV row store the value ldmfd sp!, {r4-r10, r15} @Reload the registers from SP @* @ ******************************************************************************* @ * @ * @brief @ * This function performs inverse quant and Inverse transform type Ci4 for 8*8 block @ * for dc input pattern only, i.e. only the (0,0) element of the input 8x8 block is @ * non-zero. For complete function, refer ih264_iquant_itrans_recon_a9.s @ * @ * @par Description: @ * Performs inverse transform Ci8 and adds the residue to get the @ * reconstructed block @ * @ * @param[in] pi2_src @ * Input 4x4 coefficients @ * @ * @param[in] pu1_pred @ * Prediction 4x4 block @ * @ * @param[out] pu1_out @ * Output 4x4 block @ * @ * @param[in] u4_qp_div_6 @ * QP @ * @ * @param[in] pu2_weigh_mat @ * Pointer to weight matrix @ * @ * @param[in] pred_strd, @ * Prediction stride @ * @ * @param[in] out_strd @ * Output Stride @ * @ *@param[in] pi2_tmp @ * temporary buffer of size 1*64 @ * @ * @param[in] pu2_iscal_mat @ * Pointer to the inverse quantization matrix @ * @ * @returns Void @ * @ * @remarks @ * None @ * @ ******************************************************************************* @ * @void ih264_iquant_itrans_recon_8x8_dc(WORD16 *pi2_src, @ UWORD8 *pu1_pred, @ UWORD8 *pu1_out, @ WORD32 pred_strd, @ WORD32 out_strd, @ const UWORD16 *pu2_iscal_mat, @ const UWORD16 *pu2_weigh_mat, @ UWORD32 u4_qp_div_6, @ WORD32 *pi4_tmp, @ WORD32 iq_start_idx) @**************Variables Vs Registers***************************************** @r0 => *pi2_src @r1 => *pu1_pred @r2 => *pu1_out @r3 => pred_strd @r4 => out_strd @r5 => *pu2_iscal_mat @r6 => *pu2_weigh_mat @r7 => u4_qp_div_6 .global ih264_iquant_itrans_recon_8x8_dc_a9 ih264_iquant_itrans_recon_8x8_dc_a9: stmfd sp!, {r4-r8, r14} @stack stores the values of the arguments ldr r5, [sp, #28] @Loads *pu2_iscal_mat ldr r6, [sp, #32] @Loads *pu2_weigh_mat ldrsh r8, [r0] @load pi2_src[0], SH for signed halfword load ldrh r6, [r6] @load pu2_weight_mat[0] , H for unsigned halfword load ldrh r5, [r5] @load pu2_iscal_mat[0] , H for unsigned halfword load @=======================DEQUANT FROM HERE=================================== mul r6, r6, r5 @pu2_iscal_mat[0]*pu2_weigh_mat[0] ldr r7, [sp, #36] @Loads u4_qp_div_6 mul r6, r6, r8 @pi2_src[0]*pu2_iscal_mat[0]*pu2_weigh_mat[0] ldr r4, [sp, #24] @Loads out_strd vpush {d8-d15} lsl r6, r6, r7 @(pi2_src[0]*pu2_iscal_mat[0]*pu2_weigh_mat[0])<>6 = DC output of 2-stage transform vdup.s16 q8, r6 @copy transform output to Q0 vld1.32 d24, [r1], r3 @ Q12 = 0x070605....0x070605.... vld1.32 d25, [r1], r3 @ Q12 = 0x070605....0x070605.... vld1.32 d26, [r1], r3 @ Q12 = 0x070605....0x070605.... vaddw.u8 q0, q8, d24 vld1.32 d27, [r1], r3 @ Q12 = 0x070605....0x070605.... vaddw.u8 q1, q8, d25 vld1.32 d28, [r1], r3 @ Q12 = 0x070605....0x070605.... vaddw.u8 q2, q8, d26 vld1.32 d29, [r1], r3 @ Q12 = 0x070605....0x070605.... vaddw.u8 q3, q8, d27 vld1.32 d30, [r1], r3 @ Q12 = 0x070605....0x070605.... vaddw.u8 q4, q8, d28 vld1.32 d31, [r1], r3 @ Q12 = 0x070605....0x070605.... @ Code Added to pack sign and magnitudes vqmovun.s16 d0, q0 vaddw.u8 q5, q8, d29 vqmovun.s16 d1, q1 vaddw.u8 q6, q8, d30 vqmovun.s16 d2, q2 vqmovun.s16 d3, q3 vaddw.u8 q7, q8, d31 vqmovun.s16 d4, q4 vqmovun.s16 d5, q5 vst1.32 d0, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vqmovun.s16 d6, q6 vst1.32 d1, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vqmovun.s16 d7, q7 vst1.32 d2, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vst1.32 d3, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vst1.32 d4, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vst1.32 d5, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vst1.32 d6, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vst1.32 d7, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs vpop {d8-d15} ldmfd sp!, {r4-r8, r15} @ * @ ******************************************************************************** @ * @ * @brief This function reconstructs a 4x4 sub block from quantized resiude and @ * prediction buffer if only dc value is present for residue @ * @ * @par Description: @ * The quantized residue is first inverse quantized, @ * This inverse quantized content is added to the prediction buffer to recon- @ * struct the end output @ * @ * @param[in] pi2_src @ * quantized dc coeffiient @ * @ * @param[in] pu1_pred @ * prediction 4x4 block in interleaved format @ * @ * @param[in] pred_strd, @ * Prediction buffer stride in interleaved format @ * @ * @param[in] out_strd @ * recon buffer Stride @ * @ * @returns none @ * @ * @remarks none @ * @ ******************************************************************************* @ * @ void ih264_iquant_itrans_recon_chroma_4x4_dc(WORD16 *pi2_src, @ UWORD8 *pu1_pred, @ UWORD8 *pu1_out, @ WORD32 pred_strd, @ WORD32 out_strd, @ const UWORD16 *pu2_iscal_mat, @ const UWORD16 *pu2_weigh_mat, @ UWORD32 u4_qp_div_6, @ WORD16 *pi2_tmp, @ WORD16 *pi2_dc_src) @ Register Usage @ r0 : pi2_src @ r1 : pu1_pred @ r2 : pu1_out @ r3 : pred_strd @ Neon registers d0-d7, d16-d30 are used @ No need for pushing arm and neon registers .global ih264_iquant_itrans_recon_chroma_4x4_dc_a9 ih264_iquant_itrans_recon_chroma_4x4_dc_a9: ldr r0, [sp, #20] vld1.s16 d0, [r0] @load pi2_dc_src ldr r0, [sp] @load out_strd vld2.s8 {d2, d3}, [r1], r3 @load pred plane 1 => d2 &pred palne 2 => d3 vld2.s8 {d3, d4}, [r1], r3 vrshr.s16 d0, d0, #6 @i_macro = ((q0 + 32) >> 6); vld2.s8 {d4, d5}, [r1], r3 vld2.s8 {d5, d6}, [r1], r3 vdup.s16 q0, d0[0] @duplicate pi2_sr[0] mov r1, r2 @backup pu1_out vtrn.32 d2, d3 @mov the 4 coeffs of current block to d2 vtrn.32 d4, d5 vmov.u16 q15, #0x00ff vld1.u8 d18, [r2], r0 @load out [8 bit size) -8 coeffs vaddw.u8 q1, q0, d2 @Add pred vld1.u8 d19, [r2], r0 vaddw.u8 q2, q0, d4 vld1.u8 d20, [r2], r0 vld1.u8 d21, [r2], r0 vqmovun.s16 d2, q1 vqmovun.s16 d4, q2 vmovl.u8 q1, d2 vmovl.u8 q2, d4 vbit.u8 q9, q1, q15 vbit.u8 q10, q2, q15 vst1.u8 d18, [r1], r0 @store out vst1.u8 d19, [r1], r0 vst1.u8 d20, [r1], r0 vst1.u8 d21, [r1], r0 bx lr ================================================ FILE: dependencies/ih264d/common/arm/ih264_mem_fns_neon.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @ ******************************************************************************* @ * @file @ * ih264_mem_fns_neon.s @ * @ * @brief @ * Contains function definitions for memory manipulation @ * @ * @author @ * Naveen SR @ * @ * @par List of Functions: @ * - ih264_memcpy_mul_8_a9q() @ * - ih264_memcpy_a9q() @ * - ih264_memset_mul_8_a9q() @ * - ih264_memset_a9q() @ * - ih264_memset_16bit_mul_8_a9q() @ * - ih264_memset_a9q() @ * @ * @remarks @ * None @ * @ ******************************************************************************* @* @** @******************************************************************************* @* @* @brief @* memcpy of a 1d array @* @* @par Description: @* Does memcpy of 8bit data from source to destination for 8,16 or 32 number of bytes @* @* @param[in] pu1_dst @* UWORD8 pointer to the destination @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[in] num_bytes @* number of bytes to copy @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @void ih264_memcpy_mul_8(UWORD8 *pu1_dst, @ UWORD8 *pu1_src, @ UWORD32 num_bytes) @**************Variables Vs Registers************************* @ r0 => *pu1_dst @ r1 => *pu1_src @ r2 => num_bytes .text .p2align 2 .global ih264_memcpy_mul_8_a9q ih264_memcpy_mul_8_a9q: loop_neon_memcpy_mul_8: @ Memcpy 8 bytes vld1.8 d0, [r1]! vst1.8 d0, [r0]! subs r2, r2, #8 bne loop_neon_memcpy_mul_8 bx lr @******************************************************************************* @* @void ih264_memcpy(UWORD8 *pu1_dst, @ UWORD8 *pu1_src, @ UWORD32 num_bytes) @**************Variables Vs Registers************************* @ r0 => *pu1_dst @ r1 => *pu1_src @ r2 => num_bytes .global ih264_memcpy_a9q ih264_memcpy_a9q: subs r2, #8 blt memcpy loop_neon_memcpy: @ Memcpy 8 bytes vld1.8 d0, [r1]! vst1.8 d0, [r0]! subs r2, #8 bge loop_neon_memcpy cmp r2, #-8 bxeq lr memcpy: add r2, #8 loop_memcpy: ldrb r3, [r1], #1 strb r3, [r0], #1 subs r2, #1 bne loop_memcpy bx lr @void ih264_memset_mul_8(UWORD8 *pu1_dst, @ UWORD8 value, @ UWORD32 num_bytes) @**************Variables Vs Registers************************* @ r0 => *pu1_dst @ r1 => value @ r2 => num_bytes .global ih264_memset_mul_8_a9q ih264_memset_mul_8_a9q: @ Assumptions: numbytes is either 8, 16 or 32 vdup.8 d0, r1 loop_memset_mul_8: @ Memset 8 bytes vst1.8 d0, [r0]! subs r2, r2, #8 bne loop_memset_mul_8 bx lr @void ih264_memset(UWORD8 *pu1_dst, @ UWORD8 value, @ UWORD8 num_bytes) @**************Variables Vs Registers************************* @ r0 => *pu1_dst @ r1 => value @ r2 => num_bytes .global ih264_memset_a9q ih264_memset_a9q: subs r2, #8 blt memset vdup.8 d0, r1 loop_neon_memset: @ Memcpy 8 bytes vst1.8 d0, [r0]! subs r2, #8 bge loop_neon_memset cmp r2, #-8 bxeq lr memset: add r2, #8 loop_memset: strb r1, [r0], #1 subs r2, #1 bne loop_memset bx lr @void ih264_memset_16bit_mul_8(UWORD16 *pu2_dst, @ UWORD16 value, @ UWORD32 num_words) @**************Variables Vs Registers************************* @ r0 => *pu2_dst @ r1 => value @ r2 => num_words .global ih264_memset_16bit_mul_8_a9q ih264_memset_16bit_mul_8_a9q: @ Assumptions: num_words is either 8, 16 or 32 @ Memset 8 words vdup.16 d0, r1 loop_memset_16bit_mul_8: vst1.16 d0, [r0]! vst1.16 d0, [r0]! subs r2, r2, #8 bne loop_memset_16bit_mul_8 bx lr @void ih264_memset_16bit(UWORD16 *pu2_dst, @ UWORD16 value, @ UWORD32 num_words) @**************Variables Vs Registers************************* @ r0 => *pu2_dst @ r1 => value @ r2 => num_words .global ih264_memset_16bit_a9q ih264_memset_16bit_a9q: subs r2, #8 blt memset_16bit vdup.16 d0, r1 loop_neon_memset_16bit: @ Memset 8 words vst1.16 d0, [r0]! vst1.16 d0, [r0]! subs r2, #8 bge loop_neon_memset_16bit cmp r2, #-8 bxeq lr memset_16bit: add r2, #8 loop_memset_16bit: strh r1, [r0], #2 subs r2, #1 bne loop_memset_16bit bx lr ================================================ FILE: dependencies/ih264d/common/arm/ih264_padding_neon.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @* @ ******************************************************************************* @ * @file @ * ih264_padding_neon.s @ * @ * @brief @ * Contains function definitions padding @ * @ * @author @ * Ittiam @ * @ * @par List of Functions: @ * - ih264_pad_top_a9q() @ * - ih264_pad_left_luma_a9q() @ * - ih264_pad_left_chroma_a9q() @ * - ih264_pad_right_luma_a9q() @ * - ih264_pad_right_chroma_a9q() @ * @ * @remarks @ * None @ * @ ******************************************************************************* @* @** @******************************************************************************* @* @* @brief pad at the top of a 2d array @* @* @par Description: @* The top row of a 2d array is replicated for pad_size times at the top @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[in] src_strd @* integer source stride @* @* @param[in] wd @* integer width of the array @* @* @param[in] pad_size @* integer -padding size of the array @* @* @returns none @* @* @remarks none @* @******************************************************************************* @* @void ih264_pad_top(UWORD8 *pu1_src, @ WORD32 src_strd, @ WORD32 wd, @ WORD32 pad_size) @**************Variables Vs Registers************************* @ r0 => *pu1_src @ r1 => src_strd @ r2 => wd @ r3 => pad_size .text .p2align 2 .global ih264_pad_top_a9q ih264_pad_top_a9q: stmfd sp!, {r4-r11, lr} @stack stores the values of the arguments sub r5, r0, r1 neg r6, r1 loop_neon_memcpy_mul_16: @ Load 16 bytes vld1.8 {d0, d1}, [r0]! mov r4, r5 mov r7, r3 add r5, r5, #16 loop_neon_pad_top: vst1.8 {d0, d1}, [r4], r6 subs r7, r7, #1 bne loop_neon_pad_top subs r2, r2, #16 bne loop_neon_memcpy_mul_16 ldmfd sp!, {r4-r11, pc} @Reload the registers from SP @** @******************************************************************************* @* @* @brief @* Padding (luma block) at the left of a 2d array @* @* @par Description: @* The left column of a 2d array is replicated for pad_size times at the left @* @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[in] src_strd @* integer source stride @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @param[in] pad_size @* integer -padding size of the array @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @#if PAD_LEFT_LUMA == C @void ih264_pad_left_luma(UWORD8 *pu1_src, @ WORD32 src_strd, @ WORD32 ht, @ WORD32 pad_size) @**************Variables Vs Registers************************* @ r0 => *pu1_src @ r1 => src_strd @ r2 => ht @ r3 => pad_size .global ih264_pad_left_luma_a9q ih264_pad_left_luma_a9q: stmfd sp!, {r4-r11, lr} @stack stores the values of the arguments sub r4, r0, r3 sub r6, r1, #16 subs r5, r3, #16 bne loop_32 loop_16: @ /*hard coded for width=16 ,height =8,16*/ ldrb r8, [r0], r1 ldrb r9, [r0], r1 vdup.u8 q0, r8 ldrb r10, [r0], r1 vst1.8 {q0}, [r4], r1 @ 16 bytes store vdup.u8 q1, r9 vst1.8 {q1}, [r4], r1 @ 16 bytes store ldrb r11, [r0], r1 vdup.u8 q2, r10 vdup.u8 q3, r11 vst1.8 {q2}, [r4], r1 @ 16 bytes store ldrb r8, [r0], r1 vst1.8 {q3}, [r4], r1 @ 16 bytes store ldrb r9, [r0], r1 vdup.u8 q0, r8 ldrb r10, [r0], r1 vst1.8 {q0}, [r4], r1 @ 16 bytes store vdup.u8 q1, r9 ldrb r11, [r0], r1 vst1.8 {q1}, [r4], r1 @ 16 bytes store vdup.u8 q2, r10 vdup.u8 q3, r11 subs r2, r2, #8 vst1.8 {q2}, [r4], r1 @ 16 bytes store vst1.8 {q3}, [r4], r1 @ 16 bytes store bne loop_16 b end_func loop_32: @ /*hard coded for width=32 ,height =8,16*/ ldrb r8, [r0], r1 ldrb r9, [r0], r1 vdup.u8 q0, r8 ldrb r10, [r0], r1 vst1.8 {q0}, [r4]! @ 16 bytes store vdup.u8 q1, r9 vst1.8 {q0}, [r4], r6 vst1.8 {q1}, [r4]! @ 16 bytes store vdup.u8 q2, r10 vst1.8 {q1}, [r4], r6 @ 16 bytes store ldrb r11, [r0], r1 vst1.8 {q2}, [r4]! @ 16 bytes store vdup.u8 q3, r11 vst1.8 {q2}, [r4], r6 @ 16 bytes store ldrb r8, [r0], r1 vst1.8 {q3}, [r4]! @ 16 bytes store vdup.u8 q0, r8 ldrb r9, [r0], r1 vst1.8 {q3}, [r4], r6 @ 16 bytes store ldrb r10, [r0], r1 vst1.8 {q0}, [r4]! @ 16 bytes store vdup.u8 q1, r9 vst1.8 {q0}, [r4], r6 @ 16 bytes store ldrb r11, [r0], r1 vst1.8 {q1}, [r4]! @ 16 bytes store vdup.u8 q2, r10 vst1.8 {q1}, [r4], r6 @ 16 bytes store vst1.8 {q2}, [r4]! @ 16 bytes store vdup.u8 q3, r11 vst1.8 {q2}, [r4], r6 @ 16 bytes store subs r2, r2, #8 vst1.8 {q3}, [r4]! @ 16 bytes store vst1.8 {q3}, [r4], r6 @ 16 bytes store bne loop_32 end_func: ldmfd sp!, {r4-r11, pc} @Reload the registers from SP @** @******************************************************************************* @* @* @brief @* Padding (chroma block) at the left of a 2d array @* @* @par Description: @* The left column of a 2d array is replicated for pad_size times at the left @* @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[in] src_strd @* integer source stride @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array (each colour component) @* @* @param[in] pad_size @* integer -padding size of the array @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @#if PAD_LEFT_CHROMA == C @void ih264_pad_left_chroma(UWORD8 *pu1_src, @ WORD32 src_strd, @ WORD32 ht, @ WORD32 pad_size) @{ @ r0 => *pu1_src @ r1 => src_strd @ r2 => ht @ r3 => pad_size .global ih264_pad_left_chroma_a9q ih264_pad_left_chroma_a9q: stmfd sp!, {r4-r11, lr} @stack stores the values of the arguments sub r4, r0, r3 sub r6, r1, #16 loop_32_l_c: @ /*hard coded for width=32 ,height =4,8,12*/ ldrh r8, [r0], r1 ldrh r9, [r0], r1 vdup.u16 q0, r8 ldrh r10, [r0], r1 vst1.8 {q0}, [r4]! @ 16 bytes store vdup.u16 q1, r9 vst1.8 {q0}, [r4], r6 @ 16 bytes store ldrh r11, [r0], r1 vst1.8 {q1}, [r4]! @ 16 bytes store vdup.u16 q2, r10 vst1.8 {q1}, [r4], r6 @ 16 bytes store vdup.u16 q3, r11 vst1.8 {q2}, [r4]! @ 16 bytes store vst1.8 {q2}, [r4], r6 @ 16 bytes store subs r2, r2, #4 vst1.8 {q3}, [r4]! @ 16 bytes store vst1.8 {q3}, [r4], r6 @ 16 bytes store beq end_func_l_c @/* Branching when ht=4*/ ldrh r8, [r0], r1 ldrh r9, [r0], r1 vdup.u16 q0, r8 ldrh r10, [r0], r1 vst1.8 {q0}, [r4]! @ 16 bytes store vdup.u16 q1, r9 vst1.8 {q0}, [r4], r6 ldrh r11, [r0], r1 vst1.8 {q1}, [r4]! @ 16 bytes store vdup.u16 q2, r10 vst1.8 {q1}, [r4], r6 @ 16 bytes store vdup.u16 q3, r11 vst1.8 {q2}, [r4]! @ 16 bytes store vst1.8 {q2}, [r4], r6 @ 16 bytes store subs r2, r2, #4 vst1.8 {q3}, [r4]! @ 16 bytes store vst1.8 {q3}, [r4], r6 @ 16 bytes store beq end_func_l_c @/* Branching when ht=8*/ bne loop_32_l_c ldrh r8, [r0], r1 ldrh r9, [r0], r1 vdup.u16 q0, r8 ldrh r10, [r0], r1 vst1.8 {q0}, [r4]! @ 16 bytes store vdup.u16 q1, r9 vst1.8 {q0}, [r4], r6 ldrh r11, [r0], r1 vst1.8 {q1}, [r4]! @ 16 bytes store vdup.u16 q2, r10 vst1.8 {q1}, [r4], r6 @ 16 bytes store vdup.u16 q3, r11 vst1.8 {q2}, [r4]! @ 16 bytes store vst1.8 {q2}, [r4], r6 @ 16 bytes store vst1.8 {q3}, [r4]! @ 16 bytes store vst1.8 {q3}, [r4], r6 @ 16 bytes store end_func_l_c: ldmfd sp!, {r4-r11, pc} @Reload the registers from SP @** @******************************************************************************* @* @* @brief @* Padding (luma block) at the right of a 2d array @* @* @par Description: @* The right column of a 2d array is replicated for pad_size times at the right @* @* @* @param[in] pu1_src @* UWORD8 pointer to the source @* @* @param[in] src_strd @* integer source stride @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @param[in] pad_size @* integer -padding size of the array @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @#if PAD_RIGHT_LUMA == C @void ih264_pad_right_luma(UWORD8 *pu1_src, @ WORD32 src_strd, @ WORD32 ht, @ WORD32 pad_size) @{ @ WORD32 row; @ @ for(row = 0; row < ht; row++) @ { @ memset(pu1_src, *(pu1_src -1), pad_size); @ @ pu1_src += src_strd; @ } @} @ @ r0 => *pu1_src @ r1 => src_strd @ r2 => ht @ r3 => pad_size .global ih264_pad_right_luma_a9q ih264_pad_right_luma_a9q: stmfd sp!, {r4-r11, lr} @stack stores the values of the arguments mov r4, r0 sub r6, r1, #16 sub r0, r0, #1 subs r5, r3, #16 bne loop_32 loop_16_r: @ /*hard coded for width=16 ,height =8,16*/ ldrb r8, [r0], r1 ldrb r9, [r0], r1 vdup.u8 q0, r8 ldrb r10, [r0], r1 vst1.8 {q0}, [r4], r1 @ 16 bytes store vdup.u8 q1, r9 vst1.8 {q1}, [r4], r1 @ 16 bytes store ldrb r11, [r0], r1 vdup.u8 q2, r10 vdup.u8 q3, r11 vst1.8 {q2}, [r4], r1 @ 16 bytes store ldrb r8, [r0], r1 vst1.8 {q3}, [r4], r1 @ 16 bytes store ldrb r9, [r0], r1 vdup.u8 q0, r8 ldrb r10, [r0], r1 vst1.8 {q0}, [r4], r1 @ 16 bytes store vdup.u8 q1, r9 ldrb r11, [r0], r1 vst1.8 {q1}, [r4], r1 @ 16 bytes store vdup.u8 q2, r10 vdup.u8 q3, r11 subs r2, r2, #8 vst1.8 {q2}, [r4], r1 @ 16 bytes store vst1.8 {q3}, [r4], r1 @ 16 bytes store bne loop_16_r b end_func_r loop_32_r: @ /*hard coded for width=32 ,height =8,16*/ ldrb r8, [r0], r1 ldrb r9, [r0], r1 vdup.u8 q0, r8 ldrb r10, [r0], r1 vst1.8 {q0}, [r4]! @ 16 bytes store vdup.u8 q1, r9 vst1.8 {q0}, [r4], r6 vst1.8 {q1}, [r4]! @ 16 bytes store vdup.u8 q2, r10 vst1.8 {q1}, [r4], r6 @ 16 bytes store ldrb r11, [r0], r1 vst1.8 {q2}, [r4]! @ 16 bytes store vdup.u8 q3, r11 vst1.8 {q2}, [r4], r6 @ 16 bytes store ldrb r8, [r0], r1 vst1.8 {q3}, [r4]! @ 16 bytes store ldrb r9, [r0], r1 vdup.u8 q0, r8 vst1.8 {q3}, [r4], r6 @ 16 bytes store ldrb r10, [r0], r1 vst1.8 {q0}, [r4]! @ 16 bytes store vdup.u8 q1, r9 vst1.8 {q0}, [r4], r6 @ 16 bytes store ldrb r11, [r0], r1 vst1.8 {q1}, [r4]! @ 16 bytes store vdup.u8 q2, r10 vst1.8 {q1}, [r4], r6 @ 16 bytes store vst1.8 {q2}, [r4]! @ 16 bytes store vdup.u8 q3, r11 vst1.8 {q2}, [r4], r6 @ 16 bytes store subs r2, r2, #8 vst1.8 {q3}, [r4]! @ 16 bytes store vst1.8 {q3}, [r4], r6 @ 16 bytes store bne loop_32_r end_func_r: ldmfd sp!, {r4-r11, pc} @Reload the registers from SP @** @******************************************************************************* @* @* @brief @;* Padding (chroma block) at the right of a 2d array @* @* @par Description: @* The right column of a 2d array is replicated for pad_size times at the right @* @* @* @param[in] pu1_src @;* UWORD8 pointer to the source @* @* @param[in] src_strd @* integer source stride @* @* @param[in] ht @;* integer height of the array @* @* @param[in] wd @* integer width of the array (each colour component) @* @* @param[in] pad_size @* integer -padding size of the array @* @* @param[in] ht @;* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* @* @remarks @* None @* @******************************************************************************* @* @#if PAD_RIGHT_CHROMA == C @void ih264_pad_right_chroma(UWORD8 *pu1_src, @ WORD32 src_strd, @ WORD32 ht, @ WORD32 pad_size) @ r0 => *pu1_src @ r1 => src_strd @ r2 => ht @ r3 => pad_size .global ih264_pad_right_chroma_a9q ih264_pad_right_chroma_a9q: stmfd sp!, {r4-r11, lr} @stack stores the values of the arguments mov r4, r0 sub r6, r1, #16 sub r0, r0, #2 loop_32_r_c: @ /*hard coded for width=32 ,height =8,4*/ ldrh r8, [r0], r1 ldrh r9, [r0], r1 vdup.u16 q0, r8 ldrh r10, [r0], r1 vst1.8 {q0}, [r4]! @ 16 bytes store vdup.u16 q1, r9 vst1.8 {q0}, [r4], r6 vst1.8 {q1}, [r4]! @ 16 bytes store vdup.u16 q2, r10 vst1.8 {q1}, [r4], r6 @ 16 bytes store subs r2, r2, #4 ldrh r11, [r0], r1 vst1.8 {q2}, [r4]! @ 16 bytes store vdup.u16 q3, r11 vst1.8 {q2}, [r4], r6 @ 16 bytes store vst1.8 {q3}, [r4]! @ 16 bytes store vst1.8 {q3}, [r4], r6 @ 16 bytes store beq end_func_r_c @/* Branching when ht=4*/ ldrh r8, [r0], r1 vdup.u16 q0, r8 ldrh r9, [r0], r1 ldrh r10, [r0], r1 vst1.8 {q0}, [r4]! @ 16 bytes store vdup.u16 q1, r9 vst1.8 {q0}, [r4], r6 @ 16 bytes store ldrh r11, [r0], r1 vst1.8 {q1}, [r4]! @ 16 bytes store vdup.u16 q2, r10 vst1.8 {q1}, [r4], r6 @ 16 bytes store vst1.8 {q2}, [r4]! @ 16 bytes store vdup.u16 q3, r11 vst1.8 {q2}, [r4], r6 @ 16 bytes store subs r2, r2, #4 vst1.8 {q3}, [r4]! @ 16 bytes store vst1.8 {q3}, [r4], r6 @ 16 bytes store beq end_func_r_c @/* Branching when ht=8*/ bne loop_32_r_c ldrh r8, [r0], r1 vdup.u16 q0, r8 ldrh r9, [r0], r1 ldrh r10, [r0], r1 vst1.8 {q0}, [r4]! @ 16 bytes store vdup.u16 q1, r9 vst1.8 {q0}, [r4], r6 @ 16 bytes store ldrh r11, [r0], r1 vst1.8 {q1}, [r4]! @ 16 bytes store vdup.u16 q2, r10 vst1.8 {q1}, [r4], r6 @ 16 bytes store vst1.8 {q2}, [r4]! @ 16 bytes store vdup.u16 q3, r11 vst1.8 {q2}, [r4], r6 @ 16 bytes store vst1.8 {q3}, [r4]! @ 16 bytes store vst1.8 {q3}, [r4], r6 @ 16 bytes store end_func_r_c: ldmfd sp!, {r4-r11, pc} @Reload the registers from SP ================================================ FILE: dependencies/ih264d/common/arm/ih264_platform_macros.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_platform_macros.h * * @brief * Platform specific Macro definitions used in the codec * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_PLATFORM_MACROS_H_ #define _IH264_PLATFORM_MACROS_H_ #include #ifndef ARMV8 static __inline WORD32 CLIP_U8(WORD32 x) { asm("usat %0, #8, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_S8(WORD32 x) { asm("ssat %0, #8, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_U10(WORD32 x) { asm("usat %0, #10, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_S10(WORD32 x) { asm("ssat %0, #10, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_U11(WORD32 x) { asm("usat %0, #11, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_S11(WORD32 x) { asm("ssat %0, #11, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_U12(WORD32 x) { asm("usat %0, #12, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_S12(WORD32 x) { asm("ssat %0, #12, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_U16(WORD32 x) { asm("usat %0, #16, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_S16(WORD32 x) { asm("ssat %0, #16, %1" : "=r"(x) : "r"(x)); return x; } static __inline UWORD32 ITT_BIG_ENDIAN(UWORD32 x) { asm("rev %0, %1" : "=r"(x) : "r"(x)); return x; } #define NOP(nop_cnt) {UWORD32 nop_i; for (nop_i = 0; nop_i < nop_cnt; nop_i++) asm("nop");} #else #define CLIP_U8(x) CLIP3(0, UINT8_MAX, (x)) #define CLIP_S8(x) CLIP3(INT8_MIN, INT8_MAX, (x)) #define CLIP_U10(x) CLIP3(0, 1023, (x)) #define CLIP_S10(x) CLIP3(-512, 511, (x)) #define CLIP_U11(x) CLIP3(0, 2047, (x)) #define CLIP_S11(x) CLIP3(-1024, 1023, (x)) #define CLIP_U12(x) CLIP3(0, 4095, (x)) #define CLIP_S12(x) CLIP3(-2048, 2047, (x)) #define CLIP_U16(x) CLIP3(0, UINT16_MAX, (x)) #define CLIP_S16(x) CLIP3(INT16_MIN, INT16_MAX, (x)) #define ITT_BIG_ENDIAN(x) __asm__("rev %0, %1" : "=r"(x) : "r"(x)); #define NOP(nop_cnt) \ { \ UWORD32 nop_i; \ for (nop_i = 0; nop_i < nop_cnt; nop_i++) \ __asm__ __volatile__("mov x0, x0"); \ } #endif /*saturating instructions are not available for WORD64 in ARMv7, hence we cannot * use inline assembly like other clips*/ #define CLIP_U32(x) CLIP3(0, UINT32_MAX, (x)) #define CLIP_S32(x) CLIP3(INT32_MIN, INT32_MAX, (x)) #define DATA_SYNC() __sync_synchronize() #define SHL(x,y) (((y) < 32) ? ((x) << (y)) : 0) #define SHR(x,y) (((y) < 32) ? ((x) >> (y)) : 0) #define SHR_NEG(val,shift) ((shift>0)?(val>>shift):(val<<(-shift))) #define SHL_NEG(val,shift) ((shift<0)?(val>>(-shift)):(val<> 1; vshrn.s32 d1, q1, #1 @i4_value = (x3 + x2) >> 1; vshrn.s32 d2, q2, #1 @i4_value = (x0 - x1) >> 1; vshrn.s32 d3, q3, #1 @i4_value = (x3 - x2) >> 1; vabs.s16 q5, q0 vabs.s16 q6, q1 vmov.s32 q8, q7 @Get the round fact vmov.s32 q9, q7 vmov.s32 q10, q7 vclt.s16 q3, q0, #0 @get the sign row 1,2 vclt.s16 q4, q1, #0 vneg.s32 q11, q11 @-u4_round_factor vmlal.u16 q7, d10, d30 vmlal.u16 q8, d11, d30 vmlal.u16 q9, d12, d30 vmlal.u16 q10, d13, d30 vshl.u32 q7, q7, q11 vshl.u32 q8, q8, q11 vshl.u32 q9, q9, q11 vshl.u32 q10, q10, q11 vqmovn.u32 d22, q7 vqmovn.u32 d23, q8 vqmovn.u32 d24, q9 vqmovn.u32 d25, q10 vneg.s16 q13, q11 vneg.s16 q14, q12 vbsl.s16 q3, q13, q11 vbsl.s16 q4, q14, q12 vceq.s16 q5, q11, #0 vceq.s16 q6, q12, #0 vst1.s16 {q3}, [r1]! vshrn.u16 d14, q5, #8 vshrn.u16 d15, q6, #8 ldr r3, [sp, #72] @Load *pu1_nnz vshr.u8 q7, q7, #7 vst1.s16 {q4}, [r1]! vadd.u8 d16, d14, d15 vmov.u8 d20, #16 vpadd.u8 d17, d16, d16 vpadd.u8 d18, d17, d17 vpadd.u8 d19, d18, d18 vsub.u8 d20, d20, d19 vst1.u8 d20[0], [r3] vpop {d8-d15} bx lr @***************************************************************************** @* @* Function Name : ih264_hadamard_quant_2x2_uv_a9 @* Description : This function does forward hadamard transform and @* quantization for dc block of chroma for both planes @* @* Arguments : R0 :pointer to src buffer @ R1 :pointer to dst buffer @ R2 :pu2_scale_matrix @ R2 :pu2_threshold_matrix @ STACk : u4_qbits @ u4_round_factor @ pu1_nnz @ Values Returned : NONE @ @ Register Usage : @ Stack Usage : 0 bytes @ Cycles : Around @ Interruptiaility : Interruptable @ @ Known Limitations @ \Assumptions : @ @ Revision History : @ DD MM YYYY Author(s) Changes @ 20 2 2015 100633 First version @ @***************************************************************************** @ ih264_hadamard_quant_2x2_uv_a9(WORD16 *pi2_src, WORD16 *pi2_dst, @ const UWORD16 *pu2_scale_matrix, @ const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, @ UWORD32 u4_round_factor,UWORD8 *pu1_nnz @ ) .global ih264_hadamard_quant_2x2_uv_a9 ih264_hadamard_quant_2x2_uv_a9: vpush {d8-d15} vld2.s16 {d0-d1}, [r0] @load src add r3, sp, #68 @Get address of u4_round_factor vaddl.s16 q3, d0, d1 @x0 = x4 + x5;, x2 = x6 + x7; vld1.u16 d30[0], [r2] @load pu2_scale_matrix[0] vsubl.s16 q4, d0, d1 @x1 = x4 - x5; x3 = x6 - x7; add r0, sp, #64 @Get affress of u4_qbits vld1.s32 d28[0], [r3] @load u4_round_factor vtrn.s32 q3, q4 @q1 -> x0 x1, q2 -> x2 x3 vadd.s32 q0, q3, q4 @ (x0 + x2) (x1 + x3) (y0 + y2); (y1 + y3); vld1.s32 d24[0], [r0] @load u4_qbits vsub.s32 q1, q3, q4 @ (x0 - x2) (x1 - x3) (y0 - y2); (y1 - y3); vdup.u16 d30, d30[0] @pu2_scale_matrix vabs.s32 q2, q0 vabs.s32 q3, q1 vdup.s32 q14, d28[0] @u4_round_factor vmovl.u16 q15, d30 @pu2_scale_matrix vclt.s32 q4, q0, #0 @get the sign row 1,2 vdup.s32 q12, d24[0] @u4_round_factor vclt.s32 q5, q1, #0 vqmovn.u32 d8, q4 vqmovn.s32 d9, q5 vmov.s32 q13, q14 @Get the round fact vneg.s32 q12, q12 @-u4_round_factor vmla.u32 q13, q2, q15 vmla.u32 q14, q3, q15 vshl.u32 q13, q13, q12 @>>qbit vshl.u32 q14, q14, q12 @>>qbit vqmovn.u32 d10, q13 vqmovn.u32 d11, q14 vneg.s16 q6, q5 vbsl.s16 q4, q6, q5 @*sign vtrn.s32 d8, d9 vceq.s16 q7, q4, #0 @Compute nnz vshrn.u16 d14, q7, #8 @reduce nnz comparison to 1 bit ldr r3, [sp, #72] @Load *pu1_nnz vshr.u8 d14, d14, #7 @reduce nnz comparison to 1 bit vmov.u8 d20, #4 @Since we add zeros, we need to subtract from 4 to get nnz vpadd.u8 d17, d14, d14 @Sum up nnz vst1.s16 {q4}, [r1]! @Store the block vpadd.u8 d17, d17, d17 @Sum up nnz vsub.u8 d20, d20, d17 @4- numzeros vst1.u16 d20[0], [r3] @store nnz vpop {d8-d15} bx lr ================================================ FILE: dependencies/ih264d/common/arm/ih264_weighted_bi_pred_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_weighted_bi_pred_a9q.s @* @* @brief @* Contains function definitions for weighted biprediction. @* @* @author @* Kaushik Senthoor R @* @* @par List of Functions: @* @* - ih264_weighted_bi_pred_luma_a9q() @* - ih264_weighted_bi_pred_chroma_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @******************************************************************************* @* @function @* ih264_weighted_bi_pred_luma_a9q() @* @* @brief @* This routine performs the weighted biprediction as described in sec @* 8.4.2.3.2 titled "Weighted sample prediction process" for luma. @* @* @par Description: @* This function gets two ht x wd blocks, calculates the weighted samples, @* rounds off, adds offset and stores it in the destination block. @* @* @param[in] pu1_src1 @* UWORD8 Pointer to the buffer containing the input block 1. @* @* @param[in] pu1_src2 @* UWORD8 Pointer to the buffer containing the input block 2. @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination where the output block is stored. @* @* @param[in] src_strd1 @* Stride of the input buffer 1 @* @* @param[in] src_strd2 @* Stride of the input buffer 2 @* @* @param[in] dst_strd @* Stride of the destination buffer @* @* @param[in] log_wd @* number of bits to be rounded off @* @* @param[in] wt1 @* weight for the weighted prediction @* @* @param[in] wt2 @* weight for the weighted prediction @* @* @param[in] ofst1 @* offset 1 used after rounding off @* @* @param[in] ofst2 @* offset 2 used after rounding off @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* None @* @* @remarks @* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). @* @******************************************************************************* @* @void ih264_weighted_bi_pred_luma_a9q(UWORD8 *pu1_src1, @ UWORD8 *pu1_src2, @ UWORD8 *pu1_dst, @ WORD32 src_strd1, @ WORD32 src_strd2, @ WORD32 dst_strd, @ WORD32 log_wd, @ WORD32 wt1, @ WORD32 wt2, @ WORD32 ofst1, @ WORD32 ofst2, @ WORD32 ht, @ WORD32 wd) @ @**************Variables Vs Registers***************************************** @ r0 => pu1_src1 @ r1 => pu1_src2 @ r2 => pu1_dst @ r3 => src_strd1 @ [sp] => src_strd2 (r4) @ [sp+4] => dst_strd (r5) @ [sp+8] => log_wd (r6) @ [sp+12] => wt1 (r7) @ [sp+16] => wt2 (r8) @ [sp+20] => ofst1 (r9) @ [sp+24] => ofst2 (r10) @ [sp+28] => ht (r11) @ [sp+32] => wd (r12) @ .text .p2align 2 .global ih264_weighted_bi_pred_luma_a9q ih264_weighted_bi_pred_luma_a9q: stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments ldr r6, [sp, #48] @Load log_wd in r6 ldr r7, [sp, #52] @Load wt1 in r7 ldr r8, [sp, #56] @Load wt2 in r8 ldr r9, [sp, #60] @Load ofst1 in r9 add r6, r6, #1 @r6 = log_wd + 1 sxtb r7, r7 @sign-extend 16-bit wt1 to 32-bit ldr r4, [sp, #40] @Load src_strd2 in r4 ldr r5, [sp, #44] @Load dst_strd in r5 sxtb r9, r9 @sign-extend 8-bit ofst1 to 32-bit neg r10, r6 @r10 = -(log_wd + 1) ldr r11, [sp, #68] @Load ht in r11 ldr r12, [sp, #72] @Load wd in r12 vdup.16 q0, r10 @Q0 = -(log_wd + 1) (32-bit) add r9, r9, #1 @r9 = ofst1 + 1 ldr r10, [sp, #64] @Load ofst2 in r10 sxtb r8, r8 @sign-extend 16-bit wt2 to 32-bit cmp r12, #16 @check if wd is 16 vpush {d8-d15} sxtb r10, r10 @sign-extend 8-bit ofst2 to 32-bit add r9, r9, r10 @r9 = ofst1 + ofst2 + 1 vmov d2, r7, r8 @D2 = {wt1(32-bit), wt2(32-bit)} asr r9, r9, #1 @r9 = ofst = (ofst1 + ofst2 + 1) >> 1 vdup.8 d3, r9 @D3 = ofst (8-bit) beq loop_16 @branch if wd is 16 cmp r12, #8 @check if wd is 8 beq loop_8 @branch if wd is 8 loop_4: @each iteration processes four rows vld1.32 d4[0], [r0], r3 @load row 1 in source 1 vld1.32 d4[1], [r0], r3 @load row 2 in source 1 vld1.32 d6[0], [r1], r4 @load row 1 in source 2 vld1.32 d6[1], [r1], r4 @load row 2 in source 2 vmovl.u8 q2, d4 @converting rows 1,2 in source 1 to 16-bit vld1.32 d8[0], [r0], r3 @load row 3 in source 1 vld1.32 d8[1], [r0], r3 @load row 4 in source 1 vmovl.u8 q3, d6 @converting rows 1,2 in source 2 to 16-bit vld1.32 d10[0], [r1], r4 @load row 3 in source 2 vld1.32 d10[1], [r1], r4 @load row 4 in source 2 vmovl.u8 q4, d8 @converting rows 3,4 in source 1 to 16-bit vmovl.u8 q5, d10 @converting rows 3,4 in source 2 to 16-bit vmul.s16 q2, q2, d2[0] @weight 1 mult. for rows 1,2 vmla.s16 q2, q3, d2[2] @weight 2 mult. for rows 1,2 vmul.s16 q4, q4, d2[0] @weight 1 mult. for rows 3,4 vmla.s16 q4, q5, d2[2] @weight 2 mult. for rows 3,4 subs r11, r11, #4 @decrement ht by 4 vrshl.s16 q2, q2, q0 @rounds off the weighted samples from rows 1,2 vrshl.s16 q4, q4, q0 @rounds off the weighted samples from rows 3,4 vaddw.s8 q2, q2, d3 @adding offset for rows 1,2 vaddw.s8 q4, q4, d3 @adding offset for rows 3,4 vqmovun.s16 d4, q2 @saturating rows 1,2 to unsigned 8-bit vqmovun.s16 d8, q4 @saturating rows 3,4 to unsigned 8-bit vst1.32 d4[0], [r2], r5 @store row 1 in destination vst1.32 d4[1], [r2], r5 @store row 2 in destination vst1.32 d8[0], [r2], r5 @store row 3 in destination vst1.32 d8[1], [r2], r5 @store row 4 in destination bgt loop_4 @if greater than 0 repeat the loop again b end_loops loop_8: @each iteration processes four rows vld1.8 d4, [r0], r3 @load row 1 in source 1 vld1.8 d6, [r1], r4 @load row 1 in source 2 vld1.8 d8, [r0], r3 @load row 2 in source 1 vld1.8 d10, [r1], r4 @load row 2 in source 2 vmovl.u8 q2, d4 @converting row 1 in source 1 to 16-bit vld1.8 d12, [r0], r3 @load row 3 in source 1 vld1.8 d14, [r1], r4 @load row 3 in source 2 vmovl.u8 q3, d6 @converting row 1 in source 2 to 16-bit vld1.8 d16, [r0], r3 @load row 4 in source 1 vld1.8 d18, [r1], r4 @load row 4 in source 2 vmovl.u8 q4, d8 @converting row 2 in source 1 to 16-bit vmovl.u8 q5, d10 @converting row 2 in source 2 to 16-bit vmul.s16 q2, q2, d2[0] @weight 1 mult. for row 1 vmla.s16 q2, q3, d2[2] @weight 2 mult. for row 1 vmovl.u8 q6, d12 @converting row 3 in source 1 to 16-bit vmovl.u8 q7, d14 @converting row 3 in source 2 to 16-bit vmul.s16 q4, q4, d2[0] @weight 1 mult. for row 2 vmla.s16 q4, q5, d2[2] @weight 2 mult. for row 2 vmovl.u8 q8, d16 @converting row 4 in source 1 to 16-bit vmovl.u8 q9, d18 @converting row 4 in source 2 to 16-bit vmul.s16 q6, q6, d2[0] @weight 1 mult. for row 3 vmla.s16 q6, q7, d2[2] @weight 2 mult. for row 3 vmul.s16 q8, q8, d2[0] @weight 1 mult. for row 4 vmla.s16 q8, q9, d2[2] @weight 2 mult. for row 4 vrshl.s16 q2, q2, q0 @rounds off the weighted samples from row 1 vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 2 vrshl.s16 q6, q6, q0 @rounds off the weighted samples from row 3 vaddw.s8 q2, q2, d3 @adding offset for row 1 vrshl.s16 q8, q8, q0 @rounds off the weighted samples from row 4 vaddw.s8 q4, q4, d3 @adding offset for row 2 vaddw.s8 q6, q6, d3 @adding offset for row 3 vqmovun.s16 d4, q2 @saturating row 1 to unsigned 8-bit vaddw.s8 q8, q8, d3 @adding offset for row 4 vqmovun.s16 d8, q4 @saturating row 2 to unsigned 8-bit vqmovun.s16 d12, q6 @saturating row 3 to unsigned 8-bit vqmovun.s16 d16, q8 @saturating row 4 to unsigned 8-bit vst1.8 d4, [r2], r5 @store row 1 in destination vst1.8 d8, [r2], r5 @store row 2 in destination subs r11, r11, #4 @decrement ht by 4 vst1.8 d12, [r2], r5 @store row 3 in destination vst1.8 d16, [r2], r5 @store row 4 in destination bgt loop_8 @if greater than 0 repeat the loop again b end_loops loop_16: @each iteration processes two rows vld1.8 {q2}, [r0], r3 @load row 1 in source 1 vld1.8 {q3}, [r1], r4 @load row 1 in source 2 vld1.8 {q4}, [r0], r3 @load row 2 in source 1 vld1.8 {q5}, [r1], r4 @load row 2 in source 2 vmovl.u8 q10, d4 @converting row 1L in source 1 to 16-bit vld1.8 {q6}, [r0], r3 @load row 3 in source 1 vld1.8 {q7}, [r1], r4 @load row 3 in source 2 vmovl.u8 q11, d6 @converting row 1L in source 2 to 16-bit vld1.8 {q8}, [r0], r3 @load row 4 in source 1 vld1.8 {q9}, [r1], r4 @load row 4 in source 2 vmovl.u8 q2, d5 @converting row 1H in source 1 to 16-bit vmovl.u8 q3, d7 @converting row 1H in source 2 to 16-bit vmul.s16 q10, q10, d2[0] @weight 1 mult. for row 1L vmla.s16 q10, q11, d2[2] @weight 2 mult. for row 1L vmovl.u8 q12, d8 @converting row 2L in source 1 to 16-bit vmovl.u8 q13, d10 @converting row 2L in source 2 to 16-bit vmul.s16 q2, q2, d2[0] @weight 1 mult. for row 1H vmla.s16 q2, q3, d2[2] @weight 2 mult. for row 1H vmovl.u8 q4, d9 @converting row 2H in source 1 to 16-bit vmovl.u8 q5, d11 @converting row 2H in source 2 to 16-bit vmul.s16 q12, q12, d2[0] @weight 1 mult. for row 2L vmla.s16 q12, q13, d2[2] @weight 2 mult. for row 2L vmovl.u8 q14, d12 @converting row 3L in source 1 to 16-bit vmovl.u8 q15, d14 @converting row 3L in source 2 to 16-bit vmul.s16 q4, q4, d2[0] @weight 1 mult. for row 2H vmla.s16 q4, q5, d2[2] @weight 2 mult. for row 2H vmovl.u8 q6, d13 @converting row 3H in source 1 to 16-bit vmovl.u8 q7, d15 @converting row 3H in source 2 to 16-bit vmul.s16 q14, q14, d2[0] @weight 1 mult. for row 3L vmla.s16 q14, q15, d2[2] @weight 2 mult. for row 3L vmovl.u8 q11, d16 @converting row 4L in source 1 to 16-bit vmovl.u8 q3, d18 @converting row 4L in source 2 to 16-bit vmul.s16 q6, q6, d2[0] @weight 1 mult. for row 3H vmla.s16 q6, q7, d2[2] @weight 2 mult. for row 3H vmovl.u8 q8, d17 @converting row 4H in source 1 to 16-bit vmovl.u8 q9, d19 @converting row 4H in source 2 to 16-bit vmul.s16 q11, q11, d2[0] @weight 1 mult. for row 4L vmla.s16 q11, q3, d2[2] @weight 2 mult. for row 4L vrshl.s16 q10, q10, q0 @rounds off the weighted samples from row 1L vmul.s16 q8, q8, d2[0] @weight 1 mult. for row 4H vmla.s16 q8, q9, d2[2] @weight 2 mult. for row 4H vrshl.s16 q2, q2, q0 @rounds off the weighted samples from row 1H vrshl.s16 q12, q12, q0 @rounds off the weighted samples from row 2L vaddw.s8 q10, q10, d3 @adding offset for row 1L vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 2H vaddw.s8 q2, q2, d3 @adding offset for row 1H vrshl.s16 q14, q14, q0 @rounds off the weighted samples from row 3L vaddw.s8 q12, q12, d3 @adding offset for row 2L vrshl.s16 q6, q6, q0 @rounds off the weighted samples from row 3H vaddw.s8 q4, q4, d3 @adding offset for row 2H vrshl.s16 q11, q11, q0 @rounds off the weighted samples from row 4L vaddw.s8 q14, q14, d3 @adding offset for row 3L vrshl.s16 q8, q8, q0 @rounds off the weighted samples from row 4H vaddw.s8 q6, q6, d3 @adding offset for row 3H vqmovun.s16 d26, q10 @saturating row 1L to unsigned 8-bit vaddw.s8 q11, q11, d3 @adding offset for row 4L vqmovun.s16 d27, q2 @saturating row 1H to unsigned 8-bit vaddw.s8 q8, q8, d3 @adding offset for row 4H vqmovun.s16 d10, q12 @saturating row 2L to unsigned 8-bit vqmovun.s16 d11, q4 @saturating row 2H to unsigned 8-bit vqmovun.s16 d30, q14 @saturating row 3L to unsigned 8-bit vqmovun.s16 d31, q6 @saturating row 3H to unsigned 8-bit vst1.8 {q13}, [r2], r5 @store row 1 in destination vqmovun.s16 d14, q11 @saturating row 4L to unsigned 8-bit vqmovun.s16 d15, q8 @saturating row 4H to unsigned 8-bit vst1.8 {q5}, [r2], r5 @store row 2 in destination subs r11, r11, #4 @decrement ht by 4 vst1.8 {q15}, [r2], r5 @store row 3 in destination vst1.8 {q7}, [r2], r5 @store row 4 in destination bgt loop_16 @if greater than 0 repeat the loop again end_loops: vpop {d8-d15} ldmfd sp!, {r4-r12, r15} @Reload the registers from sp @******************************************************************************* @* @function @* ih264_weighted_bi_pred_chroma_a9q() @* @* @brief @* This routine performs the default weighted prediction as described in sec @* 8.4.2.3.2 titled "Weighted sample prediction process" for chroma. @* @* @par Description: @* This function gets two ht x wd blocks, calculates the weighted samples, @* rounds off, adds offset and stores it in the destination block for U and V. @* @* @param[in] pu1_src1 @* UWORD8 Pointer to the buffer containing the input block 1. @* @* @param[in] pu1_src2 @* UWORD8 Pointer to the buffer containing the input block 2. @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination where the output block is stored. @* @* @param[in] src_strd1 @* Stride of the input buffer 1 @* @* @param[in] src_strd2 @* Stride of the input buffer 2 @* @* @param[in] dst_strd @* Stride of the destination buffer @* @* @param[in] log_wd @* number of bits to be rounded off @* @* @param[in] wt1 @* weights for the weighted prediction in U and V @* @* @param[in] wt2 @* weights for the weighted prediction in U and V @* @* @param[in] ofst1 @* offset 1 used after rounding off for U an dV @* @* @param[in] ofst2 @* offset 2 used after rounding off for U and V @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* None @* @* @remarks @* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). @* @******************************************************************************* @* @void ih264_weighted_bi_pred_chroma_a9q(UWORD8 *pu1_src1, @ UWORD8 *pu1_src2, @ UWORD8 *pu1_dst, @ WORD32 src_strd1, @ WORD32 src_strd2, @ WORD32 dst_strd, @ WORD32 log_wd, @ WORD32 wt1, @ WORD32 wt2, @ WORD32 ofst1, @ WORD32 ofst2, @ WORD32 ht, @ WORD32 wd) @ @**************Variables Vs Registers***************************************** @ r0 => pu1_src1 @ r1 => pu1_src2 @ r2 => pu1_dst @ r3 => src_strd1 @ [sp] => src_strd2 (r4) @ [sp+4] => dst_strd (r5) @ [sp+8] => log_wd (r6) @ [sp+12] => wt1 (r7) @ [sp+16] => wt2 (r8) @ [sp+20] => ofst1 (r9) @ [sp+24] => ofst2 (r10) @ [sp+28] => ht (r11) @ [sp+32] => wd (r12) @ .global ih264_weighted_bi_pred_chroma_a9q ih264_weighted_bi_pred_chroma_a9q: stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments ldr r6, [sp, #48] @Load log_wd in r6 ldr r7, [sp, #52] @Load wt1 in r7 ldr r8, [sp, #56] @Load wt2 in r8 add r6, r6, #1 @r6 = log_wd + 1 ldr r9, [sp, #60] @Load ofst1 in r9 ldr r10, [sp, #64] @Load ofst2 in r10 neg r12, r6 @r12 = -(log_wd + 1) ldr r4, [sp, #40] @Load src_strd2 in r4 ldr r5, [sp, #44] @Load dst_strd in r5 vdup.16 q0, r12 @Q0 = -(log_wd + 1) (16-bit) ldr r11, [sp, #68] @Load ht in r11 vdup.32 q1, r7 @Q1 = (wt1_u, wt1_v) (32-bit) ldr r12, [sp, #72] @Load wd in r12 vdup.32 q2, r8 @Q2 = (wt2_u, wt2_v) (32-bit) asr r7, r9, #8 @r7 = ofst1_v asr r8, r10, #8 @r8 = ofst2_v vpush {d8-d15} sxtb r9, r9 @sign-extend 8-bit ofst1_u to 32-bit sxtb r10, r10 @sign-extend 8-bit ofst2_u to 32-bit sxtb r7, r7 @sign-extend 8-bit ofst1_v to 32-bit sxtb r8, r8 @sign-extend 8-bit ofst2_v to 32-bit add r9, r9, #1 @r9 = ofst1_u + 1 add r7, r7, #1 @r7 = ofst1_v + 1 add r9, r9, r10 @r9 = ofst1_u + ofst2_u + 1 add r7, r7, r8 @r7 = ofst1_v + ofst2_v + 1 asr r9, r9, #1 @r9 = ofst_u = (ofst1_u + ofst2_u + 1) >> 1 asr r7, r7, #1 @r7 = ofst_v = (ofst1_v + ofst2_v + 1) >> 1 cmp r12, #8 @check if wd is 8 pkhbt r9, r9, r7, lsl #16 @r9 = {ofst_u(16-bit), ofst_v(16-bit)} vdup.32 q3, r9 @Q3 = {ofst_u(16-bit), ofst_v(16-bit)} beq loop_8_uv @branch if wd is 8 cmp r12, #4 @check if wd is 4 beq loop_4_uv @branch if wd is 4 loop_2_uv: @each iteration processes two rows vld1.32 d8[0], [r0], r3 @load row 1 in source 1 vld1.32 d8[1], [r0], r3 @load row 2 in source 1 vld1.32 d10[0], [r1], r4 @load row 1 in source 2 vld1.32 d10[1], [r1], r4 @load row 2 in source 2 vmovl.u8 q4, d8 @converting rows 1,2 in source 1 to 16-bit vmovl.u8 q5, d10 @converting rows 1,2 in source 2 to 16-bit vmul.s16 q4, q4, q1 @weight 1 mult. for rows 1,2 vmla.s16 q4, q5, q2 @weight 2 mult. for rows 1,2 vrshl.s16 q4, q4, q0 @rounds off the weighted samples from rows 1,2 vadd.s16 q4, q4, q3 @adding offset for rows 1,2 vqmovun.s16 d8, q4 @saturating rows 1,2 to unsigned 8-bit vst1.32 d8[0], [r2], r5 @store row 1 in destination vst1.32 d8[1], [r2], r5 @store row 2 in destination subs r11, r11, #2 @decrement ht by 2 bgt loop_2_uv @if greater than 0 repeat the loop again b end_loops_uv loop_4_uv: @each iteration processes two rows vld1.8 d8, [r0], r3 @load row 1 in source 1 vld1.8 d10, [r1], r4 @load row 1 in source 2 vmovl.u8 q4, d8 @converting row 1 in source 1 to 16-bit vld1.8 d12, [r0], r3 @load row 2 in source 1 vmovl.u8 q5, d10 @converting row 1 in source 2 to 16-bit vld1.8 d14, [r1], r4 @load row 2 in source 2 vmovl.u8 q6, d12 @converting row 2 in source 1 to 16-bit vmul.s16 q4, q4, q1 @weight 1 mult. for row 1 vmla.s16 q4, q5, q2 @weight 2 mult. for row 1 vmovl.u8 q7, d14 @converting row 2 in source 2 to 16-bit vmul.s16 q6, q6, q1 @weight 1 mult. for row 2 vmla.s16 q6, q7, q2 @weight 2 mult. for row 2 subs r11, r11, #2 @decrement ht by 2 vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 1 vrshl.s16 q6, q6, q0 @rounds off the weighted samples from row 2 vadd.s16 q4, q4, q3 @adding offset for row 1 vadd.s16 q6, q6, q3 @adding offset for row 2 vqmovun.s16 d8, q4 @saturating row 1 to unsigned 8-bit vqmovun.s16 d12, q6 @saturating row 2 to unsigned 8-bit vst1.8 d8, [r2], r5 @store row 1 in destination vst1.8 d12, [r2], r5 @store row 2 in destination bgt loop_4_uv @if greater than 0 repeat the loop again b end_loops_uv loop_8_uv: @each iteration processes two rows vld1.8 {q4}, [r0], r3 @load row 1 in source 1 vld1.8 {q5}, [r1], r4 @load row 1 in source 2 vld1.8 {q6}, [r0], r3 @load row 2 in source 1 vld1.8 {q7}, [r1], r4 @load row 2 in source 2 vmovl.u8 q12, d8 @converting row 1L in source 1 to 16-bit vld1.8 {q8}, [r0], r3 @load row 3 in source 1 vld1.8 {q9}, [r1], r4 @load row 3 in source 2 vmovl.u8 q13, d10 @converting row 1L in source 2 to 16-bit vld1.8 {q10}, [r0], r3 @load row 4 in source 1 vld1.8 {q11}, [r1], r4 @load row 4 in source 2 vmovl.u8 q4, d9 @converting row 1H in source 1 to 16-bit vmovl.u8 q5, d11 @converting row 1H in source 2 to 16-bit vmul.s16 q12, q12, q1 @weight 1 mult. for row 1L vmla.s16 q12, q13, q2 @weight 2 mult. for row 1L vmovl.u8 q14, d12 @converting row 2L in source 1 to 16-bit vmovl.u8 q15, d14 @converting row 2L in source 2 to 16-bit vmul.s16 q4, q4, q1 @weight 1 mult. for row 1H vmla.s16 q4, q5, q2 @weight 2 mult. for row 1H vmovl.u8 q6, d13 @converting row 2H in source 1 to 16-bit vmovl.u8 q7, d15 @converting row 2H in source 2 to 16-bit vmul.s16 q14, q14, q1 @weight 1 mult. for row 2L vmla.s16 q14, q15, q2 @weight 2 mult. for row 2L vmovl.u8 q13, d16 @converting row 3L in source 1 to 16-bit vmovl.u8 q5, d18 @converting row 3L in source 2 to 16-bit vmul.s16 q6, q6, q1 @weight 1 mult. for row 2H vmla.s16 q6, q7, q2 @weight 2 mult. for row 2H vmovl.u8 q8, d17 @converting row 3H in source 1 to 16-bit vmovl.u8 q9, d19 @converting row 3H in source 2 to 16-bit vmul.s16 q13, q13, q1 @weight 1 mult. for row 3L vmla.s16 q13, q5, q2 @weight 2 mult. for row 3L vmovl.u8 q15, d20 @converting row 4L in source 1 to 16-bit vmovl.u8 q7, d22 @converting row 4L in source 2 to 16-bit vmul.s16 q8, q8, q1 @weight 1 mult. for row 3H vmla.s16 q8, q9, q2 @weight 2 mult. for row 3H vmovl.u8 q10, d21 @converting row 4H in source 1 to 16-bit vmovl.u8 q11, d23 @converting row 4H in source 2 to 16-bit vmul.s16 q15, q15, q1 @weight 1 mult. for row 4L vmla.s16 q15, q7, q2 @weight 2 mult. for row 4L vrshl.s16 q12, q12, q0 @rounds off the weighted samples from row 1L vmul.s16 q10, q10, q1 @weight 1 mult. for row 4H vmla.s16 q10, q11, q2 @weight 2 mult. for row 4H vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 1H vrshl.s16 q14, q14, q0 @rounds off the weighted samples from row 2L vadd.s16 q12, q12, q3 @adding offset for row 1L vrshl.s16 q6, q6, q0 @rounds off the weighted samples from row 2H vadd.s16 q4, q4, q3 @adding offset for row 1H vrshl.s16 q13, q13, q0 @rounds off the weighted samples from row 3L vadd.s16 q14, q14, q3 @adding offset for row 2L vrshl.s16 q8, q8, q0 @rounds off the weighted samples from row 3H vadd.s16 q6, q6, q3 @adding offset for row 2H vrshl.s16 q15, q15, q0 @rounds off the weighted samples from row 4L vadd.s16 q13, q13, q3 @adding offset for row 3L vrshl.s16 q10, q10, q0 @rounds off the weighted samples from row 4H vadd.s16 q8, q8, q3 @adding offset for row 3H vqmovun.s16 d10, q12 @saturating row 1L to unsigned 8-bit vadd.s16 q15, q15, q3 @adding offset for row 4L vqmovun.s16 d11, q4 @saturating row 1H to unsigned 8-bit vadd.s16 q10, q10, q3 @adding offset for row 4H vqmovun.s16 d18, q14 @saturating row 2L to unsigned 8-bit vqmovun.s16 d19, q6 @saturating row 2H to unsigned 8-bit vqmovun.s16 d14, q13 @saturating row 3L to unsigned 8-bit vqmovun.s16 d15, q8 @saturating row 3H to unsigned 8-bit vst1.8 {q5}, [r2], r5 @store row 1 in destination vqmovun.s16 d22, q15 @saturating row 4L to unsigned 8-bit vqmovun.s16 d23, q10 @saturating row 4H to unsigned 8-bit vst1.8 {q9}, [r2], r5 @store row 2 in destination subs r11, r11, #4 @decrement ht by 4 vst1.8 {q7}, [r2], r5 @store row 3 in destination vst1.8 {q11}, [r2], r5 @store row 4 in destination bgt loop_8_uv @if greater than 0 repeat the loop again end_loops_uv: vpop {d8-d15} ldmfd sp!, {r4-r12, r15} @Reload the registers from sp ================================================ FILE: dependencies/ih264d/common/arm/ih264_weighted_pred_a9q.s ================================================ @/****************************************************************************** @ * @ * Copyright (C) 2015 The Android Open Source Project @ * @ * Licensed under the Apache License, Version 2.0 (the "License"); @ * you may not use this file except in compliance with the License. @ * You may obtain a copy of the License at: @ * @ * http://www.apache.org/licenses/LICENSE-2.0 @ * @ * Unless required by applicable law or agreed to in writing, software @ * distributed under the License is distributed on an "AS IS" BASIS, @ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @ * See the License for the specific language governing permissions and @ * limitations under the License. @ * @ ***************************************************************************** @ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore @*/ @** @****************************************************************************** @* @file @* ih264_weighted_pred_a9q.s @* @* @brief @* Contains function definitions for weighted prediction. @* @* @author @* Kaushik Senthoor R @* @* @par List of Functions: @* @* - ih264_weighted_pred_luma_a9q() @* - ih264_weighted_pred_chroma_a9q() @* @* @remarks @* None @* @******************************************************************************* @* @******************************************************************************* @* @function @* ih264_weighted_pred_luma_a9q() @* @* @brief @* This routine performs the default weighted prediction as described in sec @* 8.4.2.3.2 titled "Weighted sample prediction process" for luma. @* @* @par Description: @* This function gets a ht x wd block, calculates the weighted sample, rounds @* off, adds offset and stores it in the destination block. @* @* @param[in] pu1_src: @* UWORD8 Pointer to the buffer containing the input block. @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination where the output block is stored. @* @* @param[in] src_strd @* Stride of the input buffer @* @* @param[in] dst_strd @* Stride of the destination buffer @* @* @param[in] log_wd @* number of bits to be rounded off @* @* @param[in] wt @* weight for the weighted prediction @* @* @param[in] ofst @* offset used after rounding off @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* None @* @* @remarks @* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). @* @******************************************************************************* @* @void ih264_weighted_pred_luma_a9q(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 log_wd, @ WORD32 wt, @ WORD32 ofst, @ WORD32 ht, @ WORD32 wd) @ @**************Variables Vs Registers***************************************** @ r0 => pu1_src @ r1 => pu1_dst @ r2 => src_strd @ r3 => dst_strd @ [sp] => log_wd (r4) @ [sp+4] => wt (r5) @ [sp+8] => ofst (r6) @ [sp+12] => ht (r7) @ [sp+16] => wd (r8) @ .text .p2align 2 .global ih264_weighted_pred_luma_a9q ih264_weighted_pred_luma_a9q: stmfd sp!, {r4-r9, r14} @stack stores the values of the arguments ldr r5, [sp, #32] @Load wt ldr r4, [sp, #28] @Load log_wd in r4 ldr r6, [sp, #36] @Load ofst ldr r7, [sp, #40] @Load ht ldr r8, [sp, #44] @Load wd vpush {d8-d15} vdup.16 d2, r5 @D2 = wt (16-bit) neg r9, r4 @r9 = -log_wd vdup.8 d3, r6 @D3 = ofst (8-bit) cmp r8, #16 @check if wd is 16 vdup.16 q0, r9 @Q0 = -log_wd (16-bit) beq loop_16 @branch if wd is 16 cmp r8, #8 @check if wd is 8 beq loop_8 @branch if wd is 8 loop_4: @each iteration processes four rows vld1.32 d4[0], [r0], r2 @load row 1 in source vld1.32 d4[1], [r0], r2 @load row 2 in source vld1.32 d6[0], [r0], r2 @load row 3 in source vld1.32 d6[1], [r0], r2 @load row 4 in source vmovl.u8 q2, d4 @converting rows 1,2 to 16-bit vmovl.u8 q3, d6 @converting rows 3,4 to 16-bit vmul.s16 q2, q2, d2[0] @weight mult. for rows 1,2 vmul.s16 q3, q3, d2[0] @weight mult. for rows 3,4 subs r7, r7, #4 @decrement ht by 4 vrshl.s16 q2, q2, q0 @rounds off the weighted samples from rows 1,2 vrshl.s16 q3, q3, q0 @rounds off the weighted samples from rows 3,4 vaddw.s8 q2, q2, d3 @adding offset for rows 1,2 vaddw.s8 q3, q3, d3 @adding offset for rows 3,4 vqmovun.s16 d4, q2 @saturating rows 1,2 to unsigned 8-bit vqmovun.s16 d6, q3 @saturating rows 3,4 to unsigned 8-bit vst1.32 d4[0], [r1], r3 @store row 1 in destination vst1.32 d4[1], [r1], r3 @store row 2 in destination vst1.32 d6[0], [r1], r3 @store row 3 in destination vst1.32 d6[1], [r1], r3 @store row 4 in destination bgt loop_4 @if greater than 0 repeat the loop again b end_loops loop_8: @each iteration processes four rows vld1.8 d4, [r0], r2 @load row 1 in source vld1.8 d6, [r0], r2 @load row 2 in source vld1.8 d8, [r0], r2 @load row 3 in source vmovl.u8 q2, d4 @converting row 1 to 16-bit vld1.8 d10, [r0], r2 @load row 4 in source vmovl.u8 q3, d6 @converting row 2 to 16-bit vmovl.u8 q4, d8 @converting row 3 to 16-bit vmul.s16 q2, q2, d2[0] @weight mult. for row 1 vmovl.u8 q5, d10 @converting row 4 to 16-bit vmul.s16 q3, q3, d2[0] @weight mult. for row 2 vmul.s16 q4, q4, d2[0] @weight mult. for row 3 vmul.s16 q5, q5, d2[0] @weight mult. for row 4 vrshl.s16 q2, q2, q0 @rounds off the weighted samples from row 1 vrshl.s16 q3, q3, q0 @rounds off the weighted samples from row 2 vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 3 vaddw.s8 q2, q2, d3 @adding offset for row 1 vrshl.s16 q5, q5, q0 @rounds off the weighted samples from row 4 vaddw.s8 q3, q3, d3 @adding offset for row 2 vaddw.s8 q4, q4, d3 @adding offset for row 3 vqmovun.s16 d4, q2 @saturating row 1 to unsigned 8-bit vaddw.s8 q5, q5, d3 @adding offset for row 4 vqmovun.s16 d6, q3 @saturating row 2 to unsigned 8-bit vqmovun.s16 d8, q4 @saturating row 3 to unsigned 8-bit vqmovun.s16 d10, q5 @saturating row 4 to unsigned 8-bit vst1.8 d4, [r1], r3 @store row 1 in destination vst1.8 d6, [r1], r3 @store row 2 in destination subs r7, r7, #4 @decrement ht by 4 vst1.8 d8, [r1], r3 @store row 3 in destination vst1.8 d10, [r1], r3 @store row 4 in destination bgt loop_8 @if greater than 0 repeat the loop again b end_loops loop_16: @each iteration processes two rows vld1.8 {q2}, [r0], r2 @load row 1 in source vld1.8 {q3}, [r0], r2 @load row 2 in source vmovl.u8 q6, d4 @converting row 1L to 16-bit vld1.8 {q4}, [r0], r2 @load row 3 in source vmovl.u8 q7, d5 @converting row 1H to 16-bit vld1.8 {q5}, [r0], r2 @load row 4 in source vmovl.u8 q8, d6 @converting row 2L to 16-bit vmul.s16 q6, q6, d2[0] @weight mult. for row 1L vmovl.u8 q9, d7 @converting row 2H to 16-bit vmul.s16 q7, q7, d2[0] @weight mult. for row 1H vmovl.u8 q10, d8 @converting row 3L to 16-bit vmul.s16 q8, q8, d2[0] @weight mult. for row 2L vmovl.u8 q11, d9 @converting row 3H to 16-bit vmul.s16 q9, q9, d2[0] @weight mult. for row 2H vmovl.u8 q12, d10 @converting row 4L to 16-bit vmul.s16 q10, q10, d2[0] @weight mult. for row 3L vmovl.u8 q13, d11 @converting row 4H to 16-bit vmul.s16 q11, q11, d2[0] @weight mult. for row 3H vmul.s16 q12, q12, d2[0] @weight mult. for row 4L vrshl.s16 q6, q6, q0 @rounds off the weighted samples from row 1L vmul.s16 q13, q13, d2[0] @weight mult. for row 4H vrshl.s16 q7, q7, q0 @rounds off the weighted samples from row 1H vrshl.s16 q8, q8, q0 @rounds off the weighted samples from row 2L vaddw.s8 q6, q6, d3 @adding offset for row 1L vrshl.s16 q9, q9, q0 @rounds off the weighted samples from row 2H vaddw.s8 q7, q7, d3 @adding offset for row 1H vqmovun.s16 d4, q6 @saturating row 1L to unsigned 8-bit vrshl.s16 q10, q10, q0 @rounds off the weighted samples from row 3L vaddw.s8 q8, q8, d3 @adding offset for row 2L vqmovun.s16 d5, q7 @saturating row 1H to unsigned 8-bit vrshl.s16 q11, q11, q0 @rounds off the weighted samples from row 3H vaddw.s8 q9, q9, d3 @adding offset for row 2H vqmovun.s16 d6, q8 @saturating row 2L to unsigned 8-bit vrshl.s16 q12, q12, q0 @rounds off the weighted samples from row 4L vaddw.s8 q10, q10, d3 @adding offset for row 3L vqmovun.s16 d7, q9 @saturating row 2H to unsigned 8-bit vrshl.s16 q13, q13, q0 @rounds off the weighted samples from row 4H vaddw.s8 q11, q11, d3 @adding offset for row 3H vqmovun.s16 d8, q10 @saturating row 3L to unsigned 8-bit vaddw.s8 q12, q12, d3 @adding offset for row 4L vqmovun.s16 d9, q11 @saturating row 3H to unsigned 8-bit vaddw.s8 q13, q13, d3 @adding offset for row 4H vqmovun.s16 d10, q12 @saturating row 4L to unsigned 8-bit vst1.8 {q2}, [r1], r3 @store row 1 in destination vqmovun.s16 d11, q13 @saturating row 4H to unsigned 8-bit vst1.8 {q3}, [r1], r3 @store row 2 in destination subs r7, r7, #4 @decrement ht by 4 vst1.8 {q4}, [r1], r3 @store row 3 in destination vst1.8 {q5}, [r1], r3 @store row 4 in destination bgt loop_16 @if greater than 0 repeat the loop again end_loops: vpop {d8-d15} ldmfd sp!, {r4-r9, r15} @Reload the registers from sp @******************************************************************************* @* @function @* ih264_weighted_pred_chroma_a9q() @* @* @brief @* This routine performs the default weighted prediction as described in sec @* 8.4.2.3.2 titled "Weighted sample prediction process" for chroma. @* @* @par Description: @* This function gets a ht x wd block, calculates the weighted sample, rounds @* off, adds offset and stores it in the destination block for U and V. @* @* @param[in] pu1_src: @* UWORD8 Pointer to the buffer containing the input block. @* @* @param[out] pu1_dst @* UWORD8 pointer to the destination where the output block is stored. @* @* @param[in] src_strd @* Stride of the input buffer @* @* @param[in] dst_strd @* Stride of the destination buffer @* @* @param[in] log_wd @* number of bits to be rounded off @* @* @param[in] wt @* weights for the weighted prediction for U and V @* @* @param[in] ofst @* offsets used after rounding off for U and V @* @* @param[in] ht @* integer height of the array @* @* @param[in] wd @* integer width of the array @* @* @returns @* None @* @* @remarks @* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). @* @******************************************************************************* @* @void ih264_weighted_pred_chroma_a9q(UWORD8 *pu1_src, @ UWORD8 *pu1_dst, @ WORD32 src_strd, @ WORD32 dst_strd, @ WORD32 log_wd, @ WORD32 wt, @ WORD32 ofst, @ WORD32 ht, @ WORD32 wd) @ @**************Variables Vs Registers***************************************** @ r0 => pu1_src @ r1 => pu1_dst @ r2 => src_strd @ r3 => dst_strd @ [sp] => log_wd (r4) @ [sp+4] => wt (r5) @ [sp+8] => ofst (r6) @ [sp+12] => ht (r7) @ [sp+16] => wd (r8) @ .global ih264_weighted_pred_chroma_a9q ih264_weighted_pred_chroma_a9q: stmfd sp!, {r4-r9, r14} @stack stores the values of the arguments ldr r4, [sp, #28] @Load log_wd in r4 ldr r5, [sp, #32] @Load wt = {wt_u (16-bit), wt_v (16-bit)} ldr r6, [sp, #36] @Load ofst = {ofst_u (8-bit), ofst_v (8-bit)} ldr r8, [sp, #44] @Load wd neg r9, r4 @r9 = -log_wd vdup.32 q1, r5 @Q1 = {wt_u (16-bit), wt_v (16-bit)} ldr r7, [sp, #40] @Load ht vpush {d8-d15} vdup.16 d4, r6 @D4 = {ofst_u (8-bit), ofst_v (8-bit)} cmp r8, #8 @check if wd is 8 vdup.16 q0, r9 @Q0 = -log_wd (16-bit) beq loop_8_uv @branch if wd is 8 cmp r8, #4 @check if ws is 4 beq loop_4_uv @branch if wd is 4 loop_2_uv: @each iteration processes two rows vld1.32 d6[0], [r0], r2 @load row 1 in source vld1.32 d6[1], [r0], r2 @load row 2 in source vmovl.u8 q3, d6 @converting rows 1,2 to 16-bit vmul.s16 q3, q3, q1 @weight mult. for rows 1,2 vrshl.s16 q3, q3, q0 @rounds off the weighted samples from rows 1,2 vaddw.s8 q3, q3, d4 @adding offset for rows 1,2 vqmovun.s16 d6, q3 @saturating rows 1,2 to unsigned 8-bit subs r7, r7, #2 @decrement ht by 2 vst1.32 d6[0], [r1], r3 @store row 1 in destination vst1.32 d6[1], [r1], r3 @store row 2 in destination bgt loop_2_uv @if greater than 0 repeat the loop again b end_loops_uv loop_4_uv: @each iteration processes two rows vld1.8 d6, [r0], r2 @load row 1 in source vld1.8 d8, [r0], r2 @load row 2 in source vmovl.u8 q3, d6 @converting row 1 to 16-bit vmovl.u8 q4, d8 @converting row 2 to 16-bit vmul.s16 q3, q3, q1 @weight mult. for row 1 vmul.s16 q4, q4, q1 @weight mult. for row 2 subs r7, r7, #2 @decrement ht by 2 vrshl.s16 q3, q3, q0 @rounds off the weighted samples from row 1 vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 2 vaddw.s8 q3, q3, d4 @adding offset for row 1 vaddw.s8 q4, q4, d4 @adding offset for row 2 vqmovun.s16 d6, q3 @saturating row 1 to unsigned 8-bit vqmovun.s16 d8, q4 @saturating row 2 to unsigned 8-bit vst1.8 d6, [r1], r3 @store row 1 in destination vst1.8 d8, [r1], r3 @store row 2 in destination bgt loop_4_uv @if greater than 0 repeat the loop again b end_loops_uv loop_8_uv: @each iteration processes two rows vld1.8 {q3}, [r0], r2 @load row 1 in source vld1.8 {q4}, [r0], r2 @load row 2 in source vmovl.u8 q7, d6 @converting row 1L to 16-bit vld1.8 {q5}, [r0], r2 @load row 3 in source vmovl.u8 q8, d7 @converting row 1H to 16-bit vld1.8 {q6}, [r0], r2 @load row 4 in source vmul.s16 q7, q7, q1 @weight mult. for row 1L vmovl.u8 q9, d8 @converting row 2L to 16-bit vmul.s16 q8, q8, q1 @weight mult. for row 1H vmovl.u8 q10, d9 @converting row 2H to 16-bit vmul.s16 q9, q9, q1 @weight mult. for row 2L vmovl.u8 q11, d10 @converting row 3L to 16-bit vmul.s16 q10, q10, q1 @weight mult. for row 2H vmovl.u8 q12, d11 @converting row 3H to 16-bit vmul.s16 q11, q11, q1 @weight mult. for row 3L vmovl.u8 q13, d12 @converting row 4L to 16-bit vmul.s16 q12, q12, q1 @weight mult. for row 3H vmovl.u8 q14, d13 @converting row 4H to 16-bit vmul.s16 q13, q13, q1 @weight mult. for row 4L vrshl.s16 q7, q7, q0 @rounds off the weighted samples from row 1L vmul.s16 q14, q14, q1 @weight mult. for row 4H vrshl.s16 q8, q8, q0 @rounds off the weighted samples from row 1H vrshl.s16 q9, q9, q0 @rounds off the weighted samples from row 2L vaddw.s8 q7, q7, d4 @adding offset for row 1L vrshl.s16 q10, q10, q0 @rounds off the weighted samples from row 2H vaddw.s8 q8, q8, d4 @adding offset for row 1H vqmovun.s16 d6, q7 @saturating row 1L to unsigned 8-bit vrshl.s16 q11, q11, q0 @rounds off the weighted samples from row 3L vaddw.s8 q9, q9, d4 @adding offset for row 2L vqmovun.s16 d7, q8 @saturating row 1H to unsigned 8-bit vrshl.s16 q12, q12, q0 @rounds off the weighted samples from row 3H vaddw.s8 q10, q10, d4 @adding offset for row 2H vqmovun.s16 d8, q9 @saturating row 2L to unsigned 8-bit vrshl.s16 q13, q13, q0 @rounds off the weighted samples from row 4L vaddw.s8 q11, q11, d4 @adding offset for row 3L vqmovun.s16 d9, q10 @saturating row 2H to unsigned 8-bit vrshl.s16 q14, q14, q0 @rounds off the weighted samples from row 4H vaddw.s8 q12, q12, d4 @adding offset for row 3H vqmovun.s16 d10, q11 @saturating row 3L to unsigned 8-bit vaddw.s8 q13, q13, d4 @adding offset for row 4L vqmovun.s16 d11, q12 @saturating row 3H to unsigned 8-bit vaddw.s8 q14, q14, d4 @adding offset for row 4H vqmovun.s16 d12, q13 @saturating row 4L to unsigned 8-bit vst1.8 {q3}, [r1], r3 @store row 1 in destination vqmovun.s16 d13, q14 @saturating row 4H to unsigned 8-bit vst1.8 {q4}, [r1], r3 @store row 2 in destination subs r7, r7, #4 @decrement ht by 4 vst1.8 {q5}, [r1], r3 @store row 3 in destination vst1.8 {q6}, [r1], r3 @store row 4 in destination bgt loop_8_uv @if greater than 0 repeat the loop again end_loops_uv: vpop {d8-d15} ldmfd sp!, {r4-r9, r15} @Reload the registers from sp ================================================ FILE: dependencies/ih264d/common/armv8/ih264_deblk_chroma_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///*****************************************************************************/ ///* */ ///* File Name : ih264_deblk_chroma_av8.s */ ///* */ ///* Description : Contains function definitions for deblocking luma */ ///* edge. Functions are coded in NEON assembly and can */ ///* be compiled using ARM RVDS. */ ///* */ ///* List of Functions : ih264_deblk_chroma_vert_bs4_av8() */ ///* ih264_deblk_chroma_vert_bslt4_av8() */ ///* ih264_deblk_chroma_horz_bs4_av8() */ ///* ih264_deblk_chroma_horz_bslt4_av8() */ ///* Issues / Problems : None */ ///* */ ///* Revision History : */ ///* */ ///* DD MM YYYY Author(s) Changes (Describe the changes made) */ ///* 28 11 2013 Ittiam Draft */ ///*****************************************************************************/ .text .p2align 2 .include "ih264_neon_macros.s" ///** //******************************************************************************* //* //* @brief //* Performs filtering of a chroma block horizontal edge when the //* boundary strength is set to 4 in high profile //* //* @par Description: //* This operation is described in Sec. 8.7.2.4 under the title //* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. //* //* @param[in] x0 - pu1_src //* Pointer to the src sample q0 //* //* @param[in] w1 - src_strd //* Source stride //* //* @param[in] w2 - alpha_cb //* Alpha Value for the boundary in U //* //* @param[in] w3 - beta_cb //* Beta Value for the boundary in U //* //* @param[in] w4 - alpha_cr //* Alpha Value for the boundary in V //* //* @param[in] w5 - beta_cr //* Beta Value for the boundary in V //* //* @returns //* None //* //* @remarks //* None //* //******************************************************************************* //*/ .global ih264_deblk_chroma_horz_bs4_av8 ih264_deblk_chroma_horz_bs4_av8: // STMFD sp!,{x4-x6,x14} // push_v_regs stp x19, x20, [sp, #-16]! sxtw x1, w1 mov x6, x5 mov x5, x4 sub x0, x0, x1, lsl #1 //x0 = uc_edgePixel pointing to p1 of chroma ld2 {v6.8b, v7.8b}, [x0], x1 //D6 = p1u , D7 = p1v mov x4, x0 //Keeping a backup of the pointer p0 of chroma ld2 {v4.8b, v5.8b}, [x0], x1 //D4 = p0u , D5 = p0v dup v20.8b, w2 //D20 contains alpha_cb dup v21.8b, w5 //D21 contains alpha_cr mov v20.d[1], v21.d[0] ld2 {v0.8b, v1.8b}, [x0], x1 //D0 = q0u , D1 = q0v uaddl v8.8h, v6.8b, v0.8b // uaddl v10.8h, v7.8b, v1.8b //Q4,Q5 = q0 + p1 movi v31.8b, #2 // ld2 {v2.8b, v3.8b}, [x0] //D2 = q1u , D3 = q1v mov v0.d[1], v1.d[0] mov v2.d[1], v3.d[0] mov v4.d[1], v5.d[0] mov v6.d[1], v7.d[0] uabd v26.16b, v6.16b , v4.16b //Q13 = ABS(p1 - p0) umlal v8.8h, v2.8b, v31.8b // umlal v10.8h, v3.8b, v31.8b //Q5,Q4 = (X2(q1U) + q0U + p1U) uabd v22.16b, v4.16b , v0.16b //Q11 = ABS(p0 - q0) uabd v24.16b, v2.16b , v0.16b //Q12 = ABS(q1 - q0) uaddl v14.8h, v4.8b, v2.8b // uaddl v28.8h, v5.8b, v3.8b //Q14,Q7 = P0 + Q1 dup v16.8b, w3 //D16 contains beta_cb dup v17.8b, w6 //D17 contains beta_cr mov v16.d[1], v17.d[0] umlal v14.8h, v6.8b, v31.8b // umlal v28.8h, v7.8b, v31.8b //Q14,Q7 = (X2(p1U) + p0U + q1U) cmhs v18.16b, v22.16b, v20.16b cmhs v24.16b, v24.16b, v16.16b cmhs v26.16b, v26.16b, v16.16b rshrn v8.8b, v8.8h, #2 // rshrn v9.8b, v10.8h, #2 //Q4 = (X2(q1U) + q0U + p1U + 2) >> 2 mov v8.d[1], v9.d[0] orr v18.16b, v18.16b , v24.16b //Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) rshrn v10.8b, v14.8h, #2 // rshrn v11.8b, v28.8h, #2 //Q5 = (X2(p1U) + p0U + q1U + 2) >> 2 mov v10.d[1], v11.d[0] orr v18.16b, v18.16b , v26.16b //Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) bit v10.16b, v4.16b , v18.16b // bit v8.16b, v0.16b , v18.16b // mov v11.d[0], v10.d[1] mov v9.d[0], v8.d[1] st2 {v10.8b, v11.8b}, [x4], x1 // st2 {v8.8b, v9.8b}, [x4] // // LDMFD sp!,{x4-x6,pc} // ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //* @brief //* Performs filtering of a chroma block vertical edge when the //* boundary strength is set to 4 in high profile //* //* @par Description: //* This operation is described in Sec. 8.7.2.4 under the title //* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. //* //* @param[in] x0 - pu1_src //* Pointer to the src sample q0 //* //* @param[in] w1 - src_strd //* Source stride //* //* @param[in] w2 - alpha_cb //* Alpha Value for the boundary in U //* //* @param[in] w3 - beta_cb //* Beta Value for the boundary in U //* //* @param[in] w4 - alpha_cr //* Alpha Value for the boundary in V //* //* @param[in] w5 - beta_cr //* Beta Value for the boundary in V //* //* @returns //* None //* //* @remarks //* None //* //******************************************************************************* //*/ .global ih264_deblk_chroma_vert_bs4_av8 ih264_deblk_chroma_vert_bs4_av8: // STMFD sp!,{x4,x5,x12,x14} push_v_regs stp x19, x20, [sp, #-16]! sxtw x1, w1 sub x0, x0, #4 //point x0 to p1u of row0. mov x12, x0 //keep a back up of x0 for buffer write add w2, w2, w4, lsl #8 //w2 = (alpha_cr,alpha_cb) add w3, w3, w5, lsl #8 //w3 = (beta_cr,beta_cb) ld4 {v0.h, v1.h, v2.h, v3.h}[0], [x0], x1 ld4 {v0.h, v1.h, v2.h, v3.h}[1], [x0], x1 ld4 {v0.h, v1.h, v2.h, v3.h}[2], [x0], x1 ld4 {v0.h, v1.h, v2.h, v3.h}[3], [x0], x1 ld4 {v4.h, v5.h, v6.h, v7.h}[0], [x0], x1 ld4 {v4.h, v5.h, v6.h, v7.h}[1], [x0], x1 ld4 {v4.h, v5.h, v6.h, v7.h}[2], [x0], x1 ld4 {v4.h, v5.h, v6.h, v7.h}[3], [x0], x1 mov v10.16b, v2.16b mov v2.16b, v1.16b mov v1.16b, v4.16b mov v4.16b, v10.16b mov v10.16b, v6.16b mov v6.16b, v3.16b mov v3.16b, v5.16b mov v5.16b, v10.16b dup v22.8h, w2 //Q11 = alpha dup v24.8h, w3 //Q12 = beta movi v31.8b, #2 mov v0.d[1], v1.d[0] mov v2.d[1], v3.d[0] mov v4.d[1], v5.d[0] mov v6.d[1], v7.d[0] uabd v8.16b, v2.16b , v4.16b //|p0-q0| uabd v10.16b, v6.16b , v4.16b //|q1-q0| uabd v12.16b, v0.16b , v2.16b //|p1-p0| uaddl v14.8h, v2.8b, v6.8b uaddl v16.8h, v3.8b, v7.8b //(p0 + q1) cmhi v8.16b, v22.16b , v8.16b //|p0-q0| < alpha ? cmhi v10.16b, v24.16b , v10.16b //|q1-q0| < beta ? cmhi v12.16b, v24.16b , v12.16b //|p1-p0| < beta ? umlal v14.8h, v0.8b, v31.8b umlal v16.8h, v1.8b, v31.8b //2*p1 + (p0 + q1) uaddl v18.8h, v0.8b, v4.8b uaddl v20.8h, v1.8b, v5.8b //(p1 + q0) and v8.16b, v8.16b , v10.16b //|p0-q0| < alpha && |q1-q0| < beta umlal v18.8h, v6.8b, v31.8b umlal v20.8h, v7.8b, v31.8b //2*q1 + (p1 + q0) rshrn v14.8b, v14.8h, #2 rshrn v15.8b, v16.8h, #2 //(2*p1 + (p0 + q1) + 2) >> 2 mov v14.d[1], v15.d[0] and v8.16b, v8.16b , v12.16b //|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta rshrn v18.8b, v18.8h, #2 rshrn v19.8b, v20.8h, #2 //(2*q1 + (p1 + q0) + 2) >> 2 mov v18.d[1], v19.d[0] bit v2.16b, v14.16b , v8.16b bit v4.16b, v18.16b , v8.16b mov v1.d[0], v0.d[1] mov v3.d[0], v2.d[1] mov v5.d[0], v4.d[1] mov v7.d[0], v6.d[1] mov v10.16b, v1.16b mov v1.16b, v2.16b mov v2.16b, v4.16b mov v4.16b, v10.16b mov v10.16b, v3.16b mov v3.16b, v6.16b mov v6.16b, v5.16b mov v5.16b, v10.16b st4 {v0.h, v1.h, v2.h, v3.h}[0], [x12], x1 st4 {v0.h, v1.h, v2.h, v3.h}[1], [x12], x1 st4 {v0.h, v1.h, v2.h, v3.h}[2], [x12], x1 st4 {v0.h, v1.h, v2.h, v3.h}[3], [x12], x1 st4 {v4.h, v5.h, v6.h, v7.h}[0], [x12], x1 st4 {v4.h, v5.h, v6.h, v7.h}[1], [x12], x1 st4 {v4.h, v5.h, v6.h, v7.h}[2], [x12], x1 st4 {v4.h, v5.h, v6.h, v7.h}[3], [x12], x1 // LDMFD sp!,{x4,x5,x12,pc} ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //* @brief //* Performs filtering of a chroma block horizontal edge for cases where the //* boundary strength is less than 4 in high profile //* //* @par Description: //* This operation is described in Sec. 8.7.2.4 under the title //* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. //* //* @param[in] x0 - pu1_src //* Pointer to the src sample q0 //* //* @param[in] w1 - src_strd //* Source stride //* //* @param[in] w2 - alpha_cb //* Alpha Value for the boundary in U //* //* @param[in] w3 - beta_cb //* Beta Value for the boundary in U //* //* @param[in] w4 - alpha_cr //* Alpha Value for the boundary in V //* //* @param[in] w5 - beta_cr //* Beta Value for the boundary in V //* //* @param[in] w6 - u4_bs //* Packed Boundary strength array //* //* @param[in] x7 - pu1_cliptab_cb //* tc0_table for U //* //* @param[in] sp(0) - pu1_cliptab_cr //* tc0_table for V //* //* @returns //* None //* //* @remarks //* None //* //******************************************************************************* //*/ .global ih264_deblk_chroma_horz_bslt4_av8 ih264_deblk_chroma_horz_bslt4_av8: // STMFD sp!,{x4-x9,x14} // push_v_regs stp x19, x20, [sp, #-16]! sxtw x1, w1 ldr x8, [sp, #80] sub x0, x0, x1, lsl #1 //x0 = uc_edgePixelU pointing to p1 of chroma U rev w6, w6 // mov v12.s[0], w6 //D12[0] = ui_Bs ld1 {v16.s}[0], [x7] //D16[0] contains cliptab_cb ld1 {v17.s}[0], [x8] //D17[0] contains cliptab_cr ld2 {v6.8b, v7.8b}, [x0], x1 //Q3=p1 tbl v14.8b, {v16.16b}, v12.8b //Retreiving cliptab values for U tbl v28.8b, {v17.16b}, v12.8b //Retrieving cliptab values for V uxtl v12.8h, v12.8b //Q6 = uc_Bs in each 16 bit scalar mov x6, x0 //Keeping a backup of the pointer to chroma U P0 ld2 {v4.8b, v5.8b}, [x0], x1 //Q2=p0 movi v30.8b, #1 // dup v20.8b, w2 //D20 contains alpha_cb dup v21.8b, w4 //D21 contains alpha_cr mov v20.d[1], v21.d[0] ld2 {v0.8b, v1.8b}, [x0], x1 //Q0=q0 uxtl v14.8h, v14.8b // uxtl v28.8h, v28.8b // mov v15.d[0], v28.d[0] //D14 has cliptab values for U, D15 for V mov v14.d[1], v28.d[0] ld2 {v2.8b, v3.8b}, [x0] //Q1=q1 usubl v10.8h, v1.8b, v5.8b // usubl v8.8h, v0.8b, v4.8b //Q5,Q4 = (q0 - p0) mov v6.d[1], v7.d[0] mov v4.d[1], v5.d[0] uabd v26.16b, v6.16b , v4.16b //Q13 = ABS(p1 - p0) shl v10.8h, v10.8h, #2 //Q5 = (q0 - p0)<<2 mov v0.d[1], v1.d[0] uabd v22.16b, v4.16b , v0.16b //Q11 = ABS(p0 - q0) shl v8.8h, v8.8h, #2 //Q4 = (q0 - p0)<<2 mov v14.d[1], v15.d[0] sli v14.8h, v14.8h, #8 mov v15.d[0], v14.d[1] mov v2.d[1], v3.d[0] uabd v24.16b, v2.16b , v0.16b //Q12 = ABS(q1 - q0) cmhs v18.16b, v22.16b, v20.16b usubl v20.8h, v6.8b, v2.8b //Q10 = (p1 - q1)L usubl v6.8h, v7.8b, v3.8b //Q3 = (p1 - q1)H dup v16.8b, w3 //Q8 contains beta_cb dup v17.8b, w5 //Q8 contains beta_cr mov v16.d[1], v17.d[0] add v8.8h, v8.8h , v20.8h // add v10.8h, v10.8h , v6.8h //Q5,Q4 = [ (q0 - p0)<<2 ] + (p1 - q1) cmhs v24.16b, v24.16b, v16.16b cmgt v12.4h, v12.4h, #0 sqrshrn v8.8b, v8.8h, #3 // sqrshrn v9.8b, v10.8h, #3 //Q4 = i_macro = (((q0 - p0)<<2) + (p1 - q1) + 4)>>3 mov v8.d[1], v9.d[0] add v14.8b, v14.8b , v30.8b //D14 = C = C0+1 for U cmhs v26.16b, v26.16b, v16.16b orr v18.16b, v18.16b , v24.16b //Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) abs v6.16b, v8.16b //Q4 = ABS (i_macro) add v15.8b, v15.8b , v30.8b //D15 = C = C0+1 for V mov v14.d[1], v15.d[0] mov v13.8b, v12.8b mov v12.d[1], v13.d[0] // orr v18.16b, v18.16b , v26.16b //Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) umin v14.16b, v6.16b , v14.16b //Q7 = delta = (ABS(i_macro) > C) ? C : ABS(i_macro) bic v12.16b, v12.16b , v18.16b //final condition cmge v8.16b, v8.16b, #0 and v14.16b, v14.16b , v12.16b //Making delta zero in places where values shouldn be filterd uqadd v16.16b, v4.16b , v14.16b //Q8 = p0 + delta uqsub v4.16b, v4.16b , v14.16b //Q2 = p0 - delta uqadd v18.16b, v0.16b , v14.16b //Q9 = q0 + delta uqsub v0.16b, v0.16b , v14.16b //Q0 = q0 - delta bif v16.16b, v4.16b , v8.16b //Q8 = (i_macro >= 0 ) ? (p0+delta) : (p0-delta) bif v0.16b, v18.16b , v8.16b //Q0 = (i_macro >= 0 ) ? (q0-delta) : (q0+delta) mov v17.d[0], v16.d[1] mov v1.d[0], v0.d[1] st2 {v16.8b, v17.8b}, [x6], x1 // st2 {v0.8b, v1.8b}, [x6] // ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //* @brief //* Performs filtering of a chroma block vertical edge for cases where the //* boundary strength is less than 4 in high profile //* //* @par Description: //* This operation is described in Sec. 8.7.2.4 under the title //* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. //* //* @param[in] x0 - pu1_src //* Pointer to the src sample q0 //* //* @param[in] w1 - src_strd //* Source stride //* //* @param[in] w2 - alpha_cb //* Alpha Value for the boundary in U //* //* @param[in] w3 - beta_cb //* Beta Value for the boundary in U //* //* @param[in] w4 - alpha_cr //* Alpha Value for the boundary in V //* //* @param[in] w5 - beta_cr //* Beta Value for the boundary in V //* //* @param[in] w6 - u4_bs //* Packed Boundary strength array //* //* @param[in] x7 - pu1_cliptab_cb //* tc0_table for U //* //* @param[in] sp(0) - pu1_cliptab_cr //* tc0_table for V //* //* @returns //* None //* //* @remarks //* None //* //******************************************************************************* //*/ .global ih264_deblk_chroma_vert_bslt4_av8 ih264_deblk_chroma_vert_bslt4_av8: // STMFD sp!,{x4-x7,x10-x12,x14} push_v_regs stp x19, x20, [sp, #-16]! sxtw x1, w1 mov x10, x7 ldr x11, [sp, #80] //x11 = u4_bs sub x0, x0, #4 //point x0 to p1u of row0. add w2, w2, w4, lsl #8 add w3, w3, w5, lsl #8 mov x12, x0 //keep a back up of x0 for buffer write ld4 {v0.h, v1.h, v2.h, v3.h}[0], [x0], x1 ld4 {v0.h, v1.h, v2.h, v3.h}[1], [x0], x1 ld4 {v0.h, v1.h, v2.h, v3.h}[2], [x0], x1 ld4 {v0.h, v1.h, v2.h, v3.h}[3], [x0], x1 ld4 {v4.h, v5.h, v6.h, v7.h}[0], [x0], x1 ld4 {v4.h, v5.h, v6.h, v7.h}[1], [x0], x1 ld4 {v4.h, v5.h, v6.h, v7.h}[2], [x0], x1 ld4 {v4.h, v5.h, v6.h, v7.h}[3], [x0], x1 mov v10.16b, v2.16b mov v2.16b, v1.16b mov v1.16b, v4.16b mov v4.16b, v10.16b mov v10.16b, v6.16b mov v6.16b, v3.16b mov v3.16b, v5.16b mov v5.16b, v10.16b dup v22.8h, w2 //Q11 = alpha mov v2.d[1], v3.d[0] mov v4.d[1], v5.d[0] uabd v8.16b, v2.16b , v4.16b //|p0-q0| dup v24.8h, w3 //Q12 = beta mov v25.d[0], v24.d[1] mov v6.d[1], v7.d[0] mov v0.d[1], v1.d[0] uabd v10.16b, v6.16b , v4.16b //|q1-q0| uabd v12.16b, v0.16b , v2.16b //|p1-p0| cmhi v8.16b, v22.16b , v8.16b //|p0-q0| < alpha ? usubl v14.8h, v0.8b, v6.8b cmhi v10.16b, v24.16b , v10.16b //|q1-q0| < beta ? usubl v16.8h, v1.8b, v7.8b //(p1 - q1) cmhi v12.16b, v24.16b , v12.16b //|p1-p0| < beta ? usubl v18.8h, v4.8b, v2.8b and v8.16b, v8.16b , v10.16b //|p0-q0| < alpha && |q1-q0| < beta usubl v20.8h, v5.8b, v3.8b //(q0 - p0) movi v28.8h, #4 ld1 {v24.s}[0], [x10] //Load ClipTable for U ld1 {v25.s}[0], [x11] //Load ClipTable for V rev w6, w6 //Blocking strengths and v8.16b, v8.16b , v12.16b //|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta mov v10.s[0], w6 mla v14.8h, v18.8h , v28.8h mla v16.8h, v20.8h , v28.8h //4*(q0 - p0) + (p1 - q1) uxtl v10.8h, v10.8b sli v10.4h, v10.4h, #8 tbl v12.8b, {v24.16b}, v10.8b //tC0 for U tbl v13.8b, {v25.16b}, v10.8b //tC0 for V zip1 v31.8b, v12.8b, v13.8b zip2 v13.8b, v12.8b, v13.8b mov v12.8b, v31.8b mov v12.d[1], v13.d[0] uxtl v10.4s, v10.4h sli v10.4s, v10.4s, #16 movi v24.16b, #1 add v12.16b, v12.16b , v24.16b //tC0 + 1 cmhs v10.16b, v10.16b , v24.16b and v8.16b, v8.16b , v10.16b //|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0 // Q0 - Q3(inputs), // Q4 (|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0), // Q6 (tC) srshr v14.8h, v14.8h, #3 srshr v16.8h, v16.8h, #3 //(((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) cmgt v18.8h, v14.8h , #0 cmgt v20.8h, v16.8h , #0 xtn v18.8b, v18.8h xtn v19.8b, v20.8h //Q9 = sign(delta) mov v18.d[1], v19.d[0] abs v14.8h, v14.8h abs v16.8h, v16.8h xtn v14.8b, v14.8h xtn v15.8b, v16.8h mov v14.d[1], v15.d[0] umin v14.16b, v14.16b , v12.16b //Q7 = |delta| uqadd v20.16b, v2.16b , v14.16b //p0+|delta| uqadd v22.16b, v4.16b , v14.16b //q0+|delta| uqsub v24.16b, v2.16b , v14.16b //p0-|delta| uqsub v26.16b, v4.16b , v14.16b //q0-|delta| bit v24.16b, v20.16b , v18.16b //p0 + delta bit v22.16b, v26.16b , v18.16b //q0 - delta bit v2.16b, v24.16b , v8.16b bit v4.16b, v22.16b , v8.16b mov v1.d[0], v0.d[1] mov v3.d[0], v2.d[1] mov v5.d[0], v4.d[1] mov v7.d[0], v6.d[1] mov v10.16b, v1.16b mov v1.16b, v2.16b mov v2.16b, v4.16b mov v4.16b, v10.16b mov v10.16b, v3.16b mov v3.16b, v6.16b mov v6.16b, v5.16b mov v5.16b, v10.16b st4 {v0.h, v1.h, v2.h, v3.h}[0], [x12], x1 st4 {v0.h, v1.h, v2.h, v3.h}[1], [x12], x1 st4 {v0.h, v1.h, v2.h, v3.h}[2], [x12], x1 st4 {v0.h, v1.h, v2.h, v3.h}[3], [x12], x1 st4 {v4.h, v5.h, v6.h, v7.h}[0], [x12], x1 st4 {v4.h, v5.h, v6.h, v7.h}[1], [x12], x1 st4 {v4.h, v5.h, v6.h, v7.h}[2], [x12], x1 st4 {v4.h, v5.h, v6.h, v7.h}[3], [x12], x1 ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_deblk_luma_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///*****************************************************************************/ ///* */ ///* File Name : ih264_deblk_luma_av8.s */ ///* */ ///* Description : Contains function definitions for deblocking luma */ ///* edge. Functions are coded in NEON assembly and can */ ///* be compiled using ARM RVDS. */ ///* */ ///* List of Functions : ih264_deblk_luma_vert_bs4_av8() */ ///* ih264_deblk_luma_vert_bslt4_av8() */ ///* ih264_deblk_luma_horz_bs4_av8() */ ///* ih264_deblk_luma_horz_bslt4_av8() */ ///* */ ///* Issues / Problems : None */ ///* */ ///* Revision History : */ ///* */ ///* DD MM YYYY Author(s) Changes (Describe the changes made) */ ///* 28 11 2013 Ittiam Draft */ ///* */ ///*****************************************************************************/ .text .p2align 2 .include "ih264_neon_macros.s" ///** //******************************************************************************* //* //* @brief //* Performs filtering of a luma block horizontal edge for cases where the //* boundary strength is less than 4 //* //* @par Description: //* This operation is described in Sec. 8.7.2.4 under the title //* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. //* //* @param[in] x0 - pu1_src //* Pointer to the src sample q0 //* //* @param[in] w1 - src_strd //* Source stride //* //* @param[in] w2 - alpha //* Alpha Value for the boundary //* //* @param[in] w3 - beta //* Beta Value for the boundary //* //* @param[in] w4 - u4_bs //* Packed Boundary strength array //* //* @param[in] x5 - pu1_cliptab //* tc0_table //* //* @returns //* None //* //* @remarks //* None //* //******************************************************************************* //*/ .global ih264_deblk_luma_horz_bslt4_av8 ih264_deblk_luma_horz_bslt4_av8: // STMFD sp!,{x4-x7,x14} push_v_regs sxtw x1, w1 stp x19, x20, [sp, #-16]! //LDRD x4,x5,[SP,#0x14] //x4 = ui_Bs , x5 = *puc_ClpTab sub x0, x0, x1, lsl #1 //x1 = uc_Horizonpad sub x0, x0, x1 //x0 pointer to p2 rev w4, w4 // ld1 {v10.8b, v11.8b}, [x0], x1 //p2 values are loaded into q5 mov v12.s[0], w4 //d12[0] = ui_Bs mov x6, x0 //keeping backup of pointer to p1 ld1 {v8.8b, v9.8b}, [x0], x1 //p1 values are loaded into q4 mov x7, x0 //keeping backup of pointer to p0 ld1 {v6.8b, v7.8b}, [x0], x1 //p0 values are loaded into q3 uxtl v12.8h, v12.8b //q6 = uc_Bs in each 16 bt scalar ld1 {v0.8b, v1.8b}, [x0], x1 //q0 values are loaded into q0 mov v10.d[1], v11.d[0] mov v8.d[1], v9.d[0] mov v6.d[1], v7.d[0] uabd v26.16b, v8.16b, v6.16b ld1 {v2.8b, v3.8b}, [x0], x1 //q1 values are loaded into q1 mov v0.d[1], v1.d[0] mov v2.d[1], v3.d[0] uabd v22.16b, v6.16b, v0.16b ld1 {v16.s}[0], [x5] //D16[0] contains cliptab uabd v24.16b, v2.16b, v0.16b ld1 {v4.8b, v5.8b}, [x0], x1 //q2 values are loaded into q2 tbl v14.8b, {v16.16b}, v12.8b // mov v4.d[1], v5.d[0] dup v20.16b, w2 //Q10 contains alpha dup v16.16b, w3 //Q8 contains beta uxtl v12.4s, v12.4h // uxtl v14.4s, v14.4h // uabd v28.16b, v10.16b, v6.16b uabd v30.16b, v4.16b, v0.16b cmgt v12.4s, v12.4s, #0 sli v14.4s, v14.4s, #8 cmhs v18.16b, v22.16b, v20.16b cmhs v24.16b, v24.16b, v16.16b cmhs v26.16b, v26.16b, v16.16b cmhi v20.16b, v16.16b , v28.16b //Q10=(Ap= Alpha ) | ( ABS(q1 - q0) >= Beta ) usubl v30.8h, v1.8b, v7.8b // usubl v24.8h, v0.8b, v6.8b //Q15,Q12 = (q0 - p0) orr v18.16b, v18.16b , v26.16b //Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) usubl v28.8h, v8.8b, v2.8b //Q14 = (p1 - q1)L shl v26.8h, v30.8h, #2 //Q13 = (q0 - p0)<<2 shl v24.8h, v24.8h, #2 //Q12 = (q0 - p0)<<2 usubl v30.8h, v9.8b, v3.8b //Q15 = (p1 - q1)H bic v12.16b, v12.16b , v18.16b //final condition add v24.8h, v24.8h , v28.8h // add v26.8h, v26.8h , v30.8h //Q13,Q12 = [ (q0 - p0)<<2 ] + (p1 - q1) sub v18.16b, v14.16b , v20.16b //Q9 = C0 + (Ap < Beta) urhadd v16.16b, v6.16b , v0.16b //Q8 = ((p0+q0+1) >> 1) mov v17.d[0], v16.d[1] sqrshrn v24.8b, v24.8h, #3 // sqrshrn v25.8b, v26.8h, #3 //Q12 = i_macro = (((q0 - p0)<<2) + (p1 - q1) + 4)>>3 mov v24.d[1], v25.d[0] sub v18.16b, v18.16b , v22.16b //Q9 = C0 + (Ap < Beta) + (Aq < Beta) and v20.16b, v20.16b , v12.16b // and v22.16b, v22.16b , v12.16b // abs v26.16b, v24.16b //Q13 = ABS (i_macro) uaddl v28.8h, v17.8b, v11.8b // uaddl v10.8h, v16.8b, v10.8b //Q14,Q5 = p2 + (p0+q0+1)>>1 uaddl v30.8h, v17.8b, v5.8b // umin v18.16b, v26.16b , v18.16b //Q9 = delta = (ABS(i_macro) > C) ? C : ABS(i_macro) ushll v26.8h, v9.8b, #1 // uaddl v4.8h, v16.8b, v4.8b //Q15,Q2 = q2 + (p0+q0+1)>>1 ushll v16.8h, v8.8b, #1 //Q13,Q8 = (p1<<1) and v18.16b, v18.16b , v12.16b //Making delta zero in places where values shouldn be filterd sub v28.8h, v28.8h , v26.8h //Q14,Q5 = [p2 + (p0+q0+1)>>1] - (p1<<1) sub v10.8h, v10.8h , v16.8h // ushll v16.8h, v2.8b, #1 // ushll v26.8h, v3.8b, #1 //Q13,Q8 = (q1<<1) sqshrn v29.8b, v28.8h, #1 // sqshrn v28.8b, v10.8h, #1 //Q14 = i_macro_p1 mov v28.d[1], v29.d[0] sub v4.8h, v4.8h , v16.8h // sub v30.8h, v30.8h , v26.8h //Q15,Q2 = [q2 + (p0+q0+1)>>1] - (q1<<1) neg v26.16b, v14.16b //Q13 = -C0 smin v28.16b, v28.16b , v14.16b //Q14 = min(C0,i_macro_p1) cmge v24.16b, v24.16b, #0 sqshrn v31.8b, v30.8h, #1 // sqshrn v30.8b, v4.8h, #1 //Q15 = i_macro_q1 mov v30.d[1], v31.d[0] smax v28.16b, v28.16b , v26.16b //Q14 = max( - C0 , min(C0, i_macro_p1) ) uqadd v16.16b, v6.16b , v18.16b //Q8 = p0 + delta uqsub v6.16b, v6.16b , v18.16b //Q3 = p0 - delta smin v30.16b, v30.16b , v14.16b //Q15 = min(C0,i_macro_q1) and v28.16b, v20.16b , v28.16b //condition check Ap= 0 ) ? (p0+delta) : (p0-delta) bif v0.16b, v14.16b , v24.16b //Q0 = (i_macro >= 0 ) ? (q0-delta) : (q0+delta) add v28.16b, v28.16b , v8.16b // and v30.16b, v22.16b , v30.16b //condition check Aq= Alpha cmhs v14.16b, v14.16b , v2.16b //ABS(q1 - q0) >= Beta cmhs v16.16b, v16.16b , v2.16b //ABS(q1 - q0) >= Beta movi v20.16b, #2 orr v18.16b, v18.16b , v14.16b //ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta ld1 {v14.8b, v15.8b}, [x0], x1 //load q2 to Q7, q0 = q0 + src_strd mov v14.d[1] , v15.d[0] orr v18.16b, v18.16b , v16.16b //ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta usra v20.16b, v0.16b, #2 //alpha >>2 +2 uabd v22.16b , v14.16b, v4.16b uaddl v24.8h, v4.8b, v6.8b //p0+q0 L uaddl v26.8h, v5.8b, v7.8b //p0+q0 H cmhi v22.16b, v2.16b , v22.16b //Aq < Beta cmhi v20.16b, v20.16b , v12.16b //(ABS(p0 - q0) <((Alpha >>2) + 2)) // Deblock Filtering q0', q1', q2' uaddw v28.8h, v24.8h , v8.8b //p0+q0+q1 L uaddw v30.8h, v26.8h , v9.8b //p0+q0+q1 H and v22.16b, v22.16b , v20.16b //(Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) // q0' if (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) TRUE add v16.8h, v28.8h , v28.8h //2*(p0+q0+q1)L add v0.8h, v30.8h , v30.8h //2*(p0+q0+q1)H uaddw v16.8h, v16.8h , v14.8b //2*(p0+q0+q1)+q2 L uaddw v0.8h, v0.8h , v15.8b //2*(p0+q0+q1)+q2 H uaddw v16.8h, v16.8h , v10.8b //2*(p0+q0+q1)+q2 +p1 L uaddw v0.8h, v0.8h , v11.8b //2*(p0+q0+q1)+q2 +p1 H rshrn v12.8b, v16.8h, #3 //(2*(p0+q0+q1)+q2 +p1 +4)>> 3 L [q0'] rshrn v13.8b, v0.8h, #3 //(2*(p0+q0+q1)+q2 +p1 +4)>> 3 H [q0'] mov v12.d[1] , v13.d[0] // q0" if (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) FALSE uaddl v16.8h, v8.8b, v8.8b //2*q1 L uaddl v0.8h, v9.8b, v9.8b //2*q1 H uaddw v16.8h, v16.8h , v4.8b //2*q1+q0 L uaddw v0.8h, v0.8h , v5.8b //2*q1+q0 H uaddw v16.8h, v16.8h , v10.8b //2*q1+q0+p1 L uaddw v0.8h, v0.8h , v11.8b //2*q1+q0+p1 H rshrn v16.8b, v16.8h, #2 //(2*q1+q0+p1+2)>>2 L [q0"] rshrn v17.8b, v0.8h, #2 //(2*q1+q0+p1+2)>>2 H [q0"] mov v16.d[1] , v17.d[0] uaddw v28.8h, v28.8h , v14.8b //p0+q0+q1+q2 L uaddw v30.8h, v30.8h , v15.8b //p0+q0+q1+q2 H ld1 {v0.8b, v1.8b}, [x0], x1 //load q3 to Q0, q0 = q0 + src_strd mov v0.d[1] , v1.d[0] bit v16.16b, v12.16b , v22.16b //choosing between q0' and q0" depending on condn sub x0, x0, x1, lsl #2 //pointer to q0 bic v22.16b, v22.16b , v18.16b //((ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) // && (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) rshrn v12.8b, v28.8h, #2 //(p0+q0+q1+q2+2)>>2 L [q1'] rshrn v13.8b, v30.8h, #2 //(p0+q0+q1+q2+2)>>2 H [q1'] mov v12.d[1] , v13.d[0] bif v4.16b, v16.16b , v18.16b //choose q0 or filtered q0 mov v5.d[0] , v4.d[1] uaddl v16.8h, v14.8b, v0.8b //q2+q3,L uaddl v0.8h, v15.8b, v1.8b //q2+q3,H add v28.8h, v28.8h , v16.8h //p0+q0+q1+2*q2+q3 L st1 {v4.8b, v5.8b}, [x0], x1 //store q0 add v30.8h, v30.8h , v0.8h //p0+q0+q1+2*q2+q3 H add v28.8h, v28.8h , v16.8h //p0+q0+q1+3*q2+2*q3 L add v30.8h, v30.8h , v0.8h //p0+q0+q1+3*q2+2*q3 H rshrn v0.8b, v28.8h, #3 //(p0+q0+q1+3*q2+2*q3+4)>>3 L [q2'] rshrn v1.8b, v30.8h, #3 //(p0+q0+q1+3*q2+2*q3+4)>>3 H [q2'] mov v0.d[1] , v1.d[0] ld1 {v30.8b, v31.8b}, [x3] //load p2 to Q15 mov v30.d[1] , v31.d[0] bif v12.16b, v8.16b , v22.16b //choose q1 or filtered value of q1 mov v13.d[0] , v12.d[1] uabd v16.16b , v30.16b, v6.16b uaddw v24.8h, v24.8h , v10.8b //p0+q0+p1 L bif v0.16b, v14.16b , v22.16b //choose q2 or filtered q2 mov v1.d[0] , v0.d[1] uaddw v26.8h, v26.8h , v11.8b //p0+q0+p1 H st1 {v12.8b, v13.8b}, [x0], x1 //store q1 cmhi v16.16b, v2.16b , v16.16b //Ap < Beta add v28.8h, v24.8h , v24.8h //2*(p0+q0+p1) L add v4.8h, v26.8h , v26.8h //2*(p0+q0+p1) H st1 {v0.8b, v1.8b}, [x0], x1 //store q2 and v20.16b, v20.16b , v16.16b //((Ap < Beta) && (ABS(p0 - q0) <((Alpha >>2) + 2))) uaddw v28.8h, v28.8h , v30.8b //2*(p0+q0+p1)+p2 l uaddw v4.8h, v4.8h , v31.8b //2*(p0+q0+p1)+p2 H uaddw v28.8h, v28.8h , v8.8b //2*(p0+q0+p1)+p2+q1 L uaddw v4.8h, v4.8h , v9.8b //2*(p0+q0+p1)+p2+q1 H rshrn v28.8b, v28.8h, #3 //(2*(p0+q0+p1)+p2+q1+4)>>3 L,p0' rshrn v29.8b, v4.8h, #3 //(2*(p0+q0+p1)+p2+q1+4)>>3 H,p0' mov v28.d[1] , v29.d[0] movi v0.8b, #2 movi v1.4h, #2 uaddl v2.8h, v6.8b, v8.8b //p0+q1 L umlal v2.8h, v10.8b, v0.8b //2*p1+p0+q1 L uaddl v16.8h, v7.8b, v9.8b //p0+q1 H umlal v16.8h, v11.8b, v0.8b //2*p1+p0+q1 H uaddw v12.8h, v24.8h , v30.8b //(p0+q0+p1) +p2 L ld1 {v24.8b, v25.8b}, [x2] //load p3,Q12 mov v24.d[1] , v25.d[0] uaddw v4.8h, v26.8h , v31.8b //(p0+q0+p1) +p2 H uaddl v8.8h, v30.8b, v24.8b //p2+p3 L rshrn v26.8b, v12.8h, #2 //((p0+q0+p1)+p2 +2)>>2,p1' L rshrn v2.8b, v2.8h, #2 //(2*p1+p0+q1+2)>>2,p0"L rshrn v27.8b, v4.8h, #2 //((p0+q0+p1)+p2 +2)>>2,p1' H rshrn v3.8b, v16.8h, #2 //(2*p1+p0+q1+2)>>2,p0" H mov v26.d[1] , v27.d[0] mov v2.d[1] , v3.d[0] uaddl v16.8h, v31.8b, v25.8b //p2+p3 H mla v12.8h, v8.8h , v1.h[0] //(p0+q0+p1)+3*p2+2*p3 L mla v4.8h, v16.8h , v1.h[0] //(p0+q0+p1)+3*p2+2*p3 H bic v16.16b, v20.16b , v18.16b //((ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) mov v17.d[0] , v16.d[1] //&& (Ap < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) bit v2.16b, v28.16b , v20.16b //choosing between po' and p0" mov v3.d[0] , v2.d[1] rshrn v12.8b, v12.8h, #3 //((p0+q0+p1)+3*p2+2*p3+4)>>3 L p2' rshrn v13.8b, v4.8h, #3 //((p0+q0+p1)+3*p2+2*p3+4)>>3 H p2' mov v12.d[1] , v13.d[0] bif v6.16b, v2.16b , v18.16b //choosing between p0 and filtered value of p0 bit v10.16b, v26.16b , v16.16b //choosing between p1 and p1' bit v30.16b, v12.16b , v16.16b //choosing between p2 and p2' st1 {v6.16b}, [x12] //store p0 st1 {v10.16b}, [x14] //store p1 st1 {v30.16b}, [x3] //store p2 // LDMFD sp!,{x12,pc} ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //* @brief //* Performs filtering of a luma block vertical edge for cases where the //* boundary strength is less than 4 //* //* @par Description: //* This operation is described in Sec. 8.7.2.4 under the title //* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. //* //* @param[in] x0 - pu1_src //* Pointer to the src sample q0 //* //* @param[in] w1 - src_strd //* Source stride //* //* @param[in] w2 - alpha //* Alpha Value for the boundary //* //* @param[in] w3 - beta //* Beta Value for the boundary //* //* @param[in] w4 - u4_bs //* Packed Boundary strength array //* //* @param[in] x5 - pu1_cliptab //* tc0_table //* //* @returns //* None //* //* @remarks //* None //* //******************************************************************************* //*/ .global ih264_deblk_luma_vert_bslt4_av8 ih264_deblk_luma_vert_bslt4_av8: // STMFD sp!,{x12,x14} push_v_regs stp x19, x20, [sp, #-16]! sxtw x1, w1 sub x0, x0, #4 //pointer uc_edgePixel-4 mov x12, x4 mov x14, x5 mov x17, x0 //loading p3:p2:p1:p0:q0:q1:q2:q3 for every row ld1 {v0.8b}, [x0], x1 //row1 ld1 {v2.8b}, [x0], x1 //row2 ld1 {v4.8b}, [x0], x1 //row3 rev w12, w12 //reversing ui_bs ld1 {v6.8b}, [x0], x1 //row4 mov v18.s[0], w12 //d12[0] = ui_Bs ld1 {v16.s}[0], [x14] //D16[0] contains cliptab ld1 {v8.8b}, [x0], x1 //row5 uxtl v18.8h, v18.8b //q6 = uc_Bs in each 16 bt scalar ld1 {v10.8b}, [x0], x1 //row6 ld1 {v12.8b}, [x0], x1 //row7 tbl v16.8b, {v16.16b}, v18.8b //puc_ClipTab[uc_Bs] ld1 {v14.8b}, [x0], x1 //row8 ld1 {v1.8b}, [x0], x1 //row9 uxtl v16.4s, v16.4h // ld1 {v3.8b}, [x0], x1 //row10 ld1 {v5.8b}, [x0], x1 //row11 ld1 {v7.8b}, [x0], x1 //row12 sli v16.4s, v16.4s, #8 // ld1 {v9.8b}, [x0], x1 //row13 ld1 {v11.8b}, [x0], x1 //row14 ld1 {v13.8b}, [x0], x1 //row15 sli v16.4s, v16.4s, #16 ld1 {v15.8b}, [x0], x1 //row16 //taking two 8x8 transposes //2X2 transposes trn1 v21.8b, v0.8b, v2.8b trn2 v2.8b, v0.8b, v2.8b //row1 &2 mov v0.8b, v21.8b trn1 v21.8b, v4.8b, v6.8b trn2 v6.8b, v4.8b, v6.8b //row3&row4 mov v4.8b, v21.8b trn1 v21.8b, v8.8b, v10.8b trn2 v10.8b, v8.8b, v10.8b //row5&6 mov v8.8b, v21.8b trn1 v21.8b, v12.8b, v14.8b trn2 v14.8b, v12.8b, v14.8b //row7 & 8 mov v12.8b, v21.8b trn1 v21.8b, v1.8b, v3.8b trn2 v3.8b, v1.8b, v3.8b //row9 &10 mov v1.8b, v21.8b trn1 v21.8b, v5.8b, v7.8b trn2 v7.8b, v5.8b, v7.8b //row11 & 12 mov v5.8b, v21.8b trn1 v21.8b, v9.8b, v11.8b trn2 v11.8b, v9.8b, v11.8b //row13 &14 mov v9.8b, v21.8b trn1 v21.8b, v13.8b, v15.8b trn2 v15.8b, v13.8b, v15.8b //row15 & 16 mov v13.8b, v21.8b //4x4 transposes trn1 v21.4h, v2.4h, v6.4h trn2 v6.4h, v2.4h, v6.4h //row2 & row4 mov v2.8b, v21.8b trn1 v21.4h, v10.4h, v14.4h trn2 v14.4h, v10.4h, v14.4h //row6 & row8 mov v10.8b, v21.8b trn1 v21.4h, v3.4h, v7.4h trn2 v7.4h, v3.4h, v7.4h //row10 & 12 mov v3.8b, v21.8b trn1 v21.4h, v11.4h, v15.4h trn2 v15.4h, v11.4h, v15.4h //row14 & row16 mov v11.8b, v21.8b trn1 v21.2s, v6.2s, v14.2s trn2 v14.2s, v6.2s, v14.2s //row4 & 8 mov v6.8b, v21.8b trn1 v21.2s, v7.2s, v15.2s trn2 v15.2s, v7.2s, v15.2s //row 12 & 16 mov v7.8b, v21.8b //now Q3 ->p0 and Q7->q3 trn1 v21.4h, v0.4h, v4.4h trn2 v4.4h, v0.4h, v4.4h //row1 & 3 mov v0.8b, v21.8b trn1 v21.4h, v8.4h, v12.4h trn2 v12.4h, v8.4h, v12.4h //row 5 & 7 mov v8.8b, v21.8b trn1 v21.4h, v1.4h, v5.4h trn2 v5.4h, v1.4h, v5.4h //row9 & row11 mov v1.8b, v21.8b trn1 v21.4h, v9.4h, v13.4h trn2 v13.4h, v9.4h, v13.4h //row13 & row15 mov v9.8b, v21.8b trn1 v21.2s, v0.2s, v8.2s trn2 v8.2s, v0.2s, v8.2s //row1 & row5 mov v0.8b, v21.8b trn1 v21.2s, v1.2s, v9.2s trn2 v9.2s, v1.2s, v9.2s //row9 & 13 mov v1.8b, v21.8b //now Q0->p3 & Q4->q0 //starting processing as p0 and q0 are now ready trn1 v21.2s, v2.2s, v10.2s trn2 v10.2s, v2.2s, v10.2s //row2 &6 mov v2.8b, v21.8b mov v6.d[1] , v7.d[0] mov v8.d[1] , v9.d[0] urhadd v20.16b, v6.16b , v8.16b //((p0 + q0 + 1) >> 1) mov v21.d[0], v20.d[1] trn1 v31.2s, v3.2s, v11.2s trn2 v11.2s, v3.2s, v11.2s //row10&row14 mov v3.8b, v31.8b movi v19.8b, #2 mov v18.d[1], v19.d[0] //now Q1->p2 & Q5->q1 trn1 v31.2s, v4.2s, v12.2s trn2 v12.2s, v4.2s, v12.2s //row3 & 7 mov v4.8b, v31.8b uabd v22.16b , v6.16b, v8.16b //ABS(q1 - q0) trn1 v31.2s, v5.2s, v13.2s trn2 v13.2s, v5.2s, v13.2s //row11 & row15 mov v5.8b, v31.8b mov v0.d[1] , v1.d[0] mov v2.d[1] , v3.d[0] mov v4.d[1] , v5.d[0] mov v10.d[1] , v11.d[0] mov v12.d[1] , v13.d[0] mov v14.d[1] , v15.d[0] uaddl v24.8h, v20.8b, v2.8b //(p2 + ((p0 + q0 + 1) >> 1) L //now Q2->p1,Q6->q2 uaddl v26.8h, v21.8b, v3.8b //(p2 + ((p0 + q0 + 1) >> 1) H umlsl v24.8h, v4.8b, v19.8b //(p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) L umlsl v26.8h, v5.8b, v19.8b //(p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) H dup v28.16b, w2 //alpha cmhs v22.16b, v22.16b , v28.16b //ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) dup v28.16b, w3 //beta uabd v30.16b , v10.16b, v8.16b //ABS(q1 - q0) sqshrn v24.8b, v24.8h, #1 //((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1) L sqshrn v25.8b, v26.8h, #1 //((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1) H mov v24.d[1], v25.d[0] cmhs v30.16b, v30.16b , v28.16b //ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) uabd v26.16b , v4.16b, v6.16b //ABS(q1 - q0) smin v24.16b, v24.16b , v16.16b //min(deltap1 ,C0) orr v22.16b, v22.16b , v30.16b //ABS(q1 - q0) >= Beta ||ABS(p0 - q0) >= Alpha neg v30.16b, v16.16b //-C0 cmhs v26.16b, v26.16b , v28.16b //ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) smax v24.16b, v24.16b , v30.16b //max(deltap1,-C0) orr v22.16b, v22.16b , v26.16b //ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta) uxtl v26.4s, v18.4h //ui_bs uaddl v18.8h, v20.8b, v12.8b //q2 + ((p0 + q0 + 1) >> 1) L cmeq v26.4s, v26.4s , #0 //ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) usubw v18.8h, v18.8h , v10.8b //(q2 + ((p0 + q0 + 1) >> 1) - q1) L uaddl v20.8h, v21.8b, v13.8b //q2 + ((p0 + q0 + 1) >> 1) H usubw v18.8h, v18.8h , v10.8b //(q2 + ((p0 + q0 + 1) >> 1) - 2*q1)L usubw v20.8h, v20.8h , v11.8b //(q2 + ((p0 + q0 + 1) >> 1) - q1) H orr v26.16b, v26.16b , v22.16b //(ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) &&(ui_bs) usubw v20.8h, v20.8h , v11.8b //(q2 + ((p0 + q0 + 1) >> 1) - 2*q1) H sqshrn v18.8b, v18.8h, #1 //((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1) L uabd v22.16b , v2.16b, v6.16b //ABS(q1 - q0) sqshrn v19.8b, v20.8h, #1 //((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1) H mov v18.d[1], v19.d[0] uabd v20.16b , v12.16b, v8.16b //ABS(q1 - q0) cmhi v22.16b, v28.16b , v22.16b //Ap < Beta smin v18.16b, v18.16b , v16.16b //min(delatq1,C0) cmhi v20.16b, v28.16b , v20.16b //Aq > 3); L rshrn v29.8b, v30.8h, #3 //delta = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) H mov v28.d[1], v29.d[0] sub v16.16b, v16.16b , v20.16b //C0 + (Ap < Beta) + (Aq < Beta) bic v20.16b, v20.16b , v26.16b //final condition for q1 abs v30.16b, v28.16b //abs(delta) and v24.16b, v24.16b , v22.16b //delatp1 and v18.16b, v18.16b , v20.16b //delta q1 umin v30.16b, v30.16b , v16.16b //min((abs(delta),C) add v4.16b, v4.16b , v24.16b //p1+deltap1 add v10.16b, v10.16b , v18.16b //q1+deltaq1 mov v5.d[0], v4.d[1] mov v11.d[0], v10.d[1] bic v30.16b, v30.16b , v26.16b //abs(delta) of pixels to be changed only // VCGE.S8 Q14, Q14,#0 //sign(delta) cmge v28.16b, v28.16b , #0 uqsub v22.16b, v6.16b , v30.16b //clip(p0-delta) trn1 v21.8b, v0.8b, v2.8b trn2 v2.8b, v0.8b, v2.8b //row1 &2 mov v0.8b, v21.8b uqadd v6.16b, v6.16b , v30.16b //clip(p0+delta) trn1 v21.8b, v1.8b, v3.8b trn2 v3.8b, v1.8b, v3.8b //row9 &10 mov v1.8b, v21.8b uqadd v24.16b, v8.16b , v30.16b //clip(q0+delta) trn1 v21.8b, v12.8b, v14.8b trn2 v14.8b, v12.8b, v14.8b //row7 & 8 mov v12.8b, v21.8b uqsub v8.16b, v8.16b , v30.16b //clip(q0-delta) trn1 v21.8b, v13.8b, v15.8b trn2 v15.8b, v13.8b, v15.8b //row15 & 16 mov v13.8b, v21.8b bif v6.16b, v22.16b , v28.16b //p0 bif v8.16b, v24.16b , v28.16b //q0 mov v7.d[0], v6.d[1] mov v9.d[0], v8.d[1] trn1 v21.8b, v4.8b, v6.8b trn2 v6.8b, v4.8b, v6.8b //row3&row4 mov v4.8b, v21.8b trn1 v21.8b, v8.8b, v10.8b trn2 v10.8b, v8.8b, v10.8b //row5&6 mov v8.8b, v21.8b trn1 v21.8b, v5.8b, v7.8b trn2 v7.8b, v5.8b, v7.8b //row11 & 12 mov v5.8b, v21.8b trn1 v21.8b, v9.8b, v11.8b trn2 v11.8b, v9.8b, v11.8b //row13 &14 mov v9.8b, v21.8b trn1 v21.4h, v2.4h, v6.4h trn2 v6.4h, v2.4h, v6.4h //row2 & row4 mov v2.8b, v21.8b trn1 v21.4h, v10.4h, v14.4h trn2 v14.4h, v10.4h, v14.4h //row6 & row8 mov v10.8b, v21.8b trn1 v21.4h, v3.4h, v7.4h trn2 v7.4h, v3.4h, v7.4h //row10 & 12 mov v3.8b, v21.8b trn1 v21.4h, v11.4h, v15.4h trn2 v15.4h, v11.4h, v15.4h //row14 & row16 mov v11.8b, v21.8b trn1 v21.2s, v6.2s, v14.2s trn2 v14.2s, v6.2s, v14.2s //row4 & 8 mov v6.8b, v21.8b trn1 v21.2s, v7.2s, v15.2s trn2 v15.2s, v7.2s, v15.2s //row 12 & 16 mov v7.8b, v21.8b //now Q3 ->p0 and Q7->q3 trn1 v21.4h, v0.4h, v4.4h trn2 v4.4h, v0.4h, v4.4h //row1 & 3 mov v0.8b, v21.8b trn1 v21.4h, v8.4h, v12.4h trn2 v12.4h, v8.4h, v12.4h //row 5 & 7 mov v8.8b, v21.8b trn1 v21.4h, v1.4h, v5.4h trn2 v5.4h, v1.4h, v5.4h //row9 & row11 mov v1.8b, v21.8b trn1 v21.4h, v9.4h, v13.4h trn2 v13.4h, v9.4h, v13.4h //row13 & row15 mov v9.8b, v21.8b sub x0, x0, x1, lsl#4 //restore pointer trn1 v21.2s, v0.2s, v8.2s trn2 v8.2s, v0.2s, v8.2s //row1 & row5 mov v0.8b, v21.8b trn1 v21.2s, v1.2s, v9.2s trn2 v9.2s, v1.2s, v9.2s //row9 & 13 mov v1.8b, v21.8b trn1 v21.2s, v2.2s, v10.2s trn2 v10.2s, v2.2s, v10.2s //row2 &6 mov v2.8b, v21.8b trn1 v21.2s, v3.2s, v11.2s trn2 v11.2s, v3.2s, v11.2s //row10&row14 mov v3.8b, v21.8b trn1 v21.2s, v4.2s, v12.2s trn2 v12.2s, v4.2s, v12.2s //row3 & 7 mov v4.8b, v21.8b trn1 v21.2s, v5.2s, v13.2s trn2 v13.2s, v5.2s, v13.2s //row11 & row15 mov v5.8b, v21.8b st1 {v0.8b}, [x0], x1 //row1 st1 {v2.8b}, [x0], x1 //row2 st1 {v4.8b}, [x0], x1 //row3 st1 {v6.8b}, [x0], x1 //row4 st1 {v8.8b}, [x0], x1 //row5 st1 {v10.8b}, [x0], x1 //row6 st1 {v12.8b}, [x0], x1 //row7 st1 {v14.8b}, [x0], x1 //row8 st1 {v1.8b}, [x0], x1 //row9 st1 {v3.8b}, [x0], x1 //row10 st1 {v5.8b}, [x0], x1 //row11 st1 {v7.8b}, [x0], x1 //row12 st1 {v9.8b}, [x0], x1 //row13 st1 {v11.8b}, [x0], x1 //row14 st1 {v13.8b}, [x0], x1 //row15 st1 {v15.8b}, [x0], x1 //row16 // LDMFD sp!,{x12,pc} ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //* @brief //* Performs filtering of a luma block vertical edge when the //* boundary strength is set to 4 //* //* @par Description: //* This operation is described in Sec. 8.7.2.4 under the title //* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. //* //* @param[in] x0 - pu1_src //* Pointer to the src sample q0 //* //* @param[in] w1 - src_strd //* Source stride //* //* @param[in] w2 - alpha //* Alpha Value for the boundary //* //* @param[in] w3 - beta //* Beta Value for the boundary //* //* @returns //* None //* //* @remarks //* None //* //******************************************************************************* //*/ .global ih264_deblk_luma_vert_bs4_av8 ih264_deblk_luma_vert_bs4_av8: // STMFD sp!,{x12,x14} push_v_regs stp x19, x20, [sp, #-16]! sub x0, x0, #4 //pointer uc_edgePixel-4 mov x17, x0 //loading p3:p2:p1:p0:q0:q1:q2:q3 for every row ld1 {v0.8b}, [x0], x1 //row1 ld1 {v2.8b}, [x0], x1 //row2 ld1 {v4.8b}, [x0], x1 //row3 ld1 {v6.8b}, [x0], x1 //row4 ld1 {v8.8b}, [x0], x1 //row5 ld1 {v10.8b}, [x0], x1 //row6 ld1 {v12.8b}, [x0], x1 //row7 ld1 {v14.8b}, [x0], x1 //row8 ld1 {v1.8b}, [x0], x1 //row9 ld1 {v3.8b}, [x0], x1 //row10 ld1 {v5.8b}, [x0], x1 //row11 ld1 {v7.8b}, [x0], x1 //row12 ld1 {v9.8b}, [x0], x1 //row13 ld1 {v11.8b}, [x0], x1 //row14 ld1 {v13.8b}, [x0], x1 //row15 ld1 {v15.8b}, [x0], x1 //row16 //taking two 8x8 transposes //2X2 transposes trn1 v21.8b, v0.8b, v2.8b trn2 v2.8b, v0.8b, v2.8b //row1 &2 mov v0.8b, v21.8b trn1 v21.8b, v4.8b, v6.8b trn2 v6.8b, v4.8b, v6.8b //row3&row4 mov v4.8b, v21.8b trn1 v21.8b, v8.8b, v10.8b trn2 v10.8b, v8.8b, v10.8b //row5&6 mov v8.8b, v21.8b trn1 v21.8b, v12.8b, v14.8b trn2 v14.8b, v12.8b, v14.8b //row7 & 8 mov v12.8b, v21.8b trn1 v21.8b, v1.8b, v3.8b trn2 v3.8b, v1.8b, v3.8b //row9 &10 mov v1.8b , v21.8b trn1 v21.8b, v5.8b, v7.8b trn2 v7.8b, v5.8b, v7.8b //row11 & 12 mov v5.8b , v21.8b trn1 v21.8b, v9.8b, v11.8b trn2 v11.8b, v9.8b, v11.8b //row13 &14 mov v9.8b , v21.8b trn1 v21.8b, v13.8b, v15.8b trn2 v15.8b, v13.8b, v15.8b //row15 & 16 mov v13.8b , v21.8b //4x4 transposes trn1 v21.4h, v2.4h, v6.4h trn2 v6.4h, v2.4h, v6.4h //row2 & row4 mov v2.8b, v21.8b trn1 v21.4h, v10.4h, v14.4h trn2 v14.4h, v10.4h, v14.4h //row6 & row8 mov v10.8b , v21.8b trn1 v21.4h, v3.4h, v7.4h trn2 v7.4h, v3.4h, v7.4h //row10 & 12 mov v3.8b, v21.8b trn1 v21.4h, v11.4h, v15.4h trn2 v15.4h, v11.4h, v15.4h //row14 & row16 mov v11.8b, v21.8b trn1 v21.2s, v6.2s, v14.2s trn2 v14.2s, v6.2s, v14.2s //row4 & 8 mov v6.8b, v21.8b trn1 v21.2s, v7.2s, v15.2s trn2 v15.2s, v7.2s, v15.2s //row 12 & 16 mov v7.8b, v21.8b //now Q3 ->p0 and Q7->q3 trn1 v21.4h, v0.4h, v4.4h trn2 v4.4h, v0.4h, v4.4h //row1 & 3 mov v0.8b , v21.8b trn1 v21.4h, v8.4h, v12.4h trn2 v12.4h, v8.4h, v12.4h //row 5 & 7 mov v8.8b, v21.8b trn1 v21.4h, v1.4h, v5.4h trn2 v5.4h, v1.4h, v5.4h //row9 & row11 mov v1.8b, v21.8b trn1 v21.4h, v9.4h, v13.4h trn2 v13.4h, v9.4h, v13.4h //row13 & row15 mov v9.8b , v21.8b trn1 v21.2s, v0.2s, v8.2s trn2 v8.2s, v0.2s, v8.2s //row1 & row5 mov v0.8b, v21.8b trn1 v21.2s, v1.2s, v9.2s trn2 v9.2s, v1.2s, v9.2s //row9 & 13 mov v1.8b, v21.8b //now Q0->p3 & Q4->q0 //starting processing as p0 and q0 are now ready //now Q1->p2 & Q5->q1 mov v31.d[0], v14.d[0] mov v31.d[1], v15.d[0] trn1 v21.2s, v4.2s, v12.2s trn2 v12.2s, v4.2s, v12.2s //row3 & 7 mov v4.8b, v21.8b movi v28.8h, #2 trn1 v21.2s, v5.2s, v13.2s trn2 v13.2s, v5.2s, v13.2s //row11 & row15 mov v5.8b, v21.8b uaddl v16.8h, v6.8b, v8.8b //p0+q0 L trn1 v21.2s, v2.2s, v10.2s trn2 v10.2s, v2.2s, v10.2s //row2 &6 mov v2.8b, v21.8b uaddl v18.8h, v7.8b, v9.8b //p0+q0 H trn1 v21.2s, v3.2s, v11.2s trn2 v11.2s, v3.2s, v11.2s //row10&row14 mov v3.8b, v21.8b uaddw v20.8h, v16.8h , v4.8b //p0+q0+p1 L uaddw v22.8h, v18.8h , v5.8b //p0+q0+p1 H uaddl v24.8h, v2.8b, v10.8b //p2+q1 L uaddl v26.8h, v3.8b, v11.8b //p2+q1 H mla v24.8h, v20.8h , v28.8h //p2 + X2(p1) + X2(p0) + X2(q0) + q1 L mla v26.8h, v22.8h , v28.8h //p2 + X2(p1) + X2(p0) + X2(q0) + q1 H movi v28.16b, #2 uaddw v16.8h, v20.8h , v2.8b //p0+q0+p1+p2 L uaddw v18.8h, v22.8h , v3.8b //p0+q0+p1+p2 H dup v30.16b, w2 //duplicate alpha rshrn v20.8b, v16.8h, #2 //(p2 + p1 + p0 + q0 + 2) >> 2)L p1' rshrn v21.8b, v18.8h, #2 //(p2 + p1 + p0 + q0 + 2) >> 2)H p1' mov v20.d[1] , v21.d[0] mov v0.d[1] , v1.d[0] mov v2.d[1] , v3.d[0] mov v4.d[1] , v5.d[0] mov v6.d[1] , v7.d[0] mov v8.d[1] , v9.d[0] mov v10.d[1] , v11.d[0] mov v12.d[1] , v13.d[0] mov v14.d[1] , v15.d[0] uabd v22.16b , v6.16b, v8.16b usra v28.16b, v30.16b, #2 //alpha >>2 +2 uabd v30.16b , v2.16b, v6.16b rshrn v24.8b, v24.8h, #3 //((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3) L p0' rshrn v25.8b, v26.8h, #3 //((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3) H p0' mov v24.d[1] , v25.d[0] dup v26.16b, w3 //beta cmhi v28.16b, v28.16b , v22.16b //ABS(p0 - q0) <((Alpha >>2) + 2) uaddl v22.8h, v6.8b, v10.8b //p0+q1 L cmhi v14.16b, v26.16b , v30.16b //beta>Ap uaddl v30.8h, v7.8b, v11.8b //p0+q1 H uaddw v22.8h, v22.8h , v4.8b //p0+q1+p1 L uaddw v30.8h, v30.8h , v5.8b //p0+q1+p1 H uaddw v22.8h, v22.8h , v4.8b //p0+q1+2*p1 L uaddw v30.8h, v30.8h , v5.8b //p0+q1+2*p1 H and v14.16b, v14.16b , v28.16b //(Ap < Beta && ABS(p0 - q0) <((Alpha >>2) + 2) rshrn v22.8b, v22.8h, #2 //((X2(p1) + p0 + q1 + 2) >> 2) L p0" rshrn v23.8b, v30.8h, #2 //((X2(p1) + p0 + q1 + 2) >> 2) H p0" mov v22.d[1] , v23.d[0] uaddl v30.8h, v2.8b, v0.8b //p2+p3 L bif v24.16b, v22.16b , v14.16b //p0' or p0 " uaddl v22.8h, v3.8b, v1.8b //p2+p3 H add v30.8h, v30.8h , v30.8h //2*(p2+p3) L add v22.8h, v22.8h , v22.8h //2*(p2+p3)H add v16.8h, v16.8h , v30.8h //(X2(p3) + X3(p2) + p1 + p0 + q0) L add v18.8h, v18.8h , v22.8h //(X2(p3) + X3(p2) + p1 + p0 + q0) H uabd v30.16b , v12.16b, v8.16b uabd v22.16b , v10.16b, v8.16b rshrn v16.8b, v16.8h, #3 //((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); L p2' rshrn v17.8b, v18.8h, #3 //((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); H p2' mov v16.d[1] , v17.d[0] uabd v18.16b , v4.16b, v6.16b cmhi v30.16b, v26.16b , v30.16b //Aq < Beta cmhs v22.16b, v22.16b, v26.16b cmhs v18.16b, v18.16b, v26.16b dup v26.16b, w2 //duplicate alpha and v30.16b, v30.16b , v28.16b //(Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) uabd v28.16b , v6.16b, v8.16b orr v22.16b, v22.16b , v18.16b //ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta uaddl v18.8h, v6.8b, v8.8b //p0+q0 L cmhs v28.16b, v28.16b, v26.16b uaddl v26.8h, v7.8b, v9.8b //p0+q0 H uaddw v18.8h, v18.8h , v10.8b //p0+q0+q1 L orr v22.16b, v22.16b , v28.16b //ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta||ABS(p0 - q0) >= Alpha uaddw v26.8h, v26.8h , v11.8b //p0+q0+q1 H bic v14.16b, v14.16b , v22.16b //final condn for p's movi v28.16b, #2 bif v6.16b, v24.16b , v22.16b //final p0 bit v2.16b, v16.16b , v14.16b //final p2 bif v20.16b, v4.16b , v14.16b //final p1 mov v7.d[0] , v6.d[1] mov v3.d[0] , v2.d[1] mov v21.d[0] , v20.d[1] uaddl v24.8h, v8.8b, v4.8b //q0+p1 L umlal v24.8h, v10.8b, v28.8b //X2(q1) + q0 + p1 L uaddl v16.8h, v9.8b, v5.8b //q0+p1 H umlal v16.8h, v11.8b, v28.8b //X2(q1) + q0 + p1 H movi v28.8h, #2 uaddl v14.8h, v4.8b, v12.8b //p1+q2 L mla v14.8h, v18.8h , v28.8h //p1 + X2(p0) + X2(q0) + X2(q1) + q2L uaddl v4.8h, v5.8b, v13.8b //p1+q2H mla v4.8h, v26.8h , v28.8h //p1 + X2(p0) + X2(q0) + X2(q1) + q2H rshrn v24.8b, v24.8h, #2 //(X2(q1) + q0 + p1 + 2) >> 2; L q0' rshrn v25.8b, v16.8h, #2 //(X2(q1) + q0 + p1 + 2) >> 2; H q0' mov v24.d[1] , v25.d[0] uaddw v18.8h, v18.8h , v12.8b //p0 + q0 + q1 + q2 L uaddw v26.8h, v26.8h , v13.8b //p0 + q0 + q1 + q2 H rshrn v16.8b, v14.8h, #3 //(p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3 L qo" mov v14.16b, v31.16b rshrn v17.8b, v4.8h, #3 //(p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3 H qo" mov v16.d[1] , v17.d[0] rshrn v4.8b, v18.8h, #2 //p0 + q0 + q1 + q2 + 2)>>2 L q1' rshrn v5.8b, v26.8h, #2 //p0 + q0 + q1 + q2 + 2)>>2 H q1' mov v4.d[1] , v5.d[0] bit v24.16b, v16.16b , v30.16b //q0' or q0" bic v30.16b, v30.16b , v22.16b //final condn for q's trn1 v31.8b, v0.8b, v2.8b trn2 v2.8b, v0.8b, v2.8b //row1 &2 mov v0.8b, v31.8b bit v10.16b, v4.16b , v30.16b mov v11.d[0] , v10.d[1] mov v25.d[0] , v24.d[1] mov v31.d[0] , v30.d[1] trn1 v31.8b, v1.8b, v3.8b trn2 v3.8b, v1.8b, v3.8b //row9 &10 mov v1.8b, v31.8b uaddl v16.8h, v12.8b, v14.8b //q2+q3 L trn1 v31.8b, v20.8b, v6.8b trn2 v6.8b, v20.8b, v6.8b //row3&row4 mov v20.8b , v31.8b uaddl v4.8h, v13.8b, v15.8b //q2+q3 H trn1 v31.8b, v21.8b, v7.8b trn2 v7.8b, v21.8b, v7.8b //row11 & 12 mov v21.8b , v31.8b mla v18.8h, v16.8h , v28.8h //X2(q3) + X3(q2) + q1 + q0 + p0 L trn1 v31.4h, v2.4h, v6.4h trn2 v6.4h, v2.4h, v6.4h //row2 & row4 mov v2.8b, v31.8b mla v26.8h, v4.8h , v28.8h //X2(q3) + X3(q2) + q1 + q0 + p0 H trn1 v31.4h, v3.4h, v7.4h trn2 v7.4h, v3.4h, v7.4h //row10 & 12 mov v3.8b , v31.8b bif v8.16b, v24.16b , v22.16b //final q0 mov v9.d[0] , v8.d[1] trn1 v31.4h, v0.4h, v20.4h trn2 v20.4h, v0.4h, v20.4h //row1 & 3 mov v0.8b , v31.8b rshrn v18.8b, v18.8h, #3 //(X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; L trn1 v31.4h, v1.4h, v21.4h trn2 v21.4h, v1.4h, v21.4h //row9 & row11 mov v1.8b, v31.8b rshrn v19.8b, v26.8h, #3 //(X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; H mov v18.d[1] , v19.d[0] trn1 v31.8b, v8.8b, v10.8b trn2 v10.8b, v8.8b, v10.8b //row5&6 mov v8.8b, v31.8b bit v12.16b, v18.16b , v30.16b //final q2 mov v13.d[0] , v12.d[1] trn1 v31.8b, v9.8b, v11.8b trn2 v11.8b, v9.8b, v11.8b //row13 &14 mov v9.8b, v31.8b trn1 v31.8b, v12.8b, v14.8b trn2 v14.8b, v12.8b, v14.8b //row7 & 8 mov v12.8b, v31.8b trn1 v31.8b, v13.8b, v15.8b trn2 v15.8b, v13.8b, v15.8b //row15 & 16 mov v13.8b , v31.8b trn1 v31.4h, v10.4h, v14.4h trn2 v14.4h, v10.4h, v14.4h //row6 & row8 mov v10.8b, v31.8b trn1 v31.4h, v11.4h, v15.4h trn2 v15.4h, v11.4h, v15.4h //row14 & row16 mov v11.8b, v31.8b //now Q3 ->p0 and Q7->q3 trn1 v31.4h, v8.4h, v12.4h trn2 v12.4h, v8.4h, v12.4h //row 5 & 7 mov v8.8b, v31.8b trn1 v31.4h, v9.4h, v13.4h trn2 v13.4h, v9.4h, v13.4h //row13 & row15 mov v9.8b, v31.8b sub x0, x0, x1, lsl#4 //restore pointer trn1 v31.2s, v6.2s, v14.2s trn2 v14.2s, v6.2s, v14.2s //row4 & 8 mov v6.8b , v31.8b trn1 v31.2s, v7.2s, v15.2s trn2 v15.2s, v7.2s, v15.2s //row 12 & 16 mov v7.8b, v31.8b trn1 v31.2s, v0.2s, v8.2s trn2 v8.2s, v0.2s, v8.2s //row1 & row5 mov v0.8b , v31.8b trn1 v31.2s, v1.2s, v9.2s trn2 v9.2s, v1.2s, v9.2s //row9 & 13 mov v1.8b , v31.8b trn1 v31.2s, v2.2s, v10.2s trn2 v10.2s, v2.2s, v10.2s //row2 &6 mov v2.8b , v31.8b trn1 v31.2s, v3.2s, v11.2s trn2 v11.2s, v3.2s, v11.2s //row10&row14 mov v3.8b , v31.8b trn1 v31.2s, v20.2s, v12.2s trn2 v12.2s, v20.2s, v12.2s //row3 & 7 mov v20.8b , v31.8b trn1 v31.2s, v21.2s, v13.2s trn2 v13.2s, v21.2s, v13.2s //row11 & row15 mov v21.8b, v31.8b st1 {v0.8b}, [x0], x1 //row1 st1 {v2.8b}, [x0], x1 //row2 st1 {v20.8b}, [x0], x1 //row3 st1 {v6.8b}, [x0], x1 //row4 st1 {v8.8b}, [x0], x1 //row5 st1 {v10.8b}, [x0], x1 //row6 st1 {v12.8b}, [x0], x1 //row7 st1 {v14.8b}, [x0], x1 //row8 st1 {v1.8b}, [x0], x1 //row9 st1 {v3.8b}, [x0], x1 //row10 st1 {v21.8b}, [x0], x1 //row11 st1 {v7.8b}, [x0], x1 //row12 st1 {v9.8b}, [x0], x1 //row13 st1 {v11.8b}, [x0], x1 //row14 st1 {v13.8b}, [x0], x1 //row15 st1 {v15.8b}, [x0], x1 //row16 // LDMFD sp!,{x12,pc} ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_default_weighted_pred_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_default_weighted_pred_av8.s //* //* @brief //* Contains function definitions for default weighted prediction. //* //* @author //* Kaushik Senthoor R //* //* @par List of Functions: //* //* - ih264_default_weighted_pred_luma_av8() //* - ih264_default_weighted_pred_chroma_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ //******************************************************************************* //* @function //* ih264_default_weighted_pred_luma_av8() //* //* @brief //* This routine performs the default weighted prediction as described in sec //* 8.4.2.3.1 titled "Default weighted sample prediction process" for luma. //* //* @par Description: //* This function gets two ht x wd blocks, calculates their rounded-average and //* stores it in the destination block. //* //* @param[in] puc_src1: //* UWORD8 Pointer to the buffer containing the first input block. //* //* @param[in] puc_src2: //* UWORD8 Pointer to the buffer containing the second input block. //* //* @param[out] puc_dst //* UWORD8 pointer to the destination where the output block is stored. //* //* @param[in] src_strd1 //* Stride of the first input buffer //* //* @param[in] src_strd2 //* Stride of the second input buffer //* //* @param[in] dst_strd //* Stride of the destination buffer //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* None //* //* @remarks //* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). //* //******************************************************************************* //*/ //void ih264_default_weighted_pred_luma_av8(UWORD8 *puc_src1, // UWORD8 *puc_src2, // UWORD8 *puc_dst, // WORD32 src_strd1, // WORD32 src_strd2, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd) // //**************Variables Vs Registers***************************************** // x0 => puc_src1 // x1 => puc_src2 // x2 => puc_dst // w3 => src_strd1 // w4 => src_strd2 // w5 => dst_strd // w6 => ht // w7 => wd // .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_default_weighted_pred_luma_av8 ih264_default_weighted_pred_luma_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 cmp w7, #16 beq loop_16 //branch if wd is 16 cmp w7, #8 beq loop_8 //branch if wd is 8 loop_4: //each iteration processes four rows ld1 {v0.s}[0], [x0], x3 //load row 1 in source 1 ld1 {v0.s}[1], [x0], x3 //load row 2 in source 1 ld1 {v2.s}[0], [x1], x4 //load row 1 in source 2 ld1 {v2.s}[1], [x1], x4 //load row 2 in source 2 ld1 {v1.s}[0], [x0], x3 //load row 3 in source 1 ld1 {v1.s}[1], [x0], x3 //load row 4 in source 1 urhadd v0.8b, v0.8b , v2.8b ld1 {v3.s}[0], [x1], x4 //load row 3 in source 2 ld1 {v3.s}[1], [x1], x4 //load row 4 in source 2 subs w6, w6, #4 //decrement ht by 4 st1 {v0.s}[0], [x2], x5 //load row 1 in destination st1 {v0.s}[1], [x2], x5 //load row 2 in destination urhadd v1.8b, v1.8b , v3.8b st1 {v1.s}[0], [x2], x5 //load row 3 in destination st1 {v1.s}[1], [x2], x5 //load row 4 in destination bgt loop_4 //if greater than 0 repeat the loop again b end_loops loop_8: //each iteration processes four rows ld1 {v0.8b}, [x0], x3 //load row 1 in source 1 ld1 {v4.8b}, [x1], x4 //load row 1 in source 2 ld1 {v1.8b}, [x0], x3 //load row 2 in source 1 ld1 {v5.8b}, [x1], x4 //load row 2 in source 2 ld1 {v2.8b}, [x0], x3 //load row 3 in source 1 urhadd v0.16b, v0.16b , v4.16b urhadd v1.16b, v1.16b , v5.16b ld1 {v6.8b}, [x1], x4 //load row 3 in source 2 ld1 {v3.8b}, [x0], x3 //load row 4 in source 1 urhadd v2.8b, v2.8b , v6.8b ld1 {v7.8b}, [x1], x4 //load row 4 in source 2 subs w6, w6, #4 //decrement ht by 4 st1 {v0.8b}, [x2], x5 //load row 1 in destination urhadd v3.8b, v3.8b , v7.8b st1 {v1.8b}, [x2], x5 //load row 2 in destination st1 {v2.8b}, [x2], x5 //load row 3 in destination st1 {v3.8b}, [x2], x5 //load row 4 in destination bgt loop_8 //if greater than 0 repeat the loop again b end_loops loop_16: //each iteration processes eight rows ld1 {v0.8b, v1.8b}, [x0], x3 //load row 1 in source 1 ld1 {v16.8b, v17.8b}, [x1], x4 //load row 1 in source 2 ld1 {v2.8b, v3.8b}, [x0], x3 //load row 2 in source 1 ld1 {v18.8b, v19.8b}, [x1], x4 //load row 2 in source 2 urhadd v0.16b, v0.16b , v16.16b urhadd v1.16b, v1.16b , v17.16b ld1 {v4.8b, v5.8b}, [x0], x3 //load row 3 in source 1 ld1 {v20.8b, v21.8b}, [x1], x4 //load row 3 in source 2 urhadd v2.16b, v2.16b , v18.16b urhadd v3.16b, v3.16b , v19.16b ld1 {v6.8b, v7.8b}, [x0], x3 //load row 4 in source 1 ld1 {v22.8b, v23.8b}, [x1], x4 //load row 4 in source 2 urhadd v4.16b, v4.16b , v20.16b urhadd v5.16b, v5.16b , v21.16b ld1 {v8.8b, v9.8b}, [x0], x3 //load row 5 in source 1 ld1 {v24.8b, v25.8b}, [x1], x4 //load row 5 in source 2 urhadd v6.16b, v6.16b , v22.16b urhadd v7.16b, v7.16b , v23.16b ld1 {v10.8b, v11.8b}, [x0], x3 //load row 6 in source 1 ld1 {v26.8b, v27.8b}, [x1], x4 //load row 6 in source 2 urhadd v8.16b, v8.16b , v24.16b urhadd v9.16b, v9.16b , v25.16b ld1 {v12.8b, v13.8b}, [x0], x3 //load row 7 in source 1 ld1 {v28.8b, v29.8b}, [x1], x4 //load row 7 in source 2 urhadd v10.16b, v10.16b , v26.16b urhadd v11.16b, v11.16b , v27.16b ld1 {v14.8b, v15.8b}, [x0], x3 //load row 8 in source 1 ld1 {v30.8b, v31.8b}, [x1], x4 //load row 8 in source 2 urhadd v12.16b, v12.16b , v28.16b urhadd v13.16b, v13.16b , v29.16b st1 {v0.8b, v1.8b}, [x2], x5 //load row 1 in destination st1 {v2.8b, v3.8b}, [x2], x5 //load row 2 in destination urhadd v14.16b, v14.16b , v30.16b urhadd v15.16b, v15.16b , v31.16b st1 {v4.8b, v5.8b}, [x2], x5 //load row 3 in destination st1 {v6.8b, v7.8b}, [x2], x5 //load row 4 in destination subs w6, w6, #8 //decrement ht by 8 st1 {v8.8b, v9.8b}, [x2], x5 //load row 5 in destination st1 {v10.8b, v11.8b}, [x2], x5 //load row 6 in destination st1 {v12.8b, v13.8b}, [x2], x5 //load row 7 in destination st1 {v14.8b, v15.8b}, [x2], x5 //load row 8 in destination bgt loop_16 //if greater than 0 repeat the loop again end_loops: // LDMFD sp!,{x4-x7,x15} //Reload the registers from sp ldp x19, x20, [sp], #16 pop_v_regs ret //******************************************************************************* //* @function //* ih264_default_weighted_pred_chroma_av8() //* //* @brief //* This routine performs the default weighted prediction as described in sec //* 8.4.2.3.1 titled "Default weighted sample prediction process" for chroma. //* //* @par Description: //* This function gets two ht x wd blocks, calculates their rounded-average and //* stores it in the destination block for U and V. //* //* @param[in] puc_src1: //* UWORD8 Pointer to the buffer containing the first input block. //* //* @param[in] puc_src2: //* UWORD8 Pointer to the buffer containing the second input block. //* //* @param[out] puc_dst //* UWORD8 pointer to the destination where the output block is stored. //* //* @param[in] src_strd1 //* Stride of the first input buffer //* //* @param[in] src_strd2 //* Stride of the second input buffer //* //* @param[in] dst_strd //* Stride of the destination buffer //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* None //* //* @remarks //* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). //* //******************************************************************************* //*/ //void ih264_default_weighted_pred_chroma_av8(UWORD8 *puc_src1, // UWORD8 *puc_src2, // UWORD8 *puc_dst, // WORD32 src_strd1, // WORD32 src_strd2, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd) // //**************Variables Vs Registers***************************************** // x0 => puc_src1 // x1 => puc_src2 // x2 => puc_dst // w3 => src_strd1 // w4 => src_strd2 // w5 => dst_strd // w6 => ht // w7 => wd // .global ih264_default_weighted_pred_chroma_av8 ih264_default_weighted_pred_chroma_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 cmp w7, #8 beq loop_8_uv //branch if wd is 8 cmp w7, #4 beq loop_4_uv //branch if wd is 4 loop_2_uv: //each iteration processes two rows ld1 {v0.s}[0], [x0], x3 //load row 1 in source 1 ld1 {v0.s}[1], [x0], x3 //load row 2 in source 1 ld1 {v1.s}[0], [x1], x4 //load row 1 in source 2 ld1 {v1.s}[1], [x1], x4 //load row 2 in source 2 urhadd v0.8b, v0.8b , v1.8b subs w6, w6, #2 //decrement ht by 2 st1 {v0.s}[0], [x2], x5 //load row 1 in destination st1 {v0.s}[1], [x2], x5 //load row 2 in destination bgt loop_2_uv //if greater than 0 repeat the loop again b end_loops_uv loop_4_uv: //each iteration processes two rows ld1 {v0.8b}, [x0], x3 //load row 1 in source 1 ld1 {v2.8b}, [x1], x4 //load row 1 in source 2 ld1 {v1.8b}, [x0], x3 //load row 2 in source 1 urhadd v0.8b, v0.8b , v2.8b ld1 {v3.8b}, [x1], x4 //load row 2 in source 2 urhadd v1.8b, v1.8b , v3.8b st1 {v0.8b}, [x2], x5 //load row 1 in destination subs w6, w6, #2 //decrement ht by 2 st1 {v1.8b}, [x2], x5 //load row 2 in destination bgt loop_4_uv //if greater than 0 repeat the loop again b end_loops_uv loop_8_uv: //each iteration processes four rows ld1 {v0.8b, v1.8b}, [x0], x3 //load row 1 in source 1 ld1 {v8.8b, v9.8b}, [x1], x4 //load row 1 in source 2 ld1 {v2.8b, v3.8b}, [x0], x3 //load row 2 in source 1 urhadd v0.16b, v0.16b , v8.16b urhadd v1.16b, v1.16b , v9.16b ld1 {v10.8b, v11.8b}, [x1], x4 //load row 2 in source 2 ld1 {v4.8b, v5.8b}, [x0], x3 //load row 3 in source 1 urhadd v2.16b, v2.16b , v10.16b urhadd v3.16b, v3.16b , v11.16b ld1 {v12.8b, v13.8b}, [x1], x4 //load row 3 in source 2 ld1 {v6.8b, v7.8b}, [x0], x3 //load row 4 in source 1 urhadd v4.16b, v4.16b , v12.16b urhadd v5.16b, v5.16b , v13.16b ld1 {v14.8b, v15.8b}, [x1], x4 //load row 4 in source 2 st1 {v0.8b, v1.8b}, [x2], x5 //load row 1 in destination urhadd v6.16b, v6.16b , v14.16b urhadd v7.16b, v7.16b , v15.16b st1 {v2.8b, v3.8b}, [x2], x5 //load row 2 in destination subs w6, w6, #4 //decrement ht by 4 st1 {v4.8b, v5.8b}, [x2], x5 //load row 3 in destination st1 {v6.8b, v7.8b}, [x2], x5 //load row 4 in destination bgt loop_8_uv //if greater than 0 repeat the loop again end_loops_uv: ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_ihadamard_scaling_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** // ******************************************************************************* // * @file // * ih264_ihadamard_scaling_av8.s // * // * @brief // * Contains function definitions for inverse hadamard transform on 4x4 DC outputs // * of 16x16 intra-prediction // * // * @author // * Mohit // * // * @par List of Functions: // * - ih264_ihadamard_scaling_4x4_av8() // * // * @remarks // * None // * .include "ih264_neon_macros.s" // ******************************************************************************* // */ // * @brief This function performs a 4x4 inverse hadamard transform on the 4x4 DC coefficients // * of a 16x16 intra prediction macroblock, and then performs scaling. // * prediction buffer // * // * @par Description: // * The DC coefficients pass through a 2-stage inverse hadamard transform. // * This inverse transformed content is scaled to based on Qp value. // * // * @param[in] pi2_src // * input 4x4 block of DC coefficients // * // * @param[out] pi2_out // * output 4x4 block // * // * @param[in] pu2_iscal_mat // * pointer to scaling list // * // * @param[in] pu2_weigh_mat // * pointer to weight matrix // * // * @param[in] u4_qp_div_6 // * Floor (qp/6) // * // * @param[in] pi4_tmp // * temporary buffer of size 1*16 // * // * @returns none // * // * @remarks none // * // ******************************************************************************* // */ // * // ******************************************************************************* // */ // void ih264_ihadamard_scaling_4x4(word16* pi2_src, // word16* pi2_out, // const uword16 *pu2_iscal_mat, // const uword16 *pu2_weigh_mat, // uword32 u4_qp_div_6, // word32* pi4_tmp) //**************variables vs registers***************************************** //x0 => *pi2_src //x1 => *pi2_out //x2 => *pu2_iscal_mat //x3 => *pu2_weigh_mat //x4=> u4_qp_div_6 .text .p2align 2 .global ih264_ihadamard_scaling_4x4_av8 ih264_ihadamard_scaling_4x4_av8: //only one shift is done in horizontal inverse because, //if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value //if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 push_v_regs //=======================inverse hadamard transform================================ ld4 {v0.4h-v3.4h}, [x0] //load x4,x5,x6,x7 dup v14.4s, w4 // populate the u4_qp_div_6 ld1 {v15.h}[0], [x3] // pu2_weigh_mat ld1 {v16.h}[0], [x2] //pu2_iscal_mat saddl v4.4s, v0.4h, v3.4h //x0 = x4 + x7 saddl v5.4s, v1.4h, v2.4h //x1 = x5 + x6 ssubl v6.4s, v1.4h, v2.4h //x2 = x5 - x6 ssubl v7.4s, v0.4h, v3.4h //x3 = x4 - x7 add v0.4s, v4.4s, v5.4s //pi4_tmp_ptr[0] = x0 + x1 add v1.4s, v7.4s, v6.4s //pi4_tmp_ptr[1] = x3 + x2 sub v2.4s, v4.4s, v5.4s //pi4_tmp_ptr[2] = x0 - x1 sub v3.4s, v7.4s, v6.4s //pi4_tmp_ptr[3] = x3 - x2 umull v15.4s, v15.4h, v16.4h dup v15.4s, v15.s[0] //pu2_weigh_mat[0]*pu2_iscal_mat[0] //transpose trn1 v4.4s, v0.4s, v1.4s trn2 v5.4s, v0.4s, v1.4s trn1 v6.4s, v2.4s, v3.4s trn2 v7.4s, v2.4s, v3.4s trn1 v0.2d, v4.2d, v6.2d trn2 v2.2d, v4.2d, v6.2d trn1 v1.2d, v5.2d, v7.2d trn2 v3.2d, v5.2d, v7.2d //end transpose add v4.4s, v0.4s, v3.4s //x0 = x4+x7 add v5.4s, v1.4s, v2.4s //x1 = x5+x6 sub v6.4s, v1.4s, v2.4s //x2 = x5-x6 sub v7.4s, v0.4s, v3.4s //x3 = x4-x7 add v0.4s, v4.4s, v5.4s //pi4_tmp_ptr[0] = x0 + x1 add v1.4s, v7.4s, v6.4s //pi4_tmp_ptr[1] = x3 + x2 sub v2.4s, v4.4s, v5.4s //pi4_tmp_ptr[2] = x0 - x1 sub v3.4s, v7.4s, v6.4s //pi4_tmp_ptr[3] = x3 - x2 mul v0.4s, v0.4s, v15.4s // q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 mul v1.4s, v1.4s, v15.4s // q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 mul v2.4s, v2.4s, v15.4s // q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 mul v3.4s, v3.4s, v15.4s // q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 sshl v0.4s, v0.4s, v14.4s // q0 = q[i] = (p[i] << (qp/6)) where i = 0..3 sshl v1.4s, v1.4s, v14.4s // q1 = q[i] = (p[i] << (qp/6)) where i = 4..7 sshl v2.4s, v2.4s, v14.4s // q2 = q[i] = (p[i] << (qp/6)) where i = 8..11 sshl v3.4s, v3.4s, v14.4s // q3 = q[i] = (p[i] << (qp/6)) where i = 12..15 sqrshrn v0.4h, v0.4s, #6 // d0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 sqrshrn v1.4h, v1.4s, #6 // d1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 sqrshrn v2.4h, v2.4s, #6 // d2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 sqrshrn v3.4h, v3.4s, #6 // d3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 st1 {v0.4h-v3.4h}, [x1] //store the result pop_v_regs ret // ******************************************************************************* // */ // * @brief This function performs a 2x2 inverse hadamard transform for chroma block // * // * @par Description: // * The DC coefficients pass through a 2-stage inverse hadamard transform. // * This inverse transformed content is scaled to based on Qp value. // * Both DC blocks of U and v blocks are processesd // * // * @param[in] pi2_src // * input 1x8 block of ceffs. First 4 are from U and next from V // * // * @param[out] pi2_out // * output 1x8 block // * // * @param[in] pu2_iscal_mat // * pointer to scaling list // * // * @param[in] pu2_weigh_mat // * pointer to weight matrix // * // * @param[in] u4_qp_div_6 // * Floor (qp/6) // * // * @returns none // * // * @remarks none // * // ******************************************************************************* // */ // * // ******************************************************************************* // */ // void ih264_ihadamard_scaling_2x2_uv(WORD16* pi2_src, // WORD16* pi2_out, // const UWORD16 *pu2_iscal_mat, // const UWORD16 *pu2_weigh_mat, // UWORD32 u4_qp_div_6, .global ih264_ihadamard_scaling_2x2_uv_av8 ih264_ihadamard_scaling_2x2_uv_av8: //Registers used // x0 : *pi2_src // x1 : *pi2_out // x2 : *pu2_iscal_mat // x3 : *pu2_weigh_mat // x4 : u4_qp_div_6 push_v_regs ld1 {v26.h}[0], [x2] ld1 {v27.h}[0], [x3] sub w4, w4, #5 //qp/6 - 4 dup v28.4s, w4 //load qp/6 ld2 {v0.4h, v1.4h}, [x0] //load 8 dc coeffs //i2_x4,i2_x6,i2_y4,i1_y6 -> d0 //i2_x5,i2_x7,i2_y5,i1_y6 -> d1 saddl v2.4s, v0.4h, v1.4h //i4_x0 = i4_x4 + i4_x5;...x2 ssubl v4.4s, v0.4h, v1.4h //i4_x1 = i4_x4 - i4_x5;...x3 umull v30.4s, v26.4h, v27.4h //pu2_iscal_mat[0]*pu2_weigh_mat[0] dup v30.4s, v30.s[0] trn1 v0.4s, v2.4s, v4.4s trn2 v1.4s, v2.4s, v4.4s //i4_x0 i4_x1 -> q1 add v2.4s, v0.4s, v1.4s //i4_x4 = i4_x0+i4_x2;.. i4_x5 sub v3.4s, v0.4s, v1.4s //i4_x6 = i4_x0-i4_x2;.. i4_x7 mul v2.4s, v2.4s, v30.4s mul v3.4s, v3.4s, v30.4s sshl v2.4s, v2.4s, v28.4s sshl v3.4s, v3.4s, v28.4s xtn v0.4h, v2.4s //i4_x4 i4_x5 i4_y4 i4_y5 xtn v1.4h, v3.4s //i4_x6 i4_x7 i4_y6 i4_y7 st2 {v0.4s-v1.4s}, [x1] pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_inter_pred_chroma_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_inter_pred_chroma_av8.s //* //* @brief //* Contains function definitions for inter prediction interpolation. //* //* @author //* Ittaim //* //* @par List of Functions: //* //* - ih264_inter_pred_chroma_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_inter_pred_filters.c // ///** ///** ///** // ///** //******************************************************************************* //* //* @brief //* Interprediction chroma filter //* //* @par Description: //* Applies filtering to chroma samples as mentioned in //* sec 8.4.2.2.2 titled "chroma sample interpolation process" //* //* @param[in] pu1_src //* UWORD8 pointer to the source containing alternate U and V samples //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in]uc_dx //* dx value where the sample is to be produced(refer sec 8.4.2.2.2 ) //* //* @param[in] uc_dy //* dy value where the sample is to be produced(refer sec 8.4.2.2.2 ) //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //void ih264_inter_pred_chroma(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 u1_dx, // WORD32 u1_dy, // WORD32 ht, // WORD32 wd) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => u1_dx // w5 => u1_dy // w6 => height // w7 => width // .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_inter_pred_chroma_av8 ih264_inter_pred_chroma_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x2, w2 sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 sxtw x6, w6 sxtw x7, w7 sub x20, x4, #8 //8-u1_dx neg x8, x20 sub x20, x5, #8 //8-u1_dy neg x9, x20 mul x10, x8, x9 // mul x11, x4, x9 // dup v28.8b, w10 dup v29.8b, w11 mul x10, x8, x5 // mul x11, x4, x5 // dup v30.8b, w10 dup v31.8b, w11 subs x12, x7, #2 //if wd=4 branch to loop_4 beq loop_2 subs x12, x7, #4 //if wd=8 branch to loop_8 beq loop_4 loop_8: ld1 {v0.8b, v1.8b, v2.8b}, [x0], x2 //// Load row0 ; ext v3.8b, v0.8b , v1.8b , #2 ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row1; umull v20.8h, v0.8b, v28.8b ext v8.8b, v5.8b , v6.8b , #2 umlal v20.8h, v3.8b, v29.8b ext v9.8b, v6.8b , v7.8b , #2 umlal v20.8h, v5.8b, v30.8b ext v4.8b, v1.8b , v2.8b , #2 umlal v20.8h, v8.8b, v31.8b sqrshrun v26.8b, v20.8h, #6 umull v22.8h, v1.8b, v28.8b ld1 {v10.8b, v11.8b, v12.8b}, [x0], x2 //// Load row2 ; umlal v22.8h, v4.8b, v29.8b ext v13.8b, v10.8b , v11.8b , #2 umlal v22.8h, v6.8b, v30.8b ext v14.8b, v11.8b , v12.8b , #2 umlal v22.8h, v9.8b, v31.8b sqrshrun v27.8b, v22.8h, #6 umull v24.8h, v5.8b, v28.8b st1 { v26.8b, v27.8b}, [x1], x3 ////Store dest row umlal v24.8h, v8.8b, v29.8b ld1 {v0.8b, v1.8b, v2.8b}, [x0], x2 //// Load row3 ; umlal v24.8h, v10.8b, v30.8b ext v3.8b, v0.8b , v1.8b , #2 umlal v24.8h, v13.8b, v31.8b ext v4.8b, v1.8b , v2.8b , #2 umull v16.8h, v6.8b, v28.8b sqrshrun v18.8b, v24.8h, #6 umlal v16.8h, v9.8b, v29.8b umlal v16.8h, v11.8b, v30.8b umlal v16.8h, v14.8b, v31.8b sqrshrun v19.8b, v16.8h, #6 st1 {v18.8b, v19.8b}, [x1], x3 // store row 1 umull v20.8h, v10.8b, v28.8b umlal v20.8h, v13.8b, v29.8b umlal v20.8h, v0.8b, v30.8b umlal v20.8h, v3.8b, v31.8b sqrshrun v26.8b, v20.8h, #6 umull v24.8h, v11.8b, v28.8b ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row4; umlal v24.8h, v14.8b, v29.8b ext v8.8b, v5.8b , v6.8b , #2 umlal v24.8h, v1.8b, v30.8b ext v9.8b, v6.8b , v7.8b , #2 umlal v24.8h, v4.8b, v31.8b umull v20.8h, v0.8b, v28.8b sqrshrun v27.8b, v24.8h, #6 umlal v20.8h, v3.8b, v29.8b st1 { v26.8b, v27.8b}, [x1], x3 ////Store dest row2 umlal v20.8h, v5.8b, v30.8b umlal v20.8h, v8.8b, v31.8b umull v22.8h, v1.8b, v28.8b umlal v22.8h, v4.8b, v29.8b umlal v22.8h, v6.8b, v30.8b sqrshrun v26.8b, v20.8h, #6 umlal v22.8h, v9.8b, v31.8b subs x12, x6, #4 sqrshrun v27.8b, v22.8h, #6 st1 { v26.8b, v27.8b}, [x1], x3 ////Store dest row3 beq end_func //If ht=4 ld1 {v10.8b, v11.8b, v12.8b}, [x0], x2 //// Load row5 ext v13.8b, v10.8b , v11.8b , #2 umull v24.8h, v5.8b, v28.8b ext v14.8b, v11.8b , v12.8b , #2 ld1 {v0.8b, v1.8b, v2.8b}, [x0], x2 //// Load row6; umlal v24.8h, v8.8b, v29.8b umlal v24.8h, v10.8b, v30.8b umlal v24.8h, v13.8b, v31.8b ext v3.8b, v0.8b , v1.8b , #2 umull v16.8h, v6.8b, v28.8b sqrshrun v18.8b, v24.8h, #6 umlal v16.8h, v9.8b, v29.8b umlal v16.8h, v11.8b, v30.8b umlal v16.8h, v14.8b, v31.8b ext v4.8b, v1.8b , v2.8b , #2 sqrshrun v19.8b, v16.8h, #6 st1 { v18.8b, v19.8b}, [x1], x3 // store row 4 umull v20.8h, v10.8b, v28.8b umlal v20.8h, v13.8b, v29.8b umlal v20.8h, v0.8b, v30.8b umlal v20.8h, v3.8b, v31.8b ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row7; sqrshrun v26.8b, v20.8h, #6 umull v24.8h, v11.8b, v28.8b umlal v24.8h, v14.8b, v29.8b ext v8.8b, v5.8b , v6.8b , #2 umlal v24.8h, v1.8b, v30.8b umlal v24.8h, v4.8b, v31.8b ext v9.8b, v6.8b , v7.8b , #2 sqrshrun v27.8b, v24.8h, #6 st1 {v26.8b, v27.8b}, [x1], x3 ////Store dest row5 umull v20.8h, v0.8b, v28.8b umlal v20.8h, v3.8b, v29.8b umlal v20.8h, v5.8b, v30.8b umlal v20.8h, v8.8b, v31.8b ld1 {v10.8b, v11.8b, v12.8b}, [x0], x2 //// Load row8 ; sqrshrun v26.8b, v20.8h, #6 umull v22.8h, v1.8b, v28.8b umlal v22.8h, v4.8b, v29.8b umlal v22.8h, v6.8b, v30.8b ext v13.8b, v10.8b , v11.8b , #2 umlal v22.8h, v9.8b, v31.8b ext v14.8b, v11.8b , v12.8b , #2 sqrshrun v27.8b, v22.8h, #6 st1 { v26.8b, v27.8b}, [x1], x3 ////Store dest row6 umull v24.8h, v5.8b, v28.8b umlal v24.8h, v8.8b, v29.8b umlal v24.8h, v10.8b, v30.8b umlal v24.8h, v13.8b, v31.8b umull v16.8h, v6.8b, v28.8b sqrshrun v18.8b, v24.8h, #6 umlal v16.8h, v9.8b, v29.8b umlal v16.8h, v11.8b, v30.8b umlal v16.8h, v14.8b, v31.8b sqrshrun v19.8b, v16.8h, #6 st1 { v18.8b, v19.8b}, [x1], x3 // store row 7 b end_func loop_4: ld1 {v0.8b, v1.8b}, [x0], x2 //// Load row0 ; ext v2.8b, v0.8b , v1.8b , #2 ld1 {v3.8b, v4.8b}, [x0], x2 //// Load row1; ext v5.8b, v3.8b , v4.8b , #2 umull v20.8h, v0.8b, v28.8b umlal v20.8h, v2.8b, v29.8b umlal v20.8h, v3.8b, v30.8b umlal v20.8h, v5.8b, v31.8b ld1 {v6.8b, v7.8b}, [x0], x2 //// Load row2 sqrshrun v26.8b, v20.8h, #6 ext v8.8b, v6.8b , v7.8b , #2 st1 {v26.8b}, [x1], x3 ////Store dest row0 umull v22.8h, v3.8b, v28.8b umlal v22.8h, v5.8b, v29.8b umlal v22.8h, v6.8b, v30.8b umlal v22.8h, v8.8b, v31.8b subs x12, x6, #2 sqrshrun v27.8b, v22.8h, #6 st1 {v27.8b}, [x1], x3 ////Store dest row1 beq end_func //If ht=2 ld1 {v9.8b, v10.8b}, [x0], x2 //// Load row3; ext v11.8b, v9.8b , v10.8b , #2 umull v24.8h, v6.8b, v28.8b umlal v24.8h, v8.8b, v29.8b umlal v24.8h, v9.8b, v30.8b umlal v24.8h, v11.8b, v31.8b ld1 {v0.8b, v1.8b}, [x0], x2 //// Load row4 ; sqrshrun v16.8b, v24.8h, #6 ext v2.8b, v0.8b , v1.8b , #2 st1 {v16.8b}, [x1], x3 ////Store dest row2 umull v18.8h, v9.8b, v28.8b umlal v18.8h, v11.8b, v29.8b umlal v18.8h, v0.8b, v30.8b umlal v18.8h, v2.8b, v31.8b subs x12, x6, #4 sqrshrun v17.8b, v18.8h, #6 st1 {v17.8b}, [x1], x3 ////Store dest row3 beq end_func //If ht=4 ld1 {v3.8b, v4.8b}, [x0], x2 //// Load row5; ext v5.8b, v3.8b , v4.8b , #2 umull v20.8h, v0.8b, v28.8b umlal v20.8h, v2.8b, v29.8b umlal v20.8h, v3.8b, v30.8b umlal v20.8h, v5.8b, v31.8b ld1 {v6.8b, v7.8b}, [x0], x2 //// Load row6 ; sqrshrun v26.8b, v20.8h, #6 ext v8.8b, v6.8b , v7.8b , #2 st1 {v26.8b}, [x1], x3 ////Store dest row4 umull v22.8h, v3.8b, v28.8b umlal v22.8h, v5.8b, v29.8b umlal v22.8h, v6.8b, v30.8b umlal v22.8h, v8.8b, v31.8b ld1 {v9.8b, v10.8b}, [x0], x2 //// Load row7; sqrshrun v27.8b, v22.8h, #6 ext v11.8b, v9.8b , v10.8b , #2 st1 {v27.8b}, [x1], x3 ////Store dest row5 umull v24.8h, v6.8b, v28.8b umlal v24.8h, v8.8b, v29.8b umlal v24.8h, v9.8b, v30.8b umlal v24.8h, v11.8b, v31.8b ld1 {v0.8b, v1.8b}, [x0], x2 //// Load row8; sqrshrun v16.8b, v24.8h, #6 ext v2.8b, v0.8b , v1.8b , #2 st1 {v16.8b}, [x1], x3 ////Store dest row6 umull v18.8h, v9.8b, v28.8b umlal v18.8h, v11.8b, v29.8b umlal v18.8h, v0.8b, v30.8b umlal v18.8h, v2.8b, v31.8b sqrshrun v17.8b, v18.8h, #6 st1 {v17.8b}, [x1], x3 ////Store dest row7 b end_func loop_2: ld1 {v0.8b}, [x0], x2 //// Load row0 ; ext v2.8b, v0.8b , v0.8b , #2 ld1 {v3.8b}, [x0], x2 //// Load row1; ext v5.8b, v3.8b , v3.8b , #2 umull v20.8h, v0.8b, v28.8b umlal v20.8h, v2.8b, v29.8b umlal v20.8h, v3.8b, v30.8b umlal v20.8h, v5.8b, v31.8b ld1 {v6.8b}, [x0], x2 //// Load row2 sqrshrun v26.8b, v20.8h, #6 ext v8.8b, v6.8b , v6.8b , #2 st1 {v26.s}[0], [x1], x3 ////Store dest row0 umull v22.8h, v3.8b, v28.8b umlal v22.8h, v5.8b, v29.8b umlal v22.8h, v6.8b, v30.8b umlal v22.8h, v8.8b, v31.8b subs x12, x6, #2 sqrshrun v27.8b, v22.8h, #6 st1 {v27.s}[0], [x1], x3 ////Store dest row1 beq end_func //If ht=2 ld1 {v9.8b}, [x0], x2 //// Load row3; ext v11.8b, v9.8b , v9.8b , #2 umull v24.8h, v6.8b, v28.8b umlal v24.8h, v8.8b, v29.8b umlal v24.8h, v9.8b, v30.8b umlal v24.8h, v11.8b, v31.8b ld1 {v0.8b}, [x0], x2 //// Load row4 ; sqrshrun v16.8b, v24.8h, #6 ext v2.8b, v0.8b , v0.8b , #2 st1 {v16.s}[0], [x1], x3 ////Store dest row2 umull v18.8h, v9.8b, v28.8b umlal v18.8h, v11.8b, v29.8b umlal v18.8h, v0.8b, v30.8b umlal v18.8h, v2.8b, v31.8b sqrshrun v17.8b, v18.8h, #6 st1 {v17.s}[0], [x1], x3 ////Store dest row3 end_func: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_inter_pred_filters_luma_horz_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_inter_pred_luma_horz_av8.s //* //* @brief //* Contains function definitions for inter prediction interpolation. //* //* @author //* Ittiam //* //* @par List of Functions: //* //* - ih264_inter_pred_luma_horz_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_inter_pred_filters.c // ///** ///** //******************************************************************************* //* //* @brief //* Interprediction luma filter for horizontal input //* //* @par Description: //* Applies a 6 tap horizontal filter .The output is clipped to 8 bits //* sec 8.4.2.2.1 titled "Luma sample interpolation process" //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* // @remarks //* None //* //******************************************************************************* //*/ //void ih264_inter_pred_luma_horz ( // UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd ) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ht // w5 => wd .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_inter_pred_luma_horz_av8 ih264_inter_pred_luma_horz_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x2, w2 sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 sub x0, x0, #2 //pu1_src-2 sub x14, x4, #16 movi v0.8b, #5 //filter coeff subs x12, x5, #8 //if wd=8 branch to loop_8 movi v1.8b, #20 //filter coeff beq loop_8 subs x12, x5, #4 //if wd=4 branch to loop_4 beq loop_4 loop_16: //when wd=16 //// Processing row0 and row1 ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row0 add x14, x14, #1 //for checking loop ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row0) ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row1 ext v30.8b, v3.8b , v4.8b, #5 ////extract a[5] (column2,row0) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row1) uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row0) ext v27.8b, v6.8b , v7.8b, #5 ////extract a[5] (column2,row1) uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) ext v31.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row0) uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row1) ext v30.8b, v3.8b , v4.8b, #2 ////extract a[2] (column2,row0) umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) ext v28.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row1) umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row0) ext v27.8b, v6.8b , v7.8b, #2 ////extract a[2] (column2,row1) umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) ext v31.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row0) umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row1) ext v30.8b, v3.8b , v4.8b, #3 ////extract a[3] (column2,row0) umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) ext v28.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row1) umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row0) ext v27.8b, v6.8b , v7.8b, #3 ////extract a[3] (column2,row1) umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) ext v31.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row0) umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row1) ext v30.8b, v3.8b , v4.8b, #1 ////extract a[1] (column2,row0) umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) ext v28.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row1) umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row0) ext v27.8b, v6.8b , v7.8b, #1 ////extract a[1] (column2,row1) umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) ext v31.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row0) umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row1) ext v30.8b, v3.8b , v4.8b, #4 ////extract a[4] (column2,row0) umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) ext v28.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row1) umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row0) ext v27.8b, v6.8b , v7.8b, #4 ////extract a[4] (column2,row1) umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row2 umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row1) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row3 sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row0) ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row2) st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row0 sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) ext v30.8b, v3.8b , v4.8b, #5 ////extract a[5] (column2,row2) sqrshrun v24.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row1) //// Processing row2 and row3 ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row3) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) st1 {v23.8b, v24.8b}, [x1], x3 ////Store dest row1 uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row2) ext v27.8b, v6.8b , v7.8b, #5 ////extract a[5] (column2,row3) uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) ext v31.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row2) uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row3) ext v30.8b, v3.8b , v4.8b, #2 ////extract a[2] (column2,row2) umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) ext v27.8b, v6.8b , v7.8b, #2 ////extract a[2] (column2,row3) umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row2) ext v28.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row3) umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) ext v31.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row2) umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row3) ext v30.8b, v3.8b , v4.8b, #3 ////extract a[3] (column2,row2) umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) ext v28.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row3) umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row2) ext v27.8b, v6.8b , v7.8b, #3 ////extract a[3] (column2,row3) umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) ext v31.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row2) umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row3) ext v30.8b, v3.8b , v4.8b, #1 ////extract a[1] (column2,row2) umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) ext v28.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row3) umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row2) ext v27.8b, v6.8b , v7.8b, #1 ////extract a[1] (column2,row3) umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) ext v31.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row2) umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row3) ext v30.8b, v3.8b , v4.8b, #4 ////extract a[4] (column2,row2) umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) ext v28.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row3) umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row2) ext v27.8b, v6.8b , v7.8b, #4 ////extract a[4] (column2,row3) umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row4 umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row3) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row5 sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row2) ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row4) st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row2 sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) ext v30.8b, v3.8b , v4.8b, #5 ////extract a[5] (column2,row4) sqrshrun v24.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row3) //// Processing row4 and row5 ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row5) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row4) st1 {v23.8b, v24.8b}, [x1], x3 ////Store dest row3 uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row4) ext v27.8b, v6.8b , v7.8b, #5 ////extract a[5] (column2,row5) uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row5) ext v31.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row4) uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row5) ext v30.8b, v3.8b , v4.8b, #2 ////extract a[2] (column2,row4) umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row4) ext v27.8b, v6.8b , v7.8b, #2 ////extract a[2] (column2,row5) umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row4) ext v28.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row5) umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row5) ext v31.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row4) umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row5) ext v30.8b, v3.8b , v4.8b, #3 ////extract a[3] (column2,row4) umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row4) ext v28.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row5) umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row4) ext v27.8b, v6.8b , v7.8b, #3 ////extract a[3] (column2,row5) umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row5) ext v31.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row4) umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row5) ext v30.8b, v3.8b , v4.8b, #1 ////extract a[1] (column2,row4) umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) ext v28.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row5) umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row4) ext v27.8b, v6.8b , v7.8b, #1 ////extract a[1] (column2,row5) umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) ext v31.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row4) umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row5) ext v30.8b, v3.8b , v4.8b, #4 ////extract a[4] (column2,row4) umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row4) ext v28.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row5) umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row4) ext v27.8b, v6.8b , v7.8b, #4 ////extract a[4] (column2,row5) umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row5) ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row6 umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row5) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row4) ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row7 sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row4) ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row6) st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row2 sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row5) ext v30.8b, v3.8b , v4.8b, #5 ////extract a[5] (column2,row6) sqrshrun v24.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row5) //// Processing row6 and row7 ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row7) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row6) st1 {v23.8b, v24.8b}, [x1], x3 ////Store dest row5 uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row6) ext v27.8b, v6.8b , v7.8b, #5 ////extract a[5] (column2,row7) uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row7) ext v31.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row6) uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row7) ext v30.8b, v3.8b , v4.8b, #2 ////extract a[2] (column2,row6) umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row6) ext v27.8b, v6.8b , v7.8b, #2 ////extract a[2] (column2,row7) umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row6) ext v28.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row7) umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row7) ext v31.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row6) umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row7) ext v30.8b, v3.8b , v4.8b, #3 ////extract a[3] (column2,row6) umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row6) ext v28.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row7) umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row6) ext v27.8b, v6.8b , v7.8b, #3 ////extract a[3] (column2,row7) umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row7) ext v31.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row6) umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row7) ext v30.8b, v3.8b , v4.8b, #1 ////extract a[1] (column2,row6) umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) ext v28.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row7) umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row6) ext v27.8b, v6.8b , v7.8b, #1 ////extract a[1] (column2,row7) umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) ext v31.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row6) umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row7) ext v30.8b, v3.8b , v4.8b, #4 ////extract a[4] (column2,row6) umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row6) ext v28.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row7) umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row6) ext v27.8b, v6.8b , v7.8b, #4 ////extract a[4] (column2,row6) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row6) umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row7) sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row6) umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row7) sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row7) st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row6 sqrshrun v24.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row7) subs x12, x14, #1 // if height==16 - looping st1 {v23.8b, v24.8b}, [x1], x3 ////Store dest row7 beq loop_16 b end_func loop_8: //// Processing row0 and row1 ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row1 add x14, x14, #1 //for checking loop ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row1) ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row0 ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row1) ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row0) ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row1) ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row1) ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row1) uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row0) umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row0) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row0) ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row0) ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row2 umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row3 sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) //// Processing row2 and row3 ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row3) ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row3) ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row2) uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) st1 {v23.8b}, [x1], x3 ////Store dest row0 ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row2) ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row3) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row3) ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row2) umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) st1 {v20.8b}, [x1], x3 ////Store dest row1 uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row2) ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row2) ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row2) ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row4 umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row3 subs x9, x4, #4 sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row5) ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row5) ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row4) uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row5) ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row5) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row5) ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row4) st1 {v20.8b}, [x1], x3 ////Store dest row2 ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row4) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row4) st1 {v23.8b}, [x1], x3 ////Store dest row3 beq end_func // Branch if height==4 //// Processing row4 and row5 ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row5) umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row5) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row5) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row5) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row5) ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row4) ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row4) ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row6 umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row4) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row4) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row4) sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row5) ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row7 ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row6) ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row7) ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row7) uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row7) ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row7) ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row7) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row4) ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row6) ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row6) st1 {v20.8b}, [x1], x3 ////Store dest row4 ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row6) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row6) ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row6) umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row6) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row6) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row6) //// Processing row6 and row7 st1 {v23.8b}, [x1], x3 ////Store dest row5 ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row7) umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row7) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row7) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row7) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row7) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row6) subs x12, x14, #1 st1 {v20.8b}, [x1], x3 ////Store dest row6 sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row7) st1 {v23.8b}, [x1], x3 ////Store dest row7 beq loop_8 //looping if height ==16 b end_func loop_4: ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row1 ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row1) ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row0 ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row1) ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row0) uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row1) ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row1) ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row1) ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row0) umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row0) ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row0) ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row0) ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row2 umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row3 ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row3) ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row3) sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row2) ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row2) st1 {v23.s}[0], [x1], x3 ////Store dest row0 ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row3) ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row3) ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row2) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row2) ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row2) //// Processing row2 and row3 st1 {v20.s}[0], [x1], x3 ////Store dest row1 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row2) umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) st1 {v20.s}[0], [x1], x3 ////Store dest row2 subs x4, x4, #8 // Loop if height =8 st1 {v23.s}[0], [x1], x3 ////Store dest row3 beq loop_4 end_func: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_inter_pred_filters_luma_vert_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_inter_pred_luma_vert_av8.s //* //* @brief //* Contains function definitions for inter prediction interpolation. //* //* @author //* Ittiam //* //* @par List of Functions: //* //* - ih264_inter_pred_luma_vert_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_inter_pred_filters.c // ///** ///** ///** // ******************************************************************************* // * // * @brief // * Interprediction luma filter for vertical input // * // * @par Description: // * Applies a 6 tap vertcal filter.The output is clipped to 8 bits // * sec 8.4.2.2.1 titled "Luma sample interpolation process" // * // * @param[in] pu1_src // * UWORD8 pointer to the source // * // * @param[out] pu1_dst // * UWORD8 pointer to the destination // * // * @param[in] src_strd // * integer source stride // * // * @param[in] dst_strd // * integer destination stride // * // * @param[in] ht // * integer height of the array // * // * @param[in] wd // * integer width of the array // * // * @returns // * // * @remarks // * None // * // ******************************************************************************* //void ih264_inter_pred_luma_vert ( // UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd ) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ht // w5 => wd .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_inter_pred_luma_vert_av8 ih264_inter_pred_luma_vert_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x2, w2 sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 sub x0, x0, x2, lsl #1 //pu1_src-2*src_strd sub x14, x4, #16 movi v22.8h, #20 // Filter coeff 0x14 into Q11 subs x12, x5, #8 //if wd=8 branch to loop_8 movi v24.8h, #5 // Filter coeff 0x4 into Q12 beq loop_8_start subs x12, x5, #4 //if wd=4 branch to loop_4 beq loop_4_start ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] add x14, x14, #1 //for checking loop ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] uaddl v12.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] loop_16: //when wd=16 uaddl v14.8h, v0.8b, v10.8b // temp = src[0_0] + src[5_0] uaddl v16.8h, v2.8b, v8.8b // temp2 = src[1_0] + src[4_0] mla v14.8h, v12.8h, v22.8h // temp += temp1 * 20 uaddl v20.8h, v1.8b, v11.8b // temp4 = src[0_8] + src[5_8] uaddl v18.8h, v5.8b, v7.8b // temp3 = src[2_8] + src[3_8] mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 ld1 {v0.2s, v1.2s}, [x0], x2 uaddl v26.8h, v3.8b, v9.8b // temp5 = src[1_8] + src[4_8] uaddl v12.8h, v6.8b, v8.8b mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v16.8h, v2.8b, v0.8b uaddl v18.8h, v4.8b, v10.8b mla v16.8h, v12.8h , v22.8h mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 uaddl v26.8h, v5.8b, v11.8b uaddl v12.8h, v7.8b, v9.8b sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) uaddl v14.8h, v3.8b, v1.8b ld1 {v2.2s, v3.2s}, [x0], x2 mla v14.8h, v12.8h , v22.8h mls v16.8h, v18.8h , v24.8h sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) uaddl v18.8h, v4.8b, v2.8b uaddl v12.8h, v8.8b, v10.8b st1 {v30.2s, v31.2s}, [x1], x3 // Vector store to dst[0_0] mla v18.8h, v12.8h , v22.8h uaddl v20.8h, v6.8b, v0.8b mls v14.8h, v26.8h , v24.8h sqrshrun v30.8b, v16.8h, #5 uaddl v12.8h, v9.8b, v11.8b uaddl v16.8h, v5.8b, v3.8b uaddl v26.8h, v7.8b, v1.8b mla v16.8h, v12.8h , v22.8h mls v18.8h, v20.8h , v24.8h ld1 {v4.2s, v5.2s}, [x0], x2 sqrshrun v31.8b, v14.8h, #5 uaddl v12.8h, v10.8b, v0.8b uaddl v14.8h, v6.8b, v4.8b uaddl v20.8h, v8.8b, v2.8b mla v14.8h, v12.8h , v22.8h mls v16.8h, v26.8h , v24.8h st1 {v30.2s, v31.2s}, [x1], x3 //store row 1 sqrshrun v30.8b, v18.8h, #5 uaddl v18.8h, v7.8b, v5.8b uaddl v12.8h, v11.8b, v1.8b mla v18.8h, v12.8h , v22.8h uaddl v26.8h, v9.8b, v3.8b mls v14.8h, v20.8h , v24.8h ld1 {v6.2s, v7.2s}, [x0], x2 sqrshrun v31.8b, v16.8h, #5 mls v18.8h, v26.8h , v24.8h uaddl v12.8h, v0.8b, v2.8b // temp1 = src[2_0] + src[3_0] st1 {v30.2s, v31.2s}, [x1], x3 //store row 2 uaddl v16.8h, v10.8b, v4.8b // temp2 = src[1_0] + src[4_0] uaddl v20.8h, v9.8b, v7.8b // temp4 = src[0_8] + src[5_8] sqrshrun v30.8b, v14.8h, #5 uaddl v26.8h, v5.8b, v11.8b // temp5 = src[1_8] + src[4_8] uaddl v14.8h, v8.8b, v6.8b // temp = src[0_0] + src[5_0] sqrshrun v31.8b, v18.8h, #5 mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 uaddl v18.8h, v1.8b, v3.8b // temp3 = src[2_8] + src[3_8] st1 {v30.2s, v31.2s}, [x1], x3 //store row 3 // 4 rows processed mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 ld1 {v8.2s, v9.2s}, [x0], x2 uaddl v12.8h, v2.8b, v4.8b uaddl v18.8h, v3.8b, v5.8b mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v28.8h, v9.8b, v11.8b uaddl v16.8h, v6.8b, v0.8b mla v28.8h, v18.8h , v22.8h // temp4 += temp3 * 20 mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 uaddl v26.8h, v1.8b, v7.8b uaddl v18.8h, v5.8b, v7.8b sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) uaddl v14.8h, v8.8b, v10.8b sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) ld1 {v10.2s, v11.2s}, [x0], x2 mls v28.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 st1 {v30.2s, v31.2s}, [x1], x3 // store row 4 mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 uaddl v20.8h, v11.8b, v1.8b uaddl v26.8h, v3.8b, v9.8b mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 uaddl v12.8h, v6.8b, v4.8b uaddl v18.8h, v7.8b, v9.8b sqrshrun v31.8b, v28.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v16.8h, v8.8b, v2.8b sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 uaddl v14.8h, v10.8b, v0.8b st1 {v30.2s, v31.2s}, [x1], x3 // store row 5 mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 ld1 {v0.2s, v1.2s}, [x0], x2 uaddl v26.8h, v5.8b, v11.8b uaddl v12.8h, v8.8b, v6.8b uaddl v28.8h, v0.8b, v2.8b sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) mla v28.8h, v12.8h , v22.8h // temp += temp1 * 20 uaddl v20.8h, v1.8b, v3.8b mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 uaddl v16.8h, v10.8b, v4.8b sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) mov v2.8b, v6.8b mov v3.8b, v7.8b mls v28.8h, v16.8h , v24.8h // temp -= temp2 * 5 st1 {v30.2s, v31.2s}, [x1], x3 // store row 6 sqrshrun v30.8b, v28.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) swp v0.8b, v4.8b swp v1.8b, v5.8b mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 mov v6.8b, v10.8b mov v7.8b, v11.8b subs x12, x14, #1 // if height==16 - looping swp v4.8b, v8.8b swp v5.8b, v9.8b sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) st1 {v30.2s, v31.2s}, [x1], x3 // store row 7 bne end_func //if height =8 end function add x14, x14, #1 //for checking loop ld1 {v10.2s, v11.2s}, [x0], x2 uaddl v12.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] b loop_16 // looping if height =16 loop_8_start: //// Processing row0 and row1 ld1 {v0.2s}, [x0], x2 // Vector load from src[0_0] ld1 {v1.2s}, [x0], x2 // Vector load from src[1_0] ld1 {v2.2s}, [x0], x2 // Vector load from src[2_0] ld1 {v3.2s}, [x0], x2 // Vector load from src[3_0] add x14, x14, #1 //for checking loop ld1 {v4.2s}, [x0], x2 // Vector load from src[4_0] ld1 {v5.2s}, [x0], x2 // Vector load from src[5_0] loop_8: //for checking loop uaddl v6.8h, v2.8b, v3.8b // temp1 = src[2_0] + src[3_0] uaddl v8.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] uaddl v10.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] mla v8.8h, v6.8h , v22.8h // temp += temp1 * 20 ld1 {v6.2s}, [x0], x2 uaddl v14.8h, v3.8b, v4.8b uaddl v16.8h, v1.8b, v6.8b uaddl v18.8h, v2.8b, v5.8b mls v8.8h, v10.8h , v24.8h // temp -= temp2 * 5 mla v16.8h, v14.8h , v22.8h ld1 {v7.2s}, [x0], x2 uaddl v20.8h, v4.8b, v5.8b uaddl v12.8h, v2.8b, v7.8b uaddl v10.8h, v3.8b, v6.8b mls v16.8h, v18.8h , v24.8h sqrshrun v26.8b, v8.8h, #5 // dst[0_0] = CLIP_U8( (temp + 16) >> 5) mla v12.8h, v20.8h , v22.8h ld1 {v0.2s}, [x0], x2 uaddl v14.8h, v5.8b, v6.8b sqrshrun v27.8b, v16.8h, #5 uaddl v20.8h, v3.8b, v0.8b mls v12.8h, v10.8h , v24.8h st1 {v26.2s}, [x1], x3 // Vector store to dst[0_0] uaddl v18.8h, v4.8b, v7.8b mla v20.8h, v14.8h , v22.8h st1 {v27.2s}, [x1], x3 sqrshrun v28.8b, v12.8h, #5 st1 {v28.2s}, [x1], x3 mls v20.8h, v18.8h , v24.8h ld1 {v1.2s}, [x0], x2 sqrshrun v29.8b, v20.8h, #5 subs x9, x4, #4 st1 {v29.2s}, [x1], x3 //store row 3 beq end_func // Branch if height==4 uaddl v14.8h, v6.8b, v7.8b // temp1 = src[2_0] + src[3_0] uaddl v16.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] uaddl v18.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 ld1 {v2.2s}, [x0], x2 mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v8.8h, v0.8b, v7.8b uaddl v10.8h, v1.8b, v6.8b uaddl v12.8h, v2.8b, v5.8b sqrshrun v26.8b, v18.8h, #5 mla v12.8h, v8.8h , v22.8h ld1 {v3.2s}, [x0], x2 mls v12.8h, v10.8h , v24.8h st1 {v26.2s}, [x1], x3 sqrshrun v27.8b, v12.8h, #5 st1 {v27.2s}, [x1], x3 uaddl v14.8h, v0.8b, v1.8b // temp1 = src[2_0] + src[3_0] uaddl v16.8h, v2.8b, v7.8b // temp = src[0_0] + src[5_0] uaddl v18.8h, v3.8b, v6.8b // temp2 = src[1_0] + src[4_0] mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 ld1 {v4.2s}, [x0], x2 mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v8.8h, v2.8b, v1.8b uaddl v10.8h, v3.8b, v0.8b uaddl v12.8h, v4.8b, v7.8b sqrshrun v26.8b, v18.8h, #5 mla v12.8h, v8.8h , v22.8h ld1 {v5.2s}, [x0], x2 mls v12.8h, v10.8h , v24.8h st1 {v26.2s}, [x1], x3 sqrshrun v27.8b, v12.8h, #5 subs x12, x14, #1 st1 {v27.2s}, [x1], x3 add x14, x14, #1 beq loop_8 //looping if height ==16 b end_func loop_4_start: //// Processing row0 and row1 ld1 {v0.s}[0], [x0], x2 // Vector load from src[0_0] ld1 {v1.s}[0], [x0], x2 // Vector load from src[1_0] ld1 {v2.s}[0], [x0], x2 // Vector load from src[2_0] ld1 {v3.s}[0], [x0], x2 // Vector load from src[3_0] ld1 {v4.s}[0], [x0], x2 // Vector load from src[4_0] ld1 {v5.s}[0], [x0], x2 // Vector load from src[5_0] uaddl v6.8h, v2.8b, v3.8b // temp1 = src[2_0] + src[3_0] uaddl v8.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] uaddl v10.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] mla v8.8h, v6.8h , v22.8h // temp += temp1 * 20 ld1 {v6.2s}, [x0], x2 uaddl v14.8h, v3.8b, v4.8b uaddl v16.8h, v1.8b, v6.8b uaddl v18.8h, v2.8b, v5.8b mls v8.8h, v10.8h , v24.8h // temp -= temp2 * 5 ld1 {v7.s}[0], [x0], x2 mla v16.8h, v14.8h , v22.8h uaddl v20.8h, v4.8b, v5.8b uaddl v12.8h, v2.8b, v7.8b uaddl v10.8h, v3.8b, v6.8b mls v16.8h, v18.8h , v24.8h sqrshrun v26.8b, v8.8h, #5 // dst[0_0] = CLIP_U8( (temp + 16) >> 5) mla v12.8h, v20.8h , v22.8h ld1 {v0.s}[0], [x0], x2 uaddl v14.8h, v5.8b, v6.8b sqrshrun v27.8b, v16.8h, #5 uaddl v20.8h, v3.8b, v0.8b mls v12.8h, v10.8h , v24.8h st1 {v26.s}[0], [x1], x3 // Vector store to dst[0_0] uaddl v18.8h, v4.8b, v7.8b mla v20.8h, v14.8h , v22.8h st1 {v27.s}[0], [x1], x3 sqrshrun v28.8b, v12.8h, #5 st1 {v28.s}[0], [x1], x3 mls v20.8h, v18.8h , v24.8h ld1 {v1.s}[0], [x0], x2 sqrshrun v29.8b, v20.8h, #5 st1 {v29.s}[0], [x1], x3 //store row 3 subs x9, x4, #4 beq end_func // Branch if height==4 uaddl v14.8h, v6.8b, v7.8b // temp1 = src[2_0] + src[3_0] uaddl v16.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] uaddl v18.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 ld1 {v2.s}[0], [x0], x2 mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v8.8h, v0.8b, v7.8b uaddl v10.8h, v1.8b, v6.8b uaddl v12.8h, v2.8b, v5.8b sqrshrun v26.8b, v18.8h, #5 mla v12.8h, v8.8h , v22.8h ld1 {v3.s}[0], [x0], x2 mls v12.8h, v10.8h , v24.8h st1 {v26.s}[0], [x1], x3 sqrshrun v27.8b, v12.8h, #5 st1 {v27.s}[0], [x1], x3 uaddl v14.8h, v0.8b, v1.8b // temp1 = src[2_0] + src[3_0] uaddl v16.8h, v2.8b, v7.8b // temp = src[0_0] + src[5_0] uaddl v18.8h, v3.8b, v6.8b // temp2 = src[1_0] + src[4_0] mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 ld1 {v4.s}[0], [x0], x2 mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v8.8h, v2.8b, v1.8b uaddl v10.8h, v3.8b, v0.8b uaddl v12.8h, v4.8b, v7.8b sqrshrun v26.8b, v18.8h, #5 mla v12.8h, v8.8h , v22.8h ld1 {v5.s}[0], [x0], x2 mls v12.8h, v10.8h , v24.8h st1 {v26.s}[0], [x1], x3 sqrshrun v27.8b, v12.8h, #5 st1 {v27.s}[0], [x1], x3 end_func: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_inter_pred_luma_copy_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** ///** //******************************************************************************* //* //* @brief //* Interprediction luma function for copy //* //* @par Description: //* Copies the array of width 'wd' and height 'ht' from the location pointed //* by 'src' to the location pointed by 'dst' //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //void ih264_inter_pred_luma_copy ( // UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd ) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ht // w5 => wd .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_inter_pred_luma_copy_av8 ih264_inter_pred_luma_copy_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x2, w2 sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 mov x12, x5 mov x7, x4 cmp x7, #0 //checks ht == 0 ble end_loops tst x12, #15 //checks wd for multiples for 4 & 8 beq core_loop_wd_16 tst x12, #7 //checks wd for multiples for 4 & 8 beq core_loop_wd_8 sub x11, x12, #4 outer_loop_wd_4: subs x4, x12, #0 //checks wd == 0 ble end_inner_loop_wd_4 inner_loop_wd_4: ld1 {v0.s}[0], [x0] //vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) add x5, x0, x2 //pu1_src_tmp += src_strd add x6, x1, x3 //pu1_dst_tmp += dst_strd st1 {v0.s}[0], [x1] //vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) ld1 {v0.s}[0], [x5], x2 //vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) add x0, x0, #4 //pu1_src += 4 st1 {v0.s}[0], [x6], x3 //vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) ld1 {v0.s}[0], [x5], x2 //vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) subs x4, x4, #4 //(wd -4) st1 {v0.s}[0], [x6], x3 //vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) ld1 {v0.s}[0], [x5], x2 //vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) add x1, x1, #4 //pu1_dst += 4 st1 {v0.s}[0], [x6], x3 //vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) bgt inner_loop_wd_4 end_inner_loop_wd_4: subs x7, x7, #4 //ht - 4 sub x0, x5, x11 //pu1_src = pu1_src_tmp sub x1, x6, x11 //pu1_dst = pu1_dst_tmp bgt outer_loop_wd_4 end_loops: // LDMFD sp!,{x4-x12,x15} //Reload the registers from SP ldp x19, x20, [sp], #16 pop_v_regs ret core_loop_wd_8: sub x11, x12, #8 outer_loop_wd_8: subs x4, x12, #0 //checks wd ble end_inner_loop_wd_8 inner_loop_wd_8: add x5, x0, x2 //pu1_src_tmp += src_strd ld1 {v0.8b}, [x0], #8 //vld1_u8(pu1_src_tmp) add x6, x1, x3 //pu1_dst_tmp += dst_strd st1 {v0.8b}, [x1], #8 //vst1_u8(pu1_dst_tmp, tmp_src) ld1 {v1.8b}, [x5], x2 //vld1_u8(pu1_src_tmp) st1 {v1.8b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) subs x4, x4, #8 //wd - 8(Loop condition) ld1 {v2.8b}, [x5], x2 //vld1_u8(pu1_src_tmp) st1 {v2.8b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) ld1 {v3.8b}, [x5], x2 //vld1_u8(pu1_src_tmp) st1 {v3.8b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) bgt inner_loop_wd_8 end_inner_loop_wd_8: subs x7, x7, #4 //ht -= 4 sub x0, x5, x11 //pu1_src = pu1_src_tmp sub x1, x6, x11 //pu1_dst = pu1_dst_tmp bgt outer_loop_wd_8 // LDMFD sp!,{x4-x12,x15} //Reload the registers from SP ldp x19, x20, [sp], #16 pop_v_regs ret core_loop_wd_16: sub x11, x12, #16 outer_loop_wd_16: subs x4, x12, #0 //checks wd ble end_inner_loop_wd_16 inner_loop_wd_16: add x5, x0, x2 //pu1_src_tmp += src_strd ld1 { v0.16b}, [x0], #16 //vld1_u8(pu1_src_tmp) add x6, x1, x3 //pu1_dst_tmp += dst_strd st1 { v0.16b}, [x1], #16 //vst1_u8(pu1_dst_tmp, tmp_src) ld1 { v2.16b}, [x5], x2 //vld1_u8(pu1_src_tmp) st1 { v2.16b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) subs x4, x4, #16 //wd - 8(Loop condition) ld1 { v4.16b}, [x5], x2 //vld1_u8(pu1_src_tmp) st1 { v4.16b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) ld1 { v6.16b}, [x5], x2 //vld1_u8(pu1_src_tmp) st1 { v6.16b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) bgt inner_loop_wd_16 end_inner_loop_wd_16: subs x7, x7, #4 //ht -= 4 sub x0, x5, x11 //pu1_src = pu1_src_tmp sub x1, x6, x11 //pu1_dst = pu1_dst_tmp bgt outer_loop_wd_16 ldp x19, x20, [sp], #16 pop_v_regs ret // /* // ******************************************************************************** // * // * @brief This function copies a 4x4 block to destination // * // * @par Description: // * Copies a 4x4 block to destination, where both src and dst are interleaved // * // * @param[in] pi2_src // * Source // * // * @param[in] pu1_out // * Output pointer // * // * @param[in] pred_strd, // * Prediction buffer stride // * // * @param[in] out_strd // * output buffer buffer Stride // * // * @returns none // * // * @remarks none // * Currently wd and height is not used, ie a 4x4 block is always copied // * // ******************************************************************************* // */ // void ih264_interleave_copy(WORD16 *pi2_src, // UWORD8 *pu1_out, // WORD32 pred_strd, // WORD32 out_strd // WORD32 wd // WORD32 ht) // Register Usage // x0 : pi2_src // x1 : pu1_out // w2 : src_strd // w3 : out_strd // Neon registers d0-d7, d16-d30 are used // No need for pushing arm and neon registers .global ih264_interleave_copy_av8 ih264_interleave_copy_av8: push_v_regs sxtw x2, w2 sxtw x3, w3 ld1 {v2.8b}, [x0], x2 //load src plane 1 => d2 &pred palne 2 => d3 ld1 {v3.8b}, [x0], x2 mov v2.d[1], v3.d[0] ld1 {v4.8b}, [x0], x2 ld1 {v5.8b}, [x0], x2 mov v4.d[1], v5.d[0] mov x0, x1 ld1 {v18.8b}, [x1], x3 //load out [8 bit size) -8 coeffs ld1 {v19.8b}, [x1], x3 mov v18.d[1], v19.d[0] movi v30.8h, #0x00ff ld1 {v20.8b}, [x1], x3 ld1 {v21.8b}, [x1], x3 mov v20.d[1], v21.d[0] bit v18.16b, v2.16b , v30.16b bit v20.16b, v4.16b , v30.16b st1 {v18.8b}, [x0], x3 //store out st1 {v18.d}[1], [x0], x3 st1 {v20.8b}, [x0], x3 st1 {v20.d}[1], [x0], x3 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_hpel_vert_hpel_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_inter_pred_luma_horz_hpel_vert_hpel_av8.s //* //* @brief //* Contains function definitions for inter prediction interpolation. //* //* @author //* Mohit //* //* @par List of Functions: //* //* - ih264_inter_pred_luma_horz_hpel_vert_hpel_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ //void ih264_inter_pred_luma_horz_hpel_vert_hpel(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd,, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd, // UWORD8* pu1_tmp, // UWORD32 dydx) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ht // w5 => wd .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 ih264_inter_pred_luma_horz_hpel_vert_hpel_av8: //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x2, w2 sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 sub x0, x0, x2, lsl #1 //pu1_src-2*src_strd sub x0, x0, #2 //pu1_src-2 movi v26.8h, #0x14 // Filter coeff 20 into Q13 movi v24.8h, #0x5 // Filter coeff 5 into Q12 movi v27.8h, #0x14 // Filter coeff 20 into Q13 movi v25.8h, #0x5 // Filter coeff 5 into Q12 mov x7, #0x20 mov x8, #0x30 subs x12, x5, #4 //if wd=4 branch to loop_4 beq loop_4_start subs x12, x5, #8 //if wd=8 branch to loop_8 beq loop_8_start //when wd=16 movi v28.8h, #0x14 // Filter coeff 20 into Q13 movi v30.8h, #0x5 // Filter coeff 5 into Q12 sub x2, x2, #16 ld1 {v0.2s, v1.2s}, [x0], #16 // Vector load from src[0_0] ld1 {v12.2s}, [x0], x2 // Vector load from src[0_0] ld1 {v2.2s, v3.2s}, [x0], #16 // Vector load from src[1_0] ld1 {v13.2s}, [x0], x2 // Vector load from src[1_0] ld1 {v4.2s, v5.2s}, [x0], #16 // Vector load from src[2_0] ld1 {v14.2s}, [x0], x2 // Vector load from src[2_0] ld1 {v6.2s, v7.2s}, [x0], #16 // Vector load from src[3_0] ld1 {v15.2s}, [x0], x2 // Vector load from src[3_0] ld1 {v8.2s, v9.2s}, [x0], #16 // Vector load from src[4_0] ld1 {v16.2s}, [x0], x2 // Vector load from src[4_0] loop_16: ld1 {v10.2s, v11.2s}, [x0], #16 // Vector load from src[5_0] ld1 {v17.2s}, [x0], x2 // Vector load from src[5_0] uaddl v20.8h, v4.8b, v6.8b uaddl v18.8h, v0.8b, v10.8b uaddl v22.8h, v2.8b, v8.8b mla v18.8h, v20.8h , v28.8h uaddl v24.8h, v5.8b, v7.8b uaddl v20.8h, v1.8b, v11.8b uaddl v26.8h, v3.8b, v9.8b mla v20.8h, v24.8h , v28.8h uaddl v24.8h, v14.8b, v15.8b mls v18.8h, v22.8h , v30.8h uaddl v22.8h, v12.8b, v17.8b mls v20.8h, v26.8h , v30.8h uaddl v26.8h, v13.8b, v16.8b mla v22.8h, v24.8h , v28.8h mls v22.8h, v26.8h , v30.8h ext v24.16b, v18.16b , v20.16b , #4 ext v26.16b, v18.16b , v20.16b , #6 ext v23.16b, v18.16b , v20.16b , #10 add v0.8h, v24.8h , v26.8h ext v24.16b, v18.16b , v20.16b , #2 ext v26.16b, v18.16b , v20.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v18.4h, v23.4h smlal v26.4s, v0.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v23.4s, v18.8h, v23.8h smlal2 v23.4s, v0.8h, v28.8h smlsl2 v23.4s, v24.8h, v30.8h sqrshrun v18.4h, v26.4s, #10 sqrshrun v19.4h, v23.4s, #10 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ext v24.16b, v20.16b , v22.16b , #4 ext v26.16b, v20.16b , v22.16b , #6 ext v0.16b, v20.16b , v22.16b , #10 add v25.8h, v24.8h , v26.8h ext v24.16b, v20.16b , v22.16b , #2 ext v26.16b, v20.16b , v22.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v0.4h, v20.4h smlal v26.4s, v25.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v0.8h, v20.8h smlal2 v22.4s, v25.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h sqrshrun v19.4h, v26.4s, #10 sqrshrun v25.4h, v22.4s, #10 uaddl v24.8h, v7.8b, v9.8b uqxtn v19.8b, v19.8h uqxtn v25.8b, v25.8h mov v19.s[1], v25.s[0] uaddl v22.8h, v4.8b, v10.8b ld1 {v0.2s, v1.2s}, [x0], #16 // Vector load from src[6_0] ld1 {v12.2s}, [x0], x2 // Vector load from src[6_0] uaddl v20.8h, v6.8b, v8.8b uaddl v26.8h, v5.8b, v11.8b st1 {v18.2s, v19.2s}, [x1], x3 // store row 0 //ROW_2 uaddl v18.8h, v2.8b, v0.8b mla v18.8h, v20.8h , v28.8h uaddl v20.8h, v3.8b, v1.8b mla v20.8h, v24.8h , v28.8h uaddl v24.8h, v15.8b, v16.8b mls v18.8h, v22.8h , v30.8h uaddl v22.8h, v13.8b, v12.8b mls v20.8h, v26.8h , v30.8h uaddl v26.8h, v14.8b, v17.8b mla v22.8h, v24.8h , v28.8h mls v22.8h, v26.8h , v30.8h ext v24.16b, v18.16b , v20.16b , #4 ext v26.16b, v18.16b , v20.16b , #6 ext v23.16b, v18.16b , v20.16b , #10 add v2.8h, v24.8h , v26.8h ext v24.16b, v18.16b , v20.16b , #2 ext v26.16b, v18.16b , v20.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v18.4h, v23.4h smlal v26.4s, v2.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v23.4s, v18.8h, v23.8h smlal2 v23.4s, v2.8h, v28.8h smlsl2 v23.4s, v24.8h, v30.8h sqrshrun v18.4h, v26.4s, #10 sqrshrun v19.4h, v23.4s, #10 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ext v24.16b, v20.16b , v22.16b , #4 ext v26.16b, v20.16b , v22.16b , #6 ext v2.16b, v20.16b , v22.16b , #10 add v25.8h, v24.8h , v26.8h ext v24.16b, v20.16b , v22.16b , #2 ext v26.16b, v20.16b , v22.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v2.4h, v20.4h smlal v26.4s, v25.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v2.8h, v20.8h smlal2 v22.4s, v25.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h sqrshrun v19.4h, v26.4s, #10 sqrshrun v25.4h, v22.4s, #10 uaddl v24.8h, v9.8b, v11.8b uqxtn v19.8b, v19.8h uqxtn v25.8b, v25.8h mov v19.s[1], v25.s[0] uaddl v22.8h, v6.8b, v0.8b ld1 {v2.2s, v3.2s}, [x0], #16 // Vector load from src[7_0] ld1 {v13.2s}, [x0], x2 // Vector load from src[7_0] uaddl v20.8h, v8.8b, v10.8b uaddl v26.8h, v7.8b, v1.8b st1 {v18.2s, v19.2s}, [x1], x3 // store row 1 //ROW_3 uaddl v18.8h, v4.8b, v2.8b mla v18.8h, v20.8h , v28.8h uaddl v20.8h, v5.8b, v3.8b mla v20.8h, v24.8h , v28.8h uaddl v24.8h, v16.8b, v17.8b mls v18.8h, v22.8h , v30.8h uaddl v22.8h, v14.8b, v13.8b mls v20.8h, v26.8h , v30.8h uaddl v26.8h, v15.8b, v12.8b mla v22.8h, v24.8h , v28.8h mls v22.8h, v26.8h , v30.8h ext v24.16b, v18.16b , v20.16b , #4 ext v26.16b, v18.16b , v20.16b , #6 ext v23.16b, v18.16b , v20.16b , #10 add v4.8h, v24.8h , v26.8h ext v24.16b, v18.16b , v20.16b , #2 ext v26.16b, v18.16b , v20.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v18.4h, v23.4h smlal v26.4s, v4.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v23.4s, v18.8h, v23.8h smlal2 v23.4s, v4.8h, v28.8h smlsl2 v23.4s, v24.8h, v30.8h sqrshrun v18.4h, v26.4s, #10 sqrshrun v19.4h, v23.4s, #10 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ext v24.16b, v20.16b , v22.16b , #4 ext v26.16b, v20.16b , v22.16b , #6 ext v4.16b, v20.16b , v22.16b , #10 add v25.8h, v24.8h , v26.8h ext v24.16b, v20.16b , v22.16b , #2 ext v26.16b, v20.16b , v22.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v4.4h, v20.4h smlal v26.4s, v25.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v4.8h, v20.8h smlal2 v22.4s, v25.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h sqrshrun v19.4h, v26.4s, #10 sqrshrun v25.4h, v22.4s, #10 uaddl v24.8h, v11.8b, v1.8b uqxtn v19.8b, v19.8h uqxtn v25.8b, v25.8h mov v19.s[1], v25.s[0] uaddl v22.8h, v8.8b, v2.8b ld1 {v4.2s, v5.2s}, [x0], #16 // Vector load from src[8_0] ld1 {v14.2s}, [x0], x2 // Vector load from src[8_0] uaddl v20.8h, v10.8b, v0.8b uaddl v26.8h, v9.8b, v3.8b st1 {v18.2s, v19.2s}, [x1], x3 // store row 2 //ROW_4 uaddl v18.8h, v6.8b, v4.8b mla v18.8h, v20.8h , v28.8h uaddl v20.8h, v7.8b, v5.8b mla v20.8h, v24.8h , v28.8h uaddl v24.8h, v17.8b, v12.8b mls v18.8h, v22.8h , v30.8h uaddl v22.8h, v15.8b, v14.8b mls v20.8h, v26.8h , v30.8h uaddl v26.8h, v16.8b, v13.8b mla v22.8h, v24.8h , v28.8h mls v22.8h, v26.8h , v30.8h ext v24.16b, v18.16b , v20.16b , #4 ext v26.16b, v18.16b , v20.16b , #6 ext v23.16b, v18.16b , v20.16b , #10 add v6.8h, v24.8h , v26.8h ext v24.16b, v18.16b , v20.16b , #2 ext v26.16b, v18.16b , v20.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v18.4h, v23.4h smlal v26.4s, v6.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v23.4s, v18.8h, v23.8h smlal2 v23.4s, v6.8h, v28.8h smlsl2 v23.4s, v24.8h, v30.8h sqrshrun v18.4h, v26.4s, #10 sqrshrun v19.4h, v23.4s, #10 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ext v24.16b, v20.16b , v22.16b , #4 ext v26.16b, v20.16b , v22.16b , #6 ext v6.16b, v20.16b , v22.16b , #10 add v25.8h, v24.8h , v26.8h ext v24.16b, v20.16b , v22.16b , #2 ext v26.16b, v20.16b , v22.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v6.4h, v20.4h smlal v26.4s, v25.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v6.8h, v20.8h smlal2 v22.4s, v25.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h mov v6.16b, v2.16b mov v7.16b, v3.16b mov v2.16b, v10.16b mov v3.16b, v11.16b subs x4, x4, #4 sqrshrun v19.4h, v26.4s, #10 sqrshrun v25.4h, v22.4s, #10 mov v10.16b, v0.16b mov v11.16b, v1.16b mov v24.8b, v14.8b mov v14.16b, v12.16b mov v15.16b, v13.16b uqxtn v19.8b, v19.8h uqxtn v25.8b, v25.8h mov v19.s[1], v25.s[0] mov v0.16b, v8.16b mov v1.16b, v9.16b mov v8.16b, v4.16b mov v9.16b, v5.16b mov v12.16b, v16.16b mov v13.16b, v17.16b mov v4.16b, v10.16b mov v5.16b, v11.16b mov v16.8b, v24.8b st1 {v18.2s, v19.2s}, [x1], x3 // store row 3 bgt loop_16 // looping if height =16 b end_func loop_8_start: ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] loop_8: ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] uaddl v14.8h, v4.8b, v6.8b uaddl v12.8h, v0.8b, v10.8b uaddl v16.8h, v2.8b, v8.8b mla v12.8h, v14.8h , v26.8h uaddl v18.8h, v5.8b, v7.8b uaddl v14.8h, v1.8b, v11.8b uaddl v22.8h, v3.8b, v9.8b mla v14.8h, v18.8h , v26.8h mls v12.8h, v16.8h , v24.8h ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[6_0] uaddl v16.8h, v6.8b, v8.8b mls v14.8h, v22.8h , v24.8h uaddl v28.8h, v2.8b, v0.8b ext v22.16b, v12.16b , v14.16b , #10 uaddl v18.8h, v4.8b, v10.8b mla v28.8h, v16.8h , v26.8h saddl v30.4s, v12.4h, v22.4h saddl2 v22.4s, v12.8h, v22.8h ext v16.16b, v12.16b , v14.16b , #4 mls v28.8h, v18.8h , v24.8h ext v18.16b, v12.16b , v14.16b , #6 ext v20.16b, v12.16b , v14.16b , #8 ext v14.16b, v12.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v14.8h , v20.8h uaddl v20.8h, v7.8b, v9.8b smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal2 v22.4s, v16.8h, v26.8h smlsl2 v22.4s, v18.8h, v24.8h uaddl v14.8h, v3.8b, v1.8b mla v14.8h, v20.8h , v26.8h sqrshrun v12.4h, v30.4s, #10 uaddl v16.8h, v5.8b, v11.8b sqrshrun v13.4h, v22.4s, #10 mls v14.8h, v16.8h , v24.8h ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[7_0] uqxtn v25.8b, v12.8h uqxtn v13.8b, v13.8h mov v25.s[1], v13.s[0] uaddl v16.8h, v8.8b, v10.8b ext v22.16b, v28.16b , v14.16b , #10 uaddl v20.8h, v4.8b, v2.8b saddl v30.4s, v28.4h, v22.4h mla v20.8h, v16.8h , v26.8h saddl2 v22.4s, v28.8h, v22.8h ext v16.16b, v28.16b , v14.16b , #4 ext v18.16b, v28.16b , v14.16b , #6 ext v12.16b, v28.16b , v14.16b , #8 ext v14.16b, v28.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v12.8h , v14.8h smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal2 v22.4s, v16.8h, v26.8h smlsl2 v22.4s, v18.8h, v24.8h uaddl v18.8h, v6.8b, v0.8b sqrshrun v16.4h, v30.4s, #10 sqrshrun v17.4h, v22.4s, #10 mov v12.8b, v25.8b mov v25.8b, v24.8b uaddl v28.8h, v9.8b, v11.8b uqxtn v13.8b, v16.8h uqxtn v17.8b, v17.8h mov v13.s[1], v17.s[0] uaddl v14.8h, v5.8b, v3.8b uaddl v22.8h, v7.8b, v1.8b mls v20.8h, v18.8h , v24.8h st1 {v12.2s}, [x1], x3 // store row 0 mla v14.8h, v28.8h , v26.8h ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[8_0] uaddl v30.8h, v10.8b, v0.8b uaddl v28.8h, v6.8b, v4.8b mls v14.8h, v22.8h , v24.8h st1 {v13.2s}, [x1], x3 // store row 1 mla v28.8h, v30.8h , v26.8h ext v22.16b, v20.16b , v14.16b , #10 saddl v30.4s, v20.4h, v22.4h saddl2 v22.4s, v20.8h, v22.8h ext v16.16b, v20.16b , v14.16b , #4 ext v18.16b, v20.16b , v14.16b , #6 ext v12.16b, v20.16b , v14.16b , #8 ext v14.16b, v20.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v14.8h , v12.8h uaddl v20.8h, v8.8b, v2.8b smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal2 v22.4s, v16.8h, v26.8h smlsl2 v22.4s, v18.8h, v24.8h uaddl v18.8h, v11.8b, v1.8b uaddl v16.8h, v7.8b, v5.8b sqrshrun v12.4h, v30.4s, #10 uaddl v30.8h, v9.8b, v3.8b mla v16.8h, v18.8h , v26.8h sqrshrun v13.4h, v22.4s, #10 mls v28.8h, v20.8h , v24.8h mls v16.8h, v30.8h , v24.8h uqxtn v27.8b, v12.8h uqxtn v13.8b, v13.8h mov v27.s[1], v13.s[0] ext v22.16b, v28.16b , v16.16b , #10 saddl v30.4s, v28.4h, v22.4h saddl2 v22.4s, v28.8h, v22.8h ext v12.16b, v28.16b , v16.16b , #4 ext v18.16b, v28.16b , v16.16b , #6 ext v20.16b, v28.16b , v16.16b , #8 ext v28.16b, v28.16b , v16.16b , #2 add v12.8h, v12.8h , v18.8h add v18.8h, v28.8h , v20.8h smlal v30.4s, v12.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal2 v22.4s, v12.8h, v26.8h smlsl2 v22.4s, v18.8h, v24.8h mov v12.8b, v27.8b mov v27.8b, v26.8b sqrshrun v16.4h, v30.4s, #10 mov v6.16b, v2.16b mov v7.16b, v3.16b sqrshrun v17.4h, v22.4s, #10 mov v2.16b, v10.16b mov v3.16b, v11.16b mov v10.16b, v0.16b mov v11.16b, v1.16b subs x4, x4, #4 uqxtn v13.8b, v16.8h uqxtn v17.8b, v17.8h mov v13.s[1], v17.s[0] mov v0.16b, v8.16b mov v1.16b, v9.16b mov v8.16b, v4.16b mov v9.16b, v5.16b mov v4.16b, v10.16b mov v5.16b, v11.16b st1 {v12.2s}, [x1], x3 // store row 2 st1 {v13.2s}, [x1], x3 // store row 3 bgt loop_8 //if height =8 loop b end_func loop_4_start: ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] loop_4: ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] uaddl v14.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] uaddl v12.8h, v0.8b, v10.8b // temp = src[0_0] + src[5_0] uaddl v16.8h, v2.8b, v8.8b // temp2 = src[1_0] + src[4_0] mla v12.8h, v14.8h , v26.8h // temp += temp1 * 20 uaddl v18.8h, v5.8b, v7.8b // temp1 = src[2_0] + src[3_0] uaddl v14.8h, v1.8b, v11.8b // temp = src[0_0] + src[5_0] uaddl v22.8h, v3.8b, v9.8b // temp2 = src[1_0] + src[4_0] mla v14.8h, v18.8h , v26.8h // temp += temp1 * 20 mls v12.8h, v16.8h , v24.8h // temp -= temp2 * 5 ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[6_0] uaddl v16.8h, v6.8b, v8.8b mls v14.8h, v22.8h , v24.8h // temp -= temp2 * 5 //Q6 and Q7 have filtered values uaddl v28.8h, v2.8b, v0.8b ext v22.16b, v12.16b , v14.16b , #10 uaddl v18.8h, v4.8b, v10.8b mla v28.8h, v16.8h , v26.8h saddl v30.4s, v12.4h, v22.4h saddl v22.4s, v13.4h, v23.4h ext v16.16b, v12.16b , v14.16b , #4 mls v28.8h, v18.8h , v24.8h ext v18.16b, v12.16b , v14.16b , #6 ext v20.16b, v12.16b , v14.16b , #8 ext v14.16b, v12.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v14.8h , v20.8h uaddl v20.8h, v7.8b, v9.8b smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal v22.4s, v17.4h, v26.4h smlsl v22.4s, v19.4h, v24.4h uaddl v14.8h, v3.8b, v1.8b mla v14.8h, v20.8h , v26.8h sqrshrun v12.4h, v30.4s, #10 uaddl v16.8h, v5.8b, v11.8b sqrshrun v13.4h, v22.4s, #10 mls v14.8h, v16.8h , v24.8h ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[7_0] uqxtn v25.8b, v12.8h uaddl v16.8h, v8.8b, v10.8b ext v22.16b, v28.16b , v14.16b , #10 uaddl v20.8h, v4.8b, v2.8b saddl v30.4s, v28.4h, v22.4h mla v20.8h, v16.8h , v26.8h saddl v22.4s, v29.4h, v23.4h ext v16.16b, v28.16b , v14.16b , #4 ext v18.16b, v28.16b , v14.16b , #6 ext v12.16b, v28.16b , v14.16b , #8 ext v14.16b, v28.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v12.8h , v14.8h smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal v22.4s, v17.4h, v26.4h smlsl v22.4s, v19.4h, v24.4h uaddl v18.8h, v6.8b, v0.8b sqrshrun v16.4h, v30.4s, #10 sqrshrun v17.4h, v22.4s, #10 mov v12.8b, v25.8b mov v25.8b, v24.8b uaddl v28.8h, v9.8b, v11.8b uqxtn v13.8b, v16.8h uaddl v14.8h, v5.8b, v3.8b uaddl v22.8h, v7.8b, v1.8b mls v20.8h, v18.8h , v24.8h st1 {v12.s}[0], [x1], x3 // store row 0 mla v14.8h, v28.8h , v26.8h ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[8_0] uaddl v30.8h, v10.8b, v0.8b uaddl v28.8h, v6.8b, v4.8b mls v14.8h, v22.8h , v24.8h st1 {v13.s}[0], [x1], x3 //store row 1 mla v28.8h, v30.8h , v26.8h ext v22.16b, v20.16b , v14.16b , #10 saddl v30.4s, v20.4h, v22.4h saddl v22.4s, v21.4h, v23.4h ext v16.16b, v20.16b , v14.16b , #4 ext v18.16b, v20.16b , v14.16b , #6 ext v12.16b, v20.16b , v14.16b , #8 ext v14.16b, v20.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v14.8h , v12.8h uaddl v20.8h, v8.8b, v2.8b smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal v22.4s, v17.4h, v26.4h smlsl v22.4s, v19.4h, v24.4h uaddl v18.8h, v11.8b, v1.8b uaddl v16.8h, v7.8b, v5.8b sqrshrun v12.4h, v30.4s, #10 uaddl v30.8h, v9.8b, v3.8b mla v16.8h, v18.8h , v26.8h sqrshrun v13.4h, v22.4s, #10 mls v28.8h, v20.8h , v24.8h mls v16.8h, v30.8h , v24.8h uqxtn v27.8b, v12.8h ext v22.16b, v28.16b , v16.16b , #10 saddl v30.4s, v28.4h, v22.4h saddl v22.4s, v29.4h, v23.4h ext v12.16b, v28.16b , v16.16b , #4 ext v18.16b, v28.16b , v16.16b , #6 ext v20.16b, v28.16b , v16.16b , #8 ext v28.16b, v28.16b , v16.16b , #2 add v12.8h, v12.8h , v18.8h add v18.8h, v28.8h , v20.8h smlal v30.4s, v12.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal v22.4s, v13.4h, v26.4h smlsl v22.4s, v19.4h, v24.4h mov v12.8b, v27.8b mov v27.8b, v26.8b sqrshrun v16.4h, v30.4s, #10 mov v6.16b, v2.16b mov v7.16b, v3.16b sqrshrun v17.4h, v22.4s, #10 mov v2.16b, v10.16b mov v3.16b, v11.16b mov v10.16b, v0.16b mov v11.16b, v1.16b subs x4, x4, #4 uqxtn v13.8b, v16.8h mov v0.16b, v8.16b mov v1.16b, v9.16b mov v8.16b, v4.16b mov v9.16b, v5.16b mov v4.16b, v10.16b mov v5.16b, v11.16b st1 {v12.s}[0], [x1], x3 // store row 2 st1 {v13.s}[0], [x1], x3 // store row 3 bgt loop_4 end_func: //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_hpel_vert_qpel_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_inter_pred_luma_horz_hpel_vert_qpel_av8.s //* //* @brief //* Contains function definitions for inter prediction interpolation. //* //* @author //* Mohit //* //* @par List of Functions: //* //* - ih264_inter_pred_luma_horz_hpel_vert_qpel_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_inter_pred_filters.c // ///** ///** ///** //******************************************************************************* //* //* @brief //* This function implements a two stage cascaded six tap filter. It //* applies the six tap filter in the horizontal direction on the //* predictor values, followed by applying the same filter in the //* vertical direction on the output of the first stage. It then averages //* the output of the 1st stage and the output of the 2nd stage to obtain //* the quarter pel values. The six tap filtering operation is described //* in sec 8.4.2.2.1 titled "Luma sample interpolation process". //* //* @par Description: //* This function is called to obtain pixels lying at the following //* location (1/2,1/4) or (1/2,3/4). The function interpolates //* the predictors first in the horizontal direction and then in the //* vertical direction to output the (1/2,1/2). It then averages //* the output of the 2nd stage and (1/2,1/2) value to obtain (1/2,1/4) //* or (1/2,3/4) depending on the offset. //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @param[in] pu1_tmp: temporary buffer //* //* @param[in] dydx: x and y reference offset for qpel calculations //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/; //void ih264_inter_pred_luma_horz_hpel_vert_qpel(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd,, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd, // UWORD8* pu1_tmp, // UWORD32 dydx) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ht // w5 => wd // x6 => *pu1_tmp // w7 => dydx .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 ih264_inter_pred_luma_horz_hpel_vert_qpel_av8: // store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x2, w2 sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 sub x0, x0, x2, lsl #1 // pu1_src-2*src_strd sub x0, x0, #2 // pu1_src-2 mov x9, x6 // by writing to w7 here, we clear the upper half of x7 lsr w7, w7, #3 // dydx >> 2 followed by dydx & 0x3 and dydx>>1 to obtain the deciding bit add x7, x7, #2 mov x6, #48 madd x7, x7, x6, x9 subs x12, x5, #4 //if wd=4 branch to loop_4 beq loop_4_start subs x12, x5, #8 //if wd=8 branch to loop_8 beq loop_8_start //when wd=16 movi v22.8h, #20 // Filter coeff 0x14 into Q11 movi v24.8h, #5 // Filter coeff 0x5 into Q12 add x8, x0, #8 add x14, x1, #8 add x10, x9, #8 mov x12, x4 add x11, x7, #8 loop_16_lowhalf_start: ld1 {v0.2s, v1.2s}, [x0], x2 // row -2 load for horizontal filter ext v5.8b, v0.8b , v1.8b , #5 uaddl v6.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v8.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v6.8h, v8.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v8.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row -1 load for horizontal filter mls v6.8h, v8.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v8.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v10.8h, v2.8b, v3.8b st1 {v6.4s}, [x9], x6 // store temp buffer 0 ext v4.8b, v0.8b , v1.8b , #4 mla v8.8h, v10.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v10.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row 0 load for horizontal filter mls v8.8h, v10.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v10.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v12.8h, v2.8b, v3.8b st1 {v8.4s}, [x9], x6 // store temp buffer 1 ext v4.8b, v0.8b , v1.8b , #4 mla v10.8h, v12.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v12.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row 1 load for horizontal filter mls v10.8h, v12.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v12.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v14.8h, v2.8b, v3.8b st1 {v10.4s}, [x9], x6 // store temp buffer 2 ext v4.8b, v0.8b , v1.8b , #4 mla v12.8h, v14.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v14.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row 2 load for horizontal filter mls v12.8h, v14.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v14.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v16.8h, v2.8b, v3.8b st1 {v12.4s}, [x9], x6 // store temp buffer 3 ext v4.8b, v0.8b , v1.8b , #4 mla v14.8h, v16.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v16.8h, v1.8b, v4.8b mls v14.8h, v16.8h , v24.8h loop_16_lowhalf: ld1 {v0.2s, v1.2s}, [x0], x2 // row 3 load for horizontal filter ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v16.8h, v0.8b, v5.8b st1 {v14.4s}, [x9], x6 // store temp buffer 4 uaddl v18.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v16.8h, v18.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 add v28.8h, v8.8h , v14.8h uaddl v18.8h, v1.8b, v4.8b add v30.8h, v10.8h , v12.8h mls v16.8h, v18.8h , v24.8h ld1 {v0.2s, v1.2s}, [x0], x2 // row 4 load for hoorizontal filter ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v20.8h, v0.8b, v5.8b st1 {v16.4s}, [x9], x6 // store temp buffer x5 saddl v18.4s, v6.4h, v16.4h ld1 {v26.4s}, [x7], x6 // load from temp buffer 0 saddl2 v6.4s, v6.8h, v16.8h sqrshrun v26.8b, v26.8h, #5 smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v28.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v28.8h, v24.8h uaddl v2.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v20.8h, v2.8h , v22.8h sqrshrun v18.4h, v18.4s, #10 ext v1.8b, v0.8b , v1.8b , #1 sqrshrun v19.4h, v6.4s, #10 add v28.8h, v10.8h , v16.8h uaddl v2.8h, v1.8b, v4.8b add v30.8h, v12.8h , v14.8h mls v20.8h, v2.8h , v24.8h uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ld1 {v0.2s, v1.2s}, [x0], x2 // row 5 load for horizontal filter urhadd v26.8b, v18.8b , v26.8b ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 st1 {v20.4s}, [x9], x6 // store temp buffer x6 saddl v18.4s, v8.4h, v20.4h saddl2 v6.4s, v8.8h, v20.8h ld1 {v8.4s}, [x7], x6 //load from temp buffer 1 st1 {v26.2s}, [x1], x3 // store row 0 smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v28.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v28.8h, v24.8h sqrshrun v28.8b, v8.8h, #5 ext v3.8b, v0.8b , v1.8b , #3 uaddl v8.8h, v0.8b, v5.8b uaddl v2.8h, v2.8b, v3.8b sqrshrun v18.4h, v18.4s, #10 ext v4.8b, v0.8b , v1.8b , #4 sqrshrun v19.4h, v6.4s, #10 mla v8.8h, v2.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 add v26.8h, v12.8h , v20.8h uaddl v2.8h, v1.8b, v4.8b uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] add v30.8h, v14.8h , v16.8h mls v8.8h, v2.8h , v24.8h ld1 {v0.2s, v1.2s}, [x0], x2 // row 6 load for horizontal filter urhadd v28.8b, v28.8b , v18.8b ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 st1 {v28.2s}, [x1], x3 // store row 1 uaddl v28.8h, v0.8b, v5.8b st1 {v8.4s}, [x9], x6 // store temp buffer x7 saddl v18.4s, v10.4h, v8.4h saddl2 v6.4s, v10.8h, v8.8h ld1 {v10.4s}, [x7], x6 // load from temp buffer 2 smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v26.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v26.8h, v24.8h sqrshrun v26.8b, v10.8h, #5 uaddl v2.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v28.8h, v2.8h , v22.8h sqrshrun v18.4h, v18.4s, #10 ext v1.8b, v0.8b , v1.8b , #1 sqrshrun v19.4h, v6.4s, #10 add v10.8h, v14.8h , v8.8h uaddl v2.8h, v1.8b, v4.8b add v30.8h, v16.8h , v20.8h mls v28.8h, v2.8h , v24.8h uqxtn v27.8b, v18.8h uqxtn v19.8b, v19.8h mov v27.s[1], v19.s[0] saddl v18.4s, v12.4h, v28.4h saddl2 v6.4s, v12.8h, v28.8h urhadd v26.8b, v26.8b , v27.8b smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v10.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v10.8h, v24.8h st1 {v26.2s}, [x1], x3 // store row 2 st1 {v28.2s, v29.2s}, [x9] sqrshrun v18.4h, v18.4s, #10 mov v10.16b, v20.16b mov v11.16b, v21.16b ld1 {v30.4s}, [x7], x6 // load from temp buffer 3 sqrshrun v19.4h, v6.4s, #10 subs x4, x4, #4 sqrshrun v30.8b, v30.8h, #5 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] mov v12.16b, v8.16b mov v13.16b, v9.16b mov v6.16b, v14.16b mov v7.16b, v15.16b urhadd v30.8b, v18.8b , v30.8b mov v8.16b, v16.16b mov v9.16b, v17.16b mov v14.16b, v28.16b mov v15.16b, v29.16b st1 {v30.2s}, [x1], x3 // store row 3 bgt loop_16_lowhalf // looping if height =16 loop_16_highhalf_start: ld1 {v0.2s, v1.2s}, [x8], x2 ext v5.8b, v0.8b , v1.8b , #5 uaddl v6.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v8.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v6.8h, v8.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v8.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x8], x2 mls v6.8h, v8.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v8.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v10.8h, v2.8b, v3.8b st1 {v6.4s}, [x10], x6 ext v4.8b, v0.8b , v1.8b , #4 mla v8.8h, v10.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v10.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x8], x2 mls v8.8h, v10.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v10.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v12.8h, v2.8b, v3.8b st1 {v8.4s}, [x10], x6 ext v4.8b, v0.8b , v1.8b , #4 mla v10.8h, v12.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v12.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x8], x2 mls v10.8h, v12.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v12.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v14.8h, v2.8b, v3.8b st1 {v10.4s}, [x10], x6 ext v4.8b, v0.8b , v1.8b , #4 mla v12.8h, v14.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v14.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x8], x2 mls v12.8h, v14.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v14.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v16.8h, v2.8b, v3.8b st1 {v12.4s}, [x10], x6 ext v4.8b, v0.8b , v1.8b , #4 mla v14.8h, v16.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v16.8h, v1.8b, v4.8b mls v14.8h, v16.8h , v24.8h loop_16_highhalf: ld1 {v0.2s, v1.2s}, [x8], x2 ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v16.8h, v0.8b, v5.8b st1 {v14.4s}, [x10], x6 uaddl v18.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v16.8h, v18.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 add v28.8h, v8.8h , v14.8h uaddl v18.8h, v1.8b, v4.8b add v30.8h, v10.8h , v12.8h mls v16.8h, v18.8h , v24.8h ld1 {v0.2s, v1.2s}, [x8], x2 ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v20.8h, v0.8b, v5.8b st1 {v16.4s}, [x10], x6 saddl v18.4s, v6.4h, v16.4h ld1 {v26.4s}, [x11], x6 saddl2 v6.4s, v6.8h, v16.8h sqrshrun v26.8b, v26.8h, #5 smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v28.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v28.8h, v24.8h uaddl v2.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v20.8h, v2.8h , v22.8h sqrshrun v18.4h, v18.4s, #10 ext v1.8b, v0.8b , v1.8b , #1 sqrshrun v19.4h, v6.4s, #10 add v28.8h, v10.8h , v16.8h uaddl v2.8h, v1.8b, v4.8b add v30.8h, v12.8h , v14.8h mls v20.8h, v2.8h , v24.8h uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ld1 {v0.2s, v1.2s}, [x8], x2 urhadd v26.8b, v18.8b , v26.8b ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 st1 {v20.4s}, [x10], x6 saddl v18.4s, v8.4h, v20.4h saddl2 v6.4s, v8.8h, v20.8h ld1 {v8.4s}, [x11], x6 st1 {v26.2s}, [x14], x3 //store row 0 smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v28.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v28.8h, v24.8h sqrshrun v28.8b, v8.8h, #5 ext v3.8b, v0.8b , v1.8b , #3 uaddl v8.8h, v0.8b, v5.8b uaddl v2.8h, v2.8b, v3.8b sqrshrun v18.4h, v18.4s, #10 ext v4.8b, v0.8b , v1.8b , #4 sqrshrun v19.4h, v6.4s, #10 mla v8.8h, v2.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 add v26.8h, v12.8h , v20.8h uaddl v2.8h, v1.8b, v4.8b uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] add v30.8h, v14.8h , v16.8h mls v8.8h, v2.8h , v24.8h ld1 {v0.2s, v1.2s}, [x8], x2 urhadd v28.8b, v28.8b , v18.8b ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 st1 {v28.2s}, [x14], x3 //store row 1 uaddl v28.8h, v0.8b, v5.8b st1 {v8.4s}, [x10], x6 saddl v18.4s, v10.4h, v8.4h saddl2 v6.4s, v10.8h, v8.8h ld1 {v10.4s}, [x11], x6 smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v26.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v26.8h, v24.8h sqrshrun v26.8b, v10.8h, #5 uaddl v2.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v28.8h, v2.8h , v22.8h sqrshrun v18.4h, v18.4s, #10 ext v1.8b, v0.8b , v1.8b , #1 sqrshrun v19.4h, v6.4s, #10 add v10.8h, v14.8h , v8.8h uaddl v2.8h, v1.8b, v4.8b add v30.8h, v16.8h , v20.8h mls v28.8h, v2.8h , v24.8h uqxtn v27.8b, v18.8h uqxtn v19.8b, v19.8h mov v27.s[1], v19.s[0] saddl v18.4s, v12.4h, v28.4h saddl2 v6.4s, v12.8h, v28.8h urhadd v26.8b, v26.8b , v27.8b smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v10.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v10.8h, v24.8h st1 {v26.2s}, [x14], x3 // store row 2 st1 {v28.4s}, [x10] sqrshrun v18.4h, v18.4s, #10 mov v10.16b, v20.16b mov v11.16b, v21.16b ld1 {v30.4s}, [x11], x6 sqrshrun v19.4h, v6.4s, #10 subs x12, x12, #4 sqrshrun v30.8b, v30.8h, #5 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] mov v12.16b, v8.16b mov v13.16b, v9.16b mov v6.16b, v14.16b mov v7.16b, v15.16b urhadd v30.8b, v18.8b , v30.8b mov v8.16b, v16.16b mov v9.16b, v17.16b mov v14.16b, v28.16b mov v15.16b, v29.16b st1 {v30.2s}, [x14], x3 // store row 3 bgt loop_16_highhalf // looping if height = 8 or 16 b end_func loop_8_start: movi v22.8h, #0x14 // Filter coeff 20 into Q11 movi v24.8h, #5 // Filter coeff 5 into Q12 ld1 {v0.2s, v1.2s}, [x0], x2 // row -2 load for horizontal filter ext v5.8b, v0.8b , v1.8b , #5 uaddl v6.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v8.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v6.8h, v8.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v8.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row -1 load for horizontal filter mls v6.8h, v8.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v8.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v10.8h, v2.8b, v3.8b st1 {v6.4s}, [x9], x6 // store temp buffer 0 ext v4.8b, v0.8b , v1.8b , #4 mla v8.8h, v10.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v10.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row 0 load for horizontal filter mls v8.8h, v10.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v10.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v12.8h, v2.8b, v3.8b st1 {v8.4s}, [x9], x6 // store temp buffer 1 ext v4.8b, v0.8b , v1.8b , #4 mla v10.8h, v12.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v12.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row 1 load for horizontal filter mls v10.8h, v12.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v12.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v14.8h, v2.8b, v3.8b st1 {v10.4s}, [x9], x6 // store temp buffer 2 ext v4.8b, v0.8b , v1.8b , #4 mla v12.8h, v14.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v14.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row 2 load for horizontal filter mls v12.8h, v14.8h , v24.8h ext v5.8b, v0.8b , v1.8b , #5 uaddl v14.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v16.8h, v2.8b, v3.8b st1 {v12.4s}, [x9], x6 // store temp buffer 3 ext v4.8b, v0.8b , v1.8b , #4 mla v14.8h, v16.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 uaddl v16.8h, v1.8b, v4.8b mls v14.8h, v16.8h , v24.8h loop_8: ld1 {v0.2s, v1.2s}, [x0], x2 // row 3 load for horizontal filter ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v16.8h, v0.8b, v5.8b st1 {v14.4s}, [x9], x6 // store temp buffer 4 uaddl v18.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v16.8h, v18.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 add v28.8h, v8.8h , v14.8h uaddl v18.8h, v1.8b, v4.8b add v30.8h, v10.8h , v12.8h mls v16.8h, v18.8h , v24.8h ld1 {v0.2s, v1.2s} , [x0], x2 // row 4 load for hoorizontal filter ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v20.8h, v0.8b, v5.8b st1 {v16.4s}, [x9], x6 // store temp buffer x5 saddl v18.4s, v6.4h, v16.4h ld1 {v26.4s}, [x7], x6 // load from temp buffer 0 saddl2 v6.4s, v6.8h, v16.8h sqrshrun v26.8b, v26.8h, #5 smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v28.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v28.8h, v24.8h uaddl v2.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v20.8h, v2.8h , v22.8h sqrshrun v18.4h, v18.4s, #10 ext v1.8b, v0.8b , v1.8b , #1 sqrshrun v19.4h, v6.4s, #10 add v28.8h, v10.8h , v16.8h uaddl v2.8h, v1.8b, v4.8b add v30.8h, v12.8h , v14.8h mls v20.8h, v2.8h , v24.8h uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ld1 {v0.2s, v1.2s}, [x0], x2 // row 5 load for horizontal filter urhadd v26.8b, v18.8b , v26.8b ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 st1 {v20.4s}, [x9], x6 // store temp buffer x6 saddl v18.4s, v8.4h, v20.4h saddl2 v6.4s, v8.8h, v20.8h ld1 {v8.4s}, [x7], x6 //load from temp buffer 1 st1 {v26.2s}, [x1], x3 // store row 0 smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v28.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v28.8h, v24.8h sqrshrun v28.8b, v8.8h, #5 ext v3.8b, v0.8b , v1.8b , #3 uaddl v8.8h, v0.8b, v5.8b uaddl v2.8h, v2.8b, v3.8b sqrshrun v18.4h, v18.4s, #10 ext v4.8b, v0.8b , v1.8b , #4 sqrshrun v19.4h, v6.4s, #10 mla v8.8h, v2.8h , v22.8h ext v1.8b, v0.8b , v1.8b , #1 add v26.8h, v12.8h , v20.8h uaddl v2.8h, v1.8b, v4.8b uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] add v30.8h, v14.8h , v16.8h mls v8.8h, v2.8h , v24.8h ld1 {v0.2s, v1.2s}, [x0], x2 // row 6 load for horizontal filter urhadd v28.8b, v28.8b , v18.8b ext v5.8b, v0.8b , v1.8b , #5 ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 st1 {v28.2s}, [x1], x3 // store row 1 uaddl v28.8h, v0.8b, v5.8b st1 {v8.4s}, [x9], x6 // store temp buffer x7 saddl v18.4s, v10.4h, v8.4h saddl2 v6.4s, v10.8h, v8.8h ld1 {v10.4s}, [x7], x6 // load from temp buffer 2 smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v26.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v26.8h, v24.8h sqrshrun v26.8b, v10.8h, #5 uaddl v2.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v28.8h, v2.8h , v22.8h sqrshrun v18.4h, v18.4s, #10 ext v1.8b, v0.8b , v1.8b , #1 sqrshrun v19.4h, v6.4s, #10 add v10.8h, v14.8h , v8.8h uaddl v2.8h, v1.8b, v4.8b add v30.8h, v16.8h , v20.8h mls v28.8h, v2.8h , v24.8h uqxtn v27.8b, v18.8h uqxtn v19.8b, v19.8h mov v27.s[1], v19.s[0] saddl v18.4s, v12.4h, v28.4h saddl2 v6.4s, v12.8h, v28.8h urhadd v26.8b, v26.8b , v27.8b smlal v18.4s, v30.4h, v22.4h smlsl v18.4s, v10.4h, v24.4h smlal2 v6.4s, v30.8h, v22.8h smlsl2 v6.4s, v10.8h, v24.8h st1 {v26.2s}, [x1], x3 // store row 2 st1 {v28.2s, v29.2s}, [x9] sqrshrun v18.4h, v18.4s, #10 mov v10.16b, v20.16b mov v11.16b, v21.16b ld1 {v30.4s}, [x7], x6 // load from temp buffer 3 sqrshrun v19.4h, v6.4s, #10 subs x4, x4, #4 sqrshrun v30.8b, v30.8h, #5 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] mov v12.16b, v8.16b mov v13.16b, v9.16b mov v6.16b, v14.16b mov v7.16b, v15.16b urhadd v30.8b, v18.8b , v30.8b mov v8.16b, v16.16b mov v9.16b, v17.16b mov v14.16b, v28.16b mov v15.16b, v29.16b st1 {v30.2s}, [x1], x3 // store row 3 bgt loop_8 //if height =8 or 16 loop b end_func loop_4_start: movi v22.8h, #20 // Filter coeff 20 into D22 movi v23.8h, #5 // Filter coeff 5 into D23 ld1 {v0.2s, v1.2s}, [x0], x2 //row -2 load ext v5.8b, v0.8b , v1.8b , #5 uaddl v6.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v8.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v6.4h, v8.4h , v22.4h ext v1.8b, v0.8b , v1.8b , #1 uaddl v8.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row -1 load mls v6.4h, v8.4h , v23.4h ext v5.8b, v0.8b , v1.8b , #5 uaddl v8.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v10.8h, v2.8b, v3.8b st1 {v6.2s}, [x9], x6 // store temp buffer 0 ext v4.8b, v0.8b , v1.8b , #4 mla v8.4h, v10.4h , v22.4h ext v1.8b, v0.8b , v1.8b , #1 uaddl v10.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row 0 load mls v8.4h, v10.4h , v23.4h ext v5.8b, v0.8b , v1.8b , #5 uaddl v10.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v12.8h, v2.8b, v3.8b st1 {v8.2s}, [x9], x6 // store temp buffer 1 ext v4.8b, v0.8b , v1.8b , #4 mla v10.4h, v12.4h , v22.4h ext v1.8b, v0.8b , v1.8b , #1 uaddl v12.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row 1 load mls v10.4h, v12.4h , v23.4h ext v5.8b, v0.8b , v1.8b , #5 uaddl v12.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v14.8h, v2.8b, v3.8b st1 {v10.2s}, [x9], x6 // store temp buffer 2 ext v4.8b, v0.8b , v1.8b , #4 mla v12.4h, v14.4h , v22.4h ext v1.8b, v0.8b , v1.8b , #1 uaddl v14.8h, v1.8b, v4.8b ld1 {v0.2s, v1.2s}, [x0], x2 // row 2 load mls v12.4h, v14.4h , v23.4h ext v5.8b, v0.8b , v1.8b , #5 uaddl v14.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v16.8h, v2.8b, v3.8b ext v4.8b, v0.8b , v1.8b , #4 mla v14.4h, v16.4h , v22.4h ext v1.8b, v0.8b , v1.8b , #1 uaddl v16.8h, v1.8b, v4.8b st1 {v12.2s}, [x9], x6 // store temp buffer 3 mls v14.4h, v16.4h , v23.4h loop_4: ld1 {v0.2s, v1.2s}, [x0], x2 // row 3 load ext v5.8b, v0.8b , v1.8b , #5 uaddl v16.8h, v0.8b, v5.8b ext v2.8b, v0.8b , v1.8b , #2 ext v3.8b, v0.8b , v1.8b , #3 uaddl v18.8h, v2.8b, v3.8b st1 {v14.2s}, [x9], x6 // store temp buffer 4 ext v4.8b, v0.8b , v1.8b , #4 mla v16.4h, v18.4h , v22.4h ext v1.8b, v0.8b , v1.8b , #1 uaddl v18.8h, v1.8b, v4.8b add v2.4h, v10.4h , v12.4h mls v16.4h, v18.4h , v23.4h add v3.4h, v8.4h , v14.4h ld1 {v18.2s, v19.2s}, [x0], x2 // row 4 load ext v25.8b, v18.8b , v19.8b , #5 uaddl v26.8h, v18.8b, v25.8b ext v20.8b, v18.8b , v19.8b , #2 st1 {v16.2s}, [x9], x6 // store temp buffer 5 saddl v0.4s, v6.4h, v16.4h smlal v0.4s, v2.4h, v22.4h ext v21.8b, v18.8b , v19.8b , #3 uaddl v28.8h, v20.8b, v21.8b ext v24.8b, v18.8b , v19.8b , #4 smlsl v0.4s, v3.4h, v23.4h mla v26.4h, v28.4h , v22.4h ext v19.8b, v18.8b , v19.8b , #1 uaddl v28.8h, v19.8b, v24.8b add v2.4h, v12.4h , v14.4h mls v26.4h, v28.4h , v23.4h sqrshrun v0.4h, v0.4s, #0xa add v3.4h, v10.4h , v16.4h ld1 {v18.2s, v19.2s}, [x0], x2 // row 5 load ext v25.8b, v18.8b , v19.8b , #5 uqxtn v11.8b, v0.8h uaddl v28.8h, v18.8b, v25.8b st1 {v26.2s}, [x9], x6 // store temp buffer 6 //Q3 available here ld1 {v6.2s}, [x7], x6 // load from temp buffer 0 ld1 {v7.2s}, [x7], x6 // load from temp buffer 1 sqrshrun v9.8b, v6.8h, #5 sqrshrun v7.8b, v7.8h, #5 mov v9.s[1], v7.s[0] ext v20.8b, v18.8b , v19.8b , #2 saddl v0.4s, v8.4h, v26.4h smlal v0.4s, v2.4h, v22.4h ext v21.8b, v18.8b , v19.8b , #3 uaddl v6.8h, v20.8b, v21.8b ext v24.8b, v18.8b , v19.8b , #4 smlsl v0.4s, v3.4h, v23.4h mla v28.4h, v6.4h , v22.4h ext v19.8b, v18.8b , v19.8b , #1 uaddl v6.8h, v19.8b, v24.8b add v2.4h, v14.4h , v16.4h mls v28.4h, v6.4h , v23.4h sqrshrun v0.4h, v0.4s, #0xa add v3.4h, v12.4h , v26.4h ld1 {v18.2s, v19.2s}, [x0], x2 // row 6 load ext v25.8b, v18.8b , v19.8b , #5 uqxtn v13.8b, v0.8h trn1 v11.2s, v11.2s, v13.2s trn2 v13.2s, v11.2s, v13.2s saddl v0.4s, v10.4h, v28.4h urhadd v9.8b, v9.8b , v11.8b st1 {v28.2s}, [x9], x6 // store temp buffer 7 smlal v0.4s, v2.4h, v22.4h uaddl v30.8h, v18.8b, v25.8b st1 {v9.s}[0], [x1], x3 // store row 0 ext v20.8b, v18.8b , v19.8b , #2 st1 {v9.s}[1], [x1], x3 // store row 1 ext v21.8b, v18.8b , v19.8b , #3 smlsl v0.4s, v3.4h, v23.4h uaddl v8.8h, v20.8b, v21.8b ext v24.8b, v18.8b , v19.8b , #4 mla v30.4h, v8.4h , v22.4h ext v19.8b, v18.8b , v19.8b , #1 uaddl v8.8h, v19.8b, v24.8b sqrshrun v0.4h, v0.4s, #0xa add v2.4h, v16.4h , v26.4h mls v30.4h, v8.4h , v23.4h uqxtn v4.8b, v0.8h add v3.4h, v14.4h , v28.4h saddl v0.4s, v12.4h, v30.4h st1 {v30.2s}, [x9] smlal v0.4s, v2.4h, v22.4h ld1 {v8.2s}, [x7], x6 // load from temp buffer 2 ld1 {v9.2s}, [x7], x6 // load from temp buffer 3 smlsl v0.4s, v3.4h, v23.4h subs x4, x4, #4 sqrshrun v10.8b, v8.8h, #5 sqrshrun v9.8b, v9.8h, #5 mov v10.s[1], v9.s[0] mov v12.8b, v28.8b sqrshrun v0.4h, v0.4s, #0xa mov v6.8b, v14.8b mov v8.8b, v16.8b uqxtn v5.8b, v0.8h trn1 v4.2s, v4.2s, v5.2s trn2 v5.2s, v4.2s, v5.2s urhadd v4.8b, v4.8b , v10.8b mov v10.8b, v26.8b mov v14.8b, v30.8b st1 {v4.s}[0], [x1], x3 // store row 2 st1 {v4.s}[1], [x1], x3 // store row 3 bgt loop_4 end_func: //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_inter_pred_luma_horz_qpel_av8.s //* //* @brief //* Contains function definitions for inter prediction horizontal quarter pel interpolation. //* //* @author //* Mohit //* //* @par List of Functions: //* //* - ih264_inter_pred_luma_horz_qpel_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_inter_pred_filters.c // ///** ///** //******************************************************************************* //* //* @brief //* Quarter pel interprediction luma filter for horizontal input //* //* @par Description: //* Applies a 6 tap horizontal filter .The output is clipped to 8 bits //* sec 8.4.2.2.1 titled "Luma sample interpolation process" //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* // @param[in] pu1_tmp: temporary buffer: UNUSED in this function //* //* @param[in] dydx: x and y reference offset for qpel calculations. //* @returns //* // @remarks //* None //* //******************************************************************************* //*/ //void ih264_inter_pred_luma_horz ( // UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd, // UWORD8* pu1_tmp, // UWORD32 dydx) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ht // w5 => wd // w7 => dydx .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_inter_pred_luma_horz_qpel_av8 ih264_inter_pred_luma_horz_qpel_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x2, w2 sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 and x7, x7, #3 //Finds x-offset add x7, x0, x7, lsr #1 //pu1_src + (x_offset>>1) sub x0, x0, #2 //pu1_src-2 sub x14, x4, #16 movi v0.16b, #5 //filter coeff subs x12, x5, #8 //if wd=8 branch to loop_8 movi v1.16b, #20 //filter coeff beq loop_8 subs x12, x5, #4 //if wd=4 branch to loop_4 beq loop_4 loop_16: //when wd=16 //// Processing row0 and row1 ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row0 add x14, x14, #1 //for checking loop ext v31.8b, v2.8b , v3.8b , #5 ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row1 ext v30.8b, v3.8b , v4.8b , #5 uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) ext v28.8b, v5.8b , v6.8b , #5 uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row0) ext v27.8b, v6.8b , v7.8b , #5 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) ext v31.8b, v2.8b , v3.8b , #2 uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row1) ext v30.8b, v3.8b , v4.8b , #2 umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) ext v28.8b, v5.8b , v6.8b , #2 umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row0) ext v27.8b, v6.8b , v7.8b , #2 umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) ext v31.8b, v2.8b , v3.8b , #3 umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row1) ext v30.8b, v3.8b , v4.8b , #3 umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) ext v28.8b, v5.8b , v6.8b , #3 umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row0) ext v27.8b, v6.8b , v7.8b , #3 umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) ext v31.8b, v2.8b , v3.8b , #1 umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row1) ext v30.8b, v3.8b , v4.8b , #1 umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) ext v28.8b, v5.8b , v6.8b , #1 umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row0) ext v27.8b, v6.8b , v7.8b , #1 umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) ext v31.8b, v2.8b , v3.8b , #4 umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row1) ext v30.8b, v3.8b , v4.8b , #4 umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) ext v28.8b, v5.8b , v6.8b , #4 umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row0) ext v27.8b, v6.8b , v7.8b , #4 umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row2 umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row1) ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row0) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row3 sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row0) ext v31.8b, v2.8b , v3.8b , #5 urhadd v20.16b, v12.16b , v20.16b //Interpolation step for qpel calculation urhadd v21.16b, v13.16b , v21.16b //Interpolation step for qpel calculation sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row0 ext v30.8b, v3.8b , v4.8b , #5 sqrshrun v19.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row1) //// Processing row2 and row3 ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row1) ext v28.8b, v5.8b , v6.8b , #5 urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) st1 {v18.8b, v19.8b}, [x1], x3 ////Store dest row1 uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row2) ext v27.8b, v6.8b , v7.8b , #5 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) ext v31.8b, v2.8b , v3.8b , #2 uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row3) ext v30.8b, v3.8b , v4.8b , #2 umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) ext v27.8b, v6.8b , v7.8b , #2 umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row2) ext v28.8b, v5.8b , v6.8b , #2 umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) ext v31.8b, v2.8b , v3.8b , #3 umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row3) ext v30.8b, v3.8b , v4.8b , #3 umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) ext v28.8b, v5.8b , v6.8b , #3 umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row2) ext v27.8b, v6.8b , v7.8b , #3 umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) ext v31.8b, v2.8b , v3.8b , #1 umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row3) ext v30.8b, v3.8b , v4.8b , #1 umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) ext v28.8b, v5.8b , v6.8b , #1 umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row2) ext v27.8b, v6.8b , v7.8b , #1 umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) ext v31.8b, v2.8b , v3.8b , #4 umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row3) ext v30.8b, v3.8b , v4.8b , #4 umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) ext v28.8b, v5.8b , v6.8b , #4 umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row2) ext v27.8b, v6.8b , v7.8b , #4 umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row4 umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row3) ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row2) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row5 sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row2) ext v31.8b, v2.8b , v3.8b , #5 urhadd v20.16b, v12.16b , v20.16b //Interpolation step for qpel calculation urhadd v21.16b, v13.16b , v21.16b //Interpolation step for qpel calculation sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) ext v30.8b, v3.8b , v4.8b , #5 st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row2 sqrshrun v19.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row3) ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row3) //// Processing row4 and row5 ext v28.8b, v5.8b , v6.8b , #5 urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row4) st1 {v18.8b, v19.8b}, [x1], x3 ////Store dest row3 uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row4) ext v27.8b, v6.8b , v7.8b , #5 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row5) ext v31.8b, v2.8b , v3.8b , #2 uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row5) ext v30.8b, v3.8b , v4.8b , #2 umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row4) ext v27.8b, v6.8b , v7.8b , #2 umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row4) ext v28.8b, v5.8b , v6.8b , #2 umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row5) ext v31.8b, v2.8b , v3.8b , #3 umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row5) ext v30.8b, v3.8b , v4.8b , #3 umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row4) ext v28.8b, v5.8b , v6.8b , #3 umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row4) ext v27.8b, v6.8b , v7.8b , #3 umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row5) ext v31.8b, v2.8b , v3.8b , #1 umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row5) ext v30.8b, v3.8b , v4.8b , #1 umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) ext v28.8b, v5.8b , v6.8b , #1 umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row4) ext v27.8b, v6.8b , v7.8b , #1 umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) ext v31.8b, v2.8b , v3.8b , #4 umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row5) ext v30.8b, v3.8b , v4.8b , #4 umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row4) ext v28.8b, v5.8b , v6.8b , #4 umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row4) ext v27.8b, v6.8b , v7.8b , #4 umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row5) ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row6 umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row5) ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row4) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row4) ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row7 sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row4) ext v31.8b, v2.8b , v3.8b , #5 urhadd v20.16b, v12.16b , v20.16b //Interpolation step for qpel calculation urhadd v21.16b, v13.16b , v21.16b //Interpolation step for qpel calculation sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row5) st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row4 ext v30.8b, v3.8b , v4.8b , #5 sqrshrun v19.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row5) ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row5) //// Processing row6 and row7 ext v28.8b, v5.8b , v6.8b , #5 urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row6) st1 {v18.8b, v19.8b}, [x1], x3 ////Store dest row5 uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row6) ext v27.8b, v6.8b , v7.8b , #5 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row7) ext v31.8b, v2.8b , v3.8b , #2 uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row7) ext v30.8b, v3.8b , v4.8b , #2 umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row6) ext v27.8b, v6.8b , v7.8b , #2 umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row6) ext v28.8b, v5.8b , v6.8b , #2 umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row7) ext v31.8b, v2.8b , v3.8b , #3 umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row7) ext v30.8b, v3.8b , v4.8b , #3 umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row6) ext v28.8b, v5.8b , v6.8b , #3 umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row6) ext v27.8b, v6.8b , v7.8b , #3 umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row7) ext v31.8b, v2.8b , v3.8b , #1 umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row7) ext v30.8b, v3.8b , v4.8b , #1 umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) ext v28.8b, v5.8b , v6.8b , #1 umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row6) ext v27.8b, v6.8b , v7.8b , #1 umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) ext v31.8b, v2.8b , v3.8b , #4 umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row7) ext v30.8b, v3.8b , v4.8b , #4 umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row6) ext v28.8b, v5.8b , v6.8b , #4 umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row6) ext v27.8b, v6.8b , v7.8b , #4 ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row6) sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row6) umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row7) sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row6) umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row7) urhadd v20.16b, v12.16b , v20.16b //Interpolation step for qpel calculation urhadd v21.16b, v13.16b , v21.16b //Interpolation step for qpel calculation ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row7) sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row7) st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row6 sqrshrun v19.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row7) urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation subs x12, x14, #1 // if height==16 - looping st1 {v18.8b, v19.8b}, [x1], x3 ////Store dest row7 beq loop_16 b end_func loop_8: //// Processing row0 and row1 ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row1 add x14, x14, #1 //for checking loop ext v28.8b, v5.8b , v6.8b , #5 ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row0 ext v25.8b, v5.8b , v6.8b , #2 ext v31.8b, v2.8b , v3.8b , #5 ext v24.8b, v5.8b , v6.8b , #3 ext v23.8b, v5.8b , v6.8b , #1 ext v22.8b, v5.8b , v6.8b , #4 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) ext v29.8b, v2.8b , v3.8b , #3 umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) ext v30.8b, v2.8b , v3.8b , #2 uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) ext v27.8b, v2.8b , v3.8b , #1 ext v26.8b, v2.8b , v3.8b , #4 ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row2 umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row3 sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) //// Processing row2 and row3 ext v28.8b, v5.8b , v6.8b , #5 ext v25.8b, v5.8b , v6.8b , #2 ext v31.8b, v2.8b , v3.8b , #5 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row0) ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row1) ext v24.8b, v5.8b , v6.8b , #3 ext v23.8b, v5.8b , v6.8b , #1 sqrshrun v19.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) ext v22.8b, v5.8b , v6.8b , #4 ext v29.8b, v2.8b , v3.8b , #3 umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation st1 {v18.8b}, [x1], x3 ////Store dest row0 st1 {v19.8b}, [x1], x3 ////Store dest row1 uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) ext v30.8b, v2.8b , v3.8b , #2 ext v27.8b, v2.8b , v3.8b , #1 ext v26.8b, v2.8b , v3.8b , #4 ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row4 umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row5 subs x9, x4, #4 sqrshrun v19.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row2) ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row3) ext v28.8b, v5.8b , v6.8b , #5 ext v25.8b, v5.8b , v6.8b , #2 ext v31.8b, v2.8b , v3.8b , #5 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row5) ext v24.8b, v5.8b , v6.8b , #3 sqrshrun v18.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) ext v22.8b, v5.8b , v6.8b , #4 ext v29.8b, v2.8b , v3.8b , #3 urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation st1 {v18.8b}, [x1], x3 ////Store dest row2 ext v30.8b, v2.8b , v3.8b , #2 uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row4) st1 {v19.8b}, [x1], x3 ////Store dest row3 beq end_func // Branch if height==4 //// Processing row4 and row5 ext v23.8b, v5.8b , v6.8b , #1 umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row5) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row5) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row5) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row5) ext v27.8b, v2.8b , v3.8b , #1 ext v26.8b, v2.8b , v3.8b , #4 ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row6 umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row4) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row4) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row4) sqrshrun v19.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row5) ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row7 ext v31.8b, v2.8b , v3.8b , #5 ext v28.8b, v5.8b , v6.8b , #5 ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row4) ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row5) ext v25.8b, v5.8b , v6.8b , #2 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row7) ext v24.8b, v5.8b , v6.8b , #3 ext v22.8b, v5.8b , v6.8b , #4 sqrshrun v18.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row4) ext v29.8b, v2.8b , v3.8b , #3 ext v30.8b, v2.8b , v3.8b , #2 urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation st1 {v18.8b}, [x1], x3 ////Store dest row4 ext v27.8b, v2.8b , v3.8b , #1 uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row6) ext v26.8b, v2.8b , v3.8b , #4 umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row6) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row6) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row6) //// Processing row6 and row7 st1 {v19.8b}, [x1], x3 ////Store dest row5 ext v23.8b, v5.8b , v6.8b , #1 umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row7) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row7) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row7) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row7) ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row6) ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row7) sqrshrun v18.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row6) subs x12, x14, #1 sqrshrun v19.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row7) urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation st1 {v18.8b}, [x1], x3 ////Store dest row6 st1 {v19.8b}, [x1], x3 ////Store dest row7 beq loop_8 //looping if height ==16 b end_func loop_4: ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row1 ext v28.8b, v5.8b , v6.8b , #5 ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row0 ext v25.8b, v5.8b , v6.8b , #2 ext v31.8b, v2.8b , v3.8b , #5 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) ext v24.8b, v5.8b , v6.8b , #3 ext v23.8b, v5.8b , v6.8b , #1 ext v22.8b, v5.8b , v6.8b , #4 ext v29.8b, v2.8b , v3.8b , #3 umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) ext v30.8b, v2.8b , v3.8b , #2 ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row0) ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row1) ext v27.8b, v2.8b , v3.8b , #1 ext v26.8b, v2.8b , v3.8b , #4 ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row2 umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row3 ext v28.8b, v5.8b , v6.8b , #5 ext v25.8b, v5.8b , v6.8b , #2 sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) ext v31.8b, v2.8b , v3.8b , #5 ext v24.8b, v5.8b , v6.8b , #3 ext v23.8b, v5.8b , v6.8b , #1 ext v22.8b, v5.8b , v6.8b , #4 ext v29.8b, v2.8b , v3.8b , #3 sqrshrun v19.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) ext v30.8b, v2.8b , v3.8b , #2 ext v27.8b, v2.8b , v3.8b , #1 //// Processing row2 and row3 urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation st1 {v18.s}[0], [x1], x3 ////Store dest row0 st1 {v19.s}[0], [x1], x3 ////Store dest row1 uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) ext v26.8b, v2.8b , v3.8b , #4 ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row2) ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row3) umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) sqrshrun v19.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) sqrshrun v18.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation st1 {v18.s}[0], [x1], x3 ////Store dest row2 subs x4, x4, #8 // Loop if height =8 st1 {v19.s}[0], [x1], x3 ////Store dest row3 beq loop_4 end_func: ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_vert_hpel_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_inter_pred_luma_horz_qpel_vert_hpel_av8.s //* //* @brief //* Contains function definitions for inter prediction interpolation. //* //* @author //* Mohit //* //* @par List of Functions: //* //* - ih264_inter_pred_luma_horz_qpel_vert_hpel_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_inter_pred_filters.c // ///** ///** ///** //******************************************************************************* //* //* @brief //* This function implements a two stage cascaded six tap filter. It //* applies the six tap filter in the vertical direction on the //* predictor values, followed by applying the same filter in the //* horizontal direction on the output of the first stage. It then averages //* the output of the 1st stage and the final stage to obtain the quarter //* pel values.The six tap filtering operation is described in sec 8.4.2.2.1 //* titled "Luma sample interpolation process". //* //* @par Description: //* This function is called to obtain pixels lying at the following //* location (1/4,1/2) or (3/4,1/2). The function interpolates //* the predictors first in the verical direction and then in the //* horizontal direction to output the (1/2,1/2). It then averages //* the output of the 2nd stage and (1/2,1/2) value to obtain (1/4,1/2) //* or (3/4,1/2) depending on the offset. //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @param[in] pu1_tmp: temporary buffer //* //* @param[in] dydx: x and y reference offset for qpel calculations //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/; //void ih264_inter_pred_luma_horz_qpel_vert_hpel(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd,, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd, // UWORD8* pu1_tmp, // UWORD32 dydx) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ht // w5 => wd // x6 => *pu1_tmp // w7 => dydx .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 ih264_inter_pred_luma_horz_qpel_vert_hpel_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x2, w2 sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 sub x0, x0, x2, lsl #1 //pu1_src-2*src_strd sub x0, x0, #2 //pu1_src-2 mov x9, x6 mov w6, w7 and x6, x6, #2 // dydx & 0x3 followed by dydx>>1 and dydx<<1 add x7, x9, #4 add x6, x7, x6 // pi16_pred1_temp += (x_offset>>1) movi v26.8h, #0x14 // Filter coeff 20 into Q13 movi v24.8h, #0x5 // Filter coeff 5 into Q12 movi v27.8h, #0x14 // Filter coeff 20 into Q13 movi v25.8h, #0x5 // Filter coeff 5 into Q12 mov x7, #0x20 mov x8, #0x30 subs x12, x5, #4 //if wd=4 branch to loop_4 beq loop_4_start subs x12, x5, #8 //if wd=8 branch to loop_8 beq loop_8_start //when wd=16 movi v28.8h, #0x14 // Filter coeff 20 into Q13 movi v30.8h, #0x5 // Filter coeff 5 into Q12 sub x2, x2, #16 ld1 {v0.2s, v1.2s}, [x0], #16 // Vector load from src[0_0] ld1 {v12.2s}, [x0], x2 // Vector load from src[0_0] ld1 {v2.2s, v3.2s}, [x0], #16 // Vector load from src[1_0] ld1 {v13.2s}, [x0], x2 // Vector load from src[1_0] ld1 {v4.2s, v5.2s}, [x0], #16 // Vector load from src[2_0] ld1 {v14.2s}, [x0], x2 // Vector load from src[2_0] ld1 {v6.2s, v7.2s}, [x0], #16 // Vector load from src[3_0] ld1 {v15.2s}, [x0], x2 // Vector load from src[3_0] ld1 {v8.2s, v9.2s}, [x0], #16 // Vector load from src[4_0] ld1 {v16.2s}, [x0], x2 // Vector load from src[4_0] loop_16: ld1 {v10.2s, v11.2s}, [x0], #16 // Vector load from src[5_0] ld1 {v17.2s}, [x0], x2 // Vector load from src[5_0] uaddl v20.8h, v4.8b, v6.8b uaddl v18.8h, v0.8b, v10.8b uaddl v22.8h, v2.8b, v8.8b mla v18.8h, v20.8h , v28.8h uaddl v24.8h, v5.8b, v7.8b uaddl v20.8h, v1.8b, v11.8b uaddl v26.8h, v3.8b, v9.8b mla v20.8h, v24.8h , v28.8h uaddl v24.8h, v14.8b, v15.8b mls v18.8h, v22.8h , v30.8h uaddl v22.8h, v12.8b, v17.8b mls v20.8h, v26.8h , v30.8h uaddl v26.8h, v13.8b, v16.8b mla v22.8h, v24.8h , v28.8h mls v22.8h, v26.8h , v30.8h st1 {v18.4s }, [x9], #16 st1 {v20.4s}, [x9], #16 ext v24.16b, v18.16b , v20.16b , #4 ext v26.16b, v18.16b , v20.16b , #6 st1 {v22.4s}, [x9] ext v22.16b, v18.16b , v20.16b , #10 add v0.8h, v24.8h , v26.8h ext v24.16b, v18.16b , v20.16b , #2 ext v26.16b, v18.16b , v20.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v18.4h, v22.4h smlal v26.4s, v0.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v18.8h, v22.8h smlal2 v22.4s, v0.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h sqrshrun v18.4h, v26.4s, #10 sqrshrun v19.4h, v22.4s, #10 ld1 {v22.4s}, [x9], #16 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ext v24.16b, v20.16b , v22.16b , #4 ext v26.16b, v20.16b , v22.16b , #6 ext v0.16b, v20.16b , v22.16b , #10 st1 {v18.2s}, [x1] add v18.8h, v24.8h , v26.8h ext v24.16b, v20.16b , v22.16b , #2 ext v26.16b, v20.16b , v22.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v0.4h, v20.4h smlal v26.4s, v18.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v0.8h, v20.8h smlal2 v22.4s, v18.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h sqrshrun v19.4h, v26.4s, #10 sqrshrun v18.4h, v22.4s, #10 uaddl v24.8h, v7.8b, v9.8b ld1 {v20.4s}, [x6], #16 ld1 {v22.4s}, [x6], x7 uqxtn v19.8b, v19.8h uqxtn v18.8b, v18.8h mov v19.s[1], v18.s[0] ld1 {v18.2s}, [x1] sqrshrun v20.8b, v20.8h, #5 sqrshrun v21.8b, v22.8h, #5 uaddl v22.8h, v4.8b, v10.8b ld1 {v0.2s, v1.2s}, [x0], #16 // Vector load from src[6_0] urhadd v18.16b, v18.16b , v20.16b urhadd v19.16b, v19.16b , v21.16b ld1 {v12.2s}, [x0], x2 // Vector load from src[6_0] uaddl v20.8h, v6.8b, v8.8b uaddl v26.8h, v5.8b, v11.8b st1 {v18.2s, v19.2s}, [x1], x3 // store row 0 //ROW_2 uaddl v18.8h, v2.8b, v0.8b mla v18.8h, v20.8h , v28.8h uaddl v20.8h, v3.8b, v1.8b mla v20.8h, v24.8h , v28.8h uaddl v24.8h, v15.8b, v16.8b mls v18.8h, v22.8h , v30.8h uaddl v22.8h, v13.8b, v12.8b mls v20.8h, v26.8h , v30.8h uaddl v26.8h, v14.8b, v17.8b mla v22.8h, v24.8h , v28.8h mls v22.8h, v26.8h , v30.8h st1 {v18.4s}, [x9], #16 st1 {v20.4s}, [x9], #16 ext v24.16b, v18.16b , v20.16b , #4 ext v26.16b, v18.16b , v20.16b , #6 st1 {v22.4s}, [x9] ext v22.16b, v18.16b , v20.16b , #10 add v2.8h, v24.8h , v26.8h ext v24.16b, v18.16b , v20.16b , #2 ext v26.16b, v18.16b , v20.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v18.4h, v22.4h smlal v26.4s, v2.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v18.8h, v22.8h smlal2 v22.4s, v2.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h sqrshrun v18.4h, v26.4s, #10 sqrshrun v19.4h, v22.4s, #10 ld1 {v22.4s}, [x9], #16 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ext v24.16b, v20.16b , v22.16b , #4 ext v26.16b, v20.16b , v22.16b , #6 ext v2.16b, v20.16b , v22.16b , #10 st1 {v18.2s}, [x1] add v18.8h, v24.8h , v26.8h ext v24.16b, v20.16b , v22.16b , #2 ext v26.16b, v20.16b , v22.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v2.4h, v20.4h smlal v26.4s, v18.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v2.8h, v20.8h smlal2 v22.4s, v18.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h sqrshrun v19.4h, v26.4s, #10 sqrshrun v18.4h, v22.4s, #10 uaddl v24.8h, v9.8b, v11.8b ld1 {v20.4s}, [x6], #16 ld1 {v22.4s}, [x6], x7 uqxtn v19.8b, v19.8h uqxtn v18.8b, v18.8h mov v19.s[1], v18.s[0] ld1 {v18.4s}, [x1] sqrshrun v20.8b, v20.8h, #5 sqrshrun v21.8b, v22.8h, #5 uaddl v22.8h, v6.8b, v0.8b ld1 {v2.2s, v3.2s}, [x0], #16 // Vector load from src[7_0] urhadd v18.16b, v18.16b , v20.16b urhadd v19.16b, v19.16b , v21.16b ld1 {v13.2s}, [x0], x2 // Vector load from src[7_0] uaddl v20.8h, v8.8b, v10.8b uaddl v26.8h, v7.8b, v1.8b st1 {v18.2s, v19.2s}, [x1], x3 // store row 1 //ROW_3 uaddl v18.8h, v4.8b, v2.8b mla v18.8h, v20.8h , v28.8h uaddl v20.8h, v5.8b, v3.8b mla v20.8h, v24.8h , v28.8h uaddl v24.8h, v16.8b, v17.8b mls v18.8h, v22.8h , v30.8h uaddl v22.8h, v14.8b, v13.8b mls v20.8h, v26.8h , v30.8h uaddl v26.8h, v15.8b, v12.8b mla v22.8h, v24.8h , v28.8h mls v22.8h, v26.8h , v30.8h st1 {v18.4s}, [x9], #16 st1 {v20.4s}, [x9], #16 ext v24.16b, v18.16b , v20.16b , #4 ext v26.16b, v18.16b , v20.16b , #6 st1 {v22.4s}, [x9] ext v22.16b, v18.16b , v20.16b , #10 add v4.8h, v24.8h , v26.8h ext v24.16b, v18.16b , v20.16b , #2 ext v26.16b, v18.16b , v20.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v18.4h, v22.4h smlal v26.4s, v4.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v18.8h, v22.8h smlal2 v22.4s, v4.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h sqrshrun v18.4h, v26.4s, #10 sqrshrun v19.4h, v22.4s, #10 ld1 {v22.4s}, [x9], #16 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ext v24.16b, v20.16b , v22.16b , #4 ext v26.16b, v20.16b , v22.16b , #6 ext v4.16b, v20.16b , v22.16b , #10 st1 {v18.2s}, [x1] add v18.8h, v24.8h , v26.8h ext v24.16b, v20.16b , v22.16b , #2 ext v26.16b, v20.16b , v22.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v4.4h, v20.4h smlal v26.4s, v18.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v4.8h, v20.8h smlal2 v22.4s, v18.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h sqrshrun v19.4h, v26.4s, #10 sqrshrun v18.4h, v22.4s, #10 uaddl v24.8h, v11.8b, v1.8b ld1 {v20.4s}, [x6], #16 ld1 {v22.4s}, [x6], x7 uqxtn v19.8b, v19.8h uqxtn v18.8b, v18.8h mov v19.s[1], v18.s[0] ld1 {v18.2s}, [x1] sqrshrun v20.8b, v20.8h, #5 sqrshrun v21.8b, v22.8h, #5 uaddl v22.8h, v8.8b, v2.8b ld1 {v4.2s, v5.2s}, [x0], #16 // Vector load from src[8_0] urhadd v18.16b, v18.16b , v20.16b urhadd v19.16b, v19.16b , v21.16b ld1 {v14.2s}, [x0], x2 // Vector load from src[8_0] uaddl v20.8h, v10.8b, v0.8b uaddl v26.8h, v9.8b, v3.8b st1 {v18.2s, v19.2s}, [x1], x3 // store row 2 //ROW_4 uaddl v18.8h, v6.8b, v4.8b mla v18.8h, v20.8h , v28.8h uaddl v20.8h, v7.8b, v5.8b mla v20.8h, v24.8h , v28.8h uaddl v24.8h, v17.8b, v12.8b mls v18.8h, v22.8h , v30.8h uaddl v22.8h, v15.8b, v14.8b mls v20.8h, v26.8h , v30.8h uaddl v26.8h, v16.8b, v13.8b mla v22.8h, v24.8h , v28.8h mls v22.8h, v26.8h , v30.8h st1 {v18.4s}, [x9], #16 st1 {v20.4s}, [x9], #16 ext v24.16b, v18.16b , v20.16b , #4 ext v26.16b, v18.16b , v20.16b , #6 st1 {v22.4s}, [x9] ext v22.16b, v18.16b , v20.16b , #10 add v6.8h, v24.8h , v26.8h ext v24.16b, v18.16b , v20.16b , #2 ext v26.16b, v18.16b , v20.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v18.4h, v22.4h smlal v26.4s, v6.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v18.8h, v22.8h smlal2 v22.4s, v6.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h sqrshrun v18.4h, v26.4s, #10 sqrshrun v19.4h, v22.4s, #10 ld1 {v22.4s}, [x9], #16 uqxtn v18.8b, v18.8h uqxtn v19.8b, v19.8h mov v18.s[1], v19.s[0] ext v24.16b, v20.16b , v22.16b , #4 ext v26.16b, v20.16b , v22.16b , #6 ext v6.16b, v20.16b , v22.16b , #10 st1 {v18.2s}, [x1] add v18.8h, v24.8h , v26.8h ext v24.16b, v20.16b , v22.16b , #2 ext v26.16b, v20.16b , v22.16b , #8 add v24.8h, v24.8h , v26.8h saddl v26.4s, v6.4h, v20.4h smlal v26.4s, v18.4h, v28.4h smlsl v26.4s, v24.4h, v30.4h saddl2 v22.4s, v6.8h, v20.8h smlal2 v22.4s, v18.8h, v28.8h smlsl2 v22.4s, v24.8h, v30.8h mov v6.16b, v2.16b mov v7.16b, v3.16b mov v2.16b, v10.16b mov v3.16b, v11.16b subs x4, x4, #4 sqrshrun v19.4h, v26.4s, #10 sqrshrun v18.4h, v22.4s, #10 mov v10.16b, v0.16b mov v11.16b, v1.16b mov v24.8b, v14.8b mov v14.16b, v12.16b mov v15.16b, v13.16b uqxtn v19.8b, v19.8h uqxtn v18.8b, v18.8h mov v19.s[1], v18.s[0] ld1 {v20.4s}, [x6], #16 ld1 {v22.4s}, [x6], x7 ld1 {v18.2s}, [x1] sqrshrun v20.8b, v20.8h, #5 sqrshrun v21.8b, v22.8h, #5 mov v0.16b, v8.16b mov v1.16b, v9.16b mov v8.16b, v4.16b mov v9.16b, v5.16b mov v12.16b, v16.16b mov v13.16b, v17.16b urhadd v18.16b, v18.16b , v20.16b urhadd v19.16b, v19.16b , v21.16b mov v4.16b, v10.16b mov v5.16b, v11.16b mov v16.8b, v24.8b st1 {v18.2s, v19.2s}, [x1], x3 // store row 3 bgt loop_16 // looping if height =16 b end_func loop_8_start: ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] loop_8: ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] uaddl v14.8h, v4.8b, v6.8b uaddl v12.8h, v0.8b, v10.8b uaddl v16.8h, v2.8b, v8.8b mla v12.8h, v14.8h , v26.8h uaddl v18.8h, v5.8b, v7.8b uaddl v14.8h, v1.8b, v11.8b uaddl v22.8h, v3.8b, v9.8b mla v14.8h, v18.8h , v26.8h mls v12.8h, v16.8h , v24.8h ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[6_0] uaddl v16.8h, v6.8b, v8.8b mls v14.8h, v22.8h , v24.8h uaddl v28.8h, v2.8b, v0.8b st1 {v12.4s}, [x9], #16 // store row 0 to temp buffer: col 0 ext v22.16b, v12.16b , v14.16b , #10 uaddl v18.8h, v4.8b, v10.8b mla v28.8h, v16.8h , v26.8h saddl v30.4s, v12.4h, v22.4h st1 {v14.4s}, [x9], x7 // store row 0 to temp buffer: col 1 saddl2 v22.4s, v12.8h, v22.8h ext v16.16b, v12.16b , v14.16b , #4 mls v28.8h, v18.8h , v24.8h ext v18.16b, v12.16b , v14.16b , #6 ext v20.16b, v12.16b , v14.16b , #8 ext v14.16b, v12.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v14.8h , v20.8h uaddl v20.8h, v7.8b, v9.8b smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal2 v22.4s, v16.8h, v26.8h smlsl2 v22.4s, v18.8h, v24.8h uaddl v14.8h, v3.8b, v1.8b st1 {v28.4s}, [x9], #16 // store row 1 to temp buffer: col 0 mla v14.8h, v20.8h , v26.8h sqrshrun v12.4h, v30.4s, #10 uaddl v16.8h, v5.8b, v11.8b sqrshrun v13.4h, v22.4s, #10 mls v14.8h, v16.8h , v24.8h ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[7_0] uqxtn v25.8b, v12.8h uqxtn v13.8b, v13.8h mov v25.s[1], v13.s[0] uaddl v16.8h, v8.8b, v10.8b ext v22.16b, v28.16b , v14.16b , #10 uaddl v20.8h, v4.8b, v2.8b saddl v30.4s, v28.4h, v22.4h mla v20.8h, v16.8h , v26.8h st1 {v14.4s}, [x9], x7 // store row 1 to temp buffer: col 1 saddl2 v22.4s, v28.8h, v22.8h ext v16.16b, v28.16b , v14.16b , #4 ext v18.16b, v28.16b , v14.16b , #6 ext v12.16b, v28.16b , v14.16b , #8 ext v14.16b, v28.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v12.8h , v14.8h ld1 {v14.4s, v15.4s}, [x6], x8 // load row 0 from temp buffer smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal2 v22.4s, v16.8h, v26.8h smlsl2 v22.4s, v18.8h, v24.8h sqrshrun v14.8b, v14.8h, #0x5 ld1 {v28.4s, v29.4s}, [x6], x8 // load row 1 from temp buffer uaddl v18.8h, v6.8b, v0.8b sqrshrun v16.4h, v30.4s, #10 sqrshrun v15.8b, v28.8h, #0x5 sqrshrun v17.4h, v22.4s, #10 mov v12.8b, v25.8b mov v25.8b, v24.8b uaddl v28.8h, v9.8b, v11.8b uqxtn v13.8b, v16.8h uqxtn v17.8b, v17.8h mov v13.s[1], v17.s[0] urhadd v12.16b, v12.16b , v14.16b urhadd v13.16b, v13.16b , v15.16b uaddl v14.8h, v5.8b, v3.8b uaddl v22.8h, v7.8b, v1.8b mls v20.8h, v18.8h , v24.8h st1 {v12.2s}, [x1], x3 // store row 0 mla v14.8h, v28.8h , v26.8h ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[8_0] uaddl v30.8h, v10.8b, v0.8b uaddl v28.8h, v6.8b, v4.8b mls v14.8h, v22.8h , v24.8h st1 {v13.2s}, [x1], x3 // store row 1 mla v28.8h, v30.8h , v26.8h st1 {v20.4s}, [x9], #16 // store row 2 to temp buffer: col 0 ext v22.16b, v20.16b , v14.16b , #10 saddl v30.4s, v20.4h, v22.4h st1 {v14.2s, v15.2s}, [x9], x7 // store row 2 to temp buffer: col 0 saddl2 v22.4s, v20.8h, v22.8h ext v16.16b, v20.16b , v14.16b , #4 ext v18.16b, v20.16b , v14.16b , #6 ext v12.16b, v20.16b , v14.16b , #8 ext v14.16b, v20.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v14.8h , v12.8h uaddl v20.8h, v8.8b, v2.8b smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal2 v22.4s, v16.8h, v26.8h smlsl2 v22.4s, v18.8h, v24.8h uaddl v18.8h, v11.8b, v1.8b uaddl v16.8h, v7.8b, v5.8b sqrshrun v12.4h, v30.4s, #10 uaddl v30.8h, v9.8b, v3.8b mla v16.8h, v18.8h , v26.8h sqrshrun v13.4h, v22.4s, #10 mls v28.8h, v20.8h , v24.8h ld1 {v14.4s, v15.4s}, [x6], x8 // load row 2 from temp buffer mls v16.8h, v30.8h , v24.8h uqxtn v27.8b, v12.8h uqxtn v13.8b, v13.8h mov v27.s[1], v13.s[0] sqrshrun v14.8b, v14.8h, #5 ext v22.16b, v28.16b , v16.16b , #10 st1 {v28.4s}, [x9], #16 // store row 3 to temp buffer: col 0 saddl v30.4s, v28.4h, v22.4h st1 {v16.2s, v17.2s}, [x9], x7 // store row 3 to temp buffer: col 1 saddl2 v22.4s, v28.8h, v22.8h ext v12.16b, v28.16b , v16.16b , #4 ext v18.16b, v28.16b , v16.16b , #6 ext v20.16b, v28.16b , v16.16b , #8 ext v28.16b, v28.16b , v16.16b , #2 add v12.8h, v12.8h , v18.8h add v18.8h, v28.8h , v20.8h ld1 {v16.4s, v17.4s}, [x6], x8 // load row 3 from temp buffer smlal v30.4s, v12.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal2 v22.4s, v12.8h, v26.8h smlsl2 v22.4s, v18.8h, v24.8h sqrshrun v15.8b, v16.8h, #0x5 mov v12.8b, v27.8b mov v27.8b, v26.8b sqrshrun v16.4h, v30.4s, #10 mov v6.16b, v2.16b mov v7.16b, v3.16b sqrshrun v17.4h, v22.4s, #10 mov v2.16b, v10.16b mov v3.16b, v11.16b mov v10.16b, v0.16b mov v11.16b, v1.16b subs x4, x4, #4 uqxtn v13.8b, v16.8h uqxtn v17.8b, v17.8h mov v13.s[1], v17.s[0] urhadd v12.16b, v12.16b , v14.16b urhadd v13.16b, v13.16b , v15.16b mov v0.16b, v8.16b mov v1.16b, v9.16b mov v8.16b, v4.16b mov v9.16b, v5.16b mov v4.16b, v10.16b mov v5.16b, v11.16b st1 {v12.2s}, [x1], x3 // store row 2 st1 {v13.2s}, [x1], x3 // store row 3 bgt loop_8 //if height =8 loop b end_func loop_4_start: ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] loop_4: ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] uaddl v14.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] uaddl v12.8h, v0.8b, v10.8b // temp = src[0_0] + src[5_0] uaddl v16.8h, v2.8b, v8.8b // temp2 = src[1_0] + src[4_0] mla v12.8h, v14.8h , v26.8h // temp += temp1 * 20 uaddl v18.8h, v5.8b, v7.8b // temp1 = src[2_0] + src[3_0] uaddl v14.8h, v1.8b, v11.8b // temp = src[0_0] + src[5_0] uaddl v22.8h, v3.8b, v9.8b // temp2 = src[1_0] + src[4_0] mla v14.8h, v18.8h , v26.8h // temp += temp1 * 20 mls v12.8h, v16.8h , v24.8h // temp -= temp2 * 5 ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[6_0] uaddl v16.8h, v6.8b, v8.8b mls v14.8h, v22.8h , v24.8h // temp -= temp2 * 5 //Q6 and Q7 have filtered values uaddl v28.8h, v2.8b, v0.8b st1 {v12.4s}, [x9], #16 // store row 0 to temp buffer: col 0 ext v22.16b, v12.16b , v14.16b , #10 uaddl v18.8h, v4.8b, v10.8b mla v28.8h, v16.8h , v26.8h saddl v30.4s, v12.4h, v22.4h st1 {v14.4s}, [x9], x7 // store row 0 to temp buffer: col 1 saddl v22.4s, v13.4h, v23.4h ext v16.16b, v12.16b , v14.16b , #4 mls v28.8h, v18.8h , v24.8h ext v18.16b, v12.16b , v14.16b , #6 ext v20.16b, v12.16b , v14.16b , #8 ext v14.16b, v12.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v14.8h , v20.8h uaddl v20.8h, v7.8b, v9.8b smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal v22.4s, v17.4h, v26.4h smlsl v22.4s, v19.4h, v24.4h uaddl v14.8h, v3.8b, v1.8b st1 {v28.4s}, [x9], #16 // store row 1 to temp buffer: col 0 mla v14.8h, v20.8h , v26.8h sqrshrun v12.4h, v30.4s, #10 uaddl v16.8h, v5.8b, v11.8b sqrshrun v13.4h, v22.4s, #10 mls v14.8h, v16.8h , v24.8h ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[7_0] uqxtn v25.8b, v12.8h uaddl v16.8h, v8.8b, v10.8b ext v22.16b, v28.16b , v14.16b , #10 uaddl v20.8h, v4.8b, v2.8b saddl v30.4s, v28.4h, v22.4h mla v20.8h, v16.8h , v26.8h st1 {v14.4s}, [x9], x7 // store row 1 to temp buffer: col 1 saddl v22.4s, v29.4h, v23.4h ext v16.16b, v28.16b , v14.16b , #4 ext v18.16b, v28.16b , v14.16b , #6 ext v12.16b, v28.16b , v14.16b , #8 ext v14.16b, v28.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v12.8h , v14.8h ld1 {v14.2s}, [x6], x8 //load row 0 from temp buffer smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal v22.4s, v17.4h, v26.4h smlsl v22.4s, v19.4h, v24.4h sqrshrun v14.8b, v14.8h, #0x5 ld1 {v28.2s}, [x6], x8 //load row 1 from temp buffer uaddl v18.8h, v6.8b, v0.8b sqrshrun v16.4h, v30.4s, #10 sqrshrun v15.8b, v28.8h, #0x5 sqrshrun v17.4h, v22.4s, #10 mov v12.8b, v25.8b mov v25.8b, v24.8b uaddl v28.8h, v9.8b, v11.8b uqxtn v13.8b, v16.8h urhadd v12.16b, v12.16b , v14.16b urhadd v13.16b, v13.16b , v15.16b uaddl v14.8h, v5.8b, v3.8b uaddl v22.8h, v7.8b, v1.8b mls v20.8h, v18.8h , v24.8h st1 {v12.s}[0], [x1], x3 // store row 0 mla v14.8h, v28.8h , v26.8h ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[8_0] uaddl v30.8h, v10.8b, v0.8b uaddl v28.8h, v6.8b, v4.8b mls v14.8h, v22.8h , v24.8h st1 {v13.s}[0], [x1], x3 //store row 1 mla v28.8h, v30.8h , v26.8h st1 {v20.4s}, [x9], #16 // store row 2 to temp buffer: col 0 ext v22.16b, v20.16b , v14.16b , #10 saddl v30.4s, v20.4h, v22.4h st1 {v14.4s}, [x9], x7 // store row 2 to temp buffer: col 1 saddl v22.4s, v21.4h, v23.4h ext v16.16b, v20.16b , v14.16b , #4 ext v18.16b, v20.16b , v14.16b , #6 ext v12.16b, v20.16b , v14.16b , #8 ext v14.16b, v20.16b , v14.16b , #2 add v16.8h, v16.8h , v18.8h add v18.8h, v14.8h , v12.8h uaddl v20.8h, v8.8b, v2.8b smlal v30.4s, v16.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal v22.4s, v17.4h, v26.4h smlsl v22.4s, v19.4h, v24.4h uaddl v18.8h, v11.8b, v1.8b uaddl v16.8h, v7.8b, v5.8b sqrshrun v12.4h, v30.4s, #10 uaddl v30.8h, v9.8b, v3.8b mla v16.8h, v18.8h , v26.8h sqrshrun v13.4h, v22.4s, #10 mls v28.8h, v20.8h , v24.8h ld1 {v14.2s}, [x6], x8 //load row 3 from temp buffer mls v16.8h, v30.8h , v24.8h uqxtn v27.8b, v12.8h sqrshrun v14.8b, v14.8h, #5 ext v22.16b, v28.16b , v16.16b , #10 st1 {v28.4s}, [x9], #16 // store row 3 to temp buffer: col 0 saddl v30.4s, v28.4h, v22.4h st1 {v16.4s}, [x9], x7 // store row 3 to temp buffer: col 1 saddl v22.4s, v29.4h, v23.4h ext v12.16b, v28.16b , v16.16b , #4 ext v18.16b, v28.16b , v16.16b , #6 ext v20.16b, v28.16b , v16.16b , #8 ext v28.16b, v28.16b , v16.16b , #2 add v12.8h, v12.8h , v18.8h add v18.8h, v28.8h , v20.8h ld1 {v16.2s}, [x6], x8 //load row 4 from temp buffer smlal v30.4s, v12.4h, v26.4h smlsl v30.4s, v18.4h, v24.4h smlal v22.4s, v13.4h, v26.4h smlsl v22.4s, v19.4h, v24.4h sqrshrun v15.8b, v16.8h, #0x5 mov v12.8b, v27.8b mov v27.8b, v26.8b sqrshrun v16.4h, v30.4s, #10 mov v6.16b, v2.16b mov v7.16b, v3.16b sqrshrun v17.4h, v22.4s, #10 mov v2.16b, v10.16b mov v3.16b, v11.16b mov v10.16b, v0.16b mov v11.16b, v1.16b subs x4, x4, #4 uqxtn v13.8b, v16.8h urhadd v12.16b, v12.16b , v14.16b urhadd v13.16b, v13.16b , v15.16b mov v0.16b, v8.16b mov v1.16b, v9.16b mov v8.16b, v4.16b mov v9.16b, v5.16b mov v4.16b, v10.16b mov v5.16b, v11.16b st1 {v12.s}[0], [x1], x3 // store row 2 st1 {v13.s}[0], [x1], x3 // store row 3 bgt loop_4 end_func: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_vert_qpel_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_inter_pred_luma_horz_qpel_vert_qpel_av8.s //* //* @brief //* Contains function definitions for inter prediction interpolation. //* //* @author //* Mohit //* //* @par List of Functions: //* //* - ih264_inter_pred_luma_horz_qpel_vert_qpel_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_inter_pred_filters.c // ///** ///** ///** //******************************************************************************* //* //* @brief //* This function implements two six tap filters. It //* applies the six tap filter in the horizontal direction on the //* predictor values, then applies the same filter in the //* vertical direction on the predictor values. It then averages these //* two outputs to obtain quarter pel values in horizontal and vertical direction. //* The six tap filtering operation is described in sec 8.4.2.2.1 titled //* "Luma sample interpolation process" //* //* @par Description: //* This function is called to obtain pixels lying at the following //* location (1/4,1/4) or (3/4,1/4) or (1/4,3/4) or (3/4,3/4). //* The function interpolates the predictors first in the horizontal direction //* and then in the vertical direction, and then averages these two //* values. //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @param[in] pu1_tmp: temporary buffer //* //* @param[in] dydx: x and y reference offset for qpel calculations //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/; //void ih264_inter_pred_luma_horz_qpel_vert_qpel(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd,, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd, // UWORD8* pu1_tmp, // UWORD32 dydx) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ht // w5 => wd // w7 => dydx .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 ih264_inter_pred_luma_horz_qpel_vert_qpel_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x2, w2 sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 mov w6, w7 and x7, x6, #3 add x7, x0, x7, lsr #1 //pu1_pred_vert = pu1_src + (x_offset>>1) and x6, x6, #12 //Finds y-offset lsr x6, x6, #3 //dydx>>3 mul x6, x2, x6 add x6, x0, x6 //pu1_pred_horz = pu1_src + (y_offset>>1)*src_strd sub x7, x7, x2, lsl #1 //pu1_pred_vert-2*src_strd sub x6, x6, #2 //pu1_pred_horz-2 movi v30.8b, #20 // Filter coeff 20 movi v31.8b, #5 // Filter coeff 5 subs x12, x5, #4 //if wd=4 branch to loop_4 beq loop_4_start subs x12, x5, #8 //if wd=8 branch to loop_8 beq loop_8_start ld1 {v0.2s, v1.2s}, [x7], x2 // Vector load from src[0_0] ld1 {v2.2s, v3.2s}, [x7], x2 // Vector load from src[1_0] ld1 {v4.2s, v5.2s}, [x7], x2 // Vector load from src[2_0] ld1 {v6.2s, v7.2s}, [x7], x2 // Vector load from src[3_0] ld1 {v8.2s, v9.2s}, [x7], x2 // Vector load from src[4_0] add x11, x6, #8 loop_16: ld1 {v10.2s, v11.2s}, [x7], x2 // Vector load from src[5_0] ld1 {v18.2s, v19.2s}, [x6], x2 // horz row0, col 0 uaddl v24.8h, v0.8b, v10.8b umlal v24.8h, v4.8b, v30.8b umlal v24.8h, v6.8b, v30.8b umlsl v24.8h, v2.8b, v31.8b umlsl v24.8h, v8.8b, v31.8b ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v26.8b, v24.8h, #5 uaddl v28.8h, v18.8b, v23.8b umlal v28.8h, v20.8b, v30.8b umlal v28.8h, v21.8b, v30.8b umlsl v28.8h, v19.8b, v31.8b umlsl v28.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 0, col 1 uaddl v24.8h, v1.8b, v11.8b umlal v24.8h, v5.8b, v30.8b umlal v24.8h, v7.8b, v30.8b umlsl v24.8h, v3.8b, v31.8b umlsl v24.8h, v9.8b, v31.8b sqrshrun v28.8b, v28.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v27.8b, v24.8h, #5 ld1 {v12.2s, v13.2s}, [x7], x2 // src[6_0] uaddl v24.8h, v18.8b, v23.8b umlal v24.8h, v20.8b, v30.8b umlal v24.8h, v21.8b, v30.8b umlsl v24.8h, v19.8b, v31.8b umlsl v24.8h, v22.8b, v31.8b uaddl v16.8h, v2.8b, v12.8b umlal v16.8h, v6.8b, v30.8b umlal v16.8h, v8.8b, v30.8b umlsl v16.8h, v4.8b, v31.8b umlsl v16.8h, v10.8b, v31.8b sqrshrun v29.8b, v24.8h, #5 ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 1, col 0 uaddl v24.8h, v3.8b, v13.8b umlal v24.8h, v7.8b, v30.8b umlal v24.8h, v9.8b, v30.8b umlsl v24.8h, v5.8b, v31.8b umlsl v24.8h, v11.8b, v31.8b urhadd v28.16b, v28.16b , v26.16b urhadd v29.16b, v29.16b , v27.16b sqrshrun v26.8b, v16.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 st1 {v28.2s, v29.2s}, [x1], x3 // store row 0 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v27.8b, v24.8h, #5 uaddl v28.8h, v18.8b, v23.8b umlal v28.8h, v20.8b, v30.8b umlal v28.8h, v21.8b, v30.8b umlsl v28.8h, v19.8b, v31.8b umlsl v28.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 1, col 1 ld1 {v14.2s, v15.2s}, [x7], x2 // src[7_0] ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v28.8b, v28.8h, #5 uaddl v24.8h, v18.8b, v23.8b umlal v24.8h, v20.8b, v30.8b umlal v24.8h, v21.8b, v30.8b umlsl v24.8h, v19.8b, v31.8b umlsl v24.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 2, col 0 uaddl v16.8h, v4.8b, v14.8b umlal v16.8h, v8.8b, v30.8b umlal v16.8h, v10.8b, v30.8b umlsl v16.8h, v6.8b, v31.8b umlsl v16.8h, v12.8b, v31.8b sqrshrun v29.8b, v24.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 urhadd v28.16b, v28.16b , v26.16b urhadd v29.16b, v29.16b , v27.16b sqrshrun v26.8b, v16.8h, #5 uaddl v24.8h, v5.8b, v15.8b umlal v24.8h, v9.8b, v30.8b umlal v24.8h, v11.8b, v30.8b umlsl v24.8h, v7.8b, v31.8b umlsl v24.8h, v13.8b, v31.8b st1 {v28.2s, v29.2s}, [x1], x3 // store row 1 uaddl v28.8h, v18.8b, v23.8b umlal v28.8h, v20.8b, v30.8b umlal v28.8h, v21.8b, v30.8b umlsl v28.8h, v19.8b, v31.8b umlsl v28.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 2, col 1 sqrshrun v27.8b, v24.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v28.8b, v28.8h, #5 ld1 {v16.2s, v17.2s}, [x7], x2 // src[8_0] uaddl v24.8h, v18.8b, v23.8b umlal v24.8h, v20.8b, v30.8b umlal v24.8h, v21.8b, v30.8b umlsl v24.8h, v19.8b, v31.8b umlsl v24.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 3, col 0 uaddl v0.8h, v6.8b, v16.8b umlal v0.8h, v10.8b, v30.8b umlal v0.8h, v12.8b, v30.8b umlsl v0.8h, v8.8b, v31.8b umlsl v0.8h, v14.8b, v31.8b sqrshrun v29.8b, v24.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 urhadd v28.16b, v28.16b , v26.16b urhadd v29.16b, v29.16b , v27.16b ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v26.8b, v0.8h, #5 st1 {v28.2s, v29.2s}, [x1], x3 // store row 2 uaddl v24.8h, v18.8b, v23.8b umlal v24.8h, v20.8b, v30.8b umlal v24.8h, v21.8b, v30.8b umlsl v24.8h, v19.8b, v31.8b umlsl v24.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 3, col 1 uaddl v0.8h, v7.8b, v17.8b umlal v0.8h, v11.8b, v30.8b umlal v0.8h, v13.8b, v30.8b umlsl v0.8h, v9.8b, v31.8b umlsl v0.8h, v15.8b, v31.8b sqrshrun v28.8b, v24.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v27.8b, v0.8h, #5 uaddl v24.8h, v18.8b, v23.8b umlal v24.8h, v20.8b, v30.8b umlal v24.8h, v21.8b, v30.8b umlsl v24.8h, v19.8b, v31.8b umlsl v24.8h, v22.8b, v31.8b mov v0.16b, v8.16b mov v1.16b, v9.16b mov v2.16b, v10.16b mov v3.16b, v11.16b mov v4.16b, v12.16b mov v5.16b, v13.16b mov v6.16b, v14.16b mov v7.16b, v15.16b mov v8.16b, v16.16b mov v9.16b, v17.16b sqrshrun v29.8b, v24.8h, #5 urhadd v28.16b, v28.16b , v26.16b urhadd v29.16b, v29.16b , v27.16b st1 {v28.2s, v29.2s}, [x1], x3 // store row 3 ld1 {v10.2s, v11.2s}, [x7], x2 // Vector load from src[9_0] ld1 {v18.2s, v19.2s}, [x6], x2 // horz row4, col 0 uaddl v24.8h, v0.8b, v10.8b umlal v24.8h, v4.8b, v30.8b umlal v24.8h, v6.8b, v30.8b umlsl v24.8h, v2.8b, v31.8b umlsl v24.8h, v8.8b, v31.8b ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v26.8b, v24.8h, #5 uaddl v28.8h, v18.8b, v23.8b umlal v28.8h, v20.8b, v30.8b umlal v28.8h, v21.8b, v30.8b umlsl v28.8h, v19.8b, v31.8b umlsl v28.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 4, col 1 uaddl v24.8h, v1.8b, v11.8b umlal v24.8h, v5.8b, v30.8b umlal v24.8h, v7.8b, v30.8b umlsl v24.8h, v3.8b, v31.8b umlsl v24.8h, v9.8b, v31.8b sqrshrun v28.8b, v28.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v27.8b, v24.8h, #5 ld1 {v12.2s, v13.2s}, [x7], x2 // src[10_0] uaddl v24.8h, v18.8b, v23.8b umlal v24.8h, v20.8b, v30.8b umlal v24.8h, v21.8b, v30.8b umlsl v24.8h, v19.8b, v31.8b umlsl v24.8h, v22.8b, v31.8b uaddl v16.8h, v2.8b, v12.8b umlal v16.8h, v6.8b, v30.8b umlal v16.8h, v8.8b, v30.8b umlsl v16.8h, v4.8b, v31.8b umlsl v16.8h, v10.8b, v31.8b sqrshrun v29.8b, v24.8h, #5 ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 5, col 0 uaddl v24.8h, v3.8b, v13.8b umlal v24.8h, v7.8b, v30.8b umlal v24.8h, v9.8b, v30.8b umlsl v24.8h, v5.8b, v31.8b umlsl v24.8h, v11.8b, v31.8b urhadd v28.16b, v28.16b , v26.16b urhadd v29.16b, v29.16b , v27.16b sqrshrun v26.8b, v16.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 st1 {v28.2s, v29.2s}, [x1], x3 // store row 4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v27.8b, v24.8h, #5 uaddl v28.8h, v18.8b, v23.8b umlal v28.8h, v20.8b, v30.8b umlal v28.8h, v21.8b, v30.8b umlsl v28.8h, v19.8b, v31.8b umlsl v28.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 5, col 1 ld1 {v14.2s, v15.2s}, [x7], x2 // src[11_0] ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v28.8b, v28.8h, #5 uaddl v24.8h, v18.8b, v23.8b umlal v24.8h, v20.8b, v30.8b umlal v24.8h, v21.8b, v30.8b umlsl v24.8h, v19.8b, v31.8b umlsl v24.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 6, col 0 uaddl v16.8h, v4.8b, v14.8b umlal v16.8h, v8.8b, v30.8b umlal v16.8h, v10.8b, v30.8b umlsl v16.8h, v6.8b, v31.8b umlsl v16.8h, v12.8b, v31.8b sqrshrun v29.8b, v24.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 urhadd v28.16b, v28.16b , v26.16b urhadd v29.16b, v29.16b , v27.16b sqrshrun v26.8b, v16.8h, #5 uaddl v24.8h, v5.8b, v15.8b umlal v24.8h, v9.8b, v30.8b umlal v24.8h, v11.8b, v30.8b umlsl v24.8h, v7.8b, v31.8b umlsl v24.8h, v13.8b, v31.8b st1 {v28.2s, v29.2s}, [x1], x3 // store row 5 uaddl v28.8h, v18.8b, v23.8b umlal v28.8h, v20.8b, v30.8b umlal v28.8h, v21.8b, v30.8b umlsl v28.8h, v19.8b, v31.8b umlsl v28.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 6, col 1 sqrshrun v27.8b, v24.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v28.8b, v28.8h, #5 ld1 {v16.2s, v17.2s}, [x7], x2 // src[12_0] uaddl v24.8h, v18.8b, v23.8b umlal v24.8h, v20.8b, v30.8b umlal v24.8h, v21.8b, v30.8b umlsl v24.8h, v19.8b, v31.8b umlsl v24.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 7, col 0 uaddl v0.8h, v6.8b, v16.8b umlal v0.8h, v10.8b, v30.8b umlal v0.8h, v12.8b, v30.8b umlsl v0.8h, v8.8b, v31.8b umlsl v0.8h, v14.8b, v31.8b sqrshrun v29.8b, v24.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 urhadd v28.16b, v28.16b , v26.16b urhadd v29.16b, v29.16b , v27.16b ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v26.8b, v0.8h, #5 st1 {v28.2s, v29.2s}, [x1], x3 // store row 6 uaddl v24.8h, v18.8b, v23.8b umlal v24.8h, v20.8b, v30.8b umlal v24.8h, v21.8b, v30.8b umlsl v24.8h, v19.8b, v31.8b umlsl v24.8h, v22.8b, v31.8b ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 7, col 1 uaddl v0.8h, v7.8b, v17.8b umlal v0.8h, v11.8b, v30.8b umlal v0.8h, v13.8b, v30.8b umlsl v0.8h, v9.8b, v31.8b umlsl v0.8h, v15.8b, v31.8b sqrshrun v28.8b, v24.8h, #5 ext v23.8b, v18.8b , v19.8b , #5 ext v20.8b, v18.8b , v19.8b , #2 ext v21.8b, v18.8b , v19.8b , #3 ext v22.8b, v18.8b , v19.8b , #4 ext v19.8b, v18.8b , v19.8b , #1 sqrshrun v27.8b, v0.8h, #5 uaddl v24.8h, v18.8b, v23.8b umlal v24.8h, v20.8b, v30.8b umlal v24.8h, v21.8b, v30.8b umlsl v24.8h, v19.8b, v31.8b umlsl v24.8h, v22.8b, v31.8b mov v0.16b, v8.16b mov v1.16b, v9.16b mov v2.16b, v10.16b mov v3.16b, v11.16b mov v4.16b, v12.16b mov v5.16b, v13.16b mov v6.16b, v14.16b mov v7.16b, v15.16b mov v8.16b, v16.16b mov v9.16b, v17.16b sqrshrun v29.8b, v24.8h, #5 subs x4, x4, #8 urhadd v28.16b, v28.16b , v26.16b urhadd v29.16b, v29.16b , v27.16b st1 {v28.2s, v29.2s}, [x1], x3 // store row 7 beq end_func // stop looping if ht == 8 b loop_16 loop_8_start: ld1 {v0.2s}, [x7], x2 // Vector load from src[0_0] ld1 {v1.2s}, [x7], x2 // Vector load from src[1_0] ld1 {v2.2s}, [x7], x2 // Vector load from src[2_0] ld1 {v3.2s}, [x7], x2 // Vector load from src[3_0] ld1 {v4.2s}, [x7], x2 // Vector load from src[4_0] loop_8: ld1 {v5.2s}, [x7], x2 // Vector load from src[5_0] uaddl v10.8h, v0.8b, v5.8b umlal v10.8h, v2.8b, v30.8b umlal v10.8h, v3.8b, v30.8b umlsl v10.8h, v1.8b, v31.8b umlsl v10.8h, v4.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 0 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v26.8b, v10.8h, #5 ld1 {v6.2s}, [x7], x2 // src[6_0] uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 1 uaddl v18.8h, v1.8b, v6.8b umlal v18.8h, v3.8b, v30.8b umlal v18.8h, v4.8b, v30.8b umlsl v18.8h, v2.8b, v31.8b umlsl v18.8h, v5.8b, v31.8b sqrshrun v28.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v27.8b, v18.8h, #5 ld1 {v7.2s}, [x7], x2 // src[7_0] uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 2 uaddl v18.8h, v2.8b, v7.8b umlal v18.8h, v4.8b, v30.8b umlal v18.8h, v5.8b, v30.8b umlsl v18.8h, v3.8b, v31.8b umlsl v18.8h, v6.8b, v31.8b sqrshrun v29.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 urhadd v26.16b, v26.16b , v28.16b urhadd v27.16b, v27.16b , v29.16b sqrshrun v28.8b, v18.8h, #5 ld1 {v8.2s}, [x7], x2 // src[8_0] uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 3 uaddl v18.8h, v3.8b, v8.8b umlal v18.8h, v5.8b, v30.8b umlal v18.8h, v6.8b, v30.8b umlsl v18.8h, v4.8b, v31.8b umlsl v18.8h, v7.8b, v31.8b sqrshrun v24.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v29.8b, v18.8h, #5 uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b st1 {v26.2s}, [x1], x3 mov v0.16b, v4.16b mov v1.16b, v5.16b st1 {v27.2s}, [x1], x3 mov v2.16b, v6.16b mov v3.16b, v7.16b mov v4.8b, v8.8b sqrshrun v25.8b, v10.8h, #5 subs x9, x4, #4 urhadd v24.16b, v24.16b , v28.16b urhadd v25.16b, v25.16b , v29.16b st1 {v24.2s}, [x1], x3 st1 {v25.2s}, [x1], x3 beq end_func // Branch if height==4 ld1 {v5.2s}, [x7], x2 // Vector load from src[9_0] uaddl v10.8h, v0.8b, v5.8b umlal v10.8h, v2.8b, v30.8b umlal v10.8h, v3.8b, v30.8b umlsl v10.8h, v1.8b, v31.8b umlsl v10.8h, v4.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 4 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v26.8b, v10.8h, #5 ld1 {v6.2s}, [x7], x2 // src[10_0] uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 5 uaddl v18.8h, v1.8b, v6.8b umlal v18.8h, v3.8b, v30.8b umlal v18.8h, v4.8b, v30.8b umlsl v18.8h, v2.8b, v31.8b umlsl v18.8h, v5.8b, v31.8b sqrshrun v28.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v27.8b, v18.8h, #5 ld1 {v7.2s}, [x7], x2 // src[11_0] uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 6 uaddl v18.8h, v2.8b, v7.8b umlal v18.8h, v4.8b, v30.8b umlal v18.8h, v5.8b, v30.8b umlsl v18.8h, v3.8b, v31.8b umlsl v18.8h, v6.8b, v31.8b sqrshrun v29.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 urhadd v26.16b, v26.16b , v28.16b urhadd v27.16b, v27.16b , v29.16b sqrshrun v28.8b, v18.8h, #5 ld1 {v8.2s}, [x7], x2 // src[12_0] uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 7 uaddl v18.8h, v3.8b, v8.8b umlal v18.8h, v5.8b, v30.8b umlal v18.8h, v6.8b, v30.8b umlsl v18.8h, v4.8b, v31.8b umlsl v18.8h, v7.8b, v31.8b sqrshrun v24.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v29.8b, v18.8h, #5 uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b st1 {v26.2s}, [x1], x3 mov v0.16b, v4.16b mov v1.16b, v5.16b st1 {v27.2s}, [x1], x3 mov v2.16b, v6.16b mov v3.16b, v7.16b mov v4.8b, v8.8b mov v5.8b, v9.8b sqrshrun v25.8b, v10.8h, #5 subs x4, x4, #8 urhadd v24.16b, v24.16b , v28.16b urhadd v25.16b, v25.16b , v29.16b st1 {v24.2s}, [x1], x3 st1 {v25.2s}, [x1], x3 bgt loop_8 //if height =8 loop b end_func loop_4_start: ld1 {v0.s}[0], [x7], x2 // Vector load from src[0_0] ld1 {v1.s}[0], [x7], x2 // Vector load from src[1_0] ld1 {v2.s}[0], [x7], x2 // Vector load from src[2_0] ld1 {v3.s}[0], [x7], x2 // Vector load from src[3_0] ld1 {v4.s}[0], [x7], x2 // Vector load from src[4_0] ld1 {v5.s}[0], [x7], x2 // Vector load from src[5_0] uaddl v10.8h, v0.8b, v5.8b umlal v10.8h, v2.8b, v30.8b umlal v10.8h, v3.8b, v30.8b umlsl v10.8h, v1.8b, v31.8b umlsl v10.8h, v4.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 //load for horz filter row 0 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v26.8b, v10.8h, #5 ld1 {v6.s}[0], [x7], x2 // Vector load from src[6_0] uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 1 uaddl v18.8h, v1.8b, v6.8b umlal v18.8h, v3.8b, v30.8b umlal v18.8h, v4.8b, v30.8b umlsl v18.8h, v2.8b, v31.8b umlsl v18.8h, v5.8b, v31.8b sqrshrun v28.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v27.8b, v18.8h, #5 ld1 {v7.s}[0], [x7], x2 // Vector load from src[7_0] uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 2 uaddl v18.8h, v2.8b, v7.8b umlal v18.8h, v4.8b, v30.8b umlal v18.8h, v5.8b, v30.8b umlsl v18.8h, v3.8b, v31.8b umlsl v18.8h, v6.8b, v31.8b sqrshrun v29.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 urhadd v26.16b, v26.16b , v28.16b urhadd v27.16b, v27.16b , v29.16b sqrshrun v28.8b, v18.8h, #5 ld1 {v8.s}[0], [x7], x2 // Vector load from src[8_0] uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 3 uaddl v18.8h, v3.8b, v8.8b umlal v18.8h, v5.8b, v30.8b umlal v18.8h, v6.8b, v30.8b umlsl v18.8h, v4.8b, v31.8b umlsl v18.8h, v7.8b, v31.8b sqrshrun v24.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v29.8b, v18.8h, #5 uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b st1 {v26.s}[0], [x1], x3 mov v0.16b, v4.16b mov v1.16b, v5.16b st1 {v27.s}[0], [x1], x3 mov v2.16b, v6.16b mov v3.16b, v7.16b mov v4.8b, v8.8b sqrshrun v25.8b, v10.8h, #5 subs x4, x4, #4 urhadd v24.16b, v24.16b , v28.16b urhadd v25.16b, v25.16b , v29.16b st1 {v24.s}[0], [x1], x3 st1 {v25.s}[0], [x1], x3 beq end_func // Branch if height==4 ld1 {v5.s}[0], [x7], x2 // Vector load from src[5_0] uaddl v10.8h, v0.8b, v5.8b umlal v10.8h, v2.8b, v30.8b umlal v10.8h, v3.8b, v30.8b umlsl v10.8h, v1.8b, v31.8b umlsl v10.8h, v4.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 //load for horz filter row 4 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v26.8b, v10.8h, #5 ld1 {v6.s}[0], [x7], x2 uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 5 uaddl v18.8h, v1.8b, v6.8b umlal v18.8h, v3.8b, v30.8b umlal v18.8h, v4.8b, v30.8b umlsl v18.8h, v2.8b, v31.8b umlsl v18.8h, v5.8b, v31.8b sqrshrun v28.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v27.8b, v18.8h, #5 ld1 {v7.s}[0], [x7], x2 uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 6 uaddl v18.8h, v2.8b, v7.8b umlal v18.8h, v4.8b, v30.8b umlal v18.8h, v5.8b, v30.8b umlsl v18.8h, v3.8b, v31.8b umlsl v18.8h, v6.8b, v31.8b sqrshrun v29.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 urhadd v26.16b, v26.16b , v28.16b urhadd v27.16b, v27.16b , v29.16b sqrshrun v28.8b, v18.8h, #5 ld1 {v8.s}[0], [x7], x2 uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 7 uaddl v18.8h, v3.8b, v8.8b umlal v18.8h, v5.8b, v30.8b umlal v18.8h, v6.8b, v30.8b umlsl v18.8h, v4.8b, v31.8b umlsl v18.8h, v7.8b, v31.8b sqrshrun v24.8b, v10.8h, #5 ext v17.8b, v12.8b , v13.8b , #5 ext v14.8b, v12.8b , v13.8b , #2 ext v15.8b, v12.8b , v13.8b , #3 ext v16.8b, v12.8b , v13.8b , #4 ext v13.8b, v12.8b , v13.8b , #1 sqrshrun v29.8b, v18.8h, #5 uaddl v10.8h, v12.8b, v17.8b umlal v10.8h, v14.8b, v30.8b umlal v10.8h, v15.8b, v30.8b umlsl v10.8h, v13.8b, v31.8b umlsl v10.8h, v16.8b, v31.8b st1 {v26.s}[0], [x1], x3 st1 {v27.s}[0], [x1], x3 sqrshrun v25.8b, v10.8h, #5 urhadd v24.16b, v24.16b , v28.16b urhadd v25.16b, v25.16b , v29.16b st1 {v24.s}[0], [x1], x3 st1 {v25.s}[0], [x1], x3 end_func: ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_inter_pred_luma_vert_qpel_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_inter_pred_luma_vert_qpel_av8.s //* //* @brief //* Contains function definitions for inter prediction vertical quarter pel interpolation. //* //* @author //* Mohit //* //* @par List of Functions: //* //* - ih264_inter_pred_luma_vert_qpel_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_inter_pred_filters.c // ///** ///** //******************************************************************************* //* //* @brief //* Quarter pel interprediction luma filter for vertical input //* //* @par Description: //* Applies a 6 tap horizontal filter .The output is clipped to 8 bits //* sec 8.4.2.2.1 titled "Luma sample interpolation process" //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @param[in] pu1_tmp: temporary buffer: UNUSED in this function //* //* @param[in] dydx: x and y reference offset for qpel calculations. //* @returns //* // @remarks //* None //* //******************************************************************************* //*/ //void ih264_inter_pred_luma_vert ( // UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ht, // WORD32 wd, // UWORD8* pu1_tmp, // UWORD32 dydx) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ht // w5 => wd // w7 => dydx .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_inter_pred_luma_vert_qpel_av8 ih264_inter_pred_luma_vert_qpel_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x2, w2 sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 and x7, x7, #12 //Finds y-offset lsr x7, x7, #3 //dydx>>3 mul x7, x2, x7 add x7, x0, x7 //pu1_src + (y_offset>>1)*src_strd sub x14, x4, #16 movi v22.8h, #20 // Filter coeff 0x14 into Q11 sub x0, x0, x2, lsl #1 //pu1_src-2*src_strd subs x12, x5, #8 //if wd=8 branch to loop_8 movi v24.8h, #5 // Filter coeff 0x4 into Q12 beq loop_8_start subs x12, x5, #4 //if wd=4 branch to loop_4 beq loop_4_start ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] add x14, x14, #1 //for checking loop ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] uaddl v12.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] loop_16: //when wd=16 uaddl v14.8h, v0.8b, v10.8b // temp = src[0_0] + src[5_0] uaddl v16.8h, v2.8b, v8.8b // temp2 = src[1_0] + src[4_0] mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 uaddl v20.8h, v1.8b, v11.8b // temp4 = src[0_8] + src[5_8] uaddl v18.8h, v5.8b, v7.8b // temp3 = src[2_8] + src[3_8] mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 ld1 {v0.2s, v1.2s}, [x0], x2 uaddl v26.8h, v3.8b, v9.8b // temp5 = src[1_8] + src[4_8] uaddl v12.8h, v6.8b, v8.8b mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v16.8h, v2.8b, v0.8b uaddl v18.8h, v4.8b, v10.8b mla v16.8h, v12.8h , v22.8h mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 uaddl v26.8h, v5.8b, v11.8b uaddl v12.8h, v7.8b, v9.8b sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) uaddl v14.8h, v3.8b, v1.8b ld1 {v2.2s, v3.2s}, [x0], x2 mla v14.8h, v12.8h , v22.8h mls v16.8h, v18.8h , v24.8h sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) ld1 {v20.2s, v21.2s}, [x7], x2 // Load for interpolation row 0 urhadd v30.16b, v20.16b , v30.16b // Interpolation to obtain qpel value urhadd v31.16b, v21.16b , v31.16b // Interpolation to obtain qpel value uaddl v18.8h, v4.8b, v2.8b uaddl v12.8h, v8.8b, v10.8b st1 {v30.2s, v31.2s}, [x1], x3 // Vector store to dst[0_0] mla v18.8h, v12.8h , v22.8h uaddl v20.8h, v6.8b, v0.8b mls v14.8h, v26.8h , v24.8h sqrshrun v30.8b, v16.8h, #5 uaddl v12.8h, v9.8b, v11.8b uaddl v16.8h, v5.8b, v3.8b uaddl v26.8h, v7.8b, v1.8b mla v16.8h, v12.8h , v22.8h mls v18.8h, v20.8h , v24.8h ld1 {v4.2s, v5.2s}, [x0], x2 sqrshrun v31.8b, v14.8h, #5 ld1 {v14.2s, v15.2s}, [x7], x2 // Load for interpolation row 1 uaddl v12.8h, v10.8b, v0.8b urhadd v30.16b, v14.16b , v30.16b // Interpolation to obtain qpel value urhadd v31.16b, v15.16b , v31.16b // Interpolation to obtain qpel value uaddl v14.8h, v6.8b, v4.8b uaddl v20.8h, v8.8b, v2.8b mla v14.8h, v12.8h , v22.8h mls v16.8h, v26.8h , v24.8h st1 {v30.2s, v31.2s}, [x1], x3 //store row 1 sqrshrun v30.8b, v18.8h, #5 uaddl v18.8h, v7.8b, v5.8b uaddl v12.8h, v11.8b, v1.8b mla v18.8h, v12.8h , v22.8h uaddl v26.8h, v9.8b, v3.8b mls v14.8h, v20.8h , v24.8h ld1 {v6.2s, v7.2s}, [x0], x2 sqrshrun v31.8b, v16.8h, #5 ld1 {v16.2s, v17.2s}, [x7], x2 // Load for interpolation row 2 mls v18.8h, v26.8h , v24.8h urhadd v30.16b, v16.16b , v30.16b // Interpolation to obtain qpel value urhadd v31.16b, v17.16b , v31.16b // Interpolation to obtain qpel value uaddl v12.8h, v0.8b, v2.8b // temp1 = src[2_0] + src[3_0] st1 {v30.2s, v31.2s}, [x1], x3 //store row 2 uaddl v16.8h, v10.8b, v4.8b // temp2 = src[1_0] + src[4_0] uaddl v20.8h, v9.8b, v7.8b // temp4 = src[0_8] + src[5_8] sqrshrun v30.8b, v14.8h, #5 uaddl v26.8h, v5.8b, v11.8b // temp5 = src[1_8] + src[4_8] uaddl v14.8h, v8.8b, v6.8b // temp = src[0_0] + src[5_0] sqrshrun v31.8b, v18.8h, #5 ld1 {v18.2s, v19.2s}, [x7], x2 // Load for interpolation row 3 mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 urhadd v30.16b, v18.16b , v30.16b // Interpolation to obtain qpel value urhadd v31.16b, v19.16b , v31.16b // Interpolation to obtain qpel value uaddl v18.8h, v1.8b, v3.8b // temp3 = src[2_8] + src[3_8] st1 {v30.2s, v31.2s}, [x1], x3 //store row 3 // 4 rows processed mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 ld1 {v8.2s, v9.2s}, [x0], x2 uaddl v12.8h, v2.8b, v4.8b uaddl v18.8h, v3.8b, v5.8b mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v28.8h, v9.8b, v11.8b uaddl v16.8h, v6.8b, v0.8b mla v28.8h, v18.8h , v22.8h // temp4 += temp3 * 20 mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 uaddl v26.8h, v1.8b, v7.8b uaddl v18.8h, v5.8b, v7.8b sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) uaddl v14.8h, v8.8b, v10.8b sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) ld1 {v20.2s, v21.2s}, [x7], x2 // Load for interpolation row 4 ld1 {v10.2s, v11.2s}, [x0], x2 urhadd v30.16b, v20.16b , v30.16b // Interpolation to obtain qpel value urhadd v31.16b, v21.16b , v31.16b // Interpolation to obtain qpel value mls v28.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 st1 {v30.2s, v31.2s}, [x1], x3 // store row 4 mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 uaddl v20.8h, v11.8b, v1.8b uaddl v26.8h, v3.8b, v9.8b mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 uaddl v12.8h, v6.8b, v4.8b uaddl v18.8h, v7.8b, v9.8b sqrshrun v31.8b, v28.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v16.8h, v8.8b, v2.8b sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) ld1 {v14.2s, v15.2s}, [x7], x2 // Load for interpolation row 5 mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 urhadd v30.16b, v14.16b , v30.16b // Interpolation to obtain qpel value urhadd v31.16b, v15.16b , v31.16b // Interpolation to obtain qpel value uaddl v14.8h, v10.8b, v0.8b st1 {v30.2s, v31.2s}, [x1], x3 // store row 5 mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 ld1 {v0.2s, v1.2s}, [x0], x2 uaddl v26.8h, v5.8b, v11.8b uaddl v12.8h, v8.8b, v6.8b uaddl v28.8h, v0.8b, v2.8b sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) mla v28.8h, v12.8h , v22.8h // temp += temp1 * 20 uaddl v20.8h, v1.8b, v3.8b mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 uaddl v16.8h, v10.8b, v4.8b sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) ld1 {v14.2s, v15.2s}, [x7], x2 // Load for interpolation row 6 mov v2.8b, v6.8b mov v3.8b, v7.8b urhadd v30.16b, v14.16b , v30.16b // Interpolation to obtain qpel value urhadd v31.16b, v15.16b , v31.16b // Interpolation to obtain qpel value mls v28.8h, v16.8h , v24.8h // temp -= temp2 * 5 st1 {v30.2s, v31.2s}, [x1], x3 // store row 6 sqrshrun v30.8b, v28.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) swp v0.8b, v4.8b // swapping registers to put it in order swp v1.8b, v5.8b // swapping registers to put it in order mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 mov v6.8b, v10.8b mov v7.8b, v11.8b subs x12, x14, #1 // if height==16 - looping swp v4.8b, v8.8b swp v5.8b, v9.8b sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) ld1 {v20.2s, v21.2s}, [x7], x2 // Load for interpolation row 7 urhadd v30.16b, v20.16b , v30.16b // Interpolation to obtain qpel value urhadd v31.16b, v21.16b , v31.16b // Interpolation to obtain qpel value st1 {v30.2s, v31.2s}, [x1], x3 // store row 7 bne end_func //if height =8 end function add x14, x14, #1 //for checking loop ld1 {v10.2s, v11.2s}, [x0], x2 uaddl v12.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] b loop_16 // looping if height =16 loop_8_start: //// Processing row0 and row1 ld1 {v0.2s}, [x0], x2 // Vector load from src[0_0] ld1 {v1.2s}, [x0], x2 // Vector load from src[1_0] ld1 {v2.2s}, [x0], x2 // Vector load from src[2_0] ld1 {v3.2s}, [x0], x2 // Vector load from src[3_0] add x14, x14, #1 //for checking loop ld1 {v4.2s}, [x0], x2 // Vector load from src[4_0] ld1 {v5.2s}, [x0], x2 // Vector load from src[5_0] loop_8: //for checking loop uaddl v6.8h, v2.8b, v3.8b // temp1 = src[2_0] + src[3_0] uaddl v8.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] uaddl v10.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] mla v8.8h, v6.8h , v22.8h // temp += temp1 * 20 ld1 {v6.2s}, [x0], x2 uaddl v14.8h, v3.8b, v4.8b uaddl v16.8h, v1.8b, v6.8b uaddl v18.8h, v2.8b, v5.8b mls v8.8h, v10.8h , v24.8h // temp -= temp2 * 5 mla v16.8h, v14.8h , v22.8h ld1 {v7.2s}, [x0], x2 uaddl v20.8h, v4.8b, v5.8b uaddl v12.8h, v2.8b, v7.8b uaddl v10.8h, v3.8b, v6.8b mls v16.8h, v18.8h , v24.8h sqrshrun v26.8b, v8.8h, #5 // dst[0_0] = CLIP_U8( (temp + 16) >> 5) mla v12.8h, v20.8h , v22.8h ld1 {v8.2s}, [x7], x2 //Load value for interpolation (row0) ld1 {v9.2s}, [x7], x2 //Load value for interpolation (row1) ld1 {v0.2s}, [x0], x2 uaddl v14.8h, v5.8b, v6.8b sqrshrun v27.8b, v16.8h, #5 urhadd v26.16b, v8.16b , v26.16b // Interpolation step for qpel calculation urhadd v27.16b, v9.16b , v27.16b // Interpolation step for qpel calculation uaddl v20.8h, v3.8b, v0.8b mls v12.8h, v10.8h , v24.8h st1 {v26.2s}, [x1], x3 // Vector store to dst[0_0] uaddl v18.8h, v4.8b, v7.8b mla v20.8h, v14.8h , v22.8h st1 {v27.2s}, [x1], x3 // Vector store to dst[1_0] sqrshrun v28.8b, v12.8h, #5 mls v20.8h, v18.8h , v24.8h ld1 {v12.2s}, [x7], x2 //Load value for interpolation (row2) ld1 {v13.2s}, [x7], x2 //Load value for interpolation (row3) ld1 {v1.2s}, [x0], x2 sqrshrun v29.8b, v20.8h, #5 subs x9, x4, #4 urhadd v28.16b, v12.16b , v28.16b urhadd v29.16b, v13.16b , v29.16b st1 {v28.2s}, [x1], x3 //store row 2 st1 {v29.2s}, [x1], x3 //store row 3 beq end_func // Branch if height==4 uaddl v14.8h, v6.8b, v7.8b // temp1 = src[2_0] + src[3_0] uaddl v16.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] uaddl v18.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 ld1 {v2.2s}, [x0], x2 mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v8.8h, v0.8b, v7.8b uaddl v10.8h, v1.8b, v6.8b uaddl v12.8h, v2.8b, v5.8b sqrshrun v26.8b, v18.8h, #5 mla v12.8h, v8.8h , v22.8h ld1 {v18.2s}, [x7], x2 //Load value for interpolation (row4) ld1 {v19.2s}, [x7], x2 //Load value for interpolation (row5) ld1 {v3.2s}, [x0], x2 mls v12.8h, v10.8h , v24.8h sqrshrun v27.8b, v12.8h, #5 urhadd v26.16b, v18.16b , v26.16b // Interpolation step for qpel calculation urhadd v27.16b, v19.16b , v27.16b // Interpolation step for qpel calculation st1 {v26.2s}, [x1], x3 // store row 4 st1 {v27.2s}, [x1], x3 // store row 5 uaddl v14.8h, v0.8b, v1.8b // temp1 = src[2_0] + src[3_0] uaddl v16.8h, v2.8b, v7.8b // temp = src[0_0] + src[5_0] uaddl v18.8h, v3.8b, v6.8b // temp2 = src[1_0] + src[4_0] mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 ld1 {v4.2s}, [x0], x2 mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v8.8h, v2.8b, v1.8b uaddl v10.8h, v3.8b, v0.8b uaddl v12.8h, v4.8b, v7.8b sqrshrun v26.8b, v18.8h, #5 mla v12.8h, v8.8h , v22.8h ld1 {v18.2s}, [x7], x2 //Load value for interpolation (row6) ld1 {v19.2s}, [x7], x2 //Load value for interpolation (row7) ld1 {v5.2s}, [x0], x2 mls v12.8h, v10.8h , v24.8h sqrshrun v27.8b, v12.8h, #5 urhadd v26.16b, v18.16b , v26.16b // Interpolation step for qpel calculation urhadd v27.16b, v19.16b , v27.16b // Interpolation step for qpel calculation subs x12, x14, #1 st1 {v26.2s}, [x1], x3 // store row 6 st1 {v27.2s}, [x1], x3 // store row 7 add x14, x14, #1 beq loop_8 //looping if height ==16 b end_func loop_4_start: //// Processing row0 and row1 ld1 {v0.s}[0], [x0], x2 // Vector load from src[0_0] ld1 {v1.s}[0], [x0], x2 // Vector load from src[1_0] ld1 {v2.s}[0], [x0], x2 // Vector load from src[2_0] ld1 {v3.s}[0], [x0], x2 // Vector load from src[3_0] ld1 {v4.s}[0], [x0], x2 // Vector load from src[4_0] ld1 {v5.s}[0], [x0], x2 // Vector load from src[5_0] uaddl v6.8h, v2.8b, v3.8b // temp1 = src[2_0] + src[3_0] uaddl v8.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] uaddl v10.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] mla v8.8h, v6.8h , v22.8h // temp += temp1 * 20 ld1 {v6.2s}, [x0], x2 uaddl v14.8h, v3.8b, v4.8b uaddl v16.8h, v1.8b, v6.8b uaddl v18.8h, v2.8b, v5.8b mls v8.8h, v10.8h , v24.8h // temp -= temp2 * 5 ld1 {v7.s}[0], [x0], x2 mla v16.8h, v14.8h , v22.8h uaddl v20.8h, v4.8b, v5.8b uaddl v12.8h, v2.8b, v7.8b uaddl v10.8h, v3.8b, v6.8b mls v16.8h, v18.8h , v24.8h sqrshrun v26.8b, v8.8h, #5 // dst[0_0] = CLIP_U8( (temp + 16) >> 5) ld1 {v8.s}[0], [x7], x2 //Load value for interpolation - row 0 ld1 {v9.s}[0], [x7], x2 //Load value for interpolation - row 1 mla v12.8h, v20.8h , v22.8h ld1 {v0.s}[0], [x0], x2 uaddl v14.8h, v5.8b, v6.8b sqrshrun v27.8b, v16.8h, #5 uaddl v20.8h, v3.8b, v0.8b urhadd v26.16b, v26.16b , v8.16b //Interpolation step for qpel calculation urhadd v27.16b, v27.16b , v9.16b //Interpolation step for qpel calculation mls v12.8h, v10.8h , v24.8h st1 {v26.s}[0], [x1], x3 // Vector store to dst[0_0] uaddl v18.8h, v4.8b, v7.8b mla v20.8h, v14.8h , v22.8h st1 {v27.s}[0], [x1], x3 // store row 1 sqrshrun v28.8b, v12.8h, #5 ld1 {v12.s}[0], [x7], x2 //Load value for interpolation - row 2 ld1 {v13.s}[0], [x7], x2 //Load value for interpolation - row 3 mls v20.8h, v18.8h , v24.8h ld1 {v1.s}[0], [x0], x2 sqrshrun v29.8b, v20.8h, #5 urhadd v28.16b, v12.16b , v28.16b //Interpolation step for qpel calculation urhadd v29.16b, v13.16b , v29.16b //Interpolation step for qpel calculation st1 {v28.s}[0], [x1], x3 //store row 2 st1 {v29.s}[0], [x1], x3 //store row 3 subs x9, x4, #4 beq end_func // Branch if height==4 uaddl v14.8h, v6.8b, v7.8b // temp1 = src[2_0] + src[3_0] uaddl v16.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] uaddl v18.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 ld1 {v2.s}[0], [x0], x2 mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v8.8h, v0.8b, v7.8b uaddl v10.8h, v1.8b, v6.8b uaddl v12.8h, v2.8b, v5.8b sqrshrun v26.8b, v18.8h, #5 ld1 {v18.s}[0], [x7], x2 //Load value for interpolation - row 4 ld1 {v19.s}[0], [x7], x2 //Load value for interpolation - row 5 mla v12.8h, v8.8h , v22.8h ld1 {v3.s}[0], [x0], x2 mls v12.8h, v10.8h , v24.8h sqrshrun v27.8b, v12.8h, #5 urhadd v26.16b, v18.16b , v26.16b //Interpolation step for qpel calculation urhadd v27.16b, v27.16b , v19.16b //Interpolation step for qpel calculation st1 {v26.s}[0], [x1], x3 //store row 4 st1 {v27.s}[0], [x1], x3 // store row 5 uaddl v14.8h, v0.8b, v1.8b // temp1 = src[2_0] + src[3_0] uaddl v16.8h, v2.8b, v7.8b // temp = src[0_0] + src[5_0] uaddl v18.8h, v3.8b, v6.8b // temp2 = src[1_0] + src[4_0] mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 ld1 {v4.s}[0], [x0], x2 mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 uaddl v8.8h, v2.8b, v1.8b uaddl v10.8h, v3.8b, v0.8b uaddl v12.8h, v4.8b, v7.8b sqrshrun v26.8b, v18.8h, #5 ld1 {v18.s}[0], [x7], x2 //Load value for interpolation - row 6 ld1 {v19.s}[0], [x7], x2 //Load value for interpolation - row 7 mla v12.8h, v8.8h , v22.8h ld1 {v5.s}[0], [x0], x2 mls v12.8h, v10.8h , v24.8h sqrshrun v27.8b, v12.8h, #5 urhadd v26.16b, v18.16b , v26.16b //Interpolation step for qpel calculation urhadd v27.16b, v19.16b , v27.16b //Interpolation step for qpel calculation st1 {v26.s}[0], [x1], x3 // store row 6 st1 {v27.s}[0], [x1], x3 // store row 7 end_func: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_intra_pred_chroma_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_intra_pred_chroma.s //* //* @brief //* Contains function definitions for intra chroma prediction . //* //* @author //* Ittiam //* //* @par List of Functions: //* //* - ih264_intra_pred_luma_chroma_mode_vert_av8() //* - ih264_intra_pred_luma_chroma_mode_horz_av8() //* - ih264_intra_pred_luma_chroma_mode_dc_av8() //* - ih264_intra_pred_luma_chroma_mode_plane_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_chroma_intra_pred_filters.c // ///** ///** ///** // .text .p2align 2 .include "ih264_neon_macros.s" .extern ih264_gai1_intrapred_chroma_plane_coeffs1 .extern ih264_gai1_intrapred_chroma_plane_coeffs2 ///** //******************************************************************************* //* //*ih264_intra_pred_chroma_8x8_mode_dc //* //* @brief //* Perform Intra prediction for chroma_8x8 mode:DC //* //* @par Description: //* Perform Intra prediction for chroma_8x8 mode:DC ,described in sec 8.3.4.1 //* //* @param[in] pu1_src //* UWORD8 pointer to the source containing alternate U and V samples //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination with alternate U and V samples //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //** @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_chroma_8x8_mode_dc(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_chroma_8x8_mode_dc_av8 ih264_intra_pred_chroma_8x8_mode_dc_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 mov w19, #5 ands w6, w4, w19 beq none_available cmp w6, #1 beq left_only_available cmp w6, #4 beq top_only_available all_available: ld1 {v0.8b, v1.8b}, [x0] add x6, x0, #18 ld1 {v2.8b, v3.8b}, [x6] uxtl v0.8h, v0.8b uxtl v1.8h, v1.8b addp v0.4s, v0.4s , v0.4s addp v1.4s, v1.4s , v1.4s addp v0.4s, v0.4s , v0.4s addp v1.4s, v1.4s , v1.4s uxtl v2.8h, v2.8b uxtl v3.8h, v3.8b addp v2.4s, v2.4s , v2.4s addp v3.4s, v3.4s , v3.4s addp v2.4s, v2.4s , v2.4s addp v3.4s, v3.4s , v3.4s rshrn v5.8b, v0.8h, #2 dup v21.8h, v5.h[0] rshrn v6.8b, v3.8h, #2 dup v20.8h, v6.h[0] add v1.8h, v1.8h, v2.8h rshrn v1.8b, v1.8h, #3 dup v23.8h, v1.h[0] mov v20.d[0], v23.d[0] add v0.8h, v0.8h, v3.8h rshrn v0.8b, v0.8h, #3 dup v23.8h, v0.h[0] mov v21.d[1], v23.d[0] b store left_only_available: ld1 {v0.8b, v1.8b}, [x0] uxtl v0.8h, v0.8b uxtl v1.8h, v1.8b addp v0.4s, v0.4s , v0.4s addp v1.4s, v1.4s , v1.4s addp v0.4s, v0.4s , v0.4s addp v1.4s, v1.4s , v1.4s rshrn v0.8b, v0.8h, #2 rshrn v1.8b, v1.8h, #2 dup v20.8h , v1.h[0] dup v21.8h, v0.h[0] b store top_only_available: add x6, x0, #18 ld1 {v0.8b, v1.8b}, [x6] uxtl v0.8h, v0.8b uxtl v1.8h, v1.8b addp v0.4s, v0.4s , v0.4s addp v1.4s, v1.4s , v1.4s addp v0.4s, v0.4s , v0.4s addp v1.4s, v1.4s , v1.4s rshrn v0.8b, v0.8h, #2 rshrn v1.8b, v1.8h, #2 dup v20.8h , v0.h[0] dup v21.8h, v1.h[0] mov v20.d[1], v21.d[1] mov v21.d[0], v20.d[0] b store none_available: mov w15, #128 dup v20.16b, w15 dup v21.16b, w15 store: st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v21.16b}, [x1], x3 st1 { v21.16b}, [x1], x3 st1 { v21.16b}, [x1], x3 st1 { v21.16b}, [x1], x3 end_func: ldp x19, x20, [sp], #16 pop_v_regs ret ///****************************************************************************** ///** //******************************************************************************* //* //*ih264_intra_pred_chroma_8x8_mode_horz //* //* @brief //* Perform Intra prediction for chroma_8x8 mode:Horizontal //* //* @par Description: //* Perform Intra prediction for chroma_8x8 mode:Horizontal ,described in sec 8.3.4.2 //* //* @param[in] pu1_src //* UWORD8 pointer to the source containing alternate U and V samples //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination with alternate U and V samples //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels(Not used in this function) //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //void ih264_intra_pred_chroma_8x8_mode_horz(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_chroma_8x8_mode_horz_av8 ih264_intra_pred_chroma_8x8_mode_horz_av8: push_v_regs sxtw x3, w3 ld1 {v0.8h}, [x0] dup v10.8h, v0.h[7] dup v11.8h, v0.h[6] dup v12.8h, v0.h[5] dup v13.8h, v0.h[4] st1 {v10.8h}, [x1], x3 dup v14.8h, v0.h[3] st1 {v11.8h}, [x1], x3 dup v15.8h, v0.h[2] st1 {v12.8h}, [x1], x3 dup v16.8h, v0.h[1] st1 {v13.8h}, [x1], x3 dup v17.8h, v0.h[0] st1 {v14.8h}, [x1], x3 st1 {v15.8h}, [x1], x3 st1 {v16.8h}, [x1], x3 st1 {v17.8h}, [x1], x3 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_chroma_8x8_mode_vert //* //* @brief //* Perform Intra prediction for chroma_8x8 mode:vertical //* //* @par Description: //*Perform Intra prediction for chroma_8x8 mode:vertical ,described in sec 8.3.4.3 //* //* @param[in] pu1_src //* UWORD8 pointer to the source containing alternate U and V samples //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination with alternate U and V samples //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels(Not used in this function) //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //void ih264_intra_pred_chroma_8x8_mode_vert(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_chroma_8x8_mode_vert_av8 ih264_intra_pred_chroma_8x8_mode_vert_av8: push_v_regs sxtw x3, w3 add x0, x0, #18 ld1 {v0.8b, v1.8b}, [x0] st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 pop_v_regs ret ///****************************************************************************** ///** //******************************************************************************* //* //*ih264_intra_pred_chroma_8x8_mode_plane //* //* @brief //* Perform Intra prediction for chroma_8x8 mode:PLANE //* //* @par Description: //* Perform Intra prediction for chroma_8x8 mode:PLANE ,described in sec 8.3.4.4 //* //* @param[in] pu1_src //* UWORD8 pointer to the source containing alternate U and V samples //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination with alternate U and V samples //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_chroma_8x8_mode_plane(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_chroma_8x8_mode_plane_av8 ih264_intra_pred_chroma_8x8_mode_plane_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 ld1 {v0.2s}, [x0] add x10, x0, #10 ld1 {v1.2s}, [x10] add x10, x10, #6 rev64 v5.4h, v0.4h ld1 {v2.2s}, [x10], #8 add x10, x10, #2 rev64 v7.4h, v2.4h ld1 {v3.2s}, [x10] sub x5, x3, #8 #ifdef __APPLE__ adrp x12, _ih264_gai1_intrapred_chroma_plane_coeffs1@GOTPAGE ldr x12, [x12, _ih264_gai1_intrapred_chroma_plane_coeffs1@GOTPAGEOFF] #else adrp x12, :got:ih264_gai1_intrapred_chroma_plane_coeffs1 ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_chroma_plane_coeffs1] #endif usubl v10.8h, v5.8b, v1.8b ld1 {v8.8b, v9.8b}, [x12] // Load multiplication factors 1 to 8 into D3 mov v8.d[1], v9.d[0] usubl v12.8h, v3.8b, v7.8b mul v14.8h, v10.8h , v8.8h mul v16.8h, v12.8h , v8.8h uzp1 v15.8h, v14.8h, v16.8h uzp2 v16.8h, v14.8h, v16.8h mov v14.16b, v15.16b mov v15.d[0], v14.d[1] mov v17.d[0], v16.d[1] addp v14.4h, v14.4h, v14.4h addp v15.4h, v15.4h, v15.4h addp v16.4h, v16.4h, v16.4h addp v17.4h, v17.4h, v17.4h addp v14.4h, v14.4h, v14.4h addp v15.4h, v15.4h, v15.4h addp v16.4h, v16.4h, v16.4h addp v17.4h, v17.4h, v17.4h mov x6, #34 dup v18.8h, w6 smull v22.4s, v14.4h, v18.4h smull v24.4s, v15.4h, v18.4h smull v26.4s, v16.4h, v18.4h smull v28.4s, v17.4h, v18.4h rshrn v10.4h, v22.4s, #6 rshrn v12.4h, v24.4s, #6 rshrn v13.4h, v26.4s, #6 rshrn v14.4h, v28.4s, #6 ldrb w6, [x0], #1 add x10, x0, #31 ldrb w8, [x0], #1 ldrb w7, [x10], #1 ldrb w9, [x10], #1 add w6, w6, w7 add w8, w8, w9 lsl w6, w6, #4 lsl w8, w8, #4 dup v0.8h, w6 dup v2.8h, w8 dup v4.8h, v12.h[0] dup v6.8h, v10.h[0] dup v24.8h, v14.h[0] dup v26.8h, v13.h[0] zip1 v5.8h, v4.8h, v24.8h zip2 v24.8h, v4.8h, v24.8h mov v4.16b, v5.16b zip1 v7.8h, v6.8h, v26.8h zip2 v26.8h, v6.8h, v26.8h mov v6.16b, v7.16b zip1 v1.8h, v0.8h, v2.8h zip2 v2.8h, v0.8h, v2.8h mov v0.16b, v1.16b #ifdef __APPLE__ adrp x12, _ih264_gai1_intrapred_chroma_plane_coeffs2@GOTPAGE ldr x12, [x12, _ih264_gai1_intrapred_chroma_plane_coeffs2@GOTPAGEOFF] #else adrp x12, :got:ih264_gai1_intrapred_chroma_plane_coeffs2 ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_chroma_plane_coeffs2] #endif ld1 {v8.2s, v9.2s}, [x12] mov v8.d[1], v9.d[0] mov v10.16b, v8.16b mov v22.16b, v8.16b zip1 v9.8h, v8.8h, v10.8h zip2 v10.8h, v8.8h, v10.8h mov v8.16b, v9.16b mul v12.8h, v4.8h , v8.8h mul v16.8h, v4.8h , v10.8h add v12.8h, v0.8h , v12.8h add v16.8h, v0.8h , v16.8h dup v20.8h, v22.h[0] mul v4.8h, v6.8h , v20.8h dup v30.8h, v22.h[1] mul v18.8h, v6.8h , v20.8h mul v14.8h, v6.8h , v30.8h mul v8.8h, v6.8h , v30.8h add v24.8h, v12.8h , v4.8h add v0.8h, v16.8h , v18.8h add v2.8h, v12.8h , v14.8h sqrshrun v28.8b, v24.8h, #5 add v26.8h, v16.8h , v8.8h sqrshrun v29.8b, v0.8h, #5 dup v20.8h, v22.h[2] st1 {v28.8b, v29.8b}, [x1], x3 sqrshrun v28.8b, v2.8h, #5 sqrshrun v29.8b, v26.8h, #5 mul v4.8h, v6.8h , v20.8h mul v18.8h, v6.8h , v20.8h st1 {v28.8b, v29.8b}, [x1], x3 add v24.8h, v12.8h , v4.8h add v0.8h, v16.8h , v18.8h dup v30.8h, v22.h[3] sqrshrun v28.8b, v24.8h, #5 sqrshrun v29.8b, v0.8h, #5 mul v14.8h, v6.8h , v30.8h mul v8.8h, v6.8h , v30.8h st1 {v28.8b, v29.8b}, [x1], x3 add v2.8h, v12.8h , v14.8h add v26.8h, v16.8h , v8.8h dup v20.8h, v22.h[4] sqrshrun v28.8b, v2.8h, #5 sqrshrun v29.8b, v26.8h, #5 mul v4.8h, v6.8h , v20.8h mul v18.8h, v6.8h , v20.8h st1 {v28.8b, v29.8b}, [x1], x3 add v24.8h, v12.8h , v4.8h add v0.8h, v16.8h , v18.8h dup v30.8h, v22.h[5] sqrshrun v28.8b, v24.8h, #5 sqrshrun v29.8b, v0.8h, #5 mul v14.8h, v6.8h , v30.8h mul v8.8h, v6.8h , v30.8h st1 {v28.8b, v29.8b}, [x1], x3 add v2.8h, v12.8h , v14.8h add v26.8h, v16.8h , v8.8h dup v20.8h, v22.h[6] sqrshrun v28.8b, v2.8h, #5 sqrshrun v29.8b, v26.8h, #5 mul v4.8h, v6.8h , v20.8h mul v18.8h, v6.8h , v20.8h st1 {v28.8b, v29.8b}, [x1], x3 add v24.8h, v12.8h , v4.8h add v0.8h, v16.8h , v18.8h dup v30.8h, v22.h[7] sqrshrun v28.8b, v24.8h, #5 sqrshrun v29.8b, v0.8h, #5 mul v14.8h, v6.8h , v30.8h mul v8.8h, v6.8h , v30.8h st1 {v28.8b, v29.8b}, [x1], x3 add v2.8h, v12.8h , v14.8h add v26.8h, v16.8h , v8.8h sqrshrun v28.8b, v2.8h, #5 sqrshrun v29.8b, v26.8h, #5 st1 {v28.8b, v29.8b}, [x1], x3 end_func_plane: ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_intra_pred_luma_16x16_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_intra_pred_luma_16x16_av8.s //* //* @brief //* Contains function definitions for intra 16x16 Luma prediction . //* //* @author //* Ittiam //* //* @par List of Functions: //* //* - ih264_intra_pred_luma_16x16_mode_vert_av8() //* - ih264_intra_pred_luma_16x16_mode_horz_av8() //* - ih264_intra_pred_luma_16x16_mode_dc_av8() //* - ih264_intra_pred_luma_16x16_mode_plane_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_intra_pred_filters.c // ///** ///** ///** // .text .p2align 2 .include "ih264_neon_macros.s" .extern ih264_gai1_intrapred_luma_plane_coeffs ///** //******************************************************************************* //* //*ih264_intra_pred_luma_16x16_mode_vert //* //* @brief //* Perform Intra prediction for luma_16x16 mode:vertical //* //* @par Description: //* Perform Intra prediction for luma_16x16 mode:Vertical ,described in sec 8.3.3.1 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels(Not used in this function) //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //void ih264_intra_pred_luma_16x16_mode_vert(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_16x16_mode_vert_av8 ih264_intra_pred_luma_16x16_mode_vert_av8: push_v_regs sxtw x3, w3 add x0, x0, #17 ld1 {v0.8b, v1.8b}, [x0] st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 st1 {v0.8b, v1.8b}, [x1], x3 pop_v_regs ret ///****************************************************************************** ///** //******************************************************************************* //* //*ih264_intra_pred_luma_16x16_mode_horz //* //* @brief //* Perform Intra prediction for luma_16x16 mode:horizontal //* //* @par Description: //* Perform Intra prediction for luma_16x16 mode:horizontal ,described in sec 8.3.3.2 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels(Not used in this function) //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //void ih264_intra_pred_luma_16x16_mode_horz(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_16x16_mode_horz_av8 ih264_intra_pred_luma_16x16_mode_horz_av8: push_v_regs sxtw x3, w3 ld1 {v0.16b}, [x0] dup v10.16b, v0.b[15] dup v11.16b, v0.b[14] dup v12.16b, v0.b[13] dup v13.16b, v0.b[12] st1 {v10.16b}, [x1], x3 dup v14.16b, v0.b[11] st1 {v11.16b}, [x1], x3 dup v15.16b, v0.b[10] st1 {v12.16b}, [x1], x3 dup v16.16b, v0.b[9] st1 {v13.16b}, [x1], x3 dup v17.16b, v0.b[8] st1 {v14.16b}, [x1], x3 dup v18.16b, v0.b[7] st1 {v15.16b}, [x1], x3 dup v19.16b, v0.b[6] st1 {v16.16b}, [x1], x3 dup v20.16b, v0.b[5] st1 {v17.16b}, [x1], x3 dup v21.16b, v0.b[4] st1 {v18.16b}, [x1], x3 dup v22.16b, v0.b[3] st1 {v19.16b}, [x1], x3 dup v23.16b, v0.b[2] st1 {v20.16b}, [x1], x3 dup v24.16b, v0.b[1] st1 {v21.16b}, [x1], x3 dup v25.16b, v0.b[0] st1 {v22.16b}, [x1], x3 st1 {v23.16b}, [x1], x3 st1 {v24.16b}, [x1], x3 st1 {v25.16b}, [x1], x3 pop_v_regs ret ///****************************************************************************** ///** //******************************************************************************* //* //*ih264_intra_pred_luma_16x16_mode_dc //* //* @brief //* Perform Intra prediction for luma_16x16 mode:DC //* //* @par Description: //* Perform Intra prediction for luma_16x16 mode:DC ,described in sec 8.3.3.3 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_16x16_mode_dc(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_16x16_mode_dc_av8 ih264_intra_pred_luma_16x16_mode_dc_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 sub v0.16b, v0.16b, v0.16b sub v1.16b, v1.16b, v1.16b mov w10, #0 mov w11 , #3 ands w6, w4, #0x01 beq top_available //LEFT NOT AVAILABLE ld1 {v0.16b}, [x0] add w10, w10, #8 add w11, w11, #1 top_available: ands w6, w4, #0x04 beq none_available add x6, x0, #17 ld1 {v1.16b}, [x6] add w10, w10, #8 add w11, w11, #1 b summation none_available: cmp w4, #0 bne summation mov w15, #128 dup v20.16b, w15 b store summation: uaddl v2.8h, v0.8b, v1.8b uaddl2 v3.8h, v0.16b, v1.16b dup v10.8h, w10 neg w11, w11 dup v20.8h, w11 add v0.8h, v2.8h, v3.8h mov v1.d[0], v0.d[1] add v0.4h, v0.4h, v1.4h addp v0.4h, v0.4h , v0.4h addp v0.4h, v0.4h , v0.4h add v0.4h, v0.4h, v10.4h uqshl v0.8h, v0.8h, v20.8h sqxtun v0.8b, v0.8h dup v20.16b, v0.b[0] store: st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 st1 { v20.16b}, [x1], x3 end_func: ldp x19, x20, [sp], #16 pop_v_regs ret ///****************************************************************************** ///** //******************************************************************************* //* //*ih264_intra_pred_luma_16x16_mode_plane //* //* @brief //* Perform Intra prediction for luma_16x16 mode:PLANE //* //* @par Description: //* Perform Intra prediction for luma_16x16 mode:PLANE ,described in sec 8.3.3.4 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_16x16_mode_plane(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_16x16_mode_plane_av8 ih264_intra_pred_luma_16x16_mode_plane_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 mov x2, x1 add x1, x0, #17 add x0, x0, #15 mov x8, #9 sub x1, x1, #1 mov x10, x1 //top_left mov x4, #-1 ld1 {v2.2s}, [x1], x8 #ifdef __APPLE__ adrp x7, _ih264_gai1_intrapred_luma_plane_coeffs@GOTPAGE ldr x7, [x7, _ih264_gai1_intrapred_luma_plane_coeffs@GOTPAGEOFF] #else adrp x7, :got:ih264_gai1_intrapred_luma_plane_coeffs ldr x7, [x7, #:got_lo12:ih264_gai1_intrapred_luma_plane_coeffs] #endif ld1 {v0.2s}, [x1] rev64 v2.8b, v2.8b ld1 {v6.2s, v7.2s}, [x7] usubl v0.8h, v0.8b, v2.8b uxtl v16.8h, v6.8b mul v0.8h, v0.8h , v16.8h uxtl v18.8h, v7.8b add x7, x0, x4, lsl #3 sub x0, x7, x4, lsl #1 neg x14, x4 addp v0.8h, v0.8h, v1.8h ldrb w8, [x7], #-1 ldrb w9, [x0], #1 saddlp v0.2s, v0.4h sub w12, w8, w9 ldrb w8, [x7], #-1 saddlp v0.1d, v0.2s ldrb w9, [x0], #1 sub w8, w8, w9 shl v2.2s, v0.2s, #2 add w12, w12, w8, lsl #1 add v0.2s, v0.2s , v2.2s ldrb w8, [x7], #-1 ldrb w9, [x0], #1 srshr v0.2s, v0.2s, #6 // i_b = D0[0] sub w8, w8, w9 ldrb w5, [x7], #-1 add w8, w8, w8, lsl #1 dup v4.8h, v0.h[0] add w12, w12, w8 ldrb w9, [x0], #1 mul v0.8h, v4.8h , v16.8h sub w5, w5, w9 mul v2.8h, v4.8h , v18.8h add w12, w12, w5, lsl #2 ldrb w8, [x7], #-1 ldrb w9, [x0], #1 sub w8, w8, w9 ldrb w5, [x7], #-1 add w8, w8, w8, lsl #2 ldrb w6, [x0], #1 add w12, w12, w8 ldrb w8, [x7], #-1 ldrb w9, [x0], #1 sub w5, w5, w6 sub w8, w8, w9 add w5, w5, w5, lsl #1 sub w20, w8, w8, lsl #3 neg w8, w20 add w12, w12, w5, lsl #1 ldrb w5, [x7], #-1 ldrb w6, [x10] //top_left add w12, w12, w8 sub w9, w5, w6 ldrb w6, [x1, #7] add w12, w12, w9, lsl #3 // i_c = w12 add w8, w5, w6 add w12, w12, w12, lsl #2 lsl w8, w8, #4 // i_a = w8 add w12, w12, #0x20 lsr w12, w12, #6 shl v28.8h, v4.8h, #3 dup v6.8h, w12 dup v30.8h, w8 shl v26.8h, v6.8h, #3 sub v30.8h, v30.8h , v28.8h sub v30.8h, v30.8h , v26.8h add v28.8h, v30.8h , v6.8h add v26.8h, v28.8h , v0.8h add v28.8h, v28.8h , v2.8h sqrshrun v20.8b, v26.8h, #5 sqrshrun v21.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v22.8b, v26.8h, #5 st1 {v20.2s, v21.2s}, [x2], x3 sqrshrun v23.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v20.8b, v26.8h, #5 st1 {v22.2s, v23.2s}, [x2], x3 sqrshrun v21.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v22.8b, v26.8h, #5 st1 {v20.2s, v21.2s}, [x2], x3 sqrshrun v23.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v20.8b, v26.8h, #5 st1 {v22.2s, v23.2s}, [x2], x3 sqrshrun v21.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v22.8b, v26.8h, #5 st1 {v20.2s, v21.2s}, [x2], x3 sqrshrun v23.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v20.8b, v26.8h, #5 st1 {v22.2s, v23.2s}, [x2], x3 sqrshrun v21.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v22.8b, v26.8h, #5 st1 {v20.2s, v21.2s}, [x2], x3 sqrshrun v23.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v20.8b, v26.8h, #5 st1 {v22.2s, v23.2s}, [x2], x3 sqrshrun v21.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v22.8b, v26.8h, #5 st1 {v20.2s, v21.2s}, [x2], x3 sqrshrun v23.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v20.8b, v26.8h, #5 st1 {v22.2s, v23.2s}, [x2], x3 sqrshrun v21.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v22.8b, v26.8h, #5 st1 {v20.2s, v21.2s}, [x2], x3 sqrshrun v23.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v20.8b, v26.8h, #5 st1 {v22.2s, v23.2s}, [x2], x3 sqrshrun v21.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v22.8b, v26.8h, #5 st1 {v20.2s, v21.2s}, [x2], x3 sqrshrun v23.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v20.8b, v26.8h, #5 st1 {v22.2s, v23.2s}, [x2], x3 sqrshrun v21.8b, v28.8h, #5 add v26.8h, v26.8h , v6.8h add v28.8h, v28.8h , v6.8h sqrshrun v22.8b, v26.8h, #5 st1 {v20.2s, v21.2s}, [x2], x3 sqrshrun v23.8b, v28.8h, #5 st1 {v22.2s, v23.2s}, [x2], x3 end_func_plane: ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_intra_pred_luma_4x4_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_intra_pred_luma_4x4_av8.s //* //* @brief //* Contains function definitions for intra 4x4 Luma prediction . //* //* @author //* Ittiam //* //* @par List of Functions: //* //* -ih264_intra_pred_luma_4x4_mode_vert_av8 //* -ih264_intra_pred_luma_4x4_mode_horz_av8 //* -ih264_intra_pred_luma_4x4_mode_dc_av8 //* -ih264_intra_pred_luma_4x4_mode_diag_dl_av8 //* -ih264_intra_pred_luma_4x4_mode_diag_dr_av8 //* -ih264_intra_pred_luma_4x4_mode_vert_r_av8 //* -ih264_intra_pred_luma_4x4_mode_horz_d_av8 //* -ih264_intra_pred_luma_4x4_mode_vert_l_av8 //* -ih264_intra_pred_luma_4x4_mode_horz_u_av8 //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_intra_pred_filters.c // ///** ///** ///** // .text .p2align 2 .include "ih264_neon_macros.s" ///** //******************************************************************************* //* //*ih264_intra_pred_luma_4x4_mode_vert //* //* @brief //* Perform Intra prediction for luma_4x4 mode:vertical //* //* @par Description: //* Perform Intra prediction for luma_4x4 mode:vertical ,described in sec 8.3.1.2.1 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels(Not used in this function) //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //void ih264_intra_pred_luma_4x4_mode_vert(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_vert_av8 ih264_intra_pred_luma_4x4_mode_vert_av8: push_v_regs sxtw x3, w3 add x0, x0, #5 ld1 {v0.s}[0], [x0] st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 pop_v_regs ret ///****************************************************************************** ///** //******************************************************************************* //* //*ih264_intra_pred_luma_4x4_mode_horz //* //* @brief //* Perform Intra prediction for luma_4x4 mode:horizontal //* //* @par Description: //* Perform Intra prediction for luma_4x4 mode:horizontal ,described in sec 8.3.1.2.2 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels(Not used in this function) //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //void ih264_intra_pred_luma_4x4_mode_horz(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_horz_av8 ih264_intra_pred_luma_4x4_mode_horz_av8: push_v_regs sxtw x3, w3 ld1 {v1.s}[0], [x0] dup v0.8b, v1.b[3] dup v2.8b, v1.b[2] st1 {v0.s}[0], [x1], x3 dup v3.8b, v1.b[1] st1 {v2.s}[0], [x1], x3 dup v4.8b, v1.b[0] st1 {v3.s}[0], [x1], x3 st1 {v4.s}[0], [x1], x3 pop_v_regs ret ///****************************************************************************** ///** //******************************************************************************* //* //*ih264_intra_pred_luma_4x4_mode_dc //* //* @brief //* Perform Intra prediction for luma_4x4 mode:DC //* //* @par Description: //* Perform Intra prediction for luma_4x4 mode:DC ,described in sec 8.3.1.2.3 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_4x4_mode_dc(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_dc_av8 ih264_intra_pred_luma_4x4_mode_dc_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 ands w5, w4, #0x01 beq top_available //LEFT NOT AVAILABLE add x10, x0, #3 mov x2, #-1 ldrb w5, [x10], #-1 ldrb w6, [x10], #-1 ldrb w7, [x10], #-1 add w5, w5, w6 ldrb w8, [x10], #-1 add w5, w5, w7 ands w11, w4, #0x04 // CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE add w5, w5, w8 beq left_available add x10, x0, #5 // BOTH LEFT AND TOP AVAILABLE ldrb w6, [x10], #1 ldrb w7, [x10], #1 add w5, w5, w6 ldrb w8, [x10], #1 add w5, w5, w7 ldrb w9, [x10], #1 add w5, w5, w8 add w5, w5, w9 add w5, w5, #4 lsr w5, w5, #3 dup v0.8b, w5 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 b end_func top_available: // ONLT TOP AVAILABLE ands w11, w4, #0x04 // CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE beq none_available add x10, x0, #5 ldrb w6, [x10], #1 ldrb w7, [x10], #1 ldrb w8, [x10], #1 add w5, w6, w7 ldrb w9, [x10], #1 add w5, w5, w8 add w5, w5, w9 add w5, w5, #2 lsr w5, w5, #2 dup v0.8b, w5 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 b end_func left_available: //ONLY LEFT AVAILABLE add x5, x5, #2 lsr x5, x5, #2 dup v0.8b, w5 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 b end_func none_available: //NONE AVAILABLE mov x5, #128 dup v0.8b, w5 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 st1 {v0.s}[0], [x1], x3 b end_func end_func: ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_4x4_mode_diag_dl //* //* @brief //* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left //* //* @par Description: //* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left ,described in sec 8.3.1.2.4 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_4x4_mode_diag_dl(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_diag_dl_av8 ih264_intra_pred_luma_4x4_mode_diag_dl_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 add x0, x0, #5 sub x5, x3, #2 add x6, x0, #7 ld1 {v0.8b}, [x0] ext v1.8b, v0.8b , v0.8b , #1 ext v2.8b, v0.8b , v0.8b , #2 ld1 {v2.b}[6], [x6] uaddl v20.8h, v0.8b, v1.8b uaddl v22.8h, v1.8b, v2.8b add v24.8h, v20.8h , v22.8h sqrshrun v3.8b, v24.8h, #2 st1 {v3.s}[0], [x1], x3 ext v4.8b, v3.8b , v3.8b , #1 st1 {v4.s}[0], [x1], x3 st1 {v3.h}[1], [x1], #2 st1 {v3.h}[2], [x1], x5 st1 {v4.h}[1], [x1], #2 st1 {v4.h}[2], [x1] end_func_diag_dl: ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_4x4_mode_diag_dr //* //* @brief //* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right //* //* @par Description: //* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right ,described in sec 8.3.1.2.5 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_4x4_mode_diag_dr(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_diag_dr_av8 ih264_intra_pred_luma_4x4_mode_diag_dr_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 ld1 {v0.8b}, [x0] add x0, x0, #1 ld1 {v1.8b}, [x0] ext v2.8b, v1.8b , v1.8b , #1 uaddl v20.8h, v0.8b, v1.8b uaddl v22.8h, v1.8b, v2.8b add v24.8h, v20.8h , v22.8h sqrshrun v3.8b, v24.8h, #2 ext v4.8b, v3.8b , v3.8b , #1 sub x5, x3, #2 st1 {v4.h}[1], [x1], #2 st1 {v4.h}[2], [x1], x5 st1 {v3.h}[1], [x1], #2 st1 {v3.h}[2], [x1], x5 st1 {v4.s}[0], [x1], x3 st1 {v3.s}[0], [x1], x3 end_func_diag_dr: ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_4x4_mode_vert_r //* //* @brief //* Perform Intra prediction for luma_4x4 mode:Vertical_Right //* //* @par Description: //* Perform Intra prediction for luma_4x4 mode:Vertical_Right ,described in sec 8.3.1.2.6 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_4x4_mode_vert_r(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_vert_r_av8 ih264_intra_pred_luma_4x4_mode_vert_r_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 ld1 {v0.8b}, [x0] add x0, x0, #1 ld1 {v1.8b}, [x0] ext v2.8b, v1.8b , v1.8b , #1 uaddl v20.8h, v0.8b, v1.8b uaddl v22.8h, v1.8b, v2.8b add v24.8h, v20.8h , v22.8h sqrshrun v4.8b, v20.8h, #1 sqrshrun v3.8b, v24.8h, #2 sub x5, x3, #2 ext v5.8b, v3.8b , v3.8b , #3 st1 {v4.s}[1], [x1], x3 st1 {v5.s}[0], [x1], x3 sub x8, x3, #3 st1 {v3.b}[2], [x1], #1 st1 {v4.h}[2], [x1], #2 st1 {v4.b}[6], [x1], x8 st1 {v3.b}[1], [x1], #1 st1 {v5.h}[0], [x1], #2 st1 {v5.b}[2], [x1] end_func_vert_r: ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_4x4_mode_horz_d //* //* @brief //* Perform Intra prediction for luma_4x4 mode:Horizontal_Down //* //* @par Description: //* Perform Intra prediction for luma_4x4 mode:Horizontal_Down ,described in sec 8.3.1.2.7 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_4x4_mode_horz_d(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_horz_d_av8 ih264_intra_pred_luma_4x4_mode_horz_d_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 ld1 {v0.8b}, [x0] add x0, x0, #1 ld1 {v1.8b}, [x0] ext v2.8b, v1.8b , v0.8b , #1 uaddl v20.8h, v0.8b, v1.8b uaddl v22.8h, v1.8b, v2.8b add v24.8h, v20.8h , v22.8h sqrshrun v4.8b, v20.8h, #1 sqrshrun v5.8b, v24.8h, #2 sub x5, x3, #2 mov v6.8b, v5.8b trn1 v10.8b, v4.8b, v5.8b trn2 v5.8b, v4.8b, v5.8b // mov v4.8b, v10.8b st1 {v5.h}[1], [x1], #2 st1 {v6.h}[2], [x1], x5 st1 {v4.h}[1], [x1], #2 st1 {v5.h}[1], [x1], x5 st1 {v5.h}[0], [x1], #2 st1 {v4.h}[1], [x1], x5 st1 {v4.h}[0], [x1], #2 st1 {v5.h}[0], [x1], x5 end_func_horz_d: ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_4x4_mode_vert_l //* //* @brief //* Perform Intra prediction for luma_4x4 mode:Vertical_Left //* //* @par Description: //* Perform Intra prediction for luma_4x4 mode:Vertical_Left ,described in sec 8.3.1.2.8 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_4x4_mode_vert_l(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_vert_l_av8 ih264_intra_pred_luma_4x4_mode_vert_l_av8: push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 add x0, x0, #4 ld1 {v0.8b}, [x0] add x0, x0, #1 ld1 {v1.8b}, [x0] ext v2.8b, v1.8b , v0.8b , #1 uaddl v20.8h, v0.8b, v1.8b uaddl v22.8h, v1.8b, v2.8b add v24.8h, v20.8h , v22.8h sqrshrun v4.8b, v20.8h, #1 sqrshrun v5.8b, v24.8h, #2 ext v6.8b, v4.8b , v4.8b , #1 ext v7.8b, v5.8b , v5.8b , #1 st1 {v6.s}[0], [x1], x3 ext v8.8b, v4.8b , v4.8b , #2 ext v9.8b, v5.8b , v5.8b , #2 st1 {v7.s}[0], [x1], x3 st1 {v8.s}[0], [x1], x3 st1 {v9.s}[0], [x1], x3 end_func_vert_l: ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_4x4_mode_horz_u //* //* @brief //* Perform Intra prediction for luma_4x4 mode:Horizontal_Up //* //* @par Description: //* Perform Intra prediction for luma_4x4 mode:Horizontal_Up ,described in sec 8.3.1.2.9 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_4x4_mode_horz_u(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_4x4_mode_horz_u_av8 ih264_intra_pred_luma_4x4_mode_horz_u_av8: push_v_regs sxtw x3, w3 stp x19, x20, [sp, #-16]! mov x10, x0 ld1 {v0.8b}, [x0] ldrb w9, [x0], #1 ext v1.8b, v0.8b , v0.8b , #1 ld1 {v0.b}[7], [x10] ext v2.8b, v1.8b , v1.8b , #1 uaddl v20.8h, v0.8b, v1.8b uaddl v22.8h, v1.8b, v2.8b add v24.8h, v20.8h , v22.8h sqrshrun v4.8b, v20.8h, #1 sqrshrun v5.8b, v24.8h, #2 mov v6.8b, v4.8b ext v6.8b, v5.8b , v4.8b , #1 st1 {v4.b}[2], [x1], #1 st1 {v6.b}[0], [x1], #1 trn1 v10.8b, v6.8b, v5.8b trn2 v5.8b, v6.8b, v5.8b // mov v6.8b , v10.8b sub x5, x3, #2 trn1 v10.8b, v4.8b, v6.8b trn2 v6.8b, v4.8b, v6.8b // mov v4.8b , v10.8b dup v7.8b, w9 st1 {v6.h}[0], [x1], x5 st1 {v6.h}[0], [x1], #2 st1 {v5.h}[3], [x1], x5 st1 {v5.h}[3], [x1], #2 st1 {v7.h}[3], [x1], x5 st1 {v7.s}[0], [x1], x3 end_func_horz_u: ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_intra_pred_luma_8x8_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_intra_pred_luma_8x8_av8.s //* //* @brief //* Contains function definitions for intra 8x8 Luma prediction . //* //* @author //* Ittiam //* //* @par List of Functions: //* //* -ih264_intra_pred_luma_8x8_mode_vert_av8 //* -ih264_intra_pred_luma_8x8_mode_horz_av8 //* -ih264_intra_pred_luma_8x8_mode_dc_av8 //* -ih264_intra_pred_luma_8x8_mode_diag_dl_av8 //* -ih264_intra_pred_luma_8x8_mode_diag_dr_av8 //* -ih264_intra_pred_luma_8x8_mode_vert_r_av8 //* -ih264_intra_pred_luma_8x8_mode_horz_d_av8 //* -ih264_intra_pred_luma_8x8_mode_vert_l_av8 //* -ih264_intra_pred_luma_8x8_mode_horz_u_av8 //* //* @remarks //* None //* //******************************************************************************* //*/ ///* All the functions here are replicated from ih264_intra_pred_filters.c // ///** ///** ///** .text .p2align 2 .include "ih264_neon_macros.s" .extern ih264_gai1_intrapred_luma_8x8_horz_u ///** //******************************************************************************* //* //*ih264_intra_pred_luma_8x8_mode_vert //* //* @brief //* Perform Intra prediction for luma_8x8 mode:vertical //* //* @par Description: //* Perform Intra prediction for luma_8x8 mode:vertical ,described in sec 8.3.2.2.2 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels(Not used in this function) //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //void ih264_intra_pred_luma_8x8_mode_vert(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_vert_av8 ih264_intra_pred_luma_8x8_mode_vert_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs //stp x19, x20,[sp,#-16]! sxtw x3, w3 add x0, x0, #9 ld1 {v0.8b}, [x0] st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack //ldp x19, x20,[sp],#16 pop_v_regs ret ///****************************************************************************** ///** //******************************************************************************* //* //*ih264_intra_pred_luma_8x8_mode_horz //* //* @brief //* Perform Intra prediction for luma_8x8 mode:horizontal //* //* @par Description: //* Perform Intra prediction for luma_8x8 mode:horizontal ,described in sec 8.3.2.2.2 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels(Not used in this function) //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //void ih264_intra_pred_luma_8x8_mode_horz(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_horz_av8 ih264_intra_pred_luma_8x8_mode_horz_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 add x0, x0, #7 ldrb w5, [x0], #-1 ldrb w6, [x0], #-1 dup v0.8b, w5 st1 {v0.8b}, [x1], x3 ldrb w7, [x0], #-1 dup v1.8b, w6 st1 {v1.8b}, [x1], x3 dup v2.8b, w7 ldrb w8, [x0], #-1 dup v3.8b, w8 st1 {v2.8b}, [x1], x3 ldrb w5, [x0], #-1 st1 {v3.8b}, [x1], x3 dup v0.8b, w5 ldrb w6, [x0], #-1 st1 {v0.8b}, [x1], x3 ldrb w7, [x0], #-1 dup v1.8b, w6 dup v2.8b, w7 st1 {v1.8b}, [x1], x3 ldrb w8, [x0], #-1 dup v3.8b, w8 st1 {v2.8b}, [x1], x3 st1 {v3.8b}, [x1], x3 // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ///****************************************************************************** ///** //******************************************************************************* //* //*ih264_intra_pred_luma_8x8_mode_dc //* //* @brief //* Perform Intra prediction for luma_8x8 mode:DC //* //* @par Description: //* Perform Intra prediction for luma_8x8 mode:DC ,described in sec 8.3.2.2.3 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_8x8_mode_dc(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_dc_av8 ih264_intra_pred_luma_8x8_mode_dc_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs sxtw x3, w3 stp x19, x20, [sp, #-16]! ands w6, w4, #0x01 beq top_available //LEFT NOT AVAILABLE add x10, x0, #7 mov x2, #-1 ldrb w5, [x10], -1 ldrb w6, [x10], -1 ldrb w7, [x10], -1 add w5, w5, w6 ldrb w8, [x10], -1 add w5, w5, w7 ldrb w6, [x10], -1 add w5, w5, w8 ldrb w7, [x10], -1 add w5, w5, w6 ldrb w8, [x10], -1 add w5, w5, w7 ands w11, w4, #0x04 // CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE add w5, w5, w8 ldrb w6, [x10], -1 add w5, w5, w6 beq left_available add x10, x0, #9 // BOTH LEFT AND TOP AVAILABLE ld1 {v0.8b}, [x10] uaddlp v1.4h, v0.8b uaddlp v3.2s, v1.4h uaddlp v2.1d, v3.2s dup v10.8h, w5 dup v8.8h, v2.h[0] add v12.8h, v8.8h , v10.8h sqrshrun v31.8b, v12.8h, #4 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 b end_func top_available: // ONLT TOP AVAILABLE ands w11, w4, #0x04 // CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE beq none_available add x10, x0, #9 ld1 {v10.8b}, [x10] uaddlp v14.4h, v10.8b uaddlp v13.2s, v14.4h uaddlp v12.1d, v13.2s rshrn v4.8b, v12.8h, #3 dup v31.8b, v4.b[0] st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 st1 {v31.8b}, [x1], x3 b end_func left_available: //ONLY LEFT AVAILABLE add x5, x5, #4 lsr x5, x5, #3 dup v0.8b, w5 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 b end_func none_available: //NONE AVAILABLE mov x9, #128 dup v0.8b, w9 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 st1 {v0.8b}, [x1], x3 end_func: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_8x8_mode_diag_dl //* //* @brief //* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left //* //* @par Description: //* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left ,described in sec 8.3.2.2.4 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_8x8_mode_diag_dl(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_diag_dl_av8 ih264_intra_pred_luma_8x8_mode_diag_dl_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 add x0, x0, #9 sub x5, x3, #4 add x6, x0, #15 ld1 { v0.16b}, [x0] mov v1.d[0], v0.d[1] ext v4.16b, v0.16b , v0.16b , #2 mov v5.d[0], v4.d[1] ext v2.16b, v0.16b , v0.16b , #1 mov v3.d[0], v2.d[1] ld1 {v5.b}[6], [x6] // q1 = q0 shifted to left once // q2 = q1 shifted to left once uaddl v20.8h, v0.8b, v2.8b //Adding for FILT121 uaddl v22.8h, v1.8b, v3.8b uaddl v24.8h, v2.8b, v4.8b uaddl v26.8h, v3.8b, v5.8b add v24.8h, v20.8h , v24.8h add v26.8h, v22.8h , v26.8h sqrshrun v4.8b, v24.8h, #2 sqrshrun v5.8b, v26.8h, #2 mov v4.d[1], v5.d[0] //Q2 has all FILT121 values st1 {v4.8b}, [x1], x3 ext v18.16b, v4.16b , v4.16b , #1 ext v16.16b, v18.16b , v18.16b , #1 st1 {v18.8b}, [x1], x3 ext v14.16b, v16.16b , v16.16b , #1 st1 {v16.8b}, [x1], x3 st1 {v14.8b}, [x1], x3 st1 {v4.s}[1], [x1], #4 st1 {v5.s}[0], [x1], x5 st1 {v18.s}[1], [x1], #4 st1 {v18.s}[2], [x1], x5 st1 {v16.s}[1], [x1], #4 st1 {v16.s}[2], [x1], x5 st1 {v14.s}[1], [x1], #4 st1 {v14.s}[2], [x1], x5 end_func_diag_dl: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_8x8_mode_diag_dr //* //* @brief //* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right //* //* @par Description: //* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right ,described in sec 8.3.2.2.5 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_8x8_mode_diag_dr(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_diag_dr_av8 ih264_intra_pred_luma_8x8_mode_diag_dr_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 ld1 { v0.16b}, [x0] mov v1.d[0], v0.d[1] add x0, x0, #1 ld1 { v2.16b}, [x0] mov v3.d[0], v2.d[1] ext v4.16b, v2.16b , v2.16b , #1 mov v5.d[0], v4.d[1] // q1 = q0 shifted to left once // q2 = q1 shifted to left once uaddl v20.8h, v0.8b, v2.8b //Adding for FILT121 uaddl v22.8h, v1.8b, v3.8b uaddl v24.8h, v2.8b, v4.8b uaddl v26.8h, v3.8b, v5.8b add v24.8h, v20.8h , v24.8h add v26.8h, v22.8h , v26.8h sqrshrun v4.8b, v24.8h, #2 sqrshrun v5.8b, v26.8h, #2 mov v4.d[1], v5.d[0] //Q2 has all FILT121 values sub x5, x3, #4 ext v18.16b, v4.16b , v4.16b , #15 st1 {v18.d}[1], [x1], x3 ext v16.16b, v18.16b , v18.16b , #15 st1 {v16.d}[1], [x1], x3 ext v14.16b, v16.16b , v16.16b , #15 st1 {v14.d}[1], [x1], x3 st1 {v4.s}[1], [x1], #4 st1 {v5.s}[0], [x1], x5 st1 {v18.s}[1], [x1], #4 st1 {v18.s}[2], [x1], x5 st1 {v16.s}[1], [x1], #4 st1 {v16.s}[2], [x1], x5 st1 {v14.s}[1], [x1], #4 st1 {v14.s}[2], [x1], x5 st1 {v4.8b}, [x1], x3 end_func_diag_dr: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_8x8_mode_vert_r //* //* @brief //* Perform Intra prediction for luma_8x8 mode:Vertical_Right //* //* @par Description: //* Perform Intra prediction for luma_8x8 mode:Vertical_Right ,described in sec 8.3.2.2.6 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_8x8_mode_vert_r(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_vert_r_av8 ih264_intra_pred_luma_8x8_mode_vert_r_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 ld1 { v0.16b}, [x0] mov v1.d[0], v0.d[1] add x0, x0, #1 ld1 { v2.16b}, [x0] mov v3.d[0], v2.d[1] ext v4.16b, v2.16b , v2.16b , #1 mov v5.d[0], v4.d[1] // q1 = q0 shifted to left once // q2 = q1 shifted to left once uaddl v20.8h, v0.8b, v2.8b uaddl v22.8h, v1.8b, v3.8b uaddl v24.8h, v2.8b, v4.8b uaddl v26.8h, v3.8b, v5.8b add v24.8h, v20.8h , v24.8h add v26.8h, v22.8h , v26.8h sqrshrun v4.8b, v20.8h, #1 sqrshrun v5.8b, v22.8h, #1 mov v4.d[1], v5.d[0] sqrshrun v6.8b, v24.8h, #2 sqrshrun v7.8b, v26.8h, #2 mov v6.d[1], v7.d[0] //Q2 has all FILT11 values //Q3 has all FILT121 values sub x5, x3, #6 sub x6, x3, #4 st1 {v5.8b}, [x1], x3 // row 0 ext v18.16b, v6.16b , v6.16b , #15 mov v22.16b , v18.16b ext v16.16b, v4.16b , v4.16b , #1 st1 {v18.d}[1], [x1], x3 //row 1 mov v14.16b , v16.16b ext v20.16b, v4.16b , v4.16b , #15 uzp1 v17.16b, v16.16b, v18.16b uzp2 v18.16b, v16.16b, v18.16b mov v16.16b , v17.16b //row 2 ext v12.16b, v16.16b , v16.16b , #1 st1 {v20.d}[1], [x1] st1 {v6.b}[6], [x1], x3 //row 3 st1 {v12.h}[5], [x1], #2 st1 {v6.s}[2], [x1], #4 st1 {v6.h}[6], [x1], x5 //row 4 st1 {v18.h}[5], [x1], #2 st1 {v4.s}[2], [x1], #4 st1 {v4.h}[6], [x1], x5 //row 5 ext v26.16b, v18.16b , v18.16b , #1 st1 {v16.h}[5], [x1], #2 st1 {v22.s}[2], [x1], #4 st1 {v22.h}[6], [x1], x5 //row 6 st1 {v26.h}[4], [x1], #2 st1 {v26.b}[10], [x1], #1 st1 {v4.b}[8], [x1], #1 st1 {v14.s}[2], [x1], x6 //row 7 st1 {v12.s}[2], [x1], #4 st1 {v6.s}[2], [x1], #4 end_func_vert_r: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_8x8_mode_horz_d //* //* @brief //* Perform Intra prediction for luma_8x8 mode:Horizontal_Down //* //* @par Description: //* Perform Intra prediction for luma_8x8 mode:Horizontal_Down ,described in sec 8.3.2.2.7 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_8x8_mode_horz_d(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_horz_d_av8 ih264_intra_pred_luma_8x8_mode_horz_d_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 ld1 { v0.16b}, [x0] mov v1.d[0], v0.d[1] add x0, x0, #1 ld1 { v2.16b}, [x0] mov v3.d[0], v2.d[1] ext v4.16b, v2.16b , v2.16b , #1 mov v5.d[0], v4.d[1] // q1 = q0 shifted to left once // q2 = q1 shifted to left once uaddl v20.8h, v0.8b, v2.8b uaddl v22.8h, v1.8b, v3.8b uaddl v24.8h, v2.8b, v4.8b uaddl v26.8h, v3.8b, v5.8b add v24.8h, v20.8h , v24.8h add v26.8h, v22.8h , v26.8h sqrshrun v4.8b, v20.8h, #1 sqrshrun v5.8b, v22.8h, #1 mov v4.d[1], v5.d[0] sqrshrun v6.8b, v24.8h, #2 sqrshrun v7.8b, v26.8h, #2 mov v6.d[1], v7.d[0] //Q2 has all FILT11 values //Q3 has all FILT121 values mov v8.16b, v4.16b mov v10.16b, v6.16b sub x6, x3, #6 trn1 v9.16b, v8.16b, v10.16b trn2 v10.16b, v8.16b, v10.16b // mov v8.16b, v9.16b mov v12.16b, v8.16b mov v14.16b, v10.16b sub x5, x3, #4 trn1 v13.8h, v12.8h, v14.8h trn2 v14.8h, v12.8h, v14.8h mov v12.16b, v13.16b ext v16.16b, v6.16b , v6.16b , #14 //ROW 0 st1 {v16.d}[1], [x1] st1 {v10.h}[3], [x1], x3 //ROW 1 st1 {v14.s}[1], [x1], #4 st1 {v6.s}[2], [x1], x5 //ROW 2 st1 {v10.h}[2], [x1], #2 st1 {v14.s}[1], [x1], #4 st1 {v7.h}[0], [x1], x6 //ROW 3 st1 {v12.s}[1], [x1], #4 st1 {v14.s}[1], [x1], x5 //ROW 4 st1 {v14.h}[1], [x1], #2 st1 {v12.s}[1], [x1], #4 st1 {v14.h}[2], [x1], x6 //ROW 5 st1 {v14.s}[0], [x1], #4 st1 {v12.s}[1], [x1], x5 //ROW 6 st1 {v10.h}[0], [x1], #2 st1 {v8.h}[1], [x1], #2 st1 {v14.h}[1], [x1], #2 st1 {v12.h}[2], [x1], x6 //ROW 7 st1 {v12.s}[0], [x1], #4 st1 {v14.s}[0], [x1], x5 end_func_horz_d: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_8x8_mode_vert_l //* //* @brief //* Perform Intra prediction for luma_8x8 mode:Vertical_Left //* //* @par Description: //* Perform Intra prediction for luma_8x8 mode:Vertical_Left ,described in sec 8.3.2.2.8 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_8x8_mode_vert_l(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_vert_l_av8 ih264_intra_pred_luma_8x8_mode_vert_l_av8: // STMFD sp!, {x4-x12, x14} //Restoring registers from stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 add x0, x0, #9 ld1 { v0.16b}, [x0] mov v1.d[0], v0.d[1] add x0, x0, #1 ld1 { v2.16b}, [x0] mov v3.d[0], v2.d[1] ext v4.16b, v2.16b , v2.16b , #1 mov v5.d[0], v4.d[1] uaddl v20.8h, v0.8b, v2.8b uaddl v22.8h, v1.8b, v3.8b uaddl v24.8h, v2.8b, v4.8b uaddl v26.8h, v3.8b, v5.8b add v24.8h, v20.8h , v24.8h add v26.8h, v22.8h , v26.8h sqrshrun v4.8b, v20.8h, #1 sqrshrun v5.8b, v22.8h, #1 mov v4.d[1], v5.d[0] sqrshrun v6.8b, v24.8h, #2 ext v8.16b, v4.16b , v4.16b , #1 sqrshrun v7.8b, v26.8h, #2 mov v6.d[1], v7.d[0] //Q2 has all FILT11 values //Q3 has all FILT121 values ext v10.16b, v6.16b , v6.16b , #1 //ROW 0,1 st1 {v4.8b}, [x1], x3 st1 {v6.8b}, [x1], x3 ext v12.16b, v8.16b , v8.16b , #1 ext v14.16b, v10.16b , v10.16b , #1 //ROW 2,3 st1 {v8.8b}, [x1], x3 st1 {v10.8b}, [x1], x3 ext v16.16b, v12.16b , v12.16b , #1 ext v18.16b, v14.16b , v14.16b , #1 //ROW 4,5 st1 {v12.8b}, [x1], x3 st1 {v14.8b}, [x1], x3 //ROW 6,7 st1 {v16.8b}, [x1], x3 st1 {v18.8b}, [x1], x3 end_func_vert_l: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //*ih264_intra_pred_luma_8x8_mode_horz_u //* //* @brief //* Perform Intra prediction for luma_8x8 mode:Horizontal_Up //* //* @par Description: //* Perform Intra prediction for luma_8x8 mode:Horizontal_Up ,described in sec 8.3.2.2.9 //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[out] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] src_strd //* integer source stride //* //* @param[in] dst_strd //* integer destination stride //* //* @param[in] ui_neighboravailability //* availability of neighbouring pixels //* //* @returns //* //* @remarks //* None //* //*******************************************************************************/ //void ih264_intra_pred_luma_8x8_mode_horz_u(UWORD8 *pu1_src, // UWORD8 *pu1_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 ui_neighboravailability) //**************Variables Vs Registers***************************************** // x0 => *pu1_src // x1 => *pu1_dst // w2 => src_strd // w3 => dst_strd // w4 => ui_neighboravailability .global ih264_intra_pred_luma_8x8_mode_horz_u_av8 ih264_intra_pred_luma_8x8_mode_horz_u_av8: // STMFD sp!, {x4-x12, x14} //store register values to stack push_v_regs stp x19, x20, [sp, #-16]! sxtw x3, w3 ld1 {v0.8b}, [x0] ld1 {v1.b}[7], [x0] mov v0.d[1], v1.d[0] ext v2.16b, v0.16b , v0.16b , #1 mov v3.d[0], v2.d[1] ext v4.16b, v2.16b , v2.16b , #1 mov v5.d[0], v4.d[1] #ifdef __APPLE__ adrp x12, _ih264_gai1_intrapred_luma_8x8_horz_u@GOTPAGE ldr x12, [x12, _ih264_gai1_intrapred_luma_8x8_horz_u@GOTPAGEOFF] #else adrp x12, :got:ih264_gai1_intrapred_luma_8x8_horz_u ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_luma_8x8_horz_u] #endif uaddl v20.8h, v0.8b, v2.8b uaddl v22.8h, v1.8b, v3.8b uaddl v24.8h, v2.8b, v4.8b uaddl v26.8h, v3.8b, v5.8b add v24.8h, v20.8h , v24.8h add v26.8h, v22.8h , v26.8h ld1 { v10.16b}, [x12] mov v11.d[0], v10.d[1] sqrshrun v4.8b, v20.8h, #1 sqrshrun v5.8b, v22.8h, #1 mov v4.d[1], v5.d[0] sqrshrun v6.8b, v24.8h, #2 sqrshrun v7.8b, v26.8h, #2 mov v6.d[1], v7.d[0] //Q2 has all FILT11 values //Q3 has all FILT121 values mov v30.16b, v4.16b mov v31.16b, v6.16b tbl v12.8b, {v30.16b, v31.16b}, v10.8b dup v14.16b, v5.b[7] // tbl v13.8b, {v30.16b, v31.16b}, v11.8b mov v12.d[1], v13.d[0] ext v16.16b, v12.16b , v14.16b , #2 ext v18.16b, v16.16b , v14.16b , #2 st1 {v12.8b}, [x1], x3 //0 ext v20.16b, v18.16b , v14.16b , #2 st1 {v16.8b}, [x1], x3 //1 st1 {v18.8b}, [x1], x3 //2 st1 {v20.8b}, [x1], x3 //3 st1 {v13.8b}, [x1], x3 //4 st1 {v16.d}[1], [x1], x3 //5 st1 {v18.d}[1], [x1], x3 //6 st1 {v20.d}[1], [x1], x3 //7 end_func_horz_u: // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_iquant_itrans_recon_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** ///******************************************************************************* // * //file // * ih264_iquant_itrans_recon_a9.s // * // * //brief // * Contains function definitions for single stage inverse transform // * // * //author // * Parthiban V // * Mohit // * Harinarayanaan // * // * //par List of Functions: // * - ih264_iquant_itrans_recon_4x4_av8() // * - ih264_iquant_itrans_recon_8x8_av8() // * - ih264_iquant_itrans_recon_chroma_4x4_av8() // * // * //remarks // * None // * // ******************************************************************************* .text .p2align 2 .include "ih264_neon_macros.s" ///* // ******************************************************************************* // * // * //brief // * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block // * // * //par Description: // * Performs inverse transform Ci4 and adds the residue to get the // * reconstructed block // * // * //param[in] pi2_src // * Input 4x4 coefficients // * // * //param[in] pu1_pred // * Prediction 4x4 block // * // * //param[out] pu1_out // * Output 4x4 block // * // * //param[in] u4_qp_div_6 // * QP // * // * //param[in] pu2_weigh_mat // * Pointer to weight matrix // * // * //param[in] pred_strd, // * Prediction stride // * // * //param[in] out_strd // * Output Stride // * // *//param[in] pi2_tmp // * temporary buffer of size 1*16 // * // * //param[in] pu2_iscal_mat // * Pointer to the inverse quantization matrix // * // * //returns Void // * // * //remarks // * None // * // ******************************************************************************* // */ //void ih264_iquant_itrans_recon_4x4(WORD16 *pi2_src, // UWORD8 *pu1_pred, // UWORD8 *pu1_out, // WORD32 pred_strd, // WORD32 out_strd, // const UWORD16 *pu2_iscal_mat, // const UWORD16 *pu2_weigh_mat, // UWORD32 u4_qp_div_6, // WORD32 *pi4_tmp, // WORD32 iq_start_idx // WORD16 *pi2_dc_ld_addr) //**************Variables Vs Registers***************************************** //x0 => *pi2_src //x1 => *pu1_pred //x2 => *pu1_out //w3 => pred_strd //w4 => out_strd //x5 => *pu2_iscal_mat //x6 => *pu2_weigh_mat //w7 => u4_qp_div_6 // => pi4_tmp // => iq_start_idx // => pi2_dc_ld_addr //Only one shift is done in horizontal inverse because, //if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value //if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 .global ih264_iquant_itrans_recon_4x4_av8 ih264_iquant_itrans_recon_4x4_av8: push_v_regs sxtw x3, w3 sxtw x4, w4 dup v30.4s, w7 //Populate the u4_qp_div_6 in Q15 ldr w8, [sp, #72] //Loads iq_start_idx sxtw x8, w8 ldr x10, [sp, #80] //Load alternate dc address subs x8, x8, #1 // if x8 == 1 => intra case , so result of subtraction is zero and z flag is set //=======================DEQUANT FROM HERE=================================== ld4 {v20.4h - v23.4h}, [x5] // load pu2_iscal_mat[i], i =0..15 ld4 {v26.4h - v29.4h}, [x6] // pu2_weigh_mat[i], i =0..15 ld4 {v16.4h - v19.4h}, [x0] // pi2_src_tmp[i], i =0..15 mul v20.4h, v20.4h, v26.4h // x[i]=(scale[i] * dequant[i]) where i = 0..3 mul v21.4h, v21.4h, v27.4h // x[i]=(scale[i] * dequant[i]) where i = 4..7 mul v22.4h, v22.4h, v28.4h // x[i]=(scale[i] * dequant[i]) where i = 8..11 mul v23.4h, v23.4h, v29.4h // x[i]=(scale[i] * dequant[i]) where i = 12..14 smull v0.4s, v16.4h, v20.4h // q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 smull v2.4s, v17.4h, v21.4h // q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 smull v4.4s, v18.4h, v22.4h // q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 smull v6.4s, v19.4h, v23.4h // q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 sshl v0.4s, v0.4s, v30.4s // q0 = q[i] = (p[i] << (qp/6)) where i = 0..3 sshl v2.4s, v2.4s, v30.4s // q1 = q[i] = (p[i] << (qp/6)) where i = 4..7 sshl v4.4s, v4.4s, v30.4s // q2 = q[i] = (p[i] << (qp/6)) where i = 8..11 sshl v6.4s, v6.4s, v30.4s // q3 = q[i] = (p[i] << (qp/6)) where i = 12..15 sqrshrn v0.4h, v0.4s, #0x4 // d0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 sqrshrn v1.4h, v2.4s, #0x4 // d1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 sqrshrn v2.4h, v4.4s, #0x4 // d2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 sqrshrn v3.4h, v6.4s, #0x4 // d3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 bne skip_loading_luma_dc_src ld1 {v0.h}[0], [x10] // loads signed halfword pi2_dc_ld_addr[0], if x8==1 skip_loading_luma_dc_src: //========= PROCESS IDCT FROM HERE ======= //Steps for Stage 1: //------------------ ld1 {v30.s}[0], [x1], x3 // i row load pu1_pred buffer sshr v8.4h, v1.4h, #1 // d1>>1 sshr v9.4h, v3.4h, #1 // d3>>1 add v4.4h, v0.4h, v2.4h // x0 = d0 + d2// sub v5.4h, v0.4h, v2.4h // x1 = d0 - d2// sub v6.4h, v8.4h, v3.4h // x2 = (d1 >> 1) - d3// add v7.4h, v1.4h, v9.4h // x3 = d1 + (d3 >> 1)// ld1 {v30.s}[1], [x1], x3 // ii row load pu1_pred buffer add v10.4h, v4.4h , v7.4h // x0+x3 add v11.4h, v5.4h , v6.4h // x1+x2 sub v12.4h, v5.4h , v6.4h // x1-x2 sub v13.4h, v4.4h , v7.4h ld1 {v31.s}[0], [x1], x3 // iii row load pu1_pred buf //Steps for Stage 2: //transopose trn1 v4.4h, v10.4h, v11.4h trn2 v5.4h, v10.4h, v11.4h trn1 v6.4h, v12.4h, v13.4h trn2 v7.4h, v12.4h, v13.4h trn1 v10.2s, v4.2s, v6.2s // 0 trn1 v11.2s, v5.2s, v7.2s // 8 trn2 v12.2s, v4.2s, v6.2s // 4 trn2 v13.2s, v5.2s, v7.2s //end transpose sshr v18.4h, v11.4h, #1 // q0>>1 sshr v19.4h, v13.4h, #1 // q1>>1 add v14.4h, v10.4h, v12.4h // x0 = q0 + q2// sub v15.4h, v10.4h, v12.4h // x1 = q0 - q2// sub v16.4h, v18.4h, v13.4h // x2 = (q1 >> 1) - q3// add v17.4h, v11.4h, v19.4h // x3 = q1+ (q3 >> 3)// ld1 {v31.s}[1], [x1], x3 // iv row load pu1_pred buffer add v20.4h, v14.4h, v17.4h // x0 + x3 add v21.4h, v15.4h, v16.4h // x1 + x2 sub v22.4h, v15.4h, v16.4h // x1 - x2 sub v23.4h, v14.4h, v17.4h // x0 - x3 mov v20.d[1], v21.d[0] mov v22.d[1], v23.d[0] srshr v20.8h, v20.8h, #6 srshr v22.8h, v22.8h, #6 uaddw v20.8h, v20.8h , v30.8b uaddw v22.8h, v22.8h , v31.8b sqxtun v0.8b, v20.8h sqxtun v1.8b, v22.8h st1 {v0.s}[0], [x2], x4 //i row store the value st1 {v0.s}[1], [x2], x4 //ii row store the value st1 {v1.s}[0], [x2], x4 //iii row store the value st1 {v1.s}[1], [x2] //iv row store the value pop_v_regs ret ///** // ******************************************************************************* // * // * @brief // * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block // * // * @par Description: // * Performs inverse transform Ci4 and adds the residue to get the // * reconstructed block // * // * @param[in] pi2_src // * Input 4x4 coefficients // * // * @param[in] pu1_pred // * Prediction 4x4 block // * // * @param[out] pu1_out // * Output 4x4 block // * // * @param[in] u4_qp_div_6 // * QP // * // * @param[in] pu2_weigh_mat // * Pointer to weight matrix // * // * @param[in] pred_strd, // * Prediction stride // * // * @param[in] out_strd // * Output Stride // * // *@param[in] pi2_tmp // * temporary buffer of size 1*16 // * // * @param[in] pu2_iscal_mat // * Pointer to the inverse quantization matrix // * // * @returns Void // * // * @remarks // * None // * // ******************************************************************************* // */ //void ih264_iquant_itrans_recon_chroma_4x4(WORD16 *pi2_src, // UWORD8 *pu1_pred, // UWORD8 *pu1_out, // WORD32 pred_strd, // WORD32 out_strd, // const UWORD16 *pu2_iscal_mat, // const UWORD16 *pu2_weigh_mat, // UWORD32 u4_qp_div_6, // WORD32 *pi4_tmp // WORD16 *pi2_dc_src) //**************Variables Vs Registers***************************************** //x0 => *pi2_src //x1 => *pu1_pred //x2 => *pu1_out //w3 => pred_strd //w4 => out_strd //x5 => *pu2_iscal_mat //x6 => *pu2_weigh_mat //w7 => u4_qp_div_6 //sp => pi4_tmp //sp#8 => *pi2_dc_src .global ih264_iquant_itrans_recon_chroma_4x4_av8 ih264_iquant_itrans_recon_chroma_4x4_av8: //VLD4.S16 is used because the pointer is incremented by SUB_BLK_WIDTH_4x4 //If the macro value changes need to change the instruction according to it. //Only one shift is done in horizontal inverse because, //if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value //if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 //at the end of the fucntion, we could have moved 64 bits into heigher 64 bits of register and done further processing //but it seem to give only reduce the number of instruction by 1. [Since a15 we saw add and sub to be very high throughput //all instructions were taken as equal //reduce sp by 64 push_v_regs sxtw x3, w3 sxtw x4, w4 dup v30.4s, w7 //Populate the u4_qp_div_6 in Q15 //was at sp + 8, hence now at sp+64+8 = sp+72 ldr x10, [sp, #72] //Load alternate dc address //=======================DEQUANT FROM HERE=================================== ld4 {v20.4h - v23.4h}, [x5] // load pu2_iscal_mat[i], i =0..15 ld4 {v26.4h - v29.4h}, [x6] // pu2_weigh_mat[i], i =0..15 ld4 {v16.4h - v19.4h}, [x0] // pi2_src_tmp[i], i =0..15 mul v20.4h, v20.4h, v26.4h // x[i]=(scale[i] * dequant[i]) where i = 0..3 mul v21.4h, v21.4h, v27.4h // x[i]=(scale[i] * dequant[i]) where i = 4..7 mul v22.4h, v22.4h, v28.4h // x[i]=(scale[i] * dequant[i]) where i = 8..11 mul v23.4h, v23.4h, v29.4h // x[i]=(scale[i] * dequant[i]) where i = 12..14 smull v0.4s, v16.4h, v20.4h // q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 smull v2.4s, v17.4h, v21.4h // q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 smull v4.4s, v18.4h, v22.4h // q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 smull v6.4s, v19.4h, v23.4h // q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 sshl v0.4s, v0.4s, v30.4s // q0 = q[i] = (p[i] << (qp/6)) where i = 0..3 sshl v2.4s, v2.4s, v30.4s // q1 = q[i] = (p[i] << (qp/6)) where i = 4..7 sshl v4.4s, v4.4s, v30.4s // q2 = q[i] = (p[i] << (qp/6)) where i = 8..11 sshl v6.4s, v6.4s, v30.4s // q3 = q[i] = (p[i] << (qp/6)) where i = 12..15 sqrshrn v0.4h, v0.4s, #0x4 // d0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 sqrshrn v1.4h, v2.4s, #0x4 // d1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 sqrshrn v2.4h, v4.4s, #0x4 // d2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 sqrshrn v3.4h, v6.4s, #0x4 // d3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 ld1 {v0.h}[0], [x10] // loads signed halfword pi2_dc_src[0] //========= PROCESS IDCT FROM HERE ======= //Steps for Stage 1: //------------------ sshr v8.4h, v1.4h, #1 // d1>>1 sshr v9.4h, v3.4h, #1 // d3>>1 add v4.4h, v0.4h, v2.4h // x0 = d0 + d2// sub v5.4h, v0.4h, v2.4h // x1 = d0 - d2// sub v6.4h, v8.4h, v3.4h // x2 = (d1 >> 1) - d3// add v7.4h, v1.4h, v9.4h // x3 = d1 + (d3 >> 1)// add v10.4h, v4.4h , v7.4h // x0+x3 add v11.4h, v5.4h , v6.4h // x1+x2 sub v12.4h, v5.4h , v6.4h // x1-x2 sub v13.4h, v4.4h , v7.4h ld1 {v26.8b}, [x1], x3 // i row load pu1_pred buffer ld1 {v27.8b}, [x1], x3 // ii row load pu1_pred buffer ld1 {v28.8b}, [x1], x3 // iii row load pu1_pred buf ld1 {v29.8b}, [x1], x3 // iv row load pu1_pred buffer //Steps for Stage 2: //transopose trn1 v4.4h, v10.4h, v11.4h trn2 v5.4h, v10.4h, v11.4h trn1 v6.4h, v12.4h, v13.4h trn2 v7.4h, v12.4h, v13.4h trn1 v10.2s, v4.2s, v6.2s // 0 trn1 v11.2s, v5.2s, v7.2s // 8 trn2 v12.2s, v4.2s, v6.2s // 4 trn2 v13.2s, v5.2s, v7.2s //end transpose sshr v18.4h, v11.4h, #1 // q0>>1 sshr v19.4h, v13.4h, #1 // q1>>1 add v14.4h, v10.4h, v12.4h // x0 = q0 + q2// sub v15.4h, v10.4h, v12.4h // x1 = q0 - q2// sub v16.4h, v18.4h, v13.4h // x2 = (q1 >> 1) - q3// add v17.4h, v11.4h, v19.4h // x3 = q1+ (q3 >> 3)// //Backup the output addr mov x0, x2 //load outpt buufer for interleaving ld1 {v10.8b}, [x2], x4 ld1 {v11.8b}, [x2], x4 ld1 {v12.8b}, [x2], x4 ld1 {v13.8b}, [x2] add v20.4h, v14.4h, v17.4h // x0 + x3 add v21.4h, v15.4h, v16.4h // x1 + x2 sub v22.4h, v15.4h, v16.4h // x1 - x2 sub v23.4h, v14.4h, v17.4h // x0 - x3 srshr v20.4h, v20.4h, #6 srshr v21.4h, v21.4h, #6 srshr v22.4h, v22.4h, #6 srshr v23.4h, v23.4h, #6 //nop v30.8b //dummy for deinterleaving movi v31.4h, #0x00ff //mask for interleaving [copy lower 8 bits] //Extract u/v plane from interleaved data uzp1 v26.8b, v26.8b, v30.8b uzp1 v27.8b, v27.8b, v30.8b uzp1 v28.8b, v28.8b, v30.8b uzp1 v29.8b, v29.8b, v30.8b uaddw v20.8h, v20.8h, v26.8b uaddw v21.8h, v21.8h, v27.8b uaddw v22.8h, v22.8h, v28.8b uaddw v23.8h, v23.8h, v29.8b sqxtun v0.8b, v20.8h sqxtun v1.8b, v21.8h sqxtun v2.8b, v22.8h sqxtun v3.8b, v23.8h //long the output so that we have 0 at msb and value at lsb uxtl v6.8h, v0.8b uxtl v7.8h, v1.8b uxtl v8.8h, v2.8b uxtl v9.8h, v3.8b //select lsbs from proceesd data and msbs from pu1_out loaded data bit v10.8b, v6.8b, v31.8b bit v11.8b, v7.8b, v31.8b bit v12.8b, v8.8b, v31.8b bit v13.8b, v9.8b, v31.8b //store the interleaved result st1 {v10.8b}, [x0], x4 st1 {v11.8b}, [x0], x4 st1 {v12.8b}, [x0], x4 st1 {v13.8b}, [x0] pop_v_regs ret ///* // ******************************************************************************* // * // * //brief // * This function performs inverse quant and Inverse transform type Ci4 for 8*8 block // * // * //par Description: // * Performs inverse transform Ci8 and adds the residue to get the // * reconstructed block // * // * //param[in] pi2_src // * Input 4x4 coefficients // * // * //param[in] pu1_pred // * Prediction 4x4 block // * // * //param[out] pu1_out // * Output 4x4 block // * // * //param[in] u4_qp_div_6 // * QP // * // * //param[in] pu2_weigh_mat // * Pointer to weight matrix // * // * //param[in] pred_strd, // * Prediction stride // * // * //param[in] out_strd // * Output Stride // * // *//param[in] pi2_tmp // * temporary buffer of size 1*64 // * // * //param[in] pu2_iscal_mat // * Pointer to the inverse quantization matrix // * // * //returns Void // * // * //remarks // * None // * // ******************************************************************************* // */ //void ih264_iquant_itrans_recon_8x8(WORD16 *pi2_src, // UWORD8 *pu1_pred, // UWORD8 *pu1_out, // WORD32 pred_strd, // WORD32 out_strd, // const UWORD16 *pu2_iscal_mat, // const UWORD16 *pu2_weigh_mat, // UWORD32 u4_qp_div_6, // WORD32 *pi4_tmp, // WORD32 iq_start_idx // WORD16 *pi2_dc_ld_addr) //**************Variables Vs Registers***************************************** //x0 => *pi2_src //x1 => *pu1_pred //x2 => *pu1_out //w3 => pred_strd //w4 => out_strd //x5 => *pu2_iscal_mat //x6 => *pu2_weigh_mat //w7 => u4_qp_div_6 //NOT USED => pi4_tmp //NOT USED => iq_start_idx //NOT USED => pi2_dc_ld_addr .global ih264_iquant_itrans_recon_8x8_av8 ih264_iquant_itrans_recon_8x8_av8: push_v_regs sxtw x3, w3 sxtw x4, w4 ld1 {v8.8h -v11.8h}, [x5], #64 ld1 {v12.8h-v15.8h}, [x5] ld1 {v16.8h -v19.8h}, [x6], #64 ld1 {v20.8h -v23.8h}, [x6] mov x8, #16 ld1 {v0.8h}, [x0], x8 ld1 {v1.8h}, [x0], x8 ld1 {v2.8h}, [x0], x8 ld1 {v3.8h}, [x0], x8 ld1 {v4.8h}, [x0], x8 ld1 {v5.8h}, [x0], x8 ld1 {v6.8h}, [x0], x8 ld1 {v7.8h}, [x0] mul v8.8h, v8.8h, v16.8h mul v9.8h, v9.8h, v17.8h mul v10.8h, v10.8h, v18.8h mul v11.8h, v11.8h, v19.8h mul v12.8h, v12.8h, v20.8h mul v13.8h, v13.8h, v21.8h mul v14.8h, v14.8h, v22.8h mul v15.8h, v15.8h, v23.8h smull v16.4s, v0.4h, v8.4h smull2 v17.4s, v0.8h, v8.8h smull v18.4s, v1.4h, v9.4h smull2 v19.4s, v1.8h, v9.8h smull v20.4s, v2.4h, v10.4h smull2 v21.4s, v2.8h, v10.8h smull v22.4s, v3.4h, v11.4h smull2 v23.4s, v3.8h, v11.8h smull v24.4s, v4.4h, v12.4h smull2 v25.4s, v4.8h, v12.8h smull v26.4s, v5.4h, v13.4h smull2 v27.4s, v5.8h, v13.8h smull v28.4s, v6.4h, v14.4h smull2 v29.4s, v6.8h, v14.8h smull v30.4s, v7.4h, v15.4h smull2 v31.4s, v7.8h, v15.8h dup v0.4s, w7 sshl v16.4s, v16.4s, v0.4s sshl v17.4s, v17.4s, v0.4s sshl v18.4s, v18.4s, v0.4s sshl v19.4s, v19.4s, v0.4s sshl v20.4s, v20.4s, v0.4s sshl v21.4s, v21.4s, v0.4s sshl v22.4s, v22.4s, v0.4s sshl v23.4s, v23.4s, v0.4s sshl v24.4s, v24.4s, v0.4s sshl v25.4s, v25.4s, v0.4s sshl v26.4s, v26.4s, v0.4s sshl v27.4s, v27.4s, v0.4s sshl v28.4s, v28.4s, v0.4s sshl v29.4s, v29.4s, v0.4s sshl v30.4s, v30.4s, v0.4s sshl v31.4s, v31.4s, v0.4s sqrshrn v0.4h, v16.4s, #6 sqrshrn2 v0.8h, v17.4s, #6 sqrshrn v1.4h, v18.4s, #6 sqrshrn2 v1.8h, v19.4s, #6 sqrshrn v2.4h, v20.4s, #6 sqrshrn2 v2.8h, v21.4s, #6 sqrshrn v3.4h, v22.4s, #6 sqrshrn2 v3.8h, v23.4s, #6 sqrshrn v4.4h, v24.4s, #6 sqrshrn2 v4.8h, v25.4s, #6 sqrshrn v5.4h, v26.4s, #6 sqrshrn2 v5.8h, v27.4s, #6 sqrshrn v6.4h, v28.4s, #6 sqrshrn2 v6.8h, v29.4s, #6 sqrshrn v7.4h, v30.4s, #6 sqrshrn2 v7.8h, v31.4s, #6 //loop counter mov x8, #2 //1x8 transofORM trans_1x8_1d: //transpose 8x8 trn1 v8.8h, v0.8h, v1.8h trn2 v9.8h, v0.8h, v1.8h trn1 v10.8h, v2.8h, v3.8h trn2 v11.8h, v2.8h, v3.8h trn1 v12.8h, v4.8h, v5.8h trn2 v13.8h, v4.8h, v5.8h trn1 v14.8h, v6.8h, v7.8h trn2 v15.8h, v6.8h, v7.8h trn1 v0.4s, v8.4s, v10.4s trn2 v2.4s, v8.4s, v10.4s trn1 v1.4s, v9.4s, v11.4s trn2 v3.4s, v9.4s, v11.4s trn1 v4.4s, v12.4s, v14.4s trn2 v6.4s, v12.4s, v14.4s trn1 v5.4s, v13.4s, v15.4s trn2 v7.4s, v13.4s, v15.4s trn1 v8.2d, v0.2d, v4.2d //0 trn2 v12.2d, v0.2d, v4.2d //1 trn1 v9.2d, v1.2d, v5.2d //2 trn2 v13.2d, v1.2d, v5.2d //3 trn1 v10.2d, v2.2d, v6.2d //4 trn2 v14.2d, v2.2d, v6.2d //5 trn1 v11.2d, v3.2d, v7.2d //6 trn2 v15.2d, v3.2d, v7.2d //7 // 1 3 5 6 7 sshr v16.8h, v9.8h, #1 //(pi2_tmp_ptr[1] >> 1) sshr v17.8h, v10.8h, #1 //(pi2_tmp_ptr[2] >> 1) sshr v18.8h, v11.8h, #1 //(pi2_tmp_ptr[3] >> 1) sshr v19.8h, v13.8h, #1 //(pi2_tmp_ptr[5] >> 1) sshr v20.8h, v14.8h, #1 //(pi2_tmp_ptr[6] >> 1) sshr v21.8h, v15.8h, #1 //(pi2_tmp_ptr[7] >> 1) add v0.8h, v8.8h, v12.8h // i_y0 = (pi2_tmp_ptr[0] + pi2_tmp_ptr[4] ); sub v2.8h, v8.8h, v12.8h // i_y2 = (pi2_tmp_ptr[0] - pi2_tmp_ptr[4] ); sub v4.8h, v17.8h, v14.8h //i_y4 = ((pi2_tmp_ptr[2] >> 1) - pi2_tmp_ptr[6] ); add v6.8h, v10.8h, v20.8h //i_y6 = (pi2_tmp_ptr[2] + (pi2_tmp_ptr[6] >> 1)); //-w3 + w5 ssubl v22.4s, v13.4h, v11.4h ssubl2 v23.4s, v13.8h, v11.8h //w3 + w5 saddl v24.4s, v13.4h, v11.4h saddl2 v25.4s, v13.8h, v11.8h //-w1 + w7 ssubl v26.4s, v15.4h, v9.4h ssubl2 v27.4s, v15.8h, v9.8h //w1 + w7 saddl v28.4s, v15.4h, v9.4h saddl2 v29.4s, v15.8h, v9.8h //-w3 + w5 - w7 ssubw v22.4s, v22.4s, v15.4h ssubw2 v23.4s, v23.4s, v15.8h //w3 + w5 + w1 saddw v24.4s, v24.4s, v9.4h saddw2 v25.4s, v25.4s, v9.8h //-w1 + w7 + w5 saddw v26.4s, v26.4s, v13.4h saddw2 v27.4s, v27.4s, v13.8h //w1 + w7 - w3 ssubw v28.4s, v28.4s, v11.4h ssubw2 v29.4s, v29.4s, v11.8h //-w3 + w5 - w7 - (w7 >> 1) ssubw v22.4s, v22.4s, v21.4h ssubw2 v23.4s, v23.4s, v21.8h //w3 + w5 + w1 + (w1 >> 1) saddw v24.4s, v24.4s, v16.4h saddw2 v25.4s, v25.4s, v16.8h //-w1 + w7 + w5 + (w5 >> 1) saddw v26.4s, v26.4s, v19.4h saddw2 v27.4s, v27.4s, v19.8h //w1 + w7 - w3 - (w3 >> 1) ssubw v28.4s, v28.4s, v18.4h ssubw2 v29.4s, v29.4s, v18.8h xtn v1.4h, v22.4s xtn2 v1.8h, v23.4s xtn v3.4h, v28.4s xtn2 v3.8h, v29.4s xtn v5.4h, v26.4s xtn2 v5.8h, v27.4s xtn v7.4h, v24.4s xtn2 v7.8h, v25.4s sshr v16.8h, v1.8h, #2 //(y1 >> 2) sshr v17.8h, v3.8h, #2 //(y3 >> 2) sshr v18.8h, v5.8h, #2 //(y5 >> 2) sshr v19.8h, v7.8h, #2 //(y7 >> 2) add v8.8h, v0.8h, v6.8h add v9.8h, v1.8h, v19.8h add v10.8h, v2.8h, v4.8h add v11.8h, v3.8h, v18.8h sub v12.8h, v2.8h, v4.8h sub v13.8h, v17.8h, v5.8h sub v14.8h, v0.8h, v6.8h sub v15.8h, v7.8h, v16.8h add v0.8h, v8.8h, v15.8h add v1.8h, v10.8h, v13.8h add v2.8h, v12.8h, v11.8h add v3.8h, v14.8h, v9.8h sub v4.8h, v14.8h, v9.8h sub v5.8h, v12.8h, v11.8h sub v6.8h, v10.8h, v13.8h sub v7.8h, v8.8h, v15.8h subs x8, x8, #1 bne trans_1x8_1d ld1 {v22.8b}, [x1], x3 ld1 {v23.8b}, [x1], x3 ld1 {v24.8b}, [x1], x3 ld1 {v25.8b}, [x1], x3 ld1 {v26.8b}, [x1], x3 ld1 {v27.8b}, [x1], x3 ld1 {v28.8b}, [x1], x3 ld1 {v29.8b}, [x1] srshr v0.8h, v0.8h, #6 srshr v1.8h, v1.8h, #6 srshr v2.8h, v2.8h, #6 srshr v3.8h, v3.8h, #6 srshr v4.8h, v4.8h, #6 srshr v5.8h, v5.8h, #6 srshr v6.8h, v6.8h, #6 srshr v7.8h, v7.8h, #6 uaddw v0.8h, v0.8h, v22.8b uaddw v1.8h, v1.8h, v23.8b uaddw v2.8h, v2.8h, v24.8b uaddw v3.8h, v3.8h, v25.8b uaddw v4.8h, v4.8h, v26.8b uaddw v5.8h, v5.8h, v27.8b uaddw v6.8h, v6.8h, v28.8b uaddw v7.8h, v7.8h, v29.8b sqxtun v0.8b, v0.8h sqxtun v1.8b, v1.8h sqxtun v2.8b, v2.8h sqxtun v3.8b, v3.8h sqxtun v4.8b, v4.8h sqxtun v5.8b, v5.8h sqxtun v6.8b, v6.8h sqxtun v7.8b, v7.8h st1 {v0.8b}, [x2], x4 st1 {v1.8b}, [x2], x4 st1 {v2.8b}, [x2], x4 st1 {v3.8b}, [x2], x4 st1 {v4.8b}, [x2], x4 st1 {v5.8b}, [x2], x4 st1 {v6.8b}, [x2], x4 st1 {v7.8b}, [x2] pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_iquant_itrans_recon_dc_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** // ******************************************************************************* // * @file // * ih264_iquant_itrans_recon_dc_av8.s // * // * @brief // * Contains function definitions for single stage inverse transform // * // * @author // * Mohit // * // * @par List of Functions: // * - ih264_iquant_itrans_recon_4x4_dc_av8() // * - ih264_iquant_itrans_recon_8x8_dc_av8() // * - ih264_iquant_itrans_recon_chroma_4x4_dc_av8() // * // * @remarks // * None // * // ******************************************************************************* //*/ .include "ih264_neon_macros.s" ///** // ******************************************************************************* // * // * @brief // * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block // * for dc input pattern only, i.e. only the (0,0) element of the input 4x4 block is // * non-zero. For complete function, refer ih264_iquant_itrans_recon_a9.s // * // * @par Description: // * Performs inverse transform Ci4 and adds the residue to get the // * reconstructed block // * // * @param[in] pi2_src // * Input 4x4 coefficients // * // * @param[in] pu1_pred // * Prediction 4x4 block // * // * @param[out] pu1_out // * Output 4x4 block // * // * @param[in] u4_qp_div_6 // * QP // * // * @param[in] pu2_weigh_mat // * Pointer to weight matrix // * // * @param[in] pred_strd, // * Prediction stride // * // * @param[in] out_strd // * Output Stride // * // *@param[in] pi2_tmp // * temporary buffer of size 1*16 // * // * @param[in] pu2_iscal_mat // * Pointer to the inverse quantization matrix // * // * @returns Void // * // * @remarks // * None // * // ******************************************************************************* // */ //void ih264_iquant_itrans_recon_4x4_dc(WORD16 *pi2_src, // UWORD8 *pu1_pred, // UWORD8 *pu1_out, // WORD32 pred_strd, // WORD32 out_strd, // const UWORD16 *pu2_iscal_mat, // const UWORD16 *pu2_weigh_mat, // UWORD32 u4_qp_div_6, // WORD32 *pi4_tmp, // WORD32 iq_start_idx // WORD16 *pi2_dc_ld_addr) //**************Variables Vs Registers***************************************** //x0 => *pi2_src //x1 => *pu1_pred //x2 => *pu1_out //w3 => pred_strd //w4 => out_strd //x5 => *pu2_iscal_mat //x6 => *pu2_weigh_mat //w7 => u4_qp_div_6 // => pi4_tmp // => iq_start_idx // => pi2_dc_ld_addr .text .p2align 2 .global ih264_iquant_itrans_recon_4x4_dc_av8 ih264_iquant_itrans_recon_4x4_dc_av8: sxtw x3, w3 sxtw x4, w4 ldr w8, [sp, #8] //Loads iq_start_idx subs w8, w8, #1 // if x8 == 1 => intra case , so result of subtraction is zero and z flag is set ldr x10, [sp, #16] //Load alternate dc address push_v_regs dup v30.4s, w7 //Populate the u4_qp_div_6 in Q15 bne donot_use_pi2_dc_ld_addr_luma_dc ld1 {v0.h}[0], [x10] donot_use_pi2_dc_ld_addr_luma_dc: beq donot_use_pi2_src_luma_dc ld1 {v0.h}[0], [x5] ld1 {v1.h}[0], [x6] ld1 {v2.h}[0], [x0] mul v0.4h, v1.4h, v0.4h smull v0.4s, v0.4h, v2.4h sshl v0.4s, v0.4s, v30.4s sqrshrn v0.4h, v0.4s, #4 donot_use_pi2_src_luma_dc: dup v0.8h, v0.h[0] srshr v0.8h, v0.8h, #6 ld1 {v1.s}[0], [x1], x3 ld1 {v1.s}[1], [x1], x3 ld1 {v2.s}[0], [x1], x3 ld1 {v2.s}[1], [x1] uxtl v1.8h, v1.8b uxtl v2.8h, v2.8b add v1.8h, v0.8h, v1.8h add v2.8h, v0.8h, v2.8h sqxtun v1.8b, v1.8h sqxtun v2.8b, v2.8h st1 {v1.s}[0], [x2], x4 st1 {v1.s}[1], [x2], x4 st1 {v2.s}[0], [x2], x4 st1 {v2.s}[1], [x2] pop_v_regs ret // /* // ******************************************************************************** // * // * @brief This function reconstructs a 4x4 sub block from quantized resiude and // * prediction buffer if only dc value is present for residue // * // * @par Description: // * The quantized residue is first inverse quantized, // * This inverse quantized content is added to the prediction buffer to recon- // * struct the end output // * // * @param[in] pi2_src // * quantized dc coeffiient // * // * @param[in] pu1_pred // * prediction 4x4 block in interleaved format // * // * @param[in] pred_strd, // * Prediction buffer stride in interleaved format // * // * @param[in] out_strd // * recon buffer Stride // * // * @returns none // * // * @remarks none // * // ******************************************************************************* // */ // void ih264_iquant_itrans_recon_chroma_4x4_dc(WORD16 *pi2_src, // UWORD8 *pu1_pred, // UWORD8 *pu1_out, // WORD32 pred_strd, // WORD32 out_strd, // const UWORD16 *pu2_iscal_mat, // const UWORD16 *pu2_weigh_mat, // UWORD32 u4_qp_div_6, // WORD16 *pi2_tmp, // WORD16 *pi2_dc_src) // Register Usage // x0 : pi2_src // x1 : pu1_pred // x2 : pu1_out // w3 : pred_strd // w4 : out_strd // x5 : pu2_iscal_mat // x6 : pu2_weigh_mat // w7 : u4_qp_div_6 // : pi2_tmp // : pi2_dc_src // Neon registers d0-d7, d16-d30 are used // No need for pushing arm and neon registers .global ih264_iquant_itrans_recon_chroma_4x4_dc_av8 ih264_iquant_itrans_recon_chroma_4x4_dc_av8: sxtw x3, w3 sxtw x4, w4 ldr x0, [sp, #8] push_v_regs ld1 {v0.h}[0], [x0] dup v0.8h, v0.h[0] srshr v0.8h, v0.8h, #6 //backup pu1_out mov x0, x2 //nop v3.16b //dummy for deinterleaving movi v31.8h, #0x00ff //mask for interleaving [copy lower 8 bits] ld1 {v1.d}[0], [x1], x3 ld1 {v1.d}[1], [x1], x3 ld1 {v2.d}[0], [x1], x3 ld1 {v2.d}[1], [x1], x3 ld1 {v11.d}[0], [x2], x4 //load pu1_out for interleaving ld1 {v11.d}[1], [x2], x4 ld1 {v12.d}[0], [x2], x4 ld1 {v12.d}[1], [x2] uzp1 v1.16b, v1.16b, v3.16b uzp1 v2.16b, v2.16b, v3.16b uaddw v1.8h, v0.8h, v1.8b uaddw v2.8h, v0.8h, v2.8b sqxtun v1.8b, v1.8h sqxtun v2.8b, v2.8h uxtl v1.8h, v1.8b uxtl v2.8h, v2.8b bit v11.16b, v1.16b, v31.16b bit v12.16b, v2.16b, v31.16b st1 {v11.d}[0], [x0], x4 st1 {v11.d}[1], [x0], x4 st1 {v12.d}[0], [x0], x4 st1 {v12.d}[1], [x0] pop_v_regs ret ///* // ******************************************************************************* // * // * //brief // * This function performs inverse quant and Inverse transform type Ci4 for 8*8 block // * [Only for Dc coeff] // * //par Description: // * Performs inverse transform Ci8 and adds the residue to get the // * reconstructed block // * // * //param[in] pi2_src // * Input 4x4 coefficients // * // * //param[in] pu1_pred // * Prediction 4x4 block // * // * //param[out] pu1_out // * Output 4x4 block // * // * //param[in] u4_qp_div_6 // * QP // * // * //param[in] pu2_weigh_mat // * Pointer to weight matrix // * // * //param[in] pred_strd, // * Prediction stride // * // * //param[in] out_strd // * Output Stride // * // *//param[in] pi2_tmp // * temporary buffer of size 1*64 // * // * //param[in] pu2_iscal_mat // * Pointer to the inverse quantization matrix // * // * //returns Void // * // * //remarks // * None // * // ******************************************************************************* // */ //void ih264_iquant_itrans_recon_dc_8x8(WORD16 *pi2_src, // UWORD8 *pu1_pred, // UWORD8 *pu1_out, // WORD32 pred_strd, // WORD32 out_strd, // const UWORD16 *pu2_iscal_mat, // const UWORD16 *pu2_weigh_mat, // UWORD32 u4_qp_div_6, // WORD32 *pi4_tmp, // WORD32 iq_start_idx // WORD16 *pi2_dc_ld_addr) //**************Variables Vs Registers***************************************** //x0 => *pi2_src //x1 => *pu1_pred //x2 => *pu1_out //w3 => pred_strd //w4 => out_strd //x5 => *pu2_iscal_mat //x6 => *pu2_weigh_mat //w7 => u4_qp_div_6 //NOT USED => pi4_tmp //NOT USED => iq_start_idx //NOT USED => pi2_dc_ld_addr .global ih264_iquant_itrans_recon_8x8_dc_av8 ih264_iquant_itrans_recon_8x8_dc_av8: push_v_regs sxtw x3, w3 sxtw x4, w4 ld1 {v1.h}[0], [x5] ld1 {v2.h}[0], [x6] ld1 {v0.h}[0], [x0] dup v3.4s, w7 mul v1.8h, v1.8h, v2.8h smull v0.4s, v0.4h, v1.4h sshl v0.4s, v0.4s, v3.4s sqrshrn v0.4h, v0.4s, #6 srshr v0.8h, v0.8h, #6 dup v0.8h, v0.h[0] ld1 {v22.8b}, [x1], x3 ld1 {v23.8b}, [x1], x3 ld1 {v24.8b}, [x1], x3 ld1 {v25.8b}, [x1], x3 ld1 {v26.8b}, [x1], x3 ld1 {v27.8b}, [x1], x3 ld1 {v28.8b}, [x1], x3 ld1 {v29.8b}, [x1] uaddw v1.8h, v0.8h, v22.8b uaddw v2.8h, v0.8h, v23.8b uaddw v3.8h, v0.8h, v24.8b uaddw v8.8h, v0.8h, v25.8b uaddw v9.8h, v0.8h, v26.8b uaddw v10.8h, v0.8h, v27.8b uaddw v11.8h, v0.8h, v28.8b uaddw v12.8h, v0.8h, v29.8b sqxtun v1.8b, v1.8h sqxtun v2.8b, v2.8h sqxtun v3.8b, v3.8h sqxtun v8.8b, v8.8h sqxtun v9.8b, v9.8h sqxtun v10.8b, v10.8h sqxtun v11.8b, v11.8h sqxtun v12.8b, v12.8h st1 {v1.8b}, [x2], x4 st1 {v2.8b}, [x2], x4 st1 {v3.8b}, [x2], x4 st1 {v8.8b}, [x2], x4 st1 {v9.8b}, [x2], x4 st1 {v10.8b}, [x2], x4 st1 {v11.8b}, [x2], x4 st1 {v12.8b}, [x2] pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_mem_fns_neon_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** // ******************************************************************************* // * @file // * ih264_mem_fns_neon.s // * // * @brief // * Contains function definitions for memory manipulation // * // * @author // * Naveen SR // * // * @par List of Functions: // * - ih264_memcpy_av8() // * - ih264_memcpy_mul_8_av8() // * - ih264_memset_mul_8_av8() // * - ih264_memset_16bit_mul_8_av8() // * - ih264_memset_16bit_av8() // * // * @remarks // * None // * // ******************************************************************************* //*/ .text .p2align 2 .include "ih264_neon_macros.s" ///** //******************************************************************************* //* //* @brief //* memcpy of a 1d array //* //* @par Description: //* Does memcpy of 8bit data from source to destination for 8,16 or 32 number of bytes //* //* @param[in] pu1_dst //* UWORD8 pointer to the destination //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[in] num_bytes //* number of bytes to copy //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //void ih264_memcpy_mul_8(UWORD8 *pu1_dst, // UWORD8 *pu1_src, // UWORD32 num_bytes) //**************Variables Vs Registers************************* // x0 => *pu1_dst // x1 => *pu1_src // w2 => num_bytes .global ih264_memcpy_mul_8_av8 ih264_memcpy_mul_8_av8: loop_neon_memcpy_mul_8: // Memcpy 8 bytes ld1 {v0.8b}, [x1], #8 st1 {v0.8b}, [x0], #8 subs w2, w2, #8 bne loop_neon_memcpy_mul_8 ret //******************************************************************************* //*/ //void ih264_memcpy(UWORD8 *pu1_dst, // UWORD8 *pu1_src, // UWORD32 num_bytes) //**************Variables Vs Registers************************* // x0 => *pu1_dst // x1 => *pu1_src // w2 => num_bytes .global ih264_memcpy_av8 ih264_memcpy_av8: subs w2, w2, #8 blt arm_memcpy loop_neon_memcpy: // Memcpy 8 bytes ld1 {v0.8b}, [x1], #8 st1 {v0.8b}, [x0], #8 subs w2, w2, #8 bge loop_neon_memcpy cmn w2, #8 beq end_func1 arm_memcpy: add w2, w2, #8 loop_arm_memcpy: ldrb w3, [x1], #1 strb w3, [x0], #1 subs w2, w2, #1 bne loop_arm_memcpy ret end_func1: ret //void ih264_memset_mul_8(UWORD8 *pu1_dst, // UWORD8 value, // UWORD32 num_bytes) //**************Variables Vs Registers************************* // x0 => *pu1_dst // x1 => value // x2 => num_bytes .global ih264_memset_mul_8_av8 ih264_memset_mul_8_av8: // Assumptions: numbytes is either 8, 16 or 32 dup v0.8b, w1 loop_memset_mul_8: // Memset 8 bytes st1 {v0.8b}, [x0], #8 subs w2, w2, #8 bne loop_memset_mul_8 ret //void ih264_memset(UWORD8 *pu1_dst, // UWORD8 value, // UWORD32 num_bytes) //**************Variables Vs Registers************************* // x0 => *pu1_dst // w1 => value // w2 => num_bytes .global ih264_memset_av8 ih264_memset_av8: subs w2, w2, #8 blt arm_memset dup v0.8b, w1 loop_neon_memset: // Memcpy 8 bytes st1 {v0.8b}, [x0], #8 subs w2, w2, #8 bge loop_neon_memset cmn w2, #8 beq end_func2 arm_memset: add w2, w2, #8 loop_arm_memset: strb w1, [x0], #1 subs w2, w2, #1 bne loop_arm_memset ret end_func2: ret //void ih264_memset_16bit_mul_8(UWORD16 *pu2_dst, // UWORD16 value, // UWORD32 num_words) //**************Variables Vs Registers************************* // x0 => *pu2_dst // w1 => value // w2 => num_words .global ih264_memset_16bit_mul_8_av8 ih264_memset_16bit_mul_8_av8: // Assumptions: num_words is either 8, 16 or 32 // Memset 8 words dup v0.4h, w1 loop_memset_16bit_mul_8: st1 {v0.4h}, [x0], #8 st1 {v0.4h}, [x0], #8 subs w2, w2, #8 bne loop_memset_16bit_mul_8 ret //void ih264_memset_16bit(UWORD16 *pu2_dst, // UWORD16 value, // UWORD32 num_words) //**************Variables Vs Registers************************* // x0 => *pu2_dst // w1 => value // w2 => num_words .global ih264_memset_16bit_av8 ih264_memset_16bit_av8: subs w2, w2, #8 blt arm_memset_16bit dup v0.4h, w1 loop_neon_memset_16bit: // Memset 8 words st1 {v0.4h}, [x0], #8 st1 {v0.4h}, [x0], #8 subs w2, w2, #8 bge loop_neon_memset_16bit cmn w2, #8 beq end_func3 arm_memset_16bit: add w2, w2, #8 loop_arm_memset_16bit: strh w1, [x0], #2 subs w2, w2, #1 bne loop_arm_memset_16bit ret end_func3: ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_neon_macros.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ //******************************************************************************* .macro push_v_regs stp d8, d9, [sp, #-16]! stp d10, d11, [sp, #-16]! stp d12, d13, [sp, #-16]! stp d14, d15, [sp, #-16]! .endm .macro pop_v_regs ldp d14, d15, [sp], #16 ldp d12, d13, [sp], #16 ldp d10, d11, [sp], #16 ldp d8, d9, [sp], #16 .endm .macro swp reg1, reg2 eor \reg1, \reg1, \reg2 eor \reg2, \reg1, \reg2 eor \reg1, \reg1, \reg2 .endm ================================================ FILE: dependencies/ih264d/common/armv8/ih264_padding_neon_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** // ******************************************************************************* // * @file // * ih264_padding_neon.s // * // * @brief // * Contains function definitions padding // * // * @author // * Ittiam // * // * @par List of Functions: // * - ih264_pad_top_av8() // * - ih264_pad_left_luma_av8() // * - ih264_pad_left_chroma_av8() // * - ih264_pad_right_luma_av8() // * - ih264_pad_right_chroma_av8() // * // * @remarks // * None // * // ******************************************************************************* //*/ .text .p2align 2 .include "ih264_neon_macros.s" ///** //******************************************************************************* //* //* @brief pad at the top of a 2d array //* //* @par Description: //* The top row of a 2d array is replicated for pad_size times at the top //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[in] src_strd //* integer source stride //* //* @param[in] wd //* integer width of the array //* //* @param[in] pad_size //* integer -padding size of the array //* //* @returns none //* //* @remarks none //* //******************************************************************************* //*/ //void ih264_pad_top(UWORD8 *pu1_src, // WORD32 src_strd, // WORD32 wd, // WORD32 pad_size) //**************Variables Vs Registers************************* // x0 => *pu1_src // w1 => src_strd // w2 => wd // w3 => pad_size .global ih264_pad_top_av8 ih264_pad_top_av8: // STMFD sp!, {x4-x11,x14} //stack stores the values of the arguments push_v_regs sxtw x1, w1 stp x19, x20, [sp, #-16]! sub x5, x0, x1 neg x6, x1 loop_neon_memcpy_mul_16: // Load 16 bytes ld1 {v0.8b, v1.8b}, [x0], #16 mov x4, x5 mov w7, w3 add x5, x5, #16 loop_neon_pad_top: st1 {v0.8b, v1.8b}, [x4], x6 subs w7, w7, #1 bne loop_neon_pad_top subs w2, w2, #16 bne loop_neon_memcpy_mul_16 // LDMFD sp!,{x4-x11,pc} //Reload the registers from SP ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //* @brief //* Padding (luma block) at the left of a 2d array //* //* @par Description: //* The left column of a 2d array is replicated for pad_size times at the left //* //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[in] src_strd //* integer source stride //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @param[in] pad_size //* integer -padding size of the array //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //#if PAD_LEFT_LUMA == C //void ih264_pad_left_luma(UWORD8 *pu1_src, // WORD32 src_strd, // WORD32 ht, // WORD32 pad_size) //**************Variables Vs Registers************************* // x0 => *pu1_src // w1 => src_strd // w2 => ht // w3 => pad_size .global ih264_pad_left_luma_av8 ih264_pad_left_luma_av8: // STMFD sp!, {x4-x11,x14} //stack stores the values of the arguments push_v_regs sxtw x1, w1 sxtw x3, w3 stp x19, x20, [sp, #-16]! sub x4, x0, x3 sub x6, x1, #16 subs x5, x3, #16 bne loop_32 loop_16: // /*hard coded for width=16 ,height =8,16*/ ldrb w8, [x0] add x0, x0, x1 ldrb w9, [x0] add x0, x0, x1 dup v0.16b, w8 ldrb w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], x1 // 16 bytes store dup v2.16b, w9 st1 {v2.16b}, [x4], x1 // 16 bytes store ldrb w11, [x0] add x0, x0, x1 dup v4.16b, w10 dup v6.16b, w11 st1 {v4.16b}, [x4], x1 // 16 bytes store ldrb w8, [x0] add x0, x0, x1 st1 {v6.16b}, [x4], x1 // 16 bytes store ldrb w9, [x0] add x0, x0, x1 dup v0.16b, w8 ldrb w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], x1 // 16 bytes store dup v2.16b, w9 ldrb w11, [x0] add x0, x0, x1 st1 {v2.16b}, [x4], x1 // 16 bytes store dup v4.16b, w10 dup v6.16b, w11 subs w2, w2, #8 st1 {v4.16b}, [x4], x1 // 16 bytes store st1 {v6.16b}, [x4], x1 // 16 bytes store bne loop_16 b end_func loop_32: // /*hard coded for width=32 ,height =8,16*/ ldrb w8, [x0] add x0, x0, x1 ldrb w9, [x0] add x0, x0, x1 dup v0.16b, w8 ldrb w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], #16 // 16 bytes store dup v2.16b, w9 st1 {v0.16b}, [x4], x6 st1 {v2.16b}, [x4], #16 // 16 bytes store dup v4.16b, w10 st1 {v2.16b}, [x4], x6 // 16 bytes store ldrb w11, [x0] add x0, x0, x1 st1 {v4.16b}, [x4], #16 // 16 bytes store dup v6.16b, w11 st1 {v4.16b}, [x4], x6 // 16 bytes store ldrb w8, [x0] add x0, x0, x1 st1 {v6.16b}, [x4], #16 // 16 bytes store dup v0.16b, w8 ldrb w9, [x0] add x0, x0, x1 st1 {v6.16b}, [x4], x6 // 16 bytes store ldrb w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], #16 // 16 bytes store dup v2.16b, w9 st1 {v0.16b}, [x4], x6 // 16 bytes store ldrb w11, [x0] add x0, x0, x1 st1 {v2.16b}, [x4], #16 // 16 bytes store dup v4.16b, w10 st1 {v2.16b}, [x4], x6 // 16 bytes store st1 {v4.16b}, [x4], #16 // 16 bytes store dup v6.16b, w11 st1 {v4.16b}, [x4], x6 // 16 bytes store subs w2, w2, #8 st1 {v6.16b}, [x4], #16 // 16 bytes store st1 {v6.16b}, [x4], x6 // 16 bytes store bne loop_32 end_func: // LDMFD sp!,{x4-x11,pc} //Reload the registers from SP ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //* @brief //* Padding (chroma block) at the left of a 2d array //* //* @par Description: //* The left column of a 2d array is replicated for pad_size times at the left //* //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[in] src_strd //* integer source stride //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array (each colour component) //* //* @param[in] pad_size //* integer -padding size of the array //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //#if PAD_LEFT_CHROMA == C //void ih264_pad_left_chroma(UWORD8 *pu1_src, // WORD32 src_strd, // WORD32 ht, // WORD32 pad_size) //{ // x0 => *pu1_src // w1 => src_strd // w2 => ht // w3 => pad_size .global ih264_pad_left_chroma_av8 ih264_pad_left_chroma_av8: // STMFD sp!, {x4-x11, x14} //stack stores the values of the arguments push_v_regs sxtw x1, w1 sxtw x3, w3 stp x19, x20, [sp, #-16]! sub x4, x0, x3 sub x6, x1, #16 loop_32_l_c: // /*hard coded for width=32 ,height =4,8,12*/ ldrh w8, [x0] add x0, x0, x1 ldrh w9, [x0] add x0, x0, x1 dup v0.8h, w8 ldrh w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], #16 // 16 bytes store dup v2.8h, w9 st1 {v0.16b}, [x4], x6 // 16 bytes store ldrh w11, [x0] add x0, x0, x1 st1 {v2.16b}, [x4], #16 // 16 bytes store dup v4.8h, w10 st1 {v2.16b}, [x4], x6 // 16 bytes store dup v6.8h, w11 st1 {v4.16b}, [x4], #16 // 16 bytes store st1 {v4.16b}, [x4], x6 // 16 bytes store subs w2, w2, #4 st1 {v6.16b}, [x4], #16 // 16 bytes store st1 {v6.16b}, [x4], x6 // 16 bytes store beq end_func_l_c ///* Branching when ht=4*/ ldrh w8, [x0] add x0, x0, x1 ldrh w9, [x0] add x0, x0, x1 dup v0.8h, w8 ldrh w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], #16 // 16 bytes store dup v2.8h, w9 st1 {v0.16b}, [x4], x6 ldrh w11, [x0] add x0, x0, x1 st1 {v2.16b}, [x4], #16 // 16 bytes store dup v4.8h, w10 st1 {v2.16b}, [x4], x6 // 16 bytes store dup v6.8h, w11 st1 {v4.16b}, [x4], #16 // 16 bytes store st1 {v4.16b}, [x4], x6 // 16 bytes store subs w2, w2, #4 st1 {v6.16b}, [x4], #16 // 16 bytes store st1 {v6.16b}, [x4], x6 // 16 bytes store beq end_func_l_c ///* Branching when ht=8*/ bne loop_32_l_c ldrh w8, [x0] add x0, x0, x1 ldrh w9, [x0] add x0, x0, x1 dup v0.8h, w8 ldrh w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], #16 // 16 bytes store dup v2.8h, w9 st1 {v0.16b}, [x4], x6 ldrh w11, [x0] add x0, x0, x1 st1 {v2.16b}, [x4], #16 // 16 bytes store dup v4.8h, w10 st1 {v2.16b}, [x4], x6 // 16 bytes store dup v6.8h, w11 st1 {v4.16b}, [x4], #16 // 16 bytes store st1 {v4.16b}, [x4], x6 // 16 bytes store st1 {v6.16b}, [x4], #16 // 16 bytes store st1 {v6.16b}, [x4], x6 // 16 bytes store end_func_l_c: // LDMFD sp!,{x4-x11,pc} //Reload the registers from SP ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //* @brief //* Padding (luma block) at the right of a 2d array //* //* @par Description: //* The right column of a 2d array is replicated for pad_size times at the right //* //* //* @param[in] pu1_src //* UWORD8 pointer to the source //* //* @param[in] src_strd //* integer source stride //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @param[in] pad_size //* integer -padding size of the array //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //#if PAD_RIGHT_LUMA == C //void ih264_pad_right_luma(UWORD8 *pu1_src, // WORD32 src_strd, // WORD32 ht, // WORD32 pad_size) //{ // WORD32 row; // // for(row = 0; row < ht; row++) // { // memset(pu1_src, *(pu1_src -1), pad_size); // // pu1_src += src_strd; // } //} // // x0 => *pu1_src // w1 => src_strd // w2 => ht // w3 => pad_size .global ih264_pad_right_luma_av8 ih264_pad_right_luma_av8: // STMFD sp!, {x4-x11, x14} //stack stores the values of the arguments push_v_regs sxtw x1, w1 sxtw x3, w3 stp x19, x20, [sp, #-16]! mov x4, x0 sub x6, x1, #16 sub x0, x0, #1 subs x5, x3, #16 bne loop_32 loop_16_r: // /*hard coded for width=16 ,height =8,16*/ ldrb w8, [x0] add x0, x0, x1 ldrb w9, [x0] add x0, x0, x1 dup v0.16b, w8 ldrb w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], x1 // 16 bytes store dup v2.16b, w9 st1 {v2.16b}, [x4], x1 // 16 bytes store ldrb w11, [x0] add x0, x0, x1 dup v4.16b, w10 dup v6.16b, w11 st1 {v4.16b}, [x4], x1 // 16 bytes store ldrb w8, [x0] add x0, x0, x1 st1 {v6.16b}, [x4], x1 // 16 bytes store ldrb w9, [x0] add x0, x0, x1 dup v0.16b, w8 ldrb w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], x1 // 16 bytes store dup v2.16b, w9 ldrb w11, [x0] add x0, x0, x1 st1 {v2.16b}, [x4], x1 // 16 bytes store dup v4.16b, w10 dup v6.16b, w11 subs w2, w2, #8 st1 {v4.16b}, [x4], x1 // 16 bytes store st1 {v6.16b}, [x4], x1 // 16 bytes store bne loop_16_r b end_func_r loop_32_r: // /*hard coded for width=32 ,height =8,16*/ ldrb w8, [x0] add x0, x0, x1 ldrb w9, [x0] add x0, x0, x1 dup v0.16b, w8 ldrb w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], #16 // 16 bytes store dup v2.16b, w9 st1 {v0.16b}, [x4], x6 st1 {v2.16b}, [x4], #16 // 16 bytes store dup v4.16b, w10 st1 {v2.16b}, [x4], x6 // 16 bytes store ldrb w11, [x0] add x0, x0, x1 st1 {v4.16b}, [x4], #16 // 16 bytes store dup v6.16b, w11 st1 {v4.16b}, [x4], x6 // 16 bytes store ldrb w8, [x0] add x0, x0, x1 st1 {v6.16b}, [x4], #16 // 16 bytes store ldrb w9, [x0] add x0, x0, x1 dup v0.16b, w8 st1 {v6.16b}, [x4], x6 // 16 bytes store ldrb w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], #16 // 16 bytes store dup v2.16b, w9 st1 {v0.16b}, [x4], x6 // 16 bytes store ldrb w11, [x0] add x0, x0, x1 st1 {v2.16b}, [x4], #16 // 16 bytes store dup v4.16b, w10 st1 {v2.16b}, [x4], x6 // 16 bytes store st1 {v4.16b}, [x4], #16 // 16 bytes store dup v6.16b, w11 st1 {v4.16b}, [x4], x6 // 16 bytes store subs w2, w2, #8 st1 {v6.16b}, [x4], #16 // 16 bytes store st1 {v6.16b}, [x4], x6 // 16 bytes store bne loop_32_r end_func_r: // LDMFD sp!,{x4-x11,pc} //Reload the registers from SP ldp x19, x20, [sp], #16 pop_v_regs ret ///** //******************************************************************************* //* //* @brief //;* Padding (chroma block) at the right of a 2d array //* //* @par Description: //* The right column of a 2d array is replicated for pad_size times at the right //* //* //* @param[in] pu1_src //;* UWORD8 pointer to the source //* //* @param[in] src_strd //* integer source stride //* //* @param[in] ht //;* integer height of the array //* //* @param[in] wd //* integer width of the array (each colour component) //* //* @param[in] pad_size //* integer -padding size of the array //* //* @param[in] ht //;* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* //* @remarks //* None //* //******************************************************************************* //*/ //#if PAD_RIGHT_CHROMA == C //void ih264_pad_right_chroma(UWORD8 *pu1_src, // WORD32 src_strd, // WORD32 ht, // WORD32 pad_size) // x0 => *pu1_src // w1 => src_strd // w2 => ht // w3 => pad_size .global ih264_pad_right_chroma_av8 ih264_pad_right_chroma_av8: // STMFD sp!, {x4-x11, x14} //stack stores the values of the arguments push_v_regs sxtw x1, w1 sxtw x3, w3 stp x19, x20, [sp, #-16]! mov x4, x0 sub x6, x1, #16 sub x0, x0, #2 loop_32_r_c: // /*hard coded for width=32 ,height =8,4*/ ldrh w8, [x0] add x0, x0, x1 ldrh w9, [x0] add x0, x0, x1 dup v0.8h, w8 ldrh w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], #16 // 16 bytes store dup v2.8h, w9 st1 {v0.16b}, [x4], x6 st1 {v2.16b}, [x4], #16 // 16 bytes store dup v4.8h, w10 st1 {v2.16b}, [x4], x6 // 16 bytes store subs w2, w2, #4 ldrh w11, [x0] add x0, x0, x1 st1 {v4.16b}, [x4], #16 // 16 bytes store dup v6.8h, w11 st1 {v4.16b}, [x4], x6 // 16 bytes store st1 {v6.16b}, [x4], #16 // 16 bytes store st1 {v6.16b}, [x4], x6 // 16 bytes store beq end_func_r_c ///* Branching when ht=4*/ ldrh w8, [x0] add x0, x0, x1 dup v0.8h, w8 ldrh w9, [x0] add x0, x0, x1 ldrh w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], #16 // 16 bytes store dup v2.8h, w9 st1 {v0.16b}, [x4], x6 // 16 bytes store ldrh w11, [x0] add x0, x0, x1 st1 {v2.16b}, [x4], #16 // 16 bytes store dup v4.8h, w10 st1 {v2.16b}, [x4], x6 // 16 bytes store st1 {v4.16b}, [x4], #16 // 16 bytes store dup v6.8h, w11 st1 {v4.16b}, [x4], x6 // 16 bytes store subs w2, w2, #4 st1 {v6.16b}, [x4], #16 // 16 bytes store st1 {v6.16b}, [x4], x6 // 16 bytes store beq end_func_r_c ///* Branching when ht=8*/ bne loop_32_r_c ldrh w8, [x0] add x0, x0, x1 dup v0.8h, w8 ldrh w9, [x0] add x0, x0, x1 ldrh w10, [x0] add x0, x0, x1 st1 {v0.16b}, [x4], #16 // 16 bytes store dup v2.8h, w9 st1 {v0.16b}, [x4], x6 // 16 bytes store ldrh w11, [x0] add x0, x0, x1 st1 {v2.16b}, [x4], #16 // 16 bytes store dup v4.8h, w10 st1 {v2.16b}, [x4], x6 // 16 bytes store st1 {v4.16b}, [x4], #16 // 16 bytes store dup v6.8h, w11 st1 {v4.16b}, [x4], x6 // 16 bytes store st1 {v6.16b}, [x4], #16 // 16 bytes store st1 {v6.16b}, [x4], x6 // 16 bytes store end_func_r_c: // LDMFD sp!,{x4-x11,pc} //Reload the registers from SP ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_platform_macros.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_platform_macros.h * * @brief * Platform specific Macro definitions used in the codec * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_PLATFORM_MACROS_H_ #define _IH264_PLATFORM_MACROS_H_ #include #ifndef ARMV8 static __inline WORD32 CLIP_U8(WORD32 x) { asm("usat %0, #8, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_S8(WORD32 x) { asm("ssat %0, #8, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_U10(WORD32 x) { asm("usat %0, #10, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_S10(WORD32 x) { asm("ssat %0, #10, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_U11(WORD32 x) { asm("usat %0, #11, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_S11(WORD32 x) { asm("ssat %0, #11, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_U12(WORD32 x) { asm("usat %0, #12, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_S12(WORD32 x) { asm("ssat %0, #12, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_U16(WORD32 x) { asm("usat %0, #16, %1" : "=r"(x) : "r"(x)); return x; } static __inline WORD32 CLIP_S16(WORD32 x) { asm("ssat %0, #16, %1" : "=r"(x) : "r"(x)); return x; } static __inline UWORD32 ITT_BIG_ENDIAN(UWORD32 x) { asm("rev %0, %1" : "=r"(x) : "r"(x)); return x; } #define NOP(nop_cnt) {UWORD32 nop_i; for (nop_i = 0; nop_i < nop_cnt; nop_i++) asm("nop");} #else #define CLIP_U8(x) CLIP3(0, UINT8_MAX, (x)) #define CLIP_S8(x) CLIP3(INT8_MIN, INT8_MAX, (x)) #define CLIP_U10(x) CLIP3(0, 1023, (x)) #define CLIP_S10(x) CLIP3(-512, 511, (x)) #define CLIP_U11(x) CLIP3(0, 2047, (x)) #define CLIP_S11(x) CLIP3(-1024, 1023, (x)) #define CLIP_U12(x) CLIP3(0, 4095, (x)) #define CLIP_S12(x) CLIP3(-2048, 2047, (x)) #define CLIP_U16(x) CLIP3(0, UINT16_MAX, (x)) #define CLIP_S16(x) CLIP3(INT16_MIN, INT16_MAX, (x)) #define ITT_BIG_ENDIAN(x) __asm__("rev %0, %1" : "=r"(x) : "r"(x)); #define NOP(nop_cnt) \ { \ UWORD32 nop_i; \ for (nop_i = 0; nop_i < nop_cnt; nop_i++) \ __asm__ __volatile__("mov x0, x0"); \ } #endif /*saturating instructions are not available for WORD64 in ARMv7, hence we cannot * use inline assembly like other clips*/ #define CLIP_U32(x) CLIP3(0, UINT32_MAX, (x)) #define CLIP_S32(x) CLIP3(INT32_MIN, INT32_MAX, (x)) #define DATA_SYNC() __sync_synchronize() #define SHL(x,y) (((y) < 32) ? ((x) << (y)) : 0) #define SHR(x,y) (((y) < 32) ? ((x) >> (y)) : 0) #define SHR_NEG(val,shift) ((shift>0)?(val>>shift):(val<<(-shift))) #define SHL_NEG(val,shift) ((shift<0)?(val>>(-shift)):(val<> 1; shrn2 v0.8h, v23.4s, #1 //i4_value = (x3 + x2) >> 1; shrn v1.4h, v24.4s, #1 //i4_value = (x0 - x1) >> 1; shrn2 v1.8h, v25.4s, #1 //i4_value = (x3 - x2) >> 1; abs v2.8h, v0.8h abs v3.8h, v1.8h cmgt v4.8h, v0.8h, #0 //get the sign row 1,2 cmgt v5.8h, v1.8h, #0 neg w4, w4 //-u4_qbits dup v22.4s, w4 //load -u4_qbits umlal v14.4s, v2.4h, v30.4h umlal2 v15.4s, v2.8h, v30.8h umlal v16.4s, v3.4h, v30.4h umlal2 v17.4s, v3.8h, v30.8h ushl v14.4s, v14.4s, v22.4s ushl v15.4s, v15.4s, v22.4s ushl v16.4s, v16.4s, v22.4s ushl v17.4s, v17.4s, v22.4s uqxtn v14.4h, v14.4s uqxtn2 v14.8h, v15.4s uqxtn v16.4h, v16.4s uqxtn2 v16.8h, v17.4s neg v15.8h, v14.8h neg v17.8h, v16.8h bsl v4.16b, v14.16b, v15.16b bsl v5.16b, v16.16b, v17.16b cmeq v0.8h, v14.8h, #0 cmeq v1.8h, v16.8h, #0 st1 {v4.8h-v5.8h}, [x1] movi v20.8b, #16 xtn v2.8b, v0.8h xtn v3.8b, v1.8h ushr v2.8b, v2.8b, #7 ushr v3.8b, v3.8b, #7 add v2.8b, v2.8b, v3.8b addp v2.8b, v2.8b, v2.8b addp v2.8b, v2.8b, v2.8b addp v2.8b, v2.8b, v2.8b sub v20.8b, v20.8b, v2.8b st1 {v20.b}[0], [x6] pop_v_regs ret //***************************************************************************** //* //* function name : ih264_hadamard_quant_2x2_uv //* description : this function does forward hadamard transform and //* quantization for dc block of chroma for both planes //* //* arguments : x0 :pointer to src buffer // x1 :pointer to dst buffer // x2 :pu2_scale_matrix // x3 :pu2_threshold_matrix // w4 :u4_qbits // w5 :u4_round_factor // x6 :pu1_nnz // values returned : none // // register usage : // stack usage : 0 bytes // cycles : around // interruptiaility : interruptable // // known limitations // \assumptions : // // revision history : // dd mm yyyy author(s) changes // 20 2 2015 100633 first version // //***************************************************************************** // ih264_hadamard_quant_2x2_uv_av8(word16 *pi2_src, word16 *pi2_dst, // const uword16 *pu2_scale_matrix, // const uword16 *pu2_threshold_matrix, uword32 u4_qbits, // uword32 u4_round_factor,uword8 *pu1_nnz // ) .global ih264_hadamard_quant_2x2_uv_av8 ih264_hadamard_quant_2x2_uv_av8: push_v_regs ld2 {v0.4h-v1.4h}, [x0] //load src ld1 {v30.h}[0], [x2] //load pu2_scale_matrix[0] dup v30.4h, v30.h[0] //pu2_scale_matrix uxtl v30.4s, v30.4h //pu2_scale_matrix neg w4, w4 dup v24.4s, w4 //u4_qbits dup v25.4s, w5 //round fact dup v26.4s, v25.s[0] saddl v2.4s, v0.4h, v1.4h //x0 = x4 + x5;, x2 = x6 + x7; ssubl v3.4s, v0.4h, v1.4h //x1 = x4 - x5; x3 = x6 - x7; trn1 v4.4s, v2.4s, v3.4s trn2 v5.4s, v2.4s, v3.4s //q1 -> x0 x1, q2 -> x2 x3 add v0.4s, v4.4s , v5.4s // (x0 + x2) (x1 + x3) (y0 + y2); (y1 + y3); sub v1.4s, v4.4s , v5.4s // (x0 - x2) (x1 - x3) (y0 - y2); (y1 - y3); abs v2.4s, v0.4s abs v3.4s, v1.4s cmgt v4.4s, v0.4s, #0 //get the sign row 1,2 cmgt v5.4s, v1.4s, #0 uqxtn v4.4h, v4.4s sqxtn2 v4.8h, v5.4s mla v25.4s, v2.4s, v30.4s mla v26.4s, v3.4s, v30.4s ushl v2.4s, v25.4s, v24.4s //>>qbit ushl v3.4s, v26.4s, v24.4s //>>qbit uqxtn v2.4h, v2.4s uqxtn2 v2.8h, v3.4s neg v5.8h, v2.8h bsl v4.16b, v2.16b, v5.16b //*sign //rearrange such that we get each plane coeffs as continous mov v5.s[0], v4.s[1] mov v4.s[1], v4.s[2] mov v4.s[2], v5.s[0] cmeq v5.8h, v4.8h, #0 //compute nnz xtn v5.8b, v5.8h //reduce nnz comparison to 1 bit ushr v5.8b, v5.8b, #7 //reduce nnz comparison to 1 bit movi v20.8b, #4 //since we add zeros, we need to subtract from 4 to get nnz addp v5.8b, v5.8b, v5.8b //sum up nnz addp v5.8b, v5.8b, v5.8b //sum up nnz st1 {v4.8h}, [x1] //store the block st1 {v4.8h}, [x1] //store the block sub v20.8b, v20.8b, v5.8b //4- numzeros st1 {v20.h}[0], [x6] //store nnz pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_weighted_bi_pred_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_weighted_bi_pred_av8.s //* //* @brief //* Contains function definitions for weighted biprediction. //* //* @author //* Kaushik Senthoor R //* //* @par List of Functions: //* //* - ih264_weighted_bi_pred_luma_av8() //* - ih264_weighted_bi_pred_chroma_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ //******************************************************************************* //* @function //* ih264_weighted_bi_pred_luma_av8() //* //* @brief //* This routine performs the default weighted prediction as described in sec //* 8.4.2.3.2 titled "Weighted sample prediction process" for luma. //* //* @par Description: //* This function gets two ht x wd blocks, calculates the weighted samples, //* rounds off, adds offset and stores it in the destination block. //* //* @param[in] puc_src1 //* UWORD8 Pointer to the buffer containing the input block 1. //* //* @param[in] puc_src2 //* UWORD8 Pointer to the buffer containing the input block 2. //* //* @param[out] puc_dst //* UWORD8 pointer to the destination where the output block is stored. //* //* @param[in] src_strd1 //* Stride of the input buffer 1 //* //* @param[in] src_strd2 //* Stride of the input buffer 2 //* //* @param[in] dst_strd //* Stride of the destination buffer //* //* @param[in] log_WD //* number of bits to be rounded off //* //* @param[in] wt1 //* weight for the weighted prediction //* //* @param[in] wt2 //* weight for the weighted prediction //* //* @param[in] ofst1 //* offset 1 used after rounding off //* //* @param[in] ofst2 //* offset 2 used after rounding off //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* None //* //* @remarks //* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). //* //******************************************************************************* //*/ //void ih264_weighted_bi_pred_luma_av8(UWORD8 *puc_src1, // UWORD8 *puc_src2, // UWORD8 *puc_dst, // WORD32 src_strd1, // WORD32 src_strd2, // WORD32 dst_strd, // WORD32 log_WD, // WORD32 wt1, // WORD32 wt2, // WORD16 ofst1, // WORD16 ofst2, // WORD32 ht, // WORD32 wd) // //**************Variables Vs Registers***************************************** // x0 => puc_src1 // x1 => puc_src2 // x2 => puc_dst // w3 => src_strd1 // w4 => src_strd2 // w5 => dst_strd // w6 => log_WD // w7 => wt1 // [sp] => wt2 (w8) // [sp+8] => ofst1 (w9) // [sp+16] => ofst2 (w10) // [sp+24] => ht (w11) // [sp+32] => wd (w12) // .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_weighted_bi_pred_luma_av8 ih264_weighted_bi_pred_luma_av8: // STMFD sp!, {x4-x12,x14} //stack stores the values of the arguments push_v_regs sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 stp x19, x20, [sp, #-16]! #ifndef __APPLE__ ldr w8, [sp, #80] //Load wt2 in w8 ldr w9, [sp, #88] //Load ofst1 in w9 ldr w10, [sp, #96] //Load ofst2 in w10 ldr w11, [sp, #104] //Load ht in w11 ldr w12, [sp, #112] //Load wd in w12 #else ldr w8, [sp, #80] //Load wt2 in w8 ldr w9, [sp, #84] //Load ofst1 in w9 ldr w10, [sp, #88] //Load ofst2 in w10 ldr w11, [sp, #92] //Load ht in w11 ldr w12, [sp, #96] //Load wd in w12 #endif add w6, w6, #1 //w6 = log_WD + 1 neg w10, w6 //w10 = -(log_WD + 1) dup v0.8h, w10 //Q0 = -(log_WD + 1) (32-bit) add w9, w9, #1 //w9 = ofst1 + 1 add w9, w9, w10 //w9 = ofst1 + ofst2 + 1 mov v2.s[0], w7 mov v2.s[1], w8 //D2 = {wt1(32-bit), wt2(32-bit)} asr w9, w9, #1 //w9 = ofst = (ofst1 + ofst2 + 1) >> 1 dup v3.8b, w9 //D3 = ofst (8-bit) cmp w12, #16 beq loop_16 //branch if wd is 16 cmp w12, #8 //check if wd is 8 beq loop_8 //branch if wd is 8 loop_4: //each iteration processes four rows ld1 {v4.s}[0], [x0], x3 //load row 1 in source 1 ld1 {v4.s}[1], [x0], x3 //load row 2 in source 1 ld1 {v6.s}[0], [x1], x4 //load row 1 in source 2 ld1 {v6.s}[1], [x1], x4 //load row 2 in source 2 uxtl v4.8h, v4.8b //converting rows 1,2 in source 1 to 16-bit ld1 {v8.s}[0], [x0], x3 //load row 3 in source 1 ld1 {v8.s}[1], [x0], x3 //load row 4 in source 1 uxtl v6.8h, v6.8b //converting rows 1,2 in source 2 to 16-bit ld1 {v10.s}[0], [x1], x4 //load row 3 in source 2 ld1 {v10.s}[1], [x1], x4 //load row 4 in source 2 uxtl v8.8h, v8.8b //converting rows 3,4 in source 1 to 16-bit uxtl v10.8h, v10.8b //converting rows 3,4 in source 2 to 16-bit mul v4.8h, v4.8h , v2.h[0] //weight 1 mult. for rows 1,2 mla v4.8h, v6.8h , v2.h[2] //weight 2 mult. for rows 1,2 mul v8.8h, v8.8h , v2.h[0] //weight 1 mult. for rows 3,4 mla v8.8h, v10.8h , v2.h[2] //weight 2 mult. for rows 3,4 subs w11, w11, #4 //decrement ht by 4 srshl v4.8h, v4.8h , v0.8h //rounds off the weighted samples from rows 1,2 srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from rows 3,4 saddw v4.8h, v4.8h , v3.8b //adding offset for rows 1,2 saddw v8.8h, v8.8h , v3.8b //adding offset for rows 3,4 sqxtun v4.8b, v4.8h //saturating rows 1,2 to unsigned 8-bit sqxtun v8.8b, v8.8h //saturating rows 3,4 to unsigned 8-bit st1 {v4.s}[0], [x2], x5 //store row 1 in destination st1 {v4.s}[1], [x2], x5 //store row 2 in destination st1 {v8.s}[0], [x2], x5 //store row 3 in destination st1 {v8.s}[1], [x2], x5 //store row 4 in destination bgt loop_4 //if greater than 0 repeat the loop again b end_loops loop_8: //each iteration processes four rows ld1 {v4.8b}, [x0], x3 //load row 1 in source 1 ld1 {v6.8b}, [x1], x4 //load row 1 in source 2 ld1 {v8.8b}, [x0], x3 //load row 2 in source 1 ld1 {v10.8b}, [x1], x4 //load row 2 in source 2 uxtl v4.8h, v4.8b //converting row 1 in source 1 to 16-bit ld1 {v12.8b}, [x0], x3 //load row 3 in source 1 ld1 {v14.8b}, [x1], x4 //load row 3 in source 2 uxtl v6.8h, v6.8b //converting row 1 in source 2 to 16-bit ld1 {v16.8b}, [x0], x3 //load row 4 in source 1 ld1 {v18.8b}, [x1], x4 //load row 4 in source 2 uxtl v8.8h, v8.8b //converting row 2 in source 1 to 16-bit uxtl v10.8h, v10.8b //converting row 2 in source 2 to 16-bit mul v4.8h, v4.8h , v2.h[0] //weight 1 mult. for row 1 mla v4.8h, v6.8h , v2.h[2] //weight 2 mult. for row 1 uxtl v12.8h, v12.8b //converting row 3 in source 1 to 16-bit uxtl v14.8h, v14.8b //converting row 3 in source 2 to 16-bit mul v8.8h, v8.8h , v2.h[0] //weight 1 mult. for row 2 mla v8.8h, v10.8h , v2.h[2] //weight 2 mult. for row 2 uxtl v16.8h, v16.8b //converting row 4 in source 1 to 16-bit uxtl v18.8h, v18.8b //converting row 4 in source 2 to 16-bit mul v12.8h, v12.8h , v2.h[0] //weight 1 mult. for row 3 mla v12.8h, v14.8h , v2.h[2] //weight 2 mult. for row 3 mul v16.8h, v16.8h , v2.h[0] //weight 1 mult. for row 4 mla v16.8h, v18.8h , v2.h[2] //weight 2 mult. for row 4 srshl v4.8h, v4.8h , v0.8h //rounds off the weighted samples from row 1 srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 2 srshl v12.8h, v12.8h , v0.8h //rounds off the weighted samples from row 3 saddw v4.8h, v4.8h , v3.8b //adding offset for row 1 srshl v16.8h, v16.8h , v0.8h //rounds off the weighted samples from row 4 saddw v8.8h, v8.8h , v3.8b //adding offset for row 2 saddw v12.8h, v12.8h , v3.8b //adding offset for row 3 sqxtun v4.8b, v4.8h //saturating row 1 to unsigned 8-bit saddw v16.8h, v16.8h , v3.8b //adding offset for row 4 sqxtun v8.8b, v8.8h //saturating row 2 to unsigned 8-bit sqxtun v12.8b, v12.8h //saturating row 3 to unsigned 8-bit sqxtun v16.8b, v16.8h //saturating row 4 to unsigned 8-bit st1 {v4.8b}, [x2], x5 //store row 1 in destination st1 {v8.8b}, [x2], x5 //store row 2 in destination subs w11, w11, #4 //decrement ht by 4 st1 {v12.8b}, [x2], x5 //store row 3 in destination st1 {v16.8b}, [x2], x5 //store row 4 in destination bgt loop_8 //if greater than 0 repeat the loop again b end_loops loop_16: //each iteration processes two rows ld1 {v4.8b, v5.8b}, [x0], x3 //load row 1 in source 1 ld1 {v6.8b, v7.8b}, [x1], x4 //load row 1 in source 2 ld1 {v8.8b, v9.8b}, [x0], x3 //load row 2 in source 1 ld1 {v10.8b, v11.8b}, [x1], x4 //load row 2 in source 2 uxtl v20.8h, v4.8b //converting row 1L in source 1 to 16-bit ld1 {v12.8b, v13.8b}, [x0], x3 //load row 3 in source 1 ld1 {v14.8b, v15.8b}, [x1], x4 //load row 3 in source 2 uxtl v22.8h, v6.8b //converting row 1L in source 2 to 16-bit ld1 {v16.8b, v17.8b}, [x0], x3 //load row 4 in source 1 ld1 {v18.8b, v19.8b}, [x1], x4 //load row 4 in source 2 uxtl v4.8h, v5.8b //converting row 1H in source 1 to 16-bit uxtl v6.8h, v7.8b //converting row 1H in source 2 to 16-bit mul v20.8h, v20.8h , v2.h[0] //weight 1 mult. for row 1L mla v20.8h, v22.8h , v2.h[2] //weight 2 mult. for row 1L uxtl v24.8h, v8.8b //converting row 2L in source 1 to 16-bit uxtl v26.8h, v10.8b //converting row 2L in source 2 to 16-bit mul v4.8h, v4.8h , v2.h[0] //weight 1 mult. for row 1H mla v4.8h, v6.8h , v2.h[2] //weight 2 mult. for row 1H uxtl v8.8h, v9.8b //converting row 2H in source 1 to 16-bit uxtl v10.8h, v11.8b //converting row 2H in source 2 to 16-bit mul v24.8h, v24.8h , v2.h[0] //weight 1 mult. for row 2L mla v24.8h, v26.8h , v2.h[2] //weight 2 mult. for row 2L uxtl v28.8h, v12.8b //converting row 3L in source 1 to 16-bit uxtl v30.8h, v14.8b //converting row 3L in source 2 to 16-bit mul v8.8h, v8.8h , v2.h[0] //weight 1 mult. for row 2H mla v8.8h, v10.8h , v2.h[2] //weight 2 mult. for row 2H uxtl v12.8h, v13.8b //converting row 3H in source 1 to 16-bit uxtl v14.8h, v15.8b //converting row 3H in source 2 to 16-bit mul v28.8h, v28.8h , v2.h[0] //weight 1 mult. for row 3L mla v28.8h, v30.8h , v2.h[2] //weight 2 mult. for row 3L uxtl v22.8h, v16.8b //converting row 4L in source 1 to 16-bit uxtl v6.8h, v18.8b //converting row 4L in source 2 to 16-bit mul v12.8h, v12.8h , v2.h[0] //weight 1 mult. for row 3H mla v12.8h, v14.8h , v2.h[2] //weight 2 mult. for row 3H uxtl v16.8h, v17.8b //converting row 4H in source 1 to 16-bit uxtl v18.8h, v19.8b //converting row 4H in source 2 to 16-bit mul v22.8h, v22.8h , v2.h[0] //weight 1 mult. for row 4L mla v22.8h, v6.8h , v2.h[2] //weight 2 mult. for row 4L srshl v20.8h, v20.8h , v0.8h //rounds off the weighted samples from row 1L mul v16.8h, v16.8h , v2.h[0] //weight 1 mult. for row 4H mla v16.8h, v18.8h , v2.h[2] //weight 2 mult. for row 4H srshl v4.8h, v4.8h , v0.8h //rounds off the weighted samples from row 1H srshl v24.8h, v24.8h , v0.8h //rounds off the weighted samples from row 2L saddw v20.8h, v20.8h , v3.8b //adding offset for row 1L srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 2H saddw v4.8h, v4.8h , v3.8b //adding offset for row 1H srshl v28.8h, v28.8h , v0.8h //rounds off the weighted samples from row 3L saddw v24.8h, v24.8h , v3.8b //adding offset for row 2L srshl v12.8h, v12.8h , v0.8h //rounds off the weighted samples from row 3H saddw v8.8h, v8.8h , v3.8b //adding offset for row 2H srshl v22.8h, v22.8h , v0.8h //rounds off the weighted samples from row 4L saddw v28.8h, v28.8h , v3.8b //adding offset for row 3L srshl v16.8h, v16.8h , v0.8h //rounds off the weighted samples from row 4H saddw v12.8h, v12.8h , v3.8b //adding offset for row 3H sqxtun v26.8b, v20.8h //saturating row 1L to unsigned 8-bit saddw v22.8h, v22.8h , v3.8b //adding offset for row 4L sqxtun v27.8b, v4.8h //saturating row 1H to unsigned 8-bit saddw v16.8h, v16.8h , v3.8b //adding offset for row 4H sqxtun v10.8b, v24.8h //saturating row 2L to unsigned 8-bit sqxtun v11.8b, v8.8h //saturating row 2H to unsigned 8-bit sqxtun v30.8b, v28.8h //saturating row 3L to unsigned 8-bit sqxtun v31.8b, v12.8h //saturating row 3H to unsigned 8-bit st1 {v26.8b, v27.8b}, [x2], x5 //store row 1 in destination sqxtun v14.8b, v22.8h //saturating row 4L to unsigned 8-bit sqxtun v15.8b, v16.8h //saturating row 4H to unsigned 8-bit st1 {v10.8b, v11.8b}, [x2], x5 //store row 2 in destination subs w11, w11, #4 //decrement ht by 4 st1 {v30.8b, v31.8b}, [x2], x5 //store row 3 in destination st1 {v14.8b, v15.8b}, [x2], x5 //store row 4 in destination bgt loop_16 //if greater than 0 repeat the loop again end_loops: // LDMFD sp!,{x4-x12,x15} //Reload the registers from sp ldp x19, x20, [sp], #16 pop_v_regs ret //******************************************************************************* //* @function //* ih264_weighted_bi_pred_chroma_av8() //* //* @brief //* This routine performs the default weighted prediction as described in sec //* 8.4.2.3.2 titled "Weighted sample prediction process" for chroma. //* //* @par Description: //* This function gets two ht x wd blocks, calculates the weighted samples, //* rounds off, adds offset and stores it in the destination block for U and V. //* //* @param[in] puc_src1 //* UWORD8 Pointer to the buffer containing the input block 1. //* //* @param[in] puc_src2 //* UWORD8 Pointer to the buffer containing the input block 2. //* //* @param[out] puc_dst //* UWORD8 pointer to the destination where the output block is stored. //* //* @param[in] src_strd1 //* Stride of the input buffer 1 //* //* @param[in] src_strd2 //* Stride of the input buffer 2 //* //* @param[in] dst_strd //* Stride of the destination buffer //* //* @param[in] log_WD //* number of bits to be rounded off //* //* @param[in] wt1 //* weights for the weighted prediction in U and V //* //* @param[in] wt2 //* weights for the weighted prediction in U and V //* //* @param[in] ofst1 //* offset 1 used after rounding off for U an dV //* //* @param[in] ofst2 //* offset 2 used after rounding off for U and V //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* None //* //* @remarks //* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). //* //******************************************************************************* //*/ //void ih264_weighted_bi_pred_chroma_av8(UWORD8 *puc_src1, // UWORD8 *puc_src2, // UWORD8 *puc_dst, // WORD32 src_strd1, // WORD32 src_strd2, // WORD32 dst_strd, // WORD32 log_WD, // WORD32 wt1, // WORD32 wt2, // WORD32 ofst1, // WORD32 ofst2, // WORD32 ht, // WORD32 wd) // //**************Variables Vs Registers***************************************** // x0 => puc_src1 // x1 => puc_src2 // x2 => puc_dst // w3 => src_strd1 // w4 => src_strd2 // w5 => dst_strd // w6 => log_WD // w7 => wt1 // [sp] => wt2 (w8) // [sp+8] => ofst1 (w9) // [sp+16] => ofst2 (w10) // [sp+24] => ht (w11) // [sp+32] => wd (w12) // .global ih264_weighted_bi_pred_chroma_av8 ih264_weighted_bi_pred_chroma_av8: // STMFD sp!, {x4-x12,x14} //stack stores the values of the arguments push_v_regs sxtw x3, w3 sxtw x4, w4 sxtw x5, w5 stp x19, x20, [sp, #-16]! #ifndef __APPLE__ ldr w8, [sp, #80] //Load wt2 in w8 ldr w9, [sp, #88] //Load ofst1 in w9 ldr w10, [sp, #96] //Load ofst2 in w10 ldr w11, [sp, #104] //Load ht in w11 ldr w12, [sp, #112] //Load wd in w12 #else ldr w8, [sp, #80] //Load wt2 in w8 ldr w9, [sp, #84] //Load ofst1 in w9 ldr w10, [sp, #88] //Load ofst2 in w10 ldr w11, [sp, #92] //Load ht in w11 ldr w12, [sp, #96] //Load wd in w12 #endif dup v4.4s, w8 //Q2 = (wt2_u, wt2_v) (32-bit) dup v2.4s, w7 //Q1 = (wt1_u, wt1_v) (32-bit) add w6, w6, #1 //w6 = log_WD + 1 neg w20, w6 //w20 = -(log_WD + 1) dup v0.8h, w20 //Q0 = -(log_WD + 1) (16-bit) dup v20.8h, w9 //0ffset1 dup v21.8h, w10 //0ffset2 srhadd v6.8b, v20.8b, v21.8b sxtl v6.8h, v6.8b cmp w12, #8 //check if wd is 8 beq loop_8_uv //branch if wd is 8 cmp w12, #4 //check if wd is 4 beq loop_4_uv //branch if wd is 4 loop_2_uv: //each iteration processes two rows ld1 {v8.s}[0], [x0], x3 //load row 1 in source 1 ld1 {v8.s}[1], [x0], x3 //load row 2 in source 1 ld1 {v10.s}[0], [x1], x4 //load row 1 in source 2 ld1 {v10.s}[1], [x1], x4 //load row 2 in source 2 uxtl v8.8h, v8.8b //converting rows 1,2 in source 1 to 16-bit uxtl v10.8h, v10.8b //converting rows 1,2 in source 2 to 16-bit mul v8.8h, v8.8h , v2.8h //weight 1 mult. for rows 1,2 mla v8.8h, v10.8h , v4.8h //weight 2 mult. for rows 1,2 srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from rows 1,2 add v8.8h, v8.8h , v6.8h //adding offset for rows 1,2 sqxtun v8.8b, v8.8h //saturating rows 1,2 to unsigned 8-bit/ st1 {v8.s}[0], [x2], x5 //store row 1 in destination st1 {v8.s}[1], [x2], x5 //store row 2 in destination subs w11, w11, #2 //decrement ht by 2 bgt loop_2_uv //if greater than 0 repeat the loop again b end_loops_uv loop_4_uv: //each iteration processes two rows ld1 {v8.8b}, [x0], x3 //load row 1 in source 1 ld1 {v10.8b}, [x1], x4 //load row 1 in source 2 uxtl v8.8h, v8.8b //converting row 1 in source 1 to 16-bit ld1 {v12.8b}, [x0], x3 //load row 2 in source 1 uxtl v10.8h, v10.8b //converting row 1 in source 2 to 16-bit ld1 {v14.8b}, [x1], x4 //load row 2 in source 2 uxtl v12.8h, v12.8b //converting row 2 in source 1 to 16-bit mul v8.8h, v8.8h , v2.8h //weight 1 mult. for row 1 mla v8.8h, v10.8h , v4.8h //weight 2 mult. for row 1 uxtl v14.8h, v14.8b //converting row 2 in source 2 to 16-bit mul v12.8h, v12.8h , v2.8h //weight 1 mult. for row 2 mla v12.8h, v14.8h , v4.8h //weight 2 mult. for row 2 subs w11, w11, #2 //decrement ht by 2 srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 1 srshl v12.8h, v12.8h , v0.8h //rounds off the weighted samples from row 2 add v8.8h, v8.8h , v6.8h //adding offset for row 1 add v12.8h, v12.8h , v6.8h //adding offset for row 2 sqxtun v8.8b, v8.8h //saturating row 1 to unsigned 8-bit sqxtun v12.8b, v12.8h //saturating row 2 to unsigned 8-bit st1 {v8.8b}, [x2], x5 //store row 1 in destination st1 {v12.8b}, [x2], x5 //store row 2 in destination bgt loop_4_uv //if greater than 0 repeat the loop again b end_loops_uv loop_8_uv: //each iteration processes two rows ld1 {v8.8b, v9.8b}, [x0], x3 //load row 1 in source 1 ld1 {v10.8b, v11.8b}, [x1], x4 //load row 1 in source 2 ld1 {v12.8b, v13.8b}, [x0], x3 //load row 2 in source 1 ld1 {v14.8b, v15.8b}, [x1], x4 //load row 2 in source 2 uxtl v24.8h, v8.8b //converting row 1L in source 1 to 16-bit ld1 {v16.8b, v17.8b}, [x0], x3 //load row 3 in source 1 ld1 {v18.8b, v19.8b}, [x1], x4 //load row 3 in source 2 uxtl v26.8h, v10.8b //converting row 1L in source 2 to 16-bit ld1 {v20.8b, v21.8b}, [x0], x3 //load row 4 in source 1 ld1 {v22.8b, v23.8b}, [x1], x4 //load row 4 in source 2 uxtl v8.8h, v9.8b //converting row 1H in source 1 to 16-bit uxtl v10.8h, v11.8b //converting row 1H in source 2 to 16-bit mul v24.8h, v24.8h , v2.8h //weight 1 mult. for row 1L mla v24.8h, v26.8h , v4.8h //weight 2 mult. for row 1L uxtl v28.8h, v12.8b //converting row 2L in source 1 to 16-bit uxtl v30.8h, v14.8b //converting row 2L in source 2 to 16-bit mul v8.8h, v8.8h , v2.8h //weight 1 mult. for row 1H mla v8.8h, v10.8h , v4.8h //weight 2 mult. for row 1H uxtl v12.8h, v13.8b //converting row 2H in source 1 to 16-bit uxtl v14.8h, v15.8b //converting row 2H in source 2 to 16-bit mul v28.8h, v28.8h , v2.8h //weight 1 mult. for row 2L mla v28.8h, v30.8h , v4.8h //weight 2 mult. for row 2L uxtl v26.8h, v16.8b //converting row 3L in source 1 to 16-bit uxtl v10.8h, v18.8b //converting row 3L in source 2 to 16-bit mul v12.8h, v12.8h , v2.8h //weight 1 mult. for row 2H mla v12.8h, v14.8h , v4.8h //weight 2 mult. for row 2H uxtl v16.8h, v17.8b //converting row 3H in source 1 to 16-bit uxtl v18.8h, v19.8b //converting row 3H in source 2 to 16-bit mul v26.8h, v26.8h , v2.8h //weight 1 mult. for row 3L mla v26.8h, v10.8h , v4.8h //weight 2 mult. for row 3L uxtl v30.8h, v20.8b //converting row 4L in source 1 to 16-bit uxtl v14.8h, v22.8b //converting row 4L in source 2 to 16-bit mul v16.8h, v16.8h , v2.8h //weight 1 mult. for row 3H mla v16.8h, v18.8h , v4.8h //weight 2 mult. for row 3H uxtl v20.8h, v21.8b //converting row 4H in source 1 to 16-bit uxtl v22.8h, v23.8b //converting row 4H in source 2 to 16-bit mul v30.8h, v30.8h , v2.8h //weight 1 mult. for row 4L mla v30.8h, v14.8h , v4.8h //weight 2 mult. for row 4L srshl v24.8h, v24.8h , v0.8h //rounds off the weighted samples from row 1L mul v20.8h, v20.8h , v2.8h //weight 1 mult. for row 4H mla v20.8h, v22.8h , v4.8h //weight 2 mult. for row 4H srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 1H srshl v28.8h, v28.8h , v0.8h //rounds off the weighted samples from row 2L add v24.8h, v24.8h , v6.8h //adding offset for row 1L srshl v12.8h, v12.8h , v0.8h //rounds off the weighted samples from row 2H add v8.8h, v8.8h , v6.8h //adding offset for row 1H srshl v26.8h, v26.8h , v0.8h //rounds off the weighted samples from row 3L add v28.8h, v28.8h , v6.8h //adding offset for row 2L srshl v16.8h, v16.8h , v0.8h //rounds off the weighted samples from row 3H add v12.8h, v12.8h , v6.8h //adding offset for row 2H srshl v30.8h, v30.8h , v0.8h //rounds off the weighted samples from row 4L add v26.8h, v26.8h , v6.8h //adding offset for row 3L srshl v20.8h, v20.8h , v0.8h //rounds off the weighted samples from row 4H add v16.8h, v16.8h , v6.8h //adding offset for row 3H sqxtun v10.8b, v24.8h //saturating row 1L to unsigned 8-bit add v30.8h, v30.8h , v6.8h //adding offset for row 4L sqxtun v11.8b, v8.8h //saturating row 1H to unsigned 8-bit add v20.8h, v20.8h , v6.8h //adding offset for row 4H sqxtun v18.8b, v28.8h //saturating row 2L to unsigned 8-bit sqxtun v19.8b, v12.8h //saturating row 2H to unsigned 8-bit sqxtun v14.8b, v26.8h //saturating row 3L to unsigned 8-bit sqxtun v15.8b, v16.8h //saturating row 3H to unsigned 8-bit st1 {v10.8b, v11.8b}, [x2], x5 //store row 1 in destination sqxtun v22.8b, v30.8h //saturating row 4L to unsigned 8-bit sqxtun v23.8b, v20.8h //saturating row 4H to unsigned 8-bit st1 {v18.8b, v19.8b}, [x2], x5 //store row 2 in destination subs w11, w11, #4 //decrement ht by 4 st1 {v14.8b, v15.8b}, [x2], x5 //store row 3 in destination st1 {v22.8b, v23.8b}, [x2], x5 //store row 4 in destination bgt loop_8_uv //if greater than 0 repeat the loop again end_loops_uv: // LDMFD sp!,{x4-x12,x15} //Reload the registers from sp ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/ih264_weighted_pred_av8.s ================================================ //****************************************************************************** //* //* Copyright (C) 2015 The Android Open Source Project //* //* Licensed under the Apache License, Version 2.0 (the "License"); //* you may not use this file except in compliance with the License. //* You may obtain a copy of the License at: //* //* http://www.apache.org/licenses/LICENSE-2.0 //* //* Unless required by applicable law or agreed to in writing, software //* distributed under the License is distributed on an "AS IS" BASIS, //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //* See the License for the specific language governing permissions and //* limitations under the License. //* //***************************************************************************** //* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore //*/ ///** //****************************************************************************** //* @file //* ih264_weighted_pred_av8.s //* //* @brief //* Contains function definitions for weighted prediction. //* //* @author //* Kaushik Senthoor R //* //* @par List of Functions: //* //* - ih264_weighted_pred_luma_av8() //* - ih264_weighted_pred_chroma_av8() //* //* @remarks //* None //* //******************************************************************************* //*/ //******************************************************************************* //* @function //* ih264_weighted_pred_luma_av8() //* //* @brief //* This routine performs the default weighted prediction as described in sec //* 8.4.2.3.2 titled "Weighted sample prediction process" for luma. //* //* @par Description: //* This function gets a ht x wd block, calculates the weighted sample, rounds //* off, adds offset and stores it in the destination block. //* //* @param[in] puc_src: //* UWORD8 Pointer to the buffer containing the input block. //* //* @param[out] puc_dst //* UWORD8 pointer to the destination where the output block is stored. //* //* @param[in] src_strd //* Stride of the input buffer //* //* @param[in] dst_strd //* Stride of the destination buffer //* //* @param[in] log_WD //* number of bits to be rounded off //* //* @param[in] wt //* weight for the weighted prediction //* //* @param[in] ofst //* offset used after rounding off //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* None //* //* @remarks //* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). //* //******************************************************************************* //*/ //void ih264_weighted_pred_luma_av8(UWORD8 *puc_src, // UWORD8 *puc_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 log_WD, // WORD32 wt, // WORD32 ofst, // WORD32 ht, // WORD32 wd) // //**************Variables Vs Registers***************************************** // x0 => puc_src // x1 => puc_dst // w2 => src_strd // w3 => dst_strd // w4 => log_WD // w5 => wt // w6 => ofst // w7 => ht // [sp] => wd (w8) // .text .p2align 2 .include "ih264_neon_macros.s" .global ih264_weighted_pred_luma_av8 ih264_weighted_pred_luma_av8: // STMFD sp!, {x4-x9,x14} //stack stores the values of the arguments push_v_regs sxtw x2, w2 sxtw x3, w3 stp x19, x20, [sp, #-16]! ldr w8, [sp, #80] //Load wd sxtw x8, w8 dup v2.4h, w5 //D2 = wt (16-bit) neg w9, w4 //w9 = -log_WD dup v3.8b, w6 //D3 = ofst (8-bit) cmp w8, #16 //check if wd is 16 dup v0.8h, w9 //Q0 = -log_WD (16-bit) beq loop_16 //branch if wd is 16 cmp w8, #8 //check if wd is 8 beq loop_8 //branch if wd is 8 loop_4: //each iteration processes four rows ld1 {v4.s}[0], [x0], x2 //load row 1 in source ld1 {v4.s}[1], [x0], x2 //load row 2 in source ld1 {v6.s}[0], [x0], x2 //load row 3 in source ld1 {v6.s}[1], [x0], x2 //load row 4 in source uxtl v4.8h, v4.8b //converting rows 1,2 to 16-bit uxtl v6.8h, v6.8b //converting rows 3,4 to 16-bit mul v4.8h, v4.8h , v2.h[0] //weight mult. for rows 1,2 mul v6.8h, v6.8h , v2.h[0] //weight mult. for rows 3,4 subs w7, w7, #4 //decrement ht by 4 srshl v4.8h, v4.8h , v0.8h //rounds off the weighted samples from rows 1,2 srshl v6.8h, v6.8h , v0.8h //rounds off the weighted samples from rows 3,4 saddw v4.8h, v4.8h , v3.8b //adding offset for rows 1,2 saddw v6.8h, v6.8h , v3.8b //adding offset for rows 3,4 sqxtun v4.8b, v4.8h //saturating rows 1,2 to unsigned 8-bit sqxtun v6.8b, v6.8h //saturating rows 3,4 to unsigned 8-bit st1 {v4.s}[0], [x1], x3 //store row 1 in destination st1 {v4.s}[1], [x1], x3 //store row 2 in destination st1 {v6.s}[0], [x1], x3 //store row 3 in destination st1 {v6.s}[1], [x1], x3 //store row 4 in destination bgt loop_4 //if greater than 0 repeat the loop again b end_loops loop_8: //each iteration processes four rows ld1 {v4.8b}, [x0], x2 //load row 1 in source ld1 {v6.8b}, [x0], x2 //load row 2 in source ld1 {v8.8b}, [x0], x2 //load row 3 in source uxtl v4.8h, v4.8b //converting row 1 to 16-bit ld1 {v10.8b}, [x0], x2 //load row 4 in source uxtl v6.8h, v6.8b //converting row 2 to 16-bit uxtl v8.8h, v8.8b //converting row 3 to 16-bit mul v4.8h, v4.8h , v2.h[0] //weight mult. for row 1 uxtl v10.8h, v10.8b //converting row 4 to 16-bit mul v6.8h, v6.8h , v2.h[0] //weight mult. for row 2 mul v8.8h, v8.8h , v2.h[0] //weight mult. for row 3 mul v10.8h, v10.8h , v2.h[0] //weight mult. for row 4 srshl v4.8h, v4.8h , v0.8h //rounds off the weighted samples from row 1 srshl v6.8h, v6.8h , v0.8h //rounds off the weighted samples from row 2 srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 3 saddw v4.8h, v4.8h , v3.8b //adding offset for row 1 srshl v10.8h, v10.8h , v0.8h //rounds off the weighted samples from row 4 saddw v6.8h, v6.8h , v3.8b //adding offset for row 2 saddw v8.8h, v8.8h , v3.8b //adding offset for row 3 sqxtun v4.8b, v4.8h //saturating row 1 to unsigned 8-bit saddw v10.8h, v10.8h , v3.8b //adding offset for row 4 sqxtun v6.8b, v6.8h //saturating row 2 to unsigned 8-bit sqxtun v8.8b, v8.8h //saturating row 3 to unsigned 8-bit sqxtun v10.8b, v10.8h //saturating row 4 to unsigned 8-bit st1 {v4.8b}, [x1], x3 //store row 1 in destination st1 {v6.8b}, [x1], x3 //store row 2 in destination subs w7, w7, #4 //decrement ht by 4 st1 {v8.8b}, [x1], x3 //store row 3 in destination st1 {v10.8b}, [x1], x3 //store row 4 in destination bgt loop_8 //if greater than 0 repeat the loop again b end_loops loop_16: //each iteration processes two rows ld1 {v4.8b, v5.8b}, [x0], x2 //load row 1 in source ld1 {v6.8b, v7.8b}, [x0], x2 //load row 2 in source uxtl v12.8h, v4.8b //converting row 1L to 16-bit ld1 {v8.8b, v9.8b}, [x0], x2 //load row 3 in source uxtl v14.8h, v5.8b //converting row 1H to 16-bit ld1 {v10.8b, v11.8b}, [x0], x2 //load row 4 in source uxtl v16.8h, v6.8b //converting row 2L to 16-bit mul v12.8h, v12.8h , v2.h[0] //weight mult. for row 1L uxtl v18.8h, v7.8b //converting row 2H to 16-bit mul v14.8h, v14.8h , v2.h[0] //weight mult. for row 1H uxtl v20.8h, v8.8b //converting row 3L to 16-bit mul v16.8h, v16.8h , v2.h[0] //weight mult. for row 2L uxtl v22.8h, v9.8b //converting row 3H to 16-bit mul v18.8h, v18.8h , v2.h[0] //weight mult. for row 2H uxtl v24.8h, v10.8b //converting row 4L to 16-bit mul v20.8h, v20.8h , v2.h[0] //weight mult. for row 3L uxtl v26.8h, v11.8b //converting row 4H to 16-bit mul v22.8h, v22.8h , v2.h[0] //weight mult. for row 3H mul v24.8h, v24.8h , v2.h[0] //weight mult. for row 4L srshl v12.8h, v12.8h , v0.8h //rounds off the weighted samples from row 1L mul v26.8h, v26.8h , v2.h[0] //weight mult. for row 4H srshl v14.8h, v14.8h , v0.8h //rounds off the weighted samples from row 1H srshl v16.8h, v16.8h , v0.8h //rounds off the weighted samples from row 2L saddw v12.8h, v12.8h , v3.8b //adding offset for row 1L srshl v18.8h, v18.8h , v0.8h //rounds off the weighted samples from row 2H saddw v14.8h, v14.8h , v3.8b //adding offset for row 1H sqxtun v4.8b, v12.8h //saturating row 1L to unsigned 8-bit srshl v20.8h, v20.8h , v0.8h //rounds off the weighted samples from row 3L saddw v16.8h, v16.8h , v3.8b //adding offset for row 2L sqxtun v5.8b, v14.8h //saturating row 1H to unsigned 8-bit srshl v22.8h, v22.8h , v0.8h //rounds off the weighted samples from row 3H saddw v18.8h, v18.8h , v3.8b //adding offset for row 2H sqxtun v6.8b, v16.8h //saturating row 2L to unsigned 8-bit srshl v24.8h, v24.8h , v0.8h //rounds off the weighted samples from row 4L saddw v20.8h, v20.8h , v3.8b //adding offset for row 3L sqxtun v7.8b, v18.8h //saturating row 2H to unsigned 8-bit srshl v26.8h, v26.8h , v0.8h //rounds off the weighted samples from row 4H saddw v22.8h, v22.8h , v3.8b //adding offset for row 3H sqxtun v8.8b, v20.8h //saturating row 3L to unsigned 8-bit saddw v24.8h, v24.8h , v3.8b //adding offset for row 4L sqxtun v9.8b, v22.8h //saturating row 3H to unsigned 8-bit saddw v26.8h, v26.8h , v3.8b //adding offset for row 4H sqxtun v10.8b, v24.8h //saturating row 4L to unsigned 8-bit st1 {v4.8b, v5.8b}, [x1], x3 //store row 1 in destination sqxtun v11.8b, v26.8h //saturating row 4H to unsigned 8-bit st1 {v6.8b, v7.8b}, [x1], x3 //store row 2 in destination subs w7, w7, #4 //decrement ht by 4 st1 {v8.8b, v9.8b}, [x1], x3 //store row 3 in destination st1 {v10.8b, v11.8b}, [x1], x3 //store row 4 in destination bgt loop_16 //if greater than 0 repeat the loop again end_loops: // LDMFD sp!,{x4-x9,x15} //Reload the registers from sp ldp x19, x20, [sp], #16 pop_v_regs ret //******************************************************************************* //* @function //* ih264_weighted_pred_chroma_av8() //* //* @brief //* This routine performs the default weighted prediction as described in sec //* 8.4.2.3.2 titled "Weighted sample prediction process" for chroma. //* //* @par Description: //* This function gets a ht x wd block, calculates the weighted sample, rounds //* off, adds offset and stores it in the destination block for U and V. //* //* @param[in] puc_src: //* UWORD8 Pointer to the buffer containing the input block. //* //* @param[out] puc_dst //* UWORD8 pointer to the destination where the output block is stored. //* //* @param[in] src_strd //* Stride of the input buffer //* //* @param[in] dst_strd //* Stride of the destination buffer //* //* @param[in] log_WD //* number of bits to be rounded off //* //* @param[in] wt //* weights for the weighted prediction for U and V //* //* @param[in] ofst //* offsets used after rounding off for U and V //* //* @param[in] ht //* integer height of the array //* //* @param[in] wd //* integer width of the array //* //* @returns //* None //* //* @remarks //* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). //* //******************************************************************************* //*/ //void ih264_weighted_pred_chroma_av8(UWORD8 *puc_src, // UWORD8 *puc_dst, // WORD32 src_strd, // WORD32 dst_strd, // WORD32 log_WD, // WORD32 wt, // WORD32 ofst, // WORD32 ht, // WORD32 wd) // //**************Variables Vs Registers***************************************** // x0 => puc_src // x1 => puc_dst // w2 => src_strd // w3 => dst_strd // w4 => log_WD // w5 => wt // w6 => ofst // w7 => ht // [sp] => wd (w8) // .global ih264_weighted_pred_chroma_av8 ih264_weighted_pred_chroma_av8: // STMFD sp!, {x4-x9,x14} //stack stores the values of the arguments push_v_regs sxtw x2, w2 sxtw x3, w3 stp x19, x20, [sp, #-16]! ldr w8, [sp, #80] //Load wd sxtw x8, w8 neg w9, w4 //w9 = -log_WD dup v2.4s, w5 //Q1 = {wt_u (16-bit), wt_v (16-bit)} dup v4.4h, w6 //D4 = {ofst_u (8-bit), ofst_v (8-bit)} cmp w8, #8 //check if wd is 8 dup v0.8h, w9 //Q0 = -log_WD (16-bit) beq loop_8_uv //branch if wd is 8 cmp w8, #4 //check if ws is 4 beq loop_4_uv //branch if wd is 4 loop_2_uv: //each iteration processes two rows ld1 {v6.s}[0], [x0], x2 //load row 1 in source ld1 {v6.s}[1], [x0], x2 //load row 2 in source uxtl v6.8h, v6.8b //converting rows 1,2 to 16-bit mul v6.8h, v6.8h , v2.8h //weight mult. for rows 1,2 srshl v6.8h, v6.8h , v0.8h //rounds off the weighted samples from rows 1,2 saddw v6.8h, v6.8h , v4.8b //adding offset for rows 1,2 sqxtun v6.8b, v6.8h //saturating rows 1,2 to unsigned 8-bit subs w7, w7, #2 //decrement ht by 2 st1 {v6.s}[0], [x1], x3 //store row 1 in destination st1 {v6.s}[1], [x1], x3 //store row 2 in destination bgt loop_2_uv //if greater than 0 repeat the loop again b end_loops_uv loop_4_uv: //each iteration processes two rows ld1 {v6.8b}, [x0], x2 //load row 1 in source ld1 {v8.8b}, [x0], x2 //load row 2 in source uxtl v6.8h, v6.8b //converting row 1 to 16-bit uxtl v8.8h, v8.8b //converting row 2 to 16-bit mul v6.8h, v6.8h , v2.8h //weight mult. for row 1 mul v8.8h, v8.8h , v2.8h //weight mult. for row 2 subs w7, w7, #2 //decrement ht by 2 srshl v6.8h, v6.8h , v0.8h //rounds off the weighted samples from row 1 srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 2 saddw v6.8h, v6.8h , v4.8b //adding offset for row 1 saddw v8.8h, v8.8h , v4.8b //adding offset for row 2 sqxtun v6.8b, v6.8h //saturating row 1 to unsigned 8-bit sqxtun v8.8b, v8.8h //saturating row 2 to unsigned 8-bit st1 {v6.8b}, [x1], x3 //store row 1 in destination st1 {v8.8b}, [x1], x3 //store row 2 in destination bgt loop_4_uv //if greater than 0 repeat the loop again b end_loops_uv loop_8_uv: //each iteration processes two rows ld1 {v6.8b, v7.8b}, [x0], x2 //load row 1 in source ld1 {v8.8b, v9.8b}, [x0], x2 //load row 2 in source uxtl v14.8h, v6.8b //converting row 1L to 16-bit ld1 {v10.8b, v11.8b}, [x0], x2 //load row 3 in source uxtl v16.8h, v7.8b //converting row 1H to 16-bit ld1 {v12.8b, v13.8b}, [x0], x2 //load row 4 in source mul v14.8h, v14.8h , v2.8h //weight mult. for row 1L uxtl v18.8h, v8.8b //converting row 2L to 16-bit mul v16.8h, v16.8h , v2.8h //weight mult. for row 1H uxtl v20.8h, v9.8b //converting row 2H to 16-bit mul v18.8h, v18.8h , v2.8h //weight mult. for row 2L uxtl v22.8h, v10.8b //converting row 3L to 16-bit mul v20.8h, v20.8h , v2.8h //weight mult. for row 2H uxtl v24.8h, v11.8b //converting row 3H to 16-bit mul v22.8h, v22.8h , v2.8h //weight mult. for row 3L uxtl v26.8h, v12.8b //converting row 4L to 16-bit mul v24.8h, v24.8h , v2.8h //weight mult. for row 3H uxtl v28.8h, v13.8b //converting row 4H to 16-bit mul v26.8h, v26.8h , v2.8h //weight mult. for row 4L srshl v14.8h, v14.8h , v0.8h //rounds off the weighted samples from row 1L mul v28.8h, v28.8h , v2.8h //weight mult. for row 4H srshl v16.8h, v16.8h , v0.8h //rounds off the weighted samples from row 1H srshl v18.8h, v18.8h , v0.8h //rounds off the weighted samples from row 2L saddw v14.8h, v14.8h , v4.8b //adding offset for row 1L srshl v20.8h, v20.8h , v0.8h //rounds off the weighted samples from row 2H saddw v16.8h, v16.8h , v4.8b //adding offset for row 1H sqxtun v6.8b, v14.8h //saturating row 1L to unsigned 8-bit srshl v22.8h, v22.8h , v0.8h //rounds off the weighted samples from row 3L saddw v18.8h, v18.8h , v4.8b //adding offset for row 2L sqxtun v7.8b, v16.8h //saturating row 1H to unsigned 8-bit srshl v24.8h, v24.8h , v0.8h //rounds off the weighted samples from row 3H saddw v20.8h, v20.8h , v4.8b //adding offset for row 2H sqxtun v8.8b, v18.8h //saturating row 2L to unsigned 8-bit srshl v26.8h, v26.8h , v0.8h //rounds off the weighted samples from row 4L saddw v22.8h, v22.8h , v4.8b //adding offset for row 3L sqxtun v9.8b, v20.8h //saturating row 2H to unsigned 8-bit srshl v28.8h, v28.8h , v0.8h //rounds off the weighted samples from row 4H saddw v24.8h, v24.8h , v4.8b //adding offset for row 3H sqxtun v10.8b, v22.8h //saturating row 3L to unsigned 8-bit saddw v26.8h, v26.8h , v4.8b //adding offset for row 4L sqxtun v11.8b, v24.8h //saturating row 3H to unsigned 8-bit saddw v28.8h, v28.8h , v4.8b //adding offset for row 4H sqxtun v12.8b, v26.8h //saturating row 4L to unsigned 8-bit st1 {v6.8b, v7.8b}, [x1], x3 //store row 1 in destination sqxtun v13.8b, v28.8h //saturating row 4H to unsigned 8-bit st1 {v8.8b, v9.8b}, [x1], x3 //store row 2 in destination subs w7, w7, #4 //decrement ht by 4 st1 {v10.8b, v11.8b}, [x1], x3 //store row 3 in destination st1 {v12.8b, v13.8b}, [x1], x3 //store row 4 in destination bgt loop_8_uv //if greater than 0 repeat the loop again end_loops_uv: // LDMFD sp!,{x4-x9,x15} //Reload the registers from sp ldp x19, x20, [sp], #16 pop_v_regs ret ================================================ FILE: dependencies/ih264d/common/armv8/macos_arm_symbol_aliases.s ================================================ // macOS clang compilers append preceding underscores to function names, this is to prevent // mismatches with the assembly function names and the C functions as defined in the header. .global _ih264_deblk_chroma_horz_bs4_av8 _ih264_deblk_chroma_horz_bs4_av8 = ih264_deblk_chroma_horz_bs4_av8 .global _ih264_deblk_chroma_horz_bslt4_av8 _ih264_deblk_chroma_horz_bslt4_av8 = ih264_deblk_chroma_horz_bslt4_av8 .global _ih264_deblk_chroma_vert_bs4_av8 _ih264_deblk_chroma_vert_bs4_av8 = ih264_deblk_chroma_vert_bs4_av8 .global _ih264_deblk_chroma_vert_bslt4_av8 _ih264_deblk_chroma_vert_bslt4_av8 = ih264_deblk_chroma_vert_bslt4_av8 .global _ih264_deblk_luma_horz_bs4_av8 _ih264_deblk_luma_horz_bs4_av8 = ih264_deblk_luma_horz_bs4_av8 .global _ih264_deblk_luma_horz_bslt4_av8 _ih264_deblk_luma_horz_bslt4_av8 = ih264_deblk_luma_horz_bslt4_av8 .global _ih264_deblk_luma_vert_bs4_av8 _ih264_deblk_luma_vert_bs4_av8 = ih264_deblk_luma_vert_bs4_av8 .global _ih264_deblk_luma_vert_bslt4_av8 _ih264_deblk_luma_vert_bslt4_av8 = ih264_deblk_luma_vert_bslt4_av8 .global _ih264_default_weighted_pred_chroma_av8 _ih264_default_weighted_pred_chroma_av8 = ih264_default_weighted_pred_chroma_av8 .global _ih264_default_weighted_pred_luma_av8 _ih264_default_weighted_pred_luma_av8 = ih264_default_weighted_pred_luma_av8 .global _ih264_ihadamard_scaling_4x4_av8 _ih264_ihadamard_scaling_4x4_av8 = ih264_ihadamard_scaling_4x4_av8 .global _ih264_inter_pred_chroma_av8 _ih264_inter_pred_chroma_av8 = ih264_inter_pred_chroma_av8 .global _ih264_inter_pred_luma_copy_av8 _ih264_inter_pred_luma_copy_av8 = ih264_inter_pred_luma_copy_av8 .global _ih264_inter_pred_luma_horz_av8 _ih264_inter_pred_luma_horz_av8 = ih264_inter_pred_luma_horz_av8 .global _ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 _ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 = ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 .global _ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 _ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 = ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 .global _ih264_inter_pred_luma_horz_qpel_av8 _ih264_inter_pred_luma_horz_qpel_av8 = ih264_inter_pred_luma_horz_qpel_av8 .global _ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 _ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 = ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 .global _ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 _ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 .global _ih264_inter_pred_luma_vert_av8 _ih264_inter_pred_luma_vert_av8 = ih264_inter_pred_luma_vert_av8 .global _ih264_inter_pred_luma_vert_qpel_av8 _ih264_inter_pred_luma_vert_qpel_av8 = ih264_inter_pred_luma_vert_qpel_av8 .global _ih264_intra_pred_chroma_8x8_mode_horz_av8 _ih264_intra_pred_chroma_8x8_mode_horz_av8 = ih264_intra_pred_chroma_8x8_mode_horz_av8 .global _ih264_intra_pred_chroma_8x8_mode_plane_av8 _ih264_intra_pred_chroma_8x8_mode_plane_av8 = ih264_intra_pred_chroma_8x8_mode_plane_av8 .global _ih264_intra_pred_chroma_8x8_mode_vert_av8 _ih264_intra_pred_chroma_8x8_mode_vert_av8 = ih264_intra_pred_chroma_8x8_mode_vert_av8 .global _ih264_intra_pred_luma_16x16_mode_dc_av8 _ih264_intra_pred_luma_16x16_mode_dc_av8 = ih264_intra_pred_luma_16x16_mode_dc_av8 .global _ih264_intra_pred_luma_16x16_mode_horz_av8 _ih264_intra_pred_luma_16x16_mode_horz_av8 = ih264_intra_pred_luma_16x16_mode_horz_av8 .global _ih264_intra_pred_luma_16x16_mode_plane_av8 _ih264_intra_pred_luma_16x16_mode_plane_av8 = ih264_intra_pred_luma_16x16_mode_plane_av8 .global _ih264_intra_pred_luma_16x16_mode_vert_av8 _ih264_intra_pred_luma_16x16_mode_vert_av8 = ih264_intra_pred_luma_16x16_mode_vert_av8 .global _ih264_intra_pred_luma_4x4_mode_dc_av8 _ih264_intra_pred_luma_4x4_mode_dc_av8 = ih264_intra_pred_luma_4x4_mode_dc_av8 .global _ih264_intra_pred_luma_4x4_mode_diag_dl_av8 _ih264_intra_pred_luma_4x4_mode_diag_dl_av8 = ih264_intra_pred_luma_4x4_mode_diag_dl_av8 .global _ih264_intra_pred_luma_4x4_mode_diag_dr_av8 _ih264_intra_pred_luma_4x4_mode_diag_dr_av8 = ih264_intra_pred_luma_4x4_mode_diag_dr_av8 .global _ih264_intra_pred_luma_4x4_mode_horz_av8 _ih264_intra_pred_luma_4x4_mode_horz_av8 = ih264_intra_pred_luma_4x4_mode_horz_av8 .global _ih264_intra_pred_luma_4x4_mode_horz_d_av8 _ih264_intra_pred_luma_4x4_mode_horz_d_av8 = ih264_intra_pred_luma_4x4_mode_horz_d_av8 .global _ih264_intra_pred_luma_4x4_mode_horz_u_av8 _ih264_intra_pred_luma_4x4_mode_horz_u_av8 = ih264_intra_pred_luma_4x4_mode_horz_u_av8 .global _ih264_intra_pred_luma_4x4_mode_vert_av8 _ih264_intra_pred_luma_4x4_mode_vert_av8 = ih264_intra_pred_luma_4x4_mode_vert_av8 .global _ih264_intra_pred_luma_4x4_mode_vert_l_av8 _ih264_intra_pred_luma_4x4_mode_vert_l_av8 = ih264_intra_pred_luma_4x4_mode_vert_l_av8 .global _ih264_intra_pred_luma_4x4_mode_vert_r_av8 _ih264_intra_pred_luma_4x4_mode_vert_r_av8 = ih264_intra_pred_luma_4x4_mode_vert_r_av8 .global _ih264_intra_pred_luma_8x8_mode_dc_av8 _ih264_intra_pred_luma_8x8_mode_dc_av8 = ih264_intra_pred_luma_8x8_mode_dc_av8 .global _ih264_intra_pred_luma_8x8_mode_diag_dl_av8 _ih264_intra_pred_luma_8x8_mode_diag_dl_av8 = ih264_intra_pred_luma_8x8_mode_diag_dl_av8 .global _ih264_intra_pred_luma_8x8_mode_diag_dr_av8 _ih264_intra_pred_luma_8x8_mode_diag_dr_av8 = ih264_intra_pred_luma_8x8_mode_diag_dr_av8 .global _ih264_intra_pred_luma_8x8_mode_horz_av8 _ih264_intra_pred_luma_8x8_mode_horz_av8 = ih264_intra_pred_luma_8x8_mode_horz_av8 .global _ih264_intra_pred_luma_8x8_mode_horz_d_av8 _ih264_intra_pred_luma_8x8_mode_horz_d_av8 = ih264_intra_pred_luma_8x8_mode_horz_d_av8 .global _ih264_intra_pred_luma_8x8_mode_horz_u_av8 _ih264_intra_pred_luma_8x8_mode_horz_u_av8 = ih264_intra_pred_luma_8x8_mode_horz_u_av8 .global _ih264_intra_pred_luma_8x8_mode_vert_av8 _ih264_intra_pred_luma_8x8_mode_vert_av8 = ih264_intra_pred_luma_8x8_mode_vert_av8 .global _ih264_intra_pred_luma_8x8_mode_vert_l_av8 _ih264_intra_pred_luma_8x8_mode_vert_l_av8 = ih264_intra_pred_luma_8x8_mode_vert_l_av8 .global _ih264_intra_pred_luma_8x8_mode_vert_r_av8 _ih264_intra_pred_luma_8x8_mode_vert_r_av8 = ih264_intra_pred_luma_8x8_mode_vert_r_av8 .global _ih264_iquant_itrans_recon_4x4_av8 _ih264_iquant_itrans_recon_4x4_av8 = ih264_iquant_itrans_recon_4x4_av8 .global _ih264_iquant_itrans_recon_4x4_dc_av8 _ih264_iquant_itrans_recon_4x4_dc_av8 = ih264_iquant_itrans_recon_4x4_dc_av8 .global _ih264_iquant_itrans_recon_8x8_av8 _ih264_iquant_itrans_recon_8x8_av8 = ih264_iquant_itrans_recon_8x8_av8 .global _ih264_iquant_itrans_recon_8x8_dc_av8 _ih264_iquant_itrans_recon_8x8_dc_av8 = ih264_iquant_itrans_recon_8x8_dc_av8 .global _ih264_iquant_itrans_recon_chroma_4x4_av8 _ih264_iquant_itrans_recon_chroma_4x4_av8 = ih264_iquant_itrans_recon_chroma_4x4_av8 .global _ih264_iquant_itrans_recon_chroma_4x4_dc_av8 _ih264_iquant_itrans_recon_chroma_4x4_dc_av8 = ih264_iquant_itrans_recon_chroma_4x4_dc_av8 .global _ih264_pad_left_chroma_av8 _ih264_pad_left_chroma_av8 = ih264_pad_left_chroma_av8 .global _ih264_pad_left_luma_av8 _ih264_pad_left_luma_av8 = ih264_pad_left_luma_av8 .global _ih264_pad_right_chroma_av8 _ih264_pad_right_chroma_av8 = ih264_pad_right_chroma_av8 .global _ih264_pad_right_luma_av8 _ih264_pad_right_luma_av8 = ih264_pad_right_luma_av8 .global _ih264_pad_top_av8 _ih264_pad_top_av8 = ih264_pad_top_av8 .global _ih264_weighted_bi_pred_chroma_av8 _ih264_weighted_bi_pred_chroma_av8 = ih264_weighted_bi_pred_chroma_av8 .global _ih264_weighted_bi_pred_luma_av8 _ih264_weighted_bi_pred_luma_av8 = ih264_weighted_bi_pred_luma_av8 .global _ih264_weighted_pred_chroma_av8 _ih264_weighted_pred_chroma_av8 = ih264_weighted_pred_chroma_av8 .global _ih264_weighted_pred_luma_av8 _ih264_weighted_pred_luma_av8 = ih264_weighted_pred_luma_av8 ================================================ FILE: dependencies/ih264d/common/ih264_buf_mgr.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_buf_mgr.c * * @brief * Contains function definitions for buffer management * * @author * Srinivas T * * @par List of Functions: * - ih264_buf_mgr_size() * - ih264_buf_mgr_lock() * - ih264_buf_mgr_unlock() * - ih264_buf_mgr_yield() * - ih264_buf_mgr_free() * - ih264_buf_mgr_init() * - ih264_buf_mgr_add() * - ih264_buf_mgr_get_next_free() * - ih264_buf_mgr_check_free() * - ih264_buf_mgr_set_status() * - ih264_buf_mgr_get_status() * - ih264_buf_mgr_get_buf() * - ih264_buf_mgr_get_bufid() * - ih264_buf_mgr_get_num_active_buf() * * @remarks * None * ******************************************************************************* */ #include #include #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_defs.h" #include "ih264_error.h" #include "ih264_buf_mgr.h" #include "ithread.h" /** ******************************************************************************* * * @brief Returns size for buf queue context. Does not include buf queue buffer * requirements * * @par Description * Returns size for buf queue context. Does not include buf queue buffer * requirements. Buffer size required to store the bufs should be allocated in * addition to the value returned here. * * @returns Size of the buf queue context * * @remarks * ******************************************************************************* */ WORD32 ih264_buf_mgr_size(void) { WORD32 size; size = sizeof(buf_mgr_t); size += ithread_get_mutex_lock_size(); return size; } /** ******************************************************************************* * * @brief * Locks the buf_mgr context * * @par Description * Locks the buf_mgr context by calling ithread_mutex_lock() * * @param[in] ps_buf_mgr * Job Queue context * * @returns IH264_FAIL if mutex lock fails else IH264_SUCCESS * * @remarks * ******************************************************************************* */ IH264_ERROR_T ih264_buf_mgr_lock(buf_mgr_t *ps_buf_mgr) { WORD32 retval; retval = ithread_mutex_lock(ps_buf_mgr->pv_mutex); if(retval) { return IH264_FAIL; } return IH264_SUCCESS; } /** ******************************************************************************* * * @brief * Unlocks the buf_mgr context * * @par Description * Unlocks the buf_mgr context by calling ithread_mutex_unlock() * * @param[in] ps_buf_mgr * Job Queue context * * @returns IH264_FAIL if mutex unlock fails else IH264_SUCCESS * * @remarks * ******************************************************************************* */ IH264_ERROR_T ih264_buf_mgr_unlock(buf_mgr_t *ps_buf_mgr) { WORD32 retval; retval = ithread_mutex_unlock(ps_buf_mgr->pv_mutex); if(retval) { return IH264_FAIL; } return IH264_SUCCESS; } /** ******************************************************************************* * * @brief * Yeilds the thread * * @par Description * Unlocks the buf_mgr context by calling * ih264_buf_mgr_unlock(), ithread_yield() and then ih264_buf_mgr_lock() * buf_mgr is unlocked before to ensure the buf_mgr can be accessed by other threads * If unlock is not done before calling yield then no other thread can access * the buf_mgr functions and update buf_mgr. * * @param[in] ps_buf_mgr * Job Queue context * * @returns IH264_FAIL if mutex lock unlock or yield fails else IH264_SUCCESS * * @remarks * ******************************************************************************* */ IH264_ERROR_T ih264_buf_mgr_yield(buf_mgr_t *ps_buf_mgr) { IH264_ERROR_T ret = IH264_SUCCESS; IH264_ERROR_T rettmp; rettmp = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((rettmp != IH264_SUCCESS), rettmp); //ithread_usleep(10); ithread_yield(); rettmp = ih264_buf_mgr_lock(ps_buf_mgr); RETURN_IF((rettmp != IH264_SUCCESS), rettmp); return ret; } /** ******************************************************************************* * * @brief free the buf queue pointers * * @par Description * Frees the buf_mgr context * * @param[in] pv_buf * Memoy for buf queue buffer and buf queue context * * @returns Pointer to buf queue context * * @remarks * Since it will be called only once by master thread this is not thread safe. * ******************************************************************************* */ IH264_ERROR_T ih264_buf_mgr_free(buf_mgr_t *ps_buf_mgr) { WORD32 ret; ret = ithread_mutex_destroy(ps_buf_mgr->pv_mutex); if(0 == ret) return IH264_SUCCESS; else return IH264_FAIL; } /** ******************************************************************************* * * @brief * Buffer manager initialization function. * * @par Description: * Initializes the buffer manager structure * * @param[in] ps_buf_mgr * Pointer to the buffer manager * * @returns * * @remarks * None * ******************************************************************************* */ void *ih264_buf_mgr_init(void *pv_buf) { WORD32 id; UWORD8 *pu1_buf; buf_mgr_t *ps_buf_mgr; pu1_buf = (UWORD8 *)pv_buf; ps_buf_mgr = (buf_mgr_t *)pu1_buf; pu1_buf += sizeof(buf_mgr_t); ps_buf_mgr->pv_mutex = pu1_buf; pu1_buf += ithread_get_mutex_lock_size(); ithread_mutex_init(ps_buf_mgr->pv_mutex); ps_buf_mgr->i4_max_buf_cnt = BUF_MGR_MAX_CNT; ps_buf_mgr->i4_active_buf_cnt = 0; for(id = 0; id < BUF_MGR_MAX_CNT; id++) { ps_buf_mgr->au4_status[id] = 0; ps_buf_mgr->apv_ptr[id] = NULL; } return ps_buf_mgr; } /** ******************************************************************************* * * @brief * Adds and increments the buffer and buffer count. * * @par Description: * Adds a buffer to the buffer manager if it is not already present and * increments the active buffer count * * @param[in] ps_buf_mgr * Pointer to the buffer manager * * @param[in] pv_ptr * Pointer to the buffer to be added * * @returns Returns 0 on success, -1 otherwise * * @remarks * None * ******************************************************************************* */ IH264_ERROR_T ih264_buf_mgr_add(buf_mgr_t *ps_buf_mgr, void *pv_ptr, WORD32 buf_id) { IH264_ERROR_T ret = IH264_SUCCESS; ret = ih264_buf_mgr_lock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); /* Check if buffer ID is within allowed range */ if(buf_id >= ps_buf_mgr->i4_max_buf_cnt) { ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return IH264_FAIL; } /* Check if the current ID is being used to hold some other buffer */ if((ps_buf_mgr->apv_ptr[buf_id] != NULL) && (ps_buf_mgr->apv_ptr[buf_id] !=pv_ptr)) { ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return IH264_FAIL; } ps_buf_mgr->apv_ptr[buf_id] = pv_ptr; ps_buf_mgr->i4_active_buf_cnt++; ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return ret; } /** ******************************************************************************* * * @brief * Gets the next free buffer. * * @par Description: * Returns the next free buffer available and sets the corresponding status * to DEC * * @param[in] ps_buf_mgr * Pointer to the buffer manager * * @param[in] pi4_buf_id * Pointer to the id of the free buffer * * @returns Pointer to the free buffer * * @remarks * None * ******************************************************************************* */ void* ih264_buf_mgr_get_next_free(buf_mgr_t *ps_buf_mgr, WORD32 *pi4_buf_id) { WORD32 id; void *pv_ret_ptr; IH264_ERROR_T ret = IH264_SUCCESS; ret = ih264_buf_mgr_lock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), NULL); pv_ret_ptr = NULL; for(id = 0; id < ps_buf_mgr->i4_active_buf_cnt; id++) { /* Check if the buffer is non-null and status is zero */ if((ps_buf_mgr->au4_status[id] == 0) && (ps_buf_mgr->apv_ptr[id])) { *pi4_buf_id = id; /* DEC is set to 1 */ ps_buf_mgr->au4_status[id] = 1; pv_ret_ptr = ps_buf_mgr->apv_ptr[id]; break; } } ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), NULL); return pv_ret_ptr; } /** ******************************************************************************* * * @brief * Checks the buffer manager for free buffers available. * * @par Description: * Checks if there are any free buffers available * * @param[in] ps_buf_mgr * Pointer to the buffer manager * * @returns Returns 0 if available, -1 otherwise * * @remarks * None * ******************************************************************************* */ IH264_ERROR_T ih264_buf_mgr_check_free(buf_mgr_t *ps_buf_mgr) { WORD32 id; IH264_ERROR_T ret = IH264_SUCCESS; IH264_ERROR_T rettmp = IH264_SUCCESS; rettmp = ih264_buf_mgr_lock(ps_buf_mgr); RETURN_IF((rettmp != IH264_SUCCESS), ret); ret = IH264_FAIL; for(id = 0; id < ps_buf_mgr->i4_active_buf_cnt; id++) { if((ps_buf_mgr->au4_status[id] == 0) && (ps_buf_mgr->apv_ptr[id])) { ret = IH264_SUCCESS; break; } } rettmp = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((rettmp != IH264_SUCCESS), ret); return ret; } /** ******************************************************************************* * * @brief * Resets the status bits. * * @par Description: * resets the status bits that the mask contains (status corresponding to * the id) * * @param[in] ps_buf_mgr * Pointer to the buffer manager * * @param[in] buf_id * ID of the buffer status to be released * * @param[in] mask * Contains the bits that are to be reset * * @returns 0 if success, -1 otherwise * * @remarks * None * ******************************************************************************* */ IH264_ERROR_T ih264_buf_mgr_release(buf_mgr_t *ps_buf_mgr, WORD32 buf_id, UWORD32 mask) { IH264_ERROR_T ret = IH264_SUCCESS; ret = ih264_buf_mgr_lock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); /* If the given id is pointing to an id which is not yet added */ if(buf_id >= ps_buf_mgr->i4_active_buf_cnt) { ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return IH264_FAIL; } ps_buf_mgr->au4_status[buf_id] &= ~mask; /* If both the REF and DISP are zero, DEC is set to zero */ if(ps_buf_mgr->au4_status[buf_id] == 1) { ps_buf_mgr->au4_status[buf_id] = 0; } ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return ret; } /** ******************************************************************************* * * @brief * Sets the status bit. * * @par Description: * sets the status bits that the mask contains (status corresponding to the * id) * * * @param[in] ps_buf_mgr * Pointer to the buffer manager * * @param[in] buf_id * ID of the buffer whose status needs to be modified * * * @param[in] mask * Contains the bits that are to be set * * @returns 0 if success, -1 otherwise * * @remarks * None * ******************************************************************************* */ IH264_ERROR_T ih264_buf_mgr_set_status(buf_mgr_t *ps_buf_mgr, WORD32 buf_id, UWORD32 mask) { IH264_ERROR_T ret = IH264_SUCCESS; ret = ih264_buf_mgr_lock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); if(buf_id >= ps_buf_mgr->i4_active_buf_cnt) { ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return IH264_FAIL; } if((ps_buf_mgr->au4_status[buf_id] & mask) != 0) { ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return IH264_FAIL; } ps_buf_mgr->au4_status[buf_id] |= mask; ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return ret; } /** ******************************************************************************* * * @brief * Returns the status of the buffer. * * @par Description: * Returns the status of the buffer corresponding to the id * * @param[in] ps_buf_mgr * Pointer to the buffer manager * * @param[in] buf_id * ID of the buffer status required * * @returns Status of the buffer corresponding to the id * * @remarks * None * ******************************************************************************* */ WORD32 ih264_buf_mgr_get_status( buf_mgr_t *ps_buf_mgr, WORD32 buf_id ) { IH264_ERROR_T ret = IH264_SUCCESS; UWORD32 status; ret = ih264_buf_mgr_lock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); status = ps_buf_mgr->au4_status[buf_id]; ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return status; } /** ******************************************************************************* * * @brief * Gets the buffer from the buffer manager * * @par Description: * Returns the pointer to the buffer corresponding to the id * * @param[in] ps_buf_mgr * Pointer to the buffer manager * * @param[in] buf_id * ID of the buffer required * * @returns Pointer to the buffer required * * @remarks * None * ******************************************************************************* */ void* ih264_buf_mgr_get_buf(buf_mgr_t *ps_buf_mgr, WORD32 buf_id) { IH264_ERROR_T ret = IH264_SUCCESS; void *pv_buf; ret = ih264_buf_mgr_lock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), NULL); pv_buf = ps_buf_mgr->apv_ptr[buf_id]; ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), NULL); return pv_buf; } /** ******************************************************************************* * * @brief * Gets the buffer id from the buffer manager if the buffer is added to the * buffer manager * * @par Description: * Returns the buffer id corresponding to the given buffer if it exists * * @param[in] ps_buf_mgr * Pointer to the buffer manager * * @param[in] pv_buf * Pointer to the buffer * * @returns Buffer id if exists, else -1 * * @remarks * None * ******************************************************************************* */ WORD32 ih264_buf_mgr_get_bufid(buf_mgr_t *ps_buf_mgr, void *pv_buf) { WORD32 id; WORD32 buf_id = -1; IH264_ERROR_T ret = IH264_SUCCESS; ret = ih264_buf_mgr_lock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); for(id = 0; id < ps_buf_mgr->i4_active_buf_cnt; id++) { if(ps_buf_mgr->apv_ptr[id] == pv_buf) { buf_id = id; break; } } ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return buf_id; } /** ******************************************************************************* * * @brief * Gets the no.of active buffer * * @par Description: * Return the number of active buffers in the buffer manager * * @param[in] ps_buf_mgr * Pointer to the buffer manager * * @returns number of active buffers * * @remarks * None * ******************************************************************************* */ UWORD32 ih264_buf_mgr_get_num_active_buf(buf_mgr_t *ps_buf_mgr) { UWORD32 u4_buf_cnt; IH264_ERROR_T ret = IH264_SUCCESS; u4_buf_cnt = 0; ret = ih264_buf_mgr_lock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); u4_buf_cnt = ps_buf_mgr->i4_active_buf_cnt; ret = ih264_buf_mgr_unlock(ps_buf_mgr); RETURN_IF((ret != IH264_SUCCESS), ret); return u4_buf_cnt; } ================================================ FILE: dependencies/ih264d/common/ih264_buf_mgr.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_buf_mgr.h * * @brief * Function declarations used for buffer management * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_BUF_MGR_H_ #define _IH264_BUF_MGR_H_ #define BUF_MGR_MAX_CNT 64 /** Flag for current encoding decoder */ #define BUF_MGR_CODEC (1 << 1) /** Flag for reference status */ #define BUF_MGR_REF (1 << 2) /** Flag for I/O - Display/output in case of decoder, capture/input in case of encoder */ #define BUF_MGR_IO (1 << 3) typedef struct { /** * Mutex used to keep the functions thread-safe */ void *pv_mutex; /** * max_buf_cnt */ WORD32 i4_max_buf_cnt; /** * active_buf_cnt */ WORD32 i4_active_buf_cnt; /** * au4_status[BUF_MGR_MAX_CNT] */ UWORD32 au4_status[BUF_MGR_MAX_CNT]; /* The last three bit of status are: */ /* Bit 0 - IN USE */ /* Bit 1 - CODEC */ /* Bit 2 - REF */ /* Bit 3 - DISP/IO/RECON */ void *apv_ptr[BUF_MGR_MAX_CNT]; }buf_mgr_t; // Returns size of the buffer manager context WORD32 ih264_buf_mgr_size(void); //Free buffer manager IH264_ERROR_T ih264_buf_mgr_free(buf_mgr_t *ps_buf_mgr); // Initializes the buffer API structure void *ih264_buf_mgr_init(void *pv_buf); // Add buffer to buffer manager. 0: success, -1: fail (u4_active_buf_cnt has reached u4_max_buf_cnt) IH264_ERROR_T ih264_buf_mgr_add(buf_mgr_t *ps_buf_mgr, void *pv_ptr, WORD32 buf_id); // this function will set the buffer status to DEC void* ih264_buf_mgr_get_next_free(buf_mgr_t *ps_buf_mgr, WORD32 *pi4_id); // this function will check if there are any free buffers IH264_ERROR_T ih264_buf_mgr_check_free(buf_mgr_t *ps_buf_mgr); // mask will have who released it: DISP:REF:DEC IH264_ERROR_T ih264_buf_mgr_release(buf_mgr_t *ps_buf_mgr, WORD32 id, UWORD32 mask); // sets the status to one or all of DISP:REF:DEC IH264_ERROR_T ih264_buf_mgr_set_status(buf_mgr_t *ps_buf_mgr, WORD32 id, UWORD32 mask); // Gets status of the buffer WORD32 ih264_buf_mgr_get_status(buf_mgr_t *ps_buf_mgr, WORD32 id); // pass the ID - buffer will be returned void* ih264_buf_mgr_get_buf(buf_mgr_t *ps_buf_mgr, WORD32 id); //Pass buffer to get ID WORD32 ih264_buf_mgr_get_bufid(buf_mgr_t *ps_buf_mgr, void *pv_buf); // will return number of active buffers UWORD32 ih264_buf_mgr_get_num_active_buf(buf_mgr_t *ps_buf_mgr); #endif /* _IH264_BUF_MGR_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_cabac_tables.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ****************************************************************************** * @file * ih264_cabac_tables.c * * @brief * This file contains H264 cabac tables for init contexts, rlps and * cabac state transitions * * @author * Ittiam * * @par List of Tables * - gau4_ih264_cabac_table[][] * - gau1_ih264_cabac_ctxt_init_table[][][] * ****************************************************************************** */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* User include files */ #include "ih264_typedefs.h" #include "ih264_cabac_tables.h" /*****************************************************************************/ /* Extern global definitions */ /*****************************************************************************/ /*****************************************************************************/ /* CABAC TABLES */ /*****************************************************************************/ /*combined table :guc_RTAB,NextStateLPS,NextStateMPS input(combined_state): bits 0-5: state bits 6:mps output bits 0-7:rangeTabLPS bits 8-14 :combined_next_state_if_mps bits 15 -21:combined_next_state_if_lps */ const UWORD32 gau4_ih264_cabac_table[128][4] = { { 2097536, 2097584, 2097616, 2097648 }, { 640, 679, 709, 739 }, { 33664, 33694, 33723, 33752 }, { 66683, 66710, 66738, 66765 }, { 66932, 66958, 66985, 67011 }, { 132719, 132743, 132768, 132793 }, { 132969, 132992, 133016, 133039 }, { 165988, 166010, 166032, 166054 }, { 199007, 199028, 199049, 199070 }, { 232026, 232046, 232066, 232086 }, { 265045, 265064, 265083, 265102 }, { 298065, 298083, 298101, 298119 }, { 298317, 298334, 298351, 298368 }, { 364105, 364121, 364137, 364154 }, { 364357, 364373, 364388, 364404 }, { 397378, 397392, 397407, 397422 }, { 430398, 430412, 430426, 430440 }, { 430651, 430664, 430678, 430691 }, { 496440, 496453, 496465, 496478 }, { 496693, 496705, 496717, 496729 }, { 529715, 529726, 529737, 529749 }, { 529968, 529979, 529989, 530000 }, { 595758, 595768, 595778, 595788 }, { 596011, 596021, 596031, 596040 }, { 629033, 629042, 629051, 629061 }, { 629287, 629296, 629304, 629313 }, { 695077, 695085, 695094, 695102 }, { 695331, 695339, 695347, 695355 }, { 728353, 728361, 728368, 728376 }, { 728608, 728615, 728622, 728629 }, { 761630, 761637, 761643, 761650 }, { 794653, 794659, 794665, 794672 }, { 794907, 794913, 794919, 794925 }, { 827930, 827935, 827941, 827947 }, { 860952, 860958, 860963, 860969 }, { 861207, 861212, 861217, 861223 }, { 894230, 894235, 894240, 894245 }, { 894485, 894490, 894494, 894499 }, { 927508, 927512, 927517, 927521 }, { 960531, 960535, 960539, 960543 }, { 960786, 960790, 960794, 960798 }, { 993809, 993813, 993817, 993820 }, { 994064, 994068, 994071, 994075 }, { 994319, 994323, 994326, 994329 }, { 1027342, 1027346, 1027349, 1027352 }, { 1060366, 1060369, 1060372, 1060375 }, { 1060621, 1060624, 1060627, 1060630 }, { 1093644, 1093647, 1093650, 1093653 }, { 1093900, 1093902, 1093905, 1093908 }, { 1094155, 1094158, 1094160, 1094163 }, { 1127179, 1127181, 1127183, 1127186 }, { 1127434, 1127436, 1127439, 1127441 }, { 1160458, 1160460, 1160462, 1160464 }, { 1160713, 1160715, 1160717, 1160719 }, { 1160969, 1160971, 1160972, 1160974 }, { 1193992, 1193994, 1193996, 1193998 }, { 1194248, 1194249, 1194251, 1194253 }, { 1194503, 1194505, 1194507, 1194508 }, { 1227527, 1227529, 1227530, 1227532 }, { 1227783, 1227784, 1227786, 1227787 }, { 1228038, 1228040, 1228041, 1228043 }, { 1261062, 1261063, 1261065, 1261066 }, { 1261062, 1261063, 1261064, 1261065 }, { 2080514, 2080514, 2080514, 2080514 }, { 16768, 16816, 16848, 16880 }, { 2114176, 2114215, 2114245, 2114275 }, { 2147200, 2147230, 2147259, 2147288 }, { 2180219, 2180246, 2180274, 2180301 }, { 2180468, 2180494, 2180521, 2180547 }, { 2246255, 2246279, 2246304, 2246329 }, { 2246505, 2246528, 2246552, 2246575 }, { 2279524, 2279546, 2279568, 2279590 }, { 2312543, 2312564, 2312585, 2312606 }, { 2345562, 2345582, 2345602, 2345622 }, { 2378581, 2378600, 2378619, 2378638 }, { 2411601, 2411619, 2411637, 2411655 }, { 2411853, 2411870, 2411887, 2411904 }, { 2477641, 2477657, 2477673, 2477690 }, { 2477893, 2477909, 2477924, 2477940 }, { 2510914, 2510928, 2510943, 2510958 }, { 2543934, 2543948, 2543962, 2543976 }, { 2544187, 2544200, 2544214, 2544227 }, { 2609976, 2609989, 2610001, 2610014 }, { 2610229, 2610241, 2610253, 2610265 }, { 2643251, 2643262, 2643273, 2643285 }, { 2643504, 2643515, 2643525, 2643536 }, { 2709294, 2709304, 2709314, 2709324 }, { 2709547, 2709557, 2709567, 2709576 }, { 2742569, 2742578, 2742587, 2742597 }, { 2742823, 2742832, 2742840, 2742849 }, { 2808613, 2808621, 2808630, 2808638 }, { 2808867, 2808875, 2808883, 2808891 }, { 2841889, 2841897, 2841904, 2841912 }, { 2842144, 2842151, 2842158, 2842165 }, { 2875166, 2875173, 2875179, 2875186 }, { 2908189, 2908195, 2908201, 2908208 }, { 2908443, 2908449, 2908455, 2908461 }, { 2941466, 2941471, 2941477, 2941483 }, { 2974488, 2974494, 2974499, 2974505 }, { 2974743, 2974748, 2974753, 2974759 }, { 3007766, 3007771, 3007776, 3007781 }, { 3008021, 3008026, 3008030, 3008035 }, { 3041044, 3041048, 3041053, 3041057 }, { 3074067, 3074071, 3074075, 3074079 }, { 3074322, 3074326, 3074330, 3074334 }, { 3107345, 3107349, 3107353, 3107356 }, { 3107600, 3107604, 3107607, 3107611 }, { 3107855, 3107859, 3107862, 3107865 }, { 3140878, 3140882, 3140885, 3140888 }, { 3173902, 3173905, 3173908, 3173911 }, { 3174157, 3174160, 3174163, 3174166 }, { 3207180, 3207183, 3207186, 3207189 }, { 3207436, 3207438, 3207441, 3207444 }, { 3207691, 3207694, 3207696, 3207699 }, { 3240715, 3240717, 3240719, 3240722 }, { 3240970, 3240972, 3240975, 3240977 }, { 3273994, 3273996, 3273998, 3274000 }, { 3274249, 3274251, 3274253, 3274255 }, { 3274505, 3274507, 3274508, 3274510 }, { 3307528, 3307530, 3307532, 3307534 }, { 3307784, 3307785, 3307787, 3307789 }, { 3308039, 3308041, 3308043, 3308044 }, { 3341063, 3341065, 3341066, 3341068 }, { 3341319, 3341320, 3341322, 3341323 }, { 3341574, 3341576, 3341577, 3341579 }, { 3374598, 3374599, 3374601, 3374602 }, { 3374598, 3374599, 3374600, 3374601 }, { 4194050, 4194050, 4194050, 4194050 }, }; /*****************************************************************************/ /* Global Variable Initialization */ /*****************************************************************************/ const UWORD8 gau1_ih264_cabac_ctxt_init_table[NUM_CAB_INIT_IDC_PLUS_ONE][QP_RANGE][NUM_CABAC_CTXTS] = { { { 62, 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 30, 61, 62, 54, 14, 118, 6, 78, 65, 1, 14, 73, 13, 64, 20, 62, 67, 90, 104, 126, 104, 67, 78, 65, 1, 86, 95, 2, 18, 69, 81, 96, 8, 67, 86, 88, 5, 76, 94, 9, 69, 81, 88, 67, 74, 74, 80, 72, 5, 22, 0, 0, 0, 83, 86, 97, 72, 22, 1, 18, 78, 96, 126, 98, 101, 67, 82, 94, 83, 110, 91, 102, 93, 126, 92, 89, 96, 108, 17, 65, 6, 93, 74, 92, 87, 126, 9, 3, 4, 69, 15, 68, 69, 88, 85, 78, 75, 77, 9, 13, 68, 13, 21, 81, 0, 70, 67, 6, 76, 28, 64, 2, 28, 38, 39, 34, 27, 93, 73, 73, 17, 14, 100, 10, 10, 10, 2, 7, 7, 0, 3, 1, 6, 69, 6, 24, 12, 68, 64, 2, 0, 13, 24, 19, 11, 15, 3, 4, 4, 30, 19, 20, 78, 3, 69, 35, 23, 19, 14, 17, 19, 12, 16, 24, 1, 17, 9, 9, 5, 0, 12, 6, 10, 11, 8, 18, 27, 10, 82, 8, 78, 17, 32, 84, 56, 62, 60, 59, 62, 62, 57, 57, 54, 44, 36, 33, 43, 29, 70, 67, 4, 67, 33, 31, 28, 34, 32, 25, 20, 22, 0, 4, 64, 94, 89, 108, 76, 19, 18, 11, 64, 4, 70, 75, 82, 102, 77, 39, 21, 15, 8, 4, 71, 83, 87, 119, 5, 34, 27, 25, 20, 8, 5, 64, 74, 90, 70, 34, 32, 21, 4, 5, 72, 81, 97, 5, 58, 49, 45, 36, 23, 5, 70, 79, 85, 62, 106, 106, 87, 114, 110, 98, 110, 106, 103, 107, 108, 112, 96, 95, 91, 93, 94, 86, 67, 80, 85, 70, 3, 5, 2, 13, 13, 14, 9, 22, 17, 12, 14, 11, 22, 16, 8, 22, 19, 13, 10, 14, 0, 64, 69, 4, 70, 19, 32, 20, 10, 29, 25, 11, 23, 31, 19, 25, 13, 6, 20, 52, 49, 52, 52, 54, 62, 62, 62, 62, 62, 62, 62, 62, 62, 34, 62, 62, 62, 62, 62, 62, 54, 37, 36, 6, 82, 75, 97, 125, 62, 62, 62, 57, 55, 53, 41, 44, 31, 32, 22, 19, 16, 65, 71, 3, 0, 65, 39, 43, 40, 31, 40, 39, 23, 31, 34, 21, 6, 10, 2, 86, 23, 12, 4, 79, 71, 69, 70, 66, 68, 73, 69, 70, 67, 1, 70, 66, 65, 0, 62, 62, 62, 62, 62, 60, 54, 36, 4, 66, 28, 21, 18, 15, 7, 3, 1, 66, 76, 85, 81, 77, 81, 80, 73, 74, 83, 71, 67, 2, 66, 66, 4, 4, 62, 62, 62, 62, 61, 57, 46, 29, 1 }, { 62, 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 29, 60, 62, 54, 14, 115, 6, 77, 64, 1, 14, 72, 12, 65, 20, 62, 68, 91, 104, 124, 102, 67, 77, 64, 1, 85, 93, 3, 18, 68, 80, 95, 8, 67, 85, 88, 5, 75, 93, 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 18, 77, 95, 124, 96, 99, 65, 80, 92, 82, 108, 89, 100, 92, 125, 91, 88, 95, 107, 18, 64, 7, 92, 73, 91, 86, 124, 9, 3, 4, 69, 16, 68, 68, 87, 84, 77, 74, 76, 9, 13, 67, 13, 21, 80, 0, 69, 67, 6, 75, 28, 64, 2, 28, 37, 39, 34, 27, 92, 72, 72, 17, 14, 99, 10, 10, 10, 3, 7, 7, 1, 4, 2, 6, 68, 6, 24, 12, 68, 64, 2, 0, 13, 23, 19, 11, 15, 4, 5, 4, 29, 19, 20, 77, 3, 69, 35, 23, 19, 14, 17, 19, 12, 16, 24, 1, 17, 9, 9, 5, 0, 12, 6, 10, 11, 8, 18, 27, 10, 81, 8, 77, 17, 31, 83, 55, 62, 59, 58, 61, 62, 56, 56, 52, 43, 35, 32, 41, 28, 71, 67, 4, 67, 32, 30, 27, 33, 31, 24, 19, 21, 0, 4, 64, 93, 88, 107, 75, 20, 18, 11, 0, 5, 69, 74, 81, 100, 76, 39, 21, 15, 8, 5, 70, 82, 86, 117, 5, 35, 28, 25, 20, 9, 5, 64, 73, 89, 70, 35, 32, 21, 4, 6, 71, 80, 96, 5, 58, 49, 45, 36, 23, 5, 69, 78, 84, 62, 105, 105, 86, 112, 108, 97, 108, 104, 101, 105, 106, 110, 95, 94, 90, 92, 92, 85, 67, 79, 84, 69, 3, 5, 2, 13, 13, 13, 8, 22, 17, 13, 14, 11, 22, 16, 8, 22, 19, 13, 10, 14, 0, 64, 68, 5, 70, 19, 32, 20, 10, 29, 25, 12, 23, 30, 19, 25, 13, 6, 19, 52, 49, 52, 51, 53, 62, 62, 62, 62, 62, 62, 62, 62, 62, 33, 62, 62, 62, 62, 62, 62, 53, 36, 35, 6, 81, 74, 95, 122, 62, 62, 62, 56, 53, 52, 40, 42, 30, 31, 21, 18, 15, 66, 71, 3, 0, 66, 38, 42, 39, 30, 39, 38, 22, 30, 33, 20, 5, 9, 1, 86, 23, 12, 4, 78, 70, 68, 69, 65, 67, 71, 68, 69, 66, 3, 68, 65, 0, 2, 62, 62, 62, 62, 62, 58, 51, 34, 2, 65, 29, 22, 19, 16, 8, 4, 2, 65, 75, 84, 80, 76, 80, 78, 71, 73, 82, 70, 66, 3, 65, 65, 4, 4, 62, 62, 62, 62, 58, 54, 43, 26, 64 }, { 62, 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 28, 59, 61, 54, 14, 113, 6, 76, 0, 1, 13, 72, 11, 66, 19, 60, 70, 92, 105, 121, 101, 67, 76, 0, 1, 85, 92, 3, 17, 68, 80, 94, 8, 67, 85, 88, 5, 75, 92, 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 18, 77, 95, 122, 94, 97, 64, 78, 91, 81, 107, 88, 99, 91, 123, 91, 88, 95, 106, 18, 64, 7, 91, 73, 90, 86, 123, 9, 3, 4, 69, 16, 68, 68, 87, 84, 77, 74, 76, 9, 13, 67, 13, 21, 80, 0, 69, 67, 6, 75, 27, 64, 2, 27, 36, 38, 33, 26, 91, 72, 72, 16, 13, 99, 9, 10, 10, 3, 7, 7, 2, 4, 2, 6, 68, 6, 23, 12, 69, 64, 2, 64, 13, 22, 19, 11, 14, 4, 5, 4, 28, 19, 19, 77, 3, 70, 34, 23, 19, 14, 17, 19, 12, 16, 24, 1, 17, 9, 9, 5, 0, 12, 6, 10, 11, 8, 17, 26, 9, 81, 8, 77, 16, 30, 83, 53, 62, 57, 56, 59, 60, 54, 54, 50, 41, 33, 30, 39, 26, 72, 67, 4, 68, 31, 29, 26, 32, 29, 23, 18, 20, 64, 3, 65, 93, 88, 106, 75, 20, 18, 11, 0, 5, 69, 74, 81, 99, 75, 39, 21, 15, 8, 5, 70, 81, 85, 115, 5, 35, 28, 25, 20, 9, 5, 64, 73, 88, 70, 35, 32, 21, 4, 6, 71, 80, 95, 5, 57, 48, 44, 35, 23, 5, 69, 78, 84, 62, 104, 104, 85, 111, 107, 96, 107, 103, 100, 104, 105, 108, 94, 93, 90, 91, 91, 85, 68, 79, 83, 69, 3, 4, 2, 12, 12, 12, 7, 21, 17, 13, 14, 10, 21, 16, 8, 21, 18, 13, 10, 13, 0, 64, 68, 5, 70, 18, 31, 19, 10, 28, 24, 12, 22, 29, 19, 25, 12, 5, 17, 51, 48, 51, 50, 52, 62, 62, 62, 62, 62, 62, 62, 62, 62, 32, 62, 62, 62, 62, 62, 62, 51, 35, 34, 6, 80, 74, 94, 120, 60, 60, 62, 54, 51, 50, 38, 40, 29, 29, 20, 16, 14, 67, 72, 2, 0, 67, 37, 41, 37, 28, 37, 36, 21, 28, 31, 19, 4, 8, 0, 87, 22, 11, 3, 78, 70, 68, 68, 65, 66, 70, 67, 68, 65, 4, 67, 64, 1, 3, 62, 62, 62, 62, 60, 55, 48, 31, 0, 65, 29, 22, 19, 16, 9, 4, 2, 65, 75, 84, 80, 75, 80, 77, 70, 73, 81, 69, 65, 3, 65, 64, 4, 4, 62, 62, 62, 60, 55, 50, 39, 23, 67 }, { 62, 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 26, 57, 60, 54, 14, 111, 6, 75, 1, 1, 12, 72, 10, 67, 19, 58, 71, 93, 105, 118, 100, 67, 75, 1, 1, 84, 91, 4, 17, 68, 79, 93, 7, 68, 85, 88, 5, 75, 92, 9, 69, 80, 88, 65, 73, 73, 79, 70, 5, 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 18, 77, 95, 120, 92, 96, 1, 76, 90, 80, 105, 87, 98, 90, 121, 90, 88, 94, 105, 18, 64, 7, 91, 73, 90, 85, 121, 9, 2, 3, 70, 16, 68, 68, 86, 84, 76, 74, 75, 9, 13, 67, 13, 20, 80, 0, 69, 67, 6, 75, 26, 64, 2, 26, 35, 37, 32, 25, 91, 71, 72, 15, 13, 98, 9, 10, 10, 3, 7, 7, 3, 4, 2, 6, 67, 6, 22, 12, 70, 64, 2, 64, 12, 21, 19, 11, 13, 4, 5, 4, 26, 19, 18, 77, 3, 70, 33, 23, 19, 14, 17, 19, 12, 16, 24, 1, 16, 9, 9, 5, 0, 11, 5, 9, 10, 7, 16, 25, 9, 81, 7, 77, 15, 28, 83, 52, 62, 55, 54, 57, 58, 52, 52, 48, 39, 32, 29, 37, 24, 73, 67, 4, 68, 30, 28, 25, 30, 28, 21, 17, 19, 65, 3, 65, 93, 88, 106, 74, 20, 18, 11, 0, 5, 69, 74, 80, 98, 75, 39, 21, 15, 8, 6, 69, 80, 84, 113, 5, 35, 28, 25, 20, 10, 5, 64, 73, 88, 70, 35, 32, 20, 4, 6, 71, 80, 94, 5, 57, 48, 43, 34, 23, 5, 69, 77, 83, 62, 103, 103, 85, 110, 106, 95, 105, 102, 99, 103, 103, 107, 94, 92, 90, 91, 89, 85, 68, 79, 83, 69, 2, 4, 2, 11, 11, 11, 6, 21, 16, 13, 13, 10, 21, 15, 8, 20, 18, 12, 10, 12, 0, 65, 68, 5, 71, 18, 31, 18, 10, 27, 24, 12, 21, 28, 18, 24, 11, 5, 16, 50, 47, 51, 49, 51, 61, 62, 62, 62, 62, 62, 62, 62, 62, 31, 62, 62, 62, 62, 62, 62, 49, 34, 33, 6, 79, 74, 93, 118, 58, 58, 62, 52, 49, 48, 37, 38, 27, 28, 19, 15, 12, 68, 73, 2, 64, 68, 36, 39, 36, 26, 35, 34, 19, 27, 29, 17, 3, 6, 65, 88, 21, 10, 2, 78, 69, 68, 68, 64, 66, 69, 66, 67, 64, 5, 66, 0, 3, 4, 62, 62, 62, 62, 58, 52, 45, 28, 65, 64, 30, 23, 20, 16, 10, 5, 2, 64, 74, 84, 79, 75, 79, 76, 69, 73, 81, 69, 65, 3, 64, 0, 4, 4, 62, 62, 62, 57, 52, 46, 35, 19, 69 }, { 62, 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 25, 56, 58, 54, 14, 108, 5, 74, 1, 1, 11, 72, 9, 68, 18, 56, 73, 94, 106, 115, 99, 67, 74, 1, 1, 84, 90, 4, 16, 68, 79, 93, 7, 68, 84, 88, 5, 75, 91, 8, 70, 80, 88, 65, 72, 73, 78, 70, 5, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 18, 77, 95, 119, 91, 94, 2, 75, 89, 79, 104, 85, 97, 89, 119, 90, 87, 94, 104, 18, 64, 7, 90, 73, 89, 85, 120, 8, 2, 3, 70, 16, 68, 68, 86, 84, 76, 74, 75, 9, 12, 67, 13, 20, 80, 0, 69, 67, 6, 75, 26, 65, 2, 26, 34, 36, 31, 24, 90, 71, 72, 14, 12, 98, 8, 10, 9, 3, 7, 7, 4, 5, 2, 5, 67, 5, 21, 11, 71, 64, 2, 65, 12, 20, 18, 10, 13, 5, 5, 4, 25, 18, 17, 77, 3, 71, 33, 23, 19, 14, 17, 19, 12, 16, 23, 1, 16, 9, 9, 5, 64, 11, 5, 9, 10, 7, 16, 24, 8, 81, 7, 77, 14, 27, 83, 50, 62, 53, 52, 55, 56, 50, 50, 46, 37, 30, 27, 34, 22, 74, 67, 3, 69, 29, 27, 24, 29, 26, 20, 16, 17, 65, 2, 66, 93, 88, 105, 74, 20, 18, 11, 0, 5, 69, 74, 80, 97, 74, 39, 21, 15, 8, 6, 69, 80, 84, 111, 5, 35, 28, 25, 20, 10, 5, 64, 73, 87, 70, 35, 31, 20, 4, 6, 71, 80, 94, 5, 56, 47, 42, 33, 23, 5, 69, 77, 83, 62, 102, 102, 84, 108, 105, 94, 104, 100, 98, 101, 102, 105, 93, 92, 89, 90, 88, 84, 69, 79, 82, 69, 2, 3, 1, 10, 10, 10, 5, 20, 16, 13, 13, 9, 20, 15, 8, 19, 17, 12, 9, 11, 64, 65, 68, 5, 71, 17, 30, 17, 10, 26, 23, 12, 20, 27, 18, 24, 10, 4, 14, 49, 47, 50, 48, 49, 60, 62, 62, 62, 62, 62, 62, 62, 62, 29, 62, 62, 62, 62, 62, 62, 47, 33, 31, 6, 78, 73, 92, 116, 57, 56, 60, 51, 47, 46, 35, 36, 26, 26, 17, 13, 11, 69, 74, 1, 64, 69, 34, 38, 34, 25, 33, 32, 18, 25, 27, 16, 2, 5, 66, 88, 20, 10, 1, 78, 69, 67, 67, 64, 65, 68, 66, 66, 0, 6, 65, 1, 4, 5, 62, 62, 62, 61, 55, 49, 42, 25, 68, 64, 30, 23, 20, 17, 10, 5, 3, 64, 74, 83, 79, 74, 79, 75, 68, 73, 80, 68, 64, 3, 64, 1, 4, 4, 62, 62, 61, 54, 49, 42, 31, 16, 72 }, { 62, 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 23, 54, 57, 54, 14, 106, 5, 73, 2, 1, 11, 71, 8, 69, 18, 54, 75, 95, 106, 112, 97, 67, 73, 2, 1, 84, 89, 4, 16, 68, 79, 92, 7, 69, 84, 88, 5, 75, 90, 8, 70, 80, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 18, 76, 95, 117, 89, 93, 4, 73, 87, 78, 103, 84, 96, 88, 117, 89, 87, 93, 103, 18, 64, 7, 90, 73, 89, 84, 118, 8, 2, 3, 70, 16, 68, 67, 85, 84, 76, 74, 74, 9, 12, 67, 13, 20, 79, 0, 68, 67, 6, 75, 25, 65, 2, 25, 33, 36, 30, 23, 89, 70, 72, 13, 12, 97, 8, 10, 9, 3, 7, 7, 5, 5, 2, 5, 67, 5, 20, 11, 72, 64, 2, 65, 11, 19, 18, 10, 12, 5, 5, 4, 24, 18, 16, 77, 3, 71, 32, 23, 19, 14, 17, 19, 12, 16, 23, 1, 16, 9, 9, 5, 64, 11, 5, 8, 10, 7, 15, 23, 8, 81, 6, 77, 13, 26, 83, 49, 61, 52, 51, 53, 54, 48, 48, 44, 35, 28, 25, 32, 21, 75, 67, 3, 69, 28, 26, 23, 28, 25, 18, 15, 16, 66, 2, 66, 93, 88, 105, 74, 20, 18, 11, 0, 5, 68, 73, 79, 96, 74, 39, 21, 15, 8, 6, 68, 79, 83, 109, 5, 35, 28, 25, 20, 10, 5, 64, 73, 86, 70, 36, 31, 19, 4, 6, 71, 80, 93, 5, 56, 46, 41, 32, 23, 5, 69, 77, 82, 62, 101, 101, 83, 107, 104, 93, 103, 99, 97, 100, 100, 103, 92, 91, 89, 90, 87, 84, 69, 78, 81, 69, 1, 3, 1, 10, 9, 9, 4, 19, 15, 13, 12, 9, 20, 15, 8, 18, 16, 12, 9, 10, 64, 65, 68, 5, 71, 16, 30, 17, 10, 25, 22, 12, 19, 26, 17, 23, 9, 3, 12, 48, 46, 50, 47, 48, 58, 62, 62, 62, 62, 62, 62, 62, 62, 28, 62, 62, 62, 62, 62, 61, 45, 32, 30, 6, 77, 73, 91, 114, 55, 55, 58, 49, 45, 44, 34, 34, 25, 24, 16, 11, 9, 70, 75, 1, 64, 70, 33, 36, 32, 23, 32, 31, 16, 24, 26, 14, 1, 4, 67, 89, 20, 9, 0, 77, 68, 67, 67, 0, 64, 67, 65, 65, 1, 8, 64, 2, 5, 7, 62, 62, 62, 58, 53, 46, 39, 22, 70, 64, 31, 24, 21, 17, 11, 5, 3, 0, 73, 83, 79, 73, 78, 74, 67, 72, 79, 68, 64, 3, 0, 2, 4, 4, 62, 62, 58, 51, 46, 39, 27, 12, 75 }, { 62, 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 22, 53, 56, 54, 14, 104, 5, 73, 3, 1, 10, 71, 7, 70, 17, 53, 76, 96, 107, 109, 96, 67, 73, 3, 1, 83, 88, 5, 15, 67, 78, 91, 6, 69, 84, 88, 5, 74, 90, 8, 70, 79, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 18, 76, 94, 115, 87, 91, 5, 71, 86, 77, 101, 83, 95, 88, 116, 89, 87, 93, 103, 19, 64, 7, 89, 72, 88, 84, 117, 8, 1, 2, 71, 16, 68, 67, 85, 84, 75, 74, 74, 9, 12, 66, 13, 19, 79, 0, 68, 67, 6, 75, 24, 65, 2, 24, 32, 35, 30, 23, 89, 70, 72, 13, 11, 97, 7, 10, 9, 3, 7, 7, 5, 5, 2, 5, 66, 5, 19, 11, 72, 65, 2, 66, 11, 18, 18, 10, 11, 5, 5, 4, 22, 18, 15, 77, 3, 72, 31, 23, 18, 14, 17, 19, 12, 16, 23, 1, 15, 9, 8, 5, 64, 10, 4, 8, 9, 6, 14, 22, 7, 81, 6, 76, 12, 24, 83, 47, 59, 50, 49, 51, 52, 46, 46, 42, 33, 27, 24, 30, 19, 76, 67, 3, 70, 27, 25, 22, 26, 23, 17, 14, 15, 67, 1, 67, 93, 88, 104, 73, 20, 18, 11, 1, 5, 68, 73, 79, 95, 73, 38, 21, 15, 8, 7, 68, 78, 82, 107, 5, 36, 28, 25, 20, 11, 5, 64, 72, 86, 70, 36, 31, 19, 4, 6, 70, 79, 92, 5, 55, 46, 40, 32, 23, 5, 68, 76, 82, 62, 101, 100, 83, 106, 103, 92, 101, 98, 96, 99, 99, 102, 92, 90, 89, 89, 85, 84, 70, 78, 81, 69, 1, 2, 1, 9, 8, 8, 3, 19, 15, 13, 12, 8, 19, 14, 8, 18, 16, 11, 9, 10, 64, 66, 68, 5, 72, 16, 29, 16, 9, 24, 22, 13, 19, 25, 17, 23, 9, 3, 11, 47, 45, 49, 46, 47, 57, 62, 62, 62, 62, 62, 62, 62, 61, 27, 62, 62, 62, 62, 62, 59, 43, 31, 29, 6, 76, 73, 89, 111, 53, 53, 56, 47, 43, 42, 32, 32, 23, 23, 15, 10, 8, 71, 76, 0, 65, 71, 32, 35, 31, 21, 30, 29, 15, 22, 24, 13, 64, 2, 69, 90, 19, 8, 64, 77, 68, 67, 66, 0, 64, 65, 64, 64, 2, 9, 1, 3, 7, 8, 62, 62, 60, 56, 50, 44, 36, 20, 72, 0, 31, 24, 21, 17, 12, 6, 3, 0, 73, 83, 78, 73, 78, 73, 66, 72, 79, 67, 0, 3, 0, 3, 4, 4, 62, 62, 56, 48, 42, 35, 24, 9, 77 }, { 62, 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 20, 51, 54, 54, 14, 101, 4, 72, 3, 1, 9, 71, 6, 71, 17, 51, 78, 97, 107, 106, 95, 67, 72, 3, 1, 83, 87, 5, 15, 67, 78, 91, 6, 70, 83, 88, 5, 74, 89, 7, 70, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 18, 76, 94, 114, 85, 90, 7, 69, 85, 76, 100, 81, 94, 87, 114, 88, 86, 92, 102, 19, 64, 7, 89, 72, 88, 83, 115, 7, 1, 2, 71, 16, 68, 67, 84, 84, 75, 74, 73, 9, 11, 66, 13, 19, 79, 0, 68, 67, 6, 75, 24, 65, 2, 24, 31, 34, 29, 22, 88, 69, 72, 12, 11, 96, 7, 10, 8, 3, 7, 7, 6, 6, 2, 5, 66, 5, 18, 11, 73, 65, 2, 66, 10, 17, 17, 10, 11, 6, 5, 4, 21, 17, 14, 77, 3, 72, 31, 23, 18, 14, 17, 19, 12, 16, 23, 1, 15, 9, 8, 5, 64, 10, 4, 7, 9, 6, 14, 21, 7, 81, 5, 76, 11, 23, 83, 46, 57, 48, 47, 49, 50, 44, 44, 40, 31, 25, 22, 27, 17, 77, 67, 2, 70, 26, 24, 21, 25, 22, 15, 13, 14, 67, 1, 67, 93, 88, 104, 73, 20, 18, 11, 1, 5, 68, 73, 78, 94, 73, 38, 21, 15, 8, 7, 67, 77, 82, 105, 5, 36, 28, 25, 20, 11, 5, 64, 72, 85, 70, 36, 30, 18, 4, 6, 70, 79, 92, 5, 55, 45, 39, 31, 23, 5, 68, 76, 81, 62, 100, 99, 82, 104, 102, 91, 100, 96, 95, 97, 97, 100, 91, 89, 88, 89, 84, 83, 70, 78, 80, 69, 0, 2, 0, 8, 7, 7, 2, 18, 14, 13, 11, 8, 19, 14, 8, 17, 15, 11, 8, 9, 64, 66, 68, 5, 72, 15, 29, 15, 9, 23, 21, 13, 18, 24, 16, 22, 8, 2, 9, 46, 45, 49, 45, 45, 55, 62, 62, 62, 62, 62, 62, 62, 59, 25, 62, 62, 62, 62, 62, 56, 41, 30, 28, 6, 75, 72, 88, 109, 52, 51, 54, 46, 41, 40, 31, 30, 22, 21, 13, 8, 6, 72, 77, 0, 65, 72, 30, 33, 29, 20, 28, 27, 13, 21, 22, 11, 65, 1, 70, 90, 18, 8, 65, 77, 67, 66, 66, 1, 0, 64, 0, 0, 3, 10, 2, 4, 8, 9, 62, 61, 58, 53, 48, 41, 33, 17, 74, 0, 32, 25, 22, 18, 13, 6, 4, 1, 72, 82, 78, 72, 77, 72, 65, 72, 78, 67, 0, 3, 1, 4, 4, 4, 62, 62, 53, 45, 39, 31, 20, 5, 80 }, { 62, 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 19, 50, 53, 54, 14, 99, 4, 71, 4, 1, 8, 71, 5, 73, 16, 49, 80, 98, 108, 104, 94, 67, 71, 4, 1, 83, 86, 5, 14, 67, 78, 90, 5, 70, 83, 89, 5, 74, 89, 7, 71, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, 78, 88, 97, 68, 16, 0, 18, 76, 94, 112, 84, 88, 8, 68, 84, 75, 99, 80, 93, 86, 112, 88, 86, 92, 101, 19, 64, 7, 88, 72, 87, 83, 114, 7, 0, 1, 72, 16, 68, 67, 84, 84, 75, 74, 73, 8, 11, 66, 13, 18, 79, 0, 68, 67, 5, 75, 23, 66, 2, 23, 29, 33, 28, 21, 88, 69, 72, 11, 10, 96, 6, 9, 8, 3, 7, 7, 7, 6, 2, 4, 66, 4, 17, 10, 74, 65, 2, 67, 10, 16, 17, 9, 10, 6, 5, 4, 19, 17, 13, 77, 3, 73, 30, 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, 9, 8, 4, 65, 9, 3, 7, 8, 5, 13, 20, 6, 81, 5, 76, 10, 21, 83, 44, 55, 46, 45, 47, 47, 42, 42, 38, 29, 23, 20, 25, 15, 78, 67, 2, 71, 25, 22, 19, 23, 20, 14, 11, 12, 68, 0, 68, 93, 88, 103, 73, 20, 18, 11, 1, 5, 68, 73, 78, 93, 72, 38, 21, 15, 8, 7, 67, 77, 81, 104, 5, 36, 28, 25, 19, 11, 5, 64, 72, 85, 70, 36, 30, 18, 4, 6, 70, 79, 91, 5, 54, 44, 38, 30, 22, 5, 68, 76, 81, 62, 99, 98, 82, 103, 101, 91, 99, 95, 94, 96, 96, 99, 91, 89, 88, 88, 83, 83, 71, 78, 80, 69, 0, 1, 0, 7, 6, 5, 1, 17, 14, 13, 11, 7, 18, 13, 7, 16, 14, 10, 8, 8, 65, 67, 68, 5, 73, 14, 28, 14, 9, 22, 20, 13, 17, 23, 16, 22, 7, 1, 7, 45, 44, 48, 43, 44, 54, 62, 62, 62, 62, 62, 62, 62, 56, 24, 62, 62, 62, 62, 61, 54, 39, 28, 26, 6, 75, 72, 87, 107, 50, 49, 52, 44, 38, 38, 29, 28, 20, 19, 12, 6, 5, 73, 78, 64, 66, 73, 29, 32, 27, 18, 26, 25, 12, 19, 20, 10, 66, 64, 72, 91, 17, 7, 66, 77, 67, 66, 65, 1, 0, 0, 0, 1, 4, 11, 3, 5, 9, 10, 61, 59, 56, 51, 45, 38, 30, 14, 77, 0, 32, 25, 22, 18, 13, 6, 4, 1, 72, 82, 78, 72, 77, 71, 64, 72, 78, 66, 1, 3, 1, 4, 4, 3, 62, 61, 51, 42, 36, 27, 16, 2, 83 }, { 62, 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 18, 49, 52, 54, 14, 97, 4, 70, 5, 1, 8, 70, 4, 74, 15, 47, 81, 99, 109, 101, 92, 67, 70, 5, 1, 82, 85, 6, 13, 67, 77, 89, 5, 70, 83, 89, 5, 74, 88, 7, 71, 79, 88, 0, 71, 71, 77, 68, 5, 22, 0, 0, 0, 77, 88, 97, 68, 15, 0, 18, 75, 94, 110, 82, 86, 9, 66, 82, 74, 97, 79, 91, 85, 110, 88, 86, 92, 100, 19, 64, 7, 87, 72, 86, 82, 113, 7, 0, 1, 72, 16, 68, 66, 83, 83, 74, 74, 73, 8, 11, 66, 13, 18, 78, 0, 67, 67, 5, 74, 22, 66, 2, 22, 28, 33, 27, 20, 87, 69, 71, 10, 9, 96, 5, 9, 8, 4, 7, 7, 8, 6, 2, 4, 65, 4, 17, 10, 75, 65, 2, 68, 10, 15, 17, 9, 9, 6, 5, 4, 18, 17, 13, 77, 3, 74, 29, 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, 9, 8, 4, 65, 9, 3, 7, 8, 5, 12, 20, 6, 81, 5, 76, 9, 20, 83, 42, 54, 45, 44, 45, 45, 41, 41, 36, 27, 22, 19, 23, 14, 79, 67, 2, 72, 24, 21, 18, 22, 19, 13, 10, 11, 69, 64, 69, 93, 87, 102, 72, 21, 18, 11, 1, 6, 67, 72, 77, 92, 71, 38, 21, 15, 8, 8, 67, 76, 80, 102, 5, 36, 28, 25, 19, 12, 5, 64, 72, 84, 70, 37, 30, 18, 4, 7, 70, 79, 90, 5, 54, 44, 38, 29, 22, 5, 68, 75, 80, 62, 98, 97, 81, 102, 99, 90, 97, 94, 92, 95, 95, 97, 90, 88, 88, 87, 81, 83, 72, 77, 79, 69, 0, 0, 0, 7, 5, 4, 0, 17, 14, 13, 11, 7, 17, 13, 7, 15, 14, 10, 8, 7, 65, 67, 67, 6, 73, 14, 27, 14, 9, 22, 20, 13, 16, 22, 16, 22, 6, 1, 6, 45, 43, 47, 42, 43, 53, 60, 60, 62, 62, 62, 62, 62, 54, 23, 62, 62, 62, 62, 58, 52, 38, 27, 25, 6, 74, 72, 86, 105, 48, 48, 50, 42, 36, 37, 28, 26, 19, 18, 11, 5, 4, 74, 78, 64, 66, 74, 28, 31, 26, 16, 25, 24, 11, 18, 19, 9, 67, 65, 73, 92, 17, 6, 66, 76, 67, 66, 64, 2, 1, 1, 1, 2, 5, 13, 4, 6, 11, 12, 60, 58, 54, 49, 42, 35, 27, 11, 79, 1, 32, 25, 23, 18, 14, 7, 4, 2, 71, 82, 77, 71, 77, 70, 1, 71, 77, 65, 2, 3, 2, 5, 4, 3, 62, 59, 49, 40, 33, 24, 12, 64, 85 }, { 62, 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 16, 47, 50, 54, 14, 94, 3, 69, 5, 1, 7, 70, 3, 75, 15, 45, 83, 100, 109, 98, 91, 67, 69, 5, 1, 82, 84, 6, 13, 67, 77, 89, 5, 71, 82, 89, 5, 74, 87, 6, 71, 79, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, 77, 88, 97, 67, 14, 0, 18, 75, 94, 109, 80, 85, 11, 64, 81, 73, 96, 77, 90, 84, 108, 87, 85, 91, 99, 19, 64, 7, 87, 72, 86, 82, 111, 6, 0, 1, 72, 16, 68, 66, 83, 83, 74, 74, 72, 8, 10, 66, 13, 18, 78, 0, 67, 67, 5, 74, 22, 66, 2, 22, 27, 32, 26, 19, 86, 68, 71, 9, 9, 95, 5, 9, 7, 4, 7, 7, 9, 7, 2, 4, 65, 4, 16, 10, 76, 65, 2, 68, 9, 14, 16, 9, 9, 7, 5, 4, 17, 16, 12, 77, 3, 74, 29, 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, 9, 8, 4, 65, 9, 3, 6, 8, 5, 12, 19, 5, 81, 4, 76, 8, 19, 83, 41, 52, 43, 42, 43, 43, 39, 39, 34, 25, 20, 17, 20, 12, 80, 67, 1, 72, 23, 20, 17, 21, 17, 11, 9, 10, 69, 64, 69, 93, 87, 102, 72, 21, 18, 11, 1, 6, 67, 72, 77, 91, 71, 38, 21, 15, 8, 8, 66, 75, 80, 100, 5, 36, 28, 25, 19, 12, 5, 64, 72, 83, 70, 37, 29, 17, 4, 7, 70, 79, 90, 5, 53, 43, 37, 28, 22, 5, 68, 75, 80, 62, 97, 96, 80, 100, 98, 89, 96, 92, 91, 93, 93, 95, 89, 87, 87, 87, 80, 82, 72, 77, 78, 69, 64, 0, 64, 6, 4, 3, 64, 16, 13, 13, 10, 6, 17, 13, 7, 14, 13, 10, 7, 6, 65, 67, 67, 6, 73, 13, 27, 13, 9, 21, 19, 13, 15, 21, 15, 21, 5, 0, 4, 44, 43, 47, 41, 41, 51, 58, 58, 62, 62, 62, 62, 62, 52, 21, 59, 62, 59, 62, 56, 49, 36, 26, 24, 6, 73, 71, 85, 103, 47, 46, 48, 41, 34, 35, 26, 24, 18, 16, 9, 3, 2, 75, 79, 65, 66, 75, 26, 29, 24, 15, 23, 22, 9, 16, 17, 7, 68, 66, 74, 92, 16, 6, 67, 76, 66, 65, 64, 2, 2, 2, 2, 3, 6, 14, 5, 7, 12, 13, 60, 56, 52, 46, 40, 32, 24, 8, 81, 1, 33, 26, 23, 19, 15, 7, 5, 2, 71, 81, 77, 70, 76, 69, 2, 71, 76, 65, 2, 3, 2, 6, 4, 3, 62, 57, 46, 37, 30, 20, 8, 68, 88 }, { 62, 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 15, 46, 49, 54, 14, 92, 3, 69, 6, 1, 6, 70, 2, 76, 14, 44, 84, 101, 110, 95, 90, 67, 69, 6, 1, 81, 83, 7, 12, 66, 76, 88, 4, 71, 82, 89, 5, 73, 87, 6, 71, 78, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, 76, 88, 97, 67, 14, 64, 18, 75, 93, 107, 78, 83, 12, 1, 80, 72, 94, 76, 89, 84, 107, 87, 85, 91, 99, 20, 64, 7, 86, 71, 85, 81, 110, 6, 64, 0, 73, 16, 68, 66, 82, 83, 73, 74, 72, 8, 10, 65, 13, 17, 78, 0, 67, 67, 5, 74, 21, 66, 2, 21, 26, 31, 26, 19, 86, 68, 71, 9, 8, 95, 4, 9, 7, 4, 7, 7, 9, 7, 2, 4, 64, 4, 15, 10, 76, 66, 2, 69, 9, 13, 16, 9, 8, 7, 5, 4, 15, 16, 11, 77, 3, 75, 28, 22, 17, 14, 17, 18, 11, 16, 22, 0, 13, 9, 7, 4, 65, 8, 2, 6, 7, 4, 11, 18, 5, 81, 4, 75, 7, 17, 83, 39, 50, 41, 40, 41, 41, 37, 37, 32, 23, 19, 16, 18, 10, 81, 67, 1, 73, 22, 19, 16, 19, 16, 10, 8, 9, 70, 65, 70, 93, 87, 101, 71, 21, 18, 11, 2, 6, 67, 72, 76, 90, 70, 37, 21, 15, 8, 9, 66, 74, 79, 98, 5, 37, 28, 25, 19, 13, 5, 64, 71, 83, 70, 37, 29, 17, 4, 7, 69, 78, 89, 5, 53, 43, 36, 28, 22, 5, 67, 74, 79, 62, 97, 95, 80, 99, 97, 88, 94, 91, 90, 92, 92, 94, 89, 86, 87, 86, 78, 82, 73, 77, 78, 69, 64, 64, 64, 5, 3, 2, 65, 16, 13, 13, 10, 6, 16, 12, 7, 14, 13, 9, 7, 6, 65, 68, 67, 6, 74, 13, 26, 12, 8, 20, 19, 14, 15, 20, 15, 21, 5, 0, 3, 43, 42, 46, 40, 40, 50, 56, 56, 61, 60, 62, 62, 60, 49, 20, 57, 62, 56, 62, 53, 47, 34, 25, 23, 6, 72, 71, 83, 100, 45, 44, 46, 39, 32, 33, 25, 22, 16, 15, 8, 2, 1, 76, 80, 65, 67, 76, 25, 28, 23, 13, 21, 20, 8, 15, 15, 6, 70, 68, 76, 93, 15, 5, 68, 76, 66, 65, 0, 3, 2, 4, 3, 4, 7, 15, 7, 8, 14, 14, 59, 55, 50, 44, 37, 30, 21, 6, 83, 2, 33, 26, 24, 19, 16, 8, 5, 3, 70, 81, 76, 70, 76, 68, 3, 71, 76, 64, 3, 3, 3, 7, 4, 3, 62, 55, 44, 34, 26, 16, 5, 71, 90 }, { 62, 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 13, 44, 48, 54, 14, 90, 3, 68, 7, 1, 5, 70, 1, 77, 14, 42, 86, 102, 110, 92, 89, 67, 68, 7, 1, 81, 82, 7, 12, 66, 76, 87, 4, 72, 82, 89, 5, 73, 86, 6, 72, 78, 88, 2, 70, 71, 76, 66, 5, 22, 0, 0, 0, 76, 89, 97, 66, 13, 64, 18, 75, 93, 105, 77, 82, 14, 2, 79, 71, 93, 75, 88, 83, 105, 86, 85, 90, 98, 20, 64, 7, 86, 71, 85, 81, 108, 6, 64, 0, 73, 16, 68, 66, 82, 83, 73, 74, 71, 8, 10, 65, 13, 17, 78, 0, 67, 67, 5, 74, 20, 67, 2, 20, 25, 30, 25, 18, 85, 67, 71, 8, 8, 94, 4, 9, 7, 4, 7, 7, 10, 7, 2, 3, 64, 3, 14, 9, 77, 66, 2, 69, 8, 12, 16, 8, 7, 7, 5, 4, 14, 16, 10, 77, 3, 75, 27, 22, 17, 14, 17, 18, 11, 16, 21, 0, 13, 9, 7, 4, 66, 8, 2, 5, 7, 4, 10, 17, 4, 81, 3, 75, 6, 16, 83, 38, 48, 39, 38, 39, 39, 35, 35, 30, 21, 17, 14, 16, 8, 82, 67, 1, 73, 21, 18, 15, 18, 14, 8, 7, 7, 71, 65, 70, 93, 87, 101, 71, 21, 18, 11, 2, 6, 67, 72, 76, 89, 70, 37, 21, 15, 8, 9, 65, 74, 78, 96, 5, 37, 28, 25, 19, 13, 5, 64, 71, 82, 70, 37, 29, 16, 4, 7, 69, 78, 88, 5, 52, 42, 35, 27, 22, 5, 67, 74, 79, 62, 96, 94, 79, 98, 96, 87, 93, 90, 89, 91, 90, 92, 88, 86, 87, 86, 77, 82, 73, 77, 77, 69, 65, 64, 64, 4, 2, 1, 66, 15, 12, 13, 9, 5, 16, 12, 7, 13, 12, 9, 7, 5, 66, 68, 67, 6, 74, 12, 26, 11, 8, 19, 18, 14, 14, 19, 14, 20, 4, 64, 1, 42, 41, 46, 39, 39, 48, 54, 54, 59, 57, 62, 62, 57, 47, 19, 54, 62, 53, 58, 50, 44, 32, 24, 21, 6, 71, 71, 82, 98, 43, 42, 44, 37, 30, 31, 23, 20, 15, 13, 7, 0, 64, 77, 81, 66, 67, 77, 24, 26, 21, 11, 19, 18, 6, 13, 13, 4, 71, 69, 77, 94, 14, 4, 69, 76, 65, 65, 0, 3, 3, 5, 3, 5, 8, 16, 8, 9, 15, 15, 59, 53, 48, 41, 35, 27, 18, 3, 86, 2, 34, 27, 24, 19, 16, 8, 5, 3, 70, 81, 76, 69, 75, 67, 4, 71, 75, 64, 3, 3, 3, 8, 4, 3, 61, 53, 41, 31, 23, 12, 1, 75, 93 }, { 62, 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 12, 43, 46, 54, 14, 87, 2, 67, 7, 1, 5, 69, 0, 78, 13, 40, 88, 103, 111, 89, 87, 67, 67, 7, 1, 81, 81, 7, 11, 66, 76, 87, 4, 72, 81, 89, 5, 73, 85, 5, 72, 78, 88, 2, 69, 70, 75, 66, 5, 22, 0, 0, 0, 75, 89, 97, 66, 12, 64, 18, 74, 93, 104, 75, 80, 15, 4, 77, 70, 92, 73, 87, 82, 103, 86, 84, 90, 97, 20, 64, 7, 85, 71, 84, 80, 107, 5, 64, 0, 73, 16, 68, 65, 81, 83, 73, 74, 71, 8, 9, 65, 13, 17, 77, 0, 66, 67, 5, 74, 20, 67, 2, 20, 24, 30, 24, 17, 84, 67, 71, 7, 7, 94, 3, 9, 6, 4, 7, 7, 11, 8, 2, 3, 64, 3, 13, 9, 78, 66, 2, 70, 8, 11, 15, 8, 7, 8, 5, 4, 13, 15, 9, 77, 3, 76, 27, 22, 17, 14, 17, 18, 11, 16, 21, 0, 13, 9, 7, 4, 66, 8, 2, 5, 7, 4, 10, 16, 4, 81, 3, 75, 5, 15, 83, 36, 46, 38, 37, 37, 37, 33, 33, 28, 19, 15, 12, 13, 7, 83, 67, 0, 74, 20, 17, 14, 17, 13, 7, 6, 6, 71, 66, 71, 93, 87, 100, 71, 21, 18, 11, 2, 6, 66, 71, 75, 88, 69, 37, 21, 15, 8, 9, 65, 73, 78, 94, 5, 37, 28, 25, 19, 13, 5, 64, 71, 81, 70, 38, 28, 16, 4, 7, 69, 78, 88, 5, 52, 41, 34, 26, 22, 5, 67, 74, 78, 62, 95, 93, 78, 96, 95, 86, 92, 88, 88, 89, 89, 90, 87, 85, 86, 85, 76, 81, 74, 76, 76, 69, 65, 65, 65, 4, 1, 0, 67, 14, 12, 13, 9, 5, 15, 12, 7, 12, 11, 9, 6, 4, 66, 68, 67, 6, 74, 11, 25, 11, 8, 18, 17, 14, 13, 18, 14, 20, 3, 65, 64, 41, 41, 45, 38, 37, 47, 52, 52, 57, 55, 62, 61, 54, 45, 17, 51, 62, 50, 54, 48, 42, 30, 23, 20, 6, 70, 70, 81, 96, 42, 41, 42, 36, 28, 29, 22, 18, 14, 11, 5, 65, 65, 78, 82, 66, 67, 78, 22, 25, 19, 10, 18, 17, 5, 12, 12, 3, 72, 70, 78, 94, 14, 4, 70, 75, 65, 64, 1, 4, 4, 6, 4, 6, 9, 18, 9, 10, 16, 17, 58, 51, 46, 39, 32, 24, 15, 0, 88, 2, 34, 27, 25, 20, 17, 8, 6, 4, 69, 80, 76, 68, 75, 66, 5, 70, 74, 0, 4, 3, 4, 9, 4, 3, 59, 51, 39, 28, 20, 9, 66, 78, 96 }, { 61, 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 10, 41, 45, 54, 14, 85, 2, 66, 8, 1, 4, 69, 64, 79, 13, 38, 89, 104, 111, 86, 86, 67, 66, 8, 1, 80, 80, 8, 11, 66, 75, 86, 3, 73, 81, 89, 5, 73, 85, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, 75, 89, 97, 65, 11, 64, 18, 74, 93, 102, 73, 79, 17, 6, 76, 69, 90, 72, 86, 81, 101, 85, 84, 89, 96, 20, 64, 7, 85, 71, 84, 80, 105, 5, 65, 64, 74, 16, 68, 65, 81, 83, 72, 74, 70, 8, 9, 65, 13, 16, 77, 0, 66, 67, 5, 74, 19, 67, 2, 19, 23, 29, 23, 16, 84, 66, 71, 6, 7, 93, 3, 9, 6, 4, 7, 7, 12, 8, 2, 3, 0, 3, 12, 9, 79, 66, 2, 70, 7, 10, 15, 8, 6, 8, 5, 4, 11, 15, 8, 77, 3, 76, 26, 22, 17, 14, 17, 18, 11, 16, 21, 0, 12, 9, 7, 4, 66, 7, 1, 4, 6, 3, 9, 15, 3, 81, 2, 75, 4, 13, 83, 35, 44, 36, 35, 35, 35, 31, 31, 26, 17, 14, 11, 11, 5, 84, 67, 0, 74, 19, 16, 13, 15, 11, 5, 5, 5, 72, 66, 71, 93, 87, 100, 70, 21, 18, 11, 2, 6, 66, 71, 75, 87, 69, 37, 21, 15, 8, 10, 64, 72, 77, 92, 5, 37, 28, 25, 19, 14, 5, 64, 71, 81, 70, 38, 28, 15, 4, 7, 69, 78, 87, 5, 51, 41, 33, 25, 22, 5, 67, 73, 78, 62, 94, 92, 78, 95, 94, 85, 90, 87, 87, 88, 87, 89, 87, 84, 86, 85, 74, 81, 74, 76, 76, 69, 66, 65, 65, 3, 0, 64, 68, 14, 11, 13, 8, 4, 15, 11, 7, 11, 11, 8, 6, 3, 66, 69, 67, 6, 75, 11, 25, 10, 8, 17, 17, 14, 12, 17, 13, 19, 2, 65, 65, 40, 40, 45, 37, 36, 45, 50, 50, 55, 52, 60, 59, 51, 42, 16, 48, 62, 47, 50, 45, 39, 28, 22, 19, 6, 69, 70, 80, 94, 40, 39, 40, 34, 26, 27, 20, 16, 12, 10, 4, 66, 67, 79, 83, 67, 68, 79, 21, 23, 18, 8, 16, 15, 3, 10, 10, 1, 73, 72, 80, 95, 13, 3, 71, 75, 64, 64, 1, 4, 4, 7, 5, 7, 10, 19, 10, 11, 18, 18, 58, 50, 44, 36, 30, 21, 12, 66, 90, 3, 35, 28, 25, 20, 18, 9, 6, 4, 69, 80, 75, 68, 74, 65, 6, 70, 74, 0, 4, 3, 4, 10, 4, 3, 58, 49, 36, 25, 17, 5, 70, 82, 98 }, { 60, 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 9, 40, 44, 54, 14, 83, 2, 65, 9, 1, 3, 69, 65, 80, 12, 36, 91, 105, 112, 83, 85, 67, 65, 9, 1, 80, 79, 8, 10, 66, 75, 85, 3, 73, 81, 89, 5, 73, 84, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, 74, 89, 97, 65, 10, 64, 18, 74, 93, 100, 71, 77, 18, 8, 75, 68, 89, 71, 85, 80, 99, 85, 84, 89, 95, 20, 64, 7, 84, 71, 83, 79, 104, 5, 65, 64, 74, 16, 68, 65, 80, 83, 72, 74, 70, 8, 9, 65, 13, 16, 77, 0, 66, 67, 5, 74, 18, 67, 2, 18, 22, 28, 22, 15, 83, 66, 71, 5, 6, 93, 2, 9, 6, 4, 7, 7, 13, 8, 2, 3, 0, 3, 11, 9, 80, 66, 2, 71, 7, 9, 15, 8, 5, 8, 5, 4, 10, 15, 7, 77, 3, 77, 25, 22, 17, 14, 17, 18, 11, 16, 21, 0, 12, 9, 7, 4, 66, 7, 1, 4, 6, 3, 8, 14, 3, 81, 2, 75, 3, 12, 83, 33, 42, 34, 33, 33, 33, 29, 29, 24, 15, 12, 9, 9, 3, 85, 67, 0, 75, 18, 15, 12, 14, 10, 4, 4, 4, 73, 67, 72, 93, 87, 99, 70, 21, 18, 11, 2, 6, 66, 71, 74, 86, 68, 37, 21, 15, 8, 10, 64, 71, 76, 90, 5, 37, 28, 25, 19, 14, 5, 64, 71, 80, 70, 38, 28, 15, 4, 7, 69, 78, 86, 5, 51, 40, 32, 24, 22, 5, 67, 73, 77, 62, 93, 91, 77, 94, 93, 84, 89, 86, 86, 87, 86, 87, 86, 83, 86, 84, 73, 81, 75, 76, 75, 69, 66, 66, 65, 2, 64, 65, 69, 13, 11, 13, 8, 4, 14, 11, 7, 10, 10, 8, 6, 2, 66, 69, 67, 6, 75, 10, 24, 9, 8, 16, 16, 14, 11, 16, 13, 19, 1, 66, 67, 39, 39, 44, 36, 35, 44, 48, 48, 53, 50, 57, 56, 48, 40, 15, 45, 59, 44, 46, 42, 37, 26, 21, 18, 6, 68, 70, 79, 92, 38, 37, 38, 32, 24, 25, 19, 14, 11, 8, 3, 68, 68, 80, 84, 67, 68, 80, 20, 22, 16, 6, 14, 13, 2, 9, 8, 0, 74, 73, 81, 96, 12, 2, 72, 75, 64, 64, 2, 5, 5, 8, 6, 8, 11, 20, 11, 12, 19, 19, 57, 48, 42, 34, 27, 18, 9, 69, 92, 3, 35, 28, 26, 20, 19, 9, 6, 5, 68, 80, 75, 67, 74, 64, 7, 70, 73, 1, 5, 3, 5, 11, 4, 3, 57, 47, 34, 22, 14, 1, 74, 85, 101 }, { 58, 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 7, 38, 42, 53, 14, 81, 1, 65, 9, 0, 2, 69, 67, 82, 11, 34, 93, 106, 113, 81, 84, 68, 65, 9, 0, 80, 78, 8, 9, 66, 75, 85, 2, 74, 81, 90, 5, 73, 84, 4, 73, 78, 88, 3, 69, 70, 75, 65, 4, 22, 0, 0, 0, 74, 90, 97, 65, 9, 65, 18, 74, 93, 99, 70, 76, 19, 9, 74, 67, 88, 70, 84, 80, 98, 85, 84, 89, 95, 20, 64, 7, 84, 71, 83, 79, 103, 4, 66, 65, 75, 16, 68, 65, 80, 83, 72, 74, 70, 7, 8, 65, 12, 15, 77, 64, 66, 67, 4, 74, 17, 68, 1, 17, 20, 27, 21, 14, 83, 66, 71, 4, 5, 93, 1, 8, 5, 4, 7, 7, 13, 8, 2, 2, 0, 2, 10, 8, 81, 67, 1, 72, 6, 8, 14, 7, 4, 8, 5, 4, 8, 14, 6, 77, 3, 78, 24, 21, 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, 67, 6, 0, 3, 5, 2, 7, 13, 2, 81, 1, 75, 2, 10, 83, 31, 40, 32, 31, 31, 30, 27, 27, 22, 13, 10, 7, 6, 1, 87, 68, 64, 76, 17, 13, 10, 12, 8, 2, 2, 2, 74, 68, 73, 93, 87, 99, 70, 21, 18, 11, 2, 6, 66, 71, 74, 85, 68, 36, 21, 15, 8, 10, 64, 71, 76, 89, 4, 37, 28, 24, 18, 14, 5, 64, 71, 80, 70, 38, 27, 14, 3, 7, 69, 78, 86, 5, 50, 39, 31, 23, 21, 5, 67, 73, 77, 62, 93, 90, 77, 93, 92, 84, 88, 85, 85, 86, 85, 86, 86, 83, 86, 84, 72, 81, 76, 76, 75, 69, 67, 67, 66, 1, 65, 67, 71, 12, 10, 13, 7, 3, 13, 10, 6, 9, 9, 7, 5, 1, 67, 70, 67, 6, 76, 9, 23, 8, 7, 15, 15, 14, 10, 14, 12, 18, 0, 67, 69, 38, 38, 43, 34, 33, 42, 46, 46, 50, 47, 54, 53, 45, 37, 13, 42, 55, 41, 41, 39, 34, 24, 19, 16, 6, 68, 70, 78, 90, 36, 35, 36, 30, 21, 23, 17, 11, 9, 6, 1, 70, 70, 81, 85, 68, 69, 82, 18, 20, 14, 4, 12, 11, 0, 7, 6, 65, 76, 75, 83, 97, 11, 1, 73, 75, 64, 64, 2, 5, 5, 9, 6, 9, 11, 21, 12, 13, 20, 20, 56, 46, 39, 31, 24, 15, 5, 72, 95, 3, 35, 28, 26, 20, 19, 9, 6, 5, 68, 80, 75, 67, 74, 0, 8, 70, 73, 1, 5, 3, 5, 11, 4, 2, 55, 44, 31, 19, 10, 66, 78, 89, 104 }, { 57, 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 6, 37, 41, 53, 14, 78, 1, 64, 10, 0, 2, 68, 68, 83, 11, 33, 94, 107, 113, 78, 82, 68, 64, 10, 0, 79, 76, 9, 9, 65, 74, 84, 2, 74, 80, 90, 5, 72, 83, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 73, 90, 97, 64, 9, 65, 18, 73, 92, 97, 68, 74, 21, 11, 72, 66, 86, 68, 82, 79, 96, 84, 83, 88, 94, 21, 0, 8, 83, 70, 82, 78, 101, 4, 66, 65, 75, 17, 68, 64, 79, 82, 71, 73, 69, 7, 8, 64, 12, 15, 76, 64, 65, 67, 4, 73, 17, 68, 1, 17, 19, 27, 21, 14, 82, 65, 70, 4, 5, 92, 1, 8, 5, 5, 7, 7, 14, 9, 3, 2, 1, 2, 10, 8, 81, 67, 1, 72, 6, 7, 14, 7, 4, 9, 6, 4, 7, 14, 6, 76, 3, 78, 24, 21, 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, 67, 6, 0, 3, 5, 2, 7, 13, 2, 80, 1, 74, 2, 9, 82, 30, 39, 31, 30, 29, 28, 26, 26, 20, 12, 9, 6, 4, 0, 88, 68, 64, 76, 16, 12, 9, 11, 7, 1, 1, 1, 74, 68, 73, 92, 86, 98, 69, 22, 18, 11, 3, 7, 65, 70, 73, 83, 67, 36, 21, 15, 8, 11, 0, 70, 75, 87, 4, 38, 29, 24, 18, 15, 5, 64, 70, 79, 70, 39, 27, 14, 3, 8, 68, 77, 85, 5, 50, 39, 31, 23, 21, 5, 66, 72, 76, 62, 92, 89, 76, 91, 90, 83, 86, 83, 83, 84, 83, 84, 85, 82, 85, 83, 70, 80, 76, 75, 74, 68, 67, 67, 66, 1, 65, 68, 72, 12, 10, 14, 7, 3, 13, 10, 6, 9, 9, 7, 5, 1, 67, 70, 66, 7, 76, 9, 23, 8, 7, 15, 15, 15, 10, 13, 12, 18, 0, 67, 70, 38, 38, 43, 33, 32, 41, 44, 44, 48, 45, 52, 51, 43, 35, 12, 40, 52, 38, 37, 37, 32, 23, 18, 15, 6, 67, 69, 76, 87, 35, 34, 35, 29, 19, 22, 16, 9, 8, 5, 0, 71, 71, 82, 85, 68, 69, 83, 17, 19, 13, 3, 11, 10, 64, 6, 5, 66, 77, 76, 84, 97, 11, 1, 73, 74, 0, 0, 3, 6, 6, 11, 7, 10, 12, 23, 14, 14, 22, 22, 56, 45, 37, 29, 22, 13, 2, 74, 97, 4, 36, 29, 27, 21, 20, 10, 7, 6, 67, 79, 74, 66, 73, 2, 10, 69, 72, 2, 6, 4, 6, 12, 4, 2, 54, 42, 29, 17, 7, 69, 81, 92, 106 }, { 56, 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 5, 36, 40, 53, 14, 76, 1, 0, 11, 0, 1, 68, 69, 84, 10, 31, 96, 108, 114, 75, 81, 68, 0, 11, 0, 79, 75, 9, 8, 65, 74, 83, 2, 74, 80, 90, 5, 72, 82, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 18, 73, 92, 95, 66, 72, 22, 13, 71, 65, 85, 67, 81, 78, 94, 84, 83, 88, 93, 21, 0, 8, 82, 70, 81, 78, 100, 4, 66, 65, 75, 17, 68, 64, 79, 82, 71, 73, 69, 7, 8, 64, 12, 15, 76, 64, 65, 67, 4, 73, 16, 68, 1, 16, 18, 26, 20, 13, 81, 65, 70, 3, 4, 92, 0, 8, 5, 5, 7, 7, 15, 9, 3, 2, 1, 2, 9, 8, 82, 67, 1, 73, 6, 6, 14, 7, 3, 9, 6, 4, 6, 14, 5, 76, 3, 79, 23, 21, 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, 67, 6, 0, 3, 5, 2, 6, 12, 1, 80, 1, 74, 1, 8, 82, 28, 37, 29, 28, 27, 26, 24, 24, 18, 10, 7, 4, 2, 65, 89, 68, 64, 77, 15, 11, 8, 10, 5, 0, 0, 0, 75, 69, 74, 92, 86, 97, 69, 22, 18, 11, 3, 7, 65, 70, 73, 82, 66, 36, 21, 15, 8, 11, 0, 69, 74, 85, 4, 38, 29, 24, 18, 15, 5, 64, 70, 78, 70, 39, 27, 14, 3, 8, 68, 77, 84, 5, 49, 38, 30, 22, 21, 5, 66, 72, 76, 62, 91, 88, 75, 90, 89, 82, 85, 82, 82, 83, 82, 82, 84, 81, 85, 82, 69, 80, 77, 75, 73, 68, 67, 68, 66, 0, 66, 69, 73, 11, 10, 14, 7, 2, 12, 10, 6, 8, 8, 7, 5, 0, 67, 70, 66, 7, 76, 8, 22, 7, 7, 14, 14, 15, 9, 12, 12, 18, 64, 68, 72, 37, 37, 42, 32, 31, 40, 42, 42, 46, 43, 49, 48, 40, 33, 11, 37, 49, 35, 33, 34, 30, 21, 17, 14, 6, 66, 69, 75, 85, 33, 32, 33, 27, 17, 20, 14, 7, 7, 3, 64, 73, 72, 83, 86, 69, 69, 84, 16, 18, 11, 1, 9, 8, 65, 4, 3, 67, 78, 77, 85, 98, 10, 0, 74, 74, 0, 0, 4, 6, 7, 12, 8, 11, 13, 24, 15, 15, 23, 23, 55, 43, 35, 27, 19, 10, 64, 77, 99, 4, 36, 29, 27, 21, 21, 10, 7, 6, 67, 79, 74, 65, 73, 3, 11, 69, 71, 3, 7, 4, 6, 13, 4, 2, 53, 40, 27, 14, 4, 73, 85, 95, 109 }, { 55, 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 3, 34, 39, 53, 14, 74, 1, 1, 12, 0, 0, 68, 70, 85, 10, 29, 97, 109, 114, 72, 80, 68, 1, 12, 0, 78, 74, 10, 8, 65, 73, 82, 1, 75, 80, 90, 5, 72, 82, 4, 73, 77, 88, 5, 68, 69, 74, 0, 4, 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 18, 73, 92, 93, 64, 71, 24, 15, 70, 64, 83, 66, 80, 77, 92, 83, 83, 87, 92, 21, 0, 8, 82, 70, 81, 77, 98, 4, 67, 66, 76, 17, 68, 64, 78, 82, 70, 73, 68, 7, 8, 64, 12, 14, 76, 64, 65, 67, 4, 73, 15, 68, 1, 15, 17, 25, 19, 12, 81, 64, 70, 2, 4, 91, 0, 8, 5, 5, 7, 7, 16, 9, 3, 2, 2, 2, 8, 8, 83, 67, 1, 73, 5, 5, 14, 7, 2, 9, 6, 4, 4, 14, 4, 76, 3, 79, 22, 21, 16, 14, 17, 17, 10, 16, 20, 64, 10, 9, 6, 3, 67, 5, 64, 2, 4, 1, 5, 11, 1, 80, 0, 74, 0, 6, 82, 27, 35, 27, 26, 25, 24, 22, 22, 16, 8, 6, 3, 0, 67, 90, 68, 64, 77, 14, 10, 7, 8, 4, 65, 64, 64, 76, 69, 74, 92, 86, 97, 68, 22, 18, 11, 3, 7, 65, 70, 72, 81, 66, 36, 21, 15, 8, 12, 1, 68, 73, 83, 4, 38, 29, 24, 18, 16, 5, 64, 70, 78, 70, 39, 27, 13, 3, 8, 68, 77, 83, 5, 49, 38, 29, 21, 21, 5, 66, 71, 75, 62, 90, 87, 75, 89, 88, 81, 83, 81, 81, 82, 80, 81, 84, 80, 85, 82, 67, 80, 77, 75, 73, 68, 68, 68, 66, 64, 67, 70, 74, 11, 9, 14, 6, 2, 12, 9, 6, 7, 8, 6, 5, 64, 67, 71, 66, 7, 77, 8, 22, 6, 7, 13, 14, 15, 8, 11, 11, 17, 65, 68, 73, 36, 36, 42, 31, 30, 38, 40, 40, 44, 40, 47, 46, 37, 30, 10, 34, 46, 32, 29, 31, 27, 19, 16, 13, 6, 65, 69, 74, 83, 31, 30, 31, 25, 15, 18, 13, 5, 5, 2, 65, 74, 74, 84, 87, 69, 70, 85, 15, 16, 10, 64, 7, 6, 67, 3, 1, 69, 79, 79, 87, 99, 9, 64, 75, 74, 1, 0, 4, 7, 7, 13, 9, 12, 14, 25, 16, 16, 25, 24, 55, 42, 33, 24, 17, 7, 67, 80, 101, 5, 37, 30, 28, 21, 22, 11, 7, 7, 66, 79, 73, 65, 72, 4, 12, 69, 71, 3, 7, 4, 7, 14, 4, 2, 52, 38, 24, 11, 1, 77, 89, 99, 111 }, { 53, 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 2, 33, 37, 53, 14, 71, 0, 2, 12, 0, 64, 68, 71, 86, 9, 27, 99, 110, 115, 69, 79, 68, 2, 12, 0, 78, 73, 10, 7, 65, 73, 82, 1, 75, 79, 90, 5, 72, 81, 3, 74, 77, 88, 5, 67, 69, 73, 0, 4, 22, 0, 0, 0, 71, 91, 97, 0, 6, 65, 18, 73, 92, 92, 0, 69, 25, 16, 69, 0, 82, 64, 79, 76, 90, 83, 82, 87, 91, 21, 0, 8, 81, 70, 80, 77, 97, 3, 67, 66, 76, 17, 68, 64, 78, 82, 70, 73, 68, 7, 7, 64, 12, 14, 76, 64, 65, 67, 4, 73, 15, 69, 1, 15, 16, 24, 18, 11, 80, 64, 70, 1, 3, 91, 64, 8, 4, 5, 7, 7, 17, 10, 3, 1, 2, 1, 7, 7, 84, 67, 1, 74, 5, 4, 13, 6, 2, 10, 6, 4, 3, 13, 3, 76, 3, 80, 22, 21, 16, 14, 17, 17, 10, 16, 19, 64, 10, 9, 6, 3, 68, 5, 64, 2, 4, 1, 5, 10, 0, 80, 0, 74, 64, 5, 82, 25, 33, 25, 24, 23, 22, 20, 20, 14, 6, 4, 1, 66, 69, 91, 68, 65, 78, 13, 9, 6, 7, 2, 66, 65, 66, 76, 70, 75, 92, 86, 96, 68, 22, 18, 11, 3, 7, 65, 70, 72, 80, 65, 36, 21, 15, 8, 12, 1, 68, 73, 81, 4, 38, 29, 24, 18, 16, 5, 64, 70, 77, 70, 39, 26, 13, 3, 8, 68, 77, 83, 5, 48, 37, 28, 20, 21, 5, 66, 71, 75, 62, 89, 86, 74, 87, 87, 80, 82, 79, 80, 80, 79, 79, 83, 80, 84, 81, 66, 79, 78, 75, 72, 68, 68, 69, 67, 65, 68, 71, 75, 10, 9, 14, 6, 1, 11, 9, 6, 6, 7, 6, 4, 65, 68, 71, 66, 7, 77, 7, 21, 5, 7, 12, 13, 15, 7, 10, 11, 17, 66, 69, 75, 35, 36, 41, 30, 28, 37, 38, 38, 42, 38, 44, 43, 34, 28, 8, 31, 42, 29, 25, 29, 25, 17, 15, 11, 6, 64, 68, 73, 81, 30, 28, 29, 24, 13, 16, 11, 3, 4, 0, 67, 76, 75, 85, 88, 70, 70, 86, 13, 15, 8, 65, 5, 4, 68, 1, 64, 70, 80, 80, 88, 99, 8, 64, 76, 74, 1, 1, 5, 7, 8, 14, 9, 13, 15, 26, 17, 17, 26, 25, 54, 40, 31, 22, 14, 4, 70, 83, 104, 5, 37, 30, 28, 22, 22, 11, 8, 7, 66, 78, 73, 64, 72, 5, 13, 69, 70, 4, 8, 4, 7, 15, 4, 2, 50, 36, 22, 8, 65, 81, 93, 102, 114 }, { 52, 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 0, 31, 36, 53, 14, 69, 0, 3, 13, 0, 64, 67, 72, 87, 9, 25, 101, 111, 115, 66, 77, 68, 3, 13, 0, 78, 72, 10, 7, 65, 73, 81, 1, 76, 79, 90, 5, 72, 80, 3, 74, 77, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 18, 72, 92, 90, 2, 68, 27, 18, 67, 1, 81, 0, 78, 75, 88, 82, 82, 86, 90, 21, 0, 8, 81, 70, 80, 76, 95, 3, 67, 66, 76, 17, 68, 0, 77, 82, 70, 73, 67, 7, 7, 64, 12, 14, 75, 64, 64, 67, 4, 73, 14, 69, 1, 14, 15, 24, 17, 10, 79, 0, 70, 0, 3, 90, 64, 8, 4, 5, 7, 7, 18, 10, 3, 1, 2, 1, 6, 7, 85, 67, 1, 74, 4, 3, 13, 6, 1, 10, 6, 4, 2, 13, 2, 76, 3, 80, 21, 21, 16, 14, 17, 17, 10, 16, 19, 64, 10, 9, 6, 3, 68, 5, 64, 1, 4, 1, 4, 9, 0, 80, 64, 74, 65, 4, 82, 24, 31, 24, 23, 21, 20, 18, 18, 12, 4, 2, 64, 68, 70, 92, 68, 65, 78, 12, 8, 5, 6, 1, 68, 66, 67, 77, 70, 75, 92, 86, 96, 68, 22, 18, 11, 3, 7, 64, 69, 71, 79, 65, 36, 21, 15, 8, 12, 2, 67, 72, 79, 4, 38, 29, 24, 18, 16, 5, 64, 70, 76, 70, 40, 26, 12, 3, 8, 68, 77, 82, 5, 48, 36, 27, 19, 21, 5, 66, 71, 74, 62, 88, 85, 73, 86, 86, 79, 81, 78, 79, 79, 77, 77, 82, 79, 84, 81, 65, 79, 78, 74, 71, 68, 69, 69, 67, 65, 69, 72, 76, 9, 8, 14, 5, 1, 11, 9, 6, 5, 6, 6, 4, 66, 68, 71, 66, 7, 77, 6, 21, 5, 7, 11, 12, 15, 6, 9, 10, 16, 67, 70, 77, 34, 35, 41, 29, 27, 35, 36, 36, 40, 35, 41, 41, 31, 26, 7, 28, 39, 26, 21, 26, 22, 15, 14, 10, 6, 0, 68, 72, 79, 28, 27, 27, 22, 11, 14, 10, 1, 3, 65, 68, 78, 77, 86, 89, 70, 70, 87, 12, 13, 6, 67, 4, 3, 70, 0, 65, 72, 81, 81, 89, 100, 8, 65, 77, 73, 2, 1, 5, 8, 9, 15, 10, 14, 16, 28, 18, 18, 27, 27, 54, 38, 29, 19, 12, 1, 73, 86, 106, 5, 38, 31, 29, 22, 23, 11, 8, 8, 65, 78, 73, 0, 71, 6, 14, 68, 69, 4, 8, 4, 8, 16, 4, 2, 49, 34, 19, 5, 68, 84, 97, 106, 117 }, { 51, 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 64, 30, 35, 53, 14, 67, 0, 3, 14, 0, 65, 67, 73, 88, 8, 24, 102, 112, 116, 0, 76, 68, 3, 14, 0, 77, 71, 11, 6, 64, 72, 80, 0, 76, 79, 90, 5, 71, 80, 3, 74, 76, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 70, 91, 97, 1, 5, 66, 18, 72, 91, 88, 4, 66, 28, 20, 66, 2, 79, 1, 77, 75, 87, 82, 82, 86, 90, 22, 0, 8, 80, 69, 79, 76, 94, 3, 68, 67, 77, 17, 68, 0, 77, 82, 69, 73, 67, 7, 7, 0, 12, 13, 75, 64, 64, 67, 4, 73, 13, 69, 1, 13, 14, 23, 17, 10, 79, 0, 70, 0, 2, 90, 65, 8, 4, 5, 7, 7, 18, 10, 3, 1, 3, 1, 5, 7, 85, 68, 1, 75, 4, 2, 13, 6, 0, 10, 6, 4, 0, 13, 1, 76, 3, 81, 20, 21, 15, 14, 17, 17, 10, 16, 19, 64, 9, 9, 5, 3, 68, 4, 65, 1, 3, 0, 3, 8, 64, 80, 64, 73, 66, 2, 82, 22, 29, 22, 21, 19, 18, 16, 16, 10, 2, 1, 65, 70, 72, 93, 68, 65, 79, 11, 7, 4, 4, 64, 69, 67, 68, 78, 71, 76, 92, 86, 95, 67, 22, 18, 11, 4, 7, 64, 69, 71, 78, 64, 35, 21, 15, 8, 13, 2, 66, 71, 77, 4, 39, 29, 24, 18, 17, 5, 64, 69, 76, 70, 40, 26, 12, 3, 8, 67, 76, 81, 5, 47, 36, 26, 19, 21, 5, 65, 70, 74, 62, 88, 84, 73, 85, 85, 78, 79, 77, 78, 78, 76, 76, 82, 78, 84, 80, 0, 79, 79, 74, 71, 68, 69, 70, 67, 66, 70, 73, 77, 9, 8, 14, 5, 0, 10, 8, 6, 5, 6, 5, 4, 66, 68, 72, 66, 7, 78, 6, 20, 4, 6, 10, 12, 16, 6, 8, 10, 16, 67, 70, 78, 33, 34, 40, 28, 26, 34, 34, 34, 38, 33, 39, 38, 28, 23, 6, 26, 36, 23, 17, 23, 20, 13, 13, 9, 6, 1, 68, 70, 76, 26, 25, 25, 20, 9, 12, 8, 64, 1, 66, 69, 79, 78, 87, 90, 71, 71, 88, 11, 12, 5, 69, 2, 1, 71, 65, 67, 73, 83, 83, 91, 101, 7, 66, 78, 73, 2, 1, 6, 8, 9, 17, 11, 15, 17, 29, 20, 19, 29, 28, 53, 37, 27, 17, 9, 64, 76, 88, 108, 6, 38, 31, 29, 22, 24, 12, 8, 8, 65, 78, 72, 0, 71, 7, 15, 68, 69, 5, 9, 4, 8, 17, 4, 2, 48, 32, 17, 2, 72, 88, 100, 109, 119 }, { 50, 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 66, 28, 33, 53, 14, 64, 64, 4, 14, 0, 66, 67, 74, 89, 8, 22, 104, 113, 116, 3, 75, 68, 4, 14, 0, 77, 70, 11, 6, 64, 72, 80, 0, 77, 78, 90, 5, 71, 79, 2, 74, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 70, 91, 97, 2, 4, 66, 18, 72, 91, 87, 6, 65, 30, 22, 65, 3, 78, 3, 76, 74, 85, 81, 81, 85, 89, 22, 0, 8, 80, 69, 79, 75, 92, 2, 68, 67, 77, 17, 68, 0, 76, 82, 69, 73, 66, 7, 6, 0, 12, 13, 75, 64, 64, 67, 4, 73, 13, 69, 1, 13, 13, 22, 16, 9, 78, 1, 70, 64, 2, 89, 65, 8, 3, 5, 7, 7, 19, 11, 3, 1, 3, 1, 4, 7, 86, 68, 1, 75, 3, 1, 12, 6, 0, 11, 6, 4, 64, 12, 0, 76, 3, 81, 20, 21, 15, 14, 17, 17, 10, 16, 19, 64, 9, 9, 5, 3, 68, 4, 65, 0, 3, 0, 3, 7, 64, 80, 65, 73, 67, 1, 82, 21, 27, 20, 19, 17, 16, 14, 14, 8, 0, 64, 67, 73, 74, 94, 68, 66, 79, 10, 6, 3, 3, 65, 71, 68, 69, 78, 71, 76, 92, 86, 95, 67, 22, 18, 11, 4, 7, 64, 69, 70, 77, 64, 35, 21, 15, 8, 13, 3, 65, 71, 75, 4, 39, 29, 24, 18, 17, 5, 64, 69, 75, 70, 40, 25, 11, 3, 8, 67, 76, 81, 5, 47, 35, 25, 18, 21, 5, 65, 70, 73, 62, 87, 83, 72, 83, 84, 77, 78, 75, 77, 76, 74, 74, 81, 77, 83, 80, 1, 78, 79, 74, 70, 68, 70, 70, 68, 67, 71, 74, 78, 8, 7, 14, 4, 0, 10, 8, 6, 4, 5, 5, 3, 67, 68, 72, 66, 7, 78, 5, 20, 3, 6, 9, 11, 16, 5, 7, 9, 15, 68, 71, 80, 32, 34, 40, 27, 24, 32, 32, 32, 36, 30, 36, 36, 25, 21, 4, 23, 32, 20, 13, 21, 17, 11, 12, 8, 6, 2, 67, 69, 74, 25, 23, 23, 19, 7, 10, 7, 66, 0, 68, 71, 81, 80, 88, 91, 71, 71, 89, 9, 10, 3, 70, 0, 64, 73, 66, 69, 75, 84, 84, 92, 101, 6, 66, 79, 73, 3, 2, 6, 9, 10, 18, 12, 16, 18, 30, 21, 20, 30, 29, 53, 35, 25, 14, 7, 67, 79, 91, 110, 6, 39, 32, 30, 23, 25, 12, 9, 9, 64, 77, 72, 1, 70, 8, 16, 68, 68, 5, 9, 4, 9, 18, 4, 2, 46, 30, 14, 64, 75, 92, 104, 113, 122 }, { 48, 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 67, 27, 32, 53, 14, 1, 64, 5, 15, 0, 67, 67, 75, 91, 7, 20, 106, 114, 117, 5, 74, 68, 5, 15, 0, 77, 69, 11, 5, 64, 72, 79, 64, 77, 78, 91, 5, 71, 79, 2, 75, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 69, 92, 97, 2, 3, 66, 18, 72, 91, 85, 7, 0, 31, 23, 64, 4, 77, 4, 75, 73, 83, 81, 81, 85, 88, 22, 0, 8, 79, 69, 78, 75, 91, 2, 69, 68, 78, 17, 68, 0, 76, 82, 69, 73, 66, 6, 6, 0, 12, 12, 75, 64, 64, 67, 3, 73, 12, 70, 1, 12, 11, 21, 15, 8, 78, 1, 70, 65, 1, 89, 66, 7, 3, 5, 7, 7, 20, 11, 3, 0, 3, 0, 3, 6, 87, 68, 1, 76, 3, 0, 12, 5, 64, 11, 6, 4, 66, 12, 64, 76, 3, 82, 19, 20, 15, 14, 17, 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 0, 2, 64, 2, 6, 65, 80, 65, 73, 68, 64, 82, 19, 25, 18, 17, 15, 13, 12, 12, 6, 65, 66, 69, 75, 76, 95, 68, 66, 80, 9, 4, 1, 1, 67, 72, 70, 71, 79, 72, 77, 92, 86, 94, 67, 22, 18, 11, 4, 7, 64, 69, 70, 76, 0, 35, 21, 15, 8, 13, 3, 65, 70, 74, 4, 39, 29, 24, 17, 17, 5, 64, 69, 75, 70, 40, 25, 11, 3, 8, 67, 76, 80, 5, 46, 34, 24, 17, 20, 5, 65, 70, 73, 62, 86, 82, 72, 82, 83, 77, 77, 74, 76, 75, 73, 73, 81, 77, 83, 79, 2, 78, 80, 74, 70, 68, 70, 71, 68, 68, 72, 76, 79, 7, 7, 14, 4, 64, 9, 7, 5, 3, 4, 4, 3, 68, 69, 73, 66, 7, 79, 4, 19, 2, 6, 8, 10, 16, 4, 6, 9, 15, 69, 72, 82, 31, 33, 39, 25, 23, 31, 30, 30, 33, 28, 33, 33, 22, 18, 3, 20, 29, 17, 9, 18, 15, 9, 10, 6, 6, 2, 67, 68, 72, 23, 21, 21, 17, 4, 8, 5, 68, 65, 70, 72, 83, 81, 89, 92, 72, 72, 90, 8, 9, 1, 72, 65, 66, 74, 68, 71, 76, 85, 86, 94, 102, 5, 67, 80, 73, 3, 2, 7, 9, 10, 19, 12, 17, 19, 31, 22, 21, 31, 30, 52, 33, 23, 12, 4, 70, 82, 94, 113, 6, 39, 32, 30, 23, 25, 12, 9, 9, 64, 77, 72, 1, 70, 9, 17, 68, 68, 6, 10, 4, 9, 18, 4, 1, 45, 28, 12, 67, 78, 96, 108, 116, 125 }, { 47, 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 68, 26, 31, 53, 14, 3, 64, 6, 16, 0, 67, 66, 76, 92, 6, 18, 107, 115, 118, 8, 72, 68, 6, 16, 0, 76, 68, 12, 4, 64, 71, 78, 64, 77, 78, 91, 5, 71, 78, 2, 75, 76, 88, 7, 66, 67, 72, 2, 4, 22, 0, 0, 0, 68, 92, 97, 2, 2, 66, 18, 71, 91, 83, 9, 2, 32, 25, 1, 5, 75, 5, 73, 72, 81, 81, 81, 85, 87, 22, 0, 8, 78, 69, 77, 74, 90, 2, 69, 68, 78, 17, 68, 1, 75, 81, 68, 73, 66, 6, 6, 0, 12, 12, 74, 64, 0, 67, 3, 72, 11, 70, 1, 11, 10, 21, 14, 7, 77, 1, 69, 66, 0, 89, 67, 7, 3, 6, 7, 7, 21, 11, 3, 0, 4, 0, 3, 6, 88, 68, 1, 77, 3, 64, 12, 5, 65, 11, 6, 4, 67, 12, 64, 76, 3, 83, 18, 20, 15, 14, 17, 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 0, 2, 64, 1, 6, 65, 80, 65, 73, 69, 65, 82, 17, 24, 17, 16, 13, 11, 11, 11, 4, 67, 67, 70, 77, 77, 96, 68, 66, 81, 8, 3, 0, 0, 68, 73, 71, 72, 80, 73, 78, 92, 85, 93, 66, 23, 18, 11, 4, 8, 0, 68, 69, 75, 1, 35, 21, 15, 8, 14, 3, 64, 69, 72, 4, 39, 29, 24, 17, 18, 5, 64, 69, 74, 70, 41, 25, 11, 3, 9, 67, 76, 79, 5, 46, 34, 24, 16, 20, 5, 65, 69, 72, 62, 85, 81, 71, 81, 81, 76, 75, 73, 74, 74, 72, 71, 80, 76, 83, 78, 4, 78, 81, 73, 69, 68, 70, 72, 68, 68, 73, 77, 80, 7, 7, 14, 4, 64, 8, 7, 5, 2, 4, 4, 3, 69, 69, 73, 65, 8, 79, 4, 18, 2, 6, 8, 10, 16, 3, 5, 9, 15, 70, 72, 83, 31, 32, 38, 24, 22, 30, 28, 28, 31, 26, 31, 30, 20, 16, 2, 17, 26, 14, 5, 15, 13, 8, 9, 5, 6, 3, 67, 67, 70, 21, 20, 19, 15, 2, 7, 4, 70, 66, 71, 73, 84, 82, 90, 92, 72, 72, 91, 7, 8, 0, 74, 66, 67, 75, 69, 72, 77, 86, 87, 95, 103, 5, 68, 80, 72, 3, 2, 8, 10, 11, 20, 13, 18, 20, 33, 23, 22, 33, 32, 51, 32, 21, 10, 1, 73, 85, 97, 115, 7, 39, 32, 31, 23, 26, 13, 9, 10, 0, 77, 71, 2, 70, 10, 19, 67, 67, 7, 11, 4, 10, 19, 4, 1, 44, 26, 10, 69, 81, 99, 112, 119, 126 }, { 46, 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 70, 24, 29, 53, 14, 6, 65, 7, 16, 0, 68, 66, 77, 93, 6, 16, 109, 116, 118, 11, 71, 68, 7, 16, 0, 76, 67, 12, 4, 64, 71, 78, 64, 78, 77, 91, 5, 71, 77, 1, 75, 76, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 18, 71, 91, 82, 11, 3, 34, 27, 2, 6, 74, 7, 72, 71, 79, 80, 80, 84, 86, 22, 0, 8, 78, 69, 77, 74, 88, 1, 69, 68, 78, 17, 68, 1, 75, 81, 68, 73, 65, 6, 5, 0, 12, 12, 74, 64, 0, 67, 3, 72, 11, 70, 1, 11, 9, 20, 13, 6, 76, 2, 69, 67, 0, 88, 67, 7, 2, 6, 7, 7, 22, 12, 3, 0, 4, 0, 2, 6, 89, 68, 1, 77, 2, 65, 11, 5, 65, 12, 6, 4, 68, 11, 65, 76, 3, 83, 18, 20, 15, 14, 17, 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 64, 2, 64, 1, 5, 66, 80, 66, 73, 70, 66, 82, 16, 22, 15, 14, 11, 9, 9, 9, 2, 69, 69, 72, 80, 79, 97, 68, 67, 81, 7, 2, 64, 64, 70, 75, 72, 73, 80, 73, 78, 92, 85, 93, 66, 23, 18, 11, 4, 8, 0, 68, 69, 74, 1, 35, 21, 15, 8, 14, 4, 0, 69, 70, 4, 39, 29, 24, 17, 18, 5, 64, 69, 73, 70, 41, 24, 10, 3, 9, 67, 76, 79, 5, 45, 33, 23, 15, 20, 5, 65, 69, 72, 62, 84, 80, 70, 79, 80, 75, 74, 71, 73, 72, 70, 69, 79, 75, 82, 78, 5, 77, 81, 73, 68, 68, 71, 72, 69, 69, 74, 78, 81, 6, 6, 14, 3, 65, 8, 7, 5, 1, 3, 4, 2, 70, 69, 73, 65, 8, 79, 3, 18, 1, 6, 7, 9, 16, 2, 4, 8, 14, 71, 73, 85, 30, 32, 38, 23, 20, 28, 26, 26, 29, 23, 28, 28, 17, 14, 0, 14, 22, 11, 1, 13, 10, 6, 8, 4, 6, 4, 66, 66, 68, 20, 18, 17, 14, 0, 5, 2, 72, 67, 73, 75, 86, 84, 91, 93, 73, 72, 92, 5, 6, 65, 75, 68, 69, 77, 71, 74, 79, 87, 88, 96, 103, 4, 68, 81, 72, 4, 3, 8, 10, 12, 21, 14, 19, 21, 34, 24, 23, 34, 33, 51, 30, 19, 7, 64, 76, 88, 100, 117, 7, 40, 33, 31, 24, 27, 13, 10, 10, 0, 76, 71, 3, 69, 11, 20, 67, 66, 7, 11, 4, 10, 20, 4, 1, 42, 24, 7, 72, 84, 103, 116, 123, 126 }, { 45, 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 71, 23, 28, 53, 14, 8, 65, 7, 17, 0, 69, 66, 78, 94, 5, 15, 110, 117, 119, 14, 70, 68, 7, 17, 0, 75, 66, 13, 3, 0, 70, 77, 65, 78, 77, 91, 5, 70, 77, 1, 75, 75, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 18, 71, 90, 80, 13, 5, 35, 29, 3, 7, 72, 8, 71, 71, 78, 80, 80, 84, 86, 23, 0, 8, 77, 68, 76, 73, 87, 1, 70, 69, 79, 17, 68, 1, 74, 81, 67, 73, 65, 6, 5, 1, 12, 11, 74, 64, 0, 67, 3, 72, 10, 70, 1, 10, 8, 19, 13, 6, 76, 2, 69, 67, 64, 88, 68, 7, 2, 6, 7, 7, 22, 12, 3, 0, 5, 0, 1, 6, 89, 69, 1, 78, 2, 66, 11, 5, 66, 12, 6, 4, 70, 11, 66, 76, 3, 84, 17, 20, 14, 14, 17, 16, 9, 16, 18, 65, 7, 9, 4, 2, 69, 2, 67, 64, 1, 65, 0, 4, 66, 80, 66, 72, 71, 68, 82, 14, 20, 13, 12, 9, 7, 7, 7, 0, 71, 70, 73, 82, 81, 98, 68, 67, 82, 6, 1, 65, 66, 71, 76, 73, 74, 81, 74, 79, 92, 85, 92, 65, 23, 18, 11, 5, 8, 0, 68, 68, 73, 2, 34, 21, 15, 8, 15, 4, 1, 68, 68, 4, 40, 29, 24, 17, 19, 5, 64, 68, 73, 70, 41, 24, 10, 3, 9, 66, 75, 78, 5, 45, 33, 22, 15, 20, 5, 64, 68, 71, 62, 84, 79, 70, 78, 79, 74, 72, 70, 72, 71, 69, 68, 79, 74, 82, 77, 7, 77, 82, 73, 68, 68, 71, 73, 69, 70, 75, 79, 82, 6, 6, 14, 3, 65, 7, 6, 5, 1, 3, 3, 2, 70, 69, 74, 65, 8, 80, 3, 17, 0, 5, 6, 9, 17, 2, 3, 8, 14, 71, 73, 86, 29, 31, 37, 22, 19, 27, 24, 24, 27, 21, 26, 25, 14, 11, 64, 12, 19, 8, 66, 10, 8, 4, 7, 3, 6, 5, 66, 64, 65, 18, 16, 15, 12, 65, 3, 1, 74, 69, 74, 76, 87, 85, 92, 94, 73, 73, 93, 4, 5, 66, 77, 70, 71, 78, 72, 76, 80, 89, 90, 98, 104, 3, 69, 82, 72, 4, 3, 9, 11, 12, 23, 15, 20, 22, 35, 26, 24, 36, 34, 50, 29, 17, 5, 67, 78, 91, 102, 119, 8, 40, 33, 32, 24, 28, 14, 10, 11, 1, 76, 70, 3, 69, 12, 21, 67, 66, 8, 12, 4, 11, 21, 4, 1, 41, 22, 5, 75, 88, 107, 119, 126, 126 }, { 43, 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 73, 21, 27, 53, 14, 10, 65, 8, 18, 0, 70, 66, 79, 95, 5, 13, 112, 118, 119, 17, 69, 68, 8, 18, 0, 75, 65, 13, 3, 0, 70, 76, 65, 79, 77, 91, 5, 70, 76, 1, 76, 75, 88, 9, 65, 67, 71, 4, 4, 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 18, 71, 90, 78, 14, 6, 37, 30, 4, 8, 71, 9, 70, 70, 76, 79, 80, 83, 85, 23, 0, 8, 77, 68, 76, 73, 85, 1, 70, 69, 79, 17, 68, 1, 74, 81, 67, 73, 64, 6, 5, 1, 12, 11, 74, 64, 0, 67, 3, 72, 9, 71, 1, 9, 7, 18, 12, 5, 75, 3, 69, 68, 64, 87, 68, 7, 2, 6, 7, 7, 23, 12, 3, 64, 5, 64, 0, 5, 90, 69, 1, 78, 1, 67, 11, 4, 67, 12, 6, 4, 71, 11, 67, 76, 3, 84, 16, 20, 14, 14, 17, 16, 9, 16, 17, 65, 7, 9, 4, 2, 70, 2, 67, 65, 1, 65, 64, 3, 67, 80, 67, 72, 72, 69, 82, 13, 18, 11, 10, 7, 5, 5, 5, 65, 73, 72, 75, 84, 83, 99, 68, 67, 82, 5, 0, 66, 67, 73, 78, 74, 76, 82, 74, 79, 92, 85, 92, 65, 23, 18, 11, 5, 8, 0, 68, 68, 72, 2, 34, 21, 15, 8, 15, 5, 1, 67, 66, 4, 40, 29, 24, 17, 19, 5, 64, 68, 72, 70, 41, 24, 9, 3, 9, 66, 75, 77, 5, 44, 32, 21, 14, 20, 5, 64, 68, 71, 62, 83, 78, 69, 77, 78, 73, 71, 69, 71, 70, 67, 66, 78, 74, 82, 77, 8, 77, 82, 73, 67, 68, 72, 73, 69, 71, 76, 80, 83, 5, 5, 14, 2, 66, 7, 6, 5, 0, 2, 3, 2, 71, 70, 74, 65, 8, 80, 2, 17, 64, 5, 5, 8, 17, 1, 2, 7, 13, 72, 74, 88, 28, 30, 37, 21, 18, 25, 22, 22, 25, 18, 23, 23, 11, 9, 65, 9, 16, 5, 70, 7, 5, 2, 6, 1, 6, 6, 66, 0, 0, 16, 14, 13, 10, 67, 1, 64, 76, 70, 76, 77, 89, 87, 93, 95, 74, 73, 94, 3, 3, 68, 79, 72, 73, 80, 74, 78, 82, 90, 91, 99, 105, 2, 70, 83, 72, 5, 3, 9, 11, 13, 24, 15, 21, 23, 36, 27, 25, 37, 35, 50, 27, 15, 2, 69, 81, 94, 105, 122, 8, 41, 34, 32, 24, 28, 14, 10, 11, 1, 76, 70, 4, 68, 13, 22, 67, 65, 8, 12, 4, 11, 22, 4, 1, 40, 20, 2, 78, 91, 111, 123, 126, 126 }, { 42, 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 74, 20, 25, 53, 14, 13, 66, 9, 18, 0, 70, 65, 80, 96, 4, 11, 114, 119, 120, 20, 67, 68, 9, 18, 0, 75, 64, 13, 2, 0, 70, 76, 65, 79, 76, 91, 5, 70, 75, 0, 76, 75, 88, 9, 64, 66, 70, 4, 4, 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 18, 70, 90, 77, 16, 8, 38, 32, 6, 9, 70, 11, 69, 69, 74, 79, 79, 83, 84, 23, 0, 8, 76, 68, 75, 72, 84, 0, 70, 69, 79, 17, 68, 2, 73, 81, 67, 73, 64, 6, 4, 1, 12, 11, 73, 64, 1, 67, 3, 72, 9, 71, 1, 9, 6, 18, 11, 4, 74, 3, 69, 69, 65, 87, 69, 7, 1, 6, 7, 7, 24, 13, 3, 64, 5, 64, 64, 5, 91, 69, 1, 79, 1, 68, 10, 4, 67, 13, 6, 4, 72, 10, 68, 76, 3, 85, 16, 20, 14, 14, 17, 16, 9, 16, 17, 65, 7, 9, 4, 2, 70, 2, 67, 65, 1, 65, 64, 2, 67, 80, 67, 72, 73, 70, 82, 11, 16, 10, 9, 5, 3, 3, 3, 67, 75, 74, 77, 87, 84, 100, 68, 68, 83, 4, 64, 67, 68, 74, 79, 75, 77, 82, 75, 80, 92, 85, 91, 65, 23, 18, 11, 5, 8, 1, 67, 67, 71, 3, 34, 21, 15, 8, 15, 5, 2, 67, 64, 4, 40, 29, 24, 17, 19, 5, 64, 68, 71, 70, 42, 23, 9, 3, 9, 66, 75, 77, 5, 44, 31, 20, 13, 20, 5, 64, 68, 70, 62, 82, 77, 68, 75, 77, 72, 70, 67, 70, 68, 66, 64, 77, 73, 81, 76, 9, 76, 83, 72, 66, 68, 72, 74, 70, 71, 77, 81, 84, 4, 5, 14, 2, 66, 6, 6, 5, 64, 1, 3, 1, 72, 70, 74, 65, 8, 80, 1, 16, 64, 5, 4, 7, 17, 0, 1, 7, 13, 73, 75, 90, 27, 30, 36, 20, 16, 24, 20, 20, 23, 16, 20, 20, 8, 7, 67, 6, 12, 2, 74, 5, 3, 0, 5, 0, 6, 7, 65, 1, 2, 15, 13, 11, 9, 69, 64, 65, 78, 71, 78, 79, 91, 88, 94, 96, 74, 73, 95, 1, 2, 70, 80, 73, 74, 81, 75, 79, 83, 91, 92, 100, 105, 2, 70, 84, 71, 5, 4, 10, 12, 14, 25, 16, 22, 24, 38, 28, 26, 38, 37, 49, 25, 13, 0, 72, 84, 97, 108, 124, 8, 41, 34, 33, 25, 29, 14, 11, 12, 2, 75, 70, 5, 68, 14, 23, 66, 64, 9, 13, 4, 12, 23, 4, 1, 38, 18, 0, 81, 94, 114, 126, 126, 126 }, { 41, 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 76, 18, 24, 53, 14, 15, 66, 10, 19, 0, 71, 65, 81, 97, 4, 9, 115, 120, 120, 23, 66, 68, 10, 19, 0, 74, 0, 14, 2, 0, 69, 75, 66, 80, 76, 91, 5, 70, 75, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 18, 70, 90, 75, 18, 9, 40, 34, 7, 10, 68, 12, 68, 68, 72, 78, 79, 82, 83, 23, 0, 8, 76, 68, 75, 72, 82, 0, 71, 70, 80, 17, 68, 2, 73, 81, 66, 73, 0, 6, 4, 1, 12, 10, 73, 64, 1, 67, 3, 72, 8, 71, 1, 8, 5, 17, 10, 3, 74, 4, 69, 70, 65, 86, 69, 7, 1, 6, 7, 7, 25, 13, 3, 64, 6, 64, 65, 5, 92, 69, 1, 79, 0, 69, 10, 4, 68, 13, 6, 4, 74, 10, 69, 76, 3, 85, 15, 20, 14, 14, 17, 16, 9, 16, 17, 65, 6, 9, 4, 2, 70, 1, 68, 66, 0, 66, 65, 1, 68, 80, 68, 72, 74, 72, 82, 10, 14, 8, 7, 3, 1, 1, 1, 69, 77, 75, 78, 89, 86, 101, 68, 68, 83, 3, 65, 68, 70, 76, 81, 76, 78, 83, 75, 80, 92, 85, 91, 64, 23, 18, 11, 5, 8, 1, 67, 67, 70, 3, 34, 21, 15, 8, 16, 6, 3, 66, 1, 4, 40, 29, 24, 17, 20, 5, 64, 68, 71, 70, 42, 23, 8, 3, 9, 66, 75, 76, 5, 43, 31, 19, 12, 20, 5, 64, 67, 70, 62, 81, 76, 68, 74, 76, 71, 68, 66, 69, 67, 64, 0, 77, 72, 81, 76, 11, 76, 83, 72, 66, 68, 73, 74, 70, 72, 78, 82, 85, 4, 4, 14, 1, 67, 6, 5, 5, 65, 1, 2, 1, 73, 70, 75, 65, 8, 81, 1, 16, 65, 5, 3, 7, 17, 64, 0, 6, 12, 74, 75, 91, 26, 29, 36, 19, 15, 22, 18, 18, 21, 13, 18, 18, 5, 4, 68, 3, 9, 64, 78, 2, 0, 65, 4, 64, 6, 8, 65, 2, 4, 13, 11, 9, 7, 71, 66, 67, 80, 73, 79, 80, 92, 90, 95, 97, 75, 74, 96, 0, 0, 71, 82, 75, 76, 83, 77, 81, 85, 92, 94, 102, 106, 1, 71, 85, 71, 6, 4, 10, 12, 14, 26, 17, 23, 25, 39, 29, 27, 40, 38, 49, 24, 11, 66, 74, 87, 100, 111, 126, 9, 42, 35, 33, 25, 30, 15, 11, 12, 2, 75, 69, 5, 67, 15, 24, 66, 64, 9, 13, 4, 12, 24, 4, 1, 37, 16, 66, 84, 97, 118, 126, 126, 126 }, { 40, 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 77, 17, 23, 53, 14, 17, 66, 11, 20, 0, 72, 65, 82, 98, 3, 7, 117, 121, 121, 26, 65, 68, 11, 20, 0, 74, 1, 14, 1, 0, 69, 74, 66, 80, 76, 91, 5, 70, 74, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 18, 70, 90, 73, 20, 11, 41, 36, 8, 11, 67, 13, 67, 67, 70, 78, 79, 82, 82, 23, 0, 8, 75, 68, 74, 71, 81, 0, 71, 70, 80, 17, 68, 2, 72, 81, 66, 73, 0, 6, 4, 1, 12, 10, 73, 64, 1, 67, 3, 72, 7, 71, 1, 7, 4, 16, 9, 2, 73, 4, 69, 71, 66, 86, 70, 7, 1, 6, 7, 7, 26, 13, 3, 64, 6, 64, 66, 5, 93, 69, 1, 80, 0, 70, 10, 4, 69, 13, 6, 4, 75, 10, 70, 76, 3, 86, 14, 20, 14, 14, 17, 16, 9, 16, 17, 65, 6, 9, 4, 2, 70, 1, 68, 66, 0, 66, 66, 0, 68, 80, 68, 72, 75, 73, 82, 8, 12, 6, 5, 1, 64, 64, 64, 71, 79, 77, 80, 91, 88, 102, 68, 68, 84, 2, 66, 69, 71, 77, 82, 77, 79, 84, 76, 81, 92, 85, 90, 64, 23, 18, 11, 5, 8, 1, 67, 66, 69, 4, 34, 21, 15, 8, 16, 6, 4, 65, 3, 4, 40, 29, 24, 17, 20, 5, 64, 68, 70, 70, 42, 23, 8, 3, 9, 66, 75, 75, 5, 43, 30, 18, 11, 20, 5, 64, 67, 69, 62, 80, 75, 67, 73, 75, 70, 67, 65, 68, 66, 0, 2, 76, 71, 81, 75, 12, 76, 84, 72, 65, 68, 73, 75, 70, 73, 79, 83, 86, 3, 4, 14, 1, 67, 5, 5, 5, 66, 0, 2, 1, 74, 70, 75, 65, 8, 81, 0, 15, 66, 5, 2, 6, 17, 65, 64, 6, 12, 75, 76, 93, 25, 28, 35, 18, 14, 21, 16, 16, 19, 11, 15, 15, 2, 2, 69, 0, 6, 67, 82, 64, 65, 67, 3, 65, 6, 9, 65, 3, 6, 11, 9, 7, 5, 73, 68, 68, 82, 74, 81, 81, 94, 91, 96, 98, 75, 74, 97, 64, 64, 73, 84, 77, 78, 84, 78, 83, 86, 93, 95, 103, 107, 0, 72, 86, 71, 6, 4, 11, 13, 15, 27, 18, 24, 26, 40, 30, 28, 41, 39, 48, 22, 9, 68, 77, 90, 103, 114, 126, 9, 42, 35, 34, 25, 31, 15, 11, 13, 3, 75, 69, 6, 67, 16, 25, 66, 0, 10, 14, 4, 13, 25, 4, 1, 36, 14, 68, 87, 100, 122, 126, 126, 126 }, { 38, 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 79, 15, 21, 52, 14, 19, 67, 11, 20, 64, 73, 65, 84, 100, 2, 5, 119, 122, 122, 28, 64, 69, 11, 20, 64, 74, 2, 14, 0, 0, 69, 74, 67, 81, 76, 92, 5, 70, 74, 64, 77, 75, 88, 10, 64, 66, 70, 5, 3, 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 18, 70, 90, 72, 21, 12, 42, 37, 9, 12, 66, 14, 66, 67, 69, 78, 79, 82, 82, 23, 0, 8, 75, 68, 74, 71, 80, 64, 72, 71, 81, 17, 68, 2, 72, 81, 66, 73, 0, 5, 3, 1, 11, 9, 73, 65, 1, 67, 2, 72, 6, 72, 0, 6, 2, 15, 8, 1, 73, 4, 69, 72, 67, 86, 71, 6, 0, 6, 7, 7, 26, 13, 3, 65, 6, 65, 67, 4, 94, 70, 0, 81, 64, 71, 9, 3, 70, 13, 6, 4, 77, 9, 71, 76, 3, 87, 13, 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, 71, 0, 69, 67, 64, 67, 67, 64, 69, 80, 69, 72, 76, 75, 82, 6, 10, 4, 3, 64, 67, 66, 66, 73, 81, 79, 82, 94, 90, 104, 69, 69, 85, 1, 68, 71, 73, 79, 84, 79, 81, 85, 77, 82, 92, 85, 90, 64, 23, 18, 11, 5, 8, 1, 67, 66, 68, 4, 33, 21, 15, 8, 16, 6, 4, 65, 4, 3, 40, 29, 23, 16, 20, 5, 64, 68, 70, 70, 42, 22, 7, 2, 9, 66, 75, 75, 5, 42, 29, 17, 10, 19, 5, 64, 67, 69, 62, 80, 74, 67, 72, 74, 70, 66, 64, 67, 65, 1, 3, 76, 71, 81, 75, 13, 76, 85, 72, 65, 68, 74, 76, 71, 74, 80, 85, 88, 2, 3, 14, 0, 68, 4, 4, 4, 67, 64, 1, 0, 75, 71, 76, 65, 8, 82, 64, 14, 67, 4, 1, 5, 17, 66, 66, 5, 11, 76, 77, 95, 24, 27, 34, 16, 12, 19, 14, 14, 16, 8, 12, 12, 64, 64, 71, 66, 2, 70, 87, 67, 68, 69, 1, 67, 6, 9, 65, 4, 8, 9, 7, 5, 3, 76, 70, 70, 85, 76, 83, 83, 96, 93, 97, 99, 76, 75, 99, 66, 66, 75, 86, 79, 80, 86, 80, 85, 88, 95, 97, 105, 108, 64, 73, 87, 71, 6, 4, 11, 13, 15, 28, 18, 25, 26, 41, 31, 29, 42, 40, 47, 20, 6, 71, 80, 93, 107, 117, 126, 9, 42, 35, 34, 25, 31, 15, 11, 13, 3, 75, 69, 6, 67, 17, 26, 66, 0, 10, 14, 4, 13, 25, 4, 0, 34, 11, 71, 90, 104, 126, 126, 126, 126 }, { 37, 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 80, 14, 20, 52, 14, 22, 67, 12, 21, 64, 73, 64, 85, 101, 2, 4, 120, 123, 122, 31, 1, 69, 12, 21, 64, 73, 4, 15, 0, 1, 68, 73, 67, 81, 75, 92, 5, 69, 73, 64, 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 18, 69, 89, 70, 23, 14, 44, 39, 11, 13, 64, 16, 64, 66, 67, 77, 78, 81, 81, 24, 1, 9, 74, 67, 73, 70, 78, 64, 72, 71, 81, 18, 68, 3, 71, 80, 65, 72, 1, 5, 3, 2, 11, 9, 72, 65, 2, 67, 2, 71, 6, 72, 0, 6, 1, 15, 8, 1, 72, 5, 68, 72, 67, 85, 71, 6, 0, 7, 7, 7, 27, 14, 4, 65, 7, 65, 67, 4, 94, 70, 0, 81, 64, 72, 9, 3, 70, 14, 7, 4, 78, 9, 71, 75, 3, 87, 13, 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, 71, 0, 69, 67, 64, 67, 67, 64, 69, 79, 69, 71, 76, 76, 81, 5, 9, 3, 2, 66, 69, 67, 67, 75, 82, 80, 83, 96, 91, 105, 69, 69, 85, 0, 69, 72, 74, 80, 85, 80, 82, 85, 77, 82, 91, 84, 89, 0, 24, 18, 11, 6, 9, 2, 66, 65, 66, 5, 33, 21, 15, 8, 17, 7, 5, 64, 6, 3, 41, 30, 23, 16, 21, 5, 64, 67, 69, 70, 43, 22, 7, 2, 10, 65, 74, 74, 5, 42, 29, 17, 10, 19, 5, 0, 66, 68, 62, 79, 73, 66, 70, 72, 69, 64, 1, 65, 0, 3, 5, 75, 70, 80, 74, 15, 75, 85, 71, 64, 67, 74, 76, 71, 74, 80, 86, 89, 2, 3, 15, 0, 68, 4, 4, 4, 67, 64, 1, 0, 75, 71, 76, 64, 9, 82, 64, 14, 67, 4, 1, 5, 18, 66, 67, 5, 11, 76, 77, 96, 24, 27, 34, 15, 11, 18, 12, 12, 14, 6, 10, 10, 66, 66, 72, 68, 64, 73, 91, 69, 70, 70, 0, 68, 6, 10, 64, 6, 11, 8, 6, 4, 2, 78, 71, 71, 87, 77, 84, 84, 97, 94, 98, 99, 76, 75, 100, 67, 67, 76, 87, 80, 81, 87, 81, 86, 89, 96, 98, 106, 108, 64, 73, 87, 70, 7, 5, 12, 14, 16, 30, 19, 26, 27, 43, 33, 30, 44, 42, 47, 19, 4, 73, 82, 95, 110, 119, 126, 10, 43, 36, 35, 26, 32, 16, 12, 14, 4, 74, 68, 7, 66, 19, 28, 65, 1, 11, 15, 5, 14, 26, 4, 0, 33, 9, 73, 92, 107, 126, 126, 126, 126 }, { 36, 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 81, 13, 19, 52, 14, 24, 67, 13, 22, 64, 74, 64, 86, 102, 1, 2, 122, 124, 123, 34, 2, 69, 13, 22, 64, 73, 5, 15, 64, 1, 68, 72, 67, 81, 75, 92, 5, 69, 72, 64, 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 18, 69, 89, 68, 25, 16, 45, 41, 12, 14, 0, 17, 0, 65, 65, 77, 78, 81, 80, 24, 1, 9, 73, 67, 72, 70, 77, 64, 72, 71, 81, 18, 68, 3, 71, 80, 65, 72, 1, 5, 3, 2, 11, 9, 72, 65, 2, 67, 2, 71, 5, 72, 0, 5, 0, 14, 7, 0, 71, 5, 68, 73, 68, 85, 72, 6, 0, 7, 7, 7, 28, 14, 4, 65, 7, 65, 68, 4, 95, 70, 0, 82, 64, 73, 9, 3, 71, 14, 7, 4, 79, 9, 72, 75, 3, 88, 12, 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, 71, 0, 69, 67, 64, 67, 68, 65, 70, 79, 69, 71, 77, 77, 81, 3, 7, 1, 0, 68, 71, 69, 69, 77, 84, 82, 85, 98, 93, 106, 69, 69, 86, 64, 70, 73, 75, 82, 86, 81, 83, 86, 78, 83, 91, 84, 88, 0, 24, 18, 11, 6, 9, 2, 66, 65, 65, 6, 33, 21, 15, 8, 17, 7, 6, 0, 8, 3, 41, 30, 23, 16, 21, 5, 64, 67, 68, 70, 43, 22, 7, 2, 10, 65, 74, 73, 5, 41, 28, 16, 9, 19, 5, 0, 66, 68, 62, 78, 72, 65, 69, 71, 68, 0, 2, 64, 1, 4, 7, 74, 69, 80, 73, 16, 75, 86, 71, 0, 67, 74, 77, 71, 75, 81, 87, 90, 1, 3, 15, 0, 69, 3, 4, 4, 68, 65, 1, 0, 76, 71, 76, 64, 9, 82, 65, 13, 68, 4, 0, 4, 18, 67, 68, 5, 11, 77, 78, 98, 23, 26, 33, 14, 10, 17, 10, 10, 12, 4, 7, 7, 69, 68, 73, 71, 67, 76, 95, 72, 72, 72, 64, 69, 6, 11, 64, 7, 13, 6, 4, 2, 0, 80, 73, 73, 89, 78, 86, 85, 99, 95, 99, 100, 77, 75, 101, 68, 68, 78, 89, 82, 83, 88, 83, 88, 90, 97, 99, 107, 109, 65, 74, 88, 70, 7, 5, 13, 14, 17, 31, 20, 27, 28, 44, 34, 31, 45, 43, 46, 17, 2, 75, 85, 98, 113, 122, 126, 10, 43, 36, 35, 26, 33, 16, 12, 14, 4, 74, 68, 8, 66, 20, 29, 65, 2, 12, 16, 5, 14, 27, 4, 0, 32, 7, 75, 95, 110, 126, 126, 126, 126 }, { 35, 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 83, 11, 18, 52, 14, 26, 67, 14, 23, 64, 75, 64, 87, 103, 1, 0, 123, 125, 123, 37, 3, 69, 14, 23, 64, 72, 6, 16, 64, 1, 67, 71, 68, 82, 75, 92, 5, 69, 72, 64, 77, 74, 88, 12, 0, 65, 69, 7, 3, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 18, 69, 89, 66, 27, 17, 47, 43, 13, 15, 2, 18, 1, 64, 0, 76, 78, 80, 79, 24, 1, 9, 73, 67, 72, 69, 75, 64, 73, 72, 82, 18, 68, 3, 70, 80, 64, 72, 2, 5, 3, 2, 11, 8, 72, 65, 2, 67, 2, 71, 4, 72, 0, 4, 64, 13, 6, 64, 71, 6, 68, 74, 68, 84, 72, 6, 0, 7, 7, 7, 29, 14, 4, 65, 8, 65, 69, 4, 96, 70, 0, 82, 65, 74, 9, 3, 72, 14, 7, 4, 81, 9, 73, 75, 3, 88, 11, 19, 13, 14, 17, 15, 8, 16, 16, 66, 4, 9, 3, 1, 71, 64, 70, 68, 65, 68, 69, 66, 70, 79, 70, 71, 78, 79, 81, 2, 5, 64, 65, 70, 73, 71, 71, 79, 86, 83, 86, 100, 95, 107, 69, 69, 86, 65, 71, 74, 77, 83, 88, 82, 84, 87, 78, 83, 91, 84, 88, 1, 24, 18, 11, 6, 9, 2, 66, 64, 64, 6, 33, 21, 15, 8, 18, 8, 7, 1, 10, 3, 41, 30, 23, 16, 22, 5, 64, 67, 68, 70, 43, 22, 6, 2, 10, 65, 74, 72, 5, 41, 28, 15, 8, 19, 5, 0, 65, 67, 62, 77, 71, 65, 68, 70, 67, 2, 3, 0, 2, 6, 8, 74, 68, 80, 73, 18, 75, 86, 71, 0, 67, 75, 77, 71, 76, 82, 88, 91, 1, 2, 15, 64, 69, 3, 3, 4, 69, 65, 0, 0, 77, 71, 77, 64, 9, 83, 65, 13, 69, 4, 64, 4, 18, 68, 69, 4, 10, 78, 78, 99, 22, 25, 33, 13, 9, 15, 8, 8, 10, 1, 5, 5, 72, 71, 74, 74, 70, 79, 99, 75, 75, 74, 65, 70, 6, 12, 64, 8, 15, 4, 2, 0, 65, 82, 75, 74, 91, 80, 87, 86, 100, 97, 100, 101, 77, 76, 102, 69, 70, 79, 91, 84, 85, 90, 84, 90, 92, 98, 101, 109, 110, 66, 75, 89, 70, 8, 5, 13, 15, 17, 32, 21, 28, 29, 45, 35, 32, 47, 44, 46, 16, 0, 78, 87, 101, 116, 125, 126, 11, 44, 37, 36, 26, 34, 17, 12, 15, 5, 74, 67, 8, 65, 21, 30, 65, 2, 12, 16, 5, 15, 28, 4, 0, 31, 5, 78, 98, 113, 126, 126, 126, 126 }, { 33, 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 84, 10, 16, 52, 14, 29, 68, 15, 23, 64, 76, 64, 88, 104, 0, 65, 125, 126, 124, 40, 4, 69, 15, 23, 64, 72, 7, 16, 65, 1, 67, 71, 68, 82, 74, 92, 5, 69, 71, 65, 78, 74, 88, 12, 1, 65, 68, 7, 3, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 18, 69, 89, 65, 28, 19, 48, 44, 14, 16, 3, 20, 2, 0, 2, 76, 77, 80, 78, 24, 1, 9, 72, 67, 71, 69, 74, 65, 73, 72, 82, 18, 68, 3, 70, 80, 64, 72, 2, 5, 2, 2, 11, 8, 72, 65, 2, 67, 2, 71, 4, 73, 0, 4, 65, 12, 5, 65, 70, 6, 68, 75, 69, 84, 73, 6, 64, 7, 7, 7, 30, 15, 4, 66, 8, 66, 70, 3, 97, 70, 0, 83, 65, 75, 8, 2, 72, 15, 7, 4, 82, 8, 74, 75, 3, 89, 11, 19, 13, 14, 17, 15, 8, 16, 15, 66, 4, 9, 3, 1, 72, 64, 70, 68, 65, 68, 69, 67, 71, 79, 70, 71, 79, 80, 81, 0, 3, 66, 67, 72, 75, 73, 73, 81, 88, 85, 88, 103, 97, 108, 69, 70, 87, 66, 72, 75, 78, 85, 89, 83, 86, 87, 79, 84, 91, 84, 87, 1, 24, 18, 11, 6, 9, 2, 66, 64, 0, 7, 33, 21, 15, 8, 18, 8, 7, 1, 12, 3, 41, 30, 23, 16, 22, 5, 64, 67, 67, 70, 43, 21, 6, 2, 10, 65, 74, 72, 5, 40, 27, 14, 7, 19, 5, 0, 65, 67, 62, 76, 70, 64, 66, 69, 66, 3, 5, 1, 4, 7, 10, 73, 68, 79, 72, 19, 74, 87, 71, 1, 67, 75, 78, 72, 77, 83, 89, 92, 0, 2, 15, 64, 70, 2, 3, 4, 70, 66, 0, 64, 78, 72, 77, 64, 9, 83, 66, 12, 70, 4, 65, 3, 18, 69, 70, 4, 10, 79, 79, 101, 21, 25, 32, 12, 7, 14, 6, 6, 8, 64, 2, 2, 75, 73, 76, 77, 74, 82, 103, 77, 77, 76, 66, 72, 6, 13, 0, 9, 17, 3, 0, 65, 66, 84, 77, 76, 93, 81, 89, 88, 102, 98, 101, 102, 78, 76, 103, 71, 71, 81, 92, 86, 87, 91, 86, 92, 93, 99, 102, 110, 110, 67, 75, 90, 70, 8, 6, 14, 15, 18, 33, 21, 29, 30, 46, 36, 33, 48, 45, 45, 14, 65, 80, 90, 104, 119, 126, 126, 11, 44, 37, 36, 27, 34, 17, 13, 15, 5, 73, 67, 9, 65, 22, 31, 65, 3, 13, 17, 5, 15, 29, 4, 0, 29, 3, 80, 101, 116, 126, 126, 126, 126 }, { 32, 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 86, 8, 15, 52, 14, 31, 68, 16, 24, 64, 76, 0, 89, 105, 0, 67, 126, 126, 124, 43, 6, 69, 16, 24, 64, 72, 8, 16, 65, 1, 67, 70, 68, 83, 74, 92, 5, 69, 70, 65, 78, 74, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 18, 68, 89, 0, 30, 20, 50, 46, 16, 17, 4, 21, 3, 1, 4, 75, 77, 79, 77, 24, 1, 9, 72, 67, 71, 68, 72, 65, 73, 72, 82, 18, 68, 4, 69, 80, 64, 72, 3, 5, 2, 2, 11, 8, 71, 65, 3, 67, 2, 71, 3, 73, 0, 3, 66, 12, 4, 66, 69, 7, 68, 76, 69, 83, 73, 6, 64, 7, 7, 7, 31, 15, 4, 66, 8, 66, 71, 3, 98, 70, 0, 83, 66, 76, 8, 2, 73, 15, 7, 4, 83, 8, 75, 75, 3, 89, 10, 19, 13, 14, 17, 15, 8, 16, 15, 66, 4, 9, 3, 1, 72, 64, 70, 69, 65, 68, 70, 68, 71, 79, 71, 71, 80, 81, 81, 64, 1, 67, 68, 74, 77, 75, 75, 83, 90, 87, 90, 105, 98, 109, 69, 70, 87, 67, 73, 76, 79, 86, 91, 84, 87, 88, 79, 84, 91, 84, 87, 1, 24, 18, 11, 6, 9, 3, 65, 0, 1, 7, 33, 21, 15, 8, 18, 9, 8, 2, 14, 3, 41, 30, 23, 16, 22, 5, 64, 67, 66, 70, 44, 21, 5, 2, 10, 65, 74, 71, 5, 40, 26, 13, 6, 19, 5, 0, 65, 66, 62, 75, 69, 0, 65, 68, 65, 4, 6, 2, 5, 9, 12, 72, 67, 79, 72, 20, 74, 87, 70, 2, 67, 76, 78, 72, 77, 84, 90, 93, 64, 1, 15, 65, 70, 2, 3, 4, 71, 67, 0, 64, 79, 72, 77, 64, 9, 83, 67, 12, 70, 4, 66, 2, 18, 70, 71, 3, 9, 80, 80, 103, 20, 24, 32, 11, 6, 12, 4, 4, 6, 67, 64, 0, 78, 75, 77, 80, 77, 85, 107, 80, 80, 78, 67, 73, 6, 14, 0, 10, 19, 1, 64, 67, 68, 86, 79, 77, 95, 82, 91, 89, 104, 100, 102, 103, 78, 76, 104, 72, 73, 83, 94, 87, 88, 93, 87, 93, 95, 100, 103, 111, 111, 67, 76, 91, 69, 9, 6, 14, 16, 19, 34, 22, 30, 31, 48, 37, 34, 49, 47, 45, 12, 67, 83, 92, 107, 122, 126, 126, 11, 45, 38, 37, 27, 35, 17, 13, 16, 6, 73, 67, 10, 64, 23, 32, 64, 4, 13, 17, 5, 16, 30, 4, 0, 28, 1, 83, 104, 119, 126, 126, 126, 126 }, { 31, 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 87, 7, 14, 52, 14, 33, 68, 16, 25, 64, 77, 0, 90, 106, 64, 68, 126, 126, 125, 46, 7, 69, 16, 25, 64, 71, 9, 17, 66, 2, 66, 69, 69, 83, 74, 92, 5, 68, 70, 65, 78, 73, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 18, 68, 88, 2, 32, 22, 51, 48, 17, 18, 6, 22, 4, 1, 5, 75, 77, 79, 77, 25, 1, 9, 71, 66, 70, 68, 71, 65, 74, 73, 83, 18, 68, 4, 69, 80, 0, 72, 3, 5, 2, 3, 11, 7, 71, 65, 3, 67, 2, 71, 2, 73, 0, 2, 67, 11, 4, 66, 69, 7, 68, 76, 70, 83, 74, 6, 64, 7, 7, 7, 31, 15, 4, 66, 9, 66, 72, 3, 98, 71, 0, 84, 66, 77, 8, 2, 74, 15, 7, 4, 85, 8, 76, 75, 3, 90, 9, 19, 12, 14, 17, 15, 8, 16, 15, 66, 3, 9, 2, 1, 72, 65, 71, 69, 66, 69, 71, 69, 72, 79, 71, 70, 81, 83, 81, 66, 64, 69, 70, 76, 79, 77, 77, 85, 92, 88, 91, 107, 100, 110, 69, 70, 88, 68, 74, 77, 81, 88, 92, 85, 88, 89, 80, 85, 91, 84, 86, 2, 24, 18, 11, 7, 9, 3, 65, 0, 2, 8, 32, 21, 15, 8, 19, 9, 9, 3, 16, 3, 42, 30, 23, 16, 23, 5, 64, 66, 66, 70, 44, 21, 5, 2, 10, 64, 73, 70, 5, 39, 26, 12, 6, 19, 5, 1, 64, 66, 62, 75, 68, 0, 64, 67, 64, 6, 7, 3, 6, 10, 13, 72, 66, 79, 71, 22, 74, 88, 70, 2, 67, 76, 79, 72, 78, 85, 91, 94, 64, 1, 15, 65, 71, 1, 2, 4, 71, 67, 64, 64, 79, 72, 78, 64, 9, 84, 67, 11, 71, 3, 67, 2, 19, 70, 72, 3, 9, 80, 80, 104, 19, 23, 31, 10, 5, 11, 2, 2, 4, 69, 66, 66, 81, 78, 78, 82, 80, 88, 111, 83, 82, 80, 68, 74, 6, 15, 0, 12, 22, 64, 66, 69, 70, 88, 81, 79, 97, 84, 92, 90, 105, 101, 103, 104, 79, 77, 105, 73, 74, 84, 96, 89, 90, 94, 89, 95, 96, 102, 105, 113, 112, 68, 77, 92, 69, 9, 6, 15, 16, 19, 36, 23, 31, 32, 49, 39, 35, 51, 48, 44, 11, 69, 85, 95, 109, 125, 126, 126, 12, 45, 38, 37, 27, 36, 18, 13, 16, 6, 73, 66, 10, 64, 24, 33, 64, 4, 14, 18, 5, 16, 31, 4, 0, 27, 64, 85, 107, 123, 126, 126, 126, 126 }, { 30, 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 89, 5, 12, 52, 14, 36, 69, 17, 25, 64, 78, 0, 91, 107, 64, 70, 126, 126, 125, 49, 8, 69, 17, 25, 64, 71, 10, 17, 66, 2, 66, 69, 69, 84, 73, 92, 5, 68, 69, 66, 78, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 18, 68, 88, 3, 34, 23, 53, 50, 18, 19, 7, 24, 5, 2, 7, 74, 76, 78, 76, 25, 1, 9, 71, 66, 70, 67, 69, 66, 74, 73, 83, 18, 68, 4, 68, 80, 0, 72, 4, 5, 1, 3, 11, 7, 71, 65, 3, 67, 2, 71, 2, 73, 0, 2, 68, 10, 3, 67, 68, 8, 68, 77, 70, 82, 74, 6, 65, 7, 7, 7, 32, 16, 4, 66, 9, 66, 73, 3, 99, 71, 0, 84, 67, 78, 7, 2, 74, 16, 7, 4, 86, 7, 77, 75, 3, 90, 9, 19, 12, 14, 17, 15, 8, 16, 15, 66, 3, 9, 2, 1, 72, 65, 71, 70, 66, 69, 71, 70, 72, 79, 72, 70, 82, 84, 81, 67, 66, 71, 72, 78, 81, 79, 79, 87, 94, 90, 93, 110, 102, 111, 69, 71, 88, 69, 75, 78, 82, 89, 94, 86, 89, 89, 80, 85, 91, 84, 86, 2, 24, 18, 11, 7, 9, 3, 65, 1, 3, 8, 32, 21, 15, 8, 19, 10, 10, 3, 18, 3, 42, 30, 23, 16, 23, 5, 64, 66, 65, 70, 44, 20, 4, 2, 10, 64, 73, 70, 5, 39, 25, 11, 5, 19, 5, 1, 64, 65, 62, 74, 67, 1, 1, 66, 0, 7, 9, 4, 8, 12, 15, 71, 65, 78, 71, 23, 73, 88, 70, 3, 67, 77, 79, 73, 79, 86, 92, 95, 65, 0, 15, 66, 71, 1, 2, 4, 72, 68, 64, 65, 80, 72, 78, 64, 9, 84, 68, 11, 72, 3, 68, 1, 19, 71, 73, 2, 8, 81, 81, 106, 18, 23, 31, 9, 3, 9, 0, 0, 2, 72, 69, 68, 84, 80, 80, 85, 84, 91, 115, 85, 85, 82, 69, 75, 6, 16, 1, 13, 24, 65, 68, 71, 71, 90, 83, 80, 99, 85, 94, 92, 107, 103, 104, 105, 79, 77, 106, 75, 76, 86, 97, 91, 92, 96, 90, 97, 98, 103, 106, 114, 112, 69, 77, 93, 69, 10, 7, 15, 17, 20, 37, 24, 32, 33, 50, 40, 36, 52, 49, 44, 9, 71, 88, 97, 112, 126, 126, 126, 12, 46, 39, 38, 28, 37, 18, 14, 17, 7, 72, 66, 11, 0, 25, 34, 64, 5, 14, 18, 5, 17, 32, 4, 0, 25, 66, 88, 110, 126, 126, 126, 126, 126 }, { 28, 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 90, 4, 11, 52, 14, 38, 69, 18, 26, 64, 79, 0, 92, 109, 65, 72, 126, 126, 126, 51, 9, 69, 18, 26, 64, 71, 11, 17, 67, 2, 66, 68, 70, 84, 73, 93, 5, 68, 69, 66, 79, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 18, 68, 88, 5, 35, 25, 54, 51, 19, 20, 8, 25, 6, 3, 9, 74, 76, 78, 75, 25, 1, 9, 70, 66, 69, 67, 68, 66, 75, 74, 84, 18, 68, 4, 68, 80, 0, 72, 4, 4, 1, 3, 11, 6, 71, 65, 3, 67, 1, 71, 1, 74, 0, 1, 70, 9, 2, 68, 68, 8, 68, 78, 71, 82, 75, 5, 65, 7, 7, 7, 33, 16, 4, 67, 9, 67, 74, 2, 100, 71, 0, 85, 67, 79, 7, 1, 75, 16, 7, 4, 88, 7, 78, 75, 3, 91, 8, 18, 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, 2, 0, 73, 66, 72, 70, 67, 70, 72, 71, 73, 79, 72, 70, 83, 86, 81, 69, 68, 73, 74, 80, 84, 81, 81, 89, 96, 92, 95, 112, 104, 112, 69, 71, 89, 70, 77, 80, 84, 91, 95, 88, 91, 90, 81, 86, 91, 84, 85, 2, 24, 18, 11, 7, 9, 3, 65, 1, 4, 9, 32, 21, 15, 8, 19, 10, 10, 4, 19, 3, 42, 30, 23, 15, 23, 5, 64, 66, 65, 70, 44, 20, 4, 2, 10, 64, 73, 69, 5, 38, 24, 10, 4, 18, 5, 1, 64, 65, 62, 73, 66, 1, 2, 65, 0, 8, 10, 5, 9, 13, 16, 71, 65, 78, 70, 24, 73, 89, 70, 3, 67, 77, 80, 73, 80, 87, 94, 96, 66, 0, 15, 66, 72, 0, 1, 3, 73, 69, 65, 65, 81, 73, 79, 64, 9, 85, 69, 10, 73, 3, 69, 0, 19, 72, 74, 2, 8, 82, 82, 108, 17, 22, 30, 7, 2, 8, 65, 65, 64, 74, 72, 71, 87, 83, 81, 88, 87, 94, 119, 88, 87, 84, 71, 77, 6, 16, 1, 14, 26, 67, 70, 73, 73, 93, 85, 82, 101, 87, 96, 93, 109, 104, 105, 106, 80, 78, 107, 76, 77, 88, 99, 93, 94, 97, 92, 99, 99, 104, 108, 116, 113, 70, 78, 94, 69, 10, 7, 16, 17, 20, 38, 24, 33, 34, 51, 41, 37, 53, 50, 43, 7, 73, 90, 100, 115, 126, 126, 126, 12, 46, 39, 38, 28, 37, 18, 14, 17, 7, 72, 66, 11, 0, 26, 35, 64, 5, 15, 19, 5, 17, 32, 4, 64, 24, 68, 90, 113, 126, 126, 126, 126, 126 }, { 27, 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 91, 3, 10, 52, 14, 40, 69, 19, 27, 64, 79, 1, 93, 110, 66, 74, 126, 126, 126, 54, 11, 69, 19, 27, 64, 70, 12, 18, 68, 2, 65, 67, 70, 84, 73, 93, 5, 68, 68, 66, 79, 73, 88, 14, 2, 0, 67, 9, 3, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 18, 67, 88, 7, 37, 27, 55, 53, 21, 21, 10, 26, 8, 4, 11, 74, 76, 78, 74, 25, 1, 9, 69, 66, 68, 66, 67, 66, 75, 74, 84, 18, 68, 5, 67, 79, 1, 72, 4, 4, 1, 3, 11, 6, 70, 65, 4, 67, 1, 70, 0, 74, 0, 0, 71, 9, 1, 69, 67, 8, 67, 79, 72, 82, 76, 5, 65, 8, 7, 7, 34, 16, 4, 67, 10, 67, 74, 2, 101, 71, 0, 86, 67, 80, 7, 1, 76, 16, 7, 4, 89, 7, 78, 75, 3, 92, 7, 18, 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, 2, 0, 73, 66, 72, 70, 67, 70, 73, 71, 73, 79, 72, 70, 84, 87, 81, 71, 69, 74, 75, 82, 86, 82, 82, 91, 98, 93, 96, 114, 105, 113, 69, 71, 90, 71, 78, 81, 85, 92, 96, 89, 92, 91, 82, 87, 91, 83, 84, 3, 25, 18, 11, 7, 10, 4, 64, 2, 5, 10, 32, 21, 15, 8, 20, 10, 11, 5, 21, 3, 42, 30, 23, 15, 24, 5, 64, 66, 64, 70, 45, 20, 4, 2, 11, 64, 73, 68, 5, 38, 24, 10, 3, 18, 5, 1, 0, 64, 62, 72, 65, 2, 3, 0, 1, 10, 11, 7, 10, 14, 18, 70, 64, 78, 69, 26, 73, 90, 69, 4, 67, 77, 81, 73, 80, 88, 95, 97, 66, 0, 15, 66, 72, 64, 1, 3, 74, 69, 65, 65, 82, 73, 79, 0, 10, 85, 69, 9, 73, 3, 69, 0, 19, 73, 75, 2, 8, 83, 82, 109, 17, 21, 29, 6, 1, 7, 67, 67, 66, 76, 74, 74, 89, 85, 82, 91, 90, 97, 123, 91, 89, 85, 72, 78, 6, 17, 1, 15, 28, 69, 71, 75, 75, 95, 86, 83, 103, 88, 97, 94, 110, 105, 106, 106, 80, 78, 108, 77, 78, 89, 101, 94, 95, 98, 93, 100, 100, 105, 109, 117, 114, 70, 79, 94, 68, 10, 7, 17, 18, 21, 39, 25, 34, 35, 53, 42, 38, 55, 52, 42, 6, 75, 92, 103, 118, 126, 126, 126, 13, 46, 39, 39, 28, 38, 19, 14, 18, 8, 72, 65, 12, 0, 27, 37, 0, 6, 16, 20, 5, 18, 33, 4, 64, 23, 70, 92, 115, 126, 126, 126, 126, 126 }, { 26, 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 93, 1, 8, 52, 14, 43, 70, 20, 27, 64, 80, 1, 94, 111, 66, 76, 126, 126, 126, 57, 12, 69, 20, 27, 64, 70, 13, 18, 68, 2, 65, 67, 70, 85, 72, 93, 5, 68, 67, 67, 79, 73, 88, 15, 3, 0, 66, 10, 3, 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 18, 67, 88, 8, 39, 28, 57, 55, 22, 22, 11, 28, 9, 5, 13, 73, 75, 77, 73, 25, 1, 9, 69, 66, 68, 66, 65, 67, 75, 74, 84, 18, 68, 5, 67, 79, 1, 72, 5, 4, 0, 3, 11, 6, 70, 65, 4, 67, 1, 70, 0, 74, 0, 0, 72, 8, 0, 70, 66, 9, 67, 80, 72, 81, 76, 5, 66, 8, 7, 7, 35, 17, 4, 67, 10, 67, 75, 2, 102, 71, 0, 86, 68, 81, 6, 1, 76, 17, 7, 4, 90, 6, 79, 75, 3, 92, 7, 18, 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, 2, 0, 73, 66, 72, 71, 67, 70, 73, 72, 74, 79, 73, 70, 85, 88, 81, 72, 71, 76, 77, 84, 88, 84, 84, 93, 100, 95, 98, 117, 107, 114, 69, 72, 90, 72, 79, 82, 86, 94, 98, 90, 93, 91, 82, 87, 91, 83, 84, 3, 25, 18, 11, 7, 10, 4, 64, 2, 6, 10, 32, 21, 15, 8, 20, 11, 12, 5, 23, 3, 42, 30, 23, 15, 24, 5, 64, 66, 0, 70, 45, 19, 3, 2, 11, 64, 73, 68, 5, 37, 23, 9, 2, 18, 5, 1, 0, 64, 62, 71, 64, 3, 5, 1, 2, 11, 13, 8, 12, 16, 20, 69, 0, 77, 69, 27, 72, 90, 69, 5, 67, 78, 81, 74, 81, 89, 96, 98, 67, 64, 15, 67, 73, 64, 1, 3, 75, 70, 65, 66, 83, 73, 79, 0, 10, 85, 70, 9, 74, 3, 70, 64, 19, 74, 76, 1, 7, 84, 83, 111, 16, 21, 29, 5, 64, 5, 69, 69, 68, 79, 77, 76, 92, 87, 84, 94, 94, 100, 126, 93, 92, 87, 73, 79, 6, 18, 2, 16, 30, 70, 73, 77, 76, 97, 88, 85, 105, 89, 99, 96, 112, 107, 107, 107, 81, 78, 109, 79, 80, 91, 102, 96, 97, 100, 95, 102, 102, 106, 110, 118, 114, 71, 79, 95, 68, 11, 8, 17, 18, 22, 40, 26, 35, 36, 54, 43, 39, 56, 53, 42, 4, 77, 95, 105, 121, 126, 126, 126, 13, 47, 40, 39, 29, 39, 19, 15, 18, 8, 71, 65, 13, 1, 28, 38, 0, 7, 16, 20, 5, 18, 34, 4, 64, 21, 72, 95, 118, 126, 126, 126, 126, 126 }, { 25, 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 94, 0, 7, 52, 14, 45, 70, 20, 28, 64, 81, 1, 95, 112, 67, 77, 126, 126, 126, 60, 13, 69, 20, 28, 64, 69, 14, 19, 69, 3, 64, 66, 71, 85, 72, 93, 5, 67, 67, 67, 79, 72, 88, 15, 3, 0, 66, 10, 3, 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 18, 67, 87, 10, 41, 30, 58, 57, 23, 23, 13, 29, 10, 5, 14, 73, 75, 77, 73, 26, 1, 9, 68, 65, 67, 65, 64, 67, 76, 75, 85, 18, 68, 5, 66, 79, 2, 72, 5, 4, 0, 4, 11, 5, 70, 65, 4, 67, 1, 70, 64, 74, 0, 64, 73, 7, 0, 70, 66, 9, 67, 80, 73, 81, 77, 5, 66, 8, 7, 7, 35, 17, 4, 67, 11, 67, 76, 2, 102, 72, 0, 87, 68, 82, 6, 1, 77, 17, 7, 4, 92, 6, 80, 75, 3, 93, 6, 18, 11, 14, 17, 14, 7, 16, 14, 67, 1, 9, 1, 0, 73, 67, 73, 71, 68, 71, 74, 73, 74, 79, 73, 69, 86, 90, 81, 74, 73, 78, 79, 86, 90, 86, 86, 95, 102, 96, 99, 119, 109, 115, 69, 72, 91, 73, 80, 83, 88, 95, 99, 91, 94, 92, 83, 88, 91, 83, 83, 4, 25, 18, 11, 8, 10, 4, 64, 3, 7, 11, 31, 21, 15, 8, 21, 11, 13, 6, 25, 3, 43, 30, 23, 15, 25, 5, 64, 65, 0, 70, 45, 19, 3, 2, 11, 0, 72, 67, 5, 37, 23, 8, 2, 18, 5, 2, 1, 0, 62, 71, 0, 3, 6, 2, 3, 13, 14, 9, 13, 17, 21, 69, 1, 77, 68, 29, 72, 91, 69, 5, 67, 78, 82, 74, 82, 90, 97, 99, 67, 64, 15, 67, 73, 65, 0, 3, 75, 70, 66, 66, 83, 73, 80, 0, 10, 86, 70, 8, 75, 2, 71, 64, 20, 74, 77, 1, 7, 84, 83, 112, 15, 20, 28, 4, 65, 4, 71, 71, 70, 81, 79, 79, 95, 90, 85, 96, 97, 103, 126, 96, 94, 89, 74, 80, 6, 19, 2, 18, 33, 72, 75, 79, 78, 99, 90, 86, 107, 91, 100, 97, 113, 108, 108, 108, 81, 79, 110, 80, 81, 92, 104, 98, 99, 101, 96, 104, 103, 108, 112, 120, 115, 72, 80, 96, 68, 11, 8, 18, 19, 22, 42, 27, 36, 37, 55, 45, 40, 58, 54, 41, 3, 79, 97, 108, 123, 126, 126, 126, 14, 47, 40, 40, 29, 40, 20, 15, 19, 9, 71, 64, 13, 1, 29, 39, 0, 7, 17, 21, 5, 19, 35, 4, 64, 20, 74, 97, 121, 126, 126, 126, 126, 126 }, { 23, 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 96, 65, 6, 52, 14, 47, 70, 21, 29, 64, 82, 1, 96, 113, 67, 79, 126, 126, 126, 62, 14, 69, 21, 29, 64, 69, 15, 19, 69, 3, 64, 65, 71, 86, 72, 93, 5, 67, 66, 67, 80, 72, 88, 16, 3, 0, 66, 11, 3, 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 18, 67, 87, 12, 42, 31, 60, 58, 24, 24, 14, 30, 11, 6, 16, 72, 75, 76, 72, 26, 1, 9, 68, 65, 67, 65, 1, 67, 76, 75, 85, 18, 68, 5, 66, 79, 2, 72, 6, 4, 0, 4, 11, 5, 70, 65, 4, 67, 1, 70, 65, 75, 0, 65, 74, 6, 64, 71, 65, 10, 67, 81, 73, 80, 77, 5, 66, 8, 7, 7, 36, 17, 4, 68, 11, 68, 77, 1, 103, 72, 0, 87, 69, 83, 6, 0, 78, 17, 7, 4, 93, 6, 81, 75, 3, 93, 5, 18, 11, 14, 17, 14, 7, 16, 13, 67, 1, 9, 1, 0, 74, 67, 73, 72, 68, 71, 75, 74, 75, 79, 74, 69, 87, 91, 81, 75, 75, 80, 81, 88, 92, 88, 88, 97, 104, 98, 101, 121, 111, 116, 69, 72, 91, 74, 81, 84, 89, 97, 101, 92, 96, 93, 83, 88, 91, 83, 83, 4, 25, 18, 11, 8, 10, 4, 64, 3, 8, 11, 31, 21, 15, 8, 21, 12, 13, 7, 27, 3, 43, 30, 23, 15, 25, 5, 64, 65, 1, 70, 45, 19, 2, 2, 11, 0, 72, 66, 5, 36, 22, 7, 1, 18, 5, 2, 1, 0, 62, 70, 1, 4, 7, 3, 4, 14, 15, 10, 14, 19, 23, 68, 1, 77, 68, 30, 72, 91, 69, 6, 67, 79, 82, 74, 83, 91, 98, 100, 68, 65, 15, 68, 74, 65, 0, 3, 76, 71, 66, 66, 84, 74, 80, 0, 10, 86, 71, 8, 76, 2, 72, 65, 20, 75, 78, 0, 6, 85, 84, 114, 14, 19, 28, 3, 66, 2, 73, 73, 72, 84, 82, 81, 98, 92, 86, 99, 100, 106, 126, 99, 97, 91, 75, 82, 6, 20, 2, 19, 35, 74, 77, 81, 80, 101, 92, 88, 109, 92, 102, 98, 115, 110, 109, 109, 82, 79, 111, 81, 83, 94, 106, 100, 101, 103, 98, 106, 105, 109, 113, 121, 116, 73, 81, 97, 68, 12, 8, 18, 19, 23, 43, 27, 37, 38, 56, 46, 41, 59, 55, 41, 1, 81, 100, 110, 126, 126, 126, 126, 14, 48, 41, 40, 29, 40, 20, 15, 19, 9, 71, 64, 14, 2, 30, 40, 0, 8, 17, 21, 5, 19, 36, 4, 64, 19, 76, 100, 124, 126, 126, 126, 126, 126 }, { 22, 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 66, 4, 52, 14, 50, 71, 22, 29, 64, 82, 2, 97, 114, 68, 81, 126, 126, 126, 62, 16, 69, 22, 29, 64, 69, 16, 19, 70, 3, 64, 65, 71, 86, 71, 93, 5, 67, 65, 68, 80, 72, 88, 16, 4, 1, 65, 11, 3, 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 18, 66, 87, 13, 44, 33, 61, 60, 26, 25, 15, 32, 12, 7, 18, 72, 74, 76, 71, 26, 1, 9, 67, 65, 66, 64, 2, 68, 76, 75, 85, 18, 68, 6, 65, 79, 2, 72, 6, 4, 64, 4, 11, 5, 69, 65, 5, 67, 1, 70, 65, 75, 0, 65, 75, 6, 65, 72, 64, 10, 67, 82, 74, 80, 78, 5, 67, 8, 7, 7, 37, 18, 4, 68, 11, 68, 78, 1, 104, 72, 0, 88, 69, 84, 5, 0, 78, 18, 7, 4, 94, 5, 82, 75, 3, 94, 5, 18, 11, 14, 17, 14, 7, 16, 13, 67, 1, 9, 1, 0, 74, 67, 73, 72, 68, 71, 75, 75, 75, 79, 74, 69, 88, 92, 81, 77, 77, 81, 82, 90, 94, 90, 90, 99, 106, 100, 103, 124, 112, 117, 69, 73, 92, 75, 82, 85, 90, 98, 102, 93, 97, 93, 84, 89, 91, 83, 82, 4, 25, 18, 11, 8, 10, 5, 0, 4, 9, 12, 31, 21, 15, 8, 21, 12, 14, 7, 29, 3, 43, 30, 23, 15, 25, 5, 64, 65, 2, 70, 46, 18, 2, 2, 11, 0, 72, 66, 5, 36, 21, 6, 0, 18, 5, 2, 1, 1, 62, 69, 2, 5, 9, 4, 5, 15, 17, 11, 16, 20, 25, 67, 2, 76, 67, 31, 71, 92, 68, 7, 67, 79, 83, 75, 83, 92, 99, 101, 69, 65, 15, 68, 74, 66, 0, 3, 77, 72, 66, 67, 85, 74, 80, 0, 10, 86, 72, 7, 76, 2, 73, 66, 20, 76, 79, 0, 6, 86, 85, 116, 13, 19, 27, 2, 68, 1, 75, 75, 74, 86, 85, 84, 101, 94, 88, 102, 104, 109, 126, 101, 99, 93, 76, 83, 6, 21, 3, 20, 37, 75, 78, 83, 81, 103, 94, 89, 111, 93, 104, 100, 117, 111, 110, 110, 82, 79, 112, 83, 84, 96, 107, 101, 102, 104, 99, 107, 106, 110, 114, 122, 116, 73, 81, 98, 67, 12, 9, 19, 20, 24, 44, 28, 38, 39, 58, 47, 42, 60, 57, 40, 64, 83, 102, 113, 126, 126, 126, 126, 14, 48, 41, 41, 30, 41, 20, 16, 20, 10, 70, 64, 15, 2, 31, 41, 1, 9, 18, 22, 5, 20, 37, 4, 64, 17, 78, 102, 126, 126, 126, 126, 126, 126 }, { 21, 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 68, 3, 52, 14, 52, 71, 23, 30, 64, 83, 2, 98, 115, 68, 83, 126, 126, 126, 62, 17, 69, 23, 30, 64, 68, 17, 20, 70, 3, 0, 64, 72, 87, 71, 93, 5, 67, 65, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 18, 66, 87, 15, 46, 34, 62, 62, 27, 26, 17, 33, 13, 8, 20, 71, 74, 75, 70, 26, 1, 9, 67, 65, 66, 64, 4, 68, 77, 76, 86, 18, 68, 6, 65, 79, 3, 72, 7, 4, 64, 4, 11, 4, 69, 65, 5, 67, 1, 70, 66, 75, 0, 66, 76, 5, 66, 73, 64, 11, 67, 83, 74, 79, 78, 5, 67, 8, 7, 7, 38, 18, 4, 68, 12, 68, 79, 1, 105, 72, 0, 88, 70, 85, 5, 0, 79, 18, 7, 4, 96, 5, 83, 75, 3, 94, 4, 18, 11, 14, 17, 14, 7, 16, 13, 67, 0, 9, 1, 0, 74, 68, 74, 73, 69, 72, 76, 76, 76, 79, 75, 69, 89, 94, 81, 78, 79, 83, 84, 92, 96, 92, 92, 101, 108, 101, 104, 126, 114, 118, 69, 73, 92, 76, 83, 86, 92, 100, 104, 94, 98, 94, 84, 89, 91, 83, 82, 5, 25, 18, 11, 8, 10, 5, 0, 4, 10, 12, 31, 21, 15, 8, 22, 13, 15, 8, 31, 3, 43, 30, 23, 15, 26, 5, 64, 65, 2, 70, 46, 18, 1, 2, 11, 0, 72, 65, 5, 35, 21, 5, 64, 18, 5, 2, 2, 1, 62, 68, 3, 5, 10, 5, 6, 17, 18, 12, 17, 22, 26, 67, 3, 76, 67, 33, 71, 92, 68, 7, 67, 80, 83, 75, 84, 93, 100, 102, 69, 66, 15, 69, 75, 66, 64, 3, 78, 72, 67, 67, 86, 74, 81, 0, 10, 87, 72, 7, 77, 2, 74, 66, 20, 77, 80, 64, 5, 87, 85, 117, 12, 18, 27, 1, 69, 64, 77, 77, 76, 89, 87, 86, 104, 97, 89, 105, 107, 112, 126, 104, 102, 95, 77, 84, 6, 22, 3, 21, 39, 77, 80, 85, 83, 105, 96, 91, 113, 95, 105, 101, 118, 113, 111, 111, 83, 80, 113, 84, 86, 97, 109, 103, 104, 106, 101, 109, 108, 111, 116, 124, 117, 74, 82, 99, 67, 13, 9, 19, 20, 24, 45, 29, 39, 40, 59, 48, 43, 62, 58, 40, 65, 85, 105, 115, 126, 126, 126, 126, 15, 49, 42, 41, 30, 42, 21, 16, 20, 10, 70, 0, 15, 3, 32, 42, 1, 9, 18, 22, 5, 20, 38, 4, 64, 16, 80, 105, 126, 126, 126, 126, 126, 126 }, { 20, 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 100, 69, 2, 52, 14, 54, 71, 24, 31, 64, 84, 2, 99, 116, 69, 85, 126, 126, 126, 62, 18, 69, 24, 31, 64, 68, 18, 20, 71, 3, 0, 0, 72, 87, 71, 93, 5, 67, 64, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 18, 66, 87, 17, 48, 36, 62, 62, 28, 27, 18, 34, 14, 9, 22, 71, 74, 75, 69, 26, 1, 9, 66, 65, 65, 0, 5, 68, 77, 76, 86, 18, 68, 6, 64, 79, 3, 72, 7, 4, 64, 4, 11, 4, 69, 65, 5, 67, 1, 70, 67, 75, 0, 67, 77, 4, 67, 74, 0, 11, 67, 84, 75, 79, 79, 5, 67, 8, 7, 7, 39, 18, 4, 68, 12, 68, 80, 1, 106, 72, 0, 89, 70, 86, 5, 0, 80, 18, 7, 4, 97, 5, 84, 75, 3, 95, 3, 18, 11, 14, 17, 14, 7, 16, 13, 67, 0, 9, 1, 0, 74, 68, 74, 73, 69, 72, 77, 77, 76, 79, 75, 69, 90, 95, 81, 80, 81, 85, 86, 94, 98, 94, 94, 103, 110, 103, 106, 126, 116, 119, 69, 73, 93, 77, 84, 87, 93, 101, 105, 95, 99, 95, 85, 90, 91, 83, 81, 5, 25, 18, 11, 8, 10, 5, 0, 5, 11, 13, 31, 21, 15, 8, 22, 13, 16, 9, 33, 3, 43, 30, 23, 15, 26, 5, 64, 65, 3, 70, 46, 18, 1, 2, 11, 0, 72, 64, 5, 35, 20, 4, 65, 18, 5, 2, 2, 2, 62, 67, 4, 6, 11, 6, 7, 18, 19, 13, 18, 23, 28, 66, 4, 76, 66, 34, 71, 93, 68, 8, 67, 80, 84, 75, 85, 94, 101, 103, 70, 66, 15, 69, 75, 67, 64, 3, 79, 73, 67, 67, 87, 74, 81, 0, 10, 87, 73, 6, 78, 2, 75, 67, 20, 78, 81, 64, 5, 88, 86, 119, 11, 17, 26, 0, 70, 65, 79, 79, 78, 91, 90, 89, 107, 99, 90, 108, 110, 115, 126, 107, 104, 97, 78, 85, 6, 23, 3, 22, 41, 79, 82, 87, 85, 107, 98, 92, 115, 96, 107, 102, 120, 114, 112, 112, 83, 80, 114, 85, 87, 99, 111, 105, 106, 107, 102, 111, 109, 112, 117, 125, 118, 75, 83, 100, 67, 13, 9, 20, 21, 25, 46, 30, 40, 41, 60, 49, 44, 62, 59, 39, 67, 87, 107, 118, 126, 126, 126, 126, 15, 49, 42, 42, 30, 43, 21, 16, 21, 11, 70, 0, 16, 3, 33, 43, 1, 10, 19, 23, 5, 21, 39, 4, 64, 15, 82, 107, 126, 126, 126, 126, 126, 126 }, { 18, 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 102, 71, 0, 51, 14, 56, 72, 24, 31, 65, 85, 2, 101, 118, 70, 87, 126, 126, 126, 62, 19, 70, 24, 31, 65, 68, 19, 20, 72, 3, 0, 0, 73, 88, 71, 94, 5, 67, 64, 69, 81, 72, 88, 17, 4, 1, 65, 12, 2, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 18, 66, 87, 18, 49, 37, 62, 62, 29, 28, 19, 35, 15, 9, 23, 71, 74, 75, 69, 26, 1, 9, 66, 65, 65, 0, 6, 69, 78, 77, 87, 18, 68, 6, 64, 79, 3, 72, 7, 3, 65, 4, 10, 3, 69, 66, 5, 67, 0, 70, 68, 76, 64, 68, 79, 3, 68, 75, 0, 11, 67, 85, 76, 79, 80, 4, 68, 8, 7, 7, 39, 18, 4, 69, 12, 69, 81, 0, 107, 73, 64, 90, 71, 87, 4, 64, 81, 18, 7, 4, 99, 4, 85, 75, 3, 96, 2, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, 9, 0, 64, 75, 69, 75, 74, 70, 73, 78, 78, 77, 79, 76, 69, 91, 97, 81, 82, 83, 87, 88, 96, 101, 96, 96, 105, 112, 105, 108, 126, 118, 121, 70, 74, 94, 78, 86, 89, 95, 103, 107, 97, 101, 96, 86, 91, 91, 83, 81, 5, 25, 18, 11, 8, 10, 5, 0, 5, 12, 13, 30, 21, 15, 8, 22, 13, 16, 9, 34, 2, 43, 30, 22, 14, 26, 5, 64, 65, 3, 70, 46, 17, 0, 1, 11, 0, 72, 64, 5, 34, 19, 3, 66, 17, 5, 2, 2, 2, 62, 67, 5, 6, 12, 7, 7, 19, 20, 14, 19, 24, 29, 66, 4, 76, 66, 35, 71, 94, 68, 8, 67, 81, 85, 76, 86, 95, 103, 105, 71, 67, 15, 70, 76, 68, 65, 2, 80, 74, 68, 68, 88, 75, 82, 0, 10, 88, 74, 5, 79, 1, 76, 68, 20, 79, 83, 65, 4, 89, 87, 121, 10, 16, 25, 65, 72, 67, 81, 81, 81, 94, 93, 92, 110, 102, 92, 111, 114, 118, 126, 110, 107, 99, 80, 87, 6, 23, 3, 23, 43, 81, 84, 89, 87, 110, 100, 94, 118, 98, 109, 104, 122, 116, 113, 113, 84, 81, 116, 87, 89, 101, 113, 107, 108, 109, 104, 113, 111, 114, 119, 126, 119, 76, 84, 101, 67, 13, 9, 20, 21, 25, 47, 30, 41, 41, 61, 50, 45, 62, 60, 38, 69, 90, 110, 121, 126, 126, 126, 126, 15, 49, 42, 42, 30, 43, 21, 16, 21, 11, 70, 0, 16, 3, 34, 44, 1, 10, 19, 23, 5, 21, 39, 4, 65, 13, 85, 110, 126, 126, 126, 126, 126, 126 }, { 17, 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 103, 72, 64, 51, 14, 59, 72, 25, 32, 65, 85, 3, 102, 119, 70, 88, 126, 126, 126, 62, 21, 70, 25, 32, 65, 67, 21, 21, 72, 4, 1, 1, 73, 88, 70, 94, 5, 66, 0, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 18, 65, 86, 20, 51, 39, 62, 62, 31, 29, 21, 37, 17, 10, 25, 70, 73, 74, 68, 27, 2, 10, 65, 64, 64, 1, 8, 69, 78, 77, 87, 19, 68, 7, 0, 78, 4, 71, 8, 3, 65, 5, 10, 3, 68, 66, 6, 67, 0, 69, 68, 76, 64, 68, 80, 3, 68, 75, 1, 12, 66, 85, 76, 78, 80, 4, 68, 9, 7, 7, 40, 19, 5, 69, 13, 69, 81, 0, 107, 73, 64, 90, 71, 88, 4, 64, 81, 19, 8, 4, 100, 4, 85, 74, 3, 96, 2, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, 9, 0, 64, 75, 69, 75, 74, 70, 73, 78, 78, 77, 78, 76, 68, 91, 98, 80, 83, 84, 88, 89, 98, 103, 97, 97, 107, 113, 106, 109, 126, 119, 122, 70, 74, 94, 79, 87, 90, 96, 104, 108, 98, 102, 96, 86, 91, 90, 82, 80, 6, 26, 18, 11, 9, 11, 6, 1, 6, 14, 14, 30, 21, 15, 8, 23, 14, 17, 10, 36, 2, 44, 31, 22, 14, 27, 5, 64, 64, 4, 70, 47, 17, 0, 1, 12, 1, 71, 0, 5, 34, 19, 3, 66, 17, 5, 3, 3, 3, 62, 66, 6, 7, 14, 9, 8, 21, 22, 16, 21, 26, 31, 65, 5, 75, 65, 37, 70, 94, 67, 9, 66, 81, 85, 76, 86, 95, 104, 106, 71, 67, 16, 70, 76, 68, 65, 2, 80, 74, 68, 68, 88, 75, 82, 1, 11, 88, 74, 5, 79, 1, 76, 68, 21, 79, 84, 65, 4, 89, 87, 122, 10, 16, 25, 66, 73, 68, 83, 83, 83, 96, 95, 94, 112, 104, 93, 113, 117, 121, 126, 112, 109, 100, 81, 88, 6, 24, 4, 25, 46, 82, 85, 90, 88, 112, 101, 95, 120, 99, 110, 105, 123, 117, 114, 113, 84, 81, 117, 88, 90, 102, 114, 108, 109, 110, 105, 114, 112, 115, 120, 126, 119, 76, 84, 101, 66, 14, 10, 21, 22, 26, 49, 31, 42, 42, 62, 52, 46, 62, 62, 38, 70, 92, 112, 123, 126, 126, 126, 126, 16, 50, 43, 43, 31, 44, 22, 17, 22, 12, 69, 1, 17, 4, 36, 46, 2, 11, 20, 24, 6, 22, 40, 4, 65, 12, 87, 112, 126, 126, 126, 126, 126, 126 }, { 16, 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 104, 73, 65, 51, 14, 61, 72, 26, 33, 65, 86, 3, 103, 120, 71, 90, 126, 126, 126, 62, 22, 70, 26, 33, 65, 67, 22, 21, 73, 4, 1, 2, 73, 88, 70, 94, 5, 66, 1, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 18, 65, 86, 22, 53, 41, 62, 62, 32, 30, 22, 38, 18, 11, 27, 70, 73, 74, 67, 27, 2, 10, 64, 64, 0, 1, 9, 69, 78, 77, 87, 19, 68, 7, 0, 78, 4, 71, 8, 3, 65, 5, 10, 3, 68, 66, 6, 67, 0, 69, 69, 76, 64, 69, 81, 2, 69, 76, 2, 12, 66, 86, 77, 78, 81, 4, 68, 9, 7, 7, 41, 19, 5, 69, 13, 69, 82, 0, 108, 73, 64, 91, 71, 89, 4, 64, 82, 19, 8, 4, 101, 4, 86, 74, 3, 97, 1, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, 9, 0, 64, 75, 69, 75, 74, 70, 73, 79, 79, 78, 78, 76, 68, 92, 99, 80, 85, 86, 90, 91, 100, 105, 99, 99, 109, 115, 108, 111, 126, 121, 123, 70, 74, 95, 80, 88, 91, 97, 106, 109, 99, 103, 97, 87, 92, 90, 82, 79, 6, 26, 18, 11, 9, 11, 6, 1, 6, 15, 15, 30, 21, 15, 8, 23, 14, 18, 11, 38, 2, 44, 31, 22, 14, 27, 5, 64, 64, 5, 70, 47, 17, 0, 1, 12, 1, 71, 1, 5, 33, 18, 2, 67, 17, 5, 3, 3, 3, 62, 65, 7, 8, 15, 10, 9, 22, 23, 17, 22, 27, 33, 64, 6, 75, 64, 38, 70, 95, 67, 10, 66, 81, 86, 76, 87, 96, 105, 107, 72, 67, 16, 70, 77, 69, 65, 2, 81, 75, 68, 68, 89, 75, 82, 1, 11, 88, 75, 4, 80, 1, 77, 69, 21, 80, 85, 65, 4, 90, 88, 124, 9, 15, 24, 67, 74, 69, 85, 85, 85, 98, 98, 97, 115, 106, 94, 116, 120, 124, 126, 115, 111, 102, 82, 89, 6, 25, 4, 26, 48, 84, 87, 92, 90, 114, 103, 97, 122, 100, 112, 106, 125, 118, 115, 114, 85, 81, 118, 89, 91, 104, 116, 110, 111, 111, 107, 116, 113, 116, 121, 126, 120, 77, 85, 102, 66, 14, 10, 22, 22, 27, 50, 32, 43, 43, 62, 53, 47, 62, 62, 37, 72, 94, 114, 126, 126, 126, 126, 126, 16, 50, 43, 43, 31, 45, 22, 17, 22, 12, 69, 1, 18, 4, 37, 47, 2, 12, 21, 25, 6, 22, 41, 4, 65, 11, 89, 114, 126, 126, 126, 126, 126, 126 }, { 15, 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 106, 75, 66, 51, 14, 62, 72, 27, 34, 65, 87, 3, 104, 121, 71, 92, 126, 126, 126, 62, 23, 70, 27, 34, 65, 66, 23, 22, 73, 4, 2, 3, 74, 89, 70, 94, 5, 66, 1, 69, 81, 71, 88, 19, 5, 2, 64, 14, 2, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 18, 65, 86, 24, 55, 42, 62, 62, 33, 31, 24, 39, 19, 12, 29, 69, 73, 73, 66, 27, 2, 10, 64, 64, 0, 2, 11, 69, 79, 78, 88, 19, 68, 7, 1, 78, 5, 71, 9, 3, 65, 5, 10, 2, 68, 66, 6, 67, 0, 69, 70, 76, 64, 70, 82, 1, 70, 77, 2, 13, 66, 87, 77, 77, 81, 4, 68, 9, 7, 7, 42, 19, 5, 69, 14, 69, 83, 0, 109, 73, 64, 91, 72, 90, 4, 64, 83, 19, 8, 4, 103, 4, 87, 74, 3, 97, 0, 17, 10, 14, 17, 13, 6, 16, 12, 68, 65, 9, 0, 64, 75, 70, 76, 75, 71, 74, 80, 80, 78, 78, 77, 68, 93, 101, 80, 86, 88, 92, 93, 102, 107, 101, 101, 111, 117, 109, 112, 126, 123, 124, 70, 74, 95, 81, 89, 92, 99, 107, 111, 100, 104, 98, 87, 92, 90, 82, 79, 7, 26, 18, 11, 9, 11, 6, 1, 7, 16, 15, 30, 21, 15, 8, 24, 15, 19, 12, 40, 2, 44, 31, 22, 14, 28, 5, 64, 64, 5, 70, 47, 17, 64, 1, 12, 1, 71, 2, 5, 33, 18, 1, 68, 17, 5, 3, 4, 4, 62, 64, 8, 8, 16, 11, 10, 24, 24, 18, 23, 29, 34, 64, 7, 75, 64, 40, 70, 95, 67, 10, 66, 82, 86, 76, 88, 97, 106, 108, 72, 68, 16, 71, 77, 69, 66, 2, 82, 75, 69, 68, 90, 75, 83, 1, 11, 89, 75, 4, 81, 1, 78, 69, 21, 81, 86, 66, 3, 91, 88, 125, 8, 14, 24, 68, 75, 71, 87, 87, 87, 101, 100, 99, 118, 109, 95, 119, 123, 126, 126, 118, 114, 104, 83, 90, 6, 26, 4, 27, 50, 86, 89, 94, 92, 116, 105, 98, 124, 102, 113, 107, 126, 120, 116, 115, 85, 82, 119, 90, 93, 105, 118, 112, 113, 113, 108, 118, 115, 117, 123, 126, 121, 78, 86, 103, 66, 15, 10, 22, 23, 27, 51, 33, 44, 44, 62, 54, 48, 62, 62, 37, 73, 96, 117, 126, 126, 126, 126, 126, 17, 51, 44, 44, 31, 46, 23, 17, 23, 13, 69, 2, 18, 5, 38, 48, 2, 12, 21, 25, 6, 23, 42, 4, 65, 10, 91, 117, 126, 126, 126, 126, 126, 126 }, }, { { 62, 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 38, 62, 62, 54, 22, 118, 65, 71, 79, 11, 13, 70, 9, 29, 41, 62, 61, 27, 69, 126, 101, 76, 71, 79, 11, 69, 90, 11, 20, 69, 82, 96, 4, 75, 87, 100, 7, 74, 85, 4, 81, 86, 95, 66, 77, 70, 86, 72, 2, 22, 0, 0, 0, 83, 86, 97, 72, 22, 1, 48, 12, 80, 126, 91, 96, 81, 98, 102, 97, 119, 99, 110, 102, 126, 80, 89, 94, 92, 24, 65, 84, 126, 73, 104, 91, 126, 8, 7, 8, 2, 10, 68, 74, 88, 103, 91, 89, 92, 76, 87, 110, 105, 78, 112, 99, 126, 126, 126, 126, 66, 78, 71, 72, 4, 8, 70, 75, 89, 119, 75, 43, 41, 126, 9, 2, 5, 3, 2, 67, 84, 74, 65, 11, 6, 2, 69, 70, 8, 71, 5, 2, 22, 38, 31, 20, 16, 19, 12, 17, 25, 66, 25, 21, 29, 89, 18, 35, 32, 62, 62, 48, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 53, 62, 62, 62, 62, 62, 62, 62, 56, 62, 62, 62, 27, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 53, 45, 38, 22, 75, 72, 77, 28, 32, 28, 33, 18, 21, 18, 37, 9, 66, 7, 73, 67, 116, 112, 71, 2, 10, 66, 77, 80, 84, 87, 126, 101, 24, 10, 2, 75, 77, 91, 107, 111, 122, 76, 19, 11, 6, 5, 72, 69, 69, 74, 86, 66, 29, 31, 32, 11, 8, 67, 73, 89, 11, 59, 55, 55, 44, 26, 2, 73, 70, 78, 62, 126, 124, 110, 126, 124, 105, 121, 117, 102, 117, 116, 122, 95, 100, 95, 111, 114, 89, 80, 82, 85, 81, 72, 64, 67, 7, 69, 69, 69, 69, 67, 77, 64, 2, 67, 64, 6, 65, 66, 1, 12, 66, 71, 75, 70, 72, 3, 26, 16, 28, 26, 22, 22, 15, 22, 22, 4, 13, 23, 66, 13, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 54, 62, 62, 62, 62, 62, 62, 62, 62, 62, 49, 37, 26, 8, 65, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 43, 33, 19, 15, 14, 18, 41, 41, 42, 43, 35, 39, 29, 21, 24, 13, 70, 9, 71, 83, 31, 14, 9, 85, 81, 77, 81, 80, 73, 74, 83, 71, 67, 2, 66, 66, 4, 4, 62, 62, 62, 62, 62, 60, 53, 36, 6, 71, 39, 27, 21, 11, 6, 0, 65, 67, 82, 81, 76, 72, 78, 72, 68, 70, 76, 66, 1, 6, 2, 3, 9, 5, 62, 62, 62, 62, 62, 60, 53, 36, 6 }, { 62, 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 37, 61, 62, 55, 22, 116, 65, 70, 78, 11, 13, 69, 9, 28, 40, 61, 58, 25, 70, 124, 100, 75, 70, 78, 11, 69, 89, 11, 20, 68, 81, 95, 4, 75, 86, 99, 7, 73, 84, 4, 80, 85, 94, 65, 76, 70, 85, 71, 2, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 48, 12, 80, 124, 89, 94, 79, 95, 100, 95, 117, 97, 108, 100, 124, 80, 88, 93, 91, 24, 65, 83, 124, 72, 103, 90, 125, 8, 7, 8, 2, 11, 68, 73, 87, 102, 90, 88, 91, 75, 86, 108, 103, 77, 110, 97, 122, 122, 123, 124, 65, 77, 70, 71, 4, 9, 69, 74, 88, 116, 74, 41, 40, 124, 9, 3, 5, 4, 3, 66, 82, 73, 64, 11, 6, 2, 68, 69, 7, 70, 5, 2, 22, 37, 31, 20, 16, 19, 12, 17, 24, 65, 25, 21, 29, 89, 18, 35, 32, 62, 62, 47, 62, 62, 62, 61, 62, 62, 62, 62, 62, 62, 52, 62, 62, 62, 62, 62, 62, 62, 54, 62, 60, 62, 26, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 52, 44, 37, 21, 75, 72, 77, 28, 31, 27, 32, 17, 20, 17, 36, 8, 66, 6, 73, 67, 115, 110, 70, 3, 10, 65, 76, 79, 83, 86, 124, 99, 25, 11, 3, 74, 76, 89, 105, 109, 120, 75, 20, 12, 7, 6, 71, 68, 68, 73, 85, 66, 30, 31, 32, 11, 9, 66, 73, 88, 11, 59, 55, 54, 43, 26, 3, 72, 69, 77, 62, 124, 122, 108, 124, 122, 103, 119, 115, 100, 115, 114, 119, 94, 99, 94, 109, 112, 88, 79, 81, 84, 80, 71, 64, 67, 7, 69, 69, 69, 68, 66, 76, 0, 2, 66, 0, 6, 64, 65, 1, 12, 65, 70, 74, 69, 71, 3, 25, 16, 27, 26, 22, 22, 15, 22, 22, 4, 13, 22, 66, 12, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 52, 62, 62, 62, 62, 62, 62, 62, 61, 62, 48, 36, 25, 8, 65, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 42, 32, 18, 15, 14, 17, 40, 40, 41, 41, 34, 38, 28, 20, 23, 12, 70, 8, 71, 83, 30, 13, 8, 84, 80, 76, 80, 78, 71, 73, 82, 70, 66, 3, 65, 65, 4, 4, 62, 62, 62, 62, 60, 56, 49, 32, 4, 70, 39, 28, 22, 12, 7, 1, 64, 66, 81, 80, 75, 71, 77, 71, 67, 69, 75, 65, 2, 6, 3, 4, 9, 5, 62, 62, 62, 62, 60, 56, 49, 32, 4 }, { 62, 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 36, 59, 61, 55, 22, 114, 65, 70, 77, 11, 12, 69, 8, 26, 39, 58, 54, 22, 72, 121, 99, 75, 70, 77, 11, 69, 88, 11, 19, 68, 81, 94, 4, 75, 86, 99, 7, 73, 84, 4, 80, 85, 94, 65, 76, 70, 85, 71, 2, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 47, 12, 80, 122, 88, 93, 77, 93, 99, 94, 115, 96, 107, 99, 122, 80, 88, 93, 91, 24, 65, 82, 122, 72, 102, 89, 123, 8, 7, 8, 1, 11, 68, 73, 86, 101, 89, 87, 90, 75, 85, 107, 102, 76, 109, 96, 117, 118, 120, 121, 65, 77, 70, 71, 4, 9, 69, 74, 88, 114, 74, 39, 38, 121, 9, 3, 5, 4, 3, 66, 80, 72, 64, 11, 6, 2, 67, 68, 6, 70, 5, 2, 21, 36, 30, 20, 15, 19, 12, 17, 23, 65, 24, 20, 28, 89, 18, 34, 31, 62, 62, 46, 60, 62, 62, 59, 62, 62, 62, 62, 62, 62, 50, 62, 62, 62, 62, 62, 62, 62, 52, 62, 58, 62, 24, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 50, 42, 35, 19, 75, 72, 78, 27, 30, 26, 31, 16, 19, 16, 34, 7, 66, 5, 74, 68, 114, 109, 69, 3, 10, 65, 75, 78, 82, 85, 122, 98, 25, 11, 3, 73, 75, 88, 103, 107, 118, 74, 21, 13, 8, 7, 70, 68, 68, 73, 84, 66, 31, 31, 31, 11, 9, 66, 73, 88, 11, 59, 54, 53, 42, 26, 3, 72, 69, 77, 62, 123, 121, 107, 122, 120, 102, 117, 113, 99, 113, 112, 117, 93, 98, 94, 108, 110, 88, 79, 81, 83, 80, 71, 64, 67, 6, 69, 69, 69, 68, 66, 75, 0, 2, 66, 0, 6, 64, 65, 1, 11, 65, 70, 74, 69, 70, 2, 24, 16, 26, 25, 21, 21, 15, 21, 21, 4, 13, 21, 66, 11, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 50, 62, 62, 62, 62, 62, 62, 62, 59, 59, 46, 34, 24, 7, 66, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 40, 30, 16, 14, 13, 15, 39, 39, 39, 39, 32, 36, 26, 19, 21, 11, 71, 7, 72, 84, 28, 12, 7, 84, 80, 75, 80, 77, 70, 73, 81, 69, 65, 3, 65, 64, 4, 4, 62, 62, 62, 62, 57, 52, 45, 28, 1, 70, 39, 28, 22, 12, 8, 1, 64, 66, 81, 80, 75, 71, 77, 70, 66, 69, 75, 65, 2, 6, 3, 5, 9, 5, 62, 62, 62, 62, 57, 52, 45, 28, 1 }, { 62, 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 34, 57, 60, 55, 22, 112, 65, 69, 76, 11, 12, 69, 8, 25, 38, 56, 51, 20, 73, 118, 98, 75, 69, 76, 11, 70, 87, 11, 19, 68, 81, 94, 4, 75, 86, 99, 7, 73, 83, 4, 80, 84, 94, 65, 76, 70, 85, 71, 2, 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 46, 11, 80, 119, 87, 92, 76, 91, 97, 92, 113, 94, 106, 98, 120, 80, 88, 92, 91, 24, 65, 81, 120, 72, 101, 89, 121, 8, 6, 7, 1, 11, 68, 72, 86, 100, 88, 87, 89, 74, 84, 105, 100, 76, 108, 95, 112, 113, 117, 118, 65, 77, 70, 70, 4, 9, 68, 73, 87, 112, 74, 37, 36, 118, 9, 3, 5, 4, 3, 65, 79, 71, 64, 11, 6, 2, 67, 67, 5, 70, 5, 1, 21, 35, 30, 20, 15, 19, 12, 17, 22, 65, 23, 19, 28, 89, 18, 34, 31, 62, 62, 45, 58, 62, 62, 57, 62, 62, 62, 62, 62, 61, 48, 62, 62, 62, 62, 62, 62, 60, 50, 62, 56, 62, 22, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 48, 40, 34, 17, 75, 72, 78, 26, 29, 25, 30, 15, 18, 15, 32, 6, 67, 4, 75, 68, 114, 107, 68, 4, 10, 65, 74, 78, 82, 85, 120, 97, 25, 11, 4, 72, 74, 87, 102, 106, 116, 73, 21, 13, 8, 7, 69, 67, 68, 73, 84, 66, 31, 31, 30, 11, 9, 66, 73, 87, 11, 58, 54, 52, 41, 26, 3, 72, 69, 77, 62, 122, 119, 106, 121, 119, 101, 115, 111, 98, 112, 110, 115, 93, 97, 93, 107, 108, 87, 79, 81, 83, 79, 71, 64, 67, 6, 69, 69, 70, 67, 65, 74, 0, 2, 65, 0, 6, 64, 65, 1, 11, 65, 70, 74, 69, 70, 1, 23, 16, 25, 24, 20, 21, 15, 20, 20, 4, 13, 20, 66, 10, 62, 62, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 48, 62, 62, 62, 62, 62, 62, 62, 57, 57, 44, 32, 22, 6, 67, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 60, 38, 28, 15, 13, 12, 14, 37, 37, 37, 37, 31, 34, 24, 18, 20, 10, 72, 6, 73, 85, 27, 11, 6, 84, 79, 75, 79, 76, 69, 73, 81, 69, 65, 3, 64, 0, 4, 4, 62, 62, 62, 59, 54, 48, 41, 24, 65, 70, 39, 28, 22, 12, 8, 2, 64, 66, 80, 80, 75, 70, 76, 69, 65, 69, 74, 65, 2, 6, 3, 5, 9, 5, 62, 62, 62, 59, 54, 48, 41, 24, 65 }, { 62, 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 33, 55, 59, 55, 21, 110, 65, 69, 75, 10, 11, 69, 7, 23, 37, 53, 47, 17, 75, 115, 97, 75, 69, 75, 10, 70, 86, 11, 18, 68, 80, 93, 4, 75, 86, 99, 7, 73, 83, 4, 80, 84, 93, 65, 76, 70, 85, 70, 2, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 45, 11, 80, 117, 86, 91, 74, 89, 96, 91, 112, 93, 104, 97, 118, 80, 87, 92, 91, 24, 65, 80, 118, 72, 101, 88, 119, 8, 6, 7, 0, 11, 68, 72, 85, 99, 87, 86, 88, 74, 84, 104, 99, 75, 107, 94, 107, 109, 114, 115, 65, 76, 70, 70, 4, 9, 68, 73, 87, 110, 74, 35, 34, 116, 9, 4, 5, 4, 3, 65, 77, 70, 0, 10, 6, 2, 66, 67, 4, 70, 5, 1, 20, 34, 29, 19, 14, 19, 12, 17, 21, 65, 22, 18, 27, 89, 17, 33, 30, 62, 62, 44, 56, 62, 62, 55, 62, 62, 62, 62, 62, 59, 46, 59, 62, 62, 62, 62, 62, 57, 48, 62, 54, 62, 21, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 55, 46, 38, 32, 15, 75, 72, 79, 25, 28, 24, 28, 14, 16, 14, 31, 5, 67, 3, 75, 69, 113, 106, 67, 4, 10, 64, 74, 77, 81, 84, 118, 95, 25, 12, 4, 72, 73, 86, 100, 104, 115, 73, 22, 14, 9, 8, 68, 67, 68, 72, 83, 66, 32, 31, 30, 10, 9, 66, 73, 87, 11, 58, 53, 51, 40, 26, 3, 71, 69, 77, 62, 120, 118, 105, 119, 117, 100, 114, 110, 97, 110, 109, 113, 92, 96, 93, 106, 107, 87, 79, 81, 82, 79, 71, 65, 67, 5, 69, 69, 70, 67, 65, 73, 0, 2, 65, 0, 6, 64, 65, 1, 10, 65, 70, 74, 69, 69, 0, 22, 16, 24, 24, 19, 20, 15, 19, 19, 4, 13, 19, 66, 9, 62, 62, 60, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 46, 62, 62, 62, 62, 62, 62, 62, 54, 54, 42, 30, 21, 5, 67, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 57, 36, 26, 13, 12, 12, 12, 36, 36, 36, 35, 29, 32, 23, 17, 18, 9, 73, 4, 74, 85, 25, 9, 4, 83, 79, 74, 79, 75, 68, 73, 80, 68, 64, 3, 64, 1, 4, 4, 62, 62, 62, 56, 50, 44, 36, 20, 68, 69, 39, 28, 22, 12, 9, 2, 64, 66, 80, 80, 75, 70, 76, 69, 64, 69, 74, 64, 3, 6, 3, 6, 9, 5, 62, 62, 62, 56, 50, 44, 36, 20, 68 }, { 62, 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 32, 53, 58, 55, 21, 108, 65, 69, 74, 10, 11, 69, 6, 21, 36, 51, 44, 15, 77, 112, 96, 74, 69, 74, 10, 70, 85, 11, 18, 68, 80, 92, 4, 75, 86, 99, 7, 73, 83, 4, 80, 83, 93, 65, 76, 70, 85, 70, 2, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 44, 10, 80, 114, 85, 90, 72, 87, 94, 89, 110, 91, 103, 96, 115, 80, 87, 91, 90, 24, 65, 79, 116, 72, 100, 88, 117, 8, 5, 6, 0, 11, 68, 71, 85, 98, 86, 86, 87, 73, 83, 102, 97, 74, 105, 93, 102, 105, 111, 112, 64, 76, 69, 69, 4, 9, 67, 73, 86, 108, 74, 33, 32, 113, 9, 4, 5, 4, 3, 64, 76, 69, 0, 10, 6, 2, 66, 66, 3, 69, 5, 0, 20, 33, 29, 19, 14, 19, 12, 17, 20, 64, 21, 18, 27, 89, 17, 32, 29, 62, 62, 43, 55, 62, 62, 53, 62, 62, 62, 62, 61, 57, 44, 57, 62, 60, 62, 62, 62, 55, 46, 62, 52, 62, 19, 62, 62, 62, 62, 62, 62, 62, 62, 61, 58, 53, 44, 37, 30, 13, 75, 72, 79, 24, 27, 23, 27, 13, 15, 13, 29, 4, 68, 2, 76, 70, 112, 104, 66, 5, 10, 64, 73, 77, 81, 83, 116, 94, 25, 12, 5, 71, 72, 85, 99, 103, 113, 72, 23, 15, 10, 8, 67, 66, 67, 72, 83, 66, 32, 31, 29, 10, 9, 66, 73, 86, 11, 57, 52, 50, 39, 26, 3, 71, 69, 76, 62, 119, 116, 103, 117, 116, 99, 112, 108, 96, 108, 107, 111, 91, 95, 92, 105, 105, 87, 79, 80, 82, 78, 71, 65, 67, 5, 69, 69, 71, 66, 65, 72, 0, 2, 65, 0, 6, 64, 65, 1, 10, 65, 70, 74, 69, 69, 64, 21, 16, 23, 23, 19, 19, 15, 19, 18, 4, 13, 18, 66, 8, 62, 62, 59, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 44, 62, 62, 62, 62, 62, 62, 61, 52, 52, 40, 29, 19, 5, 68, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 55, 54, 34, 24, 12, 12, 11, 10, 35, 34, 34, 33, 27, 30, 21, 16, 17, 8, 73, 3, 75, 86, 24, 8, 3, 83, 79, 73, 78, 74, 67, 72, 79, 68, 64, 3, 0, 2, 4, 4, 62, 62, 59, 53, 47, 40, 32, 16, 71, 69, 39, 28, 22, 12, 9, 2, 0, 65, 79, 80, 75, 69, 76, 68, 0, 69, 74, 64, 3, 6, 4, 6, 9, 5, 62, 62, 59, 53, 47, 40, 32, 16, 71 }, { 62, 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 30, 51, 57, 55, 21, 107, 65, 68, 74, 10, 10, 68, 6, 20, 34, 48, 40, 12, 78, 110, 95, 74, 68, 74, 10, 71, 85, 11, 17, 68, 80, 92, 4, 75, 85, 98, 7, 72, 82, 4, 79, 83, 93, 65, 76, 70, 85, 70, 2, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 44, 10, 80, 112, 84, 89, 71, 84, 93, 88, 108, 90, 102, 95, 113, 80, 87, 91, 90, 24, 65, 78, 113, 72, 99, 87, 115, 7, 5, 6, 64, 12, 68, 71, 84, 98, 86, 85, 86, 73, 82, 101, 96, 74, 104, 92, 97, 100, 108, 109, 64, 76, 69, 69, 4, 9, 67, 72, 86, 106, 73, 31, 30, 110, 9, 4, 5, 4, 4, 64, 74, 68, 0, 10, 6, 2, 65, 65, 2, 69, 5, 0, 19, 32, 28, 19, 13, 19, 12, 17, 18, 64, 20, 17, 26, 89, 17, 32, 29, 62, 62, 42, 53, 62, 62, 51, 62, 62, 62, 62, 57, 55, 43, 55, 62, 58, 62, 62, 62, 52, 44, 62, 50, 62, 17, 62, 62, 62, 62, 62, 62, 62, 62, 59, 56, 50, 42, 35, 29, 12, 75, 72, 80, 23, 26, 22, 26, 12, 14, 12, 27, 3, 68, 1, 77, 70, 112, 103, 65, 5, 10, 64, 72, 76, 80, 83, 114, 93, 26, 12, 5, 70, 71, 84, 97, 101, 111, 71, 23, 15, 10, 9, 66, 66, 67, 72, 82, 66, 33, 31, 28, 10, 9, 66, 73, 86, 10, 57, 52, 49, 38, 25, 3, 71, 69, 76, 62, 118, 115, 102, 116, 114, 98, 110, 106, 95, 107, 105, 109, 91, 94, 92, 104, 103, 86, 79, 80, 81, 78, 71, 65, 67, 4, 69, 69, 71, 66, 64, 71, 0, 2, 64, 1, 6, 0, 64, 1, 9, 65, 70, 74, 69, 68, 65, 20, 16, 22, 22, 18, 19, 15, 18, 18, 4, 12, 16, 67, 7, 62, 62, 58, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 42, 62, 62, 62, 62, 62, 62, 58, 50, 49, 38, 27, 18, 4, 69, 62, 62, 62, 62, 62, 62, 62, 62, 61, 58, 52, 51, 32, 23, 10, 11, 10, 9, 33, 33, 32, 31, 26, 28, 19, 15, 15, 7, 74, 2, 76, 87, 22, 7, 2, 83, 78, 73, 78, 73, 66, 72, 79, 67, 0, 3, 0, 3, 4, 4, 62, 62, 57, 50, 44, 36, 28, 12, 74, 69, 39, 28, 22, 12, 10, 3, 0, 65, 79, 79, 74, 69, 75, 67, 1, 68, 73, 64, 3, 6, 4, 7, 9, 5, 62, 62, 57, 50, 44, 36, 28, 12, 74 }, { 62, 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 29, 49, 56, 55, 21, 105, 65, 68, 73, 9, 10, 68, 5, 18, 33, 46, 37, 10, 80, 107, 94, 74, 68, 73, 9, 71, 84, 11, 17, 68, 79, 91, 4, 75, 85, 98, 7, 72, 82, 4, 79, 82, 92, 65, 76, 70, 85, 69, 2, 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 43, 9, 80, 109, 83, 88, 69, 82, 91, 86, 107, 88, 100, 94, 111, 80, 86, 90, 90, 24, 65, 77, 111, 72, 98, 87, 113, 7, 4, 5, 64, 12, 68, 70, 84, 97, 85, 85, 85, 72, 81, 99, 94, 73, 103, 91, 92, 96, 105, 106, 64, 75, 69, 68, 4, 9, 66, 72, 85, 104, 73, 29, 28, 107, 9, 5, 5, 4, 4, 0, 73, 67, 1, 9, 6, 2, 65, 65, 1, 69, 5, 64, 19, 31, 28, 18, 13, 19, 12, 17, 17, 64, 19, 16, 26, 89, 17, 31, 28, 60, 62, 41, 51, 62, 62, 49, 62, 61, 62, 62, 54, 53, 41, 52, 62, 55, 62, 62, 62, 49, 42, 62, 48, 62, 16, 62, 62, 62, 62, 62, 62, 62, 62, 57, 53, 48, 40, 33, 27, 10, 75, 72, 80, 22, 25, 21, 24, 11, 13, 11, 26, 2, 69, 0, 77, 71, 111, 101, 64, 6, 10, 0, 72, 76, 80, 82, 112, 91, 26, 13, 6, 70, 70, 83, 96, 100, 109, 71, 24, 16, 11, 9, 65, 65, 67, 71, 82, 66, 33, 31, 28, 9, 9, 66, 73, 85, 10, 56, 51, 48, 37, 25, 3, 70, 69, 76, 62, 116, 113, 101, 114, 113, 97, 109, 105, 94, 105, 104, 107, 90, 93, 91, 103, 101, 86, 79, 80, 81, 77, 71, 66, 67, 4, 69, 69, 72, 65, 64, 70, 0, 2, 64, 1, 6, 0, 64, 1, 9, 65, 70, 74, 69, 68, 66, 19, 16, 21, 22, 17, 18, 15, 17, 17, 4, 12, 15, 67, 6, 61, 62, 57, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 40, 62, 62, 62, 62, 62, 62, 56, 48, 47, 36, 25, 16, 3, 69, 62, 62, 62, 62, 62, 62, 62, 62, 59, 56, 50, 48, 30, 21, 9, 10, 10, 7, 32, 31, 31, 29, 24, 26, 18, 14, 14, 6, 75, 0, 77, 87, 21, 5, 0, 82, 78, 72, 77, 72, 65, 72, 78, 67, 0, 3, 1, 4, 4, 4, 62, 62, 54, 47, 40, 32, 24, 8, 77, 68, 39, 28, 22, 12, 10, 3, 0, 65, 78, 79, 74, 68, 75, 66, 2, 68, 73, 0, 4, 6, 4, 7, 9, 5, 62, 62, 54, 47, 40, 32, 24, 8, 77 }, { 62, 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 27, 46, 55, 55, 20, 103, 66, 68, 72, 9, 9, 68, 4, 16, 32, 43, 33, 7, 82, 104, 93, 74, 68, 72, 9, 72, 83, 11, 16, 68, 79, 91, 3, 76, 85, 98, 7, 72, 82, 4, 79, 82, 92, 65, 76, 70, 85, 69, 2, 22, 0, 0, 0, 78, 88, 97, 68, 16, 0, 42, 9, 81, 107, 82, 87, 68, 80, 90, 85, 105, 87, 99, 93, 109, 80, 86, 90, 90, 24, 65, 76, 109, 72, 98, 86, 111, 7, 4, 5, 65, 12, 68, 70, 83, 96, 84, 84, 85, 72, 81, 98, 93, 73, 102, 90, 88, 92, 102, 104, 64, 75, 69, 68, 3, 9, 66, 72, 85, 102, 73, 27, 26, 105, 9, 5, 5, 4, 4, 0, 71, 67, 1, 9, 5, 2, 64, 64, 64, 69, 5, 64, 18, 29, 27, 18, 12, 19, 12, 16, 16, 64, 18, 15, 25, 89, 16, 30, 27, 58, 62, 39, 49, 62, 62, 46, 62, 59, 62, 62, 50, 51, 39, 50, 62, 53, 62, 62, 62, 46, 40, 62, 46, 62, 14, 62, 62, 62, 62, 62, 62, 62, 60, 55, 51, 46, 38, 31, 25, 8, 75, 73, 81, 21, 23, 20, 23, 10, 11, 9, 24, 1, 69, 64, 78, 72, 111, 100, 0, 6, 10, 0, 71, 75, 79, 82, 110, 90, 26, 13, 6, 69, 69, 82, 94, 98, 108, 70, 24, 16, 11, 10, 64, 65, 67, 71, 81, 67, 34, 31, 27, 9, 9, 66, 73, 85, 10, 56, 50, 47, 36, 25, 3, 70, 69, 76, 62, 115, 112, 100, 113, 111, 96, 107, 103, 93, 104, 102, 105, 90, 93, 91, 102, 100, 86, 79, 80, 80, 77, 71, 66, 67, 3, 69, 69, 72, 65, 64, 69, 0, 1, 64, 1, 5, 0, 64, 1, 8, 65, 70, 74, 69, 67, 67, 18, 16, 19, 21, 16, 17, 14, 16, 16, 4, 12, 14, 67, 4, 60, 60, 56, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 38, 62, 62, 62, 62, 62, 62, 53, 45, 44, 34, 23, 15, 2, 70, 62, 62, 62, 62, 62, 62, 62, 62, 56, 53, 47, 45, 28, 19, 7, 9, 9, 5, 30, 30, 29, 27, 22, 24, 16, 12, 12, 4, 76, 64, 78, 88, 19, 4, 64, 82, 78, 72, 77, 71, 64, 72, 78, 66, 1, 3, 1, 4, 4, 3, 62, 60, 51, 44, 37, 28, 19, 3, 80, 68, 39, 28, 22, 12, 11, 3, 0, 65, 78, 79, 74, 68, 75, 66, 2, 68, 73, 0, 4, 6, 4, 8, 9, 4, 62, 60, 51, 44, 37, 28, 19, 3, 80 }, { 62, 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 26, 44, 54, 56, 20, 101, 66, 67, 71, 9, 8, 68, 4, 15, 31, 41, 29, 4, 83, 101, 92, 73, 67, 71, 9, 72, 82, 11, 16, 67, 79, 90, 3, 76, 85, 98, 7, 72, 81, 4, 79, 82, 92, 65, 76, 70, 84, 69, 2, 22, 0, 0, 0, 77, 88, 97, 68, 15, 0, 41, 9, 81, 105, 80, 86, 66, 78, 88, 84, 103, 85, 98, 91, 106, 80, 86, 90, 89, 24, 65, 75, 107, 71, 97, 85, 109, 7, 4, 5, 65, 12, 68, 70, 82, 95, 83, 83, 84, 71, 80, 97, 91, 72, 100, 89, 83, 87, 98, 101, 0, 75, 68, 67, 3, 9, 66, 71, 84, 99, 73, 25, 25, 102, 9, 5, 5, 4, 4, 1, 69, 66, 1, 9, 5, 2, 0, 0, 65, 68, 5, 64, 17, 28, 26, 18, 11, 19, 12, 16, 15, 0, 17, 15, 24, 89, 16, 30, 27, 56, 62, 38, 48, 62, 62, 44, 60, 57, 62, 62, 47, 49, 37, 48, 62, 51, 62, 62, 62, 44, 38, 62, 44, 62, 12, 62, 62, 62, 62, 62, 62, 60, 58, 53, 49, 44, 37, 30, 24, 6, 75, 73, 81, 21, 22, 19, 22, 9, 10, 8, 22, 0, 69, 65, 79, 72, 110, 99, 1, 6, 10, 0, 70, 74, 78, 81, 107, 89, 26, 13, 6, 68, 68, 81, 92, 96, 106, 69, 25, 17, 12, 11, 0, 65, 66, 71, 80, 67, 35, 31, 26, 9, 10, 65, 73, 84, 10, 56, 50, 46, 35, 25, 3, 70, 69, 75, 62, 114, 111, 98, 111, 109, 95, 105, 101, 92, 102, 100, 103, 89, 92, 90, 101, 98, 85, 78, 79, 79, 76, 71, 66, 67, 2, 69, 69, 72, 65, 0, 68, 1, 1, 0, 1, 5, 0, 64, 1, 7, 65, 69, 73, 69, 66, 67, 17, 16, 18, 20, 16, 17, 14, 16, 15, 4, 12, 13, 67, 3, 59, 59, 56, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 36, 62, 62, 62, 62, 62, 62, 50, 43, 42, 33, 22, 14, 2, 71, 62, 62, 62, 62, 62, 62, 62, 62, 54, 51, 45, 43, 26, 17, 5, 9, 8, 4, 29, 29, 27, 25, 21, 23, 14, 11, 10, 3, 76, 65, 78, 89, 17, 3, 65, 82, 77, 71, 77, 70, 1, 71, 77, 65, 2, 3, 2, 5, 4, 3, 62, 58, 49, 41, 34, 24, 15, 64, 83, 68, 39, 28, 23, 13, 12, 4, 1, 64, 78, 79, 74, 68, 74, 65, 3, 68, 72, 0, 4, 6, 5, 9, 9, 4, 62, 58, 49, 41, 34, 24, 15, 64, 83 }, { 62, 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 25, 42, 53, 56, 20, 99, 66, 67, 70, 8, 8, 68, 3, 13, 30, 38, 26, 2, 85, 98, 91, 73, 67, 70, 8, 72, 81, 11, 15, 67, 78, 89, 3, 76, 85, 98, 7, 72, 81, 4, 79, 81, 91, 65, 76, 70, 84, 68, 2, 22, 0, 0, 0, 77, 88, 97, 67, 14, 0, 40, 8, 81, 102, 79, 85, 64, 76, 87, 82, 102, 84, 96, 90, 104, 80, 85, 89, 89, 24, 65, 74, 105, 71, 96, 85, 107, 7, 3, 4, 66, 12, 68, 69, 82, 94, 82, 83, 83, 71, 79, 95, 90, 71, 99, 88, 78, 83, 95, 98, 0, 74, 68, 67, 3, 9, 65, 71, 84, 97, 73, 23, 23, 99, 9, 6, 5, 4, 4, 1, 68, 65, 2, 8, 5, 2, 0, 0, 66, 68, 5, 65, 17, 27, 26, 17, 11, 19, 12, 16, 14, 0, 16, 14, 24, 89, 16, 29, 26, 54, 62, 37, 46, 62, 62, 42, 57, 55, 62, 62, 43, 47, 35, 45, 61, 48, 62, 62, 62, 41, 36, 58, 42, 62, 11, 62, 62, 62, 62, 62, 60, 58, 56, 51, 46, 42, 35, 28, 22, 4, 75, 73, 82, 20, 21, 18, 20, 8, 9, 7, 21, 64, 70, 66, 79, 73, 109, 97, 2, 7, 10, 1, 70, 74, 78, 80, 105, 87, 26, 14, 7, 68, 67, 80, 91, 95, 104, 69, 26, 18, 13, 11, 1, 64, 66, 70, 80, 67, 35, 31, 26, 8, 10, 65, 73, 84, 10, 55, 49, 45, 34, 25, 3, 69, 69, 75, 62, 112, 109, 97, 109, 108, 94, 104, 100, 91, 100, 99, 101, 88, 91, 90, 100, 96, 85, 78, 79, 79, 76, 71, 67, 67, 2, 69, 69, 73, 64, 0, 67, 1, 1, 0, 1, 5, 0, 64, 1, 7, 65, 69, 73, 69, 66, 68, 16, 16, 17, 20, 15, 16, 14, 15, 14, 4, 12, 12, 67, 2, 58, 58, 55, 59, 60, 62, 62, 62, 62, 62, 62, 62, 62, 55, 34, 62, 62, 62, 62, 62, 62, 48, 41, 39, 31, 20, 12, 1, 71, 62, 62, 62, 62, 62, 62, 62, 62, 52, 48, 43, 40, 24, 15, 4, 8, 8, 2, 28, 27, 26, 23, 19, 21, 13, 10, 9, 2, 77, 67, 79, 89, 16, 1, 67, 81, 77, 70, 76, 69, 2, 71, 76, 65, 2, 3, 2, 6, 4, 3, 62, 56, 46, 38, 30, 20, 11, 68, 86, 67, 39, 28, 23, 13, 12, 4, 1, 64, 77, 79, 74, 67, 74, 64, 4, 68, 72, 1, 5, 6, 5, 9, 9, 4, 62, 56, 46, 38, 30, 20, 11, 68, 86 }, { 62, 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 23, 40, 52, 56, 20, 98, 66, 66, 70, 8, 7, 67, 3, 12, 28, 36, 22, 64, 86, 96, 90, 73, 66, 70, 8, 73, 81, 11, 15, 67, 78, 89, 3, 76, 84, 97, 7, 71, 80, 4, 78, 81, 91, 65, 76, 70, 84, 68, 2, 22, 0, 0, 0, 76, 88, 97, 67, 14, 64, 40, 8, 81, 100, 78, 84, 0, 73, 85, 81, 100, 82, 95, 89, 102, 80, 85, 89, 89, 24, 65, 73, 102, 71, 95, 84, 105, 6, 3, 4, 66, 13, 68, 69, 81, 94, 82, 82, 82, 70, 78, 94, 88, 71, 98, 87, 73, 78, 92, 95, 0, 74, 68, 66, 3, 9, 65, 70, 83, 95, 72, 21, 21, 96, 9, 6, 5, 4, 5, 2, 66, 64, 2, 8, 5, 2, 1, 1, 67, 68, 5, 65, 16, 26, 25, 17, 10, 19, 12, 16, 12, 0, 15, 13, 23, 89, 16, 29, 26, 52, 62, 36, 44, 61, 62, 40, 55, 53, 62, 62, 40, 45, 34, 43, 57, 46, 62, 62, 62, 38, 34, 55, 40, 62, 9, 62, 62, 62, 62, 62, 58, 55, 54, 49, 44, 39, 33, 26, 21, 3, 75, 73, 82, 19, 20, 17, 19, 7, 8, 6, 19, 65, 70, 67, 80, 73, 109, 96, 3, 7, 10, 1, 69, 73, 77, 80, 103, 86, 27, 14, 7, 67, 66, 79, 89, 93, 102, 68, 26, 18, 13, 12, 2, 64, 66, 70, 79, 67, 36, 31, 25, 8, 10, 65, 73, 83, 9, 55, 49, 44, 33, 24, 3, 69, 69, 75, 62, 111, 108, 96, 108, 106, 93, 102, 98, 90, 99, 97, 99, 88, 90, 89, 99, 94, 84, 78, 79, 78, 75, 71, 67, 67, 1, 69, 69, 73, 64, 1, 66, 1, 1, 1, 2, 5, 1, 0, 1, 6, 65, 69, 73, 69, 65, 69, 15, 16, 16, 19, 14, 16, 14, 14, 14, 4, 11, 10, 68, 1, 56, 57, 54, 58, 58, 62, 62, 62, 62, 62, 62, 62, 62, 52, 32, 62, 62, 62, 62, 62, 62, 45, 39, 37, 29, 18, 11, 0, 72, 62, 62, 62, 62, 62, 62, 60, 59, 49, 46, 40, 37, 22, 14, 2, 7, 7, 1, 26, 26, 24, 21, 18, 19, 11, 9, 7, 1, 78, 68, 80, 90, 14, 0, 68, 81, 76, 70, 76, 68, 3, 71, 76, 64, 3, 3, 3, 7, 4, 3, 62, 54, 44, 35, 27, 16, 7, 72, 89, 67, 39, 28, 23, 13, 13, 5, 1, 64, 77, 78, 73, 67, 73, 0, 5, 67, 71, 1, 5, 6, 5, 10, 9, 4, 62, 54, 44, 35, 27, 16, 7, 72, 89 }, { 62, 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 22, 38, 51, 56, 19, 96, 66, 66, 69, 8, 7, 67, 2, 10, 27, 33, 19, 66, 88, 93, 89, 73, 66, 69, 8, 73, 80, 11, 14, 67, 78, 88, 3, 76, 84, 97, 7, 71, 80, 4, 78, 80, 91, 65, 76, 70, 84, 68, 2, 22, 0, 0, 0, 76, 89, 97, 66, 13, 64, 39, 7, 81, 97, 77, 83, 2, 71, 84, 79, 98, 81, 94, 88, 100, 80, 85, 88, 89, 24, 65, 72, 100, 71, 95, 84, 103, 6, 2, 3, 67, 13, 68, 68, 81, 93, 81, 82, 81, 70, 78, 92, 87, 70, 97, 86, 68, 74, 89, 92, 0, 74, 68, 66, 3, 9, 64, 70, 83, 93, 72, 19, 19, 94, 9, 6, 5, 4, 5, 2, 65, 0, 2, 8, 5, 2, 1, 2, 68, 68, 5, 66, 16, 25, 25, 17, 10, 19, 12, 16, 11, 0, 14, 12, 23, 89, 15, 28, 25, 50, 62, 35, 42, 59, 60, 38, 52, 51, 62, 62, 36, 43, 32, 41, 54, 43, 58, 62, 62, 35, 32, 51, 38, 62, 7, 62, 62, 62, 62, 62, 56, 53, 52, 47, 42, 37, 31, 24, 19, 1, 75, 73, 83, 18, 19, 16, 18, 6, 6, 5, 17, 66, 71, 68, 81, 74, 108, 94, 4, 8, 10, 1, 68, 73, 77, 79, 101, 85, 27, 14, 8, 66, 65, 78, 88, 92, 101, 67, 27, 19, 14, 12, 3, 0, 66, 70, 79, 67, 36, 31, 24, 8, 10, 65, 73, 83, 9, 54, 48, 43, 32, 24, 3, 69, 69, 75, 62, 110, 106, 95, 106, 105, 92, 100, 96, 89, 97, 95, 97, 87, 89, 89, 98, 93, 84, 78, 79, 78, 75, 71, 67, 67, 1, 69, 69, 74, 0, 1, 65, 1, 1, 1, 2, 5, 1, 0, 1, 6, 65, 69, 73, 69, 65, 70, 14, 16, 15, 18, 13, 15, 14, 13, 13, 4, 11, 9, 68, 0, 55, 56, 53, 56, 56, 62, 61, 62, 62, 62, 62, 62, 61, 50, 30, 62, 62, 62, 62, 62, 59, 43, 36, 34, 27, 16, 9, 64, 73, 62, 62, 62, 62, 62, 62, 57, 56, 47, 43, 38, 34, 20, 12, 1, 6, 6, 64, 25, 24, 22, 19, 16, 17, 9, 8, 6, 0, 79, 69, 81, 91, 13, 64, 69, 81, 76, 69, 75, 67, 4, 71, 75, 64, 3, 3, 3, 8, 4, 3, 61, 52, 41, 32, 24, 12, 2, 76, 92, 67, 39, 28, 23, 13, 13, 5, 1, 64, 76, 78, 73, 66, 73, 0, 6, 67, 71, 1, 5, 6, 5, 10, 9, 4, 61, 52, 41, 32, 24, 12, 2, 76, 92 }, { 62, 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 21, 36, 50, 56, 19, 94, 66, 66, 68, 7, 6, 67, 1, 8, 26, 31, 15, 69, 90, 90, 88, 72, 66, 68, 7, 73, 79, 11, 14, 67, 77, 87, 3, 76, 84, 97, 7, 71, 80, 4, 78, 80, 90, 65, 76, 70, 84, 67, 2, 22, 0, 0, 0, 75, 89, 97, 66, 12, 64, 38, 7, 81, 95, 76, 82, 4, 69, 82, 78, 97, 79, 92, 87, 97, 80, 84, 88, 88, 24, 65, 71, 98, 71, 94, 83, 101, 6, 2, 3, 67, 13, 68, 68, 80, 92, 80, 81, 80, 69, 77, 91, 85, 69, 95, 85, 0, 70, 86, 89, 1, 73, 67, 65, 3, 9, 64, 70, 82, 91, 72, 17, 17, 91, 9, 7, 5, 4, 5, 3, 0, 1, 3, 7, 5, 2, 2, 2, 69, 67, 5, 66, 15, 24, 24, 16, 9, 19, 12, 16, 10, 1, 13, 12, 22, 89, 15, 27, 24, 48, 62, 34, 41, 57, 58, 36, 50, 49, 62, 62, 33, 41, 30, 38, 51, 41, 55, 62, 62, 33, 30, 48, 36, 62, 6, 62, 62, 62, 61, 60, 54, 51, 50, 45, 39, 35, 29, 23, 17, 64, 75, 73, 83, 17, 18, 15, 16, 5, 5, 4, 16, 67, 71, 69, 81, 75, 107, 93, 5, 8, 10, 2, 68, 72, 76, 78, 99, 83, 27, 15, 8, 66, 64, 77, 86, 90, 99, 67, 28, 20, 15, 13, 4, 0, 65, 69, 78, 67, 37, 31, 24, 7, 10, 65, 73, 82, 9, 54, 47, 42, 31, 24, 3, 68, 69, 74, 62, 108, 105, 93, 104, 103, 91, 99, 95, 88, 95, 94, 95, 86, 88, 88, 97, 91, 84, 78, 78, 77, 74, 71, 68, 67, 0, 69, 69, 74, 0, 1, 64, 1, 1, 1, 2, 5, 1, 0, 1, 5, 65, 69, 73, 69, 64, 71, 13, 16, 14, 18, 13, 14, 14, 13, 12, 4, 11, 8, 68, 64, 54, 55, 52, 54, 54, 62, 59, 61, 62, 59, 62, 62, 58, 47, 28, 62, 62, 62, 62, 59, 56, 40, 34, 32, 25, 15, 8, 64, 73, 62, 62, 62, 62, 59, 59, 55, 53, 45, 41, 36, 31, 18, 10, 64, 6, 6, 66, 24, 23, 21, 17, 14, 15, 8, 7, 4, 64, 79, 71, 82, 91, 11, 66, 71, 80, 76, 68, 75, 66, 5, 70, 74, 0, 4, 3, 4, 9, 4, 3, 60, 50, 38, 29, 20, 8, 65, 80, 95, 66, 39, 28, 23, 13, 14, 5, 2, 0, 76, 78, 73, 66, 73, 1, 7, 67, 71, 2, 6, 6, 6, 11, 9, 4, 60, 50, 38, 29, 20, 8, 65, 80, 95 }, { 61, 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 19, 34, 49, 56, 19, 92, 66, 65, 67, 7, 6, 67, 1, 7, 25, 28, 12, 71, 91, 87, 87, 72, 65, 67, 7, 74, 78, 11, 13, 67, 77, 87, 3, 76, 84, 97, 7, 71, 79, 4, 78, 79, 90, 65, 76, 70, 84, 67, 2, 22, 0, 0, 0, 75, 89, 97, 65, 11, 64, 37, 6, 81, 92, 75, 81, 5, 67, 81, 76, 95, 78, 91, 86, 95, 80, 84, 87, 88, 24, 65, 70, 96, 71, 93, 83, 99, 6, 1, 2, 68, 13, 68, 67, 80, 91, 79, 81, 79, 69, 76, 89, 84, 69, 94, 84, 5, 65, 83, 86, 1, 73, 67, 65, 3, 9, 0, 69, 82, 89, 72, 15, 15, 88, 9, 7, 5, 4, 5, 3, 1, 2, 3, 7, 5, 2, 2, 3, 70, 67, 5, 67, 15, 23, 24, 16, 9, 19, 12, 16, 9, 1, 12, 11, 22, 89, 15, 27, 24, 46, 61, 33, 39, 55, 55, 34, 47, 47, 62, 62, 29, 39, 28, 36, 48, 38, 52, 61, 62, 30, 28, 44, 34, 62, 4, 60, 62, 60, 58, 57, 52, 49, 48, 43, 37, 33, 27, 21, 16, 66, 75, 73, 84, 16, 17, 14, 15, 4, 4, 3, 14, 68, 72, 70, 82, 75, 107, 91, 6, 9, 10, 2, 67, 72, 76, 78, 97, 82, 27, 15, 9, 65, 0, 76, 85, 89, 97, 66, 28, 20, 15, 13, 5, 1, 65, 69, 78, 67, 37, 31, 23, 7, 10, 65, 73, 82, 9, 53, 47, 41, 30, 24, 3, 68, 69, 74, 62, 107, 103, 92, 103, 102, 90, 97, 93, 87, 94, 92, 93, 86, 87, 88, 96, 89, 83, 78, 78, 77, 74, 71, 68, 67, 0, 69, 69, 75, 1, 2, 0, 1, 1, 2, 2, 5, 1, 0, 1, 5, 65, 69, 73, 69, 64, 72, 12, 16, 13, 17, 12, 14, 14, 12, 11, 4, 11, 7, 68, 65, 53, 54, 51, 53, 52, 60, 57, 59, 59, 57, 62, 60, 55, 45, 26, 62, 62, 62, 62, 55, 53, 38, 32, 29, 23, 13, 6, 65, 74, 62, 62, 62, 60, 56, 57, 52, 50, 42, 38, 33, 28, 16, 8, 65, 5, 5, 67, 22, 21, 19, 15, 13, 13, 6, 6, 3, 65, 80, 72, 83, 92, 10, 67, 72, 80, 75, 68, 74, 65, 6, 70, 74, 0, 4, 3, 4, 10, 4, 3, 59, 48, 36, 26, 17, 4, 69, 84, 98, 66, 39, 28, 23, 13, 14, 6, 2, 0, 75, 78, 73, 65, 72, 2, 8, 67, 70, 2, 6, 6, 6, 11, 9, 4, 59, 48, 36, 26, 17, 4, 69, 84, 98 }, { 60, 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 18, 32, 48, 56, 19, 90, 66, 65, 66, 7, 5, 67, 0, 5, 24, 26, 8, 74, 93, 84, 86, 72, 65, 66, 7, 74, 77, 11, 13, 67, 77, 86, 3, 76, 84, 97, 7, 71, 79, 4, 78, 79, 90, 65, 76, 70, 84, 67, 2, 22, 0, 0, 0, 74, 89, 97, 65, 10, 64, 36, 6, 81, 90, 74, 80, 7, 65, 79, 75, 93, 76, 90, 85, 93, 80, 84, 87, 88, 24, 65, 69, 94, 71, 92, 82, 97, 6, 1, 2, 68, 13, 68, 67, 79, 90, 78, 80, 78, 68, 75, 88, 82, 68, 93, 83, 10, 2, 80, 83, 1, 73, 67, 64, 3, 9, 0, 69, 81, 87, 72, 13, 13, 85, 9, 7, 5, 4, 5, 4, 3, 3, 3, 7, 5, 2, 3, 4, 71, 67, 5, 67, 14, 22, 23, 16, 8, 19, 12, 16, 8, 1, 11, 10, 21, 89, 15, 26, 23, 44, 58, 32, 37, 53, 53, 32, 45, 45, 62, 62, 26, 37, 26, 34, 45, 36, 49, 57, 62, 27, 26, 41, 32, 62, 2, 58, 62, 58, 56, 55, 50, 47, 46, 41, 35, 31, 25, 19, 14, 68, 75, 73, 84, 15, 16, 13, 14, 3, 3, 2, 12, 69, 72, 71, 83, 76, 106, 90, 7, 9, 10, 2, 66, 71, 75, 77, 95, 81, 27, 15, 9, 64, 1, 75, 83, 87, 95, 65, 29, 21, 16, 14, 6, 1, 65, 69, 77, 67, 38, 31, 22, 7, 10, 65, 73, 81, 9, 53, 46, 40, 29, 24, 3, 68, 69, 74, 62, 106, 102, 91, 101, 100, 89, 95, 91, 86, 92, 90, 91, 85, 86, 87, 95, 87, 83, 78, 78, 76, 73, 71, 68, 67, 64, 69, 69, 75, 1, 2, 1, 1, 1, 2, 2, 5, 1, 0, 1, 4, 65, 69, 73, 69, 0, 73, 11, 16, 12, 16, 11, 13, 14, 11, 10, 4, 11, 6, 68, 66, 52, 53, 50, 51, 50, 58, 55, 57, 57, 54, 61, 57, 52, 42, 24, 62, 62, 62, 62, 52, 50, 35, 30, 27, 21, 11, 5, 66, 75, 62, 62, 62, 58, 53, 54, 50, 47, 40, 36, 31, 25, 14, 6, 67, 4, 4, 69, 21, 20, 17, 13, 11, 11, 4, 5, 1, 66, 81, 73, 84, 93, 8, 68, 73, 80, 75, 67, 74, 64, 7, 70, 73, 1, 5, 3, 5, 11, 4, 3, 58, 46, 33, 23, 14, 0, 73, 88, 101, 66, 39, 28, 23, 13, 15, 6, 2, 0, 75, 78, 73, 65, 72, 3, 9, 67, 70, 2, 6, 6, 6, 12, 9, 4, 58, 46, 33, 23, 14, 0, 73, 88, 101 }, { 58, 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 16, 29, 47, 56, 18, 89, 67, 65, 66, 6, 4, 67, 64, 3, 22, 23, 4, 77, 95, 82, 86, 72, 65, 66, 6, 75, 77, 11, 12, 67, 77, 86, 2, 77, 84, 97, 6, 71, 79, 4, 78, 79, 90, 65, 76, 71, 84, 67, 2, 22, 0, 0, 0, 74, 90, 97, 65, 9, 65, 35, 5, 82, 88, 73, 79, 8, 0, 78, 74, 92, 75, 89, 84, 91, 80, 84, 87, 88, 24, 65, 69, 92, 71, 92, 82, 96, 5, 0, 1, 69, 13, 68, 67, 79, 90, 78, 80, 78, 68, 75, 87, 81, 68, 92, 82, 14, 6, 77, 81, 1, 73, 67, 64, 2, 9, 0, 69, 81, 85, 72, 11, 11, 83, 9, 7, 5, 4, 5, 4, 4, 3, 3, 6, 4, 2, 3, 4, 73, 67, 5, 68, 13, 20, 22, 15, 7, 19, 12, 15, 6, 1, 10, 9, 20, 89, 14, 25, 22, 41, 54, 30, 35, 50, 50, 29, 42, 43, 55, 62, 22, 34, 24, 31, 41, 33, 45, 52, 59, 24, 24, 37, 30, 62, 0, 55, 59, 55, 53, 52, 47, 44, 43, 39, 32, 28, 23, 17, 12, 70, 75, 74, 85, 14, 14, 11, 12, 1, 1, 0, 10, 70, 73, 72, 84, 77, 106, 89, 7, 9, 10, 2, 66, 71, 75, 77, 93, 80, 27, 15, 9, 64, 1, 74, 82, 86, 94, 65, 29, 21, 16, 14, 7, 1, 65, 69, 77, 68, 38, 30, 21, 6, 10, 65, 73, 81, 8, 52, 45, 38, 28, 23, 3, 68, 69, 74, 62, 105, 101, 90, 100, 99, 88, 94, 90, 85, 91, 89, 89, 85, 86, 87, 94, 86, 83, 78, 78, 76, 73, 71, 69, 68, 65, 69, 70, 76, 1, 2, 2, 1, 0, 2, 2, 4, 1, 0, 1, 3, 65, 69, 73, 69, 0, 74, 10, 16, 10, 15, 10, 12, 13, 10, 9, 4, 10, 4, 69, 68, 50, 51, 49, 49, 48, 55, 52, 54, 54, 51, 58, 54, 48, 39, 22, 62, 62, 61, 60, 48, 46, 32, 27, 24, 19, 9, 3, 67, 76, 59, 60, 60, 55, 50, 51, 47, 43, 37, 33, 28, 22, 12, 4, 69, 3, 3, 71, 19, 18, 15, 10, 9, 9, 2, 3, 64, 68, 82, 75, 85, 94, 6, 70, 75, 80, 75, 67, 74, 0, 8, 70, 73, 1, 5, 3, 5, 11, 4, 2, 56, 44, 30, 19, 10, 67, 78, 93, 104, 66, 39, 28, 23, 13, 15, 6, 2, 0, 75, 78, 73, 65, 72, 3, 9, 67, 70, 2, 6, 6, 6, 12, 8, 3, 56, 44, 30, 19, 10, 67, 78, 93, 104 }, { 57, 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 15, 27, 46, 57, 18, 87, 67, 64, 65, 6, 4, 66, 64, 2, 21, 21, 1, 79, 96, 79, 85, 71, 64, 65, 6, 75, 76, 11, 12, 66, 76, 85, 2, 77, 83, 96, 6, 70, 78, 4, 77, 78, 89, 64, 75, 71, 83, 66, 2, 22, 0, 0, 0, 73, 90, 97, 64, 9, 65, 35, 5, 82, 85, 71, 77, 10, 3, 76, 72, 90, 73, 87, 82, 88, 80, 83, 86, 87, 24, 65, 68, 89, 70, 91, 81, 94, 5, 0, 1, 69, 14, 68, 66, 78, 89, 77, 79, 77, 67, 74, 85, 79, 67, 90, 80, 19, 11, 73, 78, 2, 72, 66, 0, 2, 10, 1, 68, 80, 82, 71, 9, 10, 80, 9, 8, 5, 5, 6, 5, 6, 4, 4, 6, 4, 2, 4, 5, 74, 66, 5, 68, 13, 19, 22, 15, 7, 19, 12, 15, 5, 2, 10, 9, 20, 89, 14, 25, 22, 39, 51, 29, 34, 48, 48, 27, 40, 41, 49, 62, 19, 32, 23, 29, 38, 31, 42, 48, 55, 22, 22, 34, 28, 62, 64, 53, 57, 53, 51, 50, 45, 42, 41, 37, 30, 26, 22, 16, 11, 71, 75, 74, 85, 14, 13, 10, 11, 0, 0, 64, 9, 71, 73, 73, 84, 77, 105, 87, 8, 10, 10, 3, 65, 70, 74, 76, 90, 78, 28, 16, 10, 0, 2, 72, 80, 84, 92, 64, 30, 22, 17, 15, 8, 2, 64, 68, 76, 68, 39, 30, 21, 6, 11, 64, 73, 80, 8, 52, 45, 37, 27, 23, 4, 67, 68, 73, 62, 103, 99, 88, 98, 97, 86, 92, 88, 83, 89, 87, 86, 84, 85, 86, 92, 84, 82, 77, 77, 75, 72, 70, 69, 68, 65, 69, 70, 76, 2, 3, 3, 2, 0, 3, 3, 4, 2, 1, 1, 3, 64, 68, 72, 68, 1, 74, 9, 16, 9, 15, 10, 12, 13, 10, 9, 4, 10, 3, 69, 69, 49, 50, 49, 48, 47, 53, 50, 52, 52, 49, 56, 52, 45, 37, 20, 61, 60, 57, 56, 45, 43, 30, 25, 22, 18, 8, 2, 67, 76, 57, 58, 58, 53, 48, 49, 45, 40, 35, 31, 26, 20, 11, 3, 70, 3, 3, 72, 18, 17, 14, 8, 8, 8, 1, 2, 65, 69, 82, 76, 85, 94, 5, 71, 76, 79, 74, 66, 73, 2, 10, 69, 72, 2, 6, 4, 6, 12, 4, 2, 55, 42, 28, 16, 7, 71, 82, 97, 106, 65, 39, 29, 24, 14, 16, 7, 3, 1, 74, 77, 72, 64, 71, 4, 10, 66, 69, 3, 7, 6, 7, 13, 8, 3, 55, 42, 28, 16, 7, 71, 82, 97, 106 }, { 56, 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 14, 25, 45, 57, 18, 85, 67, 64, 64, 6, 3, 66, 65, 0, 20, 18, 66, 82, 98, 76, 84, 71, 64, 64, 6, 75, 75, 11, 11, 66, 76, 84, 2, 77, 83, 96, 6, 70, 78, 4, 77, 78, 89, 64, 75, 71, 83, 66, 2, 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 34, 5, 82, 83, 70, 76, 12, 5, 75, 71, 88, 72, 86, 81, 86, 80, 83, 86, 87, 24, 65, 67, 87, 70, 90, 80, 92, 5, 0, 1, 70, 14, 68, 66, 77, 88, 76, 78, 76, 67, 73, 84, 78, 66, 89, 79, 24, 15, 70, 75, 2, 72, 66, 0, 2, 10, 1, 68, 80, 80, 71, 7, 8, 77, 9, 8, 5, 5, 6, 5, 8, 5, 4, 6, 4, 2, 5, 6, 75, 66, 5, 68, 12, 18, 21, 15, 6, 19, 12, 15, 4, 2, 9, 8, 19, 89, 14, 24, 21, 37, 48, 28, 32, 46, 46, 25, 38, 39, 43, 62, 15, 30, 21, 27, 35, 29, 39, 44, 51, 19, 20, 31, 26, 62, 66, 51, 55, 51, 49, 48, 43, 40, 39, 35, 28, 24, 20, 14, 9, 73, 75, 74, 86, 13, 12, 9, 10, 64, 64, 65, 7, 72, 73, 74, 85, 78, 104, 86, 9, 10, 10, 3, 64, 69, 73, 75, 88, 77, 28, 16, 10, 1, 3, 71, 78, 82, 90, 0, 31, 23, 18, 16, 9, 2, 64, 68, 75, 68, 40, 30, 20, 6, 11, 64, 73, 80, 8, 52, 44, 36, 26, 23, 4, 67, 68, 73, 62, 102, 98, 87, 96, 95, 85, 90, 86, 82, 87, 85, 84, 83, 84, 86, 91, 82, 82, 77, 77, 74, 72, 70, 69, 68, 66, 69, 70, 76, 2, 3, 4, 2, 0, 3, 3, 4, 2, 1, 1, 2, 64, 68, 72, 68, 2, 75, 8, 16, 8, 14, 9, 11, 13, 9, 8, 4, 10, 2, 69, 70, 48, 49, 48, 46, 45, 51, 48, 50, 50, 46, 53, 49, 42, 34, 18, 57, 56, 53, 51, 42, 40, 27, 23, 19, 16, 6, 1, 68, 77, 55, 56, 55, 51, 45, 46, 42, 37, 33, 28, 24, 17, 9, 1, 72, 2, 2, 74, 17, 16, 12, 6, 6, 6, 64, 1, 67, 70, 83, 77, 86, 95, 3, 72, 77, 79, 74, 65, 73, 3, 11, 69, 71, 3, 7, 4, 6, 13, 4, 2, 54, 40, 25, 13, 4, 75, 86, 101, 109, 65, 39, 29, 24, 14, 17, 7, 3, 1, 74, 77, 72, 64, 71, 5, 11, 66, 69, 3, 7, 6, 7, 14, 8, 3, 54, 40, 25, 13, 4, 75, 86, 101, 109 }, { 55, 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 12, 23, 44, 57, 18, 83, 67, 0, 0, 6, 3, 66, 65, 64, 19, 16, 69, 84, 99, 73, 83, 71, 0, 0, 6, 76, 74, 11, 11, 66, 76, 84, 2, 77, 83, 96, 6, 70, 77, 4, 77, 77, 89, 64, 75, 71, 83, 66, 2, 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 33, 4, 82, 80, 69, 75, 13, 7, 73, 69, 86, 70, 85, 80, 84, 80, 83, 85, 87, 24, 65, 66, 85, 70, 89, 80, 90, 5, 64, 0, 70, 14, 68, 65, 77, 87, 75, 78, 75, 66, 72, 82, 76, 66, 88, 78, 29, 20, 67, 72, 2, 72, 66, 1, 2, 10, 2, 67, 79, 78, 71, 5, 6, 74, 9, 8, 5, 5, 6, 6, 9, 6, 4, 6, 4, 2, 5, 7, 76, 66, 5, 69, 12, 17, 21, 15, 6, 19, 12, 15, 3, 2, 8, 7, 19, 89, 14, 24, 21, 35, 45, 27, 30, 44, 43, 23, 35, 37, 36, 62, 12, 28, 19, 25, 32, 26, 36, 40, 47, 16, 18, 27, 24, 62, 68, 49, 53, 49, 46, 45, 41, 38, 37, 33, 26, 22, 18, 12, 8, 75, 75, 74, 86, 12, 11, 8, 9, 65, 65, 66, 5, 73, 74, 75, 86, 78, 104, 84, 10, 11, 10, 3, 0, 69, 73, 75, 86, 76, 28, 16, 11, 2, 4, 70, 77, 81, 88, 1, 31, 23, 18, 16, 10, 3, 64, 68, 75, 68, 40, 30, 19, 6, 11, 64, 73, 79, 8, 51, 44, 35, 25, 23, 4, 67, 68, 73, 62, 101, 96, 86, 95, 94, 84, 88, 84, 81, 86, 83, 82, 83, 83, 85, 90, 80, 81, 77, 77, 74, 71, 70, 69, 68, 66, 69, 70, 77, 3, 4, 5, 2, 0, 4, 3, 4, 2, 1, 1, 2, 64, 68, 72, 68, 2, 76, 7, 16, 7, 13, 8, 11, 13, 8, 7, 4, 10, 1, 69, 71, 47, 48, 47, 45, 43, 49, 46, 48, 47, 44, 50, 46, 39, 32, 16, 53, 52, 49, 46, 38, 37, 25, 21, 17, 14, 4, 64, 69, 78, 53, 53, 53, 48, 42, 44, 40, 34, 30, 26, 21, 14, 7, 64, 73, 1, 1, 75, 15, 14, 10, 4, 5, 4, 66, 0, 68, 71, 84, 78, 87, 96, 2, 73, 78, 79, 73, 65, 72, 4, 12, 69, 71, 3, 7, 4, 7, 14, 4, 2, 53, 38, 23, 10, 1, 79, 90, 105, 112, 65, 39, 29, 24, 14, 17, 8, 3, 1, 73, 77, 72, 0, 70, 6, 12, 66, 68, 3, 7, 6, 7, 14, 8, 3, 53, 38, 23, 10, 1, 79, 90, 105, 112 }, { 53, 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 11, 21, 43, 57, 17, 81, 67, 0, 1, 5, 2, 66, 66, 66, 18, 13, 73, 87, 101, 70, 82, 71, 0, 1, 5, 76, 73, 11, 10, 66, 75, 83, 2, 77, 83, 96, 6, 70, 77, 4, 77, 77, 88, 64, 75, 71, 83, 65, 2, 22, 0, 0, 0, 71, 91, 97, 0, 6, 65, 32, 4, 82, 78, 68, 74, 15, 9, 72, 68, 85, 69, 83, 79, 82, 80, 82, 85, 87, 24, 65, 65, 83, 70, 89, 79, 88, 5, 64, 0, 71, 14, 68, 65, 76, 86, 74, 77, 74, 66, 72, 81, 75, 65, 87, 77, 34, 24, 64, 69, 2, 71, 66, 1, 2, 10, 2, 67, 79, 76, 71, 3, 4, 72, 9, 9, 5, 5, 6, 6, 11, 7, 5, 5, 4, 2, 6, 7, 77, 66, 5, 69, 11, 16, 20, 14, 5, 19, 12, 15, 2, 2, 7, 6, 18, 89, 13, 23, 20, 33, 41, 26, 28, 42, 41, 21, 33, 35, 30, 62, 8, 26, 17, 22, 29, 24, 32, 35, 43, 13, 16, 24, 22, 62, 69, 47, 51, 46, 44, 43, 39, 36, 35, 31, 23, 20, 16, 10, 6, 77, 75, 74, 87, 11, 10, 7, 7, 66, 67, 67, 4, 74, 74, 76, 86, 79, 103, 83, 11, 11, 10, 4, 0, 68, 72, 74, 84, 74, 28, 17, 11, 2, 5, 69, 75, 79, 87, 1, 32, 24, 19, 17, 11, 3, 64, 67, 74, 68, 41, 30, 19, 5, 11, 64, 73, 79, 8, 51, 43, 34, 24, 23, 4, 66, 68, 73, 62, 99, 95, 85, 93, 92, 83, 87, 83, 80, 84, 82, 80, 82, 82, 85, 89, 79, 81, 77, 77, 73, 71, 70, 70, 68, 67, 69, 70, 77, 3, 4, 6, 2, 0, 4, 3, 4, 2, 1, 1, 1, 64, 68, 72, 68, 3, 77, 6, 16, 6, 13, 7, 10, 13, 7, 6, 4, 10, 0, 69, 72, 46, 47, 46, 43, 41, 47, 44, 45, 45, 41, 47, 43, 36, 29, 14, 48, 48, 45, 41, 35, 33, 22, 18, 14, 12, 2, 65, 70, 78, 50, 51, 50, 46, 39, 41, 37, 31, 28, 23, 19, 11, 5, 66, 75, 0, 1, 77, 14, 13, 9, 2, 3, 2, 67, 64, 70, 72, 85, 80, 88, 96, 0, 75, 80, 78, 73, 64, 72, 5, 13, 69, 70, 4, 8, 4, 7, 15, 4, 2, 52, 36, 20, 7, 66, 83, 95, 109, 115, 64, 39, 29, 24, 14, 18, 8, 3, 1, 73, 77, 72, 0, 70, 6, 13, 66, 68, 4, 8, 6, 7, 15, 8, 3, 52, 36, 20, 7, 66, 83, 95, 109, 115 }, { 52, 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 10, 19, 42, 57, 17, 79, 67, 0, 2, 5, 2, 66, 67, 68, 17, 11, 76, 89, 103, 67, 81, 70, 0, 2, 5, 76, 72, 11, 10, 66, 75, 82, 2, 77, 83, 96, 6, 70, 77, 4, 77, 76, 88, 64, 75, 71, 83, 65, 2, 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 31, 3, 82, 75, 67, 73, 17, 11, 70, 66, 83, 67, 82, 78, 79, 80, 82, 84, 86, 24, 65, 64, 81, 70, 88, 79, 86, 5, 65, 64, 71, 14, 68, 64, 76, 85, 73, 77, 73, 65, 71, 79, 73, 64, 85, 76, 39, 28, 2, 66, 3, 71, 65, 2, 2, 10, 3, 67, 78, 74, 71, 1, 2, 69, 9, 9, 5, 5, 6, 7, 12, 8, 5, 5, 4, 2, 6, 8, 78, 65, 5, 70, 11, 15, 20, 14, 5, 19, 12, 15, 1, 3, 6, 6, 18, 89, 13, 22, 19, 31, 38, 25, 27, 40, 39, 19, 30, 33, 24, 62, 5, 24, 15, 20, 26, 21, 29, 31, 39, 11, 14, 20, 20, 62, 71, 45, 49, 44, 42, 41, 37, 34, 33, 29, 21, 18, 14, 9, 4, 79, 75, 74, 87, 10, 9, 6, 6, 67, 68, 68, 2, 75, 75, 77, 87, 80, 102, 81, 12, 12, 10, 4, 1, 68, 72, 73, 82, 73, 28, 17, 12, 3, 6, 68, 74, 78, 85, 2, 33, 25, 20, 17, 12, 4, 0, 67, 74, 68, 41, 30, 18, 5, 11, 64, 73, 78, 8, 50, 42, 33, 23, 23, 4, 66, 68, 72, 62, 98, 93, 83, 91, 91, 82, 85, 81, 79, 82, 80, 78, 81, 81, 84, 88, 77, 81, 77, 76, 73, 70, 70, 70, 68, 67, 69, 70, 78, 4, 4, 7, 2, 0, 4, 3, 4, 2, 1, 1, 1, 64, 68, 72, 68, 3, 78, 5, 16, 5, 12, 7, 9, 13, 7, 5, 4, 10, 64, 69, 73, 45, 46, 45, 41, 39, 45, 42, 43, 42, 38, 44, 40, 33, 27, 12, 44, 44, 41, 36, 32, 30, 20, 16, 12, 10, 1, 67, 70, 79, 48, 48, 48, 44, 36, 38, 35, 28, 26, 21, 17, 8, 3, 68, 76, 0, 0, 79, 13, 11, 7, 0, 1, 0, 69, 65, 71, 73, 85, 81, 89, 97, 64, 76, 81, 78, 73, 0, 71, 6, 14, 68, 69, 4, 8, 4, 8, 16, 4, 2, 51, 34, 17, 4, 69, 87, 99, 113, 118, 64, 39, 29, 24, 14, 18, 8, 4, 2, 72, 77, 72, 1, 70, 7, 14, 66, 68, 4, 8, 6, 8, 15, 8, 3, 51, 34, 17, 4, 69, 87, 99, 113, 118 }, { 51, 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 8, 17, 41, 57, 17, 78, 67, 1, 2, 5, 1, 65, 67, 69, 15, 8, 80, 92, 104, 65, 80, 70, 1, 2, 5, 77, 72, 11, 9, 66, 75, 82, 2, 77, 82, 95, 6, 69, 76, 4, 76, 76, 88, 64, 75, 71, 83, 65, 2, 22, 0, 0, 0, 70, 91, 97, 1, 5, 66, 31, 3, 82, 73, 66, 72, 18, 14, 69, 65, 81, 66, 81, 77, 77, 80, 82, 84, 86, 24, 65, 0, 78, 70, 87, 78, 84, 4, 65, 64, 72, 15, 68, 64, 75, 85, 73, 76, 72, 65, 70, 78, 72, 64, 84, 75, 44, 33, 5, 0, 3, 71, 65, 2, 2, 10, 3, 66, 78, 72, 70, 64, 0, 66, 9, 9, 5, 5, 7, 7, 14, 9, 5, 5, 4, 2, 7, 9, 79, 65, 5, 70, 10, 14, 19, 14, 4, 19, 12, 15, 64, 3, 5, 5, 17, 89, 13, 22, 19, 29, 35, 24, 25, 37, 36, 17, 28, 31, 17, 62, 1, 22, 14, 18, 22, 19, 26, 27, 34, 8, 12, 17, 18, 62, 73, 43, 47, 42, 39, 38, 35, 31, 31, 27, 19, 15, 12, 7, 3, 80, 75, 74, 88, 9, 8, 5, 5, 68, 69, 69, 0, 76, 75, 78, 88, 80, 102, 80, 13, 12, 10, 4, 2, 67, 71, 73, 80, 72, 29, 17, 12, 4, 7, 67, 72, 76, 83, 3, 33, 25, 20, 18, 13, 4, 0, 67, 73, 68, 42, 30, 17, 5, 11, 64, 73, 78, 7, 50, 42, 32, 22, 22, 4, 66, 68, 72, 62, 97, 92, 82, 90, 89, 81, 83, 79, 78, 81, 78, 76, 81, 80, 84, 87, 75, 80, 77, 76, 72, 70, 70, 70, 68, 68, 69, 70, 78, 4, 5, 8, 2, 0, 5, 4, 4, 3, 2, 1, 0, 64, 68, 72, 68, 4, 79, 4, 16, 4, 11, 6, 9, 13, 6, 5, 4, 9, 66, 70, 74, 43, 45, 44, 40, 37, 43, 40, 41, 40, 36, 41, 38, 30, 24, 10, 40, 40, 37, 32, 28, 27, 17, 14, 9, 8, 64, 68, 71, 80, 46, 46, 45, 41, 33, 36, 32, 25, 23, 18, 14, 5, 1, 69, 78, 64, 64, 80, 11, 10, 5, 65, 0, 65, 71, 66, 73, 74, 86, 82, 90, 98, 66, 77, 82, 78, 72, 0, 71, 7, 15, 68, 69, 5, 9, 4, 8, 17, 4, 2, 50, 32, 15, 1, 72, 91, 103, 117, 121, 64, 39, 29, 24, 14, 19, 9, 4, 2, 72, 76, 71, 1, 69, 8, 15, 65, 67, 4, 8, 6, 8, 16, 8, 3, 50, 32, 15, 1, 72, 91, 103, 117, 121 }, { 50, 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 7, 15, 40, 57, 17, 76, 67, 1, 3, 4, 1, 65, 68, 71, 14, 6, 83, 94, 106, 1, 79, 70, 1, 3, 4, 77, 71, 11, 9, 66, 74, 81, 2, 77, 82, 95, 6, 69, 76, 4, 76, 75, 87, 64, 75, 71, 83, 64, 2, 22, 0, 0, 0, 70, 91, 97, 2, 4, 66, 30, 2, 82, 70, 65, 71, 20, 16, 67, 0, 80, 64, 79, 76, 75, 80, 81, 83, 86, 24, 65, 1, 76, 70, 86, 78, 82, 4, 66, 65, 72, 15, 68, 0, 75, 84, 72, 76, 71, 64, 69, 76, 70, 0, 83, 74, 49, 37, 8, 3, 3, 70, 65, 3, 2, 10, 4, 66, 77, 70, 70, 66, 65, 0, 9, 10, 5, 5, 7, 8, 15, 10, 6, 4, 4, 2, 7, 9, 80, 65, 5, 71, 10, 13, 19, 13, 4, 19, 12, 15, 65, 3, 4, 4, 17, 89, 13, 21, 18, 27, 32, 23, 23, 35, 34, 15, 25, 29, 11, 62, 65, 20, 12, 15, 19, 16, 23, 22, 30, 5, 10, 13, 16, 62, 74, 41, 45, 40, 37, 36, 33, 29, 29, 25, 16, 13, 10, 5, 1, 82, 75, 74, 88, 8, 7, 4, 3, 69, 70, 70, 64, 77, 76, 79, 88, 81, 101, 78, 14, 13, 10, 5, 2, 67, 71, 72, 78, 70, 29, 18, 13, 4, 8, 66, 71, 75, 81, 3, 34, 26, 21, 18, 14, 5, 0, 66, 73, 68, 42, 30, 17, 4, 11, 64, 73, 77, 7, 49, 41, 31, 21, 22, 4, 65, 68, 72, 62, 95, 90, 81, 88, 88, 80, 82, 78, 77, 79, 77, 74, 80, 79, 83, 86, 73, 80, 77, 76, 72, 69, 70, 71, 68, 68, 69, 70, 79, 5, 5, 9, 2, 0, 5, 4, 4, 3, 2, 1, 0, 64, 68, 72, 68, 4, 80, 3, 16, 3, 11, 5, 8, 13, 5, 4, 4, 9, 67, 70, 75, 42, 44, 43, 38, 35, 41, 38, 38, 37, 33, 38, 35, 27, 22, 8, 35, 36, 33, 27, 25, 24, 15, 12, 7, 6, 66, 70, 72, 80, 43, 43, 43, 39, 30, 33, 30, 22, 21, 16, 12, 2, 64, 71, 79, 65, 64, 82, 10, 8, 4, 67, 65, 67, 72, 67, 74, 75, 87, 84, 91, 98, 67, 79, 84, 77, 72, 1, 70, 8, 16, 68, 68, 5, 9, 4, 9, 18, 4, 2, 49, 30, 12, 65, 76, 95, 107, 121, 124, 0, 39, 29, 24, 14, 19, 9, 4, 2, 71, 76, 71, 2, 69, 9, 16, 65, 67, 5, 9, 6, 8, 16, 8, 3, 49, 30, 12, 65, 76, 95, 107, 121, 124 }, { 48, 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 5, 12, 39, 57, 16, 74, 68, 1, 4, 4, 0, 65, 69, 73, 13, 3, 87, 97, 108, 4, 78, 70, 1, 4, 4, 78, 70, 11, 8, 66, 74, 81, 1, 78, 82, 95, 6, 69, 76, 4, 76, 75, 87, 64, 75, 71, 83, 64, 2, 22, 0, 0, 0, 69, 92, 97, 2, 3, 66, 29, 2, 83, 68, 64, 70, 21, 18, 66, 1, 78, 0, 78, 75, 73, 80, 81, 83, 86, 24, 65, 2, 74, 70, 86, 77, 80, 4, 66, 65, 73, 15, 68, 0, 74, 83, 71, 75, 71, 64, 69, 75, 69, 0, 82, 73, 53, 41, 11, 5, 3, 70, 65, 3, 1, 10, 4, 66, 77, 68, 70, 68, 67, 2, 9, 10, 5, 5, 7, 8, 17, 10, 6, 4, 3, 2, 8, 10, 82, 65, 5, 71, 9, 11, 18, 13, 3, 19, 12, 14, 66, 3, 3, 3, 16, 89, 12, 20, 17, 25, 28, 21, 21, 33, 31, 12, 23, 27, 4, 62, 69, 18, 10, 13, 16, 14, 19, 18, 26, 2, 8, 10, 14, 62, 76, 39, 42, 37, 34, 33, 30, 27, 26, 23, 14, 11, 8, 3, 64, 84, 75, 75, 89, 7, 5, 3, 2, 70, 72, 72, 66, 78, 76, 80, 89, 82, 101, 77, 15, 13, 10, 5, 3, 66, 70, 72, 76, 69, 29, 18, 13, 5, 9, 65, 69, 73, 80, 4, 34, 26, 21, 19, 15, 5, 0, 66, 72, 69, 43, 30, 16, 4, 11, 64, 73, 77, 7, 49, 40, 30, 20, 22, 4, 65, 68, 72, 62, 94, 89, 80, 87, 86, 79, 80, 76, 76, 78, 75, 72, 80, 79, 83, 85, 72, 80, 77, 76, 71, 69, 70, 71, 68, 69, 69, 70, 79, 5, 5, 10, 2, 64, 5, 4, 3, 3, 2, 1, 64, 64, 68, 72, 68, 5, 81, 2, 16, 1, 10, 4, 7, 12, 4, 3, 4, 9, 68, 70, 77, 41, 42, 42, 36, 33, 39, 36, 36, 35, 30, 35, 32, 24, 19, 6, 31, 32, 28, 22, 21, 20, 12, 9, 4, 4, 68, 71, 73, 81, 41, 41, 40, 36, 27, 30, 27, 19, 18, 13, 9, 64, 66, 73, 81, 66, 65, 84, 8, 7, 2, 69, 67, 69, 74, 69, 76, 77, 88, 85, 92, 99, 69, 80, 85, 77, 72, 1, 70, 9, 17, 68, 68, 6, 10, 4, 9, 18, 4, 1, 48, 28, 9, 68, 79, 99, 112, 126, 126, 0, 39, 29, 24, 14, 20, 9, 4, 2, 71, 76, 71, 2, 69, 9, 16, 65, 67, 5, 9, 6, 8, 17, 8, 2, 48, 28, 9, 68, 79, 99, 112, 126, 126 }, { 47, 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 4, 10, 38, 58, 16, 72, 68, 2, 5, 4, 64, 65, 69, 74, 12, 1, 91, 100, 109, 7, 77, 69, 2, 5, 4, 78, 69, 11, 8, 65, 74, 80, 1, 78, 82, 95, 6, 69, 75, 4, 76, 75, 87, 64, 75, 71, 82, 64, 2, 22, 0, 0, 0, 68, 92, 97, 2, 2, 66, 28, 2, 83, 66, 1, 69, 23, 20, 64, 2, 76, 2, 77, 73, 70, 80, 81, 83, 85, 24, 65, 3, 72, 69, 85, 76, 78, 4, 66, 65, 73, 15, 68, 0, 73, 82, 70, 74, 70, 0, 68, 74, 67, 1, 80, 72, 58, 46, 15, 8, 4, 70, 64, 4, 1, 10, 4, 65, 76, 65, 70, 70, 68, 5, 9, 10, 5, 5, 7, 9, 19, 11, 6, 4, 3, 2, 9, 11, 83, 64, 5, 71, 8, 10, 17, 13, 2, 19, 12, 14, 67, 4, 2, 3, 15, 89, 12, 20, 17, 23, 25, 20, 20, 31, 29, 10, 21, 25, 65, 62, 72, 16, 8, 11, 13, 12, 16, 14, 22, 0, 6, 7, 12, 62, 78, 37, 40, 35, 32, 31, 28, 25, 24, 21, 12, 9, 7, 2, 65, 86, 75, 75, 89, 7, 4, 2, 1, 71, 73, 73, 68, 79, 76, 81, 90, 82, 100, 76, 16, 13, 10, 5, 4, 65, 69, 71, 73, 68, 29, 18, 13, 6, 10, 64, 67, 71, 78, 5, 35, 27, 22, 20, 16, 5, 1, 66, 71, 69, 44, 30, 15, 4, 12, 0, 73, 76, 7, 49, 40, 29, 19, 22, 4, 65, 68, 71, 62, 93, 88, 78, 85, 84, 78, 78, 74, 75, 76, 73, 70, 79, 78, 82, 84, 70, 79, 76, 75, 70, 68, 70, 71, 68, 70, 69, 70, 79, 5, 6, 11, 3, 64, 6, 4, 3, 3, 2, 1, 65, 64, 67, 71, 68, 6, 81, 1, 16, 0, 9, 4, 7, 12, 4, 2, 4, 9, 69, 70, 78, 40, 41, 42, 35, 31, 37, 34, 34, 33, 28, 32, 29, 21, 16, 4, 27, 28, 24, 17, 18, 17, 9, 7, 2, 3, 69, 72, 73, 82, 39, 39, 38, 34, 25, 28, 25, 16, 16, 11, 7, 66, 68, 75, 83, 66, 66, 85, 7, 6, 0, 71, 68, 70, 76, 70, 78, 78, 88, 86, 92, 100, 71, 81, 86, 77, 71, 2, 70, 10, 19, 67, 67, 7, 11, 4, 10, 19, 4, 1, 47, 26, 7, 71, 82, 103, 116, 126, 126, 0, 39, 29, 25, 15, 21, 10, 5, 3, 71, 76, 71, 2, 68, 10, 17, 65, 66, 5, 9, 6, 9, 18, 8, 2, 47, 26, 7, 71, 82, 103, 116, 126, 126 }, { 46, 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 3, 8, 37, 58, 16, 70, 68, 2, 6, 3, 64, 65, 70, 76, 11, 65, 94, 102, 111, 10, 76, 69, 2, 6, 3, 78, 68, 11, 7, 65, 73, 79, 1, 78, 82, 95, 6, 69, 75, 4, 76, 74, 86, 64, 75, 71, 82, 0, 2, 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 27, 1, 83, 0, 2, 68, 25, 22, 0, 4, 75, 3, 75, 72, 68, 80, 80, 82, 85, 24, 65, 4, 70, 69, 84, 76, 76, 4, 67, 66, 74, 15, 68, 1, 73, 81, 69, 74, 69, 0, 67, 72, 66, 2, 79, 71, 62, 50, 18, 11, 4, 69, 64, 4, 1, 10, 5, 65, 76, 0, 70, 72, 70, 8, 9, 11, 5, 5, 7, 9, 20, 12, 7, 3, 3, 2, 9, 11, 84, 64, 5, 72, 8, 9, 17, 12, 2, 19, 12, 14, 68, 4, 1, 2, 15, 89, 12, 19, 16, 21, 22, 19, 18, 29, 27, 8, 18, 23, 71, 62, 76, 14, 6, 8, 10, 9, 13, 9, 18, 66, 4, 3, 10, 62, 79, 35, 38, 33, 30, 29, 26, 23, 22, 19, 9, 7, 5, 0, 67, 88, 75, 75, 90, 6, 3, 1, 64, 72, 74, 74, 69, 80, 77, 82, 90, 83, 99, 74, 17, 14, 10, 6, 4, 65, 69, 70, 71, 66, 29, 19, 14, 6, 11, 0, 66, 70, 76, 5, 36, 28, 23, 20, 17, 6, 1, 65, 71, 69, 44, 30, 15, 3, 12, 0, 73, 76, 7, 48, 39, 28, 18, 22, 4, 64, 68, 71, 62, 91, 86, 77, 83, 83, 77, 77, 73, 74, 74, 72, 68, 78, 77, 82, 83, 68, 79, 76, 75, 70, 68, 70, 72, 68, 70, 69, 70, 80, 6, 6, 12, 3, 64, 6, 4, 3, 3, 2, 1, 65, 64, 67, 71, 68, 6, 82, 0, 16, 64, 9, 3, 6, 12, 3, 1, 4, 9, 70, 70, 79, 39, 40, 41, 33, 29, 35, 32, 31, 30, 25, 29, 26, 18, 14, 2, 22, 24, 20, 12, 15, 14, 7, 5, 64, 1, 71, 74, 74, 82, 36, 36, 35, 32, 22, 25, 22, 13, 14, 8, 5, 69, 70, 77, 84, 67, 66, 87, 6, 4, 64, 73, 70, 72, 77, 71, 79, 79, 89, 88, 93, 100, 72, 83, 88, 76, 71, 3, 69, 11, 20, 67, 66, 7, 11, 4, 10, 20, 4, 1, 46, 24, 4, 74, 86, 107, 120, 126, 126, 1, 39, 29, 25, 15, 21, 10, 5, 3, 70, 76, 71, 3, 68, 11, 18, 65, 66, 6, 10, 6, 9, 18, 8, 2, 46, 24, 4, 74, 86, 107, 120, 126, 126 }, { 45, 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 1, 6, 36, 58, 16, 69, 68, 3, 6, 3, 65, 64, 70, 77, 9, 67, 98, 105, 112, 12, 75, 69, 3, 6, 3, 79, 68, 11, 7, 65, 73, 79, 1, 78, 81, 94, 6, 68, 74, 4, 75, 74, 86, 64, 75, 71, 82, 0, 2, 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 27, 1, 83, 2, 3, 67, 26, 25, 2, 5, 73, 5, 74, 71, 66, 80, 80, 82, 85, 24, 65, 5, 67, 69, 83, 75, 74, 3, 67, 66, 74, 16, 68, 1, 72, 81, 69, 73, 68, 1, 66, 71, 64, 2, 78, 70, 62, 55, 21, 14, 4, 69, 64, 5, 1, 10, 5, 64, 75, 2, 69, 74, 72, 11, 9, 11, 5, 5, 8, 10, 22, 13, 7, 3, 3, 2, 10, 12, 85, 64, 5, 72, 7, 8, 16, 12, 1, 19, 12, 14, 70, 4, 0, 1, 14, 89, 12, 19, 16, 19, 19, 18, 16, 26, 24, 6, 16, 21, 78, 62, 79, 12, 5, 6, 6, 7, 10, 5, 13, 69, 2, 0, 8, 62, 81, 33, 36, 31, 27, 26, 24, 20, 20, 17, 7, 4, 3, 65, 68, 89, 75, 75, 90, 5, 2, 0, 65, 73, 75, 75, 71, 81, 77, 83, 91, 83, 99, 73, 18, 14, 10, 6, 5, 64, 68, 70, 69, 65, 30, 19, 14, 7, 12, 1, 64, 68, 74, 6, 36, 28, 23, 21, 18, 6, 1, 65, 70, 69, 45, 30, 14, 3, 12, 0, 73, 75, 6, 48, 39, 27, 17, 21, 4, 64, 68, 71, 62, 90, 85, 76, 82, 81, 76, 75, 71, 73, 73, 70, 66, 78, 76, 81, 82, 66, 78, 76, 75, 69, 67, 70, 72, 68, 71, 69, 70, 80, 6, 7, 13, 3, 64, 7, 5, 3, 4, 3, 1, 66, 64, 67, 71, 68, 7, 83, 64, 16, 65, 8, 2, 6, 12, 2, 1, 4, 8, 72, 71, 80, 37, 39, 40, 32, 27, 33, 30, 29, 28, 23, 26, 24, 15, 11, 0, 18, 20, 16, 8, 11, 11, 4, 3, 66, 64, 73, 75, 75, 83, 34, 34, 33, 29, 19, 23, 20, 10, 11, 6, 2, 72, 72, 78, 86, 68, 67, 88, 4, 3, 66, 75, 71, 74, 79, 72, 81, 80, 90, 89, 94, 101, 74, 84, 89, 76, 70, 3, 69, 12, 21, 67, 66, 8, 12, 4, 11, 21, 4, 1, 45, 22, 2, 77, 89, 111, 124, 126, 126, 1, 39, 29, 25, 15, 22, 11, 5, 3, 70, 75, 70, 3, 67, 12, 19, 64, 65, 6, 10, 6, 9, 19, 8, 2, 45, 22, 2, 77, 89, 111, 124, 126, 126 }, { 43, 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 0, 4, 35, 58, 15, 67, 68, 3, 7, 3, 65, 64, 71, 79, 8, 70, 101, 107, 114, 15, 74, 69, 3, 7, 3, 79, 67, 11, 6, 65, 73, 78, 1, 78, 81, 94, 6, 68, 74, 4, 75, 73, 86, 64, 75, 71, 82, 0, 2, 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 26, 0, 83, 5, 4, 66, 28, 27, 3, 7, 71, 6, 73, 70, 64, 80, 80, 81, 85, 24, 65, 6, 65, 69, 83, 75, 72, 3, 68, 67, 75, 16, 68, 2, 72, 80, 68, 73, 67, 1, 66, 69, 0, 3, 77, 69, 62, 59, 24, 17, 4, 69, 64, 5, 1, 10, 6, 64, 75, 4, 69, 76, 74, 13, 9, 11, 5, 5, 8, 10, 23, 14, 7, 3, 3, 2, 10, 13, 86, 64, 5, 73, 7, 7, 16, 12, 1, 19, 12, 14, 71, 4, 64, 0, 14, 89, 11, 18, 15, 17, 15, 17, 14, 24, 22, 4, 13, 19, 84, 62, 83, 10, 3, 4, 3, 4, 6, 1, 9, 72, 0, 67, 6, 62, 83, 31, 34, 28, 25, 24, 22, 18, 18, 15, 5, 2, 1, 67, 70, 91, 75, 75, 91, 4, 1, 64, 66, 74, 77, 76, 73, 82, 78, 84, 92, 84, 98, 71, 19, 15, 10, 6, 6, 64, 68, 69, 67, 64, 30, 19, 15, 8, 13, 2, 0, 67, 73, 7, 37, 29, 24, 21, 19, 7, 1, 65, 70, 69, 45, 30, 13, 3, 12, 0, 73, 75, 6, 47, 38, 26, 16, 21, 4, 64, 68, 71, 62, 89, 83, 75, 80, 80, 75, 73, 69, 72, 71, 68, 64, 77, 75, 81, 81, 65, 78, 76, 75, 69, 67, 70, 72, 68, 71, 69, 70, 81, 7, 7, 14, 3, 64, 7, 5, 3, 4, 3, 1, 66, 64, 67, 71, 68, 7, 84, 65, 16, 66, 7, 1, 5, 12, 1, 0, 4, 8, 73, 71, 81, 36, 38, 39, 30, 25, 31, 28, 27, 25, 20, 23, 21, 12, 9, 65, 14, 16, 12, 3, 8, 7, 2, 0, 69, 66, 75, 77, 76, 84, 32, 31, 30, 27, 16, 20, 17, 7, 9, 3, 0, 75, 74, 80, 87, 69, 68, 90, 3, 1, 68, 77, 73, 76, 81, 73, 82, 81, 91, 90, 95, 102, 75, 85, 90, 76, 70, 4, 68, 13, 22, 67, 65, 8, 12, 4, 11, 22, 4, 1, 44, 20, 64, 80, 92, 115, 126, 126, 126, 1, 39, 29, 25, 15, 22, 11, 5, 3, 69, 75, 70, 4, 67, 12, 20, 64, 65, 6, 10, 6, 9, 19, 8, 2, 44, 20, 64, 80, 92, 115, 126, 126, 126 }, { 42, 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 64, 2, 34, 58, 15, 65, 68, 3, 8, 2, 66, 64, 72, 81, 7, 72, 105, 110, 116, 18, 73, 68, 3, 8, 2, 79, 66, 11, 6, 65, 72, 77, 1, 78, 81, 94, 6, 68, 74, 4, 75, 73, 85, 64, 75, 71, 82, 1, 2, 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 25, 0, 83, 7, 5, 65, 30, 29, 5, 8, 70, 8, 71, 69, 2, 80, 79, 81, 84, 24, 65, 7, 0, 69, 82, 74, 70, 3, 68, 67, 75, 16, 68, 2, 71, 79, 67, 72, 66, 2, 65, 68, 2, 4, 75, 68, 62, 62, 27, 20, 5, 68, 0, 6, 1, 10, 6, 64, 74, 6, 69, 78, 76, 16, 9, 12, 5, 5, 8, 11, 25, 15, 8, 2, 3, 2, 11, 13, 87, 0, 5, 73, 6, 6, 15, 11, 0, 19, 12, 14, 72, 5, 65, 0, 13, 89, 11, 17, 14, 15, 12, 16, 13, 22, 20, 2, 11, 17, 90, 62, 86, 8, 1, 1, 0, 2, 3, 67, 5, 74, 65, 70, 4, 62, 84, 29, 32, 26, 23, 22, 20, 16, 16, 13, 2, 0, 64, 68, 72, 93, 75, 75, 91, 3, 0, 65, 68, 75, 78, 77, 74, 83, 78, 85, 92, 85, 97, 70, 20, 15, 10, 7, 6, 0, 67, 68, 65, 1, 30, 20, 15, 8, 14, 3, 2, 65, 71, 7, 38, 30, 25, 22, 20, 7, 2, 64, 69, 69, 46, 30, 13, 2, 12, 0, 73, 74, 6, 47, 37, 25, 15, 21, 4, 0, 68, 70, 62, 87, 82, 73, 78, 78, 74, 72, 68, 71, 69, 67, 1, 76, 74, 80, 80, 0, 78, 76, 74, 68, 66, 70, 73, 68, 72, 69, 70, 81, 7, 7, 15, 3, 64, 7, 5, 3, 4, 3, 1, 67, 64, 67, 71, 68, 8, 85, 66, 16, 67, 7, 1, 4, 12, 1, 64, 4, 8, 74, 71, 82, 35, 37, 38, 28, 23, 29, 26, 24, 23, 17, 20, 18, 9, 6, 67, 9, 12, 8, 65, 5, 4, 64, 65, 71, 68, 76, 78, 76, 84, 29, 29, 28, 25, 13, 17, 15, 4, 7, 1, 65, 78, 76, 82, 89, 69, 68, 92, 2, 0, 69, 79, 75, 78, 82, 74, 84, 82, 91, 92, 96, 102, 77, 87, 92, 75, 70, 5, 68, 14, 23, 66, 64, 9, 13, 4, 12, 23, 4, 1, 43, 18, 67, 83, 96, 119, 126, 126, 126, 2, 39, 29, 25, 15, 23, 11, 6, 4, 69, 75, 70, 4, 67, 13, 21, 64, 65, 7, 11, 6, 10, 20, 8, 2, 43, 18, 67, 83, 96, 119, 126, 126, 126 }, { 41, 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 66, 0, 33, 58, 15, 0, 68, 4, 9, 2, 66, 64, 72, 82, 6, 75, 108, 112, 117, 21, 72, 68, 4, 9, 2, 80, 65, 11, 5, 65, 72, 77, 1, 78, 81, 94, 6, 68, 73, 4, 75, 72, 85, 64, 75, 71, 82, 1, 2, 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 24, 64, 83, 10, 6, 64, 31, 31, 6, 10, 68, 9, 70, 68, 4, 80, 79, 80, 84, 24, 65, 8, 2, 69, 81, 74, 68, 3, 69, 68, 76, 16, 68, 3, 71, 78, 66, 72, 65, 2, 64, 66, 3, 4, 74, 67, 62, 62, 30, 23, 5, 68, 0, 6, 1, 10, 7, 0, 74, 8, 69, 80, 78, 19, 9, 12, 5, 5, 8, 11, 26, 16, 8, 2, 3, 2, 11, 14, 88, 0, 5, 74, 6, 5, 15, 11, 0, 19, 12, 14, 73, 5, 66, 64, 13, 89, 11, 17, 14, 13, 9, 15, 11, 20, 17, 0, 8, 15, 97, 62, 90, 6, 64, 64, 66, 64, 0, 71, 1, 77, 67, 74, 2, 62, 86, 27, 30, 24, 20, 19, 18, 14, 14, 11, 0, 65, 66, 70, 73, 95, 75, 75, 92, 2, 64, 66, 69, 76, 79, 78, 76, 84, 79, 86, 93, 85, 97, 68, 21, 16, 10, 7, 7, 0, 67, 68, 0, 2, 30, 20, 16, 9, 15, 4, 3, 64, 69, 8, 38, 30, 25, 22, 21, 8, 2, 64, 69, 69, 46, 30, 12, 2, 12, 0, 73, 74, 6, 46, 37, 24, 14, 21, 4, 0, 68, 70, 62, 86, 80, 72, 77, 77, 73, 70, 66, 70, 68, 65, 3, 76, 73, 80, 79, 2, 77, 76, 74, 68, 66, 70, 73, 68, 72, 69, 70, 82, 8, 8, 16, 3, 64, 8, 5, 3, 4, 3, 1, 67, 64, 67, 71, 68, 8, 86, 67, 16, 68, 6, 0, 4, 12, 0, 65, 4, 8, 75, 71, 83, 34, 36, 37, 27, 21, 27, 24, 22, 20, 15, 17, 15, 6, 4, 69, 5, 8, 4, 70, 1, 1, 66, 67, 74, 70, 78, 80, 77, 85, 27, 26, 25, 22, 10, 15, 12, 1, 4, 65, 68, 81, 78, 84, 90, 70, 69, 93, 0, 65, 71, 81, 76, 80, 84, 75, 85, 83, 92, 93, 97, 103, 78, 88, 93, 75, 69, 5, 67, 15, 24, 66, 64, 9, 13, 4, 12, 24, 4, 1, 42, 16, 69, 86, 99, 123, 126, 126, 126, 2, 39, 29, 25, 15, 23, 12, 6, 4, 68, 75, 70, 5, 66, 14, 22, 64, 64, 7, 11, 6, 10, 20, 8, 2, 42, 16, 69, 86, 99, 123, 126, 126, 126 }, { 40, 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 67, 65, 32, 58, 15, 2, 68, 4, 10, 2, 67, 64, 73, 84, 5, 77, 112, 115, 119, 24, 71, 68, 4, 10, 2, 80, 64, 11, 5, 65, 72, 76, 1, 78, 81, 94, 6, 68, 73, 4, 75, 72, 85, 64, 75, 71, 82, 1, 2, 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 23, 64, 83, 12, 7, 0, 33, 33, 8, 11, 66, 11, 69, 67, 6, 80, 79, 80, 84, 24, 65, 9, 4, 69, 80, 73, 66, 3, 69, 68, 76, 16, 68, 3, 70, 77, 65, 71, 64, 3, 0, 65, 5, 5, 73, 66, 62, 62, 33, 26, 5, 68, 0, 7, 1, 10, 7, 0, 73, 10, 69, 82, 80, 22, 9, 12, 5, 5, 8, 12, 28, 17, 8, 2, 3, 2, 12, 15, 89, 0, 5, 74, 5, 4, 14, 11, 64, 19, 12, 14, 74, 5, 67, 65, 12, 89, 11, 16, 13, 11, 6, 14, 9, 18, 15, 65, 6, 13, 103, 62, 93, 4, 66, 66, 69, 66, 66, 75, 66, 80, 69, 77, 0, 62, 88, 25, 28, 22, 18, 17, 16, 12, 12, 9, 65, 67, 68, 72, 75, 97, 75, 75, 92, 1, 65, 67, 70, 77, 80, 79, 78, 85, 79, 87, 94, 86, 96, 67, 22, 16, 10, 7, 8, 1, 66, 67, 2, 3, 30, 20, 16, 10, 16, 5, 5, 1, 67, 9, 39, 31, 26, 23, 22, 8, 2, 64, 68, 69, 47, 30, 11, 2, 12, 0, 73, 73, 6, 46, 36, 23, 13, 21, 4, 0, 68, 70, 62, 85, 79, 71, 75, 75, 72, 68, 64, 69, 66, 0, 5, 75, 72, 79, 78, 4, 77, 76, 74, 67, 65, 70, 73, 68, 73, 69, 70, 82, 8, 8, 17, 3, 64, 8, 5, 3, 4, 3, 1, 68, 64, 67, 71, 68, 9, 87, 68, 16, 69, 5, 64, 3, 12, 64, 66, 4, 8, 76, 71, 84, 33, 35, 36, 25, 19, 25, 22, 20, 18, 12, 14, 12, 3, 1, 71, 1, 4, 0, 75, 65, 65, 69, 69, 76, 72, 80, 81, 78, 86, 25, 24, 23, 20, 7, 12, 10, 65, 2, 67, 70, 84, 80, 86, 92, 71, 70, 95, 64, 66, 73, 83, 78, 82, 86, 76, 87, 84, 93, 94, 98, 104, 80, 89, 94, 75, 69, 6, 67, 16, 25, 66, 0, 10, 14, 4, 13, 25, 4, 1, 41, 14, 72, 89, 102, 126, 126, 126, 126, 2, 39, 29, 25, 15, 24, 12, 6, 4, 68, 75, 70, 5, 66, 15, 23, 64, 64, 7, 11, 6, 10, 21, 8, 2, 41, 14, 72, 89, 102, 126, 126, 126, 126 }, { 38, 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 69, 68, 31, 58, 14, 3, 69, 4, 10, 1, 68, 64, 74, 86, 3, 80, 116, 118, 121, 26, 71, 68, 4, 10, 1, 81, 64, 11, 4, 65, 72, 76, 0, 79, 81, 94, 5, 68, 73, 4, 75, 72, 85, 64, 75, 72, 82, 1, 2, 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 22, 65, 84, 14, 8, 1, 34, 35, 9, 12, 65, 12, 68, 66, 8, 80, 79, 80, 84, 24, 65, 9, 6, 69, 80, 73, 65, 2, 70, 69, 77, 16, 68, 3, 70, 77, 65, 71, 64, 3, 0, 64, 6, 5, 72, 65, 62, 62, 36, 28, 5, 68, 0, 7, 0, 10, 7, 0, 73, 12, 69, 84, 82, 24, 9, 12, 5, 5, 8, 12, 29, 17, 8, 1, 2, 2, 12, 15, 91, 0, 5, 75, 4, 2, 13, 10, 65, 19, 12, 13, 76, 5, 68, 66, 11, 89, 10, 15, 12, 8, 2, 12, 7, 15, 12, 68, 3, 11, 110, 62, 97, 1, 68, 69, 73, 69, 70, 80, 71, 83, 71, 81, 65, 62, 90, 22, 25, 19, 15, 14, 13, 9, 9, 7, 68, 70, 70, 74, 77, 99, 75, 76, 93, 0, 67, 69, 72, 79, 82, 81, 80, 86, 80, 88, 95, 87, 96, 66, 22, 16, 10, 7, 8, 1, 66, 67, 4, 4, 30, 20, 16, 10, 16, 6, 6, 2, 66, 9, 39, 31, 26, 23, 23, 8, 2, 64, 68, 70, 47, 29, 10, 1, 12, 0, 73, 73, 5, 45, 35, 21, 12, 20, 4, 0, 68, 70, 62, 84, 78, 70, 74, 74, 71, 67, 0, 68, 65, 1, 7, 75, 72, 79, 77, 5, 77, 76, 74, 67, 65, 70, 74, 69, 74, 69, 71, 83, 8, 8, 18, 3, 65, 8, 5, 2, 4, 3, 1, 69, 64, 67, 71, 68, 9, 88, 69, 16, 71, 4, 65, 2, 11, 65, 67, 4, 7, 78, 72, 86, 31, 33, 35, 23, 17, 22, 19, 17, 15, 9, 11, 9, 64, 65, 73, 67, 0, 68, 80, 69, 69, 72, 72, 79, 74, 82, 83, 79, 87, 22, 21, 20, 17, 4, 9, 7, 69, 64, 70, 73, 87, 82, 88, 94, 72, 71, 97, 66, 68, 75, 86, 80, 84, 88, 78, 89, 86, 94, 96, 99, 105, 82, 91, 96, 75, 69, 6, 67, 17, 26, 66, 0, 10, 14, 4, 13, 25, 4, 0, 39, 12, 75, 93, 106, 126, 126, 126, 126, 2, 39, 29, 25, 15, 24, 12, 6, 4, 68, 75, 70, 5, 66, 15, 23, 64, 64, 7, 11, 6, 10, 21, 7, 1, 39, 12, 75, 93, 106, 126, 126, 126, 126 }, { 37, 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 70, 70, 30, 59, 14, 5, 69, 5, 11, 1, 68, 0, 74, 87, 2, 82, 119, 120, 122, 29, 70, 67, 5, 11, 1, 81, 0, 11, 4, 64, 71, 75, 0, 79, 80, 93, 5, 67, 72, 4, 74, 71, 84, 0, 74, 72, 81, 2, 2, 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 22, 65, 84, 17, 10, 3, 36, 38, 11, 14, 0, 14, 66, 64, 11, 80, 78, 79, 83, 24, 65, 10, 9, 68, 79, 72, 0, 2, 70, 69, 77, 17, 68, 4, 69, 76, 64, 70, 0, 4, 1, 1, 8, 6, 70, 0, 62, 62, 40, 31, 6, 67, 1, 8, 0, 11, 8, 1, 72, 15, 68, 86, 83, 27, 9, 13, 5, 6, 9, 13, 31, 18, 9, 1, 2, 2, 13, 16, 92, 1, 5, 75, 4, 1, 13, 10, 65, 19, 12, 13, 77, 6, 68, 66, 11, 89, 10, 15, 12, 6, 64, 11, 6, 13, 10, 70, 1, 9, 116, 62, 100, 64, 69, 71, 76, 71, 73, 84, 75, 85, 73, 84, 67, 62, 91, 20, 23, 17, 13, 12, 11, 7, 7, 5, 70, 72, 71, 75, 78, 100, 75, 76, 93, 0, 68, 70, 73, 80, 83, 82, 81, 87, 80, 89, 95, 87, 95, 64, 23, 17, 10, 8, 9, 2, 65, 66, 7, 6, 31, 21, 17, 11, 17, 8, 8, 4, 64, 10, 40, 32, 27, 24, 24, 9, 3, 0, 67, 70, 48, 29, 10, 1, 13, 1, 73, 72, 5, 45, 35, 20, 11, 20, 5, 1, 67, 69, 62, 82, 76, 68, 72, 72, 69, 65, 2, 66, 0, 3, 10, 74, 71, 78, 75, 7, 76, 75, 73, 66, 64, 69, 74, 69, 74, 69, 71, 83, 9, 9, 19, 4, 65, 9, 6, 2, 5, 4, 1, 69, 0, 66, 70, 67, 10, 88, 70, 16, 72, 4, 65, 2, 11, 65, 67, 4, 7, 79, 72, 87, 30, 32, 35, 22, 16, 20, 17, 15, 13, 7, 9, 7, 67, 67, 75, 71, 66, 72, 84, 72, 72, 74, 74, 81, 75, 83, 84, 79, 87, 20, 19, 18, 15, 2, 7, 5, 72, 66, 72, 75, 89, 83, 89, 95, 72, 71, 98, 67, 69, 76, 88, 81, 85, 89, 79, 90, 87, 94, 97, 99, 105, 83, 92, 97, 74, 68, 7, 66, 19, 28, 65, 1, 11, 15, 5, 14, 26, 4, 0, 38, 10, 77, 96, 109, 126, 126, 126, 126, 3, 39, 30, 26, 16, 25, 13, 7, 5, 67, 74, 69, 6, 65, 16, 24, 0, 0, 8, 12, 6, 11, 22, 7, 1, 38, 10, 77, 96, 109, 126, 126, 126, 126 }, { 36, 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 71, 72, 29, 59, 14, 7, 69, 5, 12, 1, 69, 0, 75, 89, 1, 85, 123, 123, 124, 32, 69, 67, 5, 12, 1, 81, 1, 11, 3, 64, 71, 74, 0, 79, 80, 93, 5, 67, 72, 4, 74, 71, 84, 0, 74, 72, 81, 2, 2, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 21, 65, 84, 19, 11, 4, 38, 40, 12, 15, 2, 15, 65, 0, 13, 80, 78, 79, 83, 24, 65, 11, 11, 68, 78, 71, 2, 2, 70, 69, 78, 17, 68, 4, 68, 75, 0, 69, 1, 4, 2, 2, 9, 7, 69, 1, 62, 62, 43, 34, 6, 67, 1, 8, 0, 11, 8, 1, 72, 17, 68, 88, 85, 30, 9, 13, 5, 6, 9, 13, 33, 19, 9, 1, 2, 2, 14, 17, 93, 1, 5, 75, 3, 0, 12, 10, 66, 19, 12, 13, 78, 6, 69, 67, 10, 89, 10, 14, 11, 4, 67, 10, 4, 11, 8, 72, 64, 7, 122, 62, 104, 66, 71, 73, 79, 73, 76, 88, 79, 88, 75, 87, 69, 62, 93, 18, 21, 15, 11, 10, 9, 5, 5, 3, 72, 74, 73, 77, 80, 102, 75, 76, 94, 64, 69, 71, 74, 81, 84, 83, 83, 88, 80, 90, 96, 88, 94, 0, 24, 17, 10, 8, 10, 3, 64, 65, 9, 7, 31, 21, 17, 12, 18, 9, 10, 6, 1, 11, 41, 33, 28, 25, 25, 9, 3, 0, 66, 70, 49, 29, 9, 1, 13, 1, 73, 72, 5, 45, 34, 19, 10, 20, 5, 1, 67, 69, 62, 81, 75, 67, 70, 70, 68, 0, 4, 65, 2, 5, 12, 73, 70, 78, 74, 9, 76, 75, 73, 65, 64, 69, 74, 69, 75, 69, 71, 83, 9, 9, 20, 4, 65, 9, 6, 2, 5, 4, 1, 70, 0, 66, 70, 67, 11, 89, 71, 16, 73, 3, 66, 1, 11, 66, 68, 4, 7, 80, 72, 88, 29, 31, 34, 20, 14, 18, 15, 13, 11, 4, 6, 4, 70, 70, 77, 75, 70, 76, 89, 75, 75, 77, 76, 84, 77, 85, 85, 80, 88, 18, 17, 15, 13, 64, 4, 2, 75, 68, 75, 77, 92, 85, 91, 97, 73, 72, 100, 68, 70, 78, 90, 83, 87, 91, 80, 92, 88, 95, 98, 100, 106, 85, 93, 98, 74, 68, 8, 66, 20, 29, 65, 2, 12, 16, 5, 14, 27, 4, 0, 37, 8, 80, 99, 112, 126, 126, 126, 126, 3, 39, 30, 26, 16, 26, 13, 7, 5, 67, 74, 69, 6, 65, 17, 25, 0, 0, 8, 12, 6, 11, 23, 7, 1, 37, 8, 80, 99, 112, 126, 126, 126, 126 }, { 35, 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 73, 74, 28, 59, 14, 9, 69, 6, 13, 1, 69, 0, 75, 90, 0, 87, 126, 125, 125, 35, 68, 67, 6, 13, 1, 82, 2, 11, 3, 64, 71, 74, 0, 79, 80, 93, 5, 67, 71, 4, 74, 70, 84, 0, 74, 72, 81, 2, 2, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 20, 66, 84, 22, 12, 5, 39, 42, 14, 17, 4, 17, 64, 1, 15, 80, 78, 78, 83, 24, 65, 12, 13, 68, 77, 71, 4, 2, 71, 70, 78, 17, 68, 5, 68, 74, 1, 69, 2, 5, 3, 4, 11, 7, 68, 2, 62, 62, 46, 37, 6, 67, 1, 9, 0, 11, 9, 2, 71, 19, 68, 90, 87, 33, 9, 13, 5, 6, 9, 14, 34, 20, 9, 1, 2, 2, 14, 18, 94, 1, 5, 76, 3, 64, 12, 10, 66, 19, 12, 13, 79, 6, 70, 68, 10, 89, 10, 14, 11, 2, 70, 9, 2, 9, 5, 74, 67, 5, 126, 62, 107, 68, 73, 75, 82, 76, 79, 92, 83, 91, 77, 91, 71, 62, 95, 16, 19, 13, 8, 7, 7, 3, 3, 1, 74, 76, 75, 79, 81, 104, 75, 76, 94, 65, 70, 72, 75, 82, 85, 84, 85, 89, 81, 91, 97, 88, 94, 2, 25, 18, 10, 8, 11, 3, 64, 65, 11, 8, 31, 21, 18, 13, 19, 10, 11, 7, 3, 12, 41, 33, 28, 25, 26, 10, 3, 0, 66, 70, 49, 29, 8, 1, 13, 1, 73, 71, 5, 44, 34, 18, 9, 20, 5, 1, 67, 69, 62, 80, 73, 66, 69, 69, 67, 2, 6, 64, 3, 7, 14, 73, 69, 77, 73, 11, 75, 75, 73, 65, 0, 69, 74, 69, 75, 69, 71, 84, 10, 10, 21, 4, 65, 10, 6, 2, 5, 4, 1, 70, 0, 66, 70, 67, 11, 90, 72, 16, 74, 2, 67, 1, 11, 67, 69, 4, 7, 81, 72, 89, 28, 30, 33, 19, 12, 16, 13, 11, 8, 2, 3, 1, 73, 72, 79, 79, 74, 80, 94, 79, 78, 79, 78, 86, 79, 87, 87, 81, 89, 16, 14, 13, 10, 67, 2, 0, 78, 71, 77, 80, 95, 87, 93, 98, 74, 73, 101, 70, 72, 80, 92, 84, 89, 93, 81, 93, 89, 96, 99, 101, 107, 86, 94, 99, 74, 67, 8, 65, 21, 30, 65, 2, 12, 16, 5, 15, 28, 4, 0, 36, 6, 82, 102, 115, 126, 126, 126, 126, 3, 39, 30, 26, 16, 26, 14, 7, 5, 66, 74, 69, 7, 64, 18, 26, 0, 1, 8, 12, 6, 11, 23, 7, 1, 36, 6, 82, 102, 115, 126, 126, 126, 126 }, { 33, 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 74, 76, 27, 59, 13, 11, 69, 6, 14, 0, 70, 0, 76, 92, 64, 90, 126, 126, 126, 38, 67, 67, 6, 14, 0, 82, 3, 11, 2, 64, 70, 73, 0, 79, 80, 93, 5, 67, 71, 4, 74, 70, 83, 0, 74, 72, 81, 3, 2, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 19, 66, 84, 24, 13, 6, 41, 44, 15, 18, 5, 18, 1, 2, 17, 80, 77, 78, 83, 24, 65, 13, 15, 68, 77, 70, 6, 2, 71, 70, 79, 17, 68, 5, 67, 73, 2, 68, 3, 5, 3, 5, 12, 8, 67, 3, 62, 62, 49, 40, 6, 66, 1, 9, 0, 11, 9, 2, 71, 21, 68, 92, 89, 35, 9, 14, 5, 6, 9, 14, 36, 21, 10, 0, 2, 2, 15, 18, 95, 1, 5, 76, 2, 65, 11, 9, 67, 19, 12, 13, 80, 6, 71, 69, 9, 89, 9, 13, 10, 0, 74, 8, 0, 7, 3, 76, 69, 3, 126, 62, 111, 70, 75, 78, 85, 78, 83, 97, 87, 94, 79, 94, 73, 62, 96, 14, 17, 10, 6, 5, 5, 1, 1, 64, 77, 78, 77, 81, 83, 106, 75, 76, 95, 66, 71, 73, 77, 83, 87, 85, 86, 90, 81, 92, 97, 89, 93, 3, 26, 18, 10, 9, 11, 4, 0, 64, 13, 10, 31, 22, 18, 13, 20, 11, 13, 9, 4, 12, 42, 34, 29, 26, 27, 10, 3, 1, 65, 70, 50, 29, 8, 0, 13, 1, 73, 71, 5, 44, 33, 17, 8, 20, 5, 2, 67, 69, 62, 78, 72, 65, 67, 67, 66, 3, 7, 0, 5, 8, 16, 72, 68, 77, 72, 12, 75, 75, 73, 64, 0, 69, 75, 69, 76, 69, 71, 84, 10, 10, 22, 4, 65, 10, 6, 2, 5, 4, 1, 71, 0, 66, 70, 67, 12, 91, 73, 16, 75, 2, 68, 0, 11, 68, 70, 4, 7, 82, 72, 90, 27, 29, 32, 17, 10, 14, 11, 8, 6, 64, 0, 65, 76, 75, 81, 84, 78, 84, 99, 82, 82, 82, 81, 89, 81, 89, 88, 82, 89, 13, 12, 10, 8, 70, 64, 66, 81, 73, 80, 82, 98, 89, 95, 100, 75, 73, 103, 71, 73, 81, 94, 86, 91, 94, 82, 95, 90, 97, 101, 102, 107, 88, 96, 101, 73, 67, 9, 65, 22, 31, 65, 3, 13, 17, 5, 15, 29, 4, 0, 35, 4, 85, 105, 119, 126, 126, 126, 126, 4, 39, 30, 26, 16, 27, 14, 7, 5, 66, 74, 69, 7, 64, 18, 27, 0, 1, 9, 13, 6, 11, 24, 7, 1, 35, 4, 85, 105, 119, 126, 126, 126, 126 }, { 32, 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 75, 78, 26, 59, 13, 13, 69, 6, 15, 0, 70, 0, 77, 94, 65, 92, 126, 126, 126, 41, 66, 66, 6, 15, 0, 82, 4, 11, 2, 64, 70, 72, 0, 79, 80, 93, 5, 67, 71, 4, 74, 69, 83, 0, 74, 72, 81, 3, 2, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 18, 67, 84, 27, 14, 7, 43, 46, 17, 20, 7, 20, 2, 3, 20, 80, 77, 77, 82, 24, 65, 14, 17, 68, 76, 70, 8, 2, 72, 71, 79, 17, 68, 6, 67, 72, 3, 68, 4, 6, 4, 7, 14, 9, 65, 4, 62, 62, 52, 43, 7, 66, 2, 10, 0, 11, 10, 2, 70, 23, 68, 94, 91, 38, 9, 14, 5, 6, 9, 15, 37, 22, 10, 0, 2, 2, 15, 19, 96, 2, 5, 77, 2, 66, 11, 9, 67, 19, 12, 13, 81, 7, 72, 69, 9, 89, 9, 12, 9, 65, 77, 7, 64, 5, 1, 78, 72, 1, 126, 62, 114, 72, 77, 80, 88, 81, 86, 101, 91, 96, 81, 98, 75, 62, 98, 12, 15, 8, 4, 3, 3, 64, 64, 66, 79, 80, 79, 82, 85, 108, 75, 76, 95, 67, 72, 74, 78, 84, 88, 86, 88, 91, 82, 93, 98, 90, 92, 5, 27, 19, 10, 9, 12, 4, 0, 0, 15, 11, 31, 22, 19, 14, 21, 12, 14, 10, 6, 13, 43, 35, 30, 26, 28, 11, 4, 1, 65, 70, 50, 29, 7, 0, 13, 1, 73, 70, 5, 43, 32, 16, 7, 20, 5, 2, 67, 68, 62, 77, 70, 0, 65, 66, 65, 5, 9, 1, 7, 10, 18, 71, 67, 76, 71, 14, 75, 75, 72, 64, 1, 69, 75, 69, 76, 69, 71, 85, 11, 10, 23, 4, 65, 10, 6, 2, 5, 4, 1, 71, 0, 66, 70, 67, 12, 92, 74, 16, 76, 1, 68, 64, 11, 68, 71, 4, 7, 83, 72, 91, 26, 28, 31, 15, 8, 12, 9, 6, 3, 67, 66, 68, 79, 77, 83, 88, 82, 88, 104, 85, 85, 84, 83, 91, 83, 90, 90, 82, 90, 11, 9, 8, 6, 73, 67, 68, 84, 75, 82, 84, 101, 91, 97, 101, 75, 74, 105, 72, 75, 83, 96, 88, 93, 96, 83, 96, 91, 97, 102, 103, 108, 89, 97, 102, 73, 67, 10, 64, 23, 32, 64, 4, 13, 17, 5, 16, 30, 4, 0, 34, 2, 88, 108, 122, 126, 126, 126, 126, 4, 39, 30, 26, 16, 27, 14, 8, 6, 65, 74, 69, 8, 64, 19, 28, 0, 1, 9, 13, 6, 12, 24, 7, 1, 34, 2, 88, 108, 122, 126, 126, 126, 126 }, { 31, 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 77, 80, 25, 59, 13, 14, 69, 7, 15, 0, 71, 1, 77, 95, 67, 95, 126, 126, 126, 43, 65, 66, 7, 15, 0, 83, 4, 11, 1, 64, 70, 72, 0, 79, 79, 92, 5, 66, 70, 4, 73, 69, 83, 0, 74, 72, 81, 3, 2, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 18, 67, 84, 29, 15, 8, 44, 49, 18, 21, 9, 21, 3, 4, 22, 80, 77, 77, 82, 24, 65, 15, 20, 68, 75, 69, 10, 1, 72, 71, 80, 18, 68, 6, 66, 72, 3, 67, 5, 6, 5, 8, 15, 9, 64, 5, 62, 62, 55, 46, 7, 66, 2, 10, 0, 11, 10, 3, 70, 25, 67, 96, 93, 41, 9, 14, 5, 6, 10, 15, 39, 23, 10, 0, 2, 2, 16, 20, 97, 2, 5, 77, 1, 67, 10, 9, 68, 19, 12, 13, 83, 7, 73, 70, 8, 89, 9, 12, 9, 67, 80, 6, 66, 2, 65, 80, 74, 64, 126, 62, 118, 74, 78, 82, 92, 83, 89, 105, 96, 99, 83, 101, 77, 62, 100, 10, 13, 6, 1, 0, 1, 67, 66, 68, 81, 83, 81, 84, 86, 109, 75, 76, 96, 68, 73, 75, 79, 85, 89, 87, 90, 92, 82, 94, 99, 90, 92, 6, 28, 19, 10, 9, 13, 5, 1, 0, 17, 12, 32, 22, 19, 15, 22, 13, 16, 12, 8, 14, 43, 35, 30, 27, 29, 11, 4, 1, 64, 70, 51, 29, 6, 0, 13, 1, 73, 70, 4, 43, 32, 15, 6, 19, 5, 2, 67, 68, 62, 76, 69, 1, 64, 64, 64, 7, 11, 2, 8, 12, 20, 71, 66, 76, 70, 16, 74, 75, 72, 0, 1, 69, 75, 69, 77, 69, 71, 85, 11, 11, 24, 4, 65, 11, 7, 2, 6, 5, 1, 72, 0, 66, 70, 67, 13, 93, 75, 16, 77, 0, 69, 64, 11, 69, 71, 4, 6, 85, 73, 92, 24, 27, 30, 14, 6, 10, 7, 4, 1, 69, 69, 70, 82, 80, 85, 92, 86, 92, 108, 89, 88, 87, 85, 94, 85, 92, 91, 83, 91, 9, 7, 5, 3, 76, 69, 71, 87, 78, 85, 87, 104, 93, 98, 103, 76, 75, 106, 74, 76, 85, 98, 89, 95, 98, 84, 98, 92, 98, 103, 104, 109, 91, 98, 103, 73, 66, 10, 64, 24, 33, 64, 4, 14, 18, 5, 16, 31, 4, 0, 33, 0, 90, 111, 125, 126, 126, 126, 126, 4, 39, 30, 26, 16, 28, 15, 8, 6, 65, 73, 68, 8, 0, 20, 29, 1, 2, 9, 13, 6, 12, 25, 7, 1, 33, 0, 90, 111, 125, 126, 126, 126, 126 }, { 30, 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 78, 82, 24, 59, 13, 16, 69, 7, 16, 64, 71, 1, 78, 97, 68, 97, 126, 126, 126, 46, 64, 66, 7, 16, 64, 83, 5, 11, 1, 64, 69, 71, 0, 79, 79, 92, 5, 66, 70, 4, 73, 68, 82, 0, 74, 72, 81, 4, 2, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 17, 68, 84, 32, 16, 9, 46, 51, 20, 23, 10, 23, 5, 5, 24, 80, 76, 76, 82, 24, 65, 16, 22, 68, 74, 69, 12, 1, 73, 72, 80, 18, 68, 7, 66, 71, 4, 67, 6, 7, 6, 10, 17, 10, 0, 6, 62, 62, 58, 49, 7, 65, 2, 11, 0, 11, 11, 3, 69, 27, 67, 98, 95, 44, 9, 15, 5, 6, 10, 16, 40, 24, 11, 64, 2, 2, 16, 20, 98, 2, 5, 78, 1, 68, 10, 8, 68, 19, 12, 13, 84, 7, 74, 71, 8, 89, 9, 11, 8, 69, 83, 5, 68, 0, 67, 82, 77, 66, 126, 62, 121, 76, 80, 85, 95, 86, 92, 110, 100, 102, 85, 105, 79, 62, 101, 8, 11, 4, 64, 65, 64, 69, 68, 70, 84, 85, 83, 86, 88, 111, 75, 76, 96, 69, 74, 76, 81, 86, 90, 88, 91, 93, 83, 95, 99, 91, 91, 8, 29, 20, 10, 10, 13, 5, 1, 1, 19, 14, 32, 23, 20, 15, 23, 14, 17, 13, 10, 14, 44, 36, 31, 27, 30, 12, 4, 2, 64, 70, 51, 29, 6, 64, 13, 1, 73, 69, 4, 42, 31, 14, 5, 19, 5, 3, 67, 68, 62, 74, 67, 2, 1, 0, 0, 8, 12, 3, 10, 13, 22, 70, 65, 75, 69, 18, 74, 75, 72, 0, 2, 69, 76, 69, 77, 69, 71, 86, 12, 11, 25, 4, 65, 11, 7, 2, 6, 5, 1, 72, 0, 66, 70, 67, 13, 94, 76, 16, 78, 0, 70, 65, 11, 70, 72, 4, 6, 86, 73, 93, 23, 26, 29, 12, 4, 8, 5, 1, 65, 72, 72, 73, 85, 82, 87, 97, 90, 96, 113, 92, 91, 89, 87, 96, 87, 94, 93, 84, 91, 6, 4, 3, 1, 79, 72, 73, 90, 80, 87, 89, 107, 95, 100, 104, 77, 75, 108, 75, 78, 86, 100, 91, 97, 99, 85, 99, 93, 99, 105, 105, 109, 92, 100, 105, 72, 66, 11, 0, 25, 34, 64, 5, 14, 18, 5, 17, 32, 4, 0, 32, 65, 93, 114, 126, 126, 126, 126, 126, 5, 39, 30, 26, 16, 28, 15, 8, 6, 64, 73, 68, 9, 0, 21, 30, 1, 2, 10, 14, 6, 12, 25, 7, 1, 32, 65, 93, 114, 126, 126, 126, 126, 126 }, { 28, 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 80, 85, 23, 59, 12, 18, 70, 7, 17, 64, 72, 1, 79, 99, 69, 100, 126, 126, 126, 49, 0, 66, 7, 17, 64, 84, 6, 11, 0, 64, 69, 71, 64, 80, 79, 92, 5, 66, 70, 4, 73, 68, 82, 0, 74, 72, 81, 4, 2, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 16, 68, 85, 34, 17, 10, 47, 53, 21, 24, 12, 24, 6, 6, 26, 80, 76, 76, 82, 24, 65, 17, 24, 68, 74, 68, 14, 1, 73, 72, 81, 18, 68, 7, 65, 70, 5, 66, 6, 7, 6, 11, 18, 10, 1, 7, 62, 62, 61, 51, 7, 65, 2, 11, 64, 11, 11, 3, 69, 29, 67, 100, 97, 46, 9, 15, 5, 6, 10, 16, 42, 24, 11, 64, 1, 2, 17, 21, 100, 2, 5, 78, 0, 70, 9, 8, 69, 19, 12, 12, 85, 7, 75, 72, 7, 89, 8, 10, 7, 71, 87, 3, 70, 65, 70, 85, 79, 68, 126, 62, 125, 78, 82, 87, 98, 88, 96, 114, 104, 105, 87, 108, 81, 62, 103, 6, 8, 1, 67, 68, 67, 71, 71, 72, 86, 87, 85, 88, 90, 113, 75, 77, 97, 70, 76, 77, 82, 87, 92, 90, 93, 94, 83, 96, 100, 92, 91, 9, 30, 20, 10, 10, 14, 6, 2, 1, 21, 15, 32, 23, 20, 16, 24, 15, 19, 15, 11, 15, 44, 36, 31, 28, 31, 12, 4, 2, 0, 71, 52, 29, 5, 64, 13, 1, 73, 69, 4, 42, 30, 13, 4, 19, 5, 3, 67, 68, 62, 73, 66, 3, 2, 2, 1, 10, 14, 4, 11, 15, 24, 70, 65, 75, 68, 19, 74, 75, 72, 1, 2, 69, 76, 69, 78, 69, 71, 86, 12, 11, 26, 4, 66, 11, 7, 1, 6, 5, 1, 73, 0, 66, 70, 67, 14, 95, 77, 16, 80, 64, 71, 66, 10, 71, 73, 4, 6, 87, 73, 95, 22, 24, 28, 10, 2, 6, 3, 64, 67, 75, 75, 76, 88, 85, 89, 101, 94, 101, 118, 96, 95, 92, 90, 99, 89, 96, 94, 85, 92, 4, 2, 0, 65, 82, 75, 76, 93, 83, 90, 92, 110, 97, 102, 106, 78, 76, 110, 77, 79, 88, 102, 93, 99, 101, 87, 101, 95, 100, 106, 106, 110, 94, 101, 106, 72, 66, 11, 0, 26, 35, 64, 5, 15, 19, 5, 17, 32, 4, 64, 31, 67, 96, 117, 126, 126, 126, 126, 126, 5, 39, 30, 26, 16, 29, 15, 8, 6, 64, 73, 68, 9, 0, 21, 30, 1, 2, 10, 14, 6, 12, 26, 7, 0, 31, 67, 96, 117, 126, 126, 126, 126, 126 }, { 27, 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 81, 87, 22, 60, 12, 20, 70, 8, 18, 64, 73, 1, 79, 100, 70, 102, 126, 126, 126, 52, 1, 65, 8, 18, 64, 84, 7, 11, 0, 0, 69, 70, 64, 80, 79, 92, 5, 66, 69, 4, 73, 68, 82, 0, 74, 72, 80, 4, 2, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 15, 68, 85, 36, 19, 11, 49, 55, 23, 25, 14, 26, 7, 8, 29, 80, 76, 76, 81, 24, 65, 18, 26, 67, 73, 67, 16, 1, 73, 72, 81, 18, 68, 7, 64, 69, 6, 65, 7, 8, 7, 12, 20, 11, 3, 8, 62, 62, 62, 54, 8, 65, 3, 12, 64, 11, 11, 4, 68, 32, 67, 102, 98, 49, 9, 15, 5, 6, 10, 17, 44, 25, 11, 64, 1, 2, 18, 22, 101, 3, 5, 78, 64, 71, 8, 8, 70, 19, 12, 12, 86, 8, 76, 72, 6, 89, 8, 10, 7, 73, 90, 2, 71, 67, 72, 87, 81, 70, 126, 62, 126, 80, 84, 89, 101, 90, 99, 118, 108, 107, 89, 111, 83, 62, 105, 4, 6, 64, 69, 70, 69, 73, 73, 74, 88, 89, 86, 89, 91, 115, 75, 77, 97, 70, 77, 78, 83, 88, 93, 91, 95, 95, 83, 97, 101, 92, 90, 10, 31, 20, 10, 10, 15, 7, 3, 2, 24, 16, 32, 23, 20, 17, 25, 16, 21, 17, 13, 16, 45, 37, 32, 29, 32, 12, 5, 2, 1, 71, 53, 29, 4, 64, 14, 2, 73, 68, 4, 42, 30, 12, 3, 19, 5, 3, 67, 67, 62, 72, 65, 5, 4, 4, 2, 12, 16, 5, 13, 17, 26, 69, 64, 74, 67, 21, 73, 74, 71, 2, 3, 69, 76, 69, 79, 69, 71, 86, 12, 12, 27, 5, 66, 12, 7, 1, 6, 5, 1, 74, 0, 65, 69, 67, 15, 95, 78, 16, 81, 65, 71, 66, 10, 71, 74, 4, 6, 88, 73, 96, 21, 23, 28, 9, 0, 4, 1, 66, 69, 77, 78, 79, 91, 88, 91, 105, 98, 105, 123, 99, 98, 95, 92, 101, 90, 97, 95, 85, 93, 2, 0, 65, 67, 84, 77, 78, 96, 85, 92, 94, 112, 99, 104, 108, 78, 77, 111, 78, 80, 90, 104, 94, 100, 103, 88, 103, 96, 100, 107, 106, 111, 96, 102, 107, 72, 65, 12, 0, 27, 37, 0, 6, 16, 20, 5, 18, 33, 4, 64, 30, 69, 98, 120, 126, 126, 126, 126, 126, 5, 39, 30, 27, 17, 30, 16, 9, 7, 64, 73, 68, 9, 1, 22, 31, 1, 3, 10, 14, 6, 13, 27, 7, 0, 30, 69, 98, 120, 126, 126, 126, 126, 126 }, { 26, 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 82, 89, 21, 60, 12, 22, 70, 8, 19, 65, 73, 1, 80, 102, 71, 105, 126, 126, 126, 55, 2, 65, 8, 19, 65, 84, 8, 11, 64, 0, 68, 69, 64, 80, 79, 92, 5, 66, 69, 4, 73, 67, 81, 0, 74, 72, 80, 5, 2, 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 14, 69, 85, 39, 20, 12, 51, 57, 24, 27, 15, 27, 9, 9, 31, 80, 75, 75, 81, 24, 65, 19, 28, 67, 72, 67, 18, 1, 74, 73, 82, 18, 68, 8, 64, 68, 7, 65, 8, 8, 8, 14, 21, 12, 4, 9, 62, 62, 62, 57, 8, 64, 3, 12, 64, 11, 12, 4, 68, 34, 67, 104, 100, 52, 9, 16, 5, 6, 10, 17, 45, 26, 12, 65, 1, 2, 18, 22, 102, 3, 5, 79, 64, 72, 8, 7, 70, 19, 12, 12, 87, 8, 77, 73, 6, 89, 8, 9, 6, 75, 93, 1, 73, 69, 74, 89, 84, 72, 126, 62, 126, 82, 86, 92, 104, 93, 102, 123, 112, 110, 91, 115, 85, 62, 106, 2, 4, 66, 71, 72, 71, 75, 75, 76, 91, 91, 88, 91, 93, 117, 75, 77, 98, 71, 78, 79, 85, 89, 94, 92, 96, 96, 84, 98, 101, 93, 89, 12, 32, 21, 10, 11, 15, 7, 3, 3, 26, 18, 32, 24, 21, 17, 26, 17, 22, 18, 15, 16, 46, 38, 33, 29, 33, 13, 5, 3, 1, 71, 53, 29, 4, 65, 14, 2, 73, 68, 4, 41, 29, 11, 2, 19, 5, 4, 67, 67, 62, 70, 0, 6, 6, 5, 3, 13, 17, 6, 15, 18, 28, 68, 0, 74, 66, 23, 73, 74, 71, 2, 3, 69, 77, 69, 79, 69, 71, 87, 13, 12, 28, 5, 66, 12, 7, 1, 6, 5, 1, 74, 0, 65, 69, 67, 15, 96, 79, 16, 82, 65, 72, 67, 10, 72, 75, 4, 6, 89, 73, 97, 20, 22, 27, 7, 65, 2, 64, 69, 72, 80, 81, 82, 94, 90, 93, 110, 102, 109, 126, 102, 101, 97, 94, 104, 92, 99, 97, 86, 93, 64, 66, 68, 69, 87, 80, 81, 99, 87, 95, 96, 115, 101, 106, 109, 79, 77, 113, 79, 82, 91, 106, 96, 102, 104, 89, 104, 97, 101, 109, 107, 111, 97, 104, 109, 71, 65, 13, 1, 28, 38, 0, 7, 16, 20, 5, 18, 34, 4, 64, 29, 71, 101, 123, 126, 126, 126, 126, 126, 6, 39, 30, 27, 17, 30, 16, 9, 7, 0, 73, 68, 10, 1, 23, 32, 1, 3, 11, 15, 6, 13, 27, 7, 0, 29, 71, 101, 123, 126, 126, 126, 126, 126 }, { 25, 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 84, 91, 20, 60, 12, 23, 70, 9, 19, 65, 74, 2, 80, 103, 73, 107, 126, 126, 126, 57, 3, 65, 9, 19, 65, 85, 8, 11, 64, 0, 68, 69, 64, 80, 78, 91, 5, 65, 68, 4, 72, 67, 81, 0, 74, 72, 80, 5, 2, 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 14, 69, 85, 41, 21, 13, 52, 60, 26, 28, 17, 29, 10, 10, 33, 80, 75, 75, 81, 24, 65, 20, 31, 67, 71, 66, 20, 0, 74, 73, 82, 19, 68, 8, 0, 68, 7, 64, 9, 9, 9, 15, 23, 12, 5, 10, 62, 62, 62, 60, 8, 64, 3, 13, 64, 11, 12, 5, 67, 36, 66, 106, 102, 55, 9, 16, 5, 6, 11, 18, 47, 27, 12, 65, 1, 2, 19, 23, 103, 3, 5, 79, 65, 73, 7, 7, 71, 19, 12, 12, 89, 8, 78, 74, 5, 89, 8, 9, 6, 77, 96, 0, 75, 72, 77, 91, 86, 74, 126, 62, 126, 84, 87, 94, 108, 95, 105, 126, 117, 113, 93, 118, 87, 62, 108, 0, 2, 68, 74, 75, 73, 78, 77, 78, 93, 94, 90, 93, 94, 118, 75, 77, 98, 72, 79, 80, 86, 90, 95, 93, 98, 97, 84, 99, 102, 93, 89, 13, 33, 21, 10, 11, 16, 8, 4, 3, 28, 19, 33, 24, 21, 18, 27, 18, 24, 20, 17, 17, 46, 38, 33, 30, 34, 13, 5, 3, 2, 71, 54, 29, 3, 65, 14, 2, 73, 67, 3, 41, 29, 10, 1, 18, 5, 4, 67, 67, 62, 69, 1, 7, 7, 7, 4, 15, 19, 7, 16, 20, 30, 68, 1, 73, 65, 25, 72, 74, 71, 3, 4, 69, 77, 69, 80, 69, 71, 87, 13, 13, 29, 5, 66, 13, 8, 1, 7, 6, 1, 75, 0, 65, 69, 67, 16, 97, 80, 16, 83, 66, 73, 67, 10, 73, 75, 4, 5, 91, 74, 98, 18, 21, 26, 6, 67, 0, 66, 71, 74, 82, 84, 84, 97, 93, 95, 114, 106, 113, 126, 106, 104, 100, 96, 106, 94, 101, 98, 87, 94, 66, 68, 70, 72, 90, 82, 83, 102, 90, 97, 99, 118, 103, 107, 111, 80, 78, 114, 81, 83, 93, 108, 97, 104, 106, 90, 106, 98, 102, 110, 108, 112, 99, 105, 110, 71, 64, 13, 1, 29, 39, 0, 7, 17, 21, 5, 19, 35, 4, 64, 28, 73, 103, 126, 126, 126, 126, 126, 126, 6, 39, 30, 27, 17, 31, 17, 9, 7, 0, 72, 67, 10, 2, 24, 33, 2, 4, 11, 15, 6, 13, 28, 7, 0, 28, 73, 103, 126, 126, 126, 126, 126, 126 }, { 23, 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 85, 93, 19, 60, 11, 25, 70, 9, 20, 65, 74, 2, 81, 105, 74, 110, 126, 126, 126, 60, 4, 65, 9, 20, 65, 85, 9, 11, 65, 0, 68, 68, 64, 80, 78, 91, 5, 65, 68, 4, 72, 66, 81, 0, 74, 72, 80, 5, 2, 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 13, 70, 85, 44, 22, 14, 54, 62, 27, 30, 19, 30, 11, 11, 35, 80, 75, 74, 81, 24, 65, 21, 33, 67, 71, 66, 22, 0, 75, 74, 83, 19, 68, 9, 0, 67, 8, 64, 10, 9, 9, 17, 24, 13, 6, 11, 62, 62, 62, 62, 8, 64, 3, 13, 64, 11, 13, 5, 67, 38, 66, 108, 104, 57, 9, 16, 5, 6, 11, 18, 48, 28, 12, 65, 1, 2, 19, 24, 104, 3, 5, 80, 65, 74, 7, 7, 71, 19, 12, 12, 90, 8, 79, 75, 5, 89, 7, 8, 5, 79, 100, 64, 77, 74, 79, 93, 89, 76, 126, 62, 126, 86, 89, 96, 111, 98, 109, 126, 121, 116, 95, 122, 89, 62, 110, 65, 0, 71, 76, 77, 75, 80, 79, 80, 95, 96, 92, 95, 96, 120, 75, 77, 99, 73, 80, 81, 87, 91, 97, 94, 100, 98, 85, 100, 103, 94, 88, 15, 34, 22, 10, 11, 17, 8, 4, 4, 30, 20, 33, 24, 22, 19, 28, 19, 25, 21, 18, 18, 47, 39, 34, 30, 35, 14, 5, 3, 2, 71, 54, 29, 2, 65, 14, 2, 73, 67, 3, 40, 28, 9, 0, 18, 5, 4, 67, 67, 62, 68, 3, 8, 9, 8, 5, 17, 21, 8, 18, 22, 32, 67, 2, 73, 64, 26, 72, 74, 71, 3, 4, 69, 77, 69, 80, 69, 71, 88, 14, 13, 30, 5, 66, 13, 8, 1, 7, 6, 1, 75, 0, 65, 69, 67, 16, 98, 81, 16, 84, 67, 74, 68, 10, 74, 76, 4, 5, 92, 74, 99, 17, 20, 25, 4, 69, 65, 68, 73, 77, 85, 87, 87, 100, 95, 97, 118, 110, 117, 126, 109, 108, 102, 99, 109, 96, 103, 100, 88, 95, 68, 71, 73, 74, 93, 85, 86, 105, 92, 100, 101, 121, 105, 109, 112, 81, 79, 116, 82, 85, 95, 110, 99, 106, 108, 91, 107, 99, 103, 111, 109, 113, 100, 106, 111, 71, 64, 14, 2, 30, 40, 0, 8, 17, 21, 5, 19, 36, 4, 64, 27, 75, 106, 126, 126, 126, 126, 126, 126, 6, 39, 30, 27, 17, 31, 17, 9, 7, 1, 72, 67, 11, 2, 24, 34, 2, 4, 11, 15, 6, 13, 28, 7, 0, 27, 75, 106, 126, 126, 126, 126, 126, 126 }, { 22, 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 86, 95, 18, 60, 11, 27, 70, 9, 21, 66, 75, 2, 82, 107, 75, 112, 126, 126, 126, 62, 5, 64, 9, 21, 66, 85, 10, 11, 65, 0, 67, 67, 64, 80, 78, 91, 5, 65, 68, 4, 72, 66, 80, 0, 74, 72, 80, 6, 2, 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 12, 70, 85, 46, 23, 15, 56, 62, 29, 31, 20, 32, 13, 12, 38, 80, 74, 74, 80, 24, 65, 22, 35, 67, 70, 65, 24, 0, 75, 74, 83, 19, 68, 9, 1, 66, 9, 0, 11, 10, 10, 18, 26, 14, 8, 12, 62, 62, 62, 62, 9, 0, 4, 14, 64, 11, 13, 5, 66, 40, 66, 110, 106, 60, 9, 17, 5, 6, 11, 19, 50, 29, 13, 66, 1, 2, 20, 24, 105, 4, 5, 80, 66, 75, 6, 6, 72, 19, 12, 12, 91, 9, 80, 75, 4, 89, 7, 7, 4, 81, 103, 65, 78, 76, 81, 95, 91, 78, 126, 62, 126, 88, 91, 99, 114, 100, 112, 126, 125, 118, 97, 125, 91, 62, 111, 67, 65, 73, 78, 79, 77, 82, 81, 82, 98, 98, 94, 96, 98, 122, 75, 77, 99, 74, 81, 82, 89, 92, 98, 95, 101, 99, 85, 101, 103, 95, 87, 16, 35, 22, 10, 12, 17, 9, 5, 5, 32, 22, 33, 25, 22, 19, 29, 20, 27, 23, 20, 18, 48, 40, 35, 31, 36, 14, 6, 4, 3, 71, 55, 29, 2, 66, 14, 2, 73, 66, 3, 40, 27, 8, 64, 18, 5, 5, 67, 66, 62, 66, 4, 10, 11, 10, 6, 18, 22, 9, 20, 23, 34, 66, 3, 72, 0, 28, 72, 74, 70, 4, 5, 69, 78, 69, 81, 69, 71, 88, 14, 13, 31, 5, 66, 13, 8, 1, 7, 6, 1, 76, 0, 65, 69, 67, 17, 99, 82, 16, 85, 67, 74, 69, 10, 74, 77, 4, 5, 93, 74, 100, 16, 19, 24, 2, 71, 67, 70, 76, 79, 88, 90, 90, 103, 98, 99, 123, 114, 121, 126, 112, 111, 105, 101, 111, 98, 104, 101, 88, 95, 71, 73, 75, 76, 96, 88, 88, 108, 94, 102, 103, 124, 107, 111, 114, 81, 79, 118, 83, 86, 96, 112, 101, 108, 109, 92, 109, 100, 103, 113, 110, 113, 102, 108, 113, 70, 64, 15, 2, 31, 41, 1, 9, 18, 22, 5, 20, 37, 4, 64, 26, 77, 109, 126, 126, 126, 126, 126, 126, 7, 39, 30, 27, 17, 32, 17, 10, 8, 1, 72, 67, 11, 2, 25, 35, 2, 4, 12, 16, 6, 14, 29, 7, 0, 26, 77, 109, 126, 126, 126, 126, 126, 126 }, { 21, 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 88, 97, 17, 60, 11, 29, 70, 10, 22, 66, 75, 2, 82, 108, 76, 115, 126, 126, 126, 62, 6, 64, 10, 22, 66, 86, 11, 11, 66, 0, 67, 67, 64, 80, 78, 91, 5, 65, 67, 4, 72, 65, 80, 0, 74, 72, 80, 6, 2, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 11, 71, 85, 49, 24, 16, 57, 62, 30, 33, 22, 33, 14, 13, 40, 80, 74, 73, 80, 24, 65, 23, 37, 67, 69, 65, 26, 0, 76, 75, 84, 19, 68, 10, 1, 65, 10, 0, 12, 10, 11, 20, 27, 14, 9, 13, 62, 62, 62, 62, 9, 0, 4, 14, 64, 11, 14, 6, 66, 42, 66, 112, 108, 62, 9, 17, 5, 6, 11, 19, 51, 30, 13, 66, 1, 2, 20, 25, 106, 4, 5, 81, 66, 76, 6, 6, 72, 19, 12, 12, 92, 9, 81, 76, 4, 89, 7, 7, 4, 83, 106, 66, 80, 78, 84, 97, 94, 80, 126, 62, 126, 90, 93, 101, 117, 103, 115, 126, 126, 121, 99, 126, 93, 62, 113, 69, 67, 75, 81, 82, 79, 84, 83, 84, 100, 100, 96, 98, 99, 124, 75, 77, 100, 75, 82, 83, 90, 93, 99, 96, 103, 100, 86, 102, 104, 95, 87, 18, 36, 23, 10, 12, 18, 9, 5, 5, 34, 23, 33, 25, 23, 20, 30, 21, 28, 24, 22, 19, 48, 40, 35, 31, 37, 15, 6, 4, 3, 71, 55, 29, 1, 66, 14, 2, 73, 66, 3, 39, 27, 7, 65, 18, 5, 5, 67, 66, 62, 65, 6, 11, 12, 11, 7, 20, 24, 10, 21, 25, 36, 66, 4, 72, 1, 30, 71, 74, 70, 4, 5, 69, 78, 69, 81, 69, 71, 89, 15, 14, 32, 5, 66, 14, 8, 1, 7, 6, 1, 76, 0, 65, 69, 67, 17, 100, 83, 16, 86, 68, 75, 69, 10, 75, 78, 4, 5, 94, 74, 101, 15, 18, 23, 1, 73, 69, 72, 78, 82, 90, 93, 93, 106, 100, 101, 126, 118, 125, 126, 116, 114, 107, 103, 114, 100, 106, 103, 89, 96, 73, 76, 78, 79, 99, 90, 91, 111, 97, 105, 106, 126, 109, 113, 115, 82, 80, 119, 85, 88, 98, 114, 102, 110, 111, 93, 110, 101, 104, 114, 111, 114, 103, 109, 114, 70, 0, 15, 3, 32, 42, 1, 9, 18, 22, 5, 20, 38, 4, 64, 25, 79, 111, 126, 126, 126, 126, 126, 126, 7, 39, 30, 27, 17, 32, 18, 10, 8, 2, 72, 67, 12, 3, 26, 36, 2, 5, 12, 16, 6, 14, 29, 7, 0, 25, 79, 111, 126, 126, 126, 126, 126, 126 }, { 20, 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 89, 99, 16, 60, 11, 31, 70, 10, 23, 66, 76, 2, 83, 110, 77, 117, 126, 126, 126, 62, 7, 64, 10, 23, 66, 86, 12, 11, 66, 0, 67, 66, 64, 80, 78, 91, 5, 65, 67, 4, 72, 65, 80, 0, 74, 72, 80, 6, 2, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 10, 71, 85, 51, 25, 17, 59, 62, 32, 34, 24, 35, 15, 14, 42, 80, 74, 73, 80, 24, 65, 24, 39, 67, 68, 64, 28, 0, 76, 75, 84, 19, 68, 10, 2, 64, 11, 1, 13, 11, 12, 21, 29, 15, 10, 14, 62, 62, 62, 62, 9, 0, 4, 15, 64, 11, 14, 6, 65, 44, 66, 114, 110, 62, 9, 17, 5, 6, 11, 20, 53, 31, 13, 66, 1, 2, 21, 26, 107, 4, 5, 81, 67, 77, 5, 6, 73, 19, 12, 12, 93, 9, 82, 77, 3, 89, 7, 6, 3, 85, 109, 67, 82, 80, 86, 99, 96, 82, 126, 62, 126, 92, 95, 103, 120, 105, 118, 126, 126, 124, 101, 126, 95, 62, 115, 71, 69, 77, 83, 84, 81, 86, 85, 86, 102, 102, 98, 100, 101, 126, 75, 77, 100, 76, 83, 84, 91, 94, 100, 97, 105, 101, 86, 103, 105, 96, 86, 19, 37, 23, 10, 12, 19, 10, 6, 6, 36, 24, 33, 25, 23, 21, 31, 22, 30, 26, 24, 20, 49, 41, 36, 32, 38, 15, 6, 4, 4, 71, 56, 29, 0, 66, 14, 2, 73, 65, 3, 39, 26, 6, 66, 18, 5, 5, 67, 66, 62, 64, 7, 12, 14, 13, 8, 22, 26, 11, 23, 27, 38, 65, 5, 71, 2, 32, 71, 74, 70, 5, 6, 69, 78, 69, 82, 69, 71, 89, 15, 14, 33, 5, 66, 14, 8, 1, 7, 6, 1, 77, 0, 65, 69, 67, 18, 101, 84, 16, 87, 69, 76, 70, 10, 76, 79, 4, 5, 95, 74, 102, 14, 17, 22, 64, 75, 71, 74, 80, 84, 93, 96, 96, 109, 103, 103, 126, 122, 126, 126, 119, 117, 110, 105, 116, 102, 108, 104, 90, 97, 75, 78, 80, 81, 102, 93, 93, 114, 99, 107, 108, 126, 111, 115, 117, 83, 81, 121, 86, 89, 100, 116, 104, 112, 113, 94, 112, 102, 105, 115, 112, 115, 105, 110, 115, 70, 0, 16, 3, 33, 43, 1, 10, 19, 23, 5, 21, 39, 4, 64, 24, 81, 114, 126, 126, 126, 126, 126, 126, 7, 39, 30, 27, 17, 33, 18, 10, 8, 2, 72, 67, 12, 3, 27, 37, 2, 5, 12, 16, 6, 14, 30, 7, 0, 24, 81, 114, 126, 126, 126, 126, 126, 126 }, { 18, 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 91, 102, 15, 60, 10, 32, 71, 10, 23, 67, 77, 2, 84, 112, 79, 120, 126, 126, 126, 62, 7, 64, 10, 23, 67, 87, 12, 11, 67, 0, 67, 66, 65, 81, 78, 91, 4, 65, 67, 4, 72, 65, 80, 0, 74, 73, 80, 6, 2, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 9, 72, 86, 53, 26, 18, 60, 62, 33, 35, 25, 36, 16, 15, 44, 80, 74, 73, 80, 24, 65, 24, 41, 67, 68, 64, 29, 64, 77, 76, 85, 19, 68, 10, 2, 64, 11, 1, 13, 11, 12, 22, 30, 15, 11, 15, 62, 62, 62, 62, 9, 0, 4, 15, 65, 11, 14, 6, 65, 46, 66, 116, 112, 62, 9, 17, 5, 6, 11, 20, 54, 31, 13, 67, 0, 2, 21, 26, 109, 4, 5, 82, 68, 79, 4, 5, 74, 19, 12, 11, 95, 9, 83, 78, 2, 89, 6, 5, 2, 88, 113, 69, 84, 83, 89, 102, 99, 84, 126, 62, 126, 95, 97, 106, 124, 108, 122, 126, 126, 126, 103, 126, 97, 62, 117, 74, 72, 80, 86, 87, 84, 89, 88, 88, 105, 105, 100, 102, 103, 126, 75, 78, 101, 77, 85, 86, 93, 96, 102, 99, 107, 102, 87, 104, 106, 97, 86, 20, 37, 23, 10, 12, 19, 10, 6, 6, 38, 25, 33, 25, 23, 21, 31, 23, 31, 27, 25, 20, 49, 41, 36, 32, 39, 15, 6, 4, 4, 72, 56, 28, 64, 67, 14, 2, 73, 65, 2, 38, 25, 4, 67, 17, 5, 5, 67, 66, 62, 0, 8, 13, 15, 14, 9, 23, 27, 12, 24, 28, 40, 65, 5, 71, 3, 33, 71, 74, 70, 5, 6, 69, 79, 70, 83, 69, 72, 90, 15, 14, 34, 5, 67, 14, 8, 0, 7, 6, 1, 78, 0, 65, 69, 67, 18, 102, 85, 16, 89, 70, 77, 71, 9, 77, 80, 4, 4, 97, 75, 104, 12, 15, 21, 66, 77, 74, 77, 83, 87, 96, 99, 99, 113, 106, 105, 126, 126, 126, 126, 123, 121, 113, 108, 119, 104, 110, 106, 91, 98, 78, 81, 83, 84, 105, 96, 96, 118, 102, 110, 111, 126, 113, 117, 119, 84, 82, 123, 88, 91, 102, 119, 106, 114, 115, 96, 114, 104, 106, 117, 113, 116, 107, 112, 117, 70, 0, 16, 3, 34, 44, 1, 10, 19, 23, 5, 21, 39, 4, 65, 22, 83, 117, 126, 126, 126, 126, 126, 126, 7, 39, 30, 27, 17, 33, 18, 10, 8, 2, 72, 67, 12, 3, 27, 37, 2, 5, 12, 16, 6, 14, 30, 6, 64, 22, 83, 117, 126, 126, 126, 126, 126, 126 }, { 17, 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 92, 104, 14, 61, 10, 34, 71, 11, 24, 67, 77, 3, 84, 113, 80, 122, 126, 126, 126, 62, 8, 0, 11, 24, 67, 87, 13, 11, 67, 1, 66, 65, 65, 81, 77, 90, 4, 64, 66, 4, 71, 64, 79, 1, 73, 73, 79, 7, 2, 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 9, 72, 86, 56, 28, 20, 62, 62, 35, 37, 27, 38, 18, 17, 47, 80, 73, 72, 79, 24, 65, 25, 44, 66, 67, 0, 31, 64, 77, 76, 85, 20, 68, 11, 3, 0, 12, 2, 14, 12, 13, 24, 32, 16, 13, 17, 62, 62, 62, 62, 10, 1, 5, 16, 65, 12, 15, 7, 64, 49, 65, 118, 113, 62, 9, 18, 5, 7, 12, 21, 56, 32, 14, 67, 0, 2, 22, 27, 110, 5, 5, 82, 68, 80, 4, 5, 74, 19, 12, 11, 96, 10, 83, 78, 2, 89, 6, 5, 2, 90, 116, 70, 85, 85, 91, 104, 101, 86, 126, 62, 126, 97, 98, 108, 126, 110, 125, 126, 126, 126, 105, 126, 99, 62, 118, 76, 74, 82, 88, 89, 86, 91, 90, 90, 107, 107, 101, 103, 104, 126, 75, 78, 101, 77, 86, 87, 94, 97, 103, 100, 108, 103, 87, 105, 106, 97, 85, 22, 38, 24, 10, 13, 20, 11, 7, 7, 41, 27, 34, 26, 24, 22, 32, 25, 33, 29, 27, 21, 50, 42, 37, 33, 40, 16, 7, 5, 5, 72, 57, 28, 64, 67, 15, 3, 73, 64, 2, 38, 25, 3, 68, 17, 6, 6, 66, 65, 62, 2, 10, 15, 17, 16, 11, 25, 29, 14, 26, 30, 43, 64, 6, 70, 5, 35, 70, 73, 69, 6, 7, 68, 79, 70, 83, 69, 72, 90, 16, 15, 35, 6, 67, 15, 9, 0, 8, 7, 1, 78, 1, 64, 68, 66, 19, 102, 86, 16, 90, 70, 77, 71, 9, 77, 80, 4, 4, 98, 75, 105, 11, 14, 21, 67, 78, 76, 79, 85, 89, 98, 101, 101, 116, 108, 107, 126, 126, 126, 126, 126, 124, 115, 110, 121, 105, 111, 107, 91, 98, 80, 83, 85, 86, 107, 98, 98, 121, 104, 112, 113, 126, 114, 118, 120, 84, 82, 124, 89, 92, 103, 121, 107, 115, 116, 97, 115, 105, 106, 118, 113, 116, 108, 113, 118, 69, 1, 17, 4, 36, 46, 2, 11, 20, 24, 6, 22, 40, 4, 65, 21, 85, 119, 126, 126, 126, 126, 126, 126, 8, 39, 31, 28, 18, 34, 19, 11, 9, 3, 71, 66, 13, 4, 28, 38, 3, 6, 13, 17, 6, 15, 31, 6, 64, 21, 85, 119, 126, 126, 126, 126, 126, 126 }, { 16, 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 93, 106, 13, 61, 10, 36, 71, 11, 25, 67, 78, 3, 85, 115, 81, 125, 126, 126, 126, 62, 9, 0, 11, 25, 67, 87, 14, 11, 68, 1, 66, 64, 65, 81, 77, 90, 4, 64, 66, 4, 71, 64, 79, 1, 73, 73, 79, 7, 2, 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 8, 72, 86, 58, 29, 21, 62, 62, 36, 38, 29, 39, 19, 18, 49, 80, 73, 72, 79, 24, 65, 26, 46, 66, 66, 1, 33, 64, 77, 76, 86, 20, 68, 11, 4, 1, 13, 3, 15, 12, 14, 25, 33, 17, 14, 18, 62, 62, 62, 62, 10, 1, 5, 16, 65, 12, 15, 7, 64, 51, 65, 120, 115, 62, 9, 18, 5, 7, 12, 21, 58, 33, 14, 67, 0, 2, 23, 28, 111, 5, 5, 82, 69, 81, 3, 5, 75, 19, 12, 11, 97, 10, 84, 79, 1, 89, 6, 4, 1, 92, 119, 71, 87, 87, 93, 106, 103, 88, 126, 62, 126, 99, 100, 110, 126, 112, 126, 126, 126, 126, 107, 126, 101, 62, 120, 78, 76, 84, 90, 91, 88, 93, 92, 92, 109, 109, 103, 105, 106, 126, 75, 78, 102, 78, 87, 88, 95, 98, 104, 101, 110, 104, 87, 106, 107, 98, 84, 23, 39, 24, 10, 13, 21, 12, 8, 8, 43, 28, 34, 26, 24, 23, 33, 26, 35, 31, 29, 22, 51, 43, 38, 34, 41, 16, 7, 5, 6, 72, 58, 28, 65, 67, 15, 3, 73, 64, 2, 38, 24, 2, 69, 17, 6, 6, 66, 65, 62, 3, 11, 16, 19, 18, 12, 27, 31, 15, 28, 32, 45, 0, 7, 70, 6, 37, 70, 73, 69, 7, 7, 68, 79, 70, 84, 69, 72, 90, 16, 15, 36, 6, 67, 15, 9, 0, 8, 7, 1, 79, 1, 64, 68, 66, 20, 103, 87, 16, 91, 71, 78, 72, 9, 78, 81, 4, 4, 99, 75, 106, 10, 13, 20, 69, 80, 78, 81, 87, 91, 101, 104, 104, 119, 111, 109, 126, 126, 126, 126, 126, 126, 118, 112, 124, 107, 113, 108, 92, 99, 82, 85, 88, 88, 110, 101, 101, 124, 106, 115, 115, 126, 116, 120, 122, 85, 83, 126, 90, 93, 105, 123, 109, 117, 118, 98, 117, 106, 107, 119, 114, 117, 110, 114, 119, 69, 1, 18, 4, 37, 47, 2, 12, 21, 25, 6, 22, 41, 4, 65, 20, 87, 122, 126, 126, 126, 126, 126, 126, 8, 39, 31, 28, 18, 35, 19, 11, 9, 3, 71, 66, 13, 4, 29, 39, 3, 6, 13, 17, 6, 15, 32, 6, 64, 20, 87, 122, 126, 126, 126, 126, 126, 126 }, { 15, 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 95, 108, 12, 61, 10, 38, 71, 12, 26, 67, 78, 3, 85, 116, 82, 126, 126, 126, 126, 62, 10, 0, 12, 26, 67, 88, 15, 11, 68, 1, 66, 64, 65, 81, 77, 90, 4, 64, 65, 4, 71, 0, 79, 1, 73, 73, 79, 7, 2, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 7, 73, 86, 61, 30, 22, 62, 62, 38, 40, 31, 41, 20, 19, 51, 80, 73, 71, 79, 24, 65, 27, 48, 66, 65, 1, 35, 64, 78, 77, 86, 20, 68, 12, 4, 2, 14, 3, 16, 13, 15, 27, 35, 17, 15, 19, 62, 62, 62, 62, 10, 1, 5, 17, 65, 12, 16, 8, 0, 53, 65, 122, 117, 62, 9, 18, 5, 7, 12, 22, 59, 34, 14, 67, 0, 2, 23, 29, 112, 5, 5, 83, 69, 82, 3, 5, 75, 19, 12, 11, 98, 10, 85, 80, 1, 89, 6, 4, 1, 94, 122, 72, 89, 89, 96, 108, 106, 90, 126, 62, 126, 101, 102, 112, 126, 115, 126, 126, 126, 126, 109, 126, 103, 62, 122, 80, 78, 86, 93, 94, 90, 95, 94, 94, 111, 111, 105, 107, 107, 126, 75, 78, 102, 79, 88, 89, 96, 99, 105, 102, 112, 105, 88, 107, 108, 98, 84, 25, 40, 25, 10, 13, 22, 12, 8, 8, 45, 29, 34, 26, 25, 24, 34, 27, 36, 32, 31, 23, 51, 43, 38, 34, 42, 17, 7, 5, 6, 72, 58, 28, 66, 67, 15, 3, 73, 0, 2, 37, 24, 1, 70, 17, 6, 6, 66, 65, 62, 4, 13, 17, 20, 19, 13, 29, 33, 16, 29, 34, 47, 0, 8, 69, 7, 39, 69, 73, 69, 7, 8, 68, 79, 70, 84, 69, 72, 91, 17, 16, 37, 6, 67, 16, 9, 0, 8, 7, 1, 79, 1, 64, 68, 66, 20, 104, 88, 16, 92, 72, 79, 72, 9, 79, 82, 4, 4, 100, 75, 107, 9, 12, 19, 70, 82, 80, 83, 89, 94, 103, 107, 107, 122, 113, 111, 126, 126, 126, 126, 126, 126, 120, 114, 126, 109, 115, 110, 93, 100, 84, 88, 90, 91, 113, 103, 103, 126, 109, 117, 118, 126, 118, 122, 123, 86, 84, 126, 92, 95, 107, 125, 110, 119, 120, 99, 118, 107, 108, 120, 115, 118, 111, 115, 120, 69, 2, 18, 5, 38, 48, 2, 12, 21, 25, 6, 23, 42, 4, 65, 19, 89, 124, 126, 126, 126, 126, 126, 126, 8, 39, 31, 28, 18, 35, 20, 11, 9, 4, 71, 66, 14, 5, 30, 40, 3, 7, 13, 17, 6, 15, 32, 6, 64, 19, 89, 124, 126, 126, 126, 126, 126, 126 }, }, { { 62, 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 47, 62, 62, 12, 1, 99, 47, 85, 102, 6, 6, 73, 6, 23, 53, 62, 62, 21, 97, 126, 117, 74, 85, 102, 6, 93, 88, 19, 8, 89, 103, 116, 6, 5, 84, 96, 0, 85, 106, 0, 75, 90, 101, 8, 79, 75, 97, 13, 3, 22, 0, 0, 0, 83, 86, 97, 72, 22, 1, 29, 88, 126, 126, 91, 95, 84, 86, 89, 91, 126, 76, 103, 90, 126, 80, 76, 84, 78, 8, 2, 83, 126, 79, 104, 91, 126, 65, 79, 72, 92, 7, 68, 71, 98, 86, 88, 82, 72, 67, 72, 89, 69, 4, 66, 6, 71, 71, 5, 74, 19, 69, 1, 12, 16, 21, 22, 10, 76, 78, 83, 11, 67, 90, 67, 72, 75, 80, 83, 64, 32, 64, 94, 75, 0, 74, 28, 36, 91, 65, 69, 77, 66, 1, 68, 81, 33, 56, 40, 74, 66, 124, 26, 62, 62, 126, 24, 21, 29, 34, 32, 26, 21, 23, 30, 20, 27, 16, 8, 5, 3, 19, 19, 21, 15, 7, 11, 26, 14, 5, 15, 18, 69, 30, 0, 62, 62, 62, 53, 62, 62, 62, 62, 46, 38, 34, 30, 48, 43, 73, 29, 32, 19, 47, 27, 27, 35, 42, 43, 51, 47, 21, 93, 7, 6, 25, 126, 115, 82, 1, 10, 4, 85, 89, 94, 92, 126, 100, 6, 67, 71, 77, 85, 88, 104, 98, 126, 82, 15, 2, 66, 70, 75, 79, 83, 92, 108, 79, 69, 75, 5, 5, 78, 83, 81, 99, 81, 25, 1, 5, 4, 73, 76, 86, 83, 87, 62, 126, 126, 120, 126, 114, 117, 118, 117, 113, 118, 120, 124, 94, 102, 99, 106, 126, 92, 6, 86, 94, 91, 77, 71, 73, 64, 81, 64, 6, 67, 68, 67, 68, 77, 64, 68, 78, 8, 4, 65, 9, 19, 3, 70, 76, 86, 70, 64, 70, 8, 7, 69, 65, 74, 9, 9, 76, 82, 77, 77, 21, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 52, 62, 62, 62, 62, 62, 62, 48, 62, 62, 46, 25, 18, 9, 79, 62, 62, 62, 62, 48, 48, 38, 41, 47, 45, 35, 22, 35, 16, 1, 32, 37, 39, 40, 47, 33, 34, 22, 21, 3, 11, 3, 78, 123, 10, 7, 2, 30, 13, 2, 78, 74, 72, 72, 75, 71, 0, 70, 75, 72, 67, 10, 4, 11, 68, 62, 62, 62, 62, 56, 51, 40, 25, 64, 71, 26, 19, 14, 7, 4, 0, 67, 68, 79, 78, 74, 72, 72, 75, 71, 0, 70, 75, 72, 67, 10, 4, 11, 68, 62, 62, 62, 62, 56, 51, 40, 25, 64 }, { 62, 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 46, 62, 62, 13, 2, 97, 46, 84, 100, 6, 6, 71, 6, 22, 52, 62, 60, 19, 97, 125, 115, 73, 84, 100, 6, 92, 87, 20, 8, 88, 102, 114, 5, 4, 84, 96, 0, 84, 105, 0, 75, 89, 100, 8, 78, 74, 96, 14, 3, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 29, 87, 125, 124, 89, 94, 82, 84, 88, 89, 125, 75, 101, 89, 124, 80, 76, 84, 78, 9, 2, 82, 124, 78, 103, 90, 125, 65, 78, 72, 91, 8, 68, 70, 97, 85, 87, 81, 71, 66, 71, 88, 68, 5, 66, 6, 70, 70, 5, 73, 20, 68, 1, 13, 17, 22, 23, 11, 76, 77, 82, 11, 67, 89, 67, 71, 74, 79, 81, 1, 33, 1, 92, 75, 64, 73, 29, 37, 91, 65, 68, 77, 65, 1, 67, 79, 33, 56, 41, 72, 67, 122, 25, 62, 62, 125, 24, 21, 29, 34, 32, 26, 21, 23, 30, 20, 27, 16, 8, 5, 3, 19, 19, 21, 15, 7, 11, 26, 14, 4, 15, 18, 69, 29, 0, 62, 62, 62, 52, 62, 62, 62, 62, 45, 37, 32, 29, 46, 42, 74, 28, 31, 18, 46, 27, 27, 34, 41, 42, 50, 46, 20, 93, 7, 6, 24, 125, 113, 80, 2, 10, 4, 84, 88, 93, 91, 125, 98, 7, 66, 70, 76, 83, 87, 102, 97, 124, 81, 16, 3, 65, 69, 74, 78, 82, 91, 106, 78, 67, 74, 6, 5, 77, 82, 80, 98, 80, 26, 2, 6, 5, 72, 75, 85, 82, 86, 62, 125, 125, 118, 125, 112, 115, 116, 115, 111, 116, 118, 121, 93, 101, 98, 105, 123, 91, 5, 85, 93, 90, 76, 71, 72, 64, 80, 64, 6, 67, 68, 66, 68, 77, 64, 68, 77, 8, 4, 65, 9, 19, 3, 70, 75, 84, 70, 64, 69, 8, 7, 69, 65, 73, 9, 9, 75, 81, 76, 76, 20, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 50, 62, 62, 62, 62, 62, 62, 47, 60, 60, 45, 24, 17, 9, 79, 62, 62, 62, 60, 46, 47, 37, 39, 46, 43, 34, 20, 33, 15, 0, 31, 36, 37, 39, 46, 32, 33, 21, 20, 2, 11, 3, 78, 122, 9, 6, 1, 29, 12, 1, 77, 73, 71, 71, 73, 70, 1, 69, 73, 71, 66, 11, 5, 12, 67, 62, 62, 62, 62, 54, 50, 38, 24, 65, 70, 27, 20, 15, 8, 5, 1, 66, 67, 78, 77, 73, 71, 71, 73, 70, 1, 69, 73, 71, 66, 11, 5, 12, 67, 62, 62, 62, 62, 54, 50, 38, 24, 65 }, { 62, 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 44, 60, 62, 14, 2, 95, 44, 84, 99, 6, 6, 70, 5, 21, 51, 60, 57, 17, 98, 123, 114, 73, 84, 99, 6, 92, 86, 20, 8, 87, 101, 113, 4, 3, 84, 96, 0, 84, 104, 0, 75, 89, 100, 8, 78, 74, 95, 14, 3, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 29, 86, 124, 122, 88, 93, 80, 82, 87, 88, 123, 74, 100, 88, 122, 81, 76, 84, 78, 9, 2, 81, 122, 78, 102, 89, 123, 65, 78, 72, 91, 8, 68, 70, 96, 85, 86, 81, 71, 66, 71, 87, 67, 5, 66, 6, 70, 70, 5, 73, 20, 68, 1, 13, 17, 22, 23, 11, 77, 76, 81, 10, 67, 89, 67, 70, 74, 79, 80, 2, 34, 3, 90, 76, 65, 73, 29, 37, 92, 65, 68, 78, 64, 1, 67, 78, 33, 56, 41, 71, 68, 121, 24, 62, 62, 124, 24, 21, 29, 33, 31, 26, 21, 23, 29, 19, 26, 16, 8, 5, 3, 18, 18, 20, 15, 7, 11, 25, 13, 3, 14, 17, 69, 28, 64, 62, 62, 62, 50, 60, 62, 62, 62, 44, 35, 30, 27, 44, 40, 75, 27, 30, 16, 45, 26, 26, 33, 39, 40, 48, 44, 18, 93, 6, 5, 22, 124, 112, 79, 3, 10, 4, 83, 87, 92, 90, 123, 97, 8, 65, 69, 75, 82, 86, 101, 96, 122, 80, 16, 3, 65, 69, 73, 77, 81, 90, 105, 78, 66, 73, 6, 5, 76, 81, 80, 97, 79, 26, 3, 6, 5, 71, 74, 84, 81, 85, 62, 124, 123, 116, 123, 111, 114, 114, 113, 110, 114, 116, 119, 92, 100, 97, 104, 120, 91, 4, 85, 92, 89, 76, 71, 72, 64, 80, 64, 5, 67, 68, 65, 68, 77, 64, 68, 77, 8, 4, 65, 8, 18, 3, 70, 75, 83, 71, 64, 68, 7, 7, 69, 65, 73, 9, 9, 75, 80, 76, 76, 18, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 48, 62, 62, 62, 62, 62, 61, 45, 58, 58, 43, 23, 16, 8, 79, 62, 62, 62, 58, 44, 45, 35, 37, 44, 41, 32, 18, 31, 13, 64, 30, 35, 35, 37, 44, 30, 31, 20, 19, 1, 10, 2, 78, 121, 8, 5, 64, 28, 11, 0, 77, 73, 70, 70, 72, 69, 2, 69, 72, 70, 65, 11, 6, 13, 66, 62, 62, 62, 60, 52, 48, 36, 22, 66, 69, 27, 20, 16, 9, 6, 1, 65, 67, 77, 77, 73, 70, 70, 72, 69, 2, 69, 72, 70, 65, 11, 6, 13, 66, 62, 62, 62, 60, 52, 48, 36, 22, 66 }, { 62, 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 42, 59, 61, 14, 2, 93, 43, 84, 97, 6, 5, 69, 4, 20, 50, 58, 53, 15, 99, 121, 112, 73, 84, 97, 6, 91, 85, 21, 8, 86, 100, 112, 3, 2, 84, 97, 0, 84, 103, 0, 76, 89, 100, 8, 78, 74, 94, 15, 3, 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 28, 86, 123, 120, 87, 92, 79, 81, 86, 87, 121, 73, 99, 87, 120, 82, 76, 84, 78, 10, 2, 80, 120, 78, 101, 88, 121, 65, 78, 72, 91, 9, 68, 69, 95, 85, 85, 81, 71, 66, 70, 86, 67, 5, 66, 6, 70, 70, 5, 73, 20, 68, 1, 14, 17, 23, 23, 12, 77, 76, 80, 10, 67, 89, 67, 69, 74, 78, 79, 3, 35, 4, 88, 76, 66, 72, 29, 37, 93, 65, 67, 78, 64, 1, 67, 77, 33, 56, 41, 70, 69, 119, 23, 62, 62, 122, 24, 21, 28, 32, 31, 25, 20, 23, 29, 18, 25, 16, 8, 5, 2, 18, 17, 19, 14, 7, 11, 24, 13, 2, 14, 16, 69, 27, 64, 62, 62, 61, 49, 58, 62, 62, 62, 43, 33, 28, 26, 42, 38, 77, 26, 29, 14, 44, 25, 25, 32, 38, 38, 46, 42, 17, 93, 5, 4, 21, 122, 110, 77, 3, 10, 4, 82, 86, 91, 89, 121, 96, 9, 64, 68, 75, 81, 85, 99, 95, 120, 80, 17, 4, 64, 68, 72, 77, 81, 89, 104, 78, 64, 72, 6, 5, 75, 81, 80, 96, 78, 27, 4, 7, 5, 70, 74, 83, 81, 85, 62, 122, 122, 115, 121, 110, 112, 113, 112, 108, 112, 114, 117, 92, 99, 97, 103, 117, 91, 3, 85, 91, 88, 76, 71, 72, 64, 79, 64, 4, 67, 68, 65, 68, 77, 64, 68, 77, 7, 4, 65, 7, 17, 3, 70, 75, 82, 72, 64, 67, 6, 7, 69, 65, 72, 9, 8, 74, 79, 76, 76, 17, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 46, 62, 62, 62, 62, 62, 59, 43, 56, 55, 41, 22, 15, 7, 79, 62, 62, 62, 56, 42, 43, 34, 35, 42, 39, 30, 16, 29, 11, 65, 29, 34, 33, 36, 42, 29, 29, 18, 17, 0, 9, 1, 78, 120, 7, 3, 65, 27, 10, 64, 77, 72, 70, 70, 71, 68, 3, 69, 71, 69, 64, 12, 7, 13, 65, 62, 62, 62, 58, 50, 46, 34, 20, 67, 69, 28, 21, 17, 9, 7, 2, 65, 66, 77, 77, 72, 70, 70, 71, 68, 3, 69, 71, 69, 64, 12, 7, 13, 65, 62, 62, 62, 58, 50, 46, 34, 20, 67 }, { 62, 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 40, 57, 60, 15, 2, 92, 41, 84, 96, 5, 5, 68, 3, 18, 48, 56, 50, 12, 100, 119, 111, 73, 84, 96, 5, 91, 84, 21, 7, 86, 99, 110, 2, 0, 85, 97, 0, 83, 102, 64, 76, 89, 100, 8, 78, 74, 94, 15, 3, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 28, 85, 122, 118, 86, 91, 77, 79, 86, 86, 119, 72, 98, 86, 117, 82, 77, 84, 79, 10, 1, 79, 117, 77, 101, 88, 119, 65, 78, 72, 91, 9, 68, 69, 94, 85, 85, 80, 71, 66, 70, 85, 66, 5, 67, 5, 70, 70, 5, 73, 20, 68, 1, 14, 17, 23, 23, 12, 78, 75, 80, 9, 67, 88, 67, 68, 73, 78, 77, 5, 36, 6, 86, 77, 67, 72, 30, 37, 94, 65, 67, 79, 0, 1, 67, 76, 33, 56, 41, 68, 70, 118, 22, 62, 62, 121, 23, 21, 28, 32, 30, 25, 20, 23, 28, 17, 24, 15, 8, 5, 2, 17, 17, 18, 14, 6, 10, 23, 12, 1, 13, 15, 69, 25, 65, 62, 62, 59, 47, 57, 62, 62, 62, 42, 31, 25, 24, 40, 36, 78, 24, 28, 13, 43, 24, 24, 30, 36, 36, 44, 41, 15, 93, 4, 3, 19, 121, 109, 76, 4, 10, 4, 81, 85, 90, 89, 119, 94, 10, 64, 68, 74, 79, 84, 98, 94, 117, 79, 17, 4, 64, 68, 71, 76, 80, 89, 103, 78, 0, 71, 6, 5, 74, 80, 80, 95, 77, 27, 5, 7, 5, 69, 73, 82, 80, 84, 62, 121, 120, 113, 120, 109, 111, 111, 110, 107, 111, 112, 114, 91, 98, 96, 102, 114, 90, 2, 84, 90, 88, 76, 71, 72, 65, 79, 65, 3, 67, 68, 64, 68, 77, 64, 68, 76, 7, 3, 65, 6, 16, 2, 70, 75, 81, 73, 65, 67, 6, 6, 69, 65, 72, 8, 8, 74, 79, 76, 76, 15, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 44, 62, 62, 62, 62, 62, 57, 41, 54, 53, 39, 20, 14, 6, 79, 62, 62, 62, 54, 40, 41, 32, 33, 40, 37, 28, 14, 26, 10, 67, 28, 33, 30, 34, 41, 27, 27, 17, 16, 64, 8, 0, 78, 119, 5, 2, 67, 25, 9, 65, 77, 72, 69, 69, 70, 68, 3, 68, 70, 68, 0, 12, 8, 14, 65, 62, 62, 60, 56, 48, 44, 31, 18, 69, 68, 28, 21, 17, 10, 7, 2, 64, 66, 76, 77, 72, 69, 69, 70, 68, 3, 68, 70, 68, 0, 12, 8, 14, 65, 62, 62, 60, 56, 48, 44, 31, 18, 69 }, { 62, 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 38, 56, 59, 16, 2, 90, 39, 83, 94, 5, 5, 67, 2, 17, 47, 54, 47, 10, 100, 117, 110, 73, 83, 94, 5, 91, 83, 21, 7, 85, 98, 109, 1, 64, 85, 97, 0, 83, 101, 64, 76, 89, 100, 8, 77, 74, 93, 16, 3, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 27, 85, 120, 115, 85, 90, 76, 78, 85, 85, 117, 71, 97, 85, 115, 83, 77, 84, 79, 10, 1, 78, 115, 77, 100, 87, 117, 65, 78, 72, 90, 9, 68, 68, 93, 84, 84, 80, 71, 65, 69, 84, 66, 5, 67, 5, 69, 70, 5, 73, 21, 68, 1, 15, 18, 23, 23, 12, 78, 75, 79, 9, 67, 88, 67, 67, 73, 77, 76, 6, 37, 7, 84, 77, 68, 71, 30, 37, 95, 65, 66, 79, 1, 1, 67, 74, 33, 56, 41, 67, 71, 116, 21, 62, 62, 120, 23, 21, 27, 31, 30, 25, 19, 23, 28, 16, 23, 15, 8, 5, 2, 17, 16, 17, 13, 6, 10, 22, 12, 0, 12, 15, 69, 24, 65, 62, 62, 58, 46, 55, 62, 62, 62, 41, 29, 23, 23, 38, 34, 79, 23, 27, 11, 42, 23, 23, 29, 35, 34, 42, 39, 14, 93, 3, 2, 17, 119, 107, 75, 4, 10, 4, 80, 84, 89, 88, 117, 93, 11, 0, 67, 73, 78, 83, 96, 93, 115, 78, 18, 5, 0, 67, 70, 75, 80, 88, 102, 77, 1, 70, 6, 5, 73, 80, 79, 94, 76, 27, 6, 7, 5, 68, 72, 81, 80, 83, 62, 120, 119, 112, 118, 108, 109, 110, 108, 105, 109, 110, 112, 90, 97, 95, 101, 111, 90, 1, 84, 89, 87, 76, 71, 72, 65, 78, 65, 2, 67, 68, 0, 68, 77, 64, 68, 76, 6, 3, 65, 5, 15, 2, 70, 75, 80, 73, 65, 66, 5, 6, 69, 65, 72, 8, 7, 74, 78, 76, 76, 14, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 42, 62, 62, 62, 62, 62, 55, 40, 52, 50, 37, 19, 13, 5, 79, 62, 62, 62, 52, 38, 39, 31, 31, 38, 35, 26, 12, 24, 8, 68, 27, 32, 28, 33, 39, 26, 25, 16, 15, 65, 7, 64, 78, 118, 4, 1, 68, 24, 8, 66, 77, 71, 69, 68, 69, 67, 4, 68, 69, 67, 1, 13, 9, 14, 64, 62, 62, 58, 54, 46, 42, 29, 16, 70, 68, 29, 22, 18, 11, 8, 3, 64, 66, 75, 77, 71, 69, 68, 69, 67, 4, 68, 69, 67, 1, 13, 9, 14, 64, 62, 62, 58, 54, 46, 42, 29, 16, 70 }, { 62, 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 37, 54, 58, 16, 3, 88, 38, 83, 93, 5, 4, 66, 1, 16, 46, 53, 43, 8, 101, 115, 108, 73, 83, 93, 5, 90, 82, 22, 7, 84, 97, 108, 64, 65, 85, 98, 0, 83, 101, 64, 77, 88, 100, 7, 77, 74, 92, 16, 3, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 27, 84, 119, 113, 84, 89, 74, 76, 84, 84, 115, 70, 96, 85, 113, 84, 77, 84, 79, 11, 1, 77, 113, 77, 99, 86, 115, 65, 78, 72, 90, 10, 69, 68, 93, 84, 83, 80, 70, 65, 69, 83, 65, 5, 67, 5, 69, 70, 5, 73, 21, 68, 1, 15, 18, 24, 24, 13, 79, 74, 78, 8, 67, 88, 67, 66, 73, 77, 75, 7, 37, 9, 83, 78, 69, 71, 30, 37, 95, 66, 66, 80, 1, 0, 66, 73, 33, 56, 42, 66, 72, 115, 20, 62, 62, 118, 23, 21, 27, 30, 29, 24, 19, 22, 27, 16, 23, 15, 7, 5, 1, 16, 15, 16, 13, 6, 10, 22, 11, 65, 12, 14, 69, 23, 66, 62, 62, 56, 44, 53, 62, 62, 62, 39, 27, 21, 21, 36, 32, 81, 22, 25, 9, 40, 22, 22, 28, 33, 32, 40, 37, 12, 93, 2, 1, 16, 118, 106, 73, 5, 10, 4, 79, 84, 89, 87, 116, 92, 12, 1, 66, 73, 77, 82, 95, 92, 113, 78, 18, 5, 0, 67, 69, 75, 79, 87, 101, 77, 3, 69, 6, 5, 73, 79, 79, 94, 76, 28, 6, 8, 5, 67, 72, 81, 79, 83, 62, 118, 117, 110, 116, 106, 108, 108, 107, 104, 107, 108, 110, 90, 96, 95, 101, 108, 90, 0, 84, 89, 86, 76, 71, 72, 65, 78, 65, 1, 67, 68, 0, 68, 77, 64, 68, 76, 6, 3, 65, 4, 14, 2, 70, 75, 79, 74, 65, 65, 4, 6, 69, 65, 71, 8, 7, 73, 77, 76, 76, 12, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 40, 62, 62, 62, 62, 62, 52, 38, 50, 48, 35, 18, 12, 4, 79, 62, 62, 62, 50, 36, 38, 29, 29, 36, 32, 24, 10, 22, 6, 69, 26, 30, 26, 31, 37, 24, 23, 14, 13, 66, 6, 65, 79, 117, 3, 64, 70, 23, 6, 67, 76, 71, 68, 68, 68, 66, 5, 68, 68, 66, 2, 13, 10, 15, 0, 62, 62, 56, 52, 44, 40, 27, 14, 71, 67, 29, 22, 19, 11, 9, 3, 0, 65, 75, 76, 71, 68, 68, 68, 66, 5, 68, 68, 66, 2, 13, 10, 15, 0, 62, 62, 56, 52, 44, 40, 27, 14, 71 }, { 62, 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 35, 53, 57, 17, 3, 87, 36, 83, 91, 4, 4, 65, 0, 15, 45, 51, 40, 5, 102, 113, 107, 73, 83, 91, 4, 90, 81, 22, 7, 84, 96, 106, 65, 66, 85, 98, 0, 82, 100, 65, 77, 88, 100, 7, 77, 74, 91, 17, 3, 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 26, 84, 118, 111, 83, 88, 73, 75, 83, 83, 113, 69, 95, 84, 110, 84, 78, 84, 80, 11, 1, 76, 110, 76, 99, 86, 113, 65, 78, 72, 90, 10, 69, 67, 92, 84, 82, 79, 70, 65, 68, 82, 65, 5, 68, 5, 69, 70, 5, 73, 21, 68, 1, 16, 18, 24, 24, 13, 79, 74, 78, 8, 67, 87, 67, 65, 72, 76, 73, 9, 38, 10, 81, 78, 70, 70, 31, 37, 96, 66, 65, 80, 2, 0, 66, 72, 33, 56, 42, 64, 73, 113, 19, 62, 62, 117, 23, 21, 26, 30, 29, 24, 18, 22, 27, 15, 22, 15, 7, 5, 1, 16, 15, 15, 12, 6, 10, 21, 11, 66, 11, 13, 69, 22, 66, 62, 62, 54, 43, 52, 62, 62, 62, 38, 25, 19, 20, 34, 30, 82, 21, 24, 8, 39, 21, 21, 26, 32, 30, 38, 36, 11, 93, 1, 0, 14, 116, 104, 72, 5, 10, 4, 78, 83, 88, 87, 114, 90, 13, 2, 66, 72, 75, 81, 93, 91, 110, 77, 19, 6, 1, 66, 68, 74, 79, 86, 100, 77, 4, 68, 6, 5, 72, 79, 79, 93, 75, 28, 7, 8, 5, 66, 71, 80, 79, 82, 62, 117, 116, 109, 115, 105, 106, 107, 105, 102, 105, 106, 107, 89, 95, 94, 100, 105, 89, 64, 83, 88, 85, 76, 71, 72, 65, 77, 66, 0, 67, 68, 1, 68, 77, 64, 68, 75, 5, 2, 65, 3, 13, 1, 70, 75, 78, 75, 66, 64, 4, 5, 69, 65, 71, 7, 6, 73, 77, 76, 76, 11, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 38, 62, 62, 62, 62, 62, 50, 36, 48, 45, 33, 17, 11, 3, 79, 62, 61, 62, 48, 34, 36, 28, 27, 34, 30, 22, 8, 20, 5, 71, 25, 29, 24, 30, 36, 23, 21, 13, 12, 67, 5, 66, 79, 116, 1, 65, 71, 21, 5, 68, 76, 70, 68, 67, 67, 65, 5, 67, 67, 65, 3, 14, 11, 15, 0, 62, 60, 54, 50, 42, 38, 24, 12, 72, 67, 30, 23, 19, 12, 10, 4, 0, 65, 74, 76, 70, 68, 67, 67, 65, 5, 67, 67, 65, 3, 14, 11, 15, 0, 62, 60, 54, 50, 42, 38, 24, 12, 72 }, { 62, 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 33, 51, 56, 17, 3, 85, 34, 83, 90, 4, 3, 64, 64, 13, 43, 49, 36, 3, 103, 111, 106, 73, 83, 90, 4, 90, 81, 22, 6, 83, 95, 105, 66, 68, 86, 99, 0, 82, 99, 65, 78, 88, 100, 7, 77, 74, 91, 17, 3, 22, 0, 0, 0, 78, 88, 97, 68, 16, 0, 26, 83, 117, 109, 82, 88, 71, 73, 83, 82, 111, 69, 94, 83, 108, 85, 78, 85, 80, 11, 0, 76, 108, 76, 98, 85, 112, 65, 78, 72, 90, 10, 69, 67, 91, 84, 82, 79, 70, 65, 68, 81, 64, 5, 68, 4, 69, 70, 4, 73, 21, 68, 1, 16, 18, 24, 24, 13, 80, 73, 77, 7, 67, 87, 67, 64, 72, 76, 72, 10, 39, 12, 79, 79, 71, 70, 31, 37, 97, 66, 65, 81, 2, 0, 66, 71, 33, 56, 42, 0, 74, 112, 18, 59, 62, 116, 22, 21, 26, 29, 28, 23, 18, 22, 26, 14, 21, 14, 7, 4, 0, 15, 14, 14, 12, 5, 9, 20, 10, 67, 10, 12, 69, 20, 67, 62, 62, 52, 41, 50, 60, 62, 62, 37, 23, 16, 18, 31, 28, 84, 19, 23, 6, 38, 20, 20, 25, 30, 28, 36, 34, 9, 93, 0, 64, 12, 115, 103, 71, 6, 10, 4, 78, 82, 87, 86, 112, 89, 13, 2, 65, 72, 74, 80, 92, 90, 108, 77, 19, 6, 1, 66, 68, 74, 78, 86, 99, 77, 5, 67, 6, 5, 71, 78, 79, 92, 74, 28, 8, 8, 5, 65, 71, 79, 78, 82, 62, 116, 114, 107, 113, 104, 105, 105, 104, 101, 104, 104, 105, 89, 94, 94, 99, 102, 89, 65, 83, 87, 85, 76, 71, 72, 66, 77, 66, 64, 67, 68, 1, 68, 77, 65, 68, 75, 5, 2, 66, 2, 12, 1, 71, 75, 77, 76, 66, 64, 3, 5, 69, 66, 71, 7, 6, 73, 76, 76, 76, 9, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 36, 62, 62, 62, 62, 61, 48, 34, 45, 43, 31, 15, 9, 2, 79, 61, 59, 62, 46, 31, 34, 26, 24, 32, 28, 20, 6, 17, 3, 72, 23, 28, 21, 28, 34, 21, 19, 11, 10, 68, 4, 67, 79, 115, 0, 67, 73, 20, 4, 69, 76, 70, 67, 67, 66, 65, 6, 67, 66, 65, 4, 14, 11, 16, 1, 61, 58, 52, 48, 40, 36, 22, 10, 74, 66, 30, 23, 20, 12, 10, 4, 1, 65, 74, 76, 70, 67, 67, 66, 65, 6, 67, 66, 65, 4, 14, 11, 16, 1, 61, 58, 52, 48, 40, 36, 22, 10, 74 }, { 62, 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 31, 49, 56, 18, 3, 83, 33, 82, 88, 4, 3, 0, 64, 12, 42, 47, 33, 1, 103, 109, 104, 72, 82, 88, 4, 89, 80, 23, 6, 82, 94, 104, 67, 69, 86, 99, 0, 82, 98, 65, 78, 88, 100, 7, 76, 73, 90, 17, 3, 22, 0, 0, 0, 77, 88, 97, 68, 15, 0, 26, 82, 115, 106, 81, 87, 69, 71, 82, 81, 109, 68, 92, 82, 106, 86, 78, 85, 80, 12, 0, 75, 106, 76, 97, 84, 110, 65, 77, 72, 89, 11, 69, 66, 90, 83, 81, 79, 70, 64, 67, 80, 0, 5, 68, 4, 68, 69, 4, 73, 22, 68, 1, 16, 19, 25, 24, 14, 80, 72, 76, 6, 67, 87, 67, 0, 72, 75, 71, 11, 40, 14, 77, 80, 72, 69, 31, 38, 98, 66, 65, 81, 3, 0, 66, 69, 33, 56, 42, 1, 75, 111, 17, 57, 62, 114, 22, 21, 26, 28, 28, 23, 18, 22, 26, 13, 20, 14, 7, 4, 0, 15, 13, 14, 12, 5, 9, 19, 9, 68, 10, 12, 69, 19, 67, 62, 62, 51, 40, 48, 58, 62, 62, 36, 21, 14, 17, 29, 27, 85, 18, 22, 4, 37, 19, 19, 24, 28, 27, 34, 32, 8, 93, 0, 65, 11, 113, 101, 69, 7, 10, 4, 77, 81, 86, 85, 110, 88, 14, 3, 64, 71, 73, 79, 91, 89, 106, 76, 20, 7, 2, 66, 67, 73, 77, 85, 97, 76, 7, 66, 7, 5, 70, 77, 78, 91, 73, 29, 9, 9, 6, 64, 70, 78, 77, 81, 62, 114, 112, 105, 111, 103, 104, 103, 102, 99, 102, 102, 103, 88, 93, 93, 98, 98, 89, 66, 83, 86, 84, 75, 71, 72, 66, 77, 66, 65, 67, 68, 2, 68, 77, 65, 68, 75, 5, 2, 66, 2, 11, 1, 71, 74, 75, 76, 66, 0, 2, 5, 69, 66, 70, 7, 6, 72, 75, 75, 75, 7, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 58, 34, 62, 62, 62, 62, 58, 46, 33, 43, 41, 30, 14, 8, 1, 79, 59, 57, 60, 44, 29, 32, 25, 22, 30, 26, 18, 4, 15, 1, 73, 22, 27, 19, 27, 32, 20, 17, 10, 9, 69, 3, 67, 79, 114, 64, 68, 75, 19, 3, 70, 76, 69, 66, 66, 64, 64, 7, 67, 65, 64, 5, 15, 12, 17, 2, 60, 57, 50, 46, 38, 34, 20, 8, 75, 65, 30, 24, 21, 13, 11, 5, 2, 64, 73, 76, 69, 66, 66, 64, 64, 7, 67, 65, 64, 5, 15, 12, 17, 2, 60, 57, 50, 46, 38, 34, 20, 8, 75 }, { 62, 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 29, 48, 55, 19, 3, 82, 31, 82, 87, 3, 3, 1, 65, 11, 41, 45, 30, 65, 104, 107, 103, 72, 82, 87, 3, 89, 79, 23, 6, 82, 93, 102, 68, 70, 86, 99, 0, 81, 97, 66, 78, 88, 100, 7, 76, 73, 89, 18, 3, 22, 0, 0, 0, 77, 88, 97, 67, 14, 0, 25, 82, 114, 104, 80, 86, 68, 70, 81, 80, 107, 67, 91, 81, 103, 86, 79, 85, 81, 12, 0, 74, 103, 75, 97, 84, 108, 65, 77, 72, 89, 11, 69, 66, 89, 83, 80, 78, 70, 64, 67, 79, 0, 5, 69, 4, 68, 69, 4, 73, 22, 68, 1, 17, 19, 25, 24, 14, 81, 72, 76, 6, 67, 86, 67, 1, 71, 75, 69, 13, 41, 15, 75, 80, 73, 69, 32, 38, 99, 66, 64, 82, 4, 0, 66, 68, 33, 56, 42, 3, 76, 109, 16, 54, 62, 113, 22, 21, 25, 28, 27, 23, 17, 22, 25, 12, 19, 14, 7, 4, 0, 14, 13, 13, 11, 5, 9, 18, 9, 69, 9, 11, 69, 18, 68, 60, 62, 49, 38, 47, 56, 62, 62, 35, 19, 12, 15, 27, 25, 86, 17, 21, 3, 36, 18, 18, 22, 27, 25, 32, 31, 6, 93, 64, 66, 9, 112, 100, 68, 7, 10, 4, 76, 80, 85, 85, 108, 86, 15, 4, 64, 70, 71, 78, 89, 88, 103, 75, 20, 7, 2, 65, 66, 72, 77, 84, 96, 76, 8, 65, 7, 5, 69, 77, 78, 90, 72, 29, 10, 9, 6, 0, 69, 77, 77, 80, 62, 113, 111, 104, 110, 102, 102, 102, 100, 98, 100, 100, 100, 87, 92, 92, 97, 95, 88, 67, 82, 85, 83, 75, 71, 72, 66, 76, 67, 66, 67, 68, 3, 68, 77, 65, 68, 74, 4, 1, 66, 1, 10, 0, 71, 74, 74, 77, 67, 1, 2, 4, 69, 66, 70, 6, 5, 72, 75, 75, 75, 6, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 56, 32, 62, 62, 62, 62, 55, 44, 31, 41, 38, 28, 13, 7, 0, 79, 57, 54, 57, 42, 27, 30, 23, 20, 28, 24, 16, 2, 13, 0, 75, 21, 26, 17, 25, 31, 18, 15, 9, 8, 70, 2, 68, 79, 113, 66, 69, 76, 17, 2, 71, 76, 69, 66, 65, 0, 0, 7, 66, 64, 0, 6, 15, 13, 17, 2, 60, 55, 48, 44, 36, 32, 17, 6, 76, 65, 31, 24, 21, 14, 12, 5, 2, 64, 72, 76, 69, 66, 65, 0, 0, 7, 66, 64, 0, 6, 15, 13, 17, 2, 60, 55, 48, 44, 36, 32, 17, 6, 76 }, { 62, 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 28, 46, 54, 19, 4, 80, 30, 82, 85, 3, 2, 2, 66, 10, 40, 44, 26, 67, 105, 105, 101, 72, 82, 85, 3, 88, 78, 24, 6, 81, 92, 101, 70, 71, 86, 100, 0, 81, 97, 66, 79, 87, 100, 6, 76, 73, 88, 18, 3, 22, 0, 0, 0, 76, 88, 97, 67, 14, 64, 25, 81, 113, 102, 79, 85, 66, 68, 80, 79, 105, 66, 90, 81, 101, 87, 79, 85, 81, 13, 0, 73, 101, 75, 96, 83, 106, 65, 77, 72, 89, 12, 70, 65, 89, 83, 79, 78, 69, 64, 66, 78, 1, 5, 69, 4, 68, 69, 4, 73, 22, 68, 1, 17, 19, 26, 25, 15, 81, 71, 75, 5, 67, 86, 67, 2, 71, 74, 68, 14, 41, 17, 74, 81, 74, 68, 32, 38, 99, 67, 64, 82, 4, 64, 65, 67, 33, 56, 43, 4, 77, 108, 15, 51, 62, 111, 22, 21, 25, 27, 27, 22, 17, 21, 25, 12, 19, 14, 6, 4, 64, 14, 12, 12, 11, 5, 9, 18, 8, 71, 9, 10, 69, 17, 68, 57, 62, 47, 37, 45, 54, 62, 61, 33, 17, 10, 14, 25, 23, 88, 16, 19, 1, 34, 17, 17, 21, 25, 23, 30, 29, 5, 93, 65, 67, 8, 110, 98, 66, 8, 10, 4, 75, 80, 85, 84, 107, 85, 16, 5, 0, 70, 70, 77, 88, 87, 101, 75, 21, 8, 3, 65, 65, 72, 76, 83, 95, 76, 10, 64, 7, 5, 69, 76, 78, 90, 72, 30, 10, 10, 6, 1, 69, 77, 76, 80, 62, 111, 109, 102, 108, 100, 101, 100, 99, 96, 98, 98, 98, 87, 91, 92, 97, 92, 88, 68, 82, 85, 82, 75, 71, 72, 66, 76, 67, 67, 67, 68, 3, 68, 77, 65, 68, 74, 4, 1, 66, 0, 9, 0, 71, 74, 73, 78, 67, 2, 1, 4, 69, 66, 69, 6, 5, 71, 74, 75, 75, 4, 62, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 53, 30, 62, 62, 62, 62, 53, 41, 29, 39, 36, 26, 12, 6, 64, 79, 55, 52, 55, 40, 25, 29, 22, 18, 26, 21, 14, 0, 11, 65, 76, 20, 24, 15, 24, 29, 17, 13, 7, 6, 71, 1, 69, 80, 112, 67, 71, 78, 16, 0, 72, 75, 68, 65, 65, 1, 1, 8, 66, 0, 1, 7, 16, 14, 18, 3, 59, 53, 46, 42, 34, 30, 15, 4, 77, 64, 31, 25, 22, 14, 13, 6, 3, 0, 72, 75, 68, 65, 65, 1, 1, 8, 66, 0, 1, 7, 16, 14, 18, 3, 59, 53, 46, 42, 34, 30, 15, 4, 77 }, { 62, 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 26, 45, 53, 20, 4, 78, 28, 82, 84, 3, 2, 3, 67, 8, 38, 42, 23, 69, 106, 103, 100, 72, 82, 84, 3, 88, 77, 24, 5, 80, 91, 100, 71, 73, 87, 100, 0, 81, 96, 66, 79, 87, 100, 6, 76, 73, 88, 19, 3, 22, 0, 0, 0, 76, 89, 97, 66, 13, 64, 24, 81, 112, 100, 78, 84, 65, 67, 80, 78, 103, 65, 89, 80, 99, 88, 79, 85, 81, 13, 64, 72, 99, 75, 95, 82, 104, 65, 77, 72, 89, 12, 70, 65, 88, 83, 79, 78, 69, 64, 66, 77, 1, 5, 69, 3, 68, 69, 4, 73, 22, 68, 1, 18, 19, 26, 25, 15, 82, 71, 74, 5, 67, 86, 67, 3, 71, 74, 67, 15, 42, 18, 72, 81, 75, 68, 32, 38, 100, 67, 0, 83, 5, 64, 65, 66, 33, 56, 43, 5, 78, 106, 14, 48, 60, 110, 21, 21, 24, 26, 26, 22, 16, 21, 24, 11, 18, 13, 6, 4, 64, 13, 11, 11, 10, 4, 8, 17, 8, 72, 8, 9, 69, 15, 69, 55, 62, 45, 35, 43, 52, 62, 58, 32, 15, 7, 12, 23, 21, 89, 14, 18, 64, 33, 16, 16, 20, 24, 21, 28, 27, 3, 93, 66, 68, 6, 109, 97, 65, 8, 10, 4, 74, 79, 84, 83, 105, 84, 17, 5, 1, 69, 69, 76, 86, 86, 99, 74, 21, 8, 3, 64, 64, 71, 76, 83, 94, 76, 11, 0, 7, 5, 68, 76, 78, 89, 71, 30, 11, 10, 6, 2, 68, 76, 76, 79, 62, 110, 108, 101, 106, 99, 99, 99, 97, 95, 97, 96, 96, 86, 90, 91, 96, 89, 88, 69, 82, 84, 82, 75, 71, 72, 67, 75, 67, 68, 67, 68, 4, 68, 77, 65, 68, 74, 3, 1, 66, 64, 8, 0, 71, 74, 72, 79, 67, 2, 0, 4, 69, 66, 69, 6, 4, 71, 73, 75, 75, 3, 62, 60, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 50, 28, 62, 62, 62, 62, 50, 39, 27, 37, 33, 24, 10, 5, 65, 79, 52, 50, 53, 38, 23, 27, 20, 16, 24, 19, 12, 65, 8, 67, 77, 19, 23, 12, 22, 27, 15, 11, 6, 5, 72, 0, 70, 80, 111, 68, 72, 79, 15, 64, 73, 75, 68, 65, 64, 2, 1, 9, 66, 1, 2, 8, 16, 15, 18, 4, 59, 51, 44, 40, 32, 28, 13, 2, 79, 64, 32, 25, 23, 15, 13, 6, 3, 0, 71, 75, 68, 65, 64, 2, 1, 9, 66, 1, 2, 8, 16, 15, 18, 4, 59, 51, 44, 40, 32, 28, 13, 2, 79 }, { 62, 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 24, 43, 52, 21, 4, 77, 26, 81, 82, 2, 2, 4, 68, 7, 37, 40, 20, 72, 106, 101, 99, 72, 81, 82, 2, 88, 76, 24, 5, 80, 90, 98, 72, 74, 87, 100, 0, 80, 95, 67, 79, 87, 100, 6, 75, 73, 87, 19, 3, 22, 0, 0, 0, 75, 89, 97, 66, 12, 64, 24, 80, 110, 97, 77, 83, 0, 65, 79, 77, 101, 64, 88, 79, 96, 88, 80, 85, 82, 13, 64, 71, 96, 74, 95, 82, 102, 65, 77, 72, 88, 12, 70, 64, 87, 82, 78, 77, 69, 0, 65, 76, 2, 5, 70, 3, 67, 69, 4, 73, 23, 68, 1, 18, 20, 26, 25, 15, 82, 70, 74, 4, 67, 85, 67, 4, 70, 73, 65, 17, 43, 20, 70, 82, 76, 67, 33, 38, 101, 67, 0, 83, 6, 64, 65, 64, 33, 56, 43, 7, 79, 105, 13, 46, 57, 109, 21, 21, 24, 26, 26, 22, 16, 21, 24, 10, 17, 13, 6, 4, 64, 13, 11, 10, 10, 4, 8, 16, 7, 73, 7, 9, 69, 14, 69, 53, 62, 44, 34, 42, 50, 62, 56, 31, 13, 5, 11, 21, 19, 90, 13, 17, 65, 32, 15, 15, 18, 22, 19, 26, 26, 2, 93, 67, 69, 4, 107, 95, 64, 9, 10, 4, 73, 78, 83, 83, 103, 82, 18, 6, 1, 68, 67, 75, 85, 85, 96, 73, 22, 9, 4, 64, 0, 70, 75, 82, 93, 75, 12, 1, 7, 5, 67, 75, 77, 88, 70, 30, 12, 10, 6, 3, 67, 75, 75, 78, 62, 109, 106, 99, 105, 98, 98, 97, 95, 93, 95, 94, 93, 85, 89, 90, 95, 86, 87, 70, 81, 83, 81, 75, 71, 72, 67, 75, 68, 69, 67, 68, 5, 68, 77, 65, 68, 73, 3, 0, 66, 65, 7, 64, 71, 74, 71, 79, 68, 3, 0, 3, 69, 66, 69, 5, 4, 71, 73, 75, 75, 1, 62, 59, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 48, 26, 62, 62, 62, 62, 47, 37, 26, 35, 31, 22, 9, 4, 66, 79, 50, 47, 50, 36, 21, 25, 19, 14, 22, 17, 10, 67, 6, 68, 79, 18, 22, 10, 21, 26, 14, 9, 5, 4, 73, 64, 71, 80, 110, 70, 73, 81, 13, 65, 74, 75, 67, 64, 0, 3, 2, 9, 65, 2, 3, 9, 17, 16, 19, 4, 58, 49, 42, 38, 30, 26, 10, 0, 80, 0, 32, 26, 23, 16, 14, 7, 4, 0, 70, 75, 67, 64, 0, 3, 2, 9, 65, 2, 3, 9, 17, 16, 19, 4, 58, 49, 42, 38, 30, 26, 10, 0, 80 }, { 61, 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 22, 42, 51, 21, 4, 75, 25, 81, 81, 2, 1, 5, 69, 6, 36, 38, 16, 74, 107, 99, 97, 72, 81, 81, 2, 87, 75, 25, 5, 79, 89, 97, 73, 75, 87, 101, 0, 80, 94, 67, 80, 87, 100, 6, 75, 73, 86, 20, 3, 22, 0, 0, 0, 75, 89, 97, 65, 11, 64, 23, 80, 109, 95, 76, 82, 1, 64, 78, 76, 99, 0, 87, 78, 94, 89, 80, 85, 82, 14, 64, 70, 94, 74, 94, 81, 100, 65, 77, 72, 88, 13, 70, 64, 86, 82, 77, 77, 69, 0, 65, 75, 2, 5, 70, 3, 67, 69, 4, 73, 23, 68, 1, 19, 20, 27, 25, 16, 83, 70, 73, 4, 67, 85, 67, 5, 70, 73, 64, 18, 44, 21, 68, 82, 77, 67, 33, 38, 102, 67, 1, 84, 6, 64, 65, 0, 33, 56, 43, 8, 80, 103, 12, 43, 54, 107, 21, 21, 23, 25, 25, 21, 15, 21, 23, 9, 16, 13, 6, 4, 65, 12, 10, 9, 9, 4, 8, 15, 7, 74, 7, 8, 69, 13, 70, 51, 60, 42, 32, 40, 48, 62, 53, 30, 11, 3, 9, 19, 17, 92, 12, 16, 67, 31, 14, 14, 17, 21, 17, 24, 24, 0, 93, 68, 70, 3, 106, 94, 1, 9, 10, 4, 72, 77, 82, 82, 101, 81, 19, 7, 2, 68, 66, 74, 83, 84, 94, 73, 22, 9, 4, 0, 1, 70, 75, 81, 92, 75, 14, 2, 7, 5, 66, 75, 77, 87, 69, 31, 13, 11, 6, 4, 67, 74, 75, 78, 62, 107, 105, 98, 103, 97, 96, 96, 94, 92, 93, 92, 91, 85, 88, 90, 94, 83, 87, 71, 81, 82, 80, 75, 71, 72, 67, 74, 68, 70, 67, 68, 5, 68, 77, 65, 68, 73, 2, 0, 66, 66, 6, 64, 71, 74, 70, 80, 68, 4, 64, 3, 69, 66, 68, 5, 3, 70, 72, 75, 75, 0, 62, 58, 61, 61, 61, 62, 62, 62, 61, 62, 62, 62, 57, 45, 24, 62, 60, 59, 60, 44, 35, 24, 33, 28, 20, 8, 3, 67, 79, 48, 45, 48, 34, 19, 23, 17, 12, 20, 15, 8, 69, 4, 70, 80, 17, 21, 8, 19, 24, 12, 7, 3, 2, 74, 65, 72, 80, 109, 71, 75, 82, 12, 66, 75, 75, 67, 64, 0, 4, 3, 10, 65, 3, 4, 10, 17, 17, 19, 5, 58, 47, 40, 36, 28, 24, 8, 65, 81, 0, 33, 26, 24, 16, 15, 7, 4, 1, 70, 75, 67, 64, 0, 4, 3, 10, 65, 3, 4, 10, 17, 17, 19, 5, 58, 47, 40, 36, 28, 24, 8, 65, 81 }, { 60, 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 20, 40, 50, 22, 4, 73, 23, 81, 79, 2, 1, 6, 70, 5, 35, 36, 13, 76, 108, 97, 96, 72, 81, 79, 2, 87, 74, 25, 5, 78, 88, 96, 74, 76, 87, 101, 0, 80, 93, 67, 80, 87, 100, 6, 75, 73, 85, 20, 3, 22, 0, 0, 0, 74, 89, 97, 65, 10, 64, 23, 79, 108, 93, 75, 81, 3, 1, 77, 75, 97, 1, 86, 77, 92, 90, 80, 85, 82, 14, 64, 69, 92, 74, 93, 80, 98, 65, 77, 72, 88, 13, 70, 0, 85, 82, 76, 77, 69, 0, 64, 74, 3, 5, 70, 3, 67, 69, 4, 73, 23, 68, 1, 19, 20, 27, 25, 16, 83, 69, 72, 3, 67, 85, 67, 6, 70, 72, 0, 19, 45, 23, 66, 83, 78, 66, 33, 38, 103, 67, 1, 84, 7, 64, 65, 1, 33, 56, 43, 9, 81, 102, 11, 40, 51, 106, 21, 21, 23, 24, 25, 21, 15, 21, 23, 8, 15, 13, 6, 4, 65, 12, 9, 8, 9, 4, 8, 14, 6, 75, 6, 7, 69, 12, 70, 49, 58, 40, 31, 38, 46, 59, 51, 29, 9, 1, 8, 17, 15, 93, 11, 15, 69, 30, 13, 13, 16, 19, 15, 22, 22, 64, 93, 69, 71, 1, 104, 92, 2, 10, 10, 4, 71, 76, 81, 81, 99, 80, 20, 8, 3, 67, 65, 73, 82, 83, 92, 72, 23, 10, 5, 0, 2, 69, 74, 80, 91, 75, 15, 3, 7, 5, 65, 74, 77, 86, 68, 31, 14, 11, 6, 5, 66, 73, 74, 77, 62, 106, 103, 96, 101, 96, 95, 94, 92, 90, 91, 90, 89, 84, 87, 89, 93, 80, 87, 72, 81, 81, 79, 75, 71, 72, 67, 74, 68, 71, 67, 68, 6, 68, 77, 65, 68, 73, 2, 0, 66, 67, 5, 64, 71, 74, 69, 81, 68, 5, 65, 3, 69, 66, 68, 5, 3, 70, 71, 75, 75, 65, 61, 57, 60, 59, 59, 62, 62, 62, 59, 60, 62, 61, 54, 42, 22, 61, 57, 55, 55, 41, 33, 22, 31, 26, 18, 7, 2, 68, 79, 46, 43, 46, 32, 17, 21, 16, 10, 18, 13, 6, 71, 2, 72, 81, 16, 20, 6, 18, 22, 11, 5, 2, 1, 75, 66, 73, 80, 108, 72, 76, 84, 11, 67, 76, 75, 66, 0, 1, 5, 4, 11, 65, 4, 5, 11, 18, 18, 20, 6, 57, 45, 38, 34, 26, 22, 6, 67, 82, 1, 33, 27, 25, 17, 16, 8, 5, 1, 69, 75, 66, 0, 1, 5, 4, 11, 65, 4, 5, 11, 18, 18, 20, 6, 57, 45, 38, 34, 26, 22, 6, 67, 82 }, { 58, 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 18, 38, 49, 22, 4, 72, 21, 81, 78, 1, 0, 7, 71, 3, 33, 34, 9, 79, 109, 95, 95, 72, 81, 78, 1, 87, 74, 25, 4, 78, 88, 95, 76, 78, 88, 102, 64, 80, 93, 68, 81, 87, 100, 5, 75, 73, 85, 20, 2, 22, 0, 0, 0, 74, 90, 97, 65, 9, 65, 22, 79, 107, 91, 74, 81, 4, 2, 77, 74, 96, 1, 85, 77, 90, 91, 81, 86, 83, 14, 65, 69, 90, 74, 93, 80, 97, 65, 77, 72, 88, 13, 71, 0, 85, 82, 76, 77, 69, 0, 64, 73, 3, 5, 71, 2, 67, 69, 3, 73, 23, 68, 1, 19, 20, 27, 25, 16, 84, 69, 72, 2, 67, 85, 68, 6, 70, 72, 1, 20, 45, 24, 65, 84, 80, 66, 33, 38, 104, 68, 1, 85, 7, 65, 65, 2, 33, 55, 43, 10, 82, 101, 9, 37, 47, 105, 20, 21, 22, 23, 24, 20, 14, 20, 22, 7, 14, 12, 5, 3, 66, 11, 8, 7, 8, 3, 7, 13, 5, 77, 5, 6, 69, 10, 71, 46, 55, 38, 29, 36, 43, 55, 48, 27, 7, 65, 6, 14, 13, 95, 9, 13, 71, 28, 12, 12, 14, 17, 13, 20, 20, 66, 93, 70, 72, 64, 103, 91, 3, 10, 10, 4, 71, 76, 81, 81, 98, 79, 20, 8, 3, 67, 64, 72, 81, 83, 90, 72, 23, 10, 5, 0, 2, 69, 74, 80, 90, 75, 16, 4, 7, 4, 65, 74, 77, 86, 68, 31, 14, 11, 6, 6, 66, 73, 74, 77, 62, 105, 102, 95, 100, 95, 94, 93, 91, 89, 90, 89, 87, 84, 87, 89, 93, 77, 87, 74, 81, 81, 79, 75, 71, 72, 68, 74, 69, 72, 68, 68, 6, 69, 77, 66, 68, 73, 1, 64, 67, 68, 4, 65, 72, 74, 68, 82, 69, 5, 66, 2, 69, 67, 68, 4, 2, 70, 71, 75, 75, 67, 59, 56, 58, 57, 56, 62, 62, 62, 56, 57, 62, 58, 50, 39, 20, 57, 53, 51, 49, 38, 30, 20, 28, 23, 16, 5, 0, 69, 79, 43, 40, 43, 30, 14, 19, 14, 7, 16, 10, 4, 74, 64, 74, 83, 14, 18, 3, 16, 20, 9, 3, 0, 64, 76, 67, 74, 81, 107, 74, 78, 86, 9, 69, 78, 75, 66, 0, 1, 6, 4, 11, 65, 5, 5, 12, 18, 18, 20, 6, 56, 43, 36, 31, 23, 20, 3, 69, 84, 1, 33, 27, 25, 17, 16, 8, 5, 1, 69, 75, 66, 0, 1, 6, 4, 11, 65, 5, 5, 12, 18, 18, 20, 6, 56, 43, 36, 31, 23, 20, 3, 69, 84 }, { 57, 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 17, 37, 49, 23, 5, 70, 20, 80, 76, 1, 0, 9, 71, 2, 32, 33, 6, 81, 109, 93, 93, 71, 80, 76, 1, 86, 73, 26, 4, 77, 87, 93, 77, 79, 88, 102, 64, 79, 92, 68, 81, 86, 99, 5, 74, 72, 84, 21, 2, 22, 0, 0, 0, 73, 90, 97, 64, 9, 65, 22, 78, 105, 88, 72, 80, 6, 4, 76, 72, 94, 2, 83, 76, 87, 91, 81, 86, 83, 15, 65, 68, 87, 73, 92, 79, 95, 65, 76, 72, 87, 14, 71, 1, 84, 81, 75, 76, 68, 1, 0, 72, 4, 6, 71, 2, 66, 68, 3, 72, 24, 67, 1, 20, 21, 28, 26, 17, 84, 68, 71, 2, 67, 84, 68, 7, 69, 71, 3, 22, 46, 26, 0, 84, 81, 65, 34, 39, 104, 68, 2, 85, 8, 65, 64, 4, 33, 55, 44, 12, 83, 99, 8, 35, 44, 103, 20, 21, 22, 23, 24, 20, 14, 20, 22, 7, 14, 12, 5, 3, 66, 11, 8, 7, 8, 3, 7, 13, 5, 78, 5, 6, 69, 9, 71, 44, 53, 37, 28, 35, 41, 52, 46, 26, 6, 67, 5, 12, 12, 96, 8, 12, 72, 27, 12, 12, 13, 16, 12, 19, 19, 67, 93, 70, 72, 65, 101, 89, 5, 11, 10, 4, 70, 75, 80, 80, 96, 77, 21, 9, 4, 66, 1, 71, 79, 82, 87, 71, 24, 11, 6, 1, 3, 68, 73, 79, 88, 74, 18, 5, 8, 4, 64, 73, 76, 85, 67, 32, 15, 12, 7, 7, 65, 72, 73, 76, 62, 103, 100, 93, 98, 93, 92, 91, 89, 87, 88, 87, 84, 83, 86, 88, 92, 73, 86, 75, 80, 80, 78, 74, 71, 71, 68, 73, 69, 72, 68, 68, 7, 69, 77, 66, 68, 72, 1, 64, 67, 68, 4, 65, 72, 73, 66, 82, 69, 6, 66, 2, 69, 67, 67, 4, 2, 69, 70, 74, 74, 68, 58, 55, 57, 56, 54, 60, 60, 59, 54, 55, 59, 56, 47, 37, 18, 54, 50, 48, 44, 36, 28, 19, 26, 21, 15, 4, 64, 69, 79, 41, 38, 41, 28, 12, 18, 13, 5, 15, 8, 3, 76, 66, 75, 84, 13, 17, 1, 15, 19, 8, 2, 64, 65, 77, 67, 74, 81, 106, 75, 79, 87, 8, 70, 79, 74, 65, 1, 2, 8, 5, 12, 64, 7, 6, 13, 19, 19, 21, 7, 56, 42, 35, 29, 21, 19, 1, 70, 85, 2, 34, 28, 26, 18, 17, 9, 6, 2, 68, 74, 65, 1, 2, 8, 5, 12, 64, 7, 6, 13, 19, 19, 21, 7, 56, 42, 35, 29, 21, 19, 1, 70, 85 }, { 56, 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 15, 35, 48, 24, 5, 68, 18, 80, 75, 1, 0, 10, 72, 1, 31, 31, 3, 83, 110, 91, 92, 71, 80, 75, 1, 86, 72, 26, 4, 76, 86, 92, 78, 80, 88, 102, 64, 79, 91, 68, 81, 86, 99, 5, 74, 72, 83, 21, 2, 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 22, 77, 104, 86, 71, 79, 8, 6, 75, 71, 92, 3, 82, 75, 85, 92, 81, 86, 83, 15, 65, 67, 85, 73, 91, 78, 93, 65, 76, 72, 87, 14, 71, 1, 83, 81, 74, 76, 68, 1, 0, 71, 5, 6, 71, 2, 66, 68, 3, 72, 24, 67, 1, 20, 21, 28, 26, 17, 85, 67, 70, 1, 67, 84, 68, 8, 69, 71, 4, 23, 47, 28, 2, 85, 82, 65, 34, 39, 105, 68, 2, 86, 9, 65, 64, 5, 33, 55, 44, 13, 84, 98, 7, 32, 41, 102, 20, 21, 22, 22, 23, 20, 14, 20, 21, 6, 13, 12, 5, 3, 66, 10, 7, 6, 8, 3, 7, 12, 4, 79, 4, 5, 69, 8, 72, 42, 51, 35, 26, 33, 39, 49, 44, 25, 4, 69, 3, 10, 10, 97, 7, 11, 74, 26, 11, 11, 12, 14, 10, 17, 17, 69, 93, 71, 73, 67, 100, 88, 6, 12, 10, 4, 69, 74, 79, 79, 94, 76, 22, 10, 5, 65, 2, 70, 78, 81, 85, 70, 24, 11, 6, 1, 4, 67, 72, 78, 87, 74, 19, 6, 8, 4, 0, 72, 76, 84, 66, 32, 16, 12, 7, 8, 64, 71, 72, 75, 62, 102, 98, 91, 96, 92, 91, 89, 87, 86, 86, 85, 82, 82, 85, 87, 91, 70, 86, 76, 80, 79, 77, 74, 71, 71, 68, 73, 69, 73, 68, 68, 8, 69, 77, 66, 68, 72, 1, 64, 67, 69, 3, 65, 72, 73, 65, 83, 69, 7, 67, 2, 69, 67, 67, 4, 2, 69, 69, 74, 74, 70, 57, 54, 56, 54, 52, 57, 57, 56, 52, 52, 56, 53, 44, 34, 16, 50, 46, 44, 39, 33, 26, 17, 24, 19, 13, 3, 65, 70, 79, 39, 36, 39, 26, 10, 16, 11, 3, 13, 6, 1, 78, 68, 77, 85, 12, 16, 64, 13, 17, 6, 0, 65, 66, 78, 68, 75, 81, 105, 76, 80, 89, 7, 71, 80, 74, 65, 2, 3, 9, 6, 13, 64, 8, 7, 14, 19, 20, 22, 8, 55, 40, 33, 27, 19, 17, 64, 72, 86, 3, 34, 28, 27, 19, 18, 9, 7, 2, 67, 74, 65, 2, 3, 9, 6, 13, 64, 8, 7, 14, 19, 20, 22, 8, 55, 40, 33, 27, 19, 17, 64, 72, 86 }, { 55, 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 13, 34, 47, 24, 5, 66, 17, 80, 73, 1, 64, 11, 73, 0, 30, 29, 64, 85, 111, 89, 90, 71, 80, 73, 1, 85, 71, 27, 4, 75, 85, 91, 79, 81, 88, 103, 64, 79, 90, 68, 82, 86, 99, 5, 74, 72, 82, 22, 2, 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 21, 77, 103, 84, 70, 78, 9, 7, 74, 70, 90, 4, 81, 74, 83, 93, 81, 86, 83, 16, 65, 66, 83, 73, 90, 77, 91, 65, 76, 72, 87, 15, 71, 2, 82, 81, 73, 76, 68, 1, 1, 70, 5, 6, 71, 2, 66, 68, 3, 72, 24, 67, 1, 21, 21, 29, 26, 18, 85, 67, 69, 1, 67, 84, 68, 9, 69, 70, 5, 24, 48, 29, 4, 85, 83, 64, 34, 39, 106, 68, 3, 86, 9, 65, 64, 6, 33, 55, 44, 14, 85, 96, 6, 29, 38, 100, 20, 21, 21, 21, 23, 19, 13, 20, 21, 5, 12, 12, 5, 3, 67, 10, 6, 5, 7, 3, 7, 11, 4, 80, 4, 4, 69, 7, 72, 40, 49, 33, 25, 31, 37, 46, 41, 24, 2, 71, 2, 8, 8, 99, 6, 10, 76, 25, 10, 10, 11, 13, 8, 15, 15, 70, 93, 72, 74, 68, 98, 86, 8, 12, 10, 4, 68, 73, 78, 78, 92, 75, 23, 11, 6, 65, 3, 69, 76, 80, 83, 70, 25, 12, 7, 2, 5, 67, 72, 77, 86, 74, 21, 7, 8, 4, 1, 72, 76, 83, 65, 33, 17, 13, 7, 9, 64, 70, 72, 75, 62, 100, 97, 90, 94, 91, 89, 88, 86, 84, 84, 83, 80, 82, 84, 87, 90, 67, 86, 77, 80, 78, 76, 74, 71, 71, 68, 72, 69, 74, 68, 68, 8, 69, 77, 66, 68, 72, 0, 64, 67, 70, 2, 65, 72, 73, 64, 84, 69, 8, 68, 2, 69, 67, 66, 4, 1, 68, 68, 74, 74, 71, 56, 53, 55, 52, 50, 55, 55, 53, 49, 49, 53, 50, 41, 31, 14, 46, 43, 40, 34, 30, 24, 15, 22, 16, 11, 2, 66, 71, 79, 37, 34, 37, 24, 8, 14, 10, 1, 11, 4, 64, 80, 70, 79, 86, 11, 15, 66, 12, 15, 5, 65, 67, 68, 79, 69, 76, 81, 104, 77, 82, 90, 6, 72, 81, 74, 64, 2, 3, 10, 7, 14, 64, 9, 8, 15, 20, 21, 22, 9, 55, 38, 31, 25, 17, 15, 66, 74, 87, 3, 35, 29, 28, 19, 19, 10, 7, 3, 67, 74, 64, 2, 3, 10, 7, 14, 64, 9, 8, 15, 20, 21, 22, 9, 55, 38, 31, 25, 17, 15, 66, 74, 87 }, { 53, 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 11, 32, 46, 25, 5, 65, 15, 80, 72, 0, 64, 12, 74, 65, 28, 27, 67, 88, 112, 87, 89, 71, 80, 72, 0, 85, 70, 27, 3, 75, 84, 89, 80, 83, 89, 103, 64, 78, 89, 69, 82, 86, 99, 5, 74, 72, 82, 22, 2, 22, 0, 0, 0, 71, 91, 97, 0, 6, 65, 21, 76, 102, 82, 69, 77, 11, 9, 74, 69, 88, 5, 80, 73, 80, 93, 82, 86, 84, 16, 66, 65, 80, 72, 90, 77, 89, 65, 76, 72, 87, 15, 71, 2, 81, 81, 73, 75, 68, 1, 1, 69, 6, 6, 72, 1, 66, 68, 3, 72, 24, 67, 1, 21, 21, 29, 26, 18, 86, 66, 69, 0, 67, 83, 68, 10, 68, 70, 7, 26, 49, 31, 6, 86, 84, 64, 35, 39, 107, 68, 3, 87, 10, 65, 64, 7, 33, 55, 44, 16, 86, 95, 5, 26, 35, 99, 19, 21, 21, 21, 22, 19, 13, 20, 20, 4, 11, 11, 5, 3, 67, 9, 6, 4, 7, 2, 6, 10, 3, 81, 3, 3, 69, 5, 73, 38, 47, 31, 23, 30, 35, 42, 39, 23, 0, 74, 0, 6, 6, 100, 4, 9, 77, 24, 9, 9, 9, 11, 6, 13, 14, 72, 93, 73, 75, 70, 97, 85, 9, 13, 10, 4, 67, 72, 77, 78, 90, 73, 24, 11, 6, 64, 5, 68, 75, 79, 80, 69, 25, 12, 7, 2, 6, 66, 71, 77, 85, 74, 22, 8, 8, 4, 2, 71, 76, 82, 64, 33, 18, 13, 7, 10, 0, 69, 71, 74, 62, 99, 95, 88, 93, 90, 88, 86, 84, 83, 83, 81, 77, 81, 83, 86, 89, 64, 85, 78, 79, 77, 76, 74, 71, 71, 69, 72, 70, 75, 68, 68, 9, 69, 77, 66, 68, 71, 0, 65, 67, 71, 1, 66, 72, 73, 0, 85, 70, 8, 68, 1, 69, 67, 66, 3, 1, 68, 68, 74, 74, 73, 55, 52, 54, 51, 47, 52, 52, 50, 47, 46, 49, 47, 37, 29, 12, 42, 39, 36, 29, 27, 22, 13, 20, 14, 9, 0, 67, 72, 79, 34, 31, 34, 22, 6, 12, 8, 64, 9, 2, 66, 82, 73, 80, 88, 10, 14, 69, 10, 14, 3, 67, 68, 69, 80, 70, 77, 81, 103, 79, 83, 92, 4, 73, 82, 74, 64, 3, 4, 11, 7, 14, 0, 10, 9, 16, 20, 22, 23, 9, 54, 36, 29, 23, 15, 13, 69, 76, 89, 4, 35, 29, 28, 20, 19, 10, 8, 3, 66, 74, 64, 3, 4, 11, 7, 14, 0, 10, 9, 16, 20, 22, 23, 9, 54, 36, 29, 23, 15, 13, 69, 76, 89 }, { 52, 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 9, 31, 45, 26, 5, 0, 13, 79, 70, 0, 64, 13, 75, 66, 27, 25, 70, 90, 112, 85, 88, 71, 79, 70, 0, 85, 69, 27, 3, 74, 83, 88, 81, 84, 89, 103, 64, 78, 88, 69, 82, 86, 99, 5, 73, 72, 81, 23, 2, 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 20, 76, 100, 79, 68, 76, 12, 10, 73, 68, 86, 6, 79, 72, 78, 94, 82, 86, 84, 16, 66, 64, 78, 72, 89, 76, 87, 65, 76, 72, 86, 15, 71, 3, 80, 80, 72, 75, 68, 2, 2, 68, 6, 6, 72, 1, 65, 68, 3, 72, 25, 67, 1, 22, 22, 29, 26, 18, 86, 66, 68, 0, 67, 83, 68, 11, 68, 69, 8, 27, 50, 32, 8, 86, 85, 0, 35, 39, 108, 68, 4, 87, 11, 65, 64, 9, 33, 55, 44, 17, 87, 93, 4, 24, 32, 98, 19, 21, 20, 20, 22, 19, 12, 20, 20, 3, 10, 11, 5, 3, 67, 9, 5, 3, 6, 2, 6, 9, 3, 82, 2, 3, 69, 4, 73, 36, 45, 30, 22, 28, 33, 39, 36, 22, 65, 76, 64, 4, 4, 101, 3, 8, 79, 23, 8, 8, 8, 10, 4, 11, 12, 73, 93, 74, 76, 72, 95, 83, 10, 13, 10, 4, 66, 71, 76, 77, 88, 72, 25, 12, 7, 0, 6, 67, 73, 78, 78, 68, 26, 13, 8, 3, 7, 65, 71, 76, 84, 73, 23, 9, 8, 4, 3, 71, 75, 81, 0, 33, 19, 13, 7, 11, 1, 68, 71, 73, 62, 98, 94, 87, 91, 89, 86, 85, 82, 81, 81, 79, 75, 80, 82, 85, 88, 2, 85, 79, 79, 76, 75, 74, 71, 71, 69, 71, 70, 76, 68, 68, 10, 69, 77, 66, 68, 71, 64, 65, 67, 72, 0, 66, 72, 73, 1, 85, 70, 9, 69, 1, 69, 67, 66, 3, 0, 68, 67, 74, 74, 74, 54, 51, 53, 49, 45, 50, 49, 47, 44, 43, 46, 44, 34, 26, 10, 38, 36, 32, 24, 24, 20, 12, 18, 11, 7, 64, 68, 73, 79, 32, 29, 32, 20, 4, 10, 7, 66, 7, 0, 68, 84, 75, 82, 89, 9, 13, 71, 9, 12, 2, 69, 69, 70, 81, 71, 78, 81, 102, 80, 84, 93, 3, 74, 83, 74, 0, 3, 5, 12, 8, 15, 0, 11, 10, 17, 21, 23, 23, 10, 54, 34, 27, 21, 13, 11, 71, 78, 90, 4, 36, 30, 29, 21, 20, 11, 8, 3, 65, 74, 0, 3, 5, 12, 8, 15, 0, 11, 10, 17, 21, 23, 23, 10, 54, 34, 27, 21, 13, 11, 71, 78, 90 }, { 51, 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 8, 29, 44, 26, 6, 2, 12, 79, 69, 0, 65, 14, 76, 67, 26, 24, 74, 92, 113, 83, 86, 71, 79, 69, 0, 84, 68, 28, 3, 73, 82, 87, 83, 85, 89, 104, 64, 78, 88, 69, 83, 85, 99, 4, 73, 72, 80, 23, 2, 22, 0, 0, 0, 70, 91, 97, 1, 5, 66, 20, 75, 99, 77, 67, 75, 14, 12, 72, 67, 84, 7, 78, 72, 76, 95, 82, 86, 84, 17, 66, 0, 76, 72, 88, 75, 85, 65, 76, 72, 86, 16, 72, 3, 80, 80, 71, 75, 67, 2, 2, 67, 7, 6, 72, 1, 65, 68, 3, 72, 25, 67, 1, 22, 22, 30, 27, 19, 87, 65, 67, 64, 67, 83, 68, 12, 68, 69, 9, 28, 50, 34, 9, 87, 86, 0, 35, 39, 108, 69, 4, 88, 11, 66, 0, 10, 33, 55, 45, 18, 88, 92, 3, 21, 29, 96, 19, 21, 20, 19, 21, 18, 12, 19, 19, 3, 10, 11, 4, 3, 68, 8, 4, 2, 6, 2, 6, 9, 2, 84, 2, 2, 69, 3, 74, 33, 43, 28, 20, 26, 31, 36, 34, 20, 67, 78, 66, 2, 2, 103, 2, 6, 81, 21, 7, 7, 7, 8, 2, 9, 10, 75, 93, 75, 77, 73, 94, 82, 12, 14, 10, 4, 65, 71, 76, 76, 87, 71, 26, 13, 8, 0, 7, 66, 72, 77, 76, 68, 26, 13, 8, 3, 8, 65, 70, 75, 83, 73, 25, 10, 8, 4, 3, 70, 75, 81, 0, 34, 19, 14, 7, 12, 1, 68, 70, 73, 62, 96, 92, 85, 89, 87, 85, 83, 81, 80, 79, 77, 73, 80, 81, 85, 88, 5, 85, 80, 79, 76, 74, 74, 71, 71, 69, 71, 70, 77, 68, 68, 10, 69, 77, 66, 68, 71, 64, 65, 67, 73, 64, 66, 72, 73, 2, 86, 70, 10, 70, 1, 69, 67, 65, 3, 0, 67, 66, 74, 74, 76, 53, 50, 52, 47, 43, 47, 47, 44, 42, 40, 43, 41, 31, 23, 8, 35, 32, 28, 19, 22, 17, 10, 16, 9, 5, 65, 69, 74, 79, 30, 27, 30, 18, 2, 9, 5, 68, 5, 66, 70, 86, 77, 84, 90, 8, 11, 73, 7, 10, 0, 71, 71, 72, 82, 72, 79, 82, 101, 81, 86, 95, 2, 76, 84, 73, 0, 4, 5, 13, 9, 16, 0, 12, 11, 18, 21, 24, 24, 11, 53, 32, 25, 19, 11, 9, 73, 80, 91, 5, 36, 30, 30, 21, 21, 11, 9, 4, 65, 73, 0, 4, 5, 13, 9, 16, 0, 12, 11, 18, 21, 24, 24, 11, 53, 32, 25, 19, 11, 9, 73, 80, 91 }, { 50, 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 6, 28, 43, 27, 6, 3, 10, 79, 67, 64, 65, 15, 77, 68, 25, 22, 77, 95, 114, 81, 85, 71, 79, 67, 64, 84, 67, 28, 3, 73, 81, 85, 84, 86, 89, 104, 64, 77, 87, 70, 83, 85, 99, 4, 73, 72, 79, 24, 2, 22, 0, 0, 0, 70, 91, 97, 2, 4, 66, 19, 75, 98, 75, 66, 74, 15, 13, 71, 66, 82, 8, 77, 71, 73, 95, 83, 86, 85, 17, 66, 1, 73, 71, 88, 75, 83, 65, 76, 72, 86, 16, 72, 4, 79, 80, 70, 74, 67, 2, 3, 66, 7, 6, 73, 1, 65, 68, 3, 72, 25, 67, 1, 23, 22, 30, 27, 19, 87, 65, 67, 64, 67, 82, 68, 13, 67, 68, 11, 30, 51, 35, 11, 87, 87, 1, 36, 39, 109, 69, 5, 88, 12, 66, 0, 11, 33, 55, 45, 20, 89, 90, 2, 18, 26, 95, 19, 21, 19, 19, 21, 18, 11, 19, 19, 2, 9, 11, 4, 3, 68, 8, 4, 1, 5, 2, 6, 8, 2, 85, 1, 1, 69, 2, 74, 31, 41, 26, 19, 25, 29, 33, 31, 19, 69, 80, 67, 0, 0, 104, 1, 5, 82, 20, 6, 6, 5, 7, 0, 7, 9, 76, 93, 76, 78, 75, 92, 80, 13, 14, 10, 4, 64, 70, 75, 76, 85, 69, 27, 14, 8, 1, 9, 65, 70, 76, 73, 67, 27, 14, 9, 4, 9, 64, 70, 74, 82, 73, 26, 11, 8, 4, 4, 70, 75, 80, 1, 34, 20, 14, 7, 13, 2, 67, 70, 72, 62, 95, 91, 84, 88, 86, 83, 82, 79, 78, 77, 75, 70, 79, 80, 84, 87, 8, 84, 81, 78, 75, 73, 74, 71, 71, 69, 70, 71, 78, 68, 68, 11, 69, 77, 66, 68, 70, 65, 66, 67, 74, 65, 67, 72, 73, 3, 87, 71, 11, 70, 0, 69, 67, 65, 2, 64, 67, 66, 74, 74, 77, 52, 49, 51, 46, 40, 45, 44, 41, 39, 37, 40, 38, 28, 21, 6, 31, 29, 24, 14, 19, 15, 8, 14, 6, 3, 66, 70, 75, 79, 28, 24, 27, 16, 0, 7, 4, 70, 3, 68, 72, 88, 79, 85, 92, 7, 10, 75, 6, 9, 64, 73, 72, 73, 83, 73, 80, 82, 100, 83, 87, 96, 0, 77, 85, 73, 1, 4, 6, 14, 10, 16, 1, 13, 12, 19, 22, 25, 24, 11, 53, 30, 23, 17, 9, 7, 76, 82, 92, 5, 37, 31, 30, 22, 22, 12, 9, 4, 64, 73, 1, 4, 6, 14, 10, 16, 1, 13, 12, 19, 22, 25, 24, 11, 53, 30, 23, 17, 9, 7, 76, 82, 92 }, { 48, 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 4, 26, 42, 27, 6, 5, 8, 79, 66, 64, 66, 16, 78, 70, 23, 20, 81, 97, 115, 79, 84, 71, 79, 66, 64, 84, 67, 28, 2, 72, 80, 84, 85, 88, 90, 105, 64, 77, 86, 70, 84, 85, 99, 4, 73, 72, 79, 24, 2, 22, 0, 0, 0, 69, 92, 97, 2, 3, 66, 19, 74, 97, 73, 65, 74, 17, 15, 71, 65, 80, 8, 76, 70, 71, 96, 83, 87, 85, 17, 67, 1, 71, 71, 87, 74, 82, 65, 76, 72, 86, 16, 72, 4, 78, 80, 70, 74, 67, 2, 3, 65, 8, 6, 73, 0, 65, 68, 2, 72, 25, 67, 1, 23, 22, 30, 27, 19, 88, 64, 66, 65, 67, 82, 68, 14, 67, 68, 12, 31, 52, 37, 13, 88, 88, 1, 36, 39, 110, 69, 5, 89, 12, 66, 0, 12, 33, 55, 45, 21, 90, 89, 1, 15, 22, 94, 18, 21, 19, 18, 20, 17, 11, 19, 18, 1, 8, 10, 4, 2, 69, 7, 3, 0, 5, 1, 5, 7, 1, 86, 0, 0, 69, 0, 75, 29, 39, 24, 17, 23, 26, 29, 29, 18, 71, 83, 69, 66, 65, 106, 64, 4, 84, 19, 5, 5, 4, 5, 65, 5, 7, 78, 93, 77, 79, 77, 91, 79, 14, 15, 10, 4, 64, 69, 74, 75, 83, 68, 27, 14, 9, 1, 10, 64, 69, 75, 71, 67, 27, 14, 9, 4, 9, 64, 69, 74, 81, 73, 27, 12, 8, 4, 5, 69, 75, 79, 2, 34, 21, 14, 7, 14, 2, 66, 69, 72, 62, 94, 89, 82, 86, 85, 82, 80, 78, 77, 76, 73, 68, 79, 79, 84, 86, 11, 84, 82, 78, 74, 73, 74, 71, 71, 70, 70, 71, 79, 68, 68, 11, 69, 77, 67, 68, 70, 65, 66, 68, 75, 66, 67, 73, 73, 4, 88, 71, 11, 71, 0, 69, 68, 65, 2, 64, 67, 65, 74, 74, 79, 51, 48, 50, 44, 38, 42, 41, 38, 37, 34, 36, 35, 24, 18, 4, 27, 25, 20, 9, 16, 13, 6, 11, 4, 1, 68, 72, 76, 79, 25, 22, 25, 14, 66, 5, 2, 73, 1, 70, 74, 90, 82, 87, 93, 5, 9, 78, 4, 7, 66, 75, 74, 75, 84, 74, 81, 82, 99, 84, 89, 98, 64, 78, 86, 73, 1, 5, 6, 15, 10, 17, 1, 14, 12, 20, 22, 25, 25, 12, 52, 28, 21, 15, 7, 5, 78, 84, 94, 6, 37, 31, 31, 22, 22, 12, 10, 4, 64, 73, 1, 5, 6, 15, 10, 17, 1, 14, 12, 20, 22, 25, 25, 12, 52, 28, 21, 15, 7, 5, 78, 84, 94 }, { 47, 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 2, 24, 42, 28, 6, 7, 7, 78, 64, 64, 66, 17, 78, 71, 22, 18, 84, 99, 115, 77, 82, 70, 78, 64, 64, 83, 66, 29, 2, 71, 79, 83, 86, 89, 90, 105, 64, 77, 85, 70, 84, 85, 99, 4, 72, 71, 78, 24, 2, 22, 0, 0, 0, 68, 92, 97, 2, 2, 66, 19, 73, 95, 70, 64, 73, 19, 17, 70, 64, 78, 9, 74, 69, 69, 97, 83, 87, 85, 18, 67, 2, 69, 71, 86, 73, 80, 65, 75, 72, 85, 17, 72, 5, 77, 79, 69, 74, 67, 3, 4, 64, 9, 6, 73, 0, 64, 67, 2, 72, 26, 67, 1, 23, 23, 31, 27, 20, 88, 0, 65, 66, 67, 82, 68, 15, 67, 67, 13, 32, 53, 39, 15, 89, 89, 2, 36, 40, 111, 69, 5, 89, 13, 66, 0, 14, 33, 55, 45, 22, 91, 88, 0, 13, 19, 92, 18, 21, 19, 17, 20, 17, 11, 19, 18, 0, 7, 10, 4, 2, 69, 7, 2, 0, 5, 1, 5, 6, 0, 87, 0, 0, 69, 64, 75, 27, 37, 23, 16, 21, 24, 26, 27, 17, 73, 85, 70, 68, 66, 107, 65, 3, 86, 18, 4, 4, 3, 3, 66, 3, 5, 79, 93, 77, 80, 78, 89, 77, 16, 16, 10, 4, 0, 68, 73, 74, 81, 67, 28, 15, 10, 2, 11, 0, 68, 74, 69, 66, 28, 15, 10, 4, 10, 0, 68, 73, 79, 72, 29, 13, 9, 4, 6, 68, 74, 78, 3, 35, 22, 15, 8, 15, 3, 65, 68, 71, 62, 92, 87, 80, 84, 84, 81, 78, 76, 75, 74, 71, 66, 78, 78, 83, 85, 15, 84, 83, 78, 73, 72, 73, 71, 71, 70, 70, 71, 80, 68, 68, 12, 69, 77, 67, 68, 70, 65, 66, 68, 75, 67, 67, 73, 72, 6, 88, 71, 12, 72, 0, 69, 68, 64, 2, 64, 66, 64, 73, 73, 81, 50, 47, 49, 42, 36, 39, 39, 35, 35, 32, 33, 33, 21, 15, 2, 23, 22, 17, 4, 13, 11, 5, 9, 2, 0, 69, 73, 77, 79, 23, 20, 23, 12, 68, 3, 1, 75, 64, 72, 76, 92, 84, 89, 94, 4, 8, 80, 3, 5, 67, 77, 75, 76, 85, 75, 81, 82, 98, 85, 90, 100, 65, 79, 87, 73, 2, 6, 7, 17, 11, 18, 1, 15, 13, 21, 23, 26, 26, 13, 51, 27, 19, 13, 5, 3, 80, 86, 95, 7, 37, 32, 32, 23, 23, 13, 11, 5, 0, 73, 2, 6, 7, 17, 11, 18, 1, 15, 13, 21, 23, 26, 26, 13, 51, 27, 19, 13, 5, 3, 80, 86, 95 }, { 46, 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 0, 23, 41, 29, 6, 8, 5, 78, 0, 65, 66, 18, 79, 72, 21, 16, 87, 102, 116, 75, 81, 70, 78, 0, 65, 83, 65, 29, 2, 71, 78, 81, 87, 90, 90, 105, 64, 76, 84, 71, 84, 85, 99, 4, 72, 71, 77, 25, 2, 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 18, 73, 94, 68, 0, 72, 20, 18, 69, 0, 76, 10, 73, 68, 66, 97, 84, 87, 86, 18, 67, 3, 66, 70, 86, 73, 78, 65, 75, 72, 85, 17, 72, 5, 76, 79, 68, 73, 67, 3, 4, 0, 9, 6, 74, 0, 64, 67, 2, 72, 26, 67, 1, 24, 23, 31, 27, 20, 89, 0, 65, 66, 67, 81, 68, 16, 66, 67, 15, 34, 54, 40, 17, 89, 90, 2, 37, 40, 112, 69, 6, 90, 14, 66, 0, 15, 33, 55, 45, 24, 92, 86, 64, 10, 16, 91, 18, 21, 18, 17, 19, 17, 10, 19, 17, 64, 6, 10, 4, 2, 69, 6, 2, 64, 4, 1, 5, 5, 0, 88, 64, 64, 69, 65, 76, 25, 35, 21, 14, 20, 22, 23, 24, 16, 75, 87, 72, 70, 68, 108, 66, 2, 87, 17, 3, 3, 1, 2, 68, 1, 4, 81, 93, 78, 81, 80, 88, 76, 17, 16, 10, 4, 1, 67, 72, 74, 79, 65, 29, 16, 10, 3, 13, 1, 66, 73, 66, 65, 28, 15, 10, 5, 11, 1, 68, 72, 78, 72, 30, 14, 9, 4, 7, 68, 74, 77, 4, 35, 23, 15, 8, 16, 4, 64, 68, 70, 62, 91, 86, 79, 83, 83, 79, 77, 74, 74, 72, 69, 0, 77, 77, 82, 84, 18, 83, 84, 77, 72, 71, 73, 71, 71, 70, 69, 72, 81, 68, 68, 13, 69, 77, 67, 68, 69, 66, 67, 68, 76, 68, 68, 73, 72, 7, 89, 72, 13, 72, 64, 69, 68, 64, 1, 65, 66, 64, 73, 73, 82, 49, 46, 48, 41, 33, 37, 36, 32, 32, 29, 30, 30, 18, 13, 0, 19, 18, 13, 64, 10, 9, 3, 7, 64, 65, 70, 74, 78, 79, 21, 17, 20, 10, 70, 1, 64, 77, 66, 74, 78, 94, 86, 90, 96, 3, 7, 82, 1, 4, 69, 79, 76, 77, 86, 76, 82, 82, 97, 87, 91, 101, 67, 80, 88, 73, 2, 6, 8, 18, 12, 18, 2, 16, 14, 22, 23, 27, 26, 13, 51, 25, 17, 11, 3, 1, 83, 88, 96, 7, 38, 32, 32, 24, 24, 13, 11, 5, 1, 73, 2, 6, 8, 18, 12, 18, 2, 16, 14, 22, 23, 27, 26, 13, 51, 25, 17, 11, 3, 1, 83, 88, 96 }, { 45, 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 64, 21, 40, 29, 7, 10, 4, 78, 2, 65, 67, 19, 80, 73, 20, 15, 91, 104, 117, 73, 79, 70, 78, 2, 65, 82, 64, 30, 2, 70, 77, 80, 89, 91, 90, 106, 64, 76, 84, 71, 85, 84, 99, 3, 72, 71, 76, 25, 2, 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 18, 72, 93, 66, 1, 71, 22, 20, 68, 1, 74, 11, 72, 68, 64, 98, 84, 87, 86, 19, 67, 4, 64, 70, 85, 72, 76, 65, 75, 72, 85, 18, 73, 6, 76, 79, 67, 73, 66, 3, 5, 1, 10, 6, 74, 0, 64, 67, 2, 72, 26, 67, 1, 24, 23, 32, 28, 21, 89, 1, 64, 67, 67, 81, 68, 17, 66, 66, 16, 35, 54, 42, 18, 90, 91, 3, 37, 40, 112, 70, 6, 90, 14, 67, 1, 16, 33, 55, 46, 25, 93, 85, 65, 7, 13, 89, 18, 21, 18, 16, 19, 16, 10, 18, 17, 64, 6, 10, 3, 2, 70, 6, 1, 65, 4, 1, 5, 5, 64, 90, 64, 65, 69, 66, 76, 22, 33, 19, 13, 18, 20, 20, 22, 14, 77, 89, 73, 72, 70, 110, 67, 0, 89, 15, 2, 2, 0, 0, 70, 64, 2, 82, 93, 79, 82, 81, 86, 74, 19, 17, 10, 4, 2, 67, 72, 73, 78, 64, 30, 17, 11, 3, 14, 2, 65, 72, 64, 65, 29, 16, 11, 5, 12, 1, 67, 71, 77, 72, 32, 15, 9, 4, 7, 67, 74, 77, 4, 36, 23, 16, 8, 17, 4, 64, 67, 70, 62, 89, 84, 77, 81, 81, 78, 75, 73, 72, 70, 67, 2, 77, 76, 82, 84, 21, 83, 85, 77, 72, 70, 73, 71, 71, 70, 69, 72, 82, 68, 68, 13, 69, 77, 67, 68, 69, 66, 67, 68, 77, 69, 68, 73, 72, 8, 90, 72, 14, 73, 64, 69, 68, 0, 1, 65, 65, 0, 73, 73, 84, 48, 45, 47, 39, 31, 34, 34, 29, 30, 26, 27, 27, 15, 10, 65, 16, 15, 9, 69, 8, 6, 1, 5, 66, 67, 71, 75, 79, 79, 19, 15, 18, 8, 72, 0, 65, 79, 68, 77, 80, 96, 88, 92, 97, 2, 5, 84, 0, 2, 70, 81, 78, 79, 87, 77, 83, 83, 96, 88, 93, 103, 68, 82, 89, 72, 3, 7, 8, 19, 13, 19, 2, 17, 15, 23, 24, 28, 27, 14, 50, 23, 15, 9, 1, 64, 85, 90, 97, 8, 38, 33, 33, 24, 25, 14, 12, 6, 1, 72, 3, 7, 8, 19, 13, 19, 2, 17, 15, 23, 24, 28, 27, 14, 50, 23, 15, 9, 1, 64, 85, 90, 97 }, { 43, 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 66, 20, 39, 30, 7, 12, 2, 78, 3, 65, 67, 20, 81, 75, 18, 13, 94, 106, 118, 71, 78, 70, 78, 3, 65, 82, 0, 30, 1, 69, 76, 79, 90, 93, 91, 106, 64, 76, 83, 71, 85, 84, 99, 3, 72, 71, 76, 26, 2, 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 17, 72, 92, 64, 2, 70, 23, 21, 68, 2, 72, 12, 71, 67, 1, 99, 84, 87, 86, 19, 68, 5, 1, 70, 84, 71, 74, 65, 75, 72, 85, 18, 73, 6, 75, 79, 67, 73, 66, 3, 5, 2, 10, 6, 74, 64, 64, 67, 2, 72, 26, 67, 1, 25, 23, 32, 28, 21, 90, 1, 0, 67, 67, 81, 68, 18, 66, 66, 17, 36, 55, 43, 20, 90, 92, 3, 37, 40, 113, 70, 7, 91, 15, 67, 1, 17, 33, 55, 46, 26, 94, 83, 66, 4, 10, 88, 17, 21, 17, 15, 18, 16, 9, 18, 16, 65, 5, 9, 3, 2, 70, 5, 0, 66, 3, 0, 4, 4, 64, 91, 65, 66, 69, 68, 77, 20, 31, 17, 11, 16, 18, 16, 19, 13, 79, 92, 75, 74, 72, 111, 69, 64, 91, 14, 1, 1, 64, 64, 72, 66, 0, 84, 93, 80, 83, 83, 85, 73, 20, 17, 10, 4, 3, 66, 71, 72, 76, 0, 31, 17, 12, 4, 15, 3, 0, 71, 1, 64, 29, 16, 11, 6, 13, 2, 67, 71, 76, 72, 33, 16, 9, 4, 8, 67, 74, 76, 5, 36, 24, 16, 8, 18, 5, 0, 67, 69, 62, 88, 83, 76, 79, 80, 76, 74, 71, 71, 69, 65, 4, 76, 75, 81, 83, 24, 83, 86, 77, 71, 70, 73, 71, 71, 71, 68, 72, 83, 68, 68, 14, 69, 77, 67, 68, 69, 67, 67, 68, 78, 70, 68, 73, 72, 9, 91, 72, 14, 74, 64, 69, 68, 0, 1, 66, 65, 1, 73, 73, 85, 47, 44, 46, 37, 29, 32, 31, 26, 27, 23, 23, 24, 11, 7, 67, 12, 11, 5, 74, 5, 4, 64, 3, 69, 69, 73, 76, 80, 79, 16, 13, 16, 6, 74, 65, 67, 81, 70, 79, 82, 98, 91, 94, 98, 1, 4, 87, 65, 0, 72, 83, 79, 80, 88, 78, 84, 83, 95, 89, 94, 104, 69, 83, 90, 72, 3, 7, 9, 20, 13, 20, 2, 18, 16, 24, 24, 29, 27, 15, 50, 21, 13, 7, 64, 66, 87, 92, 99, 8, 39, 33, 34, 25, 25, 14, 12, 6, 2, 72, 3, 7, 9, 20, 13, 20, 2, 18, 16, 24, 24, 29, 27, 15, 50, 21, 13, 7, 64, 66, 87, 92, 99 }, { 42, 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 68, 18, 38, 31, 7, 13, 0, 77, 5, 66, 67, 21, 82, 76, 17, 11, 97, 109, 118, 69, 77, 70, 77, 5, 66, 82, 1, 30, 1, 69, 75, 77, 91, 94, 91, 106, 64, 75, 82, 72, 85, 84, 99, 3, 71, 71, 75, 26, 2, 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 17, 71, 90, 2, 3, 69, 25, 23, 67, 3, 70, 13, 70, 66, 4, 99, 85, 87, 87, 19, 68, 6, 4, 69, 84, 71, 72, 65, 75, 72, 84, 18, 73, 7, 74, 78, 66, 72, 66, 4, 6, 3, 11, 6, 75, 64, 0, 67, 2, 72, 27, 67, 1, 25, 24, 32, 28, 21, 90, 2, 0, 68, 67, 80, 68, 19, 65, 65, 19, 38, 56, 45, 22, 91, 93, 4, 38, 40, 114, 70, 7, 91, 16, 67, 1, 19, 33, 55, 46, 28, 95, 82, 67, 2, 7, 87, 17, 21, 17, 15, 18, 16, 9, 18, 16, 66, 4, 9, 3, 2, 70, 5, 0, 67, 3, 0, 4, 3, 65, 92, 66, 66, 69, 69, 77, 18, 29, 16, 10, 15, 16, 13, 17, 12, 81, 94, 76, 76, 74, 112, 70, 65, 92, 13, 0, 0, 66, 66, 74, 68, 64, 85, 93, 81, 84, 85, 83, 71, 21, 18, 10, 4, 4, 65, 70, 72, 74, 2, 32, 18, 12, 5, 17, 4, 1, 70, 4, 0, 30, 17, 12, 6, 14, 3, 66, 70, 75, 71, 34, 17, 9, 4, 9, 66, 73, 75, 6, 36, 25, 16, 8, 19, 6, 1, 66, 68, 62, 87, 81, 74, 78, 79, 75, 72, 69, 69, 67, 0, 7, 75, 74, 80, 82, 27, 82, 87, 76, 70, 69, 73, 71, 71, 71, 68, 73, 84, 68, 68, 15, 69, 77, 67, 68, 68, 67, 68, 68, 79, 71, 69, 73, 72, 10, 91, 73, 15, 74, 65, 69, 68, 0, 0, 66, 65, 1, 73, 73, 87, 46, 43, 45, 36, 26, 29, 28, 23, 25, 20, 20, 21, 8, 5, 69, 8, 8, 1, 79, 2, 2, 65, 1, 71, 71, 74, 77, 81, 79, 14, 10, 13, 4, 76, 67, 68, 83, 72, 81, 84, 100, 93, 95, 100, 0, 3, 89, 66, 64, 73, 85, 80, 81, 89, 79, 85, 83, 94, 91, 95, 106, 71, 84, 91, 72, 4, 8, 10, 21, 14, 20, 3, 19, 17, 25, 25, 30, 28, 15, 49, 19, 11, 5, 66, 68, 90, 94, 100, 9, 39, 34, 34, 26, 26, 15, 13, 6, 3, 72, 4, 8, 10, 21, 14, 20, 3, 19, 17, 25, 25, 30, 28, 15, 49, 19, 11, 5, 66, 68, 90, 94, 100 }, { 41, 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 70, 17, 37, 31, 7, 15, 64, 77, 6, 66, 68, 22, 83, 77, 16, 9, 101, 111, 119, 67, 75, 70, 77, 6, 66, 81, 2, 31, 1, 68, 74, 76, 92, 95, 91, 107, 64, 75, 81, 72, 86, 84, 99, 3, 71, 71, 74, 27, 2, 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 16, 71, 89, 4, 4, 68, 26, 24, 66, 4, 68, 14, 69, 65, 6, 100, 85, 87, 87, 20, 68, 7, 6, 69, 83, 70, 70, 65, 75, 72, 84, 19, 73, 7, 73, 78, 65, 72, 66, 4, 6, 4, 11, 6, 75, 64, 0, 67, 2, 72, 27, 67, 1, 26, 24, 33, 28, 22, 91, 2, 1, 68, 67, 80, 68, 20, 65, 65, 20, 39, 57, 46, 24, 91, 94, 4, 38, 40, 115, 70, 8, 92, 16, 67, 1, 20, 33, 55, 46, 29, 96, 80, 68, 64, 4, 85, 17, 21, 16, 14, 17, 15, 8, 18, 15, 67, 3, 9, 3, 2, 71, 4, 64, 68, 2, 0, 4, 2, 65, 93, 66, 67, 69, 70, 78, 16, 27, 14, 8, 13, 14, 10, 14, 11, 83, 96, 78, 78, 76, 114, 71, 66, 94, 12, 64, 64, 67, 67, 76, 70, 66, 87, 93, 82, 85, 86, 82, 70, 23, 18, 10, 4, 5, 64, 69, 71, 72, 3, 33, 19, 13, 5, 18, 5, 3, 69, 6, 0, 30, 17, 12, 7, 15, 3, 66, 69, 74, 71, 36, 18, 9, 4, 10, 66, 73, 74, 7, 37, 26, 17, 8, 20, 6, 2, 66, 68, 62, 85, 80, 73, 76, 78, 73, 71, 68, 68, 65, 2, 9, 75, 73, 80, 81, 30, 82, 88, 76, 69, 68, 73, 71, 71, 71, 67, 73, 85, 68, 68, 15, 69, 77, 67, 68, 68, 68, 68, 68, 80, 72, 69, 73, 72, 11, 92, 73, 16, 75, 65, 69, 68, 1, 0, 67, 64, 2, 73, 73, 88, 45, 42, 44, 34, 24, 27, 26, 20, 22, 17, 17, 18, 5, 2, 71, 4, 4, 66, 84, 64, 0, 67, 64, 74, 73, 75, 78, 82, 79, 12, 8, 11, 2, 78, 69, 70, 85, 74, 83, 86, 102, 95, 97, 101, 64, 2, 91, 68, 66, 75, 87, 82, 83, 90, 80, 86, 83, 93, 92, 97, 107, 72, 85, 92, 72, 4, 8, 10, 22, 15, 21, 3, 20, 18, 26, 25, 31, 28, 16, 49, 17, 9, 3, 68, 70, 92, 96, 101, 9, 40, 34, 35, 26, 27, 15, 13, 7, 3, 72, 4, 8, 10, 22, 15, 21, 3, 20, 18, 26, 25, 31, 28, 16, 49, 17, 9, 3, 68, 70, 92, 96, 101 }, { 40, 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 72, 15, 36, 32, 7, 17, 66, 77, 8, 66, 68, 23, 84, 78, 15, 7, 104, 113, 120, 65, 74, 70, 77, 8, 66, 81, 3, 31, 1, 67, 73, 75, 93, 96, 91, 107, 64, 75, 80, 72, 86, 84, 99, 3, 71, 71, 73, 27, 2, 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 16, 70, 88, 6, 5, 67, 28, 26, 65, 5, 66, 15, 68, 64, 8, 101, 85, 87, 87, 20, 68, 8, 8, 69, 82, 69, 68, 65, 75, 72, 84, 19, 73, 8, 72, 78, 64, 72, 66, 4, 7, 5, 12, 6, 75, 64, 0, 67, 2, 72, 27, 67, 1, 26, 24, 33, 28, 22, 91, 3, 2, 69, 67, 80, 68, 21, 65, 64, 21, 40, 58, 48, 26, 92, 95, 5, 38, 40, 116, 70, 8, 92, 17, 67, 1, 21, 33, 55, 46, 30, 97, 79, 69, 67, 1, 84, 17, 21, 16, 13, 17, 15, 8, 18, 15, 68, 2, 9, 3, 2, 71, 4, 65, 69, 2, 0, 4, 1, 66, 94, 67, 68, 69, 71, 78, 14, 25, 12, 7, 11, 12, 7, 12, 10, 85, 98, 79, 80, 78, 115, 72, 67, 96, 11, 65, 65, 68, 69, 78, 72, 68, 88, 93, 83, 86, 88, 80, 68, 24, 19, 10, 4, 6, 0, 68, 70, 70, 4, 34, 20, 14, 6, 19, 6, 4, 68, 8, 1, 31, 18, 13, 7, 16, 4, 65, 68, 73, 71, 37, 19, 9, 4, 11, 65, 73, 73, 8, 37, 27, 17, 8, 21, 7, 3, 65, 67, 62, 84, 78, 71, 74, 77, 72, 69, 66, 66, 0, 4, 11, 74, 72, 79, 80, 33, 82, 89, 76, 68, 67, 73, 71, 71, 71, 67, 73, 86, 68, 68, 16, 69, 77, 67, 68, 68, 68, 68, 68, 81, 73, 69, 73, 72, 12, 93, 73, 17, 76, 65, 69, 68, 1, 0, 67, 64, 3, 73, 73, 90, 44, 41, 43, 32, 22, 24, 23, 17, 20, 14, 14, 15, 2, 64, 73, 0, 1, 70, 89, 67, 65, 69, 66, 76, 75, 76, 79, 83, 79, 10, 6, 9, 0, 80, 71, 71, 87, 76, 85, 88, 104, 97, 99, 102, 65, 1, 93, 69, 68, 76, 89, 83, 84, 91, 81, 87, 83, 92, 93, 98, 109, 73, 86, 93, 72, 5, 9, 11, 23, 16, 22, 3, 21, 19, 27, 26, 32, 29, 17, 48, 15, 7, 1, 70, 72, 94, 98, 102, 10, 40, 35, 36, 27, 28, 16, 14, 7, 4, 72, 5, 9, 11, 23, 16, 22, 3, 21, 19, 27, 26, 32, 29, 17, 48, 15, 7, 1, 70, 72, 94, 98, 102 }, { 38, 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 74, 13, 35, 32, 7, 18, 68, 77, 9, 67, 69, 24, 85, 80, 13, 5, 108, 116, 121, 0, 73, 70, 77, 9, 67, 81, 3, 31, 0, 67, 73, 74, 95, 98, 92, 108, 65, 75, 80, 73, 87, 84, 99, 2, 71, 71, 73, 27, 1, 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 15, 70, 87, 8, 6, 67, 29, 27, 65, 6, 65, 15, 67, 64, 10, 102, 86, 88, 88, 20, 69, 8, 10, 69, 82, 69, 67, 65, 75, 72, 84, 19, 74, 8, 72, 78, 64, 72, 66, 4, 7, 6, 12, 6, 76, 65, 0, 67, 1, 72, 27, 67, 1, 26, 24, 33, 28, 22, 92, 3, 2, 70, 67, 80, 69, 21, 65, 64, 22, 41, 58, 49, 27, 93, 97, 5, 38, 40, 117, 71, 8, 93, 17, 68, 1, 22, 33, 54, 46, 31, 98, 78, 71, 70, 66, 83, 16, 21, 15, 12, 16, 14, 7, 17, 14, 69, 1, 8, 2, 1, 72, 3, 66, 70, 1, 64, 3, 0, 67, 96, 68, 69, 69, 73, 79, 11, 22, 10, 5, 9, 9, 3, 9, 8, 87, 101, 81, 83, 80, 117, 74, 69, 98, 9, 66, 66, 70, 71, 80, 74, 70, 90, 93, 84, 87, 90, 79, 67, 25, 19, 10, 4, 6, 0, 68, 70, 69, 5, 34, 20, 14, 6, 20, 7, 5, 68, 10, 1, 31, 18, 13, 7, 16, 4, 65, 68, 72, 71, 38, 20, 9, 3, 11, 65, 73, 73, 8, 37, 27, 17, 8, 22, 7, 3, 65, 67, 62, 83, 77, 70, 73, 76, 71, 68, 65, 65, 1, 5, 13, 74, 72, 79, 80, 36, 82, 91, 76, 68, 67, 73, 71, 71, 72, 67, 74, 87, 69, 68, 16, 70, 77, 68, 68, 68, 69, 69, 69, 82, 74, 70, 74, 72, 13, 94, 74, 17, 77, 66, 69, 69, 1, 64, 68, 64, 3, 73, 73, 92, 42, 40, 41, 30, 19, 21, 20, 14, 17, 11, 10, 12, 65, 67, 75, 67, 66, 74, 95, 70, 68, 71, 69, 79, 77, 78, 81, 84, 79, 7, 3, 6, 65, 83, 73, 73, 90, 78, 88, 90, 107, 100, 101, 104, 67, 64, 96, 71, 70, 78, 91, 85, 86, 92, 82, 88, 84, 91, 95, 100, 111, 75, 88, 95, 72, 5, 9, 11, 24, 16, 22, 3, 22, 19, 28, 26, 32, 29, 17, 47, 13, 5, 65, 73, 74, 97, 100, 104, 10, 40, 35, 36, 27, 28, 16, 14, 7, 4, 72, 5, 9, 11, 24, 16, 22, 3, 22, 19, 28, 26, 32, 29, 17, 47, 13, 5, 65, 73, 74, 97, 100, 104 }, { 37, 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 75, 12, 35, 33, 8, 20, 69, 76, 11, 67, 69, 26, 85, 81, 12, 4, 111, 118, 121, 2, 71, 69, 76, 11, 67, 80, 4, 32, 0, 66, 72, 72, 96, 99, 92, 108, 65, 74, 79, 73, 87, 83, 98, 2, 70, 70, 72, 28, 1, 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 15, 69, 85, 11, 8, 66, 31, 29, 64, 8, 0, 16, 65, 0, 13, 102, 86, 88, 88, 21, 69, 9, 13, 68, 81, 68, 65, 65, 74, 72, 83, 20, 74, 9, 71, 77, 0, 71, 65, 5, 8, 7, 13, 7, 76, 65, 1, 66, 1, 71, 28, 66, 1, 27, 25, 34, 29, 23, 92, 4, 3, 70, 67, 79, 69, 22, 64, 0, 24, 43, 59, 51, 29, 93, 98, 6, 39, 41, 117, 71, 9, 93, 18, 68, 2, 24, 33, 54, 47, 33, 99, 76, 72, 72, 69, 81, 16, 21, 15, 12, 16, 14, 7, 17, 14, 69, 1, 8, 2, 1, 72, 3, 66, 70, 1, 64, 3, 0, 67, 97, 68, 69, 69, 74, 79, 9, 20, 9, 4, 8, 7, 0, 7, 7, 88, 103, 82, 85, 81, 118, 75, 70, 99, 8, 66, 66, 71, 72, 81, 75, 71, 91, 93, 84, 87, 91, 77, 65, 27, 20, 10, 4, 7, 1, 67, 69, 67, 7, 35, 21, 15, 7, 22, 8, 7, 67, 13, 2, 32, 19, 14, 8, 17, 5, 64, 67, 70, 70, 40, 21, 10, 3, 12, 64, 72, 72, 9, 38, 28, 18, 9, 23, 8, 4, 64, 66, 62, 81, 75, 68, 71, 74, 69, 66, 0, 0, 3, 7, 16, 73, 71, 78, 79, 40, 81, 92, 75, 67, 66, 72, 71, 70, 72, 66, 74, 87, 69, 68, 17, 70, 77, 68, 68, 67, 69, 69, 69, 82, 74, 70, 74, 71, 15, 94, 74, 18, 77, 66, 69, 69, 2, 64, 68, 0, 4, 72, 72, 93, 41, 39, 40, 29, 17, 19, 18, 11, 15, 9, 7, 10, 68, 69, 77, 70, 69, 77, 100, 72, 70, 72, 71, 81, 78, 79, 82, 84, 79, 5, 1, 4, 67, 85, 74, 74, 92, 79, 90, 91, 109, 102, 102, 105, 68, 65, 98, 72, 71, 79, 92, 86, 87, 93, 82, 88, 84, 90, 96, 101, 112, 76, 89, 96, 71, 6, 10, 12, 26, 17, 23, 4, 24, 20, 29, 27, 33, 30, 18, 47, 12, 4, 67, 75, 75, 99, 101, 105, 11, 41, 36, 37, 28, 29, 17, 15, 8, 5, 71, 6, 10, 12, 26, 17, 23, 4, 24, 20, 29, 27, 33, 30, 18, 47, 12, 4, 67, 75, 75, 99, 101, 105 }, { 36, 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 77, 10, 34, 34, 8, 22, 71, 76, 12, 67, 69, 27, 86, 82, 11, 2, 114, 120, 122, 4, 70, 69, 76, 12, 67, 80, 5, 32, 0, 65, 71, 71, 97, 100, 92, 108, 65, 74, 78, 73, 87, 83, 98, 2, 70, 70, 71, 28, 1, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 15, 68, 84, 13, 9, 65, 33, 31, 0, 9, 2, 17, 64, 1, 15, 103, 86, 88, 88, 21, 69, 10, 15, 68, 80, 67, 0, 65, 74, 72, 83, 20, 74, 9, 70, 77, 1, 71, 65, 5, 8, 8, 14, 7, 76, 65, 1, 66, 1, 71, 28, 66, 1, 27, 25, 34, 29, 23, 93, 5, 4, 71, 67, 79, 69, 23, 64, 0, 25, 44, 60, 53, 31, 94, 99, 6, 39, 41, 118, 71, 9, 94, 19, 68, 2, 25, 33, 54, 47, 34, 100, 75, 73, 75, 72, 80, 16, 21, 15, 11, 15, 14, 7, 17, 13, 70, 0, 8, 2, 1, 72, 2, 67, 71, 1, 64, 3, 64, 68, 98, 69, 70, 69, 75, 80, 7, 18, 7, 2, 6, 5, 66, 5, 6, 90, 105, 84, 87, 83, 119, 76, 71, 101, 7, 67, 67, 72, 74, 83, 77, 73, 93, 93, 85, 88, 93, 76, 64, 28, 21, 10, 4, 8, 2, 66, 68, 65, 8, 36, 22, 16, 8, 23, 9, 8, 66, 15, 3, 32, 19, 14, 8, 18, 6, 0, 66, 69, 70, 41, 22, 10, 3, 13, 0, 72, 71, 10, 38, 29, 18, 9, 24, 9, 5, 0, 65, 62, 80, 73, 66, 69, 73, 68, 64, 2, 1, 5, 9, 18, 72, 70, 77, 78, 43, 81, 93, 75, 66, 65, 72, 71, 70, 72, 66, 74, 88, 69, 68, 18, 70, 77, 68, 68, 67, 69, 69, 69, 83, 75, 70, 74, 71, 16, 95, 74, 19, 78, 66, 69, 69, 2, 64, 68, 0, 5, 72, 72, 95, 40, 38, 39, 27, 15, 16, 15, 8, 13, 6, 4, 7, 71, 72, 79, 74, 73, 81, 105, 75, 72, 74, 73, 83, 80, 80, 83, 85, 79, 3, 64, 2, 69, 87, 76, 76, 94, 81, 92, 93, 111, 104, 104, 106, 69, 66, 100, 74, 73, 81, 94, 87, 88, 94, 83, 89, 84, 89, 97, 102, 114, 77, 90, 97, 71, 6, 11, 13, 27, 18, 24, 4, 25, 21, 30, 27, 34, 31, 19, 46, 10, 2, 69, 77, 77, 101, 103, 106, 12, 41, 36, 38, 29, 30, 17, 16, 8, 6, 71, 6, 11, 13, 27, 18, 24, 4, 25, 21, 30, 27, 34, 31, 19, 46, 10, 2, 69, 77, 77, 101, 103, 106 }, { 35, 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 79, 9, 33, 34, 8, 24, 72, 76, 14, 67, 70, 28, 87, 83, 10, 0, 118, 122, 123, 6, 68, 69, 76, 14, 67, 79, 6, 33, 0, 64, 70, 70, 98, 101, 92, 109, 65, 74, 77, 73, 88, 83, 98, 2, 70, 70, 70, 29, 1, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 14, 68, 83, 15, 10, 64, 34, 32, 1, 10, 4, 18, 0, 2, 17, 104, 86, 88, 88, 22, 69, 11, 17, 68, 79, 66, 2, 65, 74, 72, 83, 21, 74, 10, 69, 77, 2, 71, 65, 5, 9, 9, 14, 7, 76, 65, 1, 66, 1, 71, 28, 66, 1, 28, 25, 35, 29, 24, 93, 5, 5, 71, 67, 79, 69, 24, 64, 1, 26, 45, 61, 54, 33, 94, 100, 7, 39, 41, 119, 71, 10, 94, 19, 68, 2, 26, 33, 54, 47, 35, 101, 73, 74, 78, 75, 78, 16, 21, 14, 10, 15, 13, 6, 17, 13, 71, 64, 8, 2, 1, 73, 2, 68, 72, 0, 64, 3, 65, 68, 99, 69, 71, 69, 76, 80, 5, 16, 5, 1, 4, 3, 69, 2, 5, 92, 107, 85, 89, 85, 121, 77, 72, 103, 6, 68, 68, 73, 75, 85, 79, 75, 94, 93, 86, 89, 94, 74, 1, 30, 21, 10, 4, 9, 3, 65, 67, 0, 9, 37, 23, 17, 8, 24, 10, 10, 65, 17, 3, 33, 20, 15, 9, 19, 6, 0, 65, 68, 70, 43, 23, 10, 3, 14, 0, 72, 70, 11, 39, 30, 19, 9, 25, 9, 6, 0, 65, 62, 78, 72, 65, 67, 72, 66, 0, 3, 3, 7, 11, 20, 72, 69, 77, 77, 46, 81, 94, 75, 65, 64, 72, 71, 70, 72, 65, 74, 89, 69, 68, 18, 70, 77, 68, 68, 67, 70, 69, 69, 84, 76, 70, 74, 71, 17, 96, 74, 20, 79, 66, 69, 69, 3, 64, 69, 1, 6, 72, 72, 96, 39, 37, 38, 25, 13, 14, 13, 5, 10, 3, 1, 4, 74, 75, 81, 78, 76, 85, 110, 78, 74, 76, 75, 86, 82, 81, 84, 86, 79, 1, 66, 0, 71, 89, 78, 77, 96, 83, 94, 95, 113, 106, 106, 107, 70, 67, 102, 75, 75, 82, 96, 89, 90, 95, 84, 90, 84, 88, 98, 104, 115, 78, 91, 98, 71, 7, 11, 13, 28, 19, 25, 4, 26, 22, 31, 28, 35, 31, 20, 46, 8, 0, 71, 79, 79, 103, 105, 107, 12, 42, 37, 39, 29, 31, 18, 16, 9, 6, 71, 7, 11, 13, 28, 19, 25, 4, 26, 22, 31, 28, 35, 31, 20, 46, 8, 0, 71, 79, 79, 103, 105, 107 }, { 33, 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 81, 7, 32, 35, 8, 25, 74, 76, 15, 68, 70, 29, 88, 85, 8, 65, 121, 125, 124, 8, 67, 69, 76, 15, 68, 79, 7, 33, 64, 64, 69, 68, 99, 103, 93, 109, 65, 73, 76, 74, 88, 83, 98, 2, 70, 70, 70, 29, 1, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 14, 67, 82, 17, 11, 0, 36, 34, 1, 11, 6, 19, 1, 3, 20, 104, 87, 88, 89, 22, 70, 12, 20, 67, 79, 66, 4, 65, 74, 72, 83, 21, 74, 10, 68, 77, 2, 70, 65, 5, 9, 10, 15, 7, 77, 66, 1, 66, 1, 71, 28, 66, 1, 28, 25, 35, 29, 24, 94, 6, 5, 72, 67, 78, 69, 25, 0, 1, 28, 47, 62, 56, 35, 95, 101, 7, 40, 41, 120, 71, 10, 95, 20, 68, 2, 27, 33, 54, 47, 37, 102, 72, 75, 81, 78, 77, 15, 21, 14, 10, 14, 13, 6, 17, 12, 72, 65, 7, 2, 1, 73, 1, 68, 73, 0, 65, 2, 66, 69, 100, 70, 72, 69, 78, 81, 3, 14, 3, 64, 3, 1, 73, 0, 4, 94, 110, 87, 91, 87, 122, 79, 73, 104, 5, 69, 69, 75, 77, 87, 81, 76, 96, 93, 87, 90, 96, 73, 2, 31, 22, 10, 4, 10, 4, 64, 67, 2, 11, 38, 23, 17, 9, 26, 11, 11, 64, 20, 4, 33, 20, 15, 9, 20, 7, 1, 65, 67, 70, 44, 24, 10, 3, 15, 1, 72, 69, 12, 39, 31, 19, 9, 26, 10, 7, 1, 64, 62, 77, 70, 0, 66, 71, 65, 2, 5, 4, 8, 13, 23, 71, 68, 76, 76, 49, 80, 95, 74, 64, 64, 72, 71, 70, 73, 65, 75, 90, 69, 68, 19, 70, 77, 68, 68, 66, 70, 70, 69, 85, 77, 71, 74, 71, 18, 97, 75, 20, 79, 67, 69, 69, 3, 65, 69, 1, 6, 72, 72, 98, 38, 36, 37, 24, 10, 11, 10, 2, 8, 0, 66, 1, 78, 77, 83, 82, 80, 89, 115, 81, 76, 78, 77, 88, 84, 83, 85, 87, 79, 65, 69, 66, 73, 91, 80, 79, 98, 85, 96, 97, 115, 109, 107, 109, 71, 68, 105, 77, 76, 84, 98, 90, 91, 96, 85, 91, 84, 87, 100, 105, 117, 80, 92, 99, 71, 7, 12, 14, 29, 19, 25, 5, 27, 23, 32, 28, 36, 32, 20, 45, 6, 65, 73, 81, 81, 106, 107, 109, 13, 42, 37, 39, 30, 31, 18, 17, 9, 7, 71, 7, 12, 14, 29, 19, 25, 5, 27, 23, 32, 28, 36, 32, 20, 45, 6, 65, 73, 81, 81, 106, 107, 109 }, { 32, 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 83, 6, 31, 36, 8, 27, 76, 75, 17, 68, 70, 30, 89, 86, 7, 67, 124, 126, 124, 10, 66, 69, 75, 17, 68, 79, 8, 33, 64, 0, 68, 67, 100, 104, 93, 109, 65, 73, 75, 74, 88, 83, 98, 2, 69, 70, 69, 30, 1, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 13, 67, 80, 20, 12, 1, 37, 35, 2, 12, 8, 20, 2, 4, 22, 105, 87, 88, 89, 22, 70, 13, 22, 67, 78, 65, 6, 65, 74, 72, 82, 21, 74, 11, 67, 76, 3, 70, 65, 6, 10, 11, 15, 7, 77, 66, 2, 66, 1, 71, 29, 66, 1, 29, 26, 35, 29, 24, 94, 6, 6, 72, 67, 78, 69, 26, 0, 2, 29, 48, 62, 57, 37, 95, 102, 8, 40, 41, 121, 71, 11, 95, 21, 68, 2, 29, 33, 54, 47, 38, 103, 70, 76, 83, 81, 76, 15, 21, 13, 9, 14, 13, 5, 17, 12, 73, 66, 7, 2, 1, 73, 1, 69, 74, 64, 65, 2, 67, 69, 101, 71, 72, 69, 79, 81, 1, 12, 2, 65, 1, 64, 76, 66, 3, 96, 112, 88, 93, 89, 123, 80, 74, 106, 4, 70, 70, 76, 78, 89, 83, 78, 97, 93, 88, 91, 98, 71, 4, 32, 22, 10, 4, 11, 5, 0, 66, 4, 12, 39, 24, 18, 10, 27, 12, 13, 0, 22, 5, 34, 21, 16, 10, 21, 8, 1, 64, 66, 69, 45, 25, 10, 3, 16, 1, 71, 68, 13, 39, 32, 19, 9, 27, 11, 8, 1, 0, 62, 76, 69, 1, 64, 70, 0, 3, 7, 6, 10, 15, 25, 70, 67, 75, 75, 52, 80, 96, 74, 0, 0, 72, 71, 70, 73, 64, 75, 91, 69, 68, 20, 70, 77, 68, 68, 66, 71, 70, 69, 86, 78, 71, 74, 71, 19, 97, 75, 21, 80, 67, 69, 69, 3, 65, 70, 1, 7, 72, 72, 99, 37, 35, 36, 22, 8, 9, 7, 64, 5, 66, 69, 65, 81, 80, 85, 86, 83, 93, 120, 84, 78, 79, 79, 91, 86, 84, 86, 88, 79, 67, 71, 68, 75, 93, 82, 80, 100, 87, 98, 99, 117, 111, 109, 110, 72, 69, 107, 78, 78, 85, 100, 91, 92, 97, 86, 92, 84, 86, 101, 106, 118, 81, 93, 100, 71, 8, 12, 15, 30, 20, 26, 5, 28, 24, 33, 29, 37, 32, 21, 45, 4, 67, 75, 83, 83, 108, 109, 110, 13, 43, 38, 40, 31, 32, 19, 17, 9, 8, 71, 8, 12, 15, 30, 20, 26, 5, 28, 24, 33, 29, 37, 32, 21, 45, 4, 67, 75, 83, 83, 108, 109, 110 }, { 31, 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 84, 4, 30, 36, 9, 29, 77, 75, 18, 68, 71, 31, 90, 87, 6, 68, 126, 126, 125, 12, 64, 69, 75, 18, 68, 78, 9, 34, 64, 1, 67, 66, 102, 105, 93, 110, 65, 73, 75, 74, 89, 82, 98, 1, 69, 70, 68, 30, 1, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 13, 66, 79, 22, 13, 2, 39, 37, 3, 13, 10, 21, 3, 4, 24, 106, 87, 88, 89, 23, 70, 14, 24, 67, 77, 64, 8, 65, 74, 72, 82, 22, 75, 11, 67, 76, 4, 70, 64, 6, 10, 12, 16, 7, 77, 66, 2, 66, 1, 71, 29, 66, 1, 29, 26, 36, 30, 25, 95, 7, 7, 73, 67, 78, 69, 27, 0, 2, 30, 49, 62, 59, 38, 96, 103, 8, 40, 41, 121, 72, 11, 96, 21, 69, 3, 30, 33, 54, 48, 39, 104, 69, 77, 86, 84, 74, 15, 21, 13, 8, 13, 12, 5, 16, 11, 73, 66, 7, 1, 1, 74, 0, 70, 75, 64, 65, 2, 67, 70, 103, 71, 73, 69, 80, 82, 65, 10, 0, 67, 64, 66, 79, 68, 1, 98, 114, 90, 95, 91, 125, 81, 76, 108, 2, 71, 71, 77, 80, 91, 85, 80, 99, 93, 89, 92, 99, 70, 5, 34, 23, 10, 4, 12, 5, 0, 65, 5, 13, 40, 25, 19, 10, 28, 13, 14, 1, 24, 5, 34, 21, 16, 10, 22, 8, 2, 0, 65, 69, 47, 26, 10, 3, 16, 2, 71, 68, 13, 40, 32, 20, 9, 28, 11, 8, 2, 0, 62, 74, 67, 3, 1, 68, 1, 5, 8, 7, 12, 17, 27, 70, 66, 75, 75, 55, 80, 97, 74, 0, 1, 72, 71, 70, 73, 64, 75, 92, 69, 68, 20, 70, 77, 68, 68, 66, 71, 70, 69, 87, 79, 71, 74, 71, 20, 98, 75, 22, 81, 67, 69, 69, 4, 65, 70, 2, 8, 72, 72, 101, 36, 34, 35, 20, 6, 6, 5, 67, 3, 69, 72, 68, 84, 83, 87, 89, 87, 97, 125, 86, 81, 81, 81, 93, 88, 85, 87, 89, 79, 69, 73, 70, 77, 95, 83, 82, 102, 89, 101, 101, 119, 113, 111, 111, 73, 71, 109, 80, 80, 87, 102, 93, 94, 98, 87, 93, 85, 85, 102, 108, 120, 82, 95, 101, 70, 8, 13, 15, 31, 21, 27, 5, 29, 25, 34, 29, 38, 33, 22, 44, 2, 69, 77, 85, 85, 110, 111, 111, 14, 43, 38, 41, 31, 33, 19, 18, 10, 8, 70, 8, 13, 15, 31, 21, 27, 5, 29, 25, 34, 29, 38, 33, 22, 44, 2, 69, 77, 85, 85, 110, 111, 111 }, { 30, 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 86, 3, 29, 37, 9, 30, 79, 75, 20, 69, 71, 32, 91, 88, 5, 70, 126, 126, 126, 14, 0, 69, 75, 20, 69, 78, 10, 34, 64, 1, 66, 64, 103, 106, 93, 110, 65, 72, 74, 75, 89, 82, 98, 1, 69, 70, 67, 31, 1, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 12, 66, 78, 24, 14, 3, 40, 38, 4, 14, 12, 22, 4, 5, 27, 106, 88, 88, 90, 23, 70, 15, 27, 66, 77, 64, 10, 65, 74, 72, 82, 22, 75, 12, 66, 76, 5, 69, 64, 6, 11, 13, 16, 7, 78, 66, 2, 66, 1, 71, 29, 66, 1, 30, 26, 36, 30, 25, 95, 7, 7, 73, 67, 77, 69, 28, 1, 3, 32, 51, 62, 60, 40, 96, 104, 9, 41, 41, 122, 72, 12, 96, 22, 69, 3, 31, 33, 54, 48, 41, 105, 67, 78, 89, 87, 73, 15, 21, 12, 8, 13, 12, 4, 16, 11, 74, 67, 7, 1, 1, 74, 0, 70, 76, 65, 65, 2, 68, 70, 104, 72, 74, 69, 81, 82, 67, 8, 65, 68, 65, 68, 82, 71, 0, 100, 116, 91, 97, 93, 126, 82, 77, 109, 1, 72, 72, 79, 81, 93, 87, 81, 100, 93, 90, 93, 101, 68, 7, 35, 23, 10, 4, 13, 6, 1, 65, 7, 15, 41, 26, 19, 11, 30, 14, 16, 2, 27, 6, 35, 22, 17, 11, 23, 9, 2, 1, 64, 69, 48, 27, 10, 3, 17, 2, 71, 67, 14, 40, 33, 20, 9, 29, 12, 9, 2, 1, 62, 73, 66, 4, 2, 67, 3, 6, 10, 9, 14, 19, 30, 69, 65, 74, 74, 58, 79, 98, 73, 1, 2, 72, 71, 70, 73, 0, 76, 93, 69, 68, 21, 70, 77, 68, 68, 65, 72, 71, 69, 88, 80, 72, 74, 71, 21, 99, 76, 23, 81, 68, 69, 69, 4, 66, 71, 2, 8, 72, 72, 102, 35, 33, 34, 19, 3, 4, 2, 70, 0, 72, 75, 71, 87, 85, 89, 93, 90, 101, 126, 89, 83, 83, 83, 96, 90, 86, 88, 90, 79, 71, 76, 73, 79, 97, 85, 83, 104, 91, 103, 103, 121, 115, 112, 113, 74, 72, 111, 81, 81, 88, 104, 94, 95, 99, 88, 94, 85, 84, 104, 109, 121, 84, 96, 102, 70, 9, 13, 16, 32, 22, 27, 6, 30, 26, 35, 30, 39, 33, 22, 44, 0, 71, 79, 87, 87, 113, 113, 112, 14, 44, 39, 41, 32, 34, 20, 18, 10, 9, 70, 9, 13, 16, 32, 22, 27, 6, 30, 26, 35, 30, 39, 33, 22, 44, 0, 71, 79, 87, 87, 113, 113, 112 }, { 28, 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 88, 1, 28, 37, 9, 32, 81, 75, 21, 69, 72, 33, 92, 90, 3, 72, 126, 126, 126, 16, 1, 69, 75, 21, 69, 78, 10, 34, 65, 2, 65, 0, 104, 108, 94, 111, 65, 72, 73, 75, 90, 82, 98, 1, 69, 70, 67, 31, 1, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 12, 65, 77, 26, 15, 3, 42, 40, 4, 15, 14, 22, 5, 6, 29, 107, 88, 89, 90, 23, 71, 15, 29, 66, 76, 0, 11, 65, 74, 72, 82, 22, 75, 12, 65, 76, 5, 69, 64, 6, 11, 14, 17, 7, 78, 67, 2, 66, 0, 71, 29, 66, 1, 30, 26, 36, 30, 25, 96, 8, 8, 74, 67, 77, 69, 29, 1, 3, 33, 52, 62, 62, 42, 97, 105, 9, 41, 41, 123, 72, 12, 97, 22, 69, 3, 32, 33, 54, 48, 42, 106, 66, 79, 92, 91, 72, 14, 21, 12, 7, 12, 11, 4, 16, 10, 75, 68, 6, 1, 0, 75, 64, 71, 77, 65, 66, 1, 69, 71, 105, 73, 75, 69, 83, 83, 69, 6, 67, 70, 67, 71, 86, 73, 64, 102, 119, 93, 100, 95, 126, 84, 78, 111, 0, 73, 73, 80, 83, 95, 89, 83, 102, 93, 91, 94, 103, 67, 8, 36, 24, 10, 4, 13, 7, 2, 64, 9, 16, 41, 26, 20, 11, 31, 15, 17, 3, 29, 6, 35, 22, 17, 11, 23, 9, 3, 1, 0, 69, 49, 28, 10, 3, 18, 3, 71, 66, 15, 40, 34, 20, 9, 30, 12, 10, 3, 1, 62, 72, 64, 6, 4, 66, 4, 8, 11, 10, 15, 21, 32, 69, 64, 74, 73, 61, 79, 99, 73, 2, 2, 72, 71, 70, 74, 0, 76, 94, 69, 68, 21, 70, 77, 69, 68, 65, 72, 71, 70, 89, 81, 72, 75, 71, 22, 100, 76, 23, 82, 68, 69, 70, 4, 66, 71, 2, 9, 72, 72, 104, 34, 32, 33, 17, 1, 1, 64, 73, 65, 75, 79, 74, 91, 88, 91, 97, 94, 105, 126, 92, 85, 85, 86, 98, 92, 88, 90, 91, 79, 74, 78, 75, 81, 100, 87, 85, 107, 93, 105, 105, 123, 118, 114, 114, 76, 73, 114, 83, 83, 90, 106, 96, 97, 100, 89, 95, 85, 83, 105, 111, 123, 85, 97, 103, 70, 9, 14, 16, 33, 22, 28, 6, 31, 26, 36, 30, 39, 34, 23, 43, 65, 73, 81, 89, 89, 115, 115, 114, 15, 44, 39, 42, 32, 34, 20, 19, 10, 9, 70, 9, 14, 16, 33, 22, 28, 6, 31, 26, 36, 30, 39, 34, 23, 43, 65, 73, 81, 89, 89, 115, 115, 114 }, { 27, 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 90, 64, 28, 38, 9, 34, 82, 74, 23, 69, 72, 34, 92, 91, 2, 74, 126, 126, 126, 18, 3, 68, 74, 23, 69, 77, 11, 35, 65, 3, 64, 1, 105, 109, 94, 111, 65, 72, 72, 75, 90, 82, 98, 1, 68, 69, 66, 31, 1, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 12, 64, 75, 29, 16, 4, 44, 42, 5, 16, 16, 23, 7, 7, 31, 108, 88, 89, 90, 24, 71, 16, 31, 66, 75, 1, 13, 65, 73, 72, 81, 23, 75, 13, 64, 75, 6, 69, 64, 7, 12, 15, 18, 7, 78, 67, 3, 65, 0, 71, 30, 66, 1, 30, 27, 37, 30, 26, 96, 9, 9, 75, 67, 77, 69, 30, 1, 4, 34, 53, 62, 62, 44, 98, 106, 10, 41, 42, 124, 72, 12, 97, 23, 69, 3, 34, 33, 54, 48, 43, 107, 65, 80, 94, 94, 70, 14, 21, 12, 6, 12, 11, 4, 16, 10, 76, 69, 6, 1, 0, 75, 64, 72, 77, 65, 66, 1, 70, 72, 106, 73, 75, 69, 84, 83, 71, 4, 68, 71, 69, 73, 89, 75, 65, 104, 121, 94, 102, 96, 126, 85, 79, 113, 64, 74, 74, 81, 85, 96, 91, 85, 103, 93, 91, 95, 104, 65, 10, 38, 25, 10, 4, 14, 8, 3, 0, 11, 17, 42, 27, 21, 12, 32, 16, 18, 4, 31, 7, 36, 23, 18, 11, 24, 10, 4, 2, 2, 68, 51, 29, 11, 3, 19, 4, 70, 65, 16, 41, 35, 21, 10, 31, 13, 11, 4, 2, 62, 70, 1, 8, 6, 65, 5, 10, 13, 12, 17, 23, 34, 68, 0, 73, 72, 62, 79, 100, 73, 3, 3, 71, 71, 70, 74, 0, 76, 95, 69, 68, 22, 70, 77, 69, 68, 65, 72, 71, 70, 89, 82, 72, 75, 70, 24, 100, 76, 24, 83, 68, 69, 70, 5, 66, 71, 3, 10, 71, 71, 106, 33, 31, 32, 15, 64, 65, 66, 76, 67, 77, 82, 76, 94, 91, 93, 101, 97, 108, 126, 95, 87, 86, 88, 100, 93, 89, 91, 92, 79, 76, 80, 77, 83, 102, 89, 86, 109, 95, 107, 107, 125, 120, 116, 115, 77, 74, 116, 84, 85, 91, 108, 97, 98, 101, 90, 95, 85, 82, 106, 112, 125, 86, 98, 104, 70, 10, 15, 17, 35, 23, 29, 6, 32, 27, 37, 31, 40, 35, 24, 42, 66, 75, 83, 91, 91, 117, 117, 115, 16, 44, 40, 43, 33, 35, 21, 20, 11, 10, 70, 10, 15, 17, 35, 23, 29, 6, 32, 27, 37, 31, 40, 35, 24, 42, 66, 75, 83, 91, 91, 117, 117, 115 }, { 26, 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 92, 65, 27, 39, 9, 35, 84, 74, 24, 70, 72, 35, 93, 92, 1, 76, 126, 126, 126, 20, 4, 68, 74, 24, 70, 77, 12, 35, 65, 3, 0, 3, 106, 110, 94, 111, 65, 71, 71, 76, 90, 82, 98, 1, 68, 69, 65, 32, 1, 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 11, 64, 74, 31, 17, 5, 45, 43, 6, 17, 18, 24, 8, 8, 34, 108, 89, 89, 91, 24, 71, 17, 34, 65, 75, 1, 15, 65, 73, 72, 81, 23, 75, 13, 0, 75, 7, 68, 64, 7, 12, 16, 18, 7, 79, 67, 3, 65, 0, 71, 30, 66, 1, 31, 27, 37, 30, 26, 97, 9, 9, 75, 67, 76, 69, 31, 2, 4, 36, 55, 62, 62, 46, 98, 107, 10, 42, 42, 125, 72, 13, 98, 24, 69, 3, 35, 33, 54, 48, 45, 108, 0, 81, 97, 97, 69, 14, 21, 11, 6, 11, 11, 3, 16, 9, 77, 70, 6, 1, 0, 75, 65, 72, 78, 66, 66, 1, 71, 72, 107, 74, 76, 69, 85, 84, 73, 2, 70, 73, 70, 75, 92, 78, 66, 106, 123, 96, 104, 98, 126, 86, 80, 114, 65, 75, 75, 83, 86, 98, 93, 86, 105, 93, 92, 96, 106, 64, 11, 39, 25, 10, 4, 15, 9, 4, 0, 13, 19, 43, 28, 21, 13, 34, 17, 20, 5, 34, 8, 36, 23, 18, 12, 25, 11, 4, 3, 3, 68, 52, 30, 11, 3, 20, 4, 70, 64, 17, 41, 36, 21, 10, 32, 14, 12, 4, 3, 62, 69, 2, 9, 7, 64, 7, 11, 15, 13, 19, 25, 37, 67, 1, 72, 71, 62, 78, 101, 72, 4, 4, 71, 71, 70, 74, 1, 77, 96, 69, 68, 23, 70, 77, 69, 68, 64, 73, 72, 70, 90, 83, 73, 75, 70, 25, 101, 77, 25, 83, 69, 69, 70, 5, 67, 72, 3, 10, 71, 71, 107, 32, 30, 31, 14, 67, 67, 69, 79, 70, 80, 85, 79, 97, 93, 95, 105, 101, 112, 126, 98, 89, 88, 90, 103, 95, 90, 92, 93, 79, 78, 83, 80, 85, 104, 91, 88, 111, 97, 109, 109, 126, 122, 117, 117, 78, 75, 118, 86, 86, 93, 110, 98, 99, 102, 91, 96, 85, 81, 108, 113, 126, 88, 99, 105, 70, 10, 15, 18, 36, 24, 29, 7, 33, 28, 38, 31, 41, 35, 24, 42, 68, 77, 85, 93, 93, 120, 119, 116, 16, 45, 40, 43, 34, 36, 21, 20, 11, 11, 70, 10, 15, 18, 36, 24, 29, 7, 33, 28, 38, 31, 41, 35, 24, 42, 68, 77, 85, 93, 93, 120, 119, 116 }, { 25, 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 93, 67, 26, 39, 10, 37, 85, 74, 26, 70, 73, 36, 94, 93, 0, 77, 126, 126, 126, 22, 6, 68, 74, 26, 70, 76, 13, 36, 65, 4, 1, 4, 108, 111, 94, 112, 65, 71, 71, 76, 91, 81, 98, 0, 68, 69, 64, 32, 1, 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 11, 0, 73, 33, 18, 6, 47, 45, 7, 18, 20, 25, 9, 8, 36, 109, 89, 89, 91, 25, 71, 18, 36, 65, 74, 2, 17, 65, 73, 72, 81, 24, 76, 14, 0, 75, 8, 68, 0, 7, 13, 17, 19, 7, 79, 67, 3, 65, 0, 71, 30, 66, 1, 31, 27, 38, 31, 27, 97, 10, 10, 76, 67, 76, 69, 32, 2, 5, 37, 56, 62, 62, 47, 99, 108, 11, 42, 42, 125, 73, 13, 98, 24, 70, 4, 36, 33, 54, 49, 46, 109, 1, 82, 100, 100, 67, 14, 21, 11, 5, 11, 10, 3, 15, 9, 77, 70, 6, 0, 0, 76, 65, 73, 79, 66, 66, 1, 71, 73, 109, 74, 77, 69, 86, 84, 76, 0, 72, 74, 72, 77, 95, 80, 68, 108, 125, 97, 106, 100, 126, 87, 82, 116, 67, 76, 76, 84, 88, 100, 95, 88, 106, 93, 93, 97, 107, 1, 13, 41, 26, 10, 4, 16, 9, 4, 1, 14, 20, 44, 29, 22, 13, 35, 18, 21, 6, 36, 8, 37, 24, 19, 12, 26, 11, 5, 4, 4, 68, 54, 31, 11, 3, 20, 5, 70, 64, 17, 42, 36, 22, 10, 33, 14, 12, 5, 3, 62, 67, 4, 11, 9, 1, 8, 13, 16, 15, 21, 27, 39, 67, 2, 72, 71, 62, 78, 102, 72, 4, 5, 71, 71, 70, 74, 1, 77, 97, 69, 68, 23, 70, 77, 69, 68, 64, 73, 72, 70, 91, 84, 73, 75, 70, 26, 102, 77, 26, 84, 69, 69, 70, 6, 67, 72, 4, 11, 71, 71, 109, 31, 29, 30, 12, 69, 70, 71, 82, 72, 83, 88, 82, 100, 96, 97, 108, 104, 116, 126, 100, 92, 90, 92, 105, 97, 91, 93, 94, 79, 80, 85, 82, 87, 106, 92, 89, 113, 99, 112, 111, 126, 124, 119, 118, 79, 77, 120, 87, 88, 94, 112, 100, 101, 103, 92, 97, 86, 80, 109, 115, 126, 89, 101, 106, 69, 11, 16, 18, 37, 25, 30, 7, 34, 29, 39, 32, 42, 36, 25, 41, 70, 79, 87, 95, 95, 122, 121, 117, 17, 45, 41, 44, 34, 37, 22, 21, 12, 11, 69, 11, 16, 18, 37, 25, 30, 7, 34, 29, 39, 32, 42, 36, 25, 41, 70, 79, 87, 95, 95, 122, 121, 117 }, { 23, 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 95, 68, 25, 40, 10, 39, 87, 74, 27, 70, 73, 37, 95, 95, 65, 79, 126, 126, 126, 24, 7, 68, 74, 27, 70, 76, 14, 36, 66, 5, 2, 5, 109, 113, 95, 112, 65, 71, 70, 76, 91, 81, 98, 0, 68, 69, 64, 33, 1, 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 10, 0, 72, 35, 19, 7, 48, 46, 7, 19, 22, 26, 10, 9, 38, 110, 89, 89, 91, 25, 72, 19, 38, 65, 73, 3, 19, 65, 73, 72, 81, 24, 76, 14, 1, 75, 8, 68, 0, 7, 13, 18, 19, 7, 79, 68, 3, 65, 0, 71, 30, 66, 1, 32, 27, 38, 31, 27, 98, 10, 11, 76, 67, 76, 69, 33, 2, 5, 38, 57, 62, 62, 49, 99, 109, 11, 42, 42, 126, 73, 14, 99, 25, 70, 4, 37, 33, 54, 49, 47, 110, 3, 83, 103, 103, 66, 13, 21, 10, 4, 10, 10, 2, 15, 8, 78, 71, 5, 0, 0, 76, 66, 74, 80, 67, 67, 0, 72, 73, 110, 75, 78, 69, 88, 85, 78, 65, 74, 76, 74, 79, 99, 83, 69, 110, 126, 99, 108, 102, 126, 89, 83, 118, 68, 77, 77, 85, 89, 102, 97, 90, 108, 93, 94, 98, 109, 2, 14, 42, 26, 10, 4, 17, 10, 5, 2, 16, 21, 45, 29, 23, 14, 36, 19, 23, 7, 38, 9, 37, 24, 19, 13, 27, 12, 5, 4, 5, 68, 55, 32, 11, 3, 21, 5, 70, 0, 18, 42, 37, 22, 10, 34, 15, 13, 5, 4, 62, 66, 5, 12, 11, 2, 10, 14, 18, 16, 22, 29, 41, 66, 3, 71, 70, 62, 78, 103, 72, 5, 5, 71, 71, 70, 75, 2, 77, 98, 69, 68, 24, 70, 77, 69, 68, 64, 74, 72, 70, 92, 85, 73, 75, 70, 27, 103, 77, 26, 85, 69, 69, 70, 6, 67, 73, 4, 12, 71, 71, 110, 30, 28, 29, 10, 71, 72, 74, 85, 75, 86, 92, 85, 104, 99, 99, 112, 108, 120, 126, 103, 94, 92, 94, 108, 99, 93, 94, 95, 79, 83, 87, 84, 89, 108, 94, 91, 115, 101, 114, 113, 126, 126, 121, 119, 80, 78, 123, 89, 90, 96, 114, 101, 102, 104, 93, 98, 86, 79, 110, 116, 126, 90, 102, 107, 69, 11, 16, 19, 38, 25, 31, 7, 35, 30, 40, 32, 43, 36, 26, 41, 72, 81, 89, 97, 97, 124, 123, 119, 17, 46, 41, 45, 35, 37, 22, 21, 12, 12, 69, 11, 16, 19, 38, 25, 31, 7, 35, 30, 40, 32, 43, 36, 26, 41, 72, 81, 89, 97, 97, 124, 123, 119 }, { 22, 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 70, 24, 41, 10, 40, 89, 73, 29, 71, 73, 38, 96, 96, 66, 81, 126, 126, 126, 26, 8, 68, 73, 29, 71, 76, 15, 36, 66, 5, 3, 7, 110, 114, 95, 112, 65, 70, 69, 77, 91, 81, 98, 0, 67, 69, 0, 33, 1, 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 10, 1, 70, 38, 20, 8, 50, 48, 8, 20, 24, 27, 11, 10, 41, 110, 90, 89, 92, 25, 72, 20, 41, 64, 73, 3, 21, 65, 73, 72, 80, 24, 76, 15, 2, 74, 9, 67, 0, 8, 14, 19, 20, 7, 80, 68, 4, 65, 0, 71, 31, 66, 1, 32, 28, 38, 31, 27, 98, 11, 11, 77, 67, 75, 69, 34, 3, 6, 40, 59, 62, 62, 51, 100, 110, 12, 43, 42, 126, 73, 14, 99, 26, 70, 4, 39, 33, 54, 49, 49, 111, 4, 84, 105, 106, 65, 13, 21, 10, 4, 10, 10, 2, 15, 8, 79, 72, 5, 0, 0, 76, 66, 74, 81, 67, 67, 0, 73, 74, 111, 76, 78, 69, 89, 85, 80, 67, 75, 77, 75, 81, 102, 85, 70, 112, 126, 100, 110, 104, 126, 90, 84, 119, 69, 78, 78, 87, 91, 104, 99, 91, 109, 93, 95, 99, 111, 4, 16, 43, 27, 10, 4, 18, 11, 6, 2, 18, 23, 46, 30, 23, 15, 38, 20, 24, 8, 41, 10, 38, 25, 20, 13, 28, 13, 6, 5, 6, 67, 56, 33, 11, 3, 22, 6, 69, 1, 19, 42, 38, 22, 10, 35, 16, 14, 6, 5, 62, 65, 7, 14, 12, 3, 11, 16, 20, 18, 24, 31, 44, 65, 4, 70, 69, 62, 77, 104, 71, 6, 6, 71, 71, 70, 75, 2, 78, 99, 69, 68, 25, 70, 77, 69, 68, 0, 74, 73, 70, 93, 86, 74, 75, 70, 28, 103, 78, 27, 85, 70, 69, 70, 6, 68, 73, 4, 12, 71, 71, 112, 29, 27, 28, 9, 74, 75, 77, 88, 77, 89, 95, 88, 107, 101, 101, 116, 111, 124, 126, 106, 96, 93, 96, 110, 101, 94, 95, 96, 79, 85, 90, 87, 91, 110, 96, 92, 117, 103, 116, 115, 126, 126, 122, 121, 81, 79, 125, 90, 91, 97, 116, 102, 103, 105, 94, 99, 86, 78, 112, 117, 126, 92, 103, 108, 69, 12, 17, 20, 39, 26, 31, 8, 36, 31, 41, 33, 44, 37, 26, 40, 74, 83, 91, 99, 99, 126, 125, 120, 18, 46, 42, 45, 36, 38, 23, 22, 12, 13, 69, 12, 17, 20, 39, 26, 31, 8, 36, 31, 41, 33, 44, 37, 26, 40, 74, 83, 91, 99, 99, 126, 125, 120 }, { 21, 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 71, 23, 41, 10, 42, 90, 73, 30, 71, 74, 39, 97, 97, 67, 83, 126, 126, 126, 28, 10, 68, 73, 30, 71, 75, 16, 37, 66, 6, 4, 8, 111, 115, 95, 113, 65, 70, 68, 77, 92, 81, 98, 0, 67, 69, 1, 34, 1, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 9, 1, 69, 40, 21, 9, 51, 49, 9, 21, 26, 28, 12, 11, 43, 111, 90, 89, 92, 26, 72, 21, 43, 64, 72, 4, 23, 65, 73, 72, 80, 25, 76, 15, 3, 74, 10, 67, 0, 8, 14, 20, 20, 7, 80, 68, 4, 65, 0, 71, 31, 66, 1, 33, 28, 39, 31, 28, 99, 11, 12, 77, 67, 75, 69, 35, 3, 6, 41, 60, 62, 62, 53, 100, 111, 12, 43, 42, 126, 73, 15, 100, 26, 70, 4, 40, 33, 54, 49, 50, 112, 6, 85, 108, 109, 0, 13, 21, 9, 3, 9, 9, 1, 15, 7, 80, 73, 5, 0, 0, 77, 67, 75, 82, 68, 67, 0, 74, 74, 112, 76, 79, 69, 90, 86, 82, 69, 77, 79, 77, 83, 105, 88, 71, 114, 126, 102, 112, 106, 126, 91, 85, 121, 70, 79, 79, 88, 92, 106, 101, 93, 111, 93, 96, 100, 112, 5, 17, 45, 27, 10, 4, 19, 12, 7, 3, 20, 24, 47, 31, 24, 15, 39, 21, 26, 9, 43, 10, 38, 25, 20, 14, 29, 13, 6, 6, 7, 67, 58, 34, 11, 3, 23, 6, 69, 2, 20, 43, 39, 23, 10, 36, 16, 15, 6, 5, 62, 0, 8, 15, 14, 4, 13, 17, 21, 19, 26, 33, 46, 65, 5, 70, 68, 62, 77, 105, 71, 7, 7, 71, 71, 70, 75, 3, 78, 100, 69, 68, 25, 70, 77, 69, 68, 0, 75, 73, 70, 94, 87, 74, 75, 70, 29, 104, 78, 28, 86, 70, 69, 70, 7, 68, 74, 5, 13, 71, 71, 113, 28, 26, 27, 7, 76, 77, 79, 91, 80, 92, 98, 91, 110, 104, 103, 120, 115, 126, 126, 109, 98, 95, 98, 113, 103, 95, 96, 97, 79, 87, 92, 89, 93, 112, 98, 94, 119, 105, 118, 117, 126, 126, 124, 122, 82, 80, 126, 92, 93, 99, 118, 104, 105, 106, 95, 100, 86, 77, 113, 119, 126, 93, 104, 109, 69, 12, 17, 20, 40, 27, 32, 8, 37, 32, 42, 33, 45, 37, 27, 40, 76, 85, 93, 101, 101, 126, 126, 121, 18, 47, 42, 46, 36, 39, 23, 22, 13, 13, 69, 12, 17, 20, 40, 27, 32, 8, 37, 32, 42, 33, 45, 37, 27, 40, 76, 85, 93, 101, 101, 126, 126, 121 }, { 20, 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 101, 73, 22, 42, 10, 44, 92, 73, 32, 71, 74, 40, 98, 98, 68, 85, 126, 126, 126, 30, 11, 68, 73, 32, 71, 75, 17, 37, 66, 7, 5, 9, 112, 116, 95, 113, 65, 70, 67, 77, 92, 81, 98, 0, 67, 69, 2, 34, 1, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 9, 2, 68, 42, 22, 10, 53, 51, 10, 22, 28, 29, 13, 12, 45, 112, 90, 89, 92, 26, 72, 22, 45, 64, 71, 5, 25, 65, 73, 72, 80, 25, 76, 16, 4, 74, 11, 67, 0, 8, 15, 21, 21, 7, 80, 68, 4, 65, 0, 71, 31, 66, 1, 33, 28, 39, 31, 28, 99, 12, 13, 78, 67, 75, 69, 36, 3, 7, 42, 61, 62, 62, 55, 101, 112, 13, 43, 42, 126, 73, 15, 100, 27, 70, 4, 41, 33, 54, 49, 51, 113, 7, 86, 111, 112, 1, 13, 21, 9, 2, 9, 9, 1, 15, 7, 81, 74, 5, 0, 0, 77, 67, 76, 83, 68, 67, 0, 75, 75, 113, 77, 80, 69, 91, 86, 84, 71, 79, 80, 79, 85, 108, 90, 72, 116, 126, 103, 114, 108, 126, 92, 86, 123, 71, 80, 80, 89, 94, 108, 103, 95, 112, 93, 97, 101, 114, 7, 19, 46, 28, 10, 4, 20, 13, 8, 4, 22, 25, 48, 32, 25, 16, 40, 22, 27, 10, 45, 11, 39, 26, 21, 14, 30, 14, 7, 7, 8, 67, 59, 35, 11, 3, 24, 7, 69, 3, 21, 43, 40, 23, 10, 37, 17, 16, 7, 6, 62, 1, 10, 17, 16, 5, 14, 19, 23, 21, 28, 35, 48, 64, 6, 69, 67, 62, 77, 106, 71, 8, 8, 71, 71, 70, 75, 3, 78, 101, 69, 68, 26, 70, 77, 69, 68, 0, 75, 73, 70, 95, 88, 74, 75, 70, 30, 105, 78, 29, 87, 70, 69, 70, 7, 68, 74, 5, 14, 71, 71, 115, 27, 25, 26, 5, 78, 80, 82, 94, 82, 95, 101, 94, 113, 107, 105, 124, 118, 126, 126, 112, 100, 97, 100, 115, 105, 96, 97, 98, 79, 89, 94, 91, 95, 114, 100, 95, 121, 107, 120, 119, 126, 126, 126, 123, 83, 81, 126, 93, 95, 100, 120, 105, 106, 107, 96, 101, 86, 76, 114, 120, 126, 94, 105, 110, 69, 13, 18, 21, 41, 28, 33, 8, 38, 33, 43, 34, 46, 38, 28, 39, 78, 87, 95, 103, 103, 126, 126, 122, 19, 47, 43, 47, 37, 40, 24, 23, 13, 14, 69, 13, 18, 21, 41, 28, 33, 8, 38, 33, 43, 34, 46, 38, 28, 39, 78, 87, 95, 103, 103, 126, 126, 122 }, { 18, 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 103, 75, 21, 42, 10, 45, 94, 73, 33, 72, 75, 41, 99, 100, 70, 87, 126, 126, 126, 32, 12, 68, 73, 33, 72, 75, 17, 37, 67, 7, 5, 10, 114, 118, 96, 114, 66, 70, 67, 78, 93, 81, 98, 64, 67, 69, 2, 34, 0, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 8, 2, 67, 44, 23, 10, 54, 52, 10, 23, 29, 29, 14, 12, 47, 113, 91, 90, 93, 26, 73, 22, 47, 64, 71, 5, 26, 65, 73, 72, 80, 25, 77, 16, 4, 74, 11, 67, 0, 8, 15, 22, 21, 7, 81, 69, 4, 65, 64, 71, 31, 66, 1, 33, 28, 39, 31, 28, 100, 12, 13, 79, 67, 75, 70, 36, 3, 7, 43, 62, 62, 62, 56, 102, 114, 13, 43, 42, 126, 74, 15, 101, 27, 71, 4, 42, 33, 53, 49, 52, 114, 8, 88, 114, 116, 2, 12, 21, 8, 1, 8, 8, 0, 14, 6, 82, 75, 4, 64, 64, 78, 68, 77, 84, 69, 68, 64, 76, 76, 115, 78, 81, 69, 93, 87, 87, 74, 81, 82, 81, 88, 112, 93, 74, 118, 126, 105, 117, 110, 126, 94, 88, 125, 73, 81, 81, 91, 96, 110, 105, 97, 114, 93, 98, 102, 116, 8, 20, 47, 28, 10, 4, 20, 13, 8, 4, 23, 26, 48, 32, 25, 16, 41, 23, 28, 10, 47, 11, 39, 26, 21, 14, 30, 14, 7, 7, 9, 67, 60, 36, 11, 2, 24, 7, 69, 3, 21, 43, 40, 23, 10, 38, 17, 16, 7, 6, 62, 2, 11, 18, 17, 6, 15, 20, 24, 22, 29, 36, 50, 64, 6, 69, 67, 62, 77, 108, 71, 8, 8, 71, 71, 70, 76, 3, 79, 102, 70, 68, 26, 71, 77, 70, 68, 0, 76, 74, 71, 96, 89, 75, 76, 70, 31, 106, 79, 29, 88, 71, 69, 71, 7, 69, 75, 5, 14, 71, 71, 117, 25, 24, 24, 3, 81, 83, 85, 97, 85, 98, 105, 97, 117, 110, 107, 126, 122, 126, 126, 115, 103, 99, 103, 118, 107, 98, 99, 99, 79, 92, 97, 94, 97, 117, 102, 97, 124, 109, 123, 121, 126, 126, 126, 125, 85, 83, 126, 95, 97, 102, 122, 107, 108, 108, 97, 102, 87, 75, 116, 122, 126, 96, 107, 112, 69, 13, 18, 21, 42, 28, 33, 8, 39, 33, 44, 34, 46, 38, 28, 38, 80, 89, 98, 106, 105, 126, 126, 124, 19, 47, 43, 47, 37, 40, 24, 23, 13, 14, 69, 13, 18, 21, 42, 28, 33, 8, 39, 33, 44, 34, 46, 38, 28, 38, 80, 89, 98, 106, 105, 126, 126, 124 }, { 17, 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 104, 76, 21, 43, 11, 47, 95, 72, 35, 72, 75, 43, 99, 101, 71, 88, 126, 126, 126, 34, 14, 67, 72, 35, 72, 74, 18, 38, 67, 8, 6, 12, 115, 119, 96, 114, 66, 69, 66, 78, 93, 80, 97, 64, 66, 68, 3, 35, 0, 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 8, 3, 65, 47, 25, 11, 56, 54, 11, 25, 31, 30, 16, 13, 50, 113, 91, 90, 93, 27, 73, 23, 50, 0, 70, 6, 28, 65, 72, 72, 79, 26, 77, 17, 5, 73, 12, 66, 1, 9, 16, 23, 22, 8, 81, 69, 5, 64, 64, 70, 32, 65, 1, 34, 29, 40, 32, 29, 100, 13, 14, 79, 67, 74, 70, 37, 4, 8, 45, 62, 62, 62, 58, 102, 115, 14, 44, 43, 126, 74, 16, 101, 28, 71, 5, 44, 33, 53, 50, 54, 115, 10, 89, 116, 119, 4, 12, 21, 8, 1, 8, 8, 0, 14, 6, 82, 75, 4, 64, 64, 78, 68, 77, 84, 69, 68, 64, 76, 76, 116, 78, 81, 69, 94, 87, 89, 76, 82, 83, 82, 90, 115, 95, 75, 119, 126, 106, 119, 111, 126, 95, 89, 126, 74, 81, 81, 92, 97, 111, 106, 98, 115, 93, 98, 102, 117, 10, 22, 49, 29, 10, 4, 21, 14, 9, 5, 25, 28, 49, 33, 26, 17, 43, 24, 30, 11, 50, 12, 40, 27, 22, 15, 31, 15, 8, 8, 11, 66, 62, 37, 12, 2, 25, 8, 68, 4, 22, 44, 41, 24, 11, 39, 18, 17, 8, 7, 62, 4, 13, 20, 19, 8, 17, 22, 26, 24, 31, 38, 53, 0, 7, 68, 66, 62, 76, 109, 70, 9, 9, 70, 71, 69, 76, 4, 79, 102, 70, 68, 27, 71, 77, 70, 68, 1, 76, 74, 71, 96, 89, 75, 76, 69, 33, 106, 79, 30, 88, 71, 69, 71, 8, 69, 75, 6, 15, 70, 70, 118, 24, 23, 23, 2, 83, 85, 87, 100, 87, 100, 108, 99, 120, 112, 109, 126, 125, 126, 126, 117, 105, 100, 105, 120, 108, 99, 100, 99, 79, 94, 99, 96, 99, 119, 103, 98, 126, 110, 125, 122, 126, 126, 126, 126, 86, 84, 126, 96, 98, 103, 123, 108, 109, 109, 97, 102, 87, 74, 117, 123, 126, 97, 108, 113, 68, 14, 19, 22, 44, 29, 34, 9, 41, 34, 45, 35, 47, 39, 29, 38, 81, 90, 100, 108, 106, 126, 126, 125, 20, 48, 44, 48, 38, 41, 25, 24, 14, 15, 68, 14, 19, 22, 44, 29, 34, 9, 41, 34, 45, 35, 47, 39, 29, 38, 81, 90, 100, 108, 106, 126, 126, 125 }, { 16, 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 106, 78, 20, 44, 11, 49, 97, 72, 36, 72, 75, 44, 100, 102, 72, 90, 126, 126, 126, 36, 15, 67, 72, 36, 72, 74, 19, 38, 67, 9, 7, 13, 116, 120, 96, 114, 66, 69, 65, 78, 93, 80, 97, 64, 66, 68, 4, 35, 0, 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 8, 4, 64, 49, 26, 12, 58, 56, 12, 26, 33, 31, 17, 14, 52, 114, 91, 90, 93, 27, 73, 24, 52, 0, 69, 7, 30, 65, 72, 72, 79, 26, 77, 17, 6, 73, 13, 66, 1, 9, 16, 24, 23, 8, 81, 69, 5, 64, 64, 70, 32, 65, 1, 34, 29, 40, 32, 29, 101, 14, 15, 80, 67, 74, 70, 38, 4, 8, 46, 62, 62, 62, 60, 103, 116, 14, 44, 43, 126, 74, 16, 102, 29, 71, 5, 45, 33, 53, 50, 55, 116, 11, 90, 119, 122, 5, 12, 21, 8, 0, 7, 8, 0, 14, 5, 83, 76, 4, 64, 64, 78, 69, 78, 85, 69, 68, 64, 77, 77, 117, 79, 82, 69, 95, 88, 91, 78, 84, 85, 84, 92, 118, 97, 76, 121, 126, 108, 121, 113, 126, 96, 90, 126, 75, 82, 82, 93, 99, 113, 108, 100, 117, 93, 99, 103, 119, 11, 23, 50, 30, 10, 4, 22, 15, 10, 6, 27, 29, 50, 34, 27, 18, 44, 25, 31, 12, 52, 13, 40, 27, 22, 15, 32, 16, 9, 9, 12, 66, 62, 38, 12, 2, 26, 9, 68, 5, 23, 44, 42, 24, 11, 40, 19, 18, 9, 8, 62, 5, 15, 22, 21, 9, 18, 24, 28, 25, 33, 40, 55, 1, 8, 67, 65, 62, 76, 110, 70, 10, 10, 70, 71, 69, 76, 4, 79, 103, 70, 68, 28, 71, 77, 70, 68, 1, 76, 74, 71, 97, 90, 75, 76, 69, 34, 107, 79, 31, 89, 71, 69, 71, 8, 69, 75, 6, 16, 70, 70, 120, 23, 22, 22, 0, 85, 88, 90, 103, 89, 103, 111, 102, 123, 115, 111, 126, 126, 126, 126, 120, 107, 102, 107, 122, 110, 100, 101, 100, 79, 96, 101, 98, 101, 121, 105, 100, 126, 112, 126, 124, 126, 126, 126, 126, 87, 85, 126, 98, 100, 105, 125, 109, 110, 110, 98, 103, 87, 73, 118, 124, 126, 98, 109, 114, 68, 14, 20, 23, 45, 30, 35, 9, 42, 35, 46, 35, 48, 40, 30, 37, 83, 92, 102, 110, 108, 126, 126, 126, 21, 48, 44, 49, 39, 42, 25, 25, 14, 16, 68, 14, 20, 23, 45, 30, 35, 9, 42, 35, 46, 35, 48, 40, 30, 37, 83, 92, 102, 110, 108, 126, 126, 126 }, { 15, 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 108, 79, 19, 44, 11, 51, 98, 72, 38, 72, 76, 45, 101, 103, 73, 92, 126, 126, 126, 38, 17, 67, 72, 38, 72, 73, 20, 39, 67, 10, 8, 14, 117, 121, 96, 115, 66, 69, 64, 78, 94, 80, 97, 64, 66, 68, 5, 36, 0, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 7, 4, 0, 51, 27, 13, 59, 57, 13, 27, 35, 32, 18, 15, 54, 115, 91, 90, 93, 28, 73, 25, 54, 0, 68, 8, 32, 65, 72, 72, 79, 27, 77, 18, 7, 73, 14, 66, 1, 9, 17, 25, 23, 8, 81, 69, 5, 64, 64, 70, 32, 65, 1, 35, 29, 41, 32, 30, 101, 14, 16, 80, 67, 74, 70, 39, 4, 9, 47, 62, 62, 62, 62, 103, 117, 15, 44, 43, 126, 74, 17, 102, 29, 71, 5, 46, 33, 53, 50, 56, 117, 13, 91, 122, 125, 7, 12, 21, 7, 64, 7, 7, 64, 14, 5, 84, 77, 4, 64, 64, 79, 69, 79, 86, 70, 68, 64, 78, 77, 118, 79, 83, 69, 96, 88, 93, 80, 86, 86, 86, 94, 121, 100, 77, 123, 126, 109, 123, 115, 126, 97, 91, 126, 76, 83, 83, 94, 100, 115, 110, 102, 118, 93, 100, 104, 120, 13, 25, 52, 30, 10, 4, 23, 16, 11, 7, 29, 30, 51, 35, 28, 18, 45, 26, 33, 13, 54, 13, 41, 28, 23, 16, 33, 16, 9, 10, 13, 66, 62, 39, 12, 2, 27, 9, 68, 6, 24, 45, 43, 25, 11, 41, 19, 19, 9, 8, 62, 7, 16, 23, 23, 10, 20, 25, 29, 27, 35, 42, 57, 1, 9, 67, 64, 62, 76, 111, 70, 11, 11, 70, 71, 69, 76, 5, 79, 104, 70, 68, 28, 71, 77, 70, 68, 1, 77, 74, 71, 98, 91, 75, 76, 69, 35, 108, 79, 32, 90, 71, 69, 71, 9, 69, 76, 7, 17, 70, 70, 121, 22, 21, 21, 65, 87, 90, 92, 106, 92, 106, 114, 105, 126, 118, 113, 126, 126, 126, 126, 123, 109, 104, 109, 125, 112, 101, 102, 101, 79, 98, 103, 100, 103, 123, 107, 101, 126, 114, 126, 126, 126, 126, 126, 126, 88, 86, 126, 99, 102, 106, 126, 111, 112, 111, 99, 104, 87, 72, 119, 126, 126, 99, 110, 115, 68, 15, 20, 23, 46, 31, 36, 9, 43, 36, 47, 36, 49, 40, 31, 37, 85, 94, 104, 112, 110, 126, 126, 126, 21, 49, 45, 50, 39, 43, 26, 25, 15, 16, 68, 15, 20, 23, 46, 31, 36, 9, 43, 36, 47, 36, 49, 40, 31, 37, 85, 94, 104, 112, 110, 126, 126, 126 }, }, { { 62, 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 30, 61, 62, 54, 14, 118, 6, 78, 65, 1, 14, 73, 13, 64, 20, 62, 67, 90, 104, 126, 104, 67, 78, 65, 1, 86, 95, 2, 18, 69, 81, 96, 8, 67, 86, 88, 5, 76, 94, 9, 69, 81, 88, 67, 74, 74, 80, 72, 5, 22, 0, 0, 0, 83, 86, 97, 72, 22, 1, 52, 8, 69, 126, 102, 82, 74, 107, 126, 126, 126, 95, 126, 114, 126, 123, 115, 122, 115, 0, 68, 84, 104, 70, 93, 90, 126, 74, 97, 91, 126, 7, 82, 76, 125, 93, 87, 77, 71, 0, 68, 84, 1, 65, 2, 7, 66, 64, 2, 78, 13, 11, 28, 19, 25, 18, 17, 19, 46, 12, 13, 44, 30, 1, 108, 100, 101, 91, 94, 88, 84, 86, 83, 87, 94, 70, 72, 74, 4, 102, 100, 95, 75, 72, 75, 71, 17, 69, 1, 65, 26, 72, 6, 9, 1, 72, 62, 54, 38, 45, 54, 44, 26, 45, 34, 30, 33, 18, 5, 1, 2, 25, 18, 24, 21, 19, 18, 22, 14, 29, 21, 8, 12, 17, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 46, 62, 60, 41, 62, 62, 62, 62, 60, 58, 62, 47, 41, 15, 26, 3, 68, 97, 71, 21, 13, 9, 1, 5, 0, 72, 74, 91, 67, 36, 24, 19, 17, 64, 68, 78, 77, 86, 92, 8, 3, 1, 65, 73, 76, 80, 88, 110, 97, 84, 79, 73, 74, 86, 96, 97, 117, 78, 30, 15, 10, 1, 71, 79, 86, 90, 97, 62, 93, 84, 79, 66, 71, 1, 3, 4, 75, 1, 5, 66, 79, 71, 68, 19, 1, 27, 23, 36, 34, 19, 27, 31, 21, 15, 1, 17, 64, 104, 97, 96, 88, 85, 85, 85, 88, 66, 77, 76, 76, 5, 76, 83, 99, 95, 95, 76, 74, 70, 75, 68, 65, 73, 1, 1, 68, 75, 8, 64, 70, 57, 44, 47, 49, 50, 52, 48, 47, 40, 40, 43, 37, 19, 23, 16, 46, 42, 41, 36, 34, 28, 13, 6, 0, 77, 82, 94, 69, 109, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 50, 28, 5, 62, 62, 33, 62, 62, 62, 60, 62, 58, 52, 58, 51, 52, 34, 37, 24, 66, 42, 32, 13, 120, 112, 114, 85, 92, 89, 71, 81, 80, 68, 70, 7, 68, 13, 74, 62, 62, 62, 62, 60, 57, 29, 9, 82, 75, 40, 29, 20, 9, 8, 2, 64, 68, 92, 106, 97, 90, 90, 88, 73, 79, 86, 73, 70, 69, 66, 64, 5, 4, 62, 62, 62, 62, 60, 54, 43, 27, 67 }, { 62, 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 29, 60, 62, 54, 14, 115, 6, 77, 64, 1, 14, 72, 12, 65, 20, 62, 68, 91, 104, 124, 102, 67, 77, 64, 1, 85, 93, 3, 18, 68, 80, 95, 8, 67, 85, 88, 5, 75, 93, 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 52, 8, 69, 125, 101, 82, 73, 105, 125, 125, 125, 93, 125, 112, 125, 121, 114, 121, 114, 1, 67, 83, 103, 69, 92, 89, 125, 73, 96, 90, 125, 8, 81, 75, 123, 92, 86, 76, 70, 1, 67, 83, 2, 64, 2, 7, 65, 64, 2, 77, 13, 11, 28, 19, 25, 18, 17, 19, 45, 12, 13, 43, 29, 1, 107, 99, 100, 90, 93, 87, 83, 85, 82, 86, 92, 70, 72, 73, 3, 101, 99, 95, 74, 72, 74, 70, 17, 68, 1, 65, 25, 71, 6, 8, 1, 72, 62, 54, 38, 45, 54, 44, 26, 45, 34, 29, 33, 18, 5, 1, 2, 25, 18, 24, 21, 19, 17, 22, 14, 28, 20, 8, 11, 16, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 44, 62, 59, 40, 62, 62, 62, 62, 58, 56, 61, 45, 39, 15, 25, 2, 68, 97, 70, 22, 14, 10, 2, 5, 0, 71, 73, 90, 66, 37, 25, 20, 17, 0, 67, 77, 76, 85, 91, 9, 4, 2, 64, 72, 75, 79, 87, 108, 96, 82, 78, 72, 73, 85, 95, 96, 115, 77, 31, 16, 11, 2, 70, 78, 85, 89, 96, 62, 92, 83, 78, 66, 70, 1, 4, 5, 74, 2, 6, 65, 78, 71, 68, 19, 2, 27, 23, 35, 34, 19, 26, 30, 21, 15, 1, 16, 64, 103, 96, 95, 87, 84, 84, 84, 87, 66, 76, 75, 75, 5, 75, 82, 98, 94, 95, 76, 73, 70, 74, 68, 65, 72, 1, 1, 67, 74, 8, 64, 70, 57, 44, 47, 49, 49, 52, 48, 47, 40, 40, 43, 37, 19, 22, 15, 45, 41, 40, 35, 33, 27, 13, 6, 0, 76, 81, 93, 69, 108, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 59, 48, 27, 5, 62, 62, 32, 62, 62, 62, 58, 62, 56, 50, 56, 49, 50, 33, 35, 23, 67, 41, 31, 12, 118, 110, 112, 84, 91, 88, 69, 80, 79, 68, 69, 9, 66, 15, 73, 62, 62, 62, 62, 58, 55, 27, 7, 83, 74, 41, 29, 20, 9, 9, 2, 64, 68, 91, 105, 96, 89, 89, 86, 72, 78, 85, 72, 69, 68, 65, 0, 6, 4, 62, 62, 62, 62, 59, 53, 41, 26, 67 }, { 62, 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 28, 59, 61, 54, 14, 113, 6, 76, 0, 1, 13, 72, 11, 66, 19, 60, 70, 92, 105, 121, 101, 67, 76, 0, 1, 85, 92, 3, 17, 68, 80, 94, 8, 67, 85, 88, 5, 75, 92, 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 52, 8, 69, 124, 100, 82, 73, 104, 123, 123, 124, 92, 123, 111, 123, 120, 113, 120, 113, 2, 67, 82, 102, 69, 92, 88, 123, 73, 96, 90, 124, 8, 81, 75, 122, 92, 85, 76, 70, 1, 67, 82, 2, 64, 1, 7, 65, 64, 2, 77, 13, 11, 27, 19, 24, 18, 17, 19, 43, 12, 13, 41, 28, 0, 106, 98, 99, 89, 92, 86, 82, 84, 82, 85, 91, 70, 72, 73, 2, 101, 98, 95, 74, 72, 73, 70, 16, 67, 1, 65, 24, 70, 5, 7, 1, 73, 60, 53, 37, 44, 53, 43, 25, 44, 34, 28, 32, 18, 5, 1, 2, 24, 17, 23, 20, 18, 16, 21, 13, 26, 19, 7, 10, 15, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 58, 41, 62, 57, 38, 62, 62, 62, 62, 56, 54, 58, 43, 37, 14, 23, 1, 69, 97, 70, 22, 14, 10, 2, 5, 0, 71, 73, 89, 66, 37, 25, 20, 17, 1, 67, 76, 76, 84, 90, 10, 5, 2, 64, 71, 75, 79, 86, 107, 95, 81, 77, 72, 73, 84, 94, 95, 114, 77, 31, 16, 11, 2, 69, 77, 84, 88, 95, 62, 92, 83, 78, 66, 70, 1, 4, 5, 74, 2, 6, 64, 78, 71, 68, 18, 2, 26, 22, 34, 33, 19, 25, 29, 21, 15, 0, 15, 65, 102, 95, 94, 87, 84, 84, 83, 86, 66, 76, 75, 75, 4, 75, 82, 98, 93, 95, 76, 73, 70, 73, 68, 65, 71, 1, 1, 67, 73, 7, 64, 71, 56, 44, 47, 48, 48, 51, 47, 46, 39, 39, 42, 36, 18, 21, 14, 43, 40, 38, 33, 32, 26, 12, 5, 0, 76, 81, 93, 70, 107, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 57, 46, 26, 4, 62, 60, 31, 62, 62, 62, 56, 60, 54, 48, 54, 47, 48, 31, 33, 21, 68, 39, 29, 10, 117, 109, 111, 83, 90, 87, 67, 79, 78, 68, 68, 10, 65, 16, 72, 62, 62, 62, 62, 55, 52, 24, 5, 84, 74, 41, 29, 20, 9, 9, 2, 64, 68, 90, 104, 95, 88, 88, 85, 71, 77, 84, 71, 68, 67, 65, 1, 6, 4, 62, 62, 62, 61, 57, 51, 39, 24, 68 }, { 62, 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 26, 57, 60, 54, 14, 111, 6, 75, 1, 1, 12, 72, 10, 67, 19, 58, 71, 93, 105, 118, 100, 67, 75, 1, 1, 84, 91, 4, 17, 68, 79, 93, 7, 68, 85, 88, 5, 75, 92, 9, 69, 80, 88, 65, 73, 73, 79, 70, 5, 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 52, 8, 69, 123, 99, 82, 72, 103, 121, 121, 122, 91, 121, 110, 121, 119, 112, 119, 112, 3, 67, 81, 101, 69, 91, 88, 121, 73, 95, 89, 123, 8, 81, 74, 120, 91, 84, 76, 70, 1, 67, 81, 3, 0, 1, 7, 65, 64, 2, 77, 13, 10, 27, 19, 23, 18, 17, 19, 41, 12, 12, 39, 27, 64, 105, 97, 98, 88, 91, 86, 81, 84, 81, 84, 90, 70, 72, 73, 1, 100, 97, 95, 74, 72, 72, 70, 15, 66, 1, 65, 23, 69, 5, 6, 1, 74, 59, 52, 37, 43, 52, 42, 25, 43, 33, 27, 31, 18, 5, 1, 1, 23, 16, 22, 19, 17, 15, 20, 13, 24, 18, 7, 9, 14, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 55, 39, 62, 55, 37, 62, 61, 62, 59, 54, 51, 56, 41, 34, 13, 21, 0, 70, 97, 70, 23, 14, 10, 2, 5, 0, 71, 73, 89, 66, 37, 25, 20, 17, 2, 66, 76, 75, 84, 89, 11, 5, 3, 64, 70, 74, 78, 86, 106, 94, 80, 76, 71, 73, 83, 93, 94, 113, 76, 31, 16, 11, 2, 68, 77, 83, 87, 94, 62, 91, 82, 77, 66, 70, 1, 4, 5, 74, 2, 6, 64, 78, 71, 68, 18, 3, 25, 21, 33, 32, 19, 24, 28, 21, 15, 0, 14, 65, 101, 94, 93, 86, 83, 83, 83, 85, 66, 76, 75, 74, 4, 75, 82, 97, 92, 95, 76, 73, 70, 72, 68, 65, 70, 1, 1, 67, 72, 6, 64, 72, 55, 43, 46, 47, 47, 50, 46, 45, 38, 38, 41, 35, 17, 20, 13, 42, 39, 37, 31, 30, 25, 11, 5, 64, 76, 81, 93, 70, 106, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 54, 44, 24, 3, 61, 59, 29, 62, 62, 60, 54, 58, 52, 46, 52, 45, 45, 29, 31, 19, 69, 37, 27, 9, 116, 108, 110, 82, 89, 86, 66, 78, 77, 68, 67, 12, 0, 18, 71, 62, 62, 62, 62, 52, 49, 21, 3, 85, 74, 41, 29, 20, 9, 9, 2, 64, 68, 90, 103, 94, 87, 87, 84, 71, 77, 83, 71, 68, 67, 65, 1, 6, 4, 62, 62, 62, 59, 55, 49, 37, 22, 69 }, { 62, 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 25, 56, 58, 54, 14, 108, 5, 74, 1, 1, 11, 72, 9, 68, 18, 56, 73, 94, 106, 115, 99, 67, 74, 1, 1, 84, 90, 4, 16, 68, 79, 93, 7, 68, 84, 88, 5, 75, 91, 8, 70, 80, 88, 65, 72, 73, 78, 70, 5, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 52, 8, 69, 122, 98, 82, 72, 101, 120, 119, 121, 90, 120, 108, 119, 118, 112, 118, 112, 3, 67, 80, 100, 69, 91, 87, 119, 73, 95, 89, 122, 8, 80, 74, 119, 91, 84, 76, 69, 1, 67, 81, 3, 0, 0, 6, 65, 64, 2, 77, 13, 10, 26, 19, 23, 18, 17, 18, 39, 12, 12, 37, 26, 65, 104, 96, 97, 87, 91, 85, 80, 83, 81, 83, 89, 70, 72, 72, 0, 100, 96, 95, 74, 72, 72, 70, 14, 65, 1, 65, 21, 68, 4, 5, 1, 75, 57, 51, 36, 42, 51, 41, 24, 42, 33, 25, 30, 17, 5, 1, 1, 22, 16, 21, 19, 16, 14, 19, 12, 22, 17, 6, 8, 13, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 53, 36, 62, 54, 35, 62, 59, 62, 57, 51, 49, 53, 39, 32, 12, 20, 65, 71, 97, 70, 23, 15, 10, 2, 5, 0, 71, 73, 88, 65, 38, 25, 20, 17, 3, 66, 75, 75, 83, 89, 12, 6, 3, 64, 70, 74, 78, 85, 105, 94, 79, 76, 71, 73, 82, 92, 94, 112, 76, 32, 16, 11, 2, 67, 76, 83, 86, 93, 62, 91, 82, 77, 66, 70, 1, 4, 5, 73, 2, 6, 0, 78, 71, 68, 17, 3, 24, 20, 32, 31, 19, 22, 27, 20, 15, 64, 13, 66, 101, 94, 92, 86, 83, 83, 82, 84, 67, 76, 75, 74, 3, 75, 82, 97, 91, 95, 76, 72, 70, 72, 68, 65, 69, 1, 0, 67, 71, 6, 65, 73, 54, 43, 46, 46, 46, 49, 45, 44, 37, 37, 40, 34, 16, 19, 12, 40, 37, 35, 29, 29, 24, 10, 4, 64, 76, 81, 93, 71, 106, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 55, 52, 42, 23, 2, 59, 57, 28, 62, 62, 58, 52, 55, 50, 44, 50, 43, 43, 27, 29, 17, 70, 35, 25, 7, 115, 107, 109, 82, 88, 85, 64, 77, 76, 68, 66, 13, 1, 19, 71, 62, 62, 62, 62, 49, 46, 18, 1, 86, 74, 41, 29, 20, 9, 9, 2, 64, 68, 89, 102, 93, 86, 87, 83, 70, 76, 82, 70, 67, 66, 64, 2, 7, 4, 62, 62, 62, 57, 53, 47, 35, 20, 70 }, { 62, 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 23, 54, 57, 54, 14, 106, 5, 73, 2, 1, 11, 71, 8, 69, 18, 54, 75, 95, 106, 112, 97, 67, 73, 2, 1, 84, 89, 4, 16, 68, 79, 92, 7, 69, 84, 88, 5, 75, 90, 8, 70, 80, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 52, 8, 69, 121, 97, 82, 71, 100, 118, 117, 119, 89, 118, 107, 117, 117, 111, 117, 111, 4, 67, 79, 99, 69, 90, 86, 117, 73, 95, 88, 120, 9, 80, 73, 118, 90, 83, 76, 69, 2, 66, 80, 4, 1, 0, 6, 65, 64, 2, 77, 13, 9, 25, 19, 22, 18, 17, 18, 37, 12, 11, 36, 25, 66, 103, 95, 96, 86, 90, 84, 79, 82, 80, 82, 88, 70, 72, 72, 64, 99, 95, 95, 73, 72, 71, 70, 13, 64, 1, 65, 20, 67, 4, 4, 1, 75, 56, 50, 36, 41, 50, 40, 23, 42, 33, 24, 29, 17, 5, 1, 0, 22, 15, 20, 18, 15, 13, 19, 11, 20, 16, 5, 7, 12, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 51, 34, 60, 52, 33, 62, 57, 60, 55, 49, 47, 50, 37, 29, 11, 18, 66, 71, 97, 70, 23, 15, 10, 2, 5, 0, 71, 73, 88, 65, 38, 25, 20, 17, 4, 65, 74, 75, 82, 88, 13, 7, 3, 0, 69, 73, 77, 85, 104, 93, 77, 75, 71, 72, 81, 91, 93, 111, 75, 32, 17, 11, 2, 66, 75, 82, 85, 92, 62, 91, 82, 76, 66, 70, 1, 4, 5, 73, 2, 7, 0, 78, 71, 68, 16, 4, 23, 19, 31, 31, 19, 21, 26, 20, 15, 65, 12, 66, 100, 93, 91, 85, 82, 82, 82, 83, 67, 76, 75, 74, 2, 75, 82, 96, 90, 95, 76, 72, 70, 71, 68, 65, 68, 1, 0, 67, 70, 5, 65, 73, 53, 43, 45, 46, 45, 48, 44, 43, 37, 36, 39, 33, 15, 18, 11, 39, 36, 34, 27, 28, 23, 9, 3, 65, 76, 80, 93, 71, 105, 62, 62, 62, 62, 62, 62, 62, 62, 60, 58, 53, 50, 40, 21, 1, 57, 55, 27, 61, 62, 56, 50, 53, 48, 42, 48, 41, 40, 25, 27, 15, 71, 33, 23, 6, 114, 105, 108, 81, 87, 84, 1, 76, 75, 68, 65, 15, 3, 21, 70, 62, 62, 62, 62, 47, 43, 16, 64, 87, 74, 41, 29, 20, 9, 9, 2, 64, 68, 89, 101, 92, 85, 86, 82, 69, 76, 81, 69, 66, 65, 64, 2, 7, 4, 62, 62, 62, 56, 51, 45, 33, 18, 71 }, { 62, 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 22, 53, 56, 54, 14, 104, 5, 73, 3, 1, 10, 71, 7, 70, 17, 53, 76, 96, 107, 109, 96, 67, 73, 3, 1, 83, 88, 5, 15, 67, 78, 91, 6, 69, 84, 88, 5, 74, 90, 8, 70, 79, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 52, 8, 69, 120, 97, 82, 71, 99, 116, 115, 118, 88, 116, 106, 115, 116, 110, 116, 110, 5, 67, 78, 99, 68, 90, 86, 115, 73, 94, 88, 119, 9, 80, 73, 116, 90, 82, 75, 69, 2, 66, 79, 4, 1, 64, 6, 65, 64, 2, 77, 13, 9, 25, 19, 21, 18, 17, 18, 35, 12, 11, 34, 24, 67, 103, 94, 96, 86, 89, 84, 78, 82, 80, 82, 86, 70, 72, 72, 65, 99, 94, 95, 73, 72, 70, 69, 12, 64, 1, 65, 19, 66, 3, 3, 1, 76, 54, 49, 35, 41, 49, 40, 23, 41, 32, 23, 28, 17, 5, 1, 0, 21, 14, 19, 17, 15, 12, 18, 11, 18, 15, 5, 6, 11, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 54, 48, 31, 58, 50, 32, 62, 54, 57, 52, 47, 44, 48, 34, 27, 10, 16, 67, 72, 97, 69, 24, 15, 11, 2, 5, 0, 71, 73, 87, 65, 38, 26, 20, 17, 5, 65, 74, 74, 82, 87, 14, 7, 4, 0, 68, 73, 77, 84, 103, 92, 76, 74, 70, 72, 81, 91, 92, 109, 75, 32, 17, 11, 3, 66, 75, 81, 85, 91, 62, 90, 81, 76, 66, 70, 1, 4, 5, 73, 3, 7, 1, 78, 71, 69, 16, 4, 22, 18, 30, 30, 19, 20, 25, 20, 15, 65, 11, 67, 99, 92, 90, 85, 82, 82, 81, 83, 67, 75, 74, 73, 2, 75, 82, 96, 89, 95, 76, 72, 70, 70, 68, 65, 67, 0, 0, 67, 70, 4, 65, 74, 52, 42, 45, 45, 44, 48, 44, 42, 36, 36, 38, 32, 14, 17, 10, 37, 35, 32, 25, 26, 21, 8, 3, 65, 76, 80, 92, 72, 104, 62, 62, 62, 62, 62, 62, 62, 62, 58, 55, 51, 47, 38, 20, 1, 56, 54, 25, 59, 62, 54, 48, 51, 46, 40, 45, 39, 38, 23, 25, 14, 73, 31, 21, 4, 113, 104, 107, 80, 86, 83, 2, 75, 74, 68, 64, 16, 4, 22, 69, 62, 62, 62, 59, 44, 41, 13, 66, 89, 73, 41, 29, 20, 9, 9, 2, 64, 68, 88, 100, 92, 84, 85, 81, 69, 75, 80, 69, 66, 65, 64, 3, 7, 4, 62, 62, 61, 54, 50, 44, 30, 17, 72 }, { 62, 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 20, 51, 54, 54, 14, 101, 4, 72, 3, 1, 9, 71, 6, 71, 17, 51, 78, 97, 107, 106, 95, 67, 72, 3, 1, 83, 87, 5, 15, 67, 78, 91, 6, 70, 83, 88, 5, 74, 89, 7, 70, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 52, 8, 69, 119, 96, 82, 70, 97, 115, 113, 116, 87, 115, 104, 113, 115, 109, 115, 110, 6, 67, 77, 98, 68, 89, 85, 113, 73, 94, 87, 118, 9, 79, 72, 115, 89, 82, 75, 68, 2, 66, 78, 5, 2, 64, 5, 65, 64, 2, 77, 13, 8, 24, 19, 21, 18, 17, 17, 33, 12, 10, 32, 23, 68, 102, 93, 95, 85, 88, 83, 77, 81, 79, 81, 85, 70, 72, 71, 66, 98, 93, 95, 73, 72, 70, 69, 11, 0, 1, 65, 17, 65, 3, 2, 1, 77, 53, 48, 35, 40, 48, 39, 22, 40, 32, 22, 27, 17, 5, 1, 64, 20, 14, 18, 17, 14, 11, 17, 10, 16, 14, 4, 5, 10, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 61, 52, 46, 29, 56, 49, 30, 62, 52, 55, 50, 44, 42, 45, 32, 24, 9, 15, 69, 73, 97, 69, 24, 16, 11, 2, 5, 0, 71, 73, 87, 64, 39, 26, 20, 17, 6, 64, 73, 74, 81, 86, 15, 8, 4, 0, 67, 72, 76, 84, 102, 92, 75, 74, 70, 72, 80, 90, 92, 108, 74, 33, 17, 11, 3, 65, 74, 80, 84, 90, 62, 90, 81, 75, 66, 70, 1, 4, 5, 72, 3, 7, 1, 78, 71, 69, 15, 5, 21, 17, 29, 29, 19, 19, 24, 19, 15, 66, 10, 67, 98, 92, 89, 84, 81, 81, 81, 82, 67, 75, 74, 73, 1, 75, 82, 95, 88, 95, 76, 71, 70, 70, 68, 65, 66, 0, 0, 67, 69, 4, 66, 75, 51, 42, 44, 44, 43, 47, 43, 41, 35, 35, 37, 31, 13, 16, 9, 36, 33, 31, 23, 25, 20, 7, 2, 66, 76, 80, 92, 72, 103, 62, 62, 62, 62, 62, 62, 62, 61, 56, 53, 49, 45, 36, 18, 0, 54, 52, 24, 57, 62, 52, 46, 49, 44, 38, 43, 37, 35, 21, 23, 12, 74, 29, 19, 3, 112, 103, 106, 80, 85, 82, 4, 74, 73, 68, 0, 18, 6, 24, 69, 62, 62, 61, 56, 41, 38, 10, 68, 90, 73, 41, 29, 20, 9, 9, 2, 64, 68, 88, 99, 91, 83, 84, 80, 68, 75, 79, 68, 65, 64, 0, 3, 8, 4, 62, 62, 59, 52, 48, 42, 28, 15, 73 }, { 62, 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 19, 50, 53, 54, 14, 99, 4, 71, 4, 1, 8, 71, 5, 73, 16, 49, 80, 98, 108, 104, 94, 67, 71, 4, 1, 83, 86, 5, 14, 67, 78, 90, 5, 70, 83, 89, 5, 74, 89, 7, 71, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, 78, 88, 97, 68, 16, 0, 52, 8, 69, 118, 95, 82, 70, 96, 113, 111, 115, 86, 113, 103, 112, 114, 109, 114, 109, 6, 67, 76, 97, 68, 89, 85, 112, 73, 94, 87, 117, 9, 79, 72, 114, 89, 81, 75, 68, 2, 66, 78, 5, 2, 65, 5, 65, 64, 2, 77, 13, 8, 23, 19, 20, 18, 17, 17, 31, 12, 10, 30, 22, 69, 101, 92, 94, 84, 88, 83, 76, 81, 79, 80, 84, 70, 72, 71, 68, 98, 92, 95, 73, 73, 69, 69, 10, 1, 1, 65, 16, 64, 2, 1, 1, 78, 51, 47, 34, 39, 47, 38, 21, 39, 31, 20, 26, 16, 5, 1, 64, 19, 13, 17, 16, 13, 10, 16, 9, 14, 12, 3, 4, 9, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 58, 58, 49, 43, 26, 54, 47, 28, 61, 50, 52, 47, 42, 39, 42, 30, 22, 8, 13, 70, 74, 98, 69, 24, 16, 11, 2, 5, 0, 71, 73, 86, 64, 39, 26, 20, 17, 7, 64, 73, 74, 81, 86, 16, 8, 4, 0, 67, 72, 76, 83, 101, 91, 74, 73, 70, 72, 79, 89, 91, 107, 74, 33, 17, 11, 3, 64, 74, 80, 83, 90, 62, 90, 81, 75, 66, 70, 1, 4, 5, 72, 3, 7, 2, 78, 71, 69, 14, 5, 20, 16, 28, 28, 19, 17, 22, 19, 15, 67, 9, 68, 98, 91, 88, 84, 81, 81, 80, 81, 68, 75, 74, 73, 0, 75, 82, 95, 88, 96, 76, 71, 70, 69, 68, 65, 66, 0, 64, 67, 68, 3, 66, 76, 50, 41, 44, 43, 41, 46, 42, 40, 34, 34, 36, 30, 12, 15, 8, 34, 32, 29, 21, 23, 19, 6, 1, 66, 76, 80, 92, 73, 103, 62, 62, 62, 62, 62, 62, 61, 58, 54, 51, 47, 42, 34, 17, 64, 52, 50, 22, 55, 61, 49, 43, 46, 41, 36, 41, 34, 33, 19, 20, 10, 75, 27, 17, 1, 111, 102, 105, 79, 84, 82, 5, 73, 73, 68, 0, 19, 7, 25, 68, 62, 62, 58, 53, 38, 35, 7, 70, 91, 73, 41, 29, 20, 9, 9, 2, 64, 68, 87, 99, 90, 82, 84, 79, 68, 74, 79, 68, 65, 64, 0, 4, 8, 3, 62, 62, 57, 50, 46, 40, 26, 13, 74 }, { 62, 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 18, 49, 52, 54, 14, 97, 4, 70, 5, 1, 8, 70, 4, 74, 15, 47, 81, 99, 109, 101, 92, 67, 70, 5, 1, 82, 85, 6, 13, 67, 77, 89, 5, 70, 83, 89, 5, 74, 88, 7, 71, 79, 88, 0, 71, 71, 77, 68, 5, 22, 0, 0, 0, 77, 88, 97, 68, 15, 0, 52, 8, 69, 117, 94, 82, 70, 95, 111, 109, 113, 84, 111, 102, 110, 113, 108, 113, 108, 7, 66, 75, 96, 68, 88, 84, 110, 73, 93, 87, 115, 10, 79, 72, 112, 89, 80, 75, 68, 3, 65, 77, 5, 2, 65, 5, 64, 64, 2, 76, 13, 8, 23, 19, 19, 18, 17, 17, 29, 12, 10, 29, 21, 69, 100, 91, 93, 83, 87, 82, 75, 80, 79, 79, 83, 70, 72, 71, 69, 97, 91, 95, 72, 73, 68, 69, 9, 2, 1, 65, 15, 0, 1, 0, 1, 78, 50, 46, 34, 38, 46, 37, 21, 39, 31, 19, 25, 16, 5, 1, 64, 19, 12, 16, 15, 12, 9, 16, 9, 13, 11, 3, 3, 8, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 56, 56, 46, 41, 23, 53, 45, 27, 59, 48, 50, 45, 40, 37, 40, 28, 20, 8, 11, 71, 74, 98, 69, 25, 16, 11, 3, 5, 0, 70, 73, 85, 64, 39, 26, 21, 17, 8, 0, 72, 73, 80, 85, 17, 9, 5, 1, 66, 71, 76, 82, 100, 90, 72, 72, 69, 71, 78, 88, 90, 106, 73, 33, 18, 12, 3, 0, 73, 79, 82, 89, 62, 89, 80, 74, 66, 70, 1, 5, 6, 72, 3, 8, 3, 78, 71, 69, 14, 5, 19, 16, 27, 28, 19, 16, 21, 19, 15, 67, 8, 69, 97, 90, 87, 84, 80, 81, 79, 80, 68, 75, 74, 72, 0, 75, 82, 95, 87, 96, 76, 71, 70, 68, 68, 65, 65, 0, 64, 67, 67, 2, 66, 76, 49, 41, 44, 43, 40, 45, 41, 39, 34, 33, 35, 30, 12, 14, 7, 33, 31, 27, 19, 22, 18, 6, 1, 66, 75, 79, 92, 74, 102, 62, 62, 62, 62, 62, 62, 59, 56, 52, 49, 45, 40, 32, 16, 65, 50, 49, 21, 53, 59, 47, 41, 44, 39, 34, 39, 32, 31, 18, 18, 8, 76, 25, 15, 64, 110, 100, 103, 78, 83, 81, 7, 72, 72, 68, 1, 21, 8, 27, 67, 62, 62, 56, 50, 36, 32, 5, 72, 92, 73, 41, 29, 20, 9, 10, 2, 64, 68, 86, 98, 89, 81, 83, 77, 67, 73, 78, 67, 64, 0, 0, 5, 8, 3, 62, 61, 56, 49, 44, 38, 24, 11, 74 }, { 62, 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 16, 47, 50, 54, 14, 94, 3, 69, 5, 1, 7, 70, 3, 75, 15, 45, 83, 100, 109, 98, 91, 67, 69, 5, 1, 82, 84, 6, 13, 67, 77, 89, 5, 71, 82, 89, 5, 74, 87, 6, 71, 79, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, 77, 88, 97, 67, 14, 0, 52, 8, 69, 116, 93, 82, 69, 93, 110, 107, 112, 83, 110, 100, 108, 112, 107, 112, 108, 8, 66, 74, 95, 68, 88, 83, 108, 73, 93, 86, 114, 10, 78, 71, 111, 88, 80, 75, 67, 3, 65, 76, 6, 3, 66, 4, 64, 64, 2, 76, 13, 7, 22, 19, 19, 18, 17, 16, 27, 12, 9, 27, 20, 70, 99, 90, 92, 82, 86, 81, 74, 79, 78, 78, 82, 70, 72, 70, 70, 97, 90, 95, 72, 73, 68, 69, 8, 3, 1, 65, 13, 1, 1, 64, 1, 79, 48, 45, 33, 37, 45, 36, 20, 38, 31, 18, 24, 16, 5, 1, 65, 18, 12, 15, 15, 11, 8, 15, 8, 11, 10, 2, 2, 7, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 54, 53, 44, 39, 21, 51, 44, 25, 56, 46, 48, 43, 37, 35, 37, 26, 17, 7, 10, 73, 75, 98, 69, 25, 17, 11, 3, 5, 0, 70, 73, 85, 0, 40, 26, 21, 17, 9, 0, 71, 73, 79, 84, 18, 10, 5, 1, 65, 71, 75, 82, 99, 90, 71, 72, 69, 71, 77, 87, 90, 105, 73, 34, 18, 12, 3, 1, 72, 78, 81, 88, 62, 89, 80, 74, 66, 70, 1, 5, 6, 71, 3, 8, 3, 78, 71, 69, 13, 6, 18, 15, 26, 27, 19, 15, 20, 18, 15, 68, 7, 69, 96, 90, 86, 83, 80, 80, 79, 79, 68, 75, 74, 72, 64, 75, 82, 94, 86, 96, 76, 70, 70, 68, 68, 65, 64, 0, 64, 67, 66, 2, 67, 77, 48, 41, 43, 42, 39, 44, 40, 38, 33, 32, 34, 29, 11, 13, 6, 31, 29, 26, 17, 21, 17, 5, 0, 67, 75, 79, 92, 74, 101, 62, 62, 62, 62, 62, 60, 57, 53, 50, 47, 43, 38, 30, 14, 66, 48, 47, 20, 51, 57, 45, 39, 42, 37, 32, 37, 30, 28, 16, 16, 6, 77, 23, 13, 65, 109, 99, 102, 78, 82, 80, 9, 71, 71, 68, 2, 22, 10, 28, 67, 62, 60, 53, 47, 33, 29, 2, 74, 93, 73, 41, 29, 20, 9, 10, 2, 64, 68, 86, 97, 88, 80, 82, 76, 66, 73, 77, 66, 0, 1, 1, 5, 9, 3, 60, 59, 54, 47, 42, 36, 22, 9, 75 }, { 62, 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 15, 46, 49, 54, 14, 92, 3, 69, 6, 1, 6, 70, 2, 76, 14, 44, 84, 101, 110, 95, 90, 67, 69, 6, 1, 81, 83, 7, 12, 66, 76, 88, 4, 71, 82, 89, 5, 73, 87, 6, 71, 78, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, 76, 88, 97, 67, 14, 64, 52, 8, 69, 115, 93, 82, 69, 92, 108, 105, 110, 82, 108, 99, 106, 111, 106, 111, 107, 9, 66, 73, 95, 67, 87, 83, 106, 73, 92, 86, 113, 10, 78, 71, 109, 88, 79, 74, 67, 3, 65, 75, 6, 3, 66, 4, 64, 64, 2, 76, 13, 7, 22, 19, 18, 18, 17, 16, 25, 12, 9, 25, 19, 71, 99, 89, 92, 82, 85, 81, 73, 79, 78, 78, 80, 70, 72, 70, 71, 96, 89, 95, 72, 73, 67, 68, 7, 3, 1, 65, 12, 2, 0, 65, 1, 80, 47, 44, 33, 37, 44, 36, 20, 37, 30, 17, 23, 16, 5, 1, 65, 17, 11, 14, 14, 11, 7, 14, 8, 9, 9, 2, 1, 6, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 54, 52, 51, 41, 36, 18, 49, 42, 24, 54, 43, 45, 40, 35, 32, 35, 23, 15, 6, 8, 74, 76, 98, 68, 26, 17, 12, 3, 5, 0, 70, 73, 84, 0, 40, 27, 21, 17, 10, 1, 71, 72, 79, 83, 19, 10, 6, 1, 64, 70, 75, 81, 98, 89, 70, 71, 68, 71, 77, 87, 89, 103, 72, 34, 18, 12, 4, 1, 72, 77, 81, 87, 62, 88, 79, 73, 66, 70, 1, 5, 6, 71, 4, 8, 4, 78, 71, 70, 13, 6, 17, 14, 25, 26, 19, 14, 19, 18, 15, 68, 6, 70, 95, 89, 85, 83, 79, 80, 78, 79, 68, 74, 73, 71, 64, 75, 82, 94, 85, 96, 76, 70, 70, 67, 68, 65, 0, 64, 64, 67, 66, 1, 67, 78, 47, 40, 43, 41, 38, 44, 40, 37, 32, 32, 33, 28, 10, 12, 5, 30, 28, 24, 15, 19, 15, 4, 0, 67, 75, 79, 91, 75, 100, 62, 62, 62, 62, 62, 58, 55, 51, 48, 44, 41, 35, 28, 13, 66, 47, 46, 18, 49, 54, 43, 37, 40, 35, 30, 34, 28, 26, 14, 14, 5, 79, 21, 11, 67, 108, 98, 101, 77, 81, 79, 10, 70, 70, 68, 3, 24, 11, 30, 66, 61, 59, 51, 44, 30, 27, 64, 76, 95, 72, 41, 29, 20, 9, 10, 2, 64, 68, 85, 96, 88, 79, 81, 75, 66, 72, 76, 66, 0, 1, 1, 6, 9, 3, 59, 58, 52, 45, 41, 35, 19, 8, 76 }, { 62, 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 13, 44, 48, 54, 14, 90, 3, 68, 7, 1, 5, 70, 1, 77, 14, 42, 86, 102, 110, 92, 89, 67, 68, 7, 1, 81, 82, 7, 12, 66, 76, 87, 4, 72, 82, 89, 5, 73, 86, 6, 72, 78, 88, 2, 70, 71, 76, 66, 5, 22, 0, 0, 0, 76, 89, 97, 66, 13, 64, 52, 8, 69, 114, 92, 82, 68, 91, 106, 103, 109, 81, 106, 98, 104, 110, 106, 110, 106, 9, 66, 72, 94, 67, 87, 82, 104, 73, 92, 85, 112, 10, 78, 70, 108, 87, 78, 74, 67, 3, 65, 75, 7, 4, 67, 4, 64, 64, 2, 76, 13, 6, 21, 19, 17, 18, 17, 16, 23, 12, 8, 23, 18, 72, 98, 88, 91, 81, 85, 80, 72, 78, 77, 77, 79, 70, 72, 70, 72, 96, 88, 95, 72, 73, 66, 68, 6, 4, 1, 65, 11, 3, 0, 66, 1, 81, 45, 43, 32, 36, 43, 35, 19, 36, 30, 15, 22, 15, 5, 1, 66, 16, 10, 13, 13, 10, 6, 13, 7, 7, 8, 1, 0, 5, 89, 62, 62, 61, 62, 62, 62, 62, 62, 61, 52, 50, 48, 39, 34, 16, 47, 40, 22, 52, 41, 43, 38, 33, 30, 32, 21, 12, 5, 6, 75, 77, 98, 68, 26, 17, 12, 3, 5, 0, 70, 73, 84, 0, 40, 27, 21, 17, 11, 1, 70, 72, 78, 83, 20, 11, 6, 1, 64, 70, 74, 81, 97, 88, 69, 70, 68, 71, 76, 86, 88, 102, 72, 34, 18, 12, 4, 2, 71, 77, 80, 86, 62, 88, 79, 73, 66, 70, 1, 5, 6, 71, 4, 8, 4, 78, 71, 70, 12, 7, 16, 13, 24, 25, 19, 12, 18, 18, 15, 69, 5, 70, 95, 88, 84, 82, 79, 79, 78, 78, 69, 74, 73, 71, 65, 75, 82, 93, 84, 96, 76, 70, 70, 66, 68, 65, 1, 64, 65, 67, 65, 0, 67, 79, 46, 40, 42, 40, 37, 43, 39, 36, 31, 31, 32, 27, 9, 11, 4, 28, 27, 23, 13, 18, 14, 3, 64, 68, 75, 79, 91, 75, 100, 62, 62, 62, 62, 62, 56, 53, 48, 46, 42, 39, 33, 26, 11, 67, 45, 44, 17, 47, 52, 41, 35, 37, 33, 28, 32, 26, 23, 12, 12, 3, 80, 19, 9, 68, 107, 97, 100, 76, 80, 78, 12, 69, 69, 68, 4, 25, 13, 31, 65, 59, 57, 48, 41, 27, 24, 67, 78, 96, 72, 41, 29, 20, 9, 10, 2, 64, 68, 85, 95, 87, 78, 81, 74, 65, 72, 75, 65, 1, 2, 1, 6, 9, 3, 58, 56, 50, 43, 39, 33, 17, 6, 77 }, { 62, 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 12, 43, 46, 54, 14, 87, 2, 67, 7, 1, 5, 69, 0, 78, 13, 40, 88, 103, 111, 89, 87, 67, 67, 7, 1, 81, 81, 7, 11, 66, 76, 87, 4, 72, 81, 89, 5, 73, 85, 5, 72, 78, 88, 2, 69, 70, 75, 66, 5, 22, 0, 0, 0, 75, 89, 97, 66, 12, 64, 52, 8, 69, 113, 91, 82, 68, 89, 105, 101, 107, 80, 105, 96, 102, 109, 105, 109, 106, 10, 66, 71, 93, 67, 86, 81, 102, 73, 92, 85, 110, 11, 77, 70, 107, 87, 78, 74, 66, 4, 64, 74, 7, 4, 67, 3, 64, 64, 2, 76, 13, 6, 20, 19, 17, 18, 17, 15, 21, 12, 8, 22, 17, 73, 97, 87, 90, 80, 84, 79, 71, 77, 77, 76, 78, 70, 72, 69, 73, 95, 87, 95, 71, 73, 66, 68, 5, 5, 1, 65, 9, 4, 64, 67, 1, 81, 44, 42, 32, 35, 42, 34, 18, 36, 30, 14, 21, 15, 5, 1, 66, 16, 10, 12, 13, 9, 5, 13, 6, 5, 7, 0, 64, 4, 89, 61, 62, 59, 62, 61, 60, 60, 60, 59, 50, 48, 46, 36, 32, 13, 45, 39, 20, 49, 39, 41, 36, 30, 28, 29, 19, 10, 4, 5, 77, 77, 98, 68, 26, 18, 12, 3, 5, 0, 70, 73, 83, 1, 41, 27, 21, 17, 12, 2, 69, 72, 77, 82, 21, 12, 6, 2, 0, 69, 74, 80, 96, 88, 67, 70, 68, 70, 75, 85, 88, 101, 71, 35, 19, 12, 4, 3, 70, 76, 79, 85, 62, 88, 79, 72, 66, 70, 1, 5, 6, 70, 4, 9, 5, 78, 71, 70, 11, 7, 15, 12, 23, 25, 19, 11, 17, 17, 15, 70, 4, 71, 94, 88, 83, 82, 78, 79, 77, 77, 69, 74, 73, 71, 66, 75, 82, 93, 83, 96, 76, 69, 70, 66, 68, 65, 2, 64, 65, 67, 64, 0, 68, 79, 45, 40, 42, 40, 36, 42, 38, 35, 31, 30, 31, 26, 8, 10, 3, 27, 25, 21, 11, 17, 13, 2, 65, 68, 75, 78, 91, 76, 99, 62, 62, 62, 62, 60, 54, 51, 46, 44, 40, 37, 31, 24, 10, 68, 43, 42, 16, 45, 50, 39, 33, 35, 31, 26, 30, 24, 21, 10, 10, 1, 81, 17, 7, 70, 106, 95, 99, 76, 79, 77, 14, 68, 68, 68, 5, 27, 14, 33, 65, 58, 55, 46, 38, 25, 21, 69, 80, 97, 72, 41, 29, 20, 9, 10, 2, 64, 68, 84, 94, 86, 77, 80, 73, 64, 71, 74, 64, 2, 3, 2, 7, 10, 3, 56, 55, 49, 42, 37, 31, 15, 4, 78 }, { 61, 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 10, 41, 45, 54, 14, 85, 2, 66, 8, 1, 4, 69, 64, 79, 13, 38, 89, 104, 111, 86, 86, 67, 66, 8, 1, 80, 80, 8, 11, 66, 75, 86, 3, 73, 81, 89, 5, 73, 85, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, 75, 89, 97, 65, 11, 64, 52, 8, 69, 112, 90, 82, 67, 88, 103, 99, 106, 79, 103, 95, 100, 108, 104, 108, 105, 11, 66, 70, 92, 67, 86, 81, 100, 73, 91, 84, 109, 11, 77, 69, 105, 86, 77, 74, 66, 4, 64, 73, 8, 5, 68, 3, 64, 64, 2, 76, 13, 5, 20, 19, 16, 18, 17, 15, 19, 12, 7, 20, 16, 74, 96, 86, 89, 79, 83, 79, 70, 77, 76, 75, 77, 70, 72, 69, 74, 95, 86, 95, 71, 73, 65, 68, 4, 6, 1, 65, 8, 5, 64, 68, 1, 82, 42, 41, 31, 34, 41, 33, 18, 35, 29, 13, 20, 15, 5, 1, 67, 15, 9, 11, 12, 8, 4, 12, 6, 3, 6, 0, 65, 3, 89, 60, 61, 58, 62, 59, 58, 58, 58, 56, 47, 46, 43, 34, 29, 11, 43, 37, 19, 47, 37, 38, 33, 28, 25, 27, 17, 7, 3, 3, 78, 78, 98, 68, 27, 18, 12, 3, 5, 0, 70, 73, 83, 1, 41, 27, 21, 17, 13, 2, 69, 71, 77, 81, 22, 12, 7, 2, 1, 69, 73, 80, 95, 87, 66, 69, 67, 70, 74, 84, 87, 100, 71, 35, 19, 12, 4, 4, 70, 75, 78, 84, 62, 87, 78, 72, 66, 70, 1, 5, 6, 70, 4, 9, 5, 78, 71, 70, 11, 8, 14, 11, 22, 24, 19, 10, 16, 17, 15, 70, 3, 71, 93, 87, 82, 81, 78, 78, 77, 76, 69, 74, 73, 70, 66, 75, 82, 92, 82, 96, 76, 69, 70, 65, 68, 65, 3, 64, 65, 67, 0, 64, 68, 80, 44, 39, 41, 39, 35, 41, 37, 34, 30, 29, 30, 25, 7, 9, 2, 25, 24, 20, 9, 15, 12, 1, 65, 69, 75, 78, 91, 76, 98, 62, 62, 61, 61, 57, 52, 49, 43, 42, 38, 35, 28, 22, 8, 69, 41, 41, 14, 43, 48, 37, 31, 33, 29, 24, 28, 22, 18, 8, 8, 64, 82, 15, 5, 71, 105, 94, 98, 75, 78, 76, 15, 67, 67, 68, 6, 28, 16, 34, 64, 56, 54, 43, 35, 22, 18, 72, 82, 98, 72, 41, 29, 20, 9, 10, 2, 64, 68, 84, 93, 85, 76, 79, 72, 64, 71, 73, 64, 2, 3, 2, 7, 10, 3, 55, 53, 47, 40, 35, 29, 13, 2, 79 }, { 60, 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 9, 40, 44, 54, 14, 83, 2, 65, 9, 1, 3, 69, 65, 80, 12, 36, 91, 105, 112, 83, 85, 67, 65, 9, 1, 80, 79, 8, 10, 66, 75, 85, 3, 73, 81, 89, 5, 73, 84, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, 74, 89, 97, 65, 10, 64, 52, 8, 69, 111, 89, 82, 67, 87, 101, 97, 104, 78, 101, 94, 98, 107, 103, 107, 104, 12, 66, 69, 91, 67, 85, 80, 98, 73, 91, 84, 108, 11, 77, 69, 104, 86, 76, 74, 66, 4, 64, 72, 8, 5, 68, 3, 64, 64, 2, 76, 13, 5, 19, 19, 15, 18, 17, 15, 17, 12, 7, 18, 15, 75, 95, 85, 88, 78, 82, 78, 69, 76, 76, 74, 76, 70, 72, 69, 75, 94, 85, 95, 71, 73, 64, 68, 3, 7, 1, 65, 7, 6, 65, 69, 1, 83, 41, 40, 31, 33, 40, 32, 17, 34, 29, 12, 19, 15, 5, 1, 67, 14, 8, 10, 11, 7, 3, 11, 5, 1, 5, 64, 66, 2, 89, 58, 60, 56, 60, 57, 56, 56, 56, 54, 45, 44, 41, 31, 27, 8, 41, 35, 17, 45, 35, 36, 31, 26, 23, 24, 15, 5, 2, 1, 79, 79, 98, 68, 27, 18, 12, 3, 5, 0, 70, 73, 82, 1, 41, 27, 21, 17, 14, 3, 68, 71, 76, 80, 23, 13, 7, 2, 2, 68, 73, 79, 94, 86, 65, 68, 67, 70, 73, 83, 86, 99, 70, 35, 19, 12, 4, 5, 69, 74, 77, 83, 62, 87, 78, 71, 66, 70, 1, 5, 6, 70, 4, 9, 6, 78, 71, 70, 10, 8, 13, 10, 21, 23, 19, 9, 15, 17, 15, 71, 2, 72, 92, 86, 81, 81, 77, 78, 76, 75, 69, 74, 73, 70, 67, 75, 82, 92, 81, 96, 76, 69, 70, 64, 68, 65, 4, 64, 65, 67, 1, 65, 68, 81, 43, 39, 41, 38, 34, 40, 36, 33, 29, 28, 29, 24, 6, 8, 1, 24, 23, 18, 7, 14, 11, 0, 66, 69, 75, 78, 91, 77, 97, 62, 62, 59, 59, 54, 50, 47, 41, 40, 36, 33, 26, 20, 7, 70, 39, 39, 13, 41, 46, 35, 29, 31, 27, 22, 26, 20, 16, 6, 6, 66, 83, 13, 3, 73, 104, 93, 97, 74, 77, 75, 17, 66, 66, 68, 7, 30, 17, 36, 0, 55, 52, 41, 32, 19, 15, 75, 84, 99, 72, 41, 29, 20, 9, 10, 2, 64, 68, 83, 92, 84, 75, 78, 71, 0, 70, 72, 0, 3, 4, 2, 8, 10, 3, 54, 52, 45, 38, 33, 27, 11, 0, 80 }, { 58, 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 7, 38, 42, 53, 14, 81, 1, 65, 9, 0, 2, 69, 67, 82, 11, 34, 93, 106, 113, 81, 84, 68, 65, 9, 0, 80, 78, 8, 9, 66, 75, 85, 2, 74, 81, 90, 5, 73, 84, 4, 73, 78, 88, 3, 69, 70, 75, 65, 4, 22, 0, 0, 0, 74, 90, 97, 65, 9, 65, 52, 7, 69, 110, 89, 82, 67, 86, 100, 96, 103, 77, 100, 93, 97, 106, 103, 106, 104, 12, 66, 69, 91, 67, 85, 80, 97, 73, 91, 84, 107, 11, 77, 69, 103, 86, 76, 74, 66, 4, 64, 72, 8, 5, 69, 2, 64, 65, 2, 76, 12, 4, 18, 19, 14, 17, 17, 14, 15, 11, 6, 16, 14, 76, 95, 85, 88, 78, 82, 78, 68, 76, 76, 74, 75, 71, 72, 69, 77, 94, 85, 95, 71, 74, 64, 68, 2, 7, 1, 65, 5, 6, 66, 70, 1, 84, 39, 39, 30, 32, 39, 31, 16, 33, 28, 10, 18, 14, 4, 1, 68, 13, 7, 9, 10, 6, 2, 10, 4, 64, 3, 65, 68, 0, 89, 56, 58, 54, 58, 55, 53, 53, 53, 51, 42, 41, 38, 28, 24, 5, 39, 33, 15, 42, 32, 33, 28, 23, 20, 21, 12, 2, 1, 64, 81, 80, 99, 68, 27, 18, 12, 3, 5, 64, 70, 73, 82, 1, 41, 27, 21, 17, 15, 3, 68, 71, 76, 80, 23, 13, 7, 2, 2, 68, 73, 79, 93, 86, 64, 68, 67, 70, 73, 83, 86, 98, 70, 35, 19, 12, 4, 5, 69, 74, 77, 83, 62, 87, 78, 71, 66, 70, 1, 5, 6, 70, 4, 9, 6, 78, 71, 71, 9, 8, 12, 9, 20, 22, 18, 7, 13, 16, 14, 72, 0, 73, 92, 86, 80, 81, 77, 78, 76, 75, 70, 74, 73, 70, 68, 75, 82, 92, 81, 97, 76, 69, 70, 64, 69, 65, 4, 65, 66, 67, 1, 66, 69, 82, 42, 38, 40, 37, 32, 39, 35, 32, 28, 27, 28, 23, 5, 6, 64, 22, 21, 16, 5, 12, 9, 64, 67, 70, 75, 78, 91, 78, 97, 62, 61, 57, 56, 51, 47, 44, 38, 37, 33, 30, 23, 17, 5, 71, 37, 37, 11, 39, 43, 32, 26, 28, 24, 20, 23, 17, 13, 4, 3, 68, 85, 11, 1, 75, 103, 92, 96, 74, 77, 75, 18, 66, 66, 68, 7, 31, 18, 37, 0, 53, 50, 38, 28, 16, 12, 78, 87, 101, 72, 41, 28, 19, 9, 10, 2, 65, 68, 83, 92, 84, 75, 78, 70, 0, 70, 72, 0, 3, 4, 2, 8, 10, 2, 52, 50, 43, 36, 31, 25, 8, 65, 81 }, { 57, 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 6, 37, 41, 53, 14, 78, 1, 64, 10, 0, 2, 68, 68, 83, 11, 33, 94, 107, 113, 78, 82, 68, 64, 10, 0, 79, 76, 9, 9, 65, 74, 84, 2, 74, 80, 90, 5, 72, 83, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 73, 90, 97, 64, 9, 65, 52, 7, 69, 108, 88, 82, 66, 84, 98, 94, 101, 75, 98, 91, 95, 104, 102, 105, 103, 13, 65, 68, 90, 66, 84, 79, 95, 72, 90, 83, 105, 12, 76, 68, 101, 85, 75, 73, 65, 5, 0, 71, 9, 6, 69, 2, 0, 65, 2, 75, 12, 4, 18, 19, 14, 17, 17, 14, 14, 11, 6, 15, 13, 76, 94, 84, 87, 77, 81, 77, 67, 75, 75, 73, 73, 71, 72, 68, 78, 93, 84, 95, 70, 74, 0, 67, 2, 8, 1, 65, 4, 7, 66, 71, 1, 84, 38, 39, 30, 32, 39, 31, 16, 33, 28, 9, 18, 14, 4, 1, 68, 13, 7, 9, 10, 6, 1, 10, 4, 65, 2, 65, 69, 64, 89, 55, 57, 53, 57, 54, 51, 51, 51, 49, 40, 39, 36, 26, 22, 3, 38, 32, 14, 40, 30, 31, 26, 21, 18, 19, 10, 0, 1, 65, 82, 80, 99, 67, 28, 19, 13, 4, 5, 64, 69, 72, 81, 2, 42, 28, 22, 17, 16, 4, 67, 70, 75, 79, 24, 14, 8, 3, 3, 67, 72, 78, 91, 85, 1, 67, 66, 69, 72, 82, 85, 96, 69, 36, 20, 13, 5, 6, 68, 73, 76, 82, 62, 86, 77, 70, 66, 69, 1, 6, 7, 69, 5, 10, 7, 77, 71, 71, 9, 9, 12, 9, 19, 22, 18, 6, 12, 16, 14, 72, 64, 73, 91, 85, 79, 80, 76, 77, 75, 74, 70, 73, 72, 69, 68, 74, 81, 91, 80, 97, 76, 68, 70, 0, 69, 65, 5, 65, 66, 66, 2, 66, 69, 82, 42, 38, 40, 37, 31, 39, 35, 32, 28, 27, 28, 23, 5, 5, 65, 21, 20, 15, 4, 11, 8, 64, 67, 70, 74, 77, 90, 78, 96, 60, 59, 55, 54, 49, 45, 42, 36, 35, 31, 28, 21, 15, 4, 71, 36, 36, 10, 38, 41, 30, 24, 26, 22, 18, 21, 15, 11, 3, 1, 69, 86, 10, 0, 76, 101, 90, 94, 73, 76, 74, 20, 65, 65, 68, 8, 33, 20, 39, 1, 52, 49, 36, 25, 14, 10, 80, 89, 102, 71, 42, 28, 19, 9, 11, 2, 65, 68, 82, 91, 83, 74, 77, 68, 1, 69, 71, 1, 4, 5, 3, 9, 11, 2, 51, 49, 42, 35, 30, 24, 6, 66, 81 }, { 56, 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 5, 36, 40, 53, 14, 76, 1, 0, 11, 0, 1, 68, 69, 84, 10, 31, 96, 108, 114, 75, 81, 68, 0, 11, 0, 79, 75, 9, 8, 65, 74, 83, 2, 74, 80, 90, 5, 72, 82, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 52, 7, 69, 107, 87, 82, 66, 83, 96, 92, 100, 74, 96, 90, 93, 103, 101, 104, 102, 14, 65, 67, 89, 66, 84, 78, 93, 72, 90, 83, 104, 12, 76, 68, 100, 85, 74, 73, 65, 5, 0, 70, 9, 6, 70, 2, 0, 65, 2, 75, 12, 4, 17, 19, 13, 17, 17, 14, 12, 11, 6, 13, 12, 77, 93, 83, 86, 76, 80, 76, 66, 74, 75, 72, 72, 71, 72, 68, 79, 93, 83, 95, 70, 74, 1, 67, 1, 9, 1, 65, 3, 8, 67, 72, 1, 85, 36, 38, 29, 31, 38, 30, 15, 32, 28, 8, 17, 14, 4, 1, 68, 12, 6, 8, 9, 5, 0, 9, 3, 67, 1, 66, 70, 65, 89, 53, 56, 51, 55, 52, 49, 49, 49, 46, 38, 37, 33, 23, 20, 0, 36, 30, 12, 38, 28, 29, 24, 19, 16, 16, 8, 65, 0, 67, 83, 81, 99, 67, 28, 19, 13, 4, 5, 64, 69, 72, 80, 2, 42, 28, 22, 17, 17, 4, 66, 70, 74, 78, 25, 15, 8, 3, 4, 67, 72, 77, 90, 84, 2, 66, 66, 69, 71, 81, 84, 95, 69, 36, 20, 13, 5, 7, 67, 72, 75, 81, 62, 86, 77, 70, 66, 69, 1, 6, 7, 69, 5, 10, 8, 77, 71, 71, 8, 9, 11, 8, 18, 21, 18, 5, 11, 16, 14, 73, 65, 74, 90, 84, 78, 80, 76, 77, 74, 73, 70, 73, 72, 69, 69, 74, 81, 91, 79, 97, 76, 68, 70, 1, 69, 65, 6, 65, 66, 66, 3, 67, 69, 83, 41, 38, 40, 36, 30, 38, 34, 31, 27, 26, 27, 22, 4, 4, 66, 19, 19, 13, 2, 10, 7, 65, 68, 70, 74, 77, 90, 79, 95, 58, 57, 53, 52, 46, 43, 40, 33, 33, 29, 26, 19, 13, 3, 72, 34, 34, 9, 36, 39, 28, 22, 24, 20, 16, 19, 13, 9, 1, 64, 71, 87, 8, 65, 78, 100, 89, 93, 72, 75, 73, 22, 64, 64, 68, 9, 34, 21, 40, 2, 51, 47, 33, 22, 11, 7, 83, 91, 103, 71, 42, 28, 19, 9, 11, 2, 65, 68, 81, 90, 82, 73, 76, 67, 2, 68, 70, 2, 5, 6, 3, 10, 11, 2, 50, 47, 40, 33, 28, 22, 4, 68, 82 }, { 55, 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 3, 34, 39, 53, 14, 74, 1, 1, 12, 0, 0, 68, 70, 85, 10, 29, 97, 109, 114, 72, 80, 68, 1, 12, 0, 78, 74, 10, 8, 65, 73, 82, 1, 75, 80, 90, 5, 72, 82, 4, 73, 77, 88, 5, 68, 69, 74, 0, 4, 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 52, 7, 69, 106, 86, 82, 65, 82, 94, 90, 98, 73, 94, 89, 91, 102, 100, 103, 101, 15, 65, 66, 88, 66, 83, 78, 91, 72, 89, 82, 103, 12, 76, 67, 98, 84, 73, 73, 65, 5, 0, 69, 10, 7, 70, 2, 0, 65, 2, 75, 12, 3, 17, 19, 12, 17, 17, 14, 10, 11, 5, 11, 11, 78, 92, 82, 85, 75, 79, 76, 65, 74, 74, 71, 71, 71, 72, 68, 80, 92, 82, 95, 70, 74, 2, 67, 0, 10, 1, 65, 2, 9, 67, 73, 1, 86, 35, 37, 29, 30, 37, 29, 15, 31, 27, 7, 16, 14, 4, 1, 69, 11, 5, 7, 8, 4, 64, 8, 3, 69, 0, 66, 71, 66, 89, 52, 54, 50, 53, 50, 47, 47, 47, 44, 35, 35, 31, 21, 17, 65, 34, 28, 11, 36, 26, 26, 21, 17, 13, 14, 6, 68, 64, 69, 84, 82, 99, 67, 29, 19, 13, 4, 5, 64, 69, 72, 80, 2, 42, 28, 22, 17, 18, 5, 66, 69, 74, 77, 26, 15, 9, 3, 5, 66, 71, 77, 89, 83, 3, 65, 65, 69, 70, 80, 83, 94, 68, 36, 20, 13, 5, 8, 67, 71, 74, 80, 62, 85, 76, 69, 66, 69, 1, 6, 7, 69, 5, 10, 8, 77, 71, 71, 8, 10, 10, 7, 17, 20, 18, 4, 10, 16, 14, 73, 66, 74, 89, 83, 77, 79, 75, 76, 74, 72, 70, 73, 72, 68, 69, 74, 81, 90, 78, 97, 76, 68, 70, 2, 69, 65, 7, 65, 66, 66, 4, 68, 69, 84, 40, 37, 39, 35, 29, 37, 33, 30, 26, 25, 26, 21, 3, 3, 67, 18, 18, 12, 0, 8, 6, 66, 68, 71, 74, 77, 90, 79, 94, 56, 55, 51, 50, 43, 41, 38, 31, 31, 27, 24, 16, 11, 1, 73, 32, 33, 7, 34, 37, 26, 20, 22, 18, 14, 17, 11, 6, 64, 66, 73, 88, 6, 67, 79, 99, 88, 92, 71, 74, 72, 23, 0, 0, 68, 10, 36, 23, 42, 3, 49, 46, 31, 19, 8, 4, 86, 93, 104, 71, 42, 28, 19, 9, 11, 2, 65, 68, 81, 89, 81, 72, 75, 66, 2, 68, 69, 2, 5, 6, 3, 10, 11, 2, 49, 46, 38, 31, 26, 20, 2, 70, 83 }, { 53, 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 2, 33, 37, 53, 14, 71, 0, 2, 12, 0, 64, 68, 71, 86, 9, 27, 99, 110, 115, 69, 79, 68, 2, 12, 0, 78, 73, 10, 7, 65, 73, 82, 1, 75, 79, 90, 5, 72, 81, 3, 74, 77, 88, 5, 67, 69, 73, 0, 4, 22, 0, 0, 0, 71, 91, 97, 0, 6, 65, 52, 7, 69, 105, 85, 82, 65, 80, 93, 88, 97, 72, 93, 87, 89, 101, 100, 102, 101, 15, 65, 65, 87, 66, 83, 77, 89, 72, 89, 82, 102, 12, 75, 67, 97, 84, 73, 73, 64, 5, 0, 69, 10, 7, 71, 1, 0, 65, 2, 75, 12, 3, 16, 19, 12, 17, 17, 13, 8, 11, 5, 9, 10, 79, 91, 81, 84, 74, 79, 75, 64, 73, 74, 70, 70, 71, 72, 67, 81, 92, 81, 95, 70, 74, 2, 67, 64, 11, 1, 65, 0, 10, 68, 74, 1, 87, 33, 36, 28, 29, 36, 28, 14, 30, 27, 5, 15, 13, 4, 1, 69, 10, 5, 6, 8, 3, 65, 7, 2, 71, 64, 67, 72, 67, 89, 50, 53, 48, 51, 48, 45, 44, 45, 41, 33, 33, 28, 18, 15, 68, 32, 27, 9, 33, 24, 24, 19, 14, 11, 11, 4, 70, 65, 70, 86, 83, 99, 67, 29, 20, 13, 4, 5, 64, 69, 72, 79, 3, 43, 28, 22, 17, 19, 5, 65, 69, 73, 77, 27, 16, 9, 3, 5, 66, 71, 76, 88, 83, 4, 65, 65, 69, 69, 79, 83, 93, 68, 37, 20, 13, 5, 9, 66, 71, 73, 79, 62, 85, 76, 69, 66, 69, 1, 6, 7, 68, 5, 10, 9, 77, 71, 71, 7, 10, 9, 6, 16, 19, 18, 2, 9, 15, 14, 74, 67, 75, 89, 83, 76, 79, 75, 76, 73, 71, 71, 73, 72, 68, 70, 74, 81, 90, 77, 97, 76, 67, 70, 2, 69, 65, 8, 65, 67, 66, 5, 68, 70, 85, 39, 37, 39, 34, 28, 36, 32, 29, 25, 24, 25, 20, 2, 2, 68, 16, 16, 10, 65, 7, 5, 67, 69, 71, 74, 77, 90, 80, 94, 53, 52, 49, 47, 40, 39, 36, 28, 29, 25, 22, 14, 9, 0, 74, 30, 31, 6, 32, 35, 24, 18, 19, 16, 12, 15, 9, 4, 66, 68, 75, 89, 4, 69, 81, 98, 87, 91, 71, 73, 71, 25, 1, 1, 68, 11, 37, 24, 43, 3, 48, 44, 28, 16, 5, 1, 89, 95, 105, 71, 42, 28, 19, 9, 11, 2, 65, 68, 80, 88, 80, 71, 75, 65, 3, 67, 68, 3, 6, 7, 4, 11, 12, 2, 47, 44, 36, 29, 24, 18, 0, 72, 84 }, { 52, 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 0, 31, 36, 53, 14, 69, 0, 3, 13, 0, 64, 67, 72, 87, 9, 25, 101, 111, 115, 66, 77, 68, 3, 13, 0, 78, 72, 10, 7, 65, 73, 81, 1, 76, 79, 90, 5, 72, 80, 3, 74, 77, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 52, 7, 69, 104, 84, 82, 64, 79, 91, 86, 95, 71, 91, 86, 87, 100, 99, 101, 100, 16, 65, 64, 86, 66, 82, 76, 87, 72, 89, 81, 100, 13, 75, 66, 96, 83, 72, 73, 64, 6, 1, 68, 11, 8, 71, 1, 0, 65, 2, 75, 12, 2, 15, 19, 11, 17, 17, 13, 6, 11, 4, 8, 9, 80, 90, 80, 83, 73, 78, 74, 0, 72, 73, 69, 69, 71, 72, 67, 82, 91, 80, 95, 69, 74, 3, 67, 65, 12, 1, 65, 64, 11, 68, 75, 1, 87, 32, 35, 28, 28, 35, 27, 13, 30, 27, 4, 14, 13, 4, 1, 70, 10, 4, 5, 7, 2, 66, 7, 1, 73, 65, 68, 73, 68, 89, 48, 52, 46, 49, 47, 43, 42, 43, 39, 31, 31, 26, 16, 13, 70, 30, 25, 7, 31, 22, 22, 17, 12, 9, 8, 2, 73, 66, 72, 87, 83, 99, 67, 29, 20, 13, 4, 5, 64, 69, 72, 79, 3, 43, 28, 22, 17, 20, 6, 64, 69, 72, 76, 28, 17, 9, 4, 6, 65, 70, 76, 87, 82, 6, 64, 65, 68, 68, 78, 82, 92, 67, 37, 21, 13, 5, 10, 65, 70, 72, 78, 62, 85, 76, 68, 66, 69, 1, 6, 7, 68, 5, 11, 9, 77, 71, 71, 6, 11, 8, 5, 15, 19, 18, 1, 8, 15, 14, 75, 68, 75, 88, 82, 75, 78, 74, 75, 73, 70, 71, 73, 72, 68, 71, 74, 81, 89, 76, 97, 76, 67, 70, 3, 69, 65, 9, 65, 67, 66, 6, 69, 70, 85, 38, 37, 38, 34, 27, 35, 31, 28, 25, 23, 24, 19, 1, 1, 69, 15, 15, 9, 67, 6, 4, 68, 70, 72, 74, 76, 90, 80, 93, 51, 50, 47, 45, 38, 37, 34, 26, 27, 23, 20, 12, 7, 65, 75, 28, 29, 5, 30, 33, 22, 16, 17, 14, 10, 13, 7, 1, 68, 70, 77, 90, 2, 71, 82, 97, 85, 90, 70, 72, 70, 27, 2, 2, 68, 12, 39, 26, 45, 4, 46, 42, 26, 13, 3, 65, 91, 97, 106, 71, 42, 28, 19, 9, 11, 2, 65, 68, 80, 87, 79, 70, 74, 64, 4, 67, 67, 4, 7, 8, 4, 11, 12, 2, 46, 43, 35, 28, 22, 16, 65, 74, 85 }, { 51, 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 64, 30, 35, 53, 14, 67, 0, 3, 14, 0, 65, 67, 73, 88, 8, 24, 102, 112, 116, 0, 76, 68, 3, 14, 0, 77, 71, 11, 6, 64, 72, 80, 0, 76, 79, 90, 5, 71, 80, 3, 74, 76, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 70, 91, 97, 1, 5, 66, 52, 7, 69, 103, 84, 82, 64, 78, 89, 84, 94, 70, 89, 85, 85, 99, 98, 100, 99, 17, 65, 0, 86, 65, 82, 76, 85, 72, 88, 81, 99, 13, 75, 66, 94, 83, 71, 72, 64, 6, 1, 67, 11, 8, 72, 1, 0, 65, 2, 75, 12, 2, 15, 19, 10, 17, 17, 13, 4, 11, 4, 6, 8, 81, 90, 79, 83, 73, 77, 74, 1, 72, 73, 69, 67, 71, 72, 67, 83, 91, 79, 95, 69, 74, 4, 66, 66, 12, 1, 65, 65, 12, 69, 76, 1, 88, 30, 34, 27, 28, 34, 27, 13, 29, 26, 3, 13, 13, 4, 1, 70, 9, 3, 4, 6, 2, 67, 6, 1, 75, 66, 68, 74, 69, 89, 47, 50, 45, 47, 45, 41, 40, 41, 36, 28, 29, 23, 13, 10, 73, 28, 23, 6, 29, 19, 19, 14, 10, 6, 6, 64, 75, 67, 74, 88, 84, 99, 66, 30, 20, 14, 4, 5, 64, 69, 72, 78, 3, 43, 29, 22, 17, 21, 6, 64, 68, 72, 75, 29, 17, 10, 4, 7, 65, 70, 75, 86, 81, 7, 0, 64, 68, 68, 78, 81, 90, 67, 37, 21, 13, 6, 10, 65, 69, 72, 77, 62, 84, 75, 68, 66, 69, 1, 6, 7, 68, 6, 11, 10, 77, 71, 72, 6, 11, 7, 4, 14, 18, 18, 0, 7, 15, 14, 75, 69, 76, 87, 81, 74, 78, 74, 75, 72, 70, 71, 72, 71, 67, 71, 74, 81, 89, 75, 97, 76, 67, 70, 4, 69, 65, 10, 66, 67, 66, 6, 70, 70, 86, 37, 36, 38, 33, 26, 35, 31, 27, 24, 23, 23, 18, 0, 0, 70, 13, 14, 7, 69, 4, 2, 69, 70, 72, 74, 76, 89, 81, 92, 49, 48, 45, 43, 35, 35, 32, 23, 25, 20, 18, 9, 5, 66, 75, 27, 28, 3, 28, 30, 20, 14, 15, 12, 8, 10, 5, 64, 70, 72, 78, 92, 0, 73, 84, 96, 84, 89, 69, 71, 69, 28, 3, 3, 68, 13, 40, 27, 46, 5, 45, 41, 23, 10, 0, 67, 94, 99, 108, 70, 42, 28, 19, 9, 11, 2, 65, 68, 79, 86, 79, 69, 73, 0, 4, 66, 66, 4, 7, 8, 4, 12, 12, 2, 45, 41, 33, 26, 21, 15, 68, 75, 86 }, { 50, 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 66, 28, 33, 53, 14, 64, 64, 4, 14, 0, 66, 67, 74, 89, 8, 22, 104, 113, 116, 3, 75, 68, 4, 14, 0, 77, 70, 11, 6, 64, 72, 80, 0, 77, 78, 90, 5, 71, 79, 2, 74, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 70, 91, 97, 2, 4, 66, 52, 7, 69, 102, 83, 82, 0, 76, 88, 82, 92, 69, 88, 83, 83, 98, 97, 99, 99, 18, 65, 1, 85, 65, 81, 75, 83, 72, 88, 80, 98, 13, 74, 65, 93, 82, 71, 72, 0, 6, 1, 66, 12, 9, 72, 0, 0, 65, 2, 75, 12, 1, 14, 19, 10, 17, 17, 12, 2, 11, 3, 4, 7, 82, 89, 78, 82, 72, 76, 73, 2, 71, 72, 68, 66, 71, 72, 66, 84, 90, 78, 95, 69, 74, 4, 66, 67, 13, 1, 65, 67, 13, 69, 77, 1, 89, 29, 33, 27, 27, 33, 26, 12, 28, 26, 2, 12, 13, 4, 1, 71, 8, 3, 3, 6, 1, 68, 5, 0, 77, 67, 69, 75, 70, 89, 45, 49, 43, 45, 43, 39, 37, 39, 34, 26, 27, 21, 11, 8, 75, 26, 22, 4, 26, 17, 17, 12, 7, 4, 3, 66, 78, 68, 75, 90, 85, 99, 66, 30, 21, 14, 4, 5, 64, 69, 72, 78, 4, 44, 29, 22, 17, 22, 7, 0, 68, 71, 74, 30, 18, 10, 4, 8, 64, 69, 75, 85, 81, 8, 0, 64, 68, 67, 77, 81, 89, 66, 38, 21, 13, 6, 11, 64, 68, 71, 76, 62, 84, 75, 67, 66, 69, 1, 6, 7, 67, 6, 11, 10, 77, 71, 72, 5, 12, 6, 3, 13, 17, 18, 64, 6, 14, 14, 76, 70, 76, 86, 81, 73, 77, 73, 74, 72, 69, 71, 72, 71, 67, 72, 74, 81, 88, 74, 97, 76, 66, 70, 4, 69, 65, 11, 66, 67, 66, 7, 70, 71, 87, 36, 36, 37, 32, 25, 34, 30, 26, 23, 22, 22, 17, 64, 64, 71, 12, 12, 6, 71, 3, 1, 70, 71, 73, 74, 76, 89, 81, 91, 47, 46, 43, 40, 32, 33, 30, 21, 23, 18, 16, 7, 3, 68, 76, 25, 26, 2, 26, 28, 18, 12, 13, 10, 6, 8, 3, 67, 72, 74, 80, 93, 65, 75, 85, 95, 83, 88, 69, 70, 68, 30, 4, 4, 68, 14, 42, 29, 48, 5, 43, 39, 21, 7, 66, 70, 97, 101, 109, 70, 42, 28, 19, 9, 11, 2, 65, 68, 79, 85, 78, 68, 72, 1, 5, 66, 65, 5, 8, 9, 5, 12, 13, 2, 43, 40, 31, 24, 19, 13, 70, 77, 87 }, { 48, 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 67, 27, 32, 53, 14, 1, 64, 5, 15, 0, 67, 67, 75, 91, 7, 20, 106, 114, 117, 5, 74, 68, 5, 15, 0, 77, 69, 11, 5, 64, 72, 79, 64, 77, 78, 91, 5, 71, 79, 2, 75, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 69, 92, 97, 2, 3, 66, 52, 7, 69, 101, 82, 82, 0, 75, 86, 80, 91, 68, 86, 82, 82, 97, 97, 98, 98, 18, 65, 2, 84, 65, 81, 75, 82, 72, 88, 80, 97, 13, 74, 65, 92, 82, 70, 72, 0, 6, 1, 66, 12, 9, 73, 0, 0, 65, 2, 75, 12, 1, 13, 19, 9, 17, 17, 12, 0, 11, 3, 2, 6, 83, 88, 77, 81, 71, 76, 73, 3, 71, 72, 67, 65, 71, 72, 66, 86, 90, 77, 95, 69, 75, 5, 66, 68, 14, 1, 65, 68, 14, 70, 78, 1, 90, 27, 32, 26, 26, 32, 25, 11, 27, 25, 0, 11, 12, 4, 1, 71, 7, 2, 2, 5, 0, 69, 4, 64, 79, 69, 70, 76, 71, 89, 43, 47, 41, 43, 41, 37, 35, 37, 31, 23, 25, 18, 8, 5, 78, 24, 20, 2, 24, 15, 14, 9, 5, 1, 0, 68, 80, 69, 77, 91, 86, 100, 66, 30, 21, 14, 4, 5, 64, 69, 72, 77, 4, 44, 29, 22, 17, 23, 7, 0, 68, 71, 74, 31, 18, 10, 4, 8, 64, 69, 74, 84, 80, 9, 1, 64, 68, 66, 76, 80, 88, 66, 38, 21, 13, 6, 12, 64, 68, 70, 76, 62, 84, 75, 67, 66, 69, 1, 6, 7, 67, 6, 11, 11, 77, 71, 72, 4, 12, 5, 2, 12, 16, 18, 66, 4, 14, 14, 77, 71, 77, 86, 80, 72, 77, 73, 74, 71, 68, 72, 72, 71, 67, 73, 74, 81, 88, 74, 98, 76, 66, 70, 5, 69, 65, 11, 66, 68, 66, 8, 71, 71, 88, 35, 35, 37, 31, 23, 33, 29, 25, 22, 21, 21, 16, 65, 65, 72, 10, 11, 4, 73, 1, 0, 71, 72, 73, 74, 76, 89, 82, 91, 44, 43, 41, 38, 29, 30, 27, 18, 21, 16, 14, 4, 1, 69, 77, 23, 24, 0, 24, 26, 15, 9, 10, 7, 4, 6, 0, 69, 74, 77, 82, 94, 67, 77, 87, 94, 82, 87, 68, 69, 68, 31, 5, 4, 68, 14, 43, 30, 49, 6, 42, 37, 18, 4, 69, 73, 100, 103, 110, 70, 42, 28, 19, 9, 11, 2, 65, 68, 78, 85, 77, 67, 72, 2, 5, 65, 65, 5, 8, 9, 5, 13, 13, 1, 42, 38, 29, 22, 17, 11, 72, 79, 88 }, { 47, 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 68, 26, 31, 53, 14, 3, 64, 6, 16, 0, 67, 66, 76, 92, 6, 18, 107, 115, 118, 8, 72, 68, 6, 16, 0, 76, 68, 12, 4, 64, 71, 78, 64, 77, 78, 91, 5, 71, 78, 2, 75, 76, 88, 7, 66, 67, 72, 2, 4, 22, 0, 0, 0, 68, 92, 97, 2, 2, 66, 52, 7, 69, 100, 81, 82, 0, 74, 84, 78, 89, 66, 84, 81, 80, 96, 96, 97, 97, 19, 64, 3, 83, 65, 80, 74, 80, 72, 87, 80, 95, 14, 74, 65, 90, 82, 69, 72, 0, 7, 2, 65, 12, 9, 73, 0, 1, 65, 2, 74, 12, 1, 13, 19, 8, 17, 17, 12, 65, 11, 3, 1, 5, 83, 87, 76, 80, 70, 75, 72, 4, 70, 72, 66, 64, 71, 72, 66, 87, 89, 76, 95, 68, 75, 6, 66, 69, 15, 1, 65, 69, 15, 71, 79, 1, 90, 26, 31, 26, 25, 31, 24, 11, 27, 25, 64, 10, 12, 4, 1, 71, 7, 1, 1, 4, 64, 70, 4, 64, 80, 70, 70, 77, 72, 89, 42, 46, 40, 42, 40, 35, 33, 35, 29, 21, 23, 16, 5, 3, 81, 23, 18, 1, 22, 13, 12, 7, 3, 64, 65, 70, 82, 69, 79, 92, 86, 100, 66, 31, 21, 14, 5, 5, 64, 68, 72, 76, 4, 44, 29, 23, 17, 24, 8, 1, 67, 70, 73, 32, 19, 11, 5, 9, 0, 69, 73, 83, 79, 11, 2, 0, 67, 65, 75, 79, 87, 65, 38, 22, 14, 6, 13, 0, 67, 69, 75, 62, 83, 74, 66, 66, 69, 1, 7, 8, 67, 6, 12, 12, 77, 71, 72, 4, 12, 4, 2, 11, 16, 18, 67, 3, 14, 14, 77, 72, 78, 85, 79, 71, 77, 72, 74, 70, 67, 72, 72, 71, 66, 73, 74, 81, 88, 73, 98, 76, 66, 70, 6, 69, 65, 12, 66, 68, 66, 9, 72, 71, 88, 34, 35, 37, 31, 22, 32, 28, 24, 22, 20, 20, 16, 65, 66, 73, 9, 10, 2, 75, 0, 64, 71, 72, 73, 73, 75, 89, 83, 90, 42, 41, 39, 36, 27, 28, 25, 16, 19, 14, 12, 2, 64, 70, 78, 21, 23, 64, 22, 24, 13, 7, 8, 5, 2, 4, 65, 71, 75, 79, 84, 95, 69, 79, 89, 93, 80, 85, 67, 68, 67, 33, 6, 5, 68, 15, 45, 31, 51, 7, 41, 36, 16, 1, 71, 76, 102, 105, 111, 70, 42, 28, 19, 9, 12, 2, 65, 68, 77, 84, 76, 66, 71, 4, 6, 64, 64, 6, 9, 10, 5, 14, 13, 1, 41, 37, 28, 21, 15, 9, 74, 81, 88 }, { 46, 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 70, 24, 29, 53, 14, 6, 65, 7, 16, 0, 68, 66, 77, 93, 6, 16, 109, 116, 118, 11, 71, 68, 7, 16, 0, 76, 67, 12, 4, 64, 71, 78, 64, 78, 77, 91, 5, 71, 77, 1, 75, 76, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 52, 7, 69, 99, 80, 82, 1, 72, 83, 76, 88, 65, 83, 79, 78, 95, 95, 96, 97, 20, 64, 4, 82, 65, 80, 73, 78, 72, 87, 79, 94, 14, 73, 64, 89, 81, 69, 72, 1, 7, 2, 64, 13, 10, 74, 64, 1, 65, 2, 74, 12, 0, 12, 19, 8, 17, 17, 11, 67, 11, 2, 64, 4, 84, 86, 75, 79, 69, 74, 71, 5, 69, 71, 65, 0, 71, 72, 65, 88, 89, 75, 95, 68, 75, 6, 66, 70, 16, 1, 65, 71, 16, 71, 80, 1, 91, 24, 30, 25, 24, 30, 23, 10, 26, 25, 65, 9, 12, 4, 1, 72, 6, 1, 0, 4, 65, 71, 3, 65, 82, 71, 71, 78, 73, 89, 40, 45, 38, 40, 38, 33, 30, 33, 26, 19, 21, 13, 3, 1, 83, 21, 17, 64, 19, 11, 10, 5, 0, 66, 68, 72, 85, 70, 80, 94, 87, 100, 66, 31, 22, 14, 5, 5, 64, 68, 72, 76, 5, 45, 29, 23, 17, 25, 8, 2, 67, 69, 72, 33, 20, 11, 5, 10, 0, 68, 73, 82, 79, 12, 2, 0, 67, 64, 74, 79, 86, 65, 39, 22, 14, 6, 14, 1, 66, 68, 74, 62, 83, 74, 66, 66, 69, 1, 7, 8, 66, 6, 12, 12, 77, 71, 72, 3, 13, 3, 1, 10, 15, 18, 68, 2, 13, 14, 78, 73, 78, 84, 79, 70, 76, 72, 73, 70, 66, 72, 72, 71, 66, 74, 74, 81, 87, 72, 98, 76, 65, 70, 6, 69, 65, 13, 66, 68, 66, 10, 72, 72, 89, 33, 35, 36, 30, 21, 31, 27, 23, 21, 19, 19, 15, 66, 67, 74, 7, 8, 1, 77, 64, 65, 72, 73, 74, 73, 75, 89, 83, 89, 40, 39, 37, 33, 24, 26, 23, 13, 17, 12, 10, 0, 66, 72, 79, 19, 21, 65, 20, 22, 11, 5, 6, 3, 0, 2, 67, 74, 77, 81, 86, 96, 71, 81, 90, 92, 79, 84, 67, 67, 66, 35, 7, 6, 68, 16, 46, 33, 52, 7, 39, 34, 13, 65, 74, 79, 105, 107, 112, 70, 42, 28, 19, 9, 12, 2, 65, 68, 77, 83, 75, 65, 70, 5, 7, 64, 0, 7, 10, 11, 6, 14, 14, 1, 39, 35, 26, 19, 13, 7, 76, 83, 89 }, { 45, 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 71, 23, 28, 53, 14, 8, 65, 7, 17, 0, 69, 66, 78, 94, 5, 15, 110, 117, 119, 14, 70, 68, 7, 17, 0, 75, 66, 13, 3, 0, 70, 77, 65, 78, 77, 91, 5, 70, 77, 1, 75, 75, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 52, 7, 69, 98, 80, 82, 1, 71, 81, 74, 86, 64, 81, 78, 76, 94, 94, 95, 96, 21, 64, 5, 82, 64, 79, 73, 76, 72, 86, 79, 93, 14, 73, 64, 87, 81, 68, 71, 1, 7, 2, 0, 13, 10, 74, 64, 1, 65, 2, 74, 12, 0, 12, 19, 7, 17, 17, 11, 69, 11, 2, 66, 3, 85, 86, 74, 79, 69, 73, 71, 6, 69, 71, 65, 2, 71, 72, 65, 89, 88, 74, 95, 68, 75, 7, 65, 71, 16, 1, 65, 72, 17, 72, 81, 1, 92, 23, 29, 25, 24, 29, 23, 10, 25, 24, 66, 8, 12, 4, 1, 72, 5, 0, 64, 3, 65, 72, 2, 65, 84, 72, 71, 79, 74, 89, 39, 43, 37, 38, 36, 31, 28, 31, 24, 16, 19, 11, 0, 65, 86, 19, 15, 65, 17, 8, 7, 2, 65, 69, 70, 75, 87, 71, 82, 95, 88, 100, 65, 32, 22, 15, 5, 5, 64, 68, 72, 75, 5, 45, 30, 23, 17, 26, 9, 2, 66, 69, 71, 34, 20, 12, 5, 11, 1, 68, 72, 81, 78, 13, 3, 1, 67, 64, 74, 78, 84, 64, 39, 22, 14, 7, 14, 1, 65, 68, 73, 62, 82, 73, 65, 66, 69, 1, 7, 8, 66, 7, 12, 13, 77, 71, 73, 3, 13, 2, 0, 9, 14, 18, 69, 1, 13, 14, 78, 74, 79, 83, 78, 69, 76, 71, 73, 69, 66, 72, 71, 70, 65, 74, 74, 81, 87, 71, 98, 76, 65, 70, 7, 69, 65, 14, 67, 68, 66, 10, 73, 72, 90, 32, 34, 36, 29, 20, 31, 27, 22, 20, 19, 18, 14, 67, 68, 75, 6, 7, 64, 79, 66, 67, 73, 73, 74, 73, 75, 88, 84, 88, 38, 37, 35, 31, 21, 24, 21, 11, 15, 9, 8, 66, 68, 73, 79, 18, 20, 67, 18, 19, 9, 3, 4, 1, 65, 64, 69, 76, 79, 83, 87, 98, 73, 83, 92, 91, 78, 83, 66, 66, 65, 36, 8, 7, 68, 17, 48, 34, 54, 8, 38, 33, 11, 68, 77, 81, 108, 109, 114, 69, 42, 28, 19, 9, 12, 2, 65, 68, 76, 82, 75, 64, 69, 6, 7, 0, 1, 7, 10, 11, 6, 15, 14, 1, 38, 34, 24, 17, 12, 6, 79, 84, 90 }, { 43, 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 73, 21, 27, 53, 14, 10, 65, 8, 18, 0, 70, 66, 79, 95, 5, 13, 112, 118, 119, 17, 69, 68, 8, 18, 0, 75, 65, 13, 3, 0, 70, 76, 65, 79, 77, 91, 5, 70, 76, 1, 76, 75, 88, 9, 65, 67, 71, 4, 4, 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 52, 7, 69, 97, 79, 82, 2, 70, 79, 72, 85, 0, 79, 77, 74, 93, 94, 94, 95, 21, 64, 6, 81, 64, 79, 72, 74, 72, 86, 78, 92, 14, 73, 0, 86, 80, 67, 71, 1, 7, 2, 0, 14, 11, 75, 64, 1, 65, 2, 74, 12, 64, 11, 19, 6, 17, 17, 11, 71, 11, 1, 68, 2, 86, 85, 73, 78, 68, 73, 70, 7, 68, 70, 64, 3, 71, 72, 65, 90, 88, 73, 95, 68, 75, 8, 65, 72, 17, 1, 65, 73, 18, 72, 82, 1, 93, 21, 28, 24, 23, 28, 22, 9, 24, 24, 68, 7, 11, 4, 1, 73, 4, 64, 65, 2, 66, 73, 1, 66, 86, 73, 72, 80, 75, 89, 37, 42, 35, 36, 34, 29, 26, 29, 21, 14, 17, 8, 65, 67, 88, 17, 13, 67, 15, 6, 5, 0, 67, 71, 73, 77, 90, 72, 84, 96, 89, 100, 65, 32, 22, 15, 5, 5, 64, 68, 72, 75, 5, 45, 30, 23, 17, 27, 9, 3, 66, 68, 71, 35, 21, 12, 5, 11, 1, 67, 72, 80, 77, 14, 4, 1, 67, 0, 73, 77, 83, 64, 39, 22, 14, 7, 15, 2, 65, 67, 72, 62, 82, 73, 65, 66, 69, 1, 7, 8, 66, 7, 12, 13, 77, 71, 73, 2, 14, 1, 64, 8, 13, 18, 71, 0, 13, 14, 79, 75, 79, 83, 77, 68, 75, 71, 72, 69, 65, 73, 71, 70, 65, 75, 74, 81, 86, 70, 98, 76, 65, 70, 8, 69, 65, 15, 67, 69, 66, 11, 74, 72, 91, 31, 34, 35, 28, 19, 30, 26, 21, 19, 18, 17, 13, 68, 69, 76, 4, 6, 65, 81, 67, 68, 74, 74, 75, 73, 75, 88, 84, 88, 35, 34, 33, 29, 18, 22, 19, 8, 13, 7, 6, 68, 70, 75, 80, 16, 18, 68, 16, 17, 7, 1, 1, 64, 67, 66, 71, 79, 81, 85, 89, 99, 75, 85, 93, 90, 77, 82, 65, 65, 64, 38, 9, 8, 68, 18, 49, 36, 55, 9, 36, 31, 8, 71, 80, 84, 111, 111, 115, 69, 42, 28, 19, 9, 12, 2, 65, 68, 76, 81, 74, 0, 69, 7, 8, 0, 2, 8, 11, 12, 6, 15, 14, 1, 37, 32, 22, 15, 10, 4, 81, 86, 91 }, { 42, 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 74, 20, 25, 53, 14, 13, 66, 9, 18, 0, 70, 65, 80, 96, 4, 11, 114, 119, 120, 20, 67, 68, 9, 18, 0, 75, 64, 13, 2, 0, 70, 76, 65, 79, 76, 91, 5, 70, 75, 0, 76, 75, 88, 9, 64, 66, 70, 4, 4, 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 52, 7, 69, 96, 78, 82, 2, 68, 78, 70, 83, 1, 78, 75, 72, 92, 93, 93, 95, 22, 64, 7, 80, 64, 78, 71, 72, 72, 86, 78, 90, 15, 72, 0, 85, 80, 67, 71, 2, 8, 3, 1, 14, 11, 75, 65, 1, 65, 2, 74, 12, 64, 10, 19, 6, 17, 17, 10, 73, 11, 1, 69, 1, 87, 84, 72, 77, 67, 72, 69, 8, 67, 70, 0, 4, 71, 72, 64, 91, 87, 72, 95, 67, 75, 8, 65, 73, 18, 1, 65, 75, 19, 73, 83, 1, 93, 20, 27, 24, 22, 27, 21, 8, 24, 24, 69, 6, 11, 4, 1, 73, 4, 64, 66, 2, 67, 74, 1, 67, 88, 74, 73, 81, 76, 89, 35, 41, 33, 34, 33, 27, 23, 27, 19, 12, 15, 6, 68, 69, 91, 15, 12, 69, 12, 4, 3, 65, 70, 73, 76, 79, 92, 73, 85, 98, 89, 100, 65, 32, 23, 15, 5, 5, 64, 68, 72, 74, 6, 46, 30, 23, 17, 28, 10, 4, 66, 67, 70, 36, 22, 12, 6, 12, 2, 67, 71, 79, 77, 16, 4, 1, 66, 1, 72, 77, 82, 0, 40, 23, 14, 7, 16, 3, 64, 66, 71, 62, 82, 73, 64, 66, 69, 1, 7, 8, 65, 7, 13, 14, 77, 71, 73, 1, 14, 0, 65, 7, 13, 18, 72, 64, 12, 14, 80, 76, 80, 82, 77, 67, 75, 70, 72, 68, 64, 73, 71, 70, 65, 76, 74, 81, 86, 69, 98, 76, 64, 70, 8, 69, 65, 16, 67, 69, 66, 12, 74, 73, 91, 30, 34, 35, 28, 18, 29, 25, 20, 19, 17, 16, 12, 69, 70, 77, 3, 4, 67, 83, 68, 69, 75, 75, 75, 73, 74, 88, 85, 87, 33, 32, 31, 26, 16, 20, 17, 6, 11, 5, 4, 70, 72, 76, 81, 14, 16, 69, 14, 15, 5, 64, 64, 66, 69, 68, 73, 81, 83, 87, 91, 100, 77, 87, 95, 89, 75, 81, 65, 64, 0, 40, 10, 9, 68, 19, 51, 37, 57, 9, 35, 29, 6, 74, 82, 87, 113, 113, 116, 69, 42, 28, 19, 9, 12, 2, 65, 68, 75, 80, 73, 1, 68, 8, 9, 1, 3, 9, 12, 13, 7, 16, 15, 1, 35, 31, 21, 14, 8, 2, 83, 88, 92 }, { 41, 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 76, 18, 24, 53, 14, 15, 66, 10, 19, 0, 71, 65, 81, 97, 4, 9, 115, 120, 120, 23, 66, 68, 10, 19, 0, 74, 0, 14, 2, 0, 69, 75, 66, 80, 76, 91, 5, 70, 75, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 52, 7, 69, 95, 77, 82, 3, 67, 76, 68, 82, 2, 76, 74, 70, 91, 92, 92, 94, 23, 64, 8, 79, 64, 78, 71, 70, 72, 85, 77, 89, 15, 72, 1, 83, 79, 66, 71, 2, 8, 3, 2, 15, 12, 76, 65, 1, 65, 2, 74, 12, 65, 10, 19, 5, 17, 17, 10, 75, 11, 0, 71, 0, 88, 83, 71, 76, 66, 71, 69, 9, 67, 69, 1, 5, 71, 72, 64, 92, 87, 71, 95, 67, 75, 9, 65, 74, 19, 1, 65, 76, 20, 73, 84, 1, 94, 18, 26, 23, 21, 26, 20, 8, 23, 23, 70, 5, 11, 4, 1, 74, 3, 65, 67, 1, 68, 75, 0, 67, 90, 75, 73, 82, 77, 89, 34, 39, 32, 32, 31, 25, 21, 25, 16, 9, 13, 3, 70, 72, 93, 13, 10, 70, 10, 2, 0, 68, 72, 76, 78, 81, 95, 74, 87, 99, 90, 100, 65, 33, 23, 15, 5, 5, 64, 68, 72, 74, 6, 46, 30, 23, 17, 29, 10, 4, 65, 67, 69, 37, 22, 13, 6, 13, 2, 66, 71, 78, 76, 17, 5, 2, 66, 2, 71, 76, 81, 0, 40, 23, 14, 7, 17, 3, 0, 65, 70, 62, 81, 72, 64, 66, 69, 1, 7, 8, 65, 7, 13, 14, 77, 71, 73, 1, 15, 64, 66, 6, 12, 18, 73, 65, 12, 14, 80, 77, 80, 81, 76, 66, 74, 70, 71, 68, 0, 73, 71, 70, 64, 76, 74, 81, 85, 68, 98, 76, 64, 70, 9, 69, 65, 17, 67, 69, 66, 13, 75, 73, 92, 29, 33, 34, 27, 17, 28, 24, 19, 18, 16, 15, 11, 70, 71, 78, 1, 3, 68, 85, 70, 70, 76, 75, 76, 73, 74, 88, 85, 86, 31, 30, 29, 24, 13, 18, 15, 3, 9, 3, 2, 73, 74, 78, 82, 12, 15, 71, 12, 13, 3, 66, 66, 68, 71, 70, 75, 84, 85, 89, 93, 101, 79, 89, 96, 88, 74, 80, 64, 0, 1, 41, 11, 10, 68, 20, 52, 39, 58, 10, 33, 28, 3, 77, 85, 90, 116, 115, 117, 69, 42, 28, 19, 9, 12, 2, 65, 68, 75, 79, 72, 2, 67, 9, 9, 1, 4, 9, 12, 13, 7, 16, 15, 1, 34, 29, 19, 12, 6, 0, 85, 90, 93 }, { 40, 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 77, 17, 23, 53, 14, 17, 66, 11, 20, 0, 72, 65, 82, 98, 3, 7, 117, 121, 121, 26, 65, 68, 11, 20, 0, 74, 1, 14, 1, 0, 69, 74, 66, 80, 76, 91, 5, 70, 74, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 52, 7, 69, 94, 76, 82, 3, 66, 74, 66, 80, 3, 74, 73, 68, 90, 91, 91, 93, 24, 64, 9, 78, 64, 77, 70, 68, 72, 85, 77, 88, 15, 72, 1, 82, 79, 65, 71, 2, 8, 3, 3, 15, 12, 76, 65, 1, 65, 2, 74, 12, 65, 9, 19, 4, 17, 17, 10, 77, 11, 0, 73, 64, 89, 82, 70, 75, 65, 70, 68, 10, 66, 69, 2, 6, 71, 72, 64, 93, 86, 70, 95, 67, 75, 10, 65, 75, 20, 1, 65, 77, 21, 74, 85, 1, 95, 17, 25, 23, 20, 25, 19, 7, 22, 23, 71, 4, 11, 4, 1, 74, 2, 66, 68, 0, 69, 76, 64, 68, 92, 76, 74, 83, 78, 89, 32, 38, 30, 30, 29, 23, 19, 23, 14, 7, 11, 1, 73, 74, 96, 11, 8, 72, 8, 0, 65, 70, 74, 78, 81, 83, 97, 75, 89, 100, 91, 100, 65, 33, 23, 15, 5, 5, 64, 68, 72, 73, 6, 46, 30, 23, 17, 30, 11, 5, 65, 66, 68, 38, 23, 13, 6, 14, 3, 66, 70, 77, 75, 18, 6, 2, 66, 3, 70, 75, 80, 1, 40, 23, 14, 7, 18, 4, 1, 64, 69, 62, 81, 72, 0, 66, 69, 1, 7, 8, 65, 7, 13, 15, 77, 71, 73, 0, 15, 65, 67, 5, 11, 18, 74, 66, 12, 14, 81, 78, 81, 80, 75, 65, 74, 69, 71, 67, 1, 73, 71, 70, 64, 77, 74, 81, 85, 67, 98, 76, 64, 70, 10, 69, 65, 18, 67, 69, 66, 14, 76, 73, 93, 28, 33, 34, 26, 16, 27, 23, 18, 17, 15, 14, 10, 71, 72, 79, 0, 2, 70, 87, 71, 71, 77, 76, 76, 73, 74, 88, 86, 85, 29, 28, 27, 22, 10, 16, 13, 1, 7, 1, 0, 75, 76, 79, 83, 10, 13, 72, 10, 11, 1, 68, 68, 70, 73, 72, 77, 86, 87, 91, 95, 102, 81, 91, 98, 87, 73, 79, 0, 1, 2, 43, 12, 11, 68, 21, 54, 40, 60, 11, 32, 26, 1, 80, 88, 93, 119, 117, 118, 69, 42, 28, 19, 9, 12, 2, 65, 68, 74, 78, 71, 3, 66, 10, 10, 2, 5, 10, 13, 14, 7, 17, 15, 1, 33, 28, 17, 10, 4, 65, 87, 92, 94 }, { 38, 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 79, 15, 21, 52, 14, 19, 67, 11, 20, 64, 73, 65, 84, 100, 2, 5, 119, 122, 122, 28, 64, 69, 11, 20, 64, 74, 2, 14, 0, 0, 69, 74, 67, 81, 76, 92, 5, 70, 74, 64, 77, 75, 88, 10, 64, 66, 70, 5, 3, 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 52, 6, 69, 93, 76, 82, 3, 65, 73, 65, 79, 4, 73, 72, 67, 89, 91, 90, 93, 24, 64, 9, 78, 64, 77, 70, 67, 72, 85, 77, 87, 15, 72, 1, 81, 79, 65, 71, 2, 8, 3, 3, 15, 12, 77, 66, 1, 66, 2, 74, 11, 66, 8, 19, 3, 16, 17, 9, 79, 10, 64, 75, 65, 90, 82, 70, 75, 65, 70, 68, 11, 66, 69, 2, 7, 72, 72, 64, 95, 86, 70, 95, 67, 76, 10, 65, 76, 20, 1, 65, 79, 21, 75, 86, 1, 96, 15, 24, 22, 19, 24, 18, 6, 21, 22, 73, 3, 10, 3, 1, 75, 1, 67, 69, 64, 70, 77, 65, 69, 94, 78, 75, 85, 80, 89, 30, 36, 28, 28, 27, 20, 16, 20, 11, 4, 8, 65, 76, 77, 99, 9, 6, 74, 5, 66, 68, 73, 77, 81, 84, 86, 100, 76, 91, 102, 92, 101, 65, 33, 23, 15, 5, 5, 65, 68, 72, 73, 6, 46, 30, 23, 17, 31, 11, 5, 65, 66, 68, 38, 23, 13, 6, 14, 3, 66, 70, 76, 75, 19, 6, 2, 66, 3, 70, 75, 79, 1, 40, 23, 14, 7, 18, 4, 1, 64, 69, 62, 81, 72, 0, 66, 69, 1, 7, 8, 65, 7, 13, 15, 77, 71, 74, 64, 15, 66, 68, 4, 10, 17, 76, 68, 11, 13, 82, 80, 82, 80, 75, 64, 74, 69, 71, 67, 1, 74, 71, 70, 64, 78, 74, 81, 85, 67, 99, 76, 64, 70, 10, 70, 65, 18, 68, 70, 66, 14, 77, 74, 94, 27, 32, 33, 25, 14, 26, 22, 17, 16, 14, 13, 9, 72, 74, 81, 65, 0, 72, 89, 73, 73, 78, 77, 77, 73, 74, 88, 87, 85, 26, 25, 25, 19, 7, 13, 10, 65, 4, 65, 66, 78, 79, 81, 84, 8, 11, 74, 8, 8, 65, 71, 71, 73, 75, 75, 80, 89, 89, 94, 97, 104, 83, 93, 100, 86, 72, 78, 0, 1, 2, 44, 12, 11, 68, 21, 55, 41, 61, 11, 30, 24, 65, 84, 91, 96, 122, 120, 120, 69, 42, 27, 18, 9, 12, 2, 66, 68, 74, 78, 71, 3, 66, 11, 10, 2, 5, 10, 13, 14, 7, 17, 15, 0, 31, 26, 15, 8, 2, 67, 90, 94, 95 }, { 37, 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 80, 14, 20, 52, 14, 22, 67, 12, 21, 64, 73, 64, 85, 101, 2, 4, 120, 123, 122, 31, 1, 69, 12, 21, 64, 73, 4, 15, 0, 1, 68, 73, 67, 81, 75, 92, 5, 69, 73, 64, 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 52, 6, 69, 91, 75, 82, 4, 0, 71, 0, 77, 6, 71, 70, 65, 87, 90, 89, 92, 25, 0, 10, 77, 0, 76, 69, 65, 71, 84, 76, 85, 16, 71, 2, 79, 78, 64, 70, 3, 9, 4, 4, 16, 13, 77, 66, 2, 66, 2, 73, 11, 66, 8, 19, 3, 16, 17, 9, 80, 10, 64, 76, 66, 90, 81, 69, 74, 64, 69, 67, 12, 65, 68, 3, 9, 72, 72, 0, 96, 85, 69, 95, 66, 76, 11, 64, 76, 21, 1, 65, 80, 22, 75, 87, 1, 96, 14, 24, 22, 19, 24, 18, 6, 21, 22, 74, 3, 10, 3, 1, 75, 1, 67, 69, 64, 70, 78, 65, 69, 95, 79, 75, 86, 81, 89, 29, 35, 27, 27, 26, 18, 14, 18, 9, 2, 6, 67, 78, 79, 101, 8, 5, 75, 3, 68, 70, 75, 79, 83, 86, 88, 102, 76, 92, 103, 92, 101, 64, 34, 24, 16, 6, 5, 65, 67, 71, 72, 7, 47, 31, 24, 17, 32, 12, 6, 64, 65, 67, 39, 24, 14, 7, 15, 4, 65, 69, 74, 74, 21, 7, 3, 65, 4, 69, 74, 77, 2, 41, 24, 15, 8, 19, 5, 2, 0, 68, 62, 80, 71, 1, 66, 68, 1, 8, 9, 64, 8, 14, 16, 76, 71, 74, 64, 16, 66, 68, 3, 10, 17, 77, 69, 11, 13, 82, 81, 82, 79, 74, 0, 73, 68, 70, 66, 2, 74, 70, 69, 0, 78, 73, 80, 84, 66, 99, 76, 0, 70, 11, 70, 65, 19, 68, 70, 65, 15, 77, 74, 94, 27, 32, 33, 25, 13, 26, 22, 17, 16, 14, 13, 9, 72, 75, 82, 66, 64, 73, 90, 74, 74, 78, 77, 77, 72, 73, 87, 87, 84, 24, 23, 23, 17, 5, 11, 8, 67, 2, 67, 68, 80, 81, 82, 84, 7, 10, 75, 7, 6, 67, 73, 73, 75, 77, 77, 82, 91, 90, 96, 98, 105, 84, 94, 101, 84, 70, 76, 1, 2, 3, 46, 13, 12, 68, 22, 57, 43, 62, 12, 29, 23, 67, 87, 93, 98, 124, 122, 121, 68, 43, 27, 18, 9, 13, 2, 66, 68, 73, 77, 70, 4, 65, 13, 11, 3, 6, 11, 14, 15, 8, 18, 16, 0, 30, 25, 14, 7, 1, 68, 92, 95, 95 }, { 36, 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 81, 13, 19, 52, 14, 24, 67, 13, 22, 64, 74, 64, 86, 102, 1, 2, 122, 124, 123, 34, 2, 69, 13, 22, 64, 73, 5, 15, 64, 1, 68, 72, 67, 81, 75, 92, 5, 69, 72, 64, 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 52, 6, 69, 90, 74, 82, 4, 1, 69, 2, 76, 7, 69, 69, 0, 86, 89, 88, 91, 26, 0, 11, 76, 0, 76, 68, 0, 71, 84, 76, 84, 16, 71, 2, 78, 78, 0, 70, 3, 9, 4, 5, 16, 13, 78, 66, 2, 66, 2, 73, 11, 66, 7, 19, 2, 16, 17, 9, 82, 10, 64, 78, 67, 91, 80, 68, 73, 0, 68, 66, 13, 64, 68, 4, 10, 72, 72, 0, 97, 85, 68, 95, 66, 76, 12, 64, 77, 22, 1, 65, 81, 23, 76, 88, 1, 97, 12, 23, 21, 18, 23, 17, 5, 20, 22, 75, 2, 10, 3, 1, 75, 0, 68, 70, 65, 71, 79, 66, 70, 97, 80, 76, 87, 82, 89, 27, 34, 25, 25, 24, 16, 12, 16, 6, 0, 4, 70, 81, 81, 104, 6, 3, 77, 1, 70, 72, 77, 81, 85, 89, 90, 104, 77, 94, 104, 93, 101, 64, 34, 24, 16, 6, 5, 65, 67, 71, 71, 7, 47, 31, 24, 17, 33, 12, 7, 64, 64, 66, 40, 25, 14, 7, 16, 4, 65, 68, 73, 73, 22, 8, 3, 65, 5, 68, 73, 76, 2, 41, 24, 15, 8, 20, 6, 3, 1, 67, 62, 80, 71, 1, 66, 68, 1, 8, 9, 64, 8, 14, 17, 76, 71, 74, 65, 16, 67, 69, 2, 9, 17, 78, 70, 11, 13, 83, 82, 83, 78, 73, 1, 73, 68, 70, 65, 3, 74, 70, 69, 0, 79, 73, 80, 84, 65, 99, 76, 0, 70, 12, 70, 65, 20, 68, 70, 65, 16, 78, 74, 95, 26, 32, 33, 24, 12, 25, 21, 16, 15, 13, 12, 8, 73, 76, 83, 68, 65, 75, 92, 75, 75, 79, 78, 77, 72, 73, 87, 88, 83, 22, 21, 21, 15, 2, 9, 6, 70, 0, 69, 70, 82, 83, 83, 85, 5, 8, 76, 5, 4, 69, 75, 75, 77, 79, 79, 84, 93, 92, 98, 100, 106, 86, 96, 103, 83, 69, 75, 2, 3, 4, 48, 14, 13, 68, 23, 58, 44, 62, 13, 28, 21, 70, 90, 96, 101, 126, 124, 122, 68, 43, 27, 18, 9, 13, 2, 66, 68, 72, 76, 69, 5, 64, 14, 12, 4, 7, 12, 15, 16, 8, 19, 16, 0, 29, 23, 12, 5, 64, 70, 94, 97, 96 }, { 35, 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 83, 11, 18, 52, 14, 26, 67, 14, 23, 64, 75, 64, 87, 103, 1, 0, 123, 125, 123, 37, 3, 69, 14, 23, 64, 72, 6, 16, 64, 1, 67, 71, 68, 82, 75, 92, 5, 69, 72, 64, 77, 74, 88, 12, 0, 65, 69, 7, 3, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 52, 6, 69, 89, 73, 82, 5, 2, 67, 4, 74, 8, 67, 68, 2, 85, 88, 87, 90, 27, 0, 12, 75, 0, 75, 68, 2, 71, 83, 75, 83, 16, 71, 3, 76, 77, 1, 70, 3, 9, 4, 6, 17, 14, 78, 66, 2, 66, 2, 73, 11, 67, 7, 19, 1, 16, 17, 9, 84, 10, 65, 80, 68, 92, 79, 67, 72, 1, 67, 66, 14, 64, 67, 5, 11, 72, 72, 0, 98, 84, 67, 95, 66, 76, 13, 64, 78, 23, 1, 65, 82, 24, 76, 89, 1, 98, 11, 22, 21, 17, 22, 16, 5, 19, 21, 76, 1, 10, 3, 1, 76, 64, 69, 71, 66, 72, 80, 67, 70, 99, 81, 76, 88, 83, 89, 26, 32, 24, 23, 22, 14, 10, 14, 4, 66, 2, 72, 83, 84, 106, 4, 1, 78, 64, 72, 75, 80, 83, 88, 91, 92, 107, 78, 96, 105, 94, 101, 64, 35, 24, 16, 6, 5, 65, 67, 71, 71, 7, 47, 31, 24, 17, 34, 13, 7, 0, 64, 65, 41, 25, 15, 7, 17, 5, 64, 68, 72, 72, 23, 9, 4, 65, 6, 67, 72, 75, 3, 41, 24, 15, 8, 21, 6, 4, 2, 66, 62, 79, 70, 2, 66, 68, 1, 8, 9, 64, 8, 14, 17, 76, 71, 74, 65, 17, 68, 70, 1, 8, 17, 79, 71, 11, 13, 83, 83, 83, 77, 72, 2, 72, 67, 69, 65, 4, 74, 70, 69, 1, 79, 73, 80, 83, 64, 99, 76, 0, 70, 13, 70, 65, 21, 68, 70, 65, 17, 79, 74, 96, 25, 31, 32, 23, 11, 24, 20, 15, 14, 12, 11, 7, 74, 77, 84, 69, 66, 76, 94, 77, 76, 80, 78, 78, 72, 73, 87, 88, 82, 20, 19, 19, 13, 64, 7, 4, 72, 65, 71, 72, 85, 85, 85, 86, 3, 7, 78, 3, 2, 71, 77, 77, 79, 81, 81, 86, 96, 94, 100, 102, 107, 88, 98, 104, 82, 68, 74, 3, 4, 5, 49, 15, 14, 68, 24, 60, 46, 62, 14, 26, 20, 72, 93, 99, 104, 126, 126, 123, 68, 43, 27, 18, 9, 13, 2, 66, 68, 72, 75, 68, 6, 0, 15, 12, 4, 8, 12, 15, 16, 8, 19, 16, 0, 28, 22, 10, 3, 66, 72, 96, 99, 97 }, { 33, 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 84, 10, 16, 52, 14, 29, 68, 15, 23, 64, 76, 64, 88, 104, 0, 65, 125, 126, 124, 40, 4, 69, 15, 23, 64, 72, 7, 16, 65, 1, 67, 71, 68, 82, 74, 92, 5, 69, 71, 65, 78, 74, 88, 12, 1, 65, 68, 7, 3, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 52, 6, 69, 88, 72, 82, 5, 4, 66, 6, 73, 9, 66, 66, 4, 84, 88, 86, 90, 27, 0, 13, 74, 0, 75, 67, 4, 71, 83, 75, 82, 16, 70, 3, 75, 77, 1, 70, 4, 9, 4, 6, 17, 14, 79, 67, 2, 66, 2, 73, 11, 67, 6, 19, 1, 16, 17, 8, 86, 10, 65, 82, 69, 93, 78, 66, 71, 2, 67, 65, 15, 0, 67, 6, 12, 72, 72, 1, 99, 84, 66, 95, 66, 76, 13, 64, 79, 24, 1, 65, 84, 25, 77, 90, 1, 99, 9, 21, 20, 16, 21, 15, 4, 18, 21, 78, 0, 9, 3, 1, 76, 65, 69, 72, 66, 73, 81, 68, 71, 101, 82, 77, 89, 84, 89, 24, 31, 22, 21, 20, 12, 7, 12, 1, 68, 0, 75, 86, 86, 109, 2, 0, 80, 67, 74, 77, 82, 86, 90, 94, 94, 109, 79, 97, 107, 95, 101, 64, 35, 25, 16, 6, 5, 65, 67, 71, 70, 8, 48, 31, 24, 17, 35, 13, 8, 0, 0, 65, 42, 26, 15, 7, 17, 5, 64, 67, 71, 72, 24, 9, 4, 65, 7, 66, 72, 74, 3, 42, 24, 15, 8, 22, 7, 4, 3, 65, 62, 79, 70, 2, 66, 68, 1, 8, 9, 0, 8, 14, 18, 76, 71, 74, 66, 17, 69, 71, 0, 7, 17, 81, 72, 10, 13, 84, 84, 84, 77, 72, 3, 72, 67, 69, 64, 5, 75, 70, 69, 1, 80, 73, 80, 83, 0, 99, 76, 1, 70, 13, 70, 65, 22, 68, 71, 65, 18, 79, 75, 97, 24, 31, 32, 22, 10, 23, 19, 14, 13, 11, 10, 6, 75, 78, 85, 71, 68, 78, 96, 78, 77, 81, 79, 78, 72, 73, 87, 89, 82, 17, 16, 17, 10, 67, 5, 2, 75, 67, 73, 74, 87, 87, 86, 87, 1, 5, 79, 1, 0, 73, 79, 80, 81, 83, 83, 88, 98, 96, 102, 104, 108, 90, 100, 106, 81, 67, 73, 3, 5, 6, 51, 16, 15, 68, 25, 61, 47, 62, 14, 25, 18, 75, 96, 102, 107, 126, 126, 124, 68, 43, 27, 18, 9, 13, 2, 66, 68, 71, 74, 67, 7, 0, 16, 13, 5, 9, 13, 16, 17, 9, 20, 17, 0, 26, 20, 8, 1, 68, 74, 98, 101, 98 }, { 32, 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 86, 8, 15, 52, 14, 31, 68, 16, 24, 64, 76, 0, 89, 105, 0, 67, 126, 126, 124, 43, 6, 69, 16, 24, 64, 72, 8, 16, 65, 1, 67, 70, 68, 83, 74, 92, 5, 69, 70, 65, 78, 74, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 52, 6, 69, 87, 71, 82, 6, 5, 64, 8, 71, 10, 64, 65, 6, 83, 87, 85, 89, 28, 0, 14, 73, 0, 74, 66, 6, 71, 83, 74, 80, 17, 70, 4, 74, 76, 2, 70, 4, 10, 5, 7, 18, 15, 79, 67, 2, 66, 2, 73, 11, 68, 5, 19, 0, 16, 17, 8, 88, 10, 66, 83, 70, 94, 77, 65, 70, 3, 66, 64, 16, 1, 66, 7, 13, 72, 72, 1, 100, 83, 65, 95, 65, 76, 14, 64, 80, 25, 1, 65, 85, 26, 77, 91, 1, 99, 8, 20, 20, 15, 20, 14, 3, 18, 21, 79, 64, 9, 3, 1, 77, 65, 70, 73, 67, 74, 82, 68, 72, 103, 83, 78, 90, 85, 89, 22, 30, 20, 19, 19, 10, 5, 10, 64, 70, 65, 77, 88, 88, 111, 0, 65, 82, 69, 76, 79, 84, 88, 92, 97, 96, 112, 80, 99, 108, 95, 101, 64, 35, 25, 16, 6, 5, 65, 67, 71, 70, 8, 48, 31, 24, 17, 36, 14, 9, 0, 1, 64, 43, 27, 15, 8, 18, 6, 0, 67, 70, 71, 26, 10, 4, 64, 8, 65, 71, 73, 4, 42, 25, 15, 8, 23, 8, 5, 4, 64, 62, 79, 70, 3, 66, 68, 1, 8, 9, 0, 8, 15, 18, 76, 71, 74, 67, 18, 70, 72, 64, 7, 17, 82, 73, 10, 13, 85, 85, 84, 76, 71, 4, 71, 66, 68, 64, 6, 75, 70, 69, 1, 81, 73, 80, 82, 1, 99, 76, 1, 70, 14, 70, 65, 23, 68, 71, 65, 19, 80, 75, 97, 23, 31, 31, 22, 9, 22, 18, 13, 13, 10, 9, 5, 76, 79, 86, 72, 69, 79, 98, 79, 78, 82, 80, 79, 72, 72, 87, 89, 81, 15, 14, 15, 8, 69, 3, 0, 77, 69, 75, 76, 89, 89, 88, 88, 64, 3, 80, 64, 65, 75, 81, 82, 83, 85, 85, 90, 101, 98, 104, 106, 109, 92, 102, 107, 80, 65, 72, 4, 6, 7, 53, 17, 16, 68, 26, 62, 49, 62, 15, 23, 16, 77, 99, 104, 110, 126, 126, 125, 68, 43, 27, 18, 9, 13, 2, 66, 68, 71, 73, 66, 8, 1, 17, 14, 5, 10, 14, 17, 18, 9, 20, 17, 0, 25, 19, 7, 0, 70, 76, 100, 103, 99 }, { 31, 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 87, 7, 14, 52, 14, 33, 68, 16, 25, 64, 77, 0, 90, 106, 64, 68, 126, 126, 125, 46, 7, 69, 16, 25, 64, 71, 9, 17, 66, 2, 66, 69, 69, 83, 74, 92, 5, 68, 70, 65, 78, 73, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 52, 6, 69, 86, 71, 82, 6, 6, 1, 10, 70, 11, 1, 64, 8, 82, 86, 84, 88, 29, 0, 15, 73, 1, 74, 66, 8, 71, 82, 74, 79, 17, 70, 4, 72, 76, 3, 69, 4, 10, 5, 8, 18, 15, 80, 67, 2, 66, 2, 73, 11, 68, 5, 19, 64, 16, 17, 8, 90, 10, 66, 85, 71, 95, 77, 64, 70, 3, 65, 64, 17, 1, 66, 7, 15, 72, 72, 1, 101, 83, 64, 95, 65, 76, 15, 0, 81, 25, 1, 65, 86, 27, 78, 92, 1, 100, 6, 19, 19, 15, 19, 14, 3, 17, 20, 80, 65, 9, 3, 1, 77, 66, 71, 74, 68, 74, 83, 69, 72, 105, 84, 78, 91, 86, 89, 21, 28, 19, 17, 17, 8, 3, 8, 67, 73, 67, 80, 91, 91, 114, 65, 67, 83, 71, 79, 82, 87, 90, 95, 99, 99, 114, 81, 101, 109, 96, 101, 0, 36, 25, 17, 6, 5, 65, 67, 71, 69, 8, 48, 32, 24, 17, 37, 14, 9, 1, 1, 0, 44, 27, 16, 8, 19, 6, 0, 66, 69, 70, 27, 11, 5, 64, 8, 65, 70, 71, 4, 42, 25, 15, 9, 23, 8, 6, 4, 0, 62, 78, 69, 3, 66, 68, 1, 8, 9, 0, 9, 15, 19, 76, 71, 75, 67, 18, 71, 73, 65, 6, 17, 83, 74, 10, 13, 85, 86, 85, 75, 70, 5, 71, 66, 68, 0, 6, 75, 69, 68, 2, 81, 73, 80, 82, 2, 99, 76, 1, 70, 15, 70, 65, 24, 69, 71, 65, 19, 81, 75, 98, 22, 30, 31, 21, 8, 22, 18, 12, 12, 10, 8, 4, 77, 80, 87, 74, 70, 81, 100, 81, 80, 83, 80, 79, 72, 72, 86, 90, 80, 13, 12, 13, 6, 72, 1, 65, 80, 71, 78, 78, 92, 91, 89, 88, 65, 2, 82, 66, 68, 77, 83, 84, 85, 87, 88, 92, 103, 100, 106, 107, 111, 94, 104, 109, 79, 64, 71, 5, 7, 8, 54, 18, 17, 68, 27, 62, 50, 62, 16, 22, 15, 80, 102, 107, 112, 126, 126, 126, 67, 43, 27, 18, 9, 13, 2, 66, 68, 70, 72, 66, 9, 2, 18, 14, 6, 11, 14, 17, 18, 9, 21, 17, 0, 24, 17, 5, 65, 71, 77, 103, 104, 100 }, { 30, 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 89, 5, 12, 52, 14, 36, 69, 17, 25, 64, 78, 0, 91, 107, 64, 70, 126, 126, 125, 49, 8, 69, 17, 25, 64, 71, 10, 17, 66, 2, 66, 69, 69, 84, 73, 92, 5, 68, 69, 66, 78, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 52, 6, 69, 85, 70, 82, 7, 8, 2, 12, 68, 12, 2, 1, 10, 81, 85, 83, 88, 30, 0, 16, 72, 1, 73, 65, 10, 71, 82, 73, 78, 17, 69, 5, 71, 75, 3, 69, 5, 10, 5, 9, 19, 16, 80, 68, 2, 66, 2, 73, 11, 69, 4, 19, 64, 16, 17, 7, 92, 10, 67, 87, 72, 96, 76, 0, 69, 4, 64, 0, 18, 2, 65, 8, 16, 72, 72, 2, 102, 82, 0, 95, 65, 76, 15, 0, 82, 26, 1, 65, 88, 28, 78, 93, 1, 101, 5, 18, 19, 14, 18, 13, 2, 16, 20, 81, 66, 9, 3, 1, 78, 67, 71, 75, 68, 75, 84, 70, 73, 107, 85, 79, 92, 87, 89, 19, 27, 17, 15, 15, 6, 0, 6, 69, 75, 69, 82, 93, 93, 116, 67, 68, 85, 74, 81, 84, 89, 93, 97, 102, 101, 117, 82, 102, 111, 97, 101, 0, 36, 26, 17, 6, 5, 65, 67, 71, 69, 9, 49, 32, 24, 17, 38, 15, 10, 1, 2, 1, 45, 28, 16, 8, 20, 7, 1, 66, 68, 70, 28, 11, 5, 64, 9, 64, 70, 70, 5, 43, 25, 15, 9, 24, 9, 7, 5, 1, 62, 78, 69, 4, 66, 68, 1, 8, 9, 1, 9, 15, 19, 76, 71, 75, 68, 19, 72, 74, 66, 5, 17, 84, 75, 9, 13, 86, 87, 85, 74, 70, 6, 70, 65, 67, 0, 7, 75, 69, 68, 2, 82, 73, 80, 81, 3, 99, 76, 2, 70, 15, 70, 65, 25, 69, 71, 65, 20, 81, 76, 99, 21, 30, 30, 20, 7, 21, 17, 11, 11, 9, 7, 3, 78, 81, 88, 75, 72, 82, 102, 82, 81, 84, 81, 80, 72, 72, 86, 90, 79, 11, 10, 11, 3, 75, 64, 67, 82, 73, 80, 80, 94, 93, 91, 89, 67, 0, 83, 68, 70, 79, 85, 86, 87, 89, 90, 94, 106, 102, 108, 109, 112, 96, 106, 110, 78, 0, 70, 5, 8, 9, 56, 19, 18, 68, 28, 62, 52, 62, 16, 20, 13, 82, 105, 110, 115, 126, 126, 126, 67, 43, 27, 18, 9, 13, 2, 66, 68, 70, 71, 65, 10, 3, 19, 15, 6, 12, 15, 18, 19, 10, 21, 18, 0, 22, 16, 3, 67, 73, 79, 105, 106, 101 }, { 28, 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 90, 4, 11, 52, 14, 38, 69, 18, 26, 64, 79, 0, 92, 109, 65, 72, 126, 126, 126, 51, 9, 69, 18, 26, 64, 71, 11, 17, 67, 2, 66, 68, 70, 84, 73, 93, 5, 68, 69, 66, 79, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 52, 6, 69, 84, 69, 82, 7, 9, 4, 14, 67, 13, 4, 2, 11, 80, 85, 82, 87, 30, 0, 17, 71, 1, 73, 65, 11, 71, 82, 73, 77, 17, 69, 5, 70, 75, 4, 69, 5, 10, 5, 9, 19, 16, 81, 68, 2, 66, 2, 73, 11, 69, 3, 19, 65, 16, 17, 7, 94, 10, 67, 89, 73, 97, 75, 1, 68, 5, 64, 0, 19, 2, 65, 9, 17, 72, 72, 2, 104, 82, 1, 95, 65, 77, 16, 0, 83, 27, 1, 65, 89, 29, 79, 94, 1, 102, 3, 17, 18, 13, 17, 12, 1, 15, 19, 83, 67, 8, 3, 1, 78, 68, 72, 76, 69, 76, 85, 71, 74, 109, 87, 80, 93, 88, 89, 17, 25, 15, 13, 13, 4, 65, 4, 72, 78, 71, 85, 96, 96, 119, 69, 70, 87, 76, 83, 87, 92, 95, 100, 105, 103, 119, 83, 104, 112, 98, 102, 0, 36, 26, 17, 6, 5, 65, 67, 71, 68, 9, 49, 32, 24, 17, 39, 15, 10, 1, 2, 1, 46, 28, 16, 8, 20, 7, 1, 65, 67, 69, 29, 12, 5, 64, 10, 0, 69, 69, 5, 43, 25, 15, 9, 25, 9, 7, 6, 1, 62, 78, 69, 4, 66, 68, 1, 8, 9, 1, 9, 15, 20, 76, 71, 75, 69, 19, 73, 75, 67, 4, 17, 86, 77, 9, 13, 87, 88, 86, 74, 69, 7, 70, 65, 67, 1, 8, 76, 69, 68, 2, 83, 73, 80, 81, 3, 100, 76, 2, 70, 16, 70, 65, 25, 69, 72, 65, 21, 82, 76, 100, 20, 29, 30, 19, 5, 20, 16, 10, 10, 8, 6, 2, 79, 82, 89, 77, 73, 84, 104, 84, 82, 85, 82, 80, 72, 72, 86, 91, 79, 8, 7, 9, 1, 78, 67, 70, 85, 75, 82, 82, 97, 95, 92, 90, 69, 65, 85, 70, 72, 82, 88, 89, 90, 91, 92, 97, 108, 104, 111, 111, 113, 98, 108, 112, 77, 1, 69, 6, 9, 9, 57, 20, 18, 68, 28, 62, 53, 62, 17, 19, 11, 85, 108, 113, 118, 126, 126, 126, 67, 43, 27, 18, 9, 13, 2, 66, 68, 69, 71, 64, 11, 3, 20, 15, 7, 12, 15, 18, 19, 10, 22, 18, 64, 21, 14, 1, 69, 75, 81, 107, 108, 102 }, { 27, 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 91, 3, 10, 52, 14, 40, 69, 19, 27, 64, 79, 1, 93, 110, 66, 74, 126, 126, 126, 54, 11, 69, 19, 27, 64, 70, 12, 18, 68, 2, 65, 67, 70, 84, 73, 93, 5, 68, 68, 66, 79, 73, 88, 14, 2, 0, 67, 9, 3, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 52, 6, 69, 83, 68, 82, 7, 10, 6, 16, 65, 15, 6, 3, 13, 79, 84, 81, 86, 31, 1, 18, 70, 1, 72, 64, 13, 71, 81, 73, 75, 18, 69, 5, 68, 75, 5, 69, 5, 11, 6, 10, 19, 16, 81, 68, 3, 66, 2, 72, 11, 69, 3, 19, 66, 16, 17, 7, 96, 10, 67, 90, 74, 97, 74, 2, 67, 6, 0, 1, 20, 3, 65, 10, 18, 72, 72, 2, 105, 81, 2, 95, 64, 77, 17, 0, 84, 28, 1, 65, 90, 30, 80, 95, 1, 102, 2, 16, 18, 12, 16, 11, 1, 15, 19, 84, 68, 8, 3, 1, 78, 68, 73, 77, 70, 77, 86, 71, 74, 110, 88, 80, 94, 89, 89, 16, 24, 14, 12, 12, 2, 67, 2, 74, 80, 73, 87, 99, 98, 122, 70, 72, 88, 78, 85, 89, 94, 97, 102, 107, 105, 121, 83, 106, 113, 98, 102, 0, 37, 26, 17, 7, 5, 65, 66, 71, 67, 9, 49, 32, 25, 17, 40, 16, 11, 2, 3, 2, 47, 29, 17, 9, 21, 8, 1, 64, 66, 68, 31, 13, 6, 0, 11, 1, 68, 68, 6, 43, 26, 16, 9, 26, 10, 8, 7, 2, 62, 77, 68, 5, 66, 68, 1, 9, 10, 1, 9, 16, 21, 76, 71, 75, 69, 19, 74, 75, 68, 4, 17, 87, 78, 9, 13, 87, 89, 87, 73, 68, 8, 70, 64, 67, 2, 9, 76, 69, 68, 3, 83, 73, 80, 81, 4, 100, 76, 2, 70, 17, 70, 65, 26, 69, 72, 65, 22, 83, 76, 100, 19, 29, 30, 19, 4, 19, 15, 9, 10, 7, 5, 2, 79, 83, 90, 78, 74, 86, 106, 85, 83, 85, 82, 80, 71, 71, 86, 92, 78, 6, 5, 7, 64, 80, 69, 72, 87, 77, 84, 84, 99, 97, 93, 91, 71, 66, 86, 72, 74, 84, 90, 91, 92, 93, 94, 99, 110, 105, 113, 113, 114, 100, 110, 114, 76, 3, 67, 7, 10, 10, 59, 21, 19, 68, 29, 62, 54, 62, 18, 18, 10, 87, 111, 115, 121, 126, 126, 126, 67, 43, 27, 18, 9, 14, 2, 66, 68, 68, 70, 0, 12, 4, 22, 16, 8, 13, 16, 19, 20, 10, 23, 18, 64, 20, 13, 0, 70, 77, 83, 109, 110, 102 }, { 26, 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 93, 1, 8, 52, 14, 43, 70, 20, 27, 64, 80, 1, 94, 111, 66, 76, 126, 126, 126, 57, 12, 69, 20, 27, 64, 70, 13, 18, 68, 2, 65, 67, 70, 85, 72, 93, 5, 68, 67, 67, 79, 73, 88, 15, 3, 0, 66, 10, 3, 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 52, 6, 69, 82, 67, 82, 8, 12, 7, 18, 64, 16, 7, 5, 15, 78, 83, 80, 86, 32, 1, 19, 69, 1, 72, 0, 15, 71, 81, 72, 74, 18, 68, 6, 67, 74, 5, 69, 6, 11, 6, 11, 20, 17, 82, 69, 3, 66, 2, 72, 11, 70, 2, 19, 66, 16, 17, 6, 98, 10, 68, 92, 75, 98, 73, 3, 66, 7, 1, 2, 21, 4, 64, 11, 19, 72, 72, 3, 106, 81, 3, 95, 64, 77, 17, 0, 85, 29, 1, 65, 92, 31, 80, 96, 1, 103, 0, 15, 17, 11, 15, 10, 0, 14, 19, 85, 69, 8, 3, 1, 79, 69, 73, 78, 70, 78, 87, 72, 75, 112, 89, 81, 95, 90, 89, 14, 23, 12, 10, 10, 0, 70, 0, 77, 82, 75, 90, 101, 100, 124, 72, 73, 90, 81, 87, 91, 96, 100, 104, 110, 107, 124, 84, 107, 115, 99, 102, 0, 37, 27, 17, 7, 5, 65, 66, 71, 67, 10, 50, 32, 25, 17, 41, 16, 12, 2, 4, 3, 48, 30, 17, 9, 22, 8, 2, 64, 65, 68, 32, 13, 6, 0, 12, 2, 68, 67, 6, 44, 26, 16, 9, 27, 11, 9, 8, 3, 62, 77, 68, 5, 66, 68, 1, 9, 10, 2, 9, 16, 21, 76, 71, 75, 70, 20, 75, 76, 69, 3, 17, 88, 79, 8, 13, 88, 90, 87, 72, 68, 9, 69, 64, 66, 2, 10, 76, 69, 68, 3, 84, 73, 80, 80, 5, 100, 76, 3, 70, 17, 70, 65, 27, 69, 72, 65, 23, 83, 77, 101, 18, 29, 29, 18, 3, 18, 14, 8, 9, 6, 4, 1, 80, 84, 91, 80, 76, 87, 108, 86, 84, 86, 83, 81, 71, 71, 86, 92, 77, 4, 3, 5, 67, 83, 71, 74, 90, 79, 86, 86, 101, 99, 95, 92, 73, 68, 87, 74, 76, 86, 92, 93, 94, 95, 96, 101, 113, 107, 115, 115, 115, 102, 112, 115, 75, 4, 66, 7, 11, 11, 61, 22, 20, 68, 30, 62, 56, 62, 18, 16, 8, 90, 114, 118, 124, 126, 126, 126, 67, 43, 27, 18, 9, 14, 2, 66, 68, 68, 69, 1, 13, 5, 23, 17, 8, 14, 17, 20, 21, 11, 23, 19, 64, 18, 11, 65, 72, 79, 85, 111, 112, 103 }, { 25, 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 94, 0, 7, 52, 14, 45, 70, 20, 28, 64, 81, 1, 95, 112, 67, 77, 126, 126, 126, 60, 13, 69, 20, 28, 64, 69, 14, 19, 69, 3, 64, 66, 71, 85, 72, 93, 5, 67, 67, 67, 79, 72, 88, 15, 3, 0, 66, 10, 3, 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 52, 6, 69, 81, 67, 82, 8, 13, 9, 20, 1, 17, 9, 6, 17, 77, 82, 79, 85, 33, 1, 20, 69, 2, 71, 0, 17, 71, 80, 72, 73, 18, 68, 6, 65, 74, 6, 68, 6, 11, 6, 12, 20, 17, 82, 69, 3, 66, 2, 72, 11, 70, 2, 19, 67, 16, 17, 6, 100, 10, 68, 94, 76, 99, 73, 4, 66, 7, 2, 2, 22, 4, 64, 11, 21, 72, 72, 3, 107, 80, 4, 95, 64, 77, 18, 1, 86, 29, 1, 65, 93, 32, 81, 97, 1, 104, 64, 14, 17, 11, 14, 10, 0, 13, 18, 86, 70, 8, 3, 1, 79, 70, 74, 79, 71, 78, 88, 73, 75, 114, 90, 81, 96, 91, 89, 13, 21, 11, 8, 8, 65, 72, 65, 79, 85, 77, 92, 104, 103, 126, 74, 75, 91, 83, 90, 94, 99, 102, 107, 112, 110, 126, 85, 109, 116, 100, 102, 1, 38, 27, 18, 7, 5, 65, 66, 71, 66, 10, 50, 33, 25, 17, 42, 17, 12, 3, 4, 4, 49, 30, 18, 9, 23, 9, 2, 0, 64, 67, 33, 14, 7, 0, 12, 2, 67, 65, 7, 44, 26, 16, 10, 27, 11, 10, 8, 4, 62, 76, 67, 6, 66, 68, 1, 9, 10, 2, 10, 16, 22, 76, 71, 76, 70, 20, 76, 77, 70, 2, 17, 89, 80, 8, 13, 88, 91, 88, 71, 67, 10, 69, 0, 66, 3, 10, 76, 68, 67, 4, 84, 73, 80, 80, 6, 100, 76, 3, 70, 18, 70, 65, 28, 70, 72, 65, 23, 84, 77, 102, 17, 28, 29, 17, 2, 18, 14, 7, 8, 6, 3, 0, 81, 85, 92, 81, 77, 89, 110, 88, 86, 87, 83, 81, 71, 71, 85, 93, 76, 2, 1, 3, 69, 86, 73, 76, 92, 81, 89, 88, 104, 101, 96, 92, 74, 69, 89, 76, 79, 88, 94, 95, 96, 97, 99, 103, 115, 109, 117, 116, 117, 104, 114, 117, 74, 5, 65, 8, 12, 12, 62, 23, 21, 68, 31, 62, 57, 62, 19, 15, 7, 92, 117, 121, 126, 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, 67, 68, 1, 14, 6, 24, 17, 9, 15, 17, 20, 21, 11, 24, 19, 64, 17, 10, 67, 74, 80, 86, 114, 113, 104 }, { 23, 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 96, 65, 6, 52, 14, 47, 70, 21, 29, 64, 82, 1, 96, 113, 67, 79, 126, 126, 126, 62, 14, 69, 21, 29, 64, 69, 15, 19, 69, 3, 64, 65, 71, 86, 72, 93, 5, 67, 66, 67, 80, 72, 88, 16, 3, 0, 66, 11, 3, 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 52, 6, 69, 80, 66, 82, 9, 14, 11, 22, 2, 18, 11, 7, 19, 76, 82, 78, 84, 33, 1, 21, 68, 2, 71, 1, 19, 71, 80, 71, 72, 18, 68, 7, 64, 73, 7, 68, 6, 11, 6, 12, 21, 18, 83, 69, 3, 66, 2, 72, 11, 71, 1, 19, 68, 16, 17, 6, 102, 10, 69, 96, 77, 100, 72, 5, 65, 8, 2, 3, 23, 5, 0, 12, 22, 72, 72, 3, 108, 80, 5, 95, 64, 77, 19, 1, 87, 30, 1, 65, 94, 33, 81, 98, 1, 105, 66, 13, 16, 10, 13, 9, 64, 12, 18, 88, 71, 7, 3, 1, 80, 71, 75, 80, 72, 79, 89, 74, 76, 116, 91, 82, 97, 92, 89, 11, 20, 9, 6, 6, 67, 74, 67, 82, 87, 79, 95, 106, 105, 126, 76, 77, 93, 85, 92, 96, 101, 104, 109, 115, 112, 126, 86, 111, 117, 101, 102, 1, 38, 27, 18, 7, 5, 65, 66, 71, 66, 10, 50, 33, 25, 17, 43, 17, 13, 3, 5, 4, 50, 31, 18, 9, 23, 9, 3, 0, 0, 66, 34, 15, 7, 0, 13, 3, 66, 64, 7, 44, 26, 16, 10, 28, 12, 10, 9, 5, 62, 76, 67, 6, 66, 68, 1, 9, 10, 2, 10, 16, 22, 76, 71, 76, 71, 21, 77, 78, 71, 1, 17, 91, 81, 8, 13, 89, 92, 88, 71, 66, 11, 68, 0, 65, 3, 11, 77, 68, 67, 4, 85, 73, 80, 79, 7, 100, 76, 3, 70, 19, 70, 65, 29, 70, 73, 65, 24, 85, 77, 103, 16, 28, 28, 16, 1, 17, 13, 6, 7, 5, 2, 64, 82, 86, 93, 83, 78, 90, 112, 89, 87, 88, 84, 82, 71, 71, 85, 93, 76, 64, 65, 1, 71, 89, 75, 78, 95, 83, 91, 90, 106, 103, 98, 93, 76, 71, 90, 78, 81, 90, 96, 98, 98, 99, 101, 105, 118, 111, 119, 118, 118, 106, 116, 118, 73, 6, 64, 9, 13, 13, 62, 24, 22, 68, 32, 62, 59, 62, 20, 13, 5, 95, 120, 124, 126, 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, 67, 67, 2, 15, 6, 25, 18, 9, 16, 18, 21, 22, 11, 24, 19, 64, 16, 8, 69, 76, 82, 88, 116, 115, 105 }, { 22, 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 66, 4, 52, 14, 50, 71, 22, 29, 64, 82, 2, 97, 114, 68, 81, 126, 126, 126, 62, 16, 69, 22, 29, 64, 69, 16, 19, 70, 3, 64, 65, 71, 86, 71, 93, 5, 67, 65, 68, 80, 72, 88, 16, 4, 1, 65, 11, 3, 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 52, 6, 69, 79, 65, 82, 9, 16, 12, 24, 4, 19, 12, 9, 21, 75, 81, 77, 84, 34, 1, 22, 67, 2, 70, 2, 21, 71, 80, 71, 70, 19, 67, 7, 0, 73, 7, 68, 7, 12, 7, 13, 21, 18, 83, 70, 3, 66, 2, 72, 11, 71, 0, 19, 68, 16, 17, 5, 104, 10, 69, 97, 78, 101, 71, 6, 64, 9, 3, 4, 24, 6, 0, 13, 23, 72, 72, 4, 109, 79, 6, 95, 0, 77, 19, 1, 88, 31, 1, 65, 96, 34, 82, 99, 1, 105, 67, 12, 16, 9, 12, 8, 65, 12, 18, 89, 72, 7, 3, 1, 80, 71, 75, 81, 72, 80, 90, 74, 77, 118, 92, 83, 98, 93, 89, 9, 19, 7, 4, 5, 69, 77, 69, 84, 89, 81, 97, 109, 107, 126, 78, 78, 95, 88, 94, 98, 103, 107, 111, 118, 114, 126, 87, 112, 119, 101, 102, 1, 38, 28, 18, 7, 5, 65, 66, 71, 65, 11, 51, 33, 25, 17, 44, 18, 14, 3, 6, 5, 51, 32, 18, 10, 24, 10, 3, 1, 1, 66, 36, 15, 7, 1, 14, 4, 66, 0, 8, 45, 27, 16, 10, 29, 13, 11, 10, 6, 62, 76, 67, 7, 66, 68, 1, 9, 10, 3, 10, 17, 23, 76, 71, 76, 72, 21, 78, 79, 72, 1, 17, 92, 82, 7, 13, 90, 93, 89, 70, 66, 12, 68, 1, 65, 4, 12, 77, 68, 67, 4, 86, 73, 80, 79, 8, 100, 76, 4, 70, 19, 70, 65, 30, 70, 73, 65, 25, 85, 78, 103, 15, 28, 28, 16, 0, 16, 12, 5, 7, 4, 1, 65, 83, 87, 94, 84, 80, 92, 114, 90, 88, 89, 85, 82, 71, 70, 85, 94, 75, 66, 67, 64, 74, 91, 77, 80, 97, 85, 93, 92, 108, 105, 99, 94, 78, 73, 91, 80, 83, 92, 98, 100, 100, 101, 103, 107, 120, 113, 121, 120, 119, 108, 118, 120, 72, 8, 0, 9, 14, 14, 62, 25, 23, 68, 33, 62, 60, 62, 20, 12, 3, 97, 123, 126, 126, 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, 66, 66, 3, 16, 7, 26, 19, 10, 17, 19, 22, 23, 12, 25, 20, 64, 14, 7, 70, 77, 84, 90, 118, 117, 106 }, { 21, 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 68, 3, 52, 14, 52, 71, 23, 30, 64, 83, 2, 98, 115, 68, 83, 126, 126, 126, 62, 17, 69, 23, 30, 64, 68, 17, 20, 70, 3, 0, 64, 72, 87, 71, 93, 5, 67, 65, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 52, 6, 69, 78, 64, 82, 10, 17, 14, 26, 5, 20, 14, 10, 23, 74, 80, 76, 83, 35, 1, 23, 66, 2, 70, 2, 23, 71, 79, 70, 69, 19, 67, 8, 2, 72, 8, 68, 7, 12, 7, 14, 22, 19, 84, 70, 3, 66, 2, 72, 11, 72, 0, 19, 69, 16, 17, 5, 106, 10, 70, 99, 79, 102, 70, 7, 0, 10, 4, 4, 25, 6, 1, 14, 24, 72, 72, 4, 110, 79, 7, 95, 0, 77, 20, 1, 89, 32, 1, 65, 97, 35, 82, 100, 1, 106, 69, 11, 15, 8, 11, 7, 65, 11, 17, 90, 73, 7, 3, 1, 81, 72, 76, 82, 73, 81, 91, 75, 77, 120, 93, 83, 99, 94, 89, 8, 17, 6, 2, 3, 71, 79, 71, 87, 92, 83, 100, 111, 110, 126, 80, 80, 96, 90, 96, 101, 106, 109, 114, 120, 116, 126, 88, 114, 120, 102, 102, 1, 39, 28, 18, 7, 5, 65, 66, 71, 65, 11, 51, 33, 25, 17, 45, 18, 14, 4, 6, 6, 52, 32, 19, 10, 25, 10, 4, 1, 2, 65, 37, 16, 8, 1, 15, 5, 65, 1, 8, 45, 27, 16, 10, 30, 13, 12, 11, 7, 62, 75, 66, 7, 66, 68, 1, 9, 10, 3, 10, 17, 23, 76, 71, 76, 72, 22, 79, 80, 73, 0, 17, 93, 83, 7, 13, 90, 94, 89, 69, 65, 13, 67, 1, 64, 4, 13, 77, 68, 67, 5, 86, 73, 80, 78, 9, 100, 76, 4, 70, 20, 70, 65, 31, 70, 73, 65, 26, 86, 78, 104, 14, 27, 27, 15, 64, 15, 11, 4, 6, 3, 0, 66, 84, 88, 95, 86, 81, 93, 116, 92, 89, 90, 85, 83, 71, 70, 85, 94, 74, 68, 69, 66, 76, 94, 79, 82, 100, 87, 95, 94, 111, 107, 101, 95, 80, 74, 93, 82, 85, 94, 100, 102, 102, 103, 105, 109, 123, 115, 123, 122, 120, 110, 120, 121, 71, 9, 1, 10, 15, 15, 62, 26, 24, 68, 34, 62, 62, 62, 21, 10, 2, 100, 126, 126, 126, 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, 66, 65, 4, 17, 8, 27, 19, 10, 18, 19, 22, 23, 12, 25, 20, 64, 13, 5, 72, 79, 86, 92, 120, 119, 107 }, { 20, 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 100, 69, 2, 52, 14, 54, 71, 24, 31, 64, 84, 2, 99, 116, 69, 85, 126, 126, 126, 62, 18, 69, 24, 31, 64, 68, 18, 20, 71, 3, 0, 0, 72, 87, 71, 93, 5, 67, 64, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 52, 6, 69, 77, 0, 82, 10, 18, 16, 28, 7, 21, 16, 11, 25, 73, 79, 75, 82, 36, 1, 24, 65, 2, 69, 3, 25, 71, 79, 70, 68, 19, 67, 8, 3, 72, 9, 68, 7, 12, 7, 15, 22, 19, 84, 70, 3, 66, 2, 72, 11, 72, 64, 19, 70, 16, 17, 5, 108, 10, 70, 101, 80, 103, 69, 8, 1, 11, 5, 5, 26, 7, 1, 15, 25, 72, 72, 4, 111, 78, 8, 95, 0, 77, 21, 1, 90, 33, 1, 65, 98, 36, 83, 101, 1, 107, 70, 10, 15, 7, 10, 6, 66, 10, 17, 91, 74, 7, 3, 1, 81, 73, 77, 83, 74, 82, 92, 76, 78, 122, 94, 84, 100, 95, 89, 6, 16, 4, 0, 1, 73, 81, 73, 89, 94, 85, 102, 114, 112, 126, 82, 82, 98, 92, 98, 103, 108, 111, 116, 123, 118, 126, 89, 116, 121, 103, 102, 1, 39, 28, 18, 7, 5, 65, 66, 71, 64, 11, 51, 33, 25, 17, 46, 19, 15, 4, 7, 7, 53, 33, 19, 10, 26, 11, 4, 2, 3, 64, 38, 17, 8, 1, 16, 6, 64, 2, 9, 45, 27, 16, 10, 31, 14, 13, 12, 8, 62, 75, 66, 8, 66, 68, 1, 9, 10, 3, 10, 17, 24, 76, 71, 76, 73, 22, 80, 81, 74, 64, 17, 94, 84, 7, 13, 91, 95, 90, 68, 64, 14, 67, 2, 64, 5, 14, 77, 68, 67, 5, 87, 73, 80, 78, 10, 100, 76, 4, 70, 21, 70, 65, 32, 70, 73, 65, 27, 87, 78, 105, 13, 27, 27, 14, 65, 14, 10, 3, 5, 2, 64, 67, 85, 89, 96, 87, 82, 95, 118, 93, 90, 91, 86, 83, 71, 70, 85, 95, 73, 70, 71, 68, 78, 97, 81, 84, 102, 89, 97, 96, 113, 109, 102, 96, 82, 76, 94, 84, 87, 96, 102, 104, 104, 105, 107, 111, 125, 117, 125, 124, 121, 112, 122, 123, 70, 10, 2, 11, 16, 16, 62, 27, 25, 68, 35, 62, 62, 62, 22, 9, 0, 102, 126, 126, 126, 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, 65, 64, 5, 18, 9, 28, 20, 11, 19, 20, 23, 24, 12, 26, 20, 64, 12, 4, 74, 81, 88, 94, 122, 121, 108 }, { 18, 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 102, 71, 0, 51, 14, 56, 72, 24, 31, 65, 85, 2, 101, 118, 70, 87, 126, 126, 126, 62, 19, 70, 24, 31, 65, 68, 19, 20, 72, 3, 0, 0, 73, 88, 71, 94, 5, 67, 64, 69, 81, 72, 88, 17, 4, 1, 65, 12, 2, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 52, 5, 69, 76, 0, 82, 10, 19, 17, 29, 8, 22, 17, 12, 26, 72, 79, 74, 82, 36, 1, 24, 65, 2, 69, 3, 26, 71, 79, 70, 67, 19, 67, 8, 4, 72, 9, 68, 7, 12, 7, 15, 22, 19, 85, 71, 3, 67, 2, 72, 10, 73, 65, 19, 71, 15, 17, 4, 110, 9, 71, 103, 81, 104, 69, 8, 1, 11, 5, 5, 27, 7, 1, 15, 26, 73, 72, 4, 113, 78, 8, 95, 0, 78, 21, 1, 91, 33, 1, 65, 100, 36, 84, 102, 1, 108, 72, 9, 14, 6, 9, 5, 67, 9, 16, 93, 75, 6, 2, 1, 82, 74, 78, 84, 75, 83, 93, 77, 79, 124, 96, 85, 102, 97, 89, 4, 14, 2, 65, 64, 76, 84, 76, 92, 97, 88, 105, 117, 115, 126, 84, 84, 100, 95, 101, 106, 111, 114, 119, 126, 121, 126, 90, 118, 123, 104, 103, 1, 39, 28, 18, 7, 5, 66, 66, 71, 64, 11, 51, 33, 25, 17, 47, 19, 15, 4, 7, 7, 53, 33, 19, 10, 26, 11, 4, 2, 4, 64, 39, 17, 8, 1, 16, 6, 64, 3, 9, 45, 27, 16, 10, 31, 14, 13, 12, 8, 62, 75, 66, 8, 66, 68, 1, 9, 10, 3, 10, 17, 24, 76, 71, 77, 74, 22, 81, 82, 75, 65, 16, 96, 86, 6, 12, 92, 97, 91, 68, 64, 15, 67, 2, 64, 5, 14, 78, 68, 67, 5, 88, 73, 80, 78, 10, 101, 76, 4, 70, 21, 71, 65, 32, 71, 74, 65, 27, 88, 79, 106, 12, 26, 26, 13, 67, 13, 9, 2, 4, 1, 65, 68, 86, 91, 98, 89, 84, 97, 120, 95, 92, 92, 87, 84, 71, 70, 85, 96, 73, 73, 74, 70, 81, 100, 84, 87, 105, 92, 100, 99, 116, 112, 104, 97, 84, 78, 96, 86, 90, 99, 105, 107, 107, 107, 110, 114, 126, 119, 126, 126, 123, 114, 124, 125, 69, 11, 3, 11, 16, 16, 62, 27, 25, 68, 35, 62, 62, 62, 22, 7, 65, 105, 126, 126, 126, 126, 126, 126, 66, 43, 26, 17, 9, 14, 2, 67, 68, 65, 64, 5, 18, 9, 29, 20, 11, 19, 20, 23, 24, 12, 26, 20, 65, 10, 2, 76, 83, 90, 96, 125, 123, 109 }, { 17, 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 103, 72, 64, 51, 14, 59, 72, 25, 32, 65, 85, 3, 102, 119, 70, 88, 126, 126, 126, 62, 21, 70, 25, 32, 65, 67, 21, 21, 72, 4, 1, 1, 73, 88, 70, 94, 5, 66, 0, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 52, 5, 69, 74, 1, 82, 11, 21, 19, 31, 10, 24, 19, 14, 28, 70, 78, 73, 81, 37, 2, 25, 64, 3, 68, 4, 28, 70, 78, 69, 65, 20, 66, 9, 6, 71, 10, 67, 8, 13, 8, 16, 23, 20, 85, 71, 4, 67, 2, 71, 10, 73, 65, 19, 71, 15, 17, 4, 111, 9, 71, 104, 82, 104, 68, 9, 2, 12, 6, 6, 28, 8, 2, 16, 28, 73, 72, 5, 114, 77, 9, 95, 1, 78, 22, 2, 91, 34, 1, 65, 101, 37, 84, 103, 1, 108, 73, 9, 14, 6, 9, 5, 67, 9, 16, 94, 75, 6, 2, 1, 82, 74, 78, 84, 75, 83, 94, 77, 79, 125, 97, 85, 103, 98, 89, 3, 13, 1, 66, 65, 78, 86, 78, 94, 99, 90, 107, 119, 117, 126, 85, 85, 101, 97, 103, 108, 113, 116, 121, 126, 123, 126, 90, 119, 124, 104, 103, 2, 40, 29, 19, 8, 5, 66, 65, 70, 0, 12, 52, 34, 26, 17, 48, 20, 16, 5, 8, 8, 54, 34, 20, 11, 27, 12, 5, 3, 6, 0, 41, 18, 9, 2, 17, 7, 0, 5, 10, 46, 28, 17, 11, 32, 15, 14, 13, 9, 62, 74, 65, 9, 66, 67, 1, 10, 11, 4, 11, 18, 25, 75, 71, 77, 74, 23, 81, 82, 76, 65, 16, 97, 87, 6, 12, 92, 98, 91, 67, 0, 16, 66, 3, 0, 6, 15, 78, 67, 66, 6, 88, 72, 79, 77, 11, 101, 76, 5, 70, 22, 71, 65, 33, 71, 74, 64, 28, 88, 79, 106, 12, 26, 26, 13, 68, 13, 9, 2, 4, 1, 65, 68, 86, 92, 99, 90, 85, 98, 121, 96, 93, 92, 87, 84, 70, 69, 84, 96, 72, 75, 76, 72, 83, 102, 86, 89, 107, 94, 102, 101, 118, 114, 105, 97, 85, 79, 97, 87, 92, 101, 107, 109, 109, 109, 112, 116, 126, 120, 126, 126, 124, 115, 125, 126, 67, 13, 5, 12, 17, 17, 62, 28, 26, 68, 36, 62, 62, 62, 23, 6, 66, 107, 126, 126, 126, 126, 126, 126, 65, 44, 26, 17, 9, 15, 2, 67, 68, 64, 0, 6, 19, 10, 31, 21, 12, 20, 21, 24, 25, 13, 27, 21, 65, 9, 1, 77, 84, 91, 97, 126, 124, 109 }, { 16, 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 104, 73, 65, 51, 14, 61, 72, 26, 33, 65, 86, 3, 103, 120, 71, 90, 126, 126, 126, 62, 22, 70, 26, 33, 65, 67, 22, 21, 73, 4, 1, 2, 73, 88, 70, 94, 5, 66, 1, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 52, 5, 69, 73, 2, 82, 11, 22, 21, 33, 11, 25, 21, 15, 30, 69, 77, 72, 80, 38, 2, 26, 0, 3, 68, 5, 30, 70, 78, 69, 64, 20, 66, 9, 7, 71, 11, 67, 8, 13, 8, 17, 23, 20, 86, 71, 4, 67, 2, 71, 10, 73, 66, 19, 72, 15, 17, 4, 113, 9, 71, 106, 83, 105, 67, 10, 3, 13, 7, 7, 29, 9, 2, 17, 29, 73, 72, 5, 115, 77, 10, 95, 1, 78, 23, 2, 92, 35, 1, 65, 102, 38, 85, 104, 1, 109, 75, 8, 13, 5, 8, 4, 68, 8, 16, 95, 76, 6, 2, 1, 82, 75, 79, 85, 76, 84, 95, 78, 80, 126, 98, 86, 104, 99, 89, 1, 12, 64, 68, 67, 80, 88, 80, 97, 101, 92, 110, 122, 119, 126, 87, 87, 103, 99, 105, 110, 115, 118, 123, 126, 125, 126, 91, 121, 125, 105, 103, 2, 40, 29, 19, 8, 5, 66, 65, 70, 1, 12, 52, 34, 26, 17, 49, 20, 17, 5, 9, 9, 55, 35, 20, 11, 28, 12, 5, 4, 7, 1, 42, 19, 9, 2, 18, 8, 1, 6, 10, 46, 28, 17, 11, 33, 16, 15, 14, 10, 62, 74, 65, 9, 66, 67, 1, 10, 11, 4, 11, 18, 26, 75, 71, 77, 75, 23, 82, 83, 77, 66, 16, 98, 88, 6, 12, 93, 99, 92, 66, 1, 17, 66, 3, 0, 7, 16, 78, 67, 66, 6, 89, 72, 79, 77, 12, 101, 76, 5, 70, 23, 71, 65, 34, 71, 74, 64, 29, 89, 79, 107, 11, 26, 26, 12, 69, 12, 8, 1, 3, 0, 66, 69, 87, 93, 100, 92, 86, 100, 123, 97, 94, 93, 88, 84, 70, 69, 84, 97, 71, 77, 78, 74, 85, 105, 88, 91, 110, 96, 104, 103, 120, 116, 106, 98, 87, 81, 98, 89, 94, 103, 109, 111, 111, 111, 114, 118, 126, 122, 126, 126, 125, 117, 126, 126, 66, 14, 6, 13, 18, 18, 62, 29, 27, 68, 37, 62, 62, 62, 24, 5, 68, 110, 126, 126, 126, 126, 126, 126, 65, 44, 26, 17, 9, 15, 2, 67, 68, 0, 1, 7, 20, 11, 32, 22, 13, 21, 22, 25, 26, 13, 28, 21, 65, 8, 64, 79, 86, 93, 99, 126, 126, 110 }, { 15, 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 106, 75, 66, 51, 14, 62, 72, 27, 34, 65, 87, 3, 104, 121, 71, 92, 126, 126, 126, 62, 23, 70, 27, 34, 65, 66, 23, 22, 73, 4, 2, 3, 74, 89, 70, 94, 5, 66, 1, 69, 81, 71, 88, 19, 5, 2, 64, 14, 2, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 52, 5, 69, 72, 3, 82, 12, 23, 23, 35, 13, 26, 23, 16, 32, 68, 76, 71, 79, 39, 2, 27, 1, 3, 67, 5, 32, 70, 77, 68, 0, 20, 66, 10, 9, 70, 12, 67, 8, 13, 8, 18, 24, 21, 86, 71, 4, 67, 2, 71, 10, 74, 66, 19, 73, 15, 17, 4, 115, 9, 72, 108, 84, 106, 66, 11, 4, 14, 8, 7, 30, 9, 3, 18, 30, 73, 72, 5, 116, 76, 11, 95, 1, 78, 24, 2, 93, 36, 1, 65, 103, 39, 85, 105, 1, 110, 76, 7, 13, 4, 7, 3, 68, 7, 15, 96, 77, 6, 2, 1, 83, 76, 80, 86, 77, 85, 96, 79, 80, 126, 99, 86, 105, 100, 89, 0, 10, 65, 70, 69, 82, 90, 82, 99, 104, 94, 112, 124, 122, 126, 89, 89, 104, 101, 107, 113, 118, 120, 126, 126, 126, 126, 92, 123, 126, 106, 103, 2, 41, 29, 19, 8, 5, 66, 65, 70, 1, 12, 52, 34, 26, 17, 50, 21, 17, 6, 9, 10, 56, 35, 21, 11, 29, 13, 6, 4, 8, 2, 43, 20, 10, 2, 19, 9, 2, 7, 11, 46, 28, 17, 11, 34, 16, 16, 15, 11, 62, 73, 64, 10, 66, 67, 1, 10, 11, 4, 11, 18, 26, 75, 71, 77, 75, 24, 83, 84, 78, 67, 16, 99, 89, 6, 12, 93, 100, 92, 65, 2, 18, 65, 4, 1, 7, 17, 78, 67, 66, 7, 89, 72, 79, 76, 13, 101, 76, 5, 70, 24, 71, 65, 35, 71, 74, 64, 30, 90, 79, 108, 10, 25, 25, 11, 70, 11, 7, 0, 2, 64, 67, 70, 88, 94, 101, 93, 87, 101, 125, 99, 95, 94, 88, 85, 70, 69, 84, 97, 70, 79, 80, 76, 87, 108, 90, 93, 112, 98, 106, 105, 123, 118, 108, 99, 89, 82, 100, 91, 96, 105, 111, 113, 113, 113, 116, 120, 126, 124, 126, 126, 126, 119, 126, 126, 65, 15, 7, 14, 19, 19, 62, 30, 28, 68, 38, 62, 62, 62, 25, 3, 69, 112, 126, 126, 126, 126, 126, 126, 65, 44, 26, 17, 9, 15, 2, 67, 68, 0, 2, 8, 21, 12, 33, 22, 13, 22, 22, 25, 26, 13, 28, 21, 65, 7, 65, 81, 88, 95, 101, 126, 126, 111 }, }, }; ================================================ FILE: dependencies/ih264d/common/ih264_cabac_tables.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ****************************************************************************** * @file ih264_cabac_tables.h * * @brief * This file contains enumerations, macros and extern declarations of H264 * cabac tables * * @author * Ittiam * * @remarks * none ****************************************************************************** */ #ifndef IH264_CABAC_TABLES_H_ #define IH264_CABAC_TABLES_H_ /*****************************************************************************/ /* Constant Macros */ /*****************************************************************************/ /** ****************************************************************************** * @brief maximum range of cabac_init_idc (0-2) + 1 for ISLICE ****************************************************************************** */ #define NUM_CAB_INIT_IDC_PLUS_ONE 4 /** ****************************************************************************** * @brief max range of qps in H264 (0-51) ****************************************************************************** */ #define QP_RANGE 52 /** ****************************************************************************** * @brief max range of cabac contexts in H264 (0-459) ****************************************************************************** */ #define NUM_CABAC_CTXTS 460 /** Macros for Cabac checks */ /** MbType */ /** |x|x|I_PCM|SKIP| |S|Inter/Intra|P/B|NON-BD16x16/BD16x16,I16x16/I4x4| */ #define CAB_INTRA 0x00 /* 0000 00xx */ #define CAB_INTER 0x04 /* 0000 01xx */ #define CAB_I4x4 0x00 /* 0000 00x0 */ #define CAB_I16x16 0x01 /* 0000 00x1 */ #define CAB_BD16x16 0x04 /* 0000 0100 */ #define CAB_NON_BD16x16 0x05 /* 0000 0101 */ #define CAB_P 0x07 /* 0000 0111 */ #define CAB_SI4x4 0x08 /* 0000 10x0 */ #define CAB_SI16x16 0x09 /* 0000 10x1 */ #define CAB_SKIP_MASK 0x10 /* 0001 0000 */ #define CAB_SKIP 0x10 /* 0001 0000 */ #define CAB_P_SKIP 0x16 /* 0001 x11x */ #define CAB_B_SKIP 0x14 /* 0001 x100 */ #define CAB_BD16x16_MASK 0x07 /* 0000 0111 */ #define CAB_INTRA_MASK 0x04 /* 0000 0100 */ #define CAB_I_PCM 0x20 /* 001x xxxx */ /** ****************************************************************************** * @enum ctxBlockCat ****************************************************************************** */ typedef enum { LUMA_DC_CTXCAT = 0, LUMA_AC_CTXCAT = 1, LUMA_4X4_CTXCAT = 2, CHROMA_DC_CTXCAT = 3, CHROMA_AC_CTXCAT = 4, LUMA_8X8_CTXCAT = 5, NUM_CTX_CAT = 6 } CTX_BLOCK_CAT; /** ****************************************************************************** * @enum ctxIdxOffset ****************************************************************************** */ typedef enum { MB_TYPE_SI_SLICE = 0, MB_TYPE_I_SLICE = 3, MB_SKIP_FLAG_P_SLICE = 11, MB_TYPE_P_SLICE = 14, SUB_MB_TYPE_P_SLICE = 21, MB_SKIP_FLAG_B_SLICE = 24, MB_TYPE_B_SLICE = 27, SUB_MB_TYPE_B_SLICE = 36, MVD_X = 40, MVD_Y = 47, REF_IDX = 54, MB_QP_DELTA = 60, INTRA_CHROMA_PRED_MODE = 64, PREV_INTRA4X4_PRED_MODE_FLAG = 68, REM_INTRA4X4_PRED_MODE = 69, MB_FIELD_DECODING_FLAG = 70, CBP_LUMA = 73, CBP_CHROMA = 77, CBF = 85, SIGNIFICANT_COEFF_FLAG_FRAME = 105, SIGNIFICANT_COEFF_FLAG_FLD = 277, LAST_SIGNIFICANT_COEFF_FLAG_FRAME = 166, LAST_SIGNIFICANT_COEFF_FLAG_FLD = 338, COEFF_ABS_LEVEL_MINUS1 = 227, /* High profile related Syntax element CABAC offsets */ TRANSFORM_SIZE_8X8_FLAG = 399, SIGNIFICANT_COEFF_FLAG_8X8_FRAME = 402, LAST_SIGNIFICANT_COEFF_FLAG_8X8_FRAME = 417, COEFF_ABS_LEVEL_MINUS1_8X8 = 426, SIGNIFICANT_COEFF_FLAG_8X8_FIELD = 436, LAST_SIGNIFICANT_COEFF_FLAG_8X8_FIELD = 451 } cabac_table_num_t; /** ****************************************************************************** * @enum ctxIdxOffset ****************************************************************************** */ typedef enum { SIG_COEFF_CTXT_CAT_0_OFFSET = 0, SIG_COEFF_CTXT_CAT_1_OFFSET = 15, SIG_COEFF_CTXT_CAT_2_OFFSET = 29, SIG_COEFF_CTXT_CAT_3_OFFSET = 44, SIG_COEFF_CTXT_CAT_4_OFFSET = 47, SIG_COEFF_CTXT_CAT_5_OFFSET = 0, COEFF_ABS_LEVEL_CAT_0_OFFSET = 0, COEFF_ABS_LEVEL_CAT_1_OFFSET = 10, COEFF_ABS_LEVEL_CAT_2_OFFSET = 20, COEFF_ABS_LEVEL_CAT_3_OFFSET = 30, COEFF_ABS_LEVEL_CAT_4_OFFSET = 39, COEFF_ABS_LEVEL_CAT_5_OFFSET = 0 } cabac_blk_cat_offset_t; /*****************************************************************************/ /* Extern global declarations */ /*****************************************************************************/ /* CABAC Table declaration*/ extern const UWORD32 gau4_ih264_cabac_table[128][4]; /*****************************************************************************/ /* Cabac tables for context initialization depending upon type of Slice, */ /* cabac init Idc value and Qp. */ /*****************************************************************************/ extern const UWORD8 gau1_ih264_cabac_ctxt_init_table[NUM_CAB_INIT_IDC_PLUS_ONE][QP_RANGE][NUM_CABAC_CTXTS]; #endif /* IH264_CABAC_TABLES_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_cavlc_tables.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ****************************************************************************** * @file * ih264_cavlc_tables.c * * @brief * This file contains H264 cavlc tables for encoding coeff_tokens, levels, total * zeros and runs before zeros * * @author * Ittiam * * @par List of Tables * - gu1_code_coeff_token_table * - gu1_size_coeff_token_table * - gu1_code_coeff_token_table_chroma * - gu1_size_coeff_token_table_chroma * - gu1_threshold_vlc_level * - gu1_size_zero_table * - gu1_code_zero_table * - gu1_size_zero_table_chroma * - gu1_code_zero_table_chroma * - gu1_index_zero_table * - gu1_size_run_table * - gu1_code_run_table * - gu4_codeword_level_tables * - gu1_codesize_level_tables * * @remarks * none * ****************************************************************************** */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* User include files */ #include "ih264_typedefs.h" #include "ih264_cavlc_tables.h" /*****************************************************************************/ /* Extern global definitions */ /*****************************************************************************/ /** ****************************************************************************** * @brief Assignment of cbp to a codenum for intra and inter prediction modes * chroma format idc != 0 * input : cbp, intra - 0/inter - 1 * output : codenum * @remarks Table 9-4 Assignment of codeNum to values of coded_block_pattern * for macroblock prediction modes in H264 spec ****************************************************************************** */ const UWORD8 gu1_cbp_map_tables[48][2]= { { 3, 0}, {29, 2}, {30, 3}, {17, 7}, {31, 4}, {18, 8}, {37, 17}, { 8, 13}, {32, 5}, {38, 18}, {19, 9}, { 9, 14}, {20, 10}, {10, 15}, {11, 16}, { 2, 11}, {16, 1}, {33, 32}, {34, 33}, {21, 36}, {35, 34}, {22, 37}, {39, 44}, { 4, 40}, {36, 35}, {40, 45}, {23, 38}, { 5, 41}, {24, 39}, { 6, 42}, { 7, 43}, { 1, 19}, {41, 6}, {42, 24}, {43, 25}, {25, 20}, {44, 26}, {26, 21}, {46, 46}, {12, 28}, {45, 27}, {47, 47}, {27, 22}, {13, 29}, {28, 23}, {14, 30}, {15, 31}, { 0, 12}, }; /** ****************************************************************************** * @brief total non-zero coefficients and numbers of trailing ones of a residual * block are mapped to coeff_token using the tables given below. * input : VLC-Num | Trailing ones | Total coeffs * output : coeff_token (code word, size of the code word) * @remarks Table-9-5 coeff_token mapping to TotalCoeff( coeff_token ) * and TrailingOnes( coeff_token ) in H264 spec ****************************************************************************** */ const UWORD8 gu1_code_coeff_token_table[3][4][16] = { { { 5, 7, 7, 7, 7, 15, 11, 8, 15, 11, 15, 11, 15, 11, 7, 4, }, { 1, 4, 6, 6, 6, 6, 14, 10, 14, 10, 14, 10, 1, 14, 10, 6, }, { 0, 1, 5, 5, 5, 5, 5, 13, 9, 13, 9, 13, 9, 13, 9, 5, }, { 0, 0, 3, 3, 4, 4, 4, 4, 4, 12, 12, 8, 12, 8, 12, 8, }, }, { {11, 7, 7, 7, 4, 7, 15, 11, 15, 11, 8, 15, 11, 7, 9, 7, }, { 2, 7, 10, 6, 6, 6, 6, 14, 10, 14, 10, 14, 10, 11, 8, 6, }, { 0, 3, 9, 5, 5, 5, 5, 13, 9, 13, 9, 13, 9, 6, 10, 5, }, { 0, 0, 5, 4, 6, 8, 4, 4, 4, 12, 8, 12, 12, 8, 1, 4, }, }, { {15, 11, 8, 15, 11, 9, 8, 15, 11, 15, 11, 8, 13, 9, 5, 1, }, {14, 15, 12, 10, 8, 14, 10, 14, 14, 10, 14, 10, 7, 12, 8, 4, }, { 0, 13, 14, 11, 9, 13, 9, 13, 10, 13, 9, 13, 9, 11, 7, 3, }, { 0, 0, 12, 11, 10, 9, 8, 13, 12, 12, 12, 8, 12, 10, 6, 2, }, }, }; const UWORD8 gu1_size_coeff_token_table[3][4][16] = { { { 6, 8, 9, 10, 11, 13, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, }, { 2, 6, 8, 9, 10, 11, 13, 13, 14, 14, 15, 15, 15, 16, 16, 16, }, { 0, 3, 7, 8, 9, 10, 11, 13, 13, 14, 14, 15, 15, 16, 16, 16, }, { 0, 0, 5, 6, 7, 8, 9, 10, 11, 13, 14, 14, 15, 15, 16, 16, }, }, { { 6, 6, 7, 8, 8, 9, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, }, { 2, 5, 6, 6, 7, 8, 9, 11, 11, 12, 12, 13, 13, 14, 14, 14, }, { 0, 3, 6, 6, 7, 8, 9, 11, 11, 12, 12, 13, 13, 13, 14, 14, }, { 0, 0, 4, 4, 5, 6, 6, 7, 9, 11, 11, 12, 13, 13, 13, 14, }, }, { { 6, 6, 6, 7, 7, 7, 7, 8, 8, 9, 9, 9, 10, 10, 10, 10, }, { 4, 5, 5, 5, 5, 6, 6, 7, 8, 8, 9, 9, 9, 10, 10, 10, }, { 0, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 10, }, { 0, 0, 4, 4, 4, 4, 4, 5, 6, 7, 8, 8, 9, 10, 10, 10, }, }, }; const UWORD8 gu1_code_coeff_token_table_chroma[4][4] = { { 7, 4, 3, 2, }, { 1, 6, 3, 3, }, { 0, 1, 2, 2, }, { 0, 0, 5, 0, }, }; const UWORD8 gu1_size_coeff_token_table_chroma[4][4] = { { 6, 6, 6, 6, }, { 1, 6, 7, 8, }, { 0, 3, 7, 8, }, { 0, 0, 6, 7, }, }; /** ****************************************************************************** * @brief After encoding the current Level, to encode the next level, the choice * of VLC table needs to be updated. The update is carried basing on a set of thresholds. * These thresholds are listed in the table below for lookup. * input : suffix_length * output : threshold ****************************************************************************** */ const UWORD8 gu1_threshold_vlc_level[6] = { 0, 3, 6, 12, 24, 48 }; /** ****************************************************************************** * @brief table for encoding total number of zeros * input : coeff_token, total zeros * output : code word, size of the code word * @remarks Table-9-7, 9-8 total_zeros tables for 4x4 blocks with * TotalCoeff( coeff_token ) in H264 spec ****************************************************************************** */ const UWORD8 gu1_size_zero_table[135] = { 1, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 4, 3, 3, 3, 4, 4, 3, 3, 4, 5, 5, 6, 5, 6, 5, 3, 4, 4, 3, 3, 3, 4, 3, 4, 5, 5, 5, 4, 4, 4, 3, 3, 3, 3, 3, 4, 5, 4, 5, 6, 5, 3, 3, 3, 3, 3, 3, 4, 3, 6, 6, 5, 3, 3, 3, 2, 3, 4, 3, 6, 6, 4, 5, 3, 2, 2, 3, 3, 6, 6, 6, 4, 2, 2, 3, 2, 5, 5, 5, 3, 2, 2, 2, 4, 4, 4, 3, 3, 1, 3, 4, 4, 2, 1, 3, 3, 3, 1, 2, 2, 2, 1, 1, 1, }; const UWORD8 gu1_code_zero_table[135] = { 1, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 1, 7, 6, 5, 4, 3, 5, 4, 3, 2, 3, 2, 3, 2, 1, 0, 5, 7, 6, 5, 4, 3, 4, 3, 2, 3, 2, 1, 1, 0, 3, 7, 5, 4, 6, 5, 4, 3, 3, 2, 2, 1, 0, 5, 4, 3, 7, 6, 5, 4, 3, 2, 1, 1, 0, 1, 1, 7, 6, 5, 4, 3, 2, 1, 1, 0, 1, 1, 5, 4, 3, 3, 2, 1, 1, 0, 1, 1, 1, 3, 3, 2, 2, 1, 0, 1, 0, 1, 3, 2, 1, 1, 1, 1, 0, 1, 3, 2, 1, 1, 0, 1, 1, 2, 1, 3, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, }; const UWORD8 gu1_size_zero_table_chroma[9] = { 1, 2, 3, 3, 1, 2, 2, 1, 1, }; const UWORD8 gu1_code_zero_table_chroma[9] = { 1, 1, 1, 0, 1, 1, 0, 1, 0, }; /** ****************************************************************************** * @brief index to access zero table (look up) * input : TotalCoeff( coeff_token ) * output : index to access zero table ****************************************************************************** */ const UWORD8 gu1_index_zero_table[15] = { 0, 16, 31, 45, 58, 70, 81, 91, 100, 108, 115, 121, 126, 130, 133, }; /** ****************************************************************************** * @brief table for encoding runs of zeros before * input : zeros left, runs of zeros before * output : code word, size of the code word * @remarks Table-9-10 table for run_before in H264 spec ****************************************************************************** */ const UWORD8 gu1_size_run_table[42] = { 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, }; const UWORD8 gu1_code_run_table[42] = { 1, 0, 1, 1, 0, 3, 2, 1, 0, 3, 2, 1, 1, 0, 3, 2, 3, 2, 1, 0, 3, 0, 1, 3, 2, 5, 4, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; /** ****************************************************************************** * @brief index to access zero table (look up) * input : TotalCoeff( coeff_token ) * output : index to access zero table ****************************************************************************** */ const UWORD8 gu1_index_run_table[7] = { 0, 2, 5, 9, 14, 20, 27, }; ================================================ FILE: dependencies/ih264d/common/ih264_cavlc_tables.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ****************************************************************************** * @file ih264_cavlc_tables.h * * @brief * This file contains enumerations, macros and extern declarations of H264 * cavlc tables * * @author * Ittiam * * @remarks * none ****************************************************************************** */ #ifndef IH264_CAVLC_TABLES_H_ #define IH264_CAVLC_TABLES_H_ /*****************************************************************************/ /* Constant Macros */ /*****************************************************************************/ /** ****************************************************************************** * @brief maximum zeros left ****************************************************************************** */ #define MAX_ZERO_LEFT 6 /*****************************************************************************/ /* Extern global declarations */ /*****************************************************************************/ /** ****************************************************************************** * @brief Assignment of cbp to a codenum for intra and inter prediction modes * chroma format idc != 0 * input : cbp, intra - 0/inter - 1 * output : codenum * @remarks Table 9-4 Assignment of codeNum to values of coded_block_pattern * for macroblock prediction modes in H264 spec ****************************************************************************** */ extern const UWORD8 gu1_cbp_map_tables[48][2]; /** ****************************************************************************** * @brief total non-zero coefficients and numbers of trailing ones of a residual * block are mapped to coefftoken using the tables given below. * input : VLC-Num | Trailing ones | Total coeffs * output : coeff_token (code word, size of the code word) * @remarks Table-9-5 coeff_token mapping to TotalCoeff( coeff_token ) * and TrailingOnes( coeff_token ) in H264 spec ****************************************************************************** */ extern const UWORD8 gu1_code_coeff_token_table[3][4][16]; extern const UWORD8 gu1_size_coeff_token_table[3][4][16]; extern const UWORD8 gu1_code_coeff_token_table_chroma[4][4]; extern const UWORD8 gu1_size_coeff_token_table_chroma[4][4]; /** ****************************************************************************** * @brief Thresholds for determining whether to increment Level table number. * input : suffix_length * output : threshold ****************************************************************************** */ extern const UWORD8 gu1_threshold_vlc_level[6]; /** ****************************************************************************** * @brief table for encoding total number of zeros * input : coeff_token, total zeros * output : code word, size of the code word * @remarks Table-9-7, 9-8 total_zeros tables for 4x4 blocks with * TotalCoeff( coeff_token ) in H264 spec ****************************************************************************** */ extern const UWORD8 gu1_size_zero_table[135]; extern const UWORD8 gu1_code_zero_table[135]; extern const UWORD8 gu1_size_zero_table_chroma[9]; extern const UWORD8 gu1_code_zero_table_chroma[9]; /** ****************************************************************************** * @brief index to access zero table (for speed) * input : TotalCoeff( coeff_token ) * output : index to access zero table ****************************************************************************** */ extern const UWORD8 gu1_index_zero_table[15]; /** ****************************************************************************** * @brief table for encoding runs of zeros before * input : zeros left, runs of zeros before * output : code word, size of the code word * @remarks Table-9-10 table for run_before in H264 spec ****************************************************************************** */ extern const UWORD8 gu1_size_run_table[42]; extern const UWORD8 gu1_code_run_table[42]; /** ****************************************************************************** * @brief index to access run table (look up) * input : zeros left * output : index to access run table ****************************************************************************** */ extern const UWORD8 gu1_index_run_table[7]; #endif /* IH264_CAVLC_TABLES_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_chroma_intra_pred_filters.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_chroma_intra_pred_filters.c * * @brief * Contains function definitions for chroma intra prediction filters * * @author * Ittiam * * @par List of Functions: * -ih264_intra_pred_chroma_8x8_mode_dc * -ih264_intra_pred_chroma_8x8_mode_horz * -ih264_intra_pred_chroma_8x8_mode_vert * -ih264_intra_pred_chroma_8x8_mode_plane * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include #include #include /* User include files */ #include "ih264_defs.h" #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_intra_pred_filters.h" /* Global variables used only in assembly files*/ const WORD8 ih264_gai1_intrapred_chroma_plane_coeffs1[] = { 0x01,0x00,0x01,0x00, 0x02,0x00,0x02,0x00, 0x03,0x00,0x03,0x00, 0x04,0x00,0x04,0x00 }; const WORD8 ih264_gai1_intrapred_chroma_plane_coeffs2[] = { 0xfd,0xff,0xfe,0xff, 0xff,0xff,0x00,0x00, 0x01,0x00,0x02,0x00, 0x03,0x00,0x04,0x00, }; /*****************************************************************************/ /* Chroma Intra prediction 8x8 filters */ /*****************************************************************************/ /** ******************************************************************************* * * ih264_intra_pred_chroma_8x8_mode_dc * * @brief * Perform Intra prediction for chroma_8x8 mode:DC * * @par Description: * Perform Intra prediction for chroma_8x8 mode:DC ,described in sec 8.3.4.1 * * @param[in] pu1_src * UWORD8 pointer to the source containing alternate U and V samples * * @param[out] pu1_dst * UWORD8 pointer to the destination with alternate U and V samples * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * ** @param[in] ngbr_avail * availability of neighbouring pixels * * @returns * * @remarks * None * ****************************************************************************** */ void ih264_intra_pred_chroma_8x8_mode_dc(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { WORD32 left_avail, left_avail1, left_avail2; /* availability of left predictors (only for DC) */ WORD32 top_avail; /* availability of top predictors (only for DC) */ UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ /* temporary variables to store accumulated first left half,second left half, * first top half,second top half of U and V values*/ WORD32 val_u_l1 = 0, val_u_l2 = 0, val_u_t1 = 0, val_u_t2 = 0; WORD32 val_v_l1 = 0, val_v_l2 = 0, val_v_t1 = 0, val_v_t2 = 0; WORD32 val_u1 = 0, val_u2 = 0, val_v1 = 0, val_v2 = 0; WORD32 col, row; /*loop variables*/ UNUSED(src_strd); left_avail = ngbr_avail & 0x11; left_avail1 = ngbr_avail & 1; left_avail2 = (ngbr_avail >> 4) & 1; top_avail = (ngbr_avail >> 2) & 1; pu1_top = pu1_src + 2 * BLK8x8SIZE + 2; pu1_left = pu1_src + 2 * BLK8x8SIZE - 2; if(left_avail1) { /* First 4x4 block*/ val_u_l1 += *pu1_left; val_v_l1 += *(pu1_left + 1); pu1_left -= 2; val_u_l1 += *pu1_left; val_v_l1 += *(pu1_left + 1); pu1_left -= 2; val_u_l1 += *pu1_left; val_v_l1 += *(pu1_left + 1); pu1_left -= 2; val_u_l1 += *pu1_left + 2; val_v_l1 += *(pu1_left + 1) + 2; pu1_left -= 2; } else pu1_left -= 2 * 4; if(left_avail2) { /* Second 4x4 block*/ val_u_l2 += *pu1_left; val_v_l2 += *(pu1_left + 1); pu1_left -= 2; val_u_l2 += *pu1_left; val_v_l2 += *(pu1_left + 1); pu1_left -= 2; val_u_l2 += *pu1_left; val_v_l2 += *(pu1_left + 1); pu1_left -= 2; val_u_l2 += *pu1_left + 2; val_v_l2 += *(pu1_left + 1) + 2; pu1_left -= 2; } else pu1_left -= 2 * 4; if(top_avail) { val_u_t1 += *pu1_top + *(pu1_top + 2) + *(pu1_top + 4) + *(pu1_top + 6) + 2; val_u_t2 += *(pu1_top + 8) + *(pu1_top + 10) + *(pu1_top + 12) + *(pu1_top + 14) + 2; val_v_t1 += *(pu1_top + 1) + *(pu1_top + 3) + *(pu1_top + 5) + *(pu1_top + 7) + 2; val_v_t2 += *(pu1_top + 9) + *(pu1_top + 11) + *(pu1_top + 13) + *(pu1_top + 15) + 2; } if(left_avail + top_avail) { val_u1 = (left_avail1 + top_avail) ? ((val_u_l1 + val_u_t1) >> (1 + left_avail1 + top_avail)) :128; val_v1 = (left_avail1 + top_avail) ? ((val_v_l1 + val_v_t1) >> (1 + left_avail1 + top_avail)) :128; if(top_avail) { val_u2 = val_u_t2 >> 2; val_v2 = val_v_t2 >> 2; } else if(left_avail1) { val_u2 = val_u_l1 >> 2; val_v2 = val_v_l1 >> 2; } else { val_u2 = val_v2 = 128; } for(row = 0; row < 4; row++) { /*top left 4x4 block*/ for(col = 0; col < 8; col += 2) { *(pu1_dst + row * dst_strd + col) = val_u1; *(pu1_dst + row * dst_strd + col + 1) = val_v1; } /*top right 4x4 block*/ for(col = 8; col < 16; col += 2) { *(pu1_dst + row * dst_strd + col) = val_u2; *(pu1_dst + row * dst_strd + col + 1) = val_v2; } } if(left_avail2) { val_u1 = val_u_l2 >> 2; val_v1 = val_v_l2 >> 2; } else if(top_avail) { val_u1 = val_u_t1 >> 2; val_v1 = val_v_t1 >> 2; } else { val_u1 = val_v1 = 128; } val_u2 = (left_avail2 + top_avail) ? ((val_u_l2 + val_u_t2) >> (1 + left_avail2 + top_avail)) : 128; val_v2 = (left_avail2 + top_avail) ? ((val_v_l2 + val_v_t2) >> (1 + left_avail2 + top_avail)) : 128; for(row = 4; row < 8; row++) { /*bottom left 4x4 block*/ for(col = 0; col < 8; col += 2) { *(pu1_dst + row * dst_strd + col) = val_u1; *(pu1_dst + row * dst_strd + col + 1) = val_v1; } /*bottom right 4x4 block*/ for(col = 8; col < 16; col += 2) { *(pu1_dst + row * dst_strd + col) = val_u2; *(pu1_dst + row * dst_strd + col + 1) = val_v2; } } } else { /* Both left and top are unavailable, set the block to 128 */ for(row = 0; row < 8; row++) { memset(pu1_dst + row * dst_strd, 128, 8 * sizeof(UWORD16)); } } } /** ******************************************************************************* * *ih264_intra_pred_chroma_8x8_mode_horz * * @brief * Perform Intra prediction for chroma_8x8 mode:Horizontal * * @par Description: * Perform Intra prediction for chroma_8x8 mode:Horizontal ,described in sec 8.3.4.2 * * @param[in] pu1_src * UWORD8 pointer to the source containing alternate U and V samples * * @param[out] pu1_dst * UWORD8 pointer to the destination with alternate U and V samples * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ****************************************************************************** */ void ih264_intra_pred_chroma_8x8_mode_horz(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of top predictors */ WORD32 rows, cols; /* loop variables*/ UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + 2 * BLK8x8SIZE - 2; for(rows = 0; rows < 8; rows++) { for(cols = 0; cols < 16; cols += 2) { *(pu1_dst + rows * dst_strd + cols) = *pu1_left; *(pu1_dst + rows * dst_strd + cols + 1) = *(pu1_left + 1); } pu1_left -= 2; } } /** ******************************************************************************* * *ih264_intra_pred_chroma_8x8_mode_vert * * @brief * Perform Intra prediction for chroma_8x8 mode:vertical * * @par Description: * Perform Intra prediction for chroma_8x8 mode:vertical ,described in sec 8.3.4.3 * * @param[in] pu1_src * UWORD8 pointer to the source containing alternate U and V samples * * @param[out] pu1_dst * UWORD8 pointer to the destination with alternate U and V samples * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_intra_pred_chroma_8x8_mode_vert(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ WORD32 row;/*loop variable*/ UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + 2 * BLK8x8SIZE + 2; /* 8 bytes are copied from src to dst */ for(row = 0; row < 2; row++) { memcpy(pu1_dst, pu1_top, 16); pu1_dst += dst_strd; memcpy(pu1_dst, pu1_top, 16); pu1_dst += dst_strd; memcpy(pu1_dst, pu1_top, 16); pu1_dst += dst_strd; memcpy(pu1_dst, pu1_top, 16); pu1_dst += dst_strd; } } /** ******************************************************************************* * * ih264_intra_pred_chroma_8x8_mode_plane * * @brief * Perform Intra prediction for chroma_8x8 mode:PLANE * * @par Description: * Perform Intra prediction for chroma_8x8 mode:PLANE ,described in sec 8.3.4.4 * * @param[in] pu1_src * UWORD8 pointer to the source containing alternate U and V samples * * @param[out] pu1_dst * UWORD8 pointer to the destination with alternate U and V samples * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ****************************************************************************** */ void ih264_intra_pred_chroma_8x8_mode_plane(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ WORD32 val = 0; WORD32 rows, cols; /* loop variables*/ WORD32 a_u, b_u, c_u, h_u, v_u; /* Implementing section 8.3.4.4 . The variables represent the corresponding variables in the section*/ WORD32 a_v, b_v, c_v, h_v, v_v; UNUSED(src_strd); UNUSED(ngbr_avail); a_u = b_u = c_u = h_u = v_u = 0; a_v = b_v = c_v = h_v = v_v = 0; /* As chroma format 4:2:0 is used,xCF = 4 * ( chroma_format_idc = = 3 ) = 0 and yCF = 4 * ( chroma_format_idc != 1 ) = 0 */ pu1_top = pu1_src + 2 * BLK8x8SIZE + 2; pu1_left = pu1_src + 2 * BLK8x8SIZE - 2; /* Implementing section 8.3.4.4 */ for(cols = 0; cols < 4; cols++) { h_u += (cols + 1) * (pu1_top[8 + 2 * cols] - pu1_top[4 - 2 * cols]);/*section 8.3.4.4 equation (8-144)*/ h_v += (cols + 1) * (pu1_top[8 + 2 * cols + 1] - pu1_top[4 - 2 * cols+ 1]); v_u += (cols + 1) * (pu1_left[(4 + cols) * (-2)] - pu1_left[(2 - cols) * (-2)]); v_v += (cols + 1) * (pu1_left[(4 + cols) * (-2) + 1] - pu1_left[(2 - cols) * (-2) + 1]);/*section 8.3.4.4 equation (8-145)*/ } a_u = 16 * (pu1_left[7 * (-2)] + pu1_top[14]); a_v = 16 * (pu1_left[7 * (-2) + 1] + pu1_top[15]);/*section 8.3.3.4 equation (8-141)*/ b_u = (34 * h_u + 32) >> 6;/*section 8.3.3.4 equation (8-142)*/ b_v = (34 * h_v + 32) >> 6;/*section 8.3.3.4 equation (8-142)*/ c_u = (34 * v_u + 32) >> 6;/*section 8.3.3.4 equation (8-143)*/ c_v = (34 * v_v + 32) >> 6;/*section 8.3.3.4 equation (8-143)*/ for(rows = 0; rows < 8; rows++) { for(cols = 0; cols < 8; cols++) { val = (a_u + b_u * (cols - 3) + c_u * (rows - 3) );/*section 8.3.4.4 equation (8-140)*/ val = (val + 16) >> 5; *(pu1_dst + rows * dst_strd + 2 * cols) = CLIP_U8(val); val = (a_v + b_v * (cols - 3) + c_v * (rows - 3) );/*section 8.3.4.4 equation (8-140)*/ val = (val + 16) >> 5; *(pu1_dst + rows * dst_strd + 2 * cols + 1) = CLIP_U8(val); } } } ================================================ FILE: dependencies/ih264d/common/ih264_common_tables.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_common_tables.c * * @brief * Contains common global tables * * @author * Harish M * * @par List of Functions: * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* User include files */ #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_macros.h" #include "ih264_structs.h" #include "ih264_common_tables.h" /*****************************************************************************/ /* Extern global definitions */ /*****************************************************************************/ /** ****************************************************************************** * @brief while encoding, basing on the input configuration parameters, the * the level of the bitstream is computed basing on the table below. * input : table_idx * output : level_idc or cpb size * @remarks Table A-1 level table limits ****************************************************************************** */ const level_tables_t gas_ih264_lvl_tbl[16] = { { IH264_LEVEL_10, 1485, 99, 297, 64, 175, 64 }, { IH264_LEVEL_1B, 1485, 99, 297, 128, 350, 64 }, { IH264_LEVEL_11, 3000, 396, 675, 192, 500, 128 }, { IH264_LEVEL_12, 6000, 396, 1782, 384, 1000, 128 }, { IH264_LEVEL_13, 11880, 396, 1782, 768, 2000, 128 }, { IH264_LEVEL_20, 11880, 396, 1782, 2000, 2000, 128 }, { IH264_LEVEL_21, 19800, 792, 3564, 4000, 4000, 256 }, { IH264_LEVEL_22, 20250, 1620, 6075, 4000, 4000, 256 }, { IH264_LEVEL_30, 40500, 1620, 6075, 10000, 10000, 256 }, { IH264_LEVEL_31, 108000, 3600, 13500, 14000, 14000, 512 }, { IH264_LEVEL_32, 216000, 5120, 15360, 20000, 20000, 512 }, { IH264_LEVEL_40, 245760, 8192, 24576, 20000, 25000, 512 }, { IH264_LEVEL_41, 245760, 8192, 24576, 50000, 62500, 512 }, { IH264_LEVEL_42, 522240, 8704, 26112, 50000, 62500, 512 }, { IH264_LEVEL_50, 589824, 22080, 82800, 135000, 135000, 512 }, { IH264_LEVEL_51, 983040, 36864, 138240, 240000, 240000, 512 }, }; /** * Array containing supported levels */ const WORD32 gai4_ih264_levels[] = { IH264_LEVEL_10, IH264_LEVEL_11, IH264_LEVEL_12, IH264_LEVEL_13, IH264_LEVEL_20, IH264_LEVEL_21, IH264_LEVEL_22, IH264_LEVEL_30, IH264_LEVEL_31, IH264_LEVEL_32, IH264_LEVEL_40, IH264_LEVEL_41, IH264_LEVEL_42, IH264_LEVEL_50, IH264_LEVEL_51, }; /** * Array giving size of max luma samples in a picture for a given level */ const WORD32 gai4_ih264_max_luma_pic_size[] = { /* Level 1 */ 25344, /* Level 1.1 */ 101376, /* Level 1.2 */ 101376, /* Level 1.3 */ 101376, /* Level 2 */ 101376, /* Level 2.1 */ 202752, /* Level 2.2 */ 414720, /* Level 3 */ 414720, /* Level 3.1 */ 921600, /* Level 3.2 */ 1310720, /* Level 4 */ 2097152, /* Level 4.1 */ 2097152, /* Level 4.2 */ 2228224, /* Level 5 */ 5652480, /* Level 5.1 */ 9437184 }; /** Max width and height allowed for a given level */ /** This is derived as SQRT(8 * gai4_ih264_max_luma_pic_size[]) */ const WORD32 gai4_ih264_max_wd_ht[] = { /* Level 1 */ 451, /* Level 1.1 */ 901, /* Level 1.2 */ 901, /* Level 1.3 */ 901, /* Level 2 */ 901, /* Level 2.1 */ 1274, /* Level 2.2 */ 1822, /* Level 3 */ 1822, /* Level 3.1 */ 2716, /* Level 3.2 */ 3239, /* Level 4 */ 4096, /* Level 4.1 */ 4096, /* Level 4.2 */ 4223, /* Level 5 */ 6725, /* Level 5.1 */ 8689 }; /** Min width and height allowed for a given level */ /** This is derived as gai4_ih264_max_luma_pic_size[]/gai4_ih264_max_wd_ht[] */ const WORD32 gai4_ih264_min_wd_ht[] = { /* Level 1 */ 57, /* Level 1.1 */ 113, /* Level 1.2 */ 113, /* Level 1.3 */ 113, /* Level 2 */ 113, /* Level 2.1 */ 160, /* Level 2.2 */ 228, /* Level 3 */ 228, /* Level 3.1 */ 340, /* Level 3.2 */ 405, /* Level 4 */ 512, /* Level 4.1 */ 512, /* Level 4.2 */ 528, /* Level 5 */ 841, /* Level 5.1 */ 1087 }; /** Table 7-11 Macroblock types for I slices */ intra_mbtype_info_t gas_ih264_i_mbtype_info[] = { /* For first entry, if transform_size_8x8_flag is 1, mode will be MBPART_I8x8 */ /* This has to be taken care while accessing the table */ {0, MBPART_I4x4, VERT_I16x16, 0, 0}, {0, MBPART_I16x16, VERT_I16x16, 0, 0}, {0, MBPART_I16x16, HORZ_I16x16, 0, 0}, {0, MBPART_I16x16, DC_I16x16, 0, 0}, {0, MBPART_I16x16, PLANE_I16x16, 0, 0}, {0, MBPART_I16x16, VERT_I16x16, 1, 0}, {0, MBPART_I16x16, HORZ_I16x16, 1, 0}, {0, MBPART_I16x16, DC_I16x16, 1, 0}, {0, MBPART_I16x16, PLANE_I16x16, 1, 0}, {0, MBPART_I16x16, VERT_I16x16, 2, 0}, {0, MBPART_I16x16, HORZ_I16x16, 2, 0}, {0, MBPART_I16x16, DC_I16x16, 2, 0}, {0, MBPART_I16x16, PLANE_I16x16, 2, 0}, {0, MBPART_I16x16, VERT_I16x16, 0, 15}, {0, MBPART_I16x16, HORZ_I16x16, 0, 15}, {0, MBPART_I16x16, DC_I16x16, 0, 15}, {0, MBPART_I16x16, PLANE_I16x16, 0, 15}, {0, MBPART_I16x16, VERT_I16x16, 1, 15}, {0, MBPART_I16x16, HORZ_I16x16, 1, 15}, {0, MBPART_I16x16, DC_I16x16, 1, 15}, {0, MBPART_I16x16, PLANE_I16x16, 1, 15}, {0, MBPART_I16x16, VERT_I16x16, 2, 15}, {0, MBPART_I16x16, HORZ_I16x16, 2, 15}, {0, MBPART_I16x16, DC_I16x16, 2, 15}, {0, MBPART_I16x16, PLANE_I16x16, 2, 15}, {0, MBPART_IPCM, VERT_I16x16, 0, 0} }; /** Table 7-13 Macroblock types for P slices */ inter_mbtype_info_t gas_ih264_p_mbtype_info[] = { {1, MBPART_L0, MBPART_NA, 16, 16}, {2, MBPART_L0, MBPART_L0, 16, 8}, {2, MBPART_L0, MBPART_L0, 8, 16}, {4, MBPART_NA, MBPART_NA, 8, 8}, {4, MBPART_NA, MBPART_NA, 8, 8}, }; /** Table 7-14 Macroblock types for B slices */ inter_mbtype_info_t gas_ih264_b_mbtype_info[] = { {0, MBPART_DIRECT, MBPART_NA, 8, 8, }, {1, MBPART_L0, MBPART_NA, 16, 16, }, {1, MBPART_L1, MBPART_NA, 16, 16, }, {1, MBPART_BI, MBPART_NA, 16, 16, }, {2, MBPART_L0, MBPART_L0, 16, 8, }, {2, MBPART_L0, MBPART_L0, 8, 16, }, {2, MBPART_L1, MBPART_L1, 16, 8, }, {2, MBPART_L1, MBPART_L1, 8, 16, }, {2, MBPART_L0, MBPART_L1, 16, 8, }, {2, MBPART_L0, MBPART_L1, 8, 16, }, {2, MBPART_L1, MBPART_L0, 16, 8, }, {2, MBPART_L1, MBPART_L0, 8, 16, }, {2, MBPART_L0, MBPART_BI, 16, 8, }, {2, MBPART_L0, MBPART_BI, 8, 16, }, {2, MBPART_L1, MBPART_BI, 16, 8, }, {2, MBPART_L1, MBPART_BI, 8, 16, }, {2, MBPART_BI, MBPART_L0, 16, 8, }, {2, MBPART_BI, MBPART_L0, 8, 16, }, {2, MBPART_BI, MBPART_L1, 16, 8, }, {2, MBPART_BI, MBPART_L1, 8, 16, }, {2, MBPART_BI, MBPART_BI, 16, 8, }, {2, MBPART_BI, MBPART_BI, 8, 16, }, {4, MBPART_NA, MBPART_NA, 8, 8, }, }; /** Table 7-17 Sub-macroblock types in P macroblocks */ submbtype_info_t gas_ih264_p_submbtype_info[] = { {1, MBPART_L0, 8, 8}, {2, MBPART_L0, 8, 4}, {2, MBPART_L0, 4, 8}, {4, MBPART_L0, 4, 4}, }; /** Table 7-18 Sub-macroblock types in B macroblocks */ submbtype_info_t gas_ih264_b_submbtype_info[] = { {4, MBPART_DIRECT, 4, 4}, {1, MBPART_L0, 8, 8}, {1, MBPART_L1, 8, 8}, {1, MBPART_BI, 8, 8}, {2, MBPART_L0, 8, 4}, {2, MBPART_L0, 4, 8}, {2, MBPART_L1, 8, 4}, {2, MBPART_L1, 4, 8}, {2, MBPART_BI, 8, 4}, {2, MBPART_BI, 4, 8}, {4, MBPART_L0, 4, 4}, {4, MBPART_L1, 4, 4}, {4, MBPART_BI, 4, 4}, }; const UWORD8 gau1_ih264_inv_scan_prog4x4[] = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; const UWORD8 gau1_ih264_inv_scan_int4x4[] = { 0, 4, 1, 8, 12, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 }; /** Inverse scan tables for individual 4x4 blocks of 8x8 transform coeffs of CAVLC */ /* progressive */ const UWORD8 gau1_ih264_inv_scan_prog8x8_cavlc[64] = { 0, 9, 17, 18, 12, 40, 27, 7, 35, 57, 29, 30, 58, 38, 53, 47, 1, 2, 24, 11, 19, 48, 20, 14, 42, 50, 22, 37, 59, 31, 60, 55, 8, 3, 32, 4, 26, 41, 13, 21, 49, 43, 15, 44, 52, 39, 61, 62, 16, 10, 25, 5, 33, 34, 6, 28, 56, 36, 23, 51, 45, 46, 54, 63 }; /* interlace */ const UWORD8 gau1_ih264_inv_scan_int8x8_cavlc[64] = { 0, 9, 2, 56, 18, 26, 34, 27, 35, 28, 36, 29, 45, 7, 54, 39, 8, 24, 25, 33, 41, 11, 42, 12, 43, 13, 44, 14, 53, 15, 62, 47, 16, 32, 40, 10, 49, 4, 50, 5, 51, 6, 52, 22, 61, 38, 23, 55, 1, 17, 48, 3, 57, 19, 58, 20, 59, 21, 60, 37, 30, 46, 31, 63 }; /*Inverse scan tables for individual 8x8 blocks of 8x8 transform coeffs of CABAC */ /* progressive */ const UWORD8 gau1_ih264_inv_scan_prog8x8_cabac[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; /* interlace */ const UWORD8 gau1_ih264_inv_scan_int8x8_cabac[64] = { 0, 8, 16, 1, 9, 24, 32, 17, 2, 25, 40, 48, 56, 33, 10, 3, 18, 41, 49, 57, 26, 11, 4, 19, 34, 42, 50, 58, 27, 12, 5, 20, 35, 43, 51, 59, 28, 13, 6, 21, 36, 44, 52, 60, 29, 14, 22, 37, 45, 53, 61, 30, 7, 15, 38, 46, 54, 62, 23, 31, 39, 47, 55, 63 }; const UWORD8 *const gpau1_ih264_inv_scan8x8[] = { gau1_ih264_inv_scan_prog8x8_cavlc, gau1_ih264_inv_scan_int8x8_cavlc, gau1_ih264_inv_scan_prog8x8_cabac, gau1_ih264_inv_scan_int8x8_cabac }; const UWORD8 *const gpau1_ih264_inv_scan4x4[] = { gau1_ih264_inv_scan_prog4x4, gau1_ih264_inv_scan_int4x4, }; const UWORD8 gau1_ih264_8x8_subblk_idx[] = { 0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15 }; /* Table 8-15 Chroma QP offset table */ const UWORD8 gau1_ih264_chroma_qp[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 39 }; /** ****************************************************************************** * @brief look up table to compute neigbour availability of 4x4 blocks * input : subblk idx, mb neighbor availability * output : sub blk neighbor availability * @remarks ****************************************************************************** */ const UWORD8 gau1_ih264_4x4_ngbr_avbl[16][16] = { { 0x0, 0x1, 0xc, 0x7, 0x1, 0x1, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0x1, 0x1, 0xf, 0x7, 0x1, 0x1, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0x2, 0x1, 0xc, 0x7, 0x1, 0x1, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0x3, 0x1, 0xf, 0x7, 0x1, 0x1, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0xd, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0xe, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0x0, 0x1, 0xc, 0x7, 0x1, 0x9, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0x1, 0x1, 0xf, 0x7, 0x1, 0x9, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0x2, 0x1, 0xc, 0x7, 0x1, 0x9, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0x3, 0x1, 0xf, 0x7, 0x1, 0x9, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0xc, 0xf, 0xc, 0x7, 0xf, 0xf, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0xd, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0xe, 0xf, 0xc, 0x7, 0xf, 0xf, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, { 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, }; /** ****************************************************************************** * @brief look up table to compute neigbour availability of 8x8 blocks * input : subblk idx, mb neighbor availability * output : sub blk neighbor availability * @remarks ****************************************************************************** */ const UWORD8 gau1_ih264_8x8_ngbr_avbl[16][4] = { { 0x0, 0x1, 0xc, 0x7 }, { 0x1, 0x1, 0xf, 0x7 }, { 0x2, 0x1, 0xc, 0x7 }, { 0x3, 0x1, 0xf, 0x7 }, { 0xc, 0x7, 0xc, 0x7 }, { 0xd, 0x7, 0xf, 0x7 }, { 0xe, 0x7, 0xc, 0x7 }, { 0xf, 0x7, 0xf, 0x7 }, { 0x0, 0x9, 0xc, 0x7 }, { 0x1, 0x9, 0xf, 0x7 }, { 0x2, 0x9, 0xc, 0x7 }, { 0x3, 0x9, 0xf, 0x7 }, { 0xc, 0xf, 0xc, 0x7 }, { 0xd, 0xf, 0xf, 0x7 }, { 0xe, 0xf, 0xc, 0x7 }, { 0xf, 0xf, 0xf, 0x7 }, }; /** Table 7-3 Default intra 4x4 scaling list */ const UWORD16 gau2_ih264_default_intra4x4_scaling_list[] = { 6, 13, 13, 20, 20, 20, 28, 28, 28, 28, 32, 32, 32, 37, 37, 42 }; /** Table 7-3 Default inter 4x4 scaling list */ const UWORD16 gau2_ih264_default_inter4x4_scaling_list[] = { 10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34 }; /* Inverse scanned output of gau2_ih264_default_intra4x4_scaling_list */ const UWORD16 gau2_ih264_default_intra4x4_weight_scale[] = { 6, 13, 20, 28, 13, 20, 28, 32, 20, 28, 32, 37, 28, 32, 37, 42 }; /* Inverse scanned output of gau2_ih264_default_inter4x4_scaling_list */ const UWORD16 gau2_ih264_default_inter4x4_weight_scale[] = { 10, 14, 20, 24, 14, 20, 24, 27, 20, 24, 27, 30, 24, 27, 30, 34 }; /** Table 7-4 Default intra 8x8 scaling list */ const UWORD16 gau2_ih264_default_intra8x8_scaling_list[] = { 6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23, 23, 23, 23, 23, 23, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31, 31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42 }; /** Table 7-4 Default inter 8x8 scaling list */ const UWORD16 gau2_ih264_default_inter8x8_scaling_list[] = { 9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35 }; /* Inverse scanned output of gau2_ih264_default_intra8x8_scaling_list */ const UWORD16 gau2_ih264_default_intra8x8_weight_scale[] = { 6, 10, 13, 16, 18, 23, 25, 27, 10, 11, 16, 18, 23, 25, 27, 29, 13, 16, 18, 23, 25, 27, 29, 31, 16, 18, 23, 25, 27, 29, 31, 33, 18, 23, 25, 27, 29, 31, 33, 36, 23, 25, 27, 29, 31, 33, 36, 38, 25, 27, 29, 31, 33, 36, 38, 40, 27, 29, 31, 33, 36, 38, 40, 42 }; /* Inverse scanned output of gau2_ih264_default_inter8x8_scaling_list */ const UWORD16 gau2_ih264_default_inter8x8_weight_scale[] = { 9, 13, 15, 17, 19, 21, 22, 24, 13, 13, 17, 19, 21, 22, 24, 25, 15, 17, 19, 21, 22, 24, 25, 27, 17, 19, 21, 22, 24, 25, 27, 28, 19, 21, 22, 24, 25, 27, 28, 30, 21, 22, 24, 25, 27, 28, 30, 32, 22, 24, 25, 27, 28, 30, 32, 33, 24, 25, 27, 28, 30, 32, 33, 35 }; /* Eq 7-8 Flat scaling matrix for 4x4 */ const UWORD16 gau2_ih264_flat_4x4_weight_scale[] = { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }; /* Eq 7-9 Flat scaling matrix for 8x8 */ const UWORD16 gau2_ih264_flat_8x8_weight_scale[] = { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }; /** ****************************************************************************** * @brief Scale Table for inverse quantizing 4x4 subblock. To inverse quantize * a given 4x4 quantized block, the coefficient at index location (i,j) is scaled * by one of the constants in this table and right shift the result by abs (4 - * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. * * input : 16 * qp%6, index location (i,j) * output : scale constant. * * @remarks 16 constants for each index position of the subblock and 6 for each * qp%6 in the range 0-5 inclusive. ****************************************************************************** */ const UWORD16 gau2_ih264_iquant_scale_matrix_4x4[96] = { 10, 13, 10, 13, 13, 16, 13, 16, 10, 13, 10, 13, 13, 16, 13, 16, 11, 14, 11, 14, 14, 18, 14, 18, 11, 14, 11, 14, 14, 18, 14, 18, 13, 16, 13, 16, 16, 20, 16, 20, 13, 16, 13, 16, 16, 20, 16, 20, 14, 18, 14, 18, 18, 23, 18, 23, 14, 18, 14, 18, 18, 23, 18, 23, 16, 20, 16, 20, 20, 25, 20, 25, 16, 20, 16, 20, 20, 25, 20, 25, 18, 23, 18, 23, 23, 29, 23, 29, 18, 23, 18, 23, 23, 29, 23, 29, }; /** ****************************************************************************** * @brief Scale Table for inverse quantizing 8x8 subblock. To inverse quantize * a given 8x8 quantized block, the coefficient at index location (i,j) is scaled * by one of the constants in this table and right shift the result by abs (4 - * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. * * input : qp%6, index location (i,j) * output : scale constant. * * @remarks 64 constants for each index position of the subblock and 6 for each * qp%6 in the range 0-5 inclusive. ****************************************************************************** */ const UWORD16 gau2_ih264_iquant_scale_matrix_8x8 [384] = { 20, 19, 25, 19, 20, 19, 25, 19, 19, 18, 24, 18, 19, 18, 24, 18, 25, 24, 32, 24, 25, 24, 32, 24, 19, 18, 24, 18, 19, 18, 24, 18, 20, 19, 25, 19, 20, 19, 25, 19, 19, 18, 24, 18, 19, 18, 24, 18, 25, 24, 32, 24, 25, 24, 32, 24, 19, 18, 24, 18, 19, 18, 24, 18, 22, 21, 28, 21, 22, 21, 28, 21, 21, 19, 26, 19, 21, 19, 26, 19, 28, 26, 35, 26, 28, 26, 35, 26, 21, 19, 26, 19, 21, 19, 26, 19, 22, 21, 28, 21, 22, 21, 28, 21, 21, 19, 26, 19, 21, 19, 26, 19, 28, 26, 35, 26, 28, 26, 35, 26, 21, 19, 26, 19, 21, 19, 26, 19, 26, 24, 33, 24, 26, 24, 33, 24, 24, 23, 31, 23, 24, 23, 31, 23, 33, 31, 42, 31, 33, 31, 42, 31, 24, 23, 31, 23, 24, 23, 31, 23, 26, 24, 33, 24, 26, 24, 33, 24, 24, 23, 31, 23, 24, 23, 31, 23, 33, 31, 42, 31, 33, 31, 42, 31, 24, 23, 31, 23, 24, 23, 31, 23, 28, 26, 35, 26, 28, 26, 35, 26, 26, 25, 33, 25, 26, 25, 33, 25, 35, 33, 45, 33, 35, 33, 45, 33, 26, 25, 33, 25, 26, 25, 33, 25, 28, 26, 35, 26, 28, 26, 35, 26, 26, 25, 33, 25, 26, 25, 33, 25, 35, 33, 45, 33, 35, 33, 45, 33, 26, 25, 33, 25, 26, 25, 33, 25, 32, 30, 40, 30, 32, 30, 40, 30, 30, 28, 38, 28, 30, 28, 38, 28, 40, 38, 51, 38, 40, 38, 51, 38, 30, 28, 38, 28, 30, 28, 38, 28, 32, 30, 40, 30, 32, 30, 40, 30, 30, 28, 38, 28, 30, 28, 38, 28, 40, 38, 51, 38, 40, 38, 51, 38, 30, 28, 38, 28, 30, 28, 38, 28, 36, 34, 46, 34, 36, 34, 46, 34, 34, 32, 43, 32, 34, 32, 43, 32, 46, 43, 58, 43, 46, 43, 58, 43, 34, 32, 43, 32, 34, 32, 43, 32, 36, 34, 46, 34, 36, 34, 46, 34, 34, 32, 43, 32, 34, 32, 43, 32, 46, 43, 58, 43, 46, 43, 58, 43, 34, 32, 43, 32, 34, 32, 43, 32, }; ================================================ FILE: dependencies/ih264d/common/ih264_common_tables.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_common_tables.h * * @brief * Common tables * * @author * Harish * * @par List of Functions: * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_COMMON_TABLES_H_ #define _IH264_COMMON_TABLES_H_ /*****************************************************************************/ /* Structures */ /*****************************************************************************/ /** ****************************************************************************** * @brief level tables ****************************************************************************** */ typedef struct { /* level */ IH264_LEVEL_T u4_level_idc; /* max macroblock processing rate */ UWORD32 u4_max_mbps; /* max frame size in mbs */ UWORD32 u4_max_fs; /* max dpb size / 768 */ UWORD32 u4_max_dpb_size; /* max bit rate */ UWORD32 u4_max_br; /* max cpb size */ UWORD32 u4_max_cpb_size; /* max vertical MV component range */ UWORD32 u4_max_mv_y; }level_tables_t; /*****************************************************************************/ /* Extern global declarations */ /*****************************************************************************/ /** ****************************************************************************** * @brief while encoding, basing on the input configuration parameters, the * the level of the bitstream is computed basing on the table below. * input : table_idx * output : level_idc or cpb size * @remarks Table A-1 level table limits ****************************************************************************** */ extern const level_tables_t gas_ih264_lvl_tbl[16]; extern const WORD32 gai4_ih264_levels[]; extern const WORD32 gai4_ih264_max_luma_pic_size[]; extern const WORD32 gai4_ih264_max_wd_ht[]; extern const WORD32 gai4_ih264_min_wd_ht[]; extern intra_mbtype_info_t gas_ih264_i_mbtype_info[]; extern inter_mbtype_info_t gas_ih264_p_mbtype_info[]; extern inter_mbtype_info_t gas_ih264_b_mbtype_info[]; extern submbtype_info_t gas_ih264_p_submbtype_info[]; extern submbtype_info_t gas_ih264_b_submbtype_info[]; extern const UWORD8 gau1_ih264_inv_scan_prog4x4[]; extern const UWORD8 gau1_ih264_inv_scan_int4x4[]; extern const UWORD8 gau1_ih264_inv_scan_prog8x8_cavlc[64]; extern const UWORD8 gau1_ih264_inv_scan_int8x8_cavlc[64]; extern const UWORD8 gau1_ih264_inv_scan_prog8x8_cabac[64]; extern const UWORD8 gau1_ih264_inv_scan_int8x8_cabac[64]; extern const UWORD8 *const gpau1_ih264_inv_scan8x8[]; extern const UWORD8 *const gpau1_ih264_inv_scan4x4[]; extern const UWORD8 gau1_ih264_8x8_subblk_idx[]; extern const UWORD8 gau1_ih264_chroma_qp[]; extern const UWORD8 gau1_ih264_4x4_ngbr_avbl[16][16]; extern const UWORD8 gau1_ih264_8x8_ngbr_avbl[16][4]; extern const UWORD16 gau2_ih264_default_inter4x4_weight_scale[]; extern const UWORD16 gau2_ih264_default_intra4x4_weight_scale[]; extern const UWORD16 gau2_ih264_default_intra4x4_scaling_list[]; extern const UWORD16 gau2_ih264_default_inter4x4_scaling_list[]; extern const UWORD16 gau2_ih264_default_intra8x8_scaling_list[]; extern const UWORD16 gau2_ih264_default_inter8x8_scaling_list[]; extern const UWORD16 gau2_ih264_default_intra8x8_weight_scale[]; extern const UWORD16 gau2_ih264_default_inter8x8_weight_scale[]; extern const UWORD16 gau2_ih264_flat_4x4_weight_scale[]; extern const UWORD16 gau2_ih264_flat_8x8_weight_scale[]; extern const UWORD16 gau2_ih264_iquant_scale_matrix_4x4 [96]; extern const UWORD16 gau2_ih264_iquant_scale_matrix_8x8 [384]; #endif /*_IH264_COMMON_TABLES_H_*/ ================================================ FILE: dependencies/ih264d/common/ih264_deblk_edge_filters.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /**************************************************************************** */ /* */ /* File Name : ih264_deblk_edge_filters.c */ /* */ /* Description : Contains function definitions for deblocking */ /* */ /* List of Functions : ih264_deblk_luma_vert_bs4() */ /* ih264_deblk_luma_horz_bs4() */ /* ih264_deblk_luma_vert_bslt4() */ /* ih264_deblk_luma_horz_bslt4() */ /* ih264_deblk_luma_vert_bs4_mbaff() */ /* ih264_deblk_luma_vert_bslt4_mbaff() */ /* ih264_deblk_chroma_vert_bs4_bp() */ /* ih264_deblk_chroma_horz_bs4_bp() */ /* ih264_deblk_chroma_vert_bslt4_bp() */ /* ih264_deblk_chroma_horz_bslt4_bp() */ /* ih264_deblk_chroma_vert_bs4_mbaff_bp() */ /* ih264_deblk_chroma_vert_bslt4_mbaff_bp() */ /* ih264_deblk_chroma_vert_bs4() */ /* ih264_deblk_chroma_horz_bs4() */ /* ih264_deblk_chroma_vert_bslt4() */ /* ih264_deblk_chroma_horz_bslt4() */ /* ih264_deblk_chroma_vert_bs4_mbaff() */ /* ih264_deblk_chroma_vert_bslt4_mbaff() */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 11 2013 Ittiam Draft */ /* 29 12 2014 Kaushik Added double-call vertical */ /* Senthoor deblocking and high profile */ /* deblocking functions */ /* */ /******************************************************************************/ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include /* User include files */ #include "ih264_typedefs.h" #include "ih264_platform_macros.h" #include "ih264_deblk_edge_filters.h" #include "ih264_macros.h" /*****************************************************************************/ /* Function Definitions */ /*****************************************************************************/ /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_vert_bs4() */ /* */ /* Description : This function performs filtering of a luma block */ /* vertical edge when the boundary strength is set to 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.4 under the */ /* title "Filtering process for edges for bS equal to 4" in */ /* ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 11 2013 Ittiam Draft */ /* */ /*****************************************************************************/ void ih264_deblk_luma_vert_bs4(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta) { UWORD8 p3, p2, p1, p0, q0, q1, q2, q3; WORD32 pos_p3, pos_p2, pos_p1, pos_p0; WORD32 pos_q0, pos_q1, pos_q2,pos_q3; UWORD8 a_p, a_q; /* threshold variables */ WORD32 blk_strd = src_strd << 2; /* block_increment = src_strd * 4 */ UWORD8 *pu1_src_temp; WORD8 i = 0, edge; pos_q0 = 0; pos_q1 = 1; pos_q2 = 2; pos_q3 = 3; pos_p0 = -1; pos_p1 = -2; pos_p2 = -3; pos_p3 = -4; for(edge = 0; edge < 4; edge++, pu1_src += blk_strd) { pu1_src_temp = pu1_src; for(i = 0; i < 4; ++i, pu1_src_temp += src_strd) { q0 = pu1_src_temp[pos_q0]; q1 = pu1_src_temp[pos_q1]; p0 = pu1_src_temp[pos_p0]; p1 = pu1_src_temp[pos_p1]; /* Filter Decision */ if((ABS(p0 - q0) >= alpha) || (ABS(q1 - q0) >= beta) || (ABS(p1 - p0) >= beta)) continue; p2 = pu1_src_temp[pos_p2]; p3 = pu1_src_temp[pos_p3]; q2 = pu1_src_temp[pos_q2]; q3 = pu1_src_temp[pos_q3]; if(ABS(p0 - q0) < ((alpha >> 2) + 2)) { /* Threshold Variables */ a_p = (UWORD8)ABS(p2 - p0); a_q = (UWORD8)ABS(q2 - q0); if(a_p < beta) { /* p0', p1', p2' */ pu1_src_temp[pos_p0] = ((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3); pu1_src_temp[pos_p1] = ((p2 + p1 + p0 + q0 + 2) >> 2); pu1_src_temp[pos_p2] = ((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); } else { /* p0'*/ pu1_src_temp[pos_p0] = ((X2(p1) + p0 + q1 + 2) >> 2); } if(a_q < beta) { /* q0', q1', q2' */ pu1_src_temp[pos_q0] = (p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3; pu1_src_temp[pos_q1] = (p0 + q0 + q1 + q2 + 2) >> 2; pu1_src_temp[pos_q2] = (X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; } else { /* q0'*/ pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; } } else { /* p0', q0'*/ pu1_src_temp[pos_p0] = ((X2(p1) + p0 + q1 + 2) >> 2); pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_horz_bs4() */ /* */ /* Description : This function performs filtering of a luma block */ /* horizontal edge when the boundary strength is set to 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.4 under the */ /* title "Filtering process for edges for bS equal to 4" in */ /* ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 11 2013 Ittiam Draft */ /* */ /*****************************************************************************/ void ih264_deblk_luma_horz_bs4(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta) { UWORD8 p3, p2, p1, p0, q0, q1, q2, q3; WORD32 pos_p3, pos_p2, pos_p1, pos_p0, pos_q0, pos_q1, pos_q2, pos_q3; UWORD8 a_p, a_q; /* threshold variables */ UWORD8 *pu1_p3; /* pointer to the src sample p3 */ UWORD8 *pu1_p3_temp; UWORD8 *pu1_src_temp; WORD8 i = 0, edge; pu1_p3 = pu1_src - (src_strd << 2); pos_q0 = 0; pos_q1 = src_strd; pos_q2 = X2(src_strd); pos_q3 = X3(src_strd); pos_p0 = X3(src_strd); pos_p1 = X2(src_strd); pos_p2 = src_strd; pos_p3 = 0; for(edge = 0; edge < 4; edge++, pu1_src += 4, pu1_p3 += 4) { pu1_src_temp = pu1_src; pu1_p3_temp = pu1_p3; for(i = 0; i < 4; ++i, pu1_src_temp++, pu1_p3_temp++) { q0 = pu1_src_temp[pos_q0]; q1 = pu1_src_temp[pos_q1]; p0 = pu1_p3_temp[pos_p0]; p1 = pu1_p3_temp[pos_p1]; /* Filter Decision */ if((ABS(p0 - q0) >= alpha) || (ABS(q1 - q0) >= beta) || (ABS(p1 - p0) >= beta)) continue; p2 = pu1_p3_temp[pos_p2]; p3 = pu1_p3_temp[pos_p3]; q2 = pu1_src_temp[pos_q2]; q3 = pu1_src_temp[pos_q3]; if(ABS(p0 - q0) < ((alpha >> 2) + 2)) { /* Threshold Variables */ a_p = ABS(p2 - p0); a_q = ABS(q2 - q0); if((a_p < beta)) { /* p0', p1', p2' */ pu1_p3_temp[pos_p0] = (p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3; pu1_p3_temp[pos_p1] = (p2 + p1 + p0 + q0 + 2) >> 2; pu1_p3_temp[pos_p2] = (X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3; } else { /* p0'*/ pu1_p3_temp[pos_p0] = (X2(p1) + p0 + q1 + 2) >> 2; } if(a_q < beta) { /* q0', q1', q2' */ pu1_src_temp[pos_q0] = (p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3; pu1_src_temp[pos_q1] = (p0 + q0 + q1 + q2 + 2) >> 2; pu1_src_temp[pos_q2] = (X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; } else { /* q0'*/ pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; } } else { /* p0', q0'*/ pu1_p3_temp[pos_p0] = (X2(p1) + p0 + q1 + 2) >> 2; pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bs4_bp() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when the boundary strength is set to 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.4 under the */ /* title "Filtering process for edges for bS equal to 4" in */ /* ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 11 2013 Ittiam Draft */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_vert_bs4_bp(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta) { UWORD8 *pu1_src_u = pu1_src; /* pointer to the src sample q0 of U */ UWORD8 *pu1_src_v = pu1_src + 1; /* pointer to the src sample q0 of V */ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * 2 */ WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; WORD8 i = 0, edge; pos_q0 = 0; pos_q1 = 2; pos_p0 = -2; pos_p1 = -4; for(edge = 0; edge < 4; edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) { pu1_src_temp_u = pu1_src_u; pu1_src_temp_v = pu1_src_v; for(i = 0; i < 2; ++i, pu1_src_temp_u += src_strd, pu1_src_temp_v += src_strd) { q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_src_temp_u[pos_p0]; p1_u = pu1_src_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_src_temp_v[pos_p0]; p1_v = pu1_src_temp_v[pos_p1]; /* Filter Decision */ if((ABS(p0_u - q0_u) < alpha) && (ABS(q1_u - q0_u) < beta) && (ABS(p1_u - p0_u) < beta)) { /* p0' */ pu1_src_temp_u[pos_p0] = ((X2(p1_u) + p0_u + q1_u + 2) >> 2); /* q0' */ pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; } /* Filter Decision */ if((ABS(p0_v - q0_v) < alpha) && (ABS(q1_v - q0_v) < beta) && (ABS(p1_v - p0_v) < beta)) { /* p0' */ pu1_src_temp_v[pos_p0] = ((X2(p1_v) + p0_v + q1_v + 2) >> 2); /* q0' */ pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_horz_bs4_bp() */ /* */ /* Description : This function performs filtering of a chroma block */ /* horizontal edge when the boundary strength is set to 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.4 under the */ /* title "Filtering process for edges for bS equal to 4" in */ /* ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 11 2013 Ittiam Draft */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_horz_bs4_bp(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta) { UWORD8 *pu1_src_u = pu1_src; /* pointer to the src sample q0 of U */ UWORD8 *pu1_src_v = pu1_src + 1; /* pointer to the src sample q0 of V */ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; UWORD8 *pu1_p1_u; /* pointer to the src sample p1 of U */ UWORD8 *pu1_p1_v; /* pointer to the src sample p1 of U */ UWORD8 *pu1_p1_temp_u, *pu1_p1_temp_v; WORD8 i = 0, edge; pu1_p1_u = pu1_src_u - (src_strd << 1); pu1_p1_v = pu1_src_v - (src_strd << 1); pos_q0 = 0; pos_q1 = src_strd; pos_p0 = src_strd; pos_p1 = 0; for(edge = 0; edge < 4; edge++, pu1_src_u += 4, pu1_p1_u += 4, pu1_src_v += 4, pu1_p1_v += 4) { pu1_src_temp_u = pu1_src_u; pu1_p1_temp_u = pu1_p1_u; pu1_src_temp_v = pu1_src_v; pu1_p1_temp_v = pu1_p1_v; for(i = 0; i < 2; ++i, pu1_src_temp_u += 2, pu1_p1_temp_u += 2, pu1_src_temp_v += 2, pu1_p1_temp_v += 2) { q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_p1_temp_u[pos_p0]; p1_u = pu1_p1_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_p1_temp_v[pos_p0]; p1_v = pu1_p1_temp_v[pos_p1]; /* Filter Decision */ if((ABS(p0_u - q0_u) < alpha) && (ABS(q1_u - q0_u) < beta) && (ABS(p1_u - p0_u) < beta)) { /* p0' */ pu1_p1_temp_u[pos_p0] = (X2(p1_u) + p0_u + q1_u + 2) >> 2; /* q0' */ pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; } /* Filter Decision */ if((ABS(p0_v - q0_v) < alpha) && (ABS(q1_v - q0_v) < beta) && (ABS(p1_v - p0_v) < beta)) { /* p0' */ pu1_p1_temp_v[pos_p0] = (X2(p1_v) + p0_v + q1_v + 2) >> 2; /* q0' */ pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_vert_bslt4() */ /* */ /* Description : This function performs filtering of a luma block */ /* vertical edge when the boundary strength is less than 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab - tc0_table */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.3 under the */ /* title "Filtering process for edges for bS less than 4" */ /* in ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 11 2013 Ittiam Draft */ /* */ /*****************************************************************************/ void ih264_deblk_luma_vert_bslt4(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta, UWORD32 u4_bs, const UWORD8 *pu1_cliptab) { WORD8 i = 0, edge; UWORD8 p2, p1, p0, q0, q1, q2; WORD32 pos_p2, pos_p1, pos_p0, pos_q0, pos_q1, pos_q2; UWORD8 a_p, a_q; /* threshold variables */ WORD32 blk_strd = src_strd << 2; /* block_increment = src_strd * 4 */ UWORD8 *pu1_src_temp; WORD8 delta; WORD8 tc; WORD16 val; UWORD8 tc0, u1_bs; pos_q0 = 0; pos_q1 = 1; pos_q2 = 2; pos_p0 = -1; pos_p1 = -2; pos_p2 = -3; for(edge = 0; edge < 4; edge++, pu1_src += blk_strd) { pu1_src_temp = pu1_src; /* Filter Decision */ u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); if(!u1_bs) continue; /* tc0 */ tc0 = pu1_cliptab[u1_bs]; for(i = 0; i < 4; ++i, pu1_src_temp += src_strd) { q0 = pu1_src_temp[pos_q0]; q1 = pu1_src_temp[pos_q1]; p0 = pu1_src_temp[pos_p0]; p1 = pu1_src_temp[pos_p1]; /* Filter Decision */ if((ABS(p0 - q0) >= alpha) || (ABS(q1 - q0) >= beta) || (ABS(p1 - p0) >= beta)) continue; q2 = pu1_src_temp[pos_q2]; p2 = pu1_src_temp[pos_p2]; a_p = ABS(p2 - p0); a_q = ABS(q2 - q0); /* tc */ tc = tc0 + (a_p < beta) + (a_q < beta); val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3); delta = CLIP3(-tc, tc, val); /* p0' */ val = p0 + delta; pu1_src_temp[pos_p0] = CLIP_U8(val); /* q0' */ val = q0 - delta; pu1_src_temp[pos_q0] = CLIP_U8(val); /* Luma only */ if(a_p < beta) { /* p1' */ val = ((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1); pu1_src_temp[pos_p1] += CLIP3(-tc0, tc0, val); } if(a_q < beta) { /* q1' */ val = ((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1); pu1_src_temp[pos_q1] += CLIP3(-tc0, tc0, val); } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bslt4_bp() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when the boundary strength is less than 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab - tc0_table */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.3 under the */ /* title "Filtering process for edges for bS less than 4" */ /* in ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 11 2013 Ittiam Draft */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_vert_bslt4_bp(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta, UWORD32 u4_bs, const UWORD8 *pu1_cliptab) { UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * (4 >> 1)*/ WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; WORD8 i = 0, edge; WORD8 delta; WORD8 tc; WORD16 val; UWORD8 tc0, u1_bs; pos_q0 = 0; pos_q1 = 2; pos_p0 = -2; pos_p1 = -4; for(edge = 0; edge < 4; edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) { pu1_src_temp_u = pu1_src_u; pu1_src_temp_v = pu1_src_v; /* Filter Decision */ u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); if(!u1_bs) continue; /* tc0 */ tc0 = pu1_cliptab[u1_bs]; tc = tc0 + 1; for(i = 0; i < 2; ++i, pu1_src_temp_u += src_strd, pu1_src_temp_v += src_strd) { q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_src_temp_u[pos_p0]; p1_u = pu1_src_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_src_temp_v[pos_p0]; p1_v = pu1_src_temp_v[pos_p1]; /* Filter Decision */ if((ABS(p0_u - q0_u) < alpha) && (ABS(q1_u - q0_u) < beta) && (ABS(p1_u - p0_u) < beta)) { val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); delta = CLIP3(-tc, tc, val); /* p0' */ val = p0_u + delta; pu1_src_temp_u[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_u - delta; pu1_src_temp_u[pos_q0] = CLIP_U8(val); } /* Filter Decision */ if((ABS(p0_v - q0_v) < alpha) && (ABS(q1_v - q0_v) < beta) && (ABS(p1_v - p0_v) < beta)) { val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); delta = CLIP3(-tc, tc, val); /* p0' */ val = p0_v + delta; pu1_src_temp_v[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_v - delta; pu1_src_temp_v[pos_q0] = CLIP_U8(val); } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_horz_bslt4() */ /* */ /* Description : This function performs filtering of a luma block */ /* horizontal edge when boundary strength is less than 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab - tc0_table */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.3 under the */ /* title "Filtering process for edges for bS less than 4" */ /* in ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 11 2013 Ittiam Draft */ /* */ /*****************************************************************************/ void ih264_deblk_luma_horz_bslt4(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta, UWORD32 u4_bs, const UWORD8 *pu1_cliptab) { UWORD8 p2, p1, p0, q0, q1, q2; WORD32 pos_p2, pos_p1, pos_p0, pos_q0, pos_q1, pos_q2; UWORD8 a_p, a_q; /* Threshold variables */ UWORD8 *pu1_p2; /* Pointer to the src sample p2 */ UWORD8 *pu1_p2_temp; UWORD8 *pu1_src_temp; WORD8 i = 0, edge; WORD8 delta; WORD8 tc; WORD16 val; UWORD8 tc0, u1_bs; pu1_p2 = pu1_src - (src_strd << 2); pos_q0 = 0; pos_q1 = src_strd; pos_q2 = X2(src_strd); pos_p0 = X3(src_strd); pos_p1 = X2(src_strd); pos_p2 = src_strd; for(edge = 0; edge < 4; edge++, pu1_src += 4, pu1_p2 += 4) { pu1_src_temp = pu1_src; pu1_p2_temp = pu1_p2; /* Filter Decision */ u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); if(!u1_bs) continue; /* tc0 */ tc0 = pu1_cliptab[u1_bs]; for(i = 0; i < 4; ++i, pu1_src_temp++, pu1_p2_temp++) { q0 = pu1_src_temp[pos_q0]; q1 = pu1_src_temp[pos_q1]; p0 = pu1_p2_temp[pos_p0]; p1 = pu1_p2_temp[pos_p1]; /* Filter Decision */ if((ABS(p0 - q0) >= alpha) || (ABS(q1 - q0) >= beta) || (ABS(p1 - p0) >= beta)) continue; q2 = pu1_src_temp[pos_q2]; p2 = pu1_p2_temp[pos_p2]; a_p = ABS(p2 - p0); a_q = ABS(q2 - q0); /* tc */ tc = tc0 + (a_p < beta) + (a_q < beta); val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3); delta = CLIP3(-tc, tc, val); /* p0' */ val = p0 + delta; pu1_p2_temp[pos_p0] = CLIP_U8(val); /* q0' */ val = q0 - delta; pu1_src_temp[pos_q0] = CLIP_U8(val); /* Luma */ if(a_p < beta) { /* p1' */ val = ((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1); pu1_p2_temp[pos_p1] += CLIP3(-tc0, tc0, val); } if(a_q < beta) { /* q1' */ val = ((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1); pu1_src_temp[pos_q1] += CLIP3(-tc0, tc0, val); } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_horz_bslt4_bp() */ /* */ /* Description : This function performs filtering of a chroma block */ /* horizontal edge when boundary strength is less than 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab - tc0_table */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.3 under the */ /* title "Filtering process for edges for bS less than 4" */ /* in ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 11 2013 Ittiam Draft */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_horz_bslt4_bp(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta, UWORD32 u4_bs, const UWORD8 *pu1_cliptab) { UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; UWORD8 *pu1_p1_u; /* Pointer to the src sample p1 of plane U*/ UWORD8 *pu1_p1_v; /* Pointer to the src sample p1 of plane V*/ UWORD8 *pu1_p1_temp_u, *pu1_p1_temp_v; WORD8 i = 0, edge; WORD8 delta; WORD8 tc; WORD16 val; UWORD8 u1_bs; UWORD8 tc0; pu1_p1_u = pu1_src_u - (src_strd << 1); pu1_p1_v = pu1_src_v - (src_strd << 1); pos_q0 = 0; pos_q1 = src_strd; pos_p0 = src_strd; pos_p1 = 0; for(edge = 0; edge < 4; edge++, pu1_src_u += 4, pu1_p1_u += 4, pu1_src_v += 4, pu1_p1_v += 4) { pu1_src_temp_u = pu1_src_u; pu1_p1_temp_u = pu1_p1_u; pu1_src_temp_v = pu1_src_v; pu1_p1_temp_v = pu1_p1_v; /* Filter Decision */ u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); if(!u1_bs) continue; /* tc0 */ tc0 = pu1_cliptab[u1_bs]; for(i = 0; i < 2; ++i, pu1_src_temp_u += 2, pu1_p1_temp_u += 2, pu1_src_temp_v += 2, pu1_p1_temp_v += 2) { q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_p1_temp_u[pos_p0]; p1_u = pu1_p1_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_p1_temp_v[pos_p0]; p1_v = pu1_p1_temp_v[pos_p1]; /* tc */ tc = tc0 + 1; /* Filter Decision */ if(ABS(p0_u - q0_u) < alpha && ABS(q1_u - q0_u) < beta && ABS(p1_u - p0_u) < beta) { val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); delta = CLIP3(-tc, tc, val); /* p0' */ val = p0_u + delta; pu1_p1_temp_u[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_u - delta; pu1_src_temp_u[pos_q0] = CLIP_U8(val); } /* Filter Decision */ if(ABS(p0_v - q0_v) < alpha && ABS(q1_v - q0_v) < beta && ABS(p1_v - p0_v) < beta) { val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); delta = CLIP3(-tc, tc, val); /* p0' */ val = p0_v + delta; pu1_p1_temp_v[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_v - delta; pu1_src_temp_v[pos_q0] = CLIP_U8(val); } } } } /*****************************************************************************/ /* Function Definitions for vertical edge deblocking for double-call */ /*****************************************************************************/ /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_vert_bs4_mbaff() */ /* */ /* Description : This function performs filtering of a luma block */ /* vertical edge when boundary strength is set to 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* */ /* Globals : None */ /* */ /* Processing : When the function is called twice, this operation is as */ /* described in Sec. 8.7.2.3 under the title "Filtering */ /* process for edges for bS equal to 4" in ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 29 12 2014 Kaushik Draft */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_deblk_luma_vert_bs4_mbaff(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta) { UWORD8 p3, p2, p1, p0, q0, q1, q2, q3; WORD32 pos_p3, pos_p2, pos_p1, pos_p0; WORD32 pos_q0, pos_q1, pos_q2, pos_q3; UWORD8 a_p, a_q; /* threshold variables */ WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * 2 */ UWORD8 *pu1_src_temp; WORD8 i = 0, edge; pos_q0 = 0; pos_q1 = 1; pos_q2 = 2; pos_q3 = 3; pos_p0 = -1; pos_p1 = -2; pos_p2 = -3; pos_p3 = -4; for(edge = 0; edge < 4; edge++, pu1_src += blk_strd) { pu1_src_temp = pu1_src; for(i = 0; i < 2; ++i, pu1_src_temp += src_strd) { q0 = pu1_src_temp[pos_q0]; q1 = pu1_src_temp[pos_q1]; p0 = pu1_src_temp[pos_p0]; p1 = pu1_src_temp[pos_p1]; /* Filter Decision */ if((ABS(p0 - q0) >= alpha) || (ABS(q1 - q0) >= beta) || (ABS(p1 - p0) >= beta)) continue; p2 = pu1_src_temp[pos_p2]; p3 = pu1_src_temp[pos_p3]; q2 = pu1_src_temp[pos_q2]; q3 = pu1_src_temp[pos_q3]; if(ABS(p0 - q0) < ((alpha >> 2) + 2)) { /* Threshold Variables */ a_p = (UWORD8)ABS(p2 - p0); a_q = (UWORD8)ABS(q2 - q0); if(a_p < beta) { /* p0', p1', p2' */ pu1_src_temp[pos_p0] = ((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3); pu1_src_temp[pos_p1] = ((p2 + p1 + p0 + q0 + 2) >> 2); pu1_src_temp[pos_p2] = ((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); } else { /* p0'*/ pu1_src_temp[pos_p0] = ((X2(p1) + p0 + q1 + 2) >> 2); } if(a_q < beta) { /* q0', q1', q2' */ pu1_src_temp[pos_q0] = (p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3; pu1_src_temp[pos_q1] = (p0 + q0 + q1 + q2 + 2) >> 2; pu1_src_temp[pos_q2] = (X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; } else { /* q0'*/ pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; } } else { /* p0', q0'*/ pu1_src_temp[pos_p0] = ((X2(p1) + p0 + q1 + 2) >> 2); pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bs4_mbaff_bp() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when boundary strength is set to 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* */ /* Globals : None */ /* */ /* Processing : When the function is called twice, this operation is as */ /* described in Sec. 8.7.2.3 under the title "Filtering */ /* process for edges for bS equal to 4" in ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 29 12 2014 Kaushik Draft */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_vert_bs4_mbaff_bp(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta) { UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of U */ UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of V */ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 blk_strd = src_strd; WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; WORD8 edge; pos_q0 = 0; pos_q1 = 2; pos_p0 = -2; pos_p1 = -4; for(edge = 0; edge < 4; edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) { pu1_src_temp_u = pu1_src_u; pu1_src_temp_v = pu1_src_v; q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_src_temp_u[pos_p0]; p1_u = pu1_src_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_src_temp_v[pos_p0]; p1_v = pu1_src_temp_v[pos_p1]; /* Filter Decision */ if((ABS(p0_u - q0_u) < alpha) && (ABS(q1_u - q0_u) < beta) && (ABS(p1_u - p0_u) < beta)) { /* p0' */ pu1_src_temp_u[pos_p0] = ((X2(p1_u) + p0_u + q1_u + 2) >> 2); /* q0' */ pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; } /* Filter Decision */ if(ABS(p0_v - q0_v) < alpha && ABS(q1_v - q0_v) < beta && ABS(p1_v - p0_v) < beta) { /* p0' */ pu1_src_temp_v[pos_p0] = ((X2(p1_v) + p0_v + q1_v + 2) >> 2); /* q0' */ pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_vert_bslt4_mbaff() */ /* */ /* Description : This function performs filtering of a luma block */ /* vertical edge when boundary strength is less than 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab - tc0_table */ /* */ /* Globals : None */ /* */ /* Processing : When the function is called twice, this operation is as */ /* described in Sec. 8.7.2.3 under the title "Filtering */ /* process for edges for bS less than 4" in ITU T Rec H.264.*/ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 29 12 2014 Kaushik Draft */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_deblk_luma_vert_bslt4_mbaff(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta, UWORD32 u4_bs, const UWORD8 *pu1_cliptab) { WORD8 i = 0, edge; UWORD8 p2, p1, p0, q0, q1, q2; WORD32 pos_p2, pos_p1, pos_p0, pos_q0, pos_q1, pos_q2; UWORD8 a_p, a_q; /* Threshold variables */ WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * 2 */ UWORD8 *pu1_src_temp; WORD8 delta; WORD8 tc; WORD16 val; UWORD8 tc0, u1_bs; pos_q0 = 0; pos_q1 = 1; pos_q2 = 2; pos_p0 = -1; pos_p1 = -2; pos_p2 = -3; for(edge = 0; edge < 4; edge++, pu1_src += blk_strd) { pu1_src_temp = pu1_src; /* Filter Decision */ u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); if(!u1_bs) continue; /* tc0 */ tc0 = pu1_cliptab[u1_bs]; for(i = 0; i < 2; ++i, pu1_src_temp += src_strd) { q0 = pu1_src_temp[pos_q0]; q1 = pu1_src_temp[pos_q1]; p0 = pu1_src_temp[pos_p0]; p1 = pu1_src_temp[pos_p1]; /* Filter Decision */ if((ABS(p0 - q0) >= alpha) || (ABS(q1 - q0) >= beta) || (ABS(p1 - p0) >= beta)) continue; q2 = pu1_src_temp[pos_q2]; p2 = pu1_src_temp[pos_p2]; a_p = ABS(p2 - p0); a_q = ABS(q2 - q0); /* tc */ tc = tc0 + (a_p < beta) + (a_q < beta); val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3); delta = CLIP3(-tc, tc, val); /* p0' */ val = p0 + delta; pu1_src_temp[pos_p0] = CLIP_U8(val); /* q0' */ val = q0 - delta; pu1_src_temp[pos_q0] = CLIP_U8(val); /* Luma only */ if(a_p < beta) { /* p1' */ val = ((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1); pu1_src_temp[pos_p1] += CLIP3(-tc0, tc0, val); } if(a_q < beta) { /* q1' */ val = ((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1); pu1_src_temp[pos_q1] += CLIP3(-tc0, tc0, val); } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bslt4_mbaff_bp() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when boundary strength is less than 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab - tc0_table */ /* */ /* Globals : None */ /* */ /* Processing : When the function is called twice, this operation is as */ /* described in Sec. 8.7.2.3 under the title "Filtering */ /* process for edges for bS less than 4" in ITU T Rec H.264.*/ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 29 12 2014 Kaushik Draft */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_vert_bslt4_mbaff_bp(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta, UWORD32 u4_bs, const UWORD8 *pu1_cliptab) { UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 blk_strd = src_strd; WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; WORD8 edge; WORD8 delta; WORD8 tc; WORD16 val; UWORD8 tc0, u1_bs; pos_q0 = 0; pos_q1 = 2; pos_p0 = -2; pos_p1 = -4; for(edge = 0; edge < 4; edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) { pu1_src_temp_u = pu1_src_u; pu1_src_temp_v = pu1_src_v; /* Filter Decision */ u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); if(!u1_bs) continue; /* tc0 */ tc0 = pu1_cliptab[u1_bs]; tc = tc0 + 1; q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_src_temp_u[pos_p0]; p1_u = pu1_src_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_src_temp_v[pos_p0]; p1_v = pu1_src_temp_v[pos_p1]; /* Filter Decision */ if((ABS(p0_u - q0_u) < alpha) && (ABS(q1_u - q0_u) < beta) && (ABS(p1_u - p0_u) < beta)) { val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); delta = CLIP3(-tc, tc, val); /* p0' */ val = p0_u + delta; pu1_src_temp_u[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_u - delta; pu1_src_temp_u[pos_q0] = CLIP_U8(val); } /* Filter Decision */ if((ABS(p0_v - q0_v) < alpha) && (ABS(q1_v - q0_v) < beta) && (ABS(p1_v - p0_v) < beta)) { val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); delta = CLIP3(-tc, tc, val); /* p0' */ val = p0_v + delta; pu1_src_temp_v[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_v - delta; pu1_src_temp_v[pos_q0] = CLIP_U8(val); } } } /*****************************************************************************/ /* Function Definitions for chroma deblocking in high profile */ /*****************************************************************************/ /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bs4() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when the boundary strength is set to 4 in */ /* high profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.4 under the */ /* title "Filtering process for edges for bS equal to 4" in */ /* ITU T Rec H.264 with alpha and beta values different in */ /* U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 29 12 2014 Kaushik Draft */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_vert_bs4(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr) { UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of U */ UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of V */ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * 2*/ WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; WORD8 i = 0, edge; pos_q0 = 0; pos_q1 = 2; pos_p0 = -2; pos_p1 = -4; for(edge = 0; edge < 4; edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) { pu1_src_temp_u = pu1_src_u; pu1_src_temp_v = pu1_src_v; for(i = 0; i < 2; ++i, pu1_src_temp_u += src_strd, pu1_src_temp_v += src_strd) { q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_src_temp_u[pos_p0]; p1_u = pu1_src_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_src_temp_v[pos_p0]; p1_v = pu1_src_temp_v[pos_p1]; /* Filter Decision */ if((ABS(p0_u - q0_u) < alpha_cb) && (ABS(q1_u - q0_u) < beta_cb) && (ABS(p1_u - p0_u) < beta_cb)) { /* p0' */ pu1_src_temp_u[pos_p0] = ((X2(p1_u) + p0_u + q1_u + 2) >> 2); /* q0' */ pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; } /* Filter Decision */ if((ABS(p0_v - q0_v) < alpha_cr) && (ABS(q1_v - q0_v) < beta_cr) && (ABS(p1_v - p0_v) < beta_cr)) { /* p0' */ pu1_src_temp_v[pos_p0] = ((X2(p1_v) + p0_v + q1_v + 2) >> 2); /* q0' */ pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_horz_bs4() */ /* */ /* Description : This function performs filtering of a chroma block */ /* horizontal edge when the boundary strength is set to 4 */ /* in high profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.4 under the */ /* title "Filtering process for edges for bS equal to 4" in */ /* ITU T Rec H.264 with alpha and beta values different in */ /* U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 29 12 2014 Kaushik Draft */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_horz_bs4(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr) { UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of U */ UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of V */ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; UWORD8 *pu1_p1_u; /* Pointer to the src sample p1 of U */ UWORD8 *pu1_p1_v; /* Pointer to the src sample p1 of U */ UWORD8 *pu1_p1_temp_u, *pu1_p1_temp_v; WORD8 i = 0, edge; pu1_p1_u = pu1_src_u - (src_strd << 1); pu1_p1_v = pu1_src_v - (src_strd << 1); pos_q0 = 0; pos_q1 = src_strd; pos_p0 = src_strd; pos_p1 = 0; for(edge = 0; edge < 4; edge++, pu1_src_u += 4, pu1_p1_u += 4, pu1_src_v += 4, pu1_p1_v += 4) { pu1_src_temp_u = pu1_src_u; pu1_p1_temp_u = pu1_p1_u; pu1_src_temp_v = pu1_src_v; pu1_p1_temp_v = pu1_p1_v; for(i = 0; i < 2; ++i, pu1_src_temp_u += 2, pu1_p1_temp_u += 2, pu1_src_temp_v += 2, pu1_p1_temp_v += 2) { q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_p1_temp_u[pos_p0]; p1_u = pu1_p1_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_p1_temp_v[pos_p0]; p1_v = pu1_p1_temp_v[pos_p1]; /* Filter Decision */ if(ABS(p0_u - q0_u) < alpha_cb && ABS(q1_u - q0_u) < beta_cb && ABS(p1_u - p0_u) < beta_cb) { /* p0' */ pu1_p1_temp_u[pos_p0] = (X2(p1_u) + p0_u + q1_u + 2) >> 2; /* q0' */ pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; } /* Filter Decision */ if(ABS(p0_v - q0_v) < alpha_cr && ABS(q1_v - q0_v) < beta_cr && ABS(p1_v - p0_v) < beta_cr) { /* p0' */ pu1_p1_temp_v[pos_p0] = (X2(p1_v) + p0_v + q1_v + 2) >> 2; /* q0' */ pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bslt4() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when the boundary strength is less than 4 */ /* in high profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab_cb - tc0_table for U */ /* pu1_cliptab_cr - tc0_table for V */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.3 under the */ /* title "Filtering process for edges for bS less than 4" */ /* in ITU T Rec H.264 with alpha and beta values different */ /* in U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 29 12 2014 Kaushik Draft */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_vert_bslt4(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr, UWORD32 u4_bs, const UWORD8 *pu1_cliptab_cb, const UWORD8 *pu1_cliptab_cr) { UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * 2 */ WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; WORD8 i = 0, edge; WORD8 delta; WORD8 tcb, tcr; WORD16 val; UWORD8 tcb0, tcr0, u1_bs; pos_q0 = 0; pos_q1 = 2; pos_p0 = -2; pos_p1 = -4; for(edge = 0; edge < 4; edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) { pu1_src_temp_u = pu1_src_u; pu1_src_temp_v = pu1_src_v; /* Filter Decision */ u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); if(!u1_bs) continue; /* tc0 */ tcb0 = pu1_cliptab_cb[u1_bs]; tcr0 = pu1_cliptab_cr[u1_bs]; tcb = tcb0 + 1; tcr = tcr0 + 1; for(i = 0; i < 2; ++i, pu1_src_temp_u += src_strd, pu1_src_temp_v += src_strd) { q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_src_temp_u[pos_p0]; p1_u = pu1_src_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_src_temp_v[pos_p0]; p1_v = pu1_src_temp_v[pos_p1]; /* Filter Decision */ if(ABS(p0_u - q0_u) < alpha_cb && ABS(q1_u - q0_u) < beta_cb && ABS(p1_u - p0_u) < beta_cb) { val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); delta = CLIP3(-tcb, tcb, val); /* p0' */ val = p0_u + delta; pu1_src_temp_u[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_u - delta; pu1_src_temp_u[pos_q0] = CLIP_U8(val); } /* Filter Decision */ if(ABS(p0_v - q0_v) < alpha_cr && ABS(q1_v - q0_v) < beta_cr && ABS(p1_v - p0_v) < beta_cr) { val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); delta = CLIP3(-tcr, tcr, val); /* p0' */ val = p0_v + delta; pu1_src_temp_v[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_v - delta; pu1_src_temp_v[pos_q0] = CLIP_U8(val); } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_horz_bslt4() */ /* */ /* Description : This function performs filtering of a chroma block */ /* horizontal edge when the boundary strength is less than */ /* 4 in high profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab_cb - tc0_table for U */ /* pu1_cliptab_cr - tc0_table for V */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.3 under the */ /* title "Filtering process for edges for bS less than 4" */ /* in ITU T Rec H.264 with alpha and beta values different */ /* in U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 29 12 2014 Kaushik Draft */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_horz_bslt4(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr, UWORD32 u4_bs, const UWORD8 *pu1_cliptab_cb, const UWORD8 *pu1_cliptab_cr) { UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; UWORD8 *pu1_p1_u; /* Pointer to the src sample p1 of plane U*/ UWORD8 *pu1_p1_v; /* Pointer to the src sample p1 of plane V*/ UWORD8 *pu1_p1_temp_u, *pu1_p1_temp_v; WORD8 i = 0, edge; WORD8 delta; WORD8 tcb, tcr; WORD16 val; UWORD8 u1_bs; UWORD8 tcb0, tcr0; pu1_p1_u = pu1_src_u - (src_strd << 1); pu1_p1_v = pu1_src_v - (src_strd << 1); pos_q0 = 0; pos_q1 = src_strd; pos_p0 = src_strd; pos_p1 = 0; for(edge = 0; edge < 4; edge++, pu1_src_u += 4, pu1_p1_u += 4, pu1_src_v += 4, pu1_p1_v += 4) { pu1_src_temp_u = pu1_src_u; pu1_p1_temp_u = pu1_p1_u; pu1_src_temp_v = pu1_src_v; pu1_p1_temp_v = pu1_p1_v; /* Filter Decision */ u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); if(!u1_bs) continue; /* tc0 */ tcb0 = pu1_cliptab_cb[u1_bs]; tcr0 = pu1_cliptab_cr[u1_bs]; for(i = 0; i < 2; ++i, pu1_src_temp_u += 2, pu1_p1_temp_u += 2, pu1_src_temp_v += 2, pu1_p1_temp_v += 2) { q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_p1_temp_u[pos_p0]; p1_u = pu1_p1_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_p1_temp_v[pos_p0]; p1_v = pu1_p1_temp_v[pos_p1]; /* tc */ tcb = tcb0 + 1; tcr = tcr0 + 1; /* Filter Decision */ if(ABS(p0_u - q0_u) < alpha_cb && ABS(q1_u - q0_u) < beta_cb && ABS(p1_u - p0_u) < beta_cb) { val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); delta = CLIP3(-tcb, tcb, val); /* p0' */ val = p0_u + delta; pu1_p1_temp_u[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_u - delta; pu1_src_temp_u[pos_q0] = CLIP_U8(val); } /* Filter Decision */ if(ABS(p0_v - q0_v) < alpha_cr && ABS(q1_v - q0_v) < beta_cr && ABS(p1_v - p0_v) < beta_cr) { val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); delta = CLIP3(-tcr, tcr, val); /* p0' */ val = p0_v + delta; pu1_p1_temp_v[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_v - delta; pu1_src_temp_v[pos_q0] = CLIP_U8(val); } } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bs4_mbaff() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when boundary strength is set to 4 in high */ /* profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab_cb - tc0_table for U */ /* pu1_cliptab_cr - tc0_table for V */ /* */ /* Globals : None */ /* */ /* Processing : When the function is called twice, this operation is as */ /* described in Sec. 8.7.2.4 under the title "Filtering */ /* process for edges for bS equal to 4" in ITU T Rec H.264 */ /* with alpha and beta values different in U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 29 12 2014 Kaushik Draft */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_vert_bs4_mbaff(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr) { UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of U */ UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of V */ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 blk_strd = src_strd; WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; WORD8 edge; pos_q0 = 0; pos_q1 = 2; pos_p0 = -2; pos_p1 = -4; for(edge = 0; edge < 4; edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) { pu1_src_temp_u = pu1_src_u; pu1_src_temp_v = pu1_src_v; q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_src_temp_u[pos_p0]; p1_u = pu1_src_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_src_temp_v[pos_p0]; p1_v = pu1_src_temp_v[pos_p1]; /* Filter Decision */ if((ABS(p0_u - q0_u) < alpha_cb) && (ABS(q1_u - q0_u) < beta_cb) && (ABS(p1_u - p0_u) < beta_cb)) { /* p0' */ pu1_src_temp_u[pos_p0] = ((X2(p1_u) + p0_u + q1_u + 2) >> 2); /* q0' */ pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; } /* Filter Decision */ if((ABS(p0_v - q0_v) < alpha_cr) && (ABS(q1_v - q0_v) < beta_cr) && (ABS(p1_v - p0_v) < beta_cr)) { /* p0' */ pu1_src_temp_v[pos_p0] = ((X2(p1_v) + p0_v + q1_v + 2) >> 2); /* q0' */ pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; } } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bslt4_mbaff() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when boundary strength is less than 4 in */ /* high profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab_cb - tc0_table for U */ /* pu1_cliptab_cr - tc0_table for V */ /* */ /* Globals : None */ /* */ /* Processing : When the function is called twice, this operation is as */ /* described in Sec. 8.7.2.4 under the title "Filtering */ /* process for edges for bS less than 4" in ITU T Rec H.264 */ /* with alpha and beta values different in U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 29 12 2014 Kaushik Draft */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_deblk_chroma_vert_bslt4_mbaff(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr, UWORD32 u4_bs, const UWORD8 *pu1_cliptab_cb, const UWORD8 *pu1_cliptab_cr) { UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; WORD32 blk_strd = src_strd; WORD32 pos_p1, pos_p0, pos_q0, pos_q1; UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; WORD8 edge; WORD8 delta; WORD8 tcb, tcr; WORD16 val; UWORD8 tcb0, tcr0, u1_bs; pos_q0 = 0; pos_q1 = 2; pos_p0 = -2; pos_p1 = -4; for(edge = 0; edge < 4; edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) { pu1_src_temp_u = pu1_src_u; pu1_src_temp_v = pu1_src_v; /* Filter Decision */ u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); if(!u1_bs) continue; /* tc0 */ tcb0 = pu1_cliptab_cb[u1_bs]; tcr0 = pu1_cliptab_cr[u1_bs]; tcb = tcb0 + 1; tcr = tcr0 + 1; q0_u = pu1_src_temp_u[pos_q0]; q1_u = pu1_src_temp_u[pos_q1]; p0_u = pu1_src_temp_u[pos_p0]; p1_u = pu1_src_temp_u[pos_p1]; q0_v = pu1_src_temp_v[pos_q0]; q1_v = pu1_src_temp_v[pos_q1]; p0_v = pu1_src_temp_v[pos_p0]; p1_v = pu1_src_temp_v[pos_p1]; /* Filter Decision */ if((ABS(p0_u - q0_u) < alpha_cb) && (ABS(q1_u - q0_u) < beta_cb) && (ABS(p1_u - p0_u) < beta_cb)) { val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); delta = CLIP3(-tcb, tcb, val); /* p0' */ val = p0_u + delta; pu1_src_temp_u[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_u - delta; pu1_src_temp_u[pos_q0] = CLIP_U8(val); } /* Filter Decision */ if((ABS(p0_v - q0_v) < alpha_cr) && (ABS(q1_v - q0_v) < beta_cr) && (ABS(p1_v - p0_v) < beta_cr)) { val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); delta = CLIP3(-tcr, tcr, val); /* p0' */ val = p0_v + delta; pu1_src_temp_v[pos_p0] = CLIP_U8(val); /* q0' */ val = q0_v - delta; pu1_src_temp_v[pos_q0] = CLIP_U8(val); } } } ================================================ FILE: dependencies/ih264d/common/ih264_deblk_edge_filters.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_deblk_edge_filters.h * * @brief * This file contains declarations of functions used for deblocking * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef IH264_DEBLK_H_ #define IH264_DEBLK_H_ /*****************************************************************************/ /* Extern Function Declarations */ /*****************************************************************************/ typedef void ih264_deblk_edge_bslt4_ft(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta, UWORD32 u4_bs, const UWORD8 *pu1_cliptab ); typedef void ih264_deblk_edge_bs4_ft(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta ); typedef void ih264_deblk_chroma_edge_bslt4_ft(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr, UWORD32 u4_bs, const UWORD8 *pu1_cliptab_cb, const UWORD8 *pu1_cliptab_cr); typedef void ih264_deblk_chroma_edge_bs4_ft(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr); ih264_deblk_edge_bs4_ft ih264_deblk_luma_horz_bs4; ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4; ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_mbaff; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_horz_bs4_bp; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_bp; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_bp; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_horz_bslt4; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_mbaff; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_bp; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_bp; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_bp; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_mbaff; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_mbaff; /*A9*/ ih264_deblk_edge_bs4_ft ih264_deblk_luma_horz_bs4_a9; ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_a9; ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_mbaff_a9; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_horz_bs4_bp_a9; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_bp_a9; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_bp_a9; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_horz_bslt4_a9; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_a9; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_mbaff_a9; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_bp_a9; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_bp_a9; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_bp_a9; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_a9; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_a9; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_a9; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_mbaff_a9; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_a9; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_a9; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_a9; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_mbaff_a9; /*AV8*/ ih264_deblk_edge_bs4_ft ih264_deblk_luma_horz_bs4_av8; ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_av8; ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_mbaff_av8; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_horz_bs4_bp_av8; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_bp_av8; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_bp_av8; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_horz_bslt4_av8; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_av8; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_mbaff_av8; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_bp_av8; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_bp_av8; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_bp_av8; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_av8; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_av8; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_av8; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_mbaff_av8; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_av8; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_av8; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_av8; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_mbaff_av8; /*SSE3*/ ih264_deblk_edge_bs4_ft ih264_deblk_luma_horz_bs4_ssse3; ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_ssse3; ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_mbaff_ssse3; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_horz_bs4_bp_ssse3; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_bp_ssse3; ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_bp_ssse3; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_horz_bslt4_ssse3; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_ssse3; ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_mbaff_ssse3; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_bp_ssse3; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_bp_ssse3; ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_bp_ssse3; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_ssse3; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_ssse3; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_ssse3; ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_mbaff_ssse3; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_ssse3; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_ssse3; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_ssse3; ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_mbaff_ssse3; #endif /* IH264_DEBLK_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_deblk_tables.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_deblk_tables.c * * @brief * Contains tables used for deblocking * * @author * Ittiam * * @par List of Tables: * - guc_ih264_qp_scale_cr[] * - guc_ih264_alpha_table[] * - guc_ih264_beta_table[] * - guc_ih264_clip_table[][] * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include /* User include files */ #include "ih264_typedefs.h" #include "ih264_deblk_tables.h" /*****************************************************************************/ /* Extern global definitions */ /*****************************************************************************/ /** ****************************************************************************** * @brief alpha & beta tables for deblocking * input : indexA [0-51] & indexB [0-51] * output : alpha & beta * * @remarks Table 8-16 in H264 Specification, * Derivation of offset dependent threshold variables * alpha and beta from indexA and indexB ****************************************************************************** */ const UWORD8 gu1_ih264_alpha_table[52] = { /* indexA :: 0-51 inclusive */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 17, 20, 22, 25, 28, 32, 36, 40, 45, 50, 56, 63, 71, 80, 90, 101, 113, 127, 144, 162, 182, 203, 226, 255, 255, }; const UWORD8 gu1_ih264_beta_table[52] = { /* indexB :: 0-51 inclusive */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, }; /** ****************************************************************************** * @brief t'C0 table for deblocking * input : indexA [0-51] and bS [1,3] * output : t'C0 * * @remarks Table 8-17 in H264 Specification, * Value of variable t'C0 as a function of indexA and bS ****************************************************************************** */ const UWORD8 gu1_ih264_clip_table[52][4] = { /* indexA :: 0-51 inclusive */ { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 1}, { 0, 0, 0, 1}, { 0, 0, 0, 1}, { 0, 0, 0, 1}, { 0, 0, 1, 1}, { 0, 0, 1, 1}, { 0, 1, 1, 1}, { 0, 1, 1, 1}, { 0, 1, 1, 1}, { 0, 1, 1, 1}, { 0, 1, 1, 2}, { 0, 1, 1, 2}, { 0, 1, 1, 2}, { 0, 1, 1, 2}, { 0, 1, 2, 3}, { 0, 1, 2, 3}, { 0, 2, 2, 3}, { 0, 2, 2, 4}, { 0, 2, 3, 4}, { 0, 2, 3, 4}, { 0, 3, 3, 5}, { 0, 3, 4, 6}, { 0, 3, 4, 6}, { 0, 4, 5, 7}, { 0, 4, 5, 8}, { 0, 4, 6, 9}, { 0, 5, 7,10}, { 0, 6, 8,11}, { 0, 6, 8,13}, { 0, 7,10,14}, { 0, 8,11,16}, { 0, 9,12,18}, { 0,10,13,20}, { 0,11,15,23}, { 0,13,17,25}, }; ================================================ FILE: dependencies/ih264d/common/ih264_deblk_tables.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_deblk_tables.h * * @brief * This file contains declarations of tables used for deblocking * * @author * Ittiam * * @par List of Functions: * * @remarks * None * ******************************************************************************* */ #ifndef IH264_DEBLK_TABLES_H_ #define IH264_DEBLK_TABLES_H_ /*****************************************************************************/ /* Extern global declarations */ /*****************************************************************************/ /** ****************************************************************************** * @brief alpha & beta tables for deblocking * input : indexA [0-51] & indexB [0-51] * output : alpha & beta * * @remarks Table 8-16 in H264 Specification, * Derivation of offset dependent threshold variables * alpha and beta from indexA and indexB ****************************************************************************** */ extern const UWORD8 gu1_ih264_alpha_table[52]; extern const UWORD8 gu1_ih264_beta_table[52]; /** ****************************************************************************** * @brief t'C0 table for deblocking * input : indexA [0-51] and bS [1,3] * output : t'C0 * * @remarks Table 8-17 in H264 Specification, * Value of variable t'C0 as a function of indexA and bS ****************************************************************************** */ extern const UWORD8 gu1_ih264_clip_table[52][4]; #endif /* IH264_DEBLK_TABLES_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_debug.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_debug.h * * @brief * Definitions for codec debugging * * @author * Ittiam * * @par List of Functions: * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_DEBUG_H_ #define _IH264_DEBUG_H_ #if DEBUG_PRINT #define DEBUG(...) \ { \ printf("\n[H264 DBG] %s/%d:: ", __FUNCTION__, __LINE__); \ printf(__VA_ARGS__); \ } #else #define DEBUG(...) {} #endif #define ASSERT(x) assert((x)) #endif /* _IH264_DEBUG_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_defs.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_defs.h * * @brief * Definitions used in the codec * * @author * Ittiam * * * @remarks * None * ******************************************************************************* */ #ifndef IH264_DEFS_H_ #define IH264_DEFS_H_ /*****************************************************************************/ /* Enums */ /*****************************************************************************/ /*****************************************************************************/ /* Profile and Levels */ /*****************************************************************************/ /** ****************************************************************************** * @enum PROFILE_IDC * @brief Defines the set of possible profiles ****************************************************************************** */ enum { IH264_PROFILE_BASELINE = 66, IH264_PROFILE_MAIN = 77, IH264_PROFILE_EXTENDED = 88, IH264_PROFILE_HIGH = 100, IH264_PROFILE_HIGH10 = 110, IH264_PROFILE_HIGH422 = 122, IH264_PROFILE_HIGH444 = 144, }; /** ****************************************************************************** * @enum LEVEL_IDC * @brief Defines the set of possible levels ****************************************************************************** */ typedef enum { IH264_LEVEL_10 = 10, IH264_LEVEL_1B = 9, IH264_LEVEL_11 = 11, IH264_LEVEL_12 = 12, IH264_LEVEL_13 = 13, IH264_LEVEL_20 = 20, IH264_LEVEL_21 = 21, IH264_LEVEL_22 = 22, IH264_LEVEL_30 = 30, IH264_LEVEL_31 = 31, IH264_LEVEL_32 = 32, IH264_LEVEL_40 = 40, IH264_LEVEL_41 = 41, IH264_LEVEL_42 = 42, IH264_LEVEL_50 = 50, IH264_LEVEL_51 = 51, }IH264_LEVEL_T; /** ****************************************************************************** * @enum PIC TYPES * @brief Defines the set of possible picture type - not signaled in bitstream ****************************************************************************** */ typedef enum { PIC_NA = 0x7FFFFFFF, PIC_IDR = 0, PIC_I = 1, PIC_P = 2, PIC_B = 3, PIC_P_NONREF = 4, PIC_B_NONREF = 5, PIC_MAX, }PIC_TYPE_T; /** ****************************************************************************** * @enum FRAME-FIELD types * @brief Defines the set of possible field types. ****************************************************************************** */ enum { TOP_FIELD, BOTTOM_FIELD, FRAME, }; /** ****************************************************************************** * @enum SLICE TYPES * @brief Defines the set of possible SLICE TYPES ****************************************************************************** */ enum { PSLICE = 0, BSLICE = 1, ISLICE = 2, SPSLICE = 3, SISLICE = 4, MAXSLICE_TYPE, }; /** ****************************************************************************** * @enum NAL_UNIT_TYPE * @brief Defines the set of possible nal unit types ****************************************************************************** */ enum { NAL_UNSPEC_0 = 0, NAL_SLICE_NON_IDR = 1, NAL_SLICE_DPA = 2, NAL_SLICE_DPB = 3, NAL_SLICE_DPC = 4, NAL_SLICE_IDR = 5, NAL_SEI = 6, NAL_SPS = 7, NAL_PPS = 8, NAL_AUD = 9, NAL_EOSEQ = 10, NAL_EOSTR = 11, NAL_FILLER = 12, NAL_SPSE = 13, NAL_RES_18 = 14, NAL_AUX_PIC = 19, NAL_RES_23 = 20, NAL_UNSPEC_31 = 24, }; /** ****************************************************************************** * @enum CHROMA_FORMAT_IDC * @brief Defines the set of possible chroma formats * Note Chorma format Do not change enum values ****************************************************************************** */ enum { CHROMA_FMT_IDC_MONOCHROME = 0, CHROMA_FMT_IDC_YUV420 = 1, CHROMA_FMT_IDC_YUV422 = 2, CHROMA_FMT_IDC_YUV444 = 3, CHROMA_FMT_IDC_YUV444_PLANES = 4, }; /** ****************************************************************************** * @enum MBMODES_I16x16 * @brief Defines the set of possible intra 16x16 mb modes ****************************************************************************** */ typedef enum { VERT_I16x16 = 0, HORZ_I16x16 = 1, DC_I16x16 = 2, PLANE_I16x16 = 3, MAX_I16x16 = 4, }MBMODES_I16x16; /** ****************************************************************************** * @enum MBMODES_I4x4 * @brief Defines the set of possible intra 4x4 mb modes ****************************************************************************** */ typedef enum { VERT_I4x4 = 0, HORZ_I4x4 = 1, DC_I4x4 = 2, DIAG_DL_I4x4 = 3, DIAG_DR_I4x4 = 4, VERT_R_I4x4 = 5, HORZ_D_I4x4 = 6, VERT_L_I4x4 = 7, HORZ_U_I4x4 = 8, MAX_I4x4 = 9, }MBMODES_I4x4; /** ****************************************************************************** * @enum MBMODES_I8x8 * @brief Defines the set of possible intra 8x8 mb modes ****************************************************************************** */ typedef enum { VERT_I8x8 = 0, HORZ_I8x8 = 1, DC_I8x8 = 2, DIAG_DL_I8x8 = 3, DIAG_DR_I8x8 = 4, VERT_R_I8x8 = 5, HORZ_D_I8x8 = 6, VERT_L_I8x8 = 7, HORZ_U_I8x8 = 8, MAX_I8x8 = 9, }MBMODES_I8x8; /** ****************************************************************************** * @enum MBMODES_CHROMA_I8x8 (Chroma) * @brief Defines the set of possible intra 8x8 mb modes for chroma ****************************************************************************** */ typedef enum { DC_CH_I8x8 = 0, HORZ_CH_I8x8 = 1, VERT_CH_I8x8 = 2, PLANE_CH_I8x8 = 3, MAX_CH_I8x8 = 4, }MBMODES_CHROMA_I8x8; /** ****************************************************************************** * @enum MBTYPES * @brief Defines the set of possible macro block types ****************************************************************************** */ typedef enum { I16x16 = 0, I4x4 = 1, I8x8 = 2, P16x16 = 3, P16x8 = 4, P8x16 = 5, P8x8 = 6, PSKIP = 7, IPCM = 8, B16x16 = 9, BSKIP = 10, BDIRECT = 11, MAX_MBTYPES, }MBTYPES_T; /* Prediction list */ /* Do not change enum values */ enum { PRED_L0 = 0, PRED_L1 = 1, PRED_BI = 2 }; /** ****************************************************************************** * @enum ENTROPY_BLK_TYPE * @brief Defines the nature of blocks employed in entropy coding ****************************************************************************** */ typedef enum { ENTROPY_BLK_INVALID = -1, CAVLC_LUMA_4x4_DC = 0, CAVLC_LUMA_4x4_AC = 1, CAVLC_LUMA_4x4 = 2, CAVLC_CHROMA_4x4_DC = 3, CAVLC_CHROMA_4x4_AC = 4, } ENTROPY_BLK_TYPE; /** ****************************************************************************** * @enum ENTROPY_MODE * @brief Entropy coding modes ****************************************************************************** */ typedef enum { CAVLC = 0, CABAC = 1, } ENTROPY_MODE; /** ****************************************************************************** * @enum COMPONENT_TYPE * @brief components Y, U & V ****************************************************************************** */ typedef enum { Y, U, V, } COMPONENT_TYPE; /** ****************************************************************************** * @enum MBPART_PREDMODE_T * @brief MbPartps_pred_mode_ctxt Table 7-11 to 7-14 ****************************************************************************** */ typedef enum { MBPART_NA, MBPART_I4x4, MBPART_I8x8, MBPART_I16x16, MBPART_L0, MBPART_L1, MBPART_BI, MBPART_DIRECT, MBPART_IPCM, }MBPART_PREDMODE_T; typedef enum { I_NxN, I_16x16_0_0_0, I_16x16_1_0_0, I_16x16_2_0_0, I_16x16_3_0_0, I_16x16_0_1_0, I_16x16_1_1_0, I_16x16_2_1_0, I_16x16_3_1_0, I_16x16_0_2_0, I_16x16_1_2_0, I_16x16_2_2_0, I_16x16_3_2_0, I_16x16_0_0_1, I_16x16_1_0_1, I_16x16_2_0_1, I_16x16_3_0_1, I_16x16_0_1_1, I_16x16_1_1_1, I_16x16_2_1_1, I_16x16_3_1_1, I_16x16_0_2_1, I_16x16_1_2_1, I_16x16_2_2_1, I_16x16_3_2_1, I_PCM, }MBTYPE_ISLICE_T; typedef enum { P_L0_16x16, P_L0_L0_16x8, P_L0_L0_8x16, P_8x8, P_8x8REF0, P_SKIP }MBTYPE_PSLICE_T; typedef enum { B_DIRECT_16x16, B_L0_16x16, B_L1_16x16, B_BI_16x16, B_L0_L0_16x8, B_L0_L0_8x16, B_L1_L1_16x8, B_L1_L1_8x16, B_L0_L1_16x8, B_L0_L1_8x16, B_L1_L0_16x8, B_L1_L0_8x16, B_L0_BI_16x8, B_L0_BI_8x16, B_L1_BI_16x8, B_L1_BI_8x16, B_BI_L0_16x8, B_BI_L0_8x16, B_BI_L1_16x8, B_BI_L1_8x16, B_BI_BI_16x8, B_BI_BI_8x16, B_8x8, B_SKIP, }MBTYPE_BSLICE_T; typedef enum { P_L0_8x8, P_L0_8x4, P_L0_4x8, P_L0_4x4, }SUBMBTYPE_PSLICE_T; typedef enum { B_DIRECT_8x8, B_L0_8x8, B_L1_8x8, B_BI_8x8, B_L0_8x4, B_L0_4x8, B_L1_8x4, B_L1_4x8, B_BI_8x4, B_BI_4x8, B_L0_4x4, B_L1_4x4, B_BI_4x4, }SUBMBTYPE_BSLICE_T; /** * DC Mode pattern for 4 4x4 sub blocks in an MB row */ #define DC_I16X16_MB_ROW (DC_I16x16 << 24) | (DC_I16x16 << 16) | \ (DC_I16x16 << 8) | DC_I16x16 /*****************************************************************************/ /* Constant Macros */ /*****************************************************************************/ /*****************************************************************************/ /* Reference frame defs */ /*****************************************************************************/ /* Maximum DPB size */ #define MAX_DPB_SIZE 16 /* Maximum mmco commands in slice header */ #define MAX_MMCO_COMMANDS 32 /* Maximum reference reorder idc */ #define MAX_MODICATION_IDC 32 /*****************************************************************************/ /* SPS restrictions */ /*****************************************************************************/ /* Number of SPS allowed */ /* An extra buffer is allocated to write the parsed data * It is copied to the appropriate location later */ #define MAX_SPS_CNT (32 + 1) /* Maximum long term reference pics */ #define MAX_LTREF_PICS_SPS 16 /* Maximum short term reference pics */ #define MAX_STREF_PICS_SPS 64 /*****************************************************************************/ /* PPS restrictions */ /*****************************************************************************/ /* Number of PPS allowed */ /* An extra buffer is allocated to write the parsed data * It is copied to the appropriate location later */ #define MAX_PPS_CNT (256 + 1) /*****************************************************************************/ /* Macro definitions for sizes of MB, PU, TU, CU */ /*****************************************************************************/ #define MB_SIZE 16 #define BLK8x8SIZE 8 #define BLK_SIZE 4 /* TU Size Range */ #define MAX_TU_SIZE 8 #define MIN_TU_SIZE 4 /* Max Transform Size */ #define MAX_TRANS_SIZE (MAX_TU_SIZE*MAX_TU_SIZE) /* PU Size Range */ #define MAX_PU_SIZE 16 #define MIN_PU_SIZE 4 /* Number of max TU in a MB row */ #define MAX_TU_IN_MB_ROW ((MB_SIZE / MIN_TU_SIZE)) /* Number of max PU in a CTb row */ #define MAX_PU_IN_MB_ROW ((MB_SIZE / MIN_PU_SIZE)) /* Number of max PU in a MB */ /*****************************************************************************/ /* Note though for 64 x 64 MB, Max PU in MB is 128, in order to store */ /* intra pred info, 256 entries are needed */ /*****************************************************************************/ #define MAX_PU_IN_MB ((MB_SIZE / MIN_PU_SIZE) * \ (MB_SIZE / MIN_PU_SIZE)) /* Number of max TU in a MB */ #define MAX_TU_IN_MB ((MB_SIZE / MIN_TU_SIZE) * \ (MB_SIZE / MIN_TU_SIZE)) /** * Maximum transform depths */ #define MAX_TRAFO_DEPTH 5 #define MAX_DC_4x4_SUBBLK_LUMA 1 #define MAX_AC_4x4_SUBBLK_LUMA 16 #define MAX_DC_4x4_SUBBLK_CHROMA 2 #define MAX_AC_4x4_SUBBLK_CHROMA 8 #define MAX_4x4_SUBBLKS (MAX_DC_4x4_SUBBLK_LUMA + MAX_DC_4x4_SUBBLK_CHROMA +\ MAX_AC_4x4_SUBBLK_LUMA + MAX_AC_4x4_SUBBLK_CHROMA) /* Max number of deblocking edges */ #define MAX_VERT_DEBLK_EDGES ((MB_SIZE/8) * (MB_SIZE/4)) #define MAX_HORZ_DEBLK_EDGES ((MB_SIZE/4) * (MB_SIZE/8)) /* Qp can not change below 8x8 level */ #define MAX_DEBLK_QP_CNT ((MB_SIZE/8) * (MB_SIZE/8)) /*****************************************************************************/ /* Parsing related macros */ /*****************************************************************************/ #define SUBBLK_COEFF_CNT 16 /* Quant and Trans defs */ /*****************************************************************************/ /* Sizes for Transform functions */ /*****************************************************************************/ #define TRANS_SIZE_4 4 #define TRANS_SIZE_8 8 #define TRANS_SIZE_16 16 #define TRANS_SIZE_32 32 #define IT_SHIFT_STAGE_1 7 #define IT_SHIFT_STAGE_2 12 /** * @breif Maximum transform dynamic range (excluding sign bit) */ #define MAX_TR_DYNAMIC_RANGE 15 /** * @brief Q(QP%6) * IQ(QP%6) = 2^20 */ #define QUANT_IQUANT_SHIFT 20 /** * @breif Q factor for Qp%6 multiplication */ #define QUANT_SHIFT 14 /** * @breif Q shift factor for flat rescale matrix weights */ #define FLAT_RESCALE_MAT_Q_SHIFT 11 /** * @breif Scaling matrix is represented in Q15 format */ #define SCALING_Q_SHIFT 15 /** * @brief rounding factor for quantization represented in Q9 format */ #define QUANT_ROUND_FACTOR_Q 9 /** * @brief Minimum qp supported in H264 spec */ #define MIN_H264_QP 0 /** * @brief Maximum qp supported in H264 spec */ #define MAX_H264_QP 51 /** * @brief Minimum delta scale supported in H264 spec */ #define MIN_H264_DELTA_SCALE (-128) /** * @brief Maximum delta scale supported in H264 spec */ #define MAX_H264_DELTA_SCALE 127 /** * @breif Total number of transform sizes * used for sizeID while getting scale matrix */ #define NUM_UNIQUE_TRANS_SIZE 4 /** * @breif Maximum number of bits in frameNumber signaling */ #define MAX_BITS_IN_FRAME_NUM 16 /** * @breif Maximum number of bits in POC LSB signaling */ #define MAX_BITS_IN_POC_LSB 16 /** * @breif Maximum PIC Order Count type */ #define MAX_PIC_ORDER_COUNT_TYPE 2 /** * @breif Maximum Weighted bipred idc */ #define MAX_WEIGHT_BIPRED_IDC 2 /*****************************************************************************/ /* Number of scaling matrices for each transform size */ /*****************************************************************************/ #define SCALE_MAT_CNT_TRANS_SIZE_4 6 #define SCALE_MAT_CNT_TRANS_SIZE_8 6 #define SCALE_MAT_CNT_TRANS_SIZE_16 6 #define SCALE_MAT_CNT_TRANS_SIZE_32 2 /* Maximum number of scale matrices for a given transform size */ #define SCALE_MAT_CNT_MAX_PER_TRANS_SIZE 6 /* Total number of scale matrices */ #define TOTAL_SCALE_MAT_COUNT (SCALE_MAT_CNT_TRANS_SIZE_4 + \ SCALE_MAT_CNT_TRANS_SIZE_8 + \ SCALE_MAT_CNT_TRANS_SIZE_16 + \ SCALE_MAT_CNT_TRANS_SIZE_32) /*****************************************************************************/ /* Intra pred Macros */ /*****************************************************************************/ /** Planar Intra prediction mode */ #define INTRA_PLANAR 0 /** DC Intra prediction mode */ #define INTRA_DC 1 /** Gives angular mode for intra prediction */ #define INTRA_ANGULAR(x) (x) /** Following is used to signal no intra prediction in case of pcm blocks */ #define INTRA_PRED_NONE 63 /** Following is used to signal no intra prediction is needed for first three * 4x4 luma blocks in case of 4x4 TU sizes * Also used in pcm cases */ #define INTRA_PRED_CHROMA_IDX_NONE 7 /** ****************************************************************************** * @brief neighbor availability masks ****************************************************************************** */ #define LEFT_MB_AVAILABLE_MASK 0x01 #define TOP_LEFT_MB_AVAILABLE_MASK 0x02 #define TOP_MB_AVAILABLE_MASK 0x04 #define TOP_RIGHT_MB_AVAILABLE_MASK 0x08 /** ****************************************************************************** * @brief SEI macros ****************************************************************************** */ /* * @brief specifies the number of colour primary components of the mastering display */ #define NUM_SEI_MDCV_PRIMARIES 3 /* * @brief specifies the number of colour primary components of the nominal content colour volume */ #define NUM_SEI_CCV_PRIMARIES 3 #define DISPLAY_PRIMARIES_X_UPPER_LIMIT 37000 #define DISPLAY_PRIMARIES_X_LOWER_LIMIT 5 #define DISPLAY_PRIMARIES_X_DIVISION_FACTOR 5 #define DISPLAY_PRIMARIES_Y_UPPER_LIMIT 42000 #define DISPLAY_PRIMARIES_Y_LOWER_LIMIT 5 #define DISPLAY_PRIMARIES_Y_DIVISION_FACTOR 5 #define WHITE_POINT_X_UPPER_LIMIT 37000 #define WHITE_POINT_X_LOWER_LIMIT 5 #define WHITE_POINT_X_DIVISION_FACTOR 5 #define WHITE_POINT_Y_UPPER_LIMIT 42000 #define WHITE_POINT_Y_LOWER_LIMIT 5 #define WHITE_POINT_Y_DIVISION_FACTOR 5 #define MAX_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT 100000000 #define MAX_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT 50000 #define MAX_DISPLAY_MASTERING_LUMINANCE_DIVISION_FACTOR 10000 #define MIN_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT 50000 #define MIN_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT 1 #define AMBIENT_LIGHT_X_UPPER_LIMIT 50000 #define AMBIENT_LIGHT_Y_UPPER_LIMIT 50000 #define CCV_PRIMARIES_X_UPPER_LIMIT 5000000 #define CCV_PRIMARIES_X_LOWER_LIMIT -5000000 #define CCV_PRIMARIES_Y_UPPER_LIMIT 5000000 #define CCV_PRIMARIES_Y_LOWER_LIMIT -5000000 #endif /* IH264_DEFS_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_disp_mgr.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_disp_mgr.c * * @brief * Contains function definitions for display management * * @author * Srinivas T * * @par List of Functions: * - ih264_disp_mgr_init() * - ih264_disp_mgr_add() * - ih264_disp_mgr_get() * * @remarks * None * ******************************************************************************* */ #include #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_disp_mgr.h" /** ******************************************************************************* * * @brief * Initialization function for display buffer manager * * @par Description: * Initializes the display buffer management structure * * @param[in] ps_disp_mgr * Pointer to the display buffer management structure * * @returns none * * @remarks * None * ******************************************************************************* */ void ih264_disp_mgr_init(disp_mgr_t *ps_disp_mgr) { WORD32 id; ps_disp_mgr->u4_last_abs_poc = DEFAULT_POC; for(id = 0; id < DISP_MGR_MAX_CNT; id++) { ps_disp_mgr->ai4_abs_poc[id] = DEFAULT_POC; ps_disp_mgr->apv_ptr[id] = NULL; } } /** ******************************************************************************* * * @brief * Adds a buffer to the display manager * * @par Description: * Adds a buffer to the display buffer manager * * @param[in] ps_disp_mgr * Pointer to the display buffer management structure * * @param[in] buf_id * ID of the display buffer * * @param[in] abs_poc * Absolute POC of the display buffer * * @param[in] pv_ptr * Pointer to the display buffer * * @returns 0 if success, -1 otherwise * * @remarks * None * ******************************************************************************* */ WORD32 ih264_disp_mgr_add(disp_mgr_t *ps_disp_mgr, WORD32 buf_id, WORD32 abs_poc, void *pv_ptr) { if(buf_id >= DISP_MGR_MAX_CNT) { return (-1); } if(ps_disp_mgr->apv_ptr[buf_id] != NULL) { return (-1); } ps_disp_mgr->apv_ptr[buf_id] = pv_ptr; ps_disp_mgr->ai4_abs_poc[buf_id] = abs_poc; return 0; } /** ******************************************************************************* * * @brief * Gets the next buffer * * @par Description: * Gets the next display buffer * * @param[in] ps_disp_mgr * Pointer to the display buffer structure * * @param[out] pi4_buf_id * Pointer to hold buffer id of the display buffer being returned * * @returns Pointer to the next display buffer * * @remarks * None * ******************************************************************************* */ void* ih264_disp_mgr_get(disp_mgr_t *ps_disp_mgr, WORD32 *pi4_buf_id) { WORD32 id; void *pv_ret_ptr; WORD32 i4_min_poc; WORD32 min_poc_id; pv_ret_ptr = NULL; i4_min_poc = 0x7FFFFFFF; min_poc_id = -1; /* Find minimum POC */ for(id = 0; id < DISP_MGR_MAX_CNT; id++) { if((DEFAULT_POC != ps_disp_mgr->ai4_abs_poc[id]) && (ps_disp_mgr->ai4_abs_poc[id] <= i4_min_poc)) { i4_min_poc = ps_disp_mgr->ai4_abs_poc[id]; min_poc_id = id; } } *pi4_buf_id = min_poc_id; /* If all pocs are still default_poc then return NULL */ if(-1 == min_poc_id) { return NULL; } pv_ret_ptr = ps_disp_mgr->apv_ptr[min_poc_id]; /* Set abs poc to default and apv_ptr to null so that the buffer is not returned again */ ps_disp_mgr->apv_ptr[min_poc_id] = NULL; ps_disp_mgr->ai4_abs_poc[min_poc_id] = DEFAULT_POC; return pv_ret_ptr; } ================================================ FILE: dependencies/ih264d/common/ih264_disp_mgr.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_disp_mgr.h * * @brief * Function declarations used for display management * * @author * Srinivas T * * * @remarks * None * ******************************************************************************* */ #ifndef _DISP_MGR_H_ #define _DISP_MGR_H_ #define DISP_MGR_MAX_CNT 64 #define DEFAULT_POC 0x7FFFFFFF typedef struct { /** * last_abs_poc */ UWORD32 u4_last_abs_poc; /** * au4_abs_poc[DISP_MGR_MAX_CNT] */ WORD32 ai4_abs_poc[DISP_MGR_MAX_CNT]; /** * apv_ptr[DISP_MGR_MAX_CNT] */ void *apv_ptr[DISP_MGR_MAX_CNT]; }disp_mgr_t; void ih264_disp_mgr_init(disp_mgr_t *ps_disp_mgr); WORD32 ih264_disp_mgr_add(disp_mgr_t *ps_disp_mgr, WORD32 id, WORD32 abs_poc, void *pv_ptr); void* ih264_disp_mgr_get(disp_mgr_t *ps_disp_mgr, WORD32 *pi4_buf_id); #endif //_DISP_MGR_H_ ================================================ FILE: dependencies/ih264d/common/ih264_dpb_mgr.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_dpb_mgr.c * * @brief * Function definitions used for decoded picture buffer management * * @author * Srinivas T * * @par List of Functions: * - ih264_dpb_mgr_init() * - ih264_dpb_mgr_sort_short_term_fields_by_frame_num() * - ih264_dpb_mgr_sort_short_term_fields_by_poc_l0() * - ih264_dpb_mgr_sort_short_term_fields_by_poc_l1() * - ih264_dpb_mgr_sort_long_term_fields_by_frame_idx() * - ih264_dpb_mgr_alternate_ref_fields() * - ih264_dpb_mgr_insert_ref_field() * - ih264_dpb_mgr_insert_ref_frame() * - ih264_dpb_mgr_count_ref_frames() * - ih264_dpb_mgr_delete_ref_frame() * - ih264_dpb_mgr_delete_long_ref_fields_max_frame_idx() * - ih264_dpb_mgr_delete_short_ref_frame() * - ih264_dpb_mgr_delete_all_ref_frames() * - ih264_dpb_mgr_reset() * - ih264_dpb_mgr_release_pics() * * @remarks * None * ******************************************************************************* */ #include #include #include #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_macros.h" #include "ih264_error.h" #include "ih264_structs.h" #include "ih264_buf_mgr.h" #include "ih264_dpb_mgr.h" #include "ih264_debug.h" /** ******************************************************************************* * * @brief * DPB manager initializer * * @par Description: * Initialises the DPB manager structure * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @returns * * @remarks * * ******************************************************************************* */ void ih264_dpb_mgr_init(dpb_mgr_t *ps_dpb_mgr) { UWORD32 i; dpb_info_t *ps_dpb_info = ps_dpb_mgr->as_dpb_info; for(i = 0; i < MAX_DPB_BUFS; i++) { ps_dpb_info[i].ps_prev_dpb = NULL; ps_dpb_info[i].ps_pic_buf = NULL; ps_dpb_mgr->as_top_field_pics[i].i4_used_as_ref = INVALID; ps_dpb_mgr->as_bottom_field_pics[i].i4_used_as_ref = INVALID; ps_dpb_mgr->as_top_field_pics[i].i1_field_type = INVALID; ps_dpb_mgr->as_bottom_field_pics[i].i1_field_type = INVALID; ps_dpb_mgr->as_top_field_pics[i].i4_long_term_frame_idx = -1; ps_dpb_mgr->as_bottom_field_pics[i].i4_long_term_frame_idx = -1; } ps_dpb_mgr->u1_num_short_term_ref_bufs = 0; ps_dpb_mgr->u1_num_long_term_ref_bufs = 0; ps_dpb_mgr->ps_dpb_short_term_head = NULL; ps_dpb_mgr->ps_dpb_long_term_head = NULL; } /** ******************************************************************************* * * @brief * Function to sort sort term pics by frame_num. * * @par Description: * Sorts short term fields by frame_num. For 2 fields having same frame_num, * orders them based on requested first field type. * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] curr_frame_num * frame_num of the current pic * * @param[in] first_field_type * For complementary fields, required first field * * @param[in] max_frame_num * Maximum frame_num allowed * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_sort_short_term_fields_by_frame_num(dpb_mgr_t *ps_dpb_mgr, WORD32 curr_frame_num, WORD32 first_field_type, WORD32 max_frame_num) { dpb_info_t *ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; dpb_info_t *ps_dpb_node2; WORD32 frame_num_node1; WORD32 frame_num_node2; pic_buf_t *ps_pic_buf; if(ps_dpb_node1 == NULL) return -1; for (; ps_dpb_node1 != NULL; ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb) { for (ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; ps_dpb_node2 != NULL; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb) { frame_num_node1 = ps_dpb_node1->ps_pic_buf->i4_frame_num; frame_num_node2 = ps_dpb_node2->ps_pic_buf->i4_frame_num; if(frame_num_node1 > curr_frame_num) frame_num_node1 = frame_num_node1 - max_frame_num; if(frame_num_node2 > curr_frame_num) frame_num_node2 = frame_num_node2 - max_frame_num; if(frame_num_node1 < frame_num_node2) { ps_pic_buf = ps_dpb_node1->ps_pic_buf; ps_dpb_node1->ps_pic_buf = ps_dpb_node2->ps_pic_buf; ps_dpb_node2->ps_pic_buf = ps_pic_buf; } } } /** * For frames and complementary field pairs, * ensure first_field_type appears first in the list */ ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; while(ps_dpb_node2 != NULL) { pic_buf_t *ps_pic_node1 = ps_dpb_node1->ps_pic_buf; pic_buf_t *ps_pic_node2 = ps_dpb_node2->ps_pic_buf; frame_num_node1 = ps_pic_node1->i4_frame_num; frame_num_node2 = ps_pic_node2->i4_frame_num; if(frame_num_node1 == frame_num_node2) { ASSERT(ps_pic_node1->i1_field_type != ps_pic_node2->i1_field_type); if(ps_pic_node1->i1_field_type != first_field_type) { ps_dpb_node1->ps_pic_buf = ps_pic_node2; ps_dpb_node2->ps_pic_buf = ps_pic_node1; } } ps_dpb_node1 = ps_dpb_node2; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb; } return 0; } /** ******************************************************************************* * * @brief * Function to sort sort term pics by poc for list 0. * * @par Description: * Orders all the pocs less than current poc in the descending order. * Then orders all the pocs greater than current poc in the ascending order. * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] curr_poc * Poc of the current pic * * @param[in] first_field_type * For complementary fields, required first field * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_sort_short_term_fields_by_poc_l0(dpb_mgr_t *ps_dpb_mgr, WORD32 curr_poc, WORD32 first_field_type) { dpb_info_t *ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; dpb_info_t *ps_dpb_node2; WORD32 poc_node1; WORD32 poc_node2; WORD32 frame_num_node1; WORD32 frame_num_node2; pic_buf_t *ps_pic_buf; if(ps_dpb_node1 == NULL) return -1; /** * Sort the fields by poc. * All POCs less than current poc are first placed in the descending order. * Then all POCs greater than current poc are placed in the ascending order. */ for (; ps_dpb_node1 != NULL; ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb) { for (ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; ps_dpb_node2 != NULL; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb) { poc_node1 = ps_dpb_node1->ps_pic_buf->i4_abs_poc; poc_node2 = ps_dpb_node2->ps_pic_buf->i4_abs_poc; ASSERT(poc_node1 != curr_poc); ASSERT(poc_node2 != curr_poc); if(((poc_node1 < curr_poc) && (poc_node2 > curr_poc)) || ((poc_node1 < curr_poc) && (poc_node2 < curr_poc) && (poc_node1 > poc_node2)) || ((poc_node1 > curr_poc) && (poc_node2 > curr_poc) && (poc_node1 < poc_node2))) continue; ps_pic_buf = ps_dpb_node1->ps_pic_buf; ps_dpb_node1->ps_pic_buf = ps_dpb_node2->ps_pic_buf; ps_dpb_node2->ps_pic_buf = ps_pic_buf; } } ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; while(ps_dpb_node2 != NULL) { pic_buf_t *ps_pic_node1 = ps_dpb_node1->ps_pic_buf; pic_buf_t *ps_pic_node2 = ps_dpb_node2->ps_pic_buf; frame_num_node1 = ps_pic_node1->i4_frame_num; frame_num_node2 = ps_pic_node2->i4_frame_num; if(frame_num_node1 == frame_num_node2) { ASSERT(ps_pic_node1->i1_field_type != ps_pic_node2->i1_field_type); if(ps_pic_node1->i1_field_type != first_field_type) { ps_dpb_node1->ps_pic_buf = ps_pic_node2; ps_dpb_node2->ps_pic_buf = ps_pic_node1; } } ps_dpb_node1 = ps_dpb_node2; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb; } return 0; } /** ******************************************************************************* * * @brief * Function to sort sort term pics by poc for list 1. * * @par Description: * Orders all the pocs greater than current poc in the ascending order. * Then rrders all the pocs less than current poc in the descending order. * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] curr_poc * Poc of the current pic * * @param[in] first_field_type * For complementary fields, required first field * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_sort_short_term_fields_by_poc_l1(dpb_mgr_t *ps_dpb_mgr, WORD32 curr_poc, WORD32 first_field_type) { dpb_info_t *ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; dpb_info_t *ps_dpb_node2; WORD32 poc_node1; WORD32 poc_node2; WORD32 frame_num_node1; WORD32 frame_num_node2; pic_buf_t *ps_pic_buf; if(ps_dpb_node1 == NULL) return -1; /** * Sort the fields by poc. * All POCs greater than current poc are first placed in the ascending order. * Then all POCs less than current poc are placed in the decending order. */ for (; ps_dpb_node1 != NULL; ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb) { for (ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; ps_dpb_node2 != NULL; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb) { poc_node1 = ps_dpb_node1->ps_pic_buf->i4_abs_poc; poc_node2 = ps_dpb_node2->ps_pic_buf->i4_abs_poc; ASSERT(poc_node1 != curr_poc); ASSERT(poc_node2 != curr_poc); if(((poc_node1 > curr_poc) && (poc_node2 < curr_poc)) || ((poc_node1 < curr_poc) && (poc_node2 < curr_poc) && (poc_node1 > poc_node2)) || ((poc_node1 > curr_poc) && (poc_node2 > curr_poc) && (poc_node1 < poc_node2))) continue; ps_pic_buf = ps_dpb_node1->ps_pic_buf; ps_dpb_node1->ps_pic_buf = ps_dpb_node2->ps_pic_buf; ps_dpb_node2->ps_pic_buf = ps_pic_buf; } } ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; while(ps_dpb_node2 != NULL) { pic_buf_t *ps_pic_node1 = ps_dpb_node1->ps_pic_buf; pic_buf_t *ps_pic_node2 = ps_dpb_node2->ps_pic_buf; frame_num_node1 = ps_pic_node1->i4_frame_num; frame_num_node2 = ps_pic_node2->i4_frame_num; if(frame_num_node1 == frame_num_node2) { ASSERT(ps_pic_node1->i1_field_type != ps_pic_node2->i1_field_type); if(ps_pic_node1->i1_field_type != first_field_type) { ps_dpb_node1->ps_pic_buf = ps_pic_node2; ps_dpb_node2->ps_pic_buf = ps_pic_node1; } } ps_dpb_node1 = ps_dpb_node2; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb; } return 0; } /** ******************************************************************************* * * @brief * Function to sort long term pics by long term frame idx. * * @par Description: * Sorts long term fields by long term frame idx. For 2 fields * having same frame_num, orders them based on requested first field type. * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] first_field_type * For complementary fields, required first field * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_sort_long_term_fields_by_frame_idx(dpb_mgr_t *ps_dpb_mgr, WORD32 first_field_type) { dpb_info_t *ps_dpb_node1 = ps_dpb_mgr->ps_dpb_long_term_head; dpb_info_t *ps_dpb_node2; WORD32 frame_idx_node1; WORD32 frame_idx_node2; pic_buf_t *ps_pic_buf; if(ps_dpb_node1 == NULL) return -1; /* Sort the fields by frame idx */ for (; ps_dpb_node1 != NULL; ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb) { for (ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; ps_dpb_node2 != NULL; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb) { frame_idx_node1 = ps_dpb_node1->ps_pic_buf->i4_long_term_frame_idx; frame_idx_node2 = ps_dpb_node2->ps_pic_buf->i4_long_term_frame_idx; if(frame_idx_node1 > frame_idx_node2) { ps_pic_buf = ps_dpb_node1->ps_pic_buf; ps_dpb_node1->ps_pic_buf = ps_dpb_node2->ps_pic_buf; ps_dpb_node2->ps_pic_buf = ps_pic_buf; } } } /** * For frames and complementary field pairs, * ensure first_field_type appears first in the list */ ps_dpb_node1 = ps_dpb_mgr->ps_dpb_long_term_head; ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; while(ps_dpb_node2 != NULL) { pic_buf_t *ps_pic_node1 = ps_dpb_node1->ps_pic_buf; pic_buf_t *ps_pic_node2 = ps_dpb_node2->ps_pic_buf; frame_idx_node1 = ps_pic_node1->i4_long_term_frame_idx; frame_idx_node2 = ps_pic_node2->i4_long_term_frame_idx; if(frame_idx_node1 == frame_idx_node2) { ASSERT(ps_pic_node1->i1_field_type != ps_pic_node2->i1_field_type); if(ps_pic_node1->i1_field_type != first_field_type) { ps_dpb_node1->ps_pic_buf = ps_pic_node2; ps_dpb_node2->ps_pic_buf = ps_pic_node1; } } ps_dpb_node1 = ps_dpb_node2; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb; } return 0; } /** ******************************************************************************* * * @brief * Function to alternate fields. * * @par Description: * In the ordered list of fields, alternate fields starting with * first_field_type * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] reference_type * This is used to select between short-term and long-term linked list. * * @param[in] first_field_type * For complementary fields, required first field * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_alternate_ref_fields(dpb_mgr_t *ps_dpb_mgr, WORD32 reference_type, WORD32 first_field_type) { dpb_info_t s_dpb_head; dpb_info_t *ps_dpb_head; dpb_info_t *ps_dpb_node1; dpb_info_t *ps_dpb_node2; dpb_info_t *ps_dpb_node3; dpb_info_t *ps_dpb_node4; WORD32 expected_field; expected_field = first_field_type; ps_dpb_head = &s_dpb_head; ps_dpb_head->ps_prev_dpb = (reference_type == SHORT_TERM_REF) ? ps_dpb_mgr->ps_dpb_short_term_head: ps_dpb_mgr->ps_dpb_long_term_head; ps_dpb_node1 = ps_dpb_head; ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; while(ps_dpb_node2 != NULL) { pic_buf_t *ps_pic_node2 = ps_dpb_node2->ps_pic_buf; if(ps_pic_node2->i1_field_type != expected_field) { /* * If it is not expected field, loop over the node till * the expected field. */ ps_dpb_node3 = ps_dpb_node2; ps_dpb_node4 = ps_dpb_node2->ps_prev_dpb; while((ps_dpb_node4 != NULL) && (ps_dpb_node4->ps_pic_buf->i1_field_type != expected_field)) { ps_dpb_node3 = ps_dpb_node4; ps_dpb_node4 = ps_dpb_node4->ps_prev_dpb; } if(ps_dpb_node4 != NULL) { ps_dpb_node1->ps_prev_dpb = ps_dpb_node4; ps_dpb_node3->ps_prev_dpb = ps_dpb_node4->ps_prev_dpb; ps_dpb_node4->ps_prev_dpb = ps_dpb_node2; } else { /* node4 null means we have reached the end */ break; } } ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb; ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; expected_field = (ps_dpb_node1->ps_pic_buf->i1_field_type == TOP_FIELD)? BOTTOM_FIELD:TOP_FIELD; } if(reference_type == SHORT_TERM_REF) { ps_dpb_mgr->ps_dpb_short_term_head = ps_dpb_head->ps_prev_dpb; } else { ps_dpb_mgr->ps_dpb_long_term_head = ps_dpb_head->ps_prev_dpb; } return 0; } /** ******************************************************************************* * * @brief * Add a ref field to short-term or long-term linked list. * * @par Description: * This function adds a ref field to either short-term or long-term linked * list. It picks up memory for the link from the array of dpb_info in * dpb_mgr. The field is added to the beginning of the linked list and the * head is set the the field. * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] ps_pic_buf * Pic buf structure for the field being added. * * @param[in] reference_type * This is used to select between short-term and long-term linked list. * * @param[in] frame_num * frame_num for the field. * * @param[in] long_term_frame_idx * If the ref being added is long-term, long_term_frame_idx of the field. * Otherwise invalid. * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_insert_ref_field(dpb_mgr_t *ps_dpb_mgr, pic_buf_t *ps_pic_buf, WORD32 reference_type, UWORD32 frame_num, WORD32 long_term_frame_idx) { WORD32 i; dpb_info_t *ps_dpb_info; dpb_info_t *ps_dpb_head; ps_dpb_info = ps_dpb_mgr->as_dpb_info; /* Return error if buffer is already present in the DPB */ for(i = 0; i < MAX_DPB_BUFS; i++) { if( (ps_dpb_info[i].ps_pic_buf == ps_pic_buf) && (ps_dpb_info[i].ps_pic_buf->i4_used_as_ref == reference_type) ) { return (-1); } } /* Find an unused DPB location */ for(i = 0; i < MAX_DPB_BUFS; i++) { if(NULL == ps_dpb_info[i].ps_pic_buf) { break; } } if(i == MAX_DPB_BUFS) { return (-1); } ps_dpb_head = (reference_type == SHORT_TERM_REF) ?ps_dpb_mgr->ps_dpb_short_term_head :ps_dpb_mgr->ps_dpb_long_term_head; if(reference_type == SHORT_TERM_REF) long_term_frame_idx = -1; /* Create DPB info */ ps_dpb_info[i].ps_pic_buf = ps_pic_buf; ps_dpb_info[i].ps_prev_dpb = ps_dpb_head; ps_dpb_info[i].ps_pic_buf->i4_used_as_ref = reference_type; ps_dpb_info[i].ps_pic_buf->i4_frame_num = frame_num; ps_dpb_info[i].ps_pic_buf->i4_long_term_frame_idx = long_term_frame_idx; /* update the head node of linked list to point to the current picture */ if(reference_type == SHORT_TERM_REF) { ps_dpb_mgr->ps_dpb_short_term_head = ps_dpb_info + i; /* Increment Short term buffer count */ ps_dpb_mgr->u1_num_short_term_ref_bufs++; } else { ps_dpb_mgr->ps_dpb_long_term_head = ps_dpb_info + i; /* Increment Long term buffer count */ ps_dpb_mgr->u1_num_long_term_ref_bufs++; } return 0; } /** ******************************************************************************* * * @brief * Add a ref frame to short-term or long-term linked list. * * @par Description: * This function adds a ref frame to either short-term or long-term linked * list. Internally it calls add ref field twice to add top and bottom field. * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] ps_pic_buf * Pic buf structure for the field being added. * * @param[in] reference_type * This is used to select between short-term and long-term linked list. * * @param[in] frame_num * frame_num for the field. * * @param[in] long_term_frame_idx * If the ref being added is long-term, long_term_frame_idx of the field. * Otherwise invalid. * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_insert_ref_frame(dpb_mgr_t *ps_dpb_mgr, pic_buf_t *ps_pic_buf, WORD32 reference_type, UWORD32 frame_num, WORD32 long_term_frame_idx) { WORD32 buf_id; pic_buf_t *ps_pic_top; pic_buf_t *ps_pic_bottom; WORD32 ret; /* * For a frame, since the ps_pic_buf passed to this function is that of top field * obtain bottom field using buf_id. */ ps_pic_top = ps_pic_buf; buf_id = ps_pic_top->i4_buf_id; ps_pic_bottom = &ps_dpb_mgr->as_bottom_field_pics[buf_id]; /* Insert top field */ ret = ih264_dpb_mgr_insert_ref_field(ps_dpb_mgr, ps_pic_top, reference_type, frame_num, long_term_frame_idx); if(ret != 0) return ret; /* Insert bottom field */ ret = ih264_dpb_mgr_insert_ref_field(ps_dpb_mgr, ps_pic_bottom, reference_type, frame_num, long_term_frame_idx); if(ret != 0) return ret; return ret; } /** ******************************************************************************* * * @brief * Returns the number of ref frames in both the linked list. * * @par Description: * Returns the count of number of frames, number of complementary field pairs * and number of unpaired fields. * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] curr_frame_num * frame_num for the field. * * @param[in] max_frame_num * Maximum frame_num allowed * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_count_ref_frames(dpb_mgr_t *ps_dpb_mgr, WORD32 curr_frame_num, WORD32 max_frame_num) { WORD32 numShortTerm = 0; WORD32 numLongTerm = 0; dpb_info_t *ps_dpb_node; WORD32 frame_num; WORD32 prev_frame_num; /* * Compute the number of short-term frames/complementary field pairs/ * unpaired fields */ if(ps_dpb_mgr->ps_dpb_short_term_head != NULL) { /* Sort the short-term list by frame_num */ ih264_dpb_mgr_sort_short_term_fields_by_frame_num(ps_dpb_mgr, curr_frame_num, TOP_FIELD, max_frame_num); ps_dpb_node = ps_dpb_mgr->ps_dpb_short_term_head; if(ps_dpb_node != NULL) { numShortTerm++; prev_frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; ps_dpb_node = ps_dpb_node->ps_prev_dpb; } while(ps_dpb_node != NULL) { frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; if(frame_num != prev_frame_num) numShortTerm++; prev_frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; ps_dpb_node = ps_dpb_node->ps_prev_dpb; } } /* * Compute the number of long-term frames/complementary field pairs/ * unpaired fields */ if(ps_dpb_mgr->ps_dpb_long_term_head != NULL) { ih264_dpb_mgr_sort_long_term_fields_by_frame_idx(ps_dpb_mgr, TOP_FIELD); ps_dpb_node = ps_dpb_mgr->ps_dpb_long_term_head; if(ps_dpb_node != NULL) { numLongTerm++; prev_frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; ps_dpb_node = ps_dpb_node->ps_prev_dpb; } while(ps_dpb_node != NULL) { frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; if(frame_num != prev_frame_num) numLongTerm++; prev_frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; ps_dpb_node = ps_dpb_node->ps_prev_dpb; } } return (numShortTerm + numLongTerm); } /** ******************************************************************************* * * @brief * Deletes the ref frame at the end of the linked list. * * @par Description: * Deletes the ref frame at the end of the linked list. For unpaired fields, * it deletes just the last node. For frame or complementary field pair, it * deletes the last two nodes. * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] reference_type * This is used to select between short-term and long-term linked list. * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_delete_ref_frame(dpb_mgr_t *ps_dpb_mgr, WORD32 reference_type) { dpb_info_t *ps_dpb_node1; dpb_info_t *ps_dpb_node2; dpb_info_t *ps_dpb_node3; /* * Assumption: The nodes sorted for frame num. */ /* Select bw short-term and long-term list. */ ps_dpb_node1 = (reference_type == SHORT_TERM_REF) ?ps_dpb_mgr->ps_dpb_short_term_head :ps_dpb_mgr->ps_dpb_long_term_head; /* If null, no entries in the list. Hence return. */ if(ps_dpb_node1 == NULL) return 0; /* If only one node in the list, set as unsed for refer and return. */ if(ps_dpb_node1->ps_prev_dpb == NULL) { /* Set the picture as unused for reference */ ps_dpb_node1->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; ps_dpb_node1->ps_pic_buf = NULL; if(reference_type == SHORT_TERM_REF) { ps_dpb_mgr->ps_dpb_short_term_head = NULL; /* Increment Short term buffer count */ ps_dpb_mgr->u1_num_short_term_ref_bufs = 0; } else { ps_dpb_mgr->ps_dpb_long_term_head = NULL; /* Increment Long term buffer count */ ps_dpb_mgr->u1_num_long_term_ref_bufs = 0; } return 0; } /** * If there are only 2 nodes in the list, set second node as unused for reference. * If the frame_num of second node and first node is same, set first node also as * unused for reference and set the corresponding head to NULL. */ ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; if(ps_dpb_node2->ps_prev_dpb == NULL) { /* Set the picture as unused for reference */ if(ps_dpb_node2->ps_pic_buf->i4_frame_num == ps_dpb_node1->ps_pic_buf->i4_frame_num) { /* Set the picture as unused for reference */ ps_dpb_node1->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; ps_dpb_node1->ps_pic_buf = NULL; if(reference_type == SHORT_TERM_REF) { ps_dpb_mgr->ps_dpb_short_term_head = NULL; /* Increment Short term buffer count */ ps_dpb_mgr->u1_num_short_term_ref_bufs = 0; } else { ps_dpb_mgr->ps_dpb_long_term_head = NULL; /* Increment Long term buffer count */ ps_dpb_mgr->u1_num_long_term_ref_bufs = 0; } } ps_dpb_node2->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; ps_dpb_node2->ps_pic_buf = NULL; ps_dpb_node1->ps_prev_dpb = NULL; return 0; } /* * If there are more than 2 nodes, run a loop to get the last 3 nodes. */ ps_dpb_node3 = ps_dpb_node2->ps_prev_dpb; while(ps_dpb_node3->ps_prev_dpb != NULL) { ps_dpb_node1 = ps_dpb_node2; ps_dpb_node2 = ps_dpb_node3; ps_dpb_node3 = ps_dpb_node3->ps_prev_dpb; } /* * If node 2 and node 3 frame_nums are same, set node 2 also as unsed for * reference and del reference from node1. */ if(ps_dpb_node2->ps_pic_buf->i4_frame_num == ps_dpb_node3->ps_pic_buf->i4_frame_num) { ps_dpb_node2->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; ps_dpb_node2->ps_pic_buf = NULL; ps_dpb_node1->ps_prev_dpb = NULL; } /* Set the third node as unused for reference */ ps_dpb_node3->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; ps_dpb_node3->ps_pic_buf = NULL; ps_dpb_node2->ps_prev_dpb = NULL; return 0; } /** ******************************************************************************* * * @brief * Delete long-term ref fields above max frame idx. * * @par Description: * Deletes all the long-term ref fields having idx greater than max_frame_idx * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] max_frame_idx * Max long-term frame idx allowed. * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_delete_long_ref_fields_max_frame_idx(dpb_mgr_t *ps_dpb_mgr, WORD32 max_frame_idx) { dpb_info_t *ps_dpb_node1; dpb_info_t *ps_dpb_node2; /* * Loop until there is node which isn't to be deleted is encountered. */ while(ps_dpb_mgr->ps_dpb_long_term_head != NULL) { if(ps_dpb_mgr->ps_dpb_long_term_head->ps_pic_buf->i4_long_term_frame_idx <= max_frame_idx) { break; } ps_dpb_mgr->ps_dpb_long_term_head->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; ps_dpb_mgr->ps_dpb_long_term_head->ps_pic_buf = NULL; ps_dpb_mgr->ps_dpb_long_term_head = ps_dpb_mgr->ps_dpb_long_term_head->ps_prev_dpb; } ps_dpb_node1 = ps_dpb_mgr->ps_dpb_long_term_head; if(ps_dpb_node1 == NULL) return 0; /* * With the node that isn't to be deleted as head, loop until the end. */ ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; while(ps_dpb_node2 != NULL) { if(ps_dpb_node2->ps_pic_buf->i4_long_term_frame_idx > max_frame_idx) { ps_dpb_node2->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; ps_dpb_node2->ps_pic_buf = NULL; ps_dpb_node1->ps_prev_dpb = ps_dpb_node2->ps_prev_dpb; } ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb; if(ps_dpb_node1 == NULL) break; ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; } return 0; } /** ******************************************************************************* * * @brief * Deletes the short-term with least frame_num * * @par Description: * Deletes the short-term with least frame_num. It sorts the function the * short-term linked list by frame-num and the function that deletes the last * frame in the linked list. * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @param[in] curr_frame_num * frame_num of the current pic * * @param[in] max_frame_num * Maximum frame_num allowed * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_delete_short_ref_frame(dpb_mgr_t *ps_dpb_mgr, WORD32 curr_frame_num, WORD32 max_frame_num) { WORD32 ret; /* Sort the short-term list by frame_num */ ret = ih264_dpb_mgr_sort_short_term_fields_by_frame_num(ps_dpb_mgr, curr_frame_num, TOP_FIELD, max_frame_num); /* Delete the last reference frame or field */ ret = ih264_dpb_mgr_delete_ref_frame(ps_dpb_mgr,SHORT_TERM_REF); if(ret != 0) { ASSERT(0); } return ret; } /** ******************************************************************************* * * @brief * Deletes all the ref frames. * * @par Description: * Deletes all of the ref frames/fields in the short-term and long-term linked * list. * * @param[in] ps_dpb_mgr * Pointer to the DPB manager structure * * @returns * * @remarks * * ******************************************************************************* */ WORD32 ih264_dpb_mgr_delete_all_ref_frames(dpb_mgr_t *ps_dpb_mgr) { /* Loop over short-term linked list. */ while(ps_dpb_mgr->ps_dpb_short_term_head != NULL) { ih264_dpb_mgr_delete_ref_frame(ps_dpb_mgr,SHORT_TERM_REF); } /* Loop over long-term linked list. */ while(ps_dpb_mgr->ps_dpb_long_term_head != NULL) { ih264_dpb_mgr_delete_ref_frame(ps_dpb_mgr,LONG_TERM_REF); } return 0; } void ih264_dpb_mgr_reset(dpb_mgr_t *ps_dpb_mgr, buf_mgr_t *ps_buf_mgr) { WORD32 i; dpb_info_t *ps_dpb_info; ASSERT(0); ps_dpb_info = ps_dpb_mgr->as_dpb_info; for(i = 0; i < MAX_DPB_BUFS; i++) { if(ps_dpb_info[i].ps_pic_buf->i4_used_as_ref) { ps_dpb_info[i].ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; ps_dpb_info[i].ps_prev_dpb = NULL; //Release physical buffer ih264_buf_mgr_release(ps_buf_mgr, ps_dpb_info[i].ps_pic_buf->i4_buf_id, BUF_MGR_REF); ps_dpb_info[i].ps_pic_buf = NULL; } } ps_dpb_mgr->u1_num_short_term_ref_bufs = 0; ps_dpb_mgr->u1_num_long_term_ref_bufs = 0; ps_dpb_mgr->ps_dpb_short_term_head = NULL; ps_dpb_mgr->ps_dpb_long_term_head = NULL; } /** ******************************************************************************* * * @brief * deletes all pictures from DPB * * @par Description: * Deletes all pictures present in the DPB manager * * @param[in] ps_buf_mgr * Pointer to buffer manager structure * * @param[in] u1_disp_bufs * Number of buffers to be deleted * * @returns * * @remarks * * ******************************************************************************* */ void ih264_dpb_mgr_release_pics(buf_mgr_t *ps_buf_mgr, UWORD8 u1_disp_bufs) { WORD8 i; UWORD32 buf_status; ASSERT(0); for(i = 0; i < u1_disp_bufs; i++) { buf_status = ih264_buf_mgr_get_status(ps_buf_mgr, i); if(0 != buf_status) { ih264_buf_mgr_release((buf_mgr_t *)ps_buf_mgr, i, BUF_MGR_REF); } } } ================================================ FILE: dependencies/ih264d/common/ih264_dpb_mgr.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_dpb_mgr.h * * @brief * Function declarations used for decoded picture buffer management * * @author * Srinivas T * * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_DPB_MGR_H_ #define _IH264_DPB_MGR_H_ /* Temporary definitions. Have to be defined later */ #define MAX_DPB_BUFS (MAX_DPB_SIZE * 4) #define MARK_ST_PICNUM_AS_NONREF 1 #define MARK_LT_INDEX_AS_NONREF 2 #define MARK_ST_PICNUM_AS_LT_INDEX 3 #define RESET_REF_PICTURES 5 typedef struct dpb_info_t dpb_info_t; enum { INVALID = -1, UNUSED_FOR_REF = 0 , LONG_TERM_REF , SHORT_TERM_REF , }; struct dpb_info_t { /** * Pointer to picture buffer structure */ pic_buf_t *ps_pic_buf; /** * Link to the DPB buffer with previous link */ dpb_info_t *ps_prev_dpb; }; typedef struct { /** * Pointer to the most recent pic Num */ dpb_info_t *ps_dpb_short_term_head; /** * Pointer to the most recent pic Num */ dpb_info_t *ps_dpb_long_term_head; /** * Physical storage for dpbInfo for ref bufs */ dpb_info_t as_dpb_info[MAX_DPB_BUFS]; /** * Array of structures for bottom field. */ pic_buf_t as_top_field_pics[MAX_DPB_BUFS]; /** * Array of structures for bottom field. */ pic_buf_t as_bottom_field_pics[MAX_DPB_BUFS]; /** * Number of short-term reference buffers */ UWORD8 u1_num_short_term_ref_bufs; /** * Number of long-term reference buffers */ UWORD8 u1_num_long_term_ref_bufs; /** * buffer ID current frame */ WORD32 i4_cur_frame_buf_id; } dpb_mgr_t; void ih264_dpb_mgr_init(dpb_mgr_t *ps_dpb_mgr); WORD32 ih264_dpb_mgr_insert_ref_frame(dpb_mgr_t *ps_dpb_mgr, pic_buf_t *ps_pic_buf, WORD32 reference_type, UWORD32 frame_num, WORD32 long_term_frame_idx); WORD32 ih264_dpb_mgr_delete_ref_frame(dpb_mgr_t *ps_dpb_mgr, WORD32 reference_type); WORD32 ih264_dpb_mgr_delete_all_ref_frames(dpb_mgr_t *ps_dpb_mgr); WORD32 ih264_dpb_mgr_count_ref_frames(dpb_mgr_t *ps_dpb_mgr, WORD32 curr_frame_num, WORD32 max_frame_num); WORD32 ih264_dpb_mgr_delete_short_ref_frame(dpb_mgr_t *ps_dpb_mgr, WORD32 curr_frame_num, WORD32 max_frame_num); WORD32 ih264_dpb_mgr_insert_ref_field(dpb_mgr_t *ps_dpb_mgr, pic_buf_t *ps_pic_buf, WORD32 reference_type, UWORD32 frame_num, WORD32 long_term_frame_idx); WORD32 ih264_dpb_mgr_delete_ref_field(dpb_mgr_t *ps_dpb_mgr, WORD32 reference_type); WORD32 ih264_dpb_mgr_alternate_ref_fields(dpb_mgr_t *ps_dpb_mgr, WORD32 reference_type, WORD32 first_field_type); WORD32 ih264_dpb_mgr_sort_short_term_fields_by_frame_num(dpb_mgr_t *ps_dpb_mgr, WORD32 curr_frame_num, WORD32 first_field_type, WORD32 max_frame_num); WORD32 ih264_dpb_mgr_sort_short_term_fields_by_poc_l0(dpb_mgr_t *ps_dpb_mgr, WORD32 curr_poc, WORD32 first_field_type); WORD32 ih264_dpb_mgr_sort_short_term_fields_by_poc_l1(dpb_mgr_t *ps_dpb_mgr, WORD32 curr_poc, WORD32 first_field_type); WORD32 ih264_dpb_mgr_sort_long_term_fields_by_frame_idx(dpb_mgr_t *ps_dpb_mgr, WORD32 first_field_type); WORD32 ih264_dpb_mgr_delete_long_ref_fields_max_frame_idx(dpb_mgr_t *ps_dpb_mgr, WORD32 max_frame_idx); void ih264_dpb_mgr_del_ref(dpb_mgr_t *ps_dpb_mgr, buf_mgr_t *ps_buf_mgr, WORD32 u4_abs_poc); pic_buf_t *ih264_dpb_mgr_get_ref_by_nearest_poc(dpb_mgr_t *ps_dpb_mgr, WORD32 cur_abs_poc); pic_buf_t *ih264_dpb_mgr_get_ref_by_poc(dpb_mgr_t *ps_dpb_mgr, WORD32 abs_poc); pic_buf_t *ih264_dpb_mgr_get_ref_by_poc_lsb(dpb_mgr_t *ps_dpb_mgr, WORD32 poc_lsb); void ih264_dpb_mgr_reset(dpb_mgr_t *ps_dpb_mgr, buf_mgr_t *ps_buf_mgr); void ih264_dpb_mgr_release_pics(buf_mgr_t *ps_buf_mgr, UWORD8 u1_disp_bufs); #endif /* _IH264_DPB_MGR_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_error.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_error.h * * @brief * Definitions related to error handling for common modules * * @author * Harish * * @par List of Functions: * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_ERROR_H_ #define _IH264_ERROR_H_ /** * Enumerations for error codes used in the codec. * Not all these are expected to be returned to the application. * Only select few will be exported */ typedef enum { /** * No error */ IH264_SUCCESS = 0, /** * Start error code for decoder */ IH264_DEC_ERROR_START = 0x100, /** * Start error code for encoder */ IH264_ENC_ERROR_START = 0x200, /** * Generic failure */ IH264_FAIL = 0x7FFFFFFF }IH264_ERROR_T; #endif /* _IH264_ERROR_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_ihadamard_scaling.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_ihadamard_scaling.c * * @brief * Contains definition of functions for h264 inverse hadamard 4x4 transform and scaling * * @author * Mohit * * @par List of Functions: * - ih264_ihadamard_scaling_4x4() * * @remarks * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* User include files */ #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_trans_macros.h" #include "ih264_macros.h" #include "ih264_trans_data.h" #include "ih264_size_defs.h" #include "ih264_structs.h" #include "ih264_trans_quant_itrans_iquant.h" /* ******************************************************************************** * * @brief This function performs a 4x4 inverse hadamard transform on the 4x4 DC coefficients * of a 16x16 intra prediction macroblock, and then performs scaling. * prediction buffer * * @par Description: * The DC coefficients pass through a 2-stage inverse hadamard transform. * This inverse transformed content is scaled to based on Qp value. * * @param[in] pi2_src * input 4x4 block of DC coefficients * * @param[out] pi2_out * output 4x4 block * * @param[in] pu2_iscal_mat * pointer to scaling list * * @param[in] pu2_weigh_mat * pointer to weight matrix * * @param[in] u4_qp_div_6 * Floor (qp/6) * * @param[in] pi4_tmp * temporary buffer of size 1*16 * * @returns none * * @remarks none * ******************************************************************************* */ void ih264_ihadamard_scaling_4x4(WORD16* pi2_src, WORD16* pi2_out, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD32* pi4_tmp) { WORD32 i; WORD32 x0, x1, x2, x3, x4, x5, x6, x7; WORD16* pi2_src_ptr, *pi2_out_ptr; WORD32* pi4_tmp_ptr; WORD32 rnd_fact = (u4_qp_div_6 < 6) ? (1 << (5 - u4_qp_div_6)) : 0; pi4_tmp_ptr = pi4_tmp; pi2_src_ptr = pi2_src; pi2_out_ptr = pi2_out; // Horizontal transform for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) { x4 = pi2_src_ptr[0]; x5 = pi2_src_ptr[1]; x6 = pi2_src_ptr[2]; x7 = pi2_src_ptr[3]; x0 = x4 + x7; x1 = x5 + x6; x2 = x5 - x6; x3 = x4 - x7; pi4_tmp_ptr[0] = x0 + x1; pi4_tmp_ptr[1] = x2 + x3; pi4_tmp_ptr[2] = x0 - x1; pi4_tmp_ptr[3] = x3 - x2; pi4_tmp_ptr += SUB_BLK_WIDTH_4x4; pi2_src_ptr += SUB_BLK_WIDTH_4x4; } pi4_tmp_ptr = pi4_tmp; // Vertical Transform for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) { x4 = pi4_tmp_ptr[0]; x5 = pi4_tmp_ptr[4]; x6 = pi4_tmp_ptr[8]; x7 = pi4_tmp_ptr[12]; x0 = x4 + x7; x1 = x5 + x6; x2 = x5 - x6; x3 = x4 - x7; pi4_tmp_ptr[0] = x0 + x1; pi4_tmp_ptr[4] = x2 + x3; pi4_tmp_ptr[8] = x0 - x1; pi4_tmp_ptr[12] = x3 - x2; pi4_tmp_ptr++; } pi4_tmp_ptr = pi4_tmp; //Scaling for(i = 0; i < (SUB_BLK_WIDTH_4x4 * SUB_BLK_WIDTH_4x4); i++) { INV_QUANT(pi4_tmp_ptr[i], pu2_iscal_mat[0], pu2_weigh_mat[0], u4_qp_div_6, rnd_fact, 6); pi2_out_ptr[i] = pi4_tmp_ptr[i]; } } void ih264_ihadamard_scaling_2x2_uv(WORD16* pi2_src, WORD16* pi2_out, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD32* pi4_tmp) { WORD32 i4_x0,i4_x1,i4_x2,i4_x3,i4_x4,i4_x5,i4_x6,i4_x7; WORD32 i4_y0,i4_y1,i4_y2,i4_y3,i4_y4,i4_y5,i4_y6,i4_y7; UNUSED(pi4_tmp); i4_x4 = pi2_src[0]; i4_x5 = pi2_src[1]; i4_x6 = pi2_src[2]; i4_x7 = pi2_src[3]; i4_x0 = i4_x4 + i4_x5; i4_x1 = i4_x4 - i4_x5; i4_x2 = i4_x6 + i4_x7; i4_x3 = i4_x6 - i4_x7; i4_x4 = i4_x0+i4_x2; i4_x5 = i4_x1+i4_x3; i4_x6 = i4_x0-i4_x2; i4_x7 = i4_x1-i4_x3; INV_QUANT(i4_x4,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); INV_QUANT(i4_x5,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); INV_QUANT(i4_x6,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); INV_QUANT(i4_x7,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); pi2_out[0] = i4_x4; pi2_out[1] = i4_x5; pi2_out[2] = i4_x6; pi2_out[3] = i4_x7; i4_y4 = pi2_src[4]; i4_y5 = pi2_src[5]; i4_y6 = pi2_src[6]; i4_y7 = pi2_src[7]; i4_y0 = i4_y4 + i4_y5; i4_y1 = i4_y4 - i4_y5; i4_y2 = i4_y6 + i4_y7; i4_y3 = i4_y6 - i4_y7; i4_y4 = i4_y0+i4_y2; i4_y5 = i4_y1+i4_y3; i4_y6 = i4_y0-i4_y2; i4_y7 = i4_y1-i4_y3; INV_QUANT(i4_y4,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); INV_QUANT(i4_y5,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); INV_QUANT(i4_y6,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); INV_QUANT(i4_y7,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); pi2_out[4] = i4_y4; pi2_out[5] = i4_y5; pi2_out[6] = i4_y6; pi2_out[7] = i4_y7; } ================================================ FILE: dependencies/ih264d/common/ih264_inter_pred_filters.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_inter_pred_filters.c * * @brief * Contains function definitions for inter prediction interpolation filters * * @author * Ittiam * * @par List of Functions: * - ih264_inter_pred_luma_copy * - ih264_interleave_copy * - ih264_inter_pred_luma_horz * - ih264_inter_pred_luma_vert * - ih264_inter_pred_luma_horz_hpel_vert_hpel * - ih264_inter_pred_luma_horz_qpel * - ih264_inter_pred_luma_vert_qpel * - ih264_inter_pred_luma_horz_qpel_vert_qpel * - ih264_inter_pred_luma_horz_hpel_vert_qpel * - ih264_inter_pred_luma_horz_qpel_vert_hpel * - ih264_inter_pred_luma_bilinear * - ih264_inter_pred_chroma * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* User include files */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_inter_pred_filters.h" /*****************************************************************************/ /* Constant Data variables */ /*****************************************************************************/ /* coefficients for 6 tap filtering*/ const WORD32 ih264_g_six_tap[3] ={1,-5,20}; /*****************************************************************************/ /* Function definitions . */ /*****************************************************************************/ /** ******************************************************************************* * * @brief * Interprediction luma function for copy * * @par Description: * Copies the array of width 'wd' and height 'ht' from the location pointed * by 'src' to the location pointed by 'dst' * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_inter_pred_luma_copy(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 row, col; UNUSED(pu1_tmp); UNUSED(dydx); for(row = 0; row < ht; row++) { for(col = 0; col < wd; col++) { pu1_dst[col] = pu1_src[col]; } pu1_src += src_strd; pu1_dst += dst_strd; } } /** ******************************************************************************* * * @brief * Fucntion for copying to an interleaved destination * * @par Description: * Copies the array of width 'wd' and height 'ht' from the location pointed * by 'src' to the location pointed by 'dst' * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @returns * * @remarks * The alternate elements of src will be copied to alternate locations in dsr * Other locations are not touched * ******************************************************************************* */ void ih264_interleave_copy(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd) { WORD32 row, col; wd *= 2; for(row = 0; row < ht; row++) { for(col = 0; col < wd; col+=2) { pu1_dst[col] = pu1_src[col]; } pu1_src += src_strd; pu1_dst += dst_strd; } } /** ******************************************************************************* * * @brief * Interprediction luma filter for horizontal input * * @par Description: * Applies a 6 tap horizontal filter .The output is clipped to 8 bits * sec 8.4.2.2.1 titled "Luma sample interpolation process" * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_inter_pred_luma_horz(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 row, col; WORD16 i2_tmp; UNUSED(pu1_tmp); UNUSED(dydx); for(row = 0; row < ht; row++) { for(col = 0; col < wd; col++) { i2_tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ i2_tmp = ih264_g_six_tap[0] * (pu1_src[col - 2] + pu1_src[col + 3]) + ih264_g_six_tap[1] * (pu1_src[col - 1] + pu1_src[col + 2]) + ih264_g_six_tap[2] * (pu1_src[col] + pu1_src[col + 1]); i2_tmp = (i2_tmp + 16) >> 5; pu1_dst[col] = CLIP_U8(i2_tmp); } pu1_src += src_strd; pu1_dst += dst_strd; } } /** ******************************************************************************* * * @brief * Interprediction luma filter for vertical input * * @par Description: * Applies a 6 tap vertical filter.The output is clipped to 8 bits * sec 8.4.2.2.1 titled "Luma sample interpolation process" * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_inter_pred_luma_vert(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 row, col; WORD16 i2_tmp; UNUSED(pu1_tmp); UNUSED(dydx); for(row = 0; row < ht; row++) { for(col = 0; col < wd; col++) { i2_tmp = 0; /*ih264_g_six_tap[] is the array containing the filter coeffs*/ i2_tmp = ih264_g_six_tap[0] * (pu1_src[col - 2 * src_strd] + pu1_src[col + 3 * src_strd]) + ih264_g_six_tap[1] * (pu1_src[col - 1 * src_strd] + pu1_src[col + 2 * src_strd]) + ih264_g_six_tap[2] * (pu1_src[col] + pu1_src[col + 1 * src_strd]); i2_tmp = (i2_tmp + 16) >> 5; pu1_dst[col] = CLIP_U8(i2_tmp); } pu1_src += src_strd; pu1_dst += dst_strd; } } /*! ************************************************************************** * \if Function name : ih264_inter_pred_luma_horz_hpel_vert_hpel \endif * * \brief * This function implements a two stage cascaded six tap filter. It * applies the six tap filter in the horizontal direction on the * predictor values, followed by applying the same filter in the * vertical direction on the output of the first stage. The six tap * filtering operation is described in sec 8.4.2.2.1 titled "Luma sample * interpolation process" * * \param pu1_src: Pointer to the buffer containing the predictor values. * pu1_src could point to the frame buffer or the predictor buffer. * \param pu1_dst: Pointer to the destination buffer where the output of * the six tap filter is stored. * \param ht: Height of the rectangular pixel grid to be interpolated * \param wd: Width of the rectangular pixel grid to be interpolated * \param src_strd: Width of the buffer pointed to by pu1_src. * \param dst_strd: Width of the destination buffer * \param pu1_tmp: temporary buffer. * \param dydx: x and y reference offset for qpel calculations: UNUSED in this function. * * \return * None. * * \note * This function takes the 8 bit predictor values, applies the six tap * filter in the horizontal direction and outputs the result clipped to * 8 bit precision. The input is stored in the buffer pointed to by * pu1_src while the output is stored in the buffer pointed by pu1_dst. * Both pu1_src and pu1_dst could point to the same buffer i.e. the * six tap filter could be done in place. * ************************************************************************** */ void ih264_inter_pred_luma_horz_hpel_vert_hpel(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 row, col; WORD32 tmp; WORD16* pi2_pred1_temp; WORD16* pi2_pred1; UNUSED(dydx); pi2_pred1_temp = (WORD16*)pu1_tmp; pi2_pred1_temp += 2; pi2_pred1 = pi2_pred1_temp; for(row = 0; row < ht; row++) { for(col = -2; col < wd + 3; col++) { tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ tmp = ih264_g_six_tap[0] * (pu1_src[col - 2 * src_strd] + pu1_src[col + 3 * src_strd]) + ih264_g_six_tap[1] * (pu1_src[col - 1 * src_strd] + pu1_src[col + 2 * src_strd]) + ih264_g_six_tap[2] * (pu1_src[col] + pu1_src[col + 1 * src_strd]); pi2_pred1_temp[col] = tmp; } pu1_src += src_strd; pi2_pred1_temp = pi2_pred1_temp + wd + 5; } for(row = 0; row < ht; row++) { for(col = 0; col < wd; col++) { tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ tmp = ih264_g_six_tap[0] * (pi2_pred1[col - 2] + pi2_pred1[col + 3]) + ih264_g_six_tap[1] * (pi2_pred1[col - 1] + pi2_pred1[col + 2]) + ih264_g_six_tap[2] * (pi2_pred1[col] + pi2_pred1[col + 1]); tmp = (tmp + 512) >> 10; pu1_dst[col] = CLIP_U8(tmp); } pi2_pred1 += (wd + 5); pu1_dst += dst_strd; } } /*! ************************************************************************** * \if Function name : ih264_inter_pred_luma_horz_qpel \endif * * \brief * This routine applies the six tap filter to the predictors in the * horizontal direction. The six tap filtering operation is described in * sec 8.4.2.2.1 titled "Luma sample interpolation process" * * \param pu1_src: Pointer to the buffer containing the predictor values. * pu1_src could point to the frame buffer or the predictor buffer. * \param pu1_dst: Pointer to the destination buffer where the output of * the six tap filter is stored. * \param ht: Height of the rectangular pixel grid to be interpolated * \param wd: Width of the rectangular pixel grid to be interpolated * \param src_strd: Width of the buffer pointed to by pu1_src. * \param dst_strd: Width of the destination buffer * \param pu1_tmp: temporary buffer: UNUSED in this function * \param dydx: x and y reference offset for qpel calculations. * * \return * None. * * \note * This function takes the 8 bit predictor values, applies the six tap * filter in the horizontal direction and outputs the result clipped to * 8 bit precision. The input is stored in the buffer pointed to by * pu1_src while the output is stored in the buffer pointed by pu1_dst. * Both pu1_src and pu1_dst could point to the same buffer i.e. the * six tap filter could be done in place. * ************************************************************************** */ void ih264_inter_pred_luma_horz_qpel(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 row, col; UWORD8 *pu1_pred1; WORD32 x_offset = dydx & 0x3; UNUSED(pu1_tmp); pu1_pred1 = pu1_src + (x_offset >> 1); for(row = 0; row < ht; row++) { for(col = 0; col < wd; col++, pu1_src++, pu1_dst++) { WORD16 i2_temp; /* The logic below implements the following equation i2_temp = puc_pred[-2] - 5 * (puc_pred[-1] + puc_pred[2]) + 20 * (puc_pred[0] + puc_pred[1]) + puc_pred[3]; */ i2_temp = pu1_src[-2] + pu1_src[3] - (pu1_src[-1] + pu1_src[2]) + ((pu1_src[0] + pu1_src[1] - pu1_src[-1] - pu1_src[2]) << 2) + ((pu1_src[0] + pu1_src[1]) << 4); i2_temp = (i2_temp + 16) >> 5; i2_temp = CLIP_U8(i2_temp); *pu1_dst = (i2_temp + *pu1_pred1 + 1) >> 1; pu1_pred1++; } pu1_dst += dst_strd - wd; pu1_src += src_strd - wd; pu1_pred1 += src_strd - wd; } } /*! ************************************************************************** * \if Function name : ih264_inter_pred_luma_vert_qpel \endif * * \brief * This routine applies the six tap filter to the predictors in the * vertical direction and interpolates them to obtain pixels at quarter vertical * positions (0, 1/4) and (0, 3/4). The six tap filtering operation is * described in sec 8.4.2.2.1 titled "Luma sample interpolation process" * * \param pu1_src: Pointer to the buffer containing the predictor values. * pu1_src could point to the frame buffer or the predictor buffer. * \param pu1_dst: Pointer to the destination buffer where the output of * the six tap filter is stored. * \param ht: Height of the rectangular pixel grid to be interpolated * \param wd: Width of the rectangular pixel grid to be interpolated * \param src_strd: Width of the buffer pointed to by puc_pred. * \param dst_strd: Width of the destination buffer * \param pu1_tmp: temporary buffer: UNUSED in this function * \param dydx: x and y reference offset for qpel calculations. * * \return * void * * \note * This function takes the 8 bit predictor values, applies the six tap * filter in the vertical direction and outputs the result clipped to * 8 bit precision. The input is stored in the buffer pointed to by * puc_pred while the output is stored in the buffer pointed by puc_dest. * Both puc_pred and puc_dest could point to the same buffer i.e. the * six tap filter could be done in place. * * \para * <paragraph> * ... ************************************************************************** */ void ih264_inter_pred_luma_vert_qpel(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 row, col; WORD32 y_offset = dydx >> 2; WORD32 off1, off2, off3; UWORD8 *pu1_pred1; UNUSED(pu1_tmp); y_offset = y_offset & 0x3; off1 = src_strd; off2 = src_strd << 1; off3 = off1 + off2; pu1_pred1 = pu1_src + (y_offset >> 1) * src_strd; for(row = 0; row < ht; row++) { for(col = 0; col < wd; col++, pu1_dst++, pu1_src++, pu1_pred1++) { WORD16 i2_temp; /* The logic below implements the following equation i16_temp = puc_pred[-2*src_strd] + puc_pred[3*src_strd] - 5 * (puc_pred[-1*src_strd] + puc_pred[2*src_strd]) + 20 * (puc_pred[0] + puc_pred[src_strd]); */ i2_temp = pu1_src[-off2] + pu1_src[off3] - (pu1_src[-off1] + pu1_src[off2]) + ((pu1_src[0] + pu1_src[off1] - pu1_src[-off1] - pu1_src[off2]) << 2) + ((pu1_src[0] + pu1_src[off1]) << 4); i2_temp = (i2_temp + 16) >> 5; i2_temp = CLIP_U8(i2_temp); *pu1_dst = (i2_temp + *pu1_pred1 + 1) >> 1; } pu1_src += src_strd - wd; pu1_pred1 += src_strd - wd; pu1_dst += dst_strd - wd; } } /*! ************************************************************************** * \if Function name : ih264_inter_pred_luma_horz_qpel_vert_qpel \endif * * \brief * This routine applies the six tap filter to the predictors in the * vertical and horizontal direction and averages them to get pixels at locations * (1/4,1/4), (1/4, 3/4), (3/4, 1/4) & (3/4, 3/4). The six tap filtering operation * is described in sec 8.4.2.2.1 titled "Luma sample interpolation process" * * \param pu1_src: Pointer to the buffer containing the predictor values. * pu1_src could point to the frame buffer or the predictor buffer. * \param pu1_dst: Pointer to the destination buffer where the output of * the six tap filter is stored. * \param wd: Width of the rectangular pixel grid to be interpolated * \param ht: Height of the rectangular pixel grid to be interpolated * \param src_strd: Width of the buffer pointed to by puc_pred. * \param dst_strd: Width of the destination buffer * \param pu1_tmp: temporary buffer, UNUSED in this function * \param dydx: x and y reference offset for qpel calculations. * * \return * void * * \note * This function takes the 8 bit predictor values, applies the six tap * filter in the vertical direction and outputs the result clipped to * 8 bit precision. The input is stored in the buffer pointed to by * puc_pred while the output is stored in the buffer pointed by puc_dest. * Both puc_pred and puc_dest could point to the same buffer i.e. the * six tap filter could be done in place. * * \para <title> * <paragraph> * ... ************************************************************************** */ void ih264_inter_pred_luma_horz_qpel_vert_qpel(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 row, col; WORD32 x_offset = dydx & 0x3; WORD32 y_offset = dydx >> 2; WORD32 off1, off2, off3; UWORD8* pu1_pred_vert, *pu1_pred_horz; UNUSED(pu1_tmp); y_offset = y_offset & 0x3; off1 = src_strd; off2 = src_strd << 1; off3 = off1 + off2; pu1_pred_horz = pu1_src + (y_offset >> 1) * src_strd; pu1_pred_vert = pu1_src + (x_offset >> 1); for(row = 0; row < ht; row++) { for(col = 0; col < wd; col++, pu1_dst++, pu1_pred_vert++, pu1_pred_horz++) { WORD16 i2_temp_vert, i2_temp_horz; /* The logic below implements the following equation i2_temp = puc_pred[-2*src_strd] + puc_pred[3*src_strd] - 5 * (puc_pred[-1*src_strd] + puc_pred[2*src_strd]) + 20 * (puc_pred[0] + puc_pred[src_strd]); */ i2_temp_vert = pu1_pred_vert[-off2] + pu1_pred_vert[off3] - (pu1_pred_vert[-off1] + pu1_pred_vert[off2]) + ((pu1_pred_vert[0] + pu1_pred_vert[off1] - pu1_pred_vert[-off1] - pu1_pred_vert[off2]) << 2) + ((pu1_pred_vert[0] + pu1_pred_vert[off1]) << 4); i2_temp_vert = (i2_temp_vert + 16) >> 5; i2_temp_vert = CLIP_U8(i2_temp_vert); /* The logic below implements the following equation i16_temp = puc_pred[-2] - 5 * (puc_pred[-1] + puc_pred[2]) + 20 * (puc_pred[0] + puc_pred[1]) + puc_pred[3]; */ i2_temp_horz = pu1_pred_horz[-2] + pu1_pred_horz[3] - (pu1_pred_horz[-1] + pu1_pred_horz[2]) + ((pu1_pred_horz[0] + pu1_pred_horz[1] - pu1_pred_horz[-1] - pu1_pred_horz[2]) << 2) + ((pu1_pred_horz[0] + pu1_pred_horz[1]) << 4); i2_temp_horz = (i2_temp_horz + 16) >> 5; i2_temp_horz = CLIP_U8(i2_temp_horz); *pu1_dst = (i2_temp_vert + i2_temp_horz + 1) >> 1; } pu1_pred_vert += (src_strd - wd); pu1_pred_horz += (src_strd - wd); pu1_dst += (dst_strd - wd); } } /*! ************************************************************************** * \if Function name : ih264_inter_pred_luma_horz_qpel_vert_hpel \endif * * \brief * This routine applies the six tap filter to the predictors in the vertical * and horizontal direction to obtain the pixel at (1/2,1/2). It then interpolates * pixel at (0,1/2) and (1/2,1/2) to obtain pixel at (1/4,1/2). Similarly for (3/4,1/2). * The six tap filtering operation is described in sec 8.4.2.2.1 titled * "Luma sample interpolation process" * * \param pu1_src: Pointer to the buffer containing the predictor values. * pu1_src could point to the frame buffer or the predictor buffer. * \param pu1_dst: Pointer to the destination buffer where the output of * the six tap filter followed by interpolation is stored. * \param wd: Width of the rectangular pixel grid to be interpolated * \param ht: Height of the rectangular pixel grid to be interpolated * \param src_strd: Width of the buffer pointed to by puc_pred. * \param dst_strd: Width of the destination buffer * \param pu1_tmp: buffer to store temporary output after 1st 6-tap filter. * \param dydx: x and y reference offset for qpel calculations. * * \return * void * * \note * This function takes the 8 bit predictor values, applies the six tap * filter in the vertical direction and outputs the result clipped to * 8 bit precision. The input is stored in the buffer pointed to by * puc_pred while the output is stored in the buffer pointed by puc_dest. * Both puc_pred and puc_dest could point to the same buffer i.e. the * six tap filter could be done in place. * * \para <title> * <paragraph> * ... ************************************************************************** */ void ih264_inter_pred_luma_horz_qpel_vert_hpel(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 row, col; WORD32 tmp; WORD16* pi2_pred1_temp, *pi2_pred1; UWORD8* pu1_dst_tmp; WORD32 x_offset = dydx & 0x3; WORD16 i2_macro; pi2_pred1_temp = (WORD16*)pu1_tmp; pi2_pred1_temp += 2; pi2_pred1 = pi2_pred1_temp; pu1_dst_tmp = pu1_dst; for(row = 0; row < ht; row++) { for(col = -2; col < wd + 3; col++) { tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ tmp = ih264_g_six_tap[0] * (pu1_src[col - 2 * src_strd] + pu1_src[col + 3 * src_strd]) + ih264_g_six_tap[1] * (pu1_src[col - 1 * src_strd] + pu1_src[col + 2 * src_strd]) + ih264_g_six_tap[2] * (pu1_src[col] + pu1_src[col + 1 * src_strd]); pi2_pred1_temp[col] = tmp; } pu1_src += src_strd; pi2_pred1_temp = pi2_pred1_temp + wd + 5; } pi2_pred1_temp = pi2_pred1; for(row = 0; row < ht; row++) { for(col = 0; col < wd; col++) { tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ tmp = ih264_g_six_tap[0] * (pi2_pred1[col - 2] + pi2_pred1[col + 3]) + ih264_g_six_tap[1] * (pi2_pred1[col - 1] + pi2_pred1[col + 2]) + ih264_g_six_tap[2] * (pi2_pred1[col] + pi2_pred1[col + 1]); tmp = (tmp + 512) >> 10; pu1_dst[col] = CLIP_U8(tmp); } pi2_pred1 += (wd + 5); pu1_dst += dst_strd; } pu1_dst = pu1_dst_tmp; pi2_pred1_temp += (x_offset >> 1); for(row = ht; row != 0; row--) { for(col = wd; col != 0; col--, pu1_dst++, pi2_pred1_temp++) { UWORD8 uc_temp; /* Clipping the output of the six tap filter obtained from the first stage of the 2d filter stage */ *pi2_pred1_temp = (*pi2_pred1_temp + 16) >> 5; i2_macro = (*pi2_pred1_temp); uc_temp = CLIP_U8(i2_macro); *pu1_dst = (*pu1_dst + uc_temp + 1) >> 1; } pi2_pred1_temp += 5; pu1_dst += dst_strd - wd; } } /*! ************************************************************************** * \if Function name : ih264_inter_pred_luma_horz_hpel_vert_qpel \endif * * \brief * This routine applies the six tap filter to the predictors in the horizontal * and vertical direction to obtain the pixel at (1/2,1/2). It then interpolates * pixel at (1/2,0) and (1/2,1/2) to obtain pixel at (1/2,1/4). Similarly for (1/2,3/4). * The six tap filtering operation is described in sec 8.4.2.2.1 titled * "Luma sample interpolation process" * * \param pu1_src: Pointer to the buffer containing the predictor values. * pu1_src could point to the frame buffer or the predictor buffer. * \param pu1_dst: Pointer to the destination buffer where the output of * the six tap filter followed by interpolation is stored. * \param wd: Width of the rectangular pixel grid to be interpolated * \param ht: Height of the rectangular pixel grid to be interpolated * \param src_strd: Width of the buffer pointed to by puc_pred. * \param dst_strd: Width of the destination buffer * \param pu1_tmp: buffer to store temporary output after 1st 6-tap filter. * \param dydx: x and y reference offset for qpel calculations. * * \return * void * * \note * This function takes the 8 bit predictor values, applies the six tap * filter in the vertical direction and outputs the result clipped to * 8 bit precision. The input is stored in the buffer pointed to by * puc_pred while the output is stored in the buffer pointed by puc_dest. * Both puc_pred and puc_dest could point to the same buffer i.e. the * six tap filter could be done in place. * * \para <title> * <paragraph> * ... ************************************************************************** */ void ih264_inter_pred_luma_horz_hpel_vert_qpel(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 row, col; WORD32 tmp; WORD32 y_offset = dydx >> 2; WORD16* pi2_pred1_temp, *pi2_pred1; UWORD8* pu1_dst_tmp; //WORD32 x_offset = dydx & 0x3; WORD16 i2_macro; y_offset = y_offset & 0x3; pi2_pred1_temp = (WORD16*)pu1_tmp; pi2_pred1_temp += 2 * wd; pi2_pred1 = pi2_pred1_temp; pu1_dst_tmp = pu1_dst; pu1_src -= 2 * src_strd; for(row = -2; row < ht + 3; row++) { for(col = 0; col < wd; col++) { tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ tmp = ih264_g_six_tap[0] * (pu1_src[col - 2] + pu1_src[col + 3]) + ih264_g_six_tap[1] * (pu1_src[col - 1] + pu1_src[col + 2]) + ih264_g_six_tap[2] * (pu1_src[col] + pu1_src[col + 1]); pi2_pred1_temp[col - 2 * wd] = tmp; } pu1_src += src_strd; pi2_pred1_temp += wd; } pi2_pred1_temp = pi2_pred1; for(row = 0; row < ht; row++) { for(col = 0; col < wd; col++) { tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ tmp = ih264_g_six_tap[0] * (pi2_pred1[col - 2 * wd] + pi2_pred1[col + 3 * wd]) + ih264_g_six_tap[1] * (pi2_pred1[col - 1 * wd] + pi2_pred1[col + 2 * wd]) + ih264_g_six_tap[2] * (pi2_pred1[col] + pi2_pred1[col + 1 * wd]); tmp = (tmp + 512) >> 10; pu1_dst[col] = CLIP_U8(tmp); } pi2_pred1 += wd; pu1_dst += dst_strd; } pu1_dst = pu1_dst_tmp; pi2_pred1_temp += (y_offset >> 1) * wd; for(row = ht; row != 0; row--) { for(col = wd; col != 0; col--, pu1_dst++, pi2_pred1_temp++) { UWORD8 u1_temp; /* Clipping the output of the six tap filter obtained from the first stage of the 2d filter stage */ *pi2_pred1_temp = (*pi2_pred1_temp + 16) >> 5; i2_macro = (*pi2_pred1_temp); u1_temp = CLIP_U8(i2_macro); *pu1_dst = (*pu1_dst + u1_temp + 1) >> 1; } //pi16_pred1_temp += wd; pu1_dst += dst_strd - wd; } } /** ******************************************************************************* * function:ih264_inter_pred_luma_bilinear * * @brief * This routine applies the bilinear filter to the predictors . * The filtering operation is described in * sec 8.4.2.2.1 titled "Luma sample interpolation process" * * @par Description: \note * This function is called to obtain pixels lying at the following * locations (1/4,1), (3/4,1),(1,1/4), (1,3/4) ,(1/4,1/2), (3/4,1/2),(1/2,1/4), (1/2,3/4),(3/4,1/4),(1/4,3/4),(3/4,3/4)&& (1/4,1/4) . * The function averages the two adjacent values from the two input arrays in horizontal direction. * * * @param[in] pu1_src1: * UWORD8 Pointer to the buffer containing the first input array. * * @param[in] pu1_src2: * UWORD8 Pointer to the buffer containing the second input array. * * @param[out] pu1_dst * UWORD8 pointer to the destination where the output of bilinear filter is stored. * * @param[in] src_strd1 * Stride of the first input buffer * * @param[in] src_strd2 * Stride of the second input buffer * * @param[in] dst_strd * integer destination stride of pu1_dst * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_inter_pred_luma_bilinear(UWORD8 *pu1_src1, UWORD8 *pu1_src2, UWORD8 *pu1_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 ht, WORD32 wd) { WORD32 row, col; WORD16 i2_tmp; for(row = 0; row < ht; row++) { for(col = 0; col < wd; col++) { i2_tmp = pu1_src1[col] + pu1_src2[col]; i2_tmp = (i2_tmp + 1) >> 1; pu1_dst[col] = CLIP_U8(i2_tmp); } pu1_src1 += src_strd1; pu1_src2 += src_strd2; pu1_dst += dst_strd; } } /** ******************************************************************************* * * @brief * Interprediction chroma filter * * @par Description: * Applies filtering to chroma samples as mentioned in * sec 8.4.2.2.2 titled "chroma sample interpolation process" * * @param[in] pu1_src * UWORD8 pointer to the source containing alternate U and V samples * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] u1_dx * dx value where the sample is to be produced(refer sec 8.4.2.2.2 ) * * @param[in] u1_dy * dy value where the sample is to be produced(refer sec 8.4.2.2.2 ) * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_inter_pred_chroma(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 dx, WORD32 dy, WORD32 ht, WORD32 wd) { WORD32 row, col; WORD16 i2_tmp; for(row = 0; row < ht; row++) { for(col = 0; col < 2 * wd; col++) { i2_tmp = 0; /* applies equation (8-266) in section 8.4.2.2.2 */ i2_tmp = (8 - dx) * (8 - dy) * pu1_src[col] + (dx) * (8 - dy) * pu1_src[col + 2] + (8 - dx) * (dy) * (pu1_src + src_strd)[col] + (dx) * (dy) * (pu1_src + src_strd)[col + 2]; i2_tmp = (i2_tmp + 32) >> 6; pu1_dst[col] = CLIP_U8(i2_tmp); } pu1_src += src_strd; pu1_dst += dst_strd; } } ================================================ FILE: dependencies/ih264d/common/ih264_inter_pred_filters.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_inter_pred_filters.h * * @brief * Declarations of functions used for inter prediction * * @author * Ittiam * * @par List of Functions: * -ih264_inter_pred_luma_copy * -ih264_interleave_copy * -ih264_inter_pred_luma_horz * -ih264_inter_pred_luma_vert * -ih264_inter_pred_luma_horz_hpel_vert_hpel * -ih264_inter_pred_luma_vert_qpel * -ih264_inter_pred_luma_horz_qpel * -ih264_inter_pred_luma_horz_qpel_vert_qpel * -ih264_inter_pred_luma_horz_qpel_vert_hpel * -ih264_inter_pred_luma_horz_hpel_vert_qpel * -ih264_inter_pred_luma_bilinear * -ih264_inter_pred_chroma * -ih264_inter_pred_luma_copy_a9q * -ih264_interleave_copy_a9 * -ih264_inter_pred_luma_horz_a9q * -ih264_inter_pred_luma_vert_a9q * -ih264_inter_pred_luma_bilinear_a9q * -ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q * -ih264_inter_pred_luma_horz_qpel_a9q * -ih264_inter_pred_luma_vert_qpel_a9q * -ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q * -ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q * -ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q * -ih264_inter_pred_chroma_a9q * -ih264_inter_pred_luma_copy_av8 * -ih264_interleave_copy_av8 * -ih264_inter_pred_luma_horz_av8 * -ih264_inter_pred_luma_vert_av8 * -ih264_inter_pred_luma_bilinear_av8 * -ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 * -ih264_inter_pred_luma_horz_qpel_av8 * -ih264_inter_pred_luma_vert_qpel_av8 * -ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 * -ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 * -ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 * -ih264_inter_pred_chroma_av8 * -ih264_inter_pred_chroma_dx_zero_av8 * -ih264_inter_pred_chroma_dy_zero_av8 * -ih264_inter_pred_luma_copy_ssse3 * -ih264_inter_pred_luma_copy_ssse3 * -ih264_inter_pred_luma_horz_ssse3 * -ih264_inter_pred_luma_vert_ssse3 * -ih264_inter_pred_luma_bilinear_ssse3 * -ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3 * -ih264_inter_pred_luma_horz_qpel_ssse3 * -ih264_inter_pred_luma_vert_qpel_ssse3 * -ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3 * -ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3 * -ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3 * -ih264_inter_pred_chroma_ssse3 * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_INTER_PRED_H_ #define _IH264_INTER_PRED_H_ /*****************************************************************************/ /* Constant Data variables */ /*****************************************************************************/ extern const WORD32 ih264_g_six_tap[3];/* coefficients for 6 tap filtering*/ /*****************************************************************************/ /* Extern Function Declarations */ /*****************************************************************************/ typedef void ih264_inter_pred_luma_ft(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx); typedef void ih264_interleave_copy_ft(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd); typedef void ih264_inter_pred_luma_bilinear_ft(UWORD8 *pu1_src1, UWORD8 *pu1_src2, UWORD8 *pu1_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 height, WORD32 width); typedef void ih264_inter_pred_chroma_ft(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 dx, WORD32 dy, WORD32 ht, WORD32 wd); /* No NEON Declarations */ ih264_inter_pred_luma_ft ih264_inter_pred_luma_copy; ih264_interleave_copy_ft ih264_interleave_copy; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz; ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_hpel; ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_qpel; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_qpel; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_hpel; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_qpel; ih264_inter_pred_luma_bilinear_ft ih264_inter_pred_luma_bilinear; ih264_inter_pred_chroma_ft ih264_inter_pred_chroma; /* A9 NEON Declarations */ ih264_inter_pred_luma_ft ih264_inter_pred_luma_copy_a9q; ih264_interleave_copy_ft ih264_interleave_copy_a9; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_a9q; ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_a9q; ih264_inter_pred_luma_bilinear_ft ih264_inter_pred_luma_bilinear_a9q; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_a9q; ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_qpel_a9q; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q; ih264_inter_pred_chroma_ft ih264_inter_pred_chroma_a9q; /* AV8 NEON Declarations */ ih264_inter_pred_luma_ft ih264_inter_pred_luma_copy_av8; ih264_interleave_copy_ft ih264_interleave_copy_av8; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_av8; ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_av8; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_hpel_av8; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_av8; ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_qpel_av8; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_qpel_av8; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_hpel_av8; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_qpel_av8; ih264_inter_pred_chroma_ft ih264_inter_pred_chroma_av8; ih264_inter_pred_chroma_ft ih264_inter_pred_chroma_dx_zero_av8; ih264_inter_pred_chroma_ft ih264_inter_pred_chroma_dy_zero_av8; /* SSSE3 Intrinsic Declarations */ ih264_inter_pred_luma_ft ih264_inter_pred_luma_copy_ssse3; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_ssse3; ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_ssse3; ih264_inter_pred_luma_bilinear_ft ih264_inter_pred_luma_bilinear_ssse3; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_ssse3; ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_qpel_ssse3; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3; ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3; ih264_inter_pred_chroma_ft ih264_inter_pred_chroma_ssse3; #endif /** Nothing past this point */ ================================================ FILE: dependencies/ih264d/common/ih264_intra_pred_filters.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_intra_pred_filters.h * * @brief * Declarations of functions used for intra prediction * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef IH264_INTRA_PRED_FILTERS_H_ #define IH264_INTRA_PRED_FILTERS_H_ /*****************************************************************************/ /* Macro Expansion */ /*****************************************************************************/ /*! Filter (1,2,1) i.e (a + 2b + c) / 4 */ #define FILT121(a,b,c) ((a + (b<<1) + c + 2)>>2) /*! Filter (1,1) i.e (a + b) / 2 */ #define FILT11(a,b) ((a + b + 1)>>1) /*****************************************************************************/ /* Global Variables */ /*****************************************************************************/ /* Global variables used only in assembly files*/ extern const WORD8 ih264_gai1_intrapred_luma_plane_coeffs[]; extern const WORD8 ih264_gai1_intrapred_chroma_plane_coeffs1[]; extern const WORD8 ih264_gai1_intrapred_chroma_plane_coeffs2[]; extern const WORD8 ih264_gai1_intrapred_luma_8x8_horz_u[]; /*****************************************************************************/ /* Extern Function Declarations */ /*****************************************************************************/ typedef void ih264_intra_pred_ref_filtering_ft(UWORD8 *pu1_left, UWORD8 *pu1_topleft, UWORD8 *pu1_top, UWORD8 *pu1_dst, WORD32 left_strd, WORD32 ngbr_avail); typedef void ih264_intra_pred_luma_ft(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail); /* No Neon Definitions */ /* Luma 4x4 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_dc; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dl; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dr; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_r; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_d; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_l; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_u; /* Luma 8x8 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_dc; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dl; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dr; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_r; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_d; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_l; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_u; /* Luma 16x16 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_vert; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_horz; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_dc; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_plane; /* Chroma 8x8 Intra pred filters */ typedef ih264_intra_pred_luma_ft ih264_intra_pred_chroma_ft; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_dc; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_horz; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_vert; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_plane; ih264_intra_pred_ref_filtering_ft ih264_intra_pred_luma_8x8_mode_ref_filtering; /* A9 Definition */ /* Luma 4x4 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_dc_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dl_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dr_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_r_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_d_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_l_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_u_a9q; /* Luma 8x8 Intra pred filters */ ih264_intra_pred_ref_filtering_ft ih264_intra_pred_luma_8x8_mode_ref_filtering_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_dc_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dl_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dr_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_r_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_d_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_l_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_u_a9q; /* Luma 16x16 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_vert_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_horz_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_dc_a9q; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_plane_a9q; /* Chroma 8x8 Intra pred filters */ ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_dc_a9q; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_horz_a9q; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_vert_a9q; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_plane_a9q; /* X86 Intrinsic Definitions */ /* Luma 4x4 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_dc_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dl_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dr_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_r_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_d_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_l_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_u_ssse3; /* Luma 8x8 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_dc_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dl_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dr_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_r_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_d_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_l_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_u_ssse3; /* Luma 16x16 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_vert_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_horz_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_dc_ssse3; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_plane_ssse3; /* Chroma 8x8 Intra pred filters */ ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_dc_ssse3; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_horz_ssse3; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_vert_ssse3; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_plane_ssse3; /* AV8 Definition */ /* Luma 4x4 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_dc_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dl_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dr_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_r_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_d_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_l_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_u_av8; /* Luma 8x8 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_dc_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dl_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dr_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_r_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_d_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_l_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_u_av8; /* Luma 16x16 Intra pred filters */ ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_vert_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_horz_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_dc_av8; ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_plane_av8; /* Chroma 8x8 Intra pred filters */ ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_dc_av8; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_horz_av8; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_vert_av8; ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_plane_av8; #endif /* IH264_INTRA_PRED_FILTERS_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_iquant_itrans_recon.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_iquant_itrans_recon.c * * @brief * Contains definition of functions for h264 inverse quantization inverse transformation and recon * * @author * Ittiam * * @par List of Functions: * - ih264_iquant_itrans_recon_4x4() * - ih264_iquant_itrans_recon_8x8() * - ih264_iquant_itrans_recon_4x4_dc() * - ih264_iquant_itrans_recon_8x8_dc() * - ih264_iquant_itrans_recon_chroma_4x4() * -ih264_iquant_itrans_recon_chroma_4x4_dc() * * @remarks * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* User include files */ #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_trans_macros.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_trans_data.h" #include "ih264_size_defs.h" #include "ih264_structs.h" #include "ih264_trans_quant_itrans_iquant.h" /* ******************************************************************************** * * @brief This function reconstructs a 4x4 sub block from quantized resiude and * prediction buffer * * @par Description: * The quantized residue is first inverse quantized, then inverse transformed. * This inverse transformed content is added to the prediction buffer to recon- * struct the end output * * @param[in] pi2_src * quantized 4x4 block * * @param[in] pu1_pred * prediction 4x4 block * * @param[out] pu1_out * reconstructed 4x4 block * * @param[in] src_strd * quantization buffer stride * * @param[in] pred_strd, * Prediction buffer stride * * @param[in] out_strd * recon buffer Stride * * @param[in] pu2_scaling_list * pointer to scaling list * * @param[in] pu2_norm_adjust * pointer to inverse scale matrix * * @param[in] u4_qp_div_6 * Floor (qp/6) * * @param[in] pi4_tmp * temporary buffer of size 1*16 * * @returns none * * @remarks none * ******************************************************************************* */ void ih264_iquant_itrans_recon_4x4(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD16 *pi2_tmp, WORD32 iq_start_idx, WORD16 *pi2_dc_ld_addr ) { WORD16 *pi2_src_ptr = pi2_src; WORD16 *pi2_tmp_ptr = pi2_tmp; UWORD8 *pu1_pred_ptr = pu1_pred; UWORD8 *pu1_out_ptr = pu1_out; WORD16 x0, x1, x2, x3, i; WORD32 q0, q1, q2, q3; WORD16 i_macro; WORD16 rnd_fact = (u4_qp_div_6 < 4) ? 1 << (3 - u4_qp_div_6) : 0; /* inverse quant */ /*horizontal inverse transform */ for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) { q0 = pi2_src_ptr[0]; INV_QUANT(q0, pu2_iscal_mat[0], pu2_weigh_mat[0], u4_qp_div_6, rnd_fact, 4); if (i==0 && iq_start_idx == 1) q0 = pi2_dc_ld_addr[0]; // Restoring dc value for intra case q2 = pi2_src_ptr[2]; INV_QUANT(q2, pu2_iscal_mat[2], pu2_weigh_mat[2], u4_qp_div_6, rnd_fact, 4); x0 = q0 + q2; x1 = q0 - q2; q1 = pi2_src_ptr[1]; INV_QUANT(q1, pu2_iscal_mat[1], pu2_weigh_mat[1], u4_qp_div_6, rnd_fact, 4); q3 = pi2_src_ptr[3]; INV_QUANT(q3, pu2_iscal_mat[3], pu2_weigh_mat[3], u4_qp_div_6, rnd_fact, 4); x2 = (q1 >> 1) - q3; x3 = q1 + (q3 >> 1); pi2_tmp_ptr[0] = x0 + x3; pi2_tmp_ptr[1] = x1 + x2; pi2_tmp_ptr[2] = x1 - x2; pi2_tmp_ptr[3] = x0 - x3; pi2_src_ptr += SUB_BLK_WIDTH_4x4; pi2_tmp_ptr += SUB_BLK_WIDTH_4x4; pu2_iscal_mat += SUB_BLK_WIDTH_4x4; pu2_weigh_mat += SUB_BLK_WIDTH_4x4; } /* vertical inverse transform */ pi2_tmp_ptr = pi2_tmp; for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) { pu1_pred_ptr = pu1_pred; pu1_out = pu1_out_ptr; x0 = (pi2_tmp_ptr[0] + pi2_tmp_ptr[8]); x1 = (pi2_tmp_ptr[0] - pi2_tmp_ptr[8]); x2 = (pi2_tmp_ptr[4] >> 1) - pi2_tmp_ptr[12]; x3 = pi2_tmp_ptr[4] + (pi2_tmp_ptr[12] >> 1); /* inverse prediction */ i_macro = x0 + x3; i_macro = ((i_macro + 32) >> 6); i_macro += *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = x1 + x2; i_macro = ((i_macro + 32) >> 6); i_macro += *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = x1 - x2; i_macro = ((i_macro + 32) >> 6); i_macro += *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = x0 - x3; i_macro = ((i_macro + 32) >> 6); i_macro += *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pi2_tmp_ptr++; pu1_out_ptr++; pu1_pred++; } } void ih264_iquant_itrans_recon_4x4_dc(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD16 *pi2_tmp, WORD32 iq_start_idx, WORD16 *pi2_dc_ld_addr) { UWORD8 *pu1_pred_ptr = pu1_pred; UWORD8 *pu1_out_ptr = pu1_out; WORD32 q0; WORD16 x, i_macro, i; WORD16 rnd_fact = (u4_qp_div_6 < 4) ? 1 << (3 - u4_qp_div_6) : 0; UNUSED(pi2_tmp); if (iq_start_idx == 0) { q0 = pi2_src[0]; INV_QUANT(q0, pu2_iscal_mat[0], pu2_weigh_mat[0], u4_qp_div_6, rnd_fact, 4); } else { q0 = pi2_dc_ld_addr[0]; // Restoring dc value for intra case3 } i_macro = ((q0 + 32) >> 6); for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) { pu1_pred_ptr = pu1_pred; pu1_out = pu1_out_ptr; /* inverse prediction */ x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_out_ptr++; pu1_pred++; } } /** ******************************************************************************* * * @brief * This function performs inverse quant and Inverse transform type Ci4 for 8x8 block * * @par Description: * Performs inverse transform Ci8 and adds the residue to get the * reconstructed block * * @param[in] pi2_src * Input 8x8coefficients * * @param[in] pu1_pred * Prediction 8x8 block * * @param[out] pu1_recon * Output 8x8 block * * @param[in] q_div * QP/6 * * @param[in] q_rem * QP%6 * * @param[in] q_lev * Quantizer level * * @param[in] src_strd * Input stride * * @param[in] pred_strd, * Prediction stride * * @param[in] out_strd * Output Stride * * @param[in] pi4_tmp * temporary buffer of size 1*16 we dont need a bigger blcok since we reuse * the tmp for each block * * @param[in] pu4_iquant_mat * Pointer to the inverse quantization matrix * * @returns Void * * @remarks * None * ******************************************************************************* */ void ih264_iquant_itrans_recon_8x8(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscale_mat, const UWORD16 *pu2_weigh_mat, UWORD32 qp_div, WORD16 *pi2_tmp, WORD32 iq_start_idx, WORD16 *pi2_dc_ld_addr ) { WORD32 i; WORD16 *pi2_tmp_ptr = pi2_tmp; UWORD8 *pu1_pred_ptr = pu1_pred; UWORD8 *pu1_out_ptr = pu1_out; WORD16 i_z0, i_z1, i_z2, i_z3, i_z4, i_z5, i_z6, i_z7; WORD16 i_y0, i_y1, i_y2, i_y3, i_y4, i_y5, i_y6, i_y7; WORD16 i_macro; WORD32 q; WORD32 rnd_fact = (qp_div < 6) ? (1 << (5 - qp_div)) : 0; UNUSED(iq_start_idx); UNUSED(pi2_dc_ld_addr); /*************************************************************/ /* De quantization of coefficients. Will be replaced by SIMD */ /* operations on platform. Note : DC coeff is not scaled */ /*************************************************************/ for(i = 0; i < (SUB_BLK_WIDTH_8x8 * SUB_BLK_WIDTH_8x8); i++) { q = pi2_src[i]; INV_QUANT(q, pu2_iscale_mat[i], pu2_weigh_mat[i], qp_div, rnd_fact, 6); pi2_tmp_ptr[i] = q; } /* Perform Inverse transform */ /*--------------------------------------------------------------------*/ /* IDCT [ Horizontal transformation ] */ /*--------------------------------------------------------------------*/ for(i = 0; i < SUB_BLK_WIDTH_8x8; i++) { /*------------------------------------------------------------------*/ /* y0 = w0 + w4 */ /* y1 = -w3 + w5 - w7 - (w7 >> 1) */ /* y2 = w0 - w4 */ /* y3 = w1 + w7 - w3 - (w3 >> 1) */ /* y4 = (w2 >> 1) - w6 */ /* y5 = -w1 + w7 + w5 + (w5 >> 1) */ /* y6 = w2 + (w6 >> 1) */ /* y7 = w3 + w5 + w1 + (w1 >> 1) */ /*------------------------------------------------------------------*/ i_y0 = (pi2_tmp_ptr[0] + pi2_tmp_ptr[4] ); i_y1 = ((WORD32)(-pi2_tmp_ptr[3]) + pi2_tmp_ptr[5] - pi2_tmp_ptr[7] - (pi2_tmp_ptr[7] >> 1)); i_y2 = (pi2_tmp_ptr[0] - pi2_tmp_ptr[4] ); i_y3 = ((WORD32)pi2_tmp_ptr[1] + pi2_tmp_ptr[7] - pi2_tmp_ptr[3] - (pi2_tmp_ptr[3] >> 1)); i_y4 = ((pi2_tmp_ptr[2] >> 1) - pi2_tmp_ptr[6] ); i_y5 = ((WORD32)(-pi2_tmp_ptr[1]) + pi2_tmp_ptr[7] + pi2_tmp_ptr[5] + (pi2_tmp_ptr[5] >> 1)); i_y6 = (pi2_tmp_ptr[2] + (pi2_tmp_ptr[6] >> 1)); i_y7 = ((WORD32)pi2_tmp_ptr[3] + pi2_tmp_ptr[5] + pi2_tmp_ptr[1] + (pi2_tmp_ptr[1] >> 1)); /*------------------------------------------------------------------*/ /* z0 = y0 + y6 */ /* z1 = y1 + (y7 >> 2) */ /* z2 = y2 + y4 */ /* z3 = y3 + (y5 >> 2) */ /* z4 = y2 - y4 */ /* z5 = (y3 >> 2) - y5 */ /* z6 = y0 - y6 */ /* z7 = y7 - (y1 >> 2) */ /*------------------------------------------------------------------*/ i_z0 = i_y0 + i_y6; i_z1 = i_y1 + (i_y7 >> 2); i_z2 = i_y2 + i_y4; i_z3 = i_y3 + (i_y5 >> 2); i_z4 = i_y2 - i_y4; i_z5 = (i_y3 >> 2) - i_y5; i_z6 = i_y0 - i_y6; i_z7 = i_y7 - (i_y1 >> 2); /*------------------------------------------------------------------*/ /* x0 = z0 + z7 */ /* x1 = z2 + z5 */ /* x2 = z4 + z3 */ /* x3 = z6 + z1 */ /* x4 = z6 - z1 */ /* x5 = z4 - z3 */ /* x6 = z2 - z5 */ /* x7 = z0 - z7 */ /*------------------------------------------------------------------*/ pi2_tmp_ptr[0] = i_z0 + i_z7; pi2_tmp_ptr[1] = i_z2 + i_z5; pi2_tmp_ptr[2] = i_z4 + i_z3; pi2_tmp_ptr[3] = i_z6 + i_z1; pi2_tmp_ptr[4] = i_z6 - i_z1; pi2_tmp_ptr[5] = i_z4 - i_z3; pi2_tmp_ptr[6] = i_z2 - i_z5; pi2_tmp_ptr[7] = i_z0 - i_z7; /* move to the next row */ //pi2_src_ptr += SUB_BLK_WIDTH_8x8; pi2_tmp_ptr += SUB_BLK_WIDTH_8x8; } /*--------------------------------------------------------------------*/ /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ /* */ /* Add the prediction and store it back to reconstructed frame buffer */ /* [Prediction buffer itself in this case] */ /*--------------------------------------------------------------------*/ pi2_tmp_ptr = pi2_tmp; for(i = 0; i < SUB_BLK_WIDTH_8x8; i++) { pu1_pred_ptr = pu1_pred; pu1_out = pu1_out_ptr; /*------------------------------------------------------------------*/ /* y0j = w0j + w4j */ /* y1j = -w3j + w5j -w7j -(w7j >> 1) */ /* y2j = w0j -w4j */ /* y3j = w1j + w7j -w3j -(w3j >> 1) */ /* y4j = ( w2j >> 1 ) -w6j */ /* y5j = -w1j + w7j + w5j + (w5j >> 1) */ /* y6j = w2j + ( w6j >> 1 ) */ /* y7j = w3j + w5j + w1j + (w1j >> 1) */ /*------------------------------------------------------------------*/ i_y0 = pi2_tmp_ptr[0] + pi2_tmp_ptr[32]; i_y1 = (WORD32)(-pi2_tmp_ptr[24]) + pi2_tmp_ptr[40] - pi2_tmp_ptr[56] - (pi2_tmp_ptr[56] >> 1); i_y2 = pi2_tmp_ptr[0] - pi2_tmp_ptr[32]; i_y3 = (WORD32)pi2_tmp_ptr[8] + pi2_tmp_ptr[56] - pi2_tmp_ptr[24] - (pi2_tmp_ptr[24] >> 1); i_y4 = (pi2_tmp_ptr[16] >> 1) - pi2_tmp_ptr[48]; i_y5 = (WORD32)(-pi2_tmp_ptr[8]) + pi2_tmp_ptr[56] + pi2_tmp_ptr[40] + (pi2_tmp_ptr[40] >> 1); i_y6 = pi2_tmp_ptr[16] + (pi2_tmp_ptr[48] >> 1); i_y7 = (WORD32)pi2_tmp_ptr[24] + pi2_tmp_ptr[40] + pi2_tmp_ptr[8] + (pi2_tmp_ptr[8] >> 1); /*------------------------------------------------------------------*/ /* z0j = y0j + y6j */ /* z1j = y1j + (y7j >> 2) */ /* z2j = y2j + y4j */ /* z3j = y3j + (y5j >> 2) */ /* z4j = y2j -y4j */ /* z5j = (y3j >> 2) -y5j */ /* z6j = y0j -y6j */ /* z7j = y7j -(y1j >> 2) */ /*------------------------------------------------------------------*/ i_z0 = i_y0 + i_y6; i_z1 = i_y1 + (i_y7 >> 2); i_z2 = i_y2 + i_y4; i_z3 = i_y3 + (i_y5 >> 2); i_z4 = i_y2 - i_y4; i_z5 = (i_y3 >> 2) - i_y5; i_z6 = i_y0 - i_y6; i_z7 = i_y7 - (i_y1 >> 2); /*------------------------------------------------------------------*/ /* x0j = z0j + z7j */ /* x1j = z2j + z5j */ /* x2j = z4j + z3j */ /* x3j = z6j + z1j */ /* x4j = z6j -z1j */ /* x5j = z4j -z3j */ /* x6j = z2j -z5j */ /* x7j = z0j -z7j */ /*------------------------------------------------------------------*/ i_macro = ((i_z0 + i_z7 + 32) >> 6) + *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); /* Change uc_recBuffer to Point to next element in the same column*/ pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = ((i_z2 + i_z5 + 32) >> 6) + *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = ((i_z4 + i_z3 + 32) >> 6) + *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = ((i_z6 + i_z1 + 32) >> 6) + *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = ((i_z6 - i_z1 + 32) >> 6) + *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = ((i_z4 - i_z3 + 32) >> 6) + *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = ((i_z2 - i_z5 + 32) >> 6) + *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = ((i_z0 - i_z7 + 32) >> 6) + *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pi2_tmp_ptr++; pu1_out_ptr++; pu1_pred++; } } void ih264_iquant_itrans_recon_8x8_dc(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscale_mat, const UWORD16 *pu2_weigh_mat, UWORD32 qp_div, WORD16 *pi2_tmp, WORD32 iq_start_idx, WORD16 *pi2_dc_ld_addr) { UWORD8 *pu1_pred_ptr = pu1_pred; UWORD8 *pu1_out_ptr = pu1_out; WORD16 x, i, i_macro; WORD32 q; WORD32 rnd_fact = (qp_div < 6) ? (1 << (5 - qp_div)) : 0; UNUSED(pi2_tmp); UNUSED(iq_start_idx); UNUSED(pi2_dc_ld_addr); /*************************************************************/ /* Dequantization of coefficients. Will be replaced by SIMD */ /* operations on platform. Note : DC coeff is not scaled */ /*************************************************************/ q = pi2_src[0]; INV_QUANT(q, pu2_iscale_mat[0], pu2_weigh_mat[0], qp_div, rnd_fact, 6); i_macro = (q + 32) >> 6; /* Perform Inverse transform */ /*--------------------------------------------------------------------*/ /* IDCT [ Horizontal transformation ] */ /*--------------------------------------------------------------------*/ /*--------------------------------------------------------------------*/ /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ /* */ /* Add the prediction and store it back to reconstructed frame buffer */ /* [Prediction buffer itself in this case] */ /*--------------------------------------------------------------------*/ for(i = 0; i < SUB_BLK_WIDTH_8x8; i++) { pu1_pred_ptr = pu1_pred; pu1_out = pu1_out_ptr; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); /* Change uc_recBuffer to Point to next element in the same column*/ pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_out_ptr++; pu1_pred++; } } /* ******************************************************************************** * * @brief This function reconstructs a 4x4 sub block from quantized resiude and * prediction buffer * * @par Description: * The quantized residue is first inverse quantized, then inverse transformed. * This inverse transformed content is added to the prediction buffer to recon- * struct the end output * * @param[in] pi2_src * quantized 4x4 block * * @param[in] pu1_pred * prediction 4x4 block * * @param[out] pu1_out * reconstructed 4x4 block * * @param[in] src_strd * quantization buffer stride * * @param[in] pred_strd, * Prediction buffer stride * * @param[in] out_strd * recon buffer Stride * * @param[in] pu2_scaling_list * pointer to scaling list * * @param[in] pu2_norm_adjust * pointer to inverse scale matrix * * @param[in] u4_qp_div_6 * Floor (qp/6) * * @param[in] pi4_tmp * temporary buffer of size 1*16 * * @returns none * * @remarks none * ******************************************************************************* */ void ih264_iquant_itrans_recon_chroma_4x4(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD16 *pi2_tmp, WORD16 *pi2_dc_src) { WORD16 *pi2_src_ptr = pi2_src; WORD16 *pi2_tmp_ptr = pi2_tmp; UWORD8 *pu1_pred_ptr = pu1_pred; UWORD8 *pu1_out_ptr = pu1_out; WORD16 x0, x1, x2, x3, i; WORD32 q0, q1, q2, q3; WORD16 i_macro; WORD16 rnd_fact = (u4_qp_div_6 < 4) ? 1 << (3 - u4_qp_div_6) : 0; /* inverse quant */ /*horizontal inverse transform */ for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) { if(i==0) { q0 = pi2_dc_src[0]; } else { q0 = pi2_src_ptr[0]; INV_QUANT(q0, pu2_iscal_mat[0], pu2_weigh_mat[0], u4_qp_div_6, rnd_fact, 4); } q2 = pi2_src_ptr[2]; INV_QUANT(q2, pu2_iscal_mat[2], pu2_weigh_mat[2], u4_qp_div_6, rnd_fact, 4); x0 = q0 + q2; x1 = q0 - q2; q1 = pi2_src_ptr[1]; INV_QUANT(q1, pu2_iscal_mat[1], pu2_weigh_mat[1], u4_qp_div_6, rnd_fact, 4); q3 = pi2_src_ptr[3]; INV_QUANT(q3, pu2_iscal_mat[3], pu2_weigh_mat[3], u4_qp_div_6, rnd_fact, 4); x2 = (q1 >> 1) - q3; x3 = q1 + (q3 >> 1); pi2_tmp_ptr[0] = x0 + x3; pi2_tmp_ptr[1] = x1 + x2; pi2_tmp_ptr[2] = x1 - x2; pi2_tmp_ptr[3] = x0 - x3; pi2_src_ptr += SUB_BLK_WIDTH_4x4; pi2_tmp_ptr += SUB_BLK_WIDTH_4x4; pu2_iscal_mat += SUB_BLK_WIDTH_4x4; pu2_weigh_mat += SUB_BLK_WIDTH_4x4; } /* vertical inverse transform */ pi2_tmp_ptr = pi2_tmp; for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) { pu1_pred_ptr = pu1_pred; pu1_out = pu1_out_ptr; x0 = (pi2_tmp_ptr[0] + pi2_tmp_ptr[8]); x1 = (pi2_tmp_ptr[0] - pi2_tmp_ptr[8]); x2 = (pi2_tmp_ptr[4] >> 1) - pi2_tmp_ptr[12]; x3 = pi2_tmp_ptr[4] + (pi2_tmp_ptr[12] >> 1); /* inverse prediction */ i_macro = x0 + x3; i_macro = ((i_macro + 32) >> 6); i_macro += *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = x1 + x2; i_macro = ((i_macro + 32) >> 6); i_macro += *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = x1 - x2; i_macro = ((i_macro + 32) >> 6); i_macro += *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pu1_pred_ptr += pred_strd; pu1_out += out_strd; i_macro = x0 - x3; i_macro = ((i_macro + 32) >> 6); i_macro += *pu1_pred_ptr; *pu1_out = CLIP_U8(i_macro); pi2_tmp_ptr++; pu1_out_ptr+= 2; //Interleaved store for output pu1_pred+= 2; //Interleaved load for pred buffer } } /* ******************************************************************************** * * @brief This function reconstructs a 4x4 sub block from quantized resiude and * prediction buffer if only dc value is present for residue * * @par Description: * The quantized residue is first inverse quantized, * This inverse quantized content is added to the prediction buffer to recon- * struct the end output * * @param[in] pi2_src * quantized dc coefficient * * @param[in] pu1_pred * prediction 4x4 block in interleaved format * * @param[in] pred_strd, * Prediction buffer stride in interleaved format * * @param[in] out_strd * recon buffer Stride * * @returns none * * @remarks none * ******************************************************************************* */ void ih264_iquant_itrans_recon_chroma_4x4_dc(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD16 *pi2_tmp, WORD16 *pi2_dc_src) { UWORD8 *pu1_pred_ptr = pu1_pred; UWORD8 *pu1_out_ptr = pu1_out; WORD32 q0; WORD16 x, i_macro, i; UNUSED(pi2_src); UNUSED(pu2_iscal_mat); UNUSED(pu2_weigh_mat); UNUSED(u4_qp_div_6); UNUSED(pi2_tmp); q0 = pi2_dc_src[0]; // Restoring dc value for intra case3 i_macro = ((q0 + 32) >> 6); for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) { pu1_pred_ptr = pu1_pred; pu1_out = pu1_out_ptr; /* inverse prediction */ x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_pred_ptr += pred_strd; pu1_out += out_strd; x = i_macro + *pu1_pred_ptr; *pu1_out = CLIP_U8(x); pu1_out_ptr+=2; pu1_pred+=2; } } ================================================ FILE: dependencies/ih264d/common/ih264_list.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_list.c * * @brief * Contains functions for buf queue * * @author * Harish * * @par List of Functions: * ih264_list_size() * ih264_list_lock() * ih264_list_unlock() * ih264_list_yield() * ih264_list_free() * ih264_list_init() * ih264_list_reset() * ih264_list_deinit() * ih264_list_terminate() * ih264_list_queue() * ih264_list_dequeue() * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "ih264_typedefs.h" #include "ithread.h" #include "ih264_platform_macros.h" #include "ih264_macros.h" #include "ih264_debug.h" #include "ih264_error.h" #include "ih264_list.h" /** ******************************************************************************* * * @brief Returns size for buf queue context. Does not include buf queue buffer * requirements * * @par Description * Returns size for buf queue context. Does not include buf queue buffer * requirements. Buffer size required to store the bufs should be allocated in * addition to the value returned here. * * @returns Size of the buf queue context * * @remarks * ******************************************************************************* */ WORD32 ih264_list_size(WORD32 num_entries, WORD32 entry_size) { WORD32 size; WORD32 clz; size = sizeof(list_t); size += ithread_get_mutex_lock_size(); /* Use next power of two number of entries*/ clz = CLZ(num_entries); num_entries = 1 << (32 - clz); size += num_entries * entry_size; return size; } /** ******************************************************************************* * * @brief * Locks the list context * * @par Description * Locks the list context by calling ithread_mutex_lock() * * @param[in] ps_list * Job Queue context * * @returns IH264_FAIL if mutex lock fails else IH264_SUCCESS * * @remarks * ******************************************************************************* */ IH264_ERROR_T ih264_list_lock(list_t *ps_list) { WORD32 retval; retval = ithread_mutex_lock(ps_list->pv_mutex); if(retval) { return IH264_FAIL; } return IH264_SUCCESS; } /** ******************************************************************************* * * @brief * Unlocks the list context * * @par Description * Unlocks the list context by calling ithread_mutex_unlock() * * @param[in] ps_list * Job Queue context * * @returns IH264_FAIL if mutex unlock fails else IH264_SUCCESS * * @remarks * ******************************************************************************* */ IH264_ERROR_T ih264_list_unlock(list_t *ps_list) { WORD32 retval; retval = ithread_mutex_unlock(ps_list->pv_mutex); if(retval) { return IH264_FAIL; } return IH264_SUCCESS; } /** ******************************************************************************* * * @brief * Yields the thread * * @par Description * Unlocks the list context by calling * ih264_list_unlock(), ithread_yield() and then ih264_list_lock() * list is unlocked before to ensure the list can be accessed by other threads * If unlock is not done before calling yield then no other thread can access * the list functions and update list. * * @param[in] ps_list * Job Queue context * * @returns IH264_FAIL if mutex lock unlock or yield fails else IH264_SUCCESS * * @remarks * ******************************************************************************* */ IH264_ERROR_T ih264_list_yield(list_t *ps_list) { IH264_ERROR_T ret = IH264_SUCCESS; IH264_ERROR_T rettmp; rettmp = ih264_list_unlock(ps_list); RETURN_IF((rettmp != IH264_SUCCESS), rettmp); ithread_yield(); if(ps_list->i4_yeild_interval_us > 0) ithread_usleep(ps_list->i4_yeild_interval_us); rettmp = ih264_list_lock(ps_list); RETURN_IF((rettmp != IH264_SUCCESS), rettmp); return ret; } /** ******************************************************************************* * * @brief free the buf queue pointers * * @par Description * Frees the list context * * @param[in] pv_buf * Memory for buf queue buffer and buf queue context * * @returns Pointer to buf queue context * * @remarks * Since it will be called only once by master thread this is not thread safe. * ******************************************************************************* */ IH264_ERROR_T ih264_list_free(list_t *ps_list) { WORD32 ret; ret = ithread_mutex_destroy(ps_list->pv_mutex); if(0 == ret) return IH264_SUCCESS; else return IH264_FAIL; } /** ******************************************************************************* * * @brief Initialize the buf queue * * @par Description * Initializes the list context and sets write and read pointers to start of * buf queue buffer * * @param[in] pv_buf * Memoy for buf queue buffer and buf queue context * * @param[in] buf_size * Size of the total memory allocated * * @returns Pointer to buf queue context * * @remarks * Since it will be called only once by master thread this is not thread safe. * ******************************************************************************* */ void* ih264_list_init(void *pv_buf, WORD32 buf_size, WORD32 num_entries, WORD32 entry_size, WORD32 yeild_interval_us) { list_t *ps_list; UWORD8 *pu1_buf; pu1_buf = (UWORD8 *)pv_buf; ps_list = (list_t *)pu1_buf; pu1_buf += sizeof(list_t); buf_size -= sizeof(list_t); ps_list->pv_mutex = pu1_buf; pu1_buf += ithread_get_mutex_lock_size(); buf_size -= ithread_get_mutex_lock_size(); if (buf_size <= 0) return NULL; ithread_mutex_init(ps_list->pv_mutex); /* Ensure num_entries is power of two */ ASSERT(0 == (num_entries & (num_entries - 1))); /* Ensure remaining buffer is large enough to hold given number of entries */ ASSERT((num_entries * entry_size) <= buf_size); ps_list->pv_buf_base = pu1_buf; ps_list->i4_terminate = 0; ps_list->i4_entry_size = entry_size; ps_list->i4_buf_rd_idx = 0; ps_list->i4_buf_wr_idx = 0; ps_list->i4_log2_buf_max_idx = 32 - CLZ(num_entries); ps_list->i4_buf_max_idx = num_entries; ps_list->i4_yeild_interval_us = yeild_interval_us; return ps_list; } /** ******************************************************************************* * * @brief * Resets the list context * * @par Description * Resets the list context by initializing buf queue context elements * * @param[in] ps_list * Job Queue context * * @returns IH264_FAIL if lock unlock fails else IH264_SUCCESS * * @remarks * ******************************************************************************* */ IH264_ERROR_T ih264_list_reset(list_t *ps_list) { IH264_ERROR_T ret = IH264_SUCCESS; ret = ih264_list_lock(ps_list); RETURN_IF((ret != IH264_SUCCESS), ret); ps_list->i4_terminate = 0; ps_list->i4_buf_rd_idx = 0; ps_list->i4_buf_wr_idx = 0; ret = ih264_list_unlock(ps_list); RETURN_IF((ret != IH264_SUCCESS), ret); return ret; } /** ******************************************************************************* * * @brief * Deinitializes the list context * * @par Description * Deinitializes the list context by calling ih264_list_reset() * and then destrying the mutex created * * @param[in] ps_list * Job Queue context * * @returns IH264_FAIL if lock unlock fails else IH264_SUCCESS * * @remarks * ******************************************************************************* */ IH264_ERROR_T ih264_list_deinit(list_t *ps_list) { WORD32 retval; IH264_ERROR_T ret = IH264_SUCCESS; ret = ih264_list_reset(ps_list); RETURN_IF((ret != IH264_SUCCESS), ret); retval = ithread_mutex_destroy(ps_list->pv_mutex); if(retval) { return IH264_FAIL; } return IH264_SUCCESS; } /** ******************************************************************************* * * @brief * Terminates the list * * @par Description * Terminates the list by setting a flag in context. * * @param[in] ps_list * Job Queue context * * @returns IH264_FAIL if lock unlock fails else IH264_SUCCESS * * @remarks * ******************************************************************************* */ IH264_ERROR_T ih264_list_terminate(list_t *ps_list) { IH264_ERROR_T ret = IH264_SUCCESS; ret = ih264_list_lock(ps_list); RETURN_IF((ret != IH264_SUCCESS), ret); ps_list->i4_terminate = 1; ret = ih264_list_unlock(ps_list); RETURN_IF((ret != IH264_SUCCESS), ret); return ret; } /** ******************************************************************************* * * @brief Adds a buf to the queue * * @par Description * Adds a buf to the queue and updates wr address to next location. * Format/content of the buf structure is abstracted and hence size of the buf * buffer is being passed. * * @param[in] ps_list * Job Queue context * * @param[in] pv_buf * Pointer to the location that contains details of the buf to be added * * @param[in] buf_size * Size of the buf buffer * * @param[in] blocking * To signal if the write is blocking or non-blocking. * * @returns * * @remarks * Job Queue buffer is assumed to be allocated to handle worst case number of bufs * Wrap around is not supported * ******************************************************************************* */ IH264_ERROR_T ih264_list_queue(list_t *ps_list, void *pv_buf, WORD32 blocking) { IH264_ERROR_T ret = IH264_SUCCESS; IH264_ERROR_T rettmp; WORD32 diff; void *pv_buf_wr; volatile WORD32 *pi4_wr_idx, *pi4_rd_idx; WORD32 buf_size = ps_list->i4_entry_size; rettmp = ih264_list_lock(ps_list); RETURN_IF((rettmp != IH264_SUCCESS), rettmp); while(1) { /* Ensure wr idx does not go beyond rd idx by more than number of entries */ pi4_wr_idx = &ps_list->i4_buf_wr_idx; pi4_rd_idx = &ps_list->i4_buf_rd_idx; diff = *pi4_wr_idx - *pi4_rd_idx; if(diff < ps_list->i4_buf_max_idx) { WORD32 wr_idx; wr_idx = ps_list->i4_buf_wr_idx & (ps_list->i4_buf_max_idx - 1); pv_buf_wr = (UWORD8 *)ps_list->pv_buf_base + wr_idx * buf_size; memcpy(pv_buf_wr, pv_buf, buf_size); ps_list->i4_buf_wr_idx++; break; } else { /* wr is ahead, so wait for rd to consume */ if(blocking) { ih264_list_yield(ps_list); } else { ret = IH264_FAIL; break; } } } ps_list->i4_terminate = 0; rettmp = ih264_list_unlock(ps_list); RETURN_IF((rettmp != IH264_SUCCESS), rettmp); return ret; } /** ******************************************************************************* * * @brief Gets next from the Job queue * * @par Description * Gets next buf from the buf queue and updates rd address to next location. * Format/content of the buf structure is abstracted and hence size of the buf * buffer is being passed. If it is a blocking call and if there is no new buf * then this functions unlocks the mutex and calls yield and then locks it back. * and continues till a buf is available or terminate is set * * @param[in] ps_list * Job Queue context * * @param[out] pv_buf * Pointer to the location that contains details of the buf to be written * * @param[in] buf_size * Size of the buf buffer * * @param[in] blocking * To signal if the read is blocking or non-blocking. * * @returns * * @remarks * Job Queue buffer is assumed to be allocated to handle worst case number of bufs * Wrap around is not supported * ******************************************************************************* */ IH264_ERROR_T ih264_list_dequeue(list_t *ps_list, void *pv_buf, WORD32 blocking) { IH264_ERROR_T ret = IH264_SUCCESS; IH264_ERROR_T rettmp; WORD32 buf_size = ps_list->i4_entry_size; WORD32 diff; void *pv_buf_rd; volatile WORD32 *pi4_wr_idx, *pi4_rd_idx; rettmp = ih264_list_lock(ps_list); RETURN_IF((rettmp != IH264_SUCCESS), rettmp); while(1) { /* Ensure wr idx is ahead of rd idx and * wr idx does not go beyond rd idx by more than number of entries */ pi4_wr_idx = &ps_list->i4_buf_wr_idx; pi4_rd_idx = &ps_list->i4_buf_rd_idx; diff = *pi4_wr_idx - *pi4_rd_idx; if(diff > 0) { WORD32 rd_idx; rd_idx = ps_list->i4_buf_rd_idx & (ps_list->i4_buf_max_idx - 1); pv_buf_rd = (UWORD8 *)ps_list->pv_buf_base + rd_idx * buf_size; memcpy(pv_buf, pv_buf_rd, buf_size); ps_list->i4_buf_rd_idx++; break; } else { /* If terminate is signaled then break */ if(ps_list->i4_terminate) { ret = IH264_FAIL; break; } /* wr is ahead, so wait for rd to consume */ if(blocking) { ih264_list_yield(ps_list); } else { ret = IH264_FAIL; break; } } } rettmp = ih264_list_unlock(ps_list); RETURN_IF((rettmp != IH264_SUCCESS), rettmp); return ret; } ================================================ FILE: dependencies/ih264d/common/ih264_list.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_list.h * * @brief * Contains functions for buf queue * * @author * Harish * * @par List of Functions: * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_LIST_H_ #define _IH264_LIST_H_ typedef struct { /** Pointer to buffer base which contains the bufs */ void *pv_buf_base; /** Mutex used to keep the functions thread-safe */ void *pv_mutex; /** Current write index */ volatile WORD32 i4_buf_wr_idx; /** Current read index */ volatile WORD32 i4_buf_rd_idx; /** Maximum index */ WORD32 i4_buf_max_idx; /** Log2(buf_max_idx) - * To ensure number of entries is power of two * This makes it easier to wrap around by using AND with buf_max_idx - 1 * */ WORD32 i4_log2_buf_max_idx; /** Flag to indicate list has to be terminated */ WORD32 i4_terminate; /** Size of each entry */ WORD32 i4_entry_size; /** If the list is to be used frequently send this as zero, else send a large value * to ensure cores are not loaded unnecessarily. * For eg: For picture level queues this can be a large value like 100us * but for jobq this will be zero. */ WORD32 i4_yeild_interval_us; }list_t; WORD32 ih264_list_size(WORD32 num_entries, WORD32 entry_size); void* ih264_list_init(void *pv_buf, WORD32 buf_size, WORD32 num_entries, WORD32 entry_size, WORD32 yeild_interval_us); IH264_ERROR_T ih264_list_free(list_t *ps_list); IH264_ERROR_T ih264_list_reset(list_t *ps_list); IH264_ERROR_T ih264_list_deinit(list_t *ps_list); IH264_ERROR_T ih264_list_terminate(list_t *ps_list); IH264_ERROR_T ih264_list_queue(list_t *ps_list, void *pv_buf, WORD32 blocking); IH264_ERROR_T ih264_list_dequeue(list_t *ps_list, void *pv_buf, WORD32 blocking); #endif /* _IH264_PROCESS_SLICE_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_luma_intra_pred_filters.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_luma_intra_pred_filters.c * * @brief * Contains function definitions for intra prediction filters * * @author * Ittiam * * @par List of Functions: * - ih264_intra_pred_luma_4x4_mode_vert * - ih264_intra_pred_luma_4x4_mode_horz * - ih264_intra_pred_luma_4x4_mode_dc * - ih264_intra_pred_luma_4x4_mode_diag_dl * - ih264_intra_pred_luma_4x4_mode_diag_dr * - ih264_intra_pred_luma_4x4_mode_vert_r * - ih264_intra_pred_luma_4x4_mode_horz_d * - ih264_intra_pred_luma_4x4_mode_vert_l * - ih264_intra_pred_luma_4x4_mode_horz_u * - ih264_intra_pred_luma_8x8_mode_ref_filtering * - ih264_intra_pred_luma_8x8_mode_vert * - ih264_intra_pred_luma_8x8_mode_horz * - ih264_intra_pred_luma_8x8_mode_dc * - ih264_intra_pred_luma_8x8_mode_diag_dl * - ih264_intra_pred_luma_8x8_mode_diag_dr * - ih264_intra_pred_luma_8x8_mode_vert_r * - ih264_intra_pred_luma_8x8_mode_horz_d * - ih264_intra_pred_luma_8x8_mode_vert_l * - ih264_intra_pred_luma_8x8_mode_horz_u * - ih264_intra_pred_luma_16x16_mode_vert * - ih264_intra_pred_luma_16x16_mode_horz * - ih264_intra_pred_luma_16x16_mode_dc * - ih264_intra_pred_luma_16x16_mode_plane * * * @remarks * None * ****************************************************************************** */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include <stdio.h> #include <stddef.h> #include <string.h> /* User include files */ #include "ih264_defs.h" #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_intra_pred_filters.h" /* Global variables used only in assembly files*/ const WORD8 ih264_gai1_intrapred_luma_plane_coeffs[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, }; const WORD8 ih264_gai1_intrapred_luma_8x8_horz_u[] = { 0x06,0x15,0x05,0x14, 0x04,0x13,0x03,0x12, 0x02,0x11,0x01,0x10, 0x00,0x1F,0x0F,0x0F }; /******************* LUMA INTRAPREDICTION *******************/ /******************* 4x4 Modes *******************/ /** ******************************************************************************* * *ih264_intra_pred_luma_4x4_mode_vert * * @brief * Perform Intra prediction for luma_4x4 mode:vertical * * @par Description: * Perform Intra prediction for luma_4x4 mode:vertical ,described in sec 8.3.1.2.1 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_intra_pred_luma_4x4_mode_vert(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK_SIZE + 1; memcpy(pu1_dst, pu1_top, 4); memcpy(pu1_dst + dst_strd, pu1_top, 4); memcpy(pu1_dst + 2 * dst_strd, pu1_top, 4); memcpy(pu1_dst + 3 * dst_strd, pu1_top, 4); } /** ******************************************************************************* * *ih264_intra_pred_luma_4x4_mode_horz * * @brief * Perform Intra prediction for luma_4x4 mode:horizontal * * @par Description: * Perform Intra prediction for luma_4x4 mode:horizontal ,described in sec 8.3.1.2.2 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_intra_pred_luma_4x4_mode_horz(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK_SIZE - 1; memset(pu1_dst, *pu1_left, 4); memset(pu1_dst + dst_strd, *(pu1_left - 1), 4); memset(pu1_dst + 2 * dst_strd, *(pu1_left - 2), 4); memset(pu1_dst + 3 * dst_strd, *(pu1_left - 3), 4); } /** ******************************************************************************* * *ih264_intra_pred_luma_4x4_mode_dc * * @brief * Perform Intra prediction for luma_4x4 mode:DC * * @par Description: * Perform Intra prediction for luma_4x4 mode:DC ,described in sec 8.3.1.2.3 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_4x4_mode_dc(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 u1_useleft; /* availability of left predictors (only for DC) */ UWORD8 u1_usetop; /* availability of top predictors (only for DC) */ UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ WORD32 val = 0; UNUSED(src_strd); UNUSED(ngbr_avail); u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); pu1_top = pu1_src + BLK_SIZE + 1; pu1_left = pu1_src + BLK_SIZE - 1; if(u1_useleft) { val += *pu1_left--; val += *pu1_left--; val += *pu1_left--; val += *pu1_left + 2; } if(u1_usetop) { val += *pu1_top + *(pu1_top + 1) + *(pu1_top + 2) + *(pu1_top + 3) + 2; } /* Since 2 is added if either left/top pred is there, val still being zero implies both preds are not there */ val = (val) ? (val >> (1 + u1_useleft + u1_usetop)) : 128; /* 4 bytes are copied from src to dst */ memset(pu1_dst, val, 4); memset(pu1_dst + dst_strd, val, 4); memset(pu1_dst + 2 * dst_strd, val, 4); memset(pu1_dst + 3 * dst_strd, val, 4); } /** ******************************************************************************* * *ih264_intra_pred_luma_4x4_mode_diag_dl * * @brief * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left * * @par Description: * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left ,described in sec 8.3.1.2.4 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_4x4_mode_diag_dl(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h; UWORD8 predicted_pixels[7]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src +BLK_SIZE + 1; ui4_a = *pu1_top++; ui4_b = *pu1_top++; ui4_c = *pu1_top++; ui4_d = *pu1_top++; ui4_e = *pu1_top++; ui4_f = *pu1_top++; ui4_g = *pu1_top++; ui4_h = *pu1_top; predicted_pixels[0] = FILT121(ui4_a, ui4_b, ui4_c); predicted_pixels[1] = FILT121(ui4_b, ui4_c, ui4_d); predicted_pixels[2] = FILT121(ui4_c, ui4_d, ui4_e); predicted_pixels[3] = FILT121(ui4_d, ui4_e, ui4_f); predicted_pixels[4] = FILT121(ui4_e, ui4_f, ui4_g); predicted_pixels[5] = FILT121(ui4_f, ui4_g, ui4_h); predicted_pixels[6] = FILT121(ui4_g, ui4_h, ui4_h); memcpy(pu1_dst, predicted_pixels, 4); memcpy(pu1_dst + dst_strd, predicted_pixels + 1, 4); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 2, 4); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 3, 4); } /** ******************************************************************************* * *ih264_intra_pred_luma_4x4_mode_diag_dr * * @brief * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right * * @par Description: * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right ,described in sec 8.3.1.2.5 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_4x4_mode_diag_dr(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_topleft = NULL;/* Pointer to top left predictor */ UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_i, ui4_j, ui4_k, ui4_l, ui4_m; UWORD8 predicted_pixels[7]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK_SIZE + 1; pu1_left = pu1_src + BLK_SIZE - 1; pu1_topleft = pu1_src +BLK_SIZE; ui4_a = *pu1_top++; ui4_b = *pu1_top++; ui4_c = *pu1_top++; ui4_d = *pu1_top++; ui4_i = *pu1_left--; ui4_j = *pu1_left--; ui4_k = *pu1_left--; ui4_l = *pu1_left; ui4_m = *pu1_topleft; predicted_pixels[2] = FILT121(ui4_j, ui4_i, ui4_m); predicted_pixels[1] = FILT121(ui4_k, ui4_j, ui4_i); predicted_pixels[0] = FILT121(ui4_l, ui4_k, ui4_j); predicted_pixels[3] = FILT121(ui4_i, ui4_m, ui4_a); predicted_pixels[4] = FILT121(ui4_m, ui4_a, ui4_b); predicted_pixels[5] = FILT121(ui4_a, ui4_b, ui4_c); predicted_pixels[6] = FILT121(ui4_b, ui4_c, ui4_d); memcpy(pu1_dst, predicted_pixels + 3, 4); memcpy(pu1_dst + dst_strd, predicted_pixels + 2, 4); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 1, 4); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels, 4); } /** ******************************************************************************* * *ih264_intra_pred_luma_4x4_mode_vert_r * * @brief * Perform Intra prediction for luma_4x4 mode:Vertical_Right * * @par Description: * Perform Intra prediction for luma_4x4 mode:Vertical_Right ,described in sec 8.3.1.2.6 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_4x4_mode_vert_r(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_i, ui4_j, ui4_k, ui4_m; UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_topleft = NULL;/* Pointer to top left predictor */ UWORD8 predicted_pixels[10]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src +BLK_SIZE + 1; pu1_left = pu1_src + BLK_SIZE - 1; pu1_topleft = pu1_src + BLK_SIZE; ui4_a = *pu1_top++; ui4_b = *pu1_top++; ui4_c = *pu1_top++; ui4_d = *pu1_top++; ui4_i = *pu1_left--; ui4_j = *pu1_left--; ui4_k = *pu1_left; ui4_m = *pu1_topleft; predicted_pixels[6] = FILT11(ui4_m, ui4_a); predicted_pixels[7] = FILT11(ui4_a, ui4_b); predicted_pixels[8] = FILT11(ui4_b, ui4_c); predicted_pixels[9] = FILT11(ui4_c, ui4_d); predicted_pixels[1] = FILT121(ui4_i, ui4_m, ui4_a); predicted_pixels[2] = FILT121(ui4_m, ui4_a, ui4_b); predicted_pixels[3] = FILT121(ui4_a, ui4_b, ui4_c); predicted_pixels[4] = FILT121(ui4_b, ui4_c, ui4_d); predicted_pixels[5] = FILT121(ui4_j, ui4_i, ui4_m); predicted_pixels[0] = FILT121(ui4_k, ui4_j, ui4_i); memcpy(pu1_dst, predicted_pixels + 6, 4); memcpy(pu1_dst + dst_strd, predicted_pixels + 1, 4); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 5, 4); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels, 4); } /* ******************************************************************************* * *ih264_intra_pred_luma_4x4_mode_horz_d * * @brief * Perform Intra prediction for luma_4x4 mode:Horizontal_Down * * @par Description: * Perform Intra prediction for luma_4x4 mode:Horizontal_Down ,described in sec 8.3.1.2.7 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_4x4_mode_horz_d(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_topleft = NULL;/* Pointer to top left predictor */ UWORD32 ui4_a, ui4_b, ui4_c, ui4_i, ui4_j, ui4_k, ui4_l, ui4_m; UWORD8 predicted_pixels[10]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK_SIZE + 1; pu1_left = pu1_src + BLK_SIZE - 1; pu1_topleft = pu1_src + BLK_SIZE; ui4_a = *pu1_top++; ui4_b = *pu1_top++; ui4_c = *pu1_top++; ui4_i = *pu1_left--; ui4_j = *pu1_left--; ui4_k = *pu1_left--; ui4_l = *pu1_left--; ui4_m = *pu1_topleft; predicted_pixels[6] = FILT11(ui4_i, ui4_m); predicted_pixels[7] = FILT121(ui4_i, ui4_m, ui4_a); predicted_pixels[8] = FILT121(ui4_m, ui4_a, ui4_b); predicted_pixels[9] = FILT121(ui4_a, ui4_b, ui4_c); predicted_pixels[1] = FILT121(ui4_l, ui4_k, ui4_j); predicted_pixels[2] = FILT11(ui4_k, ui4_j); predicted_pixels[3] = FILT121(ui4_k, ui4_j, ui4_i); predicted_pixels[4] = FILT11(ui4_j, ui4_i); predicted_pixels[5] = FILT121(ui4_j, ui4_i, ui4_m); predicted_pixels[0] = FILT11(ui4_l, ui4_k); memcpy(pu1_dst, predicted_pixels + 6, 4); memcpy(pu1_dst + dst_strd, predicted_pixels + 4, 4); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 2, 4); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels, 4); } /** ******************************************************************************* * *ih264_intra_pred_luma_4x4_mode_vert_l * * @brief * Perform Intra prediction for luma_4x4 mode:Vertical_Left * * @par Description: * Perform Intra prediction for luma_4x4 mode:Vertical_Left ,described in sec 8.3.1.2.8 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_4x4_mode_vert_l(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g; UWORD8 predicted_pixels[10]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK_SIZE + 1; ui4_a = *pu1_top++; ui4_b = *pu1_top++; ui4_c = *pu1_top++; ui4_d = *pu1_top++; ui4_e = *pu1_top++; ui4_f = *pu1_top++; ui4_g = *pu1_top; predicted_pixels[5] = FILT11(ui4_a, ui4_b); predicted_pixels[6] = FILT11(ui4_b, ui4_c); predicted_pixels[7] = FILT11(ui4_c, ui4_d); predicted_pixels[8] = FILT11(ui4_d, ui4_e); predicted_pixels[0] = FILT121(ui4_a, ui4_b, ui4_c); predicted_pixels[1] = FILT121(ui4_b, ui4_c, ui4_d); predicted_pixels[2] = FILT121(ui4_c, ui4_d, ui4_e); predicted_pixels[3] = FILT121(ui4_d, ui4_e, ui4_f); predicted_pixels[9] = FILT11(ui4_e, ui4_f); predicted_pixels[4] = FILT121(ui4_e, ui4_f, ui4_g); memcpy(pu1_dst, predicted_pixels + 5, 4); memcpy(pu1_dst + dst_strd, predicted_pixels, 4); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 6, 4); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 1, 4); } /** ******************************************************************************* * *ih264_intra_pred_luma_4x4_mode_horz_u * * @brief * Perform Intra prediction for luma_4x4 mode:Horizontal_Up * * @par Description: * Perform Intra prediction for luma_4x4 mode:Horizontal_Up ,described in sec 8.3.1.2.9 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_4x4_mode_horz_u(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD32 ui4_i, ui4_j, ui4_k, ui4_l; UWORD8 predicted_pixels[10]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK_SIZE - 1; ui4_i = *pu1_left--; ui4_j = *pu1_left--; ui4_k = *pu1_left--; ui4_l = *pu1_left--; predicted_pixels[0] = FILT11(ui4_j, ui4_i); predicted_pixels[1] = FILT121(ui4_k, ui4_j, ui4_i); predicted_pixels[2] = FILT11(ui4_k, ui4_j); predicted_pixels[3] = FILT121(ui4_l, ui4_k, ui4_j); predicted_pixels[4] = FILT11(ui4_l, ui4_k); predicted_pixels[5] = FILT121(ui4_l, ui4_l, ui4_k); predicted_pixels[6] = ui4_l; predicted_pixels[7] = ui4_l; predicted_pixels[8] = ui4_l; predicted_pixels[9] = ui4_l; memcpy(pu1_dst, predicted_pixels, 4); memcpy(pu1_dst + dst_strd, predicted_pixels + 2, 4); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 4, 4); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 6, 4); } /******************* 8x8 Modes *******************/ /** ******************************************************************************* * *ih264_intra_pred_luma_8x8_mode_ref_filtering * * @brief * Reference sample filtering process for Intra_8x8 sample prediction * * @par Description: * Perform Reference sample filtering process for Intra_8x8 sample prediction ,described in sec 8.3.2.2.1 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride[Not Used] * * @param[in] dst_strd * integer destination stride[Not Used] * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * * *******************************************************************************/ void ih264_intra_pred_luma_8x8_mode_ref_filtering(UWORD8 *pu1_left, UWORD8 *pu1_topleft, UWORD8 *pu1_top, UWORD8 *pu1_dst, WORD32 left_strd, WORD32 ngbr_avail) { WORD32 top_avail, left_avail, top_left_avail, top_right_avail; left_avail = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); top_avail = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); top_left_avail = BOOLEAN(ngbr_avail & TOP_LEFT_MB_AVAILABLE_MASK); top_right_avail = BOOLEAN(ngbr_avail & TOP_RIGHT_MB_AVAILABLE_MASK); if(top_avail) { WORD32 i; UWORD32 u4_xm1; if(!top_right_avail) { memset(pu1_dst + 8 + 1 + 8, pu1_top[7], 8); top_right_avail = 1; } else { memcpy(pu1_dst + 8 + 1 + 8, pu1_top + 8, 8); } if(top_left_avail) { pu1_dst[8 + 1 + 0] = FILT121((*pu1_topleft), pu1_top[0], pu1_top[1]); } else { pu1_dst[8 + 1] = ((3 * pu1_top[0]) + pu1_top[1] + 2) >> 2; } for(i = 1; i <= 6; i++) { pu1_dst[8 + 1 + i] = FILT121(pu1_top[i - 1], pu1_top[i], pu1_top[i + 1]); } /* First byte of Top Right input is in pu1_dst[8 + 1 + 8]*/ pu1_dst[8 + 1 + 7] = FILT121(pu1_top[6], pu1_top[7], pu1_dst[8 + 1 + 8]); /* filtered output and source in same buf, to prevent output(x - 1) being over written in process */ u4_xm1 = pu1_top[7]; for(i = 8; i <= 14; i++) { UWORD32 u4_x; u4_x = (u4_xm1 + (pu1_dst[8 + 1 + i] << 1) + pu1_dst[8 + 1 + i + 1] + 2) >> 2; /* assigning u4_xm1 from the un-filtered values for the next iteration */ u4_xm1 = pu1_dst[8 + 1 + i]; pu1_dst[8 + 1 + i] = u4_x; } pu1_dst[8 + 1 + 15] = (u4_xm1 + (3 * pu1_dst[8 + 1 + 15]) + 2) >> 2; } /* pu1_topleft is overloaded. It is both: */ /* a. A pointer for the top left pixel */ /* b. An indicator of availability of top left. */ /* If it is null then top left not available */ if(top_left_avail) { if((!top_avail) || (!left_avail)) { if(top_avail) pu1_dst[8] = (3 * pu1_topleft[0] + pu1_top[0] + 2) >> 2; else if(left_avail) pu1_dst[8] = (3 * pu1_topleft[0] + pu1_left[0] + 2) >> 2; } else { pu1_dst[8] = FILT121(pu1_top[0], (*pu1_topleft), pu1_left[0]); } } if(left_avail) { UWORD32 idx; if(0 != pu1_topleft) { pu1_dst[7] = FILT121((*pu1_topleft), pu1_left[0], pu1_left[left_strd]); } else { pu1_dst[7] = ((3 * pu1_left[0]) + pu1_left[left_strd] + 2) >> 2; } for(idx = 1; idx <= 6; idx++) { pu1_dst[7 - idx] = FILT121(pu1_left[(idx - 1) * left_strd], pu1_left[idx * left_strd], pu1_left[(idx + 1) * left_strd]); } pu1_dst[0] = (pu1_left[6 * left_strd] + 3 * pu1_left[7 * left_strd] + 2) >> 2; } } /** ******************************************************************************* * *ih264_intra_pred_luma_8x8_mode_vert * * @brief * Perform Intra prediction for luma_8x8 mode:vertical * * @par Description: * Perform Intra prediction for luma_8x8 mode:vertical ,described in sec 8.3.2.2.2 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_intra_pred_luma_8x8_mode_vert(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK8x8SIZE + 1; memcpy(pu1_dst, pu1_top, 8); memcpy(pu1_dst + dst_strd, pu1_top, 8); memcpy(pu1_dst + 2 * dst_strd, pu1_top, 8); memcpy(pu1_dst + 3 * dst_strd, pu1_top, 8); memcpy(pu1_dst + 4 * dst_strd, pu1_top, 8); memcpy(pu1_dst + 5 * dst_strd, pu1_top, 8); memcpy(pu1_dst + 6 * dst_strd, pu1_top, 8); memcpy(pu1_dst + 7 * dst_strd, pu1_top, 8); } /** ******************************************************************************* * *ih264_intra_pred_luma_8x8_mode_horz * * @brief * Perform Intra prediction for luma_8x8 mode:horizontal * * @par Description: * Perform Intra prediction for luma_8x8 mode:horizontal ,described in sec 8.3.2.2.2 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_intra_pred_luma_8x8_mode_horz(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = pu1_src + BLK8x8SIZE - 1; UNUSED(src_strd); UNUSED(ngbr_avail); memset(pu1_dst, *pu1_left, 8); memset(pu1_dst + dst_strd, *(pu1_left - 1), 8); memset(pu1_dst + 2 * dst_strd, *(pu1_left - 2), 8); memset(pu1_dst + 3 * dst_strd, *(pu1_left - 3), 8); memset(pu1_dst + 4 * dst_strd, *(pu1_left - 4), 8); memset(pu1_dst + 5 * dst_strd, *(pu1_left - 5), 8); memset(pu1_dst + 6 * dst_strd, *(pu1_left - 6), 8); memset(pu1_dst + 7 * dst_strd, *(pu1_left - 7), 8); } /** ******************************************************************************* * *ih264_intra_pred_luma_8x8_mode_dc * * @brief * Perform Intra prediction for luma_8x8 mode:DC * * @par Description: * Perform Intra prediction for luma_8x8 mode:DC ,described in sec 8.3.2.2.4 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_8x8_mode_dc(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 u1_useleft; /* availability of left predictors (only for DC) */ UWORD8 u1_usetop; /* availability of top predictors (only for DC) */ UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ WORD32 row; WORD32 val = 0; UNUSED(src_strd); u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); pu1_top = pu1_src + BLK8x8SIZE + 1; pu1_left = pu1_src + BLK8x8SIZE - 1; if(u1_useleft) { for(row = 0; row < BLK8x8SIZE; row++) val += *(pu1_left - row); val += 4; } if(u1_usetop) { for(row = 0; row < BLK8x8SIZE; row++) val += *(pu1_top + row); val += 4; } /* Since 4 is added if either left/top pred is there, val still being zero implies both preds are not there */ val = (val) ? (val >> (2 + u1_useleft + u1_usetop)) : 128; memset(pu1_dst, val, 8); memset(pu1_dst + dst_strd, val, 8); memset(pu1_dst + 2 * dst_strd, val, 8); memset(pu1_dst + 3 * dst_strd, val, 8); memset(pu1_dst + 4 * dst_strd, val, 8); memset(pu1_dst + 5 * dst_strd, val, 8); memset(pu1_dst + 6 * dst_strd, val, 8); memset(pu1_dst + 7 * dst_strd, val, 8); } /** ******************************************************************************* * *ih264_intra_pred_luma_8x8_mode_diag_dl * * @brief * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left * * @par Description: * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left ,described in sec 8.3.2.2.5 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_8x8_mode_diag_dl(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h; UWORD32 ui4_i, ui4_j, ui4_k, ui4_l, ui4_m, ui4_n, ui4_o, ui4_p; UWORD8 predicted_pixels[15]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK8x8SIZE + 1; ui4_a = *pu1_top++; ui4_b = *pu1_top++; ui4_c = *pu1_top++; ui4_d = *pu1_top++; ui4_e = *pu1_top++; ui4_f = *pu1_top++; ui4_g = *pu1_top++; ui4_h = *pu1_top++; ui4_i = *pu1_top++; ui4_j = *pu1_top++; ui4_k = *pu1_top++; ui4_l = *pu1_top++; ui4_m = *pu1_top++; ui4_n = *pu1_top++; ui4_o = *pu1_top++; ui4_p = *pu1_top; predicted_pixels[0] = FILT121(ui4_a, ui4_b, ui4_c); predicted_pixels[1] = FILT121(ui4_b, ui4_c, ui4_d); predicted_pixels[2] = FILT121(ui4_c, ui4_d, ui4_e); predicted_pixels[3] = FILT121(ui4_d, ui4_e, ui4_f); predicted_pixels[4] = FILT121(ui4_e, ui4_f, ui4_g); predicted_pixels[5] = FILT121(ui4_f, ui4_g, ui4_h); predicted_pixels[6] = FILT121(ui4_g, ui4_h, ui4_i); predicted_pixels[7] = FILT121(ui4_h, ui4_i, ui4_j); predicted_pixels[8] = FILT121(ui4_i, ui4_j, ui4_k); predicted_pixels[9] = FILT121(ui4_j, ui4_k, ui4_l); predicted_pixels[10] = FILT121(ui4_k, ui4_l, ui4_m); predicted_pixels[11] = FILT121(ui4_l, ui4_m, ui4_n); predicted_pixels[12] = FILT121(ui4_m, ui4_n, ui4_o); predicted_pixels[13] = FILT121(ui4_n, ui4_o, ui4_p); predicted_pixels[14] = FILT121(ui4_o, ui4_p, ui4_p); memcpy(pu1_dst, predicted_pixels, 8); memcpy(pu1_dst + dst_strd, predicted_pixels + 1, 8); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 2, 8); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 3, 8); memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 4, 8); memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 5, 8); memcpy(pu1_dst + 6 * dst_strd, predicted_pixels + 6, 8); memcpy(pu1_dst + 7 * dst_strd, predicted_pixels + 7, 8); } /** ******************************************************************************* * *ih264_intra_pred_luma_8x8_mode_diag_dr * * @brief * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right * * @par Description: * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right ,described in sec 8.3.2.2.6 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_8x8_mode_diag_dr(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD8 *pu1_topleft = NULL; /* Pointer to start of top left predictors */ UWORD32 ui4_a; UWORD32 ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h, ui4_i; UWORD32 ui4_j, ui4_k, ui4_l, ui4_m, ui4_n, ui4_o, ui4_p, ui4_q; UWORD8 predicted_pixels[15]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK8x8SIZE + 1; pu1_left = pu1_src + BLK8x8SIZE - 1; pu1_topleft = pu1_src + BLK8x8SIZE; ui4_a = *pu1_topleft; ui4_b = *pu1_top++; ui4_c = *pu1_top++; ui4_d = *pu1_top++; ui4_e = *pu1_top++; ui4_f = *pu1_top++; ui4_g = *pu1_top++; ui4_h = *pu1_top++; ui4_i = *pu1_top; ui4_j = *pu1_left--; ui4_k = *pu1_left--; ui4_l = *pu1_left--; ui4_m = *pu1_left--; ui4_n = *pu1_left--; ui4_o = *pu1_left--; ui4_p = *pu1_left--; ui4_q = *pu1_left; predicted_pixels[6] = FILT121(ui4_a, ui4_j, ui4_k); predicted_pixels[5] = FILT121(ui4_j, ui4_k, ui4_l); predicted_pixels[4] = FILT121(ui4_k, ui4_l, ui4_m); predicted_pixels[3] = FILT121(ui4_l, ui4_m, ui4_n); predicted_pixels[2] = FILT121(ui4_m, ui4_n, ui4_o); predicted_pixels[1] = FILT121(ui4_n, ui4_o, ui4_p); predicted_pixels[0] = FILT121(ui4_o, ui4_p, ui4_q); predicted_pixels[7] = FILT121(ui4_b, ui4_a, ui4_j); predicted_pixels[8] = FILT121(ui4_a, ui4_b, ui4_c); predicted_pixels[9] = FILT121(ui4_b, ui4_c, ui4_d); predicted_pixels[10] = FILT121(ui4_c, ui4_d, ui4_e); predicted_pixels[11] = FILT121(ui4_d, ui4_e, ui4_f); predicted_pixels[12] = FILT121(ui4_e, ui4_f, ui4_g); predicted_pixels[13] = FILT121(ui4_f, ui4_g, ui4_h); predicted_pixels[14] = FILT121(ui4_g, ui4_h, ui4_i); memcpy(pu1_dst, predicted_pixels + 7, 8); memcpy(pu1_dst + dst_strd, predicted_pixels + 6, 8); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 5, 8); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 4, 8); memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 3, 8); memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 2, 8); memcpy(pu1_dst + 6 * dst_strd, predicted_pixels + 1, 8); memcpy(pu1_dst + 7 * dst_strd, predicted_pixels, 8); } /** ******************************************************************************* * *ih264_intra_pred_luma_8x8_mode_vert_r * * @brief * Perform Intra prediction for luma_8x8 mode:Vertical_Right * * @par Description: * Perform Intra prediction for luma_8x8 mode:Vertical_Right ,described in sec 8.3.2.2.7 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_8x8_mode_vert_r(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD8 *pu1_topleft = NULL; /* Pointer to start of top left predictors */ UWORD32 ui4_a; UWORD32 ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h, ui4_i; UWORD32 ui4_j, ui4_k, ui4_l, ui4_m, ui4_n, ui4_o, ui4_p; UWORD8 predicted_pixels[22]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK8x8SIZE + 1; pu1_left = pu1_src + BLK8x8SIZE - 1; pu1_topleft = pu1_src + BLK8x8SIZE; ui4_a = *pu1_topleft; ui4_b = *pu1_top++; ui4_c = *pu1_top++; ui4_d = *pu1_top++; ui4_e = *pu1_top++; ui4_f = *pu1_top++; ui4_g = *pu1_top++; ui4_h = *pu1_top++; ui4_i = *pu1_top; ui4_j = *pu1_left--; ui4_k = *pu1_left--; ui4_l = *pu1_left--; ui4_m = *pu1_left--; ui4_n = *pu1_left--; ui4_o = *pu1_left--; ui4_p = *pu1_left--; predicted_pixels[0] = FILT121(ui4_o, ui4_n, ui4_m); predicted_pixels[1] = FILT121(ui4_m, ui4_l, ui4_k); predicted_pixels[2] = FILT121(ui4_k, ui4_j, ui4_a); predicted_pixels[3] = FILT11(ui4_a, ui4_b); predicted_pixels[4] = FILT11(ui4_b, ui4_c); predicted_pixels[5] = FILT11(ui4_c, ui4_d); predicted_pixels[6] = FILT11(ui4_d, ui4_e); predicted_pixels[7] = FILT11(ui4_e, ui4_f); predicted_pixels[8] = FILT11(ui4_f, ui4_g); predicted_pixels[9] = FILT11(ui4_g, ui4_h); predicted_pixels[10] = FILT11(ui4_h, ui4_i); predicted_pixels[11] = FILT121(ui4_p, ui4_o, ui4_n); predicted_pixels[12] = FILT121(ui4_n, ui4_m, ui4_l); predicted_pixels[13] = FILT121(ui4_l, ui4_k, ui4_j); predicted_pixels[14] = FILT121(ui4_b, ui4_a, ui4_j); predicted_pixels[15] = FILT121(ui4_a, ui4_b, ui4_c); predicted_pixels[16] = FILT121(ui4_b, ui4_c, ui4_d); predicted_pixels[17] = FILT121(ui4_c, ui4_d, ui4_e); predicted_pixels[18] = FILT121(ui4_d, ui4_e, ui4_f); predicted_pixels[19] = FILT121(ui4_e, ui4_f, ui4_g); predicted_pixels[20] = FILT121(ui4_f, ui4_g, ui4_h); predicted_pixels[21] = FILT121(ui4_g, ui4_h, ui4_i); memcpy(pu1_dst, predicted_pixels + 3, 8); memcpy(pu1_dst + 1 * dst_strd, predicted_pixels + 14, 8); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 2, 8); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 13, 8); memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 1, 8); memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 12, 8); memcpy(pu1_dst + 6 * dst_strd, predicted_pixels, 8); memcpy(pu1_dst + 7 * dst_strd, predicted_pixels + 11, 8); } /* ******************************************************************************* * *ih264_intra_pred_luma_8x8_mode_horz_d * * @brief * Perform Intra prediction for luma_8x8 mode:Horizontal_Down * * @par Description: * Perform Intra prediction for luma_8x8 mode:Horizontal_Down ,described in sec 8.3.2.2.8 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_8x8_mode_horz_d(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD8 *pu1_topleft = NULL; /* Pointer to start of top left predictors */ UWORD32 ui4_a; UWORD32 ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h, ui4_i; UWORD32 ui4_j, ui4_k, ui4_l, ui4_m, ui4_n, ui4_o, ui4_p; UWORD8 predicted_pixels[22]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK8x8SIZE + 1; pu1_left = pu1_src + BLK8x8SIZE - 1; pu1_topleft = pu1_src + BLK8x8SIZE; ui4_a = *pu1_topleft; ui4_j = *pu1_top++; ui4_k = *pu1_top++; ui4_l = *pu1_top++; ui4_m = *pu1_top++; ui4_n = *pu1_top++; ui4_o = *pu1_top++; ui4_p = *pu1_top++; ui4_b = *pu1_left--; ui4_c = *pu1_left--; ui4_d = *pu1_left--; ui4_e = *pu1_left--; ui4_f = *pu1_left--; ui4_g = *pu1_left--; ui4_h = *pu1_left--; ui4_i = *pu1_left; predicted_pixels[0] = FILT11(ui4_h, ui4_i); predicted_pixels[1] = FILT121(ui4_g, ui4_h, ui4_i); predicted_pixels[2] = FILT11(ui4_g, ui4_h); predicted_pixels[3] = FILT121(ui4_f, ui4_g, ui4_h); predicted_pixels[4] = FILT11(ui4_f, ui4_g); predicted_pixels[5] = FILT121(ui4_e, ui4_f, ui4_g); predicted_pixels[6] = FILT11(ui4_e, ui4_f); predicted_pixels[7] = FILT121(ui4_d, ui4_e, ui4_f); predicted_pixels[8] = FILT11(ui4_d, ui4_e); predicted_pixels[9] = FILT121(ui4_c, ui4_d, ui4_e); predicted_pixels[10] = FILT11(ui4_c, ui4_d); predicted_pixels[11] = FILT121(ui4_b, ui4_c, ui4_d); predicted_pixels[12] = FILT11(ui4_b, ui4_c); predicted_pixels[13] = FILT121(ui4_a, ui4_b, ui4_c); predicted_pixels[14] = FILT11(ui4_a, ui4_b); predicted_pixels[15] = FILT121(ui4_j, ui4_a, ui4_b); predicted_pixels[16] = FILT121(ui4_k, ui4_j, ui4_a); predicted_pixels[17] = FILT121(ui4_l, ui4_k, ui4_j); predicted_pixels[18] = FILT121(ui4_m, ui4_l, ui4_k); predicted_pixels[19] = FILT121(ui4_n, ui4_m, ui4_l); predicted_pixels[20] = FILT121(ui4_o, ui4_n, ui4_m); predicted_pixels[21] = FILT121(ui4_p, ui4_o, ui4_n); memcpy(pu1_dst, predicted_pixels + 14, 8); memcpy(pu1_dst + dst_strd, predicted_pixels + 12, 8); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 10, 8); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 8, 8); memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 6, 8); memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 4, 8); memcpy(pu1_dst + 6 * dst_strd, predicted_pixels + 2, 8); memcpy(pu1_dst + 7 * dst_strd, predicted_pixels, 8); } /** ******************************************************************************* * *ih264_intra_pred_luma_8x8_mode_vert_l * * @brief * Perform Intra prediction for luma_8x8 mode:Vertical_Left * * @par Description: * Perform Intra prediction for luma_8x8 mode:Vertical_Left ,described in sec 8.3.2.2.9 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_8x8_mode_vert_l(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h; UWORD32 ui4_i, ui4_j, ui4_k, ui4_l, ui4_m; UWORD8 predicted_pixels[22]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK8x8SIZE + 1; ui4_a = *pu1_top++; ui4_b = *pu1_top++; ui4_c = *pu1_top++; ui4_d = *pu1_top++; ui4_e = *pu1_top++; ui4_f = *pu1_top++; ui4_g = *pu1_top++; ui4_h = *pu1_top++; ui4_i = *pu1_top++; ui4_j = *pu1_top++; ui4_k = *pu1_top++; ui4_l = *pu1_top++; ui4_m = *pu1_top++; predicted_pixels[0] = FILT11(ui4_a, ui4_b); predicted_pixels[1] = FILT11(ui4_b, ui4_c); predicted_pixels[2] = FILT11(ui4_c, ui4_d); predicted_pixels[3] = FILT11(ui4_d, ui4_e); predicted_pixels[4] = FILT11(ui4_e, ui4_f); predicted_pixels[5] = FILT11(ui4_f, ui4_g); predicted_pixels[6] = FILT11(ui4_g, ui4_h); predicted_pixels[7] = FILT11(ui4_h, ui4_i); predicted_pixels[8] = FILT11(ui4_i, ui4_j); predicted_pixels[9] = FILT11(ui4_j, ui4_k); predicted_pixels[10] = FILT11(ui4_k, ui4_l); predicted_pixels[11] = FILT121(ui4_a, ui4_b, ui4_c); predicted_pixels[12] = FILT121(ui4_b, ui4_c, ui4_d); predicted_pixels[13] = FILT121(ui4_c, ui4_d, ui4_e); predicted_pixels[14] = FILT121(ui4_d, ui4_e, ui4_f); predicted_pixels[15] = FILT121(ui4_e, ui4_f, ui4_g); predicted_pixels[16] = FILT121(ui4_f, ui4_g, ui4_h); predicted_pixels[17] = FILT121(ui4_g, ui4_h, ui4_i); predicted_pixels[18] = FILT121(ui4_h, ui4_i, ui4_j); predicted_pixels[19] = FILT121(ui4_i, ui4_j, ui4_k); predicted_pixels[20] = FILT121(ui4_j, ui4_k, ui4_l); predicted_pixels[21] = FILT121(ui4_k, ui4_l, ui4_m); memcpy(pu1_dst, predicted_pixels, 8); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 1, 8); memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 2, 8); memcpy(pu1_dst + 6 * dst_strd, predicted_pixels + 3, 8); memcpy(pu1_dst + 1 * dst_strd, predicted_pixels + 11, 8); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 12, 8); memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 13, 8); memcpy(pu1_dst + 7 * dst_strd, predicted_pixels + 14, 8); } /** ******************************************************************************* * *ih264_intra_pred_luma_8x8_mode_horz_u * * @brief * Perform Intra prediction for luma_8x8 mode:Horizontal_Up * * @par Description: * Perform Intra prediction for luma_8x8 mode:Horizontal_Up ,described in sec 8.3.2.2.10 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_8x8_mode_horz_u(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD32 ui4_j, ui4_k, ui4_l, ui4_m, ui4_n, ui4_o, ui4_p, ui4_q; UWORD8 predicted_pixels[22]; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK8x8SIZE - 1; ui4_j = *pu1_left--; ui4_k = *pu1_left--; ui4_l = *pu1_left--; ui4_m = *pu1_left--; ui4_n = *pu1_left--; ui4_o = *pu1_left--; ui4_p = *pu1_left--; ui4_q = *pu1_left; pu1_left = pu1_src + BLK8x8SIZE - 1; predicted_pixels[0] = FILT11(ui4_j, ui4_k); predicted_pixels[1] = FILT121(ui4_j, ui4_k, ui4_l); predicted_pixels[2] = FILT11(ui4_k, ui4_l); predicted_pixels[3] = FILT121(ui4_k, ui4_l, ui4_m); predicted_pixels[4] = FILT11(ui4_l, ui4_m); predicted_pixels[5] = FILT121(ui4_l, ui4_m, ui4_n); predicted_pixels[6] = FILT11(ui4_m, ui4_n); predicted_pixels[7] = FILT121(ui4_m, ui4_n, ui4_o); predicted_pixels[8] = FILT11(ui4_n, ui4_o); predicted_pixels[9] = FILT121(ui4_n, ui4_o, ui4_p); predicted_pixels[10] = FILT11(ui4_o, ui4_p); predicted_pixels[11] = FILT121(ui4_o, ui4_p, ui4_q); predicted_pixels[12] = FILT11(ui4_p, ui4_q); predicted_pixels[13] = FILT121(ui4_p, ui4_q, ui4_q); memset(predicted_pixels+14,ui4_q,8); memcpy(pu1_dst, predicted_pixels, 8); memcpy(pu1_dst + 1 * dst_strd, predicted_pixels + 2, 8); memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 4, 8); memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 6, 8); memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 8, 8); memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 10, 8); memcpy(pu1_dst + 6 * dst_strd, predicted_pixels + 12, 8); memcpy(pu1_dst + 7 * dst_strd, predicted_pixels + 14, 8); } /******************* 16x16 Modes *******************/ /** ******************************************************************************* * *ih264_intra_pred_luma_16x16_mode_vert * * @brief * Perform Intra prediction for luma_16x16 mode:Vertical * * @par Description: * Perform Intra prediction for luma_16x16 mode:Vertical, described in sec 8.3.3.1 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels (Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_16x16_mode_vert(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ WORD32 rows; /* loop variables*/ UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + MB_SIZE + 1; for(rows = 0; rows < 16; rows += 4, pu1_dst += dst_strd) { memcpy(pu1_dst, pu1_top, 16); pu1_dst += dst_strd; memcpy(pu1_dst, pu1_top, 16); pu1_dst += dst_strd; memcpy(pu1_dst, pu1_top, 16); pu1_dst += dst_strd; memcpy(pu1_dst, pu1_top, 16); } } /** ******************************************************************************* * *ih264_intra_pred_luma_16x16_mode_horz * * @brief * Perform Intra prediction for luma_16x16 mode:Horizontal * * @par Description: * Perform Intra prediction for luma_16x16 mode:Horizontal, described in sec 8.3.3.2 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_16x16_mode_horz(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of top predictors */ WORD32 rows; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + MB_SIZE - 1; for(rows = 0; rows < 16; rows += 4, pu1_dst += dst_strd, pu1_left --) { memset(pu1_dst, *pu1_left, 16); /* copy the left value to the entire row*/ pu1_left --; pu1_dst += dst_strd; memset(pu1_dst, *pu1_left, 16); pu1_left --; pu1_dst += dst_strd; memset(pu1_dst, *pu1_left, 16); pu1_left --; pu1_dst += dst_strd; memset(pu1_dst, *pu1_left, 16); } } /** ******************************************************************************* * *ih264_intra_pred_luma_16x16_mode_dc * * @brief * Perform Intra prediction for luma_16x16 mode:DC * * @par Description: * Perform Intra prediction for luma_16x16 mode:DC, described in sec 8.3.3.3 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * ** @param[in] ngbr_avail * availability of neighbouring pixels * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_16x16_mode_dc(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { WORD8 u1_useleft; /* availability of left predictors (only for DC) */ UWORD8 u1_usetop; /* availability of top predictors (only for DC) */ UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ WORD32 rows; /* loop variables*/ WORD32 val = 0; UNUSED(src_strd); u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); pu1_top = pu1_src + MB_SIZE + 1; pu1_left = pu1_src + MB_SIZE - 1; if(u1_useleft) { for(rows = 0; rows < 16; rows++) val += *(pu1_left - rows); val += 8; } if(u1_usetop) { for(rows = 0; rows < 16; rows++) val += *(pu1_top + rows); val += 8; } /* Since 8 is added if either left/top pred is there, val still being zero implies both preds are not there */ val = (val) ? (val >> (3 + u1_useleft + u1_usetop)) : 128; for(rows = 0; rows < 16; rows += 4, pu1_dst += dst_strd) { memset(pu1_dst, val, 16); pu1_dst += dst_strd; memset(pu1_dst, val, 16); pu1_dst += dst_strd; memset(pu1_dst, val, 16); pu1_dst += dst_strd; memset(pu1_dst, val, 16); } } /** ******************************************************************************* * *ih264_intra_pred_luma_16x16_mode_plane * * @brief * Perform Intra prediction for luma_16x16 mode:PLANE * * @par Description: * Perform Intra prediction for luma_16x16 mode:PLANE, described in sec 8.3.3.4 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ void ih264_intra_pred_luma_16x16_mode_plane(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { /*! Written with no multiplications */ UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ UWORD8 *pu1_topleft = NULL; WORD32 a, b, c, tmp; UWORD8 *pu1_tmp1, *pu1_tmp2; WORD32 shift; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + MB_SIZE + 1; pu1_left = pu1_src + MB_SIZE - 1; pu1_topleft = pu1_src + MB_SIZE; { a = (*(pu1_top + 15) + *(pu1_left - 15)) << 4; /*! Implement Sum(x*(P((x+7),-1) - P((x-7),-1))) x=1...8 */ pu1_tmp1 = pu1_top + 8; pu1_tmp2 = pu1_tmp1 - 2; /* Pixel diffs are only 9 bits; so sign extension allows shifts to be used even for signed */ b = ((*pu1_tmp1++) - (*pu1_tmp2--)); /* x=1 */ b += ((*pu1_tmp1++) - (*pu1_tmp2--)) << 1; /* x=2 */ tmp = ((*pu1_tmp1++) - (*pu1_tmp2--)); b += (tmp << 1) + tmp; /* x=3 */ b += ((*pu1_tmp1++) - (*pu1_tmp2--)) << 2; /* x=4 */ tmp = ((*pu1_tmp1++) - (*pu1_tmp2--)); b += (tmp << 2) + tmp; /* x=5 */ tmp = ((*pu1_tmp1++) - (*pu1_tmp2--)); b += (tmp << 2) + (tmp << 1); /* x=6 */ tmp = ((*pu1_tmp1++) - (*pu1_tmp2--)); b += (tmp << 3) - tmp; /* x=7 */ b += ((*pu1_tmp1) - (*pu1_topleft)) << 3; /* x=8 */ b = ((b << 2) + b + 32) >> 6; /*! (5*H + 32)>>6 */ /*! Implement Sum(y*(P(-1,(y+7)) - P(-1,(y-7)))) y=1...8 */ pu1_tmp1 = pu1_left - 8; pu1_tmp2 = pu1_tmp1 + 2; c = ((*pu1_tmp1) - (*pu1_tmp2)); /* y=1 */ pu1_tmp1--; pu1_tmp2++; c += ((*pu1_tmp1) - (*pu1_tmp2)) << 1; /* y=2 */ pu1_tmp1--; pu1_tmp2++; tmp = ((*pu1_tmp1) - (*pu1_tmp2)); c += (tmp << 1) + tmp; /* y=3 */ pu1_tmp1--; pu1_tmp2++; c += ((*pu1_tmp1) - (*pu1_tmp2)) << 2; /* y=4 */ pu1_tmp1--; pu1_tmp2++; tmp = ((*pu1_tmp1) - (*pu1_tmp2)); c += (tmp << 2) + tmp; /* y=5 */ pu1_tmp1--; pu1_tmp2++; tmp = ((*pu1_tmp1) - (*pu1_tmp2)); c += (tmp << 2) + (tmp << 1); /* y=6 */ pu1_tmp1--; pu1_tmp2++; tmp = ((*pu1_tmp1) - (*pu1_tmp2)); c += (tmp << 3) - tmp; /* y=7 */ pu1_tmp1--; //pu1_tmp2 ++; /* Modified to get (-1,-1) location as *(pu1_top - 1) instead of (pu1_left - ui4_stride) */ //c += ((*pu1_tmp1) - (*(pu1_top - 1)))<<3; /* y=8 */ c += ((*pu1_tmp1) - (*pu1_topleft)) << 3; /* y=8 */ c = ((c << 2) + c + 32) >> 6; /*! (5*V + 32)>>32 */ shift = 3; } /*! Now from the plane parameters a, b, and c, compute the fitted plane values over the block */ { WORD32 tmp1, tmpx, tmpx_init, j, i; tmpx_init = -(b << shift); /* -8b */ tmp = a - (c << shift) + 16; /* a-((4or8)*c)+16 */ for(i = 0; i < 16; i++) { tmp += c; /*increment every time by c to get c*(y-7or3)*/ tmpx = tmpx_init; /* Init to -8b */ for(j = 0; j < 16; j++) { tmpx += b; /* increment every time by b to get b*(x-7or3) */ tmp1 = (tmp + tmpx) >> 5; *pu1_dst++ = CLIP_U8(tmp1); } pu1_dst += (dst_strd - 16); } } } ================================================ FILE: dependencies/ih264d/common/ih264_macros.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /********************************************************************************* * @file * ih264_macros.h * * @brief * Macro definitions used in the codec * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_MACROS_H_ #define _IH264_MACROS_H_ /*****************************************************************************/ /* Function Macros */ /*****************************************************************************/ #define RETURN_IF(cond, retval) if(cond) {return (retval);} #define UNUSED(x) ((void)(x)) #define ALIGN128(x) ((((x) + 127) >> 7) << 7) #define ALIGN64(x) ((((x) + 63) >> 6) << 6) #define ALIGN32(x) ((((x) + 31) >> 5) << 5) #define ALIGN16(x) ((((x) + 15) >> 4) << 4) #define ALIGN8(x) ((((x) + 7) >> 3) << 3) #define ALIGN4(x) ((((x) + 3) >> 2) << 2) #define ALIGN2(x) ((((x) + 1) >> 1) << 1) /** ****************************************************************************** * @brief Min, Max ****************************************************************************** */ #define MAX(a,b) ((a > b)?(a):(b)) #define MIN(a,b) ((a < b)?(a):(b)) #define MIN3(a,b,c) ((a) < (b)) ? (((a) < (c)) ? (a) : (c)) : (((b) < (c)) ? (b) : (c)) #define MAX3(a,b,c) ((a) > (b)) ? (((a) > (c)) ? (a) : (c)) : (((b) > (c)) ? (b) : (c)) /** ****************************************************************************** * @brief Div, Mod ****************************************************************************** */ #define MOD(x,y) ((x)%(y)) #define DIV(x,y) ((x)/(y)) /** ****************************************************************************** * @brief Clip ****************************************************************************** */ #define CLIP3(miny, maxy, y) (((y) < (miny))?(miny):(((y) > (maxy))?(maxy):(y))) /** ****************************************************************************** * @brief True, False ****************************************************************************** */ #define BOOLEAN(x) (!!(x)) /** ****************************************************************************** * @brief Frequently used multiplications x2. x3, and x4 ****************************************************************************** */ #define X2(a) ((a) << 1) #define X3(a) (((a) << 1) + (a)) #define X4(a) ((a) << 2) /** ****************************************************************************** * @brief Misc ****************************************************************************** */ #define ABS(x) ((x) < 0 ? (-(x)) : (x)) #define SIGNXY(x,y) (((y) < 0) ? (-1 * (x)) : (x)) #define SIGN(x) (((x) >= 0) ? (((x) > 0) ? 1 : 0) : -1) #define RESET_BIT(x, pos) (x) = (x) & ~(1 << pos); #define SET_BIT(x, pos) (x) = (x) | (1 << pos); #define GET_BIT(x, pos) ((x) >> (pos)) & 0x1 #define INSERT_BIT(x, pos, bit) { RESET_BIT(x, pos); (x) = (x) | (bit << pos); } #endif /*_IH264_MACROS_H_*/ ================================================ FILE: dependencies/ih264d/common/ih264_mem_fns.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_mem_fns.c * * @brief * Functions used for memory operations * * @author * Ittiam * * @par List of Functions: * ih264_memcpy() * ih264_memcpy_mul_8() * ih264_memset() * ih264_memset_mul_8() * ih264_memset_16bit() * ih264_memset_16bit_mul_8() * * @remarks * None * ****************************************************************************** */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include <assert.h> /* User include files */ #include "ih264_typedefs.h" #include "ih264_mem_fns.h" /** ******************************************************************************* * * @brief * memcpy of a 8,16 or 32 bytes * * @par Description: * Does memcpy of 8bit data from source to destination for 8,16 or 32 number of bytes * * @param[in] pu1_dst * UWORD8 pointer to the destination * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] num_bytes * number of bytes to copy * @returns * * @remarks * None * ******************************************************************************* */ void ih264_memcpy(UWORD8 *pu1_dst, UWORD8 *pu1_src, UWORD32 num_bytes) { memcpy(pu1_dst, pu1_src, num_bytes); } void ih264_memcpy_mul_8(UWORD8 *pu1_dst, UWORD8 *pu1_src, UWORD32 num_bytes) { memcpy(pu1_dst, pu1_src, num_bytes); } /** ******************************************************************************* * * @brief * memset of a 8,16 or 32 bytes * * @par Description: * Does memset of 8bit data for 8,16 or 32 number of bytes * * @param[in] pu1_dst * UWORD8 pointer to the destination * * @param[in] value * UWORD8 value used for memset * * @param[in] num_bytes * number of bytes to set * @returns * * @remarks * None * ******************************************************************************* */ void ih264_memset(UWORD8 *pu1_dst, UWORD8 value, UWORD32 num_bytes) { memset(pu1_dst, value, num_bytes); } void ih264_memset_mul_8(UWORD8 *pu1_dst, UWORD8 value, UWORD32 num_bytes) { memset(pu1_dst, value, num_bytes); } /** ******************************************************************************* * * @brief * memset of 16bit data of a 8,16 or 32 bytes * * @par Description: * Does memset of 16bit data for 8,16 or 32 number of bytes * * @param[in] pu2_dst * UWORD8 pointer to the destination * * @param[in] value * UWORD16 value used for memset * * @param[in] num_words * number of words to set * @returns * * @remarks * None * ******************************************************************************* */ void ih264_memset_16bit(UWORD16 *pu2_dst, UWORD16 value, UWORD32 num_words) { UWORD32 i; for(i = 0; i < num_words; i++) { *pu2_dst++ = value; } } void ih264_memset_16bit_mul_8(UWORD16 *pu2_dst, UWORD16 value, UWORD32 num_words) { UWORD32 i; for(i = 0; i < num_words; i++) { *pu2_dst++ = value; } } ================================================ FILE: dependencies/ih264d/common/ih264_mem_fns.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_mem_fns.h * * @brief * Function declarations used for memory functions * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_MEM_FNS_H_ #define _IH264_MEM_FNS_H_ typedef void ih264_memcpy_ft(UWORD8 *pu1_dst, UWORD8 *pu1_src, UWORD32 num_bytes); typedef void ih264_memcpy_mul_8_ft(UWORD8 *pu1_dst, UWORD8 *pu1_src, UWORD32 num_bytes); /** ******************************************************************************* * * @brief * memset of a 8,16 or 32 bytes * * @par Description: * Does memset of 8bit data for 8,16 or 32 number of bytes * * @param[in] pu1_dst * UWORD8 pointer to the destination * * @param[in] value * UWORD8 value used for memset * * @param[in] num_bytes * number of bytes to set * @returns * * @remarks * None * ******************************************************************************* */ typedef void ih264_memset_ft(UWORD8 *pu1_dst, UWORD8 value, UWORD32 num_bytes); typedef void ih264_memset_mul_8_ft(UWORD8 *pu1_dst, UWORD8 value, UWORD32 num_bytes); /** ******************************************************************************* * * @brief * memset of 16bit data of a 8,16 or 32 bytes * * @par Description: * Does memset of 16bit data for 8,16 or 32 number of bytes * * @param[in] pu2_dst * UWORD8 pointer to the destination * * @param[in] value * UWORD16 value used for memset * * @param[in] num_words * number of words to set * @returns * * @remarks * None * ******************************************************************************* */ typedef void ih264_memset_16bit_ft(UWORD16 *pu2_dst, UWORD16 value, UWORD32 num_words); typedef void ih264_memset_16bit_mul_8_ft(UWORD16 *pu2_dst, UWORD16 value, UWORD32 num_words); /* C function declarations */ ih264_memcpy_ft ih264_memcpy; ih264_memcpy_mul_8_ft ih264_memcpy_mul_8; ih264_memset_ft ih264_memset; ih264_memset_mul_8_ft ih264_memset_mul_8; ih264_memset_16bit_ft ih264_memset_16bit; ih264_memset_16bit_mul_8_ft ih264_memset_16bit_mul_8; /* A9 Q function declarations */ ih264_memcpy_ft ih264_memcpy_a9q; ih264_memcpy_mul_8_ft ih264_memcpy_mul_8_a9q; ih264_memset_ft ih264_memset_a9q; ih264_memset_mul_8_ft ih264_memset_mul_8_a9q; ih264_memset_16bit_ft ih264_memset_16bit_a9q; ih264_memset_16bit_mul_8_ft ih264_memset_16bit_mul_8_a9q; /* AV8 function declarations */ ih264_memcpy_ft ih264_memcpy_av8; ih264_memcpy_mul_8_ft ih264_memcpy_mul_8_av8; ih264_memset_ft ih264_memset_av8; ih264_memset_mul_8_ft ih264_memset_mul_8_av8; ih264_memset_16bit_ft ih264_memset_16bit_av8; ih264_memset_16bit_mul_8_ft ih264_memset_16bit_mul_8_av8; ih264_memcpy_mul_8_ft ih264_memcpy_mul_8_ssse3; ih264_memset_mul_8_ft ih264_memset_mul_8_ssse3; ih264_memset_16bit_mul_8_ft ih264_memset_16bit_mul_8_ssse3; #endif //_MEM_FNS_H_ ================================================ FILE: dependencies/ih264d/common/ih264_padding.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_padding.c * * @brief * Contains function definitions for Padding * * @author * Ittiam * * @par List of Functions: * - ih264_pad_top() * - ih264_pad_bottom() * - ih264_pad_left_luma() * - ih264_pad_left_chroma() * - ih264_pad_right_luma() * - ih264_pad_right_chroma() * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include <stddef.h> #include <string.h> /* User include files */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_padding.h" /*****************************************************************************/ /* Function Definitions */ /*****************************************************************************/ /** ******************************************************************************* * * @brief pad at the top of a 2d array * * @par Description: * The top row of a 2d array is replicated for pad_size times at the top * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] src_strd * integer source stride * * @param[in] wd * integer width of the array * * @param[in] pad_size * integer -padding size of the array * * @returns none * * @remarks none * ******************************************************************************* */ void ih264_pad_top(UWORD8 *pu1_src, WORD32 src_strd, WORD32 wd, WORD32 pad_size) { WORD32 row; for(row = 1; row <= pad_size; row++) { memcpy(pu1_src - row * src_strd, pu1_src, wd); } } /** ******************************************************************************* * * @brief pad at the bottom of a 2d array * * @par Description: * The bottom row of a 2d array is replicated for pad_size times at the bottom * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] src_strd * integer source stride * * @param[in] wd * integer width of the array * * @param[in] pad_size * integer -padding size of the array * * @returns none * * @remarks none * ******************************************************************************* */ void ih264_pad_bottom(UWORD8 *pu1_src, WORD32 src_strd, WORD32 wd, WORD32 pad_size) { WORD32 row; for(row = 1; row <= pad_size; row++) { memcpy(pu1_src + (row - 1) * src_strd, pu1_src - 1 * src_strd, wd); } } /** ******************************************************************************* * * @brief pad (luma block) at the left of a 2d array * * @par Description: * The left column of a 2d array is replicated for pad_size times to the left * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] src_strd * integer source stride * * @param[in] ht * integer height of the array * * @param[in] pad_size * integer -padding size of the array * * @returns none * * @remarks none * ******************************************************************************* */ void ih264_pad_left_luma(UWORD8 *pu1_src, WORD32 src_strd, WORD32 ht, WORD32 pad_size) { WORD32 row; for(row = 0; row < ht; row++) { memset(pu1_src - pad_size, *pu1_src, pad_size); pu1_src += src_strd; } } /** ******************************************************************************* * * @brief pad (chroma block) at the left of a 2d array * * @par Description: * The left column of a 2d array is replicated for pad_size times to the left * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] src_strd * integer source stride * * @param[in] ht * integer height of the array * * @param[in] pad_size * integer -padding size of the array * * @returns none * * @remarks none * ******************************************************************************* */ void ih264_pad_left_chroma(UWORD8 *pu1_src, WORD32 src_strd, WORD32 ht, WORD32 pad_size) { /* temp var */ WORD32 row, col; UWORD16 u2_uv_val; /* pointer to src */ UWORD16 *pu2_src = (UWORD16 *)pu1_src; src_strd >>= 1; pad_size >>= 1; for(row = 0; row < ht; row++) { u2_uv_val = pu2_src[0]; for (col = -pad_size; col < 0; col++) { pu2_src[col] = u2_uv_val; } pu2_src += src_strd; } } /** ******************************************************************************* * * @brief pad (luma block) at the right of a 2d array * * @par Description: * The right column of a 2d array is replicated for pad_size times at the right * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] src_strd * integer source stride * * @param[in] ht * integer height of the array * * @param[in] pad_size * integer -padding size of the array * * @returns none * * @remarks none * ******************************************************************************* */ void ih264_pad_right_luma(UWORD8 *pu1_src, WORD32 src_strd, WORD32 ht, WORD32 pad_size) { WORD32 row; for(row = 0; row < ht; row++) { memset(pu1_src, *(pu1_src -1), pad_size); pu1_src += src_strd; } } /** ******************************************************************************* * * @brief pad (chroma block) at the right of a 2d array * * @par Description: * The right column of a 2d array is replicated for pad_size times at the right * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] src_strd * integer source stride * * @param[in] ht * integer height of the array * * @param[in] pad_size * integer -padding size of the array * * @returns none * * @remarks none * ******************************************************************************* */ void ih264_pad_right_chroma(UWORD8 *pu1_src, WORD32 src_strd, WORD32 ht, WORD32 pad_size) { WORD32 row, col; UWORD16 u2_uv_val; UWORD16 *pu2_src = (UWORD16 *)pu1_src; src_strd >>= 1; pad_size >>= 1; for(row = 0; row < ht; row++) { u2_uv_val = pu2_src[-1]; for (col = 0; col < pad_size; col++) { pu2_src[col] = u2_uv_val; } pu2_src += src_strd; } } ================================================ FILE: dependencies/ih264d/common/ih264_padding.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_padding.h * * @brief * Declarations for padding functions * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_PADDING_H_ #define _IH264_PADDING_H_ /*****************************************************************************/ /* Function Declarations */ /*****************************************************************************/ typedef void ih264_pad(UWORD8 *, WORD32, WORD32, WORD32); /* C function declarations */ ih264_pad ih264_pad_top; ih264_pad ih264_pad_bottom; ih264_pad ih264_pad_left_luma; ih264_pad ih264_pad_left_chroma; ih264_pad ih264_pad_right_luma; ih264_pad ih264_pad_right_chroma; /* A9 Q function declarations */ ih264_pad ih264_pad_top_a9q; ih264_pad ih264_pad_left_luma_a9q; ih264_pad ih264_pad_left_chroma_a9q; ih264_pad ih264_pad_right_luma_a9q; ih264_pad ih264_pad_right_chroma_a9q; /* AV8 function declarations */ ih264_pad ih264_pad_top_av8; ih264_pad ih264_pad_left_luma_av8; ih264_pad ih264_pad_left_chroma_av8; ih264_pad ih264_pad_right_luma_av8; ih264_pad ih264_pad_right_chroma_av8; ih264_pad ih264_pad_left_luma_ssse3; ih264_pad ih264_pad_left_chroma_ssse3; ih264_pad ih264_pad_right_luma_ssse3; ih264_pad ih264_pad_right_chroma_ssse3; #endif /*_IH264_PADDING_H_*/ ================================================ FILE: dependencies/ih264d/common/ih264_resi_trans.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_resi_trans.h * * @brief * Functions declarations for residue and forward transform * * @par List of Functions: * - ih264_resi_trans_ft * - ih264_resi_trans_4x4 * - ih264_resi_trans_4x4 * - ih264_resi_trans_4x4_a9 * - ih264_resi_trans_4x4_a9 * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef IH264_RESI_TRANS_H_ #define IH264_RESI_TRANS_H_ /*****************************************************************************/ /* Extern Function Declarations */ /*****************************************************************************/ typedef void ih264_resi_trans_ft(UWORD8 *pu1_src, UWORD8 *pu1_pred, WORD32 *pi4_out, WORD32 src_strd, WORD32 pred_strd, WORD32 out_strd); /*C functions*/ ih264_resi_trans_ft ih264_resi_trans_4x4; ih264_resi_trans_ft ih264_resi_trans_8x8; /*A9 functions*/ ih264_resi_trans_ft ih264_resi_trans_4x4_a9; ih264_resi_trans_ft ih264_resi_trans_8x8_a9; #endif /* IH264_RESI_TRANS_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_resi_trans_quant.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_resi_trans_quant.c * * @brief * Contains function definitions single stage forward transform for H.264 * It will calculate the residue, do the cf and then do quantization * * @author * Ittiam * * @par List of Functions: * - ih264_resi_trans_quant_4x4() * - ih264_resi_trans_quant_chroma_4x4 * - ih264_hadamard_quant_4x4 * - ih264_hadamard_quant_2x2_uv * - ih264_resi_trans_quant_8x8 * * @remarks ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include <stddef.h> /* User include files */ #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_size_defs.h" #include "ih264_macros.h" #include "ih264_trans_macros.h" #include "ih264_trans_data.h" #include "ih264_structs.h" #include "ih264_trans_quant_itrans_iquant.h" /** ******************************************************************************* * * @brief * This function performs forward transform and quantization on a 4*4 block * * @par Description: * The function accepts source buffer and estimation buffer. From these, it * computes the residue. This is residue is then transformed and quantized. * The transform and quantization are in placed computed. They use the residue * buffer for this. * * @param[in] pu1_src * Pointer to source sub-block * * @param[in] pu1_pred * Pointer to prediction sub-block * * @param[in] pi2_out * Pointer to residual sub-block * * @param[in] src_strd * Source stride * * @param[in] pred_strd * Prediction stride * * @param[in] dst_strd * Destination stride * * @param[in] u4_qbits * QP_BITS_h264_4x4 + floor(QP/6) * * @param[in] pu2_threshold_matrix * Pointer to Forward Quant Threshold Matrix * * @param[in] pu2_scale_matrix * Pointer to Forward Quant Scale Matrix * * @param[in] u4_round_factor * Quantization Round factor * * @param[out] pu1_nnz * Total non-zero coefficients in the current sub-block * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_resi_trans_quant_4x4(UWORD8 *pu1_src, UWORD8 *pu1_pred, WORD16 *pi2_out, WORD32 src_strd, WORD32 pred_strd, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor, UWORD8 *pu1_nnz, WORD16 *pi2_alt_dc_addr) { UWORD32 i; WORD32 x0, x1, x2, x3, x4, x5, x6, x7; WORD32 i4_value, i4_sign; UWORD32 u4_abs_value; WORD16 *pi2_out_tmp = pi2_out; UWORD32 u4_nonzero_coeff = 0; for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) { /* computing prediction error (residue) */ x4 = pu1_src[0] - pu1_pred[0]; x5 = pu1_src[1] - pu1_pred[1]; x6 = pu1_src[2] - pu1_pred[2]; x7 = pu1_src[3] - pu1_pred[3]; /* Horizontal transform */ x0 = x4 + x7; x1 = x5 + x6; x2 = x5 - x6; x3 = x4 - x7; pi2_out_tmp[0] = x0 + x1; pi2_out_tmp[1] = (x3 <<1) + x2; pi2_out_tmp[2] = x0 - x1; pi2_out_tmp[3] = x3 - (x2<<1); /* pointing to next row; */ pu1_src += src_strd; pu1_pred += pred_strd; pi2_out_tmp += 4; } pi2_out_tmp = pi2_out; for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) { /* Vertical transform and quantization */ x4 = pi2_out_tmp[0]; x5 = pi2_out_tmp[4]; x6 = pi2_out_tmp[8]; x7 = pi2_out_tmp[12]; x0 = x4 + x7; x1 = x5 + x6; x2 = x5 - x6; x3 = x4 - x7; /* quantization is done in place */ i4_value = x0 + x1; if(i==0) { (*pi2_alt_dc_addr) = i4_value; } FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[0] = i4_value; i4_value = (x3 << 1) + x2; FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[4], pu2_scale_matrix[4], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[4] = i4_value; i4_value = x0 - x1; FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[8], pu2_scale_matrix[8], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[8] = i4_value; i4_value = x3 - (x2 << 1); FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[12], pu2_scale_matrix[12], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[12] = i4_value; pi2_out_tmp ++; pu2_scale_matrix++; pu2_threshold_matrix++; } /* Return total nonzero coefficients in the current sub block */ *pu1_nnz = u4_nonzero_coeff; } /** ******************************************************************************* * * @brief * This function performs forward transform and quantization on a 4*4 chroma block * with interleaved values * * @par Description: * The function accepts source buffer and estimation buffer. From these, it * computes the residue. This is residue is then transformed and quantized. * The transform and quantization are in placed computed. They use the residue * buffer for this. * * @param[in] pu1_src * Pointer to source sub-block * * @param[in] pu1_pred * Pointer to prediction sub-block * * @param[in] pi2_out * Pointer to residual sub-block * * @param[in] src_strd * Source stride * * @param[in] pred_strd * Prediction stride * * @param[in] dst_strd * Destination stride * * @param[in] u4_qbits * QP_BITS_h264_4x4 + floor(QP/6) * * @param[in] pu2_threshold_matrix * Pointer to Forward Quant Threshold Matrix * * @param[in] pu2_scale_matrix * Pointer to Forward Quant Scale Matrix * * @param[in] u4_round_factor * Quantization Round factor * * @param[out] pu1_nnz * Total non-zero coefficients in the current sub-block * * @returns * * @remarks * None * ******************************************************************************* */ void ih264_resi_trans_quant_chroma_4x4(UWORD8 *pu1_src, UWORD8 *pu1_pred, WORD16 *pi2_out, WORD32 src_strd, WORD32 pred_strd, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor, UWORD8 *pu1_nnz, WORD16 *pu1_dc_alt_addr) { UWORD32 i; WORD32 x0, x1, x2, x3, x4, x5, x6, x7; WORD32 i4_value, i4_sign; UWORD32 u4_abs_value; WORD16 *pi2_out_tmp = pi2_out; UWORD32 u4_nonzero_coeff = 0; for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) { /* computing prediction error (residue) */ x4 = pu1_src[0] - pu1_pred[0]; x5 = pu1_src[2] - pu1_pred[2]; x6 = pu1_src[4] - pu1_pred[4]; x7 = pu1_src[6] - pu1_pred[6]; /* Horizontal transform */ x0 = x4 + x7; x1 = x5 + x6; x2 = x5 - x6; x3 = x4 - x7; pi2_out_tmp[0] = x0 + x1; pi2_out_tmp[1] = (x3 <<1) + x2; pi2_out_tmp[2] = x0 - x1; pi2_out_tmp[3] = x3 - (x2<<1); /* pointing to next row; */ pu1_src += src_strd; pu1_pred += pred_strd; pi2_out_tmp += 4; } pi2_out_tmp = pi2_out; for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) { /* Vertical transform and quantization */ x4 = pi2_out_tmp[0]; x5 = pi2_out_tmp[4]; x6 = pi2_out_tmp[8]; x7 = pi2_out_tmp[12]; x0 = x4 + x7; x1 = x5 + x6; x2 = x5 - x6; x3 = x4 - x7; /* quantization is done in place */ i4_value = x0 + x1; if(i==0) { *pu1_dc_alt_addr = i4_value; } FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[0] = i4_value; i4_value = (x3 << 1) + x2; FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[4], pu2_scale_matrix[4], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[4] = i4_value; i4_value = x0 - x1; FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[8], pu2_scale_matrix[8], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[8] = i4_value; i4_value = x3 - (x2 << 1); FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[12], pu2_scale_matrix[12], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[12] = i4_value; pi2_out_tmp ++; pu2_scale_matrix++; pu2_threshold_matrix++; } /* Return total nonzero coefficients in the current sub block */ *pu1_nnz = u4_nonzero_coeff; } /** ******************************************************************************* * * @brief * This function performs forward hadamard transform and quantization on a 4*4 block * * @par Description: * The function accepts source buffer and estimation buffer. From these, it * computes the residue. This is residue is then transformed and quantized. * The transform and quantization are in placed computed. They use the residue * buffer for this. * * @param[in] pu1_src * Pointer to source sub-block * * @param[in] pu1_pred * Pointer to prediction sub-block * * @param[in] pi2_out * Pointer to residual sub-block * * @param[in] src_strd * Source stride * * @param[in] pred_strd * Prediction stride * * @param[in] dst_strd * Destination stride * * @param[in] u4_qbits * QP_BITS_h264_4x4 + floor(QP/6) * * @param[in] pu2_threshold_matrix * Pointer to Forward Quant Threshold Matrix * * @param[in] pu2_scale_matrix * Pointer to Forward Quant Scale Matrix * * @param[in] u4_round_factor * Quantization Round factor * * @param[out] pu1_nnz * Total non-zero coefficients in the current sub-block * * @returns * * @remarks * None * */ void ih264_hadamard_quant_4x4(WORD16 *pi2_src, WORD16 *pi2_dst, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor, UWORD8 *pu1_nnz) { WORD32 i; WORD32 x0,x1,x2,x3,x4,x5,x6,x7,i4_value; UWORD32 u4_abs_value; WORD32 i4_sign; *pu1_nnz = 0; for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) { x4 = pi2_src[0]; x5 = pi2_src[1]; x6 = pi2_src[2]; x7 = pi2_src[3]; x0 = x4 + x7; x1 = x5 + x6; x2 = x5 - x6; x3 = x4 - x7; pi2_dst[0] = x0 + x1; pi2_dst[1] = x3 + x2; pi2_dst[2] = x0 - x1; pi2_dst[3] = x3 - x2; pi2_src += 4; pi2_dst += 4; } /* Vertical transform and quantization */ pi2_dst -= SUB_BLK_WIDTH_4x4<<2; for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) { x4 = pi2_dst[0]; x5 = pi2_dst[4]; x6 = pi2_dst[8]; x7 = pi2_dst[12] ; x0 = x4 + x7; x1 = x5 + x6; x2 = x5 - x6; x3 = x4 - x7; i4_value = (x0 + x1) >> 1; FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[0]); pi2_dst[0] = i4_value; i4_value = (x3 + x2) >> 1; FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[0]); pi2_dst[4] = i4_value; i4_value = (x0 - x1) >> 1; FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[0]); pi2_dst[8] = i4_value; i4_value = (x3 - x2) >> 1; FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[0]); pi2_dst[12] = i4_value; pi2_dst ++; } } /** ******************************************************************************* * * @brief * This function performs forward hadamard transform and quantization on a 2*2 block * for both U and V planes * * @par Description: * The function accepts source buffer and estimation buffer. From these, it * computes the residue. This is residue is then transformed and quantized. * The transform and quantization are in placed computed. They use the residue * buffer for this. * * @param[in] pu1_src * Pointer to source sub-block * * @param[in] pu1_pred * Pointer to prediction sub-block * * @param[in] pi2_out * Pointer to residual sub-block * * @param[in] src_strd * Source stride * * @param[in] pred_strd * Prediction stride * * @param[in] dst_strd * Destination stride * * @param[in] u4_qbits * QP_BITS_h264_4x4 + floor(QP/6) * * @param[in] pu2_threshold_matrix * Pointer to Forward Quant Threshold Matrix * * @param[in] pu2_scale_matrix * Pointer to Forward Quant Scale Matrix * * @param[in] u4_round_factor * Quantization Round factor * * @param[out] pu1_nnz * Total non-zero coefficients in the current sub-block * * @returns * * @remarks * NNZ for dc is populated at 0 and 5th position of pu1_nnz * */ void ih264_hadamard_quant_2x2_uv(WORD16 *pi2_src, WORD16 *pi2_dst, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor, UWORD8 *pu1_nnz) { WORD32 x0, x1, x2, x3, x4, x5, x6, x7; WORD32 i4_value, i4_sign, plane; UWORD32 u4_abs_value; for(plane = 0; plane < 2; plane++) { pu1_nnz[plane] = 0; /* Horizontal transform */ x4 = pi2_src[0]; x5 = pi2_src[1]; x6 = pi2_src[2]; x7 = pi2_src[3]; x0 = x4 + x5; x1 = x4 - x5; x2 = x6 + x7; x3 = x6 - x7; /* Vertical transform and quantization */ i4_value = (x0 + x2); FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[plane]); pi2_dst[0] = i4_value; i4_value = (x0 - x2); FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[plane]); pi2_dst[2] = i4_value; i4_value = (x1 - x3); FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[plane]); pi2_dst[3] = i4_value; i4_value = (x1 + x3); FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[plane]); pi2_dst[1] = i4_value; pi2_dst += 4; pi2_src += 4; } } /* ******************************************************************************* * * @brief * This function performs Single stage forward transform CF8 and quantization on 8*8 blocks * for h.264 * * @par Description: * Performs single stage 8x8 forward transform CF8 after calculating the residue * The result is then quantized * * @param[in] pu1_src * Input 8x8 pixels * * @param[in] pu1_pred * Input 8x8 pixels * * @param[in] pi1_out * Output 8x8 pixels * * @param[in] u4_thresh * Threshold under which the coeffs are not quantized * * @param[in] u4_qp_div * QP/6 * * @param[in] u4_qp_rem * QP%6 * * @param[in] u2_src_stride * Source stride * * @param[in] pred_strd * stride for prediciton buffer * * @param[in] dst_strd * stride for destination buffer * * @param[in] pu4_quant_mat * Pointer to the 4x4 quantization matrix * * @returns Void * * ******************************************************************************* */ void ih264_resi_trans_quant_8x8(UWORD8 *pu1_src, UWORD8 *pu1_pred, WORD16 *pi2_out, WORD32 src_strd, WORD32 pred_strd, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor, UWORD8 *pu1_nnz, WORD16 *pu1_dc_alt_addr) { WORD16 *pi2_out_tmp = pi2_out; UWORD32 i; WORD32 a0, a1, a2, a3, a4, a5, a6, a7; WORD32 r0, r1, r2, r3, r4, r5, r6, r7; WORD32 i4_sign; UWORD32 u4_abs_value; UWORD32 u4_nonzero_coeff = 0; UNUSED(pu1_dc_alt_addr); /*Horizontal transform */ /* we are going to use the a's and r's in a twisted way since */ /*i dont want to declare more variables */ for(i = 0; i < SUB_BLK_WIDTH_8x8; ++i) { r0 = pu1_src[0]; r0 -= pu1_pred[0]; r1 = pu1_src[1]; r1 -= pu1_pred[1]; r2 = pu1_src[2];r2 -= pu1_pred[2]; r3 = pu1_src[3];r3 -= pu1_pred[3]; r4 = pu1_src[4];r4 -= pu1_pred[4]; r5 = pu1_src[5];r5 -= pu1_pred[5]; r6 = pu1_src[6];r6 -= pu1_pred[6]; r7 = pu1_src[7];r7 -= pu1_pred[7]; a0 = r0 + r7; a1 = r1 + r6; a2 = r2 + r5; a3 = r3 + r4; a4 = a0 + a3; a5 = a1 + a2; a6 = a0 - a3; a7 = a1 - a2; pi2_out_tmp[0] = a4 + a5; pi2_out_tmp[2] = a6 + (a7>>1); pi2_out_tmp[4] = a4 - a5; pi2_out_tmp[6] = (a6>>1) - a7; a0 = r0 - r7; a1 = r1 - r6; a2 = r2 - r5; a3 = r3 - r4; a4 = a1 + a2 + ((a0>>1) + a0); a5 = a0 - a3 - ((a2>>1) + a2); a6 = a0 + a3 - ((a1>>1) + a1); a7 = a1 - a2 + ((a3>>1) + a3); pi2_out_tmp[1] = a4 + (a7>>2); pi2_out_tmp[3] = a5 + (a6>>2); pi2_out_tmp[5] = a6 - (a5>>2); pi2_out_tmp[7] = (a4>>2) - a7; pu1_src += src_strd; pu1_pred += pred_strd; pi2_out_tmp += 8; } /*vertical transform and quant */ pi2_out_tmp = pi2_out; for (i = 0; i < SUB_BLK_WIDTH_8x8; ++i) { r0 = pi2_out_tmp[0]; r1 = pi2_out_tmp[8]; r2 = pi2_out_tmp[16]; r3 = pi2_out_tmp[24]; r4 = pi2_out_tmp[32]; r5 = pi2_out_tmp[40]; r6 = pi2_out_tmp[48]; r7 = pi2_out_tmp[56]; a0 = r0 + r7; a1 = r1 + r6; a2 = r2 + r5; a3 = r3 + r4; a4 = a0 + a3; a5 = a1 + a2; a6 = a0 - a3; a7 = a1 - a2; a0 = r0 - r7; a1 = r1 - r6; a2 = r2 - r5; a3 = r3 - r4; r0 = a4 + a5; r2 = a6 + (a7>>1); r4 = a4 - a5; r6 = (a6>>1) - a7; a4 = a1 + a2 + ((a0>>1) + a0); a5 = a0 - a3 - ((a2>>1) + a2); a6 = a0 + a3 - ((a1>>1) + a1); a7 = a1 - a2 + ((a3>>1) + a3); r1 = a4 + (a7>>2); r3 = a5 + (a6>>2); r5 = a6 - (a5>>2); r7 = (a4>>2) - a7; FWD_QUANT(r0, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[0] = r0; FWD_QUANT(r1, u4_abs_value, i4_sign, pu2_threshold_matrix[8], pu2_scale_matrix[8], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[8] = r1; FWD_QUANT(r2, u4_abs_value, i4_sign, pu2_threshold_matrix[16], pu2_scale_matrix[16], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[16] = r2; FWD_QUANT(r3, u4_abs_value, i4_sign, pu2_threshold_matrix[24], pu2_scale_matrix[24], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[24] = r3; FWD_QUANT(r4, u4_abs_value, i4_sign, pu2_threshold_matrix[32], pu2_scale_matrix[32], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[32] = r4; FWD_QUANT(r5, u4_abs_value, i4_sign, pu2_threshold_matrix[40], pu2_scale_matrix[40], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[40] = r5; FWD_QUANT(r6, u4_abs_value, i4_sign, pu2_threshold_matrix[48], pu2_scale_matrix[48], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[48] = r6; FWD_QUANT(r7, u4_abs_value, i4_sign, pu2_threshold_matrix[56], pu2_scale_matrix[56], u4_round_factor, u4_qbits, u4_nonzero_coeff); pi2_out_tmp[56] = r7; pi2_out_tmp++; pu2_scale_matrix++; pu2_threshold_matrix++; } /* Return total nonzero coefficients in the current sub block */ *pu1_nnz = u4_nonzero_coeff; } ================================================ FILE: dependencies/ih264d/common/ih264_size_defs.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_size_defs.h * * @brief * Contains declaration of global variables for H264 transform , quant and inverse quant * * @author * Ittiam * * @remarks * ********************************************************************************/ #ifndef IH264_SIZE_DEFS_H_ #define IH264_SIZE_DEFS_H_ /*****************************************************************************/ /* Constant Macros */ /*****************************************************************************/ /*-----------------------Primary defs--------------------------*/ /*Width of a 4x4 block*/ #define SUB_BLK_WIDTH_4x4 4 /*Width of an 8x8 block*/ #define SUB_BLK_WIDTH_8x8 8 /*Number of chroma blocks in a row of coffs*/ #define SUB_BLK_COUNT_CHROMA_4x4_420 2 /*Number of luma blocks in a row of coffs*/ #define SUB_BLK_COUNT_LUMA_4x4 4 /*Numbr of chroma planes*/ #define NUM_CHROMA_PLANES 2 /*Constant bit shifts*/ #define QP_BITS_h264_4x4 15 #define QP_BITS_h264_8x8 16 /*---------------------------Derived defs------------------------*/ /*Number of coefficients ina 4x4 block*/ #define COFF_CNT_SUB_BLK_4x4 SUB_BLK_WIDTH_4x4*SUB_BLK_WIDTH_4x4; /*Number of luma blocks in a row of coffs*/ #define SUB_BLK_LUMA_4X4_CNT_MB SUB_BLK_COUNT_LUMA_4x4 * SUB_BLK_COUNT_LUMA_4x4 /*Number of chroma coffs in an MB*/ #define SUB_BLK_CHROMA_4X4_CNT_MB SUB_BLK_COUNT_CHROMA_4x4_420 * SUB_BLK_COUNT_CHROMA_4x4_420 #define SUB_BLK_CHROMA_4X4_CNT_MB_BIPLANE SUB_BLK_CHROMA_4X4_CNT_MB*NUM_CHROMA_PLANES /*Size of trans buff = 4x4 for DC block + 4x4 * coffs for 4x4 ac blocks*/ #define SIZE_TRANS_BUFF (SUB_BLK_WIDTH_4x4*SUB_BLK_WIDTH_4x4*+ \ SUB_BLK_WIDTH_4x4*SUB_BLK_WIDTH_4x4* \ SUB_BLK_COUNT_LUMA_4x4*SUB_BLK_COUNT_LUMA_4x4) /*memory size = memory size of 4x4 block of resi coff + 4x4 for DC coff block */ #define SIZE_TMP_BUFF_ITRANS ((SUB_BLK_WIDTH_4x4*SUB_BLK_WIDTH_4x4) +\ (SUB_BLK_WIDTH_4x4*SUB_BLK_WIDTH_4x4)) #endif /* IH264_DEFS_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_structs.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_structs.h * * @brief * Structure definitions used in the code * * @author * Ittiam * * @par List of Functions: * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_STRUCTS_H_ #define _IH264_STRUCTS_H_ /** MB Type info for Intra MBs */ typedef struct { UWORD32 u4_num_mbpart; MBPART_PREDMODE_T e_mbpart_predmode; MBMODES_I16x16 e_intra_predmode; UWORD32 u4_cpb_chroma; UWORD32 u4_cpb_luma; }intra_mbtype_info_t; /** MB Type info for Inter MBs */ typedef struct { UWORD32 u4_num_mbpart; MBPART_PREDMODE_T e_mbpart_predmode_0; MBPART_PREDMODE_T e_mbpart_predmode_1; UWORD32 u4_mbpart_wd; UWORD32 u4_mbpart_ht; }inter_mbtype_info_t; /** Sub MB Type info for Inter MBs */ typedef struct { UWORD32 u4_num_mbpart; MBPART_PREDMODE_T e_mbpart_predmode; UWORD32 u4_mbpart_wd; UWORD32 u4_mbpart_ht; }submbtype_info_t; /** * Picture buffer */ typedef struct { UWORD8* pu1_luma; UWORD8* pu1_chroma; WORD32 i4_abs_poc; WORD32 i4_poc_lsb; /** Lower 32 bit of time stamp */ UWORD32 u4_timestamp_low; /** Upper 32 bit of time stamp */ UWORD32 u4_timestamp_high; WORD32 i4_used_as_ref; /** * frame_num in the slice header */ WORD32 i4_frame_num; /** * Long-term frame idx * TODO: store in frame_num */ WORD32 i4_long_term_frame_idx; /* * 0: Top Field * 1: Bottom Field */ WORD8 i1_field_type; /** * buffer ID from frame buffer manager */ WORD32 i4_buf_id; } pic_buf_t; /** * Reference List */ typedef struct { void *pv_pic_buf; void *pv_mv_buf; } ref_list_t; /** * Motion vector */ typedef struct { /** * Horizontal Motion Vector */ WORD16 i2_mvx; /** * Vertical Motion Vector */ WORD16 i2_mvy; } mv_t; /*****************************************************************************/ /* Following results in packed 48 bit structure. If mv_t included */ /* ref_pic_buf_id, then 8 bits will be wasted for each mv for aligning. */ /* Also using mv_t as elements directly instead of a pointer to l0 and l1 */ /* mvs. Since pointer takes 4 bytes and MV itself is 4 bytes. It does not */ /* really help using pointers. */ /*****************************************************************************/ /** * PU Motion Vector info */ typedef struct { /** * L0 Motion Vector */ mv_t s_l0_mv; /** * L1 Motion Vector */ mv_t s_l1_mv; /** * L0 Ref index */ WORD8 i1_l0_ref_idx; /** * L1 Ref index */ WORD8 i1_l1_ref_idx; /** * L0 Ref Pic Buf ID */ WORD8 i1_l0_ref_pic_buf_id; /** * L1 Ref Pic Buf ID */ WORD8 i1_l1_ref_pic_buf_id; } pu_mv_t; /** * PU information */ typedef struct { /** * Motion Vectors */ pu_mv_t s_mv; /** * PU X position in terms of min PU (4x4) units */ UWORD32 b2_pos_x : 2; /** * PU Y position in terms of min PU (4x4) units */ UWORD32 b2_pos_y : 2; /** * PU width in pixels = (b2_wd + 1) << 2 */ UWORD32 b2_wd : 2; /** * PU height in pixels = (b2_ht + 1) << 2 */ UWORD32 b2_ht : 2; /** * Intra or Inter flag for each partition - 0 or 1 */ UWORD32 b1_intra_flag : 1; /** * PRED_L0, PRED_L1, PRED_BI */ UWORD32 b2_pred_mode : 2; } pu_t; /** * MB information to be stored for entire frame */ typedef struct { /** * Transform sizes 0: 4x4, 1: 8x8, */ UWORD32 b1_trans_size : 1; /** * CBP - 4 bits for Y, 1 for U and 1 for V */ UWORD32 b6_cbp: 6; /** * Intra pred sizes 0: 4x4, 1: 8x8, 2: 16x16 */ UWORD32 b2_intra_pred_size : 2; /** * Flag to signal if the current MB is IPCM */ UWORD32 b1_ipcm : 1; }mb_t; /*****************************************************************************/ /* Info from last TU row of MB is stored in a row level neighbour buffer */ /* , which will be used for Boundary Strength computation */ /*****************************************************************************/ /** * MB neighbor info */ typedef struct { /** * Slice index of the mb */ UWORD16 u2_slice_idx; /*************************************************************************/ /* CBF of bottom TU row (replicated in 4 pixel boundary) */ /* MSB contains CBF of first TU in the last row and LSB contains CBF */ /* of last TU in the last row */ /*************************************************************************/ /** * CBF of bottom TU row */ UWORD16 u2_packed_cbf; /*************************************************************************/ /* QP of bottom TU row (replicated at 8 pixel boundary (Since QP can */ /* not change at less than min CU granularity) */ /*************************************************************************/ /** * QP of bottom TU row */ UWORD8 u1_qp; } mb_top_ny_info_t; /** * MB level context */ typedef struct _mb_ctxt_t { /*************************************************************************/ /* Tile boundary can be detected by looking at tile start x and tile */ /* start y. And based on the tile, slice and frame boundary the */ /* following will be initialized. */ /*************************************************************************/ /** * Pointer to left MB */ /* If not available, this will be set to NULL */ struct _mb_ctxt_t *ps_mb_left; /** * Pointer to top-left MB */ /* If not available, this will be set to NULL */ mb_top_ny_info_t *ps_mb_ny_topleft; /** * Pointer to top MB */ /* If not available, this will be set to NULL */ mb_top_ny_info_t *ps_mb_ny_top; /** * Pointer to top-right MB */ /* If not available, this will be set to NULL */ mb_top_ny_info_t *ps_mb_ny_topright; /*************************************************************************/ /* Pointer to PU data. */ /* This points to a MV Bank stored at frame level. Though this */ /* pointer can be derived by reading offset at frame level, it is */ /* stored here for faster access. Can be removed if storage of MB */ /* structure is critical */ /*************************************************************************/ /** * Pointer to PU data */ pu_t *ps_pu; /*************************************************************************/ /* Pointer to a PU map stored at frame level, */ /* Though this pointer can be derived by multiplying MB address with */ /* number of minTUs in a MB, it is stored here for faster access. */ /* Can be removed if storage of MB structure is critical */ /*************************************************************************/ /** * Pointer to a PU map stored at frame level */ UWORD8 *pu1_pu_map; /** * Number of TUs filled in as_tu */ /*************************************************************************/ /* Having the first entry as 32 bit data, helps in keeping each of */ /* the structures aligned to 32 bits at MB level */ /*************************************************************************/ WORD32 i4_tu_cnt; /** * Pointer to transform coeff data */ /*************************************************************************/ /* Following format is repeated for every coded TU */ /* Luma Block */ /* num_coeffs : 16 bits */ /* zero_cols : 8 bits ( 1 bit per 4 columns) */ /* sig_coeff_map : ((TU Size * TU Size) + 31) >> 5 number of WORD32s */ /* coeff_data : Non zero coefficients */ /* Cb Block (only for last TU in 4x4 case else for every luma TU) */ /* num_coeffs : 16 bits */ /* zero_cols : 8 bits ( 1 bit per 4 columns) */ /* sig_coeff_map : ((TU Size * TU Size) + 31) >> 5 number of WORD32s */ /* coeff_data : Non zero coefficients */ /* Cr Block (only for last TU in 4x4 case else for every luma TU) */ /* num_coeffs : 16 bits */ /* zero_cols : 8 bits ( 1 bit per 4 columns) */ /* sig_coeff_map : ((TU Size * TU Size) + 31) >> 5 number of WORD32s */ /* coeff_data : Non zero coefficients */ /*************************************************************************/ void *pv_coeff_data; /** * Slice to which the MB belongs to */ WORD32 i4_slice_idx; /** * MB column position */ WORD32 i4_pos_x; /** * MB row position */ WORD32 i4_pos_y; /** * Number of PUs filled in ps_pu */ WORD32 i4_pu_cnt; /** * Index of current PU being processed in ps_pu */ /* Scratch variable set to 0 at the start of any PU processing function */ WORD32 i4_pu_idx; /** * Vertical Boundary strength */ /* Two bits per edge. Stored in format. BS[15] | BS[14] | .. |BS[0]*/ UWORD32 *pu4_vert_bs; /** * Horizontal Boundary strength */ /* Two bits per edge. Stored in format. BS[15] | BS[14] | .. |BS[0]*/ UWORD32 *pu4_horz_bs; /** * Qp array stored for each 8x8 pixels */ UWORD8 *pu1_qp; /** * Pointer to current frame's pu_t array */ pu_t *ps_frm_pu; /** * Pointer to current frame's pu_t index array, which stores starting index * of pu_t for every MB */ UWORD32 *pu4_frm_pu_idx; /** * Pointer to current frame's pu map array */ UWORD8 *pu1_frm_pu_map; /*************************************************************************/ /* Need to add encoder specific elements for identifying the order of */ /* coding for CU, TU and PU if any */ /*************************************************************************/ } mb_ctxt_t; /*************************************************************************/ /* The following describes how each of the CU cases are handled */ /*************************************************************************/ /*************************************************************************/ /* For SKIP MB */ /* One Inter PU with appropriate MV */ /* One TU which says CBP is zero and size is 16x16 */ /*************************************************************************/ /*************************************************************************/ /* For Inter MB */ /* M Inter PU with appropriate MVs (M between 1 to 4) */ /* Number of TUs derived based on transform size */ /*************************************************************************/ /*************************************************************************/ /* For Intra MB */ /* Number of TUs derived based on transform size */ /* N Intra Modes are signaled along with coeff data at the start */ /*************************************************************************/ /*************************************************************************/ /* For Intra PCM MB */ /* One TU which says ipcm is 1 */ /*************************************************************************/ /** * Structure to hold quantization parameters of an mb */ typedef struct { /* * mb qp */ UWORD8 u1_mb_qp; /* * mb qp / 6 */ UWORD8 u1_qp_div; /* * mb qp mod 6 */ UWORD8 u1_qp_rem; /* * QP bits */ UWORD8 u1_qbits; /* * forward scale matrix */ const UWORD16 *pu2_scale_mat; /* * threshold matrix for quantization */ UWORD16 *pu2_thres_mat; /* * Threshold to compare the sad with */ UWORD16 *pu2_sad_thrsh; /* * qp dependent rounding constant */ UWORD32 u4_dead_zone; /* * inverse scale matrix */ const UWORD16 *pu2_iscale_mat; /* * Weight matrix in iquant */ UWORD16 *pu2_weigh_mat; }quant_params_t; /** * Structure to hold Profile tier level info for a given layer */ typedef struct { /** * NAL unit type */ WORD8 i1_nal_unit_type; /** * NAL ref idc */ WORD8 i1_nal_ref_idc; } nal_header_t; /** * HRD parameters Info */ typedef struct { /** * Specifies the number of alternative CPB specifications in the * bitstream */ UWORD8 u1_cpb_cnt_minus1; /** * (together with bit_rate_value_minus1) specifies the * maximum input bit rate of the i-th CPB */ UWORD32 u4_bit_rate_scale; /** * (together with cpb_size_du_value_minus1) specifies * CPB size of the i-th CPB when the CPB operates * at the access unit level */ UWORD32 u4_cpb_size_scale; /** * (together with bit_rate_scale) specifies the * maximum input bit rate for the i-th CPB */ UWORD32 au4_bit_rate_value_minus1[32]; /** * together with cpb_size_scale to specify the * CPB size when the CPB operates at the access unit level. */ UWORD32 au4_cpb_size_value_minus1[32]; /** * if 1, specifies that the HSS operates in a constant bit rate (CBR) mode * if 0, specifies that the HSS operates in a intermittent bit rate (CBR) mode */ UWORD8 au1_cbr_flag[32]; /** * specifies the length, in bits for initial cpb delay (nal/vcl)syntax in bp sei */ UWORD8 u1_initial_cpb_removal_delay_length_minus1; /** * specifies the length, in bits for the cpb delay syntax in pt_sei */ UWORD8 u1_cpb_removal_delay_length_minus1; /** * specifies the length, in bits, of the pic_dpb_output_delay syntax element in the pt SEI message */ UWORD8 u1_dpb_output_delay_length_minus1; /** * Specifies length of the time offset parameter */ UWORD8 u1_time_offset_length; }hrd_params_t; /** * Structure to hold VUI parameters Info */ typedef struct { /** * indicates the presence of aspect_ratio */ UWORD8 u1_aspect_ratio_info_present_flag; /** * specifies the aspect ratio of the luma samples */ UWORD8 u1_aspect_ratio_idc; /** * width of the luma samples. user dependent */ UWORD16 u2_sar_width; /** * Height of the luma samples. user dependent */ UWORD16 u2_sar_height; /** * if 1, specifies that the overscan_appropriate_flag is present * if 0, the preferred display method for the video signal is unspecified */ UWORD8 u1_overscan_info_present_flag; /** * if 1,indicates that the cropped decoded pictures output * are suitable for display using overscan */ UWORD8 u1_overscan_appropriate_flag; /** * if 1 specifies that video_format, video_full_range_flag and * colour_description_present_flag are present */ UWORD8 u1_video_signal_type_present_flag; /** * pal, secam, ntsc, ... */ UWORD8 u1_video_format; /** * indicates the black level and range of the luma and chroma signals */ UWORD8 u1_video_full_range_flag; /** * if 1,to 1 specifies that colour_primaries, transfer_characteristics * and matrix_coefficients are present */ UWORD8 u1_colour_description_present_flag; /** * indicates the chromaticity coordinates of the source primaries */ UWORD8 u1_colour_primaries; /** * indicates the opto-electronic transfer characteristic of the source picture */ UWORD8 u1_transfer_characteristics; /** * the matrix coefficients used in deriving luma and chroma signals * from the green, blue, and red primaries */ UWORD8 u1_matrix_coefficients; /** * if 1, specifies that chroma_sample_loc_type_top_field and * chroma_sample_loc_type_bottom_field are present */ UWORD8 u1_chroma_loc_info_present_flag; /** * location of chroma samples */ UWORD8 u1_chroma_sample_loc_type_top_field; UWORD8 u1_chroma_sample_loc_type_bottom_field; /** * Indicates the presence of the * num_units_in_ticks, time_scale flag */ UWORD8 u1_vui_timing_info_present_flag; /** * Number of units that * correspond to one increment of the * clock. Indicates the resolution */ UWORD32 u4_vui_num_units_in_tick; /** * The number of time units that pass in one second */ UWORD32 u4_vui_time_scale; /** * Flag indicating that time difference between two frames is a constant */ UWORD8 u1_fixed_frame_rate_flag; /** * Indicates the presence of NAL HRD parameters */ UWORD8 u1_nal_hrd_parameters_present_flag; /** * NAL level HRD parameters */ hrd_params_t s_nal_hrd_parameters; /** * Indicates the presence of VCL HRD parameters */ UWORD8 u1_vcl_hrd_parameters_present_flag; /** * VCL level HRD parameters */ hrd_params_t s_vcl_hrd_parameters; /** * Specifies the HRD operational mode */ UWORD8 u1_low_delay_hrd_flag; /** * Indicates presence of SEI messages which include pic_struct syntax element */ UWORD8 u1_pic_struct_present_flag; /** * 1, specifies that the following cvs bitstream restriction parameters are present */ UWORD8 u1_bitstream_restriction_flag; /** * if 0, indicates that no pel outside the pic boundaries and * no sub-pels derived using pels outside the pic boundaries is used for inter prediction */ UWORD8 u1_motion_vectors_over_pic_boundaries_flag; /** * Indicates a number of bytes not exceeded by the sum of the sizes of the VCL NAL units * associated with any coded picture */ UWORD8 u1_max_bytes_per_pic_denom; /** * Indicates an upper bound for the number of bits of coding_unit() data */ UWORD8 u1_max_bits_per_mb_denom; /** * Indicate the maximum absolute value of a decoded horizontal MV component * in quarter-pel luma units */ UWORD8 u1_log2_max_mv_length_horizontal; /** * Indicate the maximum absolute value of a decoded vertical MV component * in quarter-pel luma units */ UWORD8 u1_log2_max_mv_length_vertical; /** * Max number of frames that are not synchronized in display and decode order */ UWORD8 u1_num_reorder_frames; /** * specifies required size of the HRD DPB in units of frame buffers. */ UWORD8 u1_max_dec_frame_buffering; } vui_t; /** * Structure to hold SPS info */ typedef struct { /** * profile_idc */ UWORD8 u1_profile_idc; /** constraint_set0_flag */ UWORD8 u1_constraint_set0_flag; /** constraint_set1_flag */ UWORD8 u1_constraint_set1_flag; /** constraint_set2_flag */ UWORD8 u1_constraint_set2_flag; /** constraint_set3_flag */ UWORD8 u1_constraint_set3_flag; /** * level_idc */ UWORD8 u1_level_idc; /** * seq_parameter_set_id */ UWORD8 u1_sps_id; /** * chroma_format_idc */ UWORD8 u1_chroma_format_idc; /** * residual_colour_transform_flag */ WORD8 i1_residual_colour_transform_flag; /** * bit_depth_luma_minus8 */ WORD8 i1_bit_depth_luma; /** * bit_depth_chroma_minus8 */ WORD8 i1_bit_depth_chroma; /** * qpprime_y_zero_transform_bypass_flag */ WORD8 i1_qpprime_y_zero_transform_bypass_flag; /** * seq_scaling_matrix_present_flag */ WORD8 i1_seq_scaling_matrix_present_flag; /** * seq_scaling_list_present_flag */ WORD8 ai1_seq_scaling_list_present_flag[8]; /** * log2_max_frame_num_minus4 */ WORD8 i1_log2_max_frame_num; /** * MaxFrameNum in the standard * 1 << i1_log2_max_frame_num */ WORD32 i4_max_frame_num; /** * pic_order_cnt_type */ WORD8 i1_pic_order_cnt_type; /** * log2_max_pic_order_cnt_lsb_minus4 */ WORD8 i1_log2_max_pic_order_cnt_lsb; /** * MaxPicOrderCntLsb in the standard. * 1 << log2_max_pic_order_cnt_lsb_minus4 */ WORD32 i4_max_pic_order_cnt_lsb; /** * delta_pic_order_always_zero_flag */ WORD8 i1_delta_pic_order_always_zero_flag; /** * offset_for_non_ref_pic */ WORD32 i4_offset_for_non_ref_pic; /** * offset_for_top_to_bottom_field */ WORD32 i4_offset_for_top_to_bottom_field; /** * num_ref_frames_in_pic_order_cnt_cycle */ UWORD8 u1_num_ref_frames_in_pic_order_cnt_cycle; /** * Offset_for_ref_frame */ WORD32 ai4_offset_for_ref_frame[256]; /** * max_num_ref_frames */ UWORD8 u1_max_num_ref_frames; /** * gaps_in_frame_num_value_allowed_flag */ WORD8 i1_gaps_in_frame_num_value_allowed_flag; /** * pic_width_in_mbs_minus1 */ WORD16 i2_pic_width_in_mbs_minus1; /** * pic_height_in_map_units_minus1 */ WORD16 i2_pic_height_in_map_units_minus1; /** * frame_mbs_only_flag */ WORD8 i1_frame_mbs_only_flag; /** * mb_adaptive_frame_field_flag */ WORD8 i1_mb_adaptive_frame_field_flag; /** * direct_8x8_inference_flag */ WORD8 i1_direct_8x8_inference_flag; /** * frame_cropping_flag */ WORD8 i1_frame_cropping_flag; /** * frame_crop_left_offset */ WORD16 i2_frame_crop_left_offset; /** * frame_crop_right_offset */ WORD16 i2_frame_crop_right_offset; /** * frame_crop_top_offset */ WORD16 i2_frame_crop_top_offset; /** * frame_crop_bottom_offset */ WORD16 i2_frame_crop_bottom_offset; /** * vui_parameters_present_flag */ WORD8 i1_vui_parameters_present_flag; /** * vui_parameters_Structure_info */ vui_t s_vui_parameters; /** * Flag to give status of SPS structure */ WORD8 i1_sps_valid; /** * Coded Picture width */ WORD32 i2_pic_wd; /** * Coded Picture height */ WORD32 i2_pic_ht; /** * Picture width in MB units */ WORD16 i2_pic_wd_in_mb; /** * Picture height in MB units */ WORD16 i2_pic_ht_in_mb; /** * useDefaultScalingMatrixFlag */ WORD8 ai1_use_default_scaling_matrix_flag[8]; /** * 4x4 Scaling lists after inverse zig zag scan */ UWORD16 au2_4x4_weight_scale[6][16]; /** * 4x4 Scaling lists after inverse zig zag scan */ UWORD16 au2_8x8_weight_scale[2][64]; } sps_t; /** * Structure to hold PPS info */ typedef struct { /** * pic_parameter_set_id */ UWORD8 u1_pps_id; /** * seq_parameter_set_id */ UWORD8 u1_sps_id; /** * Entropy coding : 0-VLC; 1 - CABAC */ UWORD8 u1_entropy_coding_mode_flag; /* * Pic order present flag */ UWORD8 u1_pic_order_present_flag; /* * Number of slice groups */ UWORD8 u1_num_slice_groups; /* * Slice group map type */ UWORD8 u1_slice_group_map_type; /* * Maximum reference picture index in the reference list 0 : range [0 - 31] */ WORD8 i1_num_ref_idx_l0_default_active; /* * Maximum reference picture index in the reference list 1 : range [0 - 31] */ WORD8 i1_num_ref_idx_l1_default_active; /** * weighted_pred_flag */ WORD8 i1_weighted_pred_flag; /** * weighted_bipred_flag */ WORD8 i1_weighted_bipred_idc; /** * pic_init_qp_minus26 */ WORD8 i1_pic_init_qp; /** * pic_init_qs_minus26 */ WORD8 i1_pic_init_qs; /* * Chroma QP offset w.r.t QPY {-12,12} */ WORD8 i1_chroma_qp_index_offset; /** * deblocking_filter_control_present_flag */ WORD8 i1_deblocking_filter_control_present_flag; /** * constrained_intra_pred_flag */ WORD8 i1_constrained_intra_pred_flag; /** * redundant_pic_cnt_present_flag */ WORD8 i1_redundant_pic_cnt_present_flag; /** * transform_8x8_mode_flag */ WORD8 i1_transform_8x8_mode_flag; /** * pic_scaling_matrix_present_flag */ WORD8 i1_pic_scaling_matrix_present_flag; /* * Second chroma QP offset */ WORD8 i1_second_chroma_qp_index_offset; /** * useDefaultScalingMatrixFlag */ WORD8 ai1_use_default_scaling_matrix_flag[8]; /** * 4x4 Scaling lists after inverse zig zag scan */ UWORD16 au2_4x4_weight_scale[6][16]; /** * 4x4 Scaling lists after inverse zig zag scan */ UWORD16 au2_8x8_weight_scale[2][64]; /** * pic_scaling_list_present_flag */ WORD8 ai1_pic_scaling_list_present_flag[8]; /** * Flag to give status of PPS structure */ WORD8 i1_pps_valid; } pps_t; /** * MMCO commands and params. */ typedef struct { /* memory management control operation command */ UWORD8 u1_memory_management_control_operation; /* * Contains difference of pic nums of short-term pic/frame * 1. To signal it as "unused for reference" if mmco = 1 * 2. To signal it as "used for long-term reference" if mmco = 3 */ UWORD32 u4_difference_of_pic_nums_minus1; /* Long-term pic num to be set as "unused for reference" */ UWORD8 u1_long_term_pic_num; /* * Assign a long-term idx to a picture as follows * 1. Assign to a short-term pic if mmco = 3 * 2. Assign to the current pic if mmco = 6 */ UWORD8 u1_long_term_frame_idx; /* * The max long-term idx. The long-term pics having idx above * are set as "unused for reference */ UWORD8 u1_max_long_term_frame_idx_plus1; }mmco_prms_t; /** * Structure to hold Reference picture list modification info */ typedef struct { /* ref_pic_list_modification_flag_l0 */ WORD8 i1_ref_pic_list_modification_flag_l0; /* Modification required in list0 */ WORD8 i1_modification_of_pic_nums_idc_l0[MAX_MODICATION_IDC]; /* * The absolute difference between the picture number of * the picture being moved to the current index in * list0 and the picture number prediction value */ UWORD32 u4_abs_diff_pic_num_minus1_l0[MAX_MODICATION_IDC]; /* * The long-term picture number of the picture being moved * to the current index in list0 */ UWORD8 u1_long_term_pic_num_l0[MAX_MODICATION_IDC]; /* ref_pic_list_modification_flag_l1 */ WORD8 i1_ref_pic_list_modification_flag_l1; /* Modification required in list1 */ WORD8 i1_modification_of_pic_nums_idc_l1[MAX_MODICATION_IDC]; /* * The absolute difference between the picture number of * the picture being moved to the current index in * list1 and the picture number prediction value */ UWORD32 u4_abs_diff_pic_num_minus1_l1[MAX_MODICATION_IDC]; /* * The long-term picture number of the picture being moved * to the current index in list1 */ UWORD8 u1_long_term_pic_num_l1[MAX_MODICATION_IDC]; }rplm_t; /** * Structure to hold Slice Header info */ typedef struct { /* * nal_unit_type */ WORD8 i1_nal_unit_type; /* * nal_unit_idc */ WORD8 i1_nal_unit_idc; /* * first_mb_in_slice */ UWORD16 u2_first_mb_in_slice; /* * slice_type */ UWORD8 u1_slice_type; /* * pic_parameter_set_id */ UWORD8 u1_pps_id; /* * frame_num */ WORD32 i4_frame_num; /* * field_pic_flag */ WORD8 i1_field_pic_flag; /* * bottom_field_flag */ WORD8 i1_bottom_field_flag; /* * second_field */ WORD8 i1_second_field_flag; /* * idr_pic_id */ UWORD16 u2_idr_pic_id ; /* * pic_order_cnt_lsb */ UWORD16 i4_pic_order_cnt_lsb; /* * delta_pic_order_cnt_bottom */ WORD32 i4_delta_pic_order_cnt_bottom; /* * delta_pic_order_cnt */ WORD32 ai4_delta_pic_order_cnt[2]; /* * redundant_pic_cnt */ UWORD8 u1_redundant_pic_cnt; /* * direct_spatial_mv_pred_flag */ UWORD8 u1_direct_spatial_mv_pred_flag; /* * num_ref_idx_active_override_flag */ UWORD8 u1_num_ref_idx_active_override_flag; /* * num_ref_idx_l0_active */ WORD8 i1_num_ref_idx_l0_active; /* * num_ref_idx_l1_active_minus1 */ WORD8 i1_num_ref_idx_l1_active; /* * ref_pic_list_reordering_flag_l0 */ UWORD8 u1_ref_idx_reordering_flag_l0; /* * ref_pic_list_reordering_flag_l1 */ UWORD8 u1_ref_idx_reordering_flag_l1; /** * Reference prediction list modification */ rplm_t s_rplm; /** * L0 Reference pic lists */ ref_list_t as_ref_pic_list0[MAX_DPB_SIZE]; /** * L1 Reference pic lists */ ref_list_t as_ref_pic_list1[MAX_DPB_SIZE]; /* * no_output_of_prior_pics_flag */ UWORD8 u1_no_output_of_prior_pics_flag; /* * long_term_reference_flag */ UWORD8 u1_long_term_reference_flag; /* * adaptive_ref_pic_marking_mode_flag */ UWORD8 u1_adaptive_ref_pic_marking_mode_flag; /* * Array to structures to store mmco commands * and parameters. */ mmco_prms_t as_mmco_prms[MAX_MMCO_COMMANDS]; /* * entropy_coding_mode_flag */ WORD8 u1_entropy_coding_mode_flag; /* * cabac_init_idc */ WORD8 i1_cabac_init_idc; /* * i1_slice_qp */ WORD8 i1_slice_qp; /* * sp_for_switch_flag */ UWORD8 u1_sp_for_switch_flag; /* * slice_qs_delta */ UWORD8 u1_slice_qs; /* * disable_deblocking_filter_idc */ WORD8 u1_disable_deblocking_filter_idc; /* * slice_alpha_c0_offset_div2 */ WORD8 i1_slice_alpha_c0_offset_div2; /* * slice_beta_offset_div2 */ WORD8 i1_slice_beta_offset_div2; /* * num_slice_groups_minus1 */ WORD8 u1_num_slice_groups_minus1; /* * slice_group_change_cycle */ WORD8 u1_slice_group_change_cycle; /** * Start MB X */ UWORD16 i2_mb_x; /** * Start MB Y */ UWORD16 i2_mb_y; /** * Absolute POC. Contains minimum of top and bottom POC. */ WORD32 i4_abs_pic_order_cnt; /** * Absolute top POC. Contains top poc for frame or top * field. Invalid for bottom field. */ WORD32 i4_abs_top_pic_order_cnt; /** * Absolute top POC. Contains bottom poc for frame or bottom * field. Invalid for top field. */ WORD32 i4_abs_bottom_pic_order_cnt; /** Flag signaling if the current slice is ref slice */ UWORD8 i1_nal_ref_idc; /** Flag to indicate if the current slice is MBAFF Frame */ UWORD8 u1_mbaff_frame_flag; /** luma_log2_weight_denom */ UWORD8 u1_luma_log2_weight_denom; /** chroma_log2_weight_denom */ UWORD8 u1_chroma_log2_weight_denom; /** luma_weight_l0_flag */ UWORD8 au1_luma_weight_l0_flag[MAX_DPB_SIZE]; /** luma_weight_l0 : (-128, 127 )is the range of weights * when weighted pred is enabled, 128 is default value */ WORD16 ai2_luma_weight_l0[MAX_DPB_SIZE]; /** luma_offset_l0 : (-128, 127 )is the range of offset * when weighted pred is enabled, 0 is default value */ WORD8 ai1_luma_offset_l0[MAX_DPB_SIZE]; /** chroma_weight_l0_flag */ UWORD8 au1_chroma_weight_l0_flag[MAX_DPB_SIZE]; /** chroma_weight_l0 : (-128, 127 )is the range of weights * when weighted pred is enabled, 128 is default value*/ WORD16 ai2_chroma_weight_l0[MAX_DPB_SIZE][2]; /** chroma_offset_l0 : (-128, 127 )is the range of offset * when weighted pred is enabled, 0 is default value*/ WORD8 ai1_chroma_offset_l0[MAX_DPB_SIZE][2]; /** luma_weight_l0_flag */ UWORD8 au1_luma_weight_l1_flag[MAX_DPB_SIZE]; /** luma_weight_l1 : (-128, 127 )is the range of weights * when weighted pred is enabled, 128 is default value */ WORD16 ai2_luma_weight_l1[MAX_DPB_SIZE]; /** luma_offset_l1 : (-128, 127 )is the range of offset * when weighted pred is enabled, 0 is default value */ WORD8 ai1_luma_offset_l1[MAX_DPB_SIZE]; /** chroma_weight_l1_flag */ UWORD8 au1_chroma_weight_l1_flag[MAX_DPB_SIZE]; /** chroma_weight_l1 : (-128, 127 )is the range of weights * when weighted pred is enabled, 128 is default value */ WORD16 ai2_chroma_weight_l1[MAX_DPB_SIZE][2]; /** chroma_offset_l1 :(-128, 127 )is the range of offset * when weighted pred is enabled, 0 is default value */ WORD8 ai1_chroma_offset_l1[MAX_DPB_SIZE][2]; }slice_header_t; /*****************************************************************************/ /* The following can be used to type cast coefficient data that is stored */ /* per subblock. Note that though i2_level is shown as an array that */ /* holds 16 coefficients, only the first few entries will be valid. Next */ /* subblocks data starts after the valid number of coefficients. Number */ /* of non-zero coefficients will be derived using number of non-zero bits */ /* in sig coeff map */ /*****************************************************************************/ /** * Structure to hold coefficient info for a 2x2 chroma DC transform */ typedef struct { /** * significant coefficient map */ UWORD8 u1_sig_coeff_map; /** * sub block position */ UWORD8 u1_subblk_pos; /** * holds coefficients */ WORD16 ai2_level[2 * 2]; }tu_sblk2x2_coeff_data_t; /** * Structure to hold coefficient info for a 4x4 transform */ typedef struct { /** * significant coefficient map */ UWORD16 u2_sig_coeff_map; /** * sub block position */ UWORD16 u2_subblk_pos; /** * holds coefficients */ WORD16 ai2_level[SUBBLK_COEFF_CNT]; }tu_sblk4x4_coeff_data_t; /** * Structure to hold coefficient info for a 8x8 transform */ typedef struct { /** * significant coefficient map */ UWORD32 au4_sig_coeff_map[2]; /** * sub block position */ UWORD16 u2_subblk_pos; /** * holds coefficients */ WORD16 ai2_level[TRANS_SIZE_8 * TRANS_SIZE_8]; }tu_blk8x8_coeff_data_t; /** * Structure to hold coefficient info for a 16x16 IPCM MB */ typedef struct { /** * holds coefficients */ UWORD8 au1_level[MB_SIZE * MB_SIZE * 3 / 2]; }tu_ipcm_coeff_data_t; typedef struct { /** * Transform sizes 0: 4x4, 1: 8x8, */ UWORD32 b1_trans_size : 1; /** * Flag to signal if the current MB is IPCM */ UWORD32 b1_ipcm : 1; /** * Intra pred sizes 0: 4x4, 1: 8x8, 2: 16x16 */ UWORD32 b2_intra_pred_size : 2; /** * Chroma intra mode */ UWORD32 b2_intra_chroma_pred_mode: 2; /** * Number of coded subblocks in the current MB, for which * tu data is sent. Maximum of 27 subblocks in the following * order. * 1 4x4 luma DC(for intra16x16), * 16 4x4 luma, * 2 2x2 chroma DC, * 8 4x4 chroma, */ WORD32 b5_num_coded_sblks: 5; /** * Flag to signal if 4x4 subblock for DC values (in INTRA 16x16 MB) * is coded */ UWORD32 b1_luma_dc_coded: 1; /** * Flag to signal if 4x4 subblock for DC values (in INTRA 16x16 MB) * is coded */ UWORD32 b1_chroma_dc_coded: 1; /** * CSBP - 16 bits, 1 bit for each 4x4 * for intra16x16 mb_type only ac coefficients are */ UWORD32 b16_luma_csbp: 16; /** * CSBP - 16 bits, 1 bit for each 4x4 * for intra16x16 mb_type only ac coefficients are */ UWORD32 b8_chroma_csbp: 8; /** * Luma Intra pred modes, * Based on intra pred size either 16, 4 or 1 entry will be * populated below. */ UWORD8 au1_luma_intra_modes[16]; }intra_mb_t; typedef struct { /** * Transform sizes 0: 4x4, 1: 8x8, */ UWORD8 b1_trans_size : 1; /** * Skip flag */ UWORD8 b1_skip : 1; /** * Number of coded subblocks in the current MB, for which * tu data is sent. Maximum of 26 subblocks in the following * order. * 16 4x4 luma, * 2 2x2 chroma DC, * 8 4x4 chroma, */ WORD32 b5_num_coded_sblks: 5; /** * CSBP - 16 bits, 1 bit for each 4x4 * for intra16x16 mb_type only ac coefficients are */ UWORD32 b16_luma_csbp: 16; /** * CSBP - 16 bits, 1 bit for each 4x4 * for intra16x16 mb_type only ac coefficients are */ UWORD32 b16_chroma_csbp: 8; }inter_mb_t; /** * Structure to hold Mastering Display Color Volume SEI */ typedef struct { /** * Array to store the display_primaries_x values */ UWORD16 au2_display_primaries_x[NUM_SEI_MDCV_PRIMARIES]; /** * Array to store the display_primaries_y values */ UWORD16 au2_display_primaries_y[NUM_SEI_MDCV_PRIMARIES]; /** * Variable to store the white point x value */ UWORD16 u2_white_point_x; /** * Variable to store the white point y value */ UWORD16 u2_white_point_y; /** * Variable to store the max display mastering luminance value */ UWORD32 u4_max_display_mastering_luminance; /** * Variable to store the min display mastering luminance value */ UWORD32 u4_min_display_mastering_luminance; }sei_mdcv_params_t; /** * Structure for Content Light Level Info * */ typedef struct { /** * The maximum pixel intensity of all samples */ UWORD16 u2_max_content_light_level; /** * The average pixel intensity of all samples */ UWORD16 u2_max_pic_average_light_level; }sei_cll_params_t; /** * Structure to hold Ambient viewing environment SEI */ typedef struct { /** * specifies the environmental illluminance of the ambient viewing environment */ UWORD32 u4_ambient_illuminance; /* * specify the normalized x chromaticity coordinates of the * environmental ambient light in the nominal viewing environment */ UWORD16 u2_ambient_light_x; /* * specify the normalized y chromaticity coordinates of the * environmental ambient light in the nominal viewing environment */ UWORD16 u2_ambient_light_y; }sei_ave_params_t; /** * Structure to hold Content color volume SEI */ typedef struct { /* * Flag used to control persistence of CCV SEI messages */ UWORD8 u1_ccv_cancel_flag; /* * specifies the persistence of the CCV SEI message for the current layer */ UWORD8 u1_ccv_persistence_flag; /* * specifies the presence of syntax elements ccv_primaries_x and ccv_primaries_y */ UWORD8 u1_ccv_primaries_present_flag; /* * specifies that the syntax element ccv_min_luminance_value is present */ UWORD8 u1_ccv_min_luminance_value_present_flag; /* * specifies that the syntax element ccv_max_luminance_value is present */ UWORD8 u1_ccv_max_luminance_value_present_flag; /* * specifies that the syntax element ccv_avg_luminance_value is present */ UWORD8 u1_ccv_avg_luminance_value_present_flag; /* * shall be equal to 0 in bitstreams conforming to this version. Other values * for reserved_zero_2bits are reserved for future use */ UWORD8 u1_ccv_reserved_zero_2bits; /* * specify the normalized x chromaticity coordinates of the colour * primary component c of the nominal content colour volume */ WORD32 ai4_ccv_primaries_x[NUM_SEI_CCV_PRIMARIES]; /* * specify the normalized y chromaticity coordinates of the colour * primary component c of the nominal content colour volume */ WORD32 ai4_ccv_primaries_y[NUM_SEI_CCV_PRIMARIES]; /* * specifies the normalized minimum luminance value */ UWORD32 u4_ccv_min_luminance_value; /* * specifies the normalized maximum luminance value */ UWORD32 u4_ccv_max_luminance_value; /* * specifies the normalized average luminance value */ UWORD32 u4_ccv_avg_luminance_value; }sei_ccv_params_t; /** * Structure to hold SEI parameters Info */ typedef struct { /** * mastering display color volume info present flag */ UWORD8 u1_sei_mdcv_params_present_flag; /* * MDCV parameters */ sei_mdcv_params_t s_sei_mdcv_params; /** * content light level info present flag */ UWORD8 u1_sei_cll_params_present_flag; /* * CLL parameters */ sei_cll_params_t s_sei_cll_params; /** * ambient viewing environment info present flag */ UWORD8 u1_sei_ave_params_present_flag; /* * AVE parameters */ sei_ave_params_t s_sei_ave_params; /** * content color volume info present flag */ UWORD8 u1_sei_ccv_params_present_flag; /* * CCV parameters */ sei_ccv_params_t s_sei_ccv_params; } sei_params_t; #endif /* _IH264_STRUCTS_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_trans_data.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_trans_data.c * * @brief * Contains definition of global variables for H264 encoder * * @author * Ittiam * * @remarks * ******************************************************************************* */ #include "ih264_typedefs.h" #include "ih264_trans_data.h" /*****************************************************************************/ /* Extern global definitions */ /*****************************************************************************/ /* * Since we don't have a division operation in neon * we will multiply by LCM of 16,6,10 and scale accordingly * so care that to get the actual transform you need to divide by LCM * LCM = 240 */ const UWORD16 g_scal_coff_h264_4x4[16] ={ 15,40,40,40, 40,24,40,24, 15,40,40,15, 40,24,40,24}; const UWORD16 g_scal_coff_h264_8x8[16]= { 16, 15, 20, 15, 15, 14, 19, 14, 20, 19, 25, 19, 15, 14, 19, 14 }; /* * The scaling is by an 8x8 matrix, but due its 4x4 symmetry we can use * a 4x4 matrix for scaling * now since divide is to be avoided, we will compute 1/ values and scale it up * to preserve information since our data is max 10 bit +1 sign bit we can shift a maximum of 21 bits up * hence multiply the matrix as such {16.000 15.059 20.227 15.059 15.059 14.173 19.051 14.173 20.227 19.051 25.600 19.051 15.059 14.173 19.051 14.173}; {512, 544, 405, 544, 544, 578, 430, 578, 405, 430, 320, 430, 544, 578, 430, 578};*/ /** ****************************************************************************** * @brief Scale Table for quantizing 4x4 subblock. To quantize a given 4x4 DCT * transformed block, the coefficient at index location (i,j) is scaled by one of * the constants in this table and right shift the result by (QP_BITS_h264_4x4 + * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. * * input : qp%6, index location (i,j) * output : scale constant. * * @remarks 16 constants for each index position of the subblock and 6 for each * qp%6 in the range 0-5 inclusive. ****************************************************************************** */ const UWORD16 gu2_quant_scale_matrix_4x4[96] = { 13107, 8066, 13107, 8066, 8066, 5243, 8066, 5243, 13107, 8066, 13107, 8066, 8066, 5243, 8066, 5243, 11916, 7490, 11916, 7490, 7490, 4660, 7490, 4660, 11916, 7490, 11916, 7490, 7490, 4660, 7490, 4660, 10082, 6554, 10082, 6554, 6554, 4194, 6554, 4194, 10082, 6554, 10082, 6554, 6554, 4194, 6554, 4194, 9362, 5825, 9362, 5825, 5825, 3647, 5825, 3647, 9362, 5825, 9362, 5825, 5825, 3647, 5825, 3647, 8192, 5243, 8192, 5243, 5243, 3355, 5243, 3355, 8192, 5243, 8192, 5243, 5243, 3355, 5243, 3355, 7282, 4559, 7282, 4559, 4559, 2893, 4559, 2893, 7282, 4559, 7282, 4559, 4559, 2893, 4559, 2893, }; /** ****************************************************************************** * @brief Round Factor for quantizing subblock. While quantizing a given 4x4 DCT * transformed block, the coefficient at index location (i,j) is scaled by one of * the constants in the table gu2_forward_quant_scalar_4x4 and then right shift * the result by (QP_BITS_h264_4x4 + floor(qp/6)). * Before right shifting a round factor is added. * The round factor can be any value [a * (1 << (QP_BITS_h264_4x4 + floor(qp/6)))] * for 'a' lies in the range 0-0.5. * Here qp is the quantization parameter used to quantize the mb. * * input : qp/6 * output : round factor. * * @remarks The round factor is constructed by setting a = 1/3 * * round factor constructed by setting a = 1/3 { 10922, 21845, 43690, 87381, 174762, 349525, 699050, 1398101, 2796202, } * * round factor constructed by setting a = 0.49 *{ 16056, 32112, 64225, 128450, 256901, 513802, 1027604, 2055208, 4110417, }; * round factor constructed by setting a = 0.5 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, ****************************************************************************** */ const UWORD32 gu4_forward_quant_round_factor_4x4[9] = { 10922, 21845, 43690, 87381, 174762, 349525, 699050, 1398101, 2796202, }; /** ****************************************************************************** * @brief Threshold Table. Quantizing the given DCT coefficient is done only if * it exceeds the threshold value presented in this table. * * input : qp/6, qp%6, index location (i,j) * output : Threshold constant. * * @remarks 16 constants for each index position of the subblock and 6 for each * qp%6 in the range 0-5 inclusive and 9 for each qp/6 in the range 0-51. ****************************************************************************** */ const UWORD16 gu2_forward_quant_threshold_4x4[96] = { 426, 693, 426, 693, 693, 1066, 693, 1066, 426, 693, 426, 693, 693, 1066, 693, 1066, 469, 746, 469, 746, 746, 1200, 746, 1200, 469, 746, 469, 746, 746, 1200, 746, 1200, 554, 853, 554, 853, 853, 1333, 853, 1333, 554, 853, 554, 853, 853, 1333, 853, 1333, 597, 960, 597, 960, 960, 1533, 960, 1533, 597, 960, 597, 960, 960, 1533, 960, 1533, 682, 1066, 682, 1066, 1066, 1666, 1066, 1666, 682, 1066, 682, 1066, 1066, 1666, 1066, 1666, 767, 1226, 767, 1226, 1226, 1933, 1226, 1933, 767, 1226, 767, 1226, 1226, 1933, 1226, 1933, }; /** ****************************************************************************** * @brief Scale Table for quantizing 8x8 subblock. To quantize a given 8x8 DCT * transformed block, the coefficient at index location (i,j) is scaled by one of * the constants in this table and right shift the result by (QP_BITS_h264_8x8 + * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. * * input : qp%6, index location (i,j) * output : scale constant. * * @remarks 64 constants for each index position of the subblock and 6 for each * qp%6 in the range 0-5 inclusive. ****************************************************************************** */ const UWORD16 gu2_quant_scale_matrix_8x8 [384] = { 13107, 12222, 16777, 12222, 13107, 12222, 16777, 12222, 12222, 11428, 15481, 11428, 12222, 11428, 15481, 11428, 16777, 15481, 20972, 15481, 16777, 15481, 20972, 15481, 12222, 11428, 15481, 11428, 12222, 11428, 15481, 11428, 13107, 12222, 16777, 12222, 13107, 12222, 16777, 12222, 12222, 11428, 15481, 11428, 12222, 11428, 15481, 11428, 16777, 15481, 20972, 15481, 16777, 15481, 20972, 15481, 12222, 11428, 15481, 11428, 12222, 11428, 15481, 11428, 11916, 11058, 14980, 11058, 11916, 11058, 14980, 11058, 11058, 10826, 14290, 10826, 11058, 10826, 14290, 10826, 14980, 14290, 19174, 14290, 14980, 14290, 19174, 14290, 11058, 10826, 14290, 10826, 11058, 10826, 14290, 10826, 11916, 11058, 14980, 11058, 11916, 11058, 14980, 11058, 11058, 10826, 14290, 10826, 11058, 10826, 14290, 10826, 14980, 14290, 19174, 14290, 14980, 14290, 19174, 14290, 11058, 10826, 14290, 10826, 11058, 10826, 14290, 10826, 10082, 9675, 12710, 9675, 10082, 9675, 12710, 9675, 9675, 8943, 11985, 8943, 9675, 8943, 11985, 8943, 12710, 11985, 15978, 11985, 12710, 11985, 15978, 11985, 9675, 8943, 11985, 8943, 9675, 8943, 11985, 8943, 10082, 9675, 12710, 9675, 10082, 9675, 12710, 9675, 9675, 8943, 11985, 8943, 9675, 8943, 11985, 8943, 12710, 11985, 15978, 11985, 12710, 11985, 15978, 11985, 9675, 8943, 11985, 8943, 9675, 8943, 11985, 8943, 9362, 8931, 11984, 8931, 9362, 8931, 11984, 8931, 8931, 8228, 11259, 8228, 8931, 8228, 11259, 8228, 11984, 11259, 14913, 11259, 11984, 11259, 14913, 11259, 8931, 8228, 11259, 8228, 8931, 8228, 11259, 8228, 9362, 8931, 11984, 8931, 9362, 8931, 11984, 8931, 8931, 8228, 11259, 8228, 8931, 8228, 11259, 8228, 11984, 11259, 14913, 11259, 11984, 11259, 14913, 11259, 8931, 8228, 11259, 8228, 8931, 8228, 11259, 8228, 8192, 7740, 10486, 7740, 8192, 7740, 10486, 7740, 7740, 7346, 9777, 7346, 7740, 7346, 9777, 7346, 10486, 9777, 13159, 9777, 10486, 9777, 13159, 9777, 7740, 7346, 9777, 7346, 7740, 7346, 9777, 7346, 8192, 7740, 10486, 7740, 8192, 7740, 10486, 7740, 7740, 7346, 9777, 7346, 7740, 7346, 9777, 7346, 10486, 9777, 13159, 9777, 10486, 9777, 13159, 9777, 7740, 7346, 9777, 7346, 7740, 7346, 9777, 7346, 7282, 6830, 9118, 6830, 7282, 6830, 9118, 6830, 6830, 6428, 8640, 6428, 6830, 6428, 8640, 6428, 9118, 8640, 11570, 8640, 9118, 8640, 11570, 8640, 6830, 6428, 8640, 6428, 6830, 6428, 8640, 6428, 7282, 6830, 9118, 6830, 7282, 6830, 9118, 6830, 6830, 6428, 8640, 6428, 6830, 6428, 8640, 6428, 9118, 8640, 11570, 8640, 9118, 8640, 11570, 8640, 6830, 6428, 8640, 6428, 6830, 6428, 8640, 6428, }; /** ****************************************************************************** * @brief Specification of QPc as a function of qPi * * input : qp luma * output : qp chroma. * * @remarks Refer Table 8-15 of h264 specification. ****************************************************************************** */ const UWORD8 gu1_qpc_fqpi[52] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 39, }; ================================================ FILE: dependencies/ih264d/common/ih264_trans_data.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_trans_data.h * * @brief * Contains declaration of global variables for H264 transform , qnat and inverse quant * * @author * Ittiam * * @remarks * ******************************************************************************* */ #ifndef IH264_GLOBAL_DATA_H_ #define IH264_GLOBAL_DATA_H_ /*****************************************************************************/ /* Extern global declarations */ /*****************************************************************************/ /* Scaling matrices for h264 quantization */ extern const UWORD16 g_scal_coff_h264_4x4[16]; extern const UWORD16 g_scal_coff_h264_8x8[16]; /** ****************************************************************************** * @brief Scale Table for quantizing 4x4 subblock. To quantize a given 4x4 DCT * transformed block, the coefficient at index location (i,j) is scaled by one of * the constants in this table and right shift the result by (QP_BITS_h264_4x4 + * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. * * input : qp%6, index location (i,j) * output : scale constant. * * @remarks 16 constants for each index position of the subblock and 6 for each * qp%6 in the range 0-5 inclusive. ****************************************************************************** */ extern const UWORD16 gu2_quant_scale_matrix_4x4[96]; /** ****************************************************************************** * @brief Round Factor for quantizing subblock. While quantizing a given 4x4 DCT * transformed block, the coefficient at index location (i,j) is scaled by one of * the constants in the table gu2_forward_quant_scalar_4x4 and then right shift * the result by (QP_BITS_h264_4x4 + floor(qp/6)). * Before right shifting a round factor is added. * The round factor can be any value [a * (1 << (QP_BITS_h264_4x4 + floor(qp/6)))] * for 'a' lies in the range 0-0.5. * Here qp is the quantization parameter used to quantize the mb. * * input : qp/6 * output : round factor. * * @remarks The round factor is constructed by setting a = 1/3 ****************************************************************************** */ extern const UWORD32 gu4_forward_quant_round_factor_4x4[9]; /** ****************************************************************************** * @brief Threshold Table. Quantizing the given DCT coefficient is done only if * it exceeds the threshold value presented in this table. * * input : qp/6, qp%6, index location (i,j) * output : Threshold constant. * * @remarks 16 constants for each index position of the subblock and 6 for each * qp%6 in the range 0-5 inclusive and 9 for each qp/6 in the range 0-51. ****************************************************************************** */ extern const UWORD16 gu2_forward_quant_threshold_4x4[96]; /** ****************************************************************************** * @brief Scale Table for quantizing 8x8 subblock. To quantize a given 8x8 DCT * transformed block, the coefficient at index location (i,j) is scaled by one of * the constants in this table and right shift the result by (QP_BITS_h264_8x8 + * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. * * input : qp%6, index location (i,j) * output : scale constant. * * @remarks 64 constants for each index position of the subblock and 6 for each * qp%6 in the range 0-5 inclusive. ****************************************************************************** */ extern const UWORD16 gu2_quant_scale_matrix_8x8 [384]; /** ****************************************************************************** * @brief Specification of QPc as a function of qPi * * input : qp luma * output : qp chroma. * * @remarks Refer Table 8-15 of h264 specification. ****************************************************************************** */ extern const UWORD8 gu1_qpc_fqpi[52]; #endif /* IH264_GLOBAL_DATA_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_trans_macros.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_trans_macros.h * * @brief * The file contains definitions of macros that perform forward and inverse * quantization * * @author * Ittiam * * @remark * None * ******************************************************************************* */ #ifndef IH264_TRANS_MACROS_H_ #define IH264_TRANS_MACROS_H_ /*****************************************************************************/ /* Function Macros */ /*****************************************************************************/ /** ****************************************************************************** * @brief Macro to perform forward quantization. * @description The value to be quantized is first compared with a threshold. * If the value is less than the threshold, the quantization value is returned * as zero else the value is quantized traditionally as per the rules of * h264 specification ****************************************************************************** */ #define FWD_QUANT(i4_value, u4_abs_value, i4_sign, threshold, scale, rndfactor, qbits, u4_nnz) \ {\ if (i4_value < 0)\ {\ u4_abs_value = -i4_value;\ i4_sign = -1;\ }\ else\ {\ u4_abs_value = i4_value;\ i4_sign = 1;\ }\ if (u4_abs_value < threshold)\ {\ i4_value = 0;\ }\ else\ {\ u4_abs_value *= scale;\ u4_abs_value += rndfactor;\ u4_abs_value >>= qbits;\ i4_value = u4_abs_value * i4_sign;\ if (i4_value)\ {\ u4_nnz++;\ }\ }\ } /** ****************************************************************************** * @brief Macro to perform inverse quantization. * @remarks The value can also be de-quantized as * if (u4_qp_div_6 < 4) * { * i4_value = (quant_scale * weight_scale * i4_value + (1 << (3-u4_qp_div_6))) * i4_value >>= (4 - u4_qp_div_6) * } * else * { * i4_value = (quant_scale * weight_scale * i4_value) << (u4_qp_div_6 -4) * } ****************************************************************************** */ #define INV_QUANT(i4_value, quant_scale, weight_scale, u4_qp_div_6, rndfactor, qbits)\ {\ i4_value *= quant_scale;\ i4_value *= weight_scale;\ i4_value += rndfactor;\ i4_value <<= u4_qp_div_6;\ i4_value >>= qbits;\ } #define QUANT_H264(x,y,w,z,shft) (shft = ABS(x),\ shft *= y,\ shft += z,\ shft = shft>>w,\ shft = SIGNXY(shft,x)) #define IQUANT_H264(x,y,wscal,w,shft) (shft = x, \ shft *=y, \ shft *=wscal, \ shft = shft<<w) #define IQUANT_lev_H264(x,y,wscal,add_f,w,shft) (shft = x, \ shft *=y, \ shft *=wscal, \ shft+= add_f, \ shft = shft>>w) #endif /* IH264_TRANS_MACROS_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_trans_quant_itrans_iquant.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_trans_quant.h * * @brief * Contains declarations for forward and inverse transform paths for H264 * * @author * Ittiam * * @remarks * ******************************************************************************* */ #ifndef IH264_TRANS_QUANT_H_ #define IH264_TRANS_QUANT_H_ /*****************************************************************************/ /* Extern Function Declarations */ /*****************************************************************************/ typedef void ih264_resi_trans_dctrans_quant_ft(UWORD8*pu1_src, UWORD8 *pu1_pred, WORD16 *pi2_out, WORD32 src_strd, WORD32 pred_strd, WORD32 dst_strd, const UWORD16 *pu2_scale_mat, const UWORD16 *pu2_thresh_mat, UWORD32 u4_qbit, UWORD32 u4_round_fact, UWORD8 *pu1_nnz); typedef void ih264_idctrans_iquant_itrans_recon_ft(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 src_strd, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscale_mat, const UWORD16 *pu2_weigh_mat, UWORD32 qp_div, UWORD32 pi4_cntrl, WORD32 *pi4_tmp); /*Function prototype declarations*/ typedef void ih264_resi_trans_quant_ft(UWORD8*pu1_src, UWORD8 *pu1_pred, WORD16 *pi2_out, WORD32 src_strd, WORD32 pred_strd, const UWORD16 *pu2_scale_mat, const UWORD16 *pu2_thresh_mat, UWORD32 u4_qbit, UWORD32 u4_round_fact, UWORD8 *pu1_nnz, WORD16 *pi2_alt_dc_addr); typedef void ih264_luma_16x16_resi_trans_dctrans_quant_ft(UWORD8 *pu1_src, UWORD8 *pu1_pred, WORD16 *pi2_out, WORD32 src_strd, WORD32 pred_strd, WORD32 dst_strd, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor, UWORD8 *pu1_nnz, UWORD32 u4_dc_flag); typedef void ih264_chroma_8x8_resi_trans_dctrans_quant_ft(UWORD8 *pu1_src, UWORD8 *pu1_pred, WORD16 *pi2_out, WORD32 src_strd, WORD32 pred_strd, WORD32 dst_strd, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor, UWORD8 *pu1_nnz); typedef void ih264_iquant_itrans_recon_ft(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscale_mat, const UWORD16 *pu2_weigh_mat, UWORD32 qp_div, WORD16 *pi2_tmp, WORD32 iq_start_idx, WORD16 *pi2_dc_ld_addr); typedef void ih264_iquant_itrans_recon_chroma_ft(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD16 *pi2_tmp, WORD16 *pi2_dc_src); typedef void ih264_luma_16x16_idctrans_iquant_itrans_recon_ft(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 src_strd, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscale_mat, const UWORD16 *pu2_weigh_mat, UWORD32 qp_div, UWORD32 pi4_cntrl, UWORD32 u4_dc_trans_flag, WORD32 *pi4_tmp); typedef void ih264_chroma_8x8_idctrans_iquant_itrans_recon_ft(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 src_strd, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscale_mat, const UWORD16 *pu2_weigh_mat, UWORD32 qp_div, UWORD32 pi4_cntrl, WORD32 *pi4_tmp); typedef void ih264_ihadamard_scaling_ft(WORD16* pi2_src, WORD16* pi2_out, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD32* pi4_tmp); typedef void ih264_hadamard_quant_ft(WORD16 *pi2_src, WORD16 *pi2_dst, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor,UWORD8 *pu1_nnz); ih264_resi_trans_quant_ft ih264_resi_trans_quant_4x4; ih264_resi_trans_quant_ft ih264_resi_trans_quant_chroma_4x4; ih264_resi_trans_quant_ft ih264_resi_trans_quant_8x8; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_dc; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_dc; ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4; ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_dc; ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_4x4; ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_2x2_uv; ih264_hadamard_quant_ft ih264_hadamard_quant_4x4; ih264_hadamard_quant_ft ih264_hadamard_quant_2x2_uv; /*A9 Declarations*/ ih264_resi_trans_quant_ft ih264_resi_trans_quant_4x4_a9; ih264_resi_trans_quant_ft ih264_resi_trans_quant_chroma_4x4_a9; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_a9; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_a9; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_dc_a9; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_dc_a9; ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_a9; ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_dc_a9; ih264_luma_16x16_resi_trans_dctrans_quant_ft ih264_luma_16x16_resi_trans_dctrans_quant_a9; ih264_chroma_8x8_resi_trans_dctrans_quant_ft ih264_chroma_8x8_resi_trans_dctrans_quant_a9; ih264_luma_16x16_idctrans_iquant_itrans_recon_ft ih264_luma_16x16_idctrans_iquant_itrans_recon_a9; ih264_chroma_8x8_idctrans_iquant_itrans_recon_ft ih264_chroma_8x8_idctrans_iquant_itrans_recon_a9; ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_4x4_a9; ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_2x2_uv_a9; ih264_hadamard_quant_ft ih264_hadamard_quant_4x4_a9; ih264_hadamard_quant_ft ih264_hadamard_quant_2x2_uv_a9; /*Av8 Declarations*/ ih264_resi_trans_quant_ft ih264_resi_trans_quant_4x4_av8; ih264_resi_trans_quant_ft ih264_resi_trans_quant_chroma_4x4_av8; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_av8; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_av8; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_dc_av8; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_dc_av8; ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_av8; ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_dc_av8; ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_4x4_av8; ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_2x2_uv_av8; ih264_hadamard_quant_ft ih264_hadamard_quant_4x4_av8; ih264_hadamard_quant_ft ih264_hadamard_quant_2x2_uv_av8; /*SSSE3 Declarations*/ ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_ssse3; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_ssse3; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_dc_ssse3; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_dc_ssse3; ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_dc_ssse3; ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_4x4_ssse3; ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_2x2_uv_ssse3; /*SSSE42 Declarations*/ ih264_resi_trans_quant_ft ih264_resi_trans_quant_4x4_sse42; ih264_resi_trans_quant_ft ih264_resi_trans_quant_chroma_4x4_sse42; ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_sse42; ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_sse42; ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_4x4_sse42; ih264_hadamard_quant_ft ih264_hadamard_quant_4x4_sse42; ih264_hadamard_quant_ft ih264_hadamard_quant_2x2_uv_sse42; #endif /* IH264_TRANS_QUANT_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_typedefs.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_typedefs.h * * @brief * Type definitions used in the code * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_TYPEDEFS_H_ #define _IH264_TYPEDEFS_H_ #include <stdint.h> /*****************************************************************************/ /* Unsigned data types */ /*****************************************************************************/ typedef uint8_t UWORD8; typedef uint16_t UWORD16; typedef uint32_t UWORD32; typedef uint64_t UWORD64; /*****************************************************************************/ /* Signed data types */ /*****************************************************************************/ typedef int8_t WORD8; typedef int16_t WORD16; typedef int32_t WORD32; typedef int64_t WORD64; /*****************************************************************************/ /* Miscellaneous data types */ /*****************************************************************************/ typedef char CHAR; typedef double DOUBLE; #endif /* _IH264_TYPEDEFS_H_ */ ================================================ FILE: dependencies/ih264d/common/ih264_weighted_pred.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264_weighted_pred.c */ /* */ /* Description : Contains function definitions for weighted */ /* prediction functions */ /* */ /* List of Functions : ih264_default_weighted_pred_luma() */ /* ih264_default_weighted_pred_chroma() */ /* ih264_weighted_pred_luma() */ /* ih264_weighted_pred_chroma() */ /* ih264_weighted_bipred_luma() */ /* ih264_weighted_bipred_chroma() */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes */ /* 07 01 2015 Kaushik Initial version */ /* Senthoor */ /* */ /*****************************************************************************/ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* User include files */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_weighted_pred.h" /*****************************************************************************/ /* Function definitions . */ /*****************************************************************************/ /*****************************************************************************/ /* */ /* Function Name : ih264_default_weighted_pred_luma */ /* */ /* Description : This function performs the default weighted prediction */ /* as described in sec 8.4.2.3.1 titled "Default weighted */ /* sample prediction process" for luma. The function gets */ /* two ht x wd blocks, calculates their rounded-average and */ /* stores it in the destination block. (ht,wd) can be */ /* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src1 - Pointer to source 1 */ /* puc_src2 - Pointer to source 2 */ /* puc_dst - Pointer to destination */ /* src_strd1 - stride for source 1 */ /* src_strd1 - stride for source 2 */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 07 01 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_default_weighted_pred_luma(UWORD8 *pu1_src1, UWORD8 *pu1_src2, UWORD8 *pu1_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 ht, WORD32 wd) { WORD32 i, j; src_strd1 -= wd; src_strd2 -= wd; dst_strd -= wd; for(i = 0; i < ht; i++) { for(j = 0; j < wd; j++, pu1_src1++, pu1_src2++, pu1_dst++) *pu1_dst = (*pu1_src1 + *pu1_src2 + 1) >> 1; pu1_src1 += src_strd1; pu1_src2 += src_strd2; pu1_dst += dst_strd; } } /*****************************************************************************/ /* */ /* Function Name : ih264_default_weighted_pred_chroma */ /* */ /* Description : This function performs the default weighted prediction */ /* as described in sec 8.4.2.3.1 titled "Default weighted */ /* sample prediction process" for chroma. The function gets */ /* two ht x wd blocks, calculates their rounded-average and */ /* stores it in the destination block. (ht,wd) can be */ /* (2,2), (4,2) , (2,4), (4,4), (8,4), (4,8) or (8,8). */ /* */ /* Inputs : puc_src1 - Pointer to source 1 */ /* puc_src2 - Pointer to source 2 */ /* puc_dst - Pointer to destination */ /* src_strd1 - stride for source 1 */ /* src_strd1 - stride for source 2 */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 07 01 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_default_weighted_pred_chroma(UWORD8 *pu1_src1, UWORD8 *pu1_src2, UWORD8 *pu1_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 ht, WORD32 wd) { WORD32 i, j; wd = wd << 1; src_strd1 -= wd; src_strd2 -= wd; dst_strd -= wd; for(i = 0; i < ht; i++) { for(j = 0; j < wd; j++, pu1_src1++, pu1_src2++, pu1_dst++) *pu1_dst = (*pu1_src1 + *pu1_src2 + 1) >> 1; pu1_src1 += src_strd1; pu1_src2 += src_strd2; pu1_dst += dst_strd; } } /*****************************************************************************/ /* */ /* Function Name : ih264_weighted_pred_luma */ /* */ /* Description : This function performs the weighted prediction as */ /* described in sec 8.4.2.3.2 titled "Weighted sample */ /* prediction process" for luma. The function gets one */ /* ht x wd block, weights it, rounds it off, offsets it, */ /* saturates it to unsigned 8-bit and stores it in the */ /* destination block. (ht,wd) can be (4,4), (8,4), (4,8), */ /* (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src - Pointer to source */ /* puc_dst - Pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* log_wd - number of bits to be rounded off */ /* wt - weight value */ /* ofst - offset value */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 07 01 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_weighted_pred_luma(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 log_wd, WORD32 wt, WORD32 ofst, WORD32 ht, WORD32 wd) { WORD32 i, j; wt = (WORD16)(wt & 0xffff); ofst = (WORD8)(ofst & 0xff); src_strd -= wd; dst_strd -= wd; if(log_wd >= 1) { WORD32 i_ofst = (1 << (log_wd - 1)) + (ofst << log_wd); for(i = 0; i < ht; i++) { for(j = 0; j < wd; j++, pu1_src++, pu1_dst++) *pu1_dst = CLIP_U8((wt * (*pu1_src) + i_ofst) >> log_wd); pu1_src += src_strd; pu1_dst += dst_strd; } } else { for(i = 0; i < ht; i++) { for(j = 0; j < wd; j++, pu1_src++, pu1_dst++) *pu1_dst = CLIP_U8(wt * (*pu1_src) + ofst); pu1_src += src_strd; pu1_dst += dst_strd; } } } /*****************************************************************************/ /* */ /* Function Name : ih264_weighted_pred_chroma */ /* */ /* Description : This function performs the weighted prediction as */ /* described in sec 8.4.2.3.2 titled "Weighted sample */ /* prediction process" for chroma. The function gets one */ /* ht x wd block, weights it, rounds it off, offsets it, */ /* saturates it to unsigned 8-bit and stores it in the */ /* destination block. (ht,wd) can be (2,2), (4,2), (2,4), */ /* (4,4), (8,4), (4,8) or (8,8). */ /* */ /* Inputs : puc_src - Pointer to source */ /* puc_dst - Pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* log_wd - number of bits to be rounded off */ /* wt - weight values for u and v */ /* ofst - offset values for u and v */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 07 01 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_weighted_pred_chroma(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 log_wd, WORD32 wt, WORD32 ofst, WORD32 ht, WORD32 wd) { WORD32 i, j; WORD32 wt_u, wt_v; WORD32 ofst_u, ofst_v; wt_u = (WORD16)(wt & 0xffff); wt_v = (WORD16)(wt >> 16); ofst_u = (WORD8)(ofst & 0xff); ofst_v = (WORD8)(ofst >> 8); src_strd -= wd << 1; dst_strd -= wd << 1; if(log_wd >= 1) { ofst_u = (1 << (log_wd - 1)) + (ofst_u << log_wd); ofst_v = (1 << (log_wd - 1)) + (ofst_v << log_wd); for(i = 0; i < ht; i++) { for(j = 0; j < wd; j++, pu1_src++, pu1_dst++) { *pu1_dst = CLIP_U8((wt_u * (*pu1_src) + ofst_u) >> log_wd); pu1_src++; pu1_dst++; *pu1_dst = CLIP_U8((wt_v * (*pu1_src) + ofst_v) >> log_wd); } pu1_src += src_strd; pu1_dst += dst_strd; } } else { for(i = 0; i < ht; i++) { for(j = 0; j < wd; j++, pu1_src++, pu1_dst++) { *pu1_dst = CLIP_U8(wt_u * (*pu1_src) + ofst_u); pu1_src++; pu1_dst++; *pu1_dst = CLIP_U8(wt_v * (*pu1_src) + ofst_v); } pu1_src += src_strd; pu1_dst += dst_strd; } } } /*****************************************************************************/ /* */ /* Function Name : ih264_weighted_bi_pred_luma */ /* */ /* Description : This function performs the weighted biprediction as */ /* described in sec 8.4.2.3.2 titled "Weighted sample */ /* prediction process" for luma. The function gets two */ /* ht x wd blocks, weights them, adds them, rounds off the */ /* sum, offsets it, saturates it to unsigned 8-bit and */ /* stores it in the destination block. (ht,wd) can be */ /* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src1 - Pointer to source 1 */ /* puc_src2 - Pointer to source 2 */ /* puc_dst - Pointer to destination */ /* src_strd1 - stride for source 1 */ /* src_strd2 - stride for source 2 */ /* dst_strd2 - stride for destination */ /* log_wd - number of bits to be rounded off */ /* wt1 - weight value for source 1 */ /* wt2 - weight value for source 2 */ /* ofst1 - offset value for source 1 */ /* ofst2 - offset value for source 2 */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 07 01 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_weighted_bi_pred_luma(UWORD8 *pu1_src1, UWORD8 *pu1_src2, UWORD8 *pu1_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 log_wd, WORD32 wt1, WORD32 wt2, WORD32 ofst1, WORD32 ofst2, WORD32 ht, WORD32 wd) { WORD32 i, j; WORD32 shft, ofst; ofst1 = (WORD8)(ofst1 & 0xff); ofst2 = (WORD8)(ofst2 & 0xff); wt1 = (WORD16)(wt1 & 0xffff); wt2 = (WORD16)(wt2 & 0xffff); ofst = (ofst1 + ofst2 + 1) >> 1; shft = log_wd + 1; ofst = (1 << log_wd) + (ofst << shft); src_strd1 -= wd; src_strd2 -= wd; dst_strd -= wd; for(i = 0; i < ht; i++) { for(j = 0; j < wd; j++, pu1_src1++, pu1_src2++, pu1_dst++) *pu1_dst = CLIP_U8((wt1 * (*pu1_src1) + wt2 * (*pu1_src2) + ofst) >> shft); pu1_src1 += src_strd1; pu1_src2 += src_strd2; pu1_dst += dst_strd; } } /*****************************************************************************/ /* */ /* Function Name : ih264_weighted_bi_pred_chroma */ /* */ /* Description : This function performs the weighted biprediction as */ /* described in sec 8.4.2.3.2 titled "Weighted sample */ /* prediction process" for chroma. The function gets two */ /* ht x wd blocks, weights them, adds them, rounds off the */ /* sum, offsets it, saturates it to unsigned 8-bit and */ /* stores it in the destination block. (ht,wd) can be */ /* (2,2), (4,2), (2,4), (4,4), (8,4), (4,8) or (8,8). */ /* */ /* Inputs : puc_src1 - Pointer to source 1 */ /* puc_src2 - Pointer to source 2 */ /* puc_dst - Pointer to destination */ /* src_strd1 - stride for source 1 */ /* src_strd2 - stride for source 2 */ /* dst_strd2 - stride for destination */ /* log_wd - number of bits to be rounded off */ /* wt1 - weight values for u and v in source 1 */ /* wt2 - weight values for u and v in source 2 */ /* ofst1 - offset value for u and v in source 1 */ /* ofst2 - offset value for u and v in source 2 */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 07 01 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ void ih264_weighted_bi_pred_chroma(UWORD8 *pu1_src1, UWORD8 *pu1_src2, UWORD8 *pu1_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 log_wd, WORD32 wt1, WORD32 wt2, WORD32 ofst1, WORD32 ofst2, WORD32 ht, WORD32 wd) { WORD32 i, j; WORD32 wt1_u, wt1_v, wt2_u, wt2_v; WORD32 ofst1_u, ofst1_v, ofst2_u, ofst2_v; WORD32 ofst_u, ofst_v; WORD32 shft; ofst1_u = (WORD8)(ofst1 & 0xff); ofst1_v = (WORD8)(ofst1 >> 8); ofst2_u = (WORD8)(ofst2 & 0xff); ofst2_v = (WORD8)(ofst2 >> 8); wt1_u = (WORD16)(wt1 & 0xffff); wt1_v = (WORD16)(wt1 >> 16); wt2_u = (WORD16)(wt2 & 0xffff); wt2_v = (WORD16)(wt2 >> 16); ofst_u = (ofst1_u + ofst2_u + 1) >> 1; ofst_v = (ofst1_v + ofst2_v + 1) >> 1; src_strd1 -= wd << 1; src_strd2 -= wd << 1; dst_strd -= wd << 1; shft = log_wd + 1; ofst_u = (1 << log_wd) + (ofst_u << shft); ofst_v = (1 << log_wd) + (ofst_v << shft); for(i = 0; i < ht; i++) { for(j = 0; j < wd; j++, pu1_src1++, pu1_src2++, pu1_dst++) { *pu1_dst = CLIP_U8((wt1_u * (*pu1_src1) + wt2_u * (*pu1_src2) + ofst_u) >> shft); pu1_src1++; pu1_src2++; pu1_dst++; *pu1_dst = CLIP_U8((wt1_v * (*pu1_src1) + wt2_v * (*pu1_src2) + ofst_v) >> shft); } pu1_src1 += src_strd1; pu1_src2 += src_strd2; pu1_dst += dst_strd; } } ================================================ FILE: dependencies/ih264d/common/ih264_weighted_pred.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_weighted_pred.h * * @brief * Declarations of functions used for weighted prediction * * @author * Ittiam * * @par List of Functions: * -ih264_default_weighted_pred_luma * -ih264_default_weighted_pred_chroma * -ih264_weighted_pred_luma * -ih264_weighted_pred_chroma * -ih264_weighted_bi_pred_luma * -ih264_weighted_bi_pred_chroma * -ih264_default_weighted_pred_luma_a9q * -ih264_default_weighted_pred_chroma_a9q * -ih264_weighted_pred_luma_a9q * -ih264_weighted_pred_luma_a9q * -ih264_weighted_bi_pred_luma_a9q * -ih264_weighted_bi_pred_chroma_a9q * -ih264_default_weighted_pred_luma_av8 * -ih264_default_weighted_pred_chroma_av8 * -ih264_weighted_pred_luma_av8 * -ih264_weighted_pred_chroma_av8 * -ih264_weighted_bi_pred_luma_av8 * -ih264_weighted_bi_pred_chroma_av8 * -ih264_default_weighted_pred_luma_sse42 * -ih264_default_weighted_pred_chroma_sse42 * -ih264_weighted_pred_luma_sse42 * -ih264_weighted_pred_chroma_sse42 * -ih264_weighted_bi_pred_luma_sse42 * -ih264_weighted_bi_pred_chroma_sse42 * * * @remarks * None * ******************************************************************************* */ #ifndef IH264_WEIGHTED_PRED_H_ #define IH264_WEIGHTED_PRED_H_ /*****************************************************************************/ /* Extern Function Declarations */ /*****************************************************************************/ typedef void ih264_default_weighted_pred_ft(UWORD8 *puc_src1, UWORD8 *puc_src2, UWORD8 *puc_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 ht, WORD32 wd); typedef void ih264_weighted_pred_ft(UWORD8 *puc_src, UWORD8 *puc_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 log_wd, WORD32 wt, WORD32 ofst, WORD32 ht, WORD32 wd); typedef void ih264_weighted_bi_pred_ft(UWORD8 *puc_src1, UWORD8 *puc_src2, UWORD8 *puc_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 log_wd, WORD32 wt1, WORD32 wt2, WORD32 ofst1, WORD32 ofst2, WORD32 ht, WORD32 wd); /* No NEON Declarations */ ih264_default_weighted_pred_ft ih264_default_weighted_pred_luma; ih264_default_weighted_pred_ft ih264_default_weighted_pred_chroma; ih264_weighted_pred_ft ih264_weighted_pred_luma; ih264_weighted_pred_ft ih264_weighted_pred_chroma; ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_luma; ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_chroma; /* A9 NEON Declarations */ ih264_default_weighted_pred_ft ih264_default_weighted_pred_luma_a9q; ih264_default_weighted_pred_ft ih264_default_weighted_pred_chroma_a9q; ih264_weighted_pred_ft ih264_weighted_pred_luma_a9q; ih264_weighted_pred_ft ih264_weighted_pred_chroma_a9q; ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_luma_a9q; ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_chroma_a9q; /* AV8 NEON Declarations */ ih264_default_weighted_pred_ft ih264_default_weighted_pred_luma_av8; ih264_default_weighted_pred_ft ih264_default_weighted_pred_chroma_av8; ih264_weighted_pred_ft ih264_weighted_pred_luma_av8; ih264_weighted_pred_ft ih264_weighted_pred_chroma_av8; ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_luma_av8; ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_chroma_av8; /* SSE42 Intrinsic Declarations */ ih264_default_weighted_pred_ft ih264_default_weighted_pred_luma_sse42; ih264_default_weighted_pred_ft ih264_default_weighted_pred_chroma_sse42; ih264_weighted_pred_ft ih264_weighted_pred_luma_sse42; ih264_weighted_pred_ft ih264_weighted_pred_chroma_sse42; ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_luma_sse42; ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_chroma_sse42; #endif /* IH264_WEIGHTED_PRED_H_ */ /** Nothing past this point */ ================================================ FILE: dependencies/ih264d/common/ithread.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore * Modified for use with Cemu emulator project */ /*****************************************************************************/ /* */ /* File Name : ithread.c */ /* */ /* Description : Contains abstraction for threads, mutex and semaphores*/ /* */ /* List of Functions : */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes */ /* 07 09 2012 Harish Initial Version */ /*****************************************************************************/ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ #include <string.h> #include "ih264_typedefs.h" #include "ithread.h" #include <sys/types.h> #define UNUSED(x) ((void)(x)) //#define PTHREAD_AFFINITY //#define SYSCALL_AFFINITY #ifdef PTHREAD_AFFINITY #define _GNU_SOURCE #define __USE_GNU #endif #ifdef _WIN32 #include <Windows.h> #else #include <pthread.h> #include <sched.h> #include <semaphore.h> #include <unistd.h> #endif #ifdef PTHREAD_AFFINITY #include <sys/prctl.h> #endif #ifdef _WIN32 UWORD32 ithread_get_handle_size(void) { return sizeof(HANDLE); } UWORD32 ithread_get_mutex_lock_size(void) { return sizeof(CRITICAL_SECTION); } struct _ithread_launch_param { void (*startFunc)(void* argument); void* argument; }; DWORD WINAPI _ithread_WinThreadStartRoutine(LPVOID lpThreadParameter) { struct _ithread_launch_param* param = (struct _ithread_launch_param*)lpThreadParameter; typedef void *(*ThreadStartRoutineType)(void *); ThreadStartRoutineType pfnThreadRoutine = (ThreadStartRoutineType)param->startFunc; void* arg = param->argument; free(param); pfnThreadRoutine(arg); return 0; } WORD32 ithread_create(void* thread_handle, void* attribute, void* strt, void* argument) { UNUSED(attribute); struct _ithread_launch_param* param = malloc(sizeof(struct _ithread_launch_param)); param->startFunc = (void (*)(void*))strt; param->argument = argument; HANDLE *handle = (HANDLE*)thread_handle; *handle = CreateThread(NULL, 0, _ithread_WinThreadStartRoutine, param, 0, NULL); if(*handle == NULL) { return -1; } return 0; } WORD32 ithread_join(void* thread_handle, void** val_ptr) { //UNUSED(val_ptr); HANDLE *handle = (HANDLE*)thread_handle; DWORD result = WaitForSingleObject(*handle, INFINITE); if(result == WAIT_OBJECT_0) { CloseHandle(*handle); return 0; } else { return -1; } } WORD32 ithread_get_mutex_struct_size(void) { return sizeof(CRITICAL_SECTION); } WORD32 ithread_mutex_init(void* mutex) { InitializeCriticalSection((LPCRITICAL_SECTION)mutex); return 0; } WORD32 ithread_mutex_destroy(void* mutex) { return 0; } WORD32 ithread_mutex_lock(void* mutex) { EnterCriticalSection((LPCRITICAL_SECTION)mutex); return 0; } WORD32 ithread_mutex_unlock(void* mutex) { LeaveCriticalSection((LPCRITICAL_SECTION)mutex); return 0; } void ithread_yield(void) { Sleep(0); } void ithread_msleep(UWORD32 u4_time_ms) { Sleep(u4_time_ms); } void ithread_usleep(UWORD32 u4_time_us) { __debugbreak(); //usleep(u4_time_us); } UWORD32 ithread_get_sem_struct_size(void) { __debugbreak(); return 0; //return(sizeof(sem_t)); } WORD32 ithread_sem_init(void* sem, WORD32 pshared, UWORD32 value) { __debugbreak(); return 0; //return sem_init((sem_t*)sem, pshared, value); } WORD32 ithread_sem_post(void* sem) { __debugbreak(); return 0; //return sem_post((sem_t*)sem); } WORD32 ithread_sem_wait(void* sem) { __debugbreak(); return 0; //return sem_wait((sem_t*)sem); } WORD32 ithread_sem_destroy(void* sem) { __debugbreak(); return 0; //return sem_destroy((sem_t*)sem); } void ithread_set_name(CHAR* pc_thread_name) { } WORD32 ithread_set_affinity(WORD32 core_id) { #ifdef PTHREAD_AFFINITY cpu_set_t cpuset; int num_cores = sysconf(_SC_NPROCESSORS_ONLN); pthread_t cur_thread = pthread_self(); if (core_id >= num_cores) return -1; CPU_ZERO(&cpuset); CPU_SET(core_id, &cpuset); return pthread_setaffinity_np(cur_thread, sizeof(cpu_set_t), &cpuset); #elif SYSCALL_AFFINITY WORD32 i4_sys_res; UNUSED(core_id); pid_t pid = gettid(); i4_sys_res = syscall(__NR_sched_setaffinity, pid, sizeof(i4_mask), &i4_mask); if (i4_sys_res) { //WORD32 err; //err = errno; //perror("Error in setaffinity syscall PERROR : "); //LOG_ERROR("Error in the syscall setaffinity: mask=0x%x err=0x%x", i4_mask, i4_sys_res); return -1; } #else UNUSED(core_id); #endif return 1; } #else UWORD32 ithread_get_handle_size(void) { return sizeof(pthread_t); } UWORD32 ithread_get_mutex_lock_size(void) { return sizeof(pthread_mutex_t); } WORD32 ithread_create(void* thread_handle, void* attribute, void* strt, void* argument) { UNUSED(attribute); return pthread_create((pthread_t*)thread_handle, NULL, (void* (*)(void*)) strt, argument); } WORD32 ithread_join(void* thread_handle, void** val_ptr) { UNUSED(val_ptr); pthread_t* pthread_handle = (pthread_t*)thread_handle; return pthread_join(*pthread_handle, NULL); } WORD32 ithread_get_mutex_struct_size(void) { return(sizeof(pthread_mutex_t)); } WORD32 ithread_mutex_init(void* mutex) { return pthread_mutex_init((pthread_mutex_t*)mutex, NULL); } WORD32 ithread_mutex_destroy(void* mutex) { return pthread_mutex_destroy((pthread_mutex_t*)mutex); } WORD32 ithread_mutex_lock(void* mutex) { return pthread_mutex_lock((pthread_mutex_t*)mutex); } WORD32 ithread_mutex_unlock(void* mutex) { return pthread_mutex_unlock((pthread_mutex_t*)mutex); } void ithread_yield(void) { sched_yield(); } void ithread_msleep(UWORD32 u4_time_ms) { usleep(u4_time_ms * 1000); } UWORD32 ithread_get_sem_struct_size(void) { return(sizeof(sem_t)); } WORD32 ithread_sem_init(void* sem, WORD32 pshared, UWORD32 value) { return sem_init((sem_t*)sem, pshared, value); } WORD32 ithread_sem_post(void* sem) { return sem_post((sem_t*)sem); } WORD32 ithread_sem_wait(void* sem) { return sem_wait((sem_t*)sem); } WORD32 ithread_sem_destroy(void* sem) { return sem_destroy((sem_t*)sem); } void ithread_set_name(CHAR* pc_thread_name) { UNUSED(pc_thread_name); } WORD32 ithread_set_affinity(WORD32 core_id) { #ifdef PTHREAD_AFFINITY cpu_set_t cpuset; int num_cores = sysconf(_SC_NPROCESSORS_ONLN); pthread_t cur_thread = pthread_self(); if (core_id >= num_cores) return -1; CPU_ZERO(&cpuset); CPU_SET(core_id, &cpuset); return pthread_setaffinity_np(cur_thread, sizeof(cpu_set_t), &cpuset); #elif SYSCALL_AFFINITY WORD32 i4_sys_res; UNUSED(core_id); pid_t pid = gettid(); i4_sys_res = syscall(__NR_sched_setaffinity, pid, sizeof(i4_mask), &i4_mask); if (i4_sys_res) { //WORD32 err; //err = errno; //perror("Error in setaffinity syscall PERROR : "); //LOG_ERROR("Error in the syscall setaffinity: mask=0x%x err=0x%x", i4_mask, i4_sys_res); return -1; } #else UNUSED(core_id); #endif return 1; } #endif ================================================ FILE: dependencies/ih264d/common/ithread.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ithread.h */ /* */ /* Description : This file contains all the necessary structure and */ /* enumeration definitions needed for the Application */ /* Program Interface(API) of the */ /* Thread Abstraction Layer */ /* */ /* List of Functions : ithread_get_handle_size */ /* ithread_get_mutex_lock_size */ /* ithread_create */ /* ithread_join */ /* ithread_get_mutex_struct_size */ /* ithread_mutex_init */ /* ithread_mutex_destroy */ /* ithread_mutex_lock */ /* ithread_mutex_unlock */ /* ithread_yield */ /* ithread_sleep */ /* ithread_msleep */ /* ithread_usleep */ /* ithread_get_sem_struct_size */ /* ithread_sem_init */ /* ithread_sem_post */ /* ithread_sem_wait */ /* ithread_sem_destroy */ /* ithread_set_affinity */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes */ /* 06 09 2012 Harish Initial Version */ /* */ /*****************************************************************************/ #ifndef _ITHREAD_H_ #define _ITHREAD_H_ UWORD32 ithread_get_handle_size(void); UWORD32 ithread_get_mutex_lock_size(void); WORD32 ithread_create(void *thread_handle, void *attribute, void *strt, void *argument); WORD32 ithread_join(void *thread_id, void ** val_ptr); WORD32 ithread_get_mutex_struct_size(void); WORD32 ithread_mutex_init(void *mutex); WORD32 ithread_mutex_destroy(void *mutex); WORD32 ithread_mutex_lock(void *mutex); WORD32 ithread_mutex_unlock(void *mutex); void ithread_yield(void); void ithread_msleep(UWORD32 u4_time_ms); void ithread_usleep(UWORD32 u4_time_us); UWORD32 ithread_get_sem_struct_size(void); WORD32 ithread_sem_init(void *sem,WORD32 pshared,UWORD32 value); WORD32 ithread_sem_post(void *sem); WORD32 ithread_sem_wait(void *sem); WORD32 ithread_sem_destroy(void *sem); WORD32 ithread_set_affinity(WORD32 core_id); void ithread_set_name(CHAR *pc_thread_name); #endif /* _ITHREAD_H_ */ ================================================ FILE: dependencies/ih264d/common/mips/ih264_platform_macros.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_platform_macros.h * * @brief * Platform specific Macro definitions used in the codec * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_PLATFORM_MACROS_H_ #define _IH264_PLATFORM_MACROS_H_ #include <stdint.h> #define CLIP_U8(x) CLIP3(0, UINT8_MAX, (x)) #define CLIP_S8(x) CLIP3(INT8_MIN, INT8_MAX, (x)) #define CLIP_U10(x) CLIP3(0, 1023, (x)) #define CLIP_S10(x) CLIP3(-512, 511, (x)) #define CLIP_U11(x) CLIP3(0, 2047, (x)) #define CLIP_S11(x) CLIP3(-1024, 1023, (x)) #define CLIP_U12(x) CLIP3(0, 4095, (x)) #define CLIP_S12(x) CLIP3(-2048, 2047, (x)) #define CLIP_U16(x) CLIP3(0, UINT16_MAX, (x)) #define CLIP_S16(x) CLIP3(INT16_MIN, INT16_MAX, (x)) #define CLIP_U32(x) CLIP3(0, UINT32_MAX, (x)) #define CLIP_S32(x) CLIP3(INT32_MIN, INT32_MAX, (x)) #define MEM_ALIGN16 __attribute__ ((aligned (16))) #define SHL(x,y) (((y) < 32) ? ((x) << (y)) : 0) #define SHR(x,y) (((y) < 32) ? ((x) >> (y)) : 0) #define SHR_NEG(val,shift) ((shift>0)?(val>>shift):(val<<(-shift))) #define SHL_NEG(val,shift) ((shift<0)?(val>>(-shift)):(val<<shift)) #define ITT_BIG_ENDIAN(x) ((x << 24)) | \ ((x & 0x0000ff00) << 8) | \ ((x & 0x00ff0000) >> 8) | \ ((UWORD32)x >> 24); #define NOP(nop_cnt) {UWORD32 nop_i; for (nop_i = 0; nop_i < nop_cnt; nop_i++);} #define PLD(a) /* In normal cases, 0 will not be passed as an argument to CLZ and CTZ. As CLZ and CTZ outputs are used as a shift value in few places, these return 31 for u4_word == 0 case, just to handle error cases gracefully without any undefined behaviour */ static __inline UWORD32 CLZ(UWORD32 u4_word) { if(u4_word) return(__builtin_clz(u4_word)); else return 31; } static __inline UWORD32 CTZ(UWORD32 u4_word) { if(0 == u4_word) return 31; else { unsigned int index; index = __builtin_ctz(u4_word); return (UWORD32)index; } } #define DATA_SYNC() #define INLINE #define PREFETCH(ptr, type) #define MEM_ALIGN8 __attribute__ ((aligned (8))) #define MEM_ALIGN16 __attribute__ ((aligned (16))) #define MEM_ALIGN32 __attribute__ ((aligned (32))) #endif /* _IH264_PLATFORM_MACROS_H_ */ ================================================ FILE: dependencies/ih264d/common/x86/ih264_chroma_intra_pred_filters_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_chroma_intra_pred_filters_ssse3.c * * @brief * Contains function definitions for chroma intra prediction filters in x86 * intrinsics * * @author * Ittiam * * @par List of Functions: * -ih264_intra_pred_chroma_8x8_mode_horz_ssse3 * -ih264_intra_pred_chroma_8x8_mode_vert_ssse3 * -ih264_intra_pred_chroma_8x8_mode_plane_ssse3 * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include <stdio.h> #include <stddef.h> #include <string.h> /* User include files */ #include "ih264_defs.h" #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_intra_pred_filters.h" #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /*****************************************************************************/ /* Chroma Intra prediction 8x8 filters */ /*****************************************************************************/ /** ******************************************************************************* * * ih264_intra_pred_chroma_8x8_mode_horz_ssse3 * * @brief * Perform Intra prediction for chroma_8x8 mode:Horizontal * * @par Description: * Perform Intra prediction for chroma_8x8 mode:Horizontal ,described in sec 8.3.4.2 * * @param[in] pu1_src * UWORD8 pointer to the source containing alternate U and V samples * * @param[out] pu1_dst * UWORD8 pointer to the destination with alternate U and V samples * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ****************************************************************************** */ ATTRIBUTE_SSSE3 void ih264_intra_pred_chroma_8x8_mode_horz_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left; /* Pointer to start of top predictors */ WORD32 dst_strd2; __m128i row1_16x8b, row2_16x8b; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + 2 * BLK8x8SIZE - 2; dst_strd2 = dst_strd << 1; row1_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left))); row2_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 2))); _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); pu1_dst += dst_strd2; row1_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 4))); row2_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 6))); _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); pu1_dst += dst_strd2; row1_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 8))); row2_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 10))); _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); pu1_dst += dst_strd2; row1_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 12))); row2_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 14))); _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); } /** ******************************************************************************* * * ih264_intra_pred_chroma_8x8_mode_vert_ssse3 * * @brief * Perform Intra prediction for chroma_8x8 mode:vertical * * @par Description: * Perform Intra prediction for chroma_8x8 mode:vertical ,described in sec 8.3.4.3 * * @param[in] pu1_src * UWORD8 pointer to the source containing alternate U and V samples * * @param[out] pu1_dst * UWORD8 pointer to the destination with alternate U and V samples * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_intra_pred_chroma_8x8_mode_vert_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top; /* Pointer to start of top predictors */ WORD32 dst_strd2; __m128i top_16x8b; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + 2 * BLK8x8SIZE + 2; top_16x8b = _mm_loadu_si128((__m128i *)pu1_top); dst_strd2 = dst_strd << 1; _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); pu1_dst += dst_strd2; _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); pu1_dst += dst_strd2; _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); pu1_dst += dst_strd2; _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); } /** ******************************************************************************* * * ih264_intra_pred_chroma_8x8_mode_plane_ssse3 * * @brief * Perform Intra prediction for chroma_8x8 mode:PLANE * * @par Description: * Perform Intra prediction for chroma_8x8 mode:PLANE ,described in sec 8.3.4.4 * * @param[in] pu1_src * UWORD8 pointer to the source containing alternate U and V samples * * @param[out] pu1_dst * UWORD8 pointer to the destination with alternate U and V samples * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ****************************************************************************** */ ATTRIBUTE_SSSE3 void ih264_intra_pred_chroma_8x8_mode_plane_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left, *pu1_top; WORD32 a_u, a_v, b_u, b_v, c_u, c_v; __m128i mul_8x16b, shuffle_8x16b; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + MB_SIZE + 2; pu1_left = pu1_src + MB_SIZE - 2; mul_8x16b = _mm_setr_epi16(1, 2, 3, 4, 1, 2, 3, 4); shuffle_8x16b = _mm_setr_epi16(0xff00, 0xff02, 0xff04, 0xff06, 0xff01, 0xff03, 0xff05, 0xff07); //calculating a, b and c { WORD32 h_u, h_v, v_u, v_v; __m128i h_val1_16x8b, h_val2_16x8b; __m128i h_val1_8x16b, h_val2_8x16b, h_val_4x32b; __m128i v_val1_16x8b, v_val2_16x8b; __m128i v_val1_8x16b, v_val2_8x16b, v_val_4x32b; __m128i hv_val_4x32b; h_val1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_top + 8)); h_val2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_top - 2)); v_val1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 14)); v_val2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 4)); // reversing the order h_val2_16x8b = _mm_shufflelo_epi16(h_val2_16x8b, 0x1b); v_val1_16x8b = _mm_shufflelo_epi16(v_val1_16x8b, 0x1b); // separating u and v and 8-bit to 16-bit conversion h_val1_8x16b = _mm_shuffle_epi8(h_val1_16x8b, shuffle_8x16b); h_val2_8x16b = _mm_shuffle_epi8(h_val2_16x8b, shuffle_8x16b); v_val1_8x16b = _mm_shuffle_epi8(v_val1_16x8b, shuffle_8x16b); v_val2_8x16b = _mm_shuffle_epi8(v_val2_16x8b, shuffle_8x16b); h_val1_8x16b = _mm_sub_epi16(h_val1_8x16b, h_val2_8x16b); v_val1_8x16b = _mm_sub_epi16(v_val1_8x16b, v_val2_8x16b); h_val_4x32b = _mm_madd_epi16(mul_8x16b, h_val1_8x16b); v_val_4x32b = _mm_madd_epi16(mul_8x16b, v_val1_8x16b); hv_val_4x32b = _mm_hadd_epi32(h_val_4x32b, v_val_4x32b); a_u = (pu1_left[7 * (-2)] + pu1_top[14]) << 4; a_v = (pu1_left[7 * (-2) + 1] + pu1_top[15]) << 4; h_u = _mm_extract_epi16(hv_val_4x32b, 0); h_v = _mm_extract_epi16(hv_val_4x32b, 2); v_u = _mm_extract_epi16(hv_val_4x32b, 4); v_v = _mm_extract_epi16(hv_val_4x32b, 6); h_u = (h_u << 16) >> 15; // sign-extension and multiplication by 2 h_v = (h_v << 16) >> 15; v_u = (v_u << 16) >> 15; v_v = (v_v << 16) >> 15; b_u = ((h_u << 4) + h_u + 32) >> 6; b_v = ((h_v << 4) + h_v + 32) >> 6; c_u = ((v_u << 4) + v_u + 32) >> 6; c_v = ((v_v << 4) + v_v + 32) >> 6; } //using a, b and c to compute the fitted plane values { __m128i const_8x16b, c2_8x16b; __m128i res1_l_8x16b, res1_h_8x16b; __m128i res2_l_8x16b, res2_h_8x16b; __m128i res1_sh_l_8x16b, res1_sh_h_8x16b, res1_16x8b; __m128i res2_sh_l_8x16b, res2_sh_h_8x16b, res2_16x8b; WORD32 b_u2, b_v2, b_u3, b_v3; WORD32 const_u, const_v; WORD32 dst_strd2; const_u = a_u - (c_u << 1) - c_u + 16; const_v = a_v - (c_v << 1) - c_v + 16; b_u2 = b_u << 1; b_v2 = b_v << 1; b_u3 = b_u + b_u2; b_v3 = b_v + b_v2; const_8x16b = _mm_setr_epi16(const_u, const_v, const_u, const_v, const_u, const_v, const_u, const_v); res1_l_8x16b = _mm_setr_epi16(-b_u3, -b_v3, -b_u2, -b_v2, -b_u, -b_v, 0, 0); //contains {-b*3, -b*2, -b*1, b*0} res1_h_8x16b = _mm_setr_epi16(b_u, b_v, b_u2, b_v2, b_u3, b_v3, b_u << 2, b_v << 2); //contains {b*1, b*2, b*3, b*4} c2_8x16b = _mm_setr_epi16(c_u, c_v, c_u, c_v, c_u, c_v, c_u, c_v); // rows 1, 2 res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, const_8x16b); res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, const_8x16b); res2_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); dst_strd2 = dst_strd << 1; c2_8x16b = _mm_slli_epi16(c2_8x16b, 1); res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); // rows 3, 4 res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); pu1_dst += dst_strd2; res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); // rows 5, 6 res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); pu1_dst += dst_strd2; res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); // rows 7, 8 res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); pu1_dst += dst_strd2; res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); } } ================================================ FILE: dependencies/ih264d/common/x86/ih264_deblk_chroma_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264_deblk_chroma_ssse3.c */ /* */ /* Description : Contains function definitions for deblocking */ /* */ /* List of Functions : ih264_deblk_chroma_vert_bs4_ssse3() */ /* ih264_deblk_chroma_horz_bs4_ssse3() */ /* ih264_deblk_chroma_vert_bslt4_ssse3() */ /* ih264_deblk_chroma_horz_bslt4_ssse3() */ /* ih264_deblk_chroma_vert_bs4_mbaff_ssse3() */ /* ih264_deblk_chroma_vert_bslt4_mbaff_ssse3() */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Added chrom deblocking ssse3 */ /* intrinsics */ /* */ /*****************************************************************************/ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include <stdio.h> /* User include files */ #include "ih264_typedefs.h" #include "ih264_platform_macros.h" #include "ih264_deblk_edge_filters.h" #include "ih264_macros.h" #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /*****************************************************************************/ /* Function Definitions */ /*****************************************************************************/ /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bs4_ssse3() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when the boundary strength is set to 4 in */ /* high profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.4 under the */ /* title "Filtering process for edges for bS equal to 4" in */ /* ITU T Rec H.264 with alpha and beta values different in */ /* U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_chroma_vert_bs4_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr) { UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; __m128i linea, lineb, linec, lined, linee, linef, lineg, lineh; __m128i temp1, temp2, temp3, temp4; __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; __m128i flag1, flag2; __m128i diff, alpha_cbcr_16x8, beta_cbcr_16x8; __m128i zero = _mm_setzero_si128(); __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; /* Load and transpose the pixel values */ linea = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4)); lineb = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + src_strd)); linec = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd)); lined = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd)); linee = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 4 * src_strd)); linef = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 5 * src_strd)); lineg = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 6 * src_strd)); lineh = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 7 * src_strd)); temp1 = _mm_unpacklo_epi16(linea, lineb); temp2 = _mm_unpacklo_epi16(linec, lined); temp3 = _mm_unpacklo_epi16(linee, linef); temp4 = _mm_unpacklo_epi16(lineg, lineh); p1_uv_8x16 = _mm_unpacklo_epi32(temp1, temp2); p0_uv_8x16 = _mm_unpacklo_epi32(temp3, temp4); q0_uv_8x16 = _mm_unpackhi_epi32(temp1, temp2); q1_uv_8x16 = _mm_unpackhi_epi32(temp3, temp4); p1_uv_16x8 = _mm_unpacklo_epi64(p1_uv_8x16, p0_uv_8x16); p0_uv_16x8 = _mm_unpackhi_epi64(p1_uv_8x16, p0_uv_8x16); q0_uv_16x8 = _mm_unpacklo_epi64(q0_uv_8x16, q1_uv_8x16); q1_uv_16x8 = _mm_unpackhi_epi64(q0_uv_8x16, q1_uv_8x16); /* End of transpose */ q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 diff = _mm_abs_epi16(diff); alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 diff = _mm_abs_epi16(diff); beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 diff = _mm_abs_epi16(diff); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); temp1 = _mm_slli_epi16(p1_uv_8x16, 1); temp2 = _mm_add_epi16(p0_uv_8x16, q1_uv_8x16); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); temp1 = _mm_add_epi16(temp1, temp2); p0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); temp1 = _mm_slli_epi16(q1_uv_8x16, 1); temp2 = _mm_add_epi16(p1_uv_8x16, q0_uv_8x16); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); temp1 = _mm_add_epi16(temp1, temp2); q0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); q0_uv_8x16 = _mm_unpackhi_epi8(q0_uv_16x8, zero); q1_uv_8x16 = _mm_unpackhi_epi8(q1_uv_16x8, zero); p1_uv_8x16 = _mm_unpackhi_epi8(p1_uv_16x8, zero); p0_uv_8x16 = _mm_unpackhi_epi8(p0_uv_16x8, zero); diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 diff = _mm_abs_epi16(diff); alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); flag2 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 diff = _mm_abs_epi16(diff); beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 diff = _mm_abs_epi16(diff); flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); temp1 = _mm_slli_epi16(p1_uv_8x16, 1); temp2 = _mm_add_epi16(p0_uv_8x16, q1_uv_8x16); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); temp1 = _mm_add_epi16(temp1, temp2); p0_uv_8x16_2 = _mm_srai_epi16(temp1, 2); temp1 = _mm_slli_epi16(q1_uv_8x16, 1); temp2 = _mm_add_epi16(p1_uv_8x16, q0_uv_8x16); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); temp1 = _mm_add_epi16(temp1, temp2); q0_uv_8x16_2 = _mm_srai_epi16(temp1, 2); p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_2); q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_2); flag1 = _mm_packs_epi16(flag1, flag2); p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); p0_uv_16x8 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); q0_uv_16x8 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); /* Inverse-transpose and store back */ temp1 = _mm_unpacklo_epi16(p1_uv_16x8, p0_uv_16x8); temp2 = _mm_unpackhi_epi16(p1_uv_16x8, p0_uv_16x8); temp3 = _mm_unpacklo_epi16(q0_uv_16x8, q1_uv_16x8); temp4 = _mm_unpackhi_epi16(q0_uv_16x8, q1_uv_16x8); linea = _mm_unpacklo_epi32(temp1, temp3); lineb = _mm_srli_si128(linea, 8); linec = _mm_unpackhi_epi32(temp1, temp3); lined = _mm_srli_si128(linec, 8); linee = _mm_unpacklo_epi32(temp2, temp4); linef = _mm_srli_si128(linee, 8); lineg = _mm_unpackhi_epi32(temp2, temp4); lineh = _mm_srli_si128(lineg, 8); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4), linea); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + src_strd), lineb); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd), linec); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd), lined); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 4 * src_strd), linee); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 5 * src_strd), linef); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 6 * src_strd), lineg); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 7 * src_strd), lineh); } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_horz_bs4_ssse3() */ /* */ /* Description : This function performs filtering of a chroma block */ /* horizontal edge when the boundary strength is set to 4 */ /* in high profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.4 under the */ /* title "Filtering process for edges for bS equal to 4" in */ /* ITU T Rec H.264 with alpha and beta values different in */ /* U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_chroma_horz_bs4_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr) { UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ WORD16 i16_posP1, i16_posP0, i16_posQ1; UWORD8 *pu1_HorzPixelUV; /*! < Pointer to the first pixel of the boundary */ WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; __m128i flag1, flag2; __m128i diff, alpha_cbcr_16x8, beta_cbcr_16x8; __m128i zero = _mm_setzero_si128(); __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; __m128i temp1, temp2; pu1_HorzPixelUV = pu1_src_uv - (src_strd << 1); i16_posQ1 = src_strd; i16_posP0 = src_strd; i16_posP1 = 0; q0_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_src_uv)); q1_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_src_uv + i16_posQ1)); p1_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP1)); p0_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP0)); q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 diff = _mm_abs_epi16(diff); alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 diff = _mm_abs_epi16(diff); beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 diff = _mm_abs_epi16(diff); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); temp1 = _mm_slli_epi16(p1_uv_8x16, 1); temp2 = _mm_add_epi16(p0_uv_8x16, q1_uv_8x16); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); temp1 = _mm_add_epi16(temp1, temp2); p0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); temp1 = _mm_slli_epi16(q1_uv_8x16, 1); temp2 = _mm_add_epi16(p1_uv_8x16, q0_uv_8x16); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); temp1 = _mm_add_epi16(temp1, temp2); q0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); q0_uv_8x16 = _mm_unpackhi_epi8(q0_uv_16x8, zero); q1_uv_8x16 = _mm_unpackhi_epi8(q1_uv_16x8, zero); p1_uv_8x16 = _mm_unpackhi_epi8(p1_uv_16x8, zero); p0_uv_8x16 = _mm_unpackhi_epi8(p0_uv_16x8, zero); diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 diff = _mm_abs_epi16(diff); alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); flag2 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 diff = _mm_abs_epi16(diff); beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 diff = _mm_abs_epi16(diff); flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); temp1 = _mm_slli_epi16(p1_uv_8x16, 1); temp2 = _mm_add_epi16(p0_uv_8x16, q1_uv_8x16); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); temp1 = _mm_add_epi16(temp1, temp2); p0_uv_8x16_2 = _mm_srai_epi16(temp1, 2); temp1 = _mm_slli_epi16(q1_uv_8x16, 1); temp2 = _mm_add_epi16(p1_uv_8x16, q0_uv_8x16); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); temp1 = _mm_add_epi16(temp1, temp2); q0_uv_8x16_2 = _mm_srai_epi16(temp1, 2); p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_2); q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_2); flag1 = _mm_packs_epi16(flag1, flag2); p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); p0_uv_8x16_1 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); _mm_storeu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP0), p0_uv_8x16_1); q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); q0_uv_8x16_1 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); _mm_storeu_si128((__m128i *)(pu1_src_uv), q0_uv_8x16_1); } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bslt4_ssse3() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when the boundary strength is less than 4 */ /* in high profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab_cb - tc0_table for U */ /* pu1_cliptab_cr - tc0_table for V */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.3 under the */ /* title "Filtering process for edges for bS less than 4" */ /* in ITU T Rec H.264 with alpha and beta values different */ /* in U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_chroma_vert_bslt4_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr, UWORD32 u4_bs, const UWORD8 *pu1_cliptab_cb, const UWORD8 *pu1_cliptab_cr) { UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ UWORD8 u1_Bs0, u1_Bs1, u1_Bs2, u1_Bs3; WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; __m128i linea, lineb, linec, lined, linee, linef, lineg, lineh; __m128i temp1, temp2, temp3, temp4; __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; __m128i flag_bs, flag1, flag2; __m128i diff, diff1, alpha_cbcr_16x8, beta_cbcr_16x8, in_macro; __m128i zero = _mm_setzero_si128(); __m128i C0_uv_8x16; __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; u1_Bs0 = (u4_bs >> 24) & 0xff; u1_Bs1 = (u4_bs >> 16) & 0xff; u1_Bs2 = (u4_bs >> 8) & 0xff; u1_Bs3 = (u4_bs >> 0) & 0xff; flag_bs = _mm_set_epi8(u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs2, u1_Bs2, u1_Bs2, u1_Bs2, u1_Bs1, u1_Bs1, u1_Bs1, u1_Bs1, u1_Bs0, u1_Bs0, u1_Bs0, u1_Bs0); flag_bs = _mm_cmpeq_epi8(flag_bs, zero); //Set flag to 1s and 0s flag_bs = _mm_xor_si128(flag_bs, _mm_set1_epi8(0xFF)); //Invert for required mask /* Load and transpose the pixel values */ linea = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4)); lineb = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + src_strd)); linec = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd)); lined = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd)); linee = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 4 * src_strd)); linef = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 5 * src_strd)); lineg = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 6 * src_strd)); lineh = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 7 * src_strd)); temp1 = _mm_unpacklo_epi16(linea, lineb); temp2 = _mm_unpacklo_epi16(linec, lined); temp3 = _mm_unpacklo_epi16(linee, linef); temp4 = _mm_unpacklo_epi16(lineg, lineh); p1_uv_8x16 = _mm_unpacklo_epi32(temp1, temp2); p0_uv_8x16 = _mm_unpacklo_epi32(temp3, temp4); q0_uv_8x16 = _mm_unpackhi_epi32(temp1, temp2); q1_uv_8x16 = _mm_unpackhi_epi32(temp3, temp4); p1_uv_16x8 = _mm_unpacklo_epi64(p1_uv_8x16, p0_uv_8x16); p0_uv_16x8 = _mm_unpackhi_epi64(p1_uv_8x16, p0_uv_8x16); q0_uv_16x8 = _mm_unpacklo_epi64(q0_uv_8x16, q1_uv_8x16); q1_uv_16x8 = _mm_unpackhi_epi64(q0_uv_8x16, q1_uv_8x16); /* End of transpose */ q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 diff = _mm_abs_epi16(diff); alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 diff = _mm_abs_epi16(diff); beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 diff = _mm_abs_epi16(diff); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(q0_uv_8x16, p0_uv_8x16); diff = _mm_slli_epi16(diff, 2); diff1 = _mm_subs_epi16(p1_uv_8x16, q1_uv_8x16); diff = _mm_add_epi16(diff, diff1); diff = _mm_add_epi16(diff, _mm_set1_epi16(4)); in_macro = _mm_srai_epi16(diff, 3); C0_uv_8x16 = _mm_set_epi16(pu1_cliptab_cr[u1_Bs1], pu1_cliptab_cb[u1_Bs1], pu1_cliptab_cr[u1_Bs1], pu1_cliptab_cb[u1_Bs1], pu1_cliptab_cr[u1_Bs0], pu1_cliptab_cb[u1_Bs0], pu1_cliptab_cr[u1_Bs0], pu1_cliptab_cb[u1_Bs0]); C0_uv_8x16 = _mm_add_epi16(C0_uv_8x16, _mm_set1_epi16(1)); in_macro = _mm_min_epi16(C0_uv_8x16, in_macro); //CLIP3 C0_uv_8x16 = _mm_subs_epi16(zero, C0_uv_8x16); in_macro = _mm_max_epi16(C0_uv_8x16, in_macro); p0_uv_8x16_1 = _mm_add_epi16(p0_uv_8x16, in_macro); q0_uv_8x16_1 = _mm_sub_epi16(q0_uv_8x16, in_macro); q0_uv_8x16 = _mm_unpackhi_epi8(q0_uv_16x8, zero); q1_uv_8x16 = _mm_unpackhi_epi8(q1_uv_16x8, zero); p1_uv_8x16 = _mm_unpackhi_epi8(p1_uv_16x8, zero); p0_uv_8x16 = _mm_unpackhi_epi8(p0_uv_16x8, zero); diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 diff = _mm_abs_epi16(diff); alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); flag2 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 diff = _mm_abs_epi16(diff); beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 diff = _mm_abs_epi16(diff); flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(q0_uv_8x16, p0_uv_8x16); diff = _mm_slli_epi16(diff, 2); diff1 = _mm_subs_epi16(p1_uv_8x16, q1_uv_8x16); diff = _mm_add_epi16(diff, diff1); diff = _mm_add_epi16(diff, _mm_set1_epi16(4)); in_macro = _mm_srai_epi16(diff, 3); C0_uv_8x16 = _mm_set_epi16(pu1_cliptab_cr[u1_Bs3], pu1_cliptab_cb[u1_Bs3], pu1_cliptab_cr[u1_Bs3], pu1_cliptab_cb[u1_Bs3], pu1_cliptab_cr[u1_Bs2], pu1_cliptab_cb[u1_Bs2], pu1_cliptab_cr[u1_Bs2], pu1_cliptab_cb[u1_Bs2]); C0_uv_8x16 = _mm_add_epi16(C0_uv_8x16, _mm_set1_epi16(1)); in_macro = _mm_min_epi16(C0_uv_8x16, in_macro); //CLIP3 C0_uv_8x16 = _mm_subs_epi16(zero, C0_uv_8x16); in_macro = _mm_max_epi16(C0_uv_8x16, in_macro); p0_uv_8x16_2 = _mm_add_epi16(p0_uv_8x16, in_macro); q0_uv_8x16_2 = _mm_sub_epi16(q0_uv_8x16, in_macro); p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_2); q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_2); flag1 = _mm_packs_epi16(flag1, flag2); flag1 = _mm_and_si128(flag1, flag_bs); //Final flag (BS condition + other 3 conditions) p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); p0_uv_16x8 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); q0_uv_16x8 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); /* Inverse-transpose and store back */ temp1 = _mm_unpacklo_epi16(p1_uv_16x8, p0_uv_16x8); temp2 = _mm_unpackhi_epi16(p1_uv_16x8, p0_uv_16x8); temp3 = _mm_unpacklo_epi16(q0_uv_16x8, q1_uv_16x8); temp4 = _mm_unpackhi_epi16(q0_uv_16x8, q1_uv_16x8); linea = _mm_unpacklo_epi32(temp1, temp3); lineb = _mm_srli_si128(linea, 8); linec = _mm_unpackhi_epi32(temp1, temp3); lined = _mm_srli_si128(linec, 8); linee = _mm_unpacklo_epi32(temp2, temp4); linef = _mm_srli_si128(linee, 8); lineg = _mm_unpackhi_epi32(temp2, temp4); lineh = _mm_srli_si128(lineg, 8); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4), linea); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + src_strd), lineb); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd), linec); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd), lined); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 4 * src_strd), linee); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 5 * src_strd), linef); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 6 * src_strd), lineg); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 7 * src_strd), lineh); } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_horz_bslt4_ssse3() */ /* */ /* Description : This function performs filtering of a chroma block */ /* horizontal edge when the boundary strength is less than */ /* 4 in high profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab_cb - tc0_table for U */ /* pu1_cliptab_cr - tc0_table for V */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.3 under the */ /* title "Filtering process for edges for bS less than 4" */ /* in ITU T Rec H.264 with alpha and beta values different */ /* in U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_chroma_horz_bslt4_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr, UWORD32 u4_bs, const UWORD8 *pu1_cliptab_cb, const UWORD8 *pu1_cliptab_cr) { UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ WORD16 i16_posP1, i16_posP0, i16_posQ1; UWORD8 u1_Bs0, u1_Bs1, u1_Bs2, u1_Bs3; UWORD8 *pu1_HorzPixelUV; /*! < Pointer to the first pixel of the boundary */ WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; __m128i flag_bs, flag1, flag2; __m128i diff, diff1, alpha_cbcr_16x8, beta_cbcr_16x8, in_macro; __m128i zero = _mm_setzero_si128(); __m128i C0_uv_8x16; __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; pu1_HorzPixelUV = pu1_src_uv - (src_strd << 1); i16_posQ1 = src_strd; i16_posP0 = src_strd; i16_posP1 = 0; u1_Bs0 = (u4_bs >> 24) & 0xff; u1_Bs1 = (u4_bs >> 16) & 0xff; u1_Bs2 = (u4_bs >> 8) & 0xff; u1_Bs3 = (u4_bs >> 0) & 0xff; flag_bs = _mm_set_epi8(u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs2, u1_Bs2, u1_Bs2, u1_Bs2, u1_Bs1, u1_Bs1, u1_Bs1, u1_Bs1, u1_Bs0, u1_Bs0, u1_Bs0, u1_Bs0); flag_bs = _mm_cmpeq_epi8(flag_bs, zero); //Set flag to 1s and 0s flag_bs = _mm_xor_si128(flag_bs, _mm_set1_epi8(0xFF)); //Invert for required mask q0_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_src_uv)); q1_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_src_uv + i16_posQ1)); p1_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP1)); p0_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP0)); q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 diff = _mm_abs_epi16(diff); alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 diff = _mm_abs_epi16(diff); beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 diff = _mm_abs_epi16(diff); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(q0_uv_8x16, p0_uv_8x16); diff = _mm_slli_epi16(diff, 2); diff1 = _mm_subs_epi16(p1_uv_8x16, q1_uv_8x16); diff = _mm_add_epi16(diff, diff1); diff = _mm_add_epi16(diff, _mm_set1_epi16(4)); in_macro = _mm_srai_epi16(diff, 3); C0_uv_8x16 = _mm_set_epi16(pu1_cliptab_cr[u1_Bs1], pu1_cliptab_cb[u1_Bs1], pu1_cliptab_cr[u1_Bs1], pu1_cliptab_cb[u1_Bs1], pu1_cliptab_cr[u1_Bs0], pu1_cliptab_cb[u1_Bs0], pu1_cliptab_cr[u1_Bs0], pu1_cliptab_cb[u1_Bs0]); C0_uv_8x16 = _mm_add_epi16(C0_uv_8x16, _mm_set1_epi16(1)); in_macro = _mm_min_epi16(C0_uv_8x16, in_macro); //CLIP3 C0_uv_8x16 = _mm_subs_epi16(zero, C0_uv_8x16); in_macro = _mm_max_epi16(C0_uv_8x16, in_macro); p0_uv_8x16_1 = _mm_add_epi16(p0_uv_8x16, in_macro); q0_uv_8x16_1 = _mm_sub_epi16(q0_uv_8x16, in_macro); q0_uv_8x16 = _mm_unpackhi_epi8(q0_uv_16x8, zero); q1_uv_8x16 = _mm_unpackhi_epi8(q1_uv_16x8, zero); p1_uv_8x16 = _mm_unpackhi_epi8(p1_uv_16x8, zero); p0_uv_8x16 = _mm_unpackhi_epi8(p0_uv_16x8, zero); diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 diff = _mm_abs_epi16(diff); alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); flag2 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 diff = _mm_abs_epi16(diff); beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 diff = _mm_abs_epi16(diff); flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(q0_uv_8x16, p0_uv_8x16); diff = _mm_slli_epi16(diff, 2); diff1 = _mm_subs_epi16(p1_uv_8x16, q1_uv_8x16); diff = _mm_add_epi16(diff, diff1); diff = _mm_add_epi16(diff, _mm_set1_epi16(4)); in_macro = _mm_srai_epi16(diff, 3); C0_uv_8x16 = _mm_set_epi16(pu1_cliptab_cr[u1_Bs3], pu1_cliptab_cb[u1_Bs3], pu1_cliptab_cr[u1_Bs3], pu1_cliptab_cb[u1_Bs3], pu1_cliptab_cr[u1_Bs2], pu1_cliptab_cb[u1_Bs2], pu1_cliptab_cr[u1_Bs2], pu1_cliptab_cb[u1_Bs2]); C0_uv_8x16 = _mm_add_epi16(C0_uv_8x16, _mm_set1_epi16(1)); in_macro = _mm_min_epi16(C0_uv_8x16, in_macro); //CLIP3 C0_uv_8x16 = _mm_subs_epi16(zero, C0_uv_8x16); in_macro = _mm_max_epi16(C0_uv_8x16, in_macro); p0_uv_8x16_2 = _mm_add_epi16(p0_uv_8x16, in_macro); q0_uv_8x16_2 = _mm_sub_epi16(q0_uv_8x16, in_macro); p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_2); q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_2); flag1 = _mm_packs_epi16(flag1, flag2); flag1 = _mm_and_si128(flag1, flag_bs); //Final flag (BS condition + other 3 conditions) p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); p0_uv_8x16_1 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); _mm_storeu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP0), p0_uv_8x16_1); q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); q0_uv_8x16_1 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); _mm_storeu_si128((__m128i *)(pu1_src_uv), q0_uv_8x16_1); } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bs4_mbaff_ssse3() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when boundary strength is set to 4 in high */ /* profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab_cb - tc0_table for U */ /* pu1_cliptab_cr - tc0_table for V */ /* */ /* Globals : None */ /* */ /* Processing : When the function is called twice, this operation is as */ /* described in Sec. 8.7.2.4 under the title "Filtering */ /* process for edges for bS equal to 4" in ITU T Rec H.264 */ /* with alpha and beta values different in U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_chroma_vert_bs4_mbaff_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr) { UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; __m128i linea, lineb, linec, lined; __m128i temp1, temp2; __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; __m128i flag1; __m128i diff, alpha_cbcr_16x8, beta_cbcr_16x8; __m128i zero = _mm_setzero_si128(); __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; /* Load and transpose the pixel values */ linea = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4)); lineb = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + src_strd)); linec = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd)); lined = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd)); temp1 = _mm_unpacklo_epi16(linea, lineb); temp2 = _mm_unpacklo_epi16(linec, lined); p1_uv_16x8 = _mm_unpacklo_epi32(temp1, temp2); p0_uv_16x8 = _mm_srli_si128(p1_uv_16x8, 8); q0_uv_16x8 = _mm_unpackhi_epi32(temp1, temp2); q1_uv_16x8 = _mm_srli_si128(q0_uv_16x8, 8); /* End of transpose */ q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 diff = _mm_abs_epi16(diff); alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 diff = _mm_abs_epi16(diff); beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 diff = _mm_abs_epi16(diff); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); temp1 = _mm_slli_epi16(p1_uv_8x16, 1); temp2 = _mm_add_epi16(p0_uv_8x16, q1_uv_8x16); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); temp1 = _mm_add_epi16(temp1, temp2); p0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); temp1 = _mm_slli_epi16(q1_uv_8x16, 1); temp2 = _mm_add_epi16(p1_uv_8x16, q0_uv_8x16); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); temp1 = _mm_add_epi16(temp1, temp2); q0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_1); q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_1); flag1 = _mm_packs_epi16(flag1, flag1); p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); p0_uv_16x8 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); q0_uv_16x8 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); /* Inverse-transpose and store back */ temp1 = _mm_unpacklo_epi16(p1_uv_16x8, p0_uv_16x8); temp2 = _mm_unpacklo_epi16(q0_uv_16x8, q1_uv_16x8); linea = _mm_unpacklo_epi32(temp1, temp2); lineb = _mm_srli_si128(linea, 8); linec = _mm_unpackhi_epi32(temp1, temp2); lined = _mm_srli_si128(linec, 8); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4), linea); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + src_strd), lineb); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd), linec); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd), lined); } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_chroma_vert_bslt4_mbaff_ssse3() */ /* */ /* Description : This function performs filtering of a chroma block */ /* vertical edge when boundary strength is less than 4 in */ /* high profile. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 of U */ /* src_strd - source stride */ /* alpha_cb - alpha value for the boundary in U */ /* beta_cb - beta value for the boundary in U */ /* alpha_cr - alpha value for the boundary in V */ /* beta_cr - beta value for the boundary in V */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab_cb - tc0_table for U */ /* pu1_cliptab_cr - tc0_table for V */ /* */ /* Globals : None */ /* */ /* Processing : When the function is called twice, this operation is as */ /* described in Sec. 8.7.2.4 under the title "Filtering */ /* process for edges for bS less than 4" in ITU T Rec H.264 */ /* with alpha and beta values different in U and V. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_chroma_vert_bslt4_mbaff_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha_cb, WORD32 beta_cb, WORD32 alpha_cr, WORD32 beta_cr, UWORD32 u4_bs, const UWORD8 *pu1_cliptab_cb, const UWORD8 *pu1_cliptab_cr) { UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ UWORD8 u1_Bs0, u1_Bs1, u1_Bs2, u1_Bs3; WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; __m128i linea, lineb, linec, lined; __m128i temp1, temp2; __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; __m128i flag_bs, flag1; __m128i diff, diff1, alpha_cbcr_16x8, beta_cbcr_16x8, in_macro; __m128i zero = _mm_setzero_si128(); __m128i C0_uv_8x16; __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; u1_Bs0 = (u4_bs >> 24) & 0xff; u1_Bs1 = (u4_bs >> 16) & 0xff; u1_Bs2 = (u4_bs >> 8) & 0xff; u1_Bs3 = (u4_bs >> 0) & 0xff; flag_bs = _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, u1_Bs3, u1_Bs3, u1_Bs2, u1_Bs2, u1_Bs1, u1_Bs1, u1_Bs0, u1_Bs0); flag_bs = _mm_cmpeq_epi8(flag_bs, zero); //Set flag to 1s and 0s flag_bs = _mm_xor_si128(flag_bs, _mm_set1_epi8(0xFF)); //Invert for required mask /* Load and transpose the pixel values */ linea = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4)); lineb = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + src_strd)); linec = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd)); lined = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd)); temp1 = _mm_unpacklo_epi16(linea, lineb); temp2 = _mm_unpacklo_epi16(linec, lined); p1_uv_16x8 = _mm_unpacklo_epi32(temp1, temp2); p0_uv_16x8 = _mm_srli_si128(p1_uv_16x8, 8); q0_uv_16x8 = _mm_unpackhi_epi32(temp1, temp2); q1_uv_16x8 = _mm_srli_si128(q0_uv_16x8, 8); /* End of transpose */ q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 diff = _mm_abs_epi16(diff); alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 diff = _mm_abs_epi16(diff); beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 diff = _mm_abs_epi16(diff); flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); diff = _mm_subs_epi16(q0_uv_8x16, p0_uv_8x16); diff = _mm_slli_epi16(diff, 2); diff1 = _mm_subs_epi16(p1_uv_8x16, q1_uv_8x16); diff = _mm_add_epi16(diff, diff1); diff = _mm_add_epi16(diff, _mm_set1_epi16(4)); in_macro = _mm_srai_epi16(diff, 3); C0_uv_8x16 = _mm_set_epi16(pu1_cliptab_cr[u1_Bs3], pu1_cliptab_cb[u1_Bs3], pu1_cliptab_cr[u1_Bs2], pu1_cliptab_cb[u1_Bs2], pu1_cliptab_cr[u1_Bs1], pu1_cliptab_cb[u1_Bs1], pu1_cliptab_cr[u1_Bs0], pu1_cliptab_cb[u1_Bs0]); C0_uv_8x16 = _mm_add_epi16(C0_uv_8x16, _mm_set1_epi16(1)); in_macro = _mm_min_epi16(C0_uv_8x16, in_macro); //CLIP3 C0_uv_8x16 = _mm_subs_epi16(zero, C0_uv_8x16); in_macro = _mm_max_epi16(C0_uv_8x16, in_macro); p0_uv_8x16_1 = _mm_add_epi16(p0_uv_8x16, in_macro); q0_uv_8x16_1 = _mm_sub_epi16(q0_uv_8x16, in_macro); p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_1); q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_1); flag1 = _mm_packs_epi16(flag1, flag1); flag1 = _mm_and_si128(flag1, flag_bs); //Final flag (BS condition + other 3 conditions) p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); p0_uv_16x8 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); q0_uv_16x8 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); /* Inverse-transpose and store back */ temp1 = _mm_unpacklo_epi16(p1_uv_16x8, p0_uv_16x8); temp2 = _mm_unpacklo_epi16(q0_uv_16x8, q1_uv_16x8); linea = _mm_unpacklo_epi32(temp1, temp2); lineb = _mm_srli_si128(linea, 8); linec = _mm_unpackhi_epi32(temp1, temp2); lined = _mm_srli_si128(linec, 8); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4), linea); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + src_strd), lineb); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd), linec); _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd), lined); } ================================================ FILE: dependencies/ih264d/common/x86/ih264_deblk_luma_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264_deblk_luma_ssse3.c */ /* */ /* Description : Contains function definitions for deblocking */ /* */ /* List of Functions : ih264_deblk_luma_vert_bs4_ssse3() */ /* ih264_deblk_luma_horz_bs4_ssse3() */ /* ih264_deblk_luma_vert_bslt4_ssse3() */ /* ih264_deblk_luma_horz_bslt4_ssse3() */ /* ih264_deblk_luma_vert_bs4_mbaff_ssse3() */ /* ih264_deblk_luma_vert_bslt4_mbaff_ssse3() */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Added luma deblocking ssse3 */ /* intrinsics */ /* */ /*****************************************************************************/ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include <stdio.h> /* User include files */ #include "ih264_typedefs.h" #include "ih264_platform_macros.h" #include "ih264_deblk_edge_filters.h" #include "ih264_macros.h" #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /*****************************************************************************/ /* Function Definitions */ /*****************************************************************************/ /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_vert_bs4_ssse3() */ /* */ /* Description : This function performs filtering of a luma block */ /* vertical edge when the boundary strength is set to 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.4 under the */ /* title "Filtering process for edges for bS equal to 4" in */ /* ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_luma_vert_bs4_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta) { __m128i zero = _mm_setzero_si128(); __m128i q0_16x8, q1_16x8, q2_16x8, q3_16x8; __m128i p0_16x8, p1_16x8, p2_16x8, p3_16x8; __m128i q0_8x16, q1_8x16, q2_8x16, q3_8x16; __m128i p0_8x16, p1_8x16, p2_8x16, p3_8x16; __m128i q0_16x8_1; __m128i p0_16x8_1; __m128i q0_16x8_2, q1_16x8_2, q2_16x8_2; __m128i p0_16x8_2, p1_16x8_2, p2_16x8_2; __m128i temp1, temp2, temp3, temp4, temp5, temp6; __m128i Alpha_8x16, Beta_8x16; __m128i flag1_16x8, flag2_16x8, flag3_16x8, flag4_16x8; __m128i const_val2_16x8 = _mm_set1_epi16(2); __m128i line1, line2, line3, line4, line5, line6, line7, line8; Alpha_8x16 = _mm_set1_epi16(alpha); Beta_8x16 = _mm_set1_epi16(beta); line1 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd)); line2 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd)); line3 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd)); line4 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd)); line5 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd)); line6 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd)); line7 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd)); line8 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd)); temp1 = _mm_unpacklo_epi8(line1, line2); temp2 = _mm_unpacklo_epi8(line3, line4); temp3 = _mm_unpacklo_epi8(line5, line6); temp4 = _mm_unpacklo_epi8(line7, line8); line1 = _mm_unpacklo_epi16(temp1, temp2); line2 = _mm_unpackhi_epi16(temp1, temp2); line3 = _mm_unpacklo_epi16(temp3, temp4); line4 = _mm_unpackhi_epi16(temp3, temp4); p1_8x16 = _mm_unpacklo_epi32(line1, line3); p0_8x16 = _mm_unpackhi_epi32(line1, line3); q0_8x16 = _mm_unpacklo_epi32(line2, line4); q1_8x16 = _mm_unpackhi_epi32(line2, line4); line1 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 8 * src_strd)); line2 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 9 * src_strd)); line3 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 10 * src_strd)); line4 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 11 * src_strd)); line5 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 12 * src_strd)); line6 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 13 * src_strd)); line7 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 14 * src_strd)); line8 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 15 * src_strd)); temp1 = _mm_unpacklo_epi8(line1, line2); temp2 = _mm_unpacklo_epi8(line3, line4); temp3 = _mm_unpacklo_epi8(line5, line6); temp4 = _mm_unpacklo_epi8(line7, line8); line1 = _mm_unpacklo_epi16(temp1, temp2); line2 = _mm_unpackhi_epi16(temp1, temp2); line3 = _mm_unpacklo_epi16(temp3, temp4); line4 = _mm_unpackhi_epi16(temp3, temp4); temp1 = _mm_unpacklo_epi32(line1, line3); temp2 = _mm_unpackhi_epi32(line1, line3); temp3 = _mm_unpacklo_epi32(line2, line4); temp4 = _mm_unpackhi_epi32(line2, line4); p3_16x8 = _mm_unpacklo_epi64(p1_8x16, temp1); p2_16x8 = _mm_unpackhi_epi64(p1_8x16, temp1); q2_16x8 = _mm_unpacklo_epi64(q1_8x16, temp4); q3_16x8 = _mm_unpackhi_epi64(q1_8x16, temp4); p1_16x8 = _mm_unpacklo_epi64(p0_8x16, temp2); p0_16x8 = _mm_unpackhi_epi64(p0_8x16, temp2); q0_16x8 = _mm_unpacklo_epi64(q0_8x16, temp3); q1_16x8 = _mm_unpackhi_epi64(q0_8x16, temp3); //Cond1 (ABS(p0 - q0) < alpha) temp1 = _mm_subs_epu8(q0_16x8, p0_16x8); temp2 = _mm_subs_epu8(p0_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); flag1_16x8 = _mm_packs_epi16(temp2, temp1); //Cond2 (ABS(q1 - q0) < beta) temp1 = _mm_subs_epu8(q0_16x8, q1_16x8); temp2 = _mm_subs_epu8(q1_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); //Cond3 (ABS(p1 - p0) < beta) temp1 = _mm_subs_epu8(p0_16x8, p1_16x8); temp2 = _mm_subs_epu8(p1_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); // !((ABS(p0 - q0) < alpha) || (ABS(q1 - q0) < beta) || (ABS(p1 - p0) < beta)) flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); // (ABS(p0 - q0) < ((alpha >> 2) + 2)) temp1 = _mm_subs_epu8(p0_16x8, q0_16x8); temp2 = _mm_subs_epu8(q0_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); Alpha_8x16 = _mm_srai_epi16(Alpha_8x16, 2); Alpha_8x16 = _mm_add_epi16(Alpha_8x16, const_val2_16x8); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); flag2_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); // (ABS(p2 - p0) < beta) temp1 = _mm_subs_epu8(p0_16x8, p2_16x8); temp2 = _mm_subs_epu8(p2_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag3_16x8 = _mm_packs_epi16(temp2, temp1); flag3_16x8 = _mm_and_si128(flag3_16x8, flag2_16x8); // (ABS(q2 - q0) < beta) temp1 = _mm_subs_epu8(q0_16x8, q2_16x8); temp2 = _mm_subs_epu8(q2_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag4_16x8 = _mm_packs_epi16(temp2, temp1); flag4_16x8 = _mm_and_si128(flag4_16x8, flag2_16x8); // First 8 pixels p3_8x16 = _mm_unpacklo_epi8(p3_16x8, zero); p2_8x16 = _mm_unpacklo_epi8(p2_16x8, zero); p1_8x16 = _mm_unpacklo_epi8(p1_16x8, zero); p0_8x16 = _mm_unpacklo_epi8(p0_16x8, zero); q0_8x16 = _mm_unpacklo_epi8(q0_16x8, zero); q1_8x16 = _mm_unpacklo_epi8(q1_16x8, zero); q2_8x16 = _mm_unpacklo_epi8(q2_16x8, zero); q3_8x16 = _mm_unpacklo_epi8(q3_16x8, zero); // p0_1 and q0_1 temp1 = _mm_add_epi16(p0_8x16, q1_8x16); temp2 = _mm_add_epi16(p1_8x16, q0_8x16); temp5 = _mm_add_epi16(temp1, const_val2_16x8); temp6 = _mm_add_epi16(temp2, const_val2_16x8); temp3 = _mm_slli_epi16(p1_8x16, 1); temp4 = _mm_slli_epi16(q1_8x16, 1); temp1 = _mm_add_epi16(temp5, temp3); temp2 = _mm_add_epi16(temp6, temp4); p0_16x8_1 = _mm_srai_epi16(temp1, 2); q0_16x8_1 = _mm_srai_epi16(temp2, 2); // p1_2 and q1_2 temp6 = _mm_add_epi16(temp6, p0_8x16); temp5 = _mm_add_epi16(temp5, q0_8x16); temp1 = _mm_add_epi16(temp6, p2_8x16); temp2 = _mm_add_epi16(temp5, q2_8x16); p1_16x8_2 = _mm_srai_epi16(temp1, 2); q1_16x8_2 = _mm_srai_epi16(temp2, 2); // p0_2 and q0_2 temp1 = _mm_add_epi16(temp3, p2_8x16); temp2 = _mm_add_epi16(temp4, q2_8x16); temp1 = _mm_add_epi16(temp1, q1_8x16); temp2 = _mm_add_epi16(temp2, p1_8x16); temp3 = _mm_add_epi16(p0_8x16, q0_8x16); temp3 = _mm_slli_epi16(temp3, 1); temp1 = _mm_add_epi16(temp1, temp3); temp2 = _mm_add_epi16(temp2, temp3); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(4)); temp2 = _mm_add_epi16(temp2, _mm_set1_epi16(4)); p0_16x8_2 = _mm_srai_epi16(temp1, 3); q0_16x8_2 = _mm_srai_epi16(temp2, 3); // p2_2 and q2_2 temp1 = _mm_add_epi16(temp6, const_val2_16x8); temp2 = _mm_add_epi16(temp5, const_val2_16x8); temp3 = _mm_slli_epi16(p2_8x16, 1); temp4 = _mm_slli_epi16(q2_8x16, 1); temp3 = _mm_add_epi16(p2_8x16, temp3); temp4 = _mm_add_epi16(q2_8x16, temp4); temp5 = _mm_slli_epi16(p3_8x16, 1); temp6 = _mm_slli_epi16(q3_8x16, 1); temp1 = _mm_add_epi16(temp1, temp3); temp2 = _mm_add_epi16(temp2, temp4); temp1 = _mm_add_epi16(temp1, temp5); temp2 = _mm_add_epi16(temp2, temp6); p2_16x8_2 = _mm_srai_epi16(temp1, 3); q2_16x8_2 = _mm_srai_epi16(temp2, 3); // Second 8 pixels and packing with first 8 pixels p3_8x16 = _mm_unpackhi_epi8(p3_16x8, zero); p2_8x16 = _mm_unpackhi_epi8(p2_16x8, zero); p1_8x16 = _mm_unpackhi_epi8(p1_16x8, zero); p0_8x16 = _mm_unpackhi_epi8(p0_16x8, zero); q0_8x16 = _mm_unpackhi_epi8(q0_16x8, zero); q1_8x16 = _mm_unpackhi_epi8(q1_16x8, zero); q2_8x16 = _mm_unpackhi_epi8(q2_16x8, zero); q3_8x16 = _mm_unpackhi_epi8(q3_16x8, zero); // p0_1 and q0_1 temp1 = _mm_add_epi16(p0_8x16, q1_8x16); temp2 = _mm_add_epi16(p1_8x16, q0_8x16); temp5 = _mm_add_epi16(temp1, const_val2_16x8); temp6 = _mm_add_epi16(temp2, const_val2_16x8); temp3 = _mm_slli_epi16(p1_8x16, 1); temp4 = _mm_slli_epi16(q1_8x16, 1); temp1 = _mm_add_epi16(temp5, temp3); temp2 = _mm_add_epi16(temp6, temp4); temp1 = _mm_srai_epi16(temp1, 2); temp2 = _mm_srai_epi16(temp2, 2); p0_16x8_1 = _mm_packus_epi16(p0_16x8_1, temp1); q0_16x8_1 = _mm_packus_epi16(q0_16x8_1, temp2); // p1_2 and q1_2 temp6 = _mm_add_epi16(temp6, p0_8x16); temp5 = _mm_add_epi16(temp5, q0_8x16); temp1 = _mm_add_epi16(temp6, p2_8x16); temp2 = _mm_add_epi16(temp5, q2_8x16); temp1 = _mm_srai_epi16(temp1, 2); temp2 = _mm_srai_epi16(temp2, 2); p1_16x8_2 = _mm_packus_epi16(p1_16x8_2, temp1); q1_16x8_2 = _mm_packus_epi16(q1_16x8_2, temp2); // p0_2 and q0_2 temp1 = _mm_add_epi16(temp3, p2_8x16); temp2 = _mm_add_epi16(temp4, q2_8x16); temp1 = _mm_add_epi16(temp1, q1_8x16); temp2 = _mm_add_epi16(temp2, p1_8x16); temp3 = _mm_add_epi16(p0_8x16, q0_8x16); temp3 = _mm_slli_epi16(temp3, 1); temp1 = _mm_add_epi16(temp1, temp3); temp2 = _mm_add_epi16(temp2, temp3); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(4)); temp2 = _mm_add_epi16(temp2, _mm_set1_epi16(4)); temp1 = _mm_srai_epi16(temp1, 3); temp2 = _mm_srai_epi16(temp2, 3); p0_16x8_2 = _mm_packus_epi16(p0_16x8_2, temp1); q0_16x8_2 = _mm_packus_epi16(q0_16x8_2, temp2); // p2_2 and q2_2 temp1 = _mm_add_epi16(temp6, const_val2_16x8); temp2 = _mm_add_epi16(temp5, const_val2_16x8); temp3 = _mm_slli_epi16(p2_8x16, 1); temp4 = _mm_slli_epi16(q2_8x16, 1); temp3 = _mm_add_epi16(p2_8x16, temp3); temp4 = _mm_add_epi16(q2_8x16, temp4); temp5 = _mm_slli_epi16(p3_8x16, 1); temp6 = _mm_slli_epi16(q3_8x16, 1); temp1 = _mm_add_epi16(temp1, temp3); temp2 = _mm_add_epi16(temp2, temp4); temp1 = _mm_add_epi16(temp1, temp5); temp2 = _mm_add_epi16(temp2, temp6); temp1 = _mm_srai_epi16(temp1, 3); temp2 = _mm_srai_epi16(temp2, 3); p2_16x8_2 = _mm_packus_epi16(p2_16x8_2, temp1); q2_16x8_2 = _mm_packus_epi16(q2_16x8_2, temp2); // p0 and q0 p0_16x8 = _mm_and_si128(p0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); p0_16x8_1 = _mm_and_si128(p0_16x8_1, flag1_16x8); p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_1); q0_16x8 = _mm_and_si128(q0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); q0_16x8_1 = _mm_and_si128(q0_16x8_1, flag1_16x8); q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_1); // p0 and q0 p0_16x8 = _mm_and_si128(p0_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); p0_16x8_2 = _mm_and_si128(p0_16x8_2, flag3_16x8); p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_2); q0_16x8 = _mm_and_si128(q0_16x8, _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); q0_16x8_2 = _mm_and_si128(q0_16x8_2, flag4_16x8); q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_2); // p1 and q1 p1_16x8 = _mm_and_si128(p1_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); p1_16x8_2 = _mm_and_si128(p1_16x8_2, flag3_16x8); p1_16x8 = _mm_add_epi8(p1_16x8, p1_16x8_2); q1_16x8 = _mm_and_si128(q1_16x8, _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); q1_16x8_2 = _mm_and_si128(q1_16x8_2, flag4_16x8); q1_16x8 = _mm_add_epi8(q1_16x8, q1_16x8_2); // p2 and q2 p2_16x8 = _mm_and_si128(p2_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); p2_16x8_2 = _mm_and_si128(p2_16x8_2, flag3_16x8); p2_16x8 = _mm_add_epi8(p2_16x8, p2_16x8_2); q2_16x8 = _mm_and_si128(q2_16x8, _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); q2_16x8_2 = _mm_and_si128(q2_16x8_2, flag4_16x8); q2_16x8 = _mm_add_epi8(q2_16x8, q2_16x8_2); temp1 = _mm_unpacklo_epi8(p3_16x8, p2_16x8); temp2 = _mm_unpacklo_epi8(p1_16x8, p0_16x8); temp3 = _mm_unpacklo_epi8(q0_16x8, q1_16x8); temp4 = _mm_unpacklo_epi8(q2_16x8, q3_16x8); p3_8x16 = _mm_unpacklo_epi16(temp1, temp2); p2_8x16 = _mm_unpackhi_epi16(temp1, temp2); q2_8x16 = _mm_unpacklo_epi16(temp3, temp4); q3_8x16 = _mm_unpackhi_epi16(temp3, temp4); line1 = _mm_unpacklo_epi32(p3_8x16, q2_8x16); line2 = _mm_srli_si128(line1, 8); line3 = _mm_unpackhi_epi32(p3_8x16, q2_8x16); line4 = _mm_srli_si128(line3, 8); line5 = _mm_unpacklo_epi32(p2_8x16, q3_8x16); line6 = _mm_srli_si128(line5, 8); line7 = _mm_unpackhi_epi32(p2_8x16, q3_8x16); line8 = _mm_srli_si128(line7, 8); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd), line1); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd), line2); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd), line3); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd), line4); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd), line5); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd), line6); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd), line7); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd), line8); temp1 = _mm_unpackhi_epi8(p3_16x8, p2_16x8); temp2 = _mm_unpackhi_epi8(p1_16x8, p0_16x8); temp3 = _mm_unpackhi_epi8(q0_16x8, q1_16x8); temp4 = _mm_unpackhi_epi8(q2_16x8, q3_16x8); p3_8x16 = _mm_unpacklo_epi16(temp1, temp2); p2_8x16 = _mm_unpackhi_epi16(temp1, temp2); q2_8x16 = _mm_unpacklo_epi16(temp3, temp4); q3_8x16 = _mm_unpackhi_epi16(temp3, temp4); line1 = _mm_unpacklo_epi32(p3_8x16, q2_8x16); line2 = _mm_srli_si128(line1, 8); line3 = _mm_unpackhi_epi32(p3_8x16, q2_8x16); line4 = _mm_srli_si128(line3, 8); line5 = _mm_unpacklo_epi32(p2_8x16, q3_8x16); line6 = _mm_srli_si128(line5, 8); line7 = _mm_unpackhi_epi32(p2_8x16, q3_8x16); line8 = _mm_srli_si128(line7, 8); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 8 * src_strd), line1); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 9 * src_strd), line2); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 10 * src_strd), line3); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 11 * src_strd), line4); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 12 * src_strd), line5); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 13 * src_strd), line6); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 14 * src_strd), line7); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 15 * src_strd), line8); } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_horz_bs4_ssse3() */ /* */ /* Description : This function performs filtering of a luma block */ /* horizontal edge when the boundary strength is set to 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.4 under the */ /* title "Filtering process for edges for bS equal to 4" in */ /* ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_luma_horz_bs4_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta) { WORD16 i16_posP3, i16_posP2, i16_posP1, i16_posP0; WORD16 i16_posQ1, i16_posQ2, i16_posQ3; UWORD8 *pu1_HorzPixel; __m128i zero = _mm_setzero_si128(); __m128i q0_16x8, q1_16x8, q2_16x8, q3_16x8; __m128i p0_16x8, p1_16x8, p2_16x8, p3_16x8; __m128i q0_8x16, q1_8x16, q2_8x16, q3_8x16; __m128i p0_8x16, p1_8x16, p2_8x16, p3_8x16; __m128i q0_16x8_1; __m128i p0_16x8_1; __m128i q0_16x8_2, q1_16x8_2, q2_16x8_2; __m128i p0_16x8_2, p1_16x8_2, p2_16x8_2; __m128i temp1, temp2, temp3, temp4, temp5, temp6; __m128i Alpha_8x16, Beta_8x16; __m128i flag1_16x8, flag2_16x8, flag3_16x8, flag4_16x8; __m128i const_val2_16x8 = _mm_set1_epi16(2); pu1_HorzPixel = pu1_src - (src_strd << 2); i16_posQ1 = src_strd; i16_posQ2 = X2(src_strd); i16_posQ3 = X3(src_strd); i16_posP0 = X3(src_strd); i16_posP1 = X2(src_strd); i16_posP2 = src_strd; i16_posP3 = 0; Alpha_8x16 = _mm_set1_epi16(alpha); Beta_8x16 = _mm_set1_epi16(beta); p3_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP3)); p2_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP2)); p1_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP1)); p0_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP0)); q0_16x8 = _mm_loadu_si128((__m128i *)(pu1_src)); q1_16x8 = _mm_loadu_si128((__m128i *)(pu1_src + i16_posQ1)); q2_16x8 = _mm_loadu_si128((__m128i *)(pu1_src + i16_posQ2)); q3_16x8 = _mm_loadu_si128((__m128i *)(pu1_src + i16_posQ3)); //Cond1 (ABS(p0 - q0) < alpha) temp1 = _mm_subs_epu8(q0_16x8, p0_16x8); temp2 = _mm_subs_epu8(p0_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); flag1_16x8 = _mm_packs_epi16(temp2, temp1); //Cond2 (ABS(q1 - q0) < beta) temp1 = _mm_subs_epu8(q0_16x8, q1_16x8); temp2 = _mm_subs_epu8(q1_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); //Cond3 (ABS(p1 - p0) < beta) temp1 = _mm_subs_epu8(p0_16x8, p1_16x8); temp2 = _mm_subs_epu8(p1_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); // !((ABS(p0 - q0) < alpha) || (ABS(q1 - q0) < beta) || (ABS(p1 - p0) < beta)) flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); // (ABS(p0 - q0) < ((alpha >> 2) + 2)) temp1 = _mm_subs_epu8(p0_16x8, q0_16x8); temp2 = _mm_subs_epu8(q0_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); Alpha_8x16 = _mm_srai_epi16(Alpha_8x16, 2); Alpha_8x16 = _mm_add_epi16(Alpha_8x16, const_val2_16x8); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); flag2_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); // (ABS(p2 - p0) < beta) temp1 = _mm_subs_epu8(p0_16x8, p2_16x8); temp2 = _mm_subs_epu8(p2_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag3_16x8 = _mm_packs_epi16(temp2, temp1); flag3_16x8 = _mm_and_si128(flag3_16x8, flag2_16x8); // (ABS(q2 - q0) < beta) temp1 = _mm_subs_epu8(q0_16x8, q2_16x8); temp2 = _mm_subs_epu8(q2_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag4_16x8 = _mm_packs_epi16(temp2, temp1); flag4_16x8 = _mm_and_si128(flag4_16x8, flag2_16x8); // First 8 pixels p3_8x16 = _mm_unpacklo_epi8(p3_16x8, zero); p2_8x16 = _mm_unpacklo_epi8(p2_16x8, zero); p1_8x16 = _mm_unpacklo_epi8(p1_16x8, zero); p0_8x16 = _mm_unpacklo_epi8(p0_16x8, zero); q0_8x16 = _mm_unpacklo_epi8(q0_16x8, zero); q1_8x16 = _mm_unpacklo_epi8(q1_16x8, zero); q2_8x16 = _mm_unpacklo_epi8(q2_16x8, zero); q3_8x16 = _mm_unpacklo_epi8(q3_16x8, zero); // p0_1 and q0_1 temp1 = _mm_add_epi16(p0_8x16, q1_8x16); temp2 = _mm_add_epi16(p1_8x16, q0_8x16); temp5 = _mm_add_epi16(temp1, const_val2_16x8); temp6 = _mm_add_epi16(temp2, const_val2_16x8); temp3 = _mm_slli_epi16(p1_8x16, 1); temp4 = _mm_slli_epi16(q1_8x16, 1); temp1 = _mm_add_epi16(temp5, temp3); temp2 = _mm_add_epi16(temp6, temp4); p0_16x8_1 = _mm_srai_epi16(temp1, 2); q0_16x8_1 = _mm_srai_epi16(temp2, 2); // p1_2 and q1_2 temp6 = _mm_add_epi16(temp6, p0_8x16); temp5 = _mm_add_epi16(temp5, q0_8x16); temp1 = _mm_add_epi16(temp6, p2_8x16); temp2 = _mm_add_epi16(temp5, q2_8x16); p1_16x8_2 = _mm_srai_epi16(temp1, 2); q1_16x8_2 = _mm_srai_epi16(temp2, 2); // p0_2 and q0_2 temp1 = _mm_add_epi16(temp3, p2_8x16); temp2 = _mm_add_epi16(temp4, q2_8x16); temp1 = _mm_add_epi16(temp1, q1_8x16); temp2 = _mm_add_epi16(temp2, p1_8x16); temp3 = _mm_add_epi16(p0_8x16, q0_8x16); temp3 = _mm_slli_epi16(temp3, 1); temp1 = _mm_add_epi16(temp1, temp3); temp2 = _mm_add_epi16(temp2, temp3); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(4)); temp2 = _mm_add_epi16(temp2, _mm_set1_epi16(4)); p0_16x8_2 = _mm_srai_epi16(temp1, 3); q0_16x8_2 = _mm_srai_epi16(temp2, 3); // p2_2 and q2_2 temp1 = _mm_add_epi16(temp6, const_val2_16x8); temp2 = _mm_add_epi16(temp5, const_val2_16x8); temp3 = _mm_slli_epi16(p2_8x16, 1); temp4 = _mm_slli_epi16(q2_8x16, 1); temp3 = _mm_add_epi16(p2_8x16, temp3); temp4 = _mm_add_epi16(q2_8x16, temp4); temp5 = _mm_slli_epi16(p3_8x16, 1); temp6 = _mm_slli_epi16(q3_8x16, 1); temp1 = _mm_add_epi16(temp1, temp3); temp2 = _mm_add_epi16(temp2, temp4); temp1 = _mm_add_epi16(temp1, temp5); temp2 = _mm_add_epi16(temp2, temp6); p2_16x8_2 = _mm_srai_epi16(temp1, 3); q2_16x8_2 = _mm_srai_epi16(temp2, 3); // Second 8 pixels and packing with first 8 pixels p3_8x16 = _mm_unpackhi_epi8(p3_16x8, zero); p2_8x16 = _mm_unpackhi_epi8(p2_16x8, zero); p1_8x16 = _mm_unpackhi_epi8(p1_16x8, zero); p0_8x16 = _mm_unpackhi_epi8(p0_16x8, zero); q0_8x16 = _mm_unpackhi_epi8(q0_16x8, zero); q1_8x16 = _mm_unpackhi_epi8(q1_16x8, zero); q2_8x16 = _mm_unpackhi_epi8(q2_16x8, zero); q3_8x16 = _mm_unpackhi_epi8(q3_16x8, zero); // p0_1 and q0_1 temp1 = _mm_add_epi16(p0_8x16, q1_8x16); temp2 = _mm_add_epi16(p1_8x16, q0_8x16); temp5 = _mm_add_epi16(temp1, const_val2_16x8); temp6 = _mm_add_epi16(temp2, const_val2_16x8); temp3 = _mm_slli_epi16(p1_8x16, 1); temp4 = _mm_slli_epi16(q1_8x16, 1); temp1 = _mm_add_epi16(temp5, temp3); temp2 = _mm_add_epi16(temp6, temp4); temp1 = _mm_srai_epi16(temp1, 2); temp2 = _mm_srai_epi16(temp2, 2); p0_16x8_1 = _mm_packus_epi16(p0_16x8_1, temp1); q0_16x8_1 = _mm_packus_epi16(q0_16x8_1, temp2); // p1_2 and q1_2 temp6 = _mm_add_epi16(temp6, p0_8x16); temp5 = _mm_add_epi16(temp5, q0_8x16); temp1 = _mm_add_epi16(temp6, p2_8x16); temp2 = _mm_add_epi16(temp5, q2_8x16); temp1 = _mm_srai_epi16(temp1, 2); temp2 = _mm_srai_epi16(temp2, 2); p1_16x8_2 = _mm_packus_epi16(p1_16x8_2, temp1); q1_16x8_2 = _mm_packus_epi16(q1_16x8_2, temp2); // p0_2 and q0_2 temp1 = _mm_add_epi16(temp3, p2_8x16); temp2 = _mm_add_epi16(temp4, q2_8x16); temp1 = _mm_add_epi16(temp1, q1_8x16); temp2 = _mm_add_epi16(temp2, p1_8x16); temp3 = _mm_add_epi16(p0_8x16, q0_8x16); temp3 = _mm_slli_epi16(temp3, 1); temp1 = _mm_add_epi16(temp1, temp3); temp2 = _mm_add_epi16(temp2, temp3); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(4)); temp2 = _mm_add_epi16(temp2, _mm_set1_epi16(4)); temp1 = _mm_srai_epi16(temp1, 3); temp2 = _mm_srai_epi16(temp2, 3); p0_16x8_2 = _mm_packus_epi16(p0_16x8_2, temp1); q0_16x8_2 = _mm_packus_epi16(q0_16x8_2, temp2); // p2_2 and q2_2 temp1 = _mm_add_epi16(temp6, const_val2_16x8); temp2 = _mm_add_epi16(temp5, const_val2_16x8); temp3 = _mm_slli_epi16(p2_8x16, 1); temp4 = _mm_slli_epi16(q2_8x16, 1); temp3 = _mm_add_epi16(p2_8x16, temp3); temp4 = _mm_add_epi16(q2_8x16, temp4); temp5 = _mm_slli_epi16(p3_8x16, 1); temp6 = _mm_slli_epi16(q3_8x16, 1); temp1 = _mm_add_epi16(temp1, temp3); temp2 = _mm_add_epi16(temp2, temp4); temp1 = _mm_add_epi16(temp1, temp5); temp2 = _mm_add_epi16(temp2, temp6); temp1 = _mm_srai_epi16(temp1, 3); temp2 = _mm_srai_epi16(temp2, 3); p2_16x8_2 = _mm_packus_epi16(p2_16x8_2, temp1); q2_16x8_2 = _mm_packus_epi16(q2_16x8_2, temp2); // p0 and q0 p0_16x8 = _mm_and_si128(p0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); p0_16x8_1 = _mm_and_si128(p0_16x8_1, flag1_16x8); p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_1); q0_16x8 = _mm_and_si128(q0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); q0_16x8_1 = _mm_and_si128(q0_16x8_1, flag1_16x8); q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_1); // p0 and q0 p0_16x8 = _mm_and_si128(p0_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); p0_16x8_2 = _mm_and_si128(p0_16x8_2, flag3_16x8); p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_2); q0_16x8 = _mm_and_si128(q0_16x8, _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); q0_16x8_2 = _mm_and_si128(q0_16x8_2, flag4_16x8); q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_2); // p1 and q1 p1_16x8 = _mm_and_si128(p1_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); p1_16x8_2 = _mm_and_si128(p1_16x8_2, flag3_16x8); p1_16x8 = _mm_add_epi8(p1_16x8, p1_16x8_2); q1_16x8 = _mm_and_si128(q1_16x8, _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); q1_16x8_2 = _mm_and_si128(q1_16x8_2, flag4_16x8); q1_16x8 = _mm_add_epi8(q1_16x8, q1_16x8_2); // p2 and q2 p2_16x8 = _mm_and_si128(p2_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); p2_16x8_2 = _mm_and_si128(p2_16x8_2, flag3_16x8); p2_16x8 = _mm_add_epi8(p2_16x8, p2_16x8_2); q2_16x8 = _mm_and_si128(q2_16x8, _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); q2_16x8_2 = _mm_and_si128(q2_16x8_2, flag4_16x8); q2_16x8 = _mm_add_epi8(q2_16x8, q2_16x8_2); _mm_storeu_si128((__m128i *)(pu1_HorzPixel + i16_posP2), p2_16x8); _mm_storeu_si128((__m128i *)(pu1_HorzPixel + i16_posP1), p1_16x8); _mm_storeu_si128((__m128i *)(pu1_HorzPixel + i16_posP0), p0_16x8); _mm_storeu_si128((__m128i *)(pu1_src), q0_16x8); _mm_storeu_si128((__m128i *)(pu1_src + i16_posQ1), q1_16x8); _mm_storeu_si128((__m128i *)(pu1_src + i16_posQ2), q2_16x8); } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_vert_bslt4_ssse3() */ /* */ /* Description : This function performs filtering of a luma block */ /* vertical edge when the boundary strength is less than 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab - tc0_table */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.3 under the */ /* title "Filtering process for edges for bS less than 4" */ /* in ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_luma_vert_bslt4_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta, UWORD32 u4_bs, const UWORD8 *pu1_cliptab) { UWORD8 u1_Bs, u1_Bs1; WORD32 j = 0; __m128i linea, lineb, linec, lined, linee, linef, lineg, lineh; __m128i int1, int2, int3, int4, high1, high2; __m128i flag, flag1, i_C, i_C0; __m128i i_Ap, i_Aq, diff, const1, const2, in_macro, in_macrotemp, temp, temp1; __m128i zero = _mm_setzero_si128(); for(j = 0; j <= 8 * src_strd; j += 8 * src_strd) { //Transpose linea = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + j)); lineb = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + src_strd + j)); linec = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 2 * src_strd + j)); lined = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 3 * src_strd + j)); linea = _mm_unpacklo_epi8(linea, zero); lineb = _mm_unpacklo_epi8(lineb, zero); linec = _mm_unpacklo_epi8(linec, zero); lined = _mm_unpacklo_epi8(lined, zero); int1 = _mm_unpacklo_epi16(linea, lineb); lineb = _mm_unpackhi_epi16(linea, lineb); int2 = _mm_unpacklo_epi16(linec, lined); lined = _mm_unpackhi_epi16(linec, lined); linea = _mm_unpacklo_epi16(int1, int2); int1 = _mm_unpackhi_epi16(int1, int2); linec = _mm_unpacklo_epi16(lineb, lined); high1 = _mm_unpackhi_epi16(lineb, lined); linee = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 4 * src_strd + j)); linef = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 5 * src_strd + j)); lineg = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 6 * src_strd + j)); lineh = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 7 * src_strd + j)); linee = _mm_unpacklo_epi8(linee, zero); linef = _mm_unpacklo_epi8(linef, zero); lineg = _mm_unpacklo_epi8(lineg, zero); lineh = _mm_unpacklo_epi8(lineh, zero); int2 = _mm_unpacklo_epi16(linee, linef); linef = _mm_unpackhi_epi16(linee, linef); int3 = _mm_unpacklo_epi16(lineg, lineh); lineh = _mm_unpackhi_epi16(lineg, lineh); linee = _mm_unpacklo_epi16(int2, int3); int2 = _mm_unpackhi_epi16(int2, int3); lineg = _mm_unpacklo_epi16(linef, lineh); high2 = _mm_unpackhi_epi16(linef, lineh); int4 = _mm_unpacklo_epi16(linea, linee); lineb = _mm_unpackhi_epi16(linea, linee); int3 = _mm_unpacklo_epi16(int1, int2); lined = _mm_unpackhi_epi16(int1, int2); int2 = _mm_unpacklo_epi16(linec, lineg); linef = _mm_unpackhi_epi16(linec, lineg); linea = int4; linec = int3; linee = int2; lineg = _mm_unpacklo_epi16(high1, high2); lineh = _mm_unpackhi_epi16(high1, high2); //end of transpose u1_Bs = (u4_bs >> 24) & 0xff; u1_Bs1 = (u4_bs >> 16) & 0xff; u4_bs <<= 16; flag1 = _mm_set_epi16(u1_Bs1, u1_Bs, u1_Bs1, u1_Bs, u1_Bs1, u1_Bs, u1_Bs1, u1_Bs); flag1 = _mm_cmpeq_epi16(flag1, zero); //Set flag to 1s and 0s flag1 = _mm_xor_si128(flag1, _mm_set1_epi16(0xFFFF)); //Invert for required mask i_C0 = _mm_set_epi16(pu1_cliptab[u1_Bs1], pu1_cliptab[u1_Bs], pu1_cliptab[u1_Bs1], pu1_cliptab[u1_Bs], pu1_cliptab[u1_Bs1], pu1_cliptab[u1_Bs], pu1_cliptab[u1_Bs1], pu1_cliptab[u1_Bs]); diff = _mm_subs_epi16(linec, lined); //Condn 1 diff = _mm_abs_epi16(diff); const1 = _mm_set1_epi16(alpha); flag = _mm_cmpgt_epi16(const1, diff); diff = _mm_subs_epi16(linee, lined); //Condtn 2 diff = _mm_abs_epi16(diff); const1 = _mm_set1_epi16(beta); flag = _mm_and_si128(flag, _mm_cmpgt_epi16(const1, diff)); diff = _mm_subs_epi16(lineb, linec); //Condtn 3 diff = _mm_abs_epi16(diff); flag = _mm_and_si128(flag, _mm_cmpgt_epi16(const1, diff)); //Const 1= Beta from now on flag = _mm_and_si128(flag, flag1); //Final flag (ui_B condition + other 3 conditions) //Adding Ap<Beta and Aq<Beta i_Ap = _mm_subs_epi16(linea, linec); i_Ap = _mm_abs_epi16(i_Ap); const2 = _mm_cmpgt_epi16(const1, i_Ap); const2 = _mm_subs_epi16(zero, const2); //Make FFFF=1 and 0000=0 i_C = _mm_add_epi16(i_C0, const2); i_Aq = _mm_subs_epi16(linef, lined); i_Aq = _mm_abs_epi16(i_Aq); const2 = _mm_cmpgt_epi16(const1, i_Aq); const2 = _mm_subs_epi16(zero, const2); i_C = _mm_add_epi16(i_C, const2); //Calculate in_macro diff = _mm_subs_epi16(lined, linec); diff = _mm_slli_epi16(diff, 2); const2 = _mm_subs_epi16(lineb, linee); diff = _mm_add_epi16(diff, const2); const2 = _mm_set1_epi16(4); diff = _mm_add_epi16(diff, const2); in_macro = _mm_srai_epi16(diff, 3); in_macro = _mm_min_epi16(i_C, in_macro); //CLIP3 i_C = _mm_subs_epi16(zero, i_C); in_macro = _mm_max_epi16(i_C, in_macro); //Compute and store in_macrotemp = _mm_add_epi16(linec, in_macro); in_macrotemp = _mm_and_si128(in_macrotemp, flag); temp = _mm_and_si128(linec, _mm_xor_si128(flag, _mm_set1_epi16(0xFFFF))); temp = _mm_add_epi16(temp, in_macrotemp); //temp= _mm_packus_epi16 (temp, zero); //_mm_storel_epi64(uc_HorzPixel+i16_posP0+i, in_macrotemp); in_macrotemp = _mm_subs_epi16(lined, in_macro); in_macrotemp = _mm_and_si128(in_macrotemp, flag); temp1 = _mm_and_si128(lined, _mm_xor_si128(flag, _mm_set1_epi16(0xFFFF))); temp1 = _mm_add_epi16(temp1, in_macrotemp); //temp1= _mm_packus_epi16 (temp1, zero); //_mm_storel_epi64(pu1_src+i, in_macrotemp); //If Ap<Beta flag1 = _mm_cmpgt_epi16(const1, i_Ap); flag1 = _mm_and_si128(flag, flag1); in_macrotemp = _mm_add_epi16(linec, lined); in_macrotemp = _mm_add_epi16(in_macrotemp, _mm_set1_epi16(1)); in_macrotemp = _mm_srai_epi16(in_macrotemp, 1); in_macro = _mm_add_epi16(in_macrotemp, linea); in_macro = _mm_subs_epi16(in_macro, _mm_slli_epi16(lineb, 1)); in_macro = _mm_srai_epi16(in_macro, 1); in_macro = _mm_min_epi16(i_C0, in_macro); //CLIP3 i_C0 = _mm_subs_epi16(zero, i_C0); in_macro = _mm_max_epi16(i_C0, in_macro); in_macro = _mm_and_si128(in_macro, flag1); lineb = _mm_add_epi16(lineb, in_macro); //in_macro= _mm_packus_epi16 (i_p1, zero); //_mm_storel_epi64(uc_HorzPixel+i16_posP1+i, in_macro); flag1 = _mm_cmpgt_epi16(const1, i_Aq); flag1 = _mm_and_si128(flag, flag1); in_macro = _mm_add_epi16(in_macrotemp, linef); in_macro = _mm_subs_epi16(in_macro, _mm_slli_epi16(linee, 1)); in_macro = _mm_srai_epi16(in_macro, 1); i_C0 = _mm_abs_epi16(i_C0); in_macro = _mm_min_epi16(i_C0, in_macro); //CLIP3 i_C0 = _mm_subs_epi16(zero, i_C0); in_macro = _mm_max_epi16(i_C0, in_macro); in_macro = _mm_and_si128(in_macro, flag1); linee = _mm_add_epi16(linee, in_macro); //in_macro= _mm_packus_epi16 (i_q1, zero); //_mm_storel_epi64(pu1_src+i16_posQ1+i, in_macro); linec = temp; lined = temp1; //End of filtering int1 = _mm_unpacklo_epi16(linea, linee); linee = _mm_unpackhi_epi16(linea, linee); int2 = _mm_unpacklo_epi16(linec, lineg); lineg = _mm_unpackhi_epi16(linec, lineg); linea = _mm_unpacklo_epi16(int1, int2); int3 = _mm_unpackhi_epi16(int1, int2); linec = _mm_unpacklo_epi16(linee, lineg); lineg = _mm_unpackhi_epi16(linee, lineg); int1 = _mm_unpacklo_epi16(lineb, linef); linef = _mm_unpackhi_epi16(lineb, linef); int2 = _mm_unpacklo_epi16(lined, lineh); lineh = _mm_unpackhi_epi16(lined, lineh); lineb = _mm_unpacklo_epi16(int1, int2); int4 = _mm_unpackhi_epi16(int1, int2); lined = _mm_unpacklo_epi16(linef, lineh); lineh = _mm_unpackhi_epi16(linef, lineh); int1 = _mm_unpackhi_epi16(linea, lineb); linea = _mm_unpacklo_epi16(linea, lineb); int2 = _mm_unpacklo_epi16(int3, int4); high1 = _mm_unpackhi_epi16(int3, int4); lineb = _mm_unpacklo_epi16(linec, lined); linef = _mm_unpackhi_epi16(linec, lined); lined = _mm_unpacklo_epi16(lineg, lineh); lineh = _mm_unpackhi_epi16(lineg, lineh); linee = int1; lineg = high1; linec = int2; //End of inverse transpose //Packs and stores linea = _mm_packus_epi16(linea, zero); _mm_storel_epi64((__m128i *)(pu1_src - 3 + j), linea); lineb = _mm_packus_epi16(lineb, zero); _mm_storel_epi64((__m128i *)(pu1_src - 3 + src_strd + j), lineb); linec = _mm_packus_epi16(linec, zero); _mm_storel_epi64((__m128i *)(pu1_src - 3 + 2 * src_strd + j), linec); lined = _mm_packus_epi16(lined, zero); _mm_storel_epi64((__m128i *)(pu1_src - 3 + 3 * src_strd + j), lined); linee = _mm_packus_epi16(linee, zero); _mm_storel_epi64((__m128i *)(pu1_src - 3 + 4 * src_strd + j), linee); linef = _mm_packus_epi16(linef, zero); _mm_storel_epi64((__m128i *)(pu1_src - 3 + 5 * src_strd + j), linef); lineg = _mm_packus_epi16(lineg, zero); _mm_storel_epi64((__m128i *)(pu1_src - 3 + 6 * src_strd + j), lineg); lineh = _mm_packus_epi16(lineh, zero); _mm_storel_epi64((__m128i *)(pu1_src - 3 + 7 * src_strd + j), lineh); } } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_horz_bslt4_ssse3() */ /* */ /* Description : This function performs filtering of a luma block */ /* horizontal edge when boundary strength is less than 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab - tc0_table */ /* */ /* Globals : None */ /* */ /* Processing : This operation is described in Sec. 8.7.2.3 under the */ /* title "Filtering process for edges for bS less than 4" */ /* in ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_luma_horz_bslt4_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta, UWORD32 u4_bs, const UWORD8 *pu1_cliptab) { WORD16 i16_posP2, i16_posP1, i16_posP0, i16_posQ1, i16_posQ2; UWORD8 *pu1_HorzPixel; __m128i zero = _mm_setzero_si128(); __m128i bs_flag_16x8b, C0_16x8, C0_8x16, C0_hi_8x16, C_8x16, C_hi_8x16; __m128i q0_16x8, q1_16x8, q2_16x8, p0_16x8, p1_16x8, p2_16x8; __m128i temp1, temp2; __m128i Alpha_8x16, Beta_8x16, flag1_16x8, flag2_16x8, flag3_16x8; __m128i in_macro_16x8, in_macro_hi_16x8; __m128i const_val4_8x16; UWORD8 u1_Bs0, u1_Bs1, u1_Bs2, u1_Bs3; UWORD8 clip0, clip1, clip2, clip3; pu1_HorzPixel = pu1_src - (src_strd << 2); i16_posQ1 = src_strd; i16_posQ2 = X2(src_strd); i16_posP0 = X3(src_strd); i16_posP1 = X2(src_strd); i16_posP2 = src_strd; q0_16x8 = _mm_loadu_si128((__m128i *)(pu1_src)); q1_16x8 = _mm_loadu_si128((__m128i *)(pu1_src + i16_posQ1)); u1_Bs0 = (u4_bs >> 24) & 0xff; u1_Bs1 = (u4_bs >> 16) & 0xff; u1_Bs2 = (u4_bs >> 8) & 0xff; u1_Bs3 = (u4_bs >> 0) & 0xff; clip0 = pu1_cliptab[u1_Bs0]; clip1 = pu1_cliptab[u1_Bs1]; clip2 = pu1_cliptab[u1_Bs2]; clip3 = pu1_cliptab[u1_Bs3]; Alpha_8x16 = _mm_set1_epi16(alpha); Beta_8x16 = _mm_set1_epi16(beta); bs_flag_16x8b = _mm_set_epi8(u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs2, u1_Bs2, u1_Bs2, u1_Bs2, u1_Bs1, u1_Bs1, u1_Bs1, u1_Bs1, u1_Bs0, u1_Bs0, u1_Bs0, u1_Bs0); C0_16x8 = _mm_set_epi8(clip3, clip3, clip3, clip3, clip2, clip2, clip2, clip2, clip1, clip1, clip1, clip1, clip0, clip0, clip0, clip0); bs_flag_16x8b = _mm_cmpeq_epi8(bs_flag_16x8b, zero); bs_flag_16x8b = _mm_xor_si128(bs_flag_16x8b, _mm_set1_epi8(0xFF)); //Invert for required mask C0_8x16 = _mm_unpacklo_epi8(C0_16x8, zero); C0_hi_8x16 = _mm_unpackhi_epi8(C0_16x8, zero); p1_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP1)); p0_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP0)); p2_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP2)); q2_16x8 = _mm_loadu_si128((__m128i *)(pu1_src + i16_posQ2)); //Cond1 (ABS(p0 - q0) < alpha) temp1 = _mm_subs_epu8(q0_16x8, p0_16x8); temp2 = _mm_subs_epu8(p0_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); flag1_16x8 = _mm_packs_epi16(temp2, temp1); flag1_16x8 = _mm_and_si128(flag1_16x8, bs_flag_16x8b); //Cond2 (ABS(q1 - q0) < beta) temp1 = _mm_subs_epu8(q0_16x8, q1_16x8); temp2 = _mm_subs_epu8(q1_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); //Cond3 (ABS(p1 - p0) < beta) temp1 = _mm_subs_epu8(p0_16x8, p1_16x8); temp2 = _mm_subs_epu8(p1_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); // !((ABS(p0 - q0) < alpha) || (ABS(q1 - q0) < beta) || (ABS(p1 - p0) < beta)) flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); // (ABS(p2 - p0) < beta) temp1 = _mm_subs_epu8(p0_16x8, p2_16x8); temp2 = _mm_subs_epu8(p2_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); flag2_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); temp2 = _mm_subs_epi16(zero, temp2); temp1 = _mm_subs_epi16(zero, temp1); C_8x16 = _mm_add_epi16(C0_8x16, temp2); C_hi_8x16 = _mm_add_epi16(C0_hi_8x16, temp1); // (ABS(q2 - q0) < beta) temp1 = _mm_subs_epu8(q0_16x8, q2_16x8); temp2 = _mm_subs_epu8(q2_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag3_16x8 = _mm_packs_epi16(temp2, temp1); flag3_16x8 = _mm_and_si128(flag1_16x8, flag3_16x8); temp2 = _mm_subs_epi16(zero, temp2); temp1 = _mm_subs_epi16(zero, temp1); C_8x16 = _mm_add_epi16(C_8x16, temp2); C_hi_8x16 = _mm_add_epi16(C_hi_8x16, temp1); const_val4_8x16 = _mm_set1_epi16(4); temp1 = _mm_subs_epi16(_mm_unpacklo_epi8(q0_16x8, zero), _mm_unpacklo_epi8(p0_16x8, zero)); temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(p1_16x8, zero), _mm_unpacklo_epi8(q1_16x8, zero)); temp1 = _mm_slli_epi16(temp1, 2); temp1 = _mm_add_epi16(temp1, temp2); temp1 = _mm_add_epi16(temp1, const_val4_8x16); in_macro_16x8 = _mm_srai_epi16(temp1, 3); temp1 = _mm_subs_epi16(_mm_unpackhi_epi8(q0_16x8, zero), _mm_unpackhi_epi8(p0_16x8, zero)); temp2 = _mm_subs_epi16(_mm_unpackhi_epi8(p1_16x8, zero), _mm_unpackhi_epi8(q1_16x8, zero)); temp1 = _mm_slli_epi16(temp1, 2); temp1 = _mm_add_epi16(temp1, temp2); temp1 = _mm_add_epi16(temp1, const_val4_8x16); in_macro_hi_16x8 = _mm_srai_epi16(temp1, 3); in_macro_16x8 = _mm_min_epi16(C_8x16, in_macro_16x8); //CLIP3 in_macro_hi_16x8 = _mm_min_epi16(C_hi_8x16, in_macro_hi_16x8); //CLIP3 C_8x16 = _mm_subs_epi16(zero, C_8x16); C_hi_8x16 = _mm_subs_epi16(zero, C_hi_8x16); in_macro_16x8 = _mm_max_epi16(C_8x16, in_macro_16x8); //CLIP3 in_macro_hi_16x8 = _mm_max_epi16(C_hi_8x16, in_macro_hi_16x8); //CLIP3 temp1 = _mm_add_epi16(_mm_unpacklo_epi8(p0_16x8, zero), in_macro_16x8); temp2 = _mm_add_epi16(_mm_unpackhi_epi8(p0_16x8, zero), in_macro_hi_16x8); temp1 = _mm_packus_epi16(temp1, temp2); temp1 = _mm_and_si128(temp1, flag1_16x8); temp2 = _mm_and_si128(p0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi16(0xFFFF))); temp1 = _mm_add_epi8(temp1, temp2); _mm_storeu_si128((__m128i *)(pu1_HorzPixel + i16_posP0), temp1); temp1 = _mm_sub_epi16(_mm_unpacklo_epi8(q0_16x8, zero), in_macro_16x8); temp2 = _mm_sub_epi16(_mm_unpackhi_epi8(q0_16x8, zero), in_macro_hi_16x8); temp1 = _mm_packus_epi16(temp1, temp2); temp1 = _mm_and_si128(temp1, flag1_16x8); temp2 = _mm_and_si128(q0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi16(0xFFFF))); temp1 = _mm_add_epi8(temp1, temp2); _mm_storeu_si128((__m128i *)(pu1_src), temp1); //if(Ap < Beta) temp1 = _mm_avg_epu16(_mm_unpacklo_epi8(q0_16x8, zero), _mm_unpacklo_epi8(p0_16x8, zero)); temp2 = _mm_slli_epi16(_mm_unpacklo_epi8(p1_16x8, zero), 1); //temp2 = _mm_subs_epi16(zero,temp2); temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(p2_16x8, zero), temp2); temp2 = _mm_add_epi16(temp1, temp2); in_macro_16x8 = _mm_srai_epi16(temp2, 1); temp1 = _mm_avg_epu16(_mm_unpackhi_epi8(q0_16x8, zero), _mm_unpackhi_epi8(p0_16x8, zero)); temp2 = _mm_slli_epi16(_mm_unpackhi_epi8(p1_16x8, zero), 1); //temp2 = _mm_subs_epi16(zero,temp2); temp2 = _mm_subs_epi16(_mm_unpackhi_epi8(p2_16x8, zero), temp2); temp2 = _mm_add_epi16(temp1, temp2); in_macro_hi_16x8 = _mm_srai_epi16(temp2, 1); in_macro_16x8 = _mm_min_epi16(C0_8x16, in_macro_16x8); //CLIP3 in_macro_hi_16x8 = _mm_min_epi16(C0_hi_8x16, in_macro_hi_16x8); //CLIP3 C0_8x16 = _mm_subs_epi16(zero, C0_8x16); C0_hi_8x16 = _mm_subs_epi16(zero, C0_hi_8x16); in_macro_16x8 = _mm_max_epi16(C0_8x16, in_macro_16x8); //CLIP3 in_macro_hi_16x8 = _mm_max_epi16(C0_hi_8x16, in_macro_hi_16x8); //CLIP3 temp1 = _mm_add_epi16(_mm_unpacklo_epi8(p1_16x8, zero), in_macro_16x8); temp2 = _mm_add_epi16(_mm_unpackhi_epi8(p1_16x8, zero), in_macro_hi_16x8); temp1 = _mm_packus_epi16(temp1, temp2); temp1 = _mm_and_si128(temp1, flag2_16x8); temp2 = _mm_and_si128(p1_16x8, _mm_xor_si128(flag2_16x8, _mm_set1_epi16(0xFFFF))); temp1 = _mm_add_epi8(temp1, temp2); _mm_storeu_si128((__m128i *)(pu1_HorzPixel + i16_posP1), temp1); //if(Aq < Beta) temp1 = _mm_avg_epu16(_mm_unpacklo_epi8(q0_16x8, zero), _mm_unpacklo_epi8(p0_16x8, zero)); temp2 = _mm_slli_epi16(_mm_unpacklo_epi8(q1_16x8, zero), 1); //temp2 = _mm_slli_epi16 (temp2, 1); temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(q2_16x8, zero), temp2); temp2 = _mm_add_epi16(temp1, temp2); in_macro_16x8 = _mm_srai_epi16(temp2, 1); temp1 = _mm_avg_epu16(_mm_unpackhi_epi8(q0_16x8, zero), _mm_unpackhi_epi8(p0_16x8, zero)); temp2 = _mm_slli_epi16(_mm_unpackhi_epi8(q1_16x8, zero), 1); //temp2 = _mm_slli_epi16 (temp2, 1); temp2 = _mm_subs_epi16(_mm_unpackhi_epi8(q2_16x8, zero), temp2); temp2 = _mm_add_epi16(temp1, temp2); in_macro_hi_16x8 = _mm_srai_epi16(temp2, 1); in_macro_16x8 = _mm_max_epi16(C0_8x16, in_macro_16x8); //CLIP3 in_macro_hi_16x8 = _mm_max_epi16(C0_hi_8x16, in_macro_hi_16x8); //CLIP3 C0_8x16 = _mm_subs_epi16(zero, C0_8x16); C0_hi_8x16 = _mm_subs_epi16(zero, C0_hi_8x16); in_macro_16x8 = _mm_min_epi16(C0_8x16, in_macro_16x8); //CLIP3 in_macro_hi_16x8 = _mm_min_epi16(C0_hi_8x16, in_macro_hi_16x8); //CLIP3 temp1 = _mm_add_epi16(_mm_unpacklo_epi8(q1_16x8, zero), in_macro_16x8); temp2 = _mm_add_epi16(_mm_unpackhi_epi8(q1_16x8, zero), in_macro_hi_16x8); temp1 = _mm_packus_epi16(temp1, temp2); temp1 = _mm_and_si128(temp1, flag3_16x8); temp2 = _mm_and_si128(q1_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi16(0xFFFF))); temp1 = _mm_add_epi8(temp1, temp2); _mm_storeu_si128((__m128i *)(pu1_src + i16_posQ1), temp1); } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_vert_bs4_mbaff_ssse3() */ /* */ /* Description : This function performs filtering of a luma block */ /* vertical edge when boundary strength is set to 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* */ /* Globals : None */ /* */ /* Processing : When the function is called twice, this operation is as */ /* described in Sec. 8.7.2.3 under the title "Filtering */ /* process for edges for bS equal to 4" in ITU T Rec H.264. */ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_luma_vert_bs4_mbaff_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta) { __m128i zero = _mm_setzero_si128(); __m128i q0_16x8, q1_16x8, q2_16x8, q3_16x8; __m128i p0_16x8, p1_16x8, p2_16x8, p3_16x8; __m128i q0_8x16, q1_8x16, q2_8x16, q3_8x16; __m128i p0_8x16, p1_8x16, p2_8x16, p3_8x16; __m128i q0_16x8_1; __m128i p0_16x8_1; __m128i q0_16x8_2, q1_16x8_2, q2_16x8_2; __m128i p0_16x8_2, p1_16x8_2, p2_16x8_2; __m128i temp1, temp2, temp3, temp4, temp5, temp6; __m128i Alpha_8x16, Beta_8x16; __m128i flag1_16x8, flag2_16x8, flag3_16x8, flag4_16x8; __m128i const_val2_16x8 = _mm_set1_epi16(2); __m128i line1, line2, line3, line4, line5, line6, line7, line8; Alpha_8x16 = _mm_set1_epi16(alpha); Beta_8x16 = _mm_set1_epi16(beta); line1 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd)); line2 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd)); line3 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd)); line4 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd)); line5 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd)); line6 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd)); line7 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd)); line8 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd)); temp1 = _mm_unpacklo_epi8(line1, line2); temp2 = _mm_unpacklo_epi8(line3, line4); temp3 = _mm_unpacklo_epi8(line5, line6); temp4 = _mm_unpacklo_epi8(line7, line8); line1 = _mm_unpacklo_epi16(temp1, temp2); line2 = _mm_unpackhi_epi16(temp1, temp2); line3 = _mm_unpacklo_epi16(temp3, temp4); line4 = _mm_unpackhi_epi16(temp3, temp4); p1_8x16 = _mm_unpacklo_epi32(line1, line3); p0_8x16 = _mm_unpackhi_epi32(line1, line3); q0_8x16 = _mm_unpacklo_epi32(line2, line4); q1_8x16 = _mm_unpackhi_epi32(line2, line4); p3_16x8 = _mm_unpacklo_epi64(p1_8x16, zero); p2_16x8 = _mm_unpackhi_epi64(p1_8x16, zero); q2_16x8 = _mm_unpacklo_epi64(q1_8x16, zero); q3_16x8 = _mm_unpackhi_epi64(q1_8x16, zero); p1_16x8 = _mm_unpacklo_epi64(p0_8x16, zero); p0_16x8 = _mm_unpackhi_epi64(p0_8x16, zero); q0_16x8 = _mm_unpacklo_epi64(q0_8x16, zero); q1_16x8 = _mm_unpackhi_epi64(q0_8x16, zero); //Cond1 (ABS(p0 - q0) < alpha) temp1 = _mm_subs_epu8(q0_16x8, p0_16x8); temp2 = _mm_subs_epu8(p0_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); flag1_16x8 = _mm_packs_epi16(temp2, temp1); //Cond2 (ABS(q1 - q0) < beta) temp1 = _mm_subs_epu8(q0_16x8, q1_16x8); temp2 = _mm_subs_epu8(q1_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); //Cond3 (ABS(p1 - p0) < beta) temp1 = _mm_subs_epu8(p0_16x8, p1_16x8); temp2 = _mm_subs_epu8(p1_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); // !((ABS(p0 - q0) < alpha) || (ABS(q1 - q0) < beta) || (ABS(p1 - p0) < beta)) flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); // (ABS(p0 - q0) < ((alpha >> 2) + 2)) temp1 = _mm_subs_epu8(p0_16x8, q0_16x8); temp2 = _mm_subs_epu8(q0_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); Alpha_8x16 = _mm_srai_epi16(Alpha_8x16, 2); Alpha_8x16 = _mm_add_epi16(Alpha_8x16, const_val2_16x8); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); flag2_16x8 = _mm_packs_epi16(temp2, temp1); flag2_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); // (ABS(p2 - p0) < beta) temp1 = _mm_subs_epu8(p0_16x8, p2_16x8); temp2 = _mm_subs_epu8(p2_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag3_16x8 = _mm_packs_epi16(temp2, temp1); flag3_16x8 = _mm_and_si128(flag3_16x8, flag2_16x8); // (ABS(q2 - q0) < beta) temp1 = _mm_subs_epu8(q0_16x8, q2_16x8); temp2 = _mm_subs_epu8(q2_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp1 = _mm_unpackhi_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); flag4_16x8 = _mm_packs_epi16(temp2, temp1); flag4_16x8 = _mm_and_si128(flag4_16x8, flag2_16x8); // First 8 pixels p3_8x16 = _mm_unpacklo_epi8(p3_16x8, zero); p2_8x16 = _mm_unpacklo_epi8(p2_16x8, zero); p1_8x16 = _mm_unpacklo_epi8(p1_16x8, zero); p0_8x16 = _mm_unpacklo_epi8(p0_16x8, zero); q0_8x16 = _mm_unpacklo_epi8(q0_16x8, zero); q1_8x16 = _mm_unpacklo_epi8(q1_16x8, zero); q2_8x16 = _mm_unpacklo_epi8(q2_16x8, zero); q3_8x16 = _mm_unpacklo_epi8(q3_16x8, zero); // p0_1 and q0_1 temp1 = _mm_add_epi16(p0_8x16, q1_8x16); temp2 = _mm_add_epi16(p1_8x16, q0_8x16); temp5 = _mm_add_epi16(temp1, const_val2_16x8); temp6 = _mm_add_epi16(temp2, const_val2_16x8); temp3 = _mm_slli_epi16(p1_8x16, 1); temp4 = _mm_slli_epi16(q1_8x16, 1); temp1 = _mm_add_epi16(temp5, temp3); temp2 = _mm_add_epi16(temp6, temp4); p0_16x8_1 = _mm_srai_epi16(temp1, 2); q0_16x8_1 = _mm_srai_epi16(temp2, 2); // p1_2 and q1_2 temp6 = _mm_add_epi16(temp6, p0_8x16); temp5 = _mm_add_epi16(temp5, q0_8x16); temp1 = _mm_add_epi16(temp6, p2_8x16); temp2 = _mm_add_epi16(temp5, q2_8x16); p1_16x8_2 = _mm_srai_epi16(temp1, 2); q1_16x8_2 = _mm_srai_epi16(temp2, 2); // p0_2 and q0_2 temp1 = _mm_add_epi16(temp3, p2_8x16); temp2 = _mm_add_epi16(temp4, q2_8x16); temp1 = _mm_add_epi16(temp1, q1_8x16); temp2 = _mm_add_epi16(temp2, p1_8x16); temp3 = _mm_add_epi16(p0_8x16, q0_8x16); temp3 = _mm_slli_epi16(temp3, 1); temp1 = _mm_add_epi16(temp1, temp3); temp2 = _mm_add_epi16(temp2, temp3); temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(4)); temp2 = _mm_add_epi16(temp2, _mm_set1_epi16(4)); p0_16x8_2 = _mm_srai_epi16(temp1, 3); q0_16x8_2 = _mm_srai_epi16(temp2, 3); // p2_2 and q2_2 temp1 = _mm_add_epi16(temp6, const_val2_16x8); temp2 = _mm_add_epi16(temp5, const_val2_16x8); temp3 = _mm_slli_epi16(p2_8x16, 1); temp4 = _mm_slli_epi16(q2_8x16, 1); temp3 = _mm_add_epi16(p2_8x16, temp3); temp4 = _mm_add_epi16(q2_8x16, temp4); temp5 = _mm_slli_epi16(p3_8x16, 1); temp6 = _mm_slli_epi16(q3_8x16, 1); temp1 = _mm_add_epi16(temp1, temp3); temp2 = _mm_add_epi16(temp2, temp4); temp1 = _mm_add_epi16(temp1, temp5); temp2 = _mm_add_epi16(temp2, temp6); p2_16x8_2 = _mm_srai_epi16(temp1, 3); q2_16x8_2 = _mm_srai_epi16(temp2, 3); // p0_1 and q0_1 p0_16x8_1 = _mm_packus_epi16(p0_16x8_1, zero); q0_16x8_1 = _mm_packus_epi16(q0_16x8_1, zero); // p1_2 and q1_2 p1_16x8_2 = _mm_packus_epi16(p1_16x8_2, zero); q1_16x8_2 = _mm_packus_epi16(q1_16x8_2, zero); // p0_2 and q0_2 p0_16x8_2 = _mm_packus_epi16(p0_16x8_2, zero); q0_16x8_2 = _mm_packus_epi16(q0_16x8_2, zero); // p2_2 and q2_2 p2_16x8_2 = _mm_packus_epi16(p2_16x8_2, zero); q2_16x8_2 = _mm_packus_epi16(q2_16x8_2, zero); // p0 and q0 p0_16x8 = _mm_and_si128(p0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); p0_16x8_1 = _mm_and_si128(p0_16x8_1, flag1_16x8); p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_1); q0_16x8 = _mm_and_si128(q0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); q0_16x8_1 = _mm_and_si128(q0_16x8_1, flag1_16x8); q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_1); // p0 and q0 p0_16x8 = _mm_and_si128(p0_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); p0_16x8_2 = _mm_and_si128(p0_16x8_2, flag3_16x8); p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_2); q0_16x8 = _mm_and_si128(q0_16x8, _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); q0_16x8_2 = _mm_and_si128(q0_16x8_2, flag4_16x8); q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_2); // p1 and q1 p1_16x8 = _mm_and_si128(p1_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); p1_16x8_2 = _mm_and_si128(p1_16x8_2, flag3_16x8); p1_16x8 = _mm_add_epi8(p1_16x8, p1_16x8_2); q1_16x8 = _mm_and_si128(q1_16x8, _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); q1_16x8_2 = _mm_and_si128(q1_16x8_2, flag4_16x8); q1_16x8 = _mm_add_epi8(q1_16x8, q1_16x8_2); // p2 and q2 p2_16x8 = _mm_and_si128(p2_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); p2_16x8_2 = _mm_and_si128(p2_16x8_2, flag3_16x8); p2_16x8 = _mm_add_epi8(p2_16x8, p2_16x8_2); q2_16x8 = _mm_and_si128(q2_16x8, _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); q2_16x8_2 = _mm_and_si128(q2_16x8_2, flag4_16x8); q2_16x8 = _mm_add_epi8(q2_16x8, q2_16x8_2); temp1 = _mm_unpacklo_epi8(p3_16x8, p2_16x8); temp2 = _mm_unpacklo_epi8(p1_16x8, p0_16x8); temp3 = _mm_unpacklo_epi8(q0_16x8, q1_16x8); temp4 = _mm_unpacklo_epi8(q2_16x8, q3_16x8); p3_8x16 = _mm_unpacklo_epi16(temp1, temp2); p2_8x16 = _mm_unpackhi_epi16(temp1, temp2); q2_8x16 = _mm_unpacklo_epi16(temp3, temp4); q3_8x16 = _mm_unpackhi_epi16(temp3, temp4); line1 = _mm_unpacklo_epi32(p3_8x16, q2_8x16); line2 = _mm_srli_si128(line1, 8); line3 = _mm_unpackhi_epi32(p3_8x16, q2_8x16); line4 = _mm_srli_si128(line3, 8); line5 = _mm_unpacklo_epi32(p2_8x16, q3_8x16); line6 = _mm_srli_si128(line5, 8); line7 = _mm_unpackhi_epi32(p2_8x16, q3_8x16); line8 = _mm_srli_si128(line7, 8); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd), line1); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd), line2); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd), line3); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd), line4); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd), line5); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd), line6); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd), line7); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd), line8); } /*****************************************************************************/ /* */ /* Function Name : ih264_deblk_luma_vert_bslt4_mbaff_ssse3() */ /* */ /* Description : This function performs filtering of a luma block */ /* vertical edge when boundary strength is less than 4. */ /* */ /* Inputs : pu1_src - pointer to the src sample q0 */ /* src_strd - source stride */ /* alpha - alpha value for the boundary */ /* beta - beta value for the boundary */ /* u4_bs - packed Boundary strength array */ /* pu1_cliptab - tc0_table */ /* */ /* Globals : None */ /* */ /* Processing : When the function is called twice, this operation is as */ /* described in Sec. 8.7.2.3 under the title "Filtering */ /* process for edges for bS less than 4" in ITU T Rec H.264.*/ /* */ /* Outputs : None */ /* */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 12 02 2015 Naveen Kumar P Initial version */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_deblk_luma_vert_bslt4_mbaff_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 alpha, WORD32 beta, UWORD32 u4_bs, const UWORD8 *pu1_cliptab) { __m128i zero = _mm_setzero_si128(); __m128i bs_flag_16x8b, C0_16x8, C0_8x16, C_8x16; __m128i q0_16x8, q1_16x8, q2_16x8, q3_16x8; __m128i p0_16x8, p1_16x8, p2_16x8, p3_16x8; __m128i temp1, temp2, temp3, temp4; __m128i Alpha_8x16, Beta_8x16, flag1_16x8, flag2_16x8, flag3_16x8; __m128i in_macro_16x8; __m128i const_val4_8x16; UWORD8 u1_Bs0, u1_Bs1, u1_Bs2, u1_Bs3; UWORD8 clip0, clip1, clip2, clip3; __m128i line1, line2, line3, line4, line5, line6, line7, line8; __m128i q0_16x8_1, q1_16x8_1, q0_16x8_2; __m128i p0_16x8_1, p1_16x8_1, p0_16x8_2; line1 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd)); line2 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd)); line3 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd)); line4 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd)); line5 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd)); line6 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd)); line7 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd)); line8 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd)); temp1 = _mm_unpacklo_epi8(line1, line2); temp2 = _mm_unpacklo_epi8(line3, line4); temp3 = _mm_unpacklo_epi8(line5, line6); temp4 = _mm_unpacklo_epi8(line7, line8); line1 = _mm_unpacklo_epi16(temp1, temp2); line2 = _mm_unpackhi_epi16(temp1, temp2); line3 = _mm_unpacklo_epi16(temp3, temp4); line4 = _mm_unpackhi_epi16(temp3, temp4); temp1 = _mm_unpacklo_epi32(line1, line3); temp2 = _mm_unpackhi_epi32(line1, line3); temp3 = _mm_unpacklo_epi32(line2, line4); temp4 = _mm_unpackhi_epi32(line2, line4); p3_16x8 = _mm_unpacklo_epi64(temp1, zero); p2_16x8 = _mm_unpackhi_epi64(temp1, zero); q2_16x8 = _mm_unpacklo_epi64(temp4, zero); q3_16x8 = _mm_unpackhi_epi64(temp4, zero); p1_16x8 = _mm_unpacklo_epi64(temp2, zero); p0_16x8 = _mm_unpackhi_epi64(temp2, zero); q0_16x8 = _mm_unpacklo_epi64(temp3, zero); q1_16x8 = _mm_unpackhi_epi64(temp3, zero); u1_Bs0 = (u4_bs >> 24) & 0xff; u1_Bs1 = (u4_bs >> 16) & 0xff; u1_Bs2 = (u4_bs >> 8) & 0xff; u1_Bs3 = (u4_bs >> 0) & 0xff; clip0 = pu1_cliptab[u1_Bs0]; clip1 = pu1_cliptab[u1_Bs1]; clip2 = pu1_cliptab[u1_Bs2]; clip3 = pu1_cliptab[u1_Bs3]; Alpha_8x16 = _mm_set1_epi16(alpha); Beta_8x16 = _mm_set1_epi16(beta); bs_flag_16x8b = _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, u1_Bs3, u1_Bs3, u1_Bs2, u1_Bs2, u1_Bs1, u1_Bs1, u1_Bs0, u1_Bs0); C0_16x8 = _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, clip3, clip3, clip2, clip2, clip1, clip1, clip0, clip0); bs_flag_16x8b = _mm_cmpeq_epi8(bs_flag_16x8b, zero); bs_flag_16x8b = _mm_xor_si128(bs_flag_16x8b, _mm_set1_epi8(0xFF)); //Invert for required mask C0_8x16 = _mm_unpacklo_epi8(C0_16x8, zero); //Cond1 (ABS(p0 - q0) < alpha) temp1 = _mm_subs_epu8(q0_16x8, p0_16x8); temp2 = _mm_subs_epu8(p0_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); flag1_16x8 = _mm_packs_epi16(temp2, zero); flag1_16x8 = _mm_and_si128(flag1_16x8, bs_flag_16x8b); //Cond2 (ABS(q1 - q0) < beta) temp1 = _mm_subs_epu8(q0_16x8, q1_16x8); temp2 = _mm_subs_epu8(q1_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); flag2_16x8 = _mm_packs_epi16(temp2, zero); flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); //Cond3 (ABS(p1 - p0) < beta) temp1 = _mm_subs_epu8(p0_16x8, p1_16x8); temp2 = _mm_subs_epu8(p1_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); flag2_16x8 = _mm_packs_epi16(temp2, zero); // !((ABS(p0 - q0) < alpha) || (ABS(q1 - q0) < beta) || (ABS(p1 - p0) < beta)) flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); // (ABS(p2 - p0) < beta) temp1 = _mm_subs_epu8(p0_16x8, p2_16x8); temp2 = _mm_subs_epu8(p2_16x8, p0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); flag2_16x8 = _mm_packs_epi16(temp2, zero); flag2_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); temp2 = _mm_subs_epi16(zero, temp2); C_8x16 = _mm_add_epi16(C0_8x16, temp2); // (ABS(q2 - q0) < beta) temp1 = _mm_subs_epu8(q0_16x8, q2_16x8); temp2 = _mm_subs_epu8(q2_16x8, q0_16x8); temp1 = _mm_add_epi8(temp1, temp2); temp2 = _mm_unpacklo_epi8(temp1, zero); temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); flag3_16x8 = _mm_packs_epi16(temp2, zero); flag3_16x8 = _mm_and_si128(flag1_16x8, flag3_16x8); temp2 = _mm_subs_epi16(zero, temp2); C_8x16 = _mm_add_epi16(C_8x16, temp2); const_val4_8x16 = _mm_set1_epi16(4); temp1 = _mm_subs_epi16(_mm_unpacklo_epi8(q0_16x8, zero), _mm_unpacklo_epi8(p0_16x8, zero)); temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(p1_16x8, zero), _mm_unpacklo_epi8(q1_16x8, zero)); temp1 = _mm_slli_epi16(temp1, 2); temp1 = _mm_add_epi16(temp1, temp2); temp1 = _mm_add_epi16(temp1, const_val4_8x16); in_macro_16x8 = _mm_srai_epi16(temp1, 3); in_macro_16x8 = _mm_min_epi16(C_8x16, in_macro_16x8); //CLIP3 C_8x16 = _mm_subs_epi16(zero, C_8x16); in_macro_16x8 = _mm_max_epi16(C_8x16, in_macro_16x8); //CLIP3 // p0 temp1 = _mm_add_epi16(_mm_unpacklo_epi8(p0_16x8, zero), in_macro_16x8); temp1 = _mm_packus_epi16(temp1, zero); p0_16x8_1 = _mm_and_si128(temp1, flag1_16x8); p0_16x8_2 = _mm_and_si128( p0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi16(0xFFFF))); p0_16x8_1 = _mm_add_epi8(p0_16x8_1, p0_16x8_2); // q0 temp1 = _mm_sub_epi16(_mm_unpacklo_epi8(q0_16x8, zero), in_macro_16x8); temp1 = _mm_packus_epi16(temp1, zero); q0_16x8_1 = _mm_and_si128(temp1, flag1_16x8); q0_16x8_2 = _mm_and_si128( q0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi16(0xFFFF))); q0_16x8_1 = _mm_add_epi8(q0_16x8_1, q0_16x8_2); //if(Ap < Beta) temp1 = _mm_avg_epu16(_mm_unpacklo_epi8(q0_16x8, zero), _mm_unpacklo_epi8(p0_16x8, zero)); temp2 = _mm_slli_epi16(_mm_unpacklo_epi8(p1_16x8, zero), 1); //temp2 = _mm_subs_epi16(zero,temp2); temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(p2_16x8, zero), temp2); temp2 = _mm_add_epi16(temp1, temp2); in_macro_16x8 = _mm_srai_epi16(temp2, 1); in_macro_16x8 = _mm_min_epi16(C0_8x16, in_macro_16x8); //CLIP3 C0_8x16 = _mm_subs_epi16(zero, C0_8x16); in_macro_16x8 = _mm_max_epi16(C0_8x16, in_macro_16x8); //CLIP3 // p1 temp1 = _mm_add_epi16(_mm_unpacklo_epi8(p1_16x8, zero), in_macro_16x8); temp1 = _mm_packus_epi16(temp1, zero); p1_16x8_1 = _mm_and_si128(temp1, flag2_16x8); p1_16x8 = _mm_and_si128(p1_16x8, _mm_xor_si128(flag2_16x8, _mm_set1_epi16(0xFFFF))); p1_16x8 = _mm_add_epi8(p1_16x8, p1_16x8_1); //if(Aq < Beta) temp1 = _mm_avg_epu16(_mm_unpacklo_epi8(q0_16x8, zero), _mm_unpacklo_epi8(p0_16x8, zero)); temp2 = _mm_slli_epi16(_mm_unpacklo_epi8(q1_16x8, zero), 1); //temp2 = _mm_slli_epi16 (temp2, 1); temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(q2_16x8, zero), temp2); temp2 = _mm_add_epi16(temp1, temp2); in_macro_16x8 = _mm_srai_epi16(temp2, 1); in_macro_16x8 = _mm_max_epi16(C0_8x16, in_macro_16x8); //CLIP3 C0_8x16 = _mm_subs_epi16(zero, C0_8x16); in_macro_16x8 = _mm_min_epi16(C0_8x16, in_macro_16x8); //CLIP3 temp1 = _mm_add_epi16(_mm_unpacklo_epi8(q1_16x8, zero), in_macro_16x8); // q1 temp1 = _mm_packus_epi16(temp1, zero); q1_16x8_1 = _mm_and_si128(temp1, flag3_16x8); q1_16x8 = _mm_and_si128(q1_16x8, _mm_xor_si128(flag3_16x8, _mm_set1_epi16(0xFFFF))); q1_16x8 = _mm_add_epi8(q1_16x8, q1_16x8_1); temp1 = _mm_unpacklo_epi8(p3_16x8, p2_16x8); temp2 = _mm_unpacklo_epi8(p1_16x8, p0_16x8_1); temp3 = _mm_unpacklo_epi8(q0_16x8_1, q1_16x8); temp4 = _mm_unpacklo_epi8(q2_16x8, q3_16x8); line7 = _mm_unpacklo_epi16(temp1, temp2); temp1 = _mm_unpackhi_epi16(temp1, temp2); line8 = _mm_unpacklo_epi16(temp3, temp4); temp2 = _mm_unpackhi_epi16(temp3, temp4); line1 = _mm_unpacklo_epi32(line7, line8); line2 = _mm_srli_si128(line1, 8); line3 = _mm_unpackhi_epi32(line7, line8); line4 = _mm_srli_si128(line3, 8); line5 = _mm_unpacklo_epi32(temp1, temp2); line6 = _mm_srli_si128(line5, 8); line7 = _mm_unpackhi_epi32(temp1, temp2); line8 = _mm_srli_si128(line7, 8); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd), line1); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd), line2); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd), line3); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd), line4); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd), line5); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd), line6); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd), line7); _mm_storel_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd), line8); } ================================================ FILE: dependencies/ih264d/common/x86/ih264_ihadamard_scaling_sse42.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_ihadamard_scaling_sse42.c * * @brief * Contains definition of functions for h264 inverse hadamard 4x4 transform and scaling * * @author * Mohit * * @par List of Functions: * - ih264_ihadamard_scaling_4x4_sse42() * - ih264_ihadamard_scaling_2x2_uv_ssse42() * * @remarks * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* User include files */ #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_trans_macros.h" #include "ih264_macros.h" #include "ih264_trans_data.h" #include "ih264_size_defs.h" #include "ih264_structs.h" #include "ih264_trans_quant_itrans_iquant.h" #include <immintrin.h> #include <smmintrin.h> #ifdef __GNUC__ #define ATTRIBUTE_SSE42 __attribute__((target("sse4.2"))) #else #define ATTRIBUTE_SSE42 #endif /* ******************************************************************************** * * @brief This function performs a 4x4 inverse hadamard transform on the 4x4 DC coefficients * of a 16x16 intra prediction macroblock, and then performs scaling. * prediction buffer * * @par Description: * The DC coefficients pass through a 2-stage inverse hadamard transform. * This inverse transformed content is scaled to based on Qp value. * * @param[in] pi2_src * input 4x4 block of DC coefficients * * @param[out] pi2_out * output 4x4 block * * @param[in] pu2_iscal_mat * pointer to scaling list * * @param[in] pu2_weigh_mat * pointer to weight matrix * * @param[in] u4_qp_div_6 * Floor (qp/6) * * @param[in] pi4_tmp * temporary buffer of size 1*16 * * @returns none * * @remarks none * ******************************************************************************* */ ATTRIBUTE_SSE42 void ih264_ihadamard_scaling_4x4_sse42(WORD16* pi2_src, WORD16* pi2_out, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD32* pi4_tmp) { __m128i src_r0_r1, src_r2_r3; __m128i src_r0, src_r1, src_r2, src_r3; __m128i temp0, temp1, temp2, temp3; __m128i add_rshift = _mm_set1_epi32((u4_qp_div_6 < 6) ? (1 << (5 - u4_qp_div_6)) : 0); __m128i mult_val = _mm_set1_epi32(pu2_iscal_mat[0] * pu2_weigh_mat[0]); UNUSED (pi4_tmp); src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row //sign_reg = _mm_cmpgt_epi16(zero_8x16b, src_r0_r1); src_r0 = _mm_cvtepi16_epi32(src_r0_r1); src_r0_r1 = _mm_srli_si128(src_r0_r1, 8); src_r1 = _mm_cvtepi16_epi32(src_r0_r1); src_r2 = _mm_cvtepi16_epi32(src_r2_r3); src_r2_r3 = _mm_srli_si128(src_r2_r3, 8); src_r3 = _mm_cvtepi16_epi32(src_r2_r3); /* Perform Inverse transform */ /*-------------------------------------------------------------*/ /* IDCT [ Horizontal transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 a1 a2 a3 * b0 b1 b2 b3 * c0 c1 c2 c3 * d0 d1 d2 d3 */ temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 b0 a1 b1 temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //c0 d0 c1 d1 temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //a2 b2 a3 b3 temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 d2 c3 d3 src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 b0 c0 d0 src_r1 = _mm_unpackhi_epi64(temp0, temp2); //a1 b1 c1 d1 src_r2 = _mm_unpacklo_epi64(temp1, temp3); //a2 b2 c2 d2 src_r3 = _mm_unpackhi_epi64(temp1, temp3); //a3 b3 c3 d3 temp0 = _mm_add_epi32(src_r0, src_r3); temp1 = _mm_add_epi32(src_r1, src_r2); temp2 = _mm_sub_epi32(src_r1, src_r2); temp3 = _mm_sub_epi32(src_r0, src_r3); src_r0 = _mm_add_epi32(temp0, temp1); src_r1 = _mm_add_epi32(temp2, temp3); src_r2 = _mm_sub_epi32(temp0, temp1); src_r3 = _mm_sub_epi32(temp3, temp2); /*-------------------------------------------------------------*/ /* IDCT [ Vertical transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 b0 c0 d0 * a1 b1 c1 d1 * a2 b2 c2 d2 * a3 b3 c3 d3 */ temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 a1 b0 b1 temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //a2 a3 b2 b3 temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //c0 c1 d0 d1 temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 c3 d2 d3 src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 a1 a2 a3 src_r1 = _mm_unpackhi_epi64(temp0, temp2); //b0 b1 b2 b3 src_r2 = _mm_unpacklo_epi64(temp1, temp3); //c0 c1 c2 c3 src_r3 = _mm_unpackhi_epi64(temp1, temp3); //d0 d1 d2 d3 temp0 = _mm_add_epi32(src_r0, src_r3); temp1 = _mm_add_epi32(src_r1, src_r2); temp2 = _mm_sub_epi32(src_r1, src_r2); temp3 = _mm_sub_epi32(src_r0, src_r3); src_r0 = _mm_add_epi32(temp0, temp1); src_r1 = _mm_add_epi32(temp2, temp3); src_r2 = _mm_sub_epi32(temp0, temp1); src_r3 = _mm_sub_epi32(temp3, temp2); src_r0 = _mm_mullo_epi32(src_r0, mult_val); src_r1 = _mm_mullo_epi32(src_r1, mult_val); src_r2 = _mm_mullo_epi32(src_r2, mult_val); src_r3 = _mm_mullo_epi32(src_r3, mult_val); //Scaling if(u4_qp_div_6 >= 6) { src_r0 = _mm_slli_epi32(src_r0, u4_qp_div_6 - 6); src_r1 = _mm_slli_epi32(src_r1, u4_qp_div_6 - 6); src_r2 = _mm_slli_epi32(src_r2, u4_qp_div_6 - 6); src_r3 = _mm_slli_epi32(src_r3, u4_qp_div_6 - 6); } else { temp0 = _mm_add_epi32(src_r0, add_rshift); temp1 = _mm_add_epi32(src_r1, add_rshift); temp2 = _mm_add_epi32(src_r2, add_rshift); temp3 = _mm_add_epi32(src_r3, add_rshift); src_r0 = _mm_srai_epi32(temp0, 6 - u4_qp_div_6); src_r1 = _mm_srai_epi32(temp1, 6 - u4_qp_div_6); src_r2 = _mm_srai_epi32(temp2, 6 - u4_qp_div_6); src_r3 = _mm_srai_epi32(temp3, 6 - u4_qp_div_6); } src_r0_r1 = _mm_packs_epi32(src_r0, src_r1); src_r2_r3 = _mm_packs_epi32(src_r2, src_r3); _mm_storeu_si128((__m128i *) (&pi2_out[0]), src_r0_r1); _mm_storeu_si128((__m128i *) (&pi2_out[8]), src_r2_r3); } ATTRIBUTE_SSE42 void ih264_ihadamard_scaling_2x2_uv_sse42(WORD16* pi2_src, WORD16* pi2_out, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD32* pi4_tmp) { __m128i src, plane_0, plane_1, temp0, temp1, sign_reg; __m128i zero_8x16b = _mm_setzero_si128(); __m128i scale_val = _mm_set1_epi32((WORD32)(pu2_iscal_mat[0] * pu2_weigh_mat[0])); UNUSED(pi4_tmp); src = _mm_loadu_si128((__m128i *) pi2_src); //a0 a1 a2 a3 b0 b1 b2 b3 sign_reg = _mm_cmpgt_epi16(zero_8x16b, src); plane_0 = _mm_unpacklo_epi16(src, sign_reg); //a0 a1 a2 a3 -- 32 bits plane_1 = _mm_unpackhi_epi16(src, sign_reg); //b0 b1 b2 b3 -- 32 bits temp0 = _mm_hadd_epi32(plane_0, plane_1); //a0+a1 a2+a3 b0+b1 b2+b3 temp1 = _mm_hsub_epi32(plane_0, plane_1); //a0-a1 a2-a3 b0-b1 b2-b3 plane_0 = _mm_hadd_epi32(temp0, temp1); //a0+a1+a2+a3 b0+b1+b2+b3 a0-a1+a2-a3 b0-b1+b2-b3 plane_1 = _mm_hsub_epi32(temp0, temp1); //a0+a1-a2-a3 b0+b1-b2-b3 a0-a1-a2+a3 b0-b1-b2+b3 temp0 = _mm_unpacklo_epi32(plane_0, plane_1); //a0+a1+a2+a3 a0+a1-a2-a3 b0+b1+b2+b3 b0+b1-b2-b3 temp1 = _mm_unpackhi_epi32(plane_0, plane_1); //a0-a1+a2-a3 a0-a1-a2+a3 b0-b1+b2-b3 b0-b1-b2+b3 plane_0 = _mm_unpacklo_epi64(temp0, temp1); //a0+a1+a2+a3 a0+a1-a2-a3 a0-a1+a2-a3 a0-a1-a2+a3 plane_1 = _mm_unpackhi_epi64(temp0, temp1); //b0+b1+b2+b3 b0+b1-b2-b3 b0-b1+b2-b3 b0-b1-b2+b3 plane_0 = _mm_shuffle_epi32(plane_0, 0xd8); //a0+a1+a2+a3 a0-a1+a2-a3 a0+a1-a2-a3 a0-a1-a2+a3 plane_1 = _mm_shuffle_epi32(plane_1, 0xd8); //b0+b1+b2+b3 b0-b1+b2-b3 b0+b1-b2-b3 b0-b1-b2+b3 temp0 = _mm_mullo_epi32(scale_val, plane_0); //multiply by pu2_iscal_mat[0] * pu2_weigh_mat[0] temp1 = _mm_mullo_epi32(scale_val, plane_1); //multiply by pu2_iscal_mat[0] * pu2_weigh_mat[0] temp0 = _mm_slli_epi32(temp0, u4_qp_div_6); temp1 = _mm_slli_epi32(temp1, u4_qp_div_6); temp0 = _mm_srai_epi32(temp0, 5); temp1 = _mm_srai_epi32(temp1, 5); temp0 = _mm_packs_epi32(temp0, temp1); //Final values are 16-bits only. _mm_storeu_si128((__m128i *) (&pi2_out[0]), temp0); } ================================================ FILE: dependencies/ih264d/common/x86/ih264_ihadamard_scaling_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_ihadamard_scaling_ssse3.c * * @brief * Contains definition of functions for h264 inverse hadamard 4x4 transform and scaling * * @author * Mohit * * @par List of Functions: * - ih264_ihadamard_scaling_4x4_ssse3() * * @remarks * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* User include files */ #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_trans_macros.h" #include "ih264_macros.h" #include "ih264_trans_data.h" #include "ih264_size_defs.h" #include "ih264_structs.h" #include "ih264_trans_quant_itrans_iquant.h" #include <immintrin.h> #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /* ******************************************************************************** * * @brief This function performs a 4x4 inverse hadamard transform on the 4x4 DC coefficients * of a 16x16 intra prediction macroblock, and then performs scaling. * prediction buffer * * @par Description: * The DC coefficients pass through a 2-stage inverse hadamard transform. * This inverse transformed content is scaled to based on Qp value. * * @param[in] pi2_src * input 4x4 block of DC coefficients * * @param[out] pi2_out * output 4x4 block * * @param[in] pu2_iscal_mat * pointer to scaling list * * @param[in] pu2_weigh_mat * pointer to weight matrix * * @param[in] u4_qp_div_6 * Floor (qp/6) * * @param[in] pi4_tmp * temporary buffer of size 1*16 * * @returns none * * @remarks none * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_ihadamard_scaling_4x4_ssse3(WORD16* pi2_src, WORD16* pi2_out, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD32* pi4_tmp) { int val = 0xFFFF; __m128i src_r0_r1, src_r2_r3, sign_reg, zero_8x16b = _mm_setzero_si128(); __m128i src_r0, src_r1, src_r2, src_r3; __m128i temp0, temp1, temp2, temp3; __m128i add_rshift = _mm_set1_epi32((u4_qp_div_6 < 6) ? (1 << (5 - u4_qp_div_6)) : 0); __m128i mult_val = _mm_set1_epi32(pu2_iscal_mat[0] * pu2_weigh_mat[0]); __m128i mask = _mm_set1_epi32(val); UNUSED (pi4_tmp); mult_val = _mm_and_si128(mult_val, mask); src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row sign_reg = _mm_cmpgt_epi16(zero_8x16b, src_r0_r1); src_r0 = _mm_unpacklo_epi16(src_r0_r1, sign_reg); src_r1 = _mm_unpackhi_epi16(src_r0_r1, sign_reg); sign_reg = _mm_cmpgt_epi16(zero_8x16b, src_r2_r3); src_r2 = _mm_unpacklo_epi16(src_r2_r3, sign_reg); src_r3 = _mm_unpackhi_epi16(src_r2_r3, sign_reg); /* Perform Inverse transform */ /*-------------------------------------------------------------*/ /* IDCT [ Horizontal transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 a1 a2 a3 * b0 b1 b2 b3 * c0 c1 c2 c3 * d0 d1 d2 d3 */ temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 b0 a1 b1 temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //c0 d0 c1 d1 temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //a2 b2 a3 b3 temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 d2 c3 d3 src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 b0 c0 d0 src_r1 = _mm_unpackhi_epi64(temp0, temp2); //a1 b1 c1 d1 src_r2 = _mm_unpacklo_epi64(temp1, temp3); //a2 b2 c2 d2 src_r3 = _mm_unpackhi_epi64(temp1, temp3); //a3 b3 c3 d3 temp0 = _mm_add_epi32(src_r0, src_r3); temp1 = _mm_add_epi32(src_r1, src_r2); temp2 = _mm_sub_epi32(src_r1, src_r2); temp3 = _mm_sub_epi32(src_r0, src_r3); src_r0 = _mm_add_epi32(temp0, temp1); src_r1 = _mm_add_epi32(temp2, temp3); src_r2 = _mm_sub_epi32(temp0, temp1); src_r3 = _mm_sub_epi32(temp3, temp2); /*-------------------------------------------------------------*/ /* IDCT [ Vertical transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 b0 c0 d0 * a1 b1 c1 d1 * a2 b2 c2 d2 * a3 b3 c3 d3 */ temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 a1 b0 b1 temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //a2 a3 b2 b3 temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //c0 c1 d0 d1 temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 c3 d2 d3 src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 a1 a2 a3 src_r1 = _mm_unpackhi_epi64(temp0, temp2); //b0 b1 b2 b3 src_r2 = _mm_unpacklo_epi64(temp1, temp3); //c0 c1 c2 c3 src_r3 = _mm_unpackhi_epi64(temp1, temp3); //d0 d1 d2 d3 temp0 = _mm_add_epi32(src_r0, src_r3); temp1 = _mm_add_epi32(src_r1, src_r2); temp2 = _mm_sub_epi32(src_r1, src_r2); temp3 = _mm_sub_epi32(src_r0, src_r3); src_r0 = _mm_add_epi32(temp0, temp1); src_r1 = _mm_add_epi32(temp2, temp3); src_r2 = _mm_sub_epi32(temp0, temp1); src_r3 = _mm_sub_epi32(temp3, temp2); src_r0 = _mm_and_si128(src_r0, mask); src_r1 = _mm_and_si128(src_r1, mask); src_r2 = _mm_and_si128(src_r2, mask); src_r3 = _mm_and_si128(src_r3, mask); src_r0 = _mm_madd_epi16(src_r0, mult_val); src_r1 = _mm_madd_epi16(src_r1, mult_val); src_r2 = _mm_madd_epi16(src_r2, mult_val); src_r3 = _mm_madd_epi16(src_r3, mult_val); //Scaling if(u4_qp_div_6 >= 6) { src_r0 = _mm_slli_epi32(src_r0, u4_qp_div_6 - 6); src_r1 = _mm_slli_epi32(src_r1, u4_qp_div_6 - 6); src_r2 = _mm_slli_epi32(src_r2, u4_qp_div_6 - 6); src_r3 = _mm_slli_epi32(src_r3, u4_qp_div_6 - 6); } else { temp0 = _mm_add_epi32(src_r0, add_rshift); temp1 = _mm_add_epi32(src_r1, add_rshift); temp2 = _mm_add_epi32(src_r2, add_rshift); temp3 = _mm_add_epi32(src_r3, add_rshift); src_r0 = _mm_srai_epi32(temp0, 6 - u4_qp_div_6); src_r1 = _mm_srai_epi32(temp1, 6 - u4_qp_div_6); src_r2 = _mm_srai_epi32(temp2, 6 - u4_qp_div_6); src_r3 = _mm_srai_epi32(temp3, 6 - u4_qp_div_6); } src_r0_r1 = _mm_packs_epi32(src_r0, src_r1); src_r2_r3 = _mm_packs_epi32(src_r2, src_r3); _mm_storeu_si128((__m128i *) (&pi2_out[0]), src_r0_r1); _mm_storeu_si128((__m128i *) (&pi2_out[8]), src_r2_r3); } ================================================ FILE: dependencies/ih264d/common/x86/ih264_inter_pred_filters_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264_inter_pred_filters_intr_ssse3.c */ /* */ /* Description : Contains function definitions for weighted */ /* prediction functions in x86 sse4 intrinsics */ /* */ /* List of Functions : ih264_inter_pred_luma_copy_ssse3() */ /* ih264_inter_pred_luma_horz_ssse3() */ /* ih264_inter_pred_luma_vert_ssse3() */ /* ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3() */ /* ih264_inter_pred_luma_horz_qpel_ssse3() */ /* ih264_inter_pred_luma_vert_qpel_ssse3() */ /* ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3() */ /* ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3() */ /* ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3() */ /* ih264_inter_pred_chroma_ssse3() */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial version */ /* Senthoor */ /* */ /*****************************************************************************/ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ #include <immintrin.h> #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_inter_pred_filters.h" #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /*****************************************************************************/ /* Constant Data variables */ /*****************************************************************************/ /* coefficients for 6 tap filtering*/ //const WORD32 ih264_g_six_tap[3] ={1,-5,20}; /*****************************************************************************/ /* Function definitions . */ /*****************************************************************************/ /*****************************************************************************/ /* */ /* Function Name : ih264_inter_pred_luma_copy_ssse3 */ /* */ /* Description : This function copies the contents of ht x wd block from */ /* source to destination. (ht,wd) can be (4,4), (8,4), */ /* (4,8), (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src - pointer to source */ /* puc_dst - pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_inter_pred_luma_copy_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { __m128i y_0_16x8b, y_1_16x8b, y_2_16x8b, y_3_16x8b; WORD32 src_strd2, src_strd3, src_strd4, dst_strd2, dst_strd3, dst_strd4; UNUSED(pu1_tmp); UNUSED(dydx); src_strd2 = src_strd << 1; dst_strd2 = dst_strd << 1; src_strd4 = src_strd << 2; dst_strd4 = dst_strd << 2; src_strd3 = src_strd2 + src_strd; dst_strd3 = dst_strd2 + dst_strd; if(wd == 4) { do { *((WORD32 *)(pu1_dst)) = *((WORD32 *)(pu1_src)); *((WORD32 *)(pu1_dst + dst_strd)) = *((WORD32 *)(pu1_src + src_strd)); *((WORD32 *)(pu1_dst + dst_strd2)) = *((WORD32 *)(pu1_src + src_strd2)); *((WORD32 *)(pu1_dst + dst_strd3)) = *((WORD32 *)(pu1_src + src_strd3)); ht -= 4; pu1_src += src_strd4; pu1_dst += dst_strd4; } while(ht > 0); } else if(wd == 8) { do { y_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); y_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); y_2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd2)); y_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd3)); _mm_storel_epi64((__m128i *)pu1_dst, y_0_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd2), y_2_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd3), y_3_16x8b); ht -= 4; pu1_src += src_strd4; pu1_dst += dst_strd4; } while(ht > 0); } else // wd == 16 { WORD32 src_strd5, src_strd6, src_strd7, src_strd8; WORD32 dst_strd5, dst_strd6, dst_strd7, dst_strd8; __m128i y_4_16x8b, y_5_16x8b, y_6_16x8b, y_7_16x8b; src_strd5 = src_strd2 + src_strd3; dst_strd5 = dst_strd2 + dst_strd3; src_strd6 = src_strd3 << 1; dst_strd6 = dst_strd3 << 1; src_strd7 = src_strd3 + src_strd4; dst_strd7 = dst_strd3 + dst_strd4; src_strd8 = src_strd << 3; dst_strd8 = dst_strd << 3; do { y_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); y_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); y_2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd2)); y_3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd3)); y_4_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd4)); y_5_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd5)); y_6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd6)); y_7_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd7)); _mm_storeu_si128((__m128i *)pu1_dst, y_0_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), y_2_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), y_3_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd4), y_4_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd5), y_5_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd6), y_6_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd7), y_7_16x8b); ht -= 8; pu1_src += src_strd8; pu1_dst += dst_strd8; } while(ht > 0); } } /*****************************************************************************/ /* */ /* Function Name : ih264_inter_pred_luma_horz_ssse3 */ /* */ /* Description : This function applies a horizontal 6-tap filter on */ /* ht x wd block as mentioned in sec. 8.4.2.2.1 titled */ /* "Luma sample interpolation process". (ht,wd) can be */ /* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src - pointer to source */ /* puc_dst - pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_inter_pred_luma_horz_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; __m128i const_val16_8x16b; UNUSED(pu1_tmp); UNUSED(dydx); pu1_src -= 2; // the filter input starts from x[-2] (till x[3]) coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 const_val16_8x16b = _mm_set1_epi16(16); if(wd == 4) { __m128i src_r0_16x8b, src_r1_16x8b, src_r0r1_16x8b; __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i res_r0r1_t1_8x16b, res_r0r1_t2_8x16b, res_r0r1_t3_8x16b; __m128i res_r0r1_16x8b; //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 b0 b1 b1 b2 b2 b3 b3 b4 res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 b2 b3 b3 b4 b4 b5 b5 b6 res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //b2*c2+b3*c3 b3*c2+b4*c3 b4*c2+b5*c3 b5*c2+b6*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 0 0 0 0 src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 b4 b5 b5 b6 b6 b7 b7 b8 res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //b4*c4+b5*c5 b5*c4+b6*c5 b4*c6+b7*c5 b7*c4+b8*c5 res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); res_r0r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0r1_t3_8x16b); res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t3_8x16b); //a0*c0+a1*c1+a2*c2+a3*c3+a4*a4+a5*c5 + 16; //a1*c0+a2*c1+a2*c2+a3*c3+a5*a4+a6*c5 + 16; //a2*c0+a3*c1+a4*c2+a5*c3+a6*a4+a7*c5 + 16; //a3*c0+a4*c1+a5*c2+a6*c3+a6*a4+a8*c5 + 16; //b0*c0+b1*c1+b2*c2+b3*c3+b4*b4+b5*c5 + 16; //b1*c0+b2*c1+b2*c2+b3*c3+b5*b4+b6*c5 + 16; //b2*c0+b3*c1+b4*c2+b5*c3+b6*b4+b7*c5 + 16; //b3*c0+b4*c1+b5*c2+b6*c3+b6*b4+b8*c5 + 16; res_r0r1_t1_8x16b = _mm_srai_epi16(res_r0r1_t1_8x16b, 5); //shifting right by 5 bits. res_r0r1_16x8b = _mm_packus_epi16(res_r0r1_t1_8x16b, res_r0r1_t1_8x16b); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_r0r1_16x8b); res_r0r1_16x8b = _mm_srli_si128(res_r0r1_16x8b, 4); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_r0r1_16x8b); ht -= 2; pu1_src += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else if(wd == 8) { __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); res_r0_t3_8x16b = _mm_add_epi16(res_r0_t3_8x16b, const_val16_8x16b); res_r1_t3_8x16b = _mm_add_epi16(res_r1_t3_8x16b, const_val16_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); //shifting right by 5 bits. res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); src_r0_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r0_t1_8x16b); src_r1_16x8b = _mm_packus_epi16(res_r1_t1_8x16b, res_r1_t1_8x16b); _mm_storel_epi64((__m128i *)pu1_dst, src_r0_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), src_r1_16x8b); ht -= 2; pu1_src += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else // wd == 16 { __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row0 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... //b0 is same a8. Similarly other bn pixels are same as a(n+8) pixels. do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); res_r0_t3_8x16b = _mm_add_epi16(res_r0_t3_8x16b, const_val16_8x16b); res_r1_t3_8x16b = _mm_add_epi16(res_r1_t3_8x16b, const_val16_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); //shifting right by 5 bits. res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); src_r0_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r1_t1_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, src_r0_16x8b); ht--; pu1_src += src_strd; pu1_dst += dst_strd; } while(ht > 0); } } /*****************************************************************************/ /* */ /* Function Name : ih264_inter_pred_luma_vert_ssse3 */ /* */ /* Description : This function applies a vertical 6-tap filter on */ /* ht x wd block as mentioned in sec. 8.4.2.2.1 titled */ /* "Luma sample interpolation process". (ht,wd) can be */ /* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src - pointer to source */ /* puc_dst - pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_inter_pred_luma_vert_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b, src_r4_16x8b; __m128i src_r5_16x8b, src_r6_16x8b; __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; __m128i res_16x8b, res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; __m128i const_val16_8x16b; UNUSED(pu1_tmp); UNUSED(dydx); coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 const_val16_8x16b = _mm_set1_epi16(16); pu1_src -= src_strd << 1; // the filter input starts from x[-2] (till x[3]) if(wd == 4) { //Epilogue: Load all the pred rows except sixth and seventh row // for the first and second row processing. src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r1_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r2_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r3_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r4_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r0_16x8b = _mm_unpacklo_epi32(src_r0_16x8b, src_r1_16x8b); src_r1_16x8b = _mm_unpacklo_epi32(src_r1_16x8b, src_r2_16x8b); src_r2_16x8b = _mm_unpacklo_epi32(src_r2_16x8b, src_r3_16x8b); src_r3_16x8b = _mm_unpacklo_epi32(src_r3_16x8b, src_r4_16x8b); do { src_r5_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); src_r4_16x8b = _mm_unpacklo_epi32(src_r4_16x8b, src_r5_16x8b); src_r5_16x8b = _mm_unpacklo_epi32(src_r5_16x8b, src_r6_16x8b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_16x8b); res_16x8b = _mm_srli_si128(res_16x8b, 4); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_16x8b); src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; ht -= 2; pu1_src += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else if(wd == 8) { //Epilogue: Load all the pred rows except sixth and seventh row // for the first and second row processing. src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r1_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r2_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r3_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r4_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r0_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); src_r1_16x8b = _mm_unpacklo_epi64(src_r1_16x8b, src_r2_16x8b); src_r2_16x8b = _mm_unpacklo_epi64(src_r2_16x8b, src_r3_16x8b); src_r3_16x8b = _mm_unpacklo_epi64(src_r3_16x8b, src_r4_16x8b); do { src_r5_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); src_r4_16x8b = _mm_unpacklo_epi64(src_r4_16x8b, src_r5_16x8b); src_r5_16x8b = _mm_unpacklo_epi64(src_r5_16x8b, src_r6_16x8b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); _mm_storel_epi64((__m128i *)pu1_dst, res_16x8b); src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; ht -= 2; pu1_src += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else // wd == 16 { __m128i res_t0_8x16b; //Epilogue: Load all the pred rows except sixth and seventh row // for the first and second row processing. src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); pu1_src += src_strd; src_r1_16x8b = _mm_loadu_si128((__m128i *)pu1_src); pu1_src += src_strd; src_r2_16x8b = _mm_loadu_si128((__m128i *)pu1_src); pu1_src += src_strd; src_r3_16x8b = _mm_loadu_si128((__m128i *)pu1_src); pu1_src += src_strd; src_r4_16x8b = _mm_loadu_si128((__m128i *)pu1_src); pu1_src += src_strd; do { src_r5_16x8b = _mm_loadu_si128((__m128i *)pu1_src); src_r6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r2_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r3_16x8b, src_r4_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r5_16x8b, src_r6_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. src_r0r1_16x8b = _mm_unpackhi_epi8(src_r1_16x8b, src_r2_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r3_16x8b, src_r4_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r5_16x8b, src_r6_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res_16x8b); src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; ht -= 2; pu1_src += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } } /*****************************************************************************/ /* */ /* Function Name : ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3 */ /* */ /* Description : This function implements a two stage cascaded six tap */ /* filter, horizontally and then vertically on ht x wd */ /* block as mentioned in sec. 8.4.2.2.1 titled "Luma sample */ /* interpolation process". (ht,wd) can be (4,4), (8,4), */ /* (4,8), (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src - pointer to source */ /* puc_dst - pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* pu1_tmp - pointer to temporary buffer */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { UNUSED(dydx); if(wd == 4) { WORD16 *pi2_temp; pu1_tmp += 4; pu1_src -= src_strd << 1; pi2_temp = (WORD16 *)pu1_tmp; pu1_src -= 2; // the filter input starts from x[-2] (till x[3]) // Horizontal 6-tap filtering { WORD32 ht_tmp = ht + 4; __m128i src_r0_16x8b, src_r1_16x8b; __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i src_r0r1_t1_16x8b; __m128i res_r0r1_t1_8x16b, res_r0r1_t2_8x16b, res_r0r1_t3_8x16b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 b0 b1 b1 b2 b2 b3 b3 b4 res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 b2 b3 b3 b4 b4 b5 b5 b6 res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //b2*c2+b3*c3 b3*c2+b4*c3 b4*c2+b5*c3 b5*c2+b6*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 0 0 0 0 src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 b4 b5 b5 b6 b6 b7 b7 b8 res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //b4*c4+b5*c5 b5*c4+b6*c5 b4*c6+b7*c5 b7*c4+b8*c5 res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t3_8x16b, res_r0r1_t1_8x16b); _mm_storeu_si128((__m128i *)pi2_temp, res_r0r1_t1_8x16b); ht_tmp -= 2; pu1_src += src_strd << 1; pi2_temp += 8; } while(ht_tmp > 0); src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b,4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b,4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t3_8x16b, res_r0r1_t1_8x16b); _mm_storel_epi64((__m128i *)pi2_temp, res_r0r1_t1_8x16b); } pi2_temp = (WORD16 *)pu1_tmp; // Vertical 6-tap filtering { __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b, src_r4_8x16b; __m128i src_r5_8x16b, src_r6_8x16b; __m128i src_t1_8x16b, src_t2_8x16b; __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; __m128i res_8x16b, res_16x8b; __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; __m128i const_val512_4x32b; coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); coeff2_3_8x16b = _mm_set1_epi32(0x00140014); coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); const_val512_4x32b = _mm_set1_epi32(512); src_r0_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp)); src_r1_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp + 4)); src_r2_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp + 8)); src_r3_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp + 12)); src_r4_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp + 16)); pi2_temp += 20; do { src_r5_8x16b = _mm_loadl_epi64((__m128i *)pi2_temp); src_r6_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp + 4)); src_r0_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_t1_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_t2_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_t1_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_t2_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); src_t1_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); src_t2_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_t1_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_t2_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_16x8b); res_16x8b = _mm_srli_si128(res_16x8b, 4); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_16x8b); src_r0_8x16b = src_r2_8x16b; src_r1_8x16b = src_r3_8x16b; src_r2_8x16b = src_r4_8x16b; src_r3_8x16b = src_r5_8x16b; src_r4_8x16b = src_r6_8x16b; ht -= 2; pi2_temp += 8; pu1_dst += dst_strd << 1; } while(ht > 0); } } else if(wd == 8) { WORD16 *pi2_temp; pu1_tmp += 4; pu1_src -= src_strd << 1; pi2_temp = (WORD16 *)pu1_tmp; pu1_src -= 2; // the filter input starts from x[-2] (till x[3]) // Horizontal 6-tap filtering { WORD32 ht_tmp = ht + 4; __m128i src_r0_16x8b, src_r1_16x8b; __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 b10 b11 b12 b13 b14 b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); _mm_storeu_si128((__m128i *)pi2_temp, res_r0_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp + 8), res_r1_t1_8x16b); ht_tmp -= 2; pu1_src += src_strd << 1; pi2_temp += 16; } while(ht_tmp > 0); src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b,src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b,coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); _mm_storeu_si128((__m128i *)pi2_temp, res_r0_t1_8x16b); } pi2_temp = (WORD16 *)pu1_tmp; // Vertical 6-tap filtering { __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b, src_r4_8x16b; __m128i src_r5_8x16b, src_r6_8x16b; __m128i src_r0r1_8x16b, src_r2r3_8x16b, src_r4r5_8x16b; __m128i res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; __m128i res_c0_4x32b, res_c1_4x32b; __m128i res_8x16b, res_16x8b; __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; __m128i const_val512_4x32b; coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); coeff2_3_8x16b = _mm_set1_epi32(0x00140014); coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); const_val512_4x32b = _mm_set1_epi32(512); src_r0_8x16b = _mm_loadu_si128((__m128i *)pi2_temp); src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 8)); src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 16)); src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 24)); src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 32)); pi2_temp += 40; do { src_r5_8x16b = _mm_loadu_si128((__m128i *)pi2_temp); src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 8)); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); _mm_storel_epi64((__m128i *)pu1_dst, res_16x8b); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); src_r0_8x16b = src_r2_8x16b; src_r1_8x16b = src_r3_8x16b; src_r2_8x16b = src_r4_8x16b; src_r3_8x16b = src_r5_8x16b; src_r4_8x16b = src_r6_8x16b; ht -= 2; pi2_temp += 16; pu1_dst += dst_strd << 1; } while(ht > 0); } } else // wd == 16 { WORD16 *pi2_temp; WORD32 ht_tmp; pu1_tmp += 4; pu1_src -= src_strd << 1; pi2_temp = (WORD16 *)pu1_tmp; pu1_src -= 2; // the filter input starts from x[-2] (till x[3]) // Horizontal 6-tap filtering { __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; ht_tmp = ht + 5; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row0 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... //b0 is same a8. Similarly other bn pixels are same as a(n+8) pixels. do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); _mm_storeu_si128((__m128i *)pi2_temp, res_r0_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp + 8), res_r1_t1_8x16b); ht_tmp--; pu1_src += src_strd; pi2_temp += 16; } while(ht_tmp > 0); } pi2_temp = (WORD16 *)pu1_tmp; // Vertical 6-tap filtering { WORD16 *pi2_temp2; UWORD8 *pu1_dst2; WORD32 ht_tmp; __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b, src_r4_8x16b; __m128i src_r5_8x16b, src_r6_8x16b; __m128i src_r0r1_8x16b, src_r2r3_8x16b, src_r4r5_8x16b; __m128i res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; __m128i res_c0_4x32b, res_c1_4x32b; __m128i res_8x16b, res_16x8b; __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; __m128i const_val512_4x32b; coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); coeff2_3_8x16b = _mm_set1_epi32(0x00140014); coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); const_val512_4x32b = _mm_set1_epi32(512); pi2_temp2 = pi2_temp + 8; pu1_dst2 = pu1_dst + 8; ht_tmp = ht; /**********************************************************/ /* Do first height x 8 block */ /**********************************************************/ src_r0_8x16b = _mm_loadu_si128((__m128i *)pi2_temp); src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 16)); src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 32)); src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 48)); src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 64)); pi2_temp += 80; do { src_r5_8x16b = _mm_loadu_si128((__m128i *)pi2_temp); src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 16)); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); _mm_storel_epi64((__m128i *)pu1_dst, res_16x8b); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); src_r0_8x16b = src_r2_8x16b; src_r1_8x16b = src_r3_8x16b; src_r2_8x16b = src_r4_8x16b; src_r3_8x16b = src_r5_8x16b; src_r4_8x16b = src_r6_8x16b; ht_tmp -= 2; pi2_temp += 32; pu1_dst += dst_strd << 1; } while(ht_tmp > 0); /**********************************************************/ /* Do second ht x 8 block */ /**********************************************************/ src_r0_8x16b = _mm_loadu_si128((__m128i *)pi2_temp2); src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 16)); src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 32)); src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 48)); src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 64)); pi2_temp2 += 80; do { src_r5_8x16b = _mm_loadu_si128((__m128i *)pi2_temp2); src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 16)); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); _mm_storel_epi64((__m128i *)pu1_dst2, res_16x8b); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); _mm_storel_epi64((__m128i *)(pu1_dst2 + dst_strd), res_16x8b); src_r0_8x16b = src_r2_8x16b; src_r1_8x16b = src_r3_8x16b; src_r2_8x16b = src_r4_8x16b; src_r3_8x16b = src_r5_8x16b; src_r4_8x16b = src_r6_8x16b; ht -= 2; pi2_temp2 += 32; pu1_dst2 += dst_strd << 1; } while(ht > 0); } } } /*****************************************************************************/ /* */ /* Function Name : ih264_inter_pred_luma_horz_qpel_ssse3 */ /* */ /* Description : This function implements a six-tap filter horizontally */ /* on ht x wd block and averages the values with the source */ /* pixels to calculate horizontal quarter-pel as mentioned */ /* in sec. 8.4.2.2.1 titled "Luma sample interpolation */ /* process". (ht,wd) can be (4,4), (8,4), (4,8), (8,8), */ /* (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src - pointer to source */ /* puc_dst - pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* pu1_tmp - pointer to temporary buffer */ /* dydx - x and y reference offset for q-pel */ /* calculations */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_inter_pred_luma_horz_qpel_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 x_offset; UWORD8 *pu1_pred1; __m128i src_r0_16x8b, src_r1_16x8b; __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; __m128i const_val16_8x16b; UNUSED(pu1_tmp); x_offset = dydx & 3; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 pu1_pred1 = pu1_src + (x_offset >> 1); const_val16_8x16b = _mm_set1_epi16(16); pu1_src -= 2; // the filter input starts from x[-2] (till x[3]) if(wd == 4) { __m128i src_r0r1_16x8b; __m128i res_r0r1_t1_8x16b, res_r0r1_t2_8x16b, res_r0r1_t3_8x16b; __m128i res_r0r1_16x8b; //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 b0 b1 b1 b2 b2 b3 b3 b4 res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 b2 b3 b3 b4 b4 b5 b5 b6 res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //b2*c2+b3*c3 b3*c2+b4*c3 b4*c2+b5*c3 b5*c2+b6*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 0 0 0 0 src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 b4 b5 b5 b6 b6 b7 b7 b8 res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //b4*c4+b5*c5 b5*c4+b6*c5 b4*c6+b7*c5 b7*c4+b8*c5 src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_pred1); src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred1 + src_strd)); res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); res_r0r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0r1_t3_8x16b); res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t3_8x16b); //a0*c0+a1*c1+a2*c2+a3*c3+a4*a4+a5*c5 + 16; //a1*c0+a2*c1+a2*c2+a3*c3+a5*a4+a6*c5 + 16; //a2*c0+a3*c1+a4*c2+a5*c3+a6*a4+a7*c5 + 16; //a3*c0+a4*c1+a5*c2+a6*c3+a6*a4+a8*c5 + 16; //b0*c0+b1*c1+b2*c2+b3*c3+b4*b4+b5*c5 + 16; //b1*c0+b2*c1+b2*c2+b3*c3+b5*b4+b6*c5 + 16; //b2*c0+b3*c1+b4*c2+b5*c3+b6*b4+b7*c5 + 16; //b3*c0+b4*c1+b5*c2+b6*c3+b6*b4+b8*c5 + 16; src_r0r1_16x8b = _mm_unpacklo_epi32(src_r0_16x8b,src_r1_16x8b); res_r0r1_t1_8x16b = _mm_srai_epi16(res_r0r1_t1_8x16b, 5); //shifting right by 5 bits. res_r0r1_16x8b = _mm_packus_epi16(res_r0r1_t1_8x16b, res_r0r1_t1_8x16b); res_r0r1_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_r0r1_16x8b); //computing q-pel *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_r0r1_16x8b); res_r0r1_16x8b = _mm_srli_si128(res_r0r1_16x8b, 4); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_r0r1_16x8b); ht -= 2; pu1_src += src_strd << 1; pu1_pred1 += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else if(wd == 8) { __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; __m128i res_r0_16x8b, res_r1_16x8b; //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_pred1); src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred1 + src_strd)); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); res_r0_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0_t3_8x16b); res_r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r1_t3_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); //shifting right by 5 bits. res_r0_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r0_t1_8x16b); res_r1_16x8b = _mm_packus_epi16(res_r1_t1_8x16b, res_r1_t1_8x16b); res_r0_16x8b = _mm_avg_epu8(src_r0_16x8b, res_r0_16x8b); res_r1_16x8b = _mm_avg_epu8(src_r1_16x8b, res_r1_16x8b); //computing q-pel _mm_storel_epi64((__m128i *)pu1_dst, res_r0_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_r1_16x8b); ht -= 2; pu1_src += src_strd << 1; pu1_pred1 += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else // wd == 16 { __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; __m128i res_16x8b; //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row0 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... //b0 is same a8. Similarly other bn pixels are same as a(n+8) pixels. do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_pred1); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); res_r0_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0_t3_8x16b); res_r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r1_t3_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); //shifting right by 5 bits res_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r1_t1_8x16b); res_16x8b = _mm_avg_epu8(src_r0_16x8b, res_16x8b); //computing q-pel _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); ht--; pu1_src += src_strd; pu1_pred1 += src_strd; pu1_dst += dst_strd; } while(ht > 0); } } /*****************************************************************************/ /* */ /* Function Name : ih264_inter_pred_luma_vert_qpel_ssse3 */ /* */ /* Description : This function implements a six-tap filter vertically on */ /* ht x wd block and averages the values with the source */ /* pixels to calculate vertical quarter-pel as mentioned in */ /* sec. 8.4.2.2.1 titled "Luma sample interpolation */ /* process". (ht,wd) can be (4,4), (8,4), (4,8), (8,8), */ /* (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src - pointer to source */ /* puc_dst - pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* pu1_tmp - pointer to temporary buffer */ /* dydx - x and y reference offset for q-pel */ /* calculations */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_inter_pred_luma_vert_qpel_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 y_offset; UWORD8 *pu1_pred1; __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b, src_r4_16x8b; __m128i src_r5_16x8b, src_r6_16x8b; __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; __m128i res_16x8b, res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; __m128i const_val16_8x16b; UNUSED(pu1_tmp); y_offset = dydx & 0xf; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 pu1_pred1 = pu1_src + (y_offset >> 3) * src_strd; const_val16_8x16b = _mm_set1_epi16(16); pu1_src -= src_strd << 1; // the filter input starts from x[-2] (till x[3]) if(wd == 4) { //Epilogue: Load all the pred rows except sixth and seventh row // for the first and second row processing. src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r1_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r2_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r3_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r4_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r0_16x8b = _mm_unpacklo_epi32(src_r0_16x8b, src_r1_16x8b); src_r1_16x8b = _mm_unpacklo_epi32(src_r1_16x8b, src_r2_16x8b); src_r2_16x8b = _mm_unpacklo_epi32(src_r2_16x8b, src_r3_16x8b); src_r3_16x8b = _mm_unpacklo_epi32(src_r3_16x8b, src_r4_16x8b); do { src_r5_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); src_r4_16x8b = _mm_unpacklo_epi32(src_r4_16x8b, src_r5_16x8b); src_r5_16x8b = _mm_unpacklo_epi32(src_r5_16x8b, src_r6_16x8b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_pred1); src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred1 + src_strd)); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); src_r0r1_16x8b = _mm_unpacklo_epi32(src_r0_16x8b,src_r1_16x8b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); res_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_16x8b); //computing q-pel *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_16x8b); res_16x8b = _mm_srli_si128(res_16x8b, 4); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_16x8b); src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; ht -= 2; pu1_src += src_strd << 1; pu1_pred1 += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else if(wd == 8) { //Epilogue: Load all the pred rows except sixth and seventh row // for the first and second row processing. src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r1_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r2_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r3_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r4_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); pu1_src += src_strd; src_r0_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); src_r1_16x8b = _mm_unpacklo_epi64(src_r1_16x8b, src_r2_16x8b); src_r2_16x8b = _mm_unpacklo_epi64(src_r2_16x8b, src_r3_16x8b); src_r3_16x8b = _mm_unpacklo_epi64(src_r3_16x8b, src_r4_16x8b); do { src_r5_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); src_r4_16x8b = _mm_unpacklo_epi64(src_r4_16x8b, src_r5_16x8b); src_r5_16x8b = _mm_unpacklo_epi64(src_r5_16x8b, src_r6_16x8b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); src_r0r1_16x8b = _mm_loadl_epi64((__m128i *)pu1_pred1); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); res_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_16x8b); //computing q-pel _mm_storel_epi64((__m128i *)pu1_dst, res_16x8b); src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); src_r0r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred1 + src_strd)); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); res_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_16x8b); //computing q-pel _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; ht -= 2; pu1_src += src_strd << 1; pu1_pred1 += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else // wd == 16 { __m128i res_t0_8x16b; //Epilogue: Load all the pred rows except sixth and seventh row // for the first and second row processing. src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); pu1_src += src_strd; src_r1_16x8b = _mm_loadu_si128((__m128i *)pu1_src); pu1_src += src_strd; src_r2_16x8b = _mm_loadu_si128((__m128i *)pu1_src); pu1_src += src_strd; src_r3_16x8b = _mm_loadu_si128((__m128i *)pu1_src); pu1_src += src_strd; src_r4_16x8b = _mm_loadu_si128((__m128i *)pu1_src); pu1_src += src_strd; do { src_r5_16x8b = _mm_loadu_si128((__m128i *)pu1_src); src_r6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); src_r0r1_16x8b = _mm_loadu_si128((__m128i *)pu1_pred1); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); res_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_16x8b); //computing q-pel _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r2_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r3_16x8b, src_r4_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r5_16x8b, src_r6_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. src_r0r1_16x8b = _mm_unpackhi_epi8(src_r1_16x8b, src_r2_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r3_16x8b, src_r4_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r5_16x8b, src_r6_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); src_r0r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred1 + src_strd)); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); res_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_16x8b); //computing q-pel _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res_16x8b); src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; ht -= 2; pu1_src += src_strd << 1; pu1_pred1 += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } } /*****************************************************************************/ /* */ /* Function Name : ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3 */ /* */ /* Description : This function implements a six-tap filter vertically and */ /* horizontally on ht x wd block separately and averages */ /* the two sets of values to calculate values at (1/4,1/4), */ /* (1/4, 3/4), (3/4, 1/4) or (3/4, 3/4) as mentioned in */ /* sec. 8.4.2.2.1 titled "Luma sample interpolation */ /* process". (ht,wd) can be (4,4), (8,4), (4,8), (8,8), */ /* (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src - pointer to source */ /* puc_dst - pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* pu1_tmp - pointer to temporary buffer */ /* dydx - x and y reference offset for q-pel */ /* calculations */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 ht_temp; UWORD8 *pu1_pred_vert,*pu1_pred_horiz; UWORD8 *pu1_tmp1, *pu1_tmp2; WORD32 x_offset, y_offset; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; __m128i const_val16_8x16b; pu1_tmp1 = pu1_tmp; dydx &= 0xf; ht_temp = ht; x_offset = dydx & 0x3; y_offset = dydx >> 2; pu1_tmp2 = pu1_tmp1; pu1_pred_vert = pu1_src + (x_offset >> 1) - 2*src_strd; pu1_pred_horiz = pu1_src + (y_offset >> 1) * src_strd - 2; //the filter input starts from x[-2] (till x[3]) coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 const_val16_8x16b = _mm_set1_epi16(16); if(wd == 4) { //vertical q-pel filter { __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b, src_r4_16x8b; __m128i src_r5_16x8b, src_r6_16x8b; __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; __m128i res_r0r1_16x8b, res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; //epilogue: Load all the pred rows except sixth and seventh row for the //first and second row processing. src_r0_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r0_16x8b = _mm_unpacklo_epi32(src_r0_16x8b, src_r1_16x8b); src_r2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r1_16x8b = _mm_unpacklo_epi32(src_r1_16x8b, src_r2_16x8b); src_r3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r2_16x8b = _mm_unpacklo_epi32(src_r2_16x8b, src_r3_16x8b); src_r4_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r3_16x8b = _mm_unpacklo_epi32(src_r3_16x8b, src_r4_16x8b); //Core Loop: Process all the rows. do { src_r5_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); src_r4_16x8b = _mm_unpacklo_epi32(src_r4_16x8b, src_r5_16x8b); src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert + src_strd)); src_r5_16x8b = _mm_unpacklo_epi32(src_r5_16x8b, src_r6_16x8b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_r0r1_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); _mm_storel_epi64((__m128i *)pu1_tmp1, res_r0r1_16x8b); src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; ht_temp -= 2; pu1_pred_vert += src_strd << 1; pu1_tmp1 += 8; } while(ht_temp > 0); } //horizontal q-pel filter { __m128i src_r0_16x8b, src_r1_16x8b; __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i src_r0r1_vpel_16x8b, src_r0r1_t1_16x8b; __m128i res_r0r1_t1_8x16b, res_r0r1_t2_8x16b, res_r0r1_t3_8x16b; __m128i res_r0r1_16x8b; //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_pred_horiz); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_horiz + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0r1_vpel_16x8b = _mm_loadl_epi64((__m128i *)pu1_tmp2); src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 b0 b1 b1 b2 b2 b3 b3 b4 res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 b2 b3 b3 b4 b4 b5 b5 b6 res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //b2*c2+b3*c3 b3*c2+b4*c3 b4*c2+b5*c3 b5*c2+b6*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 0 0 0 0 src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 b4 b5 b5 b6 b6 b7 b7 b8 res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //b4*c4+b5*c5 b5*c4+b6*c5 b4*c6+b7*c5 b7*c4+b8*c5 res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); res_r0r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0r1_t3_8x16b); res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t3_8x16b); //a0*c0+a1*c1+a2*c2+a3*c3+a4*a4+a5*c5 + 15; //a1*c0+a2*c1+a2*c2+a3*c3+a5*a4+a6*c5 + 15; //a2*c0+a3*c1+a4*c2+a5*c3+a6*a4+a7*c5 + 15; //a3*c0+a4*c1+a5*c2+a6*c3+a6*a4+a8*c5 + 15; //b0*c0+b1*c1+b2*c2+b3*c3+b4*b4+b5*c5 + 15; //b1*c0+b2*c1+b2*c2+b3*c3+b5*b4+b6*c5 + 15; //b2*c0+b3*c1+b4*c2+b5*c3+b6*b4+b7*c5 + 15; //b3*c0+b4*c1+b5*c2+b6*c3+b6*b4+b8*c5 + 15; res_r0r1_t1_8x16b = _mm_srai_epi16(res_r0r1_t1_8x16b, 5); //shifting right by 5 bits. res_r0r1_16x8b = _mm_packus_epi16(res_r0r1_t1_8x16b,res_r0r1_t1_8x16b); res_r0r1_16x8b = _mm_avg_epu8(res_r0r1_16x8b,src_r0r1_vpel_16x8b); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_r0r1_16x8b); res_r0r1_16x8b = _mm_srli_si128(res_r0r1_16x8b, 4); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_r0r1_16x8b); ht -= 2; pu1_pred_horiz += src_strd << 1; pu1_tmp2 += 8; pu1_dst += dst_strd << 1; } while(ht > 0); } } else if(wd == 8) { //vertical q-pel filter { __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b; __m128i src_r4_16x8b, src_r5_16x8b, src_r6_16x8b; __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; __m128i res_16x8b, res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; //epilogue: Load all the pred rows except sixth and seventh row for the //first and second row processing. src_r0_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r0_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); src_r2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r1_16x8b = _mm_unpacklo_epi64(src_r1_16x8b, src_r2_16x8b); src_r3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r2_16x8b = _mm_unpacklo_epi64(src_r2_16x8b, src_r3_16x8b); src_r4_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r3_16x8b = _mm_unpacklo_epi64(src_r3_16x8b, src_r4_16x8b); //Core Loop: Process all the rows. do { src_r5_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); src_r4_16x8b = _mm_unpacklo_epi64(src_r4_16x8b, src_r5_16x8b); src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert + src_strd)); src_r5_16x8b = _mm_unpacklo_epi64(src_r5_16x8b, src_r6_16x8b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); _mm_storel_epi64((__m128i *)(pu1_tmp1), res_16x8b); src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); _mm_storel_epi64((__m128i *)(pu1_tmp1 + 8), res_16x8b); src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; ht_temp -= 2; pu1_pred_vert += src_strd << 1; pu1_tmp1 += 16; } while(ht_temp > 0); } //horizontal q-pel filter { __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; __m128i src_r0_vpel_16x8b, src_r1_vpel_16x8b; __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b, res_16x8b; //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... do { src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_horiz)); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_horiz + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_vpel_16x8b = _mm_loadl_epi64((__m128i *)(pu1_tmp2)); //a2 a3 a4 a5 a6 a7 a8....a15 0 or //a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_vpel_16x8b = _mm_loadl_epi64((__m128i *)(pu1_tmp2 + 8)); //b2 b3 b4 b5 b6 b7 b8....b15 0 or //b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r0_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0_t3_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r0_t1_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_r0_vpel_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst), res_16x8b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); res_r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r1_t3_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_r1_t1_8x16b, res_r1_t1_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b,src_r1_vpel_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); ht -= 2; pu1_pred_horiz += src_strd << 1; pu1_dst += dst_strd << 1; pu1_tmp2 += 16; } while(ht > 0); } } else // wd == 16 { //vertical q-pel filter { __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b; __m128i src_r4_16x8b, src_r5_16x8b, src_r6_16x8b; __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; __m128i res_t0_8x16b, res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; __m128i res_16x8b; //epilogue: Load all the pred rows except sixth and seventh row for the //first and second row processing. src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r2_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r3_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; src_r4_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); pu1_pred_vert = pu1_pred_vert + src_strd; //Core Loop: Process all the rows. do { src_r5_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); src_r6_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert + src_strd)); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pu1_tmp1), res_16x8b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r2_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r3_16x8b, src_r4_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r5_16x8b, src_r6_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. src_r0r1_16x8b = _mm_unpackhi_epi8(src_r1_16x8b, src_r2_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r3_16x8b, src_r4_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r5_16x8b, src_r6_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pu1_tmp1 + 16), res_16x8b); src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; ht_temp -= 2; pu1_pred_vert += src_strd << 1; pu1_tmp1 += 32; } while(ht_temp > 0); } //horizontal q-pel filter { __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; __m128i src_vpel_16x8b; __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; __m128i res_16x8b; //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row0 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... //b0 is same a8. Similarly other bn pixels are same as a(n+8) pixels. do { src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_horiz)); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_horiz + 8)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_vpel_16x8b = _mm_loadu_si128((__m128i *)(pu1_tmp2)); src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r0_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0_t3_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); //shifting right by 5 bits. res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, const_val16_8x16b); res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); //shifting right by 5 bits. res_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r1_t1_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_vpel_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst), res_16x8b); ht --; pu1_pred_horiz += src_strd; pu1_dst += dst_strd; pu1_tmp2 += 16; } while(ht > 0); } } } /*****************************************************************************/ /* */ /* Function Name : ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3 */ /* */ /* Description : This function implements a six-tap filter vertically and */ /* horizontally on ht x wd block separately and averages */ /* the two sets of values to calculate values at (1/4,1/2), */ /* or (3/4, 1/2) as mentioned in sec. 8.4.2.2.1 titled */ /* "Luma sample interpolation process". (ht,wd) can be */ /* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src - pointer to source */ /* puc_dst - pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* pu1_tmp - pointer to temporary buffer */ /* dydx - x and y reference offset for q-pel */ /* calculations */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 ht_temp; WORD32 x_offset; WORD32 off0,off1, off2, off3, off4, off5; WORD16 *pi2_temp1,*pi2_temp2,*pi2_temp3; ht_temp = ht; x_offset = dydx & 0x3; pi2_temp1 = (WORD16 *)pu1_tmp; pi2_temp2 = pi2_temp1; pi2_temp3 = pi2_temp1 + (x_offset >> 1); pu1_src -= 2 * src_strd; pu1_src -= 2; pi2_temp3 += 2; //the filter input starts from x[-2] (till x[3]) if(wd == 4) { //vertical half-pel { __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b, src_r4_16x8b; __m128i src_r5_16x8b, src_r6_16x8b; __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; __m128i res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 off0 = -((src_strd << 2) + src_strd) + 8; off1 = -(src_strd << 2) + 8; off2 = -((src_strd << 1) + src_strd) + 8; off3 = -(src_strd << 1) + 8; off4 = -src_strd + 8; off5 = 8; //epilogue: Load all the pred rows except sixth and seventh row for the //first and second row processing. src_r0_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); pu1_src = pu1_src + src_strd; src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); pu1_src = pu1_src + src_strd; src_r2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); pu1_src = pu1_src + src_strd; src_r3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); pu1_src = pu1_src + src_strd; src_r4_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); pu1_src = pu1_src + src_strd; //Core Loop: Process all the rows. do { src_r5_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t2_8x16b, res_t1_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1), res_t1_8x16b); pi2_temp1[8] = pu1_src[off0] + pu1_src[off5] - (pu1_src[off1] + pu1_src[off4]) + ((pu1_src[off2] + pu1_src[off3] - pu1_src[off1] - pu1_src[off4]) << 2) + ((pu1_src[off2] + pu1_src[off3]) << 4); pu1_src = pu1_src + src_strd; pi2_temp1 = pi2_temp1 + 9; src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r2_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r3_16x8b, src_r4_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r5_16x8b, src_r6_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t2_8x16b, res_t1_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1), res_t1_8x16b); pi2_temp1[8] = pu1_src[off0] + pu1_src[off5] - (pu1_src[off1] + pu1_src[off4]) + ((pu1_src[off2] + pu1_src[off3] - pu1_src[off1] - pu1_src[off4]) << 2) + ((pu1_src[off2] + pu1_src[off3]) << 4); ht_temp -= 2; pu1_src = pu1_src + src_strd; pi2_temp1 = pi2_temp1 + 9; src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; } while(ht_temp > 0); } //horizontal q-pel { __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b; __m128i src_r3_8x16b, src_r4_8x16b, src_r5_8x16b; __m128i src_r0r1_c0_8x16b, src_r2r3_c0_8x16b, src_r4r5_c0_8x16b; __m128i src_hpel_16x8b, src_hpel_8x16b; __m128i res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; __m128i res_8x16b, res_16x8b; __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; __m128i const_val512_4x32b, const_val16_8x16b; coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); coeff2_3_8x16b = _mm_set1_epi32(0x00140014); coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); const_val512_4x32b = _mm_set1_epi32(512); const_val16_8x16b = _mm_set1_epi16(16); do { src_r0_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2)); src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 1)); src_r2_8x16b = _mm_srli_si128(src_r1_8x16b, 2); src_r3_8x16b = _mm_srli_si128(src_r1_8x16b, 4); src_r4_8x16b = _mm_srli_si128(src_r1_8x16b, 6); src_r5_8x16b = _mm_srli_si128(src_r1_8x16b, 8); src_r0r1_c0_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_c0_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_c0_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_c0_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_c0_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_c0_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(const_val512_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_t1_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); src_hpel_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp3)); src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_16x8b); ht--; pi2_temp2 = pi2_temp2 + 4 + 5; pi2_temp3 = pi2_temp3 + 4 + 5; pu1_dst = pu1_dst + dst_strd; } while(ht > 0); } } else if(wd == 8) { // vertical half-pel { __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b, src_r4_16x8b; __m128i src_r5_16x8b, src_r6_16x8b; __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; __m128i res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 //epilogue: Load all the pred rows except sixth and seventh row for the //first and second row processing. src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); pu1_src = pu1_src + src_strd; src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); pu1_src = pu1_src + src_strd; src_r2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); pu1_src = pu1_src + src_strd; src_r3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); pu1_src = pu1_src + src_strd; src_r4_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); pu1_src = pu1_src + src_strd; //Core Loop: Process all the rows. do { src_r5_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); src_r6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1), res_t1_8x16b); src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1 + 8), res_t1_8x16b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r2_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r3_16x8b, src_r4_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r5_16x8b, src_r6_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1 + 8 + 5), res_t1_8x16b); src_r0r1_16x8b = _mm_unpackhi_epi8(src_r1_16x8b, src_r2_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r3_16x8b, src_r4_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r5_16x8b, src_r6_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1 + 8 + 5 + 8), res_t1_8x16b); src_r0_16x8b = src_r2_16x8b; src_r1_16x8b = src_r3_16x8b; src_r2_16x8b = src_r4_16x8b; src_r3_16x8b = src_r5_16x8b; src_r4_16x8b = src_r6_16x8b; ht_temp -= 2; pu1_src = pu1_src + (src_strd << 1); pi2_temp1 = pi2_temp1 + (13 << 1); } while(ht_temp > 0); } // horizontal q-pel { __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b; __m128i src_r4_8x16b, src_r5_8x16b; __m128i src_r0r1_c0_8x16b, src_r2r3_c0_8x16b, src_r4r5_c0_8x16b; __m128i src_r0r1_c1_8x16b, src_r2r3_c1_8x16b, src_r4r5_c1_8x16b; __m128i src_hpel_8x16b, src_hpel_16x8b; __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; __m128i res_8x16b, res_16x8b; __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; __m128i const_val512_4x32b, const_val16_8x16b; coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); coeff2_3_8x16b = _mm_set1_epi32(0x00140014); coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); const_val512_4x32b = _mm_set1_epi32(512); const_val16_8x16b = _mm_set1_epi16(16); do { src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 1)); src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 2)); src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 3)); src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 4)); src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 5)); src_r0r1_c0_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_c0_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_c0_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); src_r0r1_c1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_c1_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_c1_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_c0_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_c0_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_c0_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_t1_4x32b = _mm_madd_epi16(src_r0r1_c1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_c1_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_c1_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3)); src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst), res_16x8b); ht--; pi2_temp2 = pi2_temp2 + 8 + 5; pi2_temp3 = pi2_temp3 + 8 + 5; pu1_dst = pu1_dst + dst_strd; } while(ht > 0); } } else // wd == 16 { // vertical half-pel { __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b; __m128i src_r4_16x8b, src_r5_16x8b; __m128i src_r0_c2_16x8b, src_r1_c2_16x8b, src_r2_c2_16x8b, src_r3_c2_16x8b; __m128i src_r4_c2_16x8b, src_r5_c2_16x8b; __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; __m128i res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; __m128i coeff0_1_16x8b,coeff2_3_16x8b,coeff4_5_16x8b; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); src_r0_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); pu1_src = pu1_src + src_strd; src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); src_r1_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); pu1_src = pu1_src + src_strd; src_r2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); src_r2_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); pu1_src = pu1_src + src_strd; src_r3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); src_r3_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); pu1_src = pu1_src + src_strd; src_r4_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); src_r4_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); pu1_src = pu1_src + src_strd; //Core Loop: Process all the rows. do { src_r5_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); src_r5_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1), res_t1_8x16b); src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1 + 8), res_t1_8x16b); src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_c2_16x8b, src_r1_c2_16x8b); src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_c2_16x8b, src_r3_c2_16x8b); src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_c2_16x8b, src_r5_c2_16x8b); res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1 + 16), res_t1_8x16b); src_r0_16x8b = src_r1_16x8b; src_r1_16x8b = src_r2_16x8b; src_r2_16x8b = src_r3_16x8b; src_r3_16x8b = src_r4_16x8b; src_r4_16x8b = src_r5_16x8b; src_r0_c2_16x8b = src_r1_c2_16x8b; src_r1_c2_16x8b = src_r2_c2_16x8b; src_r2_c2_16x8b = src_r3_c2_16x8b; src_r3_c2_16x8b = src_r4_c2_16x8b; src_r4_c2_16x8b = src_r5_c2_16x8b; ht_temp--; pu1_src = pu1_src + src_strd; pi2_temp1 = pi2_temp1 + 16 + 5; } while(ht_temp > 0); } // horizontal q-pel { __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b; __m128i src_r4_8x16b, src_r5_8x16b; __m128i src_r0r1_8x16b, src_r2r3_8x16b, src_r4r5_8x16b; __m128i src_hpel1_8x16b, src_hpel2_8x16b, src_hpel_16x8b; __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; __m128i res_c0_8x16b, res_c1_8x16b, res_16x8b; __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; __m128i const_val512_4x32b, const_val16_8x16b; coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); coeff2_3_8x16b = _mm_set1_epi32(0x00140014); coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); const_val512_4x32b = _mm_set1_epi32(512); const_val16_8x16b = _mm_set1_epi16(16); do { src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 1)); src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 2)); src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 3)); src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 4)); src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 5)); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(const_val512_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_c0_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8)); src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8 + 1)); src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8 + 2)); src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8 + 3)); src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8 + 4)); src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8 + 5)); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(const_val512_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b ,10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(const_val512_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_c1_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_c0_8x16b, res_c1_8x16b); src_hpel1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3)); src_hpel1_8x16b = _mm_add_epi16(src_hpel1_8x16b, const_val16_8x16b); src_hpel1_8x16b = _mm_srai_epi16(src_hpel1_8x16b, 5); //shifting right by 5 bits. src_hpel2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3 + 8)); src_hpel2_8x16b = _mm_add_epi16(src_hpel2_8x16b, const_val16_8x16b); src_hpel2_8x16b = _mm_srai_epi16(src_hpel2_8x16b, 5); //shifting right by 5 bits. src_hpel_16x8b = _mm_packus_epi16(src_hpel1_8x16b, src_hpel2_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst), res_16x8b); ht--; pi2_temp2 = pi2_temp2 + 16 + 5; pi2_temp3 = pi2_temp3 + 16 + 5; pu1_dst = pu1_dst + dst_strd; } while(ht > 0); } } } /*****************************************************************************/ /* */ /* Function Name : ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3 */ /* */ /* Description : This function implements a six-tap filter vertically and */ /* horizontally on ht x wd block separately and averages */ /* the two sets of values to calculate values at (1/2,1/4), */ /* or (1/2, 3/4) as mentioned in sec. 8.4.2.2.1 titled */ /* "Luma sample interpolation process". (ht,wd) can be */ /* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : puc_src - pointer to source */ /* puc_dst - pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* pu1_tmp - pointer to temporary buffer */ /* dydx - x and y reference offset for q-pel */ /* calculations */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ht, WORD32 wd, UWORD8* pu1_tmp, WORD32 dydx) { WORD32 ht_temp; WORD32 y_offset; WORD16 *pi2_temp1,*pi2_temp2,*pi2_temp3; y_offset = (dydx & 0xf) >> 2; pi2_temp1 = (WORD16 *)pu1_tmp; pi2_temp2 = pi2_temp1; pi2_temp3 = pi2_temp1 + (y_offset >> 1) * wd; ht_temp = ht + 5; pu1_src -= src_strd << 1; pu1_src -= 2; pi2_temp3 += wd << 1; //the filter input starts from x[-2] (till x[3]) if(wd == 4) { // horizontal half-pel { __m128i src_r0_16x8b, src_r1_16x8b, src_r0r1_t1_16x8b; __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i res_r0r1_t1_8x16b, res_r0r1_t2_8x16b, res_r0r1_t3_8x16b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... do { src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 b0 b1 b1 b2 b2 b3 b3 b4 res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 b2 b3 b3 b4 b4 b5 b5 b6 res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //b2*c2+b3*c3 b3*c2+b4*c3 b4*c2+b5*c3 b5*c2+b6*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 0 0 0 0 src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 b4 b5 b5 b6 b6 b7 b7 b8 res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //b4*c4+b5*c5 b5*c4+b6*c5 b4*c6+b7*c5 b7*c4+b8*c5 res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t3_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1), res_r0r1_t1_8x16b); ht_temp -= 2; pu1_src = pu1_src + (src_strd << 1); pi2_temp1 = pi2_temp1 + (4 << 1); } while(ht_temp > 0); } // vertical q-pel { __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b; __m128i src_r4_8x16b, src_r5_8x16b, src_r6_8x16b; __m128i src_r0r1_c0_8x16b, src_r2r3_c0_8x16b, src_r4r5_c0_8x16b; __m128i src_hpel_16x8b, src_hpel_8x16b; __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; __m128i res_8x16b, res_16x8b; __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; __m128i const_val512_4x32b, const_val16_8x16b; const_val512_4x32b = _mm_set1_epi32(512); const_val16_8x16b = _mm_set1_epi16(16); coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); coeff2_3_8x16b = _mm_set1_epi32(0x00140014); coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); src_r0_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2)); src_r1_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2 + 4)); src_r2_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2 + 8)); src_r3_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2 + 12)); src_r4_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2 + 16)); pi2_temp2 += 20; do { src_r5_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2)); src_r6_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2 + 4)); src_r0r1_c0_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_c0_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_c0_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_c0_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_c0_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_c0_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_c0_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_c0_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_c0_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_c0_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_c0_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_c0_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); src_hpel_8x16b = _mm_loadu_si128((__m128i *)pi2_temp3); src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_16x8b); res_16x8b = _mm_srli_si128(res_16x8b, 4); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_16x8b); src_r0_8x16b = src_r2_8x16b; src_r1_8x16b = src_r3_8x16b; src_r2_8x16b = src_r4_8x16b; src_r3_8x16b = src_r5_8x16b; src_r4_8x16b = src_r6_8x16b; ht -= 2; pi2_temp2 = pi2_temp2 + (4 << 1); pi2_temp3 = pi2_temp3 + (4 << 1); pu1_dst = pu1_dst + (dst_strd << 1); } while(ht > 0); } } else if(wd == 8) { // horizontal half-pel { __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... do { src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1), res_r0_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1 + 8), res_r1_t1_8x16b); ht_temp -= 2; pu1_src = pu1_src + (src_strd << 1); pi2_temp1 = pi2_temp1 + (8 << 1); } while(ht_temp > 0); } // vertical q-pel { __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b; __m128i src_r4_8x16b, src_r5_8x16b, src_r6_8x16b; __m128i src_r0r1_8x16b, src_r2r3_8x16b, src_r4r5_8x16b; __m128i src_hpel_8x16b, src_hpel_16x8b; __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; __m128i res_8x16b, res_16x8b; __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; __m128i const_val512_4x32b, const_val16_8x16b; coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); coeff2_3_8x16b = _mm_set1_epi32(0x00140014); coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); const_val512_4x32b = _mm_set1_epi32(512); const_val16_8x16b = _mm_set1_epi16(16); src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8)); src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 16)); src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 24)); src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 32)); pi2_temp2 += 40; do { src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8)); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); src_hpel_8x16b = _mm_loadu_si128((__m128i *)pi2_temp3); src_hpel_8x16b = _mm_add_epi16(const_val16_8x16b, src_hpel_8x16b); src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst), res_16x8b); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3 + 8)); src_hpel_8x16b = _mm_add_epi16(const_val16_8x16b, src_hpel_8x16b); src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); src_r0_8x16b = src_r2_8x16b; src_r1_8x16b = src_r3_8x16b; src_r2_8x16b = src_r4_8x16b; src_r3_8x16b = src_r5_8x16b; src_r4_8x16b = src_r6_8x16b; ht -= 2; pi2_temp2 = pi2_temp2 + (8 << 1); pi2_temp3 = pi2_temp3 + (8 << 1); pu1_dst = pu1_dst + (dst_strd << 1); } while(ht > 0); } } else // wd == 16 { UWORD8 *pu1_dst1; WORD16 *pi2_temp4,*pi2_temp5; pu1_dst1 = pu1_dst + 8; pi2_temp4 = pi2_temp2 + 8; pi2_temp5 = pi2_temp3 + 8; // horizontal half-pel { __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... //Row0 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... //b0 is same a8. Similarly other bn pixels are same as a(n+8) pixels. do { src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1), res_r0_t1_8x16b); _mm_storeu_si128((__m128i *)(pi2_temp1 + 8), res_r1_t1_8x16b); ht_temp--; pu1_src = pu1_src + src_strd; pi2_temp1 = pi2_temp1 + 16; } while(ht_temp > 0); } // vertical q-pel { __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b, src_r4_8x16b; __m128i src_r5_8x16b, src_r6_8x16b; __m128i src_r0r1_8x16b, src_r2r3_8x16b, src_r4r5_8x16b; __m128i src_hpel_8x16b, src_hpel_16x8b; __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; __m128i res_8x16b, res_16x8b; __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; __m128i const_val512_4x32b, const_val16_8x16b; coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); coeff2_3_8x16b = _mm_set1_epi32(0x00140014); coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); const_val512_4x32b = _mm_set1_epi32(512); const_val16_8x16b = _mm_set1_epi16(16); /**********************************************************/ /* Do first height x 8 block */ /**********************************************************/ src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 16)); src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 32)); src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 48)); src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 64)); pi2_temp2 += 80; ht_temp = ht; do { src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 16)); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3)); src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst), res_16x8b); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3 + 16)); src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); src_r0_8x16b = src_r2_8x16b; src_r1_8x16b = src_r3_8x16b; src_r2_8x16b = src_r4_8x16b; src_r3_8x16b = src_r5_8x16b; src_r4_8x16b = src_r6_8x16b; ht_temp -= 2; pi2_temp3 = pi2_temp3 + (16 << 1); pi2_temp2 = pi2_temp2 + (16 << 1); pu1_dst = pu1_dst + (dst_strd << 1); } while(ht_temp > 0); /**********************************************************/ /* Do second height * 8 block */ /**********************************************************/ src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4)); src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4 + 16)); src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4 + 32)); src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4 + 48)); src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4 + 64)); pi2_temp4 += 80; do { src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4)); src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4 + 16)); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp5)); src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst1), res_16x8b); src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp5 + 16)); src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst1 + dst_strd), res_16x8b); src_r0_8x16b = src_r2_8x16b; src_r1_8x16b = src_r3_8x16b; src_r2_8x16b = src_r4_8x16b; src_r3_8x16b = src_r5_8x16b; src_r4_8x16b = src_r6_8x16b; ht -= 2; pi2_temp5 = pi2_temp5 + (16 << 1); pi2_temp4 = pi2_temp4 + (16 << 1); pu1_dst1 = pu1_dst1 + (dst_strd << 1); } while(ht > 0); } } } /*****************************************************************************/ /* */ /* Function Name : ih264_inter_pred_chroma_ssse3 */ /* */ /* Description : This function implements a four-tap 2D filter as */ /* mentioned in sec. 8.4.2.2.2 titled "Chroma sample */ /* "interpolation process". (ht,wd) can be (2,2), (4,2), */ /* (2,4), (4,4), (8,4), (4,8) or (8,8). */ /* */ /* Inputs : puc_src - pointer to source */ /* puc_dst - pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* dx - x position of destination value */ /* dy - y position of destination value */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 13 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSSE3 void ih264_inter_pred_chroma_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 dx, WORD32 dy, WORD32 ht, WORD32 wd) { WORD32 i, j, A, B, C, D; i = 8 - dx; j = 8 - dy; A = i * j; B = dx * j; C = i * dy; D = dx * dy; if(wd == 2) { WORD32 tmp1, tmp2, tmp3, tmp4; do { //U tmp1 = A * pu1_src[0] + B * pu1_src[2] + C * pu1_src[src_strd] + D * pu1_src[src_strd + 2]; tmp2 = A * pu1_src[2] + B * pu1_src[4] + C * pu1_src[src_strd + 2] + D * pu1_src[src_strd + 4]; //V tmp3 = A * pu1_src[1] + B * pu1_src[3] + C * pu1_src[src_strd + 1] + D * pu1_src[src_strd + 3]; tmp4 = A * pu1_src[3] + B * pu1_src[5] + C * pu1_src[src_strd + 3] + D * pu1_src[src_strd + 5]; tmp1 = (tmp1 + 32) >> 6; tmp2 = (tmp2 + 32) >> 6; tmp3 = (tmp3 + 32) >> 6; tmp4 = (tmp4 + 32) >> 6; pu1_dst[0] = CLIP_U8(tmp1); pu1_dst[2] = CLIP_U8(tmp2); pu1_dst[1] = CLIP_U8(tmp3); pu1_dst[3] = CLIP_U8(tmp4); pu1_src += src_strd; pu1_dst += dst_strd; tmp1 = A * pu1_src[0] + B * pu1_src[2] + C * pu1_src[src_strd] + D * pu1_src[src_strd + 2]; tmp2 = A * pu1_src[2] + B * pu1_src[4] + C * pu1_src[src_strd + 2] + D * pu1_src[src_strd + 4]; tmp3 = A * pu1_src[1] + B * pu1_src[3] + C * pu1_src[src_strd + 1] + D * pu1_src[src_strd + 3]; tmp4 = A * pu1_src[3] + B * pu1_src[5] + C * pu1_src[src_strd + 3] + D * pu1_src[src_strd + 5]; tmp1 = (tmp1 + 32) >> 6; tmp2 = (tmp2 + 32) >> 6; tmp3 = (tmp3 + 32) >> 6; tmp4 = (tmp4 + 32) >> 6; pu1_dst[0] = CLIP_U8(tmp1); pu1_dst[2] = CLIP_U8(tmp2); pu1_dst[1] = CLIP_U8(tmp3); pu1_dst[3] = CLIP_U8(tmp4); ht -= 2; pu1_src += src_strd; pu1_dst += dst_strd; } while(ht > 0); } else if(wd == 4) { WORD32 AB, CD; __m128i src_r1_16x8b, src_r2_16x8b, src_r3_16x8b; __m128i res1_AB_8x16b, res1_CD_8x16b, res1_8x16b, res1_16x8b; __m128i res2_AB_8x16b, res2_CD_8x16b, res2_8x16b, res2_16x8b; __m128i coeffAB_16x8b, coeffCD_16x8b, round_add32_8x16b; __m128i const_shuff_16x8b; AB = (B << 8) + A; CD = (D << 8) + C; coeffAB_16x8b = _mm_set1_epi16(AB); coeffCD_16x8b = _mm_set1_epi16(CD); round_add32_8x16b = _mm_set1_epi16(32); const_shuff_16x8b = _mm_setr_epi32(0x03010200, 0x05030402, 0x07050604, 0x09070806); src_r1_16x8b = _mm_loadu_si128((__m128i *)pu1_src); src_r1_16x8b = _mm_shuffle_epi8(src_r1_16x8b, const_shuff_16x8b); pu1_src += src_strd; do { src_r2_16x8b = _mm_loadu_si128((__m128i *)pu1_src); src_r3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); src_r2_16x8b = _mm_shuffle_epi8(src_r2_16x8b, const_shuff_16x8b); src_r3_16x8b = _mm_shuffle_epi8(src_r3_16x8b, const_shuff_16x8b); res1_AB_8x16b = _mm_maddubs_epi16(src_r1_16x8b, coeffAB_16x8b); res1_CD_8x16b = _mm_maddubs_epi16(src_r2_16x8b, coeffCD_16x8b); res2_AB_8x16b = _mm_maddubs_epi16(src_r2_16x8b, coeffAB_16x8b); res2_CD_8x16b = _mm_maddubs_epi16(src_r3_16x8b, coeffCD_16x8b); res1_8x16b = _mm_add_epi16(res1_AB_8x16b, res1_CD_8x16b); res2_8x16b = _mm_add_epi16(res2_AB_8x16b, res2_CD_8x16b); res1_8x16b = _mm_add_epi16(res1_8x16b, round_add32_8x16b); res2_8x16b = _mm_add_epi16(res2_8x16b, round_add32_8x16b); res1_8x16b = _mm_srai_epi16(res1_8x16b, 6); res2_8x16b = _mm_srai_epi16(res2_8x16b, 6); res1_16x8b = _mm_packus_epi16(res1_8x16b, res1_8x16b); res2_16x8b = _mm_packus_epi16(res2_8x16b, res2_8x16b); _mm_storel_epi64((__m128i *)pu1_dst, res1_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res2_16x8b); src_r1_16x8b = src_r3_16x8b; ht -= 2; pu1_src += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else // wd == 8 { WORD32 AB, CD; __m128i src_r1l_16x8b, src_r2l_16x8b; __m128i src_r1h_16x8b, src_r2h_16x8b; __m128i res_l_AB_8x16b, res_l_CD_8x16b; __m128i res_h_AB_8x16b, res_h_CD_8x16b; __m128i res_l_8x16b, res_h_8x16b, res_16x8b; __m128i coeffAB_16x8b, coeffCD_16x8b, round_add32_8x16b; __m128i const_shuff_16x8b; AB = (B << 8) + A; CD = (D << 8) + C; coeffAB_16x8b = _mm_set1_epi16(AB); coeffCD_16x8b = _mm_set1_epi16(CD); round_add32_8x16b = _mm_set1_epi16(32); const_shuff_16x8b = _mm_setr_epi32(0x03010200, 0x05030402, 0x07050604, 0x09070806); src_r1l_16x8b = _mm_loadu_si128((__m128i *)pu1_src); src_r1h_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); src_r1l_16x8b = _mm_shuffle_epi8(src_r1l_16x8b, const_shuff_16x8b); src_r1h_16x8b = _mm_shuffle_epi8(src_r1h_16x8b, const_shuff_16x8b); pu1_src += src_strd; do { //row 1 src_r2l_16x8b = _mm_loadu_si128((__m128i *)pu1_src); src_r2h_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); src_r2l_16x8b = _mm_shuffle_epi8(src_r2l_16x8b, const_shuff_16x8b); src_r2h_16x8b = _mm_shuffle_epi8(src_r2h_16x8b, const_shuff_16x8b); res_l_AB_8x16b = _mm_maddubs_epi16(src_r1l_16x8b, coeffAB_16x8b); res_h_AB_8x16b = _mm_maddubs_epi16(src_r1h_16x8b, coeffAB_16x8b); res_l_CD_8x16b = _mm_maddubs_epi16(src_r2l_16x8b, coeffCD_16x8b); res_h_CD_8x16b = _mm_maddubs_epi16(src_r2h_16x8b, coeffCD_16x8b); res_l_8x16b = _mm_add_epi16(res_l_AB_8x16b, round_add32_8x16b); res_h_8x16b = _mm_add_epi16(res_h_AB_8x16b, round_add32_8x16b); res_l_8x16b = _mm_add_epi16(res_l_8x16b, res_l_CD_8x16b); res_h_8x16b = _mm_add_epi16(res_h_8x16b, res_h_CD_8x16b); res_l_8x16b = _mm_srai_epi16(res_l_8x16b, 6); res_h_8x16b = _mm_srai_epi16(res_h_8x16b, 6); res_16x8b = _mm_packus_epi16(res_l_8x16b, res_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); pu1_src += src_strd; pu1_dst += dst_strd; //row 2 src_r1l_16x8b = _mm_loadu_si128((__m128i *)pu1_src); src_r1h_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); src_r1l_16x8b = _mm_shuffle_epi8(src_r1l_16x8b, const_shuff_16x8b); src_r1h_16x8b = _mm_shuffle_epi8(src_r1h_16x8b, const_shuff_16x8b); res_l_AB_8x16b = _mm_maddubs_epi16(src_r2l_16x8b, coeffAB_16x8b); res_h_AB_8x16b = _mm_maddubs_epi16(src_r2h_16x8b, coeffAB_16x8b); res_l_CD_8x16b = _mm_maddubs_epi16(src_r1l_16x8b, coeffCD_16x8b); res_h_CD_8x16b = _mm_maddubs_epi16(src_r1h_16x8b, coeffCD_16x8b); res_l_8x16b = _mm_add_epi16(res_l_AB_8x16b, round_add32_8x16b); res_h_8x16b = _mm_add_epi16(res_h_AB_8x16b, round_add32_8x16b); res_l_8x16b = _mm_add_epi16(res_l_8x16b, res_l_CD_8x16b); res_h_8x16b = _mm_add_epi16(res_h_8x16b, res_h_CD_8x16b); res_l_8x16b = _mm_srai_epi16(res_l_8x16b, 6); res_h_8x16b = _mm_srai_epi16(res_h_8x16b, 6); res_16x8b = _mm_packus_epi16(res_l_8x16b, res_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); pu1_src += src_strd; pu1_dst += dst_strd; //row 3 src_r2l_16x8b = _mm_loadu_si128((__m128i *)pu1_src); src_r2h_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); src_r2l_16x8b = _mm_shuffle_epi8(src_r2l_16x8b, const_shuff_16x8b); src_r2h_16x8b = _mm_shuffle_epi8(src_r2h_16x8b, const_shuff_16x8b); res_l_AB_8x16b = _mm_maddubs_epi16(src_r1l_16x8b, coeffAB_16x8b); res_h_AB_8x16b = _mm_maddubs_epi16(src_r1h_16x8b, coeffAB_16x8b); res_l_CD_8x16b = _mm_maddubs_epi16(src_r2l_16x8b, coeffCD_16x8b); res_h_CD_8x16b = _mm_maddubs_epi16(src_r2h_16x8b, coeffCD_16x8b); res_l_8x16b = _mm_add_epi16(res_l_AB_8x16b, round_add32_8x16b); res_h_8x16b = _mm_add_epi16(res_h_AB_8x16b, round_add32_8x16b); res_l_8x16b = _mm_add_epi16(res_l_8x16b, res_l_CD_8x16b); res_h_8x16b = _mm_add_epi16(res_h_8x16b, res_h_CD_8x16b); res_l_8x16b = _mm_srai_epi16(res_l_8x16b, 6); res_h_8x16b = _mm_srai_epi16(res_h_8x16b, 6); res_16x8b = _mm_packus_epi16(res_l_8x16b, res_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); pu1_src += src_strd; pu1_dst += dst_strd; //row 1 src_r1l_16x8b = _mm_loadu_si128((__m128i *)pu1_src); src_r1h_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); src_r1l_16x8b = _mm_shuffle_epi8(src_r1l_16x8b, const_shuff_16x8b); src_r1h_16x8b = _mm_shuffle_epi8(src_r1h_16x8b, const_shuff_16x8b); res_l_AB_8x16b = _mm_maddubs_epi16(src_r2l_16x8b, coeffAB_16x8b); res_h_AB_8x16b = _mm_maddubs_epi16(src_r2h_16x8b, coeffAB_16x8b); res_l_CD_8x16b = _mm_maddubs_epi16(src_r1l_16x8b, coeffCD_16x8b); res_h_CD_8x16b = _mm_maddubs_epi16(src_r1h_16x8b, coeffCD_16x8b); res_l_8x16b = _mm_add_epi16(res_l_AB_8x16b, round_add32_8x16b); res_h_8x16b = _mm_add_epi16(res_h_AB_8x16b, round_add32_8x16b); res_l_8x16b = _mm_add_epi16(res_l_8x16b, res_l_CD_8x16b); res_h_8x16b = _mm_add_epi16(res_h_8x16b, res_h_CD_8x16b); res_l_8x16b = _mm_srai_epi16(res_l_8x16b, 6); res_h_8x16b = _mm_srai_epi16(res_h_8x16b, 6); res_16x8b = _mm_packus_epi16(res_l_8x16b, res_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); ht -= 4; pu1_src += src_strd; pu1_dst += dst_strd; } while(ht > 0); } } ================================================ FILE: dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_dc_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_iquant_itrans_recon_dc_ssse3.c * * @brief * Contains function definitions for inverse quantization, inverse * transform and reconstruction * * @author * Mohit [100664] * * @par List of Functions: * - ih264_iquant_itrans_recon_4x4_dc_ssse3() * - ih264_iquant_itrans_recon_8x8_dc_ssse3() * * @remarks * None * ******************************************************************************* */ /* User include files */ #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_trans_macros.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_trans_data.h" #include "ih264_size_defs.h" #include "ih264_structs.h" #include "ih264_trans_quant_itrans_iquant.h" #include <immintrin.h> #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /* ******************************************************************************** * * @brief This function reconstructs a 4x4 sub block from quantized resiude and * prediction buffer for dc input pattern only, i.e. only the (0,0) element of the input * 4x4 block is non-zero. For complete function, refer ih264_iquant_itrans_recon_ssse3.c * * @par Description: * The quantized residue is first inverse quantized, then inverse transformed. * This inverse transformed content is added to the prediction buffer to recon- * struct the end output * * @param[in] pi2_src * quantized 4x4 block * * @param[in] pu1_pred * prediction 4x4 block * * @param[out] pu1_out * reconstructed 4x4 block * * @param[in] src_strd * quantization buffer stride * * @param[in] pred_strd, * Prediction buffer stride * * @param[in] out_strd * recon buffer Stride * * @param[in] pu2_scaling_list * pointer to scaling list * * @param[in] pu2_norm_adjust * pointer to inverse scale matrix * * @param[in] u4_qp_div_6 * Floor (qp/6) * * @param[in] pi4_tmp * temporary buffer of size 1*16 * * @returns none * * @remarks none * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_iquant_itrans_recon_4x4_dc_ssse3(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD16 *pi2_tmp, WORD32 iq_start_idx, WORD16 *pi2_dc_ld_addr) { UWORD32 *pu4_out = (UWORD32 *)pu1_out; WORD32 q0 = pi2_src[0]; WORD16 i_macro, rnd_fact = (u4_qp_div_6 < 4) ? 1 << (3 - u4_qp_div_6) : 0; __m128i predload_r,pred_r0, pred_r1, pred_r2, pred_r3; __m128i sign_reg; __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero __m128i temp4, temp5, temp6, temp7; __m128i value_add; UNUSED (pi2_tmp); INV_QUANT(q0, pu2_iscal_mat[0], pu2_weigh_mat[0], u4_qp_div_6, rnd_fact, 4); if (iq_start_idx != 0 ) q0 = pi2_dc_ld_addr[0]; // Restoring dc value for intra case i_macro = ((q0 + 32) >> 6); value_add = _mm_set1_epi16(i_macro); zero_8x16b = _mm_setzero_si128(); // all bits reset to zero //Load pred buffer predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits pred_r0 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p00 p01 p02 p03 0 0 0 0 -- all 16 bits predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits pred_r1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p10 p11 p12 p13 0 0 0 0 -- all 16 bits predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[2*pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits pred_r2 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p20 p21 p22 p23 0 0 0 0 -- all 16 bits predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[3*pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits pred_r3 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p30 p31 p32 p33 0 0 0 0 -- all 16 bits pred_r0 = _mm_unpacklo_epi64(pred_r0, pred_r1); //p00 p01 p02 p03 p10 p11 p12 p13 pred_r2 = _mm_unpacklo_epi64(pred_r2, pred_r3); //p20 p21 p22p p23 p30 p31 p32 p33 temp4 = _mm_add_epi16(value_add, pred_r0); temp5 = _mm_add_epi16(value_add, pred_r2); /*------------------------------------------------------------------*/ //Clipping the results to 8 bits sign_reg = _mm_cmpgt_epi16(temp4, zero_8x16b); // sign check temp4 = _mm_and_si128(temp4, sign_reg); sign_reg = _mm_cmpgt_epi16(temp5, zero_8x16b); // sign check temp5 = _mm_and_si128(temp5, sign_reg); temp4 = _mm_packus_epi16(temp4,temp5); temp5 = _mm_srli_si128(temp4,4); temp6 = _mm_srli_si128(temp5,4); temp7 = _mm_srli_si128(temp6,4); *pu4_out = _mm_cvtsi128_si32(temp4); pu1_out += out_strd; pu4_out = (UWORD32 *)(pu1_out); *(pu4_out) = _mm_cvtsi128_si32(temp5); pu1_out += out_strd; pu4_out = (UWORD32 *)(pu1_out); *(pu4_out) = _mm_cvtsi128_si32(temp6); pu1_out += out_strd; pu4_out = (UWORD32 *)(pu1_out); *(pu4_out) = _mm_cvtsi128_si32(temp7); } /** ******************************************************************************* * * @brief * This function performs inverse quant and Inverse transform type Ci4 for 8x8 block * for dc input pattern only, i.e. only the (0,0) element of the input 8x8 block is * non-zero. For complete function, refer ih264_iquant_itrans_recon_ssse3.c * * @par Description: * Performs inverse transform Ci8 and adds the residue to get the * reconstructed block * * @param[in] pi2_src * Input 8x8coefficients * * @param[in] pu1_pred * Prediction 8x8 block * * @param[out] pu1_recon * Output 8x8 block * * @param[in] q_div * QP/6 * * @param[in] q_rem * QP%6 * * @param[in] q_lev * Quantizer level * * @param[in] u4_src_stride * Input stride * * @param[in] u4_pred_stride, * Prediction stride * * @param[in] u4_out_stride * Output Stride * * @param[in] pi4_tmp * temporary buffer of size 1*64 * the tmp for each block * * @param[in] pu4_iquant_mat * Pointer to the inverse quantization matrix * * @returns Void * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_iquant_itrans_recon_8x8_dc_ssse3 (WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscale_mat, const UWORD16 *pu2_weigh_mat, UWORD32 qp_div, WORD16 *pi2_tmp, WORD32 iq_start_idx, WORD16 *pi2_dc_ld_addr) { WORD32 q0 = pi2_src[0]; WORD16 i_macro, rnd_fact = (qp_div < 6) ? 1 << (5 - qp_div) : 0; __m128i predload_r,pred_r0, pred_r1, pred_r2, pred_r3,pred_r4,pred_r5,pred_r6,pred_r7; __m128i sign_reg; __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero __m128i temp1,temp2,temp3,temp4, temp5, temp6, temp7,temp8; __m128i value_add; UNUSED (pi2_tmp); UNUSED (iq_start_idx); UNUSED (pi2_dc_ld_addr); INV_QUANT(q0, pu2_iscale_mat[0], pu2_weigh_mat[0], qp_div, rnd_fact, 6); i_macro = ((q0 + 32) >> 6); value_add = _mm_set1_epi16(i_macro); //Load pred buffer row 0 predload_r = _mm_loadl_epi64((__m128i *)(&pu1_pred[0])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r0 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 1 predload_r = _mm_loadl_epi64((__m128i *)(&pu1_pred[pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 2 predload_r = _mm_loadl_epi64( (__m128i *)(&pu1_pred[2 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r2 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 3 predload_r = _mm_loadl_epi64( (__m128i *)(&pu1_pred[3 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r3 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 4 predload_r = _mm_loadl_epi64( (__m128i *)(&pu1_pred[4 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r4 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 5 predload_r = _mm_loadl_epi64( (__m128i *)(&pu1_pred[5 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bit pred_r5 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 6 predload_r = _mm_loadl_epi64( (__m128i *)(&pu1_pred[6 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r6 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 7 predload_r = _mm_loadl_epi64( (__m128i *)(&pu1_pred[7 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r7 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits temp1 = _mm_add_epi16(value_add, pred_r0); temp2 = _mm_add_epi16(value_add, pred_r1); temp3 = _mm_add_epi16(value_add, pred_r2); temp4 = _mm_add_epi16(value_add, pred_r3); temp5 = _mm_add_epi16(value_add, pred_r4); temp6 = _mm_add_epi16(value_add, pred_r5); temp7 = _mm_add_epi16(value_add, pred_r6); temp8 = _mm_add_epi16(value_add, pred_r7); /*------------------------------------------------------------------*/ //Clipping the results to 8 bits sign_reg = _mm_cmpgt_epi16(temp1, zero_8x16b); // sign check temp1 = _mm_and_si128(temp1, sign_reg); sign_reg = _mm_cmpgt_epi16(temp2, zero_8x16b); // sign check temp2 = _mm_and_si128(temp2, sign_reg); sign_reg = _mm_cmpgt_epi16(temp3, zero_8x16b); // sign check temp3 = _mm_and_si128(temp3, sign_reg); sign_reg = _mm_cmpgt_epi16(temp4, zero_8x16b); // sign check temp4 = _mm_and_si128(temp4, sign_reg); sign_reg = _mm_cmpgt_epi16(temp5, zero_8x16b); // sign check temp5 = _mm_and_si128(temp5, sign_reg); sign_reg = _mm_cmpgt_epi16(temp6, zero_8x16b); // sign check temp6 = _mm_and_si128(temp6, sign_reg); sign_reg = _mm_cmpgt_epi16(temp7, zero_8x16b); // sign check temp7 = _mm_and_si128(temp7, sign_reg); sign_reg = _mm_cmpgt_epi16(temp8, zero_8x16b); // sign check temp8 = _mm_and_si128(temp8, sign_reg); temp1 = _mm_packus_epi16(temp1, zero_8x16b); temp2 = _mm_packus_epi16(temp2, zero_8x16b); temp3 = _mm_packus_epi16(temp3, zero_8x16b); temp4 = _mm_packus_epi16(temp4, zero_8x16b); temp5 = _mm_packus_epi16(temp5, zero_8x16b); temp6 = _mm_packus_epi16(temp6, zero_8x16b); temp7 = _mm_packus_epi16(temp7, zero_8x16b); temp8 = _mm_packus_epi16(temp8, zero_8x16b); _mm_storel_epi64((__m128i *)(&pu1_out[0]), temp1); _mm_storel_epi64((__m128i *)(&pu1_out[out_strd]), temp2); _mm_storel_epi64((__m128i *)(&pu1_out[2 * out_strd]), temp3); _mm_storel_epi64((__m128i *)(&pu1_out[3 * out_strd]), temp4); _mm_storel_epi64((__m128i *)(&pu1_out[4 * out_strd]), temp5); _mm_storel_epi64((__m128i *)(&pu1_out[5 * out_strd]), temp6); _mm_storel_epi64((__m128i *)(&pu1_out[6 * out_strd]), temp7); _mm_storel_epi64((__m128i *)(&pu1_out[7 * out_strd]), temp8); } /* ******************************************************************************** * * @brief This function reconstructs a 4x4 sub block from quantized chroma resiude and * prediction buffer * * @par Description: * The quantized residue is first inverse quantized, then inverse transformed. * This inverse transformed content is added to the prediction buffer to recon- * struct the end output * * @param[in] pi2_src * quantized 4x4 block * * @param[in] pu1_pred * prediction 4x4 block * * @param[out] pu1_out * reconstructed 4x4 block * * @param[in] src_strd * quantization buffer stride * * @param[in] pred_strd, * Prediction buffer stride * * @param[in] out_strd * recon buffer Stride * * @param[in] pu2_scaling_list * pointer to scaling list * * @param[in] pu2_norm_adjust * pointer to inverse scale matrix * * @param[in] u4_qp_div_6 * Floor (qp/6) * * @param[in] pi4_tmp * temporary buffer of size 1*16 * * @returns none * * @remarks none * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_iquant_itrans_recon_chroma_4x4_dc_ssse3(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD16 *pi2_tmp, WORD16 *pi2_dc_src) { WORD16 q0 = pi2_dc_src[0]; // DC value won't be dequantized for chroma inverse transform WORD16 i_macro = ((q0 + 32) >> 6); __m128i pred_r0, pred_r1, pred_r2, pred_r3, sign_reg; __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero __m128i chroma_mask = _mm_set1_epi16 (0xFF); __m128i value_add = _mm_set1_epi16(i_macro); __m128i out_r0, out_r1, out_r2, out_r3; UNUSED (pi2_src); UNUSED (pu2_iscal_mat); UNUSED (pu2_weigh_mat); UNUSED (u4_qp_div_6); UNUSED (pi2_tmp); //Load pred buffer pred_r0 = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits pred_r1 = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits pred_r2 = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits pred_r3 = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits pred_r0 = _mm_and_si128(pred_r0, chroma_mask); pred_r1 = _mm_and_si128(pred_r1, chroma_mask); pred_r2 = _mm_and_si128(pred_r2, chroma_mask); pred_r3 = _mm_and_si128(pred_r3, chroma_mask); pred_r0 = _mm_unpacklo_epi64(pred_r0, pred_r1); //p00 p01 p02 p03 p10 p11 p12 p13 pred_r2 = _mm_unpacklo_epi64(pred_r2, pred_r3); //p20 p21 p22p p23 p30 p31 p32 p33 pred_r0 = _mm_add_epi16(value_add, pred_r0); pred_r2 = _mm_add_epi16(value_add, pred_r2); /*------------------------------------------------------------------*/ //Clipping the results to 8 bits sign_reg = _mm_cmpgt_epi16(pred_r0, zero_8x16b); // sign check pred_r0 = _mm_and_si128(pred_r0, sign_reg); sign_reg = _mm_cmpgt_epi16(pred_r2, zero_8x16b); pred_r2 = _mm_and_si128(pred_r2, sign_reg); pred_r0 = _mm_packus_epi16(pred_r0, pred_r2); pred_r1 = _mm_srli_si128(pred_r0, 4); pred_r2 = _mm_srli_si128(pred_r1, 4); pred_r3 = _mm_srli_si128(pred_r2, 4); pred_r0 = _mm_unpacklo_epi8(pred_r0, zero_8x16b); //p00 p01 p02 p03 -- all 16 bits pred_r1 = _mm_unpacklo_epi8(pred_r1, zero_8x16b); //p10 p11 p12 p13 -- all 16 bits pred_r2 = _mm_unpacklo_epi8(pred_r2, zero_8x16b); //p20 p21 p22 p23 -- all 16 bits pred_r3 = _mm_unpacklo_epi8(pred_r3, zero_8x16b); //p30 p31 p32 p33 -- all 16 bits chroma_mask = _mm_set1_epi16 (0xFF00); out_r0 = _mm_loadl_epi64((__m128i *) (&pu1_out[0])); out_r1 = _mm_loadl_epi64((__m128i *) (&pu1_out[out_strd])); out_r2 = _mm_loadl_epi64((__m128i *) (&pu1_out[2 * out_strd])); out_r3 = _mm_loadl_epi64((__m128i *) (&pu1_out[3 * out_strd])); out_r0 = _mm_and_si128(out_r0, chroma_mask); out_r1 = _mm_and_si128(out_r1, chroma_mask); out_r2 = _mm_and_si128(out_r2, chroma_mask); out_r3 = _mm_and_si128(out_r3, chroma_mask); out_r0 = _mm_add_epi8(out_r0, pred_r0); out_r1 = _mm_add_epi8(out_r1, pred_r1); out_r2 = _mm_add_epi8(out_r2, pred_r2); out_r3 = _mm_add_epi8(out_r3, pred_r3); _mm_storel_epi64((__m128i *)(&pu1_out[0]), out_r0); _mm_storel_epi64((__m128i *)(&pu1_out[out_strd]), out_r1); _mm_storel_epi64((__m128i *)(&pu1_out[2 * out_strd]), out_r2); _mm_storel_epi64((__m128i *)(&pu1_out[3 * out_strd]), out_r3); } ================================================ FILE: dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_sse42.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_iquant_itrans_recon_sse42.c * * @brief * Contains function definitions for inverse quantization, inverse * transform and reconstruction * * @author * Mohit [100664] * * @par List of Functions: * - ih264_iquant_itrans_recon_4x4_sse42() * - ih264_iquant_itrans_recon_chroma_4x4_sse42() * * @remarks * None * ******************************************************************************* */ /* User include files */ #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_trans_macros.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_trans_data.h" #include "ih264_size_defs.h" #include "ih264_structs.h" #include "ih264_trans_quant_itrans_iquant.h" #include <immintrin.h> #ifdef __GNUC__ #define ATTRIBUTE_SSE42 __attribute__((target("sse4.2"))) #else #define ATTRIBUTE_SSE42 #endif /* ******************************************************************************** * * @brief This function reconstructs a 4x4 sub block from quantized resiude and * prediction buffer * * @par Description: * The quantized residue is first inverse quantized, then inverse transformed. * This inverse transformed content is added to the prediction buffer to recon- * struct the end output * * @param[in] pi2_src * quantized 4x4 block * * @param[in] pu1_pred * prediction 4x4 block * * @param[out] pu1_out * reconstructed 4x4 block * * @param[in] src_strd * quantization buffer stride * * @param[in] pred_strd, * Prediction buffer stride * * @param[in] out_strd * recon buffer Stride * * @param[in] pu2_scaling_list * pointer to scaling list * * @param[in] pu2_norm_adjust * pointer to inverse scale matrix * * @param[in] u4_qp_div_6 * Floor (qp/6) * * @param[in] pi4_tmp * temporary buffer of size 1*16 * * @returns none * * @remarks none * ******************************************************************************* */ ATTRIBUTE_SSE42 void ih264_iquant_itrans_recon_4x4_sse42(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD16 *pi2_tmp, WORD32 iq_start_idx, WORD16 *pi2_dc_ld_addr) { UWORD32 *pu4_out = (UWORD32 *) pu1_out; __m128i src_r0_r1, src_r2_r3; __m128i src_r0, src_r1, src_r2, src_r3; __m128i scalemat_r0_r1, scalemat_r2_r3; __m128i pred_r0, pred_r1, pred_r2, pred_r3; __m128i sign_reg, dequant_r0_r1, dequant_r2_r3; __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero __m128i temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; __m128i resq_r0, resq_r1, resq_r2, resq_r3; __m128i add_rshift = _mm_set1_epi32((u4_qp_div_6 < 4) ? (1 << (3 - u4_qp_div_6)) : 0); __m128i value_32 = _mm_set1_epi32(32); UNUSED (pi2_tmp); /*************************************************************/ /* Dequantization of coefficients. Will be replaced by SIMD */ /* operations on platform */ /*************************************************************/ src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row scalemat_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat)); //b00 b01 b02 b03 b10 b11 b12 b13 -- the scaling matrix 0th,1st row scalemat_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat + 8)); //b20 b21 b22 b23 b30 b31 b32 b33 -- the scaling matrix 2nd,3rd row dequant_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat)); //q00 q01 q02 q03 q10 q11 q12 q13 -- all 16 bits dequant_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat + 8)); //q20 q21 q22 q23 q30 q31 q32 q33 -- all 16 bits temp0 = _mm_mullo_epi16(scalemat_r0_r1, dequant_r0_r1); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result temp1 = _mm_mullo_epi16(scalemat_r2_r3, dequant_r2_r3); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result temp4 = _mm_unpacklo_epi16(temp0, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long temp5 = _mm_unpackhi_epi16(temp0, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long temp6 = _mm_unpacklo_epi16(temp1, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long temp7 = _mm_unpackhi_epi16(temp1, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long src_r0 = _mm_unpacklo_epi16(src_r0_r1, zero_8x16b); // a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r1 = _mm_unpackhi_epi16(src_r0_r1, zero_8x16b); // a10 0 a11 0 a12 0 a13 0 -- 16 bit long src_r2 = _mm_unpacklo_epi16(src_r2_r3, zero_8x16b); // a20 0 a21 0 a22 0 a23 0 -- 16 bit long src_r3 = _mm_unpackhi_epi16(src_r2_r3, zero_8x16b); // a30 0 a31 0 a32 0 a33 0 -- 16 bit long temp4 = _mm_madd_epi16(src_r0, temp4); //a00*b00*q00 a10*b10*q10 a20*b20*q20 a30*b30 q30 -- 32 bits long temp5 = _mm_madd_epi16(src_r1, temp5); temp6 = _mm_madd_epi16(src_r2, temp6); temp7 = _mm_madd_epi16(src_r3, temp7); if (u4_qp_div_6 >= 4) { resq_r0 = _mm_slli_epi32(temp4, u4_qp_div_6 - 4); resq_r1 = _mm_slli_epi32(temp5, u4_qp_div_6 - 4); resq_r2 = _mm_slli_epi32(temp6, u4_qp_div_6 - 4); resq_r3 = _mm_slli_epi32(temp7, u4_qp_div_6 - 4); } else { temp4 = _mm_add_epi32(temp4, add_rshift); temp5 = _mm_add_epi32(temp5, add_rshift); temp6 = _mm_add_epi32(temp6, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r0 = _mm_srai_epi32(temp4, 4 - u4_qp_div_6); resq_r1 = _mm_srai_epi32(temp5, 4 - u4_qp_div_6); resq_r2 = _mm_srai_epi32(temp6, 4 - u4_qp_div_6); resq_r3 = _mm_srai_epi32(temp7, 4 - u4_qp_div_6); } if (iq_start_idx == 1) resq_r0 = _mm_insert_epi32(resq_r0,(WORD32)pi2_dc_ld_addr[0],0); /* Perform Inverse transform */ /*-------------------------------------------------------------*/ /* IDCT [ Horizontal transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 a1 a2 a3 * b0 b1 b2 b3 * c0 c1 c2 c3 * d0 d1 d2 d3 */ temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 b0 a1 b1 temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //c0 d0 c1 d1 temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //a2 b2 a3 b3 temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 d2 c3 d3 resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 b0 c0 d0 resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //a1 b1 c1 d1 resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //a2 b2 c2 d2 resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //a3 b3 c3 d3 //Transform starts -- horizontal transform /*------------------------------------------------------------------*/ /* z0 = w0 + w2 */ temp0 = _mm_add_epi32(resq_r0, resq_r2); /* z1 = w0 - w2 */ temp1 = _mm_sub_epi32(resq_r0, resq_r2); /* z2 = (w1 >> 1) - w3 */ temp2 = _mm_srai_epi32(resq_r1, 1); //(w1>>1) temp2 = _mm_sub_epi32(temp2, resq_r3); //(w1>>1) - w3 /* z3 = w1 + (w3 >> 1) */ temp3 = _mm_srai_epi32(resq_r3, 1); //(w3>>1) + w1 temp3 = _mm_add_epi32(temp3, resq_r1); /*----------------------------------------------------------*/ /* x0 = z0 + z3 */ resq_r0 = _mm_add_epi32(temp0, temp3); /* x1 = z1 + z2 */ resq_r1 = _mm_add_epi32(temp1, temp2); /* x2 = z1 - z2 */ resq_r2 = _mm_sub_epi32(temp1, temp2); /* x3 = z0 - z3 */ resq_r3 = _mm_sub_epi32(temp0, temp3); // Matrix transpose /* * a0 b0 c0 d0 * a1 b1 c1 d1 * a2 b2 c2 d2 * a3 b3 c3 d3 */ temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 a1 b0 b1 temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //a2 a3 b2 b3 temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //c0 c1 d0 d1 temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 c3 d2 d3 resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 a1 a2 a3 resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //b0 b1 b2 b3 resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //c0 c1 c2 c3 resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //d0 d1 d2 d3 //Transform ends -- horizontal transform //Load pred buffer pred_r0 = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits pred_r1 = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits pred_r2 = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits pred_r3 = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits pred_r0 = _mm_cvtepu8_epi32(pred_r0); //p00 p01 p02 p03 -- all 32 bits pred_r1 = _mm_cvtepu8_epi32(pred_r1); //p10 p11 p12 p13 -- all 32 bits pred_r2 = _mm_cvtepu8_epi32(pred_r2); //p20 p21 p22 p23 -- all 32 bits pred_r3 = _mm_cvtepu8_epi32(pred_r3); //p30 p31 p32 p33 -- all 32 bits /*--------------------------------------------------------------*/ /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ /* */ /* Add the prediction and store it back to same buffer */ /*--------------------------------------------------------------*/ /* z0j = y0j + y2j */ temp0 = _mm_add_epi32(resq_r0, resq_r2); /* z1j = y0j - y2j */ temp1 = _mm_sub_epi32(resq_r0, resq_r2); /* z2j = (y1j>>1) - y3j */ temp2 = _mm_srai_epi32(resq_r1, 1); //(y1j>>1) temp2 = _mm_sub_epi32(temp2, resq_r3); /* z3j = y1j + (y3j>>1) */ temp3 = _mm_srai_epi32(resq_r3, 1); //(y3j>>1) temp3 = _mm_add_epi32(temp3, resq_r1); /* x0j = z0j + z3j */ temp4 = _mm_add_epi32(temp0, temp3); temp4 = _mm_add_epi32(temp4, value_32); temp4 = _mm_srai_epi32(temp4, 6); temp4 = _mm_add_epi32(temp4, pred_r0); /* x1j = z1j + z2j */ temp5 = _mm_add_epi32(temp1, temp2); temp5 = _mm_add_epi32(temp5, value_32); temp5 = _mm_srai_epi32(temp5, 6); temp5 = _mm_add_epi32(temp5, pred_r1); /* x2j = z1j - z2j */ temp6 = _mm_sub_epi32(temp1, temp2); temp6 = _mm_add_epi32(temp6, value_32); temp6 = _mm_srai_epi32(temp6, 6); temp6 = _mm_add_epi32(temp6, pred_r2); /* x3j = z0j - z3j */ temp7 = _mm_sub_epi32(temp0, temp3); temp7 = _mm_add_epi32(temp7, value_32); temp7 = _mm_srai_epi32(temp7, 6); temp7 = _mm_add_epi32(temp7, pred_r3); // 32-bit to 16-bit conversion temp0 = _mm_packs_epi32(temp4, temp5); temp1 = _mm_packs_epi32(temp6, temp7); /*------------------------------------------------------------------*/ //Clipping the results to 8 bits sign_reg = _mm_cmpgt_epi16(temp0, zero_8x16b); // sign check temp0 = _mm_and_si128(temp0, sign_reg); sign_reg = _mm_cmpgt_epi16(temp1, zero_8x16b); temp1 = _mm_and_si128(temp1, sign_reg); resq_r0 = _mm_packus_epi16(temp0, temp1); resq_r1 = _mm_srli_si128(resq_r0, 4); resq_r2 = _mm_srli_si128(resq_r1, 4); resq_r3 = _mm_srli_si128(resq_r2, 4); *pu4_out = _mm_cvtsi128_si32(resq_r0); pu1_out += out_strd; pu4_out = (UWORD32 *) (pu1_out); *(pu4_out) = _mm_cvtsi128_si32(resq_r1); pu1_out += out_strd; pu4_out = (UWORD32 *) (pu1_out); *(pu4_out) = _mm_cvtsi128_si32(resq_r2); pu1_out += out_strd; pu4_out = (UWORD32 *) (pu1_out); *(pu4_out) = _mm_cvtsi128_si32(resq_r3); } /* ******************************************************************************** * * @brief This function reconstructs a 4x4 sub block from quantized chroma resiude and * prediction buffer * * @par Description: * The quantized residue is first inverse quantized, then inverse transformed. * This inverse transformed content is added to the prediction buffer to recon- * struct the end output * * @param[in] pi2_src * quantized 4x4 block * * @param[in] pu1_pred * prediction 4x4 block * * @param[out] pu1_out * reconstructed 4x4 block * * @param[in] src_strd * quantization buffer stride * * @param[in] pred_strd, * Prediction buffer stride * * @param[in] out_strd * recon buffer Stride * * @param[in] pu2_scaling_list * pointer to scaling list * * @param[in] pu2_norm_adjust * pointer to inverse scale matrix * * @param[in] u4_qp_div_6 * Floor (qp/6) * * @param[in] pi4_tmp * temporary buffer of size 1*16 * * @returns none * * @remarks none * ******************************************************************************* */ ATTRIBUTE_SSE42 void ih264_iquant_itrans_recon_chroma_4x4_sse42(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD16 *pi2_tmp, WORD16 *pi2_dc_ld_addr) { __m128i src_r0_r1, src_r2_r3; __m128i src_r0, src_r1, src_r2, src_r3; __m128i scalemat_r0_r1, scalemat_r2_r3; __m128i pred_r0, pred_r1, pred_r2, pred_r3; __m128i sign_reg, dequant_r0_r1, dequant_r2_r3; __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero __m128i temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; __m128i resq_r0, resq_r1, resq_r2, resq_r3; __m128i add_rshift = _mm_set1_epi32((u4_qp_div_6 < 4) ? (1 << (3 - u4_qp_div_6)) : 0); __m128i value_32 = _mm_set1_epi32(32); __m128i chroma_mask = _mm_set1_epi16 (0xFF); __m128i out_r0, out_r1, out_r2, out_r3; UNUSED (pi2_tmp); /*************************************************************/ /* Dequantization of coefficients. Will be replaced by SIMD */ /* operations on platform */ /*************************************************************/ src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row scalemat_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat)); //b00 b01 b02 b03 b10 b11 b12 b13 -- the scaling matrix 0th,1st row scalemat_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat + 8)); //b20 b21 b22 b23 b30 b31 b32 b33 -- the scaling matrix 2nd,3rd row dequant_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat)); //q00 q01 q02 q03 q10 q11 q12 q13 -- all 16 bits dequant_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat + 8)); //q20 q21 q22 q23 q30 q31 q32 q33 -- all 16 bits temp0 = _mm_mullo_epi16(scalemat_r0_r1, dequant_r0_r1); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result temp1 = _mm_mullo_epi16(scalemat_r2_r3, dequant_r2_r3); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result temp4 = _mm_unpacklo_epi16(temp0, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long temp5 = _mm_unpackhi_epi16(temp0, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long temp6 = _mm_unpacklo_epi16(temp1, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long temp7 = _mm_unpackhi_epi16(temp1, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long src_r0 = _mm_unpacklo_epi16(src_r0_r1, zero_8x16b); // a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r1 = _mm_unpackhi_epi16(src_r0_r1, zero_8x16b); // a10 0 a11 0 a12 0 a13 0 -- 16 bit long src_r2 = _mm_unpacklo_epi16(src_r2_r3, zero_8x16b); // a20 0 a21 0 a22 0 a23 0 -- 16 bit long src_r3 = _mm_unpackhi_epi16(src_r2_r3, zero_8x16b); // a30 0 a31 0 a32 0 a33 0 -- 16 bit long temp4 = _mm_madd_epi16(src_r0, temp4); //a00*b00*q00 a10*b10*q10 a20*b20*q20 a30*b30 q30 -- 32 bits long temp5 = _mm_madd_epi16(src_r1, temp5); temp6 = _mm_madd_epi16(src_r2, temp6); temp7 = _mm_madd_epi16(src_r3, temp7); if (u4_qp_div_6 >= 4) { resq_r0 = _mm_slli_epi32(temp4, u4_qp_div_6 - 4); resq_r1 = _mm_slli_epi32(temp5, u4_qp_div_6 - 4); resq_r2 = _mm_slli_epi32(temp6, u4_qp_div_6 - 4); resq_r3 = _mm_slli_epi32(temp7, u4_qp_div_6 - 4); } else { temp4 = _mm_add_epi32(temp4, add_rshift); temp5 = _mm_add_epi32(temp5, add_rshift); temp6 = _mm_add_epi32(temp6, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r0 = _mm_srai_epi32(temp4, 4 - u4_qp_div_6); resq_r1 = _mm_srai_epi32(temp5, 4 - u4_qp_div_6); resq_r2 = _mm_srai_epi32(temp6, 4 - u4_qp_div_6); resq_r3 = _mm_srai_epi32(temp7, 4 - u4_qp_div_6); } resq_r0 = _mm_insert_epi32(resq_r0,(WORD32)pi2_dc_ld_addr[0],0); /* Perform Inverse transform */ /*-------------------------------------------------------------*/ /* IDCT [ Horizontal transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 a1 a2 a3 * b0 b1 b2 b3 * c0 c1 c2 c3 * d0 d1 d2 d3 */ temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 b0 a1 b1 temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //c0 d0 c1 d1 temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //a2 b2 a3 b3 temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 d2 c3 d3 resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 b0 c0 d0 resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //a1 b1 c1 d1 resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //a2 b2 c2 d2 resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //a3 b3 c3 d3 //Transform starts -- horizontal transform /*------------------------------------------------------------------*/ /* z0 = w0 + w2 */ temp0 = _mm_add_epi32(resq_r0, resq_r2); /* z1 = w0 - w2 */ temp1 = _mm_sub_epi32(resq_r0, resq_r2); /* z2 = (w1 >> 1) - w3 */ temp2 = _mm_srai_epi32(resq_r1, 1); //(w1>>1) temp2 = _mm_sub_epi32(temp2, resq_r3); //(w1>>1) - w3 /* z3 = w1 + (w3 >> 1) */ temp3 = _mm_srai_epi32(resq_r3, 1); //(w3>>1) + w1 temp3 = _mm_add_epi32(temp3, resq_r1); /*----------------------------------------------------------*/ /* x0 = z0 + z3 */ resq_r0 = _mm_add_epi32(temp0, temp3); /* x1 = z1 + z2 */ resq_r1 = _mm_add_epi32(temp1, temp2); /* x2 = z1 - z2 */ resq_r2 = _mm_sub_epi32(temp1, temp2); /* x3 = z0 - z3 */ resq_r3 = _mm_sub_epi32(temp0, temp3); // Matrix transpose /* * a0 b0 c0 d0 * a1 b1 c1 d1 * a2 b2 c2 d2 * a3 b3 c3 d3 */ temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 a1 b0 b1 temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //a2 a3 b2 b3 temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //c0 c1 d0 d1 temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 c3 d2 d3 resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 a1 a2 a3 resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //b0 b1 b2 b3 resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //c0 c1 c2 c3 resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //d0 d1 d2 d3 //Transform ends -- horizontal transform //Load pred buffer pred_r0 = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits pred_r1 = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits pred_r2 = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits pred_r3 = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits pred_r0 = _mm_and_si128(pred_r0, chroma_mask); pred_r1 = _mm_and_si128(pred_r1, chroma_mask); pred_r2 = _mm_and_si128(pred_r2, chroma_mask); pred_r3 = _mm_and_si128(pred_r3, chroma_mask); pred_r0 = _mm_cvtepu16_epi32(pred_r0); //p00 p01 p02 p03 -- all 32 bits pred_r1 = _mm_cvtepu16_epi32(pred_r1); //p10 p11 p12 p13 -- all 32 bits pred_r2 = _mm_cvtepu16_epi32(pred_r2); //p20 p21 p22 p23 -- all 32 bits pred_r3 = _mm_cvtepu16_epi32(pred_r3); //p30 p31 p32 p33 -- all 32 bits /*--------------------------------------------------------------*/ /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ /* */ /* Add the prediction and store it back to same buffer */ /*--------------------------------------------------------------*/ /* z0j = y0j + y2j */ temp0 = _mm_add_epi32(resq_r0, resq_r2); /* z1j = y0j - y2j */ temp1 = _mm_sub_epi32(resq_r0, resq_r2); /* z2j = (y1j>>1) - y3j */ temp2 = _mm_srai_epi32(resq_r1, 1); //(y1j>>1) temp2 = _mm_sub_epi32(temp2, resq_r3); /* z3j = y1j + (y3j>>1) */ temp3 = _mm_srai_epi32(resq_r3, 1); //(y3j>>1) temp3 = _mm_add_epi32(temp3, resq_r1); /* x0j = z0j + z3j */ temp4 = _mm_add_epi32(temp0, temp3); temp4 = _mm_add_epi32(temp4, value_32); temp4 = _mm_srai_epi32(temp4, 6); temp4 = _mm_add_epi32(temp4, pred_r0); /* x1j = z1j + z2j */ temp5 = _mm_add_epi32(temp1, temp2); temp5 = _mm_add_epi32(temp5, value_32); temp5 = _mm_srai_epi32(temp5, 6); temp5 = _mm_add_epi32(temp5, pred_r1); /* x2j = z1j - z2j */ temp6 = _mm_sub_epi32(temp1, temp2); temp6 = _mm_add_epi32(temp6, value_32); temp6 = _mm_srai_epi32(temp6, 6); temp6 = _mm_add_epi32(temp6, pred_r2); /* x3j = z0j - z3j */ temp7 = _mm_sub_epi32(temp0, temp3); temp7 = _mm_add_epi32(temp7, value_32); temp7 = _mm_srai_epi32(temp7, 6); temp7 = _mm_add_epi32(temp7, pred_r3); // 32-bit to 16-bit conversion temp0 = _mm_packs_epi32(temp4, temp5); temp1 = _mm_packs_epi32(temp6, temp7); /*------------------------------------------------------------------*/ //Clipping the results to 8 bits sign_reg = _mm_cmpgt_epi16(temp0, zero_8x16b); // sign check temp0 = _mm_and_si128(temp0, sign_reg); sign_reg = _mm_cmpgt_epi16(temp1, zero_8x16b); temp1 = _mm_and_si128(temp1, sign_reg); resq_r0 = _mm_packus_epi16(temp0, temp1); resq_r1 = _mm_srli_si128(resq_r0, 4); resq_r2 = _mm_srli_si128(resq_r1, 4); resq_r3 = _mm_srli_si128(resq_r2, 4); resq_r0 = _mm_cvtepu8_epi16(resq_r0); //p00 p01 p02 p03 -- all 16 bits resq_r1 = _mm_cvtepu8_epi16(resq_r1); //p10 p11 p12 p13 -- all 16 bits resq_r2 = _mm_cvtepu8_epi16(resq_r2); //p20 p21 p22 p23 -- all 16 bits resq_r3 = _mm_cvtepu8_epi16(resq_r3); //p30 p31 p32 p33 -- all 16 bits chroma_mask = _mm_set1_epi16 (0xFF00); out_r0 = _mm_loadl_epi64((__m128i *) (&pu1_out[0])); out_r1 = _mm_loadl_epi64((__m128i *) (&pu1_out[out_strd])); out_r2 = _mm_loadl_epi64((__m128i *) (&pu1_out[2 * out_strd])); out_r3 = _mm_loadl_epi64((__m128i *) (&pu1_out[3 * out_strd])); out_r0 = _mm_and_si128(out_r0, chroma_mask); out_r1 = _mm_and_si128(out_r1, chroma_mask); out_r2 = _mm_and_si128(out_r2, chroma_mask); out_r3 = _mm_and_si128(out_r3, chroma_mask); out_r0 = _mm_add_epi8(out_r0, resq_r0); out_r1 = _mm_add_epi8(out_r1, resq_r1); out_r2 = _mm_add_epi8(out_r2, resq_r2); out_r3 = _mm_add_epi8(out_r3, resq_r3); _mm_storel_epi64((__m128i *)(&pu1_out[0]), out_r0); _mm_storel_epi64((__m128i *)(&pu1_out[out_strd]), out_r1); _mm_storel_epi64((__m128i *)(&pu1_out[2 * out_strd]), out_r2); _mm_storel_epi64((__m128i *)(&pu1_out[3 * out_strd]), out_r3); } ================================================ FILE: dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_iquant_itrans_recon_ssse3.c * * @brief * Contains function definitions for inverse quantization, inverse * transform and reconstruction * * @author * Mohit [100664] * * @par List of Functions: * - ih264_iquant_itrans_recon_4x4_ssse3() * - ih264_iquant_itrans_recon_8x8_ssse3() * * @remarks * None * ******************************************************************************* */ /* User include files */ #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_trans_macros.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_trans_data.h" #include "ih264_size_defs.h" #include "ih264_structs.h" #include "ih264_trans_quant_itrans_iquant.h" #include <immintrin.h> #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /* ******************************************************************************** * * @brief This function reconstructs a 4x4 sub block from quantized resiude and * prediction buffer * * @par Description: * The quantized residue is first inverse quantized, then inverse transformed. * This inverse transformed content is added to the prediction buffer to recon- * struct the end output * * @param[in] pi2_src * quantized 4x4 block * * @param[in] pu1_pred * prediction 4x4 block * * @param[out] pu1_out * reconstructed 4x4 block * * @param[in] src_strd * quantization buffer stride * * @param[in] pred_strd, * Prediction buffer stride * * @param[in] out_strd * recon buffer Stride * * @param[in] pu2_scaling_list * pointer to scaling list * * @param[in] pu2_norm_adjust * pointer to inverse scale matrix * * @param[in] u4_qp_div_6 * Floor (qp/6) * * @param[in] pi4_tmp * temporary buffer of size 1*16 * * @returns none * * @remarks none * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_iquant_itrans_recon_4x4_ssse3(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscal_mat, const UWORD16 *pu2_weigh_mat, UWORD32 u4_qp_div_6, WORD16 *pi2_tmp, WORD32 iq_start_idx, WORD16 *pi2_dc_ld_addr) { UWORD32 *pu4_out = (UWORD32 *) pu1_out; __m128i src_r0_r1, src_r2_r3; __m128i src_r0, src_r1, src_r2, src_r3; __m128i scalemat_r0_r1, scalemat_r2_r3, predload_r; __m128i pred_r0, pred_r1, pred_r2, pred_r3; __m128i sign_reg, dequant_r0_r1, dequant_r2_r3; __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero __m128i temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; __m128i resq_r0, resq_r1, resq_r2, resq_r3; __m128i add_rshift = _mm_set1_epi32((u4_qp_div_6 < 4) ? (1 << (3 - u4_qp_div_6)) : 0); __m128i value_32 = _mm_set1_epi32(32); UNUSED (pi2_tmp); UNUSED (pi2_dc_ld_addr); /*************************************************************/ /* Dequantization of coefficients. Will be replaced by SIMD */ /* operations on platform */ /*************************************************************/ src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row scalemat_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat)); //b00 b01 b02 b03 b10 b11 b12 b13 -- the scaling matrix 0th,1st row scalemat_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat + 8)); //b20 b21 b22 b23 b30 b31 b32 b33 -- the scaling matrix 2nd,3rd row dequant_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat)); //q00 q01 q02 q03 q10 q11 q12 q13 -- all 16 bits dequant_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat + 8)); //q20 q21 q22 q23 q30 q31 q32 q33 -- all 16 bits temp0 = _mm_mullo_epi16(scalemat_r0_r1, dequant_r0_r1); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result temp1 = _mm_mullo_epi16(scalemat_r2_r3, dequant_r2_r3); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result temp4 = _mm_unpacklo_epi16(temp0, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long temp5 = _mm_unpackhi_epi16(temp0, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long temp6 = _mm_unpacklo_epi16(temp1, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long temp7 = _mm_unpackhi_epi16(temp1, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long src_r0 = _mm_unpacklo_epi16(src_r0_r1, zero_8x16b); // a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r1 = _mm_unpackhi_epi16(src_r0_r1, zero_8x16b); // a10 0 a11 0 a12 0 a13 0 -- 16 bit long src_r2 = _mm_unpacklo_epi16(src_r2_r3, zero_8x16b); // a20 0 a21 0 a22 0 a23 0 -- 16 bit long src_r3 = _mm_unpackhi_epi16(src_r2_r3, zero_8x16b); // a30 0 a31 0 a32 0 a33 0 -- 16 bit long temp4 = _mm_madd_epi16(src_r0, temp4); //a00*b00*q00 a10*b10*q10 a20*b20*q20 a30*b30 q30 -- 32 bits long temp5 = _mm_madd_epi16(src_r1, temp5); temp6 = _mm_madd_epi16(src_r2, temp6); temp7 = _mm_madd_epi16(src_r3, temp7); if (u4_qp_div_6 >= 4) { resq_r0 = _mm_slli_epi32(temp4, u4_qp_div_6 - 4); resq_r1 = _mm_slli_epi32(temp5, u4_qp_div_6 - 4); resq_r2 = _mm_slli_epi32(temp6, u4_qp_div_6 - 4); resq_r3 = _mm_slli_epi32(temp7, u4_qp_div_6 - 4); } else { temp4 = _mm_add_epi32(temp4, add_rshift); temp5 = _mm_add_epi32(temp5, add_rshift); temp6 = _mm_add_epi32(temp6, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r0 = _mm_srai_epi32(temp4, 4 - u4_qp_div_6); resq_r1 = _mm_srai_epi32(temp5, 4 - u4_qp_div_6); resq_r2 = _mm_srai_epi32(temp6, 4 - u4_qp_div_6); resq_r3 = _mm_srai_epi32(temp7, 4 - u4_qp_div_6); } if (iq_start_idx == 1) { resq_r0 = _mm_insert_epi16(resq_r0,(WORD32)pi2_src[0],0); if (pi2_src[0] >= 0) resq_r0 = _mm_insert_epi16(resq_r0,0,1); else resq_r0 = _mm_insert_epi16(resq_r0,-1,1); } /* Perform Inverse transform */ /*-------------------------------------------------------------*/ /* IDCT [ Horizontal transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 a1 a2 a3 * b0 b1 b2 b3 * c0 c1 c2 c3 * d0 d1 d2 d3 */ temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 b0 a1 b1 temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //c0 d0 c1 d1 temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //a2 b2 a3 b3 temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 d2 c3 d3 resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 b0 c0 d0 resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //a1 b1 c1 d1 resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //a2 b2 c2 d2 resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //a3 b3 c3 d3 //Transform starts -- horizontal transform /*------------------------------------------------------------------*/ /* z0 = w0 + w2 */ temp0 = _mm_add_epi32(resq_r0, resq_r2); /* z1 = w0 - w2 */ temp1 = _mm_sub_epi32(resq_r0, resq_r2); /* z2 = (w1 >> 1) - w3 */ temp2 = _mm_srai_epi32(resq_r1, 1); //(w1>>1) temp2 = _mm_sub_epi32(temp2, resq_r3); //(w1>>1) - w3 /* z3 = w1 + (w3 >> 1) */ temp3 = _mm_srai_epi32(resq_r3, 1); //(w3>>1) + w1 temp3 = _mm_add_epi32(temp3, resq_r1); /*----------------------------------------------------------*/ /* x0 = z0 + z3 */ resq_r0 = _mm_add_epi32(temp0, temp3); /* x1 = z1 + z2 */ resq_r1 = _mm_add_epi32(temp1, temp2); /* x2 = z1 - z2 */ resq_r2 = _mm_sub_epi32(temp1, temp2); /* x3 = z0 - z3 */ resq_r3 = _mm_sub_epi32(temp0, temp3); // Matrix transpose /* * a0 b0 c0 d0 * a1 b1 c1 d1 * a2 b2 c2 d2 * a3 b3 c3 d3 */ temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 a1 b0 b1 temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //a2 a3 b2 b3 temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //c0 c1 d0 d1 temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 c3 d2 d3 resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 a1 a2 a3 resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //b0 b1 b2 b3 resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //c0 c1 c2 c3 resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //d0 d1 d2 d3 //Transform ends -- horizontal transform zero_8x16b = _mm_setzero_si128(); // all bits reset to zero //Load pred buffer predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits pred_r0 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p00 p01 p02 p03 0 0 0 0 -- all 16 bits predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits pred_r1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p10 p11 p12 p13 0 0 0 0 -- all 16 bits predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits pred_r2 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p20 p21 p22 p23 0 0 0 0 -- all 16 bits predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits pred_r3 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p30 p31 p32 p33 0 0 0 0 -- all 16 bits pred_r0 = _mm_unpacklo_epi16(pred_r0, zero_8x16b); //p00 p01 p02 p03 -- 32 bits sign extended pred_r1 = _mm_unpacklo_epi16(pred_r1, zero_8x16b); //p10 p11 p12 p13 -- 32 bits sign extended pred_r2 = _mm_unpacklo_epi16(pred_r2, zero_8x16b); //p20 p21 p22 p23 -- 32 bits sign extended pred_r3 = _mm_unpacklo_epi16(pred_r3, zero_8x16b); //p30 p31 p32 p33 -- 32 bits sign extended /*--------------------------------------------------------------*/ /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ /* */ /* Add the prediction and store it back to same buffer */ /*--------------------------------------------------------------*/ /* z0j = y0j + y2j */ temp0 = _mm_add_epi32(resq_r0, resq_r2); /* z1j = y0j - y2j */ temp1 = _mm_sub_epi32(resq_r0, resq_r2); /* z2j = (y1j>>1) - y3j */ temp2 = _mm_srai_epi32(resq_r1, 1); //(y1j>>1) temp2 = _mm_sub_epi32(temp2, resq_r3); /* z3j = y1j + (y3j>>1) */ temp3 = _mm_srai_epi32(resq_r3, 1); //(y3j>>1) temp3 = _mm_add_epi32(temp3, resq_r1); /* x0j = z0j + z3j */ temp4 = _mm_add_epi32(temp0, temp3); temp4 = _mm_add_epi32(temp4, value_32); temp4 = _mm_srai_epi32(temp4, 6); temp4 = _mm_add_epi32(temp4, pred_r0); /* x1j = z1j + z2j */ temp5 = _mm_add_epi32(temp1, temp2); temp5 = _mm_add_epi32(temp5, value_32); temp5 = _mm_srai_epi32(temp5, 6); temp5 = _mm_add_epi32(temp5, pred_r1); /* x2j = z1j - z2j */ temp6 = _mm_sub_epi32(temp1, temp2); temp6 = _mm_add_epi32(temp6, value_32); temp6 = _mm_srai_epi32(temp6, 6); temp6 = _mm_add_epi32(temp6, pred_r2); /* x3j = z0j - z3j */ temp7 = _mm_sub_epi32(temp0, temp3); temp7 = _mm_add_epi32(temp7, value_32); temp7 = _mm_srai_epi32(temp7, 6); temp7 = _mm_add_epi32(temp7, pred_r3); // 32-bit to 16-bit conversion temp0 = _mm_packs_epi32(temp4, temp5); temp1 = _mm_packs_epi32(temp6, temp7); /*------------------------------------------------------------------*/ //Clipping the results to 8 bits sign_reg = _mm_cmpgt_epi16(temp0, zero_8x16b); // sign check temp0 = _mm_and_si128(temp0, sign_reg); sign_reg = _mm_cmpgt_epi16(temp1, zero_8x16b); temp1 = _mm_and_si128(temp1, sign_reg); resq_r0 = _mm_packus_epi16(temp0, temp1); resq_r1 = _mm_srli_si128(resq_r0, 4); resq_r2 = _mm_srli_si128(resq_r1, 4); resq_r3 = _mm_srli_si128(resq_r2, 4); *pu4_out = _mm_cvtsi128_si32(resq_r0); pu1_out += out_strd; pu4_out = (UWORD32 *) (pu1_out); *(pu4_out) = _mm_cvtsi128_si32(resq_r1); pu1_out += out_strd; pu4_out = (UWORD32 *) (pu1_out); *(pu4_out) = _mm_cvtsi128_si32(resq_r2); pu1_out += out_strd; pu4_out = (UWORD32 *) (pu1_out); *(pu4_out) = _mm_cvtsi128_si32(resq_r3); } /** ******************************************************************************* * * @brief * This function performs inverse quant and Inverse transform type Ci4 for 8x8 block * * @par Description: * Performs inverse transform Ci8 and adds the residue to get the * reconstructed block * * @param[in] pi2_src * Input 8x8coefficients * * @param[in] pu1_pred * Prediction 8x8 block * * @param[out] pu1_recon * Output 8x8 block * * @param[in] q_div * QP/6 * * @param[in] q_rem * QP%6 * * @param[in] q_lev * Quantizer level * * @param[in] u4_src_stride * Input stride * * @param[in] u4_pred_stride, * Prediction stride * * @param[in] u4_out_stride * Output Stride * * @param[in] pi4_tmp * temporary buffer of size 1*64 * the tmp for each block * * @param[in] pu4_iquant_mat * Pointer to the inverse quantization matrix * * @returns Void * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_iquant_itrans_recon_8x8_ssse3(WORD16 *pi2_src, UWORD8 *pu1_pred, UWORD8 *pu1_out, WORD32 pred_strd, WORD32 out_strd, const UWORD16 *pu2_iscale_mat, const UWORD16 *pu2_weigh_mat, UWORD32 qp_div, WORD16 *pi2_tmp, WORD32 iq_start_idx, WORD16 *pi2_dc_ld_addr) { __m128i src_r0; __m128i scalemat_r0; __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero // __m128i one_8x16b = _mm_set1_epi8(255); // all bits set to 1 // __m128i one_zero_mask = _mm_unpacklo_epi16(one_8x16b, zero_8x16b); // 1 0 1 0 1 0 1 0 --- 16 bits size __m128i value_32 = _mm_set1_epi32(32); __m128i add_rshift = _mm_set1_epi32((qp_div < 6) ? (1 << (5 - qp_div)) : 0); __m128i dequant_r0; __m128i predload_r; __m128i pred_r0_1, pred_r1_1, pred_r2_1, pred_r3_1, pred_r4_1, pred_r5_1, pred_r6_1, pred_r7_1; __m128i sign_reg; __m128i src_r0_1, src_r0_2; __m128i scalemat_r0_1, scalemat_r0_2; __m128i temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; __m128i temp10, temp11, temp12, temp13, temp14, temp15, temp16, temp17, temp18, temp19, temp20; // To store dequantization results __m128i resq_r0_1, resq_r0_2, resq_r1_1, resq_r1_2, resq_r2_1, resq_r2_2, resq_r3_1, resq_r3_2, resq_r4_1, resq_r4_2, resq_r5_1, resq_r5_2, resq_r6_1, resq_r6_2, resq_r7_1, resq_r7_2; UNUSED (pi2_tmp); UNUSED (iq_start_idx); UNUSED (pi2_dc_ld_addr); /*************************************************************/ /* Dequantization of coefficients. Will be replaced by SIMD */ /* operations on platform. Note : DC coeff is not scaled */ /*************************************************************/ // Row 0 processing src_r0 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a04 a05 a06 a07 -- the source matrix 0th row scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat)); //b00 b01 b02 b03 b04 b05 b06 b07 -- the scaling matrix 0th row dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[0])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long if (qp_div >= 6) { resq_r0_1 = _mm_slli_epi32(temp5, qp_div - 6); resq_r0_2 = _mm_slli_epi32(temp7, qp_div - 6); } else { temp5 = _mm_add_epi32(temp5, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r0_1 = _mm_srai_epi32(temp5, 6 - qp_div); resq_r0_2 = _mm_srai_epi32(temp7, 6 - qp_div); } resq_r0_1 = _mm_packs_epi32(resq_r0_1, resq_r0_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long // Row 1 processing src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 1st row scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 8)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 1st row dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[8])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long if (qp_div >= 6) { resq_r1_1 = _mm_slli_epi32(temp5, qp_div - 6); resq_r1_2 = _mm_slli_epi32(temp7, qp_div - 6); } else { temp5 = _mm_add_epi32(temp5, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r1_1 = _mm_srai_epi32(temp5, 6 - qp_div); resq_r1_2 = _mm_srai_epi32(temp7, 6 - qp_div); } resq_r1_1 = _mm_packs_epi32(resq_r1_1, resq_r1_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long // Row 2 processing src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 16)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 2nd row scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 16)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 2nd row dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[16])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long if (qp_div >= 6) { resq_r2_1 = _mm_slli_epi32(temp5, qp_div - 6); resq_r2_2 = _mm_slli_epi32(temp7, qp_div - 6); } else { temp5 = _mm_add_epi32(temp5, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r2_1 = _mm_srai_epi32(temp5, 6 - qp_div); resq_r2_2 = _mm_srai_epi32(temp7, 6 - qp_div); } resq_r2_1 = _mm_packs_epi32(resq_r2_1, resq_r2_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long // Row 3 processing src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 24)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 3rd row scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 24)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 3rd row dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[24])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 - 32 bits long temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long if (qp_div >= 6) { resq_r3_1 = _mm_slli_epi32(temp5, qp_div - 6); resq_r3_2 = _mm_slli_epi32(temp7, qp_div - 6); } else { temp5 = _mm_add_epi32(temp5, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r3_1 = _mm_srai_epi32(temp5, 6 - qp_div); resq_r3_2 = _mm_srai_epi32(temp7, 6 - qp_div); } resq_r3_1 = _mm_packs_epi32(resq_r3_1, resq_r3_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long // Row 4 processing src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 32)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 4th row scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 32)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 4th row dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[32])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long if (qp_div >= 6) { resq_r4_1 = _mm_slli_epi32(temp5, qp_div - 6); resq_r4_2 = _mm_slli_epi32(temp7, qp_div - 6); } else { temp5 = _mm_add_epi32(temp5, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r4_1 = _mm_srai_epi32(temp5, 6 - qp_div); resq_r4_2 = _mm_srai_epi32(temp7, 6 - qp_div); } resq_r4_1 = _mm_packs_epi32(resq_r4_1, resq_r4_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long // Row 5 processing src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 40)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 5th row scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 40)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 5th row dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[40])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long if (qp_div >= 6) { resq_r5_1 = _mm_slli_epi32(temp5, qp_div - 6); resq_r5_2 = _mm_slli_epi32(temp7, qp_div - 6); //resq_r5_1 = _mm_and_si128(resq_r5_1,one_zero_mask); //resq_r5_2 = _mm_and_si128(resq_r5_2,one_zero_mask); } else { temp5 = _mm_add_epi32(temp5, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r5_1 = _mm_srai_epi32(temp5, 6 - qp_div); resq_r5_2 = _mm_srai_epi32(temp7, 6 - qp_div); } resq_r5_1 = _mm_packs_epi32(resq_r5_1, resq_r5_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long // Row 6 processing src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 48)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 6th row scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 48)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 6th row dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[48])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long if (qp_div >= 6) { resq_r6_1 = _mm_slli_epi32(temp5, qp_div - 6); resq_r6_2 = _mm_slli_epi32(temp7, qp_div - 6); //resq_r6_1 = _mm_and_si128(resq_r6_1,one_zero_mask); //resq_r6_2 = _mm_and_si128(resq_r6_2,one_zero_mask); } else { temp5 = _mm_add_epi32(temp5, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r6_1 = _mm_srai_epi32(temp5, 6 - qp_div); resq_r6_2 = _mm_srai_epi32(temp7, 6 - qp_div); //resq_r6_1 = _mm_and_si128(resq_r6_1,one_zero_mask); //resq_r6_2 = _mm_and_si128(resq_r6_2,one_zero_mask); } resq_r6_1 = _mm_packs_epi32(resq_r6_1, resq_r6_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long // Row 7 processing src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 56)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 7th row scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 56)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 7th row dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[56])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long if (qp_div >= 6) { resq_r7_1 = _mm_slli_epi32(temp5, qp_div - 6); resq_r7_2 = _mm_slli_epi32(temp7, qp_div - 6); } else { temp5 = _mm_add_epi32(temp5, add_rshift); temp7 = _mm_add_epi32(temp7, add_rshift); resq_r7_1 = _mm_srai_epi32(temp5, 6 - qp_div); resq_r7_2 = _mm_srai_epi32(temp7, 6 - qp_div); } resq_r7_1 = _mm_packs_epi32(resq_r7_1, resq_r7_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long /* Perform Inverse transform */ /*--------------------------------------------------------------------*/ /* IDCT [ Horizontal transformation ] */ /*--------------------------------------------------------------------*/ // Matrix transpose /* * a0 a1 a2 a3 a4 a5 a6 a7 * b0 b1 b2 b3 b4 b5 b6 b7 * c0 c1 c2 c3 c4 c5 c6 c7 * d0 d1 d2 d3 d4 d5 d6 d7 */ temp1 = _mm_unpacklo_epi16(resq_r0_1, resq_r1_1); //a0 b0 a1 b1 a2 b2 a3 b3 temp3 = _mm_unpacklo_epi16(resq_r2_1, resq_r3_1); //c0 d0 c1 d1 c2 d2 c3 d3 temp2 = _mm_unpackhi_epi16(resq_r0_1, resq_r1_1); //a4 b4 a5 b5 a6 b6 a7 b7 temp4 = _mm_unpackhi_epi16(resq_r2_1, resq_r3_1); //c4 d4 c5 d5 c6 d6 c7 d7 resq_r0_1 = _mm_unpacklo_epi32(temp1, temp3); //a0 b0 c0 d0 a1 b1 c1 d1 resq_r1_1 = _mm_unpackhi_epi32(temp1, temp3); //a2 b2 c2 d2 a3 b3 c3 d3 resq_r2_1 = _mm_unpacklo_epi32(temp2, temp4); //a4 b4 c4 d4 a5 b5 c5 d5 resq_r3_1 = _mm_unpackhi_epi32(temp2, temp4); //a6 b6 c6 d6 a7 b7 c7 d7 /* * e0 e1 e2 e3 e4 e5 e6 e7 * f0 f1 f2 f3 f4 f5 f6 f7 * g0 g1 g2 g3 g4 g5 g6 g7 * h0 h1 h2 h3 h4 h5 h6 h7 */ temp1 = _mm_unpacklo_epi16(resq_r4_1, resq_r5_1); //e0 f0 e1 f1 e2 f2 e2 f3 temp3 = _mm_unpacklo_epi16(resq_r6_1, resq_r7_1); //g0 h0 g1 h1 g2 h2 g3 h3 temp2 = _mm_unpackhi_epi16(resq_r4_1, resq_r5_1); //e4 f4 e5 f5 e6 f6 e7 f7 temp4 = _mm_unpackhi_epi16(resq_r6_1, resq_r7_1); //g4 h4 g5 h5 g6 h6 g7 h7 resq_r4_1 = _mm_unpacklo_epi32(temp1, temp3); //e0 f0 g0 h0 e1 f1 g1 h1 resq_r5_1 = _mm_unpackhi_epi32(temp1, temp3); //e2 f2 g2 h2 e3 f3 g3 h3 resq_r6_1 = _mm_unpacklo_epi32(temp2, temp4); //e4 f4 g4 h4 e5 f5 g5 h5 resq_r7_1 = _mm_unpackhi_epi32(temp2, temp4); //e6 f6 g6 h6 e7 f7 g7 h7 /* * a0 b0 c0 d0 a1 b1 c1 d1 * a2 b2 c2 d2 a3 b3 c3 d3 * a4 b4 c4 d4 a5 b5 c5 d5 * a6 b6 c6 d6 a7 b7 c7 d7 * e0 f0 g0 h0 e1 f1 g1 h1 * e2 f2 g2 h2 e3 f3 g3 h3 * e4 f4 g4 h4 e5 f5 g5 h5 * e6 f6 g6 h6 e7 f7 g7 h7 */ resq_r0_2 = _mm_unpacklo_epi64(resq_r0_1, resq_r4_1); //a0 b0 c0 d0 e0 f0 g0 h0 resq_r1_2 = _mm_unpackhi_epi64(resq_r0_1, resq_r4_1); //a1 b1 c1 d1 e1 f1 g1 h1 resq_r2_2 = _mm_unpacklo_epi64(resq_r1_1, resq_r5_1); //a2 b2 c2 d2 e2 f2 g2 h2 resq_r3_2 = _mm_unpackhi_epi64(resq_r1_1, resq_r5_1); //a3 b3 c3 d3 e3 f3 g3 h3 resq_r4_2 = _mm_unpacklo_epi64(resq_r2_1, resq_r6_1); //a4 b4 c4 d4 e4 f4 g4 h4 resq_r5_2 = _mm_unpackhi_epi64(resq_r2_1, resq_r6_1); //a5 b5 c5 d5 e5 f5 g5 h5 resq_r6_2 = _mm_unpacklo_epi64(resq_r3_1, resq_r7_1); //a6 b6 c6 d6 e6 f6 g6 h6 resq_r7_2 = _mm_unpackhi_epi64(resq_r3_1, resq_r7_1); //a7 b7 c7 d7 e7 f7 g7 h7 sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r1_2); resq_r1_1 = _mm_unpacklo_epi16(resq_r1_2, sign_reg); //a1 b1 c1 d1 -- 32 bit resq_r1_2 = _mm_unpackhi_epi16(resq_r1_2, sign_reg); //e1 f1 g1 h1 -- 32 bit sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r3_2); resq_r3_1 = _mm_unpacklo_epi16(resq_r3_2, sign_reg); //a3 b3 c3 d3 -- 32 bit resq_r3_2 = _mm_unpackhi_epi16(resq_r3_2, sign_reg); //e3 f3 g3 h3 -- 32 bit sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r5_2); resq_r5_1 = _mm_unpacklo_epi16(resq_r5_2, sign_reg); //a5 b5 c5 d5 -- 32 bit resq_r5_2 = _mm_unpackhi_epi16(resq_r5_2, sign_reg); //e5 f5 g5 h5 -- 32 bit sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r7_2); resq_r7_1 = _mm_unpacklo_epi16(resq_r7_2, sign_reg); //a7 b7 c7 d7 -- 32 bit resq_r7_2 = _mm_unpackhi_epi16(resq_r7_2, sign_reg); //e7 f7 g7 h7 -- 32 bit //Transform starts -- horizontal transform /*------------------------------------------------------------------*/ /* y0 = w0 + w4 */ temp1 = _mm_add_epi16(resq_r0_2, resq_r4_2); /* y2 = w0 - w4 */ temp3 = _mm_sub_epi16(resq_r0_2, resq_r4_2); /* y1 = -w3 + w5 - w7 - (w7 >> 1) */ temp2 = _mm_sub_epi32(resq_r5_1, resq_r3_1); //-w3+w5 temp10 = _mm_sub_epi32(resq_r5_2, resq_r3_2); temp4 = _mm_sub_epi32(temp2, resq_r7_1); //-w3+w5-w7 temp12 = _mm_sub_epi32(temp10, resq_r7_2); temp5 = _mm_srai_epi32(resq_r7_1, 1); //w7>>1 temp13 = _mm_srai_epi32(resq_r7_2, 1); temp2 = _mm_sub_epi32(temp4, temp5); //-w3+w5-w7 -(w7>>1) temp10 = _mm_sub_epi32(temp12, temp13); temp2 = _mm_packs_epi32(temp2, temp10); /* y3 = w1 + w7 - w3 - (w3 >> 1) */ temp4 = _mm_add_epi32(resq_r1_1, resq_r7_1); //w1+w7 temp12 = _mm_add_epi32(resq_r1_2, resq_r7_2); temp4 = _mm_sub_epi32(temp4, resq_r3_1); //w1+w7-w3 temp12 = _mm_sub_epi32(temp12, resq_r3_2); temp5 = _mm_srai_epi32(resq_r3_1, 1); //w3>>1 temp13 = _mm_srai_epi32(resq_r3_2, 1); temp4 = _mm_sub_epi32(temp4, temp5); //w1+w7-w3-(w3>>1) temp12 = _mm_sub_epi32(temp12, temp13); temp4 = _mm_packs_epi32(temp4, temp12); /* y4 = (w2 >> 1) - w6 */ temp5 = _mm_srai_epi16(resq_r2_2, 1); //w2>>1 temp5 = _mm_sub_epi16(temp5, resq_r6_2); //(w2>>1)-w6 /* y5 = -w1 + w7 + w5 + (w5 >> 1) */ temp6 = _mm_sub_epi32(resq_r7_1, resq_r1_1); //w7-w1 temp14 = _mm_sub_epi32(resq_r7_2, resq_r1_2); temp6 = _mm_add_epi32(temp6, resq_r5_1); //w7-w1+w5 temp14 = _mm_add_epi32(temp14, resq_r5_2); temp7 = _mm_srai_epi32(resq_r5_1, 1); //w5>>1 temp15 = _mm_srai_epi32(resq_r5_2, 1); temp6 = _mm_add_epi32(temp6, temp7); //w7-w1_w5+(w5>>1) temp14 = _mm_add_epi32(temp14, temp15); temp6 = _mm_packs_epi32(temp6, temp14); /* y6 = w2 + (w6 >> 1) */ temp7 = _mm_srai_epi16(resq_r6_2, 1); //w6>>1 temp7 = _mm_add_epi16(temp7, resq_r2_2); //(w6>>1)+w2 /* y7 = w3 + w5 + w1 + (w1 >> 1) */ temp8 = _mm_add_epi32(resq_r3_1, resq_r5_1); //w3+w5 temp16 = _mm_add_epi32(resq_r3_2, resq_r5_2); temp8 = _mm_add_epi32(temp8, resq_r1_1); //w3+w5+w1 temp16 = _mm_add_epi32(temp16, resq_r1_2); temp17 = _mm_srai_epi32(resq_r1_1, 1); //w1>>1 temp18 = _mm_srai_epi32(resq_r1_2, 1); temp8 = _mm_add_epi32(temp8, temp17); //w3+w5+w1+(w1>>1) temp16 = _mm_add_epi32(temp16, temp18); temp8 = _mm_packs_epi32(temp8, temp16); /*------------------------------------------------------------------*/ /*------------------------------------------------------------------*/ /* z0 = y0 + y6 */ resq_r0_1 = _mm_add_epi16(temp1, temp7); /* z1 = y1 + (y7 >> 2) */ resq_r1_1 = _mm_srai_epi16(temp8, 2); resq_r1_1 = _mm_add_epi16(resq_r1_1, temp2); /* z2 = y2 + y4 */ resq_r2_1 = _mm_add_epi16(temp3, temp5); /* z3 = y3 + (y5 >> 2) */ resq_r3_1 = _mm_srai_epi16(temp6, 2); resq_r3_1 = _mm_add_epi16(resq_r3_1, temp4); /* z4 = y2 - y4 */ resq_r4_1 = _mm_sub_epi16(temp3, temp5); /* z5 = (y3 >> 2) - y5 */ resq_r5_1 = _mm_srai_epi16(temp4, 2); resq_r5_1 = _mm_sub_epi16(resq_r5_1, temp6); /* z6 = y0 - y6 */ resq_r6_1 = _mm_sub_epi16(temp1, temp7); /* z7 = y7 - (y1 >> 2) */ resq_r7_1 = _mm_srai_epi16(temp2, 2); resq_r7_1 = _mm_sub_epi16(temp8, resq_r7_1); /*------------------------------------------------------------------*/ /*------------------------------------------------------------------*/ /* x0 = z0 + z7 */ temp1 = _mm_add_epi16(resq_r0_1, resq_r7_1); /* x1 = z2 + z5 */ temp2 = _mm_add_epi16(resq_r2_1, resq_r5_1); /* x2 = z4 + z3 */ temp3 = _mm_add_epi16(resq_r4_1, resq_r3_1); /* x3 = z6 + z1 */ temp4 = _mm_add_epi16(resq_r6_1, resq_r1_1); /* x4 = z6 - z1 */ temp5 = _mm_sub_epi16(resq_r6_1, resq_r1_1); /* x5 = z4 - z3 */ temp6 = _mm_sub_epi16(resq_r4_1, resq_r3_1); /* x6 = z2 - z5 */ temp7 = _mm_sub_epi16(resq_r2_1, resq_r5_1); /* x7 = z0 - z7 */ temp8 = _mm_sub_epi16(resq_r0_1, resq_r7_1); /*------------------------------------------------------------------*/ // Matrix transpose /* * a0 b0 c0 d0 e0 f0 g0 h0 * a1 b1 c1 d1 e1 f1 g1 h1 * a2 b2 c2 d2 e2 f2 g2 h2 * a3 b3 c3 d3 e3 f3 g3 h3 */ temp17 = _mm_unpacklo_epi16(temp1, temp2); //a0 a1 b0 b1 c0 c1 d0 d1 temp19 = _mm_unpacklo_epi16(temp3, temp4); //a2 a3 b2 b3 c2 c3 d2 d3 temp18 = _mm_unpackhi_epi16(temp1, temp2); //e0 e1 f0 f1 g0 g1 h0 h1 temp20 = _mm_unpackhi_epi16(temp3, temp4); //e2 e3 f2 f3 g2 g3 h2 h3 resq_r0_1 = _mm_unpacklo_epi32(temp17, temp19); //a0 a1 a2 a3 b0 b1 b2 b3 resq_r1_1 = _mm_unpackhi_epi32(temp17, temp19); //c0 c1 c2 c3 d0 d1 d2 d3 resq_r2_1 = _mm_unpacklo_epi32(temp18, temp20); //e0 e1 e2 e3 f0 f1 f2 f3 resq_r3_1 = _mm_unpackhi_epi32(temp18, temp20); //g0 g2 g2 g3 h0 h1 h2 h3 /* * a4 b4 c4 d4 e4 f4 g4 h4 * a5 b5 c5 d5 e5 f5 g5 h5 * a6 b6 c6 d6 e6 f6 g6 h6 * a7 b7 c7 d7 e7 f7 g7 h7 */ temp17 = _mm_unpacklo_epi16(temp5, temp6); //a4 a5 b4 b5 c4 c5 d4 d5 temp19 = _mm_unpacklo_epi16(temp7, temp8); //a6 a7 b6 b7 c6 c7 d6 d7 temp18 = _mm_unpackhi_epi16(temp5, temp6); //e4 e5 f4 f5 g4 g5 h4 h5 temp20 = _mm_unpackhi_epi16(temp7, temp8); //e6 e7 f6 f7 g6 g7 h6 h7 resq_r4_1 = _mm_unpacklo_epi32(temp17, temp19); //a4 a5 a6 a7 b4 b5 b6 b7 resq_r5_1 = _mm_unpackhi_epi32(temp17, temp19); //c4 c5 c6 c7 d4 d5 d6 d7 resq_r6_1 = _mm_unpacklo_epi32(temp18, temp20); //e4 e5 e6 e7 f4 f5 f6 f7 resq_r7_1 = _mm_unpackhi_epi32(temp18, temp20); //g4 g5 g6 g7 h4 h5 h6 h7 /* a0 a1 a2 a3 b0 b1 b2 b3 * c0 c1 c2 c3 d0 d1 d2 d3 * e0 e1 e2 e3 f0 f1 f2 f3 * g0 g2 g2 g3 h0 h1 h2 h3 * a4 a5 a6 a7 b4 b5 b6 b7 * c4 c5 c6 c7 d4 d5 d6 d7 * e4 e5 e6 e7 f4 f5 f6 f7 * g4 g5 g6 g7 h4 h5 h6 h7 */ resq_r0_2 = _mm_unpacklo_epi64(resq_r0_1, resq_r4_1); //a0 a1 a2 a3 a4 a5 a6 a7 resq_r1_2 = _mm_unpackhi_epi64(resq_r0_1, resq_r4_1); //b0 b1 b2 b3 b4 b5 b6 b7 resq_r2_2 = _mm_unpacklo_epi64(resq_r1_1, resq_r5_1); //c0 c1 c2 c3 c4 c5 c6 c7 resq_r3_2 = _mm_unpackhi_epi64(resq_r1_1, resq_r5_1); //d0 d1 d2 d3 d4 d5 d6 d7 resq_r4_2 = _mm_unpacklo_epi64(resq_r2_1, resq_r6_1); //e0 e1 e2 e3 e4 e5 e6 e7 resq_r5_2 = _mm_unpackhi_epi64(resq_r2_1, resq_r6_1); //f0 f1 f2 f3 f4 f5 f6 f7 resq_r6_2 = _mm_unpacklo_epi64(resq_r3_1, resq_r7_1); //g0 g1 g2 g3 g4 g5 g6 g7 resq_r7_2 = _mm_unpackhi_epi64(resq_r3_1, resq_r7_1); //h0 h1 h2 h3 h4 h5 h6 h7 sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r1_2); resq_r1_1 = _mm_unpacklo_epi16(resq_r1_2, sign_reg); //a1 b1 c1 d1 -- 32 bit resq_r1_2 = _mm_unpackhi_epi16(resq_r1_2, sign_reg); //e1 f1 g1 h1 -- 32 bit sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r3_2); resq_r3_1 = _mm_unpacklo_epi16(resq_r3_2, sign_reg); //a3 b3 c3 d3 -- 32 bit resq_r3_2 = _mm_unpackhi_epi16(resq_r3_2, sign_reg); //e3 f3 g3 h3 -- 32 bit sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r5_2); resq_r5_1 = _mm_unpacklo_epi16(resq_r5_2, sign_reg); //a5 b5 c5 d5 -- 32 bit resq_r5_2 = _mm_unpackhi_epi16(resq_r5_2, sign_reg); //e5 f5 g5 h5 -- 32 bit sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r7_2); resq_r7_1 = _mm_unpacklo_epi16(resq_r7_2, sign_reg); //a7 b7 c7 d7 -- 32 bit resq_r7_2 = _mm_unpackhi_epi16(resq_r7_2, sign_reg); //e7 f7 g7 h7 -- 32 bit zero_8x16b = _mm_setzero_si128(); // all bits reset to zero //Load pred buffer row 0 predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r0_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 1 predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r1_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 2 predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r2_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 3 predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r3_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 4 predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[4 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r4_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 5 predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[5 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bit pred_r5_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 6 predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[6 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r6_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits //Load pred buffer row 7 predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[7 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits pred_r7_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits /*--------------------------------------------------------------------*/ /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ /* */ /* Add the prediction and store it back to reconstructed frame buffer */ /* [Prediction buffer itself in this case] */ /*--------------------------------------------------------------------*/ /* y0j = w0j + w4j */ temp1 = _mm_add_epi16(resq_r0_2, resq_r4_2); /* y2j = w0j - w4j */ temp3 = _mm_sub_epi16(resq_r0_2, resq_r4_2); /* y1j = -w3j + w5j - w7j - (w7j >> 1) */ temp2 = _mm_sub_epi32(resq_r5_1, resq_r3_1); //-w3+w5 temp10 = _mm_sub_epi32(resq_r5_2, resq_r3_2); temp4 = _mm_sub_epi32(temp2, resq_r7_1); //-w3+w5-w7 temp12 = _mm_sub_epi32(temp10, resq_r7_2); temp5 = _mm_srai_epi32(resq_r7_1, 1); //w7>>1 temp13 = _mm_srai_epi32(resq_r7_2, 1); temp2 = _mm_sub_epi32(temp4, temp5); //-w3+w5-w7 -(w7>>1) temp10 = _mm_sub_epi32(temp12, temp13); temp2 = _mm_packs_epi32(temp2, temp10); /* y3j = w1j + w7j - w3j - (w3j >> 1) */ temp4 = _mm_add_epi32(resq_r1_1, resq_r7_1); //w1+w7 temp12 = _mm_add_epi32(resq_r1_2, resq_r7_2); temp4 = _mm_sub_epi32(temp4, resq_r3_1); //w1+w7-w3 temp12 = _mm_sub_epi32(temp12, resq_r3_2); temp5 = _mm_srai_epi32(resq_r3_1, 1); //w3>>1 temp13 = _mm_srai_epi32(resq_r3_2, 1); temp4 = _mm_sub_epi32(temp4, temp5); //w1+w7-w3-(w3>>1) temp12 = _mm_sub_epi32(temp12, temp13); temp4 = _mm_packs_epi32(temp4, temp12); /* y4j = (w2j >> 1) - w6j */ temp5 = _mm_srai_epi16(resq_r2_2, 1); //w2>>1 temp5 = _mm_sub_epi16(temp5, resq_r6_2); //(w2>>1)-w6 /* y5j = -w1j + w7j + w5j + (w5j >> 1) */ temp6 = _mm_sub_epi32(resq_r7_1, resq_r1_1); //w7-w1 temp14 = _mm_sub_epi32(resq_r7_2, resq_r1_2); temp6 = _mm_add_epi32(temp6, resq_r5_1); //w7-w1+w5 temp14 = _mm_add_epi32(temp14, resq_r5_2); temp7 = _mm_srai_epi32(resq_r5_1, 1); //w5>>1 temp15 = _mm_srai_epi32(resq_r5_2, 1); temp6 = _mm_add_epi32(temp6, temp7); //w7-w1_w5+(w5>>1) temp14 = _mm_add_epi32(temp14, temp15); temp6 = _mm_packs_epi32(temp6, temp14); /* y6j = w2j + (w6j >> 1) */ temp7 = _mm_srai_epi16(resq_r6_2, 1); //w6>>1 temp7 = _mm_add_epi16(temp7, resq_r2_2); //(w6>>1)+w2 /* y7j = w3j + w5j + w1j + (w1j >> 1) */ temp8 = _mm_add_epi32(resq_r3_1, resq_r5_1); //w3+w5 temp16 = _mm_add_epi32(resq_r3_2, resq_r5_2); temp8 = _mm_add_epi32(temp8, resq_r1_1); //w3+w5+w1 temp16 = _mm_add_epi32(temp16, resq_r1_2); temp17 = _mm_srai_epi32(resq_r1_1, 1); //w1>>1 temp18 = _mm_srai_epi32(resq_r1_2, 1); temp8 = _mm_add_epi32(temp8, temp17); //w3+w5+w1+(w1>>1) temp16 = _mm_add_epi32(temp16, temp18); temp8 = _mm_packs_epi32(temp8, temp16); /*------------------------------------------------------------------*/ /*------------------------------------------------------------------*/ /* z0j = y0j + y6j */ resq_r0_1 = _mm_add_epi16(temp1, temp7); /* z1j = y1j + (y7j >> 2) */ resq_r1_1 = _mm_srai_epi16(temp8, 2); resq_r1_1 = _mm_add_epi16(resq_r1_1, temp2); /* z2j = y2j + y4j */ resq_r2_1 = _mm_add_epi16(temp3, temp5); /* z3j = y3j + (y5j >> 2) */ resq_r3_1 = _mm_srai_epi16(temp6, 2); resq_r3_1 = _mm_add_epi16(resq_r3_1, temp4); /* z4j = y2j - y4j */ resq_r4_1 = _mm_sub_epi16(temp3, temp5); /* z5j = (y3j >> 2) - y5j */ resq_r5_1 = _mm_srai_epi16(temp4, 2); resq_r5_1 = _mm_sub_epi16(resq_r5_1, temp6); /* z6j = y0j - y6j */ resq_r6_1 = _mm_sub_epi16(temp1, temp7); /* z7j = y7j - (y1j >> 2) */ resq_r7_1 = _mm_srai_epi16(temp2, 2); resq_r7_1 = _mm_sub_epi16(temp8, resq_r7_1); /*------------------------------------------------------------------*/ /*------------------------------------------------------------------*/ /* x0j = z0j + z7j */ temp1 = _mm_add_epi16(resq_r0_1, resq_r7_1); sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp1); temp10 = _mm_unpacklo_epi16(temp1, sign_reg); temp11 = _mm_unpackhi_epi16(temp1, sign_reg); temp10 = _mm_add_epi32(temp10, value_32); temp11 = _mm_add_epi32(temp11, value_32); temp10 = _mm_srai_epi32(temp10, 6); temp11 = _mm_srai_epi32(temp11, 6); temp10 = _mm_packs_epi32(temp10, temp11); temp1 = _mm_add_epi16(temp10, pred_r0_1); /* x1j = z2j + z5j */ temp2 = _mm_add_epi16(resq_r2_1, resq_r5_1); sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp2); temp10 = _mm_unpacklo_epi16(temp2, sign_reg); temp11 = _mm_unpackhi_epi16(temp2, sign_reg); temp10 = _mm_add_epi32(temp10, value_32); temp11 = _mm_add_epi32(temp11, value_32); temp10 = _mm_srai_epi32(temp10, 6); temp11 = _mm_srai_epi32(temp11, 6); temp10 = _mm_packs_epi32(temp10, temp11); temp2 = _mm_add_epi16(temp10, pred_r1_1); /* x2j = z4j + z3j */ temp3 = _mm_add_epi16(resq_r4_1, resq_r3_1); sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp3); temp10 = _mm_unpacklo_epi16(temp3, sign_reg); temp11 = _mm_unpackhi_epi16(temp3, sign_reg); temp10 = _mm_add_epi32(temp10, value_32); temp11 = _mm_add_epi32(temp11, value_32); temp10 = _mm_srai_epi32(temp10, 6); temp11 = _mm_srai_epi32(temp11, 6); temp10 = _mm_packs_epi32(temp10, temp11); temp3 = _mm_add_epi16(temp10, pred_r2_1); /* x3j = z6j + z1j */ temp4 = _mm_add_epi16(resq_r6_1, resq_r1_1); sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp4); temp10 = _mm_unpacklo_epi16(temp4, sign_reg); temp11 = _mm_unpackhi_epi16(temp4, sign_reg); temp10 = _mm_add_epi32(temp10, value_32); temp11 = _mm_add_epi32(temp11, value_32); temp10 = _mm_srai_epi32(temp10, 6); temp11 = _mm_srai_epi32(temp11, 6); temp10 = _mm_packs_epi32(temp10, temp11); temp4 = _mm_add_epi16(temp10, pred_r3_1); /* x4j = z6j - z1j */ temp5 = _mm_sub_epi16(resq_r6_1, resq_r1_1); sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp5); temp10 = _mm_unpacklo_epi16(temp5, sign_reg); temp11 = _mm_unpackhi_epi16(temp5, sign_reg); temp10 = _mm_add_epi32(temp10, value_32); temp11 = _mm_add_epi32(temp11, value_32); temp10 = _mm_srai_epi32(temp10, 6); temp11 = _mm_srai_epi32(temp11, 6); temp10 = _mm_packs_epi32(temp10, temp11); temp5 = _mm_add_epi16(temp10, pred_r4_1); /* x5j = z4j - z3j */ temp6 = _mm_sub_epi16(resq_r4_1, resq_r3_1); sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp6); temp10 = _mm_unpacklo_epi16(temp6, sign_reg); temp11 = _mm_unpackhi_epi16(temp6, sign_reg); temp10 = _mm_add_epi32(temp10, value_32); temp11 = _mm_add_epi32(temp11, value_32); temp10 = _mm_srai_epi32(temp10, 6); temp11 = _mm_srai_epi32(temp11, 6); temp10 = _mm_packs_epi32(temp10, temp11); temp6 = _mm_add_epi16(temp10, pred_r5_1); /* x6j = z2j - z5j */ temp7 = _mm_sub_epi16(resq_r2_1, resq_r5_1); sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp7); temp10 = _mm_unpacklo_epi16(temp7, sign_reg); temp11 = _mm_unpackhi_epi16(temp7, sign_reg); temp10 = _mm_add_epi32(temp10, value_32); temp11 = _mm_add_epi32(temp11, value_32); temp10 = _mm_srai_epi32(temp10, 6); temp11 = _mm_srai_epi32(temp11, 6); temp10 = _mm_packs_epi32(temp10, temp11); temp7 = _mm_add_epi16(temp10, pred_r6_1); /* x7j = z0j - z7j */ temp8 = _mm_sub_epi16(resq_r0_1, resq_r7_1); sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp8); temp10 = _mm_unpacklo_epi16(temp8, sign_reg); temp11 = _mm_unpackhi_epi16(temp8, sign_reg); temp10 = _mm_add_epi32(temp10, value_32); temp11 = _mm_add_epi32(temp11, value_32); temp10 = _mm_srai_epi32(temp10, 6); temp11 = _mm_srai_epi32(temp11, 6); temp10 = _mm_packs_epi32(temp10, temp11); temp8 = _mm_add_epi16(temp10, pred_r7_1); /*------------------------------------------------------------------*/ //Clipping the results to 8 bits sign_reg = _mm_cmpgt_epi16(temp1, zero_8x16b); // sign check temp1 = _mm_and_si128(temp1, sign_reg); sign_reg = _mm_cmpgt_epi16(temp2, zero_8x16b); // sign check temp2 = _mm_and_si128(temp2, sign_reg); sign_reg = _mm_cmpgt_epi16(temp3, zero_8x16b); // sign check temp3 = _mm_and_si128(temp3, sign_reg); sign_reg = _mm_cmpgt_epi16(temp4, zero_8x16b); // sign check temp4 = _mm_and_si128(temp4, sign_reg); sign_reg = _mm_cmpgt_epi16(temp5, zero_8x16b); // sign check temp5 = _mm_and_si128(temp5, sign_reg); sign_reg = _mm_cmpgt_epi16(temp6, zero_8x16b); // sign check temp6 = _mm_and_si128(temp6, sign_reg); sign_reg = _mm_cmpgt_epi16(temp7, zero_8x16b); // sign check temp7 = _mm_and_si128(temp7, sign_reg); sign_reg = _mm_cmpgt_epi16(temp8, zero_8x16b); // sign check temp8 = _mm_and_si128(temp8, sign_reg); resq_r0_2 = _mm_packus_epi16(temp1, zero_8x16b); resq_r1_2 = _mm_packus_epi16(temp2, zero_8x16b); resq_r2_2 = _mm_packus_epi16(temp3, zero_8x16b); resq_r3_2 = _mm_packus_epi16(temp4, zero_8x16b); resq_r4_2 = _mm_packus_epi16(temp5, zero_8x16b); resq_r5_2 = _mm_packus_epi16(temp6, zero_8x16b); resq_r6_2 = _mm_packus_epi16(temp7, zero_8x16b); resq_r7_2 = _mm_packus_epi16(temp8, zero_8x16b); _mm_storel_epi64((__m128i *) (&pu1_out[0]), resq_r0_2); _mm_storel_epi64((__m128i *) (&pu1_out[out_strd]), resq_r1_2); _mm_storel_epi64((__m128i *) (&pu1_out[2 * out_strd]), resq_r2_2); _mm_storel_epi64((__m128i *) (&pu1_out[3 * out_strd]), resq_r3_2); _mm_storel_epi64((__m128i *) (&pu1_out[4 * out_strd]), resq_r4_2); _mm_storel_epi64((__m128i *) (&pu1_out[5 * out_strd]), resq_r5_2); _mm_storel_epi64((__m128i *) (&pu1_out[6 * out_strd]), resq_r6_2); _mm_storel_epi64((__m128i *) (&pu1_out[7 * out_strd]), resq_r7_2); } ================================================ FILE: dependencies/ih264d/common/x86/ih264_luma_intra_pred_filters_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_luma_intra_pred_filters_ssse3.c * * @brief * Contains function definitions for luma intra prediction filters in x86 * intrinsics * * @author * Ittiam * * @par List of Functions: * - ih264_intra_pred_luma_4x4_mode_vert_ssse3 * - ih264_intra_pred_luma_4x4_mode_horz_ssse3 * - ih264_intra_pred_luma_4x4_mode_dc_ssse3 * - ih264_intra_pred_luma_4x4_mode_diag_dl_ssse3 * - ih264_intra_pred_luma_4x4_mode_diag_dr_ssse3 * - ih264_intra_pred_luma_4x4_mode_vert_r_ssse3 * - ih264_intra_pred_luma_4x4_mode_horz_d_ssse3 * - ih264_intra_pred_luma_4x4_mode_vert_l_ssse3 * - ih264_intra_pred_luma_4x4_mode_horz_u_ssse3 * - ih264_intra_pred_luma_8x8_mode_vert_ssse3 * - ih264_intra_pred_luma_8x8_mode_horz_ssse3 * - ih264_intra_pred_luma_8x8_mode_dc_ssse3 * - ih264_intra_pred_luma_8x8_mode_diag_dl_ssse3 * - ih264_intra_pred_luma_8x8_mode_diag_dr_ssse3 * - ih264_intra_pred_luma_8x8_mode_vert_r_ssse3 * - ih264_intra_pred_luma_8x8_mode_horz_d_ssse3 * - ih264_intra_pred_luma_8x8_mode_vert_l_ssse3 * - ih264_intra_pred_luma_8x8_mode_horz_u_ssse3 * - ih264_intra_pred_luma_16x16_mode_vert_ssse3 * - ih264_intra_pred_luma_16x16_mode_horz_ssse3 * - ih264_intra_pred_luma_16x16_mode_dc_ssse3 * - ih264_intra_pred_luma_16x16_mode_plane_ssse3 * * @remarks * None * ****************************************************************************** */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include <stdio.h> #include <stddef.h> #include <string.h> #include <immintrin.h> /* User include files */ #include "ih264_defs.h" #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_intra_pred_filters.h" #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /******************* LUMA INTRAPREDICTION *******************/ /******************* 4x4 Modes *******************/ /** ******************************************************************************* * * ih264_intra_pred_luma_4x4_mode_vert_ssse3 * * @brief * Perform Intra prediction for luma_4x4 mode:vertical * * @par Description: * Perform Intra prediction for luma_4x4 mode:vertical ,described in sec 8.3.1.2.1 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_4x4_mode_vert_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top; WORD32 dst_strd2, dst_strd3; WORD32 i4_top; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK_SIZE + 1; i4_top = *((WORD32 *)pu1_top); dst_strd2 = dst_strd << 1; dst_strd3 = dst_strd + dst_strd2; *((WORD32 *)(pu1_dst)) = i4_top; *((WORD32 *)(pu1_dst + dst_strd)) = i4_top; *((WORD32 *)(pu1_dst + dst_strd2)) = i4_top; *((WORD32 *)(pu1_dst + dst_strd3)) = i4_top; } /** ******************************************************************************* * *ih264_intra_pred_luma_4x4_mode_horz_ssse3 * * @brief * Perform Intra prediction for luma_4x4 mode:horizontal * * @par Description: * Perform Intra prediction for luma_4x4 mode:horizontal ,described in sec 8.3.1.2.2 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_4x4_mode_horz_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ WORD32 row1,row2,row3,row4; UWORD8 val; WORD32 dst_strd2, dst_strd3; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK_SIZE - 1; val = *pu1_left; row1 = val + (val << 8) + (val << 16) + (val << 24); val = *(pu1_left - 1); row2 = val + (val << 8) + (val << 16) + (val << 24); val = *(pu1_left - 2); row3 = val + (val << 8) + (val << 16) + (val << 24); val = *(pu1_left - 3); row4 = val + (val << 8) + (val << 16) + (val << 24); dst_strd2 = dst_strd << 1; dst_strd3 = dst_strd + dst_strd2; *((WORD32 *)(pu1_dst)) = row1; *((WORD32 *)(pu1_dst + dst_strd)) = row2; *((WORD32 *)(pu1_dst + dst_strd2)) = row3; *((WORD32 *)(pu1_dst + dst_strd3)) = row4; } /** ******************************************************************************* * * ih264_intra_pred_luma_4x4_mode_dc_ssse3 * * @brief * Perform Intra prediction for luma_4x4 mode:DC * * @par Description: * Perform Intra prediction for luma_4x4 mode:DC ,described in sec 8.3.1.2.3 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_4x4_mode_dc_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 u1_useleft; /* availability of left predictors (only for DC) */ UWORD8 u1_usetop; /* availability of top predictors (only for DC) */ UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ WORD32 dst_strd2, dst_strd3; WORD32 val = 0; UNUSED(src_strd); UNUSED(ngbr_avail); u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); pu1_top = pu1_src + BLK_SIZE + 1; pu1_left = pu1_src + BLK_SIZE - 1; if(u1_useleft) { val += *pu1_left--; val += *pu1_left--; val += *pu1_left--; val += *pu1_left + 2; } if(u1_usetop) { val += *pu1_top + *(pu1_top + 1) + *(pu1_top + 2) + *(pu1_top + 3) + 2; } /* Since 2 is added if either left/top pred is there, val still being zero implies both preds are not there */ val = (val) ? (val >> (1 + u1_useleft + u1_usetop)) : 128; val = val + (val << 8) + (val << 16) + (val << 24); dst_strd2 = dst_strd << 1; dst_strd3 = dst_strd + dst_strd2; *((WORD32 *)(pu1_dst)) = val; *((WORD32 *)(pu1_dst + dst_strd)) = val; *((WORD32 *)(pu1_dst + dst_strd2)) = val; *((WORD32 *)(pu1_dst + dst_strd3)) = val; } /** ******************************************************************************* * * ih264_intra_pred_luma_4x4_mode_diag_dl_ssse3 * * @brief * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left * * @par Description: * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left ,described in sec 8.3.1.2.4 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_4x4_mode_diag_dl_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top; WORD32 dst_strd2, dst_strd3; __m128i top_16x8b, top_8x16b, top_sh_8x16b; __m128i res1_8x16b, res2_8x16b, res_16x8b; __m128i zero_vector, const_2_8x16b; WORD32 row1,row2,row3,row4; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK_SIZE + 1; top_16x8b = _mm_loadl_epi64((__m128i *)pu1_top); zero_vector = _mm_setzero_si128(); top_8x16b = _mm_unpacklo_epi8(top_16x8b, zero_vector); //t0 t1 t2 t3 t4 t5 t6 t7 top_sh_8x16b = _mm_srli_si128(top_8x16b, 2); //t1 t2 t3 t4 t5 t6 t7 0 const_2_8x16b = _mm_set1_epi16(2); top_sh_8x16b = _mm_shufflehi_epi16(top_sh_8x16b, 0xa4); //t1 t2 t3 t4 t5 t6 t7 t7 res1_8x16b = _mm_add_epi16(top_8x16b, top_sh_8x16b); res2_8x16b = _mm_srli_si128(res1_8x16b, 2); res1_8x16b = _mm_add_epi16(res1_8x16b, const_2_8x16b); res1_8x16b = _mm_add_epi16(res2_8x16b, res1_8x16b); res1_8x16b = _mm_srai_epi16(res1_8x16b, 2); dst_strd2 = dst_strd << 1; dst_strd3 = dst_strd + dst_strd2; res_16x8b = _mm_packus_epi16(res1_8x16b, res1_8x16b); row1 = _mm_cvtsi128_si32(res_16x8b); res_16x8b = _mm_srli_si128(res_16x8b, 1); row2 = _mm_cvtsi128_si32(res_16x8b); res_16x8b = _mm_srli_si128(res_16x8b, 1); row3 = _mm_cvtsi128_si32(res_16x8b); res_16x8b = _mm_srli_si128(res_16x8b, 1); row4 = _mm_cvtsi128_si32(res_16x8b); *((WORD32 *)(pu1_dst)) = row1; *((WORD32 *)(pu1_dst + dst_strd)) = row2; *((WORD32 *)(pu1_dst + dst_strd2)) = row3; *((WORD32 *)(pu1_dst + dst_strd3)) = row4; } /** ******************************************************************************* * * ih264_intra_pred_luma_4x4_mode_diag_dr_ssse3 * * @brief * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right * * @par Description: * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right ,described in sec 8.3.1.2.5 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_4x4_mode_diag_dr_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left; WORD32 dst_strd2, dst_strd3; __m128i top_left_16x8b, top_left_8x16b; __m128i top_left_sh_16x8b, top_left_sh_8x16b; __m128i res1_8x16b, res2_8x16b; __m128i res1_16x8b, res2_16x8b; __m128i zero_vector, const_2_8x16b; WORD32 row1,row2,row3,row4; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK_SIZE - 1; top_left_16x8b = _mm_loadu_si128((__m128i *)(pu1_left - 3)); //l3 l2 l1 l0 tl t0 t1 t2... zero_vector = _mm_setzero_si128(); top_left_sh_16x8b = _mm_srli_si128(top_left_16x8b, 1); //l2 l1 l0 tl t0 t1 t2 t3... top_left_8x16b = _mm_unpacklo_epi8(top_left_16x8b, zero_vector); top_left_sh_8x16b = _mm_unpacklo_epi8(top_left_sh_16x8b, zero_vector); res1_8x16b = _mm_add_epi16(top_left_8x16b, top_left_sh_8x16b); //l3+l2 l2+l1 l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2+t3... const_2_8x16b = _mm_set1_epi16(2); res2_8x16b = _mm_srli_si128(res1_8x16b, 2); //l2+l1 l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2+t3... res1_8x16b = _mm_add_epi16(res1_8x16b, const_2_8x16b); res1_8x16b = _mm_add_epi16(res2_8x16b, res1_8x16b); //l3+2*l2+l1+2 l2+2*l1+l0+2... res1_8x16b = _mm_srai_epi16(res1_8x16b, 2); res1_16x8b = _mm_packus_epi16(res1_8x16b, res1_8x16b); dst_strd2 = dst_strd << 1; dst_strd3 = dst_strd + dst_strd2; res2_16x8b = _mm_srli_si128(res1_16x8b, 3); row1 = _mm_cvtsi128_si32(res2_16x8b); res2_16x8b = _mm_srli_si128(res1_16x8b, 2); row2 = _mm_cvtsi128_si32(res2_16x8b); res2_16x8b = _mm_srli_si128(res1_16x8b, 1); row3 = _mm_cvtsi128_si32(res2_16x8b); row4 = _mm_cvtsi128_si32(res1_16x8b); *((WORD32 *)(pu1_dst)) = row1; *((WORD32 *)(pu1_dst + dst_strd)) = row2; *((WORD32 *)(pu1_dst + dst_strd2)) = row3; *((WORD32 *)(pu1_dst + dst_strd3)) = row4; } /** ******************************************************************************* * * ih264_intra_pred_luma_4x4_mode_vert_r_ssse3 * * @brief * Perform Intra prediction for luma_4x4 mode:Vertical_Right * * @par Description: * Perform Intra prediction for luma_4x4 mode:Vertical_Right ,described in sec 8.3.1.2.6 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_4x4_mode_vert_r_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left; WORD32 dst_strd2, dst_strd3; __m128i val_16x8b, temp_16x8b; __m128i w11_a1_16x8b, w11_a2_16x8b; __m128i w121_a1_8x16b, w121_a2_8x16b, w121_sh_8x16b; __m128i row1_16x8b, row2_16x8b, row3_16x8b, row4_16x8b; __m128i zero_vector, const_2_8x16b; WORD32 row1,row2,row3,row4; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK_SIZE - 1; val_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 2)); zero_vector = _mm_setzero_si128(); w121_a1_8x16b = _mm_unpacklo_epi8(val_16x8b, zero_vector); //l2 l1 l0 tl t0 t1 t2 t3 w11_a1_16x8b = _mm_srli_si128(val_16x8b, 3); w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l1 l0 tl t0 t1 t2 t3 0 w11_a2_16x8b = _mm_srli_si128(val_16x8b, 4); w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l2+l1 l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2+t3 t3 row1_16x8b = _mm_avg_epu8(w11_a1_16x8b, w11_a2_16x8b); w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2+t3 t3 0 const_2_8x16b = _mm_set1_epi16(2); w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l2+2*l1+l0 l1+2*l0+tl ... w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, const_2_8x16b); w121_a1_8x16b = _mm_srai_epi16(w121_a1_8x16b, 2); w121_sh_8x16b = _mm_shufflelo_epi16(w121_a1_8x16b, 0xe1); w121_sh_8x16b = _mm_srli_si128(w121_sh_8x16b, 2); row4_16x8b = _mm_packus_epi16(w121_sh_8x16b, w121_sh_8x16b); temp_16x8b = _mm_slli_si128(w121_a1_8x16b, 13); row2_16x8b = _mm_srli_si128(row4_16x8b, 1); row3_16x8b = _mm_alignr_epi8(row1_16x8b, temp_16x8b, 15); dst_strd2 = dst_strd << 1; dst_strd3 = dst_strd + dst_strd2; row1 = _mm_cvtsi128_si32(row1_16x8b); row2 = _mm_cvtsi128_si32(row2_16x8b); row3 = _mm_cvtsi128_si32(row3_16x8b); row4 = _mm_cvtsi128_si32(row4_16x8b); *((WORD32 *)(pu1_dst)) = row1; *((WORD32 *)(pu1_dst + dst_strd)) = row2; *((WORD32 *)(pu1_dst + dst_strd2)) = row3; *((WORD32 *)(pu1_dst + dst_strd3)) = row4; } /* ******************************************************************************* * * ih264_intra_pred_luma_4x4_mode_horz_d_ssse3 * * @brief * Perform Intra prediction for luma_4x4 mode:Horizontal_Down * * @par Description: * Perform Intra prediction for luma_4x4 mode:Horizontal_Down ,described in sec 8.3.1.2.7 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_4x4_mode_horz_d_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left; WORD32 dst_strd2, dst_strd3; WORD32 val_121_t0t1; __m128i val_16x8b, val_sh_16x8b; __m128i w11_16x8b; __m128i w121_a1_8x16b, w121_a2_8x16b, w121_16x8b; __m128i row1_16x8b, row2_16x8b, row3_16x8b, row4_16x8b; __m128i zero_vector, const_2_8x16b; WORD32 row1,row2,row3,row4; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK_SIZE - 1; val_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 3)); zero_vector = _mm_setzero_si128(); val_sh_16x8b = _mm_srli_si128(val_16x8b, 1); w11_16x8b = _mm_avg_epu8(val_16x8b, val_sh_16x8b); w121_a1_8x16b = _mm_unpacklo_epi8(val_16x8b, zero_vector); //l3 l2 l1 l0 tl t0 t1 t2 w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l2 l1 l0 tl t0 t1 t2 0 w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l3+l2 l2+l1 l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2 w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l2+l1 l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2 0 zero_vector = _mm_setzero_si128(); const_2_8x16b = _mm_set1_epi16(2); w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l3+2*l2+l1 l2+2*l1+l0 l1+2*l0+tl ... w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, const_2_8x16b); w121_a1_8x16b = _mm_srai_epi16(w121_a1_8x16b, 2); w121_16x8b = _mm_packus_epi16(w121_a1_8x16b, w121_a1_8x16b); row4_16x8b = _mm_unpacklo_epi8(w11_16x8b, w121_16x8b); val_121_t0t1 = _mm_extract_epi16(w121_16x8b, 2); row4_16x8b = _mm_insert_epi16(row4_16x8b, val_121_t0t1, 4); dst_strd2 = dst_strd << 1; dst_strd3 = dst_strd + dst_strd2; row1_16x8b = _mm_srli_si128(row4_16x8b, 6); row2_16x8b = _mm_srli_si128(row4_16x8b, 4); row3_16x8b = _mm_srli_si128(row4_16x8b, 2); row1 = _mm_cvtsi128_si32(row1_16x8b); row2 = _mm_cvtsi128_si32(row2_16x8b); row3 = _mm_cvtsi128_si32(row3_16x8b); row4 = _mm_cvtsi128_si32(row4_16x8b); *((WORD32 *)(pu1_dst)) = row1; *((WORD32 *)(pu1_dst + dst_strd)) = row2; *((WORD32 *)(pu1_dst + dst_strd2)) = row3; *((WORD32 *)(pu1_dst + dst_strd3)) = row4; } /** ******************************************************************************* * * ih264_intra_pred_luma_4x4_mode_vert_l_ssse3 * * @brief * Perform Intra prediction for luma_4x4 mode:Vertical_Left * * @par Description: * Perform Intra prediction for luma_4x4 mode:Vertical_Left ,described in sec 8.3.1.2.8 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_4x4_mode_vert_l_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top; WORD32 dst_strd2, dst_strd3; __m128i val_16x8b, val_sh_16x8b; __m128i w121_a1_8x16b, w121_a2_8x16b; __m128i row1_16x8b, row2_16x8b, row3_16x8b, row4_16x8b; __m128i zero_vector, const_2_8x16b; WORD32 row1,row2,row3,row4; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src +BLK_SIZE + 1; val_16x8b = _mm_loadl_epi64((__m128i *)pu1_top); zero_vector = _mm_setzero_si128(); val_sh_16x8b = _mm_srli_si128(val_16x8b, 1); row1_16x8b = _mm_avg_epu8(val_16x8b, val_sh_16x8b); w121_a1_8x16b = _mm_unpacklo_epi8(val_16x8b, zero_vector); //t0 t1 t2 t3 t4 t5... w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //t1 t2 t3 t4 t5 t6... w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //t0+t1 t1+t2 t2+t3 t3+t4 t4+t5... w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //t1+t2 t2+t3 t3+t4 t4+t5 t5+t6... zero_vector = _mm_setzero_si128(); const_2_8x16b = _mm_set1_epi16(2); w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //t0+2*t1+t2 t1+2*t2+t3 t2+2*t3+t4... w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, const_2_8x16b); w121_a1_8x16b = _mm_srai_epi16(w121_a1_8x16b, 2); row2_16x8b = _mm_packus_epi16(w121_a1_8x16b, w121_a1_8x16b); dst_strd2 = dst_strd << 1; dst_strd3 = dst_strd + dst_strd2; row3_16x8b = _mm_srli_si128(row1_16x8b, 1); row4_16x8b = _mm_srli_si128(row2_16x8b, 1); row1 = _mm_cvtsi128_si32(row1_16x8b); row2 = _mm_cvtsi128_si32(row2_16x8b); row3 = _mm_cvtsi128_si32(row3_16x8b); row4 = _mm_cvtsi128_si32(row4_16x8b); *((WORD32 *)(pu1_dst)) = row1; *((WORD32 *)(pu1_dst + dst_strd)) = row2; *((WORD32 *)(pu1_dst + dst_strd2)) = row3; *((WORD32 *)(pu1_dst + dst_strd3)) = row4; } /** ******************************************************************************* * * ih264_intra_pred_luma_4x4_mode_horz_u_ssse3 * * @brief * Perform Intra prediction for luma_4x4 mode:Horizontal_Up * * @par Description: * Perform Intra prediction for luma_4x4 mode:Horizontal_Up ,described in sec 8.3.1.2.9 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_4x4_mode_horz_u_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left; WORD32 dst_strd2, dst_strd3; __m128i val_16x8b, val_sh_16x8b; __m128i w11_16x8b; __m128i w121_a1_8x16b, w121_a2_8x16b, w121_16x8b; __m128i row1_16x8b, row2_16x8b, row3_16x8b, row4_16x8b; __m128i zero_vector, const_2_8x16b, rev_16x8b; WORD32 row1,row2,row3,row4; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK_SIZE - 1; zero_vector = _mm_setzero_si128(); rev_16x8b = _mm_setr_epi8(3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); val_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 3)); //l3 l2 l1 l0 0 0 0... val_16x8b = _mm_shuffle_epi8(val_16x8b, rev_16x8b); //l0 l1 l2 l3 l3 l3 l3... val_sh_16x8b = _mm_srli_si128(val_16x8b, 1); w11_16x8b = _mm_avg_epu8(val_16x8b, val_sh_16x8b); w121_a1_8x16b = _mm_unpacklo_epi8(val_16x8b, zero_vector); //l0 l1 l2 l3 l3 l3... w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l1 l2 l3 l3 l3 l3... w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l0+t1 l1+l2 l2+l3 2*l3 2*l3... w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l1+t2 l2+l3 2*l3 2*l3 2*l3... zero_vector = _mm_setzero_si128(); const_2_8x16b = _mm_set1_epi16(2); w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l0+2*l1+l2 l1+2*l2+l3 l2+3*l3 4*l3 4*l3... w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, const_2_8x16b); w121_a1_8x16b = _mm_srai_epi16(w121_a1_8x16b, 2); w121_16x8b = _mm_packus_epi16(w121_a1_8x16b, w121_a1_8x16b); dst_strd2 = dst_strd << 1; dst_strd3 = dst_strd + dst_strd2; row1_16x8b = _mm_unpacklo_epi8(w11_16x8b, w121_16x8b); row2_16x8b = _mm_srli_si128(row1_16x8b, 2); row3_16x8b = _mm_srli_si128(row1_16x8b, 4); row4_16x8b = _mm_srli_si128(row1_16x8b, 6); row1 = _mm_cvtsi128_si32(row1_16x8b); row2 = _mm_cvtsi128_si32(row2_16x8b); row3 = _mm_cvtsi128_si32(row3_16x8b); row4 = _mm_cvtsi128_si32(row4_16x8b); *((WORD32 *)(pu1_dst)) = row1; *((WORD32 *)(pu1_dst + dst_strd)) = row2; *((WORD32 *)(pu1_dst + dst_strd2)) = row3; *((WORD32 *)(pu1_dst + dst_strd3)) = row4; } /******************* 8x8 Modes *******************/ /** ******************************************************************************* * * ih264_intra_pred_luma_8x8_mode_vert_ssse3 * * @brief * Perform Intra prediction for luma_8x8 mode:vertical * * @par Description: * Perform Intra prediction for luma_8x8 mode:vertical ,described in sec 8.3.2.2.2 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_8x8_mode_vert_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; __m128i top_8x8b; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK8x8SIZE + 1; top_8x8b = _mm_loadl_epi64((__m128i *)pu1_top); _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), top_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), top_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), top_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), top_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), top_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), top_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), top_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), top_8x8b); } /** ******************************************************************************* * * ih264_intra_pred_luma_8x8_mode_horz_ssse3 * * @brief * Perform Intra prediction for luma_8x8 mode:horizontal * * @par Description: * Perform Intra prediction for uma_8x8 mode:horizontal ,described in sec 8.3.2.2.2 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_8x8_mode_horz_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = pu1_src + BLK8x8SIZE - 1; __m128i row1_8x8b, row2_8x8b, row3_8x8b, row4_8x8b; __m128i row5_8x8b, row6_8x8b, row7_8x8b, row8_8x8b; UNUSED(src_strd); UNUSED(ngbr_avail); row1_8x8b = _mm_set1_epi8(pu1_left[0]); row2_8x8b = _mm_set1_epi8(pu1_left[-1]); row3_8x8b = _mm_set1_epi8(pu1_left[-2]); row4_8x8b = _mm_set1_epi8(pu1_left[-3]); row5_8x8b = _mm_set1_epi8(pu1_left[-4]); row6_8x8b = _mm_set1_epi8(pu1_left[-5]); row7_8x8b = _mm_set1_epi8(pu1_left[-6]); row8_8x8b = _mm_set1_epi8(pu1_left[-7]); _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), row1_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), row2_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), row3_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), row4_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), row5_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), row6_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), row7_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), row8_8x8b); } /** ******************************************************************************* * * ih264_intra_pred_luma_8x8_mode_dc_ssse3 * * @brief * Perform Intra prediction for luma_8x8 mode:DC * * @par Description: * Perform Intra prediction for luma_8x8 mode:DC ,described in sec 8.3.2.2.4 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_8x8_mode_dc_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 u1_useleft; /* availability of left predictors (only for DC) */ UWORD8 u1_usetop; /* availability of top predictors (only for DC) */ UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ __m128i dc_val_8x8b; WORD32 dc_val = 0; UNUSED(src_strd); u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); pu1_top = pu1_src + BLK8x8SIZE + 1; pu1_left = pu1_src + BLK8x8SIZE - 1; if(u1_useleft || u1_usetop) { WORD32 shft = 2; __m128i val_8x8b, zero_8x8b, sum_8x16b; zero_8x8b = _mm_setzero_si128(); if(u1_useleft) { val_8x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 7)); sum_8x16b = _mm_sad_epu8(zero_8x8b, val_8x8b); shft++; dc_val += 4; dc_val += _mm_extract_epi16(sum_8x16b, 0); } if(u1_usetop) { val_8x8b = _mm_loadl_epi64((__m128i *)pu1_top); sum_8x16b = _mm_sad_epu8(zero_8x8b, val_8x8b); shft++; dc_val += 4; dc_val += _mm_extract_epi16(sum_8x16b, 0); } dc_val = dc_val >> shft; } else dc_val = 128; dc_val_8x8b = _mm_set1_epi8(dc_val); _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), dc_val_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), dc_val_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), dc_val_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), dc_val_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), dc_val_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), dc_val_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), dc_val_8x8b); _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), dc_val_8x8b); } /** ******************************************************************************* * * ih264_intra_pred_luma_8x8_mode_diag_dl_ssse3 * * @brief * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left * * @par Description: * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left ,described in sec 8.3.2.2.5 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_8x8_mode_diag_dl_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ __m128i top_16x8; __m128i out_15x16; __m128i a0_8x16, a1_8x16, a2_8x16; __m128i temp1, temp2; __m128i res1_8x16, res2_8x16; __m128i zero = _mm_setzero_si128(); __m128i const_val2_8x16 = _mm_set1_epi16(2); UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK8x8SIZE + 1; top_16x8 = _mm_loadu_si128((__m128i *)(pu1_top)); temp1 = _mm_srli_si128(top_16x8, 1); temp2 = _mm_srli_si128(top_16x8, 2); a0_8x16 = _mm_unpacklo_epi8(top_16x8, zero); a1_8x16 = _mm_unpacklo_epi8(temp1, zero); a2_8x16 = _mm_unpacklo_epi8(temp2, zero); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res1_8x16 = _mm_srai_epi16(a0_8x16, 2); temp2 = _mm_srli_si128(top_16x8, 2); temp1 = _mm_srli_si128(top_16x8, 1); a2_8x16 = _mm_unpackhi_epi8(temp2, zero); a0_8x16 = _mm_unpackhi_epi8(top_16x8, zero); a2_8x16 = _mm_shufflehi_epi16(a2_8x16, 0x14); a1_8x16 = _mm_unpackhi_epi8(temp1, zero); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res2_8x16 = _mm_srai_epi16(a0_8x16, 2); out_15x16 = _mm_packus_epi16(res1_8x16, res2_8x16); _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), out_15x16); out_15x16 = _mm_srli_si128(out_15x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), out_15x16); out_15x16 = _mm_srli_si128(out_15x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), out_15x16); out_15x16 = _mm_srli_si128(out_15x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), out_15x16); out_15x16 = _mm_srli_si128(out_15x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), out_15x16); out_15x16 = _mm_srli_si128(out_15x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), out_15x16); out_15x16 = _mm_srli_si128(out_15x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), out_15x16); out_15x16 = _mm_srli_si128(out_15x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), out_15x16); } /** ******************************************************************************* * * ih264_intra_pred_luma_8x8_mode_diag_dr_ssse3 * * @brief * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right * * @par Description: * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right ,described in sec 8.3.2.2.6 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_8x8_mode_diag_dr_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ __m128i top_8x8, left_16x8; __m128i out_15x16; __m128i a0_8x16, a1_8x16, a2_8x16; __m128i temp1, temp2; __m128i res1_8x16, res2_8x16; __m128i zero = _mm_setzero_si128(); __m128i const_val2_8x16 = _mm_set1_epi16(2); __m128i str_8x8; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK8x8SIZE - 1; pu1_top = pu1_src + BLK8x8SIZE + 1; left_16x8 = _mm_loadu_si128((__m128i *)(pu1_left - 7)); temp1 = _mm_srli_si128(left_16x8, 1); temp2 = _mm_srli_si128(left_16x8, 2); a0_8x16 = _mm_unpacklo_epi8(left_16x8, zero); a1_8x16 = _mm_unpacklo_epi8(temp1, zero); a2_8x16 = _mm_unpacklo_epi8(temp2, zero); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res1_8x16 = _mm_srai_epi16(a0_8x16, 2); top_8x8 = _mm_loadu_si128((__m128i *)(pu1_top - 1)); temp1 = _mm_srli_si128(top_8x8, 1); temp2 = _mm_srli_si128(top_8x8, 2); a0_8x16 = _mm_unpacklo_epi8(top_8x8, zero); a1_8x16 = _mm_unpacklo_epi8(temp1, zero); a2_8x16 = _mm_unpacklo_epi8(temp2, zero); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res2_8x16 = _mm_srai_epi16(a0_8x16, 2); out_15x16 = _mm_packus_epi16(res1_8x16, res2_8x16); str_8x8 = _mm_srli_si128(out_15x16, 7); _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out_15x16, 6); _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out_15x16, 5); _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out_15x16, 4); _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out_15x16, 3); _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out_15x16, 2); _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out_15x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), str_8x8); _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), out_15x16); } /** ******************************************************************************* * * ih264_intra_pred_luma_8x8_mode_vert_r_ssse3 * * @brief * Perform Intra prediction for luma_8x8 mode:Vertical_Right * * @par Description: * Perform Intra prediction for luma_8x8 mode:Vertical_Right ,described in sec 8.3.2.2.7 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_8x8_mode_vert_r_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ __m128i top_8x8, left_16x8; __m128i out1_16x16, out2_16x16; __m128i a0_8x16, a1_8x16, a2_8x16; __m128i temp1, temp2; __m128i res1_8x16, res2_8x16, res3_8x16; __m128i zero = _mm_setzero_si128(); __m128i const_val2_8x16 = _mm_set1_epi16(2); __m128i str_8x8; __m128i mask = _mm_set1_epi32(0xFFFF); UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK8x8SIZE - 1; pu1_top = pu1_src + BLK8x8SIZE + 1; left_16x8 = _mm_loadu_si128((__m128i *)(pu1_left - 6)); temp1 = _mm_srli_si128(left_16x8, 1); temp2 = _mm_srli_si128(left_16x8, 2); a0_8x16 = _mm_unpacklo_epi8(left_16x8, zero); a1_8x16 = _mm_unpacklo_epi8(temp1, zero); a2_8x16 = _mm_unpacklo_epi8(temp2, zero); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res1_8x16 = _mm_srai_epi16(a0_8x16, 2); top_8x8 = _mm_loadu_si128((__m128i *)(pu1_top - 1)); temp1 = _mm_srli_si128(top_8x8, 1); temp2 = _mm_srli_si128(top_8x8, 2); a0_8x16 = _mm_unpacklo_epi8(top_8x8, zero); a1_8x16 = _mm_unpacklo_epi8(temp1, zero); a2_8x16 = _mm_unpacklo_epi8(temp2, zero); res3_8x16 = _mm_avg_epu16(a0_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res2_8x16 = _mm_srai_epi16(a0_8x16, 2); str_8x8 = _mm_packus_epi16(res3_8x16, zero); _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), str_8x8); temp1 = _mm_and_si128(res1_8x16, mask); temp1 = _mm_packs_epi32(temp1, temp1); out1_16x16 = _mm_packus_epi16(temp1, res2_8x16); res1_8x16 = _mm_slli_si128(res1_8x16, 2); temp1 = _mm_and_si128(res1_8x16, mask); temp1 = _mm_packs_epi32(temp1, temp1); out2_16x16 = _mm_packus_epi16(temp1, res3_8x16); str_8x8 = _mm_srli_si128(out1_16x16, 7); _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out2_16x16, 7); _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out1_16x16, 6); _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out2_16x16, 6); _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out1_16x16, 5); _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out2_16x16, 5); _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out1_16x16, 4); _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), str_8x8); } /* ******************************************************************************* * * ih264_intra_pred_luma_8x8_mode_horz_d_ssse3 * * @brief * Perform Intra prediction for luma_8x8 mode:Horizontal_Down * * @par Description: * Perform Intra prediction for luma_8x8 mode:Horizontal_Down ,described in sec 8.3.2.2.8 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_8x8_mode_horz_d_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ __m128i pels_16x16; __m128i temp1, temp2, temp3, temp4; __m128i a0_8x16, a1_8x16, a2_8x16; __m128i zero = _mm_setzero_si128(); __m128i const_val2_8x16 = _mm_set1_epi16(2); __m128i res1_8x16, res2_8x16; __m128i out1_16x16, out2_16x16; __m128i str_8x8; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK8x8SIZE - 1; pels_16x16 = _mm_loadu_si128((__m128i *)(pu1_left - 7)); temp1 = _mm_srli_si128(pels_16x16, 1); temp2 = _mm_srli_si128(pels_16x16, 2); a0_8x16 = _mm_unpacklo_epi8(pels_16x16, zero); a1_8x16 = _mm_unpacklo_epi8(temp1, zero); a2_8x16 = _mm_unpacklo_epi8(temp2, zero); res1_8x16 = _mm_avg_epu16(a0_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res2_8x16 = _mm_srai_epi16(a0_8x16, 2); temp3 = _mm_unpacklo_epi16(res1_8x16, res2_8x16); temp4 = _mm_unpackhi_epi16(res1_8x16, res2_8x16); out2_16x16 = _mm_packus_epi16(temp3, temp4); a0_8x16 = _mm_unpackhi_epi8(pels_16x16, zero); a1_8x16 = _mm_unpackhi_epi8(temp1, zero); a2_8x16 = _mm_unpackhi_epi8(temp2, zero); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res2_8x16 = _mm_srai_epi16(a0_8x16, 2); out1_16x16 = _mm_packus_epi16(res2_8x16, zero); temp1 = _mm_srli_si128(out2_16x16, 8); out1_16x16 = _mm_unpacklo_epi64(temp1, out1_16x16); str_8x8 = _mm_srli_si128(out1_16x16, 6); _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out1_16x16, 4); _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out1_16x16, 2); _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), str_8x8); _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), out1_16x16); str_8x8 = _mm_srli_si128(out2_16x16, 6); _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out2_16x16, 4); _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out2_16x16, 2); _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), str_8x8); _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), out2_16x16); } /** ******************************************************************************* * * ih264_intra_pred_luma_8x8_mode_vert_l_ssse3 * * @brief * Perform Intra prediction for luma_8x8 mode:Vertical_Left * * @par Description: * Perform Intra prediction for luma_8x8 mode:Vertical_Left ,described in sec 8.3.2.2.9 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_8x8_mode_vert_l_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ __m128i top_16x16; __m128i temp1, temp2; __m128i a0_8x16, a1_8x16, a2_8x16; __m128i zero = _mm_setzero_si128(); __m128i const_val2_8x16 = _mm_set1_epi16(2); __m128i res1_8x16, res2_8x16, res3_8x16, res4_8x16; __m128i out1_16x16, out2_16x16; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + BLK8x8SIZE + 1; top_16x16 = _mm_loadu_si128((__m128i *)(pu1_top)); temp1 = _mm_srli_si128(top_16x16, 1); temp2 = _mm_srli_si128(top_16x16, 2); a0_8x16 = _mm_unpacklo_epi8(top_16x16, zero); a1_8x16 = _mm_unpacklo_epi8(temp1, zero); a2_8x16 = _mm_unpacklo_epi8(temp2, zero); res1_8x16 = _mm_avg_epu16(a0_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res2_8x16 = _mm_srai_epi16(a0_8x16, 2); a0_8x16 = _mm_unpackhi_epi8(top_16x16, zero); a1_8x16 = _mm_unpackhi_epi8(temp1, zero); a2_8x16 = _mm_unpackhi_epi8(temp2, zero); res3_8x16 = _mm_avg_epu16(a0_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res4_8x16 = _mm_srai_epi16(a0_8x16, 2); out1_16x16 = _mm_packus_epi16(res1_8x16, res3_8x16); out2_16x16 = _mm_packus_epi16(res2_8x16, res4_8x16); _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), out1_16x16); _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), out2_16x16); out1_16x16 = _mm_srli_si128(out1_16x16, 1); out2_16x16 = _mm_srli_si128(out2_16x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), out1_16x16); _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), out2_16x16); out1_16x16 = _mm_srli_si128(out1_16x16, 1); out2_16x16 = _mm_srli_si128(out2_16x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), out1_16x16); _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), out2_16x16); out1_16x16 = _mm_srli_si128(out1_16x16, 1); out2_16x16 = _mm_srli_si128(out2_16x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), out1_16x16); _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), out2_16x16); } /** ******************************************************************************* * * ih264_intra_pred_luma_8x8_mode_horz_u_ssse3 * * @brief * Perform Intra prediction for luma_8x8 mode:Horizontal_Up * * @par Description: * Perform Intra prediction for luma_8x8 mode:Horizontal_Up ,described in sec 8.3.2.2.10 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_8x8_mode_horz_u_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ __m128i left_16x16; __m128i temp1, temp2; __m128i a0_8x16, a1_8x16, a2_8x16; __m128i zero = _mm_setzero_si128(); __m128i const_val2_8x16 = _mm_set1_epi16(2); __m128i res1_8x16, res2_8x16; __m128i out1_16x16; __m128i str_8x8; __m128i shuffle_16x16; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + BLK8x8SIZE - 1; shuffle_16x16 = _mm_set_epi8(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F); left_16x16 = _mm_loadu_si128((__m128i *)(pu1_left - 7)); temp1 = _mm_srli_si128(left_16x16, 1); a0_8x16 = _mm_unpacklo_epi8(left_16x16, zero); a0_8x16 = _mm_slli_si128(a0_8x16, 2); a1_8x16 = _mm_unpacklo_epi8(left_16x16, zero); a0_8x16 = _mm_shufflelo_epi16(a0_8x16, 0xE5); a2_8x16 = _mm_unpacklo_epi8(temp1, zero); res1_8x16 = _mm_avg_epu16(a0_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); res2_8x16 = _mm_srai_epi16(a0_8x16, 2); temp1 = _mm_unpacklo_epi16(res1_8x16, res2_8x16); temp2 = _mm_unpackhi_epi16(res1_8x16, res2_8x16); out1_16x16 = _mm_packus_epi16(temp1, temp2); out1_16x16 = _mm_shuffle_epi8(out1_16x16, shuffle_16x16); str_8x8 = _mm_srli_si128(out1_16x16, 1); _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out1_16x16, 3); _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out1_16x16, 5); _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(out1_16x16, 7); _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), str_8x8); temp1 = _mm_set1_epi8(pu1_left[-7]); str_8x8 = _mm_unpacklo_epi64(str_8x8, temp1); str_8x8 = _mm_srli_si128(str_8x8, 2); _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(str_8x8, 2); _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(str_8x8, 2); _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), str_8x8); str_8x8 = _mm_srli_si128(str_8x8, 2); _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), str_8x8); } /******************* 16x16 Modes *******************/ /** ******************************************************************************* * *ih264_intra_pred_luma_16x16_mode_vert_ssse3 * * @brief * Perform Intra prediction for luma_16x16 mode:Vertical * * @par Description: * Perform Intra prediction for luma_16x16 mode:Vertical, described in sec 8.3.3.1 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels (Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_16x16_mode_vert_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_top; WORD32 dst_strd2, dst_strd3, dst_strd4; __m128i top_16x8b; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + MB_SIZE + 1; dst_strd2 = dst_strd << 1; dst_strd4 = dst_strd << 2; top_16x8b = _mm_loadu_si128((__m128i *)pu1_top); dst_strd3 = dst_strd + dst_strd2; _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), top_16x8b); pu1_dst += dst_strd4; _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), top_16x8b); pu1_dst += dst_strd4; _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), top_16x8b); pu1_dst += dst_strd4; _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), top_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), top_16x8b); } /** ******************************************************************************* * *ih264_intra_pred_luma_16x16_mode_horz_ssse3 * * @brief * Perform Intra prediction for luma_16x16 mode:Horizontal * * @par Description: * Perform Intra prediction for luma_16x16 mode:Horizontal, described in sec 8.3.3.2 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_16x16_mode_horz_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left; WORD32 dst_strd2, dst_strd3, dst_strd4; __m128i row1_16x8b, row2_16x8b, row3_16x8b, row4_16x8b; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_left = pu1_src + MB_SIZE - 1; dst_strd4 = dst_strd << 2; dst_strd2 = dst_strd << 1; dst_strd3 = dst_strd4 - dst_strd; row1_16x8b = _mm_set1_epi8(*(pu1_left)); row2_16x8b = _mm_set1_epi8(*(pu1_left - 1)); row3_16x8b = _mm_set1_epi8(*(pu1_left - 2)); row4_16x8b = _mm_set1_epi8(*(pu1_left - 3)); _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), row3_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), row4_16x8b); pu1_dst += dst_strd4; row1_16x8b = _mm_set1_epi8(*(pu1_left - 4)); row2_16x8b = _mm_set1_epi8(*(pu1_left - 5)); row3_16x8b = _mm_set1_epi8(*(pu1_left - 6)); row4_16x8b = _mm_set1_epi8(*(pu1_left - 7)); _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), row3_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), row4_16x8b); pu1_dst += dst_strd4; row1_16x8b = _mm_set1_epi8(*(pu1_left - 8)); row2_16x8b = _mm_set1_epi8(*(pu1_left - 9)); row3_16x8b = _mm_set1_epi8(*(pu1_left - 10)); row4_16x8b = _mm_set1_epi8(*(pu1_left - 11)); _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), row3_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), row4_16x8b); pu1_dst += dst_strd4; row1_16x8b = _mm_set1_epi8(*(pu1_left - 12)); row2_16x8b = _mm_set1_epi8(*(pu1_left - 13)); row3_16x8b = _mm_set1_epi8(*(pu1_left - 14)); row4_16x8b = _mm_set1_epi8(*(pu1_left - 15)); _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), row3_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), row4_16x8b); } /** ******************************************************************************* * *ih264_intra_pred_luma_16x16_mode_dc_ssse3 * * @brief * Perform Intra prediction for luma_16x16 mode:DC * * @par Description: * Perform Intra prediction for luma_16x16 mode:DC, described in sec 8.3.3.3 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * ** @param[in] ngbr_avail * availability of neighbouring pixels * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_16x16_mode_dc_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { WORD8 u1_useleft, u1_usetop; WORD32 dc_val; WORD32 dst_strd2, dst_strd3, dst_strd4; __m128i dc_val_16x8b; UNUSED(src_strd); u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); if(u1_useleft || u1_usetop) { WORD32 shft; __m128i val_16x8b, zero_16x8b, sum_8x16b; dc_val = 0; shft = 3; zero_16x8b = _mm_setzero_si128(); if(u1_useleft) { UWORD8 *pu1_left; pu1_left = pu1_src + MB_SIZE - 1; val_16x8b = _mm_loadu_si128((__m128i *)(pu1_left - 15)); sum_8x16b = _mm_sad_epu8(zero_16x8b, val_16x8b); shft++; dc_val += 8; dc_val += _mm_extract_epi16(sum_8x16b, 0); dc_val += _mm_extract_epi16(sum_8x16b, 4); } if(u1_usetop) { UWORD8 *pu1_top; pu1_top = pu1_src + MB_SIZE + 1; val_16x8b = _mm_loadu_si128((__m128i *)pu1_top); sum_8x16b = _mm_sad_epu8(zero_16x8b, val_16x8b); shft++; dc_val += 8; dc_val += _mm_extract_epi16(sum_8x16b, 0); dc_val += _mm_extract_epi16(sum_8x16b, 4); } dc_val = dc_val >> shft; } else dc_val = 128; dc_val_16x8b = _mm_set1_epi8(dc_val); dst_strd2 = dst_strd << 1; dst_strd4 = dst_strd << 2; dst_strd3 = dst_strd + dst_strd2; _mm_storeu_si128((__m128i *)pu1_dst, dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), dc_val_16x8b); pu1_dst += dst_strd4; _mm_storeu_si128((__m128i *)pu1_dst, dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), dc_val_16x8b); pu1_dst += dst_strd4; _mm_storeu_si128((__m128i *)pu1_dst, dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), dc_val_16x8b); pu1_dst += dst_strd4; _mm_storeu_si128((__m128i *)pu1_dst, dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), dc_val_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), dc_val_16x8b); } /** ******************************************************************************* * *ih264_intra_pred_luma_16x16_mode_plane_ssse3 * * @brief * Perform Intra prediction for luma_16x16 mode:PLANE * * @par Description: * Perform Intra prediction for luma_16x16 mode:PLANE, described in sec 8.3.3.4 * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[out] pu1_dst * UWORD8 pointer to the destination * * @param[in] src_strd * integer source stride * * @param[in] dst_strd * integer destination stride * * @param[in] ngbr_avail * availability of neighbouring pixels(Not used in this function) * * @returns * * @remarks * None * *******************************************************************************/ ATTRIBUTE_SSSE3 void ih264_intra_pred_luma_16x16_mode_plane_ssse3(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 ngbr_avail) { UWORD8 *pu1_left, *pu1_top; WORD32 a, b, c; __m128i rev_8x16b, mul_8x16b, zero_16x8b; UNUSED(src_strd); UNUSED(ngbr_avail); pu1_top = pu1_src + MB_SIZE + 1; pu1_left = pu1_src + MB_SIZE - 1; rev_8x16b = _mm_setr_epi16(0x0f0e, 0x0d0c, 0x0b0a, 0x0908, 0x0706, 0x0504, 0x0302, 0x0100); //used to reverse the order of 16-bit values in a vector mul_8x16b = _mm_setr_epi16(1, 2, 3, 4, 5, 6, 7, 8); zero_16x8b = _mm_setzero_si128(); //calculating a, b and c { WORD32 h, v; __m128i h_val1_16x8b, h_val2_16x8b; __m128i h_val1_8x16b, h_val2_8x16b, h_val_4x32b; __m128i v_val1_16x8b, v_val2_16x8b; __m128i v_val1_8x16b, v_val2_8x16b, v_val_4x32b; __m128i hv_val_4x32b; a = (pu1_top[15] + pu1_left[-15]) << 4; h_val1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_top + 8)); h_val2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_top - 1)); v_val1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 15)); v_val2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 6)); h_val1_8x16b = _mm_unpacklo_epi8(h_val1_16x8b, zero_16x8b); h_val2_8x16b = _mm_unpacklo_epi8(h_val2_16x8b, zero_16x8b); v_val1_8x16b = _mm_unpacklo_epi8(v_val1_16x8b, zero_16x8b); v_val2_8x16b = _mm_unpacklo_epi8(v_val2_16x8b, zero_16x8b); h_val2_8x16b = _mm_shuffle_epi8(h_val2_8x16b, rev_8x16b); v_val1_8x16b = _mm_shuffle_epi8(v_val1_8x16b, rev_8x16b); h_val1_8x16b = _mm_sub_epi16(h_val1_8x16b, h_val2_8x16b); v_val1_8x16b = _mm_sub_epi16(v_val1_8x16b, v_val2_8x16b); h_val_4x32b = _mm_madd_epi16(mul_8x16b, h_val1_8x16b); v_val_4x32b = _mm_madd_epi16(mul_8x16b, v_val1_8x16b); hv_val_4x32b = _mm_hadd_epi32(h_val_4x32b, v_val_4x32b); hv_val_4x32b = _mm_hadd_epi32(hv_val_4x32b, hv_val_4x32b); h = _mm_extract_epi16(hv_val_4x32b, 0); v = _mm_extract_epi16(hv_val_4x32b, 2); h = (h << 16) >> 16; v = (v << 16) >> 16; b = ((h << 2) + h + 32) >> 6; c = ((v << 2) + v + 32) >> 6; } //using a, b and c to compute the fitted plane values { __m128i const_8x16b, b_8x16b, c_8x16b, c2_8x16b; __m128i res1_l_8x16b, res1_h_8x16b; __m128i res2_l_8x16b, res2_h_8x16b; __m128i res1_sh_l_8x16b, res1_sh_h_8x16b, res1_16x8b; __m128i res2_sh_l_8x16b, res2_sh_h_8x16b, res2_16x8b; b_8x16b = _mm_set1_epi16(b); c_8x16b = _mm_set1_epi16(c); c2_8x16b = _mm_set1_epi16(c << 1); const_8x16b = _mm_set1_epi16(a - c*7 + 16); res1_h_8x16b = _mm_mullo_epi16(mul_8x16b, b_8x16b); //contains {b*1, b*2, b*3,... b*8} res1_l_8x16b = _mm_shuffle_epi8(res1_h_8x16b, rev_8x16b); res1_l_8x16b = _mm_srli_si128(res1_l_8x16b, 2); res1_l_8x16b = _mm_sub_epi16(zero_16x8b, res1_l_8x16b); //contains {-b*7, -b*6,... -b*1, b*0} // rows 1, 2 res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, const_8x16b); res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, const_8x16b); res2_h_8x16b = _mm_add_epi16(res1_h_8x16b, c_8x16b); res2_l_8x16b = _mm_add_epi16(res1_l_8x16b, c_8x16b); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); // rows 3, 4 res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); pu1_dst += dst_strd << 1; res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); // rows 5, 6 res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); pu1_dst += dst_strd << 1; res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); // rows 7, 8 res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); pu1_dst += dst_strd << 1; res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); // rows 9, 10 res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); pu1_dst += dst_strd << 1; res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); // rows 11, 12 res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); pu1_dst += dst_strd << 1; res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); // rows 13, 14 res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); pu1_dst += dst_strd << 1; res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); // rows 15, 16 res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); pu1_dst += dst_strd << 1; res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); } } ================================================ FILE: dependencies/ih264d/common/x86/ih264_mem_fns_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_mem_fns_atom_intr.c * * @brief * Functions used for memory operations * * @author * Ittiam * * @par List of Functions: * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include "ih264_typedefs.h" #include "ih264_mem_fns.h" #include <immintrin.h> #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /** ******************************************************************************* * * @brief * memcpy of a 8,16 or 32 bytes * * @par Description: * Does memcpy of 8bit data from source to destination for 8,16 or 32 number of bytes * * @param[in] pu1_dst * UWORD8 pointer to the destination * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] num_bytes * number of bytes to copy * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_memcpy_mul_8_ssse3(UWORD8 *pu1_dst, UWORD8 *pu1_src, UWORD32 num_bytes) { int col; for(col = num_bytes; col >= 8; col -= 8) { __m128i src_temp16x8b; src_temp16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); pu1_src += 8; _mm_storel_epi64((__m128i *)(pu1_dst), src_temp16x8b); pu1_dst += 8; } } /** ******************************************************************************* * * @brief * memset of a 8,16 or 32 bytes * * @par Description: * Does memset of 8bit data for 8,16 or 32 number of bytes * * @param[in] pu1_dst * UWORD8 pointer to the destination * * @param[in] value * UWORD8 value used for memset * * @param[in] num_bytes * number of bytes to set * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_memset_mul_8_ssse3(UWORD8 *pu1_dst, UWORD8 value, UWORD32 num_bytes) { int col; __m128i src_temp16x8b; src_temp16x8b = _mm_set1_epi8(value); for(col = num_bytes; col >= 8; col -= 8) { _mm_storel_epi64((__m128i *)(pu1_dst), src_temp16x8b); pu1_dst += 8; } } /** ******************************************************************************* * * @brief * memset of 16bit data of a 8,16 or 32 bytes * * @par Description: * Does memset of 16bit data for 8,16 or 32 number of bytes * * @param[in] pu2_dst * UWORD8 pointer to the destination * * @param[in] value * UWORD16 value used for memset * * @param[in] num_words * number of words to set * @returns * * @remarks * None * ******************************************************************************* */ void ih264_memset_16bit_mul_8_ssse3(UWORD16 *pu2_dst, UWORD16 value, UWORD32 num_words) { int col; __m128i src_temp16x8b; src_temp16x8b = _mm_set1_epi16(value); for(col = num_words; col >= 8; col -= 8) { _mm_storeu_si128((__m128i *)(pu2_dst), src_temp16x8b); pu2_dst += 8; } } ================================================ FILE: dependencies/ih264d/common/x86/ih264_padding_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_padding_atom_intr.c * * @brief * Contains function definitions for Padding * * @author * Srinivas T * * @par List of Functions: * - ih264_pad_left_luma_ssse3() * - ih264_pad_left_chroma_ssse3() * - ih264_pad_right_luma_ssse3() * - ih264_pad_right_chroma_ssse3() * * @remarks * None * ******************************************************************************* */ #include <string.h> #include <assert.h> #include "ih264_typedefs.h" #include "ih264_platform_macros.h" #include "ih264_mem_fns.h" #include "ih264_debug.h" #include <immintrin.h> #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /** ******************************************************************************* * * @brief * Padding (luma block) at the left of a 2d array * * @par Description: * The left column of a 2d array is replicated for pad_size times at the left * * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] src_strd * integer source stride * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @param[in] pad_size * integer -padding size of the array * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_pad_left_luma_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 ht, WORD32 pad_size) { WORD32 row; WORD32 i; UWORD8 *pu1_dst; ASSERT(pad_size % 8 == 0); for(row = 0; row < ht; row++) { __m128i src_temp0_16x8b; pu1_dst = pu1_src - pad_size; src_temp0_16x8b = _mm_set1_epi8(*pu1_src); for(i = 0; i < pad_size; i += 8) { _mm_storel_epi64((__m128i *)(pu1_dst + i), src_temp0_16x8b); } pu1_src += src_strd; } } /** ******************************************************************************* * * @brief * Padding (chroma block) at the left of a 2d array * * @par Description: * The left column of a 2d array is replicated for pad_size times at the left * * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] src_strd * integer source stride * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array (each colour component) * * @param[in] pad_size * integer -padding size of the array * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_pad_left_chroma_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 ht, WORD32 pad_size) { WORD32 row; WORD32 col; UWORD8 *pu1_dst; ASSERT(pad_size % 8 == 0); for(row = 0; row < ht; row++) { __m128i src_temp0_16x8b; pu1_dst = pu1_src - pad_size; src_temp0_16x8b = _mm_set1_epi16(*((UWORD16 *)pu1_src)); for(col = 0; col < pad_size; col += 8) { _mm_storel_epi64((__m128i *)(pu1_dst + col), src_temp0_16x8b); } pu1_src += src_strd; } } /** ******************************************************************************* * * @brief * Padding (luma block) at the right of a 2d array * * @par Description: * The right column of a 2d array is replicated for pad_size times at the right * * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] src_strd * integer source stride * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @param[in] pad_size * integer -padding size of the array * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_pad_right_luma_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 ht, WORD32 pad_size) { WORD32 row; WORD32 col; UWORD8 *pu1_dst; ASSERT(pad_size % 8 == 0); for(row = 0; row < ht; row++) { __m128i src_temp0_16x8b; pu1_dst = pu1_src; src_temp0_16x8b = _mm_set1_epi8(*(pu1_src - 1)); for(col = 0; col < pad_size; col += 8) { _mm_storel_epi64((__m128i *)(pu1_dst + col), src_temp0_16x8b); } pu1_src += src_strd; } } /** ******************************************************************************* * * @brief * Padding (chroma block) at the right of a 2d array * * @par Description: * The right column of a 2d array is replicated for pad_size times at the right * * * @param[in] pu1_src * UWORD8 pointer to the source * * @param[in] src_strd * integer source stride * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array (each colour component) * * @param[in] pad_size * integer -padding size of the array * * @param[in] ht * integer height of the array * * @param[in] wd * integer width of the array * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264_pad_right_chroma_ssse3(UWORD8 *pu1_src, WORD32 src_strd, WORD32 ht, WORD32 pad_size) { WORD32 row; WORD32 col; UWORD8 *pu1_dst; ASSERT(pad_size % 8 == 0); for(row = 0; row < ht; row++) { __m128i src_temp0_16x8b; pu1_dst = pu1_src; src_temp0_16x8b = _mm_set1_epi16(*((UWORD16 *)(pu1_src - 2))); for(col = 0; col < pad_size; col += 8) { _mm_storel_epi64((__m128i *)(pu1_dst + col), src_temp0_16x8b); } pu1_src += src_strd; } } ================================================ FILE: dependencies/ih264d/common/x86/ih264_platform_macros.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_platform_macros.h * * @brief * Platform specific Macro definitions used in the codec * * @author * Ittiam * * @remarks * None * ******************************************************************************* */ #ifndef _IH264_PLATFORM_MACROS_H_ #define _IH264_PLATFORM_MACROS_H_ #include <stdint.h> #include <immintrin.h> #if defined(_MSC_VER) && defined(__clang__) #include <intrin.h> #endif #define CLIP_U8(x) CLIP3(0, UINT8_MAX, (x)) #define CLIP_S8(x) CLIP3(INT8_MIN, INT8_MAX, (x)) #define CLIP_U10(x) CLIP3(0, 1023, (x)) #define CLIP_S10(x) CLIP3(-512, 511, (x)) #define CLIP_U11(x) CLIP3(0, 2047, (x)) #define CLIP_S11(x) CLIP3(-1024, 1023, (x)) #define CLIP_U12(x) CLIP3(0, 4095, (x)) #define CLIP_S12(x) CLIP3(-2048, 2047, (x)) #define CLIP_U16(x) CLIP3(0, UINT16_MAX, (x)) #define CLIP_S16(x) CLIP3(INT16_MIN, INT16_MAX, (x)) #define CLIP_U32(x) CLIP3(0, UINT32_MAX, (x)) #define CLIP_S32(x) CLIP3(INT32_MIN, INT32_MAX, (x)) #define MEM_ALIGN16 __attribute__ ((aligned (16))) #define SHL(x,y) (((y) < 32) ? ((x) << (y)) : 0) #define SHR(x,y) (((y) < 32) ? ((x) >> (y)) : 0) #define SHR_NEG(val,shift) ((shift>0)?(val>>shift):(val<<(-shift))) #define SHL_NEG(val,shift) ((shift<0)?(val>>(-shift)):(val<<shift)) #define PLD(a) /* For MSVC x64 */ #if defined(_MSC_VER) && !defined(__clang__) static inline int __builtin_clz(unsigned x) { unsigned long n; _BitScanReverse(&n, x); return n ^ 31; } static inline int __builtin_ctz(unsigned x) { unsigned long ret; _BitScanForward(&ret, x); return (int)ret; } static inline void __sync_synchronize() { __faststorefence(); } #define NOP(nop_cnt) {UWORD32 nop_i; for (nop_i = 0; nop_i < nop_cnt; nop_i++) __nop();} #else #define NOP(nop_cnt) {UWORD32 nop_i; for (nop_i = 0; nop_i < nop_cnt; nop_i++) asm("nop");} #endif /* In normal cases, 0 will not be passed as an argument to CLZ and CTZ. As CLZ and CTZ outputs are used as a shift value in few places, these return 31 for u4_word == 0 case, just to handle error cases gracefully without any undefined behaviour */ static __inline UWORD32 CLZ(UWORD32 u4_word) { if(u4_word || 1) return(__builtin_clz(u4_word)); else return 31; } static __inline UWORD32 CTZ(UWORD32 u4_word) { if(0 == u4_word && 0) return 31; else { unsigned int index; index = __builtin_ctz(u4_word); return (UWORD32)index; } } #define DATA_SYNC() __sync_synchronize() //#define INLINE __inline #define INLINE inline #define PREFETCH_ENABLE 1 #if PREFETCH_ENABLE #define PREFETCH(ptr, type) _mm_prefetch(ptr, type); #else #define PREFETCH(ptr, type) #endif #define MEM_ALIGN8 __attribute__ ((aligned (8))) #define MEM_ALIGN16 __attribute__ ((aligned (16))) #define MEM_ALIGN32 __attribute__ ((aligned (32))) #endif /* _IH264_PLATFORM_MACROS_H_ */ ================================================ FILE: dependencies/ih264d/common/x86/ih264_resi_trans_quant_sse42.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264_resi_trans_quant_sse42.c * * @brief * Contains function definitions single stage forward transform for H.264 * It will calculate the residue, do the cf and then do quantization * * @author * Mohit [100664] * * @par List of Functions: * - ih264_resi_trans_quant_4x4_sse42() * - ih264_resi_trans_quant_chroma_4x4_sse42() * * @remarks * None * ******************************************************************************* */ /* System include files */ #include <stddef.h> /* User include files */ #include "ih264_typedefs.h" #include "ih264_defs.h" #include "ih264_size_defs.h" #include "ih264_macros.h" #include "ih264_trans_macros.h" #include "ih264_trans_data.h" #include "ih264_structs.h" #include "ih264_trans_quant_itrans_iquant.h" #include <immintrin.h> #ifdef __GNUC__ #define ATTRIBUTE_SSE42 __attribute__((target("sse4.2"))) #else #define ATTRIBUTE_SSE42 #endif /** ******************************************************************************* * * @brief * This function performs forward transform and quantization on a 4*4 block * * @par Description: * The function accepts source buffer and estimation buffer. From these, it * computes the residue. This is residue is then transformed and quantized. * The transform and quantization are in placed computed. They use the residue * buffer for this. * * @param[in] pu1_src * Pointer to source sub-block * * @param[in] pu1_pred * Pointer to prediction sub-block * * @param[in] pi2_out * Pointer to residual sub-block * * @param[in] src_strd * Source stride * * @param[in] pred_strd * Prediction stride * * @param[in] dst_strd * Destination stride * * @param[in] u4_qbits * QP_BITS_h264_4x4 + floor(QP/6) * * @param[in] pu2_threshold_matrix * Pointer to Forward Quant Threshold Matrix * * @param[in] pu2_scale_matrix * Pointer to Forward Quant Scale Matrix * * @param[in] u4_round_factor * Quantization Round factor * * @param[out] pu1_nnz * Total non-zero coefficients in the current sub-block * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSE42 void ih264_resi_trans_quant_4x4_sse42(UWORD8 *pu1_src, UWORD8 *pu1_pred, WORD16 *pi2_out, WORD32 src_strd, WORD32 pred_strd, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor, UWORD8 *pu1_nnz, WORD16 *pi2_alt_dc_addr) { WORD32 tmp_dc, u4_zero_coeff, u4_nonzero_coeff = 0; WORD32 mask0, mask1; __m128i sum0, sum1, sum2, cmp0, cmp1; __m128i rnd_fact = _mm_set1_epi32(u4_round_factor); __m128i temp_2 = _mm_set1_epi16(2); __m128i temp_1 = _mm_set1_epi16(1); __m128i src_r0, src_r1, src_r2, src_r3; __m128i pred_r0, pred_r1, pred_r2, pred_r3; __m128i temp0, temp1, temp2, temp3; __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero __m128i sign_reg0, sign_reg2; __m128i scalemat_r0_r1, scalemat_r2_r3; UNUSED (pu2_threshold_matrix); scalemat_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_scale_matrix)); //b00 b01 b02 b03 b10 b11 b12 b13 -- the scaling matrix 0th,1st row scalemat_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_scale_matrix + 8)); //b20 b21 b22 b23 b30 b31 b32 b33 -- the scaling matrix 2nd,3rd row src_r0 = _mm_loadl_epi64((__m128i *) (&pu1_src[0])); //a00 a01 a02 a03 0 0 0 0 0 0 0 0 -- all 8 bits src_r1 = _mm_loadl_epi64((__m128i *) (&pu1_src[src_strd])); //a10 a11 a12 a13 0 0 0 0 0 0 0 0 -- all 8 bits src_r2 = _mm_loadl_epi64((__m128i *) (&pu1_src[2 * src_strd])); //a20 a21 a22 a23 0 0 0 0 0 0 0 0 -- all 8 bits src_r3 = _mm_loadl_epi64((__m128i *) (&pu1_src[3 * src_strd])); //a30 a31 a32 a33 0 0 0 0 0 0 0 0 -- all 8 bits src_r0 = _mm_cvtepu8_epi16(src_r0); src_r1 = _mm_cvtepu8_epi16(src_r1); src_r2 = _mm_cvtepu8_epi16(src_r2); src_r3 = _mm_cvtepu8_epi16(src_r3); pred_r0 = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits pred_r1 = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits pred_r2 = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits pred_r3 = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits pred_r0 = _mm_cvtepu8_epi16(pred_r0); //p00 p01 p02 p03 -- all 16 bits pred_r1 = _mm_cvtepu8_epi16(pred_r1); //p10 p11 p12 p13 -- all 16 bits pred_r2 = _mm_cvtepu8_epi16(pred_r2); //p20 p21 p22 p23 -- all 16 bits pred_r3 = _mm_cvtepu8_epi16(pred_r3); //p30 p31 p32 p33 -- all 16 bits src_r0 = _mm_sub_epi16(src_r0, pred_r0); src_r1 = _mm_sub_epi16(src_r1, pred_r1); src_r2 = _mm_sub_epi16(src_r2, pred_r2); src_r3 = _mm_sub_epi16(src_r3, pred_r3); /* Perform Forward transform */ /*-------------------------------------------------------------*/ /* DCT [ Horizontal transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 a1 a2 a3 * b0 b1 b2 b3 * c0 c1 c2 c3 * d0 d1 d2 d3 */ temp0 = _mm_unpacklo_epi16(src_r0, src_r1); //a0 b0 a1 b1 a2 b2 a3 b3 temp2 = _mm_unpacklo_epi16(src_r2, src_r3); //c0 d0 c1 d1 c2 d2 c3 d3 temp1 = _mm_unpacklo_epi32(temp0, temp2); //a0 b0 c0 d0 a1 b1 c1 d1 temp3 = _mm_unpackhi_epi32(temp0, temp2); //a2 b2 c2 d2 a3 b3 c3 d3 src_r0 = _mm_unpacklo_epi64(temp1, zero_8x16b); //a0 b0 c0 d0 src_r1 = _mm_unpackhi_epi64(temp1, zero_8x16b); //a1 b1 c1 d1 src_r2 = _mm_unpacklo_epi64(temp3, zero_8x16b); //a2 b2 c2 d2 src_r3 = _mm_unpackhi_epi64(temp3, zero_8x16b); //a3 b3 c3 d3 /*----------------------------------------------------------*/ /* x0 = z0 + z3 */ temp0 = _mm_add_epi16(src_r0, src_r3); /* x1 = z1 + z2 */ temp1 = _mm_add_epi16(src_r1, src_r2); /* x2 = z1 - z2 */ temp2 = _mm_sub_epi16(src_r1, src_r2); /* x3 = z0 - z3 */ temp3 = _mm_sub_epi16(src_r0, src_r3); /* z0 = x0 + x1 */ src_r0 = _mm_add_epi16(temp0, temp1); /* z1 = (x3 << 1) + x2 */ src_r1 = _mm_slli_epi16(temp3, 1); //(x3<<1) src_r1 = _mm_add_epi16(src_r1, temp2); /* z2 = x0 - x1 */ src_r2 = _mm_sub_epi16(temp0, temp1); /* z3 = x3 - (x2 << 1) */ src_r3 = _mm_slli_epi16(temp2, 1); //(x2<<1) src_r3 = _mm_sub_epi16(temp3, src_r3); // Matrix transpose /* * a0 b0 c0 d0 * a1 b1 c1 d1 * a2 b2 c2 d2 * a3 b3 c3 d3 */ temp0 = _mm_unpacklo_epi16(src_r0, src_r1); //a0 a1 b0 b1 c0 c1 d0 d1 temp2 = _mm_unpacklo_epi16(src_r2, src_r3); //a2 a3 b2 b3 c2 c3 d2 d3 temp1 = _mm_unpacklo_epi32(temp0, temp2); //a0 a1 a2 a3 b0 b1 b2 b3 temp3 = _mm_unpackhi_epi32(temp0, temp2); //c0 c1 c2 c3 d0 d1 d2 d3 src_r0 = _mm_unpacklo_epi64(temp1, zero_8x16b); //a0 a1 a2 a3 src_r1 = _mm_unpackhi_epi64(temp1, zero_8x16b); //b0 b1 b2 b3 src_r2 = _mm_unpacklo_epi64(temp3, zero_8x16b); //c0 c1 c2 c3 src_r3 = _mm_unpackhi_epi64(temp3, zero_8x16b); //d0 d1 d2 d3 /*----------------------------------------------------------*/ /* x0 = z0 + z3 */ temp0 = _mm_add_epi16(src_r0, src_r3); /* x1 = z1 + z2 */ temp1 = _mm_add_epi16(src_r1, src_r2); /* x2 = z1 - z2 */ temp2 = _mm_sub_epi16(src_r1, src_r2); /* x3 = z0 - z3 */ temp3 = _mm_sub_epi16(src_r0, src_r3); /* z0 = x0 + x1 */ src_r0 = _mm_add_epi16(temp0, temp1); /* z1 = (x3 << 1) + x2 */ src_r1 = _mm_slli_epi16(temp3, 1); //(x3<<1) src_r1 = _mm_add_epi16(src_r1, temp2); /* z2 = x0 - x1 */ src_r2 = _mm_sub_epi16(temp0, temp1); /* z3 = x3 - (x2 << 1) */ src_r3 = _mm_slli_epi16(temp2, 1); //(x2<<1) src_r3 = _mm_sub_epi16(temp3, src_r3); tmp_dc = _mm_extract_epi16(src_r0,0); //a0 *pi2_alt_dc_addr = tmp_dc; src_r0 = _mm_unpacklo_epi64(src_r0, src_r1); //a0 a1 a2 a3 b0 b1 b2 b3 src_r2 = _mm_unpacklo_epi64(src_r2, src_r3); //c0 c1 c2 c3 d0 d1 d2 d3 sign_reg0 = _mm_cmpgt_epi16(zero_8x16b,src_r0); sign_reg2 = _mm_cmpgt_epi16(zero_8x16b,src_r2); sign_reg0 = _mm_mullo_epi16(temp_2,sign_reg0); sign_reg2 = _mm_mullo_epi16(temp_2,sign_reg2); sign_reg0 = _mm_add_epi16(temp_1,sign_reg0); sign_reg2 = _mm_add_epi16(temp_1,sign_reg2); src_r0 = _mm_abs_epi16(src_r0); src_r2 = _mm_abs_epi16(src_r2); src_r1 = _mm_srli_si128(src_r0, 8); src_r0 = _mm_cvtepu16_epi32(src_r0); src_r1 = _mm_cvtepu16_epi32(src_r1); src_r3 = _mm_srli_si128(src_r2, 8); src_r2 = _mm_cvtepu16_epi32(src_r2); src_r3 = _mm_cvtepu16_epi32(src_r3); temp0 = _mm_cvtepu16_epi32(scalemat_r0_r1); scalemat_r0_r1 = _mm_srli_si128(scalemat_r0_r1, 8); temp2 = _mm_cvtepu16_epi32(scalemat_r2_r3); scalemat_r2_r3 = _mm_srli_si128(scalemat_r2_r3, 8); temp1 = _mm_cvtepu16_epi32(scalemat_r0_r1); temp3 = _mm_cvtepu16_epi32(scalemat_r2_r3); temp0 = _mm_mullo_epi32(temp0, src_r0); temp1 = _mm_mullo_epi32(temp1, src_r1); temp2 = _mm_mullo_epi32(temp2, src_r2); temp3 = _mm_mullo_epi32(temp3, src_r3); temp0 = _mm_add_epi32(temp0,rnd_fact); temp1 = _mm_add_epi32(temp1,rnd_fact); temp2 = _mm_add_epi32(temp2,rnd_fact); temp3 = _mm_add_epi32(temp3,rnd_fact); temp0 = _mm_srli_epi32(temp0,u4_qbits); temp1 = _mm_srli_epi32(temp1,u4_qbits); temp2 = _mm_srli_epi32(temp2,u4_qbits); temp3 = _mm_srli_epi32(temp3,u4_qbits); temp0 = _mm_packs_epi32 (temp0,temp1); temp2 = _mm_packs_epi32 (temp2,temp3); temp0 = _mm_sign_epi16(temp0, sign_reg0); temp2 = _mm_sign_epi16(temp2, sign_reg2); _mm_storeu_si128((__m128i *) (&pi2_out[0]), temp0); _mm_storeu_si128((__m128i *) (&pi2_out[8]), temp2); cmp0 = _mm_cmpeq_epi16(temp0, zero_8x16b); cmp1 = _mm_cmpeq_epi16(temp2, zero_8x16b); mask0 = _mm_movemask_epi8(cmp0); mask1 = _mm_movemask_epi8(cmp1); u4_zero_coeff = 0; if(mask0) { if(mask0 == 0xffff) u4_zero_coeff+=8; else { cmp0 = _mm_and_si128(temp_1, cmp0); sum0 = _mm_hadd_epi16(cmp0, zero_8x16b); sum1 = _mm_hadd_epi16(sum0, zero_8x16b); sum2 = _mm_hadd_epi16(sum1, zero_8x16b); u4_zero_coeff += _mm_cvtsi128_si32(sum2); } } if(mask1) { if(mask1 == 0xffff) u4_zero_coeff+=8; else { cmp1 = _mm_and_si128(temp_1, cmp1); sum0 = _mm_hadd_epi16(cmp1, zero_8x16b); sum1 = _mm_hadd_epi16(sum0, zero_8x16b); sum2 = _mm_hadd_epi16(sum1, zero_8x16b); u4_zero_coeff += _mm_cvtsi128_si32(sum2); } } /* Return total nonzero coefficients in the current sub block */ u4_nonzero_coeff = 16 - u4_zero_coeff; *pu1_nnz = u4_nonzero_coeff; } /** ******************************************************************************* * * @brief * This function performs forward transform and quantization on a 4*4 chroma block * * @par Description: * The function accepts source buffer and estimation buffer. From these, it * computes the residue. This is residue is then transformed and quantized. * The transform and quantization are in placed computed. They use the residue * buffer for this. * * @param[in] pu1_src * Pointer to source sub-block * * @param[in] pu1_pred * Pointer to prediction sub-block * * @param[in] pi2_out * Pointer to residual sub-block * * @param[in] src_strd * Source stride * * @param[in] pred_strd * Prediction stride * * @param[in] dst_strd * Destination stride * * @param[in] u4_qbits * QP_BITS_h264_4x4 + floor(QP/6) * * @param[in] pu2_threshold_matrix * Pointer to Forward Quant Threshold Matrix * * @param[in] pu2_scale_matrix * Pointer to Forward Quant Scale Matrix * * @param[in] u4_round_factor * Quantization Round factor * * @param[out] pu1_nnz * Total non-zero coefficients in the current sub-block * * @returns * * @remarks * None * ******************************************************************************* */ ATTRIBUTE_SSE42 void ih264_resi_trans_quant_chroma_4x4_sse42(UWORD8 *pu1_src,UWORD8 *pu1_pred,WORD16 *pi2_out, WORD32 src_strd,WORD32 pred_strd, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits,UWORD32 u4_round_factor, UWORD8 *pu1_nnz, WORD16 *pi2_alt_dc_addr) { WORD32 tmp_dc, u4_zero_coeff, u4_nonzero_coeff = 0; WORD32 mask0, mask1; __m128i cmp0, cmp1, sum0, sum1, sum2; __m128i rnd_fact = _mm_set1_epi32(u4_round_factor); __m128i temp_2 = _mm_set1_epi16(2); __m128i temp_1 = _mm_set1_epi16(1); __m128i src_r0, src_r1, src_r2, src_r3; __m128i pred_r0, pred_r1, pred_r2, pred_r3; __m128i temp0, temp1, temp2, temp3; __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero __m128i sign_reg0, sign_reg2; __m128i scalemat_r0_r1, scalemat_r2_r3; __m128i chroma_mask = _mm_set1_epi16 (0xFF); UNUSED (pu2_threshold_matrix); scalemat_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_scale_matrix)); //b00 b01 b02 b03 b10 b11 b12 b13 -- the scaling matrix 0th,1st row scalemat_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_scale_matrix + 8)); //b20 b21 b22 b23 b30 b31 b32 b33 -- the scaling matrix 2nd,3rd row src_r0 = _mm_loadl_epi64((__m128i *) (&pu1_src[0])); //a00 a01 a02 a03 0 0 0 0 0 0 0 0 -- all 8 bits src_r1 = _mm_loadl_epi64((__m128i *) (&pu1_src[src_strd])); //a10 a11 a12 a13 0 0 0 0 0 0 0 0 -- all 8 bits src_r2 = _mm_loadl_epi64((__m128i *) (&pu1_src[2 * src_strd])); //a20 a21 a22 a23 0 0 0 0 0 0 0 0 -- all 8 bits src_r3 = _mm_loadl_epi64((__m128i *) (&pu1_src[3 * src_strd])); //a30 a31 a32 a33 0 0 0 0 0 0 0 0 -- all 8 bits src_r0 = _mm_and_si128(src_r0, chroma_mask); src_r1 = _mm_and_si128(src_r1, chroma_mask); src_r2 = _mm_and_si128(src_r2, chroma_mask); src_r3 = _mm_and_si128(src_r3, chroma_mask); // src_r0 = _mm_cvtepu8_epi16(src_r0); // src_r1 = _mm_cvtepu8_epi16(src_r1); // src_r2 = _mm_cvtepu8_epi16(src_r2); // src_r3 = _mm_cvtepu8_epi16(src_r3); pred_r0 = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits pred_r1 = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits pred_r2 = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits pred_r3 = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits pred_r0 = _mm_and_si128(pred_r0, chroma_mask); pred_r1 = _mm_and_si128(pred_r1, chroma_mask); pred_r2 = _mm_and_si128(pred_r2, chroma_mask); pred_r3 = _mm_and_si128(pred_r3, chroma_mask); // pred_r0 = _mm_cvtepu8_epi16(pred_r0); //p00 p01 p02 p03 -- all 16 bits // pred_r1 = _mm_cvtepu8_epi16(pred_r1); //p10 p11 p12 p13 -- all 16 bits // pred_r2 = _mm_cvtepu8_epi16(pred_r2); //p20 p21 p22 p23 -- all 16 bits // pred_r3 = _mm_cvtepu8_epi16(pred_r3); //p30 p31 p32 p33 -- all 16 bits src_r0 = _mm_sub_epi16(src_r0, pred_r0); src_r1 = _mm_sub_epi16(src_r1, pred_r1); src_r2 = _mm_sub_epi16(src_r2, pred_r2); src_r3 = _mm_sub_epi16(src_r3, pred_r3); /* Perform Forward transform */ /*-------------------------------------------------------------*/ /* DCT [ Horizontal transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 a1 a2 a3 * b0 b1 b2 b3 * c0 c1 c2 c3 * d0 d1 d2 d3 */ temp0 = _mm_unpacklo_epi16(src_r0, src_r1); //a0 b0 a1 b1 a2 b2 a3 b3 temp2 = _mm_unpacklo_epi16(src_r2, src_r3); //c0 d0 c1 d1 c2 d2 c3 d3 temp1 = _mm_unpacklo_epi32(temp0, temp2); //a0 b0 c0 d0 a1 b1 c1 d1 temp3 = _mm_unpackhi_epi32(temp0, temp2); //a2 b2 c2 d2 a3 b3 c3 d3 src_r0 = _mm_unpacklo_epi64(temp1, zero_8x16b); //a0 b0 c0 d0 src_r1 = _mm_unpackhi_epi64(temp1, zero_8x16b); //a1 b1 c1 d1 src_r2 = _mm_unpacklo_epi64(temp3, zero_8x16b); //a2 b2 c2 d2 src_r3 = _mm_unpackhi_epi64(temp3, zero_8x16b); //a3 b3 c3 d3 /*----------------------------------------------------------*/ /* x0 = z0 + z3 */ temp0 = _mm_add_epi16(src_r0, src_r3); /* x1 = z1 + z2 */ temp1 = _mm_add_epi16(src_r1, src_r2); /* x2 = z1 - z2 */ temp2 = _mm_sub_epi16(src_r1, src_r2); /* x3 = z0 - z3 */ temp3 = _mm_sub_epi16(src_r0, src_r3); /* z0 = x0 + x1 */ src_r0 = _mm_add_epi16(temp0, temp1); /* z1 = (x3 << 1) + x2 */ src_r1 = _mm_slli_epi16(temp3, 1); //(x3<<1) src_r1 = _mm_add_epi16(src_r1, temp2); /* z2 = x0 - x1 */ src_r2 = _mm_sub_epi16(temp0, temp1); /* z3 = x3 - (x2 << 1) */ src_r3 = _mm_slli_epi16(temp2, 1); //(x2<<1) src_r3 = _mm_sub_epi16(temp3, src_r3); // Matrix transpose /* * a0 b0 c0 d0 * a1 b1 c1 d1 * a2 b2 c2 d2 * a3 b3 c3 d3 */ temp0 = _mm_unpacklo_epi16(src_r0, src_r1); //a0 a1 b0 b1 c0 c1 d0 d1 temp2 = _mm_unpacklo_epi16(src_r2, src_r3); //a2 a3 b2 b3 c2 c3 d2 d3 temp1 = _mm_unpacklo_epi32(temp0, temp2); //a0 a1 a2 a3 b0 b1 b2 b3 temp3 = _mm_unpackhi_epi32(temp0, temp2); //c0 c1 c2 c3 d0 d1 d2 d3 src_r0 = _mm_unpacklo_epi64(temp1, zero_8x16b); //a0 a1 a2 a3 src_r1 = _mm_unpackhi_epi64(temp1, zero_8x16b); //b0 b1 b2 b3 src_r2 = _mm_unpacklo_epi64(temp3, zero_8x16b); //c0 c1 c2 c3 src_r3 = _mm_unpackhi_epi64(temp3, zero_8x16b); //d0 d1 d2 d3 /*----------------------------------------------------------*/ /* x0 = z0 + z3 */ temp0 = _mm_add_epi16(src_r0, src_r3); /* x1 = z1 + z2 */ temp1 = _mm_add_epi16(src_r1, src_r2); /* x2 = z1 - z2 */ temp2 = _mm_sub_epi16(src_r1, src_r2); /* x3 = z0 - z3 */ temp3 = _mm_sub_epi16(src_r0, src_r3); /* z0 = x0 + x1 */ src_r0 = _mm_add_epi16(temp0, temp1); /* z1 = (x3 << 1) + x2 */ src_r1 = _mm_slli_epi16(temp3, 1); //(x3<<1) src_r1 = _mm_add_epi16(src_r1, temp2); /* z2 = x0 - x1 */ src_r2 = _mm_sub_epi16(temp0, temp1); /* z3 = x3 - (x2 << 1) */ src_r3 = _mm_slli_epi16(temp2, 1); //(x2<<1) src_r3 = _mm_sub_epi16(temp3, src_r3); tmp_dc = _mm_extract_epi16(src_r0,0); //a0 *pi2_alt_dc_addr = tmp_dc; src_r0 = _mm_unpacklo_epi64(src_r0, src_r1); //a0 a1 a2 a3 b0 b1 b2 b3 src_r2 = _mm_unpacklo_epi64(src_r2, src_r3); //c0 c1 c2 c3 d0 d1 d2 d3 sign_reg0 = _mm_cmpgt_epi16(zero_8x16b,src_r0); sign_reg2 = _mm_cmpgt_epi16(zero_8x16b,src_r2); sign_reg0 = _mm_mullo_epi16(temp_2,sign_reg0); sign_reg2 = _mm_mullo_epi16(temp_2,sign_reg2); sign_reg0 = _mm_add_epi16(temp_1,sign_reg0); sign_reg2 = _mm_add_epi16(temp_1,sign_reg2); src_r0 = _mm_abs_epi16(src_r0); src_r2 = _mm_abs_epi16(src_r2); src_r1 = _mm_srli_si128(src_r0, 8); src_r0 = _mm_cvtepu16_epi32(src_r0); src_r1 = _mm_cvtepu16_epi32(src_r1); src_r3 = _mm_srli_si128(src_r2, 8); src_r2 = _mm_cvtepu16_epi32(src_r2); src_r3 = _mm_cvtepu16_epi32(src_r3); temp0 = _mm_cvtepu16_epi32(scalemat_r0_r1); scalemat_r0_r1 = _mm_srli_si128(scalemat_r0_r1, 8); temp2 = _mm_cvtepu16_epi32(scalemat_r2_r3); scalemat_r2_r3 = _mm_srli_si128(scalemat_r2_r3, 8); temp1 = _mm_cvtepu16_epi32(scalemat_r0_r1); temp3 = _mm_cvtepu16_epi32(scalemat_r2_r3); temp0 = _mm_mullo_epi32(temp0, src_r0); temp1 = _mm_mullo_epi32(temp1, src_r1); temp2 = _mm_mullo_epi32(temp2, src_r2); temp3 = _mm_mullo_epi32(temp3, src_r3); temp0 = _mm_add_epi32(temp0,rnd_fact); temp1 = _mm_add_epi32(temp1,rnd_fact); temp2 = _mm_add_epi32(temp2,rnd_fact); temp3 = _mm_add_epi32(temp3,rnd_fact); temp0 = _mm_srli_epi32(temp0,u4_qbits); temp1 = _mm_srli_epi32(temp1,u4_qbits); temp2 = _mm_srli_epi32(temp2,u4_qbits); temp3 = _mm_srli_epi32(temp3,u4_qbits); temp0 = _mm_packs_epi32 (temp0,temp1); temp2 = _mm_packs_epi32 (temp2,temp3); temp0 = _mm_sign_epi16(temp0, sign_reg0); temp2 = _mm_sign_epi16(temp2, sign_reg2); //temp0 = _mm_insert_epi16(temp0, tmp_dc, 0); _mm_storeu_si128((__m128i *) (&pi2_out[0]), temp0); _mm_storeu_si128((__m128i *) (&pi2_out[8]), temp2); cmp0 = _mm_cmpeq_epi16(temp0, zero_8x16b); cmp1 = _mm_cmpeq_epi16(temp2, zero_8x16b); mask0 = _mm_movemask_epi8(cmp0); mask1 = _mm_movemask_epi8(cmp1); u4_zero_coeff = 0; if(mask0) { if(mask0 == 0xffff) u4_zero_coeff+=8; else { cmp0 = _mm_and_si128(temp_1, cmp0); sum0 = _mm_hadd_epi16(cmp0, zero_8x16b); sum1 = _mm_hadd_epi16(sum0, zero_8x16b); sum2 = _mm_hadd_epi16(sum1, zero_8x16b); u4_zero_coeff += _mm_cvtsi128_si32(sum2); } } if(mask1) { if(mask1 == 0xffff) u4_zero_coeff+=8; else { cmp1 = _mm_and_si128(temp_1, cmp1); sum0 = _mm_hadd_epi16(cmp1, zero_8x16b); sum1 = _mm_hadd_epi16(sum0, zero_8x16b); sum2 = _mm_hadd_epi16(sum1, zero_8x16b); u4_zero_coeff += _mm_cvtsi128_si32(sum2); } } /* Return total nonzero coefficients in the current sub block */ u4_nonzero_coeff = 16 - u4_zero_coeff; *pu1_nnz = u4_nonzero_coeff; } /** ******************************************************************************* * * @brief * This function performs forward hadamard transform and quantization on a 4*4 block * * @par Description: * The function accepts source buffer and estimation buffer. From these, it * computes the residue. This is residue is then transformed and quantized. * The transform and quantization are in placed computed. They use the residue * buffer for this. * * @param[in] pu1_src * Pointer to source sub-block * * @param[in] pu1_pred * Pointer to prediction sub-block * * @param[in] pi2_out * Pointer to residual sub-block * * @param[in] src_strd * Source stride * * @param[in] pred_strd * Prediction stride * * @param[in] dst_strd * Destination stride * * @param[in] u4_qbits * QP_BITS_h264_4x4 + floor(QP/6) * * @param[in] pu2_threshold_matrix * Pointer to Forward Quant Threshold Matrix * * @param[in] pu2_scale_matrix * Pointer to Forward Quant Scale Matrix * * @param[in] u4_round_factor * Quantization Round factor * * @param[out] pu1_nnz * Total non-zero coefficients in the current sub-block * * @returns * * @remarks * None * */ ATTRIBUTE_SSE42 void ih264_hadamard_quant_4x4_sse42(WORD16 *pi2_src, WORD16 *pi2_dst, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor,UWORD8 *pu1_nnz ) { WORD32 u4_zero_coeff,u4_nonzero_coeff=0; __m128i cmp0, cmp1, sum0, sum1, sum2; WORD32 mask0, mask1; __m128i src_r0_r1, src_r2_r3, sign_reg; __m128i src_r0, src_r1, src_r2, src_r3; __m128i zero_8x16b = _mm_setzero_si128(); __m128i temp0, temp1, temp2, temp3; __m128i sign_reg0, sign_reg1, sign_reg2, sign_reg3; __m128i temp_1 = _mm_set1_epi16(1); __m128i rnd_fact = _mm_set1_epi32(u4_round_factor); __m128i scale_val = _mm_set1_epi32(pu2_scale_matrix[0]); UNUSED (pu2_threshold_matrix); src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row sign_reg = _mm_cmpgt_epi16(zero_8x16b, src_r0_r1); src_r0 = _mm_unpacklo_epi16(src_r0_r1, sign_reg); //a0 a1 a2 a3 src_r1 = _mm_unpackhi_epi16(src_r0_r1, sign_reg); //b0 b1 b2 b3 sign_reg = _mm_cmpgt_epi16(zero_8x16b, src_r2_r3); src_r2 = _mm_unpacklo_epi16(src_r2_r3, sign_reg); //c0 c1 c2 c3 src_r3 = _mm_unpackhi_epi16(src_r2_r3, sign_reg); //d0 d1 d2 d3 /* Perform Inverse transform */ /*-------------------------------------------------------------*/ /* Forward DC transform [ Horizontal transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 a1 a2 a3 * b0 b1 b2 b3 * c0 c1 c2 c3 * d0 d1 d2 d3 */ temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 b0 a1 b1 temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //c0 d0 c1 d1 temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //a2 b2 a3 b3 temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 d2 c3 d3 src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 b0 c0 d0 src_r1 = _mm_unpackhi_epi64(temp0, temp2); //a1 b1 c1 d1 src_r2 = _mm_unpacklo_epi64(temp1, temp3); //a2 b2 c2 d2 src_r3 = _mm_unpackhi_epi64(temp1, temp3); //a3 b3 c3 d3 temp0 = _mm_add_epi32(src_r0, src_r3); temp1 = _mm_add_epi32(src_r1, src_r2); temp2 = _mm_sub_epi32(src_r1, src_r2); temp3 = _mm_sub_epi32(src_r0, src_r3); src_r0 = _mm_add_epi32(temp0, temp1); src_r1 = _mm_add_epi32(temp2, temp3); src_r2 = _mm_sub_epi32(temp0, temp1); src_r3 = _mm_sub_epi32(temp3, temp2); /*-------------------------------------------------------------*/ /* Forward DC transform [ Vertical transformation ] */ /*-------------------------------------------------------------*/ // Matrix transpose /* * a0 b0 c0 d0 * a1 b1 c1 d1 * a2 b2 c2 d2 * a3 b3 c3 d3 */ temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 a1 b0 b1 temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //a2 a3 b2 b3 temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //c0 c1 d0 d1 temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 c3 d2 d3 src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 a1 a2 a3 src_r1 = _mm_unpackhi_epi64(temp0, temp2); //b0 b1 b2 b3 src_r2 = _mm_unpacklo_epi64(temp1, temp3); //c0 c1 c2 c3 src_r3 = _mm_unpackhi_epi64(temp1, temp3); //d0 d1 d2 d3 temp0 = _mm_add_epi32(src_r0, src_r3); temp1 = _mm_add_epi32(src_r1, src_r2); temp2 = _mm_sub_epi32(src_r1, src_r2); temp3 = _mm_sub_epi32(src_r0, src_r3); src_r0 = _mm_add_epi32(temp0, temp1); src_r1 = _mm_add_epi32(temp2, temp3); src_r2 = _mm_sub_epi32(temp0, temp1); src_r3 = _mm_sub_epi32(temp3, temp2); src_r0 = _mm_srai_epi32(src_r0, 1); src_r1 = _mm_srai_epi32(src_r1, 1); src_r2 = _mm_srai_epi32(src_r2, 1); src_r3 = _mm_srai_epi32(src_r3, 1); // Quantization sign_reg0 = _mm_cmpgt_epi32(zero_8x16b, src_r0); //Find sign of each value for later restoration sign_reg1 = _mm_cmpgt_epi32(zero_8x16b, src_r1); sign_reg2 = _mm_cmpgt_epi32(zero_8x16b, src_r2); sign_reg3 = _mm_cmpgt_epi32(zero_8x16b, src_r3); sign_reg0 = _mm_packs_epi32(sign_reg0, sign_reg1); //Sign = -1 or 0 depending on <0 or >0 respectively sign_reg2 = _mm_packs_epi32(sign_reg2, sign_reg3); sign_reg0 = _mm_slli_epi16(sign_reg0, 1); //Sign = -2 or 0 depending on <0 or >0 respectively sign_reg2 = _mm_slli_epi16(sign_reg2, 1); sign_reg0 = _mm_add_epi16(temp_1,sign_reg0); //Sign = -1 or 1 depending on <0 or >0 respectively sign_reg2 = _mm_add_epi16(temp_1,sign_reg2); src_r0 = _mm_abs_epi32(src_r0); //Absolute values src_r1 = _mm_abs_epi32(src_r1); src_r2 = _mm_abs_epi32(src_r2); src_r3 = _mm_abs_epi32(src_r3); temp0 = _mm_mullo_epi32(scale_val, src_r0); //multiply by pu2_scale_matrix[0] temp1 = _mm_mullo_epi32(scale_val, src_r1); temp2 = _mm_mullo_epi32(scale_val, src_r2); temp3 = _mm_mullo_epi32(scale_val, src_r3); temp0 = _mm_add_epi32(temp0,rnd_fact); //Add round factor temp1 = _mm_add_epi32(temp1,rnd_fact); temp2 = _mm_add_epi32(temp2,rnd_fact); temp3 = _mm_add_epi32(temp3,rnd_fact); temp0 = _mm_srli_epi32(temp0,u4_qbits); //RIght shift by qbits, unsigned variable, so shift right immediate works temp1 = _mm_srli_epi32(temp1,u4_qbits); temp2 = _mm_srli_epi32(temp2,u4_qbits); temp3 = _mm_srli_epi32(temp3,u4_qbits); temp0 = _mm_packs_epi32 (temp0,temp1); //Final values are 16-bits only. temp2 = _mm_packs_epi32 (temp2,temp3); temp0 = _mm_sign_epi16(temp0, sign_reg0); //Sign restoration temp2 = _mm_sign_epi16(temp2, sign_reg2); _mm_storeu_si128((__m128i *) (&pi2_dst[0]), temp0); _mm_storeu_si128((__m128i *) (&pi2_dst[8]), temp2); cmp0 = _mm_cmpeq_epi16(temp0, zero_8x16b); cmp1 = _mm_cmpeq_epi16(temp2, zero_8x16b); mask0 = _mm_movemask_epi8(cmp0); mask1 = _mm_movemask_epi8(cmp1); u4_zero_coeff = 0; if(mask0) { if(mask0 == 0xffff) u4_zero_coeff+=8; else { cmp0 = _mm_and_si128(temp_1, cmp0); sum0 = _mm_hadd_epi16(cmp0, zero_8x16b); sum1 = _mm_hadd_epi16(sum0, zero_8x16b); sum2 = _mm_hadd_epi16(sum1, zero_8x16b); u4_zero_coeff += _mm_cvtsi128_si32(sum2); } } if(mask1) { if(mask1 == 0xffff) u4_zero_coeff+=8; else { cmp1 = _mm_and_si128(temp_1, cmp1); sum0 = _mm_hadd_epi16(cmp1, zero_8x16b); sum1 = _mm_hadd_epi16(sum0, zero_8x16b); sum2 = _mm_hadd_epi16(sum1, zero_8x16b); u4_zero_coeff += _mm_cvtsi128_si32(sum2); } } /* Return total nonzero coefficients in the current sub block */ u4_nonzero_coeff = 16 - u4_zero_coeff; pu1_nnz[0] = u4_nonzero_coeff; } /** ******************************************************************************* * * @brief * This function performs forward hadamard transform and quantization on a 2*2 block * for both U and V planes * * @par Description: * The function accepts source buffer and estimation buffer. From these, it * computes the residue. This is residue is then transformed and quantized. * The transform and quantization are in placed computed. They use the residue * buffer for this. * * @param[in] pu1_src * Pointer to source sub-block * * @param[in] pu1_pred * Pointer to prediction sub-block * * @param[in] pi2_out * Pointer to residual sub-block * * @param[in] src_strd * Source stride * * @param[in] pred_strd * Prediction stride * * @param[in] dst_strd * Destination stride * * @param[in] u4_qbits * QP_BITS_h264_4x4 + floor(QP/6) * * @param[in] pu2_threshold_matrix * Pointer to Forward Quant Threshold Matrix * * @param[in] pu2_scale_matrix * Pointer to Forward Quant Scale Matrix * * @param[in] u4_round_factor * Quantization Round factor * * @param[out] pu1_nnz * Total non-zero coefficients in the current sub-block * * @returns * * @remarks * NNZ for dc is populated at 0 and 5th position of pu1_nnz * */ ATTRIBUTE_SSE42 void ih264_hadamard_quant_2x2_uv_sse42(WORD16 *pi2_src, WORD16 *pi2_dst, const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, UWORD32 u4_round_factor,UWORD8 *pu1_nnz) { WORD32 val, nonzero_coeff_0=0, nonzero_coeff_1=0; __m128i cmp, cmp0, cmp1; __m128i sum0, sum1; WORD32 mask, mask0, mask1; __m128i src, plane_0, plane_1, temp0, temp1, sign_reg; __m128i zero_8x16b = _mm_setzero_si128(); __m128i scale_val = _mm_set1_epi32(pu2_scale_matrix[0]); __m128i sign_reg0, sign_reg1; __m128i temp_1 = _mm_set1_epi16(1); __m128i rnd_fact = _mm_set1_epi32(u4_round_factor); UNUSED (pu2_threshold_matrix); src = _mm_loadu_si128((__m128i *)pi2_src); //a0 a1 a2 a3 b0 b1 b2 b3 sign_reg = _mm_cmpgt_epi16(zero_8x16b, src); plane_0 = _mm_unpacklo_epi16(src, sign_reg); //a0 a1 a2 a3 -- 32 bits plane_1 = _mm_unpackhi_epi16(src, sign_reg); //b0 b1 b2 b3 -- 32 bits temp0 = _mm_hadd_epi32(plane_0, plane_1); //a0+a1 a2+a3 b0+b1 b2+b3 temp1 = _mm_hsub_epi32(plane_0, plane_1); //a0-a1 a2-a3 b0-b1 b2-b3 plane_0 = _mm_hadd_epi32(temp0, temp1); //a0+a1+a2+a3 b0+b1+b2+b3 a0-a1+a2-a3 b0-b1+b2-b3 plane_1 = _mm_hsub_epi32(temp0, temp1); //a0+a1-a2-a3 b0+b1-b2-b3 a0-a1-a2+a3 b0-b1-b2+b3 temp0 = _mm_unpacklo_epi32(plane_0, plane_1); //a0+a1+a2+a3 a0+a1-a2-a3 b0+b1+b2+b3 b0+b1-b2-b3 temp1 = _mm_unpackhi_epi32(plane_0, plane_1); //a0-a1+a2-a3 a0-a1-a2+a3 b0-b1+b2-b3 b0-b1-b2+b3 plane_0 = _mm_unpacklo_epi64(temp0, temp1); //a0+a1+a2+a3 a0+a1-a2-a3 a0-a1+a2-a3 a0-a1-a2+a3 plane_1 = _mm_unpackhi_epi64(temp0, temp1); //b0+b1+b2+b3 b0+b1-b2-b3 b0-b1+b2-b3 b0-b1-b2+b3 plane_0 = _mm_shuffle_epi32(plane_0, 0xd8); //a0+a1+a2+a3 a0-a1+a2-a3 a0+a1-a2-a3 a0-a1-a2+a3 plane_1 = _mm_shuffle_epi32(plane_1, 0xd8); //b0+b1+b2+b3 b0-b1+b2-b3 b0+b1-b2-b3 b0-b1-b2+b3 // Quantization sign_reg0 = _mm_cmpgt_epi32(zero_8x16b, plane_0); //Find sign of each value for later restoration sign_reg1 = _mm_cmpgt_epi32(zero_8x16b, plane_1); sign_reg0 = _mm_packs_epi32(sign_reg0, sign_reg1); //Sign = -1 or 0 depending on <0 or >0 respectively sign_reg0 = _mm_slli_epi16(sign_reg0, 1); //Sign = -2 or 0 depending on <0 or >0 respectively sign_reg0 = _mm_add_epi16(temp_1,sign_reg0); //Sign = -1 or 1 depending on <0 or >0 respectively plane_0 = _mm_abs_epi32(plane_0); //Absolute values plane_1 = _mm_abs_epi32(plane_1); temp0 = _mm_mullo_epi32(scale_val, plane_0); //multiply by pu2_scale_matrix[0] temp1 = _mm_mullo_epi32(scale_val, plane_1); //multiply by pu2_scale_matrix[0] temp0 = _mm_add_epi32(temp0,rnd_fact); //Add round factor temp1 = _mm_add_epi32(temp1,rnd_fact); temp0 = _mm_srli_epi32(temp0,u4_qbits); //RIght shift by qbits, unsigned variable, so shift right immediate works temp1 = _mm_srli_epi32(temp1,u4_qbits); temp0 = _mm_packs_epi32 (temp0,temp1); //Final values are 16-bits only. temp0 = _mm_sign_epi16(temp0, sign_reg0); //Sign restoration _mm_storeu_si128((__m128i *) (&pi2_dst[0]), temp0); cmp = _mm_cmpeq_epi16(temp0, zero_8x16b); mask = _mm_movemask_epi8(cmp); mask0 = mask & 0xff; mask1 = mask>>8; if(mask0) { if(mask0 == 0xff) nonzero_coeff_0 += 4; else { cmp0 = _mm_and_si128(temp_1, cmp); sum0 = _mm_hadd_epi16(cmp0, zero_8x16b); sum1 = _mm_hadd_epi16(sum0, zero_8x16b); val = _mm_cvtsi128_si32(sum1); val = val & 0xffff; nonzero_coeff_0 += val; } } if(mask1) { if(mask1 == 0xff) nonzero_coeff_1 += 4; else { cmp1 = _mm_srli_si128(cmp, 8); cmp1 = _mm_and_si128(temp_1, cmp1); sum0 = _mm_hadd_epi16(cmp1, zero_8x16b); sum1 = _mm_hadd_epi16(sum0, zero_8x16b); nonzero_coeff_1 += _mm_cvtsi128_si32(sum1); } } pu1_nnz[0] = 4 - nonzero_coeff_0; pu1_nnz[1] = 4 - nonzero_coeff_1; } ================================================ FILE: dependencies/ih264d/common/x86/ih264_weighted_pred_sse42.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264_weighted_pred_intr_sse42.c */ /* */ /* Description : Contains function definitions for weighted */ /* prediction functions in x86 sse4 intrinsics */ /* */ /* List of Functions : ih264_default_weighted_pred_luma_sse42() */ /* ih264_default_weighted_pred_chroma_sse42() */ /* ih264_weighted_pred_luma_sse42() */ /* ih264_weighted_pred_chroma_sse42() */ /* ih264_weighted_bipred_luma_sse42() */ /* ih264_weighted_bipred_chroma_sse42() */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes */ /* 30 01 2015 Kaushik Initial version */ /* Senthoor */ /* */ /*****************************************************************************/ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ #include <immintrin.h> #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_weighted_pred.h" #ifdef __GNUC__ #define ATTRIBUTE_SSE42 __attribute__((target("sse4.2"))) #else #define ATTRIBUTE_SSE42 #endif /*****************************************************************************/ /* Function definitions . */ /*****************************************************************************/ /*****************************************************************************/ /* */ /* Function Name : ih264_default_weighted_pred_luma_sse42 */ /* */ /* Description : This function performs the default weighted prediction */ /* as described in sec 8.4.2.3.1 titled "Default weighted */ /* sample prediction process" for luma. The function gets */ /* two ht x wd blocks, calculates their rounded-average and */ /* stores it in the destination block. (ht,wd) can be */ /* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : pu1_src1 - Pointer to source 1 */ /* pu1_src2 - Pointer to source 2 */ /* pu1_dst - Pointer to destination */ /* src_strd1 - stride for source 1 */ /* src_strd1 - stride for source 2 */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 04 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSE42 void ih264_default_weighted_pred_luma_sse42(UWORD8 *pu1_src1, UWORD8 *pu1_src2, UWORD8 *pu1_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 ht, WORD32 wd) { __m128i y0_0_16x8b, y0_1_16x8b, y0_2_16x8b, y0_3_16x8b; __m128i y1_0_16x8b, y1_1_16x8b, y1_2_16x8b, y1_3_16x8b; if(wd == 4) { do { y0_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); y0_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); y0_2_16x8b = _mm_loadl_epi64( (__m128i *)(pu1_src1 + (src_strd1 << 1))); y0_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1 * 3)); y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); y1_2_16x8b = _mm_loadl_epi64( (__m128i *)(pu1_src2 + (src_strd2 << 1))); y1_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2 * 3)); y0_0_16x8b = _mm_avg_epu8(y0_0_16x8b, y1_0_16x8b); y0_1_16x8b = _mm_avg_epu8(y0_1_16x8b, y1_1_16x8b); y0_2_16x8b = _mm_avg_epu8(y0_2_16x8b, y1_2_16x8b); y0_3_16x8b = _mm_avg_epu8(y0_3_16x8b, y1_3_16x8b); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(y0_0_16x8b); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(y0_1_16x8b); *((WORD32 *)(pu1_dst + (dst_strd << 1))) = _mm_cvtsi128_si32(y0_2_16x8b); *((WORD32 *)(pu1_dst + dst_strd * 3)) = _mm_cvtsi128_si32(y0_3_16x8b); ht -= 4; pu1_src1 += src_strd1 << 2; pu1_src2 += src_strd2 << 2; pu1_dst += dst_strd << 2; } while(ht > 0); } else if(wd == 8) { do { y0_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); y0_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); y0_2_16x8b = _mm_loadl_epi64( (__m128i *)(pu1_src1 + (src_strd1 << 1))); y0_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1 * 3)); y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); y1_2_16x8b = _mm_loadl_epi64( (__m128i *)(pu1_src2 + (src_strd2 << 1))); y1_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2 * 3)); y0_0_16x8b = _mm_avg_epu8(y0_0_16x8b, y1_0_16x8b); y0_1_16x8b = _mm_avg_epu8(y0_1_16x8b, y1_1_16x8b); y0_2_16x8b = _mm_avg_epu8(y0_2_16x8b, y1_2_16x8b); y0_3_16x8b = _mm_avg_epu8(y0_3_16x8b, y1_3_16x8b); _mm_storel_epi64((__m128i *)pu1_dst, y0_0_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y0_1_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + (dst_strd << 1)), y0_2_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd * 3), y0_3_16x8b); ht -= 4; pu1_src1 += src_strd1 << 2; pu1_src2 += src_strd2 << 2; pu1_dst += dst_strd << 2; } while(ht > 0); } else // wd == 16 { __m128i y0_4_16x8b, y0_5_16x8b, y0_6_16x8b, y0_7_16x8b; __m128i y1_4_16x8b, y1_5_16x8b, y1_6_16x8b, y1_7_16x8b; do { y0_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src1); y0_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1)); y0_2_16x8b = _mm_loadu_si128( (__m128i *)(pu1_src1 + (src_strd1 << 1))); y0_3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1 * 3)); y0_4_16x8b = _mm_loadu_si128( (__m128i *)(pu1_src1 + (src_strd1 << 2))); y0_5_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1 * 5)); y0_6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1 * 6)); y0_7_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1 * 7)); y1_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src2); y1_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2)); y1_2_16x8b = _mm_loadu_si128( (__m128i *)(pu1_src2 + (src_strd2 << 1))); y1_3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2 * 3)); y1_4_16x8b = _mm_loadu_si128( (__m128i *)(pu1_src2 + (src_strd2 << 2))); y1_5_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2 * 5)); y1_6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2 * 6)); y1_7_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2 * 7)); y0_0_16x8b = _mm_avg_epu8(y0_0_16x8b, y1_0_16x8b); y0_1_16x8b = _mm_avg_epu8(y0_1_16x8b, y1_1_16x8b); y0_2_16x8b = _mm_avg_epu8(y0_2_16x8b, y1_2_16x8b); y0_3_16x8b = _mm_avg_epu8(y0_3_16x8b, y1_3_16x8b); y0_4_16x8b = _mm_avg_epu8(y0_4_16x8b, y1_4_16x8b); y0_5_16x8b = _mm_avg_epu8(y0_5_16x8b, y1_5_16x8b); y0_6_16x8b = _mm_avg_epu8(y0_6_16x8b, y1_6_16x8b); y0_7_16x8b = _mm_avg_epu8(y0_7_16x8b, y1_7_16x8b); _mm_storeu_si128((__m128i *)pu1_dst, y0_0_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y0_1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + (dst_strd << 1)), y0_2_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 3), y0_3_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + (dst_strd << 2)), y0_4_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 5), y0_5_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 6), y0_6_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 7), y0_7_16x8b); ht -= 8; pu1_src1 += src_strd1 << 3; pu1_src2 += src_strd2 << 3; pu1_dst += dst_strd << 3; } while(ht > 0); } } /*****************************************************************************/ /* */ /* Function Name : ih264_default_weighted_pred_chroma_sse42 */ /* */ /* Description : This function performs the default weighted prediction */ /* as described in sec 8.4.2.3.1 titled "Default weighted */ /* sample prediction process" for chroma. The function gets */ /* two ht x wd blocks, calculates their rounded-average and */ /* stores it in the destination block. (ht,wd) can be */ /* (2,2), (4,2) , (2,4), (4,4), (8,4), (4,8) or (8,8). */ /* */ /* Inputs : pu1_src1 - Pointer to source 1 */ /* pu1_src2 - Pointer to source 2 */ /* pu1_dst - Pointer to destination */ /* src_strd1 - stride for source 1 */ /* src_strd1 - stride for source 2 */ /* dst_strd - stride for destination */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 04 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSE42 void ih264_default_weighted_pred_chroma_sse42(UWORD8 *pu1_src1, UWORD8 *pu1_src2, UWORD8 *pu1_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 ht, WORD32 wd) { __m128i uv0_0_16x8b, uv0_1_16x8b; __m128i uv1_0_16x8b, uv1_1_16x8b; if(wd == 2) { do { uv0_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); uv0_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); uv1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); uv1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); uv0_0_16x8b = _mm_avg_epu8(uv0_0_16x8b, uv1_0_16x8b); uv0_1_16x8b = _mm_avg_epu8(uv0_1_16x8b, uv1_1_16x8b); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(uv0_0_16x8b); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(uv0_1_16x8b); ht -= 2; pu1_src1 += src_strd1 << 1; pu1_src2 += src_strd2 << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else if(wd == 4) { do { uv0_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); uv0_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); uv1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); uv1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); uv0_0_16x8b = _mm_avg_epu8(uv0_0_16x8b, uv1_0_16x8b); uv0_1_16x8b = _mm_avg_epu8(uv0_1_16x8b, uv1_1_16x8b); _mm_storel_epi64((__m128i *)pu1_dst, uv0_0_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), uv0_1_16x8b); ht -= 2; pu1_src1 += src_strd1 << 1; pu1_src2 += src_strd2 << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else // wd == 8 { __m128i uv0_2_16x8b, uv0_3_16x8b; __m128i uv1_2_16x8b, uv1_3_16x8b; do { uv0_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src1); uv0_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1)); uv0_2_16x8b = _mm_loadu_si128( (__m128i *)(pu1_src1 + (src_strd1 << 1))); uv0_3_16x8b = _mm_loadu_si128( (__m128i *)(pu1_src1 + src_strd1 * 3)); uv1_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src2); uv1_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2)); uv1_2_16x8b = _mm_loadu_si128( (__m128i *)(pu1_src2 + (src_strd2 << 1))); uv1_3_16x8b = _mm_loadu_si128( (__m128i *)(pu1_src2 + src_strd2 * 3)); uv0_0_16x8b = _mm_avg_epu8(uv0_0_16x8b, uv1_0_16x8b); uv0_1_16x8b = _mm_avg_epu8(uv0_1_16x8b, uv1_1_16x8b); uv0_2_16x8b = _mm_avg_epu8(uv0_2_16x8b, uv1_2_16x8b); uv0_3_16x8b = _mm_avg_epu8(uv0_3_16x8b, uv1_3_16x8b); _mm_storeu_si128((__m128i *)pu1_dst, uv0_0_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), uv0_1_16x8b); _mm_storeu_si128( (__m128i *)(pu1_dst + (dst_strd << 1)), uv0_2_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 3), uv0_3_16x8b); ht -= 4; pu1_src1 += src_strd1 << 2; pu1_src2 += src_strd2 << 2; pu1_dst += dst_strd << 2; } while(ht > 0); } } /*****************************************************************************/ /* */ /* Function Name : ih264_weighted_pred_luma_sse42 */ /* */ /* Description : This function performs the weighted prediction as */ /* described in sec 8.4.2.3.2 titled "Weighted sample */ /* prediction process" for luma. The function gets one */ /* ht x wd block, weights it, rounds it off, offsets it, */ /* saturates it to unsigned 8-bit and stores it in the */ /* destination block. (ht,wd) can be (4,4), (8,4), (4,8), */ /* (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : pu1_src - Pointer to source */ /* pu1_dst - Pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* log_wd - number of bits to be rounded off */ /* wt - weight value */ /* ofst - offset value */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 04 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSE42 void ih264_weighted_pred_luma_sse42(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 log_wd, WORD32 wt, WORD32 ofst, WORD32 ht, WORD32 wd) { __m128i y_0_16x8b, y_1_16x8b, y_2_16x8b, y_3_16x8b; __m128i wt_8x16b, round_8x16b, ofst_8x16b; WORD32 round_val; wt = (WORD16)(wt & 0xffff); round_val = 1 << (log_wd - 1); ofst = (WORD8)(ofst & 0xff); wt_8x16b = _mm_set1_epi16(wt); round_8x16b = _mm_set1_epi16(round_val); ofst_8x16b = _mm_set1_epi16(ofst); if(wd == 4) { __m128i y_0_8x16b, y_2_8x16b; do { y_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); y_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); y_2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + (src_strd << 1))); y_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd * 3)); y_0_16x8b = _mm_unpacklo_epi32(y_0_16x8b, y_1_16x8b); y_2_16x8b = _mm_unpacklo_epi32(y_2_16x8b, y_3_16x8b); y_0_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); y_2_8x16b = _mm_cvtepu8_epi16(y_2_16x8b); y_0_8x16b = _mm_mullo_epi16(y_0_8x16b, wt_8x16b); y_2_8x16b = _mm_mullo_epi16(y_2_8x16b, wt_8x16b); y_0_8x16b = _mm_adds_epi16(round_8x16b, y_0_8x16b); y_2_8x16b = _mm_adds_epi16(round_8x16b, y_2_8x16b); y_0_8x16b = _mm_srai_epi16(y_0_8x16b, log_wd); y_2_8x16b = _mm_srai_epi16(y_2_8x16b, log_wd); y_0_8x16b = _mm_adds_epi16(ofst_8x16b, y_0_8x16b); y_2_8x16b = _mm_adds_epi16(ofst_8x16b, y_2_8x16b); y_0_16x8b = _mm_packus_epi16(y_0_8x16b, y_2_8x16b); y_1_16x8b = _mm_srli_si128(y_0_16x8b, 4); y_2_16x8b = _mm_srli_si128(y_0_16x8b, 8); y_3_16x8b = _mm_srli_si128(y_0_16x8b, 12); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(y_0_16x8b); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(y_1_16x8b); *((WORD32 *)(pu1_dst + (dst_strd << 1))) = _mm_cvtsi128_si32(y_2_16x8b); *((WORD32 *)(pu1_dst + dst_strd * 3)) = _mm_cvtsi128_si32(y_3_16x8b); ht -= 4; pu1_src += src_strd << 2; pu1_dst += dst_strd << 2; } while(ht > 0); } else if(wd == 8) { __m128i y_0_8x16b, y_1_8x16b, y_2_8x16b, y_3_8x16b; do { y_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); y_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); y_2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + (src_strd << 1))); y_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd * 3)); y_0_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); y_1_8x16b = _mm_cvtepu8_epi16(y_1_16x8b); y_2_8x16b = _mm_cvtepu8_epi16(y_2_16x8b); y_3_8x16b = _mm_cvtepu8_epi16(y_3_16x8b); y_0_8x16b = _mm_mullo_epi16(y_0_8x16b, wt_8x16b); y_1_8x16b = _mm_mullo_epi16(y_1_8x16b, wt_8x16b); y_2_8x16b = _mm_mullo_epi16(y_2_8x16b, wt_8x16b); y_3_8x16b = _mm_mullo_epi16(y_3_8x16b, wt_8x16b); y_0_8x16b = _mm_adds_epi16(round_8x16b, y_0_8x16b); y_1_8x16b = _mm_adds_epi16(round_8x16b, y_1_8x16b); y_2_8x16b = _mm_adds_epi16(round_8x16b, y_2_8x16b); y_3_8x16b = _mm_adds_epi16(round_8x16b, y_3_8x16b); y_0_8x16b = _mm_srai_epi16(y_0_8x16b, log_wd); y_1_8x16b = _mm_srai_epi16(y_1_8x16b, log_wd); y_2_8x16b = _mm_srai_epi16(y_2_8x16b, log_wd); y_3_8x16b = _mm_srai_epi16(y_3_8x16b, log_wd); y_0_8x16b = _mm_adds_epi16(ofst_8x16b, y_0_8x16b); y_1_8x16b = _mm_adds_epi16(ofst_8x16b, y_1_8x16b); y_2_8x16b = _mm_adds_epi16(ofst_8x16b, y_2_8x16b); y_3_8x16b = _mm_adds_epi16(ofst_8x16b, y_3_8x16b); y_0_16x8b = _mm_packus_epi16(y_0_8x16b, y_1_8x16b); y_2_16x8b = _mm_packus_epi16(y_2_8x16b, y_3_8x16b); y_1_16x8b = _mm_srli_si128(y_0_16x8b, 8); y_3_16x8b = _mm_srli_si128(y_2_16x8b, 8); _mm_storel_epi64((__m128i *)pu1_dst, y_0_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + (dst_strd << 1)), y_2_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd * 3), y_3_16x8b); ht -= 4; pu1_src += src_strd << 2; pu1_dst += dst_strd << 2; } while(ht > 0); } else // wd == 16 { __m128i y_0L_8x16b, y_1L_8x16b, y_2L_8x16b, y_3L_8x16b; __m128i y_0H_8x16b, y_1H_8x16b, y_2H_8x16b, y_3H_8x16b; __m128i zero_16x8b; zero_16x8b = _mm_set1_epi8(0); do { y_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); y_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); y_2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + (src_strd << 1))); y_3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd * 3)); y_0L_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); y_0H_8x16b = _mm_unpackhi_epi8(y_0_16x8b, zero_16x8b); y_1L_8x16b = _mm_cvtepu8_epi16(y_1_16x8b); y_1H_8x16b = _mm_unpackhi_epi8(y_1_16x8b, zero_16x8b); y_2L_8x16b = _mm_cvtepu8_epi16(y_2_16x8b); y_2H_8x16b = _mm_unpackhi_epi8(y_2_16x8b, zero_16x8b); y_3L_8x16b = _mm_cvtepu8_epi16(y_3_16x8b); y_3H_8x16b = _mm_unpackhi_epi8(y_3_16x8b, zero_16x8b); y_0L_8x16b = _mm_mullo_epi16(y_0L_8x16b, wt_8x16b); y_0H_8x16b = _mm_mullo_epi16(y_0H_8x16b, wt_8x16b); y_1L_8x16b = _mm_mullo_epi16(y_1L_8x16b, wt_8x16b); y_1H_8x16b = _mm_mullo_epi16(y_1H_8x16b, wt_8x16b); y_2L_8x16b = _mm_mullo_epi16(y_2L_8x16b, wt_8x16b); y_2H_8x16b = _mm_mullo_epi16(y_2H_8x16b, wt_8x16b); y_3L_8x16b = _mm_mullo_epi16(y_3L_8x16b, wt_8x16b); y_3H_8x16b = _mm_mullo_epi16(y_3H_8x16b, wt_8x16b); y_0L_8x16b = _mm_adds_epi16(round_8x16b, y_0L_8x16b); y_0H_8x16b = _mm_adds_epi16(round_8x16b, y_0H_8x16b); y_1L_8x16b = _mm_adds_epi16(round_8x16b, y_1L_8x16b); y_1H_8x16b = _mm_adds_epi16(round_8x16b, y_1H_8x16b); y_2L_8x16b = _mm_adds_epi16(round_8x16b, y_2L_8x16b); y_2H_8x16b = _mm_adds_epi16(round_8x16b, y_2H_8x16b); y_3L_8x16b = _mm_adds_epi16(round_8x16b, y_3L_8x16b); y_3H_8x16b = _mm_adds_epi16(round_8x16b, y_3H_8x16b); y_0L_8x16b = _mm_srai_epi16(y_0L_8x16b, log_wd); y_0H_8x16b = _mm_srai_epi16(y_0H_8x16b, log_wd); y_1L_8x16b = _mm_srai_epi16(y_1L_8x16b, log_wd); y_1H_8x16b = _mm_srai_epi16(y_1H_8x16b, log_wd); y_2L_8x16b = _mm_srai_epi16(y_2L_8x16b, log_wd); y_2H_8x16b = _mm_srai_epi16(y_2H_8x16b, log_wd); y_3L_8x16b = _mm_srai_epi16(y_3L_8x16b, log_wd); y_3H_8x16b = _mm_srai_epi16(y_3H_8x16b, log_wd); y_0L_8x16b = _mm_adds_epi16(ofst_8x16b, y_0L_8x16b); y_0H_8x16b = _mm_adds_epi16(ofst_8x16b, y_0H_8x16b); y_1L_8x16b = _mm_adds_epi16(ofst_8x16b, y_1L_8x16b); y_1H_8x16b = _mm_adds_epi16(ofst_8x16b, y_1H_8x16b); y_2L_8x16b = _mm_adds_epi16(ofst_8x16b, y_2L_8x16b); y_2H_8x16b = _mm_adds_epi16(ofst_8x16b, y_2H_8x16b); y_3L_8x16b = _mm_adds_epi16(ofst_8x16b, y_3L_8x16b); y_3H_8x16b = _mm_adds_epi16(ofst_8x16b, y_3H_8x16b); y_0_16x8b = _mm_packus_epi16(y_0L_8x16b, y_0H_8x16b); y_1_16x8b = _mm_packus_epi16(y_1L_8x16b, y_1H_8x16b); y_2_16x8b = _mm_packus_epi16(y_2L_8x16b, y_2H_8x16b); y_3_16x8b = _mm_packus_epi16(y_3L_8x16b, y_3H_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, y_0_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + (dst_strd << 1)), y_2_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 3), y_3_16x8b); ht -= 4; pu1_src += src_strd << 2; pu1_dst += dst_strd << 2; } while(ht > 0); } } /*****************************************************************************/ /* */ /* Function Name : ih264_weighted_pred_chroma_sse42 */ /* */ /* Description : This function performs the weighted prediction as */ /* described in sec 8.4.2.3.2 titled "Weighted sample */ /* prediction process" for chroma. The function gets one */ /* ht x wd block, weights it, rounds it off, offsets it, */ /* saturates it to unsigned 8-bit and stores it in the */ /* destination block. (ht,wd) can be (2,2), (4,2), (2,4), */ /* (4,4), (8,4), (4,8) or (8,8). */ /* */ /* Inputs : pu1_src - Pointer to source */ /* pu1_dst - Pointer to destination */ /* src_strd - stride for source */ /* dst_strd - stride for destination */ /* log_wd - number of bits to be rounded off */ /* wt - weight values for u and v */ /* ofst - offset values for u and v */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 04 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSE42 void ih264_weighted_pred_chroma_sse42(UWORD8 *pu1_src, UWORD8 *pu1_dst, WORD32 src_strd, WORD32 dst_strd, WORD32 log_wd, WORD32 wt, WORD32 ofst, WORD32 ht, WORD32 wd) { __m128i y_0_16x8b, y_1_16x8b; __m128i wt_8x16b, round_8x16b, ofst_8x16b; WORD32 ofst_u, ofst_v; WORD32 round_val; ofst_u = (WORD8)(ofst & 0xff); ofst_v = (WORD8)(ofst >> 8); round_val = 1 << (log_wd - 1); ofst = (ofst_u & 0xffff) | (ofst_v << 16); wt_8x16b = _mm_set1_epi32(wt); round_8x16b = _mm_set1_epi16(round_val); ofst_8x16b = _mm_set1_epi32(ofst); if(wd == 2) { __m128i y_0_8x16b; do { y_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); y_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); y_0_16x8b = _mm_unpacklo_epi32(y_0_16x8b, y_1_16x8b); y_0_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); y_0_8x16b = _mm_mullo_epi16(y_0_8x16b, wt_8x16b); y_0_8x16b = _mm_adds_epi16(round_8x16b, y_0_8x16b); y_0_8x16b = _mm_srai_epi16(y_0_8x16b, log_wd); y_0_8x16b = _mm_adds_epi16(ofst_8x16b, y_0_8x16b); y_0_16x8b = _mm_packus_epi16(y_0_8x16b, y_0_8x16b); y_1_16x8b = _mm_srli_si128(y_0_16x8b, 4); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(y_0_16x8b); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(y_1_16x8b); ht -= 2; pu1_src += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else if(wd == 4) { __m128i y_0_8x16b, y_1_8x16b; do { y_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); y_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); y_0_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); y_1_8x16b = _mm_cvtepu8_epi16(y_1_16x8b); y_0_8x16b = _mm_mullo_epi16(y_0_8x16b, wt_8x16b); y_1_8x16b = _mm_mullo_epi16(y_1_8x16b, wt_8x16b); y_0_8x16b = _mm_adds_epi16(round_8x16b, y_0_8x16b); y_1_8x16b = _mm_adds_epi16(round_8x16b, y_1_8x16b); y_0_8x16b = _mm_srai_epi16(y_0_8x16b, log_wd); y_1_8x16b = _mm_srai_epi16(y_1_8x16b, log_wd); y_0_8x16b = _mm_adds_epi16(ofst_8x16b, y_0_8x16b); y_1_8x16b = _mm_adds_epi16(ofst_8x16b, y_1_8x16b); y_0_16x8b = _mm_packus_epi16(y_0_8x16b, y_1_8x16b); y_1_16x8b = _mm_srli_si128(y_0_16x8b, 8); _mm_storel_epi64((__m128i *)pu1_dst, y_0_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); ht -= 2; pu1_src += src_strd << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else // wd == 16 { __m128i y_2_16x8b, y_3_16x8b; __m128i y_0L_8x16b, y_1L_8x16b, y_2L_8x16b, y_3L_8x16b; __m128i y_0H_8x16b, y_1H_8x16b, y_2H_8x16b, y_3H_8x16b; __m128i zero_16x8b; zero_16x8b = _mm_set1_epi8(0); do { y_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); y_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); y_2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + (src_strd << 1))); y_3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd * 3)); y_0L_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); y_0H_8x16b = _mm_unpackhi_epi8(y_0_16x8b, zero_16x8b); y_1L_8x16b = _mm_cvtepu8_epi16(y_1_16x8b); y_1H_8x16b = _mm_unpackhi_epi8(y_1_16x8b, zero_16x8b); y_2L_8x16b = _mm_cvtepu8_epi16(y_2_16x8b); y_2H_8x16b = _mm_unpackhi_epi8(y_2_16x8b, zero_16x8b); y_3L_8x16b = _mm_cvtepu8_epi16(y_3_16x8b); y_3H_8x16b = _mm_unpackhi_epi8(y_3_16x8b, zero_16x8b); y_0L_8x16b = _mm_mullo_epi16(y_0L_8x16b, wt_8x16b); y_0H_8x16b = _mm_mullo_epi16(y_0H_8x16b, wt_8x16b); y_1L_8x16b = _mm_mullo_epi16(y_1L_8x16b, wt_8x16b); y_1H_8x16b = _mm_mullo_epi16(y_1H_8x16b, wt_8x16b); y_2L_8x16b = _mm_mullo_epi16(y_2L_8x16b, wt_8x16b); y_2H_8x16b = _mm_mullo_epi16(y_2H_8x16b, wt_8x16b); y_3L_8x16b = _mm_mullo_epi16(y_3L_8x16b, wt_8x16b); y_3H_8x16b = _mm_mullo_epi16(y_3H_8x16b, wt_8x16b); y_0L_8x16b = _mm_adds_epi16(round_8x16b, y_0L_8x16b); y_0H_8x16b = _mm_adds_epi16(round_8x16b, y_0H_8x16b); y_1L_8x16b = _mm_adds_epi16(round_8x16b, y_1L_8x16b); y_1H_8x16b = _mm_adds_epi16(round_8x16b, y_1H_8x16b); y_2L_8x16b = _mm_adds_epi16(round_8x16b, y_2L_8x16b); y_2H_8x16b = _mm_adds_epi16(round_8x16b, y_2H_8x16b); y_3L_8x16b = _mm_adds_epi16(round_8x16b, y_3L_8x16b); y_3H_8x16b = _mm_adds_epi16(round_8x16b, y_3H_8x16b); y_0L_8x16b = _mm_srai_epi16(y_0L_8x16b, log_wd); y_0H_8x16b = _mm_srai_epi16(y_0H_8x16b, log_wd); y_1L_8x16b = _mm_srai_epi16(y_1L_8x16b, log_wd); y_1H_8x16b = _mm_srai_epi16(y_1H_8x16b, log_wd); y_2L_8x16b = _mm_srai_epi16(y_2L_8x16b, log_wd); y_2H_8x16b = _mm_srai_epi16(y_2H_8x16b, log_wd); y_3L_8x16b = _mm_srai_epi16(y_3L_8x16b, log_wd); y_3H_8x16b = _mm_srai_epi16(y_3H_8x16b, log_wd); y_0L_8x16b = _mm_adds_epi16(ofst_8x16b, y_0L_8x16b); y_0H_8x16b = _mm_adds_epi16(ofst_8x16b, y_0H_8x16b); y_1L_8x16b = _mm_adds_epi16(ofst_8x16b, y_1L_8x16b); y_1H_8x16b = _mm_adds_epi16(ofst_8x16b, y_1H_8x16b); y_2L_8x16b = _mm_adds_epi16(ofst_8x16b, y_2L_8x16b); y_2H_8x16b = _mm_adds_epi16(ofst_8x16b, y_2H_8x16b); y_3L_8x16b = _mm_adds_epi16(ofst_8x16b, y_3L_8x16b); y_3H_8x16b = _mm_adds_epi16(ofst_8x16b, y_3H_8x16b); y_0_16x8b = _mm_packus_epi16(y_0L_8x16b, y_0H_8x16b); y_1_16x8b = _mm_packus_epi16(y_1L_8x16b, y_1H_8x16b); y_2_16x8b = _mm_packus_epi16(y_2L_8x16b, y_2H_8x16b); y_3_16x8b = _mm_packus_epi16(y_3L_8x16b, y_3H_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, y_0_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + (dst_strd << 1)), y_2_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 3), y_3_16x8b); ht -= 4; pu1_src += src_strd << 2; pu1_dst += dst_strd << 2; } while(ht > 0); } } /*****************************************************************************/ /* */ /* Function Name : ih264_weighted_bi_pred_luma_sse42 */ /* */ /* Description : This function performs the weighted biprediction as */ /* described in sec 8.4.2.3.2 titled "Weighted sample */ /* prediction process" for luma. The function gets two */ /* ht x wd blocks, weights them, adds them, rounds off the */ /* sum, offsets it, saturates it to unsigned 8-bit and */ /* stores it in the destination block. (ht,wd) can be */ /* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ /* */ /* Inputs : pu1_src1 - Pointer to source 1 */ /* pu1_src2 - Pointer to source 2 */ /* pu1_dst - Pointer to destination */ /* src_strd1 - stride for source 1 */ /* src_strd2 - stride for source 2 */ /* dst_strd2 - stride for destination */ /* log_wd - number of bits to be rounded off */ /* wt1 - weight value for source 1 */ /* wt2 - weight value for source 2 */ /* ofst1 - offset value for source 1 */ /* ofst2 - offset value for source 2 */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 04 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSE42 void ih264_weighted_bi_pred_luma_sse42(UWORD8 *pu1_src1, UWORD8 *pu1_src2, UWORD8 *pu1_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 log_wd, WORD32 wt1, WORD32 wt2, WORD32 ofst1, WORD32 ofst2, WORD32 ht, WORD32 wd) { __m128i y1_0_16x8b, y1_1_16x8b; __m128i y2_0_16x8b, y2_1_16x8b; __m128i wt1_8x16b, wt2_8x16b; __m128i ofst_8x16b, round_8x16b; WORD32 ofst; WORD32 round_val, shft; wt1 = (WORD16)(wt1 & 0xffff); wt2 = (WORD16)(wt2 & 0xffff); round_val = 1 << log_wd; shft = log_wd + 1; ofst1 = (WORD8)(ofst1 & 0xff); ofst2 = (WORD8)(ofst2 & 0xff); ofst = (ofst1 + ofst2 + 1) >> 1; wt1_8x16b = _mm_set1_epi16(wt1); wt2_8x16b = _mm_set1_epi16(wt2); round_8x16b = _mm_set1_epi16(round_val); ofst_8x16b = _mm_set1_epi16(ofst); if(wd == 4) { __m128i y1_2_16x8b, y1_3_16x8b; __m128i y2_2_16x8b, y2_3_16x8b; __m128i y1_0_8x16b, y1_2_8x16b; __m128i y2_0_8x16b, y2_2_8x16b; do { y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); y1_2_16x8b = _mm_loadl_epi64( (__m128i *)(pu1_src1 + (src_strd1 << 1))); y1_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1 * 3)); y2_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); y2_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); y2_2_16x8b = _mm_loadl_epi64( (__m128i *)(pu1_src2 + (src_strd2 << 1))); y2_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2 * 3)); y1_0_16x8b = _mm_unpacklo_epi32(y1_0_16x8b, y1_1_16x8b); y1_2_16x8b = _mm_unpacklo_epi32(y1_2_16x8b, y1_3_16x8b); y2_0_16x8b = _mm_unpacklo_epi32(y2_0_16x8b, y2_1_16x8b); y2_2_16x8b = _mm_unpacklo_epi32(y2_2_16x8b, y2_3_16x8b); y1_0_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); y1_2_8x16b = _mm_cvtepu8_epi16(y1_2_16x8b); y2_0_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); y2_2_8x16b = _mm_cvtepu8_epi16(y2_2_16x8b); y1_0_8x16b = _mm_mullo_epi16(y1_0_8x16b, wt1_8x16b); y2_0_8x16b = _mm_mullo_epi16(y2_0_8x16b, wt2_8x16b); y1_2_8x16b = _mm_mullo_epi16(y1_2_8x16b, wt1_8x16b); y2_2_8x16b = _mm_mullo_epi16(y2_2_8x16b, wt2_8x16b); y1_0_8x16b = _mm_adds_epi16(y1_0_8x16b, y2_0_8x16b); y1_2_8x16b = _mm_adds_epi16(y1_2_8x16b, y2_2_8x16b); y1_0_8x16b = _mm_adds_epi16(round_8x16b, y1_0_8x16b); y1_2_8x16b = _mm_adds_epi16(round_8x16b, y1_2_8x16b); y1_0_8x16b = _mm_srai_epi16(y1_0_8x16b, shft); y1_2_8x16b = _mm_srai_epi16(y1_2_8x16b, shft); y1_0_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0_8x16b); y1_2_8x16b = _mm_adds_epi16(ofst_8x16b, y1_2_8x16b); y1_0_16x8b = _mm_packus_epi16(y1_0_8x16b, y1_2_8x16b); y1_1_16x8b = _mm_srli_si128(y1_0_16x8b, 4); y1_2_16x8b = _mm_srli_si128(y1_0_16x8b, 8); y1_3_16x8b = _mm_srli_si128(y1_0_16x8b, 12); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(y1_0_16x8b); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(y1_1_16x8b); *((WORD32 *)(pu1_dst + (dst_strd << 1))) = _mm_cvtsi128_si32(y1_2_16x8b); *((WORD32 *)(pu1_dst + dst_strd * 3)) = _mm_cvtsi128_si32(y1_3_16x8b); ht -= 4; pu1_src1 += src_strd1 << 2; pu1_src2 += src_strd2 << 2; pu1_dst += dst_strd << 2; } while(ht > 0); } else if(wd == 8) { __m128i y1_2_16x8b, y1_3_16x8b; __m128i y2_2_16x8b, y2_3_16x8b; __m128i y1_0_8x16b, y1_1_8x16b, y1_2_8x16b, y1_3_8x16b; __m128i y2_0_8x16b, y2_1_8x16b, y2_2_8x16b, y2_3_8x16b; do { y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); y1_2_16x8b = _mm_loadl_epi64( (__m128i *)(pu1_src1 + (src_strd1 << 1))); y1_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1 * 3)); y2_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); y2_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); y2_2_16x8b = _mm_loadl_epi64( (__m128i *)(pu1_src2 + (src_strd2 << 1))); y2_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2 * 3)); y1_0_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); y1_1_8x16b = _mm_cvtepu8_epi16(y1_1_16x8b); y1_2_8x16b = _mm_cvtepu8_epi16(y1_2_16x8b); y1_3_8x16b = _mm_cvtepu8_epi16(y1_3_16x8b); y2_0_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); y2_1_8x16b = _mm_cvtepu8_epi16(y2_1_16x8b); y2_2_8x16b = _mm_cvtepu8_epi16(y2_2_16x8b); y2_3_8x16b = _mm_cvtepu8_epi16(y2_3_16x8b); y1_0_8x16b = _mm_mullo_epi16(y1_0_8x16b, wt1_8x16b); y2_0_8x16b = _mm_mullo_epi16(y2_0_8x16b, wt2_8x16b); y1_1_8x16b = _mm_mullo_epi16(y1_1_8x16b, wt1_8x16b); y2_1_8x16b = _mm_mullo_epi16(y2_1_8x16b, wt2_8x16b); y1_2_8x16b = _mm_mullo_epi16(y1_2_8x16b, wt1_8x16b); y2_2_8x16b = _mm_mullo_epi16(y2_2_8x16b, wt2_8x16b); y1_3_8x16b = _mm_mullo_epi16(y1_3_8x16b, wt1_8x16b); y2_3_8x16b = _mm_mullo_epi16(y2_3_8x16b, wt2_8x16b); y1_0_8x16b = _mm_adds_epi16(y1_0_8x16b, y2_0_8x16b); y1_1_8x16b = _mm_adds_epi16(y1_1_8x16b, y2_1_8x16b); y1_2_8x16b = _mm_adds_epi16(y1_2_8x16b, y2_2_8x16b); y1_3_8x16b = _mm_adds_epi16(y1_3_8x16b, y2_3_8x16b); y1_0_8x16b = _mm_adds_epi16(round_8x16b, y1_0_8x16b); y1_1_8x16b = _mm_adds_epi16(round_8x16b, y1_1_8x16b); y1_2_8x16b = _mm_adds_epi16(round_8x16b, y1_2_8x16b); y1_3_8x16b = _mm_adds_epi16(round_8x16b, y1_3_8x16b); y1_0_8x16b = _mm_srai_epi16(y1_0_8x16b, shft); y1_1_8x16b = _mm_srai_epi16(y1_1_8x16b, shft); y1_2_8x16b = _mm_srai_epi16(y1_2_8x16b, shft); y1_3_8x16b = _mm_srai_epi16(y1_3_8x16b, shft); y1_0_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0_8x16b); y1_1_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1_8x16b); y1_2_8x16b = _mm_adds_epi16(ofst_8x16b, y1_2_8x16b); y1_3_8x16b = _mm_adds_epi16(ofst_8x16b, y1_3_8x16b); y1_0_16x8b = _mm_packus_epi16(y1_0_8x16b, y1_1_8x16b); y1_2_16x8b = _mm_packus_epi16(y1_2_8x16b, y1_3_8x16b); y1_1_16x8b = _mm_srli_si128(y1_0_16x8b, 8); y1_3_16x8b = _mm_srli_si128(y1_2_16x8b, 8); _mm_storel_epi64((__m128i *)pu1_dst, y1_0_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y1_1_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + (dst_strd << 1)), y1_2_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd * 3), y1_3_16x8b); ht -= 4; pu1_src1 += src_strd1 << 2; pu1_src2 += src_strd2 << 2; pu1_dst += dst_strd << 2; } while(ht > 0); } else // wd == 16 { __m128i y1_0L_8x16b, y1_0H_8x16b, y1_1L_8x16b, y1_1H_8x16b; __m128i y2_0L_8x16b, y2_0H_8x16b, y2_1L_8x16b, y2_1H_8x16b; __m128i zero_16x8b; zero_16x8b = _mm_set1_epi8(0); do { y1_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src1); y1_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1)); y2_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src2); y2_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2)); y1_0L_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); y1_0H_8x16b = _mm_unpackhi_epi8(y1_0_16x8b, zero_16x8b); y1_1L_8x16b = _mm_cvtepu8_epi16(y1_1_16x8b); y1_1H_8x16b = _mm_unpackhi_epi8(y1_1_16x8b, zero_16x8b); y2_0L_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); y2_0H_8x16b = _mm_unpackhi_epi8(y2_0_16x8b, zero_16x8b); y2_1L_8x16b = _mm_cvtepu8_epi16(y2_1_16x8b); y2_1H_8x16b = _mm_unpackhi_epi8(y2_1_16x8b, zero_16x8b); y1_0L_8x16b = _mm_mullo_epi16(y1_0L_8x16b, wt1_8x16b); y1_0H_8x16b = _mm_mullo_epi16(y1_0H_8x16b, wt1_8x16b); y1_1L_8x16b = _mm_mullo_epi16(y1_1L_8x16b, wt1_8x16b); y1_1H_8x16b = _mm_mullo_epi16(y1_1H_8x16b, wt1_8x16b); y2_0L_8x16b = _mm_mullo_epi16(y2_0L_8x16b, wt2_8x16b); y2_0H_8x16b = _mm_mullo_epi16(y2_0H_8x16b, wt2_8x16b); y2_1L_8x16b = _mm_mullo_epi16(y2_1L_8x16b, wt2_8x16b); y2_1H_8x16b = _mm_mullo_epi16(y2_1H_8x16b, wt2_8x16b); y1_0L_8x16b = _mm_adds_epi16(y1_0L_8x16b, y2_0L_8x16b); y1_0H_8x16b = _mm_adds_epi16(y1_0H_8x16b, y2_0H_8x16b); y1_1L_8x16b = _mm_adds_epi16(y1_1L_8x16b, y2_1L_8x16b); y1_1H_8x16b = _mm_adds_epi16(y1_1H_8x16b, y2_1H_8x16b); y1_0L_8x16b = _mm_adds_epi16(round_8x16b, y1_0L_8x16b); y1_0H_8x16b = _mm_adds_epi16(round_8x16b, y1_0H_8x16b); y1_1L_8x16b = _mm_adds_epi16(round_8x16b, y1_1L_8x16b); y1_1H_8x16b = _mm_adds_epi16(round_8x16b, y1_1H_8x16b); y1_0L_8x16b = _mm_srai_epi16(y1_0L_8x16b, shft); y1_0H_8x16b = _mm_srai_epi16(y1_0H_8x16b, shft); y1_1L_8x16b = _mm_srai_epi16(y1_1L_8x16b, shft); y1_1H_8x16b = _mm_srai_epi16(y1_1H_8x16b, shft); y1_0L_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0L_8x16b); y1_0H_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0H_8x16b); y1_1L_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1L_8x16b); y1_1H_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1H_8x16b); y1_0_16x8b = _mm_packus_epi16(y1_0L_8x16b, y1_0H_8x16b); y1_1_16x8b = _mm_packus_epi16(y1_1L_8x16b, y1_1H_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, y1_0_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y1_1_16x8b); ht -= 2; pu1_src1 += src_strd1 << 1; pu1_src2 += src_strd2 << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } } /*****************************************************************************/ /* */ /* Function Name : ih264_weighted_bi_pred_chroma_sse42 */ /* */ /* Description : This function performs the weighted biprediction as */ /* described in sec 8.4.2.3.2 titled "Weighted sample */ /* prediction process" for chroma. The function gets two */ /* ht x wd blocks, weights them, adds them, rounds off the */ /* sum, offsets it, saturates it to unsigned 8-bit and */ /* stores it in the destination block. (ht,wd) can be */ /* (2,2), (4,2), (2,4), (4,4), (8,4), (4,8) or (8,8). */ /* */ /* Inputs : pu1_src1 - Pointer to source 1 */ /* pu1_src2 - Pointer to source 2 */ /* pu1_dst - Pointer to destination */ /* src_strd1 - stride for source 1 */ /* src_strd2 - stride for source 2 */ /* dst_strd2 - stride for destination */ /* log_wd - number of bits to be rounded off */ /* wt1 - weight values for u and v in source 1 */ /* wt2 - weight values for u and v in source 2 */ /* ofst1 - offset value for u and v in source 1 */ /* ofst2 - offset value for u and v in source 2 */ /* ht - height of the block */ /* wd - width of the block */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes */ /* 04 02 2015 Kaushik Initial Version */ /* Senthoor */ /* */ /*****************************************************************************/ ATTRIBUTE_SSE42 void ih264_weighted_bi_pred_chroma_sse42(UWORD8 *pu1_src1, UWORD8 *pu1_src2, UWORD8 *pu1_dst, WORD32 src_strd1, WORD32 src_strd2, WORD32 dst_strd, WORD32 log_wd, WORD32 wt1, WORD32 wt2, WORD32 ofst1, WORD32 ofst2, WORD32 ht, WORD32 wd) { __m128i y1_0_16x8b, y1_1_16x8b; __m128i y2_0_16x8b, y2_1_16x8b; __m128i wt1_8x16b, wt2_8x16b; __m128i ofst_8x16b, round_8x16b; WORD32 ofst1_u, ofst2_u, ofst_u; WORD32 ofst1_v, ofst2_v, ofst_v; WORD32 round_val, shft, ofst_val; round_val = 1 << log_wd; shft = log_wd + 1; ofst1_u = (WORD8)(ofst1 & 0xff); ofst1_v = (WORD8)(ofst1 >> 8); ofst2_u = (WORD8)(ofst2 & 0xff); ofst2_v = (WORD8)(ofst2 >> 8); wt1_8x16b = _mm_set1_epi32(wt1); wt2_8x16b = _mm_set1_epi32(wt2); ofst_u = (ofst1_u + ofst2_u + 1) >> 1; ofst_v = (ofst1_v + ofst2_v + 1) >> 1; ofst_val = (ofst_u & 0xffff) | (ofst_v << 16); round_8x16b = _mm_set1_epi16(round_val); ofst_8x16b = _mm_set1_epi32(ofst_val); if(wd == 2) { __m128i y1_0_8x16b, y2_0_8x16b; do { y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); y2_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); y2_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); y1_0_16x8b = _mm_unpacklo_epi32(y1_0_16x8b, y1_1_16x8b); y2_0_16x8b = _mm_unpacklo_epi32(y2_0_16x8b, y2_1_16x8b); y1_0_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); y2_0_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); y1_0_8x16b = _mm_mullo_epi16(y1_0_8x16b, wt1_8x16b); y2_0_8x16b = _mm_mullo_epi16(y2_0_8x16b, wt2_8x16b); y1_0_8x16b = _mm_adds_epi16(y1_0_8x16b, y2_0_8x16b); y1_0_8x16b = _mm_adds_epi16(round_8x16b, y1_0_8x16b); y1_0_8x16b = _mm_srai_epi16(y1_0_8x16b, shft); y1_0_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0_8x16b); y1_0_16x8b = _mm_packus_epi16(y1_0_8x16b, y1_0_8x16b); y1_1_16x8b = _mm_srli_si128(y1_0_16x8b, 4); *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(y1_0_16x8b); *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(y1_1_16x8b); ht -= 2; pu1_src1 += src_strd1 << 1; pu1_src2 += src_strd2 << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else if(wd == 4) { __m128i y1_0_8x16b, y1_1_8x16b; __m128i y2_0_8x16b, y2_1_8x16b; do { y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); y2_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); y2_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); y1_0_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); y1_1_8x16b = _mm_cvtepu8_epi16(y1_1_16x8b); y2_0_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); y2_1_8x16b = _mm_cvtepu8_epi16(y2_1_16x8b); y1_0_8x16b = _mm_mullo_epi16(y1_0_8x16b, wt1_8x16b); y2_0_8x16b = _mm_mullo_epi16(y2_0_8x16b, wt2_8x16b); y1_1_8x16b = _mm_mullo_epi16(y1_1_8x16b, wt1_8x16b); y2_1_8x16b = _mm_mullo_epi16(y2_1_8x16b, wt2_8x16b); y1_0_8x16b = _mm_adds_epi16(y1_0_8x16b, y2_0_8x16b); y1_1_8x16b = _mm_adds_epi16(y1_1_8x16b, y2_1_8x16b); y1_0_8x16b = _mm_adds_epi16(round_8x16b, y1_0_8x16b); y1_1_8x16b = _mm_adds_epi16(round_8x16b, y1_1_8x16b); y1_0_8x16b = _mm_srai_epi16(y1_0_8x16b, shft); y1_1_8x16b = _mm_srai_epi16(y1_1_8x16b, shft); y1_0_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0_8x16b); y1_1_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1_8x16b); y1_0_16x8b = _mm_packus_epi16(y1_0_8x16b, y1_1_8x16b); y1_1_16x8b = _mm_srli_si128(y1_0_16x8b, 8); _mm_storel_epi64((__m128i *)pu1_dst, y1_0_16x8b); _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y1_1_16x8b); ht -= 2; pu1_src1 += src_strd1 << 1; pu1_src2 += src_strd2 << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } else // wd == 8 { __m128i y1_0L_8x16b, y1_0H_8x16b, y1_1L_8x16b, y1_1H_8x16b; __m128i y2_0L_8x16b, y2_0H_8x16b, y2_1L_8x16b, y2_1H_8x16b; __m128i zero_16x8b; zero_16x8b = _mm_set1_epi8(0); do { y1_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src1); y1_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1)); y2_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src2); y2_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2)); y1_0L_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); y1_0H_8x16b = _mm_unpackhi_epi8(y1_0_16x8b, zero_16x8b); y1_1L_8x16b = _mm_cvtepu8_epi16(y1_1_16x8b); y1_1H_8x16b = _mm_unpackhi_epi8(y1_1_16x8b, zero_16x8b); y2_0L_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); y2_0H_8x16b = _mm_unpackhi_epi8(y2_0_16x8b, zero_16x8b); y2_1L_8x16b = _mm_cvtepu8_epi16(y2_1_16x8b); y2_1H_8x16b = _mm_unpackhi_epi8(y2_1_16x8b, zero_16x8b); y1_0L_8x16b = _mm_mullo_epi16(y1_0L_8x16b, wt1_8x16b); y1_0H_8x16b = _mm_mullo_epi16(y1_0H_8x16b, wt1_8x16b); y1_1L_8x16b = _mm_mullo_epi16(y1_1L_8x16b, wt1_8x16b); y1_1H_8x16b = _mm_mullo_epi16(y1_1H_8x16b, wt1_8x16b); y2_0L_8x16b = _mm_mullo_epi16(y2_0L_8x16b, wt2_8x16b); y2_0H_8x16b = _mm_mullo_epi16(y2_0H_8x16b, wt2_8x16b); y2_1L_8x16b = _mm_mullo_epi16(y2_1L_8x16b, wt2_8x16b); y2_1H_8x16b = _mm_mullo_epi16(y2_1H_8x16b, wt2_8x16b); y1_0L_8x16b = _mm_adds_epi16(y1_0L_8x16b, y2_0L_8x16b); y1_0H_8x16b = _mm_adds_epi16(y1_0H_8x16b, y2_0H_8x16b); y1_1L_8x16b = _mm_adds_epi16(y1_1L_8x16b, y2_1L_8x16b); y1_1H_8x16b = _mm_adds_epi16(y1_1H_8x16b, y2_1H_8x16b); y1_0L_8x16b = _mm_adds_epi16(round_8x16b, y1_0L_8x16b); y1_0H_8x16b = _mm_adds_epi16(round_8x16b, y1_0H_8x16b); y1_1L_8x16b = _mm_adds_epi16(round_8x16b, y1_1L_8x16b); y1_1H_8x16b = _mm_adds_epi16(round_8x16b, y1_1H_8x16b); y1_0L_8x16b = _mm_srai_epi16(y1_0L_8x16b, shft); y1_0H_8x16b = _mm_srai_epi16(y1_0H_8x16b, shft); y1_1L_8x16b = _mm_srai_epi16(y1_1L_8x16b, shft); y1_1H_8x16b = _mm_srai_epi16(y1_1H_8x16b, shft); y1_0L_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0L_8x16b); y1_0H_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0H_8x16b); y1_1L_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1L_8x16b); y1_1H_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1H_8x16b); y1_0_16x8b = _mm_packus_epi16(y1_0L_8x16b, y1_0H_8x16b); y1_1_16x8b = _mm_packus_epi16(y1_1L_8x16b, y1_1H_8x16b); _mm_storeu_si128((__m128i *)pu1_dst, y1_0_16x8b); _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y1_1_16x8b); ht -= 2; pu1_src1 += src_strd1 << 1; pu1_src2 += src_strd2 << 1; pu1_dst += dst_strd << 1; } while(ht > 0); } } ================================================ FILE: dependencies/ih264d/decoder/arm/ih264d_function_selector.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ihevcd_function_selector.c * * @brief * Contains functions to initialize function pointers used in hevc * * @author * Naveen * * @par List of Functions: * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include "ih264_typedefs.h" #include "iv.h" #include "ivd.h" #include "ih264_defs.h" #include "ih264_size_defs.h" #include "ih264_error.h" #include "ih264_trans_quant_itrans_iquant.h" #include "ih264_inter_pred_filters.h" #include "ih264d_structs.h" #include "ih264d_function_selector.h" void ih264d_init_function_ptr(dec_struct_t *ps_codec) { IVD_ARCH_T e_proc_arch = ps_codec->e_processor_arch; ih264d_init_function_ptr_generic(ps_codec); switch(e_proc_arch) { #if defined(ARMV8) case ARCH_ARMV8_GENERIC: default: ih264d_init_function_ptr_av8(ps_codec); break; #elif !defined(DISABLE_NEON) case ARCH_ARM_A5: case ARCH_ARM_A7: case ARCH_ARM_A9: case ARCH_ARM_A15: case ARCH_ARM_A9Q: default: ih264d_init_function_ptr_a9q(ps_codec); break; #else default: #endif case ARCH_ARM_NONEON: break; } } void ih264d_init_arch(dec_struct_t *ps_codec) { #ifdef DEFAULT_ARCH #if DEFAULT_ARCH == D_ARCH_ARM_NONEON ps_codec->e_processor_arch = ARCH_ARM_NONEON; #elif DEFAULT_ARCH == D_ARCH_ARMV8_GENERIC ps_codec->e_processor_arch = ARCH_ARMV8_GENERIC; #elif DEFAULT_ARCH == D_ARCH_ARM_NEONINTR ps_codec->e_processor_arch = ARCH_ARM_NEONINTR; #else ps_codec->e_processor_arch = ARCH_ARM_A9Q; #endif #else ps_codec->e_processor_arch = ARCH_ARM_A9Q; #endif } ================================================ FILE: dependencies/ih264d/decoder/arm/ih264d_function_selector_a9q.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264e_function_selector_a9q.c * * @brief * Contains functions to initialize function pointers of codec context * * @author * Ittiam * * @par List of Functions: * - ih264e_init_function_ptr_a9q * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System Include files */ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> /* User Include files */ #include "ih264_typedefs.h" #include "iv.h" #include "ivd.h" #include "ih264_defs.h" #include "ih264_size_defs.h" #include "ih264_error.h" #include "ih264_trans_quant_itrans_iquant.h" #include "ih264_inter_pred_filters.h" #include "ih264d_structs.h" /** ******************************************************************************* * * @brief Initialize the intra/inter/transform/deblk function pointers of * codec context * * @par Description: the current routine initializes the function pointers of * codec context basing on the architecture in use * * @param[in] ps_codec * Codec context pointer * * @returns none * * @remarks none * ******************************************************************************* */ void ih264d_init_function_ptr_a9q(dec_struct_t *ps_codec) { /* Init function pointers for intra pred leaf level functions luma * Intra 16x16 */ ps_codec->apf_intra_pred_luma_16x16[0] = ih264_intra_pred_luma_16x16_mode_vert_a9q; ps_codec->apf_intra_pred_luma_16x16[1] = ih264_intra_pred_luma_16x16_mode_horz_a9q; ps_codec->apf_intra_pred_luma_16x16[2] = ih264_intra_pred_luma_16x16_mode_dc_a9q; ps_codec->apf_intra_pred_luma_16x16[3] = ih264_intra_pred_luma_16x16_mode_plane_a9q; /* Init function pointers for intra pred leaf level functions luma * Intra 4x4 */ ps_codec->apf_intra_pred_luma_4x4[0] = ih264_intra_pred_luma_4x4_mode_vert_a9q; ps_codec->apf_intra_pred_luma_4x4[1] = ih264_intra_pred_luma_4x4_mode_horz_a9q; ps_codec->apf_intra_pred_luma_4x4[2] = ih264_intra_pred_luma_4x4_mode_dc_a9q; ps_codec->apf_intra_pred_luma_4x4[3] = ih264_intra_pred_luma_4x4_mode_diag_dl_a9q; ps_codec->apf_intra_pred_luma_4x4[4] = ih264_intra_pred_luma_4x4_mode_diag_dr_a9q; ps_codec->apf_intra_pred_luma_4x4[5] = ih264_intra_pred_luma_4x4_mode_vert_r_a9q; ps_codec->apf_intra_pred_luma_4x4[6] = ih264_intra_pred_luma_4x4_mode_horz_d_a9q; ps_codec->apf_intra_pred_luma_4x4[7] = ih264_intra_pred_luma_4x4_mode_vert_l_a9q; ps_codec->apf_intra_pred_luma_4x4[8] = ih264_intra_pred_luma_4x4_mode_horz_u_a9q; /* Init function pointers for intra pred leaf level functions luma * Intra 8x8 */ ps_codec->apf_intra_pred_luma_8x8[0] = ih264_intra_pred_luma_8x8_mode_vert_a9q; ps_codec->apf_intra_pred_luma_8x8[1] = ih264_intra_pred_luma_8x8_mode_horz_a9q; ps_codec->apf_intra_pred_luma_8x8[2] = ih264_intra_pred_luma_8x8_mode_dc_a9q; ps_codec->apf_intra_pred_luma_8x8[3] = ih264_intra_pred_luma_8x8_mode_diag_dl_a9q; ps_codec->apf_intra_pred_luma_8x8[4] = ih264_intra_pred_luma_8x8_mode_diag_dr_a9q; ps_codec->apf_intra_pred_luma_8x8[5] = ih264_intra_pred_luma_8x8_mode_vert_r_a9q; ps_codec->apf_intra_pred_luma_8x8[6] = ih264_intra_pred_luma_8x8_mode_horz_d_a9q; ps_codec->apf_intra_pred_luma_8x8[7] = ih264_intra_pred_luma_8x8_mode_vert_l_a9q; ps_codec->apf_intra_pred_luma_8x8[8] = ih264_intra_pred_luma_8x8_mode_horz_u_a9q; /* ih264_intra_pred_luma_8x8_mode_ref_filtering_a9q does not handle all availibilities */ ps_codec->pf_intra_pred_ref_filtering = ih264_intra_pred_luma_8x8_mode_ref_filtering; /* Init function pointers for intra pred leaf level functions chroma * Intra 8x8 */ ps_codec->apf_intra_pred_chroma[0] = ih264_intra_pred_chroma_8x8_mode_vert_a9q; ps_codec->apf_intra_pred_chroma[1] = ih264_intra_pred_chroma_8x8_mode_horz_a9q; /* ih264_intra_pred_chroma_8x8_mode_dc_a9q does not support interlaced clips, hence using C */ ps_codec->apf_intra_pred_chroma[2] = ih264_intra_pred_chroma_8x8_mode_dc; ps_codec->apf_intra_pred_chroma[3] = ih264_intra_pred_chroma_8x8_mode_plane_a9q; ps_codec->pf_default_weighted_pred_luma = ih264_default_weighted_pred_luma_a9q; ps_codec->pf_default_weighted_pred_chroma = ih264_default_weighted_pred_chroma_a9q; ps_codec->pf_weighted_pred_luma = ih264_weighted_pred_luma_a9q; ps_codec->pf_weighted_pred_chroma = ih264_weighted_pred_chroma_a9q; ps_codec->pf_weighted_bi_pred_luma = ih264_weighted_bi_pred_luma_a9q; ps_codec->pf_weighted_bi_pred_chroma = ih264_weighted_bi_pred_chroma_a9q; /* Padding Functions */ ps_codec->pf_pad_top = ih264_pad_top_a9q; ps_codec->pf_pad_bottom = ih264_pad_bottom; ps_codec->pf_pad_left_luma = ih264_pad_left_luma_a9q; ps_codec->pf_pad_right_luma = ih264_pad_right_luma_a9q; ps_codec->pf_pad_left_chroma = ih264_pad_left_chroma_a9q; ps_codec->pf_pad_right_chroma = ih264_pad_right_chroma_a9q; ps_codec->pf_iquant_itrans_recon_luma_4x4 = ih264_iquant_itrans_recon_4x4_a9; ps_codec->pf_iquant_itrans_recon_luma_4x4_dc = ih264_iquant_itrans_recon_4x4_dc_a9; ps_codec->pf_iquant_itrans_recon_luma_8x8 = ih264_iquant_itrans_recon_8x8_a9; ps_codec->pf_iquant_itrans_recon_luma_8x8_dc = ih264_iquant_itrans_recon_8x8_dc_a9; ps_codec->pf_ihadamard_scaling_4x4 = ih264_ihadamard_scaling_4x4_a9; ps_codec->pf_iquant_itrans_recon_chroma_4x4 = ih264_iquant_itrans_recon_chroma_4x4_a9; ps_codec->pf_iquant_itrans_recon_chroma_4x4_dc = ih264_iquant_itrans_recon_chroma_4x4_dc_a9; /* Init fn ptr luma deblocking */ ps_codec->pf_deblk_luma_vert_bs4 = ih264_deblk_luma_vert_bs4_a9; ps_codec->pf_deblk_luma_vert_bslt4 = ih264_deblk_luma_vert_bslt4_a9; ps_codec->pf_deblk_luma_vert_bs4_mbaff = ih264_deblk_luma_vert_bs4_mbaff_a9; ps_codec->pf_deblk_luma_vert_bslt4_mbaff = ih264_deblk_luma_vert_bslt4_mbaff_a9; ps_codec->pf_deblk_luma_horz_bs4 = ih264_deblk_luma_horz_bs4_a9; ps_codec->pf_deblk_luma_horz_bslt4 = ih264_deblk_luma_horz_bslt4_a9; /* Init fn ptr chroma deblocking */ ps_codec->pf_deblk_chroma_vert_bs4 = ih264_deblk_chroma_vert_bs4_a9; ps_codec->pf_deblk_chroma_vert_bslt4 = ih264_deblk_chroma_vert_bslt4_a9; ps_codec->pf_deblk_chroma_vert_bs4_mbaff = ih264_deblk_chroma_vert_bs4_mbaff_a9; ps_codec->pf_deblk_chroma_vert_bslt4_mbaff = ih264_deblk_chroma_vert_bslt4_mbaff_a9; ps_codec->pf_deblk_chroma_horz_bs4 = ih264_deblk_chroma_horz_bs4_a9; ps_codec->pf_deblk_chroma_horz_bslt4 = ih264_deblk_chroma_horz_bslt4_a9; /* Inter pred leaf level functions */ ps_codec->apf_inter_pred_luma[0] = ih264_inter_pred_luma_copy_a9q; ps_codec->apf_inter_pred_luma[1] = ih264_inter_pred_luma_horz_qpel_a9q; ps_codec->apf_inter_pred_luma[2] = ih264_inter_pred_luma_horz_a9q; ps_codec->apf_inter_pred_luma[3] = ih264_inter_pred_luma_horz_qpel_a9q; ps_codec->apf_inter_pred_luma[4] = ih264_inter_pred_luma_vert_qpel_a9q; ps_codec->apf_inter_pred_luma[5] = ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q; ps_codec->apf_inter_pred_luma[6] = ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q; ps_codec->apf_inter_pred_luma[7] = ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q; ps_codec->apf_inter_pred_luma[8] = ih264_inter_pred_luma_vert_a9q; ps_codec->apf_inter_pred_luma[9] = ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q; ps_codec->apf_inter_pred_luma[10] = ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q; ps_codec->apf_inter_pred_luma[11] = ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q; ps_codec->apf_inter_pred_luma[12] = ih264_inter_pred_luma_vert_qpel_a9q; ps_codec->apf_inter_pred_luma[13] = ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q; ps_codec->apf_inter_pred_luma[14] = ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q; ps_codec->apf_inter_pred_luma[15] = ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q; ps_codec->pf_inter_pred_chroma = ih264_inter_pred_chroma_a9q; return; } ================================================ FILE: dependencies/ih264d/decoder/arm/ih264d_function_selector_av8.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264e_function_selector_av8.c * * @brief * Contains functions to initialize function pointers of codec context * * @author * Ittiam * * @par List of Functions: * - ih264e_init_function_ptr_av8 * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System Include files */ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> /* User Include files */ #include "ih264_typedefs.h" #include "iv.h" #include "ivd.h" #include "ih264_defs.h" #include "ih264_size_defs.h" #include "ih264_error.h" #include "ih264_trans_quant_itrans_iquant.h" #include "ih264_inter_pred_filters.h" #include "ih264d_structs.h" #include "ih264d_function_selector.h" /** ******************************************************************************* * * @brief Initialize the intra/inter/transform/deblk function pointers of * codec context * * @par Description: the current routine initializes the function pointers of * codec context basing on the architecture in use * * @param[in] ps_codec * Codec context pointer * * @returns none * * @remarks none * ******************************************************************************* */ void ih264d_init_function_ptr_av8(dec_struct_t *ps_codec) { /* Init function pointers for intra pred leaf level functions luma * Intra 16x16 */ ps_codec->apf_intra_pred_luma_16x16[0] = ih264_intra_pred_luma_16x16_mode_vert_av8; ps_codec->apf_intra_pred_luma_16x16[1] = ih264_intra_pred_luma_16x16_mode_horz_av8; ps_codec->apf_intra_pred_luma_16x16[2] = ih264_intra_pred_luma_16x16_mode_dc_av8; ps_codec->apf_intra_pred_luma_16x16[3] = ih264_intra_pred_luma_16x16_mode_plane_av8; /* Init function pointers for intra pred leaf level functions luma * Intra 4x4 */ ps_codec->apf_intra_pred_luma_4x4[0] = ih264_intra_pred_luma_4x4_mode_vert_av8; ps_codec->apf_intra_pred_luma_4x4[1] = ih264_intra_pred_luma_4x4_mode_horz_av8; ps_codec->apf_intra_pred_luma_4x4[2] = ih264_intra_pred_luma_4x4_mode_dc_av8; ps_codec->apf_intra_pred_luma_4x4[3] = ih264_intra_pred_luma_4x4_mode_diag_dl_av8; ps_codec->apf_intra_pred_luma_4x4[4] = ih264_intra_pred_luma_4x4_mode_diag_dr_av8; ps_codec->apf_intra_pred_luma_4x4[5] = ih264_intra_pred_luma_4x4_mode_vert_r_av8; ps_codec->apf_intra_pred_luma_4x4[6] = ih264_intra_pred_luma_4x4_mode_horz_d_av8; ps_codec->apf_intra_pred_luma_4x4[7] = ih264_intra_pred_luma_4x4_mode_vert_l_av8; ps_codec->apf_intra_pred_luma_4x4[8] = ih264_intra_pred_luma_4x4_mode_horz_u_av8; /* Init function pointers for intra pred leaf level functions luma * Intra 8x8 */ ps_codec->apf_intra_pred_luma_8x8[0] = ih264_intra_pred_luma_8x8_mode_vert_av8; ps_codec->apf_intra_pred_luma_8x8[1] = ih264_intra_pred_luma_8x8_mode_horz_av8; ps_codec->apf_intra_pred_luma_8x8[2] = ih264_intra_pred_luma_8x8_mode_dc_av8; ps_codec->apf_intra_pred_luma_8x8[3] = ih264_intra_pred_luma_8x8_mode_diag_dl_av8; ps_codec->apf_intra_pred_luma_8x8[4] = ih264_intra_pred_luma_8x8_mode_diag_dr_av8; ps_codec->apf_intra_pred_luma_8x8[5] = ih264_intra_pred_luma_8x8_mode_vert_r_av8; ps_codec->apf_intra_pred_luma_8x8[6] = ih264_intra_pred_luma_8x8_mode_horz_d_av8; ps_codec->apf_intra_pred_luma_8x8[7] = ih264_intra_pred_luma_8x8_mode_vert_l_av8; ps_codec->apf_intra_pred_luma_8x8[8] = ih264_intra_pred_luma_8x8_mode_horz_u_av8; ps_codec->pf_intra_pred_ref_filtering = ih264_intra_pred_luma_8x8_mode_ref_filtering; /* Init function pointers for intra pred leaf level functions chroma * Intra 8x8 */ ps_codec->apf_intra_pred_chroma[0] = ih264_intra_pred_chroma_8x8_mode_vert_av8; ps_codec->apf_intra_pred_chroma[1] = ih264_intra_pred_chroma_8x8_mode_horz_av8; /* ih264_intra_pred_chroma_8x8_mode_dc_av8 does not support interlaced clips, hence using C */ ps_codec->apf_intra_pred_chroma[2] = ih264_intra_pred_chroma_8x8_mode_dc; ps_codec->apf_intra_pred_chroma[3] = ih264_intra_pred_chroma_8x8_mode_plane_av8; ps_codec->pf_default_weighted_pred_luma = ih264_default_weighted_pred_luma_av8; ps_codec->pf_default_weighted_pred_chroma = ih264_default_weighted_pred_chroma_av8; ps_codec->pf_weighted_pred_luma = ih264_weighted_pred_luma_av8; ps_codec->pf_weighted_pred_chroma = ih264_weighted_pred_chroma_av8; ps_codec->pf_weighted_bi_pred_luma = ih264_weighted_bi_pred_luma_av8; ps_codec->pf_weighted_bi_pred_chroma = ih264_weighted_bi_pred_chroma_av8; /* Padding Functions */ ps_codec->pf_pad_top = ih264_pad_top_av8; ps_codec->pf_pad_bottom = ih264_pad_bottom; ps_codec->pf_pad_left_luma = ih264_pad_left_luma_av8; ps_codec->pf_pad_left_chroma = ih264_pad_left_chroma_av8; ps_codec->pf_pad_right_luma = ih264_pad_right_luma_av8; ps_codec->pf_pad_right_chroma = ih264_pad_right_chroma_av8; ps_codec->pf_iquant_itrans_recon_luma_4x4 = ih264_iquant_itrans_recon_4x4_av8; ps_codec->pf_iquant_itrans_recon_luma_4x4_dc = ih264_iquant_itrans_recon_4x4_dc_av8; ps_codec->pf_iquant_itrans_recon_luma_8x8 = ih264_iquant_itrans_recon_8x8_av8; ps_codec->pf_iquant_itrans_recon_luma_8x8_dc = ih264_iquant_itrans_recon_8x8_dc_av8; ps_codec->pf_iquant_itrans_recon_chroma_4x4 = ih264_iquant_itrans_recon_chroma_4x4_av8; ps_codec->pf_iquant_itrans_recon_chroma_4x4_dc = ih264_iquant_itrans_recon_chroma_4x4_dc_av8; ps_codec->pf_ihadamard_scaling_4x4 = ih264_ihadamard_scaling_4x4_av8; /* Init fn ptr luma deblocking */ ps_codec->pf_deblk_luma_vert_bs4 = ih264_deblk_luma_vert_bs4_av8; ps_codec->pf_deblk_luma_vert_bslt4 = ih264_deblk_luma_vert_bslt4_av8; ps_codec->pf_deblk_luma_vert_bs4_mbaff = ih264_deblk_luma_vert_bs4_mbaff; ps_codec->pf_deblk_luma_vert_bslt4_mbaff = ih264_deblk_luma_vert_bslt4_mbaff; ps_codec->pf_deblk_luma_horz_bs4 = ih264_deblk_luma_horz_bs4_av8; ps_codec->pf_deblk_luma_horz_bslt4 = ih264_deblk_luma_horz_bslt4_av8; /* Init fn ptr chroma deblocking */ ps_codec->pf_deblk_chroma_vert_bs4 = ih264_deblk_chroma_vert_bs4_av8; ps_codec->pf_deblk_chroma_vert_bslt4 = ih264_deblk_chroma_vert_bslt4_av8; ps_codec->pf_deblk_chroma_vert_bs4_mbaff = ih264_deblk_chroma_vert_bs4_mbaff; ps_codec->pf_deblk_chroma_vert_bslt4_mbaff = ih264_deblk_chroma_vert_bslt4_mbaff; ps_codec->pf_deblk_chroma_horz_bs4 = ih264_deblk_chroma_horz_bs4_av8; ps_codec->pf_deblk_chroma_horz_bslt4 = ih264_deblk_chroma_horz_bslt4_av8; /* Inter pred leaf level functions */ ps_codec->apf_inter_pred_luma[0] = ih264_inter_pred_luma_copy_av8; ps_codec->apf_inter_pred_luma[1] = ih264_inter_pred_luma_horz_qpel_av8; ps_codec->apf_inter_pred_luma[2] = ih264_inter_pred_luma_horz_av8; ps_codec->apf_inter_pred_luma[3] = ih264_inter_pred_luma_horz_qpel_av8; ps_codec->apf_inter_pred_luma[4] = ih264_inter_pred_luma_vert_qpel_av8; ps_codec->apf_inter_pred_luma[5] = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8; ps_codec->apf_inter_pred_luma[6] = ih264_inter_pred_luma_horz_hpel_vert_qpel_av8; ps_codec->apf_inter_pred_luma[7] = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8; ps_codec->apf_inter_pred_luma[8] = ih264_inter_pred_luma_vert_av8; ps_codec->apf_inter_pred_luma[9] = ih264_inter_pred_luma_horz_qpel_vert_hpel_av8; ps_codec->apf_inter_pred_luma[10] = ih264_inter_pred_luma_horz_hpel_vert_hpel_av8; ps_codec->apf_inter_pred_luma[11] = ih264_inter_pred_luma_horz_qpel_vert_hpel_av8; ps_codec->apf_inter_pred_luma[12] = ih264_inter_pred_luma_vert_qpel_av8; ps_codec->apf_inter_pred_luma[13] = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8; ps_codec->apf_inter_pred_luma[14] = ih264_inter_pred_luma_horz_hpel_vert_qpel_av8; ps_codec->apf_inter_pred_luma[15] = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8; ps_codec->pf_inter_pred_chroma = ih264_inter_pred_chroma_av8; return; } ================================================ FILE: dependencies/ih264d/decoder/ih264d.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264d.h */ /* */ /* Description : This file contains all the necessary structure and */ /* enumeration definitions needed for the Application */ /* Program Interface(API) of the Ittiam H264 ASP */ /* Decoder on Cortex A8 - Neon platform */ /* */ /* List of Functions : ih264d_api_function */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 26 08 2010 100239(RCY) Draft */ /* */ /*****************************************************************************/ #ifndef _IH264D_H_ #define _IH264D_H_ #ifdef __cplusplus extern "C" { #endif #include "iv.h" #include "ivd.h" /*****************************************************************************/ /* Constant Macros */ /*****************************************************************************/ #define IVD_ERROR_MASK 0xFF /*****************************************************************************/ /* Function Macros */ /*****************************************************************************/ #define IS_IVD_CONCEALMENT_APPLIED(x) (x & (1 << IVD_APPLIEDCONCEALMENT)) #define IS_IVD_INSUFFICIENTDATA_ERROR(x) (x & (1 << IVD_INSUFFICIENTDATA)) #define IS_IVD_CORRUPTEDDATA_ERROR(x) (x & (1 << IVD_CORRUPTEDDATA)) #define IS_IVD_CORRUPTEDHEADER_ERROR(x) (x & (1 << IVD_CORRUPTEDHEADER)) #define IS_IVD_UNSUPPORTEDINPUT_ERROR(x) (x & (1 << IVD_UNSUPPORTEDINPUT)) #define IS_IVD_UNSUPPORTEDPARAM_ERROR(x) (x & (1 << IVD_UNSUPPORTEDPARAM)) #define IS_IVD_FATAL_ERROR(x) (x & (1 << IVD_FATALERROR)) #define IS_IVD_INVALID_BITSTREAM_ERROR(x) (x & (1 << IVD_INVALID_BITSTREAM)) #define IS_IVD_INCOMPLETE_BITSTREAM_ERROR(x) (x & (1 << IVD_INCOMPLETE_BITSTREAM)) /*****************************************************************************/ /* API Function Prototype */ /*****************************************************************************/ IV_API_CALL_STATUS_T ih264d_api_function(iv_obj_t *ps_handle, void *pv_api_ip,void *pv_api_op); /*****************************************************************************/ /* Enums */ /*****************************************************************************/ /* Codec Error codes for H264 ASP Decoder */ typedef enum { IH264D_VID_HDR_DEC_NUM_FRM_BUF_NOT_SUFFICIENT = IVD_DUMMY_ELEMENT_FOR_CODEC_EXTENSIONS + 1, }IH264D_ERROR_CODES_T; /*****************************************************************************/ /* Extended Structures */ /*****************************************************************************/ /*****************************************************************************/ /* Delete Codec */ /*****************************************************************************/ typedef struct { ivd_delete_ip_t s_ivd_delete_ip_t; }ih264d_delete_ip_t; typedef struct{ ivd_delete_op_t s_ivd_delete_op_t; }ih264d_delete_op_t; /*****************************************************************************/ /* Initialize decoder */ /*****************************************************************************/ typedef struct { ivd_create_ip_t s_ivd_create_ip_t; }ih264d_create_ip_t; typedef struct{ ivd_create_op_t s_ivd_create_op_t; }ih264d_create_op_t; /*****************************************************************************/ /* Video Decode */ /*****************************************************************************/ typedef struct { ivd_video_decode_ip_t s_ivd_video_decode_ip_t; }ih264d_video_decode_ip_t; typedef struct{ ivd_video_decode_op_t s_ivd_video_decode_op_t; }ih264d_video_decode_op_t; /*****************************************************************************/ /* Get Display Frame */ /*****************************************************************************/ typedef struct { ivd_get_display_frame_ip_t s_ivd_get_display_frame_ip_t; }ih264d_get_display_frame_ip_t; typedef struct { ivd_get_display_frame_op_t s_ivd_get_display_frame_op_t; }ih264d_get_display_frame_op_t; /*****************************************************************************/ /* Set Display Frame */ /*****************************************************************************/ typedef struct { ivd_set_display_frame_ip_t s_ivd_set_display_frame_ip_t; }ih264d_set_display_frame_ip_t; typedef struct { ivd_set_display_frame_op_t s_ivd_set_display_frame_op_t; }ih264d_set_display_frame_op_t; /*****************************************************************************/ /* Release Display Buffers */ /*****************************************************************************/ typedef struct { ivd_rel_display_frame_ip_t s_ivd_rel_display_frame_ip_t; }ih264d_rel_display_frame_ip_t; typedef struct { ivd_rel_display_frame_op_t s_ivd_rel_display_frame_op_t; }ih264d_rel_display_frame_op_t; typedef enum { /** Set number of cores/threads to be used */ IH264D_CMD_CTL_SET_NUM_CORES = IVD_CMD_CTL_CODEC_SUBCMD_START, /** Set processor details */ IH264D_CMD_CTL_SET_PROCESSOR = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x001, /** Get display buffer dimensions */ IH264D_CMD_CTL_GET_BUFFER_DIMENSIONS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x100, /** Get VUI parameters */ IH264D_CMD_CTL_GET_VUI_PARAMS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x101, /** Enable/disable GPU, supported on select platforms */ IH264D_CMD_CTL_GPU_ENABLE_DISABLE = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x200, /** Set degrade level */ IH264D_CMD_CTL_DEGRADE = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x300, /** Get SEI MDCV parameters */ IH264D_CMD_CTL_GET_SEI_MDCV_PARAMS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x301, /** Get SEI CLL parameters */ IH264D_CMD_CTL_GET_SEI_CLL_PARAMS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x302, /** Get SEI AVE parameters */ IH264D_CMD_CTL_GET_SEI_AVE_PARAMS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x303, /** Get SEI CCV parameters */ IH264D_CMD_CTL_GET_SEI_CCV_PARAMS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x304 }IH264D_CMD_CTL_SUB_CMDS; /*****************************************************************************/ /* Video control Flush */ /*****************************************************************************/ typedef struct{ ivd_ctl_flush_ip_t s_ivd_ctl_flush_ip_t; }ih264d_ctl_flush_ip_t; typedef struct{ ivd_ctl_flush_op_t s_ivd_ctl_flush_op_t; }ih264d_ctl_flush_op_t; /*****************************************************************************/ /* Video control reset */ /*****************************************************************************/ typedef struct{ ivd_ctl_reset_ip_t s_ivd_ctl_reset_ip_t; }ih264d_ctl_reset_ip_t; typedef struct{ ivd_ctl_reset_op_t s_ivd_ctl_reset_op_t; }ih264d_ctl_reset_op_t; /*****************************************************************************/ /* Video control Set Params */ /*****************************************************************************/ typedef struct { ivd_ctl_set_config_ip_t s_ivd_ctl_set_config_ip_t; }ih264d_ctl_set_config_ip_t; typedef struct{ ivd_ctl_set_config_op_t s_ivd_ctl_set_config_op_t; }ih264d_ctl_set_config_op_t; /*****************************************************************************/ /* Video control:Get Buf Info */ /*****************************************************************************/ typedef struct{ ivd_ctl_getbufinfo_ip_t s_ivd_ctl_getbufinfo_ip_t; }ih264d_ctl_getbufinfo_ip_t; typedef struct{ ivd_ctl_getbufinfo_op_t s_ivd_ctl_getbufinfo_op_t; }ih264d_ctl_getbufinfo_op_t; /*****************************************************************************/ /* Video control:Getstatus Call */ /*****************************************************************************/ typedef struct{ ivd_ctl_getstatus_ip_t s_ivd_ctl_getstatus_ip_t; }ih264d_ctl_getstatus_ip_t; typedef struct{ ivd_ctl_getstatus_op_t s_ivd_ctl_getstatus_op_t; }ih264d_ctl_getstatus_op_t; /*****************************************************************************/ /* Video control:Get Version Info */ /*****************************************************************************/ typedef struct{ ivd_ctl_getversioninfo_ip_t s_ivd_ctl_getversioninfo_ip_t; }ih264d_ctl_getversioninfo_ip_t; typedef struct{ ivd_ctl_getversioninfo_op_t s_ivd_ctl_getversioninfo_op_t; }ih264d_ctl_getversioninfo_op_t; typedef struct{ /** * u4_size */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; /** * Pictures that are are degraded * 0 : No degrade * 1 : Only on non-reference frames * 2 : Use interval specified by u4_nondegrade_interval * 3 : All non-key frames * 4 : All frames */ WORD32 i4_degrade_pics; /** * Interval for pictures which are completely decoded without any degradation */ WORD32 i4_nondegrade_interval; /** * bit position (lsb is zero): Type of degradation * 1 : Disable deblocking * 2 : Faster inter prediction filters * 3 : Fastest inter prediction filters */ WORD32 i4_degrade_type; }ih264d_ctl_degrade_ip_t; typedef struct { /** * u4_size */ UWORD32 u4_size; /** * error_code */ UWORD32 u4_error_code; }ih264d_ctl_degrade_op_t; typedef struct{ UWORD32 u4_size; IVD_API_COMMAND_TYPE_T e_cmd; IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; UWORD32 u4_disable_deblk_level; }ih264d_ctl_disable_deblock_ip_t; typedef struct{ UWORD32 u4_size; UWORD32 u4_error_code; }ih264d_ctl_disable_deblock_op_t; typedef struct{ UWORD32 u4_size; IVD_API_COMMAND_TYPE_T e_cmd; IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; UWORD32 u4_num_cores; }ih264d_ctl_set_num_cores_ip_t; typedef struct{ UWORD32 u4_size; UWORD32 u4_error_code; }ih264d_ctl_set_num_cores_op_t; typedef struct { /** * i4_size */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; /** * Processor type */ UWORD32 u4_arch; /** * SOC type */ UWORD32 u4_soc; /** * num_cores */ UWORD32 u4_num_cores; }ih264d_ctl_set_processor_ip_t; typedef struct { /** * i4_size */ UWORD32 u4_size; /** * error_code */ UWORD32 u4_error_code; }ih264d_ctl_set_processor_op_t; typedef struct{ UWORD32 u4_size; IVD_API_COMMAND_TYPE_T e_cmd; IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; }ih264d_ctl_get_frame_dimensions_ip_t; typedef struct{ UWORD32 u4_size; UWORD32 u4_error_code; UWORD32 u4_x_offset[3]; UWORD32 u4_y_offset[3]; UWORD32 u4_disp_wd[3]; UWORD32 u4_disp_ht[3]; UWORD32 u4_buffer_wd[3]; UWORD32 u4_buffer_ht[3]; }ih264d_ctl_get_frame_dimensions_op_t; typedef struct { UWORD32 u4_size; IVD_API_COMMAND_TYPE_T e_cmd; IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; }ih264d_ctl_get_vui_params_ip_t; typedef struct { UWORD32 u4_size; UWORD32 u4_error_code; UWORD8 u1_aspect_ratio_idc; UWORD16 u2_sar_width; UWORD16 u2_sar_height; UWORD8 u1_overscan_appropriate_flag; UWORD8 u1_video_format; UWORD8 u1_video_full_range_flag; UWORD8 u1_colour_primaries; UWORD8 u1_tfr_chars; UWORD8 u1_matrix_coeffs; UWORD8 u1_cr_top_field; UWORD8 u1_cr_bottom_field; UWORD32 u4_num_units_in_tick; UWORD32 u4_time_scale; UWORD8 u1_fixed_frame_rate_flag; UWORD8 u1_nal_hrd_params_present; UWORD8 u1_vcl_hrd_params_present; UWORD8 u1_low_delay_hrd_flag; UWORD8 u1_pic_struct_present_flag; UWORD8 u1_bitstream_restriction_flag; UWORD8 u1_mv_over_pic_boundaries_flag; UWORD32 u4_max_bytes_per_pic_denom; UWORD32 u4_max_bits_per_mb_denom; UWORD32 u4_log2_max_mv_length_horz; UWORD32 u4_log2_max_mv_length_vert; UWORD32 u4_num_reorder_frames; UWORD32 u4_max_dec_frame_buffering; }ih264d_ctl_get_vui_params_op_t; typedef struct { /** * u4_size */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; }ih264d_ctl_get_sei_mdcv_params_ip_t; typedef struct { /** * u4_size */ UWORD32 u4_size; /** * error_code */ UWORD32 u4_error_code; /** * Array to store the display_primaries_x values */ UWORD16 au2_display_primaries_x[NUM_SEI_MDCV_PRIMARIES]; /** * Array to store the display_primaries_y values */ UWORD16 au2_display_primaries_y[NUM_SEI_MDCV_PRIMARIES]; /** * Variable to store the white point x value */ UWORD16 u2_white_point_x; /** * Variable to store the white point y value */ UWORD16 u2_white_point_y; /** * Variable to store the max display mastering luminance value */ UWORD32 u4_max_display_mastering_luminance; /** * Variable to store the min display mastering luminance value */ UWORD32 u4_min_display_mastering_luminance; }ih264d_ctl_get_sei_mdcv_params_op_t; typedef struct { /** * u4_size */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; }ih264d_ctl_get_sei_cll_params_ip_t; typedef struct { /** * u4_size */ UWORD32 u4_size; /** * error_code */ UWORD32 u4_error_code; /** * The maximum pixel intensity of all samples */ UWORD16 u2_max_content_light_level; /** * The average pixel intensity of all samples */ UWORD16 u2_max_pic_average_light_level; } ih264d_ctl_get_sei_cll_params_op_t; typedef struct { /** * u4_size */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; }ih264d_ctl_get_sei_ave_params_ip_t; typedef struct { /** * u4_size */ UWORD32 u4_size; /** * error_code */ UWORD32 u4_error_code; /** * specifies the environmental illluminance of the ambient viewing environment */ UWORD32 u4_ambient_illuminance; /* * specify the normalized x chromaticity coordinates of the * environmental ambient light in the nominal viewing environment */ UWORD16 u2_ambient_light_x; /* * specify the normalized y chromaticity coordinates of the * environmental ambient light in the nominal viewing environment */ UWORD16 u2_ambient_light_y; } ih264d_ctl_get_sei_ave_params_op_t; typedef struct { /** * u4_size */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; }ih264d_ctl_get_sei_ccv_params_ip_t; typedef struct { /** * u4_size */ UWORD32 u4_size; /** * error_code */ UWORD32 u4_error_code; /* * Flag used to control persistence of CCV SEI messages */ UWORD8 u1_ccv_cancel_flag; /* * specifies the persistence of the CCV SEI message for the current layer */ UWORD8 u1_ccv_persistence_flag; /* * specifies the presence of syntax elements ccv_primaries_x and ccv_primaries_y */ UWORD8 u1_ccv_primaries_present_flag; /* * specifies that the syntax element ccv_min_luminance_value is present */ UWORD8 u1_ccv_min_luminance_value_present_flag; /* * specifies that the syntax element ccv_max_luminance_value is present */ UWORD8 u1_ccv_max_luminance_value_present_flag; /* * specifies that the syntax element ccv_avg_luminance_value is present */ UWORD8 u1_ccv_avg_luminance_value_present_flag; /* * shall be equal to 0 in bitstreams conforming to this version. Other values * for reserved_zero_2bits are reserved for future use */ UWORD8 u1_ccv_reserved_zero_2bits; /* * specify the normalized x chromaticity coordinates of the colour * primary component c of the nominal content colour volume */ WORD32 ai4_ccv_primaries_x[NUM_SEI_CCV_PRIMARIES]; /* * specify the normalized y chromaticity coordinates of the colour * primary component c of the nominal content colour volume */ WORD32 ai4_ccv_primaries_y[NUM_SEI_CCV_PRIMARIES]; /* * specifies the normalized minimum luminance value */ UWORD32 u4_ccv_min_luminance_value; /* * specifies the normalized maximum luminance value */ UWORD32 u4_ccv_max_luminance_value; /* * specifies the normalized average luminance value */ UWORD32 u4_ccv_avg_luminance_value; } ih264d_ctl_get_sei_ccv_params_op_t; #ifdef __cplusplus } /* closing brace for extern "C" */ #endif #endif /* _IH264D_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_api.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore * Modified for use with Cemu emulator project */ /*****************************************************************************/ /* */ /* File Name : ih264d_api.c */ /* */ /* Description : Has all API related functions */ /* */ /* */ /* List of Functions : api_check_struct_sanity */ /* ih264d_set_processor */ /* ih264d_create */ /* ih264d_delete */ /* ih264d_init */ /* ih264d_map_error */ /* ih264d_video_decode */ /* ih264d_get_version */ /* ih264d_get_display_frame */ /* ih264d_set_display_frame */ /* ih264d_set_flush_mode */ /* ih264d_get_status */ /* ih264d_get_buf_info */ /* ih264d_set_params */ /* ih264d_set_default_params */ /* ih264d_reset */ /* ih264d_ctl */ /* ih264d_rel_display_frame */ /* ih264d_set_degrade */ /* ih264d_get_frame_dimensions */ /* ih264d_set_num_cores */ /* ih264d_fill_output_struct_from_context */ /* ih264d_api_function */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 14 10 2008 100356(SKV) Draft */ /* */ /*****************************************************************************/ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_tables.h" #include "iv.h" #include "ivd.h" #include "ih264d.h" #include "ih264d_defs.h" #include <string.h> #include <limits.h> #include <stddef.h> #include "ih264d_inter_pred.h" #include "ih264d_structs.h" #include "ih264d_nal.h" #include "ih264d_error_handler.h" #include "ih264d_defs.h" #include "ithread.h" #include "ih264d_parse_slice.h" #include "ih264d_function_selector.h" #include "ih264_error.h" #include "ih264_disp_mgr.h" #include "ih264_buf_mgr.h" #include "ih264d_deblocking.h" #include "ih264d_parse_cavlc.h" #include "ih264d_parse_cabac.h" #include "ih264d_utils.h" #include "ih264d_format_conv.h" #include "ih264d_parse_headers.h" #include "ih264d_thread_compute_bs.h" #include <assert.h> /*********************/ /* Codec Versioning */ /*********************/ //Move this to where it is used #define CODEC_NAME "H264VDEC" #define CODEC_RELEASE_TYPE "production" #define CODEC_RELEASE_VER "05.00" #define CODEC_VENDOR "ITTIAM" #define MAXVERSION_STRLEN 511 #ifdef ANDROID #define VERSION(version_string, codec_name, codec_release_type, codec_release_ver, codec_vendor) \ snprintf(version_string, MAXVERSION_STRLEN, \ "@(#)Id:%s_%s Ver:%s Released by %s", \ codec_name, codec_release_type, codec_release_ver, codec_vendor) #else #define VERSION(version_string, codec_name, codec_release_type, codec_release_ver, codec_vendor) \ snprintf(version_string, MAXVERSION_STRLEN, \ "@(#)Id:%s_%s Ver:%s Released by %s Build: %s @ %s", \ codec_name, codec_release_type, codec_release_ver, codec_vendor, __DATE__, __TIME__) #endif #define MIN_IN_BUFS 1 #define MIN_OUT_BUFS_420 3 #define MIN_OUT_BUFS_422ILE 1 #define MIN_OUT_BUFS_RGB565 1 #define MIN_OUT_BUFS_420SP 2 #define NUM_FRAMES_LIMIT_ENABLED 0 #if NUM_FRAMES_LIMIT_ENABLED #define NUM_FRAMES_LIMIT 10000 #else #define NUM_FRAMES_LIMIT 0x7FFFFFFF #endif UWORD32 ih264d_get_extra_mem_external(UWORD32 width, UWORD32 height); WORD32 ih264d_get_frame_dimensions(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op); WORD32 ih264d_get_vui_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op); WORD32 ih264d_get_sei_mdcv_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op); WORD32 ih264d_get_sei_cll_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op); WORD32 ih264d_get_sei_ave_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op); WORD32 ih264d_get_sei_ccv_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op); WORD32 ih264d_set_num_cores(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op); WORD32 ih264d_deblock_display(dec_struct_t *ps_dec); void ih264d_signal_decode_thread(dec_struct_t *ps_dec); void ih264d_signal_bs_deblk_thread(dec_struct_t *ps_dec); void ih264d_decode_picture_thread(dec_struct_t *ps_dec); WORD32 ih264d_set_degrade(iv_obj_t *ps_codec_obj, void *pv_api_ip, void *pv_api_op); void ih264d_fill_output_struct_from_context(dec_struct_t *ps_dec, ivd_video_decode_op_t *ps_dec_op); /*! ************************************************************************** * \if Function name : ih264d_export_sei_params \endif * * \brief * Exports sei params from decoder to application. * * \return * 0 on Success and error code otherwise ************************************************************************** */ void ih264d_export_sei_params(ivd_sei_decode_op_t *ps_sei_decode_op, dec_struct_t *ps_dec) { WORD32 i4_status = IV_SUCCESS; sei *ps_sei = (sei *)ps_dec->pv_disp_sei_params; i4_status = ih264d_export_sei_mdcv_params(ps_sei_decode_op, ps_sei, &ps_dec->s_sei_export); i4_status = ih264d_export_sei_cll_params(ps_sei_decode_op, ps_sei, &ps_dec->s_sei_export); i4_status = ih264d_export_sei_ave_params(ps_sei_decode_op, ps_sei, &ps_dec->s_sei_export); i4_status = ih264d_export_sei_ccv_params(ps_sei_decode_op, ps_sei, &ps_dec->s_sei_export); UNUSED(i4_status); } static IV_API_CALL_STATUS_T api_check_struct_sanity(iv_obj_t *ps_handle, void *pv_api_ip, void *pv_api_op) { IVD_API_COMMAND_TYPE_T e_cmd; UWORD32 *pu4_api_ip; UWORD32 *pu4_api_op; UWORD32 i, j; if(NULL == pv_api_op) return (IV_FAIL); if(NULL == pv_api_ip) return (IV_FAIL); pu4_api_ip = (UWORD32 *)pv_api_ip; pu4_api_op = (UWORD32 *)pv_api_op; e_cmd = *(pu4_api_ip + 1); /* error checks on handle */ switch((WORD32)e_cmd) { case IVD_CMD_CREATE: break; case IVD_CMD_REL_DISPLAY_FRAME: case IVD_CMD_SET_DISPLAY_FRAME: case IVD_CMD_GET_DISPLAY_FRAME: case IVD_CMD_VIDEO_DECODE: case IVD_CMD_DELETE: case IVD_CMD_VIDEO_CTL: if(ps_handle == NULL) { *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; *(pu4_api_op + 1) |= IVD_HANDLE_NULL; return IV_FAIL; } if(ps_handle->u4_size != sizeof(iv_obj_t)) { *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; *(pu4_api_op + 1) |= IVD_HANDLE_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_handle->pv_fxns != ih264d_api_function) { *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; *(pu4_api_op + 1) |= IVD_INVALID_HANDLE_NULL; return IV_FAIL; } if(ps_handle->pv_codec_handle == NULL) { *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; *(pu4_api_op + 1) |= IVD_INVALID_HANDLE_NULL; return IV_FAIL; } break; default: *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; *(pu4_api_op + 1) |= IVD_INVALID_API_CMD; return IV_FAIL; } switch((WORD32)e_cmd) { case IVD_CMD_CREATE: { ih264d_create_ip_t *ps_ip = (ih264d_create_ip_t *)pv_api_ip; ih264d_create_op_t *ps_op = (ih264d_create_op_t *)pv_api_op; ps_op->s_ivd_create_op_t.u4_error_code = 0; if((ps_ip->s_ivd_create_ip_t.u4_size > sizeof(ih264d_create_ip_t)) || (ps_ip->s_ivd_create_ip_t.u4_size < sizeof(ivd_create_ip_t))) { ps_op->s_ivd_create_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_create_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; H264_DEC_DEBUG_PRINT("\n"); return (IV_FAIL); } if((ps_op->s_ivd_create_op_t.u4_size != sizeof(ih264d_create_op_t)) && (ps_op->s_ivd_create_op_t.u4_size != sizeof(ivd_create_op_t))) { ps_op->s_ivd_create_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_create_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; H264_DEC_DEBUG_PRINT("\n"); return (IV_FAIL); } if((ps_ip->s_ivd_create_ip_t.e_output_format != IV_YUV_420P) && (ps_ip->s_ivd_create_ip_t.e_output_format != IV_YUV_422ILE) && (ps_ip->s_ivd_create_ip_t.e_output_format != IV_RGB_565) && (ps_ip->s_ivd_create_ip_t.e_output_format != IV_YUV_420SP_UV) && (ps_ip->s_ivd_create_ip_t.e_output_format != IV_YUV_420SP_VU)) { ps_op->s_ivd_create_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_create_op_t.u4_error_code |= IVD_INIT_DEC_COL_FMT_NOT_SUPPORTED; H264_DEC_DEBUG_PRINT("\n"); return (IV_FAIL); } } break; case IVD_CMD_GET_DISPLAY_FRAME: { ih264d_get_display_frame_ip_t *ps_ip = (ih264d_get_display_frame_ip_t *)pv_api_ip; ih264d_get_display_frame_op_t *ps_op = (ih264d_get_display_frame_op_t *)pv_api_op; ps_op->s_ivd_get_display_frame_op_t.u4_error_code = 0; if((ps_ip->s_ivd_get_display_frame_ip_t.u4_size != sizeof(ih264d_get_display_frame_ip_t)) && (ps_ip->s_ivd_get_display_frame_ip_t.u4_size != sizeof(ivd_get_display_frame_ip_t))) { ps_op->s_ivd_get_display_frame_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_get_display_frame_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return (IV_FAIL); } if((ps_op->s_ivd_get_display_frame_op_t.u4_size != sizeof(ih264d_get_display_frame_op_t)) && (ps_op->s_ivd_get_display_frame_op_t.u4_size != sizeof(ivd_get_display_frame_op_t))) { ps_op->s_ivd_get_display_frame_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_get_display_frame_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return (IV_FAIL); } } break; case IVD_CMD_REL_DISPLAY_FRAME: { ih264d_rel_display_frame_ip_t *ps_ip = (ih264d_rel_display_frame_ip_t *)pv_api_ip; ih264d_rel_display_frame_op_t *ps_op = (ih264d_rel_display_frame_op_t *)pv_api_op; ps_op->s_ivd_rel_display_frame_op_t.u4_error_code = 0; if((ps_ip->s_ivd_rel_display_frame_ip_t.u4_size != sizeof(ih264d_rel_display_frame_ip_t)) && (ps_ip->s_ivd_rel_display_frame_ip_t.u4_size != sizeof(ivd_rel_display_frame_ip_t))) { ps_op->s_ivd_rel_display_frame_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_rel_display_frame_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return (IV_FAIL); } if((ps_op->s_ivd_rel_display_frame_op_t.u4_size != sizeof(ih264d_rel_display_frame_op_t)) && (ps_op->s_ivd_rel_display_frame_op_t.u4_size != sizeof(ivd_rel_display_frame_op_t))) { ps_op->s_ivd_rel_display_frame_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_rel_display_frame_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return (IV_FAIL); } } break; case IVD_CMD_SET_DISPLAY_FRAME: { ih264d_set_display_frame_ip_t *ps_ip = (ih264d_set_display_frame_ip_t *)pv_api_ip; ih264d_set_display_frame_op_t *ps_op = (ih264d_set_display_frame_op_t *)pv_api_op; UWORD32 j; ps_op->s_ivd_set_display_frame_op_t.u4_error_code = 0; if((ps_ip->s_ivd_set_display_frame_ip_t.u4_size != sizeof(ih264d_set_display_frame_ip_t)) && (ps_ip->s_ivd_set_display_frame_ip_t.u4_size != sizeof(ivd_set_display_frame_ip_t))) { ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return (IV_FAIL); } if((ps_op->s_ivd_set_display_frame_op_t.u4_size != sizeof(ih264d_set_display_frame_op_t)) && (ps_op->s_ivd_set_display_frame_op_t.u4_size != sizeof(ivd_set_display_frame_op_t))) { ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return (IV_FAIL); } if(ps_ip->s_ivd_set_display_frame_ip_t.num_disp_bufs == 0) { ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= IVD_DISP_FRM_ZERO_OP_BUFS; return IV_FAIL; } for(j = 0; j < ps_ip->s_ivd_set_display_frame_ip_t.num_disp_bufs; j++) { if(ps_ip->s_ivd_set_display_frame_ip_t.s_disp_buffer[j].u4_num_bufs == 0) { ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= IVD_DISP_FRM_ZERO_OP_BUFS; return IV_FAIL; } for(i = 0; i < ps_ip->s_ivd_set_display_frame_ip_t.s_disp_buffer[j].u4_num_bufs; i++) { if(ps_ip->s_ivd_set_display_frame_ip_t.s_disp_buffer[j].pu1_bufs[i] == NULL) { ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= IVD_DISP_FRM_OP_BUF_NULL; return IV_FAIL; } if(ps_ip->s_ivd_set_display_frame_ip_t.s_disp_buffer[j].u4_min_out_buf_size[i] == 0) { ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= IVD_DISP_FRM_ZERO_OP_BUF_SIZE; return IV_FAIL; } } } } break; case IVD_CMD_VIDEO_DECODE: { ih264d_video_decode_ip_t *ps_ip = (ih264d_video_decode_ip_t *)pv_api_ip; ih264d_video_decode_op_t *ps_op = (ih264d_video_decode_op_t *)pv_api_op; H264_DEC_DEBUG_PRINT("The input bytes is: %d", ps_ip->s_ivd_video_decode_ip_t.u4_num_Bytes); ps_op->s_ivd_video_decode_op_t.u4_error_code = 0; if(ps_ip->s_ivd_video_decode_ip_t.u4_size != sizeof(ih264d_video_decode_ip_t)&& ps_ip->s_ivd_video_decode_ip_t.u4_size != offsetof(ivd_video_decode_ip_t, s_out_buffer)) { ps_op->s_ivd_video_decode_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_video_decode_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return (IV_FAIL); } if(ps_op->s_ivd_video_decode_op_t.u4_size != sizeof(ih264d_video_decode_op_t)&& ps_op->s_ivd_video_decode_op_t.u4_size != offsetof(ivd_video_decode_op_t, u4_output_present)) { ps_op->s_ivd_video_decode_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_video_decode_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return (IV_FAIL); } } break; case IVD_CMD_DELETE: { ih264d_delete_ip_t *ps_ip = (ih264d_delete_ip_t *)pv_api_ip; ih264d_delete_op_t *ps_op = (ih264d_delete_op_t *)pv_api_op; ps_op->s_ivd_delete_op_t.u4_error_code = 0; if(ps_ip->s_ivd_delete_ip_t.u4_size != sizeof(ih264d_delete_ip_t)) { ps_op->s_ivd_delete_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_delete_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return (IV_FAIL); } if(ps_op->s_ivd_delete_op_t.u4_size != sizeof(ih264d_delete_op_t)) { ps_op->s_ivd_delete_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_delete_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return (IV_FAIL); } } break; case IVD_CMD_VIDEO_CTL: { UWORD32 *pu4_ptr_cmd; UWORD32 sub_command; pu4_ptr_cmd = (UWORD32 *)pv_api_ip; pu4_ptr_cmd += 2; sub_command = *pu4_ptr_cmd; switch(sub_command) { case IVD_CMD_CTL_SETPARAMS: { ih264d_ctl_set_config_ip_t *ps_ip; ih264d_ctl_set_config_op_t *ps_op; ps_ip = (ih264d_ctl_set_config_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_set_config_op_t *)pv_api_op; if(ps_ip->s_ivd_ctl_set_config_ip_t.u4_size != sizeof(ih264d_ctl_set_config_ip_t)) { ps_op->s_ivd_ctl_set_config_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_set_config_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } } //no break; is needed here case IVD_CMD_CTL_SETDEFAULT: { ih264d_ctl_set_config_op_t *ps_op; ps_op = (ih264d_ctl_set_config_op_t *)pv_api_op; if(ps_op->s_ivd_ctl_set_config_op_t.u4_size != sizeof(ih264d_ctl_set_config_op_t)) { ps_op->s_ivd_ctl_set_config_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_set_config_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } } break; case IVD_CMD_CTL_GETPARAMS: { ih264d_ctl_getstatus_ip_t *ps_ip; ih264d_ctl_getstatus_op_t *ps_op; ps_ip = (ih264d_ctl_getstatus_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_getstatus_op_t *)pv_api_op; if(ps_ip->s_ivd_ctl_getstatus_ip_t.u4_size != sizeof(ih264d_ctl_getstatus_ip_t)) { ps_op->s_ivd_ctl_getstatus_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_getstatus_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->s_ivd_ctl_getstatus_op_t.u4_size != sizeof(ih264d_ctl_getstatus_op_t)) { ps_op->s_ivd_ctl_getstatus_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_getstatus_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } } break; case IVD_CMD_CTL_GETBUFINFO: { ih264d_ctl_getbufinfo_ip_t *ps_ip; ih264d_ctl_getbufinfo_op_t *ps_op; ps_ip = (ih264d_ctl_getbufinfo_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_getbufinfo_op_t *)pv_api_op; if(ps_ip->s_ivd_ctl_getbufinfo_ip_t.u4_size != sizeof(ih264d_ctl_getbufinfo_ip_t)) { ps_op->s_ivd_ctl_getbufinfo_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_getbufinfo_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->s_ivd_ctl_getbufinfo_op_t.u4_size != sizeof(ih264d_ctl_getbufinfo_op_t)) { ps_op->s_ivd_ctl_getbufinfo_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_getbufinfo_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } } break; case IVD_CMD_CTL_GETVERSION: { ih264d_ctl_getversioninfo_ip_t *ps_ip; ih264d_ctl_getversioninfo_op_t *ps_op; ps_ip = (ih264d_ctl_getversioninfo_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_getversioninfo_op_t *)pv_api_op; if(ps_ip->s_ivd_ctl_getversioninfo_ip_t.u4_size != sizeof(ih264d_ctl_getversioninfo_ip_t)) { ps_op->s_ivd_ctl_getversioninfo_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_getversioninfo_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->s_ivd_ctl_getversioninfo_op_t.u4_size != sizeof(ih264d_ctl_getversioninfo_op_t)) { ps_op->s_ivd_ctl_getversioninfo_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_getversioninfo_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } } break; case IVD_CMD_CTL_FLUSH: { ih264d_ctl_flush_ip_t *ps_ip; ih264d_ctl_flush_op_t *ps_op; ps_ip = (ih264d_ctl_flush_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_flush_op_t *)pv_api_op; if(ps_ip->s_ivd_ctl_flush_ip_t.u4_size != sizeof(ih264d_ctl_flush_ip_t)) { ps_op->s_ivd_ctl_flush_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_flush_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->s_ivd_ctl_flush_op_t.u4_size != sizeof(ih264d_ctl_flush_op_t)) { ps_op->s_ivd_ctl_flush_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_flush_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } } break; case IVD_CMD_CTL_RESET: { ih264d_ctl_reset_ip_t *ps_ip; ih264d_ctl_reset_op_t *ps_op; ps_ip = (ih264d_ctl_reset_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_reset_op_t *)pv_api_op; if(ps_ip->s_ivd_ctl_reset_ip_t.u4_size != sizeof(ih264d_ctl_reset_ip_t)) { ps_op->s_ivd_ctl_reset_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_reset_op_t.u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->s_ivd_ctl_reset_op_t.u4_size != sizeof(ih264d_ctl_reset_op_t)) { ps_op->s_ivd_ctl_reset_op_t.u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->s_ivd_ctl_reset_op_t.u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } } break; case IH264D_CMD_CTL_DEGRADE: { ih264d_ctl_degrade_ip_t *ps_ip; ih264d_ctl_degrade_op_t *ps_op; ps_ip = (ih264d_ctl_degrade_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_degrade_op_t *)pv_api_op; if(ps_ip->u4_size != sizeof(ih264d_ctl_degrade_ip_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->u4_size != sizeof(ih264d_ctl_degrade_op_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if((ps_ip->i4_degrade_pics < 0) || (ps_ip->i4_degrade_pics > 4) || (ps_ip->i4_nondegrade_interval < 0) || (ps_ip->i4_degrade_type < 0) || (ps_ip->i4_degrade_type > 15)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; return IV_FAIL; } break; } case IH264D_CMD_CTL_GET_BUFFER_DIMENSIONS: { ih264d_ctl_get_frame_dimensions_ip_t *ps_ip; ih264d_ctl_get_frame_dimensions_op_t *ps_op; ps_ip = (ih264d_ctl_get_frame_dimensions_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_frame_dimensions_op_t *)pv_api_op; if(ps_ip->u4_size != sizeof(ih264d_ctl_get_frame_dimensions_ip_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->u4_size != sizeof(ih264d_ctl_get_frame_dimensions_op_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } break; } case IH264D_CMD_CTL_GET_VUI_PARAMS: { ih264d_ctl_get_vui_params_ip_t *ps_ip; ih264d_ctl_get_vui_params_op_t *ps_op; ps_ip = (ih264d_ctl_get_vui_params_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_vui_params_op_t *)pv_api_op; if(ps_ip->u4_size != sizeof(ih264d_ctl_get_vui_params_ip_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->u4_size != sizeof(ih264d_ctl_get_vui_params_op_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } break; } case IH264D_CMD_CTL_GET_SEI_MDCV_PARAMS: { ih264d_ctl_get_sei_mdcv_params_ip_t *ps_ip; ih264d_ctl_get_sei_mdcv_params_op_t *ps_op; ps_ip = (ih264d_ctl_get_sei_mdcv_params_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_sei_mdcv_params_op_t *)pv_api_op; if(ps_ip->u4_size != sizeof(ih264d_ctl_get_sei_mdcv_params_ip_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->u4_size != sizeof(ih264d_ctl_get_sei_mdcv_params_op_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } break; } case IH264D_CMD_CTL_GET_SEI_CLL_PARAMS: { ih264d_ctl_get_sei_cll_params_ip_t *ps_ip; ih264d_ctl_get_sei_cll_params_op_t *ps_op; ps_ip = (ih264d_ctl_get_sei_cll_params_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_sei_cll_params_op_t *)pv_api_op; if(ps_ip->u4_size != sizeof(ih264d_ctl_get_sei_cll_params_ip_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->u4_size != sizeof(ih264d_ctl_get_sei_cll_params_op_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } break; } case IH264D_CMD_CTL_GET_SEI_AVE_PARAMS: { ih264d_ctl_get_sei_ave_params_ip_t *ps_ip; ih264d_ctl_get_sei_ave_params_op_t *ps_op; ps_ip = (ih264d_ctl_get_sei_ave_params_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_sei_ave_params_op_t *)pv_api_op; if(ps_ip->u4_size != sizeof(ih264d_ctl_get_sei_ave_params_ip_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->u4_size != sizeof(ih264d_ctl_get_sei_ave_params_op_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } break; } case IH264D_CMD_CTL_GET_SEI_CCV_PARAMS: { ih264d_ctl_get_sei_ccv_params_ip_t *ps_ip; ih264d_ctl_get_sei_ccv_params_op_t *ps_op; ps_ip = (ih264d_ctl_get_sei_ccv_params_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_sei_ccv_params_op_t *)pv_api_op; if(ps_ip->u4_size != sizeof(ih264d_ctl_get_sei_ccv_params_ip_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->u4_size != sizeof(ih264d_ctl_get_sei_ccv_params_op_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } break; } case IH264D_CMD_CTL_SET_NUM_CORES: { ih264d_ctl_set_num_cores_ip_t *ps_ip; ih264d_ctl_set_num_cores_op_t *ps_op; ps_ip = (ih264d_ctl_set_num_cores_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_set_num_cores_op_t *)pv_api_op; if(ps_ip->u4_size != sizeof(ih264d_ctl_set_num_cores_ip_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->u4_size != sizeof(ih264d_ctl_set_num_cores_op_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if((ps_ip->u4_num_cores != 1) && (ps_ip->u4_num_cores != 2) && (ps_ip->u4_num_cores != 3) && (ps_ip->u4_num_cores != 4)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; return IV_FAIL; } break; } case IH264D_CMD_CTL_SET_PROCESSOR: { ih264d_ctl_set_processor_ip_t *ps_ip; ih264d_ctl_set_processor_op_t *ps_op; ps_ip = (ih264d_ctl_set_processor_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_set_processor_op_t *)pv_api_op; if(ps_ip->u4_size != sizeof(ih264d_ctl_set_processor_ip_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_IP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } if(ps_op->u4_size != sizeof(ih264d_ctl_set_processor_op_t)) { ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_op->u4_error_code |= IVD_OP_API_STRUCT_SIZE_INCORRECT; return IV_FAIL; } break; } default: *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; *(pu4_api_op + 1) |= IVD_UNSUPPORTED_API_CMD; return IV_FAIL; break; } } break; } return IV_SUCCESS; } /** ******************************************************************************* * * @brief * Sets Processor type * * @par Description: * Sets Processor type * * @param[in] ps_codec_obj * Pointer to codec object at API level * * @param[in] pv_api_ip * Pointer to input argument structure * * @param[out] pv_api_op * Pointer to output argument structure * * @returns Status * * @remarks * * ******************************************************************************* */ WORD32 ih264d_set_processor(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ih264d_ctl_set_processor_ip_t *ps_ip; ih264d_ctl_set_processor_op_t *ps_op; dec_struct_t *ps_codec = (dec_struct_t *)dec_hdl->pv_codec_handle; ps_ip = (ih264d_ctl_set_processor_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_set_processor_op_t *)pv_api_op; ps_codec->e_processor_arch = (IVD_ARCH_T)ps_ip->u4_arch; ps_codec->e_processor_soc = (IVD_SOC_T)ps_ip->u4_soc; ih264d_init_function_ptr(ps_codec); ps_op->u4_error_code = 0; return IV_SUCCESS; } /************************************************************************** * \if Function name : ih264d_init_decoder \endif * * * \brief * Initializes the decoder * * \param apiVersion : Version of the api being used. * \param errorHandlingMechanism : Mechanism to be used for errror handling. * \param postFilteringType: Type of post filtering operation to be used. * \param uc_outputFormat: Format of the decoded picture [default 4:2:0]. * \param uc_dispBufs: Number of Display Buffers. * \param p_NALBufAPI: Pointer to NAL Buffer API. * \param p_DispBufAPI: Pointer to Display Buffer API. * \param ih264d_dec_mem_manager :Pointer to the function that will be called by decoder * for memory allocation and freeing. * * \return * 0 on Success and -1 on error * ************************************************************************** */ void ih264d_init_decoder(void * ps_dec_params) { dec_struct_t * ps_dec = (dec_struct_t *)ps_dec_params; dec_slice_params_t *ps_cur_slice; pocstruct_t *ps_prev_poc, *ps_cur_poc; WORD32 size; size = sizeof(pred_info_t) * 2 * 32; memset(ps_dec->ps_pred, 0 , size); size = sizeof(disp_mgr_t); memset(ps_dec->pv_disp_buf_mgr, 0 , size); size = sizeof(buf_mgr_t) + ithread_get_mutex_lock_size(); memset(ps_dec->pv_pic_buf_mgr, 0, size); size = sizeof(dec_err_status_t); memset(ps_dec->ps_dec_err_status, 0, size); size = sizeof(sei); memset(ps_dec->ps_sei, 0, size); size = sizeof(sei); memset(ps_dec->ps_sei_parse, 0, size); size = sizeof(dpb_commands_t); memset(ps_dec->ps_dpb_cmds, 0, size); size = sizeof(dec_bit_stream_t); memset(ps_dec->ps_bitstrm, 0, size); size = sizeof(dec_slice_params_t); memset(ps_dec->ps_cur_slice, 0, size); size = MAX(sizeof(dec_seq_params_t), sizeof(dec_pic_params_t)); memset(ps_dec->pv_scratch_sps_pps, 0, size); size = sizeof(ctxt_inc_mb_info_t); memset(ps_dec->ps_left_mb_ctxt_info, 0, size); size = (sizeof(neighbouradd_t) << 2); memset(ps_dec->ps_left_mvpred_addr, 0 ,size); size = sizeof(buf_mgr_t) + ithread_get_mutex_lock_size(); memset(ps_dec->pv_mv_buf_mgr, 0, size); /* Free any dynamic buffers that are allocated */ ih264d_free_dynamic_bufs(ps_dec); { UWORD8 i; struct pic_buffer_t *ps_init_dpb; ps_init_dpb = ps_dec->ps_dpb_mgr->ps_init_dpb[0][0]; for(i = 0; i < 2 * MAX_REF_BUFS; i++) { ps_init_dpb->pu1_buf1 = NULL; ps_init_dpb->u1_long_term_frm_idx = MAX_REF_BUFS + 1; ps_dec->ps_dpb_mgr->ps_init_dpb[0][i] = ps_init_dpb; ps_dec->ps_dpb_mgr->ps_mod_dpb[0][i] = ps_init_dpb; ps_init_dpb++; } ps_init_dpb = ps_dec->ps_dpb_mgr->ps_init_dpb[1][0]; for(i = 0; i < 2 * MAX_REF_BUFS; i++) { ps_init_dpb->pu1_buf1 = NULL; ps_init_dpb->u1_long_term_frm_idx = MAX_REF_BUFS + 1; ps_dec->ps_dpb_mgr->ps_init_dpb[1][i] = ps_init_dpb; ps_dec->ps_dpb_mgr->ps_mod_dpb[1][i] = ps_init_dpb; ps_init_dpb++; } } ps_cur_slice = ps_dec->ps_cur_slice; ps_dec->init_done = 0; ps_dec->u4_num_cores = 1; ps_dec->u2_pic_ht = ps_dec->u2_pic_wd = 0; ps_dec->u1_separate_parse = DEFAULT_SEPARATE_PARSE; ps_dec->u4_app_disable_deblk_frm = 0; ps_dec->i4_degrade_type = 0; ps_dec->i4_degrade_pics = 0; memset(ps_dec->ps_pps, 0, ((sizeof(dec_pic_params_t)) * MAX_NUM_PIC_PARAMS)); memset(ps_dec->ps_sps, 0, ((sizeof(dec_seq_params_t)) * MAX_NUM_SEQ_PARAMS)); /* Initialization of function pointers ih264d_deblock_picture function*/ ps_dec->p_DeblockPicture[0] = ih264d_deblock_picture_non_mbaff; ps_dec->p_DeblockPicture[1] = ih264d_deblock_picture_mbaff; ps_dec->s_cab_dec_env.pv_codec_handle = ps_dec; ps_dec->u4_num_fld_in_frm = 0; ps_dec->ps_dpb_mgr->pv_codec_handle = ps_dec; /* Initialize the sei validity u4_flag with zero indiacting sei is not valid*/ ps_dec->ps_sei->u1_is_valid = 0; /* decParams Initializations */ ps_dec->ps_cur_pps = NULL; ps_dec->ps_cur_sps = NULL; ps_dec->u1_init_dec_flag = 0; ps_dec->u1_first_slice_in_stream = 1; ps_dec->u1_last_pic_not_decoded = 0; ps_dec->u4_app_disp_width = 0; ps_dec->i4_header_decoded = 0; ps_dec->u4_total_frames_decoded = 0; ps_dec->i4_error_code = 0; ps_dec->i4_content_type = IV_CONTENTTYPE_NA; ps_dec->ps_cur_slice->u1_mbaff_frame_flag = 0; ps_dec->ps_dec_err_status->u1_err_flag = ACCEPT_ALL_PICS; //REJECT_PB_PICS; ps_dec->ps_dec_err_status->u1_cur_pic_type = PIC_TYPE_UNKNOWN; ps_dec->ps_dec_err_status->u4_frm_sei_sync = SYNC_FRM_DEFAULT; ps_dec->ps_dec_err_status->u4_cur_frm = INIT_FRAME; ps_dec->ps_dec_err_status->u1_pic_aud_i = PIC_TYPE_UNKNOWN; ps_dec->u1_pr_sl_type = 0xFF; ps_dec->u2_mbx = 0xffff; ps_dec->u2_mby = 0; ps_dec->u2_total_mbs_coded = 0; /* POC initializations */ ps_prev_poc = &ps_dec->s_prev_pic_poc; ps_cur_poc = &ps_dec->s_cur_pic_poc; ps_prev_poc->i4_pic_order_cnt_lsb = ps_cur_poc->i4_pic_order_cnt_lsb = 0; ps_prev_poc->i4_pic_order_cnt_msb = ps_cur_poc->i4_pic_order_cnt_msb = 0; ps_prev_poc->i4_delta_pic_order_cnt_bottom = ps_cur_poc->i4_delta_pic_order_cnt_bottom = 0; ps_prev_poc->i4_delta_pic_order_cnt[0] = ps_cur_poc->i4_delta_pic_order_cnt[0] = 0; ps_prev_poc->i4_delta_pic_order_cnt[1] = ps_cur_poc->i4_delta_pic_order_cnt[1] = 0; ps_prev_poc->u1_mmco_equalto5 = ps_cur_poc->u1_mmco_equalto5 = 0; ps_prev_poc->i4_top_field_order_count = ps_cur_poc->i4_top_field_order_count = 0; ps_prev_poc->i4_bottom_field_order_count = ps_cur_poc->i4_bottom_field_order_count = 0; ps_prev_poc->u1_bot_field = ps_cur_poc->u1_bot_field = 0; ps_prev_poc->u1_mmco_equalto5 = ps_cur_poc->u1_mmco_equalto5 = 0; ps_prev_poc->i4_prev_frame_num_ofst = ps_cur_poc->i4_prev_frame_num_ofst = 0; ps_cur_slice->u1_mmco_equalto5 = 0; ps_cur_slice->u2_frame_num = 0; ps_dec->i4_max_poc = 0; ps_dec->i4_prev_max_display_seq = 0; ps_dec->u1_recon_mb_grp = 4; ps_dec->i4_reorder_depth = -1; /* Field PIC initializations */ ps_dec->u1_second_field = 0; ps_dec->s_prev_seq_params.u1_eoseq_pending = 0; /* Set the cropping parameters as zero */ ps_dec->u2_crop_offset_y = 0; ps_dec->u2_crop_offset_uv = 0; ps_dec->u1_frame_cropping_flag = 0; ps_dec->u1_frame_cropping_rect_left_ofst = 0; ps_dec->u1_frame_cropping_rect_right_ofst = 0; ps_dec->u1_frame_cropping_rect_top_ofst = 0; ps_dec->u1_frame_cropping_rect_bottom_ofst = 0; /* The Initial Frame Rate Info is not Present */ ps_dec->i4_vui_frame_rate = -1; ps_dec->i4_pic_type = NA_SLICE; ps_dec->i4_frametype = IV_NA_FRAME; ps_dec->i4_content_type = IV_CONTENTTYPE_NA; ps_dec->u1_res_changed = 0; ps_dec->u1_frame_decoded_flag = 0; /* Set the default frame seek mask mode */ ps_dec->u4_skip_frm_mask = SKIP_NONE; /********************************************************/ /* Initialize CAVLC residual decoding function pointers */ /********************************************************/ ps_dec->pf_cavlc_4x4res_block[0] = ih264d_cavlc_4x4res_block_totalcoeff_1; ps_dec->pf_cavlc_4x4res_block[1] = ih264d_cavlc_4x4res_block_totalcoeff_2to10; ps_dec->pf_cavlc_4x4res_block[2] = ih264d_cavlc_4x4res_block_totalcoeff_11to16; ps_dec->pf_cavlc_parse4x4coeff[0] = ih264d_cavlc_parse4x4coeff_n0to7; ps_dec->pf_cavlc_parse4x4coeff[1] = ih264d_cavlc_parse4x4coeff_n8; ps_dec->pf_cavlc_parse_8x8block[0] = ih264d_cavlc_parse_8x8block_none_available; ps_dec->pf_cavlc_parse_8x8block[1] = ih264d_cavlc_parse_8x8block_left_available; ps_dec->pf_cavlc_parse_8x8block[2] = ih264d_cavlc_parse_8x8block_top_available; ps_dec->pf_cavlc_parse_8x8block[3] = ih264d_cavlc_parse_8x8block_both_available; /***************************************************************************/ /* Initialize Bs calculation function pointers for P and B, 16x16/non16x16 */ /***************************************************************************/ ps_dec->pf_fill_bs1[0][0] = ih264d_fill_bs1_16x16mb_pslice; ps_dec->pf_fill_bs1[0][1] = ih264d_fill_bs1_non16x16mb_pslice; ps_dec->pf_fill_bs1[1][0] = ih264d_fill_bs1_16x16mb_bslice; ps_dec->pf_fill_bs1[1][1] = ih264d_fill_bs1_non16x16mb_bslice; ps_dec->pf_fill_bs_xtra_left_edge[0] = ih264d_fill_bs_xtra_left_edge_cur_frm; ps_dec->pf_fill_bs_xtra_left_edge[1] = ih264d_fill_bs_xtra_left_edge_cur_fld; /* Initialize Reference Pic Buffers */ ih264d_init_ref_bufs(ps_dec->ps_dpb_mgr); ps_dec->u2_prv_frame_num = 0; ps_dec->u1_top_bottom_decoded = 0; ps_dec->u1_dangling_field = 0; ps_dec->s_cab_dec_env.cabac_table = gau4_ih264d_cabac_table; ps_dec->pu1_left_mv_ctxt_inc = ps_dec->u1_left_mv_ctxt_inc_arr[0]; ps_dec->pi1_left_ref_idx_ctxt_inc = &ps_dec->i1_left_ref_idx_ctx_inc_arr[0][0]; ps_dec->pu1_left_yuv_dc_csbp = &ps_dec->u1_yuv_dc_csbp_topmb; /* ! */ /* Initializing flush frame u4_flag */ ps_dec->u1_flushfrm = 0; { ps_dec->s_cab_dec_env.pv_codec_handle = (void*)ps_dec; ps_dec->ps_bitstrm->pv_codec_handle = (void*)ps_dec; ps_dec->ps_cur_slice->pv_codec_handle = (void*)ps_dec; ps_dec->ps_dpb_mgr->pv_codec_handle = (void*)ps_dec; } memset(ps_dec->disp_bufs, 0, (MAX_DISP_BUFS_NEW) * sizeof(disp_buf_t)); memset(ps_dec->u4_disp_buf_mapping, 0, (MAX_DISP_BUFS_NEW) * sizeof(UWORD32)); memset(ps_dec->u4_disp_buf_to_be_freed, 0, (MAX_DISP_BUFS_NEW) * sizeof(UWORD32)); memset(ps_dec->ps_cur_slice, 0, sizeof(dec_slice_params_t)); ih264d_init_arch(ps_dec); ih264d_init_function_ptr(ps_dec); ps_dec->e_frm_out_mode = IVD_DISPLAY_FRAME_OUT; ps_dec->init_done = 1; } WORD32 ih264d_free_static_bufs(iv_obj_t *dec_hdl) { dec_struct_t *ps_dec; void (*pf_aligned_free)(void *pv_mem_ctxt, void *pv_buf); void *pv_mem_ctxt; ps_dec = (dec_struct_t *)dec_hdl->pv_codec_handle; pf_aligned_free = ps_dec->pf_aligned_free; pv_mem_ctxt = ps_dec->pv_mem_ctxt; PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_sps); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_pps); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_dec_thread_handle); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_bs_deblk_thread_handle); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_dpb_mgr); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_pred); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_disp_buf_mgr); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_pic_buf_mgr); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_pic_buf_base); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_dec_err_status); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_sei); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_sei_parse); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_dpb_cmds); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_bitstrm); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_cur_slice); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_scratch_sps_pps); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_bits_buf_static); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ppv_map_ref_idx_to_poc_base); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->p_cabac_ctxt_table_t); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_left_mb_ctxt_info); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_ref_buff_base); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pi2_pred1); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_temp_mc_buffer); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_init_dpb_base); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu4_mbaff_wt_mat); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu4_wts_ofsts_mat); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_left_mvpred_addr); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_mv_buf_mgr); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_col_mv_base); PS_DEC_ALIGNED_FREE(ps_dec, dec_hdl->pv_codec_handle); if(dec_hdl) { pf_aligned_free(pv_mem_ctxt, dec_hdl); } return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_create */ /* */ /* Description : creates decoder */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_allocate_static_bufs(iv_obj_t **dec_hdl, void *pv_api_ip, void *pv_api_op) { ih264d_create_ip_t *ps_create_ip; ih264d_create_op_t *ps_create_op; void *pv_buf; UWORD8 *pu1_buf; dec_struct_t *ps_dec; void *(*pf_aligned_alloc)(void *pv_mem_ctxt, WORD32 alignment, WORD32 size); void (*pf_aligned_free)(void *pv_mem_ctxt, void *pv_buf); void *pv_mem_ctxt; WORD32 size; ps_create_ip = (ih264d_create_ip_t *)pv_api_ip; ps_create_op = (ih264d_create_op_t *)pv_api_op; ps_create_op->s_ivd_create_op_t.u4_error_code = 0; pf_aligned_alloc = ps_create_ip->s_ivd_create_ip_t.pf_aligned_alloc; pf_aligned_free = ps_create_ip->s_ivd_create_ip_t.pf_aligned_free; pv_mem_ctxt = ps_create_ip->s_ivd_create_ip_t.pv_mem_ctxt; /* Initialize return handle to NULL */ ps_create_op->s_ivd_create_op_t.pv_handle = NULL; pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, sizeof(iv_obj_t)); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, sizeof(iv_obj_t)); *dec_hdl = (iv_obj_t *)pv_buf; ps_create_op->s_ivd_create_op_t.pv_handle = *dec_hdl; (*dec_hdl)->pv_codec_handle = NULL; pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, sizeof(dec_struct_t)); RETURN_IF((NULL == pv_buf), IV_FAIL); (*dec_hdl)->pv_codec_handle = (dec_struct_t *)pv_buf; ps_dec = (dec_struct_t *)pv_buf; memset(ps_dec, 0, sizeof(dec_struct_t)); #ifndef LOGO_EN ps_dec->u4_share_disp_buf = ps_create_ip->s_ivd_create_ip_t.u4_share_disp_buf; #else ps_dec->u4_share_disp_buf = 0; #endif ps_dec->u1_chroma_format = (UWORD8)(ps_create_ip->s_ivd_create_ip_t.e_output_format); if((ps_dec->u1_chroma_format != IV_YUV_420P) && (ps_dec->u1_chroma_format != IV_YUV_420SP_UV) && (ps_dec->u1_chroma_format != IV_YUV_420SP_VU)) { ps_dec->u4_share_disp_buf = 0; } ps_dec->pf_aligned_alloc = pf_aligned_alloc; ps_dec->pf_aligned_free = pf_aligned_free; ps_dec->pv_mem_ctxt = pv_mem_ctxt; size = ((sizeof(dec_seq_params_t)) * MAX_NUM_SEQ_PARAMS); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_sps = pv_buf; size = (sizeof(dec_pic_params_t)) * MAX_NUM_PIC_PARAMS; pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_pps = pv_buf; size = ithread_get_handle_size(); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pv_dec_thread_handle = pv_buf; size = ithread_get_handle_size(); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pv_bs_deblk_thread_handle = pv_buf; size = sizeof(dpb_manager_t); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_dpb_mgr = pv_buf; size = sizeof(pred_info_t) * 2 * 32; pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_pred = pv_buf; size = sizeof(disp_mgr_t); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pv_disp_buf_mgr = pv_buf; size = sizeof(buf_mgr_t) + ithread_get_mutex_lock_size(); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pv_pic_buf_mgr = pv_buf; size = sizeof(struct pic_buffer_t) * (H264_MAX_REF_PICS * 2); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_pic_buf_base = pv_buf; size = sizeof(dec_err_status_t); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_dec_err_status = (dec_err_status_t *)pv_buf; size = sizeof(sei); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_sei = (sei *)pv_buf; size = sizeof(sei); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_sei_parse = (sei *)pv_buf; size = sizeof(dpb_commands_t); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_dpb_cmds = (dpb_commands_t *)pv_buf; size = sizeof(dec_bit_stream_t); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_bitstrm = (dec_bit_stream_t *)pv_buf; size = sizeof(dec_slice_params_t); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_cur_slice = (dec_slice_params_t *)pv_buf; size = MAX(sizeof(dec_seq_params_t), sizeof(dec_pic_params_t)); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pv_scratch_sps_pps = pv_buf; ps_dec->u4_static_bits_buf_size = 256000; pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, ps_dec->u4_static_bits_buf_size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, ps_dec->u4_static_bits_buf_size); ps_dec->pu1_bits_buf_static = pv_buf; size = ((TOTAL_LIST_ENTRIES + PAD_MAP_IDX_POC) * sizeof(void *)); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); ps_dec->ppv_map_ref_idx_to_poc_base = pv_buf; memset(ps_dec->ppv_map_ref_idx_to_poc_base, 0, size); ps_dec->ppv_map_ref_idx_to_poc = ps_dec->ppv_map_ref_idx_to_poc_base + OFFSET_MAP_IDX_POC; size = (sizeof(bin_ctxt_model_t) * NUM_CABAC_CTXTS); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->p_cabac_ctxt_table_t = pv_buf; size = sizeof(ctxt_inc_mb_info_t); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_left_mb_ctxt_info = pv_buf; size = MAX_REF_BUF_SIZE * 2; pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pu1_ref_buff_base = pv_buf; ps_dec->pu1_ref_buff = ps_dec->pu1_ref_buff_base + MAX_REF_BUF_SIZE; size = ((sizeof(WORD16)) * PRED_BUFFER_WIDTH * PRED_BUFFER_HEIGHT * 2); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pi2_pred1 = pv_buf; size = sizeof(UWORD8) * (MB_LUM_SIZE); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pu1_temp_mc_buffer = pv_buf; size = 8 * MAX_REF_BUFS * sizeof(struct pic_buffer_t); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pu1_init_dpb_base = pv_buf; pu1_buf = pv_buf; ps_dec->ps_dpb_mgr->ps_init_dpb[0][0] = (struct pic_buffer_t *)pu1_buf; pu1_buf += size / 2; ps_dec->ps_dpb_mgr->ps_init_dpb[1][0] = (struct pic_buffer_t *)pu1_buf; size = (sizeof(UWORD32) * 2 * 3 * ((MAX_FRAMES << 1) * (MAX_FRAMES << 1)) * 2); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pu4_mbaff_wt_mat = pv_buf; size = sizeof(UWORD32) * 2 * 3 * ((MAX_FRAMES << 1) * (MAX_FRAMES << 1)); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pu4_wts_ofsts_mat = pv_buf; size = (sizeof(neighbouradd_t) << 2); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_left_mvpred_addr = pv_buf; size = sizeof(buf_mgr_t) + ithread_get_mutex_lock_size(); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pv_mv_buf_mgr = pv_buf; size = sizeof(col_mv_buf_t) * (H264_MAX_REF_PICS * 2); pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); ps_dec->ps_col_mv_base = pv_buf; memset(ps_dec->ps_col_mv_base, 0, size); ih264d_init_decoder(ps_dec); return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_create */ /* */ /* Description : creates decoder */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_create(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ih264d_create_ip_t *ps_create_ip; ih264d_create_op_t *ps_create_op; WORD32 ret; ps_create_ip = (ih264d_create_ip_t *)pv_api_ip; ps_create_op = (ih264d_create_op_t *)pv_api_op; ps_create_op->s_ivd_create_op_t.u4_error_code = 0; dec_hdl = NULL; ret = ih264d_allocate_static_bufs(&dec_hdl, pv_api_ip, pv_api_op); /* If allocation of some buffer fails, then free buffers allocated till then */ if(IV_FAIL == ret) { if(dec_hdl) { if(dec_hdl->pv_codec_handle) { ih264d_free_static_bufs(dec_hdl); } else { void (*pf_aligned_free)(void *pv_mem_ctxt, void *pv_buf); void *pv_mem_ctxt; pf_aligned_free = ps_create_ip->s_ivd_create_ip_t.pf_aligned_free; pv_mem_ctxt = ps_create_ip->s_ivd_create_ip_t.pv_mem_ctxt; pf_aligned_free(pv_mem_ctxt, dec_hdl); } } ps_create_op->s_ivd_create_op_t.u4_error_code = IVD_MEM_ALLOC_FAILED; ps_create_op->s_ivd_create_op_t.u4_error_code |= 1 << IVD_FATALERROR; return IV_FAIL; } return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_map_error */ /* */ /* Description : Maps error codes to IVD error groups */ /* */ /* Inputs : */ /* Globals : <Does it use any global variables?> */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_map_error(UWORD32 i4_err_status) { UWORD32 temp = 0; switch(i4_err_status) { case ERROR_MEM_ALLOC_ISRAM_T: case ERROR_MEM_ALLOC_SDRAM_T: case ERROR_BUF_MGR: case ERROR_MB_GROUP_ASSGN_T: case ERROR_FRAME_LIMIT_OVER: case ERROR_ACTUAL_RESOLUTION_GREATER_THAN_INIT: case ERROR_PROFILE_NOT_SUPPORTED: case ERROR_INIT_NOT_DONE: case IVD_MEM_ALLOC_FAILED: case ERROR_FEATURE_UNAVAIL: case IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED: temp = 1 << IVD_FATALERROR; H264_DEC_DEBUG_PRINT("\nFatal Error\n"); break; case ERROR_DBP_MANAGER_T: case ERROR_GAPS_IN_FRM_NUM: case ERROR_UNKNOWN_NAL: case ERROR_INV_MB_SLC_GRP_T: case ERROR_MULTIPLE_SLC_GRP_T: case ERROR_UNKNOWN_LEVEL: case ERROR_UNAVAIL_PICBUF_T: case ERROR_UNAVAIL_MVBUF_T: case ERROR_UNAVAIL_DISPBUF_T: case ERROR_NUM_REF: case ERROR_REFIDX_ORDER_T: case ERROR_PIC0_NOT_FOUND_T: case ERROR_MB_TYPE: case ERROR_SUB_MB_TYPE: case ERROR_CBP: case ERROR_REF_IDX: case ERROR_NUM_MV: case ERROR_CHROMA_PRED_MODE: case ERROR_INTRAPRED: case ERROR_NEXT_MB_ADDRESS_T: case ERROR_MB_ADDRESS_T: case ERROR_PIC1_NOT_FOUND_T: case ERROR_CAVLC_NUM_COEFF_T: case ERROR_CAVLC_SCAN_POS_T: case ERROR_PRED_WEIGHT_TABLE_T: case ERROR_CORRUPTED_SLICE: temp = 1 << IVD_CORRUPTEDDATA; break; case ERROR_NOT_SUPP_RESOLUTION: case ERROR_ACTUAL_LEVEL_GREATER_THAN_INIT: temp = 1 << IVD_UNSUPPORTEDINPUT; break; case ERROR_INVALID_PIC_PARAM: case ERROR_INVALID_SEQ_PARAM: case ERROR_EGC_EXCEED_32_1_T: case ERROR_EGC_EXCEED_32_2_T: case ERROR_INV_RANGE_TEV_T: case ERROR_INV_SLC_TYPE_T: case ERROR_INV_POC_TYPE_T: case ERROR_INV_RANGE_QP_T: case ERROR_INV_SPS_PPS_T: case ERROR_INV_SLICE_HDR_T: case ERROR_INV_SEI_MDCV_PARAMS: case ERROR_INV_SEI_CLL_PARAMS: case ERROR_INV_SEI_AVE_PARAMS: case ERROR_INV_SEI_CCV_PARAMS: temp = 1 << IVD_CORRUPTEDHEADER; break; case ERROR_EOB_FLUSHBITS_T: case ERROR_EOB_GETBITS_T: case ERROR_EOB_GETBIT_T: case ERROR_EOB_BYPASS_T: case ERROR_EOB_DECISION_T: case ERROR_EOB_TERMINATE_T: case ERROR_EOB_READCOEFF4X4CAB_T: temp = 1 << IVD_INSUFFICIENTDATA; break; case ERROR_DYNAMIC_RESOLUTION_NOT_SUPPORTED: case ERROR_DISP_WIDTH_RESET_TO_PIC_WIDTH: temp = 1 << IVD_UNSUPPORTEDPARAM | 1 << IVD_FATALERROR; break; case ERROR_DANGLING_FIELD_IN_PIC: temp = 1 << IVD_APPLIEDCONCEALMENT; break; } return temp; } UWORD32 ih264d_get_outbuf_size(WORD32 pic_wd, UWORD32 pic_ht, UWORD8 u1_chroma_format, UWORD32 *p_buf_size) { UWORD32 u4_min_num_out_bufs = 0; if(u1_chroma_format == IV_YUV_420P) u4_min_num_out_bufs = MIN_OUT_BUFS_420; else if(u1_chroma_format == IV_YUV_422ILE) u4_min_num_out_bufs = MIN_OUT_BUFS_422ILE; else if(u1_chroma_format == IV_RGB_565) u4_min_num_out_bufs = MIN_OUT_BUFS_RGB565; else if((u1_chroma_format == IV_YUV_420SP_UV) || (u1_chroma_format == IV_YUV_420SP_VU)) u4_min_num_out_bufs = MIN_OUT_BUFS_420SP; if(u1_chroma_format == IV_YUV_420P) { p_buf_size[0] = (pic_wd * pic_ht); p_buf_size[1] = (pic_wd * pic_ht) >> 2; p_buf_size[2] = (pic_wd * pic_ht) >> 2; } else if(u1_chroma_format == IV_YUV_422ILE) { p_buf_size[0] = (pic_wd * pic_ht) * 2; p_buf_size[1] = p_buf_size[2] = 0; } else if(u1_chroma_format == IV_RGB_565) { p_buf_size[0] = (pic_wd * pic_ht) * 2; p_buf_size[1] = p_buf_size[2] = 0; } else if((u1_chroma_format == IV_YUV_420SP_UV) || (u1_chroma_format == IV_YUV_420SP_VU)) { p_buf_size[0] = (pic_wd * pic_ht); p_buf_size[1] = (pic_wd * pic_ht) >> 1; p_buf_size[2] = 0; } return u4_min_num_out_bufs; } WORD32 check_app_out_buf_size(dec_struct_t *ps_dec) { UWORD32 au4_min_out_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; UWORD32 u4_min_num_out_bufs, i; UWORD32 pic_wd, pic_ht; if(0 == ps_dec->u4_share_disp_buf) { pic_wd = ps_dec->u2_disp_width; pic_ht = ps_dec->u2_disp_height; } else { pic_wd = ps_dec->u2_frm_wd_y; pic_ht = ps_dec->u2_frm_ht_y; } if(ps_dec->u4_app_disp_width > pic_wd) pic_wd = ps_dec->u4_app_disp_width; u4_min_num_out_bufs = ih264d_get_outbuf_size(pic_wd, pic_ht, ps_dec->u1_chroma_format, &au4_min_out_buf_size[0]); if(0 == ps_dec->u4_share_disp_buf) { if(ps_dec->ps_out_buffer->u4_num_bufs < u4_min_num_out_bufs) return IV_FAIL; for(i = 0; i < u4_min_num_out_bufs; i++) { if(ps_dec->ps_out_buffer->u4_min_out_buf_size[i] < au4_min_out_buf_size[i]) return (IV_FAIL); } } else { if(ps_dec->disp_bufs[0].u4_num_bufs < u4_min_num_out_bufs) return IV_FAIL; for(i = 0; i < u4_min_num_out_bufs; i++) { /* We need to check only with the disp_buffer[0], because we have * already ensured that all the buffers are of the same size in * ih264d_set_display_frame. */ if(ps_dec->disp_bufs[0].u4_bufsize[i] < au4_min_out_buf_size[i]) return (IV_FAIL); } } return (IV_SUCCESS); } /*****************************************************************************/ /* */ /* Function Name : ih264d_video_decode */ /* */ /* Description : handle video decode API command */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_video_decode(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { /* ! */ dec_struct_t * ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); WORD32 i4_err_status = 0; UWORD8 *pu1_buf = NULL; WORD32 buflen; UWORD32 u4_max_ofst, u4_length_of_start_code = 0; UWORD32 bytes_consumed = 0; UWORD32 cur_slice_is_nonref = 0; UWORD32 u4_next_is_aud; UWORD32 u4_first_start_code_found = 0; WORD32 ret = 0,api_ret_value = IV_SUCCESS; WORD32 header_data_left = 0,frame_data_left = 0; UWORD8 *pu1_bitstrm_buf; ih264d_video_decode_ip_t *ps_h264d_dec_ip; ih264d_video_decode_op_t *ps_h264d_dec_op; ivd_video_decode_ip_t *ps_dec_ip; ivd_video_decode_op_t *ps_dec_op; ithread_set_name((void*)"Parse_thread"); ps_h264d_dec_ip = (ih264d_video_decode_ip_t *)pv_api_ip; ps_h264d_dec_op = (ih264d_video_decode_op_t *)pv_api_op; ps_dec_ip = &ps_h264d_dec_ip->s_ivd_video_decode_ip_t; ps_dec_op = &ps_h264d_dec_op->s_ivd_video_decode_op_t; { UWORD32 u4_size; u4_size = ps_dec_op->u4_size; memset(ps_h264d_dec_op, 0, sizeof(ih264d_video_decode_op_t)); ps_dec_op->u4_size = u4_size; } ps_dec->pv_dec_out = ps_dec_op; if(ps_dec->init_done != 1) { return IV_FAIL; } /*Data memory barries instruction,so that bitstream write by the application is complete*/ DATA_SYNC(); if(0 == ps_dec->u1_flushfrm) { if(ps_dec_ip->pv_stream_buffer == NULL) { ps_dec_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_dec_op->u4_error_code |= IVD_DEC_FRM_BS_BUF_NULL; return IV_FAIL; } if(ps_dec_ip->u4_num_Bytes <= 0) { ps_dec_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_dec_op->u4_error_code |= IVD_DEC_NUMBYTES_INV; return IV_FAIL; } } ps_dec->u1_pic_decode_done = 0; ps_dec_op->u4_num_bytes_consumed = 0; ps_dec_op->i4_reorder_depth = -1; ps_dec_op->i4_display_index = DEFAULT_POC; ps_dec->ps_out_buffer = NULL; if(ps_dec_ip->u4_size >= offsetof(ivd_video_decode_ip_t, s_out_buffer)) ps_dec->ps_out_buffer = &ps_dec_ip->s_out_buffer; ps_dec->u4_fmt_conv_cur_row = 0; ps_dec->u4_output_present = 0; ps_dec->s_disp_op.u4_error_code = 1; ps_dec->u4_fmt_conv_num_rows = FMT_CONV_NUM_ROWS; if(0 == ps_dec->u4_share_disp_buf && ps_dec->i4_decode_header == 0) { UWORD32 i; if((ps_dec->ps_out_buffer->u4_num_bufs == 0) || (ps_dec->ps_out_buffer->u4_num_bufs > IVD_VIDDEC_MAX_IO_BUFFERS)) { ps_dec_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_dec_op->u4_error_code |= IVD_DISP_FRM_ZERO_OP_BUFS; return IV_FAIL; } for(i = 0; i < ps_dec->ps_out_buffer->u4_num_bufs; i++) { if(ps_dec->ps_out_buffer->pu1_bufs[i] == NULL) { ps_dec_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_dec_op->u4_error_code |= IVD_DISP_FRM_OP_BUF_NULL; return IV_FAIL; } if(ps_dec->ps_out_buffer->u4_min_out_buf_size[i] == 0) { ps_dec_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; ps_dec_op->u4_error_code |= IVD_DISP_FRM_ZERO_OP_BUF_SIZE; return IV_FAIL; } } } if(ps_dec->u4_total_frames_decoded >= NUM_FRAMES_LIMIT) { ps_dec_op->u4_error_code = ERROR_FRAME_LIMIT_OVER; return IV_FAIL; } /* ! */ ps_dec->u4_ts = ps_dec_ip->u4_ts; ps_dec_op->u4_error_code = 0; ps_dec_op->e_pic_type = IV_NA_FRAME; ps_dec_op->u4_output_present = 0; ps_dec_op->u4_frame_decoded_flag = 0; ps_dec->i4_frametype = IV_NA_FRAME; ps_dec->i4_content_type = IV_CONTENTTYPE_NA; ps_dec->u4_slice_start_code_found = 0; /* In case the deocder is not in flush mode(in shared mode), then decoder has to pick up a buffer to write current frame. Check if a frame is available in such cases */ if(ps_dec->u1_init_dec_flag == 1 && ps_dec->u4_share_disp_buf == 1 && ps_dec->u1_flushfrm == 0) { UWORD32 i; WORD32 disp_avail = 0, free_id; /* Check if at least one buffer is available with the codec */ /* If not then return to application with error */ for(i = 0; i < ps_dec->u1_pic_bufs; i++) { if(0 == ps_dec->u4_disp_buf_mapping[i] || 1 == ps_dec->u4_disp_buf_to_be_freed[i]) { disp_avail = 1; break; } } if(0 == disp_avail) { /* If something is queued for display wait for that buffer to be returned */ ps_dec_op->u4_error_code = IVD_DEC_REF_BUF_NULL; ps_dec_op->u4_error_code |= (1 << IVD_UNSUPPORTEDPARAM); return (IV_FAIL); } while(1) { pic_buffer_t *ps_pic_buf; ps_pic_buf = (pic_buffer_t *)ih264_buf_mgr_get_next_free( (buf_mgr_t *)ps_dec->pv_pic_buf_mgr, &free_id); if(ps_pic_buf == NULL) { UWORD32 i, display_queued = 0; /* check if any buffer was given for display which is not returned yet */ for(i = 0; i < (MAX_DISP_BUFS_NEW); i++) { if(0 != ps_dec->u4_disp_buf_mapping[i]) { display_queued = 1; break; } } /* If some buffer is queued for display, then codec has to singal an error and wait for that buffer to be returned. If nothing is queued for display then codec has ownership of all display buffers and it can reuse any of the existing buffers and continue decoding */ if(1 == display_queued) { /* If something is queued for display wait for that buffer to be returned */ ps_dec_op->u4_error_code = IVD_DEC_REF_BUF_NULL; ps_dec_op->u4_error_code |= (1 << IVD_UNSUPPORTEDPARAM); return (IV_FAIL); } } else { /* If the buffer is with display, then mark it as in use and then look for a buffer again */ if(1 == ps_dec->u4_disp_buf_mapping[free_id]) { ih264_buf_mgr_set_status( (buf_mgr_t *)ps_dec->pv_pic_buf_mgr, free_id, BUF_MGR_IO); } else { /** * Found a free buffer for present call. Release it now. * Will be again obtained later. */ ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, free_id, BUF_MGR_IO); break; } } } } if(ps_dec->u1_flushfrm) { if(ps_dec->u1_init_dec_flag == 0) { /*Come out of flush mode and return*/ ps_dec->u1_flushfrm = 0; return (IV_FAIL); } ih264d_get_next_display_field(ps_dec, ps_dec->ps_out_buffer, &(ps_dec->s_disp_op)); if(0 == ps_dec->s_disp_op.u4_error_code) { /* check output buffer size given by the application */ if(check_app_out_buf_size(ps_dec) != IV_SUCCESS) { ps_dec_op->u4_error_code= IVD_DISP_FRM_ZERO_OP_BUF_SIZE; return (IV_FAIL); } ps_dec->u4_fmt_conv_cur_row = 0; ps_dec->u4_fmt_conv_num_rows = ps_dec->s_disp_frame_info.u4_y_ht; ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), ps_dec->u4_fmt_conv_cur_row, ps_dec->u4_fmt_conv_num_rows); ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; ps_dec->u4_output_present = 1; } ih264d_export_sei_params(&ps_dec_op->s_sei_decode_op, ps_dec); ih264d_release_display_field(ps_dec, &(ps_dec->s_disp_op)); ps_dec_op->u4_pic_wd = (UWORD32)ps_dec->u2_disp_width; ps_dec_op->u4_pic_ht = (UWORD32)ps_dec->u2_disp_height; ps_dec_op->i4_reorder_depth = ps_dec->i4_reorder_depth; ps_dec_op->i4_display_index = ps_dec->i4_display_index; ps_dec_op->u4_new_seq = 0; ps_dec_op->u4_output_present = ps_dec->u4_output_present; ps_dec_op->u4_progressive_frame_flag = ps_dec->s_disp_op.u4_progressive_frame_flag; ps_dec_op->e_output_format = ps_dec->s_disp_op.e_output_format; ps_dec_op->s_disp_frm_buf = ps_dec->s_disp_op.s_disp_frm_buf; ps_dec_op->e4_fld_type = ps_dec->s_disp_op.e4_fld_type; ps_dec_op->u4_ts = ps_dec->s_disp_op.u4_ts; ps_dec_op->u4_disp_buf_id = ps_dec->s_disp_op.u4_disp_buf_id; /*In the case of flush ,since no frame is decoded set pic type as invalid*/ ps_dec_op->u4_is_ref_flag = -1; ps_dec_op->e_pic_type = IV_NA_FRAME; ps_dec_op->u4_frame_decoded_flag = 0; if(0 == ps_dec->s_disp_op.u4_error_code) { return (IV_SUCCESS); } else return (IV_FAIL); } if(ps_dec->u1_res_changed == 1) { /*if resolution has changed and all buffers have been flushed, reset decoder*/ ih264d_init_decoder(ps_dec); } ps_dec->u2_cur_mb_addr = 0; ps_dec->u2_total_mbs_coded = 0; ps_dec->u2_cur_slice_num = 0; ps_dec->cur_dec_mb_num = 0; ps_dec->cur_recon_mb_num = 0; ps_dec->u4_first_slice_in_pic = 1; ps_dec->u1_slice_header_done = 0; ps_dec->u1_dangling_field = 0; ps_dec->u4_dec_thread_created = 0; ps_dec->u4_bs_deblk_thread_created = 0; ps_dec->u4_cur_bs_mb_num = 0; ps_dec->u4_start_recon_deblk = 0; ps_dec->u4_sps_cnt_in_process = 0; DEBUG_THREADS_PRINTF(" Starting process call\n"); ps_dec->u4_pic_buf_got = 0; do { WORD32 buf_size; pu1_buf = (UWORD8*)ps_dec_ip->pv_stream_buffer + ps_dec_op->u4_num_bytes_consumed; u4_max_ofst = ps_dec_ip->u4_num_Bytes - ps_dec_op->u4_num_bytes_consumed; /* If dynamic bitstream buffer is not allocated and * header decode is done, then allocate dynamic bitstream buffer */ if((NULL == ps_dec->pu1_bits_buf_dynamic) && (ps_dec->i4_header_decoded & 1)) { WORD32 size; void *pv_buf; void *pv_mem_ctxt = ps_dec->pv_mem_ctxt; size = MAX(256000, ps_dec->u2_pic_wd * ps_dec->u2_pic_ht * 3 / 2); pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size + EXTRA_BS_OFFSET); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size + EXTRA_BS_OFFSET); ps_dec->pu1_bits_buf_dynamic = pv_buf; ps_dec->u4_dynamic_bits_buf_size = size; } if(ps_dec->pu1_bits_buf_dynamic) { pu1_bitstrm_buf = ps_dec->pu1_bits_buf_dynamic; buf_size = ps_dec->u4_dynamic_bits_buf_size; } else { pu1_bitstrm_buf = ps_dec->pu1_bits_buf_static; buf_size = ps_dec->u4_static_bits_buf_size; } u4_next_is_aud = 0; buflen = ih264d_find_start_code(pu1_buf, 0, u4_max_ofst, &u4_length_of_start_code, &u4_next_is_aud); if(buflen == -1) buflen = 0; /* Ignore bytes beyond the allocated size of intermediate buffer */ /* Since 8 bytes are read ahead, ensure 8 bytes are free at the end of the buffer, which will be memset to 0 after emulation prevention */ buflen = MIN(buflen, buf_size - 8); bytes_consumed = buflen + u4_length_of_start_code; ps_dec_op->u4_num_bytes_consumed += bytes_consumed; if(buflen) { memcpy(pu1_bitstrm_buf, pu1_buf + u4_length_of_start_code, buflen); /* Decoder may read extra 8 bytes near end of the frame */ if((buflen + 8) < buf_size) { memset(pu1_bitstrm_buf + buflen, 0, 8); } u4_first_start_code_found = 1; } else { /*start code not found*/ if(u4_first_start_code_found == 0) { /*no start codes found in current process call*/ ps_dec->i4_error_code = ERROR_START_CODE_NOT_FOUND; ps_dec_op->u4_error_code |= 1 << IVD_INSUFFICIENTDATA; if(ps_dec->u4_pic_buf_got == 0) { ih264d_fill_output_struct_from_context(ps_dec, ps_dec_op); ps_dec_op->u4_error_code = ps_dec->i4_error_code; ps_dec_op->u4_frame_decoded_flag = 0; return (IV_FAIL); } else { ps_dec->u1_pic_decode_done = 1; continue; } } else { /* a start code has already been found earlier in the same process call*/ frame_data_left = 0; header_data_left = 0; continue; } } ret = ih264d_parse_nal_unit(dec_hdl, ps_dec_op, pu1_bitstrm_buf, buflen); if(ret != OK) { UWORD32 error = ih264d_map_error(ret); ps_dec_op->u4_error_code = error | ret; api_ret_value = IV_FAIL; if((ret == IVD_RES_CHANGED) || (ret == IVD_MEM_ALLOC_FAILED) || (ret == ERROR_UNAVAIL_PICBUF_T) || (ret == ERROR_UNAVAIL_MVBUF_T) || (ret == ERROR_INV_SPS_PPS_T) || (ret == ERROR_FEATURE_UNAVAIL) || (ret == IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED) || (ret == IVD_DISP_FRM_ZERO_OP_BUF_SIZE)) { ps_dec->u4_slice_start_code_found = 0; break; } if((ret == ERROR_INCOMPLETE_FRAME) || (ret == ERROR_DANGLING_FIELD_IN_PIC)) { ps_dec_op->u4_num_bytes_consumed -= bytes_consumed; api_ret_value = IV_FAIL; break; } if(ret == ERROR_IN_LAST_SLICE_OF_PIC) { api_ret_value = IV_FAIL; break; } } header_data_left = ((ps_dec->i4_decode_header == 1) && (ps_dec->i4_header_decoded != 3) && (ps_dec_op->u4_num_bytes_consumed < ps_dec_ip->u4_num_Bytes)); frame_data_left = (((ps_dec->i4_decode_header == 0) && ((ps_dec->u1_pic_decode_done == 0) || (u4_next_is_aud == 1))) && (ps_dec_op->u4_num_bytes_consumed < ps_dec_ip->u4_num_Bytes)); } while(( header_data_left == 1)||(frame_data_left == 1)); if((ps_dec->u4_pic_buf_got == 1) && (ret != IVD_MEM_ALLOC_FAILED) && ps_dec->u2_total_mbs_coded < ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) { // last slice - missing/corruption WORD32 num_mb_skipped; WORD32 prev_slice_err; pocstruct_t temp_poc; WORD32 ret1; WORD32 ht_in_mbs; ht_in_mbs = ps_dec->u2_pic_ht >> (4 + ps_dec->ps_cur_slice->u1_field_pic_flag); num_mb_skipped = (ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) - ps_dec->u2_total_mbs_coded; if(ps_dec->u4_first_slice_in_pic && (ps_dec->u4_pic_buf_got == 0)) prev_slice_err = 1; else prev_slice_err = 2; if(ps_dec->u4_first_slice_in_pic && (ps_dec->u2_total_mbs_coded == 0)) prev_slice_err = 1; ret1 = ih264d_mark_err_slice_skip(ps_dec, num_mb_skipped, ps_dec->u1_nal_unit_type == IDR_SLICE_NAL, ps_dec->ps_cur_slice->u2_frame_num, &temp_poc, prev_slice_err); if((ret1 == ERROR_UNAVAIL_PICBUF_T) || (ret1 == ERROR_UNAVAIL_MVBUF_T) || (ret1 == ERROR_INV_SPS_PPS_T)) { ret = ret1; } } if((ret == IVD_RES_CHANGED) || (ret == IVD_MEM_ALLOC_FAILED) || (ret == ERROR_UNAVAIL_PICBUF_T) || (ret == ERROR_UNAVAIL_MVBUF_T) || (ret == ERROR_INV_SPS_PPS_T)) { /* signal the decode thread */ ih264d_signal_decode_thread(ps_dec); /* close deblock thread if it is not closed yet */ if(ps_dec->u4_num_cores == 3) { ih264d_signal_bs_deblk_thread(ps_dec); } /* dont consume bitstream for change in resolution case */ if(ret == IVD_RES_CHANGED) { ps_dec_op->u4_num_bytes_consumed -= bytes_consumed; } return IV_FAIL; } /* Mirror some raw state info to output */ ps_dec_op->u1_frame_cropping_flag = ps_dec->u1_frame_cropping_flag; ps_dec_op->u1_frame_cropping_rect_left_ofst = ps_dec->u1_frame_cropping_rect_left_ofst; ps_dec_op->u1_frame_cropping_rect_right_ofst = ps_dec->u1_frame_cropping_rect_right_ofst; ps_dec_op->u1_frame_cropping_rect_top_ofst = ps_dec->u1_frame_cropping_rect_top_ofst; ps_dec_op->u1_frame_cropping_rect_bottom_ofst = ps_dec->u1_frame_cropping_rect_bottom_ofst; ps_dec_op->u4_raw_wd = ps_dec->u2_pic_wd; ps_dec_op->u4_raw_ht = ps_dec->u2_pic_ht; if(ps_dec->u1_separate_parse) { /* If Format conversion is not complete, complete it here */ if(ps_dec->u4_num_cores == 2) { /*do deblocking of all mbs*/ if((ps_dec->u4_nmb_deblk == 0) &&(ps_dec->u4_start_recon_deblk == 1) && (ps_dec->ps_cur_sps->u1_mb_aff_flag == 0)) { UWORD32 u4_num_mbs,u4_max_addr; tfr_ctxt_t s_tfr_ctxt; tfr_ctxt_t *ps_tfr_cxt = &s_tfr_ctxt; pad_mgr_t *ps_pad_mgr = &ps_dec->s_pad_mgr; /*BS is done for all mbs while parsing*/ u4_max_addr = (ps_dec->u2_frm_wd_in_mbs * ps_dec->u2_frm_ht_in_mbs) - 1; ps_dec->u4_cur_bs_mb_num = u4_max_addr + 1; ih264d_init_deblk_tfr_ctxt(ps_dec, ps_pad_mgr, ps_tfr_cxt, ps_dec->u2_frm_wd_in_mbs, 0); u4_num_mbs = u4_max_addr - ps_dec->u4_cur_deblk_mb_num + 1; DEBUG_PERF_PRINTF("mbs left for deblocking= %d \n",u4_num_mbs); if(u4_num_mbs != 0) ih264d_check_mb_map_deblk(ps_dec, u4_num_mbs, ps_tfr_cxt,1); ps_dec->u4_start_recon_deblk = 0; } } /*signal the decode thread*/ ih264d_signal_decode_thread(ps_dec); /* close deblock thread if it is not closed yet*/ if(ps_dec->u4_num_cores == 3) { ih264d_signal_bs_deblk_thread(ps_dec); } } DATA_SYNC(); if((ps_dec_op->u4_error_code & 0xff) != ERROR_DYNAMIC_RESOLUTION_NOT_SUPPORTED) { ps_dec_op->u4_pic_wd = (UWORD32)ps_dec->u2_disp_width; ps_dec_op->u4_pic_ht = (UWORD32)ps_dec->u2_disp_height; ps_dec_op->i4_reorder_depth = ps_dec->i4_reorder_depth; } //Report if header (sps and pps) has not been decoded yet if(ps_dec->i4_decode_header == 1 && ps_dec->i4_header_decoded != 3) { ps_dec_op->u4_error_code |= (1 << IVD_INSUFFICIENTDATA); api_ret_value = IV_FAIL; } if((ps_dec->u4_pic_buf_got == 1) && (ERROR_DANGLING_FIELD_IN_PIC != i4_err_status)) { /* * For field pictures, set the bottom and top picture decoded u4_flag correctly. */ if(ps_dec->ps_cur_slice->u1_field_pic_flag) { if(1 == ps_dec->ps_cur_slice->u1_bottom_field_flag) { ps_dec->u1_top_bottom_decoded |= BOT_FIELD_ONLY; } else { ps_dec->u1_top_bottom_decoded |= TOP_FIELD_ONLY; } } else { ps_dec->u1_top_bottom_decoded = TOP_FIELD_ONLY | BOT_FIELD_ONLY; } /* if new frame in not found (if we are still getting slices from previous frame) * ih264d_deblock_display is not called. Such frames will not be added to reference /display */ if ((ps_dec->ps_dec_err_status->u1_err_flag & REJECT_CUR_PIC) == 0) { /* Calling Function to deblock Picture and Display */ ret = ih264d_deblock_display(ps_dec); } /*set to complete ,as we dont support partial frame decode*/ if(ps_dec->i4_header_decoded == 3) { ps_dec->u2_total_mbs_coded = ps_dec->ps_cur_sps->u2_max_mb_addr + 1; } /*Update the i4_frametype at the end of picture*/ if(ps_dec->ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL) { ps_dec->i4_frametype = IV_IDR_FRAME; } else if(ps_dec->i4_pic_type == B_SLICE) { ps_dec->i4_frametype = IV_B_FRAME; } else if(ps_dec->i4_pic_type == P_SLICE) { ps_dec->i4_frametype = IV_P_FRAME; } else if(ps_dec->i4_pic_type == I_SLICE) { ps_dec->i4_frametype = IV_I_FRAME; } else { H264_DEC_DEBUG_PRINT("Shouldn't come here\n"); } //Update the content type ps_dec->i4_content_type = ps_dec->ps_cur_slice->u1_field_pic_flag; ps_dec->u4_total_frames_decoded = ps_dec->u4_total_frames_decoded + 2; ps_dec->u4_total_frames_decoded = ps_dec->u4_total_frames_decoded - ps_dec->ps_cur_slice->u1_field_pic_flag; } /* close deblock thread if it is not closed yet*/ if(ps_dec->u4_num_cores == 3) { ih264d_signal_bs_deblk_thread(ps_dec); } { /* In case the decoder is configured to run in low delay mode, * then get display buffer and then format convert. * Note in this mode, format conversion does not run paralelly in a thread and adds to the codec cycles */ if((IVD_DECODE_FRAME_OUT == ps_dec->e_frm_out_mode) && ps_dec->u1_init_dec_flag) { ih264d_get_next_display_field(ps_dec, ps_dec->ps_out_buffer, &(ps_dec->s_disp_op)); if(0 == ps_dec->s_disp_op.u4_error_code) { ps_dec->u4_fmt_conv_cur_row = 0; ps_dec->u4_output_present = 1; } } ih264d_fill_output_struct_from_context(ps_dec, ps_dec_op); /* If Format conversion is not complete, complete it here */ if(ps_dec->u4_output_present && (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) { ps_dec->u4_fmt_conv_num_rows = ps_dec->s_disp_frame_info.u4_y_ht - ps_dec->u4_fmt_conv_cur_row; ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), ps_dec->u4_fmt_conv_cur_row, ps_dec->u4_fmt_conv_num_rows); ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; } ih264d_release_display_field(ps_dec, &(ps_dec->s_disp_op)); } if(ps_dec->i4_decode_header == 1 && (ps_dec->i4_header_decoded & 1) == 1) { ps_dec_op->u4_progressive_frame_flag = 1; if((NULL != ps_dec->ps_cur_sps) && (1 == (ps_dec->ps_cur_sps->u1_is_valid))) { if((0 == ps_dec->ps_sps->u1_frame_mbs_only_flag) && (0 == ps_dec->ps_sps->u1_mb_aff_flag)) ps_dec_op->u4_progressive_frame_flag = 0; } } if((TOP_FIELD_ONLY | BOT_FIELD_ONLY) == ps_dec->u1_top_bottom_decoded) { ps_dec->u1_top_bottom_decoded = 0; } /*--------------------------------------------------------------------*/ /* Do End of Pic processing. */ /* Should be called only if frame was decoded in previous process call*/ /*--------------------------------------------------------------------*/ if(ps_dec->u4_pic_buf_got == 1) { if(1 == ps_dec->u1_last_pic_not_decoded) { ret = ih264d_end_of_pic_dispbuf_mgr(ps_dec); if(ret != OK) return ret; ret = ih264d_end_of_pic(ps_dec); if(ret != OK) return ret; } else { ret = ih264d_end_of_pic(ps_dec); if(ret != OK) return ret; } } /*Data memory barrier instruction,so that yuv write by the library is complete*/ DATA_SYNC(); H264_DEC_DEBUG_PRINT("The num bytes consumed: %d\n", ps_dec_op->u4_num_bytes_consumed); return api_ret_value; } WORD32 ih264d_get_version(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { char version_string[MAXVERSION_STRLEN + 1]; UWORD32 version_string_len; ivd_ctl_getversioninfo_ip_t *ps_ip; ivd_ctl_getversioninfo_op_t *ps_op; ps_ip = (ivd_ctl_getversioninfo_ip_t *)pv_api_ip; ps_op = (ivd_ctl_getversioninfo_op_t *)pv_api_op; UNUSED(dec_hdl); ps_op->u4_error_code = IV_SUCCESS; VERSION(version_string, CODEC_NAME, CODEC_RELEASE_TYPE, CODEC_RELEASE_VER, CODEC_VENDOR); if((WORD32)ps_ip->u4_version_buffer_size <= 0) { ps_op->u4_error_code = IH264D_VERS_BUF_INSUFFICIENT; return (IV_FAIL); } version_string_len = strnlen(version_string, MAXVERSION_STRLEN) + 1; if(ps_ip->u4_version_buffer_size >= version_string_len) //(WORD32)sizeof(sizeof(version_string))) { memcpy(ps_ip->pv_version_buffer, version_string, version_string_len); ps_op->u4_error_code = IV_SUCCESS; } else { ps_op->u4_error_code = IH264D_VERS_BUF_INSUFFICIENT; return IV_FAIL; } return (IV_SUCCESS); } /*****************************************************************************/ /* */ /* Function Name : ih264d_get_display_frame */ /* */ /* Description : */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_get_display_frame(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { UNUSED(dec_hdl); UNUSED(pv_api_ip); UNUSED(pv_api_op); // This function is no longer needed, output is returned in the process() return IV_FAIL; } /*****************************************************************************/ /* */ /* Function Name : ih264d_set_display_frame */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_set_display_frame(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { UWORD32 u4_disp_buf_size[3], u4_num_disp_bufs; ivd_set_display_frame_ip_t *dec_disp_ip; ivd_set_display_frame_op_t *dec_disp_op; UWORD32 i; dec_struct_t * ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); dec_disp_ip = (ivd_set_display_frame_ip_t *)pv_api_ip; dec_disp_op = (ivd_set_display_frame_op_t *)pv_api_op; dec_disp_op->u4_error_code = 0; ps_dec->u4_num_disp_bufs = 0; if(ps_dec->u4_share_disp_buf) { UWORD32 u4_num_bufs = dec_disp_ip->num_disp_bufs; u4_num_bufs = MIN(u4_num_bufs, MAX_DISP_BUFS_NEW); ps_dec->u4_num_disp_bufs = u4_num_bufs; /* Get the number and sizes of the first buffer. Compare this with the * rest to make sure all the buffers are of the same size. */ u4_num_disp_bufs = dec_disp_ip->s_disp_buffer[0].u4_num_bufs; u4_disp_buf_size[0] = dec_disp_ip->s_disp_buffer[0].u4_min_out_buf_size[0]; u4_disp_buf_size[1] = dec_disp_ip->s_disp_buffer[0].u4_min_out_buf_size[1]; u4_disp_buf_size[2] = dec_disp_ip->s_disp_buffer[0].u4_min_out_buf_size[2]; for(i = 0; i < u4_num_bufs; i++) { if(dec_disp_ip->s_disp_buffer[i].u4_num_bufs != u4_num_disp_bufs) { return IV_FAIL; } if((dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[0] != u4_disp_buf_size[0]) || (dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[1] != u4_disp_buf_size[1]) || (dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[2] != u4_disp_buf_size[2])) { return IV_FAIL; } ps_dec->disp_bufs[i].u4_num_bufs = dec_disp_ip->s_disp_buffer[i].u4_num_bufs; ps_dec->disp_bufs[i].buf[0] = dec_disp_ip->s_disp_buffer[i].pu1_bufs[0]; ps_dec->disp_bufs[i].buf[1] = dec_disp_ip->s_disp_buffer[i].pu1_bufs[1]; ps_dec->disp_bufs[i].buf[2] = dec_disp_ip->s_disp_buffer[i].pu1_bufs[2]; ps_dec->disp_bufs[i].u4_bufsize[0] = dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[0]; ps_dec->disp_bufs[i].u4_bufsize[1] = dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[1]; ps_dec->disp_bufs[i].u4_bufsize[2] = dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[2]; } } return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_set_flush_mode */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Globals : <Does it use any global variables?> */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_set_flush_mode(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { dec_struct_t * ps_dec; ivd_ctl_flush_op_t *ps_ctl_op = (ivd_ctl_flush_op_t*)pv_api_op; ps_ctl_op->u4_error_code = 0; ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); UNUSED(pv_api_ip); /* ! */ /* Signal flush frame control call */ ps_dec->u1_flushfrm = 1; if(ps_dec->u1_init_dec_flag == 1) { ih264d_release_pics_in_dpb((void *)ps_dec, ps_dec->u1_pic_bufs); ih264d_release_display_bufs(ps_dec); } ps_ctl_op->u4_error_code = 0; /* Ignore dangling fields during flush */ ps_dec->u1_top_bottom_decoded = 0; return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_get_status */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Globals : <Does it use any global variables?> */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_get_status(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { UWORD32 i; dec_struct_t * ps_dec; UWORD32 pic_wd, pic_ht; ivd_ctl_getstatus_op_t *ps_ctl_op = (ivd_ctl_getstatus_op_t*)pv_api_op; UNUSED(pv_api_ip); ps_ctl_op->u4_error_code = 0; ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); if((NULL != ps_dec->ps_cur_sps) && (1 == (ps_dec->ps_cur_sps->u1_is_valid))) { ps_ctl_op->u4_pic_ht = ps_dec->u2_disp_height; ps_ctl_op->u4_pic_wd = ps_dec->u2_disp_width; if(0 == ps_dec->u4_share_disp_buf) { pic_wd = ps_dec->u2_disp_width; pic_ht = ps_dec->u2_disp_height; } else { pic_wd = ps_dec->u2_frm_wd_y; pic_ht = ps_dec->u2_frm_ht_y; } } else { pic_wd = 0; pic_ht = 0; ps_ctl_op->u4_pic_ht = pic_wd; ps_ctl_op->u4_pic_wd = pic_ht; if(1 == ps_dec->u4_share_disp_buf) { pic_wd += (PAD_LEN_Y_H << 1); pic_ht += (PAD_LEN_Y_V << 2); } } if(ps_dec->u4_app_disp_width > pic_wd) pic_wd = ps_dec->u4_app_disp_width; if(0 == ps_dec->u4_share_disp_buf) ps_ctl_op->u4_num_disp_bufs = 1; else { if((NULL != ps_dec->ps_cur_sps) && (1 == (ps_dec->ps_cur_sps->u1_is_valid))) { if((ps_dec->ps_cur_sps->u1_vui_parameters_present_flag == 1) && (1 == ps_dec->ps_cur_sps->s_vui.u1_bitstream_restriction_flag)) { ps_ctl_op->u4_num_disp_bufs = ps_dec->ps_cur_sps->s_vui.u4_num_reorder_frames + 1; } else { /*if VUI is not present assume maximum possible refrence frames for the level, * as max reorder frames*/ ps_ctl_op->u4_num_disp_bufs = ih264d_get_dpb_size(ps_dec->ps_cur_sps); } ps_ctl_op->u4_num_disp_bufs += ps_dec->ps_cur_sps->u1_num_ref_frames + 1; } else { ps_ctl_op->u4_num_disp_bufs = 32; } ps_ctl_op->u4_num_disp_bufs = MAX( ps_ctl_op->u4_num_disp_bufs, 6); ps_ctl_op->u4_num_disp_bufs = MIN( ps_ctl_op->u4_num_disp_bufs, 32); } ps_ctl_op->u4_error_code = ps_dec->i4_error_code; ps_ctl_op->u4_frame_rate = 0; //make it proper ps_ctl_op->u4_bit_rate = 0; //make it proper ps_ctl_op->e_content_type = ps_dec->i4_content_type; ps_ctl_op->e_output_chroma_format = ps_dec->u1_chroma_format; ps_ctl_op->u4_min_num_in_bufs = MIN_IN_BUFS; if(ps_dec->u1_chroma_format == IV_YUV_420P) { ps_ctl_op->u4_min_num_out_bufs = MIN_OUT_BUFS_420; } else if(ps_dec->u1_chroma_format == IV_YUV_422ILE) { ps_ctl_op->u4_min_num_out_bufs = MIN_OUT_BUFS_422ILE; } else if(ps_dec->u1_chroma_format == IV_RGB_565) { ps_ctl_op->u4_min_num_out_bufs = MIN_OUT_BUFS_RGB565; } else if((ps_dec->u1_chroma_format == IV_YUV_420SP_UV) || (ps_dec->u1_chroma_format == IV_YUV_420SP_VU)) { ps_ctl_op->u4_min_num_out_bufs = MIN_OUT_BUFS_420SP; } else { //Invalid chroma format; Error code may be updated, verify in testing if needed ps_ctl_op->u4_error_code = ERROR_FEATURE_UNAVAIL; return IV_FAIL; } for(i = 0; i < ps_ctl_op->u4_min_num_in_bufs; i++) { ps_ctl_op->u4_min_in_buf_size[i] = MAX(256000, pic_wd * pic_ht * 3 / 2); } /*!*/ if(ps_dec->u1_chroma_format == IV_YUV_420P) { ps_ctl_op->u4_min_out_buf_size[0] = (pic_wd * pic_ht); ps_ctl_op->u4_min_out_buf_size[1] = (pic_wd * pic_ht) >> 2; ps_ctl_op->u4_min_out_buf_size[2] = (pic_wd * pic_ht) >> 2; } else if(ps_dec->u1_chroma_format == IV_YUV_422ILE) { ps_ctl_op->u4_min_out_buf_size[0] = (pic_wd * pic_ht) * 2; ps_ctl_op->u4_min_out_buf_size[1] = ps_ctl_op->u4_min_out_buf_size[2] = 0; } else if(ps_dec->u1_chroma_format == IV_RGB_565) { ps_ctl_op->u4_min_out_buf_size[0] = (pic_wd * pic_ht) * 2; ps_ctl_op->u4_min_out_buf_size[1] = ps_ctl_op->u4_min_out_buf_size[2] = 0; } else if((ps_dec->u1_chroma_format == IV_YUV_420SP_UV) || (ps_dec->u1_chroma_format == IV_YUV_420SP_VU)) { ps_ctl_op->u4_min_out_buf_size[0] = (pic_wd * pic_ht); ps_ctl_op->u4_min_out_buf_size[1] = (pic_wd * pic_ht) >> 1; ps_ctl_op->u4_min_out_buf_size[2] = 0; } ps_dec->u4_num_disp_bufs_requested = ps_ctl_op->u4_num_disp_bufs; return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_get_buf_info */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Globals : <Does it use any global variables?> */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_get_buf_info(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { dec_struct_t * ps_dec; UWORD8 i = 0; // Default for 420P format UWORD16 pic_wd, pic_ht; ivd_ctl_getbufinfo_op_t *ps_ctl_op = (ivd_ctl_getbufinfo_op_t*)pv_api_op; UWORD32 au4_min_out_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; UNUSED(pv_api_ip); ps_ctl_op->u4_error_code = 0; ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); ps_ctl_op->u4_min_num_in_bufs = MIN_IN_BUFS; ps_ctl_op->u4_num_disp_bufs = 1; pic_wd = 0; pic_ht = 0; if(ps_dec->i4_header_decoded == 3) { if(0 == ps_dec->u4_share_disp_buf) { pic_wd = ps_dec->u2_disp_width; pic_ht = ps_dec->u2_disp_height; } else { pic_wd = ps_dec->u2_frm_wd_y; pic_ht = ps_dec->u2_frm_ht_y; } } for(i = 0; i < ps_ctl_op->u4_min_num_in_bufs; i++) { ps_ctl_op->u4_min_in_buf_size[i] = MAX(256000, pic_wd * pic_ht * 3 / 2); } if((WORD32)ps_dec->u4_app_disp_width > pic_wd) pic_wd = ps_dec->u4_app_disp_width; if(0 == ps_dec->u4_share_disp_buf) ps_ctl_op->u4_num_disp_bufs = 1; else { if((NULL != ps_dec->ps_cur_sps) && (1 == (ps_dec->ps_cur_sps->u1_is_valid))) { if((ps_dec->ps_cur_sps->u1_vui_parameters_present_flag == 1) && (1 == ps_dec->ps_cur_sps->s_vui.u1_bitstream_restriction_flag)) { ps_ctl_op->u4_num_disp_bufs = ps_dec->ps_cur_sps->s_vui.u4_num_reorder_frames + 1; } else { /*if VUI is not present assume maximum possible refrence frames for the level, * as max reorder frames*/ ps_ctl_op->u4_num_disp_bufs = ih264d_get_dpb_size(ps_dec->ps_cur_sps); } ps_ctl_op->u4_num_disp_bufs += ps_dec->ps_cur_sps->u1_num_ref_frames + 1; } else { ps_ctl_op->u4_num_disp_bufs = 32; } ps_ctl_op->u4_num_disp_bufs = MAX( ps_ctl_op->u4_num_disp_bufs, 6); ps_ctl_op->u4_num_disp_bufs = MIN( ps_ctl_op->u4_num_disp_bufs, 32); } ps_ctl_op->u4_min_num_out_bufs = ih264d_get_outbuf_size( pic_wd, pic_ht, ps_dec->u1_chroma_format, &au4_min_out_buf_size[0]); for(i = 0; i < ps_ctl_op->u4_min_num_out_bufs; i++) { ps_ctl_op->u4_min_out_buf_size[i] = au4_min_out_buf_size[i]; } ps_dec->u4_num_disp_bufs_requested = ps_ctl_op->u4_num_disp_bufs; return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_set_params */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_set_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { dec_struct_t * ps_dec; WORD32 ret = IV_SUCCESS; ih264d_ctl_set_config_ip_t *ps_h264d_ctl_ip = (ih264d_ctl_set_config_ip_t *)pv_api_ip; ih264d_ctl_set_config_op_t *ps_h264d_ctl_op = (ih264d_ctl_set_config_op_t *)pv_api_op;; ivd_ctl_set_config_ip_t *ps_ctl_ip = &ps_h264d_ctl_ip->s_ivd_ctl_set_config_ip_t; ivd_ctl_set_config_op_t *ps_ctl_op = &ps_h264d_ctl_op->s_ivd_ctl_set_config_op_t; ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); ps_dec->u4_skip_frm_mask = 0; ps_ctl_op->u4_error_code = 0; if(ps_ctl_ip->e_frm_skip_mode != IVD_SKIP_NONE) { ps_ctl_op->u4_error_code = (1 << IVD_UNSUPPORTEDPARAM); ret = IV_FAIL; } if(ps_ctl_ip->u4_disp_wd >= ps_dec->u2_disp_width) { ps_dec->u4_app_disp_width = ps_ctl_ip->u4_disp_wd; } else if(0 == ps_dec->i4_header_decoded) { ps_dec->u4_app_disp_width = ps_ctl_ip->u4_disp_wd; } else if(ps_ctl_ip->u4_disp_wd == 0) { ps_dec->u4_app_disp_width = 0; } else { /* * Set the display width to zero. This will ensure that the wrong value we had stored (0xFFFFFFFF) * does not propogate. */ ps_dec->u4_app_disp_width = 0; ps_ctl_op->u4_error_code |= (1 << IVD_UNSUPPORTEDPARAM); ps_ctl_op->u4_error_code |= ERROR_DISP_WIDTH_INVALID; ret = IV_FAIL; } if(ps_ctl_ip->e_vid_dec_mode == IVD_DECODE_FRAME) ps_dec->i4_decode_header = 0; else if(ps_ctl_ip->e_vid_dec_mode == IVD_DECODE_HEADER) ps_dec->i4_decode_header = 1; else { ps_ctl_op->u4_error_code = (1 << IVD_UNSUPPORTEDPARAM); ps_dec->i4_decode_header = 1; ret = IV_FAIL; } ps_dec->e_frm_out_mode = IVD_DISPLAY_FRAME_OUT; if((ps_ctl_ip->e_frm_out_mode != IVD_DECODE_FRAME_OUT) && (ps_ctl_ip->e_frm_out_mode != IVD_DISPLAY_FRAME_OUT)) { ps_ctl_op->u4_error_code = (1 << IVD_UNSUPPORTEDPARAM); ret = IV_FAIL; } ps_dec->e_frm_out_mode = ps_ctl_ip->e_frm_out_mode; return ret; } /*****************************************************************************/ /* */ /* Function Name : ih264d_set_default_params */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 08 08 2011 100421 Copied from set_params */ /* */ /*****************************************************************************/ WORD32 ih264d_set_default_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { dec_struct_t * ps_dec; WORD32 ret = IV_SUCCESS; ivd_ctl_set_config_op_t *ps_ctl_op = (ivd_ctl_set_config_op_t *)pv_api_op; ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); UNUSED(pv_api_ip); { ps_dec->u4_app_disp_width = 0; ps_dec->u4_skip_frm_mask = 0; ps_dec->i4_decode_header = 1; ps_ctl_op->u4_error_code = 0; } return ret; } /*****************************************************************************/ /* */ /* Function Name : ih264d_reset */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Globals : <Does it use any global variables?> */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_delete(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { dec_struct_t *ps_dec; ih264d_delete_ip_t *ps_ip = (ih264d_delete_ip_t *)pv_api_ip; ih264d_delete_op_t *ps_op = (ih264d_delete_op_t *)pv_api_op; ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); UNUSED(ps_ip); ps_op->s_ivd_delete_op_t.u4_error_code = 0; ih264d_free_dynamic_bufs(ps_dec); ih264d_free_static_bufs(dec_hdl); return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_reset */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Globals : <Does it use any global variables?> */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_reset(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { dec_struct_t * ps_dec; ivd_ctl_reset_op_t *ps_ctl_op = (ivd_ctl_reset_op_t *)pv_api_op; UNUSED(pv_api_ip); ps_ctl_op->u4_error_code = 0; ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); if(ps_dec != NULL) { ih264d_init_decoder(ps_dec); } else { H264_DEC_DEBUG_PRINT( "\nReset called without Initializing the decoder\n"); ps_ctl_op->u4_error_code = ERROR_INIT_NOT_DONE; } return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_ctl */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_ctl(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ivd_ctl_set_config_ip_t *ps_ctl_ip; ivd_ctl_set_config_op_t *ps_ctl_op; WORD32 ret = IV_SUCCESS; UWORD32 subcommand; dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; if(ps_dec->init_done != 1) { //Return proper Error Code return IV_FAIL; } ps_ctl_ip = (ivd_ctl_set_config_ip_t*)pv_api_ip; ps_ctl_op = (ivd_ctl_set_config_op_t*)pv_api_op; ps_ctl_op->u4_error_code = 0; subcommand = ps_ctl_ip->e_sub_cmd; switch(subcommand) { case IVD_CMD_CTL_GETPARAMS: ret = ih264d_get_status(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_CTL_SETPARAMS: ret = ih264d_set_params(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_CTL_RESET: ret = ih264d_reset(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_CTL_SETDEFAULT: ret = ih264d_set_default_params(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_CTL_FLUSH: ret = ih264d_set_flush_mode(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_CTL_GETBUFINFO: ret = ih264d_get_buf_info(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_CTL_GETVERSION: ret = ih264d_get_version(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IH264D_CMD_CTL_DEGRADE: ret = ih264d_set_degrade(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IH264D_CMD_CTL_SET_NUM_CORES: ret = ih264d_set_num_cores(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IH264D_CMD_CTL_GET_BUFFER_DIMENSIONS: ret = ih264d_get_frame_dimensions(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IH264D_CMD_CTL_GET_VUI_PARAMS: ret = ih264d_get_vui_params(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IH264D_CMD_CTL_GET_SEI_MDCV_PARAMS: ret = ih264d_get_sei_mdcv_params(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IH264D_CMD_CTL_GET_SEI_CLL_PARAMS: ret = ih264d_get_sei_cll_params(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IH264D_CMD_CTL_GET_SEI_AVE_PARAMS: ret = ih264d_get_sei_ave_params(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IH264D_CMD_CTL_GET_SEI_CCV_PARAMS: ret = ih264d_get_sei_ccv_params(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IH264D_CMD_CTL_SET_PROCESSOR: ret = ih264d_set_processor(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; default: H264_DEC_DEBUG_PRINT("\ndo nothing\n") ; break; } return ret; } /*****************************************************************************/ /* */ /* Function Name : ih264d_rel_display_frame */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_rel_display_frame(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ivd_rel_display_frame_ip_t *ps_rel_ip; ivd_rel_display_frame_op_t *ps_rel_op; UWORD32 buf_released = 0; UWORD32 u4_ts = 0; dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; ps_rel_ip = (ivd_rel_display_frame_ip_t *)pv_api_ip; ps_rel_op = (ivd_rel_display_frame_op_t *)pv_api_op; ps_rel_op->u4_error_code = 0; u4_ts = ps_rel_ip->u4_disp_buf_id; if(0 == ps_dec->u4_share_disp_buf) { ps_dec->u4_disp_buf_mapping[u4_ts] = 0; ps_dec->u4_disp_buf_to_be_freed[u4_ts] = 0; return IV_SUCCESS; } if(ps_dec->pv_pic_buf_mgr != NULL) { if(1 == ps_dec->u4_disp_buf_mapping[u4_ts]) { ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, ps_rel_ip->u4_disp_buf_id, BUF_MGR_IO); ps_dec->u4_disp_buf_mapping[u4_ts] = 0; buf_released = 1; } } if((1 == ps_dec->u4_share_disp_buf) && (0 == buf_released)) ps_dec->u4_disp_buf_to_be_freed[u4_ts] = 1; return IV_SUCCESS; } /** ******************************************************************************* * * @brief * Sets degrade params * * @par Description: * Sets degrade params. * Refer to ih264d_ctl_degrade_ip_t definition for details * * @param[in] ps_codec_obj * Pointer to codec object at API level * * @param[in] pv_api_ip * Pointer to input argument structure * * @param[out] pv_api_op * Pointer to output argument structure * * @returns Status * * @remarks * * ******************************************************************************* */ WORD32 ih264d_set_degrade(iv_obj_t *ps_codec_obj, void *pv_api_ip, void *pv_api_op) { ih264d_ctl_degrade_ip_t *ps_ip; ih264d_ctl_degrade_op_t *ps_op; dec_struct_t *ps_codec = (dec_struct_t *)ps_codec_obj->pv_codec_handle; ps_ip = (ih264d_ctl_degrade_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_degrade_op_t *)pv_api_op; ps_codec->i4_degrade_type = ps_ip->i4_degrade_type; ps_codec->i4_nondegrade_interval = ps_ip->i4_nondegrade_interval; ps_codec->i4_degrade_pics = ps_ip->i4_degrade_pics; ps_op->u4_error_code = 0; ps_codec->i4_degrade_pic_cnt = 0; return IV_SUCCESS; } WORD32 ih264d_get_frame_dimensions(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ih264d_ctl_get_frame_dimensions_ip_t *ps_ip; ih264d_ctl_get_frame_dimensions_op_t *ps_op; dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; UWORD32 disp_wd, disp_ht, buffer_wd, buffer_ht, x_offset, y_offset; ps_ip = (ih264d_ctl_get_frame_dimensions_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_frame_dimensions_op_t *)pv_api_op; UNUSED(ps_ip); if((NULL != ps_dec->ps_cur_sps) && (1 == (ps_dec->ps_cur_sps->u1_is_valid))) { disp_wd = ps_dec->u2_disp_width; disp_ht = ps_dec->u2_disp_height; if(0 == ps_dec->u4_share_disp_buf) { buffer_wd = disp_wd; buffer_ht = disp_ht; } else { buffer_wd = ps_dec->u2_frm_wd_y; buffer_ht = ps_dec->u2_frm_ht_y; } } else { disp_wd = 0; disp_ht = 0; if(0 == ps_dec->u4_share_disp_buf) { buffer_wd = disp_wd; buffer_ht = disp_ht; } else { buffer_wd = ALIGN16(disp_wd) + (PAD_LEN_Y_H << 1); buffer_ht = ALIGN16(disp_ht) + (PAD_LEN_Y_V << 2); } } if(ps_dec->u4_app_disp_width > buffer_wd) buffer_wd = ps_dec->u4_app_disp_width; if(0 == ps_dec->u4_share_disp_buf) { x_offset = 0; y_offset = 0; } else { y_offset = (PAD_LEN_Y_V << 1); x_offset = PAD_LEN_Y_H; if((NULL != ps_dec->ps_sps) && (1 == (ps_dec->ps_sps->u1_is_valid)) && (0 != ps_dec->u2_crop_offset_y)) { y_offset += ps_dec->u2_crop_offset_y / ps_dec->u2_frm_wd_y; x_offset += ps_dec->u2_crop_offset_y % ps_dec->u2_frm_wd_y; } } ps_op->u4_disp_wd[0] = disp_wd; ps_op->u4_disp_ht[0] = disp_ht; ps_op->u4_buffer_wd[0] = buffer_wd; ps_op->u4_buffer_ht[0] = buffer_ht; ps_op->u4_x_offset[0] = x_offset; ps_op->u4_y_offset[0] = y_offset; ps_op->u4_disp_wd[1] = ps_op->u4_disp_wd[2] = ((ps_op->u4_disp_wd[0] + 1) >> 1); ps_op->u4_disp_ht[1] = ps_op->u4_disp_ht[2] = ((ps_op->u4_disp_ht[0] + 1) >> 1); ps_op->u4_buffer_wd[1] = ps_op->u4_buffer_wd[2] = (ps_op->u4_buffer_wd[0] >> 1); ps_op->u4_buffer_ht[1] = ps_op->u4_buffer_ht[2] = (ps_op->u4_buffer_ht[0] >> 1); ps_op->u4_x_offset[1] = ps_op->u4_x_offset[2] = (ps_op->u4_x_offset[0] >> 1); ps_op->u4_y_offset[1] = ps_op->u4_y_offset[2] = (ps_op->u4_y_offset[0] >> 1); if((ps_dec->u1_chroma_format == IV_YUV_420SP_UV) || (ps_dec->u1_chroma_format == IV_YUV_420SP_VU)) { ps_op->u4_disp_wd[2] = 0; ps_op->u4_disp_ht[2] = 0; ps_op->u4_buffer_wd[2] = 0; ps_op->u4_buffer_ht[2] = 0; ps_op->u4_x_offset[2] = 0; ps_op->u4_y_offset[2] = 0; ps_op->u4_disp_wd[1] <<= 1; ps_op->u4_buffer_wd[1] <<= 1; ps_op->u4_x_offset[1] <<= 1; } return IV_SUCCESS; } WORD32 ih264d_get_vui_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ih264d_ctl_get_vui_params_ip_t *ps_ip; ih264d_ctl_get_vui_params_op_t *ps_op; dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; dec_seq_params_t *ps_sps; vui_t *ps_vui; WORD32 i; UWORD32 u4_size; ps_ip = (ih264d_ctl_get_vui_params_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_vui_params_op_t *)pv_api_op; UNUSED(ps_ip); u4_size = ps_op->u4_size; memset(ps_op, 0, sizeof(ih264d_ctl_get_vui_params_op_t)); ps_op->u4_size = u4_size; if(NULL == ps_dec->ps_cur_sps) { ps_op->u4_error_code = ERROR_VUI_PARAMS_NOT_FOUND; return IV_FAIL; } ps_sps = ps_dec->ps_cur_sps; if((0 == ps_sps->u1_is_valid) || (0 == ps_sps->u1_vui_parameters_present_flag)) { ps_op->u4_error_code = ERROR_VUI_PARAMS_NOT_FOUND; return IV_FAIL; } ps_vui = &ps_sps->s_vui; ps_op->u1_aspect_ratio_idc = ps_vui->u1_aspect_ratio_idc; ps_op->u2_sar_width = ps_vui->u2_sar_width; ps_op->u2_sar_height = ps_vui->u2_sar_height; ps_op->u1_overscan_appropriate_flag = ps_vui->u1_overscan_appropriate_flag; ps_op->u1_video_format = ps_vui->u1_video_format; ps_op->u1_video_full_range_flag = ps_vui->u1_video_full_range_flag; ps_op->u1_colour_primaries = ps_vui->u1_colour_primaries; ps_op->u1_tfr_chars = ps_vui->u1_tfr_chars; ps_op->u1_matrix_coeffs = ps_vui->u1_matrix_coeffs; ps_op->u1_cr_top_field = ps_vui->u1_cr_top_field; ps_op->u1_cr_bottom_field = ps_vui->u1_cr_bottom_field; ps_op->u4_num_units_in_tick = ps_vui->u4_num_units_in_tick; ps_op->u4_time_scale = ps_vui->u4_time_scale; ps_op->u1_fixed_frame_rate_flag = ps_vui->u1_fixed_frame_rate_flag; ps_op->u1_nal_hrd_params_present = ps_vui->u1_nal_hrd_params_present; ps_op->u1_vcl_hrd_params_present = ps_vui->u1_vcl_hrd_params_present; ps_op->u1_low_delay_hrd_flag = ps_vui->u1_low_delay_hrd_flag; ps_op->u1_pic_struct_present_flag = ps_vui->u1_pic_struct_present_flag; ps_op->u1_bitstream_restriction_flag = ps_vui->u1_bitstream_restriction_flag; ps_op->u1_mv_over_pic_boundaries_flag = ps_vui->u1_mv_over_pic_boundaries_flag; ps_op->u4_max_bytes_per_pic_denom = ps_vui->u4_max_bytes_per_pic_denom; ps_op->u4_max_bits_per_mb_denom = ps_vui->u4_max_bits_per_mb_denom; ps_op->u4_log2_max_mv_length_horz = ps_vui->u4_log2_max_mv_length_horz; ps_op->u4_log2_max_mv_length_vert = ps_vui->u4_log2_max_mv_length_vert; ps_op->u4_num_reorder_frames = ps_vui->u4_num_reorder_frames; ps_op->u4_max_dec_frame_buffering = ps_vui->u4_max_dec_frame_buffering; return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_get_sei_mdcv_params */ /* */ /* Description : This function populates SEI mdcv message in */ /* output structure */ /* Inputs : iv_obj_t decoder handle */ /* : pv_api_ip pointer to input structure */ /* : pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : returns 0; 1 with error code when MDCV is not present */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* */ /* */ /*****************************************************************************/ WORD32 ih264d_get_sei_mdcv_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ih264d_ctl_get_sei_mdcv_params_ip_t *ps_ip; ih264d_ctl_get_sei_mdcv_params_op_t *ps_op; dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; sei_mdcv_params_t *ps_sei_mdcv; WORD32 i4_count; ps_ip = (ih264d_ctl_get_sei_mdcv_params_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_sei_mdcv_params_op_t *)pv_api_op; UNUSED(ps_ip); if(0 == ps_dec->s_sei_export.u1_sei_mdcv_params_present_flag) { ps_op->u4_error_code = ERROR_SEI_MDCV_PARAMS_NOT_FOUND; return IV_FAIL; } ps_sei_mdcv = &ps_dec->s_sei_export.s_sei_mdcv_params; for(i4_count = 0; i4_count < NUM_SEI_MDCV_PRIMARIES; i4_count++) { ps_op->au2_display_primaries_x[i4_count] = ps_sei_mdcv->au2_display_primaries_x[i4_count]; ps_op->au2_display_primaries_y[i4_count] = ps_sei_mdcv->au2_display_primaries_y[i4_count]; } ps_op->u2_white_point_x = ps_sei_mdcv->u2_white_point_x; ps_op->u2_white_point_y = ps_sei_mdcv->u2_white_point_y; ps_op->u4_max_display_mastering_luminance = ps_sei_mdcv->u4_max_display_mastering_luminance; ps_op->u4_min_display_mastering_luminance = ps_sei_mdcv->u4_min_display_mastering_luminance; return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_get_sei_cll_params */ /* */ /* Description : This function populates SEI cll message in */ /* output structure */ /* Inputs : iv_obj_t decoder handle */ /* : pv_api_ip pointer to input structure */ /* : pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : returns 0; 1 with error code when CLL is not present */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* */ /* */ /*****************************************************************************/ WORD32 ih264d_get_sei_cll_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ih264d_ctl_get_sei_cll_params_ip_t *ps_ip; ih264d_ctl_get_sei_cll_params_op_t *ps_op; dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; sei_cll_params_t *ps_sei_cll; ps_ip = (ih264d_ctl_get_sei_cll_params_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_sei_cll_params_op_t *)pv_api_op; UNUSED(ps_ip); if(0 == ps_dec->s_sei_export.u1_sei_cll_params_present_flag) { ps_op->u4_error_code = ERROR_SEI_CLL_PARAMS_NOT_FOUND; return IV_FAIL; } ps_sei_cll = &ps_dec->s_sei_export.s_sei_cll_params; ps_op->u2_max_content_light_level = ps_sei_cll->u2_max_content_light_level; ps_op->u2_max_pic_average_light_level = ps_sei_cll->u2_max_pic_average_light_level; return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_get_sei_ave_params */ /* */ /* Description : This function populates SEI ave message in */ /* output structure */ /* Inputs : iv_obj_t decoder handle */ /* : pv_api_ip pointer to input structure */ /* : pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : returns 0; 1 with error code when AVE is not present */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* */ /* */ /*****************************************************************************/ WORD32 ih264d_get_sei_ave_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ih264d_ctl_get_sei_ave_params_ip_t *ps_ip; ih264d_ctl_get_sei_ave_params_op_t *ps_op; dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; sei_ave_params_t *ps_sei_ave; ps_ip = (ih264d_ctl_get_sei_ave_params_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_sei_ave_params_op_t *)pv_api_op; UNUSED(ps_ip); if(0 == ps_dec->s_sei_export.u1_sei_ave_params_present_flag) { ps_op->u4_error_code = ERROR_SEI_AVE_PARAMS_NOT_FOUND; return IV_FAIL; } ps_sei_ave = &ps_dec->s_sei_export.s_sei_ave_params; ps_op->u4_ambient_illuminance = ps_sei_ave->u4_ambient_illuminance; ps_op->u2_ambient_light_x = ps_sei_ave->u2_ambient_light_x; ps_op->u2_ambient_light_y = ps_sei_ave->u2_ambient_light_y; return IV_SUCCESS; } /*****************************************************************************/ /* */ /* Function Name : ih264d_get_sei_ccv_params */ /* */ /* Description : This function populates SEI mdcv message in */ /* output structure */ /* Inputs : iv_obj_t decoder handle */ /* : pv_api_ip pointer to input structure */ /* : pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : returns 0; 1 with error code when CCV is not present */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* */ /* */ /*****************************************************************************/ WORD32 ih264d_get_sei_ccv_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ih264d_ctl_get_sei_ccv_params_ip_t *ps_ip; ih264d_ctl_get_sei_ccv_params_op_t *ps_op; dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; sei_ccv_params_t *ps_sei_ccv; WORD32 i4_count; ps_ip = (ih264d_ctl_get_sei_ccv_params_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_get_sei_ccv_params_op_t *)pv_api_op; UNUSED(ps_ip); if(0 == ps_dec->s_sei_export.u1_sei_ccv_params_present_flag) { ps_op->u4_error_code = ERROR_SEI_CCV_PARAMS_NOT_FOUND; return IV_FAIL; } ps_sei_ccv = &ps_dec->s_sei_export.s_sei_ccv_params; ps_op->u1_ccv_cancel_flag = ps_sei_ccv->u1_ccv_cancel_flag; if(0 == ps_op->u1_ccv_cancel_flag) { ps_op->u1_ccv_persistence_flag = ps_sei_ccv->u1_ccv_persistence_flag; ps_op->u1_ccv_primaries_present_flag = ps_sei_ccv->u1_ccv_primaries_present_flag; ps_op->u1_ccv_min_luminance_value_present_flag = ps_sei_ccv->u1_ccv_min_luminance_value_present_flag; ps_op->u1_ccv_max_luminance_value_present_flag = ps_sei_ccv->u1_ccv_max_luminance_value_present_flag; ps_op->u1_ccv_avg_luminance_value_present_flag = ps_sei_ccv->u1_ccv_avg_luminance_value_present_flag; ps_op->u1_ccv_reserved_zero_2bits = ps_sei_ccv->u1_ccv_reserved_zero_2bits; if(1 == ps_sei_ccv->u1_ccv_primaries_present_flag) { for(i4_count = 0; i4_count < NUM_SEI_CCV_PRIMARIES; i4_count++) { ps_op->ai4_ccv_primaries_x[i4_count] = ps_sei_ccv->ai4_ccv_primaries_x[i4_count]; ps_op->ai4_ccv_primaries_y[i4_count] = ps_sei_ccv->ai4_ccv_primaries_y[i4_count]; } } if(1 == ps_sei_ccv->u1_ccv_min_luminance_value_present_flag) { ps_op->u4_ccv_min_luminance_value = ps_sei_ccv->u4_ccv_min_luminance_value; } if(1 == ps_sei_ccv->u1_ccv_max_luminance_value_present_flag) { ps_op->u4_ccv_max_luminance_value = ps_sei_ccv->u4_ccv_max_luminance_value; } if(1 == ps_sei_ccv->u1_ccv_avg_luminance_value_present_flag) { ps_op->u4_ccv_avg_luminance_value = ps_sei_ccv->u4_ccv_avg_luminance_value; } } return IV_SUCCESS; } WORD32 ih264d_set_num_cores(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { ih264d_ctl_set_num_cores_ip_t *ps_ip; ih264d_ctl_set_num_cores_op_t *ps_op; dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; ps_ip = (ih264d_ctl_set_num_cores_ip_t *)pv_api_ip; ps_op = (ih264d_ctl_set_num_cores_op_t *)pv_api_op; ps_op->u4_error_code = 0; ps_dec->u4_num_cores = ps_ip->u4_num_cores; if(ps_dec->u4_num_cores == 1) { ps_dec->u1_separate_parse = 0; } else { ps_dec->u1_separate_parse = 1; } /*using only upto three threads currently*/ if(ps_dec->u4_num_cores > 3) ps_dec->u4_num_cores = 3; return IV_SUCCESS; } void ih264d_fill_output_struct_from_context(dec_struct_t *ps_dec, ivd_video_decode_op_t *ps_dec_op) { if((ps_dec_op->u4_error_code & 0xff) != ERROR_DYNAMIC_RESOLUTION_NOT_SUPPORTED) { ps_dec_op->u4_pic_wd = (UWORD32)ps_dec->u2_disp_width; ps_dec_op->u4_pic_ht = (UWORD32)ps_dec->u2_disp_height; } ps_dec_op->i4_reorder_depth = ps_dec->i4_reorder_depth; ps_dec_op->i4_display_index = ps_dec->i4_display_index; ps_dec_op->e_pic_type = ps_dec->i4_frametype; ps_dec_op->u4_new_seq = 0; ps_dec_op->u4_output_present = ps_dec->u4_output_present; ps_dec_op->u4_progressive_frame_flag = ps_dec->s_disp_op.u4_progressive_frame_flag; ps_dec_op->u4_is_ref_flag = 1; if(ps_dec_op->u4_frame_decoded_flag) { if(ps_dec->ps_cur_slice->u1_nal_ref_idc == 0) ps_dec_op->u4_is_ref_flag = 0; } ps_dec_op->e_output_format = ps_dec->s_disp_op.e_output_format; ps_dec_op->s_disp_frm_buf = ps_dec->s_disp_op.s_disp_frm_buf; ps_dec_op->e4_fld_type = ps_dec->s_disp_op.e4_fld_type; ps_dec_op->u4_ts = ps_dec->s_disp_op.u4_ts; ps_dec_op->u4_disp_buf_id = ps_dec->s_disp_op.u4_disp_buf_id; ih264d_export_sei_params(&ps_dec_op->s_sei_decode_op, ps_dec); } /*****************************************************************************/ /* */ /* Function Name : ih264d_api_function */ /* */ /* Description : */ /* */ /* Inputs :iv_obj_t decoder handle */ /* :pv_api_ip pointer to input structure */ /* :pv_api_op pointer to output structure */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 10 2008 100356 Draft */ /* */ /*****************************************************************************/ IV_API_CALL_STATUS_T ih264d_api_function(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) { UWORD32 command; UWORD32 *pu2_ptr_cmd; UWORD32 u4_api_ret; IV_API_CALL_STATUS_T e_status; e_status = api_check_struct_sanity(dec_hdl, pv_api_ip, pv_api_op); if(e_status != IV_SUCCESS) { UWORD32 *ptr_err; ptr_err = (UWORD32 *)pv_api_op; UNUSED(ptr_err); H264_DEC_DEBUG_PRINT("error code = %d\n", *(ptr_err + 1)); return IV_FAIL; } pu2_ptr_cmd = (UWORD32 *)pv_api_ip; pu2_ptr_cmd++; command = *pu2_ptr_cmd; // H264_DEC_DEBUG_PRINT("inside lib = %d\n",command); switch(command) { case IVD_CMD_CREATE: u4_api_ret = ih264d_create(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_DELETE: u4_api_ret = ih264d_delete(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_VIDEO_DECODE: u4_api_ret = ih264d_video_decode(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_GET_DISPLAY_FRAME: u4_api_ret = ih264d_get_display_frame(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_SET_DISPLAY_FRAME: u4_api_ret = ih264d_set_display_frame(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_REL_DISPLAY_FRAME: u4_api_ret = ih264d_rel_display_frame(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; case IVD_CMD_VIDEO_CTL: u4_api_ret = ih264d_ctl(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); break; default: u4_api_ret = IV_FAIL; break; } return u4_api_ret; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_bitstrm.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_bitstrm.c * * \brief * Bitstream parsing routines * * \date * 20/11/2002 * * \author AI ************************************************************************** */ #include <stdlib.h> #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_bitstrm.h" #include "ih264d_error_handler.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_structs.h" /*! ************************************************************************** * \if Function name : ih264d_get_bit_h264 \endif * * \brief * Read one bit from the bitstream. * * This is a Bitstream processing function. It reads the * bit currently pointed by the bit pointer in the * buffer and advances the pointer by one. It returns * the bit (0 or 1) in the form of an unsigned integer. * * \return * Returns the next bit (0 or 1) in the bitstream. * ************************************************************************** */ UWORD8 ih264d_get_bit_h264(dec_bit_stream_t *ps_stream) { UWORD32 u4_code; GETBIT(u4_code, ps_stream->u4_ofst, ps_stream->pu4_buffer); return (u4_code); } /*! ************************************************************************** * \if Function name : ih264d_get_bits_h264 \endif * * \brief * Read specified number of bits from the bitstream. * * This is a Bitstream processing function. It reads the * number specified number of bits from the current bit * position and advances the bit and byte pointers * appropriately. * * \return * An unsigned 32 bit integer with its least significant bits * containing the bits in order of their occurence in the bitstream. * ************************************************************************** */ UWORD32 ih264d_get_bits_h264(dec_bit_stream_t *ps_bitstrm, UWORD32 u4_num_bits) { UWORD32 u4_code = 0; if(u4_num_bits) GETBITS(u4_code, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer, u4_num_bits); return (u4_code); } /*! ************************************************************************** * \if Function name : ih264d_next_bits_h264 \endif * * \brief * Peek specified number of bits from the bitstream. * * This is a Bitstream processing function. It gets the * specified number of bits from the buffer without * altering the current pointers. It is equivalent to * next_bits() function in the standard. * * \return * An unsigned 32 bit integer with its least significant bits * containing the bits in order of their occurence in the bitstream. ************************************************************************** */ UWORD32 ih264d_next_bits_h264(dec_bit_stream_t *ps_bitstrm, UWORD32 u4_num_bits) { UWORD32 u4_word_off = (ps_bitstrm->u4_ofst >> 5); UWORD32 u4_bit_off = ps_bitstrm->u4_ofst & 0x1F; UWORD32 *pu4_bitstream = ps_bitstrm->pu4_buffer; UWORD32 u4_bits = pu4_bitstream[u4_word_off++] << u4_bit_off; /*************************************************************************/ /* Test if number of bits to be read exceeds the number of bits in the */ /* current word. If yes, read from the next word of the buffer, The bits */ /* from both the words are concatenated to get next 32 bits in 'u4_bits' */ /*************************************************************************/ if(u4_bit_off > (INT_IN_BITS - u4_num_bits)) u4_bits |= (pu4_bitstream[u4_word_off] >> (INT_IN_BITS - u4_bit_off)); return ((u4_bits >> (INT_IN_BITS - u4_num_bits))); } /*! ************************************************************************** * \if Function name : ih264d_flush_bits_h264 \endif * * \brief * Flush specified number of bits from the bitstream. * * This function flushes the specified number of bits (marks * as read) from the buffer. * * \return * A 8 bit unsigned integer with value * '1' on successful flush * '0' on failure. * ************************************************************************** */ WORD32 ih264d_flush_bits_h264(dec_bit_stream_t *ps_bitstrm, WORD32 u4_num_bits) { ps_bitstrm->u4_ofst += u4_num_bits; if(ps_bitstrm->u4_ofst > ps_bitstrm->u4_max_ofst) { return ERROR_EOB_FLUSHBITS_T; } return OK; } /*! ************************************************************************** * \if Function name : ih264d_check_byte_aligned \endif * * \brief * Checks whether the bit ps_bitstrm u4_ofst is at byte boundary. * * \param ps_bitstrm : Pointer to bitstream * * \return * Returns 1 if bit ps_bitstrm u4_ofst is at byte alligned position else zero. ************************************************************************** */ UWORD8 ih264d_check_byte_aligned(dec_bit_stream_t * ps_bitstrm) { if(ps_bitstrm->u4_ofst & 0x07) return (0); else return (1); } ================================================ FILE: dependencies/ih264d/decoder/ih264d_bitstrm.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_BITSTRM_H_ #define _IH264D_BITSTRM_H_ /*! ************************************************************************* * \file ih264d_bitstrm.h * * \brief * Contains all the declarations of bitstream reading routines * * \date * 20/11/2002 * * \author AI ************************************************************************* */ /* Includes */ #include <stdio.h> #include <stdlib.h> #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #define INT_IN_BYTES 4 #define INT_IN_BITS 32 /* Based on level 1.2 of baseline profile */ /* 396[MAX_FS] * 128 * 1.5 [ChromaFormatParameter] / sizeof(UWORD32) i.e 396 * 128 * 1.5 / 4 = 19008 */ /* Based on level 3 of main profile */ /* 1620[MAX_FS] * 128 * 1.5 [ChromaFormatParameter] / sizeof(UWORD32) i.e 1620 * 128 * 1.5 / 4= 77760 */ #define SIZE_OF_BUFFER 77760 /* Structure for the ps_bitstrm */ typedef struct { UWORD32 u4_ofst; /* Offset in the buffer for the current bit */ UWORD32 *pu4_buffer; /* Bitstream Buffer */ UWORD32 u4_max_ofst; /* points to first bit beyond the buffer */ void * pv_codec_handle; /* For Error Handling */ } dec_bit_stream_t; /* To read the next bit */ UWORD8 ih264d_get_bit_h264(dec_bit_stream_t *); /* To read the next specified number of bits */ UWORD32 ih264d_get_bits_h264(dec_bit_stream_t *, UWORD32); /* To see the next specified number of bits */ UWORD32 ih264d_next_bits_h264(dec_bit_stream_t *, UWORD32); /* To flush a specified number of bits*/ WORD32 ih264d_flush_bits_h264(dec_bit_stream_t *, WORD32); /*! ************************************************************************** * \if Function name : MoreRbspData \endif * * \brief * Determines whether there is more data in RBSP or not. * * \param ps_bitstrm : Pointer to bitstream * * \return * Returns 1 if there is more data in RBSP before rbsp_trailing_bits(). * Otherwise it returns FALSE. ************************************************************************** */ #define EXCEED_OFFSET(ps_bitstrm) \ (ps_bitstrm->u4_ofst > ps_bitstrm->u4_max_ofst) #define CHECK_BITS_SUFFICIENT(ps_bitstrm, bits_to_read) \ (ps_bitstrm->u4_ofst + bits_to_read <= ps_bitstrm->u4_max_ofst) #define MORE_RBSP_DATA(ps_bitstrm) \ CHECK_BITS_SUFFICIENT(ps_bitstrm, 1) void GoToByteBoundary(dec_bit_stream_t * ps_bitstrm); UWORD8 ih264d_check_byte_aligned(dec_bit_stream_t * ps_bitstrm); /*****************************************************************************/ /* Define a macro for inlining of GETBIT: */ /*****************************************************************************/ #define GETBIT(u4_code, u4_offset, pu4_bitstream) \ { \ UWORD32 *pu4_buf = (pu4_bitstream); \ UWORD32 u4_word_off = ((u4_offset) >> 5); \ UWORD32 u4_bit_off = (u4_offset) & 0x1F; \ u4_code = pu4_buf[u4_word_off] << u4_bit_off; \ (u4_offset)++; \ u4_code = (u4_code >> 31); \ } /*****************************************************************************/ /* Define a macro for inlining of GETBITS: u4_no_bits shall not exceed 32 */ /*****************************************************************************/ #define GETBITS(u4_code, u4_offset, pu4_bitstream, u4_no_bits) \ { \ UWORD32 *pu4_buf = (pu4_bitstream); \ UWORD32 u4_word_off = ((u4_offset) >> 5); \ UWORD32 u4_bit_off = (u4_offset) & 0x1F; \ u4_code = pu4_buf[u4_word_off++] << u4_bit_off; \ \ if(u4_bit_off) \ u4_code |= (pu4_buf[u4_word_off] >> (INT_IN_BITS - u4_bit_off)); \ u4_code = u4_code >> (INT_IN_BITS - u4_no_bits); \ (u4_offset) += u4_no_bits; \ } \ \ /*****************************************************************************/ /* Define a macro for inlining of NEXTBITS */ /*****************************************************************************/ #define NEXTBITS(u4_word, u4_offset, pu4_bitstream, u4_no_bits) \ { \ UWORD32 *pu4_buf = (pu4_bitstream); \ UWORD32 u4_word_off = ((u4_offset) >> 5); \ UWORD32 u4_bit_off = (u4_offset) & 0x1F; \ u4_word = pu4_buf[u4_word_off++] << u4_bit_off; \ if(u4_bit_off) \ u4_word |= (pu4_buf[u4_word_off] >> (INT_IN_BITS - u4_bit_off)); \ u4_word = u4_word >> (INT_IN_BITS - u4_no_bits); \ } /*****************************************************************************/ /* Define a macro for inlining of NEXTBITS_32 */ /*****************************************************************************/ #define NEXTBITS_32(u4_word, u4_offset, pu4_bitstream) \ { \ UWORD32 *pu4_buf = (pu4_bitstream); \ UWORD32 u4_word_off = ((u4_offset) >> 5); \ UWORD32 u4_bit_off = (u4_offset) & 0x1F; \ \ u4_word = pu4_buf[u4_word_off++] << u4_bit_off; \ if(u4_bit_off) \ u4_word |= (pu4_buf[u4_word_off] >> (INT_IN_BITS - u4_bit_off)); \ } /*****************************************************************************/ /* Define a macro for inlining of FIND_ONE_IN_STREAM_32 */ /*****************************************************************************/ #define FIND_ONE_IN_STREAM_32(u4_ldz, u4_offset, pu4_bitstream) \ { \ UWORD32 u4_word; \ NEXTBITS_32(u4_word, u4_offset, pu4_bitstream); \ u4_ldz = CLZ(u4_word); \ (u4_offset) += (u4_ldz + 1); \ } /*****************************************************************************/ /* Define a macro for inlining of FIND_ONE_IN_STREAM_LEN */ /*****************************************************************************/ #define FIND_ONE_IN_STREAM_LEN(u4_ldz, u4_offset, pu4_bitstream, u4_len) \ { \ UWORD32 u4_word; \ NEXTBITS_32(u4_word, u4_offset, pu4_bitstream); \ u4_ldz = CLZ(u4_word); \ if(u4_ldz < u4_len) \ (u4_offset) += (u4_ldz + 1); \ else \ { \ u4_ldz = u4_len; \ (u4_offset) += u4_ldz; \ } \ } /*****************************************************************************/ /* Define a macro for inlining of FLUSHBITS */ /*****************************************************************************/ #define FLUSHBITS(u4_offset, u4_no_bits) \ { \ (u4_offset) += (u4_no_bits); \ } #endif /* _BITSTREAM_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_cabac.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! *************************************************************************** * \file ih264d_cabac.c * * \brief * This file contains Binary decoding routines. * * \date * 04/02/2003 * * \author NS *************************************************************************** */ #include <string.h> #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" #include "ih264d_cabac.h" #include "ih264d_bitstrm.h" #include "ih264d_error_handler.h" #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_parse_cabac.h" #include "ih264d_tables.h" /*! ************************************************************************** * \if Function name : ih264d_init_cabac_dec_envirnoment \endif * * \brief * This function initializes CABAC decoding envirnoment. This function * implements 9.3.3.2.3.1 of ISO/IEC14496-10. * * \return * None * ************************************************************************** */ WORD32 ih264d_init_cabac_dec_envirnoment(decoding_envirnoment_t * ps_cab_env, dec_bit_stream_t *ps_bitstrm) { UWORD32 u4_code_int_val_ofst; ps_cab_env->u4_code_int_range = (HALF - 2) << 23; NEXTBITS(u4_code_int_val_ofst, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer, 32); FLUSHBITS(ps_bitstrm->u4_ofst, 9) if(EXCEED_OFFSET(ps_bitstrm)) return ERROR_EOB_FLUSHBITS_T; ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; /*brief description of the design adopted for CABAC*/ /*according to the standard the u4_code_int_range needs to be initialized 0x 1FE(10 bits) and 9 bits from the bit stream need to be read and into the u4_code_int_val_ofst.As and when the u4_code_int_range becomes less than 10 bits we need to renormalize and read from the bitstream* In the implemented design initially range_new = range <<23 valOffset_new = valOffset << 23 + 23 bits(read from the bit stream) Thus we have read 23 more bits ahead of time. It can be mathematical proved that even with the modified range and u4_ofst the operations like comparison and subtraction needed for a bin decode are still valid(both in the regular case and the bypass case) As bins are decoded..we consume the bits that we have already read into the valOffset.The clz of Range gives us the number of bits we consumed of the 23 bits that we have read ahead of time. when the number bits we have consumed exceeds 23 ,we renormalize..and we read from the bitstream again*/ RESET_BIN_COUNTS(ps_cab_env) return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_init_cabac_contexts */ /* */ /* Description : This function initializes the cabac contexts */ /* depending upon slice type and Init_Idc value. */ /* Inputs : ps_dec, slice type */ /* Globals : <Does it use any global variables?> */ /* Outputs : */ /* Returns : void */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 03 05 2005 100153) Draft */ /* */ /*****************************************************************************/ void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec) { bin_ctxt_model_t *p_cabac_ctxt_table_t = ps_dec->p_cabac_ctxt_table_t; UWORD8 u1_qp_y = ps_dec->ps_cur_slice->u1_slice_qp; UWORD8 u1_cabac_init_Idc = 0; if(I_SLICE != u1_slice_type) { u1_cabac_init_Idc = ps_dec->ps_cur_slice->u1_cabac_init_idc; } { /* MAKING ps_dec->p_ctxt_inc_mb_map a scratch buffer */ /* 0th entry of CtxtIncMbMap will be always be containing default values for CABAC context representing MB not available */ ctxt_inc_mb_info_t *p_DefCtxt = ps_dec->p_ctxt_inc_mb_map - 1; UWORD8 *pu1_temp; WORD8 i; p_DefCtxt->u1_mb_type = CAB_SKIP; p_DefCtxt->u1_cbp = 0x0f; p_DefCtxt->u1_intra_chroma_pred_mode = 0; p_DefCtxt->u1_yuv_dc_csbp = 0x7; p_DefCtxt->u1_transform8x8_ctxt = 0; pu1_temp = (UWORD8*)p_DefCtxt->i1_ref_idx; for(i = 0; i < 4; i++, pu1_temp++) (*pu1_temp) = 0; pu1_temp = (UWORD8*)p_DefCtxt->u1_mv; for(i = 0; i < 16; i++, pu1_temp++) (*pu1_temp) = 0; ps_dec->ps_def_ctxt_mb_info = p_DefCtxt; } if(u1_slice_type == I_SLICE) { u1_cabac_init_Idc = 3; ps_dec->p_mb_type_t = p_cabac_ctxt_table_t + MB_TYPE_I_SLICE; } else if(u1_slice_type == P_SLICE) { ps_dec->p_mb_type_t = p_cabac_ctxt_table_t + MB_TYPE_P_SLICE; ps_dec->p_mb_skip_flag_t = p_cabac_ctxt_table_t + MB_SKIP_FLAG_P_SLICE; ps_dec->p_sub_mb_type_t = p_cabac_ctxt_table_t + SUB_MB_TYPE_P_SLICE; } else if(u1_slice_type == B_SLICE) { ps_dec->p_mb_type_t = p_cabac_ctxt_table_t + MB_TYPE_B_SLICE; ps_dec->p_mb_skip_flag_t = p_cabac_ctxt_table_t + MB_SKIP_FLAG_B_SLICE; ps_dec->p_sub_mb_type_t = p_cabac_ctxt_table_t + SUB_MB_TYPE_B_SLICE; } { bin_ctxt_model_t *p_cabac_ctxt_table_t_tmp = p_cabac_ctxt_table_t; if(ps_dec->ps_cur_slice->u1_field_pic_flag) { p_cabac_ctxt_table_t_tmp += SIGNIFICANT_COEFF_FLAG_FLD; } else { p_cabac_ctxt_table_t_tmp += SIGNIFICANT_COEFF_FLAG_FRAME; } { bin_ctxt_model_t * * p_significant_coeff_flag_t = ps_dec->p_significant_coeff_flag_t; p_significant_coeff_flag_t[0] = p_cabac_ctxt_table_t_tmp + SIG_COEFF_CTXT_CAT_0_OFFSET; p_significant_coeff_flag_t[1] = p_cabac_ctxt_table_t_tmp + SIG_COEFF_CTXT_CAT_1_OFFSET; p_significant_coeff_flag_t[2] = p_cabac_ctxt_table_t_tmp + SIG_COEFF_CTXT_CAT_2_OFFSET; p_significant_coeff_flag_t[3] = p_cabac_ctxt_table_t_tmp + SIG_COEFF_CTXT_CAT_3_OFFSET; p_significant_coeff_flag_t[4] = p_cabac_ctxt_table_t_tmp + SIG_COEFF_CTXT_CAT_4_OFFSET; p_significant_coeff_flag_t[5] = p_cabac_ctxt_table_t_tmp + SIG_COEFF_CTXT_CAT_5_OFFSET; } } memcpy(p_cabac_ctxt_table_t, gau1_ih264d_cabac_ctxt_init_table[u1_cabac_init_Idc][u1_qp_y], NUM_CABAC_CTXTS * sizeof(bin_ctxt_model_t)); } /*! ************************************************************************** * \if Function name : ih264d_decode_bin \endif * * \brief * This function implements decoding process of a decision as defined * in 9.3.3.2.2. * * \return * Returns symbol decoded. * * \note * It is specified in 9.3.3.2.3.2 that, one of the input to this function * is CtxIdx. CtxIdx is used to identify state and MPS of that context * (Refer Fig 9.11 - Flowchart for encoding a decision). To suffice that * here we pass a pointer bin_ctxt_model_t which contains these values. * ************************************************************************** */ UWORD32 ih264d_decode_bin(UWORD32 u4_ctx_inc, bin_ctxt_model_t *ps_src_bin_ctxt, dec_bit_stream_t *ps_bitstrm, decoding_envirnoment_t *ps_cab_env) { UWORD32 u4_qnt_int_range, u4_code_int_range, u4_code_int_val_ofst, u4_int_range_lps; UWORD32 u4_symbol, u4_mps_state; bin_ctxt_model_t *ps_bin_ctxt; UWORD32 table_lookup; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; UWORD32 u4_clz; ps_bin_ctxt = ps_src_bin_ctxt + u4_ctx_inc; u4_code_int_range = ps_cab_env->u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; u4_mps_state = (ps_bin_ctxt->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u4_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u4_mps_state >> 6) & 0x1); u4_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u4_mps_state, table_lookup) if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) { UWORD32 *pu4_buffer, u4_offset; pu4_buffer = ps_bitstrm->pu4_buffer; u4_offset = ps_bitstrm->u4_ofst; RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) ps_bitstrm->u4_ofst = u4_offset; } INC_BIN_COUNT(ps_cab_env) ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; ps_cab_env->u4_code_int_range = u4_code_int_range; ps_bin_ctxt->u1_mps_state = u4_mps_state; return (u4_symbol); } /*! ************************************************************************** * \if Function name : ih264d_decode_terminate \endif * * \brief * This function implements decoding process of a termination as defined * 9.3.3.2.2.3 of ISO/IEC14496-10. * * \return * Returns symbol decoded. * * \note * This routine is called while decoding "end_of_skice_flag" and of the * bin indicating PCM mode in MBType. * ************************************************************************** */ UWORD8 ih264d_decode_terminate(decoding_envirnoment_t * ps_cab_env, dec_bit_stream_t * ps_stream) { UWORD32 u4_symbol; UWORD32 u4_code_int_val_ofst, u4_code_int_range; UWORD32 u4_clz; u4_code_int_range = ps_cab_env->u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; u4_clz = CLZ(u4_code_int_range); u4_code_int_range -= (2 << (23 - u4_clz)); if(u4_code_int_val_ofst >= u4_code_int_range) { /* S=1 */ u4_symbol = 1; { /*the u4_ofst needs to be updated before termination*/ ps_stream->u4_ofst += u4_clz; } } else { /* S=0 */ u4_symbol = 0; if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) { UWORD32 *pu4_buffer, u4_offset; pu4_buffer = ps_stream->pu4_buffer; u4_offset = ps_stream->u4_ofst; RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) ps_stream->u4_ofst = u4_offset; } } ps_cab_env->u4_code_int_range = u4_code_int_range; ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; INC_BIN_COUNT(ps_cab_env) return (u4_symbol); } /*****************************************************************************/ /* */ /* Function Name : ih264d_decode_bins_tunary */ /* */ /* Description : This function decodes bins in the case of TUNARY */ /* binarization technique.valid_length is assumed equal to 3 */ /* and u1_max_bins <= 4 in this functon. */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 20 11 2008 SH Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_decode_bins_tunary(UWORD8 u1_max_bins, UWORD32 u4_ctx_inc, bin_ctxt_model_t *ps_src_bin_ctxt, dec_bit_stream_t *ps_bitstrm, decoding_envirnoment_t *ps_cab_env) { UWORD32 u4_value; UWORD32 u4_symbol; UWORD8 u4_ctx_Inc; bin_ctxt_model_t *ps_bin_ctxt; UWORD32 u4_code_int_range, u4_code_int_val_ofst; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; u4_value = 0; /*u1_max_bins has to be less than or equal to 4, u1_max_bins <= 4 for this function*/ /*here the valid length is assumed to be equal to 3 ,so the calling function is expected to duplicate CtxInc if valid lenth is 2 and cmaxbin is greater than2*/ u4_code_int_range = ps_cab_env->u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; do { u4_ctx_Inc = u4_ctx_inc & 0xF; u4_ctx_inc = u4_ctx_inc >> 4; ps_bin_ctxt = ps_src_bin_ctxt + u4_ctx_Inc; DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, u4_code_int_val_ofst, pu4_table, ps_bitstrm, u4_symbol) INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); u4_value++; } while((u4_value < u1_max_bins) & (u4_symbol)); u4_value = u4_value - 1 + u4_symbol; ps_cab_env->u4_code_int_range = u4_code_int_range; ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; return (u4_value); } /*****************************************************************************/ /* */ /* Function Name : ih264d_decode_bins */ /* */ /* Description : This function decodes bins in the case of MSB_FIRST_FLC */ /* binarization technique.valid_length is always equal max_bins */ /* for MSB_FIRST_FLC. assumes u1_max_bins <= 4 */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 20 11 2008 SH Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_decode_bins(UWORD8 u1_max_bins, UWORD32 u4_ctx_inc, bin_ctxt_model_t *ps_src_bin_ctxt, dec_bit_stream_t *ps_bitstrm, decoding_envirnoment_t *ps_cab_env) { UWORD32 u4_value; UWORD32 u4_symbol, i; UWORD32 u4_ctxt_inc; bin_ctxt_model_t *ps_bin_ctxt; UWORD32 u4_code_int_range, u4_code_int_val_ofst; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; i = 0; u4_value = 0; /*u1_max_bins has to be less than or equal to 4, u1_max_bins <= 4 for this fucntion*/ u4_code_int_range = ps_cab_env->u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; do { u4_ctxt_inc = u4_ctx_inc & 0xf; u4_ctx_inc = u4_ctx_inc >> 4; ps_bin_ctxt = ps_src_bin_ctxt + u4_ctxt_inc; DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, u4_code_int_val_ofst, pu4_table, ps_bitstrm, u4_symbol) INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); u4_value = (u4_value << 1) | (u4_symbol); i++; } while(i < u1_max_bins); ps_cab_env->u4_code_int_range = u4_code_int_range; ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; return (u4_value); } /*****************************************************************************/ /* */ /* Function Name : ih264d_decode_bins_unary */ /* */ /* Description : This function decodes bins in the case of UNARY */ /* binarization technique.here the valid length is taken to 5*/ /* and cmax is always greater than 9 */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 20 11 2008 SH Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_decode_bins_unary(UWORD8 u1_max_bins, UWORD32 u4_ctx_inc, bin_ctxt_model_t *ps_src_bin_ctxt, dec_bit_stream_t *ps_bitstrm, decoding_envirnoment_t *ps_cab_env) { UWORD32 u4_value; UWORD32 u4_symbol; bin_ctxt_model_t *ps_bin_ctxt; UWORD32 u4_ctx_Inc; UWORD32 u4_code_int_range, u4_code_int_val_ofst; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; /* in this function the valid length for u4_ctx_inc is always taken to be,so if the the valid length is lessthan 5 the caller need to duplicate accordingly*/ /*u1_max_bins is always greater or equal to 9 we have the check for u1_max_bins only after the 2 loop*/ u4_value = 0; u4_code_int_range = ps_cab_env->u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; do { u4_ctx_Inc = u4_ctx_inc & 0xf; u4_ctx_inc = u4_ctx_inc >> 4; ps_bin_ctxt = ps_src_bin_ctxt + u4_ctx_Inc; DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, u4_code_int_val_ofst, pu4_table, ps_bitstrm, u4_symbol) INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); u4_value++; } while(u4_symbol && u4_value < 4); if(u4_symbol && (u4_value < u1_max_bins)) { u4_ctx_Inc = u4_ctx_inc & 0xf; ps_bin_ctxt = ps_src_bin_ctxt + u4_ctx_Inc; do { DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, u4_code_int_val_ofst, pu4_table, ps_bitstrm, u4_symbol) INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); u4_value++; } while(u4_symbol && (u4_value < u1_max_bins)); } ps_cab_env->u4_code_int_range = u4_code_int_range; ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; u4_value = u4_value - 1 + u4_symbol; return (u4_value); } /*****************************************************************************/ /* */ /* Function Name : ih264d_decode_bypass_bins_unary */ /* */ /* Description : This function is used in the case of UNARY coding */ /* */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 10 2005 Ittiam Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_decode_bypass_bins_unary(decoding_envirnoment_t *ps_cab_env, dec_bit_stream_t *ps_bitstrm) { UWORD32 u4_value; UWORD32 u4_bin; UWORD32 u4_code_int_val_ofst, u4_code_int_range; UWORD32 u1_max_bins; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; u4_code_int_range = ps_cab_env->u4_code_int_range; if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) { UWORD32 *pu4_buffer, u4_offset; pu4_buffer = ps_bitstrm->pu4_buffer; u4_offset = ps_bitstrm->u4_ofst; RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) ps_bitstrm->u4_ofst = u4_offset; } /*as it is called only form mvd*/ u1_max_bins = 32; u4_value = 0; do { u4_value++; u4_code_int_range = u4_code_int_range >> 1; if(u4_code_int_val_ofst >= u4_code_int_range) { /* S=1 */ u4_bin = 1; u4_code_int_val_ofst -= u4_code_int_range; } else { /* S=0 */ u4_bin = 0; } INC_BIN_COUNT(ps_cab_env);INC_BYPASS_BINS(ps_cab_env); if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) { UWORD32 *pu4_buffer, u4_offset; pu4_buffer = ps_bitstrm->pu4_buffer; u4_offset = ps_bitstrm->u4_ofst; RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) ps_bitstrm->u4_ofst = u4_offset; } } while(u4_bin && (u4_value < u1_max_bins)); ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; ps_cab_env->u4_code_int_range = u4_code_int_range; u4_value = (u4_value - 1 + u4_bin); return (u4_value); } /*****************************************************************************/ /* */ /* Function Name : ih264d_decode_bypass_bins */ /* */ /* Description : This function is used in the case of FLC coding */ /* */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 10 2005 Ittiam Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_decode_bypass_bins(decoding_envirnoment_t *ps_cab_env, UWORD8 u1_max_bins, dec_bit_stream_t *ps_bitstrm) { UWORD32 u4_bins; UWORD32 u4_bin; UWORD32 u4_code_int_val_ofst, u4_code_int_range; u4_bins = 0; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; u4_code_int_range = ps_cab_env->u4_code_int_range; if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) { UWORD32 *pu4_buffer, u4_offset; pu4_buffer = ps_bitstrm->pu4_buffer; u4_offset = ps_bitstrm->u4_ofst; RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) ps_bitstrm->u4_ofst = u4_offset; } do { u4_code_int_range = u4_code_int_range >> 1; if(u4_code_int_val_ofst >= u4_code_int_range) { /* S=1 */ u4_bin = 1; u4_code_int_val_ofst -= u4_code_int_range; } else { /* S=0 */ u4_bin = 0; } INC_BIN_COUNT(ps_cab_env);INC_BYPASS_BINS(ps_cab_env); u4_bins = ((u4_bins << 1) | u4_bin); u1_max_bins--; if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) { UWORD32 *pu4_buffer, u4_offset; pu4_buffer = ps_bitstrm->pu4_buffer; u4_offset = ps_bitstrm->u4_ofst; RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) ps_bitstrm->u4_ofst = u4_offset; } } while(u1_max_bins); ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; ps_cab_env->u4_code_int_range = u4_code_int_range; return (u4_bins); } ================================================ FILE: dependencies/ih264d/decoder/ih264d_cabac.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! *************************************************************************** * \file ih264d_cabac.h * * \brief * This file contains declarations of Binary decoding routines and tables. * * \date * 04/02/2003 * * \author NS *************************************************************************** */ #ifndef _IH264D_CABAC_H_ #define _IH264D_CABAC_H_ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_bitstrm.h" #include "ih264d_defs.h" #define B_BITS 10 #define HALF (1 << (B_BITS-1)) #define QUARTER (1 << (B_BITS-2)) #define CTXT_UNUSED {0,64} #define NUM_MB_SKIP_CTXT 6 #define NUM_MB_TYPE_CTXT 9 #define NUM_SUBMB_TYPE_CTXT 7 #define NUM_REF_IDX_CTXT 6 #define NUM_MB_QP_DELTA 4 #define NUM_PRED_MODE 6 #define NUM_MB_FIELD 3 #define NUM_CBP 12 #define NUM_CTX_MVD 14 /* Residual block cabac context parameters */ #define NUM_CTX_CAT 6 #define NUM_LUMA_CTX_CAT 3 #define NUM_CTX_CODED_BLOCK 4 /* Luma CtxSigCoeff + CtxLastCoeff = 15 + 15 = 30 */ #define NUM_LUMA_CTX_SIG_COEF 30 /* Chroma DC CtxSigCoeff + CtxLastCoeff = 3 + 3 = 6 */ #define NUM_CTX_CHROMA_DC_SIG_COEF 6 /* Chroma AC CtxSigCoeff + CtxLastCoeff = 14 + 14 = 28 */ #define NUM_CTX_CHROMA_AC_SIG_COEF 28 #define NUM_CTX_ABS_LEVEL 10 #define LUMA_DC_CTXCAT 0 #define LUMA_AC_CTXCAT 1 #define LUMA_4X4_CTXCAT 2 #define CHROMA_DC_CTXCAT 3 #define CHROMA_AC_CTXCAT 4 #define LUMA_8X8_CTXCAT 5 /*****************************************************************************/ /* Constant Macros */ /*****************************************************************************/ #define NUM_CABAC_CTXTS 460 #define QP_RANGE 52 #define NUM_CAB_INIT_IDC_PLUS_ONE 4 #define LAST_COEFF_CTXT_MINUS_SIG_COEFF_CTXT 61 #define LAST_COEFF_CTXT_MINUS_SIG_COEFF_CTXT_8X8 15 /*bits 0 to 5 :state bit 6:mps*/ typedef struct { UWORD8 u1_mps_state; /* state number */ } bin_ctxt_model_t; typedef struct { /* Neighbour availability Variables needed to get CtxtInc, for CABAC */ UWORD8 u1_mb_type; /** macroblock type: I/P/B/SI/SP */ UWORD8 u1_cbp; /** Coded Block Pattern */ UWORD8 u1_intra_chroma_pred_mode; /*************************************************************************/ /* Arrangnment of DC CSBP */ /* bits: b7 b6 b5 b4 b3 b2 b1 b0 */ /* CSBP: x x x x x Vdc Udc Ydc */ /*************************************************************************/ UWORD8 u1_yuv_dc_csbp; WORD8 i1_ref_idx[4]; UWORD8 u1_mv[4][4]; UWORD8 u1_transform8x8_ctxt; } ctxt_inc_mb_info_t; #define ONE_RIGHT_SHIFTED_BY_8 1<<8 #define ONE_RIGHT_SHIFTED_BY_9 1<<9 #define ONE_RIGHT_SHIFTED_BY_14 1<<14 typedef struct { UWORD32 u4_code_int_range; UWORD32 u4_code_int_val_ofst; const void *cabac_table; void * pv_codec_handle; /* For Error Handling */ } decoding_envirnoment_t; WORD32 ih264d_init_cabac_dec_envirnoment(decoding_envirnoment_t * ps_cab_env, dec_bit_stream_t *ps_bitstrm); UWORD32 ih264d_decode_bin(UWORD32 u4_ctx_inc, bin_ctxt_model_t *ps_bin_ctxt, dec_bit_stream_t *ps_bitstrm, decoding_envirnoment_t *ps_cab_env); UWORD8 ih264d_decode_terminate(decoding_envirnoment_t * ps_cab_env, dec_bit_stream_t * ps_bitstrm); UWORD32 ih264d_decode_bins_tunary(UWORD8 u1_max_bins, UWORD32 u4_ctx_inc, bin_ctxt_model_t *ps_src_bin_ctxt, dec_bit_stream_t *ps_bitstrm, decoding_envirnoment_t *ps_cab_env); UWORD32 ih264d_decode_bins(UWORD8 u1_max_bins, UWORD32 u4_ctx_inc, bin_ctxt_model_t *ps_src_bin_ctxt, dec_bit_stream_t *ps_bitstrm, decoding_envirnoment_t *ps_cab_env); UWORD32 ih264d_decode_bins_unary(UWORD8 u1_max_bins, UWORD32 u4_ctx_inc, bin_ctxt_model_t *ps_src_bin_ctxt, dec_bit_stream_t *ps_bitstrm, decoding_envirnoment_t *ps_cab_env); UWORD32 ih264d_decode_bypass_bins_unary(decoding_envirnoment_t *ps_cab_env, dec_bit_stream_t *ps_bitstrm); UWORD32 ih264d_decode_bypass_bins(decoding_envirnoment_t *ps_cab_env, UWORD8 u1_max_bins, dec_bit_stream_t *ps_bitstrm); /*****************************************************************************/ /* Function Macros */ /*****************************************************************************/ /*****************************************************************************/ /* Defining a macro for renormalization*/ /*****************************************************************************/ /*we renormalize every time the number bits(which are read ahead of time) we have consumed in the u4_ofst exceeds 23*/ #define RENORM_RANGE_OFFSET(u4_codeIntRange_m,u4_codeIntValOffset_m,u4_offset_m,pu4_buffer_m) \ { \ UWORD32 read_bits_m,u4_clz_m ; \ u4_clz_m = CLZ(u4_codeIntRange_m); \ NEXTBITS(read_bits_m,(u4_offset_m+23),pu4_buffer_m,u4_clz_m) \ FLUSHBITS(u4_offset_m,(u4_clz_m)) \ u4_codeIntRange_m = u4_codeIntRange_m << u4_clz_m; \ u4_codeIntValOffset_m = (u4_codeIntValOffset_m << u4_clz_m) | read_bits_m; \ } /*****************************************************************************/ /* Defining a macro for checking if the symbol is MPS*/ /*****************************************************************************/ #define CHECK_IF_LPS(u4_codeIntRange_m,u4_codeIntValOffset_m,u4_symbol_m, \ u4_codeIntRangeLPS_m,u1_mps_state_m,table_lookup_m) \ { \ if(u4_codeIntValOffset_m >= u4_codeIntRange_m) \ { \ u4_symbol_m = 1 - u4_symbol_m; \ u4_codeIntValOffset_m -= u4_codeIntRange_m; \ u4_codeIntRange_m = u4_codeIntRangeLPS_m; \ u1_mps_state_m = (table_lookup_m >> 15) & 0x7F; \ } \ } /*! ************************************************************************** * \if Function name : DECODE_ONE_BIN_MACRO \endif * * \brief * This function implements decoding process of a decision as defined * in 9.3.3.2.2. * * \return * Returns symbol decoded. * * \note * It is specified in 9.3.3.2.3.2 that, one of the input to this function * is CtxIdx. CtxIdx is used to identify state and MPS of that context * (Refer Fig 9.11 - Flowchart for encoding a decision). To suffice that * here we pass a pointer bin_ctxt_model_t which contains these values. * ************************************************************************** */ #define DECODE_ONE_BIN_MACRO(p_binCtxt_arg ,u4_code_int_range,u4_code_int_val_ofst, \ pu4_table_arg, \ p_DecBitStream_arg,u4_symbol) \ { \ bin_ctxt_model_t *p_binCtxt_m = (bin_ctxt_model_t *) p_binCtxt_arg; \ dec_bit_stream_t *p_DecBitStream_m = (dec_bit_stream_t *) p_DecBitStream_arg; \ const UWORD32 *pu4_table_m = (const UWORD32 *) pu4_table_arg; \ \ UWORD32 u4_quantCodeIntRange_m,u4_codeIntRangeLPS_m; \ UWORD32 u1_mps_state_m; \ UWORD32 table_lookup_m; \ UWORD32 u4_clz_m; \ \ u1_mps_state_m = (p_binCtxt_m->u1_mps_state); \ u4_clz_m = CLZ(u4_code_int_range); \ u4_quantCodeIntRange_m = u4_code_int_range << u4_clz_m; \ u4_quantCodeIntRange_m = (u4_quantCodeIntRange_m >> 29) & 0x3; \ table_lookup_m = pu4_table_m[(u1_mps_state_m << 2)+u4_quantCodeIntRange_m]; \ u4_codeIntRangeLPS_m = table_lookup_m & 0xff; \ \ u4_codeIntRangeLPS_m = u4_codeIntRangeLPS_m << (23 - u4_clz_m); \ u4_code_int_range = u4_code_int_range - u4_codeIntRangeLPS_m; \ u4_symbol = ((u1_mps_state_m>> 6) & 0x1); \ /*if mps*/ \ u1_mps_state_m = (table_lookup_m >> 8) & 0x7F; \ if(u4_code_int_val_ofst >= u4_code_int_range) \ { \ \ u4_symbol = 1 - u4_symbol; \ u4_code_int_val_ofst -= u4_code_int_range; \ u4_code_int_range = u4_codeIntRangeLPS_m; \ u1_mps_state_m = (table_lookup_m >> 15) & 0x7F; \ } \ if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) \ { \ UWORD32 *pu4_buffer,u4_offset; \ UWORD32 read_bits,u4_clz_m ; \ \ pu4_buffer = p_DecBitStream_m->pu4_buffer; \ u4_offset = p_DecBitStream_m->u4_ofst; \ u4_clz_m = CLZ(u4_code_int_range); \ NEXTBITS(read_bits,(u4_offset+23),pu4_buffer,u4_clz_m) \ FLUSHBITS(u4_offset,(u4_clz_m)) \ u4_code_int_range = u4_code_int_range << u4_clz_m; \ u4_code_int_val_ofst= (u4_code_int_val_ofst << u4_clz_m) | read_bits; \ \ \ p_DecBitStream_m->u4_ofst = u4_offset; \ } \ p_binCtxt_m->u1_mps_state = u1_mps_state_m; \ } #endif /* _IH264D_CABAC_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_cabac_init_tables.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _CABAC_INIT_TABLES_H_ #define _CABAC_INIT_TABLES_H_ /*****************************************************************************/ /* */ /* File Name : ih264d_cabac_init_tables.c */ /* */ /* Description : This file contains the initialized cabac context */ /* structures for all possible values of Qp (0 - 51) */ /* Cabac_init Idc (0 - 2) and I slice. The contexts */ /* are initialized and stored as per tables 9-11 to */ /* 9 -23 */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 10 01 2005 SH */ /* */ /*****************************************************************************/ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_cabac.h" #include "ih264d_tables.h" /*combined table :guc_RTAB,NextStateLPS,NextStateMPS input(combined_state): bits 0-5: state bits 6:mps output bits 0-7:rangeTabLPS bits 8-14 :combined_next_state_if_mps bits 15 -21:combined_next_state_if_lps */ const UWORD32 gau4_ih264d_cabac_table[128][4] = { { 2097536, 2097584, 2097616, 2097648 }, { 640, 679, 709, 739 }, { 33664, 33694, 33723, 33752 }, { 66683, 66710, 66738, 66765 }, { 66932, 66958, 66985, 67011 }, { 132719, 132743, 132768, 132793 }, { 132969, 132992, 133016, 133039 }, { 165988, 166010, 166032, 166054 }, { 199007, 199028, 199049, 199070 }, { 232026, 232046, 232066, 232086 }, { 265045, 265064, 265083, 265102 }, { 298065, 298083, 298101, 298119 }, { 298317, 298334, 298351, 298368 }, { 364105, 364121, 364137, 364154 }, { 364357, 364373, 364388, 364404 }, { 397378, 397392, 397407, 397422 }, { 430398, 430412, 430426, 430440 }, { 430651, 430664, 430678, 430691 }, { 496440, 496453, 496465, 496478 }, { 496693, 496705, 496717, 496729 }, { 529715, 529726, 529737, 529749 }, { 529968, 529979, 529989, 530000 }, { 595758, 595768, 595778, 595788 }, { 596011, 596021, 596031, 596040 }, { 629033, 629042, 629051, 629061 }, { 629287, 629296, 629304, 629313 }, { 695077, 695085, 695094, 695102 }, { 695331, 695339, 695347, 695355 }, { 728353, 728361, 728368, 728376 }, { 728608, 728615, 728622, 728629 }, { 761630, 761637, 761643, 761650 }, { 794653, 794659, 794665, 794672 }, { 794907, 794913, 794919, 794925 }, { 827930, 827935, 827941, 827947 }, { 860952, 860958, 860963, 860969 }, { 861207, 861212, 861217, 861223 }, { 894230, 894235, 894240, 894245 }, { 894485, 894490, 894494, 894499 }, { 927508, 927512, 927517, 927521 }, { 960531, 960535, 960539, 960543 }, { 960786, 960790, 960794, 960798 }, { 993809, 993813, 993817, 993820 }, { 994064, 994068, 994071, 994075 }, { 994319, 994323, 994326, 994329 }, { 1027342, 1027346, 1027349, 1027352 }, { 1060366, 1060369, 1060372, 1060375 }, { 1060621, 1060624, 1060627, 1060630 }, { 1093644, 1093647, 1093650, 1093653 }, { 1093900, 1093902, 1093905, 1093908 }, { 1094155, 1094158, 1094160, 1094163 }, { 1127179, 1127181, 1127183, 1127186 }, { 1127434, 1127436, 1127439, 1127441 }, { 1160458, 1160460, 1160462, 1160464 }, { 1160713, 1160715, 1160717, 1160719 }, { 1160969, 1160971, 1160972, 1160974 }, { 1193992, 1193994, 1193996, 1193998 }, { 1194248, 1194249, 1194251, 1194253 }, { 1194503, 1194505, 1194507, 1194508 }, { 1227527, 1227529, 1227530, 1227532 }, { 1227783, 1227784, 1227786, 1227787 }, { 1228038, 1228040, 1228041, 1228043 }, { 1261062, 1261063, 1261065, 1261066 }, { 1261062, 1261063, 1261064, 1261065 }, { 2080514, 2080514, 2080514, 2080514 }, { 16768, 16816, 16848, 16880 }, { 2114176, 2114215, 2114245, 2114275 }, { 2147200, 2147230, 2147259, 2147288 }, { 2180219, 2180246, 2180274, 2180301 }, { 2180468, 2180494, 2180521, 2180547 }, { 2246255, 2246279, 2246304, 2246329 }, { 2246505, 2246528, 2246552, 2246575 }, { 2279524, 2279546, 2279568, 2279590 }, { 2312543, 2312564, 2312585, 2312606 }, { 2345562, 2345582, 2345602, 2345622 }, { 2378581, 2378600, 2378619, 2378638 }, { 2411601, 2411619, 2411637, 2411655 }, { 2411853, 2411870, 2411887, 2411904 }, { 2477641, 2477657, 2477673, 2477690 }, { 2477893, 2477909, 2477924, 2477940 }, { 2510914, 2510928, 2510943, 2510958 }, { 2543934, 2543948, 2543962, 2543976 }, { 2544187, 2544200, 2544214, 2544227 }, { 2609976, 2609989, 2610001, 2610014 }, { 2610229, 2610241, 2610253, 2610265 }, { 2643251, 2643262, 2643273, 2643285 }, { 2643504, 2643515, 2643525, 2643536 }, { 2709294, 2709304, 2709314, 2709324 }, { 2709547, 2709557, 2709567, 2709576 }, { 2742569, 2742578, 2742587, 2742597 }, { 2742823, 2742832, 2742840, 2742849 }, { 2808613, 2808621, 2808630, 2808638 }, { 2808867, 2808875, 2808883, 2808891 }, { 2841889, 2841897, 2841904, 2841912 }, { 2842144, 2842151, 2842158, 2842165 }, { 2875166, 2875173, 2875179, 2875186 }, { 2908189, 2908195, 2908201, 2908208 }, { 2908443, 2908449, 2908455, 2908461 }, { 2941466, 2941471, 2941477, 2941483 }, { 2974488, 2974494, 2974499, 2974505 }, { 2974743, 2974748, 2974753, 2974759 }, { 3007766, 3007771, 3007776, 3007781 }, { 3008021, 3008026, 3008030, 3008035 }, { 3041044, 3041048, 3041053, 3041057 }, { 3074067, 3074071, 3074075, 3074079 }, { 3074322, 3074326, 3074330, 3074334 }, { 3107345, 3107349, 3107353, 3107356 }, { 3107600, 3107604, 3107607, 3107611 }, { 3107855, 3107859, 3107862, 3107865 }, { 3140878, 3140882, 3140885, 3140888 }, { 3173902, 3173905, 3173908, 3173911 }, { 3174157, 3174160, 3174163, 3174166 }, { 3207180, 3207183, 3207186, 3207189 }, { 3207436, 3207438, 3207441, 3207444 }, { 3207691, 3207694, 3207696, 3207699 }, { 3240715, 3240717, 3240719, 3240722 }, { 3240970, 3240972, 3240975, 3240977 }, { 3273994, 3273996, 3273998, 3274000 }, { 3274249, 3274251, 3274253, 3274255 }, { 3274505, 3274507, 3274508, 3274510 }, { 3307528, 3307530, 3307532, 3307534 }, { 3307784, 3307785, 3307787, 3307789 }, { 3308039, 3308041, 3308043, 3308044 }, { 3341063, 3341065, 3341066, 3341068 }, { 3341319, 3341320, 3341322, 3341323 }, { 3341574, 3341576, 3341577, 3341579 }, { 3374598, 3374599, 3374601, 3374602 }, { 3374598, 3374599, 3374600, 3374601 }, { 4194050, 4194050, 4194050, 4194050 }, }; /*****************************************************************************/ /* Global Variable Initialization */ /*****************************************************************************/ const UWORD8 gau1_ih264d_cabac_ctxt_init_table[NUM_CAB_INIT_IDC_PLUS_ONE][QP_RANGE][NUM_CABAC_CTXTS] = { { { 62, 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 30, 61, 62, 54, 14, 118, 6, 78, 65, 1, 14, 73, 13, 64, 20, 62, 67, 90, 104, 126, 104, 67, 78, 65, 1, 86, 95, 2, 18, 69, 81, 96, 8, 67, 86, 88, 5, 76, 94, 9, 69, 81, 88, 67, 74, 74, 80, 72, 5, 22, 0, 0, 0, 83, 86, 97, 72, 22, 1, 18, 78, 96, 126, 98, 101, 67, 82, 94, 83, 110, 91, 102, 93, 126, 92, 89, 96, 108, 17, 65, 6, 93, 74, 92, 87, 126, 9, 3, 4, 69, 15, 68, 69, 88, 85, 78, 75, 77, 9, 13, 68, 13, 21, 81, 0, 70, 67, 6, 76, 28, 64, 2, 28, 38, 39, 34, 27, 93, 73, 73, 17, 14, 100, 10, 10, 10, 2, 7, 7, 0, 3, 1, 6, 69, 6, 24, 12, 68, 64, 2, 0, 13, 24, 19, 11, 15, 3, 4, 4, 30, 19, 20, 78, 3, 69, 35, 23, 19, 14, 17, 19, 12, 16, 24, 1, 17, 9, 9, 5, 0, 12, 6, 10, 11, 8, 18, 27, 10, 82, 8, 78, 17, 32, 84, 56, 62, 60, 59, 62, 62, 57, 57, 54, 44, 36, 33, 43, 29, 70, 67, 4, 67, 33, 31, 28, 34, 32, 25, 20, 22, 0, 4, 64, 94, 89, 108, 76, 19, 18, 11, 64, 4, 70, 75, 82, 102, 77, 39, 21, 15, 8, 4, 71, 83, 87, 119, 5, 34, 27, 25, 20, 8, 5, 64, 74, 90, 70, 34, 32, 21, 4, 5, 72, 81, 97, 5, 58, 49, 45, 36, 23, 5, 70, 79, 85, 62, 106, 106, 87, 114, 110, 98, 110, 106, 103, 107, 108, 112, 96, 95, 91, 93, 94, 86, 67, 80, 85, 70, 3, 5, 2, 13, 13, 14, 9, 22, 17, 12, 14, 11, 22, 16, 8, 22, 19, 13, 10, 14, 0, 64, 69, 4, 70, 19, 32, 20, 10, 29, 25, 11, 23, 31, 19, 25, 13, 6, 20, 52, 49, 52, 52, 54, 62, 62, 62, 62, 62, 62, 62, 62, 62, 34, 62, 62, 62, 62, 62, 62, 54, 37, 36, 6, 82, 75, 97, 125, 62, 62, 62, 57, 55, 53, 41, 44, 31, 32, 22, 19, 16, 65, 71, 3, 0, 65, 39, 43, 40, 31, 40, 39, 23, 31, 34, 21, 6, 10, 2, 86, 23, 12, 4, 79, 71, 69, 70, 66, 68, 73, 69, 70, 67, 1, 70, 66, 65, 0, 62, 62, 62, 62, 62, 60, 54, 36, 4, 66, 28, 21, 18, 15, 7, 3, 1, 66, 76, 85, 81, 77, 81, 80, 73, 74, 83, 71, 67, 2, 66, 66, 4, 4, 62, 62, 62, 62, 61, 57, 46, 29, 1 }, { 62, 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 29, 60, 62, 54, 14, 115, 6, 77, 64, 1, 14, 72, 12, 65, 20, 62, 68, 91, 104, 124, 102, 67, 77, 64, 1, 85, 93, 3, 18, 68, 80, 95, 8, 67, 85, 88, 5, 75, 93, 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 18, 77, 95, 124, 96, 99, 65, 80, 92, 82, 108, 89, 100, 92, 125, 91, 88, 95, 107, 18, 64, 7, 92, 73, 91, 86, 124, 9, 3, 4, 69, 16, 68, 68, 87, 84, 77, 74, 76, 9, 13, 67, 13, 21, 80, 0, 69, 67, 6, 75, 28, 64, 2, 28, 37, 39, 34, 27, 92, 72, 72, 17, 14, 99, 10, 10, 10, 3, 7, 7, 1, 4, 2, 6, 68, 6, 24, 12, 68, 64, 2, 0, 13, 23, 19, 11, 15, 4, 5, 4, 29, 19, 20, 77, 3, 69, 35, 23, 19, 14, 17, 19, 12, 16, 24, 1, 17, 9, 9, 5, 0, 12, 6, 10, 11, 8, 18, 27, 10, 81, 8, 77, 17, 31, 83, 55, 62, 59, 58, 61, 62, 56, 56, 52, 43, 35, 32, 41, 28, 71, 67, 4, 67, 32, 30, 27, 33, 31, 24, 19, 21, 0, 4, 64, 93, 88, 107, 75, 20, 18, 11, 0, 5, 69, 74, 81, 100, 76, 39, 21, 15, 8, 5, 70, 82, 86, 117, 5, 35, 28, 25, 20, 9, 5, 64, 73, 89, 70, 35, 32, 21, 4, 6, 71, 80, 96, 5, 58, 49, 45, 36, 23, 5, 69, 78, 84, 62, 105, 105, 86, 112, 108, 97, 108, 104, 101, 105, 106, 110, 95, 94, 90, 92, 92, 85, 67, 79, 84, 69, 3, 5, 2, 13, 13, 13, 8, 22, 17, 13, 14, 11, 22, 16, 8, 22, 19, 13, 10, 14, 0, 64, 68, 5, 70, 19, 32, 20, 10, 29, 25, 12, 23, 30, 19, 25, 13, 6, 19, 52, 49, 52, 51, 53, 62, 62, 62, 62, 62, 62, 62, 62, 62, 33, 62, 62, 62, 62, 62, 62, 53, 36, 35, 6, 81, 74, 95, 122, 62, 62, 62, 56, 53, 52, 40, 42, 30, 31, 21, 18, 15, 66, 71, 3, 0, 66, 38, 42, 39, 30, 39, 38, 22, 30, 33, 20, 5, 9, 1, 86, 23, 12, 4, 78, 70, 68, 69, 65, 67, 71, 68, 69, 66, 3, 68, 65, 0, 2, 62, 62, 62, 62, 62, 58, 51, 34, 2, 65, 29, 22, 19, 16, 8, 4, 2, 65, 75, 84, 80, 76, 80, 78, 71, 73, 82, 70, 66, 3, 65, 65, 4, 4, 62, 62, 62, 62, 58, 54, 43, 26, 64 }, { 62, 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 28, 59, 61, 54, 14, 113, 6, 76, 0, 1, 13, 72, 11, 66, 19, 60, 70, 92, 105, 121, 101, 67, 76, 0, 1, 85, 92, 3, 17, 68, 80, 94, 8, 67, 85, 88, 5, 75, 92, 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 18, 77, 95, 122, 94, 97, 64, 78, 91, 81, 107, 88, 99, 91, 123, 91, 88, 95, 106, 18, 64, 7, 91, 73, 90, 86, 123, 9, 3, 4, 69, 16, 68, 68, 87, 84, 77, 74, 76, 9, 13, 67, 13, 21, 80, 0, 69, 67, 6, 75, 27, 64, 2, 27, 36, 38, 33, 26, 91, 72, 72, 16, 13, 99, 9, 10, 10, 3, 7, 7, 2, 4, 2, 6, 68, 6, 23, 12, 69, 64, 2, 64, 13, 22, 19, 11, 14, 4, 5, 4, 28, 19, 19, 77, 3, 70, 34, 23, 19, 14, 17, 19, 12, 16, 24, 1, 17, 9, 9, 5, 0, 12, 6, 10, 11, 8, 17, 26, 9, 81, 8, 77, 16, 30, 83, 53, 62, 57, 56, 59, 60, 54, 54, 50, 41, 33, 30, 39, 26, 72, 67, 4, 68, 31, 29, 26, 32, 29, 23, 18, 20, 64, 3, 65, 93, 88, 106, 75, 20, 18, 11, 0, 5, 69, 74, 81, 99, 75, 39, 21, 15, 8, 5, 70, 81, 85, 115, 5, 35, 28, 25, 20, 9, 5, 64, 73, 88, 70, 35, 32, 21, 4, 6, 71, 80, 95, 5, 57, 48, 44, 35, 23, 5, 69, 78, 84, 62, 104, 104, 85, 111, 107, 96, 107, 103, 100, 104, 105, 108, 94, 93, 90, 91, 91, 85, 68, 79, 83, 69, 3, 4, 2, 12, 12, 12, 7, 21, 17, 13, 14, 10, 21, 16, 8, 21, 18, 13, 10, 13, 0, 64, 68, 5, 70, 18, 31, 19, 10, 28, 24, 12, 22, 29, 19, 25, 12, 5, 17, 51, 48, 51, 50, 52, 62, 62, 62, 62, 62, 62, 62, 62, 62, 32, 62, 62, 62, 62, 62, 62, 51, 35, 34, 6, 80, 74, 94, 120, 60, 60, 62, 54, 51, 50, 38, 40, 29, 29, 20, 16, 14, 67, 72, 2, 0, 67, 37, 41, 37, 28, 37, 36, 21, 28, 31, 19, 4, 8, 0, 87, 22, 11, 3, 78, 70, 68, 68, 65, 66, 70, 67, 68, 65, 4, 67, 64, 1, 3, 62, 62, 62, 62, 60, 55, 48, 31, 0, 65, 29, 22, 19, 16, 9, 4, 2, 65, 75, 84, 80, 75, 80, 77, 70, 73, 81, 69, 65, 3, 65, 64, 4, 4, 62, 62, 62, 60, 55, 50, 39, 23, 67 }, { 62, 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 26, 57, 60, 54, 14, 111, 6, 75, 1, 1, 12, 72, 10, 67, 19, 58, 71, 93, 105, 118, 100, 67, 75, 1, 1, 84, 91, 4, 17, 68, 79, 93, 7, 68, 85, 88, 5, 75, 92, 9, 69, 80, 88, 65, 73, 73, 79, 70, 5, 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 18, 77, 95, 120, 92, 96, 1, 76, 90, 80, 105, 87, 98, 90, 121, 90, 88, 94, 105, 18, 64, 7, 91, 73, 90, 85, 121, 9, 2, 3, 70, 16, 68, 68, 86, 84, 76, 74, 75, 9, 13, 67, 13, 20, 80, 0, 69, 67, 6, 75, 26, 64, 2, 26, 35, 37, 32, 25, 91, 71, 72, 15, 13, 98, 9, 10, 10, 3, 7, 7, 3, 4, 2, 6, 67, 6, 22, 12, 70, 64, 2, 64, 12, 21, 19, 11, 13, 4, 5, 4, 26, 19, 18, 77, 3, 70, 33, 23, 19, 14, 17, 19, 12, 16, 24, 1, 16, 9, 9, 5, 0, 11, 5, 9, 10, 7, 16, 25, 9, 81, 7, 77, 15, 28, 83, 52, 62, 55, 54, 57, 58, 52, 52, 48, 39, 32, 29, 37, 24, 73, 67, 4, 68, 30, 28, 25, 30, 28, 21, 17, 19, 65, 3, 65, 93, 88, 106, 74, 20, 18, 11, 0, 5, 69, 74, 80, 98, 75, 39, 21, 15, 8, 6, 69, 80, 84, 113, 5, 35, 28, 25, 20, 10, 5, 64, 73, 88, 70, 35, 32, 20, 4, 6, 71, 80, 94, 5, 57, 48, 43, 34, 23, 5, 69, 77, 83, 62, 103, 103, 85, 110, 106, 95, 105, 102, 99, 103, 103, 107, 94, 92, 90, 91, 89, 85, 68, 79, 83, 69, 2, 4, 2, 11, 11, 11, 6, 21, 16, 13, 13, 10, 21, 15, 8, 20, 18, 12, 10, 12, 0, 65, 68, 5, 71, 18, 31, 18, 10, 27, 24, 12, 21, 28, 18, 24, 11, 5, 16, 50, 47, 51, 49, 51, 61, 62, 62, 62, 62, 62, 62, 62, 62, 31, 62, 62, 62, 62, 62, 62, 49, 34, 33, 6, 79, 74, 93, 118, 58, 58, 62, 52, 49, 48, 37, 38, 27, 28, 19, 15, 12, 68, 73, 2, 64, 68, 36, 39, 36, 26, 35, 34, 19, 27, 29, 17, 3, 6, 65, 88, 21, 10, 2, 78, 69, 68, 68, 64, 66, 69, 66, 67, 64, 5, 66, 0, 3, 4, 62, 62, 62, 62, 58, 52, 45, 28, 65, 64, 30, 23, 20, 16, 10, 5, 2, 64, 74, 84, 79, 75, 79, 76, 69, 73, 81, 69, 65, 3, 64, 0, 4, 4, 62, 62, 62, 57, 52, 46, 35, 19, 69 }, { 62, 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 25, 56, 58, 54, 14, 108, 5, 74, 1, 1, 11, 72, 9, 68, 18, 56, 73, 94, 106, 115, 99, 67, 74, 1, 1, 84, 90, 4, 16, 68, 79, 93, 7, 68, 84, 88, 5, 75, 91, 8, 70, 80, 88, 65, 72, 73, 78, 70, 5, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 18, 77, 95, 119, 91, 94, 2, 75, 89, 79, 104, 85, 97, 89, 119, 90, 87, 94, 104, 18, 64, 7, 90, 73, 89, 85, 120, 8, 2, 3, 70, 16, 68, 68, 86, 84, 76, 74, 75, 9, 12, 67, 13, 20, 80, 0, 69, 67, 6, 75, 26, 65, 2, 26, 34, 36, 31, 24, 90, 71, 72, 14, 12, 98, 8, 10, 9, 3, 7, 7, 4, 5, 2, 5, 67, 5, 21, 11, 71, 64, 2, 65, 12, 20, 18, 10, 13, 5, 5, 4, 25, 18, 17, 77, 3, 71, 33, 23, 19, 14, 17, 19, 12, 16, 23, 1, 16, 9, 9, 5, 64, 11, 5, 9, 10, 7, 16, 24, 8, 81, 7, 77, 14, 27, 83, 50, 62, 53, 52, 55, 56, 50, 50, 46, 37, 30, 27, 34, 22, 74, 67, 3, 69, 29, 27, 24, 29, 26, 20, 16, 17, 65, 2, 66, 93, 88, 105, 74, 20, 18, 11, 0, 5, 69, 74, 80, 97, 74, 39, 21, 15, 8, 6, 69, 80, 84, 111, 5, 35, 28, 25, 20, 10, 5, 64, 73, 87, 70, 35, 31, 20, 4, 6, 71, 80, 94, 5, 56, 47, 42, 33, 23, 5, 69, 77, 83, 62, 102, 102, 84, 108, 105, 94, 104, 100, 98, 101, 102, 105, 93, 92, 89, 90, 88, 84, 69, 79, 82, 69, 2, 3, 1, 10, 10, 10, 5, 20, 16, 13, 13, 9, 20, 15, 8, 19, 17, 12, 9, 11, 64, 65, 68, 5, 71, 17, 30, 17, 10, 26, 23, 12, 20, 27, 18, 24, 10, 4, 14, 49, 47, 50, 48, 49, 60, 62, 62, 62, 62, 62, 62, 62, 62, 29, 62, 62, 62, 62, 62, 62, 47, 33, 31, 6, 78, 73, 92, 116, 57, 56, 60, 51, 47, 46, 35, 36, 26, 26, 17, 13, 11, 69, 74, 1, 64, 69, 34, 38, 34, 25, 33, 32, 18, 25, 27, 16, 2, 5, 66, 88, 20, 10, 1, 78, 69, 67, 67, 64, 65, 68, 66, 66, 0, 6, 65, 1, 4, 5, 62, 62, 62, 61, 55, 49, 42, 25, 68, 64, 30, 23, 20, 17, 10, 5, 3, 64, 74, 83, 79, 74, 79, 75, 68, 73, 80, 68, 64, 3, 64, 1, 4, 4, 62, 62, 61, 54, 49, 42, 31, 16, 72 }, { 62, 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 23, 54, 57, 54, 14, 106, 5, 73, 2, 1, 11, 71, 8, 69, 18, 54, 75, 95, 106, 112, 97, 67, 73, 2, 1, 84, 89, 4, 16, 68, 79, 92, 7, 69, 84, 88, 5, 75, 90, 8, 70, 80, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 18, 76, 95, 117, 89, 93, 4, 73, 87, 78, 103, 84, 96, 88, 117, 89, 87, 93, 103, 18, 64, 7, 90, 73, 89, 84, 118, 8, 2, 3, 70, 16, 68, 67, 85, 84, 76, 74, 74, 9, 12, 67, 13, 20, 79, 0, 68, 67, 6, 75, 25, 65, 2, 25, 33, 36, 30, 23, 89, 70, 72, 13, 12, 97, 8, 10, 9, 3, 7, 7, 5, 5, 2, 5, 67, 5, 20, 11, 72, 64, 2, 65, 11, 19, 18, 10, 12, 5, 5, 4, 24, 18, 16, 77, 3, 71, 32, 23, 19, 14, 17, 19, 12, 16, 23, 1, 16, 9, 9, 5, 64, 11, 5, 8, 10, 7, 15, 23, 8, 81, 6, 77, 13, 26, 83, 49, 61, 52, 51, 53, 54, 48, 48, 44, 35, 28, 25, 32, 21, 75, 67, 3, 69, 28, 26, 23, 28, 25, 18, 15, 16, 66, 2, 66, 93, 88, 105, 74, 20, 18, 11, 0, 5, 68, 73, 79, 96, 74, 39, 21, 15, 8, 6, 68, 79, 83, 109, 5, 35, 28, 25, 20, 10, 5, 64, 73, 86, 70, 36, 31, 19, 4, 6, 71, 80, 93, 5, 56, 46, 41, 32, 23, 5, 69, 77, 82, 62, 101, 101, 83, 107, 104, 93, 103, 99, 97, 100, 100, 103, 92, 91, 89, 90, 87, 84, 69, 78, 81, 69, 1, 3, 1, 10, 9, 9, 4, 19, 15, 13, 12, 9, 20, 15, 8, 18, 16, 12, 9, 10, 64, 65, 68, 5, 71, 16, 30, 17, 10, 25, 22, 12, 19, 26, 17, 23, 9, 3, 12, 48, 46, 50, 47, 48, 58, 62, 62, 62, 62, 62, 62, 62, 62, 28, 62, 62, 62, 62, 62, 61, 45, 32, 30, 6, 77, 73, 91, 114, 55, 55, 58, 49, 45, 44, 34, 34, 25, 24, 16, 11, 9, 70, 75, 1, 64, 70, 33, 36, 32, 23, 32, 31, 16, 24, 26, 14, 1, 4, 67, 89, 20, 9, 0, 77, 68, 67, 67, 0, 64, 67, 65, 65, 1, 8, 64, 2, 5, 7, 62, 62, 62, 58, 53, 46, 39, 22, 70, 64, 31, 24, 21, 17, 11, 5, 3, 0, 73, 83, 79, 73, 78, 74, 67, 72, 79, 68, 64, 3, 0, 2, 4, 4, 62, 62, 58, 51, 46, 39, 27, 12, 75 }, { 62, 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 22, 53, 56, 54, 14, 104, 5, 73, 3, 1, 10, 71, 7, 70, 17, 53, 76, 96, 107, 109, 96, 67, 73, 3, 1, 83, 88, 5, 15, 67, 78, 91, 6, 69, 84, 88, 5, 74, 90, 8, 70, 79, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 18, 76, 94, 115, 87, 91, 5, 71, 86, 77, 101, 83, 95, 88, 116, 89, 87, 93, 103, 19, 64, 7, 89, 72, 88, 84, 117, 8, 1, 2, 71, 16, 68, 67, 85, 84, 75, 74, 74, 9, 12, 66, 13, 19, 79, 0, 68, 67, 6, 75, 24, 65, 2, 24, 32, 35, 30, 23, 89, 70, 72, 13, 11, 97, 7, 10, 9, 3, 7, 7, 5, 5, 2, 5, 66, 5, 19, 11, 72, 65, 2, 66, 11, 18, 18, 10, 11, 5, 5, 4, 22, 18, 15, 77, 3, 72, 31, 23, 18, 14, 17, 19, 12, 16, 23, 1, 15, 9, 8, 5, 64, 10, 4, 8, 9, 6, 14, 22, 7, 81, 6, 76, 12, 24, 83, 47, 59, 50, 49, 51, 52, 46, 46, 42, 33, 27, 24, 30, 19, 76, 67, 3, 70, 27, 25, 22, 26, 23, 17, 14, 15, 67, 1, 67, 93, 88, 104, 73, 20, 18, 11, 1, 5, 68, 73, 79, 95, 73, 38, 21, 15, 8, 7, 68, 78, 82, 107, 5, 36, 28, 25, 20, 11, 5, 64, 72, 86, 70, 36, 31, 19, 4, 6, 70, 79, 92, 5, 55, 46, 40, 32, 23, 5, 68, 76, 82, 62, 101, 100, 83, 106, 103, 92, 101, 98, 96, 99, 99, 102, 92, 90, 89, 89, 85, 84, 70, 78, 81, 69, 1, 2, 1, 9, 8, 8, 3, 19, 15, 13, 12, 8, 19, 14, 8, 18, 16, 11, 9, 10, 64, 66, 68, 5, 72, 16, 29, 16, 9, 24, 22, 13, 19, 25, 17, 23, 9, 3, 11, 47, 45, 49, 46, 47, 57, 62, 62, 62, 62, 62, 62, 62, 61, 27, 62, 62, 62, 62, 62, 59, 43, 31, 29, 6, 76, 73, 89, 111, 53, 53, 56, 47, 43, 42, 32, 32, 23, 23, 15, 10, 8, 71, 76, 0, 65, 71, 32, 35, 31, 21, 30, 29, 15, 22, 24, 13, 64, 2, 69, 90, 19, 8, 64, 77, 68, 67, 66, 0, 64, 65, 64, 64, 2, 9, 1, 3, 7, 8, 62, 62, 60, 56, 50, 44, 36, 20, 72, 0, 31, 24, 21, 17, 12, 6, 3, 0, 73, 83, 78, 73, 78, 73, 66, 72, 79, 67, 0, 3, 0, 3, 4, 4, 62, 62, 56, 48, 42, 35, 24, 9, 77 }, { 62, 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 20, 51, 54, 54, 14, 101, 4, 72, 3, 1, 9, 71, 6, 71, 17, 51, 78, 97, 107, 106, 95, 67, 72, 3, 1, 83, 87, 5, 15, 67, 78, 91, 6, 70, 83, 88, 5, 74, 89, 7, 70, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 18, 76, 94, 114, 85, 90, 7, 69, 85, 76, 100, 81, 94, 87, 114, 88, 86, 92, 102, 19, 64, 7, 89, 72, 88, 83, 115, 7, 1, 2, 71, 16, 68, 67, 84, 84, 75, 74, 73, 9, 11, 66, 13, 19, 79, 0, 68, 67, 6, 75, 24, 65, 2, 24, 31, 34, 29, 22, 88, 69, 72, 12, 11, 96, 7, 10, 8, 3, 7, 7, 6, 6, 2, 5, 66, 5, 18, 11, 73, 65, 2, 66, 10, 17, 17, 10, 11, 6, 5, 4, 21, 17, 14, 77, 3, 72, 31, 23, 18, 14, 17, 19, 12, 16, 23, 1, 15, 9, 8, 5, 64, 10, 4, 7, 9, 6, 14, 21, 7, 81, 5, 76, 11, 23, 83, 46, 57, 48, 47, 49, 50, 44, 44, 40, 31, 25, 22, 27, 17, 77, 67, 2, 70, 26, 24, 21, 25, 22, 15, 13, 14, 67, 1, 67, 93, 88, 104, 73, 20, 18, 11, 1, 5, 68, 73, 78, 94, 73, 38, 21, 15, 8, 7, 67, 77, 82, 105, 5, 36, 28, 25, 20, 11, 5, 64, 72, 85, 70, 36, 30, 18, 4, 6, 70, 79, 92, 5, 55, 45, 39, 31, 23, 5, 68, 76, 81, 62, 100, 99, 82, 104, 102, 91, 100, 96, 95, 97, 97, 100, 91, 89, 88, 89, 84, 83, 70, 78, 80, 69, 0, 2, 0, 8, 7, 7, 2, 18, 14, 13, 11, 8, 19, 14, 8, 17, 15, 11, 8, 9, 64, 66, 68, 5, 72, 15, 29, 15, 9, 23, 21, 13, 18, 24, 16, 22, 8, 2, 9, 46, 45, 49, 45, 45, 55, 62, 62, 62, 62, 62, 62, 62, 59, 25, 62, 62, 62, 62, 62, 56, 41, 30, 28, 6, 75, 72, 88, 109, 52, 51, 54, 46, 41, 40, 31, 30, 22, 21, 13, 8, 6, 72, 77, 0, 65, 72, 30, 33, 29, 20, 28, 27, 13, 21, 22, 11, 65, 1, 70, 90, 18, 8, 65, 77, 67, 66, 66, 1, 0, 64, 0, 0, 3, 10, 2, 4, 8, 9, 62, 61, 58, 53, 48, 41, 33, 17, 74, 0, 32, 25, 22, 18, 13, 6, 4, 1, 72, 82, 78, 72, 77, 72, 65, 72, 78, 67, 0, 3, 1, 4, 4, 4, 62, 62, 53, 45, 39, 31, 20, 5, 80 }, { 62, 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 19, 50, 53, 54, 14, 99, 4, 71, 4, 1, 8, 71, 5, 73, 16, 49, 80, 98, 108, 104, 94, 67, 71, 4, 1, 83, 86, 5, 14, 67, 78, 90, 5, 70, 83, 89, 5, 74, 89, 7, 71, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, 78, 88, 97, 68, 16, 0, 18, 76, 94, 112, 84, 88, 8, 68, 84, 75, 99, 80, 93, 86, 112, 88, 86, 92, 101, 19, 64, 7, 88, 72, 87, 83, 114, 7, 0, 1, 72, 16, 68, 67, 84, 84, 75, 74, 73, 8, 11, 66, 13, 18, 79, 0, 68, 67, 5, 75, 23, 66, 2, 23, 29, 33, 28, 21, 88, 69, 72, 11, 10, 96, 6, 9, 8, 3, 7, 7, 7, 6, 2, 4, 66, 4, 17, 10, 74, 65, 2, 67, 10, 16, 17, 9, 10, 6, 5, 4, 19, 17, 13, 77, 3, 73, 30, 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, 9, 8, 4, 65, 9, 3, 7, 8, 5, 13, 20, 6, 81, 5, 76, 10, 21, 83, 44, 55, 46, 45, 47, 47, 42, 42, 38, 29, 23, 20, 25, 15, 78, 67, 2, 71, 25, 22, 19, 23, 20, 14, 11, 12, 68, 0, 68, 93, 88, 103, 73, 20, 18, 11, 1, 5, 68, 73, 78, 93, 72, 38, 21, 15, 8, 7, 67, 77, 81, 104, 5, 36, 28, 25, 19, 11, 5, 64, 72, 85, 70, 36, 30, 18, 4, 6, 70, 79, 91, 5, 54, 44, 38, 30, 22, 5, 68, 76, 81, 62, 99, 98, 82, 103, 101, 91, 99, 95, 94, 96, 96, 99, 91, 89, 88, 88, 83, 83, 71, 78, 80, 69, 0, 1, 0, 7, 6, 5, 1, 17, 14, 13, 11, 7, 18, 13, 7, 16, 14, 10, 8, 8, 65, 67, 68, 5, 73, 14, 28, 14, 9, 22, 20, 13, 17, 23, 16, 22, 7, 1, 7, 45, 44, 48, 43, 44, 54, 62, 62, 62, 62, 62, 62, 62, 56, 24, 62, 62, 62, 62, 61, 54, 39, 28, 26, 6, 75, 72, 87, 107, 50, 49, 52, 44, 38, 38, 29, 28, 20, 19, 12, 6, 5, 73, 78, 64, 66, 73, 29, 32, 27, 18, 26, 25, 12, 19, 20, 10, 66, 64, 72, 91, 17, 7, 66, 77, 67, 66, 65, 1, 0, 0, 0, 1, 4, 11, 3, 5, 9, 10, 61, 59, 56, 51, 45, 38, 30, 14, 77, 0, 32, 25, 22, 18, 13, 6, 4, 1, 72, 82, 78, 72, 77, 71, 64, 72, 78, 66, 1, 3, 1, 4, 4, 3, 62, 61, 51, 42, 36, 27, 16, 2, 83 }, { 62, 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 18, 49, 52, 54, 14, 97, 4, 70, 5, 1, 8, 70, 4, 74, 15, 47, 81, 99, 109, 101, 92, 67, 70, 5, 1, 82, 85, 6, 13, 67, 77, 89, 5, 70, 83, 89, 5, 74, 88, 7, 71, 79, 88, 0, 71, 71, 77, 68, 5, 22, 0, 0, 0, 77, 88, 97, 68, 15, 0, 18, 75, 94, 110, 82, 86, 9, 66, 82, 74, 97, 79, 91, 85, 110, 88, 86, 92, 100, 19, 64, 7, 87, 72, 86, 82, 113, 7, 0, 1, 72, 16, 68, 66, 83, 83, 74, 74, 73, 8, 11, 66, 13, 18, 78, 0, 67, 67, 5, 74, 22, 66, 2, 22, 28, 33, 27, 20, 87, 69, 71, 10, 9, 96, 5, 9, 8, 4, 7, 7, 8, 6, 2, 4, 65, 4, 17, 10, 75, 65, 2, 68, 10, 15, 17, 9, 9, 6, 5, 4, 18, 17, 13, 77, 3, 74, 29, 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, 9, 8, 4, 65, 9, 3, 7, 8, 5, 12, 20, 6, 81, 5, 76, 9, 20, 83, 42, 54, 45, 44, 45, 45, 41, 41, 36, 27, 22, 19, 23, 14, 79, 67, 2, 72, 24, 21, 18, 22, 19, 13, 10, 11, 69, 64, 69, 93, 87, 102, 72, 21, 18, 11, 1, 6, 67, 72, 77, 92, 71, 38, 21, 15, 8, 8, 67, 76, 80, 102, 5, 36, 28, 25, 19, 12, 5, 64, 72, 84, 70, 37, 30, 18, 4, 7, 70, 79, 90, 5, 54, 44, 38, 29, 22, 5, 68, 75, 80, 62, 98, 97, 81, 102, 99, 90, 97, 94, 92, 95, 95, 97, 90, 88, 88, 87, 81, 83, 72, 77, 79, 69, 0, 0, 0, 7, 5, 4, 0, 17, 14, 13, 11, 7, 17, 13, 7, 15, 14, 10, 8, 7, 65, 67, 67, 6, 73, 14, 27, 14, 9, 22, 20, 13, 16, 22, 16, 22, 6, 1, 6, 45, 43, 47, 42, 43, 53, 60, 60, 62, 62, 62, 62, 62, 54, 23, 62, 62, 62, 62, 58, 52, 38, 27, 25, 6, 74, 72, 86, 105, 48, 48, 50, 42, 36, 37, 28, 26, 19, 18, 11, 5, 4, 74, 78, 64, 66, 74, 28, 31, 26, 16, 25, 24, 11, 18, 19, 9, 67, 65, 73, 92, 17, 6, 66, 76, 67, 66, 64, 2, 1, 1, 1, 2, 5, 13, 4, 6, 11, 12, 60, 58, 54, 49, 42, 35, 27, 11, 79, 1, 32, 25, 23, 18, 14, 7, 4, 2, 71, 82, 77, 71, 77, 70, 1, 71, 77, 65, 2, 3, 2, 5, 4, 3, 62, 59, 49, 40, 33, 24, 12, 64, 85 }, { 62, 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 16, 47, 50, 54, 14, 94, 3, 69, 5, 1, 7, 70, 3, 75, 15, 45, 83, 100, 109, 98, 91, 67, 69, 5, 1, 82, 84, 6, 13, 67, 77, 89, 5, 71, 82, 89, 5, 74, 87, 6, 71, 79, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, 77, 88, 97, 67, 14, 0, 18, 75, 94, 109, 80, 85, 11, 64, 81, 73, 96, 77, 90, 84, 108, 87, 85, 91, 99, 19, 64, 7, 87, 72, 86, 82, 111, 6, 0, 1, 72, 16, 68, 66, 83, 83, 74, 74, 72, 8, 10, 66, 13, 18, 78, 0, 67, 67, 5, 74, 22, 66, 2, 22, 27, 32, 26, 19, 86, 68, 71, 9, 9, 95, 5, 9, 7, 4, 7, 7, 9, 7, 2, 4, 65, 4, 16, 10, 76, 65, 2, 68, 9, 14, 16, 9, 9, 7, 5, 4, 17, 16, 12, 77, 3, 74, 29, 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, 9, 8, 4, 65, 9, 3, 6, 8, 5, 12, 19, 5, 81, 4, 76, 8, 19, 83, 41, 52, 43, 42, 43, 43, 39, 39, 34, 25, 20, 17, 20, 12, 80, 67, 1, 72, 23, 20, 17, 21, 17, 11, 9, 10, 69, 64, 69, 93, 87, 102, 72, 21, 18, 11, 1, 6, 67, 72, 77, 91, 71, 38, 21, 15, 8, 8, 66, 75, 80, 100, 5, 36, 28, 25, 19, 12, 5, 64, 72, 83, 70, 37, 29, 17, 4, 7, 70, 79, 90, 5, 53, 43, 37, 28, 22, 5, 68, 75, 80, 62, 97, 96, 80, 100, 98, 89, 96, 92, 91, 93, 93, 95, 89, 87, 87, 87, 80, 82, 72, 77, 78, 69, 64, 0, 64, 6, 4, 3, 64, 16, 13, 13, 10, 6, 17, 13, 7, 14, 13, 10, 7, 6, 65, 67, 67, 6, 73, 13, 27, 13, 9, 21, 19, 13, 15, 21, 15, 21, 5, 0, 4, 44, 43, 47, 41, 41, 51, 58, 58, 62, 62, 62, 62, 62, 52, 21, 59, 62, 59, 62, 56, 49, 36, 26, 24, 6, 73, 71, 85, 103, 47, 46, 48, 41, 34, 35, 26, 24, 18, 16, 9, 3, 2, 75, 79, 65, 66, 75, 26, 29, 24, 15, 23, 22, 9, 16, 17, 7, 68, 66, 74, 92, 16, 6, 67, 76, 66, 65, 64, 2, 2, 2, 2, 3, 6, 14, 5, 7, 12, 13, 60, 56, 52, 46, 40, 32, 24, 8, 81, 1, 33, 26, 23, 19, 15, 7, 5, 2, 71, 81, 77, 70, 76, 69, 2, 71, 76, 65, 2, 3, 2, 6, 4, 3, 62, 57, 46, 37, 30, 20, 8, 68, 88 }, { 62, 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 15, 46, 49, 54, 14, 92, 3, 69, 6, 1, 6, 70, 2, 76, 14, 44, 84, 101, 110, 95, 90, 67, 69, 6, 1, 81, 83, 7, 12, 66, 76, 88, 4, 71, 82, 89, 5, 73, 87, 6, 71, 78, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, 76, 88, 97, 67, 14, 64, 18, 75, 93, 107, 78, 83, 12, 1, 80, 72, 94, 76, 89, 84, 107, 87, 85, 91, 99, 20, 64, 7, 86, 71, 85, 81, 110, 6, 64, 0, 73, 16, 68, 66, 82, 83, 73, 74, 72, 8, 10, 65, 13, 17, 78, 0, 67, 67, 5, 74, 21, 66, 2, 21, 26, 31, 26, 19, 86, 68, 71, 9, 8, 95, 4, 9, 7, 4, 7, 7, 9, 7, 2, 4, 64, 4, 15, 10, 76, 66, 2, 69, 9, 13, 16, 9, 8, 7, 5, 4, 15, 16, 11, 77, 3, 75, 28, 22, 17, 14, 17, 18, 11, 16, 22, 0, 13, 9, 7, 4, 65, 8, 2, 6, 7, 4, 11, 18, 5, 81, 4, 75, 7, 17, 83, 39, 50, 41, 40, 41, 41, 37, 37, 32, 23, 19, 16, 18, 10, 81, 67, 1, 73, 22, 19, 16, 19, 16, 10, 8, 9, 70, 65, 70, 93, 87, 101, 71, 21, 18, 11, 2, 6, 67, 72, 76, 90, 70, 37, 21, 15, 8, 9, 66, 74, 79, 98, 5, 37, 28, 25, 19, 13, 5, 64, 71, 83, 70, 37, 29, 17, 4, 7, 69, 78, 89, 5, 53, 43, 36, 28, 22, 5, 67, 74, 79, 62, 97, 95, 80, 99, 97, 88, 94, 91, 90, 92, 92, 94, 89, 86, 87, 86, 78, 82, 73, 77, 78, 69, 64, 64, 64, 5, 3, 2, 65, 16, 13, 13, 10, 6, 16, 12, 7, 14, 13, 9, 7, 6, 65, 68, 67, 6, 74, 13, 26, 12, 8, 20, 19, 14, 15, 20, 15, 21, 5, 0, 3, 43, 42, 46, 40, 40, 50, 56, 56, 61, 60, 62, 62, 60, 49, 20, 57, 62, 56, 62, 53, 47, 34, 25, 23, 6, 72, 71, 83, 100, 45, 44, 46, 39, 32, 33, 25, 22, 16, 15, 8, 2, 1, 76, 80, 65, 67, 76, 25, 28, 23, 13, 21, 20, 8, 15, 15, 6, 70, 68, 76, 93, 15, 5, 68, 76, 66, 65, 0, 3, 2, 4, 3, 4, 7, 15, 7, 8, 14, 14, 59, 55, 50, 44, 37, 30, 21, 6, 83, 2, 33, 26, 24, 19, 16, 8, 5, 3, 70, 81, 76, 70, 76, 68, 3, 71, 76, 64, 3, 3, 3, 7, 4, 3, 62, 55, 44, 34, 26, 16, 5, 71, 90 }, { 62, 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 13, 44, 48, 54, 14, 90, 3, 68, 7, 1, 5, 70, 1, 77, 14, 42, 86, 102, 110, 92, 89, 67, 68, 7, 1, 81, 82, 7, 12, 66, 76, 87, 4, 72, 82, 89, 5, 73, 86, 6, 72, 78, 88, 2, 70, 71, 76, 66, 5, 22, 0, 0, 0, 76, 89, 97, 66, 13, 64, 18, 75, 93, 105, 77, 82, 14, 2, 79, 71, 93, 75, 88, 83, 105, 86, 85, 90, 98, 20, 64, 7, 86, 71, 85, 81, 108, 6, 64, 0, 73, 16, 68, 66, 82, 83, 73, 74, 71, 8, 10, 65, 13, 17, 78, 0, 67, 67, 5, 74, 20, 67, 2, 20, 25, 30, 25, 18, 85, 67, 71, 8, 8, 94, 4, 9, 7, 4, 7, 7, 10, 7, 2, 3, 64, 3, 14, 9, 77, 66, 2, 69, 8, 12, 16, 8, 7, 7, 5, 4, 14, 16, 10, 77, 3, 75, 27, 22, 17, 14, 17, 18, 11, 16, 21, 0, 13, 9, 7, 4, 66, 8, 2, 5, 7, 4, 10, 17, 4, 81, 3, 75, 6, 16, 83, 38, 48, 39, 38, 39, 39, 35, 35, 30, 21, 17, 14, 16, 8, 82, 67, 1, 73, 21, 18, 15, 18, 14, 8, 7, 7, 71, 65, 70, 93, 87, 101, 71, 21, 18, 11, 2, 6, 67, 72, 76, 89, 70, 37, 21, 15, 8, 9, 65, 74, 78, 96, 5, 37, 28, 25, 19, 13, 5, 64, 71, 82, 70, 37, 29, 16, 4, 7, 69, 78, 88, 5, 52, 42, 35, 27, 22, 5, 67, 74, 79, 62, 96, 94, 79, 98, 96, 87, 93, 90, 89, 91, 90, 92, 88, 86, 87, 86, 77, 82, 73, 77, 77, 69, 65, 64, 64, 4, 2, 1, 66, 15, 12, 13, 9, 5, 16, 12, 7, 13, 12, 9, 7, 5, 66, 68, 67, 6, 74, 12, 26, 11, 8, 19, 18, 14, 14, 19, 14, 20, 4, 64, 1, 42, 41, 46, 39, 39, 48, 54, 54, 59, 57, 62, 62, 57, 47, 19, 54, 62, 53, 58, 50, 44, 32, 24, 21, 6, 71, 71, 82, 98, 43, 42, 44, 37, 30, 31, 23, 20, 15, 13, 7, 0, 64, 77, 81, 66, 67, 77, 24, 26, 21, 11, 19, 18, 6, 13, 13, 4, 71, 69, 77, 94, 14, 4, 69, 76, 65, 65, 0, 3, 3, 5, 3, 5, 8, 16, 8, 9, 15, 15, 59, 53, 48, 41, 35, 27, 18, 3, 86, 2, 34, 27, 24, 19, 16, 8, 5, 3, 70, 81, 76, 69, 75, 67, 4, 71, 75, 64, 3, 3, 3, 8, 4, 3, 61, 53, 41, 31, 23, 12, 1, 75, 93 }, { 62, 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 12, 43, 46, 54, 14, 87, 2, 67, 7, 1, 5, 69, 0, 78, 13, 40, 88, 103, 111, 89, 87, 67, 67, 7, 1, 81, 81, 7, 11, 66, 76, 87, 4, 72, 81, 89, 5, 73, 85, 5, 72, 78, 88, 2, 69, 70, 75, 66, 5, 22, 0, 0, 0, 75, 89, 97, 66, 12, 64, 18, 74, 93, 104, 75, 80, 15, 4, 77, 70, 92, 73, 87, 82, 103, 86, 84, 90, 97, 20, 64, 7, 85, 71, 84, 80, 107, 5, 64, 0, 73, 16, 68, 65, 81, 83, 73, 74, 71, 8, 9, 65, 13, 17, 77, 0, 66, 67, 5, 74, 20, 67, 2, 20, 24, 30, 24, 17, 84, 67, 71, 7, 7, 94, 3, 9, 6, 4, 7, 7, 11, 8, 2, 3, 64, 3, 13, 9, 78, 66, 2, 70, 8, 11, 15, 8, 7, 8, 5, 4, 13, 15, 9, 77, 3, 76, 27, 22, 17, 14, 17, 18, 11, 16, 21, 0, 13, 9, 7, 4, 66, 8, 2, 5, 7, 4, 10, 16, 4, 81, 3, 75, 5, 15, 83, 36, 46, 38, 37, 37, 37, 33, 33, 28, 19, 15, 12, 13, 7, 83, 67, 0, 74, 20, 17, 14, 17, 13, 7, 6, 6, 71, 66, 71, 93, 87, 100, 71, 21, 18, 11, 2, 6, 66, 71, 75, 88, 69, 37, 21, 15, 8, 9, 65, 73, 78, 94, 5, 37, 28, 25, 19, 13, 5, 64, 71, 81, 70, 38, 28, 16, 4, 7, 69, 78, 88, 5, 52, 41, 34, 26, 22, 5, 67, 74, 78, 62, 95, 93, 78, 96, 95, 86, 92, 88, 88, 89, 89, 90, 87, 85, 86, 85, 76, 81, 74, 76, 76, 69, 65, 65, 65, 4, 1, 0, 67, 14, 12, 13, 9, 5, 15, 12, 7, 12, 11, 9, 6, 4, 66, 68, 67, 6, 74, 11, 25, 11, 8, 18, 17, 14, 13, 18, 14, 20, 3, 65, 64, 41, 41, 45, 38, 37, 47, 52, 52, 57, 55, 62, 61, 54, 45, 17, 51, 62, 50, 54, 48, 42, 30, 23, 20, 6, 70, 70, 81, 96, 42, 41, 42, 36, 28, 29, 22, 18, 14, 11, 5, 65, 65, 78, 82, 66, 67, 78, 22, 25, 19, 10, 18, 17, 5, 12, 12, 3, 72, 70, 78, 94, 14, 4, 70, 75, 65, 64, 1, 4, 4, 6, 4, 6, 9, 18, 9, 10, 16, 17, 58, 51, 46, 39, 32, 24, 15, 0, 88, 2, 34, 27, 25, 20, 17, 8, 6, 4, 69, 80, 76, 68, 75, 66, 5, 70, 74, 0, 4, 3, 4, 9, 4, 3, 59, 51, 39, 28, 20, 9, 66, 78, 96 }, { 61, 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 10, 41, 45, 54, 14, 85, 2, 66, 8, 1, 4, 69, 64, 79, 13, 38, 89, 104, 111, 86, 86, 67, 66, 8, 1, 80, 80, 8, 11, 66, 75, 86, 3, 73, 81, 89, 5, 73, 85, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, 75, 89, 97, 65, 11, 64, 18, 74, 93, 102, 73, 79, 17, 6, 76, 69, 90, 72, 86, 81, 101, 85, 84, 89, 96, 20, 64, 7, 85, 71, 84, 80, 105, 5, 65, 64, 74, 16, 68, 65, 81, 83, 72, 74, 70, 8, 9, 65, 13, 16, 77, 0, 66, 67, 5, 74, 19, 67, 2, 19, 23, 29, 23, 16, 84, 66, 71, 6, 7, 93, 3, 9, 6, 4, 7, 7, 12, 8, 2, 3, 0, 3, 12, 9, 79, 66, 2, 70, 7, 10, 15, 8, 6, 8, 5, 4, 11, 15, 8, 77, 3, 76, 26, 22, 17, 14, 17, 18, 11, 16, 21, 0, 12, 9, 7, 4, 66, 7, 1, 4, 6, 3, 9, 15, 3, 81, 2, 75, 4, 13, 83, 35, 44, 36, 35, 35, 35, 31, 31, 26, 17, 14, 11, 11, 5, 84, 67, 0, 74, 19, 16, 13, 15, 11, 5, 5, 5, 72, 66, 71, 93, 87, 100, 70, 21, 18, 11, 2, 6, 66, 71, 75, 87, 69, 37, 21, 15, 8, 10, 64, 72, 77, 92, 5, 37, 28, 25, 19, 14, 5, 64, 71, 81, 70, 38, 28, 15, 4, 7, 69, 78, 87, 5, 51, 41, 33, 25, 22, 5, 67, 73, 78, 62, 94, 92, 78, 95, 94, 85, 90, 87, 87, 88, 87, 89, 87, 84, 86, 85, 74, 81, 74, 76, 76, 69, 66, 65, 65, 3, 0, 64, 68, 14, 11, 13, 8, 4, 15, 11, 7, 11, 11, 8, 6, 3, 66, 69, 67, 6, 75, 11, 25, 10, 8, 17, 17, 14, 12, 17, 13, 19, 2, 65, 65, 40, 40, 45, 37, 36, 45, 50, 50, 55, 52, 60, 59, 51, 42, 16, 48, 62, 47, 50, 45, 39, 28, 22, 19, 6, 69, 70, 80, 94, 40, 39, 40, 34, 26, 27, 20, 16, 12, 10, 4, 66, 67, 79, 83, 67, 68, 79, 21, 23, 18, 8, 16, 15, 3, 10, 10, 1, 73, 72, 80, 95, 13, 3, 71, 75, 64, 64, 1, 4, 4, 7, 5, 7, 10, 19, 10, 11, 18, 18, 58, 50, 44, 36, 30, 21, 12, 66, 90, 3, 35, 28, 25, 20, 18, 9, 6, 4, 69, 80, 75, 68, 74, 65, 6, 70, 74, 0, 4, 3, 4, 10, 4, 3, 58, 49, 36, 25, 17, 5, 70, 82, 98 }, { 60, 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 9, 40, 44, 54, 14, 83, 2, 65, 9, 1, 3, 69, 65, 80, 12, 36, 91, 105, 112, 83, 85, 67, 65, 9, 1, 80, 79, 8, 10, 66, 75, 85, 3, 73, 81, 89, 5, 73, 84, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, 74, 89, 97, 65, 10, 64, 18, 74, 93, 100, 71, 77, 18, 8, 75, 68, 89, 71, 85, 80, 99, 85, 84, 89, 95, 20, 64, 7, 84, 71, 83, 79, 104, 5, 65, 64, 74, 16, 68, 65, 80, 83, 72, 74, 70, 8, 9, 65, 13, 16, 77, 0, 66, 67, 5, 74, 18, 67, 2, 18, 22, 28, 22, 15, 83, 66, 71, 5, 6, 93, 2, 9, 6, 4, 7, 7, 13, 8, 2, 3, 0, 3, 11, 9, 80, 66, 2, 71, 7, 9, 15, 8, 5, 8, 5, 4, 10, 15, 7, 77, 3, 77, 25, 22, 17, 14, 17, 18, 11, 16, 21, 0, 12, 9, 7, 4, 66, 7, 1, 4, 6, 3, 8, 14, 3, 81, 2, 75, 3, 12, 83, 33, 42, 34, 33, 33, 33, 29, 29, 24, 15, 12, 9, 9, 3, 85, 67, 0, 75, 18, 15, 12, 14, 10, 4, 4, 4, 73, 67, 72, 93, 87, 99, 70, 21, 18, 11, 2, 6, 66, 71, 74, 86, 68, 37, 21, 15, 8, 10, 64, 71, 76, 90, 5, 37, 28, 25, 19, 14, 5, 64, 71, 80, 70, 38, 28, 15, 4, 7, 69, 78, 86, 5, 51, 40, 32, 24, 22, 5, 67, 73, 77, 62, 93, 91, 77, 94, 93, 84, 89, 86, 86, 87, 86, 87, 86, 83, 86, 84, 73, 81, 75, 76, 75, 69, 66, 66, 65, 2, 64, 65, 69, 13, 11, 13, 8, 4, 14, 11, 7, 10, 10, 8, 6, 2, 66, 69, 67, 6, 75, 10, 24, 9, 8, 16, 16, 14, 11, 16, 13, 19, 1, 66, 67, 39, 39, 44, 36, 35, 44, 48, 48, 53, 50, 57, 56, 48, 40, 15, 45, 59, 44, 46, 42, 37, 26, 21, 18, 6, 68, 70, 79, 92, 38, 37, 38, 32, 24, 25, 19, 14, 11, 8, 3, 68, 68, 80, 84, 67, 68, 80, 20, 22, 16, 6, 14, 13, 2, 9, 8, 0, 74, 73, 81, 96, 12, 2, 72, 75, 64, 64, 2, 5, 5, 8, 6, 8, 11, 20, 11, 12, 19, 19, 57, 48, 42, 34, 27, 18, 9, 69, 92, 3, 35, 28, 26, 20, 19, 9, 6, 5, 68, 80, 75, 67, 74, 64, 7, 70, 73, 1, 5, 3, 5, 11, 4, 3, 57, 47, 34, 22, 14, 1, 74, 85, 101 }, { 58, 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 7, 38, 42, 53, 14, 81, 1, 65, 9, 0, 2, 69, 67, 82, 11, 34, 93, 106, 113, 81, 84, 68, 65, 9, 0, 80, 78, 8, 9, 66, 75, 85, 2, 74, 81, 90, 5, 73, 84, 4, 73, 78, 88, 3, 69, 70, 75, 65, 4, 22, 0, 0, 0, 74, 90, 97, 65, 9, 65, 18, 74, 93, 99, 70, 76, 19, 9, 74, 67, 88, 70, 84, 80, 98, 85, 84, 89, 95, 20, 64, 7, 84, 71, 83, 79, 103, 4, 66, 65, 75, 16, 68, 65, 80, 83, 72, 74, 70, 7, 8, 65, 12, 15, 77, 64, 66, 67, 4, 74, 17, 68, 1, 17, 20, 27, 21, 14, 83, 66, 71, 4, 5, 93, 1, 8, 5, 4, 7, 7, 13, 8, 2, 2, 0, 2, 10, 8, 81, 67, 1, 72, 6, 8, 14, 7, 4, 8, 5, 4, 8, 14, 6, 77, 3, 78, 24, 21, 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, 67, 6, 0, 3, 5, 2, 7, 13, 2, 81, 1, 75, 2, 10, 83, 31, 40, 32, 31, 31, 30, 27, 27, 22, 13, 10, 7, 6, 1, 87, 68, 64, 76, 17, 13, 10, 12, 8, 2, 2, 2, 74, 68, 73, 93, 87, 99, 70, 21, 18, 11, 2, 6, 66, 71, 74, 85, 68, 36, 21, 15, 8, 10, 64, 71, 76, 89, 4, 37, 28, 24, 18, 14, 5, 64, 71, 80, 70, 38, 27, 14, 3, 7, 69, 78, 86, 5, 50, 39, 31, 23, 21, 5, 67, 73, 77, 62, 93, 90, 77, 93, 92, 84, 88, 85, 85, 86, 85, 86, 86, 83, 86, 84, 72, 81, 76, 76, 75, 69, 67, 67, 66, 1, 65, 67, 71, 12, 10, 13, 7, 3, 13, 10, 6, 9, 9, 7, 5, 1, 67, 70, 67, 6, 76, 9, 23, 8, 7, 15, 15, 14, 10, 14, 12, 18, 0, 67, 69, 38, 38, 43, 34, 33, 42, 46, 46, 50, 47, 54, 53, 45, 37, 13, 42, 55, 41, 41, 39, 34, 24, 19, 16, 6, 68, 70, 78, 90, 36, 35, 36, 30, 21, 23, 17, 11, 9, 6, 1, 70, 70, 81, 85, 68, 69, 82, 18, 20, 14, 4, 12, 11, 0, 7, 6, 65, 76, 75, 83, 97, 11, 1, 73, 75, 64, 64, 2, 5, 5, 9, 6, 9, 11, 21, 12, 13, 20, 20, 56, 46, 39, 31, 24, 15, 5, 72, 95, 3, 35, 28, 26, 20, 19, 9, 6, 5, 68, 80, 75, 67, 74, 0, 8, 70, 73, 1, 5, 3, 5, 11, 4, 2, 55, 44, 31, 19, 10, 66, 78, 89, 104 }, { 57, 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 6, 37, 41, 53, 14, 78, 1, 64, 10, 0, 2, 68, 68, 83, 11, 33, 94, 107, 113, 78, 82, 68, 64, 10, 0, 79, 76, 9, 9, 65, 74, 84, 2, 74, 80, 90, 5, 72, 83, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 73, 90, 97, 64, 9, 65, 18, 73, 92, 97, 68, 74, 21, 11, 72, 66, 86, 68, 82, 79, 96, 84, 83, 88, 94, 21, 0, 8, 83, 70, 82, 78, 101, 4, 66, 65, 75, 17, 68, 64, 79, 82, 71, 73, 69, 7, 8, 64, 12, 15, 76, 64, 65, 67, 4, 73, 17, 68, 1, 17, 19, 27, 21, 14, 82, 65, 70, 4, 5, 92, 1, 8, 5, 5, 7, 7, 14, 9, 3, 2, 1, 2, 10, 8, 81, 67, 1, 72, 6, 7, 14, 7, 4, 9, 6, 4, 7, 14, 6, 76, 3, 78, 24, 21, 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, 67, 6, 0, 3, 5, 2, 7, 13, 2, 80, 1, 74, 2, 9, 82, 30, 39, 31, 30, 29, 28, 26, 26, 20, 12, 9, 6, 4, 0, 88, 68, 64, 76, 16, 12, 9, 11, 7, 1, 1, 1, 74, 68, 73, 92, 86, 98, 69, 22, 18, 11, 3, 7, 65, 70, 73, 83, 67, 36, 21, 15, 8, 11, 0, 70, 75, 87, 4, 38, 29, 24, 18, 15, 5, 64, 70, 79, 70, 39, 27, 14, 3, 8, 68, 77, 85, 5, 50, 39, 31, 23, 21, 5, 66, 72, 76, 62, 92, 89, 76, 91, 90, 83, 86, 83, 83, 84, 83, 84, 85, 82, 85, 83, 70, 80, 76, 75, 74, 68, 67, 67, 66, 1, 65, 68, 72, 12, 10, 14, 7, 3, 13, 10, 6, 9, 9, 7, 5, 1, 67, 70, 66, 7, 76, 9, 23, 8, 7, 15, 15, 15, 10, 13, 12, 18, 0, 67, 70, 38, 38, 43, 33, 32, 41, 44, 44, 48, 45, 52, 51, 43, 35, 12, 40, 52, 38, 37, 37, 32, 23, 18, 15, 6, 67, 69, 76, 87, 35, 34, 35, 29, 19, 22, 16, 9, 8, 5, 0, 71, 71, 82, 85, 68, 69, 83, 17, 19, 13, 3, 11, 10, 64, 6, 5, 66, 77, 76, 84, 97, 11, 1, 73, 74, 0, 0, 3, 6, 6, 11, 7, 10, 12, 23, 14, 14, 22, 22, 56, 45, 37, 29, 22, 13, 2, 74, 97, 4, 36, 29, 27, 21, 20, 10, 7, 6, 67, 79, 74, 66, 73, 2, 10, 69, 72, 2, 6, 4, 6, 12, 4, 2, 54, 42, 29, 17, 7, 69, 81, 92, 106 }, { 56, 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 5, 36, 40, 53, 14, 76, 1, 0, 11, 0, 1, 68, 69, 84, 10, 31, 96, 108, 114, 75, 81, 68, 0, 11, 0, 79, 75, 9, 8, 65, 74, 83, 2, 74, 80, 90, 5, 72, 82, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 18, 73, 92, 95, 66, 72, 22, 13, 71, 65, 85, 67, 81, 78, 94, 84, 83, 88, 93, 21, 0, 8, 82, 70, 81, 78, 100, 4, 66, 65, 75, 17, 68, 64, 79, 82, 71, 73, 69, 7, 8, 64, 12, 15, 76, 64, 65, 67, 4, 73, 16, 68, 1, 16, 18, 26, 20, 13, 81, 65, 70, 3, 4, 92, 0, 8, 5, 5, 7, 7, 15, 9, 3, 2, 1, 2, 9, 8, 82, 67, 1, 73, 6, 6, 14, 7, 3, 9, 6, 4, 6, 14, 5, 76, 3, 79, 23, 21, 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, 67, 6, 0, 3, 5, 2, 6, 12, 1, 80, 1, 74, 1, 8, 82, 28, 37, 29, 28, 27, 26, 24, 24, 18, 10, 7, 4, 2, 65, 89, 68, 64, 77, 15, 11, 8, 10, 5, 0, 0, 0, 75, 69, 74, 92, 86, 97, 69, 22, 18, 11, 3, 7, 65, 70, 73, 82, 66, 36, 21, 15, 8, 11, 0, 69, 74, 85, 4, 38, 29, 24, 18, 15, 5, 64, 70, 78, 70, 39, 27, 14, 3, 8, 68, 77, 84, 5, 49, 38, 30, 22, 21, 5, 66, 72, 76, 62, 91, 88, 75, 90, 89, 82, 85, 82, 82, 83, 82, 82, 84, 81, 85, 82, 69, 80, 77, 75, 73, 68, 67, 68, 66, 0, 66, 69, 73, 11, 10, 14, 7, 2, 12, 10, 6, 8, 8, 7, 5, 0, 67, 70, 66, 7, 76, 8, 22, 7, 7, 14, 14, 15, 9, 12, 12, 18, 64, 68, 72, 37, 37, 42, 32, 31, 40, 42, 42, 46, 43, 49, 48, 40, 33, 11, 37, 49, 35, 33, 34, 30, 21, 17, 14, 6, 66, 69, 75, 85, 33, 32, 33, 27, 17, 20, 14, 7, 7, 3, 64, 73, 72, 83, 86, 69, 69, 84, 16, 18, 11, 1, 9, 8, 65, 4, 3, 67, 78, 77, 85, 98, 10, 0, 74, 74, 0, 0, 4, 6, 7, 12, 8, 11, 13, 24, 15, 15, 23, 23, 55, 43, 35, 27, 19, 10, 64, 77, 99, 4, 36, 29, 27, 21, 21, 10, 7, 6, 67, 79, 74, 65, 73, 3, 11, 69, 71, 3, 7, 4, 6, 13, 4, 2, 53, 40, 27, 14, 4, 73, 85, 95, 109 }, { 55, 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 3, 34, 39, 53, 14, 74, 1, 1, 12, 0, 0, 68, 70, 85, 10, 29, 97, 109, 114, 72, 80, 68, 1, 12, 0, 78, 74, 10, 8, 65, 73, 82, 1, 75, 80, 90, 5, 72, 82, 4, 73, 77, 88, 5, 68, 69, 74, 0, 4, 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 18, 73, 92, 93, 64, 71, 24, 15, 70, 64, 83, 66, 80, 77, 92, 83, 83, 87, 92, 21, 0, 8, 82, 70, 81, 77, 98, 4, 67, 66, 76, 17, 68, 64, 78, 82, 70, 73, 68, 7, 8, 64, 12, 14, 76, 64, 65, 67, 4, 73, 15, 68, 1, 15, 17, 25, 19, 12, 81, 64, 70, 2, 4, 91, 0, 8, 5, 5, 7, 7, 16, 9, 3, 2, 2, 2, 8, 8, 83, 67, 1, 73, 5, 5, 14, 7, 2, 9, 6, 4, 4, 14, 4, 76, 3, 79, 22, 21, 16, 14, 17, 17, 10, 16, 20, 64, 10, 9, 6, 3, 67, 5, 64, 2, 4, 1, 5, 11, 1, 80, 0, 74, 0, 6, 82, 27, 35, 27, 26, 25, 24, 22, 22, 16, 8, 6, 3, 0, 67, 90, 68, 64, 77, 14, 10, 7, 8, 4, 65, 64, 64, 76, 69, 74, 92, 86, 97, 68, 22, 18, 11, 3, 7, 65, 70, 72, 81, 66, 36, 21, 15, 8, 12, 1, 68, 73, 83, 4, 38, 29, 24, 18, 16, 5, 64, 70, 78, 70, 39, 27, 13, 3, 8, 68, 77, 83, 5, 49, 38, 29, 21, 21, 5, 66, 71, 75, 62, 90, 87, 75, 89, 88, 81, 83, 81, 81, 82, 80, 81, 84, 80, 85, 82, 67, 80, 77, 75, 73, 68, 68, 68, 66, 64, 67, 70, 74, 11, 9, 14, 6, 2, 12, 9, 6, 7, 8, 6, 5, 64, 67, 71, 66, 7, 77, 8, 22, 6, 7, 13, 14, 15, 8, 11, 11, 17, 65, 68, 73, 36, 36, 42, 31, 30, 38, 40, 40, 44, 40, 47, 46, 37, 30, 10, 34, 46, 32, 29, 31, 27, 19, 16, 13, 6, 65, 69, 74, 83, 31, 30, 31, 25, 15, 18, 13, 5, 5, 2, 65, 74, 74, 84, 87, 69, 70, 85, 15, 16, 10, 64, 7, 6, 67, 3, 1, 69, 79, 79, 87, 99, 9, 64, 75, 74, 1, 0, 4, 7, 7, 13, 9, 12, 14, 25, 16, 16, 25, 24, 55, 42, 33, 24, 17, 7, 67, 80, 101, 5, 37, 30, 28, 21, 22, 11, 7, 7, 66, 79, 73, 65, 72, 4, 12, 69, 71, 3, 7, 4, 7, 14, 4, 2, 52, 38, 24, 11, 1, 77, 89, 99, 111 }, { 53, 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 2, 33, 37, 53, 14, 71, 0, 2, 12, 0, 64, 68, 71, 86, 9, 27, 99, 110, 115, 69, 79, 68, 2, 12, 0, 78, 73, 10, 7, 65, 73, 82, 1, 75, 79, 90, 5, 72, 81, 3, 74, 77, 88, 5, 67, 69, 73, 0, 4, 22, 0, 0, 0, 71, 91, 97, 0, 6, 65, 18, 73, 92, 92, 0, 69, 25, 16, 69, 0, 82, 64, 79, 76, 90, 83, 82, 87, 91, 21, 0, 8, 81, 70, 80, 77, 97, 3, 67, 66, 76, 17, 68, 64, 78, 82, 70, 73, 68, 7, 7, 64, 12, 14, 76, 64, 65, 67, 4, 73, 15, 69, 1, 15, 16, 24, 18, 11, 80, 64, 70, 1, 3, 91, 64, 8, 4, 5, 7, 7, 17, 10, 3, 1, 2, 1, 7, 7, 84, 67, 1, 74, 5, 4, 13, 6, 2, 10, 6, 4, 3, 13, 3, 76, 3, 80, 22, 21, 16, 14, 17, 17, 10, 16, 19, 64, 10, 9, 6, 3, 68, 5, 64, 2, 4, 1, 5, 10, 0, 80, 0, 74, 64, 5, 82, 25, 33, 25, 24, 23, 22, 20, 20, 14, 6, 4, 1, 66, 69, 91, 68, 65, 78, 13, 9, 6, 7, 2, 66, 65, 66, 76, 70, 75, 92, 86, 96, 68, 22, 18, 11, 3, 7, 65, 70, 72, 80, 65, 36, 21, 15, 8, 12, 1, 68, 73, 81, 4, 38, 29, 24, 18, 16, 5, 64, 70, 77, 70, 39, 26, 13, 3, 8, 68, 77, 83, 5, 48, 37, 28, 20, 21, 5, 66, 71, 75, 62, 89, 86, 74, 87, 87, 80, 82, 79, 80, 80, 79, 79, 83, 80, 84, 81, 66, 79, 78, 75, 72, 68, 68, 69, 67, 65, 68, 71, 75, 10, 9, 14, 6, 1, 11, 9, 6, 6, 7, 6, 4, 65, 68, 71, 66, 7, 77, 7, 21, 5, 7, 12, 13, 15, 7, 10, 11, 17, 66, 69, 75, 35, 36, 41, 30, 28, 37, 38, 38, 42, 38, 44, 43, 34, 28, 8, 31, 42, 29, 25, 29, 25, 17, 15, 11, 6, 64, 68, 73, 81, 30, 28, 29, 24, 13, 16, 11, 3, 4, 0, 67, 76, 75, 85, 88, 70, 70, 86, 13, 15, 8, 65, 5, 4, 68, 1, 64, 70, 80, 80, 88, 99, 8, 64, 76, 74, 1, 1, 5, 7, 8, 14, 9, 13, 15, 26, 17, 17, 26, 25, 54, 40, 31, 22, 14, 4, 70, 83, 104, 5, 37, 30, 28, 22, 22, 11, 8, 7, 66, 78, 73, 64, 72, 5, 13, 69, 70, 4, 8, 4, 7, 15, 4, 2, 50, 36, 22, 8, 65, 81, 93, 102, 114 }, { 52, 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 0, 31, 36, 53, 14, 69, 0, 3, 13, 0, 64, 67, 72, 87, 9, 25, 101, 111, 115, 66, 77, 68, 3, 13, 0, 78, 72, 10, 7, 65, 73, 81, 1, 76, 79, 90, 5, 72, 80, 3, 74, 77, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 18, 72, 92, 90, 2, 68, 27, 18, 67, 1, 81, 0, 78, 75, 88, 82, 82, 86, 90, 21, 0, 8, 81, 70, 80, 76, 95, 3, 67, 66, 76, 17, 68, 0, 77, 82, 70, 73, 67, 7, 7, 64, 12, 14, 75, 64, 64, 67, 4, 73, 14, 69, 1, 14, 15, 24, 17, 10, 79, 0, 70, 0, 3, 90, 64, 8, 4, 5, 7, 7, 18, 10, 3, 1, 2, 1, 6, 7, 85, 67, 1, 74, 4, 3, 13, 6, 1, 10, 6, 4, 2, 13, 2, 76, 3, 80, 21, 21, 16, 14, 17, 17, 10, 16, 19, 64, 10, 9, 6, 3, 68, 5, 64, 1, 4, 1, 4, 9, 0, 80, 64, 74, 65, 4, 82, 24, 31, 24, 23, 21, 20, 18, 18, 12, 4, 2, 64, 68, 70, 92, 68, 65, 78, 12, 8, 5, 6, 1, 68, 66, 67, 77, 70, 75, 92, 86, 96, 68, 22, 18, 11, 3, 7, 64, 69, 71, 79, 65, 36, 21, 15, 8, 12, 2, 67, 72, 79, 4, 38, 29, 24, 18, 16, 5, 64, 70, 76, 70, 40, 26, 12, 3, 8, 68, 77, 82, 5, 48, 36, 27, 19, 21, 5, 66, 71, 74, 62, 88, 85, 73, 86, 86, 79, 81, 78, 79, 79, 77, 77, 82, 79, 84, 81, 65, 79, 78, 74, 71, 68, 69, 69, 67, 65, 69, 72, 76, 9, 8, 14, 5, 1, 11, 9, 6, 5, 6, 6, 4, 66, 68, 71, 66, 7, 77, 6, 21, 5, 7, 11, 12, 15, 6, 9, 10, 16, 67, 70, 77, 34, 35, 41, 29, 27, 35, 36, 36, 40, 35, 41, 41, 31, 26, 7, 28, 39, 26, 21, 26, 22, 15, 14, 10, 6, 0, 68, 72, 79, 28, 27, 27, 22, 11, 14, 10, 1, 3, 65, 68, 78, 77, 86, 89, 70, 70, 87, 12, 13, 6, 67, 4, 3, 70, 0, 65, 72, 81, 81, 89, 100, 8, 65, 77, 73, 2, 1, 5, 8, 9, 15, 10, 14, 16, 28, 18, 18, 27, 27, 54, 38, 29, 19, 12, 1, 73, 86, 106, 5, 38, 31, 29, 22, 23, 11, 8, 8, 65, 78, 73, 0, 71, 6, 14, 68, 69, 4, 8, 4, 8, 16, 4, 2, 49, 34, 19, 5, 68, 84, 97, 106, 117 }, { 51, 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 64, 30, 35, 53, 14, 67, 0, 3, 14, 0, 65, 67, 73, 88, 8, 24, 102, 112, 116, 0, 76, 68, 3, 14, 0, 77, 71, 11, 6, 64, 72, 80, 0, 76, 79, 90, 5, 71, 80, 3, 74, 76, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 70, 91, 97, 1, 5, 66, 18, 72, 91, 88, 4, 66, 28, 20, 66, 2, 79, 1, 77, 75, 87, 82, 82, 86, 90, 22, 0, 8, 80, 69, 79, 76, 94, 3, 68, 67, 77, 17, 68, 0, 77, 82, 69, 73, 67, 7, 7, 0, 12, 13, 75, 64, 64, 67, 4, 73, 13, 69, 1, 13, 14, 23, 17, 10, 79, 0, 70, 0, 2, 90, 65, 8, 4, 5, 7, 7, 18, 10, 3, 1, 3, 1, 5, 7, 85, 68, 1, 75, 4, 2, 13, 6, 0, 10, 6, 4, 0, 13, 1, 76, 3, 81, 20, 21, 15, 14, 17, 17, 10, 16, 19, 64, 9, 9, 5, 3, 68, 4, 65, 1, 3, 0, 3, 8, 64, 80, 64, 73, 66, 2, 82, 22, 29, 22, 21, 19, 18, 16, 16, 10, 2, 1, 65, 70, 72, 93, 68, 65, 79, 11, 7, 4, 4, 64, 69, 67, 68, 78, 71, 76, 92, 86, 95, 67, 22, 18, 11, 4, 7, 64, 69, 71, 78, 64, 35, 21, 15, 8, 13, 2, 66, 71, 77, 4, 39, 29, 24, 18, 17, 5, 64, 69, 76, 70, 40, 26, 12, 3, 8, 67, 76, 81, 5, 47, 36, 26, 19, 21, 5, 65, 70, 74, 62, 88, 84, 73, 85, 85, 78, 79, 77, 78, 78, 76, 76, 82, 78, 84, 80, 0, 79, 79, 74, 71, 68, 69, 70, 67, 66, 70, 73, 77, 9, 8, 14, 5, 0, 10, 8, 6, 5, 6, 5, 4, 66, 68, 72, 66, 7, 78, 6, 20, 4, 6, 10, 12, 16, 6, 8, 10, 16, 67, 70, 78, 33, 34, 40, 28, 26, 34, 34, 34, 38, 33, 39, 38, 28, 23, 6, 26, 36, 23, 17, 23, 20, 13, 13, 9, 6, 1, 68, 70, 76, 26, 25, 25, 20, 9, 12, 8, 64, 1, 66, 69, 79, 78, 87, 90, 71, 71, 88, 11, 12, 5, 69, 2, 1, 71, 65, 67, 73, 83, 83, 91, 101, 7, 66, 78, 73, 2, 1, 6, 8, 9, 17, 11, 15, 17, 29, 20, 19, 29, 28, 53, 37, 27, 17, 9, 64, 76, 88, 108, 6, 38, 31, 29, 22, 24, 12, 8, 8, 65, 78, 72, 0, 71, 7, 15, 68, 69, 5, 9, 4, 8, 17, 4, 2, 48, 32, 17, 2, 72, 88, 100, 109, 119 }, { 50, 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 66, 28, 33, 53, 14, 64, 64, 4, 14, 0, 66, 67, 74, 89, 8, 22, 104, 113, 116, 3, 75, 68, 4, 14, 0, 77, 70, 11, 6, 64, 72, 80, 0, 77, 78, 90, 5, 71, 79, 2, 74, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 70, 91, 97, 2, 4, 66, 18, 72, 91, 87, 6, 65, 30, 22, 65, 3, 78, 3, 76, 74, 85, 81, 81, 85, 89, 22, 0, 8, 80, 69, 79, 75, 92, 2, 68, 67, 77, 17, 68, 0, 76, 82, 69, 73, 66, 7, 6, 0, 12, 13, 75, 64, 64, 67, 4, 73, 13, 69, 1, 13, 13, 22, 16, 9, 78, 1, 70, 64, 2, 89, 65, 8, 3, 5, 7, 7, 19, 11, 3, 1, 3, 1, 4, 7, 86, 68, 1, 75, 3, 1, 12, 6, 0, 11, 6, 4, 64, 12, 0, 76, 3, 81, 20, 21, 15, 14, 17, 17, 10, 16, 19, 64, 9, 9, 5, 3, 68, 4, 65, 0, 3, 0, 3, 7, 64, 80, 65, 73, 67, 1, 82, 21, 27, 20, 19, 17, 16, 14, 14, 8, 0, 64, 67, 73, 74, 94, 68, 66, 79, 10, 6, 3, 3, 65, 71, 68, 69, 78, 71, 76, 92, 86, 95, 67, 22, 18, 11, 4, 7, 64, 69, 70, 77, 64, 35, 21, 15, 8, 13, 3, 65, 71, 75, 4, 39, 29, 24, 18, 17, 5, 64, 69, 75, 70, 40, 25, 11, 3, 8, 67, 76, 81, 5, 47, 35, 25, 18, 21, 5, 65, 70, 73, 62, 87, 83, 72, 83, 84, 77, 78, 75, 77, 76, 74, 74, 81, 77, 83, 80, 1, 78, 79, 74, 70, 68, 70, 70, 68, 67, 71, 74, 78, 8, 7, 14, 4, 0, 10, 8, 6, 4, 5, 5, 3, 67, 68, 72, 66, 7, 78, 5, 20, 3, 6, 9, 11, 16, 5, 7, 9, 15, 68, 71, 80, 32, 34, 40, 27, 24, 32, 32, 32, 36, 30, 36, 36, 25, 21, 4, 23, 32, 20, 13, 21, 17, 11, 12, 8, 6, 2, 67, 69, 74, 25, 23, 23, 19, 7, 10, 7, 66, 0, 68, 71, 81, 80, 88, 91, 71, 71, 89, 9, 10, 3, 70, 0, 64, 73, 66, 69, 75, 84, 84, 92, 101, 6, 66, 79, 73, 3, 2, 6, 9, 10, 18, 12, 16, 18, 30, 21, 20, 30, 29, 53, 35, 25, 14, 7, 67, 79, 91, 110, 6, 39, 32, 30, 23, 25, 12, 9, 9, 64, 77, 72, 1, 70, 8, 16, 68, 68, 5, 9, 4, 9, 18, 4, 2, 46, 30, 14, 64, 75, 92, 104, 113, 122 }, { 48, 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 67, 27, 32, 53, 14, 1, 64, 5, 15, 0, 67, 67, 75, 91, 7, 20, 106, 114, 117, 5, 74, 68, 5, 15, 0, 77, 69, 11, 5, 64, 72, 79, 64, 77, 78, 91, 5, 71, 79, 2, 75, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 69, 92, 97, 2, 3, 66, 18, 72, 91, 85, 7, 0, 31, 23, 64, 4, 77, 4, 75, 73, 83, 81, 81, 85, 88, 22, 0, 8, 79, 69, 78, 75, 91, 2, 69, 68, 78, 17, 68, 0, 76, 82, 69, 73, 66, 6, 6, 0, 12, 12, 75, 64, 64, 67, 3, 73, 12, 70, 1, 12, 11, 21, 15, 8, 78, 1, 70, 65, 1, 89, 66, 7, 3, 5, 7, 7, 20, 11, 3, 0, 3, 0, 3, 6, 87, 68, 1, 76, 3, 0, 12, 5, 64, 11, 6, 4, 66, 12, 64, 76, 3, 82, 19, 20, 15, 14, 17, 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 0, 2, 64, 2, 6, 65, 80, 65, 73, 68, 64, 82, 19, 25, 18, 17, 15, 13, 12, 12, 6, 65, 66, 69, 75, 76, 95, 68, 66, 80, 9, 4, 1, 1, 67, 72, 70, 71, 79, 72, 77, 92, 86, 94, 67, 22, 18, 11, 4, 7, 64, 69, 70, 76, 0, 35, 21, 15, 8, 13, 3, 65, 70, 74, 4, 39, 29, 24, 17, 17, 5, 64, 69, 75, 70, 40, 25, 11, 3, 8, 67, 76, 80, 5, 46, 34, 24, 17, 20, 5, 65, 70, 73, 62, 86, 82, 72, 82, 83, 77, 77, 74, 76, 75, 73, 73, 81, 77, 83, 79, 2, 78, 80, 74, 70, 68, 70, 71, 68, 68, 72, 76, 79, 7, 7, 14, 4, 64, 9, 7, 5, 3, 4, 4, 3, 68, 69, 73, 66, 7, 79, 4, 19, 2, 6, 8, 10, 16, 4, 6, 9, 15, 69, 72, 82, 31, 33, 39, 25, 23, 31, 30, 30, 33, 28, 33, 33, 22, 18, 3, 20, 29, 17, 9, 18, 15, 9, 10, 6, 6, 2, 67, 68, 72, 23, 21, 21, 17, 4, 8, 5, 68, 65, 70, 72, 83, 81, 89, 92, 72, 72, 90, 8, 9, 1, 72, 65, 66, 74, 68, 71, 76, 85, 86, 94, 102, 5, 67, 80, 73, 3, 2, 7, 9, 10, 19, 12, 17, 19, 31, 22, 21, 31, 30, 52, 33, 23, 12, 4, 70, 82, 94, 113, 6, 39, 32, 30, 23, 25, 12, 9, 9, 64, 77, 72, 1, 70, 9, 17, 68, 68, 6, 10, 4, 9, 18, 4, 1, 45, 28, 12, 67, 78, 96, 108, 116, 125 }, { 47, 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 68, 26, 31, 53, 14, 3, 64, 6, 16, 0, 67, 66, 76, 92, 6, 18, 107, 115, 118, 8, 72, 68, 6, 16, 0, 76, 68, 12, 4, 64, 71, 78, 64, 77, 78, 91, 5, 71, 78, 2, 75, 76, 88, 7, 66, 67, 72, 2, 4, 22, 0, 0, 0, 68, 92, 97, 2, 2, 66, 18, 71, 91, 83, 9, 2, 32, 25, 1, 5, 75, 5, 73, 72, 81, 81, 81, 85, 87, 22, 0, 8, 78, 69, 77, 74, 90, 2, 69, 68, 78, 17, 68, 1, 75, 81, 68, 73, 66, 6, 6, 0, 12, 12, 74, 64, 0, 67, 3, 72, 11, 70, 1, 11, 10, 21, 14, 7, 77, 1, 69, 66, 0, 89, 67, 7, 3, 6, 7, 7, 21, 11, 3, 0, 4, 0, 3, 6, 88, 68, 1, 77, 3, 64, 12, 5, 65, 11, 6, 4, 67, 12, 64, 76, 3, 83, 18, 20, 15, 14, 17, 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 0, 2, 64, 1, 6, 65, 80, 65, 73, 69, 65, 82, 17, 24, 17, 16, 13, 11, 11, 11, 4, 67, 67, 70, 77, 77, 96, 68, 66, 81, 8, 3, 0, 0, 68, 73, 71, 72, 80, 73, 78, 92, 85, 93, 66, 23, 18, 11, 4, 8, 0, 68, 69, 75, 1, 35, 21, 15, 8, 14, 3, 64, 69, 72, 4, 39, 29, 24, 17, 18, 5, 64, 69, 74, 70, 41, 25, 11, 3, 9, 67, 76, 79, 5, 46, 34, 24, 16, 20, 5, 65, 69, 72, 62, 85, 81, 71, 81, 81, 76, 75, 73, 74, 74, 72, 71, 80, 76, 83, 78, 4, 78, 81, 73, 69, 68, 70, 72, 68, 68, 73, 77, 80, 7, 7, 14, 4, 64, 8, 7, 5, 2, 4, 4, 3, 69, 69, 73, 65, 8, 79, 4, 18, 2, 6, 8, 10, 16, 3, 5, 9, 15, 70, 72, 83, 31, 32, 38, 24, 22, 30, 28, 28, 31, 26, 31, 30, 20, 16, 2, 17, 26, 14, 5, 15, 13, 8, 9, 5, 6, 3, 67, 67, 70, 21, 20, 19, 15, 2, 7, 4, 70, 66, 71, 73, 84, 82, 90, 92, 72, 72, 91, 7, 8, 0, 74, 66, 67, 75, 69, 72, 77, 86, 87, 95, 103, 5, 68, 80, 72, 3, 2, 8, 10, 11, 20, 13, 18, 20, 33, 23, 22, 33, 32, 51, 32, 21, 10, 1, 73, 85, 97, 115, 7, 39, 32, 31, 23, 26, 13, 9, 10, 0, 77, 71, 2, 70, 10, 19, 67, 67, 7, 11, 4, 10, 19, 4, 1, 44, 26, 10, 69, 81, 99, 112, 119, 126 }, { 46, 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 70, 24, 29, 53, 14, 6, 65, 7, 16, 0, 68, 66, 77, 93, 6, 16, 109, 116, 118, 11, 71, 68, 7, 16, 0, 76, 67, 12, 4, 64, 71, 78, 64, 78, 77, 91, 5, 71, 77, 1, 75, 76, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 18, 71, 91, 82, 11, 3, 34, 27, 2, 6, 74, 7, 72, 71, 79, 80, 80, 84, 86, 22, 0, 8, 78, 69, 77, 74, 88, 1, 69, 68, 78, 17, 68, 1, 75, 81, 68, 73, 65, 6, 5, 0, 12, 12, 74, 64, 0, 67, 3, 72, 11, 70, 1, 11, 9, 20, 13, 6, 76, 2, 69, 67, 0, 88, 67, 7, 2, 6, 7, 7, 22, 12, 3, 0, 4, 0, 2, 6, 89, 68, 1, 77, 2, 65, 11, 5, 65, 12, 6, 4, 68, 11, 65, 76, 3, 83, 18, 20, 15, 14, 17, 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 64, 2, 64, 1, 5, 66, 80, 66, 73, 70, 66, 82, 16, 22, 15, 14, 11, 9, 9, 9, 2, 69, 69, 72, 80, 79, 97, 68, 67, 81, 7, 2, 64, 64, 70, 75, 72, 73, 80, 73, 78, 92, 85, 93, 66, 23, 18, 11, 4, 8, 0, 68, 69, 74, 1, 35, 21, 15, 8, 14, 4, 0, 69, 70, 4, 39, 29, 24, 17, 18, 5, 64, 69, 73, 70, 41, 24, 10, 3, 9, 67, 76, 79, 5, 45, 33, 23, 15, 20, 5, 65, 69, 72, 62, 84, 80, 70, 79, 80, 75, 74, 71, 73, 72, 70, 69, 79, 75, 82, 78, 5, 77, 81, 73, 68, 68, 71, 72, 69, 69, 74, 78, 81, 6, 6, 14, 3, 65, 8, 7, 5, 1, 3, 4, 2, 70, 69, 73, 65, 8, 79, 3, 18, 1, 6, 7, 9, 16, 2, 4, 8, 14, 71, 73, 85, 30, 32, 38, 23, 20, 28, 26, 26, 29, 23, 28, 28, 17, 14, 0, 14, 22, 11, 1, 13, 10, 6, 8, 4, 6, 4, 66, 66, 68, 20, 18, 17, 14, 0, 5, 2, 72, 67, 73, 75, 86, 84, 91, 93, 73, 72, 92, 5, 6, 65, 75, 68, 69, 77, 71, 74, 79, 87, 88, 96, 103, 4, 68, 81, 72, 4, 3, 8, 10, 12, 21, 14, 19, 21, 34, 24, 23, 34, 33, 51, 30, 19, 7, 64, 76, 88, 100, 117, 7, 40, 33, 31, 24, 27, 13, 10, 10, 0, 76, 71, 3, 69, 11, 20, 67, 66, 7, 11, 4, 10, 20, 4, 1, 42, 24, 7, 72, 84, 103, 116, 123, 126 }, { 45, 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 71, 23, 28, 53, 14, 8, 65, 7, 17, 0, 69, 66, 78, 94, 5, 15, 110, 117, 119, 14, 70, 68, 7, 17, 0, 75, 66, 13, 3, 0, 70, 77, 65, 78, 77, 91, 5, 70, 77, 1, 75, 75, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 18, 71, 90, 80, 13, 5, 35, 29, 3, 7, 72, 8, 71, 71, 78, 80, 80, 84, 86, 23, 0, 8, 77, 68, 76, 73, 87, 1, 70, 69, 79, 17, 68, 1, 74, 81, 67, 73, 65, 6, 5, 1, 12, 11, 74, 64, 0, 67, 3, 72, 10, 70, 1, 10, 8, 19, 13, 6, 76, 2, 69, 67, 64, 88, 68, 7, 2, 6, 7, 7, 22, 12, 3, 0, 5, 0, 1, 6, 89, 69, 1, 78, 2, 66, 11, 5, 66, 12, 6, 4, 70, 11, 66, 76, 3, 84, 17, 20, 14, 14, 17, 16, 9, 16, 18, 65, 7, 9, 4, 2, 69, 2, 67, 64, 1, 65, 0, 4, 66, 80, 66, 72, 71, 68, 82, 14, 20, 13, 12, 9, 7, 7, 7, 0, 71, 70, 73, 82, 81, 98, 68, 67, 82, 6, 1, 65, 66, 71, 76, 73, 74, 81, 74, 79, 92, 85, 92, 65, 23, 18, 11, 5, 8, 0, 68, 68, 73, 2, 34, 21, 15, 8, 15, 4, 1, 68, 68, 4, 40, 29, 24, 17, 19, 5, 64, 68, 73, 70, 41, 24, 10, 3, 9, 66, 75, 78, 5, 45, 33, 22, 15, 20, 5, 64, 68, 71, 62, 84, 79, 70, 78, 79, 74, 72, 70, 72, 71, 69, 68, 79, 74, 82, 77, 7, 77, 82, 73, 68, 68, 71, 73, 69, 70, 75, 79, 82, 6, 6, 14, 3, 65, 7, 6, 5, 1, 3, 3, 2, 70, 69, 74, 65, 8, 80, 3, 17, 0, 5, 6, 9, 17, 2, 3, 8, 14, 71, 73, 86, 29, 31, 37, 22, 19, 27, 24, 24, 27, 21, 26, 25, 14, 11, 64, 12, 19, 8, 66, 10, 8, 4, 7, 3, 6, 5, 66, 64, 65, 18, 16, 15, 12, 65, 3, 1, 74, 69, 74, 76, 87, 85, 92, 94, 73, 73, 93, 4, 5, 66, 77, 70, 71, 78, 72, 76, 80, 89, 90, 98, 104, 3, 69, 82, 72, 4, 3, 9, 11, 12, 23, 15, 20, 22, 35, 26, 24, 36, 34, 50, 29, 17, 5, 67, 78, 91, 102, 119, 8, 40, 33, 32, 24, 28, 14, 10, 11, 1, 76, 70, 3, 69, 12, 21, 67, 66, 8, 12, 4, 11, 21, 4, 1, 41, 22, 5, 75, 88, 107, 119, 126, 126 }, { 43, 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 73, 21, 27, 53, 14, 10, 65, 8, 18, 0, 70, 66, 79, 95, 5, 13, 112, 118, 119, 17, 69, 68, 8, 18, 0, 75, 65, 13, 3, 0, 70, 76, 65, 79, 77, 91, 5, 70, 76, 1, 76, 75, 88, 9, 65, 67, 71, 4, 4, 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 18, 71, 90, 78, 14, 6, 37, 30, 4, 8, 71, 9, 70, 70, 76, 79, 80, 83, 85, 23, 0, 8, 77, 68, 76, 73, 85, 1, 70, 69, 79, 17, 68, 1, 74, 81, 67, 73, 64, 6, 5, 1, 12, 11, 74, 64, 0, 67, 3, 72, 9, 71, 1, 9, 7, 18, 12, 5, 75, 3, 69, 68, 64, 87, 68, 7, 2, 6, 7, 7, 23, 12, 3, 64, 5, 64, 0, 5, 90, 69, 1, 78, 1, 67, 11, 4, 67, 12, 6, 4, 71, 11, 67, 76, 3, 84, 16, 20, 14, 14, 17, 16, 9, 16, 17, 65, 7, 9, 4, 2, 70, 2, 67, 65, 1, 65, 64, 3, 67, 80, 67, 72, 72, 69, 82, 13, 18, 11, 10, 7, 5, 5, 5, 65, 73, 72, 75, 84, 83, 99, 68, 67, 82, 5, 0, 66, 67, 73, 78, 74, 76, 82, 74, 79, 92, 85, 92, 65, 23, 18, 11, 5, 8, 0, 68, 68, 72, 2, 34, 21, 15, 8, 15, 5, 1, 67, 66, 4, 40, 29, 24, 17, 19, 5, 64, 68, 72, 70, 41, 24, 9, 3, 9, 66, 75, 77, 5, 44, 32, 21, 14, 20, 5, 64, 68, 71, 62, 83, 78, 69, 77, 78, 73, 71, 69, 71, 70, 67, 66, 78, 74, 82, 77, 8, 77, 82, 73, 67, 68, 72, 73, 69, 71, 76, 80, 83, 5, 5, 14, 2, 66, 7, 6, 5, 0, 2, 3, 2, 71, 70, 74, 65, 8, 80, 2, 17, 64, 5, 5, 8, 17, 1, 2, 7, 13, 72, 74, 88, 28, 30, 37, 21, 18, 25, 22, 22, 25, 18, 23, 23, 11, 9, 65, 9, 16, 5, 70, 7, 5, 2, 6, 1, 6, 6, 66, 0, 0, 16, 14, 13, 10, 67, 1, 64, 76, 70, 76, 77, 89, 87, 93, 95, 74, 73, 94, 3, 3, 68, 79, 72, 73, 80, 74, 78, 82, 90, 91, 99, 105, 2, 70, 83, 72, 5, 3, 9, 11, 13, 24, 15, 21, 23, 36, 27, 25, 37, 35, 50, 27, 15, 2, 69, 81, 94, 105, 122, 8, 41, 34, 32, 24, 28, 14, 10, 11, 1, 76, 70, 4, 68, 13, 22, 67, 65, 8, 12, 4, 11, 22, 4, 1, 40, 20, 2, 78, 91, 111, 123, 126, 126 }, { 42, 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 74, 20, 25, 53, 14, 13, 66, 9, 18, 0, 70, 65, 80, 96, 4, 11, 114, 119, 120, 20, 67, 68, 9, 18, 0, 75, 64, 13, 2, 0, 70, 76, 65, 79, 76, 91, 5, 70, 75, 0, 76, 75, 88, 9, 64, 66, 70, 4, 4, 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 18, 70, 90, 77, 16, 8, 38, 32, 6, 9, 70, 11, 69, 69, 74, 79, 79, 83, 84, 23, 0, 8, 76, 68, 75, 72, 84, 0, 70, 69, 79, 17, 68, 2, 73, 81, 67, 73, 64, 6, 4, 1, 12, 11, 73, 64, 1, 67, 3, 72, 9, 71, 1, 9, 6, 18, 11, 4, 74, 3, 69, 69, 65, 87, 69, 7, 1, 6, 7, 7, 24, 13, 3, 64, 5, 64, 64, 5, 91, 69, 1, 79, 1, 68, 10, 4, 67, 13, 6, 4, 72, 10, 68, 76, 3, 85, 16, 20, 14, 14, 17, 16, 9, 16, 17, 65, 7, 9, 4, 2, 70, 2, 67, 65, 1, 65, 64, 2, 67, 80, 67, 72, 73, 70, 82, 11, 16, 10, 9, 5, 3, 3, 3, 67, 75, 74, 77, 87, 84, 100, 68, 68, 83, 4, 64, 67, 68, 74, 79, 75, 77, 82, 75, 80, 92, 85, 91, 65, 23, 18, 11, 5, 8, 1, 67, 67, 71, 3, 34, 21, 15, 8, 15, 5, 2, 67, 64, 4, 40, 29, 24, 17, 19, 5, 64, 68, 71, 70, 42, 23, 9, 3, 9, 66, 75, 77, 5, 44, 31, 20, 13, 20, 5, 64, 68, 70, 62, 82, 77, 68, 75, 77, 72, 70, 67, 70, 68, 66, 64, 77, 73, 81, 76, 9, 76, 83, 72, 66, 68, 72, 74, 70, 71, 77, 81, 84, 4, 5, 14, 2, 66, 6, 6, 5, 64, 1, 3, 1, 72, 70, 74, 65, 8, 80, 1, 16, 64, 5, 4, 7, 17, 0, 1, 7, 13, 73, 75, 90, 27, 30, 36, 20, 16, 24, 20, 20, 23, 16, 20, 20, 8, 7, 67, 6, 12, 2, 74, 5, 3, 0, 5, 0, 6, 7, 65, 1, 2, 15, 13, 11, 9, 69, 64, 65, 78, 71, 78, 79, 91, 88, 94, 96, 74, 73, 95, 1, 2, 70, 80, 73, 74, 81, 75, 79, 83, 91, 92, 100, 105, 2, 70, 84, 71, 5, 4, 10, 12, 14, 25, 16, 22, 24, 38, 28, 26, 38, 37, 49, 25, 13, 0, 72, 84, 97, 108, 124, 8, 41, 34, 33, 25, 29, 14, 11, 12, 2, 75, 70, 5, 68, 14, 23, 66, 64, 9, 13, 4, 12, 23, 4, 1, 38, 18, 0, 81, 94, 114, 126, 126, 126 }, { 41, 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 76, 18, 24, 53, 14, 15, 66, 10, 19, 0, 71, 65, 81, 97, 4, 9, 115, 120, 120, 23, 66, 68, 10, 19, 0, 74, 0, 14, 2, 0, 69, 75, 66, 80, 76, 91, 5, 70, 75, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 18, 70, 90, 75, 18, 9, 40, 34, 7, 10, 68, 12, 68, 68, 72, 78, 79, 82, 83, 23, 0, 8, 76, 68, 75, 72, 82, 0, 71, 70, 80, 17, 68, 2, 73, 81, 66, 73, 0, 6, 4, 1, 12, 10, 73, 64, 1, 67, 3, 72, 8, 71, 1, 8, 5, 17, 10, 3, 74, 4, 69, 70, 65, 86, 69, 7, 1, 6, 7, 7, 25, 13, 3, 64, 6, 64, 65, 5, 92, 69, 1, 79, 0, 69, 10, 4, 68, 13, 6, 4, 74, 10, 69, 76, 3, 85, 15, 20, 14, 14, 17, 16, 9, 16, 17, 65, 6, 9, 4, 2, 70, 1, 68, 66, 0, 66, 65, 1, 68, 80, 68, 72, 74, 72, 82, 10, 14, 8, 7, 3, 1, 1, 1, 69, 77, 75, 78, 89, 86, 101, 68, 68, 83, 3, 65, 68, 70, 76, 81, 76, 78, 83, 75, 80, 92, 85, 91, 64, 23, 18, 11, 5, 8, 1, 67, 67, 70, 3, 34, 21, 15, 8, 16, 6, 3, 66, 1, 4, 40, 29, 24, 17, 20, 5, 64, 68, 71, 70, 42, 23, 8, 3, 9, 66, 75, 76, 5, 43, 31, 19, 12, 20, 5, 64, 67, 70, 62, 81, 76, 68, 74, 76, 71, 68, 66, 69, 67, 64, 0, 77, 72, 81, 76, 11, 76, 83, 72, 66, 68, 73, 74, 70, 72, 78, 82, 85, 4, 4, 14, 1, 67, 6, 5, 5, 65, 1, 2, 1, 73, 70, 75, 65, 8, 81, 1, 16, 65, 5, 3, 7, 17, 64, 0, 6, 12, 74, 75, 91, 26, 29, 36, 19, 15, 22, 18, 18, 21, 13, 18, 18, 5, 4, 68, 3, 9, 64, 78, 2, 0, 65, 4, 64, 6, 8, 65, 2, 4, 13, 11, 9, 7, 71, 66, 67, 80, 73, 79, 80, 92, 90, 95, 97, 75, 74, 96, 0, 0, 71, 82, 75, 76, 83, 77, 81, 85, 92, 94, 102, 106, 1, 71, 85, 71, 6, 4, 10, 12, 14, 26, 17, 23, 25, 39, 29, 27, 40, 38, 49, 24, 11, 66, 74, 87, 100, 111, 126, 9, 42, 35, 33, 25, 30, 15, 11, 12, 2, 75, 69, 5, 67, 15, 24, 66, 64, 9, 13, 4, 12, 24, 4, 1, 37, 16, 66, 84, 97, 118, 126, 126, 126 }, { 40, 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 77, 17, 23, 53, 14, 17, 66, 11, 20, 0, 72, 65, 82, 98, 3, 7, 117, 121, 121, 26, 65, 68, 11, 20, 0, 74, 1, 14, 1, 0, 69, 74, 66, 80, 76, 91, 5, 70, 74, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 18, 70, 90, 73, 20, 11, 41, 36, 8, 11, 67, 13, 67, 67, 70, 78, 79, 82, 82, 23, 0, 8, 75, 68, 74, 71, 81, 0, 71, 70, 80, 17, 68, 2, 72, 81, 66, 73, 0, 6, 4, 1, 12, 10, 73, 64, 1, 67, 3, 72, 7, 71, 1, 7, 4, 16, 9, 2, 73, 4, 69, 71, 66, 86, 70, 7, 1, 6, 7, 7, 26, 13, 3, 64, 6, 64, 66, 5, 93, 69, 1, 80, 0, 70, 10, 4, 69, 13, 6, 4, 75, 10, 70, 76, 3, 86, 14, 20, 14, 14, 17, 16, 9, 16, 17, 65, 6, 9, 4, 2, 70, 1, 68, 66, 0, 66, 66, 0, 68, 80, 68, 72, 75, 73, 82, 8, 12, 6, 5, 1, 64, 64, 64, 71, 79, 77, 80, 91, 88, 102, 68, 68, 84, 2, 66, 69, 71, 77, 82, 77, 79, 84, 76, 81, 92, 85, 90, 64, 23, 18, 11, 5, 8, 1, 67, 66, 69, 4, 34, 21, 15, 8, 16, 6, 4, 65, 3, 4, 40, 29, 24, 17, 20, 5, 64, 68, 70, 70, 42, 23, 8, 3, 9, 66, 75, 75, 5, 43, 30, 18, 11, 20, 5, 64, 67, 69, 62, 80, 75, 67, 73, 75, 70, 67, 65, 68, 66, 0, 2, 76, 71, 81, 75, 12, 76, 84, 72, 65, 68, 73, 75, 70, 73, 79, 83, 86, 3, 4, 14, 1, 67, 5, 5, 5, 66, 0, 2, 1, 74, 70, 75, 65, 8, 81, 0, 15, 66, 5, 2, 6, 17, 65, 64, 6, 12, 75, 76, 93, 25, 28, 35, 18, 14, 21, 16, 16, 19, 11, 15, 15, 2, 2, 69, 0, 6, 67, 82, 64, 65, 67, 3, 65, 6, 9, 65, 3, 6, 11, 9, 7, 5, 73, 68, 68, 82, 74, 81, 81, 94, 91, 96, 98, 75, 74, 97, 64, 64, 73, 84, 77, 78, 84, 78, 83, 86, 93, 95, 103, 107, 0, 72, 86, 71, 6, 4, 11, 13, 15, 27, 18, 24, 26, 40, 30, 28, 41, 39, 48, 22, 9, 68, 77, 90, 103, 114, 126, 9, 42, 35, 34, 25, 31, 15, 11, 13, 3, 75, 69, 6, 67, 16, 25, 66, 0, 10, 14, 4, 13, 25, 4, 1, 36, 14, 68, 87, 100, 122, 126, 126, 126 }, { 38, 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 79, 15, 21, 52, 14, 19, 67, 11, 20, 64, 73, 65, 84, 100, 2, 5, 119, 122, 122, 28, 64, 69, 11, 20, 64, 74, 2, 14, 0, 0, 69, 74, 67, 81, 76, 92, 5, 70, 74, 64, 77, 75, 88, 10, 64, 66, 70, 5, 3, 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 18, 70, 90, 72, 21, 12, 42, 37, 9, 12, 66, 14, 66, 67, 69, 78, 79, 82, 82, 23, 0, 8, 75, 68, 74, 71, 80, 64, 72, 71, 81, 17, 68, 2, 72, 81, 66, 73, 0, 5, 3, 1, 11, 9, 73, 65, 1, 67, 2, 72, 6, 72, 0, 6, 2, 15, 8, 1, 73, 4, 69, 72, 67, 86, 71, 6, 0, 6, 7, 7, 26, 13, 3, 65, 6, 65, 67, 4, 94, 70, 0, 81, 64, 71, 9, 3, 70, 13, 6, 4, 77, 9, 71, 76, 3, 87, 13, 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, 71, 0, 69, 67, 64, 67, 67, 64, 69, 80, 69, 72, 76, 75, 82, 6, 10, 4, 3, 64, 67, 66, 66, 73, 81, 79, 82, 94, 90, 104, 69, 69, 85, 1, 68, 71, 73, 79, 84, 79, 81, 85, 77, 82, 92, 85, 90, 64, 23, 18, 11, 5, 8, 1, 67, 66, 68, 4, 33, 21, 15, 8, 16, 6, 4, 65, 4, 3, 40, 29, 23, 16, 20, 5, 64, 68, 70, 70, 42, 22, 7, 2, 9, 66, 75, 75, 5, 42, 29, 17, 10, 19, 5, 64, 67, 69, 62, 80, 74, 67, 72, 74, 70, 66, 64, 67, 65, 1, 3, 76, 71, 81, 75, 13, 76, 85, 72, 65, 68, 74, 76, 71, 74, 80, 85, 88, 2, 3, 14, 0, 68, 4, 4, 4, 67, 64, 1, 0, 75, 71, 76, 65, 8, 82, 64, 14, 67, 4, 1, 5, 17, 66, 66, 5, 11, 76, 77, 95, 24, 27, 34, 16, 12, 19, 14, 14, 16, 8, 12, 12, 64, 64, 71, 66, 2, 70, 87, 67, 68, 69, 1, 67, 6, 9, 65, 4, 8, 9, 7, 5, 3, 76, 70, 70, 85, 76, 83, 83, 96, 93, 97, 99, 76, 75, 99, 66, 66, 75, 86, 79, 80, 86, 80, 85, 88, 95, 97, 105, 108, 64, 73, 87, 71, 6, 4, 11, 13, 15, 28, 18, 25, 26, 41, 31, 29, 42, 40, 47, 20, 6, 71, 80, 93, 107, 117, 126, 9, 42, 35, 34, 25, 31, 15, 11, 13, 3, 75, 69, 6, 67, 17, 26, 66, 0, 10, 14, 4, 13, 25, 4, 0, 34, 11, 71, 90, 104, 126, 126, 126, 126 }, { 37, 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 80, 14, 20, 52, 14, 22, 67, 12, 21, 64, 73, 64, 85, 101, 2, 4, 120, 123, 122, 31, 1, 69, 12, 21, 64, 73, 4, 15, 0, 1, 68, 73, 67, 81, 75, 92, 5, 69, 73, 64, 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 18, 69, 89, 70, 23, 14, 44, 39, 11, 13, 64, 16, 64, 66, 67, 77, 78, 81, 81, 24, 1, 9, 74, 67, 73, 70, 78, 64, 72, 71, 81, 18, 68, 3, 71, 80, 65, 72, 1, 5, 3, 2, 11, 9, 72, 65, 2, 67, 2, 71, 6, 72, 0, 6, 1, 15, 8, 1, 72, 5, 68, 72, 67, 85, 71, 6, 0, 7, 7, 7, 27, 14, 4, 65, 7, 65, 67, 4, 94, 70, 0, 81, 64, 72, 9, 3, 70, 14, 7, 4, 78, 9, 71, 75, 3, 87, 13, 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, 71, 0, 69, 67, 64, 67, 67, 64, 69, 79, 69, 71, 76, 76, 81, 5, 9, 3, 2, 66, 69, 67, 67, 75, 82, 80, 83, 96, 91, 105, 69, 69, 85, 0, 69, 72, 74, 80, 85, 80, 82, 85, 77, 82, 91, 84, 89, 0, 24, 18, 11, 6, 9, 2, 66, 65, 66, 5, 33, 21, 15, 8, 17, 7, 5, 64, 6, 3, 41, 30, 23, 16, 21, 5, 64, 67, 69, 70, 43, 22, 7, 2, 10, 65, 74, 74, 5, 42, 29, 17, 10, 19, 5, 0, 66, 68, 62, 79, 73, 66, 70, 72, 69, 64, 1, 65, 0, 3, 5, 75, 70, 80, 74, 15, 75, 85, 71, 64, 67, 74, 76, 71, 74, 80, 86, 89, 2, 3, 15, 0, 68, 4, 4, 4, 67, 64, 1, 0, 75, 71, 76, 64, 9, 82, 64, 14, 67, 4, 1, 5, 18, 66, 67, 5, 11, 76, 77, 96, 24, 27, 34, 15, 11, 18, 12, 12, 14, 6, 10, 10, 66, 66, 72, 68, 64, 73, 91, 69, 70, 70, 0, 68, 6, 10, 64, 6, 11, 8, 6, 4, 2, 78, 71, 71, 87, 77, 84, 84, 97, 94, 98, 99, 76, 75, 100, 67, 67, 76, 87, 80, 81, 87, 81, 86, 89, 96, 98, 106, 108, 64, 73, 87, 70, 7, 5, 12, 14, 16, 30, 19, 26, 27, 43, 33, 30, 44, 42, 47, 19, 4, 73, 82, 95, 110, 119, 126, 10, 43, 36, 35, 26, 32, 16, 12, 14, 4, 74, 68, 7, 66, 19, 28, 65, 1, 11, 15, 5, 14, 26, 4, 0, 33, 9, 73, 92, 107, 126, 126, 126, 126 }, { 36, 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 81, 13, 19, 52, 14, 24, 67, 13, 22, 64, 74, 64, 86, 102, 1, 2, 122, 124, 123, 34, 2, 69, 13, 22, 64, 73, 5, 15, 64, 1, 68, 72, 67, 81, 75, 92, 5, 69, 72, 64, 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 18, 69, 89, 68, 25, 16, 45, 41, 12, 14, 0, 17, 0, 65, 65, 77, 78, 81, 80, 24, 1, 9, 73, 67, 72, 70, 77, 64, 72, 71, 81, 18, 68, 3, 71, 80, 65, 72, 1, 5, 3, 2, 11, 9, 72, 65, 2, 67, 2, 71, 5, 72, 0, 5, 0, 14, 7, 0, 71, 5, 68, 73, 68, 85, 72, 6, 0, 7, 7, 7, 28, 14, 4, 65, 7, 65, 68, 4, 95, 70, 0, 82, 64, 73, 9, 3, 71, 14, 7, 4, 79, 9, 72, 75, 3, 88, 12, 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, 71, 0, 69, 67, 64, 67, 68, 65, 70, 79, 69, 71, 77, 77, 81, 3, 7, 1, 0, 68, 71, 69, 69, 77, 84, 82, 85, 98, 93, 106, 69, 69, 86, 64, 70, 73, 75, 82, 86, 81, 83, 86, 78, 83, 91, 84, 88, 0, 24, 18, 11, 6, 9, 2, 66, 65, 65, 6, 33, 21, 15, 8, 17, 7, 6, 0, 8, 3, 41, 30, 23, 16, 21, 5, 64, 67, 68, 70, 43, 22, 7, 2, 10, 65, 74, 73, 5, 41, 28, 16, 9, 19, 5, 0, 66, 68, 62, 78, 72, 65, 69, 71, 68, 0, 2, 64, 1, 4, 7, 74, 69, 80, 73, 16, 75, 86, 71, 0, 67, 74, 77, 71, 75, 81, 87, 90, 1, 3, 15, 0, 69, 3, 4, 4, 68, 65, 1, 0, 76, 71, 76, 64, 9, 82, 65, 13, 68, 4, 0, 4, 18, 67, 68, 5, 11, 77, 78, 98, 23, 26, 33, 14, 10, 17, 10, 10, 12, 4, 7, 7, 69, 68, 73, 71, 67, 76, 95, 72, 72, 72, 64, 69, 6, 11, 64, 7, 13, 6, 4, 2, 0, 80, 73, 73, 89, 78, 86, 85, 99, 95, 99, 100, 77, 75, 101, 68, 68, 78, 89, 82, 83, 88, 83, 88, 90, 97, 99, 107, 109, 65, 74, 88, 70, 7, 5, 13, 14, 17, 31, 20, 27, 28, 44, 34, 31, 45, 43, 46, 17, 2, 75, 85, 98, 113, 122, 126, 10, 43, 36, 35, 26, 33, 16, 12, 14, 4, 74, 68, 8, 66, 20, 29, 65, 2, 12, 16, 5, 14, 27, 4, 0, 32, 7, 75, 95, 110, 126, 126, 126, 126 }, { 35, 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 83, 11, 18, 52, 14, 26, 67, 14, 23, 64, 75, 64, 87, 103, 1, 0, 123, 125, 123, 37, 3, 69, 14, 23, 64, 72, 6, 16, 64, 1, 67, 71, 68, 82, 75, 92, 5, 69, 72, 64, 77, 74, 88, 12, 0, 65, 69, 7, 3, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 18, 69, 89, 66, 27, 17, 47, 43, 13, 15, 2, 18, 1, 64, 0, 76, 78, 80, 79, 24, 1, 9, 73, 67, 72, 69, 75, 64, 73, 72, 82, 18, 68, 3, 70, 80, 64, 72, 2, 5, 3, 2, 11, 8, 72, 65, 2, 67, 2, 71, 4, 72, 0, 4, 64, 13, 6, 64, 71, 6, 68, 74, 68, 84, 72, 6, 0, 7, 7, 7, 29, 14, 4, 65, 8, 65, 69, 4, 96, 70, 0, 82, 65, 74, 9, 3, 72, 14, 7, 4, 81, 9, 73, 75, 3, 88, 11, 19, 13, 14, 17, 15, 8, 16, 16, 66, 4, 9, 3, 1, 71, 64, 70, 68, 65, 68, 69, 66, 70, 79, 70, 71, 78, 79, 81, 2, 5, 64, 65, 70, 73, 71, 71, 79, 86, 83, 86, 100, 95, 107, 69, 69, 86, 65, 71, 74, 77, 83, 88, 82, 84, 87, 78, 83, 91, 84, 88, 1, 24, 18, 11, 6, 9, 2, 66, 64, 64, 6, 33, 21, 15, 8, 18, 8, 7, 1, 10, 3, 41, 30, 23, 16, 22, 5, 64, 67, 68, 70, 43, 22, 6, 2, 10, 65, 74, 72, 5, 41, 28, 15, 8, 19, 5, 0, 65, 67, 62, 77, 71, 65, 68, 70, 67, 2, 3, 0, 2, 6, 8, 74, 68, 80, 73, 18, 75, 86, 71, 0, 67, 75, 77, 71, 76, 82, 88, 91, 1, 2, 15, 64, 69, 3, 3, 4, 69, 65, 0, 0, 77, 71, 77, 64, 9, 83, 65, 13, 69, 4, 64, 4, 18, 68, 69, 4, 10, 78, 78, 99, 22, 25, 33, 13, 9, 15, 8, 8, 10, 1, 5, 5, 72, 71, 74, 74, 70, 79, 99, 75, 75, 74, 65, 70, 6, 12, 64, 8, 15, 4, 2, 0, 65, 82, 75, 74, 91, 80, 87, 86, 100, 97, 100, 101, 77, 76, 102, 69, 70, 79, 91, 84, 85, 90, 84, 90, 92, 98, 101, 109, 110, 66, 75, 89, 70, 8, 5, 13, 15, 17, 32, 21, 28, 29, 45, 35, 32, 47, 44, 46, 16, 0, 78, 87, 101, 116, 125, 126, 11, 44, 37, 36, 26, 34, 17, 12, 15, 5, 74, 67, 8, 65, 21, 30, 65, 2, 12, 16, 5, 15, 28, 4, 0, 31, 5, 78, 98, 113, 126, 126, 126, 126 }, { 33, 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 84, 10, 16, 52, 14, 29, 68, 15, 23, 64, 76, 64, 88, 104, 0, 65, 125, 126, 124, 40, 4, 69, 15, 23, 64, 72, 7, 16, 65, 1, 67, 71, 68, 82, 74, 92, 5, 69, 71, 65, 78, 74, 88, 12, 1, 65, 68, 7, 3, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 18, 69, 89, 65, 28, 19, 48, 44, 14, 16, 3, 20, 2, 0, 2, 76, 77, 80, 78, 24, 1, 9, 72, 67, 71, 69, 74, 65, 73, 72, 82, 18, 68, 3, 70, 80, 64, 72, 2, 5, 2, 2, 11, 8, 72, 65, 2, 67, 2, 71, 4, 73, 0, 4, 65, 12, 5, 65, 70, 6, 68, 75, 69, 84, 73, 6, 64, 7, 7, 7, 30, 15, 4, 66, 8, 66, 70, 3, 97, 70, 0, 83, 65, 75, 8, 2, 72, 15, 7, 4, 82, 8, 74, 75, 3, 89, 11, 19, 13, 14, 17, 15, 8, 16, 15, 66, 4, 9, 3, 1, 72, 64, 70, 68, 65, 68, 69, 67, 71, 79, 70, 71, 79, 80, 81, 0, 3, 66, 67, 72, 75, 73, 73, 81, 88, 85, 88, 103, 97, 108, 69, 70, 87, 66, 72, 75, 78, 85, 89, 83, 86, 87, 79, 84, 91, 84, 87, 1, 24, 18, 11, 6, 9, 2, 66, 64, 0, 7, 33, 21, 15, 8, 18, 8, 7, 1, 12, 3, 41, 30, 23, 16, 22, 5, 64, 67, 67, 70, 43, 21, 6, 2, 10, 65, 74, 72, 5, 40, 27, 14, 7, 19, 5, 0, 65, 67, 62, 76, 70, 64, 66, 69, 66, 3, 5, 1, 4, 7, 10, 73, 68, 79, 72, 19, 74, 87, 71, 1, 67, 75, 78, 72, 77, 83, 89, 92, 0, 2, 15, 64, 70, 2, 3, 4, 70, 66, 0, 64, 78, 72, 77, 64, 9, 83, 66, 12, 70, 4, 65, 3, 18, 69, 70, 4, 10, 79, 79, 101, 21, 25, 32, 12, 7, 14, 6, 6, 8, 64, 2, 2, 75, 73, 76, 77, 74, 82, 103, 77, 77, 76, 66, 72, 6, 13, 0, 9, 17, 3, 0, 65, 66, 84, 77, 76, 93, 81, 89, 88, 102, 98, 101, 102, 78, 76, 103, 71, 71, 81, 92, 86, 87, 91, 86, 92, 93, 99, 102, 110, 110, 67, 75, 90, 70, 8, 6, 14, 15, 18, 33, 21, 29, 30, 46, 36, 33, 48, 45, 45, 14, 65, 80, 90, 104, 119, 126, 126, 11, 44, 37, 36, 27, 34, 17, 13, 15, 5, 73, 67, 9, 65, 22, 31, 65, 3, 13, 17, 5, 15, 29, 4, 0, 29, 3, 80, 101, 116, 126, 126, 126, 126 }, { 32, 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 86, 8, 15, 52, 14, 31, 68, 16, 24, 64, 76, 0, 89, 105, 0, 67, 126, 126, 124, 43, 6, 69, 16, 24, 64, 72, 8, 16, 65, 1, 67, 70, 68, 83, 74, 92, 5, 69, 70, 65, 78, 74, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 18, 68, 89, 0, 30, 20, 50, 46, 16, 17, 4, 21, 3, 1, 4, 75, 77, 79, 77, 24, 1, 9, 72, 67, 71, 68, 72, 65, 73, 72, 82, 18, 68, 4, 69, 80, 64, 72, 3, 5, 2, 2, 11, 8, 71, 65, 3, 67, 2, 71, 3, 73, 0, 3, 66, 12, 4, 66, 69, 7, 68, 76, 69, 83, 73, 6, 64, 7, 7, 7, 31, 15, 4, 66, 8, 66, 71, 3, 98, 70, 0, 83, 66, 76, 8, 2, 73, 15, 7, 4, 83, 8, 75, 75, 3, 89, 10, 19, 13, 14, 17, 15, 8, 16, 15, 66, 4, 9, 3, 1, 72, 64, 70, 69, 65, 68, 70, 68, 71, 79, 71, 71, 80, 81, 81, 64, 1, 67, 68, 74, 77, 75, 75, 83, 90, 87, 90, 105, 98, 109, 69, 70, 87, 67, 73, 76, 79, 86, 91, 84, 87, 88, 79, 84, 91, 84, 87, 1, 24, 18, 11, 6, 9, 3, 65, 0, 1, 7, 33, 21, 15, 8, 18, 9, 8, 2, 14, 3, 41, 30, 23, 16, 22, 5, 64, 67, 66, 70, 44, 21, 5, 2, 10, 65, 74, 71, 5, 40, 26, 13, 6, 19, 5, 0, 65, 66, 62, 75, 69, 0, 65, 68, 65, 4, 6, 2, 5, 9, 12, 72, 67, 79, 72, 20, 74, 87, 70, 2, 67, 76, 78, 72, 77, 84, 90, 93, 64, 1, 15, 65, 70, 2, 3, 4, 71, 67, 0, 64, 79, 72, 77, 64, 9, 83, 67, 12, 70, 4, 66, 2, 18, 70, 71, 3, 9, 80, 80, 103, 20, 24, 32, 11, 6, 12, 4, 4, 6, 67, 64, 0, 78, 75, 77, 80, 77, 85, 107, 80, 80, 78, 67, 73, 6, 14, 0, 10, 19, 1, 64, 67, 68, 86, 79, 77, 95, 82, 91, 89, 104, 100, 102, 103, 78, 76, 104, 72, 73, 83, 94, 87, 88, 93, 87, 93, 95, 100, 103, 111, 111, 67, 76, 91, 69, 9, 6, 14, 16, 19, 34, 22, 30, 31, 48, 37, 34, 49, 47, 45, 12, 67, 83, 92, 107, 122, 126, 126, 11, 45, 38, 37, 27, 35, 17, 13, 16, 6, 73, 67, 10, 64, 23, 32, 64, 4, 13, 17, 5, 16, 30, 4, 0, 28, 1, 83, 104, 119, 126, 126, 126, 126 }, { 31, 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 87, 7, 14, 52, 14, 33, 68, 16, 25, 64, 77, 0, 90, 106, 64, 68, 126, 126, 125, 46, 7, 69, 16, 25, 64, 71, 9, 17, 66, 2, 66, 69, 69, 83, 74, 92, 5, 68, 70, 65, 78, 73, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 18, 68, 88, 2, 32, 22, 51, 48, 17, 18, 6, 22, 4, 1, 5, 75, 77, 79, 77, 25, 1, 9, 71, 66, 70, 68, 71, 65, 74, 73, 83, 18, 68, 4, 69, 80, 0, 72, 3, 5, 2, 3, 11, 7, 71, 65, 3, 67, 2, 71, 2, 73, 0, 2, 67, 11, 4, 66, 69, 7, 68, 76, 70, 83, 74, 6, 64, 7, 7, 7, 31, 15, 4, 66, 9, 66, 72, 3, 98, 71, 0, 84, 66, 77, 8, 2, 74, 15, 7, 4, 85, 8, 76, 75, 3, 90, 9, 19, 12, 14, 17, 15, 8, 16, 15, 66, 3, 9, 2, 1, 72, 65, 71, 69, 66, 69, 71, 69, 72, 79, 71, 70, 81, 83, 81, 66, 64, 69, 70, 76, 79, 77, 77, 85, 92, 88, 91, 107, 100, 110, 69, 70, 88, 68, 74, 77, 81, 88, 92, 85, 88, 89, 80, 85, 91, 84, 86, 2, 24, 18, 11, 7, 9, 3, 65, 0, 2, 8, 32, 21, 15, 8, 19, 9, 9, 3, 16, 3, 42, 30, 23, 16, 23, 5, 64, 66, 66, 70, 44, 21, 5, 2, 10, 64, 73, 70, 5, 39, 26, 12, 6, 19, 5, 1, 64, 66, 62, 75, 68, 0, 64, 67, 64, 6, 7, 3, 6, 10, 13, 72, 66, 79, 71, 22, 74, 88, 70, 2, 67, 76, 79, 72, 78, 85, 91, 94, 64, 1, 15, 65, 71, 1, 2, 4, 71, 67, 64, 64, 79, 72, 78, 64, 9, 84, 67, 11, 71, 3, 67, 2, 19, 70, 72, 3, 9, 80, 80, 104, 19, 23, 31, 10, 5, 11, 2, 2, 4, 69, 66, 66, 81, 78, 78, 82, 80, 88, 111, 83, 82, 80, 68, 74, 6, 15, 0, 12, 22, 64, 66, 69, 70, 88, 81, 79, 97, 84, 92, 90, 105, 101, 103, 104, 79, 77, 105, 73, 74, 84, 96, 89, 90, 94, 89, 95, 96, 102, 105, 113, 112, 68, 77, 92, 69, 9, 6, 15, 16, 19, 36, 23, 31, 32, 49, 39, 35, 51, 48, 44, 11, 69, 85, 95, 109, 125, 126, 126, 12, 45, 38, 37, 27, 36, 18, 13, 16, 6, 73, 66, 10, 64, 24, 33, 64, 4, 14, 18, 5, 16, 31, 4, 0, 27, 64, 85, 107, 123, 126, 126, 126, 126 }, { 30, 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 89, 5, 12, 52, 14, 36, 69, 17, 25, 64, 78, 0, 91, 107, 64, 70, 126, 126, 125, 49, 8, 69, 17, 25, 64, 71, 10, 17, 66, 2, 66, 69, 69, 84, 73, 92, 5, 68, 69, 66, 78, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 18, 68, 88, 3, 34, 23, 53, 50, 18, 19, 7, 24, 5, 2, 7, 74, 76, 78, 76, 25, 1, 9, 71, 66, 70, 67, 69, 66, 74, 73, 83, 18, 68, 4, 68, 80, 0, 72, 4, 5, 1, 3, 11, 7, 71, 65, 3, 67, 2, 71, 2, 73, 0, 2, 68, 10, 3, 67, 68, 8, 68, 77, 70, 82, 74, 6, 65, 7, 7, 7, 32, 16, 4, 66, 9, 66, 73, 3, 99, 71, 0, 84, 67, 78, 7, 2, 74, 16, 7, 4, 86, 7, 77, 75, 3, 90, 9, 19, 12, 14, 17, 15, 8, 16, 15, 66, 3, 9, 2, 1, 72, 65, 71, 70, 66, 69, 71, 70, 72, 79, 72, 70, 82, 84, 81, 67, 66, 71, 72, 78, 81, 79, 79, 87, 94, 90, 93, 110, 102, 111, 69, 71, 88, 69, 75, 78, 82, 89, 94, 86, 89, 89, 80, 85, 91, 84, 86, 2, 24, 18, 11, 7, 9, 3, 65, 1, 3, 8, 32, 21, 15, 8, 19, 10, 10, 3, 18, 3, 42, 30, 23, 16, 23, 5, 64, 66, 65, 70, 44, 20, 4, 2, 10, 64, 73, 70, 5, 39, 25, 11, 5, 19, 5, 1, 64, 65, 62, 74, 67, 1, 1, 66, 0, 7, 9, 4, 8, 12, 15, 71, 65, 78, 71, 23, 73, 88, 70, 3, 67, 77, 79, 73, 79, 86, 92, 95, 65, 0, 15, 66, 71, 1, 2, 4, 72, 68, 64, 65, 80, 72, 78, 64, 9, 84, 68, 11, 72, 3, 68, 1, 19, 71, 73, 2, 8, 81, 81, 106, 18, 23, 31, 9, 3, 9, 0, 0, 2, 72, 69, 68, 84, 80, 80, 85, 84, 91, 115, 85, 85, 82, 69, 75, 6, 16, 1, 13, 24, 65, 68, 71, 71, 90, 83, 80, 99, 85, 94, 92, 107, 103, 104, 105, 79, 77, 106, 75, 76, 86, 97, 91, 92, 96, 90, 97, 98, 103, 106, 114, 112, 69, 77, 93, 69, 10, 7, 15, 17, 20, 37, 24, 32, 33, 50, 40, 36, 52, 49, 44, 9, 71, 88, 97, 112, 126, 126, 126, 12, 46, 39, 38, 28, 37, 18, 14, 17, 7, 72, 66, 11, 0, 25, 34, 64, 5, 14, 18, 5, 17, 32, 4, 0, 25, 66, 88, 110, 126, 126, 126, 126, 126 }, { 28, 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 90, 4, 11, 52, 14, 38, 69, 18, 26, 64, 79, 0, 92, 109, 65, 72, 126, 126, 126, 51, 9, 69, 18, 26, 64, 71, 11, 17, 67, 2, 66, 68, 70, 84, 73, 93, 5, 68, 69, 66, 79, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 18, 68, 88, 5, 35, 25, 54, 51, 19, 20, 8, 25, 6, 3, 9, 74, 76, 78, 75, 25, 1, 9, 70, 66, 69, 67, 68, 66, 75, 74, 84, 18, 68, 4, 68, 80, 0, 72, 4, 4, 1, 3, 11, 6, 71, 65, 3, 67, 1, 71, 1, 74, 0, 1, 70, 9, 2, 68, 68, 8, 68, 78, 71, 82, 75, 5, 65, 7, 7, 7, 33, 16, 4, 67, 9, 67, 74, 2, 100, 71, 0, 85, 67, 79, 7, 1, 75, 16, 7, 4, 88, 7, 78, 75, 3, 91, 8, 18, 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, 2, 0, 73, 66, 72, 70, 67, 70, 72, 71, 73, 79, 72, 70, 83, 86, 81, 69, 68, 73, 74, 80, 84, 81, 81, 89, 96, 92, 95, 112, 104, 112, 69, 71, 89, 70, 77, 80, 84, 91, 95, 88, 91, 90, 81, 86, 91, 84, 85, 2, 24, 18, 11, 7, 9, 3, 65, 1, 4, 9, 32, 21, 15, 8, 19, 10, 10, 4, 19, 3, 42, 30, 23, 15, 23, 5, 64, 66, 65, 70, 44, 20, 4, 2, 10, 64, 73, 69, 5, 38, 24, 10, 4, 18, 5, 1, 64, 65, 62, 73, 66, 1, 2, 65, 0, 8, 10, 5, 9, 13, 16, 71, 65, 78, 70, 24, 73, 89, 70, 3, 67, 77, 80, 73, 80, 87, 94, 96, 66, 0, 15, 66, 72, 0, 1, 3, 73, 69, 65, 65, 81, 73, 79, 64, 9, 85, 69, 10, 73, 3, 69, 0, 19, 72, 74, 2, 8, 82, 82, 108, 17, 22, 30, 7, 2, 8, 65, 65, 64, 74, 72, 71, 87, 83, 81, 88, 87, 94, 119, 88, 87, 84, 71, 77, 6, 16, 1, 14, 26, 67, 70, 73, 73, 93, 85, 82, 101, 87, 96, 93, 109, 104, 105, 106, 80, 78, 107, 76, 77, 88, 99, 93, 94, 97, 92, 99, 99, 104, 108, 116, 113, 70, 78, 94, 69, 10, 7, 16, 17, 20, 38, 24, 33, 34, 51, 41, 37, 53, 50, 43, 7, 73, 90, 100, 115, 126, 126, 126, 12, 46, 39, 38, 28, 37, 18, 14, 17, 7, 72, 66, 11, 0, 26, 35, 64, 5, 15, 19, 5, 17, 32, 4, 64, 24, 68, 90, 113, 126, 126, 126, 126, 126 }, { 27, 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 91, 3, 10, 52, 14, 40, 69, 19, 27, 64, 79, 1, 93, 110, 66, 74, 126, 126, 126, 54, 11, 69, 19, 27, 64, 70, 12, 18, 68, 2, 65, 67, 70, 84, 73, 93, 5, 68, 68, 66, 79, 73, 88, 14, 2, 0, 67, 9, 3, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 18, 67, 88, 7, 37, 27, 55, 53, 21, 21, 10, 26, 8, 4, 11, 74, 76, 78, 74, 25, 1, 9, 69, 66, 68, 66, 67, 66, 75, 74, 84, 18, 68, 5, 67, 79, 1, 72, 4, 4, 1, 3, 11, 6, 70, 65, 4, 67, 1, 70, 0, 74, 0, 0, 71, 9, 1, 69, 67, 8, 67, 79, 72, 82, 76, 5, 65, 8, 7, 7, 34, 16, 4, 67, 10, 67, 74, 2, 101, 71, 0, 86, 67, 80, 7, 1, 76, 16, 7, 4, 89, 7, 78, 75, 3, 92, 7, 18, 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, 2, 0, 73, 66, 72, 70, 67, 70, 73, 71, 73, 79, 72, 70, 84, 87, 81, 71, 69, 74, 75, 82, 86, 82, 82, 91, 98, 93, 96, 114, 105, 113, 69, 71, 90, 71, 78, 81, 85, 92, 96, 89, 92, 91, 82, 87, 91, 83, 84, 3, 25, 18, 11, 7, 10, 4, 64, 2, 5, 10, 32, 21, 15, 8, 20, 10, 11, 5, 21, 3, 42, 30, 23, 15, 24, 5, 64, 66, 64, 70, 45, 20, 4, 2, 11, 64, 73, 68, 5, 38, 24, 10, 3, 18, 5, 1, 0, 64, 62, 72, 65, 2, 3, 0, 1, 10, 11, 7, 10, 14, 18, 70, 64, 78, 69, 26, 73, 90, 69, 4, 67, 77, 81, 73, 80, 88, 95, 97, 66, 0, 15, 66, 72, 64, 1, 3, 74, 69, 65, 65, 82, 73, 79, 0, 10, 85, 69, 9, 73, 3, 69, 0, 19, 73, 75, 2, 8, 83, 82, 109, 17, 21, 29, 6, 1, 7, 67, 67, 66, 76, 74, 74, 89, 85, 82, 91, 90, 97, 123, 91, 89, 85, 72, 78, 6, 17, 1, 15, 28, 69, 71, 75, 75, 95, 86, 83, 103, 88, 97, 94, 110, 105, 106, 106, 80, 78, 108, 77, 78, 89, 101, 94, 95, 98, 93, 100, 100, 105, 109, 117, 114, 70, 79, 94, 68, 10, 7, 17, 18, 21, 39, 25, 34, 35, 53, 42, 38, 55, 52, 42, 6, 75, 92, 103, 118, 126, 126, 126, 13, 46, 39, 39, 28, 38, 19, 14, 18, 8, 72, 65, 12, 0, 27, 37, 0, 6, 16, 20, 5, 18, 33, 4, 64, 23, 70, 92, 115, 126, 126, 126, 126, 126 }, { 26, 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 93, 1, 8, 52, 14, 43, 70, 20, 27, 64, 80, 1, 94, 111, 66, 76, 126, 126, 126, 57, 12, 69, 20, 27, 64, 70, 13, 18, 68, 2, 65, 67, 70, 85, 72, 93, 5, 68, 67, 67, 79, 73, 88, 15, 3, 0, 66, 10, 3, 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 18, 67, 88, 8, 39, 28, 57, 55, 22, 22, 11, 28, 9, 5, 13, 73, 75, 77, 73, 25, 1, 9, 69, 66, 68, 66, 65, 67, 75, 74, 84, 18, 68, 5, 67, 79, 1, 72, 5, 4, 0, 3, 11, 6, 70, 65, 4, 67, 1, 70, 0, 74, 0, 0, 72, 8, 0, 70, 66, 9, 67, 80, 72, 81, 76, 5, 66, 8, 7, 7, 35, 17, 4, 67, 10, 67, 75, 2, 102, 71, 0, 86, 68, 81, 6, 1, 76, 17, 7, 4, 90, 6, 79, 75, 3, 92, 7, 18, 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, 2, 0, 73, 66, 72, 71, 67, 70, 73, 72, 74, 79, 73, 70, 85, 88, 81, 72, 71, 76, 77, 84, 88, 84, 84, 93, 100, 95, 98, 117, 107, 114, 69, 72, 90, 72, 79, 82, 86, 94, 98, 90, 93, 91, 82, 87, 91, 83, 84, 3, 25, 18, 11, 7, 10, 4, 64, 2, 6, 10, 32, 21, 15, 8, 20, 11, 12, 5, 23, 3, 42, 30, 23, 15, 24, 5, 64, 66, 0, 70, 45, 19, 3, 2, 11, 64, 73, 68, 5, 37, 23, 9, 2, 18, 5, 1, 0, 64, 62, 71, 64, 3, 5, 1, 2, 11, 13, 8, 12, 16, 20, 69, 0, 77, 69, 27, 72, 90, 69, 5, 67, 78, 81, 74, 81, 89, 96, 98, 67, 64, 15, 67, 73, 64, 1, 3, 75, 70, 65, 66, 83, 73, 79, 0, 10, 85, 70, 9, 74, 3, 70, 64, 19, 74, 76, 1, 7, 84, 83, 111, 16, 21, 29, 5, 64, 5, 69, 69, 68, 79, 77, 76, 92, 87, 84, 94, 94, 100, 126, 93, 92, 87, 73, 79, 6, 18, 2, 16, 30, 70, 73, 77, 76, 97, 88, 85, 105, 89, 99, 96, 112, 107, 107, 107, 81, 78, 109, 79, 80, 91, 102, 96, 97, 100, 95, 102, 102, 106, 110, 118, 114, 71, 79, 95, 68, 11, 8, 17, 18, 22, 40, 26, 35, 36, 54, 43, 39, 56, 53, 42, 4, 77, 95, 105, 121, 126, 126, 126, 13, 47, 40, 39, 29, 39, 19, 15, 18, 8, 71, 65, 13, 1, 28, 38, 0, 7, 16, 20, 5, 18, 34, 4, 64, 21, 72, 95, 118, 126, 126, 126, 126, 126 }, { 25, 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 94, 0, 7, 52, 14, 45, 70, 20, 28, 64, 81, 1, 95, 112, 67, 77, 126, 126, 126, 60, 13, 69, 20, 28, 64, 69, 14, 19, 69, 3, 64, 66, 71, 85, 72, 93, 5, 67, 67, 67, 79, 72, 88, 15, 3, 0, 66, 10, 3, 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 18, 67, 87, 10, 41, 30, 58, 57, 23, 23, 13, 29, 10, 5, 14, 73, 75, 77, 73, 26, 1, 9, 68, 65, 67, 65, 64, 67, 76, 75, 85, 18, 68, 5, 66, 79, 2, 72, 5, 4, 0, 4, 11, 5, 70, 65, 4, 67, 1, 70, 64, 74, 0, 64, 73, 7, 0, 70, 66, 9, 67, 80, 73, 81, 77, 5, 66, 8, 7, 7, 35, 17, 4, 67, 11, 67, 76, 2, 102, 72, 0, 87, 68, 82, 6, 1, 77, 17, 7, 4, 92, 6, 80, 75, 3, 93, 6, 18, 11, 14, 17, 14, 7, 16, 14, 67, 1, 9, 1, 0, 73, 67, 73, 71, 68, 71, 74, 73, 74, 79, 73, 69, 86, 90, 81, 74, 73, 78, 79, 86, 90, 86, 86, 95, 102, 96, 99, 119, 109, 115, 69, 72, 91, 73, 80, 83, 88, 95, 99, 91, 94, 92, 83, 88, 91, 83, 83, 4, 25, 18, 11, 8, 10, 4, 64, 3, 7, 11, 31, 21, 15, 8, 21, 11, 13, 6, 25, 3, 43, 30, 23, 15, 25, 5, 64, 65, 0, 70, 45, 19, 3, 2, 11, 0, 72, 67, 5, 37, 23, 8, 2, 18, 5, 2, 1, 0, 62, 71, 0, 3, 6, 2, 3, 13, 14, 9, 13, 17, 21, 69, 1, 77, 68, 29, 72, 91, 69, 5, 67, 78, 82, 74, 82, 90, 97, 99, 67, 64, 15, 67, 73, 65, 0, 3, 75, 70, 66, 66, 83, 73, 80, 0, 10, 86, 70, 8, 75, 2, 71, 64, 20, 74, 77, 1, 7, 84, 83, 112, 15, 20, 28, 4, 65, 4, 71, 71, 70, 81, 79, 79, 95, 90, 85, 96, 97, 103, 126, 96, 94, 89, 74, 80, 6, 19, 2, 18, 33, 72, 75, 79, 78, 99, 90, 86, 107, 91, 100, 97, 113, 108, 108, 108, 81, 79, 110, 80, 81, 92, 104, 98, 99, 101, 96, 104, 103, 108, 112, 120, 115, 72, 80, 96, 68, 11, 8, 18, 19, 22, 42, 27, 36, 37, 55, 45, 40, 58, 54, 41, 3, 79, 97, 108, 123, 126, 126, 126, 14, 47, 40, 40, 29, 40, 20, 15, 19, 9, 71, 64, 13, 1, 29, 39, 0, 7, 17, 21, 5, 19, 35, 4, 64, 20, 74, 97, 121, 126, 126, 126, 126, 126 }, { 23, 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 96, 65, 6, 52, 14, 47, 70, 21, 29, 64, 82, 1, 96, 113, 67, 79, 126, 126, 126, 62, 14, 69, 21, 29, 64, 69, 15, 19, 69, 3, 64, 65, 71, 86, 72, 93, 5, 67, 66, 67, 80, 72, 88, 16, 3, 0, 66, 11, 3, 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 18, 67, 87, 12, 42, 31, 60, 58, 24, 24, 14, 30, 11, 6, 16, 72, 75, 76, 72, 26, 1, 9, 68, 65, 67, 65, 1, 67, 76, 75, 85, 18, 68, 5, 66, 79, 2, 72, 6, 4, 0, 4, 11, 5, 70, 65, 4, 67, 1, 70, 65, 75, 0, 65, 74, 6, 64, 71, 65, 10, 67, 81, 73, 80, 77, 5, 66, 8, 7, 7, 36, 17, 4, 68, 11, 68, 77, 1, 103, 72, 0, 87, 69, 83, 6, 0, 78, 17, 7, 4, 93, 6, 81, 75, 3, 93, 5, 18, 11, 14, 17, 14, 7, 16, 13, 67, 1, 9, 1, 0, 74, 67, 73, 72, 68, 71, 75, 74, 75, 79, 74, 69, 87, 91, 81, 75, 75, 80, 81, 88, 92, 88, 88, 97, 104, 98, 101, 121, 111, 116, 69, 72, 91, 74, 81, 84, 89, 97, 101, 92, 96, 93, 83, 88, 91, 83, 83, 4, 25, 18, 11, 8, 10, 4, 64, 3, 8, 11, 31, 21, 15, 8, 21, 12, 13, 7, 27, 3, 43, 30, 23, 15, 25, 5, 64, 65, 1, 70, 45, 19, 2, 2, 11, 0, 72, 66, 5, 36, 22, 7, 1, 18, 5, 2, 1, 0, 62, 70, 1, 4, 7, 3, 4, 14, 15, 10, 14, 19, 23, 68, 1, 77, 68, 30, 72, 91, 69, 6, 67, 79, 82, 74, 83, 91, 98, 100, 68, 65, 15, 68, 74, 65, 0, 3, 76, 71, 66, 66, 84, 74, 80, 0, 10, 86, 71, 8, 76, 2, 72, 65, 20, 75, 78, 0, 6, 85, 84, 114, 14, 19, 28, 3, 66, 2, 73, 73, 72, 84, 82, 81, 98, 92, 86, 99, 100, 106, 126, 99, 97, 91, 75, 82, 6, 20, 2, 19, 35, 74, 77, 81, 80, 101, 92, 88, 109, 92, 102, 98, 115, 110, 109, 109, 82, 79, 111, 81, 83, 94, 106, 100, 101, 103, 98, 106, 105, 109, 113, 121, 116, 73, 81, 97, 68, 12, 8, 18, 19, 23, 43, 27, 37, 38, 56, 46, 41, 59, 55, 41, 1, 81, 100, 110, 126, 126, 126, 126, 14, 48, 41, 40, 29, 40, 20, 15, 19, 9, 71, 64, 14, 2, 30, 40, 0, 8, 17, 21, 5, 19, 36, 4, 64, 19, 76, 100, 124, 126, 126, 126, 126, 126 }, { 22, 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 66, 4, 52, 14, 50, 71, 22, 29, 64, 82, 2, 97, 114, 68, 81, 126, 126, 126, 62, 16, 69, 22, 29, 64, 69, 16, 19, 70, 3, 64, 65, 71, 86, 71, 93, 5, 67, 65, 68, 80, 72, 88, 16, 4, 1, 65, 11, 3, 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 18, 66, 87, 13, 44, 33, 61, 60, 26, 25, 15, 32, 12, 7, 18, 72, 74, 76, 71, 26, 1, 9, 67, 65, 66, 64, 2, 68, 76, 75, 85, 18, 68, 6, 65, 79, 2, 72, 6, 4, 64, 4, 11, 5, 69, 65, 5, 67, 1, 70, 65, 75, 0, 65, 75, 6, 65, 72, 64, 10, 67, 82, 74, 80, 78, 5, 67, 8, 7, 7, 37, 18, 4, 68, 11, 68, 78, 1, 104, 72, 0, 88, 69, 84, 5, 0, 78, 18, 7, 4, 94, 5, 82, 75, 3, 94, 5, 18, 11, 14, 17, 14, 7, 16, 13, 67, 1, 9, 1, 0, 74, 67, 73, 72, 68, 71, 75, 75, 75, 79, 74, 69, 88, 92, 81, 77, 77, 81, 82, 90, 94, 90, 90, 99, 106, 100, 103, 124, 112, 117, 69, 73, 92, 75, 82, 85, 90, 98, 102, 93, 97, 93, 84, 89, 91, 83, 82, 4, 25, 18, 11, 8, 10, 5, 0, 4, 9, 12, 31, 21, 15, 8, 21, 12, 14, 7, 29, 3, 43, 30, 23, 15, 25, 5, 64, 65, 2, 70, 46, 18, 2, 2, 11, 0, 72, 66, 5, 36, 21, 6, 0, 18, 5, 2, 1, 1, 62, 69, 2, 5, 9, 4, 5, 15, 17, 11, 16, 20, 25, 67, 2, 76, 67, 31, 71, 92, 68, 7, 67, 79, 83, 75, 83, 92, 99, 101, 69, 65, 15, 68, 74, 66, 0, 3, 77, 72, 66, 67, 85, 74, 80, 0, 10, 86, 72, 7, 76, 2, 73, 66, 20, 76, 79, 0, 6, 86, 85, 116, 13, 19, 27, 2, 68, 1, 75, 75, 74, 86, 85, 84, 101, 94, 88, 102, 104, 109, 126, 101, 99, 93, 76, 83, 6, 21, 3, 20, 37, 75, 78, 83, 81, 103, 94, 89, 111, 93, 104, 100, 117, 111, 110, 110, 82, 79, 112, 83, 84, 96, 107, 101, 102, 104, 99, 107, 106, 110, 114, 122, 116, 73, 81, 98, 67, 12, 9, 19, 20, 24, 44, 28, 38, 39, 58, 47, 42, 60, 57, 40, 64, 83, 102, 113, 126, 126, 126, 126, 14, 48, 41, 41, 30, 41, 20, 16, 20, 10, 70, 64, 15, 2, 31, 41, 1, 9, 18, 22, 5, 20, 37, 4, 64, 17, 78, 102, 126, 126, 126, 126, 126, 126 }, { 21, 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 68, 3, 52, 14, 52, 71, 23, 30, 64, 83, 2, 98, 115, 68, 83, 126, 126, 126, 62, 17, 69, 23, 30, 64, 68, 17, 20, 70, 3, 0, 64, 72, 87, 71, 93, 5, 67, 65, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 18, 66, 87, 15, 46, 34, 62, 62, 27, 26, 17, 33, 13, 8, 20, 71, 74, 75, 70, 26, 1, 9, 67, 65, 66, 64, 4, 68, 77, 76, 86, 18, 68, 6, 65, 79, 3, 72, 7, 4, 64, 4, 11, 4, 69, 65, 5, 67, 1, 70, 66, 75, 0, 66, 76, 5, 66, 73, 64, 11, 67, 83, 74, 79, 78, 5, 67, 8, 7, 7, 38, 18, 4, 68, 12, 68, 79, 1, 105, 72, 0, 88, 70, 85, 5, 0, 79, 18, 7, 4, 96, 5, 83, 75, 3, 94, 4, 18, 11, 14, 17, 14, 7, 16, 13, 67, 0, 9, 1, 0, 74, 68, 74, 73, 69, 72, 76, 76, 76, 79, 75, 69, 89, 94, 81, 78, 79, 83, 84, 92, 96, 92, 92, 101, 108, 101, 104, 126, 114, 118, 69, 73, 92, 76, 83, 86, 92, 100, 104, 94, 98, 94, 84, 89, 91, 83, 82, 5, 25, 18, 11, 8, 10, 5, 0, 4, 10, 12, 31, 21, 15, 8, 22, 13, 15, 8, 31, 3, 43, 30, 23, 15, 26, 5, 64, 65, 2, 70, 46, 18, 1, 2, 11, 0, 72, 65, 5, 35, 21, 5, 64, 18, 5, 2, 2, 1, 62, 68, 3, 5, 10, 5, 6, 17, 18, 12, 17, 22, 26, 67, 3, 76, 67, 33, 71, 92, 68, 7, 67, 80, 83, 75, 84, 93, 100, 102, 69, 66, 15, 69, 75, 66, 64, 3, 78, 72, 67, 67, 86, 74, 81, 0, 10, 87, 72, 7, 77, 2, 74, 66, 20, 77, 80, 64, 5, 87, 85, 117, 12, 18, 27, 1, 69, 64, 77, 77, 76, 89, 87, 86, 104, 97, 89, 105, 107, 112, 126, 104, 102, 95, 77, 84, 6, 22, 3, 21, 39, 77, 80, 85, 83, 105, 96, 91, 113, 95, 105, 101, 118, 113, 111, 111, 83, 80, 113, 84, 86, 97, 109, 103, 104, 106, 101, 109, 108, 111, 116, 124, 117, 74, 82, 99, 67, 13, 9, 19, 20, 24, 45, 29, 39, 40, 59, 48, 43, 62, 58, 40, 65, 85, 105, 115, 126, 126, 126, 126, 15, 49, 42, 41, 30, 42, 21, 16, 20, 10, 70, 0, 15, 3, 32, 42, 1, 9, 18, 22, 5, 20, 38, 4, 64, 16, 80, 105, 126, 126, 126, 126, 126, 126 }, { 20, 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 100, 69, 2, 52, 14, 54, 71, 24, 31, 64, 84, 2, 99, 116, 69, 85, 126, 126, 126, 62, 18, 69, 24, 31, 64, 68, 18, 20, 71, 3, 0, 0, 72, 87, 71, 93, 5, 67, 64, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 18, 66, 87, 17, 48, 36, 62, 62, 28, 27, 18, 34, 14, 9, 22, 71, 74, 75, 69, 26, 1, 9, 66, 65, 65, 0, 5, 68, 77, 76, 86, 18, 68, 6, 64, 79, 3, 72, 7, 4, 64, 4, 11, 4, 69, 65, 5, 67, 1, 70, 67, 75, 0, 67, 77, 4, 67, 74, 0, 11, 67, 84, 75, 79, 79, 5, 67, 8, 7, 7, 39, 18, 4, 68, 12, 68, 80, 1, 106, 72, 0, 89, 70, 86, 5, 0, 80, 18, 7, 4, 97, 5, 84, 75, 3, 95, 3, 18, 11, 14, 17, 14, 7, 16, 13, 67, 0, 9, 1, 0, 74, 68, 74, 73, 69, 72, 77, 77, 76, 79, 75, 69, 90, 95, 81, 80, 81, 85, 86, 94, 98, 94, 94, 103, 110, 103, 106, 126, 116, 119, 69, 73, 93, 77, 84, 87, 93, 101, 105, 95, 99, 95, 85, 90, 91, 83, 81, 5, 25, 18, 11, 8, 10, 5, 0, 5, 11, 13, 31, 21, 15, 8, 22, 13, 16, 9, 33, 3, 43, 30, 23, 15, 26, 5, 64, 65, 3, 70, 46, 18, 1, 2, 11, 0, 72, 64, 5, 35, 20, 4, 65, 18, 5, 2, 2, 2, 62, 67, 4, 6, 11, 6, 7, 18, 19, 13, 18, 23, 28, 66, 4, 76, 66, 34, 71, 93, 68, 8, 67, 80, 84, 75, 85, 94, 101, 103, 70, 66, 15, 69, 75, 67, 64, 3, 79, 73, 67, 67, 87, 74, 81, 0, 10, 87, 73, 6, 78, 2, 75, 67, 20, 78, 81, 64, 5, 88, 86, 119, 11, 17, 26, 0, 70, 65, 79, 79, 78, 91, 90, 89, 107, 99, 90, 108, 110, 115, 126, 107, 104, 97, 78, 85, 6, 23, 3, 22, 41, 79, 82, 87, 85, 107, 98, 92, 115, 96, 107, 102, 120, 114, 112, 112, 83, 80, 114, 85, 87, 99, 111, 105, 106, 107, 102, 111, 109, 112, 117, 125, 118, 75, 83, 100, 67, 13, 9, 20, 21, 25, 46, 30, 40, 41, 60, 49, 44, 62, 59, 39, 67, 87, 107, 118, 126, 126, 126, 126, 15, 49, 42, 42, 30, 43, 21, 16, 21, 11, 70, 0, 16, 3, 33, 43, 1, 10, 19, 23, 5, 21, 39, 4, 64, 15, 82, 107, 126, 126, 126, 126, 126, 126 }, { 18, 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 102, 71, 0, 51, 14, 56, 72, 24, 31, 65, 85, 2, 101, 118, 70, 87, 126, 126, 126, 62, 19, 70, 24, 31, 65, 68, 19, 20, 72, 3, 0, 0, 73, 88, 71, 94, 5, 67, 64, 69, 81, 72, 88, 17, 4, 1, 65, 12, 2, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 18, 66, 87, 18, 49, 37, 62, 62, 29, 28, 19, 35, 15, 9, 23, 71, 74, 75, 69, 26, 1, 9, 66, 65, 65, 0, 6, 69, 78, 77, 87, 18, 68, 6, 64, 79, 3, 72, 7, 3, 65, 4, 10, 3, 69, 66, 5, 67, 0, 70, 68, 76, 64, 68, 79, 3, 68, 75, 0, 11, 67, 85, 76, 79, 80, 4, 68, 8, 7, 7, 39, 18, 4, 69, 12, 69, 81, 0, 107, 73, 64, 90, 71, 87, 4, 64, 81, 18, 7, 4, 99, 4, 85, 75, 3, 96, 2, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, 9, 0, 64, 75, 69, 75, 74, 70, 73, 78, 78, 77, 79, 76, 69, 91, 97, 81, 82, 83, 87, 88, 96, 101, 96, 96, 105, 112, 105, 108, 126, 118, 121, 70, 74, 94, 78, 86, 89, 95, 103, 107, 97, 101, 96, 86, 91, 91, 83, 81, 5, 25, 18, 11, 8, 10, 5, 0, 5, 12, 13, 30, 21, 15, 8, 22, 13, 16, 9, 34, 2, 43, 30, 22, 14, 26, 5, 64, 65, 3, 70, 46, 17, 0, 1, 11, 0, 72, 64, 5, 34, 19, 3, 66, 17, 5, 2, 2, 2, 62, 67, 5, 6, 12, 7, 7, 19, 20, 14, 19, 24, 29, 66, 4, 76, 66, 35, 71, 94, 68, 8, 67, 81, 85, 76, 86, 95, 103, 105, 71, 67, 15, 70, 76, 68, 65, 2, 80, 74, 68, 68, 88, 75, 82, 0, 10, 88, 74, 5, 79, 1, 76, 68, 20, 79, 83, 65, 4, 89, 87, 121, 10, 16, 25, 65, 72, 67, 81, 81, 81, 94, 93, 92, 110, 102, 92, 111, 114, 118, 126, 110, 107, 99, 80, 87, 6, 23, 3, 23, 43, 81, 84, 89, 87, 110, 100, 94, 118, 98, 109, 104, 122, 116, 113, 113, 84, 81, 116, 87, 89, 101, 113, 107, 108, 109, 104, 113, 111, 114, 119, 126, 119, 76, 84, 101, 67, 13, 9, 20, 21, 25, 47, 30, 41, 41, 61, 50, 45, 62, 60, 38, 69, 90, 110, 121, 126, 126, 126, 126, 15, 49, 42, 42, 30, 43, 21, 16, 21, 11, 70, 0, 16, 3, 34, 44, 1, 10, 19, 23, 5, 21, 39, 4, 65, 13, 85, 110, 126, 126, 126, 126, 126, 126 }, { 17, 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 103, 72, 64, 51, 14, 59, 72, 25, 32, 65, 85, 3, 102, 119, 70, 88, 126, 126, 126, 62, 21, 70, 25, 32, 65, 67, 21, 21, 72, 4, 1, 1, 73, 88, 70, 94, 5, 66, 0, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 18, 65, 86, 20, 51, 39, 62, 62, 31, 29, 21, 37, 17, 10, 25, 70, 73, 74, 68, 27, 2, 10, 65, 64, 64, 1, 8, 69, 78, 77, 87, 19, 68, 7, 0, 78, 4, 71, 8, 3, 65, 5, 10, 3, 68, 66, 6, 67, 0, 69, 68, 76, 64, 68, 80, 3, 68, 75, 1, 12, 66, 85, 76, 78, 80, 4, 68, 9, 7, 7, 40, 19, 5, 69, 13, 69, 81, 0, 107, 73, 64, 90, 71, 88, 4, 64, 81, 19, 8, 4, 100, 4, 85, 74, 3, 96, 2, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, 9, 0, 64, 75, 69, 75, 74, 70, 73, 78, 78, 77, 78, 76, 68, 91, 98, 80, 83, 84, 88, 89, 98, 103, 97, 97, 107, 113, 106, 109, 126, 119, 122, 70, 74, 94, 79, 87, 90, 96, 104, 108, 98, 102, 96, 86, 91, 90, 82, 80, 6, 26, 18, 11, 9, 11, 6, 1, 6, 14, 14, 30, 21, 15, 8, 23, 14, 17, 10, 36, 2, 44, 31, 22, 14, 27, 5, 64, 64, 4, 70, 47, 17, 0, 1, 12, 1, 71, 0, 5, 34, 19, 3, 66, 17, 5, 3, 3, 3, 62, 66, 6, 7, 14, 9, 8, 21, 22, 16, 21, 26, 31, 65, 5, 75, 65, 37, 70, 94, 67, 9, 66, 81, 85, 76, 86, 95, 104, 106, 71, 67, 16, 70, 76, 68, 65, 2, 80, 74, 68, 68, 88, 75, 82, 1, 11, 88, 74, 5, 79, 1, 76, 68, 21, 79, 84, 65, 4, 89, 87, 122, 10, 16, 25, 66, 73, 68, 83, 83, 83, 96, 95, 94, 112, 104, 93, 113, 117, 121, 126, 112, 109, 100, 81, 88, 6, 24, 4, 25, 46, 82, 85, 90, 88, 112, 101, 95, 120, 99, 110, 105, 123, 117, 114, 113, 84, 81, 117, 88, 90, 102, 114, 108, 109, 110, 105, 114, 112, 115, 120, 126, 119, 76, 84, 101, 66, 14, 10, 21, 22, 26, 49, 31, 42, 42, 62, 52, 46, 62, 62, 38, 70, 92, 112, 123, 126, 126, 126, 126, 16, 50, 43, 43, 31, 44, 22, 17, 22, 12, 69, 1, 17, 4, 36, 46, 2, 11, 20, 24, 6, 22, 40, 4, 65, 12, 87, 112, 126, 126, 126, 126, 126, 126 }, { 16, 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 104, 73, 65, 51, 14, 61, 72, 26, 33, 65, 86, 3, 103, 120, 71, 90, 126, 126, 126, 62, 22, 70, 26, 33, 65, 67, 22, 21, 73, 4, 1, 2, 73, 88, 70, 94, 5, 66, 1, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 18, 65, 86, 22, 53, 41, 62, 62, 32, 30, 22, 38, 18, 11, 27, 70, 73, 74, 67, 27, 2, 10, 64, 64, 0, 1, 9, 69, 78, 77, 87, 19, 68, 7, 0, 78, 4, 71, 8, 3, 65, 5, 10, 3, 68, 66, 6, 67, 0, 69, 69, 76, 64, 69, 81, 2, 69, 76, 2, 12, 66, 86, 77, 78, 81, 4, 68, 9, 7, 7, 41, 19, 5, 69, 13, 69, 82, 0, 108, 73, 64, 91, 71, 89, 4, 64, 82, 19, 8, 4, 101, 4, 86, 74, 3, 97, 1, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, 9, 0, 64, 75, 69, 75, 74, 70, 73, 79, 79, 78, 78, 76, 68, 92, 99, 80, 85, 86, 90, 91, 100, 105, 99, 99, 109, 115, 108, 111, 126, 121, 123, 70, 74, 95, 80, 88, 91, 97, 106, 109, 99, 103, 97, 87, 92, 90, 82, 79, 6, 26, 18, 11, 9, 11, 6, 1, 6, 15, 15, 30, 21, 15, 8, 23, 14, 18, 11, 38, 2, 44, 31, 22, 14, 27, 5, 64, 64, 5, 70, 47, 17, 0, 1, 12, 1, 71, 1, 5, 33, 18, 2, 67, 17, 5, 3, 3, 3, 62, 65, 7, 8, 15, 10, 9, 22, 23, 17, 22, 27, 33, 64, 6, 75, 64, 38, 70, 95, 67, 10, 66, 81, 86, 76, 87, 96, 105, 107, 72, 67, 16, 70, 77, 69, 65, 2, 81, 75, 68, 68, 89, 75, 82, 1, 11, 88, 75, 4, 80, 1, 77, 69, 21, 80, 85, 65, 4, 90, 88, 124, 9, 15, 24, 67, 74, 69, 85, 85, 85, 98, 98, 97, 115, 106, 94, 116, 120, 124, 126, 115, 111, 102, 82, 89, 6, 25, 4, 26, 48, 84, 87, 92, 90, 114, 103, 97, 122, 100, 112, 106, 125, 118, 115, 114, 85, 81, 118, 89, 91, 104, 116, 110, 111, 111, 107, 116, 113, 116, 121, 126, 120, 77, 85, 102, 66, 14, 10, 22, 22, 27, 50, 32, 43, 43, 62, 53, 47, 62, 62, 37, 72, 94, 114, 126, 126, 126, 126, 126, 16, 50, 43, 43, 31, 45, 22, 17, 22, 12, 69, 1, 18, 4, 37, 47, 2, 12, 21, 25, 6, 22, 41, 4, 65, 11, 89, 114, 126, 126, 126, 126, 126, 126 }, { 15, 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 106, 75, 66, 51, 14, 62, 72, 27, 34, 65, 87, 3, 104, 121, 71, 92, 126, 126, 126, 62, 23, 70, 27, 34, 65, 66, 23, 22, 73, 4, 2, 3, 74, 89, 70, 94, 5, 66, 1, 69, 81, 71, 88, 19, 5, 2, 64, 14, 2, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 18, 65, 86, 24, 55, 42, 62, 62, 33, 31, 24, 39, 19, 12, 29, 69, 73, 73, 66, 27, 2, 10, 64, 64, 0, 2, 11, 69, 79, 78, 88, 19, 68, 7, 1, 78, 5, 71, 9, 3, 65, 5, 10, 2, 68, 66, 6, 67, 0, 69, 70, 76, 64, 70, 82, 1, 70, 77, 2, 13, 66, 87, 77, 77, 81, 4, 68, 9, 7, 7, 42, 19, 5, 69, 14, 69, 83, 0, 109, 73, 64, 91, 72, 90, 4, 64, 83, 19, 8, 4, 103, 4, 87, 74, 3, 97, 0, 17, 10, 14, 17, 13, 6, 16, 12, 68, 65, 9, 0, 64, 75, 70, 76, 75, 71, 74, 80, 80, 78, 78, 77, 68, 93, 101, 80, 86, 88, 92, 93, 102, 107, 101, 101, 111, 117, 109, 112, 126, 123, 124, 70, 74, 95, 81, 89, 92, 99, 107, 111, 100, 104, 98, 87, 92, 90, 82, 79, 7, 26, 18, 11, 9, 11, 6, 1, 7, 16, 15, 30, 21, 15, 8, 24, 15, 19, 12, 40, 2, 44, 31, 22, 14, 28, 5, 64, 64, 5, 70, 47, 17, 64, 1, 12, 1, 71, 2, 5, 33, 18, 1, 68, 17, 5, 3, 4, 4, 62, 64, 8, 8, 16, 11, 10, 24, 24, 18, 23, 29, 34, 64, 7, 75, 64, 40, 70, 95, 67, 10, 66, 82, 86, 76, 88, 97, 106, 108, 72, 68, 16, 71, 77, 69, 66, 2, 82, 75, 69, 68, 90, 75, 83, 1, 11, 89, 75, 4, 81, 1, 78, 69, 21, 81, 86, 66, 3, 91, 88, 125, 8, 14, 24, 68, 75, 71, 87, 87, 87, 101, 100, 99, 118, 109, 95, 119, 123, 126, 126, 118, 114, 104, 83, 90, 6, 26, 4, 27, 50, 86, 89, 94, 92, 116, 105, 98, 124, 102, 113, 107, 126, 120, 116, 115, 85, 82, 119, 90, 93, 105, 118, 112, 113, 113, 108, 118, 115, 117, 123, 126, 121, 78, 86, 103, 66, 15, 10, 22, 23, 27, 51, 33, 44, 44, 62, 54, 48, 62, 62, 37, 73, 96, 117, 126, 126, 126, 126, 126, 17, 51, 44, 44, 31, 46, 23, 17, 23, 13, 69, 2, 18, 5, 38, 48, 2, 12, 21, 25, 6, 23, 42, 4, 65, 10, 91, 117, 126, 126, 126, 126, 126, 126 }, }, { { 62, 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 38, 62, 62, 54, 22, 118, 65, 71, 79, 11, 13, 70, 9, 29, 41, 62, 61, 27, 69, 126, 101, 76, 71, 79, 11, 69, 90, 11, 20, 69, 82, 96, 4, 75, 87, 100, 7, 74, 85, 4, 81, 86, 95, 66, 77, 70, 86, 72, 2, 22, 0, 0, 0, 83, 86, 97, 72, 22, 1, 48, 12, 80, 126, 91, 96, 81, 98, 102, 97, 119, 99, 110, 102, 126, 80, 89, 94, 92, 24, 65, 84, 126, 73, 104, 91, 126, 8, 7, 8, 2, 10, 68, 74, 88, 103, 91, 89, 92, 76, 87, 110, 105, 78, 112, 99, 126, 126, 126, 126, 66, 78, 71, 72, 4, 8, 70, 75, 89, 119, 75, 43, 41, 126, 9, 2, 5, 3, 2, 67, 84, 74, 65, 11, 6, 2, 69, 70, 8, 71, 5, 2, 22, 38, 31, 20, 16, 19, 12, 17, 25, 66, 25, 21, 29, 89, 18, 35, 32, 62, 62, 48, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 53, 62, 62, 62, 62, 62, 62, 62, 56, 62, 62, 62, 27, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 53, 45, 38, 22, 75, 72, 77, 28, 32, 28, 33, 18, 21, 18, 37, 9, 66, 7, 73, 67, 116, 112, 71, 2, 10, 66, 77, 80, 84, 87, 126, 101, 24, 10, 2, 75, 77, 91, 107, 111, 122, 76, 19, 11, 6, 5, 72, 69, 69, 74, 86, 66, 29, 31, 32, 11, 8, 67, 73, 89, 11, 59, 55, 55, 44, 26, 2, 73, 70, 78, 62, 126, 124, 110, 126, 124, 105, 121, 117, 102, 117, 116, 122, 95, 100, 95, 111, 114, 89, 80, 82, 85, 81, 72, 64, 67, 7, 69, 69, 69, 69, 67, 77, 64, 2, 67, 64, 6, 65, 66, 1, 12, 66, 71, 75, 70, 72, 3, 26, 16, 28, 26, 22, 22, 15, 22, 22, 4, 13, 23, 66, 13, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 54, 62, 62, 62, 62, 62, 62, 62, 62, 62, 49, 37, 26, 8, 65, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 43, 33, 19, 15, 14, 18, 41, 41, 42, 43, 35, 39, 29, 21, 24, 13, 70, 9, 71, 83, 31, 14, 9, 85, 81, 77, 81, 80, 73, 74, 83, 71, 67, 2, 66, 66, 4, 4, 62, 62, 62, 62, 62, 60, 53, 36, 6, 71, 39, 27, 21, 11, 6, 0, 65, 67, 82, 81, 76, 72, 78, 72, 68, 70, 76, 66, 1, 6, 2, 3, 9, 5, 62, 62, 62, 62, 62, 60, 53, 36, 6 }, { 62, 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 37, 61, 62, 55, 22, 116, 65, 70, 78, 11, 13, 69, 9, 28, 40, 61, 58, 25, 70, 124, 100, 75, 70, 78, 11, 69, 89, 11, 20, 68, 81, 95, 4, 75, 86, 99, 7, 73, 84, 4, 80, 85, 94, 65, 76, 70, 85, 71, 2, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 48, 12, 80, 124, 89, 94, 79, 95, 100, 95, 117, 97, 108, 100, 124, 80, 88, 93, 91, 24, 65, 83, 124, 72, 103, 90, 125, 8, 7, 8, 2, 11, 68, 73, 87, 102, 90, 88, 91, 75, 86, 108, 103, 77, 110, 97, 122, 122, 123, 124, 65, 77, 70, 71, 4, 9, 69, 74, 88, 116, 74, 41, 40, 124, 9, 3, 5, 4, 3, 66, 82, 73, 64, 11, 6, 2, 68, 69, 7, 70, 5, 2, 22, 37, 31, 20, 16, 19, 12, 17, 24, 65, 25, 21, 29, 89, 18, 35, 32, 62, 62, 47, 62, 62, 62, 61, 62, 62, 62, 62, 62, 62, 52, 62, 62, 62, 62, 62, 62, 62, 54, 62, 60, 62, 26, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 52, 44, 37, 21, 75, 72, 77, 28, 31, 27, 32, 17, 20, 17, 36, 8, 66, 6, 73, 67, 115, 110, 70, 3, 10, 65, 76, 79, 83, 86, 124, 99, 25, 11, 3, 74, 76, 89, 105, 109, 120, 75, 20, 12, 7, 6, 71, 68, 68, 73, 85, 66, 30, 31, 32, 11, 9, 66, 73, 88, 11, 59, 55, 54, 43, 26, 3, 72, 69, 77, 62, 124, 122, 108, 124, 122, 103, 119, 115, 100, 115, 114, 119, 94, 99, 94, 109, 112, 88, 79, 81, 84, 80, 71, 64, 67, 7, 69, 69, 69, 68, 66, 76, 0, 2, 66, 0, 6, 64, 65, 1, 12, 65, 70, 74, 69, 71, 3, 25, 16, 27, 26, 22, 22, 15, 22, 22, 4, 13, 22, 66, 12, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 52, 62, 62, 62, 62, 62, 62, 62, 61, 62, 48, 36, 25, 8, 65, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 42, 32, 18, 15, 14, 17, 40, 40, 41, 41, 34, 38, 28, 20, 23, 12, 70, 8, 71, 83, 30, 13, 8, 84, 80, 76, 80, 78, 71, 73, 82, 70, 66, 3, 65, 65, 4, 4, 62, 62, 62, 62, 60, 56, 49, 32, 4, 70, 39, 28, 22, 12, 7, 1, 64, 66, 81, 80, 75, 71, 77, 71, 67, 69, 75, 65, 2, 6, 3, 4, 9, 5, 62, 62, 62, 62, 60, 56, 49, 32, 4 }, { 62, 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 36, 59, 61, 55, 22, 114, 65, 70, 77, 11, 12, 69, 8, 26, 39, 58, 54, 22, 72, 121, 99, 75, 70, 77, 11, 69, 88, 11, 19, 68, 81, 94, 4, 75, 86, 99, 7, 73, 84, 4, 80, 85, 94, 65, 76, 70, 85, 71, 2, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 47, 12, 80, 122, 88, 93, 77, 93, 99, 94, 115, 96, 107, 99, 122, 80, 88, 93, 91, 24, 65, 82, 122, 72, 102, 89, 123, 8, 7, 8, 1, 11, 68, 73, 86, 101, 89, 87, 90, 75, 85, 107, 102, 76, 109, 96, 117, 118, 120, 121, 65, 77, 70, 71, 4, 9, 69, 74, 88, 114, 74, 39, 38, 121, 9, 3, 5, 4, 3, 66, 80, 72, 64, 11, 6, 2, 67, 68, 6, 70, 5, 2, 21, 36, 30, 20, 15, 19, 12, 17, 23, 65, 24, 20, 28, 89, 18, 34, 31, 62, 62, 46, 60, 62, 62, 59, 62, 62, 62, 62, 62, 62, 50, 62, 62, 62, 62, 62, 62, 62, 52, 62, 58, 62, 24, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 50, 42, 35, 19, 75, 72, 78, 27, 30, 26, 31, 16, 19, 16, 34, 7, 66, 5, 74, 68, 114, 109, 69, 3, 10, 65, 75, 78, 82, 85, 122, 98, 25, 11, 3, 73, 75, 88, 103, 107, 118, 74, 21, 13, 8, 7, 70, 68, 68, 73, 84, 66, 31, 31, 31, 11, 9, 66, 73, 88, 11, 59, 54, 53, 42, 26, 3, 72, 69, 77, 62, 123, 121, 107, 122, 120, 102, 117, 113, 99, 113, 112, 117, 93, 98, 94, 108, 110, 88, 79, 81, 83, 80, 71, 64, 67, 6, 69, 69, 69, 68, 66, 75, 0, 2, 66, 0, 6, 64, 65, 1, 11, 65, 70, 74, 69, 70, 2, 24, 16, 26, 25, 21, 21, 15, 21, 21, 4, 13, 21, 66, 11, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 50, 62, 62, 62, 62, 62, 62, 62, 59, 59, 46, 34, 24, 7, 66, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 40, 30, 16, 14, 13, 15, 39, 39, 39, 39, 32, 36, 26, 19, 21, 11, 71, 7, 72, 84, 28, 12, 7, 84, 80, 75, 80, 77, 70, 73, 81, 69, 65, 3, 65, 64, 4, 4, 62, 62, 62, 62, 57, 52, 45, 28, 1, 70, 39, 28, 22, 12, 8, 1, 64, 66, 81, 80, 75, 71, 77, 70, 66, 69, 75, 65, 2, 6, 3, 5, 9, 5, 62, 62, 62, 62, 57, 52, 45, 28, 1 }, { 62, 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 34, 57, 60, 55, 22, 112, 65, 69, 76, 11, 12, 69, 8, 25, 38, 56, 51, 20, 73, 118, 98, 75, 69, 76, 11, 70, 87, 11, 19, 68, 81, 94, 4, 75, 86, 99, 7, 73, 83, 4, 80, 84, 94, 65, 76, 70, 85, 71, 2, 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 46, 11, 80, 119, 87, 92, 76, 91, 97, 92, 113, 94, 106, 98, 120, 80, 88, 92, 91, 24, 65, 81, 120, 72, 101, 89, 121, 8, 6, 7, 1, 11, 68, 72, 86, 100, 88, 87, 89, 74, 84, 105, 100, 76, 108, 95, 112, 113, 117, 118, 65, 77, 70, 70, 4, 9, 68, 73, 87, 112, 74, 37, 36, 118, 9, 3, 5, 4, 3, 65, 79, 71, 64, 11, 6, 2, 67, 67, 5, 70, 5, 1, 21, 35, 30, 20, 15, 19, 12, 17, 22, 65, 23, 19, 28, 89, 18, 34, 31, 62, 62, 45, 58, 62, 62, 57, 62, 62, 62, 62, 62, 61, 48, 62, 62, 62, 62, 62, 62, 60, 50, 62, 56, 62, 22, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 48, 40, 34, 17, 75, 72, 78, 26, 29, 25, 30, 15, 18, 15, 32, 6, 67, 4, 75, 68, 114, 107, 68, 4, 10, 65, 74, 78, 82, 85, 120, 97, 25, 11, 4, 72, 74, 87, 102, 106, 116, 73, 21, 13, 8, 7, 69, 67, 68, 73, 84, 66, 31, 31, 30, 11, 9, 66, 73, 87, 11, 58, 54, 52, 41, 26, 3, 72, 69, 77, 62, 122, 119, 106, 121, 119, 101, 115, 111, 98, 112, 110, 115, 93, 97, 93, 107, 108, 87, 79, 81, 83, 79, 71, 64, 67, 6, 69, 69, 70, 67, 65, 74, 0, 2, 65, 0, 6, 64, 65, 1, 11, 65, 70, 74, 69, 70, 1, 23, 16, 25, 24, 20, 21, 15, 20, 20, 4, 13, 20, 66, 10, 62, 62, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 48, 62, 62, 62, 62, 62, 62, 62, 57, 57, 44, 32, 22, 6, 67, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 60, 38, 28, 15, 13, 12, 14, 37, 37, 37, 37, 31, 34, 24, 18, 20, 10, 72, 6, 73, 85, 27, 11, 6, 84, 79, 75, 79, 76, 69, 73, 81, 69, 65, 3, 64, 0, 4, 4, 62, 62, 62, 59, 54, 48, 41, 24, 65, 70, 39, 28, 22, 12, 8, 2, 64, 66, 80, 80, 75, 70, 76, 69, 65, 69, 74, 65, 2, 6, 3, 5, 9, 5, 62, 62, 62, 59, 54, 48, 41, 24, 65 }, { 62, 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 33, 55, 59, 55, 21, 110, 65, 69, 75, 10, 11, 69, 7, 23, 37, 53, 47, 17, 75, 115, 97, 75, 69, 75, 10, 70, 86, 11, 18, 68, 80, 93, 4, 75, 86, 99, 7, 73, 83, 4, 80, 84, 93, 65, 76, 70, 85, 70, 2, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 45, 11, 80, 117, 86, 91, 74, 89, 96, 91, 112, 93, 104, 97, 118, 80, 87, 92, 91, 24, 65, 80, 118, 72, 101, 88, 119, 8, 6, 7, 0, 11, 68, 72, 85, 99, 87, 86, 88, 74, 84, 104, 99, 75, 107, 94, 107, 109, 114, 115, 65, 76, 70, 70, 4, 9, 68, 73, 87, 110, 74, 35, 34, 116, 9, 4, 5, 4, 3, 65, 77, 70, 0, 10, 6, 2, 66, 67, 4, 70, 5, 1, 20, 34, 29, 19, 14, 19, 12, 17, 21, 65, 22, 18, 27, 89, 17, 33, 30, 62, 62, 44, 56, 62, 62, 55, 62, 62, 62, 62, 62, 59, 46, 59, 62, 62, 62, 62, 62, 57, 48, 62, 54, 62, 21, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 55, 46, 38, 32, 15, 75, 72, 79, 25, 28, 24, 28, 14, 16, 14, 31, 5, 67, 3, 75, 69, 113, 106, 67, 4, 10, 64, 74, 77, 81, 84, 118, 95, 25, 12, 4, 72, 73, 86, 100, 104, 115, 73, 22, 14, 9, 8, 68, 67, 68, 72, 83, 66, 32, 31, 30, 10, 9, 66, 73, 87, 11, 58, 53, 51, 40, 26, 3, 71, 69, 77, 62, 120, 118, 105, 119, 117, 100, 114, 110, 97, 110, 109, 113, 92, 96, 93, 106, 107, 87, 79, 81, 82, 79, 71, 65, 67, 5, 69, 69, 70, 67, 65, 73, 0, 2, 65, 0, 6, 64, 65, 1, 10, 65, 70, 74, 69, 69, 0, 22, 16, 24, 24, 19, 20, 15, 19, 19, 4, 13, 19, 66, 9, 62, 62, 60, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 46, 62, 62, 62, 62, 62, 62, 62, 54, 54, 42, 30, 21, 5, 67, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 57, 36, 26, 13, 12, 12, 12, 36, 36, 36, 35, 29, 32, 23, 17, 18, 9, 73, 4, 74, 85, 25, 9, 4, 83, 79, 74, 79, 75, 68, 73, 80, 68, 64, 3, 64, 1, 4, 4, 62, 62, 62, 56, 50, 44, 36, 20, 68, 69, 39, 28, 22, 12, 9, 2, 64, 66, 80, 80, 75, 70, 76, 69, 64, 69, 74, 64, 3, 6, 3, 6, 9, 5, 62, 62, 62, 56, 50, 44, 36, 20, 68 }, { 62, 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 32, 53, 58, 55, 21, 108, 65, 69, 74, 10, 11, 69, 6, 21, 36, 51, 44, 15, 77, 112, 96, 74, 69, 74, 10, 70, 85, 11, 18, 68, 80, 92, 4, 75, 86, 99, 7, 73, 83, 4, 80, 83, 93, 65, 76, 70, 85, 70, 2, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 44, 10, 80, 114, 85, 90, 72, 87, 94, 89, 110, 91, 103, 96, 115, 80, 87, 91, 90, 24, 65, 79, 116, 72, 100, 88, 117, 8, 5, 6, 0, 11, 68, 71, 85, 98, 86, 86, 87, 73, 83, 102, 97, 74, 105, 93, 102, 105, 111, 112, 64, 76, 69, 69, 4, 9, 67, 73, 86, 108, 74, 33, 32, 113, 9, 4, 5, 4, 3, 64, 76, 69, 0, 10, 6, 2, 66, 66, 3, 69, 5, 0, 20, 33, 29, 19, 14, 19, 12, 17, 20, 64, 21, 18, 27, 89, 17, 32, 29, 62, 62, 43, 55, 62, 62, 53, 62, 62, 62, 62, 61, 57, 44, 57, 62, 60, 62, 62, 62, 55, 46, 62, 52, 62, 19, 62, 62, 62, 62, 62, 62, 62, 62, 61, 58, 53, 44, 37, 30, 13, 75, 72, 79, 24, 27, 23, 27, 13, 15, 13, 29, 4, 68, 2, 76, 70, 112, 104, 66, 5, 10, 64, 73, 77, 81, 83, 116, 94, 25, 12, 5, 71, 72, 85, 99, 103, 113, 72, 23, 15, 10, 8, 67, 66, 67, 72, 83, 66, 32, 31, 29, 10, 9, 66, 73, 86, 11, 57, 52, 50, 39, 26, 3, 71, 69, 76, 62, 119, 116, 103, 117, 116, 99, 112, 108, 96, 108, 107, 111, 91, 95, 92, 105, 105, 87, 79, 80, 82, 78, 71, 65, 67, 5, 69, 69, 71, 66, 65, 72, 0, 2, 65, 0, 6, 64, 65, 1, 10, 65, 70, 74, 69, 69, 64, 21, 16, 23, 23, 19, 19, 15, 19, 18, 4, 13, 18, 66, 8, 62, 62, 59, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 44, 62, 62, 62, 62, 62, 62, 61, 52, 52, 40, 29, 19, 5, 68, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 55, 54, 34, 24, 12, 12, 11, 10, 35, 34, 34, 33, 27, 30, 21, 16, 17, 8, 73, 3, 75, 86, 24, 8, 3, 83, 79, 73, 78, 74, 67, 72, 79, 68, 64, 3, 0, 2, 4, 4, 62, 62, 59, 53, 47, 40, 32, 16, 71, 69, 39, 28, 22, 12, 9, 2, 0, 65, 79, 80, 75, 69, 76, 68, 0, 69, 74, 64, 3, 6, 4, 6, 9, 5, 62, 62, 59, 53, 47, 40, 32, 16, 71 }, { 62, 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 30, 51, 57, 55, 21, 107, 65, 68, 74, 10, 10, 68, 6, 20, 34, 48, 40, 12, 78, 110, 95, 74, 68, 74, 10, 71, 85, 11, 17, 68, 80, 92, 4, 75, 85, 98, 7, 72, 82, 4, 79, 83, 93, 65, 76, 70, 85, 70, 2, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 44, 10, 80, 112, 84, 89, 71, 84, 93, 88, 108, 90, 102, 95, 113, 80, 87, 91, 90, 24, 65, 78, 113, 72, 99, 87, 115, 7, 5, 6, 64, 12, 68, 71, 84, 98, 86, 85, 86, 73, 82, 101, 96, 74, 104, 92, 97, 100, 108, 109, 64, 76, 69, 69, 4, 9, 67, 72, 86, 106, 73, 31, 30, 110, 9, 4, 5, 4, 4, 64, 74, 68, 0, 10, 6, 2, 65, 65, 2, 69, 5, 0, 19, 32, 28, 19, 13, 19, 12, 17, 18, 64, 20, 17, 26, 89, 17, 32, 29, 62, 62, 42, 53, 62, 62, 51, 62, 62, 62, 62, 57, 55, 43, 55, 62, 58, 62, 62, 62, 52, 44, 62, 50, 62, 17, 62, 62, 62, 62, 62, 62, 62, 62, 59, 56, 50, 42, 35, 29, 12, 75, 72, 80, 23, 26, 22, 26, 12, 14, 12, 27, 3, 68, 1, 77, 70, 112, 103, 65, 5, 10, 64, 72, 76, 80, 83, 114, 93, 26, 12, 5, 70, 71, 84, 97, 101, 111, 71, 23, 15, 10, 9, 66, 66, 67, 72, 82, 66, 33, 31, 28, 10, 9, 66, 73, 86, 10, 57, 52, 49, 38, 25, 3, 71, 69, 76, 62, 118, 115, 102, 116, 114, 98, 110, 106, 95, 107, 105, 109, 91, 94, 92, 104, 103, 86, 79, 80, 81, 78, 71, 65, 67, 4, 69, 69, 71, 66, 64, 71, 0, 2, 64, 1, 6, 0, 64, 1, 9, 65, 70, 74, 69, 68, 65, 20, 16, 22, 22, 18, 19, 15, 18, 18, 4, 12, 16, 67, 7, 62, 62, 58, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 42, 62, 62, 62, 62, 62, 62, 58, 50, 49, 38, 27, 18, 4, 69, 62, 62, 62, 62, 62, 62, 62, 62, 61, 58, 52, 51, 32, 23, 10, 11, 10, 9, 33, 33, 32, 31, 26, 28, 19, 15, 15, 7, 74, 2, 76, 87, 22, 7, 2, 83, 78, 73, 78, 73, 66, 72, 79, 67, 0, 3, 0, 3, 4, 4, 62, 62, 57, 50, 44, 36, 28, 12, 74, 69, 39, 28, 22, 12, 10, 3, 0, 65, 79, 79, 74, 69, 75, 67, 1, 68, 73, 64, 3, 6, 4, 7, 9, 5, 62, 62, 57, 50, 44, 36, 28, 12, 74 }, { 62, 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 29, 49, 56, 55, 21, 105, 65, 68, 73, 9, 10, 68, 5, 18, 33, 46, 37, 10, 80, 107, 94, 74, 68, 73, 9, 71, 84, 11, 17, 68, 79, 91, 4, 75, 85, 98, 7, 72, 82, 4, 79, 82, 92, 65, 76, 70, 85, 69, 2, 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 43, 9, 80, 109, 83, 88, 69, 82, 91, 86, 107, 88, 100, 94, 111, 80, 86, 90, 90, 24, 65, 77, 111, 72, 98, 87, 113, 7, 4, 5, 64, 12, 68, 70, 84, 97, 85, 85, 85, 72, 81, 99, 94, 73, 103, 91, 92, 96, 105, 106, 64, 75, 69, 68, 4, 9, 66, 72, 85, 104, 73, 29, 28, 107, 9, 5, 5, 4, 4, 0, 73, 67, 1, 9, 6, 2, 65, 65, 1, 69, 5, 64, 19, 31, 28, 18, 13, 19, 12, 17, 17, 64, 19, 16, 26, 89, 17, 31, 28, 60, 62, 41, 51, 62, 62, 49, 62, 61, 62, 62, 54, 53, 41, 52, 62, 55, 62, 62, 62, 49, 42, 62, 48, 62, 16, 62, 62, 62, 62, 62, 62, 62, 62, 57, 53, 48, 40, 33, 27, 10, 75, 72, 80, 22, 25, 21, 24, 11, 13, 11, 26, 2, 69, 0, 77, 71, 111, 101, 64, 6, 10, 0, 72, 76, 80, 82, 112, 91, 26, 13, 6, 70, 70, 83, 96, 100, 109, 71, 24, 16, 11, 9, 65, 65, 67, 71, 82, 66, 33, 31, 28, 9, 9, 66, 73, 85, 10, 56, 51, 48, 37, 25, 3, 70, 69, 76, 62, 116, 113, 101, 114, 113, 97, 109, 105, 94, 105, 104, 107, 90, 93, 91, 103, 101, 86, 79, 80, 81, 77, 71, 66, 67, 4, 69, 69, 72, 65, 64, 70, 0, 2, 64, 1, 6, 0, 64, 1, 9, 65, 70, 74, 69, 68, 66, 19, 16, 21, 22, 17, 18, 15, 17, 17, 4, 12, 15, 67, 6, 61, 62, 57, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 40, 62, 62, 62, 62, 62, 62, 56, 48, 47, 36, 25, 16, 3, 69, 62, 62, 62, 62, 62, 62, 62, 62, 59, 56, 50, 48, 30, 21, 9, 10, 10, 7, 32, 31, 31, 29, 24, 26, 18, 14, 14, 6, 75, 0, 77, 87, 21, 5, 0, 82, 78, 72, 77, 72, 65, 72, 78, 67, 0, 3, 1, 4, 4, 4, 62, 62, 54, 47, 40, 32, 24, 8, 77, 68, 39, 28, 22, 12, 10, 3, 0, 65, 78, 79, 74, 68, 75, 66, 2, 68, 73, 0, 4, 6, 4, 7, 9, 5, 62, 62, 54, 47, 40, 32, 24, 8, 77 }, { 62, 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 27, 46, 55, 55, 20, 103, 66, 68, 72, 9, 9, 68, 4, 16, 32, 43, 33, 7, 82, 104, 93, 74, 68, 72, 9, 72, 83, 11, 16, 68, 79, 91, 3, 76, 85, 98, 7, 72, 82, 4, 79, 82, 92, 65, 76, 70, 85, 69, 2, 22, 0, 0, 0, 78, 88, 97, 68, 16, 0, 42, 9, 81, 107, 82, 87, 68, 80, 90, 85, 105, 87, 99, 93, 109, 80, 86, 90, 90, 24, 65, 76, 109, 72, 98, 86, 111, 7, 4, 5, 65, 12, 68, 70, 83, 96, 84, 84, 85, 72, 81, 98, 93, 73, 102, 90, 88, 92, 102, 104, 64, 75, 69, 68, 3, 9, 66, 72, 85, 102, 73, 27, 26, 105, 9, 5, 5, 4, 4, 0, 71, 67, 1, 9, 5, 2, 64, 64, 64, 69, 5, 64, 18, 29, 27, 18, 12, 19, 12, 16, 16, 64, 18, 15, 25, 89, 16, 30, 27, 58, 62, 39, 49, 62, 62, 46, 62, 59, 62, 62, 50, 51, 39, 50, 62, 53, 62, 62, 62, 46, 40, 62, 46, 62, 14, 62, 62, 62, 62, 62, 62, 62, 60, 55, 51, 46, 38, 31, 25, 8, 75, 73, 81, 21, 23, 20, 23, 10, 11, 9, 24, 1, 69, 64, 78, 72, 111, 100, 0, 6, 10, 0, 71, 75, 79, 82, 110, 90, 26, 13, 6, 69, 69, 82, 94, 98, 108, 70, 24, 16, 11, 10, 64, 65, 67, 71, 81, 67, 34, 31, 27, 9, 9, 66, 73, 85, 10, 56, 50, 47, 36, 25, 3, 70, 69, 76, 62, 115, 112, 100, 113, 111, 96, 107, 103, 93, 104, 102, 105, 90, 93, 91, 102, 100, 86, 79, 80, 80, 77, 71, 66, 67, 3, 69, 69, 72, 65, 64, 69, 0, 1, 64, 1, 5, 0, 64, 1, 8, 65, 70, 74, 69, 67, 67, 18, 16, 19, 21, 16, 17, 14, 16, 16, 4, 12, 14, 67, 4, 60, 60, 56, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 38, 62, 62, 62, 62, 62, 62, 53, 45, 44, 34, 23, 15, 2, 70, 62, 62, 62, 62, 62, 62, 62, 62, 56, 53, 47, 45, 28, 19, 7, 9, 9, 5, 30, 30, 29, 27, 22, 24, 16, 12, 12, 4, 76, 64, 78, 88, 19, 4, 64, 82, 78, 72, 77, 71, 64, 72, 78, 66, 1, 3, 1, 4, 4, 3, 62, 60, 51, 44, 37, 28, 19, 3, 80, 68, 39, 28, 22, 12, 11, 3, 0, 65, 78, 79, 74, 68, 75, 66, 2, 68, 73, 0, 4, 6, 4, 8, 9, 4, 62, 60, 51, 44, 37, 28, 19, 3, 80 }, { 62, 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 26, 44, 54, 56, 20, 101, 66, 67, 71, 9, 8, 68, 4, 15, 31, 41, 29, 4, 83, 101, 92, 73, 67, 71, 9, 72, 82, 11, 16, 67, 79, 90, 3, 76, 85, 98, 7, 72, 81, 4, 79, 82, 92, 65, 76, 70, 84, 69, 2, 22, 0, 0, 0, 77, 88, 97, 68, 15, 0, 41, 9, 81, 105, 80, 86, 66, 78, 88, 84, 103, 85, 98, 91, 106, 80, 86, 90, 89, 24, 65, 75, 107, 71, 97, 85, 109, 7, 4, 5, 65, 12, 68, 70, 82, 95, 83, 83, 84, 71, 80, 97, 91, 72, 100, 89, 83, 87, 98, 101, 0, 75, 68, 67, 3, 9, 66, 71, 84, 99, 73, 25, 25, 102, 9, 5, 5, 4, 4, 1, 69, 66, 1, 9, 5, 2, 0, 0, 65, 68, 5, 64, 17, 28, 26, 18, 11, 19, 12, 16, 15, 0, 17, 15, 24, 89, 16, 30, 27, 56, 62, 38, 48, 62, 62, 44, 60, 57, 62, 62, 47, 49, 37, 48, 62, 51, 62, 62, 62, 44, 38, 62, 44, 62, 12, 62, 62, 62, 62, 62, 62, 60, 58, 53, 49, 44, 37, 30, 24, 6, 75, 73, 81, 21, 22, 19, 22, 9, 10, 8, 22, 0, 69, 65, 79, 72, 110, 99, 1, 6, 10, 0, 70, 74, 78, 81, 107, 89, 26, 13, 6, 68, 68, 81, 92, 96, 106, 69, 25, 17, 12, 11, 0, 65, 66, 71, 80, 67, 35, 31, 26, 9, 10, 65, 73, 84, 10, 56, 50, 46, 35, 25, 3, 70, 69, 75, 62, 114, 111, 98, 111, 109, 95, 105, 101, 92, 102, 100, 103, 89, 92, 90, 101, 98, 85, 78, 79, 79, 76, 71, 66, 67, 2, 69, 69, 72, 65, 0, 68, 1, 1, 0, 1, 5, 0, 64, 1, 7, 65, 69, 73, 69, 66, 67, 17, 16, 18, 20, 16, 17, 14, 16, 15, 4, 12, 13, 67, 3, 59, 59, 56, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 36, 62, 62, 62, 62, 62, 62, 50, 43, 42, 33, 22, 14, 2, 71, 62, 62, 62, 62, 62, 62, 62, 62, 54, 51, 45, 43, 26, 17, 5, 9, 8, 4, 29, 29, 27, 25, 21, 23, 14, 11, 10, 3, 76, 65, 78, 89, 17, 3, 65, 82, 77, 71, 77, 70, 1, 71, 77, 65, 2, 3, 2, 5, 4, 3, 62, 58, 49, 41, 34, 24, 15, 64, 83, 68, 39, 28, 23, 13, 12, 4, 1, 64, 78, 79, 74, 68, 74, 65, 3, 68, 72, 0, 4, 6, 5, 9, 9, 4, 62, 58, 49, 41, 34, 24, 15, 64, 83 }, { 62, 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 25, 42, 53, 56, 20, 99, 66, 67, 70, 8, 8, 68, 3, 13, 30, 38, 26, 2, 85, 98, 91, 73, 67, 70, 8, 72, 81, 11, 15, 67, 78, 89, 3, 76, 85, 98, 7, 72, 81, 4, 79, 81, 91, 65, 76, 70, 84, 68, 2, 22, 0, 0, 0, 77, 88, 97, 67, 14, 0, 40, 8, 81, 102, 79, 85, 64, 76, 87, 82, 102, 84, 96, 90, 104, 80, 85, 89, 89, 24, 65, 74, 105, 71, 96, 85, 107, 7, 3, 4, 66, 12, 68, 69, 82, 94, 82, 83, 83, 71, 79, 95, 90, 71, 99, 88, 78, 83, 95, 98, 0, 74, 68, 67, 3, 9, 65, 71, 84, 97, 73, 23, 23, 99, 9, 6, 5, 4, 4, 1, 68, 65, 2, 8, 5, 2, 0, 0, 66, 68, 5, 65, 17, 27, 26, 17, 11, 19, 12, 16, 14, 0, 16, 14, 24, 89, 16, 29, 26, 54, 62, 37, 46, 62, 62, 42, 57, 55, 62, 62, 43, 47, 35, 45, 61, 48, 62, 62, 62, 41, 36, 58, 42, 62, 11, 62, 62, 62, 62, 62, 60, 58, 56, 51, 46, 42, 35, 28, 22, 4, 75, 73, 82, 20, 21, 18, 20, 8, 9, 7, 21, 64, 70, 66, 79, 73, 109, 97, 2, 7, 10, 1, 70, 74, 78, 80, 105, 87, 26, 14, 7, 68, 67, 80, 91, 95, 104, 69, 26, 18, 13, 11, 1, 64, 66, 70, 80, 67, 35, 31, 26, 8, 10, 65, 73, 84, 10, 55, 49, 45, 34, 25, 3, 69, 69, 75, 62, 112, 109, 97, 109, 108, 94, 104, 100, 91, 100, 99, 101, 88, 91, 90, 100, 96, 85, 78, 79, 79, 76, 71, 67, 67, 2, 69, 69, 73, 64, 0, 67, 1, 1, 0, 1, 5, 0, 64, 1, 7, 65, 69, 73, 69, 66, 68, 16, 16, 17, 20, 15, 16, 14, 15, 14, 4, 12, 12, 67, 2, 58, 58, 55, 59, 60, 62, 62, 62, 62, 62, 62, 62, 62, 55, 34, 62, 62, 62, 62, 62, 62, 48, 41, 39, 31, 20, 12, 1, 71, 62, 62, 62, 62, 62, 62, 62, 62, 52, 48, 43, 40, 24, 15, 4, 8, 8, 2, 28, 27, 26, 23, 19, 21, 13, 10, 9, 2, 77, 67, 79, 89, 16, 1, 67, 81, 77, 70, 76, 69, 2, 71, 76, 65, 2, 3, 2, 6, 4, 3, 62, 56, 46, 38, 30, 20, 11, 68, 86, 67, 39, 28, 23, 13, 12, 4, 1, 64, 77, 79, 74, 67, 74, 64, 4, 68, 72, 1, 5, 6, 5, 9, 9, 4, 62, 56, 46, 38, 30, 20, 11, 68, 86 }, { 62, 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 23, 40, 52, 56, 20, 98, 66, 66, 70, 8, 7, 67, 3, 12, 28, 36, 22, 64, 86, 96, 90, 73, 66, 70, 8, 73, 81, 11, 15, 67, 78, 89, 3, 76, 84, 97, 7, 71, 80, 4, 78, 81, 91, 65, 76, 70, 84, 68, 2, 22, 0, 0, 0, 76, 88, 97, 67, 14, 64, 40, 8, 81, 100, 78, 84, 0, 73, 85, 81, 100, 82, 95, 89, 102, 80, 85, 89, 89, 24, 65, 73, 102, 71, 95, 84, 105, 6, 3, 4, 66, 13, 68, 69, 81, 94, 82, 82, 82, 70, 78, 94, 88, 71, 98, 87, 73, 78, 92, 95, 0, 74, 68, 66, 3, 9, 65, 70, 83, 95, 72, 21, 21, 96, 9, 6, 5, 4, 5, 2, 66, 64, 2, 8, 5, 2, 1, 1, 67, 68, 5, 65, 16, 26, 25, 17, 10, 19, 12, 16, 12, 0, 15, 13, 23, 89, 16, 29, 26, 52, 62, 36, 44, 61, 62, 40, 55, 53, 62, 62, 40, 45, 34, 43, 57, 46, 62, 62, 62, 38, 34, 55, 40, 62, 9, 62, 62, 62, 62, 62, 58, 55, 54, 49, 44, 39, 33, 26, 21, 3, 75, 73, 82, 19, 20, 17, 19, 7, 8, 6, 19, 65, 70, 67, 80, 73, 109, 96, 3, 7, 10, 1, 69, 73, 77, 80, 103, 86, 27, 14, 7, 67, 66, 79, 89, 93, 102, 68, 26, 18, 13, 12, 2, 64, 66, 70, 79, 67, 36, 31, 25, 8, 10, 65, 73, 83, 9, 55, 49, 44, 33, 24, 3, 69, 69, 75, 62, 111, 108, 96, 108, 106, 93, 102, 98, 90, 99, 97, 99, 88, 90, 89, 99, 94, 84, 78, 79, 78, 75, 71, 67, 67, 1, 69, 69, 73, 64, 1, 66, 1, 1, 1, 2, 5, 1, 0, 1, 6, 65, 69, 73, 69, 65, 69, 15, 16, 16, 19, 14, 16, 14, 14, 14, 4, 11, 10, 68, 1, 56, 57, 54, 58, 58, 62, 62, 62, 62, 62, 62, 62, 62, 52, 32, 62, 62, 62, 62, 62, 62, 45, 39, 37, 29, 18, 11, 0, 72, 62, 62, 62, 62, 62, 62, 60, 59, 49, 46, 40, 37, 22, 14, 2, 7, 7, 1, 26, 26, 24, 21, 18, 19, 11, 9, 7, 1, 78, 68, 80, 90, 14, 0, 68, 81, 76, 70, 76, 68, 3, 71, 76, 64, 3, 3, 3, 7, 4, 3, 62, 54, 44, 35, 27, 16, 7, 72, 89, 67, 39, 28, 23, 13, 13, 5, 1, 64, 77, 78, 73, 67, 73, 0, 5, 67, 71, 1, 5, 6, 5, 10, 9, 4, 62, 54, 44, 35, 27, 16, 7, 72, 89 }, { 62, 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 22, 38, 51, 56, 19, 96, 66, 66, 69, 8, 7, 67, 2, 10, 27, 33, 19, 66, 88, 93, 89, 73, 66, 69, 8, 73, 80, 11, 14, 67, 78, 88, 3, 76, 84, 97, 7, 71, 80, 4, 78, 80, 91, 65, 76, 70, 84, 68, 2, 22, 0, 0, 0, 76, 89, 97, 66, 13, 64, 39, 7, 81, 97, 77, 83, 2, 71, 84, 79, 98, 81, 94, 88, 100, 80, 85, 88, 89, 24, 65, 72, 100, 71, 95, 84, 103, 6, 2, 3, 67, 13, 68, 68, 81, 93, 81, 82, 81, 70, 78, 92, 87, 70, 97, 86, 68, 74, 89, 92, 0, 74, 68, 66, 3, 9, 64, 70, 83, 93, 72, 19, 19, 94, 9, 6, 5, 4, 5, 2, 65, 0, 2, 8, 5, 2, 1, 2, 68, 68, 5, 66, 16, 25, 25, 17, 10, 19, 12, 16, 11, 0, 14, 12, 23, 89, 15, 28, 25, 50, 62, 35, 42, 59, 60, 38, 52, 51, 62, 62, 36, 43, 32, 41, 54, 43, 58, 62, 62, 35, 32, 51, 38, 62, 7, 62, 62, 62, 62, 62, 56, 53, 52, 47, 42, 37, 31, 24, 19, 1, 75, 73, 83, 18, 19, 16, 18, 6, 6, 5, 17, 66, 71, 68, 81, 74, 108, 94, 4, 8, 10, 1, 68, 73, 77, 79, 101, 85, 27, 14, 8, 66, 65, 78, 88, 92, 101, 67, 27, 19, 14, 12, 3, 0, 66, 70, 79, 67, 36, 31, 24, 8, 10, 65, 73, 83, 9, 54, 48, 43, 32, 24, 3, 69, 69, 75, 62, 110, 106, 95, 106, 105, 92, 100, 96, 89, 97, 95, 97, 87, 89, 89, 98, 93, 84, 78, 79, 78, 75, 71, 67, 67, 1, 69, 69, 74, 0, 1, 65, 1, 1, 1, 2, 5, 1, 0, 1, 6, 65, 69, 73, 69, 65, 70, 14, 16, 15, 18, 13, 15, 14, 13, 13, 4, 11, 9, 68, 0, 55, 56, 53, 56, 56, 62, 61, 62, 62, 62, 62, 62, 61, 50, 30, 62, 62, 62, 62, 62, 59, 43, 36, 34, 27, 16, 9, 64, 73, 62, 62, 62, 62, 62, 62, 57, 56, 47, 43, 38, 34, 20, 12, 1, 6, 6, 64, 25, 24, 22, 19, 16, 17, 9, 8, 6, 0, 79, 69, 81, 91, 13, 64, 69, 81, 76, 69, 75, 67, 4, 71, 75, 64, 3, 3, 3, 8, 4, 3, 61, 52, 41, 32, 24, 12, 2, 76, 92, 67, 39, 28, 23, 13, 13, 5, 1, 64, 76, 78, 73, 66, 73, 0, 6, 67, 71, 1, 5, 6, 5, 10, 9, 4, 61, 52, 41, 32, 24, 12, 2, 76, 92 }, { 62, 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 21, 36, 50, 56, 19, 94, 66, 66, 68, 7, 6, 67, 1, 8, 26, 31, 15, 69, 90, 90, 88, 72, 66, 68, 7, 73, 79, 11, 14, 67, 77, 87, 3, 76, 84, 97, 7, 71, 80, 4, 78, 80, 90, 65, 76, 70, 84, 67, 2, 22, 0, 0, 0, 75, 89, 97, 66, 12, 64, 38, 7, 81, 95, 76, 82, 4, 69, 82, 78, 97, 79, 92, 87, 97, 80, 84, 88, 88, 24, 65, 71, 98, 71, 94, 83, 101, 6, 2, 3, 67, 13, 68, 68, 80, 92, 80, 81, 80, 69, 77, 91, 85, 69, 95, 85, 0, 70, 86, 89, 1, 73, 67, 65, 3, 9, 64, 70, 82, 91, 72, 17, 17, 91, 9, 7, 5, 4, 5, 3, 0, 1, 3, 7, 5, 2, 2, 2, 69, 67, 5, 66, 15, 24, 24, 16, 9, 19, 12, 16, 10, 1, 13, 12, 22, 89, 15, 27, 24, 48, 62, 34, 41, 57, 58, 36, 50, 49, 62, 62, 33, 41, 30, 38, 51, 41, 55, 62, 62, 33, 30, 48, 36, 62, 6, 62, 62, 62, 61, 60, 54, 51, 50, 45, 39, 35, 29, 23, 17, 64, 75, 73, 83, 17, 18, 15, 16, 5, 5, 4, 16, 67, 71, 69, 81, 75, 107, 93, 5, 8, 10, 2, 68, 72, 76, 78, 99, 83, 27, 15, 8, 66, 64, 77, 86, 90, 99, 67, 28, 20, 15, 13, 4, 0, 65, 69, 78, 67, 37, 31, 24, 7, 10, 65, 73, 82, 9, 54, 47, 42, 31, 24, 3, 68, 69, 74, 62, 108, 105, 93, 104, 103, 91, 99, 95, 88, 95, 94, 95, 86, 88, 88, 97, 91, 84, 78, 78, 77, 74, 71, 68, 67, 0, 69, 69, 74, 0, 1, 64, 1, 1, 1, 2, 5, 1, 0, 1, 5, 65, 69, 73, 69, 64, 71, 13, 16, 14, 18, 13, 14, 14, 13, 12, 4, 11, 8, 68, 64, 54, 55, 52, 54, 54, 62, 59, 61, 62, 59, 62, 62, 58, 47, 28, 62, 62, 62, 62, 59, 56, 40, 34, 32, 25, 15, 8, 64, 73, 62, 62, 62, 62, 59, 59, 55, 53, 45, 41, 36, 31, 18, 10, 64, 6, 6, 66, 24, 23, 21, 17, 14, 15, 8, 7, 4, 64, 79, 71, 82, 91, 11, 66, 71, 80, 76, 68, 75, 66, 5, 70, 74, 0, 4, 3, 4, 9, 4, 3, 60, 50, 38, 29, 20, 8, 65, 80, 95, 66, 39, 28, 23, 13, 14, 5, 2, 0, 76, 78, 73, 66, 73, 1, 7, 67, 71, 2, 6, 6, 6, 11, 9, 4, 60, 50, 38, 29, 20, 8, 65, 80, 95 }, { 61, 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 19, 34, 49, 56, 19, 92, 66, 65, 67, 7, 6, 67, 1, 7, 25, 28, 12, 71, 91, 87, 87, 72, 65, 67, 7, 74, 78, 11, 13, 67, 77, 87, 3, 76, 84, 97, 7, 71, 79, 4, 78, 79, 90, 65, 76, 70, 84, 67, 2, 22, 0, 0, 0, 75, 89, 97, 65, 11, 64, 37, 6, 81, 92, 75, 81, 5, 67, 81, 76, 95, 78, 91, 86, 95, 80, 84, 87, 88, 24, 65, 70, 96, 71, 93, 83, 99, 6, 1, 2, 68, 13, 68, 67, 80, 91, 79, 81, 79, 69, 76, 89, 84, 69, 94, 84, 5, 65, 83, 86, 1, 73, 67, 65, 3, 9, 0, 69, 82, 89, 72, 15, 15, 88, 9, 7, 5, 4, 5, 3, 1, 2, 3, 7, 5, 2, 2, 3, 70, 67, 5, 67, 15, 23, 24, 16, 9, 19, 12, 16, 9, 1, 12, 11, 22, 89, 15, 27, 24, 46, 61, 33, 39, 55, 55, 34, 47, 47, 62, 62, 29, 39, 28, 36, 48, 38, 52, 61, 62, 30, 28, 44, 34, 62, 4, 60, 62, 60, 58, 57, 52, 49, 48, 43, 37, 33, 27, 21, 16, 66, 75, 73, 84, 16, 17, 14, 15, 4, 4, 3, 14, 68, 72, 70, 82, 75, 107, 91, 6, 9, 10, 2, 67, 72, 76, 78, 97, 82, 27, 15, 9, 65, 0, 76, 85, 89, 97, 66, 28, 20, 15, 13, 5, 1, 65, 69, 78, 67, 37, 31, 23, 7, 10, 65, 73, 82, 9, 53, 47, 41, 30, 24, 3, 68, 69, 74, 62, 107, 103, 92, 103, 102, 90, 97, 93, 87, 94, 92, 93, 86, 87, 88, 96, 89, 83, 78, 78, 77, 74, 71, 68, 67, 0, 69, 69, 75, 1, 2, 0, 1, 1, 2, 2, 5, 1, 0, 1, 5, 65, 69, 73, 69, 64, 72, 12, 16, 13, 17, 12, 14, 14, 12, 11, 4, 11, 7, 68, 65, 53, 54, 51, 53, 52, 60, 57, 59, 59, 57, 62, 60, 55, 45, 26, 62, 62, 62, 62, 55, 53, 38, 32, 29, 23, 13, 6, 65, 74, 62, 62, 62, 60, 56, 57, 52, 50, 42, 38, 33, 28, 16, 8, 65, 5, 5, 67, 22, 21, 19, 15, 13, 13, 6, 6, 3, 65, 80, 72, 83, 92, 10, 67, 72, 80, 75, 68, 74, 65, 6, 70, 74, 0, 4, 3, 4, 10, 4, 3, 59, 48, 36, 26, 17, 4, 69, 84, 98, 66, 39, 28, 23, 13, 14, 6, 2, 0, 75, 78, 73, 65, 72, 2, 8, 67, 70, 2, 6, 6, 6, 11, 9, 4, 59, 48, 36, 26, 17, 4, 69, 84, 98 }, { 60, 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 18, 32, 48, 56, 19, 90, 66, 65, 66, 7, 5, 67, 0, 5, 24, 26, 8, 74, 93, 84, 86, 72, 65, 66, 7, 74, 77, 11, 13, 67, 77, 86, 3, 76, 84, 97, 7, 71, 79, 4, 78, 79, 90, 65, 76, 70, 84, 67, 2, 22, 0, 0, 0, 74, 89, 97, 65, 10, 64, 36, 6, 81, 90, 74, 80, 7, 65, 79, 75, 93, 76, 90, 85, 93, 80, 84, 87, 88, 24, 65, 69, 94, 71, 92, 82, 97, 6, 1, 2, 68, 13, 68, 67, 79, 90, 78, 80, 78, 68, 75, 88, 82, 68, 93, 83, 10, 2, 80, 83, 1, 73, 67, 64, 3, 9, 0, 69, 81, 87, 72, 13, 13, 85, 9, 7, 5, 4, 5, 4, 3, 3, 3, 7, 5, 2, 3, 4, 71, 67, 5, 67, 14, 22, 23, 16, 8, 19, 12, 16, 8, 1, 11, 10, 21, 89, 15, 26, 23, 44, 58, 32, 37, 53, 53, 32, 45, 45, 62, 62, 26, 37, 26, 34, 45, 36, 49, 57, 62, 27, 26, 41, 32, 62, 2, 58, 62, 58, 56, 55, 50, 47, 46, 41, 35, 31, 25, 19, 14, 68, 75, 73, 84, 15, 16, 13, 14, 3, 3, 2, 12, 69, 72, 71, 83, 76, 106, 90, 7, 9, 10, 2, 66, 71, 75, 77, 95, 81, 27, 15, 9, 64, 1, 75, 83, 87, 95, 65, 29, 21, 16, 14, 6, 1, 65, 69, 77, 67, 38, 31, 22, 7, 10, 65, 73, 81, 9, 53, 46, 40, 29, 24, 3, 68, 69, 74, 62, 106, 102, 91, 101, 100, 89, 95, 91, 86, 92, 90, 91, 85, 86, 87, 95, 87, 83, 78, 78, 76, 73, 71, 68, 67, 64, 69, 69, 75, 1, 2, 1, 1, 1, 2, 2, 5, 1, 0, 1, 4, 65, 69, 73, 69, 0, 73, 11, 16, 12, 16, 11, 13, 14, 11, 10, 4, 11, 6, 68, 66, 52, 53, 50, 51, 50, 58, 55, 57, 57, 54, 61, 57, 52, 42, 24, 62, 62, 62, 62, 52, 50, 35, 30, 27, 21, 11, 5, 66, 75, 62, 62, 62, 58, 53, 54, 50, 47, 40, 36, 31, 25, 14, 6, 67, 4, 4, 69, 21, 20, 17, 13, 11, 11, 4, 5, 1, 66, 81, 73, 84, 93, 8, 68, 73, 80, 75, 67, 74, 64, 7, 70, 73, 1, 5, 3, 5, 11, 4, 3, 58, 46, 33, 23, 14, 0, 73, 88, 101, 66, 39, 28, 23, 13, 15, 6, 2, 0, 75, 78, 73, 65, 72, 3, 9, 67, 70, 2, 6, 6, 6, 12, 9, 4, 58, 46, 33, 23, 14, 0, 73, 88, 101 }, { 58, 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 16, 29, 47, 56, 18, 89, 67, 65, 66, 6, 4, 67, 64, 3, 22, 23, 4, 77, 95, 82, 86, 72, 65, 66, 6, 75, 77, 11, 12, 67, 77, 86, 2, 77, 84, 97, 6, 71, 79, 4, 78, 79, 90, 65, 76, 71, 84, 67, 2, 22, 0, 0, 0, 74, 90, 97, 65, 9, 65, 35, 5, 82, 88, 73, 79, 8, 0, 78, 74, 92, 75, 89, 84, 91, 80, 84, 87, 88, 24, 65, 69, 92, 71, 92, 82, 96, 5, 0, 1, 69, 13, 68, 67, 79, 90, 78, 80, 78, 68, 75, 87, 81, 68, 92, 82, 14, 6, 77, 81, 1, 73, 67, 64, 2, 9, 0, 69, 81, 85, 72, 11, 11, 83, 9, 7, 5, 4, 5, 4, 4, 3, 3, 6, 4, 2, 3, 4, 73, 67, 5, 68, 13, 20, 22, 15, 7, 19, 12, 15, 6, 1, 10, 9, 20, 89, 14, 25, 22, 41, 54, 30, 35, 50, 50, 29, 42, 43, 55, 62, 22, 34, 24, 31, 41, 33, 45, 52, 59, 24, 24, 37, 30, 62, 0, 55, 59, 55, 53, 52, 47, 44, 43, 39, 32, 28, 23, 17, 12, 70, 75, 74, 85, 14, 14, 11, 12, 1, 1, 0, 10, 70, 73, 72, 84, 77, 106, 89, 7, 9, 10, 2, 66, 71, 75, 77, 93, 80, 27, 15, 9, 64, 1, 74, 82, 86, 94, 65, 29, 21, 16, 14, 7, 1, 65, 69, 77, 68, 38, 30, 21, 6, 10, 65, 73, 81, 8, 52, 45, 38, 28, 23, 3, 68, 69, 74, 62, 105, 101, 90, 100, 99, 88, 94, 90, 85, 91, 89, 89, 85, 86, 87, 94, 86, 83, 78, 78, 76, 73, 71, 69, 68, 65, 69, 70, 76, 1, 2, 2, 1, 0, 2, 2, 4, 1, 0, 1, 3, 65, 69, 73, 69, 0, 74, 10, 16, 10, 15, 10, 12, 13, 10, 9, 4, 10, 4, 69, 68, 50, 51, 49, 49, 48, 55, 52, 54, 54, 51, 58, 54, 48, 39, 22, 62, 62, 61, 60, 48, 46, 32, 27, 24, 19, 9, 3, 67, 76, 59, 60, 60, 55, 50, 51, 47, 43, 37, 33, 28, 22, 12, 4, 69, 3, 3, 71, 19, 18, 15, 10, 9, 9, 2, 3, 64, 68, 82, 75, 85, 94, 6, 70, 75, 80, 75, 67, 74, 0, 8, 70, 73, 1, 5, 3, 5, 11, 4, 2, 56, 44, 30, 19, 10, 67, 78, 93, 104, 66, 39, 28, 23, 13, 15, 6, 2, 0, 75, 78, 73, 65, 72, 3, 9, 67, 70, 2, 6, 6, 6, 12, 8, 3, 56, 44, 30, 19, 10, 67, 78, 93, 104 }, { 57, 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 15, 27, 46, 57, 18, 87, 67, 64, 65, 6, 4, 66, 64, 2, 21, 21, 1, 79, 96, 79, 85, 71, 64, 65, 6, 75, 76, 11, 12, 66, 76, 85, 2, 77, 83, 96, 6, 70, 78, 4, 77, 78, 89, 64, 75, 71, 83, 66, 2, 22, 0, 0, 0, 73, 90, 97, 64, 9, 65, 35, 5, 82, 85, 71, 77, 10, 3, 76, 72, 90, 73, 87, 82, 88, 80, 83, 86, 87, 24, 65, 68, 89, 70, 91, 81, 94, 5, 0, 1, 69, 14, 68, 66, 78, 89, 77, 79, 77, 67, 74, 85, 79, 67, 90, 80, 19, 11, 73, 78, 2, 72, 66, 0, 2, 10, 1, 68, 80, 82, 71, 9, 10, 80, 9, 8, 5, 5, 6, 5, 6, 4, 4, 6, 4, 2, 4, 5, 74, 66, 5, 68, 13, 19, 22, 15, 7, 19, 12, 15, 5, 2, 10, 9, 20, 89, 14, 25, 22, 39, 51, 29, 34, 48, 48, 27, 40, 41, 49, 62, 19, 32, 23, 29, 38, 31, 42, 48, 55, 22, 22, 34, 28, 62, 64, 53, 57, 53, 51, 50, 45, 42, 41, 37, 30, 26, 22, 16, 11, 71, 75, 74, 85, 14, 13, 10, 11, 0, 0, 64, 9, 71, 73, 73, 84, 77, 105, 87, 8, 10, 10, 3, 65, 70, 74, 76, 90, 78, 28, 16, 10, 0, 2, 72, 80, 84, 92, 64, 30, 22, 17, 15, 8, 2, 64, 68, 76, 68, 39, 30, 21, 6, 11, 64, 73, 80, 8, 52, 45, 37, 27, 23, 4, 67, 68, 73, 62, 103, 99, 88, 98, 97, 86, 92, 88, 83, 89, 87, 86, 84, 85, 86, 92, 84, 82, 77, 77, 75, 72, 70, 69, 68, 65, 69, 70, 76, 2, 3, 3, 2, 0, 3, 3, 4, 2, 1, 1, 3, 64, 68, 72, 68, 1, 74, 9, 16, 9, 15, 10, 12, 13, 10, 9, 4, 10, 3, 69, 69, 49, 50, 49, 48, 47, 53, 50, 52, 52, 49, 56, 52, 45, 37, 20, 61, 60, 57, 56, 45, 43, 30, 25, 22, 18, 8, 2, 67, 76, 57, 58, 58, 53, 48, 49, 45, 40, 35, 31, 26, 20, 11, 3, 70, 3, 3, 72, 18, 17, 14, 8, 8, 8, 1, 2, 65, 69, 82, 76, 85, 94, 5, 71, 76, 79, 74, 66, 73, 2, 10, 69, 72, 2, 6, 4, 6, 12, 4, 2, 55, 42, 28, 16, 7, 71, 82, 97, 106, 65, 39, 29, 24, 14, 16, 7, 3, 1, 74, 77, 72, 64, 71, 4, 10, 66, 69, 3, 7, 6, 7, 13, 8, 3, 55, 42, 28, 16, 7, 71, 82, 97, 106 }, { 56, 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 14, 25, 45, 57, 18, 85, 67, 64, 64, 6, 3, 66, 65, 0, 20, 18, 66, 82, 98, 76, 84, 71, 64, 64, 6, 75, 75, 11, 11, 66, 76, 84, 2, 77, 83, 96, 6, 70, 78, 4, 77, 78, 89, 64, 75, 71, 83, 66, 2, 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 34, 5, 82, 83, 70, 76, 12, 5, 75, 71, 88, 72, 86, 81, 86, 80, 83, 86, 87, 24, 65, 67, 87, 70, 90, 80, 92, 5, 0, 1, 70, 14, 68, 66, 77, 88, 76, 78, 76, 67, 73, 84, 78, 66, 89, 79, 24, 15, 70, 75, 2, 72, 66, 0, 2, 10, 1, 68, 80, 80, 71, 7, 8, 77, 9, 8, 5, 5, 6, 5, 8, 5, 4, 6, 4, 2, 5, 6, 75, 66, 5, 68, 12, 18, 21, 15, 6, 19, 12, 15, 4, 2, 9, 8, 19, 89, 14, 24, 21, 37, 48, 28, 32, 46, 46, 25, 38, 39, 43, 62, 15, 30, 21, 27, 35, 29, 39, 44, 51, 19, 20, 31, 26, 62, 66, 51, 55, 51, 49, 48, 43, 40, 39, 35, 28, 24, 20, 14, 9, 73, 75, 74, 86, 13, 12, 9, 10, 64, 64, 65, 7, 72, 73, 74, 85, 78, 104, 86, 9, 10, 10, 3, 64, 69, 73, 75, 88, 77, 28, 16, 10, 1, 3, 71, 78, 82, 90, 0, 31, 23, 18, 16, 9, 2, 64, 68, 75, 68, 40, 30, 20, 6, 11, 64, 73, 80, 8, 52, 44, 36, 26, 23, 4, 67, 68, 73, 62, 102, 98, 87, 96, 95, 85, 90, 86, 82, 87, 85, 84, 83, 84, 86, 91, 82, 82, 77, 77, 74, 72, 70, 69, 68, 66, 69, 70, 76, 2, 3, 4, 2, 0, 3, 3, 4, 2, 1, 1, 2, 64, 68, 72, 68, 2, 75, 8, 16, 8, 14, 9, 11, 13, 9, 8, 4, 10, 2, 69, 70, 48, 49, 48, 46, 45, 51, 48, 50, 50, 46, 53, 49, 42, 34, 18, 57, 56, 53, 51, 42, 40, 27, 23, 19, 16, 6, 1, 68, 77, 55, 56, 55, 51, 45, 46, 42, 37, 33, 28, 24, 17, 9, 1, 72, 2, 2, 74, 17, 16, 12, 6, 6, 6, 64, 1, 67, 70, 83, 77, 86, 95, 3, 72, 77, 79, 74, 65, 73, 3, 11, 69, 71, 3, 7, 4, 6, 13, 4, 2, 54, 40, 25, 13, 4, 75, 86, 101, 109, 65, 39, 29, 24, 14, 17, 7, 3, 1, 74, 77, 72, 64, 71, 5, 11, 66, 69, 3, 7, 6, 7, 14, 8, 3, 54, 40, 25, 13, 4, 75, 86, 101, 109 }, { 55, 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 12, 23, 44, 57, 18, 83, 67, 0, 0, 6, 3, 66, 65, 64, 19, 16, 69, 84, 99, 73, 83, 71, 0, 0, 6, 76, 74, 11, 11, 66, 76, 84, 2, 77, 83, 96, 6, 70, 77, 4, 77, 77, 89, 64, 75, 71, 83, 66, 2, 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 33, 4, 82, 80, 69, 75, 13, 7, 73, 69, 86, 70, 85, 80, 84, 80, 83, 85, 87, 24, 65, 66, 85, 70, 89, 80, 90, 5, 64, 0, 70, 14, 68, 65, 77, 87, 75, 78, 75, 66, 72, 82, 76, 66, 88, 78, 29, 20, 67, 72, 2, 72, 66, 1, 2, 10, 2, 67, 79, 78, 71, 5, 6, 74, 9, 8, 5, 5, 6, 6, 9, 6, 4, 6, 4, 2, 5, 7, 76, 66, 5, 69, 12, 17, 21, 15, 6, 19, 12, 15, 3, 2, 8, 7, 19, 89, 14, 24, 21, 35, 45, 27, 30, 44, 43, 23, 35, 37, 36, 62, 12, 28, 19, 25, 32, 26, 36, 40, 47, 16, 18, 27, 24, 62, 68, 49, 53, 49, 46, 45, 41, 38, 37, 33, 26, 22, 18, 12, 8, 75, 75, 74, 86, 12, 11, 8, 9, 65, 65, 66, 5, 73, 74, 75, 86, 78, 104, 84, 10, 11, 10, 3, 0, 69, 73, 75, 86, 76, 28, 16, 11, 2, 4, 70, 77, 81, 88, 1, 31, 23, 18, 16, 10, 3, 64, 68, 75, 68, 40, 30, 19, 6, 11, 64, 73, 79, 8, 51, 44, 35, 25, 23, 4, 67, 68, 73, 62, 101, 96, 86, 95, 94, 84, 88, 84, 81, 86, 83, 82, 83, 83, 85, 90, 80, 81, 77, 77, 74, 71, 70, 69, 68, 66, 69, 70, 77, 3, 4, 5, 2, 0, 4, 3, 4, 2, 1, 1, 2, 64, 68, 72, 68, 2, 76, 7, 16, 7, 13, 8, 11, 13, 8, 7, 4, 10, 1, 69, 71, 47, 48, 47, 45, 43, 49, 46, 48, 47, 44, 50, 46, 39, 32, 16, 53, 52, 49, 46, 38, 37, 25, 21, 17, 14, 4, 64, 69, 78, 53, 53, 53, 48, 42, 44, 40, 34, 30, 26, 21, 14, 7, 64, 73, 1, 1, 75, 15, 14, 10, 4, 5, 4, 66, 0, 68, 71, 84, 78, 87, 96, 2, 73, 78, 79, 73, 65, 72, 4, 12, 69, 71, 3, 7, 4, 7, 14, 4, 2, 53, 38, 23, 10, 1, 79, 90, 105, 112, 65, 39, 29, 24, 14, 17, 8, 3, 1, 73, 77, 72, 0, 70, 6, 12, 66, 68, 3, 7, 6, 7, 14, 8, 3, 53, 38, 23, 10, 1, 79, 90, 105, 112 }, { 53, 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 11, 21, 43, 57, 17, 81, 67, 0, 1, 5, 2, 66, 66, 66, 18, 13, 73, 87, 101, 70, 82, 71, 0, 1, 5, 76, 73, 11, 10, 66, 75, 83, 2, 77, 83, 96, 6, 70, 77, 4, 77, 77, 88, 64, 75, 71, 83, 65, 2, 22, 0, 0, 0, 71, 91, 97, 0, 6, 65, 32, 4, 82, 78, 68, 74, 15, 9, 72, 68, 85, 69, 83, 79, 82, 80, 82, 85, 87, 24, 65, 65, 83, 70, 89, 79, 88, 5, 64, 0, 71, 14, 68, 65, 76, 86, 74, 77, 74, 66, 72, 81, 75, 65, 87, 77, 34, 24, 64, 69, 2, 71, 66, 1, 2, 10, 2, 67, 79, 76, 71, 3, 4, 72, 9, 9, 5, 5, 6, 6, 11, 7, 5, 5, 4, 2, 6, 7, 77, 66, 5, 69, 11, 16, 20, 14, 5, 19, 12, 15, 2, 2, 7, 6, 18, 89, 13, 23, 20, 33, 41, 26, 28, 42, 41, 21, 33, 35, 30, 62, 8, 26, 17, 22, 29, 24, 32, 35, 43, 13, 16, 24, 22, 62, 69, 47, 51, 46, 44, 43, 39, 36, 35, 31, 23, 20, 16, 10, 6, 77, 75, 74, 87, 11, 10, 7, 7, 66, 67, 67, 4, 74, 74, 76, 86, 79, 103, 83, 11, 11, 10, 4, 0, 68, 72, 74, 84, 74, 28, 17, 11, 2, 5, 69, 75, 79, 87, 1, 32, 24, 19, 17, 11, 3, 64, 67, 74, 68, 41, 30, 19, 5, 11, 64, 73, 79, 8, 51, 43, 34, 24, 23, 4, 66, 68, 73, 62, 99, 95, 85, 93, 92, 83, 87, 83, 80, 84, 82, 80, 82, 82, 85, 89, 79, 81, 77, 77, 73, 71, 70, 70, 68, 67, 69, 70, 77, 3, 4, 6, 2, 0, 4, 3, 4, 2, 1, 1, 1, 64, 68, 72, 68, 3, 77, 6, 16, 6, 13, 7, 10, 13, 7, 6, 4, 10, 0, 69, 72, 46, 47, 46, 43, 41, 47, 44, 45, 45, 41, 47, 43, 36, 29, 14, 48, 48, 45, 41, 35, 33, 22, 18, 14, 12, 2, 65, 70, 78, 50, 51, 50, 46, 39, 41, 37, 31, 28, 23, 19, 11, 5, 66, 75, 0, 1, 77, 14, 13, 9, 2, 3, 2, 67, 64, 70, 72, 85, 80, 88, 96, 0, 75, 80, 78, 73, 64, 72, 5, 13, 69, 70, 4, 8, 4, 7, 15, 4, 2, 52, 36, 20, 7, 66, 83, 95, 109, 115, 64, 39, 29, 24, 14, 18, 8, 3, 1, 73, 77, 72, 0, 70, 6, 13, 66, 68, 4, 8, 6, 7, 15, 8, 3, 52, 36, 20, 7, 66, 83, 95, 109, 115 }, { 52, 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 10, 19, 42, 57, 17, 79, 67, 0, 2, 5, 2, 66, 67, 68, 17, 11, 76, 89, 103, 67, 81, 70, 0, 2, 5, 76, 72, 11, 10, 66, 75, 82, 2, 77, 83, 96, 6, 70, 77, 4, 77, 76, 88, 64, 75, 71, 83, 65, 2, 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 31, 3, 82, 75, 67, 73, 17, 11, 70, 66, 83, 67, 82, 78, 79, 80, 82, 84, 86, 24, 65, 64, 81, 70, 88, 79, 86, 5, 65, 64, 71, 14, 68, 64, 76, 85, 73, 77, 73, 65, 71, 79, 73, 64, 85, 76, 39, 28, 2, 66, 3, 71, 65, 2, 2, 10, 3, 67, 78, 74, 71, 1, 2, 69, 9, 9, 5, 5, 6, 7, 12, 8, 5, 5, 4, 2, 6, 8, 78, 65, 5, 70, 11, 15, 20, 14, 5, 19, 12, 15, 1, 3, 6, 6, 18, 89, 13, 22, 19, 31, 38, 25, 27, 40, 39, 19, 30, 33, 24, 62, 5, 24, 15, 20, 26, 21, 29, 31, 39, 11, 14, 20, 20, 62, 71, 45, 49, 44, 42, 41, 37, 34, 33, 29, 21, 18, 14, 9, 4, 79, 75, 74, 87, 10, 9, 6, 6, 67, 68, 68, 2, 75, 75, 77, 87, 80, 102, 81, 12, 12, 10, 4, 1, 68, 72, 73, 82, 73, 28, 17, 12, 3, 6, 68, 74, 78, 85, 2, 33, 25, 20, 17, 12, 4, 0, 67, 74, 68, 41, 30, 18, 5, 11, 64, 73, 78, 8, 50, 42, 33, 23, 23, 4, 66, 68, 72, 62, 98, 93, 83, 91, 91, 82, 85, 81, 79, 82, 80, 78, 81, 81, 84, 88, 77, 81, 77, 76, 73, 70, 70, 70, 68, 67, 69, 70, 78, 4, 4, 7, 2, 0, 4, 3, 4, 2, 1, 1, 1, 64, 68, 72, 68, 3, 78, 5, 16, 5, 12, 7, 9, 13, 7, 5, 4, 10, 64, 69, 73, 45, 46, 45, 41, 39, 45, 42, 43, 42, 38, 44, 40, 33, 27, 12, 44, 44, 41, 36, 32, 30, 20, 16, 12, 10, 1, 67, 70, 79, 48, 48, 48, 44, 36, 38, 35, 28, 26, 21, 17, 8, 3, 68, 76, 0, 0, 79, 13, 11, 7, 0, 1, 0, 69, 65, 71, 73, 85, 81, 89, 97, 64, 76, 81, 78, 73, 0, 71, 6, 14, 68, 69, 4, 8, 4, 8, 16, 4, 2, 51, 34, 17, 4, 69, 87, 99, 113, 118, 64, 39, 29, 24, 14, 18, 8, 4, 2, 72, 77, 72, 1, 70, 7, 14, 66, 68, 4, 8, 6, 8, 15, 8, 3, 51, 34, 17, 4, 69, 87, 99, 113, 118 }, { 51, 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 8, 17, 41, 57, 17, 78, 67, 1, 2, 5, 1, 65, 67, 69, 15, 8, 80, 92, 104, 65, 80, 70, 1, 2, 5, 77, 72, 11, 9, 66, 75, 82, 2, 77, 82, 95, 6, 69, 76, 4, 76, 76, 88, 64, 75, 71, 83, 65, 2, 22, 0, 0, 0, 70, 91, 97, 1, 5, 66, 31, 3, 82, 73, 66, 72, 18, 14, 69, 65, 81, 66, 81, 77, 77, 80, 82, 84, 86, 24, 65, 0, 78, 70, 87, 78, 84, 4, 65, 64, 72, 15, 68, 64, 75, 85, 73, 76, 72, 65, 70, 78, 72, 64, 84, 75, 44, 33, 5, 0, 3, 71, 65, 2, 2, 10, 3, 66, 78, 72, 70, 64, 0, 66, 9, 9, 5, 5, 7, 7, 14, 9, 5, 5, 4, 2, 7, 9, 79, 65, 5, 70, 10, 14, 19, 14, 4, 19, 12, 15, 64, 3, 5, 5, 17, 89, 13, 22, 19, 29, 35, 24, 25, 37, 36, 17, 28, 31, 17, 62, 1, 22, 14, 18, 22, 19, 26, 27, 34, 8, 12, 17, 18, 62, 73, 43, 47, 42, 39, 38, 35, 31, 31, 27, 19, 15, 12, 7, 3, 80, 75, 74, 88, 9, 8, 5, 5, 68, 69, 69, 0, 76, 75, 78, 88, 80, 102, 80, 13, 12, 10, 4, 2, 67, 71, 73, 80, 72, 29, 17, 12, 4, 7, 67, 72, 76, 83, 3, 33, 25, 20, 18, 13, 4, 0, 67, 73, 68, 42, 30, 17, 5, 11, 64, 73, 78, 7, 50, 42, 32, 22, 22, 4, 66, 68, 72, 62, 97, 92, 82, 90, 89, 81, 83, 79, 78, 81, 78, 76, 81, 80, 84, 87, 75, 80, 77, 76, 72, 70, 70, 70, 68, 68, 69, 70, 78, 4, 5, 8, 2, 0, 5, 4, 4, 3, 2, 1, 0, 64, 68, 72, 68, 4, 79, 4, 16, 4, 11, 6, 9, 13, 6, 5, 4, 9, 66, 70, 74, 43, 45, 44, 40, 37, 43, 40, 41, 40, 36, 41, 38, 30, 24, 10, 40, 40, 37, 32, 28, 27, 17, 14, 9, 8, 64, 68, 71, 80, 46, 46, 45, 41, 33, 36, 32, 25, 23, 18, 14, 5, 1, 69, 78, 64, 64, 80, 11, 10, 5, 65, 0, 65, 71, 66, 73, 74, 86, 82, 90, 98, 66, 77, 82, 78, 72, 0, 71, 7, 15, 68, 69, 5, 9, 4, 8, 17, 4, 2, 50, 32, 15, 1, 72, 91, 103, 117, 121, 64, 39, 29, 24, 14, 19, 9, 4, 2, 72, 76, 71, 1, 69, 8, 15, 65, 67, 4, 8, 6, 8, 16, 8, 3, 50, 32, 15, 1, 72, 91, 103, 117, 121 }, { 50, 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 7, 15, 40, 57, 17, 76, 67, 1, 3, 4, 1, 65, 68, 71, 14, 6, 83, 94, 106, 1, 79, 70, 1, 3, 4, 77, 71, 11, 9, 66, 74, 81, 2, 77, 82, 95, 6, 69, 76, 4, 76, 75, 87, 64, 75, 71, 83, 64, 2, 22, 0, 0, 0, 70, 91, 97, 2, 4, 66, 30, 2, 82, 70, 65, 71, 20, 16, 67, 0, 80, 64, 79, 76, 75, 80, 81, 83, 86, 24, 65, 1, 76, 70, 86, 78, 82, 4, 66, 65, 72, 15, 68, 0, 75, 84, 72, 76, 71, 64, 69, 76, 70, 0, 83, 74, 49, 37, 8, 3, 3, 70, 65, 3, 2, 10, 4, 66, 77, 70, 70, 66, 65, 0, 9, 10, 5, 5, 7, 8, 15, 10, 6, 4, 4, 2, 7, 9, 80, 65, 5, 71, 10, 13, 19, 13, 4, 19, 12, 15, 65, 3, 4, 4, 17, 89, 13, 21, 18, 27, 32, 23, 23, 35, 34, 15, 25, 29, 11, 62, 65, 20, 12, 15, 19, 16, 23, 22, 30, 5, 10, 13, 16, 62, 74, 41, 45, 40, 37, 36, 33, 29, 29, 25, 16, 13, 10, 5, 1, 82, 75, 74, 88, 8, 7, 4, 3, 69, 70, 70, 64, 77, 76, 79, 88, 81, 101, 78, 14, 13, 10, 5, 2, 67, 71, 72, 78, 70, 29, 18, 13, 4, 8, 66, 71, 75, 81, 3, 34, 26, 21, 18, 14, 5, 0, 66, 73, 68, 42, 30, 17, 4, 11, 64, 73, 77, 7, 49, 41, 31, 21, 22, 4, 65, 68, 72, 62, 95, 90, 81, 88, 88, 80, 82, 78, 77, 79, 77, 74, 80, 79, 83, 86, 73, 80, 77, 76, 72, 69, 70, 71, 68, 68, 69, 70, 79, 5, 5, 9, 2, 0, 5, 4, 4, 3, 2, 1, 0, 64, 68, 72, 68, 4, 80, 3, 16, 3, 11, 5, 8, 13, 5, 4, 4, 9, 67, 70, 75, 42, 44, 43, 38, 35, 41, 38, 38, 37, 33, 38, 35, 27, 22, 8, 35, 36, 33, 27, 25, 24, 15, 12, 7, 6, 66, 70, 72, 80, 43, 43, 43, 39, 30, 33, 30, 22, 21, 16, 12, 2, 64, 71, 79, 65, 64, 82, 10, 8, 4, 67, 65, 67, 72, 67, 74, 75, 87, 84, 91, 98, 67, 79, 84, 77, 72, 1, 70, 8, 16, 68, 68, 5, 9, 4, 9, 18, 4, 2, 49, 30, 12, 65, 76, 95, 107, 121, 124, 0, 39, 29, 24, 14, 19, 9, 4, 2, 71, 76, 71, 2, 69, 9, 16, 65, 67, 5, 9, 6, 8, 16, 8, 3, 49, 30, 12, 65, 76, 95, 107, 121, 124 }, { 48, 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 5, 12, 39, 57, 16, 74, 68, 1, 4, 4, 0, 65, 69, 73, 13, 3, 87, 97, 108, 4, 78, 70, 1, 4, 4, 78, 70, 11, 8, 66, 74, 81, 1, 78, 82, 95, 6, 69, 76, 4, 76, 75, 87, 64, 75, 71, 83, 64, 2, 22, 0, 0, 0, 69, 92, 97, 2, 3, 66, 29, 2, 83, 68, 64, 70, 21, 18, 66, 1, 78, 0, 78, 75, 73, 80, 81, 83, 86, 24, 65, 2, 74, 70, 86, 77, 80, 4, 66, 65, 73, 15, 68, 0, 74, 83, 71, 75, 71, 64, 69, 75, 69, 0, 82, 73, 53, 41, 11, 5, 3, 70, 65, 3, 1, 10, 4, 66, 77, 68, 70, 68, 67, 2, 9, 10, 5, 5, 7, 8, 17, 10, 6, 4, 3, 2, 8, 10, 82, 65, 5, 71, 9, 11, 18, 13, 3, 19, 12, 14, 66, 3, 3, 3, 16, 89, 12, 20, 17, 25, 28, 21, 21, 33, 31, 12, 23, 27, 4, 62, 69, 18, 10, 13, 16, 14, 19, 18, 26, 2, 8, 10, 14, 62, 76, 39, 42, 37, 34, 33, 30, 27, 26, 23, 14, 11, 8, 3, 64, 84, 75, 75, 89, 7, 5, 3, 2, 70, 72, 72, 66, 78, 76, 80, 89, 82, 101, 77, 15, 13, 10, 5, 3, 66, 70, 72, 76, 69, 29, 18, 13, 5, 9, 65, 69, 73, 80, 4, 34, 26, 21, 19, 15, 5, 0, 66, 72, 69, 43, 30, 16, 4, 11, 64, 73, 77, 7, 49, 40, 30, 20, 22, 4, 65, 68, 72, 62, 94, 89, 80, 87, 86, 79, 80, 76, 76, 78, 75, 72, 80, 79, 83, 85, 72, 80, 77, 76, 71, 69, 70, 71, 68, 69, 69, 70, 79, 5, 5, 10, 2, 64, 5, 4, 3, 3, 2, 1, 64, 64, 68, 72, 68, 5, 81, 2, 16, 1, 10, 4, 7, 12, 4, 3, 4, 9, 68, 70, 77, 41, 42, 42, 36, 33, 39, 36, 36, 35, 30, 35, 32, 24, 19, 6, 31, 32, 28, 22, 21, 20, 12, 9, 4, 4, 68, 71, 73, 81, 41, 41, 40, 36, 27, 30, 27, 19, 18, 13, 9, 64, 66, 73, 81, 66, 65, 84, 8, 7, 2, 69, 67, 69, 74, 69, 76, 77, 88, 85, 92, 99, 69, 80, 85, 77, 72, 1, 70, 9, 17, 68, 68, 6, 10, 4, 9, 18, 4, 1, 48, 28, 9, 68, 79, 99, 112, 126, 126, 0, 39, 29, 24, 14, 20, 9, 4, 2, 71, 76, 71, 2, 69, 9, 16, 65, 67, 5, 9, 6, 8, 17, 8, 2, 48, 28, 9, 68, 79, 99, 112, 126, 126 }, { 47, 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 4, 10, 38, 58, 16, 72, 68, 2, 5, 4, 64, 65, 69, 74, 12, 1, 91, 100, 109, 7, 77, 69, 2, 5, 4, 78, 69, 11, 8, 65, 74, 80, 1, 78, 82, 95, 6, 69, 75, 4, 76, 75, 87, 64, 75, 71, 82, 64, 2, 22, 0, 0, 0, 68, 92, 97, 2, 2, 66, 28, 2, 83, 66, 1, 69, 23, 20, 64, 2, 76, 2, 77, 73, 70, 80, 81, 83, 85, 24, 65, 3, 72, 69, 85, 76, 78, 4, 66, 65, 73, 15, 68, 0, 73, 82, 70, 74, 70, 0, 68, 74, 67, 1, 80, 72, 58, 46, 15, 8, 4, 70, 64, 4, 1, 10, 4, 65, 76, 65, 70, 70, 68, 5, 9, 10, 5, 5, 7, 9, 19, 11, 6, 4, 3, 2, 9, 11, 83, 64, 5, 71, 8, 10, 17, 13, 2, 19, 12, 14, 67, 4, 2, 3, 15, 89, 12, 20, 17, 23, 25, 20, 20, 31, 29, 10, 21, 25, 65, 62, 72, 16, 8, 11, 13, 12, 16, 14, 22, 0, 6, 7, 12, 62, 78, 37, 40, 35, 32, 31, 28, 25, 24, 21, 12, 9, 7, 2, 65, 86, 75, 75, 89, 7, 4, 2, 1, 71, 73, 73, 68, 79, 76, 81, 90, 82, 100, 76, 16, 13, 10, 5, 4, 65, 69, 71, 73, 68, 29, 18, 13, 6, 10, 64, 67, 71, 78, 5, 35, 27, 22, 20, 16, 5, 1, 66, 71, 69, 44, 30, 15, 4, 12, 0, 73, 76, 7, 49, 40, 29, 19, 22, 4, 65, 68, 71, 62, 93, 88, 78, 85, 84, 78, 78, 74, 75, 76, 73, 70, 79, 78, 82, 84, 70, 79, 76, 75, 70, 68, 70, 71, 68, 70, 69, 70, 79, 5, 6, 11, 3, 64, 6, 4, 3, 3, 2, 1, 65, 64, 67, 71, 68, 6, 81, 1, 16, 0, 9, 4, 7, 12, 4, 2, 4, 9, 69, 70, 78, 40, 41, 42, 35, 31, 37, 34, 34, 33, 28, 32, 29, 21, 16, 4, 27, 28, 24, 17, 18, 17, 9, 7, 2, 3, 69, 72, 73, 82, 39, 39, 38, 34, 25, 28, 25, 16, 16, 11, 7, 66, 68, 75, 83, 66, 66, 85, 7, 6, 0, 71, 68, 70, 76, 70, 78, 78, 88, 86, 92, 100, 71, 81, 86, 77, 71, 2, 70, 10, 19, 67, 67, 7, 11, 4, 10, 19, 4, 1, 47, 26, 7, 71, 82, 103, 116, 126, 126, 0, 39, 29, 25, 15, 21, 10, 5, 3, 71, 76, 71, 2, 68, 10, 17, 65, 66, 5, 9, 6, 9, 18, 8, 2, 47, 26, 7, 71, 82, 103, 116, 126, 126 }, { 46, 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 3, 8, 37, 58, 16, 70, 68, 2, 6, 3, 64, 65, 70, 76, 11, 65, 94, 102, 111, 10, 76, 69, 2, 6, 3, 78, 68, 11, 7, 65, 73, 79, 1, 78, 82, 95, 6, 69, 75, 4, 76, 74, 86, 64, 75, 71, 82, 0, 2, 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 27, 1, 83, 0, 2, 68, 25, 22, 0, 4, 75, 3, 75, 72, 68, 80, 80, 82, 85, 24, 65, 4, 70, 69, 84, 76, 76, 4, 67, 66, 74, 15, 68, 1, 73, 81, 69, 74, 69, 0, 67, 72, 66, 2, 79, 71, 62, 50, 18, 11, 4, 69, 64, 4, 1, 10, 5, 65, 76, 0, 70, 72, 70, 8, 9, 11, 5, 5, 7, 9, 20, 12, 7, 3, 3, 2, 9, 11, 84, 64, 5, 72, 8, 9, 17, 12, 2, 19, 12, 14, 68, 4, 1, 2, 15, 89, 12, 19, 16, 21, 22, 19, 18, 29, 27, 8, 18, 23, 71, 62, 76, 14, 6, 8, 10, 9, 13, 9, 18, 66, 4, 3, 10, 62, 79, 35, 38, 33, 30, 29, 26, 23, 22, 19, 9, 7, 5, 0, 67, 88, 75, 75, 90, 6, 3, 1, 64, 72, 74, 74, 69, 80, 77, 82, 90, 83, 99, 74, 17, 14, 10, 6, 4, 65, 69, 70, 71, 66, 29, 19, 14, 6, 11, 0, 66, 70, 76, 5, 36, 28, 23, 20, 17, 6, 1, 65, 71, 69, 44, 30, 15, 3, 12, 0, 73, 76, 7, 48, 39, 28, 18, 22, 4, 64, 68, 71, 62, 91, 86, 77, 83, 83, 77, 77, 73, 74, 74, 72, 68, 78, 77, 82, 83, 68, 79, 76, 75, 70, 68, 70, 72, 68, 70, 69, 70, 80, 6, 6, 12, 3, 64, 6, 4, 3, 3, 2, 1, 65, 64, 67, 71, 68, 6, 82, 0, 16, 64, 9, 3, 6, 12, 3, 1, 4, 9, 70, 70, 79, 39, 40, 41, 33, 29, 35, 32, 31, 30, 25, 29, 26, 18, 14, 2, 22, 24, 20, 12, 15, 14, 7, 5, 64, 1, 71, 74, 74, 82, 36, 36, 35, 32, 22, 25, 22, 13, 14, 8, 5, 69, 70, 77, 84, 67, 66, 87, 6, 4, 64, 73, 70, 72, 77, 71, 79, 79, 89, 88, 93, 100, 72, 83, 88, 76, 71, 3, 69, 11, 20, 67, 66, 7, 11, 4, 10, 20, 4, 1, 46, 24, 4, 74, 86, 107, 120, 126, 126, 1, 39, 29, 25, 15, 21, 10, 5, 3, 70, 76, 71, 3, 68, 11, 18, 65, 66, 6, 10, 6, 9, 18, 8, 2, 46, 24, 4, 74, 86, 107, 120, 126, 126 }, { 45, 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 1, 6, 36, 58, 16, 69, 68, 3, 6, 3, 65, 64, 70, 77, 9, 67, 98, 105, 112, 12, 75, 69, 3, 6, 3, 79, 68, 11, 7, 65, 73, 79, 1, 78, 81, 94, 6, 68, 74, 4, 75, 74, 86, 64, 75, 71, 82, 0, 2, 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 27, 1, 83, 2, 3, 67, 26, 25, 2, 5, 73, 5, 74, 71, 66, 80, 80, 82, 85, 24, 65, 5, 67, 69, 83, 75, 74, 3, 67, 66, 74, 16, 68, 1, 72, 81, 69, 73, 68, 1, 66, 71, 64, 2, 78, 70, 62, 55, 21, 14, 4, 69, 64, 5, 1, 10, 5, 64, 75, 2, 69, 74, 72, 11, 9, 11, 5, 5, 8, 10, 22, 13, 7, 3, 3, 2, 10, 12, 85, 64, 5, 72, 7, 8, 16, 12, 1, 19, 12, 14, 70, 4, 0, 1, 14, 89, 12, 19, 16, 19, 19, 18, 16, 26, 24, 6, 16, 21, 78, 62, 79, 12, 5, 6, 6, 7, 10, 5, 13, 69, 2, 0, 8, 62, 81, 33, 36, 31, 27, 26, 24, 20, 20, 17, 7, 4, 3, 65, 68, 89, 75, 75, 90, 5, 2, 0, 65, 73, 75, 75, 71, 81, 77, 83, 91, 83, 99, 73, 18, 14, 10, 6, 5, 64, 68, 70, 69, 65, 30, 19, 14, 7, 12, 1, 64, 68, 74, 6, 36, 28, 23, 21, 18, 6, 1, 65, 70, 69, 45, 30, 14, 3, 12, 0, 73, 75, 6, 48, 39, 27, 17, 21, 4, 64, 68, 71, 62, 90, 85, 76, 82, 81, 76, 75, 71, 73, 73, 70, 66, 78, 76, 81, 82, 66, 78, 76, 75, 69, 67, 70, 72, 68, 71, 69, 70, 80, 6, 7, 13, 3, 64, 7, 5, 3, 4, 3, 1, 66, 64, 67, 71, 68, 7, 83, 64, 16, 65, 8, 2, 6, 12, 2, 1, 4, 8, 72, 71, 80, 37, 39, 40, 32, 27, 33, 30, 29, 28, 23, 26, 24, 15, 11, 0, 18, 20, 16, 8, 11, 11, 4, 3, 66, 64, 73, 75, 75, 83, 34, 34, 33, 29, 19, 23, 20, 10, 11, 6, 2, 72, 72, 78, 86, 68, 67, 88, 4, 3, 66, 75, 71, 74, 79, 72, 81, 80, 90, 89, 94, 101, 74, 84, 89, 76, 70, 3, 69, 12, 21, 67, 66, 8, 12, 4, 11, 21, 4, 1, 45, 22, 2, 77, 89, 111, 124, 126, 126, 1, 39, 29, 25, 15, 22, 11, 5, 3, 70, 75, 70, 3, 67, 12, 19, 64, 65, 6, 10, 6, 9, 19, 8, 2, 45, 22, 2, 77, 89, 111, 124, 126, 126 }, { 43, 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 0, 4, 35, 58, 15, 67, 68, 3, 7, 3, 65, 64, 71, 79, 8, 70, 101, 107, 114, 15, 74, 69, 3, 7, 3, 79, 67, 11, 6, 65, 73, 78, 1, 78, 81, 94, 6, 68, 74, 4, 75, 73, 86, 64, 75, 71, 82, 0, 2, 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 26, 0, 83, 5, 4, 66, 28, 27, 3, 7, 71, 6, 73, 70, 64, 80, 80, 81, 85, 24, 65, 6, 65, 69, 83, 75, 72, 3, 68, 67, 75, 16, 68, 2, 72, 80, 68, 73, 67, 1, 66, 69, 0, 3, 77, 69, 62, 59, 24, 17, 4, 69, 64, 5, 1, 10, 6, 64, 75, 4, 69, 76, 74, 13, 9, 11, 5, 5, 8, 10, 23, 14, 7, 3, 3, 2, 10, 13, 86, 64, 5, 73, 7, 7, 16, 12, 1, 19, 12, 14, 71, 4, 64, 0, 14, 89, 11, 18, 15, 17, 15, 17, 14, 24, 22, 4, 13, 19, 84, 62, 83, 10, 3, 4, 3, 4, 6, 1, 9, 72, 0, 67, 6, 62, 83, 31, 34, 28, 25, 24, 22, 18, 18, 15, 5, 2, 1, 67, 70, 91, 75, 75, 91, 4, 1, 64, 66, 74, 77, 76, 73, 82, 78, 84, 92, 84, 98, 71, 19, 15, 10, 6, 6, 64, 68, 69, 67, 64, 30, 19, 15, 8, 13, 2, 0, 67, 73, 7, 37, 29, 24, 21, 19, 7, 1, 65, 70, 69, 45, 30, 13, 3, 12, 0, 73, 75, 6, 47, 38, 26, 16, 21, 4, 64, 68, 71, 62, 89, 83, 75, 80, 80, 75, 73, 69, 72, 71, 68, 64, 77, 75, 81, 81, 65, 78, 76, 75, 69, 67, 70, 72, 68, 71, 69, 70, 81, 7, 7, 14, 3, 64, 7, 5, 3, 4, 3, 1, 66, 64, 67, 71, 68, 7, 84, 65, 16, 66, 7, 1, 5, 12, 1, 0, 4, 8, 73, 71, 81, 36, 38, 39, 30, 25, 31, 28, 27, 25, 20, 23, 21, 12, 9, 65, 14, 16, 12, 3, 8, 7, 2, 0, 69, 66, 75, 77, 76, 84, 32, 31, 30, 27, 16, 20, 17, 7, 9, 3, 0, 75, 74, 80, 87, 69, 68, 90, 3, 1, 68, 77, 73, 76, 81, 73, 82, 81, 91, 90, 95, 102, 75, 85, 90, 76, 70, 4, 68, 13, 22, 67, 65, 8, 12, 4, 11, 22, 4, 1, 44, 20, 64, 80, 92, 115, 126, 126, 126, 1, 39, 29, 25, 15, 22, 11, 5, 3, 69, 75, 70, 4, 67, 12, 20, 64, 65, 6, 10, 6, 9, 19, 8, 2, 44, 20, 64, 80, 92, 115, 126, 126, 126 }, { 42, 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 64, 2, 34, 58, 15, 65, 68, 3, 8, 2, 66, 64, 72, 81, 7, 72, 105, 110, 116, 18, 73, 68, 3, 8, 2, 79, 66, 11, 6, 65, 72, 77, 1, 78, 81, 94, 6, 68, 74, 4, 75, 73, 85, 64, 75, 71, 82, 1, 2, 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 25, 0, 83, 7, 5, 65, 30, 29, 5, 8, 70, 8, 71, 69, 2, 80, 79, 81, 84, 24, 65, 7, 0, 69, 82, 74, 70, 3, 68, 67, 75, 16, 68, 2, 71, 79, 67, 72, 66, 2, 65, 68, 2, 4, 75, 68, 62, 62, 27, 20, 5, 68, 0, 6, 1, 10, 6, 64, 74, 6, 69, 78, 76, 16, 9, 12, 5, 5, 8, 11, 25, 15, 8, 2, 3, 2, 11, 13, 87, 0, 5, 73, 6, 6, 15, 11, 0, 19, 12, 14, 72, 5, 65, 0, 13, 89, 11, 17, 14, 15, 12, 16, 13, 22, 20, 2, 11, 17, 90, 62, 86, 8, 1, 1, 0, 2, 3, 67, 5, 74, 65, 70, 4, 62, 84, 29, 32, 26, 23, 22, 20, 16, 16, 13, 2, 0, 64, 68, 72, 93, 75, 75, 91, 3, 0, 65, 68, 75, 78, 77, 74, 83, 78, 85, 92, 85, 97, 70, 20, 15, 10, 7, 6, 0, 67, 68, 65, 1, 30, 20, 15, 8, 14, 3, 2, 65, 71, 7, 38, 30, 25, 22, 20, 7, 2, 64, 69, 69, 46, 30, 13, 2, 12, 0, 73, 74, 6, 47, 37, 25, 15, 21, 4, 0, 68, 70, 62, 87, 82, 73, 78, 78, 74, 72, 68, 71, 69, 67, 1, 76, 74, 80, 80, 0, 78, 76, 74, 68, 66, 70, 73, 68, 72, 69, 70, 81, 7, 7, 15, 3, 64, 7, 5, 3, 4, 3, 1, 67, 64, 67, 71, 68, 8, 85, 66, 16, 67, 7, 1, 4, 12, 1, 64, 4, 8, 74, 71, 82, 35, 37, 38, 28, 23, 29, 26, 24, 23, 17, 20, 18, 9, 6, 67, 9, 12, 8, 65, 5, 4, 64, 65, 71, 68, 76, 78, 76, 84, 29, 29, 28, 25, 13, 17, 15, 4, 7, 1, 65, 78, 76, 82, 89, 69, 68, 92, 2, 0, 69, 79, 75, 78, 82, 74, 84, 82, 91, 92, 96, 102, 77, 87, 92, 75, 70, 5, 68, 14, 23, 66, 64, 9, 13, 4, 12, 23, 4, 1, 43, 18, 67, 83, 96, 119, 126, 126, 126, 2, 39, 29, 25, 15, 23, 11, 6, 4, 69, 75, 70, 4, 67, 13, 21, 64, 65, 7, 11, 6, 10, 20, 8, 2, 43, 18, 67, 83, 96, 119, 126, 126, 126 }, { 41, 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 66, 0, 33, 58, 15, 0, 68, 4, 9, 2, 66, 64, 72, 82, 6, 75, 108, 112, 117, 21, 72, 68, 4, 9, 2, 80, 65, 11, 5, 65, 72, 77, 1, 78, 81, 94, 6, 68, 73, 4, 75, 72, 85, 64, 75, 71, 82, 1, 2, 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 24, 64, 83, 10, 6, 64, 31, 31, 6, 10, 68, 9, 70, 68, 4, 80, 79, 80, 84, 24, 65, 8, 2, 69, 81, 74, 68, 3, 69, 68, 76, 16, 68, 3, 71, 78, 66, 72, 65, 2, 64, 66, 3, 4, 74, 67, 62, 62, 30, 23, 5, 68, 0, 6, 1, 10, 7, 0, 74, 8, 69, 80, 78, 19, 9, 12, 5, 5, 8, 11, 26, 16, 8, 2, 3, 2, 11, 14, 88, 0, 5, 74, 6, 5, 15, 11, 0, 19, 12, 14, 73, 5, 66, 64, 13, 89, 11, 17, 14, 13, 9, 15, 11, 20, 17, 0, 8, 15, 97, 62, 90, 6, 64, 64, 66, 64, 0, 71, 1, 77, 67, 74, 2, 62, 86, 27, 30, 24, 20, 19, 18, 14, 14, 11, 0, 65, 66, 70, 73, 95, 75, 75, 92, 2, 64, 66, 69, 76, 79, 78, 76, 84, 79, 86, 93, 85, 97, 68, 21, 16, 10, 7, 7, 0, 67, 68, 0, 2, 30, 20, 16, 9, 15, 4, 3, 64, 69, 8, 38, 30, 25, 22, 21, 8, 2, 64, 69, 69, 46, 30, 12, 2, 12, 0, 73, 74, 6, 46, 37, 24, 14, 21, 4, 0, 68, 70, 62, 86, 80, 72, 77, 77, 73, 70, 66, 70, 68, 65, 3, 76, 73, 80, 79, 2, 77, 76, 74, 68, 66, 70, 73, 68, 72, 69, 70, 82, 8, 8, 16, 3, 64, 8, 5, 3, 4, 3, 1, 67, 64, 67, 71, 68, 8, 86, 67, 16, 68, 6, 0, 4, 12, 0, 65, 4, 8, 75, 71, 83, 34, 36, 37, 27, 21, 27, 24, 22, 20, 15, 17, 15, 6, 4, 69, 5, 8, 4, 70, 1, 1, 66, 67, 74, 70, 78, 80, 77, 85, 27, 26, 25, 22, 10, 15, 12, 1, 4, 65, 68, 81, 78, 84, 90, 70, 69, 93, 0, 65, 71, 81, 76, 80, 84, 75, 85, 83, 92, 93, 97, 103, 78, 88, 93, 75, 69, 5, 67, 15, 24, 66, 64, 9, 13, 4, 12, 24, 4, 1, 42, 16, 69, 86, 99, 123, 126, 126, 126, 2, 39, 29, 25, 15, 23, 12, 6, 4, 68, 75, 70, 5, 66, 14, 22, 64, 64, 7, 11, 6, 10, 20, 8, 2, 42, 16, 69, 86, 99, 123, 126, 126, 126 }, { 40, 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 67, 65, 32, 58, 15, 2, 68, 4, 10, 2, 67, 64, 73, 84, 5, 77, 112, 115, 119, 24, 71, 68, 4, 10, 2, 80, 64, 11, 5, 65, 72, 76, 1, 78, 81, 94, 6, 68, 73, 4, 75, 72, 85, 64, 75, 71, 82, 1, 2, 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 23, 64, 83, 12, 7, 0, 33, 33, 8, 11, 66, 11, 69, 67, 6, 80, 79, 80, 84, 24, 65, 9, 4, 69, 80, 73, 66, 3, 69, 68, 76, 16, 68, 3, 70, 77, 65, 71, 64, 3, 0, 65, 5, 5, 73, 66, 62, 62, 33, 26, 5, 68, 0, 7, 1, 10, 7, 0, 73, 10, 69, 82, 80, 22, 9, 12, 5, 5, 8, 12, 28, 17, 8, 2, 3, 2, 12, 15, 89, 0, 5, 74, 5, 4, 14, 11, 64, 19, 12, 14, 74, 5, 67, 65, 12, 89, 11, 16, 13, 11, 6, 14, 9, 18, 15, 65, 6, 13, 103, 62, 93, 4, 66, 66, 69, 66, 66, 75, 66, 80, 69, 77, 0, 62, 88, 25, 28, 22, 18, 17, 16, 12, 12, 9, 65, 67, 68, 72, 75, 97, 75, 75, 92, 1, 65, 67, 70, 77, 80, 79, 78, 85, 79, 87, 94, 86, 96, 67, 22, 16, 10, 7, 8, 1, 66, 67, 2, 3, 30, 20, 16, 10, 16, 5, 5, 1, 67, 9, 39, 31, 26, 23, 22, 8, 2, 64, 68, 69, 47, 30, 11, 2, 12, 0, 73, 73, 6, 46, 36, 23, 13, 21, 4, 0, 68, 70, 62, 85, 79, 71, 75, 75, 72, 68, 64, 69, 66, 0, 5, 75, 72, 79, 78, 4, 77, 76, 74, 67, 65, 70, 73, 68, 73, 69, 70, 82, 8, 8, 17, 3, 64, 8, 5, 3, 4, 3, 1, 68, 64, 67, 71, 68, 9, 87, 68, 16, 69, 5, 64, 3, 12, 64, 66, 4, 8, 76, 71, 84, 33, 35, 36, 25, 19, 25, 22, 20, 18, 12, 14, 12, 3, 1, 71, 1, 4, 0, 75, 65, 65, 69, 69, 76, 72, 80, 81, 78, 86, 25, 24, 23, 20, 7, 12, 10, 65, 2, 67, 70, 84, 80, 86, 92, 71, 70, 95, 64, 66, 73, 83, 78, 82, 86, 76, 87, 84, 93, 94, 98, 104, 80, 89, 94, 75, 69, 6, 67, 16, 25, 66, 0, 10, 14, 4, 13, 25, 4, 1, 41, 14, 72, 89, 102, 126, 126, 126, 126, 2, 39, 29, 25, 15, 24, 12, 6, 4, 68, 75, 70, 5, 66, 15, 23, 64, 64, 7, 11, 6, 10, 21, 8, 2, 41, 14, 72, 89, 102, 126, 126, 126, 126 }, { 38, 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 69, 68, 31, 58, 14, 3, 69, 4, 10, 1, 68, 64, 74, 86, 3, 80, 116, 118, 121, 26, 71, 68, 4, 10, 1, 81, 64, 11, 4, 65, 72, 76, 0, 79, 81, 94, 5, 68, 73, 4, 75, 72, 85, 64, 75, 72, 82, 1, 2, 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 22, 65, 84, 14, 8, 1, 34, 35, 9, 12, 65, 12, 68, 66, 8, 80, 79, 80, 84, 24, 65, 9, 6, 69, 80, 73, 65, 2, 70, 69, 77, 16, 68, 3, 70, 77, 65, 71, 64, 3, 0, 64, 6, 5, 72, 65, 62, 62, 36, 28, 5, 68, 0, 7, 0, 10, 7, 0, 73, 12, 69, 84, 82, 24, 9, 12, 5, 5, 8, 12, 29, 17, 8, 1, 2, 2, 12, 15, 91, 0, 5, 75, 4, 2, 13, 10, 65, 19, 12, 13, 76, 5, 68, 66, 11, 89, 10, 15, 12, 8, 2, 12, 7, 15, 12, 68, 3, 11, 110, 62, 97, 1, 68, 69, 73, 69, 70, 80, 71, 83, 71, 81, 65, 62, 90, 22, 25, 19, 15, 14, 13, 9, 9, 7, 68, 70, 70, 74, 77, 99, 75, 76, 93, 0, 67, 69, 72, 79, 82, 81, 80, 86, 80, 88, 95, 87, 96, 66, 22, 16, 10, 7, 8, 1, 66, 67, 4, 4, 30, 20, 16, 10, 16, 6, 6, 2, 66, 9, 39, 31, 26, 23, 23, 8, 2, 64, 68, 70, 47, 29, 10, 1, 12, 0, 73, 73, 5, 45, 35, 21, 12, 20, 4, 0, 68, 70, 62, 84, 78, 70, 74, 74, 71, 67, 0, 68, 65, 1, 7, 75, 72, 79, 77, 5, 77, 76, 74, 67, 65, 70, 74, 69, 74, 69, 71, 83, 8, 8, 18, 3, 65, 8, 5, 2, 4, 3, 1, 69, 64, 67, 71, 68, 9, 88, 69, 16, 71, 4, 65, 2, 11, 65, 67, 4, 7, 78, 72, 86, 31, 33, 35, 23, 17, 22, 19, 17, 15, 9, 11, 9, 64, 65, 73, 67, 0, 68, 80, 69, 69, 72, 72, 79, 74, 82, 83, 79, 87, 22, 21, 20, 17, 4, 9, 7, 69, 64, 70, 73, 87, 82, 88, 94, 72, 71, 97, 66, 68, 75, 86, 80, 84, 88, 78, 89, 86, 94, 96, 99, 105, 82, 91, 96, 75, 69, 6, 67, 17, 26, 66, 0, 10, 14, 4, 13, 25, 4, 0, 39, 12, 75, 93, 106, 126, 126, 126, 126, 2, 39, 29, 25, 15, 24, 12, 6, 4, 68, 75, 70, 5, 66, 15, 23, 64, 64, 7, 11, 6, 10, 21, 7, 1, 39, 12, 75, 93, 106, 126, 126, 126, 126 }, { 37, 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 70, 70, 30, 59, 14, 5, 69, 5, 11, 1, 68, 0, 74, 87, 2, 82, 119, 120, 122, 29, 70, 67, 5, 11, 1, 81, 0, 11, 4, 64, 71, 75, 0, 79, 80, 93, 5, 67, 72, 4, 74, 71, 84, 0, 74, 72, 81, 2, 2, 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 22, 65, 84, 17, 10, 3, 36, 38, 11, 14, 0, 14, 66, 64, 11, 80, 78, 79, 83, 24, 65, 10, 9, 68, 79, 72, 0, 2, 70, 69, 77, 17, 68, 4, 69, 76, 64, 70, 0, 4, 1, 1, 8, 6, 70, 0, 62, 62, 40, 31, 6, 67, 1, 8, 0, 11, 8, 1, 72, 15, 68, 86, 83, 27, 9, 13, 5, 6, 9, 13, 31, 18, 9, 1, 2, 2, 13, 16, 92, 1, 5, 75, 4, 1, 13, 10, 65, 19, 12, 13, 77, 6, 68, 66, 11, 89, 10, 15, 12, 6, 64, 11, 6, 13, 10, 70, 1, 9, 116, 62, 100, 64, 69, 71, 76, 71, 73, 84, 75, 85, 73, 84, 67, 62, 91, 20, 23, 17, 13, 12, 11, 7, 7, 5, 70, 72, 71, 75, 78, 100, 75, 76, 93, 0, 68, 70, 73, 80, 83, 82, 81, 87, 80, 89, 95, 87, 95, 64, 23, 17, 10, 8, 9, 2, 65, 66, 7, 6, 31, 21, 17, 11, 17, 8, 8, 4, 64, 10, 40, 32, 27, 24, 24, 9, 3, 0, 67, 70, 48, 29, 10, 1, 13, 1, 73, 72, 5, 45, 35, 20, 11, 20, 5, 1, 67, 69, 62, 82, 76, 68, 72, 72, 69, 65, 2, 66, 0, 3, 10, 74, 71, 78, 75, 7, 76, 75, 73, 66, 64, 69, 74, 69, 74, 69, 71, 83, 9, 9, 19, 4, 65, 9, 6, 2, 5, 4, 1, 69, 0, 66, 70, 67, 10, 88, 70, 16, 72, 4, 65, 2, 11, 65, 67, 4, 7, 79, 72, 87, 30, 32, 35, 22, 16, 20, 17, 15, 13, 7, 9, 7, 67, 67, 75, 71, 66, 72, 84, 72, 72, 74, 74, 81, 75, 83, 84, 79, 87, 20, 19, 18, 15, 2, 7, 5, 72, 66, 72, 75, 89, 83, 89, 95, 72, 71, 98, 67, 69, 76, 88, 81, 85, 89, 79, 90, 87, 94, 97, 99, 105, 83, 92, 97, 74, 68, 7, 66, 19, 28, 65, 1, 11, 15, 5, 14, 26, 4, 0, 38, 10, 77, 96, 109, 126, 126, 126, 126, 3, 39, 30, 26, 16, 25, 13, 7, 5, 67, 74, 69, 6, 65, 16, 24, 0, 0, 8, 12, 6, 11, 22, 7, 1, 38, 10, 77, 96, 109, 126, 126, 126, 126 }, { 36, 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 71, 72, 29, 59, 14, 7, 69, 5, 12, 1, 69, 0, 75, 89, 1, 85, 123, 123, 124, 32, 69, 67, 5, 12, 1, 81, 1, 11, 3, 64, 71, 74, 0, 79, 80, 93, 5, 67, 72, 4, 74, 71, 84, 0, 74, 72, 81, 2, 2, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 21, 65, 84, 19, 11, 4, 38, 40, 12, 15, 2, 15, 65, 0, 13, 80, 78, 79, 83, 24, 65, 11, 11, 68, 78, 71, 2, 2, 70, 69, 78, 17, 68, 4, 68, 75, 0, 69, 1, 4, 2, 2, 9, 7, 69, 1, 62, 62, 43, 34, 6, 67, 1, 8, 0, 11, 8, 1, 72, 17, 68, 88, 85, 30, 9, 13, 5, 6, 9, 13, 33, 19, 9, 1, 2, 2, 14, 17, 93, 1, 5, 75, 3, 0, 12, 10, 66, 19, 12, 13, 78, 6, 69, 67, 10, 89, 10, 14, 11, 4, 67, 10, 4, 11, 8, 72, 64, 7, 122, 62, 104, 66, 71, 73, 79, 73, 76, 88, 79, 88, 75, 87, 69, 62, 93, 18, 21, 15, 11, 10, 9, 5, 5, 3, 72, 74, 73, 77, 80, 102, 75, 76, 94, 64, 69, 71, 74, 81, 84, 83, 83, 88, 80, 90, 96, 88, 94, 0, 24, 17, 10, 8, 10, 3, 64, 65, 9, 7, 31, 21, 17, 12, 18, 9, 10, 6, 1, 11, 41, 33, 28, 25, 25, 9, 3, 0, 66, 70, 49, 29, 9, 1, 13, 1, 73, 72, 5, 45, 34, 19, 10, 20, 5, 1, 67, 69, 62, 81, 75, 67, 70, 70, 68, 0, 4, 65, 2, 5, 12, 73, 70, 78, 74, 9, 76, 75, 73, 65, 64, 69, 74, 69, 75, 69, 71, 83, 9, 9, 20, 4, 65, 9, 6, 2, 5, 4, 1, 70, 0, 66, 70, 67, 11, 89, 71, 16, 73, 3, 66, 1, 11, 66, 68, 4, 7, 80, 72, 88, 29, 31, 34, 20, 14, 18, 15, 13, 11, 4, 6, 4, 70, 70, 77, 75, 70, 76, 89, 75, 75, 77, 76, 84, 77, 85, 85, 80, 88, 18, 17, 15, 13, 64, 4, 2, 75, 68, 75, 77, 92, 85, 91, 97, 73, 72, 100, 68, 70, 78, 90, 83, 87, 91, 80, 92, 88, 95, 98, 100, 106, 85, 93, 98, 74, 68, 8, 66, 20, 29, 65, 2, 12, 16, 5, 14, 27, 4, 0, 37, 8, 80, 99, 112, 126, 126, 126, 126, 3, 39, 30, 26, 16, 26, 13, 7, 5, 67, 74, 69, 6, 65, 17, 25, 0, 0, 8, 12, 6, 11, 23, 7, 1, 37, 8, 80, 99, 112, 126, 126, 126, 126 }, { 35, 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 73, 74, 28, 59, 14, 9, 69, 6, 13, 1, 69, 0, 75, 90, 0, 87, 126, 125, 125, 35, 68, 67, 6, 13, 1, 82, 2, 11, 3, 64, 71, 74, 0, 79, 80, 93, 5, 67, 71, 4, 74, 70, 84, 0, 74, 72, 81, 2, 2, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 20, 66, 84, 22, 12, 5, 39, 42, 14, 17, 4, 17, 64, 1, 15, 80, 78, 78, 83, 24, 65, 12, 13, 68, 77, 71, 4, 2, 71, 70, 78, 17, 68, 5, 68, 74, 1, 69, 2, 5, 3, 4, 11, 7, 68, 2, 62, 62, 46, 37, 6, 67, 1, 9, 0, 11, 9, 2, 71, 19, 68, 90, 87, 33, 9, 13, 5, 6, 9, 14, 34, 20, 9, 1, 2, 2, 14, 18, 94, 1, 5, 76, 3, 64, 12, 10, 66, 19, 12, 13, 79, 6, 70, 68, 10, 89, 10, 14, 11, 2, 70, 9, 2, 9, 5, 74, 67, 5, 126, 62, 107, 68, 73, 75, 82, 76, 79, 92, 83, 91, 77, 91, 71, 62, 95, 16, 19, 13, 8, 7, 7, 3, 3, 1, 74, 76, 75, 79, 81, 104, 75, 76, 94, 65, 70, 72, 75, 82, 85, 84, 85, 89, 81, 91, 97, 88, 94, 2, 25, 18, 10, 8, 11, 3, 64, 65, 11, 8, 31, 21, 18, 13, 19, 10, 11, 7, 3, 12, 41, 33, 28, 25, 26, 10, 3, 0, 66, 70, 49, 29, 8, 1, 13, 1, 73, 71, 5, 44, 34, 18, 9, 20, 5, 1, 67, 69, 62, 80, 73, 66, 69, 69, 67, 2, 6, 64, 3, 7, 14, 73, 69, 77, 73, 11, 75, 75, 73, 65, 0, 69, 74, 69, 75, 69, 71, 84, 10, 10, 21, 4, 65, 10, 6, 2, 5, 4, 1, 70, 0, 66, 70, 67, 11, 90, 72, 16, 74, 2, 67, 1, 11, 67, 69, 4, 7, 81, 72, 89, 28, 30, 33, 19, 12, 16, 13, 11, 8, 2, 3, 1, 73, 72, 79, 79, 74, 80, 94, 79, 78, 79, 78, 86, 79, 87, 87, 81, 89, 16, 14, 13, 10, 67, 2, 0, 78, 71, 77, 80, 95, 87, 93, 98, 74, 73, 101, 70, 72, 80, 92, 84, 89, 93, 81, 93, 89, 96, 99, 101, 107, 86, 94, 99, 74, 67, 8, 65, 21, 30, 65, 2, 12, 16, 5, 15, 28, 4, 0, 36, 6, 82, 102, 115, 126, 126, 126, 126, 3, 39, 30, 26, 16, 26, 14, 7, 5, 66, 74, 69, 7, 64, 18, 26, 0, 1, 8, 12, 6, 11, 23, 7, 1, 36, 6, 82, 102, 115, 126, 126, 126, 126 }, { 33, 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 74, 76, 27, 59, 13, 11, 69, 6, 14, 0, 70, 0, 76, 92, 64, 90, 126, 126, 126, 38, 67, 67, 6, 14, 0, 82, 3, 11, 2, 64, 70, 73, 0, 79, 80, 93, 5, 67, 71, 4, 74, 70, 83, 0, 74, 72, 81, 3, 2, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 19, 66, 84, 24, 13, 6, 41, 44, 15, 18, 5, 18, 1, 2, 17, 80, 77, 78, 83, 24, 65, 13, 15, 68, 77, 70, 6, 2, 71, 70, 79, 17, 68, 5, 67, 73, 2, 68, 3, 5, 3, 5, 12, 8, 67, 3, 62, 62, 49, 40, 6, 66, 1, 9, 0, 11, 9, 2, 71, 21, 68, 92, 89, 35, 9, 14, 5, 6, 9, 14, 36, 21, 10, 0, 2, 2, 15, 18, 95, 1, 5, 76, 2, 65, 11, 9, 67, 19, 12, 13, 80, 6, 71, 69, 9, 89, 9, 13, 10, 0, 74, 8, 0, 7, 3, 76, 69, 3, 126, 62, 111, 70, 75, 78, 85, 78, 83, 97, 87, 94, 79, 94, 73, 62, 96, 14, 17, 10, 6, 5, 5, 1, 1, 64, 77, 78, 77, 81, 83, 106, 75, 76, 95, 66, 71, 73, 77, 83, 87, 85, 86, 90, 81, 92, 97, 89, 93, 3, 26, 18, 10, 9, 11, 4, 0, 64, 13, 10, 31, 22, 18, 13, 20, 11, 13, 9, 4, 12, 42, 34, 29, 26, 27, 10, 3, 1, 65, 70, 50, 29, 8, 0, 13, 1, 73, 71, 5, 44, 33, 17, 8, 20, 5, 2, 67, 69, 62, 78, 72, 65, 67, 67, 66, 3, 7, 0, 5, 8, 16, 72, 68, 77, 72, 12, 75, 75, 73, 64, 0, 69, 75, 69, 76, 69, 71, 84, 10, 10, 22, 4, 65, 10, 6, 2, 5, 4, 1, 71, 0, 66, 70, 67, 12, 91, 73, 16, 75, 2, 68, 0, 11, 68, 70, 4, 7, 82, 72, 90, 27, 29, 32, 17, 10, 14, 11, 8, 6, 64, 0, 65, 76, 75, 81, 84, 78, 84, 99, 82, 82, 82, 81, 89, 81, 89, 88, 82, 89, 13, 12, 10, 8, 70, 64, 66, 81, 73, 80, 82, 98, 89, 95, 100, 75, 73, 103, 71, 73, 81, 94, 86, 91, 94, 82, 95, 90, 97, 101, 102, 107, 88, 96, 101, 73, 67, 9, 65, 22, 31, 65, 3, 13, 17, 5, 15, 29, 4, 0, 35, 4, 85, 105, 119, 126, 126, 126, 126, 4, 39, 30, 26, 16, 27, 14, 7, 5, 66, 74, 69, 7, 64, 18, 27, 0, 1, 9, 13, 6, 11, 24, 7, 1, 35, 4, 85, 105, 119, 126, 126, 126, 126 }, { 32, 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 75, 78, 26, 59, 13, 13, 69, 6, 15, 0, 70, 0, 77, 94, 65, 92, 126, 126, 126, 41, 66, 66, 6, 15, 0, 82, 4, 11, 2, 64, 70, 72, 0, 79, 80, 93, 5, 67, 71, 4, 74, 69, 83, 0, 74, 72, 81, 3, 2, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 18, 67, 84, 27, 14, 7, 43, 46, 17, 20, 7, 20, 2, 3, 20, 80, 77, 77, 82, 24, 65, 14, 17, 68, 76, 70, 8, 2, 72, 71, 79, 17, 68, 6, 67, 72, 3, 68, 4, 6, 4, 7, 14, 9, 65, 4, 62, 62, 52, 43, 7, 66, 2, 10, 0, 11, 10, 2, 70, 23, 68, 94, 91, 38, 9, 14, 5, 6, 9, 15, 37, 22, 10, 0, 2, 2, 15, 19, 96, 2, 5, 77, 2, 66, 11, 9, 67, 19, 12, 13, 81, 7, 72, 69, 9, 89, 9, 12, 9, 65, 77, 7, 64, 5, 1, 78, 72, 1, 126, 62, 114, 72, 77, 80, 88, 81, 86, 101, 91, 96, 81, 98, 75, 62, 98, 12, 15, 8, 4, 3, 3, 64, 64, 66, 79, 80, 79, 82, 85, 108, 75, 76, 95, 67, 72, 74, 78, 84, 88, 86, 88, 91, 82, 93, 98, 90, 92, 5, 27, 19, 10, 9, 12, 4, 0, 0, 15, 11, 31, 22, 19, 14, 21, 12, 14, 10, 6, 13, 43, 35, 30, 26, 28, 11, 4, 1, 65, 70, 50, 29, 7, 0, 13, 1, 73, 70, 5, 43, 32, 16, 7, 20, 5, 2, 67, 68, 62, 77, 70, 0, 65, 66, 65, 5, 9, 1, 7, 10, 18, 71, 67, 76, 71, 14, 75, 75, 72, 64, 1, 69, 75, 69, 76, 69, 71, 85, 11, 10, 23, 4, 65, 10, 6, 2, 5, 4, 1, 71, 0, 66, 70, 67, 12, 92, 74, 16, 76, 1, 68, 64, 11, 68, 71, 4, 7, 83, 72, 91, 26, 28, 31, 15, 8, 12, 9, 6, 3, 67, 66, 68, 79, 77, 83, 88, 82, 88, 104, 85, 85, 84, 83, 91, 83, 90, 90, 82, 90, 11, 9, 8, 6, 73, 67, 68, 84, 75, 82, 84, 101, 91, 97, 101, 75, 74, 105, 72, 75, 83, 96, 88, 93, 96, 83, 96, 91, 97, 102, 103, 108, 89, 97, 102, 73, 67, 10, 64, 23, 32, 64, 4, 13, 17, 5, 16, 30, 4, 0, 34, 2, 88, 108, 122, 126, 126, 126, 126, 4, 39, 30, 26, 16, 27, 14, 8, 6, 65, 74, 69, 8, 64, 19, 28, 0, 1, 9, 13, 6, 12, 24, 7, 1, 34, 2, 88, 108, 122, 126, 126, 126, 126 }, { 31, 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 77, 80, 25, 59, 13, 14, 69, 7, 15, 0, 71, 1, 77, 95, 67, 95, 126, 126, 126, 43, 65, 66, 7, 15, 0, 83, 4, 11, 1, 64, 70, 72, 0, 79, 79, 92, 5, 66, 70, 4, 73, 69, 83, 0, 74, 72, 81, 3, 2, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 18, 67, 84, 29, 15, 8, 44, 49, 18, 21, 9, 21, 3, 4, 22, 80, 77, 77, 82, 24, 65, 15, 20, 68, 75, 69, 10, 1, 72, 71, 80, 18, 68, 6, 66, 72, 3, 67, 5, 6, 5, 8, 15, 9, 64, 5, 62, 62, 55, 46, 7, 66, 2, 10, 0, 11, 10, 3, 70, 25, 67, 96, 93, 41, 9, 14, 5, 6, 10, 15, 39, 23, 10, 0, 2, 2, 16, 20, 97, 2, 5, 77, 1, 67, 10, 9, 68, 19, 12, 13, 83, 7, 73, 70, 8, 89, 9, 12, 9, 67, 80, 6, 66, 2, 65, 80, 74, 64, 126, 62, 118, 74, 78, 82, 92, 83, 89, 105, 96, 99, 83, 101, 77, 62, 100, 10, 13, 6, 1, 0, 1, 67, 66, 68, 81, 83, 81, 84, 86, 109, 75, 76, 96, 68, 73, 75, 79, 85, 89, 87, 90, 92, 82, 94, 99, 90, 92, 6, 28, 19, 10, 9, 13, 5, 1, 0, 17, 12, 32, 22, 19, 15, 22, 13, 16, 12, 8, 14, 43, 35, 30, 27, 29, 11, 4, 1, 64, 70, 51, 29, 6, 0, 13, 1, 73, 70, 4, 43, 32, 15, 6, 19, 5, 2, 67, 68, 62, 76, 69, 1, 64, 64, 64, 7, 11, 2, 8, 12, 20, 71, 66, 76, 70, 16, 74, 75, 72, 0, 1, 69, 75, 69, 77, 69, 71, 85, 11, 11, 24, 4, 65, 11, 7, 2, 6, 5, 1, 72, 0, 66, 70, 67, 13, 93, 75, 16, 77, 0, 69, 64, 11, 69, 71, 4, 6, 85, 73, 92, 24, 27, 30, 14, 6, 10, 7, 4, 1, 69, 69, 70, 82, 80, 85, 92, 86, 92, 108, 89, 88, 87, 85, 94, 85, 92, 91, 83, 91, 9, 7, 5, 3, 76, 69, 71, 87, 78, 85, 87, 104, 93, 98, 103, 76, 75, 106, 74, 76, 85, 98, 89, 95, 98, 84, 98, 92, 98, 103, 104, 109, 91, 98, 103, 73, 66, 10, 64, 24, 33, 64, 4, 14, 18, 5, 16, 31, 4, 0, 33, 0, 90, 111, 125, 126, 126, 126, 126, 4, 39, 30, 26, 16, 28, 15, 8, 6, 65, 73, 68, 8, 0, 20, 29, 1, 2, 9, 13, 6, 12, 25, 7, 1, 33, 0, 90, 111, 125, 126, 126, 126, 126 }, { 30, 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 78, 82, 24, 59, 13, 16, 69, 7, 16, 64, 71, 1, 78, 97, 68, 97, 126, 126, 126, 46, 64, 66, 7, 16, 64, 83, 5, 11, 1, 64, 69, 71, 0, 79, 79, 92, 5, 66, 70, 4, 73, 68, 82, 0, 74, 72, 81, 4, 2, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 17, 68, 84, 32, 16, 9, 46, 51, 20, 23, 10, 23, 5, 5, 24, 80, 76, 76, 82, 24, 65, 16, 22, 68, 74, 69, 12, 1, 73, 72, 80, 18, 68, 7, 66, 71, 4, 67, 6, 7, 6, 10, 17, 10, 0, 6, 62, 62, 58, 49, 7, 65, 2, 11, 0, 11, 11, 3, 69, 27, 67, 98, 95, 44, 9, 15, 5, 6, 10, 16, 40, 24, 11, 64, 2, 2, 16, 20, 98, 2, 5, 78, 1, 68, 10, 8, 68, 19, 12, 13, 84, 7, 74, 71, 8, 89, 9, 11, 8, 69, 83, 5, 68, 0, 67, 82, 77, 66, 126, 62, 121, 76, 80, 85, 95, 86, 92, 110, 100, 102, 85, 105, 79, 62, 101, 8, 11, 4, 64, 65, 64, 69, 68, 70, 84, 85, 83, 86, 88, 111, 75, 76, 96, 69, 74, 76, 81, 86, 90, 88, 91, 93, 83, 95, 99, 91, 91, 8, 29, 20, 10, 10, 13, 5, 1, 1, 19, 14, 32, 23, 20, 15, 23, 14, 17, 13, 10, 14, 44, 36, 31, 27, 30, 12, 4, 2, 64, 70, 51, 29, 6, 64, 13, 1, 73, 69, 4, 42, 31, 14, 5, 19, 5, 3, 67, 68, 62, 74, 67, 2, 1, 0, 0, 8, 12, 3, 10, 13, 22, 70, 65, 75, 69, 18, 74, 75, 72, 0, 2, 69, 76, 69, 77, 69, 71, 86, 12, 11, 25, 4, 65, 11, 7, 2, 6, 5, 1, 72, 0, 66, 70, 67, 13, 94, 76, 16, 78, 0, 70, 65, 11, 70, 72, 4, 6, 86, 73, 93, 23, 26, 29, 12, 4, 8, 5, 1, 65, 72, 72, 73, 85, 82, 87, 97, 90, 96, 113, 92, 91, 89, 87, 96, 87, 94, 93, 84, 91, 6, 4, 3, 1, 79, 72, 73, 90, 80, 87, 89, 107, 95, 100, 104, 77, 75, 108, 75, 78, 86, 100, 91, 97, 99, 85, 99, 93, 99, 105, 105, 109, 92, 100, 105, 72, 66, 11, 0, 25, 34, 64, 5, 14, 18, 5, 17, 32, 4, 0, 32, 65, 93, 114, 126, 126, 126, 126, 126, 5, 39, 30, 26, 16, 28, 15, 8, 6, 64, 73, 68, 9, 0, 21, 30, 1, 2, 10, 14, 6, 12, 25, 7, 1, 32, 65, 93, 114, 126, 126, 126, 126, 126 }, { 28, 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 80, 85, 23, 59, 12, 18, 70, 7, 17, 64, 72, 1, 79, 99, 69, 100, 126, 126, 126, 49, 0, 66, 7, 17, 64, 84, 6, 11, 0, 64, 69, 71, 64, 80, 79, 92, 5, 66, 70, 4, 73, 68, 82, 0, 74, 72, 81, 4, 2, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 16, 68, 85, 34, 17, 10, 47, 53, 21, 24, 12, 24, 6, 6, 26, 80, 76, 76, 82, 24, 65, 17, 24, 68, 74, 68, 14, 1, 73, 72, 81, 18, 68, 7, 65, 70, 5, 66, 6, 7, 6, 11, 18, 10, 1, 7, 62, 62, 61, 51, 7, 65, 2, 11, 64, 11, 11, 3, 69, 29, 67, 100, 97, 46, 9, 15, 5, 6, 10, 16, 42, 24, 11, 64, 1, 2, 17, 21, 100, 2, 5, 78, 0, 70, 9, 8, 69, 19, 12, 12, 85, 7, 75, 72, 7, 89, 8, 10, 7, 71, 87, 3, 70, 65, 70, 85, 79, 68, 126, 62, 125, 78, 82, 87, 98, 88, 96, 114, 104, 105, 87, 108, 81, 62, 103, 6, 8, 1, 67, 68, 67, 71, 71, 72, 86, 87, 85, 88, 90, 113, 75, 77, 97, 70, 76, 77, 82, 87, 92, 90, 93, 94, 83, 96, 100, 92, 91, 9, 30, 20, 10, 10, 14, 6, 2, 1, 21, 15, 32, 23, 20, 16, 24, 15, 19, 15, 11, 15, 44, 36, 31, 28, 31, 12, 4, 2, 0, 71, 52, 29, 5, 64, 13, 1, 73, 69, 4, 42, 30, 13, 4, 19, 5, 3, 67, 68, 62, 73, 66, 3, 2, 2, 1, 10, 14, 4, 11, 15, 24, 70, 65, 75, 68, 19, 74, 75, 72, 1, 2, 69, 76, 69, 78, 69, 71, 86, 12, 11, 26, 4, 66, 11, 7, 1, 6, 5, 1, 73, 0, 66, 70, 67, 14, 95, 77, 16, 80, 64, 71, 66, 10, 71, 73, 4, 6, 87, 73, 95, 22, 24, 28, 10, 2, 6, 3, 64, 67, 75, 75, 76, 88, 85, 89, 101, 94, 101, 118, 96, 95, 92, 90, 99, 89, 96, 94, 85, 92, 4, 2, 0, 65, 82, 75, 76, 93, 83, 90, 92, 110, 97, 102, 106, 78, 76, 110, 77, 79, 88, 102, 93, 99, 101, 87, 101, 95, 100, 106, 106, 110, 94, 101, 106, 72, 66, 11, 0, 26, 35, 64, 5, 15, 19, 5, 17, 32, 4, 64, 31, 67, 96, 117, 126, 126, 126, 126, 126, 5, 39, 30, 26, 16, 29, 15, 8, 6, 64, 73, 68, 9, 0, 21, 30, 1, 2, 10, 14, 6, 12, 26, 7, 0, 31, 67, 96, 117, 126, 126, 126, 126, 126 }, { 27, 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 81, 87, 22, 60, 12, 20, 70, 8, 18, 64, 73, 1, 79, 100, 70, 102, 126, 126, 126, 52, 1, 65, 8, 18, 64, 84, 7, 11, 0, 0, 69, 70, 64, 80, 79, 92, 5, 66, 69, 4, 73, 68, 82, 0, 74, 72, 80, 4, 2, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 15, 68, 85, 36, 19, 11, 49, 55, 23, 25, 14, 26, 7, 8, 29, 80, 76, 76, 81, 24, 65, 18, 26, 67, 73, 67, 16, 1, 73, 72, 81, 18, 68, 7, 64, 69, 6, 65, 7, 8, 7, 12, 20, 11, 3, 8, 62, 62, 62, 54, 8, 65, 3, 12, 64, 11, 11, 4, 68, 32, 67, 102, 98, 49, 9, 15, 5, 6, 10, 17, 44, 25, 11, 64, 1, 2, 18, 22, 101, 3, 5, 78, 64, 71, 8, 8, 70, 19, 12, 12, 86, 8, 76, 72, 6, 89, 8, 10, 7, 73, 90, 2, 71, 67, 72, 87, 81, 70, 126, 62, 126, 80, 84, 89, 101, 90, 99, 118, 108, 107, 89, 111, 83, 62, 105, 4, 6, 64, 69, 70, 69, 73, 73, 74, 88, 89, 86, 89, 91, 115, 75, 77, 97, 70, 77, 78, 83, 88, 93, 91, 95, 95, 83, 97, 101, 92, 90, 10, 31, 20, 10, 10, 15, 7, 3, 2, 24, 16, 32, 23, 20, 17, 25, 16, 21, 17, 13, 16, 45, 37, 32, 29, 32, 12, 5, 2, 1, 71, 53, 29, 4, 64, 14, 2, 73, 68, 4, 42, 30, 12, 3, 19, 5, 3, 67, 67, 62, 72, 65, 5, 4, 4, 2, 12, 16, 5, 13, 17, 26, 69, 64, 74, 67, 21, 73, 74, 71, 2, 3, 69, 76, 69, 79, 69, 71, 86, 12, 12, 27, 5, 66, 12, 7, 1, 6, 5, 1, 74, 0, 65, 69, 67, 15, 95, 78, 16, 81, 65, 71, 66, 10, 71, 74, 4, 6, 88, 73, 96, 21, 23, 28, 9, 0, 4, 1, 66, 69, 77, 78, 79, 91, 88, 91, 105, 98, 105, 123, 99, 98, 95, 92, 101, 90, 97, 95, 85, 93, 2, 0, 65, 67, 84, 77, 78, 96, 85, 92, 94, 112, 99, 104, 108, 78, 77, 111, 78, 80, 90, 104, 94, 100, 103, 88, 103, 96, 100, 107, 106, 111, 96, 102, 107, 72, 65, 12, 0, 27, 37, 0, 6, 16, 20, 5, 18, 33, 4, 64, 30, 69, 98, 120, 126, 126, 126, 126, 126, 5, 39, 30, 27, 17, 30, 16, 9, 7, 64, 73, 68, 9, 1, 22, 31, 1, 3, 10, 14, 6, 13, 27, 7, 0, 30, 69, 98, 120, 126, 126, 126, 126, 126 }, { 26, 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 82, 89, 21, 60, 12, 22, 70, 8, 19, 65, 73, 1, 80, 102, 71, 105, 126, 126, 126, 55, 2, 65, 8, 19, 65, 84, 8, 11, 64, 0, 68, 69, 64, 80, 79, 92, 5, 66, 69, 4, 73, 67, 81, 0, 74, 72, 80, 5, 2, 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 14, 69, 85, 39, 20, 12, 51, 57, 24, 27, 15, 27, 9, 9, 31, 80, 75, 75, 81, 24, 65, 19, 28, 67, 72, 67, 18, 1, 74, 73, 82, 18, 68, 8, 64, 68, 7, 65, 8, 8, 8, 14, 21, 12, 4, 9, 62, 62, 62, 57, 8, 64, 3, 12, 64, 11, 12, 4, 68, 34, 67, 104, 100, 52, 9, 16, 5, 6, 10, 17, 45, 26, 12, 65, 1, 2, 18, 22, 102, 3, 5, 79, 64, 72, 8, 7, 70, 19, 12, 12, 87, 8, 77, 73, 6, 89, 8, 9, 6, 75, 93, 1, 73, 69, 74, 89, 84, 72, 126, 62, 126, 82, 86, 92, 104, 93, 102, 123, 112, 110, 91, 115, 85, 62, 106, 2, 4, 66, 71, 72, 71, 75, 75, 76, 91, 91, 88, 91, 93, 117, 75, 77, 98, 71, 78, 79, 85, 89, 94, 92, 96, 96, 84, 98, 101, 93, 89, 12, 32, 21, 10, 11, 15, 7, 3, 3, 26, 18, 32, 24, 21, 17, 26, 17, 22, 18, 15, 16, 46, 38, 33, 29, 33, 13, 5, 3, 1, 71, 53, 29, 4, 65, 14, 2, 73, 68, 4, 41, 29, 11, 2, 19, 5, 4, 67, 67, 62, 70, 0, 6, 6, 5, 3, 13, 17, 6, 15, 18, 28, 68, 0, 74, 66, 23, 73, 74, 71, 2, 3, 69, 77, 69, 79, 69, 71, 87, 13, 12, 28, 5, 66, 12, 7, 1, 6, 5, 1, 74, 0, 65, 69, 67, 15, 96, 79, 16, 82, 65, 72, 67, 10, 72, 75, 4, 6, 89, 73, 97, 20, 22, 27, 7, 65, 2, 64, 69, 72, 80, 81, 82, 94, 90, 93, 110, 102, 109, 126, 102, 101, 97, 94, 104, 92, 99, 97, 86, 93, 64, 66, 68, 69, 87, 80, 81, 99, 87, 95, 96, 115, 101, 106, 109, 79, 77, 113, 79, 82, 91, 106, 96, 102, 104, 89, 104, 97, 101, 109, 107, 111, 97, 104, 109, 71, 65, 13, 1, 28, 38, 0, 7, 16, 20, 5, 18, 34, 4, 64, 29, 71, 101, 123, 126, 126, 126, 126, 126, 6, 39, 30, 27, 17, 30, 16, 9, 7, 0, 73, 68, 10, 1, 23, 32, 1, 3, 11, 15, 6, 13, 27, 7, 0, 29, 71, 101, 123, 126, 126, 126, 126, 126 }, { 25, 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 84, 91, 20, 60, 12, 23, 70, 9, 19, 65, 74, 2, 80, 103, 73, 107, 126, 126, 126, 57, 3, 65, 9, 19, 65, 85, 8, 11, 64, 0, 68, 69, 64, 80, 78, 91, 5, 65, 68, 4, 72, 67, 81, 0, 74, 72, 80, 5, 2, 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 14, 69, 85, 41, 21, 13, 52, 60, 26, 28, 17, 29, 10, 10, 33, 80, 75, 75, 81, 24, 65, 20, 31, 67, 71, 66, 20, 0, 74, 73, 82, 19, 68, 8, 0, 68, 7, 64, 9, 9, 9, 15, 23, 12, 5, 10, 62, 62, 62, 60, 8, 64, 3, 13, 64, 11, 12, 5, 67, 36, 66, 106, 102, 55, 9, 16, 5, 6, 11, 18, 47, 27, 12, 65, 1, 2, 19, 23, 103, 3, 5, 79, 65, 73, 7, 7, 71, 19, 12, 12, 89, 8, 78, 74, 5, 89, 8, 9, 6, 77, 96, 0, 75, 72, 77, 91, 86, 74, 126, 62, 126, 84, 87, 94, 108, 95, 105, 126, 117, 113, 93, 118, 87, 62, 108, 0, 2, 68, 74, 75, 73, 78, 77, 78, 93, 94, 90, 93, 94, 118, 75, 77, 98, 72, 79, 80, 86, 90, 95, 93, 98, 97, 84, 99, 102, 93, 89, 13, 33, 21, 10, 11, 16, 8, 4, 3, 28, 19, 33, 24, 21, 18, 27, 18, 24, 20, 17, 17, 46, 38, 33, 30, 34, 13, 5, 3, 2, 71, 54, 29, 3, 65, 14, 2, 73, 67, 3, 41, 29, 10, 1, 18, 5, 4, 67, 67, 62, 69, 1, 7, 7, 7, 4, 15, 19, 7, 16, 20, 30, 68, 1, 73, 65, 25, 72, 74, 71, 3, 4, 69, 77, 69, 80, 69, 71, 87, 13, 13, 29, 5, 66, 13, 8, 1, 7, 6, 1, 75, 0, 65, 69, 67, 16, 97, 80, 16, 83, 66, 73, 67, 10, 73, 75, 4, 5, 91, 74, 98, 18, 21, 26, 6, 67, 0, 66, 71, 74, 82, 84, 84, 97, 93, 95, 114, 106, 113, 126, 106, 104, 100, 96, 106, 94, 101, 98, 87, 94, 66, 68, 70, 72, 90, 82, 83, 102, 90, 97, 99, 118, 103, 107, 111, 80, 78, 114, 81, 83, 93, 108, 97, 104, 106, 90, 106, 98, 102, 110, 108, 112, 99, 105, 110, 71, 64, 13, 1, 29, 39, 0, 7, 17, 21, 5, 19, 35, 4, 64, 28, 73, 103, 126, 126, 126, 126, 126, 126, 6, 39, 30, 27, 17, 31, 17, 9, 7, 0, 72, 67, 10, 2, 24, 33, 2, 4, 11, 15, 6, 13, 28, 7, 0, 28, 73, 103, 126, 126, 126, 126, 126, 126 }, { 23, 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 85, 93, 19, 60, 11, 25, 70, 9, 20, 65, 74, 2, 81, 105, 74, 110, 126, 126, 126, 60, 4, 65, 9, 20, 65, 85, 9, 11, 65, 0, 68, 68, 64, 80, 78, 91, 5, 65, 68, 4, 72, 66, 81, 0, 74, 72, 80, 5, 2, 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 13, 70, 85, 44, 22, 14, 54, 62, 27, 30, 19, 30, 11, 11, 35, 80, 75, 74, 81, 24, 65, 21, 33, 67, 71, 66, 22, 0, 75, 74, 83, 19, 68, 9, 0, 67, 8, 64, 10, 9, 9, 17, 24, 13, 6, 11, 62, 62, 62, 62, 8, 64, 3, 13, 64, 11, 13, 5, 67, 38, 66, 108, 104, 57, 9, 16, 5, 6, 11, 18, 48, 28, 12, 65, 1, 2, 19, 24, 104, 3, 5, 80, 65, 74, 7, 7, 71, 19, 12, 12, 90, 8, 79, 75, 5, 89, 7, 8, 5, 79, 100, 64, 77, 74, 79, 93, 89, 76, 126, 62, 126, 86, 89, 96, 111, 98, 109, 126, 121, 116, 95, 122, 89, 62, 110, 65, 0, 71, 76, 77, 75, 80, 79, 80, 95, 96, 92, 95, 96, 120, 75, 77, 99, 73, 80, 81, 87, 91, 97, 94, 100, 98, 85, 100, 103, 94, 88, 15, 34, 22, 10, 11, 17, 8, 4, 4, 30, 20, 33, 24, 22, 19, 28, 19, 25, 21, 18, 18, 47, 39, 34, 30, 35, 14, 5, 3, 2, 71, 54, 29, 2, 65, 14, 2, 73, 67, 3, 40, 28, 9, 0, 18, 5, 4, 67, 67, 62, 68, 3, 8, 9, 8, 5, 17, 21, 8, 18, 22, 32, 67, 2, 73, 64, 26, 72, 74, 71, 3, 4, 69, 77, 69, 80, 69, 71, 88, 14, 13, 30, 5, 66, 13, 8, 1, 7, 6, 1, 75, 0, 65, 69, 67, 16, 98, 81, 16, 84, 67, 74, 68, 10, 74, 76, 4, 5, 92, 74, 99, 17, 20, 25, 4, 69, 65, 68, 73, 77, 85, 87, 87, 100, 95, 97, 118, 110, 117, 126, 109, 108, 102, 99, 109, 96, 103, 100, 88, 95, 68, 71, 73, 74, 93, 85, 86, 105, 92, 100, 101, 121, 105, 109, 112, 81, 79, 116, 82, 85, 95, 110, 99, 106, 108, 91, 107, 99, 103, 111, 109, 113, 100, 106, 111, 71, 64, 14, 2, 30, 40, 0, 8, 17, 21, 5, 19, 36, 4, 64, 27, 75, 106, 126, 126, 126, 126, 126, 126, 6, 39, 30, 27, 17, 31, 17, 9, 7, 1, 72, 67, 11, 2, 24, 34, 2, 4, 11, 15, 6, 13, 28, 7, 0, 27, 75, 106, 126, 126, 126, 126, 126, 126 }, { 22, 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 86, 95, 18, 60, 11, 27, 70, 9, 21, 66, 75, 2, 82, 107, 75, 112, 126, 126, 126, 62, 5, 64, 9, 21, 66, 85, 10, 11, 65, 0, 67, 67, 64, 80, 78, 91, 5, 65, 68, 4, 72, 66, 80, 0, 74, 72, 80, 6, 2, 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 12, 70, 85, 46, 23, 15, 56, 62, 29, 31, 20, 32, 13, 12, 38, 80, 74, 74, 80, 24, 65, 22, 35, 67, 70, 65, 24, 0, 75, 74, 83, 19, 68, 9, 1, 66, 9, 0, 11, 10, 10, 18, 26, 14, 8, 12, 62, 62, 62, 62, 9, 0, 4, 14, 64, 11, 13, 5, 66, 40, 66, 110, 106, 60, 9, 17, 5, 6, 11, 19, 50, 29, 13, 66, 1, 2, 20, 24, 105, 4, 5, 80, 66, 75, 6, 6, 72, 19, 12, 12, 91, 9, 80, 75, 4, 89, 7, 7, 4, 81, 103, 65, 78, 76, 81, 95, 91, 78, 126, 62, 126, 88, 91, 99, 114, 100, 112, 126, 125, 118, 97, 125, 91, 62, 111, 67, 65, 73, 78, 79, 77, 82, 81, 82, 98, 98, 94, 96, 98, 122, 75, 77, 99, 74, 81, 82, 89, 92, 98, 95, 101, 99, 85, 101, 103, 95, 87, 16, 35, 22, 10, 12, 17, 9, 5, 5, 32, 22, 33, 25, 22, 19, 29, 20, 27, 23, 20, 18, 48, 40, 35, 31, 36, 14, 6, 4, 3, 71, 55, 29, 2, 66, 14, 2, 73, 66, 3, 40, 27, 8, 64, 18, 5, 5, 67, 66, 62, 66, 4, 10, 11, 10, 6, 18, 22, 9, 20, 23, 34, 66, 3, 72, 0, 28, 72, 74, 70, 4, 5, 69, 78, 69, 81, 69, 71, 88, 14, 13, 31, 5, 66, 13, 8, 1, 7, 6, 1, 76, 0, 65, 69, 67, 17, 99, 82, 16, 85, 67, 74, 69, 10, 74, 77, 4, 5, 93, 74, 100, 16, 19, 24, 2, 71, 67, 70, 76, 79, 88, 90, 90, 103, 98, 99, 123, 114, 121, 126, 112, 111, 105, 101, 111, 98, 104, 101, 88, 95, 71, 73, 75, 76, 96, 88, 88, 108, 94, 102, 103, 124, 107, 111, 114, 81, 79, 118, 83, 86, 96, 112, 101, 108, 109, 92, 109, 100, 103, 113, 110, 113, 102, 108, 113, 70, 64, 15, 2, 31, 41, 1, 9, 18, 22, 5, 20, 37, 4, 64, 26, 77, 109, 126, 126, 126, 126, 126, 126, 7, 39, 30, 27, 17, 32, 17, 10, 8, 1, 72, 67, 11, 2, 25, 35, 2, 4, 12, 16, 6, 14, 29, 7, 0, 26, 77, 109, 126, 126, 126, 126, 126, 126 }, { 21, 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 88, 97, 17, 60, 11, 29, 70, 10, 22, 66, 75, 2, 82, 108, 76, 115, 126, 126, 126, 62, 6, 64, 10, 22, 66, 86, 11, 11, 66, 0, 67, 67, 64, 80, 78, 91, 5, 65, 67, 4, 72, 65, 80, 0, 74, 72, 80, 6, 2, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 11, 71, 85, 49, 24, 16, 57, 62, 30, 33, 22, 33, 14, 13, 40, 80, 74, 73, 80, 24, 65, 23, 37, 67, 69, 65, 26, 0, 76, 75, 84, 19, 68, 10, 1, 65, 10, 0, 12, 10, 11, 20, 27, 14, 9, 13, 62, 62, 62, 62, 9, 0, 4, 14, 64, 11, 14, 6, 66, 42, 66, 112, 108, 62, 9, 17, 5, 6, 11, 19, 51, 30, 13, 66, 1, 2, 20, 25, 106, 4, 5, 81, 66, 76, 6, 6, 72, 19, 12, 12, 92, 9, 81, 76, 4, 89, 7, 7, 4, 83, 106, 66, 80, 78, 84, 97, 94, 80, 126, 62, 126, 90, 93, 101, 117, 103, 115, 126, 126, 121, 99, 126, 93, 62, 113, 69, 67, 75, 81, 82, 79, 84, 83, 84, 100, 100, 96, 98, 99, 124, 75, 77, 100, 75, 82, 83, 90, 93, 99, 96, 103, 100, 86, 102, 104, 95, 87, 18, 36, 23, 10, 12, 18, 9, 5, 5, 34, 23, 33, 25, 23, 20, 30, 21, 28, 24, 22, 19, 48, 40, 35, 31, 37, 15, 6, 4, 3, 71, 55, 29, 1, 66, 14, 2, 73, 66, 3, 39, 27, 7, 65, 18, 5, 5, 67, 66, 62, 65, 6, 11, 12, 11, 7, 20, 24, 10, 21, 25, 36, 66, 4, 72, 1, 30, 71, 74, 70, 4, 5, 69, 78, 69, 81, 69, 71, 89, 15, 14, 32, 5, 66, 14, 8, 1, 7, 6, 1, 76, 0, 65, 69, 67, 17, 100, 83, 16, 86, 68, 75, 69, 10, 75, 78, 4, 5, 94, 74, 101, 15, 18, 23, 1, 73, 69, 72, 78, 82, 90, 93, 93, 106, 100, 101, 126, 118, 125, 126, 116, 114, 107, 103, 114, 100, 106, 103, 89, 96, 73, 76, 78, 79, 99, 90, 91, 111, 97, 105, 106, 126, 109, 113, 115, 82, 80, 119, 85, 88, 98, 114, 102, 110, 111, 93, 110, 101, 104, 114, 111, 114, 103, 109, 114, 70, 0, 15, 3, 32, 42, 1, 9, 18, 22, 5, 20, 38, 4, 64, 25, 79, 111, 126, 126, 126, 126, 126, 126, 7, 39, 30, 27, 17, 32, 18, 10, 8, 2, 72, 67, 12, 3, 26, 36, 2, 5, 12, 16, 6, 14, 29, 7, 0, 25, 79, 111, 126, 126, 126, 126, 126, 126 }, { 20, 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 89, 99, 16, 60, 11, 31, 70, 10, 23, 66, 76, 2, 83, 110, 77, 117, 126, 126, 126, 62, 7, 64, 10, 23, 66, 86, 12, 11, 66, 0, 67, 66, 64, 80, 78, 91, 5, 65, 67, 4, 72, 65, 80, 0, 74, 72, 80, 6, 2, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 10, 71, 85, 51, 25, 17, 59, 62, 32, 34, 24, 35, 15, 14, 42, 80, 74, 73, 80, 24, 65, 24, 39, 67, 68, 64, 28, 0, 76, 75, 84, 19, 68, 10, 2, 64, 11, 1, 13, 11, 12, 21, 29, 15, 10, 14, 62, 62, 62, 62, 9, 0, 4, 15, 64, 11, 14, 6, 65, 44, 66, 114, 110, 62, 9, 17, 5, 6, 11, 20, 53, 31, 13, 66, 1, 2, 21, 26, 107, 4, 5, 81, 67, 77, 5, 6, 73, 19, 12, 12, 93, 9, 82, 77, 3, 89, 7, 6, 3, 85, 109, 67, 82, 80, 86, 99, 96, 82, 126, 62, 126, 92, 95, 103, 120, 105, 118, 126, 126, 124, 101, 126, 95, 62, 115, 71, 69, 77, 83, 84, 81, 86, 85, 86, 102, 102, 98, 100, 101, 126, 75, 77, 100, 76, 83, 84, 91, 94, 100, 97, 105, 101, 86, 103, 105, 96, 86, 19, 37, 23, 10, 12, 19, 10, 6, 6, 36, 24, 33, 25, 23, 21, 31, 22, 30, 26, 24, 20, 49, 41, 36, 32, 38, 15, 6, 4, 4, 71, 56, 29, 0, 66, 14, 2, 73, 65, 3, 39, 26, 6, 66, 18, 5, 5, 67, 66, 62, 64, 7, 12, 14, 13, 8, 22, 26, 11, 23, 27, 38, 65, 5, 71, 2, 32, 71, 74, 70, 5, 6, 69, 78, 69, 82, 69, 71, 89, 15, 14, 33, 5, 66, 14, 8, 1, 7, 6, 1, 77, 0, 65, 69, 67, 18, 101, 84, 16, 87, 69, 76, 70, 10, 76, 79, 4, 5, 95, 74, 102, 14, 17, 22, 64, 75, 71, 74, 80, 84, 93, 96, 96, 109, 103, 103, 126, 122, 126, 126, 119, 117, 110, 105, 116, 102, 108, 104, 90, 97, 75, 78, 80, 81, 102, 93, 93, 114, 99, 107, 108, 126, 111, 115, 117, 83, 81, 121, 86, 89, 100, 116, 104, 112, 113, 94, 112, 102, 105, 115, 112, 115, 105, 110, 115, 70, 0, 16, 3, 33, 43, 1, 10, 19, 23, 5, 21, 39, 4, 64, 24, 81, 114, 126, 126, 126, 126, 126, 126, 7, 39, 30, 27, 17, 33, 18, 10, 8, 2, 72, 67, 12, 3, 27, 37, 2, 5, 12, 16, 6, 14, 30, 7, 0, 24, 81, 114, 126, 126, 126, 126, 126, 126 }, { 18, 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 91, 102, 15, 60, 10, 32, 71, 10, 23, 67, 77, 2, 84, 112, 79, 120, 126, 126, 126, 62, 7, 64, 10, 23, 67, 87, 12, 11, 67, 0, 67, 66, 65, 81, 78, 91, 4, 65, 67, 4, 72, 65, 80, 0, 74, 73, 80, 6, 2, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 9, 72, 86, 53, 26, 18, 60, 62, 33, 35, 25, 36, 16, 15, 44, 80, 74, 73, 80, 24, 65, 24, 41, 67, 68, 64, 29, 64, 77, 76, 85, 19, 68, 10, 2, 64, 11, 1, 13, 11, 12, 22, 30, 15, 11, 15, 62, 62, 62, 62, 9, 0, 4, 15, 65, 11, 14, 6, 65, 46, 66, 116, 112, 62, 9, 17, 5, 6, 11, 20, 54, 31, 13, 67, 0, 2, 21, 26, 109, 4, 5, 82, 68, 79, 4, 5, 74, 19, 12, 11, 95, 9, 83, 78, 2, 89, 6, 5, 2, 88, 113, 69, 84, 83, 89, 102, 99, 84, 126, 62, 126, 95, 97, 106, 124, 108, 122, 126, 126, 126, 103, 126, 97, 62, 117, 74, 72, 80, 86, 87, 84, 89, 88, 88, 105, 105, 100, 102, 103, 126, 75, 78, 101, 77, 85, 86, 93, 96, 102, 99, 107, 102, 87, 104, 106, 97, 86, 20, 37, 23, 10, 12, 19, 10, 6, 6, 38, 25, 33, 25, 23, 21, 31, 23, 31, 27, 25, 20, 49, 41, 36, 32, 39, 15, 6, 4, 4, 72, 56, 28, 64, 67, 14, 2, 73, 65, 2, 38, 25, 4, 67, 17, 5, 5, 67, 66, 62, 0, 8, 13, 15, 14, 9, 23, 27, 12, 24, 28, 40, 65, 5, 71, 3, 33, 71, 74, 70, 5, 6, 69, 79, 70, 83, 69, 72, 90, 15, 14, 34, 5, 67, 14, 8, 0, 7, 6, 1, 78, 0, 65, 69, 67, 18, 102, 85, 16, 89, 70, 77, 71, 9, 77, 80, 4, 4, 97, 75, 104, 12, 15, 21, 66, 77, 74, 77, 83, 87, 96, 99, 99, 113, 106, 105, 126, 126, 126, 126, 123, 121, 113, 108, 119, 104, 110, 106, 91, 98, 78, 81, 83, 84, 105, 96, 96, 118, 102, 110, 111, 126, 113, 117, 119, 84, 82, 123, 88, 91, 102, 119, 106, 114, 115, 96, 114, 104, 106, 117, 113, 116, 107, 112, 117, 70, 0, 16, 3, 34, 44, 1, 10, 19, 23, 5, 21, 39, 4, 65, 22, 83, 117, 126, 126, 126, 126, 126, 126, 7, 39, 30, 27, 17, 33, 18, 10, 8, 2, 72, 67, 12, 3, 27, 37, 2, 5, 12, 16, 6, 14, 30, 6, 64, 22, 83, 117, 126, 126, 126, 126, 126, 126 }, { 17, 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 92, 104, 14, 61, 10, 34, 71, 11, 24, 67, 77, 3, 84, 113, 80, 122, 126, 126, 126, 62, 8, 0, 11, 24, 67, 87, 13, 11, 67, 1, 66, 65, 65, 81, 77, 90, 4, 64, 66, 4, 71, 64, 79, 1, 73, 73, 79, 7, 2, 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 9, 72, 86, 56, 28, 20, 62, 62, 35, 37, 27, 38, 18, 17, 47, 80, 73, 72, 79, 24, 65, 25, 44, 66, 67, 0, 31, 64, 77, 76, 85, 20, 68, 11, 3, 0, 12, 2, 14, 12, 13, 24, 32, 16, 13, 17, 62, 62, 62, 62, 10, 1, 5, 16, 65, 12, 15, 7, 64, 49, 65, 118, 113, 62, 9, 18, 5, 7, 12, 21, 56, 32, 14, 67, 0, 2, 22, 27, 110, 5, 5, 82, 68, 80, 4, 5, 74, 19, 12, 11, 96, 10, 83, 78, 2, 89, 6, 5, 2, 90, 116, 70, 85, 85, 91, 104, 101, 86, 126, 62, 126, 97, 98, 108, 126, 110, 125, 126, 126, 126, 105, 126, 99, 62, 118, 76, 74, 82, 88, 89, 86, 91, 90, 90, 107, 107, 101, 103, 104, 126, 75, 78, 101, 77, 86, 87, 94, 97, 103, 100, 108, 103, 87, 105, 106, 97, 85, 22, 38, 24, 10, 13, 20, 11, 7, 7, 41, 27, 34, 26, 24, 22, 32, 25, 33, 29, 27, 21, 50, 42, 37, 33, 40, 16, 7, 5, 5, 72, 57, 28, 64, 67, 15, 3, 73, 64, 2, 38, 25, 3, 68, 17, 6, 6, 66, 65, 62, 2, 10, 15, 17, 16, 11, 25, 29, 14, 26, 30, 43, 64, 6, 70, 5, 35, 70, 73, 69, 6, 7, 68, 79, 70, 83, 69, 72, 90, 16, 15, 35, 6, 67, 15, 9, 0, 8, 7, 1, 78, 1, 64, 68, 66, 19, 102, 86, 16, 90, 70, 77, 71, 9, 77, 80, 4, 4, 98, 75, 105, 11, 14, 21, 67, 78, 76, 79, 85, 89, 98, 101, 101, 116, 108, 107, 126, 126, 126, 126, 126, 124, 115, 110, 121, 105, 111, 107, 91, 98, 80, 83, 85, 86, 107, 98, 98, 121, 104, 112, 113, 126, 114, 118, 120, 84, 82, 124, 89, 92, 103, 121, 107, 115, 116, 97, 115, 105, 106, 118, 113, 116, 108, 113, 118, 69, 1, 17, 4, 36, 46, 2, 11, 20, 24, 6, 22, 40, 4, 65, 21, 85, 119, 126, 126, 126, 126, 126, 126, 8, 39, 31, 28, 18, 34, 19, 11, 9, 3, 71, 66, 13, 4, 28, 38, 3, 6, 13, 17, 6, 15, 31, 6, 64, 21, 85, 119, 126, 126, 126, 126, 126, 126 }, { 16, 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 93, 106, 13, 61, 10, 36, 71, 11, 25, 67, 78, 3, 85, 115, 81, 125, 126, 126, 126, 62, 9, 0, 11, 25, 67, 87, 14, 11, 68, 1, 66, 64, 65, 81, 77, 90, 4, 64, 66, 4, 71, 64, 79, 1, 73, 73, 79, 7, 2, 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 8, 72, 86, 58, 29, 21, 62, 62, 36, 38, 29, 39, 19, 18, 49, 80, 73, 72, 79, 24, 65, 26, 46, 66, 66, 1, 33, 64, 77, 76, 86, 20, 68, 11, 4, 1, 13, 3, 15, 12, 14, 25, 33, 17, 14, 18, 62, 62, 62, 62, 10, 1, 5, 16, 65, 12, 15, 7, 64, 51, 65, 120, 115, 62, 9, 18, 5, 7, 12, 21, 58, 33, 14, 67, 0, 2, 23, 28, 111, 5, 5, 82, 69, 81, 3, 5, 75, 19, 12, 11, 97, 10, 84, 79, 1, 89, 6, 4, 1, 92, 119, 71, 87, 87, 93, 106, 103, 88, 126, 62, 126, 99, 100, 110, 126, 112, 126, 126, 126, 126, 107, 126, 101, 62, 120, 78, 76, 84, 90, 91, 88, 93, 92, 92, 109, 109, 103, 105, 106, 126, 75, 78, 102, 78, 87, 88, 95, 98, 104, 101, 110, 104, 87, 106, 107, 98, 84, 23, 39, 24, 10, 13, 21, 12, 8, 8, 43, 28, 34, 26, 24, 23, 33, 26, 35, 31, 29, 22, 51, 43, 38, 34, 41, 16, 7, 5, 6, 72, 58, 28, 65, 67, 15, 3, 73, 64, 2, 38, 24, 2, 69, 17, 6, 6, 66, 65, 62, 3, 11, 16, 19, 18, 12, 27, 31, 15, 28, 32, 45, 0, 7, 70, 6, 37, 70, 73, 69, 7, 7, 68, 79, 70, 84, 69, 72, 90, 16, 15, 36, 6, 67, 15, 9, 0, 8, 7, 1, 79, 1, 64, 68, 66, 20, 103, 87, 16, 91, 71, 78, 72, 9, 78, 81, 4, 4, 99, 75, 106, 10, 13, 20, 69, 80, 78, 81, 87, 91, 101, 104, 104, 119, 111, 109, 126, 126, 126, 126, 126, 126, 118, 112, 124, 107, 113, 108, 92, 99, 82, 85, 88, 88, 110, 101, 101, 124, 106, 115, 115, 126, 116, 120, 122, 85, 83, 126, 90, 93, 105, 123, 109, 117, 118, 98, 117, 106, 107, 119, 114, 117, 110, 114, 119, 69, 1, 18, 4, 37, 47, 2, 12, 21, 25, 6, 22, 41, 4, 65, 20, 87, 122, 126, 126, 126, 126, 126, 126, 8, 39, 31, 28, 18, 35, 19, 11, 9, 3, 71, 66, 13, 4, 29, 39, 3, 6, 13, 17, 6, 15, 32, 6, 64, 20, 87, 122, 126, 126, 126, 126, 126, 126 }, { 15, 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 95, 108, 12, 61, 10, 38, 71, 12, 26, 67, 78, 3, 85, 116, 82, 126, 126, 126, 126, 62, 10, 0, 12, 26, 67, 88, 15, 11, 68, 1, 66, 64, 65, 81, 77, 90, 4, 64, 65, 4, 71, 0, 79, 1, 73, 73, 79, 7, 2, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 7, 73, 86, 61, 30, 22, 62, 62, 38, 40, 31, 41, 20, 19, 51, 80, 73, 71, 79, 24, 65, 27, 48, 66, 65, 1, 35, 64, 78, 77, 86, 20, 68, 12, 4, 2, 14, 3, 16, 13, 15, 27, 35, 17, 15, 19, 62, 62, 62, 62, 10, 1, 5, 17, 65, 12, 16, 8, 0, 53, 65, 122, 117, 62, 9, 18, 5, 7, 12, 22, 59, 34, 14, 67, 0, 2, 23, 29, 112, 5, 5, 83, 69, 82, 3, 5, 75, 19, 12, 11, 98, 10, 85, 80, 1, 89, 6, 4, 1, 94, 122, 72, 89, 89, 96, 108, 106, 90, 126, 62, 126, 101, 102, 112, 126, 115, 126, 126, 126, 126, 109, 126, 103, 62, 122, 80, 78, 86, 93, 94, 90, 95, 94, 94, 111, 111, 105, 107, 107, 126, 75, 78, 102, 79, 88, 89, 96, 99, 105, 102, 112, 105, 88, 107, 108, 98, 84, 25, 40, 25, 10, 13, 22, 12, 8, 8, 45, 29, 34, 26, 25, 24, 34, 27, 36, 32, 31, 23, 51, 43, 38, 34, 42, 17, 7, 5, 6, 72, 58, 28, 66, 67, 15, 3, 73, 0, 2, 37, 24, 1, 70, 17, 6, 6, 66, 65, 62, 4, 13, 17, 20, 19, 13, 29, 33, 16, 29, 34, 47, 0, 8, 69, 7, 39, 69, 73, 69, 7, 8, 68, 79, 70, 84, 69, 72, 91, 17, 16, 37, 6, 67, 16, 9, 0, 8, 7, 1, 79, 1, 64, 68, 66, 20, 104, 88, 16, 92, 72, 79, 72, 9, 79, 82, 4, 4, 100, 75, 107, 9, 12, 19, 70, 82, 80, 83, 89, 94, 103, 107, 107, 122, 113, 111, 126, 126, 126, 126, 126, 126, 120, 114, 126, 109, 115, 110, 93, 100, 84, 88, 90, 91, 113, 103, 103, 126, 109, 117, 118, 126, 118, 122, 123, 86, 84, 126, 92, 95, 107, 125, 110, 119, 120, 99, 118, 107, 108, 120, 115, 118, 111, 115, 120, 69, 2, 18, 5, 38, 48, 2, 12, 21, 25, 6, 23, 42, 4, 65, 19, 89, 124, 126, 126, 126, 126, 126, 126, 8, 39, 31, 28, 18, 35, 20, 11, 9, 4, 71, 66, 14, 5, 30, 40, 3, 7, 13, 17, 6, 15, 32, 6, 64, 19, 89, 124, 126, 126, 126, 126, 126, 126 }, }, { { 62, 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 47, 62, 62, 12, 1, 99, 47, 85, 102, 6, 6, 73, 6, 23, 53, 62, 62, 21, 97, 126, 117, 74, 85, 102, 6, 93, 88, 19, 8, 89, 103, 116, 6, 5, 84, 96, 0, 85, 106, 0, 75, 90, 101, 8, 79, 75, 97, 13, 3, 22, 0, 0, 0, 83, 86, 97, 72, 22, 1, 29, 88, 126, 126, 91, 95, 84, 86, 89, 91, 126, 76, 103, 90, 126, 80, 76, 84, 78, 8, 2, 83, 126, 79, 104, 91, 126, 65, 79, 72, 92, 7, 68, 71, 98, 86, 88, 82, 72, 67, 72, 89, 69, 4, 66, 6, 71, 71, 5, 74, 19, 69, 1, 12, 16, 21, 22, 10, 76, 78, 83, 11, 67, 90, 67, 72, 75, 80, 83, 64, 32, 64, 94, 75, 0, 74, 28, 36, 91, 65, 69, 77, 66, 1, 68, 81, 33, 56, 40, 74, 66, 124, 26, 62, 62, 126, 24, 21, 29, 34, 32, 26, 21, 23, 30, 20, 27, 16, 8, 5, 3, 19, 19, 21, 15, 7, 11, 26, 14, 5, 15, 18, 69, 30, 0, 62, 62, 62, 53, 62, 62, 62, 62, 46, 38, 34, 30, 48, 43, 73, 29, 32, 19, 47, 27, 27, 35, 42, 43, 51, 47, 21, 93, 7, 6, 25, 126, 115, 82, 1, 10, 4, 85, 89, 94, 92, 126, 100, 6, 67, 71, 77, 85, 88, 104, 98, 126, 82, 15, 2, 66, 70, 75, 79, 83, 92, 108, 79, 69, 75, 5, 5, 78, 83, 81, 99, 81, 25, 1, 5, 4, 73, 76, 86, 83, 87, 62, 126, 126, 120, 126, 114, 117, 118, 117, 113, 118, 120, 124, 94, 102, 99, 106, 126, 92, 6, 86, 94, 91, 77, 71, 73, 64, 81, 64, 6, 67, 68, 67, 68, 77, 64, 68, 78, 8, 4, 65, 9, 19, 3, 70, 76, 86, 70, 64, 70, 8, 7, 69, 65, 74, 9, 9, 76, 82, 77, 77, 21, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 52, 62, 62, 62, 62, 62, 62, 48, 62, 62, 46, 25, 18, 9, 79, 62, 62, 62, 62, 48, 48, 38, 41, 47, 45, 35, 22, 35, 16, 1, 32, 37, 39, 40, 47, 33, 34, 22, 21, 3, 11, 3, 78, 123, 10, 7, 2, 30, 13, 2, 78, 74, 72, 72, 75, 71, 0, 70, 75, 72, 67, 10, 4, 11, 68, 62, 62, 62, 62, 56, 51, 40, 25, 64, 71, 26, 19, 14, 7, 4, 0, 67, 68, 79, 78, 74, 72, 72, 75, 71, 0, 70, 75, 72, 67, 10, 4, 11, 68, 62, 62, 62, 62, 56, 51, 40, 25, 64 }, { 62, 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 46, 62, 62, 13, 2, 97, 46, 84, 100, 6, 6, 71, 6, 22, 52, 62, 60, 19, 97, 125, 115, 73, 84, 100, 6, 92, 87, 20, 8, 88, 102, 114, 5, 4, 84, 96, 0, 84, 105, 0, 75, 89, 100, 8, 78, 74, 96, 14, 3, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 29, 87, 125, 124, 89, 94, 82, 84, 88, 89, 125, 75, 101, 89, 124, 80, 76, 84, 78, 9, 2, 82, 124, 78, 103, 90, 125, 65, 78, 72, 91, 8, 68, 70, 97, 85, 87, 81, 71, 66, 71, 88, 68, 5, 66, 6, 70, 70, 5, 73, 20, 68, 1, 13, 17, 22, 23, 11, 76, 77, 82, 11, 67, 89, 67, 71, 74, 79, 81, 1, 33, 1, 92, 75, 64, 73, 29, 37, 91, 65, 68, 77, 65, 1, 67, 79, 33, 56, 41, 72, 67, 122, 25, 62, 62, 125, 24, 21, 29, 34, 32, 26, 21, 23, 30, 20, 27, 16, 8, 5, 3, 19, 19, 21, 15, 7, 11, 26, 14, 4, 15, 18, 69, 29, 0, 62, 62, 62, 52, 62, 62, 62, 62, 45, 37, 32, 29, 46, 42, 74, 28, 31, 18, 46, 27, 27, 34, 41, 42, 50, 46, 20, 93, 7, 6, 24, 125, 113, 80, 2, 10, 4, 84, 88, 93, 91, 125, 98, 7, 66, 70, 76, 83, 87, 102, 97, 124, 81, 16, 3, 65, 69, 74, 78, 82, 91, 106, 78, 67, 74, 6, 5, 77, 82, 80, 98, 80, 26, 2, 6, 5, 72, 75, 85, 82, 86, 62, 125, 125, 118, 125, 112, 115, 116, 115, 111, 116, 118, 121, 93, 101, 98, 105, 123, 91, 5, 85, 93, 90, 76, 71, 72, 64, 80, 64, 6, 67, 68, 66, 68, 77, 64, 68, 77, 8, 4, 65, 9, 19, 3, 70, 75, 84, 70, 64, 69, 8, 7, 69, 65, 73, 9, 9, 75, 81, 76, 76, 20, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 50, 62, 62, 62, 62, 62, 62, 47, 60, 60, 45, 24, 17, 9, 79, 62, 62, 62, 60, 46, 47, 37, 39, 46, 43, 34, 20, 33, 15, 0, 31, 36, 37, 39, 46, 32, 33, 21, 20, 2, 11, 3, 78, 122, 9, 6, 1, 29, 12, 1, 77, 73, 71, 71, 73, 70, 1, 69, 73, 71, 66, 11, 5, 12, 67, 62, 62, 62, 62, 54, 50, 38, 24, 65, 70, 27, 20, 15, 8, 5, 1, 66, 67, 78, 77, 73, 71, 71, 73, 70, 1, 69, 73, 71, 66, 11, 5, 12, 67, 62, 62, 62, 62, 54, 50, 38, 24, 65 }, { 62, 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 44, 60, 62, 14, 2, 95, 44, 84, 99, 6, 6, 70, 5, 21, 51, 60, 57, 17, 98, 123, 114, 73, 84, 99, 6, 92, 86, 20, 8, 87, 101, 113, 4, 3, 84, 96, 0, 84, 104, 0, 75, 89, 100, 8, 78, 74, 95, 14, 3, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 29, 86, 124, 122, 88, 93, 80, 82, 87, 88, 123, 74, 100, 88, 122, 81, 76, 84, 78, 9, 2, 81, 122, 78, 102, 89, 123, 65, 78, 72, 91, 8, 68, 70, 96, 85, 86, 81, 71, 66, 71, 87, 67, 5, 66, 6, 70, 70, 5, 73, 20, 68, 1, 13, 17, 22, 23, 11, 77, 76, 81, 10, 67, 89, 67, 70, 74, 79, 80, 2, 34, 3, 90, 76, 65, 73, 29, 37, 92, 65, 68, 78, 64, 1, 67, 78, 33, 56, 41, 71, 68, 121, 24, 62, 62, 124, 24, 21, 29, 33, 31, 26, 21, 23, 29, 19, 26, 16, 8, 5, 3, 18, 18, 20, 15, 7, 11, 25, 13, 3, 14, 17, 69, 28, 64, 62, 62, 62, 50, 60, 62, 62, 62, 44, 35, 30, 27, 44, 40, 75, 27, 30, 16, 45, 26, 26, 33, 39, 40, 48, 44, 18, 93, 6, 5, 22, 124, 112, 79, 3, 10, 4, 83, 87, 92, 90, 123, 97, 8, 65, 69, 75, 82, 86, 101, 96, 122, 80, 16, 3, 65, 69, 73, 77, 81, 90, 105, 78, 66, 73, 6, 5, 76, 81, 80, 97, 79, 26, 3, 6, 5, 71, 74, 84, 81, 85, 62, 124, 123, 116, 123, 111, 114, 114, 113, 110, 114, 116, 119, 92, 100, 97, 104, 120, 91, 4, 85, 92, 89, 76, 71, 72, 64, 80, 64, 5, 67, 68, 65, 68, 77, 64, 68, 77, 8, 4, 65, 8, 18, 3, 70, 75, 83, 71, 64, 68, 7, 7, 69, 65, 73, 9, 9, 75, 80, 76, 76, 18, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 48, 62, 62, 62, 62, 62, 61, 45, 58, 58, 43, 23, 16, 8, 79, 62, 62, 62, 58, 44, 45, 35, 37, 44, 41, 32, 18, 31, 13, 64, 30, 35, 35, 37, 44, 30, 31, 20, 19, 1, 10, 2, 78, 121, 8, 5, 64, 28, 11, 0, 77, 73, 70, 70, 72, 69, 2, 69, 72, 70, 65, 11, 6, 13, 66, 62, 62, 62, 60, 52, 48, 36, 22, 66, 69, 27, 20, 16, 9, 6, 1, 65, 67, 77, 77, 73, 70, 70, 72, 69, 2, 69, 72, 70, 65, 11, 6, 13, 66, 62, 62, 62, 60, 52, 48, 36, 22, 66 }, { 62, 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 42, 59, 61, 14, 2, 93, 43, 84, 97, 6, 5, 69, 4, 20, 50, 58, 53, 15, 99, 121, 112, 73, 84, 97, 6, 91, 85, 21, 8, 86, 100, 112, 3, 2, 84, 97, 0, 84, 103, 0, 76, 89, 100, 8, 78, 74, 94, 15, 3, 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 28, 86, 123, 120, 87, 92, 79, 81, 86, 87, 121, 73, 99, 87, 120, 82, 76, 84, 78, 10, 2, 80, 120, 78, 101, 88, 121, 65, 78, 72, 91, 9, 68, 69, 95, 85, 85, 81, 71, 66, 70, 86, 67, 5, 66, 6, 70, 70, 5, 73, 20, 68, 1, 14, 17, 23, 23, 12, 77, 76, 80, 10, 67, 89, 67, 69, 74, 78, 79, 3, 35, 4, 88, 76, 66, 72, 29, 37, 93, 65, 67, 78, 64, 1, 67, 77, 33, 56, 41, 70, 69, 119, 23, 62, 62, 122, 24, 21, 28, 32, 31, 25, 20, 23, 29, 18, 25, 16, 8, 5, 2, 18, 17, 19, 14, 7, 11, 24, 13, 2, 14, 16, 69, 27, 64, 62, 62, 61, 49, 58, 62, 62, 62, 43, 33, 28, 26, 42, 38, 77, 26, 29, 14, 44, 25, 25, 32, 38, 38, 46, 42, 17, 93, 5, 4, 21, 122, 110, 77, 3, 10, 4, 82, 86, 91, 89, 121, 96, 9, 64, 68, 75, 81, 85, 99, 95, 120, 80, 17, 4, 64, 68, 72, 77, 81, 89, 104, 78, 64, 72, 6, 5, 75, 81, 80, 96, 78, 27, 4, 7, 5, 70, 74, 83, 81, 85, 62, 122, 122, 115, 121, 110, 112, 113, 112, 108, 112, 114, 117, 92, 99, 97, 103, 117, 91, 3, 85, 91, 88, 76, 71, 72, 64, 79, 64, 4, 67, 68, 65, 68, 77, 64, 68, 77, 7, 4, 65, 7, 17, 3, 70, 75, 82, 72, 64, 67, 6, 7, 69, 65, 72, 9, 8, 74, 79, 76, 76, 17, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 46, 62, 62, 62, 62, 62, 59, 43, 56, 55, 41, 22, 15, 7, 79, 62, 62, 62, 56, 42, 43, 34, 35, 42, 39, 30, 16, 29, 11, 65, 29, 34, 33, 36, 42, 29, 29, 18, 17, 0, 9, 1, 78, 120, 7, 3, 65, 27, 10, 64, 77, 72, 70, 70, 71, 68, 3, 69, 71, 69, 64, 12, 7, 13, 65, 62, 62, 62, 58, 50, 46, 34, 20, 67, 69, 28, 21, 17, 9, 7, 2, 65, 66, 77, 77, 72, 70, 70, 71, 68, 3, 69, 71, 69, 64, 12, 7, 13, 65, 62, 62, 62, 58, 50, 46, 34, 20, 67 }, { 62, 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 40, 57, 60, 15, 2, 92, 41, 84, 96, 5, 5, 68, 3, 18, 48, 56, 50, 12, 100, 119, 111, 73, 84, 96, 5, 91, 84, 21, 7, 86, 99, 110, 2, 0, 85, 97, 0, 83, 102, 64, 76, 89, 100, 8, 78, 74, 94, 15, 3, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 28, 85, 122, 118, 86, 91, 77, 79, 86, 86, 119, 72, 98, 86, 117, 82, 77, 84, 79, 10, 1, 79, 117, 77, 101, 88, 119, 65, 78, 72, 91, 9, 68, 69, 94, 85, 85, 80, 71, 66, 70, 85, 66, 5, 67, 5, 70, 70, 5, 73, 20, 68, 1, 14, 17, 23, 23, 12, 78, 75, 80, 9, 67, 88, 67, 68, 73, 78, 77, 5, 36, 6, 86, 77, 67, 72, 30, 37, 94, 65, 67, 79, 0, 1, 67, 76, 33, 56, 41, 68, 70, 118, 22, 62, 62, 121, 23, 21, 28, 32, 30, 25, 20, 23, 28, 17, 24, 15, 8, 5, 2, 17, 17, 18, 14, 6, 10, 23, 12, 1, 13, 15, 69, 25, 65, 62, 62, 59, 47, 57, 62, 62, 62, 42, 31, 25, 24, 40, 36, 78, 24, 28, 13, 43, 24, 24, 30, 36, 36, 44, 41, 15, 93, 4, 3, 19, 121, 109, 76, 4, 10, 4, 81, 85, 90, 89, 119, 94, 10, 64, 68, 74, 79, 84, 98, 94, 117, 79, 17, 4, 64, 68, 71, 76, 80, 89, 103, 78, 0, 71, 6, 5, 74, 80, 80, 95, 77, 27, 5, 7, 5, 69, 73, 82, 80, 84, 62, 121, 120, 113, 120, 109, 111, 111, 110, 107, 111, 112, 114, 91, 98, 96, 102, 114, 90, 2, 84, 90, 88, 76, 71, 72, 65, 79, 65, 3, 67, 68, 64, 68, 77, 64, 68, 76, 7, 3, 65, 6, 16, 2, 70, 75, 81, 73, 65, 67, 6, 6, 69, 65, 72, 8, 8, 74, 79, 76, 76, 15, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 44, 62, 62, 62, 62, 62, 57, 41, 54, 53, 39, 20, 14, 6, 79, 62, 62, 62, 54, 40, 41, 32, 33, 40, 37, 28, 14, 26, 10, 67, 28, 33, 30, 34, 41, 27, 27, 17, 16, 64, 8, 0, 78, 119, 5, 2, 67, 25, 9, 65, 77, 72, 69, 69, 70, 68, 3, 68, 70, 68, 0, 12, 8, 14, 65, 62, 62, 60, 56, 48, 44, 31, 18, 69, 68, 28, 21, 17, 10, 7, 2, 64, 66, 76, 77, 72, 69, 69, 70, 68, 3, 68, 70, 68, 0, 12, 8, 14, 65, 62, 62, 60, 56, 48, 44, 31, 18, 69 }, { 62, 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 38, 56, 59, 16, 2, 90, 39, 83, 94, 5, 5, 67, 2, 17, 47, 54, 47, 10, 100, 117, 110, 73, 83, 94, 5, 91, 83, 21, 7, 85, 98, 109, 1, 64, 85, 97, 0, 83, 101, 64, 76, 89, 100, 8, 77, 74, 93, 16, 3, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 27, 85, 120, 115, 85, 90, 76, 78, 85, 85, 117, 71, 97, 85, 115, 83, 77, 84, 79, 10, 1, 78, 115, 77, 100, 87, 117, 65, 78, 72, 90, 9, 68, 68, 93, 84, 84, 80, 71, 65, 69, 84, 66, 5, 67, 5, 69, 70, 5, 73, 21, 68, 1, 15, 18, 23, 23, 12, 78, 75, 79, 9, 67, 88, 67, 67, 73, 77, 76, 6, 37, 7, 84, 77, 68, 71, 30, 37, 95, 65, 66, 79, 1, 1, 67, 74, 33, 56, 41, 67, 71, 116, 21, 62, 62, 120, 23, 21, 27, 31, 30, 25, 19, 23, 28, 16, 23, 15, 8, 5, 2, 17, 16, 17, 13, 6, 10, 22, 12, 0, 12, 15, 69, 24, 65, 62, 62, 58, 46, 55, 62, 62, 62, 41, 29, 23, 23, 38, 34, 79, 23, 27, 11, 42, 23, 23, 29, 35, 34, 42, 39, 14, 93, 3, 2, 17, 119, 107, 75, 4, 10, 4, 80, 84, 89, 88, 117, 93, 11, 0, 67, 73, 78, 83, 96, 93, 115, 78, 18, 5, 0, 67, 70, 75, 80, 88, 102, 77, 1, 70, 6, 5, 73, 80, 79, 94, 76, 27, 6, 7, 5, 68, 72, 81, 80, 83, 62, 120, 119, 112, 118, 108, 109, 110, 108, 105, 109, 110, 112, 90, 97, 95, 101, 111, 90, 1, 84, 89, 87, 76, 71, 72, 65, 78, 65, 2, 67, 68, 0, 68, 77, 64, 68, 76, 6, 3, 65, 5, 15, 2, 70, 75, 80, 73, 65, 66, 5, 6, 69, 65, 72, 8, 7, 74, 78, 76, 76, 14, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 42, 62, 62, 62, 62, 62, 55, 40, 52, 50, 37, 19, 13, 5, 79, 62, 62, 62, 52, 38, 39, 31, 31, 38, 35, 26, 12, 24, 8, 68, 27, 32, 28, 33, 39, 26, 25, 16, 15, 65, 7, 64, 78, 118, 4, 1, 68, 24, 8, 66, 77, 71, 69, 68, 69, 67, 4, 68, 69, 67, 1, 13, 9, 14, 64, 62, 62, 58, 54, 46, 42, 29, 16, 70, 68, 29, 22, 18, 11, 8, 3, 64, 66, 75, 77, 71, 69, 68, 69, 67, 4, 68, 69, 67, 1, 13, 9, 14, 64, 62, 62, 58, 54, 46, 42, 29, 16, 70 }, { 62, 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 37, 54, 58, 16, 3, 88, 38, 83, 93, 5, 4, 66, 1, 16, 46, 53, 43, 8, 101, 115, 108, 73, 83, 93, 5, 90, 82, 22, 7, 84, 97, 108, 64, 65, 85, 98, 0, 83, 101, 64, 77, 88, 100, 7, 77, 74, 92, 16, 3, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 27, 84, 119, 113, 84, 89, 74, 76, 84, 84, 115, 70, 96, 85, 113, 84, 77, 84, 79, 11, 1, 77, 113, 77, 99, 86, 115, 65, 78, 72, 90, 10, 69, 68, 93, 84, 83, 80, 70, 65, 69, 83, 65, 5, 67, 5, 69, 70, 5, 73, 21, 68, 1, 15, 18, 24, 24, 13, 79, 74, 78, 8, 67, 88, 67, 66, 73, 77, 75, 7, 37, 9, 83, 78, 69, 71, 30, 37, 95, 66, 66, 80, 1, 0, 66, 73, 33, 56, 42, 66, 72, 115, 20, 62, 62, 118, 23, 21, 27, 30, 29, 24, 19, 22, 27, 16, 23, 15, 7, 5, 1, 16, 15, 16, 13, 6, 10, 22, 11, 65, 12, 14, 69, 23, 66, 62, 62, 56, 44, 53, 62, 62, 62, 39, 27, 21, 21, 36, 32, 81, 22, 25, 9, 40, 22, 22, 28, 33, 32, 40, 37, 12, 93, 2, 1, 16, 118, 106, 73, 5, 10, 4, 79, 84, 89, 87, 116, 92, 12, 1, 66, 73, 77, 82, 95, 92, 113, 78, 18, 5, 0, 67, 69, 75, 79, 87, 101, 77, 3, 69, 6, 5, 73, 79, 79, 94, 76, 28, 6, 8, 5, 67, 72, 81, 79, 83, 62, 118, 117, 110, 116, 106, 108, 108, 107, 104, 107, 108, 110, 90, 96, 95, 101, 108, 90, 0, 84, 89, 86, 76, 71, 72, 65, 78, 65, 1, 67, 68, 0, 68, 77, 64, 68, 76, 6, 3, 65, 4, 14, 2, 70, 75, 79, 74, 65, 65, 4, 6, 69, 65, 71, 8, 7, 73, 77, 76, 76, 12, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 40, 62, 62, 62, 62, 62, 52, 38, 50, 48, 35, 18, 12, 4, 79, 62, 62, 62, 50, 36, 38, 29, 29, 36, 32, 24, 10, 22, 6, 69, 26, 30, 26, 31, 37, 24, 23, 14, 13, 66, 6, 65, 79, 117, 3, 64, 70, 23, 6, 67, 76, 71, 68, 68, 68, 66, 5, 68, 68, 66, 2, 13, 10, 15, 0, 62, 62, 56, 52, 44, 40, 27, 14, 71, 67, 29, 22, 19, 11, 9, 3, 0, 65, 75, 76, 71, 68, 68, 68, 66, 5, 68, 68, 66, 2, 13, 10, 15, 0, 62, 62, 56, 52, 44, 40, 27, 14, 71 }, { 62, 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 35, 53, 57, 17, 3, 87, 36, 83, 91, 4, 4, 65, 0, 15, 45, 51, 40, 5, 102, 113, 107, 73, 83, 91, 4, 90, 81, 22, 7, 84, 96, 106, 65, 66, 85, 98, 0, 82, 100, 65, 77, 88, 100, 7, 77, 74, 91, 17, 3, 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 26, 84, 118, 111, 83, 88, 73, 75, 83, 83, 113, 69, 95, 84, 110, 84, 78, 84, 80, 11, 1, 76, 110, 76, 99, 86, 113, 65, 78, 72, 90, 10, 69, 67, 92, 84, 82, 79, 70, 65, 68, 82, 65, 5, 68, 5, 69, 70, 5, 73, 21, 68, 1, 16, 18, 24, 24, 13, 79, 74, 78, 8, 67, 87, 67, 65, 72, 76, 73, 9, 38, 10, 81, 78, 70, 70, 31, 37, 96, 66, 65, 80, 2, 0, 66, 72, 33, 56, 42, 64, 73, 113, 19, 62, 62, 117, 23, 21, 26, 30, 29, 24, 18, 22, 27, 15, 22, 15, 7, 5, 1, 16, 15, 15, 12, 6, 10, 21, 11, 66, 11, 13, 69, 22, 66, 62, 62, 54, 43, 52, 62, 62, 62, 38, 25, 19, 20, 34, 30, 82, 21, 24, 8, 39, 21, 21, 26, 32, 30, 38, 36, 11, 93, 1, 0, 14, 116, 104, 72, 5, 10, 4, 78, 83, 88, 87, 114, 90, 13, 2, 66, 72, 75, 81, 93, 91, 110, 77, 19, 6, 1, 66, 68, 74, 79, 86, 100, 77, 4, 68, 6, 5, 72, 79, 79, 93, 75, 28, 7, 8, 5, 66, 71, 80, 79, 82, 62, 117, 116, 109, 115, 105, 106, 107, 105, 102, 105, 106, 107, 89, 95, 94, 100, 105, 89, 64, 83, 88, 85, 76, 71, 72, 65, 77, 66, 0, 67, 68, 1, 68, 77, 64, 68, 75, 5, 2, 65, 3, 13, 1, 70, 75, 78, 75, 66, 64, 4, 5, 69, 65, 71, 7, 6, 73, 77, 76, 76, 11, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 38, 62, 62, 62, 62, 62, 50, 36, 48, 45, 33, 17, 11, 3, 79, 62, 61, 62, 48, 34, 36, 28, 27, 34, 30, 22, 8, 20, 5, 71, 25, 29, 24, 30, 36, 23, 21, 13, 12, 67, 5, 66, 79, 116, 1, 65, 71, 21, 5, 68, 76, 70, 68, 67, 67, 65, 5, 67, 67, 65, 3, 14, 11, 15, 0, 62, 60, 54, 50, 42, 38, 24, 12, 72, 67, 30, 23, 19, 12, 10, 4, 0, 65, 74, 76, 70, 68, 67, 67, 65, 5, 67, 67, 65, 3, 14, 11, 15, 0, 62, 60, 54, 50, 42, 38, 24, 12, 72 }, { 62, 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 33, 51, 56, 17, 3, 85, 34, 83, 90, 4, 3, 64, 64, 13, 43, 49, 36, 3, 103, 111, 106, 73, 83, 90, 4, 90, 81, 22, 6, 83, 95, 105, 66, 68, 86, 99, 0, 82, 99, 65, 78, 88, 100, 7, 77, 74, 91, 17, 3, 22, 0, 0, 0, 78, 88, 97, 68, 16, 0, 26, 83, 117, 109, 82, 88, 71, 73, 83, 82, 111, 69, 94, 83, 108, 85, 78, 85, 80, 11, 0, 76, 108, 76, 98, 85, 112, 65, 78, 72, 90, 10, 69, 67, 91, 84, 82, 79, 70, 65, 68, 81, 64, 5, 68, 4, 69, 70, 4, 73, 21, 68, 1, 16, 18, 24, 24, 13, 80, 73, 77, 7, 67, 87, 67, 64, 72, 76, 72, 10, 39, 12, 79, 79, 71, 70, 31, 37, 97, 66, 65, 81, 2, 0, 66, 71, 33, 56, 42, 0, 74, 112, 18, 59, 62, 116, 22, 21, 26, 29, 28, 23, 18, 22, 26, 14, 21, 14, 7, 4, 0, 15, 14, 14, 12, 5, 9, 20, 10, 67, 10, 12, 69, 20, 67, 62, 62, 52, 41, 50, 60, 62, 62, 37, 23, 16, 18, 31, 28, 84, 19, 23, 6, 38, 20, 20, 25, 30, 28, 36, 34, 9, 93, 0, 64, 12, 115, 103, 71, 6, 10, 4, 78, 82, 87, 86, 112, 89, 13, 2, 65, 72, 74, 80, 92, 90, 108, 77, 19, 6, 1, 66, 68, 74, 78, 86, 99, 77, 5, 67, 6, 5, 71, 78, 79, 92, 74, 28, 8, 8, 5, 65, 71, 79, 78, 82, 62, 116, 114, 107, 113, 104, 105, 105, 104, 101, 104, 104, 105, 89, 94, 94, 99, 102, 89, 65, 83, 87, 85, 76, 71, 72, 66, 77, 66, 64, 67, 68, 1, 68, 77, 65, 68, 75, 5, 2, 66, 2, 12, 1, 71, 75, 77, 76, 66, 64, 3, 5, 69, 66, 71, 7, 6, 73, 76, 76, 76, 9, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 36, 62, 62, 62, 62, 61, 48, 34, 45, 43, 31, 15, 9, 2, 79, 61, 59, 62, 46, 31, 34, 26, 24, 32, 28, 20, 6, 17, 3, 72, 23, 28, 21, 28, 34, 21, 19, 11, 10, 68, 4, 67, 79, 115, 0, 67, 73, 20, 4, 69, 76, 70, 67, 67, 66, 65, 6, 67, 66, 65, 4, 14, 11, 16, 1, 61, 58, 52, 48, 40, 36, 22, 10, 74, 66, 30, 23, 20, 12, 10, 4, 1, 65, 74, 76, 70, 67, 67, 66, 65, 6, 67, 66, 65, 4, 14, 11, 16, 1, 61, 58, 52, 48, 40, 36, 22, 10, 74 }, { 62, 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 31, 49, 56, 18, 3, 83, 33, 82, 88, 4, 3, 0, 64, 12, 42, 47, 33, 1, 103, 109, 104, 72, 82, 88, 4, 89, 80, 23, 6, 82, 94, 104, 67, 69, 86, 99, 0, 82, 98, 65, 78, 88, 100, 7, 76, 73, 90, 17, 3, 22, 0, 0, 0, 77, 88, 97, 68, 15, 0, 26, 82, 115, 106, 81, 87, 69, 71, 82, 81, 109, 68, 92, 82, 106, 86, 78, 85, 80, 12, 0, 75, 106, 76, 97, 84, 110, 65, 77, 72, 89, 11, 69, 66, 90, 83, 81, 79, 70, 64, 67, 80, 0, 5, 68, 4, 68, 69, 4, 73, 22, 68, 1, 16, 19, 25, 24, 14, 80, 72, 76, 6, 67, 87, 67, 0, 72, 75, 71, 11, 40, 14, 77, 80, 72, 69, 31, 38, 98, 66, 65, 81, 3, 0, 66, 69, 33, 56, 42, 1, 75, 111, 17, 57, 62, 114, 22, 21, 26, 28, 28, 23, 18, 22, 26, 13, 20, 14, 7, 4, 0, 15, 13, 14, 12, 5, 9, 19, 9, 68, 10, 12, 69, 19, 67, 62, 62, 51, 40, 48, 58, 62, 62, 36, 21, 14, 17, 29, 27, 85, 18, 22, 4, 37, 19, 19, 24, 28, 27, 34, 32, 8, 93, 0, 65, 11, 113, 101, 69, 7, 10, 4, 77, 81, 86, 85, 110, 88, 14, 3, 64, 71, 73, 79, 91, 89, 106, 76, 20, 7, 2, 66, 67, 73, 77, 85, 97, 76, 7, 66, 7, 5, 70, 77, 78, 91, 73, 29, 9, 9, 6, 64, 70, 78, 77, 81, 62, 114, 112, 105, 111, 103, 104, 103, 102, 99, 102, 102, 103, 88, 93, 93, 98, 98, 89, 66, 83, 86, 84, 75, 71, 72, 66, 77, 66, 65, 67, 68, 2, 68, 77, 65, 68, 75, 5, 2, 66, 2, 11, 1, 71, 74, 75, 76, 66, 0, 2, 5, 69, 66, 70, 7, 6, 72, 75, 75, 75, 7, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 58, 34, 62, 62, 62, 62, 58, 46, 33, 43, 41, 30, 14, 8, 1, 79, 59, 57, 60, 44, 29, 32, 25, 22, 30, 26, 18, 4, 15, 1, 73, 22, 27, 19, 27, 32, 20, 17, 10, 9, 69, 3, 67, 79, 114, 64, 68, 75, 19, 3, 70, 76, 69, 66, 66, 64, 64, 7, 67, 65, 64, 5, 15, 12, 17, 2, 60, 57, 50, 46, 38, 34, 20, 8, 75, 65, 30, 24, 21, 13, 11, 5, 2, 64, 73, 76, 69, 66, 66, 64, 64, 7, 67, 65, 64, 5, 15, 12, 17, 2, 60, 57, 50, 46, 38, 34, 20, 8, 75 }, { 62, 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 29, 48, 55, 19, 3, 82, 31, 82, 87, 3, 3, 1, 65, 11, 41, 45, 30, 65, 104, 107, 103, 72, 82, 87, 3, 89, 79, 23, 6, 82, 93, 102, 68, 70, 86, 99, 0, 81, 97, 66, 78, 88, 100, 7, 76, 73, 89, 18, 3, 22, 0, 0, 0, 77, 88, 97, 67, 14, 0, 25, 82, 114, 104, 80, 86, 68, 70, 81, 80, 107, 67, 91, 81, 103, 86, 79, 85, 81, 12, 0, 74, 103, 75, 97, 84, 108, 65, 77, 72, 89, 11, 69, 66, 89, 83, 80, 78, 70, 64, 67, 79, 0, 5, 69, 4, 68, 69, 4, 73, 22, 68, 1, 17, 19, 25, 24, 14, 81, 72, 76, 6, 67, 86, 67, 1, 71, 75, 69, 13, 41, 15, 75, 80, 73, 69, 32, 38, 99, 66, 64, 82, 4, 0, 66, 68, 33, 56, 42, 3, 76, 109, 16, 54, 62, 113, 22, 21, 25, 28, 27, 23, 17, 22, 25, 12, 19, 14, 7, 4, 0, 14, 13, 13, 11, 5, 9, 18, 9, 69, 9, 11, 69, 18, 68, 60, 62, 49, 38, 47, 56, 62, 62, 35, 19, 12, 15, 27, 25, 86, 17, 21, 3, 36, 18, 18, 22, 27, 25, 32, 31, 6, 93, 64, 66, 9, 112, 100, 68, 7, 10, 4, 76, 80, 85, 85, 108, 86, 15, 4, 64, 70, 71, 78, 89, 88, 103, 75, 20, 7, 2, 65, 66, 72, 77, 84, 96, 76, 8, 65, 7, 5, 69, 77, 78, 90, 72, 29, 10, 9, 6, 0, 69, 77, 77, 80, 62, 113, 111, 104, 110, 102, 102, 102, 100, 98, 100, 100, 100, 87, 92, 92, 97, 95, 88, 67, 82, 85, 83, 75, 71, 72, 66, 76, 67, 66, 67, 68, 3, 68, 77, 65, 68, 74, 4, 1, 66, 1, 10, 0, 71, 74, 74, 77, 67, 1, 2, 4, 69, 66, 70, 6, 5, 72, 75, 75, 75, 6, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 56, 32, 62, 62, 62, 62, 55, 44, 31, 41, 38, 28, 13, 7, 0, 79, 57, 54, 57, 42, 27, 30, 23, 20, 28, 24, 16, 2, 13, 0, 75, 21, 26, 17, 25, 31, 18, 15, 9, 8, 70, 2, 68, 79, 113, 66, 69, 76, 17, 2, 71, 76, 69, 66, 65, 0, 0, 7, 66, 64, 0, 6, 15, 13, 17, 2, 60, 55, 48, 44, 36, 32, 17, 6, 76, 65, 31, 24, 21, 14, 12, 5, 2, 64, 72, 76, 69, 66, 65, 0, 0, 7, 66, 64, 0, 6, 15, 13, 17, 2, 60, 55, 48, 44, 36, 32, 17, 6, 76 }, { 62, 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 28, 46, 54, 19, 4, 80, 30, 82, 85, 3, 2, 2, 66, 10, 40, 44, 26, 67, 105, 105, 101, 72, 82, 85, 3, 88, 78, 24, 6, 81, 92, 101, 70, 71, 86, 100, 0, 81, 97, 66, 79, 87, 100, 6, 76, 73, 88, 18, 3, 22, 0, 0, 0, 76, 88, 97, 67, 14, 64, 25, 81, 113, 102, 79, 85, 66, 68, 80, 79, 105, 66, 90, 81, 101, 87, 79, 85, 81, 13, 0, 73, 101, 75, 96, 83, 106, 65, 77, 72, 89, 12, 70, 65, 89, 83, 79, 78, 69, 64, 66, 78, 1, 5, 69, 4, 68, 69, 4, 73, 22, 68, 1, 17, 19, 26, 25, 15, 81, 71, 75, 5, 67, 86, 67, 2, 71, 74, 68, 14, 41, 17, 74, 81, 74, 68, 32, 38, 99, 67, 64, 82, 4, 64, 65, 67, 33, 56, 43, 4, 77, 108, 15, 51, 62, 111, 22, 21, 25, 27, 27, 22, 17, 21, 25, 12, 19, 14, 6, 4, 64, 14, 12, 12, 11, 5, 9, 18, 8, 71, 9, 10, 69, 17, 68, 57, 62, 47, 37, 45, 54, 62, 61, 33, 17, 10, 14, 25, 23, 88, 16, 19, 1, 34, 17, 17, 21, 25, 23, 30, 29, 5, 93, 65, 67, 8, 110, 98, 66, 8, 10, 4, 75, 80, 85, 84, 107, 85, 16, 5, 0, 70, 70, 77, 88, 87, 101, 75, 21, 8, 3, 65, 65, 72, 76, 83, 95, 76, 10, 64, 7, 5, 69, 76, 78, 90, 72, 30, 10, 10, 6, 1, 69, 77, 76, 80, 62, 111, 109, 102, 108, 100, 101, 100, 99, 96, 98, 98, 98, 87, 91, 92, 97, 92, 88, 68, 82, 85, 82, 75, 71, 72, 66, 76, 67, 67, 67, 68, 3, 68, 77, 65, 68, 74, 4, 1, 66, 0, 9, 0, 71, 74, 73, 78, 67, 2, 1, 4, 69, 66, 69, 6, 5, 71, 74, 75, 75, 4, 62, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 53, 30, 62, 62, 62, 62, 53, 41, 29, 39, 36, 26, 12, 6, 64, 79, 55, 52, 55, 40, 25, 29, 22, 18, 26, 21, 14, 0, 11, 65, 76, 20, 24, 15, 24, 29, 17, 13, 7, 6, 71, 1, 69, 80, 112, 67, 71, 78, 16, 0, 72, 75, 68, 65, 65, 1, 1, 8, 66, 0, 1, 7, 16, 14, 18, 3, 59, 53, 46, 42, 34, 30, 15, 4, 77, 64, 31, 25, 22, 14, 13, 6, 3, 0, 72, 75, 68, 65, 65, 1, 1, 8, 66, 0, 1, 7, 16, 14, 18, 3, 59, 53, 46, 42, 34, 30, 15, 4, 77 }, { 62, 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 26, 45, 53, 20, 4, 78, 28, 82, 84, 3, 2, 3, 67, 8, 38, 42, 23, 69, 106, 103, 100, 72, 82, 84, 3, 88, 77, 24, 5, 80, 91, 100, 71, 73, 87, 100, 0, 81, 96, 66, 79, 87, 100, 6, 76, 73, 88, 19, 3, 22, 0, 0, 0, 76, 89, 97, 66, 13, 64, 24, 81, 112, 100, 78, 84, 65, 67, 80, 78, 103, 65, 89, 80, 99, 88, 79, 85, 81, 13, 64, 72, 99, 75, 95, 82, 104, 65, 77, 72, 89, 12, 70, 65, 88, 83, 79, 78, 69, 64, 66, 77, 1, 5, 69, 3, 68, 69, 4, 73, 22, 68, 1, 18, 19, 26, 25, 15, 82, 71, 74, 5, 67, 86, 67, 3, 71, 74, 67, 15, 42, 18, 72, 81, 75, 68, 32, 38, 100, 67, 0, 83, 5, 64, 65, 66, 33, 56, 43, 5, 78, 106, 14, 48, 60, 110, 21, 21, 24, 26, 26, 22, 16, 21, 24, 11, 18, 13, 6, 4, 64, 13, 11, 11, 10, 4, 8, 17, 8, 72, 8, 9, 69, 15, 69, 55, 62, 45, 35, 43, 52, 62, 58, 32, 15, 7, 12, 23, 21, 89, 14, 18, 64, 33, 16, 16, 20, 24, 21, 28, 27, 3, 93, 66, 68, 6, 109, 97, 65, 8, 10, 4, 74, 79, 84, 83, 105, 84, 17, 5, 1, 69, 69, 76, 86, 86, 99, 74, 21, 8, 3, 64, 64, 71, 76, 83, 94, 76, 11, 0, 7, 5, 68, 76, 78, 89, 71, 30, 11, 10, 6, 2, 68, 76, 76, 79, 62, 110, 108, 101, 106, 99, 99, 99, 97, 95, 97, 96, 96, 86, 90, 91, 96, 89, 88, 69, 82, 84, 82, 75, 71, 72, 67, 75, 67, 68, 67, 68, 4, 68, 77, 65, 68, 74, 3, 1, 66, 64, 8, 0, 71, 74, 72, 79, 67, 2, 0, 4, 69, 66, 69, 6, 4, 71, 73, 75, 75, 3, 62, 60, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 50, 28, 62, 62, 62, 62, 50, 39, 27, 37, 33, 24, 10, 5, 65, 79, 52, 50, 53, 38, 23, 27, 20, 16, 24, 19, 12, 65, 8, 67, 77, 19, 23, 12, 22, 27, 15, 11, 6, 5, 72, 0, 70, 80, 111, 68, 72, 79, 15, 64, 73, 75, 68, 65, 64, 2, 1, 9, 66, 1, 2, 8, 16, 15, 18, 4, 59, 51, 44, 40, 32, 28, 13, 2, 79, 64, 32, 25, 23, 15, 13, 6, 3, 0, 71, 75, 68, 65, 64, 2, 1, 9, 66, 1, 2, 8, 16, 15, 18, 4, 59, 51, 44, 40, 32, 28, 13, 2, 79 }, { 62, 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 24, 43, 52, 21, 4, 77, 26, 81, 82, 2, 2, 4, 68, 7, 37, 40, 20, 72, 106, 101, 99, 72, 81, 82, 2, 88, 76, 24, 5, 80, 90, 98, 72, 74, 87, 100, 0, 80, 95, 67, 79, 87, 100, 6, 75, 73, 87, 19, 3, 22, 0, 0, 0, 75, 89, 97, 66, 12, 64, 24, 80, 110, 97, 77, 83, 0, 65, 79, 77, 101, 64, 88, 79, 96, 88, 80, 85, 82, 13, 64, 71, 96, 74, 95, 82, 102, 65, 77, 72, 88, 12, 70, 64, 87, 82, 78, 77, 69, 0, 65, 76, 2, 5, 70, 3, 67, 69, 4, 73, 23, 68, 1, 18, 20, 26, 25, 15, 82, 70, 74, 4, 67, 85, 67, 4, 70, 73, 65, 17, 43, 20, 70, 82, 76, 67, 33, 38, 101, 67, 0, 83, 6, 64, 65, 64, 33, 56, 43, 7, 79, 105, 13, 46, 57, 109, 21, 21, 24, 26, 26, 22, 16, 21, 24, 10, 17, 13, 6, 4, 64, 13, 11, 10, 10, 4, 8, 16, 7, 73, 7, 9, 69, 14, 69, 53, 62, 44, 34, 42, 50, 62, 56, 31, 13, 5, 11, 21, 19, 90, 13, 17, 65, 32, 15, 15, 18, 22, 19, 26, 26, 2, 93, 67, 69, 4, 107, 95, 64, 9, 10, 4, 73, 78, 83, 83, 103, 82, 18, 6, 1, 68, 67, 75, 85, 85, 96, 73, 22, 9, 4, 64, 0, 70, 75, 82, 93, 75, 12, 1, 7, 5, 67, 75, 77, 88, 70, 30, 12, 10, 6, 3, 67, 75, 75, 78, 62, 109, 106, 99, 105, 98, 98, 97, 95, 93, 95, 94, 93, 85, 89, 90, 95, 86, 87, 70, 81, 83, 81, 75, 71, 72, 67, 75, 68, 69, 67, 68, 5, 68, 77, 65, 68, 73, 3, 0, 66, 65, 7, 64, 71, 74, 71, 79, 68, 3, 0, 3, 69, 66, 69, 5, 4, 71, 73, 75, 75, 1, 62, 59, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 48, 26, 62, 62, 62, 62, 47, 37, 26, 35, 31, 22, 9, 4, 66, 79, 50, 47, 50, 36, 21, 25, 19, 14, 22, 17, 10, 67, 6, 68, 79, 18, 22, 10, 21, 26, 14, 9, 5, 4, 73, 64, 71, 80, 110, 70, 73, 81, 13, 65, 74, 75, 67, 64, 0, 3, 2, 9, 65, 2, 3, 9, 17, 16, 19, 4, 58, 49, 42, 38, 30, 26, 10, 0, 80, 0, 32, 26, 23, 16, 14, 7, 4, 0, 70, 75, 67, 64, 0, 3, 2, 9, 65, 2, 3, 9, 17, 16, 19, 4, 58, 49, 42, 38, 30, 26, 10, 0, 80 }, { 61, 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 22, 42, 51, 21, 4, 75, 25, 81, 81, 2, 1, 5, 69, 6, 36, 38, 16, 74, 107, 99, 97, 72, 81, 81, 2, 87, 75, 25, 5, 79, 89, 97, 73, 75, 87, 101, 0, 80, 94, 67, 80, 87, 100, 6, 75, 73, 86, 20, 3, 22, 0, 0, 0, 75, 89, 97, 65, 11, 64, 23, 80, 109, 95, 76, 82, 1, 64, 78, 76, 99, 0, 87, 78, 94, 89, 80, 85, 82, 14, 64, 70, 94, 74, 94, 81, 100, 65, 77, 72, 88, 13, 70, 64, 86, 82, 77, 77, 69, 0, 65, 75, 2, 5, 70, 3, 67, 69, 4, 73, 23, 68, 1, 19, 20, 27, 25, 16, 83, 70, 73, 4, 67, 85, 67, 5, 70, 73, 64, 18, 44, 21, 68, 82, 77, 67, 33, 38, 102, 67, 1, 84, 6, 64, 65, 0, 33, 56, 43, 8, 80, 103, 12, 43, 54, 107, 21, 21, 23, 25, 25, 21, 15, 21, 23, 9, 16, 13, 6, 4, 65, 12, 10, 9, 9, 4, 8, 15, 7, 74, 7, 8, 69, 13, 70, 51, 60, 42, 32, 40, 48, 62, 53, 30, 11, 3, 9, 19, 17, 92, 12, 16, 67, 31, 14, 14, 17, 21, 17, 24, 24, 0, 93, 68, 70, 3, 106, 94, 1, 9, 10, 4, 72, 77, 82, 82, 101, 81, 19, 7, 2, 68, 66, 74, 83, 84, 94, 73, 22, 9, 4, 0, 1, 70, 75, 81, 92, 75, 14, 2, 7, 5, 66, 75, 77, 87, 69, 31, 13, 11, 6, 4, 67, 74, 75, 78, 62, 107, 105, 98, 103, 97, 96, 96, 94, 92, 93, 92, 91, 85, 88, 90, 94, 83, 87, 71, 81, 82, 80, 75, 71, 72, 67, 74, 68, 70, 67, 68, 5, 68, 77, 65, 68, 73, 2, 0, 66, 66, 6, 64, 71, 74, 70, 80, 68, 4, 64, 3, 69, 66, 68, 5, 3, 70, 72, 75, 75, 0, 62, 58, 61, 61, 61, 62, 62, 62, 61, 62, 62, 62, 57, 45, 24, 62, 60, 59, 60, 44, 35, 24, 33, 28, 20, 8, 3, 67, 79, 48, 45, 48, 34, 19, 23, 17, 12, 20, 15, 8, 69, 4, 70, 80, 17, 21, 8, 19, 24, 12, 7, 3, 2, 74, 65, 72, 80, 109, 71, 75, 82, 12, 66, 75, 75, 67, 64, 0, 4, 3, 10, 65, 3, 4, 10, 17, 17, 19, 5, 58, 47, 40, 36, 28, 24, 8, 65, 81, 0, 33, 26, 24, 16, 15, 7, 4, 1, 70, 75, 67, 64, 0, 4, 3, 10, 65, 3, 4, 10, 17, 17, 19, 5, 58, 47, 40, 36, 28, 24, 8, 65, 81 }, { 60, 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 20, 40, 50, 22, 4, 73, 23, 81, 79, 2, 1, 6, 70, 5, 35, 36, 13, 76, 108, 97, 96, 72, 81, 79, 2, 87, 74, 25, 5, 78, 88, 96, 74, 76, 87, 101, 0, 80, 93, 67, 80, 87, 100, 6, 75, 73, 85, 20, 3, 22, 0, 0, 0, 74, 89, 97, 65, 10, 64, 23, 79, 108, 93, 75, 81, 3, 1, 77, 75, 97, 1, 86, 77, 92, 90, 80, 85, 82, 14, 64, 69, 92, 74, 93, 80, 98, 65, 77, 72, 88, 13, 70, 0, 85, 82, 76, 77, 69, 0, 64, 74, 3, 5, 70, 3, 67, 69, 4, 73, 23, 68, 1, 19, 20, 27, 25, 16, 83, 69, 72, 3, 67, 85, 67, 6, 70, 72, 0, 19, 45, 23, 66, 83, 78, 66, 33, 38, 103, 67, 1, 84, 7, 64, 65, 1, 33, 56, 43, 9, 81, 102, 11, 40, 51, 106, 21, 21, 23, 24, 25, 21, 15, 21, 23, 8, 15, 13, 6, 4, 65, 12, 9, 8, 9, 4, 8, 14, 6, 75, 6, 7, 69, 12, 70, 49, 58, 40, 31, 38, 46, 59, 51, 29, 9, 1, 8, 17, 15, 93, 11, 15, 69, 30, 13, 13, 16, 19, 15, 22, 22, 64, 93, 69, 71, 1, 104, 92, 2, 10, 10, 4, 71, 76, 81, 81, 99, 80, 20, 8, 3, 67, 65, 73, 82, 83, 92, 72, 23, 10, 5, 0, 2, 69, 74, 80, 91, 75, 15, 3, 7, 5, 65, 74, 77, 86, 68, 31, 14, 11, 6, 5, 66, 73, 74, 77, 62, 106, 103, 96, 101, 96, 95, 94, 92, 90, 91, 90, 89, 84, 87, 89, 93, 80, 87, 72, 81, 81, 79, 75, 71, 72, 67, 74, 68, 71, 67, 68, 6, 68, 77, 65, 68, 73, 2, 0, 66, 67, 5, 64, 71, 74, 69, 81, 68, 5, 65, 3, 69, 66, 68, 5, 3, 70, 71, 75, 75, 65, 61, 57, 60, 59, 59, 62, 62, 62, 59, 60, 62, 61, 54, 42, 22, 61, 57, 55, 55, 41, 33, 22, 31, 26, 18, 7, 2, 68, 79, 46, 43, 46, 32, 17, 21, 16, 10, 18, 13, 6, 71, 2, 72, 81, 16, 20, 6, 18, 22, 11, 5, 2, 1, 75, 66, 73, 80, 108, 72, 76, 84, 11, 67, 76, 75, 66, 0, 1, 5, 4, 11, 65, 4, 5, 11, 18, 18, 20, 6, 57, 45, 38, 34, 26, 22, 6, 67, 82, 1, 33, 27, 25, 17, 16, 8, 5, 1, 69, 75, 66, 0, 1, 5, 4, 11, 65, 4, 5, 11, 18, 18, 20, 6, 57, 45, 38, 34, 26, 22, 6, 67, 82 }, { 58, 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 18, 38, 49, 22, 4, 72, 21, 81, 78, 1, 0, 7, 71, 3, 33, 34, 9, 79, 109, 95, 95, 72, 81, 78, 1, 87, 74, 25, 4, 78, 88, 95, 76, 78, 88, 102, 64, 80, 93, 68, 81, 87, 100, 5, 75, 73, 85, 20, 2, 22, 0, 0, 0, 74, 90, 97, 65, 9, 65, 22, 79, 107, 91, 74, 81, 4, 2, 77, 74, 96, 1, 85, 77, 90, 91, 81, 86, 83, 14, 65, 69, 90, 74, 93, 80, 97, 65, 77, 72, 88, 13, 71, 0, 85, 82, 76, 77, 69, 0, 64, 73, 3, 5, 71, 2, 67, 69, 3, 73, 23, 68, 1, 19, 20, 27, 25, 16, 84, 69, 72, 2, 67, 85, 68, 6, 70, 72, 1, 20, 45, 24, 65, 84, 80, 66, 33, 38, 104, 68, 1, 85, 7, 65, 65, 2, 33, 55, 43, 10, 82, 101, 9, 37, 47, 105, 20, 21, 22, 23, 24, 20, 14, 20, 22, 7, 14, 12, 5, 3, 66, 11, 8, 7, 8, 3, 7, 13, 5, 77, 5, 6, 69, 10, 71, 46, 55, 38, 29, 36, 43, 55, 48, 27, 7, 65, 6, 14, 13, 95, 9, 13, 71, 28, 12, 12, 14, 17, 13, 20, 20, 66, 93, 70, 72, 64, 103, 91, 3, 10, 10, 4, 71, 76, 81, 81, 98, 79, 20, 8, 3, 67, 64, 72, 81, 83, 90, 72, 23, 10, 5, 0, 2, 69, 74, 80, 90, 75, 16, 4, 7, 4, 65, 74, 77, 86, 68, 31, 14, 11, 6, 6, 66, 73, 74, 77, 62, 105, 102, 95, 100, 95, 94, 93, 91, 89, 90, 89, 87, 84, 87, 89, 93, 77, 87, 74, 81, 81, 79, 75, 71, 72, 68, 74, 69, 72, 68, 68, 6, 69, 77, 66, 68, 73, 1, 64, 67, 68, 4, 65, 72, 74, 68, 82, 69, 5, 66, 2, 69, 67, 68, 4, 2, 70, 71, 75, 75, 67, 59, 56, 58, 57, 56, 62, 62, 62, 56, 57, 62, 58, 50, 39, 20, 57, 53, 51, 49, 38, 30, 20, 28, 23, 16, 5, 0, 69, 79, 43, 40, 43, 30, 14, 19, 14, 7, 16, 10, 4, 74, 64, 74, 83, 14, 18, 3, 16, 20, 9, 3, 0, 64, 76, 67, 74, 81, 107, 74, 78, 86, 9, 69, 78, 75, 66, 0, 1, 6, 4, 11, 65, 5, 5, 12, 18, 18, 20, 6, 56, 43, 36, 31, 23, 20, 3, 69, 84, 1, 33, 27, 25, 17, 16, 8, 5, 1, 69, 75, 66, 0, 1, 6, 4, 11, 65, 5, 5, 12, 18, 18, 20, 6, 56, 43, 36, 31, 23, 20, 3, 69, 84 }, { 57, 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 17, 37, 49, 23, 5, 70, 20, 80, 76, 1, 0, 9, 71, 2, 32, 33, 6, 81, 109, 93, 93, 71, 80, 76, 1, 86, 73, 26, 4, 77, 87, 93, 77, 79, 88, 102, 64, 79, 92, 68, 81, 86, 99, 5, 74, 72, 84, 21, 2, 22, 0, 0, 0, 73, 90, 97, 64, 9, 65, 22, 78, 105, 88, 72, 80, 6, 4, 76, 72, 94, 2, 83, 76, 87, 91, 81, 86, 83, 15, 65, 68, 87, 73, 92, 79, 95, 65, 76, 72, 87, 14, 71, 1, 84, 81, 75, 76, 68, 1, 0, 72, 4, 6, 71, 2, 66, 68, 3, 72, 24, 67, 1, 20, 21, 28, 26, 17, 84, 68, 71, 2, 67, 84, 68, 7, 69, 71, 3, 22, 46, 26, 0, 84, 81, 65, 34, 39, 104, 68, 2, 85, 8, 65, 64, 4, 33, 55, 44, 12, 83, 99, 8, 35, 44, 103, 20, 21, 22, 23, 24, 20, 14, 20, 22, 7, 14, 12, 5, 3, 66, 11, 8, 7, 8, 3, 7, 13, 5, 78, 5, 6, 69, 9, 71, 44, 53, 37, 28, 35, 41, 52, 46, 26, 6, 67, 5, 12, 12, 96, 8, 12, 72, 27, 12, 12, 13, 16, 12, 19, 19, 67, 93, 70, 72, 65, 101, 89, 5, 11, 10, 4, 70, 75, 80, 80, 96, 77, 21, 9, 4, 66, 1, 71, 79, 82, 87, 71, 24, 11, 6, 1, 3, 68, 73, 79, 88, 74, 18, 5, 8, 4, 64, 73, 76, 85, 67, 32, 15, 12, 7, 7, 65, 72, 73, 76, 62, 103, 100, 93, 98, 93, 92, 91, 89, 87, 88, 87, 84, 83, 86, 88, 92, 73, 86, 75, 80, 80, 78, 74, 71, 71, 68, 73, 69, 72, 68, 68, 7, 69, 77, 66, 68, 72, 1, 64, 67, 68, 4, 65, 72, 73, 66, 82, 69, 6, 66, 2, 69, 67, 67, 4, 2, 69, 70, 74, 74, 68, 58, 55, 57, 56, 54, 60, 60, 59, 54, 55, 59, 56, 47, 37, 18, 54, 50, 48, 44, 36, 28, 19, 26, 21, 15, 4, 64, 69, 79, 41, 38, 41, 28, 12, 18, 13, 5, 15, 8, 3, 76, 66, 75, 84, 13, 17, 1, 15, 19, 8, 2, 64, 65, 77, 67, 74, 81, 106, 75, 79, 87, 8, 70, 79, 74, 65, 1, 2, 8, 5, 12, 64, 7, 6, 13, 19, 19, 21, 7, 56, 42, 35, 29, 21, 19, 1, 70, 85, 2, 34, 28, 26, 18, 17, 9, 6, 2, 68, 74, 65, 1, 2, 8, 5, 12, 64, 7, 6, 13, 19, 19, 21, 7, 56, 42, 35, 29, 21, 19, 1, 70, 85 }, { 56, 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 15, 35, 48, 24, 5, 68, 18, 80, 75, 1, 0, 10, 72, 1, 31, 31, 3, 83, 110, 91, 92, 71, 80, 75, 1, 86, 72, 26, 4, 76, 86, 92, 78, 80, 88, 102, 64, 79, 91, 68, 81, 86, 99, 5, 74, 72, 83, 21, 2, 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 22, 77, 104, 86, 71, 79, 8, 6, 75, 71, 92, 3, 82, 75, 85, 92, 81, 86, 83, 15, 65, 67, 85, 73, 91, 78, 93, 65, 76, 72, 87, 14, 71, 1, 83, 81, 74, 76, 68, 1, 0, 71, 5, 6, 71, 2, 66, 68, 3, 72, 24, 67, 1, 20, 21, 28, 26, 17, 85, 67, 70, 1, 67, 84, 68, 8, 69, 71, 4, 23, 47, 28, 2, 85, 82, 65, 34, 39, 105, 68, 2, 86, 9, 65, 64, 5, 33, 55, 44, 13, 84, 98, 7, 32, 41, 102, 20, 21, 22, 22, 23, 20, 14, 20, 21, 6, 13, 12, 5, 3, 66, 10, 7, 6, 8, 3, 7, 12, 4, 79, 4, 5, 69, 8, 72, 42, 51, 35, 26, 33, 39, 49, 44, 25, 4, 69, 3, 10, 10, 97, 7, 11, 74, 26, 11, 11, 12, 14, 10, 17, 17, 69, 93, 71, 73, 67, 100, 88, 6, 12, 10, 4, 69, 74, 79, 79, 94, 76, 22, 10, 5, 65, 2, 70, 78, 81, 85, 70, 24, 11, 6, 1, 4, 67, 72, 78, 87, 74, 19, 6, 8, 4, 0, 72, 76, 84, 66, 32, 16, 12, 7, 8, 64, 71, 72, 75, 62, 102, 98, 91, 96, 92, 91, 89, 87, 86, 86, 85, 82, 82, 85, 87, 91, 70, 86, 76, 80, 79, 77, 74, 71, 71, 68, 73, 69, 73, 68, 68, 8, 69, 77, 66, 68, 72, 1, 64, 67, 69, 3, 65, 72, 73, 65, 83, 69, 7, 67, 2, 69, 67, 67, 4, 2, 69, 69, 74, 74, 70, 57, 54, 56, 54, 52, 57, 57, 56, 52, 52, 56, 53, 44, 34, 16, 50, 46, 44, 39, 33, 26, 17, 24, 19, 13, 3, 65, 70, 79, 39, 36, 39, 26, 10, 16, 11, 3, 13, 6, 1, 78, 68, 77, 85, 12, 16, 64, 13, 17, 6, 0, 65, 66, 78, 68, 75, 81, 105, 76, 80, 89, 7, 71, 80, 74, 65, 2, 3, 9, 6, 13, 64, 8, 7, 14, 19, 20, 22, 8, 55, 40, 33, 27, 19, 17, 64, 72, 86, 3, 34, 28, 27, 19, 18, 9, 7, 2, 67, 74, 65, 2, 3, 9, 6, 13, 64, 8, 7, 14, 19, 20, 22, 8, 55, 40, 33, 27, 19, 17, 64, 72, 86 }, { 55, 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 13, 34, 47, 24, 5, 66, 17, 80, 73, 1, 64, 11, 73, 0, 30, 29, 64, 85, 111, 89, 90, 71, 80, 73, 1, 85, 71, 27, 4, 75, 85, 91, 79, 81, 88, 103, 64, 79, 90, 68, 82, 86, 99, 5, 74, 72, 82, 22, 2, 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 21, 77, 103, 84, 70, 78, 9, 7, 74, 70, 90, 4, 81, 74, 83, 93, 81, 86, 83, 16, 65, 66, 83, 73, 90, 77, 91, 65, 76, 72, 87, 15, 71, 2, 82, 81, 73, 76, 68, 1, 1, 70, 5, 6, 71, 2, 66, 68, 3, 72, 24, 67, 1, 21, 21, 29, 26, 18, 85, 67, 69, 1, 67, 84, 68, 9, 69, 70, 5, 24, 48, 29, 4, 85, 83, 64, 34, 39, 106, 68, 3, 86, 9, 65, 64, 6, 33, 55, 44, 14, 85, 96, 6, 29, 38, 100, 20, 21, 21, 21, 23, 19, 13, 20, 21, 5, 12, 12, 5, 3, 67, 10, 6, 5, 7, 3, 7, 11, 4, 80, 4, 4, 69, 7, 72, 40, 49, 33, 25, 31, 37, 46, 41, 24, 2, 71, 2, 8, 8, 99, 6, 10, 76, 25, 10, 10, 11, 13, 8, 15, 15, 70, 93, 72, 74, 68, 98, 86, 8, 12, 10, 4, 68, 73, 78, 78, 92, 75, 23, 11, 6, 65, 3, 69, 76, 80, 83, 70, 25, 12, 7, 2, 5, 67, 72, 77, 86, 74, 21, 7, 8, 4, 1, 72, 76, 83, 65, 33, 17, 13, 7, 9, 64, 70, 72, 75, 62, 100, 97, 90, 94, 91, 89, 88, 86, 84, 84, 83, 80, 82, 84, 87, 90, 67, 86, 77, 80, 78, 76, 74, 71, 71, 68, 72, 69, 74, 68, 68, 8, 69, 77, 66, 68, 72, 0, 64, 67, 70, 2, 65, 72, 73, 64, 84, 69, 8, 68, 2, 69, 67, 66, 4, 1, 68, 68, 74, 74, 71, 56, 53, 55, 52, 50, 55, 55, 53, 49, 49, 53, 50, 41, 31, 14, 46, 43, 40, 34, 30, 24, 15, 22, 16, 11, 2, 66, 71, 79, 37, 34, 37, 24, 8, 14, 10, 1, 11, 4, 64, 80, 70, 79, 86, 11, 15, 66, 12, 15, 5, 65, 67, 68, 79, 69, 76, 81, 104, 77, 82, 90, 6, 72, 81, 74, 64, 2, 3, 10, 7, 14, 64, 9, 8, 15, 20, 21, 22, 9, 55, 38, 31, 25, 17, 15, 66, 74, 87, 3, 35, 29, 28, 19, 19, 10, 7, 3, 67, 74, 64, 2, 3, 10, 7, 14, 64, 9, 8, 15, 20, 21, 22, 9, 55, 38, 31, 25, 17, 15, 66, 74, 87 }, { 53, 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 11, 32, 46, 25, 5, 65, 15, 80, 72, 0, 64, 12, 74, 65, 28, 27, 67, 88, 112, 87, 89, 71, 80, 72, 0, 85, 70, 27, 3, 75, 84, 89, 80, 83, 89, 103, 64, 78, 89, 69, 82, 86, 99, 5, 74, 72, 82, 22, 2, 22, 0, 0, 0, 71, 91, 97, 0, 6, 65, 21, 76, 102, 82, 69, 77, 11, 9, 74, 69, 88, 5, 80, 73, 80, 93, 82, 86, 84, 16, 66, 65, 80, 72, 90, 77, 89, 65, 76, 72, 87, 15, 71, 2, 81, 81, 73, 75, 68, 1, 1, 69, 6, 6, 72, 1, 66, 68, 3, 72, 24, 67, 1, 21, 21, 29, 26, 18, 86, 66, 69, 0, 67, 83, 68, 10, 68, 70, 7, 26, 49, 31, 6, 86, 84, 64, 35, 39, 107, 68, 3, 87, 10, 65, 64, 7, 33, 55, 44, 16, 86, 95, 5, 26, 35, 99, 19, 21, 21, 21, 22, 19, 13, 20, 20, 4, 11, 11, 5, 3, 67, 9, 6, 4, 7, 2, 6, 10, 3, 81, 3, 3, 69, 5, 73, 38, 47, 31, 23, 30, 35, 42, 39, 23, 0, 74, 0, 6, 6, 100, 4, 9, 77, 24, 9, 9, 9, 11, 6, 13, 14, 72, 93, 73, 75, 70, 97, 85, 9, 13, 10, 4, 67, 72, 77, 78, 90, 73, 24, 11, 6, 64, 5, 68, 75, 79, 80, 69, 25, 12, 7, 2, 6, 66, 71, 77, 85, 74, 22, 8, 8, 4, 2, 71, 76, 82, 64, 33, 18, 13, 7, 10, 0, 69, 71, 74, 62, 99, 95, 88, 93, 90, 88, 86, 84, 83, 83, 81, 77, 81, 83, 86, 89, 64, 85, 78, 79, 77, 76, 74, 71, 71, 69, 72, 70, 75, 68, 68, 9, 69, 77, 66, 68, 71, 0, 65, 67, 71, 1, 66, 72, 73, 0, 85, 70, 8, 68, 1, 69, 67, 66, 3, 1, 68, 68, 74, 74, 73, 55, 52, 54, 51, 47, 52, 52, 50, 47, 46, 49, 47, 37, 29, 12, 42, 39, 36, 29, 27, 22, 13, 20, 14, 9, 0, 67, 72, 79, 34, 31, 34, 22, 6, 12, 8, 64, 9, 2, 66, 82, 73, 80, 88, 10, 14, 69, 10, 14, 3, 67, 68, 69, 80, 70, 77, 81, 103, 79, 83, 92, 4, 73, 82, 74, 64, 3, 4, 11, 7, 14, 0, 10, 9, 16, 20, 22, 23, 9, 54, 36, 29, 23, 15, 13, 69, 76, 89, 4, 35, 29, 28, 20, 19, 10, 8, 3, 66, 74, 64, 3, 4, 11, 7, 14, 0, 10, 9, 16, 20, 22, 23, 9, 54, 36, 29, 23, 15, 13, 69, 76, 89 }, { 52, 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 9, 31, 45, 26, 5, 0, 13, 79, 70, 0, 64, 13, 75, 66, 27, 25, 70, 90, 112, 85, 88, 71, 79, 70, 0, 85, 69, 27, 3, 74, 83, 88, 81, 84, 89, 103, 64, 78, 88, 69, 82, 86, 99, 5, 73, 72, 81, 23, 2, 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 20, 76, 100, 79, 68, 76, 12, 10, 73, 68, 86, 6, 79, 72, 78, 94, 82, 86, 84, 16, 66, 64, 78, 72, 89, 76, 87, 65, 76, 72, 86, 15, 71, 3, 80, 80, 72, 75, 68, 2, 2, 68, 6, 6, 72, 1, 65, 68, 3, 72, 25, 67, 1, 22, 22, 29, 26, 18, 86, 66, 68, 0, 67, 83, 68, 11, 68, 69, 8, 27, 50, 32, 8, 86, 85, 0, 35, 39, 108, 68, 4, 87, 11, 65, 64, 9, 33, 55, 44, 17, 87, 93, 4, 24, 32, 98, 19, 21, 20, 20, 22, 19, 12, 20, 20, 3, 10, 11, 5, 3, 67, 9, 5, 3, 6, 2, 6, 9, 3, 82, 2, 3, 69, 4, 73, 36, 45, 30, 22, 28, 33, 39, 36, 22, 65, 76, 64, 4, 4, 101, 3, 8, 79, 23, 8, 8, 8, 10, 4, 11, 12, 73, 93, 74, 76, 72, 95, 83, 10, 13, 10, 4, 66, 71, 76, 77, 88, 72, 25, 12, 7, 0, 6, 67, 73, 78, 78, 68, 26, 13, 8, 3, 7, 65, 71, 76, 84, 73, 23, 9, 8, 4, 3, 71, 75, 81, 0, 33, 19, 13, 7, 11, 1, 68, 71, 73, 62, 98, 94, 87, 91, 89, 86, 85, 82, 81, 81, 79, 75, 80, 82, 85, 88, 2, 85, 79, 79, 76, 75, 74, 71, 71, 69, 71, 70, 76, 68, 68, 10, 69, 77, 66, 68, 71, 64, 65, 67, 72, 0, 66, 72, 73, 1, 85, 70, 9, 69, 1, 69, 67, 66, 3, 0, 68, 67, 74, 74, 74, 54, 51, 53, 49, 45, 50, 49, 47, 44, 43, 46, 44, 34, 26, 10, 38, 36, 32, 24, 24, 20, 12, 18, 11, 7, 64, 68, 73, 79, 32, 29, 32, 20, 4, 10, 7, 66, 7, 0, 68, 84, 75, 82, 89, 9, 13, 71, 9, 12, 2, 69, 69, 70, 81, 71, 78, 81, 102, 80, 84, 93, 3, 74, 83, 74, 0, 3, 5, 12, 8, 15, 0, 11, 10, 17, 21, 23, 23, 10, 54, 34, 27, 21, 13, 11, 71, 78, 90, 4, 36, 30, 29, 21, 20, 11, 8, 3, 65, 74, 0, 3, 5, 12, 8, 15, 0, 11, 10, 17, 21, 23, 23, 10, 54, 34, 27, 21, 13, 11, 71, 78, 90 }, { 51, 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 8, 29, 44, 26, 6, 2, 12, 79, 69, 0, 65, 14, 76, 67, 26, 24, 74, 92, 113, 83, 86, 71, 79, 69, 0, 84, 68, 28, 3, 73, 82, 87, 83, 85, 89, 104, 64, 78, 88, 69, 83, 85, 99, 4, 73, 72, 80, 23, 2, 22, 0, 0, 0, 70, 91, 97, 1, 5, 66, 20, 75, 99, 77, 67, 75, 14, 12, 72, 67, 84, 7, 78, 72, 76, 95, 82, 86, 84, 17, 66, 0, 76, 72, 88, 75, 85, 65, 76, 72, 86, 16, 72, 3, 80, 80, 71, 75, 67, 2, 2, 67, 7, 6, 72, 1, 65, 68, 3, 72, 25, 67, 1, 22, 22, 30, 27, 19, 87, 65, 67, 64, 67, 83, 68, 12, 68, 69, 9, 28, 50, 34, 9, 87, 86, 0, 35, 39, 108, 69, 4, 88, 11, 66, 0, 10, 33, 55, 45, 18, 88, 92, 3, 21, 29, 96, 19, 21, 20, 19, 21, 18, 12, 19, 19, 3, 10, 11, 4, 3, 68, 8, 4, 2, 6, 2, 6, 9, 2, 84, 2, 2, 69, 3, 74, 33, 43, 28, 20, 26, 31, 36, 34, 20, 67, 78, 66, 2, 2, 103, 2, 6, 81, 21, 7, 7, 7, 8, 2, 9, 10, 75, 93, 75, 77, 73, 94, 82, 12, 14, 10, 4, 65, 71, 76, 76, 87, 71, 26, 13, 8, 0, 7, 66, 72, 77, 76, 68, 26, 13, 8, 3, 8, 65, 70, 75, 83, 73, 25, 10, 8, 4, 3, 70, 75, 81, 0, 34, 19, 14, 7, 12, 1, 68, 70, 73, 62, 96, 92, 85, 89, 87, 85, 83, 81, 80, 79, 77, 73, 80, 81, 85, 88, 5, 85, 80, 79, 76, 74, 74, 71, 71, 69, 71, 70, 77, 68, 68, 10, 69, 77, 66, 68, 71, 64, 65, 67, 73, 64, 66, 72, 73, 2, 86, 70, 10, 70, 1, 69, 67, 65, 3, 0, 67, 66, 74, 74, 76, 53, 50, 52, 47, 43, 47, 47, 44, 42, 40, 43, 41, 31, 23, 8, 35, 32, 28, 19, 22, 17, 10, 16, 9, 5, 65, 69, 74, 79, 30, 27, 30, 18, 2, 9, 5, 68, 5, 66, 70, 86, 77, 84, 90, 8, 11, 73, 7, 10, 0, 71, 71, 72, 82, 72, 79, 82, 101, 81, 86, 95, 2, 76, 84, 73, 0, 4, 5, 13, 9, 16, 0, 12, 11, 18, 21, 24, 24, 11, 53, 32, 25, 19, 11, 9, 73, 80, 91, 5, 36, 30, 30, 21, 21, 11, 9, 4, 65, 73, 0, 4, 5, 13, 9, 16, 0, 12, 11, 18, 21, 24, 24, 11, 53, 32, 25, 19, 11, 9, 73, 80, 91 }, { 50, 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 6, 28, 43, 27, 6, 3, 10, 79, 67, 64, 65, 15, 77, 68, 25, 22, 77, 95, 114, 81, 85, 71, 79, 67, 64, 84, 67, 28, 3, 73, 81, 85, 84, 86, 89, 104, 64, 77, 87, 70, 83, 85, 99, 4, 73, 72, 79, 24, 2, 22, 0, 0, 0, 70, 91, 97, 2, 4, 66, 19, 75, 98, 75, 66, 74, 15, 13, 71, 66, 82, 8, 77, 71, 73, 95, 83, 86, 85, 17, 66, 1, 73, 71, 88, 75, 83, 65, 76, 72, 86, 16, 72, 4, 79, 80, 70, 74, 67, 2, 3, 66, 7, 6, 73, 1, 65, 68, 3, 72, 25, 67, 1, 23, 22, 30, 27, 19, 87, 65, 67, 64, 67, 82, 68, 13, 67, 68, 11, 30, 51, 35, 11, 87, 87, 1, 36, 39, 109, 69, 5, 88, 12, 66, 0, 11, 33, 55, 45, 20, 89, 90, 2, 18, 26, 95, 19, 21, 19, 19, 21, 18, 11, 19, 19, 2, 9, 11, 4, 3, 68, 8, 4, 1, 5, 2, 6, 8, 2, 85, 1, 1, 69, 2, 74, 31, 41, 26, 19, 25, 29, 33, 31, 19, 69, 80, 67, 0, 0, 104, 1, 5, 82, 20, 6, 6, 5, 7, 0, 7, 9, 76, 93, 76, 78, 75, 92, 80, 13, 14, 10, 4, 64, 70, 75, 76, 85, 69, 27, 14, 8, 1, 9, 65, 70, 76, 73, 67, 27, 14, 9, 4, 9, 64, 70, 74, 82, 73, 26, 11, 8, 4, 4, 70, 75, 80, 1, 34, 20, 14, 7, 13, 2, 67, 70, 72, 62, 95, 91, 84, 88, 86, 83, 82, 79, 78, 77, 75, 70, 79, 80, 84, 87, 8, 84, 81, 78, 75, 73, 74, 71, 71, 69, 70, 71, 78, 68, 68, 11, 69, 77, 66, 68, 70, 65, 66, 67, 74, 65, 67, 72, 73, 3, 87, 71, 11, 70, 0, 69, 67, 65, 2, 64, 67, 66, 74, 74, 77, 52, 49, 51, 46, 40, 45, 44, 41, 39, 37, 40, 38, 28, 21, 6, 31, 29, 24, 14, 19, 15, 8, 14, 6, 3, 66, 70, 75, 79, 28, 24, 27, 16, 0, 7, 4, 70, 3, 68, 72, 88, 79, 85, 92, 7, 10, 75, 6, 9, 64, 73, 72, 73, 83, 73, 80, 82, 100, 83, 87, 96, 0, 77, 85, 73, 1, 4, 6, 14, 10, 16, 1, 13, 12, 19, 22, 25, 24, 11, 53, 30, 23, 17, 9, 7, 76, 82, 92, 5, 37, 31, 30, 22, 22, 12, 9, 4, 64, 73, 1, 4, 6, 14, 10, 16, 1, 13, 12, 19, 22, 25, 24, 11, 53, 30, 23, 17, 9, 7, 76, 82, 92 }, { 48, 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 4, 26, 42, 27, 6, 5, 8, 79, 66, 64, 66, 16, 78, 70, 23, 20, 81, 97, 115, 79, 84, 71, 79, 66, 64, 84, 67, 28, 2, 72, 80, 84, 85, 88, 90, 105, 64, 77, 86, 70, 84, 85, 99, 4, 73, 72, 79, 24, 2, 22, 0, 0, 0, 69, 92, 97, 2, 3, 66, 19, 74, 97, 73, 65, 74, 17, 15, 71, 65, 80, 8, 76, 70, 71, 96, 83, 87, 85, 17, 67, 1, 71, 71, 87, 74, 82, 65, 76, 72, 86, 16, 72, 4, 78, 80, 70, 74, 67, 2, 3, 65, 8, 6, 73, 0, 65, 68, 2, 72, 25, 67, 1, 23, 22, 30, 27, 19, 88, 64, 66, 65, 67, 82, 68, 14, 67, 68, 12, 31, 52, 37, 13, 88, 88, 1, 36, 39, 110, 69, 5, 89, 12, 66, 0, 12, 33, 55, 45, 21, 90, 89, 1, 15, 22, 94, 18, 21, 19, 18, 20, 17, 11, 19, 18, 1, 8, 10, 4, 2, 69, 7, 3, 0, 5, 1, 5, 7, 1, 86, 0, 0, 69, 0, 75, 29, 39, 24, 17, 23, 26, 29, 29, 18, 71, 83, 69, 66, 65, 106, 64, 4, 84, 19, 5, 5, 4, 5, 65, 5, 7, 78, 93, 77, 79, 77, 91, 79, 14, 15, 10, 4, 64, 69, 74, 75, 83, 68, 27, 14, 9, 1, 10, 64, 69, 75, 71, 67, 27, 14, 9, 4, 9, 64, 69, 74, 81, 73, 27, 12, 8, 4, 5, 69, 75, 79, 2, 34, 21, 14, 7, 14, 2, 66, 69, 72, 62, 94, 89, 82, 86, 85, 82, 80, 78, 77, 76, 73, 68, 79, 79, 84, 86, 11, 84, 82, 78, 74, 73, 74, 71, 71, 70, 70, 71, 79, 68, 68, 11, 69, 77, 67, 68, 70, 65, 66, 68, 75, 66, 67, 73, 73, 4, 88, 71, 11, 71, 0, 69, 68, 65, 2, 64, 67, 65, 74, 74, 79, 51, 48, 50, 44, 38, 42, 41, 38, 37, 34, 36, 35, 24, 18, 4, 27, 25, 20, 9, 16, 13, 6, 11, 4, 1, 68, 72, 76, 79, 25, 22, 25, 14, 66, 5, 2, 73, 1, 70, 74, 90, 82, 87, 93, 5, 9, 78, 4, 7, 66, 75, 74, 75, 84, 74, 81, 82, 99, 84, 89, 98, 64, 78, 86, 73, 1, 5, 6, 15, 10, 17, 1, 14, 12, 20, 22, 25, 25, 12, 52, 28, 21, 15, 7, 5, 78, 84, 94, 6, 37, 31, 31, 22, 22, 12, 10, 4, 64, 73, 1, 5, 6, 15, 10, 17, 1, 14, 12, 20, 22, 25, 25, 12, 52, 28, 21, 15, 7, 5, 78, 84, 94 }, { 47, 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 2, 24, 42, 28, 6, 7, 7, 78, 64, 64, 66, 17, 78, 71, 22, 18, 84, 99, 115, 77, 82, 70, 78, 64, 64, 83, 66, 29, 2, 71, 79, 83, 86, 89, 90, 105, 64, 77, 85, 70, 84, 85, 99, 4, 72, 71, 78, 24, 2, 22, 0, 0, 0, 68, 92, 97, 2, 2, 66, 19, 73, 95, 70, 64, 73, 19, 17, 70, 64, 78, 9, 74, 69, 69, 97, 83, 87, 85, 18, 67, 2, 69, 71, 86, 73, 80, 65, 75, 72, 85, 17, 72, 5, 77, 79, 69, 74, 67, 3, 4, 64, 9, 6, 73, 0, 64, 67, 2, 72, 26, 67, 1, 23, 23, 31, 27, 20, 88, 0, 65, 66, 67, 82, 68, 15, 67, 67, 13, 32, 53, 39, 15, 89, 89, 2, 36, 40, 111, 69, 5, 89, 13, 66, 0, 14, 33, 55, 45, 22, 91, 88, 0, 13, 19, 92, 18, 21, 19, 17, 20, 17, 11, 19, 18, 0, 7, 10, 4, 2, 69, 7, 2, 0, 5, 1, 5, 6, 0, 87, 0, 0, 69, 64, 75, 27, 37, 23, 16, 21, 24, 26, 27, 17, 73, 85, 70, 68, 66, 107, 65, 3, 86, 18, 4, 4, 3, 3, 66, 3, 5, 79, 93, 77, 80, 78, 89, 77, 16, 16, 10, 4, 0, 68, 73, 74, 81, 67, 28, 15, 10, 2, 11, 0, 68, 74, 69, 66, 28, 15, 10, 4, 10, 0, 68, 73, 79, 72, 29, 13, 9, 4, 6, 68, 74, 78, 3, 35, 22, 15, 8, 15, 3, 65, 68, 71, 62, 92, 87, 80, 84, 84, 81, 78, 76, 75, 74, 71, 66, 78, 78, 83, 85, 15, 84, 83, 78, 73, 72, 73, 71, 71, 70, 70, 71, 80, 68, 68, 12, 69, 77, 67, 68, 70, 65, 66, 68, 75, 67, 67, 73, 72, 6, 88, 71, 12, 72, 0, 69, 68, 64, 2, 64, 66, 64, 73, 73, 81, 50, 47, 49, 42, 36, 39, 39, 35, 35, 32, 33, 33, 21, 15, 2, 23, 22, 17, 4, 13, 11, 5, 9, 2, 0, 69, 73, 77, 79, 23, 20, 23, 12, 68, 3, 1, 75, 64, 72, 76, 92, 84, 89, 94, 4, 8, 80, 3, 5, 67, 77, 75, 76, 85, 75, 81, 82, 98, 85, 90, 100, 65, 79, 87, 73, 2, 6, 7, 17, 11, 18, 1, 15, 13, 21, 23, 26, 26, 13, 51, 27, 19, 13, 5, 3, 80, 86, 95, 7, 37, 32, 32, 23, 23, 13, 11, 5, 0, 73, 2, 6, 7, 17, 11, 18, 1, 15, 13, 21, 23, 26, 26, 13, 51, 27, 19, 13, 5, 3, 80, 86, 95 }, { 46, 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 0, 23, 41, 29, 6, 8, 5, 78, 0, 65, 66, 18, 79, 72, 21, 16, 87, 102, 116, 75, 81, 70, 78, 0, 65, 83, 65, 29, 2, 71, 78, 81, 87, 90, 90, 105, 64, 76, 84, 71, 84, 85, 99, 4, 72, 71, 77, 25, 2, 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 18, 73, 94, 68, 0, 72, 20, 18, 69, 0, 76, 10, 73, 68, 66, 97, 84, 87, 86, 18, 67, 3, 66, 70, 86, 73, 78, 65, 75, 72, 85, 17, 72, 5, 76, 79, 68, 73, 67, 3, 4, 0, 9, 6, 74, 0, 64, 67, 2, 72, 26, 67, 1, 24, 23, 31, 27, 20, 89, 0, 65, 66, 67, 81, 68, 16, 66, 67, 15, 34, 54, 40, 17, 89, 90, 2, 37, 40, 112, 69, 6, 90, 14, 66, 0, 15, 33, 55, 45, 24, 92, 86, 64, 10, 16, 91, 18, 21, 18, 17, 19, 17, 10, 19, 17, 64, 6, 10, 4, 2, 69, 6, 2, 64, 4, 1, 5, 5, 0, 88, 64, 64, 69, 65, 76, 25, 35, 21, 14, 20, 22, 23, 24, 16, 75, 87, 72, 70, 68, 108, 66, 2, 87, 17, 3, 3, 1, 2, 68, 1, 4, 81, 93, 78, 81, 80, 88, 76, 17, 16, 10, 4, 1, 67, 72, 74, 79, 65, 29, 16, 10, 3, 13, 1, 66, 73, 66, 65, 28, 15, 10, 5, 11, 1, 68, 72, 78, 72, 30, 14, 9, 4, 7, 68, 74, 77, 4, 35, 23, 15, 8, 16, 4, 64, 68, 70, 62, 91, 86, 79, 83, 83, 79, 77, 74, 74, 72, 69, 0, 77, 77, 82, 84, 18, 83, 84, 77, 72, 71, 73, 71, 71, 70, 69, 72, 81, 68, 68, 13, 69, 77, 67, 68, 69, 66, 67, 68, 76, 68, 68, 73, 72, 7, 89, 72, 13, 72, 64, 69, 68, 64, 1, 65, 66, 64, 73, 73, 82, 49, 46, 48, 41, 33, 37, 36, 32, 32, 29, 30, 30, 18, 13, 0, 19, 18, 13, 64, 10, 9, 3, 7, 64, 65, 70, 74, 78, 79, 21, 17, 20, 10, 70, 1, 64, 77, 66, 74, 78, 94, 86, 90, 96, 3, 7, 82, 1, 4, 69, 79, 76, 77, 86, 76, 82, 82, 97, 87, 91, 101, 67, 80, 88, 73, 2, 6, 8, 18, 12, 18, 2, 16, 14, 22, 23, 27, 26, 13, 51, 25, 17, 11, 3, 1, 83, 88, 96, 7, 38, 32, 32, 24, 24, 13, 11, 5, 1, 73, 2, 6, 8, 18, 12, 18, 2, 16, 14, 22, 23, 27, 26, 13, 51, 25, 17, 11, 3, 1, 83, 88, 96 }, { 45, 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 64, 21, 40, 29, 7, 10, 4, 78, 2, 65, 67, 19, 80, 73, 20, 15, 91, 104, 117, 73, 79, 70, 78, 2, 65, 82, 64, 30, 2, 70, 77, 80, 89, 91, 90, 106, 64, 76, 84, 71, 85, 84, 99, 3, 72, 71, 76, 25, 2, 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 18, 72, 93, 66, 1, 71, 22, 20, 68, 1, 74, 11, 72, 68, 64, 98, 84, 87, 86, 19, 67, 4, 64, 70, 85, 72, 76, 65, 75, 72, 85, 18, 73, 6, 76, 79, 67, 73, 66, 3, 5, 1, 10, 6, 74, 0, 64, 67, 2, 72, 26, 67, 1, 24, 23, 32, 28, 21, 89, 1, 64, 67, 67, 81, 68, 17, 66, 66, 16, 35, 54, 42, 18, 90, 91, 3, 37, 40, 112, 70, 6, 90, 14, 67, 1, 16, 33, 55, 46, 25, 93, 85, 65, 7, 13, 89, 18, 21, 18, 16, 19, 16, 10, 18, 17, 64, 6, 10, 3, 2, 70, 6, 1, 65, 4, 1, 5, 5, 64, 90, 64, 65, 69, 66, 76, 22, 33, 19, 13, 18, 20, 20, 22, 14, 77, 89, 73, 72, 70, 110, 67, 0, 89, 15, 2, 2, 0, 0, 70, 64, 2, 82, 93, 79, 82, 81, 86, 74, 19, 17, 10, 4, 2, 67, 72, 73, 78, 64, 30, 17, 11, 3, 14, 2, 65, 72, 64, 65, 29, 16, 11, 5, 12, 1, 67, 71, 77, 72, 32, 15, 9, 4, 7, 67, 74, 77, 4, 36, 23, 16, 8, 17, 4, 64, 67, 70, 62, 89, 84, 77, 81, 81, 78, 75, 73, 72, 70, 67, 2, 77, 76, 82, 84, 21, 83, 85, 77, 72, 70, 73, 71, 71, 70, 69, 72, 82, 68, 68, 13, 69, 77, 67, 68, 69, 66, 67, 68, 77, 69, 68, 73, 72, 8, 90, 72, 14, 73, 64, 69, 68, 0, 1, 65, 65, 0, 73, 73, 84, 48, 45, 47, 39, 31, 34, 34, 29, 30, 26, 27, 27, 15, 10, 65, 16, 15, 9, 69, 8, 6, 1, 5, 66, 67, 71, 75, 79, 79, 19, 15, 18, 8, 72, 0, 65, 79, 68, 77, 80, 96, 88, 92, 97, 2, 5, 84, 0, 2, 70, 81, 78, 79, 87, 77, 83, 83, 96, 88, 93, 103, 68, 82, 89, 72, 3, 7, 8, 19, 13, 19, 2, 17, 15, 23, 24, 28, 27, 14, 50, 23, 15, 9, 1, 64, 85, 90, 97, 8, 38, 33, 33, 24, 25, 14, 12, 6, 1, 72, 3, 7, 8, 19, 13, 19, 2, 17, 15, 23, 24, 28, 27, 14, 50, 23, 15, 9, 1, 64, 85, 90, 97 }, { 43, 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 66, 20, 39, 30, 7, 12, 2, 78, 3, 65, 67, 20, 81, 75, 18, 13, 94, 106, 118, 71, 78, 70, 78, 3, 65, 82, 0, 30, 1, 69, 76, 79, 90, 93, 91, 106, 64, 76, 83, 71, 85, 84, 99, 3, 72, 71, 76, 26, 2, 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 17, 72, 92, 64, 2, 70, 23, 21, 68, 2, 72, 12, 71, 67, 1, 99, 84, 87, 86, 19, 68, 5, 1, 70, 84, 71, 74, 65, 75, 72, 85, 18, 73, 6, 75, 79, 67, 73, 66, 3, 5, 2, 10, 6, 74, 64, 64, 67, 2, 72, 26, 67, 1, 25, 23, 32, 28, 21, 90, 1, 0, 67, 67, 81, 68, 18, 66, 66, 17, 36, 55, 43, 20, 90, 92, 3, 37, 40, 113, 70, 7, 91, 15, 67, 1, 17, 33, 55, 46, 26, 94, 83, 66, 4, 10, 88, 17, 21, 17, 15, 18, 16, 9, 18, 16, 65, 5, 9, 3, 2, 70, 5, 0, 66, 3, 0, 4, 4, 64, 91, 65, 66, 69, 68, 77, 20, 31, 17, 11, 16, 18, 16, 19, 13, 79, 92, 75, 74, 72, 111, 69, 64, 91, 14, 1, 1, 64, 64, 72, 66, 0, 84, 93, 80, 83, 83, 85, 73, 20, 17, 10, 4, 3, 66, 71, 72, 76, 0, 31, 17, 12, 4, 15, 3, 0, 71, 1, 64, 29, 16, 11, 6, 13, 2, 67, 71, 76, 72, 33, 16, 9, 4, 8, 67, 74, 76, 5, 36, 24, 16, 8, 18, 5, 0, 67, 69, 62, 88, 83, 76, 79, 80, 76, 74, 71, 71, 69, 65, 4, 76, 75, 81, 83, 24, 83, 86, 77, 71, 70, 73, 71, 71, 71, 68, 72, 83, 68, 68, 14, 69, 77, 67, 68, 69, 67, 67, 68, 78, 70, 68, 73, 72, 9, 91, 72, 14, 74, 64, 69, 68, 0, 1, 66, 65, 1, 73, 73, 85, 47, 44, 46, 37, 29, 32, 31, 26, 27, 23, 23, 24, 11, 7, 67, 12, 11, 5, 74, 5, 4, 64, 3, 69, 69, 73, 76, 80, 79, 16, 13, 16, 6, 74, 65, 67, 81, 70, 79, 82, 98, 91, 94, 98, 1, 4, 87, 65, 0, 72, 83, 79, 80, 88, 78, 84, 83, 95, 89, 94, 104, 69, 83, 90, 72, 3, 7, 9, 20, 13, 20, 2, 18, 16, 24, 24, 29, 27, 15, 50, 21, 13, 7, 64, 66, 87, 92, 99, 8, 39, 33, 34, 25, 25, 14, 12, 6, 2, 72, 3, 7, 9, 20, 13, 20, 2, 18, 16, 24, 24, 29, 27, 15, 50, 21, 13, 7, 64, 66, 87, 92, 99 }, { 42, 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 68, 18, 38, 31, 7, 13, 0, 77, 5, 66, 67, 21, 82, 76, 17, 11, 97, 109, 118, 69, 77, 70, 77, 5, 66, 82, 1, 30, 1, 69, 75, 77, 91, 94, 91, 106, 64, 75, 82, 72, 85, 84, 99, 3, 71, 71, 75, 26, 2, 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 17, 71, 90, 2, 3, 69, 25, 23, 67, 3, 70, 13, 70, 66, 4, 99, 85, 87, 87, 19, 68, 6, 4, 69, 84, 71, 72, 65, 75, 72, 84, 18, 73, 7, 74, 78, 66, 72, 66, 4, 6, 3, 11, 6, 75, 64, 0, 67, 2, 72, 27, 67, 1, 25, 24, 32, 28, 21, 90, 2, 0, 68, 67, 80, 68, 19, 65, 65, 19, 38, 56, 45, 22, 91, 93, 4, 38, 40, 114, 70, 7, 91, 16, 67, 1, 19, 33, 55, 46, 28, 95, 82, 67, 2, 7, 87, 17, 21, 17, 15, 18, 16, 9, 18, 16, 66, 4, 9, 3, 2, 70, 5, 0, 67, 3, 0, 4, 3, 65, 92, 66, 66, 69, 69, 77, 18, 29, 16, 10, 15, 16, 13, 17, 12, 81, 94, 76, 76, 74, 112, 70, 65, 92, 13, 0, 0, 66, 66, 74, 68, 64, 85, 93, 81, 84, 85, 83, 71, 21, 18, 10, 4, 4, 65, 70, 72, 74, 2, 32, 18, 12, 5, 17, 4, 1, 70, 4, 0, 30, 17, 12, 6, 14, 3, 66, 70, 75, 71, 34, 17, 9, 4, 9, 66, 73, 75, 6, 36, 25, 16, 8, 19, 6, 1, 66, 68, 62, 87, 81, 74, 78, 79, 75, 72, 69, 69, 67, 0, 7, 75, 74, 80, 82, 27, 82, 87, 76, 70, 69, 73, 71, 71, 71, 68, 73, 84, 68, 68, 15, 69, 77, 67, 68, 68, 67, 68, 68, 79, 71, 69, 73, 72, 10, 91, 73, 15, 74, 65, 69, 68, 0, 0, 66, 65, 1, 73, 73, 87, 46, 43, 45, 36, 26, 29, 28, 23, 25, 20, 20, 21, 8, 5, 69, 8, 8, 1, 79, 2, 2, 65, 1, 71, 71, 74, 77, 81, 79, 14, 10, 13, 4, 76, 67, 68, 83, 72, 81, 84, 100, 93, 95, 100, 0, 3, 89, 66, 64, 73, 85, 80, 81, 89, 79, 85, 83, 94, 91, 95, 106, 71, 84, 91, 72, 4, 8, 10, 21, 14, 20, 3, 19, 17, 25, 25, 30, 28, 15, 49, 19, 11, 5, 66, 68, 90, 94, 100, 9, 39, 34, 34, 26, 26, 15, 13, 6, 3, 72, 4, 8, 10, 21, 14, 20, 3, 19, 17, 25, 25, 30, 28, 15, 49, 19, 11, 5, 66, 68, 90, 94, 100 }, { 41, 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 70, 17, 37, 31, 7, 15, 64, 77, 6, 66, 68, 22, 83, 77, 16, 9, 101, 111, 119, 67, 75, 70, 77, 6, 66, 81, 2, 31, 1, 68, 74, 76, 92, 95, 91, 107, 64, 75, 81, 72, 86, 84, 99, 3, 71, 71, 74, 27, 2, 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 16, 71, 89, 4, 4, 68, 26, 24, 66, 4, 68, 14, 69, 65, 6, 100, 85, 87, 87, 20, 68, 7, 6, 69, 83, 70, 70, 65, 75, 72, 84, 19, 73, 7, 73, 78, 65, 72, 66, 4, 6, 4, 11, 6, 75, 64, 0, 67, 2, 72, 27, 67, 1, 26, 24, 33, 28, 22, 91, 2, 1, 68, 67, 80, 68, 20, 65, 65, 20, 39, 57, 46, 24, 91, 94, 4, 38, 40, 115, 70, 8, 92, 16, 67, 1, 20, 33, 55, 46, 29, 96, 80, 68, 64, 4, 85, 17, 21, 16, 14, 17, 15, 8, 18, 15, 67, 3, 9, 3, 2, 71, 4, 64, 68, 2, 0, 4, 2, 65, 93, 66, 67, 69, 70, 78, 16, 27, 14, 8, 13, 14, 10, 14, 11, 83, 96, 78, 78, 76, 114, 71, 66, 94, 12, 64, 64, 67, 67, 76, 70, 66, 87, 93, 82, 85, 86, 82, 70, 23, 18, 10, 4, 5, 64, 69, 71, 72, 3, 33, 19, 13, 5, 18, 5, 3, 69, 6, 0, 30, 17, 12, 7, 15, 3, 66, 69, 74, 71, 36, 18, 9, 4, 10, 66, 73, 74, 7, 37, 26, 17, 8, 20, 6, 2, 66, 68, 62, 85, 80, 73, 76, 78, 73, 71, 68, 68, 65, 2, 9, 75, 73, 80, 81, 30, 82, 88, 76, 69, 68, 73, 71, 71, 71, 67, 73, 85, 68, 68, 15, 69, 77, 67, 68, 68, 68, 68, 68, 80, 72, 69, 73, 72, 11, 92, 73, 16, 75, 65, 69, 68, 1, 0, 67, 64, 2, 73, 73, 88, 45, 42, 44, 34, 24, 27, 26, 20, 22, 17, 17, 18, 5, 2, 71, 4, 4, 66, 84, 64, 0, 67, 64, 74, 73, 75, 78, 82, 79, 12, 8, 11, 2, 78, 69, 70, 85, 74, 83, 86, 102, 95, 97, 101, 64, 2, 91, 68, 66, 75, 87, 82, 83, 90, 80, 86, 83, 93, 92, 97, 107, 72, 85, 92, 72, 4, 8, 10, 22, 15, 21, 3, 20, 18, 26, 25, 31, 28, 16, 49, 17, 9, 3, 68, 70, 92, 96, 101, 9, 40, 34, 35, 26, 27, 15, 13, 7, 3, 72, 4, 8, 10, 22, 15, 21, 3, 20, 18, 26, 25, 31, 28, 16, 49, 17, 9, 3, 68, 70, 92, 96, 101 }, { 40, 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 72, 15, 36, 32, 7, 17, 66, 77, 8, 66, 68, 23, 84, 78, 15, 7, 104, 113, 120, 65, 74, 70, 77, 8, 66, 81, 3, 31, 1, 67, 73, 75, 93, 96, 91, 107, 64, 75, 80, 72, 86, 84, 99, 3, 71, 71, 73, 27, 2, 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 16, 70, 88, 6, 5, 67, 28, 26, 65, 5, 66, 15, 68, 64, 8, 101, 85, 87, 87, 20, 68, 8, 8, 69, 82, 69, 68, 65, 75, 72, 84, 19, 73, 8, 72, 78, 64, 72, 66, 4, 7, 5, 12, 6, 75, 64, 0, 67, 2, 72, 27, 67, 1, 26, 24, 33, 28, 22, 91, 3, 2, 69, 67, 80, 68, 21, 65, 64, 21, 40, 58, 48, 26, 92, 95, 5, 38, 40, 116, 70, 8, 92, 17, 67, 1, 21, 33, 55, 46, 30, 97, 79, 69, 67, 1, 84, 17, 21, 16, 13, 17, 15, 8, 18, 15, 68, 2, 9, 3, 2, 71, 4, 65, 69, 2, 0, 4, 1, 66, 94, 67, 68, 69, 71, 78, 14, 25, 12, 7, 11, 12, 7, 12, 10, 85, 98, 79, 80, 78, 115, 72, 67, 96, 11, 65, 65, 68, 69, 78, 72, 68, 88, 93, 83, 86, 88, 80, 68, 24, 19, 10, 4, 6, 0, 68, 70, 70, 4, 34, 20, 14, 6, 19, 6, 4, 68, 8, 1, 31, 18, 13, 7, 16, 4, 65, 68, 73, 71, 37, 19, 9, 4, 11, 65, 73, 73, 8, 37, 27, 17, 8, 21, 7, 3, 65, 67, 62, 84, 78, 71, 74, 77, 72, 69, 66, 66, 0, 4, 11, 74, 72, 79, 80, 33, 82, 89, 76, 68, 67, 73, 71, 71, 71, 67, 73, 86, 68, 68, 16, 69, 77, 67, 68, 68, 68, 68, 68, 81, 73, 69, 73, 72, 12, 93, 73, 17, 76, 65, 69, 68, 1, 0, 67, 64, 3, 73, 73, 90, 44, 41, 43, 32, 22, 24, 23, 17, 20, 14, 14, 15, 2, 64, 73, 0, 1, 70, 89, 67, 65, 69, 66, 76, 75, 76, 79, 83, 79, 10, 6, 9, 0, 80, 71, 71, 87, 76, 85, 88, 104, 97, 99, 102, 65, 1, 93, 69, 68, 76, 89, 83, 84, 91, 81, 87, 83, 92, 93, 98, 109, 73, 86, 93, 72, 5, 9, 11, 23, 16, 22, 3, 21, 19, 27, 26, 32, 29, 17, 48, 15, 7, 1, 70, 72, 94, 98, 102, 10, 40, 35, 36, 27, 28, 16, 14, 7, 4, 72, 5, 9, 11, 23, 16, 22, 3, 21, 19, 27, 26, 32, 29, 17, 48, 15, 7, 1, 70, 72, 94, 98, 102 }, { 38, 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 74, 13, 35, 32, 7, 18, 68, 77, 9, 67, 69, 24, 85, 80, 13, 5, 108, 116, 121, 0, 73, 70, 77, 9, 67, 81, 3, 31, 0, 67, 73, 74, 95, 98, 92, 108, 65, 75, 80, 73, 87, 84, 99, 2, 71, 71, 73, 27, 1, 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 15, 70, 87, 8, 6, 67, 29, 27, 65, 6, 65, 15, 67, 64, 10, 102, 86, 88, 88, 20, 69, 8, 10, 69, 82, 69, 67, 65, 75, 72, 84, 19, 74, 8, 72, 78, 64, 72, 66, 4, 7, 6, 12, 6, 76, 65, 0, 67, 1, 72, 27, 67, 1, 26, 24, 33, 28, 22, 92, 3, 2, 70, 67, 80, 69, 21, 65, 64, 22, 41, 58, 49, 27, 93, 97, 5, 38, 40, 117, 71, 8, 93, 17, 68, 1, 22, 33, 54, 46, 31, 98, 78, 71, 70, 66, 83, 16, 21, 15, 12, 16, 14, 7, 17, 14, 69, 1, 8, 2, 1, 72, 3, 66, 70, 1, 64, 3, 0, 67, 96, 68, 69, 69, 73, 79, 11, 22, 10, 5, 9, 9, 3, 9, 8, 87, 101, 81, 83, 80, 117, 74, 69, 98, 9, 66, 66, 70, 71, 80, 74, 70, 90, 93, 84, 87, 90, 79, 67, 25, 19, 10, 4, 6, 0, 68, 70, 69, 5, 34, 20, 14, 6, 20, 7, 5, 68, 10, 1, 31, 18, 13, 7, 16, 4, 65, 68, 72, 71, 38, 20, 9, 3, 11, 65, 73, 73, 8, 37, 27, 17, 8, 22, 7, 3, 65, 67, 62, 83, 77, 70, 73, 76, 71, 68, 65, 65, 1, 5, 13, 74, 72, 79, 80, 36, 82, 91, 76, 68, 67, 73, 71, 71, 72, 67, 74, 87, 69, 68, 16, 70, 77, 68, 68, 68, 69, 69, 69, 82, 74, 70, 74, 72, 13, 94, 74, 17, 77, 66, 69, 69, 1, 64, 68, 64, 3, 73, 73, 92, 42, 40, 41, 30, 19, 21, 20, 14, 17, 11, 10, 12, 65, 67, 75, 67, 66, 74, 95, 70, 68, 71, 69, 79, 77, 78, 81, 84, 79, 7, 3, 6, 65, 83, 73, 73, 90, 78, 88, 90, 107, 100, 101, 104, 67, 64, 96, 71, 70, 78, 91, 85, 86, 92, 82, 88, 84, 91, 95, 100, 111, 75, 88, 95, 72, 5, 9, 11, 24, 16, 22, 3, 22, 19, 28, 26, 32, 29, 17, 47, 13, 5, 65, 73, 74, 97, 100, 104, 10, 40, 35, 36, 27, 28, 16, 14, 7, 4, 72, 5, 9, 11, 24, 16, 22, 3, 22, 19, 28, 26, 32, 29, 17, 47, 13, 5, 65, 73, 74, 97, 100, 104 }, { 37, 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 75, 12, 35, 33, 8, 20, 69, 76, 11, 67, 69, 26, 85, 81, 12, 4, 111, 118, 121, 2, 71, 69, 76, 11, 67, 80, 4, 32, 0, 66, 72, 72, 96, 99, 92, 108, 65, 74, 79, 73, 87, 83, 98, 2, 70, 70, 72, 28, 1, 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 15, 69, 85, 11, 8, 66, 31, 29, 64, 8, 0, 16, 65, 0, 13, 102, 86, 88, 88, 21, 69, 9, 13, 68, 81, 68, 65, 65, 74, 72, 83, 20, 74, 9, 71, 77, 0, 71, 65, 5, 8, 7, 13, 7, 76, 65, 1, 66, 1, 71, 28, 66, 1, 27, 25, 34, 29, 23, 92, 4, 3, 70, 67, 79, 69, 22, 64, 0, 24, 43, 59, 51, 29, 93, 98, 6, 39, 41, 117, 71, 9, 93, 18, 68, 2, 24, 33, 54, 47, 33, 99, 76, 72, 72, 69, 81, 16, 21, 15, 12, 16, 14, 7, 17, 14, 69, 1, 8, 2, 1, 72, 3, 66, 70, 1, 64, 3, 0, 67, 97, 68, 69, 69, 74, 79, 9, 20, 9, 4, 8, 7, 0, 7, 7, 88, 103, 82, 85, 81, 118, 75, 70, 99, 8, 66, 66, 71, 72, 81, 75, 71, 91, 93, 84, 87, 91, 77, 65, 27, 20, 10, 4, 7, 1, 67, 69, 67, 7, 35, 21, 15, 7, 22, 8, 7, 67, 13, 2, 32, 19, 14, 8, 17, 5, 64, 67, 70, 70, 40, 21, 10, 3, 12, 64, 72, 72, 9, 38, 28, 18, 9, 23, 8, 4, 64, 66, 62, 81, 75, 68, 71, 74, 69, 66, 0, 0, 3, 7, 16, 73, 71, 78, 79, 40, 81, 92, 75, 67, 66, 72, 71, 70, 72, 66, 74, 87, 69, 68, 17, 70, 77, 68, 68, 67, 69, 69, 69, 82, 74, 70, 74, 71, 15, 94, 74, 18, 77, 66, 69, 69, 2, 64, 68, 0, 4, 72, 72, 93, 41, 39, 40, 29, 17, 19, 18, 11, 15, 9, 7, 10, 68, 69, 77, 70, 69, 77, 100, 72, 70, 72, 71, 81, 78, 79, 82, 84, 79, 5, 1, 4, 67, 85, 74, 74, 92, 79, 90, 91, 109, 102, 102, 105, 68, 65, 98, 72, 71, 79, 92, 86, 87, 93, 82, 88, 84, 90, 96, 101, 112, 76, 89, 96, 71, 6, 10, 12, 26, 17, 23, 4, 24, 20, 29, 27, 33, 30, 18, 47, 12, 4, 67, 75, 75, 99, 101, 105, 11, 41, 36, 37, 28, 29, 17, 15, 8, 5, 71, 6, 10, 12, 26, 17, 23, 4, 24, 20, 29, 27, 33, 30, 18, 47, 12, 4, 67, 75, 75, 99, 101, 105 }, { 36, 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 77, 10, 34, 34, 8, 22, 71, 76, 12, 67, 69, 27, 86, 82, 11, 2, 114, 120, 122, 4, 70, 69, 76, 12, 67, 80, 5, 32, 0, 65, 71, 71, 97, 100, 92, 108, 65, 74, 78, 73, 87, 83, 98, 2, 70, 70, 71, 28, 1, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 15, 68, 84, 13, 9, 65, 33, 31, 0, 9, 2, 17, 64, 1, 15, 103, 86, 88, 88, 21, 69, 10, 15, 68, 80, 67, 0, 65, 74, 72, 83, 20, 74, 9, 70, 77, 1, 71, 65, 5, 8, 8, 14, 7, 76, 65, 1, 66, 1, 71, 28, 66, 1, 27, 25, 34, 29, 23, 93, 5, 4, 71, 67, 79, 69, 23, 64, 0, 25, 44, 60, 53, 31, 94, 99, 6, 39, 41, 118, 71, 9, 94, 19, 68, 2, 25, 33, 54, 47, 34, 100, 75, 73, 75, 72, 80, 16, 21, 15, 11, 15, 14, 7, 17, 13, 70, 0, 8, 2, 1, 72, 2, 67, 71, 1, 64, 3, 64, 68, 98, 69, 70, 69, 75, 80, 7, 18, 7, 2, 6, 5, 66, 5, 6, 90, 105, 84, 87, 83, 119, 76, 71, 101, 7, 67, 67, 72, 74, 83, 77, 73, 93, 93, 85, 88, 93, 76, 64, 28, 21, 10, 4, 8, 2, 66, 68, 65, 8, 36, 22, 16, 8, 23, 9, 8, 66, 15, 3, 32, 19, 14, 8, 18, 6, 0, 66, 69, 70, 41, 22, 10, 3, 13, 0, 72, 71, 10, 38, 29, 18, 9, 24, 9, 5, 0, 65, 62, 80, 73, 66, 69, 73, 68, 64, 2, 1, 5, 9, 18, 72, 70, 77, 78, 43, 81, 93, 75, 66, 65, 72, 71, 70, 72, 66, 74, 88, 69, 68, 18, 70, 77, 68, 68, 67, 69, 69, 69, 83, 75, 70, 74, 71, 16, 95, 74, 19, 78, 66, 69, 69, 2, 64, 68, 0, 5, 72, 72, 95, 40, 38, 39, 27, 15, 16, 15, 8, 13, 6, 4, 7, 71, 72, 79, 74, 73, 81, 105, 75, 72, 74, 73, 83, 80, 80, 83, 85, 79, 3, 64, 2, 69, 87, 76, 76, 94, 81, 92, 93, 111, 104, 104, 106, 69, 66, 100, 74, 73, 81, 94, 87, 88, 94, 83, 89, 84, 89, 97, 102, 114, 77, 90, 97, 71, 6, 11, 13, 27, 18, 24, 4, 25, 21, 30, 27, 34, 31, 19, 46, 10, 2, 69, 77, 77, 101, 103, 106, 12, 41, 36, 38, 29, 30, 17, 16, 8, 6, 71, 6, 11, 13, 27, 18, 24, 4, 25, 21, 30, 27, 34, 31, 19, 46, 10, 2, 69, 77, 77, 101, 103, 106 }, { 35, 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 79, 9, 33, 34, 8, 24, 72, 76, 14, 67, 70, 28, 87, 83, 10, 0, 118, 122, 123, 6, 68, 69, 76, 14, 67, 79, 6, 33, 0, 64, 70, 70, 98, 101, 92, 109, 65, 74, 77, 73, 88, 83, 98, 2, 70, 70, 70, 29, 1, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 14, 68, 83, 15, 10, 64, 34, 32, 1, 10, 4, 18, 0, 2, 17, 104, 86, 88, 88, 22, 69, 11, 17, 68, 79, 66, 2, 65, 74, 72, 83, 21, 74, 10, 69, 77, 2, 71, 65, 5, 9, 9, 14, 7, 76, 65, 1, 66, 1, 71, 28, 66, 1, 28, 25, 35, 29, 24, 93, 5, 5, 71, 67, 79, 69, 24, 64, 1, 26, 45, 61, 54, 33, 94, 100, 7, 39, 41, 119, 71, 10, 94, 19, 68, 2, 26, 33, 54, 47, 35, 101, 73, 74, 78, 75, 78, 16, 21, 14, 10, 15, 13, 6, 17, 13, 71, 64, 8, 2, 1, 73, 2, 68, 72, 0, 64, 3, 65, 68, 99, 69, 71, 69, 76, 80, 5, 16, 5, 1, 4, 3, 69, 2, 5, 92, 107, 85, 89, 85, 121, 77, 72, 103, 6, 68, 68, 73, 75, 85, 79, 75, 94, 93, 86, 89, 94, 74, 1, 30, 21, 10, 4, 9, 3, 65, 67, 0, 9, 37, 23, 17, 8, 24, 10, 10, 65, 17, 3, 33, 20, 15, 9, 19, 6, 0, 65, 68, 70, 43, 23, 10, 3, 14, 0, 72, 70, 11, 39, 30, 19, 9, 25, 9, 6, 0, 65, 62, 78, 72, 65, 67, 72, 66, 0, 3, 3, 7, 11, 20, 72, 69, 77, 77, 46, 81, 94, 75, 65, 64, 72, 71, 70, 72, 65, 74, 89, 69, 68, 18, 70, 77, 68, 68, 67, 70, 69, 69, 84, 76, 70, 74, 71, 17, 96, 74, 20, 79, 66, 69, 69, 3, 64, 69, 1, 6, 72, 72, 96, 39, 37, 38, 25, 13, 14, 13, 5, 10, 3, 1, 4, 74, 75, 81, 78, 76, 85, 110, 78, 74, 76, 75, 86, 82, 81, 84, 86, 79, 1, 66, 0, 71, 89, 78, 77, 96, 83, 94, 95, 113, 106, 106, 107, 70, 67, 102, 75, 75, 82, 96, 89, 90, 95, 84, 90, 84, 88, 98, 104, 115, 78, 91, 98, 71, 7, 11, 13, 28, 19, 25, 4, 26, 22, 31, 28, 35, 31, 20, 46, 8, 0, 71, 79, 79, 103, 105, 107, 12, 42, 37, 39, 29, 31, 18, 16, 9, 6, 71, 7, 11, 13, 28, 19, 25, 4, 26, 22, 31, 28, 35, 31, 20, 46, 8, 0, 71, 79, 79, 103, 105, 107 }, { 33, 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 81, 7, 32, 35, 8, 25, 74, 76, 15, 68, 70, 29, 88, 85, 8, 65, 121, 125, 124, 8, 67, 69, 76, 15, 68, 79, 7, 33, 64, 64, 69, 68, 99, 103, 93, 109, 65, 73, 76, 74, 88, 83, 98, 2, 70, 70, 70, 29, 1, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 14, 67, 82, 17, 11, 0, 36, 34, 1, 11, 6, 19, 1, 3, 20, 104, 87, 88, 89, 22, 70, 12, 20, 67, 79, 66, 4, 65, 74, 72, 83, 21, 74, 10, 68, 77, 2, 70, 65, 5, 9, 10, 15, 7, 77, 66, 1, 66, 1, 71, 28, 66, 1, 28, 25, 35, 29, 24, 94, 6, 5, 72, 67, 78, 69, 25, 0, 1, 28, 47, 62, 56, 35, 95, 101, 7, 40, 41, 120, 71, 10, 95, 20, 68, 2, 27, 33, 54, 47, 37, 102, 72, 75, 81, 78, 77, 15, 21, 14, 10, 14, 13, 6, 17, 12, 72, 65, 7, 2, 1, 73, 1, 68, 73, 0, 65, 2, 66, 69, 100, 70, 72, 69, 78, 81, 3, 14, 3, 64, 3, 1, 73, 0, 4, 94, 110, 87, 91, 87, 122, 79, 73, 104, 5, 69, 69, 75, 77, 87, 81, 76, 96, 93, 87, 90, 96, 73, 2, 31, 22, 10, 4, 10, 4, 64, 67, 2, 11, 38, 23, 17, 9, 26, 11, 11, 64, 20, 4, 33, 20, 15, 9, 20, 7, 1, 65, 67, 70, 44, 24, 10, 3, 15, 1, 72, 69, 12, 39, 31, 19, 9, 26, 10, 7, 1, 64, 62, 77, 70, 0, 66, 71, 65, 2, 5, 4, 8, 13, 23, 71, 68, 76, 76, 49, 80, 95, 74, 64, 64, 72, 71, 70, 73, 65, 75, 90, 69, 68, 19, 70, 77, 68, 68, 66, 70, 70, 69, 85, 77, 71, 74, 71, 18, 97, 75, 20, 79, 67, 69, 69, 3, 65, 69, 1, 6, 72, 72, 98, 38, 36, 37, 24, 10, 11, 10, 2, 8, 0, 66, 1, 78, 77, 83, 82, 80, 89, 115, 81, 76, 78, 77, 88, 84, 83, 85, 87, 79, 65, 69, 66, 73, 91, 80, 79, 98, 85, 96, 97, 115, 109, 107, 109, 71, 68, 105, 77, 76, 84, 98, 90, 91, 96, 85, 91, 84, 87, 100, 105, 117, 80, 92, 99, 71, 7, 12, 14, 29, 19, 25, 5, 27, 23, 32, 28, 36, 32, 20, 45, 6, 65, 73, 81, 81, 106, 107, 109, 13, 42, 37, 39, 30, 31, 18, 17, 9, 7, 71, 7, 12, 14, 29, 19, 25, 5, 27, 23, 32, 28, 36, 32, 20, 45, 6, 65, 73, 81, 81, 106, 107, 109 }, { 32, 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 83, 6, 31, 36, 8, 27, 76, 75, 17, 68, 70, 30, 89, 86, 7, 67, 124, 126, 124, 10, 66, 69, 75, 17, 68, 79, 8, 33, 64, 0, 68, 67, 100, 104, 93, 109, 65, 73, 75, 74, 88, 83, 98, 2, 69, 70, 69, 30, 1, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 13, 67, 80, 20, 12, 1, 37, 35, 2, 12, 8, 20, 2, 4, 22, 105, 87, 88, 89, 22, 70, 13, 22, 67, 78, 65, 6, 65, 74, 72, 82, 21, 74, 11, 67, 76, 3, 70, 65, 6, 10, 11, 15, 7, 77, 66, 2, 66, 1, 71, 29, 66, 1, 29, 26, 35, 29, 24, 94, 6, 6, 72, 67, 78, 69, 26, 0, 2, 29, 48, 62, 57, 37, 95, 102, 8, 40, 41, 121, 71, 11, 95, 21, 68, 2, 29, 33, 54, 47, 38, 103, 70, 76, 83, 81, 76, 15, 21, 13, 9, 14, 13, 5, 17, 12, 73, 66, 7, 2, 1, 73, 1, 69, 74, 64, 65, 2, 67, 69, 101, 71, 72, 69, 79, 81, 1, 12, 2, 65, 1, 64, 76, 66, 3, 96, 112, 88, 93, 89, 123, 80, 74, 106, 4, 70, 70, 76, 78, 89, 83, 78, 97, 93, 88, 91, 98, 71, 4, 32, 22, 10, 4, 11, 5, 0, 66, 4, 12, 39, 24, 18, 10, 27, 12, 13, 0, 22, 5, 34, 21, 16, 10, 21, 8, 1, 64, 66, 69, 45, 25, 10, 3, 16, 1, 71, 68, 13, 39, 32, 19, 9, 27, 11, 8, 1, 0, 62, 76, 69, 1, 64, 70, 0, 3, 7, 6, 10, 15, 25, 70, 67, 75, 75, 52, 80, 96, 74, 0, 0, 72, 71, 70, 73, 64, 75, 91, 69, 68, 20, 70, 77, 68, 68, 66, 71, 70, 69, 86, 78, 71, 74, 71, 19, 97, 75, 21, 80, 67, 69, 69, 3, 65, 70, 1, 7, 72, 72, 99, 37, 35, 36, 22, 8, 9, 7, 64, 5, 66, 69, 65, 81, 80, 85, 86, 83, 93, 120, 84, 78, 79, 79, 91, 86, 84, 86, 88, 79, 67, 71, 68, 75, 93, 82, 80, 100, 87, 98, 99, 117, 111, 109, 110, 72, 69, 107, 78, 78, 85, 100, 91, 92, 97, 86, 92, 84, 86, 101, 106, 118, 81, 93, 100, 71, 8, 12, 15, 30, 20, 26, 5, 28, 24, 33, 29, 37, 32, 21, 45, 4, 67, 75, 83, 83, 108, 109, 110, 13, 43, 38, 40, 31, 32, 19, 17, 9, 8, 71, 8, 12, 15, 30, 20, 26, 5, 28, 24, 33, 29, 37, 32, 21, 45, 4, 67, 75, 83, 83, 108, 109, 110 }, { 31, 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 84, 4, 30, 36, 9, 29, 77, 75, 18, 68, 71, 31, 90, 87, 6, 68, 126, 126, 125, 12, 64, 69, 75, 18, 68, 78, 9, 34, 64, 1, 67, 66, 102, 105, 93, 110, 65, 73, 75, 74, 89, 82, 98, 1, 69, 70, 68, 30, 1, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 13, 66, 79, 22, 13, 2, 39, 37, 3, 13, 10, 21, 3, 4, 24, 106, 87, 88, 89, 23, 70, 14, 24, 67, 77, 64, 8, 65, 74, 72, 82, 22, 75, 11, 67, 76, 4, 70, 64, 6, 10, 12, 16, 7, 77, 66, 2, 66, 1, 71, 29, 66, 1, 29, 26, 36, 30, 25, 95, 7, 7, 73, 67, 78, 69, 27, 0, 2, 30, 49, 62, 59, 38, 96, 103, 8, 40, 41, 121, 72, 11, 96, 21, 69, 3, 30, 33, 54, 48, 39, 104, 69, 77, 86, 84, 74, 15, 21, 13, 8, 13, 12, 5, 16, 11, 73, 66, 7, 1, 1, 74, 0, 70, 75, 64, 65, 2, 67, 70, 103, 71, 73, 69, 80, 82, 65, 10, 0, 67, 64, 66, 79, 68, 1, 98, 114, 90, 95, 91, 125, 81, 76, 108, 2, 71, 71, 77, 80, 91, 85, 80, 99, 93, 89, 92, 99, 70, 5, 34, 23, 10, 4, 12, 5, 0, 65, 5, 13, 40, 25, 19, 10, 28, 13, 14, 1, 24, 5, 34, 21, 16, 10, 22, 8, 2, 0, 65, 69, 47, 26, 10, 3, 16, 2, 71, 68, 13, 40, 32, 20, 9, 28, 11, 8, 2, 0, 62, 74, 67, 3, 1, 68, 1, 5, 8, 7, 12, 17, 27, 70, 66, 75, 75, 55, 80, 97, 74, 0, 1, 72, 71, 70, 73, 64, 75, 92, 69, 68, 20, 70, 77, 68, 68, 66, 71, 70, 69, 87, 79, 71, 74, 71, 20, 98, 75, 22, 81, 67, 69, 69, 4, 65, 70, 2, 8, 72, 72, 101, 36, 34, 35, 20, 6, 6, 5, 67, 3, 69, 72, 68, 84, 83, 87, 89, 87, 97, 125, 86, 81, 81, 81, 93, 88, 85, 87, 89, 79, 69, 73, 70, 77, 95, 83, 82, 102, 89, 101, 101, 119, 113, 111, 111, 73, 71, 109, 80, 80, 87, 102, 93, 94, 98, 87, 93, 85, 85, 102, 108, 120, 82, 95, 101, 70, 8, 13, 15, 31, 21, 27, 5, 29, 25, 34, 29, 38, 33, 22, 44, 2, 69, 77, 85, 85, 110, 111, 111, 14, 43, 38, 41, 31, 33, 19, 18, 10, 8, 70, 8, 13, 15, 31, 21, 27, 5, 29, 25, 34, 29, 38, 33, 22, 44, 2, 69, 77, 85, 85, 110, 111, 111 }, { 30, 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 86, 3, 29, 37, 9, 30, 79, 75, 20, 69, 71, 32, 91, 88, 5, 70, 126, 126, 126, 14, 0, 69, 75, 20, 69, 78, 10, 34, 64, 1, 66, 64, 103, 106, 93, 110, 65, 72, 74, 75, 89, 82, 98, 1, 69, 70, 67, 31, 1, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 12, 66, 78, 24, 14, 3, 40, 38, 4, 14, 12, 22, 4, 5, 27, 106, 88, 88, 90, 23, 70, 15, 27, 66, 77, 64, 10, 65, 74, 72, 82, 22, 75, 12, 66, 76, 5, 69, 64, 6, 11, 13, 16, 7, 78, 66, 2, 66, 1, 71, 29, 66, 1, 30, 26, 36, 30, 25, 95, 7, 7, 73, 67, 77, 69, 28, 1, 3, 32, 51, 62, 60, 40, 96, 104, 9, 41, 41, 122, 72, 12, 96, 22, 69, 3, 31, 33, 54, 48, 41, 105, 67, 78, 89, 87, 73, 15, 21, 12, 8, 13, 12, 4, 16, 11, 74, 67, 7, 1, 1, 74, 0, 70, 76, 65, 65, 2, 68, 70, 104, 72, 74, 69, 81, 82, 67, 8, 65, 68, 65, 68, 82, 71, 0, 100, 116, 91, 97, 93, 126, 82, 77, 109, 1, 72, 72, 79, 81, 93, 87, 81, 100, 93, 90, 93, 101, 68, 7, 35, 23, 10, 4, 13, 6, 1, 65, 7, 15, 41, 26, 19, 11, 30, 14, 16, 2, 27, 6, 35, 22, 17, 11, 23, 9, 2, 1, 64, 69, 48, 27, 10, 3, 17, 2, 71, 67, 14, 40, 33, 20, 9, 29, 12, 9, 2, 1, 62, 73, 66, 4, 2, 67, 3, 6, 10, 9, 14, 19, 30, 69, 65, 74, 74, 58, 79, 98, 73, 1, 2, 72, 71, 70, 73, 0, 76, 93, 69, 68, 21, 70, 77, 68, 68, 65, 72, 71, 69, 88, 80, 72, 74, 71, 21, 99, 76, 23, 81, 68, 69, 69, 4, 66, 71, 2, 8, 72, 72, 102, 35, 33, 34, 19, 3, 4, 2, 70, 0, 72, 75, 71, 87, 85, 89, 93, 90, 101, 126, 89, 83, 83, 83, 96, 90, 86, 88, 90, 79, 71, 76, 73, 79, 97, 85, 83, 104, 91, 103, 103, 121, 115, 112, 113, 74, 72, 111, 81, 81, 88, 104, 94, 95, 99, 88, 94, 85, 84, 104, 109, 121, 84, 96, 102, 70, 9, 13, 16, 32, 22, 27, 6, 30, 26, 35, 30, 39, 33, 22, 44, 0, 71, 79, 87, 87, 113, 113, 112, 14, 44, 39, 41, 32, 34, 20, 18, 10, 9, 70, 9, 13, 16, 32, 22, 27, 6, 30, 26, 35, 30, 39, 33, 22, 44, 0, 71, 79, 87, 87, 113, 113, 112 }, { 28, 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 88, 1, 28, 37, 9, 32, 81, 75, 21, 69, 72, 33, 92, 90, 3, 72, 126, 126, 126, 16, 1, 69, 75, 21, 69, 78, 10, 34, 65, 2, 65, 0, 104, 108, 94, 111, 65, 72, 73, 75, 90, 82, 98, 1, 69, 70, 67, 31, 1, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 12, 65, 77, 26, 15, 3, 42, 40, 4, 15, 14, 22, 5, 6, 29, 107, 88, 89, 90, 23, 71, 15, 29, 66, 76, 0, 11, 65, 74, 72, 82, 22, 75, 12, 65, 76, 5, 69, 64, 6, 11, 14, 17, 7, 78, 67, 2, 66, 0, 71, 29, 66, 1, 30, 26, 36, 30, 25, 96, 8, 8, 74, 67, 77, 69, 29, 1, 3, 33, 52, 62, 62, 42, 97, 105, 9, 41, 41, 123, 72, 12, 97, 22, 69, 3, 32, 33, 54, 48, 42, 106, 66, 79, 92, 91, 72, 14, 21, 12, 7, 12, 11, 4, 16, 10, 75, 68, 6, 1, 0, 75, 64, 71, 77, 65, 66, 1, 69, 71, 105, 73, 75, 69, 83, 83, 69, 6, 67, 70, 67, 71, 86, 73, 64, 102, 119, 93, 100, 95, 126, 84, 78, 111, 0, 73, 73, 80, 83, 95, 89, 83, 102, 93, 91, 94, 103, 67, 8, 36, 24, 10, 4, 13, 7, 2, 64, 9, 16, 41, 26, 20, 11, 31, 15, 17, 3, 29, 6, 35, 22, 17, 11, 23, 9, 3, 1, 0, 69, 49, 28, 10, 3, 18, 3, 71, 66, 15, 40, 34, 20, 9, 30, 12, 10, 3, 1, 62, 72, 64, 6, 4, 66, 4, 8, 11, 10, 15, 21, 32, 69, 64, 74, 73, 61, 79, 99, 73, 2, 2, 72, 71, 70, 74, 0, 76, 94, 69, 68, 21, 70, 77, 69, 68, 65, 72, 71, 70, 89, 81, 72, 75, 71, 22, 100, 76, 23, 82, 68, 69, 70, 4, 66, 71, 2, 9, 72, 72, 104, 34, 32, 33, 17, 1, 1, 64, 73, 65, 75, 79, 74, 91, 88, 91, 97, 94, 105, 126, 92, 85, 85, 86, 98, 92, 88, 90, 91, 79, 74, 78, 75, 81, 100, 87, 85, 107, 93, 105, 105, 123, 118, 114, 114, 76, 73, 114, 83, 83, 90, 106, 96, 97, 100, 89, 95, 85, 83, 105, 111, 123, 85, 97, 103, 70, 9, 14, 16, 33, 22, 28, 6, 31, 26, 36, 30, 39, 34, 23, 43, 65, 73, 81, 89, 89, 115, 115, 114, 15, 44, 39, 42, 32, 34, 20, 19, 10, 9, 70, 9, 14, 16, 33, 22, 28, 6, 31, 26, 36, 30, 39, 34, 23, 43, 65, 73, 81, 89, 89, 115, 115, 114 }, { 27, 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 90, 64, 28, 38, 9, 34, 82, 74, 23, 69, 72, 34, 92, 91, 2, 74, 126, 126, 126, 18, 3, 68, 74, 23, 69, 77, 11, 35, 65, 3, 64, 1, 105, 109, 94, 111, 65, 72, 72, 75, 90, 82, 98, 1, 68, 69, 66, 31, 1, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 12, 64, 75, 29, 16, 4, 44, 42, 5, 16, 16, 23, 7, 7, 31, 108, 88, 89, 90, 24, 71, 16, 31, 66, 75, 1, 13, 65, 73, 72, 81, 23, 75, 13, 64, 75, 6, 69, 64, 7, 12, 15, 18, 7, 78, 67, 3, 65, 0, 71, 30, 66, 1, 30, 27, 37, 30, 26, 96, 9, 9, 75, 67, 77, 69, 30, 1, 4, 34, 53, 62, 62, 44, 98, 106, 10, 41, 42, 124, 72, 12, 97, 23, 69, 3, 34, 33, 54, 48, 43, 107, 65, 80, 94, 94, 70, 14, 21, 12, 6, 12, 11, 4, 16, 10, 76, 69, 6, 1, 0, 75, 64, 72, 77, 65, 66, 1, 70, 72, 106, 73, 75, 69, 84, 83, 71, 4, 68, 71, 69, 73, 89, 75, 65, 104, 121, 94, 102, 96, 126, 85, 79, 113, 64, 74, 74, 81, 85, 96, 91, 85, 103, 93, 91, 95, 104, 65, 10, 38, 25, 10, 4, 14, 8, 3, 0, 11, 17, 42, 27, 21, 12, 32, 16, 18, 4, 31, 7, 36, 23, 18, 11, 24, 10, 4, 2, 2, 68, 51, 29, 11, 3, 19, 4, 70, 65, 16, 41, 35, 21, 10, 31, 13, 11, 4, 2, 62, 70, 1, 8, 6, 65, 5, 10, 13, 12, 17, 23, 34, 68, 0, 73, 72, 62, 79, 100, 73, 3, 3, 71, 71, 70, 74, 0, 76, 95, 69, 68, 22, 70, 77, 69, 68, 65, 72, 71, 70, 89, 82, 72, 75, 70, 24, 100, 76, 24, 83, 68, 69, 70, 5, 66, 71, 3, 10, 71, 71, 106, 33, 31, 32, 15, 64, 65, 66, 76, 67, 77, 82, 76, 94, 91, 93, 101, 97, 108, 126, 95, 87, 86, 88, 100, 93, 89, 91, 92, 79, 76, 80, 77, 83, 102, 89, 86, 109, 95, 107, 107, 125, 120, 116, 115, 77, 74, 116, 84, 85, 91, 108, 97, 98, 101, 90, 95, 85, 82, 106, 112, 125, 86, 98, 104, 70, 10, 15, 17, 35, 23, 29, 6, 32, 27, 37, 31, 40, 35, 24, 42, 66, 75, 83, 91, 91, 117, 117, 115, 16, 44, 40, 43, 33, 35, 21, 20, 11, 10, 70, 10, 15, 17, 35, 23, 29, 6, 32, 27, 37, 31, 40, 35, 24, 42, 66, 75, 83, 91, 91, 117, 117, 115 }, { 26, 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 92, 65, 27, 39, 9, 35, 84, 74, 24, 70, 72, 35, 93, 92, 1, 76, 126, 126, 126, 20, 4, 68, 74, 24, 70, 77, 12, 35, 65, 3, 0, 3, 106, 110, 94, 111, 65, 71, 71, 76, 90, 82, 98, 1, 68, 69, 65, 32, 1, 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 11, 64, 74, 31, 17, 5, 45, 43, 6, 17, 18, 24, 8, 8, 34, 108, 89, 89, 91, 24, 71, 17, 34, 65, 75, 1, 15, 65, 73, 72, 81, 23, 75, 13, 0, 75, 7, 68, 64, 7, 12, 16, 18, 7, 79, 67, 3, 65, 0, 71, 30, 66, 1, 31, 27, 37, 30, 26, 97, 9, 9, 75, 67, 76, 69, 31, 2, 4, 36, 55, 62, 62, 46, 98, 107, 10, 42, 42, 125, 72, 13, 98, 24, 69, 3, 35, 33, 54, 48, 45, 108, 0, 81, 97, 97, 69, 14, 21, 11, 6, 11, 11, 3, 16, 9, 77, 70, 6, 1, 0, 75, 65, 72, 78, 66, 66, 1, 71, 72, 107, 74, 76, 69, 85, 84, 73, 2, 70, 73, 70, 75, 92, 78, 66, 106, 123, 96, 104, 98, 126, 86, 80, 114, 65, 75, 75, 83, 86, 98, 93, 86, 105, 93, 92, 96, 106, 64, 11, 39, 25, 10, 4, 15, 9, 4, 0, 13, 19, 43, 28, 21, 13, 34, 17, 20, 5, 34, 8, 36, 23, 18, 12, 25, 11, 4, 3, 3, 68, 52, 30, 11, 3, 20, 4, 70, 64, 17, 41, 36, 21, 10, 32, 14, 12, 4, 3, 62, 69, 2, 9, 7, 64, 7, 11, 15, 13, 19, 25, 37, 67, 1, 72, 71, 62, 78, 101, 72, 4, 4, 71, 71, 70, 74, 1, 77, 96, 69, 68, 23, 70, 77, 69, 68, 64, 73, 72, 70, 90, 83, 73, 75, 70, 25, 101, 77, 25, 83, 69, 69, 70, 5, 67, 72, 3, 10, 71, 71, 107, 32, 30, 31, 14, 67, 67, 69, 79, 70, 80, 85, 79, 97, 93, 95, 105, 101, 112, 126, 98, 89, 88, 90, 103, 95, 90, 92, 93, 79, 78, 83, 80, 85, 104, 91, 88, 111, 97, 109, 109, 126, 122, 117, 117, 78, 75, 118, 86, 86, 93, 110, 98, 99, 102, 91, 96, 85, 81, 108, 113, 126, 88, 99, 105, 70, 10, 15, 18, 36, 24, 29, 7, 33, 28, 38, 31, 41, 35, 24, 42, 68, 77, 85, 93, 93, 120, 119, 116, 16, 45, 40, 43, 34, 36, 21, 20, 11, 11, 70, 10, 15, 18, 36, 24, 29, 7, 33, 28, 38, 31, 41, 35, 24, 42, 68, 77, 85, 93, 93, 120, 119, 116 }, { 25, 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 93, 67, 26, 39, 10, 37, 85, 74, 26, 70, 73, 36, 94, 93, 0, 77, 126, 126, 126, 22, 6, 68, 74, 26, 70, 76, 13, 36, 65, 4, 1, 4, 108, 111, 94, 112, 65, 71, 71, 76, 91, 81, 98, 0, 68, 69, 64, 32, 1, 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 11, 0, 73, 33, 18, 6, 47, 45, 7, 18, 20, 25, 9, 8, 36, 109, 89, 89, 91, 25, 71, 18, 36, 65, 74, 2, 17, 65, 73, 72, 81, 24, 76, 14, 0, 75, 8, 68, 0, 7, 13, 17, 19, 7, 79, 67, 3, 65, 0, 71, 30, 66, 1, 31, 27, 38, 31, 27, 97, 10, 10, 76, 67, 76, 69, 32, 2, 5, 37, 56, 62, 62, 47, 99, 108, 11, 42, 42, 125, 73, 13, 98, 24, 70, 4, 36, 33, 54, 49, 46, 109, 1, 82, 100, 100, 67, 14, 21, 11, 5, 11, 10, 3, 15, 9, 77, 70, 6, 0, 0, 76, 65, 73, 79, 66, 66, 1, 71, 73, 109, 74, 77, 69, 86, 84, 76, 0, 72, 74, 72, 77, 95, 80, 68, 108, 125, 97, 106, 100, 126, 87, 82, 116, 67, 76, 76, 84, 88, 100, 95, 88, 106, 93, 93, 97, 107, 1, 13, 41, 26, 10, 4, 16, 9, 4, 1, 14, 20, 44, 29, 22, 13, 35, 18, 21, 6, 36, 8, 37, 24, 19, 12, 26, 11, 5, 4, 4, 68, 54, 31, 11, 3, 20, 5, 70, 64, 17, 42, 36, 22, 10, 33, 14, 12, 5, 3, 62, 67, 4, 11, 9, 1, 8, 13, 16, 15, 21, 27, 39, 67, 2, 72, 71, 62, 78, 102, 72, 4, 5, 71, 71, 70, 74, 1, 77, 97, 69, 68, 23, 70, 77, 69, 68, 64, 73, 72, 70, 91, 84, 73, 75, 70, 26, 102, 77, 26, 84, 69, 69, 70, 6, 67, 72, 4, 11, 71, 71, 109, 31, 29, 30, 12, 69, 70, 71, 82, 72, 83, 88, 82, 100, 96, 97, 108, 104, 116, 126, 100, 92, 90, 92, 105, 97, 91, 93, 94, 79, 80, 85, 82, 87, 106, 92, 89, 113, 99, 112, 111, 126, 124, 119, 118, 79, 77, 120, 87, 88, 94, 112, 100, 101, 103, 92, 97, 86, 80, 109, 115, 126, 89, 101, 106, 69, 11, 16, 18, 37, 25, 30, 7, 34, 29, 39, 32, 42, 36, 25, 41, 70, 79, 87, 95, 95, 122, 121, 117, 17, 45, 41, 44, 34, 37, 22, 21, 12, 11, 69, 11, 16, 18, 37, 25, 30, 7, 34, 29, 39, 32, 42, 36, 25, 41, 70, 79, 87, 95, 95, 122, 121, 117 }, { 23, 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 95, 68, 25, 40, 10, 39, 87, 74, 27, 70, 73, 37, 95, 95, 65, 79, 126, 126, 126, 24, 7, 68, 74, 27, 70, 76, 14, 36, 66, 5, 2, 5, 109, 113, 95, 112, 65, 71, 70, 76, 91, 81, 98, 0, 68, 69, 64, 33, 1, 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 10, 0, 72, 35, 19, 7, 48, 46, 7, 19, 22, 26, 10, 9, 38, 110, 89, 89, 91, 25, 72, 19, 38, 65, 73, 3, 19, 65, 73, 72, 81, 24, 76, 14, 1, 75, 8, 68, 0, 7, 13, 18, 19, 7, 79, 68, 3, 65, 0, 71, 30, 66, 1, 32, 27, 38, 31, 27, 98, 10, 11, 76, 67, 76, 69, 33, 2, 5, 38, 57, 62, 62, 49, 99, 109, 11, 42, 42, 126, 73, 14, 99, 25, 70, 4, 37, 33, 54, 49, 47, 110, 3, 83, 103, 103, 66, 13, 21, 10, 4, 10, 10, 2, 15, 8, 78, 71, 5, 0, 0, 76, 66, 74, 80, 67, 67, 0, 72, 73, 110, 75, 78, 69, 88, 85, 78, 65, 74, 76, 74, 79, 99, 83, 69, 110, 126, 99, 108, 102, 126, 89, 83, 118, 68, 77, 77, 85, 89, 102, 97, 90, 108, 93, 94, 98, 109, 2, 14, 42, 26, 10, 4, 17, 10, 5, 2, 16, 21, 45, 29, 23, 14, 36, 19, 23, 7, 38, 9, 37, 24, 19, 13, 27, 12, 5, 4, 5, 68, 55, 32, 11, 3, 21, 5, 70, 0, 18, 42, 37, 22, 10, 34, 15, 13, 5, 4, 62, 66, 5, 12, 11, 2, 10, 14, 18, 16, 22, 29, 41, 66, 3, 71, 70, 62, 78, 103, 72, 5, 5, 71, 71, 70, 75, 2, 77, 98, 69, 68, 24, 70, 77, 69, 68, 64, 74, 72, 70, 92, 85, 73, 75, 70, 27, 103, 77, 26, 85, 69, 69, 70, 6, 67, 73, 4, 12, 71, 71, 110, 30, 28, 29, 10, 71, 72, 74, 85, 75, 86, 92, 85, 104, 99, 99, 112, 108, 120, 126, 103, 94, 92, 94, 108, 99, 93, 94, 95, 79, 83, 87, 84, 89, 108, 94, 91, 115, 101, 114, 113, 126, 126, 121, 119, 80, 78, 123, 89, 90, 96, 114, 101, 102, 104, 93, 98, 86, 79, 110, 116, 126, 90, 102, 107, 69, 11, 16, 19, 38, 25, 31, 7, 35, 30, 40, 32, 43, 36, 26, 41, 72, 81, 89, 97, 97, 124, 123, 119, 17, 46, 41, 45, 35, 37, 22, 21, 12, 12, 69, 11, 16, 19, 38, 25, 31, 7, 35, 30, 40, 32, 43, 36, 26, 41, 72, 81, 89, 97, 97, 124, 123, 119 }, { 22, 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 70, 24, 41, 10, 40, 89, 73, 29, 71, 73, 38, 96, 96, 66, 81, 126, 126, 126, 26, 8, 68, 73, 29, 71, 76, 15, 36, 66, 5, 3, 7, 110, 114, 95, 112, 65, 70, 69, 77, 91, 81, 98, 0, 67, 69, 0, 33, 1, 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 10, 1, 70, 38, 20, 8, 50, 48, 8, 20, 24, 27, 11, 10, 41, 110, 90, 89, 92, 25, 72, 20, 41, 64, 73, 3, 21, 65, 73, 72, 80, 24, 76, 15, 2, 74, 9, 67, 0, 8, 14, 19, 20, 7, 80, 68, 4, 65, 0, 71, 31, 66, 1, 32, 28, 38, 31, 27, 98, 11, 11, 77, 67, 75, 69, 34, 3, 6, 40, 59, 62, 62, 51, 100, 110, 12, 43, 42, 126, 73, 14, 99, 26, 70, 4, 39, 33, 54, 49, 49, 111, 4, 84, 105, 106, 65, 13, 21, 10, 4, 10, 10, 2, 15, 8, 79, 72, 5, 0, 0, 76, 66, 74, 81, 67, 67, 0, 73, 74, 111, 76, 78, 69, 89, 85, 80, 67, 75, 77, 75, 81, 102, 85, 70, 112, 126, 100, 110, 104, 126, 90, 84, 119, 69, 78, 78, 87, 91, 104, 99, 91, 109, 93, 95, 99, 111, 4, 16, 43, 27, 10, 4, 18, 11, 6, 2, 18, 23, 46, 30, 23, 15, 38, 20, 24, 8, 41, 10, 38, 25, 20, 13, 28, 13, 6, 5, 6, 67, 56, 33, 11, 3, 22, 6, 69, 1, 19, 42, 38, 22, 10, 35, 16, 14, 6, 5, 62, 65, 7, 14, 12, 3, 11, 16, 20, 18, 24, 31, 44, 65, 4, 70, 69, 62, 77, 104, 71, 6, 6, 71, 71, 70, 75, 2, 78, 99, 69, 68, 25, 70, 77, 69, 68, 0, 74, 73, 70, 93, 86, 74, 75, 70, 28, 103, 78, 27, 85, 70, 69, 70, 6, 68, 73, 4, 12, 71, 71, 112, 29, 27, 28, 9, 74, 75, 77, 88, 77, 89, 95, 88, 107, 101, 101, 116, 111, 124, 126, 106, 96, 93, 96, 110, 101, 94, 95, 96, 79, 85, 90, 87, 91, 110, 96, 92, 117, 103, 116, 115, 126, 126, 122, 121, 81, 79, 125, 90, 91, 97, 116, 102, 103, 105, 94, 99, 86, 78, 112, 117, 126, 92, 103, 108, 69, 12, 17, 20, 39, 26, 31, 8, 36, 31, 41, 33, 44, 37, 26, 40, 74, 83, 91, 99, 99, 126, 125, 120, 18, 46, 42, 45, 36, 38, 23, 22, 12, 13, 69, 12, 17, 20, 39, 26, 31, 8, 36, 31, 41, 33, 44, 37, 26, 40, 74, 83, 91, 99, 99, 126, 125, 120 }, { 21, 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 71, 23, 41, 10, 42, 90, 73, 30, 71, 74, 39, 97, 97, 67, 83, 126, 126, 126, 28, 10, 68, 73, 30, 71, 75, 16, 37, 66, 6, 4, 8, 111, 115, 95, 113, 65, 70, 68, 77, 92, 81, 98, 0, 67, 69, 1, 34, 1, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 9, 1, 69, 40, 21, 9, 51, 49, 9, 21, 26, 28, 12, 11, 43, 111, 90, 89, 92, 26, 72, 21, 43, 64, 72, 4, 23, 65, 73, 72, 80, 25, 76, 15, 3, 74, 10, 67, 0, 8, 14, 20, 20, 7, 80, 68, 4, 65, 0, 71, 31, 66, 1, 33, 28, 39, 31, 28, 99, 11, 12, 77, 67, 75, 69, 35, 3, 6, 41, 60, 62, 62, 53, 100, 111, 12, 43, 42, 126, 73, 15, 100, 26, 70, 4, 40, 33, 54, 49, 50, 112, 6, 85, 108, 109, 0, 13, 21, 9, 3, 9, 9, 1, 15, 7, 80, 73, 5, 0, 0, 77, 67, 75, 82, 68, 67, 0, 74, 74, 112, 76, 79, 69, 90, 86, 82, 69, 77, 79, 77, 83, 105, 88, 71, 114, 126, 102, 112, 106, 126, 91, 85, 121, 70, 79, 79, 88, 92, 106, 101, 93, 111, 93, 96, 100, 112, 5, 17, 45, 27, 10, 4, 19, 12, 7, 3, 20, 24, 47, 31, 24, 15, 39, 21, 26, 9, 43, 10, 38, 25, 20, 14, 29, 13, 6, 6, 7, 67, 58, 34, 11, 3, 23, 6, 69, 2, 20, 43, 39, 23, 10, 36, 16, 15, 6, 5, 62, 0, 8, 15, 14, 4, 13, 17, 21, 19, 26, 33, 46, 65, 5, 70, 68, 62, 77, 105, 71, 7, 7, 71, 71, 70, 75, 3, 78, 100, 69, 68, 25, 70, 77, 69, 68, 0, 75, 73, 70, 94, 87, 74, 75, 70, 29, 104, 78, 28, 86, 70, 69, 70, 7, 68, 74, 5, 13, 71, 71, 113, 28, 26, 27, 7, 76, 77, 79, 91, 80, 92, 98, 91, 110, 104, 103, 120, 115, 126, 126, 109, 98, 95, 98, 113, 103, 95, 96, 97, 79, 87, 92, 89, 93, 112, 98, 94, 119, 105, 118, 117, 126, 126, 124, 122, 82, 80, 126, 92, 93, 99, 118, 104, 105, 106, 95, 100, 86, 77, 113, 119, 126, 93, 104, 109, 69, 12, 17, 20, 40, 27, 32, 8, 37, 32, 42, 33, 45, 37, 27, 40, 76, 85, 93, 101, 101, 126, 126, 121, 18, 47, 42, 46, 36, 39, 23, 22, 13, 13, 69, 12, 17, 20, 40, 27, 32, 8, 37, 32, 42, 33, 45, 37, 27, 40, 76, 85, 93, 101, 101, 126, 126, 121 }, { 20, 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 101, 73, 22, 42, 10, 44, 92, 73, 32, 71, 74, 40, 98, 98, 68, 85, 126, 126, 126, 30, 11, 68, 73, 32, 71, 75, 17, 37, 66, 7, 5, 9, 112, 116, 95, 113, 65, 70, 67, 77, 92, 81, 98, 0, 67, 69, 2, 34, 1, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 9, 2, 68, 42, 22, 10, 53, 51, 10, 22, 28, 29, 13, 12, 45, 112, 90, 89, 92, 26, 72, 22, 45, 64, 71, 5, 25, 65, 73, 72, 80, 25, 76, 16, 4, 74, 11, 67, 0, 8, 15, 21, 21, 7, 80, 68, 4, 65, 0, 71, 31, 66, 1, 33, 28, 39, 31, 28, 99, 12, 13, 78, 67, 75, 69, 36, 3, 7, 42, 61, 62, 62, 55, 101, 112, 13, 43, 42, 126, 73, 15, 100, 27, 70, 4, 41, 33, 54, 49, 51, 113, 7, 86, 111, 112, 1, 13, 21, 9, 2, 9, 9, 1, 15, 7, 81, 74, 5, 0, 0, 77, 67, 76, 83, 68, 67, 0, 75, 75, 113, 77, 80, 69, 91, 86, 84, 71, 79, 80, 79, 85, 108, 90, 72, 116, 126, 103, 114, 108, 126, 92, 86, 123, 71, 80, 80, 89, 94, 108, 103, 95, 112, 93, 97, 101, 114, 7, 19, 46, 28, 10, 4, 20, 13, 8, 4, 22, 25, 48, 32, 25, 16, 40, 22, 27, 10, 45, 11, 39, 26, 21, 14, 30, 14, 7, 7, 8, 67, 59, 35, 11, 3, 24, 7, 69, 3, 21, 43, 40, 23, 10, 37, 17, 16, 7, 6, 62, 1, 10, 17, 16, 5, 14, 19, 23, 21, 28, 35, 48, 64, 6, 69, 67, 62, 77, 106, 71, 8, 8, 71, 71, 70, 75, 3, 78, 101, 69, 68, 26, 70, 77, 69, 68, 0, 75, 73, 70, 95, 88, 74, 75, 70, 30, 105, 78, 29, 87, 70, 69, 70, 7, 68, 74, 5, 14, 71, 71, 115, 27, 25, 26, 5, 78, 80, 82, 94, 82, 95, 101, 94, 113, 107, 105, 124, 118, 126, 126, 112, 100, 97, 100, 115, 105, 96, 97, 98, 79, 89, 94, 91, 95, 114, 100, 95, 121, 107, 120, 119, 126, 126, 126, 123, 83, 81, 126, 93, 95, 100, 120, 105, 106, 107, 96, 101, 86, 76, 114, 120, 126, 94, 105, 110, 69, 13, 18, 21, 41, 28, 33, 8, 38, 33, 43, 34, 46, 38, 28, 39, 78, 87, 95, 103, 103, 126, 126, 122, 19, 47, 43, 47, 37, 40, 24, 23, 13, 14, 69, 13, 18, 21, 41, 28, 33, 8, 38, 33, 43, 34, 46, 38, 28, 39, 78, 87, 95, 103, 103, 126, 126, 122 }, { 18, 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 103, 75, 21, 42, 10, 45, 94, 73, 33, 72, 75, 41, 99, 100, 70, 87, 126, 126, 126, 32, 12, 68, 73, 33, 72, 75, 17, 37, 67, 7, 5, 10, 114, 118, 96, 114, 66, 70, 67, 78, 93, 81, 98, 64, 67, 69, 2, 34, 0, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 8, 2, 67, 44, 23, 10, 54, 52, 10, 23, 29, 29, 14, 12, 47, 113, 91, 90, 93, 26, 73, 22, 47, 64, 71, 5, 26, 65, 73, 72, 80, 25, 77, 16, 4, 74, 11, 67, 0, 8, 15, 22, 21, 7, 81, 69, 4, 65, 64, 71, 31, 66, 1, 33, 28, 39, 31, 28, 100, 12, 13, 79, 67, 75, 70, 36, 3, 7, 43, 62, 62, 62, 56, 102, 114, 13, 43, 42, 126, 74, 15, 101, 27, 71, 4, 42, 33, 53, 49, 52, 114, 8, 88, 114, 116, 2, 12, 21, 8, 1, 8, 8, 0, 14, 6, 82, 75, 4, 64, 64, 78, 68, 77, 84, 69, 68, 64, 76, 76, 115, 78, 81, 69, 93, 87, 87, 74, 81, 82, 81, 88, 112, 93, 74, 118, 126, 105, 117, 110, 126, 94, 88, 125, 73, 81, 81, 91, 96, 110, 105, 97, 114, 93, 98, 102, 116, 8, 20, 47, 28, 10, 4, 20, 13, 8, 4, 23, 26, 48, 32, 25, 16, 41, 23, 28, 10, 47, 11, 39, 26, 21, 14, 30, 14, 7, 7, 9, 67, 60, 36, 11, 2, 24, 7, 69, 3, 21, 43, 40, 23, 10, 38, 17, 16, 7, 6, 62, 2, 11, 18, 17, 6, 15, 20, 24, 22, 29, 36, 50, 64, 6, 69, 67, 62, 77, 108, 71, 8, 8, 71, 71, 70, 76, 3, 79, 102, 70, 68, 26, 71, 77, 70, 68, 0, 76, 74, 71, 96, 89, 75, 76, 70, 31, 106, 79, 29, 88, 71, 69, 71, 7, 69, 75, 5, 14, 71, 71, 117, 25, 24, 24, 3, 81, 83, 85, 97, 85, 98, 105, 97, 117, 110, 107, 126, 122, 126, 126, 115, 103, 99, 103, 118, 107, 98, 99, 99, 79, 92, 97, 94, 97, 117, 102, 97, 124, 109, 123, 121, 126, 126, 126, 125, 85, 83, 126, 95, 97, 102, 122, 107, 108, 108, 97, 102, 87, 75, 116, 122, 126, 96, 107, 112, 69, 13, 18, 21, 42, 28, 33, 8, 39, 33, 44, 34, 46, 38, 28, 38, 80, 89, 98, 106, 105, 126, 126, 124, 19, 47, 43, 47, 37, 40, 24, 23, 13, 14, 69, 13, 18, 21, 42, 28, 33, 8, 39, 33, 44, 34, 46, 38, 28, 38, 80, 89, 98, 106, 105, 126, 126, 124 }, { 17, 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 104, 76, 21, 43, 11, 47, 95, 72, 35, 72, 75, 43, 99, 101, 71, 88, 126, 126, 126, 34, 14, 67, 72, 35, 72, 74, 18, 38, 67, 8, 6, 12, 115, 119, 96, 114, 66, 69, 66, 78, 93, 80, 97, 64, 66, 68, 3, 35, 0, 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 8, 3, 65, 47, 25, 11, 56, 54, 11, 25, 31, 30, 16, 13, 50, 113, 91, 90, 93, 27, 73, 23, 50, 0, 70, 6, 28, 65, 72, 72, 79, 26, 77, 17, 5, 73, 12, 66, 1, 9, 16, 23, 22, 8, 81, 69, 5, 64, 64, 70, 32, 65, 1, 34, 29, 40, 32, 29, 100, 13, 14, 79, 67, 74, 70, 37, 4, 8, 45, 62, 62, 62, 58, 102, 115, 14, 44, 43, 126, 74, 16, 101, 28, 71, 5, 44, 33, 53, 50, 54, 115, 10, 89, 116, 119, 4, 12, 21, 8, 1, 8, 8, 0, 14, 6, 82, 75, 4, 64, 64, 78, 68, 77, 84, 69, 68, 64, 76, 76, 116, 78, 81, 69, 94, 87, 89, 76, 82, 83, 82, 90, 115, 95, 75, 119, 126, 106, 119, 111, 126, 95, 89, 126, 74, 81, 81, 92, 97, 111, 106, 98, 115, 93, 98, 102, 117, 10, 22, 49, 29, 10, 4, 21, 14, 9, 5, 25, 28, 49, 33, 26, 17, 43, 24, 30, 11, 50, 12, 40, 27, 22, 15, 31, 15, 8, 8, 11, 66, 62, 37, 12, 2, 25, 8, 68, 4, 22, 44, 41, 24, 11, 39, 18, 17, 8, 7, 62, 4, 13, 20, 19, 8, 17, 22, 26, 24, 31, 38, 53, 0, 7, 68, 66, 62, 76, 109, 70, 9, 9, 70, 71, 69, 76, 4, 79, 102, 70, 68, 27, 71, 77, 70, 68, 1, 76, 74, 71, 96, 89, 75, 76, 69, 33, 106, 79, 30, 88, 71, 69, 71, 8, 69, 75, 6, 15, 70, 70, 118, 24, 23, 23, 2, 83, 85, 87, 100, 87, 100, 108, 99, 120, 112, 109, 126, 125, 126, 126, 117, 105, 100, 105, 120, 108, 99, 100, 99, 79, 94, 99, 96, 99, 119, 103, 98, 126, 110, 125, 122, 126, 126, 126, 126, 86, 84, 126, 96, 98, 103, 123, 108, 109, 109, 97, 102, 87, 74, 117, 123, 126, 97, 108, 113, 68, 14, 19, 22, 44, 29, 34, 9, 41, 34, 45, 35, 47, 39, 29, 38, 81, 90, 100, 108, 106, 126, 126, 125, 20, 48, 44, 48, 38, 41, 25, 24, 14, 15, 68, 14, 19, 22, 44, 29, 34, 9, 41, 34, 45, 35, 47, 39, 29, 38, 81, 90, 100, 108, 106, 126, 126, 125 }, { 16, 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 106, 78, 20, 44, 11, 49, 97, 72, 36, 72, 75, 44, 100, 102, 72, 90, 126, 126, 126, 36, 15, 67, 72, 36, 72, 74, 19, 38, 67, 9, 7, 13, 116, 120, 96, 114, 66, 69, 65, 78, 93, 80, 97, 64, 66, 68, 4, 35, 0, 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 8, 4, 64, 49, 26, 12, 58, 56, 12, 26, 33, 31, 17, 14, 52, 114, 91, 90, 93, 27, 73, 24, 52, 0, 69, 7, 30, 65, 72, 72, 79, 26, 77, 17, 6, 73, 13, 66, 1, 9, 16, 24, 23, 8, 81, 69, 5, 64, 64, 70, 32, 65, 1, 34, 29, 40, 32, 29, 101, 14, 15, 80, 67, 74, 70, 38, 4, 8, 46, 62, 62, 62, 60, 103, 116, 14, 44, 43, 126, 74, 16, 102, 29, 71, 5, 45, 33, 53, 50, 55, 116, 11, 90, 119, 122, 5, 12, 21, 8, 0, 7, 8, 0, 14, 5, 83, 76, 4, 64, 64, 78, 69, 78, 85, 69, 68, 64, 77, 77, 117, 79, 82, 69, 95, 88, 91, 78, 84, 85, 84, 92, 118, 97, 76, 121, 126, 108, 121, 113, 126, 96, 90, 126, 75, 82, 82, 93, 99, 113, 108, 100, 117, 93, 99, 103, 119, 11, 23, 50, 30, 10, 4, 22, 15, 10, 6, 27, 29, 50, 34, 27, 18, 44, 25, 31, 12, 52, 13, 40, 27, 22, 15, 32, 16, 9, 9, 12, 66, 62, 38, 12, 2, 26, 9, 68, 5, 23, 44, 42, 24, 11, 40, 19, 18, 9, 8, 62, 5, 15, 22, 21, 9, 18, 24, 28, 25, 33, 40, 55, 1, 8, 67, 65, 62, 76, 110, 70, 10, 10, 70, 71, 69, 76, 4, 79, 103, 70, 68, 28, 71, 77, 70, 68, 1, 76, 74, 71, 97, 90, 75, 76, 69, 34, 107, 79, 31, 89, 71, 69, 71, 8, 69, 75, 6, 16, 70, 70, 120, 23, 22, 22, 0, 85, 88, 90, 103, 89, 103, 111, 102, 123, 115, 111, 126, 126, 126, 126, 120, 107, 102, 107, 122, 110, 100, 101, 100, 79, 96, 101, 98, 101, 121, 105, 100, 126, 112, 126, 124, 126, 126, 126, 126, 87, 85, 126, 98, 100, 105, 125, 109, 110, 110, 98, 103, 87, 73, 118, 124, 126, 98, 109, 114, 68, 14, 20, 23, 45, 30, 35, 9, 42, 35, 46, 35, 48, 40, 30, 37, 83, 92, 102, 110, 108, 126, 126, 126, 21, 48, 44, 49, 39, 42, 25, 25, 14, 16, 68, 14, 20, 23, 45, 30, 35, 9, 42, 35, 46, 35, 48, 40, 30, 37, 83, 92, 102, 110, 108, 126, 126, 126 }, { 15, 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 108, 79, 19, 44, 11, 51, 98, 72, 38, 72, 76, 45, 101, 103, 73, 92, 126, 126, 126, 38, 17, 67, 72, 38, 72, 73, 20, 39, 67, 10, 8, 14, 117, 121, 96, 115, 66, 69, 64, 78, 94, 80, 97, 64, 66, 68, 5, 36, 0, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 7, 4, 0, 51, 27, 13, 59, 57, 13, 27, 35, 32, 18, 15, 54, 115, 91, 90, 93, 28, 73, 25, 54, 0, 68, 8, 32, 65, 72, 72, 79, 27, 77, 18, 7, 73, 14, 66, 1, 9, 17, 25, 23, 8, 81, 69, 5, 64, 64, 70, 32, 65, 1, 35, 29, 41, 32, 30, 101, 14, 16, 80, 67, 74, 70, 39, 4, 9, 47, 62, 62, 62, 62, 103, 117, 15, 44, 43, 126, 74, 17, 102, 29, 71, 5, 46, 33, 53, 50, 56, 117, 13, 91, 122, 125, 7, 12, 21, 7, 64, 7, 7, 64, 14, 5, 84, 77, 4, 64, 64, 79, 69, 79, 86, 70, 68, 64, 78, 77, 118, 79, 83, 69, 96, 88, 93, 80, 86, 86, 86, 94, 121, 100, 77, 123, 126, 109, 123, 115, 126, 97, 91, 126, 76, 83, 83, 94, 100, 115, 110, 102, 118, 93, 100, 104, 120, 13, 25, 52, 30, 10, 4, 23, 16, 11, 7, 29, 30, 51, 35, 28, 18, 45, 26, 33, 13, 54, 13, 41, 28, 23, 16, 33, 16, 9, 10, 13, 66, 62, 39, 12, 2, 27, 9, 68, 6, 24, 45, 43, 25, 11, 41, 19, 19, 9, 8, 62, 7, 16, 23, 23, 10, 20, 25, 29, 27, 35, 42, 57, 1, 9, 67, 64, 62, 76, 111, 70, 11, 11, 70, 71, 69, 76, 5, 79, 104, 70, 68, 28, 71, 77, 70, 68, 1, 77, 74, 71, 98, 91, 75, 76, 69, 35, 108, 79, 32, 90, 71, 69, 71, 9, 69, 76, 7, 17, 70, 70, 121, 22, 21, 21, 65, 87, 90, 92, 106, 92, 106, 114, 105, 126, 118, 113, 126, 126, 126, 126, 123, 109, 104, 109, 125, 112, 101, 102, 101, 79, 98, 103, 100, 103, 123, 107, 101, 126, 114, 126, 126, 126, 126, 126, 126, 88, 86, 126, 99, 102, 106, 126, 111, 112, 111, 99, 104, 87, 72, 119, 126, 126, 99, 110, 115, 68, 15, 20, 23, 46, 31, 36, 9, 43, 36, 47, 36, 49, 40, 31, 37, 85, 94, 104, 112, 110, 126, 126, 126, 21, 49, 45, 50, 39, 43, 26, 25, 15, 16, 68, 15, 20, 23, 46, 31, 36, 9, 43, 36, 47, 36, 49, 40, 31, 37, 85, 94, 104, 112, 110, 126, 126, 126 }, }, { { 62, 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 30, 61, 62, 54, 14, 118, 6, 78, 65, 1, 14, 73, 13, 64, 20, 62, 67, 90, 104, 126, 104, 67, 78, 65, 1, 86, 95, 2, 18, 69, 81, 96, 8, 67, 86, 88, 5, 76, 94, 9, 69, 81, 88, 67, 74, 74, 80, 72, 5, 22, 0, 0, 0, 83, 86, 97, 72, 22, 1, 52, 8, 69, 126, 102, 82, 74, 107, 126, 126, 126, 95, 126, 114, 126, 123, 115, 122, 115, 0, 68, 84, 104, 70, 93, 90, 126, 74, 97, 91, 126, 7, 82, 76, 125, 93, 87, 77, 71, 0, 68, 84, 1, 65, 2, 7, 66, 64, 2, 78, 13, 11, 28, 19, 25, 18, 17, 19, 46, 12, 13, 44, 30, 1, 108, 100, 101, 91, 94, 88, 84, 86, 83, 87, 94, 70, 72, 74, 4, 102, 100, 95, 75, 72, 75, 71, 17, 69, 1, 65, 26, 72, 6, 9, 1, 72, 62, 54, 38, 45, 54, 44, 26, 45, 34, 30, 33, 18, 5, 1, 2, 25, 18, 24, 21, 19, 18, 22, 14, 29, 21, 8, 12, 17, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 46, 62, 60, 41, 62, 62, 62, 62, 60, 58, 62, 47, 41, 15, 26, 3, 68, 97, 71, 21, 13, 9, 1, 5, 0, 72, 74, 91, 67, 36, 24, 19, 17, 64, 68, 78, 77, 86, 92, 8, 3, 1, 65, 73, 76, 80, 88, 110, 97, 84, 79, 73, 74, 86, 96, 97, 117, 78, 30, 15, 10, 1, 71, 79, 86, 90, 97, 62, 93, 84, 79, 66, 71, 1, 3, 4, 75, 1, 5, 66, 79, 71, 68, 19, 1, 27, 23, 36, 34, 19, 27, 31, 21, 15, 1, 17, 64, 104, 97, 96, 88, 85, 85, 85, 88, 66, 77, 76, 76, 5, 76, 83, 99, 95, 95, 76, 74, 70, 75, 68, 65, 73, 1, 1, 68, 75, 8, 64, 70, 57, 44, 47, 49, 50, 52, 48, 47, 40, 40, 43, 37, 19, 23, 16, 46, 42, 41, 36, 34, 28, 13, 6, 0, 77, 82, 94, 69, 109, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 50, 28, 5, 62, 62, 33, 62, 62, 62, 60, 62, 58, 52, 58, 51, 52, 34, 37, 24, 66, 42, 32, 13, 120, 112, 114, 85, 92, 89, 71, 81, 80, 68, 70, 7, 68, 13, 74, 62, 62, 62, 62, 60, 57, 29, 9, 82, 75, 40, 29, 20, 9, 8, 2, 64, 68, 92, 106, 97, 90, 90, 88, 73, 79, 86, 73, 70, 69, 66, 64, 5, 4, 62, 62, 62, 62, 60, 54, 43, 27, 67 }, { 62, 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 29, 60, 62, 54, 14, 115, 6, 77, 64, 1, 14, 72, 12, 65, 20, 62, 68, 91, 104, 124, 102, 67, 77, 64, 1, 85, 93, 3, 18, 68, 80, 95, 8, 67, 85, 88, 5, 75, 93, 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 52, 8, 69, 125, 101, 82, 73, 105, 125, 125, 125, 93, 125, 112, 125, 121, 114, 121, 114, 1, 67, 83, 103, 69, 92, 89, 125, 73, 96, 90, 125, 8, 81, 75, 123, 92, 86, 76, 70, 1, 67, 83, 2, 64, 2, 7, 65, 64, 2, 77, 13, 11, 28, 19, 25, 18, 17, 19, 45, 12, 13, 43, 29, 1, 107, 99, 100, 90, 93, 87, 83, 85, 82, 86, 92, 70, 72, 73, 3, 101, 99, 95, 74, 72, 74, 70, 17, 68, 1, 65, 25, 71, 6, 8, 1, 72, 62, 54, 38, 45, 54, 44, 26, 45, 34, 29, 33, 18, 5, 1, 2, 25, 18, 24, 21, 19, 17, 22, 14, 28, 20, 8, 11, 16, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 44, 62, 59, 40, 62, 62, 62, 62, 58, 56, 61, 45, 39, 15, 25, 2, 68, 97, 70, 22, 14, 10, 2, 5, 0, 71, 73, 90, 66, 37, 25, 20, 17, 0, 67, 77, 76, 85, 91, 9, 4, 2, 64, 72, 75, 79, 87, 108, 96, 82, 78, 72, 73, 85, 95, 96, 115, 77, 31, 16, 11, 2, 70, 78, 85, 89, 96, 62, 92, 83, 78, 66, 70, 1, 4, 5, 74, 2, 6, 65, 78, 71, 68, 19, 2, 27, 23, 35, 34, 19, 26, 30, 21, 15, 1, 16, 64, 103, 96, 95, 87, 84, 84, 84, 87, 66, 76, 75, 75, 5, 75, 82, 98, 94, 95, 76, 73, 70, 74, 68, 65, 72, 1, 1, 67, 74, 8, 64, 70, 57, 44, 47, 49, 49, 52, 48, 47, 40, 40, 43, 37, 19, 22, 15, 45, 41, 40, 35, 33, 27, 13, 6, 0, 76, 81, 93, 69, 108, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 59, 48, 27, 5, 62, 62, 32, 62, 62, 62, 58, 62, 56, 50, 56, 49, 50, 33, 35, 23, 67, 41, 31, 12, 118, 110, 112, 84, 91, 88, 69, 80, 79, 68, 69, 9, 66, 15, 73, 62, 62, 62, 62, 58, 55, 27, 7, 83, 74, 41, 29, 20, 9, 9, 2, 64, 68, 91, 105, 96, 89, 89, 86, 72, 78, 85, 72, 69, 68, 65, 0, 6, 4, 62, 62, 62, 62, 59, 53, 41, 26, 67 }, { 62, 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 28, 59, 61, 54, 14, 113, 6, 76, 0, 1, 13, 72, 11, 66, 19, 60, 70, 92, 105, 121, 101, 67, 76, 0, 1, 85, 92, 3, 17, 68, 80, 94, 8, 67, 85, 88, 5, 75, 92, 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 52, 8, 69, 124, 100, 82, 73, 104, 123, 123, 124, 92, 123, 111, 123, 120, 113, 120, 113, 2, 67, 82, 102, 69, 92, 88, 123, 73, 96, 90, 124, 8, 81, 75, 122, 92, 85, 76, 70, 1, 67, 82, 2, 64, 1, 7, 65, 64, 2, 77, 13, 11, 27, 19, 24, 18, 17, 19, 43, 12, 13, 41, 28, 0, 106, 98, 99, 89, 92, 86, 82, 84, 82, 85, 91, 70, 72, 73, 2, 101, 98, 95, 74, 72, 73, 70, 16, 67, 1, 65, 24, 70, 5, 7, 1, 73, 60, 53, 37, 44, 53, 43, 25, 44, 34, 28, 32, 18, 5, 1, 2, 24, 17, 23, 20, 18, 16, 21, 13, 26, 19, 7, 10, 15, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 58, 41, 62, 57, 38, 62, 62, 62, 62, 56, 54, 58, 43, 37, 14, 23, 1, 69, 97, 70, 22, 14, 10, 2, 5, 0, 71, 73, 89, 66, 37, 25, 20, 17, 1, 67, 76, 76, 84, 90, 10, 5, 2, 64, 71, 75, 79, 86, 107, 95, 81, 77, 72, 73, 84, 94, 95, 114, 77, 31, 16, 11, 2, 69, 77, 84, 88, 95, 62, 92, 83, 78, 66, 70, 1, 4, 5, 74, 2, 6, 64, 78, 71, 68, 18, 2, 26, 22, 34, 33, 19, 25, 29, 21, 15, 0, 15, 65, 102, 95, 94, 87, 84, 84, 83, 86, 66, 76, 75, 75, 4, 75, 82, 98, 93, 95, 76, 73, 70, 73, 68, 65, 71, 1, 1, 67, 73, 7, 64, 71, 56, 44, 47, 48, 48, 51, 47, 46, 39, 39, 42, 36, 18, 21, 14, 43, 40, 38, 33, 32, 26, 12, 5, 0, 76, 81, 93, 70, 107, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 57, 46, 26, 4, 62, 60, 31, 62, 62, 62, 56, 60, 54, 48, 54, 47, 48, 31, 33, 21, 68, 39, 29, 10, 117, 109, 111, 83, 90, 87, 67, 79, 78, 68, 68, 10, 65, 16, 72, 62, 62, 62, 62, 55, 52, 24, 5, 84, 74, 41, 29, 20, 9, 9, 2, 64, 68, 90, 104, 95, 88, 88, 85, 71, 77, 84, 71, 68, 67, 65, 1, 6, 4, 62, 62, 62, 61, 57, 51, 39, 24, 68 }, { 62, 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 26, 57, 60, 54, 14, 111, 6, 75, 1, 1, 12, 72, 10, 67, 19, 58, 71, 93, 105, 118, 100, 67, 75, 1, 1, 84, 91, 4, 17, 68, 79, 93, 7, 68, 85, 88, 5, 75, 92, 9, 69, 80, 88, 65, 73, 73, 79, 70, 5, 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 52, 8, 69, 123, 99, 82, 72, 103, 121, 121, 122, 91, 121, 110, 121, 119, 112, 119, 112, 3, 67, 81, 101, 69, 91, 88, 121, 73, 95, 89, 123, 8, 81, 74, 120, 91, 84, 76, 70, 1, 67, 81, 3, 0, 1, 7, 65, 64, 2, 77, 13, 10, 27, 19, 23, 18, 17, 19, 41, 12, 12, 39, 27, 64, 105, 97, 98, 88, 91, 86, 81, 84, 81, 84, 90, 70, 72, 73, 1, 100, 97, 95, 74, 72, 72, 70, 15, 66, 1, 65, 23, 69, 5, 6, 1, 74, 59, 52, 37, 43, 52, 42, 25, 43, 33, 27, 31, 18, 5, 1, 1, 23, 16, 22, 19, 17, 15, 20, 13, 24, 18, 7, 9, 14, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 55, 39, 62, 55, 37, 62, 61, 62, 59, 54, 51, 56, 41, 34, 13, 21, 0, 70, 97, 70, 23, 14, 10, 2, 5, 0, 71, 73, 89, 66, 37, 25, 20, 17, 2, 66, 76, 75, 84, 89, 11, 5, 3, 64, 70, 74, 78, 86, 106, 94, 80, 76, 71, 73, 83, 93, 94, 113, 76, 31, 16, 11, 2, 68, 77, 83, 87, 94, 62, 91, 82, 77, 66, 70, 1, 4, 5, 74, 2, 6, 64, 78, 71, 68, 18, 3, 25, 21, 33, 32, 19, 24, 28, 21, 15, 0, 14, 65, 101, 94, 93, 86, 83, 83, 83, 85, 66, 76, 75, 74, 4, 75, 82, 97, 92, 95, 76, 73, 70, 72, 68, 65, 70, 1, 1, 67, 72, 6, 64, 72, 55, 43, 46, 47, 47, 50, 46, 45, 38, 38, 41, 35, 17, 20, 13, 42, 39, 37, 31, 30, 25, 11, 5, 64, 76, 81, 93, 70, 106, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 54, 44, 24, 3, 61, 59, 29, 62, 62, 60, 54, 58, 52, 46, 52, 45, 45, 29, 31, 19, 69, 37, 27, 9, 116, 108, 110, 82, 89, 86, 66, 78, 77, 68, 67, 12, 0, 18, 71, 62, 62, 62, 62, 52, 49, 21, 3, 85, 74, 41, 29, 20, 9, 9, 2, 64, 68, 90, 103, 94, 87, 87, 84, 71, 77, 83, 71, 68, 67, 65, 1, 6, 4, 62, 62, 62, 59, 55, 49, 37, 22, 69 }, { 62, 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 25, 56, 58, 54, 14, 108, 5, 74, 1, 1, 11, 72, 9, 68, 18, 56, 73, 94, 106, 115, 99, 67, 74, 1, 1, 84, 90, 4, 16, 68, 79, 93, 7, 68, 84, 88, 5, 75, 91, 8, 70, 80, 88, 65, 72, 73, 78, 70, 5, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 52, 8, 69, 122, 98, 82, 72, 101, 120, 119, 121, 90, 120, 108, 119, 118, 112, 118, 112, 3, 67, 80, 100, 69, 91, 87, 119, 73, 95, 89, 122, 8, 80, 74, 119, 91, 84, 76, 69, 1, 67, 81, 3, 0, 0, 6, 65, 64, 2, 77, 13, 10, 26, 19, 23, 18, 17, 18, 39, 12, 12, 37, 26, 65, 104, 96, 97, 87, 91, 85, 80, 83, 81, 83, 89, 70, 72, 72, 0, 100, 96, 95, 74, 72, 72, 70, 14, 65, 1, 65, 21, 68, 4, 5, 1, 75, 57, 51, 36, 42, 51, 41, 24, 42, 33, 25, 30, 17, 5, 1, 1, 22, 16, 21, 19, 16, 14, 19, 12, 22, 17, 6, 8, 13, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 53, 36, 62, 54, 35, 62, 59, 62, 57, 51, 49, 53, 39, 32, 12, 20, 65, 71, 97, 70, 23, 15, 10, 2, 5, 0, 71, 73, 88, 65, 38, 25, 20, 17, 3, 66, 75, 75, 83, 89, 12, 6, 3, 64, 70, 74, 78, 85, 105, 94, 79, 76, 71, 73, 82, 92, 94, 112, 76, 32, 16, 11, 2, 67, 76, 83, 86, 93, 62, 91, 82, 77, 66, 70, 1, 4, 5, 73, 2, 6, 0, 78, 71, 68, 17, 3, 24, 20, 32, 31, 19, 22, 27, 20, 15, 64, 13, 66, 101, 94, 92, 86, 83, 83, 82, 84, 67, 76, 75, 74, 3, 75, 82, 97, 91, 95, 76, 72, 70, 72, 68, 65, 69, 1, 0, 67, 71, 6, 65, 73, 54, 43, 46, 46, 46, 49, 45, 44, 37, 37, 40, 34, 16, 19, 12, 40, 37, 35, 29, 29, 24, 10, 4, 64, 76, 81, 93, 71, 106, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 55, 52, 42, 23, 2, 59, 57, 28, 62, 62, 58, 52, 55, 50, 44, 50, 43, 43, 27, 29, 17, 70, 35, 25, 7, 115, 107, 109, 82, 88, 85, 64, 77, 76, 68, 66, 13, 1, 19, 71, 62, 62, 62, 62, 49, 46, 18, 1, 86, 74, 41, 29, 20, 9, 9, 2, 64, 68, 89, 102, 93, 86, 87, 83, 70, 76, 82, 70, 67, 66, 64, 2, 7, 4, 62, 62, 62, 57, 53, 47, 35, 20, 70 }, { 62, 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 23, 54, 57, 54, 14, 106, 5, 73, 2, 1, 11, 71, 8, 69, 18, 54, 75, 95, 106, 112, 97, 67, 73, 2, 1, 84, 89, 4, 16, 68, 79, 92, 7, 69, 84, 88, 5, 75, 90, 8, 70, 80, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 52, 8, 69, 121, 97, 82, 71, 100, 118, 117, 119, 89, 118, 107, 117, 117, 111, 117, 111, 4, 67, 79, 99, 69, 90, 86, 117, 73, 95, 88, 120, 9, 80, 73, 118, 90, 83, 76, 69, 2, 66, 80, 4, 1, 0, 6, 65, 64, 2, 77, 13, 9, 25, 19, 22, 18, 17, 18, 37, 12, 11, 36, 25, 66, 103, 95, 96, 86, 90, 84, 79, 82, 80, 82, 88, 70, 72, 72, 64, 99, 95, 95, 73, 72, 71, 70, 13, 64, 1, 65, 20, 67, 4, 4, 1, 75, 56, 50, 36, 41, 50, 40, 23, 42, 33, 24, 29, 17, 5, 1, 0, 22, 15, 20, 18, 15, 13, 19, 11, 20, 16, 5, 7, 12, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 51, 34, 60, 52, 33, 62, 57, 60, 55, 49, 47, 50, 37, 29, 11, 18, 66, 71, 97, 70, 23, 15, 10, 2, 5, 0, 71, 73, 88, 65, 38, 25, 20, 17, 4, 65, 74, 75, 82, 88, 13, 7, 3, 0, 69, 73, 77, 85, 104, 93, 77, 75, 71, 72, 81, 91, 93, 111, 75, 32, 17, 11, 2, 66, 75, 82, 85, 92, 62, 91, 82, 76, 66, 70, 1, 4, 5, 73, 2, 7, 0, 78, 71, 68, 16, 4, 23, 19, 31, 31, 19, 21, 26, 20, 15, 65, 12, 66, 100, 93, 91, 85, 82, 82, 82, 83, 67, 76, 75, 74, 2, 75, 82, 96, 90, 95, 76, 72, 70, 71, 68, 65, 68, 1, 0, 67, 70, 5, 65, 73, 53, 43, 45, 46, 45, 48, 44, 43, 37, 36, 39, 33, 15, 18, 11, 39, 36, 34, 27, 28, 23, 9, 3, 65, 76, 80, 93, 71, 105, 62, 62, 62, 62, 62, 62, 62, 62, 60, 58, 53, 50, 40, 21, 1, 57, 55, 27, 61, 62, 56, 50, 53, 48, 42, 48, 41, 40, 25, 27, 15, 71, 33, 23, 6, 114, 105, 108, 81, 87, 84, 1, 76, 75, 68, 65, 15, 3, 21, 70, 62, 62, 62, 62, 47, 43, 16, 64, 87, 74, 41, 29, 20, 9, 9, 2, 64, 68, 89, 101, 92, 85, 86, 82, 69, 76, 81, 69, 66, 65, 64, 2, 7, 4, 62, 62, 62, 56, 51, 45, 33, 18, 71 }, { 62, 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 22, 53, 56, 54, 14, 104, 5, 73, 3, 1, 10, 71, 7, 70, 17, 53, 76, 96, 107, 109, 96, 67, 73, 3, 1, 83, 88, 5, 15, 67, 78, 91, 6, 69, 84, 88, 5, 74, 90, 8, 70, 79, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 52, 8, 69, 120, 97, 82, 71, 99, 116, 115, 118, 88, 116, 106, 115, 116, 110, 116, 110, 5, 67, 78, 99, 68, 90, 86, 115, 73, 94, 88, 119, 9, 80, 73, 116, 90, 82, 75, 69, 2, 66, 79, 4, 1, 64, 6, 65, 64, 2, 77, 13, 9, 25, 19, 21, 18, 17, 18, 35, 12, 11, 34, 24, 67, 103, 94, 96, 86, 89, 84, 78, 82, 80, 82, 86, 70, 72, 72, 65, 99, 94, 95, 73, 72, 70, 69, 12, 64, 1, 65, 19, 66, 3, 3, 1, 76, 54, 49, 35, 41, 49, 40, 23, 41, 32, 23, 28, 17, 5, 1, 0, 21, 14, 19, 17, 15, 12, 18, 11, 18, 15, 5, 6, 11, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 54, 48, 31, 58, 50, 32, 62, 54, 57, 52, 47, 44, 48, 34, 27, 10, 16, 67, 72, 97, 69, 24, 15, 11, 2, 5, 0, 71, 73, 87, 65, 38, 26, 20, 17, 5, 65, 74, 74, 82, 87, 14, 7, 4, 0, 68, 73, 77, 84, 103, 92, 76, 74, 70, 72, 81, 91, 92, 109, 75, 32, 17, 11, 3, 66, 75, 81, 85, 91, 62, 90, 81, 76, 66, 70, 1, 4, 5, 73, 3, 7, 1, 78, 71, 69, 16, 4, 22, 18, 30, 30, 19, 20, 25, 20, 15, 65, 11, 67, 99, 92, 90, 85, 82, 82, 81, 83, 67, 75, 74, 73, 2, 75, 82, 96, 89, 95, 76, 72, 70, 70, 68, 65, 67, 0, 0, 67, 70, 4, 65, 74, 52, 42, 45, 45, 44, 48, 44, 42, 36, 36, 38, 32, 14, 17, 10, 37, 35, 32, 25, 26, 21, 8, 3, 65, 76, 80, 92, 72, 104, 62, 62, 62, 62, 62, 62, 62, 62, 58, 55, 51, 47, 38, 20, 1, 56, 54, 25, 59, 62, 54, 48, 51, 46, 40, 45, 39, 38, 23, 25, 14, 73, 31, 21, 4, 113, 104, 107, 80, 86, 83, 2, 75, 74, 68, 64, 16, 4, 22, 69, 62, 62, 62, 59, 44, 41, 13, 66, 89, 73, 41, 29, 20, 9, 9, 2, 64, 68, 88, 100, 92, 84, 85, 81, 69, 75, 80, 69, 66, 65, 64, 3, 7, 4, 62, 62, 61, 54, 50, 44, 30, 17, 72 }, { 62, 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 20, 51, 54, 54, 14, 101, 4, 72, 3, 1, 9, 71, 6, 71, 17, 51, 78, 97, 107, 106, 95, 67, 72, 3, 1, 83, 87, 5, 15, 67, 78, 91, 6, 70, 83, 88, 5, 74, 89, 7, 70, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 52, 8, 69, 119, 96, 82, 70, 97, 115, 113, 116, 87, 115, 104, 113, 115, 109, 115, 110, 6, 67, 77, 98, 68, 89, 85, 113, 73, 94, 87, 118, 9, 79, 72, 115, 89, 82, 75, 68, 2, 66, 78, 5, 2, 64, 5, 65, 64, 2, 77, 13, 8, 24, 19, 21, 18, 17, 17, 33, 12, 10, 32, 23, 68, 102, 93, 95, 85, 88, 83, 77, 81, 79, 81, 85, 70, 72, 71, 66, 98, 93, 95, 73, 72, 70, 69, 11, 0, 1, 65, 17, 65, 3, 2, 1, 77, 53, 48, 35, 40, 48, 39, 22, 40, 32, 22, 27, 17, 5, 1, 64, 20, 14, 18, 17, 14, 11, 17, 10, 16, 14, 4, 5, 10, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 60, 61, 52, 46, 29, 56, 49, 30, 62, 52, 55, 50, 44, 42, 45, 32, 24, 9, 15, 69, 73, 97, 69, 24, 16, 11, 2, 5, 0, 71, 73, 87, 64, 39, 26, 20, 17, 6, 64, 73, 74, 81, 86, 15, 8, 4, 0, 67, 72, 76, 84, 102, 92, 75, 74, 70, 72, 80, 90, 92, 108, 74, 33, 17, 11, 3, 65, 74, 80, 84, 90, 62, 90, 81, 75, 66, 70, 1, 4, 5, 72, 3, 7, 1, 78, 71, 69, 15, 5, 21, 17, 29, 29, 19, 19, 24, 19, 15, 66, 10, 67, 98, 92, 89, 84, 81, 81, 81, 82, 67, 75, 74, 73, 1, 75, 82, 95, 88, 95, 76, 71, 70, 70, 68, 65, 66, 0, 0, 67, 69, 4, 66, 75, 51, 42, 44, 44, 43, 47, 43, 41, 35, 35, 37, 31, 13, 16, 9, 36, 33, 31, 23, 25, 20, 7, 2, 66, 76, 80, 92, 72, 103, 62, 62, 62, 62, 62, 62, 62, 61, 56, 53, 49, 45, 36, 18, 0, 54, 52, 24, 57, 62, 52, 46, 49, 44, 38, 43, 37, 35, 21, 23, 12, 74, 29, 19, 3, 112, 103, 106, 80, 85, 82, 4, 74, 73, 68, 0, 18, 6, 24, 69, 62, 62, 61, 56, 41, 38, 10, 68, 90, 73, 41, 29, 20, 9, 9, 2, 64, 68, 88, 99, 91, 83, 84, 80, 68, 75, 79, 68, 65, 64, 0, 3, 8, 4, 62, 62, 59, 52, 48, 42, 28, 15, 73 }, { 62, 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 19, 50, 53, 54, 14, 99, 4, 71, 4, 1, 8, 71, 5, 73, 16, 49, 80, 98, 108, 104, 94, 67, 71, 4, 1, 83, 86, 5, 14, 67, 78, 90, 5, 70, 83, 89, 5, 74, 89, 7, 71, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, 78, 88, 97, 68, 16, 0, 52, 8, 69, 118, 95, 82, 70, 96, 113, 111, 115, 86, 113, 103, 112, 114, 109, 114, 109, 6, 67, 76, 97, 68, 89, 85, 112, 73, 94, 87, 117, 9, 79, 72, 114, 89, 81, 75, 68, 2, 66, 78, 5, 2, 65, 5, 65, 64, 2, 77, 13, 8, 23, 19, 20, 18, 17, 17, 31, 12, 10, 30, 22, 69, 101, 92, 94, 84, 88, 83, 76, 81, 79, 80, 84, 70, 72, 71, 68, 98, 92, 95, 73, 73, 69, 69, 10, 1, 1, 65, 16, 64, 2, 1, 1, 78, 51, 47, 34, 39, 47, 38, 21, 39, 31, 20, 26, 16, 5, 1, 64, 19, 13, 17, 16, 13, 10, 16, 9, 14, 12, 3, 4, 9, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 58, 58, 49, 43, 26, 54, 47, 28, 61, 50, 52, 47, 42, 39, 42, 30, 22, 8, 13, 70, 74, 98, 69, 24, 16, 11, 2, 5, 0, 71, 73, 86, 64, 39, 26, 20, 17, 7, 64, 73, 74, 81, 86, 16, 8, 4, 0, 67, 72, 76, 83, 101, 91, 74, 73, 70, 72, 79, 89, 91, 107, 74, 33, 17, 11, 3, 64, 74, 80, 83, 90, 62, 90, 81, 75, 66, 70, 1, 4, 5, 72, 3, 7, 2, 78, 71, 69, 14, 5, 20, 16, 28, 28, 19, 17, 22, 19, 15, 67, 9, 68, 98, 91, 88, 84, 81, 81, 80, 81, 68, 75, 74, 73, 0, 75, 82, 95, 88, 96, 76, 71, 70, 69, 68, 65, 66, 0, 64, 67, 68, 3, 66, 76, 50, 41, 44, 43, 41, 46, 42, 40, 34, 34, 36, 30, 12, 15, 8, 34, 32, 29, 21, 23, 19, 6, 1, 66, 76, 80, 92, 73, 103, 62, 62, 62, 62, 62, 62, 61, 58, 54, 51, 47, 42, 34, 17, 64, 52, 50, 22, 55, 61, 49, 43, 46, 41, 36, 41, 34, 33, 19, 20, 10, 75, 27, 17, 1, 111, 102, 105, 79, 84, 82, 5, 73, 73, 68, 0, 19, 7, 25, 68, 62, 62, 58, 53, 38, 35, 7, 70, 91, 73, 41, 29, 20, 9, 9, 2, 64, 68, 87, 99, 90, 82, 84, 79, 68, 74, 79, 68, 65, 64, 0, 4, 8, 3, 62, 62, 57, 50, 46, 40, 26, 13, 74 }, { 62, 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 18, 49, 52, 54, 14, 97, 4, 70, 5, 1, 8, 70, 4, 74, 15, 47, 81, 99, 109, 101, 92, 67, 70, 5, 1, 82, 85, 6, 13, 67, 77, 89, 5, 70, 83, 89, 5, 74, 88, 7, 71, 79, 88, 0, 71, 71, 77, 68, 5, 22, 0, 0, 0, 77, 88, 97, 68, 15, 0, 52, 8, 69, 117, 94, 82, 70, 95, 111, 109, 113, 84, 111, 102, 110, 113, 108, 113, 108, 7, 66, 75, 96, 68, 88, 84, 110, 73, 93, 87, 115, 10, 79, 72, 112, 89, 80, 75, 68, 3, 65, 77, 5, 2, 65, 5, 64, 64, 2, 76, 13, 8, 23, 19, 19, 18, 17, 17, 29, 12, 10, 29, 21, 69, 100, 91, 93, 83, 87, 82, 75, 80, 79, 79, 83, 70, 72, 71, 69, 97, 91, 95, 72, 73, 68, 69, 9, 2, 1, 65, 15, 0, 1, 0, 1, 78, 50, 46, 34, 38, 46, 37, 21, 39, 31, 19, 25, 16, 5, 1, 64, 19, 12, 16, 15, 12, 9, 16, 9, 13, 11, 3, 3, 8, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 56, 56, 46, 41, 23, 53, 45, 27, 59, 48, 50, 45, 40, 37, 40, 28, 20, 8, 11, 71, 74, 98, 69, 25, 16, 11, 3, 5, 0, 70, 73, 85, 64, 39, 26, 21, 17, 8, 0, 72, 73, 80, 85, 17, 9, 5, 1, 66, 71, 76, 82, 100, 90, 72, 72, 69, 71, 78, 88, 90, 106, 73, 33, 18, 12, 3, 0, 73, 79, 82, 89, 62, 89, 80, 74, 66, 70, 1, 5, 6, 72, 3, 8, 3, 78, 71, 69, 14, 5, 19, 16, 27, 28, 19, 16, 21, 19, 15, 67, 8, 69, 97, 90, 87, 84, 80, 81, 79, 80, 68, 75, 74, 72, 0, 75, 82, 95, 87, 96, 76, 71, 70, 68, 68, 65, 65, 0, 64, 67, 67, 2, 66, 76, 49, 41, 44, 43, 40, 45, 41, 39, 34, 33, 35, 30, 12, 14, 7, 33, 31, 27, 19, 22, 18, 6, 1, 66, 75, 79, 92, 74, 102, 62, 62, 62, 62, 62, 62, 59, 56, 52, 49, 45, 40, 32, 16, 65, 50, 49, 21, 53, 59, 47, 41, 44, 39, 34, 39, 32, 31, 18, 18, 8, 76, 25, 15, 64, 110, 100, 103, 78, 83, 81, 7, 72, 72, 68, 1, 21, 8, 27, 67, 62, 62, 56, 50, 36, 32, 5, 72, 92, 73, 41, 29, 20, 9, 10, 2, 64, 68, 86, 98, 89, 81, 83, 77, 67, 73, 78, 67, 64, 0, 0, 5, 8, 3, 62, 61, 56, 49, 44, 38, 24, 11, 74 }, { 62, 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 16, 47, 50, 54, 14, 94, 3, 69, 5, 1, 7, 70, 3, 75, 15, 45, 83, 100, 109, 98, 91, 67, 69, 5, 1, 82, 84, 6, 13, 67, 77, 89, 5, 71, 82, 89, 5, 74, 87, 6, 71, 79, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, 77, 88, 97, 67, 14, 0, 52, 8, 69, 116, 93, 82, 69, 93, 110, 107, 112, 83, 110, 100, 108, 112, 107, 112, 108, 8, 66, 74, 95, 68, 88, 83, 108, 73, 93, 86, 114, 10, 78, 71, 111, 88, 80, 75, 67, 3, 65, 76, 6, 3, 66, 4, 64, 64, 2, 76, 13, 7, 22, 19, 19, 18, 17, 16, 27, 12, 9, 27, 20, 70, 99, 90, 92, 82, 86, 81, 74, 79, 78, 78, 82, 70, 72, 70, 70, 97, 90, 95, 72, 73, 68, 69, 8, 3, 1, 65, 13, 1, 1, 64, 1, 79, 48, 45, 33, 37, 45, 36, 20, 38, 31, 18, 24, 16, 5, 1, 65, 18, 12, 15, 15, 11, 8, 15, 8, 11, 10, 2, 2, 7, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 54, 53, 44, 39, 21, 51, 44, 25, 56, 46, 48, 43, 37, 35, 37, 26, 17, 7, 10, 73, 75, 98, 69, 25, 17, 11, 3, 5, 0, 70, 73, 85, 0, 40, 26, 21, 17, 9, 0, 71, 73, 79, 84, 18, 10, 5, 1, 65, 71, 75, 82, 99, 90, 71, 72, 69, 71, 77, 87, 90, 105, 73, 34, 18, 12, 3, 1, 72, 78, 81, 88, 62, 89, 80, 74, 66, 70, 1, 5, 6, 71, 3, 8, 3, 78, 71, 69, 13, 6, 18, 15, 26, 27, 19, 15, 20, 18, 15, 68, 7, 69, 96, 90, 86, 83, 80, 80, 79, 79, 68, 75, 74, 72, 64, 75, 82, 94, 86, 96, 76, 70, 70, 68, 68, 65, 64, 0, 64, 67, 66, 2, 67, 77, 48, 41, 43, 42, 39, 44, 40, 38, 33, 32, 34, 29, 11, 13, 6, 31, 29, 26, 17, 21, 17, 5, 0, 67, 75, 79, 92, 74, 101, 62, 62, 62, 62, 62, 60, 57, 53, 50, 47, 43, 38, 30, 14, 66, 48, 47, 20, 51, 57, 45, 39, 42, 37, 32, 37, 30, 28, 16, 16, 6, 77, 23, 13, 65, 109, 99, 102, 78, 82, 80, 9, 71, 71, 68, 2, 22, 10, 28, 67, 62, 60, 53, 47, 33, 29, 2, 74, 93, 73, 41, 29, 20, 9, 10, 2, 64, 68, 86, 97, 88, 80, 82, 76, 66, 73, 77, 66, 0, 1, 1, 5, 9, 3, 60, 59, 54, 47, 42, 36, 22, 9, 75 }, { 62, 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 15, 46, 49, 54, 14, 92, 3, 69, 6, 1, 6, 70, 2, 76, 14, 44, 84, 101, 110, 95, 90, 67, 69, 6, 1, 81, 83, 7, 12, 66, 76, 88, 4, 71, 82, 89, 5, 73, 87, 6, 71, 78, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, 76, 88, 97, 67, 14, 64, 52, 8, 69, 115, 93, 82, 69, 92, 108, 105, 110, 82, 108, 99, 106, 111, 106, 111, 107, 9, 66, 73, 95, 67, 87, 83, 106, 73, 92, 86, 113, 10, 78, 71, 109, 88, 79, 74, 67, 3, 65, 75, 6, 3, 66, 4, 64, 64, 2, 76, 13, 7, 22, 19, 18, 18, 17, 16, 25, 12, 9, 25, 19, 71, 99, 89, 92, 82, 85, 81, 73, 79, 78, 78, 80, 70, 72, 70, 71, 96, 89, 95, 72, 73, 67, 68, 7, 3, 1, 65, 12, 2, 0, 65, 1, 80, 47, 44, 33, 37, 44, 36, 20, 37, 30, 17, 23, 16, 5, 1, 65, 17, 11, 14, 14, 11, 7, 14, 8, 9, 9, 2, 1, 6, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, 54, 52, 51, 41, 36, 18, 49, 42, 24, 54, 43, 45, 40, 35, 32, 35, 23, 15, 6, 8, 74, 76, 98, 68, 26, 17, 12, 3, 5, 0, 70, 73, 84, 0, 40, 27, 21, 17, 10, 1, 71, 72, 79, 83, 19, 10, 6, 1, 64, 70, 75, 81, 98, 89, 70, 71, 68, 71, 77, 87, 89, 103, 72, 34, 18, 12, 4, 1, 72, 77, 81, 87, 62, 88, 79, 73, 66, 70, 1, 5, 6, 71, 4, 8, 4, 78, 71, 70, 13, 6, 17, 14, 25, 26, 19, 14, 19, 18, 15, 68, 6, 70, 95, 89, 85, 83, 79, 80, 78, 79, 68, 74, 73, 71, 64, 75, 82, 94, 85, 96, 76, 70, 70, 67, 68, 65, 0, 64, 64, 67, 66, 1, 67, 78, 47, 40, 43, 41, 38, 44, 40, 37, 32, 32, 33, 28, 10, 12, 5, 30, 28, 24, 15, 19, 15, 4, 0, 67, 75, 79, 91, 75, 100, 62, 62, 62, 62, 62, 58, 55, 51, 48, 44, 41, 35, 28, 13, 66, 47, 46, 18, 49, 54, 43, 37, 40, 35, 30, 34, 28, 26, 14, 14, 5, 79, 21, 11, 67, 108, 98, 101, 77, 81, 79, 10, 70, 70, 68, 3, 24, 11, 30, 66, 61, 59, 51, 44, 30, 27, 64, 76, 95, 72, 41, 29, 20, 9, 10, 2, 64, 68, 85, 96, 88, 79, 81, 75, 66, 72, 76, 66, 0, 1, 1, 6, 9, 3, 59, 58, 52, 45, 41, 35, 19, 8, 76 }, { 62, 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 13, 44, 48, 54, 14, 90, 3, 68, 7, 1, 5, 70, 1, 77, 14, 42, 86, 102, 110, 92, 89, 67, 68, 7, 1, 81, 82, 7, 12, 66, 76, 87, 4, 72, 82, 89, 5, 73, 86, 6, 72, 78, 88, 2, 70, 71, 76, 66, 5, 22, 0, 0, 0, 76, 89, 97, 66, 13, 64, 52, 8, 69, 114, 92, 82, 68, 91, 106, 103, 109, 81, 106, 98, 104, 110, 106, 110, 106, 9, 66, 72, 94, 67, 87, 82, 104, 73, 92, 85, 112, 10, 78, 70, 108, 87, 78, 74, 67, 3, 65, 75, 7, 4, 67, 4, 64, 64, 2, 76, 13, 6, 21, 19, 17, 18, 17, 16, 23, 12, 8, 23, 18, 72, 98, 88, 91, 81, 85, 80, 72, 78, 77, 77, 79, 70, 72, 70, 72, 96, 88, 95, 72, 73, 66, 68, 6, 4, 1, 65, 11, 3, 0, 66, 1, 81, 45, 43, 32, 36, 43, 35, 19, 36, 30, 15, 22, 15, 5, 1, 66, 16, 10, 13, 13, 10, 6, 13, 7, 7, 8, 1, 0, 5, 89, 62, 62, 61, 62, 62, 62, 62, 62, 61, 52, 50, 48, 39, 34, 16, 47, 40, 22, 52, 41, 43, 38, 33, 30, 32, 21, 12, 5, 6, 75, 77, 98, 68, 26, 17, 12, 3, 5, 0, 70, 73, 84, 0, 40, 27, 21, 17, 11, 1, 70, 72, 78, 83, 20, 11, 6, 1, 64, 70, 74, 81, 97, 88, 69, 70, 68, 71, 76, 86, 88, 102, 72, 34, 18, 12, 4, 2, 71, 77, 80, 86, 62, 88, 79, 73, 66, 70, 1, 5, 6, 71, 4, 8, 4, 78, 71, 70, 12, 7, 16, 13, 24, 25, 19, 12, 18, 18, 15, 69, 5, 70, 95, 88, 84, 82, 79, 79, 78, 78, 69, 74, 73, 71, 65, 75, 82, 93, 84, 96, 76, 70, 70, 66, 68, 65, 1, 64, 65, 67, 65, 0, 67, 79, 46, 40, 42, 40, 37, 43, 39, 36, 31, 31, 32, 27, 9, 11, 4, 28, 27, 23, 13, 18, 14, 3, 64, 68, 75, 79, 91, 75, 100, 62, 62, 62, 62, 62, 56, 53, 48, 46, 42, 39, 33, 26, 11, 67, 45, 44, 17, 47, 52, 41, 35, 37, 33, 28, 32, 26, 23, 12, 12, 3, 80, 19, 9, 68, 107, 97, 100, 76, 80, 78, 12, 69, 69, 68, 4, 25, 13, 31, 65, 59, 57, 48, 41, 27, 24, 67, 78, 96, 72, 41, 29, 20, 9, 10, 2, 64, 68, 85, 95, 87, 78, 81, 74, 65, 72, 75, 65, 1, 2, 1, 6, 9, 3, 58, 56, 50, 43, 39, 33, 17, 6, 77 }, { 62, 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 12, 43, 46, 54, 14, 87, 2, 67, 7, 1, 5, 69, 0, 78, 13, 40, 88, 103, 111, 89, 87, 67, 67, 7, 1, 81, 81, 7, 11, 66, 76, 87, 4, 72, 81, 89, 5, 73, 85, 5, 72, 78, 88, 2, 69, 70, 75, 66, 5, 22, 0, 0, 0, 75, 89, 97, 66, 12, 64, 52, 8, 69, 113, 91, 82, 68, 89, 105, 101, 107, 80, 105, 96, 102, 109, 105, 109, 106, 10, 66, 71, 93, 67, 86, 81, 102, 73, 92, 85, 110, 11, 77, 70, 107, 87, 78, 74, 66, 4, 64, 74, 7, 4, 67, 3, 64, 64, 2, 76, 13, 6, 20, 19, 17, 18, 17, 15, 21, 12, 8, 22, 17, 73, 97, 87, 90, 80, 84, 79, 71, 77, 77, 76, 78, 70, 72, 69, 73, 95, 87, 95, 71, 73, 66, 68, 5, 5, 1, 65, 9, 4, 64, 67, 1, 81, 44, 42, 32, 35, 42, 34, 18, 36, 30, 14, 21, 15, 5, 1, 66, 16, 10, 12, 13, 9, 5, 13, 6, 5, 7, 0, 64, 4, 89, 61, 62, 59, 62, 61, 60, 60, 60, 59, 50, 48, 46, 36, 32, 13, 45, 39, 20, 49, 39, 41, 36, 30, 28, 29, 19, 10, 4, 5, 77, 77, 98, 68, 26, 18, 12, 3, 5, 0, 70, 73, 83, 1, 41, 27, 21, 17, 12, 2, 69, 72, 77, 82, 21, 12, 6, 2, 0, 69, 74, 80, 96, 88, 67, 70, 68, 70, 75, 85, 88, 101, 71, 35, 19, 12, 4, 3, 70, 76, 79, 85, 62, 88, 79, 72, 66, 70, 1, 5, 6, 70, 4, 9, 5, 78, 71, 70, 11, 7, 15, 12, 23, 25, 19, 11, 17, 17, 15, 70, 4, 71, 94, 88, 83, 82, 78, 79, 77, 77, 69, 74, 73, 71, 66, 75, 82, 93, 83, 96, 76, 69, 70, 66, 68, 65, 2, 64, 65, 67, 64, 0, 68, 79, 45, 40, 42, 40, 36, 42, 38, 35, 31, 30, 31, 26, 8, 10, 3, 27, 25, 21, 11, 17, 13, 2, 65, 68, 75, 78, 91, 76, 99, 62, 62, 62, 62, 60, 54, 51, 46, 44, 40, 37, 31, 24, 10, 68, 43, 42, 16, 45, 50, 39, 33, 35, 31, 26, 30, 24, 21, 10, 10, 1, 81, 17, 7, 70, 106, 95, 99, 76, 79, 77, 14, 68, 68, 68, 5, 27, 14, 33, 65, 58, 55, 46, 38, 25, 21, 69, 80, 97, 72, 41, 29, 20, 9, 10, 2, 64, 68, 84, 94, 86, 77, 80, 73, 64, 71, 74, 64, 2, 3, 2, 7, 10, 3, 56, 55, 49, 42, 37, 31, 15, 4, 78 }, { 61, 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 10, 41, 45, 54, 14, 85, 2, 66, 8, 1, 4, 69, 64, 79, 13, 38, 89, 104, 111, 86, 86, 67, 66, 8, 1, 80, 80, 8, 11, 66, 75, 86, 3, 73, 81, 89, 5, 73, 85, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, 75, 89, 97, 65, 11, 64, 52, 8, 69, 112, 90, 82, 67, 88, 103, 99, 106, 79, 103, 95, 100, 108, 104, 108, 105, 11, 66, 70, 92, 67, 86, 81, 100, 73, 91, 84, 109, 11, 77, 69, 105, 86, 77, 74, 66, 4, 64, 73, 8, 5, 68, 3, 64, 64, 2, 76, 13, 5, 20, 19, 16, 18, 17, 15, 19, 12, 7, 20, 16, 74, 96, 86, 89, 79, 83, 79, 70, 77, 76, 75, 77, 70, 72, 69, 74, 95, 86, 95, 71, 73, 65, 68, 4, 6, 1, 65, 8, 5, 64, 68, 1, 82, 42, 41, 31, 34, 41, 33, 18, 35, 29, 13, 20, 15, 5, 1, 67, 15, 9, 11, 12, 8, 4, 12, 6, 3, 6, 0, 65, 3, 89, 60, 61, 58, 62, 59, 58, 58, 58, 56, 47, 46, 43, 34, 29, 11, 43, 37, 19, 47, 37, 38, 33, 28, 25, 27, 17, 7, 3, 3, 78, 78, 98, 68, 27, 18, 12, 3, 5, 0, 70, 73, 83, 1, 41, 27, 21, 17, 13, 2, 69, 71, 77, 81, 22, 12, 7, 2, 1, 69, 73, 80, 95, 87, 66, 69, 67, 70, 74, 84, 87, 100, 71, 35, 19, 12, 4, 4, 70, 75, 78, 84, 62, 87, 78, 72, 66, 70, 1, 5, 6, 70, 4, 9, 5, 78, 71, 70, 11, 8, 14, 11, 22, 24, 19, 10, 16, 17, 15, 70, 3, 71, 93, 87, 82, 81, 78, 78, 77, 76, 69, 74, 73, 70, 66, 75, 82, 92, 82, 96, 76, 69, 70, 65, 68, 65, 3, 64, 65, 67, 0, 64, 68, 80, 44, 39, 41, 39, 35, 41, 37, 34, 30, 29, 30, 25, 7, 9, 2, 25, 24, 20, 9, 15, 12, 1, 65, 69, 75, 78, 91, 76, 98, 62, 62, 61, 61, 57, 52, 49, 43, 42, 38, 35, 28, 22, 8, 69, 41, 41, 14, 43, 48, 37, 31, 33, 29, 24, 28, 22, 18, 8, 8, 64, 82, 15, 5, 71, 105, 94, 98, 75, 78, 76, 15, 67, 67, 68, 6, 28, 16, 34, 64, 56, 54, 43, 35, 22, 18, 72, 82, 98, 72, 41, 29, 20, 9, 10, 2, 64, 68, 84, 93, 85, 76, 79, 72, 64, 71, 73, 64, 2, 3, 2, 7, 10, 3, 55, 53, 47, 40, 35, 29, 13, 2, 79 }, { 60, 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 9, 40, 44, 54, 14, 83, 2, 65, 9, 1, 3, 69, 65, 80, 12, 36, 91, 105, 112, 83, 85, 67, 65, 9, 1, 80, 79, 8, 10, 66, 75, 85, 3, 73, 81, 89, 5, 73, 84, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, 74, 89, 97, 65, 10, 64, 52, 8, 69, 111, 89, 82, 67, 87, 101, 97, 104, 78, 101, 94, 98, 107, 103, 107, 104, 12, 66, 69, 91, 67, 85, 80, 98, 73, 91, 84, 108, 11, 77, 69, 104, 86, 76, 74, 66, 4, 64, 72, 8, 5, 68, 3, 64, 64, 2, 76, 13, 5, 19, 19, 15, 18, 17, 15, 17, 12, 7, 18, 15, 75, 95, 85, 88, 78, 82, 78, 69, 76, 76, 74, 76, 70, 72, 69, 75, 94, 85, 95, 71, 73, 64, 68, 3, 7, 1, 65, 7, 6, 65, 69, 1, 83, 41, 40, 31, 33, 40, 32, 17, 34, 29, 12, 19, 15, 5, 1, 67, 14, 8, 10, 11, 7, 3, 11, 5, 1, 5, 64, 66, 2, 89, 58, 60, 56, 60, 57, 56, 56, 56, 54, 45, 44, 41, 31, 27, 8, 41, 35, 17, 45, 35, 36, 31, 26, 23, 24, 15, 5, 2, 1, 79, 79, 98, 68, 27, 18, 12, 3, 5, 0, 70, 73, 82, 1, 41, 27, 21, 17, 14, 3, 68, 71, 76, 80, 23, 13, 7, 2, 2, 68, 73, 79, 94, 86, 65, 68, 67, 70, 73, 83, 86, 99, 70, 35, 19, 12, 4, 5, 69, 74, 77, 83, 62, 87, 78, 71, 66, 70, 1, 5, 6, 70, 4, 9, 6, 78, 71, 70, 10, 8, 13, 10, 21, 23, 19, 9, 15, 17, 15, 71, 2, 72, 92, 86, 81, 81, 77, 78, 76, 75, 69, 74, 73, 70, 67, 75, 82, 92, 81, 96, 76, 69, 70, 64, 68, 65, 4, 64, 65, 67, 1, 65, 68, 81, 43, 39, 41, 38, 34, 40, 36, 33, 29, 28, 29, 24, 6, 8, 1, 24, 23, 18, 7, 14, 11, 0, 66, 69, 75, 78, 91, 77, 97, 62, 62, 59, 59, 54, 50, 47, 41, 40, 36, 33, 26, 20, 7, 70, 39, 39, 13, 41, 46, 35, 29, 31, 27, 22, 26, 20, 16, 6, 6, 66, 83, 13, 3, 73, 104, 93, 97, 74, 77, 75, 17, 66, 66, 68, 7, 30, 17, 36, 0, 55, 52, 41, 32, 19, 15, 75, 84, 99, 72, 41, 29, 20, 9, 10, 2, 64, 68, 83, 92, 84, 75, 78, 71, 0, 70, 72, 0, 3, 4, 2, 8, 10, 3, 54, 52, 45, 38, 33, 27, 11, 0, 80 }, { 58, 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 7, 38, 42, 53, 14, 81, 1, 65, 9, 0, 2, 69, 67, 82, 11, 34, 93, 106, 113, 81, 84, 68, 65, 9, 0, 80, 78, 8, 9, 66, 75, 85, 2, 74, 81, 90, 5, 73, 84, 4, 73, 78, 88, 3, 69, 70, 75, 65, 4, 22, 0, 0, 0, 74, 90, 97, 65, 9, 65, 52, 7, 69, 110, 89, 82, 67, 86, 100, 96, 103, 77, 100, 93, 97, 106, 103, 106, 104, 12, 66, 69, 91, 67, 85, 80, 97, 73, 91, 84, 107, 11, 77, 69, 103, 86, 76, 74, 66, 4, 64, 72, 8, 5, 69, 2, 64, 65, 2, 76, 12, 4, 18, 19, 14, 17, 17, 14, 15, 11, 6, 16, 14, 76, 95, 85, 88, 78, 82, 78, 68, 76, 76, 74, 75, 71, 72, 69, 77, 94, 85, 95, 71, 74, 64, 68, 2, 7, 1, 65, 5, 6, 66, 70, 1, 84, 39, 39, 30, 32, 39, 31, 16, 33, 28, 10, 18, 14, 4, 1, 68, 13, 7, 9, 10, 6, 2, 10, 4, 64, 3, 65, 68, 0, 89, 56, 58, 54, 58, 55, 53, 53, 53, 51, 42, 41, 38, 28, 24, 5, 39, 33, 15, 42, 32, 33, 28, 23, 20, 21, 12, 2, 1, 64, 81, 80, 99, 68, 27, 18, 12, 3, 5, 64, 70, 73, 82, 1, 41, 27, 21, 17, 15, 3, 68, 71, 76, 80, 23, 13, 7, 2, 2, 68, 73, 79, 93, 86, 64, 68, 67, 70, 73, 83, 86, 98, 70, 35, 19, 12, 4, 5, 69, 74, 77, 83, 62, 87, 78, 71, 66, 70, 1, 5, 6, 70, 4, 9, 6, 78, 71, 71, 9, 8, 12, 9, 20, 22, 18, 7, 13, 16, 14, 72, 0, 73, 92, 86, 80, 81, 77, 78, 76, 75, 70, 74, 73, 70, 68, 75, 82, 92, 81, 97, 76, 69, 70, 64, 69, 65, 4, 65, 66, 67, 1, 66, 69, 82, 42, 38, 40, 37, 32, 39, 35, 32, 28, 27, 28, 23, 5, 6, 64, 22, 21, 16, 5, 12, 9, 64, 67, 70, 75, 78, 91, 78, 97, 62, 61, 57, 56, 51, 47, 44, 38, 37, 33, 30, 23, 17, 5, 71, 37, 37, 11, 39, 43, 32, 26, 28, 24, 20, 23, 17, 13, 4, 3, 68, 85, 11, 1, 75, 103, 92, 96, 74, 77, 75, 18, 66, 66, 68, 7, 31, 18, 37, 0, 53, 50, 38, 28, 16, 12, 78, 87, 101, 72, 41, 28, 19, 9, 10, 2, 65, 68, 83, 92, 84, 75, 78, 70, 0, 70, 72, 0, 3, 4, 2, 8, 10, 2, 52, 50, 43, 36, 31, 25, 8, 65, 81 }, { 57, 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 6, 37, 41, 53, 14, 78, 1, 64, 10, 0, 2, 68, 68, 83, 11, 33, 94, 107, 113, 78, 82, 68, 64, 10, 0, 79, 76, 9, 9, 65, 74, 84, 2, 74, 80, 90, 5, 72, 83, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 73, 90, 97, 64, 9, 65, 52, 7, 69, 108, 88, 82, 66, 84, 98, 94, 101, 75, 98, 91, 95, 104, 102, 105, 103, 13, 65, 68, 90, 66, 84, 79, 95, 72, 90, 83, 105, 12, 76, 68, 101, 85, 75, 73, 65, 5, 0, 71, 9, 6, 69, 2, 0, 65, 2, 75, 12, 4, 18, 19, 14, 17, 17, 14, 14, 11, 6, 15, 13, 76, 94, 84, 87, 77, 81, 77, 67, 75, 75, 73, 73, 71, 72, 68, 78, 93, 84, 95, 70, 74, 0, 67, 2, 8, 1, 65, 4, 7, 66, 71, 1, 84, 38, 39, 30, 32, 39, 31, 16, 33, 28, 9, 18, 14, 4, 1, 68, 13, 7, 9, 10, 6, 1, 10, 4, 65, 2, 65, 69, 64, 89, 55, 57, 53, 57, 54, 51, 51, 51, 49, 40, 39, 36, 26, 22, 3, 38, 32, 14, 40, 30, 31, 26, 21, 18, 19, 10, 0, 1, 65, 82, 80, 99, 67, 28, 19, 13, 4, 5, 64, 69, 72, 81, 2, 42, 28, 22, 17, 16, 4, 67, 70, 75, 79, 24, 14, 8, 3, 3, 67, 72, 78, 91, 85, 1, 67, 66, 69, 72, 82, 85, 96, 69, 36, 20, 13, 5, 6, 68, 73, 76, 82, 62, 86, 77, 70, 66, 69, 1, 6, 7, 69, 5, 10, 7, 77, 71, 71, 9, 9, 12, 9, 19, 22, 18, 6, 12, 16, 14, 72, 64, 73, 91, 85, 79, 80, 76, 77, 75, 74, 70, 73, 72, 69, 68, 74, 81, 91, 80, 97, 76, 68, 70, 0, 69, 65, 5, 65, 66, 66, 2, 66, 69, 82, 42, 38, 40, 37, 31, 39, 35, 32, 28, 27, 28, 23, 5, 5, 65, 21, 20, 15, 4, 11, 8, 64, 67, 70, 74, 77, 90, 78, 96, 60, 59, 55, 54, 49, 45, 42, 36, 35, 31, 28, 21, 15, 4, 71, 36, 36, 10, 38, 41, 30, 24, 26, 22, 18, 21, 15, 11, 3, 1, 69, 86, 10, 0, 76, 101, 90, 94, 73, 76, 74, 20, 65, 65, 68, 8, 33, 20, 39, 1, 52, 49, 36, 25, 14, 10, 80, 89, 102, 71, 42, 28, 19, 9, 11, 2, 65, 68, 82, 91, 83, 74, 77, 68, 1, 69, 71, 1, 4, 5, 3, 9, 11, 2, 51, 49, 42, 35, 30, 24, 6, 66, 81 }, { 56, 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 5, 36, 40, 53, 14, 76, 1, 0, 11, 0, 1, 68, 69, 84, 10, 31, 96, 108, 114, 75, 81, 68, 0, 11, 0, 79, 75, 9, 8, 65, 74, 83, 2, 74, 80, 90, 5, 72, 82, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 52, 7, 69, 107, 87, 82, 66, 83, 96, 92, 100, 74, 96, 90, 93, 103, 101, 104, 102, 14, 65, 67, 89, 66, 84, 78, 93, 72, 90, 83, 104, 12, 76, 68, 100, 85, 74, 73, 65, 5, 0, 70, 9, 6, 70, 2, 0, 65, 2, 75, 12, 4, 17, 19, 13, 17, 17, 14, 12, 11, 6, 13, 12, 77, 93, 83, 86, 76, 80, 76, 66, 74, 75, 72, 72, 71, 72, 68, 79, 93, 83, 95, 70, 74, 1, 67, 1, 9, 1, 65, 3, 8, 67, 72, 1, 85, 36, 38, 29, 31, 38, 30, 15, 32, 28, 8, 17, 14, 4, 1, 68, 12, 6, 8, 9, 5, 0, 9, 3, 67, 1, 66, 70, 65, 89, 53, 56, 51, 55, 52, 49, 49, 49, 46, 38, 37, 33, 23, 20, 0, 36, 30, 12, 38, 28, 29, 24, 19, 16, 16, 8, 65, 0, 67, 83, 81, 99, 67, 28, 19, 13, 4, 5, 64, 69, 72, 80, 2, 42, 28, 22, 17, 17, 4, 66, 70, 74, 78, 25, 15, 8, 3, 4, 67, 72, 77, 90, 84, 2, 66, 66, 69, 71, 81, 84, 95, 69, 36, 20, 13, 5, 7, 67, 72, 75, 81, 62, 86, 77, 70, 66, 69, 1, 6, 7, 69, 5, 10, 8, 77, 71, 71, 8, 9, 11, 8, 18, 21, 18, 5, 11, 16, 14, 73, 65, 74, 90, 84, 78, 80, 76, 77, 74, 73, 70, 73, 72, 69, 69, 74, 81, 91, 79, 97, 76, 68, 70, 1, 69, 65, 6, 65, 66, 66, 3, 67, 69, 83, 41, 38, 40, 36, 30, 38, 34, 31, 27, 26, 27, 22, 4, 4, 66, 19, 19, 13, 2, 10, 7, 65, 68, 70, 74, 77, 90, 79, 95, 58, 57, 53, 52, 46, 43, 40, 33, 33, 29, 26, 19, 13, 3, 72, 34, 34, 9, 36, 39, 28, 22, 24, 20, 16, 19, 13, 9, 1, 64, 71, 87, 8, 65, 78, 100, 89, 93, 72, 75, 73, 22, 64, 64, 68, 9, 34, 21, 40, 2, 51, 47, 33, 22, 11, 7, 83, 91, 103, 71, 42, 28, 19, 9, 11, 2, 65, 68, 81, 90, 82, 73, 76, 67, 2, 68, 70, 2, 5, 6, 3, 10, 11, 2, 50, 47, 40, 33, 28, 22, 4, 68, 82 }, { 55, 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 3, 34, 39, 53, 14, 74, 1, 1, 12, 0, 0, 68, 70, 85, 10, 29, 97, 109, 114, 72, 80, 68, 1, 12, 0, 78, 74, 10, 8, 65, 73, 82, 1, 75, 80, 90, 5, 72, 82, 4, 73, 77, 88, 5, 68, 69, 74, 0, 4, 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 52, 7, 69, 106, 86, 82, 65, 82, 94, 90, 98, 73, 94, 89, 91, 102, 100, 103, 101, 15, 65, 66, 88, 66, 83, 78, 91, 72, 89, 82, 103, 12, 76, 67, 98, 84, 73, 73, 65, 5, 0, 69, 10, 7, 70, 2, 0, 65, 2, 75, 12, 3, 17, 19, 12, 17, 17, 14, 10, 11, 5, 11, 11, 78, 92, 82, 85, 75, 79, 76, 65, 74, 74, 71, 71, 71, 72, 68, 80, 92, 82, 95, 70, 74, 2, 67, 0, 10, 1, 65, 2, 9, 67, 73, 1, 86, 35, 37, 29, 30, 37, 29, 15, 31, 27, 7, 16, 14, 4, 1, 69, 11, 5, 7, 8, 4, 64, 8, 3, 69, 0, 66, 71, 66, 89, 52, 54, 50, 53, 50, 47, 47, 47, 44, 35, 35, 31, 21, 17, 65, 34, 28, 11, 36, 26, 26, 21, 17, 13, 14, 6, 68, 64, 69, 84, 82, 99, 67, 29, 19, 13, 4, 5, 64, 69, 72, 80, 2, 42, 28, 22, 17, 18, 5, 66, 69, 74, 77, 26, 15, 9, 3, 5, 66, 71, 77, 89, 83, 3, 65, 65, 69, 70, 80, 83, 94, 68, 36, 20, 13, 5, 8, 67, 71, 74, 80, 62, 85, 76, 69, 66, 69, 1, 6, 7, 69, 5, 10, 8, 77, 71, 71, 8, 10, 10, 7, 17, 20, 18, 4, 10, 16, 14, 73, 66, 74, 89, 83, 77, 79, 75, 76, 74, 72, 70, 73, 72, 68, 69, 74, 81, 90, 78, 97, 76, 68, 70, 2, 69, 65, 7, 65, 66, 66, 4, 68, 69, 84, 40, 37, 39, 35, 29, 37, 33, 30, 26, 25, 26, 21, 3, 3, 67, 18, 18, 12, 0, 8, 6, 66, 68, 71, 74, 77, 90, 79, 94, 56, 55, 51, 50, 43, 41, 38, 31, 31, 27, 24, 16, 11, 1, 73, 32, 33, 7, 34, 37, 26, 20, 22, 18, 14, 17, 11, 6, 64, 66, 73, 88, 6, 67, 79, 99, 88, 92, 71, 74, 72, 23, 0, 0, 68, 10, 36, 23, 42, 3, 49, 46, 31, 19, 8, 4, 86, 93, 104, 71, 42, 28, 19, 9, 11, 2, 65, 68, 81, 89, 81, 72, 75, 66, 2, 68, 69, 2, 5, 6, 3, 10, 11, 2, 49, 46, 38, 31, 26, 20, 2, 70, 83 }, { 53, 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 2, 33, 37, 53, 14, 71, 0, 2, 12, 0, 64, 68, 71, 86, 9, 27, 99, 110, 115, 69, 79, 68, 2, 12, 0, 78, 73, 10, 7, 65, 73, 82, 1, 75, 79, 90, 5, 72, 81, 3, 74, 77, 88, 5, 67, 69, 73, 0, 4, 22, 0, 0, 0, 71, 91, 97, 0, 6, 65, 52, 7, 69, 105, 85, 82, 65, 80, 93, 88, 97, 72, 93, 87, 89, 101, 100, 102, 101, 15, 65, 65, 87, 66, 83, 77, 89, 72, 89, 82, 102, 12, 75, 67, 97, 84, 73, 73, 64, 5, 0, 69, 10, 7, 71, 1, 0, 65, 2, 75, 12, 3, 16, 19, 12, 17, 17, 13, 8, 11, 5, 9, 10, 79, 91, 81, 84, 74, 79, 75, 64, 73, 74, 70, 70, 71, 72, 67, 81, 92, 81, 95, 70, 74, 2, 67, 64, 11, 1, 65, 0, 10, 68, 74, 1, 87, 33, 36, 28, 29, 36, 28, 14, 30, 27, 5, 15, 13, 4, 1, 69, 10, 5, 6, 8, 3, 65, 7, 2, 71, 64, 67, 72, 67, 89, 50, 53, 48, 51, 48, 45, 44, 45, 41, 33, 33, 28, 18, 15, 68, 32, 27, 9, 33, 24, 24, 19, 14, 11, 11, 4, 70, 65, 70, 86, 83, 99, 67, 29, 20, 13, 4, 5, 64, 69, 72, 79, 3, 43, 28, 22, 17, 19, 5, 65, 69, 73, 77, 27, 16, 9, 3, 5, 66, 71, 76, 88, 83, 4, 65, 65, 69, 69, 79, 83, 93, 68, 37, 20, 13, 5, 9, 66, 71, 73, 79, 62, 85, 76, 69, 66, 69, 1, 6, 7, 68, 5, 10, 9, 77, 71, 71, 7, 10, 9, 6, 16, 19, 18, 2, 9, 15, 14, 74, 67, 75, 89, 83, 76, 79, 75, 76, 73, 71, 71, 73, 72, 68, 70, 74, 81, 90, 77, 97, 76, 67, 70, 2, 69, 65, 8, 65, 67, 66, 5, 68, 70, 85, 39, 37, 39, 34, 28, 36, 32, 29, 25, 24, 25, 20, 2, 2, 68, 16, 16, 10, 65, 7, 5, 67, 69, 71, 74, 77, 90, 80, 94, 53, 52, 49, 47, 40, 39, 36, 28, 29, 25, 22, 14, 9, 0, 74, 30, 31, 6, 32, 35, 24, 18, 19, 16, 12, 15, 9, 4, 66, 68, 75, 89, 4, 69, 81, 98, 87, 91, 71, 73, 71, 25, 1, 1, 68, 11, 37, 24, 43, 3, 48, 44, 28, 16, 5, 1, 89, 95, 105, 71, 42, 28, 19, 9, 11, 2, 65, 68, 80, 88, 80, 71, 75, 65, 3, 67, 68, 3, 6, 7, 4, 11, 12, 2, 47, 44, 36, 29, 24, 18, 0, 72, 84 }, { 52, 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 0, 31, 36, 53, 14, 69, 0, 3, 13, 0, 64, 67, 72, 87, 9, 25, 101, 111, 115, 66, 77, 68, 3, 13, 0, 78, 72, 10, 7, 65, 73, 81, 1, 76, 79, 90, 5, 72, 80, 3, 74, 77, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 52, 7, 69, 104, 84, 82, 64, 79, 91, 86, 95, 71, 91, 86, 87, 100, 99, 101, 100, 16, 65, 64, 86, 66, 82, 76, 87, 72, 89, 81, 100, 13, 75, 66, 96, 83, 72, 73, 64, 6, 1, 68, 11, 8, 71, 1, 0, 65, 2, 75, 12, 2, 15, 19, 11, 17, 17, 13, 6, 11, 4, 8, 9, 80, 90, 80, 83, 73, 78, 74, 0, 72, 73, 69, 69, 71, 72, 67, 82, 91, 80, 95, 69, 74, 3, 67, 65, 12, 1, 65, 64, 11, 68, 75, 1, 87, 32, 35, 28, 28, 35, 27, 13, 30, 27, 4, 14, 13, 4, 1, 70, 10, 4, 5, 7, 2, 66, 7, 1, 73, 65, 68, 73, 68, 89, 48, 52, 46, 49, 47, 43, 42, 43, 39, 31, 31, 26, 16, 13, 70, 30, 25, 7, 31, 22, 22, 17, 12, 9, 8, 2, 73, 66, 72, 87, 83, 99, 67, 29, 20, 13, 4, 5, 64, 69, 72, 79, 3, 43, 28, 22, 17, 20, 6, 64, 69, 72, 76, 28, 17, 9, 4, 6, 65, 70, 76, 87, 82, 6, 64, 65, 68, 68, 78, 82, 92, 67, 37, 21, 13, 5, 10, 65, 70, 72, 78, 62, 85, 76, 68, 66, 69, 1, 6, 7, 68, 5, 11, 9, 77, 71, 71, 6, 11, 8, 5, 15, 19, 18, 1, 8, 15, 14, 75, 68, 75, 88, 82, 75, 78, 74, 75, 73, 70, 71, 73, 72, 68, 71, 74, 81, 89, 76, 97, 76, 67, 70, 3, 69, 65, 9, 65, 67, 66, 6, 69, 70, 85, 38, 37, 38, 34, 27, 35, 31, 28, 25, 23, 24, 19, 1, 1, 69, 15, 15, 9, 67, 6, 4, 68, 70, 72, 74, 76, 90, 80, 93, 51, 50, 47, 45, 38, 37, 34, 26, 27, 23, 20, 12, 7, 65, 75, 28, 29, 5, 30, 33, 22, 16, 17, 14, 10, 13, 7, 1, 68, 70, 77, 90, 2, 71, 82, 97, 85, 90, 70, 72, 70, 27, 2, 2, 68, 12, 39, 26, 45, 4, 46, 42, 26, 13, 3, 65, 91, 97, 106, 71, 42, 28, 19, 9, 11, 2, 65, 68, 80, 87, 79, 70, 74, 64, 4, 67, 67, 4, 7, 8, 4, 11, 12, 2, 46, 43, 35, 28, 22, 16, 65, 74, 85 }, { 51, 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 64, 30, 35, 53, 14, 67, 0, 3, 14, 0, 65, 67, 73, 88, 8, 24, 102, 112, 116, 0, 76, 68, 3, 14, 0, 77, 71, 11, 6, 64, 72, 80, 0, 76, 79, 90, 5, 71, 80, 3, 74, 76, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 70, 91, 97, 1, 5, 66, 52, 7, 69, 103, 84, 82, 64, 78, 89, 84, 94, 70, 89, 85, 85, 99, 98, 100, 99, 17, 65, 0, 86, 65, 82, 76, 85, 72, 88, 81, 99, 13, 75, 66, 94, 83, 71, 72, 64, 6, 1, 67, 11, 8, 72, 1, 0, 65, 2, 75, 12, 2, 15, 19, 10, 17, 17, 13, 4, 11, 4, 6, 8, 81, 90, 79, 83, 73, 77, 74, 1, 72, 73, 69, 67, 71, 72, 67, 83, 91, 79, 95, 69, 74, 4, 66, 66, 12, 1, 65, 65, 12, 69, 76, 1, 88, 30, 34, 27, 28, 34, 27, 13, 29, 26, 3, 13, 13, 4, 1, 70, 9, 3, 4, 6, 2, 67, 6, 1, 75, 66, 68, 74, 69, 89, 47, 50, 45, 47, 45, 41, 40, 41, 36, 28, 29, 23, 13, 10, 73, 28, 23, 6, 29, 19, 19, 14, 10, 6, 6, 64, 75, 67, 74, 88, 84, 99, 66, 30, 20, 14, 4, 5, 64, 69, 72, 78, 3, 43, 29, 22, 17, 21, 6, 64, 68, 72, 75, 29, 17, 10, 4, 7, 65, 70, 75, 86, 81, 7, 0, 64, 68, 68, 78, 81, 90, 67, 37, 21, 13, 6, 10, 65, 69, 72, 77, 62, 84, 75, 68, 66, 69, 1, 6, 7, 68, 6, 11, 10, 77, 71, 72, 6, 11, 7, 4, 14, 18, 18, 0, 7, 15, 14, 75, 69, 76, 87, 81, 74, 78, 74, 75, 72, 70, 71, 72, 71, 67, 71, 74, 81, 89, 75, 97, 76, 67, 70, 4, 69, 65, 10, 66, 67, 66, 6, 70, 70, 86, 37, 36, 38, 33, 26, 35, 31, 27, 24, 23, 23, 18, 0, 0, 70, 13, 14, 7, 69, 4, 2, 69, 70, 72, 74, 76, 89, 81, 92, 49, 48, 45, 43, 35, 35, 32, 23, 25, 20, 18, 9, 5, 66, 75, 27, 28, 3, 28, 30, 20, 14, 15, 12, 8, 10, 5, 64, 70, 72, 78, 92, 0, 73, 84, 96, 84, 89, 69, 71, 69, 28, 3, 3, 68, 13, 40, 27, 46, 5, 45, 41, 23, 10, 0, 67, 94, 99, 108, 70, 42, 28, 19, 9, 11, 2, 65, 68, 79, 86, 79, 69, 73, 0, 4, 66, 66, 4, 7, 8, 4, 12, 12, 2, 45, 41, 33, 26, 21, 15, 68, 75, 86 }, { 50, 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 66, 28, 33, 53, 14, 64, 64, 4, 14, 0, 66, 67, 74, 89, 8, 22, 104, 113, 116, 3, 75, 68, 4, 14, 0, 77, 70, 11, 6, 64, 72, 80, 0, 77, 78, 90, 5, 71, 79, 2, 74, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 70, 91, 97, 2, 4, 66, 52, 7, 69, 102, 83, 82, 0, 76, 88, 82, 92, 69, 88, 83, 83, 98, 97, 99, 99, 18, 65, 1, 85, 65, 81, 75, 83, 72, 88, 80, 98, 13, 74, 65, 93, 82, 71, 72, 0, 6, 1, 66, 12, 9, 72, 0, 0, 65, 2, 75, 12, 1, 14, 19, 10, 17, 17, 12, 2, 11, 3, 4, 7, 82, 89, 78, 82, 72, 76, 73, 2, 71, 72, 68, 66, 71, 72, 66, 84, 90, 78, 95, 69, 74, 4, 66, 67, 13, 1, 65, 67, 13, 69, 77, 1, 89, 29, 33, 27, 27, 33, 26, 12, 28, 26, 2, 12, 13, 4, 1, 71, 8, 3, 3, 6, 1, 68, 5, 0, 77, 67, 69, 75, 70, 89, 45, 49, 43, 45, 43, 39, 37, 39, 34, 26, 27, 21, 11, 8, 75, 26, 22, 4, 26, 17, 17, 12, 7, 4, 3, 66, 78, 68, 75, 90, 85, 99, 66, 30, 21, 14, 4, 5, 64, 69, 72, 78, 4, 44, 29, 22, 17, 22, 7, 0, 68, 71, 74, 30, 18, 10, 4, 8, 64, 69, 75, 85, 81, 8, 0, 64, 68, 67, 77, 81, 89, 66, 38, 21, 13, 6, 11, 64, 68, 71, 76, 62, 84, 75, 67, 66, 69, 1, 6, 7, 67, 6, 11, 10, 77, 71, 72, 5, 12, 6, 3, 13, 17, 18, 64, 6, 14, 14, 76, 70, 76, 86, 81, 73, 77, 73, 74, 72, 69, 71, 72, 71, 67, 72, 74, 81, 88, 74, 97, 76, 66, 70, 4, 69, 65, 11, 66, 67, 66, 7, 70, 71, 87, 36, 36, 37, 32, 25, 34, 30, 26, 23, 22, 22, 17, 64, 64, 71, 12, 12, 6, 71, 3, 1, 70, 71, 73, 74, 76, 89, 81, 91, 47, 46, 43, 40, 32, 33, 30, 21, 23, 18, 16, 7, 3, 68, 76, 25, 26, 2, 26, 28, 18, 12, 13, 10, 6, 8, 3, 67, 72, 74, 80, 93, 65, 75, 85, 95, 83, 88, 69, 70, 68, 30, 4, 4, 68, 14, 42, 29, 48, 5, 43, 39, 21, 7, 66, 70, 97, 101, 109, 70, 42, 28, 19, 9, 11, 2, 65, 68, 79, 85, 78, 68, 72, 1, 5, 66, 65, 5, 8, 9, 5, 12, 13, 2, 43, 40, 31, 24, 19, 13, 70, 77, 87 }, { 48, 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 67, 27, 32, 53, 14, 1, 64, 5, 15, 0, 67, 67, 75, 91, 7, 20, 106, 114, 117, 5, 74, 68, 5, 15, 0, 77, 69, 11, 5, 64, 72, 79, 64, 77, 78, 91, 5, 71, 79, 2, 75, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 69, 92, 97, 2, 3, 66, 52, 7, 69, 101, 82, 82, 0, 75, 86, 80, 91, 68, 86, 82, 82, 97, 97, 98, 98, 18, 65, 2, 84, 65, 81, 75, 82, 72, 88, 80, 97, 13, 74, 65, 92, 82, 70, 72, 0, 6, 1, 66, 12, 9, 73, 0, 0, 65, 2, 75, 12, 1, 13, 19, 9, 17, 17, 12, 0, 11, 3, 2, 6, 83, 88, 77, 81, 71, 76, 73, 3, 71, 72, 67, 65, 71, 72, 66, 86, 90, 77, 95, 69, 75, 5, 66, 68, 14, 1, 65, 68, 14, 70, 78, 1, 90, 27, 32, 26, 26, 32, 25, 11, 27, 25, 0, 11, 12, 4, 1, 71, 7, 2, 2, 5, 0, 69, 4, 64, 79, 69, 70, 76, 71, 89, 43, 47, 41, 43, 41, 37, 35, 37, 31, 23, 25, 18, 8, 5, 78, 24, 20, 2, 24, 15, 14, 9, 5, 1, 0, 68, 80, 69, 77, 91, 86, 100, 66, 30, 21, 14, 4, 5, 64, 69, 72, 77, 4, 44, 29, 22, 17, 23, 7, 0, 68, 71, 74, 31, 18, 10, 4, 8, 64, 69, 74, 84, 80, 9, 1, 64, 68, 66, 76, 80, 88, 66, 38, 21, 13, 6, 12, 64, 68, 70, 76, 62, 84, 75, 67, 66, 69, 1, 6, 7, 67, 6, 11, 11, 77, 71, 72, 4, 12, 5, 2, 12, 16, 18, 66, 4, 14, 14, 77, 71, 77, 86, 80, 72, 77, 73, 74, 71, 68, 72, 72, 71, 67, 73, 74, 81, 88, 74, 98, 76, 66, 70, 5, 69, 65, 11, 66, 68, 66, 8, 71, 71, 88, 35, 35, 37, 31, 23, 33, 29, 25, 22, 21, 21, 16, 65, 65, 72, 10, 11, 4, 73, 1, 0, 71, 72, 73, 74, 76, 89, 82, 91, 44, 43, 41, 38, 29, 30, 27, 18, 21, 16, 14, 4, 1, 69, 77, 23, 24, 0, 24, 26, 15, 9, 10, 7, 4, 6, 0, 69, 74, 77, 82, 94, 67, 77, 87, 94, 82, 87, 68, 69, 68, 31, 5, 4, 68, 14, 43, 30, 49, 6, 42, 37, 18, 4, 69, 73, 100, 103, 110, 70, 42, 28, 19, 9, 11, 2, 65, 68, 78, 85, 77, 67, 72, 2, 5, 65, 65, 5, 8, 9, 5, 13, 13, 1, 42, 38, 29, 22, 17, 11, 72, 79, 88 }, { 47, 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 68, 26, 31, 53, 14, 3, 64, 6, 16, 0, 67, 66, 76, 92, 6, 18, 107, 115, 118, 8, 72, 68, 6, 16, 0, 76, 68, 12, 4, 64, 71, 78, 64, 77, 78, 91, 5, 71, 78, 2, 75, 76, 88, 7, 66, 67, 72, 2, 4, 22, 0, 0, 0, 68, 92, 97, 2, 2, 66, 52, 7, 69, 100, 81, 82, 0, 74, 84, 78, 89, 66, 84, 81, 80, 96, 96, 97, 97, 19, 64, 3, 83, 65, 80, 74, 80, 72, 87, 80, 95, 14, 74, 65, 90, 82, 69, 72, 0, 7, 2, 65, 12, 9, 73, 0, 1, 65, 2, 74, 12, 1, 13, 19, 8, 17, 17, 12, 65, 11, 3, 1, 5, 83, 87, 76, 80, 70, 75, 72, 4, 70, 72, 66, 64, 71, 72, 66, 87, 89, 76, 95, 68, 75, 6, 66, 69, 15, 1, 65, 69, 15, 71, 79, 1, 90, 26, 31, 26, 25, 31, 24, 11, 27, 25, 64, 10, 12, 4, 1, 71, 7, 1, 1, 4, 64, 70, 4, 64, 80, 70, 70, 77, 72, 89, 42, 46, 40, 42, 40, 35, 33, 35, 29, 21, 23, 16, 5, 3, 81, 23, 18, 1, 22, 13, 12, 7, 3, 64, 65, 70, 82, 69, 79, 92, 86, 100, 66, 31, 21, 14, 5, 5, 64, 68, 72, 76, 4, 44, 29, 23, 17, 24, 8, 1, 67, 70, 73, 32, 19, 11, 5, 9, 0, 69, 73, 83, 79, 11, 2, 0, 67, 65, 75, 79, 87, 65, 38, 22, 14, 6, 13, 0, 67, 69, 75, 62, 83, 74, 66, 66, 69, 1, 7, 8, 67, 6, 12, 12, 77, 71, 72, 4, 12, 4, 2, 11, 16, 18, 67, 3, 14, 14, 77, 72, 78, 85, 79, 71, 77, 72, 74, 70, 67, 72, 72, 71, 66, 73, 74, 81, 88, 73, 98, 76, 66, 70, 6, 69, 65, 12, 66, 68, 66, 9, 72, 71, 88, 34, 35, 37, 31, 22, 32, 28, 24, 22, 20, 20, 16, 65, 66, 73, 9, 10, 2, 75, 0, 64, 71, 72, 73, 73, 75, 89, 83, 90, 42, 41, 39, 36, 27, 28, 25, 16, 19, 14, 12, 2, 64, 70, 78, 21, 23, 64, 22, 24, 13, 7, 8, 5, 2, 4, 65, 71, 75, 79, 84, 95, 69, 79, 89, 93, 80, 85, 67, 68, 67, 33, 6, 5, 68, 15, 45, 31, 51, 7, 41, 36, 16, 1, 71, 76, 102, 105, 111, 70, 42, 28, 19, 9, 12, 2, 65, 68, 77, 84, 76, 66, 71, 4, 6, 64, 64, 6, 9, 10, 5, 14, 13, 1, 41, 37, 28, 21, 15, 9, 74, 81, 88 }, { 46, 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 70, 24, 29, 53, 14, 6, 65, 7, 16, 0, 68, 66, 77, 93, 6, 16, 109, 116, 118, 11, 71, 68, 7, 16, 0, 76, 67, 12, 4, 64, 71, 78, 64, 78, 77, 91, 5, 71, 77, 1, 75, 76, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 52, 7, 69, 99, 80, 82, 1, 72, 83, 76, 88, 65, 83, 79, 78, 95, 95, 96, 97, 20, 64, 4, 82, 65, 80, 73, 78, 72, 87, 79, 94, 14, 73, 64, 89, 81, 69, 72, 1, 7, 2, 64, 13, 10, 74, 64, 1, 65, 2, 74, 12, 0, 12, 19, 8, 17, 17, 11, 67, 11, 2, 64, 4, 84, 86, 75, 79, 69, 74, 71, 5, 69, 71, 65, 0, 71, 72, 65, 88, 89, 75, 95, 68, 75, 6, 66, 70, 16, 1, 65, 71, 16, 71, 80, 1, 91, 24, 30, 25, 24, 30, 23, 10, 26, 25, 65, 9, 12, 4, 1, 72, 6, 1, 0, 4, 65, 71, 3, 65, 82, 71, 71, 78, 73, 89, 40, 45, 38, 40, 38, 33, 30, 33, 26, 19, 21, 13, 3, 1, 83, 21, 17, 64, 19, 11, 10, 5, 0, 66, 68, 72, 85, 70, 80, 94, 87, 100, 66, 31, 22, 14, 5, 5, 64, 68, 72, 76, 5, 45, 29, 23, 17, 25, 8, 2, 67, 69, 72, 33, 20, 11, 5, 10, 0, 68, 73, 82, 79, 12, 2, 0, 67, 64, 74, 79, 86, 65, 39, 22, 14, 6, 14, 1, 66, 68, 74, 62, 83, 74, 66, 66, 69, 1, 7, 8, 66, 6, 12, 12, 77, 71, 72, 3, 13, 3, 1, 10, 15, 18, 68, 2, 13, 14, 78, 73, 78, 84, 79, 70, 76, 72, 73, 70, 66, 72, 72, 71, 66, 74, 74, 81, 87, 72, 98, 76, 65, 70, 6, 69, 65, 13, 66, 68, 66, 10, 72, 72, 89, 33, 35, 36, 30, 21, 31, 27, 23, 21, 19, 19, 15, 66, 67, 74, 7, 8, 1, 77, 64, 65, 72, 73, 74, 73, 75, 89, 83, 89, 40, 39, 37, 33, 24, 26, 23, 13, 17, 12, 10, 0, 66, 72, 79, 19, 21, 65, 20, 22, 11, 5, 6, 3, 0, 2, 67, 74, 77, 81, 86, 96, 71, 81, 90, 92, 79, 84, 67, 67, 66, 35, 7, 6, 68, 16, 46, 33, 52, 7, 39, 34, 13, 65, 74, 79, 105, 107, 112, 70, 42, 28, 19, 9, 12, 2, 65, 68, 77, 83, 75, 65, 70, 5, 7, 64, 0, 7, 10, 11, 6, 14, 14, 1, 39, 35, 26, 19, 13, 7, 76, 83, 89 }, { 45, 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 71, 23, 28, 53, 14, 8, 65, 7, 17, 0, 69, 66, 78, 94, 5, 15, 110, 117, 119, 14, 70, 68, 7, 17, 0, 75, 66, 13, 3, 0, 70, 77, 65, 78, 77, 91, 5, 70, 77, 1, 75, 75, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 52, 7, 69, 98, 80, 82, 1, 71, 81, 74, 86, 64, 81, 78, 76, 94, 94, 95, 96, 21, 64, 5, 82, 64, 79, 73, 76, 72, 86, 79, 93, 14, 73, 64, 87, 81, 68, 71, 1, 7, 2, 0, 13, 10, 74, 64, 1, 65, 2, 74, 12, 0, 12, 19, 7, 17, 17, 11, 69, 11, 2, 66, 3, 85, 86, 74, 79, 69, 73, 71, 6, 69, 71, 65, 2, 71, 72, 65, 89, 88, 74, 95, 68, 75, 7, 65, 71, 16, 1, 65, 72, 17, 72, 81, 1, 92, 23, 29, 25, 24, 29, 23, 10, 25, 24, 66, 8, 12, 4, 1, 72, 5, 0, 64, 3, 65, 72, 2, 65, 84, 72, 71, 79, 74, 89, 39, 43, 37, 38, 36, 31, 28, 31, 24, 16, 19, 11, 0, 65, 86, 19, 15, 65, 17, 8, 7, 2, 65, 69, 70, 75, 87, 71, 82, 95, 88, 100, 65, 32, 22, 15, 5, 5, 64, 68, 72, 75, 5, 45, 30, 23, 17, 26, 9, 2, 66, 69, 71, 34, 20, 12, 5, 11, 1, 68, 72, 81, 78, 13, 3, 1, 67, 64, 74, 78, 84, 64, 39, 22, 14, 7, 14, 1, 65, 68, 73, 62, 82, 73, 65, 66, 69, 1, 7, 8, 66, 7, 12, 13, 77, 71, 73, 3, 13, 2, 0, 9, 14, 18, 69, 1, 13, 14, 78, 74, 79, 83, 78, 69, 76, 71, 73, 69, 66, 72, 71, 70, 65, 74, 74, 81, 87, 71, 98, 76, 65, 70, 7, 69, 65, 14, 67, 68, 66, 10, 73, 72, 90, 32, 34, 36, 29, 20, 31, 27, 22, 20, 19, 18, 14, 67, 68, 75, 6, 7, 64, 79, 66, 67, 73, 73, 74, 73, 75, 88, 84, 88, 38, 37, 35, 31, 21, 24, 21, 11, 15, 9, 8, 66, 68, 73, 79, 18, 20, 67, 18, 19, 9, 3, 4, 1, 65, 64, 69, 76, 79, 83, 87, 98, 73, 83, 92, 91, 78, 83, 66, 66, 65, 36, 8, 7, 68, 17, 48, 34, 54, 8, 38, 33, 11, 68, 77, 81, 108, 109, 114, 69, 42, 28, 19, 9, 12, 2, 65, 68, 76, 82, 75, 64, 69, 6, 7, 0, 1, 7, 10, 11, 6, 15, 14, 1, 38, 34, 24, 17, 12, 6, 79, 84, 90 }, { 43, 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 73, 21, 27, 53, 14, 10, 65, 8, 18, 0, 70, 66, 79, 95, 5, 13, 112, 118, 119, 17, 69, 68, 8, 18, 0, 75, 65, 13, 3, 0, 70, 76, 65, 79, 77, 91, 5, 70, 76, 1, 76, 75, 88, 9, 65, 67, 71, 4, 4, 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 52, 7, 69, 97, 79, 82, 2, 70, 79, 72, 85, 0, 79, 77, 74, 93, 94, 94, 95, 21, 64, 6, 81, 64, 79, 72, 74, 72, 86, 78, 92, 14, 73, 0, 86, 80, 67, 71, 1, 7, 2, 0, 14, 11, 75, 64, 1, 65, 2, 74, 12, 64, 11, 19, 6, 17, 17, 11, 71, 11, 1, 68, 2, 86, 85, 73, 78, 68, 73, 70, 7, 68, 70, 64, 3, 71, 72, 65, 90, 88, 73, 95, 68, 75, 8, 65, 72, 17, 1, 65, 73, 18, 72, 82, 1, 93, 21, 28, 24, 23, 28, 22, 9, 24, 24, 68, 7, 11, 4, 1, 73, 4, 64, 65, 2, 66, 73, 1, 66, 86, 73, 72, 80, 75, 89, 37, 42, 35, 36, 34, 29, 26, 29, 21, 14, 17, 8, 65, 67, 88, 17, 13, 67, 15, 6, 5, 0, 67, 71, 73, 77, 90, 72, 84, 96, 89, 100, 65, 32, 22, 15, 5, 5, 64, 68, 72, 75, 5, 45, 30, 23, 17, 27, 9, 3, 66, 68, 71, 35, 21, 12, 5, 11, 1, 67, 72, 80, 77, 14, 4, 1, 67, 0, 73, 77, 83, 64, 39, 22, 14, 7, 15, 2, 65, 67, 72, 62, 82, 73, 65, 66, 69, 1, 7, 8, 66, 7, 12, 13, 77, 71, 73, 2, 14, 1, 64, 8, 13, 18, 71, 0, 13, 14, 79, 75, 79, 83, 77, 68, 75, 71, 72, 69, 65, 73, 71, 70, 65, 75, 74, 81, 86, 70, 98, 76, 65, 70, 8, 69, 65, 15, 67, 69, 66, 11, 74, 72, 91, 31, 34, 35, 28, 19, 30, 26, 21, 19, 18, 17, 13, 68, 69, 76, 4, 6, 65, 81, 67, 68, 74, 74, 75, 73, 75, 88, 84, 88, 35, 34, 33, 29, 18, 22, 19, 8, 13, 7, 6, 68, 70, 75, 80, 16, 18, 68, 16, 17, 7, 1, 1, 64, 67, 66, 71, 79, 81, 85, 89, 99, 75, 85, 93, 90, 77, 82, 65, 65, 64, 38, 9, 8, 68, 18, 49, 36, 55, 9, 36, 31, 8, 71, 80, 84, 111, 111, 115, 69, 42, 28, 19, 9, 12, 2, 65, 68, 76, 81, 74, 0, 69, 7, 8, 0, 2, 8, 11, 12, 6, 15, 14, 1, 37, 32, 22, 15, 10, 4, 81, 86, 91 }, { 42, 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 74, 20, 25, 53, 14, 13, 66, 9, 18, 0, 70, 65, 80, 96, 4, 11, 114, 119, 120, 20, 67, 68, 9, 18, 0, 75, 64, 13, 2, 0, 70, 76, 65, 79, 76, 91, 5, 70, 75, 0, 76, 75, 88, 9, 64, 66, 70, 4, 4, 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 52, 7, 69, 96, 78, 82, 2, 68, 78, 70, 83, 1, 78, 75, 72, 92, 93, 93, 95, 22, 64, 7, 80, 64, 78, 71, 72, 72, 86, 78, 90, 15, 72, 0, 85, 80, 67, 71, 2, 8, 3, 1, 14, 11, 75, 65, 1, 65, 2, 74, 12, 64, 10, 19, 6, 17, 17, 10, 73, 11, 1, 69, 1, 87, 84, 72, 77, 67, 72, 69, 8, 67, 70, 0, 4, 71, 72, 64, 91, 87, 72, 95, 67, 75, 8, 65, 73, 18, 1, 65, 75, 19, 73, 83, 1, 93, 20, 27, 24, 22, 27, 21, 8, 24, 24, 69, 6, 11, 4, 1, 73, 4, 64, 66, 2, 67, 74, 1, 67, 88, 74, 73, 81, 76, 89, 35, 41, 33, 34, 33, 27, 23, 27, 19, 12, 15, 6, 68, 69, 91, 15, 12, 69, 12, 4, 3, 65, 70, 73, 76, 79, 92, 73, 85, 98, 89, 100, 65, 32, 23, 15, 5, 5, 64, 68, 72, 74, 6, 46, 30, 23, 17, 28, 10, 4, 66, 67, 70, 36, 22, 12, 6, 12, 2, 67, 71, 79, 77, 16, 4, 1, 66, 1, 72, 77, 82, 0, 40, 23, 14, 7, 16, 3, 64, 66, 71, 62, 82, 73, 64, 66, 69, 1, 7, 8, 65, 7, 13, 14, 77, 71, 73, 1, 14, 0, 65, 7, 13, 18, 72, 64, 12, 14, 80, 76, 80, 82, 77, 67, 75, 70, 72, 68, 64, 73, 71, 70, 65, 76, 74, 81, 86, 69, 98, 76, 64, 70, 8, 69, 65, 16, 67, 69, 66, 12, 74, 73, 91, 30, 34, 35, 28, 18, 29, 25, 20, 19, 17, 16, 12, 69, 70, 77, 3, 4, 67, 83, 68, 69, 75, 75, 75, 73, 74, 88, 85, 87, 33, 32, 31, 26, 16, 20, 17, 6, 11, 5, 4, 70, 72, 76, 81, 14, 16, 69, 14, 15, 5, 64, 64, 66, 69, 68, 73, 81, 83, 87, 91, 100, 77, 87, 95, 89, 75, 81, 65, 64, 0, 40, 10, 9, 68, 19, 51, 37, 57, 9, 35, 29, 6, 74, 82, 87, 113, 113, 116, 69, 42, 28, 19, 9, 12, 2, 65, 68, 75, 80, 73, 1, 68, 8, 9, 1, 3, 9, 12, 13, 7, 16, 15, 1, 35, 31, 21, 14, 8, 2, 83, 88, 92 }, { 41, 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 76, 18, 24, 53, 14, 15, 66, 10, 19, 0, 71, 65, 81, 97, 4, 9, 115, 120, 120, 23, 66, 68, 10, 19, 0, 74, 0, 14, 2, 0, 69, 75, 66, 80, 76, 91, 5, 70, 75, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 52, 7, 69, 95, 77, 82, 3, 67, 76, 68, 82, 2, 76, 74, 70, 91, 92, 92, 94, 23, 64, 8, 79, 64, 78, 71, 70, 72, 85, 77, 89, 15, 72, 1, 83, 79, 66, 71, 2, 8, 3, 2, 15, 12, 76, 65, 1, 65, 2, 74, 12, 65, 10, 19, 5, 17, 17, 10, 75, 11, 0, 71, 0, 88, 83, 71, 76, 66, 71, 69, 9, 67, 69, 1, 5, 71, 72, 64, 92, 87, 71, 95, 67, 75, 9, 65, 74, 19, 1, 65, 76, 20, 73, 84, 1, 94, 18, 26, 23, 21, 26, 20, 8, 23, 23, 70, 5, 11, 4, 1, 74, 3, 65, 67, 1, 68, 75, 0, 67, 90, 75, 73, 82, 77, 89, 34, 39, 32, 32, 31, 25, 21, 25, 16, 9, 13, 3, 70, 72, 93, 13, 10, 70, 10, 2, 0, 68, 72, 76, 78, 81, 95, 74, 87, 99, 90, 100, 65, 33, 23, 15, 5, 5, 64, 68, 72, 74, 6, 46, 30, 23, 17, 29, 10, 4, 65, 67, 69, 37, 22, 13, 6, 13, 2, 66, 71, 78, 76, 17, 5, 2, 66, 2, 71, 76, 81, 0, 40, 23, 14, 7, 17, 3, 0, 65, 70, 62, 81, 72, 64, 66, 69, 1, 7, 8, 65, 7, 13, 14, 77, 71, 73, 1, 15, 64, 66, 6, 12, 18, 73, 65, 12, 14, 80, 77, 80, 81, 76, 66, 74, 70, 71, 68, 0, 73, 71, 70, 64, 76, 74, 81, 85, 68, 98, 76, 64, 70, 9, 69, 65, 17, 67, 69, 66, 13, 75, 73, 92, 29, 33, 34, 27, 17, 28, 24, 19, 18, 16, 15, 11, 70, 71, 78, 1, 3, 68, 85, 70, 70, 76, 75, 76, 73, 74, 88, 85, 86, 31, 30, 29, 24, 13, 18, 15, 3, 9, 3, 2, 73, 74, 78, 82, 12, 15, 71, 12, 13, 3, 66, 66, 68, 71, 70, 75, 84, 85, 89, 93, 101, 79, 89, 96, 88, 74, 80, 64, 0, 1, 41, 11, 10, 68, 20, 52, 39, 58, 10, 33, 28, 3, 77, 85, 90, 116, 115, 117, 69, 42, 28, 19, 9, 12, 2, 65, 68, 75, 79, 72, 2, 67, 9, 9, 1, 4, 9, 12, 13, 7, 16, 15, 1, 34, 29, 19, 12, 6, 0, 85, 90, 93 }, { 40, 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 77, 17, 23, 53, 14, 17, 66, 11, 20, 0, 72, 65, 82, 98, 3, 7, 117, 121, 121, 26, 65, 68, 11, 20, 0, 74, 1, 14, 1, 0, 69, 74, 66, 80, 76, 91, 5, 70, 74, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 52, 7, 69, 94, 76, 82, 3, 66, 74, 66, 80, 3, 74, 73, 68, 90, 91, 91, 93, 24, 64, 9, 78, 64, 77, 70, 68, 72, 85, 77, 88, 15, 72, 1, 82, 79, 65, 71, 2, 8, 3, 3, 15, 12, 76, 65, 1, 65, 2, 74, 12, 65, 9, 19, 4, 17, 17, 10, 77, 11, 0, 73, 64, 89, 82, 70, 75, 65, 70, 68, 10, 66, 69, 2, 6, 71, 72, 64, 93, 86, 70, 95, 67, 75, 10, 65, 75, 20, 1, 65, 77, 21, 74, 85, 1, 95, 17, 25, 23, 20, 25, 19, 7, 22, 23, 71, 4, 11, 4, 1, 74, 2, 66, 68, 0, 69, 76, 64, 68, 92, 76, 74, 83, 78, 89, 32, 38, 30, 30, 29, 23, 19, 23, 14, 7, 11, 1, 73, 74, 96, 11, 8, 72, 8, 0, 65, 70, 74, 78, 81, 83, 97, 75, 89, 100, 91, 100, 65, 33, 23, 15, 5, 5, 64, 68, 72, 73, 6, 46, 30, 23, 17, 30, 11, 5, 65, 66, 68, 38, 23, 13, 6, 14, 3, 66, 70, 77, 75, 18, 6, 2, 66, 3, 70, 75, 80, 1, 40, 23, 14, 7, 18, 4, 1, 64, 69, 62, 81, 72, 0, 66, 69, 1, 7, 8, 65, 7, 13, 15, 77, 71, 73, 0, 15, 65, 67, 5, 11, 18, 74, 66, 12, 14, 81, 78, 81, 80, 75, 65, 74, 69, 71, 67, 1, 73, 71, 70, 64, 77, 74, 81, 85, 67, 98, 76, 64, 70, 10, 69, 65, 18, 67, 69, 66, 14, 76, 73, 93, 28, 33, 34, 26, 16, 27, 23, 18, 17, 15, 14, 10, 71, 72, 79, 0, 2, 70, 87, 71, 71, 77, 76, 76, 73, 74, 88, 86, 85, 29, 28, 27, 22, 10, 16, 13, 1, 7, 1, 0, 75, 76, 79, 83, 10, 13, 72, 10, 11, 1, 68, 68, 70, 73, 72, 77, 86, 87, 91, 95, 102, 81, 91, 98, 87, 73, 79, 0, 1, 2, 43, 12, 11, 68, 21, 54, 40, 60, 11, 32, 26, 1, 80, 88, 93, 119, 117, 118, 69, 42, 28, 19, 9, 12, 2, 65, 68, 74, 78, 71, 3, 66, 10, 10, 2, 5, 10, 13, 14, 7, 17, 15, 1, 33, 28, 17, 10, 4, 65, 87, 92, 94 }, { 38, 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 79, 15, 21, 52, 14, 19, 67, 11, 20, 64, 73, 65, 84, 100, 2, 5, 119, 122, 122, 28, 64, 69, 11, 20, 64, 74, 2, 14, 0, 0, 69, 74, 67, 81, 76, 92, 5, 70, 74, 64, 77, 75, 88, 10, 64, 66, 70, 5, 3, 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 52, 6, 69, 93, 76, 82, 3, 65, 73, 65, 79, 4, 73, 72, 67, 89, 91, 90, 93, 24, 64, 9, 78, 64, 77, 70, 67, 72, 85, 77, 87, 15, 72, 1, 81, 79, 65, 71, 2, 8, 3, 3, 15, 12, 77, 66, 1, 66, 2, 74, 11, 66, 8, 19, 3, 16, 17, 9, 79, 10, 64, 75, 65, 90, 82, 70, 75, 65, 70, 68, 11, 66, 69, 2, 7, 72, 72, 64, 95, 86, 70, 95, 67, 76, 10, 65, 76, 20, 1, 65, 79, 21, 75, 86, 1, 96, 15, 24, 22, 19, 24, 18, 6, 21, 22, 73, 3, 10, 3, 1, 75, 1, 67, 69, 64, 70, 77, 65, 69, 94, 78, 75, 85, 80, 89, 30, 36, 28, 28, 27, 20, 16, 20, 11, 4, 8, 65, 76, 77, 99, 9, 6, 74, 5, 66, 68, 73, 77, 81, 84, 86, 100, 76, 91, 102, 92, 101, 65, 33, 23, 15, 5, 5, 65, 68, 72, 73, 6, 46, 30, 23, 17, 31, 11, 5, 65, 66, 68, 38, 23, 13, 6, 14, 3, 66, 70, 76, 75, 19, 6, 2, 66, 3, 70, 75, 79, 1, 40, 23, 14, 7, 18, 4, 1, 64, 69, 62, 81, 72, 0, 66, 69, 1, 7, 8, 65, 7, 13, 15, 77, 71, 74, 64, 15, 66, 68, 4, 10, 17, 76, 68, 11, 13, 82, 80, 82, 80, 75, 64, 74, 69, 71, 67, 1, 74, 71, 70, 64, 78, 74, 81, 85, 67, 99, 76, 64, 70, 10, 70, 65, 18, 68, 70, 66, 14, 77, 74, 94, 27, 32, 33, 25, 14, 26, 22, 17, 16, 14, 13, 9, 72, 74, 81, 65, 0, 72, 89, 73, 73, 78, 77, 77, 73, 74, 88, 87, 85, 26, 25, 25, 19, 7, 13, 10, 65, 4, 65, 66, 78, 79, 81, 84, 8, 11, 74, 8, 8, 65, 71, 71, 73, 75, 75, 80, 89, 89, 94, 97, 104, 83, 93, 100, 86, 72, 78, 0, 1, 2, 44, 12, 11, 68, 21, 55, 41, 61, 11, 30, 24, 65, 84, 91, 96, 122, 120, 120, 69, 42, 27, 18, 9, 12, 2, 66, 68, 74, 78, 71, 3, 66, 11, 10, 2, 5, 10, 13, 14, 7, 17, 15, 0, 31, 26, 15, 8, 2, 67, 90, 94, 95 }, { 37, 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 80, 14, 20, 52, 14, 22, 67, 12, 21, 64, 73, 64, 85, 101, 2, 4, 120, 123, 122, 31, 1, 69, 12, 21, 64, 73, 4, 15, 0, 1, 68, 73, 67, 81, 75, 92, 5, 69, 73, 64, 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 52, 6, 69, 91, 75, 82, 4, 0, 71, 0, 77, 6, 71, 70, 65, 87, 90, 89, 92, 25, 0, 10, 77, 0, 76, 69, 65, 71, 84, 76, 85, 16, 71, 2, 79, 78, 64, 70, 3, 9, 4, 4, 16, 13, 77, 66, 2, 66, 2, 73, 11, 66, 8, 19, 3, 16, 17, 9, 80, 10, 64, 76, 66, 90, 81, 69, 74, 64, 69, 67, 12, 65, 68, 3, 9, 72, 72, 0, 96, 85, 69, 95, 66, 76, 11, 64, 76, 21, 1, 65, 80, 22, 75, 87, 1, 96, 14, 24, 22, 19, 24, 18, 6, 21, 22, 74, 3, 10, 3, 1, 75, 1, 67, 69, 64, 70, 78, 65, 69, 95, 79, 75, 86, 81, 89, 29, 35, 27, 27, 26, 18, 14, 18, 9, 2, 6, 67, 78, 79, 101, 8, 5, 75, 3, 68, 70, 75, 79, 83, 86, 88, 102, 76, 92, 103, 92, 101, 64, 34, 24, 16, 6, 5, 65, 67, 71, 72, 7, 47, 31, 24, 17, 32, 12, 6, 64, 65, 67, 39, 24, 14, 7, 15, 4, 65, 69, 74, 74, 21, 7, 3, 65, 4, 69, 74, 77, 2, 41, 24, 15, 8, 19, 5, 2, 0, 68, 62, 80, 71, 1, 66, 68, 1, 8, 9, 64, 8, 14, 16, 76, 71, 74, 64, 16, 66, 68, 3, 10, 17, 77, 69, 11, 13, 82, 81, 82, 79, 74, 0, 73, 68, 70, 66, 2, 74, 70, 69, 0, 78, 73, 80, 84, 66, 99, 76, 0, 70, 11, 70, 65, 19, 68, 70, 65, 15, 77, 74, 94, 27, 32, 33, 25, 13, 26, 22, 17, 16, 14, 13, 9, 72, 75, 82, 66, 64, 73, 90, 74, 74, 78, 77, 77, 72, 73, 87, 87, 84, 24, 23, 23, 17, 5, 11, 8, 67, 2, 67, 68, 80, 81, 82, 84, 7, 10, 75, 7, 6, 67, 73, 73, 75, 77, 77, 82, 91, 90, 96, 98, 105, 84, 94, 101, 84, 70, 76, 1, 2, 3, 46, 13, 12, 68, 22, 57, 43, 62, 12, 29, 23, 67, 87, 93, 98, 124, 122, 121, 68, 43, 27, 18, 9, 13, 2, 66, 68, 73, 77, 70, 4, 65, 13, 11, 3, 6, 11, 14, 15, 8, 18, 16, 0, 30, 25, 14, 7, 1, 68, 92, 95, 95 }, { 36, 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 81, 13, 19, 52, 14, 24, 67, 13, 22, 64, 74, 64, 86, 102, 1, 2, 122, 124, 123, 34, 2, 69, 13, 22, 64, 73, 5, 15, 64, 1, 68, 72, 67, 81, 75, 92, 5, 69, 72, 64, 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 52, 6, 69, 90, 74, 82, 4, 1, 69, 2, 76, 7, 69, 69, 0, 86, 89, 88, 91, 26, 0, 11, 76, 0, 76, 68, 0, 71, 84, 76, 84, 16, 71, 2, 78, 78, 0, 70, 3, 9, 4, 5, 16, 13, 78, 66, 2, 66, 2, 73, 11, 66, 7, 19, 2, 16, 17, 9, 82, 10, 64, 78, 67, 91, 80, 68, 73, 0, 68, 66, 13, 64, 68, 4, 10, 72, 72, 0, 97, 85, 68, 95, 66, 76, 12, 64, 77, 22, 1, 65, 81, 23, 76, 88, 1, 97, 12, 23, 21, 18, 23, 17, 5, 20, 22, 75, 2, 10, 3, 1, 75, 0, 68, 70, 65, 71, 79, 66, 70, 97, 80, 76, 87, 82, 89, 27, 34, 25, 25, 24, 16, 12, 16, 6, 0, 4, 70, 81, 81, 104, 6, 3, 77, 1, 70, 72, 77, 81, 85, 89, 90, 104, 77, 94, 104, 93, 101, 64, 34, 24, 16, 6, 5, 65, 67, 71, 71, 7, 47, 31, 24, 17, 33, 12, 7, 64, 64, 66, 40, 25, 14, 7, 16, 4, 65, 68, 73, 73, 22, 8, 3, 65, 5, 68, 73, 76, 2, 41, 24, 15, 8, 20, 6, 3, 1, 67, 62, 80, 71, 1, 66, 68, 1, 8, 9, 64, 8, 14, 17, 76, 71, 74, 65, 16, 67, 69, 2, 9, 17, 78, 70, 11, 13, 83, 82, 83, 78, 73, 1, 73, 68, 70, 65, 3, 74, 70, 69, 0, 79, 73, 80, 84, 65, 99, 76, 0, 70, 12, 70, 65, 20, 68, 70, 65, 16, 78, 74, 95, 26, 32, 33, 24, 12, 25, 21, 16, 15, 13, 12, 8, 73, 76, 83, 68, 65, 75, 92, 75, 75, 79, 78, 77, 72, 73, 87, 88, 83, 22, 21, 21, 15, 2, 9, 6, 70, 0, 69, 70, 82, 83, 83, 85, 5, 8, 76, 5, 4, 69, 75, 75, 77, 79, 79, 84, 93, 92, 98, 100, 106, 86, 96, 103, 83, 69, 75, 2, 3, 4, 48, 14, 13, 68, 23, 58, 44, 62, 13, 28, 21, 70, 90, 96, 101, 126, 124, 122, 68, 43, 27, 18, 9, 13, 2, 66, 68, 72, 76, 69, 5, 64, 14, 12, 4, 7, 12, 15, 16, 8, 19, 16, 0, 29, 23, 12, 5, 64, 70, 94, 97, 96 }, { 35, 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 83, 11, 18, 52, 14, 26, 67, 14, 23, 64, 75, 64, 87, 103, 1, 0, 123, 125, 123, 37, 3, 69, 14, 23, 64, 72, 6, 16, 64, 1, 67, 71, 68, 82, 75, 92, 5, 69, 72, 64, 77, 74, 88, 12, 0, 65, 69, 7, 3, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 52, 6, 69, 89, 73, 82, 5, 2, 67, 4, 74, 8, 67, 68, 2, 85, 88, 87, 90, 27, 0, 12, 75, 0, 75, 68, 2, 71, 83, 75, 83, 16, 71, 3, 76, 77, 1, 70, 3, 9, 4, 6, 17, 14, 78, 66, 2, 66, 2, 73, 11, 67, 7, 19, 1, 16, 17, 9, 84, 10, 65, 80, 68, 92, 79, 67, 72, 1, 67, 66, 14, 64, 67, 5, 11, 72, 72, 0, 98, 84, 67, 95, 66, 76, 13, 64, 78, 23, 1, 65, 82, 24, 76, 89, 1, 98, 11, 22, 21, 17, 22, 16, 5, 19, 21, 76, 1, 10, 3, 1, 76, 64, 69, 71, 66, 72, 80, 67, 70, 99, 81, 76, 88, 83, 89, 26, 32, 24, 23, 22, 14, 10, 14, 4, 66, 2, 72, 83, 84, 106, 4, 1, 78, 64, 72, 75, 80, 83, 88, 91, 92, 107, 78, 96, 105, 94, 101, 64, 35, 24, 16, 6, 5, 65, 67, 71, 71, 7, 47, 31, 24, 17, 34, 13, 7, 0, 64, 65, 41, 25, 15, 7, 17, 5, 64, 68, 72, 72, 23, 9, 4, 65, 6, 67, 72, 75, 3, 41, 24, 15, 8, 21, 6, 4, 2, 66, 62, 79, 70, 2, 66, 68, 1, 8, 9, 64, 8, 14, 17, 76, 71, 74, 65, 17, 68, 70, 1, 8, 17, 79, 71, 11, 13, 83, 83, 83, 77, 72, 2, 72, 67, 69, 65, 4, 74, 70, 69, 1, 79, 73, 80, 83, 64, 99, 76, 0, 70, 13, 70, 65, 21, 68, 70, 65, 17, 79, 74, 96, 25, 31, 32, 23, 11, 24, 20, 15, 14, 12, 11, 7, 74, 77, 84, 69, 66, 76, 94, 77, 76, 80, 78, 78, 72, 73, 87, 88, 82, 20, 19, 19, 13, 64, 7, 4, 72, 65, 71, 72, 85, 85, 85, 86, 3, 7, 78, 3, 2, 71, 77, 77, 79, 81, 81, 86, 96, 94, 100, 102, 107, 88, 98, 104, 82, 68, 74, 3, 4, 5, 49, 15, 14, 68, 24, 60, 46, 62, 14, 26, 20, 72, 93, 99, 104, 126, 126, 123, 68, 43, 27, 18, 9, 13, 2, 66, 68, 72, 75, 68, 6, 0, 15, 12, 4, 8, 12, 15, 16, 8, 19, 16, 0, 28, 22, 10, 3, 66, 72, 96, 99, 97 }, { 33, 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 84, 10, 16, 52, 14, 29, 68, 15, 23, 64, 76, 64, 88, 104, 0, 65, 125, 126, 124, 40, 4, 69, 15, 23, 64, 72, 7, 16, 65, 1, 67, 71, 68, 82, 74, 92, 5, 69, 71, 65, 78, 74, 88, 12, 1, 65, 68, 7, 3, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 52, 6, 69, 88, 72, 82, 5, 4, 66, 6, 73, 9, 66, 66, 4, 84, 88, 86, 90, 27, 0, 13, 74, 0, 75, 67, 4, 71, 83, 75, 82, 16, 70, 3, 75, 77, 1, 70, 4, 9, 4, 6, 17, 14, 79, 67, 2, 66, 2, 73, 11, 67, 6, 19, 1, 16, 17, 8, 86, 10, 65, 82, 69, 93, 78, 66, 71, 2, 67, 65, 15, 0, 67, 6, 12, 72, 72, 1, 99, 84, 66, 95, 66, 76, 13, 64, 79, 24, 1, 65, 84, 25, 77, 90, 1, 99, 9, 21, 20, 16, 21, 15, 4, 18, 21, 78, 0, 9, 3, 1, 76, 65, 69, 72, 66, 73, 81, 68, 71, 101, 82, 77, 89, 84, 89, 24, 31, 22, 21, 20, 12, 7, 12, 1, 68, 0, 75, 86, 86, 109, 2, 0, 80, 67, 74, 77, 82, 86, 90, 94, 94, 109, 79, 97, 107, 95, 101, 64, 35, 25, 16, 6, 5, 65, 67, 71, 70, 8, 48, 31, 24, 17, 35, 13, 8, 0, 0, 65, 42, 26, 15, 7, 17, 5, 64, 67, 71, 72, 24, 9, 4, 65, 7, 66, 72, 74, 3, 42, 24, 15, 8, 22, 7, 4, 3, 65, 62, 79, 70, 2, 66, 68, 1, 8, 9, 0, 8, 14, 18, 76, 71, 74, 66, 17, 69, 71, 0, 7, 17, 81, 72, 10, 13, 84, 84, 84, 77, 72, 3, 72, 67, 69, 64, 5, 75, 70, 69, 1, 80, 73, 80, 83, 0, 99, 76, 1, 70, 13, 70, 65, 22, 68, 71, 65, 18, 79, 75, 97, 24, 31, 32, 22, 10, 23, 19, 14, 13, 11, 10, 6, 75, 78, 85, 71, 68, 78, 96, 78, 77, 81, 79, 78, 72, 73, 87, 89, 82, 17, 16, 17, 10, 67, 5, 2, 75, 67, 73, 74, 87, 87, 86, 87, 1, 5, 79, 1, 0, 73, 79, 80, 81, 83, 83, 88, 98, 96, 102, 104, 108, 90, 100, 106, 81, 67, 73, 3, 5, 6, 51, 16, 15, 68, 25, 61, 47, 62, 14, 25, 18, 75, 96, 102, 107, 126, 126, 124, 68, 43, 27, 18, 9, 13, 2, 66, 68, 71, 74, 67, 7, 0, 16, 13, 5, 9, 13, 16, 17, 9, 20, 17, 0, 26, 20, 8, 1, 68, 74, 98, 101, 98 }, { 32, 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 86, 8, 15, 52, 14, 31, 68, 16, 24, 64, 76, 0, 89, 105, 0, 67, 126, 126, 124, 43, 6, 69, 16, 24, 64, 72, 8, 16, 65, 1, 67, 70, 68, 83, 74, 92, 5, 69, 70, 65, 78, 74, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 52, 6, 69, 87, 71, 82, 6, 5, 64, 8, 71, 10, 64, 65, 6, 83, 87, 85, 89, 28, 0, 14, 73, 0, 74, 66, 6, 71, 83, 74, 80, 17, 70, 4, 74, 76, 2, 70, 4, 10, 5, 7, 18, 15, 79, 67, 2, 66, 2, 73, 11, 68, 5, 19, 0, 16, 17, 8, 88, 10, 66, 83, 70, 94, 77, 65, 70, 3, 66, 64, 16, 1, 66, 7, 13, 72, 72, 1, 100, 83, 65, 95, 65, 76, 14, 64, 80, 25, 1, 65, 85, 26, 77, 91, 1, 99, 8, 20, 20, 15, 20, 14, 3, 18, 21, 79, 64, 9, 3, 1, 77, 65, 70, 73, 67, 74, 82, 68, 72, 103, 83, 78, 90, 85, 89, 22, 30, 20, 19, 19, 10, 5, 10, 64, 70, 65, 77, 88, 88, 111, 0, 65, 82, 69, 76, 79, 84, 88, 92, 97, 96, 112, 80, 99, 108, 95, 101, 64, 35, 25, 16, 6, 5, 65, 67, 71, 70, 8, 48, 31, 24, 17, 36, 14, 9, 0, 1, 64, 43, 27, 15, 8, 18, 6, 0, 67, 70, 71, 26, 10, 4, 64, 8, 65, 71, 73, 4, 42, 25, 15, 8, 23, 8, 5, 4, 64, 62, 79, 70, 3, 66, 68, 1, 8, 9, 0, 8, 15, 18, 76, 71, 74, 67, 18, 70, 72, 64, 7, 17, 82, 73, 10, 13, 85, 85, 84, 76, 71, 4, 71, 66, 68, 64, 6, 75, 70, 69, 1, 81, 73, 80, 82, 1, 99, 76, 1, 70, 14, 70, 65, 23, 68, 71, 65, 19, 80, 75, 97, 23, 31, 31, 22, 9, 22, 18, 13, 13, 10, 9, 5, 76, 79, 86, 72, 69, 79, 98, 79, 78, 82, 80, 79, 72, 72, 87, 89, 81, 15, 14, 15, 8, 69, 3, 0, 77, 69, 75, 76, 89, 89, 88, 88, 64, 3, 80, 64, 65, 75, 81, 82, 83, 85, 85, 90, 101, 98, 104, 106, 109, 92, 102, 107, 80, 65, 72, 4, 6, 7, 53, 17, 16, 68, 26, 62, 49, 62, 15, 23, 16, 77, 99, 104, 110, 126, 126, 125, 68, 43, 27, 18, 9, 13, 2, 66, 68, 71, 73, 66, 8, 1, 17, 14, 5, 10, 14, 17, 18, 9, 20, 17, 0, 25, 19, 7, 0, 70, 76, 100, 103, 99 }, { 31, 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 87, 7, 14, 52, 14, 33, 68, 16, 25, 64, 77, 0, 90, 106, 64, 68, 126, 126, 125, 46, 7, 69, 16, 25, 64, 71, 9, 17, 66, 2, 66, 69, 69, 83, 74, 92, 5, 68, 70, 65, 78, 73, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 52, 6, 69, 86, 71, 82, 6, 6, 1, 10, 70, 11, 1, 64, 8, 82, 86, 84, 88, 29, 0, 15, 73, 1, 74, 66, 8, 71, 82, 74, 79, 17, 70, 4, 72, 76, 3, 69, 4, 10, 5, 8, 18, 15, 80, 67, 2, 66, 2, 73, 11, 68, 5, 19, 64, 16, 17, 8, 90, 10, 66, 85, 71, 95, 77, 64, 70, 3, 65, 64, 17, 1, 66, 7, 15, 72, 72, 1, 101, 83, 64, 95, 65, 76, 15, 0, 81, 25, 1, 65, 86, 27, 78, 92, 1, 100, 6, 19, 19, 15, 19, 14, 3, 17, 20, 80, 65, 9, 3, 1, 77, 66, 71, 74, 68, 74, 83, 69, 72, 105, 84, 78, 91, 86, 89, 21, 28, 19, 17, 17, 8, 3, 8, 67, 73, 67, 80, 91, 91, 114, 65, 67, 83, 71, 79, 82, 87, 90, 95, 99, 99, 114, 81, 101, 109, 96, 101, 0, 36, 25, 17, 6, 5, 65, 67, 71, 69, 8, 48, 32, 24, 17, 37, 14, 9, 1, 1, 0, 44, 27, 16, 8, 19, 6, 0, 66, 69, 70, 27, 11, 5, 64, 8, 65, 70, 71, 4, 42, 25, 15, 9, 23, 8, 6, 4, 0, 62, 78, 69, 3, 66, 68, 1, 8, 9, 0, 9, 15, 19, 76, 71, 75, 67, 18, 71, 73, 65, 6, 17, 83, 74, 10, 13, 85, 86, 85, 75, 70, 5, 71, 66, 68, 0, 6, 75, 69, 68, 2, 81, 73, 80, 82, 2, 99, 76, 1, 70, 15, 70, 65, 24, 69, 71, 65, 19, 81, 75, 98, 22, 30, 31, 21, 8, 22, 18, 12, 12, 10, 8, 4, 77, 80, 87, 74, 70, 81, 100, 81, 80, 83, 80, 79, 72, 72, 86, 90, 80, 13, 12, 13, 6, 72, 1, 65, 80, 71, 78, 78, 92, 91, 89, 88, 65, 2, 82, 66, 68, 77, 83, 84, 85, 87, 88, 92, 103, 100, 106, 107, 111, 94, 104, 109, 79, 64, 71, 5, 7, 8, 54, 18, 17, 68, 27, 62, 50, 62, 16, 22, 15, 80, 102, 107, 112, 126, 126, 126, 67, 43, 27, 18, 9, 13, 2, 66, 68, 70, 72, 66, 9, 2, 18, 14, 6, 11, 14, 17, 18, 9, 21, 17, 0, 24, 17, 5, 65, 71, 77, 103, 104, 100 }, { 30, 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 89, 5, 12, 52, 14, 36, 69, 17, 25, 64, 78, 0, 91, 107, 64, 70, 126, 126, 125, 49, 8, 69, 17, 25, 64, 71, 10, 17, 66, 2, 66, 69, 69, 84, 73, 92, 5, 68, 69, 66, 78, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 52, 6, 69, 85, 70, 82, 7, 8, 2, 12, 68, 12, 2, 1, 10, 81, 85, 83, 88, 30, 0, 16, 72, 1, 73, 65, 10, 71, 82, 73, 78, 17, 69, 5, 71, 75, 3, 69, 5, 10, 5, 9, 19, 16, 80, 68, 2, 66, 2, 73, 11, 69, 4, 19, 64, 16, 17, 7, 92, 10, 67, 87, 72, 96, 76, 0, 69, 4, 64, 0, 18, 2, 65, 8, 16, 72, 72, 2, 102, 82, 0, 95, 65, 76, 15, 0, 82, 26, 1, 65, 88, 28, 78, 93, 1, 101, 5, 18, 19, 14, 18, 13, 2, 16, 20, 81, 66, 9, 3, 1, 78, 67, 71, 75, 68, 75, 84, 70, 73, 107, 85, 79, 92, 87, 89, 19, 27, 17, 15, 15, 6, 0, 6, 69, 75, 69, 82, 93, 93, 116, 67, 68, 85, 74, 81, 84, 89, 93, 97, 102, 101, 117, 82, 102, 111, 97, 101, 0, 36, 26, 17, 6, 5, 65, 67, 71, 69, 9, 49, 32, 24, 17, 38, 15, 10, 1, 2, 1, 45, 28, 16, 8, 20, 7, 1, 66, 68, 70, 28, 11, 5, 64, 9, 64, 70, 70, 5, 43, 25, 15, 9, 24, 9, 7, 5, 1, 62, 78, 69, 4, 66, 68, 1, 8, 9, 1, 9, 15, 19, 76, 71, 75, 68, 19, 72, 74, 66, 5, 17, 84, 75, 9, 13, 86, 87, 85, 74, 70, 6, 70, 65, 67, 0, 7, 75, 69, 68, 2, 82, 73, 80, 81, 3, 99, 76, 2, 70, 15, 70, 65, 25, 69, 71, 65, 20, 81, 76, 99, 21, 30, 30, 20, 7, 21, 17, 11, 11, 9, 7, 3, 78, 81, 88, 75, 72, 82, 102, 82, 81, 84, 81, 80, 72, 72, 86, 90, 79, 11, 10, 11, 3, 75, 64, 67, 82, 73, 80, 80, 94, 93, 91, 89, 67, 0, 83, 68, 70, 79, 85, 86, 87, 89, 90, 94, 106, 102, 108, 109, 112, 96, 106, 110, 78, 0, 70, 5, 8, 9, 56, 19, 18, 68, 28, 62, 52, 62, 16, 20, 13, 82, 105, 110, 115, 126, 126, 126, 67, 43, 27, 18, 9, 13, 2, 66, 68, 70, 71, 65, 10, 3, 19, 15, 6, 12, 15, 18, 19, 10, 21, 18, 0, 22, 16, 3, 67, 73, 79, 105, 106, 101 }, { 28, 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 90, 4, 11, 52, 14, 38, 69, 18, 26, 64, 79, 0, 92, 109, 65, 72, 126, 126, 126, 51, 9, 69, 18, 26, 64, 71, 11, 17, 67, 2, 66, 68, 70, 84, 73, 93, 5, 68, 69, 66, 79, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 52, 6, 69, 84, 69, 82, 7, 9, 4, 14, 67, 13, 4, 2, 11, 80, 85, 82, 87, 30, 0, 17, 71, 1, 73, 65, 11, 71, 82, 73, 77, 17, 69, 5, 70, 75, 4, 69, 5, 10, 5, 9, 19, 16, 81, 68, 2, 66, 2, 73, 11, 69, 3, 19, 65, 16, 17, 7, 94, 10, 67, 89, 73, 97, 75, 1, 68, 5, 64, 0, 19, 2, 65, 9, 17, 72, 72, 2, 104, 82, 1, 95, 65, 77, 16, 0, 83, 27, 1, 65, 89, 29, 79, 94, 1, 102, 3, 17, 18, 13, 17, 12, 1, 15, 19, 83, 67, 8, 3, 1, 78, 68, 72, 76, 69, 76, 85, 71, 74, 109, 87, 80, 93, 88, 89, 17, 25, 15, 13, 13, 4, 65, 4, 72, 78, 71, 85, 96, 96, 119, 69, 70, 87, 76, 83, 87, 92, 95, 100, 105, 103, 119, 83, 104, 112, 98, 102, 0, 36, 26, 17, 6, 5, 65, 67, 71, 68, 9, 49, 32, 24, 17, 39, 15, 10, 1, 2, 1, 46, 28, 16, 8, 20, 7, 1, 65, 67, 69, 29, 12, 5, 64, 10, 0, 69, 69, 5, 43, 25, 15, 9, 25, 9, 7, 6, 1, 62, 78, 69, 4, 66, 68, 1, 8, 9, 1, 9, 15, 20, 76, 71, 75, 69, 19, 73, 75, 67, 4, 17, 86, 77, 9, 13, 87, 88, 86, 74, 69, 7, 70, 65, 67, 1, 8, 76, 69, 68, 2, 83, 73, 80, 81, 3, 100, 76, 2, 70, 16, 70, 65, 25, 69, 72, 65, 21, 82, 76, 100, 20, 29, 30, 19, 5, 20, 16, 10, 10, 8, 6, 2, 79, 82, 89, 77, 73, 84, 104, 84, 82, 85, 82, 80, 72, 72, 86, 91, 79, 8, 7, 9, 1, 78, 67, 70, 85, 75, 82, 82, 97, 95, 92, 90, 69, 65, 85, 70, 72, 82, 88, 89, 90, 91, 92, 97, 108, 104, 111, 111, 113, 98, 108, 112, 77, 1, 69, 6, 9, 9, 57, 20, 18, 68, 28, 62, 53, 62, 17, 19, 11, 85, 108, 113, 118, 126, 126, 126, 67, 43, 27, 18, 9, 13, 2, 66, 68, 69, 71, 64, 11, 3, 20, 15, 7, 12, 15, 18, 19, 10, 22, 18, 64, 21, 14, 1, 69, 75, 81, 107, 108, 102 }, { 27, 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 91, 3, 10, 52, 14, 40, 69, 19, 27, 64, 79, 1, 93, 110, 66, 74, 126, 126, 126, 54, 11, 69, 19, 27, 64, 70, 12, 18, 68, 2, 65, 67, 70, 84, 73, 93, 5, 68, 68, 66, 79, 73, 88, 14, 2, 0, 67, 9, 3, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 52, 6, 69, 83, 68, 82, 7, 10, 6, 16, 65, 15, 6, 3, 13, 79, 84, 81, 86, 31, 1, 18, 70, 1, 72, 64, 13, 71, 81, 73, 75, 18, 69, 5, 68, 75, 5, 69, 5, 11, 6, 10, 19, 16, 81, 68, 3, 66, 2, 72, 11, 69, 3, 19, 66, 16, 17, 7, 96, 10, 67, 90, 74, 97, 74, 2, 67, 6, 0, 1, 20, 3, 65, 10, 18, 72, 72, 2, 105, 81, 2, 95, 64, 77, 17, 0, 84, 28, 1, 65, 90, 30, 80, 95, 1, 102, 2, 16, 18, 12, 16, 11, 1, 15, 19, 84, 68, 8, 3, 1, 78, 68, 73, 77, 70, 77, 86, 71, 74, 110, 88, 80, 94, 89, 89, 16, 24, 14, 12, 12, 2, 67, 2, 74, 80, 73, 87, 99, 98, 122, 70, 72, 88, 78, 85, 89, 94, 97, 102, 107, 105, 121, 83, 106, 113, 98, 102, 0, 37, 26, 17, 7, 5, 65, 66, 71, 67, 9, 49, 32, 25, 17, 40, 16, 11, 2, 3, 2, 47, 29, 17, 9, 21, 8, 1, 64, 66, 68, 31, 13, 6, 0, 11, 1, 68, 68, 6, 43, 26, 16, 9, 26, 10, 8, 7, 2, 62, 77, 68, 5, 66, 68, 1, 9, 10, 1, 9, 16, 21, 76, 71, 75, 69, 19, 74, 75, 68, 4, 17, 87, 78, 9, 13, 87, 89, 87, 73, 68, 8, 70, 64, 67, 2, 9, 76, 69, 68, 3, 83, 73, 80, 81, 4, 100, 76, 2, 70, 17, 70, 65, 26, 69, 72, 65, 22, 83, 76, 100, 19, 29, 30, 19, 4, 19, 15, 9, 10, 7, 5, 2, 79, 83, 90, 78, 74, 86, 106, 85, 83, 85, 82, 80, 71, 71, 86, 92, 78, 6, 5, 7, 64, 80, 69, 72, 87, 77, 84, 84, 99, 97, 93, 91, 71, 66, 86, 72, 74, 84, 90, 91, 92, 93, 94, 99, 110, 105, 113, 113, 114, 100, 110, 114, 76, 3, 67, 7, 10, 10, 59, 21, 19, 68, 29, 62, 54, 62, 18, 18, 10, 87, 111, 115, 121, 126, 126, 126, 67, 43, 27, 18, 9, 14, 2, 66, 68, 68, 70, 0, 12, 4, 22, 16, 8, 13, 16, 19, 20, 10, 23, 18, 64, 20, 13, 0, 70, 77, 83, 109, 110, 102 }, { 26, 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 93, 1, 8, 52, 14, 43, 70, 20, 27, 64, 80, 1, 94, 111, 66, 76, 126, 126, 126, 57, 12, 69, 20, 27, 64, 70, 13, 18, 68, 2, 65, 67, 70, 85, 72, 93, 5, 68, 67, 67, 79, 73, 88, 15, 3, 0, 66, 10, 3, 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 52, 6, 69, 82, 67, 82, 8, 12, 7, 18, 64, 16, 7, 5, 15, 78, 83, 80, 86, 32, 1, 19, 69, 1, 72, 0, 15, 71, 81, 72, 74, 18, 68, 6, 67, 74, 5, 69, 6, 11, 6, 11, 20, 17, 82, 69, 3, 66, 2, 72, 11, 70, 2, 19, 66, 16, 17, 6, 98, 10, 68, 92, 75, 98, 73, 3, 66, 7, 1, 2, 21, 4, 64, 11, 19, 72, 72, 3, 106, 81, 3, 95, 64, 77, 17, 0, 85, 29, 1, 65, 92, 31, 80, 96, 1, 103, 0, 15, 17, 11, 15, 10, 0, 14, 19, 85, 69, 8, 3, 1, 79, 69, 73, 78, 70, 78, 87, 72, 75, 112, 89, 81, 95, 90, 89, 14, 23, 12, 10, 10, 0, 70, 0, 77, 82, 75, 90, 101, 100, 124, 72, 73, 90, 81, 87, 91, 96, 100, 104, 110, 107, 124, 84, 107, 115, 99, 102, 0, 37, 27, 17, 7, 5, 65, 66, 71, 67, 10, 50, 32, 25, 17, 41, 16, 12, 2, 4, 3, 48, 30, 17, 9, 22, 8, 2, 64, 65, 68, 32, 13, 6, 0, 12, 2, 68, 67, 6, 44, 26, 16, 9, 27, 11, 9, 8, 3, 62, 77, 68, 5, 66, 68, 1, 9, 10, 2, 9, 16, 21, 76, 71, 75, 70, 20, 75, 76, 69, 3, 17, 88, 79, 8, 13, 88, 90, 87, 72, 68, 9, 69, 64, 66, 2, 10, 76, 69, 68, 3, 84, 73, 80, 80, 5, 100, 76, 3, 70, 17, 70, 65, 27, 69, 72, 65, 23, 83, 77, 101, 18, 29, 29, 18, 3, 18, 14, 8, 9, 6, 4, 1, 80, 84, 91, 80, 76, 87, 108, 86, 84, 86, 83, 81, 71, 71, 86, 92, 77, 4, 3, 5, 67, 83, 71, 74, 90, 79, 86, 86, 101, 99, 95, 92, 73, 68, 87, 74, 76, 86, 92, 93, 94, 95, 96, 101, 113, 107, 115, 115, 115, 102, 112, 115, 75, 4, 66, 7, 11, 11, 61, 22, 20, 68, 30, 62, 56, 62, 18, 16, 8, 90, 114, 118, 124, 126, 126, 126, 67, 43, 27, 18, 9, 14, 2, 66, 68, 68, 69, 1, 13, 5, 23, 17, 8, 14, 17, 20, 21, 11, 23, 19, 64, 18, 11, 65, 72, 79, 85, 111, 112, 103 }, { 25, 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 94, 0, 7, 52, 14, 45, 70, 20, 28, 64, 81, 1, 95, 112, 67, 77, 126, 126, 126, 60, 13, 69, 20, 28, 64, 69, 14, 19, 69, 3, 64, 66, 71, 85, 72, 93, 5, 67, 67, 67, 79, 72, 88, 15, 3, 0, 66, 10, 3, 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 52, 6, 69, 81, 67, 82, 8, 13, 9, 20, 1, 17, 9, 6, 17, 77, 82, 79, 85, 33, 1, 20, 69, 2, 71, 0, 17, 71, 80, 72, 73, 18, 68, 6, 65, 74, 6, 68, 6, 11, 6, 12, 20, 17, 82, 69, 3, 66, 2, 72, 11, 70, 2, 19, 67, 16, 17, 6, 100, 10, 68, 94, 76, 99, 73, 4, 66, 7, 2, 2, 22, 4, 64, 11, 21, 72, 72, 3, 107, 80, 4, 95, 64, 77, 18, 1, 86, 29, 1, 65, 93, 32, 81, 97, 1, 104, 64, 14, 17, 11, 14, 10, 0, 13, 18, 86, 70, 8, 3, 1, 79, 70, 74, 79, 71, 78, 88, 73, 75, 114, 90, 81, 96, 91, 89, 13, 21, 11, 8, 8, 65, 72, 65, 79, 85, 77, 92, 104, 103, 126, 74, 75, 91, 83, 90, 94, 99, 102, 107, 112, 110, 126, 85, 109, 116, 100, 102, 1, 38, 27, 18, 7, 5, 65, 66, 71, 66, 10, 50, 33, 25, 17, 42, 17, 12, 3, 4, 4, 49, 30, 18, 9, 23, 9, 2, 0, 64, 67, 33, 14, 7, 0, 12, 2, 67, 65, 7, 44, 26, 16, 10, 27, 11, 10, 8, 4, 62, 76, 67, 6, 66, 68, 1, 9, 10, 2, 10, 16, 22, 76, 71, 76, 70, 20, 76, 77, 70, 2, 17, 89, 80, 8, 13, 88, 91, 88, 71, 67, 10, 69, 0, 66, 3, 10, 76, 68, 67, 4, 84, 73, 80, 80, 6, 100, 76, 3, 70, 18, 70, 65, 28, 70, 72, 65, 23, 84, 77, 102, 17, 28, 29, 17, 2, 18, 14, 7, 8, 6, 3, 0, 81, 85, 92, 81, 77, 89, 110, 88, 86, 87, 83, 81, 71, 71, 85, 93, 76, 2, 1, 3, 69, 86, 73, 76, 92, 81, 89, 88, 104, 101, 96, 92, 74, 69, 89, 76, 79, 88, 94, 95, 96, 97, 99, 103, 115, 109, 117, 116, 117, 104, 114, 117, 74, 5, 65, 8, 12, 12, 62, 23, 21, 68, 31, 62, 57, 62, 19, 15, 7, 92, 117, 121, 126, 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, 67, 68, 1, 14, 6, 24, 17, 9, 15, 17, 20, 21, 11, 24, 19, 64, 17, 10, 67, 74, 80, 86, 114, 113, 104 }, { 23, 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 96, 65, 6, 52, 14, 47, 70, 21, 29, 64, 82, 1, 96, 113, 67, 79, 126, 126, 126, 62, 14, 69, 21, 29, 64, 69, 15, 19, 69, 3, 64, 65, 71, 86, 72, 93, 5, 67, 66, 67, 80, 72, 88, 16, 3, 0, 66, 11, 3, 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 52, 6, 69, 80, 66, 82, 9, 14, 11, 22, 2, 18, 11, 7, 19, 76, 82, 78, 84, 33, 1, 21, 68, 2, 71, 1, 19, 71, 80, 71, 72, 18, 68, 7, 64, 73, 7, 68, 6, 11, 6, 12, 21, 18, 83, 69, 3, 66, 2, 72, 11, 71, 1, 19, 68, 16, 17, 6, 102, 10, 69, 96, 77, 100, 72, 5, 65, 8, 2, 3, 23, 5, 0, 12, 22, 72, 72, 3, 108, 80, 5, 95, 64, 77, 19, 1, 87, 30, 1, 65, 94, 33, 81, 98, 1, 105, 66, 13, 16, 10, 13, 9, 64, 12, 18, 88, 71, 7, 3, 1, 80, 71, 75, 80, 72, 79, 89, 74, 76, 116, 91, 82, 97, 92, 89, 11, 20, 9, 6, 6, 67, 74, 67, 82, 87, 79, 95, 106, 105, 126, 76, 77, 93, 85, 92, 96, 101, 104, 109, 115, 112, 126, 86, 111, 117, 101, 102, 1, 38, 27, 18, 7, 5, 65, 66, 71, 66, 10, 50, 33, 25, 17, 43, 17, 13, 3, 5, 4, 50, 31, 18, 9, 23, 9, 3, 0, 0, 66, 34, 15, 7, 0, 13, 3, 66, 64, 7, 44, 26, 16, 10, 28, 12, 10, 9, 5, 62, 76, 67, 6, 66, 68, 1, 9, 10, 2, 10, 16, 22, 76, 71, 76, 71, 21, 77, 78, 71, 1, 17, 91, 81, 8, 13, 89, 92, 88, 71, 66, 11, 68, 0, 65, 3, 11, 77, 68, 67, 4, 85, 73, 80, 79, 7, 100, 76, 3, 70, 19, 70, 65, 29, 70, 73, 65, 24, 85, 77, 103, 16, 28, 28, 16, 1, 17, 13, 6, 7, 5, 2, 64, 82, 86, 93, 83, 78, 90, 112, 89, 87, 88, 84, 82, 71, 71, 85, 93, 76, 64, 65, 1, 71, 89, 75, 78, 95, 83, 91, 90, 106, 103, 98, 93, 76, 71, 90, 78, 81, 90, 96, 98, 98, 99, 101, 105, 118, 111, 119, 118, 118, 106, 116, 118, 73, 6, 64, 9, 13, 13, 62, 24, 22, 68, 32, 62, 59, 62, 20, 13, 5, 95, 120, 124, 126, 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, 67, 67, 2, 15, 6, 25, 18, 9, 16, 18, 21, 22, 11, 24, 19, 64, 16, 8, 69, 76, 82, 88, 116, 115, 105 }, { 22, 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 66, 4, 52, 14, 50, 71, 22, 29, 64, 82, 2, 97, 114, 68, 81, 126, 126, 126, 62, 16, 69, 22, 29, 64, 69, 16, 19, 70, 3, 64, 65, 71, 86, 71, 93, 5, 67, 65, 68, 80, 72, 88, 16, 4, 1, 65, 11, 3, 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 52, 6, 69, 79, 65, 82, 9, 16, 12, 24, 4, 19, 12, 9, 21, 75, 81, 77, 84, 34, 1, 22, 67, 2, 70, 2, 21, 71, 80, 71, 70, 19, 67, 7, 0, 73, 7, 68, 7, 12, 7, 13, 21, 18, 83, 70, 3, 66, 2, 72, 11, 71, 0, 19, 68, 16, 17, 5, 104, 10, 69, 97, 78, 101, 71, 6, 64, 9, 3, 4, 24, 6, 0, 13, 23, 72, 72, 4, 109, 79, 6, 95, 0, 77, 19, 1, 88, 31, 1, 65, 96, 34, 82, 99, 1, 105, 67, 12, 16, 9, 12, 8, 65, 12, 18, 89, 72, 7, 3, 1, 80, 71, 75, 81, 72, 80, 90, 74, 77, 118, 92, 83, 98, 93, 89, 9, 19, 7, 4, 5, 69, 77, 69, 84, 89, 81, 97, 109, 107, 126, 78, 78, 95, 88, 94, 98, 103, 107, 111, 118, 114, 126, 87, 112, 119, 101, 102, 1, 38, 28, 18, 7, 5, 65, 66, 71, 65, 11, 51, 33, 25, 17, 44, 18, 14, 3, 6, 5, 51, 32, 18, 10, 24, 10, 3, 1, 1, 66, 36, 15, 7, 1, 14, 4, 66, 0, 8, 45, 27, 16, 10, 29, 13, 11, 10, 6, 62, 76, 67, 7, 66, 68, 1, 9, 10, 3, 10, 17, 23, 76, 71, 76, 72, 21, 78, 79, 72, 1, 17, 92, 82, 7, 13, 90, 93, 89, 70, 66, 12, 68, 1, 65, 4, 12, 77, 68, 67, 4, 86, 73, 80, 79, 8, 100, 76, 4, 70, 19, 70, 65, 30, 70, 73, 65, 25, 85, 78, 103, 15, 28, 28, 16, 0, 16, 12, 5, 7, 4, 1, 65, 83, 87, 94, 84, 80, 92, 114, 90, 88, 89, 85, 82, 71, 70, 85, 94, 75, 66, 67, 64, 74, 91, 77, 80, 97, 85, 93, 92, 108, 105, 99, 94, 78, 73, 91, 80, 83, 92, 98, 100, 100, 101, 103, 107, 120, 113, 121, 120, 119, 108, 118, 120, 72, 8, 0, 9, 14, 14, 62, 25, 23, 68, 33, 62, 60, 62, 20, 12, 3, 97, 123, 126, 126, 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, 66, 66, 3, 16, 7, 26, 19, 10, 17, 19, 22, 23, 12, 25, 20, 64, 14, 7, 70, 77, 84, 90, 118, 117, 106 }, { 21, 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 68, 3, 52, 14, 52, 71, 23, 30, 64, 83, 2, 98, 115, 68, 83, 126, 126, 126, 62, 17, 69, 23, 30, 64, 68, 17, 20, 70, 3, 0, 64, 72, 87, 71, 93, 5, 67, 65, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 52, 6, 69, 78, 64, 82, 10, 17, 14, 26, 5, 20, 14, 10, 23, 74, 80, 76, 83, 35, 1, 23, 66, 2, 70, 2, 23, 71, 79, 70, 69, 19, 67, 8, 2, 72, 8, 68, 7, 12, 7, 14, 22, 19, 84, 70, 3, 66, 2, 72, 11, 72, 0, 19, 69, 16, 17, 5, 106, 10, 70, 99, 79, 102, 70, 7, 0, 10, 4, 4, 25, 6, 1, 14, 24, 72, 72, 4, 110, 79, 7, 95, 0, 77, 20, 1, 89, 32, 1, 65, 97, 35, 82, 100, 1, 106, 69, 11, 15, 8, 11, 7, 65, 11, 17, 90, 73, 7, 3, 1, 81, 72, 76, 82, 73, 81, 91, 75, 77, 120, 93, 83, 99, 94, 89, 8, 17, 6, 2, 3, 71, 79, 71, 87, 92, 83, 100, 111, 110, 126, 80, 80, 96, 90, 96, 101, 106, 109, 114, 120, 116, 126, 88, 114, 120, 102, 102, 1, 39, 28, 18, 7, 5, 65, 66, 71, 65, 11, 51, 33, 25, 17, 45, 18, 14, 4, 6, 6, 52, 32, 19, 10, 25, 10, 4, 1, 2, 65, 37, 16, 8, 1, 15, 5, 65, 1, 8, 45, 27, 16, 10, 30, 13, 12, 11, 7, 62, 75, 66, 7, 66, 68, 1, 9, 10, 3, 10, 17, 23, 76, 71, 76, 72, 22, 79, 80, 73, 0, 17, 93, 83, 7, 13, 90, 94, 89, 69, 65, 13, 67, 1, 64, 4, 13, 77, 68, 67, 5, 86, 73, 80, 78, 9, 100, 76, 4, 70, 20, 70, 65, 31, 70, 73, 65, 26, 86, 78, 104, 14, 27, 27, 15, 64, 15, 11, 4, 6, 3, 0, 66, 84, 88, 95, 86, 81, 93, 116, 92, 89, 90, 85, 83, 71, 70, 85, 94, 74, 68, 69, 66, 76, 94, 79, 82, 100, 87, 95, 94, 111, 107, 101, 95, 80, 74, 93, 82, 85, 94, 100, 102, 102, 103, 105, 109, 123, 115, 123, 122, 120, 110, 120, 121, 71, 9, 1, 10, 15, 15, 62, 26, 24, 68, 34, 62, 62, 62, 21, 10, 2, 100, 126, 126, 126, 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, 66, 65, 4, 17, 8, 27, 19, 10, 18, 19, 22, 23, 12, 25, 20, 64, 13, 5, 72, 79, 86, 92, 120, 119, 107 }, { 20, 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 100, 69, 2, 52, 14, 54, 71, 24, 31, 64, 84, 2, 99, 116, 69, 85, 126, 126, 126, 62, 18, 69, 24, 31, 64, 68, 18, 20, 71, 3, 0, 0, 72, 87, 71, 93, 5, 67, 64, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 52, 6, 69, 77, 0, 82, 10, 18, 16, 28, 7, 21, 16, 11, 25, 73, 79, 75, 82, 36, 1, 24, 65, 2, 69, 3, 25, 71, 79, 70, 68, 19, 67, 8, 3, 72, 9, 68, 7, 12, 7, 15, 22, 19, 84, 70, 3, 66, 2, 72, 11, 72, 64, 19, 70, 16, 17, 5, 108, 10, 70, 101, 80, 103, 69, 8, 1, 11, 5, 5, 26, 7, 1, 15, 25, 72, 72, 4, 111, 78, 8, 95, 0, 77, 21, 1, 90, 33, 1, 65, 98, 36, 83, 101, 1, 107, 70, 10, 15, 7, 10, 6, 66, 10, 17, 91, 74, 7, 3, 1, 81, 73, 77, 83, 74, 82, 92, 76, 78, 122, 94, 84, 100, 95, 89, 6, 16, 4, 0, 1, 73, 81, 73, 89, 94, 85, 102, 114, 112, 126, 82, 82, 98, 92, 98, 103, 108, 111, 116, 123, 118, 126, 89, 116, 121, 103, 102, 1, 39, 28, 18, 7, 5, 65, 66, 71, 64, 11, 51, 33, 25, 17, 46, 19, 15, 4, 7, 7, 53, 33, 19, 10, 26, 11, 4, 2, 3, 64, 38, 17, 8, 1, 16, 6, 64, 2, 9, 45, 27, 16, 10, 31, 14, 13, 12, 8, 62, 75, 66, 8, 66, 68, 1, 9, 10, 3, 10, 17, 24, 76, 71, 76, 73, 22, 80, 81, 74, 64, 17, 94, 84, 7, 13, 91, 95, 90, 68, 64, 14, 67, 2, 64, 5, 14, 77, 68, 67, 5, 87, 73, 80, 78, 10, 100, 76, 4, 70, 21, 70, 65, 32, 70, 73, 65, 27, 87, 78, 105, 13, 27, 27, 14, 65, 14, 10, 3, 5, 2, 64, 67, 85, 89, 96, 87, 82, 95, 118, 93, 90, 91, 86, 83, 71, 70, 85, 95, 73, 70, 71, 68, 78, 97, 81, 84, 102, 89, 97, 96, 113, 109, 102, 96, 82, 76, 94, 84, 87, 96, 102, 104, 104, 105, 107, 111, 125, 117, 125, 124, 121, 112, 122, 123, 70, 10, 2, 11, 16, 16, 62, 27, 25, 68, 35, 62, 62, 62, 22, 9, 0, 102, 126, 126, 126, 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, 65, 64, 5, 18, 9, 28, 20, 11, 19, 20, 23, 24, 12, 26, 20, 64, 12, 4, 74, 81, 88, 94, 122, 121, 108 }, { 18, 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 102, 71, 0, 51, 14, 56, 72, 24, 31, 65, 85, 2, 101, 118, 70, 87, 126, 126, 126, 62, 19, 70, 24, 31, 65, 68, 19, 20, 72, 3, 0, 0, 73, 88, 71, 94, 5, 67, 64, 69, 81, 72, 88, 17, 4, 1, 65, 12, 2, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 52, 5, 69, 76, 0, 82, 10, 19, 17, 29, 8, 22, 17, 12, 26, 72, 79, 74, 82, 36, 1, 24, 65, 2, 69, 3, 26, 71, 79, 70, 67, 19, 67, 8, 4, 72, 9, 68, 7, 12, 7, 15, 22, 19, 85, 71, 3, 67, 2, 72, 10, 73, 65, 19, 71, 15, 17, 4, 110, 9, 71, 103, 81, 104, 69, 8, 1, 11, 5, 5, 27, 7, 1, 15, 26, 73, 72, 4, 113, 78, 8, 95, 0, 78, 21, 1, 91, 33, 1, 65, 100, 36, 84, 102, 1, 108, 72, 9, 14, 6, 9, 5, 67, 9, 16, 93, 75, 6, 2, 1, 82, 74, 78, 84, 75, 83, 93, 77, 79, 124, 96, 85, 102, 97, 89, 4, 14, 2, 65, 64, 76, 84, 76, 92, 97, 88, 105, 117, 115, 126, 84, 84, 100, 95, 101, 106, 111, 114, 119, 126, 121, 126, 90, 118, 123, 104, 103, 1, 39, 28, 18, 7, 5, 66, 66, 71, 64, 11, 51, 33, 25, 17, 47, 19, 15, 4, 7, 7, 53, 33, 19, 10, 26, 11, 4, 2, 4, 64, 39, 17, 8, 1, 16, 6, 64, 3, 9, 45, 27, 16, 10, 31, 14, 13, 12, 8, 62, 75, 66, 8, 66, 68, 1, 9, 10, 3, 10, 17, 24, 76, 71, 77, 74, 22, 81, 82, 75, 65, 16, 96, 86, 6, 12, 92, 97, 91, 68, 64, 15, 67, 2, 64, 5, 14, 78, 68, 67, 5, 88, 73, 80, 78, 10, 101, 76, 4, 70, 21, 71, 65, 32, 71, 74, 65, 27, 88, 79, 106, 12, 26, 26, 13, 67, 13, 9, 2, 4, 1, 65, 68, 86, 91, 98, 89, 84, 97, 120, 95, 92, 92, 87, 84, 71, 70, 85, 96, 73, 73, 74, 70, 81, 100, 84, 87, 105, 92, 100, 99, 116, 112, 104, 97, 84, 78, 96, 86, 90, 99, 105, 107, 107, 107, 110, 114, 126, 119, 126, 126, 123, 114, 124, 125, 69, 11, 3, 11, 16, 16, 62, 27, 25, 68, 35, 62, 62, 62, 22, 7, 65, 105, 126, 126, 126, 126, 126, 126, 66, 43, 26, 17, 9, 14, 2, 67, 68, 65, 64, 5, 18, 9, 29, 20, 11, 19, 20, 23, 24, 12, 26, 20, 65, 10, 2, 76, 83, 90, 96, 125, 123, 109 }, { 17, 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 103, 72, 64, 51, 14, 59, 72, 25, 32, 65, 85, 3, 102, 119, 70, 88, 126, 126, 126, 62, 21, 70, 25, 32, 65, 67, 21, 21, 72, 4, 1, 1, 73, 88, 70, 94, 5, 66, 0, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 52, 5, 69, 74, 1, 82, 11, 21, 19, 31, 10, 24, 19, 14, 28, 70, 78, 73, 81, 37, 2, 25, 64, 3, 68, 4, 28, 70, 78, 69, 65, 20, 66, 9, 6, 71, 10, 67, 8, 13, 8, 16, 23, 20, 85, 71, 4, 67, 2, 71, 10, 73, 65, 19, 71, 15, 17, 4, 111, 9, 71, 104, 82, 104, 68, 9, 2, 12, 6, 6, 28, 8, 2, 16, 28, 73, 72, 5, 114, 77, 9, 95, 1, 78, 22, 2, 91, 34, 1, 65, 101, 37, 84, 103, 1, 108, 73, 9, 14, 6, 9, 5, 67, 9, 16, 94, 75, 6, 2, 1, 82, 74, 78, 84, 75, 83, 94, 77, 79, 125, 97, 85, 103, 98, 89, 3, 13, 1, 66, 65, 78, 86, 78, 94, 99, 90, 107, 119, 117, 126, 85, 85, 101, 97, 103, 108, 113, 116, 121, 126, 123, 126, 90, 119, 124, 104, 103, 2, 40, 29, 19, 8, 5, 66, 65, 70, 0, 12, 52, 34, 26, 17, 48, 20, 16, 5, 8, 8, 54, 34, 20, 11, 27, 12, 5, 3, 6, 0, 41, 18, 9, 2, 17, 7, 0, 5, 10, 46, 28, 17, 11, 32, 15, 14, 13, 9, 62, 74, 65, 9, 66, 67, 1, 10, 11, 4, 11, 18, 25, 75, 71, 77, 74, 23, 81, 82, 76, 65, 16, 97, 87, 6, 12, 92, 98, 91, 67, 0, 16, 66, 3, 0, 6, 15, 78, 67, 66, 6, 88, 72, 79, 77, 11, 101, 76, 5, 70, 22, 71, 65, 33, 71, 74, 64, 28, 88, 79, 106, 12, 26, 26, 13, 68, 13, 9, 2, 4, 1, 65, 68, 86, 92, 99, 90, 85, 98, 121, 96, 93, 92, 87, 84, 70, 69, 84, 96, 72, 75, 76, 72, 83, 102, 86, 89, 107, 94, 102, 101, 118, 114, 105, 97, 85, 79, 97, 87, 92, 101, 107, 109, 109, 109, 112, 116, 126, 120, 126, 126, 124, 115, 125, 126, 67, 13, 5, 12, 17, 17, 62, 28, 26, 68, 36, 62, 62, 62, 23, 6, 66, 107, 126, 126, 126, 126, 126, 126, 65, 44, 26, 17, 9, 15, 2, 67, 68, 64, 0, 6, 19, 10, 31, 21, 12, 20, 21, 24, 25, 13, 27, 21, 65, 9, 1, 77, 84, 91, 97, 126, 124, 109 }, { 16, 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 104, 73, 65, 51, 14, 61, 72, 26, 33, 65, 86, 3, 103, 120, 71, 90, 126, 126, 126, 62, 22, 70, 26, 33, 65, 67, 22, 21, 73, 4, 1, 2, 73, 88, 70, 94, 5, 66, 1, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 52, 5, 69, 73, 2, 82, 11, 22, 21, 33, 11, 25, 21, 15, 30, 69, 77, 72, 80, 38, 2, 26, 0, 3, 68, 5, 30, 70, 78, 69, 64, 20, 66, 9, 7, 71, 11, 67, 8, 13, 8, 17, 23, 20, 86, 71, 4, 67, 2, 71, 10, 73, 66, 19, 72, 15, 17, 4, 113, 9, 71, 106, 83, 105, 67, 10, 3, 13, 7, 7, 29, 9, 2, 17, 29, 73, 72, 5, 115, 77, 10, 95, 1, 78, 23, 2, 92, 35, 1, 65, 102, 38, 85, 104, 1, 109, 75, 8, 13, 5, 8, 4, 68, 8, 16, 95, 76, 6, 2, 1, 82, 75, 79, 85, 76, 84, 95, 78, 80, 126, 98, 86, 104, 99, 89, 1, 12, 64, 68, 67, 80, 88, 80, 97, 101, 92, 110, 122, 119, 126, 87, 87, 103, 99, 105, 110, 115, 118, 123, 126, 125, 126, 91, 121, 125, 105, 103, 2, 40, 29, 19, 8, 5, 66, 65, 70, 1, 12, 52, 34, 26, 17, 49, 20, 17, 5, 9, 9, 55, 35, 20, 11, 28, 12, 5, 4, 7, 1, 42, 19, 9, 2, 18, 8, 1, 6, 10, 46, 28, 17, 11, 33, 16, 15, 14, 10, 62, 74, 65, 9, 66, 67, 1, 10, 11, 4, 11, 18, 26, 75, 71, 77, 75, 23, 82, 83, 77, 66, 16, 98, 88, 6, 12, 93, 99, 92, 66, 1, 17, 66, 3, 0, 7, 16, 78, 67, 66, 6, 89, 72, 79, 77, 12, 101, 76, 5, 70, 23, 71, 65, 34, 71, 74, 64, 29, 89, 79, 107, 11, 26, 26, 12, 69, 12, 8, 1, 3, 0, 66, 69, 87, 93, 100, 92, 86, 100, 123, 97, 94, 93, 88, 84, 70, 69, 84, 97, 71, 77, 78, 74, 85, 105, 88, 91, 110, 96, 104, 103, 120, 116, 106, 98, 87, 81, 98, 89, 94, 103, 109, 111, 111, 111, 114, 118, 126, 122, 126, 126, 125, 117, 126, 126, 66, 14, 6, 13, 18, 18, 62, 29, 27, 68, 37, 62, 62, 62, 24, 5, 68, 110, 126, 126, 126, 126, 126, 126, 65, 44, 26, 17, 9, 15, 2, 67, 68, 0, 1, 7, 20, 11, 32, 22, 13, 21, 22, 25, 26, 13, 28, 21, 65, 8, 64, 79, 86, 93, 99, 126, 126, 110 }, { 15, 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 106, 75, 66, 51, 14, 62, 72, 27, 34, 65, 87, 3, 104, 121, 71, 92, 126, 126, 126, 62, 23, 70, 27, 34, 65, 66, 23, 22, 73, 4, 2, 3, 74, 89, 70, 94, 5, 66, 1, 69, 81, 71, 88, 19, 5, 2, 64, 14, 2, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 52, 5, 69, 72, 3, 82, 12, 23, 23, 35, 13, 26, 23, 16, 32, 68, 76, 71, 79, 39, 2, 27, 1, 3, 67, 5, 32, 70, 77, 68, 0, 20, 66, 10, 9, 70, 12, 67, 8, 13, 8, 18, 24, 21, 86, 71, 4, 67, 2, 71, 10, 74, 66, 19, 73, 15, 17, 4, 115, 9, 72, 108, 84, 106, 66, 11, 4, 14, 8, 7, 30, 9, 3, 18, 30, 73, 72, 5, 116, 76, 11, 95, 1, 78, 24, 2, 93, 36, 1, 65, 103, 39, 85, 105, 1, 110, 76, 7, 13, 4, 7, 3, 68, 7, 15, 96, 77, 6, 2, 1, 83, 76, 80, 86, 77, 85, 96, 79, 80, 126, 99, 86, 105, 100, 89, 0, 10, 65, 70, 69, 82, 90, 82, 99, 104, 94, 112, 124, 122, 126, 89, 89, 104, 101, 107, 113, 118, 120, 126, 126, 126, 126, 92, 123, 126, 106, 103, 2, 41, 29, 19, 8, 5, 66, 65, 70, 1, 12, 52, 34, 26, 17, 50, 21, 17, 6, 9, 10, 56, 35, 21, 11, 29, 13, 6, 4, 8, 2, 43, 20, 10, 2, 19, 9, 2, 7, 11, 46, 28, 17, 11, 34, 16, 16, 15, 11, 62, 73, 64, 10, 66, 67, 1, 10, 11, 4, 11, 18, 26, 75, 71, 77, 75, 24, 83, 84, 78, 67, 16, 99, 89, 6, 12, 93, 100, 92, 65, 2, 18, 65, 4, 1, 7, 17, 78, 67, 66, 7, 89, 72, 79, 76, 13, 101, 76, 5, 70, 24, 71, 65, 35, 71, 74, 64, 30, 90, 79, 108, 10, 25, 25, 11, 70, 11, 7, 0, 2, 64, 67, 70, 88, 94, 101, 93, 87, 101, 125, 99, 95, 94, 88, 85, 70, 69, 84, 97, 70, 79, 80, 76, 87, 108, 90, 93, 112, 98, 106, 105, 123, 118, 108, 99, 89, 82, 100, 91, 96, 105, 111, 113, 113, 113, 116, 120, 126, 124, 126, 126, 126, 119, 126, 126, 65, 15, 7, 14, 19, 19, 62, 30, 28, 68, 38, 62, 62, 62, 25, 3, 69, 112, 126, 126, 126, 126, 126, 126, 65, 44, 26, 17, 9, 15, 2, 67, 68, 0, 2, 8, 21, 12, 33, 22, 13, 22, 22, 25, 26, 13, 28, 21, 65, 7, 65, 81, 88, 95, 101, 126, 126, 111 }, }, }; #endif ================================================ FILE: dependencies/ih264d/decoder/ih264d_compute_bs.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264d_deblocking.h" #include "string.h" #include "ih264d_debug.h" #include "ih264d_tables.h" UWORD16 ih264d_update_csbp_8x8(UWORD16 u2_luma_csbp) { UWORD16 u2_mod_csbp; u2_mod_csbp = u2_luma_csbp; if(u2_mod_csbp & 0x0033) { u2_mod_csbp |= 0x0033; } if(u2_mod_csbp & 0x00CC) { u2_mod_csbp |= 0x00CC; } if(u2_mod_csbp & 0x3300) { u2_mod_csbp |= 0x3300; } if(u2_mod_csbp & 0xCC00) { u2_mod_csbp |= 0xCC00; } return u2_mod_csbp; } /*****************************************************************************/ /* */ /* Function Name : ih264d_fill_bs2_horz_vert */ /* */ /* Description : This function fills boundray strength (=2) for all horz */ /* and vert edges of current mb based on coded sub block */ /* pattern of current, top and left mb */ /* Inputs : */ /* pu4_bs : Base pointer of BS table which gets updated */ /* u4_left_mb_csbp : left mb's coded sub block pattern */ /* u4_top_mb_csbp : top mb's coded sub block pattern */ /* u4_cur_mb_csbp : current mb's coded sub block pattern */ /* */ /* Globals : <Does it use any global variables?> */ /* Processing : */ /* */ /* csbp for each 4x4 block in a mb is bit packet in reverse */ /* raster scan order for each mb as shown below: */ /* 15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0. */ /* */ /* BS=2 for a 4x4 edge if any of adjacent blocks forming edge */ /* are coded. Keeping this in mind, bs=2 for all horz and vert */ /* edges can be derived using a lookup table for each edge */ /* after "ORing" the csbp values as follows: */ /* (C means current Mb, T means top mb and L means left mb) */ /* */ /* All Horz edges: */ /* 15C|14C|13C|12C|11C|10C|9C|8C|7C|6C|5C|4C|3C |2C |1C |0C */ /* (or with) 11C|10C| 9C| 8C| 7C|6C |5C|4C|3C|2C|1C|0C|15T|14T|13T|12T */ /* -----BS[3]-----|----BS[2]----|---BS[1]---|----BS[0]-----| */ /* */ /* All Vert edges: */ /* 15C|14C|13C|12C|11C|10C|9C| 8C|7C|6C|5C|4C|3C |2C |1C |0C */ /* (or with) 14C|13C|12C|15L|10C| 9C|8C|11L|6C|5C|4C|7L|2C |1C |0C |3L */ /* Do 4x4 transpose of resulting pattern to get vertBS[4]-BS[7] */ /* */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 16 10 2008 Jay Draft */ /* */ /*****************************************************************************/ #define CSBP_LEFT_BLOCK_MASK 0x1111 #define CSBP_RIGHT_BLOCK_MASK 0x8888 void ih264d_fill_bs2_horz_vert(UWORD32 *pu4_bs, /* Base pointer of BS table */ WORD32 u4_left_mb_csbp, /* csbp of left mb */ WORD32 u4_top_mb_csbp, /* csbp of top mb */ WORD32 u4_cur_mb_csbp, /* csbp of current mb */ const UWORD32 *pu4_packed_bs2, const UWORD16 *pu2_4x4_v2h_reorder) { /*************************************************************************/ /*u4_nbr_horz_csbp=11C|10C|9C|8C|7C|6C|5C|4C|3C|2C|1C|0C|15T|14T|13T|12T */ /*************************************************************************/ UWORD32 u4_nbr_horz_csbp = (u4_cur_mb_csbp << 4) | (u4_top_mb_csbp >> 12); UWORD32 u4_horz_bs2_dec = u4_cur_mb_csbp | u4_nbr_horz_csbp; /*************************************************************************/ /*u4_left_mb_masked_csbp = 15L|0|0|0|11L|0|0|0|7L|0|0|0|3L|0|0|0 */ /*************************************************************************/ UWORD32 u4_left_mb_masked_csbp = u4_left_mb_csbp & CSBP_RIGHT_BLOCK_MASK; /*************************************************************************/ /*u4_cur_mb_masked_csbp =14C|13C|12C|x|10C|9C|8C|x|6C|5C|4C|x|2C|1C|0C|x */ /*************************************************************************/ UWORD32 u4_cur_mb_masked_csbp = (u4_cur_mb_csbp << 1) & (~CSBP_LEFT_BLOCK_MASK); /*************************************************************************/ /*u4_nbr_vert_csbp=14C|13C|12C|15L|10C|9C|8C|11L|6C|5C|4C|7L|2C|1C|0C|3L */ /*************************************************************************/ UWORD32 u4_nbr_vert_csbp = (u4_cur_mb_masked_csbp) | (u4_left_mb_masked_csbp >> 3); UWORD32 u4_vert_bs2_dec = u4_cur_mb_csbp | u4_nbr_vert_csbp; UWORD32 u4_reordered_vert_bs2_dec, u4_temp; PROFILE_DISABLE_BOUNDARY_STRENGTH() /*************************************************************************/ /* Fill horz edges (0,1,2,3) boundary strengths 2 using look up table */ /*************************************************************************/ pu4_bs[0] = pu4_packed_bs2[u4_horz_bs2_dec & 0xF]; pu4_bs[1] = pu4_packed_bs2[(u4_horz_bs2_dec >> 4) & 0xF]; pu4_bs[2] = pu4_packed_bs2[(u4_horz_bs2_dec >> 8) & 0xF]; pu4_bs[3] = pu4_packed_bs2[(u4_horz_bs2_dec >> 12) & 0xF]; /*************************************************************************/ /* Do 4x4 tranpose of u4_vert_bs2_dec by using look up table for reorder */ /*************************************************************************/ u4_reordered_vert_bs2_dec = pu2_4x4_v2h_reorder[u4_vert_bs2_dec & 0xF]; u4_temp = pu2_4x4_v2h_reorder[(u4_vert_bs2_dec >> 4) & 0xF]; u4_reordered_vert_bs2_dec |= (u4_temp << 1); u4_temp = pu2_4x4_v2h_reorder[(u4_vert_bs2_dec >> 8) & 0xF]; u4_reordered_vert_bs2_dec |= (u4_temp << 2); u4_temp = pu2_4x4_v2h_reorder[(u4_vert_bs2_dec >> 12) & 0xF]; u4_reordered_vert_bs2_dec |= (u4_temp << 3); /*************************************************************************/ /* Fill vert edges (4,5,6,7) boundary strengths 2 using look up table */ /*************************************************************************/ pu4_bs[4] = pu4_packed_bs2[u4_reordered_vert_bs2_dec & 0xF]; pu4_bs[5] = pu4_packed_bs2[(u4_reordered_vert_bs2_dec >> 4) & 0xF]; pu4_bs[6] = pu4_packed_bs2[(u4_reordered_vert_bs2_dec >> 8) & 0xF]; pu4_bs[7] = pu4_packed_bs2[(u4_reordered_vert_bs2_dec >> 12) & 0xF]; } /*****************************************************************************/ /* */ /* Function Name : ih264d_fill_bs1_16x16mb_pslice */ /* */ /* Description : This function fills boundray strength (=1) for those */ /* horz and vert mb edges of 16x16mb which are set to 0 by */ /* ih264d_fill_bs2_horz_vert. This function is used for p slices */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : If any motion vector component of adjacent 4x4 blocks */ /* differs by more than 1 integer pel or if reference */ /* pictures are different, Bs is set to 1. */ /* */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 16 10 2008 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_fill_bs1_16x16mb_pslice(mv_pred_t *ps_cur_mv_pred, mv_pred_t *ps_top_mv_pred, void **ppv_map_ref_idx_to_poc, UWORD32 *pu4_bs_table, /* pointer to the BsTable array */ mv_pred_t *ps_leftmost_mv_pred, neighbouradd_t *ps_left_addr, void **u4_pic_addrress, /* picture address for BS calc */ WORD32 i4_ver_mvlimit) { WORD16 i2_q_mv0, i2_q_mv1; WORD16 i2_p_mv0, i2_p_mv1; void *pv_cur_pic_addr0, *pv_cur_pic_addr1; void *pv_nbr_pic_addr0, *pv_nbr_pic_addr1; void **ppv_map_ref_idx_to_poc_l0; //,*ppv_map_ref_idx_to_poc_l1; UWORD32 i; UWORD32 u4_bs_horz = pu4_bs_table[0]; UWORD32 u4_bs_vert = pu4_bs_table[4]; PROFILE_DISABLE_BOUNDARY_STRENGTH() ppv_map_ref_idx_to_poc_l0 = ppv_map_ref_idx_to_poc; i2_q_mv0 = ps_cur_mv_pred->i2_mv[0]; i2_q_mv1 = ps_cur_mv_pred->i2_mv[1]; pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[ps_cur_mv_pred->i1_ref_frame[0]]; pv_cur_pic_addr1 = 0; /*********************************/ /* Computing Bs for the top edge */ /*********************************/ for(i = 0; i < 4; i++, ps_top_mv_pred++) { UWORD32 u4_idx = 24 - (i << 3); /*********************************/ /* check if Bs is already set */ /*********************************/ if(!((u4_bs_horz >> u4_idx) & 0xf)) { /************************************************************/ /* If Bs is not set, use left edge and current edge mvs and */ /* reference pictures addresses to evaluate Bs==1 */ /************************************************************/ UWORD32 u4_bs_temp1; UWORD32 u4_bs; /*********************************************************/ /* If any motion vector component differs by more than 1 */ /* integer pel or if reference pictures are different Bs */ /* is set to 1. Note that this condition shall be met for*/ /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ /*********************************************************/ i2_p_mv0 = ps_top_mv_pred->i2_mv[0]; i2_p_mv1 = ps_top_mv_pred->i2_mv[1]; pv_nbr_pic_addr0 = u4_pic_addrress[i & 2]; pv_nbr_pic_addr1 = u4_pic_addrress[1 + (i & 2)]; u4_bs_temp1 = ((ABS((i2_p_mv0 - i2_q_mv0)) >= 4) || (ABS((i2_p_mv1 - i2_q_mv1)) >= i4_ver_mvlimit)); u4_bs = ((pv_cur_pic_addr0 != pv_nbr_pic_addr0) || (pv_cur_pic_addr1 != pv_nbr_pic_addr1) || u4_bs_temp1); u4_bs_horz |= (u4_bs << u4_idx); } } pu4_bs_table[0] = u4_bs_horz; /***********************************/ /* Computing Bs for the left edge */ /***********************************/ for(i = 0; i < 4; i++, ps_leftmost_mv_pred += 4) { UWORD32 u4_idx = 24 - (i << 3); /*********************************/ /* check if Bs is already set */ /*********************************/ if(!((u4_bs_vert >> u4_idx) & 0xf)) { /****************************************************/ /* If Bs is not set, evalaute conditions for Bs=1 */ /****************************************************/ UWORD32 u4_bs_temp1; UWORD32 u4_bs; /*********************************************************/ /* If any motion vector component differs by more than 1 */ /* integer pel or if reference pictures are different Bs */ /* is set to 1. Note that this condition shall be met for*/ /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ /*********************************************************/ i2_p_mv0 = ps_leftmost_mv_pred->i2_mv[0]; i2_p_mv1 = ps_leftmost_mv_pred->i2_mv[1]; pv_nbr_pic_addr0 = ps_left_addr->u4_add[i & 2]; pv_nbr_pic_addr1 = ps_left_addr->u4_add[1 + (i & 2)]; u4_bs_temp1 = ((ABS((i2_p_mv0 - i2_q_mv0)) >= 4) | (ABS((i2_p_mv1 - i2_q_mv1)) >= i4_ver_mvlimit)); u4_bs = ((pv_cur_pic_addr0 != pv_nbr_pic_addr0) || (pv_cur_pic_addr1 != pv_nbr_pic_addr1) || u4_bs_temp1); u4_bs_vert |= (u4_bs << u4_idx); } } pu4_bs_table[4] = u4_bs_vert; return; } /*****************************************************************************/ /* */ /* Function Name : ih264d_fill_bs1_non16x16mb_pslice */ /* */ /* Description : This function fills boundray strength (=1) for those */ /* horz and vert edges of non16x16mb which are set to 0 by */ /* ih264d_fill_bs2_horz_vert. This function is used for p slices */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : If any motion vector component of adjacent 4x4 blocks */ /* differs by more than 1 integer pel or if reference */ /* pictures are different, Bs is set to 1. */ /* */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 16 10 2008 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_fill_bs1_non16x16mb_pslice(mv_pred_t *ps_cur_mv_pred, mv_pred_t *ps_top_mv_pred, void **ppv_map_ref_idx_to_poc, UWORD32 *pu4_bs_table, /* pointer to the BsTable array */ mv_pred_t *ps_leftmost_mv_pred, neighbouradd_t *ps_left_addr, void **u4_pic_addrress, WORD32 i4_ver_mvlimit) { UWORD32 edge; void **ppv_map_ref_idx_to_poc_l0; //,*ppv_map_ref_idx_to_poc_l1; PROFILE_DISABLE_BOUNDARY_STRENGTH() ppv_map_ref_idx_to_poc_l0 = ppv_map_ref_idx_to_poc; for(edge = 0; edge < 4; edge++, ps_top_mv_pred = ps_cur_mv_pred - 4) { /*********************************************************************/ /* Each iteration of this loop fills the four BS values of one HORIZ */ /* edge and one BS value for each of the four VERT edges. */ /*********************************************************************/ WORD32 i; UWORD32 u4_vert_idx = 24 - (edge << 3); UWORD32 u4_bs_horz = pu4_bs_table[edge]; mv_pred_t *ps_left_mv_pred = ps_leftmost_mv_pred + (edge << 2); for(i = 0; i < 4; i++, ps_top_mv_pred++, ps_cur_mv_pred++) { WORD16 i2_cur_mv0, i2_cur_mv1; WORD8 i1_cur_ref0; void *pv_cur_pic_addr0, *pv_cur_pic_addr1 = 0; void *pv_nbr_pic_addr0, *pv_nbr_pic_addr1; /******************************************************/ /* Each iteration of this inner loop computes a HORIZ */ /* and a VERT BS value for a 4x4 block */ /******************************************************/ UWORD32 u4_bs_vert = (pu4_bs_table[i + 4] >> u4_vert_idx) & 0xf; UWORD32 u4_horz_idx = 24 - (i << 3); /*****************************************************/ /* check if vert Bs for this block is already set */ /*****************************************************/ if(!u4_bs_vert) { WORD16 i2_left_mv0, i2_left_mv1; /************************************************************/ /* If Bs is not set, use left edge and current edge mvs and */ /* reference pictures addresses to evaluate Bs==1 */ /************************************************************/ i2_left_mv0 = ps_left_mv_pred->i2_mv[0]; i2_left_mv1 = ps_left_mv_pred->i2_mv[1]; i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; if(i) { WORD8 i1_left_ref0 = ps_left_mv_pred->i1_ref_frame[0]; pv_nbr_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_left_ref0]; pv_nbr_pic_addr1 = 0; } else { pv_nbr_pic_addr0 = ps_left_addr->u4_add[edge & 2]; pv_nbr_pic_addr1 = ps_left_addr->u4_add[1 + (edge & 2)]; } { UWORD32 u4_bs_temp1; /*********************************************************/ /* If any motion vector component differs by more than 1 */ /* integer pel or if reference pictures are different Bs */ /* is set to 1. Note that this condition shall be met for*/ /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ /*********************************************************/ u4_bs_temp1 = ((ABS((i2_left_mv0 - i2_cur_mv0)) >= 4) | (ABS((i2_left_mv1 - i2_cur_mv1)) >= i4_ver_mvlimit)); u4_bs_vert = ((pv_nbr_pic_addr0 != pv_cur_pic_addr0) || (pv_nbr_pic_addr1 != pv_cur_pic_addr1) || u4_bs_temp1); pu4_bs_table[i + 4] |= (u4_bs_vert << u4_vert_idx); } } /*****************************************************/ /* check if horz Bs for this block is already set */ /*****************************************************/ if(!((u4_bs_horz >> u4_horz_idx) & 0xf)) { WORD16 i2_top_mv0, i2_top_mv1; /************************************************************/ /* If Bs is not set, use top edge and current edge mvs and */ /* reference pictures addresses to evaluate Bs==1 */ /************************************************************/ i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; i2_top_mv0 = ps_top_mv_pred->i2_mv[0]; i2_top_mv1 = ps_top_mv_pred->i2_mv[1]; pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; if(edge) { WORD8 i1_top_ref0 = ps_top_mv_pred->i1_ref_frame[0]; pv_nbr_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_top_ref0]; pv_nbr_pic_addr1 = 0; } else { pv_nbr_pic_addr0 = u4_pic_addrress[i & 2]; pv_nbr_pic_addr1 = u4_pic_addrress[1 + (i & 2)]; } { UWORD32 u4_bs_temp1; UWORD32 u4_bs; /*********************************************************/ /* If any motion vector component differs by more than 1 */ /* integer pel or if reference pictures are different Bs */ /* is set to 1. Note that this condition shall be met for*/ /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ /*********************************************************/ u4_bs_temp1 = ((ABS((i2_top_mv0 - i2_cur_mv0)) >= 4) | (ABS((i2_top_mv1 - i2_cur_mv1)) >= i4_ver_mvlimit)); u4_bs = ((pv_nbr_pic_addr0 != pv_cur_pic_addr0) || (pv_nbr_pic_addr1 != pv_cur_pic_addr1) || u4_bs_temp1); u4_bs_horz |= (u4_bs << u4_horz_idx); } } ps_left_mv_pred = ps_cur_mv_pred; } pu4_bs_table[edge] = u4_bs_horz; } } /*****************************************************************************/ /* */ /* Function Name : ih264d_fill_bs1_16x16mb_bslice */ /* */ /* Description : This function fills boundray strength (=1) for those */ /* horz and vert mb edges of 16x16mb which are set to 0 by */ /* ih264d_fill_bs2_horz_vert. This function is used for b slices */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : If any motion vector component of adjacent 4x4 blocks */ /* differs by more than 1 integer pel or if reference */ /* pictures are different, Bs is set to 1. */ /* */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 16 10 2008 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_fill_bs1_16x16mb_bslice(mv_pred_t *ps_cur_mv_pred, mv_pred_t *ps_top_mv_pred, void **ppv_map_ref_idx_to_poc, UWORD32 *pu4_bs_table, /* pointer to the BsTable array */ mv_pred_t *ps_leftmost_mv_pred, neighbouradd_t *ps_left_addr, void **u4_pic_addrress, WORD32 i4_ver_mvlimit) { WORD16 i2_q_mv0, i2_q_mv1, i2_q_mv2, i2_q_mv3; WORD16 i2_p_mv0, i2_p_mv1, i2_p_mv2, i2_p_mv3; void *pv_cur_pic_addr0, *pv_cur_pic_addr1; void *pv_nbr_pic_addr0, *pv_nbr_pic_addr1; void **ppv_map_ref_idx_to_poc_l0, **ppv_map_ref_idx_to_poc_l1; UWORD32 i; UWORD32 u4_bs_horz = pu4_bs_table[0]; UWORD32 u4_bs_vert = pu4_bs_table[4]; PROFILE_DISABLE_BOUNDARY_STRENGTH() ppv_map_ref_idx_to_poc_l0 = ppv_map_ref_idx_to_poc; ppv_map_ref_idx_to_poc_l1 = ppv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; i2_q_mv0 = ps_cur_mv_pred->i2_mv[0]; i2_q_mv1 = ps_cur_mv_pred->i2_mv[1]; i2_q_mv2 = ps_cur_mv_pred->i2_mv[2]; i2_q_mv3 = ps_cur_mv_pred->i2_mv[3]; pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[ps_cur_mv_pred->i1_ref_frame[0]]; pv_cur_pic_addr1 = ppv_map_ref_idx_to_poc_l1[ps_cur_mv_pred->i1_ref_frame[1]]; /*********************************/ /* Computing Bs for the top edge */ /*********************************/ for(i = 0; i < 4; i++, ps_top_mv_pred++) { UWORD32 u4_idx = 24 - (i << 3); /*********************************/ /* check if Bs is already set */ /*********************************/ if(!((u4_bs_horz >> u4_idx) & 0xf)) { /************************************************************/ /* If Bs is not set, use left edge and current edge mvs and */ /* reference pictures addresses to evaluate Bs==1 */ /************************************************************/ UWORD32 u4_bs_temp1, u4_bs_temp2; UWORD32 u4_bs; /*********************************************************/ /* If any motion vector component differs by more than 1 */ /* integer pel or if reference pictures are different Bs */ /* is set to 1. Note that this condition shall be met for*/ /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ /*********************************************************/ i2_p_mv0 = ps_top_mv_pred->i2_mv[0]; i2_p_mv1 = ps_top_mv_pred->i2_mv[1]; i2_p_mv2 = ps_top_mv_pred->i2_mv[2]; i2_p_mv3 = ps_top_mv_pred->i2_mv[3]; pv_nbr_pic_addr0 = u4_pic_addrress[i & 2]; pv_nbr_pic_addr1 = u4_pic_addrress[1 + (i & 2)]; u4_bs_temp1 = ((ABS((i2_p_mv0 - i2_q_mv0)) >= 4) | (ABS((i2_p_mv1 - i2_q_mv1)) >= i4_ver_mvlimit) | (ABS((i2_p_mv2 - i2_q_mv2)) >= 4) | (ABS((i2_p_mv3 - i2_q_mv3)) >= i4_ver_mvlimit)); u4_bs_temp2 = ((ABS((i2_p_mv0 - i2_q_mv2)) >= 4) | (ABS((i2_p_mv1 - i2_q_mv3)) >= i4_ver_mvlimit) | (ABS((i2_p_mv2 - i2_q_mv0)) >= 4) | (ABS((i2_p_mv3 - i2_q_mv1)) >= i4_ver_mvlimit)); u4_bs = ((pv_cur_pic_addr0 != pv_nbr_pic_addr0) || (pv_cur_pic_addr1 != pv_nbr_pic_addr1) || u4_bs_temp1) && ((pv_cur_pic_addr0 != pv_nbr_pic_addr1) || (pv_cur_pic_addr1 != pv_nbr_pic_addr0) || u4_bs_temp2); u4_bs_horz |= (u4_bs << u4_idx); } } pu4_bs_table[0] = u4_bs_horz; /***********************************/ /* Computing Bs for the left edge */ /***********************************/ for(i = 0; i < 4; i++, ps_leftmost_mv_pred += 4) { UWORD32 u4_idx = 24 - (i << 3); /*********************************/ /* check if Bs is already set */ /*********************************/ if(!((u4_bs_vert >> u4_idx) & 0xf)) { /****************************************************/ /* If Bs is not set, evalaute conditions for Bs=1 */ /****************************************************/ UWORD32 u4_bs_temp1, u4_bs_temp2; UWORD32 u4_bs; /*********************************************************/ /* If any motion vector component differs by more than 1 */ /* integer pel or if reference pictures are different Bs */ /* is set to 1. Note that this condition shall be met for*/ /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ /*********************************************************/ i2_p_mv0 = ps_leftmost_mv_pred->i2_mv[0]; i2_p_mv1 = ps_leftmost_mv_pred->i2_mv[1]; i2_p_mv2 = ps_leftmost_mv_pred->i2_mv[2]; i2_p_mv3 = ps_leftmost_mv_pred->i2_mv[3]; pv_nbr_pic_addr0 = ps_left_addr->u4_add[i & 2]; pv_nbr_pic_addr1 = ps_left_addr->u4_add[1 + (i & 2)]; u4_bs_temp1 = ((ABS((i2_p_mv0 - i2_q_mv0)) >= 4) | (ABS((i2_p_mv1 - i2_q_mv1)) >= i4_ver_mvlimit) | (ABS((i2_p_mv2 - i2_q_mv2)) >= 4) | (ABS((i2_p_mv3 - i2_q_mv3)) >= i4_ver_mvlimit)); u4_bs_temp2 = ((ABS((i2_p_mv0 - i2_q_mv2)) >= 4) | (ABS((i2_p_mv1 - i2_q_mv3)) >= i4_ver_mvlimit) | (ABS((i2_p_mv2 - i2_q_mv0)) >= 4) | (ABS((i2_p_mv3 - i2_q_mv1)) >= i4_ver_mvlimit)); u4_bs = ((pv_cur_pic_addr0 != pv_nbr_pic_addr0) || (pv_cur_pic_addr1 != pv_nbr_pic_addr1) || u4_bs_temp1) && ((pv_cur_pic_addr0 != pv_nbr_pic_addr1) || (pv_cur_pic_addr1 != pv_nbr_pic_addr0) || u4_bs_temp2); u4_bs_vert |= (u4_bs << u4_idx); } } pu4_bs_table[4] = u4_bs_vert; return; } /*****************************************************************************/ /* */ /* Function Name : ih264d_fill_bs1_non16x16mb_bslice */ /* */ /* Description : This function fills boundray strength (=1) for those */ /* horz and vert edges of non16x16mb which are set to 0 by */ /* ih264d_fill_bs2_horz_vert. This function is used for b slices */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : If any motion vector component of adjacent 4x4 blocks */ /* differs by more than 1 integer pel or if reference */ /* pictures are different, Bs is set to 1. */ /* */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 16 10 2008 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_fill_bs1_non16x16mb_bslice(mv_pred_t *ps_cur_mv_pred, mv_pred_t *ps_top_mv_pred, void **ppv_map_ref_idx_to_poc, UWORD32 *pu4_bs_table, /* pointer to the BsTable array */ mv_pred_t *ps_leftmost_mv_pred, neighbouradd_t *ps_left_addr, void **u4_pic_addrress, WORD32 i4_ver_mvlimit) { UWORD32 edge; void **ppv_map_ref_idx_to_poc_l0, **ppv_map_ref_idx_to_poc_l1; ppv_map_ref_idx_to_poc_l0 = ppv_map_ref_idx_to_poc; ppv_map_ref_idx_to_poc_l1 = ppv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; PROFILE_DISABLE_BOUNDARY_STRENGTH() for(edge = 0; edge < 4; edge++, ps_top_mv_pred = ps_cur_mv_pred - 4) { /*********************************************************************/ /* Each iteration of this loop fills the four BS values of one HORIZ */ /* edge and one BS value for each of the four VERT edges. */ /*********************************************************************/ WORD32 i; UWORD32 u4_vert_idx = 24 - (edge << 3); UWORD32 u4_bs_horz = pu4_bs_table[edge]; mv_pred_t *ps_left_mv_pred = ps_leftmost_mv_pred + (edge << 2); for(i = 0; i < 4; i++, ps_top_mv_pred++, ps_cur_mv_pred++) { WORD16 i2_cur_mv0, i2_cur_mv1, i16_curMv2, i16_curMv3; WORD8 i1_cur_ref0, i1_cur_ref1; void *pv_cur_pic_addr0, *pv_cur_pic_addr1; void *pv_nbr_pic_addr0, *pv_nbr_pic_addr1; /******************************************************/ /* Each iteration of this inner loop computes a HORIZ */ /* and a VERT BS value for a 4x4 block */ /******************************************************/ UWORD32 u4_bs_vert = (pu4_bs_table[i + 4] >> u4_vert_idx) & 0xf; UWORD32 u4_horz_idx = 24 - (i << 3); /*****************************************************/ /* check if vert Bs for this block is already set */ /*****************************************************/ if(!u4_bs_vert) { WORD16 i2_left_mv0, i2_left_mv1, i2_left_mv2, i2_left_mv3; /************************************************************/ /* If Bs is not set, use left edge and current edge mvs and */ /* reference pictures addresses to evaluate Bs==1 */ /************************************************************/ i2_left_mv0 = ps_left_mv_pred->i2_mv[0]; i2_left_mv1 = ps_left_mv_pred->i2_mv[1]; i2_left_mv2 = ps_left_mv_pred->i2_mv[2]; i2_left_mv3 = ps_left_mv_pred->i2_mv[3]; i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; i16_curMv2 = ps_cur_mv_pred->i2_mv[2]; i16_curMv3 = ps_cur_mv_pred->i2_mv[3]; i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; i1_cur_ref1 = ps_cur_mv_pred->i1_ref_frame[1]; pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; pv_cur_pic_addr1 = ppv_map_ref_idx_to_poc_l1[i1_cur_ref1]; if(i) { WORD8 i1_left_ref0, i1_left_ref1; i1_left_ref0 = ps_left_mv_pred->i1_ref_frame[0]; i1_left_ref1 = ps_left_mv_pred->i1_ref_frame[1]; pv_nbr_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_left_ref0]; pv_nbr_pic_addr1 = ppv_map_ref_idx_to_poc_l1[i1_left_ref1]; } else { pv_nbr_pic_addr0 = ps_left_addr->u4_add[edge & 2]; pv_nbr_pic_addr1 = ps_left_addr->u4_add[1 + (edge & 2)]; } { UWORD32 u4_bs_temp1, u4_bs_temp2; /*********************************************************/ /* If any motion vector component differs by more than 1 */ /* integer pel or if reference pictures are different Bs */ /* is set to 1. Note that this condition shall be met for*/ /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ /*********************************************************/ u4_bs_temp1 = ((ABS((i2_left_mv0 - i2_cur_mv0)) >= 4) | (ABS((i2_left_mv1 - i2_cur_mv1)) >= i4_ver_mvlimit) | (ABS((i2_left_mv2 - i16_curMv2)) >= 4) | (ABS((i2_left_mv3 - i16_curMv3)) >= i4_ver_mvlimit)); u4_bs_temp2 = ((ABS((i2_left_mv0 - i16_curMv2)) >= 4) | (ABS((i2_left_mv1 - i16_curMv3)) >= i4_ver_mvlimit) | (ABS((i2_left_mv2 - i2_cur_mv0)) >= 4) | (ABS((i2_left_mv3 - i2_cur_mv1)) >= i4_ver_mvlimit)); u4_bs_vert = ((pv_nbr_pic_addr0 != pv_cur_pic_addr0) || (pv_nbr_pic_addr1 != pv_cur_pic_addr1) || u4_bs_temp1) && ((pv_nbr_pic_addr0 != pv_cur_pic_addr1) || (pv_nbr_pic_addr1 != pv_cur_pic_addr0) || u4_bs_temp2); pu4_bs_table[i + 4] |= (u4_bs_vert << u4_vert_idx); } } /*****************************************************/ /* check if horz Bs for this block is already set */ /*****************************************************/ if(!((u4_bs_horz >> u4_horz_idx) & 0xf)) { WORD16 i2_top_mv0, i2_top_mv1, i16_topMv2, i16_topMv3; /************************************************************/ /* If Bs is not set, use top edge and current edge mvs and */ /* reference pictures addresses to evaluate Bs==1 */ /************************************************************/ i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; i16_curMv2 = ps_cur_mv_pred->i2_mv[2]; i16_curMv3 = ps_cur_mv_pred->i2_mv[3]; i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; i1_cur_ref1 = ps_cur_mv_pred->i1_ref_frame[1]; i2_top_mv0 = ps_top_mv_pred->i2_mv[0]; i2_top_mv1 = ps_top_mv_pred->i2_mv[1]; i16_topMv2 = ps_top_mv_pred->i2_mv[2]; i16_topMv3 = ps_top_mv_pred->i2_mv[3]; pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; pv_cur_pic_addr1 = ppv_map_ref_idx_to_poc_l1[i1_cur_ref1]; if(edge) { WORD8 i1_top_ref0, i1_top_ref1; i1_top_ref0 = ps_top_mv_pred->i1_ref_frame[0]; i1_top_ref1 = ps_top_mv_pred->i1_ref_frame[1]; pv_nbr_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_top_ref0]; pv_nbr_pic_addr1 = ppv_map_ref_idx_to_poc_l1[i1_top_ref1]; } else { pv_nbr_pic_addr0 = u4_pic_addrress[i & 2]; pv_nbr_pic_addr1 = u4_pic_addrress[1 + (i & 2)]; } { UWORD32 u4_bs_temp1, u4_bs_temp2; UWORD32 u4_bs; /*********************************************************/ /* If any motion vector component differs by more than 1 */ /* integer pel or if reference pictures are different Bs */ /* is set to 1. Note that this condition shall be met for*/ /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ /*********************************************************/ u4_bs_temp1 = ((ABS((i2_top_mv0 - i2_cur_mv0)) >= 4) | (ABS((i2_top_mv1 - i2_cur_mv1)) >= i4_ver_mvlimit) | (ABS((i16_topMv2 - i16_curMv2)) >= 4) | (ABS((i16_topMv3 - i16_curMv3)) >= i4_ver_mvlimit)); u4_bs_temp2 = ((ABS((i2_top_mv0 - i16_curMv2)) >= 4) | (ABS((i2_top_mv1 - i16_curMv3)) >= i4_ver_mvlimit) | (ABS((i16_topMv2 - i2_cur_mv0)) >= 4) | (ABS((i16_topMv3 - i2_cur_mv1)) >= i4_ver_mvlimit)); u4_bs = ((pv_nbr_pic_addr0 != pv_cur_pic_addr0) || (pv_nbr_pic_addr1 != pv_cur_pic_addr1) || u4_bs_temp1) && ((pv_nbr_pic_addr0 != pv_cur_pic_addr1) || (pv_nbr_pic_addr1 != pv_cur_pic_addr0) || u4_bs_temp2); u4_bs_horz |= (u4_bs << u4_horz_idx); } } ps_left_mv_pred = ps_cur_mv_pred; } pu4_bs_table[edge] = u4_bs_horz; } } /*****************************************************************************/ /* */ /* Function Name : ih264d_fill_bs_xtra_left_edge_cur_fld */ /* */ /* Description : This function fills boundray strength (= 2 or 1) for */ /* xtra left mb edge when cur mb is field and left mb is */ /* frame. */ /* Inputs : */ /* */ /* Globals : <Does it use any global variables?> */ /* Processing : */ /* */ /* */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 16 10 2008 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_fill_bs_xtra_left_edge_cur_fld(UWORD32 *pu4_bs, /* Base pointer of BS table */ WORD32 u4_left_mb_t_csbp, /* left mbpair's top csbp */ WORD32 u4_left_mb_b_csbp, /* left mbpair's bottom csbp*/ WORD32 u4_cur_mb_csbp, /* csbp of current mb */ UWORD32 u4_cur_mb_top /* is top or bottom mb */ ) { const UWORD32 *pu4_packed_bs = (const UWORD32 *)gau4_ih264d_packed_bs2; UWORD32 u4_cur, u4_left, u4_or; UNUSED(u4_cur_mb_top); PROFILE_DISABLE_BOUNDARY_STRENGTH() u4_left_mb_t_csbp = ((u4_left_mb_t_csbp & 0x0008) >> 3) + ((u4_left_mb_t_csbp & 0x0080) >> 6) + ((u4_left_mb_t_csbp & 0x0800) >> 9) + ((u4_left_mb_t_csbp & 0x8000) >> 12); u4_left_mb_b_csbp = ((u4_left_mb_b_csbp & 0x0008) << 1) + ((u4_left_mb_b_csbp & 0x0080) >> 2) + ((u4_left_mb_b_csbp & 0x0800) >> 5) + ((u4_left_mb_b_csbp & 0x8000) >> 8); /*********************************************************************/ /* u4_cur = 0|0|0|0|0|0|0|0|12C|12C|8C|8C|4C|4C|0C|0C */ /*********************************************************************/ u4_cur = (u4_cur_mb_csbp & 0x0001) + ((u4_cur_mb_csbp & 0x0001) << 1) + ((u4_cur_mb_csbp & 0x0010) >> 2) + ((u4_cur_mb_csbp & 0x0010) >> 1) + ((u4_cur_mb_csbp & 0x0100) >> 4) + ((u4_cur_mb_csbp & 0x0100) >> 3) + ((u4_cur_mb_csbp & 0x1000) >> 6) + ((u4_cur_mb_csbp & 0x1000) >> 5); /*********************************************************************/ /* u4_left =0|0|0|0|0|0|0|0|15Lb|11Lb|7Lb|3Lb|15Lt|11Lt|7Lt|3Lt */ /*********************************************************************/ u4_left = u4_left_mb_t_csbp + u4_left_mb_b_csbp; u4_or = (u4_cur | u4_left); /*********************************************************************/ /* Fill vert edges (4,9) boundary strengths using look up table */ /*********************************************************************/ pu4_packed_bs += 16; pu4_bs[4] = pu4_packed_bs[u4_or & 0xF]; pu4_bs[9] = pu4_packed_bs[(u4_or >> 4)]; } /*****************************************************************************/ /* */ /* Function Name : ih264d_fill_bs_xtra_left_edge_cur_frm */ /* */ /* Description : This function fills boundray strength (= 2 or 1) for */ /* xtra left mb edge when cur mb is frame and left mb is */ /* field. */ /* Inputs : */ /* */ /* Globals : <Does it use any global variables?> */ /* Processing : */ /* */ /* */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 16 10 2008 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_fill_bs_xtra_left_edge_cur_frm(UWORD32 *pu4_bs, /* Base pointer of BS table */ WORD32 u4_left_mb_t_csbp, /* left mbpair's top csbp */ WORD32 u4_left_mb_b_csbp, /* left mbpair's bottom csbp*/ WORD32 u4_cur_mb_csbp, /* csbp of current mb */ UWORD32 u4_cur_mb_bot /* is top or bottom mb */ ) { const UWORD32 *pu4_packed_bs = (const UWORD32 *)gau4_ih264d_packed_bs2; UWORD32 u4_cur, u4_left, u4_or; UWORD32 u4_right_shift = (u4_cur_mb_bot << 3); PROFILE_DISABLE_BOUNDARY_STRENGTH() u4_left_mb_t_csbp >>= u4_right_shift; u4_left_mb_b_csbp >>= u4_right_shift; u4_left_mb_t_csbp = ((u4_left_mb_t_csbp & 0x08) >> 3) + ((u4_left_mb_t_csbp & 0x08) >> 2) + ((u4_left_mb_t_csbp & 0x80) >> 5) + ((u4_left_mb_t_csbp & 0x80) >> 4); u4_left_mb_b_csbp = ((u4_left_mb_b_csbp & 0x08) << 1) + ((u4_left_mb_b_csbp & 0x08) << 2) + ((u4_left_mb_b_csbp & 0x80) >> 1) + ((u4_left_mb_b_csbp & 0x80)); u4_cur = ((u4_cur_mb_csbp & 0x0001)) + ((u4_cur_mb_csbp & 0x0010) >> 3) + ((u4_cur_mb_csbp & 0x0100) >> 6) + ((u4_cur_mb_csbp & 0x1000) >> 9); u4_cur += (u4_cur << 4); u4_left = u4_left_mb_t_csbp + u4_left_mb_b_csbp; u4_or = (u4_cur | u4_left); /*********************************************************************/ /* Fill vert edges (4,9) boundary strengths using look up table */ /*********************************************************************/ pu4_packed_bs += 16; pu4_bs[4] = pu4_packed_bs[u4_or & 0xF]; pu4_bs[9] = pu4_packed_bs[(u4_or >> 4)]; } /*****************************************************************************/ /* */ /* Function Name : ih264d_fill_bs_xtra_top_edge */ /* */ /* Description : This function fills boundray strength (= 2 or 1) for */ /* xtra top mb edge when cur mb is top mb of frame mb pair */ /* and top mbpair is field coded. */ /* Inputs : */ /* */ /* Globals : <Does it use any global variables?> */ /* Processing : */ /* */ /* */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 16 10 2008 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_fill_bs_xtra_top_edge(UWORD32 *pu4_bs, /* Base pointer of BS table */ WORD32 u4_topmb_t_csbp, /* top mbpair's top csbp */ WORD32 u4_topmb_b_csbp, /* top mbpair's bottom csbp*/ WORD32 u4_cur_mb_csbp /* csbp of current mb */ ) { const UWORD32 *pu4_packed_bs = (const UWORD32 *)gau4_ih264d_packed_bs2; UWORD32 u4_or; u4_cur_mb_csbp &= 0xf; u4_topmb_t_csbp >>= 12; u4_topmb_b_csbp >>= 12; u4_or = (u4_cur_mb_csbp | u4_topmb_t_csbp); /*********************************************************************/ /* Fill vert edges (0,8) boundary strengths using look up table */ /*********************************************************************/ pu4_packed_bs += 16; pu4_bs[8] = pu4_packed_bs[u4_or]; u4_or = (u4_cur_mb_csbp | u4_topmb_b_csbp); pu4_bs[0] = pu4_packed_bs[u4_or]; } /*****************************************************************************/ /* */ /* Function Name : ih264d_compute_bs_non_mbaff */ /* */ /* Description : This function computes the pointers of left,top & current*/ /* : Nnz, MvPred & deblk_mb_t and supplies to FillBs function for*/ /* : Boundary Strength Calculation */ /* Inputs : <What inputs does the function take?> */ /* Processing : This functions calls deblock MB in the MB increment order*/ /* */ /* Outputs : Produces the Boundary Strength for Current Mb */ /* Returns : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* ITTIAM */ /*****************************************************************************/ void ih264d_compute_bs_non_mbaff(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, const UWORD16 u2_mbxn_mb) { /* Mvpred and Nnz for top and Courrent */ mv_pred_t *ps_cur_mv_pred, *ps_top_mv_pred = NULL, *ps_left_mv_pred; /* deblk_mb_t Params */ deblk_mb_t *ps_cur_mb_params; /*< Parameters of current MacroBlock */ deblkmb_neighbour_t *ps_deblk_top_mb; /* Reference Index to POC mapping*/ void ** apv_map_ref_idx_to_poc; UWORD32 u4_leftmbtype; UWORD16 u2_left_csbp, u2_top_csbp, u2_cur_csbp; /* Set of flags */ UWORD32 u4_cur_mb_intra, u1_top_mb_typ, u4_cur_mb_fld; UWORD32 u1_cur_mb_type; UWORD32 * pu4_bs_table; /* Neighbour availability */ /* Initialization */ const UWORD32 u2_mbx = ps_cur_mb_info->u2_mbx; const UWORD32 u2_mby = ps_cur_mb_info->u2_mby; const UWORD32 u1_pingpong = u2_mbx & 0x01; PROFILE_DISABLE_BOUNDARY_STRENGTH() ps_deblk_top_mb = ps_dec->ps_deblk_top_mb + u2_mbx; /* Pointer assignment for Current DeblkMB, Current Mv Pred */ ps_cur_mb_params = ps_dec->ps_deblk_mbn + u2_mbxn_mb; ps_cur_mv_pred = ps_dec->ps_mv_cur + (u2_mbxn_mb << 4); apv_map_ref_idx_to_poc = ps_dec->ppv_map_ref_idx_to_poc + 1; u1_cur_mb_type = ps_cur_mb_params->u1_mb_type; u1_top_mb_typ = ps_deblk_top_mb->u1_mb_type; ps_deblk_top_mb->u1_mb_type = u1_cur_mb_type; { UWORD8 mb_qp_temp; ps_cur_mb_params->u1_topmb_qp = ps_deblk_top_mb->u1_mb_qp; ps_deblk_top_mb->u1_mb_qp = ps_cur_mb_params->u1_mb_qp; ps_cur_mb_params->u1_left_mb_qp = ps_dec->deblk_left_mb[1].u1_mb_qp; ps_dec->deblk_left_mb[1].u1_mb_qp = ps_cur_mb_params->u1_mb_qp; } /* if no deblocking required for current Mb then continue */ /* Check next Mbs in Mb group */ if(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_FILTERING) { void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; { /* Store Parameter for Top MvPred refernce frame Address */ void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; /* Store Left addresses for Next Mb */ void ** ppv_left_mv_pred_addr = ps_dec->ps_left_mvpred_addr[!u1_pingpong][1].u4_add; WORD8 * p1_refleft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refleft0[0]]; ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refleft0[1]]; //} /* Storing the leftMbtype for next Mb */ ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; } return; } /* Flag for extra left Edge */ ps_cur_mb_params->u1_single_call = 1; /* Update the Left deblk_mb_t and Left MvPred Parameters */ if(!u2_mbx) { u4_leftmbtype = 0; /* Initialize the ps_left_mv_pred with Junk but Valid Location */ /* to avoid invalid memory access */ /* this is read only pointer */ ps_left_mv_pred = ps_dec->ps_mv_cur + 3; } else { u4_leftmbtype = ps_dec->deblk_left_mb[1].u1_mb_type; /* Come to Left Most Edge of the MB */ ps_left_mv_pred = (u2_mbxn_mb) ? ps_dec->ps_mv_cur + ((u2_mbxn_mb - 1) << 4) + 3 : ps_dec->ps_mv_left + 3; } if(!u2_mby) u1_top_mb_typ = 0; /* MvPred Pointer Calculation */ /* CHANGED CODE */ ps_top_mv_pred = ps_cur_mv_pred - (ps_dec->u2_frm_wd_in_mbs << 4) + 12; u4_cur_mb_intra = u1_cur_mb_type & D_INTRA_MB; u4_cur_mb_fld = !!(u1_cur_mb_type & D_FLD_MB); /* Compute BS function */ pu4_bs_table = ps_cur_mb_params->u4_bs_table; u2_cur_csbp = ps_cur_mb_info->ps_curmb->u2_luma_csbp; u2_left_csbp = ps_cur_mb_info->ps_left_mb->u2_luma_csbp; u2_top_csbp = ps_cur_mb_info->ps_top_mb->u2_luma_csbp; /* Compute BS function */ if(ps_dec->ps_cur_sps->u1_profile_idc == HIGH_PROFILE_IDC) { if(ps_cur_mb_info->u1_tran_form8x8 == 1) { u2_cur_csbp = ih264d_update_csbp_8x8( ps_cur_mb_info->ps_curmb->u2_luma_csbp); } if(ps_cur_mb_info->ps_left_mb->u1_tran_form8x8 == 1) { u2_left_csbp = ih264d_update_csbp_8x8( ps_cur_mb_info->ps_left_mb->u2_luma_csbp); } if(ps_cur_mb_info->ps_top_mb->u1_tran_form8x8 == 1) { u2_top_csbp = ih264d_update_csbp_8x8( ps_cur_mb_info->ps_top_mb->u2_luma_csbp); } } if(u4_cur_mb_intra) { pu4_bs_table[4] = 0x04040404; pu4_bs_table[0] = u4_cur_mb_fld ? 0x03030303 : 0x04040404; pu4_bs_table[1] = 0x03030303; pu4_bs_table[2] = 0x03030303; pu4_bs_table[3] = 0x03030303; pu4_bs_table[5] = 0x03030303; pu4_bs_table[6] = 0x03030303; pu4_bs_table[7] = 0x03030303; } else { UWORD32 u4_is_non16x16 = !!(u1_cur_mb_type & D_PRED_NON_16x16); UWORD32 u4_is_b = ps_dec->u1_B; ih264d_fill_bs2_horz_vert( pu4_bs_table, u2_left_csbp, u2_top_csbp, u2_cur_csbp, (const UWORD32 *)(gau4_ih264d_packed_bs2), (const UWORD16 *)(gau2_ih264d_4x4_v2h_reorder)); if(u4_leftmbtype & D_INTRA_MB) pu4_bs_table[4] = 0x04040404; if(u1_top_mb_typ & D_INTRA_MB) pu4_bs_table[0] = u4_cur_mb_fld ? 0x03030303 : 0x04040404; ps_dec->pf_fill_bs1[u4_is_b][u4_is_non16x16]( ps_cur_mv_pred, ps_top_mv_pred, apv_map_ref_idx_to_poc, pu4_bs_table, ps_left_mv_pred, &(ps_dec->ps_left_mvpred_addr[u1_pingpong][1]), ps_cur_mb_info->ps_top_mb->u4_pic_addrress, (4 >> u4_cur_mb_fld)); } { void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; { /* Store Parameter for Top MvPred refernce frame Address */ void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; /* Store Left addresses for Next Mb */ void ** ppv_left_mv_pred_addr = ps_dec->ps_left_mvpred_addr[!u1_pingpong][1].u4_add; WORD8 * p1_refleft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refleft0[0]]; ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refleft0[1]]; /* Storing the leftMbtype for next Mb */ ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; } } /* For transform 8x8 disable deblocking of the intrernal edges of a 8x8 block */ if(ps_cur_mb_info->u1_tran_form8x8) { pu4_bs_table[1] = 0; pu4_bs_table[3] = 0; pu4_bs_table[5] = 0; pu4_bs_table[7] = 0; } } /*****************************************************************************/ /* */ /* Function Name : ih264d_compute_bs_mbaff */ /* */ /* Description : This function computes the pointers of left,top & current*/ /* : Nnz, MvPred & deblk_mb_t and supplies to FillBs function for*/ /* : Boundary Strength Calculation */ /* Inputs : <What inputs does the function take?> */ /* Processing : This functions calls deblock MB in the MB increment order*/ /* */ /* Outputs : Produces the Boundary Strength for Current Mb */ /* Returns : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* ITTIAM */ /*****************************************************************************/ void ih264d_compute_bs_mbaff(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, const UWORD16 u2_mbxn_mb) { /* Mvpred and Nnz for top and Courrent */ mv_pred_t *ps_cur_mv_pred, *ps_top_mv_pred = NULL, *ps_left_mv_pred; /* deblk_mb_t Params */ deblk_mb_t *ps_cur_mb_params; /*< Parameters of current MacroBlock */ neighbouradd_t * ps_left_ngbr; deblkmb_neighbour_t *ps_deblk_top_mb; /* Reference Index to POC mapping*/ void ** apv_map_ref_idx_to_poc; UWORD32 u4_leftmbtype; UWORD16 u2_left_csbp, u2_top_csbp, u2_cur_csbp; /* Set of flags */ UWORD32 u4_cur_mb_intra, u4_cur_mb_fld, u4_top_mb_fld, u1_top_mb_typ, u4_left_mb_fld; UWORD32 u1_cur_mb_type; UWORD32 * pu4_bs_table; const UWORD32 u4_bot_mb = (1 - ps_cur_mb_info->u1_topmb); /* Initialization */ const UWORD32 u2_mbx = ps_cur_mb_info->u2_mbx; const UWORD32 u2_mby = ps_cur_mb_info->u2_mby; /* Load From u1_pingpong and Store in !u1_pingpong */ const UWORD32 u1_pingpong = u2_mbx & 0x01; PROFILE_DISABLE_BOUNDARY_STRENGTH() ps_deblk_top_mb = ps_dec->ps_deblk_top_mb + (u2_mbx << 1); /************************************************/ /* Initialize the left Mb type */ /* Left MvPred */ /************************************************/ if(!u2_mbx) { /************************************************************/ /* Initialize the ps_left_mv_pred with Junk but Valid Location */ /* to avoid invalid memory access */ /* this is read only pointer */ /************************************************************/ ps_left_mv_pred = ps_dec->ps_mv_cur + 16; } else { /* Come to Left Most Edge of the MB */ ps_left_mv_pred = (u2_mbxn_mb) ? ps_dec->ps_mv_cur + ((u2_mbxn_mb - 1) << 5) + 3 : ps_dec->ps_mv_left + 3; ps_left_mv_pred += (u4_bot_mb << 4); } u4_leftmbtype = ps_dec->deblk_left_mb[u4_bot_mb].u1_mb_type; ps_left_ngbr = &(ps_dec->ps_left_mvpred_addr[u1_pingpong][u4_bot_mb]); /************************************************/ /* Pointer Assignment for Current Mb Parameters */ /* Pointer Assignment for Current MvPred */ /************************************************/ ps_cur_mb_params = ps_dec->ps_deblk_mbn + (u2_mbxn_mb << 1) + u4_bot_mb; u1_cur_mb_type = ps_cur_mb_params->u1_mb_type; ps_cur_mv_pred = ps_dec->ps_mv_cur + (u2_mbxn_mb << 5); ps_cur_mv_pred += (u4_bot_mb << 4); /********************************************/ /* Pointer Assignment for Top Mb Parameters */ /* Pointer Assignment for Top MvPred and */ /* Pointer Assignment for Top Nnz */ /********************************************/ /* CHANGED CODE */ ps_top_mv_pred = ps_cur_mv_pred - (ps_dec->u2_frm_wd_in_mbs << 5) + 12; u4_cur_mb_fld = !!(u1_cur_mb_type & D_FLD_MB); u4_left_mb_fld = !!(ps_dec->deblk_left_mb[0].u1_mb_type & D_FLD_MB); if(u4_left_mb_fld != u4_cur_mb_fld) { /* Flag for extra left Edge */ ps_cur_mb_params->u1_single_call = 0; if(u4_bot_mb) { ps_left_ngbr--; ps_left_mv_pred -= 16; } } else ps_cur_mb_params->u1_single_call = 1; apv_map_ref_idx_to_poc = ps_dec->ppv_map_ref_idx_to_poc + 1; if(u4_cur_mb_fld) { if(u4_bot_mb) { apv_map_ref_idx_to_poc += BOT_LIST_FLD_L0; } else { apv_map_ref_idx_to_poc += TOP_LIST_FLD_L0; } } /**********************************************************/ /* if no deblocking required for current Mb then continue */ /**********************************************************/ if(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_FILTERING) { void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; { /* Store Parameter for Top MvPred refernce frame Address */ void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; void ** ppv_left_mv_pred_addr = ps_dec->ps_left_mvpred_addr[!u1_pingpong][u4_bot_mb].u4_add; WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; WORD8 * p1_refLeft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refLeft0[0]]; ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refLeft0[1]]; } if(u4_bot_mb) { /* store The Left Mb Type*/ ps_dec->deblk_left_mb[0].u1_mb_type = (ps_cur_mb_params - 1)->u1_mb_type; ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; } ps_deblk_top_mb[u4_bot_mb].u1_mb_type = u1_cur_mb_type; return; } if(u2_mby) { u1_top_mb_typ = ps_deblk_top_mb[1].u1_mb_type; u4_top_mb_fld = !!(u1_top_mb_typ & D_FLD_MB); if(!u4_bot_mb) { if(u4_top_mb_fld & u4_cur_mb_fld) u1_top_mb_typ = ps_deblk_top_mb[0].u1_mb_type; else { ps_top_mv_pred += 16; } } } else { u4_top_mb_fld = u4_cur_mb_fld; u1_top_mb_typ = 0; } if(u4_bot_mb & !u4_cur_mb_fld) { u1_top_mb_typ = ps_deblk_top_mb[0].u1_mb_type; u4_top_mb_fld = u4_cur_mb_fld; ps_top_mv_pred = ps_cur_mv_pred - 4; } pu4_bs_table = ps_cur_mb_params->u4_bs_table; u4_cur_mb_intra = u1_cur_mb_type & D_INTRA_MB; u2_cur_csbp = ps_cur_mb_info->ps_curmb->u2_luma_csbp; u2_left_csbp = ps_cur_mb_info->ps_left_mb->u2_luma_csbp; u2_top_csbp = ps_cur_mb_info->ps_top_mb->u2_luma_csbp; /* Compute BS function */ if(ps_dec->ps_cur_sps->u1_profile_idc == HIGH_PROFILE_IDC) { if(ps_cur_mb_info->u1_tran_form8x8 == 1) { u2_cur_csbp = ih264d_update_csbp_8x8( ps_cur_mb_info->ps_curmb->u2_luma_csbp); } if(ps_cur_mb_info->ps_left_mb->u1_tran_form8x8 == 1) { u2_left_csbp = ih264d_update_csbp_8x8( ps_cur_mb_info->ps_left_mb->u2_luma_csbp); } if(ps_cur_mb_info->ps_top_mb->u1_tran_form8x8 == 1) { u2_top_csbp = ih264d_update_csbp_8x8( ps_cur_mb_info->ps_top_mb->u2_luma_csbp); } } if(u4_cur_mb_intra) { pu4_bs_table[4] = 0x04040404; if((0 == u4_cur_mb_fld) && (0 == u4_top_mb_fld)) { pu4_bs_table[0] = 0x04040404; } else { pu4_bs_table[0] = 0x03030303; } pu4_bs_table[1] = 0x03030303; pu4_bs_table[2] = 0x03030303; pu4_bs_table[3] = 0x03030303; pu4_bs_table[5] = 0x03030303; pu4_bs_table[6] = 0x03030303; pu4_bs_table[7] = 0x03030303; /*********************************************************************/ /* Fill Bs of xtra top and left edge unconditionally to avoid checks */ /*********************************************************************/ pu4_bs_table[8] = 0x03030303; pu4_bs_table[9] = 0x04040404; } else { UWORD32 u4_is_non16x16 = !!(u1_cur_mb_type & D_PRED_NON_16x16); UWORD32 u4_is_b = ps_dec->u1_B; ih264d_fill_bs2_horz_vert( pu4_bs_table, u2_left_csbp, u2_top_csbp, u2_cur_csbp, (const UWORD32 *)(gau4_ih264d_packed_bs2), (const UWORD16 *)(gau2_ih264d_4x4_v2h_reorder)); if(u4_leftmbtype & D_INTRA_MB) pu4_bs_table[4] = 0x04040404; if(u1_top_mb_typ & D_INTRA_MB) pu4_bs_table[0] = u4_cur_mb_fld ? 0x03030303 : 0x04040404; else if(u4_cur_mb_fld != u4_top_mb_fld) { /****************************************************/ /* Setting BS for mixed mode edge=1 when (Bs!=2) */ /****************************************************/ pu4_bs_table[0] = (pu4_bs_table[0] >> 1) + 0x01010101; } { /* Call to Compute Boundary Strength for Extra Left Edge */ if(u2_mbx && !(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_LEFT_EDGE)) { if(u4_cur_mb_fld != u4_left_mb_fld) { UWORD32 u4_left_mb_t_csbp = ps_cur_mb_info->ps_left_mb[0].u2_luma_csbp; UWORD32 u4_left_mb_b_csbp = ps_cur_mb_info->ps_left_mb[1].u2_luma_csbp; if(1 == ps_cur_mb_info->ps_left_mb[0].u1_tran_form8x8) { u4_left_mb_t_csbp = (UWORD32)ih264d_update_csbp_8x8( (UWORD16)u4_left_mb_t_csbp); } if(1 == ps_cur_mb_info->ps_left_mb[1].u1_tran_form8x8) { u4_left_mb_b_csbp = (UWORD32)ih264d_update_csbp_8x8( (UWORD16)u4_left_mb_b_csbp); } ps_dec->pf_fill_bs_xtra_left_edge[u4_cur_mb_fld]( pu4_bs_table, u4_left_mb_t_csbp, u4_left_mb_b_csbp, u2_cur_csbp, u4_bot_mb); if(ps_dec->deblk_left_mb[0].u1_mb_type & D_INTRA_MB) pu4_bs_table[4] = 0x04040404; if(ps_dec->deblk_left_mb[1].u1_mb_type & D_INTRA_MB) pu4_bs_table[9] = 0x04040404; } } /* Call to Compute Boundary Strength for Extra Top Edge */ if(u2_mby && !(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_TOP_EDGE)) { if((((!u4_bot_mb) & (!u4_cur_mb_fld)) && u4_top_mb_fld)) { UWORD32 u4_topmb_t_csbp = ps_cur_mb_info->ps_top_mb[-1].u2_luma_csbp; UWORD32 u4_topmb_b_csbp = ps_cur_mb_info->ps_top_mb[0].u2_luma_csbp; if(1 == ps_cur_mb_info->ps_top_mb[-1].u1_tran_form8x8) { u4_topmb_t_csbp = (UWORD32)ih264d_update_csbp_8x8( (UWORD16)u4_topmb_t_csbp); } if(1 == ps_cur_mb_info->ps_top_mb[0].u1_tran_form8x8) { u4_topmb_b_csbp = (UWORD32)ih264d_update_csbp_8x8( (UWORD16)u4_topmb_b_csbp); } ih264d_fill_bs_xtra_top_edge(pu4_bs_table, u4_topmb_t_csbp, u4_topmb_b_csbp, u2_cur_csbp); if(ps_deblk_top_mb[0].u1_mb_type & D_INTRA_MB) pu4_bs_table[8] = 0x03030303; if(ps_deblk_top_mb[1].u1_mb_type & D_INTRA_MB) pu4_bs_table[0] = 0x03030303; } } } ps_dec->pf_fill_bs1[u4_is_b][u4_is_non16x16]( ps_cur_mv_pred, ps_top_mv_pred, apv_map_ref_idx_to_poc, pu4_bs_table, ps_left_mv_pred, ps_left_ngbr, ps_cur_mb_info->ps_top_mb->u4_pic_addrress, (4 >> u4_cur_mb_fld)); } { void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; { /* Store Parameter for Top MvPred refernce frame Address */ void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; void ** ppv_left_mv_pred_addr = ps_dec->ps_left_mvpred_addr[!u1_pingpong][u4_bot_mb].u4_add; WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; WORD8 * p1_refLeft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refLeft0[0]]; ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refLeft0[1]]; } if(u4_bot_mb) { /* store The Left Mb Type*/ ps_dec->deblk_left_mb[0].u1_mb_type = (ps_cur_mb_params - 1)->u1_mb_type; ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; } ps_deblk_top_mb[u4_bot_mb].u1_mb_type = u1_cur_mb_type; } /* For transform 8x8 disable deblocking of the intrernal edges of a 8x8 block */ if(ps_cur_mb_info->u1_tran_form8x8) { pu4_bs_table[1] = 0; pu4_bs_table[3] = 0; pu4_bs_table[5] = 0; pu4_bs_table[7] = 0; } } /*! ************************************************************************** * \if Function name : ih264d_fill_bs_for_mb \endif * * \brief * Determines the boundary strength (Bs), for the complete MB. Bs is * determined for each block boundary between two neighbouring 4x4 * luma blocks, then packed in a UWORD32, first Bs placed in MSB and * so on. Such packed Bs values for all 8 edges are kept in an array. * * \return * Returns the packed boundary strength(Bs) MSB -> LSB Bs0|Bs1|Bs2|Bs3 * ************************************************************************** */ void ih264d_fill_bs_for_mb(deblk_mb_t * ps_cur_mb_params, deblk_mb_t * ps_top_mb_params, deblk_mb_t * ps_left_mb_params, mv_pred_t *ps_cur_mv_pred, mv_pred_t *ps_top_mv_pred, UWORD8 *puc_cur_nnz, UWORD8 *puc_top_nnz, void **ppv_map_ref_idx_to_poc, UWORD32 ui_mbAff, UWORD32 ui_bs_table[], /* pointer to the BsTable array */ mv_pred_t *ps_leftmost_mv_pred, neighbouradd_t *ps_left_addr, neighbouradd_t *ps_top_add) { UWORD32 u4_bs_horz = 0; UWORD8 edge, u1_top_intra = 0, u1_left_intra = 0; mv_pred_t *ps_left_mv_pred; WORD16 i2_cur_mv0, i2_cur_mv1, i16_curMv2, i16_curMv3; WORD16 i2_left_mv0, i2_left_mv1, i2_left_mv2, i2_left_mv3; WORD16 i2_top_mv0, i2_top_mv1, i16_topMv2, i16_topMv3; WORD8 i1_cur_ref0, i1_cur_ref1, i1_left_ref0, i1_left_ref1, i1_top_ref0, i1_top_ref1; UWORD8 uc_cur_nnz, uc_left_nnz, uc_top_nnz, u1_mb_type, uc_Bslice; void **ppv_map_ref_idx_to_poc_l0, **ppv_map_ref_idx_to_poc_l1; UWORD8 uc_temp; UWORD8 uc_cur_mb_fld, uc_top_mb_fld; UWORD32 c_mv_limit; u1_mb_type = ps_cur_mb_params->u1_mb_type; uc_Bslice = u1_mb_type & D_B_SLICE; ppv_map_ref_idx_to_poc_l0 = ppv_map_ref_idx_to_poc; ppv_map_ref_idx_to_poc_l1 = ppv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; ps_top_mb_params = ps_top_mb_params ? ps_top_mb_params : ps_cur_mb_params; u1_top_intra = ps_top_mb_params->u1_mb_type & D_INTRA_MB; u1_left_intra = ps_left_mb_params->u1_mb_type & D_INTRA_MB; ui_bs_table[4] = 0x04040404; //Default for INTRA MB Boundary edges. uc_cur_mb_fld = (ps_cur_mb_params->u1_mb_type & D_FLD_MB) >> 7; uc_top_mb_fld = (ps_top_mb_params->u1_mb_type & D_FLD_MB) >> 7; c_mv_limit = 4 >> uc_cur_mb_fld; if((0 == uc_cur_mb_fld) && (0 == uc_top_mb_fld)) { ui_bs_table[0] = 0x04040404; } else { ui_bs_table[0] = 0x03030303; } for(edge = 0; edge < 4; edge++, ps_top_mv_pred = ps_cur_mv_pred - 4, puc_top_nnz = puc_cur_nnz - 4) { //Each iteration of this loop fills the four BS values of one HORIZ edge and //one BS value for each of the four VERT edges. WORD8 i = 0; UWORD8 uc_bs_horiz, uc_bs_vert; UWORD32 ui_cnd; void *ui_ref_pic_addr[4]; UWORD8 uc_mixed_mode_edge; uc_mixed_mode_edge = 0; uc_temp = (ui_mbAff << 4) + 13; uc_cur_nnz = *(puc_cur_nnz - uc_temp); ps_left_mv_pred = ps_leftmost_mv_pred + (edge << 2); for(i = 0; i < 4; i++, ps_top_mv_pred++, ps_cur_mv_pred++) { //Each iteration of this inner loop computes a HORIZ //and a VERT BS value for a 4x4 block uc_left_nnz = uc_cur_nnz; uc_cur_nnz = *puc_cur_nnz++; uc_top_nnz = *puc_top_nnz++; //VERT edge is assigned BS values first ui_cnd = !(uc_left_nnz || uc_cur_nnz); uc_bs_vert = 2; if(ui_cnd) { i2_left_mv0 = ps_left_mv_pred->i2_mv[0]; i2_left_mv1 = ps_left_mv_pred->i2_mv[1]; i2_left_mv2 = ps_left_mv_pred->i2_mv[2]; i2_left_mv3 = ps_left_mv_pred->i2_mv[3]; i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; i16_curMv2 = ps_cur_mv_pred->i2_mv[2]; i16_curMv3 = ps_cur_mv_pred->i2_mv[3]; i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; i1_cur_ref1 = ps_cur_mv_pred->i1_ref_frame[1]; ui_ref_pic_addr[2] = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; ui_ref_pic_addr[3] = ppv_map_ref_idx_to_poc_l1[i1_cur_ref1]; if(i) { i1_left_ref0 = ps_left_mv_pred->i1_ref_frame[0]; i1_left_ref1 = ps_left_mv_pred->i1_ref_frame[1]; ui_ref_pic_addr[0] = ppv_map_ref_idx_to_poc_l0[i1_left_ref0]; ui_ref_pic_addr[1] = ppv_map_ref_idx_to_poc_l1[i1_left_ref1]; } else { ui_ref_pic_addr[0] = ps_left_addr->u4_add[edge & 2]; ui_ref_pic_addr[1] = ps_left_addr->u4_add[1 + (edge & 2)]; } if(!uc_Bslice) { uc_bs_vert = (ui_ref_pic_addr[0] != ui_ref_pic_addr[2]) | (ABS((i2_left_mv0 - i2_cur_mv0)) >= 4) | (ABS((i2_left_mv1 - i2_cur_mv1)) >= (UWORD8)c_mv_limit); } else { UWORD8 uc_bs_temp1, uc_bs_temp2; uc_bs_vert = 1; uc_bs_temp1 = ((ABS((i2_left_mv0 - i2_cur_mv0)) >= 4) | (ABS((i2_left_mv1 - i2_cur_mv1)) >= (UWORD8)c_mv_limit) | (ABS((i2_left_mv2 - i16_curMv2)) >= 4) | (ABS((i2_left_mv3 - i16_curMv3)) >= (UWORD8)c_mv_limit)); uc_bs_temp2 = ((ABS((i2_left_mv0 - i16_curMv2)) >= 4) | (ABS((i2_left_mv1 - i16_curMv3)) >= (UWORD8)c_mv_limit) | (ABS((i2_left_mv2 - i2_cur_mv0)) >= 4) | (ABS((i2_left_mv3 - i2_cur_mv1)) >= (UWORD8)c_mv_limit)); uc_bs_vert = (((ui_ref_pic_addr[0] != ui_ref_pic_addr[2]) || (ui_ref_pic_addr[1] != ui_ref_pic_addr[3])) || (uc_bs_temp1)) && (((ui_ref_pic_addr[0] != ui_ref_pic_addr[3]) || (ui_ref_pic_addr[1] != ui_ref_pic_addr[2])) || (uc_bs_temp2)); } } //Fill the VERT BS, only if valid i.e., //if it is a non-edge OR it is an edge, which is not yet filled uc_bs_vert = (!i && u1_left_intra) ? 4 : uc_bs_vert; ui_bs_table[i + 4] = (ui_bs_table[i + 4] << 8) | uc_bs_vert; //HORIZ edge is assigned BS values next ui_cnd = !(uc_top_nnz || uc_cur_nnz); uc_bs_horiz = 2; if(ui_cnd) { uc_mixed_mode_edge = (0 == edge) ? (uc_top_mb_fld != uc_cur_mb_fld) : 0; ui_cnd = 1 - uc_mixed_mode_edge; uc_bs_horiz = uc_mixed_mode_edge; } if(ui_cnd) { i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; i16_curMv2 = ps_cur_mv_pred->i2_mv[2]; i16_curMv3 = ps_cur_mv_pred->i2_mv[3]; i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; i1_cur_ref1 = ps_cur_mv_pred->i1_ref_frame[1]; i2_top_mv0 = ps_top_mv_pred->i2_mv[0]; i2_top_mv1 = ps_top_mv_pred->i2_mv[1]; i16_topMv2 = ps_top_mv_pred->i2_mv[2]; i16_topMv3 = ps_top_mv_pred->i2_mv[3]; ui_ref_pic_addr[2] = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; ui_ref_pic_addr[3] = ppv_map_ref_idx_to_poc_l1[i1_cur_ref1]; if(edge) { i1_top_ref0 = ps_top_mv_pred->i1_ref_frame[0]; i1_top_ref1 = ps_top_mv_pred->i1_ref_frame[1]; ui_ref_pic_addr[0] = ppv_map_ref_idx_to_poc_l0[i1_top_ref0]; ui_ref_pic_addr[1] = ppv_map_ref_idx_to_poc_l1[i1_top_ref1]; } else { ui_ref_pic_addr[0] = ps_top_add->u4_add[i & 2]; ui_ref_pic_addr[1] = ps_top_add->u4_add[1 + (i & 2)]; } if(!uc_Bslice) { uc_bs_horiz = (ui_ref_pic_addr[0] != ui_ref_pic_addr[2]) | (ABS((i2_top_mv0 - i2_cur_mv0)) >= 4) | (ABS((i2_top_mv1 - i2_cur_mv1)) >= (UWORD8)c_mv_limit); } else { UWORD8 uc_bs_temp1, uc_bs_temp2; uc_bs_horiz = 1; uc_bs_temp1 = ((ABS((i2_top_mv0 - i2_cur_mv0)) >= 4) | (ABS((i2_top_mv1 - i2_cur_mv1)) >= (UWORD8)c_mv_limit) | (ABS((i16_topMv2 - i16_curMv2)) >= 4) | (ABS((i16_topMv3 - i16_curMv3)) >= (UWORD8)c_mv_limit)); uc_bs_temp2 = ((ABS((i2_top_mv0 - i16_curMv2)) >= 4) | (ABS((i2_top_mv1 - i16_curMv3)) >= (UWORD8)c_mv_limit) | (ABS((i16_topMv2 - i2_cur_mv0)) >= 4) | (ABS((i16_topMv3 - i2_cur_mv1)) >= (UWORD8)c_mv_limit)); uc_bs_horiz = (((ui_ref_pic_addr[0] != ui_ref_pic_addr[2]) || (ui_ref_pic_addr[1] != ui_ref_pic_addr[3])) || (uc_bs_temp1)) && (((ui_ref_pic_addr[0] != ui_ref_pic_addr[3]) || (ui_ref_pic_addr[1] != ui_ref_pic_addr[2])) || (uc_bs_temp2)); } } ps_left_mv_pred = ps_cur_mv_pred; u4_bs_horz = (u4_bs_horz << 8) + uc_bs_horiz; } //Fill the HORIZ BS, only if valid i.e., //if it is a non-edge OR it is an edge, which is not yet filled if(edge || (!edge && !u1_top_intra)) ui_bs_table[edge] = u4_bs_horz; } } /*! ************************************************************************** * \if Function name : ih264d_fill_bs_for_extra_left_edge \endif * * \brief * Fills the boundary strength (Bs), for the top extra edge. ock * * \return * Returns the packed boundary strength(Bs) MSB -> LSB Bs0|Bs1|Bs2|Bs3 * ************************************************************************** */ void ih264d_fill_bs_for_extra_left_edge(deblk_mb_t *ps_cur_deblk_mb, deblk_mb_t *ps_leftDeblkMb, UWORD8* puc_cur_nnz, UWORD8 uc_botMb) { /* Set the Flag in uc_deblocking_mode variable of current MB*/ /* for mixed mode edge*/ ps_cur_deblk_mb->u1_single_call = 0; if(ps_cur_deblk_mb->u1_mb_type & D_INTRA_MB) { ps_cur_deblk_mb->u4_bs_table[4] = 0x04040404; ps_cur_deblk_mb->u4_bs_table[9] = 0x04040404; } else if((ps_leftDeblkMb->u1_mb_type & D_INTRA_MB) && ((ps_leftDeblkMb + 1)->u1_mb_type & D_INTRA_MB)) { ps_cur_deblk_mb->u4_bs_table[4] = 0x04040404; ps_cur_deblk_mb->u4_bs_table[9] = 0x04040404; } else { /* Get strengths of left MB edge */ UWORD32 u4_bs; UWORD8 uc_Bs; WORD32 i; UWORD32 ui_curMbFld; UWORD8 *puc_left_nnz; UWORD32 ui_bs_left_edge[2]; ui_curMbFld = (ps_cur_deblk_mb->u1_mb_type & D_FLD_MB) >> 7; puc_left_nnz = puc_cur_nnz - 29; if((ui_curMbFld == 0) && uc_botMb) { puc_left_nnz -= 8; } else if(ui_curMbFld && uc_botMb) { puc_left_nnz -= 16; } if(ui_curMbFld) { if(ps_leftDeblkMb->u1_mb_type & D_INTRA_MB) { ui_bs_left_edge[0] = 0x04040404; puc_left_nnz += 16; puc_cur_nnz += 8; } else { u4_bs = 0; for(i = 4; i > 0; i--) { uc_Bs = ((*puc_cur_nnz || *puc_left_nnz)) ? 2 : 1; u4_bs = (u4_bs << 8) | uc_Bs; puc_left_nnz += 4; if(i & 0x01) puc_cur_nnz += 4; } ui_bs_left_edge[0] = u4_bs; } if((ps_leftDeblkMb + 1)->u1_mb_type & D_INTRA_MB) { ui_bs_left_edge[1] = 0x04040404; } else { u4_bs = 0; for(i = 4; i > 0; i--) { uc_Bs = ((*puc_cur_nnz || *puc_left_nnz)) ? 2 : 1; u4_bs = (u4_bs << 8) | uc_Bs; puc_left_nnz += 4; if(i & 0x01) puc_cur_nnz += 4; } ui_bs_left_edge[1] = u4_bs; } } else { UWORD8 *puc_curNnzB, *puc_leftNnzB; puc_curNnzB = puc_cur_nnz; puc_leftNnzB = puc_left_nnz + 16; if(ps_leftDeblkMb->u1_mb_type & D_INTRA_MB) { ui_bs_left_edge[0] = 0x04040404; } else { u4_bs = 0; for(i = 4; i > 0; i--, puc_cur_nnz += 4) { uc_Bs = ((*puc_cur_nnz || *puc_left_nnz)) ? 2 : 1; u4_bs = (u4_bs << 8) | uc_Bs; if(i & 0x01) puc_left_nnz += 4; } ui_bs_left_edge[0] = u4_bs; } if((ps_leftDeblkMb + 1)->u1_mb_type & D_INTRA_MB) { ui_bs_left_edge[1] = 0x04040404; } else { u4_bs = 0; for(i = 4; i > 0; i--, puc_curNnzB += 4) { uc_Bs = ((*puc_curNnzB || *puc_leftNnzB)) ? 2 : 1; u4_bs = (u4_bs << 8) | uc_Bs; if(i & 0x01) puc_leftNnzB += 4; } ui_bs_left_edge[1] = u4_bs; } } /* Copy The Values in Cur Deblk Mb Parameters */ ps_cur_deblk_mb->u4_bs_table[4] = ui_bs_left_edge[0]; ps_cur_deblk_mb->u4_bs_table[9] = ui_bs_left_edge[1]; } } /*! ************************************************************************** * \if Function name : ih264d_fill_bs_for_extra_top_edge \endif * * \brief * Fills the boundary strength (Bs), for the top extra edge. ock * * \return * Returns the packed boundary strength(Bs) MSB -> LSB Bs0|Bs1|Bs2|Bs3 * ************************************************************************** */ void ih264d_fill_bs_for_extra_top_edge(deblk_mb_t *ps_cur_mb_params, UWORD8 u1_Edge0_mb_typ, UWORD8 u1_Edge1_mb_typ, UWORD8 *pu1_curNnz, UWORD8 *pu1_topNnz) { UWORD32 u4_bs; UWORD8 uc_Bs; WORD32 i; UWORD8 *pu1_cur_nnz_tmp; UWORD8 *pu1_top_nnz_tmp; UWORD8 u1_top_edge; UWORD8 u1_top_mb_type; for(u1_top_edge = 0; u1_top_edge < 2; u1_top_edge++) { u1_top_mb_type = u1_top_edge ? u1_Edge1_mb_typ : u1_Edge0_mb_typ; pu1_cur_nnz_tmp = pu1_curNnz; pu1_top_nnz_tmp = pu1_topNnz + (u1_top_edge << 2); if((ps_cur_mb_params->u1_mb_type & D_INTRA_MB) + (u1_top_mb_type & D_INTRA_MB)) { u4_bs = 0x03030303; } else { u4_bs = 0; for(i = 4; i > 0; i--, pu1_cur_nnz_tmp += 1, pu1_top_nnz_tmp += 1) { uc_Bs = ((*pu1_cur_nnz_tmp || *pu1_top_nnz_tmp)) ? 2 : 1; u4_bs = (u4_bs << 8) | uc_Bs; } } if(u1_top_edge) ps_cur_mb_params->u4_bs_table[0] = u4_bs; else ps_cur_mb_params->u4_bs_table[8] = u4_bs; } } void ih264d_fill_bs_mbedge_4(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, const UWORD16 u2_mbxn_mb) { /* deblk_mb_t Params */ deblk_mb_t *ps_cur_mb_params; /*< Parameters of current MacroBlock */ deblkmb_neighbour_t *ps_deblk_top_mb; UWORD32 * pu4_bs_table; UWORD8 u1_cur_mb_type; /* Neighbour availability */ /* Initialization */ const UWORD32 u2_mbx = ps_cur_mb_info->u2_mbx; const UWORD32 u2_mby = ps_cur_mb_info->u2_mby; const UWORD32 u1_pingpong = u2_mbx & 0x01; ps_deblk_top_mb = ps_dec->ps_deblk_top_mb + u2_mbx; /* Pointer assignment for Current DeblkMB, Current Mv Pred */ ps_cur_mb_params = ps_dec->ps_deblk_mbn + u2_mbxn_mb; u1_cur_mb_type = ps_cur_mb_params->u1_mb_type; ps_deblk_top_mb->u1_mb_type = u1_cur_mb_type; { UWORD8 mb_qp_temp; ps_cur_mb_params->u1_topmb_qp = ps_deblk_top_mb->u1_mb_qp; ps_deblk_top_mb->u1_mb_qp = ps_cur_mb_params->u1_mb_qp; ps_cur_mb_params->u1_left_mb_qp = ps_dec->deblk_left_mb[1].u1_mb_qp; ps_dec->deblk_left_mb[1].u1_mb_qp = ps_cur_mb_params->u1_mb_qp; } ps_cur_mb_params->u1_single_call = 1; ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; /* if no deblocking required for current Mb then continue */ /* Check next Mbs in Mb group */ if(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_FILTERING) { /* Storing the leftMbtype for next Mb */ return; } /* Compute BS function */ pu4_bs_table = ps_cur_mb_params->u4_bs_table; pu4_bs_table[4] = 0x04040404; pu4_bs_table[0] = 0x04040404; pu4_bs_table[1] = 0; pu4_bs_table[2] = 0; pu4_bs_table[3] = 0; pu4_bs_table[5] = 0; pu4_bs_table[6] = 0; pu4_bs_table[7] = 0; } void ih264d_fill_bs_mbedge_2(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, const UWORD16 u2_mbxn_mb) { /* deblk_mb_t Params */ deblk_mb_t *ps_cur_mb_params; /*< Parameters of current MacroBlock */ deblkmb_neighbour_t *ps_deblk_top_mb; UWORD32 * pu4_bs_table; UWORD8 u1_cur_mb_type; /* Neighbour availability */ /* Initialization */ const UWORD32 u2_mbx = ps_cur_mb_info->u2_mbx; const UWORD32 u2_mby = ps_cur_mb_info->u2_mby; const UWORD32 u1_pingpong = u2_mbx & 0x01; ps_deblk_top_mb = ps_dec->ps_deblk_top_mb + u2_mbx; /* Pointer assignment for Current DeblkMB, Current Mv Pred */ ps_cur_mb_params = ps_dec->ps_deblk_mbn + u2_mbxn_mb; u1_cur_mb_type = ps_cur_mb_params->u1_mb_type; ps_deblk_top_mb->u1_mb_type = u1_cur_mb_type; { UWORD8 mb_qp_temp; ps_cur_mb_params->u1_topmb_qp = ps_deblk_top_mb->u1_mb_qp; ps_deblk_top_mb->u1_mb_qp = ps_cur_mb_params->u1_mb_qp; ps_cur_mb_params->u1_left_mb_qp = ps_dec->deblk_left_mb[1].u1_mb_qp; ps_dec->deblk_left_mb[1].u1_mb_qp = ps_cur_mb_params->u1_mb_qp; } ps_cur_mb_params->u1_single_call = 1; ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; /* if no deblocking required for current Mb then continue */ /* Check next Mbs in Mb group */ if(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_FILTERING) { /* Storing the leftMbtype for next Mb */ return; } /* Compute BS function */ pu4_bs_table = ps_cur_mb_params->u4_bs_table; { UWORD32 top_mb_csbp, left_mb_csbp, cur_mb_csbp; UWORD32 top_edge, left_edge; top_mb_csbp = ps_cur_mb_info->ps_top_mb->u2_luma_csbp; left_mb_csbp = ps_cur_mb_info->ps_left_mb->u2_luma_csbp; cur_mb_csbp = ps_cur_mb_info->ps_curmb->u2_luma_csbp; top_mb_csbp = top_mb_csbp >> 12; top_edge = top_mb_csbp | (cur_mb_csbp & 0xf); if(top_edge) pu4_bs_table[0] = 0x02020202; else pu4_bs_table[0] = 0; cur_mb_csbp = cur_mb_csbp & CSBP_LEFT_BLOCK_MASK; left_mb_csbp = left_mb_csbp & CSBP_RIGHT_BLOCK_MASK; left_edge = cur_mb_csbp | left_mb_csbp; if(left_edge) pu4_bs_table[4] = 0x02020202; else pu4_bs_table[4] = 0; pu4_bs_table[1] = 0; pu4_bs_table[2] = 0; pu4_bs_table[3] = 0; pu4_bs_table[5] = 0; pu4_bs_table[6] = 0; pu4_bs_table[7] = 0; } } ================================================ FILE: dependencies/ih264d/decoder/ih264d_deblocking.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #include <string.h> #include "ih264_typedefs.h" #include "iv.h" #include "ivd.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_debug.h" #include "ih264d_defs.h" #include "ih264d_defs.h" #include "ih264d_structs.h" #include "ih264d_deblocking.h" #include "ih264d_mb_utils.h" #include "ih264d_error_handler.h" #include "ih264d_utils.h" #include "ih264d_defs.h" #include "ih264d_format_conv.h" #include "ih264d_deblocking.h" #include "ih264d_tables.h" /*! ************************************************************************* * \file ih264d_deblocking.c * * \brief * Decoder specific deblocking routines * * \author AI ************************************************************************* */ /*! ************************************************************************** * \if Function name : HorizonPad \endif * * \brief * Does the Horizontal padding on a whole pic. * * \return * None ************************************************************************** */ /*! ************************************************************************** * \if Function name : FilterBoundaryLeft \endif * * \brief * Filters MacroBlock Left Boundary egdes. * * \return * None ************************************************************************** */ void ih264d_filter_boundary_left_nonmbaff(dec_struct_t *ps_dec, tfr_ctxt_t * ps_tfr_cxt, WORD8 i1_cb_qp_idx_ofst, WORD8 i1_cr_qp_idx_ofst, deblk_mb_t * ps_cur_mb, WORD32 i4_strd_y, WORD32 i4_strd_uv, deblk_mb_t * ps_left_mb, UWORD32 pu4_bs_tab[], UWORD8 u1_cur_fld) { UWORD8 *pu1_y, *pu1_u, *pu1_v; WORD32 uc_tmp, qp_avg; WORD32 alpha_u = 0, beta_u = 0, alpha_v = 0, beta_v = 0; WORD32 alpha_y = 0, beta_y = 0; WORD32 idx_b_u, idx_a_u, idx_b_v, idx_a_v; WORD32 idx_b_y, idx_a_y; UWORD32 u4_bs_val; UWORD8 *pu1_cliptab_u, *pu1_cliptab_v, *pu1_cliptab_y; UWORD8 u1_double_cl = !ps_cur_mb->u1_single_call; WORD32 ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; WORD32 ofst_b = ps_cur_mb->i1_slice_beta_offset; PROFILE_DISABLE_DEBLK() pu1_y = ps_tfr_cxt->pu1_mb_y; pu1_u = ps_tfr_cxt->pu1_mb_u; pu1_v = ps_tfr_cxt->pu1_mb_v; /* LUMA values */ /* Deblock rounding change */ qp_avg = (UWORD8)((ps_cur_mb->u1_left_mb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); idx_a_y = qp_avg + ofst_a; alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; idx_b_y = qp_avg + ofst_b; beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; /* Chroma cb values */ { WORD32 mb_qp1, mb_qp2; mb_qp1 = (ps_cur_mb->u1_left_mb_qp + i1_cb_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_u = qp_avg + ofst_a; alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; idx_b_u = qp_avg + ofst_b; beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; /* Chroma cr values */ { WORD32 mb_qp1, mb_qp2; mb_qp1 = (ps_cur_mb->u1_left_mb_qp + i1_cr_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_v = qp_avg + ofst_a; alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; idx_b_v = qp_avg + ofst_b; beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; if(u1_double_cl == 0) { u4_bs_val = pu4_bs_tab[4]; if(0x04040404 == u4_bs_val) { ps_dec->pf_deblk_luma_vert_bs4(pu1_y, i4_strd_y, alpha_y, beta_y); ps_dec->pf_deblk_chroma_vert_bs4(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v); } else { if(u4_bs_val) { pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; ps_dec->pf_deblk_luma_vert_bslt4(pu1_y, i4_strd_y, alpha_y, beta_y, u4_bs_val, pu1_cliptab_y); ps_dec->pf_deblk_chroma_vert_bslt4(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs_val, pu1_cliptab_u, pu1_cliptab_v); } } } else { i4_strd_y <<= (!u1_cur_fld); u4_bs_val = pu4_bs_tab[4]; i4_strd_uv <<= (!u1_cur_fld); if(0x04040404 == u4_bs_val) { ps_dec->pf_deblk_luma_vert_bs4_mbaff(pu1_y, i4_strd_y, alpha_y, beta_y); ps_dec->pf_deblk_chroma_vert_bs4_mbaff(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v); } else { if(u4_bs_val) { pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; ps_dec->pf_deblk_luma_vert_bslt4_mbaff(pu1_y, i4_strd_y, alpha_y, beta_y, u4_bs_val, pu1_cliptab_y); ps_dec->pf_deblk_chroma_vert_bslt4_mbaff(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs_val, pu1_cliptab_u, pu1_cliptab_v); } } { UWORD16 u2_shift = (i4_strd_y >> 1) << (u1_cur_fld ? 4 : 0); pu1_y += u2_shift; u2_shift = (i4_strd_uv >> 1) << (u1_cur_fld ? 3 : 0); pu1_u += u2_shift; pu1_v += u2_shift; } qp_avg = (((ps_left_mb + 1)->u1_mb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); idx_a_y = qp_avg + ofst_a; alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; idx_b_y = qp_avg + ofst_b; beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; u4_bs_val = pu4_bs_tab[9]; { WORD32 mb_qp1, mb_qp2; mb_qp1 = ((ps_left_mb + 1)->u1_mb_qp + i1_cb_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_u = qp_avg + ofst_a; alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; idx_b_u = qp_avg + ofst_b; beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; u4_bs_val = pu4_bs_tab[9]; { WORD32 mb_qp1, mb_qp2; mb_qp1 = ((ps_left_mb + 1)->u1_mb_qp + i1_cr_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_v = qp_avg + ofst_a; alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; idx_b_v = qp_avg + ofst_b; beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; if(0x04040404 == u4_bs_val) { ps_dec->pf_deblk_luma_vert_bs4_mbaff(pu1_y, i4_strd_y, alpha_y, beta_y); ps_dec->pf_deblk_chroma_vert_bs4_mbaff(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v); } else { if(u4_bs_val) { pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; ps_dec->pf_deblk_luma_vert_bslt4_mbaff(pu1_y, i4_strd_y, alpha_y, beta_y, u4_bs_val, pu1_cliptab_y); ps_dec->pf_deblk_chroma_vert_bslt4_mbaff(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs_val, pu1_cliptab_u, pu1_cliptab_v); } } } } /*! ************************************************************************** * \if Function name : FilterBoundaryTop \endif * * \brief * Filters MacroBlock Top Boundary egdes. * * \return * None ************************************************************************** */ void ih264d_filter_boundary_top_nonmbaff(dec_struct_t *ps_dec, tfr_ctxt_t * ps_tfr_cxt, WORD8 i1_cb_qp_idx_ofst, WORD8 i1_cr_qp_idx_ofst, deblk_mb_t * ps_cur_mb, WORD32 i4_strd_y, WORD32 i4_strd_uv, deblk_mb_t * ps_top_mb, UWORD32 u4_bs) { UWORD8 *pu1_y, *pu1_u; WORD32 alpha_u = 0, beta_u = 0, alpha_v = 0, beta_v = 0; WORD32 alpha_y = 0, beta_y = 0; WORD32 qp_avg; WORD32 idx_b_u, idx_a_u, idx_b_v, idx_a_v; WORD32 idx_b_y, idx_a_y; UWORD16 uc_tmp; UWORD8 *pu1_cliptab_u, *pu1_cliptab_v, *pu1_cliptab_y; WORD32 ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; WORD32 ofst_b = ps_cur_mb->i1_slice_beta_offset; UNUSED(ps_top_mb); /* LUMA values */ /* Deblock rounding change */ uc_tmp = ((ps_cur_mb->u1_topmb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); qp_avg = (UWORD8)uc_tmp; idx_a_y = qp_avg + ofst_a; alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; idx_b_y = qp_avg + ofst_b; beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; pu1_y = ps_tfr_cxt->pu1_mb_y; /* CHROMA cb values */ { WORD32 mb_qp1, mb_qp2; mb_qp1 = (ps_cur_mb->u1_topmb_qp + i1_cb_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_u = qp_avg + ofst_a; alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; idx_b_u = qp_avg + ofst_b; beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; /* CHROMA cr values */ { WORD32 mb_qp1, mb_qp2; mb_qp1 = (ps_cur_mb->u1_topmb_qp + i1_cr_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_v = qp_avg + ofst_a; alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; idx_b_v = qp_avg + ofst_b; beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; pu1_u = ps_tfr_cxt->pu1_mb_u; if(u4_bs == 0x04040404) { /* Code specific to the assembly module */ ps_dec->pf_deblk_luma_horz_bs4(pu1_y, i4_strd_y, alpha_y, beta_y); ps_dec->pf_deblk_chroma_horz_bs4(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v); } else { if(u4_bs) { pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; ps_dec->pf_deblk_luma_horz_bslt4(pu1_y, i4_strd_y, alpha_y, beta_y, u4_bs, pu1_cliptab_y); ps_dec->pf_deblk_chroma_horz_bslt4(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs, pu1_cliptab_u, pu1_cliptab_v); } } } void ih264d_deblock_mb_nonmbaff(dec_struct_t *ps_dec, tfr_ctxt_t * ps_tfr_cxt, WORD8 i1_cb_qp_idx_ofst, WORD8 i1_cr_qp_idx_ofst, WORD32 i4_strd_y, WORD32 i4_strd_uv ) { UWORD8 *pu1_y, *pu1_u; UWORD32 u4_bs; WORD32 alpha, beta, alpha_u, beta_u, alpha_v, beta_v; UWORD8 *pu1_cliptab_u; UWORD8 *pu1_cliptab_v; UWORD8 *pu1_cliptab_y; UWORD32 * pu4_bs_tab; WORD32 idx_a_y, idx_a_u, idx_a_v; UWORD32 u4_deb_mode, u4_mbs_next; UWORD32 u4_image_wd_mb; deblk_mb_t *ps_top_mb,*ps_left_mb,*ps_cur_mb; PROFILE_DISABLE_DEBLK() /* Return from here to switch off deblocking */ u4_image_wd_mb = ps_dec->u2_frm_wd_in_mbs; ps_cur_mb = ps_dec->ps_cur_deblk_mb; pu4_bs_tab = ps_cur_mb->u4_bs_table; u4_deb_mode = ps_cur_mb->u1_deblocking_mode; if(!(u4_deb_mode & MB_DISABLE_FILTERING)) { if(ps_dec->u4_deblk_mb_x) { ps_left_mb = ps_cur_mb - 1; } else { ps_left_mb = NULL; } if(ps_dec->u4_deblk_mb_y != 0) { ps_top_mb = ps_cur_mb - (u4_image_wd_mb); } else { ps_top_mb = NULL; } if(u4_deb_mode & MB_DISABLE_LEFT_EDGE) ps_left_mb = NULL; if(u4_deb_mode & MB_DISABLE_TOP_EDGE) ps_top_mb = NULL; /*---------------------------------------------------------------------*/ /* Filter wrt Left edge */ /* except */ /* - Left Egde is Picture Boundary */ /* - Left Egde is part of Slice Boundary and Deblocking */ /* parameters of slice disable Filtering of Slice Boundary Edges*/ /*---------------------------------------------------------------------*/ if(ps_left_mb) ih264d_filter_boundary_left_nonmbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, i1_cr_qp_idx_ofst, ps_cur_mb, i4_strd_y, i4_strd_uv, ps_left_mb, pu4_bs_tab, 0); /*--------------------------------------------------------------------*/ /* Filter wrt Other Vertical Edges */ /*--------------------------------------------------------------------*/ { WORD32 ofst_a, ofst_b, idx_b_y, idx_b_u, idx_b_v; WORD32 qp_avg, qp_avg_u, qp_avg_v; ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; ofst_b = ps_cur_mb->i1_slice_beta_offset; qp_avg = ps_cur_mb->u1_mb_qp; idx_a_y = qp_avg + ofst_a; alpha = gau1_ih264d_alpha_table[12 + idx_a_y]; idx_b_y = qp_avg + ofst_b; beta = gau1_ih264d_beta_table[12 + idx_b_y]; /* CHROMA values */ /* CHROMA Cb values */ qp_avg_u = (qp_avg + i1_cb_qp_idx_ofst); qp_avg_u = gau1_ih264d_qp_scale_cr[12 + qp_avg_u]; idx_a_u = qp_avg_u + ofst_a; alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; idx_b_u = qp_avg_u + ofst_b; beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; /* CHROMA Cr values */ qp_avg_v = (qp_avg + i1_cr_qp_idx_ofst); qp_avg_v = gau1_ih264d_qp_scale_cr[12 + qp_avg_v]; idx_a_v = qp_avg_v + ofst_a; alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; idx_b_v = qp_avg_v + ofst_b; beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; } pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; //this for Luma pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; //this for chroma pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; //this for chroma //edge=1 u4_bs = pu4_bs_tab[5]; pu1_y = ps_tfr_cxt->pu1_mb_y; pu1_u = ps_tfr_cxt->pu1_mb_u; if(u4_bs) { ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 4, i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); } //edge=2 u4_bs = pu4_bs_tab[6]; if(u4_bs) { ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 8, i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); ps_dec->pf_deblk_chroma_vert_bslt4(pu1_u + 4 * YUV420SP_FACTOR, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs, pu1_cliptab_u, pu1_cliptab_v); } //edge=3 u4_bs = pu4_bs_tab[7]; if(u4_bs) { ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 12, i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); } /*--------------------------------------------------------------------*/ /* Filter wrt Top edge */ /* except */ /* - Top Egde is Picture Boundary */ /* - Top Egde is part of Slice Boundary and Deblocking */ /* parameters of slice disable Filtering of Slice Boundary Edges*/ /*--------------------------------------------------------------------*/ if(ps_top_mb) { /** if top MB and MB AFF and cur MB is frame and top is field then */ /* one extra top edge needs to be deblocked */ ih264d_filter_boundary_top_nonmbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, i1_cr_qp_idx_ofst, ps_cur_mb, i4_strd_y, i4_strd_uv, ps_top_mb, pu4_bs_tab[0]); } /*--------------------------------------------------------------------*/ /* Filter wrt Other Horizontal Edges */ /*--------------------------------------------------------------------*/ //edge1 u4_bs = pu4_bs_tab[1]; if(u4_bs) { ps_dec->pf_deblk_luma_horz_bslt4(pu1_y + (i4_strd_y << 2), i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); } //edge2 u4_bs = pu4_bs_tab[2]; if(u4_bs) { ps_dec->pf_deblk_luma_horz_bslt4(pu1_y + (i4_strd_y << 3), i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); ps_dec->pf_deblk_chroma_horz_bslt4(pu1_u + (i4_strd_uv << 2), i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs, pu1_cliptab_u, pu1_cliptab_v); } //edge3 u4_bs = pu4_bs_tab[3]; if(u4_bs) { ps_dec->pf_deblk_luma_horz_bslt4( (pu1_y + (i4_strd_y << 3) + (i4_strd_y << 2)), i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); } } ps_dec->u4_deblk_mb_x++; ps_dec->ps_cur_deblk_mb++; ps_dec->u4_cur_deblk_mb_num++; u4_mbs_next = u4_image_wd_mb - ps_dec->u4_deblk_mb_x; ps_tfr_cxt->pu1_mb_y += 16; ps_tfr_cxt->pu1_mb_u += 8 * YUV420SP_FACTOR; ps_tfr_cxt->pu1_mb_v += 8; if(!u4_mbs_next) { ps_tfr_cxt->pu1_mb_y += ps_tfr_cxt->u4_y_inc; ps_tfr_cxt->pu1_mb_u += ps_tfr_cxt->u4_uv_inc; ps_tfr_cxt->pu1_mb_v += ps_tfr_cxt->u4_uv_inc; ps_dec->u4_deblk_mb_y++; ps_dec->u4_deblk_mb_x = 0; } } /************************************************************************** * * Function Name : ih264d_init_deblk_tfr_ctxt * * Description : This function is called once per deblockpicture call * This sets up the transfer address contexts * * Revision History: * * DD MM YYYY Author(s) Changes (Describe the changes made) * 14 06 2005 SWRN Draft **************************************************************************/ void ih264d_init_deblk_tfr_ctxt(dec_struct_t * ps_dec, pad_mgr_t *ps_pad_mgr, tfr_ctxt_t *ps_tfr_cxt, UWORD16 u2_image_wd_mb, UWORD8 u1_mbaff) { UWORD32 i4_wd_y; UWORD32 i4_wd_uv; UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; /*< Field u4_flag */ UNUSED(u2_image_wd_mb); ps_tfr_cxt->pu1_src_y = ps_dec->s_cur_pic.pu1_buf1 - 4; ps_tfr_cxt->pu1_src_u = ps_dec->s_cur_pic.pu1_buf2 - 4; ps_tfr_cxt->pu1_src_v = ps_dec->s_cur_pic.pu1_buf3 - 4; ps_tfr_cxt->pu1_dest_y = ps_tfr_cxt->pu1_src_y; ps_tfr_cxt->pu1_dest_u = ps_tfr_cxt->pu1_src_u; ps_tfr_cxt->pu1_dest_v = ps_tfr_cxt->pu1_src_v; ps_tfr_cxt->pu1_mb_y = ps_tfr_cxt->pu1_src_y + 4; ps_tfr_cxt->pu1_mb_u = ps_tfr_cxt->pu1_src_u + 4; ps_tfr_cxt->pu1_mb_v = ps_tfr_cxt->pu1_src_v + 4; i4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; i4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; ps_tfr_cxt->u4_y_inc = ((i4_wd_y << u1_mbaff) * 16 - (ps_dec->u2_frm_wd_in_mbs << 4)); ps_tfr_cxt->u4_uv_inc = (i4_wd_uv << u1_mbaff) * 8 - (ps_dec->u2_frm_wd_in_mbs << 4); /* padding related initialisations */ if(ps_dec->ps_cur_slice->u1_nal_ref_idc) { ps_pad_mgr->u1_vert_pad_top = !(ps_dec->ps_cur_slice->u1_field_pic_flag && ps_dec->ps_cur_slice->u1_bottom_field_flag); ps_pad_mgr->u1_vert_pad_bot = ((!ps_dec->ps_cur_slice->u1_field_pic_flag) || ps_dec->ps_cur_slice->u1_bottom_field_flag); ps_pad_mgr->u1_horz_pad = 1; } else { ps_pad_mgr->u1_horz_pad = 0; ps_pad_mgr->u1_vert_pad_top = 0; ps_pad_mgr->u1_vert_pad_bot = 0; } } /*****************************************************************************/ /* */ /* Function Name : ih264d_deblock_picture_mbaff */ /* */ /* Description : This function carries out deblocking on a whole picture */ /* with MBAFF */ /* */ /* Inputs : <What inputs does the function take?> */ /* Processing : This functions calls deblock MB in the MB increment order*/ /* */ /* Outputs : Produces the deblocked picture */ /* Returns : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 17 02 2005 NS Creation */ /* 14 06 2005 SWRN clean-up */ /*****************************************************************************/ void ih264d_deblock_picture_mbaff(dec_struct_t * ps_dec) { WORD16 i2_mb_x, i2_mb_y; deblk_mb_t *ps_cur_mb; deblk_mb_t *ps_top_mb; deblk_mb_t *ps_left_mb; UWORD8 u1_vert_pad_top = 1; UWORD8 u1_cur_fld, u1_top_fld, u1_left_fld; UWORD8 u1_first_row; UWORD8 * pu1_deb_y, *pu1_deb_u, *pu1_deb_v; UWORD8 u1_deb_mode, u1_extra_top_edge; WORD32 i4_wd_y, i4_wd_uv; UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; /*< Field u4_flag */ UWORD8 u1_bottom_field_flag = ps_dec->ps_cur_slice->u1_bottom_field_flag; /*< Bottom field u4_flag*/ /**************************************************/ /* one time loads from ps_dec which will be used */ /* frequently throughout the deblocking procedure */ /**************************************************/ pad_mgr_t * ps_pad_mgr = &ps_dec->s_pad_mgr; tfr_ctxt_t s_tfr_ctxt; tfr_ctxt_t * ps_tfr_cxt = &s_tfr_ctxt; UWORD16 u2_image_wd_mb = ps_dec->u2_frm_wd_in_mbs; UWORD16 u2_image_ht_mb = ps_dec->u2_frm_ht_in_mbs; UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; WORD8 i1_cb_qp_idx_ofst = ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; WORD8 i1_cr_qp_idx_ofst = ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; /* Set up Parameter for DMA transfer */ ih264d_init_deblk_tfr_ctxt(ps_dec, ps_pad_mgr, ps_tfr_cxt, u2_image_wd_mb, u1_mbaff); /* Pic level Initialisations */ i2_mb_y = u2_image_ht_mb; i2_mb_x = 0; u1_extra_top_edge = 0; u1_first_row = 1; i4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; i4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; /* Initial filling of the buffers with deblocking data */ pu1_deb_y = ps_tfr_cxt->pu1_mb_y; pu1_deb_u = ps_tfr_cxt->pu1_mb_u; pu1_deb_v = ps_tfr_cxt->pu1_mb_v; ps_cur_mb = ps_dec->ps_deblk_pic; if(ps_dec->u4_app_disable_deblk_frm == 0) { { while(i2_mb_y > 0) { do { u1_deb_mode = ps_cur_mb->u1_deblocking_mode; if(!(u1_deb_mode & MB_DISABLE_FILTERING)) { ps_tfr_cxt->pu1_mb_y = pu1_deb_y; ps_tfr_cxt->pu1_mb_u = pu1_deb_u; ps_tfr_cxt->pu1_mb_v = pu1_deb_v; u1_cur_fld = (ps_cur_mb->u1_mb_type & D_FLD_MB) >> 7; u1_cur_fld &= 1; if(i2_mb_x) { ps_left_mb = ps_cur_mb - 2; } else { ps_left_mb = NULL; } if(!u1_first_row) { ps_top_mb = ps_cur_mb - (u2_image_wd_mb << 1) + 1; u1_top_fld = (ps_top_mb->u1_mb_type & D_FLD_MB) >> 7; } else { ps_top_mb = NULL; u1_top_fld = 0; } if((!u1_first_row) & u1_top_fld & u1_cur_fld) ps_top_mb--; /********************************************************/ /* if top MB and MB AFF and cur MB is frame and top is */ /* field, then one extra top edge needs to be deblocked */ /********************************************************/ u1_extra_top_edge = (!u1_cur_fld) & u1_top_fld; if(u1_deb_mode & MB_DISABLE_LEFT_EDGE) ps_left_mb = NULL; if(u1_deb_mode & MB_DISABLE_TOP_EDGE) ps_top_mb = NULL; ih264d_deblock_mb_mbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, i1_cr_qp_idx_ofst, ps_cur_mb, i4_wd_y, i4_wd_uv, ps_top_mb, ps_left_mb, u1_cur_fld, u1_extra_top_edge); } ps_cur_mb++; u1_deb_mode = ps_cur_mb->u1_deblocking_mode; if(!(u1_deb_mode & MB_DISABLE_FILTERING)) { ps_tfr_cxt->pu1_mb_y = pu1_deb_y; ps_tfr_cxt->pu1_mb_u = pu1_deb_u; ps_tfr_cxt->pu1_mb_v = pu1_deb_v; u1_cur_fld = (ps_cur_mb->u1_mb_type & D_FLD_MB) >> 7; u1_cur_fld &= 1; if(i2_mb_x) { ps_left_mb = ps_cur_mb - 2; u1_left_fld = (ps_left_mb->u1_mb_type & D_FLD_MB) >> 7; } else { ps_left_mb = NULL; u1_left_fld = u1_cur_fld; } if(!u1_first_row) { ps_top_mb = ps_cur_mb - (u2_image_wd_mb << 1); } else { ps_top_mb = NULL; } { UWORD8 u1_row_shift_y = 0, u1_row_shift_uv = 0; if(!u1_cur_fld) { ps_top_mb = ps_cur_mb - 1; u1_top_fld = (ps_top_mb->u1_mb_type & D_FLD_MB) >> 7; u1_row_shift_y = 4; u1_row_shift_uv = 3; } ps_tfr_cxt->pu1_mb_y += i4_wd_y << u1_row_shift_y; ps_tfr_cxt->pu1_mb_u += (i4_wd_uv << u1_row_shift_uv); ps_tfr_cxt->pu1_mb_v += i4_wd_uv << u1_row_shift_uv; } /* point to A if top else A+1 */ if(u1_left_fld ^ u1_cur_fld) ps_left_mb--; /********************************************************/ /* if top MB and MB AFF and cur MB is frame and top is */ /* field, then one extra top edge needs to be deblocked */ /********************************************************/ u1_extra_top_edge = 0; if(u1_deb_mode & MB_DISABLE_LEFT_EDGE) ps_left_mb = NULL; if(u1_deb_mode & MB_DISABLE_TOP_EDGE) ps_top_mb = NULL; ih264d_deblock_mb_mbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, i1_cr_qp_idx_ofst, ps_cur_mb, i4_wd_y, i4_wd_uv, ps_top_mb, ps_left_mb, u1_cur_fld, u1_extra_top_edge); } ps_cur_mb++; i2_mb_x++; pu1_deb_y += 16; pu1_deb_u += 8 * YUV420SP_FACTOR; pu1_deb_v += 8; } while(u2_image_wd_mb > i2_mb_x); pu1_deb_y += ps_tfr_cxt->u4_y_inc; pu1_deb_u += ps_tfr_cxt->u4_uv_inc; pu1_deb_v += ps_tfr_cxt->u4_uv_inc; i2_mb_x = 0; i2_mb_y -= 2; u1_first_row = 0; } } } //Padd the Picture //Horizontal Padd if(ps_pad_mgr->u1_horz_pad) { UWORD32 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; ps_dec->pf_pad_left_luma(ps_tfr_cxt->pu1_src_y + 4, ps_dec->u2_frm_wd_y << u1_field_pic_flag, ps_dec->u2_pic_ht >> u1_field_pic_flag, PAD_LEN_Y_H); ps_dec->pf_pad_right_luma( ps_tfr_cxt->pu1_src_y + 4 + (ps_dec->u2_frm_wd_in_mbs << 4), ps_dec->u2_frm_wd_y << u1_field_pic_flag, ps_dec->u2_pic_ht >> u1_field_pic_flag, PAD_LEN_Y_H); ps_dec->pf_pad_left_chroma(ps_tfr_cxt->pu1_src_u + 4, ps_dec->u2_frm_wd_uv << u1_field_pic_flag, (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, PAD_LEN_UV_H * YUV420SP_FACTOR); ps_dec->pf_pad_right_chroma( ps_tfr_cxt->pu1_src_u + 4 + (ps_dec->u2_frm_wd_in_mbs << 4), ps_dec->u2_frm_wd_uv << u1_field_pic_flag, (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, PAD_LEN_UV_H * YUV420SP_FACTOR); } //Vertical Padd Top if(ps_pad_mgr->u1_vert_pad_top) { ps_dec->pf_pad_top(ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H, ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, ps_pad_mgr->u1_pad_len_y_v); ps_dec->pf_pad_top( ps_dec->ps_cur_pic->pu1_buf2 - PAD_LEN_UV_H * YUV420SP_FACTOR, ps_dec->u2_frm_wd_uv, ps_dec->u2_frm_wd_uv, ps_pad_mgr->u1_pad_len_cr_v); ps_pad_mgr->u1_vert_pad_top = 0; } //Vertical Padd Bottom if(ps_pad_mgr->u1_vert_pad_bot) { UWORD8 *pu1_buf; pu1_buf = ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H; pu1_buf += ps_dec->u2_pic_ht * ps_dec->u2_frm_wd_y; ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, ps_pad_mgr->u1_pad_len_y_v); pu1_buf = ps_dec->ps_cur_pic->pu1_buf2 - PAD_LEN_UV_H * YUV420SP_FACTOR; pu1_buf += (ps_dec->u2_pic_ht >> 1) * ps_dec->u2_frm_wd_uv; ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_uv, ps_dec->u2_frm_wd_uv, ps_pad_mgr->u1_pad_len_cr_v); } } /*****************************************************************************/ /* */ /* Function Name : ih264d_deblock_picture_non_mbaff */ /* */ /* Description : This function carries out deblocking on a whole picture */ /* without MBAFF */ /* */ /* Inputs : <What inputs does the function take?> */ /* Processing : This functions calls deblock MB in the MB increment order*/ /* */ /* Outputs : Produces the deblocked picture */ /* Returns : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 17 02 2005 NS Creation */ /* 14 06 2005 SWRN clean-up */ /*****************************************************************************/ void ih264d_deblock_picture_non_mbaff(dec_struct_t * ps_dec) { deblk_mb_t *ps_cur_mb; UWORD8 u1_vert_pad_top = 1; UWORD8 u1_deb_mode; WORD32 i4_wd_y, i4_wd_uv; UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; /*< Field u4_flag */ UWORD8 u1_bottom_field_flag = ps_dec->ps_cur_slice->u1_bottom_field_flag; /*< Bottom field u4_flag */ /**************************************************/ /* one time loads from ps_dec which will be used */ /* frequently throughout the deblocking procedure */ /**************************************************/ pad_mgr_t * ps_pad_mgr = &ps_dec->s_pad_mgr; tfr_ctxt_t s_tfr_ctxt; tfr_ctxt_t * ps_tfr_cxt = &s_tfr_ctxt; // = &ps_dec->s_tran_addrecon; UWORD16 u2_image_wd_mb = ps_dec->u2_frm_wd_in_mbs; UWORD16 u2_image_ht_mb = ps_dec->u2_frm_ht_in_mbs; WORD8 i1_cb_qp_idx_ofst = ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; WORD8 i1_cr_qp_idx_ofst = ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; /* Set up Parameter for DMA transfer */ ih264d_init_deblk_tfr_ctxt(ps_dec, ps_pad_mgr, ps_tfr_cxt, u2_image_wd_mb, 0); /* Pic level Initialisations */ i4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; i4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; /* Initial filling of the buffers with deblocking data */ ps_cur_mb = ps_dec->ps_deblk_pic; if(ps_dec->u4_app_disable_deblk_frm == 0) { if(ps_dec->ps_cur_sps->u1_mb_aff_flag == 1) { while( ps_dec->u4_deblk_mb_y < u2_image_ht_mb) { ih264d_deblock_mb_nonmbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, i1_cr_qp_idx_ofst, i4_wd_y, i4_wd_uv); ps_cur_mb++; } } } //Padd the Picture //Horizontal Padd if(ps_pad_mgr->u1_horz_pad) { UWORD32 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; ps_dec->pf_pad_left_luma(ps_tfr_cxt->pu1_src_y + 4, ps_dec->u2_frm_wd_y << u1_field_pic_flag, ps_dec->u2_pic_ht >> u1_field_pic_flag, PAD_LEN_Y_H); ps_dec->pf_pad_right_luma( ps_tfr_cxt->pu1_src_y + 4 + (ps_dec->u2_frm_wd_in_mbs << 4), ps_dec->u2_frm_wd_y << u1_field_pic_flag, ps_dec->u2_pic_ht >> u1_field_pic_flag, PAD_LEN_Y_H); ps_dec->pf_pad_left_chroma(ps_tfr_cxt->pu1_src_u + 4, ps_dec->u2_frm_wd_uv << u1_field_pic_flag, (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, PAD_LEN_UV_H * YUV420SP_FACTOR); ps_dec->pf_pad_right_chroma( ps_tfr_cxt->pu1_src_u + 4 + (ps_dec->u2_frm_wd_in_mbs << 4), ps_dec->u2_frm_wd_uv << u1_field_pic_flag, (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, PAD_LEN_UV_H * YUV420SP_FACTOR); } //Vertical Padd Top if(ps_pad_mgr->u1_vert_pad_top) { ps_dec->pf_pad_top(ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H, ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, ps_pad_mgr->u1_pad_len_y_v); ps_dec->pf_pad_top( ps_dec->ps_cur_pic->pu1_buf2 - PAD_LEN_UV_H * YUV420SP_FACTOR, ps_dec->u2_frm_wd_uv, ps_dec->u2_frm_wd_uv, ps_pad_mgr->u1_pad_len_cr_v); ps_pad_mgr->u1_vert_pad_top = 0; } //Vertical Padd Bottom if(ps_pad_mgr->u1_vert_pad_bot) { UWORD8 *pu1_buf; pu1_buf = ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H; pu1_buf += ps_dec->u2_pic_ht * ps_dec->u2_frm_wd_y; ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, ps_pad_mgr->u1_pad_len_y_v); pu1_buf = ps_dec->ps_cur_pic->pu1_buf2 - PAD_LEN_UV_H * YUV420SP_FACTOR; pu1_buf += (ps_dec->u2_pic_ht >> 1) * ps_dec->u2_frm_wd_uv; ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_uv, ps_dec->u2_frm_wd_uv, ps_pad_mgr->u1_pad_len_cr_v); } } void ih264d_deblock_picture_progressive(dec_struct_t * ps_dec) { deblk_mb_t *ps_cur_mb; UWORD8 u1_vert_pad_top = 1; UWORD8 u1_mbs_next; UWORD8 u1_deb_mode; WORD32 i4_wd_y, i4_wd_uv; /**************************************************/ /* one time loads from ps_dec which will be used */ /* frequently throughout the deblocking procedure */ /**************************************************/ pad_mgr_t * ps_pad_mgr = &ps_dec->s_pad_mgr; tfr_ctxt_t s_tfr_ctxt; tfr_ctxt_t * ps_tfr_cxt = &s_tfr_ctxt; // = &ps_dec->s_tran_addrecon; UWORD16 u2_image_wd_mb = ps_dec->u2_frm_wd_in_mbs; UWORD16 u2_image_ht_mb = ps_dec->u2_frm_ht_in_mbs; UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; WORD8 i1_cb_qp_idx_ofst = ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; WORD8 i1_cr_qp_idx_ofst = ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; /* Set up Parameter for deblocking */ ih264d_init_deblk_tfr_ctxt(ps_dec, ps_pad_mgr, ps_tfr_cxt, u2_image_wd_mb, 0); /* Pic level Initialisations */ i4_wd_y = ps_dec->u2_frm_wd_y; i4_wd_uv = ps_dec->u2_frm_wd_uv; /* Initial filling of the buffers with deblocking data */ ps_cur_mb = ps_dec->ps_deblk_pic; if(ps_dec->u4_app_disable_deblk_frm == 0) { if(ps_dec->ps_cur_sps->u1_mb_aff_flag == 1) { while( ps_dec->u4_deblk_mb_y < u2_image_ht_mb) { ih264d_deblock_mb_nonmbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, i1_cr_qp_idx_ofst, i4_wd_y, i4_wd_uv); ps_cur_mb++; } } } //Padd the Picture //Horizontal Padd if(ps_pad_mgr->u1_horz_pad) { UWORD32 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; ps_dec->pf_pad_left_luma(ps_tfr_cxt->pu1_src_y + 4, ps_dec->u2_frm_wd_y << u1_field_pic_flag, ps_dec->u2_pic_ht >> u1_field_pic_flag, PAD_LEN_Y_H); ps_dec->pf_pad_right_luma( ps_tfr_cxt->pu1_src_y + 4 + (ps_dec->u2_frm_wd_in_mbs << 4), ps_dec->u2_frm_wd_y << u1_field_pic_flag, ps_dec->u2_pic_ht >> u1_field_pic_flag, PAD_LEN_Y_H); ps_dec->pf_pad_left_chroma(ps_tfr_cxt->pu1_src_u + 4, ps_dec->u2_frm_wd_uv << u1_field_pic_flag, (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, PAD_LEN_UV_H * YUV420SP_FACTOR); ps_dec->pf_pad_right_chroma( ps_tfr_cxt->pu1_src_u + 4 + (ps_dec->u2_frm_wd_in_mbs << 4), ps_dec->u2_frm_wd_uv << u1_field_pic_flag, (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, PAD_LEN_UV_H * YUV420SP_FACTOR); } //Vertical Padd Top if(ps_pad_mgr->u1_vert_pad_top) { ps_dec->pf_pad_top(ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H, ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, ps_pad_mgr->u1_pad_len_y_v); ps_dec->pf_pad_top( ps_dec->ps_cur_pic->pu1_buf2 - PAD_LEN_UV_H * YUV420SP_FACTOR, ps_dec->u2_frm_wd_uv, ps_dec->u2_frm_wd_uv, ps_pad_mgr->u1_pad_len_cr_v); } //Vertical Padd Bottom if(ps_pad_mgr->u1_vert_pad_bot) { UWORD8 *pu1_buf; pu1_buf = ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H; pu1_buf += ps_dec->u2_pic_ht * ps_dec->u2_frm_wd_y; ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, ps_pad_mgr->u1_pad_len_y_v); pu1_buf = ps_dec->ps_cur_pic->pu1_buf2 - PAD_LEN_UV_H * YUV420SP_FACTOR; pu1_buf += (ps_dec->u2_pic_ht >> 1) * ps_dec->u2_frm_wd_uv; ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_uv, ps_dec->u2_frm_wd_uv, ps_pad_mgr->u1_pad_len_cr_v); } } /*! ************************************************************************** * \if Function name : ih264d_set_deblocking_parameters \endif * * \brief * Sets the deblocking parameters of the macroblock * * \return * 0 on Success and Error code otherwise * * \note * Given the neighbour availablity information, and the deblocking * parameters of the slice,this function will set the deblocking * mode of the macroblock. ************************************************************************** */ WORD8 ih264d_set_deblocking_parameters(deblk_mb_t * ps_cur_mb, dec_slice_params_t * ps_slice, UWORD8 u1_mb_ngbr_availablity, UWORD8 u1_mb_field_decoding_flag) { /*------------------------------------------------------------------*/ /* Set the deblocking parameters */ /*------------------------------------------------------------------*/ ps_cur_mb->i1_slice_alpha_c0_offset = ps_slice->i1_slice_alpha_c0_offset; ps_cur_mb->i1_slice_beta_offset = ps_slice->i1_slice_beta_offset; ps_cur_mb->u1_mb_type = (u1_mb_field_decoding_flag << 7); switch(ps_slice->u1_disable_dblk_filter_idc) { case DBLK_ENABLED: ps_cur_mb->u1_deblocking_mode = MB_ENABLE_FILTERING; break; case DBLK_DISABLED: ps_cur_mb->u1_deblocking_mode = MB_DISABLE_FILTERING; break; case SLICE_BOUNDARY_DBLK_DISABLED: { ps_cur_mb->u1_deblocking_mode = MB_ENABLE_FILTERING; if(!(u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK)) ps_cur_mb->u1_deblocking_mode |= MB_DISABLE_LEFT_EDGE; if(!(u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK)) ps_cur_mb->u1_deblocking_mode |= MB_DISABLE_TOP_EDGE; break; } } return (0); } void ih264d_copy_intra_pred_line(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD32 nmb_index) { UWORD8 *pu1_mb_last_row, u1_mb_field_decoding_flag; UWORD32 u4_recWidth, u4_recwidth_cr; u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; u4_recWidth = ps_dec->u2_frm_wd_y << u1_mb_field_decoding_flag; u4_recwidth_cr = ps_dec->u2_frm_wd_uv << u1_mb_field_decoding_flag; pu1_mb_last_row = ps_dec->ps_frame_buf_ip_recon->pu1_dest_y + (u4_recWidth * (MB_SIZE - 1)); pu1_mb_last_row += MB_SIZE * nmb_index; MEMCPY_16BYTES(ps_dec->pu1_cur_y_intra_pred_line, pu1_mb_last_row); pu1_mb_last_row = ps_dec->ps_frame_buf_ip_recon->pu1_dest_u + (u4_recwidth_cr * (BLK8x8SIZE - 1)); pu1_mb_last_row += BLK8x8SIZE * nmb_index * YUV420SP_FACTOR; MEMCPY_16BYTES(ps_dec->pu1_cur_u_intra_pred_line, pu1_mb_last_row); ps_dec->pu1_cur_y_intra_pred_line = ps_dec->pu1_cur_y_intra_pred_line_base + (MB_SIZE * (ps_cur_mb_info->u2_mbx + 1)); ps_dec->pu1_cur_u_intra_pred_line = ps_dec->pu1_cur_u_intra_pred_line_base + (BLK8x8SIZE * (ps_cur_mb_info->u2_mbx + 1)) * YUV420SP_FACTOR; ps_dec->pu1_cur_v_intra_pred_line = ps_dec->pu1_cur_v_intra_pred_line_base + (BLK8x8SIZE * (ps_cur_mb_info->u2_mbx + 1)); if(ps_cur_mb_info->u2_mbx == (ps_dec->u2_frm_wd_in_mbs - 1)) { UWORD8* pu1_temp; ps_dec->pu1_cur_y_intra_pred_line = ps_dec->pu1_cur_y_intra_pred_line_base; ps_dec->pu1_cur_u_intra_pred_line = ps_dec->pu1_cur_u_intra_pred_line_base; ps_dec->pu1_cur_v_intra_pred_line = ps_dec->pu1_cur_v_intra_pred_line_base; /*swap current and previous rows*/ pu1_temp = ps_dec->pu1_cur_y_intra_pred_line; ps_dec->pu1_cur_y_intra_pred_line = ps_dec->pu1_prev_y_intra_pred_line; ps_dec->pu1_prev_y_intra_pred_line = pu1_temp; pu1_temp = ps_dec->pu1_cur_u_intra_pred_line; ps_dec->pu1_cur_u_intra_pred_line = ps_dec->pu1_prev_u_intra_pred_line; ps_dec->pu1_prev_u_intra_pred_line = pu1_temp; pu1_temp = ps_dec->pu1_cur_v_intra_pred_line; ps_dec->pu1_cur_v_intra_pred_line = ps_dec->pu1_prev_v_intra_pred_line; ps_dec->pu1_prev_v_intra_pred_line = pu1_temp; ps_dec->pu1_cur_y_intra_pred_line_base = ps_dec->pu1_cur_y_intra_pred_line; ps_dec->pu1_cur_u_intra_pred_line_base = ps_dec->pu1_cur_u_intra_pred_line; ps_dec->pu1_cur_v_intra_pred_line_base = ps_dec->pu1_cur_v_intra_pred_line; } } void ih264d_filter_boundary_left_mbaff(dec_struct_t *ps_dec, tfr_ctxt_t * ps_tfr_cxt, WORD8 i1_cb_qp_idx_ofst, WORD8 i1_cr_qp_idx_ofst, deblk_mb_t * ps_cur_mb, WORD32 i4_strd_y, WORD32 i4_strd_uv, deblk_mb_t * ps_left_mb, /* Neighbouring MB parameters */ UWORD32 pu4_bs_tab[], /* pointer to the BsTable array */ UWORD8 u1_cur_fld) { UWORD8 *pu1_y, *pu1_u, *pu1_v; UWORD8 uc_tmp, qp_avg; WORD32 alpha_u = 0, beta_u = 0, alpha_v = 0, beta_v = 0; WORD32 alpha_y = 0, beta_y = 0; WORD32 idx_b_u, idx_a_u, idx_b_v, idx_a_v; WORD32 idx_b_y, idx_a_y; UWORD32 u4_bs_val; UWORD8 *pu1_cliptab_u, *pu1_cliptab_v, *pu1_cliptab_y; UWORD8 u1_double_cl = !ps_cur_mb->u1_single_call; WORD32 ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; WORD32 ofst_b = ps_cur_mb->i1_slice_beta_offset; PROFILE_DISABLE_DEBLK() pu1_y = ps_tfr_cxt->pu1_mb_y; pu1_u = ps_tfr_cxt->pu1_mb_u; pu1_v = ps_tfr_cxt->pu1_mb_v; /* LUMA values */ /* Deblock rounding change */ uc_tmp = (UWORD8)((ps_left_mb->u1_mb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); qp_avg = uc_tmp; idx_a_y = qp_avg + ofst_a; alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; idx_b_y = qp_avg + ofst_b; beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; /* Chroma cb values */ { WORD32 mb_qp1, mb_qp2; mb_qp1 = (ps_left_mb->u1_mb_qp + i1_cb_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_u = qp_avg + ofst_a; alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; idx_b_u = qp_avg + ofst_b; beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; /* Chroma cr values */ { WORD32 mb_qp1, mb_qp2; mb_qp1 = (ps_left_mb->u1_mb_qp + i1_cr_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_v = qp_avg + ofst_a; alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; idx_b_v = qp_avg + ofst_b; beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; if(u1_double_cl == 0) { u4_bs_val = pu4_bs_tab[4]; if(0x04040404 == u4_bs_val) { ps_dec->pf_deblk_luma_vert_bs4(pu1_y, i4_strd_y, alpha_y, beta_y); ps_dec->pf_deblk_chroma_vert_bs4(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v); } else { if(u4_bs_val) { pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; ps_dec->pf_deblk_luma_vert_bslt4(pu1_y, i4_strd_y, alpha_y, beta_y, u4_bs_val, pu1_cliptab_y); ps_dec->pf_deblk_chroma_vert_bslt4(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs_val, pu1_cliptab_u, pu1_cliptab_v); } } } else { i4_strd_y <<= (!u1_cur_fld); u4_bs_val = pu4_bs_tab[4]; i4_strd_uv <<= (!u1_cur_fld); if(0x04040404 == u4_bs_val) { ps_dec->pf_deblk_luma_vert_bs4_mbaff(pu1_y, i4_strd_y, alpha_y, beta_y); ps_dec->pf_deblk_chroma_vert_bs4_mbaff(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v); } else { if(u4_bs_val) { pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; ps_dec->pf_deblk_luma_vert_bslt4_mbaff(pu1_y, i4_strd_y, alpha_y, beta_y, u4_bs_val, pu1_cliptab_y); ps_dec->pf_deblk_chroma_vert_bslt4_mbaff(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs_val, pu1_cliptab_u, pu1_cliptab_v); } } { UWORD16 u2_shift = (i4_strd_y >> 1) << (u1_cur_fld ? 4 : 0); pu1_y += u2_shift; u2_shift = (i4_strd_uv >> 1) << (u1_cur_fld ? 3 : 0); pu1_u += u2_shift; pu1_v += u2_shift; } uc_tmp = (((ps_left_mb + 1)->u1_mb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); qp_avg = uc_tmp; idx_a_y = qp_avg + ofst_a; alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; idx_b_y = qp_avg + ofst_b; beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; u4_bs_val = pu4_bs_tab[9]; { WORD32 mb_qp1, mb_qp2; mb_qp1 = ((ps_left_mb + 1)->u1_mb_qp + i1_cb_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_u = qp_avg + ofst_a; alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; idx_b_u = qp_avg + ofst_b; beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; u4_bs_val = pu4_bs_tab[9]; { WORD32 mb_qp1, mb_qp2; mb_qp1 = ((ps_left_mb + 1)->u1_mb_qp + i1_cr_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_v = qp_avg + ofst_a; alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; idx_b_v = qp_avg + ofst_b; beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; if(0x04040404 == u4_bs_val) { ps_dec->pf_deblk_luma_vert_bs4_mbaff(pu1_y, i4_strd_y, alpha_y, beta_y); ps_dec->pf_deblk_chroma_vert_bs4_mbaff(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v); } else { if(u4_bs_val) { pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; ps_dec->pf_deblk_luma_vert_bslt4_mbaff(pu1_y, i4_strd_y, alpha_y, beta_y, u4_bs_val, pu1_cliptab_y); ps_dec->pf_deblk_chroma_vert_bslt4_mbaff(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs_val, pu1_cliptab_u, pu1_cliptab_v); } } } } void ih264d_filter_boundary_topmbaff(dec_struct_t *ps_dec, tfr_ctxt_t * ps_tfr_cxt, WORD8 i1_cb_qp_idx_ofst, WORD8 i1_cr_qp_idx_ofst, deblk_mb_t * ps_cur_mb, WORD32 i4_strd_y, WORD32 i4_strd_uv, deblk_mb_t * ps_top_mb, UWORD32 u4_bs) { UWORD8 *pu1_y, *pu1_u; WORD32 alpha_u = 0, beta_u = 0, alpha_v = 0, beta_v = 0; WORD32 alpha_y = 0, beta_y = 0; WORD32 qp_avg; WORD32 idx_b_u, idx_a_u, idx_b_v, idx_a_v; WORD32 idx_b_y, idx_a_y; UWORD16 uc_tmp; UWORD8 *pu1_cliptab_u, *pu1_cliptab_v, *pu1_cliptab_y; WORD32 ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; WORD32 ofst_b = ps_cur_mb->i1_slice_beta_offset; /* LUMA values */ /* Deblock rounding change */ uc_tmp = ((ps_top_mb->u1_mb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); qp_avg = (UWORD8)uc_tmp; idx_a_y = qp_avg + ofst_a; alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; idx_b_y = qp_avg + ofst_b; beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; pu1_y = ps_tfr_cxt->pu1_mb_y; /* CHROMA cb values */ { WORD32 mb_qp1, mb_qp2; mb_qp1 = (ps_top_mb->u1_mb_qp + i1_cb_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_u = qp_avg + ofst_a; alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; idx_b_u = qp_avg + ofst_b; beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; /* CHROMA cr values */ { WORD32 mb_qp1, mb_qp2; mb_qp1 = (ps_top_mb->u1_mb_qp + i1_cr_qp_idx_ofst); mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); } idx_a_v = qp_avg + ofst_a; alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; idx_b_v = qp_avg + ofst_b; beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; pu1_u = ps_tfr_cxt->pu1_mb_u; if(u4_bs == 0x04040404) { /* Code specific to the assembly module */ ps_dec->pf_deblk_luma_horz_bs4(pu1_y, i4_strd_y, alpha_y, beta_y); ps_dec->pf_deblk_chroma_horz_bs4(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v); } else { if(u4_bs) { pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; ps_dec->pf_deblk_luma_horz_bslt4(pu1_y, i4_strd_y, alpha_y, beta_y, u4_bs, pu1_cliptab_y); ps_dec->pf_deblk_chroma_horz_bslt4(pu1_u, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs, pu1_cliptab_u, pu1_cliptab_v); } } } void ih264d_deblock_mb_mbaff(dec_struct_t *ps_dec, tfr_ctxt_t * ps_tfr_cxt, WORD8 i1_cb_qp_idx_ofst, WORD8 i1_cr_qp_idx_ofst, deblk_mb_t * ps_cur_mb, WORD32 i4_strd_y, WORD32 i4_strd_uv, deblk_mb_t * ps_top_mb, deblk_mb_t * ps_left_mb, UWORD8 u1_cur_fld, UWORD8 u1_extra_top_edge) { UWORD8 *pu1_y, *pu1_u; UWORD32 u4_bs; // WORD8 edge; WORD32 alpha, beta, alpha_u, beta_u, alpha_v, beta_v; UWORD8 *pu1_cliptab_u; UWORD8 *pu1_cliptab_v; UWORD8 *pu1_cliptab_y; UWORD32 * pu4_bs_tab = ps_cur_mb->u4_bs_table; WORD32 idx_a_y, idx_a_u, idx_a_v; /* Return from here to switch off deblocking */ PROFILE_DISABLE_DEBLK() i4_strd_y <<= u1_cur_fld; i4_strd_uv <<= u1_cur_fld; /*--------------------------------------------------------------------*/ /* Filter wrt Left edge */ /* except */ /* - Left Egde is Picture Boundary */ /* - Left Egde is part of Slice Boundary and Deblocking */ /* parameters of slice disable Filtering of Slice Boundary Edges*/ /*--------------------------------------------------------------------*/ if(ps_left_mb) ih264d_filter_boundary_left_mbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, i1_cr_qp_idx_ofst, ps_cur_mb, i4_strd_y, i4_strd_uv, ps_left_mb, pu4_bs_tab, u1_cur_fld); /*--------------------------------------------------------------------*/ /* Filter wrt Other Vertical Edges */ /*--------------------------------------------------------------------*/ { WORD32 ofst_a, ofst_b, idx_b_y, idx_b_u, idx_b_v; WORD32 qp_avg, qp_avg_u, qp_avg_v; ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; ofst_b = ps_cur_mb->i1_slice_beta_offset; qp_avg = ps_cur_mb->u1_mb_qp; idx_a_y = qp_avg + ofst_a; alpha = gau1_ih264d_alpha_table[12 + idx_a_y]; idx_b_y = qp_avg + ofst_b; beta = gau1_ih264d_beta_table[12 + idx_b_y]; /* CHROMA Cb values */ qp_avg_u = (qp_avg + i1_cb_qp_idx_ofst); qp_avg_u = gau1_ih264d_qp_scale_cr[12 + qp_avg_u]; idx_a_u = qp_avg_u + ofst_a; alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; idx_b_u = qp_avg_u + ofst_b; beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; /* CHROMA Cr values */ qp_avg_v = (qp_avg + i1_cr_qp_idx_ofst); qp_avg_v = gau1_ih264d_qp_scale_cr[12 + qp_avg_v]; idx_a_v = qp_avg_v + ofst_a; alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; idx_b_v = qp_avg_v + ofst_b; beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; } //STARTL4_FILTER_VERT; pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; //this for Luma pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; //this for chroma pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; //this for chroma //edge=1 u4_bs = pu4_bs_tab[5]; pu1_y = ps_tfr_cxt->pu1_mb_y; pu1_u = ps_tfr_cxt->pu1_mb_u; if(u4_bs) { ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 4, i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); } //edge=2 u4_bs = pu4_bs_tab[6]; if(u4_bs) { ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 8, i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); ps_dec->pf_deblk_chroma_vert_bslt4(pu1_u + 4 * YUV420SP_FACTOR, i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs, pu1_cliptab_u, pu1_cliptab_v); } //edge=3 u4_bs = pu4_bs_tab[7]; if(u4_bs) { ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 12, i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); } /*--------------------------------------------------------------------*/ /* Filter wrt Top edge */ /* except */ /* - Top Egde is Picture Boundary */ /* - Top Egde is part of Slice Boundary and Deblocking */ /* parameters of slice disable Filtering of Slice Boundary Edges*/ /*--------------------------------------------------------------------*/ if(ps_top_mb) { /** if top MB and MB AFF and cur MB is frame and top is field then */ /* one extra top edge needs to be deblocked */ if(u1_extra_top_edge) { ih264d_filter_boundary_topmbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, i1_cr_qp_idx_ofst, ps_cur_mb, (UWORD16)(i4_strd_y << 1), (UWORD16)(i4_strd_uv << 1), ps_top_mb - 1, pu4_bs_tab[8]); ps_tfr_cxt->pu1_mb_y += i4_strd_y; ps_tfr_cxt->pu1_mb_u += i4_strd_uv; ps_tfr_cxt->pu1_mb_v += i4_strd_uv; ih264d_filter_boundary_topmbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, i1_cr_qp_idx_ofst, ps_cur_mb, (UWORD16)(i4_strd_y << 1), (UWORD16)(i4_strd_uv << 1), ps_top_mb, pu4_bs_tab[0]); ps_tfr_cxt->pu1_mb_y -= i4_strd_y; ps_tfr_cxt->pu1_mb_u -= i4_strd_uv; ps_tfr_cxt->pu1_mb_v -= i4_strd_uv; } else { ih264d_filter_boundary_topmbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, i1_cr_qp_idx_ofst, ps_cur_mb, i4_strd_y, i4_strd_uv, ps_top_mb, pu4_bs_tab[0]); } } /*--------------------------------------------------------------------*/ /* Filter wrt Other Horizontal Edges */ /*--------------------------------------------------------------------*/ //edge1 u4_bs = pu4_bs_tab[1]; if(u4_bs) { ps_dec->pf_deblk_luma_horz_bslt4(pu1_y + (i4_strd_y << 2), i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); } //edge2 u4_bs = pu4_bs_tab[2]; if(u4_bs) { ps_dec->pf_deblk_luma_horz_bslt4(pu1_y + (i4_strd_y << 3), i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); ps_dec->pf_deblk_chroma_horz_bslt4(pu1_u + (i4_strd_uv << 2), i4_strd_uv, alpha_u, beta_u, alpha_v, beta_v, u4_bs, pu1_cliptab_u, pu1_cliptab_v); } //edge3 u4_bs = pu4_bs_tab[3]; if(u4_bs) { ps_dec->pf_deblk_luma_horz_bslt4( (pu1_y + (i4_strd_y << 3) + (i4_strd_y << 2)), i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); } } ================================================ FILE: dependencies/ih264d/decoder/ih264d_deblocking.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_DEBLOCKING_H_ #define _IH264D_DEBLOCKING_H_ /*! ************************************************************************** * \file ih264d_deblocking.h * * \brief * Declarations of deblocking functions * * \date * 23/11/2002 * * \author AI ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" WORD8 ih264d_set_deblocking_parameters(deblk_mb_t * ps_cur_deblk_mb, dec_slice_params_t * ps_slice, UWORD8 u1_mb_ngbr_availablity, UWORD8 u1_mb_field_decoding_flag); void ih264d_copy_intra_pred_line(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD32 nmb_index); void FilterBoundaryLeft(tfr_ctxt_t * const ps_tfr_cxt, const WORD8 i1_cb_qp_idx_ofst, const WORD8 i1_cr_qp_idx_ofst, deblk_mb_t * const ps_cur_mb, UWORD16 u2_strd_y, UWORD16 u2_strd_uv, deblk_mb_t * const ps_left_mb, const UWORD32 pu4_bs_tab[], const UWORD8 u1_cur_fld); void FilterBoundaryTop(tfr_ctxt_t * const ps_tfr_cxt, const WORD8 i1_cb_qp_idx_ofst, const WORD8 i1_cr_qp_idx_ofst, deblk_mb_t * const ps_cur_mb, const UWORD16 u2_strd_y, const UWORD16 u2_strd_uv, deblk_mb_t * const ps_top_mb, const UWORD32 u4_bs); void deblock_mb(tfr_ctxt_t * const ps_tfr_cxt, const WORD8 i1_cb_qp_idx_ofst, const WORD8 i1_cr_qp_idx_ofst, deblk_mb_t * const ps_cur_mb, WORD32 i4_strd_y, WORD32 i4_strd_uv, deblk_mb_t * const ps_top_mb, deblk_mb_t * const ps_left_mb, const UWORD8 u1_cur_fld, const UWORD8 u1_extra_top_edge); void ih264d_deblock_mb_mbaff(dec_struct_t *ps_dec, tfr_ctxt_t * const ps_tfr_cxt, const WORD8 i1_cb_qp_idx_ofst, const WORD8 i1_cr_qp_idx_ofst, deblk_mb_t * const ps_cur_mb, WORD32 i4_strd_y, WORD32 i4_strd_uv, deblk_mb_t * const ps_top_mb, deblk_mb_t * const ps_left_mb, const UWORD8 u1_cur_fld, const UWORD8 u1_extra_top_edge); void ih264d_deblock_picture_mbaff(dec_struct_t * const ps_dec); void ih264d_deblock_picture_non_mbaff(dec_struct_t * const ps_dec); void ih264d_deblock_picture_progressive(dec_struct_t * const ps_dec); void ih264d_compute_bs_mbaff(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, const UWORD16 u2_mbxn_mb); void ih264d_compute_bs_non_mbaff(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, const UWORD16 u2_mbxn_mb); void ih264d_fill_bs_mbedge_2(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, const UWORD16 u2_mbxn_mb); void ih264d_fill_bs_mbedge_4(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, const UWORD16 u2_mbxn_mb); void ih264d_fill_bs1_16x16mb_pslice(mv_pred_t *ps_cur_mv_pred, mv_pred_t *ps_top_mv_pred, void **ppv_map_ref_idx_to_poc, UWORD32 *pu4_bs_table, mv_pred_t *ps_leftmost_mv_pred, neighbouradd_t *ps_left_addr, void **u4_pic_addrress, WORD32 i4_ver_mvlimit); void ih264d_fill_bs1_non16x16mb_pslice(mv_pred_t *ps_cur_mv_pred, mv_pred_t *ps_top_mv_pred, void **ppv_map_ref_idx_to_poc, UWORD32 *pu4_bs_table, mv_pred_t *ps_leftmost_mv_pred, neighbouradd_t *ps_left_addr, void **u4_pic_addrress, WORD32 i4_ver_mvlimit); void ih264d_fill_bs1_16x16mb_bslice(mv_pred_t *ps_cur_mv_pred, mv_pred_t *ps_top_mv_pred, void **ppv_map_ref_idx_to_poc, UWORD32 *pu4_bs_table, mv_pred_t *ps_leftmost_mv_pred, neighbouradd_t *ps_left_addr, void **u4_pic_addrress, WORD32 i4_ver_mvlimit); void ih264d_fill_bs1_non16x16mb_bslice(mv_pred_t *ps_cur_mv_pred, mv_pred_t *ps_top_mv_pred, void **ppv_map_ref_idx_to_poc, UWORD32 *pu4_bs_table, mv_pred_t *ps_leftmost_mv_pred, neighbouradd_t *ps_left_addr, void **u4_pic_addrress, WORD32 i4_ver_mvlimit); void ih264d_fill_bs_xtra_left_edge_cur_fld(UWORD32 *pu4_bs, WORD32 u4_left_mb_t_csbp, WORD32 u4_left_mb_b_csbp, WORD32 u4_cur_mb_csbp, UWORD32 u4_cur_mb_top); void ih264d_fill_bs_xtra_left_edge_cur_frm(UWORD32 *pu4_bs, WORD32 u4_left_mb_t_csbp, WORD32 u4_left_mb_b_csbp, WORD32 u4_cur_mb_csbp, UWORD32 u4_cur_mb_top); void ih264d_deblock_mb_nonmbaff(dec_struct_t *ps_dec, tfr_ctxt_t * const ps_tfr_cxt, const WORD8 i1_cb_qp_idx_ofst, const WORD8 i1_cr_qp_idx_ofst, WORD32 i4_strd_y, WORD32 i4_strd_uv); void ih264d_init_deblk_tfr_ctxt(dec_struct_t * ps_dec, pad_mgr_t *ps_pad_mgr, tfr_ctxt_t *ps_tfr_cxt, UWORD16 u2_image_wd_mb, UWORD8 u1_mbaff); void ih264d_deblock_mb_level(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD32 nmb_index); #endif /* _IH264D_DEBLOCKING_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_debug.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_DEBUG_H_ #define _IH264D_DEBUG_H_ /*! ************************************************************************** * \file ih264d_debug.h * * \brief * Contains declarations used for debugging * * \date * 2/12/2002 * * \author AI ************************************************************************** */ #ifdef DEBUG_DEC #define H264_DEC_DEBUG_PRINT(...) printf("\n[H264_DEBUG] %s/%d:: ", __FUNCTION__, __LINE__);printf(__VA_ARGS__) #else //DEBUG_DEC #define H264_DEC_DEBUG_PRINT(...) {} #endif //DEBUG_DEC #define STRENGTH_DEBLOCKING 0 //sanjeev #define DEBUG_RECONSTRUCT_LUMA 0 #define DEBUG_RECONSTRUCT_CHROMA 0 #define DEBUG_IDCT 0 #define DEBUG_LUMA_IDCT 0 #define DEBUG_REF_IDCT 0 #define BIN_BIT_RATIO 0 #define MB_PART_HIST 0 #define MB_INTRA_PREDICTION 1 #ifdef WIN32 #define CHK_PURIFY 0 #else #define CHK_PURIFY 0 #endif #if MB_INTRA_PREDICTION #define MB_INTRA_CHROMA_PREDICTION_ON 1 #define MB_INTRA_4x4_PREDICTION_ON 1 #define MB_INTRA_16x16_PREDICTION_ON 1 #endif #define TRACE 0 #define DEBUG_CABAC 0 #define DEBUG_ABS_MVD 0 #define DEBUG_INTRA_PRED_MODES 0 #define DEBUG_DEBLOCKING 0 #define COPYTHECONTEXT(s,val) #define PRINT_TRACE #define PRINT_TRACE_CAB #define SWITCHOFFTRACE #define SWITCHONTRACE #define SWITCHOFFTRACECABAC #define SWITCHONTRACECABAC #define INC_BIN_COUNT(ps_cab_env) #define INC_DECISION_BINS(ps_cab_env) #define INC_BYPASS_BINS(ps_cab_env) #define INC_SYM_COUNT(ps_cab_env) #define PRINT_BIN_BIT_RATIO(ps_dec) #define RESET_BIN_COUNTS(ps_cab_env) #ifdef PROFILE_DIS_DEBLK #define PROFILE_DISABLE_DEBLK() return; #else #define PROFILE_DISABLE_DEBLK() ; #endif #ifdef PROFILE_DIS_IQ_IT_RECON #define PROFILE_DISABLE_IQ_IT_RECON() if (0) #define PROFILE_DISABLE_IQ_IT_RECON_RETURN() return; #else #define PROFILE_DISABLE_IQ_IT_RECON() ; #define PROFILE_DISABLE_IQ_IT_RECON_RETURN() ; #endif #ifdef PROFILE_DIS_INTRA_PRED #define PROFILE_DISABLE_INTRA_PRED() if (0) #else #define PROFILE_DISABLE_INTRA_PRED() ; #endif #ifdef PROFILE_DIS_UNPACK #define PROFILE_DISABLE_UNPACK_LUMA() return 0; #define PROFILE_DISABLE_UNPACK_CHROMA() return ; #else #define PROFILE_DISABLE_UNPACK_LUMA() ; #define PROFILE_DISABLE_UNPACK_CHROMA() ; #endif #ifdef PROFILE_DIS_INTER_PRED #define PROFILE_DISABLE_INTER_PRED() return; #else #define PROFILE_DISABLE_INTER_PRED() ; #endif #ifdef PROFILE_DIS_BOUNDARY_STRENGTH #define PROFILE_DISABLE_BOUNDARY_STRENGTH() return; #else #define PROFILE_DISABLE_BOUNDARY_STRENGTH() ; #endif #ifdef PROFILE_DIS_MB_PART_INFO #define PROFILE_DISABLE_MB_PART_INFO() return 0; #else #define PROFILE_DISABLE_MB_PART_INFO() ; #endif #endif /* _IH264D_DEBUG_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_defs.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_DEFS_H_ #define _IH264D_DEFS_H_ /** ************************************************************************ * \file ih264d_defs.h * * \brief * Type definitions used in the code * * \date * 19/11/2002 * * \author Sriram Sethuraman * ************************************************************************ */ #include <stdint.h> #define H264_MAX_FRAME_WIDTH 4080 #define H264_MAX_FRAME_HEIGHT 4080 #define H264_MAX_FRAME_SIZE (4096 * 2048) #define H264_MIN_FRAME_WIDTH 16 #define H264_MIN_FRAME_HEIGHT 16 #define FMT_CONV_NUM_ROWS 16 /** Decoder currently has an additional latency of 2 pictures when * returning output for display */ #define DISPLAY_LATENCY 2 /** Bit manipulation macros */ #define CHECKBIT(a,i) ((a) & (1 << i)) #define CLEARBIT(a,i) ((a) &= ~(1 << i)) /** Macro to check if a number lies in the valid integer range */ #define IS_OUT_OF_RANGE_S32(a) (((a) < INT32_MIN) || ((a) > INT32_MAX)) /** Macro to convert a integer to a boolean value */ #define BOOLEAN(x) (!!(x)) /** Arithmetic operations */ #define MOD(x,y) ((x)%(y)) #define DIV(x,y) ((x)/(y)) #define MUL(x,y) ((x)*(y)) #define SIGN_POW2_DIV(x, y) (((x) < 0) ? (-((-(x)) >> (y))) : ((x) >> (y))) #define MB_ENABLE_FILTERING 0x00 #define MB_DISABLE_FILTERING 0x01 #define MB_DISABLE_TOP_EDGE 0x02 #define MB_DISABLE_LEFT_EDGE 0x04 /** Maximum number of reference pics */ #define MAX_REF_BUFS 32 #define MAX_DISP_BUFS_NEW 64 #define MAX_FRAMES 16 #define INVALID_FRAME_NUM 0x0fffffff #define GAP_FRAME_NUM 0x1fffffff /** macros for reference picture lists, refIdx to POC mapping */ // 1 extra entry into reference picture lists for refIdx = -1. // this entry is always 0. this saves conditional checks in // FillBs modules. #define POC_LIST_L0_TO_L1_DIFF (( 2*MAX_FRAMES) + 1) #define POC_LIST_L0_TO_L1_DIFF_1 ((MAX_FRAMES) + 1) #define FRM_LIST_L0 0 //0 #define FRM_LIST_L1 1 * POC_LIST_L0_TO_L1_DIFF//FRM_LIST_L0 + POC_LIST_L0_TO_L1_DIFF //0+33 //(1 * POC_LIST_L0_TO_L1_DIFF) #define TOP_LIST_FLD_L0 2 * POC_LIST_L0_TO_L1_DIFF//FRM_LIST_L1 + POC_LIST_L0_TO_L1_DIFF //0+33+33 //(2 * POC_LIST_L0_TO_L1_DIFF) #define TOP_LIST_FLD_L1 3 * POC_LIST_L0_TO_L1_DIFF//TOP_LIST_FLD_L0 + POC_LIST_L0_TO_L1_DIFF_1 //0+33+33+17 //(3 * POC_LIST_L0_TO_L1_DIFF) #define BOT_LIST_FLD_L0 4 * POC_LIST_L0_TO_L1_DIFF//TOP_LIST_FLD_L1 + POC_LIST_L0_TO_L1_DIFF_1 //0+33+33+17+17 #define BOT_LIST_FLD_L1 5 * POC_LIST_L0_TO_L1_DIFF//BOT_LIST_FLD_L0 + POC_LIST_L0_TO_L1_DIFF_1 //0+33+33+17+17+17 #define TOTAL_LIST_ENTRIES 6 * POC_LIST_L0_TO_L1_DIFF//BOT_LIST_FLD_L1 + POC_LIST_L0_TO_L1_DIFF_1 //0+33+33+17+17+17+17 #define PAD_MV_BANK_ROW 64 #define OFFSET_MV_BANK_ROW ((PAD_MV_BANK_ROW)>>1) #define PAD_PUC_CURNNZ 32 #define OFFSET_PUC_CURNNZ (PAD_PUC_CURNNZ) #define PAD_MAP_IDX_POC (1) #define OFFSET_MAP_IDX_POC (1) #define OFFSET_MAP_IDX_POC (1) #define NAL_REF_IDC(nal_first_byte) ((nal_first_byte >> 5) & 0x3) #define NAL_FORBIDDEN_BIT(nal_first_byte) (nal_first_byte>>7) #define NAL_UNIT_TYPE(nal_first_byte) (nal_first_byte & 0x1F) #define INT_PIC_TYPE_I (0x00) #define YIELD_CNT_THRESHOLD 8 #define OK 0 #define END 1 #define NOT_OK -1 /* For 420SP */ #define YUV420SP_FACTOR 2 /*To prevent buffer overflow access; in case the size of nal unit is * greater than the allocated buffer size*/ #define EXTRA_BS_OFFSET 16*16*2 /** *************************************************************************** * Enum to hold various mem records being request **************************************************************************** */ enum { /** * Codec Object at API level */ MEM_REC_IV_OBJ, /** * Codec context */ MEM_REC_CODEC, /** * Bitstream buffer which holds emulation prevention removed bytes */ MEM_REC_BITSBUF, /** * Buffer to hold coeff data */ MEM_REC_COEFF_DATA, /** * Motion vector bank */ MEM_REC_MVBANK, /** * Holds mem records passed to the codec. */ MEM_REC_BACKUP, /** * Holds SPS */ MEM_REC_SPS, /** * Holds PPS */ MEM_REC_PPS, /** * Holds Slice Headers */ MEM_REC_SLICE_HDR, /** * Holds thread handles */ MEM_REC_THREAD_HANDLE, /** * Contains i4_status map indicating parse i4_status per MB basis */ MEM_REC_PARSE_MAP, /** * Contains i4_status map indicating processing i4_status per MB basis */ MEM_REC_PROC_MAP, /** * Contains slice number info for each MB */ MEM_REC_SLICE_NUM_MAP, /** * Holds dpb manager context */ MEM_REC_DPB_MGR, /** * Holds neighbors' info */ MEM_REC_NEIGHBOR_INFO, /** * Holds neighbors' info */ MEM_REC_PRED_INFO, /** * Holds inter pred inforamation on packed format info */ MEM_REC_PRED_INFO_PKD, /** * Holds neighbors' info */ MEM_REC_MB_INFO, /** * Holds deblock Mb info structure frame level) */ MEM_REC_DEBLK_MB_INFO, /** * Holds reference picture buffers in non-shared mode */ MEM_REC_REF_PIC, /** * Holds some misc intermediate_buffers */ MEM_REC_EXTRA_MEM, /** * Holds some misc intermediate_buffers */ MEM_REC_INTERNAL_SCRATCH, /** * Holds some misc intermediate_buffers */ MEM_REC_INTERNAL_PERSIST, /* holds structures related to picture buffer manager*/ MEM_REC_PIC_BUF_MGR, /*holds structure related to MV buffer manager*/ MEM_REC_MV_BUF_MGR, /** * Place holder to compute number of memory records. */ MEM_REC_CNT /* Do not add anything below */ }; #ifdef DEBLOCK_THREAD #define H264_MUTEX_LOCK(lock) ithread_mutex_lock(lock) #define H264_MUTEX_UNLOCK(lock) ithread_mutex_unlock(lock) #else //DEBLOCK_THREAD #define H264_MUTEX_LOCK(lock) #define H264_MUTEX_UNLOCK(lock) #define DEBUG_THREADS_PRINTF(...) #define DEBUG_PERF_PRINTF(...) /** Profile Types*/ #define BASE_PROFILE_IDC 66 #define MAIN_PROFILE_IDC 77 #define EXTENDED_PROFILE_IDC 88 #define HIGH_PROFILE_IDC 100 #define MB_SIZE 16 #define BLK8x8SIZE 8 #define BLK_SIZE 4 #define NUM_BLKS_PER_MB 24 #define NUM_LUM_BLKS_PER_MB 16 #define LUM_BLK 0 #define CHROM_BLK 1 #define NUM_PELS_IN_MB 64 /* Level Types */ #define H264_LEVEL_1_0 10 #define H264_LEVEL_1_1 11 #define H264_LEVEL_1_2 12 #define H264_LEVEL_1_3 13 #define H264_LEVEL_2_0 20 #define H264_LEVEL_2_1 21 #define H264_LEVEL_2_2 22 #define H264_LEVEL_3_0 30 #define H264_LEVEL_3_1 31 #define H264_LEVEL_3_2 32 #define H264_LEVEL_4_0 40 #define H264_LEVEL_4_1 41 #define H264_LEVEL_4_2 42 #define H264_LEVEL_5_0 50 #define H264_LEVEL_5_1 51 #define MAX_MBS_LEVEL_51 36864 #define MAX_MBS_LEVEL_50 22080 #define MAX_MBS_LEVEL_42 8704 #define MAX_MBS_LEVEL_41 8192 #define MAX_MBS_LEVEL_40 8192 #define MAX_MBS_LEVEL_32 5120 #define MAX_MBS_LEVEL_31 3600 #define MAX_MBS_LEVEL_30 1620 #define MAX_MBS_LEVEL_22 1620 #define MAX_MBS_LEVEL_21 792 #define MAX_MBS_LEVEL_20 396 #define MAX_MBS_LEVEL_13 396 #define MAX_MBS_LEVEL_12 396 #define MAX_MBS_LEVEL_11 396 #define MAX_MBS_LEVEL_10 99 /** NAL Types */ #define SLICE_NAL 1 #define SLICE_DATA_PARTITION_A_NAL 2 #define SLICE_DATA_PARTITION_B_NAL 3 #define SLICE_DATA_PARTITION_C_NAL 4 #define IDR_SLICE_NAL 5 #define SEI_NAL 6 #define SEQ_PARAM_NAL 7 #define PIC_PARAM_NAL 8 #define ACCESS_UNIT_DELIMITER_RBSP 9 #define END_OF_SEQ_RBSP 10 #define END_OF_STREAM_RBSP 11 #define FILLER_DATA_NAL 12 /** Entropy coding modes */ #define CAVLC 0 #define CABAC 1 /** Picture Types */ #define I_PIC 0 #define IP_PIC 1 #define IPB_PIC 2 #define SI_PIC 3 #define SIP_PIC 4 #define ISI_PIC 5 #define ISI_PSP_PIC 6 #define ALL_PIC 7 /* Frame or field picture type */ #define FRM_PIC 0x00 #define TOP_FLD 0x01 #define BOT_FLD 0x02 #define COMP_FLD_PAIR 0x03 /* TOP_FLD | BOT_FLD */ #define AFRM_PIC 0x04 #define TOP_REF 0x08 #define BOT_REF 0x10 #define PIC_MASK 0x03 #define NON_EXISTING 0xff /* field picture type for display */ #define DISP_TOP_FLD 0x00 #define DISP_BOT_FLD 0x01 /** Slice Types */ #define NA_SLICE -1 #define P_SLICE 0 #define B_SLICE 1 #define I_SLICE 2 #define SP_SLICE 3 #define SI_SLICE 4 /* Definition for picture skip */ #define SKIP_NONE (0x0) #define I_SLC_BIT (0x1) #define P_SLC_BIT (0x2) #define B_SLC_BIT (0x4) /** Macros used for Deblocking */ #define D_INTER_MB 0 #define D_INTRA_MB 1 #define D_PRED_NON_16x16 2 #define D_B_SLICE 4 #define D_B_SUBMB 6 //D_B_SLICE | D_PRED_NON_16x16 | D_INTER_MB #define D_FLD_MB 0x80 /** Macros for Cabac checks */ /** MbType */ /** |x|x|I_PCM|SKIP| |S|Inter/Intra|P/B|NON-BD16x16/BD16x16,I16x16/I4x4| */ #define CAB_INTRA 0x00 /* 0000 00xx */ #define CAB_INTER 0x04 /* 0000 01xx */ #define CAB_I4x4 0x00 /* 0000 00x0 */ #define CAB_I16x16 0x01 /* 0000 00x1 */ #define CAB_BD16x16 0x04 /* 0000 0100 */ #define CAB_NON_BD16x16 0x05 /* 0000 0101 */ #define CAB_P 0x07 /* 0000 0111 */ #define CAB_SI4x4 0x08 /* 0000 10x0 */ #define CAB_SI16x16 0x09 /* 0000 10x1 */ #define CAB_SKIP_MASK 0x10 /* 0001 0000 */ #define CAB_SKIP 0x10 /* 0001 0000 */ #define CAB_P_SKIP 0x16 /* 0001 x11x */ #define CAB_B_SKIP 0x14 /* 0001 x100 */ #define CAB_BD16x16_MASK 0x07 /* 0000 0111 */ #define CAB_INTRA_MASK 0x04 /* 0000 0100 */ #define CAB_I_PCM 0x20 /* 001x xxxx */ /**< Binarization types for CABAC */ /* |x|x|x|x|MSB_FIRST_FLC|FLC|TUNARY|UNARY| */ #define UNARY 1 #define TUNARY 2 #define FLC 4 #define MSB_FIRST_FLC 12 /** Macroblock Types */ #define I_4x4_MB 0 #define I_16x16_MB 1 #define P_MB 2 #define B_MB 3 #define SI_MB 4 #define SP_MB 5 #define I_PCM_MB 6 #define SI4x4_MB 0xFF /** Intra luma 16x16 and chroma 8x8 prediction modes */ #define NUM_INTRA_PRED_MODES 4 #define VERT 0 #define HORIZ 1 #define DC 2 #define PLANE 3 #define NOT_VALID -1 #define DC_DC_DC_DC 0x02020202 /*packed 4 bytes used in Decode Intra Mb*/ /** Intra luma 4x4 prediction modes */ #define NUM_INTRA4x4_PRED_MODES 9 /** VERT, HORIZ, DC are applicable to 4x4 as well */ /** D - Down; U - Up; L - Left; R - Right */ #define DIAG_DL 3 #define DIAG_DR 4 #define VERT_R 5 #define HORIZ_D 6 #define VERT_L 7 #define HORIZ_U 8 /** P_MB prediction modes */ #define NUM_INTER_MB_PRED_MODES 5 #define PRED_16x16 0 #define PRED_16x8 1 #define PRED_8x16 2 #define PRED_8x8 3 #define PRED_8x8R0 4 #define MAGIC_16x16 5 #define MB_SKIP 255 /* P_MB submb modes */ #define P_L0_8x8 0 #define P_L0_8x4 1 #define P_L0_4x8 2 #define P_L0_4x4 3 /* B_MB submb modes */ #define B_DIRECT_8x8 0 #define B_L0_8x8 1 #define B_L1_8x8 2 #define B_BI_8x8 3 #define B_L0_8x4 4 #define B_L0_4x8 5 #define B_L1_8x4 6 #define B_L1_4x8 7 #define B_BI_8x4 8 #define B_BI_4x8 9 #define B_L0_4x4 10 #define B_L1_4x4 11 #define B_BI_4x4 12 /** B_MB prediction modes */ #define B_8x8 22 #define PRED_INVALID -1 #define B_DIRECT 0 #define PRED_L0 1 #define PRED_L1 2 #define BI_PRED 3 #define B_DIRECT_BI_PRED 23 #define B_DIRECT_PRED_L0 24 #define B_DIRECT_PRED_L1 25 #define B_DIRECT_SPATIAL 26 #define B_DIRECT8x8_BI_PRED 13 #define B_DIRECT8x8_PRED_L0 14 #define B_DIRECT8x8_PRED_L1 15 #define ONE_TO_ONE 0 #define FRM_TO_FLD 1 #define FLD_TO_FRM 2 /** Inter Sub MB Pred modes */ #define NUM_INTER_SUBMB_PRED_MODES 4 #define SUBMB_8x8 0 #define SUBMB_8x4 1 #define SUBMB_4x8 2 #define SUBMB_4x4 3 /** Coded Block Pattern - Chroma */ #define CBPC_ALLZERO 0 #define CBPC_ACZERO 1 #define CBPC_NONZERO 2 /** Index for accessing the left MB in the MV predictor array */ #define LEFT 0 /** Index for accessing the top MB in the MV predictor array */ #define TOP 1 /** Index for accessing the top right MB in the MV predictor array */ #define TOP_R 2 /** Index for accessing the top Left MB in the MV predictor array */ #define TOP_L 3 /** Maximum number of Sequence Parameter sets */ #define MAX_NUM_SEQ_PARAMS 32 /** Maximum number of Picture Parameter sets */ #define MAX_NUM_PIC_PARAMS 256 #define MASK_ERR_SEQ_SET_ID (0xFFFFFFE0) #define MASK_ERR_PIC_SET_ID (0xFFFFFF00) #define MAX_PIC_ORDER_CNT_TYPE 2 #define MAX_BITS_IN_FRAME_NUM 16 #define MAX_BITS_IN_POC_LSB 16 #define H264_MAX_REF_PICS 16 #define H264_MAX_REF_IDX 32 #define MAX_WEIGHT_BIPRED_IDC 2 #define MAX_CABAC_INIT_IDC 2 #define H264_DEFAULT_NUM_CORES 1 #define DEFAULT_SEPARATE_PARSE (H264_DEFAULT_NUM_CORES == 2)? 1 :0 /** Maximum number of Slice groups */ #define MAX_NUM_SLICE_GROUPS 8 #define MAX_NUM_REF_FRAMES_OFFSET 255 /** Deblocking modes for a slice */ #define SLICE_BOUNDARY_DBLK_DISABLED 2 #define DBLK_DISABLED 1 #define DBLK_ENABLED 0 #define MIN_DBLK_FIL_OFF -12 #define MAX_DBLK_FIL_OFF 12 /** Width of the predictor buffers used for MC */ #define MB_SIZE 16 #define BLK8x8SIZE 8 #define BLK_SIZE 4 #define NUM_BLKS_PER_MB 24 #define NUM_LUM_BLKS_PER_MB 16 #define SUB_BLK_WIDTH 4 #define SUB_SUB_BLK_SIZE 4 /* 2x2 pixel i4_size */ #define SUB_BLK_SIZE ((SUB_BLK_WIDTH) * (SUB_BLK_WIDTH)) #define MB_LUM_SIZE 256 #define MB_CHROM_SIZE 64 /**< Width to pad the luminance frame buff */ /**< Height to pad the luminance frame buff */ /**< Width to pad the chrominance frame buff */ /**< Height to pad the chrominance frame buff */ #define PAD_LEN_Y_H 32 #define PAD_LEN_Y_V 20 #define PAD_LEN_UV_H 16 #define PAD_LEN_UV_V 8 #define PAD_MV_BANK_ROW 64 /**< Maimum u4_ofst by which the Mvs could point outside the frame buffers horizontally in the left and vertically in the top direction */ #define MAX_OFFSET_OUTSIDE_X_FRM -20 #define MAX_OFFSET_OUTSIDE_Y_FRM -20 #define MAX_OFFSET_OUTSIDE_UV_FRM -8 /** UVLC parsing macros */ #define UEV 1 #define SEV 2 #define TEV 3 /** Defines for Boolean values */ #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #define UNUSED_FOR_REF 0 #define IS_SHORT_TERM 1 #define IS_LONG_TERM 2 /** Defines for which field gets displayed first */ #define MAX_FRAMES 16 #define INVALID_FRAME_NUM 0x0fffffff #define DO_NOT_DISP 254 #define DISP_FLD_FIRST_UNDEF 0 #define DISP_TOP_FLD_FIRST 1 #define DISP_BOT_FLD_FIRST 2 /** Misc error resilience requirements*/ #define MAX_LOG2_WEIGHT_DENOM 7 #define PRED_WEIGHT_MIN (-128) #define PRED_WEIGHT_MAX 127 #define MAX_REDUNDANT_PIC_CNT 127 #endif //DEBLOCK_THREAD #define NUM_COEFFS_IN_4x4BLK 16 #define CABAC_BITS_TO_READ 23 #define DISPLAY_PRIMARIES_X_UPPER_LIMIT 37000 #define DISPLAY_PRIMARIES_X_LOWER_LIMIT 5 #define DISPLAY_PRIMARIES_X_DIVISION_FACTOR 5 #define DISPLAY_PRIMARIES_Y_UPPER_LIMIT 42000 #define DISPLAY_PRIMARIES_Y_LOWER_LIMIT 5 #define DISPLAY_PRIMARIES_Y_DIVISION_FACTOR 5 #define WHITE_POINT_X_UPPER_LIMIT 37000 #define WHITE_POINT_X_LOWER_LIMIT 5 #define WHITE_POINT_X_DIVISION_FACTOR 5 #define WHITE_POINT_Y_UPPER_LIMIT 42000 #define WHITE_POINT_Y_LOWER_LIMIT 5 #define WHITE_POINT_Y_DIVISION_FACTOR 5 #define MAX_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT 100000000 #define MAX_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT 50000 #define MAX_DISPLAY_MASTERING_LUMINANCE_DIVISION_FACTOR 10000 #define MIN_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT 50000 #define MIN_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT 1 #define AMBIENT_LIGHT_X_UPPER_LIMIT 50000 #define AMBIENT_LIGHT_Y_UPPER_LIMIT 50000 #define CCV_PRIMARIES_X_UPPER_LIMIT 5000000 #define CCV_PRIMARIES_X_LOWER_LIMIT -5000000 #define CCV_PRIMARIES_Y_UPPER_LIMIT 5000000 #define CCV_PRIMARIES_Y_LOWER_LIMIT -5000000 #define MEMSET_16BYTES(pu4_start,value) \ { \ memset(pu4_start,value,16); \ } #define MEMCPY_16BYTES(dst,src) \ { \ memcpy(dst,src,16); \ } #endif /*_IH264D_DEFS_H_*/ ================================================ FILE: dependencies/ih264d/decoder/ih264d_dpb_manager.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_DPB_MANAGER_H_ #define _IH264D_DPB_MANAGER_H_ /*! *************************************************************************** * \file ih264d_dpb_manager.h * * \brief * Decoded Picture Buffer Manager Include File * * Detailed_description * * \date * 19-12-2002 * * \author Sriram Sethuraman *************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_bitstrm.h" #include "ih264d_defs.h" #define END_OF_MMCO 0 #define MARK_ST_PICNUM_AS_NONREF 1 #define MARK_LT_INDEX_AS_NONREF 2 #define MARK_ST_PICNUM_AS_LT_INDEX 3 #define SET_MAX_LT_INDEX 4 #define RESET_REF_PICTURES 5 #define SET_LT_INDEX 6 #define RESET_NONREF_PICTURES 7 #define RESET_ALL_PICTURES 8 #define NO_LONG_TERM_INDICIES 255 struct field_t { /* picNum of tbe reference field */ WORD32 i4_pic_num; /* assigned when used for long term reference */ /* else MAX_REF_BUFS+1 */ UWORD8 u1_long_term_frame_idx; /* 0 : unused for reference */ /* 1 : used for short term reference */ /* 2 : used for long term reference */ UWORD8 u1_reference_info; }; struct dpb_info_t { struct pic_buffer_t *ps_pic_buf; /** Pointer to picture buffer structure */ WORD32 i4_frame_num; /** frame number of picture - unique for each ref*/ struct dpb_info_t *ps_prev_short;/** Link to the DPB with previous picNum */ struct dpb_info_t *ps_prev_long; /** Link to the DPB with previous long term frame*/ struct field_t s_top_field; /** Contains information of the top_field reference info, pic num and longt term frame idx */ struct field_t s_bot_field; /** Contains information of the bot_field reference info, pic num and longt term frame idx */ UWORD8 u1_buf_id; /** bufID from bufAPI */ UWORD8 u1_used_as_ref; /** whether buffer is used as ref for frame or complementary reference field pair */ UWORD8 u1_lt_idx; /** If buf is assigned long-term index; else MAX_REF_BUFS+1 */ }; typedef struct { struct pic_buffer_t *ps_def_dpb[MAX_REF_BUFS];/** DPB in default index order */ struct pic_buffer_t *ps_mod_dpb[2][2 * MAX_REF_BUFS];/** DPB in reordered index order, 0-fwd,1-bwd */ struct pic_buffer_t *ps_init_dpb[2][2 * MAX_REF_BUFS];/** DPB in reordered index order, 0-fwd,1-bwd */ struct dpb_info_t *ps_dpb_st_head; /** Pointer to the most recent picNum */ struct dpb_info_t *ps_dpb_ht_head; /** Pointer to the smallest LT index */ struct dpb_info_t as_dpb_info[MAX_REF_BUFS]; /** Physical storage for dpbInfo for ref bufs */ UWORD8 u1_num_st_ref_bufs; /** Number of short term ref. buffers */ UWORD8 u1_num_lt_ref_bufs; /** Number of long term ref. buffer */ UWORD8 u1_max_lt_frame_idx; /** Maximum long term frame index */ UWORD8 u1_num_gaps; /** Total number of outstanding gaps */ void * pv_codec_handle; /* For Error Handling */ WORD32 i4_max_frm_num; /** Max frame number */ WORD32 ai4_gaps_start_frm_num[MAX_FRAMES];/** start frame number for a gap seqn */ WORD32 ai4_gaps_end_frm_num[MAX_FRAMES]; /** start frame number for a gap seqn */ WORD8 ai1_gaps_per_seq[MAX_FRAMES]; /** number of gaps with each gap seqn */ WORD32 ai4_poc_buf_id_map[MAX_FRAMES][3]; WORD8 i1_poc_buf_id_entries; WORD8 i1_gaps_deleted; UWORD16 u2_pic_wd; UWORD16 u2_pic_ht; UWORD8 u1_mmco_error_in_seq; }dpb_manager_t; /** Structure store the MMC Commands */ struct MMCParams { UWORD32 u4_mmco; /** memory managemet control operation */ UWORD32 u4_diff_pic_num; /** diff Of Pic Nums Minus1 */ UWORD32 u4_lt_idx; /** Long Term Pic Idx */ UWORD32 u4_max_lt_idx_plus1; /** MaxLongTermPicIdxPlus1 */ }; typedef struct { UWORD8 u1_dpb_commands_read; /** Flag to indicate that DBP commands are read */ UWORD8 u1_buf_mode; /** decoder Pic bugffering mode*/ UWORD8 u1_num_of_commands; /** Number of MMC commands */ /* These variables are ised in case of IDR pictures only */ UWORD8 u1_idr_pic; /** = 1 ,IDR pic */ UWORD8 u1_no_output_of_prior_pics_flag; UWORD8 u1_long_term_reference_flag; struct MMCParams as_mmc_params[MAX_REF_BUFS]; /* < Buffer to store MMC commands */ UWORD8 u1_dpb_commands_read_slc; }dpb_commands_t; void ih264d_init_ref_bufs(dpb_manager_t *ps_dpb_mgr); WORD32 ih264d_insert_st_node(dpb_manager_t *ps_dpb_mgr, struct pic_buffer_t *ps_pic_buf, UWORD8 u1_buf_id, UWORD32 u2_cur_pic_num); WORD32 ih264d_update_default_index_list(dpb_manager_t *ps_dpb_mgr); WORD32 ih264d_do_mmco_buffer(dpb_commands_t *ps_dpb_cmds, dpb_manager_t *ps_dpb_mgr, UWORD8 u1_numRef_frames_for_seq, UWORD32 u4_cur_pic_num, UWORD32 u2_u4_max_pic_num_minus1, UWORD8 u1_nal_unit_type, struct pic_buffer_t *ps_pic_buf, UWORD8 u1_buf_id, UWORD8 u1_fld_pic_flag, UWORD8 u1_curr_pic_in_err); void ih264d_release_pics_in_dpb(void *pv_dec, UWORD8 u1_disp_bufs); void ih264d_reset_ref_bufs(dpb_manager_t *ps_dpb_mgr); WORD32 ih264d_delete_st_node_or_make_lt(dpb_manager_t *ps_dpb_mgr, WORD32 u4_pic_num, UWORD32 u4_lt_idx, UWORD8 u1_fld_pic_flag); WORD32 ih264d_delete_gap_frm_mmco(dpb_manager_t *ps_dpb_mgr, WORD32 i4_frame_num, UWORD8 *pu1_del_node); WORD32 ih264d_delete_gap_frm_sliding(dpb_manager_t *ps_dpb_mgr, WORD32 i4_frame_num, UWORD8 *pu1_del_node); WORD32 ih264d_do_mmco_for_gaps(dpb_manager_t *ps_dpb_mgr, UWORD8 u1_num_ref_frames); WORD32 ih264d_insert_pic_in_display_list(dpb_manager_t *ps_dpb_mgr, UWORD8 u1_buf_id, WORD32 i4_display_poc, UWORD32 u4_frame_num); void ih264d_delete_nonref_nondisplay_pics(dpb_manager_t *ps_dpb_mgr); #endif /* _IH264D_DPB_MANAGER_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_dpb_mgr.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifdef __ANDROID__ #include <log/log.h> #endif #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "iv.h" #include "ih264d_dpb_manager.h" #include "ih264d_bitstrm.h" #include "ih264d_parse_cavlc.h" #include "ih264d_defs.h" #include "ih264d_structs.h" #include "ih264d_process_bslice.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_error_handler.h" #include "string.h" #include "ih264d_defs.h" #include "ih264_error.h" #include "ih264_buf_mgr.h" #include "assert.h" /*! *************************************************************************** * \file ih264d_dpb_mgr.c * * \brief * Functions for managing the decoded picture buffer * * Detailed_description * * \date * 19-12-2002 * * \author Sriram Sethuraman *************************************************************************** */ /*! ************************************************************************** * \if Function name : ih264d_init_ref_bufs \endif * * \brief * Called at the start for initialization. * * \return * none ************************************************************************** */ void ih264d_init_ref_bufs(dpb_manager_t *ps_dpb_mgr) { UWORD32 i; struct dpb_info_t *ps_dpb_info = ps_dpb_mgr->as_dpb_info; for(i = 0; i < MAX_REF_BUFS; i++) { ps_dpb_info[i].u1_used_as_ref = UNUSED_FOR_REF; ps_dpb_info[i].u1_lt_idx = MAX_REF_BUFS + 1; ps_dpb_info[i].ps_prev_short = NULL; ps_dpb_info[i].ps_prev_long = NULL; ps_dpb_info[i].ps_pic_buf = NULL; ps_dpb_info[i].s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_dpb_info[i].s_bot_field.u1_reference_info = UNUSED_FOR_REF; ps_dpb_info[i].s_top_field.u1_long_term_frame_idx = MAX_REF_BUFS + 1; ps_dpb_info[i].s_bot_field.u1_long_term_frame_idx = MAX_REF_BUFS + 1; } ps_dpb_mgr->u1_num_st_ref_bufs = ps_dpb_mgr->u1_num_lt_ref_bufs = 0; ps_dpb_mgr->ps_dpb_st_head = NULL; ps_dpb_mgr->ps_dpb_ht_head = NULL; ps_dpb_mgr->i1_gaps_deleted = 0; ps_dpb_mgr->i1_poc_buf_id_entries = 0; ps_dpb_mgr->u1_mmco_error_in_seq = 0; ps_dpb_mgr->u1_num_gaps = 0; for(i = 0; i < MAX_FRAMES; i++) { ps_dpb_mgr->ai4_gaps_start_frm_num[i] = INVALID_FRAME_NUM; ps_dpb_mgr->ai4_gaps_end_frm_num[i] = 0; ps_dpb_mgr->ai1_gaps_per_seq[i] = 0; ps_dpb_mgr->ai4_poc_buf_id_map[i][0] = -1; ps_dpb_mgr->ai4_poc_buf_id_map[i][1] = 0x7fffffff; ps_dpb_mgr->ai4_poc_buf_id_map[i][2] = 0; } } void ih264d_free_ref_pic_mv_bufs(void* pv_dec, UWORD8 pic_buf_id) { dec_struct_t *ps_dec = (dec_struct_t *)pv_dec; if((pic_buf_id == ps_dec->u1_pic_buf_id) && ps_dec->ps_cur_slice->u1_field_pic_flag && (ps_dec->u1_top_bottom_decoded == 0)) { return; } ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, pic_buf_id, BUF_MGR_REF); ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, ps_dec->au1_pic_buf_id_mv_buf_id_map[pic_buf_id], BUF_MGR_REF); } /*! ************************************************************************** * \if Function name : ih264d_delete_lt_node \endif * * \brief * Delete a buffer with a long term index from the LT linked list * * \return * none ************************************************************************** */ WORD32 ih264d_delete_lt_node(dpb_manager_t *ps_dpb_mgr, UWORD32 u4_lt_idx, UWORD8 u1_fld_pic_flag, struct dpb_info_t *ps_lt_node_to_insert, WORD32 *pi4_status) { *pi4_status = 0; if(ps_dpb_mgr->u1_num_lt_ref_bufs > 0) { WORD32 i; struct dpb_info_t *ps_next_dpb; /* ps_unmark_node points to the node to be removed */ /* from long term list. */ struct dpb_info_t *ps_unmark_node; //Find the node with matching LTIndex ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; if(ps_next_dpb->u1_lt_idx == u4_lt_idx) { ps_unmark_node = ps_next_dpb; } else { for(i = 1; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) { if(ps_next_dpb->ps_prev_long->u1_lt_idx == u4_lt_idx) break; ps_next_dpb = ps_next_dpb->ps_prev_long; } if(i == ps_dpb_mgr->u1_num_lt_ref_bufs) *pi4_status = 1; else ps_unmark_node = ps_next_dpb->ps_prev_long; } if(*pi4_status == 0) { if(u1_fld_pic_flag) { if(ps_lt_node_to_insert != ps_unmark_node) { UWORD8 u1_deleted = 0; /* for the ps_unmark_node mark the corresponding field */ /* field as unused for reference */ if(ps_unmark_node->s_top_field.u1_long_term_frame_idx == u4_lt_idx) { ps_unmark_node->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_unmark_node->s_top_field.u1_long_term_frame_idx = MAX_REF_BUFS + 1; u1_deleted = 1; } if(ps_unmark_node->s_bot_field.u1_long_term_frame_idx == u4_lt_idx) { ps_unmark_node->s_bot_field.u1_reference_info = UNUSED_FOR_REF; ps_unmark_node->s_bot_field.u1_long_term_frame_idx = MAX_REF_BUFS + 1; u1_deleted = 1; } if(!u1_deleted) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } } ps_unmark_node->u1_used_as_ref = ps_unmark_node->s_top_field.u1_reference_info | ps_unmark_node->s_bot_field.u1_reference_info; } else ps_unmark_node->u1_used_as_ref = UNUSED_FOR_REF; if(UNUSED_FOR_REF == ps_unmark_node->u1_used_as_ref) { if(ps_unmark_node == ps_dpb_mgr->ps_dpb_ht_head) ps_dpb_mgr->ps_dpb_ht_head = ps_next_dpb->ps_prev_long; ps_unmark_node->u1_lt_idx = MAX_REF_BUFS + 1; ps_unmark_node->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_unmark_node->s_bot_field.u1_reference_info = UNUSED_FOR_REF; // Release the physical buffer ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_unmark_node->u1_buf_id); ps_next_dpb->ps_prev_long = ps_unmark_node->ps_prev_long; //update link ps_unmark_node->ps_prev_long = NULL; ps_dpb_mgr->u1_num_lt_ref_bufs--; //decrement LT buf count } } } return OK; } /*! ************************************************************************** * \if Function name : ih264d_insert_lt_node \endif * * \brief * Insert a buffer into the LT linked list at a given LT index * * \return * none ************************************************************************** */ WORD32 ih264d_insert_lt_node(dpb_manager_t *ps_dpb_mgr, struct dpb_info_t *ps_mov_node, UWORD32 u4_lt_idx, UWORD8 u1_fld_pic_flag) { UWORD8 u1_mark_top_field_long_term = 0; UWORD8 u1_mark_bot_field_long_term = 0; { if(u1_fld_pic_flag) { /* Assign corresponding field (top or bottom) long_term_frame_idx */ if((ps_mov_node->s_top_field.u1_reference_info == IS_LONG_TERM) && (ps_mov_node->s_bot_field.u1_reference_info == IS_LONG_TERM)) { if(ps_mov_node->u1_lt_idx == u4_lt_idx) u1_mark_bot_field_long_term = 1; else { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } } else if(ps_mov_node->s_top_field.u1_reference_info == IS_LONG_TERM) { u1_mark_top_field_long_term = 1; } if(!(u1_mark_top_field_long_term || u1_mark_bot_field_long_term)) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } } else { ps_mov_node->s_top_field.u1_reference_info = IS_LONG_TERM; ps_mov_node->s_bot_field.u1_reference_info = IS_LONG_TERM; ps_mov_node->s_top_field.u1_long_term_frame_idx = u4_lt_idx; ps_mov_node->s_bot_field.u1_long_term_frame_idx = u4_lt_idx; u1_mark_bot_field_long_term = 1; u1_mark_top_field_long_term = 1; } ps_mov_node->u1_lt_idx = u4_lt_idx; //Assign the LT index to the node ps_mov_node->ps_pic_buf->u1_long_term_frm_idx = u4_lt_idx; ps_mov_node->u1_used_as_ref = IS_LONG_TERM; /* Insert the new long term in the LT list with u4_lt_idx */ /* in ascending order. */ if(ps_dpb_mgr->u1_num_lt_ref_bufs > 0) { struct dpb_info_t *ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; if(u4_lt_idx < ps_next_dpb->u1_lt_idx) { //LTIndex to be inserted is the smallest LT index //Update head and point prev to the next higher index ps_mov_node->ps_prev_long = ps_next_dpb; ps_dpb_mgr->ps_dpb_ht_head = ps_mov_node; } else { WORD32 i; struct dpb_info_t *ps_nxtDPB = ps_next_dpb; ps_next_dpb = ps_next_dpb->ps_prev_long; for(i = 1; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) { if(ps_next_dpb->u1_lt_idx > u4_lt_idx) break; ps_nxtDPB = ps_next_dpb; ps_next_dpb = ps_next_dpb->ps_prev_long; } ps_nxtDPB->ps_prev_long = ps_mov_node; ps_mov_node->ps_prev_long = ps_next_dpb; } } else { ps_dpb_mgr->ps_dpb_ht_head = ps_mov_node; ps_mov_node->ps_prev_long = NULL; } /* Identify the picture buffer as a long term picture buffer */ ps_mov_node->ps_pic_buf->u1_is_short = 0; /* Increment LT buf count only if new LT node inserted */ /* If Increment during top_field is done, don't increment */ /* for bottom field, as both them are part of same pic. */ if(u1_mark_bot_field_long_term) ps_dpb_mgr->u1_num_lt_ref_bufs++; } return OK; } /*! ************************************************************************** * \if Function name : ih264d_insert_st_node \endif * * \brief * Adds a short term reference picture into the ST linked list * * \return * None * * \note * Called only for a new coded picture with nal_ref_idc!=0 ************************************************************************** */ WORD32 ih264d_insert_st_node(dpb_manager_t *ps_dpb_mgr, struct pic_buffer_t *ps_pic_buf, UWORD8 u1_buf_id, UWORD32 u4_cur_pic_num) { WORD32 i; struct dpb_info_t *ps_dpb_info = ps_dpb_mgr->as_dpb_info; UWORD8 u1_picture_type = ps_pic_buf->u1_picturetype; /* Find an unused dpb location */ for(i = 0; i < MAX_REF_BUFS; i++) { if((ps_dpb_info[i].ps_pic_buf == ps_pic_buf) && ps_dpb_info[i].u1_used_as_ref) { /*signal an error in the case of frame pic*/ if(ps_dpb_info[i].ps_pic_buf->u1_pic_type == FRM_PIC) { return ERROR_DBP_MANAGER_T; } else { /* Can occur only for field bottom pictures */ ps_dpb_info[i].s_bot_field.u1_reference_info = IS_SHORT_TERM; return OK; } } if((ps_dpb_info[i].u1_used_as_ref == UNUSED_FOR_REF) && (ps_dpb_info[i].s_top_field.u1_reference_info == UNUSED_FOR_REF) && (ps_dpb_info[i].s_bot_field.u1_reference_info == UNUSED_FOR_REF)) break; } if(i == MAX_REF_BUFS) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } /* Create dpb info */ ps_dpb_info[i].ps_pic_buf = ps_pic_buf; ps_dpb_info[i].ps_prev_short = ps_dpb_mgr->ps_dpb_st_head; ps_dpb_info[i].u1_buf_id = u1_buf_id; ps_dpb_info[i].u1_used_as_ref = TRUE; ps_dpb_info[i].u1_lt_idx = MAX_REF_BUFS + 1; ps_dpb_info[i].i4_frame_num = u4_cur_pic_num; ps_dpb_info[i].ps_pic_buf->i4_frame_num = u4_cur_pic_num; /* update the head node of linked list to point to the cur Pic */ ps_dpb_mgr->ps_dpb_st_head = ps_dpb_info + i; // Increment Short term bufCount ps_dpb_mgr->u1_num_st_ref_bufs++; /* Identify the picture as a short term picture buffer */ ps_pic_buf->u1_is_short = IS_SHORT_TERM; if((u1_picture_type & 0x03) == FRM_PIC) { ps_dpb_info[i].u1_used_as_ref = IS_SHORT_TERM; ps_dpb_info[i].s_top_field.u1_reference_info = IS_SHORT_TERM; ps_dpb_info[i].s_bot_field.u1_reference_info = IS_SHORT_TERM; } if((u1_picture_type & 0x03) == TOP_FLD) ps_dpb_info[i].s_top_field.u1_reference_info = IS_SHORT_TERM; if((u1_picture_type & 0x03) == BOT_FLD) ps_dpb_info[i].s_bot_field.u1_reference_info = IS_SHORT_TERM; return OK; } /*! ************************************************************************** * \if Function name : ih264d_delete_st_node_or_make_lt \endif * * \brief * Delete short term ref with a given picNum from the ST linked list or * make it an LT node * * \return * 0 - if successful; -1 - otherwise * * \note * Common parts to MMCO==1 and MMCO==3 have been combined here ************************************************************************** */ WORD32 ih264d_delete_st_node_or_make_lt(dpb_manager_t *ps_dpb_mgr, WORD32 i4_pic_num, UWORD32 u4_lt_idx, UWORD8 u1_fld_pic_flag) { WORD32 i; struct dpb_info_t *ps_next_dpb; WORD32 i4_frame_num = i4_pic_num; struct dpb_info_t *ps_unmark_node = NULL; UWORD8 u1_del_node = 0, u1_del_st = 0; UWORD8 u1_reference_type = UNUSED_FOR_REF; WORD32 ret; if(u1_fld_pic_flag) { i4_frame_num = i4_frame_num >> 1; if(u4_lt_idx == (MAX_REF_BUFS + 1)) u1_reference_type = UNUSED_FOR_REF; else u1_reference_type = IS_LONG_TERM; } //Find the node with matching picNum ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; if((WORD32)ps_next_dpb->i4_frame_num == i4_frame_num) { ps_unmark_node = ps_next_dpb; } else { for(i = 1; i < ps_dpb_mgr->u1_num_st_ref_bufs; i++) { if((WORD32)ps_next_dpb->ps_prev_short->i4_frame_num == i4_frame_num) break; ps_next_dpb = ps_next_dpb->ps_prev_short; } if(i == ps_dpb_mgr->u1_num_st_ref_bufs) { if(ps_dpb_mgr->u1_num_gaps) { ret = ih264d_delete_gap_frm_mmco(ps_dpb_mgr, i4_frame_num, &u1_del_st); if(ret != OK) return ret; } else { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } if(u1_del_st) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } else { return 0; } } else ps_unmark_node = ps_next_dpb->ps_prev_short; } if(u1_fld_pic_flag) { /* Mark the corresponding field ( top or bot) as */ /* UNUSED_FOR_REF or IS_LONG_TERM depending on */ /* u1_reference_type. */ if(ps_unmark_node->s_top_field.i4_pic_num == i4_pic_num) { ps_unmark_node->s_top_field.u1_reference_info = u1_reference_type; ps_unmark_node->s_top_field.u1_long_term_frame_idx = u4_lt_idx; { UWORD8 *pu1_src = ps_unmark_node->ps_pic_buf->pu1_col_zero_flag; WORD32 i4_size = ((ps_dpb_mgr->u2_pic_wd * ps_dpb_mgr->u2_pic_ht) >> 5); /* memset the colocated zero u4_flag buffer */ memset(pu1_src, 0, i4_size); } } else if(ps_unmark_node->s_bot_field.i4_pic_num == i4_pic_num) { ps_unmark_node->s_bot_field.u1_reference_info = u1_reference_type; ps_unmark_node->s_bot_field.u1_long_term_frame_idx = u4_lt_idx; { UWORD8 *pu1_src = ps_unmark_node->ps_pic_buf->pu1_col_zero_flag + ((ps_dpb_mgr->u2_pic_wd * ps_dpb_mgr->u2_pic_ht) >> 5); WORD32 i4_size = ((ps_dpb_mgr->u2_pic_wd * ps_dpb_mgr->u2_pic_ht) >> 5); /* memset the colocated zero u4_flag buffer */ memset(pu1_src, 0, i4_size); } } ps_unmark_node->u1_used_as_ref = ps_unmark_node->s_top_field.u1_reference_info | ps_unmark_node->s_bot_field.u1_reference_info; } else { ps_unmark_node->u1_used_as_ref = UNUSED_FOR_REF; ps_unmark_node->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_unmark_node->s_bot_field.u1_reference_info = UNUSED_FOR_REF; { UWORD8 *pu1_src = ps_unmark_node->ps_pic_buf->pu1_col_zero_flag; WORD32 i4_size = ((ps_dpb_mgr->u2_pic_wd * ps_dpb_mgr->u2_pic_ht) >> 4); /* memset the colocated zero u4_flag buffer */ memset(pu1_src, 0, i4_size); } } if(!(ps_unmark_node->u1_used_as_ref & IS_SHORT_TERM)) { if(ps_unmark_node == ps_dpb_mgr->ps_dpb_st_head) ps_dpb_mgr->ps_dpb_st_head = ps_next_dpb->ps_prev_short; else ps_next_dpb->ps_prev_short = ps_unmark_node->ps_prev_short; //update link ps_dpb_mgr->u1_num_st_ref_bufs--; //decrement ST buf count u1_del_node = 1; } if(u4_lt_idx == MAX_REF_BUFS + 1) { if(u1_del_node) { // Release the physical buffer ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_unmark_node->u1_buf_id); ps_unmark_node->ps_prev_short = NULL; } } else { WORD32 i4_status; //If another node has the same LT index, delete that node ret = ih264d_delete_lt_node(ps_dpb_mgr, u4_lt_idx, u1_fld_pic_flag, ps_unmark_node, &i4_status); if(ret != OK) return ret; // Now insert the short term node as a long term node ret = ih264d_insert_lt_node(ps_dpb_mgr, ps_unmark_node, u4_lt_idx, u1_fld_pic_flag); if(ret != OK) return ret; } return OK; } /*! ************************************************************************** * \if Function name : ih264d_reset_ref_bufs \endif * * \brief * Called if MMCO==5/7 or on the first slice of an IDR picture * * \return * none ************************************************************************** */ void ih264d_reset_ref_bufs(dpb_manager_t *ps_dpb_mgr) { WORD32 i; struct dpb_info_t *ps_dpb_info = ps_dpb_mgr->as_dpb_info; for(i = 0; i < MAX_REF_BUFS; i++) { if(ps_dpb_info[i].u1_used_as_ref) { ps_dpb_info[i].u1_used_as_ref = UNUSED_FOR_REF; ps_dpb_info[i].u1_lt_idx = MAX_REF_BUFS + 1; ps_dpb_info[i].ps_prev_short = NULL; ps_dpb_info[i].ps_prev_long = NULL; ps_dpb_info[i].ps_pic_buf = NULL; ps_dpb_info[i].s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_dpb_info[i].s_bot_field.u1_reference_info = UNUSED_FOR_REF; ps_dpb_info[i].s_top_field.u1_long_term_frame_idx = MAX_REF_BUFS + 1; ps_dpb_info[i].s_bot_field.u1_long_term_frame_idx = MAX_REF_BUFS + 1; //Release physical buffer ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_dpb_info[i].u1_buf_id); } } ps_dpb_mgr->u1_num_st_ref_bufs = ps_dpb_mgr->u1_num_lt_ref_bufs = 0; ps_dpb_mgr->ps_dpb_st_head = NULL; ps_dpb_mgr->ps_dpb_ht_head = NULL; ps_dpb_mgr->u1_mmco_error_in_seq = 0; /* release all gaps */ ps_dpb_mgr->u1_num_gaps = 0; for(i = 0; i < MAX_FRAMES; i++) { ps_dpb_mgr->ai4_gaps_start_frm_num[i] = INVALID_FRAME_NUM; ps_dpb_mgr->ai4_gaps_end_frm_num[i] = 0; ps_dpb_mgr->ai1_gaps_per_seq[i] = 0; } } /*! ************************************************************************** * \if Function name : Name \endif * * \brief * create the default index list after an MMCO * * \return * 0 - if no_error; -1 - error * ************************************************************************** */ WORD32 ih264d_update_default_index_list(dpb_manager_t *ps_dpb_mgr) { WORD32 i; struct dpb_info_t *ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; for(i = 0; i < ps_dpb_mgr->u1_num_st_ref_bufs; i++) { ps_dpb_mgr->ps_def_dpb[i] = ps_next_dpb->ps_pic_buf; ps_next_dpb = ps_next_dpb->ps_prev_short; } ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; for(;i< ps_dpb_mgr->u1_num_st_ref_bufs + ps_dpb_mgr->u1_num_lt_ref_bufs; i++) { ps_dpb_mgr->ps_def_dpb[i] = ps_next_dpb->ps_pic_buf; ps_next_dpb = ps_next_dpb->ps_prev_long; } return 0; } /*! ************************************************************************** * \if Function name : ref_idx_reordering \endif * * \brief * Parse the bitstream and reorder indices for the current slice * * \return * 0 - if no_error; -1 - error * * \note * Called only if ref_idx_reordering_flag_l0 is decoded as 1 * Remove error checking for unmatching picNum or LTIndex later (if not needed) * \para * This section implements 7.3.3.1 and 8.2.6.4 * Uses the default index list as the starting point and * remaps the picNums sent to the next higher index in the * modified list. The unmodified ones are copied from the * default to modified list retaining their order in the default list. * ************************************************************************** */ WORD32 ih264d_ref_idx_reordering(dec_struct_t *ps_dec, UWORD8 uc_lx) { dpb_manager_t *ps_dpb_mgr = ps_dec->ps_dpb_mgr; UWORD16 u4_cur_pic_num = ps_dec->ps_cur_slice->u2_frame_num; /*< Maximum Picture Number Minus 1 */ UWORD16 ui_max_frame_num = ps_dec->ps_cur_sps->u2_u4_max_pic_num_minus1 + 1; WORD32 i, count = 0; UWORD32 ui_remapIdc, ui_nextUev; WORD16 u2_pred_frame_num = u4_cur_pic_num; WORD32 i_temp; UWORD16 u2_def_mod_flag = 0; /* Flag to keep track of which indices have been remapped */ UWORD8 modCount = 0; UWORD32 *pu4_bitstrm_buf = ps_dec->ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_dec->ps_bitstrm->u4_ofst; dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; UWORD8 u1_field_pic_flag = ps_cur_slice->u1_field_pic_flag; if(u1_field_pic_flag) { u4_cur_pic_num = u4_cur_pic_num * 2 + 1; ui_max_frame_num = ui_max_frame_num * 2; } u2_pred_frame_num = u4_cur_pic_num; ui_remapIdc = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); while((ui_remapIdc != 3) && (count < ps_cur_slice->u1_num_ref_idx_lx_active[uc_lx])) { ui_nextUev = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(ui_remapIdc != 2) { if(ui_nextUev > ui_max_frame_num) return ERROR_DBP_MANAGER_T; ui_nextUev = ui_nextUev + 1; if(ui_remapIdc == 0) { // diffPicNum is -ve i_temp = (WORD32)u2_pred_frame_num - (WORD32)ui_nextUev; if(i_temp < 0) i_temp += ui_max_frame_num; } else { // diffPicNum is +ve i_temp = (WORD32)u2_pred_frame_num + (WORD32)ui_nextUev; if(i_temp >= ui_max_frame_num) i_temp -= ui_max_frame_num; } /* Find the dpb with the matching picNum (picNum==frameNum for framePic) */ if(i_temp > u4_cur_pic_num) i_temp = i_temp - ui_max_frame_num; for(i = 0; i < (ps_cur_slice->u1_initial_list_size[uc_lx]); i++) { if(ps_dpb_mgr->ps_init_dpb[uc_lx][i]->i4_pic_num == i_temp) break; } if(i == (ps_cur_slice->u1_initial_list_size[uc_lx])) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } u2_def_mod_flag |= (1 << i); ps_dpb_mgr->ps_mod_dpb[uc_lx][modCount++] = ps_dpb_mgr->ps_init_dpb[uc_lx][i]; u2_pred_frame_num = i_temp; //update predictor to be the picNum just obtained } else //2 { UWORD8 u1_lt_idx; if(ui_nextUev > (MAX_REF_BUFS + 1)) return ERROR_DBP_MANAGER_T; u1_lt_idx = (UWORD8)ui_nextUev; for(i = 0; i < (ps_cur_slice->u1_initial_list_size[uc_lx]); i++) { if(!ps_dpb_mgr->ps_init_dpb[uc_lx][i]->u1_is_short) { if(ps_dpb_mgr->ps_init_dpb[uc_lx][i]->u1_long_term_pic_num == u1_lt_idx) break; } } if(i == (ps_cur_slice->u1_initial_list_size[uc_lx])) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } u2_def_mod_flag |= (1 << i); ps_dpb_mgr->ps_mod_dpb[uc_lx][modCount++] = ps_dpb_mgr->ps_init_dpb[uc_lx][i]; } ui_remapIdc = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); /* Get the remapping_idc - 0/1/2/3 */ count++; } //Handle the ref indices that were not remapped for(i = 0; i < (ps_cur_slice->u1_num_ref_idx_lx_active[uc_lx]); i++) { if(!(u2_def_mod_flag & (1 << i))) ps_dpb_mgr->ps_mod_dpb[uc_lx][modCount++] = ps_dpb_mgr->ps_init_dpb[uc_lx][i]; } return OK; } /*! ************************************************************************** * \if Function name : ih264d_read_mmco_commands \endif * * \brief * Parses MMCO commands and stores them in a structure for later use. * * \return * 0 - No error; -1 - Error * * \note * This function stores MMCO commands in structure only for the first time. * In case of MMCO commands being issued for same Picture Number, they are * just parsed and not stored them in the structure. * ************************************************************************** */ WORD32 ih264d_read_mmco_commands(struct _DecStruct * ps_dec) { dec_pic_params_t *ps_pps = ps_dec->ps_cur_pps; dec_seq_params_t *ps_sps = ps_pps->ps_sps; dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm; dpb_commands_t *ps_dpb_cmds = &(ps_dec->s_dpb_cmds_scratch); dec_slice_params_t * ps_slice = ps_dec->ps_cur_slice; WORD32 j; UWORD8 u1_buf_mode; struct MMCParams *ps_mmc_params; UWORD32 *pu4_bitstrm_buf = ps_dec->ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD32 u4_bit_ofst = ps_dec->ps_bitstrm->u4_ofst; ps_slice->u1_mmco_equalto5 = 0; { if(ps_dec->u1_nal_unit_type == IDR_SLICE_NAL) { ps_slice->u1_no_output_of_prior_pics_flag = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("SH: no_output_of_prior_pics_flag", ps_slice->u1_no_output_of_prior_pics_flag); ps_slice->u1_long_term_reference_flag = ih264d_get_bit_h264( ps_bitstrm); COPYTHECONTEXT("SH: long_term_reference_flag", ps_slice->u1_long_term_reference_flag); ps_dpb_cmds->u1_idr_pic = 1; ps_dpb_cmds->u1_no_output_of_prior_pics_flag = ps_slice->u1_no_output_of_prior_pics_flag; ps_dpb_cmds->u1_long_term_reference_flag = ps_slice->u1_long_term_reference_flag; } else { u1_buf_mode = ih264d_get_bit_h264(ps_bitstrm); //0 - sliding window; 1 - arbitrary COPYTHECONTEXT("SH: adaptive_ref_pic_buffering_flag", u1_buf_mode); ps_dpb_cmds->u1_buf_mode = u1_buf_mode; j = 0; if(u1_buf_mode == 1) { UWORD32 u4_mmco; UWORD32 u4_diff_pic_num; UWORD32 u4_lt_idx, u4_max_lt_idx_plus1; u4_mmco = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); while(u4_mmco != END_OF_MMCO) { if (j >= MAX_REF_BUFS) { #ifdef __ANDROID__ ALOGE("b/25818142"); android_errorWriteLog(0x534e4554, "25818142"); #endif ps_dpb_cmds->u1_num_of_commands = 0; return -1; } ps_mmc_params = &ps_dpb_cmds->as_mmc_params[j]; ps_mmc_params->u4_mmco = u4_mmco; switch(u4_mmco) { case MARK_ST_PICNUM_AS_NONREF: u4_diff_pic_num = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); //Get absDiffPicnumMinus1 ps_mmc_params->u4_diff_pic_num = u4_diff_pic_num; break; case MARK_LT_INDEX_AS_NONREF: u4_lt_idx = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_mmc_params->u4_lt_idx = u4_lt_idx; break; case MARK_ST_PICNUM_AS_LT_INDEX: u4_diff_pic_num = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_mmc_params->u4_diff_pic_num = u4_diff_pic_num; u4_lt_idx = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_mmc_params->u4_lt_idx = u4_lt_idx; break; case SET_MAX_LT_INDEX: { u4_max_lt_idx_plus1 = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if (u4_max_lt_idx_plus1 > ps_sps->u1_num_ref_frames) { /* Invalid max LT ref index */ return -1; } ps_mmc_params->u4_max_lt_idx_plus1 = u4_max_lt_idx_plus1; break; } case RESET_REF_PICTURES: { ps_slice->u1_mmco_equalto5 = 1; break; } case SET_LT_INDEX: u4_lt_idx = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_mmc_params->u4_lt_idx = u4_lt_idx; break; default: break; } u4_mmco = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); j++; } ps_dpb_cmds->u1_num_of_commands = j; } } ps_dpb_cmds->u1_dpb_commands_read = 1; ps_dpb_cmds->u1_dpb_commands_read_slc = 1; } u4_bit_ofst = ps_dec->ps_bitstrm->u4_ofst - u4_bit_ofst; return u4_bit_ofst; } /*! ************************************************************************** * \if Function name : ih264d_do_mmco_buffer \endif * * \brief * Perform decoded picture buffer memory management control operations * * \return * 0 - No error; -1 - Error * * \note * Bitstream is also parsed here to get the MMCOs * ************************************************************************** */ WORD32 ih264d_do_mmco_buffer(dpb_commands_t *ps_dpb_cmds, dpb_manager_t *ps_dpb_mgr, UWORD8 u1_numRef_frames_for_seq, /*!< num_ref_frames from active SeqParSet*/ UWORD32 u4_cur_pic_num, UWORD32 u2_u4_max_pic_num_minus1, UWORD8 u1_nal_unit_type, struct pic_buffer_t *ps_pic_buf, UWORD8 u1_buf_id, UWORD8 u1_fld_pic_flag, UWORD8 u1_curr_pic_in_err) { WORD32 i; UWORD8 u1_buf_mode, u1_marked_lt; struct dpb_info_t *ps_next_dpb; UWORD8 u1_num_gaps; UWORD8 u1_del_node = 1; UWORD8 u1_insert_st_pic = 1; WORD32 ret; UNUSED(u1_nal_unit_type); UNUSED(u2_u4_max_pic_num_minus1); u1_buf_mode = ps_dpb_cmds->u1_buf_mode; //0 - sliding window; 1 - Adaptive u1_marked_lt = 0; u1_num_gaps = ps_dpb_mgr->u1_num_gaps; if(!u1_buf_mode) { //Sliding window - implements 8.2.5.3 if((ps_dpb_mgr->u1_num_st_ref_bufs + ps_dpb_mgr->u1_num_lt_ref_bufs + u1_num_gaps) == u1_numRef_frames_for_seq) { UWORD8 u1_new_node_flag = 1; if((0 == ps_dpb_mgr->u1_num_st_ref_bufs) && (0 == u1_num_gaps)) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } // Chase the links to reach the last but one picNum, if available ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; if(ps_dpb_mgr->u1_num_st_ref_bufs > 1) { if(ps_next_dpb->i4_frame_num == (WORD32)u4_cur_pic_num) { /* Incase of filed pictures top_field has been allocated */ /* picture buffer and complementary bottom field pair comes */ /* then the sliding window mechanism should not allocate a */ /* new node */ u1_new_node_flag = 0; } for(i = 1; i < (ps_dpb_mgr->u1_num_st_ref_bufs - 1); i++) { if(ps_next_dpb == NULL) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } if(ps_next_dpb->i4_frame_num == (WORD32)u4_cur_pic_num) { /* Incase of field pictures top_field has been allocated */ /* picture buffer and complementary bottom field pair comes */ /* then the sliding window mechanism should not allocate a */ /* new node */ u1_new_node_flag = 0; } ps_next_dpb = ps_next_dpb->ps_prev_short; } if(ps_next_dpb->ps_prev_short->ps_prev_short != NULL) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } if(u1_new_node_flag) { if(u1_num_gaps) { ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, ps_next_dpb->ps_prev_short->i4_frame_num, &u1_del_node); if(ret != OK) return ret; } if(u1_del_node) { ps_dpb_mgr->u1_num_st_ref_bufs--; ps_next_dpb->ps_prev_short->u1_used_as_ref = UNUSED_FOR_REF; ps_next_dpb->ps_prev_short->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_next_dpb->ps_prev_short->s_bot_field.u1_reference_info = UNUSED_FOR_REF; ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_next_dpb->ps_prev_short->u1_buf_id); ps_next_dpb->ps_prev_short->ps_pic_buf = NULL; ps_next_dpb->ps_prev_short = NULL; } } } else { if(ps_dpb_mgr->u1_num_st_ref_bufs) { ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, ps_next_dpb->i4_frame_num, &u1_del_node); if(ret != OK) return ret; if((ps_next_dpb->i4_frame_num != (WORD32)u4_cur_pic_num) && u1_del_node) { ps_dpb_mgr->u1_num_st_ref_bufs--; ps_next_dpb->u1_used_as_ref = FALSE; ps_next_dpb->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_next_dpb->s_bot_field.u1_reference_info = UNUSED_FOR_REF; ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_next_dpb->u1_buf_id); ps_next_dpb->ps_pic_buf = NULL; ps_next_dpb->ps_prev_short = NULL; ps_dpb_mgr->ps_dpb_st_head = NULL; ps_next_dpb = NULL; } else if(ps_next_dpb->i4_frame_num == (WORD32)u4_cur_pic_num) { if(u1_curr_pic_in_err) { u1_insert_st_pic = 0; } else if(ps_dpb_mgr->u1_num_st_ref_bufs > 0) { ps_dpb_mgr->u1_num_st_ref_bufs--; ps_next_dpb->u1_used_as_ref = FALSE; ps_next_dpb->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_next_dpb->s_bot_field.u1_reference_info = UNUSED_FOR_REF; ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_next_dpb->u1_buf_id); ps_next_dpb->ps_pic_buf = NULL; ps_next_dpb = NULL; } } } else { ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, INVALID_FRAME_NUM, &u1_del_node); if(ret != OK) return ret; if(u1_del_node) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } } } } } else { //Adaptive memory control - implements 8.2.5.4 UWORD32 u4_mmco; UWORD32 u4_diff_pic_num; WORD32 i4_pic_num; UWORD32 u4_lt_idx; WORD32 j; struct MMCParams *ps_mmc_params; for(j = 0; j < ps_dpb_cmds->u1_num_of_commands; j++) { ps_mmc_params = &ps_dpb_cmds->as_mmc_params[j]; u4_mmco = ps_mmc_params->u4_mmco; //Get MMCO switch(u4_mmco) { case MARK_ST_PICNUM_AS_NONREF: { { UWORD32 i4_cur_pic_num = u4_cur_pic_num; WORD64 i8_pic_num; u4_diff_pic_num = ps_mmc_params->u4_diff_pic_num; //Get absDiffPicnumMinus1 if(u1_fld_pic_flag) i4_cur_pic_num = i4_cur_pic_num * 2 + 1; i8_pic_num = ((WORD64)i4_cur_pic_num - ((WORD64)u4_diff_pic_num + 1)); if(IS_OUT_OF_RANGE_S32(i8_pic_num)) { return ERROR_DBP_MANAGER_T; } i4_pic_num = i8_pic_num; } if(ps_dpb_mgr->u1_num_st_ref_bufs > 0) { ret = ih264d_delete_st_node_or_make_lt(ps_dpb_mgr, i4_pic_num, MAX_REF_BUFS + 1, u1_fld_pic_flag); if(ret != OK) return ret; } else { UWORD8 u1_dummy; ret = ih264d_delete_gap_frm_mmco(ps_dpb_mgr, i4_pic_num, &u1_dummy); if(ret != OK) return ret; } break; } case MARK_LT_INDEX_AS_NONREF: { WORD32 i4_status; u4_lt_idx = ps_mmc_params->u4_lt_idx; //Get long term index ret = ih264d_delete_lt_node(ps_dpb_mgr, u4_lt_idx, u1_fld_pic_flag, 0, &i4_status); if(ret != OK) return ret; if(i4_status) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } break; } case MARK_ST_PICNUM_AS_LT_INDEX: { { UWORD32 i4_cur_pic_num = u4_cur_pic_num; WORD64 i8_pic_num; u4_diff_pic_num = ps_mmc_params->u4_diff_pic_num; //Get absDiffPicnumMinus1 if(u1_fld_pic_flag) i4_cur_pic_num = i4_cur_pic_num * 2 + 1; i8_pic_num = (WORD64)i4_cur_pic_num - ((WORD64)u4_diff_pic_num + 1); if(IS_OUT_OF_RANGE_S32(i8_pic_num)) { return ERROR_DBP_MANAGER_T; } i4_pic_num = i8_pic_num; } u4_lt_idx = ps_mmc_params->u4_lt_idx; //Get long term index if((ps_dpb_mgr->u1_max_lt_frame_idx == NO_LONG_TERM_INDICIES) || (u4_lt_idx > ps_dpb_mgr->u1_max_lt_frame_idx)) { return ERROR_DBP_MANAGER_T; } if(ps_dpb_mgr->u1_num_st_ref_bufs > 0) { ret = ih264d_delete_st_node_or_make_lt(ps_dpb_mgr, i4_pic_num, u4_lt_idx, u1_fld_pic_flag); if(ret != OK) return ret; } break; } case SET_MAX_LT_INDEX: { UWORD8 uc_numLT = ps_dpb_mgr->u1_num_lt_ref_bufs; u4_lt_idx = ps_mmc_params->u4_max_lt_idx_plus1; //Get Max_long_term_index_plus1 if(u4_lt_idx <= ps_dpb_mgr->u1_max_lt_frame_idx && uc_numLT > 0) { struct dpb_info_t *ps_nxtDPB; //Set all LT buffers with index >= u4_lt_idx to nonreference ps_nxtDPB = ps_dpb_mgr->ps_dpb_ht_head; ps_next_dpb = ps_nxtDPB->ps_prev_long; if(ps_nxtDPB->u1_lt_idx >= u4_lt_idx) { i = 0; ps_dpb_mgr->ps_dpb_ht_head = NULL; } else { for(i = 1; i < uc_numLT; i++) { if(ps_next_dpb->u1_lt_idx >= u4_lt_idx) break; ps_nxtDPB = ps_next_dpb; ps_next_dpb = ps_next_dpb->ps_prev_long; } ps_nxtDPB->ps_prev_long = NULL; //Terminate the link of the closest LTIndex that is <=Max } ps_dpb_mgr->u1_num_lt_ref_bufs = i; if(i == 0) ps_next_dpb = ps_nxtDPB; for(; i < uc_numLT; i++) { ps_nxtDPB = ps_next_dpb; ps_nxtDPB->u1_lt_idx = MAX_REF_BUFS + 1; ps_nxtDPB->u1_used_as_ref = UNUSED_FOR_REF; ps_nxtDPB->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_nxtDPB->s_bot_field.u1_reference_info = UNUSED_FOR_REF; ps_nxtDPB->ps_pic_buf = NULL; //Release buffer ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_nxtDPB->u1_buf_id); ps_next_dpb = ps_nxtDPB->ps_prev_long; ps_nxtDPB->ps_prev_long = NULL; } } if(u4_lt_idx == 0) { ps_dpb_mgr->u1_max_lt_frame_idx = NO_LONG_TERM_INDICIES; } else { ps_dpb_mgr->u1_max_lt_frame_idx = u4_lt_idx - 1; } break; } case SET_LT_INDEX: { u4_lt_idx = ps_mmc_params->u4_lt_idx; //Get long term index if((ps_dpb_mgr->u1_max_lt_frame_idx == NO_LONG_TERM_INDICIES) || (u4_lt_idx > ps_dpb_mgr->u1_max_lt_frame_idx)) { return ERROR_DBP_MANAGER_T; } ret = ih264d_insert_st_node(ps_dpb_mgr, ps_pic_buf, u1_buf_id, u4_cur_pic_num); if(ret != OK) return ret; if(ps_dpb_mgr->u1_num_st_ref_bufs > 0) { ret = ih264d_delete_st_node_or_make_lt(ps_dpb_mgr, u4_cur_pic_num, u4_lt_idx, u1_fld_pic_flag); if(ret != OK) return ret; } else { return ERROR_DBP_MANAGER_T; } u1_marked_lt = 1; break; } default: break; } if(u4_mmco == RESET_REF_PICTURES || u4_mmco == RESET_ALL_PICTURES) { ih264d_reset_ref_bufs(ps_dpb_mgr); u4_cur_pic_num = 0; } } } if(!u1_marked_lt && u1_insert_st_pic) { ret = ih264d_insert_st_node(ps_dpb_mgr, ps_pic_buf, u1_buf_id, u4_cur_pic_num); if(ret != OK) return ret; } return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_release_pics_in_dpb */ /* */ /* Description : This function deletes all pictures from DPB */ /* */ /* Inputs : h_pic_buf_api: pointer to picture buffer API */ /* u1_disp_bufs: number pictures ready for display */ /* */ /* Globals : None */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 06 2005 NS Draft */ /* */ /*****************************************************************************/ void ih264d_release_pics_in_dpb(void *pv_dec, UWORD8 u1_disp_bufs) { WORD8 i; dec_struct_t *ps_dec = (dec_struct_t *)pv_dec; for(i = 0; i < u1_disp_bufs; i++) { ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, i, BUF_MGR_REF); ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, ps_dec->au1_pic_buf_id_mv_buf_id_map[i], BUF_MGR_REF); } } /*****************************************************************************/ /* */ /* Function Name : ih264d_delete_gap_frm_sliding */ /* */ /* Description : This function deletes a picture from the list of gaps, */ /* if the frame number of gap frame is lesser than the one */ /* to be deleted by sliding window */ /* Inputs : ps_dpb_mgr: pointer to dpb manager */ /* i4_frame_num: frame number of picture that's going to */ /* be deleted by sliding window */ /* pu1_del_node: holds 0 if a gap is deleted else 1 */ /* Globals : None */ /* Processing : Function searches for frame number lesser than */ /* i4_frame_num in the gaps list */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 06 2005 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_delete_gap_frm_sliding(dpb_manager_t *ps_dpb_mgr, WORD32 i4_frame_num, UWORD8 *pu1_del_node) { WORD8 i1_gap_idx, i, j, j_min; WORD32 *pi4_gaps_start_frm_num, *pi4_gaps_end_frm_num, i4_gap_frame_num; WORD32 i4_start_frm_num, i4_end_frm_num; WORD32 i4_max_frm_num; WORD32 i4_frm_num, i4_gap_frm_num_min; /* find the least frame num from gaps and current DPB node */ /* Delete the least one */ *pu1_del_node = 1; if(0 == ps_dpb_mgr->u1_num_gaps) return OK; pi4_gaps_start_frm_num = ps_dpb_mgr->ai4_gaps_start_frm_num; pi4_gaps_end_frm_num = ps_dpb_mgr->ai4_gaps_end_frm_num; i4_gap_frame_num = INVALID_FRAME_NUM; i4_max_frm_num = ps_dpb_mgr->i4_max_frm_num; i1_gap_idx = -1; if(INVALID_FRAME_NUM != i4_frame_num) { i4_gap_frame_num = i4_frame_num; for(i = 0; i < MAX_FRAMES; i++) { i4_start_frm_num = pi4_gaps_start_frm_num[i]; if(INVALID_FRAME_NUM != i4_start_frm_num) { i4_end_frm_num = pi4_gaps_end_frm_num[i]; if(i4_end_frm_num < i4_max_frm_num) { if(i4_start_frm_num <= i4_gap_frame_num) { i4_gap_frame_num = i4_start_frm_num; i1_gap_idx = i; } } else { if(((i4_start_frm_num <= i4_gap_frame_num) && (i4_gap_frame_num <= i4_max_frm_num)) || ((i4_start_frm_num >= i4_gap_frame_num) && ((i4_gap_frame_num + i4_max_frm_num) >= i4_end_frm_num))) { i4_gap_frame_num = i4_start_frm_num; i1_gap_idx = i; } } } } } else { /* no valid short term buffers, delete one gap from the least start */ /* of gap sequence */ i4_gap_frame_num = pi4_gaps_start_frm_num[0]; i1_gap_idx = 0; for(i = 1; i < MAX_FRAMES; i++) { if(INVALID_FRAME_NUM != pi4_gaps_start_frm_num[i]) { if(pi4_gaps_start_frm_num[i] < i4_gap_frame_num) { i4_gap_frame_num = pi4_gaps_start_frm_num[i]; i1_gap_idx = i; } } } if(INVALID_FRAME_NUM == i4_gap_frame_num) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } } if(-1 != i1_gap_idx) { /* find least frame_num in the poc_map, which is in this range */ i4_start_frm_num = pi4_gaps_start_frm_num[i1_gap_idx]; if(i4_start_frm_num < 0) i4_start_frm_num += i4_max_frm_num; i4_end_frm_num = pi4_gaps_end_frm_num[i1_gap_idx]; if(i4_end_frm_num < 0) i4_end_frm_num += i4_max_frm_num; i4_gap_frm_num_min = 0xfffffff; j_min = MAX_FRAMES; for(j = 0; j < MAX_FRAMES; j++) { i4_frm_num = ps_dpb_mgr->ai4_poc_buf_id_map[j][2]; if((i4_start_frm_num <= i4_frm_num) && (i4_end_frm_num >= i4_frm_num)) { if(i4_frm_num < i4_gap_frm_num_min) { j_min = j; i4_gap_frm_num_min = i4_frm_num; } } } if(j_min != MAX_FRAMES) { ps_dpb_mgr->ai4_poc_buf_id_map[j_min][0] = -1; ps_dpb_mgr->ai4_poc_buf_id_map[j_min][1] = 0x7fffffff; ps_dpb_mgr->ai4_poc_buf_id_map[j_min][2] = GAP_FRAME_NUM; ps_dpb_mgr->i1_gaps_deleted++; ps_dpb_mgr->ai1_gaps_per_seq[i1_gap_idx]--; ps_dpb_mgr->u1_num_gaps--; *pu1_del_node = 0; if(0 == ps_dpb_mgr->ai1_gaps_per_seq[i1_gap_idx]) { ps_dpb_mgr->ai4_gaps_start_frm_num[i1_gap_idx] = INVALID_FRAME_NUM; ps_dpb_mgr->ai4_gaps_end_frm_num[i1_gap_idx] = 0; } } } return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_delete_gap_frm_mmco */ /* */ /* Description : This function deletes a picture from the list of gaps, */ /* if the frame number (specified by mmco commands) to be */ /* deleted is in the range by gap sequence. */ /* */ /* Inputs : ps_dpb_mgr: pointer to dpb manager */ /* i4_frame_num: frame number of picture that's going to */ /* be deleted by mmco */ /* pu1_del_node: holds 0 if a gap is deleted else 1 */ /* Globals : None */ /* Processing : Function searches for frame number lesser in the range */ /* specified by gap sequence */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 22 06 2005 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_delete_gap_frm_mmco(dpb_manager_t *ps_dpb_mgr, WORD32 i4_frame_num, UWORD8 *pu1_del_node) { WORD8 i, j; WORD32 *pi4_start, *pi4_end; WORD32 i4_start_frm_num, i4_end_frm_num, i4_max_frm_num; /* find the least frame num from gaps and current DPB node */ /* Delete the gaps */ *pu1_del_node = 1; pi4_start = ps_dpb_mgr->ai4_gaps_start_frm_num; pi4_end = ps_dpb_mgr->ai4_gaps_end_frm_num; i4_max_frm_num = ps_dpb_mgr->i4_max_frm_num; if(0 == ps_dpb_mgr->u1_num_gaps) return OK; if(i4_frame_num < 0) i4_frame_num += i4_max_frm_num; for(i = 0; i < MAX_FRAMES; i++) { i4_start_frm_num = pi4_start[i]; if(i4_start_frm_num < 0) i4_start_frm_num += i4_max_frm_num; if(INVALID_FRAME_NUM != i4_start_frm_num) { i4_end_frm_num = pi4_end[i]; if(i4_end_frm_num < 0) i4_end_frm_num += i4_max_frm_num; if((i4_frame_num >= i4_start_frm_num) && (i4_frame_num <= i4_end_frm_num)) { break; } else { if(((i4_frame_num + i4_max_frm_num) >= i4_start_frm_num) && ((i4_frame_num + i4_max_frm_num) <= i4_end_frm_num)) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } } } } /* find frame_num index, in the poc_map which needs to be deleted */ for(j = 0; j < MAX_FRAMES; j++) { if(i4_frame_num == ps_dpb_mgr->ai4_poc_buf_id_map[j][2]) break; } if(MAX_FRAMES != i) { if(j == MAX_FRAMES) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } ps_dpb_mgr->ai4_poc_buf_id_map[j][0] = -1; ps_dpb_mgr->ai4_poc_buf_id_map[j][1] = 0x7fffffff; ps_dpb_mgr->ai4_poc_buf_id_map[j][2] = GAP_FRAME_NUM; ps_dpb_mgr->i1_gaps_deleted++; ps_dpb_mgr->ai1_gaps_per_seq[i]--; ps_dpb_mgr->u1_num_gaps--; *pu1_del_node = 0; if(0 == ps_dpb_mgr->ai1_gaps_per_seq[i]) { ps_dpb_mgr->ai4_gaps_start_frm_num[i] = INVALID_FRAME_NUM; ps_dpb_mgr->ai4_gaps_end_frm_num[i] = 0; } } else { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } return OK; } /*! ************************************************************************** * \if Function name : ih264d_do_mmco_for_gaps \endif * * \brief * Perform decoded picture buffer memory management control operations * * \return * 0 - No error; -1 - Error * * \note * Bitstream is also parsed here to get the MMCOs * ************************************************************************** */ WORD32 ih264d_do_mmco_for_gaps(dpb_manager_t *ps_dpb_mgr, UWORD8 u1_num_ref_frames /*!< num_ref_frames from active SeqParSet*/ ) { struct dpb_info_t *ps_next_dpb; UWORD8 u1_num_gaps; UWORD8 u1_st_ref_bufs, u1_lt_ref_bufs, u1_del_node; WORD8 i; WORD32 i4_frame_gaps = 1; WORD32 ret; //Sliding window - implements 8.2.5.3, flush out buffers u1_st_ref_bufs = ps_dpb_mgr->u1_num_st_ref_bufs; u1_lt_ref_bufs = ps_dpb_mgr->u1_num_lt_ref_bufs; while(1) { u1_num_gaps = ps_dpb_mgr->u1_num_gaps; if((u1_st_ref_bufs + u1_lt_ref_bufs + u1_num_gaps + i4_frame_gaps) > u1_num_ref_frames) { if(0 == (u1_st_ref_bufs + u1_num_gaps)) { i4_frame_gaps = 0; ps_dpb_mgr->u1_num_gaps = (u1_num_ref_frames - u1_lt_ref_bufs); } else { u1_del_node = 1; ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; if(u1_st_ref_bufs > 1) { for(i = 1; i < (u1_st_ref_bufs - 1); i++) { if(ps_next_dpb == NULL) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; return i4_error_code; } ps_next_dpb = ps_next_dpb->ps_prev_short; } if(ps_next_dpb->ps_prev_short->ps_prev_short != NULL) { return ERROR_DBP_MANAGER_T; } if(u1_num_gaps) { ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, ps_next_dpb->ps_prev_short->i4_frame_num, &u1_del_node); if(ret != OK) return ret; } if(u1_del_node) { u1_st_ref_bufs--; ps_next_dpb->ps_prev_short->u1_used_as_ref = UNUSED_FOR_REF; ps_next_dpb->ps_prev_short->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_next_dpb->ps_prev_short->s_bot_field.u1_reference_info = UNUSED_FOR_REF; ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_next_dpb->ps_prev_short->u1_buf_id); ps_next_dpb->ps_prev_short->ps_pic_buf = NULL; ps_next_dpb->ps_prev_short = NULL; } } else { if(u1_st_ref_bufs) { if(u1_num_gaps) { ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, ps_next_dpb->i4_frame_num, &u1_del_node); if(ret != OK) return ret; } if(u1_del_node) { u1_st_ref_bufs--; ps_next_dpb->u1_used_as_ref = FALSE; ps_next_dpb->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_next_dpb->s_bot_field.u1_reference_info = UNUSED_FOR_REF; ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_next_dpb->u1_buf_id); ps_next_dpb->ps_pic_buf = NULL; ps_next_dpb = NULL; ps_dpb_mgr->ps_dpb_st_head = NULL; ps_dpb_mgr->u1_num_st_ref_bufs = u1_st_ref_bufs; } } else { ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, INVALID_FRAME_NUM, &u1_del_node); if(ret != OK) return ret; if(u1_del_node) { return ERROR_DBP_MANAGER_T; } } } } } else { ps_dpb_mgr->u1_num_gaps += i4_frame_gaps; break; } } ps_dpb_mgr->u1_num_st_ref_bufs = u1_st_ref_bufs; return OK; } /****************************************************************************/ /* */ /* Function Name : ih264d_free_node_from_dpb */ /* */ /* Description : */ /* */ /* Inputs : */ /* */ /* Globals : */ /* */ /* Processing : */ /* */ /* Outputs : */ /* */ /* Returns : */ /* */ /* Known Issues : */ /* */ /* Revision History */ /* */ /* DD MM YY Author Changes */ /* Sarat */ /****************************************************************************/ /**** Function Added for Error Resilience *****/ WORD32 ih264d_free_node_from_dpb(dpb_manager_t *ps_dpb_mgr, UWORD32 u4_cur_pic_num, UWORD8 u1_numRef_frames_for_seq) { WORD32 i; UWORD8 u1_num_gaps = ps_dpb_mgr->u1_num_gaps; struct dpb_info_t *ps_next_dpb; UWORD8 u1_del_node = 1; WORD32 ret; //Sliding window - implements 8.2.5.3 if((ps_dpb_mgr->u1_num_st_ref_bufs + ps_dpb_mgr->u1_num_lt_ref_bufs + u1_num_gaps) == u1_numRef_frames_for_seq) { UWORD8 u1_new_node_flag = 1; if((0 == ps_dpb_mgr->u1_num_st_ref_bufs) && (0 == u1_num_gaps)) { return ERROR_DBP_MANAGER_T; } // Chase the links to reach the last but one picNum, if available ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; if(ps_dpb_mgr->u1_num_st_ref_bufs > 1) { if(ps_next_dpb->i4_frame_num == (WORD32)u4_cur_pic_num) { /* Incase of filed pictures top_field has been allocated */ /* picture buffer and complementary bottom field pair comes */ /* then the sliding window mechanism should not allocate a */ /* new node */ u1_new_node_flag = 0; } for(i = 1; i < (ps_dpb_mgr->u1_num_st_ref_bufs - 1); i++) { if(ps_next_dpb == NULL) return ERROR_DBP_MANAGER_T; if(ps_next_dpb->i4_frame_num == (WORD32)u4_cur_pic_num) { /* Incase of field pictures top_field has been allocated */ /* picture buffer and complementary bottom field pair comes */ /* then the sliding window mechanism should not allocate a */ /* new node */ u1_new_node_flag = 0; } ps_next_dpb = ps_next_dpb->ps_prev_short; } if(ps_next_dpb->ps_prev_short->ps_prev_short != NULL) return ERROR_DBP_MANAGER_T; if(u1_new_node_flag) { if(u1_num_gaps) { ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, ps_next_dpb->ps_prev_short->i4_frame_num, &u1_del_node); if(ret != OK) return ret; } if(u1_del_node) { ps_dpb_mgr->u1_num_st_ref_bufs--; ps_next_dpb->ps_prev_short->u1_used_as_ref = UNUSED_FOR_REF; ps_next_dpb->ps_prev_short->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_next_dpb->ps_prev_short->s_bot_field.u1_reference_info = UNUSED_FOR_REF; ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_next_dpb->ps_prev_short->u1_buf_id); ps_next_dpb->ps_prev_short->ps_pic_buf = NULL; ps_next_dpb->ps_prev_short = NULL; } } } else { if(ps_dpb_mgr->u1_num_st_ref_bufs) { ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, ps_next_dpb->i4_frame_num, &u1_del_node); if(ret != OK) return ret; if((ps_next_dpb->i4_frame_num != (WORD32)u4_cur_pic_num) && u1_del_node) { ps_dpb_mgr->u1_num_st_ref_bufs--; ps_next_dpb->u1_used_as_ref = FALSE; ps_next_dpb->s_top_field.u1_reference_info = UNUSED_FOR_REF; ps_next_dpb->s_bot_field.u1_reference_info = UNUSED_FOR_REF; ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, ps_next_dpb->u1_buf_id); ps_next_dpb->ps_pic_buf = NULL; ps_next_dpb = NULL; } } else { ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, INVALID_FRAME_NUM, &u1_del_node); if(ret != OK) return ret; if(u1_del_node) return ERROR_DBP_MANAGER_T; } } } return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_delete_nonref_nondisplay_pics */ /* */ /* Description : */ /* */ /* */ /* Inputs : */ /* Globals : */ /* Processing : */ /* */ /* Outputs : */ /* Returns : */ /* */ /* Issues : */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 05 06 2007 Varun Draft */ /* */ /*****************************************************************************/ void ih264d_delete_nonref_nondisplay_pics(dpb_manager_t *ps_dpb_mgr) { WORD8 i; WORD32 (*i4_poc_buf_id_map)[3] = ps_dpb_mgr->ai4_poc_buf_id_map; /* remove all gaps marked as unused for ref */ for(i = 0; (i < MAX_FRAMES) && ps_dpb_mgr->i1_gaps_deleted; i++) { if(GAP_FRAME_NUM == i4_poc_buf_id_map[i][2]) { ps_dpb_mgr->i1_gaps_deleted--; ps_dpb_mgr->i1_poc_buf_id_entries--; i4_poc_buf_id_map[i][0] = -1; i4_poc_buf_id_map[i][1] = 0x7fffffff; i4_poc_buf_id_map[i][2] = 0; } } } /*****************************************************************************/ /* */ /* Function Name : ih264d_insert_pic_in_display_list */ /* */ /* Description : */ /* */ /* */ /* Inputs : */ /* Globals : */ /* Processing : */ /* */ /* Outputs : */ /* Returns : */ /* */ /* Issues : */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 05 06 2007 Varun Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_insert_pic_in_display_list(dpb_manager_t *ps_dpb_mgr, UWORD8 u1_buf_id, WORD32 i4_display_poc, UWORD32 u4_frame_num) { WORD8 i; WORD32 (*i4_poc_buf_id_map)[3] = ps_dpb_mgr->ai4_poc_buf_id_map; for(i = 0; i < MAX_FRAMES; i++) { /* Find an empty slot */ if(i4_poc_buf_id_map[i][0] == -1) { if(GAP_FRAME_NUM == i4_poc_buf_id_map[i][2]) ps_dpb_mgr->i1_gaps_deleted--; else ps_dpb_mgr->i1_poc_buf_id_entries++; i4_poc_buf_id_map[i][0] = u1_buf_id; i4_poc_buf_id_map[i][1] = i4_display_poc; i4_poc_buf_id_map[i][2] = u4_frame_num; break; } } if(MAX_FRAMES == i) { UWORD32 i4_error_code; i4_error_code = ERROR_GAPS_IN_FRM_NUM; return i4_error_code; } return OK; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_error_handler.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_ERROR_HANDLER_H_ #define _IH264D_ERROR_HANDLER_H_ /*! ************************************************************************* * \file ih264d_error_handler.h * * \brief * Contains declaration of ih264d_global_error_handler function * * \date * 21/11/2002 * * \author AI ************************************************************************* */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" typedef enum { ERROR_MEM_ALLOC_ISRAM_T = 0x50, ERROR_MEM_ALLOC_SDRAM_T = 0x51, ERROR_BUF_MGR = 0x52, ERROR_DBP_MANAGER_T = 0x53, ERROR_GAPS_IN_FRM_NUM = 0x54, ERROR_UNKNOWN_NAL = 0x55, ERROR_INV_MB_SLC_GRP_T = 0x56, ERROR_MULTIPLE_SLC_GRP_T = 0x57, ERROR_UNKNOWN_LEVEL = 0x58, ERROR_FEATURE_UNAVAIL = 0x59, ERROR_NOT_SUPP_RESOLUTION = 0x5A, ERROR_INVALID_PIC_PARAM = 0x5B, ERROR_INVALID_SEQ_PARAM = 0x5C, ERROR_EGC_EXCEED_32_1_T = 0x5D, ERROR_EGC_EXCEED_32_2_T = 0x5E, ERROR_INV_RANGE_TEV_T = 0x5F, ERROR_INV_SLC_TYPE_T = 0x60, ERROR_UNAVAIL_PICBUF_T = 0x61, ERROR_UNAVAIL_MVBUF_T = 0x62, ERROR_UNAVAIL_DISPBUF_T = 0x63, ERROR_INV_POC_TYPE_T = 0x64, ERROR_PIC1_NOT_FOUND_T = 0x65, ERROR_PIC0_NOT_FOUND_T = 0x66, ERROR_NUM_REF = 0x67, ERROR_REFIDX_ORDER_T = 0x68, ERROR_EOB_FLUSHBITS_T = 0x69, ERROR_EOB_GETBITS_T = 0x6A, ERROR_EOB_GETBIT_T = 0x6B, ERROR_EOB_BYPASS_T = 0x6C, ERROR_EOB_DECISION_T = 0x6D, ERROR_EOB_TERMINATE_T = 0x6E, ERROR_EOB_READCOEFF4X4CAB_T = 0x6F, ERROR_INV_RANGE_QP_T = 0x70, ERROR_END_OF_FRAME_EXPECTED_T = 0x71, ERROR_MB_TYPE = 0x72, ERROR_SUB_MB_TYPE = 0x73, ERROR_CBP = 0x74, ERROR_REF_IDX = 0x75, ERROR_NUM_MV = 0x76, ERROR_CHROMA_PRED_MODE = 0x77, ERROR_INTRAPRED = 0x78, ERROR_NEXT_MB_ADDRESS_T = 0x79, ERROR_MB_ADDRESS_T = 0x7A, ERROR_MB_GROUP_ASSGN_T = 0x7B, ERROR_CAVLC_NUM_COEFF_T = 0x7C, ERROR_CAVLC_SCAN_POS_T = 0x7D, ERROR_CABAC_RENORM_T = 0x7E, ERROR_CABAC_SIG_COEFF1_T = 0x7F, ERROR_CABAC_SIG_COEFF2_T = 0x80, ERROR_CABAC_ENCODE_COEFF_T = 0x81, ERROR_INV_SPS_PPS_T = 0x82, ERROR_INV_SLICE_HDR_T = 0x83, ERROR_PRED_WEIGHT_TABLE_T = 0x84, IH264D_VERS_BUF_INSUFFICIENT = 0x85, ERROR_ACTUAL_LEVEL_GREATER_THAN_INIT = 0x86, ERROR_CORRUPTED_SLICE = 0x87, ERROR_FRAME_LIMIT_OVER = 0x88, ERROR_ACTUAL_RESOLUTION_GREATER_THAN_INIT = 0x89, ERROR_PROFILE_NOT_SUPPORTED = 0x8A, ERROR_DISP_WIDTH_RESET_TO_PIC_WIDTH = 0x8B, ERROR_DISP_WIDTH_INVALID = 0x8C, ERROR_DANGLING_FIELD_IN_PIC = 0x8D, ERROR_DYNAMIC_RESOLUTION_NOT_SUPPORTED = 0x8E, ERROR_INIT_NOT_DONE = 0x8F, ERROR_LEVEL_UNSUPPORTED = 0x90, ERROR_START_CODE_NOT_FOUND = 0x91, ERROR_PIC_NUM_IS_REPEATED = 0x92, ERROR_IN_LAST_SLICE_OF_PIC = 0x93, ERROR_NEW_FRAME_EXPECTED = 0x94, ERROR_INCOMPLETE_FRAME = 0x95, ERROR_VUI_PARAMS_NOT_FOUND = 0x96, ERROR_INV_POC = 0x97, ERROR_SEI_MDCV_PARAMS_NOT_FOUND = 0x98, ERROR_SEI_CLL_PARAMS_NOT_FOUND = 0x99, ERROR_SEI_AVE_PARAMS_NOT_FOUND = 0x9A, ERROR_SEI_CCV_PARAMS_NOT_FOUND = 0x9B, ERROR_INV_SEI_MDCV_PARAMS = 0x9C, ERROR_INV_SEI_CLL_PARAMS = 0x9D, ERROR_INV_SEI_AVE_PARAMS = 0x9E, ERROR_INV_SEI_CCV_PARAMS = 0x9F, ERROR_INV_FRAME_NUM = 0xA0 } h264_decoder_error_code_t; WORD32 ih264d_mark_err_slice_skip(dec_struct_t * ps_dec, WORD32 num_mb_skip, UWORD8 u1_is_idr_slice, UWORD16 u2_frame_num, pocstruct_t *ps_cur_poc, WORD32 prev_slice_err); void ih264d_err_pic_dispbuf_mgr(dec_struct_t *ps_dec); #endif /* _IH264D_ERROR_HANDLER_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_format_conv.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264d_format_conv.c */ /* */ /* Description : Contains functions needed to convert the images in */ /* different color spaces to yuv 422i color space */ /* */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 08 2007 Naveen Kumar T Draft */ /* */ /*****************************************************************************/ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System include files */ #include <string.h> /* User include files */ #include "ih264_typedefs.h" #include "iv.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" #include "ih264d_format_conv.h" #include "ih264d_defs.h" #ifdef LOGO_EN #include "ih264d_ittiam_logo.h" #define INSERT_LOGO(pu1_buf_y,pu1_buf_u,pu1_buf_v, u4_stride, u4_x_pos, u4_y_pos, u4_yuv_fmt, u4_disp_wd, u4_disp_ht) \ ih264d_insert_logo(pu1_buf_y,pu1_buf_u,pu1_buf_v, u4_stride,\ u4_x_pos, u4_y_pos, u4_yuv_fmt, u4_disp_wd, u4_disp_ht) #else #define INSERT_LOGO(pu1_buf_y,pu1_buf_u,pu1_buf_v, u4_stride, u4_x_pos, u4_y_pos, u4_yuv_fmt, u4_disp_wd, u4_disp_ht) #endif /** ******************************************************************************* * * @brief Function used from copying a 420SP buffer * * @par Description * Function used from copying a 420SP buffer * * @param[in] pu1_y_src * Input Y pointer * * @param[in] pu1_uv_src * Input UV pointer (UV is interleaved either in UV or VU format) * * @param[in] pu1_y_dst * Output Y pointer * * @param[in] pu1_uv_dst * Output UV pointer (UV is interleaved in the same format as that of input) * * @param[in] wd * Width * * @param[in] ht * Height * * @param[in] src_y_strd * Input Y Stride * * @param[in] src_uv_strd * Input UV stride * * @param[in] dst_y_strd * Output Y stride * * @param[in] dst_uv_strd * Output UV stride * * @returns None * * @remarks In case there is a need to perform partial frame copy then * by passion appropriate source and destination pointers and appropriate * values for wd and ht it can be done * ******************************************************************************* */ void ih264d_fmt_conv_420sp_to_rgb565(UWORD8 *pu1_y_src, UWORD8 *pu1_uv_src, UWORD16 *pu2_rgb_dst, WORD32 wd, WORD32 ht, WORD32 src_y_strd, WORD32 src_uv_strd, WORD32 dst_strd, WORD32 is_u_first) { WORD16 i2_r, i2_g, i2_b; UWORD32 u4_r, u4_g, u4_b; WORD16 i2_i, i2_j; UWORD8 *pu1_y_src_nxt; UWORD16 *pu2_rgb_dst_next_row; UWORD8 *pu1_u_src, *pu1_v_src; if(is_u_first) { pu1_u_src = (UWORD8 *)pu1_uv_src; pu1_v_src = (UWORD8 *)pu1_uv_src + 1; } else { pu1_u_src = (UWORD8 *)pu1_uv_src + 1; pu1_v_src = (UWORD8 *)pu1_uv_src; } pu1_y_src_nxt = pu1_y_src + src_y_strd; pu2_rgb_dst_next_row = pu2_rgb_dst + dst_strd; for(i2_i = 0; i2_i < (ht >> 1); i2_i++) { for(i2_j = (wd >> 1); i2_j > 0; i2_j--) { i2_b = ((*pu1_u_src - 128) * COEFF4 >> 13); i2_g = ((*pu1_u_src - 128) * COEFF2 + (*pu1_v_src - 128) * COEFF3) >> 13; i2_r = ((*pu1_v_src - 128) * COEFF1) >> 13; pu1_u_src += 2; pu1_v_src += 2; /* pixel 0 */ /* B */ u4_b = CLIP_U8(*pu1_y_src + i2_b); u4_b >>= 3; /* G */ u4_g = CLIP_U8(*pu1_y_src + i2_g); u4_g >>= 2; /* R */ u4_r = CLIP_U8(*pu1_y_src + i2_r); u4_r >>= 3; pu1_y_src++; *pu2_rgb_dst++ = ((u4_r << 11) | (u4_g << 5) | u4_b); /* pixel 1 */ /* B */ u4_b = CLIP_U8(*pu1_y_src + i2_b); u4_b >>= 3; /* G */ u4_g = CLIP_U8(*pu1_y_src + i2_g); u4_g >>= 2; /* R */ u4_r = CLIP_U8(*pu1_y_src + i2_r); u4_r >>= 3; pu1_y_src++; *pu2_rgb_dst++ = ((u4_r << 11) | (u4_g << 5) | u4_b); /* pixel 2 */ /* B */ u4_b = CLIP_U8(*pu1_y_src_nxt + i2_b); u4_b >>= 3; /* G */ u4_g = CLIP_U8(*pu1_y_src_nxt + i2_g); u4_g >>= 2; /* R */ u4_r = CLIP_U8(*pu1_y_src_nxt + i2_r); u4_r >>= 3; pu1_y_src_nxt++; *pu2_rgb_dst_next_row++ = ((u4_r << 11) | (u4_g << 5) | u4_b); /* pixel 3 */ /* B */ u4_b = CLIP_U8(*pu1_y_src_nxt + i2_b); u4_b >>= 3; /* G */ u4_g = CLIP_U8(*pu1_y_src_nxt + i2_g); u4_g >>= 2; /* R */ u4_r = CLIP_U8(*pu1_y_src_nxt + i2_r); u4_r >>= 3; pu1_y_src_nxt++; *pu2_rgb_dst_next_row++ = ((u4_r << 11) | (u4_g << 5) | u4_b); } pu1_u_src = pu1_u_src + src_uv_strd - wd; pu1_v_src = pu1_v_src + src_uv_strd - wd; pu1_y_src = pu1_y_src + (src_y_strd << 1) - wd; pu1_y_src_nxt = pu1_y_src_nxt + (src_y_strd << 1) - wd; pu2_rgb_dst = pu2_rgb_dst_next_row - wd + dst_strd; pu2_rgb_dst_next_row = pu2_rgb_dst_next_row + (dst_strd << 1) - wd; } } void ih264d_fmt_conv_420sp_to_rgba8888(UWORD8 *pu1_y_src, UWORD8 *pu1_uv_src, UWORD32 *pu4_rgba_dst, WORD32 wd, WORD32 ht, WORD32 src_y_strd, WORD32 src_uv_strd, WORD32 dst_strd, WORD32 is_u_first) { WORD16 i2_r, i2_g, i2_b; UWORD32 u4_r, u4_g, u4_b; WORD16 i2_i, i2_j; UWORD8 *pu1_y_src_nxt; UWORD32 *pu4_rgba_dst_next_row; UWORD8 *pu1_u_src, *pu1_v_src; if(is_u_first) { pu1_u_src = (UWORD8 *)pu1_uv_src; pu1_v_src = (UWORD8 *)pu1_uv_src + 1; } else { pu1_u_src = (UWORD8 *)pu1_uv_src + 1; pu1_v_src = (UWORD8 *)pu1_uv_src; } pu1_y_src_nxt = pu1_y_src + src_y_strd; pu4_rgba_dst_next_row = pu4_rgba_dst + dst_strd; for(i2_i = 0; i2_i < (ht >> 1); i2_i++) { for(i2_j = (wd >> 1); i2_j > 0; i2_j--) { i2_b = ((*pu1_u_src - 128) * COEFF4 >> 13); i2_g = ((*pu1_u_src - 128) * COEFF2 + (*pu1_v_src - 128) * COEFF3) >> 13; i2_r = ((*pu1_v_src - 128) * COEFF1) >> 13; pu1_u_src += 2; pu1_v_src += 2; /* pixel 0 */ /* B */ u4_b = CLIP_U8(*pu1_y_src + i2_b); /* G */ u4_g = CLIP_U8(*pu1_y_src + i2_g); /* R */ u4_r = CLIP_U8(*pu1_y_src + i2_r); pu1_y_src++; *pu4_rgba_dst++ = ((u4_r << 16) | (u4_g << 8) | (u4_b << 0)); /* pixel 1 */ /* B */ u4_b = CLIP_U8(*pu1_y_src + i2_b); /* G */ u4_g = CLIP_U8(*pu1_y_src + i2_g); /* R */ u4_r = CLIP_U8(*pu1_y_src + i2_r); pu1_y_src++; *pu4_rgba_dst++ = ((u4_r << 16) | (u4_g << 8) | (u4_b << 0)); /* pixel 2 */ /* B */ u4_b = CLIP_U8(*pu1_y_src_nxt + i2_b); /* G */ u4_g = CLIP_U8(*pu1_y_src_nxt + i2_g); /* R */ u4_r = CLIP_U8(*pu1_y_src_nxt + i2_r); pu1_y_src_nxt++; *pu4_rgba_dst_next_row++ = ((u4_r << 16) | (u4_g << 8) | (u4_b << 0)); /* pixel 3 */ /* B */ u4_b = CLIP_U8(*pu1_y_src_nxt + i2_b); /* G */ u4_g = CLIP_U8(*pu1_y_src_nxt + i2_g); /* R */ u4_r = CLIP_U8(*pu1_y_src_nxt + i2_r); pu1_y_src_nxt++; *pu4_rgba_dst_next_row++ = ((u4_r << 16) | (u4_g << 8) | (u4_b << 0)); } pu1_u_src = pu1_u_src + src_uv_strd - wd; pu1_v_src = pu1_v_src + src_uv_strd - wd; pu1_y_src = pu1_y_src + (src_y_strd << 1) - wd; pu1_y_src_nxt = pu1_y_src_nxt + (src_y_strd << 1) - wd; pu4_rgba_dst = pu4_rgba_dst_next_row - wd + dst_strd; pu4_rgba_dst_next_row = pu4_rgba_dst_next_row + (dst_strd << 1) - wd; } } /** ******************************************************************************* * * @brief Function used from copying a 420SP buffer * * @par Description * Function used from copying a 420SP buffer * * @param[in] pu1_y_src * Input Y pointer * * @param[in] pu1_uv_src * Input UV pointer (UV is interleaved either in UV or VU format) * * @param[in] pu1_y_dst * Output Y pointer * * @param[in] pu1_uv_dst * Output UV pointer (UV is interleaved in the same format as that of input) * * @param[in] wd * Width * * @param[in] ht * Height * * @param[in] src_y_strd * Input Y Stride * * @param[in] src_uv_strd * Input UV stride * * @param[in] dst_y_strd * Output Y stride * * @param[in] dst_uv_strd * Output UV stride * * @returns None * * @remarks In case there is a need to perform partial frame copy then * by passion appropriate source and destination pointers and appropriate * values for wd and ht it can be done * ******************************************************************************* */ void ih264d_fmt_conv_420sp_to_420sp(UWORD8 *pu1_y_src, UWORD8 *pu1_uv_src, UWORD8 *pu1_y_dst, UWORD8 *pu1_uv_dst, WORD32 wd, WORD32 ht, WORD32 src_y_strd, WORD32 src_uv_strd, WORD32 dst_y_strd, WORD32 dst_uv_strd) { UWORD8 *pu1_src, *pu1_dst; WORD32 num_rows, num_cols, src_strd, dst_strd; WORD32 i; /* copy luma */ pu1_src = (UWORD8 *)pu1_y_src; pu1_dst = (UWORD8 *)pu1_y_dst; num_rows = ht; num_cols = wd; src_strd = src_y_strd; dst_strd = dst_y_strd; for(i = 0; i < num_rows; i++) { memcpy(pu1_dst, pu1_src, num_cols); pu1_dst += dst_strd; pu1_src += src_strd; } /* copy U and V */ pu1_src = (UWORD8 *)pu1_uv_src; pu1_dst = (UWORD8 *)pu1_uv_dst; num_rows = ht >> 1; num_cols = wd; src_strd = src_uv_strd; dst_strd = dst_uv_strd; for(i = 0; i < num_rows; i++) { memcpy(pu1_dst, pu1_src, num_cols); pu1_dst += dst_strd; pu1_src += src_strd; } return; } /** ******************************************************************************* * * @brief Function used from copying a 420SP buffer * * @par Description * Function used from copying a 420SP buffer * * @param[in] pu1_y_src * Input Y pointer * * @param[in] pu1_uv_src * Input UV pointer (UV is interleaved either in UV or VU format) * * @param[in] pu1_y_dst * Output Y pointer * * @param[in] pu1_uv_dst * Output UV pointer (UV is interleaved in the same format as that of input) * * @param[in] wd * Width * * @param[in] ht * Height * * @param[in] src_y_strd * Input Y Stride * * @param[in] src_uv_strd * Input UV stride * * @param[in] dst_y_strd * Output Y stride * * @param[in] dst_uv_strd * Output UV stride * * @returns None * * @remarks In case there is a need to perform partial frame copy then * by passion appropriate source and destination pointers and appropriate * values for wd and ht it can be done * ******************************************************************************* */ void ih264d_fmt_conv_420sp_to_420sp_swap_uv(UWORD8 *pu1_y_src, UWORD8 *pu1_uv_src, UWORD8 *pu1_y_dst, UWORD8 *pu1_uv_dst, WORD32 wd, WORD32 ht, WORD32 src_y_strd, WORD32 src_uv_strd, WORD32 dst_y_strd, WORD32 dst_uv_strd) { UWORD8 *pu1_src, *pu1_dst; WORD32 num_rows, num_cols, src_strd, dst_strd; WORD32 i; /* copy luma */ pu1_src = (UWORD8 *)pu1_y_src; pu1_dst = (UWORD8 *)pu1_y_dst; num_rows = ht; num_cols = wd; src_strd = src_y_strd; dst_strd = dst_y_strd; for(i = 0; i < num_rows; i++) { memcpy(pu1_dst, pu1_src, num_cols); pu1_dst += dst_strd; pu1_src += src_strd; } /* copy U and V */ pu1_src = (UWORD8 *)pu1_uv_src; pu1_dst = (UWORD8 *)pu1_uv_dst; num_rows = ht >> 1; num_cols = wd; src_strd = src_uv_strd; dst_strd = dst_uv_strd; for(i = 0; i < num_rows; i++) { WORD32 j; for(j = 0; j < num_cols; j += 2) { pu1_dst[j + 0] = pu1_src[j + 1]; pu1_dst[j + 1] = pu1_src[j + 0]; } pu1_dst += dst_strd; pu1_src += src_strd; } return; } /** ******************************************************************************* * * @brief Function used from copying a 420SP buffer * * @par Description * Function used from copying a 420SP buffer * * @param[in] pu1_y_src * Input Y pointer * * @param[in] pu1_uv_src * Input UV pointer (UV is interleaved either in UV or VU format) * * @param[in] pu1_y_dst * Output Y pointer * * @param[in] pu1_u_dst * Output U pointer * * @param[in] pu1_v_dst * Output V pointer * * @param[in] wd * Width * * @param[in] ht * Height * * @param[in] src_y_strd * Input Y Stride * * @param[in] src_uv_strd * Input UV stride * * @param[in] dst_y_strd * Output Y stride * * @param[in] dst_uv_strd * Output UV stride * * @param[in] is_u_first * Flag to indicate if U is the first byte in input chroma part * * @returns none * * @remarks In case there is a need to perform partial frame copy then * by passion appropriate source and destination pointers and appropriate * values for wd and ht it can be done * ******************************************************************************* */ void ih264d_fmt_conv_420sp_to_420p(UWORD8 *pu1_y_src, UWORD8 *pu1_uv_src, UWORD8 *pu1_y_dst, UWORD8 *pu1_u_dst, UWORD8 *pu1_v_dst, WORD32 wd, WORD32 ht, WORD32 src_y_strd, WORD32 src_uv_strd, WORD32 dst_y_strd, WORD32 dst_uv_strd, WORD32 is_u_first, WORD32 disable_luma_copy) { UWORD8 *pu1_src, *pu1_dst; UWORD8 *pu1_u_src, *pu1_v_src; WORD32 num_rows, num_cols, src_strd, dst_strd; WORD32 i, j; if(0 == disable_luma_copy) { /* copy luma */ pu1_src = (UWORD8 *)pu1_y_src; pu1_dst = (UWORD8 *)pu1_y_dst; num_rows = ht; num_cols = wd; src_strd = src_y_strd; dst_strd = dst_y_strd; for(i = 0; i < num_rows; i++) { memcpy(pu1_dst, pu1_src, num_cols); pu1_dst += dst_strd; pu1_src += src_strd; } } /* de-interleave U and V and copy to destination */ if(is_u_first) { pu1_u_src = (UWORD8 *)pu1_uv_src; pu1_v_src = (UWORD8 *)pu1_uv_src + 1; } else { pu1_u_src = (UWORD8 *)pu1_uv_src + 1; pu1_v_src = (UWORD8 *)pu1_uv_src; } num_rows = ht >> 1; num_cols = wd >> 1; src_strd = src_uv_strd; dst_strd = dst_uv_strd; for(i = 0; i < num_rows; i++) { for(j = 0; j < num_cols; j++) { pu1_u_dst[j] = pu1_u_src[j * 2]; pu1_v_dst[j] = pu1_v_src[j * 2]; } pu1_u_dst += dst_strd; pu1_v_dst += dst_strd; pu1_u_src += src_strd; pu1_v_src += src_strd; } return; } /*****************************************************************************/ /* Function Name : ih264d_format_convert */ /* */ /* Description : Implements format conversion/frame copy */ /* Inputs : ps_dec - Decoder parameters */ /* Globals : None */ /* Processing : Refer bumping process in the standard */ /* Outputs : Assigns display sequence number. */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 27 04 2005 NS Draft */ /* */ /*****************************************************************************/ void ih264d_format_convert(dec_struct_t *ps_dec, ivd_get_display_frame_op_t *pv_disp_op, UWORD32 u4_start_y, UWORD32 u4_num_rows_y) { UWORD32 convert_uv_only = 0; iv_yuv_buf_t *ps_op_frm; UWORD8 *pu1_y_src, *pu1_uv_src; UWORD32 start_uv = u4_start_y >> 1; if(1 == pv_disp_op->u4_error_code) return; ps_op_frm = &(ps_dec->s_disp_frame_info); /* Requires u4_start_y and u4_num_rows_y to be even */ if(u4_start_y & 1) { return; } if((1 == ps_dec->u4_share_disp_buf) && (pv_disp_op->e_output_format == IV_YUV_420SP_UV)) { return; } pu1_y_src = (UWORD8 *)ps_op_frm->pv_y_buf; pu1_y_src += u4_start_y * ps_op_frm->u4_y_strd, pu1_uv_src = (UWORD8 *)ps_op_frm->pv_u_buf; pu1_uv_src += start_uv * ps_op_frm->u4_u_strd; if(pv_disp_op->e_output_format == IV_YUV_420P) { UWORD8 *pu1_y_dst, *pu1_u_dst, *pu1_v_dst; IV_COLOR_FORMAT_T e_output_format = pv_disp_op->e_output_format; if(0 == ps_dec->u4_share_disp_buf) { convert_uv_only = 0; } else { convert_uv_only = 1; } pu1_y_dst = (UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_y_buf; pu1_y_dst += u4_start_y * pv_disp_op->s_disp_frm_buf.u4_y_strd; pu1_u_dst = (UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_u_buf; pu1_u_dst += start_uv * pv_disp_op->s_disp_frm_buf.u4_u_strd; pu1_v_dst = (UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_v_buf; pu1_v_dst += start_uv * pv_disp_op->s_disp_frm_buf.u4_v_strd; ih264d_fmt_conv_420sp_to_420p(pu1_y_src, pu1_uv_src, pu1_y_dst, pu1_u_dst, pu1_v_dst, ps_op_frm->u4_y_wd, u4_num_rows_y, ps_op_frm->u4_y_strd, ps_op_frm->u4_u_strd, pv_disp_op->s_disp_frm_buf.u4_y_strd, pv_disp_op->s_disp_frm_buf.u4_u_strd, 1, convert_uv_only); } else if((pv_disp_op->e_output_format == IV_YUV_420SP_UV) || (pv_disp_op->e_output_format == IV_YUV_420SP_VU)) { UWORD8* pu1_y_dst, *pu1_uv_dst; pu1_y_dst = (UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_y_buf; pu1_y_dst += u4_start_y * pv_disp_op->s_disp_frm_buf.u4_y_strd; pu1_uv_dst = (UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_u_buf; pu1_uv_dst += start_uv * pv_disp_op->s_disp_frm_buf.u4_u_strd; if(pv_disp_op->e_output_format == IV_YUV_420SP_UV) { ih264d_fmt_conv_420sp_to_420sp(pu1_y_src, pu1_uv_src, pu1_y_dst, pu1_uv_dst, ps_op_frm->u4_y_wd, u4_num_rows_y, ps_op_frm->u4_y_strd, ps_op_frm->u4_u_strd, pv_disp_op->s_disp_frm_buf.u4_y_strd, pv_disp_op->s_disp_frm_buf.u4_u_strd); } else { ih264d_fmt_conv_420sp_to_420sp_swap_uv(pu1_y_src, pu1_uv_src, pu1_y_dst, pu1_uv_dst, ps_op_frm->u4_y_wd, u4_num_rows_y, ps_op_frm->u4_y_strd, ps_op_frm->u4_u_strd, pv_disp_op->s_disp_frm_buf.u4_y_strd, pv_disp_op->s_disp_frm_buf.u4_u_strd); } } else if(pv_disp_op->e_output_format == IV_RGB_565) { UWORD16 *pu2_rgb_dst; pu2_rgb_dst = (UWORD16 *)pv_disp_op->s_disp_frm_buf.pv_y_buf; pu2_rgb_dst += u4_start_y * pv_disp_op->s_disp_frm_buf.u4_y_strd; ih264d_fmt_conv_420sp_to_rgb565(pu1_y_src, pu1_uv_src, pu2_rgb_dst, ps_op_frm->u4_y_wd, u4_num_rows_y, ps_op_frm->u4_y_strd, ps_op_frm->u4_u_strd, pv_disp_op->s_disp_frm_buf.u4_y_strd, 1); } if((u4_start_y + u4_num_rows_y) >= ps_dec->s_disp_frame_info.u4_y_ht) { INSERT_LOGO(pv_disp_op->s_disp_frm_buf.pv_y_buf, pv_disp_op->s_disp_frm_buf.pv_u_buf, pv_disp_op->s_disp_frm_buf.pv_v_buf, pv_disp_op->s_disp_frm_buf.u4_y_strd, ps_dec->u2_disp_width, ps_dec->u2_disp_height, pv_disp_op->e_output_format, ps_op_frm->u4_y_wd, ps_op_frm->u4_y_ht); } return; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_format_conv.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264d_format_conv.h */ /* */ /* Description : Contains coefficients and constant reqquired for */ /* converting from rgb and gray color spaces to yuv422i */ /* color space */ /* */ /* List of Functions : None */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 27 08 2007 Naveen Kumar T Draft */ /* */ /*****************************************************************************/ #ifndef _IH264D_FORMAT_CONV_H_ #define _IH264D_FORMAT_CONV_H_ /*****************************************************************************/ /* Typedefs */ /*****************************************************************************/ #define COEFF_0_Y 66 #define COEFF_1_Y 129 #define COEFF_2_Y 25 #define COEFF_0_U -38 #define COEFF_1_U -75 #define COEFF_2_U 112 #define COEFF_0_V 112 #define COEFF_1_V -94 #define COEFF_2_V -18 #define CONST_RGB_YUV1 4096 #define CONST_RGB_YUV2 32768 #define CONST_GRAY_YUV 128 #define COEF_2_V2_U 0xFFEE0070 #define COF_2Y_0Y 0X00190042 #define COF_1U_0U 0XFFB5FFDA #define COF_1V_0V 0XFFA20070 void ih264d_fmt_conv_420sp_to_420p(UWORD8 *pu1_y_src, UWORD8 *pu1_uv_src, UWORD8 *pu1_y_dst, UWORD8 *pu1_u_dst, UWORD8 *pu1_v_dst, WORD32 wd, WORD32 ht, WORD32 src_y_strd, WORD32 src_uv_strd, WORD32 dst_y_strd, WORD32 dst_uv_strd, WORD32 is_u_first, WORD32 disable_luma_copy); void ih264d_fmt_conv_420sp_to_420sp_swap_uv(UWORD8 *pu1_y_src, UWORD8 *pu1_uv_src, UWORD8 *pu1_y_dst, UWORD8 *pu1_uv_dst, WORD32 wd, WORD32 ht, WORD32 src_y_strd, WORD32 src_uv_strd, WORD32 dst_y_strd, WORD32 dst_uv_strd); void ih264d_fmt_conv_420sp_to_420sp(UWORD8 *pu1_y_src, UWORD8 *pu1_uv_src, UWORD8 *pu1_y_dst, UWORD8 *pu1_uv_dst, WORD32 wd, WORD32 ht, WORD32 src_y_strd, WORD32 src_uv_strd, WORD32 dst_y_strd, WORD32 dst_uv_strd); void ih264d_fmt_conv_420sp_to_rgb565(UWORD8 *pu1_y_src, UWORD8 *pu1_uv_src, UWORD16 *pu2_rgb_dst, WORD32 wd, WORD32 ht, WORD32 src_y_strd, WORD32 src_uv_strd, WORD32 dst_strd, WORD32 is_u_first); #define COEFF1 13073 #define COEFF2 -3207 #define COEFF3 -6664 #define COEFF4 16530 void ih264d_format_convert(dec_struct_t *ps_dec, ivd_get_display_frame_op_t *pv_disp_op, UWORD32 u4_start_y, UWORD32 u4_num_rows_y); #endif /* _IH264D_FORMAT_CONV_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_function_selector.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264d_function_selector.h * * @brief * Structure definitions used in the decoder * * @author * Harish * * @par List of Functions: * * @remarks * None * ******************************************************************************* */ #ifndef _IH264D_FUNCTION_SELECTOR_H_ #define _IH264D_FUNCTION_SELECTOR_H_ #define D_ARCH_NA 1 #define D_ARCH_ARM_NONEON 2 #define D_ARCH_ARM_A9Q 3 #define D_ARCH_ARM_A9A 4 #define D_ARCH_ARM_A9 5 #define D_ARCH_ARM_A7 6 #define D_ARCH_ARM_A5 7 #define D_ARCH_ARM_A15 8 #define D_ARCH_ARM_NEONINTR 9 #define D_ARCH_ARMV8_GENERIC 10 #define D_ARCH_X86_GENERIC 11 #define D_ARCH_X86_SSSE3 12 #define D_ARCH_X86_SSE42 13 #define D_ARCH_X86_AVX2 14 #define D_ARCH_MIPS_GENERIC 15 #define D_ARCH_MIPS_32 16 void ih264d_init_arch(dec_struct_t *ps_codec); void ih264d_init_function_ptr(dec_struct_t *ps_codec); void ih264d_init_function_ptr_generic(dec_struct_t *ps_codec); void ih264d_init_function_ptr_ssse3(dec_struct_t *ps_codec); void ih264d_init_function_ptr_sse42(dec_struct_t *ps_codec); void ih264d_init_function_ptr_a9q(dec_struct_t *ps_codec); void ih264d_init_function_ptr_av8(dec_struct_t *ps_codec); #endif /* _IH264D_FUNCTION_SELECTOR_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_function_selector_generic.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264e_function_selector_generic.c * * @brief * Contains functions to initialize function pointers of codec context * * @author * Ittiam * * @par List of Functions: * - ih264e_init_function_ptr_generic * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System Include files */ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> /* User Include files */ #include "ih264_typedefs.h" #include "iv.h" #include "ivd.h" #include "ih264_defs.h" #include "ih264_size_defs.h" #include "ih264_error.h" #include "ih264_trans_quant_itrans_iquant.h" #include "ih264_inter_pred_filters.h" #include "ih264d_structs.h" #include "ih264d_function_selector.h" /** ******************************************************************************* * * @brief Initialize the intra/inter/transform/deblk function pointers of * codec context * * @par Description: the current routine initializes the function pointers of * codec context basing on the architecture in use * * @param[in] ps_codec * Codec context pointer * * @returns none * * @remarks none * ******************************************************************************* */ void ih264d_init_function_ptr_generic(dec_struct_t *ps_codec) { WORD32 i = 0; /* Init function pointers for intra pred leaf level functions luma * Intra 16x16 */ ps_codec->apf_intra_pred_luma_16x16[0] = ih264_intra_pred_luma_16x16_mode_vert; ps_codec->apf_intra_pred_luma_16x16[1] = ih264_intra_pred_luma_16x16_mode_horz; ps_codec->apf_intra_pred_luma_16x16[2] = ih264_intra_pred_luma_16x16_mode_dc; ps_codec->apf_intra_pred_luma_16x16[3] = ih264_intra_pred_luma_16x16_mode_plane; /* Init function pointers for intra pred leaf level functions luma * Intra 4x4 */ ps_codec->apf_intra_pred_luma_4x4[0] = ih264_intra_pred_luma_4x4_mode_vert; ps_codec->apf_intra_pred_luma_4x4[1] = ih264_intra_pred_luma_4x4_mode_horz; ps_codec->apf_intra_pred_luma_4x4[2] = ih264_intra_pred_luma_4x4_mode_dc; ps_codec->apf_intra_pred_luma_4x4[3] = ih264_intra_pred_luma_4x4_mode_diag_dl; ps_codec->apf_intra_pred_luma_4x4[4] = ih264_intra_pred_luma_4x4_mode_diag_dr; ps_codec->apf_intra_pred_luma_4x4[5] = ih264_intra_pred_luma_4x4_mode_vert_r; ps_codec->apf_intra_pred_luma_4x4[6] = ih264_intra_pred_luma_4x4_mode_horz_d; ps_codec->apf_intra_pred_luma_4x4[7] = ih264_intra_pred_luma_4x4_mode_vert_l; ps_codec->apf_intra_pred_luma_4x4[8] = ih264_intra_pred_luma_4x4_mode_horz_u; /* Init function pointers for intra pred leaf level functions luma * Intra 8x8 */ ps_codec->apf_intra_pred_luma_8x8[0] = ih264_intra_pred_luma_8x8_mode_vert; ps_codec->apf_intra_pred_luma_8x8[1] = ih264_intra_pred_luma_8x8_mode_horz; ps_codec->apf_intra_pred_luma_8x8[2] = ih264_intra_pred_luma_8x8_mode_dc; ps_codec->apf_intra_pred_luma_8x8[3] = ih264_intra_pred_luma_8x8_mode_diag_dl; ps_codec->apf_intra_pred_luma_8x8[4] = ih264_intra_pred_luma_8x8_mode_diag_dr; ps_codec->apf_intra_pred_luma_8x8[5] = ih264_intra_pred_luma_8x8_mode_vert_r; ps_codec->apf_intra_pred_luma_8x8[6] = ih264_intra_pred_luma_8x8_mode_horz_d; ps_codec->apf_intra_pred_luma_8x8[7] = ih264_intra_pred_luma_8x8_mode_vert_l; ps_codec->apf_intra_pred_luma_8x8[8] = ih264_intra_pred_luma_8x8_mode_horz_u; ps_codec->pf_intra_pred_ref_filtering = ih264_intra_pred_luma_8x8_mode_ref_filtering; /* Init function pointers for intra pred leaf level functions chroma * Intra 8x8 */ ps_codec->apf_intra_pred_chroma[0] = ih264_intra_pred_chroma_8x8_mode_vert; ps_codec->apf_intra_pred_chroma[1] = ih264_intra_pred_chroma_8x8_mode_horz; ps_codec->apf_intra_pred_chroma[2] = ih264_intra_pred_chroma_8x8_mode_dc; ps_codec->apf_intra_pred_chroma[3] = ih264_intra_pred_chroma_8x8_mode_plane; ps_codec->pf_default_weighted_pred_luma = ih264_default_weighted_pred_luma; ps_codec->pf_default_weighted_pred_chroma = ih264_default_weighted_pred_chroma; ps_codec->pf_weighted_pred_luma = ih264_weighted_pred_luma; ps_codec->pf_weighted_pred_chroma = ih264_weighted_pred_chroma; ps_codec->pf_weighted_bi_pred_luma = ih264_weighted_bi_pred_luma; ps_codec->pf_weighted_bi_pred_chroma = ih264_weighted_bi_pred_chroma; /* Padding Functions */ ps_codec->pf_pad_top = ih264_pad_top; ps_codec->pf_pad_bottom = ih264_pad_bottom; ps_codec->pf_pad_left_luma = ih264_pad_left_luma; ps_codec->pf_pad_left_chroma = ih264_pad_left_chroma; ps_codec->pf_pad_right_luma = ih264_pad_right_luma; ps_codec->pf_pad_right_chroma = ih264_pad_right_chroma; ps_codec->pf_iquant_itrans_recon_luma_4x4 = ih264_iquant_itrans_recon_4x4; ps_codec->pf_iquant_itrans_recon_luma_4x4_dc = ih264_iquant_itrans_recon_4x4_dc; ps_codec->pf_iquant_itrans_recon_luma_8x8 = ih264_iquant_itrans_recon_8x8; ps_codec->pf_iquant_itrans_recon_luma_8x8_dc = ih264_iquant_itrans_recon_8x8_dc; ps_codec->pf_iquant_itrans_recon_chroma_4x4 = ih264_iquant_itrans_recon_chroma_4x4; ps_codec->pf_iquant_itrans_recon_chroma_4x4_dc = ih264_iquant_itrans_recon_chroma_4x4_dc; ps_codec->pf_ihadamard_scaling_4x4 = ih264_ihadamard_scaling_4x4; /* Init fn ptr luma deblocking */ ps_codec->pf_deblk_luma_vert_bs4 = ih264_deblk_luma_vert_bs4; ps_codec->pf_deblk_luma_vert_bslt4 = ih264_deblk_luma_vert_bslt4; ps_codec->pf_deblk_luma_vert_bs4_mbaff = ih264_deblk_luma_vert_bs4_mbaff; ps_codec->pf_deblk_luma_vert_bslt4_mbaff = ih264_deblk_luma_vert_bslt4_mbaff; ps_codec->pf_deblk_luma_horz_bs4 = ih264_deblk_luma_horz_bs4; ps_codec->pf_deblk_luma_horz_bslt4 = ih264_deblk_luma_horz_bslt4; /* Init fn ptr chroma deblocking */ ps_codec->pf_deblk_chroma_vert_bs4 = ih264_deblk_chroma_vert_bs4; ps_codec->pf_deblk_chroma_vert_bslt4 = ih264_deblk_chroma_vert_bslt4; ps_codec->pf_deblk_chroma_vert_bs4_mbaff = ih264_deblk_chroma_vert_bs4_mbaff; ps_codec->pf_deblk_chroma_vert_bslt4_mbaff = ih264_deblk_chroma_vert_bslt4_mbaff; ps_codec->pf_deblk_chroma_horz_bs4 = ih264_deblk_chroma_horz_bs4; ps_codec->pf_deblk_chroma_horz_bslt4 = ih264_deblk_chroma_horz_bslt4; /* Inter pred leaf level functions */ ps_codec->apf_inter_pred_luma[0] = ih264_inter_pred_luma_copy; ps_codec->apf_inter_pred_luma[1] = ih264_inter_pred_luma_horz_qpel; ps_codec->apf_inter_pred_luma[2] = ih264_inter_pred_luma_horz; ps_codec->apf_inter_pred_luma[3] = ih264_inter_pred_luma_horz_qpel; ps_codec->apf_inter_pred_luma[4] = ih264_inter_pred_luma_vert_qpel; ps_codec->apf_inter_pred_luma[5] = ih264_inter_pred_luma_horz_qpel_vert_qpel; ps_codec->apf_inter_pred_luma[6] = ih264_inter_pred_luma_horz_hpel_vert_qpel; ps_codec->apf_inter_pred_luma[7] = ih264_inter_pred_luma_horz_qpel_vert_qpel; ps_codec->apf_inter_pred_luma[8] = ih264_inter_pred_luma_vert; ps_codec->apf_inter_pred_luma[9] = ih264_inter_pred_luma_horz_qpel_vert_hpel; ps_codec->apf_inter_pred_luma[10] = ih264_inter_pred_luma_horz_hpel_vert_hpel; ps_codec->apf_inter_pred_luma[11] = ih264_inter_pred_luma_horz_qpel_vert_hpel; ps_codec->apf_inter_pred_luma[12] = ih264_inter_pred_luma_vert_qpel; ps_codec->apf_inter_pred_luma[13] = ih264_inter_pred_luma_horz_qpel_vert_qpel; ps_codec->apf_inter_pred_luma[14] = ih264_inter_pred_luma_horz_hpel_vert_qpel; ps_codec->apf_inter_pred_luma[15] = ih264_inter_pred_luma_horz_qpel_vert_qpel; ps_codec->pf_inter_pred_chroma = ih264_inter_pred_chroma; return; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_inter_pred.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_inter_pred.c * * \brief * This file contains routines to perform MotionCompensation tasks * * Detailed_description * * \date * 20/11/2002 * * \author Arvind Raman ************************************************************************** */ #include <string.h> #include "ih264d_defs.h" #include "ih264d_mvpred.h" #include "ih264d_error_handler.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264d_inter_pred.h" #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_mb_utils.h" void ih264d_pad_on_demand(pred_info_t *ps_pred, UWORD8 lum_chrom_blk); void ih264d_copy_multiplex_data(UWORD8 *puc_Source, UWORD8 *puc_To, UWORD32 uc_w, UWORD32 uc_h, UWORD32 ui16_sourceWidth, UWORD32 ui16_toWidth) { UWORD8 uc_i, uc_j; for(uc_i = 0; uc_i < uc_h; uc_i++) { memcpy(puc_To, puc_Source, uc_w); puc_To += ui16_toWidth; puc_Source += ui16_sourceWidth; } } /*! ************************************************************************** * \if Function name : dma_2d1d \endif * * \brief * 2D -> 1D linear DMA into the reference buffers * * \return * None ************************************************************************** */ void ih264d_copy_2d1d(UWORD8 *puc_src, UWORD8 *puc_dest, UWORD16 ui16_srcWidth, UWORD16 ui16_widthToFill, UWORD16 ui16_heightToFill) { UWORD32 uc_w, uc_h; for(uc_h = ui16_heightToFill; uc_h != 0; uc_h--) { memcpy(puc_dest, puc_src, ui16_widthToFill); puc_dest += ui16_widthToFill; puc_src += ui16_srcWidth; } } /*! ************************************************************************** * \if Function name : ih264d_fill_pred_info \endif * * \brief * Fills inter prediction related info * * \return * None ************************************************************************** */ void ih264d_fill_pred_info(WORD16 *pi2_mv,WORD32 part_width,WORD32 part_height, WORD32 sub_mb_num, WORD32 pred_dir,pred_info_pkd_t *ps_pred_pkd,WORD8 i1_buf_id, WORD8 i1_ref_idx,UWORD32 *pu4_wt_offset,UWORD8 u1_pic_type) { WORD32 insert_bits; ps_pred_pkd->i2_mv[0] = pi2_mv[0]; ps_pred_pkd->i2_mv[1] = pi2_mv[1]; insert_bits = sub_mb_num & 3; /*sub mb x*/ ps_pred_pkd->i1_size_pos_info = insert_bits; insert_bits = sub_mb_num >> 2;/*sub mb y*/ ps_pred_pkd->i1_size_pos_info |= insert_bits << 2; insert_bits = part_width >> 1; ps_pred_pkd->i1_size_pos_info |= insert_bits << 4; insert_bits = part_height >> 1; ps_pred_pkd->i1_size_pos_info |= insert_bits << 6; ps_pred_pkd->i1_ref_idx_info = i1_ref_idx; ps_pred_pkd->i1_ref_idx_info |= (pred_dir << 6); ps_pred_pkd->i1_buf_id = i1_buf_id; ps_pred_pkd->pu4_wt_offst = pu4_wt_offset; ps_pred_pkd->u1_pic_type = u1_pic_type; } /*****************************************************************************/ /* \if Function name : formMbPartInfo \endif */ /* */ /* \brief */ /* Form the Mb partition information structure, to be used by the MC */ /* routine */ /* */ /* \return */ /* None */ /* \note */ /* c_bufx is used to select PredBuffer, */ /* if it's only Forward/Backward prediction always buffer used is */ /* puc_MbLumaPredBuffer[0 to X1],pu1_mb_cb_pred_buffer[0 to X1] and */ /* pu1_mb_cr_pred_buffer[0 to X1] */ /* */ /* if it's bidirect for forward ..PredBuffer[0 to X1] buffer is used and */ /* ..PredBuffer[X2 to X3] for backward prediction. and */ /* */ /* Final predicted samples values are the average of ..PredBuffer[0 to X1]*/ /* and ..PredBuffer[X2 to X3] */ /* */ /* X1 is 255 for Luma and 63 for Chroma */ /* X2 is 256 for Luma and 64 for Chroma */ /* X3 is 511 for Luma and 127 for Chroma */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 11 05 2005 SWRN Modified to handle pod */ /*****************************************************************************/ WORD32 ih264d_form_mb_part_info_bp(pred_info_pkd_t *ps_pred_pkd, dec_struct_t * ps_dec, UWORD16 u2_mb_x, UWORD16 u2_mb_y, WORD32 mb_index, dec_mb_info_t *ps_cur_mb_info) { /* The reference buffer pointer */ WORD32 i2_frm_x, i2_frm_y; WORD32 i2_tmp_mv_x, i2_tmp_mv_y; WORD32 i2_rec_x, i2_rec_y; WORD32 u2_pic_ht; WORD32 u2_frm_wd; WORD32 u2_rec_wd; UWORD8 u1_sub_x = 0,u1_sub_y=0 ; UWORD8 u1_part_wd = 0,u1_part_ht = 0; WORD16 i2_mv_x,i2_mv_y; /********************************************/ /* i1_mc_wd width reqd for mcomp */ /* u1_dma_ht height reqd for mcomp */ /* u1_dma_wd width aligned to 4 bytes */ /* u1_dx fractional part of width */ /* u1_dx fractional part of height */ /********************************************/ UWORD32 i1_mc_wd; WORD32 u1_dma_ht; UWORD32 u1_dma_wd; UWORD32 u1_dx; UWORD32 u1_dy; pred_info_t * ps_pred = ps_dec->ps_pred + ps_dec->u4_pred_info_idx; dec_slice_params_t * const ps_cur_slice = ps_dec->ps_cur_slice; tfr_ctxt_t *ps_frame_buf; struct pic_buffer_t *ps_ref_frm; UWORD8 u1_scale_ref,u1_mbaff,u1_field; pic_buffer_t **pps_ref_frame; WORD8 i1_size_pos_info,i1_buf_id; PROFILE_DISABLE_MB_PART_INFO() UNUSED(ps_cur_mb_info); i1_size_pos_info = ps_pred_pkd->i1_size_pos_info; GET_XPOS_PRED(u1_sub_x,i1_size_pos_info); GET_YPOS_PRED(u1_sub_y,i1_size_pos_info); GET_WIDTH_PRED(u1_part_wd,i1_size_pos_info); GET_HEIGHT_PRED(u1_part_ht,i1_size_pos_info); i2_mv_x = ps_pred_pkd->i2_mv[0]; i2_mv_y = ps_pred_pkd->i2_mv[1]; i1_buf_id = ps_pred_pkd->i1_buf_id; ps_ref_frm = ps_dec->apv_buf_id_pic_buf_map[i1_buf_id]; { ps_frame_buf = &ps_dec->s_tran_addrecon; } /* Transfer Setup Y */ { UWORD8 *pu1_pred, *pu1_rec; /* calculating rounded motion vectors and fractional components */ i2_tmp_mv_x = i2_mv_x; i2_tmp_mv_y = i2_mv_y; u1_dx = i2_tmp_mv_x & 0x3; u1_dy = i2_tmp_mv_y & 0x3; i2_tmp_mv_x >>= 2; i2_tmp_mv_y >>= 2; i1_mc_wd = u1_part_wd << 2; u1_dma_ht = u1_part_ht << 2; if(u1_dx) { i2_tmp_mv_x -= 2; i1_mc_wd += 5; } if(u1_dy) { i2_tmp_mv_y -= 2; u1_dma_ht += 5; } /********************************************************************/ /* Calulating the horizontal and the vertical u4_ofst from top left */ /* edge of the reference frame, and subsequent clipping */ /********************************************************************/ u2_pic_ht = ps_dec->u2_pic_ht; u2_frm_wd = ps_dec->u2_frm_wd_y; i2_rec_x = u1_sub_x << 2; i2_rec_y = u1_sub_y << 2; i2_frm_x = (u2_mb_x << 4) + i2_rec_x + i2_tmp_mv_x; i2_frm_y = (u2_mb_y << 4) + i2_rec_y + i2_tmp_mv_y; i2_frm_x = CLIP3(MAX_OFFSET_OUTSIDE_X_FRM, (ps_dec->u2_pic_wd - 1), i2_frm_x); i2_frm_y = CLIP3(((1 - u1_dma_ht)), (u2_pic_ht - (1)), i2_frm_y); pu1_pred = ps_ref_frm->pu1_buf1 + i2_frm_y * u2_frm_wd + i2_frm_x; u1_dma_wd = (i1_mc_wd + 3) & 0xFC; /********************************************************************/ /* Calulating the horizontal and the vertical u4_ofst from top left */ /* edge of the recon buffer */ /********************************************************************/ u2_rec_wd = MB_SIZE; { u2_rec_wd = ps_dec->u2_frm_wd_y; i2_rec_x += (mb_index << 4); pu1_rec = ps_frame_buf->pu1_dest_y + i2_rec_y * u2_rec_wd + i2_rec_x; } /* filling the pred and dma structures for Y */ u2_frm_wd = ps_dec->u2_frm_wd_y; ps_pred->u2_u1_ref_buf_wd = u1_dma_wd; ps_pred->i1_dma_ht = u1_dma_ht; ps_pred->i1_mc_wd = i1_mc_wd; ps_pred->u2_frm_wd = u2_frm_wd; ps_pred->pu1_rec_y_u = pu1_rec; ps_pred->u2_dst_stride = u2_rec_wd; ps_pred->i1_mb_partwidth = u1_part_wd << 2; ps_pred->i1_mb_partheight = u1_part_ht << 2; ps_pred->u1_dydx = (u1_dy << 2) + u1_dx; ps_pred->pu1_y_ref = pu1_pred; } /* Increment ps_pred index */ ps_pred++; /* Transfer Setup U & V */ { WORD32 i4_ref_offset, i4_rec_offset; UWORD8 *pu1_pred_u, *pu1_pred_v; /* calculating rounded motion vectors and fractional components */ i2_tmp_mv_x = i2_mv_x; i2_tmp_mv_y = i2_mv_y; /************************************************************************/ /* Table 8-9: Derivation of the vertical component of the chroma vector */ /* in field coding mode */ /************************************************************************/ /* Eighth sample of the chroma MV */ u1_dx = i2_tmp_mv_x & 0x7; u1_dy = i2_tmp_mv_y & 0x7; /********************************************************************/ /* Calculating the full pel MV for chroma which is 1/2 of the Luma */ /* MV in full pel units */ /********************************************************************/ i2_mv_x = i2_tmp_mv_x; i2_mv_y = i2_tmp_mv_y; i2_tmp_mv_x = SIGN_POW2_DIV(i2_tmp_mv_x, 3); i2_tmp_mv_y = SIGN_POW2_DIV(i2_tmp_mv_y, 3); i1_mc_wd = u1_part_wd << 1; u1_dma_ht = u1_part_ht << 1; if(u1_dx) { i2_tmp_mv_x -= (i2_mv_x < 0); i1_mc_wd++; } if(u1_dy != 0) { i2_tmp_mv_y -= (i2_mv_y < 0); u1_dma_ht++; } /********************************************************************/ /* Calulating the horizontal and the vertical u4_ofst from top left */ /* edge of the reference frame, and subsequent clipping */ /********************************************************************/ u2_pic_ht >>= 1; u2_frm_wd = ps_dec->u2_frm_wd_uv; i2_rec_x = u1_sub_x << 1; i2_rec_y = u1_sub_y << 1; i2_frm_x = (u2_mb_x << 3) + i2_rec_x + i2_tmp_mv_x; i2_frm_y = (u2_mb_y << 3) + i2_rec_y + i2_tmp_mv_y; i2_frm_x = CLIP3(MAX_OFFSET_OUTSIDE_UV_FRM, ((ps_dec->u2_pic_wd >> 1) - 1), i2_frm_x); i2_frm_y = CLIP3(((1 - u1_dma_ht)), (u2_pic_ht - (1)), i2_frm_y); i4_ref_offset = i2_frm_y * u2_frm_wd + i2_frm_x * YUV420SP_FACTOR; u1_dma_wd = (i1_mc_wd + 3) & 0xFC; /********************************************************************/ /* Calulating the horizontal and the vertical u4_ofst from top left */ /* edge of the recon buffer */ /********************************************************************/ /* CHANGED CODE */ u2_rec_wd = BLK8x8SIZE * YUV420SP_FACTOR; i4_rec_offset = i2_rec_y * u2_rec_wd + i2_rec_x * YUV420SP_FACTOR; { u2_rec_wd = ps_dec->u2_frm_wd_uv; i2_rec_x += (mb_index << 3); i4_rec_offset = i2_rec_y * u2_rec_wd + i2_rec_x * YUV420SP_FACTOR; ps_pred->pu1_rec_y_u = ps_frame_buf->pu1_dest_u + i4_rec_offset; ps_pred->u1_pi1_wt_ofst_rec_v = ps_frame_buf->pu1_dest_v + i4_rec_offset; } /* CHANGED CODE */ /* filling the common pred structures for U */ u2_frm_wd = ps_dec->u2_frm_wd_uv; ps_pred->u2_u1_ref_buf_wd = u1_dma_wd; ps_pred->i1_dma_ht = u1_dma_ht; ps_pred->i1_mc_wd = i1_mc_wd; ps_pred->u2_frm_wd = u2_frm_wd; ps_pred->u2_dst_stride = u2_rec_wd; ps_pred->i1_mb_partwidth = u1_part_wd << 1; ps_pred->i1_mb_partheight = u1_part_ht << 1; ps_pred->u1_dydx = (u1_dy << 3) + u1_dx; pu1_pred_u = ps_ref_frm->pu1_buf2 + i4_ref_offset; pu1_pred_v = ps_ref_frm->pu1_buf3 + i4_ref_offset; /* Copy U & V partitions */ ps_pred->pu1_u_ref = pu1_pred_u; /* Increment the reference buffer Index */ ps_pred->pu1_v_ref = pu1_pred_v; } /* Increment ps_pred index */ ps_dec->u4_pred_info_idx += 2; return OK; } /*****************************************************************************/ /* \if Function name : formMbPartInfo \endif */ /* */ /* \brief */ /* Form the Mb partition information structure, to be used by the MC */ /* routine */ /* */ /* \return */ /* None */ /* \note */ /* c_bufx is used to select PredBuffer, */ /* if it's only Forward/Backward prediction always buffer used is */ /* puc_MbLumaPredBuffer[0 to X1],pu1_mb_cb_pred_buffer[0 to X1] and */ /* pu1_mb_cr_pred_buffer[0 to X1] */ /* */ /* if it's bidirect for forward ..PredBuffer[0 to X1] buffer is used and */ /* ..PredBuffer[X2 to X3] for backward prediction. and */ /* */ /* Final predicted samples values are the average of ..PredBuffer[0 to X1]*/ /* and ..PredBuffer[X2 to X3] */ /* */ /* X1 is 255 for Luma and 63 for Chroma */ /* X2 is 256 for Luma and 64 for Chroma */ /* X3 is 511 for Luma and 127 for Chroma */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 11 05 2005 SWRN Modified to handle pod */ /*****************************************************************************/ WORD32 ih264d_form_mb_part_info_mp(pred_info_pkd_t *ps_pred_pkd, dec_struct_t * ps_dec, UWORD16 u2_mb_x, UWORD16 u2_mb_y, WORD32 mb_index, dec_mb_info_t *ps_cur_mb_info) { /* The reference buffer pointer */ UWORD8 *pu1_ref_buf; WORD16 i2_frm_x, i2_frm_y, i2_tmp_mv_x, i2_tmp_mv_y, i2_pod_ht; WORD16 i2_rec_x, i2_rec_y; UWORD16 u2_pic_ht, u2_frm_wd, u2_rec_wd; UWORD8 u1_wght_pred_type, u1_wted_bipred_idc; UWORD16 u2_tot_ref_scratch_size; UWORD8 u1_sub_x = 0; UWORD8 u1_sub_y = 0; UWORD8 u1_is_bi_dir = 0; /********************************************/ /* i1_mc_wd width reqd for mcomp */ /* u1_dma_ht height reqd for mcomp */ /* u1_dma_wd width aligned to 4 bytes */ /* u1_dx fractional part of width */ /* u1_dx fractional part of height */ /********************************************/ UWORD8 i1_mc_wd, u1_dma_ht, u1_dma_wd, u1_dx, u1_dy; pred_info_t * ps_pred ; dec_slice_params_t * const ps_cur_slice = ps_dec->ps_cur_slice; const UWORD8 u1_slice_type = ps_dec->ps_decode_cur_slice->slice_type; UWORD8 u1_pod_bot, u1_pod_top; /* load the pictype for pod u4_flag & chroma motion vector derivation */ UWORD8 u1_ref_pic_type ; /* set default value to flags specifying field nature of picture & mb */ UWORD32 u1_mb_fld = 0, u1_mb_or_pic_fld; UWORD32 u1_mb_bot = 0, u1_pic_bot = 0, u1_mb_or_pic_bot; tfr_ctxt_t *ps_frame_buf; /* calculate flags specifying field nature of picture & mb */ const UWORD32 u1_pic_fld = ps_cur_slice->u1_field_pic_flag; WORD8 i1_pred; WORD8 i1_size_pos_info,i1_buf_id,i1_ref_idx; UWORD8 u1_part_wd,u1_part_ht; WORD16 i2_mv_x,i2_mv_y; struct pic_buffer_t *ps_ref_frm; UWORD32 *pu4_wt_offset; UWORD8 *pu1_buf1,*pu1_buf2,*pu1_buf3; PROFILE_DISABLE_MB_PART_INFO() ps_pred = ps_dec->ps_pred + ps_dec->u4_pred_info_idx; i1_size_pos_info = ps_pred_pkd->i1_size_pos_info; GET_XPOS_PRED(u1_sub_x,i1_size_pos_info); GET_YPOS_PRED(u1_sub_y,i1_size_pos_info); GET_WIDTH_PRED(u1_part_wd,i1_size_pos_info); GET_HEIGHT_PRED(u1_part_ht,i1_size_pos_info); i2_mv_x = ps_pred_pkd->i2_mv[0]; i2_mv_y = ps_pred_pkd->i2_mv[1]; i1_ref_idx = ps_pred_pkd->i1_ref_idx_info & 0x3f; i1_buf_id = ps_pred_pkd->i1_buf_id; ps_ref_frm = ps_dec->apv_buf_id_pic_buf_map[i1_buf_id]; i1_pred = (ps_pred_pkd->i1_ref_idx_info & 0xC0) >> 6; u1_is_bi_dir = (i1_pred == BI_PRED); u1_ref_pic_type = ps_pred_pkd->u1_pic_type & PIC_MASK; pu1_buf1 = ps_ref_frm->pu1_buf1; pu1_buf2 = ps_ref_frm->pu1_buf2; pu1_buf3 = ps_ref_frm->pu1_buf3; if(u1_ref_pic_type == BOT_FLD) { pu1_buf1 += ps_ref_frm->u2_frm_wd_y; pu1_buf2 += ps_ref_frm->u2_frm_wd_uv; pu1_buf3 += ps_ref_frm->u2_frm_wd_uv; } if(ps_dec->ps_cur_pps->u1_wted_pred_flag) { pu4_wt_offset = (UWORD32*)&ps_dec->pu4_wt_ofsts[2 * X3(i1_ref_idx)]; } pu4_wt_offset = ps_pred_pkd->pu4_wt_offst; /* Pointer to the frame buffer */ { ps_frame_buf = &ps_dec->s_tran_addrecon; /* CHANGED CODE */ } if(!u1_pic_fld) { u1_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; u1_mb_bot = 1 - ps_cur_mb_info->u1_topmb; } else u1_pic_bot = ps_cur_slice->u1_bottom_field_flag; /****************************************************************/ /* calculating the flags the tell whether to use frame-padding */ /* or use software pad-on-demand */ /****************************************************************/ u1_mb_or_pic_bot = u1_mb_bot | u1_pic_bot; u1_mb_or_pic_fld = u1_mb_fld | u1_pic_fld; u1_pod_bot = u1_mb_or_pic_fld && (u1_ref_pic_type == TOP_FLD); u1_pod_top = u1_mb_or_pic_fld && (u1_ref_pic_type == BOT_FLD); /* Weighted Pred additions */ u1_wted_bipred_idc = ps_dec->ps_cur_pps->u1_wted_bipred_idc; if((u1_slice_type == P_SLICE) || (u1_slice_type == SP_SLICE)) { /* P Slice only */ u1_wght_pred_type = ps_dec->ps_cur_pps->u1_wted_pred_flag; } else { /* B Slice only */ u1_wght_pred_type = 1 + u1_is_bi_dir; if(u1_wted_bipred_idc == 0) u1_wght_pred_type = 0; if((u1_wted_bipred_idc == 2) && (!u1_is_bi_dir)) u1_wght_pred_type = 0; } /* load the scratch reference buffer index */ pu1_ref_buf = ps_dec->pu1_ref_buff + ps_dec->u4_dma_buf_idx; u2_tot_ref_scratch_size = 0; /* Transfer Setup Y */ { UWORD8 *pu1_pred, *pu1_rec; /* calculating rounded motion vectors and fractional components */ i2_tmp_mv_x = i2_mv_x; i2_tmp_mv_y = i2_mv_y; u1_dx = i2_tmp_mv_x & 0x3; u1_dy = i2_tmp_mv_y & 0x3; i2_tmp_mv_x >>= 2; i2_tmp_mv_y >>= 2; i1_mc_wd = u1_part_wd << 2; u1_dma_ht = u1_part_ht << 2; if(u1_dx) { i2_tmp_mv_x -= 2; i1_mc_wd += 5; } if(u1_dy) { i2_tmp_mv_y -= 2; u1_dma_ht += 5; } /********************************************************************/ /* Calulating the horizontal and the vertical u4_ofst from top left */ /* edge of the reference frame, and subsequent clipping */ /********************************************************************/ u2_pic_ht = ps_dec->u2_pic_ht >> u1_pic_fld; u2_frm_wd = ps_dec->u2_frm_wd_y << u1_pic_fld; i2_frm_x = (u2_mb_x << 4) + (u1_sub_x << 2) + i2_tmp_mv_x; i2_frm_y = ((u2_mb_y + (u1_mb_bot && !u1_mb_fld)) << 4) + (((u1_sub_y << 2) + i2_tmp_mv_y) << u1_mb_fld); i2_frm_x = CLIP3(MAX_OFFSET_OUTSIDE_X_FRM, (ps_dec->u2_pic_wd - 1), i2_frm_x); i2_frm_y = CLIP3(((1 - u1_dma_ht) << u1_mb_fld), (u2_pic_ht - (1 << u1_mb_fld)), i2_frm_y); pu1_pred = pu1_buf1 + i2_frm_y * u2_frm_wd + i2_frm_x; u1_dma_wd = (i1_mc_wd + 3) & 0xFC; /********************************************************************/ /* Calulating the horizontal and the vertical u4_ofst from top left */ /* edge of the recon buffer */ /********************************************************************/ /* CHANGED CODE */ u2_rec_wd = MB_SIZE; i2_rec_x = u1_sub_x << 2; i2_rec_y = u1_sub_y << 2; { u2_rec_wd = ps_dec->u2_frm_wd_y << u1_mb_or_pic_fld; i2_rec_x += (mb_index << 4); pu1_rec = ps_frame_buf->pu1_dest_y + i2_rec_y * u2_rec_wd + i2_rec_x; if(u1_mb_bot) pu1_rec += ps_dec->u2_frm_wd_y << ((u1_mb_fld) ? 0 : 4); } /* CHANGED CODE */ /* filling the pred and dma structures for Y */ u2_frm_wd = ps_dec->u2_frm_wd_y << u1_mb_or_pic_fld; ps_pred->pu1_dma_dest_addr = pu1_ref_buf; ps_pred->u2_u1_ref_buf_wd = u1_dma_wd; ps_pred->u2_frm_wd = u2_frm_wd; ps_pred->i1_dma_ht = u1_dma_ht; ps_pred->i1_mc_wd = i1_mc_wd; ps_pred->pu1_rec_y_u = pu1_rec; ps_pred->u2_dst_stride = u2_rec_wd; ps_pred->i1_mb_partwidth = u1_part_wd << 2; ps_pred->i1_mb_partheight = u1_part_ht << 2; ps_pred->u1_dydx = (u1_dy << 2) + u1_dx; ps_pred->u1_is_bi_direct = u1_is_bi_dir; ps_pred->u1_pi1_wt_ofst_rec_v = (UWORD8 *)pu4_wt_offset; ps_pred->u1_wght_pred_type = u1_wght_pred_type; ps_pred->i1_pod_ht = 0; /* Increment the Reference buffer Indices */ pu1_ref_buf += u1_dma_wd * u1_dma_ht; u2_tot_ref_scratch_size += u1_dma_wd * u1_dma_ht; /* unrestricted field motion comp for top region outside frame */ i2_pod_ht = (-i2_frm_y) >> u1_mb_fld; if((i2_pod_ht > 0) && u1_pod_top) { ps_pred->i1_pod_ht = (WORD8)(-i2_pod_ht); u1_dma_ht -= i2_pod_ht; pu1_pred += i2_pod_ht * u2_frm_wd; } /* unrestricted field motion comp for bottom region outside frame */ else if(u1_pod_bot) { i2_pod_ht = u1_dma_ht + ((i2_frm_y - u2_pic_ht) >> u1_mb_fld); if(i2_pod_ht > 0) { u1_dma_ht -= i2_pod_ht; ps_pred->i1_pod_ht = (WORD8)i2_pod_ht; } } /* Copy Y partition */ /* * ps_pred->i1_pod_ht is non zero when MBAFF is present. In case of MBAFF the reference data * is copied in the Scrath buffer so that the padding_on_demand doesnot corrupt the frame data */ if(ps_pred->i1_pod_ht) { ps_pred->pu1_pred = pu1_pred; ps_pred->u1_dma_ht_y = u1_dma_ht; ps_pred->u1_dma_wd_y = u1_dma_wd; } ps_pred->pu1_y_ref = pu1_pred; } /* Increment ps_pred index */ ps_pred++; /* Transfer Setup U & V */ { WORD32 i4_ref_offset, i4_rec_offset; UWORD8 *pu1_pred_u, *pu1_pred_v, u1_tmp_dma_ht; /* CHANGED CODE */ UWORD8 u1_chroma_cbp = (UWORD8)(ps_cur_mb_info->u1_cbp >> 4); /* CHANGED CODE */ /* calculating rounded motion vectors and fractional components */ i2_tmp_mv_x = i2_mv_x; i2_tmp_mv_y = i2_mv_y; /************************************************************************/ /* Table 8-9: Derivation of the vertical component of the chroma vector */ /* in field coding mode */ /************************************************************************/ if(u1_pod_bot && u1_mb_or_pic_bot) i2_tmp_mv_y += 2; if(u1_pod_top && !u1_mb_or_pic_bot) i2_tmp_mv_y -= 2; /* Eighth sample of the chroma MV */ u1_dx = i2_tmp_mv_x & 0x7; u1_dy = i2_tmp_mv_y & 0x7; /********************************************************************/ /* Calculating the full pel MV for chroma which is 1/2 of the Luma */ /* MV in full pel units */ /********************************************************************/ i2_mv_x = i2_tmp_mv_x; i2_mv_y = i2_tmp_mv_y; i2_tmp_mv_x = SIGN_POW2_DIV(i2_tmp_mv_x, 3); i2_tmp_mv_y = SIGN_POW2_DIV(i2_tmp_mv_y, 3); i1_mc_wd = u1_part_wd << 1; u1_dma_ht = u1_part_ht << 1; if(u1_dx) { if(i2_mv_x < 0) i2_tmp_mv_x -= 1; i1_mc_wd++; } if(u1_dy != 0) { if(i2_mv_y < 0) i2_tmp_mv_y -= 1; u1_dma_ht++; } /********************************************************************/ /* Calulating the horizontal and the vertical u4_ofst from top left */ /* edge of the reference frame, and subsequent clipping */ /********************************************************************/ u2_pic_ht >>= 1; u2_frm_wd = ps_dec->u2_frm_wd_uv << u1_pic_fld; i2_frm_x = (u2_mb_x << 3) + (u1_sub_x << 1) + i2_tmp_mv_x; i2_frm_y = ((u2_mb_y + (u1_mb_bot && !u1_mb_fld)) << 3) + (((u1_sub_y << 1) + i2_tmp_mv_y) << u1_mb_fld); i2_frm_x = CLIP3(MAX_OFFSET_OUTSIDE_UV_FRM, ((ps_dec->u2_pic_wd >> 1) - 1), i2_frm_x); i2_frm_y = CLIP3(((1 - u1_dma_ht) << u1_mb_fld), (u2_pic_ht - (1 << u1_mb_fld)), i2_frm_y); i4_ref_offset = i2_frm_y * u2_frm_wd + i2_frm_x * YUV420SP_FACTOR; u1_dma_wd = (i1_mc_wd + 3) & 0xFC; /********************************************************************/ /* Calulating the horizontal and the vertical u4_ofst from top left */ /* edge of the recon buffer */ /********************************************************************/ /* CHANGED CODE */ u2_rec_wd = BLK8x8SIZE * YUV420SP_FACTOR; i2_rec_x = u1_sub_x << 1; i2_rec_y = u1_sub_y << 1; i4_rec_offset = i2_rec_y * u2_rec_wd + i2_rec_x * YUV420SP_FACTOR; { u2_rec_wd = ps_dec->u2_frm_wd_uv << u1_mb_or_pic_fld; i2_rec_x += (mb_index << 3); i4_rec_offset = i2_rec_y * u2_rec_wd + i2_rec_x * YUV420SP_FACTOR; if(u1_mb_bot) i4_rec_offset += ps_dec->u2_frm_wd_uv << ((u1_mb_fld) ? 0 : 3); ps_pred->pu1_rec_y_u = ps_frame_buf->pu1_dest_u + i4_rec_offset; ps_pred->u1_pi1_wt_ofst_rec_v = ps_frame_buf->pu1_dest_v + i4_rec_offset; } /* CHANGED CODE */ /* filling the common pred structures for U */ u2_frm_wd = ps_dec->u2_frm_wd_uv << u1_mb_or_pic_fld; u1_tmp_dma_ht = u1_dma_ht; ps_pred->u2_u1_ref_buf_wd = u1_dma_wd; ps_pred->u2_frm_wd = u2_frm_wd; ps_pred->i1_dma_ht = u1_dma_ht; ps_pred->i1_mc_wd = i1_mc_wd; ps_pred->u2_dst_stride = u2_rec_wd; ps_pred->i1_mb_partwidth = u1_part_wd << 1; ps_pred->i1_mb_partheight = u1_part_ht << 1; ps_pred->u1_dydx = (u1_dy << 3) + u1_dx; ps_pred->u1_is_bi_direct = u1_is_bi_dir; ps_pred->u1_wght_pred_type = u1_wght_pred_type; ps_pred->i1_pod_ht = 0; ps_pred->pu1_dma_dest_addr = pu1_ref_buf; /* unrestricted field motion comp for top region outside frame */ i2_pod_ht = (-i2_frm_y) >> u1_mb_fld; if((i2_pod_ht > 0) && u1_pod_top) { i4_ref_offset += i2_pod_ht * u2_frm_wd; u1_dma_ht -= i2_pod_ht; ps_pred->i1_pod_ht = (WORD8)(-i2_pod_ht); } /* unrestricted field motion comp for bottom region outside frame */ else if(u1_pod_bot) { i2_pod_ht = u1_dma_ht + ((i2_frm_y - u2_pic_ht) >> u1_mb_fld); if(i2_pod_ht > 0) { u1_dma_ht -= i2_pod_ht; ps_pred->i1_pod_ht = (WORD8)i2_pod_ht; } } pu1_pred_u = pu1_buf2 + i4_ref_offset; pu1_pred_v = pu1_buf3 + i4_ref_offset; /* Copy U & V partitions */ if(ps_pred->i1_pod_ht) { ps_pred->pu1_pred_u = pu1_pred_u; ps_pred->u1_dma_ht_uv = u1_dma_ht; ps_pred->u1_dma_wd_uv = u1_dma_wd; } ps_pred->pu1_u_ref = pu1_pred_u; /* Increment the reference buffer Index */ u2_tot_ref_scratch_size += (u1_dma_wd * u1_tmp_dma_ht) << 1; if(ps_pred->i1_pod_ht) { ps_pred->pu1_pred_v = pu1_pred_v; ps_pred->u1_dma_ht_uv = u1_dma_ht; ps_pred->u1_dma_wd_uv = u1_dma_wd; } ps_pred->pu1_v_ref = pu1_pred_v; } /* Increment ps_pred index */ ps_dec->u4_pred_info_idx += 2; /* Increment the reference buffer Index */ ps_dec->u4_dma_buf_idx += u2_tot_ref_scratch_size; if(ps_dec->u4_dma_buf_idx > MAX_REF_BUF_SIZE) return ERROR_NUM_MV; return OK; } /*! ************************************************************************** * \if Function name : MotionCompensate \endif * * \brief * The routine forms predictor blocks for the entire MB and stores it in * predictor buffers.This function works only for BASELINE profile * * \param ps_dec: Pointer to the structure decStruct. This is used to get * pointers to the current and the reference frame and to the MbParams * structure. * * \return * None * * \note * The routine forms predictors for all the luma and the chroma MB * partitions. ************************************************************************** */ void ih264d_motion_compensate_bp(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info) { pred_info_t *ps_pred ; UWORD8 *puc_ref, *pu1_dest_y; UWORD8 *pu1_dest_u; UWORD32 u2_num_pels, u2_ref_wd_y, u2_ref_wd_uv, u2_dst_wd; UWORD32 u4_wd_y, u4_ht_y, u4_wd_uv; UWORD32 u4_ht_uv; UWORD8 *puc_pred0 = (UWORD8 *)(ps_dec->pi2_pred1); PROFILE_DISABLE_INTER_PRED() UNUSED(ps_cur_mb_info); ps_pred = ps_dec->ps_pred ; for(u2_num_pels = 0; u2_num_pels < 256;) { UWORD32 uc_dx, uc_dy; /* Pointer to the destination buffer. If the CBPs of all 8x8 blocks in the MB partition are zero then it would be better to copy the predictor valus directly to the current frame buffer */ /* * ps_pred->i1_pod_ht is non zero when MBAFF is present. In case of MBAFF the reference data * is copied in the Scrath buffer so that the padding_on_demand doesnot corrupt the frame data */ u2_ref_wd_y = ps_pred->u2_frm_wd; puc_ref = ps_pred->pu1_y_ref; if(ps_pred->u1_dydx & 0x3) puc_ref += 2; if(ps_pred->u1_dydx >> 2) puc_ref += 2 * u2_ref_wd_y; u4_wd_y = ps_pred->i1_mb_partwidth; u4_ht_y = ps_pred->i1_mb_partheight; uc_dx = ps_pred->u1_dydx; uc_dy = uc_dx >> 2; uc_dx &= 0x3; pu1_dest_y = ps_pred->pu1_rec_y_u; u2_dst_wd = ps_pred->u2_dst_stride; ps_dec->apf_inter_pred_luma[ps_pred->u1_dydx](puc_ref, pu1_dest_y, u2_ref_wd_y, u2_dst_wd, u4_ht_y, u4_wd_y, puc_pred0, ps_pred->u1_dydx); ps_pred++; /* Interpolate samples for the chroma components */ { UWORD8 *pu1_ref_u; u2_ref_wd_uv = ps_pred->u2_frm_wd; pu1_ref_u = ps_pred->pu1_u_ref; u4_wd_uv = ps_pred->i1_mb_partwidth; u4_ht_uv = ps_pred->i1_mb_partheight; uc_dx = ps_pred->u1_dydx; /* 8*dy + dx */ uc_dy = uc_dx >> 3; uc_dx &= 0x7; pu1_dest_u = ps_pred->pu1_rec_y_u; u2_dst_wd = ps_pred->u2_dst_stride; ps_pred++; ps_dec->pf_inter_pred_chroma(pu1_ref_u, pu1_dest_u, u2_ref_wd_uv, u2_dst_wd, uc_dx, uc_dy, u4_ht_uv, u4_wd_uv); } u2_num_pels += (UWORD8)u4_wd_y * (UWORD8)u4_ht_y; } } /* ************************************************************************** * \if Function name : MotionCompensateB \endif * * \brief * The routine forms predictor blocks for the entire MB and stores it in * predictor buffers. * * \param ps_dec: Pointer to the structure decStruct. This is used to get * pointers to the current and the reference frame and to the MbParams * structure. * * \return * None * * \note * The routine forms predictors for all the luma and the chroma MB * partitions. ************************************************************************** */ void ih264d_motion_compensate_mp(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info) { pred_info_t *ps_pred ; pred_info_t *ps_pred_y_forw, *ps_pred_y_back, *ps_pred_cr_forw; UWORD8 *puc_ref, *pu1_dest_y, *puc_pred0, *puc_pred1; UWORD8 *pu1_dest_u, *pu1_dest_v; WORD16 *pi16_intm; UWORD32 u2_num_pels, u2_ref_wd_y, u2_ref_wd_uv, u2_dst_wd; UWORD32 u2_dest_wd_y, u2_dest_wd_uv; UWORD32 u2_row_buf_wd_y = 0; UWORD32 u2_row_buf_wd_uv = 0; UWORD32 u2_log2Y_crwd; UWORD32 u4_wd_y, u4_ht_y, u1_dir, u4_wd_uv; UWORD32 u4_ht_uv; UWORD8 *pu1_temp_mc_buffer = ps_dec->pu1_temp_mc_buffer; WORD32 i2_pod_ht; UWORD32 u2_pic_ht, u2_frm_wd, u2_rec_wd; UWORD32 u1_pod_bot, u1_pod_top; UWORD8 *pu1_pred, *pu1_dma_dst; UWORD32 u1_dma_wd, u1_dma_ht; dec_slice_params_t * const ps_cur_slice = ps_dec->ps_cur_slice; /* set default value to flags specifying field nature of picture & mb */ UWORD32 u1_mb_fld = 0, u1_mb_or_pic_fld; UWORD32 u1_mb_or_pic_bot; /* calculate flags specifying field nature of picture & mb */ const UWORD8 u1_pic_fld = ps_cur_slice->u1_field_pic_flag; PROFILE_DISABLE_INTER_PRED() ps_pred = ps_dec->ps_pred ; /* Initialize both ps_pred_y_forw, ps_pred_cr_forw and ps_pred_y_back * to avoid static analysis warnings */ ps_pred_y_forw = ps_pred; ps_pred_y_back = ps_pred; ps_pred_cr_forw = ps_pred; u2_log2Y_crwd = ps_dec->ps_decode_cur_slice->u2_log2Y_crwd; if(!u1_pic_fld) { u1_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; } u1_mb_or_pic_fld = u1_mb_fld | u1_pic_fld; pi16_intm = ps_dec->pi2_pred1; puc_pred0 = (UWORD8 *)pi16_intm; puc_pred1 = puc_pred0 + PRED_BUFFER_WIDTH * PRED_BUFFER_HEIGHT * sizeof(WORD16); for(u2_num_pels = 0; u2_num_pels < 256;) { UWORD8 uc_dx, uc_dy; const UWORD8 u1_is_bi_direct = ps_pred->u1_is_bi_direct; for(u1_dir = 0; u1_dir <= u1_is_bi_direct; u1_dir++) { /* Pointer to the destination buffer. If the CBPs of all 8x8 blocks in the MB partition are zero then it would be better to copy the predictor valus directly to the current frame buffer */ /* * ps_pred->i1_pod_ht is non zero when MBAFF is present. In case of MBAFF the reference data * is copied in the Scrath buffer so that the padding_on_demand doesnot corrupt the frame data */ if(ps_pred->i1_pod_ht) { u2_ref_wd_y = ps_pred->u2_u1_ref_buf_wd; puc_ref = ps_pred->pu1_dma_dest_addr; } else { u2_ref_wd_y = ps_pred->u2_frm_wd; puc_ref = ps_pred->pu1_y_ref; } if(ps_pred->u1_dydx & 0x3) puc_ref += 2; if(ps_pred->u1_dydx >> 2) puc_ref += 2 * u2_ref_wd_y; u4_wd_y = ps_pred->i1_mb_partwidth; u4_ht_y = ps_pred->i1_mb_partheight; uc_dx = ps_pred->u1_dydx; uc_dy = uc_dx >> 2; uc_dx &= 0x3; if(u1_dir == 0) { pu1_dest_y = ps_pred->pu1_rec_y_u; u2_row_buf_wd_y = ps_pred->u2_dst_stride; u2_dst_wd = ps_pred->u2_dst_stride; u2_dest_wd_y = u2_dst_wd; ps_pred_y_forw = ps_pred; } else { pu1_dest_y = pu1_temp_mc_buffer; u2_dst_wd = MB_SIZE; u2_dest_wd_y = u2_dst_wd; ps_pred_y_back = ps_pred; ps_pred_y_back->pu1_rec_y_u = pu1_dest_y; } /* padding on demand (POD) for y done here */ if(ps_pred->i1_pod_ht) { pu1_pred = ps_pred->pu1_pred; pu1_dma_dst = ps_pred->pu1_dma_dest_addr; u1_dma_wd = ps_pred->u1_dma_wd_y; u1_dma_ht = ps_pred->u1_dma_ht_y; u2_frm_wd = ps_dec->u2_frm_wd_y << u1_mb_or_pic_fld; if(ps_pred->i1_pod_ht < 0) { pu1_dma_dst = pu1_dma_dst - (ps_pred->i1_pod_ht * ps_pred->u2_u1_ref_buf_wd); } ih264d_copy_2d1d(pu1_pred, pu1_dma_dst, u2_frm_wd, u1_dma_wd, u1_dma_ht); ih264d_pad_on_demand(ps_pred, LUM_BLK); } ps_dec->apf_inter_pred_luma[ps_pred->u1_dydx](puc_ref, pu1_dest_y, u2_ref_wd_y, u2_dst_wd, u4_ht_y, u4_wd_y, puc_pred0, ps_pred->u1_dydx); ps_pred++; /* Interpolate samples for the chroma components */ { UWORD8 *pu1_ref_u; UWORD32 u1_dma_ht; /* padding on demand (POD) for U and V done here */ u1_dma_ht = ps_pred->i1_dma_ht; if(ps_pred->i1_pod_ht) { pu1_pred = ps_pred->pu1_pred_u; pu1_dma_dst = ps_pred->pu1_dma_dest_addr; u1_dma_ht = ps_pred->u1_dma_ht_uv; u1_dma_wd = ps_pred->u1_dma_wd_uv * YUV420SP_FACTOR; u2_frm_wd = ps_dec->u2_frm_wd_uv << u1_mb_or_pic_fld; if(ps_pred->i1_pod_ht < 0) { /*Top POD*/ pu1_dma_dst -= (ps_pred->i1_pod_ht * ps_pred->u2_u1_ref_buf_wd * YUV420SP_FACTOR); } ih264d_copy_2d1d(pu1_pred, pu1_dma_dst, u2_frm_wd, u1_dma_wd, u1_dma_ht); pu1_dma_dst += (ps_pred->i1_dma_ht * ps_pred->u2_u1_ref_buf_wd); pu1_pred = ps_pred->pu1_pred_v; ih264d_pad_on_demand(ps_pred, CHROM_BLK); } if(ps_pred->i1_pod_ht) { pu1_ref_u = ps_pred->pu1_dma_dest_addr; u2_ref_wd_uv = ps_pred->u2_u1_ref_buf_wd * YUV420SP_FACTOR; } else { u2_ref_wd_uv = ps_pred->u2_frm_wd; pu1_ref_u = ps_pred->pu1_u_ref; } u4_wd_uv = ps_pred->i1_mb_partwidth; u4_ht_uv = ps_pred->i1_mb_partheight; uc_dx = ps_pred->u1_dydx; /* 8*dy + dx */ uc_dy = uc_dx >> 3; uc_dx &= 0x7; if(u1_dir == 0) { pu1_dest_u = ps_pred->pu1_rec_y_u; pu1_dest_v = ps_pred->u1_pi1_wt_ofst_rec_v; u2_row_buf_wd_uv = ps_pred->u2_dst_stride; u2_dst_wd = ps_pred->u2_dst_stride; u2_dest_wd_uv = u2_dst_wd; ps_pred_cr_forw = ps_pred; } else { pu1_dest_u = puc_pred0; pu1_dest_v = puc_pred1; u2_dest_wd_uv = BUFFER_WIDTH; u2_dst_wd = BUFFER_WIDTH; ps_pred->pu1_rec_y_u = pu1_dest_u; ps_pred->u1_pi1_wt_ofst_rec_v = pu1_dest_v; } ps_pred++; ps_dec->pf_inter_pred_chroma(pu1_ref_u, pu1_dest_u, u2_ref_wd_uv, u2_dst_wd, uc_dx, uc_dy, u4_ht_uv, u4_wd_uv); if(ps_cur_mb_info->u1_Mux == 1) { /******************************************************************/ /* padding on demand (POD) for U and V done here */ /* ps_pred now points to the Y entry of the 0,0 component */ /* Y need not be checked for POD because Y lies within */ /* the picture((0,0) mv for Y doesnot get changed. But (0,0) for */ /* U and V can need POD beacause of cross-field mv adjustments */ /* (Table 8-9 of standard) */ /******************************************************************/ if((ps_pred + 1)->i1_pod_ht) { pu1_pred = (ps_pred + 1)->pu1_pred_u; pu1_dma_dst = (ps_pred + 1)->pu1_dma_dest_addr; u1_dma_ht = (ps_pred + 1)->u1_dma_ht_uv; u1_dma_wd = (ps_pred + 1)->u1_dma_wd_uv * YUV420SP_FACTOR; u2_frm_wd = ps_dec->u2_frm_wd_uv << u1_mb_or_pic_fld; if((ps_pred + 1)->i1_pod_ht < 0) { /*Top POD*/ pu1_dma_dst -= ((ps_pred + 1)->i1_pod_ht * (ps_pred + 1)->u2_u1_ref_buf_wd * YUV420SP_FACTOR); } ih264d_copy_2d1d(pu1_pred, pu1_dma_dst, u2_frm_wd, u1_dma_wd, u1_dma_ht); pu1_dma_dst += ((ps_pred + 1)->i1_dma_ht * (ps_pred + 1)->u2_u1_ref_buf_wd); //(u1_dma_ht * u1_dma_wd);// pu1_pred = (ps_pred + 1)->pu1_pred_v; ih264d_pad_on_demand(ps_pred + 1, CHROM_BLK); } ih264d_multiplex_ref_data(ps_dec, ps_pred, pu1_dest_y, pu1_dest_u, ps_cur_mb_info, u2_dest_wd_y, u2_dest_wd_uv, u1_dir); ps_pred += 2; } } } if(u1_dir != 0) u2_ref_wd_y = MB_SIZE; u2_num_pels += u4_wd_y * u4_ht_y; /* if BI_DIRECT, average the two pred's, and put in ..PredBuffer[0] */ if((u1_is_bi_direct != 0) || (ps_pred_y_forw->u1_wght_pred_type != 0)) { switch(ps_pred_y_forw->u1_wght_pred_type) { case 0: ps_dec->pf_default_weighted_pred_luma( ps_pred_y_forw->pu1_rec_y_u, pu1_dest_y, ps_pred_y_forw->pu1_rec_y_u, u2_row_buf_wd_y, u2_ref_wd_y, u2_row_buf_wd_y, u4_ht_uv * 2, u4_wd_uv * 2); ps_dec->pf_default_weighted_pred_chroma( ps_pred_cr_forw->pu1_rec_y_u, pu1_dest_u, ps_pred_cr_forw->pu1_rec_y_u, u2_row_buf_wd_uv, u2_dst_wd, u2_row_buf_wd_uv, u4_ht_uv, u4_wd_uv); break; case 1: { UWORD32 *pu4_weight_ofst = (UWORD32*)ps_pred_y_forw->u1_pi1_wt_ofst_rec_v; UWORD32 u4_wt_ofst_u, u4_wt_ofst_v; UWORD32 u4_wt_ofst_y = (UWORD32)(pu4_weight_ofst[0]); WORD32 weight = (WORD16)(u4_wt_ofst_y & 0xffff); WORD32 ofst = (WORD8)(u4_wt_ofst_y >> 16); ps_dec->pf_weighted_pred_luma(ps_pred_y_forw->pu1_rec_y_u, ps_pred_y_forw->pu1_rec_y_u, u2_row_buf_wd_y, u2_row_buf_wd_y, (u2_log2Y_crwd & 0x0ff), weight, ofst, u4_ht_y, u4_wd_y); u4_wt_ofst_u = (UWORD32)(pu4_weight_ofst[2]); u4_wt_ofst_v = (UWORD32)(pu4_weight_ofst[4]); weight = ((u4_wt_ofst_v & 0xffff) << 16) | (u4_wt_ofst_u & 0xffff); ofst = ((u4_wt_ofst_v >> 16) << 8) | ((u4_wt_ofst_u >> 16) & 0xFF); ps_dec->pf_weighted_pred_chroma( ps_pred_cr_forw->pu1_rec_y_u, ps_pred_cr_forw->pu1_rec_y_u, u2_row_buf_wd_uv, u2_row_buf_wd_uv, (u2_log2Y_crwd >> 8), weight, ofst, u4_ht_y >> 1, u4_wd_y >> 1); } break; case 2: { UWORD32 *pu4_weight_ofst = (UWORD32*)ps_pred_y_forw->u1_pi1_wt_ofst_rec_v; UWORD32 u4_wt_ofst_u, u4_wt_ofst_v; UWORD32 u4_wt_ofst_y; WORD32 weight1, weight2; WORD32 ofst1, ofst2; u4_wt_ofst_y = (UWORD32)(pu4_weight_ofst[0]); weight1 = (WORD16)(u4_wt_ofst_y & 0xffff); ofst1 = (WORD8)(u4_wt_ofst_y >> 16); u4_wt_ofst_y = (UWORD32)(pu4_weight_ofst[1]); weight2 = (WORD16)(u4_wt_ofst_y & 0xffff); ofst2 = (WORD8)(u4_wt_ofst_y >> 16); ps_dec->pf_weighted_bi_pred_luma(ps_pred_y_forw->pu1_rec_y_u, ps_pred_y_back->pu1_rec_y_u, ps_pred_y_forw->pu1_rec_y_u, u2_row_buf_wd_y, u2_ref_wd_y, u2_row_buf_wd_y, (u2_log2Y_crwd & 0x0ff), weight1, weight2, ofst1, ofst2, u4_ht_y, u4_wd_y); u4_wt_ofst_u = (UWORD32)(pu4_weight_ofst[2]); u4_wt_ofst_v = (UWORD32)(pu4_weight_ofst[4]); weight1 = ((u4_wt_ofst_v & 0xffff) << 16) | (u4_wt_ofst_u & 0xffff); ofst1 = ((u4_wt_ofst_v >> 16) << 8) | ((u4_wt_ofst_u >> 16) & 0xFF); u4_wt_ofst_u = (UWORD32)(pu4_weight_ofst[3]); u4_wt_ofst_v = (UWORD32)(pu4_weight_ofst[5]); weight2 = ((u4_wt_ofst_v & 0xffff) << 16) | (u4_wt_ofst_u & 0xffff); ofst2 = ((u4_wt_ofst_v >> 16) << 8) | ((u4_wt_ofst_u >> 16) & 0xFF); ps_dec->pf_weighted_bi_pred_chroma( (ps_pred_y_forw + 1)->pu1_rec_y_u, (ps_pred_y_back + 1)->pu1_rec_y_u, (ps_pred_y_forw + 1)->pu1_rec_y_u, u2_row_buf_wd_uv, u2_dst_wd, u2_row_buf_wd_uv, (u2_log2Y_crwd >> 8), weight1, weight2, ofst1, ofst2, u4_ht_y >> 1, u4_wd_y >> 1); } break; } } } } /*! ************************************************************************** * \if Function name : ih264d_multiplex_ref_data \endif * * \brief * Initializes forward and backward refernce lists for B slice decoding. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ void ih264d_multiplex_ref_data(dec_struct_t * ps_dec, pred_info_t *ps_pred, UWORD8* pu1_dest_y, UWORD8* pu1_dest_u, dec_mb_info_t *ps_cur_mb_info, UWORD16 u2_dest_wd_y, UWORD16 u2_dest_wd_uv, UWORD8 u1_dir) { UWORD16 u2_mask = ps_cur_mb_info->u2_mask[u1_dir]; UWORD8 *pu1_ref_y, *pu1_ref_u; UWORD8 uc_cond, i, j, u1_dydx; UWORD16 u2_ref_wd_y, u2_ref_wd_uv; PROFILE_DISABLE_INTER_PRED() if(ps_pred->i1_pod_ht) { pu1_ref_y = ps_pred->pu1_dma_dest_addr; u2_ref_wd_y = ps_pred->u2_u1_ref_buf_wd; } else { pu1_ref_y = ps_pred->pu1_y_ref; u2_ref_wd_y = ps_pred->u2_frm_wd; } ps_pred++; if(ps_pred->i1_pod_ht) { pu1_ref_u = ps_pred->pu1_dma_dest_addr; u2_ref_wd_uv = ps_pred->u2_u1_ref_buf_wd * YUV420SP_FACTOR; } else { pu1_ref_u = ps_pred->pu1_u_ref; u2_ref_wd_uv = ps_pred->u2_frm_wd; } u1_dydx = ps_pred->u1_dydx; { UWORD8 uc_dx, uc_dy; UWORD8 *pu1_scratch_u; uc_dx = u1_dydx & 0x3; uc_dy = u1_dydx >> 3; if(u1_dydx != 0) { pred_info_t * ps_prv_pred = ps_pred - 2; pu1_scratch_u = ps_prv_pred->pu1_dma_dest_addr; ps_dec->pf_inter_pred_chroma(pu1_ref_u, pu1_scratch_u, u2_ref_wd_uv, 16, uc_dx, uc_dy, 8, 8); /* Modify ref pointer and refWidth to point to scratch */ /* buffer to be used below in ih264d_copy_multiplex_data functions */ /* CHANGED CODE */ pu1_ref_u = pu1_scratch_u; u2_ref_wd_uv = 8 * YUV420SP_FACTOR; } } { for(i = 0; i < 4; i++) { for(j = 0; j < 4; j++) { uc_cond = u2_mask & 1; u2_mask >>= 1; if(uc_cond) { *(UWORD32 *)(pu1_dest_y + u2_dest_wd_y) = *(UWORD32 *)(pu1_ref_y + u2_ref_wd_y); *(UWORD32 *)(pu1_dest_y + 2 * u2_dest_wd_y) = *(UWORD32 *)(pu1_ref_y + 2 * u2_ref_wd_y); *(UWORD32 *)(pu1_dest_y + 3 * u2_dest_wd_y) = *(UWORD32 *)(pu1_ref_y + 3 * u2_ref_wd_y); { UWORD32 *dst, *src; dst = (UWORD32 *)pu1_dest_y; src = (UWORD32 *)pu1_ref_y; *dst = *src; dst++; src++; pu1_dest_y = (UWORD8 *)dst; pu1_ref_y = (UWORD8 *)src; } *(UWORD32 *)(pu1_dest_u + u2_dest_wd_uv) = *(UWORD32 *)(pu1_ref_u + u2_ref_wd_uv); { UWORD32 *dst, *src; dst = (UWORD32 *)pu1_dest_u; src = (UWORD32 *)pu1_ref_u; *dst = *src; dst++; src++; pu1_dest_u = (UWORD8 *)dst; pu1_ref_u = (UWORD8 *)src; } } else { pu1_dest_y += 4; pu1_ref_y += 4; pu1_dest_u += 2 * YUV420SP_FACTOR; pu1_ref_u += 2 * YUV420SP_FACTOR; } } pu1_ref_y += 4 * (u2_ref_wd_y - 4); pu1_ref_u += 2 * (u2_ref_wd_uv - 4 * YUV420SP_FACTOR); pu1_dest_y += 4 * (u2_dest_wd_y - 4); pu1_dest_u += 2 * (u2_dest_wd_uv - 4 * YUV420SP_FACTOR); } } } void ih264d_pad_on_demand(pred_info_t *ps_pred, UWORD8 lum_chrom_blk) { if(CHROM_BLK == lum_chrom_blk) { UWORD32 *pu4_pod_src_u, *pu4_pod_dst_u; UWORD32 *pu4_pod_src_v, *pu4_pod_dst_v; WORD32 j, u1_wd_stride; WORD32 i, u1_dma_ht, i1_ht; UWORD32 u2_dma_size; u1_wd_stride = (ps_pred->u2_u1_ref_buf_wd >> 2) * YUV420SP_FACTOR; u1_dma_ht = ps_pred->i1_dma_ht; u2_dma_size = u1_wd_stride * u1_dma_ht; pu4_pod_src_u = (UWORD32 *)ps_pred->pu1_dma_dest_addr; pu4_pod_dst_u = pu4_pod_src_u; pu4_pod_src_v = pu4_pod_src_u + u2_dma_size; pu4_pod_dst_v = pu4_pod_src_v; i1_ht = ps_pred->i1_pod_ht; pu4_pod_src_u -= u1_wd_stride * i1_ht; pu4_pod_src_v -= u1_wd_stride * i1_ht; if(i1_ht < 0) /* Top POD */ i1_ht = -i1_ht; else { /* Bottom POD */ pu4_pod_src_u += (u1_dma_ht - 1) * u1_wd_stride; pu4_pod_dst_u += (u1_dma_ht - i1_ht) * u1_wd_stride; pu4_pod_src_v += (u1_dma_ht - 1) * u1_wd_stride; pu4_pod_dst_v += (u1_dma_ht - i1_ht) * u1_wd_stride; } for(i = 0; i < i1_ht; i++) for(j = 0; j < u1_wd_stride; j++) { *pu4_pod_dst_u++ = *(pu4_pod_src_u + j); } } else { UWORD32 *pu4_pod_src, *pu4_pod_dst; WORD32 j, u1_wd_stride; WORD32 i, i1_ht; pu4_pod_src = (UWORD32 *)ps_pred->pu1_dma_dest_addr; pu4_pod_dst = pu4_pod_src; u1_wd_stride = ps_pred->u2_u1_ref_buf_wd >> 2; i1_ht = ps_pred->i1_pod_ht; pu4_pod_src -= u1_wd_stride * i1_ht; if(i1_ht < 0) /* Top POD */ i1_ht = -i1_ht; else { /* Bottom POD */ pu4_pod_src += (ps_pred->i1_dma_ht - 1) * u1_wd_stride; pu4_pod_dst += (ps_pred->i1_dma_ht - i1_ht) * u1_wd_stride; } for(i = 0; i < i1_ht; i++) for(j = 0; j < u1_wd_stride; j++) *pu4_pod_dst++ = *(pu4_pod_src + j); } } ================================================ FILE: dependencies/ih264d/decoder/ih264d_inter_pred.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_INTER_PRED_H_ #define _IH264D_INTER_PRED_H_ /*! ************************************************************************** * \file ih264d_inter_pred.h * * \brief * Decalaration for routines defined in MorionCompensate.c * * Detailed_description * * \date * creation_date * * \author Arvind Raman ************************************************************************** */ #include "ih264d_structs.h" #define BUFFER_WIDTH 16 /*! ************************************************************************** * \brief PRED_BUFFER_WIDTH / HEIGHT * * Width and height of the 16 bit (also reused a 2 8 bits buffers). The * required dimensions for these buffers are 21x21, however to align the * start of every row to a WORD aligned boundary the width has been increased * to 24. ************************************************************************** */ //#define PRED_BUFFER_WIDTH 24 //#define PRED_BUFFER_HEIGHT 21 #define PRED_BUFFER_WIDTH 24*2 #define PRED_BUFFER_HEIGHT 24*2 void ih264d_fill_pred_info(WORD16 *pi2_mv,WORD32 part_width,WORD32 part_height, WORD32 sub_mb_num, WORD32 pred_dir,pred_info_pkd_t *ps_pred_pkd,WORD8 i1_buf_id, WORD8 i1_ref_idx,UWORD32 *pu4_wt_offset,UWORD8 u1_pic_type); WORD32 ih264d_form_mb_part_info_bp(pred_info_pkd_t *ps_pred_pkd, dec_struct_t * ps_dec, UWORD16 u2_mb_x, UWORD16 u2_mb_y, WORD32 mb_index, dec_mb_info_t *ps_cur_mb_info); WORD32 ih264d_form_mb_part_info_mp(pred_info_pkd_t *ps_pred_pkd, dec_struct_t * ps_dec, UWORD16 u2_mb_x, UWORD16 u2_mb_y, WORD32 mb_index, dec_mb_info_t *ps_cur_mb_info); void ih264d_motion_compensate_bp(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info); void ih264d_motion_compensate_mp(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info); void TransferRefBuffs(dec_struct_t *ps_dec); void ih264d_multiplex_ref_data(dec_struct_t * ps_dec, pred_info_t *ps_pred, UWORD8* pu1_dest_y, UWORD8* pu1_dest_u, dec_mb_info_t *ps_cur_mb_info, UWORD16 u2_dest_wd_y, UWORD16 u2_dest_wd_uv, UWORD8 u1_dir); #endif /* _IH264D_INTER_PRED_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_mb_utils.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_mb_utils.c * * \brief * Contains utitlity functions needed for Macroblock decoding * * \date * 18/12/2002 * * \author AI ************************************************************************** */ #include <string.h> #include <stdlib.h> #include "ih264d_bitstrm.h" #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264d_mb_utils.h" #include "ih264d_parse_slice.h" #include "ih264d_error_handler.h" #include "ih264d_parse_mb_header.h" #include "ih264d_cabac.h" #include "ih264d_defs.h" #include "ih264d_tables.h" /*****************************************************************************/ /* */ /* Function Name : get_mb_info_cavlc */ /* */ /* Description : This function sets the following information of cur MB */ /* (a) mb_x and mb_y */ /* (b) Neighbour availablity */ /* (c) Macroblock location in the frame buffer */ /* (e) For mbaff predicts field/frame u4_flag for topMb */ /* and sets the field/frame for botMb. This is */ /* written in ps_dec->u1_cur_mb_fld_dec_flag */ /* */ /* Inputs : pointer to decstruct */ /* pointer to current mb info */ /* currentMbaddress */ /* */ /* Processing : leftMb and TopMb params are used by DecMbskip and */ /* DecCtxMbfield modules so that these modules do not */ /* check for neigbour availability and then find the */ /* neigbours for context increments */ /* */ /* Returns : OK */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 07 2002 Jay Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_get_mb_info_cavlc_nonmbaff(dec_struct_t *ps_dec, const UWORD16 u2_cur_mb_address, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mbskip_run) { WORD32 mb_x; WORD32 mb_y; UWORD8 u1_mb_ngbr_avail = 0; UWORD16 u2_frm_width_in_mb = ps_dec->u2_frm_wd_in_mbs; WORD16 i2_prev_slice_mbx = ps_dec->i2_prev_slice_mbx; UWORD16 u2_top_right_mask = TOP_RIGHT_DEFAULT_AVAILABLE; UWORD16 u2_top_left_mask = TOP_LEFT_DEFAULT_AVAILABLE; UNUSED(u4_mbskip_run); /*--------------------------------------------------------------------*/ /* Calculate values of mb_x and mb_y */ /*--------------------------------------------------------------------*/ mb_x = (WORD16)ps_dec->u2_mbx; mb_y = (WORD16)ps_dec->u2_mby; ps_dec->u2_cur_mb_addr = u2_cur_mb_address; mb_x++; if(mb_x == u2_frm_width_in_mb) { mb_x = 0; mb_y++; } if(mb_y > ps_dec->i2_prev_slice_mby) { /* if not in the immemdiate row of prev slice end then top will be available */ if(mb_y > (ps_dec->i2_prev_slice_mby + 1)) i2_prev_slice_mbx = -1; if(mb_x > i2_prev_slice_mbx) { u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; } if((mb_x > (i2_prev_slice_mbx - 1)) && (mb_x != (u2_frm_width_in_mb - 1))) { u1_mb_ngbr_avail |= TOP_RIGHT_MB_AVAILABLE_MASK; u2_top_right_mask |= TOP_RIGHT_TOPR_AVAILABLE; } if(mb_x > (i2_prev_slice_mbx + 1)) { u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; } /* Next row Left will be available*/ i2_prev_slice_mbx = -1; } /* Same row */ if(mb_x > (i2_prev_slice_mbx + 1)) { u1_mb_ngbr_avail |= LEFT_MB_AVAILABLE_MASK; u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; } { mb_neigbour_params_t *ps_cur_mb_row = ps_dec->ps_cur_mb_row; mb_neigbour_params_t *ps_top_mb_row = ps_dec->ps_top_mb_row; /* copy the parameters of topleft Mb */ ps_cur_mb_info->u1_topleft_mbtype = ps_dec->u1_topleft_mbtype; /* Neighbour pointer assignments*/ ps_cur_mb_info->ps_curmb = ps_cur_mb_row + mb_x; ps_cur_mb_info->ps_left_mb = ps_cur_mb_row + mb_x - 1; ps_cur_mb_info->ps_top_mb = ps_top_mb_row + mb_x; ps_cur_mb_info->ps_top_right_mb = ps_top_mb_row + mb_x + 1; /* Update the parameters of topleftmb*/ ps_dec->u1_topleft_mbtype = ps_cur_mb_info->ps_top_mb->u1_mb_type; } ps_dec->u2_mby = mb_y; ps_dec->u2_mbx = mb_x; ps_cur_mb_info->u2_mbx = mb_x; ps_cur_mb_info->u2_mby = mb_y; ps_cur_mb_info->u1_topmb = 1; ps_dec->i4_submb_ofst += SUB_BLK_SIZE; ps_dec->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; ps_cur_mb_info->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; ps_cur_mb_info->ps_curmb->u1_mb_fld = ps_dec->u1_cur_mb_fld_dec_flag; ps_cur_mb_info->u1_mb_field_decodingflag = ps_dec->u1_cur_mb_fld_dec_flag; ps_cur_mb_info->u2_top_left_avail_mask = u2_top_left_mask; ps_cur_mb_info->u2_top_right_avail_mask = u2_top_right_mask; return (OK); } /*****************************************************************************/ /* */ /* Function Name : get_mb_info_cavlc */ /* */ /* Description : This function sets the following information of cur MB */ /* (a) mb_x and mb_y */ /* (b) Neighbour availablity */ /* (c) Macroblock location in the frame buffer */ /* (e) For mbaff predicts field/frame u4_flag for topMb */ /* and sets the field/frame for botMb. This is */ /* written in ps_dec->u1_cur_mb_fld_dec_flag */ /* */ /* Inputs : pointer to decstruct */ /* pointer to current mb info */ /* currentMbaddress */ /* */ /* Processing : leftMb and TopMb params are used by DecMbskip and */ /* DecCtxMbfield modules so that these modules do not */ /* check for neigbour availability and then find the */ /* neigbours for context increments */ /* */ /* Returns : OK */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 07 2002 Jay Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_get_mb_info_cavlc_mbaff(dec_struct_t *ps_dec, const UWORD16 u2_cur_mb_address, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mbskip_run) { UWORD16 u2_mb_x; UWORD16 u2_mb_y; UWORD8 u1_mb_ngbr_avail = 0; UWORD16 u2_frm_width_in_mb = ps_dec->u2_frm_wd_in_mbs; UWORD8 u1_top_mb = 1 - (u2_cur_mb_address & 0x01); WORD16 i2_prev_slice_mbx = ps_dec->i2_prev_slice_mbx; UWORD8 u1_cur_mb_field = 0; UWORD16 u2_top_right_mask = TOP_RIGHT_DEFAULT_AVAILABLE; UWORD16 u2_top_left_mask = TOP_LEFT_DEFAULT_AVAILABLE; /*--------------------------------------------------------------------*/ /* Calculate values of mb_x and mb_y */ /*--------------------------------------------------------------------*/ u2_mb_x = ps_dec->u2_mbx; u2_mb_y = ps_dec->u2_mby; ps_dec->u2_cur_mb_addr = u2_cur_mb_address; if(u1_top_mb) { u2_mb_x++; if(u2_mb_x == u2_frm_width_in_mb) { u2_mb_x = 0; u2_mb_y += 2; } if(u2_mb_y > ps_dec->i2_prev_slice_mby) { /* if not in the immemdiate row of prev slice end then top will be available */ if(u2_mb_y > (ps_dec->i2_prev_slice_mby + 2)) i2_prev_slice_mbx = -1; if(u2_mb_x > i2_prev_slice_mbx) { u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; u1_cur_mb_field = ps_dec->ps_top_mb_row[u2_mb_x << 1].u1_mb_fld; u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; } if((u2_mb_x > (i2_prev_slice_mbx - 1)) && (u2_mb_x != (u2_frm_width_in_mb - 1))) { u1_mb_ngbr_avail |= TOP_RIGHT_MB_AVAILABLE_MASK; u2_top_right_mask |= TOP_RIGHT_TOPR_AVAILABLE; } if(u2_mb_x > (i2_prev_slice_mbx + 1)) { u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; } i2_prev_slice_mbx = -1; } /* Same row */ if(u2_mb_x > (i2_prev_slice_mbx + 1)) { u1_mb_ngbr_avail |= LEFT_MB_AVAILABLE_MASK; u1_cur_mb_field = ps_dec->ps_cur_mb_row[(u2_mb_x << 1) - 1].u1_mb_fld; u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; } /* Read u1_cur_mb_field from the bitstream if u4_mbskip_run <= 1*/ if(u4_mbskip_run <= 1) u1_cur_mb_field = (UWORD8)ih264d_get_bit_h264(ps_dec->ps_bitstrm); ps_dec->u1_cur_mb_fld_dec_flag = u1_cur_mb_field; ps_dec->u2_top_left_mask = u2_top_left_mask; ps_dec->u2_top_right_mask = u2_top_right_mask; } else { u1_mb_ngbr_avail = ps_dec->u1_mb_ngbr_availablity; u1_cur_mb_field = ps_dec->u1_cur_mb_fld_dec_flag; u2_top_left_mask = ps_dec->u2_top_left_mask; u2_top_right_mask = ps_dec->u2_top_right_mask; if(!u1_cur_mb_field) { /* Top is available */ u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; /* Top Right not available */ u1_mb_ngbr_avail &= TOP_RT_SUBBLOCK_MASK_MOD; u2_top_right_mask &= (~TOP_RIGHT_TOPR_AVAILABLE); if(u1_mb_ngbr_avail & LEFT_MB_AVAILABLE_MASK) { u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; } } } ps_dec->u2_mby = u2_mb_y; ps_dec->u2_mbx = u2_mb_x; ps_cur_mb_info->u2_mbx = u2_mb_x; ps_cur_mb_info->u2_mby = u2_mb_y; ps_cur_mb_info->u1_topmb = u1_top_mb; ps_dec->i4_submb_ofst += SUB_BLK_SIZE; ps_dec->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; ps_cur_mb_info->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; ps_cur_mb_info->u1_mb_field_decodingflag = u1_cur_mb_field; ps_cur_mb_info->u2_top_left_avail_mask = u2_top_left_mask; ps_cur_mb_info->u2_top_right_avail_mask = u2_top_right_mask; ih264d_get_mbaff_neighbours(ps_dec, ps_cur_mb_info, u1_cur_mb_field); return (OK); } /*****************************************************************************/ /* */ /* Function Name : get_mb_info_cabac */ /* */ /* Description : This function sets the following information of cur MB */ /* (a) mb_x and mb_y */ /* (b) Neighbour availablity */ /* (c) Macroblock location in the frame buffer */ /* (e) leftMb parama and TopMb params of curMB */ /* (f) For Mbaff case leftMb params and TopMb params of */ /* bottomMb are also set if curMB is top */ /* (g) For mbaff predicts field/frame u4_flag for topMb */ /* and sets the field/frame for botMb. This is */ /* written in ps_dec->u1_cur_mb_fld_dec_flag */ /* */ /* Inputs : pointer to decstruct */ /* pointer to current mb info */ /* currentMbaddress */ /* */ /* Processing : leftMb and TopMb params are used by DecMbskip and */ /* DecCtxMbfield modules so that these modules do not */ /* check for neigbour availability and then find the */ /* neigbours for context increments */ /* */ /* Returns : OK */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 07 2002 Jay Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_get_mb_info_cabac_nonmbaff(dec_struct_t *ps_dec, const UWORD16 u2_cur_mb_address, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mbskip) { WORD32 mb_x; WORD32 mb_y; UWORD32 u1_mb_ngbr_avail = 0; UWORD32 u2_frm_width_in_mb = ps_dec->u2_frm_wd_in_mbs; UWORD32 u1_top_mb = 1; WORD32 i2_prev_slice_mbx = ps_dec->i2_prev_slice_mbx; UWORD32 u2_top_right_mask = TOP_RIGHT_DEFAULT_AVAILABLE; UWORD32 u2_top_left_mask = TOP_LEFT_DEFAULT_AVAILABLE; ctxt_inc_mb_info_t * const p_ctx_inc_mb_map = ps_dec->p_ctxt_inc_mb_map; /*--------------------------------------------------------------------*/ /* Calculate values of mb_x and mb_y */ /*--------------------------------------------------------------------*/ mb_x = (WORD16)ps_dec->u2_mbx; mb_y = (WORD16)ps_dec->u2_mby; ps_dec->u2_cur_mb_addr = u2_cur_mb_address; mb_x++; if((UWORD32)mb_x == u2_frm_width_in_mb) { mb_x = 0; mb_y++; } /*********************************************************************/ /* Cabac Context Initialisations */ /*********************************************************************/ ps_dec->ps_curr_ctxt_mb_info = p_ctx_inc_mb_map + mb_x; ps_dec->p_left_ctxt_mb_info = p_ctx_inc_mb_map - 1; ps_dec->p_top_ctxt_mb_info = p_ctx_inc_mb_map - 1; /********************************************************************/ /* neighbour availablility */ /********************************************************************/ if(mb_y > ps_dec->i2_prev_slice_mby) { /* if not in the immemdiate row of prev slice end then top will be available */ if(mb_y > (ps_dec->i2_prev_slice_mby + 1)) i2_prev_slice_mbx = -1; if(mb_x > i2_prev_slice_mbx) { u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; ps_dec->p_top_ctxt_mb_info = ps_dec->ps_curr_ctxt_mb_info; } if((mb_x > (i2_prev_slice_mbx - 1)) && ((UWORD32)mb_x != (u2_frm_width_in_mb - 1))) { u1_mb_ngbr_avail |= TOP_RIGHT_MB_AVAILABLE_MASK; u2_top_right_mask |= TOP_RIGHT_TOPR_AVAILABLE; } if(mb_x > (i2_prev_slice_mbx + 1)) { u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; } /* Next row */ i2_prev_slice_mbx = -1; } /* Same row */ if(mb_x > (i2_prev_slice_mbx + 1)) { u1_mb_ngbr_avail |= LEFT_MB_AVAILABLE_MASK; u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; ps_dec->p_left_ctxt_mb_info = ps_dec->ps_curr_ctxt_mb_info - 1; } { mb_neigbour_params_t *ps_cur_mb_row = ps_dec->ps_cur_mb_row; mb_neigbour_params_t *ps_top_mb_row = ps_dec->ps_top_mb_row; /* copy the parameters of topleft Mb */ ps_cur_mb_info->u1_topleft_mbtype = ps_dec->u1_topleft_mbtype; /* Neighbour pointer assignments*/ ps_cur_mb_info->ps_curmb = ps_cur_mb_row + mb_x; ps_cur_mb_info->ps_left_mb = ps_cur_mb_row + mb_x - 1; ps_cur_mb_info->ps_top_mb = ps_top_mb_row + mb_x; ps_cur_mb_info->ps_top_right_mb = ps_top_mb_row + mb_x + 1; /* Update the parameters of topleftmb*/ ps_dec->u1_topleft_mbtype = ps_cur_mb_info->ps_top_mb->u1_mb_type; } ps_dec->u2_mby = mb_y; ps_dec->u2_mbx = mb_x; ps_cur_mb_info->u2_mbx = mb_x; ps_cur_mb_info->u2_mby = mb_y; ps_cur_mb_info->u1_topmb = u1_top_mb; ps_dec->i4_submb_ofst += SUB_BLK_SIZE; ps_dec->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; ps_cur_mb_info->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; ps_cur_mb_info->ps_curmb->u1_mb_fld = ps_dec->u1_cur_mb_fld_dec_flag; ps_cur_mb_info->u1_mb_field_decodingflag = ps_dec->u1_cur_mb_fld_dec_flag; ps_cur_mb_info->u2_top_left_avail_mask = u2_top_left_mask; ps_cur_mb_info->u2_top_right_avail_mask = u2_top_right_mask; /*********************************************************************/ /* Assign the neigbours */ /*********************************************************************/ if(u4_mbskip) { UWORD32 u4_ctx_inc = 2 - ((!!(ps_dec->p_top_ctxt_mb_info->u1_mb_type & CAB_SKIP_MASK)) + (!!(ps_dec->p_left_ctxt_mb_info->u1_mb_type & CAB_SKIP_MASK))); u4_mbskip = ih264d_decode_bin(u4_ctx_inc, ps_dec->p_mb_skip_flag_t, ps_dec->ps_bitstrm, &ps_dec->s_cab_dec_env); if(!u4_mbskip) { if(!(u1_mb_ngbr_avail & LEFT_MB_AVAILABLE_MASK)) { UWORD32 *pu4_buf; UWORD8 *pu1_buf; pu1_buf = ps_dec->pu1_left_nnz_y; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0; pu1_buf = ps_dec->pu1_left_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0; *(ps_dec->pu1_left_yuv_dc_csbp) = 0; MEMSET_16BYTES(&ps_dec->pu1_left_mv_ctxt_inc[0][0], 0); *(UWORD32 *)ps_dec->pi1_left_ref_idx_ctxt_inc = 0; } if(!(u1_mb_ngbr_avail & TOP_MB_AVAILABLE_MASK)) { MEMSET_16BYTES(ps_dec->ps_curr_ctxt_mb_info->u1_mv, 0); memset(ps_dec->ps_curr_ctxt_mb_info->i1_ref_idx, 0, 4); } } } return (u4_mbskip); } /*****************************************************************************/ /* */ /* Function Name : get_mb_info_cabac */ /* */ /* Description : This function sets the following information of cur MB */ /* (a) mb_x and mb_y */ /* (b) Neighbour availablity */ /* (c) Macroblock location in the frame buffer */ /* (e) leftMb parama and TopMb params of curMB */ /* (f) For Mbaff case leftMb params and TopMb params of */ /* bottomMb are also set if curMB is top */ /* (g) For mbaff predicts field/frame u4_flag for topMb */ /* and sets the field/frame for botMb. This is */ /* written in ps_dec->u1_cur_mb_fld_dec_flag */ /* */ /* Inputs : pointer to decstruct */ /* pointer to current mb info */ /* currentMbaddress */ /* */ /* Processing : leftMb and TopMb params are used by DecMbskip and */ /* DecCtxMbfield modules so that these modules do not */ /* check for neigbour availability and then find the */ /* neigbours for context increments */ /* */ /* Returns : OK */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 07 2002 Jay Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_get_mb_info_cabac_mbaff(dec_struct_t *ps_dec, const UWORD16 u2_cur_mb_address, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mbskip) { WORD32 mb_x; WORD32 mb_y; UWORD8 u1_mb_ngbr_avail = 0; UWORD16 u2_frm_width_in_mb = ps_dec->u2_frm_wd_in_mbs; ctxt_inc_mb_info_t * const p_ctx_inc_mb_map = ps_dec->p_ctxt_inc_mb_map; ctxt_inc_mb_info_t *ps_curr_ctxt, *ps_top_ctxt, *ps_left_ctxt; mb_neigbour_params_t *ps_cur_mb_row = ps_dec->ps_cur_mb_row; mb_neigbour_params_t *ps_top_mb_row = ps_dec->ps_top_mb_row; UWORD32 u4_left_mb_pair_fld = 0; UWORD32 u4_top_mb_pair_fld = 0; UWORD8 u1_cur_mb_field = 0; UWORD8 u1_top_mb = 1 - (u2_cur_mb_address & 0x01); WORD16 i2_prev_slice_mbx = ps_dec->i2_prev_slice_mbx; UWORD16 u2_top_right_mask = TOP_RIGHT_DEFAULT_AVAILABLE; UWORD16 u2_top_left_mask = TOP_LEFT_DEFAULT_AVAILABLE; /*--------------------------------------------------------------------*/ /* Calculate values of mb_x and mb_y */ /*--------------------------------------------------------------------*/ mb_x = (WORD16)ps_dec->u2_mbx; mb_y = (WORD16)ps_dec->u2_mby; ps_dec->u2_cur_mb_addr = u2_cur_mb_address; ps_top_ctxt = ps_left_ctxt = p_ctx_inc_mb_map - 1; if(u1_top_mb) { ctxt_inc_mb_info_t *ps_left_mb_of_bot = ps_left_ctxt; ctxt_inc_mb_info_t *ps_top_mb_of_bot = ps_top_ctxt; mb_x++; if(mb_x == u2_frm_width_in_mb) { mb_x = 0; mb_y += 2; } ps_curr_ctxt = p_ctx_inc_mb_map + (mb_x << 1); if(mb_y > ps_dec->i2_prev_slice_mby) { UWORD8 u1_cur_mb_fld_flag_known = 0; /* Next row */ if(mb_x > 0) { /***********************************************************************/ /* Left Mb is avialable */ /***********************************************************************/ u1_mb_ngbr_avail |= LEFT_MB_AVAILABLE_MASK; ps_left_ctxt = ps_curr_ctxt - 2; ps_left_mb_of_bot = ps_curr_ctxt - 1; u1_cur_mb_field = u4_left_mb_pair_fld = ps_cur_mb_row[(mb_x << 1) - 1].u1_mb_fld; u1_cur_mb_fld_flag_known = 1; u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; } /* if not in the immemdiate row of prev slice end then top will be available */ if(mb_y > (ps_dec->i2_prev_slice_mby + 2)) i2_prev_slice_mbx = -1; if(mb_x > i2_prev_slice_mbx) { /*********************************************************************/ /* Top Mb is avialable */ /*********************************************************************/ u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; /* point to MbAddrB + 1 */ ps_top_ctxt = ps_curr_ctxt + 1; u4_top_mb_pair_fld = ps_top_mb_row[(mb_x << 1)].u1_mb_fld; u1_cur_mb_field = u1_cur_mb_fld_flag_known ? u1_cur_mb_field : u4_top_mb_pair_fld; ps_top_mb_of_bot = u1_cur_mb_field ? ps_top_ctxt : ps_curr_ctxt; /* MbAddrB */ ps_top_ctxt -= (u1_cur_mb_field && u4_top_mb_pair_fld); } if((mb_x > (i2_prev_slice_mbx - 1)) && (mb_x != (u2_frm_width_in_mb - 1))) { u1_mb_ngbr_avail |= TOP_RIGHT_MB_AVAILABLE_MASK; u2_top_right_mask |= TOP_RIGHT_TOPR_AVAILABLE; } if(mb_x > (i2_prev_slice_mbx + 1)) { u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; } } else { /* Same row */ if(mb_x > (i2_prev_slice_mbx + 1)) { /***************************************************************/ /* Left Mb is avialable */ /***************************************************************/ u1_mb_ngbr_avail |= LEFT_MB_AVAILABLE_MASK; u1_cur_mb_field = u4_left_mb_pair_fld = ps_cur_mb_row[(mb_x << 1) - 1].u1_mb_fld; ps_left_ctxt = ps_curr_ctxt - 2; ps_left_mb_of_bot = ps_curr_ctxt - 1; u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; } } /*********************************************************/ /* Check whether the call is from I slice or Inter slice */ /*********************************************************/ if(u4_mbskip) { UWORD32 u4_ctx_inc = 2 - ((!!(ps_top_ctxt->u1_mb_type & CAB_SKIP_MASK)) + (!!(ps_left_ctxt->u1_mb_type & CAB_SKIP_MASK))); dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; decoding_envirnoment_t *ps_cab_dec_env = &ps_dec->s_cab_dec_env; bin_ctxt_model_t *p_mb_skip_flag_t = ps_dec->p_mb_skip_flag_t; ps_dec->u4_next_mb_skip = 0; u4_mbskip = ih264d_decode_bin(u4_ctx_inc, p_mb_skip_flag_t, ps_bitstrm, ps_cab_dec_env); if(u4_mbskip) { UWORD32 u4_next_mbskip; ps_curr_ctxt->u1_mb_type = CAB_SKIP; u4_ctx_inc = 2 - ((!!(ps_top_mb_of_bot->u1_mb_type & CAB_SKIP_MASK)) + (!!(ps_left_mb_of_bot->u1_mb_type & CAB_SKIP_MASK))); /* Decode the skip u4_flag of bottom Mb */ u4_next_mbskip = ih264d_decode_bin(u4_ctx_inc, p_mb_skip_flag_t, ps_bitstrm, ps_cab_dec_env); ps_dec->u4_next_mb_skip = u4_next_mbskip; if(!u4_next_mbskip) { u4_ctx_inc = u4_top_mb_pair_fld + u4_left_mb_pair_fld; u1_cur_mb_field = ih264d_decode_bin( u4_ctx_inc, ps_dec->p_mb_field_dec_flag_t, ps_bitstrm, ps_cab_dec_env); } } } if(!u4_mbskip) { UWORD32 u4_ctx_inc = u4_top_mb_pair_fld + u4_left_mb_pair_fld; u1_cur_mb_field = ih264d_decode_bin(u4_ctx_inc, ps_dec->p_mb_field_dec_flag_t, ps_dec->ps_bitstrm, &ps_dec->s_cab_dec_env); } ps_dec->u1_cur_mb_fld_dec_flag = u1_cur_mb_field; ps_dec->u2_top_left_mask = u2_top_left_mask; ps_dec->u2_top_right_mask = u2_top_right_mask; ps_dec->u2_mby = mb_y; ps_dec->u2_mbx = mb_x; } else { u1_cur_mb_field = ps_dec->u1_cur_mb_fld_dec_flag; u1_mb_ngbr_avail = ps_dec->u1_mb_ngbr_availablity; u2_top_left_mask = ps_dec->u2_top_left_mask; u2_top_right_mask = ps_dec->u2_top_right_mask; ps_curr_ctxt = p_ctx_inc_mb_map + (mb_x << 1) + 1; if(u1_mb_ngbr_avail & LEFT_MB_AVAILABLE_MASK) { u4_left_mb_pair_fld = ps_cur_mb_row[(mb_x << 1) - 1].u1_mb_fld; /* point to A if top else A+1 */ ps_left_ctxt = ps_curr_ctxt - 2 - (u4_left_mb_pair_fld != u1_cur_mb_field); } if(u1_cur_mb_field) { if(u1_mb_ngbr_avail & TOP_MB_AVAILABLE_MASK) { /* point to MbAddrB + 1 */ ps_top_ctxt = ps_curr_ctxt; } } else { /* Top is available */ u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; /* Top Right not available */ u1_mb_ngbr_avail &= TOP_RT_SUBBLOCK_MASK_MOD; u2_top_right_mask &= (~TOP_RIGHT_TOPR_AVAILABLE); if(u1_mb_ngbr_avail & LEFT_MB_AVAILABLE_MASK) { u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; } /* CurMbAddr - 1 */ ps_top_ctxt = ps_curr_ctxt - 1; } if(u4_mbskip) { if(ps_curr_ctxt[-1].u1_mb_type & CAB_SKIP_MASK) { /* If previous mb is skipped, return value of next mb skip */ u4_mbskip = ps_dec->u4_next_mb_skip; } else { /* If previous mb is not skipped then call DecMbSkip */ UWORD32 u4_ctx_inc = 2 - ((!!(ps_top_ctxt->u1_mb_type & CAB_SKIP_MASK)) + (!!(ps_left_ctxt->u1_mb_type & CAB_SKIP_MASK))); u4_mbskip = ih264d_decode_bin(u4_ctx_inc, ps_dec->p_mb_skip_flag_t, ps_dec->ps_bitstrm, &ps_dec->s_cab_dec_env); } } } ps_cur_mb_info->u2_mbx = mb_x; ps_cur_mb_info->u2_mby = mb_y; ps_cur_mb_info->u1_topmb = u1_top_mb; ps_dec->i4_submb_ofst += SUB_BLK_SIZE; ps_dec->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; ps_cur_mb_info->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; ps_cur_mb_info->u1_mb_field_decodingflag = u1_cur_mb_field; ps_cur_mb_info->u2_top_left_avail_mask = u2_top_left_mask; ps_cur_mb_info->u2_top_right_avail_mask = u2_top_right_mask; ih264d_get_mbaff_neighbours(ps_dec, ps_cur_mb_info, u1_cur_mb_field); { ih264d_get_cabac_context_mbaff(ps_dec, ps_cur_mb_info, u4_mbskip); } { bin_ctxt_model_t *p_cabac_ctxt_table_t = ps_dec->p_cabac_ctxt_table_t; if(u1_cur_mb_field) { p_cabac_ctxt_table_t += SIGNIFICANT_COEFF_FLAG_FLD; } else { p_cabac_ctxt_table_t += SIGNIFICANT_COEFF_FLAG_FRAME; } { bin_ctxt_model_t * * p_significant_coeff_flag_t = ps_dec->p_significant_coeff_flag_t; p_significant_coeff_flag_t[0] = p_cabac_ctxt_table_t + SIG_COEFF_CTXT_CAT_0_OFFSET; p_significant_coeff_flag_t[1] = p_cabac_ctxt_table_t + SIG_COEFF_CTXT_CAT_1_OFFSET; p_significant_coeff_flag_t[2] = p_cabac_ctxt_table_t + SIG_COEFF_CTXT_CAT_2_OFFSET; p_significant_coeff_flag_t[3] = p_cabac_ctxt_table_t + SIG_COEFF_CTXT_CAT_3_OFFSET; p_significant_coeff_flag_t[4] = p_cabac_ctxt_table_t + SIG_COEFF_CTXT_CAT_4_OFFSET; p_significant_coeff_flag_t[5] = p_cabac_ctxt_table_t + SIG_COEFF_CTXT_CAT_5_OFFSET; } } return (u4_mbskip); } /*****************************************************************************/ /* */ /* Function Name : ih264d_get_cabac_context_mbaff */ /* */ /* Description : Gets the current macroblock Cabac Context and sets the */ /* top and left cabac context ptrs in CtxIncMbMap */ /* 1. For Coss field left neigbours it alters coded block */ /* u4_flag , motion vectors, reference indices, cbp of */ /* the left neigbours which increases the code i4_size */ /* 2. For Coss field top neigbours it alters motion */ /* vectors reference indices of the top neigbours */ /* which further increases the code i4_size */ /* */ /* Inputs : 1. dec_struct_t */ /* 2. CurMbAddr used for Mbaff (only to see if curMB */ /* is top or bottom) */ /* 3. uc_curMbFldDecFlag only for Mbaff */ /* */ /* Returns : 0 */ /* */ /* Issues : code i4_size can be reduced if ui_CodedBlockFlag storage */ /* structure in context is changed. This change however */ /* would break the parseResidual4x4Cabac asm routine. */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 18 06 2005 Jay */ /* */ /*****************************************************************************/ UWORD32 ih264d_get_cabac_context_mbaff(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD32 u4_mbskip) { const UWORD8 u1_mb_ngbr_availablity = ps_dec->u1_mb_ngbr_availablity; ctxt_inc_mb_info_t * const p_ctx_inc_mb_map = ps_dec->p_ctxt_inc_mb_map; UWORD8 (*pu1_left_mv_ctxt_inc_2d)[4] = &ps_dec->pu1_left_mv_ctxt_inc[0]; WORD8 (*pi1_left_ref_idx_ctxt_inc) = ps_dec->pi1_left_ref_idx_ctxt_inc; const UWORD8 u1_cur_mb_fld_flag = ps_cur_mb_info->u1_mb_field_decodingflag; const UWORD8 u1_topmb = ps_cur_mb_info->u1_topmb; const UWORD8 uc_botMb = 1 - ps_cur_mb_info->u1_topmb; ctxt_inc_mb_info_t * ps_leftMB; ps_dec->ps_curr_ctxt_mb_info = p_ctx_inc_mb_map + (ps_dec->u2_mbx << 1); ps_dec->p_top_ctxt_mb_info = ps_dec->ps_curr_ctxt_mb_info; if(u1_topmb) { pu1_left_mv_ctxt_inc_2d = ps_dec->u1_left_mv_ctxt_inc_arr[0]; pi1_left_ref_idx_ctxt_inc = &ps_dec->i1_left_ref_idx_ctx_inc_arr[0][0]; ps_dec->pu1_left_yuv_dc_csbp = &ps_dec->u1_yuv_dc_csbp_topmb; } else { /* uc_botMb */ pu1_left_mv_ctxt_inc_2d = ps_dec->u1_left_mv_ctxt_inc_arr[1]; pi1_left_ref_idx_ctxt_inc = &ps_dec->i1_left_ref_idx_ctx_inc_arr[1][0]; ps_dec->pu1_left_yuv_dc_csbp = &ps_dec->u1_yuv_dc_csbp_bot_mb; ps_dec->ps_curr_ctxt_mb_info += 1; } ps_dec->pu1_left_mv_ctxt_inc = pu1_left_mv_ctxt_inc_2d; ps_dec->pi1_left_ref_idx_ctxt_inc = pi1_left_ref_idx_ctxt_inc; if(u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK) { const UWORD8 u1_left_mb_fld_flag = ps_cur_mb_info->ps_left_mb->u1_mb_fld; ps_leftMB = ps_dec->ps_curr_ctxt_mb_info - 2; if(u1_left_mb_fld_flag != u1_cur_mb_fld_flag) { ctxt_inc_mb_info_t *ps_tempLeft; UWORD8 u1_cbp_t, u1_cbp_b; UWORD8 u1_cr_cpb; ps_leftMB -= uc_botMb; ps_tempLeft = ps_dec->ps_left_mb_ctxt_info; ps_tempLeft->u1_mb_type = ps_leftMB->u1_mb_type; ps_tempLeft->u1_intra_chroma_pred_mode = ps_leftMB->u1_intra_chroma_pred_mode; ps_tempLeft->u1_transform8x8_ctxt = ps_leftMB->u1_transform8x8_ctxt; u1_cr_cpb = ps_leftMB->u1_cbp; /*****************************************************************/ /* reform RefIdx, CBP, MV and CBF ctxInc taking care of A and A+1*/ /*****************************************************************/ if(u1_cur_mb_fld_flag) { /* current MB is a FLD and left a FRM */ UWORD8 (* const pu1_left_mv_ctxt_inc_2d_arr_top)[4] = ps_dec->u1_left_mv_ctxt_inc_arr[0]; UWORD8 (* const pu1_left_mv_ctxt_inc_2d_arr_bot)[4] = ps_dec->u1_left_mv_ctxt_inc_arr[1]; WORD8 (* const i1_left_ref_idx_ctxt_inc_arr_top) = &ps_dec->i1_left_ref_idx_ctx_inc_arr[0][0]; WORD8 (* const i1_left_ref_idx_ctxt_inc_arr_bot) = &ps_dec->i1_left_ref_idx_ctx_inc_arr[1][0]; u1_cbp_t = ps_leftMB->u1_cbp; u1_cbp_b = (ps_leftMB + 1)->u1_cbp; ps_tempLeft->u1_cbp = (u1_cbp_t & 0x02) | ((u1_cbp_b & 0x02) << 2); // set motionvectors as // 0T = 0T 0B = 0T // 1T = 2T 1B = 2T // 2T = 0B 2B = 0B // 3T = 2B 3B = 2B if(u1_topmb) { /********************************************/ /* Bottoms DC CBF = Top DC CBF */ /********************************************/ ps_dec->u1_yuv_dc_csbp_bot_mb = ps_dec->u1_yuv_dc_csbp_topmb; *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[3] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d_arr_bot[2]; *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[1] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d_arr_top[2]; *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[2] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d_arr_bot[0]; *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[0] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d_arr_top[0]; i1_left_ref_idx_ctxt_inc_arr_top[1] = i1_left_ref_idx_ctxt_inc_arr_bot[0]; i1_left_ref_idx_ctxt_inc_arr_top[3] = i1_left_ref_idx_ctxt_inc_arr_bot[2]; *(UWORD32 *)(i1_left_ref_idx_ctxt_inc_arr_bot) = *(UWORD32 *)(i1_left_ref_idx_ctxt_inc_arr_top); memcpy(pu1_left_mv_ctxt_inc_2d_arr_bot, pu1_left_mv_ctxt_inc_2d_arr_top, 16); } { UWORD8 i; for(i = 0; i < 4; i++) { pu1_left_mv_ctxt_inc_2d[i][1] >>= 1; pu1_left_mv_ctxt_inc_2d[i][3] >>= 1; } } } else { /* current MB is a FRM and left FLD */ if(u1_topmb) { u1_cbp_t = ps_leftMB->u1_cbp; u1_cbp_t = (u1_cbp_t & 0x02); ps_tempLeft->u1_cbp = (u1_cbp_t | (u1_cbp_t << 2)); /********************************************/ /* Bottoms DC CBF = Top DC CBF */ /********************************************/ ps_dec->u1_yuv_dc_csbp_bot_mb = ps_dec->u1_yuv_dc_csbp_topmb; // set motionvectors as // 3B = 2B = 3T // 1B = 0B = 2T // 3T = 2T = 1T // 1T = 0T = 0T *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[7] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[3]; *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[6] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[3]; *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[5] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[2]; *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[4] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[2]; *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[3] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[1]; *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[2] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[1]; *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[1] = *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[0]; pi1_left_ref_idx_ctxt_inc[7] = (pi1_left_ref_idx_ctxt_inc[3] - 1); pi1_left_ref_idx_ctxt_inc[6] = (pi1_left_ref_idx_ctxt_inc[3] - 1); pi1_left_ref_idx_ctxt_inc[5] = (pi1_left_ref_idx_ctxt_inc[1] - 1); pi1_left_ref_idx_ctxt_inc[4] = (pi1_left_ref_idx_ctxt_inc[1] - 1); pi1_left_ref_idx_ctxt_inc[3] = (pi1_left_ref_idx_ctxt_inc[2] - 1); pi1_left_ref_idx_ctxt_inc[2] = (pi1_left_ref_idx_ctxt_inc[2] - 1); pi1_left_ref_idx_ctxt_inc[1] = (pi1_left_ref_idx_ctxt_inc[0] - 1); pi1_left_ref_idx_ctxt_inc[0] = (pi1_left_ref_idx_ctxt_inc[0] - 1); } else { u1_cbp_t = ps_leftMB->u1_cbp; u1_cbp_t = (u1_cbp_t & 0x08); ps_tempLeft->u1_cbp = (u1_cbp_t | (u1_cbp_t >> 2)); } { UWORD8 i; for(i = 0; i < 4; i++) { pu1_left_mv_ctxt_inc_2d[i][1] <<= 1; pu1_left_mv_ctxt_inc_2d[i][3] <<= 1; } } } ps_tempLeft->u1_cbp = ps_tempLeft->u1_cbp + ((u1_cr_cpb >> 4) << 4); ps_leftMB = ps_tempLeft; } ps_dec->p_left_ctxt_mb_info = ps_leftMB; } else { ps_dec->p_left_ctxt_mb_info = p_ctx_inc_mb_map - 1; if(!u4_mbskip) { *(ps_dec->pu1_left_yuv_dc_csbp) = 0; MEMSET_16BYTES(&pu1_left_mv_ctxt_inc_2d[0][0], 0); *(UWORD32 *)pi1_left_ref_idx_ctxt_inc = 0; } } /*************************************************************************/ /* Now get the top context mb info */ /*************************************************************************/ { UWORD8 (*u1_top_mv_ctxt_inc_arr_2d)[4] = ps_dec->ps_curr_ctxt_mb_info->u1_mv; WORD8 (*pi1_top_ref_idx_ctxt_inc) = ps_dec->ps_curr_ctxt_mb_info->i1_ref_idx; UWORD8 uc_topMbFldDecFlag = ps_cur_mb_info->ps_top_mb->u1_mb_fld; if(u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK) { if(ps_cur_mb_info->i1_offset) ps_dec->p_top_ctxt_mb_info += 1; if(!u4_mbskip) { memcpy(u1_top_mv_ctxt_inc_arr_2d, &ps_dec->p_top_ctxt_mb_info->u1_mv, 16); memcpy(pi1_top_ref_idx_ctxt_inc, &ps_dec->p_top_ctxt_mb_info->i1_ref_idx, 4); if(uc_topMbFldDecFlag ^ u1_cur_mb_fld_flag) { UWORD8 i; if(u1_cur_mb_fld_flag) { for(i = 0; i < 4; i++) { u1_top_mv_ctxt_inc_arr_2d[i][1] >>= 1; u1_top_mv_ctxt_inc_arr_2d[i][3] >>= 1; } } else { for(i = 0; i < 4; i++) { u1_top_mv_ctxt_inc_arr_2d[i][1] <<= 1; u1_top_mv_ctxt_inc_arr_2d[i][3] <<= 1; pi1_top_ref_idx_ctxt_inc[i] -= 1; } } } } } else { ps_dec->p_top_ctxt_mb_info = p_ctx_inc_mb_map - 1; if(!u4_mbskip) { MEMSET_16BYTES(&u1_top_mv_ctxt_inc_arr_2d[0][0], 0); memset(pi1_top_ref_idx_ctxt_inc, 0, 4); } } } return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_update_mbaff_left_nnz */ /* */ /* Description : This function updates the left luma and chroma nnz for */ /* mbaff cases. */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 07 2002 Ittiam Draft */ /* */ /*****************************************************************************/ void ih264d_update_mbaff_left_nnz(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info) { UWORD32 *pu4_buf; UWORD8 *pu1_buf; if(ps_cur_mb_info->u1_topmb) { pu1_buf = ps_dec->pu1_left_nnz_y; pu4_buf = (UWORD32 *)pu1_buf; ps_dec->u4_n_left_temp_y = *pu4_buf; pu1_buf = ps_dec->pu1_left_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; ps_dec->u4_n_left_temp_uv = *pu4_buf; } else { ps_dec->u4_n_leftY[0] = ps_dec->u4_n_left_temp_y; pu1_buf = ps_dec->pu1_left_nnz_y; pu4_buf = (UWORD32 *)pu1_buf; ps_dec->u4_n_leftY[1] = *pu4_buf; ps_dec->u4_n_left_cr[0] = ps_dec->u4_n_left_temp_uv; pu1_buf = ps_dec->pu1_left_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; ps_dec->u4_n_left_cr[1] = *pu4_buf; } } /*! ************************************************************************** * \if Function name : ih264d_get_mbaff_neighbours \endif * * \brief * Gets the neighbors for the current MB if it is of type MB-AFF * frame. * * \return * None * ************************************************************************** */ void ih264d_get_mbaff_neighbours(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 uc_curMbFldDecFlag) { mb_neigbour_params_t *ps_left_mb; mb_neigbour_params_t *ps_top_mb; mb_neigbour_params_t *ps_top_right_mb = NULL; mb_neigbour_params_t *ps_curmb; const UWORD8 u1_topmb = ps_cur_mb_info->u1_topmb; const UWORD8 uc_botMb = 1 - u1_topmb; const UWORD32 u4_mb_x = ps_cur_mb_info->u2_mbx; /* Current MbParams location in top row buffer */ ps_curmb = ps_dec->ps_cur_mb_row + (u4_mb_x << 1) + uc_botMb; ps_left_mb = ps_curmb - 2; /* point to A if top else A+1 */ if(uc_botMb && (ps_left_mb->u1_mb_fld != uc_curMbFldDecFlag)) { /* move from A + 1 to A */ ps_left_mb--; } ps_cur_mb_info->i1_offset = 0; if((uc_curMbFldDecFlag == 0) && uc_botMb) { mb_neigbour_params_t *ps_topleft_mb; /* CurMbAddr - 1 */ ps_top_mb = ps_curmb - 1; /* Mark Top right Not available */ /* point to A */ ps_topleft_mb = ps_curmb - 3; if(ps_topleft_mb->u1_mb_fld) { /* point to A + 1 */ ps_topleft_mb++; } ps_cur_mb_info->u1_topleft_mb_fld = ps_topleft_mb->u1_mb_fld; ps_cur_mb_info->u1_topleft_mbtype = ps_topleft_mb->u1_mb_type; } else { /* Top = B + 1 */ ps_top_mb = ps_dec->ps_top_mb_row + (u4_mb_x << 1) + 1; ps_top_right_mb = ps_top_mb + 2; ps_cur_mb_info->i1_offset = 4; /* TopRight = C + 1 */ /* TopLeft = D+1 */ ps_cur_mb_info->u1_topleft_mb_fld = ps_dec->u1_topleft_mb_fld_bot; ps_cur_mb_info->u1_topleft_mbtype = ps_dec->u1_topleft_mbtype_bot; if(uc_curMbFldDecFlag && u1_topmb) { if(ps_top_mb->u1_mb_fld) { /* MbAddrB */ ps_top_mb--; ps_cur_mb_info->i1_offset = 0; } /* If topright is field then point to C */ ps_top_right_mb -= ps_top_right_mb->u1_mb_fld ? 1 : 0; if(ps_cur_mb_info->u1_topleft_mb_fld) { /* TopLeft = D */ ps_cur_mb_info->u1_topleft_mb_fld = ps_dec->u1_topleft_mb_fld; ps_cur_mb_info->u1_topleft_mbtype = ps_dec->u1_topleft_mbtype; } } } if(u1_topmb) { /* Update the parameters of topleftmb*/ ps_dec->u1_topleft_mb_fld = ps_top_mb->u1_mb_fld; ps_dec->u1_topleft_mbtype = ps_top_mb->u1_mb_type; /* Set invscan and dequantMatrixScan*/ if(uc_curMbFldDecFlag) { ps_dec->pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_fld; } else { ps_dec->pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; } ps_dec->pu2_quant_scale_y = gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_y_rem6]; ps_dec->pu2_quant_scale_u = gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_u_rem6]; ps_dec->pu2_quant_scale_v = gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_v_rem6]; } else { /* Update the parameters of topleftmb*/ mb_neigbour_params_t *ps_top_mb_temp = ps_dec->ps_top_mb_row + (u4_mb_x << 1) + 1; ps_dec->u1_topleft_mb_fld_bot = ps_top_mb_temp->u1_mb_fld; ps_dec->u1_topleft_mbtype_bot = ps_top_mb_temp->u1_mb_type; } ps_cur_mb_info->ps_left_mb = ps_left_mb; ps_cur_mb_info->ps_top_mb = ps_top_mb; ps_cur_mb_info->ps_top_right_mb = ps_top_right_mb; ps_cur_mb_info->ps_curmb = ps_curmb; ps_curmb->u1_mb_fld = uc_curMbFldDecFlag; { /* Form Left NNZ */ UWORD8 u1_is_left_mb_fld = ps_left_mb->u1_mb_fld; UWORD8 *pu1_left_mb_pair_nnz_y = (UWORD8 *)&ps_dec->u4_n_leftY[0]; UWORD8 *pu1_left_mb_pair_nnz_uv = (UWORD8 *)&ps_dec->u4_n_left_cr[0]; UWORD8 *pu1_left_nnz_y = ps_dec->pu1_left_nnz_y; UWORD8 *pu1_left_nnz_uv = ps_dec->pu1_left_nnz_uv; if(uc_curMbFldDecFlag == u1_is_left_mb_fld) { *(UWORD32 *)pu1_left_nnz_y = *(UWORD32 *)(pu1_left_mb_pair_nnz_y + (uc_botMb << 2)); *(UWORD32 *)pu1_left_nnz_uv = *(UWORD32 *)(pu1_left_mb_pair_nnz_uv + (uc_botMb << 2)); } else if((uc_curMbFldDecFlag == 0) && u1_topmb && u1_is_left_mb_fld) { /* 0 0 1 1 of u4_n_leftY[0], 0 0 2 2 of u4_n_left_cr[0] */ pu1_left_nnz_y[0] = pu1_left_nnz_y[1] = pu1_left_mb_pair_nnz_y[0]; pu1_left_nnz_y[2] = pu1_left_nnz_y[3] = pu1_left_mb_pair_nnz_y[1]; pu1_left_nnz_uv[0] = pu1_left_nnz_uv[1] = pu1_left_mb_pair_nnz_uv[0]; pu1_left_nnz_uv[2] = pu1_left_nnz_uv[3] = pu1_left_mb_pair_nnz_uv[2]; } else if((uc_curMbFldDecFlag == 0) && uc_botMb && u1_is_left_mb_fld) { /* 2 2 3 3 of u4_n_leftY[0] , 1 1 3 3 of u4_n_left_cr[0] */ pu1_left_nnz_y[0] = pu1_left_nnz_y[1] = pu1_left_mb_pair_nnz_y[2]; pu1_left_nnz_y[2] = pu1_left_nnz_y[3] = pu1_left_mb_pair_nnz_y[3]; pu1_left_nnz_uv[0] = pu1_left_nnz_uv[1] = pu1_left_mb_pair_nnz_uv[1]; pu1_left_nnz_uv[2] = pu1_left_nnz_uv[3] = pu1_left_mb_pair_nnz_uv[3]; } else { /* 0 2 0 2 of u4_n_leftY[0], u4_n_leftY[1] */ pu1_left_nnz_y[0] = pu1_left_mb_pair_nnz_y[0]; pu1_left_nnz_y[1] = pu1_left_mb_pair_nnz_y[2]; pu1_left_nnz_y[2] = pu1_left_mb_pair_nnz_y[4 + 0]; pu1_left_nnz_y[3] = pu1_left_mb_pair_nnz_y[4 + 2]; /* 0 of u4_n_left_cr[0] and 0 u4_n_left_cr[1] 2 of u4_n_left_cr[0] and 2 u4_n_left_cr[1] */ pu1_left_nnz_uv[0] = pu1_left_mb_pair_nnz_uv[0]; pu1_left_nnz_uv[1] = pu1_left_mb_pair_nnz_uv[4 + 0]; pu1_left_nnz_uv[2] = pu1_left_mb_pair_nnz_uv[2]; pu1_left_nnz_uv[3] = pu1_left_mb_pair_nnz_uv[4 + 2]; } } } /* ************************************************************************** * \if Function name : ih264d_transfer_mb_group_data \endif * * \brief * Transfer the Following things * N-Mb DeblkParams Data ( To Ext DeblkParams Buffer ) * N-Mb Recon Data ( To Ext Frame Buffer ) * N-Mb Intrapredline Data ( Updated Internally) * N-Mb MV Data ( To Ext MV Buffer ) * N-Mb MVTop/TopRight Data ( To Int MV Top Scratch Buffers) * * \return * None * ************************************************************************** */ void ih264d_transfer_mb_group_data(dec_struct_t * ps_dec, const UWORD8 u1_num_mbs, const UWORD8 u1_end_of_row, /* Cur n-Mb End of Row Flag */ const UWORD8 u1_end_of_row_next /* Next n-Mb End of Row Flag */ ) { dec_mb_info_t *ps_cur_mb_info = ps_dec->ps_nmb_info; tfr_ctxt_t *ps_trns_addr = &ps_dec->s_tran_addrecon; UWORD16 u2_mb_y; UWORD32 y_offset; UWORD32 u4_frame_stride; mb_neigbour_params_t *ps_temp; const UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; UNUSED(u1_end_of_row_next); ps_trns_addr->pu1_dest_y += ps_trns_addr->u4_inc_y[u1_end_of_row]; ps_trns_addr->pu1_dest_u += ps_trns_addr->u4_inc_uv[u1_end_of_row]; ps_trns_addr->pu1_dest_v += ps_trns_addr->u4_inc_uv[u1_end_of_row]; /* Swap top and current pointers */ if(u1_end_of_row) { if(ps_dec->u1_separate_parse) { u2_mb_y = ps_dec->i2_dec_thread_mb_y; } else { ps_temp = ps_dec->ps_cur_mb_row; ps_dec->ps_cur_mb_row = ps_dec->ps_top_mb_row; ps_dec->ps_top_mb_row = ps_temp; u2_mb_y = ps_dec->u2_mby + (1 + u1_mbaff); } u4_frame_stride = ps_dec->u2_frm_wd_y << ps_dec->ps_cur_slice->u1_field_pic_flag; y_offset = (u2_mb_y * u4_frame_stride) << 4; ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + y_offset; u4_frame_stride = ps_dec->u2_frm_wd_uv << ps_dec->ps_cur_slice->u1_field_pic_flag; y_offset = (u2_mb_y * u4_frame_stride) << 3; ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + y_offset; ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + y_offset; ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; } /* * The Slice boundary is also a valid condition to transfer. So recalculate * the Left increment, in case the number of MBs is lesser than the * N MB value. u1_num_mbs will be equal to N of N MB if the entire N Mb is * decoded. */ ps_dec->s_tran_addrecon.u2_mv_left_inc = ((u1_num_mbs >> u1_mbaff) - 1) << (4 + u1_mbaff); ps_dec->s_tran_addrecon.u2_mv_top_left_inc = (u1_num_mbs << 2) - 1 - (u1_mbaff << 2); if(ps_dec->u1_separate_parse == 0) { /* reassign left MV and cur MV pointers */ ps_dec->ps_mv_left = ps_dec->ps_mv_cur + ps_dec->s_tran_addrecon.u2_mv_left_inc; ps_dec->ps_mv_cur += (u1_num_mbs << 4); } /* Increment deblock parameters pointer in external memory */ if(ps_dec->u1_separate_parse == 0) { ps_dec->ps_deblk_mbn += u1_num_mbs; } } ================================================ FILE: dependencies/ih264d/decoder/ih264d_mb_utils.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_MB_UTILS_H_ #define _IH264D_MB_UTILS_H_ /*! ************************************************************************** * \file ih264d_mb_utils.h * * \brief * Contains declarations of the utility functions needed to decode MB * * \date * 18/12/2002 * * \author AI ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" /*--------------------------------------------------------------------*/ /* Macros to get raster scan position of a block[8x8] / sub block[4x4]*/ /*--------------------------------------------------------------------*/ #define GET_BLK_RASTER_POS_X(x) ((x & 0x01) << 1) #define GET_BLK_RASTER_POS_Y(y) ((y >> 1) << 1) #define GET_SUB_BLK_RASTER_POS_X(x) ((x & 0x01)) #define GET_SUB_BLK_RASTER_POS_Y(y) ((y >> 1)) /*--------------------------------------------------------------------*/ /* Masks used in decoding of Macroblock */ /*--------------------------------------------------------------------*/ #define LEFT_MB_AVAILABLE_MASK 0x01 #define TOP_LEFT_MB_AVAILABLE_MASK 0x02 #define TOP_MB_AVAILABLE_MASK 0x04 #define TOP_RIGHT_MB_AVAILABLE_MASK 0x08 #define TOP_RT_SUBBLOCK_MASK_MOD 0xFFF7 #define TOP_RIGHT_DEFAULT_AVAILABLE 0x5750 #define TOP_RIGHT_TOPR_AVAILABLE 0x0008 #define TOP_RIGHT_TOP_AVAILABLE 0x0007 #define TOP_LEFT_DEFAULT_AVAILABLE 0xEEE0 #define TOP_LEFT_TOPL_AVAILABLE 0x0001 #define TOP_LEFT_TOP_AVAILABLE 0x000E #define TOP_LEFT_LEFT_AVAILABLE 0x1110 #define CHECK_MB_MAP(u4_mb_num, mb_map, u4_cond) \ { \ UWORD32 u4_bit_number; \ volatile UWORD8 *pu1_mb_flag; \ \ u4_bit_number = u4_mb_num & 0x07; \ pu1_mb_flag = (UWORD8 *)mb_map + (u4_mb_num >> 3); \ \ u4_cond = CHECKBIT((*pu1_mb_flag), u4_bit_number); \ } #define CHECK_MB_MAP_BYTE(u4_mb_num, mb_map, u4_cond) \ { \ volatile UWORD8 *pu1_mb_flag; \ \ pu1_mb_flag = (UWORD8 *)mb_map + (u4_mb_num ); \ \ u4_cond = (*pu1_mb_flag); \ } #define UPDATE_MB_MAP(u2_frm_wd_in_mbs, u2_mbx, u2_mby, mb_map, mb_count) \ { \ UWORD32 u4_bit_number; \ UWORD32 u4_mb_number; \ \ u4_mb_number = u2_frm_wd_in_mbs * (u2_mby >> u1_mbaff) + u2_mbx; \ \ u4_bit_number = u4_mb_number & 0x07; \ /* \ * In case of MbAff, update the mb_map only if the entire MB is done. We can check that \ * by checking if Y is odd, implying that this is the second row in the MbAff MB \ */ \ SET_BIT(mb_map[u4_mb_number >> 3], u4_bit_number); \ \ if (1 == u1_mbaff) \ { \ /* \ * If MBAFF u4_flag is set, set this MB and the MB just below this. \ * So, add frame width to the MB number and set that bit. \ */ \ /* \ u4_mb_number += u2_frm_wd_in_mbs; \ \ u4_bit_number = u4_mb_number & 0x07; \ \ SET_BIT(mb_map[u4_mb_number >> 3], u4_bit_number); \ */ \ } \ \ /*H264_DEC_DEBUG_PRINT("SETBIT: %d\n", u4_mb_number);*/ \ mb_count++; \ } #define UPDATE_MB_MAP_MBNUM(mb_map, u4_mb_number) \ { \ UWORD32 u4_bit_number; \ volatile UWORD8 *pu1_mb_flag; \ \ u4_bit_number = u4_mb_number & 0x07; \ pu1_mb_flag = (UWORD8 *)mb_map + (u4_mb_number >> 3); \ /* \ * In case of MbAff, update the mb_map only if the entire MB is done. We can check that \ * by checking if Y is odd, implying that this is the second row in the MbAff MB \ */ \ SET_BIT((*pu1_mb_flag), u4_bit_number); \ } #define UPDATE_MB_MAP_MBNUM_BYTE(mb_map, u4_mb_number) \ { \ volatile UWORD8 *pu1_mb_flag; \ \ pu1_mb_flag = (UWORD8 *)mb_map + (u4_mb_number); \ /* \ * In case of MbAff, update the mb_map only if the entire MB is done. We can check that \ * by checking if Y is odd, implying that this is the second row in the MbAff MB \ */ \ (*pu1_mb_flag) = 1; \ } #define UPDATE_SLICE_NUM_MAP(slice_map, u4_mb_number,u2_slice_num) \ { \ volatile UWORD16 *pu2_slice_map; \ \ pu2_slice_map = (UWORD16 *)slice_map + (u4_mb_number); \ (*pu2_slice_map) = u2_slice_num; \ } #define GET_SLICE_NUM_MAP(slice_map, mb_number,u2_slice_num) \ { \ volatile UWORD16 *pu2_slice_map; \ \ pu2_slice_map = (UWORD16 *)slice_map + (mb_number); \ u2_slice_num = (*pu2_slice_map) ; \ } #define GET_XPOS_PRED(u1_out,pkd_info) \ { \ WORD32 bit_field; \ bit_field = pkd_info & 0x3; \ u1_out = bit_field; \ } #define GET_YPOS_PRED(u1_out,pkd_info) \ { \ WORD32 bit_field; \ bit_field = pkd_info >> 2; \ u1_out = bit_field & 0x3; \ } #define GET_WIDTH_PRED(u1_out,pkd_info) \ { \ WORD32 bit_field; \ bit_field = pkd_info >> 4; \ bit_field = (bit_field & 0x3) << 1 ; \ u1_out = (bit_field == 0)?1:bit_field; \ } #define GET_HEIGHT_PRED(u1_out,pkd_info) \ { \ WORD32 bit_field; \ bit_field = pkd_info >> 6; \ bit_field = (bit_field & 0x3) << 1 ; \ u1_out = (bit_field == 0)?1:bit_field; \ } /*! ************************************************************************** * \brief Masks for elements present in the first column but not on the * first row. ************************************************************************** */ #define FIRST_COL_NOT_FIRST_ROW 0xFAFB #define FIRST_ROW_MASK 0xFFCC /*! ************************************************************************** * \brief Mask for elements presen in the first row but not in the * last column. ************************************************************************** */ #define FIRST_ROW_NOT_LAST_COL 0xFFEC /*! ************************************************************************** * \brief Mask for elements presen in the first row but not in the * first column. ************************************************************************** */ #define FIRST_ROW_NOT_FIRST_COL 0xFFCD /*! ************************************************************************** * \brief Masks for the top right subMB of a 4x4 block ************************************************************************** */ #define TOP_RT_SUBBLOCK_MASK 0xFFDF /*! ************************************************************************** * \brief Masks for the top left subMB of a 4x4 block ************************************************************************** */ #define TOP_LT_SUBBLOCK_MASK 0xFFFE /*! ************************************************************************** * \brief Indicates if a subMB has a top right subMB available ************************************************************************** */ #define TOP_RT_SUBBLOCK_MB_MASK 0x5F4C #define FIRST_COL_MASK 0xFAFA /*--------------------------------------------------------------------*/ /* Macros to calculate the current position of a MB wrt picture */ /*--------------------------------------------------------------------*/ #define MB_LUMA_PIC_OFFSET(mb_x,mb_y,frmWidthY) (((mb_y)*(frmWidthY) + (mb_x))<<4) #define MB_CHROMA_PIC_OFFSET(mb_x,mb_y,frmWidthUV) (((mb_y)*(frmWidthUV) + (mb_x))<<3) /*--------------------------------------------------------------------*/ /* Macros to calculate the current position of a MB wrt N[ Num coeff] Array */ /*--------------------------------------------------------------------*/ #define MB_PARAM_OFFSET(mb_x,mb_y,frmWidthInMbs,u1_mbaff,u1_topmb) \ ((mb_x << u1_mbaff) + (1 - u1_topmb) + (mb_y * frmWidthInMbs)) UWORD32 ih264d_get_mb_info_cavlc_mbaff(dec_struct_t * ps_dec, const UWORD16 ui16_curMbAddress, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mbskip_run); UWORD32 ih264d_get_mb_info_cavlc_nonmbaff(dec_struct_t * ps_dec, const UWORD16 ui16_curMbAddress, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mbskip_run); UWORD32 ih264d_get_mb_info_cabac_mbaff(dec_struct_t * ps_dec, const UWORD16 ui16_curMbAddress, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mbskip_run); UWORD32 ih264d_get_mb_info_cabac_nonmbaff(dec_struct_t * ps_dec, const UWORD16 ui16_curMbAddress, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mbskip_run); UWORD8 get_cabac_context_non_mbaff(dec_struct_t * ps_dec, UWORD16 u2_mbskip); UWORD32 ih264d_get_cabac_context_mbaff(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mbskip); WORD32 PutMbToFrame(dec_struct_t * ps_dec); void ih264d_get_mbaff_neighbours(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 uc_curMbFldDecFlag); void ih264d_update_mbaff_left_nnz(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info); void ih264d_transfer_mb_group_data(dec_struct_t * ps_dec, const UWORD8 u1_num_mbs, const UWORD8 u1_end_of_row, /* Cur n-Mb End of Row Flag */ const UWORD8 u1_end_of_row_next /* Next n-Mb End of Row Flag */ ); //void FillRandomData(UWORD8 *pu1_buf, WORD32 u4_bufSize); #endif /* _MB_UTILS_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_mem_request.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_MEM_REQUEST_H_ #define _IH264D_MEM_REQUEST_H_ /*! *************************************************************************** * \file ih264d_mem_request.h * * \brief * This file contains declarations and data structures of the API's which * required to interact with Picture Buffer. * * * \date * 11/12/2002 * * \author NS ***************************************************************************/ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_defs.h" #include "ih264d_structs.h" #define MAX_MEM_BLOCKS 64 + 8 struct MemBlock { void ** v_memLocation; /** memory location where address of allocated memory should be stored*/ UWORD32 u4_mem_size; /** Size of the memory block */ }; struct MemReq { UWORD32 u4_num_memBlocks; /** Number of memory blocks */ struct MemBlock s_memBlock[MAX_MEM_BLOCKS]; /** Pointer to the first memory block */ }; struct PicMemBlock { void * buf1; /** memory location for buf1 */ void * buf2; /** memory location for buf2 */ void * buf3; /** memory location for buf3 */ }; struct PicMemReq { WORD32 i4_num_pic_memBlocks; /** Number of memory blocks */ UWORD32 u4_size1; /** Size of the buf1 in PicMemBlock */ UWORD32 u4_size2; /** Size of the buf2 in PicMemBlock */ UWORD32 u4_size3; /** Size of the buf3 in PicMemBlock */ struct PicMemBlock s_PicMemBlock[MAX_DISP_BUFS_NEW]; }; WORD32 ih264d_create_pic_buffers(UWORD8 u1_num_of_buf, dec_struct_t *ps_dec); WORD32 ih264d_create_mv_bank(void * pv_codec_handle, UWORD32 u4_wd, UWORD32 u4_ht); WORD16 ih264d_allocate_dynamic_bufs(dec_struct_t * ps_dec); #endif /* _IH264D_MEM_REQUEST_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_mvpred.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_mvpred.c * * \brief * This file contains function specific to decoding Motion vector. * * Detailed_description * * \date * 10-12-2002 * * \author Arvind Raman ************************************************************************** */ #include <string.h> #include "ih264d_parse_cavlc.h" #include "ih264d_error_handler.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_mb_utils.h" #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_process_bslice.h" #include "ih264d_mvpred.h" #include "ih264d_inter_pred.h" #include "ih264d_tables.h" /*! ************************************************************************** * \if ih264d_get_motion_vector_predictor name : Name \endif * * \brief * The routine calculates the motion vector predictor for a given block, * given the candidate MV predictors. * * \param ps_mv_pred: Candidate predictors for the current block * \param ps_currMv: Pointer to the left top edge of the current block in * the MV bank * * \return * _mvPred: The x & y components of the MV predictor. * * \note * The code implements the logic as described in sec 8.4.1.2.1. Given * the candidate predictors and the pointer to the top left edge of the * block in the MV bank. * ************************************************************************** */ void ih264d_get_motion_vector_predictor(mv_pred_t * ps_result, mv_pred_t **ps_mv_pred, UWORD8 u1_ref_idx, UWORD8 u1_B, const UWORD8 *pu1_mv_pred_condition) { WORD8 c_temp; UWORD8 uc_B2 = (u1_B << 1); /* If only one of the candidate blocks has a reference frame equal to the current block then use the same block as the final predictor */ c_temp = (ps_mv_pred[LEFT]->i1_ref_frame[u1_B] == u1_ref_idx) | ((ps_mv_pred[TOP]->i1_ref_frame[u1_B] == u1_ref_idx) << 1) | ((ps_mv_pred[TOP_R]->i1_ref_frame[u1_B] == u1_ref_idx) << 2); c_temp = pu1_mv_pred_condition[c_temp]; if(c_temp != -1) { /* Case when only when one of the cadidate block has the same reference frame as the current block */ ps_result->i2_mv[uc_B2 + 0] = ps_mv_pred[c_temp]->i2_mv[uc_B2 + 0]; ps_result->i2_mv[uc_B2 + 1] = ps_mv_pred[c_temp]->i2_mv[uc_B2 + 1]; } else { WORD32 D0, D1; D0 = MIN(ps_mv_pred[0]->i2_mv[uc_B2 + 0], ps_mv_pred[1]->i2_mv[uc_B2 + 0]); D1 = MAX(ps_mv_pred[0]->i2_mv[uc_B2 + 0], ps_mv_pred[1]->i2_mv[uc_B2 + 0]); D1 = MIN(D1, ps_mv_pred[2]->i2_mv[uc_B2 + 0]); ps_result->i2_mv[uc_B2 + 0] = (WORD16)(MAX(D0, D1)); D0 = MIN(ps_mv_pred[0]->i2_mv[uc_B2 + 1], ps_mv_pred[1]->i2_mv[uc_B2 + 1]); D1 = MAX(ps_mv_pred[0]->i2_mv[uc_B2 + 1], ps_mv_pred[1]->i2_mv[uc_B2 + 1]); D1 = MIN(D1, ps_mv_pred[2]->i2_mv[uc_B2 + 1]); ps_result->i2_mv[uc_B2 + 1] = (WORD16)(MAX(D0, D1)); } } /*! ************************************************************************** * \if ih264d_mbaff_mv_pred name : Name \endif * * \brief * The routine calculates the motion vector predictor for a given block, * given the candidate MV predictors. * * \param ps_mv_pred: Candidate predictors for the current block * \param ps_currMv: Pointer to the left top edge of the current block in * the MV bank * * \return * _mvPred: The x & y components of the MV predictor. * * \note * The code implements the logic as described in sec 8.4.1.2.1. Given * the candidate predictors and the pointer to the top left edge of the * block in the MV bank. * ************************************************************************** */ void ih264d_mbaff_mv_pred(mv_pred_t **ps_mv_pred, UWORD8 u1_sub_mb_num, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, dec_struct_t *ps_dec, UWORD8 uc_mb_part_width, dec_mb_info_t *ps_cur_mb_info, UWORD8* pu0_scale) { UWORD16 u2_a_in = 0, u2_b_in = 0, u2_c_in = 0, u2_d_in = 0; mv_pred_t *ps_mvpred_l, *ps_mvpred_tmp; UWORD8 u1_sub_mb_x = (u1_sub_mb_num & 3), uc_sub_mb_y = (u1_sub_mb_num >> 2); UWORD8 u1_is_cur_mb_fld, u1_is_left_mb_fld, u1_is_top_mb_fld; UWORD8 u1_is_cur_mb_top; u1_is_cur_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; u1_is_cur_mb_top = ps_cur_mb_info->u1_topmb; u1_is_left_mb_fld = ps_cur_mb_info->ps_left_mb->u1_mb_fld; u1_is_top_mb_fld = ps_cur_mb_info->ps_top_mb->u1_mb_fld; /* Checking in the subMB exists, calculating their motion vectors to be used as predictors and the reference frames of those subMBs */ ps_mv_pred[LEFT] = &ps_dec->s_default_mv_pred; ps_mv_pred[TOP] = &(ps_dec->s_default_mv_pred); ps_mv_pred[TOP_R] = &(ps_dec->s_default_mv_pred); /* Check if the left subMb is available */ if(u1_sub_mb_x) { u2_a_in = 1; ps_mv_pred[LEFT] = (ps_mv_nmb - 1); } else { UWORD8 uc_temp; u2_a_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK); if(u2_a_in) { ps_mvpred_l = (ps_dec->u4_num_pmbair) ? ps_mv_nmb : (ps_dec->ps_mv_left + (uc_sub_mb_y << 2) + 48 - (u1_is_cur_mb_top << 4)); uc_temp = 29; if(u1_is_cur_mb_fld ^ u1_is_left_mb_fld) { if(u1_is_left_mb_fld) { uc_temp += (((uc_sub_mb_y & 1) << 2) + ((uc_sub_mb_y & 2) << 1)); uc_temp += ((u1_is_cur_mb_top) ? 0 : 8); } else { uc_temp = uc_temp - (uc_sub_mb_y << 2); uc_temp += ((u1_is_cur_mb_top) ? 0 : 16); } } ps_mv_pred[LEFT] = (ps_mvpred_l - uc_temp); pu0_scale[LEFT] = u1_is_cur_mb_fld - u1_is_left_mb_fld; } } /* Check if the top subMB is available */ if((uc_sub_mb_y > 0) || ((u1_is_cur_mb_top | u1_is_cur_mb_fld) == 0)) { u2_b_in = 1; ps_mv_pred[TOP] = ps_mv_nmb - 4; } else { u2_b_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK); if(u2_b_in) { /* CHANGED CODE */ if(u1_is_top_mb_fld && u1_is_cur_mb_fld) ps_mvpred_tmp = ps_mv_ntop; else { ps_mvpred_tmp = ps_mv_ntop; if(u1_is_cur_mb_top) ps_mvpred_tmp += 16; } ps_mv_pred[TOP] = ps_mvpred_tmp; pu0_scale[TOP] = u1_is_cur_mb_fld - u1_is_top_mb_fld; } } /* Check if the top right subMb is available. The top right subMb is defined as the top right subMb at the top right corner of the MB partition. The top right subMb index starting from the top left corner of the MB partition is given by TopRightSubMbIndx = TopLeftSubMbIndx + (WidthOfMbPartition - 6) / 2 */ u2_c_in = CHECKBIT(ps_cur_mb_info->u2_top_right_avail_mask, (u1_sub_mb_num + uc_mb_part_width - 1)); if(u2_c_in) { ps_mv_pred[TOP_R] = ps_mv_pred[TOP] + uc_mb_part_width; pu0_scale[TOP_R] = pu0_scale[TOP]; if((uc_sub_mb_y == 0) && ((u1_sub_mb_x + uc_mb_part_width) > 3)) { UWORD8 uc_isTopRtMbFld; uc_isTopRtMbFld = ps_cur_mb_info->ps_top_right_mb->u1_mb_fld; /* CHANGED CODE */ ps_mvpred_tmp = ps_mv_ntop + uc_mb_part_width + 12; ps_mvpred_tmp += (u1_is_cur_mb_top) ? 16 : 0; ps_mvpred_tmp += (u1_is_cur_mb_fld && u1_is_cur_mb_top && uc_isTopRtMbFld) ? 0 : 16; ps_mv_pred[TOP_R] = ps_mvpred_tmp; pu0_scale[TOP_R] = u1_is_cur_mb_fld - uc_isTopRtMbFld; } } else { u2_d_in = CHECKBIT(ps_cur_mb_info->u2_top_left_avail_mask, u1_sub_mb_num); /* Check if the the top left subMB is available */ if(u2_d_in) { UWORD8 uc_isTopLtMbFld; ps_mv_pred[TOP_R] = ps_mv_pred[TOP] - 1; pu0_scale[TOP_R] = pu0_scale[TOP]; if(u1_sub_mb_x == 0) { if((uc_sub_mb_y > 0) || ((u1_is_cur_mb_top | u1_is_cur_mb_fld) == 0)) { uc_isTopLtMbFld = u1_is_left_mb_fld; ps_mvpred_tmp = ps_mv_pred[LEFT] - 4; if((u1_is_cur_mb_fld == 0) && uc_isTopLtMbFld) { ps_mvpred_tmp = ps_mv_pred[LEFT] + 16; ps_mvpred_tmp -= (uc_sub_mb_y & 1) ? 0 : 4; } } else { UWORD32 u4_cond = ps_dec->u4_num_pmbair; uc_isTopLtMbFld = ps_cur_mb_info->u1_topleft_mb_fld; /* CHANGED CODE */ ps_mvpred_tmp = ps_mv_ntop - 29; ps_mvpred_tmp += (u1_is_cur_mb_top) ? 16 : 0; if(u1_is_cur_mb_fld && u1_is_cur_mb_top) ps_mvpred_tmp -= (uc_isTopLtMbFld) ? 16 : 0; } ps_mv_pred[TOP_R] = ps_mvpred_tmp; pu0_scale[TOP_R] = u1_is_cur_mb_fld - uc_isTopLtMbFld; } } else if(u2_b_in == 0) { /* If all the subMBs B, C, D are all out of the frame then their MV and their reference picture is equal to that of A */ ps_mv_pred[TOP] = ps_mv_pred[LEFT]; ps_mv_pred[TOP_R] = ps_mv_pred[LEFT]; pu0_scale[TOP] = pu0_scale[LEFT]; pu0_scale[TOP_R] = pu0_scale[LEFT]; } } } /*! ************************************************************************** * \if ih264d_non_mbaff_mv_pred name : Name \endif * * \brief * The routine calculates the motion vector predictor for a given block, * given the candidate MV predictors. * * \param ps_mv_pred: Candidate predictors for the current block * \param ps_currMv: Pointer to the left top edge of the current block in * the MV bank * * \return * _mvPred: The x & y components of the MV predictor. * * \note * The code implements the logic as described in sec 8.4.1.2.1. Given * the candidate predictors and the pointer to the top left edge of the * block in the MV bank. * ************************************************************************** */ #if(!MVPRED_NONMBAFF) void ih264d_non_mbaff_mv_pred(mv_pred_t **ps_mv_pred, UWORD8 u1_sub_mb_num, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, dec_struct_t *ps_dec, UWORD8 uc_mb_part_width, dec_mb_info_t *ps_cur_mb_info) { UWORD16 u2_b_in = 0, u2_c_in = 0, u2_d_in = 0; UWORD8 u1_sub_mb_x = (u1_sub_mb_num & 3), uc_sub_mb_y = (u1_sub_mb_num >> 2); /* Checking in the subMB exists, calculating their motion vectors to be used as predictors and the reference frames of those subMBs */ ps_mv_pred[LEFT] = &ps_dec->s_default_mv_pred; ps_mv_pred[TOP] = &(ps_dec->s_default_mv_pred); ps_mv_pred[TOP_R] = &(ps_dec->s_default_mv_pred); /* Check if the left subMb is available */ if(u1_sub_mb_x) { ps_mv_pred[LEFT] = (ps_mv_nmb - 1); } else { if(ps_cur_mb_info->u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK) { ps_mv_pred[LEFT] = (ps_mv_nmb - 13); } } /* Check if the top subMB is available */ if(uc_sub_mb_y) { u2_b_in = 1; ps_mv_ntop = ps_mv_nmb - 4; ps_mv_pred[TOP] = ps_mv_ntop; } else { u2_b_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK); if(u2_b_in) { ps_mv_pred[TOP] = ps_mv_ntop; } } /* Check if the top right subMb is available. The top right subMb is defined as the top right subMb at the top right corner of the MB partition. The top right subMb index starting from the top left corner of the MB partition is given by TopRightSubMbIndx = TopLeftSubMbIndx + (WidthOfMbPartition - 6) / 2 */ u2_c_in = CHECKBIT(ps_cur_mb_info->u2_top_right_avail_mask, (u1_sub_mb_num + uc_mb_part_width - 1)); if(u2_c_in) { ps_mv_pred[TOP_R] = (ps_mv_ntop + uc_mb_part_width); if(uc_sub_mb_y == 0) { /* CHANGED CODE */ if((u1_sub_mb_x + uc_mb_part_width) > 3) ps_mv_pred[TOP_R] += 12; } } else { u2_d_in = CHECKBIT(ps_cur_mb_info->u2_top_left_avail_mask, u1_sub_mb_num); /* Check if the the top left subMB is available */ if(u2_d_in) { /* CHANGED CODE */ ps_mv_pred[TOP_R] = (ps_mv_ntop - 1); if(u1_sub_mb_x == 0) { if(uc_sub_mb_y) { ps_mv_pred[TOP_R] = (ps_mv_nmb - 17); } else { /* CHANGED CODE */ ps_mv_pred[TOP_R] -= 12; } } } else if(u2_b_in == 0) { /* If all the subMBs B, C, D are all out of the frame then their MV and their reference picture is equal to that of A */ ps_mv_pred[TOP] = ps_mv_pred[LEFT]; ps_mv_pred[TOP_R] = ps_mv_pred[LEFT]; } } } #endif /*****************************************************************************/ /* */ /* Function Name : ih264d_mvpred_nonmbaffB */ /* */ /* Description : This function calculates the motion vector predictor, */ /* for B-Slices */ /* Inputs : <What inputs does the function take?> */ /* Globals : None */ /* Processing : The neighbours A(Left),B(Top),C(TopRight) are calculated */ /* and based on the type of Mb the prediction is */ /* appropriately done */ /* Outputs : populates ps_mv_final_pred structure */ /* Returns : u1_direct_zero_pred_flag which is used only in */ /* decodeSpatialdirect() */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 03 05 2005 TA First Draft */ /* */ /*****************************************************************************/ #if(!MVPRED_NONMBAFF) UWORD8 ih264d_mvpred_nonmbaffB(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, mv_pred_t *ps_mv_final_pred, UWORD8 u1_sub_mb_num, UWORD8 uc_mb_part_width, UWORD8 u1_lx_start, UWORD8 u1_lxend, UWORD8 u1_mb_mc_mode) { UWORD8 u1_a_in, u1_b_in, uc_temp1, uc_temp2, uc_temp3; mv_pred_t *ps_mv_pred[3]; UWORD8 uc_B2, uc_lx, u1_ref_idx; UWORD8 u1_direct_zero_pred_flag = 0; ih264d_non_mbaff_mv_pred(ps_mv_pred, u1_sub_mb_num, ps_mv_nmb, ps_mv_ntop, ps_dec, uc_mb_part_width, ps_cur_mb_info); for(uc_lx = u1_lx_start; uc_lx < u1_lxend; uc_lx++) { u1_ref_idx = ps_mv_final_pred->i1_ref_frame[uc_lx]; uc_B2 = (uc_lx << 1); switch(u1_mb_mc_mode) { case PRED_16x8: /* Directional prediction for a 16x8 MB partition */ if(u1_sub_mb_num == 0) { /* Calculating the MV pred for the top 16x8 block */ if(ps_mv_pred[TOP]->i1_ref_frame[uc_lx] == u1_ref_idx) { /* If the reference frame used by the top subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the top subMB */ ps_mv_final_pred->i2_mv[uc_B2 + 0] = ps_mv_pred[TOP]->i2_mv[uc_B2 + 0]; ps_mv_final_pred->i2_mv[uc_B2 + 1] = ps_mv_pred[TOP]->i2_mv[uc_B2 + 1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } else { if(ps_mv_pred[LEFT]->i1_ref_frame[uc_lx] == u1_ref_idx) { /* If the reference frame used by the left subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the left subMB */ ps_mv_final_pred->i2_mv[uc_B2 + 0] = ps_mv_pred[LEFT]->i2_mv[uc_B2 + 0]; ps_mv_final_pred->i2_mv[uc_B2 + 1] = ps_mv_pred[LEFT]->i2_mv[uc_B2 + 1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } break; case PRED_8x16: /* Directional prediction for a 8x16 MB partition */ if(u1_sub_mb_num == 0) { if(ps_mv_pred[LEFT]->i1_ref_frame[uc_lx] == u1_ref_idx) { /* If the reference frame used by the left subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the left subMB */ ps_mv_final_pred->i2_mv[uc_B2 + 0] = ps_mv_pred[LEFT]->i2_mv[uc_B2 + 0]; ps_mv_final_pred->i2_mv[uc_B2 + 1] = ps_mv_pred[LEFT]->i2_mv[uc_B2 + 1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } else { if(ps_mv_pred[TOP_R]->i1_ref_frame[uc_lx] == u1_ref_idx) { /* If the reference frame used by the top right subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the left subMB */ ps_mv_final_pred->i2_mv[uc_B2 + 0] = ps_mv_pred[TOP_R]->i2_mv[uc_B2 + 0]; ps_mv_final_pred->i2_mv[uc_B2 + 1] = ps_mv_pred[TOP_R]->i2_mv[uc_B2 + 1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } break; case B_DIRECT_SPATIAL: /* Case when the MB has been skipped */ /* If either of left or the top subMB is not present OR If both the MV components of either the left or the top subMB are zero and their reference frame pointer pointing to 0 then MV for the skipped MB is zero else the Median of the mv_pred_t is used */ uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[0]; uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[0]; uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[0]; ps_mv_final_pred->i1_ref_frame[0] = MIN(uc_temp1, MIN(uc_temp2, uc_temp3)); uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[1]; uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[1]; uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[1]; ps_mv_final_pred->i1_ref_frame[1] = MIN(uc_temp1, MIN(uc_temp2, uc_temp3)); if((ps_mv_final_pred->i1_ref_frame[0] < 0) && (ps_mv_final_pred->i1_ref_frame[1] < 0)) { u1_direct_zero_pred_flag = 1; ps_mv_final_pred->i1_ref_frame[0] = 0; ps_mv_final_pred->i1_ref_frame[1] = 0; } ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, ps_mv_final_pred->i1_ref_frame[0], 0, (const UWORD8 *)gau1_ih264d_mv_pred_condition); ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, ps_mv_final_pred->i1_ref_frame[1], 1, (const UWORD8 *)gau1_ih264d_mv_pred_condition); break; case MB_SKIP: /* Case when the MB has been skipped */ /* If either of left or the top subMB is not present OR If both the MV components of either the left or the top subMB are zero and their reference frame pointer pointing to 0 then MV for the skipped MB is zero else the Median of the mv_pred_t is used */ u1_a_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK); u1_b_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK); if(((u1_a_in * u1_b_in) == 0) || ((ps_mv_pred[LEFT]->i2_mv[0] | ps_mv_pred[LEFT]->i2_mv[1] | ps_mv_pred[LEFT]->i1_ref_frame[0]) == 0) || ((ps_mv_pred[TOP]->i2_mv[0] | ps_mv_pred[TOP]->i2_mv[1] | ps_mv_pred[TOP]->i1_ref_frame[0]) == 0)) { ps_mv_final_pred->i2_mv[0] = 0; ps_mv_final_pred->i2_mv[1] = 0; break; } /* If the condition above is not true calculate the MV predictor according to the process defined in sec 8.4.1.2.1 */ default: ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, (const UWORD8 *)gau1_ih264d_mv_pred_condition); break; } } return (u1_direct_zero_pred_flag); } #endif /*****************************************************************************/ /* */ /* Function Name : ih264d_mvpred_nonmbaff */ /* */ /* Description : This function calculates the motion vector predictor, */ /* for all the slice types other than B_SLICE */ /* Inputs : <What inputs does the function take?> */ /* Globals : None */ /* Processing : The neighbours A(Left),B(Top),C(TopRight) are calculated */ /* and based on the type of Mb the prediction is */ /* appropriately done */ /* Outputs : populates ps_mv_final_pred structure */ /* Returns : u1_direct_zero_pred_flag which is used only in */ /* decodeSpatialdirect() */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 03 05 2005 TA First Draft */ /* */ /*****************************************************************************/ #if(!MVPRED_NONMBAFF) UWORD8 ih264d_mvpred_nonmbaff(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, mv_pred_t *ps_mv_final_pred, UWORD8 u1_sub_mb_num, UWORD8 uc_mb_part_width, UWORD8 u1_lx_start, UWORD8 u1_lxend, UWORD8 u1_mb_mc_mode) { UWORD8 u1_a_in, u1_b_in, uc_temp1, uc_temp2, uc_temp3; mv_pred_t *ps_mv_pred[3]; UWORD8 u1_ref_idx; UWORD8 u1_direct_zero_pred_flag = 0; UNUSED(u1_lx_start); UNUSED(u1_lxend); ih264d_non_mbaff_mv_pred(ps_mv_pred, u1_sub_mb_num, ps_mv_nmb, ps_mv_ntop, ps_dec, uc_mb_part_width, ps_cur_mb_info); u1_ref_idx = ps_mv_final_pred->i1_ref_frame[0]; switch(u1_mb_mc_mode) { case PRED_16x8: /* Directional prediction for a 16x8 MB partition */ if(u1_sub_mb_num == 0) { /* Calculating the MV pred for the top 16x8 block */ if(ps_mv_pred[TOP]->i1_ref_frame[0] == u1_ref_idx) { /* If the reference frame used by the top subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the top subMB */ ps_mv_final_pred->i2_mv[0] = ps_mv_pred[TOP]->i2_mv[0]; ps_mv_final_pred->i2_mv[1] = ps_mv_pred[TOP]->i2_mv[1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, 0, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } else { if(ps_mv_pred[LEFT]->i1_ref_frame[0] == u1_ref_idx) { /* If the reference frame used by the left subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the left subMB */ ps_mv_final_pred->i2_mv[0] = ps_mv_pred[LEFT]->i2_mv[0]; ps_mv_final_pred->i2_mv[1] = ps_mv_pred[LEFT]->i2_mv[1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, 0, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } break; case PRED_8x16: /* Directional prediction for a 8x16 MB partition */ if(u1_sub_mb_num == 0) { if(ps_mv_pred[LEFT]->i1_ref_frame[0] == u1_ref_idx) { /* If the reference frame used by the left subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the left subMB */ ps_mv_final_pred->i2_mv[0] = ps_mv_pred[LEFT]->i2_mv[0]; ps_mv_final_pred->i2_mv[1] = ps_mv_pred[LEFT]->i2_mv[1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, 0, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } else { if(ps_mv_pred[TOP_R]->i1_ref_frame[0] == u1_ref_idx) { /* If the reference frame used by the top right subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the left subMB */ ps_mv_final_pred->i2_mv[0] = ps_mv_pred[TOP_R]->i2_mv[0]; ps_mv_final_pred->i2_mv[1] = ps_mv_pred[TOP_R]->i2_mv[1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, 0, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } break; case B_DIRECT_SPATIAL: /* Case when the MB has been skipped */ /* If either of left or the top subMB is not present OR If both the MV components of either the left or the top subMB are zero and their reference frame pointer pointing to 0 then MV for the skipped MB is zero else the Median of the mv_pred_t is used */ uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[0]; uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[0]; uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[0]; ps_mv_final_pred->i1_ref_frame[0] = MIN(uc_temp1, MIN(uc_temp2, uc_temp3)); uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[1]; uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[1]; uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[1]; ps_mv_final_pred->i1_ref_frame[1] = MIN(uc_temp1, MIN(uc_temp2, uc_temp3)); if((ps_mv_final_pred->i1_ref_frame[0] < 0) && (ps_mv_final_pred->i1_ref_frame[1] < 0)) { u1_direct_zero_pred_flag = 1; ps_mv_final_pred->i1_ref_frame[0] = 0; ps_mv_final_pred->i1_ref_frame[1] = 0; } ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, ps_mv_final_pred->i1_ref_frame[0], 0, (const UWORD8 *)gau1_ih264d_mv_pred_condition); ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, ps_mv_final_pred->i1_ref_frame[1], 1, (const UWORD8 *)gau1_ih264d_mv_pred_condition); break; case MB_SKIP: /* Case when the MB has been skipped */ /* If either of left or the top subMB is not present OR If both the MV components of either the left or the top subMB are zero and their reference frame pointer pointing to 0 then MV for the skipped MB is zero else the Median of the mv_pred_t is used */ u1_a_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK); u1_b_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK); if(((u1_a_in * u1_b_in) == 0) || ((ps_mv_pred[LEFT]->i2_mv[0] | ps_mv_pred[LEFT]->i2_mv[1] | ps_mv_pred[LEFT]->i1_ref_frame[0]) == 0) || ((ps_mv_pred[TOP]->i2_mv[0] | ps_mv_pred[TOP]->i2_mv[1] | ps_mv_pred[TOP]->i1_ref_frame[0]) == 0)) { ps_mv_final_pred->i2_mv[0] = 0; ps_mv_final_pred->i2_mv[1] = 0; break; } /* If the condition above is not true calculate the MV predictor according to the process defined in sec 8.4.1.2.1 */ default: ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, 0, (const UWORD8 *)gau1_ih264d_mv_pred_condition); break; } return (u1_direct_zero_pred_flag); } #endif /*****************************************************************************/ /* */ /* Function Name : ih264d_mvpred_mbaff */ /* */ /* Description : This function calculates the motion vector predictor, */ /* Inputs : <What inputs does the function take?> */ /* Globals : None */ /* Processing : The neighbours A(Left),B(Top),C(TopRight) are calculated */ /* and based on the type of Mb the prediction is */ /* appropriately done */ /* Outputs : populates ps_mv_final_pred structure */ /* Returns : u1_direct_zero_pred_flag which is used only in */ /* decodeSpatialdirect() */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 03 05 2005 TA First Draft */ /* */ /*****************************************************************************/ UWORD8 ih264d_mvpred_mbaff(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, mv_pred_t *ps_mv_final_pred, UWORD8 u1_sub_mb_num, UWORD8 uc_mb_part_width, UWORD8 u1_lx_start, UWORD8 u1_lxend, UWORD8 u1_mb_mc_mode) { UWORD8 u1_a_in, u1_b_in, uc_temp1, uc_temp2, uc_temp3; mv_pred_t *ps_mv_pred[3], s_mvPred[3]; UWORD8 uc_B2, pu0_scale[3], i, uc_lx, u1_ref_idx; UWORD8 u1_direct_zero_pred_flag = 0; pu0_scale[0] = pu0_scale[1] = pu0_scale[2] = 0; ih264d_mbaff_mv_pred(ps_mv_pred, u1_sub_mb_num, ps_mv_nmb, ps_mv_ntop, ps_dec, uc_mb_part_width, ps_cur_mb_info, pu0_scale); for(i = 0; i < 3; i++) { if(pu0_scale[i] != 0) { memcpy(&s_mvPred[i], ps_mv_pred[i], sizeof(mv_pred_t)); if(pu0_scale[i] == 1) { s_mvPred[i].i1_ref_frame[0] = s_mvPred[i].i1_ref_frame[0] << 1; s_mvPred[i].i1_ref_frame[1] = s_mvPred[i].i1_ref_frame[1] << 1; s_mvPred[i].i2_mv[1] = SIGN_POW2_DIV(s_mvPred[i].i2_mv[1], 1); s_mvPred[i].i2_mv[3] = SIGN_POW2_DIV(s_mvPred[i].i2_mv[3], 1); } else { s_mvPred[i].i1_ref_frame[0] = s_mvPred[i].i1_ref_frame[0] >> 1; s_mvPred[i].i1_ref_frame[1] = s_mvPred[i].i1_ref_frame[1] >> 1; s_mvPred[i].i2_mv[1] = s_mvPred[i].i2_mv[1] << 1; s_mvPred[i].i2_mv[3] = s_mvPred[i].i2_mv[3] << 1; } ps_mv_pred[i] = &s_mvPred[i]; } } for(uc_lx = u1_lx_start; uc_lx < u1_lxend; uc_lx++) { u1_ref_idx = ps_mv_final_pred->i1_ref_frame[uc_lx]; uc_B2 = (uc_lx << 1); switch(u1_mb_mc_mode) { case PRED_16x8: /* Directional prediction for a 16x8 MB partition */ if(u1_sub_mb_num == 0) { /* Calculating the MV pred for the top 16x8 block */ if(ps_mv_pred[TOP]->i1_ref_frame[uc_lx] == u1_ref_idx) { /* If the reference frame used by the top subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the top subMB */ ps_mv_final_pred->i2_mv[uc_B2 + 0] = ps_mv_pred[TOP]->i2_mv[uc_B2 + 0]; ps_mv_final_pred->i2_mv[uc_B2 + 1] = ps_mv_pred[TOP]->i2_mv[uc_B2 + 1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } else { if(ps_mv_pred[LEFT]->i1_ref_frame[uc_lx] == u1_ref_idx) { /* If the reference frame used by the left subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the left subMB */ ps_mv_final_pred->i2_mv[uc_B2 + 0] = ps_mv_pred[LEFT]->i2_mv[uc_B2 + 0]; ps_mv_final_pred->i2_mv[uc_B2 + 1] = ps_mv_pred[LEFT]->i2_mv[uc_B2 + 1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } break; case PRED_8x16: /* Directional prediction for a 8x16 MB partition */ if(u1_sub_mb_num == 0) { if(ps_mv_pred[LEFT]->i1_ref_frame[uc_lx] == u1_ref_idx) { /* If the reference frame used by the left subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the left subMB */ ps_mv_final_pred->i2_mv[uc_B2 + 0] = ps_mv_pred[LEFT]->i2_mv[uc_B2 + 0]; ps_mv_final_pred->i2_mv[uc_B2 + 1] = ps_mv_pred[LEFT]->i2_mv[uc_B2 + 1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } else { if(ps_mv_pred[TOP_R]->i1_ref_frame[uc_lx] == u1_ref_idx) { /* If the reference frame used by the top right subMB is same as the reference frame used by the current block then MV predictor to be used for the current block is same as the MV of the left subMB */ ps_mv_final_pred->i2_mv[uc_B2 + 0] = ps_mv_pred[TOP_R]->i2_mv[uc_B2 + 0]; ps_mv_final_pred->i2_mv[uc_B2 + 1] = ps_mv_pred[TOP_R]->i2_mv[uc_B2 + 1]; } else { /* The MV predictor is calculated according to the process defined in 8.4.1.2.1 */ ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, (const UWORD8 *)gau1_ih264d_mv_pred_condition); } } break; case B_DIRECT_SPATIAL: /* Case when the MB has been skipped */ /* If either of left or the top subMB is not present OR If both the MV components of either the left or the top subMB are zero and their reference frame pointer pointing to 0 then MV for the skipped MB is zero else the Median of the mv_pred_t is used */ uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[0]; uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[0]; uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[0]; ps_mv_final_pred->i1_ref_frame[0] = MIN(uc_temp1, MIN(uc_temp2, uc_temp3)); uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[1]; uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[1]; uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[1]; ps_mv_final_pred->i1_ref_frame[1] = MIN(uc_temp1, MIN(uc_temp2, uc_temp3)); /* If the reference indices are negative clip the scaled reference indices to -1 */ /* i.e invalid reference index */ /*if(ps_mv_final_pred->i1_ref_frame[0] < 0) ps_mv_final_pred->i1_ref_frame[0] = -1; if(ps_mv_final_pred->i1_ref_frame[1] < 0) ps_mv_final_pred->i1_ref_frame[1] = -1; */ if((ps_mv_final_pred->i1_ref_frame[0] < 0) && (ps_mv_final_pred->i1_ref_frame[1] < 0)) { u1_direct_zero_pred_flag = 1; ps_mv_final_pred->i1_ref_frame[0] = 0; ps_mv_final_pred->i1_ref_frame[1] = 0; } ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, ps_mv_final_pred->i1_ref_frame[0], 0, (const UWORD8 *)gau1_ih264d_mv_pred_condition); ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, ps_mv_final_pred->i1_ref_frame[1], 1, (const UWORD8 *)gau1_ih264d_mv_pred_condition); break; case MB_SKIP: /* Case when the MB has been skipped */ /* If either of left or the top subMB is not present OR If both the MV components of either the left or the top subMB are zero and their reference frame pointer pointing to 0 then MV for the skipped MB is zero else the Median of the mv_pred_t is used */ u1_a_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK); u1_b_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK); if(((u1_a_in * u1_b_in) == 0) || ((ps_mv_pred[LEFT]->i2_mv[0] | ps_mv_pred[LEFT]->i2_mv[1] | ps_mv_pred[LEFT]->i1_ref_frame[0]) == 0) || ((ps_mv_pred[TOP]->i2_mv[0] | ps_mv_pred[TOP]->i2_mv[1] | ps_mv_pred[TOP]->i1_ref_frame[0]) == 0)) { ps_mv_final_pred->i2_mv[0] = 0; ps_mv_final_pred->i2_mv[1] = 0; break; } /* If the condition above is not true calculate the MV predictor according to the process defined in sec 8.4.1.2.1 */ default: ih264d_get_motion_vector_predictor( ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, (const UWORD8 *)gau1_ih264d_mv_pred_condition); break; } } return (u1_direct_zero_pred_flag); } void ih264d_rep_mv_colz(dec_struct_t *ps_dec, mv_pred_t *ps_mv_pred_src, mv_pred_t *ps_mv_pred_dst, UWORD8 u1_sub_mb_num, UWORD8 u1_colz, UWORD8 u1_ht, UWORD8 u1_wd) { UWORD8 k, m; UWORD8 *pu1_colz = ps_dec->pu1_col_zero_flag + ps_dec->i4_submb_ofst + u1_sub_mb_num; for(k = 0; k < u1_ht; k++) { for(m = 0; m < u1_wd; m++) { *(ps_mv_pred_dst + m) = *(ps_mv_pred_src); *(pu1_colz + m) = u1_colz; } pu1_colz += SUB_BLK_WIDTH; ps_mv_pred_dst += SUB_BLK_WIDTH; } } ================================================ FILE: dependencies/ih264d/decoder/ih264d_mvpred.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_MVPRED_H_ #define _IH264D_MVPRED_H_ /** ************************************************************************** * \file ih264d_mvpred.h * * \brief * This file contains declarations of functions specific to decoding * Motion vector. * * Detailed_description * * \date * 10-12-2002 * * \author Arvind Raman ************************************************************************** */ #include "ih264d_structs.h" #include "ih264d_defs.h" //#include "structs.h" /** Reference number that is not valid */ #define OUT_OF_RANGE_REF -1 #define ONE_TO_ONE 0 #define FRM_TO_FLD 1 #define FLD_TO_FRM 2 /** ************************************************************************** * \brief POSITION_IN_MVBANK * * a: Pointer to the top left subMb of the MB in the MV bank array * b: Horiz posn in terms of subMbs * c: Vert posn in terms of subMbs * d: subMb number ************************************************************************** */ #define POSITION_IN_MVBANK(a, b, c, d) (a) + (c) * (d) + (b) /** ************************************************************************** * \brief col4x4_t * * Container to return the information related to the co-located 4x4 * sub-macroblock. ************************************************************************** */ typedef struct { mv_pred_t *ps_mv; /** Ptr to the Mv bank */ UWORD16 u2_mb_addr_col; /** Addr of the co-located MB */ WORD16 i2_mv[2]; /** Mv of the colocated MB */ WORD8 i1_ref_idx_col; /** Ref idx of the co-located picture */ UWORD8 u1_col_pic; /** Idx of the colocated pic */ UWORD8 u1_yM; /** "y" coord of the colocated MB addr */ UWORD8 u1_vert_mv_scale; /** as defined in sec 8.4.1.2.1 */ } col4x4_t; void ih264d_update_nnz_for_skipmb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_entrpy); void ih264d_get_motion_vector_predictor(mv_pred_t * ps_result, mv_pred_t **ps_mv_pred, UWORD8 u1_ref_idx, UWORD8 u1_B, const UWORD8 *pu1_mv_pred_condition); void ih264d_mbaff_mv_pred(mv_pred_t **ps_mv_pred, UWORD8 u1_sub_mb_num, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, dec_struct_t *ps_dec, UWORD8 uc_mb_part_width, dec_mb_info_t *ps_cur_mb_info, UWORD8* pu0_scale); void ih264d_non_mbaff_mv_pred(mv_pred_t **ps_mv_pred, UWORD8 u1_sub_mb_num, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, dec_struct_t *ps_dec, UWORD8 uc_mb_part_width, dec_mb_info_t *ps_cur_mb_info); UWORD8 ih264d_mvpred_nonmbaff(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, mv_pred_t *ps_mv_final_pred, UWORD8 u1_sub_mb_num, UWORD8 uc_mb_part_width, UWORD8 u1_lx_start, UWORD8 u1_lxend, UWORD8 u1_mb_mc_mode); UWORD8 ih264d_mvpred_nonmbaffB(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, mv_pred_t *ps_mv_final_pred, UWORD8 u1_sub_mb_num, UWORD8 uc_mb_part_width, UWORD8 u1_lx_start, UWORD8 u1_lxend, UWORD8 u1_mb_mc_mode); UWORD8 ih264d_mvpred_mbaff(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, mv_pred_t *ps_mv_final_pred, UWORD8 u1_sub_mb_num, UWORD8 uc_mb_part_width, UWORD8 u1_lx_start, UWORD8 u1_lxend, UWORD8 u1_mb_mc_mode); void ih264d_rep_mv_colz(dec_struct_t *ps_dec, mv_pred_t *ps_mv_pred_src, mv_pred_t *ps_mv_pred_dst, UWORD8 u1_sub_mb_num, UWORD8 u1_colz, UWORD8 u1_ht, UWORD8 u1_wd); #endif /* _IH264D_MVPRED_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_nal.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_nal.c * * \brief NAL parsing routines * * Detailed_description * * \author * - AI 19 11 2002 Creation ************************************************************************** */ #include "ih264d_bitstrm.h" #include "ih264d_defs.h" #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_defs.h" #define NUM_OF_ZERO_BYTES_BEFORE_START_CODE 2 #define EMULATION_PREVENTION_BYTE 0x03 #define NAL_FIRST_BYTE_SIZE 1 /*! ************************************************************************** * \if Function name : ih264d_find_start_code \endif * * \brief * This function searches for the Start Code Prefix. * * \param pu1_buf : Pointer to char buffer which contains bitstream. * \param u4_cur_pos : Current position in the buffer. * \param u4_max_ofst : Number of bytes in Buffer. * \param pu4_length_of_start_code : Poiter to length of Start Code. * * \return * Returns 0 on success and -1 on error. * ************************************************************************** */ #define START_CODE_NOT_FOUND -1 #define END_OF_STREAM_BUFFER -2 #define END_OF_STREAM -1 void ih264d_check_if_aud(UWORD8 *pu1_buf, UWORD32 u4_cur_pos, UWORD32 u4_max_ofst, UWORD32 *pu4_next_is_aud) { UWORD8 u1_first_byte, u1_nal_unit_type; if(u4_cur_pos + 1 < u4_max_ofst) { u1_first_byte = pu1_buf[u4_cur_pos + 1]; u1_nal_unit_type = NAL_UNIT_TYPE(u1_first_byte); if(u1_nal_unit_type == ACCESS_UNIT_DELIMITER_RBSP) { *pu4_next_is_aud = 1; } } } WORD32 ih264d_find_start_code(UWORD8 *pu1_buf, UWORD32 u4_cur_pos, UWORD32 u4_max_ofst, UWORD32 *pu4_length_of_start_code, UWORD32 *pu4_next_is_aud) { WORD32 zero_byte_cnt = 0; UWORD32 ui_curPosTemp; *pu4_length_of_start_code = 0; /*Find first start code */ while(u4_cur_pos < u4_max_ofst) { if(pu1_buf[u4_cur_pos] == 0) zero_byte_cnt++; else if(pu1_buf[u4_cur_pos] == 0x01 && zero_byte_cnt >= NUM_OF_ZERO_BYTES_BEFORE_START_CODE) { /* Found the start code */ u4_cur_pos++; break; } else { zero_byte_cnt = 0; } u4_cur_pos++; } /*Find Next Start Code */ *pu4_length_of_start_code = u4_cur_pos; zero_byte_cnt = 0; ui_curPosTemp = u4_cur_pos; while(u4_cur_pos < u4_max_ofst) { if(pu1_buf[u4_cur_pos] == 0) zero_byte_cnt++; else if(pu1_buf[u4_cur_pos] == 0x01 && zero_byte_cnt >= NUM_OF_ZERO_BYTES_BEFORE_START_CODE) { /* Found the start code */ ih264d_check_if_aud(pu1_buf, u4_cur_pos, u4_max_ofst, pu4_next_is_aud); return (u4_cur_pos - zero_byte_cnt - ui_curPosTemp); } else { zero_byte_cnt = 0; } u4_cur_pos++; } return (u4_cur_pos - zero_byte_cnt - ui_curPosTemp); //(START_CODE_NOT_FOUND); } /*! ************************************************************************** * \if Function name : ih264d_get_next_nal_unit \endif * * \brief * This function reads one NAl unit. * * \param ps_nalStream : Poiter to NalUnitStream structure. * \param ps_nalUnit : Pointer to NalUnit. * * \return * Returns 0 on success and -1 on error. * ************************************************************************** */ WORD32 ih264d_get_next_nal_unit(UWORD8 *pu1_buf, UWORD32 u4_cur_pos, UWORD32 u4_max_ofst, UWORD32 *pu4_length_of_start_code) { WORD32 i_length_of_nal_unit = 0; UWORD32 u4_next_is_aud; /* NAL Thread starts */ ih264d_find_start_code(pu1_buf, u4_cur_pos, u4_max_ofst, pu4_length_of_start_code, &u4_next_is_aud); return (i_length_of_nal_unit); } /*! ************************************************************************** * \if Function name : ih264d_process_nal_unit \endif * * \brief * This function removes emulation byte "0x03" from bitstream (EBSP to RBSP). * It also converts bytestream format into 32 bit little-endian format. * * \param ps_bitstrm : Poiter to dec_bit_stream_t structure. * \param pu1_nal_unit : Pointer to char buffer of NalUnit. * \param u4_numbytes_in_nal_unit : Number bytes in NalUnit buffer. * * \return * Returns number of bytes in RBSP ps_bitstrm. * * \note * This function is same as nal_unit() of 7.3.1. Apart from nal_unit() * implementation it converts char buffer into 32 bit Buffer. This * facilitates efficient access of bitstream. This has been done taking * into account present processor architectures. * ************************************************************************** */ WORD32 ih264d_process_nal_unit(dec_bit_stream_t *ps_bitstrm, UWORD8 *pu1_nal_unit, UWORD32 u4_numbytes_in_nal_unit) { UWORD32 u4_num_bytes_in_rbsp; UWORD8 u1_cur_byte; WORD32 i = 0; WORD8 c_count; UWORD32 ui_word; UWORD32 *puc_bitstream_buffer = (UWORD32*)pu1_nal_unit; ps_bitstrm->pu4_buffer = puc_bitstream_buffer; /*--------------------------------------------------------------------*/ /* First Byte of the NAL Unit */ /*--------------------------------------------------------------------*/ ui_word = *pu1_nal_unit++; /*--------------------------------------------------------------------*/ /* Convertion of the EBSP to RBSP */ /* ie Remove the emulation_prevention_byte [equal to 0x03] */ /*--------------------------------------------------------------------*/ u4_num_bytes_in_rbsp = 0; c_count = 0; //first iteration u1_cur_byte = *pu1_nal_unit++; ui_word = ((ui_word << 8) | u1_cur_byte); c_count++; if(u1_cur_byte != 0x00) c_count = 0; //second iteration u1_cur_byte = *pu1_nal_unit++; ui_word = ((ui_word << 8) | u1_cur_byte); u4_num_bytes_in_rbsp = 2; c_count++; if(u1_cur_byte != 0x00) c_count = 0; if(u4_numbytes_in_nal_unit > 2) { i = ((u4_numbytes_in_nal_unit - 3)); } for(; i > 8; i -= 4) { // loop 0 u1_cur_byte = *pu1_nal_unit++; if(c_count == NUM_OF_ZERO_BYTES_BEFORE_START_CODE && u1_cur_byte == EMULATION_PREVENTION_BYTE) { c_count = 0; u1_cur_byte = *pu1_nal_unit++; i--; } ui_word = ((ui_word << 8) | u1_cur_byte); *puc_bitstream_buffer = ui_word; puc_bitstream_buffer++; c_count++; if(u1_cur_byte != 0x00) c_count = 0; // loop 1 u1_cur_byte = *pu1_nal_unit++; if(c_count == NUM_OF_ZERO_BYTES_BEFORE_START_CODE && u1_cur_byte == EMULATION_PREVENTION_BYTE) { c_count = 0; u1_cur_byte = *pu1_nal_unit++; i--; } ui_word = ((ui_word << 8) | u1_cur_byte); c_count++; if(u1_cur_byte != 0x00) c_count = 0; // loop 2 u1_cur_byte = *pu1_nal_unit++; if(c_count == NUM_OF_ZERO_BYTES_BEFORE_START_CODE && u1_cur_byte == EMULATION_PREVENTION_BYTE) { c_count = 0; u1_cur_byte = *pu1_nal_unit++; i--; } ui_word = ((ui_word << 8) | u1_cur_byte); c_count++; if(u1_cur_byte != 0x00) c_count = 0; // loop 3 u1_cur_byte = *pu1_nal_unit++; if(c_count == NUM_OF_ZERO_BYTES_BEFORE_START_CODE && u1_cur_byte == EMULATION_PREVENTION_BYTE) { c_count = 0; u1_cur_byte = *pu1_nal_unit++; i--; } ui_word = ((ui_word << 8) | u1_cur_byte); c_count++; if(u1_cur_byte != 0x00) c_count = 0; u4_num_bytes_in_rbsp += 4; } for(; i > 0; i--) { u1_cur_byte = *pu1_nal_unit++; if(c_count == NUM_OF_ZERO_BYTES_BEFORE_START_CODE && u1_cur_byte == EMULATION_PREVENTION_BYTE) { c_count = 0; i--; u1_cur_byte = *pu1_nal_unit++; } ui_word = ((ui_word << 8) | u1_cur_byte); u4_num_bytes_in_rbsp++; if((u4_num_bytes_in_rbsp & 0x03) == 0x03) { *puc_bitstream_buffer = ui_word; puc_bitstream_buffer++; } c_count++; if(u1_cur_byte != 0x00) c_count = 0; } *puc_bitstream_buffer = (ui_word << ((3 - (((u4_num_bytes_in_rbsp << 30) >> 30))) << 3)); ps_bitstrm->u4_ofst = 0; ps_bitstrm->u4_max_ofst = ((u4_num_bytes_in_rbsp + NAL_FIRST_BYTE_SIZE) << 3); return (u4_num_bytes_in_rbsp); } /*! ************************************************************************** * \if Function name : ih264d_rbsp_to_sodb \endif * * \brief * This function converts RBSP to SODB. * * \param ps_bitstrm : Poiter to dec_bit_stream_t structure. * * \return * None. * ************************************************************************** */ void ih264d_rbsp_to_sodb(dec_bit_stream_t *ps_bitstrm) { UWORD32 ui_lastWord; UWORD32 ui_word; UWORD8 uc_lastByte; WORD8 i; ui_lastWord = (ps_bitstrm->u4_max_ofst >> 5); i = (ps_bitstrm->u4_max_ofst >> 3) & 0x03; if(i) { ui_word = ps_bitstrm->pu4_buffer[ui_lastWord]; uc_lastByte = ((ui_word << ((i - 1) << 3)) >> 24); } else { ui_word = ps_bitstrm->pu4_buffer[ui_lastWord - 1]; uc_lastByte = ((ui_word << 24) >> 24); } /*--------------------------------------------------------------------*/ /* Find out the rbsp_stop_bit position in the last byte of rbsp */ /*--------------------------------------------------------------------*/ for(i = 0; (i < 8) && !CHECKBIT(uc_lastByte, i); ++i) ; ps_bitstrm->u4_max_ofst = ps_bitstrm->u4_max_ofst - (i + 1); } ================================================ FILE: dependencies/ih264d/decoder/ih264d_nal.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_NAL_H_ #define _IH264D_NAL_H_ /*! ************************************************************************* * \file ih264d_nal.h * * \brief * short_description * * Detailed_description * * \date * 21/11/2002 * * \author AI ************************************************************************* */ #include <stdio.h> #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_bitstrm.h" WORD32 ih264d_process_nal_unit(dec_bit_stream_t *ps_bitstrm, UWORD8 *pu1_nal_unit, UWORD32 u4_numbytes_in_nal_unit); void ih264d_rbsp_to_sodb(dec_bit_stream_t *ps_bitstrm); WORD32 ih264d_find_start_code(UWORD8 *pu1_buf, UWORD32 u4_cur_pos, UWORD32 u4_max_ofst, UWORD32 *pu4_length_of_start_code, UWORD32 *pu4_next_is_aud); #endif /* _IH264D_NAL_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_bslice.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_parse_bslice.c * * \brief * Contains routines that decode a I slice type * * Detailed_description * * \date * 07/07/2003 * * \author NS ************************************************************************** */ #include <string.h> #include "ih264_defs.h" #include "ih264d_bitstrm.h" #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264d_parse_cavlc.h" #include "ih264d_mb_utils.h" #include "ih264d_parse_slice.h" #include "ih264d_process_intra_mb.h" #include "ih264d_mvpred.h" #include "ih264d_parse_islice.h" #include "ih264d_inter_pred.h" #include "ih264d_process_pslice.h" #include "ih264d_process_bslice.h" #include "ih264d_deblocking.h" #include "ih264d_cabac.h" #include "ih264d_parse_mb_header.h" #include "ih264d_error_handler.h" #include "ih264d_mvpred.h" #include "ih264d_cabac.h" #include "ih264d_utils.h" void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec); /*! ************************************************************************** * \if Function name : ParseMb_SubMb_PredBCav\endif * * \brief * Implements sub_mb_pred() of 7.3.5.2. & mb_pred() of 7.3.5.1 * * \return * None. * ************************************************************************** */ WORD32 ih264d_parse_bmb_non_direct_cavlc(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2) { dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD8 * pu1_sub_mb_pred_modes = (UWORD8 *)(gau1_ih264d_submb_pred_modes) + 4; const UWORD8 (*pu1_mb_pred_modes)[32] = (const UWORD8 (*)[32])gau1_ih264d_mb_pred_modes; const UWORD8 * pu1_num_mb_part = (const UWORD8 *)gau1_ih264d_num_mb_part; const UWORD8 * pu1_sub_mb_mc_mode = (const UWORD8 *)(gau1_ih264d_submb_mc_mode) + 4; parse_pmbarams_t * ps_parse_mb_data = ps_dec->ps_parse_mb_data + u1_num_mbsNby2; UWORD8 * pu1_col_info = ps_parse_mb_data->u1_col_info; WORD8 (*pi1_ref_idx)[MAX_REFIDX_INFO_PER_MB] = ps_parse_mb_data->i1_ref_idx; UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; UWORD8 u1_mb_mc_mode, u1_num_mb_part, u1_sub_mb = !(u1_mb_type ^ B_8x8); UWORD32 u4_mb_mc_mode = 0, u4_mb_pred_mode = 0; WORD32 ret; if(u1_sub_mb) { UWORD8 uc_i; u1_mb_mc_mode = 0; u1_num_mb_part = 4; /* Reading the subMB type */ for(uc_i = 0; uc_i < 4; uc_i++) { UWORD32 ui_sub_mb_mode; //Inlined ih264d_uev UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; ui_sub_mb_mode = ((1 << u4_ldz) + u4_word - 1); //Inlined ih264d_uev if(ui_sub_mb_mode > 12) return ERROR_SUB_MB_TYPE; else { UWORD8 u1_subMbPredMode = pu1_sub_mb_pred_modes[ui_sub_mb_mode]; u4_mb_mc_mode = (u4_mb_mc_mode << 8) | pu1_sub_mb_mc_mode[ui_sub_mb_mode]; u4_mb_pred_mode = (u4_mb_pred_mode << 8) | u1_subMbPredMode; pi1_ref_idx[0][uc_i] = ((u1_subMbPredMode & PRED_L0) - 1) >> 1; pi1_ref_idx[1][uc_i] = ((u1_subMbPredMode & PRED_L1) - 1) >> 1; COPYTHECONTEXT("sub_mb_type", u1_subMbPredMode); } /* Storing collocated Mb and SubMb mode information */ *pu1_col_info++ = ((PRED_8x8) << 6) | ((pu1_sub_mb_mc_mode[ui_sub_mb_mode] << 4)); if(ui_sub_mb_mode != B_DIRECT_8x8) { if(ui_sub_mb_mode > B_BI_8x8) { ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 0; } } else if(!ps_dec->s_high_profile.u1_direct_8x8_inference_flag) { ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 0; } } } else { UWORD8 u1_mb_pred_mode_idx = 5 + u1_mb_type; UWORD8 u1_mb_pred_mode_part0 = pu1_mb_pred_modes[0][u1_mb_pred_mode_idx]; UWORD8 u1_mb_pred_mode_part1 = pu1_mb_pred_modes[1][u1_mb_pred_mode_idx]; u1_mb_mc_mode = ps_cur_mb_info->u1_mb_mc_mode; u1_num_mb_part = pu1_num_mb_part[u1_mb_mc_mode]; pi1_ref_idx[0][0] = ((u1_mb_pred_mode_part0 & PRED_L0) - 1) >> 1; pi1_ref_idx[1][0] = ((u1_mb_pred_mode_part0 & PRED_L1) - 1) >> 1; pi1_ref_idx[0][1] = ((u1_mb_pred_mode_part1 & PRED_L0) - 1) >> 1; pi1_ref_idx[1][1] = ((u1_mb_pred_mode_part1 & PRED_L1) - 1) >> 1; u4_mb_pred_mode = (u1_mb_pred_mode_part0 << 8) | u1_mb_pred_mode_part1; u4_mb_mc_mode = u1_mb_mc_mode | (u1_mb_mc_mode << 8); u4_mb_mc_mode <<= 16; u4_mb_pred_mode <<= 16; /* Storing collocated Mb and SubMb mode information */ *pu1_col_info++ = (u1_mb_mc_mode << 6); if(u1_mb_mc_mode) *pu1_col_info++ = (u1_mb_mc_mode << 6); } { UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; UWORD8 uc_field = ps_cur_mb_info->u1_mb_field_decodingflag; UWORD8 *pu1_num_ref_idx_lx_active = ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active; const UWORD8 u1_mbaff_field = (u1_mbaff & uc_field); UWORD8 u4_num_ref_idx_lx_active; u4_num_ref_idx_lx_active = (pu1_num_ref_idx_lx_active[0] << u1_mbaff_field) - 1; if(u4_num_ref_idx_lx_active) { if(1 == u4_num_ref_idx_lx_active) ih264d_parse_bmb_ref_index_cavlc_range1( u1_num_mb_part, ps_bitstrm, pi1_ref_idx[0], u4_num_ref_idx_lx_active); else { ret = ih264d_parse_bmb_ref_index_cavlc(u1_num_mb_part, ps_bitstrm, pi1_ref_idx[0], u4_num_ref_idx_lx_active); if(ret != OK) return ret; } } u4_num_ref_idx_lx_active = (pu1_num_ref_idx_lx_active[1] << u1_mbaff_field) - 1; if(u4_num_ref_idx_lx_active) { if(1 == u4_num_ref_idx_lx_active) ih264d_parse_bmb_ref_index_cavlc_range1( u1_num_mb_part, ps_bitstrm, pi1_ref_idx[1], u4_num_ref_idx_lx_active); else { ret = ih264d_parse_bmb_ref_index_cavlc(u1_num_mb_part, ps_bitstrm, pi1_ref_idx[1], u4_num_ref_idx_lx_active); if(ret != OK) return ret; } } } /* Read MotionVectors */ { const UWORD8 * pu1_top_left_sub_mb_indx; const UWORD8 * pu1_sub_mb_indx_mod = (const UWORD8 *)(gau1_ih264d_submb_indx_mod) + (u1_sub_mb * 6); const UWORD8 * pu1_sub_mb_partw = (const UWORD8 *)gau1_ih264d_submb_partw; const UWORD8 * pu1_sub_mb_parth = (const UWORD8 *)gau1_ih264d_submb_parth; const UWORD8 * pu1_num_sub_mb_part = (const UWORD8 *)gau1_ih264d_num_submb_part; const UWORD8 * pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; const UWORD8 * pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; UWORD8 u1_p_idx = 0, u1_num_submb_part, uc_lx; parse_part_params_t * ps_part; mv_pred_t *ps_mv_start = ps_dec->ps_mv_cur + (u1_mb_num << 4); UWORD8 u1_mb_part_wd, u1_mb_part_ht; /* Initialisations */ ps_part = ps_dec->ps_part; /* Default Initialization for Non subMb Case Mode */ u1_mb_part_wd = pu1_mb_partw[u1_mb_mc_mode]; u1_mb_part_ht = pu1_mb_parth[u1_mb_mc_mode]; u1_num_submb_part = 1; /* Decoding the MV for the subMB */ for(uc_lx = 0; uc_lx < 2; uc_lx++) { UWORD8 u1_sub_mb_num = 0, u1_pred_mode, uc_i; UWORD32 u4_mb_mc_mode_tmp = u4_mb_mc_mode; UWORD32 u4_mb_pred_mode_tmp = u4_mb_pred_mode; UWORD16 u2_sub_mb_num = 0x028A; // for sub mb case UWORD8 u1_b2 = uc_lx << 1; u1_pred_mode = (uc_lx) ? PRED_L1 : PRED_L0; pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_mc_mode << 1); for(uc_i = 0; uc_i < u1_num_mb_part; uc_i++) { UWORD8 u1_mb_mc_mode, uc_j; UWORD8 i1_pred = u4_mb_pred_mode_tmp >> 24; u1_mb_mc_mode = u4_mb_mc_mode_tmp >> 24; u4_mb_pred_mode_tmp <<= 8; u4_mb_mc_mode_tmp <<= 8; /* subMb prediction mode */ if(u1_sub_mb) { u1_mb_part_wd = pu1_sub_mb_partw[u1_mb_mc_mode]; u1_mb_part_ht = pu1_sub_mb_parth[u1_mb_mc_mode]; u1_sub_mb_num = u2_sub_mb_num >> 12; u1_num_submb_part = pu1_num_sub_mb_part[u1_mb_mc_mode]; pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_mc_mode << 1); u2_sub_mb_num <<= 4; } for(uc_j = 0; uc_j < u1_num_submb_part; uc_j++, pu1_top_left_sub_mb_indx++) { mv_pred_t * ps_mv; u1_sub_mb_num = u1_sub_mb_num + *pu1_top_left_sub_mb_indx; ps_mv = ps_mv_start + u1_sub_mb_num; /* Storing Info for partitions, writing only once */ if(uc_lx) { ps_part->u1_is_direct = (!i1_pred); ps_part->u1_pred_mode = i1_pred; ps_part->u1_sub_mb_num = u1_sub_mb_num; ps_part->u1_partheight = u1_mb_part_ht; ps_part->u1_partwidth = u1_mb_part_wd; /* Increment partition Index */ u1_p_idx++; ps_part++; } if(i1_pred & u1_pred_mode) { WORD16 i2_mvx, i2_mvy; //inlining ih264d_sev { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_abs_val; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; if(u4_word & 0x1) i2_mvx = (-(WORD32)u4_abs_val); else i2_mvx = (u4_abs_val); } //inlinined ih264d_sev //inlining ih264d_sev { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_abs_val; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; if(u4_word & 0x1) i2_mvy = (-(WORD32)u4_abs_val); else i2_mvy = (u4_abs_val); } //inlinined ih264d_sev /* Storing Mv residuals */ ps_mv->i2_mv[u1_b2] = i2_mvx; ps_mv->i2_mv[u1_b2 + 1] = i2_mvy; } } } } /* write back to the scratch partition info */ ps_dec->ps_part = ps_part; ps_parse_mb_data->u1_num_part = u1_sub_mb ? u1_p_idx : u1_num_mb_part; } return OK; } /*! ************************************************************************** * \if Function name : ParseMb_SubMb_PredBCab\endif * * \brief * Implements sub_mb_pred() of 7.3.5.2. & mb_pred() of 7.3.5.1 * * \return * None. * ************************************************************************** */ WORD32 ih264d_parse_bmb_non_direct_cabac(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2) { /* Loads from ps_dec */ decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; ctxt_inc_mb_info_t *p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; parse_pmbarams_t * ps_parse_mb_data = ps_dec->ps_parse_mb_data + u1_num_mbsNby2; /* table pointer loads */ const UWORD8 * pu1_sub_mb_pred_modes = (UWORD8 *)(gau1_ih264d_submb_pred_modes) + 4; const UWORD8 (*pu1_mb_pred_modes)[32] = (const UWORD8 (*)[32])gau1_ih264d_mb_pred_modes; const UWORD8 *pu1_num_mb_part = (const UWORD8 *)gau1_ih264d_num_mb_part; const UWORD8 *pu1_sub_mb_mc_mode = (UWORD8 *)(gau1_ih264d_submb_mc_mode) + 4; const UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; UWORD8 * pu1_col_info = ps_parse_mb_data->u1_col_info; WORD8 *pi1_ref_idx_l0 = &ps_parse_mb_data->i1_ref_idx[0][0]; WORD8 *pi1_ref_idx_l1 = &ps_parse_mb_data->i1_ref_idx[1][0]; UWORD8 u1_dec_ref_l0, u1_dec_ref_l1; UWORD8 u1_num_mb_part, u1_mb_mc_mode, u1_sub_mb, u1_mbpred_mode = 5 + u1_mb_type; UWORD32 u4_mb_mc_mode = 0, u4_mb_pred_mode = 0; WORD32 ret; p_curr_ctxt->u1_mb_type = CAB_NON_BD16x16; u1_sub_mb = !(u1_mb_type ^ B_8x8); { UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; UWORD8 *pu1_num_ref_idx_lx_active = ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active; UWORD8 uc_field = ps_cur_mb_info->u1_mb_field_decodingflag; UWORD8 u1_mbaff_field = (u1_mbaff & uc_field); u1_dec_ref_l0 = (pu1_num_ref_idx_lx_active[0] << u1_mbaff_field) - 1; u1_dec_ref_l1 = (pu1_num_ref_idx_lx_active[1] << u1_mbaff_field) - 1; } if(u1_sub_mb) { const UWORD8 u1_colz = ((PRED_8x8) << 6); UWORD8 uc_i; u1_mb_mc_mode = 0; u1_num_mb_part = 4; /* Reading the subMB type */ for(uc_i = 0; uc_i < 4; uc_i++) { UWORD8 u1_sub_mb_mode, u1_subMbPredModes; u1_sub_mb_mode = ih264d_parse_submb_type_cabac( 1, ps_cab_env, ps_bitstrm, ps_dec->p_sub_mb_type_t); if(u1_sub_mb_mode > 12) return ERROR_SUB_MB_TYPE; u1_subMbPredModes = pu1_sub_mb_pred_modes[u1_sub_mb_mode]; u4_mb_mc_mode = (u4_mb_mc_mode << 8) | pu1_sub_mb_mc_mode[u1_sub_mb_mode]; u4_mb_pred_mode = (u4_mb_pred_mode << 8) | u1_subMbPredModes; *pi1_ref_idx_l0++ = (u1_subMbPredModes & PRED_L0) ? u1_dec_ref_l0 : -1; *pi1_ref_idx_l1++ = (u1_subMbPredModes & PRED_L1) ? u1_dec_ref_l1 : -1; COPYTHECONTEXT("sub_mb_type", u1_sub_mb_mode); /* Storing collocated Mb and SubMb mode information */ *pu1_col_info++ = (u1_colz | (pu1_sub_mb_mc_mode[u1_sub_mb_mode] << 4)); if(u1_sub_mb_mode != B_DIRECT_8x8) { if(u1_sub_mb_mode > B_BI_8x8) { ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 0; } } else if(!ps_dec->s_high_profile.u1_direct_8x8_inference_flag) { ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 0; } } pi1_ref_idx_l0 -= 4; pi1_ref_idx_l1 -= 4; } else { UWORD8 u1_mb_pred_mode_part0 = pu1_mb_pred_modes[0][u1_mbpred_mode]; UWORD8 u1_mb_pred_mode_part1 = pu1_mb_pred_modes[1][u1_mbpred_mode]; u1_mb_mc_mode = ps_cur_mb_info->u1_mb_mc_mode; u1_num_mb_part = pu1_num_mb_part[u1_mb_mc_mode]; /* Storing collocated Mb and SubMb mode information */ *pu1_col_info++ = (u1_mb_mc_mode << 6); if(u1_mb_mc_mode) *pu1_col_info++ = (u1_mb_mc_mode << 6); u4_mb_mc_mode = u1_mb_mc_mode | (u1_mb_mc_mode << 8); u4_mb_mc_mode <<= 16; u4_mb_pred_mode = ((u1_mb_pred_mode_part0 << 8) | u1_mb_pred_mode_part1) << 16; *pi1_ref_idx_l0++ = (u1_mb_pred_mode_part0 & PRED_L0) ? u1_dec_ref_l0 : -1; *pi1_ref_idx_l0-- = (u1_mb_pred_mode_part1 & PRED_L0) ? u1_dec_ref_l0 : -1; *pi1_ref_idx_l1++ = (u1_mb_pred_mode_part0 & PRED_L1) ? u1_dec_ref_l1 : -1; *pi1_ref_idx_l1-- = (u1_mb_pred_mode_part1 & PRED_L1) ? u1_dec_ref_l1 : -1; } { WORD8 *pi1_lft_cxt = ps_dec->pi1_left_ref_idx_ctxt_inc; WORD8 *pi1_top_cxt = p_curr_ctxt->i1_ref_idx; ret = ih264d_parse_ref_idx_cabac(u1_num_mb_part, 0, u1_dec_ref_l0, u1_mb_mc_mode, pi1_ref_idx_l0, pi1_lft_cxt, pi1_top_cxt, ps_cab_env, ps_bitstrm, ps_dec->p_ref_idx_t); if(ret != OK) return ret; ret = ih264d_parse_ref_idx_cabac(u1_num_mb_part, 2, u1_dec_ref_l1, u1_mb_mc_mode, pi1_ref_idx_l1, pi1_lft_cxt, pi1_top_cxt, ps_cab_env, ps_bitstrm, ps_dec->p_ref_idx_t); if(ret != OK) return ret; } /* Read MotionVectors */ { const UWORD8 *pu1_top_left_sub_mb_indx; UWORD8 uc_j, uc_lx; UWORD8 u1_mb_part_wd, u1_mb_part_ht; const UWORD8 *pu1_sub_mb_indx_mod = (const UWORD8 *)gau1_ih264d_submb_indx_mod + (u1_sub_mb * 6); const UWORD8 *pu1_sub_mb_partw = (const UWORD8 *)gau1_ih264d_submb_partw; const UWORD8 *pu1_sub_mb_parth = (const UWORD8 *)gau1_ih264d_submb_parth; const UWORD8 *pu1_num_sub_mb_part = (const UWORD8 *)gau1_ih264d_num_submb_part; const UWORD8 *pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; const UWORD8 *pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; UWORD8 u1_p_idx = 0; UWORD8 u1_num_submb_part; parse_part_params_t *ps_part; /* Initialisations */ mv_pred_t *ps_mv_start = ps_dec->ps_mv_cur + (u1_mb_num << 4); ps_part = ps_dec->ps_part; /* Default initialization for non subMb case */ u1_mb_part_wd = pu1_mb_partw[u1_mb_mc_mode]; u1_mb_part_ht = pu1_mb_parth[u1_mb_mc_mode]; u1_num_submb_part = 1; /* Decoding the MV for the subMB */ for(uc_lx = 0; uc_lx < 2; uc_lx++) { UWORD8 u1_sub_mb_num = 0; UWORD32 u4_mb_pred_mode_tmp = u4_mb_pred_mode; UWORD32 u4_mb_mc_mode_tmp = u4_mb_mc_mode; UWORD8 u1_mb_mc_mode_1, u1_pred_mode, uc_i; UWORD16 u2_sub_mb_num = 0x028A; UWORD8 u1_b2 = uc_lx << 1; u1_pred_mode = (uc_lx) ? PRED_L1 : PRED_L0; /* Default for Cabac */ pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_mc_mode << 1); for(uc_i = 0; uc_i < u1_num_mb_part; uc_i++) { WORD8 i1_pred = (UWORD8)(u4_mb_pred_mode_tmp >> 24); u1_mb_mc_mode_1 = (UWORD8)(u4_mb_mc_mode_tmp >> 24); u4_mb_pred_mode_tmp <<= 8; u4_mb_mc_mode_tmp <<= 8; /* subMb prediction mode */ if(u1_sub_mb) { u1_mb_part_wd = pu1_sub_mb_partw[u1_mb_mc_mode_1]; u1_mb_part_ht = pu1_sub_mb_parth[u1_mb_mc_mode_1]; u1_sub_mb_num = u2_sub_mb_num >> 12; pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_mc_mode_1 << 1); u1_num_submb_part = pu1_num_sub_mb_part[u1_mb_mc_mode_1]; u2_sub_mb_num = u2_sub_mb_num << 4; } for(uc_j = 0; uc_j < u1_num_submb_part; uc_j++, pu1_top_left_sub_mb_indx++) { mv_pred_t *ps_mv; u1_sub_mb_num = u1_sub_mb_num + *pu1_top_left_sub_mb_indx; ps_mv = ps_mv_start + u1_sub_mb_num; /* Storing Info for partitions, writing only once */ if(uc_lx) { ps_part->u1_is_direct = (!i1_pred); ps_part->u1_pred_mode = i1_pred; ps_part->u1_sub_mb_num = u1_sub_mb_num; ps_part->u1_partheight = u1_mb_part_ht; ps_part->u1_partwidth = u1_mb_part_wd; /* Increment partition Index */ u1_p_idx++; ps_part++; } ih264d_get_mvd_cabac(u1_sub_mb_num, u1_b2, u1_mb_part_wd, u1_mb_part_ht, (UWORD8)(i1_pred & u1_pred_mode), ps_dec, ps_mv); } } } /* write back to the scratch partition info */ ps_dec->ps_part = ps_part; ps_parse_mb_data->u1_num_part = u1_sub_mb ? u1_p_idx : u1_num_mb_part; } return OK; } /*! ************************************************************************** * \if Function name : ih264d_parse_bmb_cabac \endif * * \brief * This function parses CABAC syntax of a B MB. * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_bmb_cabac(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2) { UWORD8 u1_cbp; deblk_mb_t * ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_mb_num; const UWORD8 *puc_mb_mc_mode = (const UWORD8 *)gau1_ih264d_mb_mc_mode; UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; ctxt_inc_mb_info_t *p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; WORD32 ret; UWORD8 u1_Bdirect_tranform_read = 1; ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 1; ps_cur_mb_info->u1_mb_mc_mode = puc_mb_mc_mode[5 + u1_mb_type]; ps_cur_mb_info->u1_yuv_dc_block_flag = 0; ps_cur_deblk_mb->u1_mb_type |= D_B_SLICE; if(u1_mb_type != B_DIRECT) { ret = ih264d_parse_bmb_non_direct_cabac(ps_dec, ps_cur_mb_info, u1_mb_num, u1_num_mbsNby2); if(ret != OK) return ret; } else { /************ STORING PARTITION INFO ***********/ parse_part_params_t * ps_part_info; ps_part_info = ps_dec->ps_part; ps_part_info->u1_is_direct = PART_DIRECT_16x16; ps_part_info->u1_sub_mb_num = 0; ps_dec->ps_part++; p_curr_ctxt->u1_mb_type = CAB_BD16x16; MEMSET_16BYTES(&ps_dec->pu1_left_mv_ctxt_inc[0][0], 0); memset(ps_dec->pi1_left_ref_idx_ctxt_inc, 0, 4); MEMSET_16BYTES(p_curr_ctxt->u1_mv, 0); memset(p_curr_ctxt->i1_ref_idx, 0, 4); /* check whether transform8x8 u4_flag to be read or not */ u1_Bdirect_tranform_read = ps_dec->s_high_profile.u1_direct_8x8_inference_flag; } /* Read the Coded block pattern */ u1_cbp = (WORD8)ih264d_parse_ctx_cbp_cabac(ps_dec); p_curr_ctxt->u1_cbp = u1_cbp; ps_cur_mb_info->u1_cbp = u1_cbp; if(u1_cbp > 47) return ERROR_CBP; COPYTHECONTEXT("coded_block_pattern", u1_cbp); ps_cur_mb_info->u1_tran_form8x8 = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; if((ps_dec->s_high_profile.u1_transform8x8_present) && (u1_cbp & (0xf)) && (ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag) && (u1_Bdirect_tranform_read)) { ps_cur_mb_info->u1_tran_form8x8 = ih264d_parse_transform8x8flag_cabac( ps_dec, ps_cur_mb_info); COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; p_curr_ctxt->u1_transform8x8_ctxt = ps_cur_mb_info->u1_tran_form8x8; } else { p_curr_ctxt->u1_transform8x8_ctxt = 0; } p_curr_ctxt->u1_intra_chroma_pred_mode = 0; p_curr_ctxt->u1_yuv_dc_csbp &= 0xFE; ps_dec->pu1_left_yuv_dc_csbp[0] &= 0x6; /* Read mb_qp_delta */ if(u1_cbp) { WORD8 c_temp; ret = ih264d_parse_mb_qp_delta_cabac(ps_dec, &c_temp); if(ret != OK) return ret; COPYTHECONTEXT("mb_qp_delta", c_temp); if(c_temp) { ret = ih264d_update_qp(ps_dec, c_temp); if(ret != OK) return ret; } } else ps_dec->i1_prev_mb_qp_delta = 0; ih264d_parse_residual4x4_cabac(ps_dec, ps_cur_mb_info, 0); if(EXCEED_OFFSET(ps_dec->ps_bitstrm)) return ERROR_EOB_TERMINATE_T; return OK; } /*! ************************************************************************** * \if Function name : ih264d_parse_bmb_cavlc \endif * * \brief * This function parses CAVLC syntax of a B MB. * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_bmb_cavlc(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2) { UWORD32 u4_cbp; deblk_mb_t * ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_mb_num; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 * pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; const UWORD8 *puc_mb_mc_mode = (const UWORD8 *)gau1_ih264d_mb_mc_mode; UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; WORD32 ret; UWORD8 u1_Bdirect_tranform_read = 1; ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 1; ps_cur_mb_info->u1_tran_form8x8 = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; ps_cur_mb_info->u1_yuv_dc_block_flag = 0; ps_cur_mb_info->u1_mb_mc_mode = puc_mb_mc_mode[5 + u1_mb_type]; ps_cur_deblk_mb->u1_mb_type |= D_B_SLICE; if(u1_mb_type != B_DIRECT) { ret = ih264d_parse_bmb_non_direct_cavlc(ps_dec, ps_cur_mb_info, u1_mb_num, u1_num_mbsNby2); if(ret != OK) return ret; } else { /************ STORING PARTITION INFO ***********/ parse_part_params_t * ps_part_info; ps_part_info = ps_dec->ps_part; ps_part_info->u1_is_direct = PART_DIRECT_16x16; ps_part_info->u1_sub_mb_num = 0; ps_dec->ps_part++; /* check whether transform8x8 u4_flag to be read or not */ u1_Bdirect_tranform_read = ps_dec->s_high_profile.u1_direct_8x8_inference_flag; } /* Read the Coded block pattern */ { const UWORD8 * puc_CbpInter = gau1_ih264d_cbp_inter; //Inlined ih264d_uev UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_cbp = ((1 << u4_ldz) + u4_word - 1); //Inlined ih264d_uev if(u4_cbp > 47) return ERROR_CBP; u4_cbp = puc_CbpInter[u4_cbp]; if((ps_dec->s_high_profile.u1_transform8x8_present) && (u4_cbp & (0xf)) && (ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag) && (u1_Bdirect_tranform_read)) { ps_cur_mb_info->u1_tran_form8x8 = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; } } COPYTHECONTEXT("coded_block_pattern", u4_cbp); ps_cur_mb_info->u1_cbp = u4_cbp; /* Read mb_qp_delta */ if(u4_cbp) { WORD32 i_temp; //inlining ih264d_sev UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_abs_val; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; if(u4_word & 0x1) i_temp = (-(WORD32)u4_abs_val); else i_temp = (u4_abs_val); if(i_temp < -26 || i_temp > 25) return ERROR_INV_RANGE_QP_T; //inlinined ih264d_sev COPYTHECONTEXT("mb_qp_delta", i_temp); if(i_temp) { ret = ih264d_update_qp(ps_dec, (WORD8)i_temp); if(ret != OK) return ret; } ret = ih264d_parse_residual4x4_cavlc(ps_dec, ps_cur_mb_info, 0); if(ret != OK) return ret; if(EXCEED_OFFSET(ps_bitstrm)) return ERROR_EOB_TERMINATE_T; } else { ps_dec->i1_prev_mb_qp_delta = 0; ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CAVLC); } return OK; } WORD32 ih264d_mv_pred_ref_tfr_nby2_bmb(dec_struct_t * ps_dec, UWORD8 u1_mb_idx, UWORD8 u1_num_mbs) { parse_pmbarams_t * ps_mb_part_info; parse_part_params_t * ps_part; mv_pred_t *ps_mv_nmb, *ps_mv_nmb_start, *ps_mv_ntop, *ps_mv_ntop_start; pic_buffer_t * ps_ref_frame; UWORD8 u1_direct_mode_width; UWORD8 i, j; dec_mb_info_t * ps_cur_mb_info; const UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; UWORD8 u1_field; WORD32 ret = 0; ps_dec->i4_submb_ofst -= (u1_num_mbs - u1_mb_idx) << 4; ps_mb_part_info = ps_dec->ps_parse_mb_data; ps_part = ps_dec->ps_parse_part_params; /* N/2 Mb MvPred and Transfer Setup Loop */ for(i = u1_mb_idx; i < u1_num_mbs; i++, ps_mb_part_info++) { UWORD8 u1_colz = 0; ps_dec->i4_submb_ofst += SUB_BLK_SIZE; /* Restore the slice scratch MbX and MbY context */ ps_cur_mb_info = ps_dec->ps_nmb_info + i; u1_field = ps_cur_mb_info->u1_mb_field_decodingflag; ps_mv_nmb_start = ps_dec->ps_mv_cur + (i << 4); ps_dec->u2_mbx = ps_cur_mb_info->u2_mbx; ps_dec->u2_mby = ps_cur_mb_info->u2_mby; ps_dec->u1_currB_type = 0; ps_dec->u2_mv_2mb[i & 0x1] = 0; /* Look for MV Prediction and Reference Transfer in Non-I Mbs */ if(!ps_mb_part_info->u1_isI_mb) { UWORD8 u1_blk_no; WORD16 i1_ref_idx, i1_ref_idx1; UWORD8 u1_pred_mode; UWORD8 u1_sub_mb_x, u1_sub_mb_y, u1_sub_mb_num; UWORD8 u1_lx, u1_lx_start, u1_lxend, u1_tmp_lx; UWORD8 u1_num_part, u1_num_ref, u1_wd, u1_ht; UWORD32 *pu4_wt_offst; UWORD8 u1_scale_ref, u4_bot_mb; deblk_mb_t * ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + i; WORD8 (*pi1_ref_idx)[MAX_REFIDX_INFO_PER_MB] = ps_mb_part_info->i1_ref_idx; WORD8 *pi1_ref_idx0 = pi1_ref_idx[0], *pi1_ref_idx1 = pi1_ref_idx[1]; UWORD32 **ppu4_wt_ofst = ps_mb_part_info->pu4_wt_offst; /* MB Level initialisations */ ps_dec->u4_num_pmbair = i >> u1_mbaff; ps_dec->u1_mb_idx_mv = i; /* CHANGED CODE */ ps_mv_ntop_start = ps_mv_nmb_start - (ps_dec->u2_frm_wd_in_mbs << (4 + u1_mbaff)) + 12; u1_num_part = ps_mb_part_info->u1_num_part; ps_cur_deblk_mb->u1_mb_type |= (u1_num_part > 1) << 1; u1_direct_mode_width = (1 == ps_mb_part_info->u1_num_part) ? 16 : 8; ps_cur_mb_info->u4_pred_info_pkd_idx = ps_dec->u4_pred_info_pkd_idx; ps_cur_mb_info->u1_num_pred_parts = 0; /****************************************************/ /* weighted u4_ofst pointer calculations, this loop */ /* runs maximum 4 times, even in direct cases */ /****************************************************/ u1_scale_ref = u1_mbaff & ps_cur_mb_info->u1_mb_field_decodingflag; u4_bot_mb = 1 - ps_cur_mb_info->u1_topmb; if(ps_dec->ps_cur_pps->u1_wted_bipred_idc) { u1_num_ref = MIN(u1_num_part, 4); if(PART_DIRECT_16x16 != ps_part->u1_is_direct) { for(u1_blk_no = 0; u1_blk_no < u1_num_ref; u1_blk_no++) { i1_ref_idx = MAX(pi1_ref_idx0[u1_blk_no], 0); if(u1_scale_ref) i1_ref_idx >>= 1; i1_ref_idx *= ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; if(u1_scale_ref) i1_ref_idx += (MAX(pi1_ref_idx1[u1_blk_no], 0) >> 1); else i1_ref_idx += MAX(pi1_ref_idx1[u1_blk_no], 0); pu4_wt_offst = (UWORD32*)&ps_dec->pu4_wt_ofsts[2 * X3(i1_ref_idx)]; if(pi1_ref_idx0[u1_blk_no] < 0) pu4_wt_offst += 1; ppu4_wt_ofst[u1_blk_no] = pu4_wt_offst; if(u1_scale_ref && (ps_dec->ps_cur_pps->u1_wted_bipred_idc == 2)) { i1_ref_idx = MAX(pi1_ref_idx0[u1_blk_no], 0); i1_ref_idx *= (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] << 1); i1_ref_idx += MAX(pi1_ref_idx1[u1_blk_no], 0); if(u4_bot_mb) { i1_ref_idx += (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0] << 1) * (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] << 1); } pu4_wt_offst = (UWORD32*)&ps_dec->pu4_mbaff_wt_mat[2 * X3(i1_ref_idx)]; ppu4_wt_ofst[u1_blk_no] = pu4_wt_offst; } } } } /**************************************************/ /* Loop on Partitions */ /* direct mode is reflected as a single partition */ /**************************************************/ for(j = 0; j < u1_num_part; j++, ps_part++) { u1_sub_mb_num = ps_part->u1_sub_mb_num; ps_dec->u1_sub_mb_num = u1_sub_mb_num; if(PART_NOT_DIRECT != ps_part->u1_is_direct) { /**************************************************/ /* Direct Mode, Call DecodeSpatial/TemporalDirect */ /* only (those will in turn call FormMbPartInfo) */ /**************************************************/ ret = ps_dec->ps_cur_slice->pf_decodeDirect(ps_dec, u1_direct_mode_width, ps_cur_mb_info, i); if(ret != OK) return ret; ps_cur_deblk_mb->u1_mb_type |= (ps_dec->u1_currB_type << 1); } else { mv_pred_t s_mvPred; /**************************************************/ /* Non Direct Mode, Call Motion Vector Predictor */ /* and FormMbpartInfo */ /**************************************************/ u1_sub_mb_x = u1_sub_mb_num & 0x03; u1_sub_mb_y = u1_sub_mb_num >> 2; u1_blk_no = (u1_num_part < 4) ? j : (((u1_sub_mb_y >> 1) << 1) + (u1_sub_mb_x >> 1)); ps_mv_ntop = ps_mv_ntop_start + u1_sub_mb_x; ps_mv_nmb = ps_mv_nmb_start + u1_sub_mb_num; u1_pred_mode = ps_part->u1_pred_mode; u1_wd = ps_part->u1_partwidth; u1_ht = ps_part->u1_partheight; u1_lx_start = 0; u1_lxend = 2; if( PRED_L0 == u1_pred_mode) { s_mvPred.i2_mv[2] = 0; s_mvPred.i2_mv[3] = 0; u1_lxend = 1; } if( PRED_L1 == u1_pred_mode) { s_mvPred.i2_mv[0] = 0; s_mvPred.i2_mv[1] = 0; u1_lx_start = 1; } /* Populate the colpic info and reference frames */ s_mvPred.i1_ref_frame[0] = pi1_ref_idx0[u1_blk_no]; s_mvPred.i1_ref_frame[1] = pi1_ref_idx1[u1_blk_no]; ps_dec->pf_mvpred(ps_dec, ps_cur_mb_info, ps_mv_nmb, ps_mv_ntop, &s_mvPred, u1_sub_mb_num, u1_wd, u1_lx_start, u1_lxend, ps_cur_mb_info->u1_mb_mc_mode); /**********************************************************/ /* Loop on number of predictors, 1 Each for Forw Backw */ /* Loop 2 times for BiDirect mode */ /**********************************************************/ for(u1_lx = u1_lx_start; u1_lx < u1_lxend; u1_lx++) { WORD16 i2_mv_x, i2_mv_y; /********************************************************/ /* Predict Mv */ /* Add Mv Residuals and store back */ /********************************************************/ i1_ref_idx = s_mvPred.i1_ref_frame[u1_lx]; u1_tmp_lx = (u1_lx << 1); i2_mv_x = ps_mv_nmb->i2_mv[u1_tmp_lx]; i2_mv_y = ps_mv_nmb->i2_mv[u1_tmp_lx + 1]; i2_mv_x += s_mvPred.i2_mv[u1_tmp_lx]; i2_mv_y += s_mvPred.i2_mv[u1_tmp_lx + 1]; s_mvPred.i2_mv[u1_tmp_lx] = i2_mv_x; s_mvPred.i2_mv[u1_tmp_lx + 1] = i2_mv_y; /********************************************************/ /* Transfer setup call */ /* convert RefIdx if it is MbAff */ /* Pass Weight Offset and refFrame */ /********************************************************/ i1_ref_idx1 = i1_ref_idx >> u1_scale_ref; if(u1_scale_ref && ((i1_ref_idx & 0x01) != u4_bot_mb)) i1_ref_idx1 += MAX_REF_BUFS; ps_ref_frame = ps_dec->ps_ref_pic_buf_lx[u1_lx][i1_ref_idx1]; /* Storing Colocated-Zero u4_flag */ if(u1_lx == u1_lx_start) { /* Fill colocated info in MvPred structure */ s_mvPred.u1_col_ref_pic_idx = ps_ref_frame->u1_mv_buf_id; s_mvPred.u1_pic_type = ps_ref_frame->u1_pic_type; /* Calculating colocated zero information */ u1_colz = (u1_field << 1) | ((i1_ref_idx == 0) && (ABS(i2_mv_x) <= 1) && (ABS(i2_mv_y) <= 1)); u1_colz |= ps_mb_part_info->u1_col_info[u1_blk_no]; } pu4_wt_offst = ppu4_wt_ofst[u1_blk_no]; { pred_info_pkd_t *ps_pred_pkd; WORD16 i2_mv[2]; i2_mv[0] = i2_mv_x; i2_mv[1] = i2_mv_y; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(i2_mv,u1_wd,u1_ht,u1_sub_mb_num,u1_pred_mode, ps_pred_pkd,ps_ref_frame->u1_pic_buf_id,i1_ref_idx,pu4_wt_offst, ps_ref_frame->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } } ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb, u1_sub_mb_num, u1_colz, u1_ht, u1_wd); } } } else { /* Set zero values in case of Intra Mbs */ mv_pred_t s_mvPred = { { 0, 0, 0, 0 }, { -1, -1 }, 0, 0}; /* Storing colocated zero information */ ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb_start, 0, (UWORD8)(u1_field << 1), 4, 4); } /*if num _cores is set to 3 ,compute bs will be done in another thread*/ if(ps_dec->u4_num_cores < 3) { if(ps_dec->u4_app_disable_deblk_frm == 0) ps_dec->pf_compute_bs(ps_dec, ps_cur_mb_info, (UWORD16)(i >> u1_mbaff)); } } return OK; } /*! ************************************************************************** * \if Function name : ih264d_get_implicit_weights \endif * * \brief * Calculates Implicit Weights. * * \return * None * ************************************************************************** */ void ih264d_get_implicit_weights(dec_struct_t *ps_dec) { UWORD32 *pu4_iwt_ofst; UWORD8 i, j; struct pic_buffer_t *ps_pic_buff0, *ps_pic_buff1; WORD16 i2_dist_scale_factor; WORD16 i2_tb, i2_td, i2_tx; WORD64 i8_tb, i8_td; WORD32 i4_poc0, i4_poc1; UWORD32 ui_temp0, ui_temp1; UWORD8 uc_num_ref_idx_l0_active, uc_num_ref_idx_l1_active; pu4_iwt_ofst = ps_dec->pu4_wts_ofsts_mat; uc_num_ref_idx_l0_active = ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; uc_num_ref_idx_l1_active = ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; for(i = 0; i < uc_num_ref_idx_l0_active; i++) { ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][i]; i4_poc0 = ps_pic_buff0->i4_avg_poc; for(j = 0; j < uc_num_ref_idx_l1_active; j++) { ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][j]; i4_poc1 = ps_pic_buff1->i4_avg_poc; if(i4_poc1 != i4_poc0) { i8_tb = (WORD64)ps_dec->ps_cur_pic->i4_poc - i4_poc0; i2_tb = CLIP_S8(i8_tb); i8_td = (WORD64)i4_poc1 - i4_poc0; i2_td = CLIP_S8(i8_td); i2_tx = (16384 + ABS(SIGN_POW2_DIV(i2_td, 1))) / i2_td; i2_dist_scale_factor = CLIP_S11( (((i2_tb * i2_tx) + 32) >> 6)); if(/*((u4_poc1 - u4_poc0) == 0) ||*/ (!(ps_pic_buff1->u1_is_short && ps_pic_buff0->u1_is_short)) || ((i2_dist_scale_factor >> 2) < -64) || ((i2_dist_scale_factor >> 2) > 128)) { /* same for forward and backward, wt=32 and Offset = 0 */ ui_temp0 = 0x00000020; ui_temp1 = 0x00000020; } else { ui_temp0 = 64 - (i2_dist_scale_factor >> 2); ui_temp1 = (i2_dist_scale_factor >> 2); } } else { ui_temp0 = 0x00000020; ui_temp1 = 0x00000020; } pu4_iwt_ofst[0] = pu4_iwt_ofst[2] = pu4_iwt_ofst[4] = ui_temp0; pu4_iwt_ofst[1] = pu4_iwt_ofst[3] = pu4_iwt_ofst[5] = ui_temp1; pu4_iwt_ofst += 6; } } if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) { UWORD8 k; WORD32 i4_cur_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; UWORD32* pu4_wt_mat = ps_dec->pu4_mbaff_wt_mat; /* Form the Implicit Weighted prediction matrix for field MBs also */ for(k = 0; k < 2; k++) { for(i = 0; i < (uc_num_ref_idx_l0_active << 1); i++) { UWORD16 u2_l0_idx; /*u2_l0_idx = (i >= uc_num_ref_idx_l0_active) ?(MAX_REF_BUFS + i - uc_num_ref_idx_l0_active) : (i) ;*/ u2_l0_idx = i >> 1; if((i & 0x01) != k) { u2_l0_idx += MAX_REF_BUFS; } ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][u2_l0_idx]; i4_poc0 = ps_pic_buff0->i4_poc; for(j = 0; j < (uc_num_ref_idx_l1_active << 1); j++) { UWORD16 u2_l1_idx; /*u2_l1_idx = (j >= uc_num_ref_idx_l1_active) ? (MAX_REF_BUFS + j - uc_num_ref_idx_l1_active ) : (j) ;*/ u2_l1_idx = j >> 1; if((j & 0x01) != k) { u2_l1_idx += MAX_REF_BUFS; } ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][u2_l1_idx]; i4_poc1 = ps_pic_buff1->i4_poc; if(i4_poc1 != i4_poc0) { i8_tb = (WORD64)i4_cur_poc - i4_poc0; i2_tb = CLIP_S8(i8_tb); i8_td = (WORD64)i4_poc1 - i4_poc0; i2_td = CLIP_S8(i8_td); i2_tx = (16384 + ABS(SIGN_POW2_DIV(i2_td, 1))) / i2_td; i2_dist_scale_factor = CLIP_S11( (((i2_tb * i2_tx) + 32) >> 6)); if(/*((u4_poc1 - u4_poc0) == 0) ||*/ (!(ps_pic_buff1->u1_is_short && ps_pic_buff0->u1_is_short)) || ((i2_dist_scale_factor >> 2) < -64) || ((i2_dist_scale_factor >> 2) > 128)) { /* same for forward and backward, wt=32 and Offset = 0 */ ui_temp0 = 0x00000020; ui_temp1 = 0x00000020; } else { ui_temp0 = 64 - (i2_dist_scale_factor >> 2); ui_temp1 = (i2_dist_scale_factor >> 2); } } else { ui_temp0 = 0x00000020; ui_temp1 = 0x00000020; } /* Store in the weight matrix */ *pu4_wt_mat++ = ui_temp0; *pu4_wt_mat++ = ui_temp1; *pu4_wt_mat++ = ui_temp0; *pu4_wt_mat++ = ui_temp1; *pu4_wt_mat++ = ui_temp0; *pu4_wt_mat++ = ui_temp1; } } i4_cur_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; } } } /*! ************************************************************************** * \if Function name : ih264d_decode_bslice \endif * * \brief * Decodes a B Slice * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_bslice(dec_struct_t * ps_dec, UWORD16 u2_first_mb_in_slice) { dec_pic_params_t * ps_pps = ps_dec->ps_cur_pps; dec_slice_params_t * ps_slice = ps_dec->ps_cur_slice; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; UWORD8 u1_ref_idx_re_flag_lx; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD64 u8_ref_idx_l0, u8_ref_idx_l1; UWORD32 u4_temp, ui_temp1; WORD32 i_temp; WORD32 ret; /*--------------------------------------------------------------------*/ /* Read remaining contents of the slice header */ /*--------------------------------------------------------------------*/ { WORD8 *pi1_buf; WORD16 *pi2_mv = ps_dec->s_default_mv_pred.i2_mv; WORD32 *pi4_mv = (WORD32*)pi2_mv; WORD16 *pi16_refFrame; pi1_buf = ps_dec->s_default_mv_pred.i1_ref_frame; pi16_refFrame = (WORD16*)pi1_buf; *pi4_mv = 0; *(pi4_mv + 1) = 0; *pi16_refFrame = OUT_OF_RANGE_REF; ps_dec->s_default_mv_pred.u1_col_ref_pic_idx = (UWORD8)-1; ps_dec->s_default_mv_pred.u1_pic_type = (UWORD8)-1; } ps_slice->u1_num_ref_idx_active_override_flag = ih264d_get_bit_h264( ps_bitstrm); COPYTHECONTEXT("SH: num_ref_idx_override_flag", ps_slice->u1_num_ref_idx_active_override_flag); u8_ref_idx_l0 = ps_dec->ps_cur_pps->u1_num_ref_idx_lx_active[0]; u8_ref_idx_l1 = ps_dec->ps_cur_pps->u1_num_ref_idx_lx_active[1]; if(ps_slice->u1_num_ref_idx_active_override_flag) { u8_ref_idx_l0 = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SH: num_ref_idx_l0_active_minus1", u8_ref_idx_l0 - 1); u8_ref_idx_l1 = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SH: num_ref_idx_l1_active_minus1", u8_ref_idx_l1 - 1); } { UWORD8 u1_max_ref_idx = H264_MAX_REF_PICS; if(ps_slice->u1_field_pic_flag) { u1_max_ref_idx = H264_MAX_REF_PICS << 1; } if((u8_ref_idx_l0 > u1_max_ref_idx) || (u8_ref_idx_l1 > u1_max_ref_idx)) { return ERROR_NUM_REF; } ps_slice->u1_num_ref_idx_lx_active[0] = u8_ref_idx_l0; ps_slice->u1_num_ref_idx_lx_active[1] = u8_ref_idx_l1; } ih264d_init_ref_idx_lx_b(ps_dec); /* Store the value for future slices in the same picture */ ps_dec->u1_num_ref_idx_lx_active_prev = ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; u1_ref_idx_re_flag_lx = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("SH: ref_pic_list_reordering_flag_l0",u1_ref_idx_re_flag_lx); /* Modified temporarily */ if(u1_ref_idx_re_flag_lx) { WORD8 ret; ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_mod_dpb[0]; ret = ih264d_ref_idx_reordering(ps_dec, 0); if(ret == -1) return ERROR_REFIDX_ORDER_T; } else ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_init_dpb[0]; u1_ref_idx_re_flag_lx = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("SH: ref_pic_list_reordering_flag_l1",u1_ref_idx_re_flag_lx); /* Modified temporarily */ if(u1_ref_idx_re_flag_lx) { WORD8 ret; ps_dec->ps_ref_pic_buf_lx[1] = ps_dec->ps_dpb_mgr->ps_mod_dpb[1]; ret = ih264d_ref_idx_reordering(ps_dec, 1); if(ret == -1) return ERROR_REFIDX_ORDER_T; } else ps_dec->ps_ref_pic_buf_lx[1] = ps_dec->ps_dpb_mgr->ps_init_dpb[1]; /* Create refIdx to POC mapping */ { void **ppv_map_ref_idx_to_poc_lx; WORD8 idx; struct pic_buffer_t *ps_pic; ppv_map_ref_idx_to_poc_lx = ps_dec->ppv_map_ref_idx_to_poc + FRM_LIST_L0; ppv_map_ref_idx_to_poc_lx[0] = 0; ppv_map_ref_idx_to_poc_lx++; for(idx = 0; idx < ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; idx++) { ps_pic = ps_dec->ps_ref_pic_buf_lx[0][idx]; ppv_map_ref_idx_to_poc_lx[idx] = (ps_pic->pu1_buf1); } ppv_map_ref_idx_to_poc_lx = ps_dec->ppv_map_ref_idx_to_poc + FRM_LIST_L1; ppv_map_ref_idx_to_poc_lx[0] = 0; ppv_map_ref_idx_to_poc_lx++; for(idx = 0; idx < ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; idx++) { ps_pic = ps_dec->ps_ref_pic_buf_lx[1][idx]; ppv_map_ref_idx_to_poc_lx[idx] = (ps_pic->pu1_buf1); } if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) { void **ppv_map_ref_idx_to_poc_lx_t, **ppv_map_ref_idx_to_poc_lx_b; ppv_map_ref_idx_to_poc_lx_t = ps_dec->ppv_map_ref_idx_to_poc + TOP_LIST_FLD_L0; ppv_map_ref_idx_to_poc_lx_b = ps_dec->ppv_map_ref_idx_to_poc + BOT_LIST_FLD_L0; ppv_map_ref_idx_to_poc_lx_t[0] = 0; ppv_map_ref_idx_to_poc_lx_t++; ppv_map_ref_idx_to_poc_lx_b[0] = 0; ppv_map_ref_idx_to_poc_lx_b++; for(idx = 0; idx < ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; idx++) { ps_pic = ps_dec->ps_ref_pic_buf_lx[0][idx]; ppv_map_ref_idx_to_poc_lx_t[0] = (ps_pic->pu1_buf1); ppv_map_ref_idx_to_poc_lx_b[1] = (ps_pic->pu1_buf1); ppv_map_ref_idx_to_poc_lx_b[0] = (ps_pic->pu1_buf1) + 1; ppv_map_ref_idx_to_poc_lx_t[1] = (ps_pic->pu1_buf1) + 1; ppv_map_ref_idx_to_poc_lx_t += 2; ppv_map_ref_idx_to_poc_lx_b += 2; } ppv_map_ref_idx_to_poc_lx_t = ps_dec->ppv_map_ref_idx_to_poc + TOP_LIST_FLD_L1; ppv_map_ref_idx_to_poc_lx_b = ps_dec->ppv_map_ref_idx_to_poc + BOT_LIST_FLD_L1; ppv_map_ref_idx_to_poc_lx_t[0] = 0; ppv_map_ref_idx_to_poc_lx_t++; ppv_map_ref_idx_to_poc_lx_b[0] = 0; ppv_map_ref_idx_to_poc_lx_b++; for(idx = 0; idx < ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; idx++) { UWORD8 u1_tmp_idx = idx << 1; ps_pic = ps_dec->ps_ref_pic_buf_lx[1][idx]; ppv_map_ref_idx_to_poc_lx_t[u1_tmp_idx] = (ps_pic->pu1_buf1); ppv_map_ref_idx_to_poc_lx_b[u1_tmp_idx + 1] = (ps_pic->pu1_buf1); ppv_map_ref_idx_to_poc_lx_b[u1_tmp_idx] = (ps_pic->pu1_buf1) + 1; ppv_map_ref_idx_to_poc_lx_t[u1_tmp_idx + 1] = (ps_pic->pu1_buf1) + 1; } } if(ps_dec->u4_num_cores >= 3) { WORD32 num_entries; WORD32 size; num_entries = MAX_FRAMES; if((1 >= ps_dec->ps_cur_sps->u1_num_ref_frames) && (0 == ps_dec->i4_display_delay)) { num_entries = 1; } num_entries = ((2 * num_entries) + 1); num_entries *= 2; size = num_entries * sizeof(void *); size += PAD_MAP_IDX_POC * sizeof(void *); memcpy((void *)ps_dec->ps_parse_cur_slice->ppv_map_ref_idx_to_poc, ps_dec->ppv_map_ref_idx_to_poc, size); } } if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag && (ps_dec->ps_cur_slice->u1_field_pic_flag == 0)) { ih264d_convert_frm_mbaff_list(ps_dec); } if(ps_pps->u1_wted_bipred_idc == 1) { ret = ih264d_parse_pred_weight_table(ps_slice, ps_bitstrm); if(ret != OK) return ret; ih264d_form_pred_weight_matrix(ps_dec); ps_dec->pu4_wt_ofsts = ps_dec->pu4_wts_ofsts_mat; } else if(ps_pps->u1_wted_bipred_idc == 2) { /* Implicit Weighted prediction */ ps_slice->u2_log2Y_crwd = 0x0505; ps_dec->pu4_wt_ofsts = ps_dec->pu4_wts_ofsts_mat; ih264d_get_implicit_weights(ps_dec); } else ps_dec->ps_cur_slice->u2_log2Y_crwd = 0; ps_dec->ps_parse_cur_slice->u2_log2Y_crwd = ps_dec->ps_cur_slice->u2_log2Y_crwd; /* G050 */ if(ps_slice->u1_nal_ref_idc != 0) { if(!ps_dec->ps_dpb_cmds->u1_dpb_commands_read) { i_temp = ih264d_read_mmco_commands(ps_dec); if (i_temp < 0) { return ERROR_DBP_MANAGER_T; } ps_dec->u4_bitoffset = i_temp; } else ps_bitstrm->u4_ofst += ps_dec->u4_bitoffset; } /* G050 */ if(ps_pps->u1_entropy_coding_mode == CABAC) { u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp > MAX_CABAC_INIT_IDC) { return ERROR_INV_SLICE_HDR_T; } ps_slice->u1_cabac_init_idc = u4_temp; COPYTHECONTEXT("SH: cabac_init_idc",ps_slice->u1_cabac_init_idc); } /* Read slice_qp_delta */ WORD64 i8_temp = (WORD64)ps_pps->u1_pic_init_qp + ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i8_temp < MIN_H264_QP) || (i8_temp > MAX_H264_QP)) { return ERROR_INV_RANGE_QP_T; } ps_slice->u1_slice_qp = i8_temp; COPYTHECONTEXT("SH: slice_qp_delta", (WORD8)(ps_slice->u1_slice_qp - ps_pps->u1_pic_init_qp)); if(ps_pps->u1_deblocking_filter_parameters_present_flag == 1) { u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp > SLICE_BOUNDARY_DBLK_DISABLED) { return ERROR_INV_SLICE_HDR_T; } COPYTHECONTEXT("SH: disable_deblocking_filter_idc", u4_temp); ps_slice->u1_disable_dblk_filter_idc = u4_temp; if(u4_temp != 1) { i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) << 1; if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) { return ERROR_INV_SLICE_HDR_T; } ps_slice->i1_slice_alpha_c0_offset = i_temp; COPYTHECONTEXT("SH: slice_alpha_c0_offset_div2", ps_slice->i1_slice_alpha_c0_offset >> 1); i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) << 1; if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) { return ERROR_INV_SLICE_HDR_T; } ps_slice->i1_slice_beta_offset = i_temp; COPYTHECONTEXT("SH: slice_beta_offset_div2", ps_slice->i1_slice_beta_offset >> 1); } else { ps_slice->i1_slice_alpha_c0_offset = 0; ps_slice->i1_slice_beta_offset = 0; } } else { ps_slice->u1_disable_dblk_filter_idc = 0; ps_slice->i1_slice_alpha_c0_offset = 0; ps_slice->i1_slice_beta_offset = 0; } ps_dec->u1_slice_header_done = 2; if(ps_pps->u1_entropy_coding_mode) { SWITCHOFFTRACE; SWITCHONTRACECABAC; ps_dec->pf_parse_inter_slice = ih264d_parse_inter_slice_data_cabac; ps_dec->pf_parse_inter_mb = ih264d_parse_bmb_cabac; ih264d_init_cabac_contexts(B_SLICE, ps_dec); if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_mbaff; else ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_nonmbaff; } else { SWITCHONTRACE; SWITCHOFFTRACECABAC; ps_dec->pf_parse_inter_slice = ih264d_parse_inter_slice_data_cavlc; ps_dec->pf_parse_inter_mb = ih264d_parse_bmb_cavlc; if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_mbaff; else ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_nonmbaff; } ret = ih264d_cal_col_pic(ps_dec); if(ret != OK) return ret; ps_dec->u1_B = 1; ps_dec->pf_mvpred_ref_tfr_nby2mb = ih264d_mv_pred_ref_tfr_nby2_bmb; ret = ps_dec->pf_parse_inter_slice(ps_dec, ps_slice, u2_first_mb_in_slice); if(ret != OK) return ret; return OK; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_cabac.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! *************************************************************************** * \file ih264d_parse_cabac.c * * \brief * This file contains cabac Residual decoding routines. * * \date * 20/03/2003 * * \author NS *************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_defs.h" #include "ih264d_structs.h" #include "ih264d_cabac.h" #include "ih264d_bitstrm.h" #include "ih264d_parse_mb_header.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_error_handler.h" #include "ih264d_parse_cabac.h" #include "ih264d_parse_slice.h" #include "ih264d_tables.h" #include "ih264d_mb_utils.h" #include "ih264d_utils.h" /*! ******************************************************************************** * \if Function name : ih264d_read_coeff4x4_cabac \endif * * \brief This function encodes residual_block_cabac as defined in 7.3.5.3.2. * * \return * Returns the index of last significant coeff. * ******************************************************************************** */ UWORD8 ih264d_read_coeff4x4_cabac(dec_bit_stream_t *ps_bitstrm, UWORD32 u4_ctxcat, bin_ctxt_model_t *ps_ctxt_sig_coeff, dec_struct_t *ps_dec, /*!< pointer to access global variables*/ bin_ctxt_model_t *ps_ctxt_coded) { decoding_envirnoment_t *ps_cab_env = &ps_dec->s_cab_dec_env; UWORD32 u4_coded_flag; UWORD32 u4_offset, *pu4_buffer; UWORD32 u4_code_int_range, u4_code_int_val_ofst; tu_sblk4x4_coeff_data_t *ps_tu_4x4; WORD16 *pi2_coeff_data; WORD32 num_sig_coeffs = 0; /*loading from strcuctures*/ ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; ps_tu_4x4->u2_sig_coeff_map = 0; pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; u4_offset = ps_bitstrm->u4_ofst; pu4_buffer = ps_bitstrm->pu4_buffer; u4_code_int_range = ps_cab_env->u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; { /*inilined DecodeDecision_onebin begins*/ { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u4_symbol, u1_mps_state; UWORD32 table_lookup; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; UWORD32 u4_clz; u1_mps_state = (ps_ctxt_coded->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) { RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) } ps_ctxt_coded->u1_mps_state = u1_mps_state; u4_coded_flag = u4_symbol; /*inilined DecodeDecision_onebin ends*/ } } if(u4_coded_flag) { { bin_ctxt_model_t *p_binCtxt_last, *p_binCtxt_last_org; UWORD32 uc_last_coeff_idx; UWORD32 uc_bin; UWORD32 i; WORD32 first_coeff_offset = 0; if((u4_ctxcat == CHROMA_AC_CTXCAT) || (u4_ctxcat == LUMA_AC_CTXCAT)) { first_coeff_offset = 1; } i = 0; if(u4_ctxcat == CHROMA_DC_CTXCAT) { uc_last_coeff_idx = 3; } else { UWORD32 u4_start; u4_start = (u4_ctxcat & 1) + (u4_ctxcat >> 2); uc_last_coeff_idx = 15 - u4_start; } p_binCtxt_last_org = ps_ctxt_sig_coeff + LAST_COEFF_CTXT_MINUS_SIG_COEFF_CTXT; do { /*inilined DecodeDecision_onebin begins*/ { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u4_symbol, u1_mps_state; UWORD32 table_lookup; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; UWORD32 u4_clz; u1_mps_state = (ps_ctxt_sig_coeff->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_14) { UWORD32 read_bits, u4_clz; u4_clz = CLZ(u4_code_int_range); NEXTBITS(read_bits, (u4_offset + 23), pu4_buffer, u4_clz) FLUSHBITS(u4_offset, (u4_clz)) u4_code_int_range = u4_code_int_range << u4_clz; u4_code_int_val_ofst = (u4_code_int_val_ofst << u4_clz) | read_bits; } INC_BIN_COUNT( ps_cab_env) ps_ctxt_sig_coeff->u1_mps_state = u1_mps_state; uc_bin = u4_symbol; } /*incrementing pointer to point to the context of the next bin*/ ps_ctxt_sig_coeff++; /*inilined DecodeDecision_onebin ends*/ if(uc_bin) { num_sig_coeffs++; SET_BIT(ps_tu_4x4->u2_sig_coeff_map, (i + first_coeff_offset)); p_binCtxt_last = p_binCtxt_last_org + i; /*inilined DecodeDecision_onebin begins*/ { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u4_symbol, u1_mps_state; UWORD32 table_lookup; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; UWORD32 u4_clz; u1_mps_state = (p_binCtxt_last->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) INC_BIN_COUNT(ps_cab_env) p_binCtxt_last->u1_mps_state = u1_mps_state; uc_bin = u4_symbol; } /*inilined DecodeDecision_onebin ends*/ if(uc_bin == 1) goto label_read_levels; } i = i + 1; } while(i < uc_last_coeff_idx); num_sig_coeffs++; SET_BIT(ps_tu_4x4->u2_sig_coeff_map, (i + first_coeff_offset)); label_read_levels: ; } /// VALUE of No of Coeff in BLOCK = i + 1 for second case else i; /* Decode coeff_abs_level_minus1 and coeff_sign_flag */ { WORD32 i2_abs_lvl; UWORD32 u1_abs_level_equal1 = 1, u1_abs_level_gt1 = 0; UWORD32 u4_ctx_inc; UWORD32 ui_prefix; bin_ctxt_model_t *p_ctxt_abs_level; p_ctxt_abs_level = ps_dec->p_coeff_abs_level_minus1_t[u4_ctxcat]; u4_ctx_inc = ((0x51)); /*****************************************************/ /* Main Loop runs for no. of Significant coefficient */ /*****************************************************/ do { { INC_SYM_COUNT(&(ps_dec.s_cab_dec_env)); /*****************************************************/ /* inilining a modified ih264d_decode_bins_unary */ /*****************************************************/ { UWORD32 u4_value; UWORD32 u4_symbol; bin_ctxt_model_t *ps_bin_ctxt; UWORD32 u4_ctx_Inc; u4_value = 0; u4_ctx_Inc = u4_ctx_inc & 0xf; ps_bin_ctxt = p_ctxt_abs_level + u4_ctx_Inc; do { { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u1_mps_state; UWORD32 table_lookup; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; UWORD32 u4_clz; u1_mps_state = (ps_bin_ctxt->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) { RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) } INC_BIN_COUNT(ps_cab_env); ps_bin_ctxt->u1_mps_state = u1_mps_state; } INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); u4_value++; ps_bin_ctxt = p_ctxt_abs_level + (u4_ctx_inc >> 4); } while(u4_symbol && (u4_value < UCOFF_LEVEL)); ui_prefix = u4_value - 1 + u4_symbol; } if(ui_prefix == UCOFF_LEVEL) { UWORD32 ui16_sufS = 0; UWORD32 u1_max_bins; UWORD32 u4_value; i2_abs_lvl = UCOFF_LEVEL; /*inlining ih264d_decode_bypass_bins_unary begins*/ { UWORD32 uc_bin; UWORD32 bits_to_flush; bits_to_flush = 0; /*renormalize to ensure there 23 bits more in the u4_code_int_val_ofst*/ { UWORD32 u4_clz, read_bits; u4_clz = CLZ(u4_code_int_range); FLUSHBITS(u4_offset, u4_clz) NEXTBITS(read_bits, u4_offset, pu4_buffer, CABAC_BITS_TO_READ) u4_code_int_range = u4_code_int_range << u4_clz; u4_code_int_val_ofst = (u4_code_int_val_ofst << u4_clz) | read_bits; } do { bits_to_flush++; u4_code_int_range = u4_code_int_range >> 1; if(u4_code_int_val_ofst >= u4_code_int_range) { /* S=1 */ uc_bin = 1; u4_code_int_val_ofst -= u4_code_int_range; } else { /* S=0 */ uc_bin = 0; } INC_BIN_COUNT( ps_cab_env);INC_BYPASS_BINS(ps_cab_env); } while(uc_bin && (bits_to_flush < CABAC_BITS_TO_READ)); u4_value = (bits_to_flush - 1); } /*inlining ih264d_decode_bypass_bins_unary ends*/ ui16_sufS = (1 << u4_value); u1_max_bins = u4_value; if(u4_value > 0) { /*inline bypassbins_flc begins*/ if(u4_value > 10) { UWORD32 u4_clz, read_bits; u4_clz = CLZ(u4_code_int_range); FLUSHBITS(u4_offset, u4_clz) NEXTBITS(read_bits, u4_offset, pu4_buffer, CABAC_BITS_TO_READ) u4_code_int_range = u4_code_int_range << u4_clz; u4_code_int_val_ofst = (u4_code_int_val_ofst << u4_clz) | read_bits; } { UWORD32 ui_bins; UWORD32 uc_bin; UWORD32 bits_to_flush; ui_bins = 0; bits_to_flush = 0; do { bits_to_flush++; u4_code_int_range = u4_code_int_range >> 1; if(u4_code_int_val_ofst >= u4_code_int_range) { /* S=1 */ uc_bin = 1; u4_code_int_val_ofst -= u4_code_int_range; } else { /* S=0 */ uc_bin = 0; } INC_BIN_COUNT( ps_cab_env);INC_BYPASS_BINS(ps_cab_env); ui_bins = ((ui_bins << 1) | uc_bin); } while(bits_to_flush < u1_max_bins); u4_value = ui_bins; } /*inline bypassbins_flc ends*/ } //Value of K ui16_sufS += u4_value; i2_abs_lvl += ui16_sufS; } else i2_abs_lvl = 1 + ui_prefix; if(i2_abs_lvl > 1) { u1_abs_level_gt1++; } if(!u1_abs_level_gt1) { u1_abs_level_equal1++; u4_ctx_inc = (5 << 4) + MIN(u1_abs_level_equal1, 4); } else u4_ctx_inc = (5 + MIN(u1_abs_level_gt1, 4)) << 4; /*u4_ctx_inc = g_table_temp[u1_abs_level_gt1][u1_abs_level_equal1];*/ /* encode coeff_sign_flag[i] */ { u4_code_int_range = u4_code_int_range >> 1; if(u4_code_int_val_ofst >= (u4_code_int_range)) { /* S=1 */ u4_code_int_val_ofst -= u4_code_int_range; i2_abs_lvl = (-i2_abs_lvl); } } num_sig_coeffs--; *pi2_coeff_data++ = i2_abs_lvl; } } while(num_sig_coeffs > 0); } } if(u4_coded_flag) { WORD32 offset; offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; offset = ALIGN4(offset); ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); } /*updating structures*/ ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; ps_cab_env->u4_code_int_range = u4_code_int_range; ps_bitstrm->u4_ofst = u4_offset; return (u4_coded_flag); } /*! ******************************************************************************** * \if Function name : ih264d_read_coeff8x8_cabac \endif * * \brief This function encodes residual_block_cabac as defined in 7.3.5.3.2. when transform_8x8_flag = 1 * * \return * Returns the index of last significant coeff. * ******************************************************************************** */ void ih264d_read_coeff8x8_cabac(dec_bit_stream_t *ps_bitstrm, dec_struct_t *ps_dec, /*!< pointer to access global variables*/ dec_mb_info_t *ps_cur_mb_info) { decoding_envirnoment_t *ps_cab_env = &ps_dec->s_cab_dec_env; UWORD32 u4_offset, *pu4_buffer; UWORD32 u4_code_int_range, u4_code_int_val_ofst; /* High profile related declarations */ UWORD8 u1_field_coding_flag = ps_cur_mb_info->ps_curmb->u1_mb_fld; const UWORD8 *pu1_lastcoeff_context_inc = (UWORD8 *)gau1_ih264d_lastcoeff_context_inc; const UWORD8 *pu1_sigcoeff_context_inc; bin_ctxt_model_t *ps_ctxt_sig_coeff; WORD32 num_sig_coeffs = 0; tu_blk8x8_coeff_data_t *ps_tu_8x8; WORD16 *pi2_coeff_data; /*loading from strcuctures*/ ps_tu_8x8 = (tu_blk8x8_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; ps_tu_8x8->au4_sig_coeff_map[0] = 0; ps_tu_8x8->au4_sig_coeff_map[1] = 0; pi2_coeff_data = &ps_tu_8x8->ai2_level[0]; if(!u1_field_coding_flag) { pu1_sigcoeff_context_inc = (UWORD8 *)gau1_ih264d_sigcoeff_context_inc_frame; /*******************************************************************/ /* last coefficient context is derived from significant coeff u4_flag */ /* only significant coefficient matrix need to be initialized */ /*******************************************************************/ ps_ctxt_sig_coeff = ps_dec->s_high_profile.ps_sigcoeff_8x8_frame; } else { pu1_sigcoeff_context_inc = (UWORD8 *)gau1_ih264d_sigcoeff_context_inc_field; /*******************************************************************/ /* last coefficient context is derived from significant coeff u4_flag */ /* only significant coefficient matrix need to be initialized */ /*******************************************************************/ ps_ctxt_sig_coeff = ps_dec->s_high_profile.ps_sigcoeff_8x8_field; } /*loading from strcuctures*/ u4_offset = ps_bitstrm->u4_ofst; pu4_buffer = ps_bitstrm->pu4_buffer; u4_code_int_range = ps_cab_env->u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; { { bin_ctxt_model_t *p_binCtxt_last, *p_binCtxt_last_org, *p_ctxt_sig_coeff_org; UWORD32 uc_last_coeff_idx; UWORD32 uc_bin; UWORD32 i; i = 0; uc_last_coeff_idx = 63; p_binCtxt_last_org = ps_ctxt_sig_coeff + LAST_COEFF_CTXT_MINUS_SIG_COEFF_CTXT_8X8; p_ctxt_sig_coeff_org = ps_ctxt_sig_coeff; do { /*inilined DecodeDecision_onebin begins*/ { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u4_symbol, u1_mps_state; UWORD32 table_lookup; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; UWORD32 u4_clz; u1_mps_state = (ps_ctxt_sig_coeff->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_14) { UWORD32 read_bits, u4_clz; u4_clz = CLZ(u4_code_int_range); NEXTBITS(read_bits, (u4_offset + 23), pu4_buffer, u4_clz) FLUSHBITS(u4_offset, (u4_clz)) u4_code_int_range = u4_code_int_range << u4_clz; u4_code_int_val_ofst = (u4_code_int_val_ofst << u4_clz) | read_bits; } ps_ctxt_sig_coeff->u1_mps_state = u1_mps_state; uc_bin = u4_symbol; } /*incrementing pointer to point to the context of the next bin*/ ps_ctxt_sig_coeff = p_ctxt_sig_coeff_org + pu1_sigcoeff_context_inc[i + 1]; /*inilined DecodeDecision_onebin ends*/ if(uc_bin) { num_sig_coeffs++; SET_BIT(ps_tu_8x8->au4_sig_coeff_map[i>31], (i > 31 ? i - 32:i)); p_binCtxt_last = p_binCtxt_last_org + pu1_lastcoeff_context_inc[i]; /*inilined DecodeDecision_onebin begins*/ { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u4_symbol, u1_mps_state; UWORD32 table_lookup; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; UWORD32 u4_clz; u1_mps_state = (p_binCtxt_last->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) p_binCtxt_last->u1_mps_state = u1_mps_state; uc_bin = u4_symbol; } /*inilined DecodeDecision_onebin ends*/ if(uc_bin == 1) goto label_read_levels; } i = i + 1; } while(i < uc_last_coeff_idx); num_sig_coeffs++; SET_BIT(ps_tu_8x8->au4_sig_coeff_map[i>31], (i > 31 ? i - 32:i)); label_read_levels: ; } /// VALUE of No of Coeff in BLOCK = i + 1 for second case else i; /* Decode coeff_abs_level_minus1 and coeff_sign_flag */ { WORD32 i2_abs_lvl; UWORD32 u1_abs_level_equal1 = 1, u1_abs_level_gt1 = 0; UWORD32 u4_ctx_inc; UWORD32 ui_prefix; bin_ctxt_model_t *p_ctxt_abs_level; p_ctxt_abs_level = ps_dec->p_coeff_abs_level_minus1_t[LUMA_8X8_CTXCAT]; u4_ctx_inc = ((0x51)); /*****************************************************/ /* Main Loop runs for no. of Significant coefficient */ /*****************************************************/ do { { /*****************************************************/ /* inilining a modified ih264d_decode_bins_unary */ /*****************************************************/ { UWORD32 u4_value; UWORD32 u4_symbol; bin_ctxt_model_t *ps_bin_ctxt; UWORD32 u4_ctx_Inc; u4_value = 0; u4_ctx_Inc = u4_ctx_inc & 0xf; ps_bin_ctxt = p_ctxt_abs_level + u4_ctx_Inc; do { { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u1_mps_state; UWORD32 table_lookup; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; UWORD32 u4_clz; u1_mps_state = (ps_bin_ctxt->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) { RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) } ps_bin_ctxt->u1_mps_state = u1_mps_state; } u4_value++; ps_bin_ctxt = p_ctxt_abs_level + (u4_ctx_inc >> 4); } while(u4_symbol && (u4_value < UCOFF_LEVEL)); ui_prefix = u4_value - 1 + u4_symbol; } if(ui_prefix == UCOFF_LEVEL) { UWORD32 ui16_sufS = 0; UWORD32 u1_max_bins; UWORD32 u4_value; i2_abs_lvl = UCOFF_LEVEL; /*inlining ih264d_decode_bypass_bins_unary begins*/ { UWORD32 uc_bin; UWORD32 bits_to_flush; bits_to_flush = 0; /*renormalize to ensure there 23 bits more in the u4_code_int_val_ofst*/ { UWORD32 u4_clz, read_bits; u4_clz = CLZ(u4_code_int_range); FLUSHBITS(u4_offset, u4_clz) NEXTBITS(read_bits, u4_offset, pu4_buffer, CABAC_BITS_TO_READ) u4_code_int_range = u4_code_int_range << u4_clz; u4_code_int_val_ofst = (u4_code_int_val_ofst << u4_clz) | read_bits; } do { bits_to_flush++; u4_code_int_range = u4_code_int_range >> 1; if(u4_code_int_val_ofst >= u4_code_int_range) { /* S=1 */ uc_bin = 1; u4_code_int_val_ofst -= u4_code_int_range; } else { /* S=0 */ uc_bin = 0; } } while(uc_bin && (bits_to_flush < CABAC_BITS_TO_READ)); u4_value = (bits_to_flush - 1); } /*inlining ih264d_decode_bypass_bins_unary ends*/ ui16_sufS = (1 << u4_value); u1_max_bins = u4_value; if(u4_value > 0) { /*inline bypassbins_flc begins*/ if(u4_value > 10) { UWORD32 u4_clz, read_bits; u4_clz = CLZ(u4_code_int_range); FLUSHBITS(u4_offset, u4_clz) NEXTBITS(read_bits, u4_offset, pu4_buffer, CABAC_BITS_TO_READ) u4_code_int_range = u4_code_int_range << u4_clz; u4_code_int_val_ofst = (u4_code_int_val_ofst << u4_clz) | read_bits; } { UWORD32 ui_bins; UWORD32 uc_bin; UWORD32 bits_to_flush; ui_bins = 0; bits_to_flush = 0; do { bits_to_flush++; u4_code_int_range = u4_code_int_range >> 1; if(u4_code_int_val_ofst >= u4_code_int_range) { /* S=1 */ uc_bin = 1; u4_code_int_val_ofst -= u4_code_int_range; } else { /* S=0 */ uc_bin = 0; } ui_bins = ((ui_bins << 1) | uc_bin); } while(bits_to_flush < u1_max_bins); u4_value = ui_bins; } /*inline bypassbins_flc ends*/ } //Value of K ui16_sufS += u4_value; i2_abs_lvl += (WORD32)ui16_sufS; } else { i2_abs_lvl = 1 + ui_prefix; } if(i2_abs_lvl > 1) { u1_abs_level_gt1++; } if(!u1_abs_level_gt1) { u1_abs_level_equal1++; u4_ctx_inc = (5 << 4) + MIN(u1_abs_level_equal1, 4); } else { u4_ctx_inc = (5 + MIN(u1_abs_level_gt1, 4)) << 4; } /*u4_ctx_inc = g_table_temp[u1_abs_level_gt1][u1_abs_level_equal1];*/ /* encode coeff_sign_flag[i] */ { u4_code_int_range = u4_code_int_range >> 1; if(u4_code_int_val_ofst >= (u4_code_int_range)) { /* S=1 */ u4_code_int_val_ofst -= u4_code_int_range; i2_abs_lvl = (-i2_abs_lvl); } } *pi2_coeff_data++ = i2_abs_lvl; num_sig_coeffs--; } } while(num_sig_coeffs > 0); } } { WORD32 offset; offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_8x8; offset = ALIGN4(offset); ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); } /*updating structures*/ ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; ps_cab_env->u4_code_int_range = u4_code_int_range; ps_bitstrm->u4_ofst = u4_offset; } /*****************************************************************************/ /* */ /* Function Name : ih264d_cabac_parse_8x8block */ /* */ /* Description : This function does the residual parsing of 4 subblocks */ /* in a 8x8 block. */ /* */ /* Inputs : pi2_coeff_block : pointer to residual block where */ /* decoded and inverse scan coefficients are updated */ /* */ /* u4_sub_block_strd : indicates the number of sublocks */ /* in a row. It is 4 for luma and 2 for chroma. */ /* */ /* u4_ctx_cat : inidicates context category for residual */ /* decoding. */ /* */ /* ps_dec : pointer to Decstruct (decoder context) */ /* */ /* pu1_top_nnz : top nnz pointer */ /* */ /* pu1_left_nnz : left nnz pointer */ /* */ /* Globals : No */ /* Processing : Parsing for four subblocks in unrolled, top and left nnz */ /* are updated on the fly. csbp is set in accordance to */ /* decoded numcoeff for the subblock index in raster order */ /* */ /* Outputs : The updated residue buffer, nnzs and csbp current block */ /* */ /* Returns : Returns the coded sub block pattern csbp for the block */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 09 10 2008 Jay Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_cabac_parse_8x8block(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_ctx_cat, dec_struct_t * ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz) { UWORD32 u4_ctxinc, u4_subblock_coded; UWORD32 u4_top0, u4_top1; UWORD32 u4_csbp = 0; UWORD32 u4_idx = 0; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; bin_ctxt_model_t * const ps_cbf = ps_dec->p_cbf_t[u4_ctx_cat]; bin_ctxt_model_t *ps_src_bin_ctxt; bin_ctxt_model_t * const ps_sig_coeff_flag = ps_dec->p_significant_coeff_flag_t[u4_ctx_cat]; UWORD8 *pu1_inv_scan = ps_dec->pu1_inv_scan; /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 0 */ /*------------------------------------------------------*/ u4_ctxinc = ((!!pu1_top_nnz[0]) << 1) + (!!pu1_left_nnz[0]); ps_src_bin_ctxt = ps_cbf + u4_ctxinc; u4_top0 = ih264d_read_coeff4x4_cabac( ps_bitstrm, u4_ctx_cat, ps_sig_coeff_flag, ps_dec, ps_src_bin_ctxt); INSERT_BIT(u4_csbp, u4_idx, u4_top0); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 1 */ /*------------------------------------------------------*/ u4_idx++; pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; u4_ctxinc = ((!!pu1_top_nnz[1]) << 1) + u4_top0; ps_src_bin_ctxt = ps_cbf + u4_ctxinc; u4_top1 = ih264d_read_coeff4x4_cabac(ps_bitstrm, u4_ctx_cat, ps_sig_coeff_flag, ps_dec, ps_src_bin_ctxt); INSERT_BIT(u4_csbp, u4_idx, u4_top1); pu1_left_nnz[0] = u4_top1; /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 2 */ /*------------------------------------------------------*/ u4_idx += (u4_sub_block_strd - 1); pi2_coeff_block += ((u4_sub_block_strd - 1) * NUM_COEFFS_IN_4x4BLK); u4_ctxinc = (u4_top0 << 1) + (!!pu1_left_nnz[1]); ps_src_bin_ctxt = ps_cbf + u4_ctxinc; u4_subblock_coded = ih264d_read_coeff4x4_cabac(ps_bitstrm, u4_ctx_cat, ps_sig_coeff_flag, ps_dec, ps_src_bin_ctxt); INSERT_BIT(u4_csbp, u4_idx, u4_subblock_coded); pu1_top_nnz[0] = u4_subblock_coded; /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 3 */ /*------------------------------------------------------*/ u4_idx++; pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; u4_ctxinc = (u4_top1 << 1) + u4_subblock_coded; ps_src_bin_ctxt = ps_cbf + u4_ctxinc; u4_subblock_coded = ih264d_read_coeff4x4_cabac(ps_bitstrm, u4_ctx_cat, ps_sig_coeff_flag, ps_dec, ps_src_bin_ctxt); INSERT_BIT(u4_csbp, u4_idx, u4_subblock_coded); pu1_top_nnz[1] = pu1_left_nnz[1] = u4_subblock_coded; return (u4_csbp); } /*! ************************************************************************** * \if Function name : ih264d_parse_residual4x4_cabac \endif * * \brief * This function parses CABAC syntax of a Luma and Chroma AC Residuals. * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_residual4x4_cabac(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD8 u1_offset) { UWORD8 u1_cbp = ps_cur_mb_info->u1_cbp; UWORD16 ui16_csbp = 0; WORD16 *pi2_residual_buf; UWORD8 uc_ctx_cat; UWORD8 *pu1_top_nnz = ps_cur_mb_info->ps_curmb->pu1_nnz_y; UWORD8 *pu1_left_nnz = ps_dec->pu1_left_nnz_y; UWORD8 *pu1_top_nnz_uv = ps_cur_mb_info->ps_curmb->pu1_nnz_uv; ctxt_inc_mb_info_t *p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; ctxt_inc_mb_info_t *ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 u4_nbr_avail = ps_dec->u1_mb_ngbr_availablity; WORD16 *pi2_coeff_block = NULL; bin_ctxt_model_t *ps_src_bin_ctxt; UWORD8 u1_top_dc_csbp = (ps_top_ctxt->u1_yuv_dc_csbp) >> 1; UWORD8 u1_left_dc_csbp = (ps_dec->pu1_left_yuv_dc_csbp[0]) >> 1; if(!(u4_nbr_avail & TOP_MB_AVAILABLE_MASK)) { if(p_curr_ctxt->u1_mb_type & CAB_INTRA_MASK) { *(UWORD32 *)pu1_top_nnz = 0; u1_top_dc_csbp = 0; *(UWORD32 *)pu1_top_nnz_uv = 0; } else { *(UWORD32 *)pu1_top_nnz = 0x01010101; u1_top_dc_csbp = 0x3; *(UWORD32 *)pu1_top_nnz_uv = 0x01010101; } } else { UWORD32 *pu4_buf; UWORD8 *pu1_buf; pu1_buf = ps_cur_mb_info->ps_top_mb->pu1_nnz_y; pu4_buf = (UWORD32 *)pu1_buf; *(UWORD32 *)(pu1_top_nnz) = *pu4_buf; pu1_buf = ps_cur_mb_info->ps_top_mb->pu1_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; *(UWORD32 *)(pu1_top_nnz_uv) = *pu4_buf; } if(!(u4_nbr_avail & LEFT_MB_AVAILABLE_MASK)) { if(p_curr_ctxt->u1_mb_type & CAB_INTRA_MASK) { UWORD32 *pu4_buf; UWORD8 *pu1_buf; *(UWORD32 *)pu1_left_nnz = 0; u1_left_dc_csbp = 0; pu1_buf = ps_dec->pu1_left_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0; } else { UWORD32 *pu4_buf; UWORD8 *pu1_buf; *(UWORD32 *)pu1_left_nnz = 0x01010101; u1_left_dc_csbp = 0x3; pu1_buf = ps_dec->pu1_left_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0x01010101; } } uc_ctx_cat = u1_offset ? LUMA_AC_CTXCAT : LUMA_4X4_CTXCAT; ps_cur_mb_info->u1_qp_div6 = ps_dec->u1_qp_y_div6; ps_cur_mb_info->u1_qpc_div6 = ps_dec->u1_qp_u_div6; ps_cur_mb_info->u1_qp_rem6 = ps_dec->u1_qp_y_rem6; ps_cur_mb_info->u1_qpc_rem6 = ps_dec->u1_qp_u_rem6; // CHECK_THIS ps_cur_mb_info->u1_qpcr_div6 = ps_dec->u1_qp_v_div6; ps_cur_mb_info->u1_qpcr_rem6 = ps_dec->u1_qp_v_rem6; if(u1_cbp & 0x0f) { if(ps_cur_mb_info->u1_tran_form8x8 == 0) { /*******************************************************************/ /* Block 0 residual decoding, check cbp and proceed (subblock = 0) */ /*******************************************************************/ if(!(u1_cbp & 0x1)) { *(UWORD16 *)(pu1_top_nnz) = 0; *(UWORD16 *)(pu1_left_nnz) = 0; } else { ui16_csbp = ih264d_cabac_parse_8x8block(pi2_coeff_block, 4, uc_ctx_cat, ps_dec, pu1_top_nnz, pu1_left_nnz); } /*******************************************************************/ /* Block 1 residual decoding, check cbp and proceed (subblock = 2) */ /*******************************************************************/ pi2_coeff_block += (2 * NUM_COEFFS_IN_4x4BLK); if(!(u1_cbp & 0x2)) { *(UWORD16 *)(pu1_top_nnz + 2) = 0; *(UWORD16 *)(pu1_left_nnz) = 0; } else { UWORD32 u4_temp = ih264d_cabac_parse_8x8block(pi2_coeff_block, 4, uc_ctx_cat, ps_dec, (pu1_top_nnz + 2), pu1_left_nnz); ui16_csbp |= (u4_temp << 2); } /*******************************************************************/ /* Block 2 residual decoding, check cbp and proceed (subblock = 8) */ /*******************************************************************/ pi2_coeff_block += (6 * NUM_COEFFS_IN_4x4BLK); if(!(u1_cbp & 0x4)) { *(UWORD16 *)(pu1_top_nnz) = 0; *(UWORD16 *)(pu1_left_nnz + 2) = 0; } else { UWORD32 u4_temp = ih264d_cabac_parse_8x8block( pi2_coeff_block, 4, uc_ctx_cat, ps_dec, pu1_top_nnz, (pu1_left_nnz + 2)); ui16_csbp |= (u4_temp << 8); } /*******************************************************************/ /* Block 3 residual decoding, check cbp and proceed (subblock = 10)*/ /*******************************************************************/ pi2_coeff_block += (2 * NUM_COEFFS_IN_4x4BLK); if(!(u1_cbp & 0x8)) { *(UWORD16 *)(pu1_top_nnz + 2) = 0; *(UWORD16 *)(pu1_left_nnz + 2) = 0; } else { UWORD32 u4_temp = ih264d_cabac_parse_8x8block( pi2_coeff_block, 4, uc_ctx_cat, ps_dec, (pu1_top_nnz + 2), (pu1_left_nnz + 2)); ui16_csbp |= (u4_temp << 10); } } else { ui16_csbp = 0; /*******************************************************************/ /* Block 0 residual decoding, check cbp and proceed (subblock = 0) */ /*******************************************************************/ if(!(u1_cbp & 0x1)) { *(UWORD16 *)(pu1_top_nnz) = 0; *(UWORD16 *)(pu1_left_nnz) = 0; } else { dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; ih264d_read_coeff8x8_cabac( ps_bitstrm, ps_dec, ps_cur_mb_info); pu1_left_nnz[0] = 1; pu1_left_nnz[1] = 1; pu1_top_nnz[0] = 1; pu1_top_nnz[1] = 1; /* added to be used by BS computation module */ ui16_csbp |= 0x0033; } /*******************************************************************/ /* Block 1 residual decoding, check cbp and proceed (subblock = 2) */ /*******************************************************************/ pi2_coeff_block += 64; if(!(u1_cbp & 0x2)) { *(UWORD16 *)(pu1_top_nnz + 2) = 0; *(UWORD16 *)(pu1_left_nnz) = 0; } else { dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; ih264d_read_coeff8x8_cabac(ps_bitstrm, ps_dec, ps_cur_mb_info); pu1_left_nnz[0] = 1; pu1_left_nnz[1] = 1; pu1_top_nnz[2] = 1; pu1_top_nnz[3] = 1; /* added to be used by BS computation module */ ui16_csbp |= 0x00CC; } /*******************************************************************/ /* Block 2 residual decoding, check cbp and proceed (subblock = 8) */ /*******************************************************************/ pi2_coeff_block += 64; if(!(u1_cbp & 0x4)) { *(UWORD16 *)(pu1_top_nnz) = 0; *(UWORD16 *)(pu1_left_nnz + 2) = 0; } else { dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; ih264d_read_coeff8x8_cabac(ps_bitstrm, ps_dec, ps_cur_mb_info); pu1_left_nnz[2] = 1; pu1_left_nnz[3] = 1; pu1_top_nnz[0] = 1; pu1_top_nnz[1] = 1; /* added to be used by BS computation module */ ui16_csbp |= 0x3300; } /*******************************************************************/ /* Block 3 residual decoding, check cbp and proceed (subblock = 10)*/ /*******************************************************************/ pi2_coeff_block += 64; if(!(u1_cbp & 0x8)) { *(UWORD16 *)(pu1_top_nnz + 2) = 0; *(UWORD16 *)(pu1_left_nnz + 2) = 0; } else { dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; ih264d_read_coeff8x8_cabac(ps_bitstrm, ps_dec, ps_cur_mb_info); pu1_left_nnz[2] = 1; pu1_left_nnz[3] = 1; pu1_top_nnz[2] = 1; pu1_top_nnz[3] = 1; /* added to be used by BS computation module */ ui16_csbp |= 0xCC00; } } } else { *(UWORD32 *)(pu1_top_nnz) = 0; *(UWORD32 *)(pu1_left_nnz) = 0; } /*--------------------------------------------------------------------*/ /* Store the last row of N values to top row */ /*--------------------------------------------------------------------*/ ps_cur_mb_info->u2_luma_csbp = ui16_csbp; ps_cur_mb_info->ps_curmb->u2_luma_csbp = ui16_csbp; { WORD8 i; UWORD16 u2_chroma_csbp = 0; ps_cur_mb_info->u2_chroma_csbp = 0; u1_cbp >>= 4; pu1_top_nnz = pu1_top_nnz_uv; pu1_left_nnz = ps_dec->pu1_left_nnz_uv; /*--------------------------------------------------------------------*/ /* if Chroma Component not present OR no ac values present */ /* Set the values of N to zero */ /*--------------------------------------------------------------------*/ if(u1_cbp == CBPC_ALLZERO) { ps_dec->pu1_left_yuv_dc_csbp[0] &= 0x1; *(UWORD32 *)(pu1_top_nnz) = 0; *(UWORD32 *)(pu1_left_nnz) = 0; p_curr_ctxt->u1_yuv_dc_csbp &= 0x1; return (0); } /*--------------------------------------------------------------------*/ /* Decode Chroma DC values */ /*--------------------------------------------------------------------*/ for(i = 0; i < 2; i++) { UWORD8 uc_a = 1, uc_b = 1; UWORD32 u4_ctx_inc; UWORD8 uc_codedBlockFlag; UWORD8 pu1_inv_scan[4] = { 0, 1, 2, 3 }; WORD32 u4_scale; WORD32 i4_mb_inter_inc; tu_sblk4x4_coeff_data_t *ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; WORD16 *pi2_coeff_data = (WORD16 *)ps_dec->pv_parse_tu_coeff_data; WORD16 ai2_dc_coef[4]; INC_SYM_COUNT(&(ps_dec->s_cab_dec_env)); u4_scale = (i) ? (ps_dec->pu2_quant_scale_v[0] << ps_dec->u1_qp_v_div6) : (ps_dec->pu2_quant_scale_u[0] << ps_dec->u1_qp_u_div6); /*--------------------------------------------------------------------*/ /* Decode Bitstream to get the DC coeff */ /*--------------------------------------------------------------------*/ uc_a = (u1_left_dc_csbp >> i) & 0x01; uc_b = (u1_top_dc_csbp >> i) & 0x01; u4_ctx_inc = (uc_a + (uc_b << 1)); ps_src_bin_ctxt = (ps_dec->p_cbf_t[CHROMA_DC_CTXCAT]) + u4_ctx_inc; uc_codedBlockFlag = ih264d_read_coeff4x4_cabac(ps_bitstrm, CHROMA_DC_CTXCAT, ps_dec->p_significant_coeff_flag_t[CHROMA_DC_CTXCAT], ps_dec, ps_src_bin_ctxt); i4_mb_inter_inc = (!((ps_cur_mb_info->ps_curmb->u1_mb_type == I_4x4_MB) || (ps_cur_mb_info->ps_curmb->u1_mb_type == I_16x16_MB))) * 3; if(ps_dec->s_high_profile.u1_scaling_present) { u4_scale *= ps_dec->s_high_profile.i2_scalinglist4x4[i4_mb_inter_inc + 1 + i][0]; } else { u4_scale <<= 4; } if(uc_codedBlockFlag) { WORD32 i_z0, i_z1, i_z2, i_z3; WORD32 *pi4_scale; SET_BIT(u1_top_dc_csbp, i); SET_BIT(u1_left_dc_csbp, i); ai2_dc_coef[0] = 0; ai2_dc_coef[1] = 0; ai2_dc_coef[2] = 0; ai2_dc_coef[3] = 0; ih264d_unpack_coeff4x4_dc_4x4blk(ps_tu_4x4, ai2_dc_coef, pu1_inv_scan); i_z0 = (ai2_dc_coef[0] + ai2_dc_coef[2]); i_z1 = (ai2_dc_coef[0] - ai2_dc_coef[2]); i_z2 = (ai2_dc_coef[1] - ai2_dc_coef[3]); i_z3 = (ai2_dc_coef[1] + ai2_dc_coef[3]); /*-----------------------------------------------------------*/ /* Scaling and storing the values back */ /*-----------------------------------------------------------*/ *pi2_coeff_data++ = ((i_z0 + i_z3) * u4_scale) >> 5; *pi2_coeff_data++ = ((i_z0 - i_z3) * u4_scale) >> 5; *pi2_coeff_data++ = ((i_z1 + i_z2) * u4_scale) >> 5; *pi2_coeff_data++ = ((i_z1 - i_z2) * u4_scale) >> 5; ps_dec->pv_parse_tu_coeff_data = (void *)pi2_coeff_data; SET_BIT(ps_cur_mb_info->u1_yuv_dc_block_flag,(i+1)); } else { CLEARBIT(u1_top_dc_csbp, i); CLEARBIT(u1_left_dc_csbp, i); } } /*********************************************************************/ /* Update the DC csbp */ /*********************************************************************/ ps_dec->pu1_left_yuv_dc_csbp[0] &= 0x1; p_curr_ctxt->u1_yuv_dc_csbp &= 0x1; ps_dec->pu1_left_yuv_dc_csbp[0] |= (u1_left_dc_csbp << 1); p_curr_ctxt->u1_yuv_dc_csbp |= (u1_top_dc_csbp << 1); if(u1_cbp == CBPC_ACZERO) { *(UWORD32 *)(pu1_top_nnz) = 0; *(UWORD32 *)(pu1_left_nnz) = 0; return (0); } /*--------------------------------------------------------------------*/ /* Decode Chroma AC values */ /*--------------------------------------------------------------------*/ { UWORD32 u4_temp; /*****************************************************************/ /* U Block residual decoding, check cbp and proceed (subblock=0)*/ /*****************************************************************/ u2_chroma_csbp = ih264d_cabac_parse_8x8block(pi2_coeff_block, 2, CHROMA_AC_CTXCAT, ps_dec, pu1_top_nnz, pu1_left_nnz); pi2_coeff_block += MB_CHROM_SIZE; /*****************************************************************/ /* V Block residual decoding, check cbp and proceed (subblock=1)*/ /*****************************************************************/ u4_temp = ih264d_cabac_parse_8x8block(pi2_coeff_block, 2, CHROMA_AC_CTXCAT, ps_dec, (pu1_top_nnz + 2), (pu1_left_nnz + 2)); u2_chroma_csbp |= (u4_temp << 4); } /*********************************************************************/ /* Update the AC csbp */ /*********************************************************************/ ps_cur_mb_info->u2_chroma_csbp = u2_chroma_csbp; } return (0); } ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_cabac.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! *************************************************************************** * \file ih264d_parse_cabac.h * * \brief * This file contains cabac Residual decoding routines. * * \date * 20/03/2003 * * \author NS *************************************************************************** */ #ifndef _IH264D_PARSE_CABAC_H_ #define _IH264D_PARSE_CABAC_H_ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #define UCOFF_LEVEL 14 UWORD8 ih264d_read_coeff4x4_cabac(dec_bit_stream_t *ps_bitstrm, UWORD32 u4_ctxcat, bin_ctxt_model_t *ps_ctxt_sig_coeff, dec_struct_t *ps_dec, bin_ctxt_model_t *ps_ctxt_coded); void ih264d_read_coeff8x8_cabac(dec_bit_stream_t *ps_bitstrm, dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info); UWORD32 cabac_parse_8x8block_transform8x8_set(WORD16 *pi2_coeff_block, dec_struct_t * ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, dec_mb_info_t *ps_cur_mb_info); #endif /* _IH264D_PARSE_CABAC_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_cavlc.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! *************************************************************************** * \file ih264d_parse_cavlc.c * * \brief * This file contains UVLC related functions. * * \date * 20/11/2002 * * \author NS *************************************************************************** */ #include <string.h> #include <stdio.h> #include "ih264d_bitstrm.h" #include "ih264d_parse_cavlc.h" #include "ih264d_error_handler.h" #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_cabac.h" #include "ih264d_structs.h" #include "ih264d_tables.h" #include "ih264d_tables.h" #include "ih264d_mb_utils.h" void ih264d_unpack_coeff4x4_dc_4x4blk(tu_sblk4x4_coeff_data_t *ps_tu_4x4, WORD16 *pi2_out_coeff_data, UWORD8 *pu1_inv_scan); /*****************************************************************************/ /* */ /* Function Name : ih264d_uev */ /* */ /* Description : Reads the unsigned Exp Golomb codec syntax from the */ /* ps_bitstrm as specified in section 9.1 of H264 standard */ /* It also increases bitstream u4_ofst by the number of bits */ /* parsed for UEV decode operation */ /* */ /* Inputs : bitstream base pointer and bitsream u4_ofst in bits */ /* Globals : None */ /* Processing : */ /* Outputs : UEV decoded syntax element and incremented ps_bitstrm u4_ofst */ /* Returns : UEV decoded syntax element */ /* */ /* Issues : Does not check if ps_bitstrm u4_ofst exceeds max ps_bitstrm i4_size */ /* for performamce. Caller might have to do error resilence */ /* check for bitstream overflow */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 19 09 2008 Jay Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_uev(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf) { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; return ((1 << u4_ldz) + u4_word - 1); } /*****************************************************************************/ /* */ /* Function Name : ih264d_sev */ /* */ /* Description : Reads the signed Exp Golomb codec syntax from the ps_bitstrm */ /* as specified in section 9.1 of H264 standard. */ /* It also increases bitstream u4_ofst by the number of bits */ /* parsed for SEV decode operation */ /* */ /* Inputs : bitstream base pointer and bitsream u4_ofst in bits */ /* Globals : None */ /* Processing : */ /* Outputs : SEV decoded syntax element and incremented ps_bitstrm u4_ofst */ /* Returns : SEV decoded syntax element */ /* */ /* Issues : Does not check if ps_bitstrm u4_ofst exceeds max ps_bitstrm i4_size */ /* for performamce. Caller might have to do error resilence */ /* check for bitstream overflow */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 19 09 2008 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_sev(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf) { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_abs_val; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; if(u4_word & 0x1) return (-(WORD32)u4_abs_val); else return (u4_abs_val); } /*****************************************************************************/ /* */ /* Function Name : get_tev_range_1 */ /* */ /* Description : Reads the TEV Exp Golomb codec syntax from the ps_bitstrm */ /* as specified in section 9.1 of H264 standard. This will */ /* called only when the input range is 1 for TEV decode. */ /* If range is more than 1, then UEV decode is done */ /* */ /* Inputs : bitstream base pointer and bitsream u4_ofst in bits */ /* Globals : None */ /* Processing : */ /* Outputs : TEV decoded syntax element and incremented ps_bitstrm u4_ofst */ /* Returns : TEV decoded syntax element */ /* */ /* Issues : Does not check if ps_bitstrm u4_ofst exceeds max ps_bitstrm i4_size */ /* for performamce. Caller might have to do error resilence */ /* check for bitstream overflow */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 19 09 2008 Jay Draft */ /* */ /*****************************************************************************/ UWORD32 ih264d_tev_range1(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf) { UWORD32 u4_code; GETBIT(u4_code, *pu4_bitstrm_ofst, pu4_bitstrm_buf); return (!u4_code); } /*! ************************************************************************** * \if Function name : ih264d_uvlc \endif * * \brief * * Reads the unsigned/signed/truncated integer Exp-Golomb-coded syntax element * with the left bit first. The parsing process for this descriptor is specified * in subclause 9.1. * * \param ps_bitstrm : Pointer to Bitstream Structure . * \param u4_range : Range value in case of Truncated Exp-Golomb-code * \param pi_bitstrm_ofst : Pointer to the local copy of Bitstream u4_ofst * \param u1_flag : Flag indicating the case of UEV,SEV or TEV * \param u4_bitstrm_ofst : Local copy of Bitstream u4_ofst * \param pu4_bitstrm_buf : Pointer to the Bitstream buffer * * \return * Returns Code Value. * ************************************************************************** */ WORD32 ih264d_uvlc(dec_bit_stream_t *ps_bitstrm, UWORD32 u4_range, UWORD32 *pi_bitstrm_ofst, UWORD8 u1_flag, UWORD32 u4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf) { UWORD32 word, word2, cur_bit, cur_word, code_val, code_num, clz; SWITCHOFFTRACE; cur_bit = u4_bitstrm_ofst & 0x1F; cur_word = u4_bitstrm_ofst >> 5; word = pu4_bitstrm_buf[cur_word]; word2 = pu4_bitstrm_buf[cur_word + 1]; if(cur_bit != 0) { word <<= cur_bit; word2 >>= (32 - cur_bit); word |= word2; } if(u1_flag == TEV && u4_range == 1) { word >>= 31; word = 1 - word; (*pi_bitstrm_ofst)++; ps_bitstrm->u4_ofst = *pi_bitstrm_ofst; return (WORD32)word; } //finding clz { UWORD32 ui32_code, ui32_mask; ui32_code = word; ui32_mask = 0x80000000; clz = 0; /* DSP implements this with LMBD instruction */ /* so there we don't need to break the loop */ while(!(ui32_code & ui32_mask)) { clz++; ui32_mask >>= 1; if(0 == ui32_mask) break; } } if(clz == 0) { *pi_bitstrm_ofst = *pi_bitstrm_ofst + (2 * clz) + 1; ps_bitstrm->u4_ofst = *pi_bitstrm_ofst; return 0; } word <<= (clz + 1); word >>= (32 - clz); code_num = (1 << clz) + word - 1; *pi_bitstrm_ofst = *pi_bitstrm_ofst + (2 * clz) + 1; ps_bitstrm->u4_ofst = *pi_bitstrm_ofst; if(u1_flag == TEV || u1_flag == UEV) return (WORD32)code_num; code_val = (code_num + 1) >> 1; if(!(code_num & 0x01)) return -((WORD32)code_val); return (WORD32)code_val; } /*****************************************************************************/ /* */ /* Function Name : ih264d_cavlc_4x4res_block_totalcoeff_1 */ /* */ /* Description : This function does cavlc decoding of 4x4 block residual */ /* coefficient when total coeff is equal to 1. The parsing */ /* is done as defined in section 9.2.2 and 9.2.3 of the */ /* H264 standard. */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 25 09 2008 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_cavlc_4x4res_block_totalcoeff_1(UWORD32 u4_isdc, UWORD32 u4_total_coeff_trail_one, dec_bit_stream_t *ps_bitstrm) { UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; UWORD32 u4_trailing_ones = u4_total_coeff_trail_one & 0xFFFF; WORD32 i2_level; UWORD32 u4_tot_zero, u4_ldz, u4_scan_pos; tu_sblk4x4_coeff_data_t *ps_tu_4x4; WORD16 *pi2_coeff_data; dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; ps_tu_4x4->u2_sig_coeff_map = 0; pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; if(u4_trailing_ones) { UWORD32 u4_sign; /****************************************************************/ /* Decode Trailing One as in section 9.2.2 */ /****************************************************************/ GETBIT(u4_sign, u4_bitstream_offset, pu4_bitstrm_buf); i2_level = u4_sign ? -1 : 1; } else { /****************************************************************/ /* Decoding Level based on prefix and suffix as in 9.2.2 */ /****************************************************************/ UWORD32 u4_lev_suffix, u4_lev_suffix_size; WORD32 u2_lev_code, u2_abs_value; UWORD32 u4_lev_prefix; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, pu4_bitstrm_buf); u2_lev_code = (2 + MIN(u4_lev_prefix, 15)); if(14 == u4_lev_prefix) u4_lev_suffix_size = 4; else if(15 <= u4_lev_prefix) { u2_lev_code += 15; u4_lev_suffix_size = u4_lev_prefix - 3; } else u4_lev_suffix_size = 0; //HP_LEVEL_PREFIX if(16 <= u4_lev_prefix) { u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); } if(u4_lev_suffix_size) { GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, u4_lev_suffix_size); u2_lev_code += u4_lev_suffix; } u2_abs_value = (u2_lev_code + 2) >> 1; /*********************************************************/ /* If Level code is odd, level is negative else positive */ /*********************************************************/ i2_level = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; } /****************************************************************/ /* Decoding total zeros as in section 9.2.3, table 9.7 */ /****************************************************************/ FIND_ONE_IN_STREAM_LEN(u4_ldz, u4_bitstream_offset, pu4_bitstrm_buf, 8); if(u4_ldz) { GETBIT(u4_tot_zero, u4_bitstream_offset, pu4_bitstrm_buf); u4_tot_zero = (u4_ldz << 1) - u4_tot_zero; } else u4_tot_zero = 0; /***********************************************************************/ /* Inverse scan and store residual coeff. Update the bitstream u4_ofst */ /***********************************************************************/ u4_scan_pos = u4_tot_zero + u4_isdc; if(u4_scan_pos > 15) return -1; SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); *pi2_coeff_data++ = i2_level; { WORD32 offset; offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; offset = ALIGN4(offset); ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); } ps_bitstrm->u4_ofst = u4_bitstream_offset; return 0; } /*****************************************************************************/ /* */ /* Function Name : ih264d_cavlc_4x4res_block_totalcoeff_2to10 */ /* */ /* Description : This function does cavlc decoding of 4x4 block residual */ /* coefficient when total coeffs are between two and ten */ /* inclusive. Parsing is done as defined in section 9.2.2 */ /* and 9.2.3 the H264 standard. */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 25 09 2008 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_cavlc_4x4res_block_totalcoeff_2to10(UWORD32 u4_isdc, UWORD32 u4_total_coeff_trail_one, /*!<TotalCoefficients<<16+trailingones*/ dec_bit_stream_t *ps_bitstrm) { UWORD32 u4_total_zeroes; WORD32 i; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; UWORD32 u4_trailing_ones = u4_total_coeff_trail_one & 0xFFFF; UWORD32 u4_total_coeff = u4_total_coeff_trail_one >> 16; // To avoid error check at 4x4 level, allocating for 3 extra levels(16+3) // since u4_trailing_ones can at the max be 3. This will be required when // u4_total_coeff is less than u4_trailing_ones WORD16 ai2_level_arr[19]; WORD16 *i2_level_arr = &ai2_level_arr[3]; tu_sblk4x4_coeff_data_t *ps_tu_4x4; WORD16 *pi2_coeff_data; dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; ps_tu_4x4->u2_sig_coeff_map = 0; pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; i = u4_total_coeff - 1; if(u4_trailing_ones) { /*********************************************************************/ /* Decode Trailing Ones */ /* read the sign of T1's and put them in level array */ /*********************************************************************/ UWORD32 u4_signs, u4_cnt = u4_trailing_ones; WORD16 (*ppi2_trlone_lkup)[3] = (WORD16 (*)[3])gai2_ih264d_trailing_one_level; WORD16 *pi2_trlone_lkup; GETBITS(u4_signs, u4_bitstream_offset, pu4_bitstrm_buf, u4_cnt); pi2_trlone_lkup = ppi2_trlone_lkup[(1 << u4_cnt) - 2 + u4_signs]; while(u4_cnt) { i2_level_arr[i--] = *pi2_trlone_lkup++; u4_cnt--; } } /****************************************************************/ /* Decoding Levels Begins */ /****************************************************************/ if(i >= 0) { /****************************************************************/ /* First level is decoded outside the loop as it has lot of */ /* special cases. */ /****************************************************************/ UWORD32 u4_lev_suffix, u4_suffix_len, u4_lev_suffix_size; WORD32 u2_lev_code, u2_abs_value; UWORD32 u4_lev_prefix; /***************************************************************/ /* u4_suffix_len = 0, Find leading zeros in next 32 bits */ /***************************************************************/ FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, pu4_bitstrm_buf); /*********************************************************/ /* Special decoding case when trailing ones are 3 */ /*********************************************************/ u2_lev_code = MIN(15, u4_lev_prefix); u2_lev_code += (3 == u4_trailing_ones) ? 0 : 2; if(14 == u4_lev_prefix) u4_lev_suffix_size = 4; else if(15 <= u4_lev_prefix) { u2_lev_code += 15; u4_lev_suffix_size = u4_lev_prefix - 3; } else u4_lev_suffix_size = 0; //HP_LEVEL_PREFIX if(16 <= u4_lev_prefix) { u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); } if(u4_lev_suffix_size) { GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, u4_lev_suffix_size); u2_lev_code += u4_lev_suffix; } u2_abs_value = (u2_lev_code + 2) >> 1; /*********************************************************/ /* If Level code is odd, level is negative else positive */ /*********************************************************/ i2_level_arr[i--] = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; u4_suffix_len = (u2_abs_value > 3) ? 2 : 1; /*********************************************************/ /* Now loop over the remaining levels */ /*********************************************************/ while(i >= 0) { /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, pu4_bitstrm_buf); u4_lev_suffix_size = (15 <= u4_lev_prefix) ? (u4_lev_prefix - 3) : u4_suffix_len; /*********************************************************/ /* Compute level code using prefix and suffix */ /*********************************************************/ GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, u4_lev_suffix_size); u2_lev_code = (MIN(15,u4_lev_prefix) << u4_suffix_len) + u4_lev_suffix; //HP_LEVEL_PREFIX if(16 <= u4_lev_prefix) { u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); } u2_abs_value = (u2_lev_code + 2) >> 1; /*********************************************************/ /* If Level code is odd, level is negative else positive */ /*********************************************************/ i2_level_arr[i--] = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; /*********************************************************/ /* Increment suffix length if required */ /*********************************************************/ u4_suffix_len += (u4_suffix_len < 6) ? (u2_abs_value > (3 << (u4_suffix_len - 1))) : 0; } /****************************************************************/ /* Decoding Levels Ends */ /****************************************************************/ } /****************************************************************/ /* Decoding total zeros as in section 9.2.3, table 9.7 */ /****************************************************************/ { UWORD32 u4_index; const UWORD8 (*ppu1_total_zero_lkup)[64] = (const UWORD8 (*)[64])gau1_ih264d_table_total_zero_2to10; NEXTBITS(u4_index, u4_bitstream_offset, pu4_bitstrm_buf, 6); u4_total_zeroes = ppu1_total_zero_lkup[u4_total_coeff - 2][u4_index]; FLUSHBITS(u4_bitstream_offset, (u4_total_zeroes >> 4)); u4_total_zeroes &= 0xf; } /**************************************************************/ /* Decode the runs and form the coefficient buffer */ /**************************************************************/ { const UWORD8 *pu1_table_runbefore; UWORD32 u4_run; WORD32 k; WORD32 u4_scan_pos = u4_total_coeff + u4_total_zeroes - 1 + u4_isdc; WORD32 u4_zeroes_left = u4_total_zeroes; k = u4_total_coeff - 1; /**************************************************************/ /* Decoding Runs Begin for zeros left > 6 */ /**************************************************************/ while((u4_zeroes_left > 6) && k) { UWORD32 u4_code; NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 3); if(u4_code != 0) { FLUSHBITS(u4_bitstream_offset, 3); u4_run = (7 - u4_code); } else { FIND_ONE_IN_STREAM_LEN(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 11); u4_run = (4 + u4_code); } SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); *pi2_coeff_data++ = i2_level_arr[k--]; u4_zeroes_left -= (WORD32)u4_run; u4_scan_pos -= (WORD32)(u4_run + 1); } if (u4_zeroes_left < 0 || u4_scan_pos < 0) return -1; /**************************************************************/ /* Decoding Runs for 0 < zeros left <=6 */ /**************************************************************/ pu1_table_runbefore = (UWORD8 *)gau1_ih264d_table_run_before; while((u4_zeroes_left > 0) && k) { UWORD32 u4_code; NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 3); u4_code = pu1_table_runbefore[u4_code + (u4_zeroes_left << 3)]; u4_run = u4_code >> 2; FLUSHBITS(u4_bitstream_offset, (u4_code & 0x03)); SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); *pi2_coeff_data++ = i2_level_arr[k--]; u4_zeroes_left -= (WORD32)u4_run; u4_scan_pos -= (WORD32)(u4_run + 1); } if (u4_zeroes_left < 0 || u4_scan_pos < 0) return -1; /**************************************************************/ /* Decoding Runs End */ /**************************************************************/ /**************************************************************/ /* Copy the remaining coefficients */ /**************************************************************/ while(k >= 0) { SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); *pi2_coeff_data++ = i2_level_arr[k--]; u4_scan_pos--; } } { WORD32 offset; offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; offset = ALIGN4(offset); ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); } ps_bitstrm->u4_ofst = u4_bitstream_offset; return 0; } /*****************************************************************************/ /* */ /* Function Name : ih264d_cavlc_4x4res_block_totalcoeff_11to16 */ /* */ /* Description : This function does cavlc decoding of 4x4 block residual */ /* coefficient when total coeffs are greater than ten. */ /* Parsing is done as defined in section 9.2.2 and 9.2.3 of */ /* the H264 standard. */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 25 09 2008 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_cavlc_4x4res_block_totalcoeff_11to16(UWORD32 u4_isdc, UWORD32 u4_total_coeff_trail_one, /*!<TotalCoefficients<<16+trailingones*/ dec_bit_stream_t *ps_bitstrm ) { UWORD32 u4_total_zeroes; WORD32 i; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; UWORD32 u4_trailing_ones = u4_total_coeff_trail_one & 0xFFFF; UWORD32 u4_total_coeff = u4_total_coeff_trail_one >> 16; // To avoid error check at 4x4 level, allocating for 3 extra levels(16+3) // since u4_trailing_ones can at the max be 3. This will be required when // u4_total_coeff is less than u4_trailing_ones WORD16 ai2_level_arr[19];// WORD16 *i2_level_arr = &ai2_level_arr[3]; tu_sblk4x4_coeff_data_t *ps_tu_4x4; WORD16 *pi2_coeff_data; dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; ps_tu_4x4->u2_sig_coeff_map = 0; pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; i = u4_total_coeff - 1; if(u4_trailing_ones) { /*********************************************************************/ /* Decode Trailing Ones */ /* read the sign of T1's and put them in level array */ /*********************************************************************/ UWORD32 u4_signs, u4_cnt = u4_trailing_ones; WORD16 (*ppi2_trlone_lkup)[3] = (WORD16 (*)[3])gai2_ih264d_trailing_one_level; WORD16 *pi2_trlone_lkup; GETBITS(u4_signs, u4_bitstream_offset, pu4_bitstrm_buf, u4_cnt); pi2_trlone_lkup = ppi2_trlone_lkup[(1 << u4_cnt) - 2 + u4_signs]; while(u4_cnt) { i2_level_arr[i--] = *pi2_trlone_lkup++; u4_cnt--; } } /****************************************************************/ /* Decoding Levels Begins */ /****************************************************************/ if(i >= 0) { /****************************************************************/ /* First level is decoded outside the loop as it has lot of */ /* special cases. */ /****************************************************************/ UWORD32 u4_lev_suffix, u4_suffix_len, u4_lev_suffix_size; UWORD16 u2_lev_code, u2_abs_value; UWORD32 u4_lev_prefix; if(u4_trailing_ones < 3) { /*********************************************************/ /* u4_suffix_len = 1 */ /*********************************************************/ /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, pu4_bitstrm_buf); u4_lev_suffix_size = (15 <= u4_lev_prefix) ? (u4_lev_prefix - 3) : 1; GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, u4_lev_suffix_size); u2_lev_code = 2 + (MIN(u4_lev_prefix,15) << 1) + u4_lev_suffix; //HP_LEVEL_PREFIX if(16 <= u4_lev_prefix) { u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); } } else { /*********************************************************/ /*u4_suffix_len = 0 */ /*********************************************************/ /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, pu4_bitstrm_buf); /*********************************************************/ /* Special decoding case when trailing ones are 3 */ /*********************************************************/ u2_lev_code = MIN(15, u4_lev_prefix); u2_lev_code += (3 == u4_trailing_ones) ? 0 : (2); if(14 == u4_lev_prefix) u4_lev_suffix_size = 4; else if(15 <= u4_lev_prefix) { u2_lev_code += 15; u4_lev_suffix_size = (u4_lev_prefix - 3); } else u4_lev_suffix_size = 0; //HP_LEVEL_PREFIX if(16 <= u4_lev_prefix) { u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); } if(u4_lev_suffix_size) { GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, u4_lev_suffix_size); u2_lev_code += u4_lev_suffix; } } u2_abs_value = (u2_lev_code + 2) >> 1; /*********************************************************/ /* If Level code is odd, level is negative else positive */ /*********************************************************/ i2_level_arr[i--] = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; u4_suffix_len = (u2_abs_value > 3) ? 2 : 1; /*********************************************************/ /* Now loop over the remaining levels */ /*********************************************************/ while(i >= 0) { /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, pu4_bitstrm_buf); u4_lev_suffix_size = (15 <= u4_lev_prefix) ? (u4_lev_prefix - 3) : u4_suffix_len; /*********************************************************/ /* Compute level code using prefix and suffix */ /*********************************************************/ GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, u4_lev_suffix_size); u2_lev_code = (MIN(15,u4_lev_prefix) << u4_suffix_len) + u4_lev_suffix; //HP_LEVEL_PREFIX if(16 <= u4_lev_prefix) { u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); } u2_abs_value = (u2_lev_code + 2) >> 1; /*********************************************************/ /* If Level code is odd, level is negative else positive */ /*********************************************************/ i2_level_arr[i--] = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; /*********************************************************/ /* Increment suffix length if required */ /*********************************************************/ u4_suffix_len += (u4_suffix_len < 6) ? (u2_abs_value > (3 << (u4_suffix_len - 1))) : 0; } /****************************************************************/ /* Decoding Levels Ends */ /****************************************************************/ } if(u4_total_coeff < (16 - u4_isdc)) { UWORD32 u4_index; const UWORD8 (*ppu1_total_zero_lkup)[16] = (const UWORD8 (*)[16])gau1_ih264d_table_total_zero_11to15; NEXTBITS(u4_index, u4_bitstream_offset, pu4_bitstrm_buf, 4); u4_total_zeroes = ppu1_total_zero_lkup[u4_total_coeff - 11][u4_index]; FLUSHBITS(u4_bitstream_offset, (u4_total_zeroes >> 4)); u4_total_zeroes &= 0xf; } else u4_total_zeroes = 0; /**************************************************************/ /* Decode the runs and form the coefficient buffer */ /**************************************************************/ { const UWORD8 *pu1_table_runbefore; UWORD32 u4_run; WORD32 k; WORD32 u4_scan_pos = u4_total_coeff + u4_total_zeroes - 1 + u4_isdc; WORD32 u4_zeroes_left = u4_total_zeroes; k = u4_total_coeff - 1; /**************************************************************/ /* Decoding Runs for 0 < zeros left <=6 */ /**************************************************************/ pu1_table_runbefore = (UWORD8 *)gau1_ih264d_table_run_before; while((u4_zeroes_left > 0) && k) { UWORD32 u4_code; NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 3); u4_code = pu1_table_runbefore[u4_code + (u4_zeroes_left << 3)]; u4_run = u4_code >> 2; FLUSHBITS(u4_bitstream_offset, (u4_code & 0x03)); SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); *pi2_coeff_data++ = i2_level_arr[k--]; u4_zeroes_left -= (WORD32)u4_run; u4_scan_pos -= (WORD32)(u4_run + 1); } if (u4_zeroes_left < 0 || u4_scan_pos < 0) return -1; /**************************************************************/ /* Decoding Runs End */ /**************************************************************/ /**************************************************************/ /* Copy the remaining coefficients */ /**************************************************************/ while(k >= 0) { SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); *pi2_coeff_data++ = i2_level_arr[k--]; u4_scan_pos--; } } { WORD32 offset; offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; offset = ALIGN4(offset); ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); } ps_bitstrm->u4_ofst = u4_bitstream_offset; return 0; } /*****************************************************************************/ /* */ /* Function Name : ih264d_rest_of_residual_cav_chroma_dc_block */ /* */ /* Description : This function does the Cavlc parsing of the bitstream */ /* for chroma dc coefficients */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 15 09 2008 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_rest_of_residual_cav_chroma_dc_block(UWORD32 u4_total_coeff_trail_one, dec_bit_stream_t *ps_bitstrm) { UWORD32 u4_total_zeroes; WORD16 i; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; UWORD32 u4_trailing_ones = u4_total_coeff_trail_one & 0xFFFF; UWORD32 u4_total_coeff = u4_total_coeff_trail_one >> 16; // To avoid error check at 4x4 level, allocating for 3 extra levels(4+3) // since u4_trailing_ones can at the max be 3. This will be required when // u4_total_coeff is less than u4_trailing_ones WORD16 ai2_level_arr[7];// WORD16 *i2_level_arr = &ai2_level_arr[3]; tu_sblk4x4_coeff_data_t *ps_tu_4x4; WORD16 *pi2_coeff_data; dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; ps_tu_4x4->u2_sig_coeff_map = 0; pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; i = u4_total_coeff - 1; if(u4_trailing_ones) { /*********************************************************************/ /* Decode Trailing Ones */ /* read the sign of T1's and put them in level array */ /*********************************************************************/ UWORD32 u4_signs, u4_cnt = u4_trailing_ones; WORD16 (*ppi2_trlone_lkup)[3] = (WORD16 (*)[3])gai2_ih264d_trailing_one_level; WORD16 *pi2_trlone_lkup; GETBITS(u4_signs, u4_bitstream_offset, pu4_bitstrm_buf, u4_cnt); pi2_trlone_lkup = ppi2_trlone_lkup[(1 << u4_cnt) - 2 + u4_signs]; while(u4_cnt) { i2_level_arr[i--] = *pi2_trlone_lkup++; u4_cnt--; } } /****************************************************************/ /* Decoding Levels Begins */ /****************************************************************/ if(i >= 0) { /****************************************************************/ /* First level is decoded outside the loop as it has lot of */ /* special cases. */ /****************************************************************/ UWORD32 u4_lev_suffix, u4_suffix_len, u4_lev_suffix_size; UWORD16 u2_lev_code, u2_abs_value; UWORD32 u4_lev_prefix; /***************************************************************/ /* u4_suffix_len = 0, Find leading zeros in next 32 bits */ /***************************************************************/ FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, pu4_bitstrm_buf); /*********************************************************/ /* Special decoding case when trailing ones are 3 */ /*********************************************************/ u2_lev_code = MIN(15, u4_lev_prefix); u2_lev_code += (3 == u4_trailing_ones) ? 0 : (2); if(14 == u4_lev_prefix) u4_lev_suffix_size = 4; else if(15 <= u4_lev_prefix) { u2_lev_code += 15; u4_lev_suffix_size = u4_lev_prefix - 3; } else u4_lev_suffix_size = 0; //HP_LEVEL_PREFIX if(16 <= u4_lev_prefix) { u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); } if(u4_lev_suffix_size) { GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, u4_lev_suffix_size); u2_lev_code += u4_lev_suffix; } u2_abs_value = (u2_lev_code + 2) >> 1; /*********************************************************/ /* If Level code is odd, level is negative else positive */ /*********************************************************/ i2_level_arr[i--] = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; u4_suffix_len = (u2_abs_value > 3) ? 2 : 1; /*********************************************************/ /* Now loop over the remaining levels */ /*********************************************************/ while(i >= 0) { /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, pu4_bitstrm_buf); u4_lev_suffix_size = (15 <= u4_lev_prefix) ? (u4_lev_prefix - 3) : u4_suffix_len; /*********************************************************/ /* Compute level code using prefix and suffix */ /*********************************************************/ GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, u4_lev_suffix_size); u2_lev_code = (MIN(u4_lev_prefix,15) << u4_suffix_len) + u4_lev_suffix; //HP_LEVEL_PREFIX if(16 <= u4_lev_prefix) { u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); } u2_abs_value = (u2_lev_code + 2) >> 1; /*********************************************************/ /* If Level code is odd, level is negative else positive */ /*********************************************************/ i2_level_arr[i--] = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; /*********************************************************/ /* Increment suffix length if required */ /*********************************************************/ u4_suffix_len += (u2_abs_value > (3 << (u4_suffix_len - 1))); } /****************************************************************/ /* Decoding Levels Ends */ /****************************************************************/ } if(u4_total_coeff < 4) { UWORD32 u4_max_ldz = (4 - u4_total_coeff); FIND_ONE_IN_STREAM_LEN(u4_total_zeroes, u4_bitstream_offset, pu4_bitstrm_buf, u4_max_ldz); } else u4_total_zeroes = 0; /**************************************************************/ /* Decode the runs and form the coefficient buffer */ /**************************************************************/ { const UWORD8 *pu1_table_runbefore; UWORD32 u4_run; WORD32 u4_scan_pos = (u4_total_coeff + u4_total_zeroes - 1); UWORD32 u4_zeroes_left = u4_total_zeroes; i = u4_total_coeff - 1; /**************************************************************/ /* Decoding Runs for 0 < zeros left <=6 */ /**************************************************************/ pu1_table_runbefore = (UWORD8 *)gau1_ih264d_table_run_before; while(u4_zeroes_left && i) { UWORD32 u4_code; NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 3); u4_code = pu1_table_runbefore[u4_code + (u4_zeroes_left << 3)]; u4_run = u4_code >> 2; FLUSHBITS(u4_bitstream_offset, (u4_code & 0x03)); SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); *pi2_coeff_data++ = i2_level_arr[i--]; u4_zeroes_left -= (WORD32)u4_run; u4_scan_pos -= (WORD32)(u4_run + 1); } /**************************************************************/ /* Decoding Runs End */ /**************************************************************/ /**************************************************************/ /* Copy the remaining coefficients */ /**************************************************************/ while(i >= 0) { SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); *pi2_coeff_data++ = i2_level_arr[i--]; u4_scan_pos--; } } { WORD32 offset; offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; offset = ALIGN4(offset); ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); } ps_bitstrm->u4_ofst = u4_bitstream_offset; } /*! ************************************************************************** * \if Function name : CavlcParsingInvScanInvQuant \endif * * \brief * This function do cavlc parsing of coefficient tokens for any block * type except chromDc and depending * on whenther any coefficients to be parsed calls module * RestOfResidualBlockCavlc. * * \return * Returns total number of non-zero coefficients. * ************************************************************************** */ WORD32 ih264d_cavlc_parse4x4coeff_n0to7(WORD16 *pi2_coeff_block, UWORD32 u4_isdc, /* is it a DC block */ WORD32 u4_n, dec_struct_t *ps_dec, UWORD32 *pu4_total_coeff) { dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; UWORD32 u4_code, u4_index, u4_ldz; const UWORD16 *pu2_code = (const UWORD16*)gau2_ih264d_code_gx; const UWORD16 *pu2_offset_num_vlc = (const UWORD16 *)gau2_ih264d_offset_num_vlc_tab; UWORD32 u4_offset_num_vlc = pu2_offset_num_vlc[u4_n]; UNUSED(pi2_coeff_block); *pu4_total_coeff = 0; FIND_ONE_IN_STREAM_32(u4_ldz, u4_bitstream_offset, pu4_bitstrm_buf); NEXTBITS(u4_index, u4_bitstream_offset, pu4_bitstrm_buf, 3); u4_index += (u4_ldz << 3); u4_index += u4_offset_num_vlc; u4_index = MIN(u4_index, 303); u4_code = pu2_code[u4_index]; FLUSHBITS(u4_bitstream_offset, (u4_code & 0x03)); ps_bitstrm->u4_ofst = u4_bitstream_offset; *pu4_total_coeff = (u4_code >> 4); if(*pu4_total_coeff) { UWORD32 u4_trailing_ones, u4_offset, u4_total_coeff_tone; const UWORD8 *pu1_offset = (UWORD8 *)gau1_ih264d_total_coeff_fn_ptr_offset; WORD32 ret; u4_trailing_ones = ((u4_code >> 2) & 0x03); u4_offset = pu1_offset[*pu4_total_coeff - 1]; u4_total_coeff_tone = (*pu4_total_coeff << 16) | u4_trailing_ones; ret = ps_dec->pf_cavlc_4x4res_block[u4_offset](u4_isdc, u4_total_coeff_tone, ps_bitstrm); if(ret != 0) return ERROR_CAVLC_NUM_COEFF_T; } return OK; } WORD32 ih264d_cavlc_parse4x4coeff_n8(WORD16 *pi2_coeff_block, UWORD32 u4_isdc, /* is it a DC block */ WORD32 u4_n, dec_struct_t *ps_dec, UWORD32 *pu4_total_coeff) { dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; UWORD32 u4_code; UNUSED(u4_n); UNUSED(pi2_coeff_block); GETBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 6); ps_bitstrm->u4_ofst = u4_bitstream_offset; *pu4_total_coeff = 0; if(u4_code != 3) { UWORD8 *pu1_offset = (UWORD8 *)gau1_ih264d_total_coeff_fn_ptr_offset; UWORD32 u4_trailing_ones, u4_offset, u4_total_coeff_tone; *pu4_total_coeff = (u4_code >> 2) + 1; u4_trailing_ones = u4_code & 0x03; u4_offset = pu1_offset[*pu4_total_coeff - 1]; u4_total_coeff_tone = (*pu4_total_coeff << 16) | u4_trailing_ones; ps_dec->pf_cavlc_4x4res_block[u4_offset](u4_isdc, u4_total_coeff_tone, ps_bitstrm); } return OK; } /*! ************************************************************************** * \if Function name : ih264d_cavlc_parse_chroma_dc \endif * * \brief * This function do cavlc parsing of coefficient tokens chromDc block * and depending on whenther any coefficients to be parsed calls module * ih264d_rest_of_residual_cav_chroma_dc_block. * * \return * Returns total number of non-zero coefficients. * ************************************************************************** */ void ih264d_cavlc_parse_chroma_dc(dec_mb_info_t *ps_cur_mb_info, WORD16 *pi2_coeff_block, dec_bit_stream_t *ps_bitstrm, UWORD32 u4_scale_u, UWORD32 u4_scale_v, WORD32 i4_mb_inter_inc) { UWORD32 u4_total_coeff, u4_trailing_ones, u4_total_coeff_tone, u4_code; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; const UWORD8 *pu1_cav_chromdc = (const UWORD8*)gau1_ih264d_cav_chromdc_vld; UNUSED(i4_mb_inter_inc); /******************************************************************/ /* Chroma DC Block for U component */ /******************************************************************/ NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 8); u4_code = pu1_cav_chromdc[u4_code]; FLUSHBITS(u4_bitstream_offset, ((u4_code & 0x7) + 1)); ps_bitstrm->u4_ofst = u4_bitstream_offset; u4_total_coeff = (u4_code >> 5); if(u4_total_coeff) { WORD32 i_z0, i_z1, i_z2, i_z3; tu_sblk4x4_coeff_data_t *ps_tu_4x4; dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; WORD16 ai2_dc_coef[4]; UWORD8 pu1_inv_scan[4] = { 0, 1, 2, 3 }; WORD16 *pi2_coeff_data = (WORD16 *)ps_dec->pv_parse_tu_coeff_data; ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; u4_trailing_ones = ((u4_code >> 3) & 0x3); u4_total_coeff_tone = (u4_total_coeff << 16) | u4_trailing_ones; ih264d_rest_of_residual_cav_chroma_dc_block(u4_total_coeff_tone, ps_bitstrm); ai2_dc_coef[0] = 0; ai2_dc_coef[1] = 0; ai2_dc_coef[2] = 0; ai2_dc_coef[3] = 0; ih264d_unpack_coeff4x4_dc_4x4blk(ps_tu_4x4, ai2_dc_coef, pu1_inv_scan); /*-------------------------------------------------------------------*/ /* Inverse 2x2 transform and scaling of chroma DC */ /*-------------------------------------------------------------------*/ i_z0 = (ai2_dc_coef[0] + ai2_dc_coef[2]); i_z1 = (ai2_dc_coef[0] - ai2_dc_coef[2]); i_z2 = (ai2_dc_coef[1] - ai2_dc_coef[3]); i_z3 = (ai2_dc_coef[1] + ai2_dc_coef[3]); /*-----------------------------------------------------------*/ /* Scaling and storing the values back */ /*-----------------------------------------------------------*/ *pi2_coeff_data++ = (WORD16)(((i_z0 + i_z3) * (WORD32)u4_scale_u) >> 5); *pi2_coeff_data++ = (WORD16)(((i_z0 - i_z3) * (WORD32)u4_scale_u) >> 5); *pi2_coeff_data++ = (WORD16)(((i_z1 + i_z2) * (WORD32)u4_scale_u) >> 5); *pi2_coeff_data++ = (WORD16)(((i_z1 - i_z2) * (WORD32)u4_scale_u) >> 5); ps_dec->pv_parse_tu_coeff_data = (void *)pi2_coeff_data; SET_BIT(ps_cur_mb_info->u1_yuv_dc_block_flag,1); } /******************************************************************/ /* Chroma DC Block for V component */ /******************************************************************/ pi2_coeff_block += 64; u4_bitstream_offset = ps_bitstrm->u4_ofst; NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 8); u4_code = pu1_cav_chromdc[u4_code]; FLUSHBITS(u4_bitstream_offset, ((u4_code & 0x7) + 1)); ps_bitstrm->u4_ofst = u4_bitstream_offset; u4_total_coeff = (u4_code >> 5); if(u4_total_coeff) { WORD32 i_z0, i_z1, i_z2, i_z3; tu_sblk4x4_coeff_data_t *ps_tu_4x4; dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; WORD16 ai2_dc_coef[4]; UWORD8 pu1_inv_scan[4] = { 0, 1, 2, 3 }; WORD16 *pi2_coeff_data = (WORD16 *)ps_dec->pv_parse_tu_coeff_data; ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; u4_trailing_ones = ((u4_code >> 3) & 0x3); u4_total_coeff_tone = (u4_total_coeff << 16) | u4_trailing_ones; ih264d_rest_of_residual_cav_chroma_dc_block(u4_total_coeff_tone, ps_bitstrm); ai2_dc_coef[0] = 0; ai2_dc_coef[1] = 0; ai2_dc_coef[2] = 0; ai2_dc_coef[3] = 0; ih264d_unpack_coeff4x4_dc_4x4blk(ps_tu_4x4, ai2_dc_coef, pu1_inv_scan); /*-------------------------------------------------------------------*/ /* Inverse 2x2 transform and scaling of chroma DC */ /*-------------------------------------------------------------------*/ i_z0 = (ai2_dc_coef[0] + ai2_dc_coef[2]); i_z1 = (ai2_dc_coef[0] - ai2_dc_coef[2]); i_z2 = (ai2_dc_coef[1] - ai2_dc_coef[3]); i_z3 = (ai2_dc_coef[1] + ai2_dc_coef[3]); /*-----------------------------------------------------------*/ /* Scaling and storing the values back */ /*-----------------------------------------------------------*/ *pi2_coeff_data++ = (WORD16)(((i_z0 + i_z3) * (WORD32)u4_scale_v) >> 5); *pi2_coeff_data++ = (WORD16)(((i_z0 - i_z3) * (WORD32)u4_scale_v) >> 5); *pi2_coeff_data++ = (WORD16)(((i_z1 + i_z2) * (WORD32)u4_scale_v) >> 5); *pi2_coeff_data++ = (WORD16)(((i_z1 - i_z2) * (WORD32)u4_scale_v) >> 5); ps_dec->pv_parse_tu_coeff_data = (void *)pi2_coeff_data; SET_BIT(ps_cur_mb_info->u1_yuv_dc_block_flag,2); } } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_pmb_ref_index_cavlc_range1 */ /* */ /* Description : This function does the Cavlc TEV range =1 parsing of */ /* reference index for a P MB. Range is 1 when */ /* num_ref_idx_active_minus1 is 0 */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 19 09 2008 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_parse_pmb_ref_index_cavlc_range1(UWORD32 u4_num_part, /* Number of partitions in MB */ dec_bit_stream_t *ps_bitstrm, /* Pointer to bitstream Structure. */ WORD8 *pi1_ref_idx, /* pointer to reference index array */ UWORD32 u4_num_ref_idx_active_minus1 /* Not used for range 1 */ ) { UWORD32 u4_i; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstream_off = &ps_bitstrm->u4_ofst; UNUSED(u4_num_ref_idx_active_minus1); for(u4_i = 0; u4_i < u4_num_part; u4_i++) { UWORD32 u4_ref_idx; u4_ref_idx = ih264d_tev_range1(pu4_bitstream_off, pu4_bitstrm_buf); /* Storing Reference Idx Information */ pi1_ref_idx[u4_i] = (WORD8)u4_ref_idx; } } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_pmb_ref_index_cavlc */ /* */ /* Description : This function does the Cavlc TEV range > 1 parsing of */ /* reference index for a P MB. */ /* Range > 1 when num_ref_idx_active_minus1 > 0 */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 19 09 2008 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_pmb_ref_index_cavlc(UWORD32 u4_num_part, /* Number of partitions in MB */ dec_bit_stream_t *ps_bitstrm, /* Pointer to bitstream Structure. */ WORD8 *pi1_ref_idx, /* pointer to reference index array */ UWORD32 u4_num_ref_idx_active_minus1 /* Number of active references - 1 */ ) { UWORD32 u4_i; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstream_off = &ps_bitstrm->u4_ofst; for(u4_i = 0; u4_i < u4_num_part; u4_i++) { UWORD32 u4_ref_idx; //Inlined ih264d_uev UWORD32 u4_bitstream_offset = *pu4_bitstream_off; UWORD32 u4_word, u4_ldz; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstream_off = u4_bitstream_offset; u4_ref_idx = ((1 << u4_ldz) + u4_word - 1); //Inlined ih264d_uev if(u4_ref_idx > u4_num_ref_idx_active_minus1) return ERROR_REF_IDX; /* Storing Reference Idx Information */ pi1_ref_idx[u4_i] = (WORD8)u4_ref_idx; } return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_bmb_ref_index_cavlc_range1 */ /* */ /* Description : This function does the Cavlc TEV range =1 parsing of */ /* reference index for a B MB. Range is 1 when */ /* num_ref_idx_active_minus1 is 0 */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 19 09 2008 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_parse_bmb_ref_index_cavlc_range1(UWORD32 u4_num_part, /* Number of partitions in MB */ dec_bit_stream_t *ps_bitstrm, /* Pointer to bitstream Structure. */ WORD8 *pi1_ref_idx, /* pointer to reference index array */ UWORD32 u4_num_ref_idx_active_minus1 /* Not used for range 1 */ ) { UWORD32 u4_i; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstream_off = &ps_bitstrm->u4_ofst; UNUSED(u4_num_ref_idx_active_minus1); for(u4_i = 0; u4_i < u4_num_part; u4_i++) { if(pi1_ref_idx[u4_i] > -1) { UWORD32 u4_ref_idx; u4_ref_idx = ih264d_tev_range1(pu4_bitstream_off, pu4_bitstrm_buf); /* Storing Reference Idx Information */ pi1_ref_idx[u4_i] = (WORD8)u4_ref_idx; } } } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_bmb_ref_index_cavlc */ /* */ /* Description : This function does the Cavlc TEV range > 1 parsing of */ /* reference index for a B MB. */ /* Range > 1 when num_ref_idx_active_minus1 > 0 */ /* */ /* Inputs : <What inputs does the function take?> */ /* Globals : <Does it use any global variables?> */ /* Processing : <Describe how the function operates - include algorithm */ /* description> */ /* Outputs : <What does the function produce?> */ /* Returns : <What does the function return?> */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 19 09 2008 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_bmb_ref_index_cavlc(UWORD32 u4_num_part, /* Number of partitions in MB */ dec_bit_stream_t *ps_bitstrm, /* Pointer to bitstream Structure. */ WORD8 *pi1_ref_idx, /* pointer to reference index array */ UWORD32 u4_num_ref_idx_active_minus1 /* Number of active references - 1 */ ) { UWORD32 u4_i; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstream_off = &ps_bitstrm->u4_ofst; for(u4_i = 0; u4_i < u4_num_part; u4_i++) { if(pi1_ref_idx[u4_i] > -1) { UWORD32 u4_ref_idx; //inlining ih264d_uev UWORD32 u4_bitstream_offset = *pu4_bitstream_off; UWORD32 u4_word, u4_ldz; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstream_off = u4_bitstream_offset; u4_ref_idx = ((1 << u4_ldz) + u4_word - 1); //inlining ih264d_uev if(u4_ref_idx > u4_num_ref_idx_active_minus1) return ERROR_REF_IDX; /* Storing Reference Idx Information */ pi1_ref_idx[u4_i] = (WORD8)u4_ref_idx; } } return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_cavlc_parse_8x8block_both_available */ /* */ /* Description : This function does the residual parsing of 4 subblocks */ /* in a 8x8 block when both top and left are available */ /* */ /* Inputs : pi2_coeff_block : pointer to residual block where */ /* decoded and inverse scan coefficients are updated */ /* */ /* u4_sub_block_strd : indicates the number of sublocks */ /* in a row. It is 4 for luma and 2 for chroma. */ /* */ /* u4_isdc : required to indicate 4x4 parse modules if the */ /* current Mb is I_16x16/chroma DC coded. */ /* */ /* ps_dec : pointer to Decstruct (decoder context) */ /* */ /* pu1_top_nnz : top nnz pointer */ /* */ /* pu1_left_nnz : left nnz pointer */ /* */ /* Globals : No */ /* Processing : Parsing for four subblocks in unrolled, top and left nnz */ /* are updated on the fly. csbp is set in accordance to */ /* decoded numcoeff for the subblock index in raster order */ /* */ /* Outputs : The updated residue buffer, nnzs and csbp current block */ /* */ /* Returns : Returns the coded sub block pattern csbp for the block */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 09 10 2008 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_cavlc_parse_8x8block_both_available(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_isdc, dec_struct_t * ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, UWORD8 u1_tran_form8x8, UWORD8 u1_mb_field_decodingflag, UWORD32 *pu4_csbp) { UWORD32 u4_num_coeff, u4_n, u4_subblock_coded; UWORD32 u4_top0, u4_top1; UWORD32 *pu4_dummy; WORD32 (**pf_cavlc_parse4x4coeff)(WORD16 *pi2_coeff_block, UWORD32 u4_isdc, WORD32 u4_n, struct _DecStruct *ps_dec, UWORD32 *pu4_dummy) = ps_dec->pf_cavlc_parse4x4coeff; UWORD32 u4_idx = 0; UWORD8 *puc_temp; WORD32 ret; *pu4_csbp = 0; /* need to change the inverse scan matrices here */ puc_temp = ps_dec->pu1_inv_scan; /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 0 */ /*------------------------------------------------------*/ if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[0]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[0]; } } u4_n = (pu1_top_nnz[0] + pu1_left_nnz[0] + 1) >> 1; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; u4_top0 = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 1 */ /*------------------------------------------------------*/ u4_idx++; if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[1]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[1]; } } else { pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; } u4_n = (pu1_top_nnz[1] + u4_num_coeff + 1) >> 1; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; u4_top1 = pu1_left_nnz[0] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 2 */ /*------------------------------------------------------*/ u4_idx += (u4_sub_block_strd - 1); if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[2]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[2]; } } else { pi2_coeff_block += ((u4_sub_block_strd - 1) * NUM_COEFFS_IN_4x4BLK); } u4_n = (u4_top0 + pu1_left_nnz[1] + 1) >> 1; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; pu1_top_nnz[0] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 3 */ /*------------------------------------------------------*/ u4_idx++; if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[3]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[3]; } } else { pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; } u4_n = (u4_top1 + u4_num_coeff + 1) >> 1; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; pu1_top_nnz[1] = pu1_left_nnz[1] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); ps_dec->pu1_inv_scan = puc_temp; return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_cavlc_parse_8x8block_left_available */ /* */ /* Description : This function does the residual parsing of 4 subblocks */ /* in a 8x8 block when only left is available for block */ /* */ /* Inputs : pi2_coeff_block : pointer to residual block where */ /* decoded and inverse scan coefficients are updated */ /* */ /* u4_sub_block_strd : indicates the number of sublocks */ /* in a row. It is 4 for luma and 2 for chroma. */ /* */ /* u4_isdc : required to indicate 4x4 parse modules if the */ /* current Mb is I_16x16/chroma DC coded. */ /* */ /* ps_dec : pointer to Decstruct (decoder context) */ /* */ /* pu1_top_nnz : top nnz pointer */ /* */ /* pu1_left_nnz : left nnz pointer */ /* */ /* Globals : No */ /* Processing : Parsing for four subblocks in unrolled, top and left nnz */ /* are updated on the fly. csbp is set in accordance to */ /* decoded numcoeff for the subblock index in raster order */ /* */ /* Outputs : The updated residue buffer, nnzs and csbp current block */ /* */ /* Returns : Returns the coded sub block pattern csbp for the block */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 09 10 2008 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_cavlc_parse_8x8block_left_available(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_isdc, dec_struct_t * ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, UWORD8 u1_tran_form8x8, UWORD8 u1_mb_field_decodingflag, UWORD32 *pu4_csbp) { UWORD32 u4_num_coeff, u4_n, u4_subblock_coded; UWORD32 u4_top0, u4_top1; UWORD32 *pu4_dummy; WORD32 (**pf_cavlc_parse4x4coeff)(WORD16 *pi2_coeff_block, UWORD32 u4_isdc, WORD32 u4_n, struct _DecStruct *ps_dec, UWORD32 *pu4_dummy) = ps_dec->pf_cavlc_parse4x4coeff; UWORD32 u4_idx = 0; UWORD8 *puc_temp; WORD32 ret; *pu4_csbp = 0; puc_temp = ps_dec->pu1_inv_scan; /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 0 */ /*------------------------------------------------------*/ if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[0]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[0]; } } u4_n = pu1_left_nnz[0]; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; u4_top0 = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 1 */ /*------------------------------------------------------*/ u4_idx++; if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[1]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[1]; } } else { pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; } u4_n = u4_num_coeff; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; u4_top1 = pu1_left_nnz[0] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 2 */ /*------------------------------------------------------*/ u4_idx += (u4_sub_block_strd - 1); if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[2]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[2]; } } else { pi2_coeff_block += ((u4_sub_block_strd - 1) * NUM_COEFFS_IN_4x4BLK); } u4_n = (u4_top0 + pu1_left_nnz[1] + 1) >> 1; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; pu1_top_nnz[0] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 3 */ /*------------------------------------------------------*/ u4_idx++; if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[3]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[3]; } } else { pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; } u4_n = (u4_top1 + u4_num_coeff + 1) >> 1; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; pu1_top_nnz[1] = pu1_left_nnz[1] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); ps_dec->pu1_inv_scan = puc_temp; return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_cavlc_parse_8x8block_top_available */ /* */ /* Description : This function does the residual parsing of 4 subblocks */ /* in a 8x8 block when only top is available for block */ /* */ /* Inputs : pi2_coeff_block : pointer to residual block where */ /* decoded and inverse scan coefficients are updated */ /* */ /* u4_sub_block_strd : indicates the number of sublocks */ /* in a row. It is 4 for luma and 2 for chroma. */ /* */ /* u4_isdc : required to indicate 4x4 parse modules if the */ /* current Mb is I_16x16/chroma DC coded. */ /* */ /* ps_dec : pointer to Decstruct (decoder context) */ /* */ /* pu1_top_nnz : top nnz pointer */ /* */ /* pu1_left_nnz : left nnz pointer */ /* */ /* Globals : No */ /* Processing : Parsing for four subblocks in unrolled, top and left nnz */ /* are updated on the fly. csbp is set in accordance to */ /* decoded numcoeff for the subblock index in raster order */ /* */ /* Outputs : The updated residue buffer, nnzs and csbp current block */ /* */ /* Returns : Returns the coded sub block pattern csbp for the block */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 09 10 2008 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_cavlc_parse_8x8block_top_available(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_isdc, dec_struct_t * ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, UWORD8 u1_tran_form8x8, UWORD8 u1_mb_field_decodingflag, UWORD32 *pu4_csbp) { UWORD32 u4_num_coeff, u4_n, u4_subblock_coded; UWORD32 u4_top0, u4_top1; UWORD32 *pu4_dummy; WORD32 (**pf_cavlc_parse4x4coeff)(WORD16 *pi2_coeff_block, UWORD32 u4_isdc, WORD32 u4_n, struct _DecStruct *ps_dec, UWORD32 *pu4_dummy) = ps_dec->pf_cavlc_parse4x4coeff; UWORD32 u4_idx = 0; UWORD8 *puc_temp; WORD32 ret; *pu4_csbp = 0; puc_temp = ps_dec->pu1_inv_scan; /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 0 */ /*------------------------------------------------------*/ if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[0]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[0]; } } u4_n = pu1_top_nnz[0]; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; u4_top0 = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 1 */ /*------------------------------------------------------*/ u4_idx++; if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[1]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[1]; } } else { pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; } u4_n = (pu1_top_nnz[1] + u4_num_coeff + 1) >> 1; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; u4_top1 = pu1_left_nnz[0] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 2 */ /*------------------------------------------------------*/ u4_idx += (u4_sub_block_strd - 1); if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[2]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[2]; } } else { pi2_coeff_block += ((u4_sub_block_strd - 1) * NUM_COEFFS_IN_4x4BLK); } u4_n = u4_top0; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; pu1_top_nnz[0] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 3 */ /*------------------------------------------------------*/ u4_idx++; if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[3]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[3]; } } else { pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; } u4_n = (u4_top1 + u4_num_coeff + 1) >> 1; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; pu1_top_nnz[1] = pu1_left_nnz[1] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); ps_dec->pu1_inv_scan = puc_temp; return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_cavlc_parse_8x8block_none_available */ /* */ /* Description : This function does the residual parsing of 4 subblocks */ /* in a 8x8 block when none of the neigbours are available */ /* */ /* Inputs : pi2_coeff_block : pointer to residual block where */ /* decoded and inverse scan coefficients are updated */ /* */ /* u4_sub_block_strd : indicates the number of sublocks */ /* in a row. It is 4 for luma and 2 for chroma. */ /* */ /* u4_isdc : required to indicate 4x4 parse modules if the */ /* current Mb is I_16x16/chroma DC coded. */ /* */ /* ps_dec : pointer to Decstruct (decoder context) */ /* */ /* pu1_top_nnz : top nnz pointer */ /* */ /* pu1_left_nnz : left nnz pointer */ /* */ /* Globals : No */ /* Processing : Parsing for four subblocks in unrolled, top and left nnz */ /* are updated on the fly. csbp is set in accordance to */ /* decoded numcoeff for the subblock index in raster order */ /* */ /* Outputs : The updated residue buffer, nnzs and csbp current block */ /* */ /* Returns : Returns the coded sub block pattern csbp for the block */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 09 10 2008 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_cavlc_parse_8x8block_none_available(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_isdc, dec_struct_t * ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, UWORD8 u1_tran_form8x8, UWORD8 u1_mb_field_decodingflag, UWORD32 *pu4_csbp) { UWORD32 u4_num_coeff, u4_n, u4_subblock_coded; UWORD32 u4_top0, u4_top1; UWORD32 *pu4_dummy; WORD32 (**pf_cavlc_parse4x4coeff)(WORD16 *pi2_coeff_block, UWORD32 u4_isdc, WORD32 u4_n, struct _DecStruct *ps_dec, UWORD32 *pu4_dummy) = ps_dec->pf_cavlc_parse4x4coeff; UWORD32 u4_idx = 0; UWORD8 *puc_temp; WORD32 ret; *pu4_csbp = 0; puc_temp = ps_dec->pu1_inv_scan; /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 0 */ /*------------------------------------------------------*/ if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[0]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[0]; } } ret = pf_cavlc_parse4x4coeff[0](pi2_coeff_block, u4_isdc, 0, ps_dec, &u4_num_coeff); if(ret != OK) return ret; u4_top0 = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 1 */ /*------------------------------------------------------*/ u4_idx++; if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[1]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[1]; } } else { pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; } u4_n = u4_num_coeff; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; u4_top1 = pu1_left_nnz[0] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 2 */ /*------------------------------------------------------*/ u4_idx += (u4_sub_block_strd - 1); if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[2]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[2]; } } else { pi2_coeff_block += ((u4_sub_block_strd - 1) * NUM_COEFFS_IN_4x4BLK); } u4_n = u4_top0; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; pu1_top_nnz[0] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); /*------------------------------------------------------*/ /* Residual 4x4 decoding: SubBlock 3 */ /*------------------------------------------------------*/ u4_idx++; if(u1_tran_form8x8) { if(!u1_mb_field_decodingflag) { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[3]; } else { ps_dec->pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[3]; } } else { pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; } u4_n = (u4_top1 + u4_num_coeff + 1) >> 1; ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, u4_n, ps_dec, &u4_num_coeff); if(ret != OK) return ret; pu1_top_nnz[1] = pu1_left_nnz[1] = u4_num_coeff; u4_subblock_coded = (u4_num_coeff != 0); INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); ps_dec->pu1_inv_scan = puc_temp; return OK; } /*! ************************************************************************** * \if Function name : ih264d_parse_residual4x4_cavlc \endif * * \brief * This function parses CAVLC syntax of a Luma and Chroma AC Residuals. * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_residual4x4_cavlc(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD8 u1_offset) { UWORD8 u1_cbp = ps_cur_mb_info->u1_cbp; UWORD16 ui16_csbp = 0; UWORD32 u4_nbr_avl; WORD16 *pi2_residual_buf; UWORD8 u1_is_top_mb_avail; UWORD8 u1_is_left_mb_avail; UWORD8 *pu1_top_nnz = ps_cur_mb_info->ps_curmb->pu1_nnz_y; UWORD8 *pu1_left_nnz = ps_dec->pu1_left_nnz_y; WORD16 *pi2_coeff_block = NULL; UWORD32 *pu4_dummy; WORD32 ret; WORD32 (**pf_cavlc_parse_8x8block)(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_isdc, struct _DecStruct *ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, UWORD8 u1_tran_form8x8, UWORD8 u1_mb_field_decodingflag, UWORD32 *pu4_dummy) = ps_dec->pf_cavlc_parse_8x8block; { UWORD8 uc_temp = ps_dec->u1_mb_ngbr_availablity; u1_is_top_mb_avail = BOOLEAN(uc_temp & TOP_MB_AVAILABLE_MASK); u1_is_left_mb_avail = BOOLEAN(uc_temp & LEFT_MB_AVAILABLE_MASK); u4_nbr_avl = (u1_is_top_mb_avail << 1) | u1_is_left_mb_avail; } ps_cur_mb_info->u1_qp_div6 = ps_dec->u1_qp_y_div6; ps_cur_mb_info->u1_qp_rem6 = ps_dec->u1_qp_y_rem6; ps_cur_mb_info->u1_qpc_div6 = ps_dec->u1_qp_u_div6; ps_cur_mb_info->u1_qpc_rem6 = ps_dec->u1_qp_u_rem6; ps_cur_mb_info->u1_qpcr_div6 = ps_dec->u1_qp_v_div6; ps_cur_mb_info->u1_qpcr_rem6 = ps_dec->u1_qp_v_rem6; if(u1_cbp & 0xf) { pu1_top_nnz[0] = ps_cur_mb_info->ps_top_mb->pu1_nnz_y[0]; pu1_top_nnz[1] = ps_cur_mb_info->ps_top_mb->pu1_nnz_y[1]; pu1_top_nnz[2] = ps_cur_mb_info->ps_top_mb->pu1_nnz_y[2]; pu1_top_nnz[3] = ps_cur_mb_info->ps_top_mb->pu1_nnz_y[3]; /*******************************************************************/ /* Block 0 residual decoding, check cbp and proceed (subblock = 0) */ /*******************************************************************/ if(!(u1_cbp & 0x1)) { *(UWORD16 *)(pu1_top_nnz) = 0; *(UWORD16 *)(pu1_left_nnz) = 0; } else { UWORD32 u4_temp; ret = pf_cavlc_parse_8x8block[u4_nbr_avl]( pi2_coeff_block, 4, u1_offset, ps_dec, pu1_top_nnz, pu1_left_nnz, ps_cur_mb_info->u1_tran_form8x8, ps_cur_mb_info->u1_mb_field_decodingflag, &u4_temp); if(ret != OK) return ret; ui16_csbp = u4_temp; } /*******************************************************************/ /* Block 1 residual decoding, check cbp and proceed (subblock = 2) */ /*******************************************************************/ if(ps_cur_mb_info->u1_tran_form8x8) { pi2_coeff_block += 64; } else { pi2_coeff_block += (2 * NUM_COEFFS_IN_4x4BLK); } if(!(u1_cbp & 0x2)) { *(UWORD16 *)(pu1_top_nnz + 2) = 0; *(UWORD16 *)(pu1_left_nnz) = 0; } else { UWORD32 u4_temp = (u4_nbr_avl | 0x1); ret = pf_cavlc_parse_8x8block[u4_temp]( pi2_coeff_block, 4, u1_offset, ps_dec, (pu1_top_nnz + 2), pu1_left_nnz, ps_cur_mb_info->u1_tran_form8x8, ps_cur_mb_info->u1_mb_field_decodingflag, &u4_temp); if(ret != OK) return ret; ui16_csbp |= (u4_temp << 2); } /*******************************************************************/ /* Block 2 residual decoding, check cbp and proceed (subblock = 8) */ /*******************************************************************/ if(ps_cur_mb_info->u1_tran_form8x8) { pi2_coeff_block += 64; } else { pi2_coeff_block += (6 * NUM_COEFFS_IN_4x4BLK); } if(!(u1_cbp & 0x4)) { *(UWORD16 *)(pu1_top_nnz) = 0; *(UWORD16 *)(pu1_left_nnz + 2) = 0; } else { UWORD32 u4_temp = (u4_nbr_avl | 0x2); ret = pf_cavlc_parse_8x8block[u4_temp]( pi2_coeff_block, 4, u1_offset, ps_dec, pu1_top_nnz, (pu1_left_nnz + 2), ps_cur_mb_info->u1_tran_form8x8, ps_cur_mb_info->u1_mb_field_decodingflag, &u4_temp); if(ret != OK) return ret; ui16_csbp |= (u4_temp << 8); } /*******************************************************************/ /* Block 3 residual decoding, check cbp and proceed (subblock = 10)*/ /*******************************************************************/ if(ps_cur_mb_info->u1_tran_form8x8) { pi2_coeff_block += 64; } else { pi2_coeff_block += (2 * NUM_COEFFS_IN_4x4BLK); } if(!(u1_cbp & 0x8)) { *(UWORD16 *)(pu1_top_nnz + 2) = 0; *(UWORD16 *)(pu1_left_nnz + 2) = 0; } else { UWORD32 u4_temp; ret = pf_cavlc_parse_8x8block[0x3]( pi2_coeff_block, 4, u1_offset, ps_dec, (pu1_top_nnz + 2), (pu1_left_nnz + 2), ps_cur_mb_info->u1_tran_form8x8, ps_cur_mb_info->u1_mb_field_decodingflag, &u4_temp); if(ret != OK) return ret; ui16_csbp |= (u4_temp << 10); } } else { *(UWORD32 *)(pu1_top_nnz) = 0; *(UWORD32 *)(pu1_left_nnz) = 0; } ps_cur_mb_info->u2_luma_csbp = ui16_csbp; ps_cur_mb_info->ps_curmb->u2_luma_csbp = ui16_csbp; { UWORD16 u2_chroma_csbp = 0; ps_cur_mb_info->u2_chroma_csbp = 0; pu1_top_nnz = ps_cur_mb_info->ps_curmb->pu1_nnz_uv; pu1_left_nnz = ps_dec->pu1_left_nnz_uv; u1_cbp >>= 4; /*--------------------------------------------------------------------*/ /* if Chroma Component not present OR no ac values present */ /* Set the values of N to zero */ /*--------------------------------------------------------------------*/ if(u1_cbp == CBPC_ALLZERO || u1_cbp == CBPC_ACZERO) { *(UWORD32 *)(pu1_top_nnz) = 0; *(UWORD32 *)(pu1_left_nnz) = 0; } if(u1_cbp == CBPC_ALLZERO) { return (0); } /*--------------------------------------------------------------------*/ /* Decode Chroma DC values */ /*--------------------------------------------------------------------*/ { WORD32 u4_scale_u; WORD32 u4_scale_v; WORD32 i4_mb_inter_inc; u4_scale_u = ps_dec->pu2_quant_scale_u[0] << ps_dec->u1_qp_u_div6; u4_scale_v = ps_dec->pu2_quant_scale_v[0] << ps_dec->u1_qp_v_div6; i4_mb_inter_inc = (!((ps_cur_mb_info->ps_curmb->u1_mb_type == I_4x4_MB) || (ps_cur_mb_info->ps_curmb->u1_mb_type == I_16x16_MB))) * 3; if(ps_dec->s_high_profile.u1_scaling_present) { u4_scale_u *= ps_dec->s_high_profile.i2_scalinglist4x4[i4_mb_inter_inc + 1][0]; u4_scale_v *= ps_dec->s_high_profile.i2_scalinglist4x4[i4_mb_inter_inc + 2][0]; } else { u4_scale_u <<= 4; u4_scale_v <<= 4; } ih264d_cavlc_parse_chroma_dc(ps_cur_mb_info,pi2_coeff_block, ps_dec->ps_bitstrm, u4_scale_u, u4_scale_v, i4_mb_inter_inc); } if(u1_cbp == CBPC_ACZERO) return (0); pu1_top_nnz[0] = ps_cur_mb_info->ps_top_mb->pu1_nnz_uv[0]; pu1_top_nnz[1] = ps_cur_mb_info->ps_top_mb->pu1_nnz_uv[1]; pu1_top_nnz[2] = ps_cur_mb_info->ps_top_mb->pu1_nnz_uv[2]; pu1_top_nnz[3] = ps_cur_mb_info->ps_top_mb->pu1_nnz_uv[3]; /*--------------------------------------------------------------------*/ /* Decode Chroma AC values */ /*--------------------------------------------------------------------*/ { UWORD32 u4_temp; /*****************************************************************/ /* U Block residual decoding, check cbp and proceed (subblock=0)*/ /*****************************************************************/ ret = pf_cavlc_parse_8x8block[u4_nbr_avl]( pi2_coeff_block, 2, 1, ps_dec, pu1_top_nnz, pu1_left_nnz, 0, 0, &u4_temp); if(ret != OK) return ret; u2_chroma_csbp = u4_temp; pi2_coeff_block += MB_CHROM_SIZE; /*****************************************************************/ /* V Block residual decoding, check cbp and proceed (subblock=1)*/ /*****************************************************************/ ret = pf_cavlc_parse_8x8block[u4_nbr_avl](pi2_coeff_block, 2, 1, ps_dec, (pu1_top_nnz + 2), (pu1_left_nnz + 2), 0, 0, &u4_temp); if(ret != OK) return ret; u2_chroma_csbp |= (u4_temp << 4); } ps_cur_mb_info->u2_chroma_csbp = u2_chroma_csbp; } return OK; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_cavlc.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_PARSE_CAVLC_H_ #define _IH264D_PARSE_CAVLC_H_ /*! ************************************************************************** * \file ih264d_parse_cavlc.h * * \brief * Declaration of UVLC and CAVLC functions * * \date * 18/12/2002 * * \author AI ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_bitstrm.h" #include "ih264d_structs.h" #include "ih264d_cabac.h" enum cavlcTableNum { tableTotalZeroOffset, tableTotalZero, tableRunBefore, codeGx, chromTab, offsetNumVlcTab }; WORD32 ih264d_uvlc(dec_bit_stream_t *ps_bitstrm, UWORD32 u4_range, UWORD32 *pi_bitstrm_ofst, UWORD8 u1_flag, UWORD32 u4_bitstrm_ofst, UWORD32 *pi_bitstrm_buf); UWORD32 ih264d_uev(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf); WORD32 ih264d_sev(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf); UWORD32 ih264d_tev_range1(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf); UWORD8 RestOfResidualBlockCavlc(WORD16 *pi2_coeff_block, UWORD32 u1_ofst_is_dc_max_coef_scale_fact, UWORD32 u4_total_coeff_trail_one, dec_bit_stream_t *ps_bitstrm, UWORD8 *pu1_invscan); WORD32 ih264d_cavlc_4x4res_block_totalcoeff_1( UWORD32 u4_isdc, UWORD32 u4_total_coeff_trail_one, dec_bit_stream_t *ps_bitstrm); WORD32 ih264d_cavlc_4x4res_block_totalcoeff_2to10(UWORD32 u4_isdc, UWORD32 u4_total_coeff_trail_one, dec_bit_stream_t *ps_bitstrm); WORD32 ih264d_cavlc_4x4res_block_totalcoeff_11to16(UWORD32 u4_isdc, UWORD32 u4_total_coeff_trail_one, dec_bit_stream_t *ps_bitstrm); WORD32 ih264d_cavlc_parse4x4coeff_n0to7(WORD16 *pi2_coeff_block, UWORD32 u4_isdc, WORD32 u4_n, dec_struct_t *ps_dec, UWORD32 *pu4_total_coeff); WORD32 ih264d_cavlc_parse4x4coeff_n8(WORD16 *pi2_coeff_block, UWORD32 u4_isdc, WORD32 u4_n, dec_struct_t *ps_dec, UWORD32 *pu4_total_coeff); void ih264d_cavlc_parse_chroma_dc(dec_mb_info_t *ps_cur_mb_info, WORD16 *pi2_coeff_block, dec_bit_stream_t *ps_bitstrm, UWORD32 u4_scale_u, UWORD32 u4_scale_v, WORD32 i4_mb_inter_inc); WORD32 ih264d_cavlc_parse_8x8block_none_available(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_isdc, dec_struct_t * ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, UWORD8 u1_tran_form8x8, UWORD8 u1_mb_field_decodingflag, UWORD32 *pu4_csbp); WORD32 ih264d_cavlc_parse_8x8block_left_available(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_isdc, dec_struct_t * ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, UWORD8 u1_tran_form8x8, UWORD8 u1_mb_field_decodingflag, UWORD32 *pu4_csbp); WORD32 ih264d_cavlc_parse_8x8block_top_available(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_isdc, dec_struct_t * ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, UWORD8 u1_tran_form8x8, UWORD8 u1_mb_field_decodingflag, UWORD32 *pu4_csbp); WORD32 ih264d_cavlc_parse_8x8block_both_available(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_isdc, dec_struct_t * ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, UWORD8 u1_tran_form8x8, UWORD8 u1_mb_field_decodingflag, UWORD32 *pu4_csbp); WORD8 ResidualBlockChromaDC(WORD16 *pi2_level, dec_bit_stream_t *ps_bitstrm); void ih264d_parse_pmb_ref_index_cavlc_range1(UWORD32 u4_num_part, dec_bit_stream_t *ps_bitstrm, WORD8 *pi1_ref_idx, UWORD32 u4_num_ref_idx_active_minus1); WORD32 ih264d_parse_pmb_ref_index_cavlc(UWORD32 u4_num_part, dec_bit_stream_t *ps_bitstrm, WORD8 *pi1_ref_idx, UWORD32 u4_num_ref_idx_active_minus1); void ih264d_parse_bmb_ref_index_cavlc_range1(UWORD32 u4_num_part, dec_bit_stream_t *ps_bitstrm, WORD8 *pi1_ref_idx, UWORD32 u4_num_ref_idx_active_minus1); WORD32 ih264d_parse_bmb_ref_index_cavlc(UWORD32 u4_num_part, dec_bit_stream_t *ps_bitstrm, WORD8 *pi1_ref_idx, UWORD32 u4_num_ref_idx_active_minus1); #endif /* _IH264D_PARSE_CAVLC_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_headers.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore * Modified for use with Cemu emulator project */ /*! ************************************************************************** * \file ih264d_parse_headers.c * * \brief * Contains High level syntax[above slice] parsing routines * * \date * 19/12/2002 * * \author AI ************************************************************************** */ #include <string.h> #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_defs.h" #include "ih264d_bitstrm.h" #include "ih264d_structs.h" #include "ih264d_parse_cavlc.h" #include "ih264d_defs.h" #include "ih264d_defs.h" #include "ih264d_defs.h" #include "ih264d_parse_slice.h" #include "ih264d_tables.h" #include "ih264d_utils.h" #include "ih264d_nal.h" #include "ih264d_deblocking.h" #include "ih264d_mem_request.h" #include "ih264d_debug.h" #include "ih264d_error_handler.h" #include "ih264d_mb_utils.h" #include "ih264d_sei.h" #include "ih264d_vui.h" #include "ih264d_thread_parse_decode.h" #include "ih264d_thread_compute_bs.h" #include "ih264d_quant_scaling.h" #include "ih264d_defs.h" #include "ivd.h" #include "ih264d.h" /*****************************************************************************/ /* */ /* Function Name : ih264d_get_pre_sei_params */ /* */ /* Description : Gets valid pre-sei params in decoder struct from parse */ /* struct. */ /* Inputs : u1_nal_unit_type slice type */ /* ps_dec Decoder parameters */ /* Globals : None */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* Draft */ /* */ /*****************************************************************************/ void ih264d_get_pre_sei_params(dec_struct_t *ps_dec, UWORD8 u1_nal_unit_type) { if((NULL != ps_dec->ps_sei) && ((0 == ps_dec->ps_sei->s_sei_ccv_params.u1_ccv_cancel_flag) && (0 == ps_dec->ps_sei->s_sei_ccv_params.u1_ccv_persistence_flag))) { ps_dec->ps_sei->u1_sei_ccv_params_present_flag = 0; memset(&ps_dec->ps_sei->s_sei_ccv_params, 0, sizeof(sei_ccv_params_t)); } if((NULL != ps_dec->ps_cur_sps) && ((1 == ps_dec->ps_cur_sps->u1_vui_parameters_present_flag) && ((2 != ps_dec->ps_cur_sps->s_vui.u1_colour_primaries) && (2 != ps_dec->ps_cur_sps->s_vui.u1_matrix_coeffs) && (2 != ps_dec->ps_cur_sps->s_vui.u1_tfr_chars) && (4 != ps_dec->ps_cur_sps->s_vui.u1_tfr_chars) && (5 != ps_dec->ps_cur_sps->s_vui.u1_tfr_chars)))) { if((1 == ps_dec->ps_sei_parse->u1_sei_ccv_params_present_flag) || (IDR_SLICE_NAL == u1_nal_unit_type)) { ps_dec->ps_sei->u1_sei_ccv_params_present_flag = ps_dec->ps_sei_parse->u1_sei_ccv_params_present_flag; ps_dec->ps_sei->s_sei_ccv_params = ps_dec->ps_sei_parse->s_sei_ccv_params; } } else { ps_dec->ps_sei->u1_sei_ccv_params_present_flag = 0; memset(&ps_dec->ps_sei->s_sei_ccv_params, 0, sizeof(sei_ccv_params_t)); } if(IDR_SLICE_NAL == u1_nal_unit_type) { ps_dec->ps_sei->u1_sei_mdcv_params_present_flag = ps_dec->ps_sei_parse->u1_sei_mdcv_params_present_flag; ps_dec->ps_sei->s_sei_mdcv_params = ps_dec->ps_sei_parse->s_sei_mdcv_params; ps_dec->ps_sei->u1_sei_cll_params_present_flag = ps_dec->ps_sei_parse->u1_sei_cll_params_present_flag; ps_dec->ps_sei->s_sei_cll_params = ps_dec->ps_sei_parse->s_sei_cll_params; ps_dec->ps_sei->u1_sei_ave_params_present_flag = ps_dec->ps_sei_parse->u1_sei_ave_params_present_flag; ps_dec->ps_sei->s_sei_ave_params = ps_dec->ps_sei_parse->s_sei_ave_params; } ps_dec->ps_sei_parse->u1_sei_mdcv_params_present_flag = 0; memset(&ps_dec->ps_sei_parse->s_sei_mdcv_params, 0, sizeof(sei_mdcv_params_t)); ps_dec->ps_sei_parse->u1_sei_cll_params_present_flag = 0; memset(&ps_dec->ps_sei_parse->s_sei_cll_params, 0, sizeof(sei_cll_params_t)); ps_dec->ps_sei_parse->u1_sei_ave_params_present_flag = 0; memset(&ps_dec->ps_sei_parse->s_sei_ave_params, 0, sizeof(sei_ave_params_t)); ps_dec->ps_sei_parse->u1_sei_ccv_params_present_flag = 0; memset(&ps_dec->ps_sei_parse->s_sei_ccv_params, 0, sizeof(sei_ccv_params_t)); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_slice_partition */ /* */ /* Description : This function is intended to parse and decode slice part */ /* itions. Currently it's not implemented. Decoder will */ /* print a message, skips this NAL and continues */ /* Inputs : ps_dec Decoder parameters */ /* ps_bitstrm Bitstream */ /* Globals : None */ /* Processing : This functionality needs to be implemented */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : Not implemented */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_slice_partition(dec_struct_t * ps_dec, dec_bit_stream_t * ps_bitstrm) { H264_DEC_DEBUG_PRINT("\nSlice partition not supported"); UNUSED(ps_dec); UNUSED(ps_bitstrm); return (0); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_sei */ /* */ /* Description : This function is intended to parse and decode SEI */ /* Currently it's not implemented. Decoder will print a */ /* message, skips this NAL and continues */ /* Inputs : ps_dec Decoder parameters */ /* ps_bitstrm Bitstream */ /* Globals : None */ /* Processing : This functionality needs to be implemented */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : Not implemented */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_sei(dec_struct_t * ps_dec, dec_bit_stream_t * ps_bitstrm) { UNUSED(ps_dec); UNUSED(ps_bitstrm); return (0); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_filler_data */ /* */ /* Description : This function is intended to parse and decode filler */ /* data NAL. Currently it's not implemented. Decoder will */ /* print a message, skips this NAL and continues */ /* Inputs : ps_dec Decoder parameters */ /* ps_bitstrm Bitstream */ /* Globals : None */ /* Processing : This functionality needs to be implemented */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : Not implemented */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_filler_data(dec_struct_t * ps_dec, dec_bit_stream_t * ps_bitstrm) { UNUSED(ps_dec); UNUSED(ps_bitstrm); return (0); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_end_of_stream */ /* */ /* Description : This function is intended to parse and decode end of */ /* sequence. Currently it's not implemented. Decoder will */ /* print a message, skips this NAL and continues */ /* Inputs : ps_dec Decoder parameters */ /* Globals : None */ /* Processing : This functionality needs to be implemented */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : Not implemented */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ void ih264d_parse_end_of_stream(dec_struct_t * ps_dec) { UNUSED(ps_dec); return; } /*! ************************************************************************** * \if Function name : ih264d_parse_pps \endif * * \brief * Decodes Picture Parameter set * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_pps(dec_struct_t * ps_dec, dec_bit_stream_t * ps_bitstrm) { UWORD8 uc_temp; dec_seq_params_t * ps_sps = NULL; dec_pic_params_t * ps_pps = NULL; UWORD32 *pu4_bitstrm_buf = ps_dec->ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_dec->ps_bitstrm->u4_ofst; /* Variables used for error resilience checks */ UWORD64 u8_temp; UWORD32 u4_temp; WORD32 i_temp; /* For High profile related syntax elements */ UWORD8 u1_more_data_flag; WORD32 i4_i; if(!(ps_dec->i4_header_decoded & 1)) return ERROR_INV_SPS_PPS_T; /*--------------------------------------------------------------------*/ /* Decode pic_parameter_set_id and find corresponding pic params */ /*--------------------------------------------------------------------*/ u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp & MASK_ERR_PIC_SET_ID) return ERROR_INV_SPS_PPS_T; ps_pps = ps_dec->pv_scratch_sps_pps; *ps_pps = ps_dec->ps_pps[u4_temp]; ps_pps->u1_pic_parameter_set_id = (WORD8)u4_temp; COPYTHECONTEXT("PPS: pic_parameter_set_id",ps_pps->u1_pic_parameter_set_id); /************************************************/ /* initilization of High profile syntax element */ /************************************************/ ps_pps->i4_transform_8x8_mode_flag = 0; ps_pps->i4_pic_scaling_matrix_present_flag = 0; /*--------------------------------------------------------------------*/ /* Decode seq_parameter_set_id and map it to a seq_parameter_set */ /*--------------------------------------------------------------------*/ u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp & MASK_ERR_SEQ_SET_ID) return ERROR_INV_SPS_PPS_T; COPYTHECONTEXT("PPS: seq_parameter_set_id",u4_temp); ps_sps = &ps_dec->ps_sps[u4_temp]; if(FALSE == ps_sps->u1_is_valid) return ERROR_INV_SPS_PPS_T; ps_pps->ps_sps = ps_sps; /*--------------------------------------------------------------------*/ /* Decode entropy_coding_mode */ /*--------------------------------------------------------------------*/ ps_pps->u1_entropy_coding_mode = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("PPS: entropy_coding_mode_flag",ps_pps->u1_entropy_coding_mode); ps_pps->u1_pic_order_present_flag = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("PPS: pic_order_present_flag",ps_pps->u1_pic_order_present_flag); /*--------------------------------------------------------------------*/ /* Decode num_slice_groups_minus1 */ /*--------------------------------------------------------------------*/ u8_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf) + (UWORD64)1; if(u8_temp != 1) { return ERROR_FEATURE_UNAVAIL; } ps_pps->u1_num_slice_groups = u8_temp; COPYTHECONTEXT("PPS: num_slice_groups_minus1",ps_pps->u1_num_slice_groups -1); /*--------------------------------------------------------------------*/ /* Other parameter set values */ /*--------------------------------------------------------------------*/ u8_temp = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u8_temp > H264_MAX_REF_IDX) return ERROR_REF_IDX; ps_pps->u1_num_ref_idx_lx_active[0] = u8_temp; COPYTHECONTEXT("PPS: num_ref_idx_l0_active_minus1", ps_pps->u1_num_ref_idx_lx_active[0] - 1); u8_temp = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u8_temp > H264_MAX_REF_IDX) return ERROR_REF_IDX; ps_pps->u1_num_ref_idx_lx_active[1] = u8_temp; COPYTHECONTEXT("PPS: num_ref_idx_l1_active_minus1", ps_pps->u1_num_ref_idx_lx_active[1] - 1); ps_pps->u1_wted_pred_flag = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("PPS: weighted prediction u4_flag",ps_pps->u1_wted_pred_flag); uc_temp = ih264d_get_bits_h264(ps_bitstrm, 2); COPYTHECONTEXT("PPS: weighted_bipred_idc",uc_temp); ps_pps->u1_wted_bipred_idc = uc_temp; if(ps_pps->u1_wted_bipred_idc > MAX_WEIGHT_BIPRED_IDC) return ERROR_INV_SPS_PPS_T; WORD64 i8_temp = (WORD64)26 + ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i8_temp < MIN_H264_QP) || (i8_temp > MAX_H264_QP)) return ERROR_INV_RANGE_QP_T; ps_pps->u1_pic_init_qp = i8_temp; COPYTHECONTEXT("PPS: pic_init_qp_minus26",ps_pps->u1_pic_init_qp - 26); i8_temp = (WORD64)26 + ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i8_temp < MIN_H264_QP) || (i8_temp > MAX_H264_QP)) return ERROR_INV_RANGE_QP_T; ps_pps->u1_pic_init_qs = i8_temp; COPYTHECONTEXT("PPS: pic_init_qs_minus26",ps_pps->u1_pic_init_qs - 26); i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i_temp < -12) || (i_temp > 12)) return ERROR_INV_RANGE_QP_T; ps_pps->i1_chroma_qp_index_offset = i_temp; COPYTHECONTEXT("PPS: chroma_qp_index_offset",ps_pps->i1_chroma_qp_index_offset); /***************************************************************************/ /* initialize second_chroma_qp_index_offset to i1_chroma_qp_index_offset if */ /* second_chroma_qp_index_offset is not present in bit-ps_bitstrm */ /***************************************************************************/ ps_pps->i1_second_chroma_qp_index_offset = ps_pps->i1_chroma_qp_index_offset; ps_pps->u1_deblocking_filter_parameters_present_flag = ih264d_get_bit_h264( ps_bitstrm); COPYTHECONTEXT("PPS: deblocking_filter_control_present_flag", ps_pps->u1_deblocking_filter_parameters_present_flag); ps_pps->u1_constrained_intra_pred_flag = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("PPS: constrained_intra_pred_flag", ps_pps->u1_constrained_intra_pred_flag); ps_pps->u1_redundant_pic_cnt_present_flag = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("PPS: redundant_pic_cnt_present_flag", ps_pps->u1_redundant_pic_cnt_present_flag); /* High profile related syntax elements */ u1_more_data_flag = MORE_RBSP_DATA(ps_bitstrm); if(u1_more_data_flag && (ps_pps->ps_sps->u1_profile_idc == HIGH_PROFILE_IDC)) { /* read transform_8x8_mode_flag */ ps_pps->i4_transform_8x8_mode_flag = (WORD32)ih264d_get_bit_h264( ps_bitstrm); /* read pic_scaling_matrix_present_flag */ ps_pps->i4_pic_scaling_matrix_present_flag = (WORD32)ih264d_get_bit_h264(ps_bitstrm); if(ps_pps->i4_pic_scaling_matrix_present_flag) { /* read the scaling matrices */ for(i4_i = 0; i4_i < (6 + (ps_pps->i4_transform_8x8_mode_flag << 1)); i4_i++) { ps_pps->u1_pic_scaling_list_present_flag[i4_i] = ih264d_get_bit_h264(ps_bitstrm); if(ps_pps->u1_pic_scaling_list_present_flag[i4_i]) { WORD32 ret; if(i4_i < 6) { ret = ih264d_scaling_list( ps_pps->i2_pic_scalinglist4x4[i4_i], 16, &ps_pps->u1_pic_use_default_scaling_matrix_flag[i4_i], ps_bitstrm); } else { ret = ih264d_scaling_list( ps_pps->i2_pic_scalinglist8x8[i4_i - 6], 64, &ps_pps->u1_pic_use_default_scaling_matrix_flag[i4_i], ps_bitstrm); } if(ret != OK) { return ret; } } } } /* read second_chroma_qp_index_offset syntax element */ i_temp = ih264d_sev( pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i_temp < -12) || (i_temp > 12)) return ERROR_INV_RANGE_QP_T; ps_pps->i1_second_chroma_qp_index_offset = i_temp; } /* In case bitstream read has exceeded the filled size, then return an error */ if(EXCEED_OFFSET(ps_bitstrm)) { return ERROR_INV_SPS_PPS_T; } ps_pps->u1_is_valid = TRUE; ps_dec->ps_pps[ps_pps->u1_pic_parameter_set_id] = *ps_pps; return OK; } /*! ************************************************************************** * \if Function name : ih264d_parse_sps \endif * * \brief * Decodes Sequence parameter set from the bitstream * * \return * 0 on Success and Error code otherwise ************************************************************************** */ UWORD32 ih264d_correct_level_idc(UWORD32 u4_level_idc, UWORD32 u4_total_mbs) { UWORD32 u4_max_mbs_allowed; switch(u4_level_idc) { case H264_LEVEL_1_0: u4_max_mbs_allowed = MAX_MBS_LEVEL_10; break; case H264_LEVEL_1_1: u4_max_mbs_allowed = MAX_MBS_LEVEL_11; break; case H264_LEVEL_1_2: u4_max_mbs_allowed = MAX_MBS_LEVEL_12; break; case H264_LEVEL_1_3: u4_max_mbs_allowed = MAX_MBS_LEVEL_13; break; case H264_LEVEL_2_0: u4_max_mbs_allowed = MAX_MBS_LEVEL_20; break; case H264_LEVEL_2_1: u4_max_mbs_allowed = MAX_MBS_LEVEL_21; break; case H264_LEVEL_2_2: u4_max_mbs_allowed = MAX_MBS_LEVEL_22; break; case H264_LEVEL_3_0: u4_max_mbs_allowed = MAX_MBS_LEVEL_30; break; case H264_LEVEL_3_1: u4_max_mbs_allowed = MAX_MBS_LEVEL_31; break; case H264_LEVEL_3_2: u4_max_mbs_allowed = MAX_MBS_LEVEL_32; break; case H264_LEVEL_4_0: u4_max_mbs_allowed = MAX_MBS_LEVEL_40; break; case H264_LEVEL_4_1: u4_max_mbs_allowed = MAX_MBS_LEVEL_41; break; case H264_LEVEL_4_2: u4_max_mbs_allowed = MAX_MBS_LEVEL_42; break; case H264_LEVEL_5_0: u4_max_mbs_allowed = MAX_MBS_LEVEL_50; break; case H264_LEVEL_5_1: default: u4_max_mbs_allowed = MAX_MBS_LEVEL_51; break; } /*correct of the level is incorrect*/ if(u4_total_mbs > u4_max_mbs_allowed) { if(u4_total_mbs > MAX_MBS_LEVEL_50) u4_level_idc = H264_LEVEL_5_1; else if(u4_total_mbs > MAX_MBS_LEVEL_42) u4_level_idc = H264_LEVEL_5_0; else if(u4_total_mbs > MAX_MBS_LEVEL_41) u4_level_idc = H264_LEVEL_4_2; else if(u4_total_mbs > MAX_MBS_LEVEL_40) u4_level_idc = H264_LEVEL_4_1; else if(u4_total_mbs > MAX_MBS_LEVEL_32) u4_level_idc = H264_LEVEL_4_0; else if(u4_total_mbs > MAX_MBS_LEVEL_31) u4_level_idc = H264_LEVEL_3_2; else if(u4_total_mbs > MAX_MBS_LEVEL_30) u4_level_idc = H264_LEVEL_3_1; else if(u4_total_mbs > MAX_MBS_LEVEL_21) u4_level_idc = H264_LEVEL_3_0; else if(u4_total_mbs > MAX_MBS_LEVEL_20) u4_level_idc = H264_LEVEL_2_1; else if(u4_total_mbs > MAX_MBS_LEVEL_10) u4_level_idc = H264_LEVEL_2_0; } return (u4_level_idc); } WORD32 ih264d_parse_sps(dec_struct_t *ps_dec, dec_bit_stream_t *ps_bitstrm) { UWORD8 i; dec_seq_params_t *ps_seq = NULL; UWORD8 u1_profile_idc, u1_level_idc, u1_seq_parameter_set_id, u1_mb_aff_flag = 0; UWORD16 i2_max_frm_num; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD8 u1_frm, uc_constraint_set0_flag, uc_constraint_set1_flag; WORD32 i4_cropped_ht, i4_cropped_wd; UWORD32 u4_temp; UWORD64 u8_temp; UWORD32 u4_pic_height_in_map_units, u4_pic_width_in_mbs; UWORD32 u2_pic_wd = 0; UWORD32 u2_pic_ht = 0; UWORD32 u2_frm_wd_y = 0; UWORD32 u2_frm_ht_y = 0; UWORD32 u2_frm_wd_uv = 0; UWORD32 u2_frm_ht_uv = 0; UWORD32 u2_crop_offset_y = 0; UWORD32 u2_crop_offset_uv = 0; WORD32 ret; WORD32 num_reorder_frames; /* High profile related syntax element */ WORD32 i4_i; /* G050 */ UWORD8 u1_frame_cropping_flag, u1_frame_cropping_rect_left_ofst, u1_frame_cropping_rect_right_ofst, u1_frame_cropping_rect_top_ofst, u1_frame_cropping_rect_bottom_ofst; /* G050 */ /*--------------------------------------------------------------------*/ /* Decode seq_parameter_set_id and profile and level values */ /*--------------------------------------------------------------------*/ SWITCHONTRACE; u1_profile_idc = ih264d_get_bits_h264(ps_bitstrm, 8); COPYTHECONTEXT("SPS: profile_idc",u1_profile_idc); /* G050 */ uc_constraint_set0_flag = ih264d_get_bit_h264(ps_bitstrm); uc_constraint_set1_flag = ih264d_get_bit_h264(ps_bitstrm); ih264d_get_bit_h264(ps_bitstrm); /*****************************************************/ /* Read 5 bits for uc_constraint_set3_flag (1 bit) */ /* and reserved_zero_4bits (4 bits) - Sushant */ /*****************************************************/ ih264d_get_bits_h264(ps_bitstrm, 5); /* G050 */ /* Check whether particular profile is suported or not */ /* Check whether particular profile is suported or not */ if((u1_profile_idc != MAIN_PROFILE_IDC) && (u1_profile_idc != BASE_PROFILE_IDC) && (u1_profile_idc != HIGH_PROFILE_IDC) ) { /* Apart from Baseline, main and high profile, * only extended profile is supported provided * uc_constraint_set0_flag or uc_constraint_set1_flag are set to 1 */ if((u1_profile_idc != EXTENDED_PROFILE_IDC) || ((uc_constraint_set1_flag != 1) && (uc_constraint_set0_flag != 1))) { return (ERROR_FEATURE_UNAVAIL); } } u1_level_idc = ih264d_get_bits_h264(ps_bitstrm, 8); COPYTHECONTEXT("SPS: u4_level_idc",u1_level_idc); u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp & MASK_ERR_SEQ_SET_ID) return ERROR_INV_SPS_PPS_T; u1_seq_parameter_set_id = u4_temp; COPYTHECONTEXT("SPS: seq_parameter_set_id", u1_seq_parameter_set_id); /*--------------------------------------------------------------------*/ /* Find an seq param entry in seqparam array of decStruct */ /*--------------------------------------------------------------------*/ ps_seq = ps_dec->pv_scratch_sps_pps; memset(ps_seq, 0, sizeof(dec_seq_params_t)); if(ps_dec->i4_header_decoded & 1) { *ps_seq = *ps_dec->ps_cur_sps; } if((ps_dec->i4_header_decoded & 1) && (ps_seq->u1_profile_idc != u1_profile_idc)) { ps_dec->u1_res_changed = 1; return IVD_RES_CHANGED; } if((ps_dec->i4_header_decoded & 1) && (ps_seq->u1_level_idc != u1_level_idc)) { ps_dec->u1_res_changed = 1; return IVD_RES_CHANGED; } ps_seq->u1_profile_idc = u1_profile_idc; ps_seq->u1_level_idc = u1_level_idc; ps_seq->u1_seq_parameter_set_id = u1_seq_parameter_set_id; /*******************************************************************/ /* Initializations for high profile - Sushant */ /*******************************************************************/ ps_seq->i4_chroma_format_idc = 1; ps_seq->i4_bit_depth_luma_minus8 = 0; ps_seq->i4_bit_depth_chroma_minus8 = 0; ps_seq->i4_qpprime_y_zero_transform_bypass_flag = 0; ps_seq->i4_seq_scaling_matrix_present_flag = 0; if(u1_profile_idc == HIGH_PROFILE_IDC) { /* reading chroma_format_idc */ ps_seq->i4_chroma_format_idc = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); /* Monochrome is not supported */ if(ps_seq->i4_chroma_format_idc != 1) { return ERROR_FEATURE_UNAVAIL; } /* reading bit_depth_luma_minus8 */ ps_seq->i4_bit_depth_luma_minus8 = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(ps_seq->i4_bit_depth_luma_minus8 != 0) { return ERROR_FEATURE_UNAVAIL; } /* reading bit_depth_chroma_minus8 */ ps_seq->i4_bit_depth_chroma_minus8 = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(ps_seq->i4_bit_depth_chroma_minus8 != 0) { return ERROR_FEATURE_UNAVAIL; } /* reading qpprime_y_zero_transform_bypass_flag */ ps_seq->i4_qpprime_y_zero_transform_bypass_flag = (WORD32)ih264d_get_bit_h264(ps_bitstrm); if(ps_seq->i4_qpprime_y_zero_transform_bypass_flag != 0) { return ERROR_INV_SPS_PPS_T; } /* reading seq_scaling_matrix_present_flag */ ps_seq->i4_seq_scaling_matrix_present_flag = (WORD32)ih264d_get_bit_h264(ps_bitstrm); if(ps_seq->i4_seq_scaling_matrix_present_flag) { for(i4_i = 0; i4_i < 8; i4_i++) { ps_seq->u1_seq_scaling_list_present_flag[i4_i] = ih264d_get_bit_h264(ps_bitstrm); /* initialize u1_use_default_scaling_matrix_flag[i4_i] to zero */ /* before calling scaling list */ ps_seq->u1_use_default_scaling_matrix_flag[i4_i] = 0; if(ps_seq->u1_seq_scaling_list_present_flag[i4_i]) { if(i4_i < 6) { ret = ih264d_scaling_list( ps_seq->i2_scalinglist4x4[i4_i], 16, &ps_seq->u1_use_default_scaling_matrix_flag[i4_i], ps_bitstrm); } else { ret = ih264d_scaling_list( ps_seq->i2_scalinglist8x8[i4_i - 6], 64, &ps_seq->u1_use_default_scaling_matrix_flag[i4_i], ps_bitstrm); } if(ret != OK) { return ret; } } } } } /*--------------------------------------------------------------------*/ /* Decode MaxFrameNum */ /*--------------------------------------------------------------------*/ u8_temp = (UWORD64)4 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u8_temp > MAX_BITS_IN_FRAME_NUM) { return ERROR_INV_SPS_PPS_T; } ps_seq->u1_bits_in_frm_num = u8_temp; COPYTHECONTEXT("SPS: log2_max_frame_num_minus4", (ps_seq->u1_bits_in_frm_num - 4)); i2_max_frm_num = (1 << (ps_seq->u1_bits_in_frm_num)); ps_seq->u2_u4_max_pic_num_minus1 = i2_max_frm_num - 1; /*--------------------------------------------------------------------*/ /* Decode picture order count and related values */ /*--------------------------------------------------------------------*/ u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp > MAX_PIC_ORDER_CNT_TYPE) { return ERROR_INV_POC_TYPE_T; } ps_seq->u1_pic_order_cnt_type = u4_temp; COPYTHECONTEXT("SPS: pic_order_cnt_type",ps_seq->u1_pic_order_cnt_type); ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle = 1; if(ps_seq->u1_pic_order_cnt_type == 0) { u8_temp = (UWORD64)4 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u8_temp > MAX_BITS_IN_POC_LSB) { return ERROR_INV_SPS_PPS_T; } ps_seq->u1_log2_max_pic_order_cnt_lsb_minus = u8_temp; ps_seq->i4_max_pic_order_cntLsb = (1 << u8_temp); COPYTHECONTEXT("SPS: log2_max_pic_order_cnt_lsb_minus4",(u8_temp - 4)); } else if(ps_seq->u1_pic_order_cnt_type == 1) { ps_seq->u1_delta_pic_order_always_zero_flag = ih264d_get_bit_h264( ps_bitstrm); COPYTHECONTEXT("SPS: delta_pic_order_always_zero_flag", ps_seq->u1_delta_pic_order_always_zero_flag); ps_seq->i4_ofst_for_non_ref_pic = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SPS: offset_for_non_ref_pic", ps_seq->i4_ofst_for_non_ref_pic); ps_seq->i4_ofst_for_top_to_bottom_field = ih264d_sev( pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SPS: offset_for_top_to_bottom_field", ps_seq->i4_ofst_for_top_to_bottom_field); u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp > 255) return ERROR_INV_SPS_PPS_T; ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle = u4_temp; COPYTHECONTEXT("SPS: num_ref_frames_in_pic_order_cnt_cycle", ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle); for(i = 0; i < ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle; i++) { ps_seq->i4_ofst_for_ref_frame[i] = ih264d_sev( pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SPS: offset_for_ref_frame", ps_seq->i4_ofst_for_ref_frame[i]); } } u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((u4_temp > H264_MAX_REF_PICS)) { return ERROR_NUM_REF; } /* Compare with older num_ref_frames is header is already once */ if((ps_dec->i4_header_decoded & 1) && (ps_seq->u1_num_ref_frames != u4_temp)) { ps_dec->u1_res_changed = 1; return IVD_RES_CHANGED; } ps_seq->u1_num_ref_frames = u4_temp; COPYTHECONTEXT("SPS: num_ref_frames",ps_seq->u1_num_ref_frames); ps_seq->u1_gaps_in_frame_num_value_allowed_flag = ih264d_get_bit_h264( ps_bitstrm); COPYTHECONTEXT("SPS: gaps_in_frame_num_value_allowed_flag", ps_seq->u1_gaps_in_frame_num_value_allowed_flag); /*--------------------------------------------------------------------*/ /* Decode FrameWidth and FrameHeight and related values */ /*--------------------------------------------------------------------*/ u8_temp = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); /* Check for unsupported resolutions*/ if(u8_temp > (H264_MAX_FRAME_WIDTH >> 4)) { return IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED; } u4_pic_width_in_mbs = u8_temp; COPYTHECONTEXT("SPS: pic_width_in_mbs_minus1", u4_pic_width_in_mbs - 1); u8_temp = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if (u8_temp > (H264_MAX_FRAME_HEIGHT >> 4)) { return IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED; } u4_pic_height_in_map_units = u8_temp; ps_seq->u2_frm_wd_in_mbs = u4_pic_width_in_mbs; ps_seq->u2_frm_ht_in_mbs = u4_pic_height_in_map_units; u2_pic_wd = (u4_pic_width_in_mbs << 4); u2_pic_ht = (u4_pic_height_in_map_units << 4); /*--------------------------------------------------------------------*/ /* Get the value of MaxMbAddress and Number of bits needed for it */ /*--------------------------------------------------------------------*/ ps_seq->u2_max_mb_addr = (ps_seq->u2_frm_wd_in_mbs * ps_seq->u2_frm_ht_in_mbs) - 1; ps_seq->u2_total_num_of_mbs = ps_seq->u2_max_mb_addr + 1; ps_seq->u1_level_idc = ih264d_correct_level_idc( u1_level_idc, ps_seq->u2_total_num_of_mbs); u1_frm = ih264d_get_bit_h264(ps_bitstrm); if((ps_dec->i4_header_decoded & 1) && (ps_seq->u1_frame_mbs_only_flag != u1_frm)) { ps_dec->u1_res_changed = 1; return IVD_RES_CHANGED; } ps_seq->u1_frame_mbs_only_flag = u1_frm; COPYTHECONTEXT("SPS: frame_mbs_only_flag", u1_frm); if(!u1_frm) u1_mb_aff_flag = ih264d_get_bit_h264(ps_bitstrm); if((ps_dec->i4_header_decoded & 1) && (ps_seq->u1_mb_aff_flag != u1_mb_aff_flag)) { ps_dec->u1_res_changed = 1; return IVD_RES_CHANGED; } if(!u1_frm) { u2_pic_ht <<= 1; ps_seq->u1_mb_aff_flag = u1_mb_aff_flag; COPYTHECONTEXT("SPS: mb_adaptive_frame_field_flag", ps_seq->u1_mb_aff_flag); } else ps_seq->u1_mb_aff_flag = 0; ps_seq->u1_direct_8x8_inference_flag = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("SPS: direct_8x8_inference_flag", ps_seq->u1_direct_8x8_inference_flag); /* G050 */ u1_frame_cropping_flag = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("SPS: frame_cropping_flag",u1_frame_cropping_flag); if(u1_frame_cropping_flag) { u1_frame_cropping_rect_left_ofst = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SPS: frame_cropping_rect_left_offset", u1_frame_cropping_rect_left_ofst); u1_frame_cropping_rect_right_ofst = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SPS: frame_cropping_rect_right_offset", u1_frame_cropping_rect_right_ofst); u1_frame_cropping_rect_top_ofst = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SPS: frame_cropping_rect_top_offset", u1_frame_cropping_rect_top_ofst); u1_frame_cropping_rect_bottom_ofst = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SPS: frame_cropping_rect_bottom_offset", u1_frame_cropping_rect_bottom_ofst); ps_dec->u1_frame_cropping_flag = u1_frame_cropping_flag; ps_dec->u1_frame_cropping_rect_left_ofst = u1_frame_cropping_rect_left_ofst; ps_dec->u1_frame_cropping_rect_right_ofst = u1_frame_cropping_rect_right_ofst; ps_dec->u1_frame_cropping_rect_top_ofst = u1_frame_cropping_rect_top_ofst; ps_dec->u1_frame_cropping_rect_bottom_ofst = u1_frame_cropping_rect_bottom_ofst; } else { ps_dec->u1_frame_cropping_flag = 0; ps_dec->u1_frame_cropping_rect_left_ofst = 0; ps_dec->u1_frame_cropping_rect_right_ofst = 0; ps_dec->u1_frame_cropping_rect_top_ofst = 0; ps_dec->u1_frame_cropping_rect_bottom_ofst = 0; } /* G050 */ ps_seq->u1_vui_parameters_present_flag = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("SPS: vui_parameters_present_flag", ps_seq->u1_vui_parameters_present_flag); u2_frm_wd_y = u2_pic_wd + (UWORD8)(PAD_LEN_Y_H << 1); if(1 == ps_dec->u4_share_disp_buf) { if(ps_dec->u4_app_disp_width > u2_frm_wd_y) u2_frm_wd_y = ps_dec->u4_app_disp_width; } u2_frm_ht_y = u2_pic_ht + (UWORD8)(PAD_LEN_Y_V << 2); u2_frm_wd_uv = u2_pic_wd + (UWORD8)(PAD_LEN_UV_H << 2); u2_frm_wd_uv = MAX(u2_frm_wd_uv, u2_frm_wd_y); u2_frm_ht_uv = (u2_pic_ht >> 1) + (UWORD8)(PAD_LEN_UV_V << 2); u2_frm_ht_uv = MAX(u2_frm_ht_uv, (u2_frm_ht_y >> 1)); /* Calculate display picture width, height and start u4_ofst from YUV420 */ /* pictute buffers as per cropping information parsed above */ { UWORD16 u2_rgt_ofst = 0; UWORD16 u2_lft_ofst = 0; UWORD16 u2_top_ofst = 0; UWORD16 u2_btm_ofst = 0; UWORD8 u1_frm_mbs_flag; UWORD8 u1_vert_mult_factor; //if(u1_frame_cropping_flag) //{ // /* Calculate right and left u4_ofst for cropped picture */ // u2_rgt_ofst = u1_frame_cropping_rect_right_ofst << 1; // u2_lft_ofst = u1_frame_cropping_rect_left_ofst << 1; // /* Know frame MBs only u4_flag */ // u1_frm_mbs_flag = (1 == ps_seq->u1_frame_mbs_only_flag); // /* Simplify the vertical u4_ofst calculation from field/frame */ // u1_vert_mult_factor = (2 - u1_frm_mbs_flag); // /* Calculate bottom and top u4_ofst for cropped picture */ // u2_btm_ofst = (u1_frame_cropping_rect_bottom_ofst // << u1_vert_mult_factor); // u2_top_ofst = (u1_frame_cropping_rect_top_ofst // << u1_vert_mult_factor); //} /* Calculate u4_ofst from start of YUV 420 picture buffer to start of*/ /* cropped picture buffer */ u2_crop_offset_y = (u2_frm_wd_y * u2_top_ofst) + (u2_lft_ofst); u2_crop_offset_uv = (u2_frm_wd_uv * (u2_top_ofst >> 1)) + (u2_lft_ofst >> 1) * YUV420SP_FACTOR; /* Calculate the display picture width and height based on crop */ /* information */ i4_cropped_ht = (WORD32)u2_pic_ht - (WORD32)(u2_btm_ofst + u2_top_ofst); i4_cropped_wd = (WORD32)u2_pic_wd - (WORD32)(u2_rgt_ofst + u2_lft_ofst); if((i4_cropped_ht < MB_SIZE) || (i4_cropped_wd < MB_SIZE)) { return ERROR_INV_SPS_PPS_T; } if((ps_dec->i4_header_decoded & 1) && (ps_dec->u2_pic_wd != u2_pic_wd)) { ps_dec->u1_res_changed = 1; return IVD_RES_CHANGED; } if((ps_dec->i4_header_decoded & 1) && (ps_dec->u2_disp_width != i4_cropped_wd)) { ps_dec->u1_res_changed = 1; return IVD_RES_CHANGED; } if((ps_dec->i4_header_decoded & 1) && (ps_dec->u2_pic_ht != u2_pic_ht)) { ps_dec->u1_res_changed = 1; return IVD_RES_CHANGED; } if((ps_dec->i4_header_decoded & 1) && (ps_dec->u2_disp_height != i4_cropped_ht)) { ps_dec->u1_res_changed = 1; return IVD_RES_CHANGED; } /* Check again for unsupported resolutions with updated values*/ if((u2_pic_wd > H264_MAX_FRAME_WIDTH) || (u2_pic_ht > H264_MAX_FRAME_HEIGHT) || (u2_pic_wd < H264_MIN_FRAME_WIDTH) || (u2_pic_ht < H264_MIN_FRAME_HEIGHT) || (u2_pic_wd * (UWORD32)u2_pic_ht > H264_MAX_FRAME_SIZE)) { return IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED; } /* If MBAff is enabled, decoder support is limited to streams with * width less than half of H264_MAX_FRAME_WIDTH. * In case of MBAff decoder processes two rows at a time */ if((u2_pic_wd << ps_seq->u1_mb_aff_flag) > H264_MAX_FRAME_WIDTH) { return IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED; } } /* Backup num_reorder_frames if header is already decoded */ if((ps_dec->i4_header_decoded & 1) && (1 == ps_seq->u1_vui_parameters_present_flag) && (1 == ps_seq->s_vui.u1_bitstream_restriction_flag)) { num_reorder_frames = (WORD32)ps_seq->s_vui.u4_num_reorder_frames; } else { num_reorder_frames = -1; } if(1 == ps_seq->u1_vui_parameters_present_flag) { ret = ih264d_parse_vui_parametres(&ps_seq->s_vui, ps_bitstrm); if(ret != OK) return ret; } /* Compare older num_reorder_frames with the new one if header is already decoded */ if((ps_dec->i4_header_decoded & 1) && (-1 != num_reorder_frames) && (1 == ps_seq->u1_vui_parameters_present_flag) && (1 == ps_seq->s_vui.u1_bitstream_restriction_flag) && ((WORD32)ps_seq->s_vui.u4_num_reorder_frames != num_reorder_frames)) { ps_dec->u1_res_changed = 1; return IVD_RES_CHANGED; } /* In case bitstream read has exceeded the filled size, then return an error */ if (EXCEED_OFFSET(ps_bitstrm)) { return ERROR_INV_SPS_PPS_T; } /*--------------------------------------------------------------------*/ /* All initializations to ps_dec are beyond this point */ /*--------------------------------------------------------------------*/ { WORD32 reorder_depth = ih264d_get_dpb_size(ps_seq); if((1 == ps_seq->u1_vui_parameters_present_flag) && (1 == ps_seq->s_vui.u1_bitstream_restriction_flag)) { reorder_depth = ps_seq->s_vui.u4_num_reorder_frames + 1; } if (reorder_depth > H264_MAX_REF_PICS) { return ERROR_INV_SPS_PPS_T; } if(ps_seq->u1_frame_mbs_only_flag != 1) reorder_depth *= 2; ps_dec->i4_reorder_depth = reorder_depth + DISPLAY_LATENCY; } ps_dec->u2_disp_height = i4_cropped_ht; ps_dec->u2_disp_width = i4_cropped_wd; ps_dec->u2_pic_wd = u2_pic_wd; ps_dec->u2_pic_ht = u2_pic_ht; /* Determining the Width and Height of Frame from that of Picture */ ps_dec->u2_frm_wd_y = u2_frm_wd_y; ps_dec->u2_frm_ht_y = u2_frm_ht_y; ps_dec->u2_frm_wd_uv = u2_frm_wd_uv; ps_dec->u2_frm_ht_uv = u2_frm_ht_uv; ps_dec->s_pad_mgr.u1_pad_len_y_v = (UWORD8)(PAD_LEN_Y_V << (1 - u1_frm)); ps_dec->s_pad_mgr.u1_pad_len_cr_v = (UWORD8)(PAD_LEN_UV_V << (1 - u1_frm)); ps_dec->u2_frm_wd_in_mbs = ps_seq->u2_frm_wd_in_mbs; ps_dec->u2_frm_ht_in_mbs = ps_seq->u2_frm_ht_in_mbs; ps_dec->u2_crop_offset_y = u2_crop_offset_y; ps_dec->u2_crop_offset_uv = u2_crop_offset_uv; ps_seq->u1_is_valid = TRUE; ps_dec->ps_sps[u1_seq_parameter_set_id] = *ps_seq; ps_dec->ps_cur_sps = &ps_dec->ps_sps[u1_seq_parameter_set_id]; return OK; } /*! ************************************************************************** * \if Function name : ih264d_parse_end_of_sequence \endif * * \brief * Decodes End of Sequence. * * \param ps_bitstrm : Pointer to bit ps_bitstrm containing the NAL unit * * \return * 0 on Success and error code otherwise ************************************************************************** */ WORD32 ih264d_parse_end_of_sequence(dec_struct_t * ps_dec) { WORD32 ret; ret = ih264d_end_of_pic_processing(ps_dec); return ret; } /*! ************************************************************************** * \if Function name : AcessUnitDelimiterRbsp \endif * * \brief * Decodes AcessUnitDelimiterRbsp. * * \param ps_bitstrm : Pointer to bit ps_bitstrm containing the NAL unit * * \return * 0 on Success and error code otherwise ************************************************************************** */ WORD32 ih264d_access_unit_delimiter_rbsp(dec_struct_t * ps_dec) { UWORD8 u1_primary_pic_type; u1_primary_pic_type = ih264d_get_bits_h264(ps_dec->ps_bitstrm, 3); switch(u1_primary_pic_type) { case I_PIC: case SI_PIC: case ISI_PIC: ps_dec->ps_dec_err_status->u1_pic_aud_i = PIC_TYPE_I; break; default: ps_dec->ps_dec_err_status->u1_pic_aud_i = PIC_TYPE_UNKNOWN; } return (0); } /*! ************************************************************************** * \if Function name : ih264d_parse_nal_unit \endif * * \brief * Decodes NAL unit * * \return * 0 on Success and error code otherwise ************************************************************************** */ WORD32 ih264d_parse_nal_unit(iv_obj_t *dec_hdl, ivd_video_decode_op_t *ps_dec_op, UWORD8 *pu1_buf, UWORD32 u4_length) { dec_bit_stream_t *ps_bitstrm; dec_struct_t *ps_dec = (dec_struct_t *)dec_hdl->pv_codec_handle; ivd_video_decode_ip_t *ps_dec_in = (ivd_video_decode_ip_t *)ps_dec->pv_dec_in; dec_slice_params_t * ps_cur_slice = ps_dec->ps_cur_slice; UWORD8 u1_first_byte, u1_nal_ref_idc; UWORD8 u1_nal_unit_type; WORD32 i_status = OK; ps_bitstrm = ps_dec->ps_bitstrm; if(pu1_buf) { if(u4_length) { ps_dec_op->u4_frame_decoded_flag = 0; ih264d_process_nal_unit(ps_dec->ps_bitstrm, pu1_buf, u4_length); SWITCHOFFTRACE; u1_first_byte = ih264d_get_bits_h264(ps_bitstrm, 8); if(NAL_FORBIDDEN_BIT(u1_first_byte)) { H264_DEC_DEBUG_PRINT("\nForbidden bit set in Nal Unit, Let's try\n"); } u1_nal_unit_type = NAL_UNIT_TYPE(u1_first_byte); // if any other nal unit other than slice nal is encountered in between a // frame break out of loop without consuming header if ((ps_dec->u4_slice_start_code_found == 1) && (ps_dec->u1_pic_decode_done != 1) && (u1_nal_unit_type > IDR_SLICE_NAL)) { return ERROR_INCOMPLETE_FRAME; } ps_dec->u1_nal_unit_type = u1_nal_unit_type; u1_nal_ref_idc = (UWORD8)(NAL_REF_IDC(u1_first_byte)); //Skip all NALUs if SPS and PPS are not decoded switch(u1_nal_unit_type) { case SLICE_DATA_PARTITION_A_NAL: case SLICE_DATA_PARTITION_B_NAL: case SLICE_DATA_PARTITION_C_NAL: if(!ps_dec->i4_decode_header) ih264d_parse_slice_partition(ps_dec, ps_bitstrm); break; case IDR_SLICE_NAL: case SLICE_NAL: /* ! */ DEBUG_THREADS_PRINTF("Decoding a slice NAL\n"); if(!ps_dec->i4_decode_header) { if(ps_dec->i4_header_decoded == 3) { ih264d_get_pre_sei_params(ps_dec, u1_nal_unit_type); /* ! */ ps_dec->u4_slice_start_code_found = 1; ih264d_rbsp_to_sodb(ps_dec->ps_bitstrm); i_status = ih264d_parse_decode_slice( (UWORD8)(u1_nal_unit_type == IDR_SLICE_NAL), u1_nal_ref_idc, ps_dec); if(i_status != OK) { return i_status; } } else { H264_DEC_DEBUG_PRINT( "\nSlice NAL Supplied but no header has been supplied\n"); } } break; case SEI_NAL: if(!ps_dec->i4_decode_header) { ih264d_rbsp_to_sodb(ps_dec->ps_bitstrm); i_status = ih264d_parse_sei_message(ps_dec, ps_bitstrm); if(i_status != OK) return i_status; ih264d_parse_sei(ps_dec, ps_bitstrm); } break; case SEQ_PARAM_NAL: /* ! */ ih264d_rbsp_to_sodb(ps_dec->ps_bitstrm); i_status = ih264d_parse_sps(ps_dec, ps_bitstrm); ps_dec->u4_sps_cnt_in_process++; /*If a resolution change happens within a process call, due to multiple sps * we will not support it. */ if((ps_dec->u4_sps_cnt_in_process > 1 ) && (i_status == IVD_RES_CHANGED)) { i_status = ERROR_INV_SPS_PPS_T; ps_dec->u1_res_changed = 0; } if(i_status == ERROR_INV_SPS_PPS_T) return i_status; if(!i_status) ps_dec->i4_header_decoded |= 0x1; break; case PIC_PARAM_NAL: /* ! */ ih264d_rbsp_to_sodb(ps_dec->ps_bitstrm); i_status = ih264d_parse_pps(ps_dec, ps_bitstrm); if(i_status == ERROR_INV_SPS_PPS_T) return i_status; if(!i_status) ps_dec->i4_header_decoded |= 0x2; break; case ACCESS_UNIT_DELIMITER_RBSP: if(!ps_dec->i4_decode_header) { ih264d_access_unit_delimiter_rbsp(ps_dec); } break; //Let us ignore the END_OF_SEQ_RBSP NAL and decode even after this NAL case END_OF_STREAM_RBSP: if(!ps_dec->i4_decode_header) { ih264d_parse_end_of_stream(ps_dec); } break; case FILLER_DATA_NAL: if(!ps_dec->i4_decode_header) { ih264d_parse_filler_data(ps_dec, ps_bitstrm); } break; default: H264_DEC_DEBUG_PRINT("\nUnknown NAL type %d\n", u1_nal_unit_type); break; } } } return i_status; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_headers.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore * Modified for use with Cemu emulator project */ #ifndef _IH264D_PARSE_HEADERS_H_ #define _IH264D_PARSE_HEADERS_H_ /*! ************************************************************************** * \file ih264d_parse_headers.h * * \brief * Contains declarations high level syntax[above slice] * parsing routines * * \date * 19/12/2002 * * \author AI ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_bitstrm.h" #include "ih264d_structs.h" WORD32 ih264d_parse_nal_unit(iv_obj_t *dec_hdl, ivd_video_decode_op_t *ps_dec_op, UWORD8 *pu1_buf, UWORD32 u4_length); #endif /* _IH264D_PARSE_HEADERS_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_islice.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_parse_islice.c * * \brief * Contains routines that decode a I slice type * * Detailed_description * * \date * 07/07/2003 * * \author NS ************************************************************************** */ #include <string.h> #include "ih264_defs.h" #include "ih264d_error_handler.h" #include "ih264d_debug.h" #include "ih264d_bitstrm.h" #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264d_parse_cavlc.h" #include "ih264d_mb_utils.h" #include "ih264d_deblocking.h" #include "ih264d_cabac.h" #include "ih264d_parse_cabac.h" #include "ih264d_parse_mb_header.h" #include "ih264d_parse_slice.h" #include "ih264d_process_pslice.h" #include "ih264d_process_intra_mb.h" #include "ih264d_parse_islice.h" #include "ih264d_error_handler.h" #include "ih264d_mvpred.h" #include "ih264d_defs.h" #include "ih264d_thread_parse_decode.h" #include "ithread.h" #include "ih264d_parse_mb_header.h" #include "assert.h" #include "ih264d_utils.h" #include "ih264d_format_conv.h" void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec); void ih264d_itrans_recon_luma_dc(dec_struct_t *ps_dec, WORD16* pi2_src, WORD16* pi2_coeff_block, const UWORD16 *pu2_weigh_mat); /*! ************************************************************************** * \if Function name : ParseIMb \endif * * \brief * This function parses CAVLC syntax of a I MB. If 16x16 Luma DC transform * is also done here. Transformed Luma DC values are copied in their * 0th pixel location of corrosponding CoeffBlock. * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_imb_cavlc(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_mb_type) { WORD32 i4_delta_qp; UWORD32 u4_temp; UWORD32 ui_is_top_mb_available; UWORD32 ui_is_left_mb_available; UWORD32 u4_cbp; UWORD32 u4_offset; UWORD32 *pu4_bitstrm_buf; WORD32 ret; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UNUSED(u1_mb_num); ps_cur_mb_info->u1_tran_form8x8 = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; ps_cur_mb_info->u1_yuv_dc_block_flag = 0; u4_temp = ps_dec->u1_mb_ngbr_availablity; ui_is_top_mb_available = BOOLEAN(u4_temp & TOP_MB_AVAILABLE_MASK); ui_is_left_mb_available = BOOLEAN(u4_temp & LEFT_MB_AVAILABLE_MASK); pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; if(u1_mb_type == I_4x4_MB) { ps_cur_mb_info->ps_curmb->u1_mb_type = I_4x4_MB; u4_offset = 0; /*--------------------------------------------------------------------*/ /* Read transform_size_8x8_flag if present */ /*--------------------------------------------------------------------*/ if(ps_dec->s_high_profile.u1_transform8x8_present) { ps_cur_mb_info->u1_tran_form8x8 = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; } /*--------------------------------------------------------------------*/ /* Read the IntraPrediction modes for LUMA */ /*--------------------------------------------------------------------*/ if (!ps_cur_mb_info->u1_tran_form8x8) { UWORD8 *pu1_temp; ih264d_read_intra_pred_modes(ps_dec, ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data), ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data+16), ps_cur_mb_info->u1_tran_form8x8); pu1_temp = (UWORD8 *)ps_dec->pv_parse_tu_coeff_data; pu1_temp += 32; ps_dec->pv_parse_tu_coeff_data = (void *)pu1_temp; } else { UWORD8 *pu1_temp; ih264d_read_intra_pred_modes(ps_dec, ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data), ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data+4), ps_cur_mb_info->u1_tran_form8x8); pu1_temp = (UWORD8 *)ps_dec->pv_parse_tu_coeff_data; pu1_temp += 8; ps_dec->pv_parse_tu_coeff_data = (void *)pu1_temp; } /*--------------------------------------------------------------------*/ /* Read the IntraPrediction mode for CHROMA */ /*--------------------------------------------------------------------*/ //Inlined ih264d_uev { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_temp; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) { GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); } *pu4_bitstrm_ofst = u4_bitstream_offset; u4_temp = ((1 << u4_ldz) + u4_word - 1); if(u4_temp > 3) { return ERROR_CHROMA_PRED_MODE; } ps_cur_mb_info->u1_chroma_pred_mode = u4_temp; COPYTHECONTEXT("intra_chroma_pred_mode", ps_cur_mb_info->u1_chroma_pred_mode); } /*--------------------------------------------------------------------*/ /* Read the Coded block pattern */ /*--------------------------------------------------------------------*/ { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) { GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); } *pu4_bitstrm_ofst = u4_bitstream_offset; u4_cbp = ((1 << u4_ldz) + u4_word - 1); } if(u4_cbp > 47) { return ERROR_CBP; } u4_cbp = gau1_ih264d_cbp_table[u4_cbp][0]; COPYTHECONTEXT("coded_block_pattern", u1_cbp); ps_cur_mb_info->u1_cbp = u4_cbp; /*--------------------------------------------------------------------*/ /* Read mb_qp_delta */ /*--------------------------------------------------------------------*/ if(ps_cur_mb_info->u1_cbp) { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_abs_val; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) { GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); } *pu4_bitstrm_ofst = u4_bitstream_offset; u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; if(u4_word & 0x1) { i4_delta_qp = (-(WORD32)u4_abs_val); } else { i4_delta_qp = (u4_abs_val); } if((i4_delta_qp < -26) || (i4_delta_qp > 25)) { return ERROR_INV_RANGE_QP_T; } COPYTHECONTEXT("mb_qp_delta", i1_delta_qp); if(i4_delta_qp != 0) { ret = ih264d_update_qp(ps_dec, (WORD8)i4_delta_qp); if(ret != OK) return ret; } } } else { u4_offset = 1; ps_cur_mb_info->ps_curmb->u1_mb_type = I_16x16_MB; /*-------------------------------------------------------------------*/ /* Read the IntraPrediction mode for CHROMA */ /*-------------------------------------------------------------------*/ { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) { GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); } *pu4_bitstrm_ofst = u4_bitstream_offset; u4_temp = ((1 << u4_ldz) + u4_word - 1); //Inlined ih264d_uev if(u4_temp > 3) { return ERROR_CHROMA_PRED_MODE; } ps_cur_mb_info->u1_chroma_pred_mode = u4_temp; COPYTHECONTEXT("intra_chroma_pred_mode", ps_cur_mb_info->u1_chroma_pred_mode); } /*-------------------------------------------------------------------*/ /* Read the Coded block pattern */ /*-------------------------------------------------------------------*/ u4_cbp = gau1_ih264d_cbp_tab[(u1_mb_type - 1) >> 2]; ps_cur_mb_info->u1_cbp = u4_cbp; /*-------------------------------------------------------------------*/ /* Read mb_qp_delta */ /*-------------------------------------------------------------------*/ { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_abs_val; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; if(u4_word & 0x1) i4_delta_qp = (-(WORD32)u4_abs_val); else i4_delta_qp = (u4_abs_val); if((i4_delta_qp < -26) || (i4_delta_qp > 25)) return ERROR_INV_RANGE_QP_T; } //inlinined ih264d_sev COPYTHECONTEXT("Delta quant", i1_delta_qp); if(i4_delta_qp != 0) { ret = ih264d_update_qp(ps_dec, (WORD8)i4_delta_qp); if(ret != OK) return ret; } { WORD16 i_scaleFactor; UWORD32 ui_N = 0; WORD16 *pi2_scale_matrix_ptr; /*******************************************************************/ /* for luma DC coefficients the scaling is done during the parsing */ /* to preserve the precision */ /*******************************************************************/ if(ps_dec->s_high_profile.u1_scaling_present) { pi2_scale_matrix_ptr = ps_dec->s_high_profile.i2_scalinglist4x4[0]; } else { i_scaleFactor = 16; pi2_scale_matrix_ptr = &i_scaleFactor; } /*---------------------------------------------------------------*/ /* Decode DC coefficients */ /*---------------------------------------------------------------*/ /*---------------------------------------------------------------*/ /* Calculation of N */ /*---------------------------------------------------------------*/ if(ui_is_left_mb_available) { if(ui_is_top_mb_available) { ui_N = ((ps_cur_mb_info->ps_top_mb->pu1_nnz_y[0] + ps_dec->pu1_left_nnz_y[0] + 1) >> 1); } else { ui_N = ps_dec->pu1_left_nnz_y[0]; } } else if(ui_is_top_mb_available) { ui_N = ps_cur_mb_info->ps_top_mb->pu1_nnz_y[0]; } { WORD16 pi2_dc_coef[16]; WORD32 pi4_tmp[16]; tu_sblk4x4_coeff_data_t *ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; WORD16 *pi2_coeff_block = (WORD16 *)ps_dec->pv_parse_tu_coeff_data; UWORD32 u4_num_coeff; ps_tu_4x4->u2_sig_coeff_map = 0; ret = ps_dec->pf_cavlc_parse4x4coeff[(ui_N > 7)](pi2_dc_coef, 0, ui_N, ps_dec, &u4_num_coeff); if(ret != OK) return ret; if(EXCEED_OFFSET(ps_bitstrm)) return ERROR_EOB_TERMINATE_T; if(ps_tu_4x4->u2_sig_coeff_map) { memset(pi2_dc_coef,0,sizeof(pi2_dc_coef)); ih264d_unpack_coeff4x4_dc_4x4blk(ps_tu_4x4, pi2_dc_coef, ps_dec->pu1_inv_scan); PROFILE_DISABLE_IQ_IT_RECON() ps_dec->pf_ihadamard_scaling_4x4(pi2_dc_coef, pi2_coeff_block, ps_dec->pu2_quant_scale_y, (UWORD16 *)pi2_scale_matrix_ptr, ps_dec->u1_qp_y_div6, pi4_tmp); pi2_coeff_block += 16; ps_dec->pv_parse_tu_coeff_data = (void *)pi2_coeff_block; SET_BIT(ps_cur_mb_info->u1_yuv_dc_block_flag,0); } } } } if(u4_cbp) { ret = ih264d_parse_residual4x4_cavlc(ps_dec, ps_cur_mb_info, (UWORD8)u4_offset); if(ret != OK) return ret; if(EXCEED_OFFSET(ps_bitstrm)) return ERROR_EOB_TERMINATE_T; /* Store Left Mb NNZ and TOP chroma NNZ */ } else { ps_cur_mb_info->u1_qp_div6 = ps_dec->u1_qp_y_div6; ps_cur_mb_info->u1_qpc_div6 = ps_dec->u1_qp_u_div6; ps_cur_mb_info->u1_qpcr_div6 = ps_dec->u1_qp_v_div6; ps_cur_mb_info->u1_qp_rem6 = ps_dec->u1_qp_y_rem6; ps_cur_mb_info->u1_qpc_rem6 = ps_dec->u1_qp_u_rem6; ps_cur_mb_info->u1_qpcr_rem6 = ps_dec->u1_qp_v_rem6; ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CAVLC); } return OK; } /*! ************************************************************************** * \if Function name : ParseIMbCab \endif * * \brief * This function parses CABAC syntax of a I MB. If 16x16 Luma DC transform * is also done here. Transformed Luma DC values are copied in their * 0th pixel location of corrosponding CoeffBlock. * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_imb_cabac(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_type) { WORD8 i1_delta_qp; UWORD8 u1_cbp; UWORD8 u1_offset; /* Variables for handling Cabac contexts */ ctxt_inc_mb_info_t *p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; ctxt_inc_mb_info_t *ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; bin_ctxt_model_t *p_bin_ctxt; UWORD8 u1_intra_chrom_pred_mode; UWORD8 u1_dc_block_flag = 0; WORD32 ret; ps_cur_mb_info->u1_yuv_dc_block_flag = 0; if(ps_left_ctxt == ps_dec->ps_def_ctxt_mb_info) { ps_dec->pu1_left_yuv_dc_csbp[0] = 0xf; } if(ps_dec->ps_cur_slice->u1_slice_type != I_SLICE) { WORD32 *pi4_buf; WORD8 *pi1_buf; MEMSET_16BYTES(&ps_dec->pu1_left_mv_ctxt_inc[0][0], 0); *((UWORD32 *)ps_dec->pi1_left_ref_idx_ctxt_inc) = 0; MEMSET_16BYTES(p_curr_ctxt->u1_mv, 0); memset(p_curr_ctxt->i1_ref_idx, 0, 4); } if(u1_mb_type == I_4x4_MB) { ps_cur_mb_info->ps_curmb->u1_mb_type = I_4x4_MB; p_curr_ctxt->u1_mb_type = CAB_I4x4; u1_offset = 0; ps_cur_mb_info->u1_tran_form8x8 = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; /*--------------------------------------------------------------------*/ /* Read transform_size_8x8_flag if present */ /*--------------------------------------------------------------------*/ if(ps_dec->s_high_profile.u1_transform8x8_present) { ps_cur_mb_info->u1_tran_form8x8 = ih264d_parse_transform8x8flag_cabac( ps_dec, ps_cur_mb_info); COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); p_curr_ctxt->u1_transform8x8_ctxt = ps_cur_mb_info->u1_tran_form8x8; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; } else { p_curr_ctxt->u1_transform8x8_ctxt = 0; } /*--------------------------------------------------------------------*/ /* Read the IntraPrediction modes for LUMA */ /*--------------------------------------------------------------------*/ if (!ps_cur_mb_info->u1_tran_form8x8) { UWORD8 *pu1_temp; ih264d_read_intra_pred_modes_cabac( ps_dec, ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data), ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data+16), ps_cur_mb_info->u1_tran_form8x8); pu1_temp = (UWORD8 *)ps_dec->pv_parse_tu_coeff_data; pu1_temp += 32; ps_dec->pv_parse_tu_coeff_data = (void *)pu1_temp; } else { UWORD8 *pu1_temp; ih264d_read_intra_pred_modes_cabac( ps_dec, ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data), ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data+4), ps_cur_mb_info->u1_tran_form8x8); pu1_temp = (UWORD8 *)ps_dec->pv_parse_tu_coeff_data; pu1_temp += 8; ps_dec->pv_parse_tu_coeff_data = (void *)pu1_temp; } /*--------------------------------------------------------------------*/ /* Read the IntraPrediction mode for CHROMA */ /*--------------------------------------------------------------------*/ u1_intra_chrom_pred_mode = ih264d_parse_chroma_pred_mode_cabac(ps_dec); COPYTHECONTEXT("intra_chroma_pred_mode", u1_intra_chrom_pred_mode); p_curr_ctxt->u1_intra_chroma_pred_mode = ps_cur_mb_info->u1_chroma_pred_mode = u1_intra_chrom_pred_mode; /*--------------------------------------------------------------------*/ /* Read the Coded block pattern */ /*--------------------------------------------------------------------*/ u1_cbp = ih264d_parse_ctx_cbp_cabac(ps_dec); COPYTHECONTEXT("coded_block_pattern", u1_cbp); ps_cur_mb_info->u1_cbp = u1_cbp; p_curr_ctxt->u1_cbp = u1_cbp; /*--------------------------------------------------------------------*/ /* Read mb_qp_delta */ /*--------------------------------------------------------------------*/ if(ps_cur_mb_info->u1_cbp) { ret = ih264d_parse_mb_qp_delta_cabac(ps_dec, &i1_delta_qp); if(ret != OK) return ret; COPYTHECONTEXT("mb_qp_delta", i1_delta_qp); if(i1_delta_qp != 0) { ret = ih264d_update_qp(ps_dec, i1_delta_qp); if(ret != OK) return ret; } } else ps_dec->i1_prev_mb_qp_delta = 0; p_curr_ctxt->u1_yuv_dc_csbp &= 0xFE; } else { u1_offset = 1; ps_cur_mb_info->ps_curmb->u1_mb_type = I_16x16_MB; p_curr_ctxt->u1_mb_type = CAB_I16x16; ps_cur_mb_info->u1_tran_form8x8 = 0; p_curr_ctxt->u1_transform8x8_ctxt = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; /*--------------------------------------------------------------------*/ /* Read the IntraPrediction mode for CHROMA */ /*--------------------------------------------------------------------*/ u1_intra_chrom_pred_mode = ih264d_parse_chroma_pred_mode_cabac(ps_dec); if(u1_intra_chrom_pred_mode > 3) return ERROR_CHROMA_PRED_MODE; COPYTHECONTEXT("Chroma intra_chroma_pred_mode pred mode", u1_intra_chrom_pred_mode); p_curr_ctxt->u1_intra_chroma_pred_mode = ps_cur_mb_info->u1_chroma_pred_mode = u1_intra_chrom_pred_mode; /*--------------------------------------------------------------------*/ /* Read the Coded block pattern */ /*--------------------------------------------------------------------*/ u1_cbp = gau1_ih264d_cbp_tab[(u1_mb_type - 1) >> 2]; ps_cur_mb_info->u1_cbp = u1_cbp; p_curr_ctxt->u1_cbp = u1_cbp; /*--------------------------------------------------------------------*/ /* Read mb_qp_delta */ /*--------------------------------------------------------------------*/ ret = ih264d_parse_mb_qp_delta_cabac(ps_dec, &i1_delta_qp); if(ret != OK) return ret; COPYTHECONTEXT("mb_qp_delta", i1_delta_qp); if(i1_delta_qp != 0) { ret = ih264d_update_qp(ps_dec, i1_delta_qp); if(ret != OK) return ret; } { WORD16 i_scaleFactor; WORD16* pi2_scale_matrix_ptr; /*******************************************************************/ /* for luma DC coefficients the scaling is done during the parsing */ /* to preserve the precision */ /*******************************************************************/ if(ps_dec->s_high_profile.u1_scaling_present) { pi2_scale_matrix_ptr = ps_dec->s_high_profile.i2_scalinglist4x4[0]; } else { i_scaleFactor = 16; pi2_scale_matrix_ptr = &i_scaleFactor; } { ctxt_inc_mb_info_t *ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; UWORD8 uc_a, uc_b; UWORD32 u4_ctx_inc; INC_SYM_COUNT(&(ps_dec->s_cab_dec_env)); /* if MbAddrN not available then CondTermN = 1 */ uc_b = ((ps_top_ctxt->u1_yuv_dc_csbp) & 0x01); /* if MbAddrN not available then CondTermN = 1 */ uc_a = ((ps_dec->pu1_left_yuv_dc_csbp[0]) & 0x01); u4_ctx_inc = (uc_a + (uc_b << 1)); { WORD16 pi2_dc_coef[16]; tu_sblk4x4_coeff_data_t *ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; WORD16 *pi2_coeff_block = (WORD16 *)ps_dec->pv_parse_tu_coeff_data; p_bin_ctxt = (ps_dec->p_cbf_t[LUMA_DC_CTXCAT]) + u4_ctx_inc; u1_dc_block_flag = ih264d_read_coeff4x4_cabac(ps_bitstrm, LUMA_DC_CTXCAT, ps_dec->p_significant_coeff_flag_t[LUMA_DC_CTXCAT], ps_dec, p_bin_ctxt); /* Store coded_block_flag */ p_curr_ctxt->u1_yuv_dc_csbp &= 0xFE; p_curr_ctxt->u1_yuv_dc_csbp |= u1_dc_block_flag; if(u1_dc_block_flag) { WORD32 pi4_tmp[16]; memset(pi2_dc_coef,0,sizeof(pi2_dc_coef)); ih264d_unpack_coeff4x4_dc_4x4blk(ps_tu_4x4, pi2_dc_coef, ps_dec->pu1_inv_scan); PROFILE_DISABLE_IQ_IT_RECON() ps_dec->pf_ihadamard_scaling_4x4(pi2_dc_coef, pi2_coeff_block, ps_dec->pu2_quant_scale_y, (UWORD16 *)pi2_scale_matrix_ptr, ps_dec->u1_qp_y_div6, pi4_tmp); pi2_coeff_block += 16; ps_dec->pv_parse_tu_coeff_data = (void *)pi2_coeff_block; SET_BIT(ps_cur_mb_info->u1_yuv_dc_block_flag,0); } } } } } ps_dec->pu1_left_yuv_dc_csbp[0] &= 0x6; ps_dec->pu1_left_yuv_dc_csbp[0] |= u1_dc_block_flag; ih264d_parse_residual4x4_cabac(ps_dec, ps_cur_mb_info, u1_offset); if(EXCEED_OFFSET(ps_bitstrm)) return ERROR_EOB_TERMINATE_T; return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_islice_data_cavlc */ /* */ /* Description : This function parses cabac syntax of a inter slice on */ /* N MB basis. */ /* */ /* Inputs : ps_dec */ /* sliceparams */ /* firstMbInSlice */ /* */ /* Processing : 1. After parsing syntax for N MBs those N MBs are */ /* decoded till the end of slice. */ /* */ /* Returns : 0 */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 24 06 2005 ARNY Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_islice_data_cavlc(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice) { UWORD8 uc_more_data_flag; UWORD8 u1_num_mbs, u1_mb_idx; dec_mb_info_t *ps_cur_mb_info; deblk_mb_t *ps_cur_deblk_mb; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD16 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; WORD16 i2_cur_mb_addr; UWORD8 u1_mbaff; UWORD8 u1_num_mbs_next, u1_end_of_row, u1_tfr_n_mb; WORD32 ret = OK; ps_dec->u1_qp = ps_slice->u1_slice_qp; ih264d_update_qp(ps_dec, 0); u1_mbaff = ps_slice->u1_mbaff_frame_flag; /* initializations */ u1_mb_idx = ps_dec->u1_mb_idx; u1_num_mbs = u1_mb_idx; uc_more_data_flag = 1; i2_cur_mb_addr = u2_first_mb_in_slice << u1_mbaff; do { UWORD8 u1_mb_type; ps_dec->pv_prev_mb_parse_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; if(i2_cur_mb_addr > ps_dec->ps_cur_sps->u2_max_mb_addr) { break; } ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs; ps_dec->u4_num_mbs_cur_nmb = u1_num_mbs; ps_dec->u4_num_pmbair = (u1_num_mbs >> u1_mbaff); ps_cur_mb_info->u1_end_of_slice = 0; /***************************************************************/ /* Get the required information for decoding of MB */ /* mb_x, mb_y , neighbour availablity, */ /***************************************************************/ ps_dec->pf_get_mb_info(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, 0); /***************************************************************/ /* Set the deblocking parameters for this MB */ /***************************************************************/ ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_num_mbs; if(ps_dec->u4_app_disable_deblk_frm == 0) ih264d_set_deblocking_parameters(ps_cur_deblk_mb, ps_slice, ps_dec->u1_mb_ngbr_availablity, ps_dec->u1_cur_mb_fld_dec_flag); ps_cur_deblk_mb->u1_mb_type = ps_cur_deblk_mb->u1_mb_type | D_INTRA_MB; /**************************************************************/ /* Macroblock Layer Begins, Decode the u1_mb_type */ /**************************************************************/ //Inlined ih264d_uev { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_temp; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_temp = ((1 << u4_ldz) + u4_word - 1); if(u4_temp > 25) return ERROR_MB_TYPE; u1_mb_type = u4_temp; } //Inlined ih264d_uev ps_cur_mb_info->u1_mb_type = u1_mb_type; COPYTHECONTEXT("u1_mb_type", u1_mb_type); /**************************************************************/ /* Parse Macroblock data */ /**************************************************************/ if(25 == u1_mb_type) { /* I_PCM_MB */ ps_cur_mb_info->ps_curmb->u1_mb_type = I_PCM_MB; ret = ih264d_parse_ipcm_mb(ps_dec, ps_cur_mb_info, u1_num_mbs); if(ret != OK) return ret; ps_cur_deblk_mb->u1_mb_qp = 0; } else { ret = ih264d_parse_imb_cavlc(ps_dec, ps_cur_mb_info, u1_num_mbs, u1_mb_type); if(ret != OK) return ret; ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; } uc_more_data_flag = MORE_RBSP_DATA(ps_bitstrm); if(u1_mbaff) { ih264d_update_mbaff_left_nnz(ps_dec, ps_cur_mb_info); if(!uc_more_data_flag && (0 == (i2_cur_mb_addr & 1))) { return ERROR_EOB_FLUSHBITS_T; } } /**************************************************************/ /* Get next Macroblock address */ /**************************************************************/ i2_cur_mb_addr++; /* Store the colocated information */ { mv_pred_t *ps_mv_nmb_start = ps_dec->ps_mv_cur + (u1_num_mbs << 4); mv_pred_t s_mvPred = { { 0, 0, 0, 0 }, { -1, -1 }, 0, 0}; ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb_start, 0, (UWORD8)(ps_dec->u1_cur_mb_fld_dec_flag << 1), 4, 4); } /*if num _cores is set to 3,compute bs will be done in another thread*/ if(ps_dec->u4_num_cores < 3) { if(ps_dec->u4_app_disable_deblk_frm == 0) ps_dec->pf_compute_bs(ps_dec, ps_cur_mb_info, (UWORD16)(u1_num_mbs >> u1_mbaff)); } u1_num_mbs++; /****************************************************************/ /* Check for End Of Row */ /****************************************************************/ u1_num_mbs_next = i2_pic_wdin_mbs - ps_dec->u2_mbx - 1; u1_end_of_row = (!u1_num_mbs_next) && (!(u1_mbaff && (u1_num_mbs & 0x01))); u1_tfr_n_mb = (u1_num_mbs == ps_dec->u1_recon_mb_grp) || u1_end_of_row || (!uc_more_data_flag); ps_cur_mb_info->u1_end_of_slice = (!uc_more_data_flag); /*H264_DEC_DEBUG_PRINT("Pic: %d Mb_X=%d Mb_Y=%d", ps_slice->i4_poc >> ps_slice->u1_field_pic_flag, ps_dec->u2_mbx,ps_dec->u2_mby + (1 - ps_cur_mb_info->u1_topmb)); H264_DEC_DEBUG_PRINT("u1_tfr_n_mb || (!uc_more_data_flag): %d", u1_tfr_n_mb || (!uc_more_data_flag));*/ if(u1_tfr_n_mb || (!uc_more_data_flag)) { if(ps_dec->u1_separate_parse) { ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); ps_dec->ps_nmb_info += u1_num_mbs; } else { ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); } ps_dec->u2_total_mbs_coded += u1_num_mbs; if(u1_tfr_n_mb) u1_num_mbs = 0; u1_mb_idx = u1_num_mbs; ps_dec->u1_mb_idx = u1_num_mbs; } } while(uc_more_data_flag); ps_dec->u4_num_mbs_cur_nmb = 0; ps_dec->ps_cur_slice->u4_mbs_in_slice = i2_cur_mb_addr - (u2_first_mb_in_slice << u1_mbaff); return ret; } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_islice_data_cabac */ /* */ /* Description : This function parses cabac syntax of a inter slice on */ /* N MB basis. */ /* */ /* Inputs : ps_dec */ /* sliceparams */ /* firstMbInSlice */ /* */ /* Processing : 1. After parsing syntax for N MBs those N MBs are */ /* decoded till the end of slice. */ /* */ /* Returns : 0 */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 24 06 2005 ARNY Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_islice_data_cabac(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice) { UWORD8 uc_more_data_flag; UWORD8 u1_num_mbs, u1_mb_idx; dec_mb_info_t *ps_cur_mb_info; deblk_mb_t *ps_cur_deblk_mb; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; UWORD16 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; WORD16 i2_cur_mb_addr; UWORD8 u1_mbaff; UWORD8 u1_num_mbs_next, u1_end_of_row, u1_tfr_n_mb; WORD32 ret = OK; ps_dec->u1_qp = ps_slice->u1_slice_qp; ih264d_update_qp(ps_dec, 0); u1_mbaff = ps_slice->u1_mbaff_frame_flag; if(ps_bitstrm->u4_ofst & 0x07) { ps_bitstrm->u4_ofst += 8; ps_bitstrm->u4_ofst &= 0xFFFFFFF8; } ret = ih264d_init_cabac_dec_envirnoment(&(ps_dec->s_cab_dec_env), ps_bitstrm); if(ret != OK) return ret; ih264d_init_cabac_contexts(I_SLICE, ps_dec); ps_dec->i1_prev_mb_qp_delta = 0; /* initializations */ u1_mb_idx = ps_dec->u1_mb_idx; u1_num_mbs = u1_mb_idx; uc_more_data_flag = 1; i2_cur_mb_addr = u2_first_mb_in_slice << u1_mbaff; do { UWORD16 u2_mbx; ps_dec->pv_prev_mb_parse_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; if(i2_cur_mb_addr > ps_dec->ps_cur_sps->u2_max_mb_addr) { break; } { UWORD8 u1_mb_type; ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs; ps_dec->u4_num_mbs_cur_nmb = u1_num_mbs; ps_dec->u4_num_pmbair = (u1_num_mbs >> u1_mbaff); ps_cur_mb_info->u1_end_of_slice = 0; /***************************************************************/ /* Get the required information for decoding of MB */ /* mb_x, mb_y , neighbour availablity, */ /***************************************************************/ ps_dec->pf_get_mb_info(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, 0); u2_mbx = ps_dec->u2_mbx; /*********************************************************************/ /* initialize u1_tran_form8x8 to zero to aviod uninitialized accesses */ /*********************************************************************/ ps_cur_mb_info->u1_tran_form8x8 = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; /***************************************************************/ /* Set the deblocking parameters for this MB */ /***************************************************************/ ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_num_mbs; if(ps_dec->u4_app_disable_deblk_frm == 0) ih264d_set_deblocking_parameters( ps_cur_deblk_mb, ps_slice, ps_dec->u1_mb_ngbr_availablity, ps_dec->u1_cur_mb_fld_dec_flag); ps_cur_deblk_mb->u1_mb_type = ps_cur_deblk_mb->u1_mb_type | D_INTRA_MB; /* Macroblock Layer Begins */ /* Decode the u1_mb_type */ u1_mb_type = ih264d_parse_mb_type_intra_cabac(0, ps_dec); if(u1_mb_type > 25) return ERROR_MB_TYPE; ps_cur_mb_info->u1_mb_type = u1_mb_type; COPYTHECONTEXT("u1_mb_type", u1_mb_type); /* Parse Macroblock Data */ if(25 == u1_mb_type) { /* I_PCM_MB */ ps_cur_mb_info->ps_curmb->u1_mb_type = I_PCM_MB; ret = ih264d_parse_ipcm_mb(ps_dec, ps_cur_mb_info, u1_num_mbs); if(ret != OK) return ret; ps_cur_deblk_mb->u1_mb_qp = 0; } else { ret = ih264d_parse_imb_cabac(ps_dec, ps_cur_mb_info, u1_mb_type); if(ret != OK) return ret; ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; } if(u1_mbaff) { ih264d_update_mbaff_left_nnz(ps_dec, ps_cur_mb_info); } if(ps_cur_mb_info->u1_topmb && u1_mbaff) uc_more_data_flag = 1; else { uc_more_data_flag = ih264d_decode_terminate(&ps_dec->s_cab_dec_env, ps_bitstrm); uc_more_data_flag = !uc_more_data_flag; COPYTHECONTEXT("Decode Sliceterm",!uc_more_data_flag); } if(u1_mbaff) { if(!uc_more_data_flag && (0 == (i2_cur_mb_addr & 1))) { return ERROR_EOB_FLUSHBITS_T; } } /* Next macroblock information */ i2_cur_mb_addr++; /* Store the colocated information */ { mv_pred_t *ps_mv_nmb_start = ps_dec->ps_mv_cur + (u1_num_mbs << 4); mv_pred_t s_mvPred = { { 0, 0, 0, 0 }, { -1, -1 }, 0, 0}; ih264d_rep_mv_colz( ps_dec, &s_mvPred, ps_mv_nmb_start, 0, (UWORD8)(ps_dec->u1_cur_mb_fld_dec_flag << 1), 4, 4); } /*if num _cores is set to 3,compute bs will be done in another thread*/ if(ps_dec->u4_num_cores < 3) { if(ps_dec->u4_app_disable_deblk_frm == 0) ps_dec->pf_compute_bs(ps_dec, ps_cur_mb_info, (UWORD16)(u1_num_mbs >> u1_mbaff)); } u1_num_mbs++; } /****************************************************************/ /* Check for End Of Row */ /****************************************************************/ u1_num_mbs_next = i2_pic_wdin_mbs - u2_mbx - 1; u1_end_of_row = (!u1_num_mbs_next) && (!(u1_mbaff && (u1_num_mbs & 0x01))); u1_tfr_n_mb = (u1_num_mbs == ps_dec->u1_recon_mb_grp) || u1_end_of_row || (!uc_more_data_flag); ps_cur_mb_info->u1_end_of_slice = (!uc_more_data_flag); if(u1_tfr_n_mb || (!uc_more_data_flag)) { if(ps_dec->u1_separate_parse) { ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); ps_dec->ps_nmb_info += u1_num_mbs; } else { ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); } ps_dec->u2_total_mbs_coded += u1_num_mbs; if(u1_tfr_n_mb) u1_num_mbs = 0; u1_mb_idx = u1_num_mbs; ps_dec->u1_mb_idx = u1_num_mbs; } } while(uc_more_data_flag); ps_dec->u4_num_mbs_cur_nmb = 0; ps_dec->ps_cur_slice->u4_mbs_in_slice = i2_cur_mb_addr - (u2_first_mb_in_slice << u1_mbaff); return ret; } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_ipcm_mb */ /* */ /* Description : This function decodes the pixel values of I_PCM Mb. */ /* */ /* Inputs : ps_dec, ps_cur_mb_info and mb number */ /* */ /* Description : This function reads the luma and chroma pixels directly */ /* from the bitstream when the mbtype is I_PCM and stores */ /* them in recon buffer. If the entropy coding mode is */ /* cabac, decoding engine is re-initialized. The nnzs and */ /* cabac contexts are appropriately modified. */ /* Returns : void */ /* */ /* Revision History: */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 07 2002 Jay */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_ipcm_mb(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD8 u1_mbNum) { dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; UWORD8 *pu1_y, *pu1_u, *pu1_v; WORD32 ret; UWORD32 u4_rec_width_y, u4_rec_width_uv; UWORD32 u1_num_mb_pair; UWORD8 u1_x, u1_y; /* CHANGED CODE */ tfr_ctxt_t *ps_frame_buf; UWORD8 u1_mb_field_decoding_flag; UWORD32 *pu4_buf; UWORD8 *pu1_buf; /* CHANGED CODE */ if(ps_dec->u1_separate_parse) { ps_frame_buf = &ps_dec->s_tran_addrecon_parse; } else { ps_frame_buf = &ps_dec->s_tran_addrecon; } /* align bistream to byte boundary. */ /* pcm_alignment_zero_bit discarded */ /* For XX GotoByteBoundary */ if(ps_bitstrm->u4_ofst & 0x07) { ps_bitstrm->u4_ofst += 8; ps_bitstrm->u4_ofst &= 0xFFFFFFF8; } /* Store left Nnz as 16 for each 4x4 blk */ pu1_buf = ps_dec->pu1_left_nnz_y; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0x10101010; pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_y; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0x10101010; pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0x10101010; pu1_buf = ps_dec->pu1_left_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0x10101010; ps_cur_mb_info->u1_cbp = 0xff; ps_dec->i1_prev_mb_qp_delta = 0; /* Get neighbour MB's */ u1_num_mb_pair = (u1_mbNum >> u1_mbaff); /*****************************************************************************/ /* calculate the RECON buffer YUV pointers for the PCM data */ /*****************************************************************************/ /* CHANGED CODE */ u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; pu1_y = ps_frame_buf->pu1_dest_y + (u1_num_mb_pair << 4); pu1_u = ps_frame_buf->pu1_dest_u + (u1_num_mb_pair << 4); pu1_v = pu1_u + 1; u4_rec_width_y = ps_dec->u2_frm_wd_y << u1_mb_field_decoding_flag; u4_rec_width_uv = ps_dec->u2_frm_wd_uv << u1_mb_field_decoding_flag; /* CHANGED CODE */ if(u1_mbaff) { UWORD8 u1_top_mb; u1_top_mb = ps_cur_mb_info->u1_topmb; if(u1_top_mb == 0) { pu1_y += (u1_mb_field_decoding_flag ? (u4_rec_width_y >> 1) : (u4_rec_width_y << 4)); pu1_u += (u1_mb_field_decoding_flag ? (u4_rec_width_uv) : (u4_rec_width_uv << 4)); pu1_v = pu1_u + 1; } } /* Read Luma samples */ for(u1_y = 0; u1_y < 16; u1_y++) { for(u1_x = 0; u1_x < 16; u1_x++) pu1_y[u1_x] = ih264d_get_bits_h264(ps_bitstrm, 8); pu1_y += u4_rec_width_y; } /* Read Chroma samples */ for(u1_y = 0; u1_y < 8; u1_y++) { for(u1_x = 0; u1_x < 8; u1_x++) pu1_u[u1_x * YUV420SP_FACTOR] = ih264d_get_bits_h264(ps_bitstrm, 8); pu1_u += u4_rec_width_uv; } for(u1_y = 0; u1_y < 8; u1_y++) { for(u1_x = 0; u1_x < 8; u1_x++) pu1_v[u1_x * YUV420SP_FACTOR] = ih264d_get_bits_h264(ps_bitstrm, 8); pu1_v += u4_rec_width_uv; } if(CABAC == ps_dec->ps_cur_pps->u1_entropy_coding_mode) { UWORD32 *pu4_buf; UWORD8 *pu1_buf; ctxt_inc_mb_info_t *p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; /* Re-initialize the cabac decoding engine. */ ret = ih264d_init_cabac_dec_envirnoment(&(ps_dec->s_cab_dec_env), ps_bitstrm); if(ret != OK) return ret; /* update the cabac contetxs */ p_curr_ctxt->u1_mb_type = CAB_I_PCM; p_curr_ctxt->u1_cbp = 47; p_curr_ctxt->u1_intra_chroma_pred_mode = 0; p_curr_ctxt->u1_transform8x8_ctxt = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; pu1_buf = ps_dec->pu1_left_nnz_y; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0x01010101; pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_y; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0x01010101; pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0x01010101; pu1_buf = ps_dec->pu1_left_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0x01010101; p_curr_ctxt->u1_yuv_dc_csbp = 0x7; ps_dec->pu1_left_yuv_dc_csbp[0] = 0x7; if(ps_dec->ps_cur_slice->u1_slice_type != I_SLICE) { MEMSET_16BYTES(&ps_dec->pu1_left_mv_ctxt_inc[0][0], 0); memset(ps_dec->pi1_left_ref_idx_ctxt_inc, 0, 4); MEMSET_16BYTES(p_curr_ctxt->u1_mv, 0); memset(p_curr_ctxt->i1_ref_idx, 0, 4); } } return OK; } /*! ************************************************************************** * \if Function name : ih264d_decode_islice \endif * * \brief * Decodes an I Slice * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_islice(dec_struct_t *ps_dec, UWORD16 u2_first_mb_in_slice) { dec_pic_params_t * ps_pps = ps_dec->ps_cur_pps; dec_slice_params_t * ps_slice = ps_dec->ps_cur_slice; UWORD32 *pu4_bitstrm_buf = ps_dec->ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_dec->ps_bitstrm->u4_ofst; UWORD32 u4_temp; WORD32 i_temp; WORD32 ret; /*--------------------------------------------------------------------*/ /* Read remaining contents of the slice header */ /*--------------------------------------------------------------------*/ /* dec_ref_pic_marking function */ /* G050 */ if(ps_slice->u1_nal_ref_idc != 0) { if(!ps_dec->ps_dpb_cmds->u1_dpb_commands_read) { i_temp = ih264d_read_mmco_commands(ps_dec); if (i_temp < 0) { return ERROR_DBP_MANAGER_T; } ps_dec->u4_bitoffset = i_temp; } else ps_dec->ps_bitstrm->u4_ofst += ps_dec->u4_bitoffset; } /* G050 */ /* Read slice_qp_delta */ WORD64 i8_temp = (WORD64)ps_pps->u1_pic_init_qp + ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i8_temp < MIN_H264_QP) || (i8_temp > MAX_H264_QP)) return ERROR_INV_RANGE_QP_T; ps_slice->u1_slice_qp = i8_temp; COPYTHECONTEXT("SH: slice_qp_delta", ps_slice->u1_slice_qp - ps_pps->u1_pic_init_qp); if(ps_pps->u1_deblocking_filter_parameters_present_flag == 1) { u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SH: disable_deblocking_filter_idc", u4_temp); if(u4_temp > SLICE_BOUNDARY_DBLK_DISABLED) { return ERROR_INV_SLICE_HDR_T; } ps_slice->u1_disable_dblk_filter_idc = u4_temp; if(u4_temp != 1) { i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) << 1; if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) { return ERROR_INV_SLICE_HDR_T; } ps_slice->i1_slice_alpha_c0_offset = i_temp; COPYTHECONTEXT("SH: slice_alpha_c0_offset_div2", ps_slice->i1_slice_alpha_c0_offset >> 1); i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) << 1; if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) { return ERROR_INV_SLICE_HDR_T; } ps_slice->i1_slice_beta_offset = i_temp; COPYTHECONTEXT("SH: slice_beta_offset_div2", ps_slice->i1_slice_beta_offset >> 1); } else { ps_slice->i1_slice_alpha_c0_offset = 0; ps_slice->i1_slice_beta_offset = 0; } } else { ps_slice->u1_disable_dblk_filter_idc = 0; ps_slice->i1_slice_alpha_c0_offset = 0; ps_slice->i1_slice_beta_offset = 0; } /* Initialization to check if number of motion vector per 2 Mbs */ /* are exceeding the range or not */ ps_dec->u2_mv_2mb[0] = 0; ps_dec->u2_mv_2mb[1] = 0; /*set slice header cone to 2 ,to indicate correct header*/ ps_dec->u1_slice_header_done = 2; if(ps_pps->u1_entropy_coding_mode) { SWITCHOFFTRACE; SWITCHONTRACECABAC; if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) { ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_mbaff; } else ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_nonmbaff; ret = ih264d_parse_islice_data_cabac(ps_dec, ps_slice, u2_first_mb_in_slice); if(ret != OK) return ret; SWITCHONTRACE; SWITCHOFFTRACECABAC; } else { if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) { ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_mbaff; } else ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_nonmbaff; ret = ih264d_parse_islice_data_cavlc(ps_dec, ps_slice, u2_first_mb_in_slice); if(ret != OK) return ret; } return OK; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_islice.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_parse_islice.h * * \brief * Contains routines that decode a I slice type * * Detailed_description * * \date * 07/07/2003 * * \author NS ************************************************************************** */ #ifndef _IH264D_PARSE_ISLICE_H_ #define _IH264D_PARSE_ISLICE_H_ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_tables.h" WORD32 ih264d_parse_residual4x4_cavlc(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD8 u1_offset); WORD32 ih264d_parse_residual4x4_cabac(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD8 u1_offset); WORD32 ih264d_parse_imb_cavlc(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_mb_type); WORD32 ih264d_parse_imb_cabac(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_type); WORD32 ih264d_parse_islice_data_cavlc(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice); WORD32 ih264d_parse_islice_data_cabac(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice); WORD32 ih264d_parse_pmb_cavlc(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2); WORD32 ih264d_parse_pmb_cabac(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2); WORD32 ih264d_parse_bmb_non_direct_cavlc(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_mbNumModNBy2); WORD32 ih264d_parse_bmb_non_direct_cabac(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_mbNumModNBy2); WORD32 ih264d_parse_bmb_cavlc(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2); WORD32 ih264d_parse_bmb_cabac(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2); WORD32 ih264d_parse_inter_slice_data_cavlc(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice); WORD32 ih264d_parse_inter_slice_data_cabac(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice); WORD32 ParseBMb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2); WORD32 ih264d_parse_ipcm_mb(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD8 u1_mbNum); WORD32 ih264d_parse_islice(dec_struct_t *ps_dec, UWORD16 u2_first_mb_in_slice); #endif /* _IH264D_PARSE_ISLICE_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_mb_header.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! *************************************************************************** * \file ih264d_parse_mb_header.c * * \brief * This file contains context identifier encoding routines. * * \date * 04/02/2003 * * \author NS *************************************************************************** */ #include <string.h> #include "ih264d_structs.h" #include "ih264d_bitstrm.h" #include "ih264d_cabac.h" #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_defs.h" #include "ih264d_error_handler.h" #include "ih264d_tables.h" #include "ih264d_debug.h" #include "ih264d_defs.h" #include "ih264d_defs.h" #include "ih264d_mb_utils.h" #include "ih264d_parse_mb_header.h" #include "ih264d_defs.h" /*! < CtxtInc index 0 - CtxMbTypeI, CtxMbTypeSISuffix index 1 - CtxMbTypePSuffix, CtxMbTypeBSuffix */ /*! ************************************************************************** * \if Function name : ih264d_parse_mb_type_intra_cabac \endif * * \brief * This function decodes MB type using CABAC entropy coding mode. * * \return * MBType. * ************************************************************************** */ UWORD8 ih264d_parse_mb_type_intra_cabac(UWORD8 u1_inter, struct _DecStruct * ps_dec) { decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; ctxt_inc_mb_info_t * ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; ctxt_inc_mb_info_t * ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; bin_ctxt_model_t *ps_mb_bin_ctxt = ps_dec->p_mb_type_t; WORD8 u1_mb_type, u1_bin; UWORD32 u4_cxt_inc; u4_cxt_inc = 0; if(!u1_inter) { if(ps_left_ctxt != ps_dec->ps_def_ctxt_mb_info) u4_cxt_inc += ((ps_left_ctxt->u1_mb_type != CAB_I4x4) ? 1 : 0); if(ps_top_ctxt != ps_dec->ps_def_ctxt_mb_info) u4_cxt_inc += ((ps_top_ctxt->u1_mb_type != CAB_I4x4) ? 1 : 0); } else { ps_mb_bin_ctxt = ps_mb_bin_ctxt + 3 + (ps_dec->u1_B << 1); } /* b0 */ u1_mb_type = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); if(u1_mb_type) { /* I16x16 or I_PCM mode */ /* b1 */ u1_bin = ih264d_decode_terminate(ps_cab_env, ps_bitstrm); if(u1_bin == 0) { /* I16x16 mode */ /* Read b2 and b3 */ u4_cxt_inc = (u1_inter) ? 0x021 : 0x043; u1_bin = ih264d_decode_bins(2, u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); if(u1_bin & 0x01) u1_mb_type += 4; if(u1_bin & 0x02) u1_mb_type += 12; if(u1_bin & 0x01) { /* since b3=1, Read three bins */ u4_cxt_inc = (u1_inter) ? 0x0332 : 0x0765; u1_bin = (UWORD8)ih264d_decode_bins(3, u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); } else { /* Read two bins */ u4_cxt_inc = (u1_inter) ? 0x033 : 0x076; u1_bin = (UWORD8)ih264d_decode_bins(2, u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); } u1_mb_type += u1_bin; } else { /* I_PCM mode */ /* b1=1 */ u1_mb_type = 25; } } return (u1_mb_type); } /*! ************************************************************************** * \if Function name : ih264d_parse_mb_type_cabac \endif * * \brief * This function decodes MB type using CABAC entropy coding mode. * * \return * MBType. * ************************************************************************** */ UWORD32 ih264d_parse_mb_type_cabac(struct _DecStruct * ps_dec) { const UWORD8 uc_slice_type = ps_dec->ps_cur_slice->u1_slice_type; decoding_envirnoment_t *ps_cab_env = &ps_dec->s_cab_dec_env; dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm; ctxt_inc_mb_info_t *ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; ctxt_inc_mb_info_t *ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; WORD8 c_ctxt_inc; bin_ctxt_model_t *ps_mb_bin_ctxt = ps_dec->p_mb_type_t; WORD8 u1_mb_type = 0, u1_bin; UWORD32 u4_cxt_inc; INC_SYM_COUNT(ps_cab_env); c_ctxt_inc = 0; if(uc_slice_type == SI_SLICE) { /* b0 */ if(ps_left_ctxt != ps_dec->ps_def_ctxt_mb_info) c_ctxt_inc += ((ps_left_ctxt->u1_mb_type != CAB_SI4x4) ? 1 : 0); if(ps_top_ctxt != ps_dec->ps_def_ctxt_mb_info) c_ctxt_inc += ((ps_top_ctxt->u1_mb_type != CAB_SI4x4) ? 1 : 0); u4_cxt_inc = c_ctxt_inc; u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); if(u1_bin == 0) { /* SI MB */ u1_mb_type = 0; } else { u1_mb_type = 1 + ih264d_parse_mb_type_intra_cabac(0, ps_dec); } } else if(uc_slice_type == P_SLICE) { /* P Slice */ /* b0 */ u4_cxt_inc = 0; u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); if(!u1_bin) { /* Inter MB types */ /* b1 */ u4_cxt_inc = 0x01; u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); /* b2 */ u4_cxt_inc = u1_bin + 2; u1_mb_type = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); u1_mb_type = (u1_bin << 1) + u1_mb_type; if(u1_mb_type) u1_mb_type = 4 - u1_mb_type; } else { /* Intra Prefix 1 found */ /* Intra MB type */ u1_mb_type = 5 + ih264d_parse_mb_type_intra_cabac(1, ps_dec); } } else if(uc_slice_type == B_SLICE) { WORD8 a, b; /* B Slice */ /* b0 */ /* a = b = 0, if B slice and MB is a SKIP or B_DIRECT16x16 */ a = 0; b = 0; u1_mb_type = 0; if(ps_left_ctxt != ps_dec->ps_def_ctxt_mb_info) a = ((ps_left_ctxt->u1_mb_type & CAB_BD16x16_MASK) != CAB_BD16x16); if(ps_top_ctxt != ps_dec->ps_def_ctxt_mb_info) b = ((ps_top_ctxt->u1_mb_type & CAB_BD16x16_MASK) != CAB_BD16x16); u4_cxt_inc = a + b; u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); if(u1_bin) { /* b1 */ u4_cxt_inc = 0x03; u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); if(!u1_bin) { /* b2 */ u4_cxt_inc = 0x05; u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); u1_mb_type = u1_bin + 1; } else { u1_mb_type = 3; /* b2 */ u4_cxt_inc = 0x04; u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); if(u1_bin) { u1_mb_type += 8; /* b3 */ u4_cxt_inc = 0x05; u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); if(!u1_bin) { u1_mb_type++; /* b4, b5, b6 */ u4_cxt_inc = 0x0555; u1_bin = (UWORD8)ih264d_decode_bins(3, u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); u1_mb_type += u1_bin; } else { /* b4 */ u4_cxt_inc = 0x05; u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); if(u1_bin) { /* b5 */ u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); u1_mb_type += (u1_bin ? 11 : 0); } else { u1_mb_type = 20; /* b5 */ u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); if(!u1_bin) { /* b6 */ u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); u1_mb_type += u1_bin; } else { /* Intra Prefix 111101 found */ /* Intra MB type */ u1_mb_type = 23 + ih264d_parse_mb_type_intra_cabac( 1, ps_dec); } } } } else { /* b3, b4, b5 */ u4_cxt_inc = 0x0555; u1_bin = (UWORD8)ih264d_decode_bins(3, u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, ps_cab_env); u1_mb_type += u1_bin; } } } } return ((UWORD32)u1_mb_type); } /*! ************************************************************************** * \if Function name : DecSubMBType \endif * * \brief * This function decodes MB type using CABAC entropy coding mode. * * \return * MBType. * ************************************************************************** */ UWORD32 ih264d_parse_submb_type_cabac(const UWORD8 u1_slc_type_b, decoding_envirnoment_t * ps_cab_env, dec_bit_stream_t * ps_bitstrm, bin_ctxt_model_t * ps_sub_mb_cxt) { WORD8 u1_sub_mb_type, u1_bin; INC_SYM_COUNT(ps_cab_env); u1_sub_mb_type = 0; u1_bin = (UWORD8)ih264d_decode_bin(0, ps_sub_mb_cxt, ps_bitstrm, ps_cab_env); if(u1_slc_type_b ^ u1_bin) return 0; if(!u1_slc_type_b) { /* P Slice */ u1_sub_mb_type = 1; u1_bin = (UWORD8)ih264d_decode_bin(1, ps_sub_mb_cxt, ps_bitstrm, ps_cab_env); if(u1_bin == 1) { u1_bin = (UWORD8)ih264d_decode_bin(2, ps_sub_mb_cxt, ps_bitstrm, ps_cab_env); u1_sub_mb_type = (2 + (!u1_bin)); } return u1_sub_mb_type; } else { /* B Slice */ /* b1 */ u1_bin = (UWORD8)ih264d_decode_bin(1, ps_sub_mb_cxt, ps_bitstrm, ps_cab_env); if(u1_bin) { /* b2 */ u1_bin = (UWORD8)ih264d_decode_bin(2, ps_sub_mb_cxt, ps_bitstrm, ps_cab_env); if(u1_bin) { /* b3 */ u1_sub_mb_type = 7; u1_bin = (UWORD8)ih264d_decode_bin(3, ps_sub_mb_cxt, ps_bitstrm, ps_cab_env); u1_sub_mb_type += u1_bin << 2; u1_bin = !u1_bin; /* b4 */ if(u1_bin == 0) { u1_bin = ih264d_decode_bin(3, ps_sub_mb_cxt, ps_bitstrm, ps_cab_env); } else { u1_bin = (UWORD8)ih264d_decode_bins(2, 0x33, ps_sub_mb_cxt, ps_bitstrm, ps_cab_env); } return (u1_sub_mb_type + u1_bin); } else { /* b3 */ u1_bin = (UWORD8)ih264d_decode_bins(2, 0x33, ps_sub_mb_cxt, ps_bitstrm, ps_cab_env); return (3 + u1_bin); } } else { /* b2 */ u1_bin = (UWORD8)ih264d_decode_bin(3, ps_sub_mb_cxt, ps_bitstrm, ps_cab_env); return (1 + u1_bin); } } } /*! ************************************************************************** * \if Function name : ih264d_parse_ref_idx_cabac \endif * * \brief * This function decodes Reference Index using CABAC entropy coding mode. * * \return * None * ************************************************************************** */ WORD32 ih264d_parse_ref_idx_cabac(const UWORD8 u1_num_part, const UWORD8 u1_b2, const UWORD8 u1_max_ref_minus1, const UWORD8 u1_mb_mode, WORD8 * pi1_ref_idx, WORD8 * const pi1_lft_cxt, WORD8 * const pi1_top_cxt, decoding_envirnoment_t * const ps_cab_env, dec_bit_stream_t * const ps_bitstrm, bin_ctxt_model_t * const ps_ref_cxt) { UWORD8 u1_a, u1_b; UWORD32 u4_cxt_inc; UWORD8 u1_blk_no, u1_i, u1_idx_lft, u1_idx_top; WORD8 i1_ref_idx; for(u1_blk_no = 0, u1_i = 0; u1_i < u1_num_part; u1_i++, pi1_ref_idx++) { u1_idx_lft = ((u1_blk_no & 0x02) >> 1) + u1_b2; u1_idx_top = (u1_blk_no & 0x01) + u1_b2; i1_ref_idx = *pi1_ref_idx; if(i1_ref_idx > 0) { u1_a = pi1_lft_cxt[u1_idx_lft] > 0; u1_b = pi1_top_cxt[u1_idx_top] > 0; u4_cxt_inc = u1_a + (u1_b << 1); u4_cxt_inc = (u4_cxt_inc | 0x55540); i1_ref_idx = (WORD8)ih264d_decode_bins_unary(32, u4_cxt_inc, ps_ref_cxt, ps_bitstrm, ps_cab_env); if((i1_ref_idx > u1_max_ref_minus1) || (i1_ref_idx < 0)) { return ERROR_REF_IDX; } *pi1_ref_idx = i1_ref_idx; INC_SYM_COUNT(ps_cab_env); } /* Storing Reference Idx Information */ pi1_lft_cxt[u1_idx_lft] = i1_ref_idx; pi1_top_cxt[u1_idx_top] = i1_ref_idx; u1_blk_no = u1_blk_no + 1 + (u1_mb_mode & 0x01); } /* if(!u1_sub_mb) */ if(u1_num_part != 4) { pi1_lft_cxt[(!(u1_mb_mode & 0x1)) + u1_b2] = pi1_lft_cxt[u1_b2]; pi1_top_cxt[(!(u1_mb_mode & 0x2)) + u1_b2] = pi1_top_cxt[u1_b2]; } return OK; } /*! ************************************************************************** * \if Function name : ih264d_parse_mb_qp_delta_cabac \endif * * \brief * This function decodes MB Qp delta using CABAC entropy coding mode. * * \return * None * ************************************************************************** */ WORD32 ih264d_parse_mb_qp_delta_cabac(struct _DecStruct * ps_dec, WORD8 *pi1_mb_qp_delta) { decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; UWORD8 u1_code_num; bin_ctxt_model_t *ps_mb_qp_delta_ctxt = ps_dec->p_mb_qp_delta_t; UWORD32 u4_cxt_inc; INC_SYM_COUNT(ps_cab_env); u4_cxt_inc = (!(!(ps_dec->i1_prev_mb_qp_delta))); u1_code_num = 0; u4_cxt_inc = (u4_cxt_inc | 0x33320); /* max number of bins = 53, since Range for MbQpDelta= -26 to +25 inclusive, UNARY code */ u1_code_num = ih264d_decode_bins_unary(32, u4_cxt_inc, ps_mb_qp_delta_ctxt, ps_bitstrm, ps_cab_env); if(u1_code_num == 32) { /* Read remaining 21 bins */ UWORD8 uc_codeNumX; u4_cxt_inc = 0x33333; uc_codeNumX = ih264d_decode_bins_unary(21, u4_cxt_inc, ps_mb_qp_delta_ctxt, ps_bitstrm, ps_cab_env); u1_code_num = u1_code_num + uc_codeNumX; } *pi1_mb_qp_delta = (u1_code_num + 1) >> 1; /* Table 9.3: If code_num is even Syntax Element has -ve value */ if(!(u1_code_num & 0x01)) *pi1_mb_qp_delta = -(*pi1_mb_qp_delta); /* Range of MbQpDelta= -26 to +25 inclusive */ if((*pi1_mb_qp_delta < -26) || (*pi1_mb_qp_delta > 25)) return ERROR_INV_RANGE_QP_T; ps_dec->i1_prev_mb_qp_delta = *pi1_mb_qp_delta; return OK; } /*! ************************************************************************** * \if Function name : ih264d_parse_chroma_pred_mode_cabac \endif * * \brief * This function decodes Chroma Pred mode using CABAC entropy coding mode. * * \return * None * ************************************************************************** */ WORD8 ih264d_parse_chroma_pred_mode_cabac(struct _DecStruct * ps_dec) { decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; ctxt_inc_mb_info_t * ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; ctxt_inc_mb_info_t * ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; WORD8 i1_chroma_pred_mode, a, b; UWORD32 u4_cxt_inc; INC_SYM_COUNT(ps_cab_env); /* Binarization is TU and Cmax=3 */ i1_chroma_pred_mode = 0; a = 0; b = 0; a = ((ps_left_ctxt->u1_intra_chroma_pred_mode != 0) ? 1 : 0); b = ((ps_top_ctxt->u1_intra_chroma_pred_mode != 0) ? 1 : 0); u4_cxt_inc = a + b; u4_cxt_inc = (u4_cxt_inc | 0x330); i1_chroma_pred_mode = ih264d_decode_bins_tunary( 3, u4_cxt_inc, ps_dec->p_intra_chroma_pred_mode_t, ps_bitstrm, ps_cab_env); return (i1_chroma_pred_mode); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_transform8x8flag_cabac */ /* */ /* Description : */ /* Inputs : */ /* */ /* */ /* Returns : */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* Rajasekhar Creation */ /* */ /*****************************************************************************/ UWORD8 ih264d_parse_transform8x8flag_cabac(struct _DecStruct * ps_dec, dec_mb_info_t * ps_cur_mb_info) { decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; ctxt_inc_mb_info_t * ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; ctxt_inc_mb_info_t * ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; UWORD8 u1_transform_8x8flag; UWORD8 u1_mb_ngbr_avail = ps_cur_mb_info->u1_mb_ngbr_availablity; WORD8 a, b; UWORD32 u4_cxt_inc; /* for calculating the context increment for transform8x8 u4_flag */ /* it reads transform8x8 u4_flag of the neighbors through */ /* Binarization is FLC */ a = 0; b = 0; if(u1_mb_ngbr_avail & LEFT_MB_AVAILABLE_MASK) { a = ps_left_ctxt->u1_transform8x8_ctxt; } if(u1_mb_ngbr_avail & TOP_MB_AVAILABLE_MASK) { b = ps_top_ctxt->u1_transform8x8_ctxt; } u4_cxt_inc = a + b; u1_transform_8x8flag = ih264d_decode_bin( u4_cxt_inc, ps_dec->s_high_profile.ps_transform8x8_flag, ps_bitstrm, ps_cab_env); return (u1_transform_8x8flag); } /*! ************************************************************************** * \if Function name : ih264d_read_intra_pred_modes_cabac \endif * * \brief * Reads the intra pred mode related values of I4x4 MB from bitstream. * * This function will read the prev intra pred mode flags and * stores it in pu1_prev_intra4x4_pred_mode_flag. If the u4_flag * indicates that most probable mode is not intra pred mode, then * the rem_intra4x4_pred_mode is read and stored in * pu1_rem_intra4x4_pred_mode array. * * * \return * 0 on success and Error code otherwise * ************************************************************************** */ WORD32 ih264d_read_intra_pred_modes_cabac(dec_struct_t * ps_dec, UWORD8 * pu1_prev_intra4x4_pred_mode_flag, UWORD8 * pu1_rem_intra4x4_pred_mode, UWORD8 u1_tran_form8x8) { WORD32 i4x4_luma_blk_idx = 0; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; bin_ctxt_model_t *ps_ctxt_ipred_luma_mpm, *ps_ctx_ipred_luma_rm; WORD32 i4_rem_intra4x4_pred_mode; UWORD32 u4_prev_intra4x4_pred_mode_flag; UWORD32 u4_code_int_range, u4_code_int_val_ofst; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; ps_ctxt_ipred_luma_mpm = ps_dec->p_prev_intra4x4_pred_mode_flag_t; ps_ctx_ipred_luma_rm = ps_dec->p_rem_intra4x4_pred_mode_t; SWITCHOFFTRACE; i4x4_luma_blk_idx = (0 == u1_tran_form8x8) ? 16 : 4; u4_code_int_range = ps_cab_env->u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; do { DECODE_ONE_BIN_MACRO(ps_ctxt_ipred_luma_mpm, u4_code_int_range, u4_code_int_val_ofst, pu4_table, ps_bitstrm, u4_prev_intra4x4_pred_mode_flag) *pu1_prev_intra4x4_pred_mode_flag = u4_prev_intra4x4_pred_mode_flag; i4_rem_intra4x4_pred_mode = -1; if(!u4_prev_intra4x4_pred_mode_flag) { /*inlining DecodeDecisionBins_FLC*/ { UWORD8 u1_max_bins = 3; UWORD32 u4_value; UWORD32 u4_symbol, i; i = 0; u4_value = 0; do { DECODE_ONE_BIN_MACRO(ps_ctx_ipred_luma_rm, u4_code_int_range, u4_code_int_val_ofst, pu4_table, ps_bitstrm, u4_symbol) INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); u4_value = u4_value | (u4_symbol << i); i++; } while(i < u1_max_bins); i4_rem_intra4x4_pred_mode = (u4_value); } } (*pu1_rem_intra4x4_pred_mode) = i4_rem_intra4x4_pred_mode; COPYTHECONTEXT("intra4x4_pred_mode", i4_rem_intra4x4_pred_mode); pu1_prev_intra4x4_pred_mode_flag++; pu1_rem_intra4x4_pred_mode++; i4x4_luma_blk_idx--; } while(i4x4_luma_blk_idx); ps_cab_env->u4_code_int_range = u4_code_int_range; ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; return (0); } /*! ************************************************************************** * \if Function name : ih264d_parse_ctx_cbp_cabac \endif * * \brief * This function decodes CtxCbpLuma and CtxCbpChroma (CBP of a Macroblock). * using CABAC entropy coding mode. * * \return * CBP of a MB. * ************************************************************************** */ UWORD32 ih264d_parse_ctx_cbp_cabac(struct _DecStruct * ps_dec) { UWORD32 u4_cxt_inc; decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; ctxt_inc_mb_info_t * ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; ctxt_inc_mb_info_t * ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; bin_ctxt_model_t *ps_ctxt_cbp_luma = ps_dec->p_cbp_luma_t, *ps_bin_ctxt; WORD8 c_Cbp; //,i,j; UWORD32 u4_code_int_range, u4_code_int_val_ofst; UWORD32 u4_offset, *pu4_buffer; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; INC_SYM_COUNT(ps_cab_env); /* CBP Luma, FL, Cmax = 15, L = 4 */ u4_cxt_inc = (!((ps_top_ctxt->u1_cbp >> 2) & 0x01)) << 1; u4_cxt_inc += !((ps_left_ctxt->u1_cbp >> 1) & 0x01); u4_offset = ps_bitstrm->u4_ofst; pu4_buffer = ps_bitstrm->pu4_buffer; u4_code_int_range = ps_cab_env->u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; /*renormalize to ensure there 23 bits more in the u4_code_int_val_ofst*/ { UWORD32 u4_clz, read_bits; u4_clz = CLZ(u4_code_int_range); FLUSHBITS(u4_offset, u4_clz) NEXTBITS(read_bits, u4_offset, pu4_buffer, 23) u4_code_int_range = u4_code_int_range << u4_clz; u4_code_int_val_ofst = (u4_code_int_val_ofst << u4_clz) | read_bits; } ps_bin_ctxt = ps_ctxt_cbp_luma + u4_cxt_inc; /*inlining DecodeDecision_onebin without renorm*/ { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u4_symbol, u1_mps_state; UWORD32 table_lookup; UWORD32 u4_clz; u1_mps_state = (ps_bin_ctxt->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); /*if mps*/ u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) INC_BIN_COUNT(ps_cab_env); ps_bin_ctxt->u1_mps_state = u1_mps_state; c_Cbp = u4_symbol; } u4_cxt_inc = (!((ps_top_ctxt->u1_cbp >> 3) & 0x01)) << 1; u4_cxt_inc += !(c_Cbp & 0x01); ps_bin_ctxt = ps_ctxt_cbp_luma + u4_cxt_inc; /*inlining DecodeDecision_onebin without renorm*/ { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u4_symbol, u1_mps_state; UWORD32 table_lookup; UWORD32 u4_clz; u1_mps_state = (ps_bin_ctxt->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); /*if mps*/ u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) INC_BIN_COUNT(ps_cab_env); ps_bin_ctxt->u1_mps_state = u1_mps_state; c_Cbp |= u4_symbol << 1; } u4_cxt_inc = (!(c_Cbp & 0x01)) << 1; u4_cxt_inc += !((ps_left_ctxt->u1_cbp >> 3) & 0x01); ps_bin_ctxt = ps_ctxt_cbp_luma + u4_cxt_inc; /*inlining DecodeDecision_onebin without renorm*/ { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u4_symbol, u1_mps_state; UWORD32 table_lookup; UWORD32 u4_clz; u1_mps_state = (ps_bin_ctxt->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); /*if mps*/ u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) INC_BIN_COUNT(ps_cab_env); ps_bin_ctxt->u1_mps_state = u1_mps_state; c_Cbp |= u4_symbol << 2; } u4_cxt_inc = (!((c_Cbp >> 1) & 0x01)) << 1; u4_cxt_inc += !((c_Cbp >> 2) & 0x01); ps_bin_ctxt = ps_ctxt_cbp_luma + u4_cxt_inc; /*inlining DecodeDecision_onebin without renorm*/ { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u4_symbol, u1_mps_state; UWORD32 table_lookup; UWORD32 u4_clz; u1_mps_state = (ps_bin_ctxt->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); /*if mps*/ u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) INC_BIN_COUNT(ps_cab_env); ps_bin_ctxt->u1_mps_state = u1_mps_state; c_Cbp |= u4_symbol << 3; } if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) { RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) } { UWORD32 u4_cxt_inc; WORD8 a, b, c, d; bin_ctxt_model_t *p_CtxtCbpChroma = ps_dec->p_cbp_chroma_t; /* CBP Chroma, TU, Cmax = 2 */ a = 0; b = 0; c = 0; d = 0; { a = (ps_top_ctxt->u1_cbp > 15) ? 2 : 0; c = (ps_top_ctxt->u1_cbp > 31) ? 2 : 0; } { b = (ps_left_ctxt->u1_cbp > 15) ? 1 : 0; d = (ps_left_ctxt->u1_cbp > 31) ? 1 : 0; } u4_cxt_inc = a + b; u4_cxt_inc = (u4_cxt_inc | ((4 + c + d) << 4)); /*inlining ih264d_decode_bins_tunary */ { UWORD8 u1_max_bins = 2; UWORD32 u4_ctx_inc = u4_cxt_inc; UWORD32 u4_value; UWORD32 u4_symbol; UWORD8 u4_ctx_Inc; bin_ctxt_model_t *ps_bin_ctxt; u4_value = 0; do { u4_ctx_Inc = u4_ctx_inc & 0xF; u4_ctx_inc = u4_ctx_inc >> 4; ps_bin_ctxt = p_CtxtCbpChroma + u4_ctx_Inc; /*inlining DecodeDecision_onebin*/ { UWORD32 u4_qnt_int_range, u4_int_range_lps; UWORD32 u1_mps_state; UWORD32 table_lookup; UWORD32 u4_clz; u1_mps_state = (ps_bin_ctxt->u1_mps_state); u4_clz = CLZ(u4_code_int_range); u4_qnt_int_range = u4_code_int_range << u4_clz; u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; u4_int_range_lps = table_lookup & 0xff; u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); u4_code_int_range = u4_code_int_range - u4_int_range_lps; u4_symbol = ((u1_mps_state >> 6) & 0x1); /*if mps*/ u1_mps_state = (table_lookup >> 8) & 0x7F; CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, u4_int_range_lps, u1_mps_state, table_lookup) if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) { RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) } ps_bin_ctxt->u1_mps_state = u1_mps_state; } INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS( ps_cab_env); u4_value++; } while((u4_value < u1_max_bins) & (u4_symbol)); u4_value = u4_value - 1 + u4_symbol; a = (u4_value); } c_Cbp = (c_Cbp | (a << 4)); } ps_bitstrm->u4_ofst = u4_offset; ps_cab_env->u4_code_int_range = u4_code_int_range; ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; return (c_Cbp); } /*! ************************************************************************** * \if Function name : ih264d_get_mvd_cabac \endif * * \brief * This function decodes Horz and Vert mvd_l0 and mvd_l1 using CABAC entropy * coding mode as defined in 9.3.2.3. * * \return * None * ************************************************************************** */ void ih264d_get_mvd_cabac(UWORD8 u1_sub_mb, UWORD8 u1_b2, UWORD8 u1_part_wd, UWORD8 u1_part_ht, UWORD8 u1_dec_mvd, dec_struct_t *ps_dec, mv_pred_t *ps_mv) { UWORD8 u1_abs_mvd_x = 0, u1_abs_mvd_y = 0; UWORD8 u1_sub_mb_x, u1_sub_mb_y; UWORD8 *pu1_top_mv_ctxt, *pu1_lft_mv_ctxt; WORD16 *pi2_mv; u1_sub_mb_x = (UWORD8)(u1_sub_mb & 0x03); u1_sub_mb_y = (UWORD8)(u1_sub_mb >> 2); pu1_top_mv_ctxt = &ps_dec->ps_curr_ctxt_mb_info->u1_mv[u1_sub_mb_x][u1_b2]; pu1_lft_mv_ctxt = &ps_dec->pu1_left_mv_ctxt_inc[u1_sub_mb_y][u1_b2]; pi2_mv = &ps_mv->i2_mv[u1_b2]; if(u1_dec_mvd) { WORD16 i2_mv_x, i2_mv_y; WORD32 i2_temp; { decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; UWORD16 u2_abs_mvd_x_a, u2_abs_mvd_x_b, u2_abs_mvd_y_a, u2_abs_mvd_y_b; u2_abs_mvd_x_b = (UWORD16)pu1_top_mv_ctxt[0]; u2_abs_mvd_y_b = (UWORD16)pu1_top_mv_ctxt[1]; u2_abs_mvd_x_a = (UWORD16)pu1_lft_mv_ctxt[0]; u2_abs_mvd_y_a = (UWORD16)pu1_lft_mv_ctxt[1]; i2_temp = u2_abs_mvd_x_a + u2_abs_mvd_x_b; i2_mv_x = ih264d_parse_mvd_cabac(ps_bitstrm, ps_cab_env, ps_dec->p_mvd_x_t, i2_temp); i2_temp = u2_abs_mvd_y_a + u2_abs_mvd_y_b; i2_mv_y = ih264d_parse_mvd_cabac(ps_bitstrm, ps_cab_env, ps_dec->p_mvd_y_t, i2_temp); } /***********************************************************************/ /* Store the abs_mvd_values in cabac contexts */ /* The follownig code can be easily optimzed if mvX, mvY clip values */ /* are packed in 16 bits follwed by memcpy */ /***********************************************************************/ u1_abs_mvd_x = CLIP3(0, 127, ABS(i2_mv_x)); u1_abs_mvd_y = CLIP3(0, 127, ABS(i2_mv_y)); COPYTHECONTEXT("MVD", i2_mv_x);COPYTHECONTEXT("MVD", i2_mv_y); /* Storing Mv residuals */ pi2_mv[0] = i2_mv_x; pi2_mv[1] = i2_mv_y; } /***************************************************************/ /* Store abs_mvd_values cabac contexts */ /***************************************************************/ { UWORD8 u1_i; for(u1_i = 0; u1_i < u1_part_wd; u1_i++, pu1_top_mv_ctxt += 4) { pu1_top_mv_ctxt[0] = u1_abs_mvd_x; pu1_top_mv_ctxt[1] = u1_abs_mvd_y; } for(u1_i = 0; u1_i < u1_part_ht; u1_i++, pu1_lft_mv_ctxt += 4) { pu1_lft_mv_ctxt[0] = u1_abs_mvd_x; pu1_lft_mv_ctxt[1] = u1_abs_mvd_y; } } } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_mvd_cabac */ /* */ /* Description : This cabac function decodes the mvd in a given direction */ /* direction ( x or y ) as defined in 9.3.2.3. */ /* */ /* Inputs : 1. pointer to Bitstream */ /* 2. pointer to cabac decoding environmnet */ /* 3. pointer to Mvd context */ /* 4. abs(Top mvd) = u2_abs_mvd_b */ /* 5. abs(left mvd)= u2_abs_mvd_a */ /* */ /* Processing : see section 9.3.2.3 of the standard */ /* */ /* Outputs : i2_mvd */ /* Returns : i2_mvd */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 16 06 2005 Jay Draft */ /* */ /*****************************************************************************/ WORD16 ih264d_parse_mvd_cabac(dec_bit_stream_t * ps_bitstrm, decoding_envirnoment_t * ps_cab_env, bin_ctxt_model_t * p_ctxt_mvd, UWORD32 i4_temp) { WORD8 k; WORD16 i2_suf; WORD16 i2_mvd; UWORD16 u2_abs_mvd; UWORD32 u4_ctx_inc; UWORD32 u4_prefix; const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; UWORD32 u4_code_int_range, u4_code_int_val_ofst; /* if mvd < 9 */ /* mvd = Prefix */ /* else */ /* mvd = Prefix + Suffix */ /* decode sign bit */ /* Prefix TU decoding Cmax =Ucoff and Suffix 3rd order Exp-Golomb */ u2_abs_mvd = (UWORD16)i4_temp; u4_ctx_inc = 1; if(u2_abs_mvd < 3) u4_ctx_inc = 0; else if(u2_abs_mvd > 32) u4_ctx_inc = 2; u4_ctx_inc = (u4_ctx_inc | 0x65430); /*inlining modified version of ih264d_decode_bins_unary*/ { UWORD8 u1_max_bins = 9; UWORD32 u4_value; UWORD32 u4_symbol; bin_ctxt_model_t *ps_bin_ctxt; UWORD32 u4_ctx_Inc; u4_value = 0; u4_code_int_range = ps_cab_env->u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; do { u4_ctx_Inc = u4_ctx_inc & 0xf; u4_ctx_inc = u4_ctx_inc >> 4; ps_bin_ctxt = p_ctxt_mvd + u4_ctx_Inc; DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, u4_code_int_val_ofst, pu4_table, ps_bitstrm, u4_symbol) INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); u4_value++; } while(u4_symbol && u4_value < 5); ps_bin_ctxt = p_ctxt_mvd + 6; if(u4_symbol && (u4_value < u1_max_bins)) { do { DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, u4_code_int_val_ofst, pu4_table, ps_bitstrm, u4_symbol) INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); u4_value++; } while(u4_symbol && (u4_value < u1_max_bins)); } ps_cab_env->u4_code_int_range = u4_code_int_range; ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; u4_value = u4_value - 1 + u4_symbol; u4_prefix = (u4_value); } i2_mvd = u4_prefix; if(i2_mvd == 9) { /* Read Suffix */ k = ih264d_decode_bypass_bins_unary(ps_cab_env, ps_bitstrm); i2_suf = (1 << k) - 1; k = k + 3; i2_suf = (i2_suf << 3); i2_mvd += i2_suf; i2_suf = ih264d_decode_bypass_bins(ps_cab_env, k, ps_bitstrm); i2_mvd += i2_suf; } /* Read Sign bit */ if(!i2_mvd) return (i2_mvd); else { UWORD32 u4_code_int_val_ofst, u4_code_int_range; u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; u4_code_int_range = ps_cab_env->u4_code_int_range; if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) { UWORD32 *pu4_buffer, u4_offset; pu4_buffer = ps_bitstrm->pu4_buffer; u4_offset = ps_bitstrm->u4_ofst; RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, pu4_buffer) ps_bitstrm->u4_ofst = u4_offset; } u4_code_int_range = u4_code_int_range >> 1; if(u4_code_int_val_ofst >= u4_code_int_range) { /* S=1 */ u4_code_int_val_ofst -= u4_code_int_range; i2_mvd = (-i2_mvd); } ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; ps_cab_env->u4_code_int_range = u4_code_int_range; return (i2_mvd); } } ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_mb_header.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! *************************************************************************** * \file ih264d_parse_mb_header.h * * \brief * This file contains context identifier decoding routines. * * \date * 04/02/2003 * * \author NS *************************************************************************** */ #ifndef _IH264D_PARSE_MB_HEADER_H_ #define _IH264D_PARSE_MB_HEADER_H_ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" #include "ih264d_cabac.h" WORD32 ih264d_read_intra_pred_modes_cabac(dec_struct_t * ps_dec, UWORD8 * pu1_prev_intra4x4_pred_mode_flag, UWORD8 * pu1_rem_intra4x4_pred_mode, UWORD8 u1_tran_form8x8); UWORD32 ih264d_parse_mb_type_cabac(struct _DecStruct * ps_dec); UWORD8 ih264d_parse_mb_type_intra_cabac(UWORD8 u1_inter, struct _DecStruct * ps_dec); UWORD32 ih264d_parse_submb_type_cabac(const UWORD8 u1_slc_type_p, decoding_envirnoment_t * ps_cab_env, dec_bit_stream_t * ps_bitstrm, bin_ctxt_model_t * ps_sub_mb_cxt); WORD32 ih264d_parse_ref_idx_cabac(const UWORD8 u1_num_part, const UWORD8 u1_b2, const UWORD8 u1_max_ref_minus1, const UWORD8 u1_mb_mode, WORD8 * pi1_ref_idx, WORD8 * const pi1_lft_cxt, WORD8 * const pi1_top_cxt, decoding_envirnoment_t * const ps_cab_env, dec_bit_stream_t * const ps_bitstrm, bin_ctxt_model_t * const ps_ref_cxt); WORD32 ih264d_parse_mb_qp_delta_cabac(struct _DecStruct * ps_dec, WORD8 *pi1_mb_qp_delta); WORD8 ih264d_parse_chroma_pred_mode_cabac(struct _DecStruct * ps_dec); UWORD32 ih264d_parse_ctx_cbp_cabac(struct _DecStruct * ps_dec); UWORD8 ih264d_parse_transform8x8flag_cabac(struct _DecStruct * ps_dec, dec_mb_info_t * ps_cur_mb_info); void ih264d_get_mvd_cabac(UWORD8 u1_sub_mb, UWORD8 u1_b2, UWORD8 u1_part_wd, UWORD8 u1_part_ht, UWORD8 u1_dec_mvd, dec_struct_t *ps_dec, mv_pred_t *ps_mv); WORD16 ih264d_parse_mvd_cabac(dec_bit_stream_t * ps_bitstrm, decoding_envirnoment_t * ps_cab_env, bin_ctxt_model_t * p_ctxt_mvd, UWORD32 temp); #endif /* _IH264D_PARSE_MB_HEADER_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_pslice.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_parse_pslice.c * * \brief * Contains routines that decode a I slice type * * Detailed_description * * \date * 07/07/2003 * * \author NS ************************************************************************** */ #include <string.h> #include "ih264_defs.h" #include "ih264d_bitstrm.h" #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264d_parse_cavlc.h" #include "ih264d_mb_utils.h" #include "ih264d_parse_slice.h" #include "ih264d_mvpred.h" #include "ih264d_parse_islice.h" #include "ih264d_process_intra_mb.h" #include "ih264d_inter_pred.h" #include "ih264d_process_pslice.h" #include "ih264d_deblocking.h" #include "ih264d_cabac.h" #include "ih264d_parse_mb_header.h" #include "ih264d_error_handler.h" #include "ih264d_defs.h" #include "ih264d_format_conv.h" #include "ih264d_quant_scaling.h" #include "ih264d_thread_parse_decode.h" #include "ih264d_thread_compute_bs.h" #include "ih264d_process_bslice.h" #include "ithread.h" #include "ih264d_utils.h" #include "ih264d_format_conv.h" void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec); void ih264d_deblock_mb_level(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD32 nmb_index); /*! ************************************************************************** * \if Function name : ih264d_parse_pmb_cavlc \endif * * \brief * This function parses CAVLC syntax of a P MB. * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_pmb_cavlc(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2) { UWORD32 u1_num_mb_part; UWORD32 uc_sub_mb; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 * const pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; parse_pmbarams_t * ps_parse_mb_data = ps_dec->ps_parse_mb_data + u1_num_mbsNby2; WORD8 * pi1_ref_idx = ps_parse_mb_data->i1_ref_idx[0]; const UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; const UWORD8 * pu1_num_mb_part = (const UWORD8 *)gau1_ih264d_num_mb_part; UWORD8 * pu1_col_info = ps_parse_mb_data->u1_col_info; UWORD32 u1_mb_type = ps_cur_mb_info->u1_mb_type; UWORD32 u4_sum_mb_mode_pack = 0; WORD32 ret; UWORD8 u1_no_submb_part_size_lt8x8_flag = 1; ps_cur_mb_info->u1_tran_form8x8 = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; ps_cur_mb_info->u1_yuv_dc_block_flag = 0; ps_cur_mb_info->u1_mb_mc_mode = u1_mb_type; uc_sub_mb = ((u1_mb_type == PRED_8x8) | (u1_mb_type == PRED_8x8R0)); /* Reading the subMB type */ if(uc_sub_mb) { WORD32 i; UWORD8 u1_colz = (PRED_8x8 << 6); for(i = 0; i < 4; i++) { UWORD32 ui_sub_mb_mode; //Inlined ih264d_uev UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; ui_sub_mb_mode = ((1 << u4_ldz) + u4_word - 1); //Inlined ih264d_uev if(ui_sub_mb_mode > 3) { return ERROR_SUB_MB_TYPE; } else { u4_sum_mb_mode_pack = (u4_sum_mb_mode_pack << 8) | ui_sub_mb_mode; /* Storing collocated information */ *pu1_col_info++ = u1_colz | (UWORD8)(ui_sub_mb_mode << 4); COPYTHECONTEXT("sub_mb_type", ui_sub_mb_mode); } /* check if Motion compensation is done below 8x8 */ if(ui_sub_mb_mode != P_L0_8x8) { u1_no_submb_part_size_lt8x8_flag = 0; } } // u1_num_mb_part = 4; } else { *pu1_col_info++ = (u1_mb_type << 6); if(u1_mb_type) *pu1_col_info++ = (u1_mb_type << 6); u1_num_mb_part = pu1_num_mb_part[u1_mb_type]; } /* Decoding reference index 0: For simple profile the following */ /* conditions are always true (mb_field_decoding_flag == 0); */ /* (MbPartPredMode != PredL1) */ { UWORD8 uc_field = ps_cur_mb_info->u1_mb_field_decodingflag; UWORD8 uc_num_ref_idx_l0_active_minus1 = (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0] << (u1_mbaff & uc_field)) - 1; if((uc_num_ref_idx_l0_active_minus1 > 0) & (u1_mb_type != PRED_8x8R0)) { if(1 == uc_num_ref_idx_l0_active_minus1) ih264d_parse_pmb_ref_index_cavlc_range1( u1_num_mb_part, ps_bitstrm, pi1_ref_idx, uc_num_ref_idx_l0_active_minus1); else { ret = ih264d_parse_pmb_ref_index_cavlc( u1_num_mb_part, ps_bitstrm, pi1_ref_idx, uc_num_ref_idx_l0_active_minus1); if(ret != OK) return ret; } } else { /* When there exists only a single frame to predict from */ UWORD8 uc_i; for(uc_i = 0; uc_i < u1_num_mb_part; uc_i++) /* Storing Reference Idx Information */ pi1_ref_idx[uc_i] = 0; } } { UWORD8 u1_p_idx, uc_i; parse_part_params_t * ps_part = ps_dec->ps_part; UWORD8 u1_sub_mb_mode, u1_num_subpart, u1_mb_part_width, u1_mb_part_height; UWORD8 u1_sub_mb_num; const UWORD8 * pu1_top_left_sub_mb_indx; mv_pred_t * ps_mv, *ps_mv_start = ps_dec->ps_mv_cur + (u1_mb_num << 4); /* Loading the table pointers */ const UWORD8 * pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; const UWORD8 * pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; const UWORD8 * pu1_sub_mb_indx_mod = (const UWORD8 *)(gau1_ih264d_submb_indx_mod) + (uc_sub_mb * 6); const UWORD8 * pu1_sub_mb_partw = (const UWORD8 *)gau1_ih264d_submb_partw; const UWORD8 * pu1_sub_mb_parth = (const UWORD8 *)gau1_ih264d_submb_parth; const UWORD8 * pu1_num_sub_mb_part = (const UWORD8 *)gau1_ih264d_num_submb_part; UWORD16 u2_sub_mb_num = 0x028A; /*********************************************************/ /* default initialisations for condition (uc_sub_mb == 0) */ /* i.e. all are subpartitions of 8x8 */ /*********************************************************/ u1_sub_mb_mode = 0; u1_num_subpart = 1; u1_mb_part_width = pu1_mb_partw[u1_mb_type]; u1_mb_part_height = pu1_mb_parth[u1_mb_type]; pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_type << 1); u1_sub_mb_num = 0; /* Loop on number of partitions */ for(uc_i = 0, u1_p_idx = 0; uc_i < u1_num_mb_part; uc_i++) { UWORD8 uc_j; if(uc_sub_mb) { u1_sub_mb_mode = u4_sum_mb_mode_pack >> 24; u1_num_subpart = pu1_num_sub_mb_part[u1_sub_mb_mode]; u1_mb_part_width = pu1_sub_mb_partw[u1_sub_mb_mode]; u1_mb_part_height = pu1_sub_mb_parth[u1_sub_mb_mode]; pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_sub_mb_mode << 1); u1_sub_mb_num = u2_sub_mb_num >> 12; u4_sum_mb_mode_pack <<= 8; u2_sub_mb_num <<= 4; } /* Loop on Number of sub-partitions */ for(uc_j = 0; uc_j < u1_num_subpart; uc_j++, pu1_top_left_sub_mb_indx++) { WORD16 i2_mvx, i2_mvy; u1_sub_mb_num += *pu1_top_left_sub_mb_indx; ps_mv = ps_mv_start + u1_sub_mb_num; /* Reading the differential Mv from the bitstream */ //i2_mvx = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); //inlining ih264d_sev { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_abs_val; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; if(u4_word & 0x1) i2_mvx = (-(WORD32)u4_abs_val); else i2_mvx = (u4_abs_val); } //inlinined ih264d_sev COPYTHECONTEXT("MVD", i2_mvx); i2_mvy = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("MVD", i2_mvy); /* Storing Info for partitions */ ps_part->u1_is_direct = PART_NOT_DIRECT; ps_part->u1_sub_mb_num = u1_sub_mb_num; ps_part->u1_partheight = u1_mb_part_height; ps_part->u1_partwidth = u1_mb_part_width; /* Storing Mv residuals */ ps_mv->i2_mv[0] = i2_mvx; ps_mv->i2_mv[1] = i2_mvy; /* Increment partition Index */ u1_p_idx++; ps_part++; } } ps_parse_mb_data->u1_num_part = u1_p_idx; ps_dec->ps_part = ps_part; } { UWORD32 u4_cbp; /* Read the Coded block pattern */ UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_cbp = ((1 << u4_ldz) + u4_word - 1); if(u4_cbp > 47) return ERROR_CBP; u4_cbp = *((UWORD8*)gau1_ih264d_cbp_inter + u4_cbp); COPYTHECONTEXT("coded_block_pattern", u4_cbp); ps_cur_mb_info->u1_cbp = u4_cbp; /* Read the transform8x8 u4_flag if present */ if((ps_dec->s_high_profile.u1_transform8x8_present) && (u4_cbp & 0xf) && u1_no_submb_part_size_lt8x8_flag) { ps_cur_mb_info->u1_tran_form8x8 = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; } /* Read mb_qp_delta */ if(u4_cbp) { WORD32 i_temp; UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_abs_val; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; if(u4_word & 0x1) i_temp = (-(WORD32)u4_abs_val); else i_temp = (u4_abs_val); if((i_temp < -26) || (i_temp > 25)) return ERROR_INV_RANGE_QP_T; //inlinined ih264d_sev COPYTHECONTEXT("mb_qp_delta", i_temp); if(i_temp) { ret = ih264d_update_qp(ps_dec, (WORD8)i_temp); if(ret != OK) return ret; } ret = ih264d_parse_residual4x4_cavlc(ps_dec, ps_cur_mb_info, 0); if(ret != OK) return ret; if(EXCEED_OFFSET(ps_bitstrm)) return ERROR_EOB_TERMINATE_T; } else { ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CAVLC); } } return OK; } /*! ************************************************************************** * \if Function name : ih264d_parse_pmb_cabac \endif * * \brief * This function parses CABAC syntax of a P MB. * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_pmb_cabac(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2) { UWORD32 u1_num_mb_part; UWORD32 uc_sub_mb; parse_pmbarams_t * ps_parse_mb_data = ps_dec->ps_parse_mb_data + u1_num_mbsNby2; WORD8 * pi1_ref_idx = ps_parse_mb_data->i1_ref_idx[0]; const UWORD8 * pu1_num_mb_part = (const UWORD8 *)gau1_ih264d_num_mb_part; const UWORD32 u1_mb_type = ps_cur_mb_info->u1_mb_type; UWORD8 * pu1_col_info = ps_parse_mb_data->u1_col_info; UWORD32 u1_mb_mc_mode = u1_mb_type; ctxt_inc_mb_info_t * p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 u4_sub_mb_pack = 0; WORD32 ret; UWORD8 u1_no_submb_part_size_lt8x8_flag = 1; ps_cur_mb_info->u1_tran_form8x8 = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; ps_cur_mb_info->u1_yuv_dc_block_flag = 0; p_curr_ctxt->u1_mb_type = CAB_P; ps_cur_mb_info->u1_mb_mc_mode = u1_mb_type; uc_sub_mb = ((u1_mb_type == PRED_8x8) | (u1_mb_type == PRED_8x8R0)); /* Reading the subMB type */ if(uc_sub_mb) { UWORD8 u1_colz = (PRED_8x8 << 6); u1_mb_mc_mode = 0; { UWORD8 u1_sub_mb_mode; u1_sub_mb_mode = ih264d_parse_submb_type_cabac( 0, ps_cab_env, ps_bitstrm, ps_dec->p_sub_mb_type_t); if(u1_sub_mb_mode > 3) return ERROR_SUB_MB_TYPE; u4_sub_mb_pack = (u4_sub_mb_pack << 8) | u1_sub_mb_mode; /* Storing collocated information */ *pu1_col_info++ = u1_colz | ((UWORD8)(u1_sub_mb_mode << 4)); COPYTHECONTEXT("sub_mb_type", u1_sub_mb_mode); /* check if Motion compensation is done below 8x8 */ if(u1_sub_mb_mode != P_L0_8x8) { u1_no_submb_part_size_lt8x8_flag = 0; } } { UWORD8 u1_sub_mb_mode; u1_sub_mb_mode = ih264d_parse_submb_type_cabac( 0, ps_cab_env, ps_bitstrm, ps_dec->p_sub_mb_type_t); if(u1_sub_mb_mode > 3) return ERROR_SUB_MB_TYPE; u4_sub_mb_pack = (u4_sub_mb_pack << 8) | u1_sub_mb_mode; /* Storing collocated information */ *pu1_col_info++ = u1_colz | ((UWORD8)(u1_sub_mb_mode << 4)); COPYTHECONTEXT("sub_mb_type", u1_sub_mb_mode); /* check if Motion compensation is done below 8x8 */ if(u1_sub_mb_mode != P_L0_8x8) { u1_no_submb_part_size_lt8x8_flag = 0; } } { UWORD8 u1_sub_mb_mode; u1_sub_mb_mode = ih264d_parse_submb_type_cabac( 0, ps_cab_env, ps_bitstrm, ps_dec->p_sub_mb_type_t); if(u1_sub_mb_mode > 3) return ERROR_SUB_MB_TYPE; u4_sub_mb_pack = (u4_sub_mb_pack << 8) | u1_sub_mb_mode; /* Storing collocated information */ *pu1_col_info++ = u1_colz | ((UWORD8)(u1_sub_mb_mode << 4)); COPYTHECONTEXT("sub_mb_type", u1_sub_mb_mode); /* check if Motion compensation is done below 8x8 */ if(u1_sub_mb_mode != P_L0_8x8) { u1_no_submb_part_size_lt8x8_flag = 0; } } { UWORD8 u1_sub_mb_mode; u1_sub_mb_mode = ih264d_parse_submb_type_cabac( 0, ps_cab_env, ps_bitstrm, ps_dec->p_sub_mb_type_t); if(u1_sub_mb_mode > 3) return ERROR_SUB_MB_TYPE; u4_sub_mb_pack = (u4_sub_mb_pack << 8) | u1_sub_mb_mode; /* Storing collocated information */ *pu1_col_info++ = u1_colz | ((UWORD8)(u1_sub_mb_mode << 4)); COPYTHECONTEXT("sub_mb_type", u1_sub_mb_mode); /* check if Motion compensation is done below 8x8 */ if(u1_sub_mb_mode != P_L0_8x8) { u1_no_submb_part_size_lt8x8_flag = 0; } } u1_num_mb_part = 4; } else { u1_num_mb_part = pu1_num_mb_part[u1_mb_type]; /* Storing collocated Mb and SubMb mode information */ *pu1_col_info++ = (u1_mb_type << 6); if(u1_mb_type) *pu1_col_info++ = (u1_mb_type << 6); } /* Decoding reference index 0: For simple profile the following */ /* conditions are always true (mb_field_decoding_flag == 0); */ /* (MbPartPredMode != PredL1) */ { WORD8 * pi1_top_ref_idx_ctx_inc_arr = p_curr_ctxt->i1_ref_idx; WORD8 * pi1_left_ref_idx_ctxt_inc = ps_dec->pi1_left_ref_idx_ctxt_inc; UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; UWORD8 uc_field = ps_cur_mb_info->u1_mb_field_decodingflag; UWORD8 uc_num_ref_idx_l0_active_minus1 = (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0] << (u1_mbaff & uc_field)) - 1; if((uc_num_ref_idx_l0_active_minus1 > 0) & (u1_mb_type != PRED_8x8R0)) { /* force the routine to decode ref idx for each partition */ *((UWORD32 *)pi1_ref_idx) = 0x01010101; ret = ih264d_parse_ref_idx_cabac(u1_num_mb_part, 0, uc_num_ref_idx_l0_active_minus1, u1_mb_mc_mode, pi1_ref_idx, pi1_left_ref_idx_ctxt_inc, pi1_top_ref_idx_ctx_inc_arr, ps_cab_env, ps_bitstrm, ps_dec->p_ref_idx_t); if(ret != OK) return ret; } else { /* When there exists only a single frame to predict from */ pi1_left_ref_idx_ctxt_inc[0] = 0; pi1_left_ref_idx_ctxt_inc[1] = 0; pi1_top_ref_idx_ctx_inc_arr[0] = 0; pi1_top_ref_idx_ctx_inc_arr[1] = 0; *((UWORD32 *)pi1_ref_idx) = 0; } } { UWORD8 u1_p_idx, uc_i; parse_part_params_t * ps_part = ps_dec->ps_part; UWORD8 u1_sub_mb_mode, u1_num_subpart, u1_mb_part_width, u1_mb_part_height; UWORD8 u1_sub_mb_num; const UWORD8 * pu1_top_left_sub_mb_indx; mv_pred_t *ps_mv_start = ps_dec->ps_mv_cur + (u1_mb_num << 4); UWORD16 u2_sub_mb_num_pack = 0x028A; /* Loading the table pointers */ const UWORD8 * pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; const UWORD8 * pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; const UWORD8 * pu1_sub_mb_indx_mod = (const UWORD8 *)(gau1_ih264d_submb_indx_mod) + (uc_sub_mb * 6); const UWORD8 * pu1_sub_mb_partw = (const UWORD8 *)gau1_ih264d_submb_partw; const UWORD8 * pu1_sub_mb_parth = (const UWORD8 *)gau1_ih264d_submb_parth; const UWORD8 * pu1_num_sub_mb_part = (const UWORD8 *)gau1_ih264d_num_submb_part; /*********************************************************/ /* default initialisations for condition (uc_sub_mb == 0) */ /* i.e. all are subpartitions of 8x8 */ /*********************************************************/ u1_sub_mb_mode = 0; u1_num_subpart = 1; u1_mb_part_width = pu1_mb_partw[u1_mb_type]; u1_mb_part_height = pu1_mb_parth[u1_mb_type]; pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_type << 1); u1_sub_mb_num = 0; /* Loop on number of partitions */ for(uc_i = 0, u1_p_idx = 0; uc_i < u1_num_mb_part; uc_i++) { UWORD8 uc_j; if(uc_sub_mb) { u1_sub_mb_mode = u4_sub_mb_pack >> 24; u1_num_subpart = pu1_num_sub_mb_part[u1_sub_mb_mode]; u1_mb_part_width = pu1_sub_mb_partw[u1_sub_mb_mode]; u1_mb_part_height = pu1_sub_mb_parth[u1_sub_mb_mode]; pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_sub_mb_mode << 1); u1_sub_mb_num = u2_sub_mb_num_pack >> 12; u4_sub_mb_pack <<= 8; u2_sub_mb_num_pack <<= 4; } /* Loop on Number of sub-partitions */ for(uc_j = 0; uc_j < u1_num_subpart; uc_j++, pu1_top_left_sub_mb_indx++) { mv_pred_t * ps_mv; u1_sub_mb_num += *pu1_top_left_sub_mb_indx; ps_mv = ps_mv_start + u1_sub_mb_num; /* Storing Info for partitions */ ps_part->u1_is_direct = PART_NOT_DIRECT; ps_part->u1_sub_mb_num = u1_sub_mb_num; ps_part->u1_partheight = u1_mb_part_height; ps_part->u1_partwidth = u1_mb_part_width; /* Increment partition Index */ u1_p_idx++; ps_part++; ih264d_get_mvd_cabac(u1_sub_mb_num, 0, u1_mb_part_width, u1_mb_part_height, 1, ps_dec, ps_mv); } } ps_parse_mb_data->u1_num_part = u1_p_idx; ps_dec->ps_part = ps_part; } { UWORD8 u1_cbp; /* Read the Coded block pattern */ u1_cbp = (WORD8)ih264d_parse_ctx_cbp_cabac(ps_dec); COPYTHECONTEXT("coded_block_pattern", u1_cbp); ps_cur_mb_info->u1_cbp = u1_cbp; p_curr_ctxt->u1_cbp = u1_cbp; p_curr_ctxt->u1_intra_chroma_pred_mode = 0; p_curr_ctxt->u1_yuv_dc_csbp &= 0xFE; ps_dec->pu1_left_yuv_dc_csbp[0] &= 0x6; if(u1_cbp > 47) return ERROR_CBP; ps_cur_mb_info->u1_tran_form8x8 = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; /* Read the transform8x8 u4_flag if present */ if((ps_dec->s_high_profile.u1_transform8x8_present) && (u1_cbp & 0xf) && u1_no_submb_part_size_lt8x8_flag) { ps_cur_mb_info->u1_tran_form8x8 = ih264d_parse_transform8x8flag_cabac( ps_dec, ps_cur_mb_info); COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); p_curr_ctxt->u1_transform8x8_ctxt = ps_cur_mb_info->u1_tran_form8x8; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; } else { p_curr_ctxt->u1_transform8x8_ctxt = 0; } /* Read mb_qp_delta */ if(u1_cbp) { WORD8 c_temp; ret = ih264d_parse_mb_qp_delta_cabac(ps_dec, &c_temp); if(ret != OK) return ret; COPYTHECONTEXT("mb_qp_delta", c_temp); if(c_temp != 0) { ret = ih264d_update_qp(ps_dec, c_temp); if(ret != OK) return ret; } } else ps_dec->i1_prev_mb_qp_delta = 0; ih264d_parse_residual4x4_cabac(ps_dec, ps_cur_mb_info, 0); if(EXCEED_OFFSET(ps_dec->ps_bitstrm)) return ERROR_EOB_TERMINATE_T; } return OK; } /*! ************************************************************************** * \if Function name : parsePSliceData \endif * * \brief * This function parses CAVLC syntax of N MB's of a P slice. * 1. After parsing syntax of N MB's, for those N MB's (less than N, incase * of end of slice or end of row), MB is decoded. This process is carried * for one complete MB row or till end of slice. * 2. Bottom one row of current MB is copied to IntraPredLine buffers. * IntraPredLine buffers are used for Intra prediction of next row. * 3. Current MB row along with previous 4 rows of Luma (and 2 of Chroma) are * deblocked. * 4. 4 rows (2 for Chroma) previous row and 12 rows (6 for Chroma) are * DMA'ed to picture buffers. * * \return * 0 on Success and Error code otherwise ************************************************************************** */ /*! ************************************************************************** * \if Function name : ih264d_update_nnz_for_skipmb \endif * * \brief * * \return * None * ************************************************************************** */ void ih264d_update_nnz_for_skipmb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_entrpy) { UWORD32 *pu4_buf; UWORD8 *pu1_buf; UNUSED(u1_entrpy); pu1_buf = ps_dec->pu1_left_nnz_y; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0; pu1_buf = ps_dec->pu1_left_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0; pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_y; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0; pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_uv; pu4_buf = (UWORD32 *)pu1_buf; *pu4_buf = 0; ps_cur_mb_info->ps_curmb->u2_luma_csbp = 0; ps_cur_mb_info->u2_luma_csbp = 0; ps_cur_mb_info->u2_chroma_csbp = 0; } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_inter_slice_data_cabac */ /* */ /* Description : This function parses cabac syntax of a inter slice on */ /* N MB basis. */ /* */ /* Inputs : ps_dec */ /* sliceparams */ /* firstMbInSlice */ /* */ /* Processing : 1. After parsing syntax for N MBs those N MBs are */ /* decoded till the end of slice. */ /* 2. MV prediction and DMA happens on a N/2 MB basis. */ /* */ /* Returns : 0 */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 07 2002 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_inter_slice_data_cabac(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice) { UWORD32 uc_more_data_flag; WORD32 i2_cur_mb_addr; UWORD32 u1_num_mbs, u1_num_mbsNby2, u1_mb_idx; UWORD32 u1_mbaff; UWORD32 u1_num_mbs_next, u1_end_of_row; const UWORD16 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; UWORD32 u1_slice_end = 0; UWORD32 u1_tfr_n_mb = 0; UWORD32 u1_decode_nmb = 0; deblk_mb_t *ps_cur_deblk_mb; dec_mb_info_t *ps_cur_mb_info; parse_pmbarams_t *ps_parse_mb_data = ps_dec->ps_parse_mb_data; UWORD32 u1_inter_mb_skip_type; UWORD32 u1_inter_mb_type; UWORD32 u1_deblk_mb_type; UWORD32 u1_mb_threshold; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; WORD32 ret = OK; /******************************************************/ /* Initialisations specific to B or P slice */ /******************************************************/ if(ps_slice->u1_slice_type == P_SLICE) { u1_inter_mb_skip_type = CAB_P_SKIP; u1_inter_mb_type = P_MB; u1_deblk_mb_type = D_INTER_MB; u1_mb_threshold = 5; } else // B_SLICE { u1_inter_mb_skip_type = CAB_B_SKIP; u1_inter_mb_type = B_MB; u1_deblk_mb_type = D_B_SLICE; u1_mb_threshold = 23; } /******************************************************/ /* Slice Level Initialisations */ /******************************************************/ i2_cur_mb_addr = u2_first_mb_in_slice; ps_dec->u1_qp = ps_slice->u1_slice_qp; ih264d_update_qp(ps_dec, 0); u1_mb_idx = ps_dec->u1_mb_idx; u1_num_mbs = u1_mb_idx; u1_num_mbsNby2 = 0; u1_mbaff = ps_slice->u1_mbaff_frame_flag; i2_cur_mb_addr = u2_first_mb_in_slice << u1_mbaff; uc_more_data_flag = 1; /* Initialisations specific to cabac */ if(ps_bitstrm->u4_ofst & 0x07) { ps_bitstrm->u4_ofst += 8; ps_bitstrm->u4_ofst &= 0xFFFFFFF8; } ret = ih264d_init_cabac_dec_envirnoment(&(ps_dec->s_cab_dec_env), ps_bitstrm); if(ret != OK) return ret; ps_dec->i1_prev_mb_qp_delta = 0; while(!u1_slice_end) { UWORD8 u1_mb_type; UWORD32 u4_mb_skip; ps_dec->pv_prev_mb_parse_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; if(i2_cur_mb_addr > ps_dec->ps_cur_sps->u2_max_mb_addr) { break; } ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs; ps_dec->u4_num_mbs_cur_nmb = u1_num_mbs; ps_cur_mb_info->u1_Mux = 0; ps_dec->u4_num_pmbair = (u1_num_mbs >> u1_mbaff); ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_num_mbs; ps_cur_mb_info->u1_end_of_slice = 0; /* Storing Default partition info */ ps_parse_mb_data->u1_num_part = 1; ps_parse_mb_data->u1_isI_mb = 0; /***************************************************************/ /* Get the required information for decoding of MB */ /* mb_x, mb_y , neighbour availablity, */ /***************************************************************/ u4_mb_skip = ps_dec->pf_get_mb_info(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, 1); /*********************************************************************/ /* initialize u1_tran_form8x8 to zero to aviod uninitialized accesses */ /*********************************************************************/ ps_cur_mb_info->u1_tran_form8x8 = 0; ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; /***************************************************************/ /* Set the deblocking parameters for this MB */ /***************************************************************/ if(ps_dec->u4_app_disable_deblk_frm == 0) ih264d_set_deblocking_parameters(ps_cur_deblk_mb, ps_slice, ps_dec->u1_mb_ngbr_availablity, ps_dec->u1_cur_mb_fld_dec_flag); if(u4_mb_skip) { /* Set appropriate flags in ps_cur_mb_info and ps_dec */ memset(ps_dec->ps_curr_ctxt_mb_info, 0, sizeof(ctxt_inc_mb_info_t)); ps_dec->ps_curr_ctxt_mb_info->u1_mb_type = u1_inter_mb_skip_type; MEMSET_16BYTES(&ps_dec->pu1_left_mv_ctxt_inc[0][0], 0); *((UWORD32 *)ps_dec->pi1_left_ref_idx_ctxt_inc) = 0; *(ps_dec->pu1_left_yuv_dc_csbp) = 0; ps_dec->i1_prev_mb_qp_delta = 0; ps_cur_mb_info->u1_mb_type = MB_SKIP; ps_cur_mb_info->u1_cbp = 0; { /* Storing Skip partition info */ parse_part_params_t *ps_part_info = ps_dec->ps_part; ps_part_info->u1_is_direct = PART_DIRECT_16x16; ps_part_info->u1_sub_mb_num = 0; ps_dec->ps_part++; } /* Update Nnzs */ ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CABAC); ps_cur_mb_info->ps_curmb->u1_mb_type = u1_inter_mb_type; ps_cur_deblk_mb->u1_mb_type |= u1_deblk_mb_type; ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; } else { /* Macroblock Layer Begins */ /* Decode the u1_mb_type */ u1_mb_type = ih264d_parse_mb_type_cabac(ps_dec); ps_cur_mb_info->u1_mb_type = u1_mb_type; if(u1_mb_type > (25 + u1_mb_threshold)) return ERROR_MB_TYPE; /* Parse Macroblock Data */ if(u1_mb_type < u1_mb_threshold) { ps_cur_mb_info->ps_curmb->u1_mb_type = u1_inter_mb_type; *(ps_dec->pu1_left_yuv_dc_csbp) &= 0x6; ret = ps_dec->pf_parse_inter_mb(ps_dec, ps_cur_mb_info, u1_num_mbs, u1_num_mbsNby2); if(ret != OK) return ret; ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; ps_cur_deblk_mb->u1_mb_type |= u1_deblk_mb_type; } else { /* Storing Intra partition info */ ps_parse_mb_data->u1_num_part = 0; ps_parse_mb_data->u1_isI_mb = 1; if((25 + u1_mb_threshold) == u1_mb_type) { /* I_PCM_MB */ ps_cur_mb_info->ps_curmb->u1_mb_type = I_PCM_MB; ret = ih264d_parse_ipcm_mb(ps_dec, ps_cur_mb_info, u1_num_mbs); if(ret != OK) return ret; ps_cur_deblk_mb->u1_mb_qp = 0; } else { if(u1_mb_type == u1_mb_threshold) ps_cur_mb_info->ps_curmb->u1_mb_type = I_4x4_MB; else ps_cur_mb_info->ps_curmb->u1_mb_type = I_16x16_MB; ret = ih264d_parse_imb_cabac( ps_dec, ps_cur_mb_info, (UWORD8)(u1_mb_type - u1_mb_threshold)); if(ret != OK) return ret; ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; } ps_cur_deblk_mb->u1_mb_type |= D_INTRA_MB; } } if(u1_mbaff) { ih264d_update_mbaff_left_nnz(ps_dec, ps_cur_mb_info); } if(ps_cur_mb_info->u1_topmb && u1_mbaff) uc_more_data_flag = 1; else { uc_more_data_flag = ih264d_decode_terminate(&ps_dec->s_cab_dec_env, ps_bitstrm); uc_more_data_flag = !uc_more_data_flag; COPYTHECONTEXT("Decode Sliceterm",!uc_more_data_flag); } if(u1_mbaff) { if(!uc_more_data_flag && (0 == (i2_cur_mb_addr & 1))) { return ERROR_EOB_FLUSHBITS_T; } } /* Next macroblock information */ i2_cur_mb_addr++; u1_num_mbs++; u1_num_mbsNby2++; ps_parse_mb_data++; /****************************************************************/ /* Check for End Of Row and other flags that determine when to */ /* do DMA setup for N/2-Mb, Decode for N-Mb, and Transfer for */ /* N-Mb */ /****************************************************************/ u1_num_mbs_next = i2_pic_wdin_mbs - ps_dec->u2_mbx - 1; u1_end_of_row = (!u1_num_mbs_next) && (!(u1_mbaff && (u1_num_mbs & 0x01))); u1_slice_end = !uc_more_data_flag; u1_tfr_n_mb = (u1_num_mbs == ps_dec->u1_recon_mb_grp) || u1_end_of_row || u1_slice_end; u1_decode_nmb = u1_tfr_n_mb || u1_slice_end; ps_cur_mb_info->u1_end_of_slice = u1_slice_end; /*u1_dma_nby2mb = u1_decode_nmb || (u1_num_mbsNby2 == ps_dec->u1_recon_mb_grp_pair);*/ //if(u1_dma_nby2mb) if(u1_decode_nmb) { ps_dec->pf_mvpred_ref_tfr_nby2mb(ps_dec, u1_mb_idx, u1_num_mbs); u1_num_mbsNby2 = 0; { ps_parse_mb_data = ps_dec->ps_parse_mb_data; ps_dec->ps_part = ps_dec->ps_parse_part_params; } } /*H264_DEC_DEBUG_PRINT("Pic: %d Mb_X=%d Mb_Y=%d", ps_slice->i4_poc >> ps_slice->u1_field_pic_flag, ps_dec->u2_mbx,ps_dec->u2_mby + (1 - ps_cur_mb_info->u1_topmb)); H264_DEC_DEBUG_PRINT("u1_decode_nmb: %d, u1_num_mbs: %d", u1_decode_nmb, u1_num_mbs);*/ if(u1_decode_nmb) { if(ps_dec->u1_separate_parse) { ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); ps_dec->ps_nmb_info += u1_num_mbs; } else { ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); } ps_dec->u2_total_mbs_coded += u1_num_mbs; if(u1_tfr_n_mb) u1_num_mbs = 0; u1_mb_idx = u1_num_mbs; ps_dec->u1_mb_idx = u1_num_mbs; } } ps_dec->u4_num_mbs_cur_nmb = 0; ps_dec->ps_cur_slice->u4_mbs_in_slice = i2_cur_mb_addr - (u2_first_mb_in_slice << u1_mbaff); return ret; } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_inter_slice_data_cavlc */ /* */ /* Description : This function parses cavlc syntax of a inter slice on */ /* N MB basis. */ /* */ /* Inputs : ps_dec */ /* sliceparams */ /* firstMbInSlice */ /* */ /* Processing : 1. After parsing syntax for N MBs those N MBs are */ /* decoded till the end of slice. */ /* 2. MV prediction and DMA happens on a N/2 MB basis. */ /* */ /* Returns : 0 */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 07 2002 Jay Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_inter_slice_data_cavlc(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice) { UWORD32 uc_more_data_flag; WORD32 i2_cur_mb_addr; UWORD32 u1_num_mbs, u1_num_mbsNby2, u1_mb_idx; UWORD32 i2_mb_skip_run; UWORD32 u1_read_mb_type; UWORD32 u1_mbaff; UWORD32 u1_num_mbs_next, u1_end_of_row; const UWORD32 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; UWORD32 u1_slice_end = 0; UWORD32 u1_tfr_n_mb = 0; UWORD32 u1_decode_nmb = 0; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; deblk_mb_t *ps_cur_deblk_mb; dec_mb_info_t *ps_cur_mb_info; parse_pmbarams_t *ps_parse_mb_data = ps_dec->ps_parse_mb_data; UWORD32 u1_inter_mb_type; UWORD32 u1_deblk_mb_type; UWORD32 u1_mb_threshold; WORD32 ret = OK; /******************************************************/ /* Initialisations specific to B or P slice */ /******************************************************/ if(ps_slice->u1_slice_type == P_SLICE) { u1_inter_mb_type = P_MB; u1_deblk_mb_type = D_INTER_MB; u1_mb_threshold = 5; } else // B_SLICE { u1_inter_mb_type = B_MB; u1_deblk_mb_type = D_B_SLICE; u1_mb_threshold = 23; } /******************************************************/ /* Slice Level Initialisations */ /******************************************************/ ps_dec->u1_qp = ps_slice->u1_slice_qp; ih264d_update_qp(ps_dec, 0); u1_mb_idx = ps_dec->u1_mb_idx; u1_num_mbs = u1_mb_idx; u1_num_mbsNby2 = 0; u1_mbaff = ps_slice->u1_mbaff_frame_flag; i2_cur_mb_addr = u2_first_mb_in_slice << u1_mbaff; i2_mb_skip_run = 0; uc_more_data_flag = 1; u1_read_mb_type = 0; while(!u1_slice_end) { UWORD8 u1_mb_type; ps_dec->pv_prev_mb_parse_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; if(i2_cur_mb_addr > ps_dec->ps_cur_sps->u2_max_mb_addr) { break; } ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs; ps_dec->u4_num_mbs_cur_nmb = u1_num_mbs; ps_cur_mb_info->u1_Mux = 0; ps_dec->u4_num_pmbair = (u1_num_mbs >> u1_mbaff); ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_num_mbs; ps_cur_mb_info->u1_end_of_slice = 0; /* Storing Default partition info */ ps_parse_mb_data->u1_num_part = 1; ps_parse_mb_data->u1_isI_mb = 0; if((!i2_mb_skip_run) && (!u1_read_mb_type)) { //Inlined ih264d_uev UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz; /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) { GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); } *pu4_bitstrm_ofst = u4_bitstream_offset; i2_mb_skip_run = ((1 << u4_ldz) + u4_word - 1); //Inlined ih264d_uev COPYTHECONTEXT("mb_skip_run", i2_mb_skip_run); uc_more_data_flag = MORE_RBSP_DATA(ps_bitstrm); u1_read_mb_type = uc_more_data_flag; } /***************************************************************/ /* Get the required information for decoding of MB */ /* mb_x, mb_y , neighbour availablity, */ /***************************************************************/ ps_dec->pf_get_mb_info(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, i2_mb_skip_run); /***************************************************************/ /* Set the deblocking parameters for this MB */ /***************************************************************/ if(ps_dec->u4_app_disable_deblk_frm == 0) ih264d_set_deblocking_parameters(ps_cur_deblk_mb, ps_slice, ps_dec->u1_mb_ngbr_availablity, ps_dec->u1_cur_mb_fld_dec_flag); if(i2_mb_skip_run) { /* Set appropriate flags in ps_cur_mb_info and ps_dec */ ps_dec->i1_prev_mb_qp_delta = 0; ps_dec->u1_sub_mb_num = 0; ps_cur_mb_info->u1_mb_type = MB_SKIP; ps_cur_mb_info->u1_mb_mc_mode = PRED_16x16; ps_cur_mb_info->u1_cbp = 0; { /* Storing Skip partition info */ parse_part_params_t *ps_part_info = ps_dec->ps_part; ps_part_info->u1_is_direct = PART_DIRECT_16x16; ps_part_info->u1_sub_mb_num = 0; ps_dec->ps_part++; } /* Update Nnzs */ ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CAVLC); ps_cur_mb_info->ps_curmb->u1_mb_type = u1_inter_mb_type; ps_cur_deblk_mb->u1_mb_type |= u1_deblk_mb_type; i2_mb_skip_run--; } else { u1_read_mb_type = 0; /**************************************************************/ /* Macroblock Layer Begins, Decode the u1_mb_type */ /**************************************************************/ { UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; UWORD32 u4_word, u4_ldz, u4_temp; //Inlined ih264d_uev /***************************************************************/ /* Find leading zeros in next 32 bits */ /***************************************************************/ NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); u4_ldz = CLZ(u4_word); /* Flush the ps_bitstrm */ u4_bitstream_offset += (u4_ldz + 1); /* Read the suffix from the ps_bitstrm */ u4_word = 0; if(u4_ldz) GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); *pu4_bitstrm_ofst = u4_bitstream_offset; u4_temp = ((1 << u4_ldz) + u4_word - 1); //Inlined ih264d_uev if(u4_temp > (UWORD32)(25 + u1_mb_threshold)) return ERROR_MB_TYPE; u1_mb_type = u4_temp; COPYTHECONTEXT("u1_mb_type", u1_mb_type); } ps_cur_mb_info->u1_mb_type = u1_mb_type; /**************************************************************/ /* Parse Macroblock data */ /**************************************************************/ if(u1_mb_type < u1_mb_threshold) { ps_cur_mb_info->ps_curmb->u1_mb_type = u1_inter_mb_type; ret = ps_dec->pf_parse_inter_mb(ps_dec, ps_cur_mb_info, u1_num_mbs, u1_num_mbsNby2); if(ret != OK) return ret; ps_cur_deblk_mb->u1_mb_type |= u1_deblk_mb_type; } else { /* Storing Intra partition info */ ps_parse_mb_data->u1_num_part = 0; ps_parse_mb_data->u1_isI_mb = 1; if((25 + u1_mb_threshold) == u1_mb_type) { /* I_PCM_MB */ ps_cur_mb_info->ps_curmb->u1_mb_type = I_PCM_MB; ret = ih264d_parse_ipcm_mb(ps_dec, ps_cur_mb_info, u1_num_mbs); if(ret != OK) return ret; ps_dec->u1_qp = 0; } else { ret = ih264d_parse_imb_cavlc( ps_dec, ps_cur_mb_info, u1_num_mbs, (UWORD8)(u1_mb_type - u1_mb_threshold)); if(ret != OK) return ret; } ps_cur_deblk_mb->u1_mb_type |= D_INTRA_MB; } uc_more_data_flag = MORE_RBSP_DATA(ps_bitstrm); } ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; if(u1_mbaff) { ih264d_update_mbaff_left_nnz(ps_dec, ps_cur_mb_info); if(!uc_more_data_flag && !i2_mb_skip_run && (0 == (i2_cur_mb_addr & 1))) { return ERROR_EOB_FLUSHBITS_T; } } /**************************************************************/ /* Get next Macroblock address */ /**************************************************************/ i2_cur_mb_addr++; u1_num_mbs++; u1_num_mbsNby2++; ps_parse_mb_data++; /****************************************************************/ /* Check for End Of Row and other flags that determine when to */ /* do DMA setup for N/2-Mb, Decode for N-Mb, and Transfer for */ /* N-Mb */ /****************************************************************/ u1_num_mbs_next = i2_pic_wdin_mbs - ps_dec->u2_mbx - 1; u1_end_of_row = (!u1_num_mbs_next) && (!(u1_mbaff && (u1_num_mbs & 0x01))); u1_slice_end = (!(uc_more_data_flag || i2_mb_skip_run)); u1_tfr_n_mb = (u1_num_mbs == ps_dec->u1_recon_mb_grp) || u1_end_of_row || u1_slice_end; u1_decode_nmb = u1_tfr_n_mb || u1_slice_end; ps_cur_mb_info->u1_end_of_slice = u1_slice_end; /*u1_dma_nby2mb = u1_decode_nmb || (u1_num_mbsNby2 == ps_dec->u1_recon_mb_grp_pair);*/ //if(u1_dma_nby2mb) if(u1_decode_nmb) { ps_dec->pf_mvpred_ref_tfr_nby2mb(ps_dec, u1_mb_idx, u1_num_mbs); u1_num_mbsNby2 = 0; { ps_parse_mb_data = ps_dec->ps_parse_mb_data; ps_dec->ps_part = ps_dec->ps_parse_part_params; } } /*H264_DEC_DEBUG_PRINT("Pic: %d Mb_X=%d Mb_Y=%d", ps_slice->i4_poc >> ps_slice->u1_field_pic_flag, ps_dec->u2_mbx,ps_dec->u2_mby + (1 - ps_cur_mb_info->u1_topmb)); H264_DEC_DEBUG_PRINT("u1_decode_nmb: %d", u1_decode_nmb);*/ if(u1_decode_nmb) { if(ps_dec->u1_separate_parse) { ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); ps_dec->ps_nmb_info += u1_num_mbs; } else { ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); } ps_dec->u2_total_mbs_coded += u1_num_mbs; if(u1_tfr_n_mb) u1_num_mbs = 0; u1_mb_idx = u1_num_mbs; ps_dec->u1_mb_idx = u1_num_mbs; } //ps_dec->ps_pred++; } ps_dec->u4_num_mbs_cur_nmb = 0; ps_dec->ps_cur_slice->u4_mbs_in_slice = i2_cur_mb_addr - (u2_first_mb_in_slice << u1_mbaff); return ret; } WORD32 ih264d_mark_err_slice_skip(dec_struct_t * ps_dec, WORD32 num_mb_skip, UWORD8 u1_is_idr_slice, UWORD16 u2_frame_num, pocstruct_t *ps_cur_poc, WORD32 prev_slice_err) { WORD32 i2_cur_mb_addr; UWORD32 u1_num_mbs, u1_num_mbsNby2; UWORD32 u1_mb_idx = ps_dec->u1_mb_idx; UWORD32 i2_mb_skip_run; UWORD32 u1_num_mbs_next, u1_end_of_row; const UWORD32 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; UWORD32 u1_slice_end; UWORD32 u1_tfr_n_mb; UWORD32 u1_decode_nmb; dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; dec_slice_params_t * ps_slice = ps_dec->ps_cur_slice; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; deblk_mb_t *ps_cur_deblk_mb; dec_mb_info_t *ps_cur_mb_info; parse_pmbarams_t *ps_parse_mb_data; UWORD32 u1_inter_mb_type; UWORD32 u1_deblk_mb_type; UWORD16 u2_total_mbs_coded; UWORD32 u1_mbaff; parse_part_params_t *ps_part_info; WORD32 ret; UNUSED(u1_is_idr_slice); if(ps_dec->ps_dec_err_status->u1_err_flag & REJECT_CUR_PIC) { ih264d_err_pic_dispbuf_mgr(ps_dec); return 0; } if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag && (num_mb_skip & 1)) { num_mb_skip++; } ps_dec->ps_dpb_cmds->u1_long_term_reference_flag = 0; if(prev_slice_err == 1) { /* first slice - missing/header corruption */ ps_dec->ps_cur_slice->u2_frame_num = u2_frame_num; { WORD32 i, j, poc = 0; ps_dec->ps_cur_slice->u2_first_mb_in_slice = 0; ps_dec->pf_mvpred = ih264d_mvpred_nonmbaff; ps_dec->p_form_mb_part_info = ih264d_form_mb_part_info_bp; ps_dec->p_motion_compensate = ih264d_motion_compensate_bp; if(ps_dec->ps_cur_pic != NULL) { poc = ps_dec->ps_cur_pic->i4_poc; if (poc <= INT32_MAX - 2) poc += 2; } j = -1; for(i = 0; i < MAX_NUM_PIC_PARAMS; i++) { if(ps_dec->ps_pps[i].u1_is_valid == TRUE) { if(ps_dec->ps_pps[i].ps_sps->u1_is_valid == TRUE) { j = i; break; } } } //if valid SPS PPS is not found return error if(j == -1) { return ERROR_INV_SLICE_HDR_T; } /* call ih264d_start_of_pic only if it was not called earlier*/ if(ps_dec->u4_pic_buf_got == 0) { //initialize slice params required by ih264d_start_of_pic to valid values ps_dec->ps_cur_slice->u1_slice_type = P_SLICE; ps_dec->ps_cur_slice->u1_nal_ref_idc = 1; ps_dec->ps_cur_slice->u1_nal_unit_type = 1; ret = ih264d_start_of_pic(ps_dec, poc, ps_cur_poc, ps_dec->ps_cur_slice->u2_frame_num, &ps_dec->ps_pps[j]); if(ret != OK) { return ret; } } ps_dec->ps_ref_pic_buf_lx[0][0]->u1_pic_buf_id = 0; ps_dec->u4_output_present = 0; { ih264d_get_next_display_field(ps_dec, ps_dec->ps_out_buffer, &(ps_dec->s_disp_op)); /* If error code is non-zero then there is no buffer available for display, hence avoid format conversion */ if(0 != ps_dec->s_disp_op.u4_error_code) { ps_dec->u4_fmt_conv_cur_row = ps_dec->s_disp_frame_info.u4_y_ht; } else ps_dec->u4_output_present = 1; } if(ps_dec->u1_separate_parse == 1) { if(ps_dec->u4_dec_thread_created == 0) { ithread_create(ps_dec->pv_dec_thread_handle, NULL, (void *)ih264d_decode_picture_thread, (void *)ps_dec); ps_dec->u4_dec_thread_created = 1; } if((ps_dec->u4_num_cores == 3) && ((ps_dec->u4_app_disable_deblk_frm == 0) || ps_dec->i1_recon_in_thread3_flag) && (ps_dec->u4_bs_deblk_thread_created == 0)) { ps_dec->u4_start_recon_deblk = 0; ithread_create(ps_dec->pv_bs_deblk_thread_handle, NULL, (void *)ih264d_recon_deblk_thread, (void *)ps_dec); ps_dec->u4_bs_deblk_thread_created = 1; } } } } else { // Middle / last slice dec_slice_struct_t *ps_parse_cur_slice; ps_parse_cur_slice = ps_dec->ps_dec_slice_buf + ps_dec->u2_cur_slice_num; if(ps_dec->u1_slice_header_done && ps_parse_cur_slice == ps_dec->ps_parse_cur_slice) { // Slice data corrupted // in the case of mbaff, conceal from the even mb. if((ps_dec->ps_cur_slice->u1_mbaff_frame_flag) && (ps_dec->u4_num_mbs_cur_nmb & 1)) { ps_dec->u4_num_mbs_cur_nmb = ps_dec->u4_num_mbs_cur_nmb - 1; ps_dec->u2_cur_mb_addr--; } u1_num_mbs = ps_dec->u4_num_mbs_cur_nmb; if(u1_num_mbs) { ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs - 1; } else { if(ps_dec->u1_separate_parse) { ps_cur_mb_info = ps_dec->ps_nmb_info; } else { ps_cur_mb_info = ps_dec->ps_nmb_info + ps_dec->u4_num_mbs_prev_nmb - 1; } } ps_dec->u2_mby = ps_cur_mb_info->u2_mby; ps_dec->u2_mbx = ps_cur_mb_info->u2_mbx; ps_dec->u1_mb_ngbr_availablity = ps_cur_mb_info->u1_mb_ngbr_availablity; if(u1_num_mbs) { // Going back 1 mb ps_dec->pv_parse_tu_coeff_data = ps_dec->pv_prev_mb_parse_tu_coeff_data; ps_dec->u2_cur_mb_addr--; ps_dec->i4_submb_ofst -= SUB_BLK_SIZE; // Parse/decode N-MB left unparsed if (ps_dec->u1_pr_sl_type == P_SLICE || ps_dec->u1_pr_sl_type == B_SLICE) { ps_dec->pf_mvpred_ref_tfr_nby2mb(ps_dec, u1_mb_idx, u1_num_mbs); ps_dec->ps_part = ps_dec->ps_parse_part_params; } u1_num_mbs_next = i2_pic_wdin_mbs - ps_dec->u2_mbx - 1; u1_end_of_row = (!u1_num_mbs_next) && (!(ps_dec->ps_cur_slice->u1_mbaff_frame_flag && (u1_num_mbs & 0x01))); u1_slice_end = 1; u1_tfr_n_mb = 1; ps_cur_mb_info->u1_end_of_slice = u1_slice_end; if(ps_dec->u1_separate_parse) { ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); ps_dec->ps_nmb_info += u1_num_mbs; } else { ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); } ps_dec->u2_total_mbs_coded += u1_num_mbs; ps_dec->u1_mb_idx = 0; ps_dec->u4_num_mbs_cur_nmb = 0; } if(ps_dec->u2_total_mbs_coded >= ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) { ps_dec->u1_pic_decode_done = 1; return 0; } /* Inserting new slice only if the current slice has atleast 1 MB*/ if(ps_dec->ps_parse_cur_slice->u4_first_mb_in_slice < (UWORD32)(ps_dec->u2_total_mbs_coded >> ps_slice->u1_mbaff_frame_flag)) { ps_dec->i2_prev_slice_mbx = ps_dec->u2_mbx; ps_dec->i2_prev_slice_mby = ps_dec->u2_mby; ps_dec->u2_cur_slice_num++; ps_dec->ps_parse_cur_slice++; } } else { // Slice missing / header corrupted ps_dec->ps_parse_cur_slice = ps_dec->ps_dec_slice_buf + ps_dec->u2_cur_slice_num; } } /******************************************************/ /* Initializations to new slice */ /******************************************************/ { WORD32 num_entries; WORD32 size; UWORD8 *pu1_buf; num_entries = MAX_FRAMES; if((1 >= ps_dec->ps_cur_sps->u1_num_ref_frames) && (0 == ps_dec->i4_display_delay)) { num_entries = 1; } num_entries = ((2 * num_entries) + 1); num_entries *= 2; size = num_entries * sizeof(void *); size += PAD_MAP_IDX_POC * sizeof(void *); pu1_buf = (UWORD8 *)ps_dec->pv_map_ref_idx_to_poc_buf; pu1_buf += size * ps_dec->u2_cur_slice_num; ps_dec->ps_parse_cur_slice->ppv_map_ref_idx_to_poc = (volatile void **)pu1_buf; } u1_mbaff = ps_slice->u1_mbaff_frame_flag; ps_dec->ps_cur_slice->u2_first_mb_in_slice = ps_dec->u2_total_mbs_coded >> u1_mbaff; ps_dec->ps_cur_slice->i1_slice_alpha_c0_offset = 0; ps_dec->ps_cur_slice->i1_slice_beta_offset = 0; if(ps_dec->ps_cur_slice->u1_field_pic_flag) ps_dec->u2_prv_frame_num = ps_dec->ps_cur_slice->u2_frame_num; ps_dec->ps_parse_cur_slice->u4_first_mb_in_slice = ps_dec->u2_total_mbs_coded >> u1_mbaff; ps_dec->ps_parse_cur_slice->u2_log2Y_crwd = ps_dec->ps_cur_slice->u2_log2Y_crwd; if(ps_dec->u1_separate_parse) { ps_dec->ps_parse_cur_slice->pv_tu_coeff_data_start = ps_dec->pv_parse_tu_coeff_data; } else { ps_dec->pv_proc_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; } /******************************************************/ /* Initializations specific to P slice */ /******************************************************/ u1_inter_mb_type = P_MB; u1_deblk_mb_type = D_INTER_MB; ps_dec->ps_cur_slice->u1_slice_type = P_SLICE; ps_dec->ps_parse_cur_slice->slice_type = P_SLICE; ps_dec->pf_mvpred_ref_tfr_nby2mb = ih264d_mv_pred_ref_tfr_nby2_pmb; ps_dec->ps_part = ps_dec->ps_parse_part_params; ps_dec->u2_mbx = (MOD(ps_dec->ps_cur_slice->u2_first_mb_in_slice - 1, ps_dec->u2_frm_wd_in_mbs)); ps_dec->u2_mby = (DIV(ps_dec->ps_cur_slice->u2_first_mb_in_slice - 1, ps_dec->u2_frm_wd_in_mbs)); ps_dec->u2_mby <<= u1_mbaff; /******************************************************/ /* Parsing / decoding the slice */ /******************************************************/ ps_dec->u1_slice_header_done = 2; ps_dec->u1_qp = ps_slice->u1_slice_qp; ih264d_update_qp(ps_dec, 0); u1_mb_idx = ps_dec->u1_mb_idx; ps_parse_mb_data = ps_dec->ps_parse_mb_data; u1_num_mbs = u1_mb_idx; u1_slice_end = 0; u1_tfr_n_mb = 0; u1_decode_nmb = 0; u1_num_mbsNby2 = 0; i2_cur_mb_addr = ps_dec->u2_total_mbs_coded; i2_mb_skip_run = num_mb_skip; while(!u1_slice_end) { UWORD8 u1_mb_type; if(i2_cur_mb_addr > ps_dec->ps_cur_sps->u2_max_mb_addr) break; ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs; ps_dec->u4_num_mbs_cur_nmb = u1_num_mbs; ps_cur_mb_info->u1_Mux = 0; ps_dec->u4_num_pmbair = (u1_num_mbs >> u1_mbaff); ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_num_mbs; ps_cur_mb_info->u1_end_of_slice = 0; /* Storing Default partition info */ ps_parse_mb_data->u1_num_part = 1; ps_parse_mb_data->u1_isI_mb = 0; /**************************************************************/ /* Get the required information for decoding of MB */ /**************************************************************/ /* mb_x, mb_y, neighbor availablity, */ if (u1_mbaff) ih264d_get_mb_info_cavlc_mbaff(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, i2_mb_skip_run); else ih264d_get_mb_info_cavlc_nonmbaff(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, i2_mb_skip_run); /* Set the deblocking parameters for this MB */ if(ps_dec->u4_app_disable_deblk_frm == 0) { ih264d_set_deblocking_parameters(ps_cur_deblk_mb, ps_slice, ps_dec->u1_mb_ngbr_availablity, ps_dec->u1_cur_mb_fld_dec_flag); } /* Set appropriate flags in ps_cur_mb_info and ps_dec */ ps_dec->i1_prev_mb_qp_delta = 0; ps_dec->u1_sub_mb_num = 0; ps_cur_mb_info->u1_mb_type = MB_SKIP; ps_cur_mb_info->u1_mb_mc_mode = PRED_16x16; ps_cur_mb_info->u1_cbp = 0; /* Storing Skip partition info */ ps_part_info = ps_dec->ps_part; ps_part_info->u1_is_direct = PART_DIRECT_16x16; ps_part_info->u1_sub_mb_num = 0; ps_dec->ps_part++; /* Update Nnzs */ ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CAVLC); ps_cur_mb_info->ps_curmb->u1_mb_type = u1_inter_mb_type; ps_cur_deblk_mb->u1_mb_type |= u1_deblk_mb_type; i2_mb_skip_run--; ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; if (u1_mbaff) { ih264d_update_mbaff_left_nnz(ps_dec, ps_cur_mb_info); } /**************************************************************/ /* Get next Macroblock address */ /**************************************************************/ i2_cur_mb_addr++; u1_num_mbs++; u1_num_mbsNby2++; ps_parse_mb_data++; /****************************************************************/ /* Check for End Of Row and other flags that determine when to */ /* do DMA setup for N/2-Mb, Decode for N-Mb, and Transfer for */ /* N-Mb */ /****************************************************************/ u1_num_mbs_next = i2_pic_wdin_mbs - ps_dec->u2_mbx - 1; u1_end_of_row = (!u1_num_mbs_next) && (!(u1_mbaff && (u1_num_mbs & 0x01))); u1_slice_end = !i2_mb_skip_run; u1_tfr_n_mb = (u1_num_mbs == ps_dec->u1_recon_mb_grp) || u1_end_of_row || u1_slice_end; u1_decode_nmb = u1_tfr_n_mb || u1_slice_end; ps_cur_mb_info->u1_end_of_slice = u1_slice_end; if(u1_decode_nmb) { ps_dec->pf_mvpred_ref_tfr_nby2mb(ps_dec, u1_mb_idx, u1_num_mbs); u1_num_mbsNby2 = 0; ps_parse_mb_data = ps_dec->ps_parse_mb_data; ps_dec->ps_part = ps_dec->ps_parse_part_params; if(ps_dec->u1_separate_parse) { ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); ps_dec->ps_nmb_info += u1_num_mbs; } else { ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); } ps_dec->u2_total_mbs_coded += u1_num_mbs; if(u1_tfr_n_mb) u1_num_mbs = 0; u1_mb_idx = u1_num_mbs; ps_dec->u1_mb_idx = u1_num_mbs; } } ps_dec->u4_num_mbs_cur_nmb = 0; ps_dec->ps_cur_slice->u4_mbs_in_slice = i2_cur_mb_addr - ps_dec->ps_parse_cur_slice->u4_first_mb_in_slice; H264_DEC_DEBUG_PRINT("Mbs in slice: %d\n", ps_dec->ps_cur_slice->u4_mbs_in_slice); /* incremented here only if first slice is inserted */ if(ps_dec->u4_first_slice_in_pic != 0) { ps_dec->ps_parse_cur_slice++; ps_dec->u2_cur_slice_num++; } ps_dec->i2_prev_slice_mbx = ps_dec->u2_mbx; ps_dec->i2_prev_slice_mby = ps_dec->u2_mby; if(ps_dec->u2_total_mbs_coded >= ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) { ps_dec->u1_pic_decode_done = 1; } return 0; } /*! ************************************************************************** * \if Function name : ih264d_decode_pslice \endif * * \brief * Decodes a P Slice * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_pslice(dec_struct_t *ps_dec, UWORD16 u2_first_mb_in_slice) { dec_pic_params_t * ps_pps = ps_dec->ps_cur_pps; dec_slice_params_t * ps_cur_slice = ps_dec->ps_cur_slice; dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; //ps_dec->ps_cur_sps->u1_mb_aff_flag; UWORD8 u1_field_pic_flag = ps_cur_slice->u1_field_pic_flag; UWORD64 u8_ref_idx_l0; UWORD32 u4_temp; WORD32 i_temp; WORD32 ret; /*--------------------------------------------------------------------*/ /* Read remaining contents of the slice header */ /*--------------------------------------------------------------------*/ { WORD8 *pi1_buf; WORD16 *pi2_mv = ps_dec->s_default_mv_pred.i2_mv; WORD32 *pi4_mv = (WORD32*)pi2_mv; WORD16 *pi16_refFrame; pi1_buf = ps_dec->s_default_mv_pred.i1_ref_frame; pi16_refFrame = (WORD16*)pi1_buf; *pi4_mv = 0; *(pi4_mv + 1) = 0; *pi16_refFrame = OUT_OF_RANGE_REF; ps_dec->s_default_mv_pred.u1_col_ref_pic_idx = (UWORD8)-1; ps_dec->s_default_mv_pred.u1_pic_type = (UWORD8)-1; } ps_cur_slice->u1_num_ref_idx_active_override_flag = ih264d_get_bit_h264( ps_bitstrm); COPYTHECONTEXT("SH: num_ref_idx_override_flag", ps_cur_slice->u1_num_ref_idx_active_override_flag); u8_ref_idx_l0 = ps_dec->ps_cur_pps->u1_num_ref_idx_lx_active[0]; if(ps_cur_slice->u1_num_ref_idx_active_override_flag) { u8_ref_idx_l0 = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf) + (UWORD64)1; } { UWORD8 u1_max_ref_idx = H264_MAX_REF_PICS << u1_field_pic_flag; if(u8_ref_idx_l0 > u1_max_ref_idx) { return ERROR_NUM_REF; } ps_cur_slice->u1_num_ref_idx_lx_active[0] = u8_ref_idx_l0; COPYTHECONTEXT("SH: num_ref_idx_l0_active_minus1", ps_cur_slice->u1_num_ref_idx_lx_active[0] - 1); } { UWORD8 uc_refIdxReFlagL0 = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("SH: ref_pic_list_reordering_flag_l0",uc_refIdxReFlagL0); ih264d_init_ref_idx_lx_p(ps_dec); /* Store the value for future slices in the same picture */ ps_dec->u1_num_ref_idx_lx_active_prev = ps_cur_slice->u1_num_ref_idx_lx_active[0]; /* Modified temporarily */ if(uc_refIdxReFlagL0) { WORD8 ret; ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_mod_dpb[0]; ret = ih264d_ref_idx_reordering(ps_dec, 0); if(ret == -1) return ERROR_REFIDX_ORDER_T; ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_mod_dpb[0]; } else ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_init_dpb[0]; } /* Create refIdx to POC mapping */ { void **pui_map_ref_idx_to_poc_lx0, **pui_map_ref_idx_to_poc_lx1; WORD8 idx; struct pic_buffer_t *ps_pic; pui_map_ref_idx_to_poc_lx0 = ps_dec->ppv_map_ref_idx_to_poc + FRM_LIST_L0; pui_map_ref_idx_to_poc_lx0[0] = 0; //For ref_idx = -1 pui_map_ref_idx_to_poc_lx0++; for(idx = 0; idx < ps_cur_slice->u1_num_ref_idx_lx_active[0]; idx++) { ps_pic = ps_dec->ps_ref_pic_buf_lx[0][idx]; pui_map_ref_idx_to_poc_lx0[idx] = (ps_pic->pu1_buf1); } /* Bug Fix Deblocking */ pui_map_ref_idx_to_poc_lx1 = ps_dec->ppv_map_ref_idx_to_poc + FRM_LIST_L1; pui_map_ref_idx_to_poc_lx1[0] = 0; if(u1_mbaff) { void **ppv_map_ref_idx_to_poc_lx_t, **ppv_map_ref_idx_to_poc_lx_b; void **ppv_map_ref_idx_to_poc_lx_t1, **ppv_map_ref_idx_to_poc_lx_b1; ppv_map_ref_idx_to_poc_lx_t = ps_dec->ppv_map_ref_idx_to_poc + TOP_LIST_FLD_L0; ppv_map_ref_idx_to_poc_lx_b = ps_dec->ppv_map_ref_idx_to_poc + BOT_LIST_FLD_L0; ppv_map_ref_idx_to_poc_lx_t[0] = 0; // For ref_idx = -1 ppv_map_ref_idx_to_poc_lx_t++; ppv_map_ref_idx_to_poc_lx_b[0] = 0; // For ref_idx = -1 ppv_map_ref_idx_to_poc_lx_b++; idx = 0; for(idx = 0; idx < ps_cur_slice->u1_num_ref_idx_lx_active[0]; idx++) { ps_pic = ps_dec->ps_ref_pic_buf_lx[0][idx]; ppv_map_ref_idx_to_poc_lx_t[0] = (ps_pic->pu1_buf1); ppv_map_ref_idx_to_poc_lx_b[1] = (ps_pic->pu1_buf1); ppv_map_ref_idx_to_poc_lx_b[0] = (ps_pic->pu1_buf1) + 1; ppv_map_ref_idx_to_poc_lx_t[1] = (ps_pic->pu1_buf1) + 1; ppv_map_ref_idx_to_poc_lx_t += 2; ppv_map_ref_idx_to_poc_lx_b += 2; } ppv_map_ref_idx_to_poc_lx_t1 = ps_dec->ppv_map_ref_idx_to_poc + TOP_LIST_FLD_L1; ppv_map_ref_idx_to_poc_lx_t1[0] = 0; ppv_map_ref_idx_to_poc_lx_b1 = ps_dec->ppv_map_ref_idx_to_poc + BOT_LIST_FLD_L1; ppv_map_ref_idx_to_poc_lx_b1[0] = 0; } if(ps_dec->u4_num_cores >= 3) { WORD32 num_entries; WORD32 size; num_entries = MAX_FRAMES; if((1 >= ps_dec->ps_cur_sps->u1_num_ref_frames) && (0 == ps_dec->i4_display_delay)) { num_entries = 1; } num_entries = ((2 * num_entries) + 1); num_entries *= 2; size = num_entries * sizeof(void *); size += PAD_MAP_IDX_POC * sizeof(void *); memcpy((void *)ps_dec->ps_parse_cur_slice->ppv_map_ref_idx_to_poc, ps_dec->ppv_map_ref_idx_to_poc, size); } } if(ps_pps->u1_wted_pred_flag) { ret = ih264d_parse_pred_weight_table(ps_cur_slice, ps_bitstrm); if(ret != OK) return ret; ih264d_form_pred_weight_matrix(ps_dec); ps_dec->pu4_wt_ofsts = ps_dec->pu4_wts_ofsts_mat; } else { ps_dec->ps_cur_slice->u2_log2Y_crwd = 0; ps_dec->pu4_wt_ofsts = ps_dec->pu4_wts_ofsts_mat; } ps_dec->ps_parse_cur_slice->u2_log2Y_crwd = ps_dec->ps_cur_slice->u2_log2Y_crwd; if(u1_mbaff && (u1_field_pic_flag == 0)) { ih264d_convert_frm_mbaff_list(ps_dec); } /* G050 */ if(ps_cur_slice->u1_nal_ref_idc != 0) { if(!ps_dec->ps_dpb_cmds->u1_dpb_commands_read) { i_temp = ih264d_read_mmco_commands(ps_dec); if (i_temp < 0) { return ERROR_DBP_MANAGER_T; } ps_dec->u4_bitoffset = i_temp; } else ps_bitstrm->u4_ofst += ps_dec->u4_bitoffset; } /* G050 */ if(ps_pps->u1_entropy_coding_mode == CABAC) { u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp > MAX_CABAC_INIT_IDC) { return ERROR_INV_SLICE_HDR_T; } ps_cur_slice->u1_cabac_init_idc = u4_temp; COPYTHECONTEXT("SH: cabac_init_idc",ps_cur_slice->u1_cabac_init_idc); } /* Read slice_qp_delta */ WORD64 i8_temp = (WORD64)ps_pps->u1_pic_init_qp + ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i8_temp < MIN_H264_QP) || (i8_temp > MAX_H264_QP)) { return ERROR_INV_RANGE_QP_T; } ps_cur_slice->u1_slice_qp = i8_temp; COPYTHECONTEXT("SH: slice_qp_delta", (WORD8)(ps_cur_slice->u1_slice_qp - ps_pps->u1_pic_init_qp)); if(ps_pps->u1_deblocking_filter_parameters_present_flag == 1) { u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp > SLICE_BOUNDARY_DBLK_DISABLED) { return ERROR_INV_SLICE_HDR_T; } COPYTHECONTEXT("SH: disable_deblocking_filter_idc", u4_temp); ps_cur_slice->u1_disable_dblk_filter_idc = u4_temp; if(u4_temp != 1) { i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) << 1; if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) { return ERROR_INV_SLICE_HDR_T; } ps_cur_slice->i1_slice_alpha_c0_offset = i_temp; COPYTHECONTEXT("SH: slice_alpha_c0_offset_div2", ps_cur_slice->i1_slice_alpha_c0_offset >> 1); i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) << 1; if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) { return ERROR_INV_SLICE_HDR_T; } ps_cur_slice->i1_slice_beta_offset = i_temp; COPYTHECONTEXT("SH: slice_beta_offset_div2", ps_cur_slice->i1_slice_beta_offset >> 1); } else { ps_cur_slice->i1_slice_alpha_c0_offset = 0; ps_cur_slice->i1_slice_beta_offset = 0; } } else { ps_cur_slice->u1_disable_dblk_filter_idc = 0; ps_cur_slice->i1_slice_alpha_c0_offset = 0; ps_cur_slice->i1_slice_beta_offset = 0; } ps_dec->u1_slice_header_done = 2; if(ps_pps->u1_entropy_coding_mode) { SWITCHOFFTRACE; SWITCHONTRACECABAC; ps_dec->pf_parse_inter_slice = ih264d_parse_inter_slice_data_cabac; ps_dec->pf_parse_inter_mb = ih264d_parse_pmb_cabac; ih264d_init_cabac_contexts(P_SLICE, ps_dec); if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_mbaff; else ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_nonmbaff; } else { SWITCHONTRACE; SWITCHOFFTRACECABAC; ps_dec->pf_parse_inter_slice = ih264d_parse_inter_slice_data_cavlc; ps_dec->pf_parse_inter_mb = ih264d_parse_pmb_cavlc; if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) { ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_mbaff; } else ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_nonmbaff; } ps_dec->u1_B = 0; ps_dec->pf_mvpred_ref_tfr_nby2mb = ih264d_mv_pred_ref_tfr_nby2_pmb; ret = ps_dec->pf_parse_inter_slice(ps_dec, ps_cur_slice, u2_first_mb_in_slice); if(ret != OK) return ret; // ps_dec->curr_slice_in_error = 0 ; return OK; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_slice.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_parse_slice.c * * \brief * Contains routines that decodes a slice NAL unit * * \date * 19/12/2002 * * \author AI ************************************************************************** */ #include <string.h> #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ithread.h" #include "ih264d_structs.h" #include "ih264d_debug.h" #include "ih264d_bitstrm.h" #include "ih264d_parse_mb_header.h" #include "ih264d_process_bslice.h" #include "ih264d_process_pslice.h" #include "ih264d_parse_cavlc.h" #include "ih264d_utils.h" #include "ih264d_deblocking.h" #include "ih264d_defs.h" #include "ih264d_error_handler.h" #include "ih264d_tables.h" #include "ih264d_defs.h" #include "ih264d_mem_request.h" #include "ih264d_parse_islice.h" #include "ih264d_parse_slice.h" #include "ih264d_mvpred.h" #include "ih264d_mb_utils.h" #include "ih264d_defs.h" #include "ih264d_quant_scaling.h" #include "ih264d_inter_pred.h" #include "ih264d_sei.h" #include "ih264d.h" #include "ih264_error.h" #include "ih264_disp_mgr.h" #include "ih264_buf_mgr.h" #include "ih264d_thread_parse_decode.h" #include "ih264d_thread_compute_bs.h" #include "ih264d_dpb_manager.h" #include <assert.h> #include "ih264d_parse_islice.h" #define RET_LAST_SKIP 0x80000000 WORD32 check_app_out_buf_size(dec_struct_t *ps_dec); /*! ************************************************************************** * \if Function name : ih264d_form_pred_weight_matrix \endif * * \brief * Forms pred weight matrix. * * \return * None * ************************************************************************** */ void ih264d_form_pred_weight_matrix(dec_struct_t *ps_dec) { dec_slice_params_t *ps_cur_slice; UWORD8 uc_num_ref_idx_l0_active, uc_num_ref_idx_l1_active; UWORD8 i, j; UWORD32 *pu4_mat_iwt_ofst; UWORD16 i2_idx; UWORD32 *pui32_weight_offset_l0, *pui32_weight_offset_l1; UWORD32 u4_temp; ps_cur_slice = ps_dec->ps_cur_slice; uc_num_ref_idx_l0_active = ps_cur_slice->u1_num_ref_idx_lx_active[0]; uc_num_ref_idx_l1_active = ps_cur_slice->u1_num_ref_idx_lx_active[1]; pu4_mat_iwt_ofst = ps_dec->pu4_wts_ofsts_mat; if(ps_cur_slice->u1_slice_type == B_SLICE) { for(i = 0; i < uc_num_ref_idx_l0_active; i++) { pui32_weight_offset_l0 = ps_cur_slice->u4_wt_ofst_lx[0][i]; for(j = 0; j < uc_num_ref_idx_l1_active; j++) { pui32_weight_offset_l1 = ps_cur_slice->u4_wt_ofst_lx[1][j]; i2_idx = i * uc_num_ref_idx_l0_active + j; i2_idx = X3(i2_idx); /* u4_temp = (pui32_weight_offset_l0[0] | (pui32_weight_offset_l1[0] << 16)); pu4_mat_iwt_ofst[0] = u4_temp; u4_temp = (pui32_weight_offset_l0[1] | (pui32_weight_offset_l1[1] << 16)); pu4_mat_iwt_ofst[1] = u4_temp; u4_temp = (pui32_weight_offset_l0[2] | (pui32_weight_offset_l1[2] << 16)); pu4_mat_iwt_ofst[2] = u4_temp; pu4_mat_iwt_ofst += 3;*/ pu4_mat_iwt_ofst[0] = pui32_weight_offset_l0[0]; pu4_mat_iwt_ofst[1] = pui32_weight_offset_l1[0]; pu4_mat_iwt_ofst[2] = pui32_weight_offset_l0[1]; pu4_mat_iwt_ofst[3] = pui32_weight_offset_l1[1]; pu4_mat_iwt_ofst[4] = pui32_weight_offset_l0[2]; pu4_mat_iwt_ofst[5] = pui32_weight_offset_l1[2]; pu4_mat_iwt_ofst += 6; } } } else { for(i = 0; i < uc_num_ref_idx_l0_active; i++) { pui32_weight_offset_l0 = ps_cur_slice->u4_wt_ofst_lx[0][i]; i2_idx = X3(i); u4_temp = (UWORD32)pui32_weight_offset_l0[0]; pu4_mat_iwt_ofst[0] = u4_temp; u4_temp = (UWORD32)pui32_weight_offset_l0[1]; pu4_mat_iwt_ofst[2] = u4_temp; u4_temp = (UWORD32)pui32_weight_offset_l0[2]; pu4_mat_iwt_ofst[4] = u4_temp; pu4_mat_iwt_ofst += 6; } } } /*! ************************************************************************** * \if Function name : init_firstSliceParam \endif * * \brief * Initialize the Parameter required for all the slices for a picture * * \return : Nothing * ************************************************************************** */ WORD32 ih264d_start_of_pic(dec_struct_t *ps_dec, WORD32 i4_poc, pocstruct_t *ps_temp_poc, UWORD16 u2_frame_num, dec_pic_params_t *ps_pps) { pocstruct_t *ps_prev_poc = &ps_dec->s_cur_pic_poc; pocstruct_t *ps_cur_poc = ps_temp_poc; pic_buffer_t *pic_buf; ivd_video_decode_op_t * ps_dec_output = (ivd_video_decode_op_t *)ps_dec->pv_dec_out; dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; dec_seq_params_t *ps_seq = ps_pps->ps_sps; UWORD8 u1_bottom_field_flag = ps_cur_slice->u1_bottom_field_flag; UWORD8 u1_field_pic_flag = ps_cur_slice->u1_field_pic_flag; /* high profile related declarations */ high_profile_tools_t s_high_profile; WORD32 ret; H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); /* check output buffer size given by the application */ if(check_app_out_buf_size(ps_dec) != IV_SUCCESS) return IVD_DISP_FRM_ZERO_OP_BUF_SIZE; ps_prev_poc->i4_pic_order_cnt_lsb = ps_cur_poc->i4_pic_order_cnt_lsb; ps_prev_poc->i4_pic_order_cnt_msb = ps_cur_poc->i4_pic_order_cnt_msb; ps_prev_poc->i4_delta_pic_order_cnt_bottom = ps_cur_poc->i4_delta_pic_order_cnt_bottom; ps_prev_poc->i4_delta_pic_order_cnt[0] = ps_cur_poc->i4_delta_pic_order_cnt[0]; ps_prev_poc->i4_delta_pic_order_cnt[1] = ps_cur_poc->i4_delta_pic_order_cnt[1]; ps_prev_poc->u1_bot_field = ps_dec->ps_cur_slice->u1_bottom_field_flag; ps_prev_poc->i4_prev_frame_num_ofst = ps_cur_poc->i4_prev_frame_num_ofst; ps_prev_poc->u2_frame_num = u2_frame_num; ps_dec->i1_prev_mb_qp_delta = 0; ps_dec->i1_next_ctxt_idx = 0; ps_dec->u4_nmb_deblk = 0; if(ps_dec->u4_num_cores == 1) ps_dec->u4_nmb_deblk = 1; if(ps_seq->u1_mb_aff_flag == 1) { ps_dec->u4_nmb_deblk = 0; if(ps_dec->u4_num_cores > 2) ps_dec->u4_num_cores = 2; } ps_dec->u4_use_intrapred_line_copy = 0; if (ps_seq->u1_mb_aff_flag == 0) { ps_dec->u4_use_intrapred_line_copy = 1; } ps_dec->u4_app_disable_deblk_frm = 0; /* If degrade is enabled, set the degrade flags appropriately */ if(ps_dec->i4_degrade_type && ps_dec->i4_degrade_pics) { WORD32 degrade_pic; ps_dec->i4_degrade_pic_cnt++; degrade_pic = 0; /* If degrade is to be done in all frames, then do not check further */ switch(ps_dec->i4_degrade_pics) { case 4: { degrade_pic = 1; break; } case 3: { if(ps_cur_slice->u1_slice_type != I_SLICE) degrade_pic = 1; break; } case 2: { /* If pic count hits non-degrade interval or it is an islice, then do not degrade */ if((ps_cur_slice->u1_slice_type != I_SLICE) && (ps_dec->i4_degrade_pic_cnt != ps_dec->i4_nondegrade_interval)) degrade_pic = 1; break; } case 1: { /* Check if the current picture is non-ref */ if(0 == ps_cur_slice->u1_nal_ref_idc) { degrade_pic = 1; } break; } } if(degrade_pic) { if(ps_dec->i4_degrade_type & 0x2) ps_dec->u4_app_disable_deblk_frm = 1; /* MC degrading is done only for non-ref pictures */ if(0 == ps_cur_slice->u1_nal_ref_idc) { if(ps_dec->i4_degrade_type & 0x4) ps_dec->i4_mv_frac_mask = 0; if(ps_dec->i4_degrade_type & 0x8) ps_dec->i4_mv_frac_mask = 0; } } else ps_dec->i4_degrade_pic_cnt = 0; } { dec_err_status_t * ps_err = ps_dec->ps_dec_err_status; if((ps_cur_slice->u1_slice_type == I_SLICE) || (ps_cur_slice->u1_slice_type == SI_SLICE)) ps_err->u1_cur_pic_type = PIC_TYPE_I; else ps_err->u1_cur_pic_type = PIC_TYPE_UNKNOWN; if(ps_err->u1_pic_aud_i == PIC_TYPE_I) { ps_err->u1_cur_pic_type = PIC_TYPE_I; ps_err->u1_pic_aud_i = PIC_TYPE_UNKNOWN; } if(ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL) { if(ps_err->u1_err_flag) ih264d_reset_ref_bufs(ps_dec->ps_dpb_mgr); ps_err->u1_err_flag = ACCEPT_ALL_PICS; } } if(ps_dec->u1_init_dec_flag && ps_dec->s_prev_seq_params.u1_eoseq_pending) { /* Reset the decoder picture buffers */ WORD32 j; for(j = 0; j < MAX_DISP_BUFS_NEW; j++) { ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, j, BUF_MGR_REF); ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, ps_dec->au1_pic_buf_id_mv_buf_id_map[j], BUF_MGR_REF); ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, j, BUF_MGR_IO); } /* reset the decoder structure parameters related to buffer handling */ ps_dec->u1_second_field = 0; ps_dec->i4_cur_display_seq = 0; /********************************************************************/ /* indicate in the decoder output i4_status that some frames are being */ /* dropped, so that it resets timestamp and wait for a new sequence */ /********************************************************************/ ps_dec->s_prev_seq_params.u1_eoseq_pending = 0; } ret = ih264d_init_pic(ps_dec, u2_frame_num, i4_poc, ps_pps); if(ret != OK) return ret; ps_dec->pv_parse_tu_coeff_data = ps_dec->pv_pic_tu_coeff_data; ps_dec->pv_proc_tu_coeff_data = ps_dec->pv_pic_tu_coeff_data; ps_dec->ps_nmb_info = ps_dec->ps_frm_mb_info; if(ps_dec->u1_separate_parse) { UWORD16 pic_wd; UWORD16 pic_ht; UWORD32 num_mbs; pic_wd = ps_dec->u2_pic_wd; pic_ht = ps_dec->u2_pic_ht; num_mbs = (pic_wd * pic_ht) >> 8; if(ps_dec->pu1_dec_mb_map) { memset((void *)ps_dec->pu1_dec_mb_map, 0, num_mbs); } if(ps_dec->pu1_recon_mb_map) { memset((void *)ps_dec->pu1_recon_mb_map, 0, num_mbs); } if(ps_dec->pu2_slice_num_map) { memset((void *)ps_dec->pu2_slice_num_map, 0, (num_mbs * sizeof(UWORD16))); } } ps_dec->ps_parse_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); ps_dec->ps_decode_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); ps_dec->ps_computebs_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); ps_dec->u2_cur_slice_num = 0; /* Initialize all the HP toolsets to zero */ ps_dec->s_high_profile.u1_scaling_present = 0; ps_dec->s_high_profile.u1_transform8x8_present = 0; /* Get Next Free Picture */ if(1 == ps_dec->u4_share_disp_buf) { UWORD32 i; /* Free any buffer that is in the queue to be freed */ for(i = 0; i < MAX_DISP_BUFS_NEW; i++) { if(0 == ps_dec->u4_disp_buf_to_be_freed[i]) continue; ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, i, BUF_MGR_IO); ps_dec->u4_disp_buf_to_be_freed[i] = 0; ps_dec->u4_disp_buf_mapping[i] = 0; } } if(!(u1_field_pic_flag && 0 != ps_dec->u1_top_bottom_decoded)) //ps_dec->u1_second_field)) { pic_buffer_t *ps_cur_pic; WORD32 cur_pic_buf_id, cur_mv_buf_id; col_mv_buf_t *ps_col_mv; while(1) { ps_cur_pic = (pic_buffer_t *)ih264_buf_mgr_get_next_free( (buf_mgr_t *)ps_dec->pv_pic_buf_mgr, &cur_pic_buf_id); if(ps_cur_pic == NULL) { ps_dec->i4_error_code = ERROR_UNAVAIL_PICBUF_T; return ERROR_UNAVAIL_PICBUF_T; } if(0 == ps_dec->u4_disp_buf_mapping[cur_pic_buf_id]) { break; } } ps_col_mv = (col_mv_buf_t *)ih264_buf_mgr_get_next_free((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, &cur_mv_buf_id); if(ps_col_mv == NULL) { ps_dec->i4_error_code = ERROR_UNAVAIL_MVBUF_T; return ERROR_UNAVAIL_MVBUF_T; } ps_dec->ps_cur_pic = ps_cur_pic; ps_dec->u1_pic_buf_id = cur_pic_buf_id; ps_cur_pic->u4_ts = ps_dec->u4_ts; memcpy(&ps_cur_pic->s_sei_pic, ps_dec->ps_sei, sizeof(sei)); ps_cur_pic->u1_mv_buf_id = cur_mv_buf_id; ps_dec->au1_pic_buf_id_mv_buf_id_map[cur_pic_buf_id] = cur_mv_buf_id; ps_cur_pic->pu1_col_zero_flag = (UWORD8 *)ps_col_mv->pv_col_zero_flag; ps_cur_pic->ps_mv = (mv_pred_t *)ps_col_mv->pv_mv; ps_dec->au1_pic_buf_ref_flag[cur_pic_buf_id] = 0; { /*make first entry of list0 and list1 point to cur pic, *so that if first slice is in error, ref pic struct will have valid entries*/ ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_init_dpb[0]; ps_dec->ps_ref_pic_buf_lx[1] = ps_dec->ps_dpb_mgr->ps_init_dpb[1]; *(ps_dec->ps_dpb_mgr->ps_init_dpb[0][0]) = *ps_cur_pic; /* Initialize for field reference as well */ *(ps_dec->ps_dpb_mgr->ps_init_dpb[0][MAX_REF_BUFS]) = *ps_cur_pic; *(ps_dec->ps_dpb_mgr->ps_mod_dpb[0][0]) = *ps_cur_pic; /* Initialize for field reference as well */ *(ps_dec->ps_dpb_mgr->ps_mod_dpb[0][MAX_REF_BUFS]) = *ps_cur_pic; *(ps_dec->ps_dpb_mgr->ps_init_dpb[1][0]) = *ps_cur_pic; /* Initialize for field reference as well */ *(ps_dec->ps_dpb_mgr->ps_init_dpb[1][MAX_REF_BUFS]) = *ps_cur_pic; *(ps_dec->ps_dpb_mgr->ps_mod_dpb[1][0]) = *ps_cur_pic; /* Initialize for field reference as well */ *(ps_dec->ps_dpb_mgr->ps_mod_dpb[1][MAX_REF_BUFS]) = *ps_cur_pic; } if(!ps_dec->ps_cur_pic) { WORD32 j; H264_DEC_DEBUG_PRINT("------- Display Buffers Reset --------\n"); for(j = 0; j < MAX_DISP_BUFS_NEW; j++) { ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, j, BUF_MGR_REF); ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, ps_dec->au1_pic_buf_id_mv_buf_id_map[j], BUF_MGR_REF); ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, j, BUF_MGR_IO); } ps_dec->i4_cur_display_seq = 0; ps_dec->i4_prev_max_display_seq = 0; ps_dec->i4_max_poc = 0; ps_cur_pic = (pic_buffer_t *)ih264_buf_mgr_get_next_free( (buf_mgr_t *)ps_dec->pv_pic_buf_mgr, &cur_pic_buf_id); if(ps_cur_pic == NULL) { ps_dec->i4_error_code = ERROR_UNAVAIL_PICBUF_T; return ERROR_UNAVAIL_PICBUF_T; } ps_col_mv = (col_mv_buf_t *)ih264_buf_mgr_get_next_free((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, &cur_mv_buf_id); if(ps_col_mv == NULL) { ps_dec->i4_error_code = ERROR_UNAVAIL_MVBUF_T; return ERROR_UNAVAIL_MVBUF_T; } ps_dec->ps_cur_pic = ps_cur_pic; ps_dec->u1_pic_buf_id = cur_pic_buf_id; ps_cur_pic->u4_ts = ps_dec->u4_ts; ps_dec->apv_buf_id_pic_buf_map[cur_pic_buf_id] = (void *)ps_cur_pic; ps_cur_pic->u1_mv_buf_id = cur_mv_buf_id; ps_dec->au1_pic_buf_id_mv_buf_id_map[cur_pic_buf_id] = cur_mv_buf_id; ps_cur_pic->pu1_col_zero_flag = (UWORD8 *)ps_col_mv->pv_col_zero_flag; ps_cur_pic->ps_mv = (mv_pred_t *)ps_col_mv->pv_mv; ps_dec->au1_pic_buf_ref_flag[cur_pic_buf_id] = 0; } ps_dec->ps_cur_pic->u1_picturetype = u1_field_pic_flag; ps_dec->ps_cur_pic->u4_pack_slc_typ = SKIP_NONE; H264_DEC_DEBUG_PRINT("got a buffer\n"); } else { H264_DEC_DEBUG_PRINT("did not get a buffer\n"); } ps_dec->u4_pic_buf_got = 1; ps_dec->ps_cur_pic->i4_poc = i4_poc; ps_dec->ps_cur_pic->i4_frame_num = u2_frame_num; ps_dec->ps_cur_pic->i4_pic_num = u2_frame_num; ps_dec->ps_cur_pic->i4_top_field_order_cnt = ps_pps->i4_top_field_order_cnt; ps_dec->ps_cur_pic->i4_bottom_field_order_cnt = ps_pps->i4_bottom_field_order_cnt; ps_dec->ps_cur_pic->i4_avg_poc = ps_pps->i4_avg_poc; ps_dec->ps_cur_pic->u4_time_stamp = ps_dec->u4_pts; ps_dec->s_cur_pic = *(ps_dec->ps_cur_pic); if(u1_field_pic_flag && u1_bottom_field_flag) { WORD32 i4_temp_poc; WORD32 i4_top_field_order_poc, i4_bot_field_order_poc; /* Point to odd lines, since it's bottom field */ ps_dec->s_cur_pic.pu1_buf1 += ps_dec->s_cur_pic.u2_frm_wd_y; ps_dec->s_cur_pic.pu1_buf2 += ps_dec->s_cur_pic.u2_frm_wd_uv; ps_dec->s_cur_pic.pu1_buf3 += ps_dec->s_cur_pic.u2_frm_wd_uv; ps_dec->s_cur_pic.ps_mv += ((ps_dec->u2_pic_ht * ps_dec->u2_pic_wd) >> 5); ps_dec->s_cur_pic.pu1_col_zero_flag += ((ps_dec->u2_pic_ht * ps_dec->u2_pic_wd) >> 5); ps_dec->ps_cur_pic->u1_picturetype |= BOT_FLD; i4_top_field_order_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; i4_bot_field_order_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; i4_temp_poc = MIN(i4_top_field_order_poc, i4_bot_field_order_poc); ps_dec->ps_cur_pic->i4_avg_poc = i4_temp_poc; } ps_cur_slice->u1_mbaff_frame_flag = ps_seq->u1_mb_aff_flag && (!u1_field_pic_flag); ps_dec->ps_cur_pic->u1_picturetype |= (ps_cur_slice->u1_mbaff_frame_flag << 2); ps_dec->ps_cur_mb_row = ps_dec->ps_nbr_mb_row; //[0]; //Increment by 2 ,so that left mb (mbaff decrements by 2) will always be valid ps_dec->ps_cur_mb_row += 2; ps_dec->ps_top_mb_row = ps_dec->ps_nbr_mb_row; ps_dec->ps_top_mb_row += ((ps_dec->u2_frm_wd_in_mbs + 2) << (1 - ps_dec->ps_cur_sps->u1_frame_mbs_only_flag)); //Increment by 2 ,so that left mb (mbaff decrements by 2) will always be valid ps_dec->ps_top_mb_row += 2; /* CHANGED CODE */ ps_dec->ps_mv_cur = ps_dec->s_cur_pic.ps_mv; ps_dec->ps_mv_top = ps_dec->ps_mv_top_p[0]; /* CHANGED CODE */ ps_dec->u1_mv_top_p = 0; ps_dec->u1_mb_idx = 0; /* CHANGED CODE */ ps_dec->ps_mv_left = ps_dec->s_cur_pic.ps_mv; ps_dec->u2_total_mbs_coded = 0; ps_dec->i4_submb_ofst = -(SUB_BLK_SIZE); ps_dec->u4_pred_info_idx = 0; ps_dec->u4_pred_info_pkd_idx = 0; ps_dec->u4_dma_buf_idx = 0; ps_dec->ps_mv = ps_dec->s_cur_pic.ps_mv; ps_dec->ps_mv_bank_cur = ps_dec->s_cur_pic.ps_mv; ps_dec->pu1_col_zero_flag = ps_dec->s_cur_pic.pu1_col_zero_flag; ps_dec->ps_part = ps_dec->ps_parse_part_params; ps_dec->i2_prev_slice_mbx = -1; ps_dec->i2_prev_slice_mby = 0; ps_dec->u2_mv_2mb[0] = 0; ps_dec->u2_mv_2mb[1] = 0; ps_dec->u1_last_pic_not_decoded = 0; ps_dec->u2_cur_slice_num_dec_thread = 0; ps_dec->u2_cur_slice_num_bs = 0; ps_dec->u4_intra_pred_line_ofst = 0; ps_dec->pu1_cur_y_intra_pred_line = ps_dec->pu1_y_intra_pred_line; ps_dec->pu1_cur_u_intra_pred_line = ps_dec->pu1_u_intra_pred_line; ps_dec->pu1_cur_v_intra_pred_line = ps_dec->pu1_v_intra_pred_line; ps_dec->pu1_cur_y_intra_pred_line_base = ps_dec->pu1_y_intra_pred_line; ps_dec->pu1_cur_u_intra_pred_line_base = ps_dec->pu1_u_intra_pred_line; ps_dec->pu1_cur_v_intra_pred_line_base = ps_dec->pu1_v_intra_pred_line; ps_dec->pu1_prev_y_intra_pred_line = ps_dec->pu1_y_intra_pred_line + (ps_dec->u2_frm_wd_in_mbs * MB_SIZE); ps_dec->pu1_prev_u_intra_pred_line = ps_dec->pu1_u_intra_pred_line + ps_dec->u2_frm_wd_in_mbs * BLK8x8SIZE * YUV420SP_FACTOR; ps_dec->pu1_prev_v_intra_pred_line = ps_dec->pu1_v_intra_pred_line + ps_dec->u2_frm_wd_in_mbs * BLK8x8SIZE; ps_dec->ps_deblk_mbn = ps_dec->ps_deblk_pic; /* Initialize The Function Pointer Depending Upon the Entropy and MbAff Flag */ { if(ps_cur_slice->u1_mbaff_frame_flag) { ps_dec->pf_compute_bs = ih264d_compute_bs_mbaff; ps_dec->pf_mvpred = ih264d_mvpred_mbaff; } else { ps_dec->pf_compute_bs = ih264d_compute_bs_non_mbaff; ps_dec->u1_cur_mb_fld_dec_flag = ps_cur_slice->u1_field_pic_flag; } } /* Set up the Parameter for DMA transfer */ { UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; UWORD8 u1_mbaff = ps_cur_slice->u1_mbaff_frame_flag; UWORD8 uc_lastmbs = (((ps_dec->u2_pic_wd) >> 4) % (ps_dec->u1_recon_mb_grp >> u1_mbaff)); UWORD16 ui16_lastmbs_widthY = (uc_lastmbs ? (uc_lastmbs << 4) : ((ps_dec->u1_recon_mb_grp >> u1_mbaff) << 4)); UWORD16 ui16_lastmbs_widthUV = uc_lastmbs ? (uc_lastmbs << 3) : ((ps_dec->u1_recon_mb_grp >> u1_mbaff) << 3); ps_dec->s_tran_addrecon.pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1; ps_dec->s_tran_addrecon.pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2; ps_dec->s_tran_addrecon.pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3; ps_dec->s_tran_addrecon.u2_frm_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; ps_dec->s_tran_addrecon.u2_frm_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; if(u1_field_pic_flag) { ui16_lastmbs_widthY += ps_dec->u2_frm_wd_y; ui16_lastmbs_widthUV += ps_dec->u2_frm_wd_uv; } /* Normal Increment of Pointer */ ps_dec->s_tran_addrecon.u4_inc_y[0] = ((ps_dec->u1_recon_mb_grp << 4) >> u1_mbaff); ps_dec->s_tran_addrecon.u4_inc_uv[0] = ((ps_dec->u1_recon_mb_grp << 4) >> u1_mbaff); /* End of Row Increment */ ps_dec->s_tran_addrecon.u4_inc_y[1] = (ui16_lastmbs_widthY + (PAD_LEN_Y_H << 1) + ps_dec->s_tran_addrecon.u2_frm_wd_y * ((15 << u1_mbaff) + u1_mbaff)); ps_dec->s_tran_addrecon.u4_inc_uv[1] = (ui16_lastmbs_widthUV + (PAD_LEN_UV_H << 2) + ps_dec->s_tran_addrecon.u2_frm_wd_uv * ((15 << u1_mbaff) + u1_mbaff)); /* Assign picture numbers to each frame/field */ /* only once per picture. */ ih264d_assign_pic_num(ps_dec); ps_dec->s_tran_addrecon.u2_mv_top_left_inc = (ps_dec->u1_recon_mb_grp << 2) - 1 - (u1_mbaff << 2); ps_dec->s_tran_addrecon.u2_mv_left_inc = ((ps_dec->u1_recon_mb_grp >> u1_mbaff) - 1) << (4 + u1_mbaff); } /**********************************************************************/ /* High profile related initialization at pictrue level */ /**********************************************************************/ if(ps_seq->u1_profile_idc == HIGH_PROFILE_IDC) { if((ps_seq->i4_seq_scaling_matrix_present_flag) || (ps_pps->i4_pic_scaling_matrix_present_flag)) { ret = ih264d_form_scaling_matrix_picture(ps_seq, ps_pps, ps_dec); ps_dec->s_high_profile.u1_scaling_present = 1; } else { ret = ih264d_form_default_scaling_matrix(ps_dec); } if(ps_pps->i4_transform_8x8_mode_flag) { ps_dec->s_high_profile.u1_transform8x8_present = 1; } } else { ret = ih264d_form_default_scaling_matrix(ps_dec); } if(ret != OK) return ret; /* required while reading the transform_size_8x8 u4_flag */ ps_dec->s_high_profile.u1_direct_8x8_inference_flag = ps_seq->u1_direct_8x8_inference_flag; ps_dec->s_high_profile.s_cavlc_ctxt = ps_dec->s_cavlc_ctxt; ps_dec->i1_recon_in_thread3_flag = 1; ps_dec->ps_frame_buf_ip_recon = &ps_dec->s_tran_addrecon; if(ps_dec->u1_separate_parse) { memcpy(&ps_dec->s_tran_addrecon_parse, &ps_dec->s_tran_addrecon, sizeof(tfr_ctxt_t)); if(ps_dec->u4_num_cores >= 3 && ps_dec->i1_recon_in_thread3_flag) { memcpy(&ps_dec->s_tran_iprecon, &ps_dec->s_tran_addrecon, sizeof(tfr_ctxt_t)); ps_dec->ps_frame_buf_ip_recon = &ps_dec->s_tran_iprecon; } } ih264d_init_deblk_tfr_ctxt(ps_dec,&(ps_dec->s_pad_mgr), &(ps_dec->s_tran_addrecon), ps_dec->u2_frm_wd_in_mbs, 0); ps_dec->ps_cur_deblk_mb = ps_dec->ps_deblk_pic; ps_dec->u4_cur_deblk_mb_num = 0; ps_dec->u4_deblk_mb_x = 0; ps_dec->u4_deblk_mb_y = 0; ps_dec->pu4_wt_ofsts = ps_dec->pu4_wts_ofsts_mat; ps_dec->u4_first_slice_in_pic = 0; H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); return OK; } /*! ************************************************************************** * \if Function name : ih264d_deblock_display \endif * * \brief : The function callls the deblocking routine and manages : the Recon buffers and displays . * \return : Nothing * ************************************************************************** */ WORD32 ih264d_end_of_pic_dispbuf_mgr(dec_struct_t * ps_dec) { dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; UWORD8 u1_num_of_users = 0; WORD32 ret; H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); if(1) { { ih264d_delete_nonref_nondisplay_pics(ps_dec->ps_dpb_mgr); if(ps_cur_slice->u1_mmco_equalto5 || (ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL)) { ps_dec->ps_cur_pic->i4_poc = 0; if(ps_dec->u2_total_mbs_coded == (ps_dec->ps_cur_sps->u2_max_mb_addr + 1)) ih264d_reset_ref_bufs(ps_dec->ps_dpb_mgr); ih264d_release_display_bufs(ps_dec); } if(IVD_DECODE_FRAME_OUT != ps_dec->e_frm_out_mode) { ret = ih264d_assign_display_seq(ps_dec); if(ret != OK) return ret; } } if(ps_cur_slice->u1_nal_ref_idc) { /* Mark pic buf as needed for reference */ ih264_buf_mgr_set_status((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, ps_dec->u1_pic_buf_id, BUF_MGR_REF); /* Mark mv buf as needed for reference */ ih264_buf_mgr_set_status((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, ps_dec->au1_pic_buf_id_mv_buf_id_map[ps_dec->u1_pic_buf_id], BUF_MGR_REF); ps_dec->au1_pic_buf_ref_flag[ps_dec->u1_pic_buf_id] = 1; } /* 420 consumer */ /* Increment the number of users by 1 for display based upon */ /*the SEEK KEY FRAME control sent to decoder */ if(((0 == ps_dec->u1_last_pic_not_decoded) && (0 == (ps_dec->ps_cur_pic->u4_pack_slc_typ & ps_dec->u4_skip_frm_mask))) || (ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL)) { /* Mark pic buf as needed for display */ ih264_buf_mgr_set_status((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, ps_dec->u1_pic_buf_id, BUF_MGR_IO); } if(!ps_cur_slice->u1_field_pic_flag || ((TOP_FIELD_ONLY | BOT_FIELD_ONLY) != ps_dec->u1_top_bottom_decoded)) { pic_buffer_t *ps_cur_pic = ps_dec->ps_cur_pic; ps_cur_pic->u2_disp_width = ps_dec->u2_disp_width; ps_cur_pic->u2_disp_height = ps_dec->u2_disp_height >> 1; ps_cur_pic->u2_crop_offset_y = ps_dec->u2_crop_offset_y; ps_cur_pic->u2_crop_offset_uv = ps_dec->u2_crop_offset_uv; ps_cur_pic->u1_pic_type = 0; { WORD64 i8_display_poc; i8_display_poc = (WORD64)ps_dec->i4_prev_max_display_seq + ps_dec->ps_cur_pic->i4_poc; if(IS_OUT_OF_RANGE_S32(i8_display_poc)) { ps_dec->i4_prev_max_display_seq = 0; } } ret = ih264d_insert_pic_in_display_list( ps_dec->ps_dpb_mgr, ps_dec->u1_pic_buf_id, ps_dec->i4_prev_max_display_seq + ps_dec->ps_cur_pic->i4_poc, ps_dec->ps_cur_pic->i4_frame_num); if(ret != OK) return ret; { ivd_video_decode_op_t * ps_dec_output = (ivd_video_decode_op_t *)ps_dec->pv_dec_out; ps_dec_output->u4_frame_decoded_flag = 1; } if(ps_dec->au1_pic_buf_ref_flag[ps_dec->u1_pic_buf_id] == 0) { ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, ps_dec->au1_pic_buf_id_mv_buf_id_map[ps_dec->u1_pic_buf_id], BUF_MGR_REF); ps_dec->au1_pic_buf_ref_flag[ps_dec->u1_pic_buf_id] = 0; } } else { H264_DEC_DEBUG_PRINT("pic not inserted display %d %d\n", ps_cur_slice->u1_field_pic_flag, ps_dec->u1_second_field); } if(!ps_cur_slice->u1_field_pic_flag || ((TOP_FIELD_ONLY | BOT_FIELD_ONLY) == ps_dec->u1_top_bottom_decoded)) { if(IVD_DECODE_FRAME_OUT == ps_dec->e_frm_out_mode) { ret = ih264d_assign_display_seq(ps_dec); if(ret != OK) return ret; } } } H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); return OK; } void ih264d_err_pic_dispbuf_mgr(dec_struct_t *ps_dec) { dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; ivd_video_decode_op_t * ps_dec_output = (ivd_video_decode_op_t *)ps_dec->pv_dec_out; ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, ps_dec->u1_pic_buf_id, BUF_MGR_REF); ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, ps_dec->au1_pic_buf_id_mv_buf_id_map[ps_dec->u1_pic_buf_id], BUF_MGR_REF); ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, ps_dec->u1_pic_buf_id, BUF_MGR_IO); } void ih264d_deblock_picture(void *ptr) { dec_struct_t *ps_dec = (dec_struct_t *)ptr; { /*Deblock picture only if all the mb's in the frame have been decoded*/ if(ps_dec->u1_pic_decode_done == 1) { if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag || ps_dec->ps_cur_slice->u1_field_pic_flag) { ps_dec->p_DeblockPicture[ps_dec->ps_cur_slice->u1_mbaff_frame_flag]( ps_dec); } else { ih264d_deblock_picture_progressive(ps_dec); } } } } /*! ************************************************************************** * \if Function name : ih264d_deblock_display \endif * * \brief : The function callls the deblocking routine and manages : the Recon buffers and displays . * \return : Nothing * ************************************************************************** */ WORD32 ih264d_deblock_display(dec_struct_t *ps_dec) { WORD32 ret; /* Call deblocking */ ih264d_deblock_picture(ps_dec); ret = ih264d_end_of_pic_dispbuf_mgr(ps_dec); if(ret != OK) return ret; return OK; } /* *! ************************************************************************** * \if Function name : EndofPoc \endif * * \brief * EndofPoc Processing * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_end_of_pic(dec_struct_t *ps_dec) { dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; WORD32 ret; { dec_err_status_t * ps_err = ps_dec->ps_dec_err_status; if(ps_err->u1_err_flag & REJECT_CUR_PIC) { ih264d_err_pic_dispbuf_mgr(ps_dec); return ERROR_NEW_FRAME_EXPECTED; } } H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); ret = ih264d_end_of_pic_processing(ps_dec); if(ret != OK) return ret; /*--------------------------------------------------------------------*/ /* ih264d_decode_pic_order_cnt - calculate the Pic Order Cnt */ /* Needed to detect end of picture */ /*--------------------------------------------------------------------*/ H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); return OK; } /*! ************************************************************************** * \if Function name : ih264d_fix_error_in_dpb \endif * * \brief * fix error in DPB * * \return * Number of node(s) deleted ************************************************************************** */ WORD32 ih264d_fix_error_in_dpb(dec_struct_t *ps_dec) { /*--------------------------------------------------------------------*/ /* If there is common node in lt_list and st_list then delete it from */ /* st_list */ /*--------------------------------------------------------------------*/ UWORD8 no_of_nodes_deleted = 0; UWORD8 lt_ref_num = ps_dec->ps_dpb_mgr->u1_num_lt_ref_bufs; struct dpb_info_t *ps_lt_curr_dpb = ps_dec->ps_dpb_mgr->ps_dpb_ht_head; while(lt_ref_num && ps_lt_curr_dpb) { if(ps_dec->ps_dpb_mgr->ps_dpb_st_head && ((ps_lt_curr_dpb->s_bot_field.u1_reference_info | ps_lt_curr_dpb->s_top_field.u1_reference_info) == (IS_SHORT_TERM | IS_LONG_TERM))) { struct dpb_info_t *ps_st_next_dpb = ps_dec->ps_dpb_mgr->ps_dpb_st_head; struct dpb_info_t *ps_st_curr_dpb = ps_dec->ps_dpb_mgr->ps_dpb_st_head; UWORD8 st_ref_num = ps_dec->ps_dpb_mgr->u1_num_st_ref_bufs; while(st_ref_num && ps_st_curr_dpb) { if(ps_st_curr_dpb == ps_lt_curr_dpb) { if(st_ref_num == ps_dec->ps_dpb_mgr->u1_num_st_ref_bufs) { ps_dec->ps_dpb_mgr->ps_dpb_st_head = ps_dec->ps_dpb_mgr->ps_dpb_st_head->ps_prev_short; ps_st_curr_dpb = ps_dec->ps_dpb_mgr->ps_dpb_st_head; } else { ps_st_next_dpb->ps_prev_short = ps_st_curr_dpb->ps_prev_short; } ps_dec->ps_dpb_mgr->u1_num_st_ref_bufs--; no_of_nodes_deleted++; break; } ps_st_next_dpb = ps_st_curr_dpb; ps_st_curr_dpb = ps_st_curr_dpb->ps_prev_short; st_ref_num--; } } ps_lt_curr_dpb = ps_lt_curr_dpb->ps_prev_long; lt_ref_num--; } return no_of_nodes_deleted; } /*! ************************************************************************** * \if Function name : DecodeSlice \endif * * \brief * Parses a slice * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_parse_decode_slice(UWORD8 u1_is_idr_slice, UWORD8 u1_nal_ref_idc, dec_struct_t *ps_dec /* Decoder parameters */ ) { dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; dec_pic_params_t *ps_pps; dec_seq_params_t *ps_seq; dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; pocstruct_t s_tmp_poc = {0}; WORD32 i_delta_poc[2]; WORD32 i4_poc = 0; UWORD16 u2_first_mb_in_slice, u2_frame_num; UWORD8 u1_field_pic_flag, u1_redundant_pic_cnt = 0, u1_slice_type; UWORD32 u4_idr_pic_id = 0; UWORD8 u1_bottom_field_flag, u1_pic_order_cnt_type; UWORD8 u1_nal_unit_type; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; WORD8 i1_is_end_of_poc; WORD32 ret, end_of_frame; WORD32 prev_slice_err, num_mb_skipped; UWORD8 u1_mbaff; pocstruct_t *ps_cur_poc; UWORD32 u4_temp; WORD32 i_temp; UWORD32 u4_call_end_of_pic = 0; /* read FirstMbInSlice and slice type*/ ps_dec->ps_dpb_cmds->u1_dpb_commands_read_slc = 0; u2_first_mb_in_slice = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u2_first_mb_in_slice >= (ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs)) { return ERROR_CORRUPTED_SLICE; } /*we currently don not support ASO*/ if(((u2_first_mb_in_slice << ps_cur_slice->u1_mbaff_frame_flag) <= ps_dec->u2_cur_mb_addr) && (ps_dec->u4_first_slice_in_pic == 0)) { return ERROR_CORRUPTED_SLICE; } COPYTHECONTEXT("SH: first_mb_in_slice",u2_first_mb_in_slice); u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp > 9) return ERROR_INV_SLC_TYPE_T; u1_slice_type = u4_temp; COPYTHECONTEXT("SH: slice_type",(u1_slice_type)); /* Find Out the Slice Type is 5 to 9 or not then Set the Flag */ /* u1_sl_typ_5_9 = 1 .Which tells that all the slices in the Pic*/ /* will be of same type of current */ if(u1_slice_type > 4) { u1_slice_type -= 5; } u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp & MASK_ERR_PIC_SET_ID) return ERROR_INV_SLICE_HDR_T; /* discard slice if pic param is invalid */ COPYTHECONTEXT("SH: pic_parameter_set_id", u4_temp); ps_pps = &ps_dec->ps_pps[u4_temp]; if(FALSE == ps_pps->u1_is_valid) { return ERROR_INV_SLICE_HDR_T; } ps_seq = ps_pps->ps_sps; if(!ps_seq) return ERROR_INV_SLICE_HDR_T; if(FALSE == ps_seq->u1_is_valid) return ERROR_INV_SLICE_HDR_T; /* Get the frame num */ u2_frame_num = ih264d_get_bits_h264(ps_bitstrm, ps_seq->u1_bits_in_frm_num); // H264_DEC_DEBUG_PRINT("FRAME %d First MB in slice: %d\n", u2_frame_num, u2_first_mb_in_slice); COPYTHECONTEXT("SH: frame_num", u2_frame_num); // H264_DEC_DEBUG_PRINT("Second field: %d frame num: %d prv_frame_num: %d \n", ps_dec->u1_second_field, u2_frame_num, ps_dec->u2_prv_frame_num); if(!ps_dec->u1_first_slice_in_stream && ps_dec->u4_first_slice_in_pic) { pocstruct_t *ps_prev_poc = &ps_dec->s_prev_pic_poc; pocstruct_t *ps_cur_poc = &ps_dec->s_cur_pic_poc; ps_dec->u2_mbx = 0xffff; ps_dec->u2_mby = 0; if((0 == u1_is_idr_slice) && ps_cur_slice->u1_nal_ref_idc) ps_dec->u2_prev_ref_frame_num = ps_cur_slice->u2_frame_num; if(u1_is_idr_slice || ps_cur_slice->u1_mmco_equalto5) ps_dec->u2_prev_ref_frame_num = 0; if(ps_dec->ps_cur_sps->u1_gaps_in_frame_num_value_allowed_flag) { ih264d_decode_gaps_in_frame_num(ps_dec, u2_frame_num); } ps_prev_poc->i4_prev_frame_num_ofst = ps_cur_poc->i4_prev_frame_num_ofst; ps_prev_poc->u2_frame_num = ps_cur_poc->u2_frame_num; ps_prev_poc->u1_mmco_equalto5 = ps_cur_slice->u1_mmco_equalto5; if(ps_cur_slice->u1_nal_ref_idc) { ps_prev_poc->i4_pic_order_cnt_lsb = ps_cur_poc->i4_pic_order_cnt_lsb; ps_prev_poc->i4_pic_order_cnt_msb = ps_cur_poc->i4_pic_order_cnt_msb; ps_prev_poc->i4_delta_pic_order_cnt_bottom = ps_cur_poc->i4_delta_pic_order_cnt_bottom; ps_prev_poc->i4_delta_pic_order_cnt[0] = ps_cur_poc->i4_delta_pic_order_cnt[0]; ps_prev_poc->i4_delta_pic_order_cnt[1] = ps_cur_poc->i4_delta_pic_order_cnt[1]; ps_prev_poc->u1_bot_field = ps_cur_poc->u1_bot_field; } ps_dec->u2_total_mbs_coded = 0; } /* Get the field related flags */ if(!ps_seq->u1_frame_mbs_only_flag) { u1_field_pic_flag = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("SH: field_pic_flag", u1_field_pic_flag); u1_bottom_field_flag = 0; if(u1_field_pic_flag) { ps_dec->pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_fld; u1_bottom_field_flag = ih264d_get_bit_h264(ps_bitstrm); COPYTHECONTEXT("SH: bottom_field_flag", u1_bottom_field_flag); } else { ps_dec->pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; } } else { u1_field_pic_flag = 0; u1_bottom_field_flag = 0; ps_dec->pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; } u1_nal_unit_type = SLICE_NAL; if(u1_is_idr_slice) { u1_nal_unit_type = IDR_SLICE_NAL; u4_idr_pic_id = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_idr_pic_id > 65535) return ERROR_INV_SLICE_HDR_T; COPYTHECONTEXT("SH: ", u4_idr_pic_id); } /* read delta pic order count information*/ i_delta_poc[0] = i_delta_poc[1] = 0; s_tmp_poc.i4_pic_order_cnt_lsb = 0; s_tmp_poc.i4_delta_pic_order_cnt_bottom = 0; u1_pic_order_cnt_type = ps_seq->u1_pic_order_cnt_type; if(u1_pic_order_cnt_type == 0) { i_temp = ih264d_get_bits_h264( ps_bitstrm, ps_seq->u1_log2_max_pic_order_cnt_lsb_minus); if(i_temp < 0 || i_temp >= ps_seq->i4_max_pic_order_cntLsb) return ERROR_INV_SLICE_HDR_T; s_tmp_poc.i4_pic_order_cnt_lsb = i_temp; COPYTHECONTEXT("SH: pic_order_cnt_lsb", s_tmp_poc.i4_pic_order_cnt_lsb); if((ps_pps->u1_pic_order_present_flag == 1) && (!u1_field_pic_flag)) { s_tmp_poc.i4_delta_pic_order_cnt_bottom = ih264d_sev( pu4_bitstrm_ofst, pu4_bitstrm_buf); //if(s_tmp_poc.i4_delta_pic_order_cnt_bottom > ps_seq->i4_max_pic_order_cntLsb) COPYTHECONTEXT("SH: delta_pic_order_cnt_bottom", s_tmp_poc.i4_delta_pic_order_cnt_bottom); } } s_tmp_poc.i4_delta_pic_order_cnt[0] = 0; s_tmp_poc.i4_delta_pic_order_cnt[1] = 0; if(u1_pic_order_cnt_type == 1 && (!ps_seq->u1_delta_pic_order_always_zero_flag)) { s_tmp_poc.i4_delta_pic_order_cnt[0] = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SH: delta_pic_order_cnt[0]", s_tmp_poc.i4_delta_pic_order_cnt[0]); if(ps_pps->u1_pic_order_present_flag && !u1_field_pic_flag) { s_tmp_poc.i4_delta_pic_order_cnt[1] = ih264d_sev( pu4_bitstrm_ofst, pu4_bitstrm_buf); COPYTHECONTEXT("SH: delta_pic_order_cnt[1]", s_tmp_poc.i4_delta_pic_order_cnt[1]); } } if(ps_pps->u1_redundant_pic_cnt_present_flag) { u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp > MAX_REDUNDANT_PIC_CNT) return ERROR_INV_SLICE_HDR_T; u1_redundant_pic_cnt = u4_temp; COPYTHECONTEXT("SH: redundant_pic_cnt", u1_redundant_pic_cnt); } /*--------------------------------------------------------------------*/ /* Check if the slice is part of new picture */ /*--------------------------------------------------------------------*/ /* First slice of a picture is always considered as part of new picture */ i1_is_end_of_poc = 1; ps_dec->ps_dec_err_status->u1_err_flag &= MASK_REJECT_CUR_PIC; if(ps_dec->u4_first_slice_in_pic == 0) { i1_is_end_of_poc = ih264d_is_end_of_pic(u2_frame_num, u1_nal_ref_idc, &s_tmp_poc, &ps_dec->s_cur_pic_poc, ps_cur_slice, u1_pic_order_cnt_type, u1_nal_unit_type, u4_idr_pic_id, u1_field_pic_flag, u1_bottom_field_flag); if(i1_is_end_of_poc) { ps_dec->u1_first_slice_in_stream = 0; return ERROR_INCOMPLETE_FRAME; } } /*--------------------------------------------------------------------*/ /* Check for error in slice and parse the missing/corrupted MB's */ /* as skip-MB's in an inserted P-slice */ /*--------------------------------------------------------------------*/ u1_mbaff = ps_seq->u1_mb_aff_flag && (!u1_field_pic_flag); prev_slice_err = 0; if(i1_is_end_of_poc || ps_dec->u1_first_slice_in_stream) { /* If the current slice is not a field or frame number of the current * slice doesn't match with previous slice, and decoder is expecting * to decode a field i.e. ps_dec->u1_top_bottom_decoded is not 0 and * is not (TOP_FIELD_ONLY | BOT_FIELD_ONLY), treat it as a dangling * field */ if((u1_field_pic_flag == 0 || u2_frame_num != ps_dec->u2_prv_frame_num) && ps_dec->u1_top_bottom_decoded != 0 && ps_dec->u1_top_bottom_decoded != (TOP_FIELD_ONLY | BOT_FIELD_ONLY)) { ps_dec->u1_dangling_field = 1; if(ps_dec->u4_first_slice_in_pic) { // first slice - dangling field prev_slice_err = 1; } else { // last slice - dangling field prev_slice_err = 2; } if(ps_dec->u1_top_bottom_decoded ==TOP_FIELD_ONLY) ps_cur_slice->u1_bottom_field_flag = 1; else ps_cur_slice->u1_bottom_field_flag = 0; num_mb_skipped = (ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) - ps_dec->u2_total_mbs_coded; ps_cur_poc = &ps_dec->s_cur_pic_poc; u1_is_idr_slice = ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL; } else if(ps_dec->u4_first_slice_in_pic) { if(u2_first_mb_in_slice > 0) { // first slice - missing/header corruption prev_slice_err = 1; num_mb_skipped = u2_first_mb_in_slice << u1_mbaff; ps_cur_poc = &s_tmp_poc; // initializing slice parameters ps_cur_slice->u4_idr_pic_id = u4_idr_pic_id; ps_cur_slice->u1_field_pic_flag = u1_field_pic_flag; ps_cur_slice->u1_bottom_field_flag = u1_bottom_field_flag; ps_cur_slice->i4_pic_order_cnt_lsb = s_tmp_poc.i4_pic_order_cnt_lsb; ps_cur_slice->u1_nal_unit_type = u1_nal_unit_type; ps_cur_slice->u1_redundant_pic_cnt = u1_redundant_pic_cnt; ps_cur_slice->u1_nal_ref_idc = u1_nal_ref_idc; ps_cur_slice->u1_pic_order_cnt_type = u1_pic_order_cnt_type; ps_cur_slice->u1_mbaff_frame_flag = ps_seq->u1_mb_aff_flag && (!u1_field_pic_flag); } } else { /* since i1_is_end_of_poc is set ,means new frame num is encountered. so conceal the current frame * completely */ prev_slice_err = 2; num_mb_skipped = (ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) - ps_dec->u2_total_mbs_coded; ps_cur_poc = &s_tmp_poc; } } else { if((u2_first_mb_in_slice << u1_mbaff) > ps_dec->u2_total_mbs_coded) { // previous slice - missing/corruption prev_slice_err = 2; num_mb_skipped = (u2_first_mb_in_slice << u1_mbaff) - ps_dec->u2_total_mbs_coded; ps_cur_poc = &s_tmp_poc; } else if((u2_first_mb_in_slice << u1_mbaff) < ps_dec->u2_total_mbs_coded) { return ERROR_CORRUPTED_SLICE; } } if(prev_slice_err) { ret = ih264d_mark_err_slice_skip(ps_dec, num_mb_skipped, u1_is_idr_slice, u2_frame_num, ps_cur_poc, prev_slice_err); if(ps_dec->u1_dangling_field == 1) { ps_dec->u1_second_field = 1 - ps_dec->u1_second_field; ps_dec->u1_first_slice_in_stream = 0; ps_dec->u1_top_bottom_decoded = TOP_FIELD_ONLY | BOT_FIELD_ONLY; return ERROR_DANGLING_FIELD_IN_PIC; } if(prev_slice_err == 2) { ps_dec->u1_first_slice_in_stream = 0; return ERROR_INCOMPLETE_FRAME; } if(ps_dec->u2_total_mbs_coded >= ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) { /* return if all MBs in frame are parsed*/ ps_dec->u1_first_slice_in_stream = 0; return ERROR_IN_LAST_SLICE_OF_PIC; } if(ps_dec->ps_dec_err_status->u1_err_flag & REJECT_CUR_PIC) { ih264d_err_pic_dispbuf_mgr(ps_dec); return ERROR_NEW_FRAME_EXPECTED; } if(ret != OK) return ret; i1_is_end_of_poc = 0; } /* Increment only if the current slice has atleast 1 more MB */ if (ps_dec->u4_first_slice_in_pic == 0 && (ps_dec->ps_parse_cur_slice->u4_first_mb_in_slice < (UWORD32)(ps_dec->u2_total_mbs_coded >> ps_dec->ps_cur_slice->u1_mbaff_frame_flag))) { ps_dec->ps_parse_cur_slice++; ps_dec->u2_cur_slice_num++; // in the case of single core increment ps_decode_cur_slice if(ps_dec->u1_separate_parse == 0) { ps_dec->ps_decode_cur_slice++; } } ps_dec->u1_slice_header_done = 0; if(u1_field_pic_flag) { ps_dec->u2_prv_frame_num = u2_frame_num; } if(ps_cur_slice->u1_mmco_equalto5) { WORD32 i4_temp_poc; WORD32 i4_top_field_order_poc, i4_bot_field_order_poc; if(!ps_cur_slice->u1_field_pic_flag) // or a complementary field pair { i4_top_field_order_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; i4_bot_field_order_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; i4_temp_poc = MIN(i4_top_field_order_poc, i4_bot_field_order_poc); } else if(!ps_cur_slice->u1_bottom_field_flag) i4_temp_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; else i4_temp_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; WORD64 i8_result = (WORD64)i4_temp_poc - ps_dec->ps_cur_pic->i4_top_field_order_cnt; if(IS_OUT_OF_RANGE_S32(i8_result)) { return ERROR_INV_POC; } ps_dec->ps_cur_pic->i4_top_field_order_cnt = i8_result; i8_result = (WORD64)i4_temp_poc - ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; if(IS_OUT_OF_RANGE_S32(i8_result)) { return ERROR_INV_POC; } ps_dec->ps_cur_pic->i4_bottom_field_order_cnt = i8_result; ps_dec->ps_cur_pic->i4_poc = i4_temp_poc; ps_dec->ps_cur_pic->i4_avg_poc = i4_temp_poc; } if(ps_dec->u4_first_slice_in_pic) { ret = ih264d_decode_pic_order_cnt(u1_is_idr_slice, u2_frame_num, &ps_dec->s_prev_pic_poc, &s_tmp_poc, ps_cur_slice, ps_pps, u1_nal_ref_idc, u1_bottom_field_flag, u1_field_pic_flag, &i4_poc); if(ret != OK) return ret; /* Display seq no calculations */ if(i4_poc >= ps_dec->i4_max_poc) ps_dec->i4_max_poc = i4_poc; /* IDR Picture or POC wrap around */ if(i4_poc == 0) { WORD64 i8_temp; i8_temp = (WORD64)ps_dec->i4_prev_max_display_seq + ps_dec->i4_max_poc + ps_dec->u1_max_dec_frame_buffering + 1; /*If i4_prev_max_display_seq overflows integer range, reset it */ ps_dec->i4_prev_max_display_seq = IS_OUT_OF_RANGE_S32(i8_temp)? 0 : i8_temp; ps_dec->i4_max_poc = 0; } } /*--------------------------------------------------------------------*/ /* Copy the values read from the bitstream to the slice header and then*/ /* If the slice is first slice in picture, then do Start of Picture */ /* processing. */ /*--------------------------------------------------------------------*/ ps_cur_slice->i4_delta_pic_order_cnt[0] = i_delta_poc[0]; ps_cur_slice->i4_delta_pic_order_cnt[1] = i_delta_poc[1]; ps_cur_slice->u4_idr_pic_id = u4_idr_pic_id; ps_cur_slice->u2_first_mb_in_slice = u2_first_mb_in_slice; ps_cur_slice->u1_field_pic_flag = u1_field_pic_flag; ps_cur_slice->u1_bottom_field_flag = u1_bottom_field_flag; ps_cur_slice->u1_slice_type = u1_slice_type; ps_cur_slice->i4_pic_order_cnt_lsb = s_tmp_poc.i4_pic_order_cnt_lsb; ps_cur_slice->u1_nal_unit_type = u1_nal_unit_type; ps_cur_slice->u1_redundant_pic_cnt = u1_redundant_pic_cnt; ps_cur_slice->u1_nal_ref_idc = u1_nal_ref_idc; ps_cur_slice->u1_pic_order_cnt_type = u1_pic_order_cnt_type; if(ps_seq->u1_frame_mbs_only_flag) ps_cur_slice->u1_direct_8x8_inference_flag = ps_seq->u1_direct_8x8_inference_flag; else ps_cur_slice->u1_direct_8x8_inference_flag = 1; if(u1_slice_type == B_SLICE) { ps_cur_slice->u1_direct_spatial_mv_pred_flag = ih264d_get_bit_h264( ps_bitstrm); COPYTHECONTEXT("SH: direct_spatial_mv_pred_flag", ps_cur_slice->u1_direct_spatial_mv_pred_flag); if(ps_cur_slice->u1_direct_spatial_mv_pred_flag) ps_cur_slice->pf_decodeDirect = ih264d_decode_spatial_direct; else ps_cur_slice->pf_decodeDirect = ih264d_decode_temporal_direct; if(!((ps_pps->ps_sps->u1_mb_aff_flag) && (!u1_field_pic_flag))) ps_dec->pf_mvpred = ih264d_mvpred_nonmbaffB; } else { if(!((ps_pps->ps_sps->u1_mb_aff_flag) && (!u1_field_pic_flag))) ps_dec->pf_mvpred = ih264d_mvpred_nonmbaff; } if(ps_dec->u4_first_slice_in_pic) { if(u2_first_mb_in_slice == 0) { ret = ih264d_start_of_pic(ps_dec, i4_poc, &s_tmp_poc, u2_frame_num, ps_pps); if(ret != OK) return ret; } ps_dec->u4_output_present = 0; { ih264d_get_next_display_field(ps_dec, ps_dec->ps_out_buffer, &(ps_dec->s_disp_op)); /* If error code is non-zero then there is no buffer available for display, hence avoid format conversion */ if(0 != ps_dec->s_disp_op.u4_error_code) { ps_dec->u4_fmt_conv_cur_row = ps_dec->s_disp_frame_info.u4_y_ht; } else ps_dec->u4_output_present = 1; } if(ps_dec->u1_separate_parse == 1) { if(ps_dec->u4_dec_thread_created == 0) { ithread_create(ps_dec->pv_dec_thread_handle, NULL, (void *)ih264d_decode_picture_thread, (void *)ps_dec); ps_dec->u4_dec_thread_created = 1; } if((ps_dec->u4_num_cores == 3) && ((ps_dec->u4_app_disable_deblk_frm == 0) || ps_dec->i1_recon_in_thread3_flag) && (ps_dec->u4_bs_deblk_thread_created == 0)) { ps_dec->u4_start_recon_deblk = 0; ithread_create(ps_dec->pv_bs_deblk_thread_handle, NULL, (void *)ih264d_recon_deblk_thread, (void *)ps_dec); ps_dec->u4_bs_deblk_thread_created = 1; } } } /* INITIALIZATION of fn ptrs for MC and formMbPartInfo functions */ { UWORD8 uc_nofield_nombaff; uc_nofield_nombaff = ((ps_dec->ps_cur_slice->u1_field_pic_flag == 0) && (ps_dec->ps_cur_slice->u1_mbaff_frame_flag == 0) && (u1_slice_type != B_SLICE) && (ps_dec->ps_cur_pps->u1_wted_pred_flag == 0)); /* Initialise MC and formMbPartInfo fn ptrs one time based on profile_idc */ if(uc_nofield_nombaff) { ps_dec->p_form_mb_part_info = ih264d_form_mb_part_info_bp; ps_dec->p_motion_compensate = ih264d_motion_compensate_bp; } else { ps_dec->p_form_mb_part_info = ih264d_form_mb_part_info_mp; ps_dec->p_motion_compensate = ih264d_motion_compensate_mp; } } /* * Decide whether to decode the current picture or not */ { dec_err_status_t * ps_err = ps_dec->ps_dec_err_status; if(ps_err->u4_frm_sei_sync == u2_frame_num) { ps_err->u1_err_flag = ACCEPT_ALL_PICS; ps_err->u4_frm_sei_sync = SYNC_FRM_DEFAULT; } ps_err->u4_cur_frm = u2_frame_num; } /* Decision for decoding if the picture is to be skipped */ { WORD32 i4_skip_b_pic, i4_skip_p_pic; i4_skip_b_pic = (ps_dec->u4_skip_frm_mask & B_SLC_BIT) && (B_SLICE == u1_slice_type) && (0 == u1_nal_ref_idc); i4_skip_p_pic = (ps_dec->u4_skip_frm_mask & P_SLC_BIT) && (P_SLICE == u1_slice_type) && (0 == u1_nal_ref_idc); /**************************************************************/ /* Skip the B picture if skip mask is set for B picture and */ /* Current B picture is a non reference B picture or there is */ /* no user for reference B picture */ /**************************************************************/ if(i4_skip_b_pic) { ps_dec->ps_cur_pic->u4_pack_slc_typ |= B_SLC_BIT; /* Don't decode the picture in SKIP-B mode if that picture is B */ /* and also it is not to be used as a reference picture */ ps_dec->u1_last_pic_not_decoded = 1; return OK; } /**************************************************************/ /* Skip the P picture if skip mask is set for P picture and */ /* Current P picture is a non reference P picture or there is */ /* no user for reference P picture */ /**************************************************************/ if(i4_skip_p_pic) { ps_dec->ps_cur_pic->u4_pack_slc_typ |= P_SLC_BIT; /* Don't decode the picture in SKIP-P mode if that picture is P */ /* and also it is not to be used as a reference picture */ ps_dec->u1_last_pic_not_decoded = 1; return OK; } } { UWORD16 u2_mb_x, u2_mb_y; ps_dec->i4_submb_ofst = ((u2_first_mb_in_slice << ps_cur_slice->u1_mbaff_frame_flag) * SUB_BLK_SIZE) - SUB_BLK_SIZE; if(u2_first_mb_in_slice) { UWORD8 u1_mb_aff; UWORD8 u1_field_pic; UWORD16 u2_frm_wd_in_mbs; u2_frm_wd_in_mbs = ps_seq->u2_frm_wd_in_mbs; u1_mb_aff = ps_cur_slice->u1_mbaff_frame_flag; u1_field_pic = ps_cur_slice->u1_field_pic_flag; { UWORD32 x_offset; UWORD32 y_offset; UWORD32 u4_frame_stride; tfr_ctxt_t *ps_trns_addr; // = &ps_dec->s_tran_addrecon_parse; if(ps_dec->u1_separate_parse) { ps_trns_addr = &ps_dec->s_tran_addrecon_parse; } else { ps_trns_addr = &ps_dec->s_tran_addrecon; } u2_mb_x = MOD(u2_first_mb_in_slice, u2_frm_wd_in_mbs); u2_mb_y = DIV(u2_first_mb_in_slice, u2_frm_wd_in_mbs); u2_mb_y <<= u1_mb_aff; if((u2_mb_x > u2_frm_wd_in_mbs - 1) || (u2_mb_y > ps_dec->u2_frm_ht_in_mbs - 1)) { return ERROR_CORRUPTED_SLICE; } u4_frame_stride = ps_dec->u2_frm_wd_y << u1_field_pic; x_offset = u2_mb_x << 4; y_offset = (u2_mb_y * u4_frame_stride) << 4; ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + x_offset + y_offset; u4_frame_stride = ps_dec->u2_frm_wd_uv << u1_field_pic; x_offset >>= 1; y_offset = (u2_mb_y * u4_frame_stride) << 3; x_offset *= YUV420SP_FACTOR; ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + x_offset + y_offset; ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + x_offset + y_offset; ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; // assign the deblock structure pointers to start of slice if(ps_dec->u1_separate_parse == 1) { ps_dec->ps_deblk_mbn = ps_dec->ps_deblk_pic + (u2_first_mb_in_slice << u1_mb_aff); } else { ps_dec->ps_deblk_mbn = ps_dec->ps_deblk_pic + (u2_first_mb_in_slice << u1_mb_aff); } ps_dec->u2_cur_mb_addr = (u2_first_mb_in_slice << u1_mb_aff); ps_dec->ps_mv_cur = ps_dec->s_cur_pic.ps_mv + ((u2_first_mb_in_slice << u1_mb_aff) << 4); } } else { tfr_ctxt_t *ps_trns_addr; if(ps_dec->u1_separate_parse) { ps_trns_addr = &ps_dec->s_tran_addrecon_parse; } else { ps_trns_addr = &ps_dec->s_tran_addrecon; } u2_mb_x = 0xffff; u2_mb_y = 0; // assign the deblock structure pointers to start of slice ps_dec->u2_cur_mb_addr = 0; ps_dec->ps_deblk_mbn = ps_dec->ps_deblk_pic; ps_dec->ps_mv_cur = ps_dec->s_cur_pic.ps_mv; ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1; ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2; ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3; ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; } ps_dec->ps_part = ps_dec->ps_parse_part_params; ps_dec->u2_mbx = (MOD(u2_first_mb_in_slice - 1, ps_seq->u2_frm_wd_in_mbs)); ps_dec->u2_mby = (DIV(u2_first_mb_in_slice - 1, ps_seq->u2_frm_wd_in_mbs)); ps_dec->u2_mby <<= ps_cur_slice->u1_mbaff_frame_flag; ps_dec->i2_prev_slice_mbx = ps_dec->u2_mbx; ps_dec->i2_prev_slice_mby = ps_dec->u2_mby; } /* RBSP stop bit is used for CABAC decoding*/ ps_bitstrm->u4_max_ofst += ps_dec->ps_cur_pps->u1_entropy_coding_mode; ps_dec->u1_B = (u1_slice_type == B_SLICE); ps_dec->u4_next_mb_skip = 0; ps_dec->ps_parse_cur_slice->u4_first_mb_in_slice = ps_dec->ps_cur_slice->u2_first_mb_in_slice; ps_dec->ps_parse_cur_slice->slice_type = ps_dec->ps_cur_slice->u1_slice_type; ps_dec->u4_start_recon_deblk = 1; { WORD32 num_entries; WORD32 size; UWORD8 *pu1_buf; num_entries = MAX_FRAMES; if((1 >= ps_dec->ps_cur_sps->u1_num_ref_frames) && (0 == ps_dec->i4_display_delay)) { num_entries = 1; } num_entries = ((2 * num_entries) + 1); num_entries *= 2; size = num_entries * sizeof(void *); size += PAD_MAP_IDX_POC * sizeof(void *); pu1_buf = (UWORD8 *)ps_dec->pv_map_ref_idx_to_poc_buf; pu1_buf += size * ps_dec->u2_cur_slice_num; ps_dec->ps_parse_cur_slice->ppv_map_ref_idx_to_poc = ( void *)pu1_buf; } if(ps_dec->u1_separate_parse) { ps_dec->ps_parse_cur_slice->pv_tu_coeff_data_start = ps_dec->pv_parse_tu_coeff_data; } else { ps_dec->pv_proc_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; } ret = ih264d_fix_error_in_dpb(ps_dec); if(ret < 0) return ERROR_DBP_MANAGER_T; if(u1_slice_type == I_SLICE) { ps_dec->ps_cur_pic->u4_pack_slc_typ |= I_SLC_BIT; ret = ih264d_parse_islice(ps_dec, u2_first_mb_in_slice); ps_dec->u1_pr_sl_type = u1_slice_type; if(ps_dec->i4_pic_type != B_SLICE && ps_dec->i4_pic_type != P_SLICE) ps_dec->i4_pic_type = I_SLICE; } else if(u1_slice_type == P_SLICE) { ps_dec->ps_cur_pic->u4_pack_slc_typ |= P_SLC_BIT; ret = ih264d_parse_pslice(ps_dec, u2_first_mb_in_slice); ps_dec->u1_pr_sl_type = u1_slice_type; if(ps_dec->i4_pic_type != B_SLICE) ps_dec->i4_pic_type = P_SLICE; } else if(u1_slice_type == B_SLICE) { ps_dec->ps_cur_pic->u4_pack_slc_typ |= B_SLC_BIT; ret = ih264d_parse_bslice(ps_dec, u2_first_mb_in_slice); ps_dec->u1_pr_sl_type = u1_slice_type; ps_dec->i4_pic_type = B_SLICE; } else return ERROR_INV_SLC_TYPE_T; if(ps_dec->u1_slice_header_done) { /* set to zero to indicate a valid slice has been decoded */ ps_dec->u1_first_slice_in_stream = 0; } if(ret != OK) return ret; if(u1_nal_ref_idc != 0) { if(!ps_dec->ps_dpb_cmds->u1_dpb_commands_read) { memcpy((void *)ps_dec->ps_dpb_cmds, (void *)(&(ps_dec->s_dpb_cmds_scratch)), sizeof(dpb_commands_t)); } } /* storing last Mb X and MbY of the slice */ ps_dec->i2_prev_slice_mbx = ps_dec->u2_mbx; ps_dec->i2_prev_slice_mby = ps_dec->u2_mby; /* End of Picture detection */ if(ps_dec->u2_total_mbs_coded >= (ps_seq->u2_max_mb_addr + 1)) { ps_dec->u1_pic_decode_done = 1; } { dec_err_status_t * ps_err = ps_dec->ps_dec_err_status; if((ps_err->u1_err_flag & REJECT_PB_PICS) && (ps_err->u1_cur_pic_type == PIC_TYPE_I)) { ps_err->u1_err_flag = ACCEPT_ALL_PICS; } } PRINT_BIN_BIT_RATIO(ps_dec) return ret; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_parse_slice.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_PARSE_SLICE_H_ #define _IH264D_PARSE_SLICE_H_ /*! ************************************************************************** * \file ih264d_parse_slice.h * * \brief * Contains routines that decodes a slice NAL unit * * \date * 19/12/2002 * * \author AI ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" #include "ih264d_error_handler.h" WORD32 ih264d_fix_error_in_dpb(dec_struct_t * ps_dec); WORD32 ih264d_parse_decode_slice(UWORD8 u1_is_idr_slice, UWORD8 u1_nal_ref_idc, dec_struct_t * ps_dec ); WORD32 ih264d_end_of_pic(dec_struct_t *ps_dec); WORD32 ih264d_start_of_pic(dec_struct_t *ps_dec, WORD32 i4_poc, pocstruct_t *ps_temp_poc, UWORD16 u2_frame_num, dec_pic_params_t *ps_pps); WORD32 ih264d_ref_idx_reordering(dec_struct_t * ps_dec, UWORD8 u1_isB); WORD32 ih264d_read_mmco_commands(dec_struct_t * ps_dec); void ih264d_form_pred_weight_matrix(dec_struct_t *ps_dec); WORD32 ih264d_end_of_pic_dispbuf_mgr(dec_struct_t * ps_dec); #endif /* _IH264D_PARSE_SLICE_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_process_bslice.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_process_bslice.c * * \brief * Contains routines that decode B slice type * * Detailed_description * * \date * 21/12/2002 * * \author NS ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include <string.h> #include "ih264d_structs.h" #include "ih264d_bitstrm.h" #include "ih264d_parse_cavlc.h" #include "ih264d_mb_utils.h" #include "ih264d_mvpred.h" #include "ih264d_inter_pred.h" #include "ih264d_process_pslice.h" #include "ih264d_error_handler.h" #include "ih264d_tables.h" #include "ih264d_parse_slice.h" #include "ih264d_process_pslice.h" #include "ih264d_process_bslice.h" #include "ih264d_tables.h" #include "ih264d_parse_islice.h" #include "ih264d_mvpred.h" void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec); //UWORD32 g_hits = 0; //UWORD32 g_miss = 0; /*! ************************************************************************** * \if Function name : ih264d_decode_spatial_direct \endif * * \brief * Decodes spatial direct mode. * * \return * None. * Arunoday T ************************************************************************** */ WORD32 ih264d_decode_spatial_direct(dec_struct_t * ps_dec, UWORD8 u1_wd_x, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num) { mv_pred_t s_mv_pred, *ps_mv; UWORD8 u1_col_zero_flag, u1_sub_mb_num, u1_direct_zero_pred_flag = 0; UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; mv_pred_t *ps_mv_ntop_start; mv_pred_t *ps_mv_nmb_start = ps_dec->ps_mv_cur + (u1_mb_num << 4); UWORD8 partition_size, sub_partition, u1_mb_partw, u1_mb_parth; UWORD8 i; WORD8 i1_pred, i1_ref_frame0, i1_ref_frame1; struct pic_buffer_t *ps_ref_frame = NULL, *ps_col_pic, *ps_pic_buff0 = NULL, *ps_pic_buff1 = NULL; UWORD8 u1_zero_pred_cond_f, u1_zero_pred_cond_b; WORD16 i2_def_mv[2], i2_spat_pred_mv[4], *pi2_final_mv0, *pi2_final_mv1; UWORD16 ui2_mask_fwd = 0, ui2_mask_bwd = 0, u2_mask = 0; UWORD32 *pui32_weight_ofsts = NULL; directmv_t s_mvdirect; UWORD8 u1_colz; UWORD8 u1_final_ref_idx = 0; const UWORD8 *pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; const UWORD8 *pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; const UWORD16 sub_mask_table[] = { 0x33, 0x3, 0x11, 0x1 }; const UWORD16 mask_table[] = { 0xffff, /*16x16 NA */ 0xff, /* 16x8*/ 0x3333, /* 8x16*/ 0x33 };/* 8x8*/ mv_pred_t s_temp_mv_pred; WORD32 ret = 0; /* CHANGED CODE */ ps_mv_ntop_start = ps_dec->ps_mv_cur + (u1_mb_num << 4) - (ps_dec->u2_frm_wd_in_mbs << (4 + u1_mbaff)) + 12; /* assign default values for MotionVector as zero */ i2_def_mv[0] = 0; i2_def_mv[1] = 0; u1_direct_zero_pred_flag = ps_dec->pf_mvpred(ps_dec, ps_cur_mb_info, ps_mv_nmb_start, ps_mv_ntop_start, &s_mv_pred, 0, 4, 0, 1, B_DIRECT_SPATIAL); i2_spat_pred_mv[0] = s_mv_pred.i2_mv[0]; i2_spat_pred_mv[1] = s_mv_pred.i2_mv[1]; i2_spat_pred_mv[2] = s_mv_pred.i2_mv[2]; i2_spat_pred_mv[3] = s_mv_pred.i2_mv[3]; i1_ref_frame0 = s_mv_pred.i1_ref_frame[0]; i1_ref_frame1 = s_mv_pred.i1_ref_frame[1]; i1_ref_frame0 = (i1_ref_frame0 < 0) ? -1 : i1_ref_frame0; i1_ref_frame1 = (i1_ref_frame1 < 0) ? -1 : i1_ref_frame1; i1_pred = 0; { WORD8 u1_ref_idx, u1_ref_idx1; UWORD32 uc_Idx, uc_Idx1; UWORD8 u1_scale_ref = (ps_dec->ps_cur_slice->u1_mbaff_frame_flag && ps_cur_mb_info->u1_mb_field_decodingflag); u1_final_ref_idx = i1_ref_frame0; if(i1_ref_frame0 >= 0) { /* convert RefIdx if it is MbAff */ u1_ref_idx = i1_ref_frame0; u1_ref_idx1 = i1_ref_frame0; if(u1_scale_ref) { u1_ref_idx1 = u1_ref_idx >> 1; if((u1_ref_idx & 0x01) != (1 - ps_cur_mb_info->u1_topmb)) u1_ref_idx1 += MAX_REF_BUFS; } /* If i1_ref_frame0 < 0 then refIdxCol is obtained from ps_pic_buff1 */ ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][u1_ref_idx1]; ps_ref_frame = ps_pic_buff0; i1_pred = PRED_L0; } if(i1_ref_frame1 >= 0) { /* convert RefIdx if it is MbAff */ u1_ref_idx = i1_ref_frame1; u1_ref_idx1 = i1_ref_frame1; if(u1_scale_ref) { u1_ref_idx1 = u1_ref_idx >> 1; if((u1_ref_idx & 0x01) != (1 - ps_cur_mb_info->u1_topmb)) u1_ref_idx1 += MAX_REF_BUFS; } ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][u1_ref_idx1]; i1_pred = i1_pred | PRED_L1; } if(i1_ref_frame0 < 0) { ps_ref_frame = ps_pic_buff1; u1_final_ref_idx = i1_ref_frame1; } u1_zero_pred_cond_f = (u1_direct_zero_pred_flag) || (i1_ref_frame0 < 0); u1_zero_pred_cond_b = (u1_direct_zero_pred_flag) || (i1_ref_frame1 < 0); if(ps_dec->ps_cur_pps->u1_wted_bipred_idc) { uc_Idx = ((i1_ref_frame0 < 1) ? 0 : i1_ref_frame0) * ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; if(u1_scale_ref) uc_Idx >>= 1; uc_Idx1 = (i1_ref_frame1 < 0) ? 0 : i1_ref_frame1; uc_Idx += (u1_scale_ref) ? (uc_Idx1 >> 1) : uc_Idx1; pui32_weight_ofsts = (UWORD32*)&ps_dec->pu4_wt_ofsts[2 * X3(uc_Idx)]; if(i1_ref_frame0 < 0) pui32_weight_ofsts += 1; if(u1_scale_ref && (ps_dec->ps_cur_pps->u1_wted_bipred_idc == 2)) { WORD16 i2_ref_idx; i2_ref_idx = MAX(i1_ref_frame0, 0); i2_ref_idx *= (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] << 1); i2_ref_idx += MAX(i1_ref_frame1, 0); if(!ps_cur_mb_info->u1_topmb) i2_ref_idx += (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0] << 1) * (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] << 1); pui32_weight_ofsts = (UWORD32*)&ps_dec->pu4_mbaff_wt_mat[2 * X3(i2_ref_idx)]; } } } s_temp_mv_pred.i1_ref_frame[0] = i1_ref_frame0; s_temp_mv_pred.i1_ref_frame[1] = i1_ref_frame1; s_temp_mv_pred.u1_col_ref_pic_idx = ps_ref_frame->u1_mv_buf_id; s_temp_mv_pred.u1_pic_type = ps_ref_frame->u1_pic_type; /**********************************************************************/ /* Call the function which gets the number of partitions and */ /* partition info of colocated Mb */ /**********************************************************************/ ps_dec->pf_parse_mvdirect(ps_dec, ps_dec->ps_col_pic, &s_mvdirect, u1_wd_x, ps_dec->i4_submb_ofst, ps_cur_mb_info); ps_col_pic = ps_dec->ps_col_pic; if((s_mvdirect.u1_col_zeroflag_change == 0) || u1_direct_zero_pred_flag) { WORD16 i2_mv_x, i2_mv_y, i2_mvX1, i2_mvY1; /* Most probable case */ u1_col_zero_flag = *(ps_col_pic->pu1_col_zero_flag + s_mvdirect.i4_mv_indices[0]); u1_col_zero_flag = u1_col_zero_flag & 0x01; if(u1_zero_pred_cond_f || ((i1_ref_frame0 == 0) && (u1_col_zero_flag == 1))) { i2_mv_x = 0; i2_mv_y = 0; } else { i2_mv_x = i2_spat_pred_mv[0]; i2_mv_y = i2_spat_pred_mv[1]; } if(u1_zero_pred_cond_b || ((i1_ref_frame1 == 0) && (u1_col_zero_flag == 1))) { i2_mvX1 = 0; i2_mvY1 = 0; } else { i2_mvX1 = i2_spat_pred_mv[2]; i2_mvY1 = i2_spat_pred_mv[3]; } u1_sub_mb_num = ps_dec->u1_sub_mb_num; u1_mb_partw = (u1_wd_x >> 2); if(i1_ref_frame0 >= 0) { { pred_info_pkd_t *ps_pred_pkd; WORD16 i2_mv[2]; WORD8 i1_ref_idx= 0; i2_mv[0] = i2_mv_x; i2_mv[1] = i2_mv_y; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(i2_mv,u1_mb_partw,u1_mb_partw,u1_sub_mb_num,i1_pred, ps_pred_pkd,ps_pic_buff0->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, ps_pic_buff0->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } } if(i1_ref_frame1 >= 0) { { pred_info_pkd_t *ps_pred_pkd; WORD16 i2_mv[2]; WORD8 i1_ref_idx= 0; i2_mv[0] = i2_mvX1; i2_mv[1] = i2_mvY1; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(i2_mv,u1_mb_partw,u1_mb_partw,u1_sub_mb_num,i1_pred, ps_pred_pkd,ps_pic_buff1->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, ps_pic_buff1->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } } /* Replication optimisation */ s_temp_mv_pred.i2_mv[0] = i2_mv_x; s_temp_mv_pred.i2_mv[1] = i2_mv_y; s_temp_mv_pred.i2_mv[2] = i2_mvX1; s_temp_mv_pred.i2_mv[3] = i2_mvY1; /* Calculating colocated zero information */ { /*************************************/ /* If(bit2 and bit3 set) */ /* then */ /* (bit0 and bit1) => submmbmode */ /* (bit2 and bit3) => mbmode */ /* else */ /* (bit0 and bit1) => mbmode */ /*************************************/ /*UWORD8 u1_packed_mb_sub_mb_mode = sub_partition ? (s_mvdirect.i1_partitionsize[0]) : ((s_mvdirect.i1_partitionsize[0]) << 2);*/ UWORD8 u1_packed_mb_sub_mb_mode = (u1_mb_partw == 2) ? 0x03 : 0; if(i1_ref_frame0 < 0) { i2_mv_x = i2_mvX1; i2_mv_y = i2_mvY1; } /* Change from left shift 4 to 6 - Varun */ u1_colz = (ps_cur_mb_info->u1_mb_field_decodingflag << 1) | ((u1_final_ref_idx == 0) && (ABS(i2_mv_x) <= 1) && (ABS(i2_mv_y) <= 1)); u1_colz |= (u1_packed_mb_sub_mb_mode << 6); } ps_mv = ps_mv_nmb_start + u1_sub_mb_num; ih264d_rep_mv_colz(ps_dec, &s_temp_mv_pred, ps_mv, u1_sub_mb_num, u1_colz, u1_mb_partw, u1_mb_partw); if(u1_wd_x == MB_SIZE) ps_dec->u1_currB_type = 0; return OK; } /***************************************************************************/ /* If present MB is 16x16 and the partition of colocated Mb is >= PRED_8x8 */ /* i.e 8x8 or less than 8x8 partitions then set up DMA for (0,0) and */ /* spatially predicted motion vector and do the multiplexing after */ /* motion compensation */ /***************************************************************************/ if((u1_wd_x == MB_SIZE) && (s_mvdirect.i1_num_partitions > 2)) { ps_cur_mb_info->u1_Mux = 1; if(i1_ref_frame0 >= 0) { { pred_info_pkd_t *ps_pred_pkd; WORD8 i1_ref_idx= 0; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(&(i2_spat_pred_mv[0]),4,4,0,i1_pred, ps_pred_pkd,ps_pic_buff0->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, ps_pic_buff0->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } /****** (0,0) Motion vectors DMA *****/ { pred_info_pkd_t *ps_pred_pkd; WORD16 i2_mv[2]; WORD8 i1_ref_idx= 0; i2_mv[0] = 0; i2_mv[1] = 0; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(i2_mv,4,4,0,i1_pred, ps_pred_pkd,ps_pic_buff0->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, ps_pic_buff0->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } } if(i1_ref_frame1 >= 0) { { pred_info_pkd_t *ps_pred_pkd; WORD16 i2_mv[2]; WORD8 i1_ref_idx= 0; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(&(i2_spat_pred_mv[2]),4,4,0,i1_pred, ps_pred_pkd,ps_pic_buff1->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, ps_pic_buff1->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } /****** (0,0) Motion vectors DMA *****/ { pred_info_pkd_t *ps_pred_pkd; WORD16 i2_mv[2]; WORD8 i1_ref_idx= 0; i2_mv[0] = 0; i2_mv[1] = 0; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(i2_mv,4,4,0,i1_pred, ps_pred_pkd,ps_pic_buff1->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, ps_pic_buff1->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } } } /*u1_col = *(ps_col_pic->pu1_col_zero_flag + s_mvdirect.i4_mv_indices[0]); u1_col &= 1; u1_init = 0;*/ for(i = 0; i < s_mvdirect.i1_num_partitions; i++) { partition_size = s_mvdirect.i1_partitionsize[i]; u1_sub_mb_num = s_mvdirect.i1_submb_num[i]; sub_partition = partition_size >> 2; partition_size &= 0x3; u1_mb_partw = pu1_mb_partw[partition_size]; u1_mb_parth = pu1_mb_parth[partition_size]; u2_mask = mask_table[partition_size]; if(sub_partition != 0) { u1_mb_partw >>= 1; u1_mb_parth >>= 1; u2_mask = sub_mask_table[partition_size]; } u1_col_zero_flag = *(ps_col_pic->pu1_col_zero_flag + s_mvdirect.i4_mv_indices[i]); u1_col_zero_flag = u1_col_zero_flag & 0x01; /*if(u1_col != u1_col_zero_flag) u1_init = 1;*/ if(u1_zero_pred_cond_f || ((i1_ref_frame0 == 0) && (u1_col_zero_flag == 1))) { pi2_final_mv0 = &i2_def_mv[0]; ui2_mask_fwd |= (u2_mask << u1_sub_mb_num); } else pi2_final_mv0 = &i2_spat_pred_mv[0]; if(u1_zero_pred_cond_b || ((i1_ref_frame1 == 0) && (u1_col_zero_flag == 1))) { pi2_final_mv1 = &i2_def_mv[0]; ui2_mask_bwd |= (u2_mask << u1_sub_mb_num); } else pi2_final_mv1 = &i2_spat_pred_mv[2]; if(ps_cur_mb_info->u1_Mux != 1) { /*u1_sub_mb_x = u1_sub_mb_num & 0x03; uc_sub_mb_y = (u1_sub_mb_num >> 2);*/ if(i1_ref_frame0 >= 0) { { pred_info_pkd_t *ps_pred_pkd; WORD8 i1_ref_idx= 0; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(pi2_final_mv0,u1_mb_partw,u1_mb_parth,u1_sub_mb_num,i1_pred, ps_pred_pkd,ps_pic_buff0->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, ps_pic_buff0->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } } if(i1_ref_frame1 >= 0) { { pred_info_pkd_t *ps_pred_pkd; WORD8 i1_ref_idx= 0; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(pi2_final_mv1,u1_mb_partw,u1_mb_parth,u1_sub_mb_num,i1_pred, ps_pred_pkd,ps_pic_buff1->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, ps_pic_buff1->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } } } /* Replication optimisation */ s_temp_mv_pred.i2_mv[0] = pi2_final_mv0[0]; s_temp_mv_pred.i2_mv[1] = pi2_final_mv0[1]; s_temp_mv_pred.i2_mv[2] = pi2_final_mv1[0]; s_temp_mv_pred.i2_mv[3] = pi2_final_mv1[1]; /* Calculating colocated zero information */ { WORD16 i2_mv_x = 0, i2_mv_y = 0; /*************************************/ /* If(bit2 and bit3 set) */ /* then */ /* (bit0 and bit1) => submmbmode */ /* (bit2 and bit3) => mbmode */ /* else */ /* (bit0 and bit1) => mbmode */ /*************************************/ UWORD8 u1_packed_mb_sub_mb_mode = sub_partition ? (s_mvdirect.i1_partitionsize[i]) : ((s_mvdirect.i1_partitionsize[i]) << 2); if(i1_ref_frame0 >= 0) { i2_mv_x = pi2_final_mv0[0]; i2_mv_y = pi2_final_mv0[1]; } else { i2_mv_x = pi2_final_mv1[0]; i2_mv_y = pi2_final_mv1[1]; } u1_colz = (ps_cur_mb_info->u1_mb_field_decodingflag << 1) | ((u1_final_ref_idx == 0) && (ABS(i2_mv_x) <= 1) && (ABS(i2_mv_y) <= 1)); u1_colz |= (u1_packed_mb_sub_mb_mode << 4); } ps_mv = ps_mv_nmb_start + u1_sub_mb_num; ih264d_rep_mv_colz(ps_dec, &s_temp_mv_pred, ps_mv, u1_sub_mb_num, u1_colz, u1_mb_parth, u1_mb_partw); } i = 0; if(i1_ref_frame0 >= 0) ps_cur_mb_info->u2_mask[i++] = ui2_mask_fwd; if(i1_ref_frame1 >= 0) ps_cur_mb_info->u2_mask[i] = ui2_mask_bwd; /*if(u1_init) H264_DEC_DEBUG_PRINT("hit\n"); else H264_DEC_DEBUG_PRINT("miss\n");*/ return OK; } /*! ************************************************************************** * \if Function name : ih264d_decode_temporal_direct \endif * * \brief * Decodes temporal direct mode. * * \return * None. * ************************************************************************** */ WORD32 ih264d_decode_temporal_direct(dec_struct_t * ps_dec, UWORD8 u1_wd_x, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num) { struct pic_buffer_t *ps_pic_buff0, *ps_pic_buff1, *ps_col_pic; mv_pred_t *ps_mv, s_temp_mv_pred; UWORD8 u1_sub_mb_num; UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; WORD16 i2_mv_x0, i2_mv_y0, i2_mv_x1, i2_mv_y1; UWORD8 u1_mb_partw, u1_mb_parth; UWORD8 i, partition_size, sub_partition; UWORD32 *pui32_weight_ofsts = NULL; directmv_t s_mvdirect; const UWORD8 *pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; const UWORD8 *pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; WORD8 c_refFrm0, c_refFrm1; UWORD8 u1_ref_idx0, u1_is_cur_mb_fld; WORD32 pic0_poc, pic1_poc, cur_poc; WORD32 ret = 0; u1_is_cur_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][0]; /**********************************************************************/ /* Call the function which gets the number of partitions and */ /* partition info of colocated Mb */ /**********************************************************************/ ps_dec->pf_parse_mvdirect(ps_dec, ps_dec->ps_col_pic, &s_mvdirect, u1_wd_x, ps_dec->i4_submb_ofst, ps_cur_mb_info); ps_col_pic = ps_dec->ps_col_pic; for(i = 0; i < s_mvdirect.i1_num_partitions; i++) { UWORD8 u1_colz; partition_size = s_mvdirect.i1_partitionsize[i]; u1_sub_mb_num = s_mvdirect.i1_submb_num[i]; ps_mv = ps_col_pic->ps_mv + s_mvdirect.i4_mv_indices[i]; /* This should be removed to catch unitialized memory read */ u1_ref_idx0 = 0; sub_partition = partition_size >> 2; partition_size &= 0x3; u1_mb_partw = pu1_mb_partw[partition_size]; u1_mb_parth = pu1_mb_parth[partition_size]; if(sub_partition != 0) { u1_mb_partw >>= 1; u1_mb_parth >>= 1; } c_refFrm0 = ps_mv->i1_ref_frame[0]; c_refFrm1 = ps_mv->i1_ref_frame[1]; if((c_refFrm0 == -1) && (c_refFrm1 == -1)) { u1_ref_idx0 = 0; ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][0]; if(u1_mbaff && u1_is_cur_mb_fld) { if(ps_cur_mb_info->u1_topmb) { pic0_poc = ps_pic_buff0->i4_top_field_order_cnt; pic1_poc = ps_pic_buff1->i4_top_field_order_cnt; cur_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; } else { pic1_poc = ps_pic_buff1->i4_bottom_field_order_cnt; cur_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][MAX_REF_BUFS]; pic0_poc = ps_pic_buff0->i4_bottom_field_order_cnt; ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][MAX_REF_BUFS]; } } else { pic0_poc = ps_pic_buff0->i4_avg_poc; pic1_poc = ps_pic_buff1->i4_avg_poc; cur_poc = ps_dec->ps_cur_pic->i4_poc; } } else { UWORD8 uc_i, u1_num_frw_ref_pics; UWORD8 buf_id, u1_pic_type; buf_id = ps_mv->u1_col_ref_pic_idx; u1_pic_type = ps_mv->u1_pic_type; if(ps_dec->ps_cur_slice->u1_field_pic_flag) { if(s_mvdirect.u1_vert_mv_scale == FRM_TO_FLD) { u1_pic_type = TOP_FLD; if(ps_dec->ps_cur_slice->u1_bottom_field_flag) u1_pic_type = BOT_FLD; } } u1_num_frw_ref_pics = ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; for(uc_i = 0; uc_i < u1_num_frw_ref_pics; uc_i++) { if(ps_dec->ps_cur_slice->u1_field_pic_flag) { if(ps_dec->ps_ref_pic_buf_lx[0][uc_i]->u1_mv_buf_id == buf_id) { if(ps_dec->ps_ref_pic_buf_lx[0][uc_i]->u1_pic_type == u1_pic_type) { u1_ref_idx0 = uc_i; break; } } } else { if(ps_dec->ps_ref_pic_buf_lx[0][uc_i]->u1_mv_buf_id == buf_id) { u1_ref_idx0 = uc_i; break; } } } ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][u1_ref_idx0]; ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][0]; if(u1_mbaff && u1_is_cur_mb_fld) { pic0_poc = ps_pic_buff0->i4_top_field_order_cnt; u1_ref_idx0 <<= 1; if(s_mvdirect.u1_vert_mv_scale == ONE_TO_ONE) { if(u1_pic_type == BOT_FLD) { pic0_poc = ps_pic_buff0->i4_bottom_field_order_cnt; ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][(u1_ref_idx0 >> 1) + MAX_REF_BUFS]; if(ps_cur_mb_info->u1_topmb) u1_ref_idx0++; } else { if(1 - ps_cur_mb_info->u1_topmb) u1_ref_idx0++; } } if(s_mvdirect.u1_vert_mv_scale == FRM_TO_FLD) { if(1 - ps_cur_mb_info->u1_topmb) { pic0_poc = ps_pic_buff0->i4_bottom_field_order_cnt; ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][(u1_ref_idx0 >> 1) + MAX_REF_BUFS]; } } if(ps_cur_mb_info->u1_topmb) { pic1_poc = ps_pic_buff1->i4_top_field_order_cnt; cur_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; } else { pic1_poc = ps_pic_buff1->i4_bottom_field_order_cnt; cur_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][MAX_REF_BUFS]; } } else { pic0_poc = ps_pic_buff0->i4_avg_poc; pic1_poc = ps_pic_buff1->i4_avg_poc; cur_poc = ps_dec->ps_cur_pic->i4_poc; } } { WORD16 i16_td; WORD64 diff; if(c_refFrm0 >= 0) { i2_mv_x0 = ps_mv->i2_mv[0]; i2_mv_y0 = ps_mv->i2_mv[1]; } else if(c_refFrm1 >= 0) { i2_mv_x0 = ps_mv->i2_mv[2]; i2_mv_y0 = ps_mv->i2_mv[3]; } else { i2_mv_x0 = 0; i2_mv_y0 = 0; } /* If FRM_TO_FLD or FLD_TO_FRM scale the "y" component of the colocated Mv*/ if(s_mvdirect.u1_vert_mv_scale == FRM_TO_FLD) { i2_mv_y0 /= 2; } else if(s_mvdirect.u1_vert_mv_scale == FLD_TO_FRM) { i2_mv_y0 *= 2; } diff = (WORD64)pic1_poc - pic0_poc; i16_td = CLIP_S8(diff); if((ps_pic_buff0->u1_is_short == 0) || (i16_td == 0)) { i2_mv_x1 = 0; i2_mv_y1 = 0; } else { WORD16 i2_tb, i2_tx, i2_dist_scale_factor, i2_temp; diff = (WORD64)cur_poc - pic0_poc; i2_tb = CLIP_S8(diff); i2_tx = (16384 + ABS(SIGN_POW2_DIV(i16_td, 1))) / i16_td; i2_dist_scale_factor = CLIP_S11( (((i2_tb * i2_tx) + 32) >> 6)); i2_temp = (i2_mv_x0 * i2_dist_scale_factor + 128) >> 8; i2_mv_x1 = i2_temp - i2_mv_x0; i2_mv_x0 = i2_temp; i2_temp = (i2_mv_y0 * i2_dist_scale_factor + 128) >> 8; i2_mv_y1 = i2_temp - i2_mv_y0; i2_mv_y0 = i2_temp; } { mv_pred_t *ps_mv; /*u1_sub_mb_x = u1_sub_mb_num & 0x03; uc_sub_mb_y = u1_sub_mb_num >> 2;*/ if(ps_dec->ps_cur_pps->u1_wted_bipred_idc) { UWORD8 u1_idx = u1_ref_idx0 * ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; UWORD8 u1_scale_ref = u1_mbaff && u1_is_cur_mb_fld; if(u1_scale_ref) u1_idx >>= 1; pui32_weight_ofsts = (UWORD32*)&ps_dec->pu4_wt_ofsts[2 * X3(u1_idx)]; if(u1_scale_ref && (ps_dec->ps_cur_pps->u1_wted_bipred_idc == 2)) { WORD16 i2_ref_idx; i2_ref_idx = u1_ref_idx0; i2_ref_idx *= (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] << 1); if(!ps_cur_mb_info->u1_topmb) i2_ref_idx += (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0] << 1) * (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] << 1); pui32_weight_ofsts = (UWORD32*)&ps_dec->pu4_mbaff_wt_mat[2 * X3(i2_ref_idx)]; } } { pred_info_pkd_t *ps_pred_pkd; WORD16 i2_mv[2]; WORD8 i1_ref_idx= 0; i2_mv[0] = i2_mv_x0; i2_mv[1] = i2_mv_y0; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(i2_mv,u1_mb_partw,u1_mb_parth,u1_sub_mb_num,PRED_L0 | PRED_L1, ps_pred_pkd,ps_pic_buff0->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, ps_pic_buff0->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } { pred_info_pkd_t *ps_pred_pkd; WORD16 i2_mv[2]; WORD8 i1_ref_idx= 0; i2_mv[0] = i2_mv_x1; i2_mv[1] = i2_mv_y1; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info(i2_mv,u1_mb_partw,u1_mb_parth,u1_sub_mb_num,PRED_L0 | PRED_L1, ps_pred_pkd,ps_pic_buff1->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, ps_pic_buff1->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } /* Replication optimisation */ s_temp_mv_pred.i2_mv[0] = i2_mv_x0; s_temp_mv_pred.i2_mv[1] = i2_mv_y0; s_temp_mv_pred.i2_mv[2] = i2_mv_x1; s_temp_mv_pred.i2_mv[3] = i2_mv_y1; s_temp_mv_pred.i1_ref_frame[0] = u1_ref_idx0; s_temp_mv_pred.i1_ref_frame[1] = 0; s_temp_mv_pred.u1_col_ref_pic_idx = ps_pic_buff0->u1_mv_buf_id; s_temp_mv_pred.u1_pic_type = ps_pic_buff0->u1_pic_type; ps_mv = ps_dec->ps_mv_cur + (u1_mb_num << 4) + u1_sub_mb_num; { WORD16 i2_mv_x = 0, i2_mv_y = 0; UWORD8 u1_packed_mb_sub_mb_mode = sub_partition ? (s_mvdirect.i1_partitionsize[i]) : ((s_mvdirect.i1_partitionsize[i]) << 2); if(c_refFrm0 >= 0) { i2_mv_x = i2_mv_x0; i2_mv_y = i2_mv_y0; } else { i2_mv_x = i2_mv_x1; i2_mv_y = i2_mv_y1; } u1_colz = (ps_cur_mb_info->u1_mb_field_decodingflag << 1) | ((u1_ref_idx0 == 0) && (ABS(i2_mv_x) <= 1) && (ABS(i2_mv_y) <= 1)); u1_colz |= (u1_packed_mb_sub_mb_mode << 4); } ih264d_rep_mv_colz(ps_dec, &s_temp_mv_pred, ps_mv, u1_sub_mb_num, u1_colz, u1_mb_parth, u1_mb_partw); } } } /* return value set to UWORD8 to make it homogeneous */ /* with decodespatialdirect */ return OK; } void ih264d_convert_frm_to_fld_list(struct pic_buffer_t *ps_ref_pic_buf_lx, UWORD8 *pu1_L0, dec_struct_t *ps_dec, UWORD8 u1_num_short_term_bufs) { UWORD8 uc_count = *pu1_L0, i, uc_l1, uc_lx, j; struct pic_buffer_t *ps_ref_lx[2], *ps_ref_pic_lx; UWORD8 u1_bottom_field_flag; dec_slice_params_t *ps_cur_slice; UWORD8 u1_ref[2], u1_fld[2], u1_same_fld, u1_op_fld; UWORD32 ui_half_num_of_sub_mbs; uc_l1 = 0; uc_lx = 0; ps_cur_slice = ps_dec->ps_cur_slice; ps_ref_pic_lx = ps_ref_pic_buf_lx - MAX_REF_BUFS; ps_ref_lx[0] = ps_ref_pic_buf_lx; ps_ref_lx[1] = ps_ref_pic_buf_lx; u1_bottom_field_flag = ps_cur_slice->u1_bottom_field_flag; ui_half_num_of_sub_mbs = ((ps_dec->u2_pic_ht * ps_dec->u2_pic_wd) >> 5); if(u1_bottom_field_flag) { u1_ref[0] = BOT_REF; u1_ref[1] = TOP_REF; u1_fld[0] = BOT_FLD; u1_fld[1] = TOP_FLD; u1_same_fld = BOT_FLD; u1_op_fld = TOP_FLD; } else { u1_ref[0] = TOP_REF; u1_ref[1] = BOT_REF; u1_fld[0] = TOP_FLD; u1_fld[1] = BOT_FLD; u1_same_fld = TOP_FLD; u1_op_fld = BOT_FLD; } /* Create the field list starting with all the short term */ /* frames followed by all the long term frames. No long term */ /* reference field should have a list idx less than a short */ /* term reference field during initiailization. */ for(j = 0; j < 2; j++) { i = ((j == 0) ? 0 : u1_num_short_term_bufs); uc_count = ((j == 0) ? u1_num_short_term_bufs : *pu1_L0); for(; i < uc_count; i++, ps_ref_lx[0]++) { /* Search field of same parity in Frame list */ if((ps_ref_lx[0]->u1_pic_type & u1_ref[0])) // || ((ps_ref_lx[0]->u1_picturetype & 0x3) == 0)) { /* Insert PIC of same parity in RefPicList */ ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_lx, ps_ref_lx[0]); ps_ref_pic_lx->i4_pic_num = (ps_ref_pic_lx->i4_pic_num * 2 + 1); ps_ref_pic_lx->u1_long_term_pic_num = (ps_ref_pic_lx->u1_long_term_frm_idx * 2 + 1); ps_ref_pic_lx->u1_pic_type = u1_same_fld; if(u1_fld[0] & BOT_FLD) { ps_ref_pic_lx->u1_pic_type = BOT_FLD; ps_ref_pic_lx->pu1_buf1 += ps_ref_pic_lx->u2_frm_wd_y; ps_ref_pic_lx->pu1_buf2 += ps_ref_pic_lx->u2_frm_wd_uv; ps_ref_pic_lx->pu1_buf3 += ps_ref_pic_lx->u2_frm_wd_uv; if(ps_ref_pic_lx->u1_picturetype & 0x3) { ps_ref_pic_lx->pu1_col_zero_flag += ui_half_num_of_sub_mbs; ps_ref_pic_lx->ps_mv += ui_half_num_of_sub_mbs; } ps_ref_pic_lx->i4_poc = ps_ref_pic_lx->i4_bottom_field_order_cnt; ps_ref_pic_lx->i4_avg_poc = ps_ref_pic_lx->i4_bottom_field_order_cnt; } else { ps_ref_pic_lx->u1_pic_type = TOP_FLD; ps_ref_pic_lx->i4_poc = ps_ref_pic_lx->i4_top_field_order_cnt; ps_ref_pic_lx->i4_avg_poc = ps_ref_pic_lx->i4_top_field_order_cnt; } ps_ref_pic_lx++; uc_lx++; /* Find field of opposite parity */ if(uc_l1 < uc_count && ps_ref_lx[1]) { while(!(ps_ref_lx[1]->u1_pic_type & u1_ref[1])) { ps_ref_lx[1]++; uc_l1++; if(uc_l1 >= uc_count) ps_ref_lx[1] = 0; if(!ps_ref_lx[1]) break; } if(ps_ref_lx[1]) { uc_l1++; ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_lx, ps_ref_lx[1]); ps_ref_pic_lx->u1_pic_type = u1_op_fld; ps_ref_pic_lx->i4_pic_num = (ps_ref_pic_lx->i4_pic_num * 2); ps_ref_pic_lx->u1_long_term_pic_num = (ps_ref_pic_lx->u1_long_term_frm_idx * 2); if(u1_fld[1] & BOT_FLD) { ps_ref_pic_lx->u1_pic_type = BOT_FLD; ps_ref_pic_lx->pu1_buf1 += ps_ref_pic_lx->u2_frm_wd_y; ps_ref_pic_lx->pu1_buf2 += ps_ref_pic_lx->u2_frm_wd_uv; ps_ref_pic_lx->pu1_buf3 += ps_ref_pic_lx->u2_frm_wd_uv; if(ps_ref_pic_lx->u1_picturetype & 0x3) { ps_ref_pic_lx->pu1_col_zero_flag += ui_half_num_of_sub_mbs; ps_ref_pic_lx->ps_mv += ui_half_num_of_sub_mbs; } ps_ref_pic_lx->i4_poc = ps_ref_pic_lx->i4_bottom_field_order_cnt; ps_ref_pic_lx->i4_avg_poc = ps_ref_pic_lx->i4_bottom_field_order_cnt; } else { ps_ref_pic_lx->u1_pic_type = TOP_FLD; ps_ref_pic_lx->i4_poc = ps_ref_pic_lx->i4_top_field_order_cnt; ps_ref_pic_lx->i4_avg_poc = ps_ref_pic_lx->i4_top_field_order_cnt; } ps_ref_pic_lx++; uc_lx++; ps_ref_lx[1]++; } } } } /* Same parity fields are over, now insert left over opposite parity fields */ /** Added if(ps_ref_lx[1]) for error checks */ if(ps_ref_lx[1]) { for(; uc_l1 < uc_count; uc_l1++) { if(ps_ref_lx[1]->u1_pic_type & u1_ref[1]) { /* Insert PIC of opposite parity in RefPicList */ ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_lx, ps_ref_lx[1]); ps_ref_pic_lx->u1_pic_type = u1_op_fld; ps_ref_pic_lx->i4_pic_num = (ps_ref_pic_lx->i4_pic_num * 2); ps_ref_pic_lx->u1_long_term_pic_num = (ps_ref_pic_lx->u1_long_term_frm_idx * 2); if(u1_op_fld == BOT_FLD) { ps_ref_pic_lx->u1_pic_type = BOT_FLD; ps_ref_pic_lx->pu1_buf1 += ps_ref_pic_lx->u2_frm_wd_y; ps_ref_pic_lx->pu1_buf2 += ps_ref_pic_lx->u2_frm_wd_uv; ps_ref_pic_lx->pu1_buf3 += ps_ref_pic_lx->u2_frm_wd_uv; if(ps_ref_pic_lx->u1_picturetype & 0x3) { ps_ref_pic_lx->pu1_col_zero_flag += ui_half_num_of_sub_mbs; ps_ref_pic_lx->ps_mv += ui_half_num_of_sub_mbs; } ps_ref_pic_lx->i4_poc = ps_ref_pic_lx->i4_bottom_field_order_cnt; ps_ref_pic_lx->i4_avg_poc = ps_ref_pic_lx->i4_bottom_field_order_cnt; } else { ps_ref_pic_lx->i4_poc = ps_ref_pic_lx->i4_top_field_order_cnt; ps_ref_pic_lx->i4_avg_poc = ps_ref_pic_lx->i4_top_field_order_cnt; } ps_ref_pic_lx++; uc_lx++; ps_ref_lx[1]++; } } } } *pu1_L0 = uc_lx; } void ih264d_convert_frm_mbaff_list(dec_struct_t *ps_dec) { struct pic_buffer_t **ps_ref_pic_lx; UWORD8 u1_max_ref_idx, idx; UWORD16 u2_frm_wd_y, u2_frm_wd_uv; struct pic_buffer_t **ps_ref_pic_buf_lx; UWORD32 u4_half_num_of_sub_mbs = ((ps_dec->u2_pic_ht * ps_dec->u2_pic_wd) >> 5); ps_ref_pic_buf_lx = ps_dec->ps_ref_pic_buf_lx[0]; ps_ref_pic_lx = ps_dec->ps_ref_pic_buf_lx[0]; u1_max_ref_idx = ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; for(idx = 0; idx < u1_max_ref_idx; idx++) { ps_ref_pic_lx[idx]->u1_pic_type = TOP_FLD; ps_ref_pic_lx[idx]->i4_poc = ps_ref_pic_lx[idx]->i4_top_field_order_cnt; } u2_frm_wd_y = ps_dec->u2_frm_wd_y; u2_frm_wd_uv = ps_dec->u2_frm_wd_uv; for(idx = 0; idx < u1_max_ref_idx; idx++) { *ps_ref_pic_lx[idx + MAX_REF_BUFS] = *ps_ref_pic_buf_lx[idx]; ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf1 = ps_ref_pic_buf_lx[idx]->pu1_buf1 + u2_frm_wd_y; ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf2 = ps_ref_pic_buf_lx[idx]->pu1_buf2 + u2_frm_wd_uv; ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf3 = ps_ref_pic_buf_lx[idx]->pu1_buf3 + u2_frm_wd_uv; ps_ref_pic_lx[idx + MAX_REF_BUFS]->u1_pic_type = BOT_FLD; ps_ref_pic_lx[idx + MAX_REF_BUFS]->i4_poc = ps_ref_pic_buf_lx[idx]->i4_bottom_field_order_cnt; if(ps_ref_pic_buf_lx[idx]->u1_picturetype & 0x3) { ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_col_zero_flag = ps_ref_pic_buf_lx[idx]->pu1_col_zero_flag + u4_half_num_of_sub_mbs; ps_ref_pic_lx[idx + MAX_REF_BUFS]->ps_mv = ps_ref_pic_buf_lx[idx]->ps_mv + u4_half_num_of_sub_mbs; } } if(ps_dec->u1_B) { ps_ref_pic_buf_lx = ps_dec->ps_ref_pic_buf_lx[1]; ps_ref_pic_lx = ps_dec->ps_ref_pic_buf_lx[1]; u1_max_ref_idx = ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; for(idx = 0; idx < u1_max_ref_idx; idx++) { ps_ref_pic_lx[idx]->u1_pic_type = TOP_FLD; ps_ref_pic_lx[idx]->i4_poc = ps_ref_pic_lx[idx]->i4_top_field_order_cnt; } for(idx = 0; idx < u1_max_ref_idx; idx++) { *ps_ref_pic_lx[idx + MAX_REF_BUFS] = *ps_ref_pic_buf_lx[idx]; ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf1 = ps_ref_pic_buf_lx[idx]->pu1_buf1 + u2_frm_wd_y; ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf2 = ps_ref_pic_buf_lx[idx]->pu1_buf2 + u2_frm_wd_uv; ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf3 = ps_ref_pic_buf_lx[idx]->pu1_buf3 + u2_frm_wd_uv; ps_ref_pic_lx[idx + MAX_REF_BUFS]->u1_pic_type = BOT_FLD; ps_ref_pic_lx[idx + MAX_REF_BUFS]->i4_poc = ps_ref_pic_buf_lx[idx]->i4_bottom_field_order_cnt; if(ps_ref_pic_buf_lx[idx]->u1_picturetype & 0x3) { ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_col_zero_flag = ps_ref_pic_buf_lx[idx]->pu1_col_zero_flag + u4_half_num_of_sub_mbs; ps_ref_pic_lx[idx + MAX_REF_BUFS]->ps_mv = ps_ref_pic_buf_lx[idx]->ps_mv + u4_half_num_of_sub_mbs; } } } } static int poc_compare(const void *pv_pic1, const void *pv_pic2) { struct pic_buffer_t *ps_pic1 = *(struct pic_buffer_t **) pv_pic1; struct pic_buffer_t *ps_pic2 = *(struct pic_buffer_t **) pv_pic2; if (ps_pic1->i4_poc < ps_pic2->i4_poc) { return -1; } else if (ps_pic1->i4_poc > ps_pic2->i4_poc) { return 1; } else { return 0; } } /*! ************************************************************************** * \if Function name : ih264d_init_ref_idx_lx_b \endif * * \brief * Initializes forward and backward refernce lists for B slice decoding. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ void ih264d_init_ref_idx_lx_b(dec_struct_t *ps_dec) { struct pic_buffer_t *ps_ref_pic_buf_lx; dpb_manager_t *ps_dpb_mgr; struct dpb_info_t *ps_next_dpb; WORD32 i_cur_poc, i_max_st_poc, i_min_st_poc, i_ref_poc, i_temp_poc; WORD8 i, j; UWORD8 u1_max_lt_index, u1_min_lt_index; UWORD32 u4_lt_index; WORD32 i_cur_idx; UWORD8 u1_field_pic_flag; dec_slice_params_t *ps_cur_slice; UWORD8 u1_L0, u1_L1; UWORD8 u1_num_short_term_bufs; UWORD8 u1_max_ref_idx_l0, u1_max_ref_idx_l1; struct pic_buffer_t *aps_st_pic_bufs[2 * MAX_REF_BUFS] = {NULL}; ps_cur_slice = ps_dec->ps_cur_slice; u1_field_pic_flag = ps_cur_slice->u1_field_pic_flag; u1_max_ref_idx_l0 = ps_cur_slice->u1_num_ref_idx_lx_active[0] << u1_field_pic_flag; u1_max_ref_idx_l1 = ps_cur_slice->u1_num_ref_idx_lx_active[1] << u1_field_pic_flag; ps_dpb_mgr = ps_dec->ps_dpb_mgr; /* Get the current POC */ i_cur_poc = ps_dec->ps_cur_pic->i4_poc; /* Get MaxStPOC,MinStPOC,MaxLt,MinLt */ i_max_st_poc = i_cur_poc; i_min_st_poc = i_cur_poc; u1_max_lt_index = MAX_REF_BUFS + 1; u1_min_lt_index = MAX_REF_BUFS + 1; /* Start from ST head */ ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; for(i = 0; i < ps_dpb_mgr->u1_num_st_ref_bufs; i++) { i_ref_poc = ps_next_dpb->ps_pic_buf->i4_poc; if(i_ref_poc < i_cur_poc) { /* RefPic Buf POC is before Current POC in display order */ i_min_st_poc = MIN(i_min_st_poc, i_ref_poc); } else { /* RefPic Buf POC is after Current POC in display order */ i_max_st_poc = MAX(i_max_st_poc, i_ref_poc); } /* Chase the next link */ ps_next_dpb = ps_next_dpb->ps_prev_short; } /* Sort ST ref pocs in ascending order */ ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; for (j = 0; j < ps_dpb_mgr->u1_num_st_ref_bufs; j++) { aps_st_pic_bufs[j] = ps_next_dpb->ps_pic_buf; ps_next_dpb = ps_next_dpb->ps_prev_short; } qsort(aps_st_pic_bufs, ps_dpb_mgr->u1_num_st_ref_bufs, sizeof(aps_st_pic_bufs[0]), poc_compare); /* Start from LT head */ ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; if(ps_next_dpb) { u1_max_lt_index = ps_next_dpb->u1_lt_idx; u1_min_lt_index = ps_next_dpb->u1_lt_idx; } for(i = 0; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) { u4_lt_index = ps_next_dpb->u1_lt_idx; u1_max_lt_index = (UWORD8)(MAX(u1_max_lt_index, u4_lt_index)); u1_min_lt_index = (UWORD8)(MIN(u1_min_lt_index, u4_lt_index)); /* Chase the next link */ ps_next_dpb = ps_next_dpb->ps_prev_long; } /* 1. Initialize refIdxL0 */ u1_L0 = 0; i_temp_poc = i_cur_poc; if(u1_field_pic_flag) { ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0]; ps_ref_pic_buf_lx += MAX_REF_BUFS; } else { ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0]; /* Avoid integer overflow while decrementing by one */ if (i_temp_poc > INT32_MIN) i_temp_poc--; } i_cur_idx = -1; for(j = 0; j < ps_dpb_mgr->u1_num_st_ref_bufs; j++) { if (NULL == aps_st_pic_bufs[j]) { break; } if (aps_st_pic_bufs[j]->i4_poc <= i_temp_poc) { i_cur_idx = j; } } /* Arrange all short term buffers in output order as given by POC */ /* 1.1 Arrange POC's less than CurrPOC in the descending POC order starting from (CurrPOC - 1)*/ for(j = i_cur_idx; j >= 0; j--) { if(aps_st_pic_bufs[j]) { /* Copy info in pic buffer */ ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, aps_st_pic_bufs[j]); ps_ref_pic_buf_lx++; u1_L0++; } } /* 1.2. Arrange POC's more than CurrPOC in the ascending POC order starting from (CurrPOC + 1)*/ for(j = i_cur_idx + 1; j < ps_dpb_mgr->u1_num_st_ref_bufs; j++) { if(aps_st_pic_bufs[j]) { ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, aps_st_pic_bufs[j]); ps_ref_pic_buf_lx++; u1_L0++; } } /* 1.3 Arrange all Long term buffers in ascending order, in LongtermIndex */ /* Start from ST head */ u1_num_short_term_bufs = u1_L0; for(u4_lt_index = u1_min_lt_index; u4_lt_index <= u1_max_lt_index; u4_lt_index++) { ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; for(i = 0; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) { if(ps_next_dpb->u1_lt_idx == u4_lt_index) { ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, ps_next_dpb->ps_pic_buf); ps_ref_pic_buf_lx->u1_long_term_pic_num = ps_ref_pic_buf_lx->u1_long_term_frm_idx; ps_ref_pic_buf_lx++; u1_L0++; break; } ps_next_dpb = ps_next_dpb->ps_prev_long; } } if(u1_field_pic_flag) { /* Initialize the rest of the entries in the */ /* reference list to handle of errors */ { UWORD8 u1_i; pic_buffer_t ref_pic; ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0] + MAX_REF_BUFS); if(NULL == ref_pic.pu1_buf1) { ref_pic = *ps_dec->ps_cur_pic; } for(u1_i = u1_L0; u1_i < u1_max_ref_idx_l0; u1_i++) { *ps_ref_pic_buf_lx = ref_pic; ps_ref_pic_buf_lx++; } } ih264d_convert_frm_to_fld_list( ps_dpb_mgr->ps_init_dpb[0][0] + MAX_REF_BUFS, &u1_L0, ps_dec, u1_num_short_term_bufs); ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0] + u1_L0; } ps_dec->ps_cur_slice->u1_initial_list_size[0] = u1_L0; /* Initialize the rest of the entries in the */ /* reference list to handle of errors */ { UWORD8 u1_i; pic_buffer_t ref_pic; ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0]); if(NULL == ref_pic.pu1_buf1) { ref_pic = *ps_dec->ps_cur_pic; } for(u1_i = u1_L0; u1_i < u1_max_ref_idx_l0; u1_i++) { *ps_ref_pic_buf_lx = ref_pic; ps_ref_pic_buf_lx++; } } { /* 2. Initialize refIdxL1 */ u1_L1 = 0; if(u1_field_pic_flag) { ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[1][0] + MAX_REF_BUFS; } else { ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[1][0]; } /* 2.1. Arrange POC's more than CurrPOC in the ascending POC order starting from (CurrPOC + 1)*/ for(j = i_cur_idx + 1; j < ps_dpb_mgr->u1_num_st_ref_bufs; j++) { if(aps_st_pic_bufs[j]) { /* Start from ST head */ ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, aps_st_pic_bufs[j]); ps_ref_pic_buf_lx++; u1_L1++; } } /* Arrange all short term buffers in output order as given by POC */ /* 2.2 Arrange POC's less than CurrPOC in the descending POC order starting from (CurrPOC - 1)*/ for(j = i_cur_idx; j >= 0; j--) { if(aps_st_pic_bufs[j]) { ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, aps_st_pic_bufs[j]); ps_ref_pic_buf_lx++; u1_L1++; } } /* 2.3 Arrange all Long term buffers in ascending order, in LongtermIndex */ /* Start from ST head */ u1_num_short_term_bufs = u1_L1; for(u4_lt_index = u1_min_lt_index; u4_lt_index <= u1_max_lt_index; u4_lt_index++) { ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; for(i = 0; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) { if(ps_next_dpb->u1_lt_idx == u4_lt_index) { ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, ps_next_dpb->ps_pic_buf); ps_ref_pic_buf_lx->u1_long_term_pic_num = ps_ref_pic_buf_lx->u1_long_term_frm_idx; ps_ref_pic_buf_lx++; u1_L1++; break; } ps_next_dpb = ps_next_dpb->ps_prev_long; } } if(u1_field_pic_flag) { /* Initialize the rest of the entries in the */ /* reference list to handle of errors */ { UWORD8 u1_i; pic_buffer_t ref_pic; ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0] + MAX_REF_BUFS); if(NULL == ref_pic.pu1_buf1) { ref_pic = *ps_dec->ps_cur_pic; } for(u1_i = u1_L1; u1_i < u1_max_ref_idx_l1; u1_i++) { *ps_ref_pic_buf_lx = ref_pic; ps_ref_pic_buf_lx++; } } ih264d_convert_frm_to_fld_list( ps_dpb_mgr->ps_init_dpb[1][0] + MAX_REF_BUFS, &u1_L1, ps_dec, u1_num_short_term_bufs); ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[1][0] + u1_L1; } ps_dec->ps_cur_slice->u1_initial_list_size[1] = u1_L1; /* Initialize the rest of the entries in the */ /* reference list to handle of errors */ { UWORD8 u1_i; pic_buffer_t ref_pic; ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0]); if(NULL == ref_pic.pu1_buf1) { ref_pic = *ps_dec->ps_cur_pic; } for(u1_i = u1_L1; u1_i < u1_max_ref_idx_l1; u1_i++) { *ps_ref_pic_buf_lx = ref_pic; ps_ref_pic_buf_lx++; } } /* If list0 and list 1 ebtries are same then swap the 0th and 1st entry */ /* of list 1 */ { struct pic_buffer_t *ps_ref_pic1_buf_l0, *ps_ref_pic1_buf_l1; struct pic_buffer_t s_ref_pic1_buf_temp; ps_ref_pic1_buf_l0 = ps_dpb_mgr->ps_init_dpb[0][0]; ps_ref_pic1_buf_l1 = ps_dpb_mgr->ps_init_dpb[1][0]; if((u1_L0 == u1_L1) && (u1_L0 > 1)) { WORD32 i_index, i_swap; i_swap = 1; for(i_index = 0; i_index < u1_L0; i_index++) { if((ps_ref_pic1_buf_l0[i_index]).pu1_buf1 != (ps_ref_pic1_buf_l1[i_index]).pu1_buf1) { i_swap = 0; break; } } if(1 == i_swap) { memcpy(&s_ref_pic1_buf_temp, &ps_ref_pic1_buf_l1[1], sizeof(struct pic_buffer_t)); memcpy(&ps_ref_pic1_buf_l1[1], &ps_ref_pic1_buf_l1[0], sizeof(struct pic_buffer_t)); memcpy(&ps_ref_pic1_buf_l1[0], &s_ref_pic1_buf_temp, sizeof(struct pic_buffer_t)); } } } } } void ih264d_get_implicit_weights(dec_struct_t *ps_dec); /*! ************************************************************************** * \if Function name : ih264d_one_to_one \endif * * \brief * Initializes forward and backward refernce lists for B slice decoding. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ void ih264d_one_to_one(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info) { UWORD8 *pu1_col_zero_flag_start, u1_col_mb_pred_mode, u1_num_blks, u1_sub_mb_num; UWORD8 u1_init_colzero_flag; UNUSED(ps_cur_mb_info); pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; u1_col_mb_pred_mode = pu1_col_zero_flag_start[ps_dec->u1_sub_mb_num]; u1_init_colzero_flag = u1_col_mb_pred_mode & 1; u1_col_mb_pred_mode >>= 6; ps_direct->u1_vert_mv_scale = ONE_TO_ONE; ps_direct->u1_col_zeroflag_change = 0; if(u1_wd_x == MB_SIZE) { ps_dec->u1_currB_type = (!!u1_col_mb_pred_mode); if(u1_col_mb_pred_mode == PRED_16x16) { ps_direct->i1_num_partitions = 1; ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; ps_direct->i1_submb_num[0] = 0; ps_direct->i1_partitionsize[0] = PRED_16x16; return; } else if(u1_col_mb_pred_mode < PRED_8x8) { ps_direct->i1_num_partitions = 2; ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; ps_direct->i1_submb_num[0] = 0; ps_direct->i1_partitionsize[0] = u1_col_mb_pred_mode; u1_sub_mb_num = (u1_col_mb_pred_mode == PRED_16x8) ? 8 : 2; ps_direct->i1_submb_num[1] = u1_sub_mb_num; ps_direct->i4_mv_indices[1] = u2_sub_mb_ofst + ps_direct->i1_submb_num[1]; ps_direct->i1_partitionsize[1] = u1_col_mb_pred_mode; if((pu1_col_zero_flag_start[u1_sub_mb_num] & 1) != u1_init_colzero_flag) ps_direct->u1_col_zeroflag_change = 1; return; } else { u1_num_blks = 4; } } else { u1_num_blks = 1; } { const UWORD8 *pu1_top_lt_mb_part_idx; UWORD8 u1_col_sub_mb_pred_mode, uc_blk, u1_sub_blk, u1_submb_col = 0; UWORD8 u1_num_sub_blks, uc_direct8x8inf, *pu1_col_zero_flag, u1_sub_mb_num; const UWORD8 *pu1_num_sub_mb_part = (const UWORD8 *)gau1_ih264d_num_submb_part; UWORD8 i1_num_partitions = 0, partition_size; WORD32 mv_index; const UWORD8 *pu1_top_lt_sub_mb_idx = gau1_ih264d_submb_indx_mod_sp_drct; u1_sub_mb_num = ps_dec->u1_sub_mb_num; uc_direct8x8inf = ps_dec->ps_cur_slice->u1_direct_8x8_inference_flag; pu1_top_lt_mb_part_idx = gau1_ih264d_top_left_mb_part_indx_mod + (PRED_8x8 << 1) + 1; for(uc_blk = 0; uc_blk < u1_num_blks; uc_blk++) { partition_size = PRED_8x8; pu1_top_lt_sub_mb_idx = gau1_ih264d_submb_indx_mod_sp_drct; if(uc_direct8x8inf == 1) { u1_submb_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); mv_index = u2_sub_mb_ofst + u1_submb_col; u1_num_sub_blks = 1; } else { /* colMbPart is either 8x8, 8x4, 4x8, 4x4 */ pu1_col_zero_flag = pu1_col_zero_flag_start + u1_sub_mb_num; u1_col_sub_mb_pred_mode = *pu1_col_zero_flag; u1_col_sub_mb_pred_mode = (u1_col_sub_mb_pred_mode & 0x30) >> 4; partition_size = (UWORD8)((u1_col_sub_mb_pred_mode) | (PRED_8x8 << 2)); mv_index = u2_sub_mb_ofst + u1_sub_mb_num; pu1_top_lt_sub_mb_idx += (u1_col_sub_mb_pred_mode << 1); u1_num_sub_blks = pu1_num_sub_mb_part[u1_col_sub_mb_pred_mode]; } for(u1_sub_blk = 0; u1_sub_blk < u1_num_sub_blks; u1_sub_blk++, pu1_top_lt_sub_mb_idx++) { u1_sub_mb_num += *pu1_top_lt_sub_mb_idx; mv_index += *pu1_top_lt_sub_mb_idx; ps_direct->i4_mv_indices[i1_num_partitions] = mv_index; ps_direct->i1_submb_num[i1_num_partitions] = u1_sub_mb_num; ps_direct->i1_partitionsize[i1_num_partitions] = partition_size; i1_num_partitions++; if(!uc_direct8x8inf) u1_submb_col = u1_sub_mb_num; if((pu1_col_zero_flag_start[u1_submb_col] & 1) != u1_init_colzero_flag) ps_direct->u1_col_zeroflag_change = 1; } u1_sub_mb_num = *pu1_top_lt_mb_part_idx++; } ps_direct->i1_num_partitions = i1_num_partitions; } } /*! ************************************************************************** * \if Function name : ih264d_mbaff_cross_pmbair \endif * * \brief * Initializes forward and backward refernce lists for B slice decoding. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ void ih264d_mbaff_cross_pmbair(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info) { UWORD8 *pu1_col_zero_flag_start, *pu1_col_zero_flag, u1_sub_mb_num, uc_sub_mb_num_col; UWORD8 *pu1_col_zero_flag_right_half; WORD32 i4_force_8X8; UWORD8 u1_num_blks, u1_col_mb_pred_mode, uc_blk, u1_col_sub_mb_pred_mode, u1_col_sub_mb_pred_mode_rt; UWORD8 i1_num_partitions = 0, partition_size; WORD32 mv_index; UWORD8 u1_num_sub_blks; UWORD8 u1_is_cur_mb_fld, i; UWORD8 u1_init_colzero_flag; u1_is_cur_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; u1_sub_mb_num = ps_dec->u1_sub_mb_num; ps_direct->u1_col_zeroflag_change = 0; /*pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; u1_col_mb_pred_mode = pu1_col_zero_flag_start[u1_sub_mb_num]; u1_init_colzero_flag = u1_col_mb_pred_mode & 1; u1_col_mb_pred_mode >>= 6; */ if(0 == u1_is_cur_mb_fld) { ps_direct->u1_vert_mv_scale = FLD_TO_FRM; if(u1_wd_x == MB_SIZE) { pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; u1_col_mb_pred_mode = pu1_col_zero_flag_start[0]; u1_init_colzero_flag = u1_col_mb_pred_mode & 1; u1_col_mb_pred_mode >>= 6; if(u1_col_mb_pred_mode & 0x2) { ps_dec->u1_currB_type = 1; if(u1_col_mb_pred_mode == PRED_8x16) { ps_direct->i1_num_partitions = 2; ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; ps_direct->i1_submb_num[0] = 0; ps_direct->i1_partitionsize[0] = PRED_8x16; ps_direct->i4_mv_indices[1] = u2_sub_mb_ofst + 2; ps_direct->i1_submb_num[1] = 2; ps_direct->i1_partitionsize[1] = PRED_8x16; if((pu1_col_zero_flag_start[2] & 1) != u1_init_colzero_flag) ps_direct->u1_col_zeroflag_change = 1; } else { pu1_col_zero_flag = pu1_col_zero_flag_start + u1_sub_mb_num; u1_col_sub_mb_pred_mode = (*pu1_col_zero_flag & 0x10);/* 8x4 or 4x4 mode */ pu1_col_zero_flag_right_half = pu1_col_zero_flag_start + u1_sub_mb_num + 2; u1_col_sub_mb_pred_mode_rt = (*pu1_col_zero_flag_right_half & 0x10);/* 8x4 or 4x4 mode */ i4_force_8X8 = (u1_col_sub_mb_pred_mode) || (u1_col_sub_mb_pred_mode_rt); if(i4_force_8X8) { u1_num_sub_blks = 2; partition_size = PRED_8x8; } else { partition_size = PRED_8x16; u1_num_sub_blks = 1; } for(i = 0; i < 2; i++) { for(uc_blk = 0; uc_blk < u1_num_sub_blks; uc_blk++) { uc_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); uc_sub_mb_num_col &= 0x7; mv_index = u2_sub_mb_ofst + uc_sub_mb_num_col; ps_direct->i4_mv_indices[i1_num_partitions] = mv_index; ps_direct->i1_submb_num[i1_num_partitions] = u1_sub_mb_num; ps_direct->i1_partitionsize[i1_num_partitions] = partition_size; i1_num_partitions++; if((pu1_col_zero_flag_start[uc_sub_mb_num_col] & 1) != u1_init_colzero_flag) ps_direct->u1_col_zeroflag_change = 1; u1_sub_mb_num += 8; } u1_sub_mb_num = 2; /* move to second half of Cur MB */ } ps_direct->i1_num_partitions = i1_num_partitions; return; } } else { ps_direct->i1_num_partitions = 1; ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; ps_direct->i1_submb_num[0] = 0; ps_direct->i1_partitionsize[0] = PRED_16x16; ps_dec->u1_currB_type = 0; return; } } else { uc_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); uc_sub_mb_num_col &= 0x7; ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst + uc_sub_mb_num_col; ps_direct->i1_submb_num[0] = u1_sub_mb_num; ps_direct->i1_partitionsize[0] = PRED_8x8; ps_direct->i1_num_partitions = 1; } } else { ps_direct->u1_vert_mv_scale = FRM_TO_FLD; pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; u1_init_colzero_flag = pu1_col_zero_flag_start[0] & 1; if(u1_wd_x == MB_SIZE) { UWORD8 u1_submb_col; UWORD8 *puc_colZeroFlagStart_bot_mb, uc_colMbPredMode_bot_mb; pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; u1_col_mb_pred_mode = pu1_col_zero_flag_start[u1_sub_mb_num] >> 6; puc_colZeroFlagStart_bot_mb = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst + 16; uc_colMbPredMode_bot_mb = puc_colZeroFlagStart_bot_mb[8] >> 6; i4_force_8X8 = (u1_col_mb_pred_mode & 0x2) || (uc_colMbPredMode_bot_mb & 0x2); if(i4_force_8X8) { u1_num_blks = 2; partition_size = PRED_8x8; } else { u1_num_blks = 1; partition_size = PRED_16x8; } ps_dec->u1_currB_type = 1; /*As this mb is derived from 2 Mbs min no of partitions = 2*/ for(i = 0; i < 2; i++) { pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; u1_col_mb_pred_mode = pu1_col_zero_flag_start[u1_sub_mb_num] >> 6; for(uc_blk = 0; uc_blk < u1_num_blks; uc_blk++) { u1_submb_col = (u1_sub_mb_num & 0x7) ? 1 : 0; u1_submb_col += u1_sub_mb_num; mv_index = u2_sub_mb_ofst + u1_submb_col; ps_direct->i4_mv_indices[i1_num_partitions] = mv_index; ps_direct->i1_submb_num[i1_num_partitions] = u1_sub_mb_num; ps_direct->i1_partitionsize[i1_num_partitions] = partition_size; i1_num_partitions++; if((pu1_col_zero_flag_start[u1_submb_col] & 1) != u1_init_colzero_flag) ps_direct->u1_col_zeroflag_change = 1; u1_sub_mb_num += 2; } u1_sub_mb_num = 8; /* move to second half of Cur MB */ u2_sub_mb_ofst += 16;/* move to next Colocated MB */ } ps_direct->i1_num_partitions = i1_num_partitions; return; } else { uc_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); uc_sub_mb_num_col &= 0xb; u2_sub_mb_ofst += (u1_sub_mb_num >> 3) ? 16 : 0; ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst + uc_sub_mb_num_col; ps_direct->i1_submb_num[0] = u1_sub_mb_num; ps_direct->i1_partitionsize[0] = PRED_8x8; ps_direct->i1_num_partitions = 1; return; } } } /*! ************************************************************************** * \if Function name : ih264d_cal_col_pic \endif * * \brief * Finds the colocated picture. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_cal_col_pic(dec_struct_t *ps_dec) { struct pic_buffer_t* ps_col_pic = ps_dec->ps_col_pic; UWORD8 uc_curpictype, uc_colpictype; ps_col_pic = ps_dec->ps_ref_pic_buf_lx[1][0]; uc_curpictype = (ps_dec->ps_cur_pic->u1_picturetype & 0x7); uc_colpictype = (ps_col_pic->u1_picturetype & 0x7); if(uc_curpictype == FRM_PIC) { if(uc_colpictype == FRM_PIC) ps_dec->pf_parse_mvdirect = ih264d_one_to_one; else if(uc_colpictype == COMP_FLD_PAIR) { ps_dec->pf_parse_mvdirect = ih264d_fld_to_frm; if(ps_col_pic->i4_top_field_order_cnt >= ps_col_pic->i4_bottom_field_order_cnt) { struct pic_buffer_t* ps_tempPic = ps_col_pic; UWORD32 ui_half_num_of_sub_mbs = ((ps_dec->u2_pic_ht * ps_dec->u2_pic_wd) >> 5); ps_col_pic = ps_dec->ps_ref_pic_buf_lx[1][MAX_REF_BUFS]; /* memcpy ps_tempPic to ps_col_pic */ *ps_col_pic = *ps_tempPic; ps_col_pic->pu1_buf1 = ps_tempPic->pu1_buf1 + ps_tempPic->u2_frm_wd_y; ps_col_pic->pu1_buf2 = ps_tempPic->pu1_buf2 + ps_tempPic->u2_frm_wd_uv; ps_col_pic->pu1_buf3 = ps_tempPic->pu1_buf3 + ps_tempPic->u2_frm_wd_uv; ps_col_pic->pu1_col_zero_flag = ps_tempPic->pu1_col_zero_flag + ui_half_num_of_sub_mbs; ps_col_pic->ps_mv = ps_tempPic->ps_mv + ui_half_num_of_sub_mbs; ps_col_pic->u1_pic_type = 0;/*complementary reference field pair-refering as frame */ } } else { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; // i4_error_code |= 1<<IVD_CORRUPTEDDATA; return i4_error_code; } } else if(uc_curpictype == AFRM_PIC) { ps_dec->pf_parse_mvdirect = ih264d_fld_to_mbaff; } else /* must be a field*/ { if(uc_colpictype == FRM_PIC) ps_dec->pf_parse_mvdirect = ih264d_frm_to_fld; else if(uc_colpictype == AFRM_PIC) ps_dec->pf_parse_mvdirect = ih264d_mbaff_to_fld; else ps_dec->pf_parse_mvdirect = ih264d_one_to_one; } ps_dec->ps_col_pic = ps_col_pic; return OK; } /*! ************************************************************************** * \if Function name : ih264d_frm_to_fld \endif * * \brief * Initializes forward and backward refernce lists for B slice decoding. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ void ih264d_frm_to_fld(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info) { UWORD8 *pu1_col_zero_flag_start, u1_sub_mb_num; UWORD8 u1_num_blks, u1_col_mb_pred_mode, uc_blk; UWORD8 i1_num_partitions = 0, partition_size, i; WORD32 mv_index; UWORD32 increment; WORD32 i4_force_8X8; UNUSED(ps_cur_mb_info); ps_direct->u1_col_zeroflag_change = 1; ps_direct->u1_vert_mv_scale = FRM_TO_FLD; u1_sub_mb_num = ps_dec->u1_sub_mb_num; /* new calculation specific to this function */ if((ps_col_pic->u1_picturetype & 0x7) == FRM_PIC) { UWORD16 u2_frm_wd_in_mbs = ps_dec->u2_frm_wd_in_mbs; increment = (u2_frm_wd_in_mbs << 4); /*mbAddrCol = mbAddrCol1 */ u2_sub_mb_ofst = (ps_dec->u2_mbx + (2 * ps_dec->u2_mby * u2_frm_wd_in_mbs)) << 4; } else increment = 16; if(u1_wd_x == MB_SIZE) { ps_dec->u1_currB_type = 1; { UWORD8 *puc_colZeroFlagStart_bot_mb, uc_colMbPredMode_bot_mb; pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; u1_col_mb_pred_mode = (*pu1_col_zero_flag_start >> 6); puc_colZeroFlagStart_bot_mb = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst + increment; uc_colMbPredMode_bot_mb = (*puc_colZeroFlagStart_bot_mb >> 6); i4_force_8X8 = (u1_col_mb_pred_mode & 0x2) || (uc_colMbPredMode_bot_mb & 0x2); if(i4_force_8X8) { u1_num_blks = 2; partition_size = PRED_8x8; } else { partition_size = PRED_16x8; u1_num_blks = 1; } } /*As this mb is derived from 2 Mbs, min no of partitions = 2*/ for(i = 0; i < 2; i++) { for(uc_blk = 0; uc_blk < u1_num_blks; uc_blk++) { mv_index = u2_sub_mb_ofst + u1_sub_mb_num; mv_index += (u1_sub_mb_num & 0x7) ? 1 : 0; ps_direct->i4_mv_indices[i1_num_partitions] = mv_index; ps_direct->i1_submb_num[i1_num_partitions] = u1_sub_mb_num; ps_direct->i1_partitionsize[i1_num_partitions] = partition_size; i1_num_partitions++; u1_sub_mb_num += 2; } u1_sub_mb_num = 8; /* move to second half of Cur MB */ u2_sub_mb_ofst += increment;/* move to next Colocated MB */ } ps_direct->i1_num_partitions = i1_num_partitions; return; } else { UWORD8 u1_sub_mb_num_col; u1_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); u1_sub_mb_num_col &= 0xb; u2_sub_mb_ofst += (u1_sub_mb_num >> 3) ? increment : 0; ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst + u1_sub_mb_num_col; ps_direct->i1_submb_num[0] = u1_sub_mb_num; ps_direct->i1_partitionsize[0] = PRED_8x8; ps_direct->i1_num_partitions = 1; return; } } /*! ************************************************************************** * \if Function name : ih264d_fld_to_frm \endif * * \brief * Initializes forward and backward refernce lists for B slice decoding. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ void ih264d_fld_to_frm(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info) { UWORD8 *pu1_col_zero_flag_start, *pu1_col_zero_flag, *pu1_col_zero_flag_right_half, u1_sub_mb_num, uc_sub_mb_num_col; UWORD8 u1_col_mb_pred_mode, uc_blk; WORD32 i4_force_8X8; UNUSED(ps_cur_mb_info); ps_direct->u1_vert_mv_scale = FLD_TO_FRM; ps_direct->u1_col_zeroflag_change = 1; /* new calculation specific to this function for u2_sub_mb_ofst*/ u2_sub_mb_ofst = (ps_dec->u2_mbx + ((ps_dec->u2_mby >> 1) * ps_dec->u2_frm_wd_in_mbs)) << 4; u2_sub_mb_ofst += ((ps_dec->u2_mby & 1) << 3); if(u1_wd_x == MB_SIZE) { pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; u1_col_mb_pred_mode = (*pu1_col_zero_flag_start >> 6); ps_dec->u1_currB_type = (!!u1_col_mb_pred_mode); if(u1_col_mb_pred_mode & 0x2) { if(u1_col_mb_pred_mode == PRED_8x16) { ps_direct->i1_num_partitions = 2; ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; ps_direct->i1_submb_num[0] = 0; ps_direct->i1_partitionsize[0] = PRED_8x16; ps_direct->i4_mv_indices[1] = u2_sub_mb_ofst + 2; ps_direct->i1_submb_num[1] = 2; ps_direct->i1_partitionsize[1] = PRED_8x16; } else { UWORD8 i1_num_partitions = 0, partition_size; UWORD32 mv_index; UWORD8 u1_num_sub_blks, i, u1_col_sub_mb_pred_mode, u1_col_sub_mb_pred_mode_rt; u1_sub_mb_num = ps_dec->u1_sub_mb_num; pu1_col_zero_flag = pu1_col_zero_flag_start + u1_sub_mb_num; u1_col_sub_mb_pred_mode = (*pu1_col_zero_flag & 0x10);/* 8x4 or 4x4 mode */ pu1_col_zero_flag_right_half = pu1_col_zero_flag_start + u1_sub_mb_num + 2; u1_col_sub_mb_pred_mode_rt = (*pu1_col_zero_flag_right_half & 0x10);/* 8x4 or 4x4 mode */ i4_force_8X8 = (u1_col_sub_mb_pred_mode) || (u1_col_sub_mb_pred_mode_rt); if(i4_force_8X8) { u1_num_sub_blks = 2; partition_size = PRED_8x8; } else { partition_size = PRED_8x16; u1_num_sub_blks = 1; } for(i = 0; i < 2; i++) { for(uc_blk = 0; uc_blk < u1_num_sub_blks; uc_blk++) { uc_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); uc_sub_mb_num_col &= 0x7; mv_index = u2_sub_mb_ofst + uc_sub_mb_num_col; ps_direct->i4_mv_indices[i1_num_partitions] = mv_index; ps_direct->i1_submb_num[i1_num_partitions] = u1_sub_mb_num; ps_direct->i1_partitionsize[i1_num_partitions] = partition_size; i1_num_partitions++; u1_sub_mb_num += 8; } u1_sub_mb_num = 2; /* move to second half of Cur MB */ } ps_direct->i1_num_partitions = i1_num_partitions; return; } } else { ps_direct->i1_num_partitions = 1; ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; ps_direct->i1_submb_num[0] = 0; ps_direct->i1_partitionsize[0] = PRED_16x16; return; } } else { u1_sub_mb_num = ps_dec->u1_sub_mb_num; uc_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); uc_sub_mb_num_col &= 0x7; ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst + uc_sub_mb_num_col; ps_direct->i1_submb_num[0] = u1_sub_mb_num; ps_direct->i1_partitionsize[0] = PRED_8x8; ps_direct->i1_num_partitions = 1; } } /*! ************************************************************************** * \if Function name : ih264d_one_to_one \endif * * \brief * Initializes forward and backward refernce lists for B slice decoding. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ void ih264d_mbaff_to_fld(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info) { UWORD8* pu1_col_zero_flag, u1_iscol_mb_fld; u2_sub_mb_ofst <<= 1; pu1_col_zero_flag = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; u1_iscol_mb_fld = (*pu1_col_zero_flag & 0x2) >> 1; if(u1_iscol_mb_fld) { u2_sub_mb_ofst += (ps_dec->ps_cur_slice->u1_bottom_field_flag << 4); ih264d_one_to_one(ps_dec, ps_col_pic, ps_direct, u1_wd_x, u2_sub_mb_ofst, ps_cur_mb_info); } else ih264d_frm_to_fld(ps_dec, ps_col_pic, ps_direct, u1_wd_x, u2_sub_mb_ofst, ps_cur_mb_info); } /*! ************************************************************************** * \if Function name : ih264d_one_to_one \endif * * \brief * Initializes forward and backward refernce lists for B slice decoding. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ void ih264d_fld_to_mbaff(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info) { if((ps_col_pic->u1_picturetype & 0x7) == COMP_FLD_PAIR) { /* first calculate the colocated picture which varies with Mb */ UWORD8 u1_is_cur_mb_fld; u1_is_cur_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; u2_sub_mb_ofst = (u2_sub_mb_ofst & 0xffe0); /* mbaddrCol5 = curmbaddr/2;*/ u2_sub_mb_ofst >>= 1; ps_col_pic = ps_dec->ps_ref_pic_buf_lx[1][0]; if(u1_is_cur_mb_fld) { if(1 - ps_cur_mb_info->u1_topmb) ps_col_pic = ps_dec->ps_ref_pic_buf_lx[1][MAX_REF_BUFS]; ih264d_one_to_one(ps_dec, ps_col_pic, ps_direct, u1_wd_x, u2_sub_mb_ofst, ps_cur_mb_info); } else { if(ABS(ps_col_pic->i4_top_field_order_cnt - (WORD64)ps_dec->ps_cur_pic->i4_poc) >= ABS((WORD64)ps_dec->ps_cur_pic->i4_poc - ps_col_pic->i4_bottom_field_order_cnt)) { ps_col_pic = ps_dec->ps_ref_pic_buf_lx[1][MAX_REF_BUFS]; } if(ps_cur_mb_info->u1_topmb == 0) u2_sub_mb_ofst += 8; ih264d_mbaff_cross_pmbair(ps_dec, ps_col_pic, ps_direct, u1_wd_x, u2_sub_mb_ofst, ps_cur_mb_info); } ps_dec->ps_col_pic = ps_col_pic; } else { UWORD8* pu1_col_zero_flag = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; UWORD8 temp, u1_is_cur_mb_fld, u1_iscol_mb_fld; u1_iscol_mb_fld = (*pu1_col_zero_flag & 0x2) >> 1; u1_is_cur_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; temp = (u1_iscol_mb_fld ^ u1_is_cur_mb_fld); if(temp == 0) ih264d_one_to_one(ps_dec, ps_col_pic, ps_direct, u1_wd_x, u2_sub_mb_ofst, ps_cur_mb_info); else { u2_sub_mb_ofst &= 0xffef; if(u1_is_cur_mb_fld == 0) { if(ABS(ps_col_pic->i4_top_field_order_cnt - (WORD64)ps_dec->ps_cur_pic->i4_poc) >= ABS((WORD64)ps_dec->ps_cur_pic->i4_poc - ps_col_pic->i4_bottom_field_order_cnt)) { u2_sub_mb_ofst += 0x10; } if(ps_cur_mb_info->u1_topmb == 0) u2_sub_mb_ofst += 8; } ih264d_mbaff_cross_pmbair(ps_dec, ps_col_pic, ps_direct, u1_wd_x, u2_sub_mb_ofst, ps_cur_mb_info); } } } ================================================ FILE: dependencies/ih264d/decoder/ih264d_process_bslice.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_PARSE_BSLICE_H_ #define _IH264D_PARSE_BSLICE_H_ /*! ************************************************************************** * \file ih264d_process_bslice.h * * \brief * Contains declarations of routines that decode a B slice type * * Detailed_description * * \date * 21/12/2002 * * \author NS ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" WORD32 ih264d_parse_bslice(dec_struct_t * ps_dec, UWORD16 u2_first_mb_in_slice); WORD32 ih264d_decode_spatial_direct(dec_struct_t * ps_dec, UWORD8 u1_wd_x, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num); WORD32 ih264d_decode_temporal_direct(dec_struct_t * ps_dec, UWORD8 u1_wd_x, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num); WORD32 parseBSliceData(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice); WORD32 parseBSliceData(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice); void ih264d_init_ref_idx_lx_b(dec_struct_t *ps_dec); void ih264d_convert_frm_to_fld_list(struct pic_buffer_t *ps_ref_pic_buf_lx, UWORD8 *pu1_L0, dec_struct_t *ps_dec, UWORD8 u1_num_short_term_bufs); void ih264d_convert_frm_mbaff_list(dec_struct_t *ps_dec); void ih264d_one_to_one(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info); void ih264d_mbaff_cross_pmbair(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info); void ih264d_frm_to_fld(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info); void ih264d_fld_to_frm(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info); void ih264d_mbaff_to_fld(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info); void ih264d_fld_to_mbaff(dec_struct_t *ps_dec, struct pic_buffer_t *ps_col_pic, directmv_t *ps_direct, UWORD8 u1_wd_x, WORD32 u2_sub_mb_ofst, dec_mb_info_t * ps_cur_mb_info); WORD32 ih264d_cal_col_pic(dec_struct_t *ps_dec); WORD32 ih264d_mv_pred_ref_tfr_nby2_bmb(dec_struct_t * ps_dec, UWORD8 u1_num_mbs, UWORD8 u1_num_mbsNby2); #endif /* _IH264D_PARSE_BSLICE_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_process_intra_mb.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_process_intra_mb.c * * \brief * Contains routines that decode a I slice type * * Detailed_description * * \date * 07/07/2003 * * \author NS ************************************************************************** */ #include <string.h> #include "ih264d_bitstrm.h" #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264d_parse_cavlc.h" #include "ih264d_mb_utils.h" #include "ih264d_parse_slice.h" #include "ih264d_process_intra_mb.h" #include "ih264d_error_handler.h" #include "ih264d_quant_scaling.h" #include "ih264d_tables.h" /*! ************************************************************************** * \if Function name : ih264d_itrans_recon_luma_dc \endif * * \brief * This function does InvTransform, scaling and reconstruction of Luma DC. * * \return * 0 on Success and Error code otherwise ************************************************************************** */ void ih264d_itrans_recon_luma_dc(dec_struct_t *ps_dec, WORD16* pi2_src, WORD16* pi2_coeff_block, const UWORD16 *pu2_weigh_mat) { WORD32 i; WORD16 pi2_out[16]; WORD32 pi4_tmp[16]; WORD16 *pi2_out_ptr = &pi2_out[0]; PROFILE_DISABLE_IQ_IT_RECON_RETURN() ps_dec->pf_ihadamard_scaling_4x4(pi2_src, pi2_out, ps_dec->pu2_quant_scale_y, pu2_weigh_mat, ps_dec->u1_qp_y_div6, pi4_tmp); for(i = 0; i < 4; i++) { pi2_coeff_block[0] = pi2_out_ptr[0]; pi2_coeff_block[4 * 16] = pi2_out_ptr[4]; pi2_coeff_block[8 * 16] = pi2_out_ptr[8]; pi2_coeff_block[12 * 16] = pi2_out_ptr[12]; pi2_out_ptr++; /* Point to next column */ pi2_coeff_block += 16; } } /*! ************************************************************************** * \if Function name : ih264d_read_intra_pred_modes \endif * * \brief * Reads the intra pred mode related values of I4x4 MB from bitstream. * * This function will read the prev intra pred mode flags and * stores it in pu1_prev_intra4x4_pred_mode_flag. If the u4_flag * indicates that most probable mode is not intra pred mode, then * the rem_intra4x4_pred_mode is read and stored in * pu1_rem_intra4x4_pred_mode array. * * * \return * 0 on success and Error code otherwise * ************************************************************************** */ WORD32 ih264d_read_intra_pred_modes(dec_struct_t * ps_dec, UWORD8 * pu1_prev_intra4x4_pred_mode_flag, UWORD8 * pu1_rem_intra4x4_pred_mode, UWORD32 u4_trans_form8x8) { WORD32 i4x4_luma_blk_idx = 0, i8x8_luma_blk_idx = 0; dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; if(!u4_trans_form8x8) { for(i4x4_luma_blk_idx = 0; i4x4_luma_blk_idx < 16; ++i4x4_luma_blk_idx) { UWORD32 u4_temp; SWITCHOFFTRACE; GETBIT(u4_temp, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer); *pu1_prev_intra4x4_pred_mode_flag = (UWORD8)u4_temp; if(!(*pu1_prev_intra4x4_pred_mode_flag)) { GETBITS(u4_temp, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer, 3); *(pu1_rem_intra4x4_pred_mode) = (UWORD8)u4_temp; } pu1_prev_intra4x4_pred_mode_flag++; pu1_rem_intra4x4_pred_mode++; } } else { /**********************************************************************/ /* prev_intra4x4_pred_modes to be interpreted as */ /* prev_intra8x8_pred_modes in case of transform 8x8 */ /**********************************************************************/ for(i8x8_luma_blk_idx = 0; i8x8_luma_blk_idx < 4; i8x8_luma_blk_idx++) { UWORD32 u4_temp; GETBIT(u4_temp, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer); *pu1_prev_intra4x4_pred_mode_flag = (UWORD8)u4_temp; if(!(*pu1_prev_intra4x4_pred_mode_flag)) { GETBITS(u4_temp, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer, 3); (*pu1_rem_intra4x4_pred_mode) = (UWORD8)u4_temp; } pu1_prev_intra4x4_pred_mode_flag++; pu1_rem_intra4x4_pred_mode++; } } return (0); } WORD32 ih264d_unpack_coeff4x4_4x4blk(dec_struct_t * ps_dec, WORD16 *pi2_out_coeff_data, UWORD8 *pu1_inv_scan) { tu_sblk4x4_coeff_data_t *ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_proc_tu_coeff_data; UWORD16 u2_sig_coeff_map = ps_tu_4x4->u2_sig_coeff_map; WORD32 idx = 0; WORD16 *pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; WORD32 dc_only_flag = 0; WORD32 num_coeff = 0; PROFILE_DISABLE_UNPACK_LUMA() while(u2_sig_coeff_map) { idx = CLZ(u2_sig_coeff_map); idx = 31 - idx; RESET_BIT(u2_sig_coeff_map,idx); idx = pu1_inv_scan[idx]; pi2_out_coeff_data[idx] = *pi2_coeff_data++; num_coeff++; } if((num_coeff == 1) && (idx == 0)) { dc_only_flag = 1; } { WORD32 offset; offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; offset = ALIGN4(offset); ps_dec->pv_proc_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_proc_tu_coeff_data + offset); } return dc_only_flag; } UWORD32 ih264d_unpack_coeff4x4_8x8blk(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD16 ui2_luma_csbp, WORD16 *pi2_out_coeff_data) { UWORD8 *pu1_inv_scan; UWORD8 u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; UWORD8 u1_field_coding_flag = ps_cur_mb_info->ps_curmb->u1_mb_fld; UWORD32 u4_luma_dc_only_csbp = 0; WORD32 dc_only_flag = 0; PROFILE_DISABLE_UNPACK_LUMA() if(u1_field_coding_flag || u1_mb_field_decoding_flag) { pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_fld; } else { pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; } // sub 0 if(ui2_luma_csbp & 0x1) { memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); dc_only_flag = ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); INSERT_BIT(u4_luma_dc_only_csbp, 0, dc_only_flag); } pi2_out_coeff_data += 16; // sub 1 if(ui2_luma_csbp & 0x2) { memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); dc_only_flag = ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); INSERT_BIT(u4_luma_dc_only_csbp, 1, dc_only_flag); } pi2_out_coeff_data += 16 + 32; // sub 2 if(ui2_luma_csbp & 0x10) { memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); dc_only_flag = ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); INSERT_BIT(u4_luma_dc_only_csbp, 4, dc_only_flag); } pi2_out_coeff_data += 16; // sub 3 if(ui2_luma_csbp & 0x20) { memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); dc_only_flag = ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); INSERT_BIT(u4_luma_dc_only_csbp, 5, dc_only_flag); } return u4_luma_dc_only_csbp; } WORD32 ih264d_unpack_coeff8x8_8x8blk_cavlc(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD16 ui2_luma_csbp, WORD16 *pi2_out_coeff_data) { UWORD8 *pu1_inv_scan; UWORD8 u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; UWORD8 u1_field_coding_flag = ps_cur_mb_info->ps_curmb->u1_mb_fld; WORD32 dc_only_flag = 0; PROFILE_DISABLE_UNPACK_LUMA() if(ui2_luma_csbp & 0x33) { memset(pi2_out_coeff_data,0,64*sizeof(WORD16)); } if(!u1_mb_field_decoding_flag) { pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[0]; } else { pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[0]; } // sub 0 if(ui2_luma_csbp & 0x1) { dc_only_flag = ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); } if(!u1_mb_field_decoding_flag) { pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[1]; } else { pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[1]; } // sub 1 if(ui2_luma_csbp & 0x2) { dc_only_flag = 0; ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); } if(!u1_mb_field_decoding_flag) { pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[2]; } else { pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[2]; } // sub 2 if(ui2_luma_csbp & 0x10) { dc_only_flag = 0; ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); } if(!u1_mb_field_decoding_flag) { pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[3]; } else { pu1_inv_scan = (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[3]; } // sub 3 if(ui2_luma_csbp & 0x20) { dc_only_flag = 0; ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); } return dc_only_flag; } void ih264d_unpack_coeff4x4_8x8blk_chroma(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD16 ui2_chroma_csbp, WORD16 *pi2_out_coeff_data) { UWORD8 *pu1_inv_scan; UWORD8 u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; UWORD8 u1_field_coding_flag = ps_cur_mb_info->ps_curmb->u1_mb_fld; PROFILE_DISABLE_UNPACK_CHROMA() if(u1_field_coding_flag || u1_mb_field_decoding_flag) { pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_fld; } else { pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; } if(ui2_chroma_csbp & 0x1) { memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); } pi2_out_coeff_data += 16; if(ui2_chroma_csbp & 0x2) { memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); } pi2_out_coeff_data += 16; if(ui2_chroma_csbp & 0x4) { memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); } pi2_out_coeff_data += 16; if(ui2_chroma_csbp & 0x8) { memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); ih264d_unpack_coeff4x4_4x4blk(ps_dec, pi2_out_coeff_data, pu1_inv_scan); } } UWORD32 ih264d_unpack_luma_coeff4x4_mb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 intra_flag) { UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; UWORD16 ui2_luma_csbp = ps_cur_mb_info->u2_luma_csbp; UWORD8 *pu1_inv_scan = ps_dec->pu1_inv_scan; WORD16 *pi2_coeff_data = ps_dec->pi2_coeff_data; PROFILE_DISABLE_UNPACK_LUMA() if(!ps_cur_mb_info->u1_tran_form8x8) { UWORD32 u4_luma_dc_only_csbp = 0; UWORD32 u4_temp = 0; WORD16* pi2_dc_val = NULL; /* * Reserve the pointer to dc vals. The dc vals will be copied * after unpacking of ac vals since memset to 0 inside. */ if(intra_flag && (u1_mb_type != I_4x4_MB)) { if(CHECKBIT(ps_cur_mb_info->u1_yuv_dc_block_flag,0)) { pi2_dc_val = (WORD16 *)ps_dec->pv_proc_tu_coeff_data; ps_dec->pv_proc_tu_coeff_data = (void *)(pi2_dc_val + 16); } } if(ui2_luma_csbp) { pi2_coeff_data = ps_dec->pi2_coeff_data; u4_temp = ih264d_unpack_coeff4x4_8x8blk(ps_dec, ps_cur_mb_info, ui2_luma_csbp, pi2_coeff_data); u4_luma_dc_only_csbp = u4_temp; pi2_coeff_data += 32; ui2_luma_csbp = ui2_luma_csbp >> 2; u4_temp = ih264d_unpack_coeff4x4_8x8blk(ps_dec, ps_cur_mb_info, ui2_luma_csbp, pi2_coeff_data); u4_luma_dc_only_csbp |= (u4_temp << 2); pi2_coeff_data += 32 + 64; ui2_luma_csbp = ui2_luma_csbp >> 6; u4_temp = ih264d_unpack_coeff4x4_8x8blk(ps_dec, ps_cur_mb_info, ui2_luma_csbp, pi2_coeff_data); u4_luma_dc_only_csbp |= (u4_temp << 8); pi2_coeff_data += 32; ui2_luma_csbp = ui2_luma_csbp >> 2; u4_temp = ih264d_unpack_coeff4x4_8x8blk(ps_dec, ps_cur_mb_info, ui2_luma_csbp, pi2_coeff_data); u4_luma_dc_only_csbp |= (u4_temp << 10); } if(pi2_dc_val != NULL) { WORD32 i; pi2_coeff_data = ps_dec->pi2_coeff_data; for(i = 0; i < 4; i++) { pi2_coeff_data[0] = pi2_dc_val[0]; pi2_coeff_data[4 * 16] = pi2_dc_val[4]; pi2_coeff_data[8 * 16] = pi2_dc_val[8]; pi2_coeff_data[12 * 16] = pi2_dc_val[12]; pi2_dc_val++; /* Point to next column */ pi2_coeff_data += 16; } u4_luma_dc_only_csbp = ps_cur_mb_info->u2_luma_csbp ^ 0xFFFF; } return u4_luma_dc_only_csbp; } else { UWORD32 u4_luma_dc_only_cbp = 0; WORD32 dc_only_flag; if(ui2_luma_csbp) { pi2_coeff_data = ps_dec->pi2_coeff_data; dc_only_flag = ih264d_unpack_coeff8x8_8x8blk_cavlc(ps_dec, ps_cur_mb_info, ui2_luma_csbp, pi2_coeff_data); INSERT_BIT(u4_luma_dc_only_cbp, 0, dc_only_flag); pi2_coeff_data += 64; ui2_luma_csbp = ui2_luma_csbp >> 2; dc_only_flag = ih264d_unpack_coeff8x8_8x8blk_cavlc(ps_dec, ps_cur_mb_info, ui2_luma_csbp, pi2_coeff_data); INSERT_BIT(u4_luma_dc_only_cbp, 1, dc_only_flag); pi2_coeff_data += 64; ui2_luma_csbp = ui2_luma_csbp >> 6; dc_only_flag = ih264d_unpack_coeff8x8_8x8blk_cavlc(ps_dec, ps_cur_mb_info, ui2_luma_csbp, pi2_coeff_data); INSERT_BIT(u4_luma_dc_only_cbp, 2, dc_only_flag); pi2_coeff_data += 64; ui2_luma_csbp = ui2_luma_csbp >> 2; dc_only_flag = ih264d_unpack_coeff8x8_8x8blk_cavlc(ps_dec, ps_cur_mb_info, ui2_luma_csbp, pi2_coeff_data); INSERT_BIT(u4_luma_dc_only_cbp, 3, dc_only_flag); } return u4_luma_dc_only_cbp; } } void ih264d_unpack_chroma_coeff4x4_mb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info) { UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; UWORD16 ui2_chroma_csbp = ps_cur_mb_info->u2_chroma_csbp; UWORD8 *pu1_inv_scan = ps_dec->pu1_inv_scan; WORD16 *pi2_coeff_data = ps_dec->pi2_coeff_data; WORD32 i; WORD16 *pi2_dc_val_u = NULL; WORD16 *pi2_dc_val_v = NULL; PROFILE_DISABLE_UNPACK_CHROMA() if((ps_cur_mb_info->u1_cbp >> 4) == CBPC_ALLZERO) return; /* * Reserve the pointers to dc vals. The dc vals will be copied * after unpacking of ac vals since memset to 0 inside. */ if(CHECKBIT(ps_cur_mb_info->u1_yuv_dc_block_flag,1)) { pi2_dc_val_u = (WORD16 *)ps_dec->pv_proc_tu_coeff_data; ps_dec->pv_proc_tu_coeff_data = (void *)(pi2_dc_val_u + 4); } if(CHECKBIT(ps_cur_mb_info->u1_yuv_dc_block_flag,2)) { pi2_dc_val_v = (WORD16 *)ps_dec->pv_proc_tu_coeff_data; ps_dec->pv_proc_tu_coeff_data = (void *)(pi2_dc_val_v + 4); } if((ps_cur_mb_info->u1_cbp >> 4) == CBPC_NONZERO) { pi2_coeff_data = ps_dec->pi2_coeff_data; ih264d_unpack_coeff4x4_8x8blk_chroma(ps_dec, ps_cur_mb_info, ui2_chroma_csbp, pi2_coeff_data); pi2_coeff_data += 64; ui2_chroma_csbp = ui2_chroma_csbp >> 4; ih264d_unpack_coeff4x4_8x8blk_chroma(ps_dec, ps_cur_mb_info, ui2_chroma_csbp, pi2_coeff_data); } pi2_coeff_data = ps_dec->pi2_coeff_data; if(pi2_dc_val_u != NULL) { pi2_coeff_data[0] = *pi2_dc_val_u++; pi2_coeff_data[1 * 16] = *pi2_dc_val_u++; pi2_coeff_data[2 * 16] = *pi2_dc_val_u++; pi2_coeff_data[3 * 16] = *pi2_dc_val_u++; } else { pi2_coeff_data[0] = 0; pi2_coeff_data[1 * 16] = 0; pi2_coeff_data[2 * 16] = 0; pi2_coeff_data[3 * 16] = 0; } pi2_coeff_data += 64; if(pi2_dc_val_v != NULL) { pi2_coeff_data[0] = *pi2_dc_val_v++; pi2_coeff_data[1 * 16] = *pi2_dc_val_v++; pi2_coeff_data[2 * 16] = *pi2_dc_val_v++; pi2_coeff_data[3 * 16] = *pi2_dc_val_v++; } else { pi2_coeff_data[0] = 0; pi2_coeff_data[1 * 16] = 0; pi2_coeff_data[2 * 16] = 0; pi2_coeff_data[3 * 16] = 0; } } UWORD32 ih264d_unpack_luma_coeff8x8_mb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info) { WORD32 blk_8x8_cnt; WORD16 *pi2_out_coeff_data = ps_dec->pi2_coeff_data; UWORD8 u1_field_coding_flag = ps_cur_mb_info->ps_curmb->u1_mb_fld; UWORD8 *pu1_inv_scan; UWORD32 u4_luma_dc_only_cbp = 0; PROFILE_DISABLE_UNPACK_LUMA() if(!u1_field_coding_flag) { /*******************************************************************/ /* initializing inverse scan matrices */ /*******************************************************************/ pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_prog8x8_cabac; } else { /*******************************************************************/ /* initializing inverse scan matrices */ /*******************************************************************/ pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_int8x8_cabac; } for(blk_8x8_cnt = 0; blk_8x8_cnt < 4; blk_8x8_cnt++) { if(CHECKBIT(ps_cur_mb_info->u1_cbp, blk_8x8_cnt)) { tu_blk8x8_coeff_data_t *ps_tu_8x8 = (tu_blk8x8_coeff_data_t *)ps_dec->pv_proc_tu_coeff_data; UWORD32 u4_sig_coeff_map; WORD32 idx = 0; WORD16 *pi2_coeff_data = &ps_tu_8x8->ai2_level[0]; WORD32 num_coeff = 0; /* memset 64 coefficient to zero */ memset(pi2_out_coeff_data,0,64*sizeof(WORD16)); u4_sig_coeff_map = ps_tu_8x8->au4_sig_coeff_map[1]; while(u4_sig_coeff_map) { idx = CLZ(u4_sig_coeff_map); idx = 31 - idx; RESET_BIT(u4_sig_coeff_map,idx); idx = pu1_inv_scan[idx + 32]; pi2_out_coeff_data[idx] = *pi2_coeff_data++; num_coeff++; } u4_sig_coeff_map = ps_tu_8x8->au4_sig_coeff_map[0]; while(u4_sig_coeff_map) { idx = CLZ(u4_sig_coeff_map); idx = 31 - idx; RESET_BIT(u4_sig_coeff_map,idx); idx = pu1_inv_scan[idx]; pi2_out_coeff_data[idx] = *pi2_coeff_data++; num_coeff++; } if((num_coeff == 1) && (idx == 0)) { SET_BIT(u4_luma_dc_only_cbp,blk_8x8_cnt); } { WORD32 offset; offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_8x8; offset = ALIGN4(offset); ps_dec->pv_proc_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_proc_tu_coeff_data + offset); } } pi2_out_coeff_data += 64; } return u4_luma_dc_only_cbp; } /*! ************************************************************************** * \if Function name : ih264d_process_intra_mb \endif * * \brief * This function decodes an I MB. Intraprediction is carried out followed * by InvTramsform. Both IntraPrediction and Reconstrucion are carried out * row buffer itself. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_process_intra_mb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num) { UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; UWORD8 uc_temp = ps_cur_mb_info->u1_mb_ngbr_availablity; UWORD8 u1_top_available = BOOLEAN(uc_temp & TOP_MB_AVAILABLE_MASK); UWORD8 u1_left_available = BOOLEAN(uc_temp & LEFT_MB_AVAILABLE_MASK); UWORD8 u1_use_top_right_mb = BOOLEAN(uc_temp & TOP_RIGHT_MB_AVAILABLE_MASK); UWORD8 u1_use_top_left_mb = BOOLEAN(uc_temp & TOP_LEFT_MB_AVAILABLE_MASK); UWORD8 uc_useTopMB = u1_top_available; UWORD16 u2_use_left_mb = u1_left_available; UWORD16 u2_use_left_mb_pack; UWORD8 *pu1_luma_pred_buffer; /* CHANGED CODE */ UWORD8 *pu1_luma_rec_buffer; UWORD8 *puc_top; mb_neigbour_params_t *ps_left_mb; mb_neigbour_params_t *ps_top_mb; mb_neigbour_params_t *ps_top_right_mb; mb_neigbour_params_t *ps_curmb; UWORD16 u2_mbx = ps_cur_mb_info->u2_mbx; UWORD32 ui_pred_width, ui_rec_width; WORD16 *pi2_y_coeff; UWORD8 u1_mbaff, u1_topmb, u1_mb_field_decoding_flag; UWORD32 u4_num_pmbair; UWORD16 ui2_luma_csbp = ps_cur_mb_info->u2_luma_csbp; UWORD8 *pu1_yleft, *pu1_ytop_left; /* Chroma variables*/ UWORD8 *pu1_top_u; UWORD8 *pu1_uleft; UWORD8 *pu1_u_top_left; /* CHANGED CODE */ UWORD8 *pu1_mb_cb_rei1_buffer, *pu1_mb_cr_rei1_buffer; UWORD32 u4_recwidth_cr; /* CHANGED CODE */ tfr_ctxt_t *ps_frame_buf = ps_dec->ps_frame_buf_ip_recon; UWORD32 u4_luma_dc_only_csbp = 0; UWORD32 u4_luma_dc_only_cbp = 0; UWORD8 *pu1_prev_intra4x4_pred_mode_data = (UWORD8 *)ps_dec->pv_proc_tu_coeff_data; //Pointer to keep track of intra4x4_pred_mode data in pv_proc_tu_coeff_data buffer u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; u1_topmb = ps_cur_mb_info->u1_topmb; u4_num_pmbair = (u1_mb_num >> u1_mbaff); /*--------------------------------------------------------------------*/ /* Find the current MB's mb params */ /*--------------------------------------------------------------------*/ u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; ps_curmb = ps_cur_mb_info->ps_curmb; ps_top_mb = ps_cur_mb_info->ps_top_mb; ps_left_mb = ps_cur_mb_info->ps_left_mb; ps_top_right_mb = ps_cur_mb_info->ps_top_right_mb; /*--------------------------------------------------------------------*/ /* Check whether neighbouring MB is Inter MB and */ /* constrained intra pred is 1. */ /*--------------------------------------------------------------------*/ u2_use_left_mb_pack = (u2_use_left_mb << 8) + u2_use_left_mb; if(ps_dec->ps_cur_pps->u1_constrained_intra_pred_flag) { UWORD8 u1_left = (UWORD8)u2_use_left_mb; uc_useTopMB = uc_useTopMB && ((ps_top_mb->u1_mb_type != P_MB) && (ps_top_mb->u1_mb_type != B_MB)); u2_use_left_mb = u2_use_left_mb && ((ps_left_mb->u1_mb_type != P_MB) && (ps_left_mb->u1_mb_type != B_MB)); u2_use_left_mb_pack = (u2_use_left_mb << 8) + u2_use_left_mb; if(u1_mbaff) { if(u1_mb_field_decoding_flag ^ ps_left_mb->u1_mb_fld) { u1_left = u1_left && (((ps_left_mb + 1)->u1_mb_type != P_MB) && ((ps_left_mb + 1)->u1_mb_type != B_MB)); u2_use_left_mb = u2_use_left_mb && u1_left; if(u1_mb_field_decoding_flag) u2_use_left_mb_pack = (u1_left << 8) + (u2_use_left_mb_pack & 0xff); else u2_use_left_mb_pack = (u2_use_left_mb << 8) + (u2_use_left_mb); } } u1_use_top_right_mb = u1_use_top_right_mb && ((ps_top_right_mb->u1_mb_type != P_MB) && (ps_top_right_mb->u1_mb_type != B_MB)); u1_use_top_left_mb = u1_use_top_left_mb && ((ps_cur_mb_info->u1_topleft_mbtype != P_MB) && (ps_cur_mb_info->u1_topleft_mbtype != B_MB)); } /*********************Common pointer calculations *************************/ /* CHANGED CODE */ pu1_luma_pred_buffer = ps_dec->pu1_y; pu1_luma_rec_buffer = ps_frame_buf->pu1_dest_y + (u4_num_pmbair << 4); pu1_mb_cb_rei1_buffer = ps_frame_buf->pu1_dest_u + (u4_num_pmbair << 3) * YUV420SP_FACTOR; pu1_mb_cr_rei1_buffer = ps_frame_buf->pu1_dest_v + (u4_num_pmbair << 3); ui_pred_width = MB_SIZE; ui_rec_width = ps_dec->u2_frm_wd_y << u1_mb_field_decoding_flag; u4_recwidth_cr = ps_dec->u2_frm_wd_uv << u1_mb_field_decoding_flag; /************* Current and top luma pointer *****************/ if(u1_mbaff) { if(u1_topmb == 0) { pu1_luma_rec_buffer += ( u1_mb_field_decoding_flag ? (ui_rec_width >> 1) : (ui_rec_width << 4)); pu1_mb_cb_rei1_buffer += ( u1_mb_field_decoding_flag ? (u4_recwidth_cr >> 1) : (u4_recwidth_cr << 3)); pu1_mb_cr_rei1_buffer += ( u1_mb_field_decoding_flag ? (u4_recwidth_cr >> 1) : (u4_recwidth_cr << 3)); } } /* CHANGED CODE */ if(ps_dec->u4_use_intrapred_line_copy == 1) { puc_top = ps_dec->pu1_prev_y_intra_pred_line + (ps_cur_mb_info->u2_mbx << 4); pu1_top_u = ps_dec->pu1_prev_u_intra_pred_line + (ps_cur_mb_info->u2_mbx << 3) * YUV420SP_FACTOR; } else { puc_top = pu1_luma_rec_buffer - ui_rec_width; pu1_top_u = pu1_mb_cb_rei1_buffer - u4_recwidth_cr; } /* CHANGED CODE */ /************* Left pointer *****************/ pu1_yleft = pu1_luma_rec_buffer - 1; pu1_uleft = pu1_mb_cb_rei1_buffer - 1 * YUV420SP_FACTOR; /**************Top Left pointer calculation**********/ pu1_ytop_left = puc_top - 1; pu1_u_top_left = pu1_top_u - 1 * YUV420SP_FACTOR; /* CHANGED CODE */ PROFILE_DISABLE_INTRA_PRED() { pu1_prev_intra4x4_pred_mode_data = (UWORD8 *)ps_dec->pv_proc_tu_coeff_data; if(u1_mb_type == I_4x4_MB && ps_cur_mb_info->u1_tran_form8x8 == 0) { ps_dec->pv_proc_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_proc_tu_coeff_data + 32); } else if (u1_mb_type == I_4x4_MB && ps_cur_mb_info->u1_tran_form8x8 == 1) { ps_dec->pv_proc_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_proc_tu_coeff_data + 8); } } if(!ps_cur_mb_info->u1_tran_form8x8) { u4_luma_dc_only_csbp = ih264d_unpack_luma_coeff4x4_mb(ps_dec, ps_cur_mb_info, 1); } else { if(!ps_dec->ps_cur_pps->u1_entropy_coding_mode) { u4_luma_dc_only_cbp = ih264d_unpack_luma_coeff4x4_mb(ps_dec, ps_cur_mb_info, 1); } else { u4_luma_dc_only_cbp = ih264d_unpack_luma_coeff8x8_mb(ps_dec, ps_cur_mb_info); } } pi2_y_coeff = ps_dec->pi2_coeff_data; if(u1_mb_type != I_4x4_MB) { UWORD8 u1_intrapred_mode = MB_TYPE_TO_INTRA_16x16_MODE(u1_mb_type); /*--------------------------------------------------------------------*/ /* 16x16 IntraPrediction */ /*--------------------------------------------------------------------*/ { UWORD8 u1_packed_modes = (u1_top_available << 1) + u1_left_available; UWORD8 u1_err_code = (u1_intrapred_mode & 1) ? u1_intrapred_mode : (u1_intrapred_mode ^ 2); if((u1_err_code & u1_packed_modes) ^ u1_err_code) { u1_intrapred_mode = 0; ps_dec->i4_error_code = ERROR_INTRAPRED; } } { /* Align the size to multiple of 8, so that SIMD functions can read 64 bits at a time. Only 33 bytes are actaully used */ UWORD8 au1_ngbr_pels[40]; /* Get neighbour pixels */ /* left pels */ if(u2_use_left_mb) { WORD32 i; for(i = 0; i < 16; i++) au1_ngbr_pels[16 - 1 - i] = pu1_yleft[i * ui_rec_width]; } else { memset(au1_ngbr_pels, 0, 16); } /* top left pels */ au1_ngbr_pels[16] = *pu1_ytop_left; /* top pels */ if(uc_useTopMB) { memcpy(au1_ngbr_pels + 16 + 1, puc_top, 16); } else { memset(au1_ngbr_pels + 16 + 1, 0, 16); } PROFILE_DISABLE_INTRA_PRED() ps_dec->apf_intra_pred_luma_16x16[u1_intrapred_mode]( au1_ngbr_pels, pu1_luma_rec_buffer, 1, ui_rec_width, ((uc_useTopMB << 2) | u2_use_left_mb)); } { UWORD32 i; WORD16 ai2_tmp[16]; for(i = 0; i < 16; i++) { WORD16 *pi2_level = pi2_y_coeff + (i << 4); UWORD8 *pu1_pred_sblk = pu1_luma_rec_buffer + ((i & 0x3) * BLK_SIZE) + (i >> 2) * (ui_rec_width << 2); PROFILE_DISABLE_IQ_IT_RECON() { if(CHECKBIT(ps_cur_mb_info->u2_luma_csbp, i)) { ps_dec->pf_iquant_itrans_recon_luma_4x4( pi2_level, pu1_pred_sblk, pu1_pred_sblk, ui_rec_width, ui_rec_width, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[0], ps_cur_mb_info->u1_qp_div6, ai2_tmp, 1, pi2_level); } else if((CHECKBIT(u4_luma_dc_only_csbp, i)) && pi2_level[0] != 0) { ps_dec->pf_iquant_itrans_recon_luma_4x4_dc( pi2_level, pu1_pred_sblk, pu1_pred_sblk, ui_rec_width, ui_rec_width, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[0], ps_cur_mb_info->u1_qp_div6, ai2_tmp, 1, pi2_level); } } } } } else if(!ps_cur_mb_info->u1_tran_form8x8) { UWORD8 u1_is_left_sub_block, u1_is_top_sub_block = uc_useTopMB; UWORD8 u1_sub_blk_x, u1_sub_blk_y, u1_sub_mb_num; WORD8 i1_top_pred_mode; WORD8 i1_left_pred_mode; UWORD8 *pu1_top, *pu1_left, *pu1_top_left, *pu1_top_right; WORD8 *pi1_cur_pred_mode, *pi1_left_pred_mode, *pc_topPredMode; UWORD16 ui2_left_pred_buf_width = 0xffff; WORD8 i1_intra_pred; UWORD8 *pu1_prev_intra4x4_pred_mode_flag = pu1_prev_intra4x4_pred_mode_data; UWORD8 *pu1_rem_intra4x4_pred_mode = pu1_prev_intra4x4_pred_mode_data + 16; WORD16 *pi2_y_coeff1; UWORD8 u1_cur_sub_block; UWORD16 ui2_top_rt_mask; /*--------------------------------------------------------------------*/ /* 4x4 IntraPrediction */ /*--------------------------------------------------------------------*/ /* Calculation of Top Right subblock mask */ /* */ /* (a) Set it to default mask */ /* [It has 0 for sublocks which will never have top-right sub block] */ /* */ /* (b) If top MB is not available */ /* Clear the bits of the first row sub blocks */ /* */ /* (c) Set/Clear bit for top-right sublock of MB */ /* [5 sub-block in decoding order] based on TOP RIGHT MB availablity */ /*--------------------------------------------------------------------*/ pu1_top = puc_top; ui2_top_rt_mask = (u1_use_top_right_mb << 3) | (0x5750); if(uc_useTopMB) ui2_top_rt_mask |= 0x7; /*Top Related initialisations*/ pi1_cur_pred_mode = ps_cur_mb_info->ps_curmb->pi1_intrapredmodes; pc_topPredMode = ps_cur_mb_info->ps_top_mb->pi1_intrapredmodes; /*-------------------------------------- if(u1_mbaff) { pi1_cur_pred_mode += (u2_mbx << 2); pc_topPredMode = pi1_cur_pred_mode + ps_cur_mb_info->i1_offset; pi1_cur_pred_mode += (u1_topmb) ? 0: 4; }*/ if(u1_top_available) { if(ps_top_mb->u1_mb_type == I_4x4_MB) *(WORD32*)pi1_cur_pred_mode = *(WORD32*)pc_topPredMode; else *(WORD32*)pi1_cur_pred_mode = (uc_useTopMB) ? DC_DC_DC_DC : NOT_VALID; } else *(WORD32*)pi1_cur_pred_mode = NOT_VALID; /* CHANGED CODE */ /* CHANGED CODE */ /*Left Related initialisations*/ pi1_left_pred_mode = ps_dec->pi1_left_pred_mode; if(!u1_mbaff) { if(u1_left_available) { if(ps_left_mb->u1_mb_type != I_4x4_MB) *(WORD32*)pi1_left_pred_mode = (u2_use_left_mb_pack) ? DC_DC_DC_DC : NOT_VALID; } else { *(WORD32*)pi1_left_pred_mode = NOT_VALID; } } else { UWORD8 u1_curMbfld = ps_cur_mb_info->u1_mb_field_decodingflag; UWORD8 u1_leftMbfld = ps_left_mb->u1_mb_fld; if(u1_curMbfld ^ u1_leftMbfld) { if(u1_topmb | ((u1_topmb == 0) && ((ps_curmb - 1)->u1_mb_type != I_4x4_MB))) { if(u1_left_available) { if(ps_left_mb->u1_mb_type != I_4x4_MB) { if(CHECKBIT(u2_use_left_mb_pack,0) == 0) *(WORD32*)pi1_left_pred_mode = NOT_VALID; else *(WORD32*)pi1_left_pred_mode = DC_DC_DC_DC; } } else *(WORD32*)pi1_left_pred_mode = NOT_VALID; if(u1_curMbfld) { if(u1_left_available) { if((ps_left_mb + 1)->u1_mb_type != I_4x4_MB) { if(u2_use_left_mb_pack >> 8) *(WORD32*)(pi1_left_pred_mode + 4) = DC_DC_DC_DC; else *(WORD32*)(pi1_left_pred_mode + 4) = NOT_VALID; } } else *(WORD32*)(pi1_left_pred_mode + 4) = NOT_VALID; pi1_left_pred_mode[1] = pi1_left_pred_mode[2]; pi1_left_pred_mode[2] = pi1_left_pred_mode[4]; pi1_left_pred_mode[3] = pi1_left_pred_mode[6]; *(WORD32*)(pi1_left_pred_mode + 4) = *(WORD32*)pi1_left_pred_mode; } else { pi1_left_pred_mode[7] = pi1_left_pred_mode[3]; pi1_left_pred_mode[6] = pi1_left_pred_mode[3]; pi1_left_pred_mode[5] = pi1_left_pred_mode[2]; pi1_left_pred_mode[4] = pi1_left_pred_mode[2]; pi1_left_pred_mode[3] = pi1_left_pred_mode[1]; pi1_left_pred_mode[2] = pi1_left_pred_mode[1]; pi1_left_pred_mode[1] = pi1_left_pred_mode[0]; } } pi1_left_pred_mode += (u1_topmb) ? 0 : 4; } else { pi1_left_pred_mode += (u1_topmb) ? 0 : 4; if(u1_left_available) { if(ps_left_mb->u1_mb_type != I_4x4_MB) *(WORD32*)pi1_left_pred_mode = (u2_use_left_mb_pack) ? DC_DC_DC_DC : NOT_VALID; } else *(WORD32*)pi1_left_pred_mode = NOT_VALID; } } /* One time pointer initialisations*/ pi2_y_coeff1 = pi2_y_coeff; pu1_top_left = pu1_ytop_left; /* Scan the sub-blocks in Raster Scan Order */ for(u1_sub_mb_num = 0; u1_sub_mb_num < 16; u1_sub_mb_num++) { /* Align the size to multiple of 8, so that SIMD functions can read 64 bits at a time. Only 13 bytes are actaully used */ UWORD8 au1_ngbr_pels[16]; u1_sub_blk_x = u1_sub_mb_num & 0x3; u1_sub_blk_y = u1_sub_mb_num >> 2; i1_top_pred_mode = pi1_cur_pred_mode[u1_sub_blk_x]; i1_left_pred_mode = pi1_left_pred_mode[u1_sub_blk_y]; u1_use_top_right_mb = (!!CHECKBIT(ui2_top_rt_mask, u1_sub_mb_num)); /*********** left subblock availability**********/ if(u1_sub_blk_x) u1_is_left_sub_block = 1; else u1_is_left_sub_block = (u1_sub_blk_y < 2) ? (CHECKBIT(u2_use_left_mb_pack, 0)) : (u2_use_left_mb_pack >> 8); /* CHANGED CODE */ if(u1_sub_blk_y) u1_is_top_sub_block = 1; /* CHANGED CODE */ /***************** Top *********************/ if(ps_dec->u4_use_intrapred_line_copy == 1) { if(u1_sub_blk_y) pu1_top = pu1_luma_rec_buffer - ui_rec_width; else pu1_top = puc_top + (u1_sub_blk_x << 2); } else { pu1_top = pu1_luma_rec_buffer - ui_rec_width; } /***************** Top Right *********************/ pu1_top_right = pu1_top + 4; /***************** Top Left *********************/ pu1_top_left = pu1_top - 1; /***************** Left *********************/ pu1_left = pu1_luma_rec_buffer - 1; /* CHANGED CODE */ /*---------------------------------------------------------------*/ /* Calculation of Intra prediction mode */ /*---------------------------------------------------------------*/ i1_intra_pred = ((i1_left_pred_mode < 0) | (i1_top_pred_mode < 0)) ? DC : MIN(i1_left_pred_mode, i1_top_pred_mode); { UWORD8 u1_packed_modes = (u1_is_top_sub_block << 1) + u1_is_left_sub_block; UWORD8 *pu1_intra_err_codes = (UWORD8 *)gau1_ih264d_intra_pred_err_code; UWORD8 uc_b2b0 = ((u1_sub_mb_num & 4) >> 1) | (u1_sub_mb_num & 1); UWORD8 uc_b3b1 = ((u1_sub_mb_num & 8) >> 2) | ((u1_sub_mb_num & 2) >> 1); u1_cur_sub_block = (uc_b3b1 << 2) + uc_b2b0; PROFILE_DISABLE_INTRA_PRED() if(!pu1_prev_intra4x4_pred_mode_flag[u1_cur_sub_block]) { i1_intra_pred = pu1_rem_intra4x4_pred_mode[u1_cur_sub_block] + (pu1_rem_intra4x4_pred_mode[u1_cur_sub_block] >= i1_intra_pred); } i1_intra_pred = CLIP3(0, 8, i1_intra_pred); { UWORD8 u1_err_code = pu1_intra_err_codes[i1_intra_pred]; if((u1_err_code & u1_packed_modes) ^ u1_err_code) { i1_intra_pred = 0; ps_dec->i4_error_code = ERROR_INTRAPRED; } } } { /* Get neighbour pixels */ /* left pels */ if(u1_is_left_sub_block) { WORD32 i; for(i = 0; i < 4; i++) au1_ngbr_pels[4 - 1 - i] = pu1_left[i * ui_rec_width]; } else { memset(au1_ngbr_pels, 0, 4); } /* top left pels */ au1_ngbr_pels[4] = *pu1_top_left; /* top pels */ if(u1_is_top_sub_block) { memcpy(au1_ngbr_pels + 4 + 1, pu1_top, 4); } else { memset(au1_ngbr_pels + 4 + 1, 0, 4); } /* top right pels */ if(u1_use_top_right_mb) { memcpy(au1_ngbr_pels + 4 * 2 + 1, pu1_top_right, 4); } else if(u1_is_top_sub_block) { memset(au1_ngbr_pels + 4 * 2 + 1, au1_ngbr_pels[4 * 2], 4); } } PROFILE_DISABLE_INTRA_PRED() ps_dec->apf_intra_pred_luma_4x4[i1_intra_pred]( au1_ngbr_pels, pu1_luma_rec_buffer, 1, ui_rec_width, ((u1_is_top_sub_block << 2) | u1_is_left_sub_block)); /* CHANGED CODE */ if(CHECKBIT(ui2_luma_csbp, u1_sub_mb_num)) { WORD16 ai2_tmp[16]; PROFILE_DISABLE_IQ_IT_RECON() { if(CHECKBIT(u4_luma_dc_only_csbp, u1_sub_mb_num)) { ps_dec->pf_iquant_itrans_recon_luma_4x4_dc( pi2_y_coeff1, pu1_luma_rec_buffer, pu1_luma_rec_buffer, ui_rec_width, ui_rec_width, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[0], ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, NULL); } else { ps_dec->pf_iquant_itrans_recon_luma_4x4( pi2_y_coeff1, pu1_luma_rec_buffer, pu1_luma_rec_buffer, ui_rec_width, ui_rec_width, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[0], ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, NULL); } } } /*---------------------------------------------------------------*/ /* Update sub block number */ /*---------------------------------------------------------------*/ pi2_y_coeff1 += 16; pu1_luma_rec_buffer += (u1_sub_blk_x == 3) ? (ui_rec_width << 2) - 12 : 4; pu1_luma_pred_buffer += (u1_sub_blk_x == 3) ? (ui_pred_width << 2) - 12 : 4; /* CHANGED CODE */ pi1_cur_pred_mode[u1_sub_blk_x] = i1_intra_pred; pi1_left_pred_mode[u1_sub_blk_y] = i1_intra_pred; } } else if((u1_mb_type == I_4x4_MB) && (ps_cur_mb_info->u1_tran_form8x8 == 1)) { UWORD8 u1_is_left_sub_block, u1_is_top_sub_block = uc_useTopMB; UWORD8 u1_sub_blk_x, u1_sub_blk_y, u1_sub_mb_num; WORD8 i1_top_pred_mode; WORD8 i1_left_pred_mode; UWORD8 *pu1_top, *pu1_left, *pu1_top_left; WORD8 *pi1_cur_pred_mode, *pi1_left_pred_mode, *pc_topPredMode; UWORD16 ui2_left_pred_buf_width = 0xffff; WORD8 i1_intra_pred; UWORD8 *pu1_prev_intra4x4_pred_mode_flag = pu1_prev_intra4x4_pred_mode_data; UWORD8 *pu1_rem_intra4x4_pred_mode = pu1_prev_intra4x4_pred_mode_data + 4; WORD16 *pi2_y_coeff1; UWORD16 ui2_top_rt_mask; UWORD32 u4_4x4_left_offset = 0; /*--------------------------------------------------------------------*/ /* 8x8 IntraPrediction */ /*--------------------------------------------------------------------*/ /* Calculation of Top Right subblock mask */ /* */ /* (a) Set it to default mask */ /* [It has 0 for sublocks which will never have top-right sub block] */ /* */ /* (b) If top MB is not available */ /* Clear the bits of the first row sub blocks */ /* */ /* (c) Set/Clear bit for top-right sublock of MB */ /* [5 sub-block in decoding order] based on TOP RIGHT MB availablity */ /* */ /* ui2_top_rt_mask: marks availibility of top right(neighbour) */ /* in the 8x8 Block ordering */ /* */ /* tr0 tr1 */ /* 0 1 tr3 */ /* 2 3 */ /* */ /* Top rights for 0 is in top MB */ /* top right of 1 will be in top right MB */ /* top right of 3 in right MB and hence not available */ /* This corresponds to ui2_top_rt_mask having default value 0x4 */ /*--------------------------------------------------------------------*/ ui2_top_rt_mask = (u1_use_top_right_mb << 1) | (0x4); if(uc_useTopMB) { ui2_top_rt_mask |= 0x1; } /* Top Related initialisations */ pi1_cur_pred_mode = ps_cur_mb_info->ps_curmb->pi1_intrapredmodes; pc_topPredMode = ps_cur_mb_info->ps_top_mb->pi1_intrapredmodes; /* if(u1_mbaff) { pi1_cur_pred_mode += (u2_mbx << 2); pc_topPredMode = pi1_cur_pred_mode + ps_cur_mb_info->i1_offset; pi1_cur_pred_mode += (u1_topmb) ? 0: 4; } */ if(u1_top_available) { if(ps_top_mb->u1_mb_type == I_4x4_MB) { *(WORD32*)pi1_cur_pred_mode = *(WORD32*)pc_topPredMode; } else { *(WORD32*)pi1_cur_pred_mode = (uc_useTopMB) ? DC_DC_DC_DC : NOT_VALID; } } else { *(WORD32*)pi1_cur_pred_mode = NOT_VALID; } pu1_top = puc_top - 8; /*Left Related initialisations*/ pi1_left_pred_mode = ps_dec->pi1_left_pred_mode; if(!u1_mbaff) { if(u1_left_available) { if(ps_left_mb->u1_mb_type != I_4x4_MB) { *(WORD32*)pi1_left_pred_mode = (u2_use_left_mb_pack) ? DC_DC_DC_DC : NOT_VALID; } } else { *(WORD32*)pi1_left_pred_mode = NOT_VALID; } } else { UWORD8 u1_curMbfld = ps_cur_mb_info->u1_mb_field_decodingflag; UWORD8 u1_leftMbfld = ps_left_mb->u1_mb_fld; if((!u1_curMbfld) && (u1_leftMbfld)) { u4_4x4_left_offset = 1; } if(u1_curMbfld ^ u1_leftMbfld) { if(u1_topmb | ((u1_topmb == 0) && ((ps_curmb - 1)->u1_mb_type != I_4x4_MB))) { if(u1_left_available) { if(ps_left_mb->u1_mb_type != I_4x4_MB) { if(CHECKBIT(u2_use_left_mb_pack,0) == 0) { *(WORD32*)pi1_left_pred_mode = NOT_VALID; } else { *(WORD32*)pi1_left_pred_mode = DC_DC_DC_DC; } } } else { *(WORD32*)pi1_left_pred_mode = NOT_VALID; } if(u1_curMbfld) { if(u1_left_available) { if((ps_left_mb + 1)->u1_mb_type != I_4x4_MB) { if(u2_use_left_mb_pack >> 8) { *(WORD32*)(pi1_left_pred_mode + 4) = DC_DC_DC_DC; } else { *(WORD32*)(pi1_left_pred_mode + 4) = NOT_VALID; } } } else { *(WORD32*)(pi1_left_pred_mode + 4) = NOT_VALID; } pi1_left_pred_mode[1] = pi1_left_pred_mode[2]; pi1_left_pred_mode[2] = pi1_left_pred_mode[4]; pi1_left_pred_mode[3] = pi1_left_pred_mode[6]; *(WORD32*)(pi1_left_pred_mode + 4) = *(WORD32*)pi1_left_pred_mode; } else { pi1_left_pred_mode[7] = pi1_left_pred_mode[3]; pi1_left_pred_mode[6] = pi1_left_pred_mode[3]; pi1_left_pred_mode[5] = pi1_left_pred_mode[2]; pi1_left_pred_mode[4] = pi1_left_pred_mode[2]; pi1_left_pred_mode[3] = pi1_left_pred_mode[1]; pi1_left_pred_mode[2] = pi1_left_pred_mode[1]; pi1_left_pred_mode[1] = pi1_left_pred_mode[0]; } } pi1_left_pred_mode += (u1_topmb) ? 0 : 4; } else { pi1_left_pred_mode += (u1_topmb) ? 0 : 4; if(u1_left_available) { if(ps_left_mb->u1_mb_type != I_4x4_MB) { *(WORD32*)pi1_left_pred_mode = (u2_use_left_mb_pack) ? DC_DC_DC_DC : NOT_VALID; } } else { *(WORD32*)pi1_left_pred_mode = NOT_VALID; } } } /* One time pointer initialisations*/ pi2_y_coeff1 = pi2_y_coeff; if(u1_use_top_left_mb) { pu1_top_left = pu1_ytop_left; } else { pu1_top_left = NULL; } /* Scan the sub-blocks in Raster Scan Order */ for(u1_sub_mb_num = 0; u1_sub_mb_num < 4; u1_sub_mb_num++) { u1_sub_blk_x = (u1_sub_mb_num & 0x1); u1_sub_blk_y = (u1_sub_mb_num >> 1); i1_top_pred_mode = pi1_cur_pred_mode[u1_sub_blk_x << 1]; i1_left_pred_mode = pi1_left_pred_mode[u1_sub_blk_y << 1]; if(2 == u1_sub_mb_num) { i1_left_pred_mode = pi1_left_pred_mode[(u1_sub_blk_y << 1) + u4_4x4_left_offset]; } u1_use_top_right_mb = (!!CHECKBIT(ui2_top_rt_mask, u1_sub_mb_num)); /*********** left subblock availability**********/ if(u1_sub_blk_x) { u1_is_left_sub_block = 1; } else { u1_is_left_sub_block = (u1_sub_blk_y < 1) ? (CHECKBIT(u2_use_left_mb_pack, 0)) : (u2_use_left_mb_pack >> 8); } /***************** Top *********************/ if(u1_sub_blk_y) { u1_is_top_sub_block = 1; // sushant pu1_top = /*pu1_luma_pred_buffer*/pu1_luma_rec_buffer - ui_rec_width; } else { pu1_top += 8; } /***************** Left *********************/ if((u1_sub_blk_x) | (u4_num_pmbair != 0)) { // sushant pu1_left = /*pu1_luma_pred_buffer*/pu1_luma_rec_buffer - 1; ui2_left_pred_buf_width = ui_rec_width; } else { pu1_left = pu1_yleft; pu1_yleft += (ui_rec_width << 3); ui2_left_pred_buf_width = ui_rec_width; } /***************** Top Left *********************/ if(u1_sub_mb_num) { pu1_top_left = (u1_sub_blk_x) ? pu1_top - 1 : pu1_left - ui_rec_width; if((u1_sub_blk_x && (!u1_is_top_sub_block)) || ((!u1_sub_blk_x) && (!u1_is_left_sub_block))) { pu1_top_left = NULL; } } /*---------------------------------------------------------------*/ /* Calculation of Intra prediction mode */ /*---------------------------------------------------------------*/ i1_intra_pred = ((i1_left_pred_mode < 0) | (i1_top_pred_mode < 0)) ? DC : MIN(i1_left_pred_mode, i1_top_pred_mode); { UWORD8 u1_packed_modes = (u1_is_top_sub_block << 1) + u1_is_left_sub_block; UWORD8 *pu1_intra_err_codes = (UWORD8 *)gau1_ih264d_intra_pred_err_code; /********************************************************************/ /* Same intra4x4_pred_mode array is filled with intra4x4_pred_mode */ /* for a MB with 8x8 intrapredicition */ /********************************************************************/ PROFILE_DISABLE_INTRA_PRED() if(!pu1_prev_intra4x4_pred_mode_flag[u1_sub_mb_num]) { i1_intra_pred = pu1_rem_intra4x4_pred_mode[u1_sub_mb_num] + (pu1_rem_intra4x4_pred_mode[u1_sub_mb_num] >= i1_intra_pred); } i1_intra_pred = CLIP3(0, 8, i1_intra_pred); { UWORD8 u1_err_code = pu1_intra_err_codes[i1_intra_pred]; if((u1_err_code & u1_packed_modes) ^ u1_err_code) { i1_intra_pred = 0; ps_dec->i4_error_code = ERROR_INTRAPRED; } } } { /* Align the size to multiple of 8, so that SIMD functions can read 64 bits at a time. Only 25 bytes are actaully used */ UWORD8 au1_ngbr_pels[32] = {0}; WORD32 ngbr_avail; ngbr_avail = u1_is_left_sub_block << 0; ngbr_avail |= u1_is_top_sub_block << 2; if(pu1_top_left) ngbr_avail |= 1 << 1; ngbr_avail |= u1_use_top_right_mb << 3; PROFILE_DISABLE_INTRA_PRED() { ps_dec->pf_intra_pred_ref_filtering(pu1_left, pu1_top_left, pu1_top, au1_ngbr_pels, ui2_left_pred_buf_width, ngbr_avail); ps_dec->apf_intra_pred_luma_8x8[i1_intra_pred]( au1_ngbr_pels, pu1_luma_rec_buffer, 1, ui_rec_width, ((u1_is_top_sub_block << 2) | u1_is_left_sub_block)); } } /* Inverse Transform and Reconstruction */ if(CHECKBIT(ps_cur_mb_info->u1_cbp, u1_sub_mb_num)) { WORD16 *pi2_scale_matrix_ptr; WORD16 ai2_tmp[64]; pi2_scale_matrix_ptr = ps_dec->s_high_profile.i2_scalinglist8x8[0]; PROFILE_DISABLE_IQ_IT_RECON() { if(CHECKBIT(u4_luma_dc_only_cbp, u1_sub_mb_num)) { ps_dec->pf_iquant_itrans_recon_luma_8x8_dc( pi2_y_coeff1, pu1_luma_rec_buffer, pu1_luma_rec_buffer, ui_rec_width, ui_rec_width, gau1_ih264d_dequant8x8_cavlc[ps_cur_mb_info->u1_qp_rem6], (UWORD16 *)pi2_scale_matrix_ptr, ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, NULL); } else { ps_dec->pf_iquant_itrans_recon_luma_8x8( pi2_y_coeff1, pu1_luma_rec_buffer, pu1_luma_rec_buffer, ui_rec_width, ui_rec_width, gau1_ih264d_dequant8x8_cavlc[ps_cur_mb_info->u1_qp_rem6], (UWORD16 *)pi2_scale_matrix_ptr, ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, NULL); } } } /*---------------------------------------------------------------*/ /* Update sub block number */ /*---------------------------------------------------------------*/ pi2_y_coeff1 += 64; pu1_luma_rec_buffer += (u1_sub_blk_x == 1) ? (ui_rec_width << 3) - (8 * 1) : 8; /*---------------------------------------------------------------*/ /* Pred mode filled in terms of 4x4 block so replicated in 2 */ /* locations. */ /*---------------------------------------------------------------*/ pi1_cur_pred_mode[u1_sub_blk_x << 1] = i1_intra_pred; pi1_cur_pred_mode[(u1_sub_blk_x << 1) + 1] = i1_intra_pred; pi1_left_pred_mode[u1_sub_blk_y << 1] = i1_intra_pred; pi1_left_pred_mode[(u1_sub_blk_y << 1) + 1] = i1_intra_pred; } } /* Decode Chroma Block */ ih264d_unpack_chroma_coeff4x4_mb(ps_dec, ps_cur_mb_info); /*--------------------------------------------------------------------*/ /* Chroma Blocks decoding */ /*--------------------------------------------------------------------*/ { UWORD8 u1_intra_chrom_pred_mode; UWORD8 u1_chroma_cbp = (UWORD8)(ps_cur_mb_info->u1_cbp >> 4); /*--------------------------------------------------------------------*/ /* Perform Chroma intra prediction */ /*--------------------------------------------------------------------*/ u1_intra_chrom_pred_mode = CHROMA_TO_LUMA_INTRA_MODE( ps_cur_mb_info->u1_chroma_pred_mode); { UWORD8 u1_packed_modes = (u1_top_available << 1) + u1_left_available; UWORD8 u1_err_code = (u1_intra_chrom_pred_mode & 1) ? u1_intra_chrom_pred_mode : (u1_intra_chrom_pred_mode ^ 2); if((u1_err_code & u1_packed_modes) ^ u1_err_code) { u1_intra_chrom_pred_mode = 0; ps_dec->i4_error_code = ERROR_INTRAPRED; } } /* CHANGED CODE */ if(u1_chroma_cbp != CBPC_ALLZERO) { UWORD16 u2_chroma_csbp = (u1_chroma_cbp == CBPC_ACZERO) ? 0 : ps_cur_mb_info->u2_chroma_csbp; UWORD32 u4_scale_u; UWORD32 u4_scale_v; { UWORD16 au2_ngbr_pels[33]; UWORD8 *pu1_ngbr_pels = (UWORD8 *)au2_ngbr_pels; UWORD16 *pu2_left_uv; UWORD16 *pu2_topleft_uv; WORD32 use_left1 = (u2_use_left_mb_pack & 0x0ff); WORD32 use_left2 = (u2_use_left_mb_pack & 0xff00) >> 8; pu2_left_uv = (UWORD16 *)pu1_uleft; pu2_topleft_uv = (UWORD16 *)pu1_u_top_left; /* Get neighbour pixels */ /* left pels */ if(u2_use_left_mb_pack) { WORD32 i; if(use_left1) { for(i = 0; i < 4; i++) au2_ngbr_pels[8 - 1 - i] = pu2_left_uv[i * u4_recwidth_cr / YUV420SP_FACTOR]; } else { memset(au2_ngbr_pels + 4, 0, 4 * sizeof(UWORD16)); } if(use_left2) { for(i = 4; i < 8; i++) au2_ngbr_pels[8 - 1 - i] = pu2_left_uv[i * u4_recwidth_cr / YUV420SP_FACTOR]; } else { memset(au2_ngbr_pels, 0, 4 * sizeof(UWORD16)); } } else { memset(au2_ngbr_pels, 0, 8 * sizeof(UWORD16)); } /* top left pels */ au2_ngbr_pels[8] = *pu2_topleft_uv; /* top pels */ if(uc_useTopMB) { memcpy(au2_ngbr_pels + 8 + 1, pu1_top_u, 8 * sizeof(UWORD16)); } else { memset(au2_ngbr_pels + 8 + 1, 0, 8 * sizeof(UWORD16)); } PROFILE_DISABLE_INTRA_PRED() ps_dec->apf_intra_pred_chroma[u1_intra_chrom_pred_mode]( pu1_ngbr_pels, pu1_mb_cb_rei1_buffer, 1, u4_recwidth_cr, ((uc_useTopMB << 2) | (use_left2 << 4) | use_left1)); } u4_scale_u = ps_cur_mb_info->u1_qpc_div6; u4_scale_v = ps_cur_mb_info->u1_qpcr_div6; pi2_y_coeff = ps_dec->pi2_coeff_data; { UWORD32 i; WORD16 ai2_tmp[16]; for(i = 0; i < 4; i++) { WORD16 *pi2_level = pi2_y_coeff + (i << 4); UWORD8 *pu1_pred_sblk = pu1_mb_cb_rei1_buffer + ((i & 0x1) * BLK_SIZE * YUV420SP_FACTOR) + (i >> 1) * (u4_recwidth_cr << 2); PROFILE_DISABLE_IQ_IT_RECON() { if(CHECKBIT(u2_chroma_csbp, i)) { ps_dec->pf_iquant_itrans_recon_chroma_4x4( pi2_level, pu1_pred_sblk, pu1_pred_sblk, u4_recwidth_cr, u4_recwidth_cr, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpc_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[1], u4_scale_u, ai2_tmp, pi2_level); } else if(pi2_level[0] != 0) { ps_dec->pf_iquant_itrans_recon_chroma_4x4_dc( pi2_level, pu1_pred_sblk, pu1_pred_sblk, u4_recwidth_cr, u4_recwidth_cr, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpc_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[1], u4_scale_u, ai2_tmp, pi2_level); } } } } pi2_y_coeff += MB_CHROM_SIZE; u2_chroma_csbp = u2_chroma_csbp >> 4; { UWORD32 i; WORD16 ai2_tmp[16]; for(i = 0; i < 4; i++) { WORD16 *pi2_level = pi2_y_coeff + (i << 4); UWORD8 *pu1_pred_sblk = pu1_mb_cb_rei1_buffer + 1 + ((i & 0x1) * BLK_SIZE * YUV420SP_FACTOR) + (i >> 1) * (u4_recwidth_cr << 2); PROFILE_DISABLE_IQ_IT_RECON() { if(CHECKBIT(u2_chroma_csbp, i)) { ps_dec->pf_iquant_itrans_recon_chroma_4x4( pi2_level, pu1_pred_sblk, pu1_pred_sblk, u4_recwidth_cr, u4_recwidth_cr, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpcr_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[2], u4_scale_v, ai2_tmp, pi2_level); } else if(pi2_level[0] != 0) { ps_dec->pf_iquant_itrans_recon_chroma_4x4_dc( pi2_level, pu1_pred_sblk, pu1_pred_sblk, u4_recwidth_cr, u4_recwidth_cr, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpcr_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[2], u4_scale_v, ai2_tmp, pi2_level); } } } } } else { /* If no inverse transform is needed, pass recon buffer pointer */ /* to Intraprediction module instead of pred buffer pointer */ { UWORD16 au2_ngbr_pels[33]; UWORD8 *pu1_ngbr_pels = (UWORD8 *)au2_ngbr_pels; UWORD16 *pu2_left_uv; UWORD16 *pu2_topleft_uv; WORD32 use_left1 = (u2_use_left_mb_pack & 0x0ff); WORD32 use_left2 = (u2_use_left_mb_pack & 0xff00) >> 8; pu2_topleft_uv = (UWORD16 *)pu1_u_top_left; pu2_left_uv = (UWORD16 *)pu1_uleft; /* Get neighbour pixels */ /* left pels */ if(u2_use_left_mb_pack) { WORD32 i; if(use_left1) { for(i = 0; i < 4; i++) au2_ngbr_pels[8 - 1 - i] = pu2_left_uv[i * u4_recwidth_cr / YUV420SP_FACTOR]; } else { memset(au2_ngbr_pels + 4, 0, 4 * sizeof(UWORD16)); } if(use_left2) { for(i = 4; i < 8; i++) au2_ngbr_pels[8 - 1 - i] = pu2_left_uv[i * u4_recwidth_cr / YUV420SP_FACTOR]; } else { memset(au2_ngbr_pels, 0, 4 * sizeof(UWORD16)); } } else { memset(au2_ngbr_pels, 0, 8 * sizeof(UWORD16)); } /* top left pels */ au2_ngbr_pels[8] = *pu2_topleft_uv; /* top pels */ if(uc_useTopMB) { memcpy(au2_ngbr_pels + 8 + 1, pu1_top_u, 8 * sizeof(UWORD16)); } else { memset(au2_ngbr_pels + 8 + 1, 0, 8 * sizeof(UWORD16)); } PROFILE_DISABLE_INTRA_PRED() ps_dec->apf_intra_pred_chroma[u1_intra_chrom_pred_mode]( pu1_ngbr_pels, pu1_mb_cb_rei1_buffer, 1, u4_recwidth_cr, ((uc_useTopMB << 2) | (use_left2 << 4) | use_left1)); } } } return OK; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_process_intra_mb.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_process_intra_mb.h * * \brief * Contains routines that decode a I slice type * * Detailed_description * * \date * 07/07/2003 * * \author NS ************************************************************************** */ #ifndef _IH264D_PROCESS_INTRA_MB_H_ #define _IH264D_PROCESS_INTRA_MB_H_ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" #define CHROMA_TO_LUMA_INTRA_MODE(x) (x ^ ( (!(x & 0x01)) << 1)) #define MB_TYPE_TO_INTRA_16x16_MODE(x) ((x - 1) & 0x03) UWORD32 ih264d_unpack_luma_coeff4x4_mb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 intra_flag); void ih264d_unpack_chroma_coeff4x4_mb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info); UWORD32 ih264d_unpack_luma_coeff8x8_mb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info); WORD32 ih264d_read_intra_pred_modes(dec_struct_t *ps_dec, UWORD8 *pu1_prev_intra4x4_pred_mode_flag, UWORD8 *pu1_rem_intra4x4_pred_mode, UWORD32 u4_trans_form8x8); WORD32 ih264d_process_intra_mb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num); #endif /* _IH264D_PROCESS_INTRA_MB_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_process_pslice.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_process_pslice.c * * \brief * Contains routines that decode a I slice type * * Detailed_description * * \date * 21/12/2002 * * \author NS ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include <string.h> #include "ih264d_bitstrm.h" #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264d_parse_cavlc.h" #include "ih264d_mb_utils.h" #include "ih264d_deblocking.h" #include "ih264d_dpb_manager.h" #include "ih264d_mvpred.h" #include "ih264d_inter_pred.h" #include "ih264d_process_pslice.h" #include "ih264d_error_handler.h" #include "ih264d_cabac.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_parse_slice.h" #include "ih264d_utils.h" #include "ih264d_parse_islice.h" #include "ih264d_process_bslice.h" #include "ih264d_process_intra_mb.h" void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec); void ih264d_insert_pic_in_ref_pic_listx(struct pic_buffer_t *ps_ref_pic_buf_lx, struct pic_buffer_t *ps_pic) { *ps_ref_pic_buf_lx = *ps_pic; } WORD32 ih264d_mv_pred_ref_tfr_nby2_pmb(dec_struct_t * ps_dec, UWORD8 u1_mb_idx, UWORD8 u1_num_mbs) { parse_pmbarams_t * ps_mb_part_info; parse_part_params_t * ps_part; mv_pred_t *ps_mv_nmb, *ps_mv_nmb_start, *ps_mv_ntop, *ps_mv_ntop_start; UWORD32 i, j; const UWORD32 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; dec_mb_info_t * ps_cur_mb_info; WORD32 i2_mv_x, i2_mv_y; WORD32 ret; ps_dec->i4_submb_ofst -= (u1_num_mbs - u1_mb_idx) << 4; ps_mb_part_info = ps_dec->ps_parse_mb_data; // + u1_mb_idx; ps_part = ps_dec->ps_parse_part_params; // + u1_mb_idx; /* N/2 Mb MvPred and Transfer Setup Loop */ for(i = u1_mb_idx; i < u1_num_mbs; i++, ps_mb_part_info++) { UWORD32 u1_colz; UWORD32 u1_field; mv_pred_t s_mvPred; mv_pred_t *ps_mv_pred = &s_mvPred; *ps_mv_pred = ps_dec->s_default_mv_pred; ps_dec->i4_submb_ofst += SUB_BLK_SIZE; /* Restore the slice scratch MbX and MbY context */ ps_cur_mb_info = ps_dec->ps_nmb_info + i; u1_field = ps_cur_mb_info->u1_mb_field_decodingflag; ps_mv_nmb_start = ps_dec->ps_mv_cur + (i << 4); ps_dec->u2_mbx = ps_cur_mb_info->u2_mbx; ps_dec->u2_mby = ps_cur_mb_info->u2_mby; ps_dec->u2_mv_2mb[i & 0x1] = 0; /* Look for MV Prediction and Reference Transfer in Non-I Mbs */ if(!ps_mb_part_info->u1_isI_mb) { UWORD32 u1_blk_no; WORD32 i1_ref_idx, i1_ref_idx1; UWORD32 u1_sub_mb_x, u1_sub_mb_y, u1_sub_mb_num; UWORD32 u1_num_part, u1_num_ref, u1_wd, u1_ht; UWORD32 *pu4_wt_offst, **ppu4_wt_ofst; UWORD32 u1_scale_ref, u4_bot_mb; WORD8 *pi1_ref_idx = ps_mb_part_info->i1_ref_idx[0]; pic_buffer_t *ps_ref_frame, **pps_ref_frame; deblk_mb_t * ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + i; /* MB Level initialisations */ ps_dec->u4_num_pmbair = i >> u1_mbaff; ps_dec->u1_mb_idx_mv = i; ppu4_wt_ofst = ps_mb_part_info->pu4_wt_offst; pps_ref_frame = ps_dec->ps_ref_pic_buf_lx[0]; /* CHANGED CODE */ ps_mv_ntop_start = ps_mv_nmb_start - (ps_dec->u2_frm_wd_in_mbs << (4 + u1_mbaff)) + 12; u1_num_part = ps_mb_part_info->u1_num_part; ps_cur_deblk_mb->u1_mb_type |= (u1_num_part > 1) << 1; ps_cur_mb_info->u4_pred_info_pkd_idx = ps_dec->u4_pred_info_pkd_idx; ps_cur_mb_info->u1_num_pred_parts = 0; /****************************************************/ /* weighted u4_ofst pointer calculations, this loop */ /* runs maximum 4 times, even in direct cases */ /****************************************************/ u1_scale_ref = u1_mbaff & u1_field; u4_bot_mb = 1 - ps_cur_mb_info->u1_topmb; if(ps_dec->ps_cur_pps->u1_wted_pred_flag) { u1_num_ref = MIN(u1_num_part, 4); for(u1_blk_no = 0; u1_blk_no < u1_num_ref; u1_blk_no++) { i1_ref_idx = pi1_ref_idx[u1_blk_no]; if(u1_scale_ref) i1_ref_idx >>= 1; pu4_wt_offst = (UWORD32*)&ps_dec->pu4_wt_ofsts[2 * X3(i1_ref_idx)]; ppu4_wt_ofst[u1_blk_no] = pu4_wt_offst; } } else { ppu4_wt_ofst[0] = NULL; ppu4_wt_ofst[1] = NULL; ppu4_wt_ofst[2] = NULL; ppu4_wt_ofst[3] = NULL; } /**************************************************/ /* Loop on Partitions */ /**************************************************/ for(j = 0; j < u1_num_part; j++, ps_part++) { u1_sub_mb_num = ps_part->u1_sub_mb_num; ps_dec->u1_sub_mb_num = u1_sub_mb_num; if(PART_NOT_DIRECT != ps_part->u1_is_direct) { /* Mb Skip Mode */ /* Setting the default and other members of MvPred Structure */ s_mvPred.i2_mv[2] = -1; s_mvPred.i2_mv[3] = -1; s_mvPred.i1_ref_frame[0] = 0; i1_ref_idx = (u1_scale_ref && u4_bot_mb) ? MAX_REF_BUFS : 0; ps_ref_frame = pps_ref_frame[i1_ref_idx]; s_mvPred.u1_col_ref_pic_idx = ps_ref_frame->u1_mv_buf_id; s_mvPred.u1_pic_type = ps_ref_frame->u1_pic_type; pu4_wt_offst = (UWORD32*)&ps_dec->pu4_wt_ofsts[0]; ps_dec->pf_mvpred(ps_dec, ps_cur_mb_info, ps_mv_nmb_start, ps_mv_ntop_start, &s_mvPred, 0, 4, 0, 1, MB_SKIP); { pred_info_pkd_t *ps_pred_pkd; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info (s_mvPred.i2_mv,4,4,0,PRED_L0,ps_pred_pkd,ps_ref_frame->u1_pic_buf_id, (i1_ref_idx >> u1_scale_ref),pu4_wt_offst, ps_ref_frame->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } /* Storing colocated zero information */ u1_colz = ((ABS(s_mvPred.i2_mv[0]) <= 1) && (ABS(s_mvPred.i2_mv[1]) <= 1)) + (u1_field << 1); ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb_start, 0, u1_colz, 4, 4); } else { u1_sub_mb_x = u1_sub_mb_num & 0x03; u1_sub_mb_y = u1_sub_mb_num >> 2; u1_blk_no = (u1_num_part < 4) ? j : (((u1_sub_mb_y >> 1) << 1) + (u1_sub_mb_x >> 1)); ps_mv_ntop = ps_mv_ntop_start + u1_sub_mb_x; ps_mv_nmb = ps_mv_nmb_start + u1_sub_mb_num; u1_wd = ps_part->u1_partwidth; u1_ht = ps_part->u1_partheight; /* Populate the colpic info and reference frames */ i1_ref_idx = pi1_ref_idx[u1_blk_no]; s_mvPred.i1_ref_frame[0] = i1_ref_idx; /********************************************************/ /* Predict Mv */ /* Add Mv Residuals and store back */ /********************************************************/ ps_dec->pf_mvpred(ps_dec, ps_cur_mb_info, ps_mv_nmb, ps_mv_ntop, &s_mvPred, u1_sub_mb_num, u1_wd, 0, 1, ps_cur_mb_info->u1_mb_mc_mode); i2_mv_x = ps_mv_nmb->i2_mv[0]; i2_mv_y = ps_mv_nmb->i2_mv[1]; i2_mv_x += s_mvPred.i2_mv[0]; i2_mv_y += s_mvPred.i2_mv[1]; s_mvPred.i2_mv[0] = i2_mv_x; s_mvPred.i2_mv[1] = i2_mv_y; /********************************************************/ /* Transfer setup call */ /* convert RefIdx if it is MbAff */ /* Pass Weight Offset and refFrame */ /********************************************************/ i1_ref_idx1 = i1_ref_idx >> u1_scale_ref; if(u1_scale_ref && ((i1_ref_idx & 0x01) != u4_bot_mb)) i1_ref_idx1 += MAX_REF_BUFS; ps_ref_frame = pps_ref_frame[i1_ref_idx1]; pu4_wt_offst = ppu4_wt_ofst[u1_blk_no]; { pred_info_pkd_t *ps_pred_pkd; ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; ih264d_fill_pred_info (s_mvPred.i2_mv,u1_wd,u1_ht,u1_sub_mb_num,PRED_L0,ps_pred_pkd, ps_ref_frame->u1_pic_buf_id,(i1_ref_idx >> u1_scale_ref),pu4_wt_offst, ps_ref_frame->u1_pic_type); ps_dec->u4_pred_info_pkd_idx++; ps_cur_mb_info->u1_num_pred_parts++; } /* Fill colocated info in MvPred structure */ s_mvPred.u1_col_ref_pic_idx = ps_ref_frame->u1_mv_buf_id; s_mvPred.u1_pic_type = ps_ref_frame->u1_pic_type; /* Calculating colocated zero information */ u1_colz = (u1_field << 1) | ((i1_ref_idx == 0) && (ABS(i2_mv_x) <= 1) && (ABS(i2_mv_y) <= 1)); u1_colz |= ps_mb_part_info->u1_col_info[u1_blk_no]; /* Replicate the motion vectors and colzero u4_flag */ /* for all sub-partitions */ ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb, u1_sub_mb_num, u1_colz, u1_ht, u1_wd); } } } else { /* Storing colocated zero information */ ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb_start, 0, (UWORD8)(u1_field << 1), 4, 4); } /*if num _cores is set to 3,compute bs will be done in another thread*/ if(ps_dec->u4_num_cores < 3) { if(ps_dec->u4_app_disable_deblk_frm == 0) ps_dec->pf_compute_bs(ps_dec, ps_cur_mb_info, (UWORD16)(i >> u1_mbaff)); } } return OK; } WORD32 ih264d_decode_recon_tfr_nmb(dec_struct_t * ps_dec, UWORD8 u1_mb_idx, UWORD8 u1_num_mbs, UWORD8 u1_num_mbs_next, UWORD8 u1_tfr_n_mb, UWORD8 u1_end_of_row) { WORD32 i,j; UWORD32 u1_end_of_row_next; dec_mb_info_t * ps_cur_mb_info; UWORD32 u4_update_mbaff = 0; WORD32 ret; const UWORD32 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; const UWORD32 u1_slice_type = ps_dec->ps_cur_slice->u1_slice_type; const WORD32 u1_skip_th = ( (u1_slice_type != I_SLICE) ? (ps_dec->u1_B ? B_8x8 : PRED_8x8R0) : -1); const UWORD32 u1_ipcm_th = ( (u1_slice_type != I_SLICE) ? (ps_dec->u1_B ? 23 : 5) : 0); /* N Mb MC Loop */ for(i = u1_mb_idx; i < u1_num_mbs; i++) { ps_cur_mb_info = ps_dec->ps_nmb_info + i; ps_dec->u4_dma_buf_idx = 0; ps_dec->u4_pred_info_idx = 0; if(ps_cur_mb_info->u1_mb_type <= u1_skip_th) { { WORD32 pred_cnt = 0; pred_info_pkd_t *ps_pred_pkd; UWORD32 u4_pred_info_pkd_idx; WORD8 i1_pred; u4_pred_info_pkd_idx = ps_cur_mb_info->u4_pred_info_pkd_idx; while(pred_cnt < ps_cur_mb_info->u1_num_pred_parts) { ps_pred_pkd = ps_dec->ps_pred_pkd + u4_pred_info_pkd_idx; ps_dec->p_form_mb_part_info(ps_pred_pkd,ps_dec, ps_cur_mb_info->u2_mbx,ps_cur_mb_info->u2_mby,(i >> u1_mbaff), ps_cur_mb_info); u4_pred_info_pkd_idx++; pred_cnt++; } } ps_dec->p_motion_compensate(ps_dec, ps_cur_mb_info); } else if(ps_cur_mb_info->u1_mb_type == MB_SKIP) { { WORD32 pred_cnt = 0; pred_info_pkd_t *ps_pred_pkd; UWORD32 u4_pred_info_pkd_idx; WORD8 i1_pred; u4_pred_info_pkd_idx = ps_cur_mb_info->u4_pred_info_pkd_idx; while(pred_cnt < ps_cur_mb_info->u1_num_pred_parts) { ps_pred_pkd = ps_dec->ps_pred_pkd + u4_pred_info_pkd_idx; ps_dec->p_form_mb_part_info(ps_pred_pkd,ps_dec, ps_cur_mb_info->u2_mbx,ps_cur_mb_info->u2_mby,(i >> u1_mbaff), ps_cur_mb_info); u4_pred_info_pkd_idx++; pred_cnt++; } } /* Decode MB skip */ ps_dec->p_motion_compensate(ps_dec, ps_cur_mb_info); } } /* N Mb IQ IT RECON Loop */ for(j = u1_mb_idx; j < i; j++) { ps_cur_mb_info = ps_dec->ps_nmb_info + j; if(ps_cur_mb_info->u1_mb_type <= u1_skip_th) { ih264d_process_inter_mb(ps_dec, ps_cur_mb_info, j); } else if(ps_cur_mb_info->u1_mb_type != MB_SKIP) { if((u1_ipcm_th + 25) != ps_cur_mb_info->u1_mb_type) { ps_cur_mb_info->u1_mb_type -= (u1_skip_th + 1); ih264d_process_intra_mb(ps_dec, ps_cur_mb_info, j); } } if(ps_dec->u4_use_intrapred_line_copy) { ih264d_copy_intra_pred_line(ps_dec, ps_cur_mb_info, j); } } /*N MB deblocking*/ if(ps_dec->u4_nmb_deblk == 1) { UWORD32 u4_cur_mb, u4_right_mb; UWORD32 u4_mb_x, u4_mb_y; UWORD32 u4_wd_y, u4_wd_uv; tfr_ctxt_t *ps_tfr_cxt = &(ps_dec->s_tran_addrecon); UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; const WORD32 i4_cb_qp_idx_ofst = ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; const WORD32 i4_cr_qp_idx_ofst = ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; u4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; u4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; ps_cur_mb_info = ps_dec->ps_nmb_info + u1_mb_idx; ps_dec->u4_deblk_mb_x = ps_cur_mb_info->u2_mbx; ps_dec->u4_deblk_mb_y = ps_cur_mb_info->u2_mby; for(j = u1_mb_idx; j < i; j++) { ih264d_deblock_mb_nonmbaff(ps_dec, ps_tfr_cxt, i4_cb_qp_idx_ofst, i4_cr_qp_idx_ofst, u4_wd_y, u4_wd_uv); } } if(u1_tfr_n_mb) { /****************************************************************/ /* Check for End Of Row in Next iteration */ /****************************************************************/ u1_end_of_row_next = u1_num_mbs_next && (u1_num_mbs_next <= (ps_dec->u1_recon_mb_grp >> u1_mbaff)); /****************************************************************/ /* Transfer the Following things */ /* N-Mb DeblkParams Data ( To Ext DeblkParams Buffer ) */ /* N-Mb Recon Data ( To Ext Frame Buffer ) */ /* N-Mb Intrapredline Data ( Updated Internally) */ /* N-Mb MV Data ( To Ext MV Buffer ) */ /* N-Mb MVTop/TopRight Data ( To Int MV Top Scratch Buffers) */ /****************************************************************/ ih264d_transfer_mb_group_data(ps_dec, u1_num_mbs, u1_end_of_row, u1_end_of_row_next); ps_dec->u4_num_mbs_prev_nmb = u1_num_mbs; ps_dec->u4_pred_info_idx = 0; ps_dec->u4_dma_buf_idx = 0; } return OK; } /*! ************************************************************************** * \if Function name : ih264d_process_inter_mb \endif * * \brief * This function decodes an Inter MB. * * * \return * 0 on Success and Error code otherwise ************************************************************************** */ WORD32 ih264d_process_inter_mb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num) { /* CHANGED CODE */ UWORD8 *pu1_rec_y, *pu1_rec_u, *pu1_rec_v; /*CHANGED CODE */ UWORD32 ui_rec_width, u4_recwidth_cr; WORD16 *pi2_y_coeff; UWORD32 u1_mb_field_decoding_flag; const UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; UWORD32 uc_botMb; UWORD32 u4_num_pmbair; /* CHANGED CODE */ tfr_ctxt_t *ps_frame_buf = ps_dec->ps_frame_buf_ip_recon; UWORD32 u4_luma_dc_only_csbp = 0; UWORD32 u4_luma_dc_only_cbp = 0; /* CHANGED CODE */ uc_botMb = 1 - ps_cur_mb_info->u1_topmb; u4_num_pmbair = (u1_mb_num >> u1_mbaff); u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; /* CHANGED CODE */ pu1_rec_y = ps_frame_buf->pu1_dest_y + (u4_num_pmbair << 4); pu1_rec_u = ps_frame_buf->pu1_dest_u + (u4_num_pmbair << 3) * YUV420SP_FACTOR; pu1_rec_v = ps_frame_buf->pu1_dest_v + (u4_num_pmbair << 3); ui_rec_width = ps_dec->u2_frm_wd_y << u1_mb_field_decoding_flag; u4_recwidth_cr = ps_dec->u2_frm_wd_uv << u1_mb_field_decoding_flag; /* CHANGED CODE */ if(u1_mbaff) { if(uc_botMb) { pu1_rec_y += (u1_mb_field_decoding_flag ? (ui_rec_width >> 1) : (ui_rec_width << 4)); pu1_rec_u += (u1_mb_field_decoding_flag ? (u4_recwidth_cr >> 1) : (u4_recwidth_cr << 3)); pu1_rec_v += (u1_mb_field_decoding_flag ? (u4_recwidth_cr >> 1) : (u4_recwidth_cr << 3)); } } if(!ps_cur_mb_info->u1_tran_form8x8) { u4_luma_dc_only_csbp = ih264d_unpack_luma_coeff4x4_mb(ps_dec, ps_cur_mb_info, 0); } else { if(!ps_dec->ps_cur_pps->u1_entropy_coding_mode) { u4_luma_dc_only_cbp = ih264d_unpack_luma_coeff4x4_mb(ps_dec, ps_cur_mb_info, 0); } else { u4_luma_dc_only_cbp = ih264d_unpack_luma_coeff8x8_mb(ps_dec, ps_cur_mb_info); } } pi2_y_coeff = ps_dec->pi2_coeff_data; /* Inverse Transform and Reconstruction */ if(ps_cur_mb_info->u1_cbp & 0x0f) { /* CHANGED CODE */ if(!ps_cur_mb_info->u1_tran_form8x8) { UWORD32 i; WORD16 ai2_tmp[16]; for(i = 0; i < 16; i++) { if(CHECKBIT(ps_cur_mb_info->u2_luma_csbp, i)) { WORD16 *pi2_level = pi2_y_coeff + (i << 4); UWORD8 *pu1_pred_sblk = pu1_rec_y + ((i & 0x3) * BLK_SIZE) + (i >> 2) * (ui_rec_width << 2); PROFILE_DISABLE_IQ_IT_RECON() { if(CHECKBIT(u4_luma_dc_only_csbp, i)) { ps_dec->pf_iquant_itrans_recon_luma_4x4_dc( pi2_level, pu1_pred_sblk, pu1_pred_sblk, ui_rec_width, ui_rec_width, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[3], ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, NULL); } else { ps_dec->pf_iquant_itrans_recon_luma_4x4( pi2_level, pu1_pred_sblk, pu1_pred_sblk, ui_rec_width, ui_rec_width, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[3], ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, NULL); } } } } } else { WORD16 *pi2_scale_matrix_ptr; WORD32 i; pi2_scale_matrix_ptr = ps_dec->s_high_profile.i2_scalinglist8x8[1]; for(i = 0; i < 4; i++) { WORD16 ai2_tmp[64]; WORD16 *pi16_levelBlock = pi2_y_coeff + (i << 6); /* move to the next 8x8 adding 64 */ UWORD8 *pu1_pred_sblk = pu1_rec_y + ((i & 0x1) * BLK8x8SIZE) + (i >> 1) * (ui_rec_width << 3); if(CHECKBIT(ps_cur_mb_info->u1_cbp, i)) { PROFILE_DISABLE_IQ_IT_RECON() { if(CHECKBIT(u4_luma_dc_only_cbp, i)) { ps_dec->pf_iquant_itrans_recon_luma_8x8_dc( pi16_levelBlock, pu1_pred_sblk, pu1_pred_sblk, ui_rec_width, ui_rec_width, gau1_ih264d_dequant8x8_cavlc[ps_cur_mb_info->u1_qp_rem6], (UWORD16 *)pi2_scale_matrix_ptr, ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, NULL); } else { ps_dec->pf_iquant_itrans_recon_luma_8x8( pi16_levelBlock, pu1_pred_sblk, pu1_pred_sblk, ui_rec_width, ui_rec_width, gau1_ih264d_dequant8x8_cavlc[ps_cur_mb_info->u1_qp_rem6], (UWORD16 *)pi2_scale_matrix_ptr, ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, NULL); } } } } } } /* Decode Chroma Block */ ih264d_unpack_chroma_coeff4x4_mb(ps_dec, ps_cur_mb_info); /*--------------------------------------------------------------------*/ /* Chroma Blocks decoding */ /*--------------------------------------------------------------------*/ { UWORD8 u1_chroma_cbp = (UWORD8)(ps_cur_mb_info->u1_cbp >> 4); if(u1_chroma_cbp != CBPC_ALLZERO) { UWORD32 u4_scale_u = ps_cur_mb_info->u1_qpc_div6; UWORD32 u4_scale_v = ps_cur_mb_info->u1_qpcr_div6; UWORD16 u2_chroma_csbp = ps_cur_mb_info->u2_chroma_csbp; pi2_y_coeff = ps_dec->pi2_coeff_data; { UWORD32 i; WORD16 ai2_tmp[16]; for(i = 0; i < 4; i++) { WORD16 *pi2_level = pi2_y_coeff + (i << 4); UWORD8 *pu1_pred_sblk = pu1_rec_u + ((i & 0x1) * BLK_SIZE * YUV420SP_FACTOR) + (i >> 1) * (u4_recwidth_cr << 2); PROFILE_DISABLE_IQ_IT_RECON() { if(CHECKBIT(u2_chroma_csbp, i)) { ps_dec->pf_iquant_itrans_recon_chroma_4x4( pi2_level, pu1_pred_sblk, pu1_pred_sblk, u4_recwidth_cr, u4_recwidth_cr, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpc_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[4], u4_scale_u, ai2_tmp, pi2_level); } else if(pi2_level[0] != 0) { ps_dec->pf_iquant_itrans_recon_chroma_4x4_dc( pi2_level, pu1_pred_sblk, pu1_pred_sblk, u4_recwidth_cr, u4_recwidth_cr, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpc_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[4], u4_scale_u, ai2_tmp, pi2_level); } } } } pi2_y_coeff += MB_CHROM_SIZE; u2_chroma_csbp >>= 4; { UWORD32 i; WORD16 ai2_tmp[16]; for(i = 0; i < 4; i++) { WORD16 *pi2_level = pi2_y_coeff + (i << 4); UWORD8 *pu1_pred_sblk = pu1_rec_u + 1 + ((i & 0x1) * BLK_SIZE * YUV420SP_FACTOR) + (i >> 1) * (u4_recwidth_cr << 2); PROFILE_DISABLE_IQ_IT_RECON() { if(CHECKBIT(u2_chroma_csbp, i)) { ps_dec->pf_iquant_itrans_recon_chroma_4x4( pi2_level, pu1_pred_sblk, pu1_pred_sblk, u4_recwidth_cr, u4_recwidth_cr, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpcr_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[5], u4_scale_v, ai2_tmp, pi2_level); } else if(pi2_level[0] != 0) { ps_dec->pf_iquant_itrans_recon_chroma_4x4_dc( pi2_level, pu1_pred_sblk, pu1_pred_sblk, u4_recwidth_cr, u4_recwidth_cr, gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpcr_rem6], (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[5], u4_scale_v, ai2_tmp, pi2_level); } } } } } } return (0); } /*! ************************************************************************** * \if Function name : ih264d_parse_pred_weight_table \endif * * \brief * Implements pred_weight_table() of 7.3.3.2. * * \return * None * ************************************************************************** */ WORD32 ih264d_parse_pred_weight_table(dec_slice_params_t * ps_cur_slice, dec_bit_stream_t * ps_bitstrm) { UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; WORD8 i, cont, lx; UWORD8 uc_weight_flag; UWORD32 *pui32_weight_offset_lx; WORD16 c_weight, c_offset; UWORD32 ui32_y_def_weight_ofst, ui32_cr_def_weight_ofst; UWORD32 ui32_temp; UWORD8 uc_luma_log2_weight_denom; UWORD8 uc_chroma_log2_weight_denom; /* Variables for error resilience checks */ UWORD32 u4_temp; WORD32 i_temp; u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp > MAX_LOG2_WEIGHT_DENOM) { return ERROR_PRED_WEIGHT_TABLE_T; } uc_luma_log2_weight_denom = u4_temp; COPYTHECONTEXT("SH: luma_log2_weight_denom",uc_luma_log2_weight_denom); ui32_y_def_weight_ofst = (1 << uc_luma_log2_weight_denom); u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u4_temp > MAX_LOG2_WEIGHT_DENOM) { return ERROR_PRED_WEIGHT_TABLE_T; } uc_chroma_log2_weight_denom = u4_temp; COPYTHECONTEXT("SH: chroma_log2_weight_denom",uc_chroma_log2_weight_denom); ui32_cr_def_weight_ofst = (1 << uc_chroma_log2_weight_denom); ps_cur_slice->u2_log2Y_crwd = uc_luma_log2_weight_denom | (uc_chroma_log2_weight_denom << 8); cont = (ps_cur_slice->u1_slice_type == B_SLICE); lx = 0; do { for(i = 0; i < ps_cur_slice->u1_num_ref_idx_lx_active[lx]; i++) { pui32_weight_offset_lx = ps_cur_slice->u4_wt_ofst_lx[lx][i]; uc_weight_flag = ih264d_get_bit_h264(ps_bitstrm); pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; COPYTHECONTEXT("SH: luma_weight_l0_flag",uc_weight_flag); if(uc_weight_flag) { i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) return ERROR_PRED_WEIGHT_TABLE_T; c_weight = i_temp; COPYTHECONTEXT("SH: luma_weight_l0",c_weight); i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) return ERROR_PRED_WEIGHT_TABLE_T; c_offset = i_temp; COPYTHECONTEXT("SH: luma_offset_l0",c_offset); ui32_temp = (c_offset << 16) | (c_weight & 0xFFFF); pui32_weight_offset_lx[0] = ui32_temp; } else { pui32_weight_offset_lx[0] = ui32_y_def_weight_ofst; } { WORD8 c_weightCb, c_weightCr, c_offsetCb, c_offsetCr; uc_weight_flag = ih264d_get_bit_h264(ps_bitstrm); pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; COPYTHECONTEXT("SH: chroma_weight_l0_flag",uc_weight_flag); if(uc_weight_flag) { i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) return ERROR_PRED_WEIGHT_TABLE_T; c_weightCb = i_temp; COPYTHECONTEXT("SH: chroma_weight_l0",c_weightCb); i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) return ERROR_PRED_WEIGHT_TABLE_T; c_offsetCb = i_temp; COPYTHECONTEXT("SH: chroma_weight_l0",c_offsetCb); ui32_temp = (c_offsetCb << 16) | (c_weightCb & 0xFFFF); pui32_weight_offset_lx[1] = ui32_temp; i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) return ERROR_PRED_WEIGHT_TABLE_T; c_weightCr = i_temp; COPYTHECONTEXT("SH: chroma_weight_l0",c_weightCr); i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) return ERROR_PRED_WEIGHT_TABLE_T; c_offsetCr = i_temp; COPYTHECONTEXT("SH: chroma_weight_l0",c_offsetCr); ui32_temp = (c_offsetCr << 16) | (c_weightCr & 0xFFFF); pui32_weight_offset_lx[2] = ui32_temp; } else { pui32_weight_offset_lx[1] = ui32_cr_def_weight_ofst; pui32_weight_offset_lx[2] = ui32_cr_def_weight_ofst; } } } lx++; } while(cont--); return OK; } static int pic_num_compare(const void *pv_pic1, const void *pv_pic2) { struct pic_buffer_t *ps_pic1 = *(struct pic_buffer_t **) pv_pic1; struct pic_buffer_t *ps_pic2 = *(struct pic_buffer_t **) pv_pic2; if (ps_pic1->i4_pic_num < ps_pic2->i4_pic_num) { return -1; } else if (ps_pic1->i4_pic_num > ps_pic2->i4_pic_num) { return 1; } else { return 0; } } /*****************************************************************************/ /* */ /* Function Name : ih264d_init_ref_idx_lx_p */ /* */ /* Description : This function initializes the reference picture L0 list */ /* for P slices as per section 8.2.4.2.1 and 8.2.4.2.2. */ /* */ /* Inputs : pointer to ps_dec struture */ /* Globals : NO */ /* Processing : arranges all the short term pictures according to */ /* pic_num in descending order starting from curr pic_num. */ /* and inserts it in L0 list followed by all Long term */ /* pictures in ascending order. */ /* */ /* Returns : void */ /* */ /* Issues : <List any issues or problems with this function> */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 07 2002 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_init_ref_idx_lx_p(dec_struct_t *ps_dec) { struct pic_buffer_t *ps_ref_pic_buf_lx; dpb_manager_t *ps_dpb_mgr; struct dpb_info_t *ps_next_dpb; WORD8 i, j; UWORD8 u1_max_lt_index, u1_min_lt_index; UWORD32 u4_lt_index; UWORD8 u1_field_pic_flag; dec_slice_params_t *ps_cur_slice; UWORD8 u1_L0; WORD32 i4_cur_pic_num, i4_min_st_pic_num; WORD32 i4_temp_pic_num, i4_ref_pic_num; UWORD8 u1_num_short_term_bufs; UWORD8 u1_max_ref_idx_l0; struct pic_buffer_t *aps_st_pic_bufs[2 * MAX_REF_BUFS] = {NULL}; ps_cur_slice = ps_dec->ps_cur_slice; u1_field_pic_flag = ps_cur_slice->u1_field_pic_flag; u1_max_ref_idx_l0 = ps_cur_slice->u1_num_ref_idx_lx_active[0] << u1_field_pic_flag; ps_dpb_mgr = ps_dec->ps_dpb_mgr; /* Get the current frame number */ i4_cur_pic_num = ps_dec->ps_cur_pic->i4_pic_num; /* Get Min pic_num,MinLt */ i4_min_st_pic_num = i4_cur_pic_num; u1_max_lt_index = MAX_REF_BUFS + 1; u1_min_lt_index = MAX_REF_BUFS + 1; /* Start from ST head */ ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; for(i = 0; i < ps_dpb_mgr->u1_num_st_ref_bufs; i++) { i4_ref_pic_num = ps_next_dpb->ps_pic_buf->i4_pic_num; if(i4_ref_pic_num < i4_cur_pic_num) { /* RefPic Buf pic_num is before Current pic_num in decode order */ i4_min_st_pic_num = MIN(i4_min_st_pic_num, i4_ref_pic_num); } /* Chase the next link */ ps_next_dpb = ps_next_dpb->ps_prev_short; } /* Sort ST ref pocs in ascending order */ ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; for (j = 0; j < ps_dpb_mgr->u1_num_st_ref_bufs; j++) { aps_st_pic_bufs[j] = ps_next_dpb->ps_pic_buf; ps_next_dpb = ps_next_dpb->ps_prev_short; } qsort(aps_st_pic_bufs, ps_dpb_mgr->u1_num_st_ref_bufs, sizeof(aps_st_pic_bufs[0]), pic_num_compare); /* Start from LT head */ ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; if(ps_next_dpb) { u1_max_lt_index = ps_next_dpb->u1_lt_idx; u1_min_lt_index = ps_next_dpb->u1_lt_idx; for(i = 0; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) { u4_lt_index = ps_next_dpb->u1_lt_idx; u1_max_lt_index = (UWORD8)(MAX(u1_max_lt_index, u4_lt_index)); u1_min_lt_index = (UWORD8)(MIN(u1_min_lt_index, u4_lt_index)); /* Chase the next link */ ps_next_dpb = ps_next_dpb->ps_prev_long; } } /* 1. Initialize refIdxL0 */ u1_L0 = 0; if(u1_field_pic_flag) { ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0]; ps_ref_pic_buf_lx += MAX_REF_BUFS; i4_temp_pic_num = i4_cur_pic_num; } else { ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0]; i4_temp_pic_num = i4_cur_pic_num; } /* Arrange all short term buffers in output order as given by pic_num */ /* Arrange pic_num's less than Curr pic_num in the descending pic_num */ /* order starting from (Curr pic_num - 1) */ for(j = ps_dpb_mgr->u1_num_st_ref_bufs - 1; j >= 0; j--) { if(aps_st_pic_bufs[j]) { /* Copy info in pic buffer */ ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, aps_st_pic_bufs[j]); ps_ref_pic_buf_lx++; u1_L0++; } } /* Arrange all Long term buffers in ascending order, in LongtermIndex */ /* Start from LT head */ u1_num_short_term_bufs = u1_L0; for(u4_lt_index = u1_min_lt_index; u4_lt_index <= u1_max_lt_index; u4_lt_index++) { ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; for(i = 0; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) { if(ps_next_dpb->u1_lt_idx == u4_lt_index) { ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, ps_next_dpb->ps_pic_buf); ps_ref_pic_buf_lx->u1_long_term_pic_num = ps_ref_pic_buf_lx->u1_long_term_frm_idx; ps_ref_pic_buf_lx++; u1_L0++; break; } ps_next_dpb = ps_next_dpb->ps_prev_long; } } if(u1_field_pic_flag) { /* Initialize the rest of the entries in the */ /* reference list to handle of errors */ { UWORD8 u1_i; pic_buffer_t ref_pic; ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0] + MAX_REF_BUFS); if(NULL == ref_pic.pu1_buf1) { ref_pic = *ps_dec->ps_cur_pic; } for(u1_i = u1_L0; u1_i < u1_max_ref_idx_l0; u1_i++) { *ps_ref_pic_buf_lx = ref_pic; ps_ref_pic_buf_lx++; } } ih264d_convert_frm_to_fld_list( ps_dpb_mgr->ps_init_dpb[0][0] + MAX_REF_BUFS, &u1_L0, ps_dec, u1_num_short_term_bufs); ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0] + u1_L0; } /* Initialize the rest of the entries in the */ /* reference list to handle of errors */ { UWORD8 u1_i; pic_buffer_t ref_pic; ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0]); if(NULL == ref_pic.pu1_buf1) { ref_pic = *ps_dec->ps_cur_pic; } for(u1_i = u1_L0; u1_i < u1_max_ref_idx_l0; u1_i++) { *ps_ref_pic_buf_lx = ref_pic; ps_ref_pic_buf_lx++; } } ps_dec->ps_cur_slice->u1_initial_list_size[0] = u1_L0; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_process_pslice.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_PROCESS_PSLICE_H_ #define _IH264D_PROCESS_PSLICE_H_ /*! ************************************************************************** * \file ih264d_process_pslice.h * * \brief * Contains declarations of routines that decode a P slice type * * Detailed_description * * \date * 21/12/2002 * * \author NS ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" WORD32 ih264d_parse_pslice(dec_struct_t *ps_dec, UWORD16 u2_first_mb_in_slice); WORD32 ih264d_parse_pred_weight_table(dec_slice_params_t * ps_cur_slice, dec_bit_stream_t * ps_bitstrm); WORD32 parsePSliceData(dec_struct_t * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice); WORD32 ih264d_process_inter_mb(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num); void ih264d_init_ref_idx_lx_p(dec_struct_t *ps_dec); WORD32 ih264d_mv_pred_ref_tfr_nby2_pmb(dec_struct_t * ps_dec, UWORD8 u1_num_mbs, UWORD8 u1_num_mbsNby2); WORD32 ih264d_decode_recon_tfr_nmb(dec_struct_t * ps_dec, UWORD8 u1_mb_idx, UWORD8 u1_num_mbs, UWORD8 u1_num_mbs_next, UWORD8 u1_tfr_n_mb, UWORD8 u1_end_of_row); void ih264d_insert_pic_in_ref_pic_listx(struct pic_buffer_t *ps_ref_pic_buf_lx, struct pic_buffer_t *ps_pic); #endif /* _IH264D_PROCESS_PSLICE_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_quant_scaling.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264_defs.h" #include "ih264d_bitstrm.h" #include "ih264d_structs.h" #include "ih264d_parse_cavlc.h" #include "ih264d_defs.h" #include "ih264d_defs.h" #include "ih264d_defs.h" #include "ih264d_parse_slice.h" #include "ih264d_tables.h" #include "ih264d_utils.h" #include "ih264d_nal.h" #include "ih264d_deblocking.h" #include "ih264d_mem_request.h" #include "ih264d_debug.h" #include "ih264d_error_handler.h" #include "ih264d_mb_utils.h" #include "ih264d_sei.h" #include "ih264d_vui.h" #include "ih264d_tables.h" #define IDCT_BLOCK_WIDTH8X8 8 WORD32 ih264d_scaling_list(WORD16 *pi2_scaling_list, WORD32 i4_size_of_scalinglist, UWORD8 *pu1_use_default_scaling_matrix_flag, dec_bit_stream_t *ps_bitstrm) { WORD32 i4_j, i4_delta_scale, i4_lastScale = 8, i4_nextScale = 8; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; *pu1_use_default_scaling_matrix_flag = 0; for(i4_j = 0; i4_j < i4_size_of_scalinglist; i4_j++) { if(i4_nextScale != 0) { i4_delta_scale = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(i4_delta_scale < MIN_H264_DELTA_SCALE || i4_delta_scale > MAX_H264_DELTA_SCALE) { return ERROR_INV_RANGE_QP_T; } i4_nextScale = ((i4_lastScale + i4_delta_scale + 256) & 0xff); *pu1_use_default_scaling_matrix_flag = ((i4_j == 0) && (i4_nextScale == 0)); } pi2_scaling_list[i4_j] = (i4_nextScale == 0) ? (i4_lastScale) : (i4_nextScale); i4_lastScale = pi2_scaling_list[i4_j]; } return OK; } WORD32 ih264d_form_default_scaling_matrix(dec_struct_t *ps_dec) { /*************************************************************************/ /* perform the inverse scanning for the frame and field scaling matrices */ /*************************************************************************/ { UWORD8 *pu1_inv_scan; WORD32 i4_i, i4_j; pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; /* for all 4x4 matrices */ for(i4_i = 0; i4_i < 6; i4_i++) { for(i4_j = 0; i4_j < 16; i4_j++) { ps_dec->s_high_profile.i2_scalinglist4x4[i4_i][pu1_inv_scan[i4_j]] = 16; } } /* for all 8x8 matrices */ for(i4_i = 0; i4_i < 2; i4_i++) { for(i4_j = 0; i4_j < 64; i4_j++) { ps_dec->s_high_profile.i2_scalinglist8x8[i4_i][gau1_ih264d_inv_scan_prog8x8_cabac[i4_j]] = 16; } } } return OK; } WORD32 ih264d_form_scaling_matrix_picture(dec_seq_params_t *ps_seq, dec_pic_params_t *ps_pic, dec_struct_t *ps_dec) { /* default scaling matrices */ WORD32 i4_i; /* check the SPS first */ if(ps_seq->i4_seq_scaling_matrix_present_flag) { for(i4_i = 0; i4_i < 8; i4_i++) { if(i4_i < 6) { /* fall-back rule A */ if(!ps_seq->u1_seq_scaling_list_present_flag[i4_i]) { if((i4_i == 0) || (i4_i == 3)) { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = (i4_i == 0) ? (WORD16 *)(gai2_ih264d_default_intra4x4) : (WORD16 *)(gai2_ih264d_default_inter4x4); } else { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = ps_dec->s_high_profile.pi2_scale_mat[i4_i - 1]; } } else { if(ps_seq->u1_use_default_scaling_matrix_flag[i4_i]) { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = (i4_i < 3) ? (WORD16 *)(gai2_ih264d_default_intra4x4) : (WORD16 *)(gai2_ih264d_default_inter4x4); } else { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = ps_seq->i2_scalinglist4x4[i4_i]; } } } else { /* fall-back rule A */ if((!ps_seq->u1_seq_scaling_list_present_flag[i4_i]) || (ps_seq->u1_use_default_scaling_matrix_flag[i4_i])) { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = (i4_i == 6) ? ((WORD16*)gai2_ih264d_default_intra8x8) : ((WORD16*)gai2_ih264d_default_inter8x8); } else { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = ps_seq->i2_scalinglist8x8[i4_i - 6]; } } } } /* checking for the PPS */ if(ps_pic->i4_pic_scaling_matrix_present_flag) { for(i4_i = 0; i4_i < 8; i4_i++) { if(i4_i < 6) { /* fall back rule B */ if(!ps_pic->u1_pic_scaling_list_present_flag[i4_i]) { if((i4_i == 0) || (i4_i == 3)) { if(!ps_seq->i4_seq_scaling_matrix_present_flag) { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = (i4_i == 0) ? (WORD16 *)(gai2_ih264d_default_intra4x4) : (WORD16 *)(gai2_ih264d_default_inter4x4); } } else { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = ps_dec->s_high_profile.pi2_scale_mat[i4_i - 1]; } } else { if(ps_pic->u1_pic_use_default_scaling_matrix_flag[i4_i]) { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = (i4_i < 3) ? (WORD16 *)(gai2_ih264d_default_intra4x4) : (WORD16 *)(gai2_ih264d_default_inter4x4); } else { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = ps_pic->i2_pic_scalinglist4x4[i4_i]; } } } else { if(!ps_pic->u1_pic_scaling_list_present_flag[i4_i]) { if(!ps_seq->i4_seq_scaling_matrix_present_flag) { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = (i4_i == 6) ? ((WORD16*)gai2_ih264d_default_intra8x8) : ((WORD16*)gai2_ih264d_default_inter8x8); } } else { if(ps_pic->u1_pic_use_default_scaling_matrix_flag[i4_i]) { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = (i4_i == 6) ? (WORD16 *)(gai2_ih264d_default_intra8x8) : (WORD16 *)(gai2_ih264d_default_inter8x8); } else { ps_dec->s_high_profile.pi2_scale_mat[i4_i] = ps_pic->i2_pic_scalinglist8x8[i4_i - 6]; } } } } } /*************************************************************************/ /* perform the inverse scanning for the frame and field scaling matrices */ /*************************************************************************/ { UWORD8 *pu1_inv_scan_4x4; WORD32 i4_i, i4_j; pu1_inv_scan_4x4 = (UWORD8 *)gau1_ih264d_inv_scan; /* for all 4x4 matrices */ for(i4_i = 0; i4_i < 6; i4_i++) { if(ps_dec->s_high_profile.pi2_scale_mat[i4_i] == NULL) return ERROR_CORRUPTED_SLICE; for(i4_j = 0; i4_j < 16; i4_j++) { ps_dec->s_high_profile.i2_scalinglist4x4[i4_i][pu1_inv_scan_4x4[i4_j]] = ps_dec->s_high_profile.pi2_scale_mat[i4_i][i4_j]; } } /* for all 8x8 matrices */ for(i4_i = 0; i4_i < 2; i4_i++) { if(ps_dec->s_high_profile.pi2_scale_mat[i4_i + 6] == NULL) return ERROR_CORRUPTED_SLICE; for(i4_j = 0; i4_j < 64; i4_j++) { ps_dec->s_high_profile.i2_scalinglist8x8[i4_i][gau1_ih264d_inv_scan_prog8x8_cabac[i4_j]] = ps_dec->s_high_profile.pi2_scale_mat[i4_i + 6][i4_j]; } } } return OK; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_quant_scaling.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_QUANT_SCALING_H_ #define _IH264D_QUANT_SCALING_H_ WORD32 ih264d_scaling_list(WORD16 *pi2_scaling_list, WORD32 i4_size_of_scalinglist, UWORD8 *pu1_use_default_scaling_matrix_flag, dec_bit_stream_t *ps_bitstrm); WORD32 ih264d_form_scaling_matrix_picture(dec_seq_params_t *ps_seq, dec_pic_params_t *ps_pic, dec_struct_t *ps_dec); WORD32 ih264d_form_default_scaling_matrix(dec_struct_t *ps_dec); #endif ================================================ FILE: dependencies/ih264d/decoder/ih264d_sei.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264d_sei.c */ /* */ /* Description : This file contains routines to parse SEI NAL's */ /* */ /* List of Functions : <List the functions defined in this file> */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 25 05 2005 NS Draft */ /* */ /*****************************************************************************/ #include <string.h> #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_bitstrm.h" #include "ih264d_structs.h" #include "ih264d_error_handler.h" #include "ih264d_vui.h" #include "ih264d_parse_cavlc.h" #include "ih264d_defs.h" /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_buffering_period */ /* */ /* Description : This function parses SEI message buffering_period */ /* Inputs : ps_buf_prd pointer to struct buf_period_t */ /* ps_bitstrm Bitstream */ /* Globals : None */ /* Processing : Parses SEI payload buffering period. */ /* Outputs : None */ /* Return : 0 for successfull parsing, else error message */ /* */ /* Issues : Not implemented fully */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_buffering_period(buf_period_t *ps_buf_prd, dec_bit_stream_t *ps_bitstrm, dec_struct_t *ps_dec) { UWORD8 u1_seq_parameter_set_id; dec_seq_params_t *ps_seq; UWORD8 u1_nal_hrd_present, u1_vcl_hrd_present; UWORD32 i; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UNUSED(ps_buf_prd); u1_seq_parameter_set_id = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(u1_seq_parameter_set_id >= MAX_NUM_SEQ_PARAMS) return ERROR_INVALID_SEQ_PARAM; ps_seq = &ps_dec->ps_sps[u1_seq_parameter_set_id]; if(TRUE != ps_seq->u1_is_valid) return ERROR_INVALID_SEQ_PARAM; ps_dec->ps_sei->u1_seq_param_set_id = u1_seq_parameter_set_id; ps_dec->ps_cur_sps = ps_seq; if(FALSE == ps_seq->u1_is_valid) return ERROR_INVALID_SEQ_PARAM; if(1 == ps_seq->u1_vui_parameters_present_flag) { u1_nal_hrd_present = ps_seq->s_vui.u1_nal_hrd_params_present; if(u1_nal_hrd_present) { for(i = 0; i < ps_seq->s_vui.s_nal_hrd.u4_cpb_cnt; i++) { ih264d_get_bits_h264( ps_bitstrm, ps_seq->s_vui.s_nal_hrd.u1_initial_cpb_removal_delay); ih264d_get_bits_h264( ps_bitstrm, ps_seq->s_vui.s_nal_hrd.u1_initial_cpb_removal_delay); } } u1_vcl_hrd_present = ps_seq->s_vui.u1_vcl_hrd_params_present; if(u1_vcl_hrd_present) { for(i = 0; i < ps_seq->s_vui.s_vcl_hrd.u4_cpb_cnt; i++) { ih264d_get_bits_h264( ps_bitstrm, ps_seq->s_vui.s_vcl_hrd.u1_initial_cpb_removal_delay); ih264d_get_bits_h264( ps_bitstrm, ps_seq->s_vui.s_vcl_hrd.u1_initial_cpb_removal_delay); } } } return (OK); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_pic_timing */ /* */ /* Description : This function parses SEI message pic_timing */ /* Inputs : ps_bitstrm Bitstream */ /* ps_dec Poniter decoder context */ /* ui4_payload_size pay load i4_size */ /* Globals : None */ /* Processing : Parses SEI payload picture timing */ /* Outputs : None */ /* Return : 0 */ /* */ /* Issues : Not implemented fully */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_pic_timing(dec_bit_stream_t *ps_bitstrm, dec_struct_t *ps_dec, UWORD32 ui4_payload_size) { sei *ps_sei; vui_t *ps_vu4; UWORD8 u1_cpb_dpb_present; UWORD8 u1_pic_struct_present_flag; UWORD32 u4_start_offset, u4_bits_consumed; UWORD8 u1_cpb_removal_delay_length, u1_dpb_output_delay_length; ps_sei = (sei *)ps_dec->ps_sei; ps_vu4 = &ps_dec->ps_cur_sps->s_vui; u1_cpb_dpb_present = ps_vu4->u1_vcl_hrd_params_present + ps_vu4->u1_nal_hrd_params_present; if(ps_vu4->u1_vcl_hrd_params_present) { u1_cpb_removal_delay_length = ps_vu4->s_vcl_hrd.u1_cpb_removal_delay_length; u1_dpb_output_delay_length = ps_vu4->s_vcl_hrd.u1_dpb_output_delay_length; } else if(ps_vu4->u1_nal_hrd_params_present) { u1_cpb_removal_delay_length = ps_vu4->s_nal_hrd.u1_cpb_removal_delay_length; u1_dpb_output_delay_length = ps_vu4->s_nal_hrd.u1_dpb_output_delay_length; } else { u1_cpb_removal_delay_length = 24; u1_dpb_output_delay_length = 24; } u4_start_offset = ps_bitstrm->u4_ofst; if(u1_cpb_dpb_present) { ih264d_get_bits_h264(ps_bitstrm, u1_cpb_removal_delay_length); ih264d_get_bits_h264(ps_bitstrm, u1_dpb_output_delay_length); } u1_pic_struct_present_flag = ps_vu4->u1_pic_struct_present_flag; if(u1_pic_struct_present_flag) { ps_sei->u1_pic_struct = ih264d_get_bits_h264(ps_bitstrm, 4); ps_dec->u1_pic_struct_copy = ps_sei->u1_pic_struct; ps_sei->u1_is_valid = 1; } u4_bits_consumed = ps_bitstrm->u4_ofst - u4_start_offset; if((ui4_payload_size << 3) < u4_bits_consumed) return ERROR_CORRUPTED_SLICE; ih264d_flush_bits_h264(ps_bitstrm, (ui4_payload_size << 3) - u4_bits_consumed); return (OK); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_recovery_point */ /* */ /* Description : This function parses SEI message recovery point */ /* Inputs : ps_bitstrm Bitstream */ /* ps_dec Poniter decoder context */ /* ui4_payload_size pay load i4_size */ /* Globals : None */ /* Processing : Parses SEI payload picture timing */ /* Outputs : None */ /* Return : 0 */ /* */ /* Issues : Not implemented fully */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_recovery_point(dec_bit_stream_t *ps_bitstrm, dec_struct_t *ps_dec, UWORD32 ui4_payload_size) { sei *ps_sei = ps_dec->ps_sei; dec_err_status_t *ps_err = ps_dec->ps_dec_err_status; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UNUSED(ui4_payload_size); ps_sei->u2_recovery_frame_cnt = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_err->u4_frm_sei_sync = ps_err->u4_cur_frm + ps_sei->u2_recovery_frame_cnt; ps_sei->u1_exact_match_flag = ih264d_get_bit_h264(ps_bitstrm); ps_sei->u1_broken_link_flag = ih264d_get_bit_h264(ps_bitstrm); ps_sei->u1_changing_slice_grp_idc = ih264d_get_bits_h264(ps_bitstrm, 2); return (OK); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_mdcv */ /* */ /* Description : This function parses SEI message mdcv */ /* Inputs : ps_bitstrm Bitstream */ /* ps_dec Poniter decoder context */ /* ui4_payload_size pay load i4_size */ /* Globals : None */ /* Processing : */ /* Outputs : None */ /* Return : 0 for successfull parsing, else -1 */ /* */ /* Issues : */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_mdcv(dec_bit_stream_t *ps_bitstrm, dec_struct_t *ps_dec, UWORD32 ui4_payload_size) { sei *ps_sei = ps_dec->ps_sei_parse; dec_err_status_t *ps_err = ps_dec->ps_dec_err_status; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 u4_count; UNUSED(ui4_payload_size); if((ps_dec == NULL) || (ps_sei == NULL)) { return NOT_OK; } ps_sei->u1_sei_mdcv_params_present_flag = 1; /* display primaries x */ for(u4_count = 0; u4_count < NUM_SEI_MDCV_PRIMARIES; u4_count++) { ps_sei->s_sei_mdcv_params.au2_display_primaries_x[u4_count] = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); if((ps_sei->s_sei_mdcv_params.au2_display_primaries_x[u4_count] > DISPLAY_PRIMARIES_X_UPPER_LIMIT) || (ps_sei->s_sei_mdcv_params.au2_display_primaries_x[u4_count] < DISPLAY_PRIMARIES_X_LOWER_LIMIT) || ((ps_sei->s_sei_mdcv_params.au2_display_primaries_x[u4_count] % DISPLAY_PRIMARIES_X_DIVISION_FACTOR) != 0)) { ps_sei->u1_sei_mdcv_params_present_flag = 0; return ERROR_INV_SEI_MDCV_PARAMS; } ps_sei->s_sei_mdcv_params.au2_display_primaries_y[u4_count] = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); if((ps_sei->s_sei_mdcv_params.au2_display_primaries_y[u4_count] > DISPLAY_PRIMARIES_Y_UPPER_LIMIT) || (ps_sei->s_sei_mdcv_params.au2_display_primaries_y[u4_count] < DISPLAY_PRIMARIES_Y_LOWER_LIMIT) || ((ps_sei->s_sei_mdcv_params.au2_display_primaries_y[u4_count] % DISPLAY_PRIMARIES_Y_DIVISION_FACTOR) != 0)) { ps_sei->u1_sei_mdcv_params_present_flag = 0; return ERROR_INV_SEI_MDCV_PARAMS; } } /* white point x */ ps_sei->s_sei_mdcv_params.u2_white_point_x = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); if((ps_sei->s_sei_mdcv_params.u2_white_point_x > WHITE_POINT_X_UPPER_LIMIT) || (ps_sei->s_sei_mdcv_params.u2_white_point_x < WHITE_POINT_X_LOWER_LIMIT) || ((ps_sei->s_sei_mdcv_params.u2_white_point_x % WHITE_POINT_X_DIVISION_FACTOR) != 0)) { ps_sei->u1_sei_mdcv_params_present_flag = 0; return ERROR_INV_SEI_MDCV_PARAMS; } /* white point y */ ps_sei->s_sei_mdcv_params.u2_white_point_y = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); if((ps_sei->s_sei_mdcv_params.u2_white_point_y > WHITE_POINT_Y_UPPER_LIMIT) || (ps_sei->s_sei_mdcv_params.u2_white_point_y < WHITE_POINT_Y_LOWER_LIMIT) || ((ps_sei->s_sei_mdcv_params.u2_white_point_y % WHITE_POINT_Y_DIVISION_FACTOR) != 0)) { ps_sei->u1_sei_mdcv_params_present_flag = 0; return ERROR_INV_SEI_MDCV_PARAMS; } /* max display mastering luminance */ ps_sei->s_sei_mdcv_params.u4_max_display_mastering_luminance = (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); if((ps_sei->s_sei_mdcv_params.u4_max_display_mastering_luminance > MAX_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT) || (ps_sei->s_sei_mdcv_params.u4_max_display_mastering_luminance < MAX_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT) || ((ps_sei->s_sei_mdcv_params.u4_max_display_mastering_luminance % MAX_DISPLAY_MASTERING_LUMINANCE_DIVISION_FACTOR) != 0)) { ps_sei->u1_sei_mdcv_params_present_flag = 0; return ERROR_INV_SEI_MDCV_PARAMS; } /* min display mastering luminance */ ps_sei->s_sei_mdcv_params.u4_min_display_mastering_luminance = (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); if((ps_sei->s_sei_mdcv_params.u4_min_display_mastering_luminance > MIN_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT) || (ps_sei->s_sei_mdcv_params.u4_min_display_mastering_luminance < MIN_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT)) { ps_sei->u1_sei_mdcv_params_present_flag = 0; return ERROR_INV_SEI_MDCV_PARAMS; } if(ps_sei->s_sei_mdcv_params.u4_max_display_mastering_luminance <= ps_sei->s_sei_mdcv_params.u4_min_display_mastering_luminance) { ps_sei->u1_sei_mdcv_params_present_flag = 0; return ERROR_INV_SEI_MDCV_PARAMS; } return (OK); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_cll */ /* */ /* Description : This function parses SEI message cll */ /* Inputs : ps_bitstrm Bitstream */ /* ps_dec Poniter decoder context */ /* ui4_payload_size pay load i4_size */ /* Globals : None */ /* Processing : */ /* Outputs : None */ /* Return : 0 for successfull parsing, else -1 */ /* */ /* Issues : */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_cll(dec_bit_stream_t *ps_bitstrm, dec_struct_t *ps_dec, UWORD32 ui4_payload_size) { sei *ps_sei = ps_dec->ps_sei_parse; dec_err_status_t *ps_err = ps_dec->ps_dec_err_status; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UNUSED(ui4_payload_size); if((ps_dec == NULL) || (ps_sei == NULL)) { return NOT_OK; } ps_sei->u1_sei_cll_params_present_flag = 1; ps_sei->s_sei_cll_params.u2_max_content_light_level = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); ps_sei->s_sei_cll_params.u2_max_pic_average_light_level = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); /*No any sanity checks done for CLL params*/ return (OK); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_ave */ /* */ /* Description : This function parses SEI message ave */ /* Inputs : ps_bitstrm Bitstream */ /* ps_dec Poniter decoder context */ /* ui4_payload_size pay load i4_size */ /* Globals : None */ /* Processing : */ /* Outputs : None */ /* Return : 0 for successfull parsing, else -1 */ /* */ /* Issues : */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_ave(dec_bit_stream_t *ps_bitstrm, dec_struct_t *ps_dec, UWORD32 ui4_payload_size) { sei *ps_sei = ps_dec->ps_sei_parse; dec_err_status_t *ps_err = ps_dec->ps_dec_err_status; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UNUSED(ui4_payload_size); if((ps_dec == NULL) || (ps_sei == NULL)) { return NOT_OK; } ps_sei->u1_sei_ave_params_present_flag = 1; ps_sei->s_sei_ave_params.u4_ambient_illuminance = (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); if(0 == ps_sei->s_sei_ave_params.u4_ambient_illuminance) { ps_sei->u1_sei_ave_params_present_flag = 0; return ERROR_INV_SEI_AVE_PARAMS; } ps_sei->s_sei_ave_params.u2_ambient_light_x = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); if(ps_sei->s_sei_ave_params.u2_ambient_light_x > AMBIENT_LIGHT_X_UPPER_LIMIT) { ps_sei->u1_sei_ave_params_present_flag = 0; return ERROR_INV_SEI_AVE_PARAMS; } ps_sei->s_sei_ave_params.u2_ambient_light_y = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); if(ps_sei->s_sei_ave_params.u2_ambient_light_y > AMBIENT_LIGHT_Y_UPPER_LIMIT) { ps_sei->u1_sei_ave_params_present_flag = 0; return ERROR_INV_SEI_AVE_PARAMS; } return (OK); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_ccv */ /* */ /* Description : This function parses SEI message ccv */ /* Inputs : ps_bitstrm Bitstream */ /* ps_dec Poniter decoder context */ /* ui4_payload_size pay load i4_size */ /* Globals : None */ /* Processing : */ /* Outputs : None */ /* Return : 0 for successfull parsing, else -1 */ /* */ /* Issues : */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_ccv(dec_bit_stream_t *ps_bitstrm, dec_struct_t *ps_dec, UWORD32 ui4_payload_size) { sei *ps_sei = ps_dec->ps_sei_parse; dec_err_status_t *ps_err = ps_dec->ps_dec_err_status; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; UWORD32 u4_count; UNUSED(ui4_payload_size); if((ps_dec == NULL) || (ps_sei == NULL)) { return NOT_OK; } ps_sei->u1_sei_ccv_params_present_flag = 0; ps_sei->s_sei_ccv_params.u1_ccv_cancel_flag = (UWORD8)ih264d_get_bit_h264(ps_bitstrm); if(ps_sei->s_sei_ccv_params.u1_ccv_cancel_flag > 1) { return ERROR_INV_SEI_CCV_PARAMS; } if(0 == ps_sei->s_sei_ccv_params.u1_ccv_cancel_flag) { ps_sei->s_sei_ccv_params.u1_ccv_persistence_flag = (UWORD8)ih264d_get_bit_h264(ps_bitstrm); if(ps_sei->s_sei_ccv_params.u1_ccv_persistence_flag > 1) { return ERROR_INV_SEI_CCV_PARAMS; } ps_sei->s_sei_ccv_params.u1_ccv_primaries_present_flag = (UWORD8)ih264d_get_bit_h264(ps_bitstrm); if(ps_sei->s_sei_ccv_params.u1_ccv_primaries_present_flag > 1) { return ERROR_INV_SEI_CCV_PARAMS; } ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag = (UWORD8)ih264d_get_bit_h264(ps_bitstrm); if(ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag > 1) { return ERROR_INV_SEI_CCV_PARAMS; } ps_sei->s_sei_ccv_params.u1_ccv_max_luminance_value_present_flag = (UWORD8)ih264d_get_bit_h264(ps_bitstrm); if(ps_sei->s_sei_ccv_params.u1_ccv_max_luminance_value_present_flag > 1) { return ERROR_INV_SEI_CCV_PARAMS; } ps_sei->s_sei_ccv_params.u1_ccv_avg_luminance_value_present_flag = (UWORD8)ih264d_get_bit_h264(ps_bitstrm); if(ps_sei->s_sei_ccv_params.u1_ccv_avg_luminance_value_present_flag > 1) { return ERROR_INV_SEI_CCV_PARAMS; } if((ps_sei->s_sei_ccv_params.u1_ccv_primaries_present_flag == 0) && (ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag == 0) && (ps_sei->s_sei_ccv_params.u1_ccv_max_luminance_value_present_flag == 0) && (ps_sei->s_sei_ccv_params.u1_ccv_avg_luminance_value_present_flag == 0)) { return ERROR_INV_SEI_CCV_PARAMS; } ps_sei->s_sei_ccv_params.u1_ccv_reserved_zero_2bits = (UWORD8)ih264d_get_bits_h264(ps_bitstrm, 2); if((ps_sei->s_sei_ccv_params.u1_ccv_reserved_zero_2bits != 0)) { return ERROR_INV_SEI_CCV_PARAMS; } /* ccv primaries */ if(1 == ps_sei->s_sei_ccv_params.u1_ccv_primaries_present_flag) { for(u4_count = 0; u4_count < NUM_SEI_CCV_PRIMARIES; u4_count++) { ps_sei->s_sei_ccv_params.ai4_ccv_primaries_x[u4_count] = (WORD32)ih264d_get_bits_h264(ps_bitstrm, 32); if((ps_sei->s_sei_ccv_params.ai4_ccv_primaries_x[u4_count] > CCV_PRIMARIES_X_UPPER_LIMIT) || (ps_sei->s_sei_ccv_params.ai4_ccv_primaries_x[u4_count] < CCV_PRIMARIES_X_LOWER_LIMIT)) { return ERROR_INV_SEI_CCV_PARAMS; } ps_sei->s_sei_ccv_params.ai4_ccv_primaries_y[u4_count] = (WORD32)ih264d_get_bits_h264(ps_bitstrm, 32); if((ps_sei->s_sei_ccv_params.ai4_ccv_primaries_y[u4_count] > CCV_PRIMARIES_Y_UPPER_LIMIT) || (ps_sei->s_sei_ccv_params.ai4_ccv_primaries_y[u4_count] < CCV_PRIMARIES_Y_LOWER_LIMIT)) { return ERROR_INV_SEI_CCV_PARAMS; } } } if(1 == ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag) { ps_sei->s_sei_ccv_params.u4_ccv_min_luminance_value = (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); } if(1 == ps_sei->s_sei_ccv_params.u1_ccv_max_luminance_value_present_flag) { ps_sei->s_sei_ccv_params.u4_ccv_max_luminance_value = (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); if((1 == ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag) && (ps_sei->s_sei_ccv_params.u4_ccv_max_luminance_value < ps_sei->s_sei_ccv_params.u4_ccv_min_luminance_value)) { return ERROR_INV_SEI_CCV_PARAMS; } } if(1 == ps_sei->s_sei_ccv_params.u1_ccv_avg_luminance_value_present_flag) { ps_sei->s_sei_ccv_params.u4_ccv_avg_luminance_value = (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); if((1 == ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag) && (ps_sei->s_sei_ccv_params.u4_ccv_avg_luminance_value < ps_sei->s_sei_ccv_params.u4_ccv_min_luminance_value)) { return ERROR_INV_SEI_CCV_PARAMS; } if((1 == ps_sei->s_sei_ccv_params.u1_ccv_max_luminance_value_present_flag) && (ps_sei->s_sei_ccv_params.u4_ccv_max_luminance_value < ps_sei->s_sei_ccv_params.u4_ccv_avg_luminance_value)) { return ERROR_INV_SEI_CCV_PARAMS; } } } ps_sei->u1_sei_ccv_params_present_flag = 1; return (OK); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_sei_payload */ /* */ /* Description : This function parses SEI pay loads. Currently it's */ /* implemented partially. */ /* Inputs : ps_bitstrm Bitstream */ /* ui4_payload_type SEI payload type */ /* ui4_payload_size SEI payload i4_size */ /* Globals : None */ /* Processing : Parses SEI payloads units and stores the info */ /* Outputs : None */ /* Return : status for successful parsing, else -1 */ /* */ /* Issues : Not implemented fully */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_sei_payload(dec_bit_stream_t *ps_bitstrm, UWORD32 ui4_payload_type, UWORD32 ui4_payload_size, dec_struct_t *ps_dec) { sei *ps_sei; WORD32 i4_status = 0; ps_sei = (sei *)ps_dec->ps_sei_parse; if(ui4_payload_size == 0) return -1; if(NULL == ps_bitstrm) { return NOT_OK; } switch(ui4_payload_type) { case SEI_BUF_PERIOD: i4_status = ih264d_parse_buffering_period(&ps_sei->s_buf_period, ps_bitstrm, ps_dec); break; case SEI_PIC_TIMING: if(NULL == ps_dec->ps_cur_sps) i4_status = ih264d_flush_bits_h264(ps_bitstrm, (ui4_payload_size << 3)); else i4_status = ih264d_parse_pic_timing(ps_bitstrm, ps_dec, ui4_payload_size); break; case SEI_RECOVERY_PT: i4_status = ih264d_parse_recovery_point(ps_bitstrm, ps_dec, ui4_payload_size); break; case SEI_MASTERING_DISP_COL_VOL: i4_status = ih264d_parse_mdcv(ps_bitstrm, ps_dec, ui4_payload_size); break; case SEI_CONTENT_LIGHT_LEVEL_DATA: i4_status = ih264d_parse_cll(ps_bitstrm, ps_dec, ui4_payload_size); break; case SEI_AMBIENT_VIEWING_ENVIRONMENT: i4_status = ih264d_parse_ave(ps_bitstrm, ps_dec, ui4_payload_size); break; case SEI_CONTENT_COLOR_VOLUME: i4_status = ih264d_parse_ccv(ps_bitstrm, ps_dec, ui4_payload_size); break; default: i4_status = ih264d_flush_bits_h264(ps_bitstrm, (ui4_payload_size << 3)); break; } return (i4_status); } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_sei_message */ /* */ /* Description : This function is parses and decode SEI. Currently it's */ /* not implemented fully. */ /* Inputs : ps_dec Decoder parameters */ /* ps_bitstrm Bitstream */ /* Globals : None */ /* Processing : Parses SEI NAL units and stores the info */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : Not implemented fully */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_sei_message(dec_struct_t *ps_dec, dec_bit_stream_t *ps_bitstrm) { UWORD32 ui4_payload_type, ui4_payload_size; UWORD32 u4_bits; WORD32 i4_status = 0; do { ui4_payload_type = 0; if(!CHECK_BITS_SUFFICIENT(ps_bitstrm, 8)) { return ERROR_EOB_GETBITS_T; } u4_bits = ih264d_get_bits_h264(ps_bitstrm, 8); while(0xff == u4_bits && CHECK_BITS_SUFFICIENT(ps_bitstrm, 8)) { u4_bits = ih264d_get_bits_h264(ps_bitstrm, 8); ui4_payload_type += 255; } ui4_payload_type += u4_bits; ui4_payload_size = 0; if(!CHECK_BITS_SUFFICIENT(ps_bitstrm, 8)) { return ERROR_EOB_GETBITS_T; } u4_bits = ih264d_get_bits_h264(ps_bitstrm, 8); while(0xff == u4_bits && CHECK_BITS_SUFFICIENT(ps_bitstrm, 8)) { u4_bits = ih264d_get_bits_h264(ps_bitstrm, 8); ui4_payload_size += 255; } ui4_payload_size += u4_bits; if(!CHECK_BITS_SUFFICIENT(ps_bitstrm, (ui4_payload_size << 3))) { return ERROR_EOB_GETBITS_T; } i4_status = ih264d_parse_sei_payload(ps_bitstrm, ui4_payload_type, ui4_payload_size, ps_dec); if(i4_status != OK) return i4_status; if(ih264d_check_byte_aligned(ps_bitstrm) == 0) { u4_bits = ih264d_get_bit_h264(ps_bitstrm); if(0 == u4_bits) { H264_DEC_DEBUG_PRINT("\nError in parsing SEI message"); } while(0 == ih264d_check_byte_aligned(ps_bitstrm) && CHECK_BITS_SUFFICIENT(ps_bitstrm, 1)) { u4_bits = ih264d_get_bit_h264(ps_bitstrm); if(u4_bits) { H264_DEC_DEBUG_PRINT("\nError in parsing SEI message"); } } } } while(MORE_RBSP_DATA(ps_bitstrm)); return (i4_status); } /*****************************************************************************/ /* */ /* Function Name : ih264d_export_sei_mdcv_params */ /* */ /* Description : This function populates SEI mdcv message in */ /* output structure */ /* Inputs : ps_sei_mdcv_op pointer to sei mdcv o\p struct */ /* : ps_sei pointer to decoded sei params */ /* Outputs : */ /* Returns : returns 0 for success; -1 for failure */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* */ /* */ /*****************************************************************************/ WORD32 ih264d_export_sei_mdcv_params(ivd_sei_decode_op_t *ps_sei_decode_op, sei *ps_sei, sei *ps_sei_export) { if((ps_sei_export == NULL) || (ps_sei == NULL)) { return NOT_OK; } ps_sei_export->u1_sei_mdcv_params_present_flag = ps_sei->u1_sei_mdcv_params_present_flag; ps_sei_decode_op->u1_sei_mdcv_params_present_flag = ps_sei->u1_sei_mdcv_params_present_flag; if(0 == ps_sei_export->u1_sei_mdcv_params_present_flag) { memset(&ps_sei_export->s_sei_mdcv_params, 0, sizeof(sei_mdcv_params_t)); } else { memcpy(&ps_sei_export->s_sei_mdcv_params, &ps_sei->s_sei_mdcv_params, sizeof(sei_mdcv_params_t)); } return (OK); } /*****************************************************************************/ /* */ /* Function Name : ih264d_export_sei_cll_params */ /* */ /* Description : This function populates SEI cll message in */ /* output structure */ /* Inputs : ps_sei_cll_op pointer to sei cll o\p struct */ /* : ps_sei pointer to decoded sei params */ /* Outputs : */ /* Returns : returns 0 for success; -1 for failure */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* */ /* */ /*****************************************************************************/ WORD32 ih264d_export_sei_cll_params(ivd_sei_decode_op_t *ps_sei_decode_op, sei *ps_sei, sei *ps_sei_export) { if((ps_sei_export == NULL) || (ps_sei == NULL)) { return NOT_OK; } ps_sei_export->u1_sei_cll_params_present_flag = ps_sei->u1_sei_cll_params_present_flag; ps_sei_decode_op->u1_sei_cll_params_present_flag = ps_sei->u1_sei_cll_params_present_flag; if(0 == ps_sei_export->u1_sei_cll_params_present_flag) { memset(&ps_sei_export->s_sei_cll_params, 0, sizeof(sei_cll_params_t)); } else { memcpy(&ps_sei_export->s_sei_cll_params, &ps_sei->s_sei_cll_params, sizeof(sei_cll_params_t)); } return (OK); } /*****************************************************************************/ /* */ /* Function Name : ih264d_export_sei_ave_params */ /* */ /* Description : This function populates SEI ave message in */ /* output structure */ /* Inputs : ps_sei_ave_op pointer to sei ave o\p struct */ /* : ps_sei pointer to decoded sei params */ /* Outputs : */ /* Returns : returns 0 for success; -1 for failure */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* */ /* */ /*****************************************************************************/ WORD32 ih264d_export_sei_ave_params(ivd_sei_decode_op_t *ps_sei_decode_op, sei *ps_sei, sei *ps_sei_export) { if((ps_sei_export == NULL) || (ps_sei == NULL)) { return NOT_OK; } ps_sei_export->u1_sei_ave_params_present_flag = ps_sei->u1_sei_ave_params_present_flag; ps_sei_decode_op->u1_sei_ave_params_present_flag = ps_sei->u1_sei_ave_params_present_flag; if(0 == ps_sei_export->u1_sei_ave_params_present_flag) { memset(&ps_sei_export->s_sei_ave_params, 0, sizeof(sei_ave_params_t)); } else { memcpy(&ps_sei_export->s_sei_ave_params, &ps_sei->s_sei_ave_params, sizeof(sei_ave_params_t)); } return (OK); } /*****************************************************************************/ /* */ /* Function Name : ih264d_export_sei_ccv_params */ /* */ /* Description : This function populates SEI ccv message in */ /* output structure */ /* Inputs : ps_sei_ccv_op pointer to sei ccv o\p struct */ /* : ps_sei pointer to decoded sei params */ /* Outputs : */ /* Returns : returns 0 for success; -1 for failure */ /* */ /* Issues : none */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* */ /* */ /*****************************************************************************/ WORD32 ih264d_export_sei_ccv_params(ivd_sei_decode_op_t *ps_sei_decode_op, sei *ps_sei, sei *ps_sei_export) { if((ps_sei_export == NULL) || (ps_sei == NULL)) { return NOT_OK; } ps_sei_export->u1_sei_ccv_params_present_flag = ps_sei->u1_sei_ccv_params_present_flag; ps_sei_decode_op->u1_sei_ccv_params_present_flag = ps_sei->u1_sei_ccv_params_present_flag; if(0 == ps_sei_export->u1_sei_ccv_params_present_flag) { memset(&ps_sei_export->s_sei_ccv_params, 0, sizeof(sei_ccv_params_t)); } else { memcpy(&ps_sei_export->s_sei_ccv_params, &ps_sei->s_sei_ccv_params, sizeof(sei_ccv_params_t)); } return (OK); } ================================================ FILE: dependencies/ih264d/decoder/ih264d_sei.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264d_sei.h */ /* */ /* Description : This file contains routines to parse SEI NAL's */ /* */ /* List of Functions : <List the functions defined in this file> */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 25 05 2005 NS Draft */ /* */ /*****************************************************************************/ #ifndef _IH264D_SEI_H_ #define _IH264D_SEI_H_ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_bitstrm.h" #include "ih264d_structs.h" #include "ih264d.h" #define SEI_BUF_PERIOD 0 #define SEI_PIC_TIMING 1 #define SEI_PAN_SCAN_RECT 2 #define SEI_FILLER 3 #define SEI_UD_REG_T35 4 #define SEI_UD_UN_REG 5 #define SEI_RECOVERY_PT 6 #define SEI_DEC_REF_MARK 7 #define SEI_SPARE_PIC 8 #define SEI_SCENE_INFO 9 #define SEI_SUB_SEQN_INFO 10 #define SEI_SUB_SEQN_LAY_CHAR 11 #define SEI_SUB_SEQN_CHAR 12 #define SEI_FULL_FRAME_FREEZE 13 #define SEI_FULL_FRAME_FREEZE_REL 14 #define SEI_FULL_FRAME_SNAP_SHOT 15 #define SEI_PROG_REF_SEGMENT_START 16 #define SEI_PROG_REF_SEGMENT_END 17 #define SEI_MOT_CON_SLICE_GRP_SET 18 #define SEI_MASTERING_DISP_COL_VOL 137 #define SEI_CONTENT_LIGHT_LEVEL_DATA 144 #define SEI_AMBIENT_VIEWING_ENVIRONMENT 148 #define SEI_CONTENT_COLOR_VOLUME 149 /* Declaration of dec_struct_t to avoid CCS compilation Error */ struct _DecStruct; WORD32 ih264d_parse_sei_message(struct _DecStruct *ps_dec, dec_bit_stream_t *ps_bitstrm); typedef struct { UWORD8 u1_seq_parameter_set_id; UWORD32 u4_initial_cpb_removal_delay; UWORD32 u4_nitial_cpb_removal_delay_offset; } buf_period_t; /** * Structure to hold Mastering Display Color Volume SEI */ typedef struct { /** * Array to store the display_primaries_x values */ UWORD16 au2_display_primaries_x[NUM_SEI_MDCV_PRIMARIES]; /** * Array to store the display_primaries_y values */ UWORD16 au2_display_primaries_y[NUM_SEI_MDCV_PRIMARIES]; /** * Variable to store the white point x value */ UWORD16 u2_white_point_x; /** * Variable to store the white point y value */ UWORD16 u2_white_point_y; /** * Variable to store the max display mastering luminance value */ UWORD32 u4_max_display_mastering_luminance; /** * Variable to store the min display mastering luminance value */ UWORD32 u4_min_display_mastering_luminance; }sei_mdcv_params_t; /** * Structure for Content Light Level Info * */ typedef struct { /** * The maximum pixel intensity of all samples */ UWORD16 u2_max_content_light_level; /** * The average pixel intensity of all samples */ UWORD16 u2_max_pic_average_light_level; }sei_cll_params_t; /** * Structure to hold Ambient viewing environment SEI */ typedef struct { /** * specifies the environmental illuminance of the ambient viewing environment */ UWORD32 u4_ambient_illuminance; /* * specify the normalized x chromaticity coordinates of the * environmental ambient light in the nominal viewing environment */ UWORD16 u2_ambient_light_x; /* * specify the normalized y chromaticity coordinates of the * environmental ambient light in the nominal viewing environment */ UWORD16 u2_ambient_light_y; }sei_ave_params_t; /** * Structure to hold Content color volume SEI */ typedef struct { /* * Flag used to control persistence of CCV SEI messages */ UWORD8 u1_ccv_cancel_flag; /* * specifies the persistence of the CCV SEI message for the current layer */ UWORD8 u1_ccv_persistence_flag; /* * specifies the presence of syntax elements ccv_primaries_x and ccv_primaries_y */ UWORD8 u1_ccv_primaries_present_flag; /* * specifies that the syntax element ccv_min_luminance_value is present */ UWORD8 u1_ccv_min_luminance_value_present_flag; /* * specifies that the syntax element ccv_max_luminance_value is present */ UWORD8 u1_ccv_max_luminance_value_present_flag; /* * specifies that the syntax element ccv_avg_luminance_value is present */ UWORD8 u1_ccv_avg_luminance_value_present_flag; /* * shall be equal to 0 in bitstreams conforming to this version. Other values * for reserved_zero_2bits are reserved for future use */ UWORD8 u1_ccv_reserved_zero_2bits; /* * specify the normalized x chromaticity coordinates of the colour * primary component c of the nominal content colour volume */ WORD32 ai4_ccv_primaries_x[NUM_SEI_CCV_PRIMARIES]; /* * specify the normalized y chromaticity coordinates of the colour * primary component c of the nominal content colour volume */ WORD32 ai4_ccv_primaries_y[NUM_SEI_CCV_PRIMARIES]; /* * specifies the normalized minimum luminance value */ UWORD32 u4_ccv_min_luminance_value; /* * specifies the normalized maximum luminance value */ UWORD32 u4_ccv_max_luminance_value; /* * specifies the normalized average luminance value */ UWORD32 u4_ccv_avg_luminance_value; }sei_ccv_params_t; struct _sei { UWORD8 u1_seq_param_set_id; buf_period_t s_buf_period; UWORD8 u1_pic_struct; UWORD16 u2_recovery_frame_cnt; UWORD8 u1_exact_match_flag; UWORD8 u1_broken_link_flag; UWORD8 u1_changing_slice_grp_idc; UWORD8 u1_is_valid; /** * mastering display color volume info present flag */ UWORD8 u1_sei_mdcv_params_present_flag; /* * MDCV parameters */ sei_mdcv_params_t s_sei_mdcv_params; /** * content light level info present flag */ UWORD8 u1_sei_cll_params_present_flag; /* * CLL parameters */ sei_cll_params_t s_sei_cll_params; /** * ambient viewing environment info present flag */ UWORD8 u1_sei_ave_params_present_flag; /* * AVE parameters */ sei_ave_params_t s_sei_ave_params; /** * content color volume info present flag */ UWORD8 u1_sei_ccv_params_present_flag; /* * CCV parameters */ sei_ccv_params_t s_sei_ccv_params; }; typedef struct _sei sei; WORD32 ih264d_export_sei_mdcv_params(ivd_sei_decode_op_t *ps_sei_decode_op, sei *ps_sei, sei *ps_sei_export); WORD32 ih264d_export_sei_cll_params(ivd_sei_decode_op_t *ps_sei_decode_op, sei *ps_sei, sei *ps_sei_export); WORD32 ih264d_export_sei_ave_params(ivd_sei_decode_op_t *ps_sei_decode_op, sei *ps_sei, sei *ps_sei_export); WORD32 ih264d_export_sei_ccv_params(ivd_sei_decode_op_t *ps_sei_decode_op, sei *ps_sei, sei *ps_sei_export); #endif /* _IH264D_SEI_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_structs.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_STRUCTS_H_ #define _IH264D_STRUCTS_H_ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "iv.h" #include "ivd.h" #include "ih264d_transfer_address.h" #include "ih264d_defs.h" #include "ih264d_defs.h" #include "ih264d_bitstrm.h" #include "ih264d_debug.h" #include "ih264d_dpb_manager.h" /* includes for CABAC */ #include "ih264d_cabac.h" #include "ih264d_dpb_manager.h" #include "ih264d_vui.h" #include "ih264d_sei.h" #include "iv.h" #include "ivd.h" #include "ih264_weighted_pred.h" #include "ih264_trans_quant_itrans_iquant.h" #include "ih264_inter_pred_filters.h" #include "ih264_mem_fns.h" #include "ih264_padding.h" #include "ih264_intra_pred_filters.h" #include "ih264_deblk_edge_filters.h" /** Number of Mb's whoose syntax will be read */ /************************************************************/ /* MB_GROUP should be a multiple of 2 */ /************************************************************/ #define PARSE_MB_GROUP_4 4 /* MV_SCRATCH_BUFS assumed to be pow(2) */ #define MV_SCRATCH_BUFS 4 #define TOP_FIELD_ONLY 0x02 #define BOT_FIELD_ONLY 0x01 #define MAX_REF_BUF_SIZE (3776*2*2) struct _DecStruct; struct _DecMbInfo; typedef enum { MB_TYPE_SI_SLICE = 0, MB_TYPE_I_SLICE = 3, MB_SKIP_FLAG_P_SLICE = 11, MB_TYPE_P_SLICE = 14, SUB_MB_TYPE_P_SLICE = 21, MB_SKIP_FLAG_B_SLICE = 24, MB_TYPE_B_SLICE = 27, SUB_MB_TYPE_B_SLICE = 36, MVD_X = 40, MVD_Y = 47, REF_IDX = 54, MB_QP_DELTA = 60, INTRA_CHROMA_PRED_MODE = 64, PREV_INTRA4X4_PRED_MODE_FLAG = 68, REM_INTRA4X4_PRED_MODE = 69, MB_FIELD_DECODING_FLAG = 70, CBP_LUMA = 73, CBP_CHROMA = 77, CBF = 85, SIGNIFICANT_COEFF_FLAG_FRAME = 105, SIGNIFICANT_COEFF_FLAG_FLD = 277, LAST_SIGNIFICANT_COEFF_FLAG_FRAME = 166, LAST_SIGNIFICANT_COEFF_FLAG_FLD = 338, COEFF_ABS_LEVEL_MINUS1 = 227, /* High profile related Syntax element CABAC offsets */ TRANSFORM_SIZE_8X8_FLAG = 399, SIGNIFICANT_COEFF_FLAG_8X8_FRAME = 402, LAST_SIGNIFICANT_COEFF_FLAG_8X8_FRAME = 417, COEFF_ABS_LEVEL_MINUS1_8X8 = 426, SIGNIFICANT_COEFF_FLAG_8X8_FIELD = 436, LAST_SIGNIFICANT_COEFF_FLAG_8X8_FIELD = 451 } cabac_table_num_t; typedef enum { SIG_COEFF_CTXT_CAT_0_OFFSET = 0, SIG_COEFF_CTXT_CAT_1_OFFSET = 15, SIG_COEFF_CTXT_CAT_2_OFFSET = 29, SIG_COEFF_CTXT_CAT_3_OFFSET = 44, SIG_COEFF_CTXT_CAT_4_OFFSET = 47, SIG_COEFF_CTXT_CAT_5_OFFSET = 0, COEFF_ABS_LEVEL_CAT_0_OFFSET = 0, COEFF_ABS_LEVEL_CAT_1_OFFSET = 10, COEFF_ABS_LEVEL_CAT_2_OFFSET = 20, COEFF_ABS_LEVEL_CAT_3_OFFSET = 30, COEFF_ABS_LEVEL_CAT_4_OFFSET = 39, COEFF_ABS_LEVEL_CAT_5_OFFSET = 0 } cabac_blk_cat_offset_t; /** Structure for the MV bank */ typedef struct _mv_pred_t { WORD16 i2_mv[4]; /** 0- mvFwdX, 1- mvFwdY, 2- mvBwdX, 3- mvBwdY */ WORD8 i1_ref_frame[2]; UWORD8 u1_col_ref_pic_idx; /** Idx into the pic buff array */ UWORD8 u1_pic_type; /** Idx into the pic buff array */ } mv_pred_t; typedef struct { WORD32 i4_mv_indices[16]; WORD8 i1_submb_num[16]; WORD8 i1_partitionsize[16]; WORD8 i1_num_partitions; WORD8 u1_vert_mv_scale; UWORD8 u1_col_zeroflag_change; } directmv_t; typedef struct pic_buffer_t { /**Different components of the picture */ UWORD8 *pu1_buf1; UWORD8 *pu1_buf2; UWORD8 *pu1_buf3; UWORD16 u2_disp_width; /** Width of the display luma frame in pixels */ UWORD16 u2_disp_height; /** Height of the display luma frame in pixels */ UWORD32 u4_time_stamp; /** Time at which frame has to be displayed */ UWORD16 u2_frm_wd_y; /** Width of the luma frame in pixels */ UWORD16 u2_frm_wd_uv; /** Width of the chroma frame */ UWORD16 u2_frm_ht_y; /** Height of the luma frame in pixels */ UWORD16 u2_frm_ht_uv; /** Height of the chroma frame */ /* Upto this is resembling the structure IH264DEC_DispUnit */ /* If any member is to be added, add below this */ /* u4_ofst from start of picture buffer to display position for Y buffer */ UWORD16 u2_crop_offset_y; /* u4_ofst from start of picture buffer to display position for UV buffer */ UWORD16 u2_crop_offset_uv; UWORD8 u1_is_short; /** (1: short 0: long) term ref pic */ UWORD8 u1_pic_type; /** frame / field / complementary field pair */ UWORD8 u1_pic_buf_id; /** Idx into the picBufAPI array */ UWORD8 u1_mv_buf_id; WORD32 i4_seq; UWORD8 *pu1_col_zero_flag; mv_pred_t *ps_mv; /** Pointer to the MV bank array */ WORD32 i4_poc; /** POC */ WORD32 i4_pic_num; WORD32 i4_frame_num; WORD32 i4_top_field_order_cnt; /** TopPOC */ WORD32 i4_bottom_field_order_cnt; /** BottomPOC */ WORD32 i4_avg_poc; /** minPOC */ UWORD8 u1_picturetype; /*Same as u1_pic_type..u1_pic_type gets overwritten whereas this doesnot get overwritten ...stores the pictype of frame/complementary field pair/ mbaff */ UWORD8 u1_long_term_frm_idx; UWORD8 u1_long_term_pic_num; UWORD32 u4_pack_slc_typ; /* It will contain information about types of slices */ /* ! */ UWORD32 u4_ts; UWORD8 u1_pic_struct;/* Refer to SEI table D-1 */ sei s_sei_pic; } pic_buffer_t; typedef struct { void *u4_add[4]; } neighbouradd_t; typedef struct { const UWORD8 *pu1_inv_scan; void *pv_table[6]; } cavlc_cntxt_t; /** ************************************************************************ * \file ih264d_structs.h * * \brief * Structures used in the H.264 decoder * * \date * 18/11/2002 * * \author Sriram Sethuraman * ************************************************************************ */ /** * Structure to represent a MV Bank buffer and col flag */ typedef struct { /** * Pointer to buffer that holds col flag. */ void *pv_col_zero_flag; /** * Pointer to buffer that holds mv_pred */ void *pv_mv; }col_mv_buf_t; typedef struct { UWORD8 u1_dydx; /** 4*dy + dx for Y comp / 8*dy + dx for UV comp */ UWORD8 u1_is_bi_direct; /** 1: is bi-direct 0: forward / backward only */ UWORD8 u1_wght_pred_type; /** 0-default 1-singleWeighted 2-BiWeighted */ WORD8 i1_mb_partwidth; /** Width of MB partition */ WORD8 i1_mb_partheight; /** Height of MB partition */ WORD8 i1_mc_wd; /** Number of bytes in a DMA stride */ WORD8 i1_dma_ht; /** Number of strides */ WORD8 i1_pod_ht; /** Flag specifying height of pad on demand */ /** 0 (No pod) -ve(Top pod) +ve(Bottom pod) */ UWORD16 u2_dst_stride; /** Stride value of the destination */ UWORD16 u2_u1_ref_buf_wd; /** Width of the ref buffer */ UWORD16 u2_frm_wd; UWORD16 u2_dummy; UWORD8 *u1_pi1_wt_ofst_rec_v; /** Pointer to packed weight and u4_ofst */ UWORD8 *pu1_rec_y_u; /** MB partition address in row buffer */ UWORD8 *pu1_dma_dest_addr; /** Destination address for DMA transfer */ UWORD8 *pu1_y_ref; UWORD8 *pu1_u_ref; UWORD8 *pu1_v_ref; UWORD8 *pu1_pred; UWORD8 *pu1_pred_u; UWORD8 *pu1_pred_v; UWORD8 u1_dma_wd_y; UWORD8 u1_dma_ht_y; UWORD8 u1_dma_wd_uv; UWORD8 u1_dma_ht_uv; } pred_info_t; typedef struct { UWORD32 *pu4_wt_offst; WORD16 i2_mv[2]; /***************************************************/ /*packing information i1_size_pos_info */ /* bit 1:0 -> X position in terms of (4x4) units */ /* bit 3:2 -> Y position in terms of (4x4) units */ /* bit 5:4 -> PU width 0:4,1:8,2:16 */ /* bit 7:6 -> PU height 0:4,1:8,2:16 */ /***************************************************/ WORD8 i1_size_pos_info; /***************************************************/ /*packing information ref idx info */ /* bit 5:0 ->ref_idx */ /* bit 6:7 -> 0:l0,1:l1,2:bipred */ /***************************************************/ WORD8 i1_ref_idx_info; WORD8 i1_buf_id; UWORD8 u1_pic_type; /** frame /top field/bottom field/mbaff / complementary field pair */ }pred_info_pkd_t; /*! Sequence level parameters */ typedef struct { UWORD8 u1_seq_parameter_set_id; /** id for the seq par set 0-31 */ UWORD8 u1_is_valid; /** is Seq Param set valid */ UWORD16 u2_frm_wd_in_mbs; /** Frame width expressed in MB units */ UWORD16 u2_frm_ht_in_mbs; /** Frame height expressed in MB units */ /* Following are derived from the above two */ UWORD16 u2_fld_ht_in_mbs; /** Field height expressed in MB units */ UWORD16 u2_max_mb_addr; /** Total number of macroblocks in a coded picture */ UWORD16 u2_total_num_of_mbs; /** Total number of macroblocks in a coded picture */ UWORD32 u4_fld_ht; /** field height */ UWORD32 u4_cwidth; /** chroma width */ UWORD32 u4_chr_frm_ht; /** chroma height */ UWORD32 u4_chr_fld_ht; /** chroma field height */ UWORD8 u1_mb_aff_flag; /** 0 - no mb_aff; 1 - uses mb_aff */ UWORD8 u1_profile_idc; /** profile value */ UWORD8 u1_level_idc; /** level value */ /* high profile related syntax elements */ WORD32 i4_chroma_format_idc; WORD32 i4_bit_depth_luma_minus8; WORD32 i4_bit_depth_chroma_minus8; WORD32 i4_qpprime_y_zero_transform_bypass_flag; WORD32 i4_seq_scaling_matrix_present_flag; UWORD8 u1_seq_scaling_list_present_flag[8]; UWORD8 u1_use_default_scaling_matrix_flag[8]; WORD16 i2_scalinglist4x4[6][16]; WORD16 i2_scalinglist8x8[2][64]; UWORD8 u1_more_than_one_slice_group_allowed_flag; UWORD8 u1_arbitrary_slice_order_allowed_flag; UWORD8 u1_redundant_slices_allowed_flag; UWORD8 u1_bits_in_frm_num; /** Number of bits in frame num */ UWORD16 u2_u4_max_pic_num_minus1; /** Maximum frame num minus 1 */ UWORD8 u1_pic_order_cnt_type; /** 0 - 2 indicates the method to code picture order count */ UWORD8 u1_log2_max_pic_order_cnt_lsb_minus; WORD32 i4_max_pic_order_cntLsb; UWORD8 u1_num_ref_frames_in_pic_order_cnt_cycle; UWORD8 u1_delta_pic_order_always_zero_flag; WORD32 i4_ofst_for_non_ref_pic; WORD32 i4_ofst_for_top_to_bottom_field; WORD32 i4_ofst_for_ref_frame[MAX_NUM_REF_FRAMES_OFFSET]; UWORD8 u1_num_ref_frames; UWORD8 u1_gaps_in_frame_num_value_allowed_flag; UWORD8 u1_frame_mbs_only_flag; /** 1 - frame only; 0 - field/frame pic */ UWORD8 u1_direct_8x8_inference_flag; UWORD8 u1_vui_parameters_present_flag; vui_t s_vui; } dec_seq_params_t; typedef struct { UWORD16 u2_frm_wd_in_mbs; /** Frame width expressed in MB units */ UWORD16 u2_frm_ht_in_mbs; /** Frame height expressed in MB units */ UWORD8 u1_frame_mbs_only_flag; /** 1 - frame only; 0 - field/frame pic */ UWORD8 u1_profile_idc; /** profile value */ UWORD8 u1_level_idc; /** level value */ UWORD8 u1_direct_8x8_inference_flag; UWORD8 u1_eoseq_pending; } prev_seq_params_t; /** Picture level parameters */ typedef struct { dec_seq_params_t *ps_sps; /** applicable seq. parameter set */ /* High profile related syntax elements */ WORD32 i4_transform_8x8_mode_flag; WORD32 i4_pic_scaling_matrix_present_flag; UWORD8 u1_pic_scaling_list_present_flag[8]; UWORD8 u1_pic_use_default_scaling_matrix_flag[8]; WORD16 i2_pic_scalinglist4x4[6][16]; WORD16 i2_pic_scalinglist8x8[2][64]; WORD8 i1_second_chroma_qp_index_offset; UWORD32 u4_slice_group_change_rate; UWORD8 *pu1_slice_groupmb_map; /** MB map with slice membership labels */ UWORD8 u1_pic_parameter_set_id; /** id for the picture par set 0-255*/ UWORD8 u1_entropy_coding_mode; /** Entropy coding : 0-VLC; 1 - CABAC */ UWORD8 u1_num_slice_groups; /** Number of slice groups */ UWORD8 u1_pic_init_qp; /** Initial QPY for the picture {-26,25}*/ WORD8 i1_chroma_qp_index_offset; /** Chroma QP u4_ofst w.r.t QPY {-12,12} */ UWORD8 u1_dblk_filter_parms_flag; /** Slice layer has deblocking filter parameters */ UWORD8 u1_constrained_intra_pred_flag; /** Constrained intra prediction u4_flag */ UWORD8 u1_redundant_pic_cnt_present_flag; /** Redundant_pic_cnt is in slices using this PPS */ UWORD8 u1_pic_order_present_flag; /** Pic order present u4_flag */ UWORD8 u1_num_ref_idx_lx_active[2]; /** Maximum reference picture index in the reference list 0 : range [1 - 15] */ UWORD8 u1_wted_pred_flag; UWORD8 u1_wted_bipred_idc; UWORD8 u1_pic_init_qs; UWORD8 u1_deblocking_filter_parameters_present_flag; UWORD8 u1_vui_pic_parameters_flag; UWORD8 u1_mb_slice_group_map_type; UWORD8 u1_slice_group_change_direction_flag; UWORD8 u1_frame_cropping_flag; UWORD8 u1_frame_cropping_rect_left_ofst; UWORD8 u1_frame_cropping_rect_right_ofst; UWORD8 u1_frame_cropping_rect_top_ofst; UWORD8 u1_frame_cropping_rect_bottom_ofst; void * pv_codec_handle; /* For Error Handling */ WORD32 i4_top_field_order_cnt; WORD32 i4_bottom_field_order_cnt; WORD32 i4_avg_poc; UWORD8 u1_is_valid; /** is Pic Param set valid */ } dec_pic_params_t; /** Picture Order Count Paramsters */ typedef struct { WORD32 i4_pic_order_cnt_lsb; WORD32 i4_pic_order_cnt_msb; WORD32 i4_delta_pic_order_cnt_bottom; WORD32 i4_delta_pic_order_cnt[2]; WORD32 i4_prev_frame_num_ofst; UWORD8 u1_mmco_equalto5; UWORD8 u1_bot_field; UWORD16 u2_frame_num; WORD32 i4_top_field_order_count; WORD32 i4_bottom_field_order_count; } pocstruct_t; /*****************************************************************************/ /* parse_mb_pers_info contains necessary mb info data required persistently */ /* in the form of top and left neighbours. */ /*****************************************************************************/ typedef struct { void *u4_pic_addrress[4]; /* picture address for BS calc */ WORD8 pi1_intrapredmodes[4]; /* calc Intra pred modes */ UWORD8 pu1_nnz_y[4]; UWORD8 pu1_nnz_uv[4]; UWORD8 u1_mb_fld; UWORD8 u1_mb_type; UWORD16 u2_luma_csbp; /* Luma csbp used for BS calc */ UWORD8 u1_tran_form8x8; } mb_neigbour_params_t; /* This info is required for decoding purposes except Deblockng */ typedef struct _DecMbInfo { UWORD8 u1_mb_type; /** macroblock type: I/P/B/SI/SP */ UWORD8 u1_chroma_pred_mode; UWORD8 u1_cbp; UWORD8 u1_mb_mc_mode; /** 16x16, 2 16x8, 2 8x16, 4 8x8 */ UWORD8 u1_topmb; /** top Mb u4_flag */ UWORD8 u1_mb_ngbr_availablity; UWORD8 u1_end_of_slice; UWORD8 u1_mb_field_decodingflag; UWORD8 u1_topleft_mb_fld; UWORD8 u1_topleft_mbtype; WORD8 i1_offset; UWORD8 u1_Mux; UWORD8 u1_qp_div6; UWORD8 u1_qp_rem6; UWORD8 u1_qpc_div6; UWORD8 u1_qpcr_div6; UWORD8 u1_qpc_rem6; UWORD8 u1_qpcr_rem6; UWORD8 u1_tran_form8x8; UWORD8 u1_num_pred_parts; UWORD8 u1_yuv_dc_block_flag; UWORD16 u2_top_right_avail_mask; UWORD16 u2_top_left_avail_mask; UWORD16 u2_luma_csbp; /** Coded 4x4 Sub Block Pattern */ UWORD16 u2_chroma_csbp; /** Coded 4x4 Sub Block Pattern */ UWORD16 u2_mbx; UWORD16 u2_mby; UWORD16 u2_mask[2]; UWORD32 u4_pred_info_pkd_idx; mb_neigbour_params_t *ps_left_mb; mb_neigbour_params_t *ps_top_mb; mb_neigbour_params_t *ps_top_right_mb; mb_neigbour_params_t *ps_curmb; } dec_mb_info_t; /** Slice level parameters */ typedef struct { dec_pic_params_t *ps_pps; /** PPS used */ WORD32 i4_delta_pic_order_cnt[2]; WORD32 i4_poc; /** Pic order cnt of picture to which slice belongs*/ UWORD32 u4_idr_pic_id; /** IDR pic ID */ UWORD16 u2_first_mb_in_slice; /** Address of first MB in slice*/ UWORD16 u2_frame_num; /** Frame number from prev IDR pic */ UWORD8 u1_mbaff_frame_flag; /** Mb adaptive frame field u4_flag */ UWORD8 u1_field_pic_flag; /** Field picture or not */ UWORD8 u1_bottom_field_flag; /** If slice belongs to bot field pic */ UWORD8 u1_slice_type; /** I/P/B/SI/SP */ WORD32 i4_pic_order_cnt_lsb; /** Picture Order Count */ UWORD8 u1_slice_qp; /** Add slice_qp_delta to pic_init_QP */ UWORD8 u1_disable_dblk_filter_idc; /** 0-dblk all edges; 1 - suppress; 2 - suppress only edges */ WORD8 i1_slice_alpha_c0_offset; /** dblk: alpha and C0 table u4_ofst {-12,12}*/ WORD8 i1_slice_beta_offset; /** dblk: beta table u4_ofst {-12, 12}*/ UWORD8 u1_sp_for_switch_flag; UWORD8 u1_no_output_of_prior_pics_flag; UWORD8 u1_long_term_reference_flag; UWORD8 u1_num_ref_idx_lx_active[2]; UWORD8 u1_cabac_init_idc; /** cabac_init_idc */ UWORD8 u1_num_ref_idx_active_override_flag; UWORD8 u1_direct_spatial_mv_pred_flag; WORD32 (*pf_decodeDirect)(struct _DecStruct *ps_dec, UWORD8 u1_wd_x, dec_mb_info_t *ps_cur_mb_info, UWORD8 u1_mb_num); UWORD8 u1_redundant_pic_cnt; WORD8 i1_slice_qs_delta; UWORD8 u1_nal_ref_idc; /** NAL ref idc of the Slice NAL unit */ UWORD8 u1_nal_unit_type; /** NAL unit type of the Slice NAL */ UWORD8 u1_direct_8x8_inference_flag; UWORD8 u1_mmco_equalto5; /** any of the MMCO command equal to 5 */ UWORD8 u1_pic_order_cnt_type; pocstruct_t s_POC; /* DataStructures required for weighted prediction */ UWORD16 u2_log2Y_crwd; /** Packed luma and chroma log2_weight_denom */ /* [list0/list1]:[ref pics index]:[0-Y 1-Cb 2-Cr] [weight/u4_ofst], weights and offsets are signed numbers, since they are packed, it is defined unsigned. LSB byte : weight and MSB byte: u4_ofst */ UWORD32 u4_wt_ofst_lx[2][MAX_REF_BUFS][3]; void * pv_codec_handle; /* For Error Handling */ /* This is used when reordering is done in Forward or */ /* backward lists. This is because reordering can point */ /* to any valid entry in initial list irrespective of */ /* num_ref_idx_active which could be overwritten using */ /* ref_idx_reorder_flag */ UWORD8 u1_initial_list_size[2]; UWORD32 u4_mbs_in_slice; } dec_slice_params_t; typedef struct { UWORD8 u1_mb_type; /* Bit representations, X- reserved */ /** |Field/Frame|X|X|X|X|Bslice u4_flag|PRED_NON_16x16 u4_flag |Intra Mbflag| */ UWORD8 u1_mb_qp; UWORD8 u1_deblocking_mode; /** dblk: Mode [ NO / NO TOP / NO LEFT] filter */ WORD8 i1_slice_alpha_c0_offset; /** dblk: alpha and C0 table u4_ofst {-12,12}*/ WORD8 i1_slice_beta_offset; /** dblk: beta table u4_ofst {-12, 12}*/ UWORD8 u1_single_call; UWORD8 u1_topmb_qp; UWORD8 u1_left_mb_qp; UWORD32 u4_bs_table[10]; /* Boundary strength */ } deblk_mb_t; typedef struct { UWORD8 u1_mb_type; UWORD8 u1_mb_qp; } deblkmb_neighbour_t; #define MAX_MV_RESIDUAL_INFO_PER_MB 32 #define MAX_REFIDX_INFO_PER_MB 4 #define PART_NOT_DIRECT 0 #define PART_DIRECT_8x8 1 #define PART_DIRECT_16x16 2 typedef struct { UWORD8 u1_is_direct; UWORD8 u1_pred_mode; UWORD8 u1_sub_mb_num; UWORD8 u1_partheight; UWORD8 u1_partwidth; } parse_part_params_t; typedef struct { UWORD8 u1_isI_mb; UWORD8 u1_num_part; UWORD32 *pu4_wt_offst[MAX_REFIDX_INFO_PER_MB]; WORD8 i1_ref_idx[2][MAX_REFIDX_INFO_PER_MB]; UWORD8 u1_col_info[MAX_REFIDX_INFO_PER_MB]; } parse_pmbarams_t; typedef struct { UWORD8 u1_vert_pad_top; /* flip-flop u4_flag remembering pad area (Vert) */ UWORD8 u1_vert_pad_bot; /* flip-flop u4_flag remembering pad area (Vert) */ UWORD8 u1_horz_pad; /* flip-flop u4_flag remembering pad area (Vert) */ UWORD8 u1_pad_len_y_v; /* vertical pad amount for luma */ UWORD8 u1_pad_len_cr_v; /* vertical pad amount for chroma */ } pad_mgr_t; #define ACCEPT_ALL_PICS (0x00) #define REJECT_CUR_PIC (0x01) #define REJECT_PB_PICS (0x02) #define MASK_REJECT_CUR_PIC (0xFE) #define MASK_REJECT_PB_PICS (0xFD) #define PIC_TYPE_UNKNOWN (0xFF) #define PIC_TYPE_I (0x00) #define SYNC_FRM_DEFAULT (0xFFFFFFFF) #define INIT_FRAME (0xFFFFFF) typedef struct dec_err_status_t { UWORD8 u1_cur_pic_type; UWORD8 u1_pic_aud_i; UWORD8 u1_err_flag; UWORD32 u4_frm_sei_sync; UWORD32 u4_cur_frm; } dec_err_status_t; /**************************************************************************/ /* Structure holds information about all high profile toolsets */ /**************************************************************************/ typedef struct { /*****************************************/ /* variables required for scaling */ /*****************************************/ UWORD8 u1_scaling_present; WORD16 *pi2_scale_mat[8]; /*************************************************/ /* scaling matrices for frame macroblocks after */ /* inverse scanning */ /*************************************************/ WORD16 i2_scalinglist4x4[6][16]; WORD16 i2_scalinglist8x8[2][64]; /*****************************************/ /* variables required for transform8x8 */ /*****************************************/ UWORD8 u1_transform8x8_present; UWORD8 u1_direct_8x8_inference_flag; /* temporary variable to get noSubMbPartSizeLessThan8x8Flag from ih264d_parse_bmb_non_direct_cavlc */ UWORD8 u1_no_submb_part_size_lt8x8_flag; /* needed for inverse scanning */ cavlc_cntxt_t s_cavlc_ctxt; /* contexts for the CABAC related parsing */ bin_ctxt_model_t *ps_transform8x8_flag; bin_ctxt_model_t *ps_sigcoeff_8x8_frame; bin_ctxt_model_t *ps_last_sigcoeff_8x8_frame; bin_ctxt_model_t *ps_coeff_abs_levelminus1; bin_ctxt_model_t *ps_sigcoeff_8x8_field; bin_ctxt_model_t *ps_last_sigcoeff_8x8_field; /* variables required for intra8x8 */ /* variables required for handling different Qp for Cb and Cr */ } high_profile_tools_t; typedef struct { UWORD32 u4_num_bufs; /* Number of buffers in each display frame. 2 for 420SP and 3 for 420P and so on */ void *buf[3]; /* Pointers to each of the components */ UWORD32 u4_bufsize[3]; UWORD32 u4_ofst[3]; } disp_buf_t; typedef struct _dec_slice_struct { volatile UWORD32 u4_first_mb_in_slice; volatile UWORD32 slice_type; volatile UWORD16 u2_log2Y_crwd; volatile void **ppv_map_ref_idx_to_poc; volatile void *pv_tu_coeff_data_start; } dec_slice_struct_t; /** * Structure to hold coefficient info for a 4x4 transform */ typedef struct { /** * significant coefficient map */ UWORD16 u2_sig_coeff_map; /** * holds coefficients */ WORD16 ai2_level[16]; }tu_sblk4x4_coeff_data_t; /** * Structure to hold coefficient info for a 8x8 transform */ typedef struct { /** * significant coefficient map */ UWORD32 au4_sig_coeff_map[2]; /** * holds coefficients */ WORD16 ai2_level[64]; }tu_blk8x8_coeff_data_t; /** Aggregating structure that is globally available */ typedef struct _DecStruct { /* Add below all other static memory allocations and pointers to items that are dynamically allocated once per session */ dec_bit_stream_t *ps_bitstrm; dec_seq_params_t *ps_cur_sps; dec_pic_params_t *ps_cur_pps; dec_slice_params_t *ps_cur_slice; dec_pic_params_t *ps_pps; dec_seq_params_t *ps_sps; const UWORD16 *pu2_quant_scale_y; const UWORD16 *pu2_quant_scale_u; const UWORD16 *pu2_quant_scale_v; UWORD16 u2_mbx; UWORD16 u2_mby; UWORD16 u2_frm_wd_y; /** Width for luma buff */ UWORD16 u2_frm_ht_y; /** Height for luma buff */ UWORD16 u2_frm_wd_uv; /** Width for chroma buff */ UWORD16 u2_frm_ht_uv; /** Height for chroma buff */ UWORD16 u2_frm_wd_in_mbs; /** Frame width expressed in MB units */ UWORD16 u2_frm_ht_in_mbs; /** Frame height expressed in MB units */ WORD32 i4_submb_ofst; /** Offset in subMbs from the top left edge */ /* Pointer to colocated Zero frame Image, will be used in B_DIRECT mode */ /* colZeroFlag | // 0th bit field_flag | // 1st bit XX | // 2:3 bit don't cares subMbMode | // 4:5 bit MbMode | // 6:7 bit */ UWORD8 *pu1_col_zero_flag; UWORD16 u2_pic_wd; /** Width of the picture being decoded */ UWORD16 u2_pic_ht; /** Height of the picture being decoded */ UWORD8 u1_first_slice_in_stream; UWORD8 u1_mb_ngbr_availablity; UWORD8 u1_ref_idxl0_active_minus1; UWORD8 u1_qp; UWORD8 u1_qp_y_div6; UWORD8 u1_qp_u_div6; UWORD8 u1_qp_y_rem6; UWORD8 u1_qp_u_rem6; /*********************************/ /* configurable mb-group numbers */ /* very critical to the decoder */ /*********************************/ /************************************************************/ /* MB_GROUP should be a multiple of 2 */ /************************************************************/ UWORD8 u1_recon_mb_grp; UWORD8 u1_recon_mb_grp_pair; /* Variables to handle Cabac */ decoding_envirnoment_t s_cab_dec_env; /* < Structure for decoding_envirnoment_t */ /* These things need to be updated at each MbLevel */ WORD8 i1_next_ctxt_idx; /* < next Ctxt Index */ UWORD8 u1_currB_type; WORD8 i1_prev_mb_qp_delta; /* Prev MbQpDelta */ UWORD8 u1_nal_unit_type; ctxt_inc_mb_info_t *p_ctxt_inc_mb_map; /* Pointer to ctxt_inc_mb_info_t map */ ctxt_inc_mb_info_t *p_left_ctxt_mb_info; /* Pointer to left ctxt_inc_mb_info_t */ ctxt_inc_mb_info_t *p_top_ctxt_mb_info; /* Pointer to top ctxt_inc_mb_info_t */ ctxt_inc_mb_info_t *ps_curr_ctxt_mb_info; /* Pointer to current ctxt_inc_mb_info_t */ ctxt_inc_mb_info_t *ps_def_ctxt_mb_info; /* Pointer to default ctxt_inc_mb_info_t */ /* mv contexts for mv decoding using cabac */ //UWORD8 u1_top_mv_ctxt_inc[4][4]; /* Dimensions for u1_left_mv_ctxt_inc_arr is [2][4][4] for Mbaff case */ UWORD8 u1_left_mv_ctxt_inc_arr[2][4][4]; UWORD8 (*pu1_left_mv_ctxt_inc)[4]; UWORD8 u1_sub_mb_num; UWORD8 u1_B; /** if B slice u1_B = 1 else 0 */ WORD16 i2_only_backwarddma_info_idx; mv_pred_t *ps_mv; /** Pointer to the MV bank array */ mv_pred_t *ps_mv_bank_cur; /** Pointer to the MV bank array */ mv_pred_t s_default_mv_pred; /** Structure containing the default values for MV predictor */ pred_info_t *ps_pred; /** Stores info to cfg MC */ pred_info_t *ps_pred_start; UWORD32 u4_pred_info_idx; pred_info_pkd_t *ps_pred_pkd; pred_info_pkd_t *ps_pred_pkd_start; UWORD32 u4_pred_info_pkd_idx; UWORD8 *pu1_ref_buff; /** Destination buffer for DMAs */ UWORD32 u4_dma_buf_idx; UWORD8 *pu1_y; UWORD8 *pu1_u; UWORD8 *pu1_v; WORD16 *pi2_y_coeff; UWORD8 *pu1_inv_scan; /** * Pointer frame level TU subblock coeff data */ void *pv_pic_tu_coeff_data; /** * Pointer to TU subblock coeff data and number of subblocks and scan idx * Incremented each time a coded subblock is processed * */ void *pv_parse_tu_coeff_data; void *pv_prev_mb_parse_tu_coeff_data; void *pv_proc_tu_coeff_data; WORD16 *pi2_coeff_data; cavlc_cntxt_t s_cavlc_ctxt; UWORD32 u4_n_leftY[2]; UWORD32 u4_n_left_cr[2]; UWORD32 u4_n_left_temp_y; UWORD8 pu1_left_nnz_y[4]; UWORD8 pu1_left_nnz_uv[4]; UWORD32 u4_n_left_temp_uv; /***************************************************************************/ /* Base pointer to all the cabac contexts */ /***************************************************************************/ bin_ctxt_model_t *p_cabac_ctxt_table_t; /***************************************************************************/ /* cabac context pointers for every SE mapped into in p_cabac_ctxt_table_t */ /***************************************************************************/ bin_ctxt_model_t *p_mb_type_t; bin_ctxt_model_t *p_mb_skip_flag_t; bin_ctxt_model_t *p_sub_mb_type_t; bin_ctxt_model_t *p_mvd_x_t; bin_ctxt_model_t *p_mvd_y_t; bin_ctxt_model_t *p_ref_idx_t; bin_ctxt_model_t *p_mb_qp_delta_t; bin_ctxt_model_t *p_intra_chroma_pred_mode_t; bin_ctxt_model_t *p_prev_intra4x4_pred_mode_flag_t; bin_ctxt_model_t *p_rem_intra4x4_pred_mode_t; bin_ctxt_model_t *p_mb_field_dec_flag_t; bin_ctxt_model_t *p_cbp_luma_t; bin_ctxt_model_t *p_cbp_chroma_t; bin_ctxt_model_t *p_cbf_t[NUM_CTX_CAT]; bin_ctxt_model_t *p_significant_coeff_flag_t[NUM_CTX_CAT]; bin_ctxt_model_t *p_coeff_abs_level_minus1_t[NUM_CTX_CAT]; UWORD32 u4_num_pmbair; /** MB pair number */ mv_pred_t *ps_mv_left; /** Pointer to left motion vector bank */ mv_pred_t *ps_mv_top_left; /** Pointer to top left motion vector bank */ mv_pred_t *ps_mv_top_right; /** Pointer to top right motion vector bank */ UWORD8 *pu1_left_yuv_dc_csbp; deblkmb_neighbour_t deblk_left_mb[2]; deblkmb_neighbour_t *ps_deblk_top_mb; neighbouradd_t (*ps_left_mvpred_addr)[2]; /* Left MvPred Address Ping Pong*/ /***************************************************************************/ /* Ref_idx contexts are stored in the following way */ /* Array Idx 0,1 for reference indices in Forward direction */ /* Array Idx 2,3 for reference indices in backward direction */ /***************************************************************************/ /* Dimensions for u1_left_ref_ctxt_inc_arr is [2][4] for Mbaff:Top and Bot */ WORD8 i1_left_ref_idx_ctx_inc_arr[2][4]; WORD8 *pi1_left_ref_idx_ctxt_inc; /*************************************************************************/ /* Arrangnment of DC CSBP */ /* bits: b7 b6 b5 b4 b3 b2 b1 b0 */ /* CSBP: x x x x x Vdc Udc Ydc */ /*************************************************************************/ /*************************************************************************/ /* Points either to u1_yuv_dc_csbp_topmb or u1_yuv_dc_csbp_bot_mb */ /*************************************************************************/ UWORD8 u1_yuv_dc_csbp_topmb; UWORD8 u1_yuv_dc_csbp_bot_mb; /* DMA SETUP */ tfr_ctxt_t s_tran_addrecon_parse; tfr_ctxt_t s_tran_addrecon; tfr_ctxt_t s_tran_iprecon; tfr_ctxt_t *ps_frame_buf_ip_recon; WORD8 i1_recon_in_thread3_flag; /* slice Header Simplification */ UWORD8 u1_pr_sl_type; WORD32 i4_frametype; UWORD32 u4_app_disp_width; WORD32 i4_error_code; UWORD32 u4_bitoffset; /* Variables added to handle field pics */ UWORD8 u1_second_field; WORD32 i4_pic_type; WORD32 i4_content_type; WORD32 i4_decode_header; WORD32 i4_header_decoded; UWORD32 u4_total_frames_decoded; ctxt_inc_mb_info_t *ps_left_mb_ctxt_info; /* structure containing the left MB's context info, incase of Mbaff */ pocstruct_t s_prev_pic_poc; pocstruct_t s_cur_pic_poc; WORD32 i4_cur_display_seq; WORD32 i4_prev_max_display_seq; WORD32 i4_max_poc; deblk_mb_t *ps_cur_deblk_mb; /* Pointers to local scratch buffers */ deblk_mb_t *ps_deblk_pic; /* Pointers to Picture Buffers (Given by BufAPI Lib) */ struct pic_buffer_t *ps_cur_pic; /** Pointer to Current picture buffer */ /* Scratch Picture Buffers (Given by BufAPI Lib) */ struct pic_buffer_t s_cur_pic; /* Current Slice related information */ volatile UWORD16 u2_cur_slice_num; volatile UWORD16 u2_cur_slice_num_dec_thread; /* Variables needed for Buffer API handling */ UWORD8 u1_nal_buf_id; UWORD8 u1_pic_buf_id; UWORD8 u1_pic_bufs; WORD16 *pi2_pred1; //[441]; /** Temp predictor buffer for MC */ /* Pointer to refernce Pic buffers list, 0:fwd, 1:bwd */ pic_buffer_t **ps_ref_pic_buf_lx[2]; /* refIdx to POC mapping */ void **ppv_map_ref_idx_to_poc; void **ppv_map_ref_idx_to_poc_base; UWORD32 *pu4_wts_ofsts_mat; UWORD32 *pu4_wt_ofsts; UWORD32 *pu4_mbaff_wt_mat; /* Function pointers to read Params common to CAVLC and CABAC */ WORD32 (*pf_parse_inter_mb)(struct _DecStruct * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD8 u1_mb_num, UWORD8 u1_num_mbsNby2); WORD32 (*pf_mvpred_ref_tfr_nby2mb)(struct _DecStruct * ps_dec, UWORD8 u1_num_mbs, UWORD8 u1_num_mbsNby2); WORD32 (*pf_parse_inter_slice)(struct _DecStruct * ps_dec, dec_slice_params_t * ps_slice, UWORD16 u2_first_mb_in_slice); UWORD32 (*pf_get_mb_info)(struct _DecStruct * ps_dec, const UWORD16 u2_cur_mb_address, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mbskip_run); /* Variables for Decode Buffer Management */ dpb_manager_t *ps_dpb_mgr; dpb_commands_t *ps_dpb_cmds; dpb_commands_t s_dpb_cmds_scratch; /* Variables Required for N MB design */ dec_mb_info_t *ps_nmb_info; UWORD8 *pu1_y_intra_pred_line; UWORD8 *pu1_u_intra_pred_line; UWORD8 *pu1_v_intra_pred_line; UWORD8 *pu1_cur_y_intra_pred_line; UWORD8 *pu1_cur_u_intra_pred_line; UWORD8 *pu1_cur_v_intra_pred_line; UWORD8 *pu1_cur_y_intra_pred_line_base; UWORD8 *pu1_cur_u_intra_pred_line_base; UWORD8 *pu1_cur_v_intra_pred_line_base; UWORD8 *pu1_prev_y_intra_pred_line; UWORD8 *pu1_prev_u_intra_pred_line; UWORD8 *pu1_prev_v_intra_pred_line; UWORD32 u4_intra_pred_line_ofst; UWORD8 u1_res_changed; mv_pred_t *ps_mv_cur; /** pointer to current motion vector bank */ mv_pred_t *ps_mv_top; /** pointer to top motion vector bank */ mv_pred_t *ps_mv_top_right2;/** Pointer to top right motion vector bank */ mv_pred_t *ps_mv_p[2]; /** Scratch ping motion vector bank */ mv_pred_t *ps_mv_top_p[MV_SCRATCH_BUFS]; /** Scratch top pong motion vector bank */ UWORD8 u1_mv_top_p; deblk_mb_t *ps_deblk_mbn; UWORD8 *pu1_temp_mc_buffer; struct _sei *ps_sei; struct _sei *ps_sei_parse; struct _sei s_sei_export; void *pv_disp_sei_params; UWORD8 u1_pic_struct_copy; /* Variables required for cropping */ UWORD16 u2_disp_width; UWORD16 u2_disp_height; UWORD16 u2_crop_offset_y; UWORD16 u2_crop_offset_uv; /* Crop info from SPS */ UWORD8 u1_frame_cropping_flag; UWORD8 u1_frame_cropping_rect_left_ofst; UWORD8 u1_frame_cropping_rect_right_ofst; UWORD8 u1_frame_cropping_rect_top_ofst; UWORD8 u1_frame_cropping_rect_bottom_ofst; /* Variable required to get presentation time stamp through application */ UWORD32 u4_pts; /* Variables used for gaps in frame number */ UWORD16 u2_prev_ref_frame_num; UWORD8 u1_mb_idx; struct pic_buffer_t *ps_col_pic; void (*pf_parse_mvdirect)(struct _DecStruct*, struct pic_buffer_t*, directmv_t*, UWORD8, WORD32, dec_mb_info_t *); void *pv_dec_out; void *pv_dec_in; void *pv_scratch_sps_pps; /*used temeporarily store sps/ spps while parsing*/ /* state pointers to mb and partition information */ parse_pmbarams_t *ps_parse_mb_data; parse_part_params_t *ps_parse_part_params; /* scratch pointers to mb and partition information */ parse_part_params_t *ps_part; UWORD8 u1_max_dec_frame_buffering; pad_mgr_t s_pad_mgr; UWORD8 (*pf_mvpred)(struct _DecStruct *ps_dec, struct _DecMbInfo *ps_cur_mb_info, mv_pred_t *ps_mv_pred, mv_pred_t *ps_mv_nmb, mv_pred_t *ps_mv_ntop, UWORD8 u1_sub_mb_num, UWORD8 uc_mb_part_width, UWORD8 uc_lxstart, UWORD8 uc_lxend, UWORD8 u1_mb_mc_mode); void (*pf_compute_bs)(struct _DecStruct * ps_dec, struct _DecMbInfo * ps_cur_mb_info, const UWORD16 u2_mbxn_mb); UWORD8 u1_init_dec_flag; WORD32 i4_reorder_depth; prev_seq_params_t s_prev_seq_params; UWORD8 u1_cur_mb_fld_dec_flag; /* current Mb fld or Frm */ UWORD8 u1_topleft_mb_fld; UWORD8 u1_topleft_mbtype; UWORD8 u1_topleft_mb_fld_bot; UWORD8 u1_topleft_mbtype_bot; WORD16 i2_prev_slice_mbx; WORD16 i2_prev_slice_mby; UWORD16 u2_top_left_mask; UWORD16 u2_top_right_mask; dec_err_status_t * ps_dec_err_status; /* Ensure pi1_left_pred_mode is aligned to 4 byte boundary, by declaring this after a pointer or an integer */ WORD8 pi1_left_pred_mode[8]; UWORD8 u1_mb_idx_mv; UWORD16 u2_mv_2mb[2]; UWORD32 u4_skip_frm_mask; /* variable for finding the no.of mbs decoded in the current picture */ UWORD16 u2_total_mbs_coded; /* member added for supporting fragmented annex - B */ // frg_annex_read_t s_frag_annex_read; /* added for vui_t, sei support*/ WORD32 i4_vui_frame_rate; /* To Store the value of ref_idx_active for previous slice */ /* useful in error handling */ UWORD8 u1_num_ref_idx_lx_active_prev; /* Flag added to come out of process call in annex-b if&if frame is decoded */ /* presence of access unit delimters and pps and sps */ UWORD8 u1_frame_decoded_flag; /* To keep track of whether the last picture was decoded or not */ /* in case of skip mode set by the application */ UWORD8 u1_last_pic_not_decoded; WORD32 e_dec_status; UWORD32 u4_num_fld_in_frm; /* Function pointer for 4x4 residual cavlc parsing based on total coeff */ WORD32 (*pf_cavlc_4x4res_block[3])(UWORD32 u4_isdc, UWORD32 u4_total_coeff_trail_one, /**TotalCoefficients<<16+trailingones*/ dec_bit_stream_t *ps_bitstrm); /* Function pointer array for interpolate functions in called from motion compensattion module */ void (*p_mc_interpolate_x_y[16][3])(UWORD8*, UWORD8*, UWORD8*, UWORD8, UWORD16, UWORD16, UWORD8); /**************************************************************************/ /* Function pointer for 4x4 totalcoeff, trlone and residual cavlc parsing */ /* based on u4_n (neigbourinng nnz average) */ /* These point to two functions depending on (u4_n > 7) and (u4_n <= 7) */ /**************************************************************************/ WORD32 (*pf_cavlc_parse4x4coeff[2])(WORD16 *pi2_coeff_block, UWORD32 u4_isdc, /* is it a DC block */ WORD32 u4_n, struct _DecStruct *ps_dec, /** Decoder Parameters */ UWORD32 *pu4_total_coeff); /**************************************************************************/ /* Function pointer for luma 8x8block cavlc parsing based on top and left */ /* neigbour availability. */ /**************************************************************************/ WORD32 (*pf_cavlc_parse_8x8block[4])(WORD16 *pi2_coeff_block, UWORD32 u4_sub_block_strd, UWORD32 u4_isdc, struct _DecStruct *ps_dec, UWORD8 *pu1_top_nnz, UWORD8 *pu1_left_nnz, UWORD8 u1_tran_form8x8, UWORD8 u1_mb_field_decodingflag, UWORD32 *pu4_csbp); /**************************************************************************/ /* Ping pong top and current rows of mb neigbour_params */ /**************************************************************************/ mb_neigbour_params_t *ps_nbr_mb_row; mb_neigbour_params_t *ps_cur_mb_row; mb_neigbour_params_t *ps_top_mb_row; /**************************************************************************/ /* Function pointer for 16x16 and non16x16 Bs1 calculations depending on */ /* P and B slice. */ /***************************************************************************/ void (*pf_fill_bs1[2][2])(mv_pred_t *ps_cur_mv_pred, mv_pred_t *ps_top_mv_pred, void **ppv_map_ref_idx_to_poc, UWORD32 *pu4_bs_table, /* pointer to the BsTable array */ mv_pred_t *ps_leftmost_mv_pred, neighbouradd_t *ps_left_addr, void **u4_pic_addrress, WORD32 i4_ver_mvlimit); void (*pf_fill_bs_xtra_left_edge[2])(UWORD32 *pu4_bs, /* Base pointer of BS table */ WORD32 u4_left_mb_t_csbp, /* left mbpair's top csbp */ WORD32 u4_left_mb_b_csbp, /* left mbpair's bottom csbp*/ WORD32 u4_cur_mb_csbp, /* csbp of current mb */ UWORD32 u4_cur_mb_bot /* is top or bottom mb */ ); /* Function pointer array for BP and MP functions for MC*/ void (*p_motion_compensate)(struct _DecStruct * ps_dec, dec_mb_info_t *ps_cur_mb_info); void (*p_mc_dec_thread)(struct _DecStruct * ps_dec, dec_mb_info_t *ps_cur_mb_info); /* Function pointer array for BP and MP functions for formMbPartInfo*/ WORD32 (*p_form_mb_part_info)(pred_info_pkd_t *ps_pred_pkd, struct _DecStruct * ps_dec, UWORD16 u2_mb_x, UWORD16 u2_mb_y, WORD32 mb_index, dec_mb_info_t *ps_cur_mb_info); WORD32 (*p_form_mb_part_info_thread)(pred_info_pkd_t *ps_pred_pkd, struct _DecStruct * ps_dec, UWORD16 u2_mb_x, UWORD16 u2_mb_y, WORD32 mb_index, dec_mb_info_t *ps_cur_mb_info); /* Required for cabac mbaff bottom mb */ UWORD32 u4_next_mb_skip; void (*p_DeblockPicture[2])(struct _DecStruct *); /* ! */ UWORD32 u4_ts; UWORD8 u1_flushfrm; /* Output format sent by the application */ UWORD8 u1_chroma_format; UWORD8 u1_pic_decode_done; UWORD8 u1_slice_header_done; WORD32 init_done; /******************************************/ /* For the high profile related variables */ /******************************************/ high_profile_tools_t s_high_profile; /* CBCR */ UWORD8 u1_qp_v_div6; UWORD8 u1_qp_v_rem6; /* * TO help solve the dangling field case. * Check for the previous frame number and the current frame number. */ UWORD16 u2_prv_frame_num; UWORD8 u1_top_bottom_decoded; UWORD8 u1_dangling_field; IVD_DISPLAY_FRAME_OUT_MODE_T e_frm_out_mode; UWORD8 *pu1_bits_buf_static; UWORD8 *pu1_bits_buf_dynamic; UWORD32 u4_static_bits_buf_size; UWORD32 u4_dynamic_bits_buf_size; UWORD32 u4_num_disp_bufs_requested; WORD32 i4_display_delay; UWORD32 u4_slice_start_code_found; UWORD32 u4_nmb_deblk; UWORD32 u4_use_intrapred_line_copy; UWORD32 u4_num_mbs_prev_nmb; UWORD32 u4_num_mbs_cur_nmb; UWORD32 u4_app_deblk_disable_level; UWORD32 u4_app_disable_deblk_frm; WORD32 i4_mv_frac_mask; disp_buf_t disp_bufs[MAX_DISP_BUFS_NEW]; UWORD32 u4_disp_buf_mapping[MAX_DISP_BUFS_NEW]; UWORD32 u4_disp_buf_to_be_freed[MAX_DISP_BUFS_NEW]; UWORD32 u4_share_disp_buf; UWORD32 u4_num_disp_bufs; UWORD32 u4_bs_deblk_thread_created; volatile UWORD32 u4_start_recon_deblk; void *pv_bs_deblk_thread_handle; UWORD32 u4_cur_bs_mb_num; UWORD32 u4_bs_cur_slice_num_mbs; UWORD32 u4_cur_deblk_mb_num; UWORD32 u4_sps_cnt_in_process; volatile UWORD16 u2_cur_slice_num_bs; UWORD32 u4_deblk_mb_x; UWORD32 u4_deblk_mb_y; iv_yuv_buf_t s_disp_frame_info; UWORD32 u4_fmt_conv_num_rows; UWORD32 u4_fmt_conv_cur_row; ivd_out_bufdesc_t *ps_out_buffer; ivd_get_display_frame_op_t s_disp_op; UWORD32 u4_output_present; volatile UWORD16 cur_dec_mb_num; volatile UWORD16 cur_recon_mb_num; volatile UWORD16 u2_cur_mb_addr; WORD16 i2_dec_thread_mb_y; WORD16 i2_recon_thread_mb_y; UWORD8 u1_separate_parse; UWORD32 u4_dec_thread_created; void *pv_dec_thread_handle; volatile UWORD8 *pu1_dec_mb_map; volatile UWORD8 *pu1_recon_mb_map; volatile UWORD16 *pu2_slice_num_map; dec_slice_struct_t *ps_dec_slice_buf; void *pv_map_ref_idx_to_poc_buf; dec_mb_info_t *ps_frm_mb_info; volatile dec_slice_struct_t * volatile ps_parse_cur_slice; volatile dec_slice_struct_t * volatile ps_decode_cur_slice; volatile dec_slice_struct_t * volatile ps_computebs_cur_slice; UWORD32 u4_cur_slice_decode_done; UWORD32 u4_extra_mem_used; /* 2 first slice not parsed , 1 :first slice parsed , 0 :first valid slice header parsed*/ UWORD32 u4_first_slice_in_pic; UWORD32 u4_num_cores; IVD_ARCH_T e_processor_arch; IVD_SOC_T e_processor_soc; /** * Pictures that are are degraded * 0 : No degrade * 1 : Only on non-reference frames * 2 : Use interval specified by u4_nondegrade_interval * 3 : All non-key frames * 4 : All frames */ WORD32 i4_degrade_pics; /** * Interval for pictures which are completely decoded without any degradation */ WORD32 i4_nondegrade_interval; /** * bit position (lsb is zero): Type of degradation * 1 : Disable deblocking * 2 : Faster inter prediction filters * 3 : Fastest inter prediction filters */ WORD32 i4_degrade_type; /** Degrade pic count, Used to maintain the interval between non-degraded pics * */ WORD32 i4_degrade_pic_cnt; WORD32 i4_display_index; UWORD32 u4_pic_buf_got; /** * Col flag and mv pred buffer manager */ void *pv_mv_buf_mgr; /** * Picture buffer manager */ void *pv_pic_buf_mgr; /** * Display buffer manager */ void *pv_disp_buf_mgr; void *apv_buf_id_pic_buf_map[MAX_DISP_BUFS_NEW]; UWORD8 au1_pic_buf_id_mv_buf_id_map[MAX_DISP_BUFS_NEW]; UWORD8 au1_pic_buf_ref_flag[MAX_DISP_BUFS_NEW]; struct pic_buffer_t *ps_pic_buf_base; UWORD8 *pu1_ref_buff_base; col_mv_buf_t *ps_col_mv_base; void *(*pf_aligned_alloc)(void *pv_mem_ctxt, WORD32 alignment, WORD32 size); void (*pf_aligned_free)(void *pv_mem_ctxt, void *pv_buf); void *pv_mem_ctxt; UWORD8 *pu1_pic_buf_base; UWORD8 *pu1_mv_bank_buf_base; UWORD8 *pu1_init_dpb_base; ih264_default_weighted_pred_ft *pf_default_weighted_pred_luma; ih264_default_weighted_pred_ft *pf_default_weighted_pred_chroma; ih264_weighted_pred_ft *pf_weighted_pred_luma; ih264_weighted_pred_ft *pf_weighted_pred_chroma; ih264_weighted_bi_pred_ft *pf_weighted_bi_pred_luma; ih264_weighted_bi_pred_ft *pf_weighted_bi_pred_chroma; ih264_pad *pf_pad_top; ih264_pad *pf_pad_bottom; ih264_pad *pf_pad_left_luma; ih264_pad *pf_pad_left_chroma; ih264_pad *pf_pad_right_luma; ih264_pad *pf_pad_right_chroma; ih264_inter_pred_chroma_ft *pf_inter_pred_chroma; ih264_inter_pred_luma_ft *apf_inter_pred_luma[16]; ih264_intra_pred_luma_ft *apf_intra_pred_luma_16x16[4]; ih264_intra_pred_luma_ft *apf_intra_pred_luma_8x8[9]; ih264_intra_pred_luma_ft *apf_intra_pred_luma_4x4[9]; ih264_intra_pred_ref_filtering_ft *pf_intra_pred_ref_filtering; ih264_intra_pred_chroma_ft *apf_intra_pred_chroma[4]; ih264_iquant_itrans_recon_ft *pf_iquant_itrans_recon_luma_4x4; ih264_iquant_itrans_recon_ft *pf_iquant_itrans_recon_luma_4x4_dc; ih264_iquant_itrans_recon_ft *pf_iquant_itrans_recon_luma_8x8; ih264_iquant_itrans_recon_ft *pf_iquant_itrans_recon_luma_8x8_dc; ih264_iquant_itrans_recon_chroma_ft *pf_iquant_itrans_recon_chroma_4x4; ih264_iquant_itrans_recon_chroma_ft *pf_iquant_itrans_recon_chroma_4x4_dc; ih264_ihadamard_scaling_ft *pf_ihadamard_scaling_4x4; /** * deblock vertical luma edge with blocking strength 4 */ ih264_deblk_edge_bs4_ft *pf_deblk_luma_vert_bs4; /** * deblock vertical luma edge with blocking strength less than 4 */ ih264_deblk_edge_bslt4_ft *pf_deblk_luma_vert_bslt4; /** * deblock vertical luma edge with blocking strength 4 for mbaff */ ih264_deblk_edge_bs4_ft *pf_deblk_luma_vert_bs4_mbaff; /** * deblock vertical luma edge with blocking strength less than 4 for mbaff */ ih264_deblk_edge_bslt4_ft *pf_deblk_luma_vert_bslt4_mbaff; /** * deblock vertical chroma edge with blocking strength 4 */ ih264_deblk_chroma_edge_bs4_ft *pf_deblk_chroma_vert_bs4; /** * deblock vertical chroma edge with blocking strength less than 4 */ ih264_deblk_chroma_edge_bslt4_ft *pf_deblk_chroma_vert_bslt4; /** * deblock vertical chroma edge with blocking strength 4 for mbaff */ ih264_deblk_chroma_edge_bs4_ft *pf_deblk_chroma_vert_bs4_mbaff; /** * deblock vertical chroma edge with blocking strength less than 4 for mbaff */ ih264_deblk_chroma_edge_bslt4_ft *pf_deblk_chroma_vert_bslt4_mbaff; /** * deblock horizontal luma edge with blocking strength 4 */ ih264_deblk_edge_bs4_ft *pf_deblk_luma_horz_bs4; /** * deblock horizontal luma edge with blocking strength less than 4 */ ih264_deblk_edge_bslt4_ft *pf_deblk_luma_horz_bslt4; /** * deblock horizontal chroma edge with blocking strength 4 */ ih264_deblk_chroma_edge_bs4_ft *pf_deblk_chroma_horz_bs4; /** * deblock horizontal chroma edge with blocking strength less than 4 */ ih264_deblk_chroma_edge_bslt4_ft *pf_deblk_chroma_horz_bslt4; } dec_struct_t; #endif /* _H264_DEC_STRUCTS_H */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_tables.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ************************************************************************** * \file ih264d_tables.c * * \brief * Defination of all tables used by h264 decoder * * \date * 17/09/2004 * * \author MA ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_defs.h" #include "ih264d_tables.h" const UWORD8 gau1_ih264d_qp_scale_cr[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39 }; const UWORD8 gau1_ih264d_alpha_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 17, 20, 22, 25, 28, 32, 36, 40, 45, 50, 56, 63, 71, 80, 90, 101, 113, 127, 144, 162, 182, 203, 226, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; const UWORD8 gau1_ih264d_beta_table[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18 }; const UWORD8 gau1_ih264d_clip_table[][4] = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 1 }, { 0, 0, 0, 1 }, { 0, 0, 0, 1 }, { 0, 0, 0, 1 }, { 0, 0, 1, 1 }, { 0, 0, 1, 1 }, { 0, 1, 1, 1 }, { 0, 1, 1, 1 }, { 0, 1, 1, 1 }, { 0, 1, 1, 1 }, { 0, 1, 1, 2 }, { 0, 1, 1, 2 }, { 0, 1, 1, 2 }, { 0, 1, 1, 2 }, { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, { 0, 2, 2, 3 }, { 0, 2, 2, 4 }, { 0, 2, 3, 4 }, { 0, 2, 3, 4 }, { 0, 3, 3, 5 }, { 0, 3, 4, 6 }, { 0, 3, 4, 6 }, { 0, 4, 5, 7 }, { 0, 4, 5, 8 }, { 0, 4, 6, 9 }, { 0, 5, 7, 10 }, { 0, 6, 8, 11 }, { 0, 6, 8, 13 }, { 0, 7, 10, 14 }, { 0, 8, 11, 16 }, { 0, 9, 12, 18 }, { 0, 10, 13, 20 }, { 0, 11, 15, 23 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 }, { 0, 13, 17, 25 } }; const UWORD8 gau1_ih264d_clip_table_deblock[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51 }; /****************DEBLOCKING TABLES ENDS*******************/ /*************************************************************/ /* BS CALCULATION TABLES */ /*************************************************************/ UWORD32 const gau4_ih264d_packed_bs2[32] = { /*************************************************************/ /* BS TABLES FOR NORMAL EDGES */ /*************************************************************/ 0x00000000, 0x02000000, 0x00020000, 0x02020000, 0x00000200, 0x02000200, 0x00020200, 0x02020200, 0x00000002, 0x02000002, 0x00020002, 0x02020002, 0x00000202, 0x02000202, 0x00020202, 0x02020202, /*************************************************************/ /* BS TABLES FOR XTRA LEFT MB EDGES IN MBAFF CASE */ /*************************************************************/ 0x01010101, 0x02010101, 0x01020101, 0x02020101, 0x01010201, 0x02010201, 0x01020201, 0x02020201, 0x01010102, 0x02010102, 0x01020102, 0x02020102, 0x01010202, 0x02010202, 0x01020202, 0x02020202, }; UWORD16 const gau2_ih264d_4x4_v2h_reorder[16] = { 0x0000, 0x0001, 0x0010, 0x0011, 0x0100, 0x0101, 0x0110, 0x0111, 0x1000, 0x1001, 0x1010, 0x1011, 0x1100, 0x1101, 0x1110, 0x1111 }; /****************SCALING TABLES STARTS *****************/ const WORD16 gai2_ih264d_default_intra4x4[16] = { 6, 13, 13, 20, 20, 20, 28, 28, 28, 28, 32, 32, 32, 37, 37, 42 }; const WORD16 gai2_ih264d_default_inter4x4[16] = { 10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34 }; const WORD16 gai2_ih264d_default_intra8x8[64] = { 6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23, 23, 23, 23, 23, 23, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31, 31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42 }; const WORD16 gai2_ih264d_default_inter8x8[64] = { 9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35 }; const WORD16 gai2_ih264d_flat_4x4[16] = { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }; const WORD16 gai2_ih264d_flat_8x8[64] = { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }; /****************SCALING TABLES ENDS *****************/ /*Inverse scan tables for individual 4x4 blocks of 8x8 transform coeffs of CAVLC */ /* progressive */ const UWORD8 gau1_ih264d_inv_scan_prog8x8_cavlc[4][16] = { { 0, 9, 17, 18, 12, 40, 27, 7, 35, 57, 29, 30, 58, 38, 53, 47 }, /* for First subblock */ { 1, 2, 24, 11, 19, 48, 20, 14, 42, 50, 22, 37, 59, 31, 60, 55 }, /* for second subblock */ { 8, 3, 32, 4, 26, 41, 13, 21, 49, 43, 15, 44, 52, 39, 61, 62 }, /* for third subblock */ { 16, 10, 25, 5, 33, 34, 6, 28, 56, 36, 23, 51, 45, 46, 54, 63 } /* for fourth subblock */ }; const UWORD8 gau1_ih264d_inv_scan_int8x8_cavlc[4][16] = { { 0, 9, 2, 56, 18, 26, 34, 27, 35, 28, 36, 29, 45, 7, 54, 39 }, /* for First subblock */ { 8, 24, 25, 33, 41, 11, 42, 12, 43, 13, 44, 14, 53, 15, 62, 47 }, /* for second subblock */ { 16, 32, 40, 10, 49, 4, 50, 5, 51, 6, 52, 22, 61, 38, 23, 55 }, /* for third subblock */ { 1, 17, 48, 3, 57, 19, 58, 20, 59, 21, 60, 37, 30, 46, 31, 63 } /* for fourth subblock */ }; /*Inverse scan tables for individual 8x8 blocks of 8x8 transform coeffs of CABAC */ /* progressive */ const UWORD8 gau1_ih264d_inv_scan_prog8x8_cabac[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; /* interlace */ const UWORD8 gau1_ih264d_inv_scan_int8x8_cabac[64] = { 0, 8, 16, 1, 9, 24, 32, 17, 2, 25, 40, 48, 56, 33, 10, 3, 18, 41, 49, 57, 26, 11, 4, 19, 34, 42, 50, 58, 27, 12, 5, 20, 35, 43, 51, 59, 28, 13, 6, 21, 36, 44, 52, 60, 29, 14, 22, 37, 45, 53, 61, 30, 7, 15, 38, 46, 54, 62, 23, 31, 39, 47, 55, 63 }; /****************PARSING TABLES *******************/ UWORD8 const gau1_ih264d_subblk_offset[16] = { 8, 9, 12, 13, 10, 11, 14, 15, 16, 17, 20, 21, 18, 19, 22, 23 }; const UWORD8 gau1_ih264d_cbp_tab[6] = { 0, 16, 32, 15, 31, 47 }; /** gives CBP value from codeword number, both for intra and inter */ const UWORD8 gau1_ih264d_cbp_table[48][2] = { { 47, 0 }, { 31, 16 }, { 15, 1 }, { 0, 2 }, { 23, 4 }, { 27, 8 }, { 29, 32 }, { 30, 3 }, { 7, 5 }, { 11, 10 }, { 13, 12 }, { 14, 15 }, { 39, 47 }, { 43, 7 }, { 45, 11 }, { 46, 13 }, { 16, 14 }, { 3, 6 }, { 5, 9 }, { 10, 31 }, { 12, 35 }, { 19, 37 }, { 21, 42 }, { 26, 44 }, { 28, 33 }, { 35, 34 }, { 37, 36 }, { 42, 40 }, { 44, 39 }, { 1, 43 }, { 2, 45 }, { 4, 46 }, { 8, 17 }, { 17, 18 }, { 18, 20 }, { 20, 24 }, { 24, 19 }, { 6, 21 }, { 9, 26 }, { 22, 28 }, { 25, 23 }, { 32, 27 }, { 33, 29 }, { 34, 30 }, { 36, 22 }, { 40, 25 }, { 38, 38 }, { 41, 41 }, }; /****************PARSING TABLES ENDS *******************/ /****************DECODE SLICE TABLES STARTS *******************/ /*Definition of Tables needed by functions of this file */ const UWORD8 gau1_ih264d_inv_scan[16] = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; const UWORD8 gau1_ih264d_inv_scan_fld[16] = { 0, 4, 1, 8, 12, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 }; const UWORD8 gau1_ih264d_dequant_matrix[6][16] = { { 10, 13, 10, 13, 13, 16, 13, 16, 10, 13, 10 ,13, 13, 16, 13, 16}, { 11, 14, 11, 14, 14, 18, 14, 18, 11, 14, 11 ,14, 14, 18, 14, 18}, { 13, 16, 13, 16, 16, 20, 16, 20, 13, 16, 13 ,16, 16, 20, 16, 20}, { 14, 18, 14, 18, 18, 23, 18, 23, 14, 18, 14, 18, 18, 23, 18, 23}, { 16, 20, 16, 20, 20, 25, 20, 25, 16, 20, 16, 20, 20, 25, 20, 25}, { 18, 23, 18, 23, 23, 29, 23, 29, 18, 23, 18, 23, 23, 29, 23, 29} }; const UWORD16 gau2_ih264_iquant_scale_4x4[6][16] = { { 10, 13, 10, 13, 13, 16, 13, 16, 10, 13, 10, 13, 13, 16, 13, 16 }, { 11, 14, 11, 14, 14, 18, 14, 18, 11, 14, 11, 14, 14, 18, 14, 18 }, { 13, 16, 13, 16, 16, 20, 16, 20, 13, 16, 13, 16, 16, 20, 16, 20 }, { 14, 18, 14, 18, 18, 23, 18, 23, 14, 18, 14, 18, 18, 23, 18, 23 }, { 16, 20, 16, 20, 20, 25, 20, 25, 16, 20, 16, 20, 20, 25, 20, 25 }, { 18, 23, 18, 23, 23, 29, 23, 29, 18, 23, 18, 23, 23, 29, 23, 29 } }; const UWORD8 gau1_ih264d_dequant8x8_zigzag_cavlc[4][6][16] = { { { 20, 18, 24, 32, 19, 19, 18, 19, 19, 18, 18, 24, 24, 25, 24, 18 }, /* for First subblock */ { 22, 19, 26, 35, 21, 21, 19, 21, 21, 19, 19, 26, 26, 28, 26, 19 }, { 26, 23, 31, 42, 24, 24, 23, 24, 24, 23, 23, 31, 31, 33, 31, 23 }, { 28, 25, 33, 45, 26, 26, 25, 26, 26, 25, 25, 33, 33, 35, 33, 25 }, { 32, 28, 38, 51, 30, 30, 28, 30, 30, 28, 28, 38, 38, 40, 38, 28 }, { 36, 32, 43, 58, 34, 34, 32, 34, 34, 32, 32, 43, 43, 46, 43, 32 } }, { { 19, 25, 19, 18, 24, 25, 25, 24, 24, 32, 32, 19, 18, 18, 19, 24 }, /* for second subblock */ { 21, 28, 21, 19, 26, 28, 28, 26, 26, 35, 35, 21, 19, 19, 21, 26 }, { 24, 33, 24, 23, 31, 33, 33, 31, 31, 42, 42, 24, 23, 23, 24, 31 }, { 26, 35, 26, 25, 33, 35, 35, 33, 33, 45, 45, 26, 25, 25, 26, 33 }, { 30, 40, 30, 28, 38, 40, 40, 38, 38, 51, 51, 30, 28, 28, 30, 38 }, { 34, 46, 34, 32, 43, 46, 46, 43, 43, 58, 58, 34, 32, 32, 34, 43 } }, { { 19, 19, 20, 20, 24, 18, 18, 24, 24, 18, 18, 19, 25, 19, 18, 24 }, /* for third subblock */ { 21, 21, 22, 22, 26, 19, 19, 26, 26, 19, 19, 21, 28, 21, 19, 26 }, { 24, 24, 26, 26, 31, 23, 23, 31, 31, 23, 23, 24, 33, 24, 23, 31 }, { 26, 26, 28, 28, 33, 25, 25, 33, 33, 25, 25, 26, 35, 26, 25, 33 }, { 30, 30, 32, 32, 38, 28, 28, 38, 38, 28, 28, 30, 40, 30, 28, 38 }, { 34, 34, 36, 36, 43, 32, 32, 43, 43, 32, 32, 34, 46, 34, 32, 43 } }, { { 25, 24, 18, 19, 19, 25, 25, 19, 19, 20, 24, 24, 18, 24, 32, 18 }, /* for fourth subblock */ { 28, 26, 19, 21, 21, 28, 28, 21, 21, 22, 26, 26, 19, 26, 35, 19 }, { 33, 31, 23, 24, 24, 33, 33, 24, 24, 26, 31, 31, 23, 31, 42, 23 }, { 35, 33, 25, 26, 26, 35, 35, 26, 26, 28, 33, 33, 25, 33, 45, 25 }, { 40, 38, 28, 30, 30, 40, 40, 30, 30, 32, 38, 38, 28, 38, 51, 28 }, { 46, 43, 32, 34, 34, 46, 46, 34, 34, 36, 43, 43, 32, 43, 58, 32 } } }; const UWORD16 gau1_ih264d_dequant8x8_cavlc[6][64] = { { 20, 19, 25, 19, 20, 19, 25, 19, 19, 18, 24, 18, 19, 18, 24, 18, 25, 24, 32, 24, 25, 24, 32, 24, 19, 18, 24, 18, 19, 18, 24, 18, 20, 19, 25, 19, 20, 19, 25, 19, 19, 18, 24, 18, 19, 18, 24, 18, 25, 24, 32, 24, 25, 24, 32, 24, 19, 18, 24, 18, 19, 18, 24, 18 }, { 22, 21, 28, 21, 22, 21, 28, 21, 21, 19, 26, 19, 21, 19, 26, 19, 28, 26, 35, 26, 28, 26, 35, 26, 21, 19, 26, 19, 21, 19, 26, 19, 22, 21, 28, 21, 22, 21, 28, 21, 21, 19, 26, 19, 21, 19, 26, 19, 28, 26, 35, 26, 28, 26, 35, 26, 21, 19, 26, 19, 21, 19, 26, 19 }, { 26, 24, 33, 24, 26, 24, 33, 24, 24, 23, 31, 23, 24, 23, 31, 23, 33, 31, 42, 31, 33, 31, 42, 31, 24, 23, 31, 23, 24, 23, 31, 23, 26, 24, 33, 24, 26, 24, 33, 24, 24, 23, 31, 23, 24, 23, 31, 23, 33, 31, 42, 31, 33, 31, 42, 31, 24, 23, 31, 23, 24, 23, 31, 23 }, { 28, 26, 35, 26, 28, 26, 35, 26, 26, 25, 33, 25, 26, 25, 33, 25, 35, 33, 45, 33, 35, 33, 45, 33, 26, 25, 33, 25, 26, 25, 33, 25, 28, 26, 35, 26, 28, 26, 35, 26, 26, 25, 33, 25, 26, 25, 33, 25, 35, 33, 45, 33, 35, 33, 45, 33, 26, 25, 33, 25, 26, 25, 33, 25 }, { 32, 30, 40, 30, 32, 30, 40, 30, 30, 28, 38, 28, 30, 28, 38, 28, 40, 38, 51, 38, 40, 38, 51, 38, 30, 28, 38, 28, 30, 28, 38, 28, 32, 30, 40, 30, 32, 30, 40, 30, 30, 28, 38, 28, 30, 28, 38, 28, 40, 38, 51, 38, 40, 38, 51, 38, 30, 28, 38, 28, 30, 28, 38, 28 }, { 36, 34, 46, 34, 36, 34, 46, 34, 34, 32, 43, 32, 34, 32, 43, 32, 46, 43, 58, 43, 46, 43, 58, 43, 34, 32, 43, 32, 34, 32, 43, 32, 36, 34, 46, 34, 36, 34, 46, 34, 34, 32, 43, 32, 34, 32, 43, 32, 46, 43, 58, 43, 46, 43, 58, 43, 34, 32, 43, 32, 34, 32, 43, 32 }, }; /****************DECODE SLICE TABLES ENDS *******************/ /****************MOTION VECTOR DECODING TABLES STARTS *******************/ /** ************************************************************************** * \brief This array is used to evaluate the condition when only one of * predictor subMbs has a reference frame equal to that of E subMb. ************************************************************************** */ const WORD8 gau1_ih264d_mv_pred_condition[] = { -1, 0, 1, -1, 2, -1, -1, -1 }; /** Number of subMbs for the 8x8 prediction mode */ const UWORD8 gau1_ih264d_num_submb_part[] = { 1, 2, 2, 4 }; /** Width of the 8x8 prediction mode in terms of subMbs */ const UWORD8 gau1_ih264d_submb_partw[] = { 2, 2, 1, 1 }; /** Height of the 8x8 prediction mode in terms of subMbs */ const UWORD8 gau1_ih264d_submb_parth[] = { 2, 1, 2, 1 }; /** Number of MB partitions for the MB prediction mode */ const UWORD8 gau1_ih264d_num_mb_part[] = { 1, 2, 2, 4 }; /** Width of the MB partition in terms of subMbs */ const UWORD8 gau1_ih264d_mb_partw[] = { 4, 4, 2, 2, 2 }; /** Height of the MB partition in terms of subMbs */ const UWORD8 gau1_ih264d_mb_parth[] = { 4, 2, 4, 2, 2 }; /** MB partition information is packed into a UWORD32 {0,number,width,height} */ const UWORD32 gau4_ih264d_submb_part[] = { 0x00010202, 0x00020201, 0x00020102, 0x00040101 }; const UWORD8 gau1_ih264d_submb_indx_mod[] = { 0, 0, /* 16x16 */ 0, 8, /* 16x8 */ 0, 2, /* 8x16 */ 0, 0, /* 8x8 */ 0, 4, /* 8x4 */ 0, 1, /* 4x8 */ 0, 1, 3, 1 /* 4x4 */ }; /** This table is used to assign CBPs to Inter MBs. */ const UWORD8 gau1_ih264d_cbp_inter[] = { 0, 16, 1, 2, 4, 8, 32, 3, 5, 10, 12, 15, 47, 7, 11, 13, 14, 6, 9, 31, 35, 37, 42, 44, 33, 34, 36, 40, 39, 43, 45, 46, 17, 18, 20, 24, 19, 21, 26, 28, 23, 27, 29, 30, 22, 25, 38, 41 }; /** Motion comp modes for P followed by B, 0 to 4 : P Mbs 5 to 27 : B Mbs 28 to 30 : DIRECT */ const UWORD8 gau1_ih264d_mb_mc_mode[] = { PRED_16x16, PRED_16x8, PRED_8x16, PRED_8x8, PRED_8x8R0, PRED_16x16, PRED_16x16, PRED_16x16, PRED_16x16, PRED_16x8, PRED_8x16, PRED_16x8, PRED_8x16, PRED_16x8, PRED_8x16, PRED_16x8, PRED_8x16, PRED_16x8, PRED_8x16, PRED_16x8, PRED_8x16, PRED_16x8, PRED_8x16, PRED_16x8, PRED_8x16, PRED_16x8, PRED_8x16, PRED_8x8, /* Self defined modes for B_SKIP and DIRECT16x16 */ PRED_8x8, PRED_8x8, PRED_8x8 }; const UWORD8 gau1_ih264d_submb_mc_mode[] = { SUBMB_8x8, SUBMB_8x4, SUBMB_4x8, SUBMB_4x4, SUBMB_8x8, SUBMB_8x8, SUBMB_8x8, SUBMB_8x8, SUBMB_8x4, SUBMB_4x8, SUBMB_8x4, SUBMB_4x8, SUBMB_8x4, SUBMB_4x8, SUBMB_4x4, SUBMB_4x4, SUBMB_4x4, /* Self defined modes B DIRECT8x8 */ SUBMB_4x4, SUBMB_4x4, SUBMB_4x4 }; /** Sub MB pred modes for B slice */ const UWORD8 gau1_ih264d_submb_pred_modes[] = { PRED_L0, PRED_L0, PRED_L0, PRED_L0, B_DIRECT, PRED_L0, PRED_L1, BI_PRED, PRED_L0, PRED_L0, PRED_L1, PRED_L1, BI_PRED, BI_PRED, PRED_L0, PRED_L1, BI_PRED, /* Self defined modes for B DIRECT8x8 */ BI_PRED, PRED_L0, PRED_L1, }; /** MB pred modes for P and B slice */ const WORD8 gau1_ih264d_mb_pred_modes[2][32] = { { PRED_L0, PRED_L0, PRED_L0, PRED_INVALID, PRED_INVALID, B_DIRECT, PRED_L0, PRED_L1, BI_PRED, PRED_L0, PRED_L0, PRED_L1, PRED_L1, PRED_L0, PRED_L0, PRED_L1, PRED_L1, PRED_L0, PRED_L0, PRED_L1, PRED_L1, BI_PRED, BI_PRED, BI_PRED, BI_PRED, BI_PRED, BI_PRED, PRED_INVALID, /* Self defined modes for B_SKIP and DIRECT16x16 */ BI_PRED, PRED_L0, PRED_L1, }, { PRED_INVALID, PRED_L0, PRED_L0, PRED_INVALID, PRED_INVALID, PRED_INVALID, PRED_INVALID, PRED_INVALID, PRED_INVALID, PRED_L0, PRED_L0, PRED_L1, PRED_L1, PRED_L1, PRED_L1, PRED_L0, PRED_L0, BI_PRED, BI_PRED, BI_PRED, BI_PRED, PRED_L0, PRED_L0, PRED_L1, PRED_L1, BI_PRED, BI_PRED, PRED_INVALID, /* Self defined modes for B_SKIP and DIRECT16x16 */ PRED_INVALID, PRED_INVALID, PRED_INVALID } }; /****************MOTION VECTOR DECODING TABLES ENDS *******************/ /****************CAVLC DECODING TABLES STARTS *******************/ /*****************************************************************************/ /* 6 Bit table look for total zeros (totalcoeff = 2to10) as in Table 9.7 */ /* of H264 standard. In each table entry, lower 4 bits represent total zeros */ /* decoded while upper 4 bit represent the bits to be flushed from ps_bitstrm */ /*****************************************************************************/ const UWORD8 gau1_ih264d_table_total_zero_2to10[9][64] = { /* For total coeff = 2 */ { 0x6E, 0x6D, 0x6C, 0x6B, 0x5A, 0x5A, 0x59, 0x59, 0x48, 0x48, 0x48, 0x48, 0x47, 0x47, 0x47, 0x47, 0x46, 0x46, 0x46, 0x46, 0x45, 0x45, 0x45, 0x45, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, }, /* For total coeff = 3 */ { 0x6D, 0x6B, 0x5C, 0x5C, 0x5A, 0x5A, 0x59, 0x59, 0x48, 0x48, 0x48, 0x48, 0x45, 0x45, 0x45, 0x45, 0x44, 0x44, 0x44, 0x44, 0x40, 0x40, 0x40, 0x40, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, }, /* For total coeff = 4 */ { 0x5C, 0x5C, 0x5B, 0x5B, 0x5A, 0x5A, 0x50, 0x50, 0x49, 0x49, 0x49, 0x49, 0x47, 0x47, 0x47, 0x47, 0x43, 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x42, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, }, /* For total coeff = 5 */ { 0x5B, 0x5B, 0x59, 0x59, 0x4A, 0x4A, 0x4A, 0x4A, 0x48, 0x48, 0x48, 0x48, 0x42, 0x42, 0x42, 0x42, 0x41, 0x41, 0x41, 0x41, 0x40, 0x40, 0x40, 0x40, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, }, /* For total coeff = 6 */ { 0x6A, 0x60, 0x51, 0x51, 0x48, 0x48, 0x48, 0x48, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, }, /* For total coeff = 7 */ { 0x69, 0x60, 0x51, 0x51, 0x47, 0x47, 0x47, 0x47, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, }, /* For total coeff = 8 */ { 0x68, 0x60, 0x52, 0x52, 0x41, 0x41, 0x41, 0x41, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, }, /* For total coeff = 9 */ { 0x61, 0x60, 0x57, 0x57, 0x42, 0x42, 0x42, 0x42, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, }, /* For total coeff = 10 */ { 0x51, 0x51, 0x50, 0x50, 0x46, 0x46, 0x46, 0x46, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, } }; /*****************************************************************************/ /* 4 Bit table look for total zeros (totalcoeff = 11to15) as in Table 9.7 */ /* of H264 standard. In each table entry, lower 4 bits represent total zeros */ /* decoded while upper 4 bit represent the bits to be flushed from ps_bitstrm */ /*****************************************************************************/ const UWORD8 gau1_ih264d_table_total_zero_11to15[5][16] = { /* For total coeff = 11 */ { 0x40, 0x41, 0x32, 0x32, 0x33, 0x33, 0x35, 0x35, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, }, /* For total coeff = 12 */ { 0x40, 0x41, 0x34, 0x34, 0x22, 0x22, 0x22, 0x22, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, }, /* For total coeff = 13 */ { 0x30, 0x30, 0x31, 0x31, 0x23, 0x23, 0x23, 0x23, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, }, /* For total coeff = 14 */ { 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, }, /* For total coeff = 15 */ { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, }, }; /** Tables used to read "Run Before", Below tables are packed to reduce lookups */ /** (Base addess of Gx << 2) + (Max code length for that Gx) */ const UWORD8 gau1_ih264d_table_run_before[64] = { 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 1, 1, 1, 1, 10, 10, 6, 6, 1, 1, 1, 1, 14, 14, 10, 10, 6, 6, 2, 2, 19, 15, 10, 10, 6, 6, 2, 2, 23, 19, 15, 11, 6, 6, 2, 2, 7, 11, 19, 15, 27, 23, 2, 2, 27, 27, 23, 19, 15, 11, 7, 3 }; /*****************************************************************************/ /* Lookup table for CAVLC 4x4 total_coeff,trailing_ones as pers Table 9-5 */ /* in the standard. Starting form lsb first 2 bits=flushbits, next 2bits= */ /* trailing ones, next 5 bits=total_coeff. Total bits used = 9 out of 16 */ /*****************************************************************************/ const UWORD16 gau2_ih264d_code_gx[304] = { /* Lookup for 0 <= nC < 2 */ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0026, 0x0026, 0x0012, 0x0012, 0x003D, 0x003D, 0x003D, 0x003D, 0x005E, 0x005E, 0x003A, 0x003A, 0x004D, 0x004D, 0x004D, 0x004D, 0x006E, 0x006E, 0x004A, 0x004A, 0x0036, 0x0036, 0x0022, 0x0022, 0x007E, 0x007E, 0x005A, 0x005A, 0x0046, 0x0046, 0x0032, 0x0032, 0x008E, 0x008E, 0x006A, 0x006A, 0x0056, 0x0056, 0x0042, 0x0042, 0x009E, 0x009E, 0x007A, 0x007A, 0x0066, 0x0066, 0x0052, 0x0052, 0x0083, 0x009B, 0x0087, 0x0073, 0x00AF, 0x008B, 0x0077, 0x0063, 0x00CF, 0x00BB, 0x00A7, 0x00A3, 0x00BF, 0x00AB, 0x0097, 0x0093, 0x00EF, 0x00DB, 0x00C7, 0x00C3, 0x00DF, 0x00CB, 0x00B7, 0x00B3, 0x010F, 0x00FB, 0x00F7, 0x00E3, 0x00FF, 0x00EB, 0x00E7, 0x00D3, 0x0102, 0x0102, 0x010A, 0x010A, 0x0106, 0x0106, 0x00F2, 0x00F2, 0x00D4, 0x00D4, 0x00D4, 0x00D4, 0x00D4, 0x00D4, 0x00D4, 0x00D4, /* Lookup for 2 <= nC < 4 */ 0x0015, 0x0015, 0x0015, 0x0015, 0x0001, 0x0001, 0x0001, 0x0001, 0x004E, 0x004E, 0x003E, 0x003E, 0x0029, 0x0029, 0x0029, 0x0029, 0x006F, 0x003B, 0x0037, 0x0013, 0x005E, 0x005E, 0x0026, 0x0026, 0x007E, 0x007E, 0x004A, 0x004A, 0x0046, 0x0046, 0x0022, 0x0022, 0x008E, 0x008E, 0x005A, 0x005A, 0x0056, 0x0056, 0x0032, 0x0032, 0x0052, 0x0052, 0x006A, 0x006A, 0x0066, 0x0066, 0x0042, 0x0042, 0x009E, 0x009E, 0x007A, 0x007A, 0x0076, 0x0076, 0x0062, 0x0062, 0x00BF, 0x009B, 0x0097, 0x0083, 0x00AF, 0x008B, 0x0087, 0x0073, 0x00B3, 0x00BB, 0x00B7, 0x00A3, 0x00CF, 0x00AB, 0x00A7, 0x0093, 0x00EF, 0x00DB, 0x00D7, 0x00D3, 0x00DF, 0x00CB, 0x00C7, 0x00C3, 0x00F7, 0x00F3, 0x00FB, 0x00E7, 0x00EA, 0x00EA, 0x00E2, 0x00E2, 0x010E, 0x010E, 0x010A, 0x010A, 0x0106, 0x0106, 0x0102, 0x0102, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FC, 0x00FC, /* Lookup for 4 <= nC < 8 */ 0x007F, 0x006F, 0x005F, 0x004F, 0x003F, 0x002B, 0x0017, 0x0003, 0x0057, 0x005B, 0x0047, 0x004B, 0x0037, 0x008F, 0x003B, 0x0027, 0x0033, 0x007B, 0x0077, 0x0023, 0x009F, 0x006B, 0x0067, 0x0013, 0x0073, 0x0063, 0x009B, 0x0053, 0x00AF, 0x008B, 0x0087, 0x0043, 0x00CF, 0x00BB, 0x00A7, 0x0093, 0x00BF, 0x00AB, 0x0097, 0x0083, 0x00C3, 0x00DB, 0x00C7, 0x00B3, 0x00DF, 0x00CB, 0x00B7, 0x00A3, 0x00F7, 0x00E3, 0x00EF, 0x00EB, 0x00E7, 0x00D3, 0x00D6, 0x00D6, 0x0106, 0x0106, 0x00F2, 0x00F2, 0x00FE, 0x00FE, 0x00FA, 0x00FA, 0x010D, 0x010D, 0x010D, 0x010D, 0x0109, 0x0109, 0x0109, 0x0109, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100 }; /*****************************************************************************/ /* Lookup table for CAVLC ChromaDC total_coeff,trailing_ones parsing as per */ /* Table 9-5 in the standard. Starting from msb, First 4bits=total_coeff, */ /* next 2bits=trailing_ones and last 2bits=flushbits-1 */ /*****************************************************************************/ const UWORD8 gau1_ih264d_cav_chromdc_vld[256] = { 0x9E, 0x9E, 0x97, 0x8F, 0x76, 0x76, 0x6E, 0x6E, 0x85, 0x85, 0x85, 0x85, 0x65, 0x65, 0x65, 0x65, 0x45, 0x45, 0x45, 0x45, 0x7D, 0x7D, 0x7D, 0x7D, 0x4D, 0x4D, 0x4D, 0x4D, 0x25, 0x25, 0x25, 0x25, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }; const UWORD16 gau2_ih264d_offset_num_vlc_tab[9] = { 0, 0, 120, 120, 224, 224, 224, 224, 224 }; /*****************************************************************************/ /* Function pointer u4_ofst table lookup for parsing 4x4 residual blocks in */ /* CAVLC. The u4_ofst is dependent on total coeffs coded */ /*****************************************************************************/ const UWORD8 gau1_ih264d_total_coeff_fn_ptr_offset[16] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2 }; /****************************************************************************/ /* gai2_ih264d_trailing_one_level lookup tables based on trailing one bits */ /* All zeroes are u2_dummy in the table are u2_dummy to keep 3 uniform elements */ /****************************************************************************/ const WORD16 gai2_ih264d_trailing_one_level[14][3] = { /* All zeroes are u2_dummy */ /**********************************************************************/ /* Levels for trailing ones = 1, bits read can be 0 or 1 */ /**********************************************************************/ { 1, 0, 0 }, /* 0 */ { -1, 0, 0 }, /* 1 */ /**********************************************************************/ /* Levels for trailing ones = 2, bits read can be 00, 01, 10 ,11 */ /**********************************************************************/ { 1, 1, 0 }, /* 00 */ { 1, -1, 0 }, /* 01 */ { -1, 1, 0 }, /* 10 */ { -1, -1, 0 }, /* 11 */ /**********************************************************************/ /* Levels for trailing ones = 3, bits read can be 000 - 111 */ /**********************************************************************/ { 1, 1, 1 }, /* 000 */ { 1, 1, -1 }, /* 001 */ { 1, -1, 1 }, /* 010 */ { 1, -1, -1 }, /* 011 */ { -1, 1, 1 }, /* 100 */ { -1, 1, -1 }, /* 101 */ { -1, -1, 1 }, /* 110 */ { -1, -1, -1 }, /* 111 */ }; /****************CAVLC DECODING TABLES ENDS *******************/ /****************************************************************************/ /* These are the codes used for error detection in intra pred4x4 modes */ /****************************************************************************/ const UWORD8 gau1_ih264d_intra_pred_err_code[9] = { 2, 1, 0, 2, 3, 3, 3, 2, 1 }; /* Number of users for top field , bottom field, which field needs to be */ /* displayed first */ const UWORD8 gau1_ih264d_sei_fld_usage[9][3] = { { 1, 1, DISP_FLD_FIRST_UNDEF }, { 1, 0, DISP_TOP_FLD_FIRST }, { 0, 1, DISP_BOT_FLD_FIRST }, { 1, 1, DISP_TOP_FLD_FIRST }, { 1, 1, DISP_BOT_FLD_FIRST }, { 2, 1, DISP_TOP_FLD_FIRST }, { 1, 2, DISP_BOT_FLD_FIRST }, { 2, 2, DISP_FLD_FIRST_UNDEF }, { 3, 3, DISP_FLD_FIRST_UNDEF } }; /*****************************************************************/ /* Context increment for significant coefficient(CABAC) */ /* Requires only 63 elements. But the last element with value -1 */ /* is kept to make it 64 */ /*****************************************************************/ const UWORD8 gau1_ih264d_sigcoeff_context_inc_frame[64] = { 0, 1, 2, 3, 4, 5, 5, 4, 4, 3, 3, 4, 4, 4, 5, 5, 4, 4, 4, 4, 3, 3, 6, 7, 7, 7, 8, 9, 10, 9, 8, 7, 7, 6, 11, 12, 13, 11, 6, 7, 8, 9, 14, 10, 9, 8, 6, 11, 12, 13, 11, 6, 9, 14, 10, 9, 11, 12, 13, 11, 14, 10, 12, -1 }; const UWORD8 gau1_ih264d_sigcoeff_context_inc_field[64] = { 0, 1, 1, 2, 2, 3, 3, 4, 5, 6, 7, 7, 7, 8, 4, 5, 6, 9, 10, 10, 8, 11, 12, 11, 9, 9, 10, 10, 8, 11, 12, 11, 9, 9, 10, 10, 8, 11, 12, 11, 9, 9, 10, 10, 8, 13, 13, 9, 9, 10, 10, 8, 13, 13, 9, 9, 10, 10, 14, 14, 14, 14, 14, -1 }; const UWORD8 gau1_ih264d_lastcoeff_context_inc[64] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, -1 }; /*! ************************************************************************** * \brief gau1_ih264d_top_left_mb_part_indx_mod * * SubBlk number of the top left subBlk in each of the MB partition * (16x16, 16x8, 8x16, 8x8) ************************************************************************** */ const UWORD8 gau1_ih264d_top_left_mb_part_indx_mod[] = { 0, 0 /* Junk */, /* 16x16 */ 0, 8, /* 16x8 */ 0, 2, /* 8x16 */ 0, 2, 8, 10 /* 8x8 */, 0 /* One extra entry is read at the end of loop, but not used */ }; /*! ************************************************************************** * \brief gau1_ih264d_submb_indx_mod_sp_drct * * Contains increments to the subBlk num in a given subMb partition. ************************************************************************** */ const UWORD8 gau1_ih264d_submb_indx_mod_sp_drct[] = { 0, 0 /* Junk */, /* 8x8 */ 0, 4, /* 8x4 */ 0, 1, /* 4x8 */ 0, 1, 3, 1 /* 4x4 */ }; ================================================ FILE: dependencies/ih264d/decoder/ih264d_tables.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_TABLES_H_ #define _IH264D_TABLES_H_ /** ************************************************************************** * \file ih264d_tables.h * * \brief * Declaration of all tables used by h264 decoder * * \date * 17/09/2004 * * \author MA ************************************************************************** */ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_cabac.h" /*Deblocking Table declaration*/ extern const UWORD8 gau1_ih264d_qp_scale_cr[]; extern const UWORD8 gau1_ih264d_alpha_table[]; extern const UWORD8 gau1_ih264d_clip_table_deblock[]; extern const UWORD8 gau1_ih264d_beta_table[]; extern const UWORD8 gau1_ih264d_clip_table[][4]; /*Parsing Table declaration*/ extern const UWORD8 gau1_ih264d_cbp_tab[6]; extern const UWORD32 gau4_ih264d_packed_bs2[32]; extern const UWORD16 gau2_ih264d_4x4_v2h_reorder[16]; extern const UWORD8 gau1_ih264d_subblk_offset[16]; extern const UWORD8 gau1_ih264d_cbp_table[48][2]; /*Decode Slice Table declaration*/ extern const UWORD8 gau1_ih264d_inv_scan[16]; extern const UWORD8 gau1_ih264d_inv_scan_fld[16]; extern const UWORD8 gau1_ih264d_dequant_matrix[6][16]; extern const UWORD16 gau2_ih264_iquant_scale_4x4[6][16]; extern const UWORD8 gau1_ih264d_dequant8x8_zigzag_cavlc[4][6][16]; extern const UWORD16 gau1_ih264d_dequant8x8_cavlc[6][64]; extern const UWORD8 gau1_ih264d_inv_scan_prog8x8_cavlc[4][16]; extern const UWORD8 gau1_ih264d_inv_scan_int8x8_cavlc[4][16]; extern const UWORD8 gau1_ih264d_inv_scan_prog8x8_cabac[64]; extern const UWORD8 gau1_ih264d_inv_scan_int8x8_cabac[64]; extern const UWORD8 gau1_ih264d_lastcoeff_context_inc[64]; extern const UWORD8 gau1_ih264d_sigcoeff_context_inc_frame[64]; extern const UWORD8 gau1_ih264d_sigcoeff_context_inc_field[64]; /* scaling related table declaration */ extern const WORD16 gai2_ih264d_default_intra4x4[16]; extern const WORD16 gai2_ih264d_default_inter4x4[16]; extern const WORD16 gai2_ih264d_default_intra8x8[64]; extern const WORD16 gai2_ih264d_default_inter8x8[64]; extern const WORD16 gai2_ih264d_flat_4x4[16]; extern const WORD16 gai2_ih264d_flat_8x8[64]; /*Decode MV Table declaration*/ extern const WORD8 gau1_ih264d_mv_pred_condition[]; /** Number of subMbs for the 8x8 prediction mode */ extern const UWORD8 gau1_ih264d_num_submb_part[]; /** Width of the 8x8 prediction mode in terms of subMbs */ extern const UWORD8 gau1_ih264d_submb_partw[]; /** Height of the 8x8 prediction mode in terms of subMbs */ extern const UWORD8 gau1_ih264d_submb_parth[]; /** Number of MB partitions for the MB prediction mode */ extern const UWORD8 gau1_ih264d_num_mb_part[]; /** Width of the MB partition in terms of subMbs */ extern const UWORD8 gau1_ih264d_mb_partw[]; /** Height of the MB partition in terms of subMbs */ extern const UWORD8 gau1_ih264d_mb_parth[]; /** MB partition information is packed into a UWORD32 {0,number,width,height} */ extern const UWORD32 gau4_ih264d_submb_part[]; extern const UWORD8 gau1_ih264d_submb_indx_mod[]; /** This table is used to assign CBPs to Inter MBs. */ extern const UWORD8 gau1_ih264d_cbp_inter[]; /** Motion comp modes for P followed by B, 0 to 4 : P Mbs 5 to 27 : B Mbs 28 to 30 : DIRECT */ extern const UWORD8 gau1_ih264d_mb_mc_mode[]; extern const UWORD8 gau1_ih264d_submb_mc_mode[]; /** Sub MB pred modes for B slice */ extern const UWORD8 gau1_ih264d_submb_pred_modes[]; /** MB pred modes for P and B slice */ extern const WORD8 gau1_ih264d_mb_pred_modes[2][32]; /*Decode CAVLC Table declaration*/ extern const UWORD8 gau1_ih264d_table_total_zero_2to10[9][64]; extern const UWORD8 gau1_ih264d_table_total_zero_11to15[5][16]; extern const UWORD8 gau1_ih264d_table_run_before[64]; extern const UWORD16 gau2_ih264d_code_gx[304]; extern const UWORD8 gau1_ih264d_cav_chromdc_vld[256]; extern const UWORD16 gau2_ih264d_offset_num_vlc_tab[9]; extern const UWORD8 gau1_ih264d_total_coeff_fn_ptr_offset[16]; extern const WORD16 gai2_ih264d_trailing_one_level[14][3]; /*Decode CABAC Table declaration*/ extern const UWORD32 gau4_ih264d_cabac_table[128][4]; /****************************************************************************/ /* For error detection in intra pred4x4 modes */ /****************************************************************************/ extern const UWORD8 gau1_ih264d_intra_pred_err_code[9]; /*****************************************************************************/ /* Cabac tables for context initialization depending upon type of Slice, */ /* cabac init Idc value and Qp. */ /*****************************************************************************/ extern const UWORD8 gau1_ih264d_cabac_ctxt_init_table[NUM_CAB_INIT_IDC_PLUS_ONE][QP_RANGE][NUM_CABAC_CTXTS]; /*****************************************************************************/ /* SEI tables for field usge and which field first */ /*****************************************************************************/ extern const UWORD8 gau1_ih264d_sei_fld_usage[9][3]; extern const UWORD8 gau1_ih264d_top_left_mb_part_indx_mod[]; extern const UWORD8 gau1_ih264d_submb_indx_mod_sp_drct[]; #endif /*TABLES_H*/ ================================================ FILE: dependencies/ih264d/decoder/ih264d_thread_compute_bs.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_thread_compute_bs.c * * \brief * Contains routines that for multi-thread decoder * * Detailed_description * * \date * 20/02/2012 * * \author ZR ************************************************************************** */ #include "ih264d_error_handler.h" #include "ih264d_debug.h" #include <string.h> #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264d_mb_utils.h" #include "ih264d_thread_compute_bs.h" #include "ithread.h" #include "ih264d_deblocking.h" #include "ih264d_process_pslice.h" #include "ih264d_process_intra_mb.h" #include "ih264d_mb_utils.h" #include "ih264d_tables.h" #include "ih264d_format_conv.h" #include "ih264d_defs.h" UWORD16 ih264d_update_csbp_8x8(UWORD16 u2_luma_csbp); void ih264d_fill_bs2_horz_vert(UWORD32 *pu4_bs, /* Base pointer of BS table */ WORD32 u4_left_mb_csbp, /* csbp of left mb */ WORD32 u4_top_mb_csbp, /* csbp of top mb */ WORD32 u4_cur_mb_csbp, /* csbp of current mb */ const UWORD32 *pu4_packed_bs2, const UWORD16 *pu2_4x4_v2h_reorder); void ih264d_copy_intra_pred_line(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD32 nmb_index); #define BS_MB_GROUP 4 #define DEBLK_MB_GROUP 1 #define FORMAT_CONV_MB_GROUP 4 /*****************************************************************************/ /* */ /* Function Name : ih264d_compute_bs_non_mbaff_thread */ /* */ /* Description : This function computes the pointers of left,top & current*/ /* : Nnz, MvPred & deblk_mb_t and supplies to FillBs function for*/ /* : Boundary Strength Calculation .this function is used */ /* : BS being calculated in separate thread */ /* Inputs : pointer to decoder context,cur_mb_info,u4_mb_num */ /* Processing : */ /* */ /* Outputs : Produces the Boundary Strength for Current Mb */ /* Returns : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* ITTIAM */ /*****************************************************************************/ void ih264d_compute_bs_non_mbaff_thread(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mb_num) { /* Mvpred and Nnz for top and Courrent */ mv_pred_t *ps_cur_mv_pred, *ps_top_mv_pred = NULL, *ps_left_mv_pred; /* deblk_mb_t Params */ deblk_mb_t *ps_cur_mb_params; /*< Parameters of current MacroBlock */ deblkmb_neighbour_t *ps_deblk_top_mb; /* Reference Index to POC mapping*/ void ** apv_map_ref_idx_to_poc; UWORD32 u4_leftmbtype; UWORD16 u2_left_csbp, u2_top_csbp, u2_cur_csbp; /* Set of flags */ UWORD32 u4_cur_mb_intra, u1_top_mb_typ, u4_cur_mb_fld; UWORD32 u1_cur_mb_type; UWORD32 * pu4_bs_table; /* Neighbour availability */ /* Initialization */ const UWORD32 u2_mbx = ps_cur_mb_info->u2_mbx; const UWORD32 u2_mby = ps_cur_mb_info->u2_mby; const UWORD32 u1_pingpong = u2_mbx & 0x01; PROFILE_DISABLE_BOUNDARY_STRENGTH() ps_deblk_top_mb = ps_dec->ps_deblk_top_mb + u2_mbx; /* Pointer assignment for Current DeblkMB, Current Mv Pred */ ps_cur_mb_params = ps_dec->ps_deblk_pic + u4_mb_num; ps_cur_mv_pred = ps_dec->s_cur_pic.ps_mv + (u4_mb_num << 4); apv_map_ref_idx_to_poc = (void **)ps_dec->ps_computebs_cur_slice->ppv_map_ref_idx_to_poc + 1; u1_cur_mb_type = ps_cur_mb_params->u1_mb_type; u1_top_mb_typ = ps_deblk_top_mb->u1_mb_type; ps_deblk_top_mb->u1_mb_type = u1_cur_mb_type; { ps_cur_mb_params->u1_topmb_qp = ps_deblk_top_mb->u1_mb_qp; ps_deblk_top_mb->u1_mb_qp = ps_cur_mb_params->u1_mb_qp; ps_cur_mb_params->u1_left_mb_qp = ps_dec->deblk_left_mb[1].u1_mb_qp; ps_dec->deblk_left_mb[1].u1_mb_qp = ps_cur_mb_params->u1_mb_qp; } /* if no deblocking required for current Mb then continue */ /* Check next Mbs in Mb group */ if(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_FILTERING) { void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; { /* Store Parameter for Top MvPred refernce frame Address */ void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; /* Store Left addresses for Next Mb */ void ** ppv_left_mv_pred_addr = ps_dec->ps_left_mvpred_addr[!u1_pingpong][1].u4_add; WORD8 * p1_refleft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refleft0[0]]; ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refleft0[1]]; //} /* Storing the leftMbtype for next Mb */ ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; } return; } /* Flag for extra left Edge */ ps_cur_mb_params->u1_single_call = 1; /* Update the Left deblk_mb_t and Left MvPred Parameters */ if(!u2_mbx) { u4_leftmbtype = 0; /* Initialize the ps_left_mv_pred with Junk but Valid Location */ /* to avoid invalid memory access */ /* this is read only pointer */ ps_left_mv_pred = ps_cur_mv_pred + 3; } else { u4_leftmbtype = ps_dec->deblk_left_mb[1].u1_mb_type; /* Come to Left Most Edge of the MB */ ps_left_mv_pred = ps_cur_mv_pred - (1 << 4) + 3; } if(!u2_mby) u1_top_mb_typ = 0; /* MvPred Pointer Calculation */ /* CHANGED CODE */ ps_top_mv_pred = ps_cur_mv_pred - (ps_dec->u2_frm_wd_in_mbs << 4) + 12; u4_cur_mb_intra = u1_cur_mb_type & D_INTRA_MB; u4_cur_mb_fld = !!(u1_cur_mb_type & D_FLD_MB); /* Compute BS function */ pu4_bs_table = ps_cur_mb_params->u4_bs_table; u2_cur_csbp = ps_cur_mb_info->ps_curmb->u2_luma_csbp; u2_left_csbp = ps_cur_mb_info->ps_left_mb->u2_luma_csbp; u2_top_csbp = ps_cur_mb_info->ps_top_mb->u2_luma_csbp; /* Compute BS function */ if(ps_dec->ps_cur_sps->u1_profile_idc == HIGH_PROFILE_IDC) { if(ps_cur_mb_info->u1_tran_form8x8 == 1) { u2_cur_csbp = ih264d_update_csbp_8x8( ps_cur_mb_info->ps_curmb->u2_luma_csbp); } if(ps_cur_mb_info->ps_left_mb->u1_tran_form8x8 == 1) { u2_left_csbp = ih264d_update_csbp_8x8( ps_cur_mb_info->ps_left_mb->u2_luma_csbp); } if(ps_cur_mb_info->ps_top_mb->u1_tran_form8x8 == 1) { u2_top_csbp = ih264d_update_csbp_8x8( ps_cur_mb_info->ps_top_mb->u2_luma_csbp); } } if(u4_cur_mb_intra) { pu4_bs_table[4] = 0x04040404; pu4_bs_table[0] = u4_cur_mb_fld ? 0x03030303 : 0x04040404; pu4_bs_table[1] = 0x03030303; pu4_bs_table[2] = 0x03030303; pu4_bs_table[3] = 0x03030303; pu4_bs_table[5] = 0x03030303; pu4_bs_table[6] = 0x03030303; pu4_bs_table[7] = 0x03030303; } else { UWORD32 u4_is_non16x16 = !!(u1_cur_mb_type & D_PRED_NON_16x16); UWORD32 u4_is_b = (ps_dec->ps_computebs_cur_slice->slice_type == B_SLICE); ih264d_fill_bs2_horz_vert(pu4_bs_table, u2_left_csbp, u2_top_csbp, u2_cur_csbp, gau4_ih264d_packed_bs2, gau2_ih264d_4x4_v2h_reorder); if(u4_leftmbtype & D_INTRA_MB) pu4_bs_table[4] = 0x04040404; if(u1_top_mb_typ & D_INTRA_MB) pu4_bs_table[0] = u4_cur_mb_fld ? 0x03030303 : 0x04040404; ps_dec->pf_fill_bs1[u4_is_b][u4_is_non16x16]( ps_cur_mv_pred, ps_top_mv_pred, apv_map_ref_idx_to_poc, pu4_bs_table, ps_left_mv_pred, &(ps_dec->ps_left_mvpred_addr[u1_pingpong][1]), ps_cur_mb_info->ps_top_mb->u4_pic_addrress, (4 >> u4_cur_mb_fld)); } { void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; { /* Store Parameter for Top MvPred refernce frame Address */ void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; /* Store Left addresses for Next Mb */ void ** ppv_left_mv_pred_addr = ps_dec->ps_left_mvpred_addr[!u1_pingpong][1].u4_add; WORD8 * p1_refleft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refleft0[0]]; ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refleft0[1]]; /* Storing the leftMbtype for next Mb */ ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; } } /* For transform 8x8 disable deblocking of the intrernal edges of a 8x8 block */ if(ps_cur_mb_info->u1_tran_form8x8) { pu4_bs_table[1] = 0; pu4_bs_table[3] = 0; pu4_bs_table[5] = 0; pu4_bs_table[7] = 0; } } void ih264d_check_mb_map_deblk(dec_struct_t *ps_dec, UWORD32 deblk_mb_grp, tfr_ctxt_t *ps_tfr_cxt, UWORD32 u4_check_mb_map) { UWORD32 i = 0; UWORD32 u4_mb_num; UWORD32 u4_cond; volatile UWORD8 *mb_map = ps_dec->pu1_recon_mb_map; const WORD32 i4_cb_qp_idx_ofst = ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; const WORD32 i4_cr_qp_idx_ofst = ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; UWORD32 u4_wd_y, u4_wd_uv; UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; u4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; u4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; for(i = 0; i < deblk_mb_grp; i++) { WORD32 nop_cnt = 8*128; while(u4_check_mb_map == 1) { u4_mb_num = ps_dec->u4_cur_deblk_mb_num; /*we wait for the right mb because of intra pred data dependency*/ u4_mb_num = MIN(u4_mb_num + 1, (ps_dec->u4_deblk_mb_y + 1) * ps_dec->u2_frm_wd_in_mbs - 1); CHECK_MB_MAP_BYTE(u4_mb_num, mb_map, u4_cond); if(u4_cond) { break; } else { if(nop_cnt > 0) { nop_cnt -= 128; NOP(128); } else { nop_cnt = 8*128; ithread_yield(); } } } ih264d_deblock_mb_nonmbaff(ps_dec, ps_tfr_cxt, i4_cb_qp_idx_ofst, i4_cr_qp_idx_ofst, u4_wd_y, u4_wd_uv); } } void ih264d_recon_deblk_slice(dec_struct_t *ps_dec, tfr_ctxt_t *ps_tfr_cxt) { dec_mb_info_t *p_cur_mb; UWORD32 u4_max_addr; WORD32 i; UWORD32 u1_mb_aff; UWORD16 u2_slice_num; UWORD32 u4_mb_num; UWORD16 u2_first_mb_in_slice; UWORD32 i2_pic_wdin_mbs; UWORD8 u1_num_mbsleft, u1_end_of_row; UWORD8 u1_mbaff; UWORD16 i16_mb_x, i16_mb_y; WORD32 j; dec_mb_info_t * ps_cur_mb_info; UWORD32 u1_slice_type, u1_B; WORD32 u1_skip_th; UWORD32 u1_ipcm_th; WORD32 ret; tfr_ctxt_t *ps_trns_addr; UWORD32 u4_frame_stride; UWORD32 x_offset, y_offset; UWORD32 u4_slice_end; pad_mgr_t *ps_pad_mgr ; /*check for mb map of first mb in slice to ensure slice header is parsed*/ while(1) { UWORD32 u4_mb_num = ps_dec->cur_recon_mb_num; UWORD32 u4_cond = 0; WORD32 nop_cnt = 8*128; CHECK_MB_MAP_BYTE(u4_mb_num, ps_dec->pu1_recon_mb_map, u4_cond); if(u4_cond) { break; } else { if(nop_cnt > 0) { nop_cnt -= 128; NOP(128); } else { if(ps_dec->u4_output_present && (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) { ps_dec->u4_fmt_conv_num_rows = MIN(FMT_CONV_NUM_ROWS, (ps_dec->s_disp_frame_info.u4_y_ht - ps_dec->u4_fmt_conv_cur_row)); ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), ps_dec->u4_fmt_conv_cur_row, ps_dec->u4_fmt_conv_num_rows); ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; } else { nop_cnt = 8*128; ithread_yield(); } } DEBUG_THREADS_PRINTF("waiting for mb mapcur_dec_mb_num = %d,ps_dec->u2_cur_mb_addr = %d\n",u2_cur_dec_mb_num, ps_dec->u2_cur_mb_addr); } } u4_max_addr = ps_dec->ps_cur_sps->u2_max_mb_addr; u1_mb_aff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; u2_first_mb_in_slice = ps_dec->ps_computebs_cur_slice->u4_first_mb_in_slice; i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; ps_pad_mgr = &ps_dec->s_pad_mgr; if(u2_first_mb_in_slice == 0) ih264d_init_deblk_tfr_ctxt(ps_dec, ps_pad_mgr, ps_tfr_cxt, ps_dec->u2_frm_wd_in_mbs, 0); i16_mb_x = MOD(u2_first_mb_in_slice, i2_pic_wdin_mbs); i16_mb_y = DIV(u2_first_mb_in_slice, i2_pic_wdin_mbs); i16_mb_y <<= u1_mbaff; ps_dec->i2_recon_thread_mb_y = i16_mb_y; u4_frame_stride = ps_dec->u2_frm_wd_y << ps_dec->ps_cur_slice->u1_field_pic_flag; x_offset = i16_mb_x << 4; y_offset = (i16_mb_y * u4_frame_stride) << 4; ps_trns_addr = &ps_dec->s_tran_iprecon; ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + x_offset + y_offset; u4_frame_stride = ps_dec->u2_frm_wd_uv << ps_dec->ps_cur_slice->u1_field_pic_flag; x_offset >>= 1; y_offset = (i16_mb_y * u4_frame_stride) << 3; x_offset *= YUV420SP_FACTOR; ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + x_offset + y_offset; ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + x_offset + y_offset; ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; ps_dec->cur_recon_mb_num = u2_first_mb_in_slice << u1_mbaff; u4_slice_end = 0; ps_dec->u4_bs_cur_slice_num_mbs = 0; ps_dec->u4_cur_bs_mb_num = (ps_dec->ps_computebs_cur_slice->u4_first_mb_in_slice) << u1_mb_aff; if(ps_dec->i1_recon_in_thread3_flag) { ps_dec->pv_proc_tu_coeff_data = (void *) ps_dec->ps_computebs_cur_slice->pv_tu_coeff_data_start; } u1_slice_type = ps_dec->ps_computebs_cur_slice->slice_type; u1_B = (u1_slice_type == B_SLICE); u1_skip_th = ((u1_slice_type != I_SLICE) ? (u1_B ? B_8x8 : PRED_8x8R0) : -1); u1_ipcm_th = ((u1_slice_type != I_SLICE) ? (u1_B ? 23 : 5) : 0); while(u4_slice_end != 1) { WORD32 recon_mb_grp,bs_mb_grp; WORD32 nop_cnt = 8*128; u1_num_mbsleft = ((i2_pic_wdin_mbs - i16_mb_x) << u1_mbaff); if(u1_num_mbsleft <= ps_dec->u1_recon_mb_grp) { recon_mb_grp = u1_num_mbsleft; u1_end_of_row = 1; i16_mb_x = 0; } else { recon_mb_grp = ps_dec->u1_recon_mb_grp; u1_end_of_row = 0; i16_mb_x += (recon_mb_grp >> u1_mbaff); } while(1) { UWORD32 u4_cond = 0; UWORD32 u4_mb_num = ps_dec->cur_recon_mb_num + recon_mb_grp - 1; /* * Wait for one extra mb of MC, because some chroma IQ-IT functions * sometimes loads the pixels of the right mb and stores with the loaded * values. */ u4_mb_num = MIN(u4_mb_num + 1, (ps_dec->i2_recon_thread_mb_y + 1) * i2_pic_wdin_mbs - 1); CHECK_MB_MAP_BYTE(u4_mb_num, ps_dec->pu1_recon_mb_map, u4_cond); if(u4_cond) { break; } else { if(nop_cnt > 0) { nop_cnt -= 128; NOP(128); } else { if(ps_dec->u4_output_present && (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) { ps_dec->u4_fmt_conv_num_rows = MIN(FMT_CONV_NUM_ROWS, (ps_dec->s_disp_frame_info.u4_y_ht - ps_dec->u4_fmt_conv_cur_row)); ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), ps_dec->u4_fmt_conv_cur_row, ps_dec->u4_fmt_conv_num_rows); ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; } else { nop_cnt = 8*128; ithread_yield(); } } } } for(j = 0; j < recon_mb_grp; j++) { GET_SLICE_NUM_MAP(ps_dec->pu2_slice_num_map, ps_dec->cur_recon_mb_num, u2_slice_num); if(u2_slice_num != ps_dec->u2_cur_slice_num_bs) { u4_slice_end = 1; break; } if(ps_dec->i1_recon_in_thread3_flag) { ps_cur_mb_info = &ps_dec->ps_frm_mb_info[ps_dec->cur_recon_mb_num]; if(ps_cur_mb_info->u1_mb_type <= u1_skip_th) { ih264d_process_inter_mb(ps_dec, ps_cur_mb_info, j); } else if(ps_cur_mb_info->u1_mb_type != MB_SKIP) { if((u1_ipcm_th + 25) != ps_cur_mb_info->u1_mb_type) { ps_cur_mb_info->u1_mb_type -= (u1_skip_th + 1); ih264d_process_intra_mb(ps_dec, ps_cur_mb_info, j); } } ih264d_copy_intra_pred_line(ps_dec, ps_cur_mb_info, j); } ps_dec->cur_recon_mb_num++; } if(j != recon_mb_grp) { u1_end_of_row = 0; } { tfr_ctxt_t *ps_trns_addr = &ps_dec->s_tran_iprecon; UWORD16 u2_mb_y; ps_trns_addr->pu1_dest_y += ps_trns_addr->u4_inc_y[u1_end_of_row]; ps_trns_addr->pu1_dest_u += ps_trns_addr->u4_inc_uv[u1_end_of_row]; ps_trns_addr->pu1_dest_v += ps_trns_addr->u4_inc_uv[u1_end_of_row]; if(u1_end_of_row) { ps_dec->i2_recon_thread_mb_y += (1 << u1_mbaff); u2_mb_y = ps_dec->i2_recon_thread_mb_y; y_offset = (u2_mb_y * u4_frame_stride) << 4; ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + y_offset; u4_frame_stride = ps_dec->u2_frm_wd_uv << ps_dec->ps_cur_slice->u1_field_pic_flag; y_offset = (u2_mb_y * u4_frame_stride) << 3; ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + y_offset; ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + y_offset; ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; } } bs_mb_grp = j; /* Compute BS for NMB group*/ for(i = 0; i < bs_mb_grp; i++) { p_cur_mb = &ps_dec->ps_frm_mb_info[ps_dec->u4_cur_bs_mb_num]; DEBUG_THREADS_PRINTF("ps_dec->u4_cur_bs_mb_num = %d\n",ps_dec->u4_cur_bs_mb_num); ih264d_compute_bs_non_mbaff_thread(ps_dec, p_cur_mb, ps_dec->u4_cur_bs_mb_num); ps_dec->u4_cur_bs_mb_num++; ps_dec->u4_bs_cur_slice_num_mbs++; } if(ps_dec->u4_cur_bs_mb_num > u4_max_addr) { u4_slice_end = 1; } /*deblock MB group*/ { UWORD32 u4_num_mbs; if(ps_dec->u4_cur_bs_mb_num > ps_dec->u4_cur_deblk_mb_num) { if(u1_end_of_row) { u4_num_mbs = ps_dec->u4_cur_bs_mb_num - ps_dec->u4_cur_deblk_mb_num; } else { u4_num_mbs = ps_dec->u4_cur_bs_mb_num - ps_dec->u4_cur_deblk_mb_num - 1; } } else u4_num_mbs = 0; ih264d_check_mb_map_deblk(ps_dec, u4_num_mbs, ps_tfr_cxt,0); } } } void ih264d_recon_deblk_thread(dec_struct_t *ps_dec) { tfr_ctxt_t s_tfr_ctxt; tfr_ctxt_t *ps_tfr_cxt = &s_tfr_ctxt; // = &ps_dec->s_tran_addrecon; UWORD32 yield_cnt = 0; ithread_set_name("ih264d_recon_deblk_thread"); while(1) { DEBUG_THREADS_PRINTF(" Entering compute bs slice\n"); ih264d_recon_deblk_slice(ps_dec, ps_tfr_cxt); DEBUG_THREADS_PRINTF(" Exit compute bs slice \n"); if(ps_dec->cur_recon_mb_num > ps_dec->ps_cur_sps->u2_max_mb_addr) { break; } else { ps_dec->ps_computebs_cur_slice++; ps_dec->u2_cur_slice_num_bs++; } DEBUG_THREADS_PRINTF("CBS thread:Got next slice/end of frame signal \n "); } if(ps_dec->u4_output_present && (3 == ps_dec->u4_num_cores) && (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) { ps_dec->u4_fmt_conv_num_rows = (ps_dec->s_disp_frame_info.u4_y_ht - ps_dec->u4_fmt_conv_cur_row); ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), ps_dec->u4_fmt_conv_cur_row, ps_dec->u4_fmt_conv_num_rows); ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; } } ================================================ FILE: dependencies/ih264d/decoder/ih264d_thread_compute_bs.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /* * ih264d_thread_parse_decode.h * * Created on: Feb 21, 2012 * Author: 100492 */ #ifndef _IH264D_THREAD_COMPUTE_BS_H_ #define _IH264D_THREAD_COMPUTE_BS_H_ void ih264d_compute_bs_non_mbaff_thread(dec_struct_t * ps_dec, dec_mb_info_t * ps_cur_mb_info, UWORD32 u4_mb_num); void ih264d_recon_deblk_thread(dec_struct_t *ps_dec); void ih264d_check_mb_map_deblk(dec_struct_t *ps_dec, UWORD32 deblk_mb_grp, tfr_ctxt_t *ps_tfr_cxt, UWORD32 u4_check_mb_map); #endif /* _IH264D_THREAD_COMPUTE_BS_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_thread_parse_decode.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_thread_parse_decode.c * * \brief * Contains routines that for multi-thread decoder * * Detailed_description * * \date * 20/02/2012 * * \author ZR ************************************************************************** */ #include "ih264d_error_handler.h" #include "ih264d_debug.h" #include "ithread.h" #include <string.h> #include "ih264d_defs.h" #include "ih264d_debug.h" #include "ih264d_tables.h" #include "ih264d_structs.h" #include "ih264d_defs.h" #include "ih264d_mb_utils.h" #include "ih264d_thread_parse_decode.h" #include "ih264d_inter_pred.h" #include "ih264d_process_pslice.h" #include "ih264d_process_intra_mb.h" #include "ih264d_deblocking.h" #include "ih264d_format_conv.h" void ih264d_deblock_mb_level(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD32 nmb_index); void ih264d_copy_intra_pred_line(dec_struct_t *ps_dec, dec_mb_info_t *ps_cur_mb_info, UWORD32 nmb_index); void ih264d_parse_tfr_nmb(dec_struct_t * ps_dec, UWORD8 u1_mb_idx, UWORD8 u1_num_mbs, UWORD8 u1_num_mbs_next, UWORD8 u1_tfr_n_mb, UWORD8 u1_end_of_row) { WORD32 i, u4_mb_num; const UWORD32 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; UWORD32 u4_n_mb_start; UNUSED(u1_mb_idx); UNUSED(u1_num_mbs_next); if(u1_tfr_n_mb) { u4_n_mb_start = (ps_dec->u2_cur_mb_addr + 1) - u1_num_mbs; // copy into s_frmMbInfo u4_mb_num = u4_n_mb_start; u4_mb_num = (ps_dec->u2_cur_mb_addr + 1) - u1_num_mbs; for(i = 0; i < u1_num_mbs; i++) { UPDATE_SLICE_NUM_MAP(ps_dec->pu2_slice_num_map, u4_mb_num, ps_dec->u2_cur_slice_num); DATA_SYNC(); UPDATE_MB_MAP_MBNUM_BYTE(ps_dec->pu1_dec_mb_map, u4_mb_num); u4_mb_num++; } /****************************************************************/ /* Check for End Of Row in Next iteration */ /****************************************************************/ /****************************************************************/ /* Transfer the Following things */ /* N-Mb DeblkParams Data ( To Ext DeblkParams Buffer ) */ /* N-Mb Recon Data ( To Ext Frame Buffer ) */ /* N-Mb Intrapredline Data ( Updated Internally) */ /* N-Mb MV Data ( To Ext MV Buffer ) */ /* N-Mb MVTop/TopRight Data ( To Int MV Top Scratch Buffers) */ /****************************************************************/ /* Swap top and current pointers */ ps_dec->s_tran_addrecon_parse.pu1_dest_y += ps_dec->s_tran_addrecon_parse.u4_inc_y[u1_end_of_row]; ps_dec->s_tran_addrecon_parse.pu1_dest_u += ps_dec->s_tran_addrecon_parse.u4_inc_uv[u1_end_of_row]; ps_dec->s_tran_addrecon_parse.pu1_dest_v += ps_dec->s_tran_addrecon_parse.u4_inc_uv[u1_end_of_row]; if(u1_end_of_row) { UWORD16 u2_mb_y; UWORD32 u4_frame_stride, y_offset; ps_dec->ps_top_mb_row = ps_dec->ps_cur_mb_row; ps_dec->ps_cur_mb_row += ((ps_dec->u2_frm_wd_in_mbs) << u1_mbaff); u2_mb_y = ps_dec->u2_mby + (1 + u1_mbaff); u4_frame_stride = ps_dec->u2_frm_wd_y << ps_dec->ps_cur_slice->u1_field_pic_flag; y_offset = (u2_mb_y * u4_frame_stride) << 4; ps_dec->s_tran_addrecon_parse.pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + y_offset; u4_frame_stride = ps_dec->u2_frm_wd_uv << ps_dec->ps_cur_slice->u1_field_pic_flag; y_offset = (u2_mb_y * u4_frame_stride) << 3; ps_dec->s_tran_addrecon_parse.pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + y_offset; ps_dec->s_tran_addrecon_parse.pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + y_offset; } ps_dec->ps_deblk_mbn += u1_num_mbs; /* * The Slice boundary is also a valid condition to transfer. So recalculate * the Left increment, in case the number of MBs is lesser than the * N MB value. c_numMbs will be equal to N of N MB if the entire N Mb is * decoded. */ ps_dec->s_tran_addrecon.u2_mv_left_inc = ((u1_num_mbs >> u1_mbaff) - 1) << (4 + u1_mbaff); ps_dec->s_tran_addrecon.u2_mv_top_left_inc = (u1_num_mbs << 2) - 1 - (u1_mbaff << 2); /* reassign left MV and cur MV pointers */ ps_dec->ps_mv_left = ps_dec->ps_mv_cur + ps_dec->s_tran_addrecon.u2_mv_left_inc; ps_dec->ps_mv_cur += (u1_num_mbs << 4); ps_dec->u4_num_mbs_prev_nmb = u1_num_mbs; } } void ih264d_decode_tfr_nmb(dec_struct_t * ps_dec, UWORD8 u1_num_mbs, UWORD8 u1_num_mbs_next, UWORD8 u1_end_of_row) { UWORD32 u1_end_of_row_next; const UWORD32 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; /****************************************************************/ /* Check for End Of Row in Next iteration */ /****************************************************************/ u1_end_of_row_next = u1_num_mbs_next && ((u1_num_mbs_next) <= (ps_dec->u1_recon_mb_grp >> u1_mbaff)); /****************************************************************/ /* Transfer the Following things */ /* N-Mb DeblkParams Data ( To Ext DeblkParams Buffer ) */ /* N-Mb Recon Data ( To Ext Frame Buffer ) */ /* N-Mb Intrapredline Data ( Updated Internally) */ /* N-Mb MV Data ( To Ext MV Buffer ) */ /* N-Mb MVTop/TopRight Data ( To Int MV Top Scratch Buffers) */ /****************************************************************/ if(u1_end_of_row) { ps_dec->i2_dec_thread_mb_y += (1 << u1_mbaff); } ih264d_transfer_mb_group_data(ps_dec, u1_num_mbs, u1_end_of_row, u1_end_of_row_next); } WORD32 ih264d_decode_recon_tfr_nmb_thread(dec_struct_t * ps_dec, UWORD8 u1_num_mbs, UWORD8 u1_num_mbs_next, UWORD8 u1_end_of_row) { WORD32 i,j; dec_mb_info_t * ps_cur_mb_info; UWORD32 u4_update_mbaff = 0; const UWORD32 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; UWORD32 u1_slice_type, u1_B; WORD32 u1_skip_th; UWORD32 u1_ipcm_th; UWORD32 u4_cond; UWORD16 u2_slice_num,u2_cur_dec_mb_num; WORD32 ret; UWORD32 u4_mb_num; WORD32 nop_cnt = 8*128; u1_slice_type = ps_dec->ps_decode_cur_slice->slice_type; u1_B = (u1_slice_type == B_SLICE); u1_skip_th = ((u1_slice_type != I_SLICE) ? (u1_B ? B_8x8 : PRED_8x8R0) : -1); u1_ipcm_th = ((u1_slice_type != I_SLICE) ? (u1_B ? 23 : 5) : 0); u2_cur_dec_mb_num = ps_dec->cur_dec_mb_num; while(1) { UWORD32 u4_max_mb = (UWORD32)(ps_dec->i2_dec_thread_mb_y + (1 << u1_mbaff)) * ps_dec->u2_frm_wd_in_mbs - 1; u4_mb_num = u2_cur_dec_mb_num; /*introducing 1 MB delay*/ u4_mb_num = MIN(u4_mb_num + u1_num_mbs + 1, u4_max_mb); CHECK_MB_MAP_BYTE(u4_mb_num, ps_dec->pu1_dec_mb_map, u4_cond); if(u4_cond) { break; } else { if(nop_cnt > 0) { nop_cnt -= 128; NOP(128); } else { if(ps_dec->u4_output_present && (2 == ps_dec->u4_num_cores) && (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) { ps_dec->u4_fmt_conv_num_rows = MIN(FMT_CONV_NUM_ROWS, (ps_dec->s_disp_frame_info.u4_y_ht - ps_dec->u4_fmt_conv_cur_row)); ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), ps_dec->u4_fmt_conv_cur_row, ps_dec->u4_fmt_conv_num_rows); ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; } else { nop_cnt = 8*128; ithread_yield(); } } } } /* N Mb MC Loop */ for(i = 0; i < u1_num_mbs; i++) { u4_mb_num = u2_cur_dec_mb_num; GET_SLICE_NUM_MAP(ps_dec->pu2_slice_num_map, u2_cur_dec_mb_num, u2_slice_num); if(u2_slice_num != ps_dec->u2_cur_slice_num_dec_thread) { ps_dec->u4_cur_slice_decode_done = 1; break; } ps_cur_mb_info = &ps_dec->ps_frm_mb_info[u2_cur_dec_mb_num]; ps_dec->u4_dma_buf_idx = 0; ps_dec->u4_pred_info_idx = 0; if(ps_cur_mb_info->u1_mb_type <= u1_skip_th) { WORD32 pred_cnt = 0; pred_info_pkd_t *ps_pred_pkd; UWORD32 u4_pred_info_pkd_idx; WORD8 i1_pred; u4_pred_info_pkd_idx = ps_cur_mb_info->u4_pred_info_pkd_idx; while(pred_cnt < ps_cur_mb_info->u1_num_pred_parts) { ps_pred_pkd = ps_dec->ps_pred_pkd + u4_pred_info_pkd_idx; ps_dec->p_form_mb_part_info_thread(ps_pred_pkd,ps_dec, ps_cur_mb_info->u2_mbx, ps_cur_mb_info->u2_mby, (i >> u1_mbaff), ps_cur_mb_info); u4_pred_info_pkd_idx++; pred_cnt++; } ps_dec->p_mc_dec_thread(ps_dec, ps_cur_mb_info); } else if(ps_cur_mb_info->u1_mb_type == MB_SKIP) { WORD32 pred_cnt = 0; pred_info_pkd_t *ps_pred_pkd; UWORD32 u4_pred_info_pkd_idx; WORD8 i1_pred; u4_pred_info_pkd_idx = ps_cur_mb_info->u4_pred_info_pkd_idx; while(pred_cnt < ps_cur_mb_info->u1_num_pred_parts) { ps_pred_pkd = ps_dec->ps_pred_pkd + u4_pred_info_pkd_idx; ps_dec->p_form_mb_part_info_thread(ps_pred_pkd,ps_dec, ps_cur_mb_info->u2_mbx, ps_cur_mb_info->u2_mby, (i >> u1_mbaff), ps_cur_mb_info); u4_pred_info_pkd_idx++; pred_cnt++; } /* Decode MB skip */ ps_dec->p_mc_dec_thread(ps_dec, ps_cur_mb_info); } u2_cur_dec_mb_num++; } /* N Mb IQ IT RECON Loop */ for(j = 0; j < i; j++) { ps_cur_mb_info = &ps_dec->ps_frm_mb_info[ps_dec->cur_dec_mb_num]; if((ps_dec->u4_num_cores == 2) || !ps_dec->i1_recon_in_thread3_flag) { if(ps_cur_mb_info->u1_mb_type <= u1_skip_th) { ih264d_process_inter_mb(ps_dec, ps_cur_mb_info, j); } else if(ps_cur_mb_info->u1_mb_type != MB_SKIP) { if((u1_ipcm_th + 25) != ps_cur_mb_info->u1_mb_type) { ps_cur_mb_info->u1_mb_type -= (u1_skip_th + 1); ih264d_process_intra_mb(ps_dec, ps_cur_mb_info, j); } } if(ps_dec->u4_use_intrapred_line_copy == 1) ih264d_copy_intra_pred_line(ps_dec, ps_cur_mb_info, j); } DATA_SYNC(); if(u1_mbaff) { if(u4_update_mbaff) { UWORD32 u4_mb_num = ps_cur_mb_info->u2_mbx + ps_dec->u2_frm_wd_in_mbs * (ps_cur_mb_info->u2_mby >> 1); UPDATE_MB_MAP_MBNUM_BYTE(ps_dec->pu1_recon_mb_map, u4_mb_num); u4_update_mbaff = 0; } else { u4_update_mbaff = 1; } } else { UWORD32 u4_mb_num = ps_cur_mb_info->u2_mbx + ps_dec->u2_frm_wd_in_mbs * ps_cur_mb_info->u2_mby; UPDATE_MB_MAP_MBNUM_BYTE(ps_dec->pu1_recon_mb_map, u4_mb_num); } ps_dec->cur_dec_mb_num++; } /*N MB deblocking*/ if(ps_dec->u4_nmb_deblk == 1) { UWORD32 u4_wd_y, u4_wd_uv; tfr_ctxt_t *ps_tfr_cxt = &(ps_dec->s_tran_addrecon); UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; const WORD32 i4_cb_qp_idx_ofst = ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; const WORD32 i4_cr_qp_idx_ofst = ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; u4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; u4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; ps_cur_mb_info = &ps_dec->ps_frm_mb_info[ps_dec->u4_cur_deblk_mb_num]; ps_dec->u4_deblk_mb_x = ps_cur_mb_info->u2_mbx; ps_dec->u4_deblk_mb_y = ps_cur_mb_info->u2_mby; for(j = 0; j < i; j++) { ih264d_deblock_mb_nonmbaff(ps_dec, ps_tfr_cxt, i4_cb_qp_idx_ofst, i4_cr_qp_idx_ofst, u4_wd_y, u4_wd_uv); } } /*handle the last mb in picture case*/ if(ps_dec->cur_dec_mb_num > ps_dec->ps_cur_sps->u2_max_mb_addr) ps_dec->u4_cur_slice_decode_done = 1; if(i != u1_num_mbs) { u1_end_of_row = 0; /*Number of MB's left in row*/ u1_num_mbs_next = u1_num_mbs_next + ((u1_num_mbs - i) >> u1_mbaff); } ih264d_decode_tfr_nmb(ps_dec, (i), u1_num_mbs_next, u1_end_of_row); return OK; } WORD32 ih264d_decode_slice_thread(dec_struct_t *ps_dec) { UWORD8 u1_num_mbs_next, u1_num_mbsleft, u1_end_of_row = 0; const UWORD32 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; UWORD8 u1_mbaff, u1_num_mbs; UWORD16 u2_first_mb_in_slice; UWORD16 i16_mb_x, i16_mb_y; UWORD8 u1_field_pic; UWORD32 u4_frame_stride, x_offset, y_offset; WORD32 ret; tfr_ctxt_t *ps_trns_addr; /*check for mb map of first mb in slice to ensure slice header is parsed*/ while(1) { UWORD32 u4_mb_num = ps_dec->cur_dec_mb_num; UWORD32 u4_cond = 0; WORD32 nop_cnt = 8 * 128; CHECK_MB_MAP_BYTE(u4_mb_num, ps_dec->pu1_dec_mb_map, u4_cond); if(u4_cond) { break; } else { if(nop_cnt > 0) { nop_cnt -= 128; NOP(128); } else if(ps_dec->u4_output_present && (2 == ps_dec->u4_num_cores) && (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) { ps_dec->u4_fmt_conv_num_rows = MIN(FMT_CONV_NUM_ROWS, (ps_dec->s_disp_frame_info.u4_y_ht - ps_dec->u4_fmt_conv_cur_row)); ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), ps_dec->u4_fmt_conv_cur_row, ps_dec->u4_fmt_conv_num_rows); ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; } else { nop_cnt = 8*128; ithread_yield(); } DEBUG_THREADS_PRINTF("waiting for mb mapcur_dec_mb_num = %d,ps_dec->u2_cur_mb_addr = %d\n",u2_cur_dec_mb_num, ps_dec->u2_cur_mb_addr); } } u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; u2_first_mb_in_slice = ps_dec->ps_decode_cur_slice->u4_first_mb_in_slice; i16_mb_x = MOD(u2_first_mb_in_slice, i2_pic_wdin_mbs); i16_mb_y = DIV(u2_first_mb_in_slice, i2_pic_wdin_mbs); i16_mb_y <<= u1_mbaff; ps_dec->i2_dec_thread_mb_y = i16_mb_y; ps_dec->cur_dec_mb_num = u2_first_mb_in_slice << u1_mbaff; if((ps_dec->u4_num_cores == 2) || !ps_dec->i1_recon_in_thread3_flag) { ps_dec->pv_proc_tu_coeff_data = (void *) ps_dec->ps_decode_cur_slice->pv_tu_coeff_data_start; } // recalculate recon pointers u1_field_pic = ps_dec->ps_cur_slice->u1_field_pic_flag; u4_frame_stride = ps_dec->u2_frm_wd_y << u1_field_pic; x_offset = i16_mb_x << 4; y_offset = (i16_mb_y * u4_frame_stride) << 4; ps_trns_addr = &(ps_dec->s_tran_addrecon); ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + x_offset + y_offset; u4_frame_stride = ps_dec->u2_frm_wd_uv << u1_field_pic; x_offset >>= 1; y_offset = (i16_mb_y * u4_frame_stride) << 3; x_offset *= YUV420SP_FACTOR; ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + x_offset + y_offset; ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + x_offset + y_offset; ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; /* Initialise MC and formMbPartInfo fn ptrs one time based on profile_idc */ { ps_dec->p_mc_dec_thread = ih264d_motion_compensate_bp; ps_dec->p_form_mb_part_info_thread = ih264d_form_mb_part_info_bp; } { UWORD8 uc_nofield_nombaff; uc_nofield_nombaff = ((ps_dec->ps_cur_slice->u1_field_pic_flag == 0) && (ps_dec->ps_cur_slice->u1_mbaff_frame_flag == 0) && (ps_dec->ps_decode_cur_slice->slice_type != B_SLICE) && (ps_dec->ps_cur_pps->u1_wted_pred_flag == 0)); if(uc_nofield_nombaff == 0) { ps_dec->p_mc_dec_thread = ih264d_motion_compensate_mp; ps_dec->p_form_mb_part_info_thread = ih264d_form_mb_part_info_mp; } } ps_dec->u4_cur_slice_decode_done = 0; while(ps_dec->u4_cur_slice_decode_done != 1) { u1_num_mbsleft = ((i2_pic_wdin_mbs - i16_mb_x) << u1_mbaff); if(u1_num_mbsleft <= ps_dec->u1_recon_mb_grp) { u1_num_mbs = u1_num_mbsleft; /*Indicate number of mb's left in a row*/ u1_num_mbs_next = 0; u1_end_of_row = 1; i16_mb_x = 0; } else { u1_num_mbs = ps_dec->u1_recon_mb_grp; /*Indicate number of mb's left in a row*/ u1_num_mbs_next = i2_pic_wdin_mbs - i16_mb_x - (ps_dec->u1_recon_mb_grp >> u1_mbaff); i16_mb_x += (u1_num_mbs >> u1_mbaff); u1_end_of_row = 0; } ret = ih264d_decode_recon_tfr_nmb_thread(ps_dec, u1_num_mbs, u1_num_mbs_next, u1_end_of_row); if(ret != OK) return ret; } return OK; } void ih264d_decode_picture_thread(dec_struct_t *ps_dec ) { ithread_set_name("ih264d_decode_picture_thread"); while(1) { /*Complete all writes before processing next slice*/ DEBUG_THREADS_PRINTF(" Entering decode slice\n"); ih264d_decode_slice_thread(ps_dec); DEBUG_THREADS_PRINTF(" Exit ih264d_decode_slice_thread \n"); if(ps_dec->cur_dec_mb_num > ps_dec->ps_cur_sps->u2_max_mb_addr) { /*Last slice in frame*/ break; } else { ps_dec->ps_decode_cur_slice++; ps_dec->u2_cur_slice_num_dec_thread++; } } if(ps_dec->u4_output_present && (2 == ps_dec->u4_num_cores) && (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) { ps_dec->u4_fmt_conv_num_rows = (ps_dec->s_disp_frame_info.u4_y_ht - ps_dec->u4_fmt_conv_cur_row); ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), ps_dec->u4_fmt_conv_cur_row, ps_dec->u4_fmt_conv_num_rows); ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; } } void ih264d_signal_decode_thread(dec_struct_t *ps_dec) { if(ps_dec->u4_dec_thread_created == 1) { ithread_join(ps_dec->pv_dec_thread_handle, NULL); ps_dec->u4_dec_thread_created = 0; } } void ih264d_signal_bs_deblk_thread(dec_struct_t *ps_dec) { if(ps_dec->u4_bs_deblk_thread_created) { ithread_join(ps_dec->pv_bs_deblk_thread_handle, NULL); ps_dec->u4_bs_deblk_thread_created = 0; } } ================================================ FILE: dependencies/ih264d/decoder/ih264d_thread_parse_decode.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /* * ih264d_thread_parse_decode.h * * Created on: Feb 21, 2012 * Author: 100492 */ #ifndef _IH264D_THREAD_PARSE_DECPDE_H_ #define _IH264D_THREAD_PARSE_DECPDE_H_ void ih264d_parse_tfr_nmb(dec_struct_t *ps_dec, UWORD8 u1_mb_idx, UWORD8 u1_num_mbs, UWORD8 u1_num_mbs_next, UWORD8 u1_tfr_n_mb, UWORD8 u1_end_of_row); void ih264d_decode_tfr_nmb(dec_struct_t *ps_dec, UWORD8 u1_num_mbs, UWORD8 u1_num_mbs_next, UWORD8 u1_end_of_row); WORD32 ih264d_decode_recon_tfr_nmb_thread(dec_struct_t * ps_dec, UWORD8 u1_num_mbs, UWORD8 u1_num_mbs_next, UWORD8 u1_end_of_row); void ih264d_decode_picture_thread(dec_struct_t *ps_dec); WORD32 ih264d_decode_slice_thread(dec_struct_t *ps_dec); #endif /* _IH264D_THREAD_PARSE_DECPDE_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_transfer_address.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_TRANSFER_ADDRESS_H_ #define _IH264D_TRANSFER_ADDRESS_H_ typedef struct { UWORD8 *pu1_src_y; UWORD8 *pu1_src_u; UWORD8 *pu1_src_v; UWORD8 *pu1_dest_y; UWORD8 *pu1_dest_u; UWORD8 *pu1_dest_v; UWORD32 u4_inc_y[2]; UWORD32 u4_inc_uv[2]; UWORD16 u2_frm_wd_y; UWORD16 u2_frm_wd_uv; UWORD8 *pu1_mb_y; UWORD8 *pu1_mb_u; UWORD8 *pu1_mb_v; UWORD16 u2_mv_left_inc; UWORD16 u2_mv_top_left_inc; UWORD32 u4_y_inc; UWORD32 u4_uv_inc; } tfr_ctxt_t; #endif ================================================ FILE: dependencies/ih264d/decoder/ih264d_utils.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*! ************************************************************************** * \file ih264d_utils.c * * \brief * Contains routines that handle of start and end of pic processing * * \date * 19/12/2002 * * \author AI ************************************************************************** */ #include <string.h> #include "ih264_typedefs.h" #include "ithread.h" #include "ih264d_deblocking.h" #include "ih264d_parse_slice.h" #include "ih264d_parse_cavlc.h" #include "ih264d_dpb_manager.h" #include "ih264d_defs.h" #include "ih264d_structs.h" #include "ih264d_mem_request.h" #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_tables.h" #include "ih264d_debug.h" #include "ih264d_mb_utils.h" #include "ih264d_error_handler.h" #include "ih264d_dpb_manager.h" #include "ih264d_utils.h" #include "ih264d_defs.h" #include "ih264d_tables.h" #include "ih264d_inter_pred.h" #include "ih264d_dpb_manager.h" #include "iv.h" #include "ivd.h" #include "ih264d_format_conv.h" #include "ih264_error.h" #include "ih264_disp_mgr.h" #include "ih264_buf_mgr.h" #include "ih264d_utils.h" /*! ************************************************************************** * \if Function name : ih264d_is_end_of_pic \endif * * \brief * Determines whether current slice is first slice of a new picture as * defined in 7.4.1.2.4 of 14496-10. * * \return * Return 1 if current slice is first slice of a new picture * Otherwise it returns 0 ************************************************************************** */ UWORD8 ih264d_is_end_of_pic(UWORD16 u2_frame_num, UWORD8 u1_nal_ref_idc, pocstruct_t *ps_cur_poc, pocstruct_t *ps_prev_poc, dec_slice_params_t * ps_prev_slice, /*!< Previous slice parameters*/ UWORD8 u1_pic_order_cnt_type, UWORD8 u1_nal_unit_type, UWORD32 u4_idr_pic_id, UWORD8 u1_field_pic_flag, UWORD8 u1_bottom_field_flag) { WORD8 i1_is_end_of_pic; WORD8 a, b, c, d, e, f, g, h; a = b = c = d = e = f = g = h = 0; a = (ps_prev_slice->u2_frame_num != u2_frame_num); b = (ps_prev_slice->u1_field_pic_flag != u1_field_pic_flag); if(u1_field_pic_flag && ps_prev_slice->u1_field_pic_flag) c = (u1_bottom_field_flag != ps_prev_slice->u1_bottom_field_flag); d = (u1_nal_ref_idc == 0 && ps_prev_slice->u1_nal_ref_idc != 0) || (u1_nal_ref_idc != 0 && ps_prev_slice->u1_nal_ref_idc == 0); if(!a) { if((u1_pic_order_cnt_type == 0) && (ps_prev_slice->u1_pic_order_cnt_type == 0)) { e = ((ps_cur_poc->i4_pic_order_cnt_lsb != ps_prev_poc->i4_pic_order_cnt_lsb) || (ps_cur_poc->i4_delta_pic_order_cnt_bottom != ps_prev_poc->i4_delta_pic_order_cnt_bottom)); } if((u1_pic_order_cnt_type == 1) && (ps_prev_slice->u1_pic_order_cnt_type == 1)) { f = ((ps_cur_poc->i4_delta_pic_order_cnt[0] != ps_prev_poc->i4_delta_pic_order_cnt[0]) || (ps_cur_poc->i4_delta_pic_order_cnt[1] != ps_prev_poc->i4_delta_pic_order_cnt[1])); } } if((u1_nal_unit_type == IDR_SLICE_NAL) && (ps_prev_slice->u1_nal_unit_type == IDR_SLICE_NAL)) { g = (u4_idr_pic_id != ps_prev_slice->u4_idr_pic_id); } if((u1_nal_unit_type == IDR_SLICE_NAL) && (ps_prev_slice->u1_nal_unit_type != IDR_SLICE_NAL)) { h = 1; } i1_is_end_of_pic = a + b + c + d + e + f + g + h; return (i1_is_end_of_pic); } /*! ************************************************************************** * \if Function name : ih264d_decode_pic_order_cnt \endif * * \brief * Calculates picture order count of picture. * * \return * Returns the pic order count of the picture to which current * Slice belongs. * ************************************************************************** */ WORD32 ih264d_decode_pic_order_cnt(UWORD8 u1_is_idr_slice, UWORD32 u2_frame_num, pocstruct_t *ps_prev_poc, pocstruct_t *ps_cur_poc, dec_slice_params_t *ps_cur_slice, /*!< Pointer to current slice Params*/ dec_pic_params_t * ps_pps, UWORD8 u1_nal_ref_idc, UWORD8 u1_bottom_field_flag, UWORD8 u1_field_pic_flag, WORD32 *pi4_poc) { WORD64 i8_pic_msb; WORD32 i4_top_field_order_cnt = 0, i4_bottom_field_order_cnt = 0; dec_seq_params_t *ps_seq = ps_pps->ps_sps; WORD32 i4_prev_frame_num_ofst; switch(ps_seq->u1_pic_order_cnt_type) { case 0: /* POC TYPE 0 */ if(u1_is_idr_slice) { ps_prev_poc->i4_pic_order_cnt_msb = 0; ps_prev_poc->i4_pic_order_cnt_lsb = 0; } if(ps_prev_poc->u1_mmco_equalto5) { if(ps_prev_poc->u1_bot_field != 1) { ps_prev_poc->i4_pic_order_cnt_msb = 0; ps_prev_poc->i4_pic_order_cnt_lsb = ps_prev_poc->i4_top_field_order_count; } else { ps_prev_poc->i4_pic_order_cnt_msb = 0; ps_prev_poc->i4_pic_order_cnt_lsb = 0; } } if((ps_cur_poc->i4_pic_order_cnt_lsb < ps_prev_poc->i4_pic_order_cnt_lsb) && ((ps_prev_poc->i4_pic_order_cnt_lsb - ps_cur_poc->i4_pic_order_cnt_lsb) >= (ps_seq->i4_max_pic_order_cntLsb >> 1))) { i8_pic_msb = (WORD64)ps_prev_poc->i4_pic_order_cnt_msb + ps_seq->i4_max_pic_order_cntLsb; } else if((ps_cur_poc->i4_pic_order_cnt_lsb > ps_prev_poc->i4_pic_order_cnt_lsb) && ((ps_cur_poc->i4_pic_order_cnt_lsb - ps_prev_poc->i4_pic_order_cnt_lsb) >= (ps_seq->i4_max_pic_order_cntLsb >> 1))) { i8_pic_msb = (WORD64)ps_prev_poc->i4_pic_order_cnt_msb - ps_seq->i4_max_pic_order_cntLsb; } else { i8_pic_msb = ps_prev_poc->i4_pic_order_cnt_msb; } if(!u1_field_pic_flag || !u1_bottom_field_flag) { WORD64 i8_result = i8_pic_msb + ps_cur_poc->i4_pic_order_cnt_lsb; if(IS_OUT_OF_RANGE_S32(i8_result)) { return ERROR_INV_POC; } i4_top_field_order_cnt = i8_result; } if(!u1_field_pic_flag) { WORD64 i8_result = (WORD64)i4_top_field_order_cnt + ps_cur_poc->i4_delta_pic_order_cnt_bottom; if(IS_OUT_OF_RANGE_S32(i8_result)) { return ERROR_INV_POC; } i4_bottom_field_order_cnt = i8_result; } else if(u1_bottom_field_flag) { WORD64 i8_result = i8_pic_msb + ps_cur_poc->i4_pic_order_cnt_lsb; if(IS_OUT_OF_RANGE_S32(i8_result)) { return ERROR_INV_POC; } i4_bottom_field_order_cnt = i8_result; } if(IS_OUT_OF_RANGE_S32(i8_pic_msb)) { return ERROR_INV_POC; } ps_cur_poc->i4_pic_order_cnt_msb = i8_pic_msb; break; case 1: { /* POC TYPE 1 */ UWORD8 i; WORD32 prev_frame_num; WORD32 frame_num_ofst; WORD32 abs_frm_num; WORD32 poc_cycle_cnt, frame_num_in_poc_cycle; WORD64 i8_expected_delta_poc_cycle; WORD32 expected_poc; WORD64 i8_result; prev_frame_num = (WORD32)ps_cur_slice->u2_frame_num; if(!u1_is_idr_slice) { if(ps_cur_slice->u1_mmco_equalto5) { prev_frame_num = 0; i4_prev_frame_num_ofst = 0; } else { i4_prev_frame_num_ofst = ps_prev_poc->i4_prev_frame_num_ofst; } } else i4_prev_frame_num_ofst = 0; /* 1. Derivation for FrameNumOffset */ if(u1_is_idr_slice) { frame_num_ofst = 0; ps_cur_poc->i4_delta_pic_order_cnt[0] = 0; ps_cur_poc->i4_delta_pic_order_cnt[1] = 0; } else if(prev_frame_num > ((WORD32)u2_frame_num)) { WORD64 i8_result = i4_prev_frame_num_ofst + (WORD64)ps_seq->u2_u4_max_pic_num_minus1 + 1; if(IS_OUT_OF_RANGE_S32(i8_result)) { return ERROR_INV_FRAME_NUM; } frame_num_ofst = i8_result; } else frame_num_ofst = i4_prev_frame_num_ofst; /* 2. Derivation for absFrameNum */ if(0 != ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle) { WORD64 i8_result = frame_num_ofst + (WORD64)u2_frame_num; if(IS_OUT_OF_RANGE_S32(i8_result)) { return ERROR_INV_FRAME_NUM; } abs_frm_num = i8_result; } else abs_frm_num = 0; if((u1_nal_ref_idc == 0) && (abs_frm_num > 0)) abs_frm_num = abs_frm_num - 1; /* 4. expectedDeltaPerPicOrderCntCycle is derived as */ i8_expected_delta_poc_cycle = 0; for(i = 0; i < ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle; i++) { i8_expected_delta_poc_cycle += ps_seq->i4_ofst_for_ref_frame[i]; } /* 3. When absFrameNum > 0, picOrderCntCycleCnt and frame_num_in_poc_cycle are derived as : */ /* 5. expectedPicOrderCnt is derived as : */ if(abs_frm_num > 0) { poc_cycle_cnt = DIV((abs_frm_num - 1), ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle); frame_num_in_poc_cycle = MOD((abs_frm_num - 1), ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle); i8_result = poc_cycle_cnt * i8_expected_delta_poc_cycle; for(i = 0; i <= frame_num_in_poc_cycle; i++) { i8_result = i8_result + ps_seq->i4_ofst_for_ref_frame[i]; } if(IS_OUT_OF_RANGE_S32(i8_result)) return ERROR_INV_POC; expected_poc = (WORD32)i8_result; } else expected_poc = 0; if(u1_nal_ref_idc == 0) { i8_result = (WORD64)expected_poc + ps_seq->i4_ofst_for_non_ref_pic; if(IS_OUT_OF_RANGE_S32(i8_result)) return ERROR_INV_POC; expected_poc = (WORD32)i8_result; } /* 6. TopFieldOrderCnt or BottomFieldOrderCnt are derived as */ if(!u1_field_pic_flag) { i8_result = (WORD64)expected_poc + ps_cur_poc->i4_delta_pic_order_cnt[0]; if(IS_OUT_OF_RANGE_S32(i8_result)) return ERROR_INV_POC; i4_top_field_order_cnt = (WORD32)i8_result; i8_result = (WORD64)i4_top_field_order_cnt + ps_seq->i4_ofst_for_top_to_bottom_field + ps_cur_poc->i4_delta_pic_order_cnt[1]; if(IS_OUT_OF_RANGE_S32(i8_result)) return ERROR_INV_POC; i4_bottom_field_order_cnt = (WORD32)i8_result; } else if(!u1_bottom_field_flag) { i8_result = (WORD64)expected_poc + ps_cur_poc->i4_delta_pic_order_cnt[0]; if(IS_OUT_OF_RANGE_S32(i8_result)) return ERROR_INV_POC; i4_top_field_order_cnt = (WORD32)i8_result; } else { i8_result = (WORD64)expected_poc + ps_seq->i4_ofst_for_top_to_bottom_field + ps_cur_poc->i4_delta_pic_order_cnt[0]; if(IS_OUT_OF_RANGE_S32(i8_result)) return ERROR_INV_POC; i4_bottom_field_order_cnt = (WORD32)i8_result; } /* Copy the current POC info into Previous POC structure */ ps_cur_poc->i4_prev_frame_num_ofst = frame_num_ofst; } break; case 2: { /* POC TYPE 2 */ WORD32 prev_frame_num; WORD32 frame_num_ofst; WORD32 tmp_poc; prev_frame_num = (WORD32)ps_cur_slice->u2_frame_num; if(!u1_is_idr_slice) { if(ps_cur_slice->u1_mmco_equalto5) { prev_frame_num = 0; i4_prev_frame_num_ofst = 0; } else i4_prev_frame_num_ofst = ps_prev_poc->i4_prev_frame_num_ofst; } else i4_prev_frame_num_ofst = 0; /* 1. Derivation for FrameNumOffset */ if(u1_is_idr_slice) { frame_num_ofst = 0; ps_cur_poc->i4_delta_pic_order_cnt[0] = 0; ps_cur_poc->i4_delta_pic_order_cnt[1] = 0; } else if(prev_frame_num > ((WORD32)u2_frame_num)) { WORD64 i8_result = i4_prev_frame_num_ofst + (WORD64)ps_seq->u2_u4_max_pic_num_minus1 + 1; if(IS_OUT_OF_RANGE_S32(i8_result)) { return ERROR_INV_FRAME_NUM; } frame_num_ofst = i8_result; } else frame_num_ofst = i4_prev_frame_num_ofst; /* 2. Derivation for tempPicOrderCnt */ if(u1_is_idr_slice) tmp_poc = 0; else if(u1_nal_ref_idc == 0) { WORD64 i8_result = ((frame_num_ofst + (WORD64)u2_frame_num) << 1) - 1; if(IS_OUT_OF_RANGE_S32(i8_result)) { return ERROR_INV_POC; } tmp_poc = i8_result; } else { WORD64 i8_result = (frame_num_ofst + (WORD64)u2_frame_num) << 1; if(IS_OUT_OF_RANGE_S32(i8_result)) { return ERROR_INV_POC; } tmp_poc = i8_result; } /* 6. TopFieldOrderCnt or BottomFieldOrderCnt are derived as */ if(!u1_field_pic_flag) { i4_top_field_order_cnt = tmp_poc; i4_bottom_field_order_cnt = tmp_poc; } else if(!u1_bottom_field_flag) i4_top_field_order_cnt = tmp_poc; else i4_bottom_field_order_cnt = tmp_poc; /* Copy the current POC info into Previous POC structure */ ps_prev_poc->i4_prev_frame_num_ofst = frame_num_ofst; ps_cur_poc->i4_prev_frame_num_ofst = frame_num_ofst; } break; default: return ERROR_INV_POC_TYPE_T; break; } if(!u1_field_pic_flag) // or a complementary field pair { *pi4_poc = MIN(i4_top_field_order_cnt, i4_bottom_field_order_cnt); ps_pps->i4_top_field_order_cnt = i4_top_field_order_cnt; ps_pps->i4_bottom_field_order_cnt = i4_bottom_field_order_cnt; } else if(!u1_bottom_field_flag) { *pi4_poc = i4_top_field_order_cnt; ps_pps->i4_top_field_order_cnt = i4_top_field_order_cnt; } else { *pi4_poc = i4_bottom_field_order_cnt; ps_pps->i4_bottom_field_order_cnt = i4_bottom_field_order_cnt; } ps_pps->i4_avg_poc = *pi4_poc; return OK; } /*! ************************************************************************** * \if Function name : ih264d_end_of_pic_processing \endif * * \brief * Performs the end of picture processing. * * It performs deblocking on the current picture and sets the i4_status of * current picture as decoded. * * \return * 0 on Success and Error code otherwise. ************************************************************************** */ WORD32 ih264d_end_of_pic_processing(dec_struct_t *ps_dec) { UWORD8 u1_pic_type, u1_nal_ref_idc; dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; /* If nal_ref_idc is equal to 0 for one slice or slice data partition NAL unit of a particular picture, it shall be equal to 0 for all slice and slice data partition NAL units of the picture. nal_ref_idc greater than 0 indicates that the content of the NAL unit belongs to a decoded picture that is stored and marked for use as a reference picture in the decoded picture buffer. */ /* 1. Do MMCO 2. Add Cur Pic to list of reference pics. */ /* Call MMCO */ u1_pic_type = 0; u1_nal_ref_idc = ps_cur_slice->u1_nal_ref_idc; if(u1_nal_ref_idc) { if(ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL) { ps_dec->ps_dpb_mgr->u1_mmco_error_in_seq = 0; if(ps_dec->ps_dpb_cmds->u1_long_term_reference_flag == 0) { ih264d_reset_ref_bufs(ps_dec->ps_dpb_mgr); /* ignore DPB errors */ ih264d_insert_st_node(ps_dec->ps_dpb_mgr, ps_dec->ps_cur_pic, ps_dec->u1_pic_buf_id, ps_cur_slice->u2_frame_num); ps_dec->ps_dpb_mgr->u1_max_lt_frame_idx = NO_LONG_TERM_INDICIES; } else { /* Equivalent of inserting a pic directly as longterm Pic */ { /* ignore DPB errors */ ih264d_insert_st_node(ps_dec->ps_dpb_mgr, ps_dec->ps_cur_pic, ps_dec->u1_pic_buf_id, ps_cur_slice->u2_frame_num); /* Set longTermIdx = 0, MaxLongTermFrameIdx = 0 */ ih264d_delete_st_node_or_make_lt( ps_dec->ps_dpb_mgr, ps_cur_slice->u2_frame_num, 0, ps_cur_slice->u1_field_pic_flag); ps_dec->ps_dpb_mgr->u1_max_lt_frame_idx = 0; } } } else { { UWORD16 u2_pic_num = ps_cur_slice->u2_frame_num; if(!ps_dec->ps_dpb_mgr->u1_mmco_error_in_seq) { WORD32 ret = ih264d_do_mmco_buffer(ps_dec->ps_dpb_cmds, ps_dec->ps_dpb_mgr, ps_dec->ps_cur_sps->u1_num_ref_frames, u2_pic_num, (ps_dec->ps_cur_sps->u2_u4_max_pic_num_minus1), ps_dec->u1_nal_unit_type, ps_dec->ps_cur_pic, ps_dec->u1_pic_buf_id, ps_cur_slice->u1_field_pic_flag, ps_dec->e_dec_status); ps_dec->ps_dpb_mgr->u1_mmco_error_in_seq = ret != OK; } } } ih264d_update_default_index_list(ps_dec->ps_dpb_mgr); } if(ps_cur_slice->u1_field_pic_flag) { if(ps_cur_slice->u1_bottom_field_flag) { if(u1_nal_ref_idc) u1_pic_type = u1_pic_type | BOT_REF; u1_pic_type = u1_pic_type | BOT_FLD; } else { if(u1_nal_ref_idc) u1_pic_type = u1_pic_type | TOP_REF; u1_pic_type = u1_pic_type | TOP_FLD; } } else u1_pic_type = TOP_REF | BOT_REF; ps_dec->ps_cur_pic->u1_pic_type |= u1_pic_type; if(ps_cur_slice->u1_field_pic_flag) { H264_DEC_DEBUG_PRINT("Toggling secondField\n"); ps_dec->u1_second_field = 1 - ps_dec->u1_second_field; } return OK; } /*****************************************************************************/ /* */ /* Function Name : init_dpb_size */ /* */ /* Description : This function calculates the DBP i4_size in frames */ /* Inputs : ps_seq - current sequence params */ /* */ /* Globals : None */ /* */ /* Outputs : None */ /* */ /* Returns : DPB in frames */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 28 04 2005 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_get_dpb_size(dec_seq_params_t *ps_seq) { WORD32 i4_size; UWORD8 u1_level_idc; u1_level_idc = ps_seq->u1_level_idc; switch(u1_level_idc) { case 10: i4_size = 152064; break; case 11: i4_size = 345600; break; case 12: i4_size = 912384; break; case 13: i4_size = 912384; break; case 20: i4_size = 912384; break; case 21: i4_size = 1824768; break; case 22: i4_size = 3110400; break; case 30: i4_size = 3110400; break; case 31: i4_size = 6912000; break; case 32: i4_size = 7864320; break; case 40: i4_size = 12582912; break; case 41: i4_size = 12582912; break; case 42: i4_size = 12582912; break; case 50: i4_size = 42393600; break; case 51: i4_size = 70778880; break; case 52: i4_size = 70778880; break; default: i4_size = 70778880; break; } i4_size /= (ps_seq->u2_frm_wd_in_mbs * (ps_seq->u2_frm_ht_in_mbs << (1 - ps_seq->u1_frame_mbs_only_flag))); i4_size /= 384; i4_size = MIN(i4_size, 16); i4_size = MAX(i4_size, 1); return (i4_size); } /**************************************************************************/ /* This function initialises the value of ps_dec->u1_recon_mb_grp */ /* ps_dec->u1_recon_mb_grp must satisfy the following criteria */ /* - multiple of 2 (required for N/2 parse-mvpred design) */ /* - multiple of 4 (if it is not a frame_mbs_only sequence), */ /* in this case N/2 itself needs to be even for mbpair processing */ /* - lesser than ps_dec->u2_frm_wd_in_mbs/2 (at least 3 N-Chunks */ /* should make a row to ensure proper MvTop transferring) */ /**************************************************************************/ WORD32 ih264d_init_dec_mb_grp(dec_struct_t *ps_dec) { dec_seq_params_t *ps_seq = ps_dec->ps_cur_sps; UWORD8 u1_frm = ps_seq->u1_frame_mbs_only_flag; ps_dec->u1_recon_mb_grp = ps_dec->u2_frm_wd_in_mbs << ps_seq->u1_mb_aff_flag; ps_dec->u1_recon_mb_grp_pair = ps_dec->u1_recon_mb_grp >> 1; if(!ps_dec->u1_recon_mb_grp) { return ERROR_MB_GROUP_ASSGN_T; } ps_dec->u4_num_mbs_prev_nmb = ps_dec->u1_recon_mb_grp; return OK; } /*! ************************************************************************** * \if Function name : ih264d_init_pic \endif * * \brief * Initializes the picture. * * \return * 0 on Success and Error code otherwise * * \note * This function is called when first slice of the * NON -IDR picture is encountered. ************************************************************************** */ WORD32 ih264d_init_pic(dec_struct_t *ps_dec, UWORD16 u2_frame_num, WORD32 i4_poc, dec_pic_params_t *ps_pps) { dec_seq_params_t *ps_seq = ps_pps->ps_sps; prev_seq_params_t * ps_prev_seq_params = &ps_dec->s_prev_seq_params; WORD32 i4_pic_bufs; WORD32 ret; ps_dec->ps_cur_slice->u2_frame_num = u2_frame_num; ps_dec->ps_cur_slice->i4_poc = i4_poc; ps_dec->ps_cur_pps = ps_pps; ps_dec->ps_cur_pps->pv_codec_handle = ps_dec; ps_dec->ps_cur_sps = ps_seq; ps_dec->ps_dpb_mgr->i4_max_frm_num = ps_seq->u2_u4_max_pic_num_minus1 + 1; ps_dec->ps_dpb_mgr->u2_pic_ht = ps_dec->u2_pic_ht; ps_dec->ps_dpb_mgr->u2_pic_wd = ps_dec->u2_pic_wd; ps_dec->i4_pic_type = NA_SLICE; ps_dec->i4_frametype = IV_NA_FRAME; ps_dec->i4_content_type = IV_CONTENTTYPE_NA; /*--------------------------------------------------------------------*/ /* Get the value of MaxMbAddress and frmheight in Mbs */ /*--------------------------------------------------------------------*/ ps_seq->u2_max_mb_addr = (ps_seq->u2_frm_wd_in_mbs * (ps_dec->u2_pic_ht >> (4 + ps_dec->ps_cur_slice->u1_field_pic_flag))) - 1; ps_dec->u2_frm_ht_in_mbs = (ps_dec->u2_pic_ht >> (4 + ps_dec->ps_cur_slice->u1_field_pic_flag)); /***************************************************************************/ /* If change in Level or the required PicBuffers i4_size is more than the */ /* current one FREE the current PicBuffers and allocate affresh */ /***************************************************************************/ if(!ps_dec->u1_init_dec_flag) { ps_dec->u1_max_dec_frame_buffering = ih264d_get_dpb_size(ps_seq); ps_dec->i4_display_delay = ps_dec->u1_max_dec_frame_buffering; if((1 == ps_seq->u1_vui_parameters_present_flag) && (1 == ps_seq->s_vui.u1_bitstream_restriction_flag)) { if(ps_seq->u1_frame_mbs_only_flag == 1) ps_dec->i4_display_delay = ps_seq->s_vui.u4_num_reorder_frames + 1; else ps_dec->i4_display_delay = ps_seq->s_vui.u4_num_reorder_frames * 2 + 2; } if(IVD_DECODE_FRAME_OUT == ps_dec->e_frm_out_mode) ps_dec->i4_display_delay = 0; if(ps_dec->u4_share_disp_buf == 0) { if(ps_seq->u1_frame_mbs_only_flag == 1) ps_dec->u1_pic_bufs = ps_dec->i4_display_delay + ps_seq->u1_num_ref_frames + 1; else ps_dec->u1_pic_bufs = ps_dec->i4_display_delay + ps_seq->u1_num_ref_frames * 2 + 2; } else { ps_dec->u1_pic_bufs = (WORD32)ps_dec->u4_num_disp_bufs; } /* Ensure at least two buffers are allocated */ ps_dec->u1_pic_bufs = MAX(ps_dec->u1_pic_bufs, 2); if(ps_dec->u4_share_disp_buf == 0) ps_dec->u1_pic_bufs = MIN(ps_dec->u1_pic_bufs, (H264_MAX_REF_PICS * 2)); ps_dec->u1_max_dec_frame_buffering = MIN( ps_dec->u1_max_dec_frame_buffering, ps_dec->u1_pic_bufs); /* Temporary hack to run Tractor Cav/Cab/MbAff Profiler streams also for CAFI1_SVA_C.264 in conformance*/ if(ps_dec->u1_init_dec_flag) { ih264d_release_pics_in_dpb((void *)ps_dec, ps_dec->u1_pic_bufs); ih264d_release_display_bufs(ps_dec); ih264d_reset_ref_bufs(ps_dec->ps_dpb_mgr); } /*********************************************************************/ /* Configuring decoder parameters based on level and then */ /* fresh pointer initialisation in decoder scratch and state buffers */ /*********************************************************************/ if(!ps_dec->u1_init_dec_flag || ((ps_seq->u1_level_idc < H264_LEVEL_3_0) ^ (ps_prev_seq_params->u1_level_idc < H264_LEVEL_3_0))) { ret = ih264d_init_dec_mb_grp(ps_dec); if(ret != OK) return ret; } ret = ih264d_allocate_dynamic_bufs(ps_dec); if(ret != OK) { /* Free any dynamic buffers that are allocated */ ih264d_free_dynamic_bufs(ps_dec); ps_dec->i4_error_code = IVD_MEM_ALLOC_FAILED; return IVD_MEM_ALLOC_FAILED; } ret = ih264d_create_pic_buffers(ps_dec->u1_pic_bufs, ps_dec); if(ret != OK) return ret; ret = ih264d_create_mv_bank(ps_dec, ps_dec->u2_pic_wd, ps_dec->u2_pic_ht); if(ret != OK) return ret; /* In shared mode, set all of them as used by display */ if(ps_dec->u4_share_disp_buf == 1) { WORD32 i; for(i = 0; i < ps_dec->u1_pic_bufs; i++) { ih264_buf_mgr_set_status((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, i, BUF_MGR_IO); } } ps_dec->u1_init_dec_flag = 1; ps_prev_seq_params->u2_frm_wd_in_mbs = ps_seq->u2_frm_wd_in_mbs; ps_prev_seq_params->u1_level_idc = ps_seq->u1_level_idc; ps_prev_seq_params->u1_profile_idc = ps_seq->u1_profile_idc; ps_prev_seq_params->u2_frm_ht_in_mbs = ps_seq->u2_frm_ht_in_mbs; ps_prev_seq_params->u1_frame_mbs_only_flag = ps_seq->u1_frame_mbs_only_flag; ps_prev_seq_params->u1_direct_8x8_inference_flag = ps_seq->u1_direct_8x8_inference_flag; ps_dec->i4_cur_display_seq = 0; ps_dec->i4_prev_max_display_seq = 0; ps_dec->i4_max_poc = 0; { /* 0th entry of CtxtIncMbMap will be always be containing default values for CABAC context representing MB not available */ ctxt_inc_mb_info_t *p_DefCtxt = ps_dec->p_ctxt_inc_mb_map - 1; UWORD8 *pu1_temp; WORD8 i; p_DefCtxt->u1_mb_type = CAB_SKIP; p_DefCtxt->u1_cbp = 0x0f; p_DefCtxt->u1_intra_chroma_pred_mode = 0; p_DefCtxt->u1_yuv_dc_csbp = 0x7; p_DefCtxt->u1_transform8x8_ctxt = 0; pu1_temp = (UWORD8*)p_DefCtxt->i1_ref_idx; for(i = 0; i < 4; i++, pu1_temp++) (*pu1_temp) = 0; pu1_temp = (UWORD8*)p_DefCtxt->u1_mv; for(i = 0; i < 16; i++, pu1_temp++) (*pu1_temp) = 0; ps_dec->ps_def_ctxt_mb_info = p_DefCtxt; } } /* reset DBP commands read u4_flag */ ps_dec->ps_dpb_cmds->u1_dpb_commands_read = 0; return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_get_next_display_field */ /* */ /* Description : Application calls this module to get the next field */ /* to be displayed */ /* */ /* Inputs : 1. IBUFAPI_Handle Hnadle to the Display buffer */ /* 2. IH264DEC_DispUnit Pointer to the display struct */ /* */ /* Globals : */ /* */ /* */ /* Processing : None */ /* Outputs : None */ /* Returns : None */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 27 05 2005 Ittiam Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_get_next_display_field(dec_struct_t * ps_dec, ivd_out_bufdesc_t *ps_out_buffer, ivd_get_display_frame_op_t *pv_disp_op) { pic_buffer_t *pic_buf; UWORD8 i1_cur_fld; WORD32 u4_api_ret = -1; WORD32 i4_disp_buf_id; iv_yuv_buf_t *ps_op_frm; ps_op_frm = &(ps_dec->s_disp_frame_info); H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); pic_buf = (pic_buffer_t *)ih264_disp_mgr_get( (disp_mgr_t *)ps_dec->pv_disp_buf_mgr, &i4_disp_buf_id); ps_dec->u4_num_fld_in_frm = 0; u4_api_ret = -1; pv_disp_op->u4_ts = 0; pv_disp_op->e_output_format = ps_dec->u1_chroma_format; pv_disp_op->s_disp_frm_buf.pv_y_buf = ps_out_buffer->pu1_bufs[0]; pv_disp_op->s_disp_frm_buf.pv_u_buf = ps_out_buffer->pu1_bufs[1]; pv_disp_op->s_disp_frm_buf.pv_v_buf = ps_out_buffer->pu1_bufs[2]; ps_dec->i4_display_index = DEFAULT_POC; if(pic_buf != NULL) { ps_dec->pv_disp_sei_params = &pic_buf->s_sei_pic; pv_disp_op->e4_fld_type = 0; pv_disp_op->u4_disp_buf_id = i4_disp_buf_id; ps_op_frm->u4_y_ht = pic_buf->u2_disp_height << 1; ps_op_frm->u4_u_ht = ps_op_frm->u4_v_ht = ps_op_frm->u4_y_ht >> 1; ps_op_frm->u4_y_wd = pic_buf->u2_disp_width; ps_op_frm->u4_u_wd = ps_op_frm->u4_v_wd = ps_op_frm->u4_y_wd >> 1; ps_op_frm->u4_y_strd = pic_buf->u2_frm_wd_y; ps_op_frm->u4_u_strd = ps_op_frm->u4_v_strd = pic_buf->u2_frm_wd_uv; /* ! */ pv_disp_op->u4_ts = pic_buf->u4_ts; ps_dec->i4_display_index = pic_buf->i4_poc; /* set the start of the Y, U and V buffer pointer for display */ ps_op_frm->pv_y_buf = pic_buf->pu1_buf1 + pic_buf->u2_crop_offset_y; ps_op_frm->pv_u_buf = pic_buf->pu1_buf2 + pic_buf->u2_crop_offset_uv; ps_op_frm->pv_v_buf = pic_buf->pu1_buf3 + pic_buf->u2_crop_offset_uv; ps_dec->u4_num_fld_in_frm++; ps_dec->u4_num_fld_in_frm++; u4_api_ret = 0; if(pic_buf->u1_picturetype == 0) pv_disp_op->u4_progressive_frame_flag = 1; else pv_disp_op->u4_progressive_frame_flag = 0; } H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); pv_disp_op->u4_error_code = u4_api_ret; pv_disp_op->e_pic_type = 0xFFFFFFFF; //Junk; if(u4_api_ret) { pv_disp_op->u4_error_code = 1; //put a proper error code here } else { //Release the buffer if being sent for display UWORD32 temp; UWORD32 dest_inc_Y = 0, dest_inc_UV = 0; pv_disp_op->s_disp_frm_buf.u4_y_wd = temp = MIN(ps_op_frm->u4_y_wd, ps_op_frm->u4_y_strd); pv_disp_op->s_disp_frm_buf.u4_u_wd = pv_disp_op->s_disp_frm_buf.u4_y_wd >> 1; pv_disp_op->s_disp_frm_buf.u4_v_wd = pv_disp_op->s_disp_frm_buf.u4_y_wd >> 1; pv_disp_op->s_disp_frm_buf.u4_y_ht = ps_op_frm->u4_y_ht; pv_disp_op->s_disp_frm_buf.u4_u_ht = pv_disp_op->s_disp_frm_buf.u4_y_ht >> 1; pv_disp_op->s_disp_frm_buf.u4_v_ht = pv_disp_op->s_disp_frm_buf.u4_y_ht >> 1; if(0 == ps_dec->u4_share_disp_buf) { pv_disp_op->s_disp_frm_buf.u4_y_strd = pv_disp_op->s_disp_frm_buf.u4_y_wd; pv_disp_op->s_disp_frm_buf.u4_u_strd = pv_disp_op->s_disp_frm_buf.u4_y_wd >> 1; pv_disp_op->s_disp_frm_buf.u4_v_strd = pv_disp_op->s_disp_frm_buf.u4_y_wd >> 1; } else { pv_disp_op->s_disp_frm_buf.u4_y_strd = ps_op_frm->u4_y_strd; } if(ps_dec->u4_app_disp_width) { pv_disp_op->s_disp_frm_buf.u4_y_strd = MAX( ps_dec->u4_app_disp_width, pv_disp_op->s_disp_frm_buf.u4_y_strd); } pv_disp_op->u4_error_code = 0; if(pv_disp_op->e_output_format == IV_YUV_420P) { UWORD32 i; pv_disp_op->s_disp_frm_buf.u4_u_strd = pv_disp_op->s_disp_frm_buf.u4_y_strd >> 1; pv_disp_op->s_disp_frm_buf.u4_v_strd = pv_disp_op->s_disp_frm_buf.u4_y_strd >> 1; pv_disp_op->s_disp_frm_buf.u4_u_wd = ps_op_frm->u4_y_wd >> 1; pv_disp_op->s_disp_frm_buf.u4_v_wd = ps_op_frm->u4_y_wd >> 1; if(1 == ps_dec->u4_share_disp_buf) { pv_disp_op->s_disp_frm_buf.pv_y_buf = ps_op_frm->pv_y_buf; for(i = 0; i < MAX_DISP_BUFS_NEW; i++) { UWORD8 *buf = ps_dec->disp_bufs[i].buf[0]; buf += ps_dec->disp_bufs[i].u4_ofst[0]; if(((UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_y_buf - pic_buf->u2_crop_offset_y) == buf) { buf = ps_dec->disp_bufs[i].buf[1]; buf += ps_dec->disp_bufs[i].u4_ofst[1]; pv_disp_op->s_disp_frm_buf.pv_u_buf = buf + (pic_buf->u2_crop_offset_uv / YUV420SP_FACTOR); buf = ps_dec->disp_bufs[i].buf[2]; buf += ps_dec->disp_bufs[i].u4_ofst[2]; pv_disp_op->s_disp_frm_buf.pv_v_buf = buf + (pic_buf->u2_crop_offset_uv / YUV420SP_FACTOR); } } } } else if((pv_disp_op->e_output_format == IV_YUV_420SP_UV) || (pv_disp_op->e_output_format == IV_YUV_420SP_VU)) { pv_disp_op->s_disp_frm_buf.u4_u_strd = pv_disp_op->s_disp_frm_buf.u4_y_strd; pv_disp_op->s_disp_frm_buf.u4_v_strd = 0; if(1 == ps_dec->u4_share_disp_buf) { UWORD32 i; pv_disp_op->s_disp_frm_buf.pv_y_buf = ps_op_frm->pv_y_buf; for(i = 0; i < MAX_DISP_BUFS_NEW; i++) { UWORD8 *buf = ps_dec->disp_bufs[i].buf[0]; buf += ps_dec->disp_bufs[i].u4_ofst[0]; if((UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_y_buf - pic_buf->u2_crop_offset_y == buf) { buf = ps_dec->disp_bufs[i].buf[1]; buf += ps_dec->disp_bufs[i].u4_ofst[1]; pv_disp_op->s_disp_frm_buf.pv_u_buf = buf + pic_buf->u2_crop_offset_uv; ; buf = ps_dec->disp_bufs[i].buf[2]; buf += ps_dec->disp_bufs[i].u4_ofst[2]; pv_disp_op->s_disp_frm_buf.pv_v_buf = buf + pic_buf->u2_crop_offset_uv; ; } } } pv_disp_op->s_disp_frm_buf.u4_u_wd = pv_disp_op->s_disp_frm_buf.u4_y_wd; pv_disp_op->s_disp_frm_buf.u4_v_wd = 0; } else if((pv_disp_op->e_output_format == IV_RGB_565) || (pv_disp_op->e_output_format == IV_YUV_422ILE)) { pv_disp_op->s_disp_frm_buf.u4_u_strd = 0; pv_disp_op->s_disp_frm_buf.u4_v_strd = 0; pv_disp_op->s_disp_frm_buf.u4_u_wd = 0; pv_disp_op->s_disp_frm_buf.u4_v_wd = 0; pv_disp_op->s_disp_frm_buf.u4_u_ht = 0; pv_disp_op->s_disp_frm_buf.u4_v_ht = 0; } } return u4_api_ret; } /*****************************************************************************/ /* Function Name : ih264d_release_display_field */ /* */ /* Description : This function releases the display field that was returned */ /* here. */ /* Inputs : ps_dec - Decoder parameters */ /* Globals : None */ /* Processing : Refer bumping process in the standard */ /* Outputs : Assigns display sequence number. */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 27 04 2005 NS Draft */ /* */ /*****************************************************************************/ void ih264d_release_display_field(dec_struct_t *ps_dec, ivd_get_display_frame_op_t *pv_disp_op) { if(1 == pv_disp_op->u4_error_code) { if(1 == ps_dec->u1_flushfrm) { UWORD32 i; if(1 == ps_dec->u4_share_disp_buf) { H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); for(i = 0; i < (MAX_DISP_BUFS_NEW); i++) { if(1 == ps_dec->u4_disp_buf_mapping[i]) { ih264_buf_mgr_release( (buf_mgr_t *)ps_dec->pv_pic_buf_mgr, i, BUF_MGR_IO); ps_dec->u4_disp_buf_mapping[i] = 0; } } H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); memset(ps_dec->u4_disp_buf_to_be_freed, 0, (MAX_DISP_BUFS_NEW) * sizeof(UWORD32)); for(i = 0; i < ps_dec->u1_pic_bufs; i++) ps_dec->u4_disp_buf_mapping[i] = 1; } ps_dec->u1_flushfrm = 0; } } else { H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); if(0 == ps_dec->u4_share_disp_buf) { ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, pv_disp_op->u4_disp_buf_id, BUF_MGR_IO); } else { ps_dec->u4_disp_buf_mapping[pv_disp_op->u4_disp_buf_id] = 1; } H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); } } /*****************************************************************************/ /* Function Name : ih264d_assign_display_seq */ /* */ /* Description : This function implments bumping process. Every outgoing */ /* frame from DPB is assigned a display sequence number */ /* which increases monotonically. System looks for this */ /* number to display a frame. */ /* here. */ /* Inputs : ps_dec - Decoder parameters */ /* Globals : None */ /* Processing : Refer bumping process in the standard */ /* Outputs : Assigns display sequence number. */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 27 04 2005 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_assign_display_seq(dec_struct_t *ps_dec) { WORD32 i; WORD32 i4_min_poc; WORD32 i4_min_poc_buf_id; WORD32 i4_min_index; dpb_manager_t *ps_dpb_mgr = ps_dec->ps_dpb_mgr; WORD32 (*i4_poc_buf_id_map)[3] = ps_dpb_mgr->ai4_poc_buf_id_map; i4_min_poc = 0x7fffffff; i4_min_poc_buf_id = -1; i4_min_index = -1; if(ps_dpb_mgr->i1_poc_buf_id_entries >= ps_dec->i4_display_delay) { for(i = 0; i < MAX_FRAMES; i++) { if((i4_poc_buf_id_map[i][0] != -1) && (DO_NOT_DISP != ps_dpb_mgr->ai4_poc_buf_id_map[i][0])) { /* Checking for <= is necessary to handle cases where there is one valid buffer with poc set to 0x7FFFFFFF. */ if(i4_poc_buf_id_map[i][1] <= i4_min_poc) { i4_min_poc = i4_poc_buf_id_map[i][1]; i4_min_poc_buf_id = i4_poc_buf_id_map[i][0]; i4_min_index = i; } } } if((i4_min_index != -1) && (DO_NOT_DISP != i4_min_poc_buf_id)) { ps_dec->i4_cur_display_seq++; ih264_disp_mgr_add( (disp_mgr_t *)ps_dec->pv_disp_buf_mgr, i4_min_poc_buf_id, ps_dec->i4_cur_display_seq, ps_dec->apv_buf_id_pic_buf_map[i4_min_poc_buf_id]); i4_poc_buf_id_map[i4_min_index][0] = -1; i4_poc_buf_id_map[i4_min_index][1] = 0x7fffffff; ps_dpb_mgr->i1_poc_buf_id_entries--; } else if(DO_NOT_DISP == i4_min_poc_buf_id) { WORD32 i4_error_code; i4_error_code = ERROR_GAPS_IN_FRM_NUM; // i4_error_code |= 1<<IVD_CORRUPTEDDATA; return i4_error_code; } } return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_release_display_bufs */ /* */ /* Description : This function implments bumping process when mmco = 5. */ /* Each outgoing frame from DPB is assigned a display */ /* sequence number which increases monotonically. System */ /* looks for this number to display a frame. */ /* Inputs : ps_dec - Decoder parameters */ /* Globals : None */ /* Processing : Refer bumping process in the standard for mmco = 5 */ /* Outputs : Assigns display sequence number. */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 27 04 2005 NS Draft */ /* */ /*****************************************************************************/ void ih264d_release_display_bufs(dec_struct_t *ps_dec) { WORD32 i, j; WORD32 i4_min_poc; WORD32 i4_min_poc_buf_id; WORD32 i4_min_index; WORD64 i8_temp; dpb_manager_t *ps_dpb_mgr = ps_dec->ps_dpb_mgr; WORD32 (*i4_poc_buf_id_map)[3] = ps_dpb_mgr->ai4_poc_buf_id_map; i4_min_poc = 0x7fffffff; i4_min_poc_buf_id = 0; i4_min_index = 0; ih264d_delete_nonref_nondisplay_pics(ps_dpb_mgr); for(j = 0; j < ps_dpb_mgr->i1_poc_buf_id_entries; j++) { i4_min_poc = 0x7fffffff; for(i = 0; i < MAX_FRAMES; i++) { if(i4_poc_buf_id_map[i][0] != -1) { /* Checking for <= is necessary to handle cases where there is one valid buffer with poc set to 0x7FFFFFFF. */ if(i4_poc_buf_id_map[i][1] <= i4_min_poc) { i4_min_poc = i4_poc_buf_id_map[i][1]; i4_min_poc_buf_id = i4_poc_buf_id_map[i][0]; i4_min_index = i; } } } if(DO_NOT_DISP != i4_min_poc_buf_id) { ps_dec->i4_cur_display_seq++; ih264_disp_mgr_add( (disp_mgr_t *)ps_dec->pv_disp_buf_mgr, i4_min_poc_buf_id, ps_dec->i4_cur_display_seq, ps_dec->apv_buf_id_pic_buf_map[i4_min_poc_buf_id]); i4_poc_buf_id_map[i4_min_index][0] = -1; i4_poc_buf_id_map[i4_min_index][1] = 0x7fffffff; ps_dpb_mgr->ai4_poc_buf_id_map[i4_min_index][2] = 0; } else { i4_poc_buf_id_map[i4_min_index][0] = -1; i4_poc_buf_id_map[i4_min_index][1] = 0x7fffffff; ps_dpb_mgr->ai4_poc_buf_id_map[i4_min_index][2] = 0; } } ps_dpb_mgr->i1_poc_buf_id_entries = 0; i8_temp = (WORD64)ps_dec->i4_prev_max_display_seq + ps_dec->i4_max_poc + ps_dec->u1_max_dec_frame_buffering + 1; /*If i4_prev_max_display_seq overflows integer range, reset it */ ps_dec->i4_prev_max_display_seq = IS_OUT_OF_RANGE_S32(i8_temp)? 0 : i8_temp; ps_dec->i4_max_poc = 0; } /*****************************************************************************/ /* */ /* Function Name : ih264d_assign_pic_num */ /* */ /* Description : This function assigns pic num to each reference frame */ /* depending on the cur_frame_num as speified in section */ /* 8.2.4.1 */ /* */ /* Inputs : ps_dec */ /* */ /* Globals : NO globals used */ /* */ /* Processing : for all ST pictures */ /* if( FrameNum > cur_frame_num) */ /* PicNum = FrameNum - MaxFrameNum */ /* else */ /* PicNum = FrameNum */ /* */ /* Returns : void */ /* */ /* Issues : NO */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 13 07 2002 Jay Draft */ /* */ /*****************************************************************************/ void ih264d_assign_pic_num(dec_struct_t *ps_dec) { dpb_manager_t *ps_dpb_mgr; struct dpb_info_t *ps_next_dpb; WORD8 i; WORD32 i4_cur_frame_num, i4_max_frame_num; WORD32 i4_ref_frame_num; UWORD8 u1_fld_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; i4_max_frame_num = ps_dec->ps_cur_sps->u2_u4_max_pic_num_minus1 + 1; i4_cur_frame_num = ps_dec->ps_cur_pic->i4_frame_num; ps_dpb_mgr = ps_dec->ps_dpb_mgr; /* Start from ST head */ ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; for(i = 0; i < ps_dpb_mgr->u1_num_st_ref_bufs; i++) { WORD32 i4_pic_num; i4_ref_frame_num = ps_next_dpb->ps_pic_buf->i4_frame_num; if(i4_ref_frame_num > i4_cur_frame_num) { /* RefPic Buf frame_num is before Current frame_num in decode order */ i4_pic_num = i4_ref_frame_num - i4_max_frame_num; } else { /* RefPic Buf frame_num is after Current frame_num in decode order */ i4_pic_num = i4_ref_frame_num; } ps_next_dpb->ps_pic_buf->i4_pic_num = i4_pic_num; ps_next_dpb->i4_frame_num = i4_pic_num; ps_next_dpb->ps_pic_buf->u1_long_term_frm_idx = MAX_REF_BUFS + 1; if(u1_fld_pic_flag) { /* Assign the pic num to top fields and bot fields */ ps_next_dpb->s_top_field.i4_pic_num = i4_pic_num * 2 + !(ps_dec->ps_cur_slice->u1_bottom_field_flag); ps_next_dpb->s_bot_field.i4_pic_num = i4_pic_num * 2 + ps_dec->ps_cur_slice->u1_bottom_field_flag; } /* Chase the next link */ ps_next_dpb = ps_next_dpb->ps_prev_short; } if(ps_dec->ps_cur_sps->u1_gaps_in_frame_num_value_allowed_flag && ps_dpb_mgr->u1_num_gaps) { WORD32 i4_start_frm, i4_end_frm; /* Assign pic numbers for gaps */ for(i = 0; i < MAX_FRAMES; i++) { i4_start_frm = ps_dpb_mgr->ai4_gaps_start_frm_num[i]; if(i4_start_frm != INVALID_FRAME_NUM) { if(i4_start_frm > i4_cur_frame_num) { /* gap's frame_num is before Current frame_num in decode order */ i4_start_frm -= i4_max_frame_num; } ps_dpb_mgr->ai4_gaps_start_frm_num[i] = i4_start_frm; i4_end_frm = ps_dpb_mgr->ai4_gaps_end_frm_num[i]; if(i4_end_frm > i4_cur_frame_num) { /* gap's frame_num is before Current frame_num in decode order */ i4_end_frm -= i4_max_frame_num; } ps_dpb_mgr->ai4_gaps_end_frm_num[i] = i4_end_frm; } } } } /*! ************************************************************************** * \if Function name : ih264d_update_qp \endif * * \brief * Updates the values of QP and its related entities * * \return * 0 on Success and Error code otherwise * ************************************************************************** */ WORD32 ih264d_update_qp(dec_struct_t * ps_dec, const WORD8 i1_qp) { WORD32 i_temp; i_temp = (ps_dec->u1_qp + i1_qp + 52) % 52; if((i_temp < 0) || (i_temp > 51) || (i1_qp < -26) || (i1_qp > 25)) return ERROR_INV_RANGE_QP_T; ps_dec->u1_qp = i_temp; ps_dec->u1_qp_y_rem6 = ps_dec->u1_qp % 6; ps_dec->u1_qp_y_div6 = ps_dec->u1_qp / 6; i_temp = CLIP3(0, 51, ps_dec->u1_qp + ps_dec->ps_cur_pps->i1_chroma_qp_index_offset); ps_dec->u1_qp_u_rem6 = MOD(gau1_ih264d_qp_scale_cr[12 + i_temp], 6); ps_dec->u1_qp_u_div6 = DIV(gau1_ih264d_qp_scale_cr[12 + i_temp], 6); i_temp = CLIP3(0, 51, ps_dec->u1_qp + ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset); ps_dec->u1_qp_v_rem6 = MOD(gau1_ih264d_qp_scale_cr[12 + i_temp], 6); ps_dec->u1_qp_v_div6 = DIV(gau1_ih264d_qp_scale_cr[12 + i_temp], 6); ps_dec->pu2_quant_scale_y = gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_y_rem6]; ps_dec->pu2_quant_scale_u = gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_u_rem6]; ps_dec->pu2_quant_scale_v = gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_v_rem6]; return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_decode_gaps_in_frame_num */ /* */ /* Description : This function decodes gaps in frame number */ /* */ /* Inputs : ps_dec Decoder parameters */ /* u2_frame_num current frame number */ /* */ /* Globals : None */ /* Processing : This functionality needs to be implemented */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : Not implemented */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_decode_gaps_in_frame_num(dec_struct_t *ps_dec, UWORD16 u2_frame_num) { UWORD32 u4_next_frm_num, u4_start_frm_num; UWORD32 u4_max_frm_num; pocstruct_t s_tmp_poc; WORD32 i4_poc; dec_slice_params_t *ps_cur_slice; dec_pic_params_t *ps_pic_params; WORD8 i1_gap_idx; WORD32 *i4_gaps_start_frm_num; dpb_manager_t *ps_dpb_mgr; WORD32 i4_frame_gaps; WORD8 *pi1_gaps_per_seq; WORD32 ret; ps_cur_slice = ps_dec->ps_cur_slice; if(ps_cur_slice->u1_field_pic_flag) { if(ps_dec->u2_prev_ref_frame_num == u2_frame_num) return 0; } u4_next_frm_num = ps_dec->u2_prev_ref_frame_num + 1; u4_max_frm_num = ps_dec->ps_cur_sps->u2_u4_max_pic_num_minus1 + 1; // check if(u4_next_frm_num >= u4_max_frm_num) { u4_next_frm_num -= u4_max_frm_num; } if(u4_next_frm_num == u2_frame_num) { return (0); } // check if((ps_dec->u1_nal_unit_type == IDR_SLICE_NAL) && (u4_next_frm_num >= u2_frame_num)) { return (0); } u4_start_frm_num = u4_next_frm_num; s_tmp_poc.i4_pic_order_cnt_lsb = 0; s_tmp_poc.i4_delta_pic_order_cnt_bottom = 0; s_tmp_poc.i4_pic_order_cnt_lsb = 0; s_tmp_poc.i4_delta_pic_order_cnt_bottom = 0; s_tmp_poc.i4_delta_pic_order_cnt[0] = 0; s_tmp_poc.i4_delta_pic_order_cnt[1] = 0; ps_cur_slice = ps_dec->ps_cur_slice; ps_pic_params = ps_dec->ps_cur_pps; i4_frame_gaps = 0; ps_dpb_mgr = ps_dec->ps_dpb_mgr; /* Find a empty slot to store gap seqn info */ i4_gaps_start_frm_num = ps_dpb_mgr->ai4_gaps_start_frm_num; for(i1_gap_idx = 0; i1_gap_idx < MAX_FRAMES; i1_gap_idx++) { if(INVALID_FRAME_NUM == i4_gaps_start_frm_num[i1_gap_idx]) break; } if(MAX_FRAMES == i1_gap_idx) { UWORD32 i4_error_code; i4_error_code = ERROR_DBP_MANAGER_T; // i4_error_code |= 1<<IVD_CORRUPTEDDATA; return i4_error_code; } i4_poc = 0; i4_gaps_start_frm_num[i1_gap_idx] = u4_start_frm_num; ps_dpb_mgr->ai4_gaps_end_frm_num[i1_gap_idx] = u2_frame_num - 1; pi1_gaps_per_seq = ps_dpb_mgr->ai1_gaps_per_seq; pi1_gaps_per_seq[i1_gap_idx] = 0; while(u4_next_frm_num != u2_frame_num) { ih264d_delete_nonref_nondisplay_pics(ps_dpb_mgr); if(ps_pic_params->ps_sps->u1_pic_order_cnt_type) { /* allocate a picture buffer and insert it as ST node */ ret = ih264d_decode_pic_order_cnt(0, u4_next_frm_num, &ps_dec->s_prev_pic_poc, &s_tmp_poc, ps_cur_slice, ps_pic_params, 1, 0, 0, &i4_poc); if(ret != OK) return ret; /* Display seq no calculations */ if(i4_poc >= ps_dec->i4_max_poc) ps_dec->i4_max_poc = i4_poc; /* IDR Picture or POC wrap around */ if(i4_poc == 0) { WORD64 i8_temp; i8_temp = (WORD64)ps_dec->i4_prev_max_display_seq + ps_dec->i4_max_poc + ps_dec->u1_max_dec_frame_buffering + 1; /*If i4_prev_max_display_seq overflows integer range, reset it */ ps_dec->i4_prev_max_display_seq = IS_OUT_OF_RANGE_S32(i8_temp)? 0 : i8_temp; ps_dec->i4_max_poc = 0; } ps_cur_slice->u1_mmco_equalto5 = 0; ps_cur_slice->u2_frame_num = u4_next_frm_num; } // check if(ps_dpb_mgr->i1_poc_buf_id_entries >= ps_dec->u1_max_dec_frame_buffering) { ret = ih264d_assign_display_seq(ps_dec); if(ret != OK) return ret; } { WORD64 i8_display_poc; i8_display_poc = (WORD64)ps_dec->i4_prev_max_display_seq + i4_poc; if(IS_OUT_OF_RANGE_S32(i8_display_poc)) { ps_dec->i4_prev_max_display_seq = 0; } } ret = ih264d_insert_pic_in_display_list( ps_dec->ps_dpb_mgr, (WORD8) DO_NOT_DISP, (WORD32)(ps_dec->i4_prev_max_display_seq + i4_poc), u4_next_frm_num); if(ret != OK) return ret; pi1_gaps_per_seq[i1_gap_idx]++; ret = ih264d_do_mmco_for_gaps(ps_dpb_mgr, ps_dec->ps_cur_sps->u1_num_ref_frames); if(ret != OK) return ret; ih264d_delete_nonref_nondisplay_pics(ps_dpb_mgr); u4_next_frm_num++; if(u4_next_frm_num >= u4_max_frm_num) { u4_next_frm_num -= u4_max_frm_num; } i4_frame_gaps++; } return OK; } /*! ************************************************************************** * \if Function name : ih264d_create_pic_buffers \endif * * \brief * This function creates Picture Buffers. * * \return * 0 on Success and -1 on error ************************************************************************** */ WORD32 ih264d_create_pic_buffers(UWORD8 u1_num_of_buf, dec_struct_t *ps_dec) { struct pic_buffer_t *ps_pic_buf; UWORD8 i; UWORD32 u4_luma_size, u4_chroma_size; UWORD8 u1_frm = ps_dec->ps_cur_sps->u1_frame_mbs_only_flag; WORD32 j; UWORD8 *pu1_buf; ps_pic_buf = ps_dec->ps_pic_buf_base; ih264_disp_mgr_init((disp_mgr_t *)ps_dec->pv_disp_buf_mgr); ih264_buf_mgr_init((buf_mgr_t *)ps_dec->pv_pic_buf_mgr); u4_luma_size = ps_dec->u2_frm_wd_y * ps_dec->u2_frm_ht_y; u4_chroma_size = ps_dec->u2_frm_wd_uv * ps_dec->u2_frm_ht_uv; { if(ps_dec->u4_share_disp_buf == 1) { /* In case of buffers getting shared between application and library there is no need of reference memtabs. Instead of setting the i4_size to zero, it is reduced to a small i4_size to ensure that changes in the code are minimal */ if((ps_dec->u1_chroma_format == IV_YUV_420SP_UV) || (ps_dec->u1_chroma_format == IV_YUV_420SP_VU) || (ps_dec->u1_chroma_format == IV_YUV_420P)) { u4_luma_size = 64; } if(ps_dec->u1_chroma_format == IV_YUV_420SP_UV) { u4_chroma_size = 64; } } } pu1_buf = ps_dec->pu1_pic_buf_base; /* Allocate memory for refernce buffers */ for(i = 0; i < u1_num_of_buf; i++) { UWORD32 u4_offset; WORD32 buf_ret; UWORD8 *pu1_luma, *pu1_chroma; void *pv_mem_ctxt = ps_dec->pv_mem_ctxt; pu1_luma = pu1_buf; pu1_buf += ALIGN64(u4_luma_size); pu1_chroma = pu1_buf; pu1_buf += ALIGN64(u4_chroma_size); /* Offset to the start of the pic from the top left corner of the frame buffer */ if((0 == ps_dec->u4_share_disp_buf) || (NULL == ps_dec->disp_bufs[i].buf[0])) { UWORD32 pad_len_h, pad_len_v; u4_offset = ps_dec->u2_frm_wd_y * (PAD_LEN_Y_V << 1) + PAD_LEN_Y_H; ps_pic_buf->pu1_buf1 = (UWORD8 *)(pu1_luma) + u4_offset; pad_len_h = MAX(PAD_LEN_UV_H, (PAD_LEN_Y_H >> 1)); pad_len_v = MAX(PAD_LEN_UV_V, PAD_LEN_Y_V); u4_offset = ps_dec->u2_frm_wd_uv * pad_len_v + pad_len_h; ps_pic_buf->pu1_buf2 = (UWORD8 *)(pu1_chroma) + u4_offset; ps_pic_buf->pu1_buf3 = (UWORD8 *)(NULL) + u4_offset; } else { UWORD32 pad_len_h, pad_len_v; u4_offset = ps_dec->u2_frm_wd_y * (PAD_LEN_Y_V << 1) + PAD_LEN_Y_H; ps_pic_buf->pu1_buf1 = (UWORD8 *)ps_dec->disp_bufs[i].buf[0] + u4_offset; ps_dec->disp_bufs[i].u4_ofst[0] = u4_offset; if(ps_dec->u1_chroma_format == IV_YUV_420P) { pad_len_h = MAX(PAD_LEN_UV_H * YUV420SP_FACTOR, (PAD_LEN_Y_H >> 1)); pad_len_v = MAX(PAD_LEN_UV_V, PAD_LEN_Y_V); u4_offset = ps_dec->u2_frm_wd_uv * pad_len_v + pad_len_h; ps_pic_buf->pu1_buf2 = (UWORD8 *)(pu1_chroma) + u4_offset; ps_pic_buf->pu1_buf3 = (UWORD8 *)(NULL) + u4_offset; ps_dec->disp_bufs[i].u4_ofst[1] = u4_offset; ps_dec->disp_bufs[i].u4_ofst[2] = u4_offset; } else { pad_len_h = MAX(PAD_LEN_UV_H * YUV420SP_FACTOR, (PAD_LEN_Y_H >> 1)); pad_len_v = MAX(PAD_LEN_UV_V, PAD_LEN_Y_V); u4_offset = ps_dec->u2_frm_wd_uv * pad_len_v + pad_len_h; ps_pic_buf->pu1_buf2 = (UWORD8 *)(ps_dec->disp_bufs[i].buf[1]) + u4_offset; ps_pic_buf->pu1_buf3 = (UWORD8 *)(ps_dec->disp_bufs[i].buf[1]) + u4_offset; ps_dec->disp_bufs[i].u4_ofst[1] = u4_offset; ps_dec->disp_bufs[i].u4_ofst[2] = u4_offset; } } ps_pic_buf->u2_frm_ht_y = ps_dec->u2_frm_ht_y; ps_pic_buf->u2_frm_ht_uv = ps_dec->u2_frm_ht_uv; ps_pic_buf->u2_frm_wd_y = ps_dec->u2_frm_wd_y; ps_pic_buf->u2_frm_wd_uv = ps_dec->u2_frm_wd_uv; ps_pic_buf->u1_pic_buf_id = i; buf_ret = ih264_buf_mgr_add((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, ps_pic_buf, i); if(0 != buf_ret) { ps_dec->i4_error_code = ERROR_BUF_MGR; return ERROR_BUF_MGR; } ps_dec->apv_buf_id_pic_buf_map[i] = (void *)ps_pic_buf; ps_pic_buf++; } if(1 == ps_dec->u4_share_disp_buf) { for(i = 0; i < u1_num_of_buf; i++) ps_dec->u4_disp_buf_mapping[i] = 1; } return OK; } /*! ************************************************************************** * \if Function name : ih264d_allocate_dynamic_bufs \endif * * \brief * This function allocates memory required by Decoder. * * \param ps_dec: Pointer to dec_struct_t. * * \return * Returns i4_status as returned by MemManager. * ************************************************************************** */ WORD16 ih264d_allocate_dynamic_bufs(dec_struct_t * ps_dec) { struct MemReq s_MemReq; struct MemBlock *p_MemBlock; pred_info_t *ps_pred_frame; dec_mb_info_t *ps_frm_mb_info; dec_slice_struct_t *ps_dec_slice_buf; UWORD8 *pu1_dec_mb_map, *pu1_recon_mb_map; UWORD16 *pu2_slice_num_map; WORD16 *pi16_res_coeff; WORD16 i16_status = 0; UWORD8 uc_frmOrFld = (1 - ps_dec->ps_cur_sps->u1_frame_mbs_only_flag); UWORD16 u4_luma_wd = ps_dec->u2_frm_wd_y; UWORD16 u4_chroma_wd = ps_dec->u2_frm_wd_uv; WORD8 c_i = 0; dec_seq_params_t *ps_sps = ps_dec->ps_cur_sps; UWORD32 u4_total_mbs = ps_sps->u2_total_num_of_mbs << uc_frmOrFld; UWORD32 u4_wd_mbs = ps_dec->u2_frm_wd_in_mbs; UWORD32 u4_ht_mbs = ps_dec->u2_frm_ht_in_mbs; UWORD32 u4_blk_wd; UWORD32 ui_size = 0; UWORD32 u4_int_scratch_size = 0, u4_ref_pred_size = 0; UWORD8 *pu1_buf; WORD32 num_entries; WORD32 size; void *pv_buf; UWORD32 u4_num_bufs; UWORD32 u4_luma_size, u4_chroma_size; void *pv_mem_ctxt = ps_dec->pv_mem_ctxt; size = u4_total_mbs; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pu1_dec_mb_map = pv_buf; size = u4_total_mbs; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pu1_recon_mb_map = pv_buf; size = u4_total_mbs * sizeof(UWORD16); pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pu2_slice_num_map = pv_buf; /************************************************************/ /* Post allocation Initialisations */ /************************************************************/ ps_dec->ps_parse_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); ps_dec->ps_decode_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); ps_dec->ps_computebs_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); ps_dec->ps_pred_start = ps_dec->ps_pred; size = sizeof(parse_pmbarams_t) * (ps_dec->u1_recon_mb_grp); pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_parse_mb_data = pv_buf; size = sizeof(parse_part_params_t) * ((ps_dec->u1_recon_mb_grp) << 4); pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_parse_part_params = pv_buf; size = ((u4_wd_mbs * sizeof(deblkmb_neighbour_t)) << uc_frmOrFld); pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_deblk_top_mb = pv_buf; size = ((sizeof(ctxt_inc_mb_info_t)) * (((u4_wd_mbs + 1) << uc_frmOrFld) + 1)); pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->p_ctxt_inc_mb_map = pv_buf; /* 0th entry of CtxtIncMbMap will be always be containing default values for CABAC context representing MB not available */ ps_dec->p_ctxt_inc_mb_map += 1; size = (sizeof(mv_pred_t) * ps_dec->u1_recon_mb_grp * 16); pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_mv_p[0] = pv_buf; size = (sizeof(mv_pred_t) * ps_dec->u1_recon_mb_grp * 16); pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_mv_p[1] = pv_buf; { UWORD8 i; for(i = 0; i < MV_SCRATCH_BUFS; i++) { size = (sizeof(mv_pred_t) * ps_dec->u1_recon_mb_grp * 4); pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_mv_top_p[i] = pv_buf; } } size = sizeof(UWORD8) * ((u4_wd_mbs + 2) * MB_SIZE) * 2; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); ps_dec->pu1_y_intra_pred_line = pv_buf; memset(ps_dec->pu1_y_intra_pred_line, 0, size); ps_dec->pu1_y_intra_pred_line += MB_SIZE; size = sizeof(UWORD8) * ((u4_wd_mbs + 2) * MB_SIZE) * 2; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); ps_dec->pu1_u_intra_pred_line = pv_buf; memset(ps_dec->pu1_u_intra_pred_line, 0, size); ps_dec->pu1_u_intra_pred_line += MB_SIZE; size = sizeof(UWORD8) * ((u4_wd_mbs + 2) * MB_SIZE) * 2; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); ps_dec->pu1_v_intra_pred_line = pv_buf; memset(ps_dec->pu1_v_intra_pred_line, 0, size); ps_dec->pu1_v_intra_pred_line += MB_SIZE; if(ps_dec->u1_separate_parse) { /* Needs one extra row of info, to hold top row data */ size = sizeof(mb_neigbour_params_t) * 2 * ((u4_wd_mbs + 2) * (u4_ht_mbs + 1)); } else { size = sizeof(mb_neigbour_params_t) * 2 * ((u4_wd_mbs + 2) << uc_frmOrFld); } pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); ps_dec->ps_nbr_mb_row = pv_buf; memset(ps_dec->ps_nbr_mb_row, 0, size); /* Allocate deblock MB info */ size = (u4_total_mbs + u4_wd_mbs) * sizeof(deblk_mb_t); pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); ps_dec->ps_deblk_pic = pv_buf; memset(ps_dec->ps_deblk_pic, 0, size); /* Allocate frame level mb info */ size = sizeof(dec_mb_info_t) * u4_total_mbs; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); ps_dec->ps_frm_mb_info = pv_buf; memset(ps_dec->ps_frm_mb_info, 0, size); /* Allocate memory for slice headers dec_slice_struct_t */ num_entries = MAX_FRAMES; if((1 >= ps_dec->ps_cur_sps->u1_num_ref_frames) && (0 == ps_dec->i4_display_delay)) { num_entries = 1; } num_entries = ((2 * num_entries) + 1); num_entries *= 2; size = num_entries * sizeof(void *); size += PAD_MAP_IDX_POC * sizeof(void *); size *= u4_total_mbs; size += sizeof(dec_slice_struct_t) * u4_total_mbs; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); ps_dec->ps_dec_slice_buf = pv_buf; memset(ps_dec->ps_dec_slice_buf, 0, size); pu1_buf = (UWORD8 *)ps_dec->ps_dec_slice_buf; pu1_buf += sizeof(dec_slice_struct_t) * u4_total_mbs; ps_dec->pv_map_ref_idx_to_poc_buf = (void *)pu1_buf; /* Allocate memory for packed pred info */ num_entries = u4_total_mbs; num_entries *= 16 * 2; size = sizeof(pred_info_pkd_t) * num_entries; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->ps_pred_pkd = pv_buf; /* Allocate memory for coeff data */ size = MB_LUM_SIZE * sizeof(WORD16); /*For I16x16 MBs, 16 4x4 AC coeffs and 1 4x4 DC coeff TU blocks will be sent For all MBs along with 8 4x4 AC coeffs 2 2x2 DC coeff TU blocks will be sent So use 17 4x4 TU blocks for luma and 9 4x4 TU blocks for chroma */ size += u4_total_mbs * (MAX(17 * sizeof(tu_sblk4x4_coeff_data_t),4 * sizeof(tu_blk8x8_coeff_data_t)) + 9 * sizeof(tu_sblk4x4_coeff_data_t)); //32 bytes for each mb to store u1_prev_intra4x4_pred_mode and u1_rem_intra4x4_pred_mode data size += u4_total_mbs * 32; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pi2_coeff_data = pv_buf; ps_dec->pv_pic_tu_coeff_data = (void *)(ps_dec->pi2_coeff_data + MB_LUM_SIZE); /* Allocate MV bank buffer */ { UWORD32 col_flag_buffer_size, mvpred_buffer_size; col_flag_buffer_size = ((ps_dec->u2_pic_wd * ps_dec->u2_pic_ht) >> 4); mvpred_buffer_size = sizeof(mv_pred_t) * ((ps_dec->u2_pic_wd * (ps_dec->u2_pic_ht + PAD_MV_BANK_ROW)) >> 4); u4_num_bufs = ps_dec->ps_cur_sps->u1_num_ref_frames + 1; u4_num_bufs = MIN(u4_num_bufs, ps_dec->u1_pic_bufs); u4_num_bufs = MAX(u4_num_bufs, 2); size = ALIGN64(mvpred_buffer_size) + ALIGN64(col_flag_buffer_size); size *= u4_num_bufs; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pu1_mv_bank_buf_base = pv_buf; } /* Allocate Pic buffer */ u4_luma_size = ps_dec->u2_frm_wd_y * ps_dec->u2_frm_ht_y; u4_chroma_size = ps_dec->u2_frm_wd_uv * ps_dec->u2_frm_ht_uv; { if(ps_dec->u4_share_disp_buf == 1) { /* In case of buffers getting shared between application and library there is no need of reference memtabs. Instead of setting the i4_size to zero, it is reduced to a small i4_size to ensure that changes in the code are minimal */ if((ps_dec->u1_chroma_format == IV_YUV_420SP_UV) || (ps_dec->u1_chroma_format == IV_YUV_420SP_VU) || (ps_dec->u1_chroma_format == IV_YUV_420P)) { u4_luma_size = 64; } if(ps_dec->u1_chroma_format == IV_YUV_420SP_UV) { u4_chroma_size = 64; } } } size = ALIGN64(u4_luma_size) + ALIGN64(u4_chroma_size); size *= ps_dec->u1_pic_bufs; pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); RETURN_IF((NULL == pv_buf), IV_FAIL); memset(pv_buf, 0, size); ps_dec->pu1_pic_buf_base = pv_buf; /* Post allocation Increment Actions */ /***************************************************************************/ /*Initialize cabac context pointers for every SE that has fixed contextIdx */ /***************************************************************************/ { bin_ctxt_model_t * const p_cabac_ctxt_table_t = ps_dec->p_cabac_ctxt_table_t; bin_ctxt_model_t * * p_coeff_abs_level_minus1_t = ps_dec->p_coeff_abs_level_minus1_t; bin_ctxt_model_t * * p_cbf_t = ps_dec->p_cbf_t; ps_dec->p_mb_field_dec_flag_t = p_cabac_ctxt_table_t + MB_FIELD_DECODING_FLAG; ps_dec->p_prev_intra4x4_pred_mode_flag_t = p_cabac_ctxt_table_t + PREV_INTRA4X4_PRED_MODE_FLAG; ps_dec->p_rem_intra4x4_pred_mode_t = p_cabac_ctxt_table_t + REM_INTRA4X4_PRED_MODE; ps_dec->p_intra_chroma_pred_mode_t = p_cabac_ctxt_table_t + INTRA_CHROMA_PRED_MODE; ps_dec->p_mb_qp_delta_t = p_cabac_ctxt_table_t + MB_QP_DELTA; ps_dec->p_ref_idx_t = p_cabac_ctxt_table_t + REF_IDX; ps_dec->p_mvd_x_t = p_cabac_ctxt_table_t + MVD_X; ps_dec->p_mvd_y_t = p_cabac_ctxt_table_t + MVD_Y; p_cbf_t[0] = p_cabac_ctxt_table_t + CBF + 0; p_cbf_t[1] = p_cabac_ctxt_table_t + CBF + 4; p_cbf_t[2] = p_cabac_ctxt_table_t + CBF + 8; p_cbf_t[3] = p_cabac_ctxt_table_t + CBF + 12; p_cbf_t[4] = p_cabac_ctxt_table_t + CBF + 16; ps_dec->p_cbp_luma_t = p_cabac_ctxt_table_t + CBP_LUMA; ps_dec->p_cbp_chroma_t = p_cabac_ctxt_table_t + CBP_CHROMA; p_coeff_abs_level_minus1_t[LUMA_DC_CTXCAT] = p_cabac_ctxt_table_t + COEFF_ABS_LEVEL_MINUS1 + COEFF_ABS_LEVEL_CAT_0_OFFSET; p_coeff_abs_level_minus1_t[LUMA_AC_CTXCAT] = p_cabac_ctxt_table_t + COEFF_ABS_LEVEL_MINUS1 + COEFF_ABS_LEVEL_CAT_1_OFFSET; p_coeff_abs_level_minus1_t[LUMA_4X4_CTXCAT] = p_cabac_ctxt_table_t + COEFF_ABS_LEVEL_MINUS1 + COEFF_ABS_LEVEL_CAT_2_OFFSET; p_coeff_abs_level_minus1_t[CHROMA_DC_CTXCAT] = p_cabac_ctxt_table_t + COEFF_ABS_LEVEL_MINUS1 + COEFF_ABS_LEVEL_CAT_3_OFFSET; p_coeff_abs_level_minus1_t[CHROMA_AC_CTXCAT] = p_cabac_ctxt_table_t + COEFF_ABS_LEVEL_MINUS1 + COEFF_ABS_LEVEL_CAT_4_OFFSET; p_coeff_abs_level_minus1_t[LUMA_8X8_CTXCAT] = p_cabac_ctxt_table_t + COEFF_ABS_LEVEL_MINUS1_8X8 + COEFF_ABS_LEVEL_CAT_5_OFFSET; /********************************************************/ /* context for the high profile related syntax elements */ /* This is maintained seperately in s_high_profile */ /********************************************************/ { ps_dec->s_high_profile.ps_transform8x8_flag = p_cabac_ctxt_table_t + TRANSFORM_SIZE_8X8_FLAG; ps_dec->s_high_profile.ps_sigcoeff_8x8_frame = p_cabac_ctxt_table_t + SIGNIFICANT_COEFF_FLAG_8X8_FRAME; ps_dec->s_high_profile.ps_last_sigcoeff_8x8_frame = p_cabac_ctxt_table_t + LAST_SIGNIFICANT_COEFF_FLAG_8X8_FRAME; ps_dec->s_high_profile.ps_coeff_abs_levelminus1 = p_cabac_ctxt_table_t + COEFF_ABS_LEVEL_MINUS1_8X8; ps_dec->s_high_profile.ps_sigcoeff_8x8_field = p_cabac_ctxt_table_t + SIGNIFICANT_COEFF_FLAG_8X8_FIELD; ps_dec->s_high_profile.ps_last_sigcoeff_8x8_field = p_cabac_ctxt_table_t + LAST_SIGNIFICANT_COEFF_FLAG_8X8_FIELD; } } return (i16_status); } /*! ************************************************************************** * \if Function name : ih264d_free_dynamic_bufs \endif * * \brief * This function frees dynamic memory allocated by Decoder. * * \param ps_dec: Pointer to dec_struct_t. * * \return * Returns i4_status as returned by MemManager. * ************************************************************************** */ WORD16 ih264d_free_dynamic_bufs(dec_struct_t * ps_dec) { PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_bits_buf_dynamic); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_deblk_pic); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_dec_mb_map); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_recon_mb_map); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu2_slice_num_map); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_dec_slice_buf); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_frm_mb_info); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pi2_coeff_data); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_parse_mb_data); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_parse_part_params); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_deblk_top_mb); if(ps_dec->p_ctxt_inc_mb_map) { ps_dec->p_ctxt_inc_mb_map -= 1; PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->p_ctxt_inc_mb_map); } PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_mv_p[0]); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_mv_p[1]); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_pred_pkd); { UWORD8 i; for(i = 0; i < MV_SCRATCH_BUFS; i++) { PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_mv_top_p[i]); } } if(ps_dec->pu1_y_intra_pred_line) { ps_dec->pu1_y_intra_pred_line -= MB_SIZE; } PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_y_intra_pred_line); if(ps_dec->pu1_u_intra_pred_line) { ps_dec->pu1_u_intra_pred_line -= MB_SIZE; } PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_u_intra_pred_line); if(ps_dec->pu1_v_intra_pred_line) { ps_dec->pu1_v_intra_pred_line -= MB_SIZE; } PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_v_intra_pred_line); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_nbr_mb_row); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_mv_bank_buf_base); PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_pic_buf_base); return 0; } /*! ************************************************************************** * \if Function name : ih264d_create_mv_bank \endif * * \brief * This function creates MV bank. * * \param memType : Type of memory being handled * 0: Display Buffer * 1: Decoder Buffer * 2: Internal Buffer * \param u1_num_of_buf: Number of decode or display buffers. * \param u4_wd : Frame width. * \param u4_ht : Frame Height. * \param ps_pic_buf_api : Pointer to Picture Buffer API. * \param ih264d_dec_mem_manager : Memory manager utility supplied by system. * * \return * 0 on Success and -1 on error * ************************************************************************** */ WORD32 ih264d_create_mv_bank(void *pv_dec, UWORD32 ui_width, UWORD32 ui_height) { UWORD8 i; UWORD32 col_flag_buffer_size, mvpred_buffer_size; UWORD8 *pu1_mv_buf_mgr_base, *pu1_mv_bank_base; col_mv_buf_t *ps_col_mv; mv_pred_t *ps_mv; UWORD8 *pu1_col_zero_flag_buf; dec_struct_t *ps_dec = (dec_struct_t *)pv_dec; WORD32 buf_ret; UWORD32 u4_num_bufs; UWORD8 *pu1_buf; WORD32 size; void *pv_mem_ctxt = ps_dec->pv_mem_ctxt; col_flag_buffer_size = ((ui_width * ui_height) >> 4); mvpred_buffer_size = sizeof(mv_pred_t) * ((ui_width * (ui_height + PAD_MV_BANK_ROW)) >> 4); ih264_buf_mgr_init((buf_mgr_t *)ps_dec->pv_mv_buf_mgr); ps_col_mv = ps_dec->ps_col_mv_base; u4_num_bufs = ps_dec->ps_cur_sps->u1_num_ref_frames + 1; u4_num_bufs = MIN(u4_num_bufs, ps_dec->u1_pic_bufs); u4_num_bufs = MAX(u4_num_bufs, 2); pu1_buf = ps_dec->pu1_mv_bank_buf_base; for(i = 0 ; i < u4_num_bufs ; i++) { pu1_col_zero_flag_buf = pu1_buf; pu1_buf += ALIGN64(col_flag_buffer_size); ps_mv = (mv_pred_t *)pu1_buf; pu1_buf += ALIGN64(mvpred_buffer_size); memset(ps_mv, 0, ((ui_width * OFFSET_MV_BANK_ROW) >> 4) * sizeof(mv_pred_t)); ps_mv += (ui_width*OFFSET_MV_BANK_ROW) >> 4; ps_col_mv->pv_col_zero_flag = (void *)pu1_col_zero_flag_buf; ps_col_mv->pv_mv = (void *)ps_mv; buf_ret = ih264_buf_mgr_add((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, ps_col_mv, i); if(0 != buf_ret) { ps_dec->i4_error_code = ERROR_BUF_MGR; return ERROR_BUF_MGR; } ps_col_mv++; } return OK; } void ih264d_unpack_coeff4x4_dc_4x4blk(tu_sblk4x4_coeff_data_t *ps_tu_4x4, WORD16 *pi2_out_coeff_data, UWORD8 *pu1_inv_scan) { UWORD16 u2_sig_coeff_map = ps_tu_4x4->u2_sig_coeff_map; WORD32 idx; WORD16 *pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; while(u2_sig_coeff_map) { idx = CLZ(u2_sig_coeff_map); idx = 31 - idx; RESET_BIT(u2_sig_coeff_map,idx); idx = pu1_inv_scan[idx]; pi2_out_coeff_data[idx] = *pi2_coeff_data++; } } ================================================ FILE: dependencies/ih264d/decoder/ih264d_utils.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ #ifndef _IH264D_UTILS_H_ #define _IH264D_UTILS_H_ /*! ************************************************************************** * \file ih264d_utils.h * * \brief * Contains declaration of routines * that handle of start and end of pic processing * * \date * 19/12/2002 * * \author AI ************************************************************************** */ #include "ih264d_defs.h" #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_structs.h" #include "ih264d_parse_cavlc.h" #define PS_DEC_ALIGNED_FREE(ps_dec, y) \ if(y) {ps_dec->pf_aligned_free(ps_dec->pv_mem_ctxt, ((void *)y)); (y) = NULL;} void pad_frm_buff_vert(dec_struct_t *ps_dec); UWORD8 ih264d_is_end_of_pic(UWORD16 u2_frame_num, UWORD8 u1_nal_ref_idc, pocstruct_t *ps_cur_poc, pocstruct_t *ps_prev_poc, dec_slice_params_t * ps_prev_slice, UWORD8 u1_pic_order_cnt_type, UWORD8 u1_nal_unit_type, UWORD32 u4_idr_pic_id, UWORD8 u1_field_pic_flag, UWORD8 u1_bottom_field_flag); WORD32 ih264d_end_of_pic_processing(dec_struct_t * ps_dec); WORD32 ih264d_init_pic(dec_struct_t *ps_dec, UWORD16 u2_frame_num, WORD32 i4_poc, dec_pic_params_t * ps_pps); WORD32 ih264d_end_of_pic_processing(dec_struct_t * ps_dec); WORD32 ih264d_decode_pic_order_cnt(UWORD8 u1_is_idr_slice, UWORD32 u2_frame_num, pocstruct_t *ps_prev_poc, pocstruct_t *ps_cur_poc, dec_slice_params_t *ps_cur_slice, dec_pic_params_t * ps_pps, UWORD8 u1_nal_ref_idc, UWORD8 u1_bottom_field_flag, UWORD8 u1_field_pic_flag, WORD32 *pi4_poc); void ih264d_release_display_bufs(dec_struct_t *ps_dec); WORD32 ih264d_assign_display_seq(dec_struct_t *ps_dec); void ih264d_assign_pic_num(dec_struct_t *ps_dec); void ih264d_unpack_coeff4x4_dc_4x4blk(tu_sblk4x4_coeff_data_t *ps_tu_4x4, WORD16 *pi2_out_coeff_data, UWORD8 *pu1_inv_scan); WORD32 ih264d_update_qp(dec_struct_t * ps_dec, const WORD8 i1_qp); WORD32 ih264d_decode_gaps_in_frame_num(dec_struct_t *ps_dec, UWORD16 u2_frame_num); WORD32 ih264d_get_next_display_field(dec_struct_t * ps_dec, ivd_out_bufdesc_t *ps_out_buffer, ivd_get_display_frame_op_t *pv_disp_op); void ih264d_release_display_field(dec_struct_t *ps_dec, ivd_get_display_frame_op_t *pv_disp_op); void ih264d_close_video_decoder(iv_obj_t *iv_obj_t); WORD32 ih264d_get_dpb_size(dec_seq_params_t *ps_seq); WORD32 ih264d_get_next_nal_unit(UWORD8 *pu1_buf, UWORD32 u4_cur_pos, UWORD32 u4_max_ofst, UWORD32 *pu4_length_of_start_code); WORD16 ih264d_free_dynamic_bufs(dec_struct_t * ps_dec); #endif /* _IH264D_UTILS_H_ */ ================================================ FILE: dependencies/ih264d/decoder/ih264d_vui.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264d_vui.c */ /* */ /* Description : This file contains routines to parse VUI NAL's */ /* */ /* List of Functions : <List the functions defined in this file> */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 25 05 2005 NS Draft */ /* */ /*****************************************************************************/ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_vui.h" #include "ih264d_bitstrm.h" #include "ih264d_parse_cavlc.h" #include "ih264d_structs.h" #include "ih264d_error_handler.h" /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_hrd_parametres */ /* */ /* Description : This function parses hrd_t parametres */ /* Inputs : ps_hrd pointer to HRD params */ /* ps_bitstrm Bitstream */ /* Globals : None */ /* Processing : Parses HRD params */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_hrd_parametres(hrd_t *ps_hrd, dec_bit_stream_t *ps_bitstrm) { UWORD8 u1_index; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; ps_hrd->u4_cpb_cnt = 1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if(ps_hrd->u4_cpb_cnt > 31) return ERROR_INV_SPS_PPS_T; ps_hrd->u1_bit_rate_scale = ih264d_get_bits_h264(ps_bitstrm, 4); ps_hrd->u1_cpb_size_scale = ih264d_get_bits_h264(ps_bitstrm, 4); for(u1_index = 0; u1_index < (UWORD8)ps_hrd->u4_cpb_cnt; u1_index++) { ps_hrd->u4_bit_rate[u1_index] = 1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_hrd->u4_cpb_size[u1_index] = 1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_hrd->u1_cbr_flag[u1_index] = ih264d_get_bits_h264(ps_bitstrm, 1); } ps_hrd->u1_initial_cpb_removal_delay = 1 + ih264d_get_bits_h264(ps_bitstrm, 5); ps_hrd->u1_cpb_removal_delay_length = 1 + ih264d_get_bits_h264(ps_bitstrm, 5); ps_hrd->u1_dpb_output_delay_length = 1 + ih264d_get_bits_h264(ps_bitstrm, 5); ps_hrd->u1_time_offset_length = ih264d_get_bits_h264(ps_bitstrm, 5); return OK; } /*****************************************************************************/ /* */ /* Function Name : ih264d_parse_vui_parametres */ /* */ /* Description : This function parses VUI NALs. */ /* Inputs : ps_vu4 pointer to VUI params */ /* ps_bitstrm Bitstream */ /* Globals : None */ /* Processing : Parses VUI NAL's units and stores the info */ /* Outputs : None */ /* Returns : None */ /* */ /* Issues : None */ /* */ /* Revision History: */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 06 05 2002 NS Draft */ /* */ /*****************************************************************************/ WORD32 ih264d_parse_vui_parametres(vui_t *ps_vu4, dec_bit_stream_t *ps_bitstrm) { UWORD8 u4_bits; UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; WORD32 ret; u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); if(u4_bits) { u4_bits = ih264d_get_bits_h264(ps_bitstrm, 8); ps_vu4->u1_aspect_ratio_idc = (UWORD8)u4_bits; if(VUI_EXTENDED_SAR == u4_bits) { ps_vu4->u2_sar_width = ih264d_get_bits_h264(ps_bitstrm, 16); ps_vu4->u2_sar_height = ih264d_get_bits_h264(ps_bitstrm, 16); } } u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); if(u4_bits) { ps_vu4->u1_overscan_appropriate_flag = ih264d_get_bits_h264( ps_bitstrm, 1); } u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); /* Initialize to unspecified (5 for video_format and 2 for colour_primaries, tfr_chars, matrix_coeffs */ ps_vu4->u1_video_format = 5; ps_vu4->u1_video_full_range_flag = 0; ps_vu4->u1_colour_primaries = 2; ps_vu4->u1_tfr_chars = 2; ps_vu4->u1_matrix_coeffs = 2; if(u4_bits) { ps_vu4->u1_video_format = ih264d_get_bits_h264(ps_bitstrm, 3); ps_vu4->u1_video_full_range_flag = ih264d_get_bits_h264(ps_bitstrm, 1); u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); if(u4_bits) { ps_vu4->u1_colour_primaries = ih264d_get_bits_h264(ps_bitstrm, 8); ps_vu4->u1_tfr_chars = ih264d_get_bits_h264(ps_bitstrm, 8); ps_vu4->u1_matrix_coeffs = ih264d_get_bits_h264(ps_bitstrm, 8); } } u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); if(u4_bits) { ps_vu4->u1_cr_top_field = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_vu4->u1_cr_bottom_field = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); } u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); if(u4_bits) { ps_vu4->u4_num_units_in_tick = ih264d_get_bits_h264(ps_bitstrm, 32); ps_vu4->u4_time_scale = ih264d_get_bits_h264(ps_bitstrm, 32); ps_vu4->u1_fixed_frame_rate_flag = ih264d_get_bits_h264(ps_bitstrm, 1); } u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); ps_vu4->u1_nal_hrd_params_present = u4_bits; if(u4_bits) { ret = ih264d_parse_hrd_parametres(&ps_vu4->s_nal_hrd, ps_bitstrm); if(ret != OK) return ret; } u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); ps_vu4->u1_vcl_hrd_params_present = u4_bits; if(u4_bits) { ret = ih264d_parse_hrd_parametres(&ps_vu4->s_vcl_hrd, ps_bitstrm); if(ret != OK) return ret; } if(ps_vu4->u1_nal_hrd_params_present || u4_bits) { ps_vu4->u1_low_delay_hrd_flag = ih264d_get_bits_h264(ps_bitstrm, 1); } ps_vu4->u1_pic_struct_present_flag = ih264d_get_bits_h264(ps_bitstrm, 1); ps_vu4->u1_bitstream_restriction_flag = ih264d_get_bits_h264(ps_bitstrm, 1); if(ps_vu4->u1_bitstream_restriction_flag) { ps_vu4->u1_mv_over_pic_boundaries_flag = ih264d_get_bits_h264( ps_bitstrm, 1); ps_vu4->u4_max_bytes_per_pic_denom = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_vu4->u4_max_bits_per_mb_denom = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_vu4->u4_log2_max_mv_length_horz = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_vu4->u4_log2_max_mv_length_vert = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_vu4->u4_num_reorder_frames = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); ps_vu4->u4_max_dec_frame_buffering = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); if((ps_vu4->u4_max_dec_frame_buffering > (H264_MAX_REF_PICS * 2)) || (ps_vu4->u4_num_reorder_frames > ps_vu4->u4_max_dec_frame_buffering)) { return ERROR_INV_SPS_PPS_T; } } else { /* Setting this to a large value if not present */ ps_vu4->u4_num_reorder_frames = 64; ps_vu4->u4_max_dec_frame_buffering = 64; } return OK; } ================================================ FILE: dependencies/ih264d/decoder/ih264d_vui.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /*****************************************************************************/ /* */ /* File Name : ih264d_vui.h */ /* */ /* Description : This file contains routines to parse SEI NAL's */ /* */ /* List of Functions : <List the functions defined in this file> */ /* */ /* Issues / Problems : None */ /* */ /* Revision History : */ /* */ /* DD MM YYYY Author(s) Changes (Describe the changes made) */ /* 25 05 2005 NS Draft */ /* */ /*****************************************************************************/ #ifndef _IH264D_VUI_H_ #define _IH264D_VUI_H_ #include "ih264_typedefs.h" #include "ih264_macros.h" #include "ih264_platform_macros.h" #include "ih264d_bitstrm.h" #define VUI_EXTENDED_SAR 255 typedef struct { UWORD32 u4_cpb_cnt; UWORD8 u1_bit_rate_scale; UWORD8 u1_cpb_size_scale; UWORD32 u4_bit_rate[32]; UWORD32 u4_cpb_size[32]; UWORD8 u1_cbr_flag[32]; UWORD8 u1_initial_cpb_removal_delay; UWORD8 u1_cpb_removal_delay_length; UWORD8 u1_dpb_output_delay_length; UWORD8 u1_time_offset_length; } hrd_t; typedef struct { UWORD8 u1_aspect_ratio_idc; UWORD16 u2_sar_width; UWORD16 u2_sar_height; UWORD8 u1_overscan_appropriate_flag; UWORD8 u1_video_format; UWORD8 u1_video_full_range_flag; UWORD8 u1_colour_primaries; UWORD8 u1_tfr_chars; UWORD8 u1_matrix_coeffs; UWORD8 u1_cr_top_field; UWORD8 u1_cr_bottom_field; UWORD32 u4_num_units_in_tick; UWORD32 u4_time_scale; UWORD8 u1_fixed_frame_rate_flag; UWORD8 u1_nal_hrd_params_present; hrd_t s_nal_hrd; UWORD8 u1_vcl_hrd_params_present; hrd_t s_vcl_hrd; UWORD8 u1_low_delay_hrd_flag; UWORD8 u1_pic_struct_present_flag; UWORD8 u1_bitstream_restriction_flag; UWORD8 u1_mv_over_pic_boundaries_flag; UWORD32 u4_max_bytes_per_pic_denom; UWORD32 u4_max_bits_per_mb_denom; UWORD32 u4_log2_max_mv_length_horz; UWORD32 u4_log2_max_mv_length_vert; UWORD32 u4_num_reorder_frames; UWORD32 u4_max_dec_frame_buffering; } vui_t; WORD32 ih264d_parse_vui_parametres(vui_t *ps_vu4, dec_bit_stream_t *ps_bitstrm); #endif /* _SEI_H_ */ ================================================ FILE: dependencies/ih264d/decoder/iv.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore * Modified for use with Cemu emulator project */ /** ******************************************************************************* * @file * iv.h * * @brief * This file contains all the necessary structure and enumeration * definitions needed for the Application Program Interface(API) of the * Ittiam Video and Image codecs * * @author * 100239(RCY) * * @par List of Functions: * * @remarks * None * ******************************************************************************* */ #ifndef _IV_H #define _IV_H /*****************************************************************************/ /* Constant Macros */ /*****************************************************************************/ /*****************************************************************************/ /* Typedefs */ /*****************************************************************************/ /*****************************************************************************/ /* Enums */ /*****************************************************************************/ /* IV_API_CALL_STATUS_T:This is only to return the FAIL/PASS status to the */ /* application for the current API call */ typedef enum { IV_STATUS_NA = 0x7FFFFFFF, IV_SUCCESS = 0x0, IV_FAIL = 0x1, }IV_API_CALL_STATUS_T; /* IV_COLOR_FORMAT_T: This enumeration lists all the color formats which */ /* finds usage in video/image codecs */ typedef enum { IV_CHROMA_NA = 0x7FFFFFFF, IV_YUV_420P = 0x1, IV_YUV_422P = 0x2, IV_420_UV_INTL = 0x3, IV_YUV_422IBE = 0x4, IV_YUV_422ILE = 0x5, IV_YUV_444P = 0x6, IV_YUV_411P = 0x7, IV_GRAY = 0x8, IV_RGB_565 = 0x9, IV_RGB_24 = 0xa, IV_YUV_420SP_UV = 0xb, IV_YUV_420SP_VU = 0xc, IV_RGBA_8888 = 0xd }IV_COLOR_FORMAT_T; /* IV_PICTURE_CODING_TYPE_T: VOP/Frame coding type Enumeration */ typedef enum { IV_NA_FRAME = 0x7FFFFFFF, IV_I_FRAME = 0x0, IV_P_FRAME = 0x1, IV_B_FRAME = 0x2, IV_IDR_FRAME = 0x3, IV_II_FRAME = 0x4, IV_IP_FRAME = 0x5, IV_IB_FRAME = 0x6, IV_PI_FRAME = 0x7, IV_PP_FRAME = 0x8, IV_PB_FRAME = 0x9, IV_BI_FRAME = 0xa, IV_BP_FRAME = 0xb, IV_BB_FRAME = 0xc, IV_MBAFF_I_FRAME = 0xd, IV_MBAFF_P_FRAME = 0xe, IV_MBAFF_B_FRAME = 0xf, IV_MBAFF_IDR_FRAME = 0x10, IV_NOT_CODED_FRAME = 0x11, IV_FRAMETYPE_DEFAULT = IV_I_FRAME }IV_PICTURE_CODING_TYPE_T; /* IV_FLD_TYPE_T: field type Enumeration */ typedef enum { IV_NA_FLD = 0x7FFFFFFF, IV_TOP_FLD = 0x0, IV_BOT_FLD = 0x1, IV_FLD_TYPE_DEFAULT = IV_TOP_FLD }IV_FLD_TYPE_T; /* IV_CONTENT_TYPE_T: Video content type */ typedef enum { IV_CONTENTTYPE_NA = 0x7FFFFFFF, IV_PROGRESSIVE = 0x0, IV_INTERLACED = 0x1, IV_PROGRESSIVE_FRAME = 0x2, IV_INTERLACED_FRAME = 0x3, IV_INTERLACED_TOPFIELD = 0x4, IV_INTERLACED_BOTTOMFIELD = 0x5, IV_CONTENTTYPE_DEFAULT = IV_PROGRESSIVE, }IV_CONTENT_TYPE_T; /* IV_API_COMMAND_TYPE_T:API command type */ typedef enum { IV_CMD_NA = 0x7FFFFFFF, IV_CMD_DUMMY_ELEMENT = 0x4, }IV_API_COMMAND_TYPE_T; /*****************************************************************************/ /* Structure */ /*****************************************************************************/ /* IV_OBJ_T: This structure defines the handle for the codec instance */ typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * Pointer to the API function pointer table of the codec */ void *pv_fxns; /** * Pointer to the handle of the codec */ void *pv_codec_handle; }iv_obj_t; /* IV_YUV_BUF_T: This structure defines attributes for the yuv buffer */ typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * Pointer to Luma (Y) Buffer */ void *pv_y_buf; /** * Pointer to Chroma (Cb) Buffer */ void *pv_u_buf; /** * Pointer to Chroma (Cr) Buffer */ void *pv_v_buf; /** * Width of the Luma (Y) Buffer */ UWORD32 u4_y_wd; /** * Height of the Luma (Y) Buffer */ UWORD32 u4_y_ht; /** * Stride/Pitch of the Luma (Y) Buffer */ UWORD32 u4_y_strd; /** * Width of the Chroma (Cb) Buffer */ UWORD32 u4_u_wd; /** * Height of the Chroma (Cb) Buffer */ UWORD32 u4_u_ht; /** * Stride/Pitch of the Chroma (Cb) Buffer */ UWORD32 u4_u_strd; /** * Width of the Chroma (Cr) Buffer */ UWORD32 u4_v_wd; /** * Height of the Chroma (Cr) Buffer */ UWORD32 u4_v_ht; /** * Stride/Pitch of the Chroma (Cr) Buffer */ UWORD32 u4_v_strd; }iv_yuv_buf_t; #endif /* _IV_H */ ================================================ FILE: dependencies/ih264d/decoder/ivd.h ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ivd.h * * @brief * This file contains all the necessary structure and enumeration * definitions needed for the Application Program Interface(API) of the * Ittiam Video Decoders * * @author * 100239(RCY) * * @remarks * None * ******************************************************************************* */ #ifndef _IVD_H #define _IVD_H /*****************************************************************************/ /* Constant Macros */ /*****************************************************************************/ #define IVD_VIDDEC_MAX_IO_BUFFERS 64 /** SEI macros */ /* * @brief specifies the number of colour primary components of the mastering display */ #define NUM_SEI_MDCV_PRIMARIES 3 /* * @brief specifies the number of colour primary components of the nominal content colour volume */ #define NUM_SEI_CCV_PRIMARIES 3 /*****************************************************************************/ /* Typedefs */ /*****************************************************************************/ /*****************************************************************************/ /* Enums */ /*****************************************************************************/ /* IVD_ARCH_T: Architecture Enumeration */ typedef enum { ARCH_NA = 0x7FFFFFFF, ARCH_ARM_NONEON = 0x0, ARCH_ARM_A9Q, ARCH_ARM_A9A, ARCH_ARM_A9, ARCH_ARM_A7, ARCH_ARM_A5, ARCH_ARM_A15, ARCH_ARM_NEONINTR, ARCH_ARMV8_GENERIC, ARCH_X86_GENERIC = 0x100, ARCH_X86_SSSE3, ARCH_X86_SSE42, ARCH_X86_AVX2, ARCH_MIPS_GENERIC = 0x200, ARCH_MIPS_32 }IVD_ARCH_T; /* IVD_SOC_T: SOC Enumeration */ typedef enum { SOC_NA = 0x7FFFFFFF, SOC_GENERIC = 0x0, SOC_HISI_37X = 0x100, }IVD_SOC_T; /* IVD_FRAME_SKIP_MODE_T:Skip mode Enumeration */ typedef enum { IVD_SKIP_NONE = 0x7FFFFFFF, IVD_SKIP_P = 0x1, IVD_SKIP_B = 0x2, IVD_SKIP_I = 0x3, IVD_SKIP_IP = 0x4, IVD_SKIP_IB = 0x5, IVD_SKIP_PB = 0x6, IVD_SKIP_IPB = 0x7, IVD_SKIP_IDR = 0x8, IVD_SKIP_DEFAULT = IVD_SKIP_NONE, }IVD_FRAME_SKIP_MODE_T; /* IVD_VIDEO_DECODE_MODE_T: Set decoder to decode either frame worth of data */ /* or only header worth of data */ typedef enum { IVD_DECODE_MODE_NA = 0x7FFFFFFF, /* This enables the codec to process all decodable units */ IVD_DECODE_FRAME = 0x0, /* This enables the codec to decode header only */ IVD_DECODE_HEADER = 0x1, }IVD_VIDEO_DECODE_MODE_T; /* IVD_DISPLAY_FRAME_OUT_MODE_T: Video Display Frame Output Mode */ typedef enum { IVD_DISPLAY_ORDER_NA = 0x7FFFFFFF, /* To set codec to fill output buffers in display order */ IVD_DISPLAY_FRAME_OUT = 0x0, /* To set codec to fill output buffers in decode order */ IVD_DECODE_FRAME_OUT = 0x1, }IVD_DISPLAY_FRAME_OUT_MODE_T; /* IVD_API_COMMAND_TYPE_T:API command type */ typedef enum { IVD_CMD_VIDEO_NA = 0x7FFFFFFF, IVD_CMD_CREATE = IV_CMD_DUMMY_ELEMENT + 1, IVD_CMD_DELETE, IVD_CMD_VIDEO_CTL, IVD_CMD_VIDEO_DECODE, IVD_CMD_GET_DISPLAY_FRAME, IVD_CMD_REL_DISPLAY_FRAME, IVD_CMD_SET_DISPLAY_FRAME }IVD_API_COMMAND_TYPE_T; /* IVD_CONTROL_API_COMMAND_TYPE_T: Video Control API command type */ typedef enum { IVD_CMD_NA = 0x7FFFFFFF, IVD_CMD_CTL_GETPARAMS = 0x0, IVD_CMD_CTL_SETPARAMS = 0x1, IVD_CMD_CTL_RESET = 0x2, IVD_CMD_CTL_SETDEFAULT = 0x3, IVD_CMD_CTL_FLUSH = 0x4, IVD_CMD_CTL_GETBUFINFO = 0x5, IVD_CMD_CTL_GETVERSION = 0x6, IVD_CMD_CTL_CODEC_SUBCMD_START = 0x7 }IVD_CONTROL_API_COMMAND_TYPE_T; /* IVD_ERROR_BITS_T: A UWORD32 container will be used for reporting the error*/ /* code to the application. The first 8 bits starting from LSB have been */ /* reserved for the codec to report internal error details. The rest of the */ /* bits will be generic for all video decoders and each bit has an associated*/ /* meaning as mentioned below. The unused bit fields are reserved for future */ /* extenstions and will be zero in the current implementation */ typedef enum { /* Bit 8 - Applied concealment. */ IVD_APPLIEDCONCEALMENT = 0x8, /* Bit 9 - Insufficient input data. */ IVD_INSUFFICIENTDATA = 0x9, /* Bit 10 - Data problem/corruption. */ IVD_CORRUPTEDDATA = 0xa, /* Bit 11 - Header problem/corruption. */ IVD_CORRUPTEDHEADER = 0xb, /* Bit 12 - Unsupported feature/parameter in input. */ IVD_UNSUPPORTEDINPUT = 0xc, /* Bit 13 - Unsupported input parameter orconfiguration. */ IVD_UNSUPPORTEDPARAM = 0xd, /* Bit 14 - Fatal error (stop the codec).If there is an */ /* error and this bit is not set, the error is a recoverable one. */ IVD_FATALERROR = 0xe, /* Bit 15 - Invalid bitstream. Applies when Bitstream/YUV frame */ /* buffer for encode/decode call is made with non-valid or zero u4_size */ /* data */ IVD_INVALID_BITSTREAM = 0xf, /* Bit 16 */ IVD_INCOMPLETE_BITSTREAM = 0x10, IVD_ERROR_BITS_T_DUMMY_ELEMENT = 0x7FFFFFFF }IVD_ERROR_BITS_T; /* IVD_CONTROL_API_COMMAND_TYPE_T: Video Control API command type */ typedef enum { IVD_ERROR_NONE = 0x0, IVD_NUM_MEM_REC_FAILED = 0x1, IVD_NUM_REC_NOT_SUFFICIENT = 0x2, IVD_FILL_MEM_REC_FAILED = 0x3, IVD_REQUESTED_WIDTH_NOT_SUPPPORTED = 0x4, IVD_REQUESTED_HEIGHT_NOT_SUPPPORTED = 0x5, IVD_INIT_DEC_FAILED = 0x6, IVD_INIT_DEC_NOT_SUFFICIENT = 0x7, IVD_INIT_DEC_WIDTH_NOT_SUPPPORTED = 0x8, IVD_INIT_DEC_HEIGHT_NOT_SUPPPORTED = 0x9, IVD_INIT_DEC_MEM_NOT_ALIGNED = 0xa, IVD_INIT_DEC_COL_FMT_NOT_SUPPORTED = 0xb, IVD_INIT_DEC_MEM_REC_NOT_SUFFICIENT = 0xc, IVD_GET_VERSION_DATABUFFER_SZ_INSUFFICIENT = 0xd, IVD_BUFFER_SIZE_SET_TO_ZERO = 0xe, IVD_UNEXPECTED_END_OF_STREAM = 0xf, IVD_SEQUENCE_HEADER_NOT_DECODED = 0x10, IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED = 0x11, IVD_MAX_FRAME_LIMIT_REACHED = 0x12, IVD_IP_API_STRUCT_SIZE_INCORRECT = 0x13, IVD_OP_API_STRUCT_SIZE_INCORRECT = 0x14, IVD_HANDLE_NULL = 0x15, IVD_HANDLE_STRUCT_SIZE_INCORRECT = 0x16, IVD_INVALID_HANDLE_NULL = 0x17, IVD_INVALID_API_CMD = 0x18, IVD_UNSUPPORTED_API_CMD = 0x19, IVD_MEM_REC_STRUCT_SIZE_INCORRECT = 0x1a, IVD_DISP_FRM_ZERO_OP_BUFS = 0x1b, IVD_DISP_FRM_OP_BUF_NULL = 0x1c, IVD_DISP_FRM_ZERO_OP_BUF_SIZE = 0x1d, IVD_DEC_FRM_BS_BUF_NULL = 0x1e, IVD_SET_CONFG_INVALID_DEC_MODE = 0x1f, IVD_SET_CONFG_UNSUPPORTED_DISP_WIDTH = 0x20, IVD_RESET_FAILED = 0x21, IVD_INIT_DEC_MEM_REC_OVERLAP_ERR = 0x22, IVD_INIT_DEC_MEM_REC_BASE_NULL = 0x23, IVD_INIT_DEC_MEM_REC_ALIGNMENT_ERR = 0x24, IVD_INIT_DEC_MEM_REC_INSUFFICIENT_SIZE = 0x25, IVD_INIT_DEC_MEM_REC_INCORRECT_TYPE = 0x26, IVD_DEC_NUMBYTES_INV = 0x27, IVD_DEC_REF_BUF_NULL = 0x28, IVD_DEC_FRM_SKIPPED = 0x29, IVD_RES_CHANGED = 0x2a, IVD_MEM_ALLOC_FAILED = 0x2b, IVD_DUMMY_ELEMENT_FOR_CODEC_EXTENSIONS = 0xD0, }IVD_ERROR_CODES_T; /*****************************************************************************/ /* Structure */ /*****************************************************************************/ /* structure for passing output buffers to codec during get display buffer */ /* call */ typedef struct { /** * number of output buffers */ UWORD32 u4_num_bufs; /** *list of pointers to output buffers */ UWORD8 *pu1_bufs[IVD_VIDDEC_MAX_IO_BUFFERS]; /** * sizes of each output buffer */ UWORD32 u4_min_out_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; }ivd_out_bufdesc_t; /*****************************************************************************/ /* Create decoder */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_CREATE */ typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * e_cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * format in which codec has to give out frame data for display */ IV_COLOR_FORMAT_T e_output_format; /** * Flag to indicate shared display buffer mode */ UWORD32 u4_share_disp_buf; /** * Pointer to a function for aligned allocation. */ void *(*pf_aligned_alloc)(void *pv_mem_ctxt, WORD32 alignment, WORD32 size); /** * Pointer to a function for aligned free. */ void (*pf_aligned_free)(void *pv_mem_ctxt, void *pv_buf); /** * Pointer to memory context that is needed during alloc/free for custom * memory managers. This will be passed as first argument to pf_aligned_alloc and * pf_aligned_free. * If application is using standard memory functions like * malloc/aligned_malloc/memalign/free/aligned_free, * then this is not needed and can be set to NULL */ void *pv_mem_ctxt; }ivd_create_ip_t; typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * u4_error_code */ UWORD32 u4_error_code; /** * Codec Handle */ void *pv_handle; }ivd_create_op_t; /*****************************************************************************/ /* Delete decoder */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_DELETE */ typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; }ivd_delete_ip_t; typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * error_code */ UWORD32 u4_error_code; }ivd_delete_op_t; /*****************************************************************************/ /* Video Decode */ /*****************************************************************************/ /* SEI params deocde */ typedef struct { UWORD8 u1_sei_mdcv_params_present_flag; UWORD8 u1_sei_cll_params_present_flag; UWORD8 u1_sei_ave_params_present_flag; UWORD8 u1_sei_ccv_params_present_flag; }ivd_sei_decode_op_t; /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_DECODE */ typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * e_cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * u4_ts */ UWORD32 u4_ts; /** * u4_num_Bytes */ UWORD32 u4_num_Bytes; /** * pv_stream_buffer */ void *pv_stream_buffer; /** * output buffer desc */ ivd_out_bufdesc_t s_out_buffer; }ivd_video_decode_ip_t; typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * u4_error_code */ UWORD32 u4_error_code; /** * num_bytes_consumed */ UWORD32 u4_num_bytes_consumed; /** * pic_wd */ UWORD32 u4_pic_wd; /** * pic_ht */ UWORD32 u4_pic_ht; /** * pic_type */ IV_PICTURE_CODING_TYPE_T e_pic_type; /** * frame_decoded_flag */ UWORD32 u4_frame_decoded_flag; /** * new_seq */ UWORD32 u4_new_seq; /** * output_present */ UWORD32 u4_output_present; /** * progressive_frame_flag */ UWORD32 u4_progressive_frame_flag; /** * is_ref_flag */ UWORD32 u4_is_ref_flag; /** * output_format */ IV_COLOR_FORMAT_T e_output_format; /** * disp_frm_buf */ iv_yuv_buf_t s_disp_frm_buf; /** * sei params o/p struct */ ivd_sei_decode_op_t s_sei_decode_op; /** * fld_type */ IV_FLD_TYPE_T e4_fld_type; /** * ts */ UWORD32 u4_ts; /** * disp_buf_id */ UWORD32 u4_disp_buf_id; /** * reorder_depth */ WORD32 i4_reorder_depth; /** * disp_buf_id */ WORD32 i4_display_index; /* crop info from SPS */ UWORD8 u1_frame_cropping_flag; UWORD8 u1_frame_cropping_rect_left_ofst; UWORD8 u1_frame_cropping_rect_right_ofst; UWORD8 u1_frame_cropping_rect_top_ofst; UWORD8 u1_frame_cropping_rect_bottom_ofst; /* uncropped image size */ UWORD32 u4_raw_wd; UWORD32 u4_raw_ht; }ivd_video_decode_op_t; /*****************************************************************************/ /* Get Display Frame */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_GET_DISPLAY_FRAME */ typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * e_cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * output buffer desc */ ivd_out_bufdesc_t s_out_buffer; }ivd_get_display_frame_ip_t; typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * error_code */ UWORD32 u4_error_code; /** * progressive_frame_flag */ UWORD32 u4_progressive_frame_flag; /** * pic_type */ IV_PICTURE_CODING_TYPE_T e_pic_type; /** * is_ref_flag */ UWORD32 u4_is_ref_flag; /** * output_format */ IV_COLOR_FORMAT_T e_output_format; /** * disp_frm_buf */ iv_yuv_buf_t s_disp_frm_buf; /** * fld_type */ IV_FLD_TYPE_T e4_fld_type; /** * ts */ UWORD32 u4_ts; /** * disp_buf_id */ UWORD32 u4_disp_buf_id; }ivd_get_display_frame_op_t; /*****************************************************************************/ /* Set Display Frame */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_SET_DISPLAY_FRAME */ typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * num_disp_bufs */ UWORD32 num_disp_bufs; /** * output buffer desc */ ivd_out_bufdesc_t s_disp_buffer[IVD_VIDDEC_MAX_IO_BUFFERS]; }ivd_set_display_frame_ip_t; typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * error code */ UWORD32 u4_error_code; }ivd_set_display_frame_op_t; /*****************************************************************************/ /* Release Display Frame */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_SET_DISPLAY_FRAME */ typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * e_cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * disp_buf_id */ UWORD32 u4_disp_buf_id; }ivd_rel_display_frame_ip_t; typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * error code */ UWORD32 u4_error_code; }ivd_rel_display_frame_op_t; /*****************************************************************************/ /* Video control Flush */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ /* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd = IVD_CMD_ctl_FLUSH */ typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; }ivd_ctl_flush_ip_t; typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * error code */ UWORD32 u4_error_code; }ivd_ctl_flush_op_t; /*****************************************************************************/ /* Video control reset */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ /* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd = IVD_CMD_ctl_RESET */ typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; }ivd_ctl_reset_ip_t; typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * error code */ UWORD32 u4_error_code; }ivd_ctl_reset_op_t; /*****************************************************************************/ /* Video control Set Params */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ /* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd=IVD_CMD_ctl_SETPARAMS */ /* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd=IVD_CMD_ctl_SETDEFAULT */ typedef struct { /** * u4_size of the structure */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; /** * vid_dec_mode */ IVD_VIDEO_DECODE_MODE_T e_vid_dec_mode; /** * disp_wd */ UWORD32 u4_disp_wd; /** * frm_skip_mode */ IVD_FRAME_SKIP_MODE_T e_frm_skip_mode; /** * frm_out_mode */ IVD_DISPLAY_FRAME_OUT_MODE_T e_frm_out_mode; }ivd_ctl_set_config_ip_t; typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * u4_error_code */ UWORD32 u4_error_code; }ivd_ctl_set_config_op_t; /*****************************************************************************/ /* Video control:Get Buf Info */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ /* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd=IVD_CMD_ctl_GETBUFINFO */ typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * e_cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; }ivd_ctl_getbufinfo_ip_t; typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * error code */ UWORD32 u4_error_code; /** * no of display buffer sets required by codec */ UWORD32 u4_num_disp_bufs; /** * no of input buffers required for codec */ UWORD32 u4_min_num_in_bufs; /** * no of output buffers required for codec */ UWORD32 u4_min_num_out_bufs; /** * sizes of each input buffer required */ UWORD32 u4_min_in_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; /** * sizes of each output buffer required */ UWORD32 u4_min_out_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; }ivd_ctl_getbufinfo_op_t; /*****************************************************************************/ /* Video control:Getstatus Call */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ /* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd=IVD_CMD_ctl_GETPARAMS */ typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; }ivd_ctl_getstatus_ip_t; typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * error code */ UWORD32 u4_error_code; /** * no of display buffer sets required by codec */ UWORD32 u4_num_disp_bufs; /** * u4_pic_ht */ UWORD32 u4_pic_ht; /** * u4_pic_wd */ UWORD32 u4_pic_wd; /** * frame_rate */ UWORD32 u4_frame_rate; /** * u4_bit_rate */ UWORD32 u4_bit_rate; /** * content_type */ IV_CONTENT_TYPE_T e_content_type; /** * output_chroma_format */ IV_COLOR_FORMAT_T e_output_chroma_format; /** * no of input buffers required for codec */ UWORD32 u4_min_num_in_bufs; /** * no of output buffers required for codec */ UWORD32 u4_min_num_out_bufs; /** * sizes of each input buffer required */ UWORD32 u4_min_in_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; /** * sizes of each output buffer required */ UWORD32 u4_min_out_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; }ivd_ctl_getstatus_op_t; /*****************************************************************************/ /* Video control:Get Version Info */ /*****************************************************************************/ /* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ /* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd=IVD_CMD_ctl_GETVERSION */ typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * cmd */ IVD_API_COMMAND_TYPE_T e_cmd; /** * sub_cmd */ IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; /** * pv_version_buffer */ void *pv_version_buffer; /** * version_buffer_size */ UWORD32 u4_version_buffer_size; }ivd_ctl_getversioninfo_ip_t; typedef struct{ /** * u4_size of the structure */ UWORD32 u4_size; /** * error code */ UWORD32 u4_error_code; }ivd_ctl_getversioninfo_op_t; #endif /* __IVD_H__ */ ================================================ FILE: dependencies/ih264d/decoder/mips/ih264d_function_selector.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * imp2d_function_selector.c * * @brief * Contains functions to initialize function pointers used in hevc * * @author * Naveen * * @par List of Functions: * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> /* User Include files */ #include "ih264_typedefs.h" #include "iv.h" #include "ivd.h" #include "ih264_defs.h" #include "ih264_size_defs.h" #include "ih264_error.h" #include "ih264_trans_quant_itrans_iquant.h" #include "ih264_inter_pred_filters.h" #include "ih264d_structs.h" #include "ih264d_function_selector.h" void ih264d_init_function_ptr(dec_struct_t *ps_codec) { ih264d_init_function_ptr_generic(ps_codec); } void ih264d_init_arch(dec_struct_t *ps_codec) { ps_codec->e_processor_arch = ARCH_NA; } ================================================ FILE: dependencies/ih264d/decoder/x86/ih264d_function_selector.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore * Modified for use with Cemu emulator project */ /** ******************************************************************************* * @file * imp2d_function_selector.c * * @brief * Contains functions to initialize function pointers used in hevc * * @author * Naveen * * @par List of Functions: * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> /* User Include files */ #include "ih264_typedefs.h" #include "iv.h" #include "ivd.h" #include "ih264_defs.h" #include "ih264_size_defs.h" #include "ih264_error.h" #include "ih264_trans_quant_itrans_iquant.h" #include "ih264_inter_pred_filters.h" #include "ih264d_structs.h" #include "ih264d_function_selector.h" void ih264d_init_function_ptr(dec_struct_t *ps_codec) { ih264d_init_function_ptr_generic(ps_codec); switch(ps_codec->e_processor_arch) { case ARCH_X86_GENERIC: ih264d_init_function_ptr_generic(ps_codec); break; case ARCH_X86_SSSE3: ih264d_init_function_ptr_ssse3(ps_codec); break; case ARCH_X86_SSE42: default: ih264d_init_function_ptr_ssse3(ps_codec); ih264d_init_function_ptr_sse42(ps_codec); break; } } #ifdef __GNUC__ #include <cpuid.h> void __cpuid2(signed int* cpuInfo, unsigned int level) { __get_cpuid (level, (unsigned int*)cpuInfo+0, (unsigned int*)cpuInfo+1, (unsigned int*)cpuInfo+2, (unsigned int*)cpuInfo+3); } #define __getCPUId __cpuid2 #else #define __getCPUId __cpuid #endif void ih264d_init_arch(dec_struct_t *ps_codec) { int cpuInfo[4]; UWORD8 hasSSSE3; UWORD8 hasSSE4_2; __getCPUId(cpuInfo, 0x1); hasSSSE3 = ((cpuInfo[2] >> 9) & 1); hasSSE4_2 = ((cpuInfo[2] >> 20) & 1); if (hasSSE4_2 != 0) ps_codec->e_processor_arch = ARCH_X86_SSE42; else if (hasSSSE3 != 0) ps_codec->e_processor_arch = ARCH_X86_SSSE3; else ps_codec->e_processor_arch = ARCH_X86_GENERIC; } ================================================ FILE: dependencies/ih264d/decoder/x86/ih264d_function_selector_sse42.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264e_function_selector_generic.c * * @brief * Contains functions to initialize function pointers of codec context * * @author * Ittiam * * @par List of Functions: * - ih264e_init_function_ptr_generic * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System Include files */ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> /* User Include files */ #include "ih264_typedefs.h" #include "iv.h" #include "ivd.h" #include "ih264_defs.h" #include "ih264_size_defs.h" #include "ih264_error.h" #include "ih264_trans_quant_itrans_iquant.h" #include "ih264_inter_pred_filters.h" #include "ih264d_structs.h" #ifdef __GNUC__ #define ATTRIBUTE_SSE42 __attribute__((target("sse4.2"))) #else #define ATTRIBUTE_SSE42 #endif /** ******************************************************************************* * * @brief Initialize the intra/inter/transform/deblk function pointers of * codec context * * @par Description: the current routine initializes the function pointers of * codec context basing on the architecture in use * * @param[in] ps_codec * Codec context pointer * * @returns none * * @remarks none * ******************************************************************************* */ ATTRIBUTE_SSE42 void ih264d_init_function_ptr_sse42(dec_struct_t *ps_codec) { ps_codec->pf_default_weighted_pred_luma = ih264_default_weighted_pred_luma_sse42; ps_codec->pf_default_weighted_pred_chroma = ih264_default_weighted_pred_chroma_sse42; ps_codec->pf_weighted_pred_luma = ih264_weighted_pred_luma_sse42; ps_codec->pf_weighted_pred_chroma = ih264_weighted_pred_chroma_sse42; ps_codec->pf_weighted_bi_pred_luma = ih264_weighted_bi_pred_luma_sse42; ps_codec->pf_weighted_bi_pred_chroma = ih264_weighted_bi_pred_chroma_sse42; ps_codec->pf_iquant_itrans_recon_luma_4x4 = ih264_iquant_itrans_recon_4x4_sse42; ps_codec->pf_iquant_itrans_recon_chroma_4x4 = ih264_iquant_itrans_recon_chroma_4x4_sse42; ps_codec->pf_ihadamard_scaling_4x4 = ih264_ihadamard_scaling_4x4_sse42; return; } ================================================ FILE: dependencies/ih264d/decoder/x86/ih264d_function_selector_ssse3.c ================================================ /****************************************************************************** * * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************** * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore */ /** ******************************************************************************* * @file * ih264e_function_selector_generic.c * * @brief * Contains functions to initialize function pointers of codec context * * @author * Ittiam * * @par List of Functions: * - ih264e_init_function_ptr_generic * * @remarks * None * ******************************************************************************* */ /*****************************************************************************/ /* File Includes */ /*****************************************************************************/ /* System Include files */ #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <string.h> /* User Include files */ #include "ih264_typedefs.h" #include "iv.h" #include "ivd.h" #include "ih264_defs.h" #include "ih264_size_defs.h" #include "ih264_error.h" #include "ih264_trans_quant_itrans_iquant.h" #include "ih264_inter_pred_filters.h" #include "ih264d_structs.h" #ifdef __GNUC__ #define ATTRIBUTE_SSSE3 __attribute__((target("ssse3"))) #else #define ATTRIBUTE_SSSE3 #endif /** ******************************************************************************* * * @brief Initialize the intra/inter/transform/deblk function pointers of * codec context * * @par Description: the current routine initializes the function pointers of * codec context basing on the architecture in use * * @param[in] ps_codec * Codec context pointer * * @returns none * * @remarks none * ******************************************************************************* */ ATTRIBUTE_SSSE3 void ih264d_init_function_ptr_ssse3(dec_struct_t *ps_codec) { /* Init function pointers for intra pred leaf level functions luma * Intra 16x16 */ ps_codec->apf_intra_pred_luma_16x16[0] = ih264_intra_pred_luma_16x16_mode_vert_ssse3; ps_codec->apf_intra_pred_luma_16x16[1] = ih264_intra_pred_luma_16x16_mode_horz_ssse3; ps_codec->apf_intra_pred_luma_16x16[2] = ih264_intra_pred_luma_16x16_mode_dc_ssse3; ps_codec->apf_intra_pred_luma_16x16[3] = ih264_intra_pred_luma_16x16_mode_plane_ssse3; /* Init function pointers for intra pred leaf level functions luma * Intra 4x4 */ ps_codec->apf_intra_pred_luma_4x4[0] = ih264_intra_pred_luma_4x4_mode_vert_ssse3; ps_codec->apf_intra_pred_luma_4x4[1] = ih264_intra_pred_luma_4x4_mode_horz_ssse3; ps_codec->apf_intra_pred_luma_4x4[2] = ih264_intra_pred_luma_4x4_mode_dc_ssse3; ps_codec->apf_intra_pred_luma_4x4[3] = ih264_intra_pred_luma_4x4_mode_diag_dl_ssse3; ps_codec->apf_intra_pred_luma_4x4[4] = ih264_intra_pred_luma_4x4_mode_diag_dr_ssse3; ps_codec->apf_intra_pred_luma_4x4[5] = ih264_intra_pred_luma_4x4_mode_vert_r_ssse3; ps_codec->apf_intra_pred_luma_4x4[6] = ih264_intra_pred_luma_4x4_mode_horz_d_ssse3; ps_codec->apf_intra_pred_luma_4x4[7] = ih264_intra_pred_luma_4x4_mode_vert_l_ssse3; ps_codec->apf_intra_pred_luma_4x4[8] = ih264_intra_pred_luma_4x4_mode_horz_u_ssse3; /* Init function pointers for intra pred leaf level functions luma * Intra 8x8 */ ps_codec->apf_intra_pred_luma_8x8[0] = ih264_intra_pred_luma_8x8_mode_vert_ssse3; ps_codec->apf_intra_pred_luma_8x8[1] = ih264_intra_pred_luma_8x8_mode_horz_ssse3; ps_codec->apf_intra_pred_luma_8x8[2] = ih264_intra_pred_luma_8x8_mode_dc_ssse3; ps_codec->apf_intra_pred_luma_8x8[3] = ih264_intra_pred_luma_8x8_mode_diag_dl_ssse3; ps_codec->apf_intra_pred_luma_8x8[4] = ih264_intra_pred_luma_8x8_mode_diag_dr_ssse3; ps_codec->apf_intra_pred_luma_8x8[5] = ih264_intra_pred_luma_8x8_mode_vert_r_ssse3; ps_codec->apf_intra_pred_luma_8x8[6] = ih264_intra_pred_luma_8x8_mode_horz_d_ssse3; ps_codec->apf_intra_pred_luma_8x8[7] = ih264_intra_pred_luma_8x8_mode_vert_l_ssse3; ps_codec->apf_intra_pred_luma_8x8[8] = ih264_intra_pred_luma_8x8_mode_horz_u_ssse3; ps_codec->pf_intra_pred_ref_filtering = ih264_intra_pred_luma_8x8_mode_ref_filtering; /* Init function pointers for intra pred leaf level functions chroma * Intra 8x8 */ ps_codec->apf_intra_pred_chroma[0] = ih264_intra_pred_chroma_8x8_mode_vert_ssse3; ps_codec->apf_intra_pred_chroma[1] = ih264_intra_pred_chroma_8x8_mode_horz_ssse3; ps_codec->apf_intra_pred_chroma[2] = ih264_intra_pred_chroma_8x8_mode_dc; ps_codec->apf_intra_pred_chroma[3] = ih264_intra_pred_chroma_8x8_mode_plane_ssse3; ps_codec->pf_pad_left_luma = ih264_pad_left_luma_ssse3; ps_codec->pf_pad_left_chroma = ih264_pad_left_chroma_ssse3; ps_codec->pf_pad_right_luma = ih264_pad_right_luma_ssse3; ps_codec->pf_pad_right_chroma = ih264_pad_right_chroma_ssse3; ps_codec->pf_iquant_itrans_recon_luma_4x4 = ih264_iquant_itrans_recon_4x4_ssse3; ps_codec->pf_iquant_itrans_recon_luma_4x4_dc = ih264_iquant_itrans_recon_4x4_dc_ssse3; ps_codec->pf_iquant_itrans_recon_luma_8x8 = ih264_iquant_itrans_recon_8x8_ssse3; ps_codec->pf_iquant_itrans_recon_luma_8x8_dc = ih264_iquant_itrans_recon_8x8_dc_ssse3; ps_codec->pf_iquant_itrans_recon_chroma_4x4_dc = ih264_iquant_itrans_recon_chroma_4x4_dc_ssse3; /* Init fn ptr luma deblocking */ ps_codec->pf_deblk_luma_vert_bs4 = ih264_deblk_luma_vert_bs4_ssse3; ps_codec->pf_deblk_luma_vert_bslt4 = ih264_deblk_luma_vert_bslt4_ssse3; ps_codec->pf_deblk_luma_vert_bs4_mbaff = ih264_deblk_luma_vert_bs4_mbaff_ssse3; ps_codec->pf_deblk_luma_vert_bslt4_mbaff = ih264_deblk_luma_vert_bslt4_mbaff_ssse3; ps_codec->pf_deblk_luma_horz_bs4 = ih264_deblk_luma_horz_bs4_ssse3; ps_codec->pf_deblk_luma_horz_bslt4 = ih264_deblk_luma_horz_bslt4_ssse3; /* Init fn ptr chroma deblocking */ ps_codec->pf_deblk_chroma_vert_bs4 = ih264_deblk_chroma_vert_bs4_ssse3; ps_codec->pf_deblk_chroma_horz_bs4 = ih264_deblk_chroma_horz_bs4_ssse3; ps_codec->pf_deblk_chroma_vert_bs4_mbaff = ih264_deblk_chroma_vert_bs4_mbaff_ssse3; ps_codec->pf_deblk_chroma_vert_bslt4 = ih264_deblk_chroma_vert_bslt4_ssse3; ps_codec->pf_deblk_chroma_horz_bslt4 = ih264_deblk_chroma_horz_bslt4_ssse3; ps_codec->pf_deblk_chroma_vert_bslt4_mbaff = ih264_deblk_chroma_vert_bslt4_mbaff_ssse3; /* Inter pred leaf level functions */ ps_codec->apf_inter_pred_luma[0] = ih264_inter_pred_luma_copy_ssse3; ps_codec->apf_inter_pred_luma[1] = ih264_inter_pred_luma_horz_qpel_ssse3; ps_codec->apf_inter_pred_luma[2] = ih264_inter_pred_luma_horz_ssse3; ps_codec->apf_inter_pred_luma[3] = ih264_inter_pred_luma_horz_qpel_ssse3; ps_codec->apf_inter_pred_luma[4] = ih264_inter_pred_luma_vert_qpel_ssse3; ps_codec->apf_inter_pred_luma[5] = ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3; ps_codec->apf_inter_pred_luma[6] = ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3; ps_codec->apf_inter_pred_luma[7] = ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3; ps_codec->apf_inter_pred_luma[8] = ih264_inter_pred_luma_vert_ssse3; ps_codec->apf_inter_pred_luma[9] = ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3; ps_codec->apf_inter_pred_luma[10] = ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3; ps_codec->apf_inter_pred_luma[11] = ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3; ps_codec->apf_inter_pred_luma[12] = ih264_inter_pred_luma_vert_qpel_ssse3; ps_codec->apf_inter_pred_luma[13] = ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3; ps_codec->apf_inter_pred_luma[14] = ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3; ps_codec->apf_inter_pred_luma[15] = ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3; ps_codec->pf_inter_pred_chroma = ih264_inter_pred_chroma_ssse3; return; } ================================================ FILE: dependencies/vcpkg_overlay_ports/.gitkeep ================================================ ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/example/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.7) project(wxwidgets-example) add_executable(main WIN32 popup.cpp) find_package(wxWidgets REQUIRED) target_compile_features(main PRIVATE cxx_std_11) target_compile_definitions(main PRIVATE ${wxWidgets_DEFINITIONS} "$<$<CONFIG:DEBUG>:${wxWidgets_DEFINITIONS_DEBUG}>") target_include_directories(main PRIVATE ${wxWidgets_INCLUDE_DIRS}) target_link_libraries(main PRIVATE ${wxWidgets_LIBRARIES}) add_executable(main2 WIN32 popup.cpp) find_package(wxWidgets CONFIG REQUIRED) target_link_libraries(main2 PRIVATE wx::core wx::base) target_compile_features(main2 PRIVATE cxx_std_11) option(USE_WXRC "Use the wxrc resource compiler" ON) if(USE_WXRC) execute_process( COMMAND "${wxWidgets_wxrc_EXECUTABLE}" --help RESULTS_VARIABLE error_result ) if(error_result) message(FATAL_ERROR "Failed to run wxWidgets_wxrc_EXECUTABLE (${wxWidgets_wxrc_EXECUTABLE})") endif() endif() set(PRINT_VARS "" CACHE STRING "Variables to print at the end of configuration") foreach(var IN LISTS PRINT_VARS) message(STATUS "${var}:=${${var}}") endforeach() ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/fix-libs-export.patch ================================================ diff --git a/build/cmake/config.cmake b/build/cmake/config.cmake index b359560..7504458 100644 --- a/build/cmake/config.cmake +++ b/build/cmake/config.cmake @@ -39,8 +39,14 @@ macro(wx_get_dependencies var lib) else() # For the value like $<$<CONFIG:DEBUG>:LIB_PATH> # Or $<$<NOT:$<CONFIG:DEBUG>>:LIB_PATH> - string(REGEX REPLACE "^.+>:(.+)>$" "\\1" dep_name ${dep}) - if (NOT dep_name) + if(dep MATCHES "^(.+>):(.+)>$") + if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND CMAKE_MATCH_1 STREQUAL [[$<$<NOT:$<CONFIG:DEBUG>>]]) + continue() + elseif(CMAKE_BUILD_TYPE STREQUAL "Release" AND CMAKE_MATCH_1 STREQUAL [[$<$<CONFIG:DEBUG>]]) + continue() + endif() + set(dep_name "${CMAKE_MATCH_2}") + else() set(dep_name ${dep}) endif() endif() ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/fix-listctrl-layout.patch ================================================ diff --git a/src/msw/listctrl.cpp b/src/msw/listctrl.cpp index 5e1cd98a8b..169ab7847d 100644 --- a/src/msw/listctrl.cpp +++ b/src/msw/listctrl.cpp @@ -1385,20 +1385,29 @@ bool wxListCtrl::GetSubItemRect(long item, long subItem, wxRect& rect, int code) wxCopyRECTToRect(rectWin, rect); - // We can't use wxGetListCtrlSubItemRect() for the 0th subitem as 0 means - // the entire row for this function, so we need to calculate it ourselves. - if ( subItem == 0 && code == wxLIST_RECT_BOUNDS ) + // We can't trust the native LVM_GETSUBITEMRECT for LVIR_BOUNDS because: + // - subitem 0 returns the entire row bounds, not just column 0 + // - when column 0 is narrower than the icon, the native control clamps + // all subitems' left edge to at least the icon width, misaligning them + // + // So for all subitems we calculate x and width from GetColumnWidth() in + // visual (column order) order, which always reflects the real column sizes. + if ( subItem != wxLIST_GETSUBITEMRECT_WHOLEITEM && code == wxLIST_RECT_BOUNDS ) { - // Because the columns can be reordered, we need to sum the widths of - // all preceding columns to get the correct x position. + // Start from the row's left edge (which accounts for scrolling). + RECT rowRect; + wxGetListCtrlItemRect(GetHwnd(), item, LVIR_BOUNDS, rowRect); + int x = rowRect.left; + for ( auto col : GetColumnsOrder() ) { if ( col == subItem ) break; - rect.x += GetColumnWidth(col); + x += GetColumnWidth(col); } + rect.x = x; rect.width = GetColumnWidth(subItem); } ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/fix-pcre2.patch ================================================ diff --git a/build/cmake/modules/FindPCRE2.cmake b/build/cmake/modules/FindPCRE2.cmake index a27693a..455675a 100644 --- a/build/cmake/modules/FindPCRE2.cmake +++ b/build/cmake/modules/FindPCRE2.cmake @@ -24,7 +24,10 @@ set(PCRE2_CODE_UNIT_WIDTH_USED "${PCRE2_CODE_UNIT_WIDTH}" CACHE INTERNAL "") find_package(PkgConfig QUIET) pkg_check_modules(PC_PCRE2 QUIET libpcre2-${PCRE2_CODE_UNIT_WIDTH}) +set(PCRE2_LIBRARIES ${PC_PCRE2_LINK_LIBRARIES}) +set(PCRE2_INCLUDE_DIRS ${PC_PCRE2_INCLUDE_DIRS}) +if (0) find_path(PCRE2_INCLUDE_DIRS NAMES pcre2.h HINTS ${PC_PCRE2_INCLUDEDIR} @@ -36,6 +39,7 @@ find_library(PCRE2_LIBRARIES HINTS ${PC_PCRE2_LIBDIR} ${PC_PCRE2_LIBRARY_DIRS} ) +endif() include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(PCRE2 REQUIRED_VARS PCRE2_LIBRARIES PCRE2_INCLUDE_DIRS VERSION_VAR PC_PCRE2_VERSION) ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/gtk3-link-libraries.patch ================================================ diff --git a/build/cmake/modules/FindGTK3.cmake b/build/cmake/modules/FindGTK3.cmake index d2939a1..daf33fe 100644 --- a/build/cmake/modules/FindGTK3.cmake +++ b/build/cmake/modules/FindGTK3.cmake @@ -47,6 +47,12 @@ include(CheckSymbolExists) set(CMAKE_REQUIRED_INCLUDES ${GTK3_INCLUDE_DIRS}) check_symbol_exists(GDK_WINDOWING_WAYLAND "gdk/gdk.h" wxHAVE_GDK_WAYLAND) check_symbol_exists(GDK_WINDOWING_X11 "gdk/gdk.h" wxHAVE_GDK_X11) +# With Lerc support in TIFF, Gtk3 may carry C++ compiler libs which break FindWxWidgets.cmake. +# WxWidgets is C++, so we can remove them here using the inverse pattern. +set(cxx_libs "${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES}") +list(REMOVE_ITEM cxx_libs ${CMAKE_C_IMPLICIT_LINK_LIBRARIES}) +list(REMOVE_ITEM GTK3_LINK_LIBRARIES ${cxx_libs}) +set(GTK3_LIBRARIES "${GTK3_LINK_LIBRARIES}" CACHE INTERNAL "") include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(GTK3 DEFAULT_MSG GTK3_INCLUDE_DIRS GTK3_LIBRARIES VERSION_OK) ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/install-layout.patch ================================================ diff --git a/build/cmake/init.cmake b/build/cmake/init.cmake index 4ed7b264ef..195d776aeb 100644 --- a/build/cmake/init.cmake +++ b/build/cmake/init.cmake @@ -201,11 +201,11 @@ endif() wx_get_install_dir(include) if(WIN32_MSVC_NAMING) if(wxBUILD_SHARED) - set(lib_suffix "_dll") + # set(lib_suffix "_dll") else() - set(lib_suffix "_lib") + # set(lib_suffix "_lib") endif() - set(wxPLATFORM_LIB_DIR "${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}${lib_suffix}") + # set(wxPLATFORM_LIB_DIR "${wxCOMPILER_PREFIX}${wxARCH_SUFFIX}${lib_suffix}") set(wxINSTALL_INCLUDE_DIR "${include_dir}") else() wx_get_flavour(lib_flavour "-") diff --git a/build/cmake/install.cmake b/build/cmake/install.cmake index cbbefdebe9..8f2759d5ad 100644 --- a/build/cmake/install.cmake +++ b/build/cmake/install.cmake @@ -66,7 +66,7 @@ else() wx_get_install_platform_dir(runtime) install(DIRECTORY DESTINATION "${runtime_dir}") install(CODE "execute_process( \ - COMMAND ${CMAKE_COMMAND} -E create_symlink \ + COMMAND ${CMAKE_COMMAND} -E copy \ \"${CMAKE_INSTALL_PREFIX}/${library_dir}/wx/config/${wxBUILD_FILE_ID}\" \ \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${runtime_dir}/wx-config\" \ )" diff --git a/build/cmake/utils/CMakeLists.txt b/build/cmake/utils/CMakeLists.txt index 608351ac42..d8110f9148 100644 --- a/build/cmake/utils/CMakeLists.txt +++ b/build/cmake/utils/CMakeLists.txt @@ -40,7 +40,7 @@ if(wxUSE_XRC) # Don't use wx_install() here to preserve escaping. install(CODE "execute_process( \ - COMMAND ${CMAKE_COMMAND} -E create_symlink \ + COMMAND ${CMAKE_COMMAND} -E copy \ \"${CMAKE_INSTALL_PREFIX}/${runtime_dir}/${wxrc_output_name}${EXE_SUFFIX}\" \ \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${runtime_dir}/wxrc${EXE_SUFFIX}\" \ )" ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/nanosvg-ext-depend.patch ================================================ diff --git a/build/cmake/wxWidgetsConfig.cmake.in b/build/cmake/wxWidgetsConfig.cmake.in index 2f3a735d92..c134900764 100644 --- a/build/cmake/wxWidgetsConfig.cmake.in +++ b/build/cmake/wxWidgetsConfig.cmake.in @@ -155,6 +155,7 @@ foreach(libname @wxLIB_TARGETS@) endforeach() include(CMakeFindDependencyMacro) +find_dependency(NanoSVG CONFIG) if(TARGET wx::wxnet AND @wxUSE_WEBREQUEST_CURL@) # make sure CURL targets are available: ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/portfile.cmake ================================================ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO wxWidgets/wxWidgets REF "v${VERSION}" SHA512 c47460c8bc150445e7774a287a79bd70b6d96b91ea46b51f238a317f068e466d570c5a94e2204d112e42da781a5cef98236718efff4b7f539d61a12ceaf65eb7 HEAD_REF master PATCHES install-layout.patch relocatable-wx-config.patch nanosvg-ext-depend.patch fix-libs-export.patch fix-pcre2.patch gtk3-link-libraries.patch sdl2.patch fix-listctrl-layout.patch ) # Submodule dependencies vcpkg_from_github( OUT_SOURCE_PATH lexilla_SOURCE_PATH REPO wxWidgets/lexilla REF "0dbce0b418b8b3d2ef30304d0bf53ff58c07ed84" SHA512 61f7b0217f4518121ecad32f015b53600e565bdd499d4020468cf6c8a533d516c6881115aa5e640afca5ea428535f47680cde426fa3dbabe9e4423b4526853fd HEAD_REF wx ) file(COPY "${lexilla_SOURCE_PATH}/" DESTINATION "${SOURCE_PATH}/src/stc/lexilla") vcpkg_from_github( OUT_SOURCE_PATH scintilla_SOURCE_PATH REPO wxWidgets/scintilla REF "0b90f31ced23241054e8088abb50babe9a44ae67" SHA512 db1f3007f4bd8860fad0817b6cf87980a4b713777025128cf5caea8d6d17b6fafe23fd22ff6886d7d5a420f241d85b7502b85d7e52b4ddb0774edc4b0a0203e7 HEAD_REF wx ) file(COPY "${scintilla_SOURCE_PATH}/" DESTINATION "${SOURCE_PATH}/src/stc/scintilla") vcpkg_check_features( OUT_FEATURE_OPTIONS FEATURE_OPTIONS FEATURES fonts wxUSE_PRIVATE_FONTS media wxUSE_MEDIACTRL secretstore wxUSE_SECRETSTORE sound wxUSE_SOUND webview wxUSE_WEBVIEW webview wxUSE_WEBVIEW_EDGE ) set(OPTIONS_RELEASE "") if(NOT "debug-support" IN_LIST FEATURES) list(APPEND OPTIONS_RELEASE "-DwxBUILD_DEBUG_LEVEL=0") endif() set(OPTIONS "") if(VCPKG_TARGET_IS_WINDOWS AND (VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64" OR VCPKG_TARGET_ARCHITECTURE STREQUAL "arm")) list(APPEND OPTIONS -DwxUSE_STACKWALKER=OFF ) endif() if(VCPKG_TARGET_IS_WINDOWS OR VCPKG_TARGET_IS_OSX) list(APPEND OPTIONS -DwxUSE_WEBREQUEST_CURL=OFF) else() list(APPEND OPTIONS -DwxUSE_WEBREQUEST_CURL=ON) endif() if(VCPKG_TARGET_IS_WINDOWS) if(VCPKG_CRT_LINKAGE STREQUAL "dynamic") list(APPEND OPTIONS -DwxBUILD_USE_STATIC_RUNTIME=OFF) else() list(APPEND OPTIONS -DwxBUILD_USE_STATIC_RUNTIME=ON) endif() endif() if("webview" IN_LIST FEATURES AND VCPKG_LIBRARY_LINKAGE STREQUAL "static") list(APPEND OPTIONS -DwxUSE_WEBVIEW_EDGE_STATIC=ON) endif() vcpkg_find_acquire_program(PKGCONFIG) # This may be set to ON by users in a custom triplet. # The use of 'WXWIDGETS_USE_STD_CONTAINERS' (ON or OFF) is not API compatible # which is why it must be set in a custom triplet rather than a port feature. # For backwards compatibility, we also replace 'wxUSE_STL' (which no longer # exists) with 'wxUSE_STD_STRING_CONV_IN_WXSTRING' which still exists and was # set by `wxUSE_STL` previously. if(NOT DEFINED WXWIDGETS_USE_STL) set(WXWIDGETS_USE_STL OFF) endif() if(NOT DEFINED WXWIDGETS_USE_STD_CONTAINERS) set(WXWIDGETS_USE_STD_CONTAINERS OFF) endif() vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" OPTIONS ${FEATURE_OPTIONS} -DwxUSE_REGEX=sys -DwxUSE_ZLIB=sys -DwxUSE_EXPAT=sys -DwxUSE_LIBJPEG=sys -DwxUSE_LIBPNG=sys -DwxUSE_LIBTIFF=sys -DwxUSE_NANOSVG=sys -DwxUSE_LIBWEBP=sys -DwxUSE_GLCANVAS=ON -DwxUSE_LIBGNOMEVFS=OFF -DwxUSE_LIBNOTIFY=OFF -DwxUSE_STD_STRING_CONV_IN_WXSTRING=${WXWIDGETS_USE_STL} -DwxUSE_STD_CONTAINERS=${WXWIDGETS_USE_STD_CONTAINERS} -DwxUSE_UIACTIONSIMULATOR=OFF -DCMAKE_DISABLE_FIND_PACKAGE_GSPELL=ON -DCMAKE_DISABLE_FIND_PACKAGE_MSPACK=ON -DwxBUILD_INSTALL_RUNTIME_DIR:PATH=bin ${OPTIONS} "-DPKG_CONFIG_EXECUTABLE=${PKGCONFIG}" # The minimum cmake version requirement for Cotire is 2.8.12. # however, we need to declare that the minimum cmake version requirement is at least 3.1 to use CMAKE_PREFIX_PATH as the path to find .pc. -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON OPTIONS_RELEASE ${OPTIONS_RELEASE} MAYBE_UNUSED_VARIABLES CMAKE_DISABLE_FIND_PACKAGE_GSPELL CMAKE_DISABLE_FIND_PACKAGE_MSPACK ) vcpkg_cmake_install() vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/wxWidgets-3.3) # The CMake export is not ready for use: It lacks a config file. file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/lib/cmake ${CURRENT_PACKAGES_DIR}/debug/lib/cmake ) set(tools wxrc) if(NOT VCPKG_TARGET_IS_WINDOWS) list(APPEND tools wxrc-3.3) file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/tools/${PORT}") file(RENAME "${CURRENT_PACKAGES_DIR}/bin/wx-config" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/wx-config") if(NOT VCPKG_BUILD_TYPE) file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/tools/${PORT}/debug") file(RENAME "${CURRENT_PACKAGES_DIR}/debug/bin/wx-config" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/debug/wx-config") endif() endif() vcpkg_copy_tools(TOOL_NAMES ${tools} AUTO_CLEAN) # do the copy pdbs now after the dlls got moved to the expected /bin folder above vcpkg_copy_pdbs() file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/include/msvc") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/lib/mswu") if(VCPKG_BUILD_TYPE STREQUAL "release") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/mswud") endif() file(GLOB_RECURSE INCLUDES "${CURRENT_PACKAGES_DIR}/include/*.h") if(EXISTS "${CURRENT_PACKAGES_DIR}/lib/mswu/wx/setup.h") list(APPEND INCLUDES "${CURRENT_PACKAGES_DIR}/lib/mswu/wx/setup.h") endif() if(EXISTS "${CURRENT_PACKAGES_DIR}/debug/lib/mswud/wx/setup.h") list(APPEND INCLUDES "${CURRENT_PACKAGES_DIR}/debug/lib/mswud/wx/setup.h") endif() foreach(INC IN LISTS INCLUDES) file(READ "${INC}" _contents) if(VCPKG_LIBRARY_LINKAGE STREQUAL "static") string(REPLACE "defined(WXUSINGDLL)" "0" _contents "${_contents}") else() string(REPLACE "defined(WXUSINGDLL)" "1" _contents "${_contents}") endif() # Remove install prefix from setup.h to ensure package is relocatable string(REGEX REPLACE "\n#define wxINSTALL_PREFIX [^\n]*" "\n#define wxINSTALL_PREFIX \"\"" _contents "${_contents}") file(WRITE "${INC}" "${_contents}") endforeach() if(NOT EXISTS "${CURRENT_PACKAGES_DIR}/include/wx/setup.h") file(GLOB_RECURSE WX_SETUP_H_FILES_DBG "${CURRENT_PACKAGES_DIR}/debug/lib/*.h") file(GLOB_RECURSE WX_SETUP_H_FILES_REL "${CURRENT_PACKAGES_DIR}/lib/*.h") if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") vcpkg_replace_string("${WX_SETUP_H_FILES_REL}" "${CURRENT_PACKAGES_DIR}" "" IGNORE_UNCHANGED) string(REPLACE "${CURRENT_PACKAGES_DIR}/lib/" "" WX_SETUP_H_FILES_REL "${WX_SETUP_H_FILES_REL}") string(REPLACE "/setup.h" "" WX_SETUP_H_REL_RELATIVE "${WX_SETUP_H_FILES_REL}") endif() if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") vcpkg_replace_string("${WX_SETUP_H_FILES_DBG}" "${CURRENT_PACKAGES_DIR}" "" IGNORE_UNCHANGED) string(REPLACE "${CURRENT_PACKAGES_DIR}/debug/lib/" "" WX_SETUP_H_FILES_DBG "${WX_SETUP_H_FILES_DBG}") string(REPLACE "/setup.h" "" WX_SETUP_H_DBG_RELATIVE "${WX_SETUP_H_FILES_DBG}") endif() configure_file("${CMAKE_CURRENT_LIST_DIR}/setup.h.in" "${CURRENT_PACKAGES_DIR}/include/wx/setup.h" @ONLY) endif() file(GLOB configs LIST_DIRECTORIES false "${CURRENT_PACKAGES_DIR}/lib/wx/config/*" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/wx-config") foreach(config IN LISTS configs) vcpkg_replace_string("${config}" "${CURRENT_INSTALLED_DIR}" [[${prefix}]]) endforeach() file(GLOB configs LIST_DIRECTORIES false "${CURRENT_PACKAGES_DIR}/debug/lib/wx/config/*" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/debug/wx-config") foreach(config IN LISTS configs) vcpkg_replace_string("${config}" "${CURRENT_INSTALLED_DIR}/debug" [[${prefix}]]) endforeach() # For CMake multi-config in connection with wrapper if(EXISTS "${CURRENT_PACKAGES_DIR}/debug/lib/mswud/wx/setup.h") file(INSTALL "${CURRENT_PACKAGES_DIR}/debug/lib/mswud/wx/setup.h" DESTINATION "${CURRENT_PACKAGES_DIR}/lib/mswud/wx" ) endif() if(NOT "debug-support" IN_LIST FEATURES) if(VCPKG_TARGET_IS_WINDOWS) vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/wx/debug.h" "#define wxDEBUG_LEVEL 1" "#define wxDEBUG_LEVEL 0") else() vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/wx-3.3/wx/debug.h" "#define wxDEBUG_LEVEL 1" "#define wxDEBUG_LEVEL 0") endif() endif() if("example" IN_LIST FEATURES) file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/minimal_example/CMakeLists.txt" "${SOURCE_PATH}/samples/minimal/minimal.cpp" "${SOURCE_PATH}/samples/sample.xpm" "${SOURCE_PATH}/samples/sample.rc" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}/minimal_example" ) vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/${PORT}/minimal_example/minimal.cpp" "../sample.xpm" "sample.xpm") endif() if("example" IN_LIST FEATURES) file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/listctrl_example/CMakeLists.txt" "${SOURCE_PATH}/samples/listctrl/listtest.cpp" "${SOURCE_PATH}/samples/listctrl/listtest.h" "${SOURCE_PATH}/samples/listctrl/listtest.rc" "${SOURCE_PATH}/samples/sample.xpm" "${SOURCE_PATH}/samples/sample.rc" "${SOURCE_PATH}/samples/listctrl/bitmaps" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}/listctrl_example" ) vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/${PORT}/listctrl_example/listtest.cpp" "../sample.xpm" "sample.xpm") vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/${PORT}/listctrl_example/listtest.rc" "../sample.rc" "sample.rc") endif() configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) file(REMOVE "${CURRENT_PACKAGES_DIR}/wxwidgets.props") file(REMOVE "${CURRENT_PACKAGES_DIR}/debug/wxwidgets.props") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/build") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/build") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/docs/licence.txt") ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/relocatable-wx-config.patch ================================================ diff --git a/wx-config.in b/wx-config.in index 4df8571..a90db3d 100644 --- a/wx-config.in +++ b/wx-config.in @@ -398,8 +398,23 @@ is_cross() { [ "x@cross_compiling@" = "xyes" ]; } # Determine the base directories we require. -prefix=${input_option_prefix-${this_prefix:-@prefix@}} -exec_prefix=${input_option_exec_prefix-${input_option_prefix-${this_exec_prefix:-@exec_prefix@}}} +vcpkg_prefix=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) +case "$vcpkg_prefix" in + */lib/wx/config) + vcpkg_prefix=${vcpkg_prefix%/*/*/*} + ;; + */tools/wxwidgets/debug) + vcpkg_prefix=${vcpkg_prefix%/*/*/*}/debug + ;; + */tools/wxwidgets) + vcpkg_prefix=${vcpkg_prefix%/*/*} + ;; +esac +if [ -n "@MINGW@" -a -n "@CMAKE_HOST_WIN32@" ]; then + vcpkg_prefix=$(cygpath -m "$vcpkg_prefix") +fi +prefix=${input_option_prefix-${this_prefix:-$vcpkg_prefix}} +exec_prefix=${input_option_exec_prefix-${input_option_prefix-${this_exec_prefix:-$prefix}}} wxconfdir="@libdir@/wx/config" installed_configs=`cd "$wxconfdir" 2> /dev/null && ls | grep -v "^inplace-"` @@ -936,6 +951,9 @@ prefix=${this_prefix-$prefix} exec_prefix=${this_exec_prefix-$exec_prefix} includedir="@includedir@" +if [ "@CMAKE_BUILD_TYPE@" = "Debug" ] ; then + includedir="${includedir%/debug/include}/include" +fi libdir="@libdir@" bindir="@bindir@" ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/sdl2.patch ================================================ diff --git a/build/cmake/init.cmake b/build/cmake/init.cmake index 4ed7b264ef..9abf7c9d5d 100644 --- a/build/cmake/init.cmake +++ b/build/cmake/init.cmake @@ -667,7 +667,9 @@ if(wxUSE_GUI) endif() if(wxUSE_SOUND AND wxUSE_LIBSDL AND UNIX AND NOT APPLE) - find_package(SDL2) + find_package(SDL2 CONFIG REQUIRED) + set(SDL2_INCLUDE_DIR "" CACHE INTERNAL "") + set(SDL2_LIBRARY SDL2::SDL2 CACHE INTERNAL "") if(NOT SDL2_FOUND) find_package(SDL) mark_as_advanced(SDL_INCLUDE_DIR SDLMAIN_LIBRARY) diff --git a/build/cmake/wxWidgetsConfig.cmake.in b/build/cmake/wxWidgetsConfig.cmake.in index c134900764..7bcb691074 100644 --- a/build/cmake/wxWidgetsConfig.cmake.in +++ b/build/cmake/wxWidgetsConfig.cmake.in @@ -156,6 +156,9 @@ endforeach() include(CMakeFindDependencyMacro) find_dependency(NanoSVG CONFIG) +if("@wxUSE_LIBSDL@") + find_dependency(SDL2 CONFIG) +endif() if(TARGET wx::wxnet AND @wxUSE_WEBREQUEST_CURL@) # make sure CURL targets are available: ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/setup.h.in ================================================ #ifdef _DEBUG #include "../../debug/lib/@WX_SETUP_H_DBG_RELATIVE@/setup.h" #else #include "../../lib/@WX_SETUP_H_REL_RELATIVE@/setup.h" #endif ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/usage ================================================ The package wxwidgets provides CMake targets: find_package(wxWidgets CONFIG REQUIRED) target_link_libraries(main PRIVATE wx::core wx::base) ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg-cmake-wrapper.cmake ================================================ cmake_policy(PUSH) cmake_policy(SET CMP0012 NEW) cmake_policy(SET CMP0054 NEW) cmake_policy(SET CMP0057 NEW) get_filename_component(_vcpkg_wx_root "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) set(wxWidgets_ROOT_DIR "${_vcpkg_wx_root}" CACHE INTERNAL "") set(WX_ROOT_DIR "${_vcpkg_wx_root}" CACHE INTERNAL "") unset(_vcpkg_wx_root) if(WIN32) # Find all libs with "33" infix which is unknown to FindwxWidgets.cmake function(z_vcpkg_wxwidgets_find_base_library BASENAME) find_library(WX_${BASENAME}d wx${BASENAME}33ud NAMES wx${BASENAME}d PATHS "${wxWidgets_ROOT_DIR}/debug/lib" NO_DEFAULT_PATH) find_library(WX_${BASENAME} wx${BASENAME}33u NAMES wx${BASENAME} PATHS "${wxWidgets_ROOT_DIR}/lib" NO_DEFAULT_PATH REQUIRED) endfunction() function(z_vcpkg_wxwidgets_find_suffix_library BASENAME) foreach(lib IN LISTS ARGN) find_library(WX_${lib}d NAMES wx${BASENAME}33ud_${lib} PATHS "${wxWidgets_ROOT_DIR}/debug/lib" NO_DEFAULT_PATH) find_library(WX_${lib} NAMES wx${BASENAME}33u_${lib} PATHS "${wxWidgets_ROOT_DIR}/lib" NO_DEFAULT_PATH) endforeach() endfunction() z_vcpkg_wxwidgets_find_base_library(base) z_vcpkg_wxwidgets_find_suffix_library(base net odbc xml) z_vcpkg_wxwidgets_find_suffix_library(msw core adv aui html media xrc dbgrid gl qa richtext stc ribbon propgrid webview) if(WX_stc AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") z_vcpkg_wxwidgets_find_base_library(scintilla) endif() # Force FindwxWidgets.cmake win32 mode for all windows targets built on windows set(_vcpkg_wxwidgets_backup_crosscompiling "${CMAKE_CROSSCOMPILING}") set(CMAKE_CROSSCOMPILING 0) set(wxWidgets_LIB_DIR "${wxWidgets_ROOT_DIR}/lib" CACHE INTERNAL "") else() # FindwxWidgets.cmake unix mode, single-config set(_vcpkg_wxconfig "") if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR "Debug" IN_LIST MAP_IMPORTED_CONFIG_${CMAKE_BUILD_TYPE}) # Debug set(wxWidgets_LIB_DIR "${wxWidgets_ROOT_DIR}/debug/lib" CACHE INTERNAL "") file(GLOB _vcpkg_wxconfig LIST_DIRECTORIES false "${wxWidgets_LIB_DIR}/wx/config/*") endif() if(NOT _vcpkg_wxconfig) # Release or fallback set(wxWidgets_LIB_DIR "${wxWidgets_ROOT_DIR}/lib" CACHE INTERNAL "") file(GLOB _vcpkg_wxconfig LIST_DIRECTORIES false "${wxWidgets_LIB_DIR}/wx/config/*") endif() set(wxWidgets_CONFIG_EXECUTABLE "${_vcpkg_wxconfig}" CACHE INTERNAL "") unset(_vcpkg_wxconfig) endif() set(WX_LIB_DIR "${wxWidgets_LIB_DIR}" CACHE INTERNAL "") # https://gitlab.kitware.com/cmake/cmake/-/issues/26718 # Instead of special-casing the `atomic` library, we skip the checks entirely. set(_wx_lib_found TRUE) _find_package(${ARGS}) unset(_wx_lib_found) if(DEFINED _vcpkg_wxwidgets_backup_crosscompiling) set(CMAKE_CROSSCOMPILING "${_vcpkg_wxwidgets_backup_crosscompiling}") unset(_vcpkg_wxwidgets_backup_crosscompiling) endif() if("@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static" AND NOT "wx::core" IN_LIST wxWidgets_LIBRARIES) find_package(NanoSVG CONFIG QUIET) list(APPEND wxWidgets_LIBRARIES NanoSVG::nanosvg NanoSVG::nanosvgrast ) endif() if(WIN32 AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static" AND NOT "wx::core" IN_LIST wxWidgets_LIBRARIES) find_package(EXPAT QUIET) find_package(JPEG QUIET) find_package(PNG QUIET) find_package(TIFF QUIET) find_package(ZLIB QUIET) list(APPEND wxWidgets_LIBRARIES ${EXPAT_LIBRARIES} ${JPEG_LIBRARIES} ${PNG_LIBRARIES} ${TIFF_LIBRARIES} ${ZLIB_LIBRARIES} ) endif() cmake_policy(POP) ================================================ FILE: dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg.json ================================================ { "name": "wxwidgets", "version": "3.3.2", "description": [ "Widget toolkit and tools library for creating graphical user interfaces (GUIs) for cross-platform applications. ", "Set WXWIDGETS_USE_STL in a custom triplet to build with the wxUSE_STL build option.", "Set WXWIDGETS_USE_STD_CONTAINERS in a custom triplet to build with the wxUSE_STD_CONTAINERS build option." ], "homepage": "https://github.com/wxWidgets/wxWidgets", "license": "LGPL-2.0-or-later WITH WxWindows-exception-3.1", "supports": "!uwp & !xbox", "dependencies": [ { "name": "cairo", "default-features": false, "platform": "!windows & !osx & !ios" }, { "name": "curl", "default-features": false, "platform": "!windows & !osx" }, "expat", { "name": "gtk3", "platform": "!windows & !osx & !ios" }, { "name": "libiconv", "platform": "!windows" }, "libjpeg-turbo", "libpng", "libwebp", "nanosvg", "opengl", { "name": "pcre2", "default-features": false }, { "name": "tiff", "default-features": false }, { "name": "vcpkg-cmake", "host": true }, { "name": "vcpkg-cmake-config", "host": true }, "zlib" ], "default-features": [ "debug-support", "sound" ], "features": { "debug-support": { "description": "Enable wxWidgets debugging support hooks even for release builds (wxDEBUG_LEVEL 1)" }, "example": { "description": "Example source code and CMake project" }, "fonts": { "description": "Enable to use the font functionality of wxWidgets", "dependencies": [ { "name": "fontconfig", "platform": "!windows & !osx" }, { "name": "pango", "platform": "!windows & !osx" } ] }, "media": { "description": "Build wxMediaCtrl support", "dependencies": [ { "name": "gstreamer", "default-features": false, "platform": "!windows & !osx & !ios" } ] }, "secretstore": { "description": "Use wxSecretStore class" }, "sound": { "description": "Build wxSound support", "dependencies": [ { "name": "sdl2", "default-features": false, "platform": "!windows & !osx & !ios" } ] }, "webview": { "description": "The Edge backend uses Microsoft's Edge WebView2", "dependencies": [ "webview2" ] } } } ================================================ FILE: dependencies/vcpkg_overlay_ports_linux/.gitkeep ================================================ ================================================ FILE: dependencies/vcpkg_overlay_ports_linux/cairo/portfile.cmake ================================================ set(VCPKG_POLICY_EMPTY_PACKAGE enabled) ================================================ FILE: dependencies/vcpkg_overlay_ports_linux/cairo/vcpkg.json ================================================ { "name": "cairo", "version": "1.17.8", "description": "Cairo is a 2D graphics library with support for multiple output devices. Currently supported output targets include the X Window System (via both Xlib and XCB), Quartz, Win32, image buffers, PostScript, PDF, and SVG file output. Experimental backends include OpenGL, BeOS, OS/2, and DirectFB.", "homepage": "https://cairographics.org", "license": "MPL-1.1", "port-version": 2 } ================================================ FILE: dependencies/vcpkg_overlay_ports_linux/glm/portfile.cmake ================================================ set(VCPKG_POLICY_EMPTY_PACKAGE enabled) ================================================ FILE: dependencies/vcpkg_overlay_ports_linux/glm/vcpkg.json ================================================ { "name": "glm", "version": "0.9.9.8", "port-version": 3, "description": "OpenGL Mathematics (GLM)", "homepage": "https://glm.g-truc.net", "license": "MIT" } ================================================ FILE: dependencies/vcpkg_overlay_ports_linux/gtk3/portfile.cmake ================================================ set(VCPKG_POLICY_EMPTY_PACKAGE enabled) ================================================ FILE: dependencies/vcpkg_overlay_ports_linux/gtk3/vcpkg.json ================================================ { "name": "gtk3", "version": "3.24.34", "port-version": 5, "description": "Portable library for creating graphical user interfaces.", "homepage": "https://www.gtk.org/", "license": null } ================================================ FILE: dependencies/vcpkg_overlay_ports_linux/libpng/portfile.cmake ================================================ set(VCPKG_POLICY_EMPTY_PACKAGE enabled) ================================================ FILE: dependencies/vcpkg_overlay_ports_linux/libpng/vcpkg.json ================================================ { "name": "libpng", "version": "1.6.39", "port-version": 2, "description": "libpng is a library implementing an interface for reading and writing PNG (Portable Network Graphics) format files", "homepage": "https://github.com/glennrp/libpng", "license": "libpng-2.0" } ================================================ FILE: dependencies/vcpkg_overlay_ports_mac/.gitkeep ================================================ ================================================ FILE: dependencies/vcpkg_overlay_ports_mac/libusb/portfile.cmake ================================================ set(VCPKG_LIBRARY_LINKAGE dynamic) if(VCPKG_TARGET_IS_LINUX) message("${PORT} currently requires the following tools and libraries from the system package manager:\n autoreconf\n libudev\n\nThese can be installed on Ubuntu systems via apt-get install autoconf libudev-dev") endif() set(VERSION 1.0.26) vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO libusb/libusb REF fcf0c710ef5911ae37fbbf1b39d48a89f6f14e8a # v1.0.26.11791 2023-03-12 SHA512 0aa6439f7988487adf2a3bff473fec80b5c722a47f117a60696d2aa25c87cc3f20fb6aaca7c66e49be25db6a35eb0bb5f71ed7b211d1b8ee064c5d7f1b985c73 HEAD_REF master ) if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") set(LIBUSB_PROJECT_TYPE dll) else() set(LIBUSB_PROJECT_TYPE static) endif() # The README.md file in the archive is a symlink to README # which causes issues with the windows MSBUILD process file(REMOVE "${SOURCE_PATH}/README.md") vcpkg_msbuild_install( SOURCE_PATH "${SOURCE_PATH}" PROJECT_SUBPATH msvc/libusb_${LIBUSB_PROJECT_TYPE}.vcxproj ) file(INSTALL "${SOURCE_PATH}/libusb/libusb.h" DESTINATION "${CURRENT_PACKAGES_DIR}/include/libusb-1.0") set(prefix "") set(exec_prefix [[${prefix}]]) set(libdir [[${prefix}/lib]]) set(includedir [[${prefix}/include]]) configure_file("${SOURCE_PATH}/libusb-1.0.pc.in" "${CURRENT_PACKAGES_DIR}/lib/pkgconfig/libusb-1.0.pc" @ONLY) vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/libusb-1.0.pc" " -lusb-1.0" " -llibusb-1.0") if(NOT VCPKG_BUILD_TYPE) set(includedir [[${prefix}/../include]]) configure_file("${SOURCE_PATH}/libusb-1.0.pc.in" "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libusb-1.0.pc" @ONLY) vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libusb-1.0.pc" " -lusb-1.0" " -llibusb-1.0") endif() else() vcpkg_list(SET MAKE_OPTIONS) vcpkg_list(SET LIBUSB_LINK_LIBRARIES) if(VCPKG_TARGET_IS_EMSCRIPTEN) vcpkg_list(APPEND MAKE_OPTIONS BUILD_TRIPLET --host=wasm32) endif() if("udev" IN_LIST FEATURES) vcpkg_list(APPEND MAKE_OPTIONS "--enable-udev") vcpkg_list(APPEND LIBUSB_LINK_LIBRARIES udev) else() vcpkg_list(APPEND MAKE_OPTIONS "--disable-udev") endif() vcpkg_configure_make( SOURCE_PATH "${SOURCE_PATH}" AUTOCONFIG OPTIONS ${MAKE_OPTIONS} "--enable-examples-build=no" "--enable-tests-build=no" ) vcpkg_install_make() endif() vcpkg_fixup_pkgconfig() file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/COPYING") ================================================ FILE: dependencies/vcpkg_overlay_ports_mac/libusb/usage ================================================ libusb can be imported via CMake FindPkgConfig module: find_package(PkgConfig REQUIRED) pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0) target_link_libraries(main PRIVATE PkgConfig::libusb) ================================================ FILE: dependencies/vcpkg_overlay_ports_mac/libusb/vcpkg.json ================================================ { "name": "libusb", "version": "1.0.26.11791", "port-version": 7, "description": "a cross-platform library to access USB devices", "homepage": "https://github.com/libusb/libusb", "license": "LGPL-2.1-or-later" } ================================================ FILE: dependencies/vcpkg_overlay_ports_win/.gitkeep ================================================ ================================================ FILE: dist/linux/appimage.sh ================================================ #!/bin/bash if [ "$1" == 'X64' ]; then CPU_ARCH="x86_64"; else CPU_ARCH="aarch64"; fi if [[ -z "${GITHUB_WORKSPACE}" ]]; then export GITHUB_WORKSPACE="." fi curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-"${CPU_ARCH}".AppImage" chmod a+x linuxdeploy*.AppImage curl -sSfL https://github.com"$(curl https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous | grep "mkappimage-.*-"${CPU_ARCH}".AppImage" | head -n 1 | cut -d '"' -f 2)" -o mkappimage.AppImage chmod a+x mkappimage.AppImage curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh" chmod a+x linuxdeploy-plugin-gtk.sh curl -sSfLO "https://github.com/darealshinji/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt.sh" chmod a+x linuxdeploy-plugin-checkrt.sh if [[ ! -e /usr/lib/"${CPU_ARCH}"-linux-gnu ]]; then sed -i 's#lib\/"${CPU_ARCH}"-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh fi mkdir -p AppDir/usr/bin mkdir -p AppDir/usr/share/Cemu mkdir -p AppDir/usr/share/applications mkdir -p AppDir/usr/share/icons/hicolor/128x128/apps mkdir -p AppDir/usr/share/metainfo mkdir -p AppDir/usr/lib cp dist/linux/info.cemu.Cemu.{desktop,png} AppDir/ cp dist/linux/info.cemu.Cemu.metainfo.xml AppDir/usr/share/metainfo/info.cemu.Cemu.appdata.xml cp -r bin/* AppDir/usr/share/Cemu mv AppDir/usr/share/Cemu/Cemu AppDir/usr/bin/ chmod +x AppDir/usr/bin/Cemu cp /usr/lib/"${CPU_ARCH}"-linux-gnu/{libsepol.so.1,libffi.so.7,libpcre.so.3,libGLU.so.1,libthai.so.0} AppDir/usr/lib export UPD_INFO="gh-releases-zsync|cemu-project|Cemu|ci|Cemu.AppImage.zsync" export NO_STRIP=1 ./linuxdeploy-"${CPU_ARCH}".AppImage --appimage-extract-and-run \ --appdir="${GITHUB_WORKSPACE}"/AppDir/ \ -d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \ -i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \ -e "${GITHUB_WORKSPACE}"/AppDir/usr/bin/Cemu \ --plugin gtk \ --plugin checkrt if ! GITVERSION="$(git rev-parse --short HEAD 2>/dev/null)"; then GITVERSION=experimental fi echo "Cemu Version Cemu-${GITVERSION}" rm AppDir/usr/lib/libwayland-client.so.0 echo -e "export LC_ALL=C\nexport FONTCONFIG_PATH=/etc/fonts" >> AppDir/apprun-hooks/linuxdeploy-plugin-gtk.sh VERSION="${GITVERSION}" ./mkappimage.AppImage --appimage-extract-and-run "${GITHUB_WORKSPACE}"/AppDir mkdir -p "${GITHUB_WORKSPACE}"/artifacts/ mv Cemu-"${GITVERSION}"-"${CPU_ARCH}".AppImage "${GITHUB_WORKSPACE}"/artifacts/ ================================================ FILE: dist/linux/info.cemu.Cemu.desktop ================================================ [Desktop Entry] Name=Cemu Type=Application Terminal=false Icon=info.cemu.Cemu Exec=Cemu GenericName=Wii U Emulator GenericName[fi]=Wii U -emulaattori GenericName[el]=Πρόγραμμα προσομοίωσης Wii U GenericName[es]=Emulador de Wii U GenericName[pt_BR]=Emulador de Wii U GenericName[fr]=Émulateur de Wii U GenericName[it]=Emulatore Wii U Comment=Software to emulate Wii U games and applications on PC Comment[fi]=Ohjelmisto Wii U -pelien ja sovellusten emulointiin PC:lla Comment[de]=Software zum emulieren von Wii U Spielen und Anwendungen auf dem PC Comment[ru]=Программа для эмуляции игр и приложений Wii U на PC Comment[fr]=Application pour émuler des jeux et des applications Wii U sur PC Comment[nl]=Applicatie om Wii U spellen en applicaties te emuleren op PC Comment[el]=Πρόγραμμα προσομοίωσης παιχνιδιών και εφαρμογών Wii U στον υπολογιστή Comment[es]=Software para emular juegos y aplicaciones de Wii U en PC Comment[pt_BR]=Software para emular jogos e aplicativos de Wii U no PC Comment[it]=Software per emulare giochi e applicazioni per Wii U su PC Categories=Game;Emulator; Keywords=Nintendo; MimeType=application/x-wii-u-rom; StartupWMClass=Cemu ================================================ FILE: dist/linux/info.cemu.Cemu.metainfo.xml ================================================ <?xml version='1.0' encoding='utf-8'?> <component type="desktop"> <!--Created with jdAppdataEdit 4.2--> <id>info.cemu.Cemu</id> <name>Cemu</name> <summary>Nintendo Wii U Emulator</summary> <summary xml:lang="de">Nintendo Wii U Emulator</summary> <summary xml:lang="fr">Émulateur Nintendo Wii U</summary> <summary xml:lang="nl">Nintendo Wii U Emulator</summary> <summary xml:lang="el">Εξομοιωτής Nintendo Wii U</summary> <summary xml:lang="es">Emulador de Nintendo Wii U</summary> <summary xml:lang="pt_BR">Emulador Nintendo Wii U</summary> <summary xml:lang="it">Emulatore Nintendo Wii U</summary> <summary xml:lang="fi">Nintendo Wii U Emulaattori</summary> <developer_name>Cemu Project</developer_name> <launchable type="desktop-id">info.cemu.Cemu.desktop</launchable> <metadata_license>CC0-1.0</metadata_license> <project_license>MPL-2.0</project_license> <description> <p>Cemu is a Nintendo Wii U emulator that is able to run most Wii U games and homebrew in a playable state. Created by Exzap, and written in C/C++.</p> <p xml:lang="de">Cemu ist ein Nintendo Wii U-Emulator, der die meisten Wii U Spiele und Homebrew in einem spielbaren Zustand ausführen kann. Erstellt von Exzap, und geschrieben in C/C++.</p> <p xml:lang="fr">Cemu est un émulateur de Wii U capable de lancer la plupart des jeux Wii U et des homebrews en interface de jeu. Crée par Exzap, et développé en C/C++.</p> <p xml:lang="nl">Cemu is een Nintendo Wii U emulator die de meeste Wii U en Homebrew games speelbaar kan runnen. Het project is begonnen door Exzap, en het is geschreven in C/C++.</p> <p xml:lang="el">Το Cemu είναι ένας προσομοιωτής της κονσόλας Nintendo Wii U που προσομοιώνει επίσημα παιχνίδια, καθώς και μη επίσημα προγράμματα ("homebrew") του Wii U. Δημιουργήθηκε από τον Exzap σε C/C++.</p> <p xml:lang="es">Cemu es un emulador de Nintendo Wii U que es capaz de ejecutar la mayoría de los juegos de Wii U y homebrew en un estado jugable. Creado por Exzap, y escrito en C y C++.</p> <p xml:lang="pt_BR">Cemu é um emulador de Nintendo Wii U que é capaz de executar a maioría dos jogos de Wii U e homebrew em um estado jogavel. Criado por Exzap, e escrito em C e C++.</p> <p xml:lang="it">Cemu è un emulatore del Nintendo Wii U in grado di eseguire in stato giocabile la maggior parte dei giochi e homebrew per Wii U. Creato da Exzap, e scritto in C/C++.</p> <p xml:lang="fi">Cemu on Wii U -emulaattori, joka pystyy toistamaan useimpia Wii U -pelejä ja homebrew'ta pelikelvollisesti. Luonut Exzap, kirjoitettu C:llä/C++:lla.</p> <p>This emulator aims at providing both high-accuracy and performance, and is actively being developed with new features and fixes to increase compatibility, convenience and usability.</p> <p xml:lang="de">Dieser Emulator zielt darauf ab, sowohl hohe Genauigkeit als auch Leistung zu bieten, und wird aktiv mit neuen Funktionen und Korrekturen weiterentwickelt, um Kompatibilität, Komfort und Benutzerfreundlichkeit zu verbessern.</p> <p xml:lang="fr">Cet émulateur vise à la fois à offrir fidélité et performance, il est activement développé avec des nouvelles fonctionnalités et des correctifs pour augmenter la compatibilité, la commodité et la facilité d'utilisation.</p> <p xml:lang="nl">De emulator richt zich op integriteit en snelheid, en wordt continu verder ontwikkeld met nieuwe toevoegingen en fixes om de compatibiliteit, het gemak en de gebruiksvriendelijkheid te verbeteren.</p> <p xml:lang="el">Ο προσομοιωτής αυτός στοχεύει τόσο στην ακρίβεια, όσο και στην ταχύτητα, και βελτιώνεται συνεχώς με νέες δυνατότητες και διορθώσεις που τον καθιστούν πιο βολικό, εύκολο στην χρήση και συμβατό με περισσότερα παιχνίδια.</p> <p xml:lang="es">Este emulador tiene como objetivo proporcionar tanto alta precisión como rendimiento y se desarrolla activamente con nuevas características y correcciones para mejorar la compatibilidad, la comodidad y la usabilidad.</p> <p xml:lang="pt_BR">Esse emulador visa proporcionar tanto alta precisão como rendimento e está sendo ativamente desenvolvido com novas características e correções para melhorar a compatibilidade, a comodidade e a usabilidade.</p> <p xml:lang="it">Questo emulatore ha l'obiettivo di fornire sia alta precisione che alte prestazioni, ed è in continuo sviluppo con nuove funzionalità e correzioni per aumentare la compatibilità, la comodità e l'usabilità.</p> <p xml:lang="fi">Tämä emulaattori pyrkii tarjoamaan korkeaa tarkkuutta sekä suorituskykyä, ja sitä kehitetään aktiivisesti uusilla ominaisuuksilla ja korjauksilla, jotka parantavat yhteensopivuutta, mukavuutta ja käytettävyyttä..</p> <p>It was written from scratch and development on the project began roughly early 2015.</p> <p xml:lang="de">Er wird seit Anfang 2015 entwickelt.</p> <p xml:lang="fr">Il a été écrit à partir de zéro et son développement a débuté vers le début de l'année 2015.</p> <p xml:lang="nl">Ontwikkeling van Cemu begon ongeveer in het voorjaar van 2015.</p> <p xml:lang="el">Αρχισε απο το τίποτα και βρίσκεται υπό ανάπτυξη από το 2015.</p> <p xml:lang="es">Fue escrito desde cero y el desarrollo del proyecto comenzó aproximadamente a principios de 2015.</p> <p xml:lang="pt_BR">Foi escrito do zero e o desenvolvimento do projeto começou aproximadamente a princípio de 2015.</p> <p xml:lang="it">È stato scritto da zero e lo sviluppo del progetto è cominciato intorno all'inizio del 2015.</p> <p xml:lang="fi">Se kirjoitettiin tyhjästä ja sen kehitys alkoi suunnilleen vuoden 2015 alussa.</p> </description> <screenshots> <screenshot type="default"> <image type="source">https://upload.wikimedia.org/wikipedia/commons/5/58/Cemu_Screenshot.png</image> </screenshot> </screenshots> <releases> <release version="2.0" date="2022-08-22" type="stable"> <url>https://github.com/cemu-project/Cemu/releases/tag/v2.0</url> </release> </releases> <url type="homepage">https://cemu.info</url> <url type="bugtracker">https://github.com/cemu-project/Cemu/issues</url> <url type="faq">https://cemu.info/faq.html</url> <url type="help">https://wiki.cemu.info</url> <!-- <url type="vcs-browser">https://github.com/cemu-project/Cemu</url> --> <categories> <category>Game</category> <category>Emulator</category> </categories> <recommends> <memory>8192</memory> </recommends> <supports> <control>pointing</control> <control>keyboard</control> <control>gamepad</control> </supports> <content_rating type="oars-1.1"/> <provides> <binary>cemu</binary> <mediatype>application/x-wii-u-rom</mediatype> </provides> <keywords> <keyword>nintendo</keyword> </keywords> </component> ================================================ FILE: dist/network_services.xml ================================================ <?xml version="1.0" encoding="UTF-8"?> <content> <networkname>CustomExample</networkname> <disablesslverification>0</disablesslverification> <urls> <act>https://account.nintendo.net</act> <ecs>https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP</ecs> <nus>https://nus.wup.shop.nintendo.net/nus/services/NetUpdateSOAP</nus> <ias>https://ias.wup.shop.nintendo.net/ias/services/IdentityAuthenticationSOAP</ias> <ccsu>https://ccs.wup.shop.nintendo.net/ccs/download</ccsu> <ccs>http://ccs.cdn.wup.shop.nintendo.net/ccs/download</ccs> <idbe>https://idbe-wup.cdn.nintendo.net/icondata</idbe> <boss>https://npts.app.nintendo.net/p01/tasksheet</boss> <tagaya>https://tagaya.wup.shop.nintendo.net/tagaya/versionlist</tagaya> <olv>https://discovery.olv.nintendo.net/v1/endpoint</olv> </urls> </content> ================================================ FILE: dist/windows/Cemu.manifest ================================================ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="amd64" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity> </dependentAssembly> </dependency> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges> <requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel></requestedPrivileges></security> </trustInfo> <application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware> </windowsSettings> </application> </assembly> ================================================ FILE: src/CMakeLists.txt ================================================ project(cemuMain) option(CEMU_CXX_FLAGS "Additional flags used for compiling Cemu source code") if(CEMU_CXX_FLAGS) add_compile_options(${CEMU_CXX_FLAGS}) endif() if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) message( FATAL_ERROR "Pointers are not 64bit" ) endif() if(MSVC) add_compile_definitions(WIN32_LEAN_AND_MEAN CURL_STATICLIB) elseif(UNIX) if(APPLE) add_compile_definitions( _XOPEN_SOURCE VK_USE_PLATFORM_MACOS_MVK VK_USE_PLATFORM_METAL_EXT ) else() add_compile_definitions( VK_USE_PLATFORM_XLIB_KHR # legacy. Do we need to support XLIB surfaces? VK_USE_PLATFORM_XCB_KHR ) if (ENABLE_WAYLAND) add_compile_definitions(VK_USE_PLATFORM_WAYLAND_KHR) endif() endif() # warnings if(CMAKE_C_COMPILER_ID STREQUAL "Clang") add_compile_options(-Wno-ambiguous-reversed-operator) endif() add_compile_options(-Wno-multichar -Wno-invalid-offsetof -Wno-switch -Wno-ignored-attributes -Wno-deprecated-enum-enum-conversion) endif() add_compile_definitions(VK_NO_PROTOTYPES) set(CMAKE_INCLUDE_CURRENT_DIR ON) add_subdirectory(Common) add_subdirectory(gui) add_subdirectory(Cafe) add_subdirectory(Cemu) add_subdirectory(config) add_subdirectory(input) add_subdirectory(audio) add_subdirectory(util) add_subdirectory(imgui) add_subdirectory(resource) add_executable(CemuBin main.cpp mainLLE.cpp ) if(MSVC AND MSVC_VERSION EQUAL 1940) # workaround for an msvc issue on VS 17.10 where generated ILK files are too large # see https://developercommunity.visualstudio.com/t/After-updating-to-VS-1710-the-size-of-/10665511 set_target_properties(CemuBin PROPERTIES LINK_FLAGS "/INCREMENTAL:NO") endif() if(WIN32) target_sources(CemuBin PRIVATE resource/cemu.rc ../dist/windows/cemu.manifest ) endif() set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") set_property(TARGET CemuBin PROPERTY WIN32_EXECUTABLE $<NOT:$<CONFIG:Debug>>) set(OUTPUT_NAME "Cemu_$<LOWER_CASE:$<CONFIG>>") if (MACOS_BUNDLE) set_property(TARGET CemuBin PROPERTY MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/resource/MacOSXBundleInfo.plist.in") set(RESOURCE_FILES "${CMAKE_SOURCE_DIR}/src/resource/cemu.icns") target_sources(CemuBin PRIVATE "${RESOURCE_FILES}") set(MACOSX_BUNDLE_ICON_FILE "cemu.icns") set(MACOSX_BUNDLE_GUI_IDENTIFIER "info.cemu.Cemu") set(MACOSX_BUNDLE_BUNDLE_NAME "Cemu") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}") set(MACOSX_BUNDLE_BUNDLE_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}") set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 Cemu Project") set(MACOSX_BUNDLE_CATEGORY "public.app-category.games") set(MACOSX_MINIMUM_SYSTEM_VERSION "13.4") set(MACOSX_BUNDLE_TYPE_EXTENSION "wua") set_target_properties(CemuBin PROPERTIES MACOSX_BUNDLE true RESOURCE "${RESOURCE_FILES}" ) set(FOLDERS gameProfiles resources) foreach(folder ${FOLDERS}) add_custom_command (TARGET CemuBin POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/SharedSupport/${folder}") endforeach(folder) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/debug/lib/libusb-1.0.0.dylib") else() set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/lib/libusb-1.0.0.dylib") endif() if (EXISTS "/usr/local/lib/libMoltenVK.dylib") set(MOLTENVK_PATH "/usr/local/lib/libMoltenVK.dylib") elseif (EXISTS "/opt/homebrew/lib/libMoltenVK.dylib") set(MOLTENVK_PATH "/opt/homebrew/lib/libMoltenVK.dylib") else() message(FATAL_ERROR "failed to find libMoltenVK.dylib") endif () add_custom_command (TARGET CemuBin POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy "${MOLTENVK_PATH}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${LIBUSB_PATH}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh" COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}" COMMAND bash -c "install_name_tool -change ${LIBUSB_PATH} @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") else() if(APPLE) find_library(MOLTENVK_LIBRARY NAMES MoltenVK moltenvk libMoltenVK.dylib PATHS /usr/local/lib /opt/homebrew/lib ) if(MOLTENVK_LIBRARY) message(STATUS "Found MoltenVK: ${MOLTENVK_LIBRARY}") target_link_libraries(CemuBin PRIVATE ${MOLTENVK_LIBRARY}) else() message(WARNING "libMoltenVK.dylib not found") endif() set_target_properties(CemuBin PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "/usr/local/lib;/opt/homebrew/lib" ) endif() endif() set_target_properties(CemuBin PROPERTIES # multi-configuration generators will add a config subdirectory to RUNTIME_OUTPUT_DIRECTORY if no generator expression is used # to get the same behavior everywhere we append an empty generator expression RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../bin/$<1:>" OUTPUT_NAME "${OUTPUT_NAME}" ) target_link_libraries(CemuBin PRIVATE CemuAudio CemuCafe CemuCommon CemuComponents CemuConfig CemuGui CemuInput CemuUtil SDL2::SDL2 ) if(UNIX AND NOT APPLE) # due to nasm output some linkers will make stack executable # cemu does not require this so we explicity disable it target_link_options(CemuBin PRIVATE -z noexecstack) # some residual debug info from boost/discord-rpc is normally included # most likely not helpful in debugging problems with cemu code target_link_options(CemuBin PRIVATE "$<$<CONFIG:Release>:-Xlinker;--strip-debug>") endif() if (BSD) target_link_libraries(CemuBin PRIVATE execinfo SPIRV-Tools SPIRV-Tools-opt) endif() ================================================ FILE: src/Cafe/Account/Account.cpp ================================================ #include "Account.h" #include "util/helpers/helpers.h" #include "util/helpers/SystemException.h" #include "util/helpers/StringHelpers.h" #include "config/ActiveSettings.h" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Common/FileStream.h" #include <boost/random/uniform_int.hpp> #include <random> std::vector<Account> Account::s_account_list; Account::Account(uint32 persistent_id) : m_persistent_id(persistent_id) {} typedef struct { uint32be high; uint32be low; }FFLDataID_t; typedef struct { /* +0x00 */ uint32 uknFlags; /* +0x04 */ FFLDataID_t miiId; // bytes 8 and 9 are part of the CRC? (miiId is based on account transferable id?) /* +0x0C */ uint8 ukn0C[0xA]; /* +0x16 */ uint8 ukn16[2]; /* +0x18 */ uint16 ukn18; /* +0x1A */ uint16le miiName[10]; /* +0x2E */ uint16 ukn2E; /* +0x30 */ uint8 ukn30[96 - 0x30]; }FFLData_t; uint16 FFLCalculateCRC16(uint8* input, sint32 length) { cemu_assert_debug((length % 8) == 0); uint16 crc = 0; for (sint32 c = 0; c < length; c++) { for (sint32 f = 0; f < 8; f++) { if ((crc & 0x8000) != 0) { uint16 t = crc << 1; crc = t ^ 0x1021; } else { crc <<= 1; } } crc ^= (uint16)input[c]; } return crc; } Account::Account(uint32 persistent_id, std::wstring_view mii_name) : m_persistent_id(persistent_id) { if (mii_name.empty()) throw std::system_error(AccountErrc::InvalidMiiName); static std::random_device s_random_device; static std::mt19937 s_mte(s_random_device()); // use boost library to escape static asserts in linux builds boost::random::uniform_int_distribution<uint16> dist(std::numeric_limits<uint8>::min(), std::numeric_limits<uint8>::max()); std::generate(m_uuid.begin(), m_uuid.end(), [&]() { return (uint8)dist(s_mte); }); // 1000004 or 2000004 | lower uint32 from uuid from uuid m_transferable_id_base = (0x2000004ULL << 32); m_transferable_id_base |= ((uint64)m_uuid[12] << 24) | ((uint64)m_uuid[13] << 16) | ((uint64)m_uuid[14] << 8) | (uint64)m_uuid[15]; SetMiiName(mii_name); // todo: generate mii data // iosuAct_generateDefaultMii // void* pMiiData = &_actAccountData[accountIndex].miiData; uint8* fflByteDataBE = m_mii_data.data(); uint16* fflDataBE = (uint16*)m_mii_data.data(); // FFLCreateId is derived from the words at location: 0x7 - 0xb (5 words, so it's a 80 bit id) for (sint32 i = 0; i < 96 / 2; i++) fflDataBE[i] = _swapEndianU16(1); *(uint16*)(fflByteDataBE + 0x3A) = _swapEndianU16(1 | (3 << 9)); *(uint16*)(fflByteDataBE + 0x2) = _swapEndianU16(1 | (1 << 12)); //*(uint32*)(fflByteDataBE + 4) = 0; // transferable id high ? *(uint32*)(fflByteDataBE + 8) = _swapEndianU32(0x33333 + 0 + 1); // mii id low // swap endian (apparently it's stored little-endian) for (sint32 i = 0; i < 96 / 2; i++) fflDataBE[i] = _swapEndianU16(fflDataBE[i]); // set default name FFLData_t* fflData = (FFLData_t*)m_mii_data.data(); const auto tmp_name = GetMiiName(); memset(fflData->miiName, 0, sizeof(fflData->miiName)); std::copy(tmp_name.begin(), tmp_name.end(), fflData->miiName); // calculate checksum uint32 crcCounter = 0; while (FFLCalculateCRC16(m_mii_data.data(), 96) != 0) { *(uint32*)(fflDataBE + 2) = _swapEndianU32(crcCounter); crcCounter++; } const auto error = CheckValid(); if (error) throw std::system_error(error); } Account::Account(std::wstring_view file_name) { if (!fs::exists(file_name.data())) throw std::runtime_error("given file doesn't exist"); std::unique_ptr<FileStream> file(FileStream::openFile2(file_name)); if (!file) throw std::runtime_error("can't open file"); ParseFile(file.get()); const auto error = CheckValid(); if (error) throw std::system_error(error); } std::error_code Account::CheckValid() const { if (m_persistent_id < kMinPersistendId) return AccountErrc::InvalidPersistentId; if (m_mii_name[0] == '\0') return AccountErrc::InvalidMiiName; if (m_mii_data == decltype(m_mii_data){}) return AccountErrc::InvalidMiiData; // todo: check for other needed properties return AccountErrc::NoError; } std::error_code Account::Load() { const auto persistent_id = m_persistent_id; const fs::path path = GetFileName(); try { std::unique_ptr<FileStream> file(FileStream::openFile2(path)); if (!file) throw std::runtime_error("can't open file"); ParseFile(file.get()); // fix persistent id if it's in the wrong folder m_persistent_id = persistent_id; return CheckValid(); } catch (const std::system_error& ex) { return ex.code(); } catch(const std::exception& ex) { cemuLog_log(LogType::Force, "handled error in Account::Load: {}", ex.what()); return AccountErrc::ParseError; } } std::error_code Account::Save() { fs::path path = ActiveSettings::GetMlcPath(fmt::format(L"usr/save/system/act/{:08x}", m_persistent_id)); if (!fs::exists(path)) { std::error_code ec; fs::create_directories(path, ec); if (ec) return ec; } path /= "account.dat"; try { std::ofstream file; file.exceptions(std::ios::badbit); file.open(path); file << "AccountInstance_20120705" << std::endl; file << fmt::format("PersistentId={:08x}", m_persistent_id) << std::endl; file << fmt::format("TransferableIdBase={:x}", m_transferable_id_base) << std::endl; file << fmt::format("Uuid="); for (const auto& b : m_uuid) file << fmt::format("{:02x}", b); file << std::endl; file << fmt::format("MiiData="); for (const auto& b : m_mii_data) file << fmt::format("{:02x}", b); file << std::endl; file << fmt::format("MiiName="); for (const auto& b : m_mii_name) file << fmt::format("{:04x}", (uint16)b); file << std::endl; file << fmt::format("AccountId={}", m_account_id) << std::endl; file << fmt::format("BirthYear={:x}", m_birth_year) << std::endl; file << fmt::format("BirthMonth={:x}", m_birth_month) << std::endl; file << fmt::format("BirthDay={:x}", m_birth_day) << std::endl; file << fmt::format("Gender={:x}", m_gender) << std::endl; file << fmt::format("EmailAddress={}", m_email) << std::endl; file << fmt::format("Country={:x}", m_country) << std::endl; file << fmt::format("SimpleAddressId={:x}", m_simple_address_id) << std::endl; file << fmt::format("PrincipalId={:x}", m_principal_id) << std::endl; file << fmt::format("IsPasswordCacheEnabled={:x}", m_password_cache_enabled) << std::endl; file << fmt::format("AccountPasswordCache="); for (const auto& b : m_account_password_cache) file << fmt::format("{:02x}", b); file << std::endl; // write rest of stuff we got for(const auto& [key, value] : m_storage) { file << fmt::format("{}={}", key, value) << std::endl; } file.flush(); file.close(); return CheckValid(); } catch (const std::system_error& e) { return e.code(); } } OnlineAccountError Account::GetOnlineAccountError() const { if (m_account_id.empty()) return OnlineAccountError::kNoAccountId; if (!IsPasswordCacheEnabled()) return OnlineAccountError::kNoPasswordCached; if (m_account_password_cache == decltype(m_account_password_cache){}) return OnlineAccountError::kPasswordCacheEmpty; /*if (m_simple_address_id == 0) not really needed return false;*/ if (m_principal_id == 0) return OnlineAccountError::kNoPrincipalId; // TODO return OnlineAccountError::kNone; } bool Account::IsValidOnlineAccount() const { return GetOnlineAccountError() == OnlineAccountError::kNone; } fs::path Account::GetFileName() const { return GetFileName(m_persistent_id); } std::wstring_view Account::GetMiiName() const { const auto it = std::find(m_mii_name.cbegin(), m_mii_name.cend(), '\0'); if(it == m_mii_name.cend()) return { m_mii_name.data(), m_mii_name.size() - 1 }; const size_t count = std::distance(m_mii_name.cbegin(), it); return { m_mii_name.data(), count}; } std::string_view Account::GetStorageValue(std::string_view key) const { const auto it = m_storage.find(key.data()); if (it == m_storage.cend()) return {}; return it->second; } void Account::SetMiiName(std::wstring_view name) { m_mii_name = {}; std::copy(name.data(), name.data() + std::min(name.size(), m_mii_name.size() - 1), m_mii_name.begin()); } const std::vector<Account>& Account::RefreshAccounts() { std::vector<Account> result; const fs::path path = ActiveSettings::GetMlcPath("usr/save/system/act"); if (fs::exists(path)) { for (const auto& it : fs::directory_iterator(path)) { if (!fs::is_directory(it)) continue; const auto file_name = it.path().filename().string(); if (file_name.size() != 8) continue; const auto persistent_id = ConvertString<uint32>(file_name, 16); if (persistent_id < kMinPersistendId) continue; Account account(persistent_id); const auto error = account.Load(); if (!error) result.emplace_back(account); } } // we always force at least one account if (result.empty()) { result.emplace_back(kMinPersistendId, L"default"); result.begin()->Save(); } s_account_list = result; UpdatePersisidDat(); return s_account_list; } void Account::UpdatePersisidDat() { const auto max_id = std::max(kMinPersistendId, GetNextPersistentId() - 1); const auto file = ActiveSettings::GetMlcPath("usr/save/system/act/persisid.dat"); std::ofstream f(file); if(f.is_open()) { f << "PersistentIdManager_20120607" << std::endl << "PersistentIdHead=" << std::hex << max_id << std::endl << std::endl; f.flush(); f.close(); } else cemuLog_log(LogType::Force, "Unable to save persisid.dat"); } bool Account::HasFreeAccountSlots() { return s_account_list.size() < 12; } const std::vector<Account>& Account::GetAccounts() { if (!s_account_list.empty()) return s_account_list; return RefreshAccounts(); } const Account& Account::GetAccount(uint32 persistent_id) { for (const auto& account : GetAccounts()) { if (account.GetPersistentId() == persistent_id) return account; } return *GetAccounts().begin(); } const Account& Account::GetCurrentAccount() { return GetAccount(ActiveSettings::GetPersistentId()); } uint32 Account::GetNextPersistentId() { uint32 result = kMinPersistendId; const auto file = ActiveSettings::GetMlcPath("usr/save/system/act/persisid.dat"); if(fs::exists(file)) { std::ifstream f(file); if(f.is_open()) { std::string line; while(std::getline(f, line)) { if(boost::starts_with(line, "PersistentIdHead=")) { result = ConvertString<uint32>(line.data() + sizeof("PersistentIdHead=") - 1, 16); break; } } } } // next id ++result; const auto it = std::max_element(s_account_list.cbegin(), s_account_list.cend(), [](const Account& acc1, const Account& acc2) {return acc1.GetPersistentId() < acc2.GetPersistentId(); }); if (it != s_account_list.cend()) return std::max(result, it->GetPersistentId() + 1); else return result; } fs::path Account::GetFileName(uint32 persistent_id) { if (persistent_id < kMinPersistendId) throw std::invalid_argument(fmt::format("persistent id {:#x} is invalid", persistent_id)); return ActiveSettings::GetMlcPath(fmt::format("usr/save/system/act/{:08x}/account.dat", persistent_id)); } OnlineValidator Account::ValidateOnlineFiles() const { OnlineValidator result{}; const auto otp = ActiveSettings::GetUserDataPath("otp.bin"); if (!fs::exists(otp)) result.otp = OnlineValidator::FileState::Missing; else if (fs::file_size(otp) != 1024) result.otp = OnlineValidator::FileState::Corrupted; else result.otp = OnlineValidator::FileState::Ok; const auto seeprom = ActiveSettings::GetUserDataPath("seeprom.bin"); if (!fs::exists(seeprom)) result.seeprom = OnlineValidator::FileState::Missing; else if (fs::file_size(seeprom) != 512) result.seeprom = OnlineValidator::FileState::Corrupted; else result.seeprom = OnlineValidator::FileState::Ok; for(const auto& v : iosuCrypt_getCertificateKeys()) { const auto p = ActiveSettings::GetMlcPath(L"sys/title/0005001b/10054000/content/{}", v); if (!fs::exists(p) || !fs::is_regular_file(p)) result.missing_files.emplace_back(p.generic_wstring()); } for (const auto& v : iosuCrypt_getCertificateNames()) { const auto p = ActiveSettings::GetMlcPath(L"sys/title/0005001b/10054000/content/{}", v); if (!fs::exists(p) || !fs::is_regular_file(p)) result.missing_files.emplace_back(p.generic_wstring()); } result.valid_account = IsValidOnlineAccount(); result.account_error = GetOnlineAccountError(); return result; } void Account::ParseFile(class FileStream* file) { std::vector<uint8> buffer; buffer.resize(file->GetSize()); if( file->readData(buffer.data(), buffer.size()) != buffer.size()) throw std::system_error(AccountErrc::ParseError); for (const auto& s : StringHelpers::StringLineIterator(buffer)) { std::string_view view = s; const auto find = view.find('='); if (find == std::string_view::npos) continue; const auto key = view.substr(0, find); const auto value = view.substr(find + 1); if (key == "PersistentId") m_persistent_id = ConvertString<uint32>(value, 16); else if (key == "TransferableIdBase") m_transferable_id_base = ConvertString<uint64>(value, 16); else if (key == "Uuid") { if (value.size() != m_uuid.size() * 2) // = 32 throw std::system_error(AccountErrc::InvalidUuid); for (size_t i = 0; i < m_uuid.size(); ++i) { m_uuid[i] = ConvertString<uint8>(value.substr(i * 2, 2), 16); } } else if (key == "MiiData") { if (value.size() != m_mii_data.size() * 2) // = 192 throw std::system_error(AccountErrc::InvalidMiiData); for (size_t i = 0; i < m_mii_data.size(); ++i) { m_mii_data[i] = ConvertString<uint8>(value.substr(i * 2, 2), 16); } } else if (key == "MiiName") { if(value.size() != m_mii_name.size() * 4) // = 44 throw std::system_error(AccountErrc::InvalidMiiName); for (size_t i = 0; i < m_mii_name.size(); ++i) { m_mii_name[i] = (wchar_t)ConvertString<uint16>(value.substr(i * 4, 4), 16); } } else if (key == "AccountId") m_account_id = value; else if (key == "BirthYear") m_birth_year = ConvertString<uint16>(value, 16); else if (key == "BirthMonth") m_birth_month = ConvertString<uint8>(value, 16); else if (key == "BirthDay") m_birth_day = ConvertString<uint8>(value, 16); else if (key == "Gender") m_gender = ConvertString<uint8>(value, 16); else if (key == "EmailAddress") m_email = value; else if (key == "Country") m_country = ConvertString<uint32>(value, 16); else if (key == "SimpleAddressId") m_simple_address_id = ConvertString<uint32>(value, 16); else if (key == "TimeZoneId") m_timezone_id = value; else if (key == "UtcOffset") m_utc_offset = ConvertString<uint64>(value, 16); else if (key == "PrincipalId") m_principal_id = ConvertString<uint32>(value, 16); else if (key == "IsPasswordCacheEnabled") m_password_cache_enabled = ConvertString<uint8>(value, 16); else if (key == "AccountPasswordCache") { for (size_t i = 0; i < m_account_password_cache.size(); ++i) { m_account_password_cache[i] = ConvertString<uint8>(value.substr(i * 2, 2), 16); } } else // store anything else not needed for now m_storage[std::string(key)] = value; } } #include"openssl/sha.h" void makePWHash(uint8* input, sint32 length, uint32 magic, uint8* output) { uint8 buffer[64 + 8]; if (length > (sizeof(buffer) - 8)) { cemu_assert_debug(false); memset(output, 0, 32); return; } buffer[4] = 0x02; buffer[5] = 0x65; buffer[6] = 0x43; buffer[7] = 0x46; buffer[0] = (magic >> 0) & 0xFF; buffer[1] = (magic >> 8) & 0xFF; buffer[2] = (magic >> 16) & 0xFF; buffer[3] = (magic >> 24) & 0xFF; memcpy(buffer + 8, input, length); uint8 md[SHA256_DIGEST_LENGTH]; SHA256(buffer, 8 + length, md); memcpy(output, md, SHA256_DIGEST_LENGTH); } void actPwTest() { uint8 pwHash[32]; uint32 principalId = 0x12345678; uint32 pid = 0x12345678; makePWHash((uint8*)"pass123", 7, pid, pwHash); // calculates AccountPasswordCache makePWHash(pwHash, 32, pid, pwHash); // calculates AccountPasswordHash assert_dbg(); } ================================================ FILE: src/Cafe/Account/Account.h ================================================ #pragma once #include "AccountError.h" #include <string> #include <string_view> #include <system_error> #include <vector> #include <optional> enum class OnlineAccountError { kNone, kNoAccountId, kNoPasswordCached, kPasswordCacheEmpty, kNoPrincipalId, }; struct OnlineValidator { enum class FileState { Missing, Corrupted, Ok, }; bool valid_account = false; FileState otp = FileState::Missing; FileState seeprom = FileState::Missing; std::vector<std::wstring> missing_files; OnlineAccountError account_error = OnlineAccountError::kNone; bool IsValid() const { return valid_account && otp == FileState::Ok && seeprom == FileState::Ok && missing_files.empty(); } explicit operator bool() const { return IsValid(); } }; class Account { public: static constexpr uint32 kMinPersistendId = 0x80000001; // create dummy account object from scratch Account(uint32 persistent_id, std::wstring_view mii_name); // load an existing account Account(std::wstring_view file_name); std::error_code Load(); std::error_code Save(); [[nodiscard]] std::wstring ToString() const { return fmt::format(L"{} ({:x})", GetMiiName(), GetPersistentId()); } // test if the account file has all fields set required for online play [[nodiscard]] bool IsValidOnlineAccount() const; [[nodiscard]] OnlineAccountError GetOnlineAccountError() const; [[nodiscard]] fs::path GetFileName() const; [[nodiscard]] uint32 GetPersistentId() const { return m_persistent_id; } [[nodiscard]] uint64 GetTransferableIdBase() const { return m_transferable_id_base; } [[nodiscard]] const std::array<uint8, 16>& GetUuid() const { return m_uuid; } [[nodiscard]] const std::array<uint8, 96>& GetMiiData() const { return m_mii_data; } [[nodiscard]] std::wstring_view GetMiiName() const; // only max 10 characters excluding '\0' [[nodiscard]] std::string_view GetAccountId() const { return m_account_id; } [[nodiscard]] uint16 GetBirthYear() const { return m_birth_year; } [[nodiscard]] uint8 GetBirthMonth() const { return m_birth_month; } [[nodiscard]] uint8 GetBirthDay() const { return m_birth_day; } [[nodiscard]] uint8 GetGender() const { return m_gender; } [[nodiscard]] std::string_view GetEmail() const { return m_email; } [[nodiscard]] uint32 GetCountry() const { return m_country; } [[nodiscard]] uint32 GetSimpleAddressId() const { return m_simple_address_id; } [[nodiscard]] std::string_view GetTimeZoneId() const { return m_timezone_id; } [[nodiscard]] sint64 GetUtcOffset() const { return m_utc_offset; } [[nodiscard]] uint32 GetPrincipalId() const { return m_principal_id; } [[nodiscard]] bool IsPasswordCacheEnabled() const { return m_password_cache_enabled != 0; } [[nodiscard]] const std::array<uint8, 32>& GetAccountPasswordCache() const { return m_account_password_cache; } [[nodiscard]] std::string_view GetStorageValue(std::string_view key) const; void SetMiiName(std::wstring_view name); void SetBirthYear(uint16 birth_year) { m_birth_year = birth_year; } void SetBirthMonth(uint8 birth_month) { m_birth_month = birth_month; } void SetBirthDay(uint8 birth_day) { m_birth_day = birth_day; } void SetGender(uint8 gender) { m_gender = gender; } void SetEmail(std::string_view email) { m_email = email; } void SetCountry(uint32 country) { m_country = country; } void SetTimeZoneId(std::string_view timezone_id) { m_timezone_id = timezone_id; } void SetUtcOffset(sint64 utc_offset) { m_utc_offset = utc_offset; } // this will always return at least one account (default one) static const std::vector<Account>& RefreshAccounts(); static void UpdatePersisidDat(); [[nodiscard]] static bool HasFreeAccountSlots(); [[nodiscard]] static const std::vector<Account>& GetAccounts(); [[nodiscard]] static const Account& GetAccount(uint32 persistent_id); [[nodiscard]] static const Account& GetCurrentAccount(); [[nodiscard]] static uint32 GetNextPersistentId(); [[nodiscard]] static fs::path GetFileName(uint32 persistent_id); [[nodiscard]] OnlineValidator ValidateOnlineFiles() const; private: Account(uint32 persistent_id); [[nodiscard]] std::error_code CheckValid() const; void ParseFile(class FileStream* file); uint32 m_persistent_id = 0; uint64 m_transferable_id_base = 0; std::array<uint8, 16> m_uuid {}; std::array<uint8, 96> m_mii_data{}; std::array<wchar_t, 11> m_mii_name{}; std::string m_account_id; uint16 m_birth_year = 0; uint8 m_birth_month = 0; uint8 m_birth_day = 0; uint8 m_gender = 0; std::string m_email; uint32 m_country = 0; uint32 m_simple_address_id = 0; std::string m_timezone_id; sint64 m_utc_offset; uint32 m_principal_id = 0; uint8 m_password_cache_enabled = 0; std::array<uint8, 32> m_account_password_cache{}; // misc storage for unused local properties std::unordered_map<std::string, std::string> m_storage; static std::vector<Account> s_account_list; }; ================================================ FILE: src/Cafe/Account/AccountError.h ================================================ #pragma once #include <system_error> enum class AccountErrc { NoError = 0, ParseError, InvalidPersistentId, InvalidUuid, InvalidMiiName, InvalidMiiData, }; namespace std { template <> struct is_error_code_enum<AccountErrc> : true_type {}; } namespace detail { // Define a custom error code category derived from std::error_category class AccountErrc_category : public std::error_category { public: // Return a short descriptive name for the category [[nodiscard]] const char* name() const noexcept override final { return "AccountError"; } // Return what each enum means in text [[nodiscard]] std::string message(int c) const override final { switch (static_cast<AccountErrc>(c)) { case AccountErrc::NoError: return "no error"; case AccountErrc::InvalidPersistentId: return "invalid PersistentId"; case AccountErrc::InvalidMiiName: return "invalid MiiName"; default: return "unknown error"; } } [[nodiscard]] std::error_condition default_error_condition(int c) const noexcept override final { switch (static_cast<AccountErrc>(c)) { case AccountErrc::InvalidPersistentId: return make_error_condition(std::errc::invalid_argument); default: return std::error_condition(c, *this); } } }; } inline std::error_code make_error_code(AccountErrc e) { static detail::AccountErrc_category c; return { static_cast<int>(e), c }; } ================================================ FILE: src/Cafe/CMakeLists.txt ================================================ add_library(CemuCafe Account/Account.cpp Account/AccountError.h Account/Account.h CafeSystem.cpp CafeSystem.h Filesystem/fsc.cpp Filesystem/fscDeviceHostFS.cpp Filesystem/fscDeviceHostFS.h Filesystem/fscDeviceRedirect.cpp Filesystem/fscDeviceWua.cpp Filesystem/fscDeviceWud.cpp Filesystem/fscDeviceWuhb.cpp Filesystem/fsc.h Filesystem/FST/FST.cpp Filesystem/FST/FST.h Filesystem/FST/fstUtil.h Filesystem/FST/KeyCache.cpp Filesystem/FST/KeyCache.h Filesystem/WUD/wud.cpp Filesystem/WUD/wud.h Filesystem/WUHB/RomFSStructs.h Filesystem/WUHB/WUHBReader.cpp Filesystem/WUHB/WUHBReader.h GamePatch.cpp GamePatch.h GameProfile/GameProfile.cpp GameProfile/GameProfile.h GraphicPack/GraphicPack2.cpp GraphicPack/GraphicPack2.h GraphicPack/GraphicPack2PatchesApply.cpp GraphicPack/GraphicPack2Patches.cpp GraphicPack/GraphicPack2Patches.h GraphicPack/GraphicPack2PatchesParser.cpp GraphicPack/GraphicPackError.h HW/ACR/ACR.cpp HW/AI/AI.cpp HW/AI/AI.h HW/Common/HwReg.h HW/Espresso/Const.h HW/Espresso/Debugger/Debugger.cpp HW/Espresso/Debugger/Debugger.h HW/Espresso/Debugger/DebugSymbolStorage.cpp HW/Espresso/Debugger/DebugSymbolStorage.h HW/Espresso/Debugger/GDBStub.h HW/Espresso/Debugger/GDBStub.cpp HW/Espresso/Debugger/GDBBreakpoints.cpp HW/Espresso/Debugger/GDBBreakpoints.h HW/Espresso/EspressoISA.h HW/Espresso/Interpreter/PPCInterpreterALU.hpp HW/Espresso/Interpreter/PPCInterpreterFPU.cpp HW/Espresso/Interpreter/PPCInterpreterHelper.h HW/Espresso/Interpreter/PPCInterpreterHLE.cpp HW/Espresso/Interpreter/PPCInterpreterImpl.cpp HW/Espresso/Interpreter/PPCInterpreterInternal.h HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp HW/Espresso/Interpreter/PPCInterpreterMain.cpp HW/Espresso/Interpreter/PPCInterpreterOPC.cpp HW/Espresso/Interpreter/PPCInterpreterOPC.hpp HW/Espresso/Interpreter/PPCInterpreterPS.cpp HW/Espresso/Interpreter/PPCInterpreterSPR.hpp HW/Espresso/PPCCallback.h HW/Espresso/PPCScheduler.cpp HW/Espresso/PPCSchedulerLLE.cpp HW/Espresso/PPCState.h HW/Espresso/PPCTimer.cpp HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h HW/Espresso/Recompiler/PPCRecompiler.cpp HW/Espresso/Recompiler/PPCRecompiler.h HW/Espresso/Recompiler/IML/IML.h HW/Espresso/Recompiler/IML/IMLSegment.cpp HW/Espresso/Recompiler/IML/IMLSegment.h HW/Espresso/Recompiler/IML/IMLInstruction.cpp HW/Espresso/Recompiler/IML/IMLInstruction.h HW/Espresso/Recompiler/IML/IMLDebug.cpp HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp HW/Espresso/Recompiler/IML/IMLOptimizer.cpp HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp HW/Espresso/Recompiler/PPCRecompilerIml.h HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp HW/Espresso/Recompiler/BackendX64/BackendX64AVX.cpp HW/Espresso/Recompiler/BackendX64/BackendX64BMI.cpp HW/Espresso/Recompiler/BackendX64/BackendX64.cpp HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp HW/Espresso/Recompiler/BackendX64/BackendX64Gen.cpp HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp HW/Espresso/Recompiler/BackendX64/BackendX64.h HW/Espresso/Recompiler/BackendX64/X64Emit.hpp HW/Espresso/Recompiler/BackendX64/x86Emitter.h HW/Latte/Common/RegisterSerializer.cpp HW/Latte/Common/RegisterSerializer.h HW/Latte/Common/ShaderSerializer.cpp HW/Latte/Common/ShaderSerializer.h HW/Latte/Core/FetchShader.cpp HW/Latte/Core/FetchShader.h HW/Latte/Core/LatteAsyncCommands.cpp HW/Latte/Core/LatteAsyncCommands.h HW/Latte/Core/LatteBufferCache.cpp HW/Latte/Core/LatteBufferCache.h HW/Latte/Core/LatteBufferData.cpp HW/Latte/Core/LatteCachedFBO.h HW/Latte/Core/LatteCommandProcessor.cpp HW/Latte/Core/LatteConst.h HW/Latte/Core/LatteDefaultShaders.cpp HW/Latte/Core/LatteDefaultShaders.h HW/Latte/Core/LatteDraw.h HW/Latte/Core/LatteGSCopyShaderParser.cpp HW/Latte/Core/Latte.h HW/Latte/Core/LatteIndices.cpp HW/Latte/Core/LatteIndices.h HW/Latte/Core/LatteOverlay.cpp HW/Latte/Core/LatteOverlay.h HW/Latte/Core/LattePerformanceMonitor.cpp HW/Latte/Core/LattePerformanceMonitor.h HW/Latte/Core/LattePM4.h HW/Latte/Core/LatteQuery.cpp HW/Latte/Core/LatteQueryObject.h HW/Latte/Core/LatteRenderTarget.cpp HW/Latte/Core/LatteRingBuffer.cpp HW/Latte/Core/LatteRingBuffer.h HW/Latte/Core/LatteShaderAssembly.h HW/Latte/Core/LatteShaderCache.cpp HW/Latte/Core/LatteShaderCache.h HW/Latte/Core/LatteShader.cpp HW/Latte/Core/LatteShaderGL.cpp HW/Latte/Core/LatteShader.h HW/Latte/Core/LatteSoftware.cpp HW/Latte/Core/LatteSoftware.h HW/Latte/Core/LatteStreamoutGPU.cpp HW/Latte/Core/LatteSurfaceCopy.cpp HW/Latte/Core/LatteTextureCache.cpp HW/Latte/Core/LatteTexture.cpp HW/Latte/Core/LatteTexture.h HW/Latte/Core/LatteTextureLegacy.cpp HW/Latte/Core/LatteTextureLoader.cpp HW/Latte/Core/LatteTextureLoader.h HW/Latte/Core/LatteTextureReadback.cpp HW/Latte/Core/LatteTextureReadbackInfo.h HW/Latte/Core/LatteTextureView.cpp HW/Latte/Core/LatteTextureView.h HW/Latte/Core/LatteThread.cpp HW/Latte/Core/LatteTiming.cpp HW/Latte/Core/LatteTiming.h HW/Latte/ISA/LatteInstructions.h HW/Latte/ISA/LatteReg.h HW/Latte/ISA/RegDefines.h HW/Latte/LatteAddrLib/AddrLibFastDecode.h HW/Latte/LatteAddrLib/LatteAddrLib_Coord.cpp HW/Latte/LatteAddrLib/LatteAddrLib.cpp HW/Latte/LatteAddrLib/LatteAddrLib.h HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h HW/Latte/LegacyShaderDecompiler/LatteDecompilerRegisterDataTypeTracker.cpp HW/Latte/Renderer/OpenGL/CachedFBOGL.h HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp HW/Latte/Renderer/OpenGL/LatteTextureGL.h HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h HW/Latte/Renderer/OpenGL/OpenGLQuery.cpp HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp HW/Latte/Renderer/OpenGL/OpenGLRenderer.h HW/Latte/Renderer/OpenGL/OpenGLRendererStreamout.cpp HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp HW/Latte/Renderer/OpenGL/RendererShaderGL.h HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp HW/Latte/Renderer/Renderer.cpp HW/Latte/Renderer/Renderer.h HW/Latte/Renderer/RendererOuputShader.cpp HW/Latte/Renderer/RendererOuputShader.h HW/Latte/Renderer/RendererShader.cpp HW/Latte/Renderer/RendererShader.h HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp HW/Latte/Renderer/Vulkan/CachedFBOVk.h HW/Latte/Renderer/Vulkan/CocoaSurface.h HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp HW/Latte/Renderer/Vulkan/LatteTextureVk.h HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp HW/Latte/Renderer/Vulkan/RendererShaderVk.h HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp HW/Latte/Renderer/Vulkan/VKRBase.h HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp HW/Latte/Renderer/Vulkan/VKRMemoryManager.h HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp HW/Latte/Renderer/Vulkan/VsyncDriver.cpp HW/Latte/Renderer/Vulkan/VsyncDriver.h HW/Latte/Renderer/Vulkan/VulkanAPI.cpp HW/Latte/Renderer/Vulkan/VulkanAPI.h HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h HW/Latte/Renderer/Vulkan/VulkanQuery.cpp HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp HW/Latte/Renderer/Vulkan/VulkanRenderer.h HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h HW/Latte/ShaderInfo/ShaderDescription.cpp HW/Latte/ShaderInfo/ShaderInfo.h HW/Latte/ShaderInfo/ShaderInstanceInfo.cpp HW/Latte/Transcompiler/LatteTC.cpp HW/Latte/Transcompiler/LatteTCGenIR.cpp HW/Latte/Transcompiler/LatteTC.h HW/MMU/MMU.cpp HW/MMU/MMU.h HW/SI/SI.cpp HW/SI/si.h HW/VI/VI.cpp IOSU/ccr_nfc/iosu_ccr_nfc.cpp IOSU/ccr_nfc/iosu_ccr_nfc.h IOSU/fsa/fsa_types.h IOSU/fsa/iosu_fsa.cpp IOSU/fsa/iosu_fsa.h IOSU/iosu_ipc_common.h IOSU/iosu_types_common.h IOSU/kernel/iosu_kernel.cpp IOSU/kernel/iosu_kernel.h IOSU/legacy/iosu_acp.cpp IOSU/legacy/iosu_acp.h IOSU/legacy/iosu_act.cpp IOSU/legacy/iosu_act.h IOSU/legacy/iosu_crypto.cpp IOSU/legacy/iosu_crypto.h IOSU/legacy/iosu_fpd.cpp IOSU/legacy/iosu_fpd.h IOSU/legacy/iosu_ioctl.cpp IOSU/legacy/iosu_ioctl.h IOSU/legacy/iosu_mcp.cpp IOSU/legacy/iosu_mcp.h IOSU/legacy/iosu_nim.cpp IOSU/legacy/iosu_nim.h IOSU/nn/iosu_nn_service.cpp IOSU/nn/iosu_nn_service.h IOSU/nn/boss/boss_common.cpp IOSU/nn/boss/boss_common.h IOSU/nn/boss/boss_service.cpp IOSU/nn/boss/boss_service.h IOSU/PDM/iosu_pdm.cpp IOSU/PDM/iosu_pdm.h IOSU/ODM/iosu_odm.cpp IOSU/ODM/iosu_odm.h OS/common/OSCommon.cpp OS/common/OSCommon.h OS/common/OSUtil.h OS/common/PPCConcurrentQueue.h OS/libs/avm/avm.cpp OS/libs/avm/avm.h OS/libs/camera/camera.cpp OS/libs/camera/camera.h OS/libs/coreinit/coreinit_Alarm.cpp OS/libs/coreinit/coreinit_Alarm.h OS/libs/coreinit/coreinit_Atomic.cpp OS/libs/coreinit/coreinit_Atomic.h OS/libs/coreinit/coreinit_BSP.cpp OS/libs/coreinit/coreinit_BSP.h OS/libs/coreinit/coreinit_Callbacks.cpp OS/libs/coreinit/coreinit_CodeGen.cpp OS/libs/coreinit/coreinit_CodeGen.h OS/libs/coreinit/coreinit_Coroutine.cpp OS/libs/coreinit/coreinit_Coroutine.h OS/libs/coreinit/coreinit.cpp OS/libs/coreinit/coreinit_DynLoad.cpp OS/libs/coreinit/coreinit_DynLoad.h OS/libs/coreinit/coreinit_FG.cpp OS/libs/coreinit/coreinit_FG.h OS/libs/coreinit/coreinit_FS.cpp OS/libs/coreinit/coreinit_FS.h OS/libs/coreinit/coreinit_GHS.cpp OS/libs/coreinit/coreinit_GHS.h OS/libs/coreinit/coreinit.h OS/libs/coreinit/coreinit_HWInterface.cpp OS/libs/coreinit/coreinit_HWInterface.h OS/libs/coreinit/coreinit_IM.cpp OS/libs/coreinit/coreinit_IM.h OS/libs/coreinit/coreinit_Init.cpp OS/libs/coreinit/coreinit_IOS.cpp OS/libs/coreinit/coreinit_IOS.h OS/libs/coreinit/coreinit_IPCBuf.cpp OS/libs/coreinit/coreinit_IPCBuf.h OS/libs/coreinit/coreinit_IPC.cpp OS/libs/coreinit/coreinit_IPC.h OS/libs/coreinit/coreinit_LockedCache.cpp OS/libs/coreinit/coreinit_LockedCache.h OS/libs/coreinit/coreinit_MCP.cpp OS/libs/coreinit/coreinit_MCP.h OS/libs/coreinit/coreinit_MEM_BlockHeap.cpp OS/libs/coreinit/coreinit_MEM_BlockHeap.h OS/libs/coreinit/coreinit_MEM.cpp OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp OS/libs/coreinit/coreinit_MEM_ExpHeap.h OS/libs/coreinit/coreinit_MEM_FrmHeap.cpp OS/libs/coreinit/coreinit_MEM_FrmHeap.h OS/libs/coreinit/coreinit_MEM.h OS/libs/coreinit/coreinit_Memory.cpp OS/libs/coreinit/coreinit_Memory.h OS/libs/coreinit/coreinit_MemoryMapping.cpp OS/libs/coreinit/coreinit_MemoryMapping.h OS/libs/coreinit/coreinit_MEM_UnitHeap.cpp OS/libs/coreinit/coreinit_MEM_UnitHeap.h OS/libs/coreinit/coreinit_MessageQueue.cpp OS/libs/coreinit/coreinit_MessageQueue.h OS/libs/coreinit/coreinit_Misc.cpp OS/libs/coreinit/coreinit_Misc.h OS/libs/coreinit/coreinit_MPQueue.cpp OS/libs/coreinit/coreinit_MPQueue.h OS/libs/coreinit/coreinit_OSScreen.cpp OS/libs/coreinit/coreinit_OSScreen_font.h OS/libs/coreinit/coreinit_OSScreen.h OS/libs/coreinit/coreinit_OverlayArena.cpp OS/libs/coreinit/coreinit_OverlayArena.h OS/libs/coreinit/coreinit_Scheduler.cpp OS/libs/coreinit/coreinit_Scheduler.h OS/libs/coreinit/coreinit_Spinlock.cpp OS/libs/coreinit/coreinit_Spinlock.h OS/libs/coreinit/coreinit_Synchronization.cpp OS/libs/coreinit/coreinit_SysHeap.cpp OS/libs/coreinit/coreinit_SysHeap.h OS/libs/coreinit/coreinit_SystemInfo.cpp OS/libs/coreinit/coreinit_SystemInfo.h OS/libs/coreinit/coreinit_Thread.cpp OS/libs/coreinit/coreinit_Thread.h OS/libs/coreinit/coreinit_ThreadQueue.cpp OS/libs/coreinit/coreinit_Time.cpp OS/libs/coreinit/coreinit_Time.h OS/libs/dmae/dmae.cpp OS/libs/dmae/dmae.h OS/libs/drmapp/drmapp.cpp OS/libs/drmapp/drmapp.h OS/libs/erreula/erreula.cpp OS/libs/erreula/erreula.h OS/libs/gx2/GX2_AddrTest.cpp OS/libs/gx2/GX2_Blit.cpp OS/libs/gx2/GX2_Blit.h OS/libs/gx2/GX2_Command.cpp OS/libs/gx2/GX2_Command.h OS/libs/gx2/GX2_ContextState.cpp OS/libs/gx2/GX2.cpp OS/libs/gx2/GX2_Draw.cpp OS/libs/gx2/GX2_Draw.h OS/libs/gx2/GX2_Event.cpp OS/libs/gx2/GX2_Event.h OS/libs/gx2/GX2.h OS/libs/gx2/GX2_Memory.cpp OS/libs/gx2/GX2_Memory.h OS/libs/gx2/GX2_Misc.cpp OS/libs/gx2/GX2_Misc.h OS/libs/gx2/GX2_Query.cpp OS/libs/gx2/GX2_Query.h OS/libs/gx2/GX2_RenderTarget.cpp OS/libs/gx2/GX2_Resource.cpp OS/libs/gx2/GX2_Resource.h OS/libs/gx2/GX2_Shader.cpp OS/libs/gx2/GX2_Shader.h OS/libs/gx2/GX2_shader_legacy.cpp OS/libs/gx2/GX2_State.cpp OS/libs/gx2/GX2_State.h OS/libs/gx2/GX2_Streamout.cpp OS/libs/gx2/GX2_Streamout.h OS/libs/gx2/GX2_Surface_Copy.cpp OS/libs/gx2/GX2_Surface_Copy.h OS/libs/gx2/GX2_Surface.cpp OS/libs/gx2/GX2_Surface.h OS/libs/gx2/GX2_Texture.cpp OS/libs/gx2/GX2_Texture.h OS/libs/gx2/GX2_TilingAperture.cpp OS/libs/h264_avc/H264Dec.cpp OS/libs/h264_avc/H264DecBackendAVC.cpp OS/libs/h264_avc/h264dec.h OS/libs/h264_avc/H264DecInternal.h OS/libs/h264_avc/parser OS/libs/h264_avc/parser/H264Parser.cpp OS/libs/h264_avc/parser/H264Parser.h OS/libs/mic/mic.cpp OS/libs/mic/mic.h OS/libs/nfc/ndef.cpp OS/libs/nfc/ndef.h OS/libs/nfc/nfc.cpp OS/libs/nfc/nfc.h OS/libs/nfc/stream.cpp OS/libs/nfc/stream.h OS/libs/nfc/TagV0.cpp OS/libs/nfc/TagV0.h OS/libs/nfc/TLV.cpp OS/libs/nfc/TLV.h OS/libs/nlibcurl/nlibcurl.cpp OS/libs/nlibcurl/nlibcurlDebug.hpp OS/libs/nlibcurl/nlibcurl.h OS/libs/nlibnss/nlibnss.cpp OS/libs/nlibnss/nlibnss.h OS/libs/nn_ac/nn_ac.cpp OS/libs/nn_ac/nn_ac.h OS/libs/nn_acp/nn_acp.cpp OS/libs/nn_acp/nn_acp.h OS/libs/nn_act/nn_act.cpp OS/libs/nn_act/nn_act.h OS/libs/nn_aoc/nn_aoc.cpp OS/libs/nn_aoc/nn_aoc.h OS/libs/nn_boss/nn_boss.cpp OS/libs/nn_boss/nn_boss.h OS/libs/nn_ccr/nn_ccr.cpp OS/libs/nn_ccr/nn_ccr.h OS/libs/nn_cmpt/nn_cmpt.cpp OS/libs/nn_cmpt/nn_cmpt.h OS/libs/nn_client_service.h OS/libs/nn_common.h OS/libs/nn_ec/nn_ec.cpp OS/libs/nn_ec/nn_ec.h OS/libs/nn_fp/nn_fp.cpp OS/libs/nn_fp/nn_fp.h OS/libs/nn_idbe/nn_idbe.cpp OS/libs/nn_idbe/nn_idbe.h OS/libs/nn_ndm/nn_ndm.cpp OS/libs/nn_ndm/nn_ndm.h OS/libs/nn_spm/nn_spm.cpp OS/libs/nn_spm/nn_spm.h OS/libs/nn_sl/nn_sl.cpp OS/libs/nn_sl/nn_sl.h OS/libs/nn_nfp/AmiiboCrypto.h OS/libs/nn_nfp/nn_nfp.cpp OS/libs/nn_nfp/nn_nfp.h OS/libs/nn_nim/nn_nim.cpp OS/libs/nn_nim/nn_nim.h OS/libs/nn_olv/nn_olv.cpp OS/libs/nn_olv/nn_olv.h OS/libs/nn_olv/nn_olv_Common.cpp OS/libs/nn_olv/nn_olv_Common.h OS/libs/nn_olv/nn_olv_InitializeTypes.cpp OS/libs/nn_olv/nn_olv_InitializeTypes.h OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h OS/libs/nn_olv/nn_olv_PostTypes.cpp OS/libs/nn_olv/nn_olv_PostTypes.h OS/libs/nn_olv/nn_olv_OfflineDB.cpp OS/libs/nn_olv/nn_olv_OfflineDB.h OS/libs/nn_pdm/nn_pdm.cpp OS/libs/nn_pdm/nn_pdm.h OS/libs/nn_save/nn_save.cpp OS/libs/nn_save/nn_save.h OS/libs/nn_temp/nn_temp.cpp OS/libs/nn_temp/nn_temp.h OS/libs/nn_uds/nn_uds.cpp OS/libs/nn_uds/nn_uds.h OS/libs/nsyshid/nsyshid.cpp OS/libs/nsyshid/nsyshid.h OS/libs/nsyshid/Backend.h OS/libs/nsyshid/AttachDefaultBackends.cpp OS/libs/nsyshid/Whitelist.cpp OS/libs/nsyshid/Whitelist.h OS/libs/nsyshid/BackendEmulated.cpp OS/libs/nsyshid/BackendEmulated.h OS/libs/nsyshid/BackendLibusb.cpp OS/libs/nsyshid/BackendLibusb.h OS/libs/nsyshid/Dimensions.cpp OS/libs/nsyshid/Dimensions.h OS/libs/nsyshid/Infinity.cpp OS/libs/nsyshid/Infinity.h OS/libs/nsyshid/Skylander.cpp OS/libs/nsyshid/Skylander.h OS/libs/nsyshid/SkylanderXbox360.cpp OS/libs/nsyshid/SkylanderXbox360.h OS/libs/nsyshid/g721/g721.cpp OS/libs/nsyshid/g721/g721.h OS/libs/nsyskbd/nsyskbd.cpp OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp OS/libs/nsysnet/nsysnet.h OS/libs/ntag/ntag.cpp OS/libs/ntag/ntag.h OS/libs/padscore/padscore.cpp OS/libs/padscore/padscore.h OS/libs/proc_ui/proc_ui.cpp OS/libs/proc_ui/proc_ui.h OS/libs/snd_core/ax_aux.cpp OS/libs/snd_core/ax_exports.cpp OS/libs/snd_core/ax.h OS/libs/snd_core/ax_internal.h OS/libs/snd_core/ax_ist.cpp OS/libs/snd_core/ax_mix.cpp OS/libs/snd_core/ax_multivoice.cpp OS/libs/snd_core/ax_out.cpp OS/libs/snd_core/ax_voice.cpp OS/libs/snd_user/snd_user.cpp OS/libs/snd_user/snd_user.h OS/libs/swkbd/swkbd.cpp OS/libs/swkbd/swkbd.h OS/libs/sysapp/sysapp.cpp OS/libs/sysapp/sysapp.h OS/libs/TCL/TCL.cpp OS/libs/TCL/TCL.h OS/libs/vpad/vpad.cpp OS/libs/vpad/vpad.h OS/libs/zlib125 OS/libs/zlib125/zlib125.cpp OS/libs/zlib125/zlib125.h OS/RPL/COSModule.cpp OS/RPL/COSModule.h OS/RPL/elf.cpp OS/RPL/rpl.cpp OS/RPL/rpl_debug_symbols.cpp OS/RPL/rpl_debug_symbols.h OS/RPL/rpl.h OS/RPL/rpl_structs.h OS/RPL/rpl_symbol_storage.cpp OS/RPL/rpl_symbol_storage.h TitleList/GameInfo.h TitleList/ParsedMetaXml.h TitleList/SaveInfo.cpp TitleList/SaveInfo.h TitleList/SaveList.cpp TitleList/SaveList.h TitleList/TitleId.h TitleList/TitleInfo.cpp TitleList/TitleInfo.h TitleList/TitleList.cpp TitleList/TitleList.h ) if(APPLE) target_sources(CemuCafe PRIVATE HW/Latte/Renderer/Vulkan/CocoaSurface.mm HW/Latte/Renderer/MetalView.mm HW/Latte/Renderer/MetalView.h ) endif() if(ENABLE_METAL) target_sources(CemuCafe PRIVATE HW/Latte/Renderer/Metal/CachedFBOMtl.cpp HW/Latte/Renderer/Metal/CachedFBOMtl.h HW/Latte/Renderer/Metal/LatteTextureMtl.cpp HW/Latte/Renderer/Metal/LatteTextureMtl.h HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.cpp HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.h HW/Latte/Renderer/Metal/LatteTextureViewMtl.cpp HW/Latte/Renderer/Metal/LatteTextureViewMtl.h HW/Latte/Renderer/Metal/LatteToMtl.cpp HW/Latte/Renderer/Metal/LatteToMtl.h HW/Latte/Renderer/Metal/MetalAttachmentsInfo.cpp HW/Latte/Renderer/Metal/MetalAttachmentsInfo.h HW/Latte/Renderer/Metal/MetalBufferAllocator.cpp HW/Latte/Renderer/Metal/MetalBufferAllocator.h HW/Latte/Renderer/Metal/MetalCommon.h HW/Latte/Renderer/Metal/MetalCppImpl.cpp HW/Latte/Renderer/Metal/MetalDepthStencilCache.cpp HW/Latte/Renderer/Metal/MetalDepthStencilCache.h HW/Latte/Renderer/Metal/MetalLayer.h HW/Latte/Renderer/Metal/MetalLayer.mm HW/Latte/Renderer/Metal/MetalLayerHandle.cpp HW/Latte/Renderer/Metal/MetalLayerHandle.h HW/Latte/Renderer/Metal/MetalMemoryManager.cpp HW/Latte/Renderer/Metal/MetalMemoryManager.h HW/Latte/Renderer/Metal/MetalOutputShaderCache.cpp HW/Latte/Renderer/Metal/MetalOutputShaderCache.h HW/Latte/Renderer/Metal/MetalPerformanceMonitor.h HW/Latte/Renderer/Metal/MetalPipelineCache.cpp HW/Latte/Renderer/Metal/MetalPipelineCache.h HW/Latte/Renderer/Metal/MetalPipelineCompiler.cpp HW/Latte/Renderer/Metal/MetalPipelineCompiler.h HW/Latte/Renderer/Metal/MetalQuery.cpp HW/Latte/Renderer/Metal/MetalQuery.h HW/Latte/Renderer/Metal/MetalRenderer.cpp HW/Latte/Renderer/Metal/MetalRenderer.h HW/Latte/Renderer/Metal/MetalSamplerCache.cpp HW/Latte/Renderer/Metal/MetalSamplerCache.h HW/Latte/Renderer/Metal/MetalVoidVertexPipeline.cpp HW/Latte/Renderer/Metal/MetalVoidVertexPipeline.h HW/Latte/Renderer/Metal/RendererShaderMtl.cpp HW/Latte/Renderer/Metal/RendererShaderMtl.h HW/Latte/Renderer/Metal/UtilityShaderSource.h ) target_sources(CemuCafe PRIVATE HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLAttrDecoder.cpp HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSL.cpp HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp ) target_link_libraries(CemuCafe PRIVATE "-framework Metal" "-framework QuartzCore" ) endif() if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") target_sources(CemuCafe PRIVATE HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h ) target_link_libraries(CemuCafe PRIVATE xbyak_aarch64) endif() set_property(TARGET CemuCafe PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") target_include_directories(CemuCafe PUBLIC "../") if (glslang_VERSION VERSION_LESS "15.0.0") set(glslang_target "glslang::SPIRV") else() set(glslang_target "glslang::glslang") endif() target_link_libraries(CemuCafe PRIVATE CemuCommon CemuGui ZArchive::zarchive imguiImpl pugixml::pugixml ZLIB::ZLIB CURL::libcurl ih264d ${glslang_target} PUBLIC OpenSSL::SSL ) if (ENABLE_WAYLAND) # PUBLIC because wayland-client.h is included in VulkanAPI.h target_link_libraries(CemuCafe PUBLIC Wayland::Client) endif() if (ENABLE_VCPKG) if(WIN32) set(PKG_CONFIG_EXECUTABLE "${VCPKG_INSTALLED_DIR}/x64-windows/tools/pkgconf/pkgconf.exe") endif() find_package(PkgConfig REQUIRED) pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0) target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb) else () find_package(libusb MODULE REQUIRED) target_link_libraries(CemuCafe PRIVATE libusb::libusb) endif () if(WIN32) target_link_libraries(CemuCafe PRIVATE iphlpapi) endif() ================================================ FILE: src/Cafe/CafeSystem.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "WindowSystem.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "audio/IAudioAPI.h" #include "audio/IAudioInputAPI.h" #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" #include "Cafe/TitleList/GameInfo.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "util/helpers/SystemException.h" #include "Common/cpu_features.h" #include "input/InputManager.h" #include "Cafe/CafeSystem.h" #include "Cafe/TitleList/TitleList.h" #include "Cafe/TitleList/GameInfo.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/Filesystem/FST/FST.h" #include "Common/FileStream.h" #include "GamePatch.h" #include "HW/Espresso/Debugger/GDBStub.h" #include "Cafe/IOSU/legacy/iosu_ioctl.h" #include "Cafe/IOSU/legacy/iosu_act.h" #include "Cafe/IOSU/legacy/iosu_fpd.h" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cafe/IOSU/legacy/iosu_mcp.h" #include "Cafe/IOSU/legacy/iosu_acp.h" #include "Cafe/IOSU/legacy/iosu_nim.h" #include "Cafe/IOSU/PDM/iosu_pdm.h" #include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" #include "Cafe/IOSU/nn/boss/boss_service.h" // IOSU initializer functions #include "Cafe/IOSU/kernel/iosu_kernel.h" #include "Cafe/IOSU/fsa/iosu_fsa.h" #include "Cafe/IOSU/ODM/iosu_odm.h" // Cafe OS initializer and shutdown functions #include "Cafe/OS/libs/avm/avm.h" #include "Cafe/OS/libs/drmapp/drmapp.h" #include "Cafe/OS/libs/TCL/TCL.h" #include "Cafe/OS/libs/snd_user/snd_user.h" #include "Cafe/OS/libs/h264_avc/h264dec.h" #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/gx2/GX2_Misc.h" #include "Cafe/OS/libs/mic/mic.h" #include "Cafe/OS/libs/nfc/nfc.h" #include "Cafe/OS/libs/ntag/ntag.h" #include "Cafe/OS/libs/nn_aoc/nn_aoc.h" #include "Cafe/OS/libs/nn_pdm/nn_pdm.h" #include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h" #include "Cafe/OS/libs/nn_ccr/nn_ccr.h" #include "Cafe/OS/libs/nn_temp/nn_temp.h" #include "Cafe/OS/libs/nn_save/nn_save.h" // HW interfaces #include "Cafe/HW/SI/si.h" #include <time.h> #if BOOST_OS_LINUX #include <sys/sysinfo.h> #elif BOOST_OS_MACOS || BOOST_OS_BSD #include <sys/types.h> #include <sys/sysctl.h> #endif std::string _pathToExecutable; std::string _pathToBaseExecutable; RPLModule* applicationRPX = nullptr; uint32 currentBaseApplicationHash = 0; uint32 currentUpdatedApplicationHash = 0; bool isLaunchTypeELF = false; MPTR _entryPoint = MPTR_NULL; uint32 generateHashFromRawRPXData(uint8* rpxData, sint32 size) { uint32 h = 0x3416DCBF; for (sint32 i = 0; i < size; i++) { uint32 c = rpxData[i]; h = (h << 3) | (h >> 29); h += c; } return h; } bool ScanForRPX() { bool rpxFound = false; sint32 fscStatus = 0; FSCVirtualFile* fscDirItr = fsc_openDirIterator("/internal/current_title/code/", &fscStatus); if (fscDirItr) { FSCDirEntry dirEntry; while (fsc_nextDir(fscDirItr, &dirEntry)) { sint32 dirItrPathLen = strlen(dirEntry.path); if (dirItrPathLen < 4) continue; if (boost::iequals(dirEntry.path + dirItrPathLen - 4, ".rpx")) { rpxFound = true; _pathToExecutable = fmt::format("/internal/current_title/code/{}", dirEntry.path); break; } } fsc_close(fscDirItr); } return rpxFound; } void SetEntryPoint(MPTR entryPoint) { _entryPoint = entryPoint; } // load executable into virtual memory and set entrypoint void LoadMainExecutable() { isLaunchTypeELF = false; // when launching from a disc image _pathToExecutable is initially empty if (_pathToExecutable.empty()) { // try to get the RPX path from the meta files // todo // otherwise search for first file with .rpx extension in the code folder if (!ScanForRPX()) { cemuLog_log(LogType::Force, "Unable to find RPX executable"); cemuLog_waitForFlush(); cemu_assert(false); } } // extract and load RPX uint32 rpxSize = 0; uint8* rpxData = fsc_extractFile(_pathToExecutable.c_str(), &rpxSize); if (rpxData == nullptr) { cemuLog_log(LogType::Force, "Failed to load \"{}\"", _pathToExecutable); cemuLog_waitForFlush(); cemu_assert(false); } currentUpdatedApplicationHash = generateHashFromRawRPXData(rpxData, rpxSize); // determine if this file is an ELF const uint8 elfHeaderMagic[9] = { 0x7F,0x45,0x4C,0x46,0x01,0x02,0x01,0x00,0x00 }; if (rpxSize >= 10 && memcmp(rpxData, elfHeaderMagic, sizeof(elfHeaderMagic)) == 0) { // ELF SetEntryPoint(ELF_LoadFromMemory(rpxData, rpxSize, _pathToExecutable.c_str())); isLaunchTypeELF = true; } else { // RPX RPLLoader_AddDependency(_pathToExecutable.c_str()); applicationRPX = RPLLoader_LoadFromMemory(rpxData, rpxSize, (char*)_pathToExecutable.c_str()); if (!applicationRPX) { WindowSystem::ShowErrorDialog(_tr("Failed to run this title because the executable is damaged")); cemuLog_createLogFile(false); cemuLog_waitForFlush(); exit(0); } RPLLoader_SetMainModule(applicationRPX); SetEntryPoint(RPLLoader_GetModuleEntrypoint(applicationRPX)); } free(rpxData); // get RPX hash of game without update uint32 baseRpxSize = 0; uint8* baseRpxData = fsc_extractFile(!_pathToBaseExecutable.empty() ? _pathToBaseExecutable.c_str() : _pathToExecutable.c_str(), &baseRpxSize, FSC_PRIORITY_BASE); if (baseRpxData == nullptr) { currentBaseApplicationHash = currentUpdatedApplicationHash; } else { currentBaseApplicationHash = generateHashFromRawRPXData(baseRpxData, baseRpxSize); } free(baseRpxData); debug_printf("RPXHash: 0x%08x\n", currentBaseApplicationHash); } fs::path getTitleSavePath() { const uint64 titleId = CafeSystem::GetForegroundTitleId(); return ActiveSettings::GetMlcPath("usr/save/{:08X}/{:08X}/user/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); } void InfoLog_TitleLoaded() { uint64 titleId = CafeSystem::GetForegroundTitleId(); cemuLog_log(LogType::Force, "------- Loaded title -------"); cemuLog_log(LogType::Force, "TitleId: {:08x}-{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); cemuLog_log(LogType::Force, "TitleVersion: v{}", CafeSystem::GetForegroundTitleVersion()); CafeConsoleRegion region = CafeSystem::GetForegroundTitleRegion(); if(region == CafeConsoleRegion::JPN) cemuLog_log(LogType::Force, "TitleRegion: JP"); else if (region == CafeConsoleRegion::EUR) cemuLog_log(LogType::Force, "TitleRegion: EU"); else if (region == CafeConsoleRegion::USA) cemuLog_log(LogType::Force, "TitleRegion: US"); fs::path effectiveSavePath = getTitleSavePath(); std::error_code ec; const bool saveDirExists = fs::exists(effectiveSavePath, ec); cemuLog_log(LogType::Force, "Save path: {}{}", _pathToUtf8(effectiveSavePath), saveDirExists ? "" : " (not present)"); // log shader cache name cemuLog_log(LogType::Force, "Shader cache file: shaderCache/transferable/{:016x}.bin", titleId); // game profile info std::string gameProfilePath; if(g_current_game_profile->IsDefaultProfile()) gameProfilePath = fmt::format("gameProfiles/default/{:016x}.ini", titleId); else gameProfilePath = fmt::format("gameProfiles/{:016x}.ini", titleId); cemuLog_log(LogType::Force, "gameprofile path: {}", g_current_game_profile->IsLoaded() ? gameProfilePath : std::string(" (not present)")); // rpx hash of updated game cemuLog_log(LogType::Force, "RPX hash (updated): {:08x}", currentUpdatedApplicationHash); cemuLog_log(LogType::Force, "RPX hash (base): {:08x}", currentBaseApplicationHash); memory_logModifiedMemoryRanges(); } void InfoLog_PrintActiveSettings() { const auto& config = GetConfig(); cemuLog_log(LogType::Force, "------- Active settings -------"); // settings to log: cemuLog_log(LogType::Force, "CPU-Mode: {}{}", fmt::format("{}", ActiveSettings::GetCPUMode()).c_str(), g_current_game_profile->GetCPUMode().has_value() ? " (gameprofile)" : ""); cemuLog_log(LogType::Force, "Load shared libraries: {}{}", ActiveSettings::LoadSharedLibrariesEnabled() ? "true" : "false", g_current_game_profile->ShouldLoadSharedLibraries().has_value() ? " (gameprofile)" : ""); cemuLog_log(LogType::Force, "Use precompiled shaders: {}{}", fmt::format("{}", ActiveSettings::GetPrecompiledShadersOption()), g_current_game_profile->GetPrecompiledShadersState().has_value() ? " (gameprofile)" : ""); cemuLog_log(LogType::Force, "Full sync at GX2DrawDone: {}", ActiveSettings::WaitForGX2DrawDoneEnabled() ? "true" : "false"); cemuLog_log(LogType::Force, "Strict shader mul: {}", g_current_game_profile->GetAccurateShaderMul() == AccurateShaderMulOption::True ? "true" : "false"); if (ActiveSettings::GetGraphicsAPI() == GraphicAPI::kVulkan) { cemuLog_log(LogType::Force, "Async compile: {}", GetConfig().async_compile.GetValue() ? "true" : "false"); if (!GetConfig().vk_accurate_barriers.GetValue()) cemuLog_log(LogType::Force, "Accurate barriers are disabled!"); } #if ENABLE_METAL else if (ActiveSettings::GetGraphicsAPI() == GraphicAPI::kMetal) { cemuLog_log(LogType::Force, "Async compile: {}", GetConfig().async_compile.GetValue() ? "true" : "false"); cemuLog_log(LogType::Force, "Force mesh shaders: {}", GetConfig().force_mesh_shaders.GetValue() ? "true" : "false"); cemuLog_log(LogType::Force, "Fast math: {}", g_current_game_profile->GetShaderFastMath() ? "true" : "false"); cemuLog_log(LogType::Force, "Buffer cache type: {}", g_current_game_profile->GetBufferCacheMode()); cemuLog_log(LogType::Force, "Position invariance: {}", g_current_game_profile->GetPositionInvariance()); if (!GetConfig().vk_accurate_barriers.GetValue()) cemuLog_log(LogType::Force, "Accurate barriers are disabled!"); } #endif cemuLog_log(LogType::Force, "Console language: {}", stdx::to_underlying(config.console_language.GetValue())); } struct SharedDataEntry { /* +0x00 */ uint32be name; /* +0x04 */ uint32be fileType; // 2 = font /* +0x08 */ uint32be kernelFilenamePtr; /* +0x0C */ MEMPTR<void> data; /* +0x10 */ uint32be size; /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be ukn18; }; struct { uint32 name; uint32 fileType; const char* fileName; const char* resourcePath; const char* mlcPath; }shareddataDef[] = { 0xFFCAFE01, 2, "CafeCn.ttf", "resources/sharedFonts/CafeCn.ttf", "sys/title/0005001b/10042400/content/CafeCn.ttf", 0xFFCAFE02, 2, "CafeKr.ttf", "resources/sharedFonts/CafeKr.ttf", "sys/title/0005001b/10042400/content/CafeKr.ttf", 0xFFCAFE03, 2, "CafeStd.ttf", "resources/sharedFonts/CafeStd.ttf", "sys/title/0005001b/10042400/content/CafeStd.ttf", 0xFFCAFE04, 2, "CafeTw.ttf", "resources/sharedFonts/CafeTw.ttf", "sys/title/0005001b/10042400/content/CafeTw.ttf" }; static_assert(sizeof(SharedDataEntry) == 0x1C); uint32 LoadSharedData() { // check if font files are dumped bool hasAllShareddataFiles = true; for (sint32 i = 0; i < sizeof(shareddataDef) / sizeof(shareddataDef[0]); i++) { bool existsInMLC = fs::exists(ActiveSettings::GetMlcPath(shareddataDef[i].mlcPath)); bool existsInResources = fs::exists(ActiveSettings::GetDataPath(shareddataDef[i].resourcePath)); if (!existsInMLC && !existsInResources) { cemuLog_log(LogType::Force, "Shared font {} is not present", shareddataDef[i].fileName); hasAllShareddataFiles = false; break; } } sint32 numEntries = sizeof(shareddataDef) / sizeof(shareddataDef[0]); if (hasAllShareddataFiles) { // all shareddata font files are present -> load them SharedDataEntry* shareddataTable = (SharedDataEntry*)memory_getPointerFromVirtualOffset(0xF8000000); memset(shareddataTable, 0, sizeof(SharedDataEntry) * numEntries); uint8* dataWritePtr = memory_getPointerFromVirtualOffset(0xF8000000 + sizeof(SharedDataEntry) * numEntries); // setup entries for (sint32 i = 0; i < numEntries; i++) { // try to read font from MLC first auto path = ActiveSettings::GetMlcPath(shareddataDef[i].mlcPath); FileStream* fontFile = FileStream::openFile2(path); // alternatively fall back to our shared fonts if (!fontFile) { path = ActiveSettings::GetDataPath(shareddataDef[i].resourcePath); fontFile = FileStream::openFile2(path); } if (!fontFile) { cemuLog_log(LogType::Force, "Failed to load shared font {}", shareddataDef[i].fileName); continue; } uint32 fileSize = fontFile->GetSize(); fontFile->readData(dataWritePtr, fileSize); delete fontFile; // setup entry shareddataTable[i].name = shareddataDef[i].name; shareddataTable[i].fileType = shareddataDef[i].fileType; shareddataTable[i].kernelFilenamePtr = 0x00000000; shareddataTable[i].data = dataWritePtr; shareddataTable[i].size = fileSize; shareddataTable[i].ukn14 = 0x00000000; shareddataTable[i].ukn18 = 0x00000000; // advance write offset and pad to 16 byte alignment dataWritePtr += ((fileSize + 15) & ~15); } cemuLog_log(LogType::Force, "COS: System fonts found. Generated shareddata ({}KB)", (uint32)(dataWritePtr - (uint8*)shareddataTable) / 1024); return memory_getVirtualOffsetFromPointer(dataWritePtr); } // alternative method: load RAM dump const auto path = ActiveSettings::GetUserDataPath("shareddata.bin"); FileStream* ramDumpFile = FileStream::openFile2(path); if (ramDumpFile) { ramDumpFile->readData(memory_getPointerFromVirtualOffset(0xF8000000), 0x02000000); delete ramDumpFile; return (mmuRange_SHARED_AREA.getBase() + 0x02000000); } return mmuRange_SHARED_AREA.getBase() + sizeof(SharedDataEntry) * numEntries; } void cemu_initForGame() { WindowSystem::UpdateWindowTitles(false, true, 0.0); cemuLog_createLogFile(false); // input manager apply game profile InputManager::instance().apply_game_profile(); // determine cycle offset since 1.1.2000 uint64 secondsSince2000_UTC = (uint64)(time(NULL) - 946684800); ppcCyclesSince2000_UTC = secondsSince2000_UTC * (uint64)ESPRESSO_CORE_CLOCK; time_t theTime = (time(NULL) - 946684800); { tm* lt = localtime(&theTime); #if BOOST_OS_WINDOWS theTime = _mkgmtime(lt); #else theTime = timegm(lt); #endif } ppcCyclesSince2000 = theTime * (uint64)ESPRESSO_CORE_CLOCK; ppcCyclesSince2000TimerClock = ppcCyclesSince2000 / 20ULL; PPCTimer_start(); // coreinit is bootstrapped first and then the main game executable is loaded RPLLoader_LoadCoreinit(); LoadMainExecutable(); // log info for launched title InfoLog_TitleLoaded(); // link all modules uint32 linkTimeStart = GetTickCount(); RPLLoader_UpdateDependencies(); RPLLoader_Link(); RPLLoader_NotifyControlPassedToApplication(); uint32 linkTime = GetTickCount() - linkTimeStart; cemuLog_log(LogType::Force, "RPL link time: {}ms", linkTime); // for HBL ELF: Setup OS-specifics struct if (isLaunchTypeELF) { memory_writeU32(0x801500, rpl_mapHLEImport(nullptr, "coreinit", "OSDynLoad_Acquire", true)); memory_writeU32(0x801504, rpl_mapHLEImport(nullptr, "coreinit", "OSDynLoad_FindExport", true)); } else { // replace any known function signatures with our HLE implementations and patch bugs in the games GamePatch_scan(); } LatteGPUState.isDRCPrimary = ActiveSettings::DisplayDRCEnabled(); InfoLog_PrintActiveSettings(); Latte_Start(); // check for debugger entrypoint bp if (g_gdbstub) { g_gdbstub->HandleEntryStop(_entryPoint); g_gdbstub->Initialize(); } debugger_handleEntryBreakpoint(_entryPoint); // load graphic packs cemuLog_log(LogType::Force, "------- Activate graphic packs -------"); GraphicPack2::ActivateForCurrentTitle(); // print audio log IAudioAPI::PrintLogging(); IAudioInputAPI::PrintLogging(); // everything initialized cemuLog_log(LogType::Force, "------- Run title -------"); // wait till GPU thread is initialized while (g_isGPUInitFinished == false) std::this_thread::sleep_for(std::chrono::milliseconds(50)); // run coreinit rpl_entry RPLLoader_CallCoreinitEntrypoint(); // init AX and start AX I/O thread snd_core::AXOut_init(); } namespace CafeSystem { void InitVirtualMlcStorage(); void MlcStorageMountTitle(TitleInfo& titleInfo); void MlcStorageUnmountAllTitles(); static bool s_initialized = false; static SystemImplementation* s_implementation{nullptr}; bool sLaunchModeIsStandalone = false; std::optional<std::vector<std::string>> s_overrideArgs; bool sSystemRunning = false; TitleId sForegroundTitleId = 0; GameInfo2 sGameInfo_ForegroundTitle; static void _CheckForWine() { #if BOOST_OS_WINDOWS const HMODULE hmodule = GetModuleHandleA("ntdll.dll"); if (!hmodule) return; const auto pwine_get_version = (const char*(__cdecl*)())GetProcAddress(hmodule, "wine_get_version"); if (pwine_get_version) { cemuLog_log(LogType::Force, "Wine version: {}", pwine_get_version()); } #endif } void logCPUAndMemoryInfo() { std::string cpuName = g_CPUFeatures.GetCPUName(); if (!cpuName.empty()) cemuLog_log(LogType::Force, "CPU: {}", cpuName); #if BOOST_OS_WINDOWS MEMORYSTATUSEX statex; statex.dwLength = sizeof(statex); GlobalMemoryStatusEx(&statex); uint32 memoryInMB = (uint32)(statex.ullTotalPhys / 1024LL / 1024LL); cemuLog_log(LogType::Force, "RAM: {}MB", memoryInMB); #elif BOOST_OS_LINUX struct sysinfo info {}; sysinfo(&info); cemuLog_log(LogType::Force, "RAM: {}MB", ((static_cast<uint64_t>(info.totalram) * info.mem_unit) / 1024LL / 1024LL)); #elif BOOST_OS_MACOS int64_t totalRam; size_t size = sizeof(totalRam); int result = sysctlbyname("hw.memsize", &totalRam, &size, NULL, 0); if (result == 0) cemuLog_log(LogType::Force, "RAM: {}MB", (totalRam / 1024LL / 1024LL)); #elif BOOST_OS_BSD int64_t totalRam; size_t size = sizeof(totalRam); int result = sysctlbyname("hw.physmem", &totalRam, &size, NULL, 0); if (result == 0) cemuLog_log(LogType::Force, "RAM: {}MB", (totalRam / 1024LL / 1024LL)); #endif } #if BOOST_OS_WINDOWS std::string GetWindowsNamedVersion(uint32& buildNumber) { char productName[256]; HKEY hKey; DWORD dwType = REG_SZ; DWORD dwSize = sizeof(productName); if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { if (RegQueryValueExA(hKey, "ProductName", NULL, &dwType, (LPBYTE)productName, &dwSize) != ERROR_SUCCESS) strcpy(productName, "Windows"); RegCloseKey(hKey); } OSVERSIONINFO osvi; ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osvi); buildNumber = osvi.dwBuildNumber; return std::string(productName); } #endif void logPlatformInfo() { std::string buffer; const char* platform = NULL; #if BOOST_OS_WINDOWS uint32 buildNumber; std::string windowsVersionName = GetWindowsNamedVersion(buildNumber); buffer = fmt::format("{} (Build {})", windowsVersionName, buildNumber); platform = buffer.c_str(); #elif BOOST_OS_LINUX if (getenv ("APPIMAGE")) platform = "Linux (AppImage)"; else if (getenv ("SNAP")) platform = "Linux (Snap)"; else if (platform = getenv ("container")) { if (strcmp (platform, "flatpak") == 0) platform = "Linux (Flatpak)"; } else platform = "Linux"; #elif BOOST_OS_MACOS platform = "MacOS"; #elif BOOST_OS_BSD #if defined(__FreeBSD__) platform = "FreeBSD"; #elif defined(__OpenBSD__) platform = "OpenBSD"; #elif defined(__NetBSD__) platform = "NetBSD"; #else platform = "Unknown BSD"; #endif #endif cemuLog_log(LogType::Force, "Platform: {}", platform); } static std::vector<IOSUModule*> s_iosuModules = { // entries in this list are ordered by initialization order. Shutdown in reverse order iosu::kernel::GetModule(), iosu::acp::GetModule(), iosu::fpd::GetModule(), iosu::pdm::GetModule(), iosu::ccr_nfc::GetModule(), iosu::boss::GetModule() }; // initialize all subsystems which are persistent and don't depend on a game running void Initialize() { if (s_initialized) return; s_initialized = true; // init core systems cemuLog_log(LogType::Force, "------- Init {} -------", BUILD_VERSION_WITH_NAME_STRING); fsc_init(); memory_init(); cemuLog_log(LogType::Force, "Init Wii U memory space (base: 0x{:016x})", (size_t)memory_base); PPCCore_init(); RPLLoader_InitState(); cemuLog_log(LogType::Force, "mlc01 path: {}", _pathToUtf8(ActiveSettings::GetMlcPath())); _CheckForWine(); // CPU and RAM info logCPUAndMemoryInfo(); logPlatformInfo(); cemuLog_log(LogType::Force, "Used CPU extensions: {}", g_CPUFeatures.GetCommaSeparatedExtensionList()); // misc systems rplSymbolStorage_init(); // allocate memory for all SysAllocators // must happen before COS module init, but also before iosu::kernel::Initialize() SysAllocatorContainer::GetInstance().Initialize(); // init IOSU modules for(auto& module : s_iosuModules) module->SystemLaunch(); // init IOSU (deprecated manual init) iosuCrypto_init(); iosu::fsa::Initialize(); iosuIoctl_init(); iosuAct_init_depr(); iosu::act::Initialize(); iosu::iosuMcp_init(); iosu::mcp::Init(); iosu::iosuAcp_init(); iosu::nim::Initialize(); iosu::odm::Initialize(); // init hardware register interfaces HW_SI::Initialize(); } void SetImplementation(SystemImplementation* impl) { s_implementation = impl; } void Shutdown() { cemu_assert_debug(s_initialized); // if a title is running, shut it down if (sSystemRunning) ShutdownTitle(); // shutdown persistent subsystems (deprecated manual shutdown) iosu::odm::Shutdown(); iosu::act::Stop(); iosu::mcp::Shutdown(); iosu::fsa::Shutdown(); // shutdown IOSU modules for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it) (*it)->SystemExit(); s_initialized = false; } std::string GetInternalVirtualCodeFolder() { return "/internal/current_title/code/"; } void MountBaseDirectories() { const auto mlc = ActiveSettings::GetMlcPath(); FSCDeviceHostFS_Mount("/cemuBossStorage/", _pathToUtf8(mlc / "usr/boss/"), FSC_PRIORITY_BASE); FSCDeviceHostFS_Mount("/vol/storage_mlc01/", _pathToUtf8(mlc / ""), FSC_PRIORITY_BASE); } void UnmountBaseDirectories() { fsc_unmount("/vol/storage_mlc01/", FSC_PRIORITY_BASE); fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE); } PREPARE_STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId) { cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId); sGameInfo_ForegroundTitle = CafeTitleList::GetGameInfo(titleId); if (!sGameInfo_ForegroundTitle.IsValid()) { cemuLog_log(LogType::Force, "Mounting failed: Game meta information is either missing, inaccessible or not valid (missing or invalid .xml files in code and meta folder)"); return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; } // check base TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase(); if (!titleBase.IsValid()) return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; if(!titleBase.ParseXmlInfo()) return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; cemuLog_log(LogType::Force, "Base: {}", titleBase.GetPrintPath()); // mount base if (!titleBase.Mount("/vol/content", "content", FSC_PRIORITY_BASE) || !titleBase.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_BASE)) { cemuLog_log(LogType::Force, "Mounting failed"); return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; } // check update TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate(); if (titleUpdate.IsValid()) { if (!titleUpdate.ParseXmlInfo()) return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; cemuLog_log(LogType::Force, "Update: {}", titleUpdate.GetPrintPath()); // mount update if (!titleUpdate.Mount("/vol/content", "content", FSC_PRIORITY_PATCH) || !titleUpdate.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_PATCH)) { cemuLog_log(LogType::Force, "Mounting failed"); return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; } } else cemuLog_log(LogType::Force, "Update: Not present"); // check AOC auto aocList = sGameInfo_ForegroundTitle.GetAOC(); if (!aocList.empty()) { // todo - support for multi-title AOC TitleInfo& titleAOC = aocList[0]; if (!titleAOC.ParseXmlInfo()) return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; cemu_assert_debug(titleAOC.IsValid()); cemuLog_log(LogType::Force, "DLC: {}", titleAOC.GetPrintPath()); // mount AOC if (!titleAOC.Mount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId()), "content", FSC_PRIORITY_PATCH)) { cemuLog_log(LogType::Force, "Mounting failed"); return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; } } else cemuLog_log(LogType::Force, "DLC: Not present"); sForegroundTitleId = titleId; return PREPARE_STATUS_CODE::SUCCESS; } void UnmountForegroundTitle() { if(sLaunchModeIsStandalone) return; cemu_assert_debug(sGameInfo_ForegroundTitle.IsValid()); // unmounting title which was never mounted? if (!sGameInfo_ForegroundTitle.IsValid()) return; sGameInfo_ForegroundTitle.GetBase().Unmount("/vol/content"); sGameInfo_ForegroundTitle.GetBase().Unmount(GetInternalVirtualCodeFolder()); if (sGameInfo_ForegroundTitle.HasUpdate()) { if(auto& update = sGameInfo_ForegroundTitle.GetUpdate(); update.IsValid()) { update.Unmount("/vol/content"); update.Unmount(GetInternalVirtualCodeFolder()); } } auto aocList = sGameInfo_ForegroundTitle.GetAOC(); if (!aocList.empty()) { TitleInfo& titleAOC = aocList[0]; titleAOC.Unmount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId())); } } PREPARE_STATUS_CODE PrepareExecutable() { // set rpx path from cos.xml if available _pathToBaseExecutable = _pathToExecutable; if (!sLaunchModeIsStandalone) { std::string _argstr = CafeSystem::GetForegroundTitleArgStr(); const char* argstr = _argstr.c_str(); if (argstr && *argstr != '\0') { const std::string tmp = argstr; const auto index = tmp.find(".rpx"); if (index != std::string::npos) { fs::path rpx = _pathToExecutable; rpx.replace_filename(tmp.substr(0, index + 4)); // cut off after .rpx std::string rpxPath; rpxPath = "/internal/current_title/code/"; rpxPath.append(rpx.generic_string()); int status; const auto file = fsc_open(rpxPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &status); if (file) { _pathToExecutable = std::move(rpxPath); fsc_close(file); } } } } return PREPARE_STATUS_CODE::SUCCESS; } void SetupMemorySpace() { memory_mapForCurrentTitle(); LoadSharedData(); } void DestroyMemorySpace() { memory_unmapForCurrentTitle(); } PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId) { CafeTitleList::WaitForMandatoryScan(); sLaunchModeIsStandalone = false; _pathToExecutable.clear(); TitleIdParser tip(titleId); if (tip.GetType() == TitleIdParser::TITLE_TYPE::AOC || tip.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) cemuLog_log(LogType::Force, "Launched titleId is not the base of a title"); // mount mlc storage MountBaseDirectories(); // mount title folders PREPARE_STATUS_CODE r = LoadAndMountForegroundTitle(titleId); if (r != PREPARE_STATUS_CODE::SUCCESS) return r; gameProfile_load(); // setup memory space and PPC recompiler SetupMemorySpace(); PPCRecompiler_init(); r = PrepareExecutable(); // load RPX if (r != PREPARE_STATUS_CODE::SUCCESS) return r; InitVirtualMlcStorage(); return PREPARE_STATUS_CODE::SUCCESS; } PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path) { sLaunchModeIsStandalone = true; cemuLog_log(LogType::Force, "Launching executable in standalone mode due to incorrect layout or missing meta files"); fs::path executablePath = path; std::string dirName = _pathToUtf8(executablePath.parent_path().filename()); if (boost::iequals(dirName, "code")) { // check for content folder fs::path contentPath = executablePath.parent_path().parent_path().append("content"); std::error_code ec; if (fs::is_directory(contentPath, ec)) { // mounting content folder bool r = FSCDeviceHostFS_Mount(std::string("/vol/content").c_str(), _pathToUtf8(contentPath), FSC_PRIORITY_BASE); if (!r) { cemuLog_log(LogType::Force, "Failed to mount {}", _pathToUtf8(contentPath)); return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; } } } // mount code folder to a virtual temporary path FSCDeviceHostFS_Mount(std::string("/internal/code/").c_str(), _pathToUtf8(executablePath.parent_path()), FSC_PRIORITY_BASE); std::string internalExecutablePath = "/internal/code/"; internalExecutablePath.append(_pathToUtf8(executablePath.filename())); _pathToExecutable = internalExecutablePath; // since a lot of systems (including save folder location) rely on a TitleId, we derive a placeholder id from the executable hash auto execData = fsc_extractFile(_pathToExecutable.c_str()); if (!execData) return PREPARE_STATUS_CODE::INVALID_RPX; uint32 h = generateHashFromRawRPXData(execData->data(), execData->size()); sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h; cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId); // setup memory space and ppc recompiler SetupMemorySpace(); PPCRecompiler_init(); // load executable PrepareExecutable(); InitVirtualMlcStorage(); return PREPARE_STATUS_CODE::SUCCESS; } void _LaunchTitleThread() { for(auto& module : s_iosuModules) module->TitleStart(); cemu_initForGame(); // enter scheduler if ((ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler || LaunchSettings::ForceMultiCoreInterpreter()) && !LaunchSettings::ForceInterpreter()) coreinit::OSSchedulerBegin(3); else coreinit::OSSchedulerBegin(1); } void LaunchForegroundTitle() { PPCTimer_waitForInit(); // start system sSystemRunning = true; WindowSystem::NotifyGameLoaded(); std::thread t(_LaunchTitleThread); t.detach(); } bool IsTitleRunning() { return sSystemRunning; } TitleId GetForegroundTitleId() { cemu_assert_debug(sForegroundTitleId != 0); return sForegroundTitleId; } uint16 GetForegroundTitleVersion() { if (sLaunchModeIsStandalone) return 0; return sGameInfo_ForegroundTitle.GetVersion(); } uint32 GetForegroundTitleSDKVersion() { if (sLaunchModeIsStandalone) return 999999; return sGameInfo_ForegroundTitle.GetSDKVersion(); } CafeConsoleRegion GetForegroundTitleRegion() { if (sLaunchModeIsStandalone) return CafeConsoleRegion::USA; return sGameInfo_ForegroundTitle.GetRegion(); } std::string GetForegroundTitleName() { if (sLaunchModeIsStandalone) return "Unknown Game"; std::string applicationName; applicationName = sGameInfo_ForegroundTitle.GetBase().GetMetaInfo()->GetShortName(GetConfig().console_language); if (applicationName.empty()) //Try to get the English Title applicationName = sGameInfo_ForegroundTitle.GetBase().GetMetaInfo()->GetShortName(CafeConsoleLanguage::EN); if (applicationName.empty()) //Unknown Game applicationName = "Unknown Game"; return applicationName; } uint32 GetForegroundTitleOlvAccesskey() { if (sLaunchModeIsStandalone) return -1; return sGameInfo_ForegroundTitle.GetBase().GetMetaInfo()->GetOlvAccesskey(); } std::string GetForegroundTitleArgStr() { if (sLaunchModeIsStandalone) return ""; auto& update = sGameInfo_ForegroundTitle.GetUpdate(); if (update.IsValid()) return update.GetArgStr(); return sGameInfo_ForegroundTitle.GetBase().GetArgStr(); } CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group) { if (sLaunchModeIsStandalone) return CosCapabilityBits::All; auto& update = sGameInfo_ForegroundTitle.GetUpdate(); if (update.IsValid()) { ParsedCosXml* cosXml = update.GetCosInfo(); if (cosXml) return cosXml->GetCapabilityBits(group); } auto& base = sGameInfo_ForegroundTitle.GetBase(); if(base.IsValid()) { ParsedCosXml* cosXml = base.GetCosInfo(); if (cosXml) return cosXml->GetCapabilityBits(group); } return CosCapabilityBits::All; } // when switching titles custom parameters can be passed, returns true if override args are used bool GetOverrideArgStr(std::vector<std::string>& args) { args.clear(); if(!s_overrideArgs) return false; args = *s_overrideArgs; return true; } void SetOverrideArgs(std::span<std::string> args) { s_overrideArgs = std::vector<std::string>(args.begin(), args.end()); } void UnsetOverrideArgs() { s_overrideArgs = std::nullopt; } // pick platform region based on title region CafeConsoleRegion GetPlatformRegion() { CafeConsoleRegion titleRegion = GetForegroundTitleRegion(); CafeConsoleRegion platformRegion = CafeConsoleRegion::USA; if (HAS_FLAG(titleRegion, CafeConsoleRegion::JPN)) platformRegion = CafeConsoleRegion::JPN; else if (HAS_FLAG(titleRegion, CafeConsoleRegion::EUR)) platformRegion = CafeConsoleRegion::EUR; else if (HAS_FLAG(titleRegion, CafeConsoleRegion::USA)) platformRegion = CafeConsoleRegion::USA; return platformRegion; } void UnmountCurrentTitle() { UnmountForegroundTitle(); fsc_unmount("/internal/code/", FSC_PRIORITY_BASE); } void ShutdownTitle() { if(!sSystemRunning) return; coreinit::OSSchedulerEnd(); Latte_Stop(); // reset Cafe OS userspace modules snd_core::reset(); coreinit::OSAlarm_Shutdown(); GX2::_GX2DriverReset(); nn::save::ResetToDefaultState(); coreinit::__OSDeleteAllActivePPCThreads(); RPLLoader_UnloadAll(); for(auto it = s_iosuModules.rbegin(); it != s_iosuModules.rend(); ++it) (*it)->TitleStop(); // reset Cemu subsystems PPCRecompiler_Shutdown(); GraphicPack2::Reset(); UnmountCurrentTitle(); MlcStorageUnmountAllTitles(); UnmountBaseDirectories(); DestroyMemorySpace(); sSystemRunning = false; } /* Virtual mlc storage */ void InitVirtualMlcStorage() { // starting with Cemu 1.27.0 /vol/storage_mlc01/ is virtualized, meaning that it doesn't point to one singular host os folder anymore // instead it now uses a more complex solution to source titles with various formats (folder, wud, wua) from the game paths and host mlc path // todo - mount /vol/storage_mlc01/ with base priority to the host mlc? // since mounting titles is an expensive operation we have to avoid mounting all titles at once // only the current title gets mounted immediately, every other title should be mounted lazily on first access // always mount the currently running title if (sGameInfo_ForegroundTitle.GetBase().IsValid()) MlcStorageMountTitle(sGameInfo_ForegroundTitle.GetBase()); if (sGameInfo_ForegroundTitle.GetUpdate().IsValid()) MlcStorageMountTitle(sGameInfo_ForegroundTitle.GetUpdate()); for(auto& it : sGameInfo_ForegroundTitle.GetAOC()) MlcStorageMountTitle(it); // setup system for lazy-mounting of other known titles // todo - how to handle this? // when something iterates /vol/storage_mlc01/usr/title/ we can use a fake FS device mounted to /vol/storage_mlc01/usr/title and sys/title that simulates the title id folders // the same device would then have to mount titles when their folders are actually accessed } // /vol/storage_mlc01/<usr or sys>/title/<titleIdHigh>/<titleIdLow> std::string GetMlcStoragePath(TitleId titleId) { TitleIdParser tip(titleId); return fmt::format("/vol/storage_mlc01/{}/title/{:08x}/{:08x}", tip.IsSystemTitle() ? "sys" : "usr", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); } std::map<TitleId, TitleInfo*> m_mlcMountedTitles; // mount title to our virtual MLC storage // /vol/storage_mlc01/<usr or sys>/title/<titleIdHigh>/<titleIdLow> void MlcStorageMountTitle(TitleInfo& titleInfo) { if (!titleInfo.IsValid()) { cemu_assert_suspicious(); return; } TitleId titleId = titleInfo.GetAppTitleId(); if (m_mlcMountedTitles.find(titleId) != m_mlcMountedTitles.end()) return; std::string mlcStoragePath = GetMlcStoragePath(titleId); TitleInfo* mountTitleInfo = new TitleInfo(titleInfo); if (!mountTitleInfo->Mount(mlcStoragePath, "", FSC_PRIORITY_BASE)) { cemuLog_log(LogType::Force, "Failed to mount title to virtual storage"); delete mountTitleInfo; return; } m_mlcMountedTitles.emplace(titleId, mountTitleInfo); } void MlcStorageMountTitle(TitleId titleId) { TitleInfo titleInfo; if (!CafeTitleList::GetFirstByTitleId(titleId, titleInfo)) return; MlcStorageMountTitle(titleInfo); } void MlcStorageMountAllTitles() { std::vector<uint64> titleIds = CafeTitleList::GetAllTitleIds(); for (auto& it : titleIds) MlcStorageMountTitle(it); } void MlcStorageUnmountAllTitles() { for(auto& it : m_mlcMountedTitles) { std::string mlcStoragePath = GetMlcStoragePath(it.first); it.second->Unmount(mlcStoragePath); } m_mlcMountedTitles.clear(); } uint32 GetRPXHashBase() { return currentBaseApplicationHash; } uint32 GetRPXHashUpdated() { return currentUpdatedApplicationHash; } void RequestRecreateCanvas() { s_implementation->CafeRecreateCanvas(); } } ================================================ FILE: src/Cafe/CafeSystem.h ================================================ #pragma once #include "Cafe/OS/RPL/rpl.h" #include "util/helpers/Semaphore.h" #include "Cafe/TitleList/TitleId.h" #include "config/CemuConfig.h" enum class CosCapabilityBits : uint64; enum class CosCapabilityGroup : uint32; namespace CafeSystem { class SystemImplementation { public: virtual void CafeRecreateCanvas() = 0; }; enum class PREPARE_STATUS_CODE { SUCCESS, INVALID_RPX, UNABLE_TO_MOUNT, // failed to mount through TitleInfo (most likely caused by an invalid or outdated path) }; void Initialize(); void SetImplementation(SystemImplementation* impl); void Shutdown(); PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId); PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path); void LaunchForegroundTitle(); bool IsTitleRunning(); bool GetOverrideArgStr(std::vector<std::string>& args); void SetOverrideArgs(std::span<std::string> args); void UnsetOverrideArgs(); TitleId GetForegroundTitleId(); uint16 GetForegroundTitleVersion(); uint32 GetForegroundTitleSDKVersion(); CafeConsoleRegion GetForegroundTitleRegion(); CafeConsoleRegion GetPlatformRegion(); std::string GetForegroundTitleName(); std::string GetForegroundTitleArgStr(); uint32 GetForegroundTitleOlvAccesskey(); CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group); void ShutdownTitle(); std::string GetMlcStoragePath(TitleId titleId); void MlcStorageMountAllTitles(); std::string GetInternalVirtualCodeFolder(); uint32 GetRPXHashBase(); uint32 GetRPXHashUpdated(); void RequestRecreateCanvas(); }; extern RPLModule* applicationRPX; extern std::atomic_bool g_isGPUInitFinished; ================================================ FILE: src/Cafe/Filesystem/FST/FST.cpp ================================================ #include "Common/precompiled.h" #include "Common/FileStream.h" #include "Cemu/ncrypto/ncrypto.h" #include "Cafe/Filesystem/WUD/wud.h" #include "util/crypto/aes128.h" #include "openssl/sha.h" /* SHA1 / SHA256 */ #include "fstUtil.h" #include "FST.h" #include "KeyCache.h" #include "boost/range/adaptor/reversed.hpp" #define SET_FST_ERROR(__code) if (errorCodeOut) *errorCodeOut = ErrorCode::__code static_assert(sizeof(NCrypto::AesIv) == 16); // make sure IV is actually 16 bytes class FSTDataSource { public: virtual uint64 readData(uint16 clusterIndex, uint64 clusterOffset, uint64 offset, void* data, uint64 size) = 0; virtual ~FSTDataSource() {}; protected: FSTDataSource() {}; bool m_isOpen; }; class FSTDataSourceWUD : public FSTDataSource { public: static FSTDataSourceWUD* Open(const fs::path& path) { wud_t* wudFile = wud_open(path); if (!wudFile) return nullptr; FSTDataSourceWUD* ds = new FSTDataSourceWUD(); ds->m_wudFile = wudFile; return ds; } void SetBaseOffset(uint64 baseOffset) { m_baseOffset = baseOffset; } uint64 GetBaseOffset() const { return m_baseOffset; } uint64 readData(uint16 clusterIndex, uint64 clusterOffset, uint64 offset, void* data, uint64 size) override { cemu_assert_debug(size <= 0xFFFFFFFF); return wud_readData(m_wudFile, data, (uint32)size, clusterOffset + offset + m_baseOffset); } ~FSTDataSourceWUD() override { if(m_wudFile) wud_close(m_wudFile); } protected: FSTDataSourceWUD() {} wud_t* m_wudFile; uint64 m_baseOffset{}; std::vector<uint64> m_clusterOffset; }; class FSTDataSourceApp : public FSTDataSource { public: static FSTDataSourceApp* Open(fs::path path, NCrypto::TMDParser& tmd) { std::vector<std::unique_ptr<FileStream>> clusterFile; uint32 maxIndex = 0; for (auto& itr : tmd.GetContentList()) maxIndex = std::max(maxIndex, (uint32)itr.index); clusterFile.resize(maxIndex + 1); // open all the app files for (auto& itr : tmd.GetContentList()) { FileStream* appFile = FileStream::openFile2(path / fmt::format("{:08x}.app", itr.contentId)); if (!appFile) return nullptr; clusterFile[itr.index].reset(appFile); } // construct FSTDataSourceApp FSTDataSourceApp* dsApp = new FSTDataSourceApp(std::move(clusterFile)); return dsApp; } uint64 readData(uint16 clusterIndex, uint64 clusterOffset, uint64 offset, void* data, uint64 size) override { // ignore clusterOffset for .app files since each file is already relative to the cluster base cemu_assert_debug(clusterIndex < m_clusterFile.size()); cemu_assert_debug(m_clusterFile[clusterIndex].get()); cemu_assert_debug(size <= 0xFFFFFFFF); if (!m_clusterFile[clusterIndex].get()) return 0; m_clusterFile[clusterIndex].get()->SetPosition(offset); return m_clusterFile[clusterIndex].get()->readData(data, (uint32)size); } ~FSTDataSourceApp() override { } private: FSTDataSourceApp(std::vector<std::unique_ptr<FileStream>>&& clusterFiles) { m_clusterFile = std::move(clusterFiles); } std::vector<std::unique_ptr<FileStream>> m_clusterFile; }; constexpr size_t DISC_SECTOR_SIZE = 0x8000; struct DiscHeaderA { // header in first sector (0x0) uint8 productCode[22]; // ? }; struct DiscHeaderB { // header at 0x10000 static constexpr uint32 MAGIC_VALUE = 0xCC549EB9; /* +0x00 */ uint32be magic; }; static_assert(sizeof(DiscHeaderB) == 0x04); struct DiscPartitionTableHeader { // header at 0x18000, encrypted static constexpr uint32 MAGIC_VALUE = 0xCCA6E67B; /* +0x00 */ uint32be magic; /* +0x04 */ uint32be blockSize; // must be 0x8000? /* +0x08 */ uint8 partitionTableHash[20]; // hash of the data range at +0x800 to end of sector (0x8000) /* +0x1C */ uint32be numPartitions; }; static_assert(sizeof(DiscPartitionTableHeader) == 0x20); struct DiscPartitionTableEntry { /* +0x00 */ uint8be partitionName[31]; /* +0x1F */ uint8be numAddresses; // ? /* +0x20 */ uint32be partitionAddress; // this is an array? /* +0x24 */ uint8 padding[0x80 - 0x24]; }; static_assert(sizeof(DiscPartitionTableEntry) == 0x80); struct DiscPartitionHeader { // header at the beginning of each partition static constexpr uint32 MAGIC_VALUE = 0xCC93A4F5; /* +0x00 */ uint32be magic; /* +0x04 */ uint32be sectorSize; // must match DISC_SECTOR_SIZE for hashed blocks /* +0x08 */ uint32be ukn008; /* +0x0C */ uint32be ukn00C; // h3 array size? /* +0x10 */ uint32be h3HashNum; /* +0x14 */ uint32be fstSize; // in bytes /* +0x18 */ uint32be fstSector; // relative to partition start /* +0x1C */ uint32be ukn01C; /* +0x20 */ uint32be ukn020; // the hash and encryption mode for the FST cluster /* +0x24 */ uint8 fstHashType; /* +0x25 */ uint8 fstEncryptionType; // purpose of this isn't really understood. Maybe it controls which key is being used? (1 -> disc key, 2 -> partition key) /* +0x26 */ uint8be versionA; /* +0x27 */ uint8be ukn027; // also a version field? // there is an array at +0x40 ? Related to H3 list. Also related to value at +0x0C and h3HashNum /* +0x28 */ uint8be _uknOrPadding028[0x18]; /* +0x40 */ uint8be h3HashArray[32]; // dynamic size. Only present if fstHashType != 0 }; static_assert(sizeof(DiscPartitionHeader) == 0x40+0x20); bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey) { std::unique_ptr<FSTDataSourceWUD> dataSource(FSTDataSourceWUD::Open(path)); if (!dataSource) return false; // read section of header which should only contain zero bytes if decrypted uint8 header[16*3]; if (dataSource->readData(0, 0, 0x18000 + 0x100, header, sizeof(header)) != sizeof(header)) return false; // try all the keys uint8 headerDecrypted[sizeof(header)-16]; for (sint32 i = 0; i < 0x7FFFFFFF; i++) { uint8* key128 = KeyCache_GetAES128(i); if (key128 == NULL) break; AES128_CBC_decrypt(headerDecrypted, header + 16, sizeof(headerDecrypted), key128, header); if (std::all_of(headerDecrypted, headerDecrypted + sizeof(headerDecrypted), [](const uint8 v) {return v == 0; })) { // key found std::memcpy(discTitleKey.b, key128, 16); return true; } } return false; } // open WUD image using key cache // if no matching key is found then keyFound will return false FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, ErrorCode* errorCodeOut) { SET_FST_ERROR(UNKNOWN_ERROR); KeyCache_Prepare(); NCrypto::AesKey discTitleKey; if (!FindDiscKey(path, discTitleKey)) { SET_FST_ERROR(DISC_KEY_MISSING); return nullptr; } return OpenFromDiscImage(path, discTitleKey, errorCodeOut); } // open WUD image FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey, ErrorCode* errorCodeOut) { // WUD images support multiple partitions, each with their own key and FST // the process for loading game data FSTVolume from a WUD image is as follows: // 1) parse WUD headers and verify // 2) read SI partition FST // 3) find main GM partition // 4) use SI information to get titleKey for GM partition // 5) Load FST for GM SET_FST_ERROR(UNKNOWN_ERROR); std::unique_ptr<FSTDataSourceWUD> dataSource(FSTDataSourceWUD::Open(path)); if (!dataSource) return nullptr; // check HeaderA (only contains product code?) DiscHeaderA headerA{}; if (dataSource->readData(0, 0, 0, &headerA, sizeof(headerA)) != sizeof(headerA)) return nullptr; // check HeaderB DiscHeaderB headerB{}; if (dataSource->readData(0, 0, DISC_SECTOR_SIZE * 2, &headerB, sizeof(headerB)) != sizeof(headerB)) return nullptr; if (headerB.magic != headerB.MAGIC_VALUE) return nullptr; // read, decrypt and parse partition table uint8 partitionSector[DISC_SECTOR_SIZE]; if (dataSource->readData(0, 0, DISC_SECTOR_SIZE * 3, partitionSector, DISC_SECTOR_SIZE) != DISC_SECTOR_SIZE) return nullptr; uint8 iv[16]{}; AES128_CBC_decrypt(partitionSector, partitionSector, DISC_SECTOR_SIZE, discTitleKey.b, iv); // parse partition info DiscPartitionTableHeader* partitionHeader = (DiscPartitionTableHeader*)partitionSector; if (partitionHeader->magic != DiscPartitionTableHeader::MAGIC_VALUE) { cemuLog_log(LogType::Force, "Disc image rejected because decryption failed"); return nullptr; } if (partitionHeader->blockSize != DISC_SECTOR_SIZE) { cemuLog_log(LogType::Force, "Disc image rejected because partition sector size is invalid"); return nullptr; } uint32 numPartitions = partitionHeader->numPartitions; if (numPartitions > 30) // there is space for up to 240 partitions but we use a more reasonable limit { cemuLog_log(LogType::Force, "Disc image rejected due to exceeding the partition limit (has {} partitions)", numPartitions); return nullptr; } DiscPartitionTableEntry* partitionArray = (DiscPartitionTableEntry*)(partitionSector + 0x800); // validate partitions and find SI partition uint32 siPartitionIndex = std::numeric_limits<uint32>::max(); uint32 gmPartitionIndex = std::numeric_limits<uint32>::max(); for (uint32 i = 0; i < numPartitions; i++) { if (partitionArray[i].numAddresses != 1) { cemuLog_log(LogType::Force, "Disc image has unsupported partition with {} addresses", (uint32)partitionArray[i].numAddresses); return nullptr; } auto& name = partitionArray[i].partitionName; if (name[0] == 'S' && name[1] == 'I') { if (siPartitionIndex != std::numeric_limits<uint32>::max()) { cemuLog_log(LogType::Force, "Disc image has multiple SI partitions. Not supported"); return nullptr; } siPartitionIndex = i; } if (name[0] == 'G' && name[1] == 'M') { if (gmPartitionIndex == std::numeric_limits<uint32>::max()) gmPartitionIndex = i; // we use the first GM partition we find. This is likely not correct but it seems to work for practically all disc images } } if (siPartitionIndex == std::numeric_limits<uint32>::max() || gmPartitionIndex == std::numeric_limits<uint32>::max()) { cemuLog_log(LogType::Force, "Disc image has no SI or GM partition. Cannot read game data"); return nullptr; } // read and verify partition headers for SI and GM auto readPartitionHeader = [&](DiscPartitionHeader& partitionHeader, uint32 partitionIndex) -> bool { cemu_assert_debug(dataSource->GetBaseOffset() == 0); if (dataSource->readData(0, 0, partitionArray[partitionIndex].partitionAddress * DISC_SECTOR_SIZE, &partitionHeader, sizeof(DiscPartitionHeader)) != sizeof(DiscPartitionHeader)) return false; if (partitionHeader.magic != partitionHeader.MAGIC_VALUE && partitionHeader.sectorSize != DISC_SECTOR_SIZE) return false; return true; }; // SI partition DiscPartitionHeader partitionHeaderSI{}; if (!readPartitionHeader(partitionHeaderSI, siPartitionIndex)) { cemuLog_log(LogType::Force, "Disc image SI partition header is invalid"); return nullptr; } cemu_assert_debug(partitionHeaderSI.fstHashType == 0); cemu_assert_debug(partitionHeaderSI.fstEncryptionType == 1); // todo - check other fields? if(partitionHeaderSI.fstHashType == 0 && partitionHeaderSI.h3HashNum != 0) cemuLog_log(LogType::Force, "FST: Partition uses unhashed blocks but stores a non-zero amount of H3 hashes"); // GM partition DiscPartitionHeader partitionHeaderGM{}; if (!readPartitionHeader(partitionHeaderGM, gmPartitionIndex)) { cemuLog_log(LogType::Force, "Disc image GM partition header is invalid"); return nullptr; } cemu_assert_debug(partitionHeaderGM.fstHashType == 1); cemu_assert_debug(partitionHeaderGM.fstEncryptionType == 2); // if decryption is necessary // load SI FST dataSource->SetBaseOffset((uint64)partitionArray[siPartitionIndex].partitionAddress * DISC_SECTOR_SIZE); auto siFST = OpenFST(dataSource.get(), (uint64)partitionHeaderSI.fstSector * DISC_SECTOR_SIZE, partitionHeaderSI.fstSize, &discTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderSI.fstHashType), nullptr); if (!siFST) return nullptr; cemu_assert_debug(!(siFST->HashIsDisabled() && partitionHeaderSI.h3HashNum != 0)); // if hash is disabled, no H3 data may be present // load ticket file for partition that we want to decrypt NCrypto::ETicketParser ticketParser; std::vector<uint8> ticketData = siFST->ExtractFile(fmt::format("{:02x}/title.tik", gmPartitionIndex)); if (ticketData.empty() || !ticketParser.parse(ticketData.data(), ticketData.size())) { cemuLog_log(LogType::Force, "Disc image ticket file is invalid"); return nullptr; } #if 0 // each SI partition seems to contain a title.tmd that we could parse and which should have information about the associated GM partition // but the console seems to ignore this file for disc images, at least when mounting, so we shouldn't rely on it either std::vector<uint8> tmdData = siFST->ExtractFile(fmt::format("{:02x}/title.tmd", gmPartitionIndex)); if (tmdData.empty()) { cemuLog_log(LogType::Force, "Disc image TMD file is missing"); return nullptr; } // parse TMD NCrypto::TMDParser tmdParser; if (!tmdParser.parse(tmdData.data(), tmdData.size())) { cemuLog_log(LogType::Force, "Disc image TMD file is invalid"); return nullptr; } #endif delete siFST; NCrypto::AesKey gmTitleKey; ticketParser.GetTitleKey(gmTitleKey); // load GM partition dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE); FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType), nullptr); if (r) SET_FST_ERROR(OK); cemu_assert_debug(!(r->HashIsDisabled() && partitionHeaderGM.h3HashNum != 0)); // if hash is disabled, no H3 data may be present return r; } FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath, ErrorCode* errorCodeOut) { SET_FST_ERROR(UNKNOWN_ERROR); // load TMD FileStream* tmdFile = FileStream::openFile2(folderPath / "title.tmd"); if (!tmdFile) return nullptr; std::vector<uint8> tmdData; tmdFile->extract(tmdData); delete tmdFile; NCrypto::TMDParser tmdParser; if (!tmdParser.parse(tmdData.data(), tmdData.size())) { SET_FST_ERROR(BAD_TITLE_TMD); return nullptr; } // load ticket FileStream* ticketFile = FileStream::openFile2(folderPath / "title.tik"); if (!ticketFile) { SET_FST_ERROR(TITLE_TIK_MISSING); return nullptr; } std::vector<uint8> ticketData; ticketFile->extract(ticketData); delete ticketFile; NCrypto::ETicketParser ticketParser; if (!ticketParser.parse(ticketData.data(), ticketData.size())) { SET_FST_ERROR(BAD_TITLE_TIK); return nullptr; } NCrypto::AesKey titleKey; ticketParser.GetTitleKey(titleKey); // open data source std::unique_ptr<FSTDataSource> dataSource(FSTDataSourceApp::Open(folderPath, tmdParser)); if (!dataSource) return nullptr; // get info about FST from first cluster (todo - is this correct or does the TMD store info about the fst?) ClusterHashMode fstHashMode = ClusterHashMode::RAW; uint32 fstSize = 0; for (auto& itr : tmdParser.GetContentList()) { if (itr.index == 0) { if (HAS_FLAG(itr.contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_HASHED_CONTENT)) fstHashMode = ClusterHashMode::HASH_INTERLEAVED; cemu_assert_debug(itr.size <= 0xFFFFFFFF); fstSize = (uint32)itr.size; } } // load FST // fstSize = size of first cluster? FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode, &tmdParser); if (fstVolume) SET_FST_ERROR(OK); return fstVolume; } FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD) { cemu_assert_debug(fstHashMode != ClusterHashMode::RAW || fstHashMode != ClusterHashMode::RAW_STREAM); if (fstSize < sizeof(FSTHeader)) return nullptr; constexpr uint64 FST_CLUSTER_OFFSET = 0; uint32 fstSizePadded = (fstSize + 15) & ~15; // pad to AES block size // read FST data and decrypt std::vector<uint8> fstData(fstSizePadded); if (dataSource->readData(0, FST_CLUSTER_OFFSET, fstOffset, fstData.data(), fstSizePadded) != fstSizePadded) return nullptr; uint8 iv[16]{}; AES128_CBC_decrypt(fstData.data(), fstData.data(), fstSizePadded, partitionTitleKey->b, iv); // validate header FSTHeader* fstHeader = (FSTHeader*)fstData.data(); const void* fstEnd = fstData.data() + fstSize; if (fstHeader->magic != 0x46535400 || fstHeader->numCluster >= 0x1000) { cemuLog_log(LogType::Force, "FST has invalid header"); return nullptr; } // load cluster table uint32 numCluster = fstHeader->numCluster; FSTHeader_ClusterEntry* clusterDataTable = (FSTHeader_ClusterEntry*)(fstData.data() + sizeof(FSTHeader)); if ((clusterDataTable + numCluster) > fstEnd) return nullptr; std::vector<FSTCluster> clusterTable; clusterTable.resize(numCluster); for (size_t i = 0; i < numCluster; i++) { clusterTable[i].offset = clusterDataTable[i].offset; clusterTable[i].size = clusterDataTable[i].size; clusterTable[i].hashMode = static_cast<FSTVolume::ClusterHashMode>((uint8)clusterDataTable[i].hashMode); clusterTable[i].hasContentHash = false; // from the TMD file (H4?) } // if the TMD is available (when opening .app files) we can use the extra info from it to validate unhashed clusters // each content entry in the TMD corresponds to one cluster used by the FST if(optionalTMD) { if(numCluster != optionalTMD->GetContentList().size()) { cemuLog_log(LogType::Force, "FST: Number of clusters does not match TMD content list"); return nullptr; } auto& contentList = optionalTMD->GetContentList(); for(size_t i=0; i<contentList.size(); i++) { auto& cluster = clusterTable[i]; auto& content = contentList[i]; cluster.hasContentHash = true; cluster.contentHashIsSHA1 = HAS_FLAG(contentList[i].contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_SHA1); cluster.contentSize = content.size; static_assert(sizeof(content.hash32) == sizeof(cluster.contentHash32)); memcpy(cluster.contentHash32, content.hash32, sizeof(cluster.contentHash32)); // if unhashed mode, then initialize the hash context if(cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW_STREAM) { cluster.singleHashCtx.reset(EVP_MD_CTX_new()); EVP_DigestInit_ex(cluster.singleHashCtx.get(), cluster.contentHashIsSHA1 ? EVP_sha1() : EVP_sha256(), nullptr); } } } // preprocess FST table FSTHeader_FileEntry* fileTable = (FSTHeader_FileEntry*)(clusterDataTable + numCluster); if ((fileTable + 1) > fstEnd) return nullptr; if (fileTable[0].GetType() != FSTHeader_FileEntry::TYPE::DIRECTORY) return nullptr; uint32 numFileEntries = fileTable[0].size; if (numFileEntries == 0 || (fileTable + numFileEntries) > fstEnd) return nullptr; // load name string table ptrdiff_t nameLookupTableSize = ((const uint8*)fstEnd - (const uint8*)(fileTable + numFileEntries)); if (nameLookupTableSize < 1) return nullptr; std::vector<char> nameStringTable(nameLookupTableSize); std::memcpy(nameStringTable.data(), (fileTable + numFileEntries), nameLookupTableSize); // process FST std::vector<FSTEntry> fstEntries; if (!ProcessFST(fileTable, numFileEntries, numCluster, nameStringTable, fstEntries)) return nullptr; // construct FSTVolume from the processed data FSTVolume* fstVolume = new FSTVolume(); fstVolume->m_dataSource = dataSource; fstVolume->m_offsetFactor = fstHeader->offsetFactor; fstVolume->m_sectorSize = DISC_SECTOR_SIZE; fstVolume->m_partitionTitlekey = *partitionTitleKey; fstVolume->m_hashIsDisabled = fstHeader->hashIsDisabled != 0; fstVolume->m_cluster = std::move(clusterTable); fstVolume->m_entries = std::move(fstEntries); fstVolume->m_nameStringTable = std::move(nameStringTable); return fstVolume; } FSTVolume* FSTVolume::OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD) { FSTDataSource* ds = dataSource.release(); FSTVolume* fstVolume = OpenFST(ds, fstOffset, fstSize, partitionTitleKey, fstHashMode, optionalTMD); if (!fstVolume) { delete ds; return nullptr; } fstVolume->m_sourceIsOwned = true; return fstVolume; } bool FSTVolume::ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries) { struct DirHierachyInfo { DirHierachyInfo(uint32 parentIndex, uint32 endIndex) : parentIndex(parentIndex), endIndex(endIndex) {}; uint32 parentIndex; uint32 endIndex; }; std::vector<DirHierachyInfo> currentDirEnd; currentDirEnd.reserve(32); currentDirEnd.emplace_back(0, numFileEntries); // create a fake parent for the root directory, the root's parent index is zero (referencing itself) uint32 currentIndex = 0; FSTHeader_FileEntry* pFileIn = fileTable + currentIndex; fstEntries.resize(numFileEntries); FSTEntry* pFileOut = fstEntries.data(); // validate root directory if (pFileIn->GetType() != FSTHeader_FileEntry::TYPE::DIRECTORY || pFileIn->GetDirectoryEndIndex() != numFileEntries || pFileIn->GetDirectoryParent() != 0) { cemuLog_log(LogType::Force, "FSTVolume::ProcessFST() - root node is invalid"); return false; } for (; currentIndex < numFileEntries; currentIndex++) { while (currentIndex >= currentDirEnd.back().endIndex) currentDirEnd.pop_back(); // process entry name uint32 nameOffset = pFileIn->GetNameOffset(); uint32 pos = nameOffset; while (true) { if (pos >= nameStringTable.size()) return false; // name exceeds string table if (nameStringTable[pos] == '\0') break; pos++; } uint32 nameLen = pos - nameOffset; pFileOut->nameOffset = nameOffset; pFileOut->nameHash = _QuickNameHash(nameStringTable.data() + nameOffset, nameLen); // parent directory index pFileOut->parentDirIndex = currentDirEnd.back().parentIndex; //if (currentDirEnd.back().parentIndex == 0) // pFileOut->parentDirIndex = std::numeric_limits<uint32>::max(); //else // pFileOut->parentDirIndex = currentDirEnd.back().parentIndex; // process type specific data auto entryType = pFileIn->GetType(); uint8 flags = 0; if (pFileIn->HasFlagLink()) flags |= FSTEntry::FLAG_LINK; if (pFileIn->HasUknFlag02()) flags |= FSTEntry::FLAG_UKN02; pFileOut->SetFlags((FSTEntry::FLAGS)flags); if (entryType == FSTHeader_FileEntry::TYPE::FILE) { bool isSysLink = entryType == FSTHeader_FileEntry::TYPE::FILE; if (pFileIn->clusterIndex >= numCluster) { cemuLog_log(LogType::Force, "FST: File references cluster out of range"); return false; } cemu_assert_debug(pFileIn->flagsOrPermissions != 0x4004); pFileOut->SetType(FSTEntry::TYPE::FILE); pFileOut->fileInfo.fileOffset = pFileIn->offset; pFileOut->fileInfo.fileSize = pFileIn->size; pFileOut->fileInfo.clusterIndex = pFileIn->clusterIndex; } else if (entryType == FSTHeader_FileEntry::TYPE::DIRECTORY) { cemu_assert_debug(pFileIn->flagsOrPermissions != 0x4004); pFileOut->SetType(FSTEntry::TYPE::DIRECTORY); uint32 endIndex = pFileIn->GetDirectoryEndIndex(); uint32 parentIndex = pFileIn->GetDirectoryParent(); if (endIndex < currentIndex || endIndex > currentDirEnd.back().endIndex) { cemuLog_log(LogType::Force, "FST: Directory range out of bounds"); return false; // dir index out of range } if (parentIndex != currentDirEnd.back().parentIndex) { cemuLog_log(LogType::Force, "FST: Parent index does not match"); cemu_assert_debug(false); return false; } currentDirEnd.emplace_back(currentIndex, endIndex); pFileOut->dirInfo.endIndex = endIndex; } else { cemuLog_log(LogType::Force, "FST: Encountered node with unknown type"); cemu_assert_debug(false); return false; } pFileIn++; pFileOut++; } // end remaining directory hierarchy with final index cemu_assert_debug(currentIndex == numFileEntries); while (!currentDirEnd.empty() && currentIndex >= currentDirEnd.back().endIndex) currentDirEnd.pop_back(); cemu_assert_debug(currentDirEnd.empty()); // no entries should remain return true; } uint32 FSTVolume::GetFileCount() const { uint32 fileCount = 0; for (auto& itr : m_entries) { if (itr.GetType() == FSTEntry::TYPE::FILE) fileCount++; } return fileCount; } bool FSTVolume::OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles) { FSCPath fscPath(path); if (fscPath.GetNodeCount() == 0) { // empty path pointers to root directory if(openOnlyFiles) return false; fileHandleOut.m_fstIndex = 0; return true; } // scan directory and find sub folder or file // skips iterating subdirectories auto findSubentry = [this](size_t firstIndex, size_t lastIndex, std::string_view nodeName) -> sint32 { uint16 nodeHash = _QuickNameHash(nodeName.data(), nodeName.size()); size_t index = firstIndex; while (index < lastIndex) { if (m_entries[index].nameHash == nodeHash && MatchFSTEntryName(m_entries[index], nodeName)) return (sint32)index; if (m_entries[index].GetType() == FSTEntry::TYPE::DIRECTORY) index = m_entries[index].dirInfo.endIndex; else index++; } return -1; }; // current FST range we iterate, starting with root directory which covers all entries uint32 parentIndex = std::numeric_limits<uint32>::max(); size_t curDirStart = 1; // skip root directory size_t curDirEnd = m_entries[0].dirInfo.endIndex; // find the subdirectory for (size_t nodeIndex = 0; nodeIndex < fscPath.GetNodeCount() - 1; nodeIndex++) { // get hash of node name sint32 fstIndex = findSubentry(curDirStart, curDirEnd, fscPath.GetNodeName(nodeIndex)); if (fstIndex < 0) return false; if (m_entries[fstIndex].GetType() != FSTEntry::TYPE::DIRECTORY) return false; parentIndex = fstIndex; curDirStart = fstIndex + 1; curDirEnd = m_entries[fstIndex].dirInfo.endIndex; } // find the entry sint32 fstIndex = findSubentry(curDirStart, curDirEnd, fscPath.GetNodeName(fscPath.GetNodeCount() - 1)); if (fstIndex < 0) return false; if (openOnlyFiles && m_entries[fstIndex].GetType() != FSTEntry::TYPE::FILE) return false; fileHandleOut.m_fstIndex = fstIndex; return true; } bool FSTVolume::IsDirectory(const FSTFileHandle& fileHandle) const { cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size()); return m_entries[fileHandle.m_fstIndex].GetType() == FSTEntry::TYPE::DIRECTORY; }; bool FSTVolume::IsFile(const FSTFileHandle& fileHandle) const { cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size()); return m_entries[fileHandle.m_fstIndex].GetType() == FSTEntry::TYPE::FILE; }; bool FSTVolume::HasLinkFlag(const FSTFileHandle& fileHandle) const { cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size()); return HAS_FLAG(m_entries[fileHandle.m_fstIndex].GetFlags(), FSTEntry::FLAGS::FLAG_LINK); }; std::string_view FSTVolume::GetName(const FSTFileHandle& fileHandle) const { if (fileHandle.m_fstIndex > m_entries.size()) return ""; const char* entryName = m_nameStringTable.data() + m_entries[fileHandle.m_fstIndex].nameOffset; return entryName; } std::string FSTVolume::GetPath(const FSTFileHandle& fileHandle) const { std::string path; auto& entry = m_entries[fileHandle.m_fstIndex]; // get parent chain boost::container::small_vector<uint32, 8> parentChain; if (entry.HasNonRootNodeParent()) { parentChain.emplace_back(entry.parentDirIndex); auto* parentItr = &m_entries[entry.parentDirIndex]; while (parentItr->HasNonRootNodeParent()) { cemu_assert_debug(parentItr->GetType() == FSTEntry::TYPE::DIRECTORY); parentChain.emplace_back(parentItr->parentDirIndex); parentItr = &m_entries[parentItr->parentDirIndex]; } } // build path cemu_assert_debug(parentChain.size() <= 1); // test this case for (auto& itr : parentChain | boost::adaptors::reversed) { const char* name = m_nameStringTable.data() + m_entries[itr].nameOffset; path.append(name); path.push_back('/'); } // append node name const char* name = m_nameStringTable.data() + entry.nameOffset; path.append(name); return path; } uint32 FSTVolume::GetFileSize(const FSTFileHandle& fileHandle) const { if (m_entries[fileHandle.m_fstIndex].GetType() != FSTEntry::TYPE::FILE) return 0; return m_entries[fileHandle.m_fstIndex].fileInfo.fileSize; } uint32 FSTVolume::ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size, void* dataOut) { FSTEntry& entry = m_entries[fileHandle.m_fstIndex]; if (entry.GetType() != FSTEntry::TYPE::FILE) return 0; cemu_assert_debug(!HAS_FLAG(entry.GetFlags(), FSTEntry::FLAGS::FLAG_LINK)); FSTCluster& cluster = m_cluster[entry.fileInfo.clusterIndex]; if (cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW_STREAM) return ReadFile_HashModeRaw(entry.fileInfo.clusterIndex, entry, offset, size, dataOut); else if (cluster.hashMode == ClusterHashMode::HASH_INTERLEAVED) return ReadFile_HashModeHashed(entry.fileInfo.clusterIndex, entry, offset, size, dataOut); cemu_assert_debug(false); return 0; } constexpr size_t BLOCK_SIZE = 0x10000; constexpr size_t BLOCK_HASH_SIZE = 0x0400; constexpr size_t BLOCK_FILE_SIZE = 0xFC00; struct FSTRawBlock { std::vector<uint8> rawData; // unhashed block size depends on sector size field in partition header }; struct FSTHashedBlock { uint8 rawData[BLOCK_SIZE]; uint8* getHashData() { return rawData; } uint8* getH0Hash(uint32 index) { cemu_assert_debug(index < 16); return getHashData() + 20 * index; } uint8* getH1Hash(uint32 index) { cemu_assert_debug(index < 16); return getHashData() + (20 * 16) * 1 + 20 * index; } uint8* getH2Hash(uint32 index) { cemu_assert_debug(index < 16); return getHashData() + (20 * 16) * 2 + 20 * index; } uint8* getFileData() { return rawData + BLOCK_HASH_SIZE; } uint8* getH0Hash(size_t index) { cemu_assert_debug(index < 16); return rawData + index * 20; } }; static_assert(sizeof(FSTHashedBlock) == BLOCK_SIZE); struct FSTCachedRawBlock { FSTRawBlock blockData; NCrypto::AesIv ivForNextBlock; uint64 lastAccess; }; struct FSTCachedHashedBlock { FSTHashedBlock blockData; uint64 lastAccess; }; // Checks cache fill state and if necessary drops least recently accessed block from the cache. Optionally allows to recycle the released cache entry to cut down cost of memory allocation and clearing void FSTVolume::TrimCacheIfRequired(FSTCachedRawBlock** droppedRawBlock, FSTCachedHashedBlock** droppedHashedBlock) { // calculate size used by cache size_t cacheSize = 0; for (auto& itr : m_cacheDecryptedRawBlocks) cacheSize += itr.second->blockData.rawData.size(); for (auto& itr : m_cacheDecryptedHashedBlocks) cacheSize += sizeof(FSTCachedHashedBlock) + sizeof(FSTHashedBlock); // only trim if cache is full (larger than 2MB) if (cacheSize < 2*1024*1024) // 2MB return; // scan both cache lists to find least recently accessed block to drop auto dropRawItr = std::min_element(m_cacheDecryptedRawBlocks.begin(), m_cacheDecryptedRawBlocks.end(), [](const auto& a, const auto& b) -> bool { return a.second->lastAccess < b.second->lastAccess; }); auto dropHashedItr = std::min_element(m_cacheDecryptedHashedBlocks.begin(), m_cacheDecryptedHashedBlocks.end(), [](const auto& a, const auto& b) -> bool { return a.second->lastAccess < b.second->lastAccess; }); uint64 lastAccess = std::numeric_limits<uint64>::max(); if(dropRawItr != m_cacheDecryptedRawBlocks.end()) lastAccess = dropRawItr->second->lastAccess; if(dropHashedItr != m_cacheDecryptedHashedBlocks.end()) lastAccess = std::min<uint64>(lastAccess, dropHashedItr->second->lastAccess); if(dropRawItr != m_cacheDecryptedRawBlocks.end() && dropRawItr->second->lastAccess == lastAccess) { if (droppedRawBlock) *droppedRawBlock = dropRawItr->second; else delete dropRawItr->second; m_cacheDecryptedRawBlocks.erase(dropRawItr); return; } else if(dropHashedItr != m_cacheDecryptedHashedBlocks.end() && dropHashedItr->second->lastAccess == lastAccess) { if (droppedHashedBlock) *droppedHashedBlock = dropHashedItr->second; else delete dropHashedItr->second; m_cacheDecryptedHashedBlocks.erase(dropHashedItr); } } void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, NCrypto::AesIv& ivOut) { ivOut = {}; if(blockIndex == 0) { ivOut.iv[0] = (uint8)(clusterIndex >> 8); ivOut.iv[1] = (uint8)(clusterIndex >> 0); } else { // the last 16 encrypted bytes of the previous block are the IV (AES CBC) // if the previous block is cached we can grab the IV from there. Otherwise we have to read the 16 bytes from the data source uint32 prevBlockIndex = blockIndex - 1; uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)prevBlockIndex; auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId); if (itr != m_cacheDecryptedRawBlocks.end()) { ivOut = itr->second->ivForNextBlock; } else { cemu_assert(m_sectorSize >= NCrypto::AesIv::SIZE); uint64 clusterOffset = (uint64)m_cluster[clusterIndex].offset * m_sectorSize; NCrypto::AesIv prevIV{}; if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize - NCrypto::AesIv::SIZE, prevIV.iv, NCrypto::AesIv::SIZE) != NCrypto::AesIv::SIZE) { cemuLog_log(LogType::Force, "Failed to read IV for raw FST block"); m_detectedCorruption = true; return; } ivOut = prevIV; } } } FSTCachedRawBlock* FSTVolume::GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex) { FSTCluster& cluster = m_cluster[clusterIndex]; uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize; // generate id for cache uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)blockIndex; // lookup block in cache FSTCachedRawBlock* block = nullptr; auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId); if (itr != m_cacheDecryptedRawBlocks.end()) { block = itr->second; block->lastAccess = ++m_cacheAccessCounter; return block; } // if cache already full, drop least recently accessed block and recycle FSTCachedRawBlock object if possible TrimCacheIfRequired(&block, nullptr); if (!block) block = new FSTCachedRawBlock(); block->blockData.rawData.resize(m_sectorSize); // block not cached, read new block->lastAccess = ++m_cacheAccessCounter; if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize, block->blockData.rawData.data(), m_sectorSize) != m_sectorSize) { cemuLog_log(LogType::Force, "Failed to read raw FST block"); delete block; m_detectedCorruption = true; return nullptr; } // decrypt hash data NCrypto::AesIv iv{}; DetermineUnhashedBlockIV(clusterIndex, blockIndex, iv); std::copy(block->blockData.rawData.data() + m_sectorSize - NCrypto::AesIv::SIZE, block->blockData.rawData.data() + m_sectorSize, block->ivForNextBlock.iv); AES128_CBC_decrypt(block->blockData.rawData.data(), block->blockData.rawData.data(), m_sectorSize, m_partitionTitlekey.b, iv.iv); // if this is the next block, then hash it if(cluster.hasContentHash) { if(cluster.singleHashNumBlocksHashed == blockIndex) { cemu_assert_debug(!(cluster.contentSize % m_sectorSize)); // size should be multiple of sector size? Regardless, the hashing code below can handle non-aligned sizes bool isLastBlock = blockIndex == (std::max<uint32>(cluster.contentSize / m_sectorSize, 1) - 1); uint32 hashSize = m_sectorSize; if(isLastBlock) hashSize = cluster.contentSize - (uint64)blockIndex*m_sectorSize; EVP_DigestUpdate(cluster.singleHashCtx.get(), block->blockData.rawData.data(), hashSize); cluster.singleHashNumBlocksHashed++; if(isLastBlock) { uint8 hash[32]; EVP_DigestFinal_ex(cluster.singleHashCtx.get(), hash, nullptr); if(memcmp(hash, cluster.contentHash32, cluster.contentHashIsSHA1 ? 20 : 32) != 0) { cemuLog_log(LogType::Force, "FST: Raw section hash mismatch"); delete block; m_detectedCorruption = true; return nullptr; } } } } // register in cache m_cacheDecryptedRawBlocks.emplace(cacheBlockId, block); return block; } FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex) { const FSTCluster& cluster = m_cluster[clusterIndex]; uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize; // generate id for cache uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)blockIndex; // lookup block in cache FSTCachedHashedBlock* block = nullptr; auto itr = m_cacheDecryptedHashedBlocks.find(cacheBlockId); if (itr != m_cacheDecryptedHashedBlocks.end()) { block = itr->second; block->lastAccess = ++m_cacheAccessCounter; return block; } // if cache already full, drop least recently accessed block and recycle FSTCachedHashedBlock object if possible TrimCacheIfRequired(nullptr, &block); if (!block) block = new FSTCachedHashedBlock(); // block not cached, read new block->lastAccess = ++m_cacheAccessCounter; if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * BLOCK_SIZE, block->blockData.rawData, BLOCK_SIZE) != BLOCK_SIZE) { cemuLog_log(LogType::Force, "Failed to read hashed FST block"); delete block; m_detectedCorruption = true; return nullptr; } // decrypt hash data uint8 iv[16]{}; AES128_CBC_decrypt(block->blockData.getHashData(), block->blockData.getHashData(), BLOCK_HASH_SIZE, m_partitionTitlekey.b, iv); // decrypt file data AES128_CBC_decrypt(block->blockData.getFileData(), block->blockData.getFileData(), BLOCK_FILE_SIZE, m_partitionTitlekey.b, block->blockData.getH0Hash(blockIndex%16)); // compare with H0 to verify data integrity NCrypto::CHash160 h0; SHA1(block->blockData.getFileData(), BLOCK_FILE_SIZE, h0.b); uint32 h0Index = (blockIndex % 4096); if (memcmp(h0.b, block->blockData.getH0Hash(h0Index & 0xF), sizeof(h0.b)) != 0) { cemuLog_log(LogType::Force, "FST: Hash H0 mismatch in hashed block (section {} index {})", clusterIndex, blockIndex); delete block; m_detectedCorruption = true; return nullptr; } // register in cache m_cacheDecryptedHashedBlocks.emplace(cacheBlockId, block); return block; } uint32 FSTVolume::ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut) { uint8* dataOutU8 = (uint8*)dataOut; if (readOffset >= entry.fileInfo.fileSize) return 0; else if ((readOffset + readSize) >= entry.fileInfo.fileSize) readSize = (entry.fileInfo.fileSize - readOffset); uint64 absFileOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset; uint32 remainingReadSize = readSize; while (remainingReadSize > 0) { const FSTCachedRawBlock* rawBlock = this->GetDecryptedRawBlock(clusterIndex, absFileOffset/m_sectorSize); if (!rawBlock) break; uint32 blockOffset = (uint32)(absFileOffset % m_sectorSize); uint32 bytesToRead = std::min<uint32>(remainingReadSize, m_sectorSize - blockOffset); std::memcpy(dataOutU8, rawBlock->blockData.rawData.data() + blockOffset, bytesToRead); dataOutU8 += bytesToRead; remainingReadSize -= bytesToRead; absFileOffset += bytesToRead; } return readSize - remainingReadSize; } uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut) { /* Data is divided into 0x10000 (64KiB) blocks Layout: +0x0000 Hash20[16] H0 hashes +0x0140 Hash20[16] H1 hashes +0x0240 Hash20[16] H2 hashes +0x03C0 uint8[64] padding +0x0400 uint8[0xFC00] fileData The hash part (0-0x3FF) uses AES-CBC with IV initialized to zero The file part (0x400 - 0xFFFF) uses AES-CBC with IV initialized to block->h0Hash[blockIndex % 16] The hash data itself is calculated over 4096 blocks. Where each individual H0 entry hashes a single 0xFC00 file data block (unencrypted) Each H1 hash is calculated from 16 H0 hashes Each H2 hash is calculated from 16 H1 hashes. The H3 hash is calculated from 16 H2 hashes. Thus for each 4096 block group we end up with: 4096 H0 hashes 256 H1 hashes 16 H2 hashes 1 H3 hash The embedded H0/H1 hashes per block are only a slice of the larger array. Whereas H2 always get embedded as a whole, due to only having 16 hashes in total There is also a H4 hash that covers all H3 hashes and is stored in the TMD */ const FSTCluster& cluster = m_cluster[clusterIndex]; uint64 fileReadOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset; uint32 blockIndex = (uint32)(fileReadOffset / BLOCK_FILE_SIZE); uint32 bytesRemaining = readSize; uint32 offsetWithinBlock = (uint32)(fileReadOffset % BLOCK_FILE_SIZE); while (bytesRemaining > 0) { FSTCachedHashedBlock* block = GetDecryptedHashedBlock(clusterIndex, blockIndex); if (!block) return 0; uint32 bytesToRead = std::min(bytesRemaining, (uint32)BLOCK_FILE_SIZE - offsetWithinBlock); std::memcpy(dataOut, block->blockData.getFileData() + offsetWithinBlock, bytesToRead); dataOut = (uint8*)dataOut + bytesToRead; bytesRemaining -= bytesToRead; blockIndex++; offsetWithinBlock = 0; } return readSize - bytesRemaining; } bool FSTVolume::OpenDirectoryIterator(std::string_view path, FSTDirectoryIterator& directoryIteratorOut) { FSTFileHandle fileHandle; if (!OpenFile(path, fileHandle, false)) return false; if (!IsDirectory(fileHandle)) return false; auto const& fstEntry = m_entries[fileHandle.m_fstIndex]; directoryIteratorOut.dirHandle = fileHandle; directoryIteratorOut.startIndex = fileHandle.m_fstIndex + 1; directoryIteratorOut.endIndex = fstEntry.dirInfo.endIndex; directoryIteratorOut.currentIndex = directoryIteratorOut.startIndex; return true; } bool FSTVolume::Next(FSTDirectoryIterator& directoryIterator, FSTFileHandle& fileHandleOut) { if (directoryIterator.currentIndex >= directoryIterator.endIndex) return false; auto const& fstEntry = m_entries[directoryIterator.currentIndex]; fileHandleOut.m_fstIndex = directoryIterator.currentIndex; if (fstEntry.GetType() == FSTEntry::TYPE::DIRECTORY) { cemu_assert_debug(fstEntry.dirInfo.endIndex > directoryIterator.currentIndex); directoryIterator.currentIndex = fstEntry.dirInfo.endIndex; } else directoryIterator.currentIndex++; return true; } FSTVolume::~FSTVolume() { for (auto& itr : m_cacheDecryptedRawBlocks) delete itr.second; for (auto& itr : m_cacheDecryptedHashedBlocks) delete itr.second; if (m_sourceIsOwned) delete m_dataSource; } bool FSTVerifier::VerifyContentFile(FileStream* fileContent, const NCrypto::AesKey* key, uint32 contentIndex, uint32 contentSize, uint32 contentSizePadded, bool isSHA1, const uint8* tmdContentHash) { cemu_assert_debug(isSHA1); // test this case cemu_assert_debug(((contentSize+0xF)&~0xF) == contentSizePadded); std::vector<uint8> buffer; buffer.resize(64 * 1024); if ((uint32)fileContent->GetSize() != contentSizePadded) return false; fileContent->SetPosition(0); uint8 iv[16]{}; iv[0] = (contentIndex >> 8) & 0xFF; iv[1] = (contentIndex >> 0) & 0xFF; // raw content uint64 remainingBytes = contentSize; uint8 calculatedHash[SHA256_DIGEST_LENGTH]; EVP_MD_CTX *ctx = EVP_MD_CTX_new(); EVP_DigestInit(ctx, isSHA1 ? EVP_sha1() : EVP_sha256()); while (remainingBytes > 0) { uint32 bytesToRead = (uint32)std::min(remainingBytes, (uint64)buffer.size()); uint32 bytesToReadPadded = ((bytesToRead + 0xF) & ~0xF); uint32 bytesRead = fileContent->readData(buffer.data(), bytesToReadPadded); if (bytesRead != bytesToReadPadded) return false; AES128_CBC_decrypt_updateIV(buffer.data(), buffer.data(), bytesToReadPadded, key->b, iv); EVP_DigestUpdate(ctx, buffer.data(), bytesToRead); remainingBytes -= bytesToRead; } unsigned int md_len; EVP_DigestFinal_ex(ctx, calculatedHash, &md_len); EVP_MD_CTX_free(ctx); return memcmp(calculatedHash, tmdContentHash, md_len) == 0; } bool FSTVerifier::VerifyHashedContentFile(FileStream* fileContent, const NCrypto::AesKey* key, uint32 contentIndex, uint32 contentSize, uint32 contentSizePadded, bool isSHA1, const uint8* tmdContentHash) { if (!isSHA1) return false; // not supported if ((contentSize % sizeof(FSTHashedBlock)) != 0) return false; if ((uint32)fileContent->GetSize() != contentSize) return false; fileContent->SetPosition(0); std::vector<NCrypto::CHash160> h0List(4096); FSTHashedBlock block; uint32 numBlocks = contentSize / sizeof(FSTHashedBlock); for (uint32 blockIndex = 0; blockIndex < numBlocks; blockIndex++) { if (fileContent->readData(&block, sizeof(FSTHashedBlock)) != sizeof(FSTHashedBlock)) return false; uint32 h0Index = (blockIndex % 4096); // decrypt hash data and file data uint8 iv[16]{}; AES128_CBC_decrypt(block.getHashData(), block.getHashData(), BLOCK_HASH_SIZE, key->b, iv); AES128_CBC_decrypt(block.getFileData(), block.getFileData(), BLOCK_FILE_SIZE, key->b, block.getH0Hash(blockIndex % 16)); // generate H0 hash and compare NCrypto::CHash160 h0; SHA1(block.getFileData(), BLOCK_FILE_SIZE, h0.b); if (memcmp(h0.b, block.getH0Hash(h0Index & 0xF), sizeof(h0.b)) != 0) return false; std::memcpy(h0List[h0Index].b, h0.b, sizeof(h0.b)); // Sixteen H0 hashes become one H1 hash if (((h0Index + 1) % 16) == 0 && h0Index > 0) { uint32 h1Index = ((h0Index - 15) / 16); NCrypto::CHash160 h1; SHA1((unsigned char *) (h0List.data() + h1Index * 16), sizeof(NCrypto::CHash160) * 16, h1.b); if (memcmp(h1.b, block.getH1Hash(h1Index&0xF), sizeof(h1.b)) != 0) return false; } // todo - repeat same for H1 and H2 // At the end all H3 hashes are hashed into a single H4 hash which is then compared with the content hash from the TMD // Checking only H0 and H1 is sufficient enough for verifying if the file data is intact // but if we wanted to be strict and only allow correctly signed data we would have to hash all the way up to H4 } return true; } void FSTVolumeTest() { FSTPathUnitTest(); } ================================================ FILE: src/Cafe/Filesystem/FST/FST.h ================================================ #pragma once #include "Cemu/ncrypto/ncrypto.h" #include "openssl/evp.h" struct FSTFileHandle { friend class FSTVolume; private: uint32 m_fstIndex; }; struct FSTDirectoryIterator { friend class FSTVolume; const FSTFileHandle& GetDirHandle() const { return dirHandle; } private: FSTFileHandle dirHandle; uint32 startIndex; uint32 endIndex; uint32 currentIndex; }; class FSTVolume { public: enum class ErrorCode { OK = 0, UNKNOWN_ERROR = 1, DISC_KEY_MISSING = 2, TITLE_TIK_MISSING = 3, BAD_TITLE_TMD = 4, BAD_TITLE_TIK = 5, }; static bool FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey); static FSTVolume* OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey, ErrorCode* errorCodeOut = nullptr); static FSTVolume* OpenFromDiscImage(const fs::path& path, ErrorCode* errorCodeOut = nullptr); static FSTVolume* OpenFromContentFolder(fs::path folderPath, ErrorCode* errorCodeOut = nullptr); ~FSTVolume(); uint32 GetFileCount() const; bool HasCorruption() const { return m_detectedCorruption; } bool OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles = false); // file and directory functions bool IsDirectory(const FSTFileHandle& fileHandle) const; bool IsFile(const FSTFileHandle& fileHandle) const; bool HasLinkFlag(const FSTFileHandle& fileHandle) const; std::string_view GetName(const FSTFileHandle& fileHandle) const; std::string GetPath(const FSTFileHandle& fileHandle) const; // file functions uint32 GetFileSize(const FSTFileHandle& fileHandle) const; uint32 ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size, void* dataOut); // directory iterator bool OpenDirectoryIterator(std::string_view path, FSTDirectoryIterator& directoryIteratorOut); bool Next(FSTDirectoryIterator& directoryIterator, FSTFileHandle& fileHandleOut); // helper function to read whole file std::vector<uint8> ExtractFile(std::string_view path, bool* success = nullptr) { if (success) *success = false; std::vector<uint8> fileData; FSTFileHandle fileHandle; if (!OpenFile(path, fileHandle, true)) return fileData; fileData.resize(GetFileSize(fileHandle)); ReadFile(fileHandle, 0, (uint32)fileData.size(), fileData.data()); if (success) *success = true; return fileData; } private: /* FST data (in memory) */ enum class ClusterHashMode : uint8 { RAW = 0, // raw data + encryption, no hashing? RAW_STREAM = 1, // raw data + encryption, with hash stored in tmd? HASH_INTERLEAVED = 2, // hashes + raw interleaved in 0x10000 blocks (0x400 bytes of hashes at the beginning, followed by 0xFC00 bytes of data) }; struct FSTCluster { FSTCluster() : singleHashCtx(nullptr, &EVP_MD_CTX_free) {} uint32 offset; uint32 size; ClusterHashMode hashMode; // extra data if TMD is available bool hasContentHash; uint8 contentHash32[32]; bool contentHashIsSHA1; // if true then it's SHA1 (with extra bytes zeroed out), otherwise it's SHA256 uint64 contentSize; // size of the content (in blocks) // hash context for single hash mode (content hash must be available) std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> singleHashCtx; // unique_ptr to make this move-only uint32 singleHashNumBlocksHashed{0}; }; struct FSTEntry { enum class TYPE : uint8 { FILE, DIRECTORY, }; enum FLAGS : uint8 { FLAG_NONE = 0x0, FLAG_LINK = 0x1, FLAG_UKN02 = 0x2, // seen in Super Mario Galaxy. Used for vWii files? }; uint32 nameOffset; uint32 parentDirIndex; // index of parent directory uint16 nameHash; uint8 typeAndFlags; TYPE GetType() const { return (TYPE)(typeAndFlags & 0xF); } void SetType(TYPE t) { typeAndFlags &= ~0x0F; typeAndFlags |= ((uint8)t); } FLAGS GetFlags() const { return (FLAGS)(typeAndFlags >> 4); } void SetFlags(FLAGS flags) { typeAndFlags &= ~0xF0; typeAndFlags |= ((uint8)flags << 4); } // note: The root node is not considered a valid parent bool HasNonRootNodeParent() const { return parentDirIndex != 0; } union { struct { uint32 endIndex; }dirInfo; struct { uint32 fileOffset; uint32 fileSize; uint16 clusterIndex; }fileInfo; }; }; class FSTDataSource* m_dataSource; bool m_sourceIsOwned{}; uint32 m_sectorSize{}; // for cluster offsets uint32 m_offsetFactor{}; // for file offsets bool m_hashIsDisabled{}; // disables hash verification (for all clusters of this volume?) std::vector<FSTCluster> m_cluster; std::vector<FSTEntry> m_entries; std::vector<char> m_nameStringTable; NCrypto::AesKey m_partitionTitlekey; bool m_detectedCorruption{false}; bool HashIsDisabled() const { return m_hashIsDisabled; } /* Cache for decrypted raw and hashed blocks */ std::unordered_map<uint64, struct FSTCachedRawBlock*> m_cacheDecryptedRawBlocks; std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks; uint64 m_cacheAccessCounter{}; void DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, NCrypto::AesIv& ivOut); struct FSTCachedRawBlock* GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex); struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex); void TrimCacheIfRequired(struct FSTCachedRawBlock** droppedRawBlock, struct FSTCachedHashedBlock** droppedHashedBlock); /* File reading */ uint32 ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut); uint32 ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut); /* FST parsing */ struct FSTHeader { /* +0x00 */ uint32be magic; /* +0x04 */ uint32be offsetFactor; /* +0x08 */ uint32be numCluster; /* +0x0C */ uint8be hashIsDisabled; /* +0x0D */ uint8be ukn0D; /* +0x0E */ uint8be ukn0E; /* +0x0F */ uint8be ukn0F; /* +0x10 */ uint32be ukn10; /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be ukn18; /* +0x1C */ uint32be ukn1C; }; static_assert(sizeof(FSTHeader) == 0x20); struct FSTHeader_ClusterEntry { /* +0x00 */ uint32be offset; /* +0x04 */ uint32be size; /* +0x08 */ uint64be ownerTitleId; /* +0x10 */ uint32be groupId; /* +0x14 */ uint8be hashMode; /* +0x15 */ uint8be padding[0xB]; // ? }; static_assert(sizeof(FSTHeader_ClusterEntry) == 0x20); struct FSTHeader_FileEntry { enum class TYPE : uint8 { FILE = 0, DIRECTORY = 1, }; /* +0x00 */ uint32be typeAndNameOffset; /* +0x04 */ uint32be offset; // for directories: parent directory index /* +0x08 */ uint32be size; // for directories: end index /* +0x0C */ uint16be flagsOrPermissions; // three entries, each one shifted by 4. (so 0xXYZ). Possible bits per value seem to be 0x1 and 0x4 ? These are probably permissions /* +0x0E */ uint16be clusterIndex; TYPE GetType() { uint8 v = GetTypeFlagField(); cemu_assert_debug((v & ~0x83) == 0); // unknown type/flag return static_cast<TYPE>(v & 0x01); } bool HasFlagLink() { uint8 v = GetTypeFlagField(); return (v & 0x80) != 0; } bool HasUknFlag02() { uint8 v = GetTypeFlagField(); return (v & 0x02) != 0; } uint32 GetNameOffset() { return (uint32)typeAndNameOffset & 0xFFFFFF; } uint32 GetDirectoryParent() { return offset; } uint32 GetDirectoryEndIndex() { return size; } private: uint8 GetTypeFlagField() { return static_cast<uint8>((typeAndNameOffset >> 24) & 0xFF); } }; static_assert(sizeof(FSTHeader_FileEntry) == 0x10); static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD); static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD); static bool ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries); bool MatchFSTEntryName(FSTEntry& entry, std::string_view comparedName) { const char* entryName = m_nameStringTable.data() + entry.nameOffset; const char* comparedNameCur = comparedName.data(); const char* comparedNameEnd = comparedName.data() + comparedName.size(); while (comparedNameCur < comparedNameEnd) { uint8 c1 = *entryName; uint8 c2 = *comparedNameCur; if (c1 >= (uint8)'A' && c1 <= (uint8)'Z') c1 = c1 - ((uint8)'A' - (uint8)'a'); if (c2 >= (uint8)'A' && c2 <= (uint8)'Z') c2 = c2 - ((uint8)'A' - (uint8)'a'); if (c1 != c2) return false; entryName++; comparedNameCur++; } return *entryName == '\0'; // all the characters match, check for same length } // we utilize hashes to accelerate string comparisons when doing path lookups static uint16 _QuickNameHash(const char* fileName, size_t len) { uint16 v = 0; const char* fileNameEnd = fileName + len; while (fileName < fileNameEnd) { uint8 c = (uint8)*fileName; if (c >= (uint8)'A' && c <= (uint8)'Z') c = c - ((uint8)'A' - (uint8)'a'); v += (uint16)c; v = (v >> 3) | (v << 13); fileName++; } return v; } }; class FSTVerifier { public: static bool VerifyContentFile(class FileStream* fileContent, const NCrypto::AesKey* key, uint32 contentIndex, uint32 contentSize, uint32 contentSizePadded, bool isSHA1, const uint8* tmdContentHash); static bool VerifyHashedContentFile(class FileStream* fileContent, const NCrypto::AesKey* key, uint32 contentIndex, uint32 contentSize, uint32 contentSizePadded, bool isSHA1, const uint8* tmdContentHash); }; ================================================ FILE: src/Cafe/Filesystem/FST/KeyCache.cpp ================================================ #include <mutex> #include "Cemu/Logging/CemuLogging.h" #include "WindowSystem.h" #include "config/ActiveSettings.h" #include "util/crypto/aes128.h" #include "Common/FileStream.h" #include "util/helpers/StringHelpers.h" std::mutex mtxKeyCache; struct KeyCacheEntry { uint8 aes128key[16]; }; std::vector<KeyCacheEntry> g_keyCache; bool strishex(std::string_view str) { for(size_t i=0; i<str.size(); i++) { char c = str[i]; if( (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) continue; return false; } return true; } /* * Returns AES-128 key from the key cache * nullptr is returned if index >= max_keys */ uint8* KeyCache_GetAES128(sint32 index) { if( index < 0 || index >= (sint32)g_keyCache.size()) return nullptr; KeyCacheEntry* keyCacheEntry = &g_keyCache[index]; return keyCacheEntry->aes128key; } void KeyCache_AddKey128(uint8* key) { KeyCacheEntry newEntry = {0}; memcpy(newEntry.aes128key, key, 16); g_keyCache.emplace_back(newEntry); } bool sKeyCachePrepared = false; void KeyCache_Prepare() { mtxKeyCache.lock(); if (sKeyCachePrepared) { mtxKeyCache.unlock(); return; } sKeyCachePrepared = true; g_keyCache.clear(); // load keys auto keysPath = ActiveSettings::GetUserDataPath("keys.txt"); FileStream* fs_keys = FileStream::openFile2(keysPath); if( !fs_keys ) { fs_keys = FileStream::createFile2(keysPath); if(fs_keys) { fs_keys->writeString("# this file contains keys needed for decryption of disc file system data (WUD/WUX)\r\n"); fs_keys->writeString("# 1 key per line, any text after a '#' character is considered a comment\r\n"); fs_keys->writeString("# the emulator will automatically pick the right key\r\n"); fs_keys->writeString("541b9889519b27d363cd21604b97c67a # example key (can be deleted)\r\n"); delete fs_keys; } else { WindowSystem::ShowErrorDialog(_tr("Unable to create file keys.txt\nThis can happen if Cemu does not have write permission to its own directory, the disk is full or if anti-virus software is blocking Cemu."), _tr("Error"), WindowSystem::ErrorCategory::KEYS_TXT_CREATION); } mtxKeyCache.unlock(); return; } sint32 lineNumber = 0; std::string line; while( fs_keys->readLine(line) ) { lineNumber++; // truncate anything after '#' or ';' for(size_t i=0; i<line.size(); i++) { if(line[i] == '#' || line[i] == ';' ) { line.resize(i); break; } } // remove whitespaces and other common formatting characters auto itr = line.begin(); while (itr != line.end()) { char c = *itr; if (c == ' ' || c == '\t' || c == '-' || c == '_') itr = line.erase(itr); else itr++; } if (line.empty()) continue; if( strishex(line) == false ) { auto errorMsg = _tr("Error in keys.txt at line {}", lineNumber); WindowSystem::ShowErrorDialog(errorMsg, WindowSystem::ErrorCategory::KEYS_TXT_CREATION); continue; } if(line.size() == 32 ) { // 128-bit key uint8 keyData128[16]; StringHelpers::ParseHexString(line, keyData128, 16); KeyCache_AddKey128(keyData128); } else { // invalid key length } } delete fs_keys; mtxKeyCache.unlock(); } ================================================ FILE: src/Cafe/Filesystem/FST/KeyCache.h ================================================ #pragma once void KeyCache_Prepare(); uint8* KeyCache_GetAES128(sint32 index); ================================================ FILE: src/Cafe/Filesystem/FST/fstUtil.h ================================================ #pragma once #include <wchar.h> #include <boost/container/small_vector.hpp> #include "../fsc.h" // path parser and utility class for Wii U paths // optimized to be allocation-free for common path lengths class FSCPath { struct PathNode { PathNode(uint16 offset, uint16 len) : offset(offset), len(len) {}; uint16 offset; uint16 len; }; boost::container::small_vector<PathNode, 8> m_nodes; boost::container::small_vector<char, 64> m_names; bool m_isAbsolute{}; inline bool isSlash(char c) { return c == '\\' || c == '/'; } void appendNode(const char* name, uint16 nameLen) { if (m_names.size() > 0xFFFF) return; if (nameLen == 1 && *name == '.') return; m_nodes.emplace_back((uint16)m_names.size(), nameLen); m_names.insert(m_names.end(), name, name + nameLen); } public: FSCPath(std::string_view path) { if (path.empty()) return; if (isSlash(path.front())) { m_isAbsolute = true; path.remove_prefix(1); // skip any additional leading slashes while (!path.empty() && isSlash(path.front())) path.remove_prefix(1); } // parse nodes size_t n = 0; size_t nodeNameStartIndex = 0; while (n < path.size()) { if (isSlash(path[n])) { size_t nodeNameLen = n - nodeNameStartIndex; if (nodeNameLen > 0xFFFF) nodeNameLen = 0xFFFF; // truncate suspiciously long node names cemu_assert_debug(nodeNameLen > 0); appendNode(path.data() + nodeNameStartIndex, (uint16)nodeNameLen); // skip any repeating slashes while (n < path.size() && isSlash(path[n])) n++; nodeNameStartIndex = n; continue; } n++; } if (nodeNameStartIndex < n) { size_t nodeNameLen = n - nodeNameStartIndex; if (nodeNameLen > 0xFFFF) nodeNameLen = 0xFFFF; // truncate suspiciously long node names appendNode(path.data() + nodeNameStartIndex, (uint16)nodeNameLen); } } size_t GetNodeCount() const { return m_nodes.size(); } std::string_view GetNodeName(size_t index) const { if (index < 0 || index >= m_nodes.size()) return std::basic_string_view<char>(); return std::basic_string_view<char>(m_names.data() + m_nodes[index].offset, m_nodes[index].len); } // returns true if the node names match according to FSA case-insensitivity rules static bool MatchNodeName(std::string_view name1, std::string_view name2) { if (name1.size() != name2.size()) return false; for (size_t i = 0; i < name1.size(); i++) { char c1 = name1[i]; char c2 = name2[i]; if (c1 >= 'A' && c1 <= 'Z') c1 += ('a' - 'A'); if (c2 >= 'A' && c2 <= 'Z') c2 += ('a' - 'A'); if (c1 != c2) return false; } return true; } bool MatchNodeName(sint32 index, std::string_view name) const { if (index < 0 || index >= (sint32)m_nodes.size()) return false; auto nodeName = GetNodeName(index); return MatchNodeName(nodeName, name); } }; template<typename F> class FSAFileTree { private: enum NODETYPE : uint8 { NODETYPE_DIRECTORY, NODETYPE_FILE, }; struct node_t { std::string name; std::vector<node_t*> subnodes; size_t fileSize; F* custom; NODETYPE type; }; node_t* getByNodePath(FSCPath& p, sint32 numNodes, bool createAsDirectories) { node_t* currentNode = &rootNode; for (sint32 i = 0; i < numNodes; i++) { // find subnode by path node_t* foundSubnode = getSubnode(currentNode, p.GetNodeName(i)); if (foundSubnode == nullptr) { // no subnode found -> create new directory node (if requested) if (createAsDirectories == false) return nullptr; // path not found currentNode = newNode(currentNode, NODETYPE_DIRECTORY, p.GetNodeName(i)); } else { currentNode = foundSubnode; } } return currentNode; } node_t* getSubnode(node_t* parentNode, std::string_view name) { for (auto& sn : parentNode->subnodes) { if (FSCPath::MatchNodeName(sn->name, name)) return sn; } return nullptr; } node_t* newNode(node_t* parentNode, NODETYPE type, std::string_view name) { node_t* newNode = new node_t; newNode->name.assign(name); newNode->type = type; newNode->custom = nullptr; parentNode->subnodes.push_back(newNode); return newNode; } class DirectoryIterator : public FSCVirtualFile { public: DirectoryIterator(node_t* node) : m_node(node), m_subnodeIndex(0) { } sint32 fscGetType() override { return FSC_TYPE_DIRECTORY; } bool fscDirNext(FSCDirEntry* dirEntry) override { if (m_subnodeIndex >= m_node->subnodes.size()) return false; const node_t* subnode = m_node->subnodes[m_subnodeIndex]; strncpy(dirEntry->path, subnode->name.c_str(), sizeof(dirEntry->path) - 1); dirEntry->path[sizeof(dirEntry->path) - 1] = '\0'; dirEntry->isDirectory = subnode->type == FSAFileTree::NODETYPE_DIRECTORY; dirEntry->isFile = subnode->type == FSAFileTree::NODETYPE_FILE; dirEntry->fileSize = subnode->type == FSAFileTree::NODETYPE_FILE ? subnode->fileSize : 0; ++m_subnodeIndex; return true; } bool fscRewindDir() override { m_subnodeIndex = 0; return true; } private: node_t* m_node; size_t m_subnodeIndex; }; public: FSAFileTree() { rootNode.type = NODETYPE_DIRECTORY; } bool addFile(std::string_view path, size_t fileSize, F* custom) { FSCPath p(path); if (p.GetNodeCount() == 0) return false; node_t* directoryNode = getByNodePath(p, p.GetNodeCount() - 1, true); // check if a node with same name already exists if (getSubnode(directoryNode, p.GetNodeName(p.GetNodeCount() - 1)) != nullptr) return false; // node already exists // add file node node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.GetNodeName(p.GetNodeCount() - 1)); fileNode->fileSize = fileSize; fileNode->custom = custom; return true; } bool getFile(std::string_view path, F* &custom) { FSCPath p(path); if (p.GetNodeCount() == 0) return false; node_t* node = getByNodePath(p, p.GetNodeCount(), false); if (node == nullptr) return false; if (node->type != NODETYPE_FILE) return false; custom = node->custom; return true; } bool getDirectory(std::string_view path, FSCVirtualFile*& dirIterator) { FSCPath p(path); if (p.GetNodeCount() == 0) return false; node_t* node = getByNodePath(p, p.GetNodeCount(), false); if (node == nullptr) return false; if (node->type != NODETYPE_DIRECTORY) return false; dirIterator = new DirectoryIterator(node); return true; } bool removeFile(std::string_view path) { FSCPath p(path); if (p.GetNodeCount() == 0) return false; node_t* directoryNode = getByNodePath(p, p.GetNodeCount() - 1, false); if (directoryNode == nullptr) return false; // find node node_t* fileNode = getSubnode(directoryNode, p.GetNodeName(p.GetNodeCount() - 1)); if (fileNode == nullptr) return false; if (fileNode->type != NODETYPE_FILE) return false; if (fileNode->subnodes.empty() == false) { // files shouldn't have subnodes assert(false); } // remove node from parent directoryNode->subnodes.erase(std::remove(directoryNode->subnodes.begin(), directoryNode->subnodes.end(), fileNode), directoryNode->subnodes.end()); // delete node delete fileNode; return true; } template<typename TFunc> bool listDirectory(std::string_view path, TFunc fn) { FSCPath p(path); node_t* node = getByNodePath(p, p.GetNodeCount(), false); if (node == nullptr) return false; if (node->type != NODETYPE_DIRECTORY) return false; for (auto& it : node->subnodes) { if (it->type == NODETYPE_DIRECTORY) { fn(it->name, true, it->custom); } else if (it->type == NODETYPE_FILE) { fn(it->name, false, it->custom); } } return true; } private: node_t rootNode; }; static void FSTPathUnitTest() { // test 1 FSCPath p1("/vol/content"); cemu_assert_debug(p1.GetNodeCount() == 2); cemu_assert_debug(p1.MatchNodeName(0, "tst") == false); cemu_assert_debug(p1.MatchNodeName(0, "vol")); cemu_assert_debug(p1.MatchNodeName(1, "CONTENT")); // test 2 FSCPath p2("/vol/content/"); cemu_assert_debug(p2.GetNodeCount() == 2); cemu_assert_debug(p2.MatchNodeName(0, "vol")); cemu_assert_debug(p2.MatchNodeName(1, "content")); // test 3 FSCPath p3("/vol//content/\\/"); cemu_assert_debug(p3.GetNodeCount() == 2); cemu_assert_debug(p3.MatchNodeName(0, "vol")); cemu_assert_debug(p3.MatchNodeName(1, "content")); // test 4 FSCPath p4("vol/content/"); cemu_assert_debug(p4.GetNodeCount() == 2); // test 5 FSCPath p5("/vol/content/test.bin"); cemu_assert_debug(p5.GetNodeCount() == 3); cemu_assert_debug(p5.MatchNodeName(0, "vol")); cemu_assert_debug(p5.MatchNodeName(1, "content")); cemu_assert_debug(p5.MatchNodeName(2, "TEST.BIN")); // test 6 - empty paths FSCPath p6(""); cemu_assert_debug(p6.GetNodeCount() == 0); p6 = FSCPath("/////////////"); cemu_assert_debug(p6.GetNodeCount() == 0); // test 7 - periods in path FSCPath p7("/vol/content/./.."); cemu_assert_debug(p7.GetNodeCount() == 3); cemu_assert_debug(p7.MatchNodeName(0, "vol")); cemu_assert_debug(p7.MatchNodeName(1, "content")); cemu_assert_debug(p7.MatchNodeName(2, "..")); } ================================================ FILE: src/Cafe/Filesystem/WUD/wud.cpp ================================================ #include <stdio.h> #include <string.h> #include <stdlib.h> #include "wud.h" #include "Common/FileStream.h" wud_t* wud_open(const fs::path& path) { FileStream* fs = FileStream::openFile2(path); if( !fs ) return nullptr; // allocate wud struct wud_t* wud = (wud_t*)malloc(sizeof(wud_t)); memset(wud, 0x00, sizeof(wud_t)); wud->fs = fs; // get size of file long long inputFileSize = wud->fs->GetSize(); // determine whether the WUD is compressed or not wuxHeader_t wuxHeader = {0}; if( wud->fs->readData(&wuxHeader, sizeof(wuxHeader_t)) != sizeof(wuxHeader_t)) { // file is too short to be either wud_close(wud); return nullptr; } if( wuxHeader.magic0 == WUX_MAGIC_0 && wuxHeader.magic1 == WUX_MAGIC_1 ) { // this is a WUX file wud->isCompressed = true; wud->sectorSize = wuxHeader.sectorSize; wud->uncompressedSize = wuxHeader.uncompressedSize; // validate header values if( wud->sectorSize < 0x100 || wud->sectorSize >= 0x10000000 ) { wud_close(wud); return nullptr; } // calculate offsets and sizes wud->indexTableEntryCount = (unsigned int)((wud->uncompressedSize+(long long)(wud->sectorSize-1)) / (long long)wud->sectorSize); wud->offsetIndexTable = sizeof(wuxHeader_t); wud->offsetSectorArray = (wud->offsetIndexTable + (long long)wud->indexTableEntryCount*sizeof(unsigned int)); // align to SECTOR_SIZE wud->offsetSectorArray = (wud->offsetSectorArray + (long long)(wud->sectorSize-1)); wud->offsetSectorArray = wud->offsetSectorArray - (wud->offsetSectorArray%(long long)wud->sectorSize); // read index table unsigned int indexTableSize = sizeof(unsigned int) * wud->indexTableEntryCount; wud->indexTable = (unsigned int*)malloc(indexTableSize); wud->fs->SetPosition(wud->offsetIndexTable); if( wud->fs->readData(wud->indexTable, indexTableSize) != indexTableSize ) { // could not read index table wud_close(wud); return nullptr; } } else { // uncompressed file wud->uncompressedSize = inputFileSize; } return wud; } void wud_close(wud_t* wud) { delete wud->fs; if( wud->indexTable ) free(wud->indexTable); free(wud); } bool wud_isWUXCompressed(wud_t* wud) { return wud->isCompressed; } /* * Read data from WUD file * Can read up to 4GB at once */ unsigned int wud_readData(wud_t* wud, void* buffer, unsigned int length, long long offset) { // make sure there is no out-of-bounds read long long fileBytesLeft = wud->uncompressedSize - offset; if( fileBytesLeft <= 0 ) return 0; if( fileBytesLeft < (long long)length ) length = (unsigned int)fileBytesLeft; // read data unsigned int readBytes = 0; if( wud->isCompressed == false ) { // uncompressed read is straight forward wud->fs->SetPosition(offset); readBytes = (unsigned int)wud->fs->readData(buffer, length); } else { // compressed read must be handled on a per-sector level while( length > 0 ) { unsigned int sectorOffset = (unsigned int)(offset % (long long)wud->sectorSize); unsigned int remainingSectorBytes = wud->sectorSize - sectorOffset; unsigned int sectorIndex = (unsigned int)(offset / (long long)wud->sectorSize); unsigned int bytesToRead = (remainingSectorBytes<length)?remainingSectorBytes:length; // read only up to the end of the current sector // look up real sector index sectorIndex = wud->indexTable[sectorIndex]; wud->fs->SetPosition(wud->offsetSectorArray + (long long)sectorIndex * (long long)wud->sectorSize + (long long)sectorOffset); readBytes += (unsigned int)wud->fs->readData(buffer, bytesToRead); // progress read offset, write pointer and decrease length buffer = (void*)((char*)buffer + bytesToRead); length -= bytesToRead; offset += bytesToRead; } } return readBytes; } /* * Returns the size of the data * For .wud: Size of file * For .wux: Size of uncompressed data */ long long wud_getWUDSize(wud_t* wud) { return wud->uncompressedSize; } ================================================ FILE: src/Cafe/Filesystem/WUD/wud.h ================================================ #pragma once struct wuxHeader_t { unsigned int magic0; unsigned int magic1; unsigned int sectorSize; unsigned long long uncompressedSize; unsigned int flags; }; struct wud_t { class FileStream* fs; long long uncompressedSize; bool isCompressed; // data used when compressed unsigned int sectorSize; unsigned int indexTableEntryCount; unsigned int* indexTable; long long offsetIndexTable; long long offsetSectorArray; }; #define WUX_MAGIC_0 '0XUW' // "WUX0" #define WUX_MAGIC_1 0x1099d02e // wud and wux functions wud_t* wud_open(const fs::path& path); // transparently handles wud and wux files void wud_close(wud_t* wud); bool wud_isWUXCompressed(wud_t* wud); unsigned int wud_readData(wud_t* wud, void* buffer, unsigned int length, long long offset); long long wud_getWUDSize(wud_t* wud); ================================================ FILE: src/Cafe/Filesystem/WUHB/RomFSStructs.h ================================================ #pragma once struct romfs_header_t { uint32 header_magic; uint32be header_size; uint64be dir_hash_table_ofs; uint64be dir_hash_table_size; uint64be dir_table_ofs; uint64be dir_table_size; uint64be file_hash_table_ofs; uint64be file_hash_table_size; uint64be file_table_ofs; uint64be file_table_size; uint64be file_partition_ofs; }; struct romfs_direntry_t { uint32be parent; uint32be listNext; // offset to next directory entry in linked list of parent directory (aka "sibling") uint32be dirListHead; // offset to first entry in linked list of directory entries (aka "child") uint32be fileListHead; // offset to first entry in linked list of file entries (aka "file") uint32be hash; uint32be name_size; std::string name; }; struct romfs_fentry_t { uint32be parent; uint32be listNext; // offset to next file entry in linked list of parent directory (aka "sibling") uint64be offset; uint64be size; uint32be hash; uint32be name_size; std::string name; }; #define ROMFS_ENTRY_EMPTY 0xFFFFFFFF ================================================ FILE: src/Cafe/Filesystem/WUHB/WUHBReader.cpp ================================================ #include "WUHBReader.h" WUHBReader* WUHBReader::FromPath(const fs::path& path) { FileStream* fileIn{FileStream::openFile2(path)}; if (!fileIn) return nullptr; WUHBReader* ret = new WUHBReader(fileIn); if (!ret->CheckMagicValue()) { delete ret; return nullptr; } if (!ret->ReadHeader()) { delete ret; return nullptr; } return ret; } static const romfs_direntry_t fallbackDirEntry{ .parent = ROMFS_ENTRY_EMPTY, .listNext = ROMFS_ENTRY_EMPTY, .dirListHead = ROMFS_ENTRY_EMPTY, .fileListHead = ROMFS_ENTRY_EMPTY, .hash = ROMFS_ENTRY_EMPTY, .name_size = 0, .name = "" }; static const romfs_fentry_t fallbackFileEntry{ .parent = ROMFS_ENTRY_EMPTY, .listNext = ROMFS_ENTRY_EMPTY, .offset = 0, .size = 0, .hash = ROMFS_ENTRY_EMPTY, .name_size = 0, .name = "" }; template<bool File> const WUHBReader::EntryType<File>& WUHBReader::GetFallback() { if constexpr (File) return fallbackFileEntry; else return fallbackDirEntry; } template<bool File> WUHBReader::EntryType<File> WUHBReader::GetEntry(uint32 offset) const { auto fallback = GetFallback<File>(); if(offset == ROMFS_ENTRY_EMPTY) return fallback; const char* typeName = File ? "fentry" : "direntry"; EntryType<File> ret; if (offset >= (File ? m_header.file_table_size : m_header.dir_table_size)) { cemuLog_log(LogType::Force, "WUHB {} offset exceeds table size declared in header", typeName); return fallback; } // read the entry m_fileIn->SetPosition((File ? m_header.file_table_ofs : m_header.dir_table_ofs) + offset); auto read = m_fileIn->readData(&ret, offsetof(EntryType<File>, name)); if (read != offsetof(EntryType<File>, name)) { cemuLog_log(LogType::Force, "failed to read WUHB {} at offset: {}", typeName, offset); return fallback; } // read the name ret.name.resize(ret.name_size); read = m_fileIn->readData(ret.name.data(), ret.name_size); if (read != ret.name_size) { cemuLog_log(LogType::Force, "failed to read WUHB {} name", typeName); return fallback; } return ret; } romfs_direntry_t WUHBReader::GetDirEntry(uint32 offset) const { return GetEntry<false>(offset); } romfs_fentry_t WUHBReader::GetFileEntry(uint32 offset) const { return GetEntry<true>(offset); } uint64 WUHBReader::GetFileSize(uint32 entryOffset) const { return GetFileEntry(entryOffset).size; } uint64 WUHBReader::ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const { const auto fileEntry = GetFileEntry(entryOffset); if (fileOffset >= fileEntry.size) return 0; const uint64 readAmount = std::min(length, fileEntry.size - fileOffset); const uint64 wuhbOffset = m_header.file_partition_ofs + fileEntry.offset + fileOffset; m_fileIn->SetPosition(wuhbOffset); return m_fileIn->readData(buffer, readAmount); } uint32 WUHBReader::GetHashTableEntryOffset(uint32 hash, bool isFile) const { const uint64 hash_table_size = (isFile ? m_header.file_hash_table_size : m_header.dir_hash_table_size); const uint64 hash_table_ofs = (isFile ? m_header.file_hash_table_ofs : m_header.dir_hash_table_ofs); const uint64 hash_table_entry_count = hash_table_size / sizeof(uint32); const uint64 hash_table_entry_offset = hash_table_ofs + (hash % hash_table_entry_count) * sizeof(uint32); m_fileIn->SetPosition(hash_table_entry_offset); uint32 tableOffset; if (!m_fileIn->readU32(tableOffset)) { cemuLog_log(LogType::Force, "failed to read WUHB hash table entry at file offset: {}", hash_table_entry_offset); return ROMFS_ENTRY_EMPTY; } return uint32be::from_bevalue(tableOffset); } template<bool T> bool WUHBReader::SearchHashList(uint32& entryOffset, const fs::path& targetName) const { for (;;) { if (entryOffset == ROMFS_ENTRY_EMPTY) return false; auto entry = GetEntry<T>(entryOffset); if (entry.name == targetName) return true; entryOffset = entry.hash; } return false; } uint32 WUHBReader::Lookup(const std::filesystem::path& path, bool isFile) const { uint32 currentEntryOffset = 0; auto look = [&](const fs::path& part, bool lookInFileHT) { const auto partString = part.string(); currentEntryOffset = GetHashTableEntryOffset(CalcPathHash(currentEntryOffset, partString.c_str(), 0, partString.size()), lookInFileHT); if (lookInFileHT) return SearchHashList<true>(currentEntryOffset, part); else return SearchHashList<false>(currentEntryOffset, part); }; // look for the root entry if (!look("", false)) return ROMFS_ENTRY_EMPTY; auto it = path.begin(); while (it != path.end()) { fs::path part = *it; ++it; // no need to recurse after trailing forward slash (e.g. directory/) if (part.empty() && !isFile) break; // skip leading forward slash if (part == "/") continue; // if the lookup target is a file and this is the last iteration, look in the file hash table instead. if (!look(part, it == path.end() && isFile)) return ROMFS_ENTRY_EMPTY; } return currentEntryOffset; } bool WUHBReader::CheckMagicValue() const { uint8 magic[4]; m_fileIn->SetPosition(0); int read = m_fileIn->readData(magic, 4); if (read != 4) { cemuLog_log(LogType::Force, "Failed to read WUHB magic numbers"); return false; } static_assert(sizeof(magic) == s_headerMagicValue.size()); return std::memcmp(&magic, s_headerMagicValue.data(), sizeof(magic)) == 0; } bool WUHBReader::ReadHeader() { m_fileIn->SetPosition(0); auto read = m_fileIn->readData(&m_header, sizeof(m_header)); auto readSuccess = read == sizeof(m_header); if (!readSuccess) cemuLog_log(LogType::Force, "Failed to read WUHB header"); return readSuccess; } unsigned char WUHBReader::NormalizeChar(unsigned char c) { if (c >= 'a' && c <= 'z') { return c + 'A' - 'a'; } else { return c; } } uint32 WUHBReader::CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len) { cemu_assert(path != nullptr || path_len == 0); uint32 hash = parent ^ 123456789; for (uint32 i = 0; i < path_len; i++) { hash = (hash >> 5) | (hash << 27); hash ^= NormalizeChar(path[start + i]); } return hash; } ================================================ FILE: src/Cafe/Filesystem/WUHB/WUHBReader.h ================================================ #pragma once #include <Common/FileStream.h> #include "RomFSStructs.h" class WUHBReader { public: static WUHBReader* FromPath(const fs::path& path); romfs_direntry_t GetDirEntry(uint32 offset) const; romfs_fentry_t GetFileEntry(uint32 offset) const; uint64 GetFileSize(uint32 entryOffset) const; uint64 ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const; uint32 Lookup(const std::filesystem::path& path, bool isFile) const; private: WUHBReader(FileStream* file) : m_fileIn(file) { cemu_assert_debug(file != nullptr); }; WUHBReader() = delete; romfs_header_t m_header; std::unique_ptr<FileStream> m_fileIn; constexpr static std::string_view s_headerMagicValue = "WUHB"; bool ReadHeader(); bool CheckMagicValue() const; static inline unsigned char NormalizeChar(unsigned char c); static uint32 CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len); template<bool File> using EntryType = std::conditional_t<File, romfs_fentry_t, romfs_direntry_t>; template<bool File> static const EntryType<File>& GetFallback(); template<bool File> EntryType<File> GetEntry(uint32 offset) const; template<bool T> bool SearchHashList(uint32& entryOffset, const fs::path& targetName) const; uint32 GetHashTableEntryOffset(uint32 hash, bool isFile) const; }; ================================================ FILE: src/Cafe/Filesystem/fsc.cpp ================================================ #include "Cafe/Filesystem/fsc.h" #include "Cafe/Filesystem/FST/fstUtil.h" struct FSCMountPathNode { std::string path; std::vector<FSCMountPathNode*> subnodes; FSCMountPathNode* parent; // associated device target and path fscDeviceC* device{ nullptr }; void* ctx{ nullptr }; std::string deviceTargetPath; // the destination base path for the device, utf8 // priority sint32 priority{}; FSCMountPathNode(FSCMountPathNode* parent) : parent(parent) { } void AssignDevice(fscDeviceC* device, void* ctx, std::string_view deviceBasePath) { this->device = device; this->ctx = ctx; this->deviceTargetPath = deviceBasePath; } void UnassignDevice() { this->device = nullptr; this->ctx = nullptr; this->deviceTargetPath.clear(); } bool IsRootNode() const { return !parent; } ~FSCMountPathNode() { for (auto& itr : subnodes) delete itr; subnodes.clear(); } }; // compare two file or directory names using FSA rules bool FSA_CompareNodeName(std::string_view a, std::string_view b) { if (a.size() != b.size()) return false; for (size_t i = 0; i < a.size(); i++) { uint8 ac = (uint8)a[i]; uint8 bc = (uint8)b[i]; // lower case compare if (ac >= (uint8)'A' && ac <= (uint8)'Z') ac -= ((uint8)'A' - (uint8)'a'); if (bc >= (uint8)'A' && bc <= (uint8)'Z') bc -= ((uint8)'A' - (uint8)'a'); if (ac != bc) return false; } return true; } FSCMountPathNode* s_fscRootNodePerPrio[FSC_PRIORITY_COUNT]{}; std::recursive_mutex s_fscMutex; #define fscEnter() s_fscMutex.lock(); #define fscLeave() s_fscMutex.unlock(); FSCMountPathNode* fsc_lookupPathVirtualNode(const char* path, sint32 priority = FSC_PRIORITY_BASE); void fsc_reset() { // delete existing nodes for (auto& itr : s_fscRootNodePerPrio) { delete itr; itr = nullptr; } // init root node for each priority for (sint32 i = 0; i < FSC_PRIORITY_COUNT; i++) s_fscRootNodePerPrio[i] = new FSCMountPathNode(nullptr); } /* * Creates a node chain for the given mount path. Returns the bottom node. * If the path already exists for the given priority, NULL is returned (we can't mount two devices to the same path with the same priority) * But we can map devices to subdirectories. Something like this is possible: * /vol/content -> Map to WUD (includes all subdirectories except /data, which is handled by the entry below. This exclusion rule applies only if the priority of both mount entries is the same) * /vol/content/data -> Map to HostFS * If overlapping paths with different priority are created, then the higher priority one will be checked first */ FSCMountPathNode* fsc_createMountPath(const FSCPath& mountPath, sint32 priority) { cemu_assert(priority >= 0 && priority < FSC_PRIORITY_COUNT); fscEnter(); FSCMountPathNode* nodeParent = s_fscRootNodePerPrio[priority]; for (size_t i=0; i< mountPath.GetNodeCount(); i++) { // search for subdirectory FSCMountPathNode* nodeSub = nullptr; // set if we found a subnode with a matching name, else this is used to store the new nodes for (auto& nodeItr : nodeParent->subnodes) { if (mountPath.MatchNodeName(i, nodeItr->path)) { // subnode found nodeSub = nodeItr; break; } } if (nodeSub) { // traverse subnode nodeParent = nodeSub; continue; } // no matching subnode, add new entry nodeSub = new FSCMountPathNode(nodeParent); nodeSub->path = mountPath.GetNodeName(i); nodeSub->priority = priority; nodeParent->subnodes.emplace_back(nodeSub); if (i == (mountPath.GetNodeCount() - 1)) { // last node fscLeave(); return nodeSub; } // traverse subnode nodeParent = nodeSub; } // path is empty or already mounted fscLeave(); if (mountPath.GetNodeCount() == 0) return nodeParent; return nullptr; } // Map a virtual FSC directory to a device. targetPath points to the destination base directory within the device sint32 fsc_mount(std::string_view mountPath, std::string_view targetPath, fscDeviceC* fscDevice, void* ctx, sint32 priority) { cemu_assert(fscDevice); std::string mountPathTmp(mountPath); // make sure the target path ends with a slash std::string targetPathWithSlash(targetPath); if (!targetPathWithSlash.empty() && (targetPathWithSlash.back() != '/' && targetPathWithSlash.back() != '\\')) targetPathWithSlash.push_back('/'); FSCPath parsedMountPath(mountPathTmp); // register path fscEnter(); FSCMountPathNode* node = fsc_createMountPath(parsedMountPath, priority); if( !node ) { // path empty, invalid or already used cemuLog_log(LogType::Force, "fsc_mount failed (virtual path: {})", mountPath); fscLeave(); return FSC_STATUS_INVALID_PATH; } node->AssignDevice(fscDevice, ctx, targetPathWithSlash); fscLeave(); return FSC_STATUS_OK; } bool fsc_unmount(std::string_view mountPath, sint32 priority) { std::string _tmp(mountPath); fscEnter(); FSCMountPathNode* mountPathNode = fsc_lookupPathVirtualNode(_tmp.c_str(), priority); if (!mountPathNode) { fscLeave(); return false; } cemu_assert(mountPathNode->priority == priority); cemu_assert(mountPathNode->device); // unassign device mountPathNode->UnassignDevice(); // prune empty branch while (mountPathNode && !mountPathNode->IsRootNode() && mountPathNode->subnodes.empty() && !mountPathNode->device) { FSCMountPathNode* parent = mountPathNode->parent; std::erase(parent->subnodes, mountPathNode); delete mountPathNode; mountPathNode = parent; } fscLeave(); return true; } void fsc_unmountAll() { fscEnter(); fsc_reset(); fscLeave(); } // lookup virtual path and find mounted device and relative device directory bool fsc_lookupPath(const char* path, std::string& devicePathOut, fscDeviceC** fscDeviceOut, void** ctxOut, sint32 priority = FSC_PRIORITY_BASE) { FSCPath parsedPath(path); FSCMountPathNode* nodeParent = s_fscRootNodePerPrio[priority]; size_t i; fscEnter(); for (i = 0; i < parsedPath.GetNodeCount(); i++) { // search for subdirectory FSCMountPathNode* nodeSub = nullptr; for(auto& nodeItr : nodeParent->subnodes) { if (parsedPath.MatchNodeName(i, nodeItr->path)) { nodeSub = nodeItr; break; } } if (nodeSub) { nodeParent = nodeSub; continue; } // no matching subnode break; } // if the found node is not a device mount point, then travel back towards the root until we find one while (nodeParent) { if (nodeParent->device) { devicePathOut = nodeParent->deviceTargetPath; for (size_t f = i; f < parsedPath.GetNodeCount(); f++) { auto nodeName = parsedPath.GetNodeName(f); devicePathOut.append(nodeName); if (f < (parsedPath.GetNodeCount() - 1)) devicePathOut.push_back('/'); } *fscDeviceOut = nodeParent->device; *ctxOut = nodeParent->ctx; fscLeave(); return true; } nodeParent = nodeParent->parent; i--; } fscLeave(); return false; } // lookup path and find virtual device node FSCMountPathNode* fsc_lookupPathVirtualNode(const char* path, sint32 priority) { FSCPath parsedPath(path); FSCMountPathNode* nodeCurrentDir = s_fscRootNodePerPrio[priority]; fscEnter(); for (size_t i = 0; i < parsedPath.GetNodeCount(); i++) { // search for subdirectory FSCMountPathNode* nodeSub = nullptr; for (auto& nodeItr : nodeCurrentDir->subnodes) { if (parsedPath.MatchNodeName(i, nodeItr->path)) { nodeSub = nodeItr; break; } } if (nodeSub) { // traverse subdirectory nodeCurrentDir = nodeSub; continue; } fscLeave(); return nullptr; } fscLeave(); return nodeCurrentDir; } // this wraps multiple iterated directories from different devices into one unified virtual representation class FSCVirtualFileDirectoryIterator : public FSCVirtualFile { public: sint32 fscGetType() override { return FSC_TYPE_DIRECTORY; } FSCVirtualFileDirectoryIterator(std::string_view path, std::span<FSCVirtualFile*> mappedFolders) : m_path(path), m_folders(mappedFolders.begin(), mappedFolders.end()) { dirIterator = nullptr; } ~FSCVirtualFileDirectoryIterator() { // dirIterator is deleted in base constructor for (auto& itr : m_folders) delete itr; } bool fscDirNext(FSCDirEntry* dirEntry) override { if (!dirIterator) { // lazily populate list only if directory is actually iterated PopulateIterationList(); cemu_assert_debug(dirIterator); } if (dirIterator->index >= dirIterator->dirEntries.size()) return false; *dirEntry = dirIterator->dirEntries[dirIterator->index]; dirIterator->index++; return true; } bool fscRewindDir() override { if (!dirIterator) return true; dirIterator->index = 0; return true; } void addUniqueDirEntry(const FSCDirEntry& dirEntry) { // skip if already in list for (auto& itr : dirIterator->dirEntries) { if (FSA_CompareNodeName(dirEntry.path, itr.path)) return; } dirIterator->dirEntries.emplace_back(dirEntry); } private: void PopulateIterationList() { cemu_assert_debug(!dirIterator); dirIterator = new FSCVirtualFile::FSCDirIteratorState(); FSCDirEntry dirEntry; fscEnter(); for (auto& itr : m_folders) { while (itr->fscDirNext(&dirEntry)) addUniqueDirEntry(dirEntry); } for (sint32 prio = FSC_PRIORITY_COUNT - 1; prio >= 0; prio--) { FSCMountPathNode* nodeVirtualPath = fsc_lookupPathVirtualNode(m_path.c_str(), prio); if (nodeVirtualPath) { for (auto& itr : nodeVirtualPath->subnodes) { dirEntry = {}; dirEntry.isDirectory = true; strncpy(dirEntry.path, itr->path.c_str(), sizeof(dirEntry.path) - 1); dirEntry.path[sizeof(dirEntry.path) - 1] = '\0'; dirEntry.fileSize = 0; addUniqueDirEntry(dirEntry); } } } fscLeave(); } private: std::string m_path; std::vector<FSCVirtualFile*> m_folders; // list of all folders mapped to the same directory (at different priorities) }; // Open file or directory from virtual file system FSCVirtualFile* fsc_open(const char* path, FSC_ACCESS_FLAG accessFlags, sint32* fscStatus, sint32 maxPriority) { cemu_assert_debug(HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) || HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)); // must open either file or directory FSCVirtualFile* dirList[FSC_PRIORITY_COUNT]; uint8 dirListCount = 0; std::string devicePath; fscDeviceC* fscDevice = NULL; *fscStatus = FSC_STATUS_UNDEFINED; void* ctx; fscEnter(); for (sint32 prio = maxPriority; prio >= 0; prio--) { if (fsc_lookupPath(path, devicePath, &fscDevice, &ctx, prio)) { FSCVirtualFile* fscVirtualFile = fscDevice->fscDeviceOpenByPath(devicePath, accessFlags, ctx, fscStatus); if (fscVirtualFile) { if (fscVirtualFile->fscGetType() == FSC_TYPE_DIRECTORY) { cemu_assert_debug(HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)); // collect all folders dirList[dirListCount] = fscVirtualFile; dirListCount++; } else { // return first found file cemu_assert_debug(HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)); fscVirtualFile->m_isAppend = HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::IS_APPEND); fscLeave(); return fscVirtualFile; } } } } // for directories we create a virtual representation of the enumerated files of all priorities as well as the FSC folder structure itself if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) { // create a virtual directory VirtualFile that represents all the mounted folders as well as the virtual FSC folder structure bool folderExists = dirListCount > 0; for (sint32 prio = FSC_PRIORITY_COUNT - 1; prio >= 0; prio--) { if (folderExists) break; folderExists |= (fsc_lookupPathVirtualNode(path, prio) != 0); } if (folderExists) { FSCVirtualFileDirectoryIterator* dirIteratorFile = new FSCVirtualFileDirectoryIterator(path, { dirList, dirListCount}); *fscStatus = FSC_STATUS_OK; fscLeave(); return dirIteratorFile; } } else { cemu_assert_debug(dirListCount == 0); } fscLeave(); *fscStatus = FSC_STATUS_FILE_NOT_FOUND; return nullptr; } /* * Open file using virtual path */ FSCVirtualFile* fsc_openDirIterator(const char* path, sint32* fscStatus) { return fsc_open(path, FSC_ACCESS_FLAG::OPEN_DIR, fscStatus); } /* * Iterate next node in directory * Returns false if there is no node left */ bool fsc_nextDir(FSCVirtualFile* fscFile, FSCDirEntry* dirEntry) { fscEnter(); if (fscFile->fscGetType() != FSC_TYPE_DIRECTORY) { cemu_assert_suspicious(); fscLeave(); return false; } bool r = fscFile->fscDirNext(dirEntry); fscLeave(); return r; } /* * Create directory */ bool fsc_createDir(const char* path, sint32* fscStatus) { fscDeviceC* fscDevice = NULL; *fscStatus = FSC_STATUS_UNDEFINED; void* ctx; std::string devicePath; fscEnter(); if( fsc_lookupPath(path, devicePath, &fscDevice, &ctx) ) { sint32 status = fscDevice->fscDeviceCreateDir(devicePath, ctx, fscStatus); fscLeave(); return status; } fscLeave(); return false; } /* * Rename file or directory */ bool fsc_rename(const char* srcPath, const char* dstPath, sint32* fscStatus) { std::string srcDevicePath; std::string dstDevicePath; void* srcCtx; void* dstCtx; fscDeviceC* fscSrcDevice = NULL; fscDeviceC* fscDstDevice = NULL; *fscStatus = FSC_STATUS_UNDEFINED; if( fsc_lookupPath(srcPath, srcDevicePath, &fscSrcDevice, &srcCtx) && fsc_lookupPath(dstPath, dstDevicePath, &fscDstDevice, &dstCtx) ) { if( fscSrcDevice == fscDstDevice ) return fscSrcDevice->fscDeviceRename(srcDevicePath, dstDevicePath, srcCtx, fscStatus); } return false; } /* * Delete file or subdirectory */ bool fsc_remove(const char* path, sint32* fscStatus) { std::string devicePath; fscDeviceC* fscDevice = NULL; *fscStatus = FSC_STATUS_UNDEFINED; void* ctx; if( fsc_lookupPath(path, devicePath, &fscDevice, &ctx) ) { return fscDevice->fscDeviceRemoveFileOrDir(devicePath, ctx, fscStatus); } return false; } /* * Close file handle */ void fsc_close(FSCVirtualFile* fscFile) { fscEnter(); delete fscFile; fscLeave(); } /* * Return size of file */ uint32 fsc_getFileSize(FSCVirtualFile* fscFile) { return (uint32)fscFile->fscQueryValueU64(FSC_QUERY_SIZE); } /* * Return file position */ uint32 fsc_getFileSeek(FSCVirtualFile* fscFile) { return (uint32)fscFile->fscGetSeek(); } /* * Set file seek * For writable files the seek pointer can be set past the end of the file */ void fsc_setFileSeek(FSCVirtualFile* fscFile, uint32 newSeek) { fscEnter(); uint32 fileSize = fsc_getFileSize(fscFile); if (fsc_isWritable(fscFile) == false) newSeek = std::min(newSeek, fileSize); fscFile->fscSetSeek((uint64)newSeek); fscLeave(); } // set file length void fsc_setFileLength(FSCVirtualFile* fscFile, uint32 newEndOffset) { fscEnter(); uint32 fileSize = fsc_getFileSize(fscFile); if (!fsc_isWritable(fscFile)) { cemuLog_log(LogType::Force, "TruncateFile called on read-only file"); } else { fscFile->fscSetFileLength((uint64)newEndOffset); } fscLeave(); } /* * Returns true if the file object is a directory */ bool fsc_isDirectory(FSCVirtualFile* fscFile) { return fscFile->fscGetType() == FSC_TYPE_DIRECTORY; } /* * Returns true if the file object is a file */ bool fsc_isFile(FSCVirtualFile* fscFile) { return fscFile->fscGetType() == FSC_TYPE_FILE; } /* * Returns true if the file is writable */ bool fsc_isWritable(FSCVirtualFile* fscFile) { return fscFile->fscQueryValueU64(FSC_QUERY_WRITEABLE) != 0; } /* * Read data from file * Returns number of bytes successfully read */ uint32 fsc_readFile(FSCVirtualFile* fscFile, void* buffer, uint32 size) { fscEnter(); uint32 fscStatus = fscFile->fscReadData(buffer, size); fscLeave(); return fscStatus; } /* * Write data to file * Returns number of bytes successfully written */ uint32 fsc_writeFile(FSCVirtualFile* fscFile, void* buffer, uint32 size) { fscEnter(); if (fsc_isWritable(fscFile) == false) { fscLeave(); return 0; } if (fscFile->m_isAppend) fsc_setFileSeek(fscFile, fsc_getFileSize(fscFile)); uint32 fscStatus = fscFile->fscWriteData(buffer, size); fscLeave(); return fscStatus; } // helper function to load a file into memory uint8* fsc_extractFile(const char* path, uint32* fileSize, sint32 maxPriority) { fscDeviceC* fscDevice = nullptr; sint32 fscStatus = FSC_STATUS_UNDEFINED; fscEnter(); FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus, maxPriority); if( !fscFile ) { *fileSize = 0; fscLeave(); return nullptr; } uint32 fscFileSize = fsc_getFileSize(fscFile); *fileSize = fscFileSize; uint8* fileMem = (uint8*)malloc(fscFileSize); if( fsc_readFile(fscFile, fileMem, fscFileSize) != fscFileSize ) { free(fileMem); fsc_close(fscFile); *fileSize = 0; fscLeave(); return nullptr; } fsc_close(fscFile); fscLeave(); return fileMem; } std::optional<std::vector<uint8>> fsc_extractFile(const char* path, sint32 maxPriority) { fscDeviceC* fscDevice = nullptr; sint32 fscStatus = FSC_STATUS_UNDEFINED; fscEnter(); FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus, maxPriority); if (!fscFile) { fscLeave(); return std::nullopt; } std::vector<uint8> fileData; uint32 fscFileSize = fsc_getFileSize(fscFile); fileData.resize(fscFileSize); uint32 readOffset = 0; while (readOffset < fscFileSize) { uint32 stepReadSize = std::min(fscFileSize - readOffset, (uint32)1024 * 1024 * 32); uint32 numBytesRead = fsc_readFile(fscFile, fileData.data() + readOffset, stepReadSize); if (numBytesRead != stepReadSize) { fsc_close(fscFile); fscLeave(); return std::nullopt; } readOffset += stepReadSize; } fsc_close(fscFile); fscLeave(); return fileData; } // helper function to check if a file exists bool fsc_doesFileExist(const char* path, sint32 maxPriority) { fscDeviceC* fscDevice = nullptr; sint32 fscStatus = FSC_STATUS_UNDEFINED; fscEnter(); FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_FILE, &fscStatus, maxPriority); if (!fscFile) { fscLeave(); return false; } fsc_close(fscFile); fscLeave(); return true; } // helper function to check if a directory exists bool fsc_doesDirectoryExist(const char* path, sint32 maxPriority) { fscDeviceC* fscDevice = nullptr; sint32 fscStatus = FSC_STATUS_UNDEFINED; fscEnter(); FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_DIR, &fscStatus, maxPriority); if (!fscFile) { fscLeave(); return false; } fsc_close(fscFile); fscLeave(); return true; } // initialize Cemu's virtual filesystem void fsc_init() { fsc_reset(); } ================================================ FILE: src/Cafe/Filesystem/fsc.h ================================================ #pragma once struct FSCVirtualFile; #define FSC_TYPE_INVALID (0) #define FSC_TYPE_FILE (1) #define FSC_TYPE_DIRECTORY (2) #define FSC_QUERY_SIZE (1) // file size, 0 for directories #define FSC_QUERY_WRITEABLE (2) // non-zero if file is writeable, else 0 enum class FSC_ACCESS_FLAG : uint8 { NONE = 0, // file permissions READ_PERMISSION = (1<<0), WRITE_PERMISSION = (1<<1), // file open mode (incompatible with OPEN_DIR flag) FILE_ALLOW_CREATE = (1 << 2), // create file if it does not exist FILE_ALWAYS_CREATE = (1 << 3), // overwrite any existing file // which types can be opened // invalid operation if neither is set OPEN_DIR = (1 << 4), OPEN_FILE = (1 << 5), // Writing seeks to the end of the file if set IS_APPEND = (1 << 6) }; DEFINE_ENUM_FLAG_OPERATORS(FSC_ACCESS_FLAG); #define FSC_STATUS_UNDEFINED (-1) #define FSC_STATUS_OK (0) #define FSC_STATUS_INVALID_PATH (1) #define FSC_STATUS_FILE_NOT_FOUND (2) #define FSC_STATUS_ALREADY_EXISTS (3) // note: Unlike the native Wii U filesystem, FSC does not provide separate error codes for NOT_A_FILE and NOT_A_DIRECTORY // to determine them manually, open with both modes (file and dir) and check the type #define FSC_MAX_DIR_NAME_LENGTH (256) #define FSC_MAX_DEVICE_PATH_LENGTH ((std::max)(260,FSA_PATH_SIZE_MAX)) // max length for FSC device paths (should be at least equal or greater than supported by host filesystem) struct FSCDirEntry { char path[FSC_MAX_DIR_NAME_LENGTH]; // stats bool isDirectory; bool isFile; uint32 fileSize; std::string_view GetPath() { size_t len = strnlen(path, FSC_MAX_DIR_NAME_LENGTH); return std::basic_string_view<char>(path, len); } }; class fscDeviceC { public: virtual FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) { cemu_assert_unimplemented(); return nullptr; } virtual bool fscDeviceCreateDir(std::string_view path, void* ctx, sint32* fscStatus) { cemu_assert_unimplemented(); return false; } virtual bool fscDeviceRemoveFileOrDir(std::string_view path, void* ctx, sint32* fscStatus) { cemu_assert_unimplemented(); return false; } virtual bool fscDeviceRename(std::string_view srcPath, std::string_view dstPath, void* ctx, sint32* fscStatus) { cemu_assert_unimplemented(); return false; } }; struct FSCVirtualFile { struct FSCDirIteratorState { sint32 index; std::vector<FSCDirEntry> dirEntries; }; FSCVirtualFile() { } virtual ~FSCVirtualFile() { if (dirIterator) delete dirIterator; } virtual sint32 fscGetType() { cemu_assert_unimplemented(); return 0; } virtual uint64 fscQueryValueU64(uint32 id) { cemu_assert_unimplemented(); return 0; } virtual uint32 fscWriteData(void* buffer, uint32 size) { cemu_assert_unimplemented(); return 0; } virtual uint32 fscReadData(void* buffer, uint32 size) { cemu_assert_unimplemented(); return 0; } virtual void fscSetSeek(uint64 seek) { cemu_assert_unimplemented(); } virtual uint64 fscGetSeek() { cemu_assert_unimplemented(); return 0; } virtual void fscSetFileLength(uint64 endOffset) { cemu_assert_unimplemented(); } virtual bool fscDirNext(FSCDirEntry* dirEntry) { cemu_assert_unimplemented(); return false; } virtual bool fscRewindDir() { cemu_assert_unimplemented(); return false; } FSCDirIteratorState* dirIterator{}; bool m_isAppend{ false }; }; #define FSC_PRIORITY_BASE (0) #define FSC_PRIORITY_AOC (1) #define FSC_PRIORITY_PATCH (2) #define FSC_PRIORITY_REDIRECT (3) #define FSC_PRIORITY_MAX (3) #define FSC_PRIORITY_COUNT (4) void fsc_init(); sint32 fsc_mount(std::string_view mountPath, std::string_view targetPath, fscDeviceC* fscDevice, void* ctx, sint32 priority=0); bool fsc_unmount(std::string_view mountPath, sint32 priority); void fsc_unmountAll(); FSCVirtualFile* fsc_open(const char* path, FSC_ACCESS_FLAG accessFlags, sint32* fscStatus, sint32 maxPriority=FSC_PRIORITY_MAX); FSCVirtualFile* fsc_openDirIterator(const char* path, sint32* fscStatus); bool fsc_createDir(const char* path, sint32* fscStatus); bool fsc_rename(const char* srcPath, const char* dstPath, sint32* fscStatus); bool fsc_remove(const char* path, sint32* fscStatus); bool fsc_nextDir(FSCVirtualFile* fscFile, FSCDirEntry* dirEntry); void fsc_close(FSCVirtualFile* fscFile); uint32 fsc_getFileSize(FSCVirtualFile* fscFile); uint32 fsc_getFileSeek(FSCVirtualFile* fscFile); void fsc_setFileSeek(FSCVirtualFile* fscFile, uint32 newSeek); void fsc_setFileLength(FSCVirtualFile* fscFile, uint32 newEndOffset); bool fsc_isDirectory(FSCVirtualFile* fscFile); bool fsc_isFile(FSCVirtualFile* fscFile); bool fsc_isWritable(FSCVirtualFile* fscFile); uint32 fsc_readFile(FSCVirtualFile* fscFile, void* buffer, uint32 size); uint32 fsc_writeFile(FSCVirtualFile* fscFile, void* buffer, uint32 size); uint8* fsc_extractFile(const char* path, uint32* fileSize, sint32 maxPriority = FSC_PRIORITY_MAX); std::optional<std::vector<uint8>> fsc_extractFile(const char* path, sint32 maxPriority = FSC_PRIORITY_MAX); bool fsc_doesFileExist(const char* path, sint32 maxPriority = FSC_PRIORITY_MAX); bool fsc_doesDirectoryExist(const char* path, sint32 maxPriority = FSC_PRIORITY_MAX); // wud device bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class FSTVolume* mountedVolume, sint32 priority); // wua device bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority); // wuhb device bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class WUHBReader* wuhbReader, sint32 priority); // hostFS device bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority); // redirect device void fscDeviceRedirect_map(); void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority); ================================================ FILE: src/Cafe/Filesystem/fscDeviceHostFS.cpp ================================================ #include "config/ActiveSettings.h" #include "Cafe/Filesystem/fsc.h" #include "Cafe/Filesystem/fscDeviceHostFS.h" #include "Common/FileStream.h" /* FSCVirtualFile implementation for HostFS */ FSCVirtualFile_Host::~FSCVirtualFile_Host() { if (m_type == FSC_TYPE_FILE) delete m_fs; } sint32 FSCVirtualFile_Host::fscGetType() { return m_type; } uint32 FSCVirtualFile_Host::fscDeviceHostFSFile_getFileSize() { if (m_type == FSC_TYPE_FILE) { if (m_fileSize > 0xFFFFFFFFULL) cemu_assert_suspicious(); // files larger than 4GB are not supported by Wii U filesystem return (uint32)m_fileSize; } else if (m_type == FSC_TYPE_DIRECTORY) { // todo return (uint32)0; } return 0; } uint64 FSCVirtualFile_Host::fscQueryValueU64(uint32 id) { if (m_type == FSC_TYPE_FILE) { if (id == FSC_QUERY_SIZE) return fscDeviceHostFSFile_getFileSize(); else if (id == FSC_QUERY_WRITEABLE) return m_isWritable; else cemu_assert_unimplemented(); } else if (m_type == FSC_TYPE_DIRECTORY) { if (id == FSC_QUERY_SIZE) return fscDeviceHostFSFile_getFileSize(); else cemu_assert_unimplemented(); } cemu_assert_unimplemented(); return 0; } uint32 FSCVirtualFile_Host::fscWriteData(void* buffer, uint32 size) { if (m_type != FSC_TYPE_FILE) return 0; if (size >= (2UL * 1024UL * 1024UL * 1024UL)) { cemu_assert_suspicious(); return 0; } sint32 writtenBytes = m_fs->writeData(buffer, (sint32)size); m_seek += (uint64)writtenBytes; m_fileSize = std::max(m_fileSize, m_seek); return (uint32)writtenBytes; } uint32 FSCVirtualFile_Host::fscReadData(void* buffer, uint32 size) { if (m_type != FSC_TYPE_FILE) return 0; if (size >= (2UL * 1024UL * 1024UL * 1024UL)) { cemu_assert_suspicious(); return 0; } uint32 bytesLeft = (uint32)(m_fileSize - m_seek); bytesLeft = std::min(bytesLeft, 0x7FFFFFFFu); sint32 bytesToRead = std::min(bytesLeft, size); uint32 bytesRead = m_fs->readData(buffer, bytesToRead); m_seek += bytesRead; return bytesRead; } void FSCVirtualFile_Host::fscSetSeek(uint64 seek) { if (m_type != FSC_TYPE_FILE) return; this->m_seek = seek; cemu_assert_debug(seek <= m_fileSize); m_fs->SetPosition(seek); } uint64 FSCVirtualFile_Host::fscGetSeek() { if (m_type != FSC_TYPE_FILE) return 0; return m_seek; } void FSCVirtualFile_Host::fscSetFileLength(uint64 endOffset) { if (m_type != FSC_TYPE_FILE) return; m_fs->SetPosition(endOffset); bool r = m_fs->SetEndOfFile(); m_seek = std::min(m_seek, endOffset); m_fileSize = m_seek; m_fs->SetPosition(m_seek); if (!r) cemuLog_log(LogType::Force, "fscSetFileLength: Failed to set size to 0x{:x}", endOffset); } bool FSCVirtualFile_Host::fscDirNext(FSCDirEntry* dirEntry) { if (m_type != FSC_TYPE_DIRECTORY) return false; if (!m_dirIterator) { // init iterator on first iteration attempt m_dirIterator.reset(new fs::directory_iterator(*m_path)); if (!m_dirIterator) { cemuLog_log(LogType::Force, "Failed to iterate directory: {}", _pathToUtf8(*m_path)); return false; } } if (*m_dirIterator == fs::end(*m_dirIterator)) return false; const fs::directory_entry& entry = **m_dirIterator; std::string fileName = entry.path().filename().generic_string(); if (fileName.size() >= sizeof(dirEntry->path) - 1) fileName.resize(sizeof(dirEntry->path) - 1); strncpy(dirEntry->path, fileName.data(), sizeof(dirEntry->path)); if (entry.is_directory()) { dirEntry->isDirectory = true; dirEntry->isFile = false; dirEntry->fileSize = 0; } else { dirEntry->isDirectory = false; dirEntry->isFile = true; dirEntry->fileSize = entry.file_size(); } (*m_dirIterator)++; return true; } FSCVirtualFile* FSCVirtualFile_Host::OpenFile(const fs::path& path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus) { if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && !HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) cemu_assert_debug(false); // not allowed. At least one of both flags must be set // attempt to open as file if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) { FileStream* fs{}; bool writeAccessRequested = HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION); if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::FILE_ALLOW_CREATE)) { fs = FileStream::openFile2(path, writeAccessRequested); if (!fs) { cemu_assert_debug(writeAccessRequested); fs = FileStream::createFile2(path); if (!fs) cemuLog_log(LogType::Force, "FSC: File create failed for {}", _pathToUtf8(path)); } } else if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE)) { fs = FileStream::createFile2(path); if (!fs) cemuLog_log(LogType::Force, "FSC: File create failed for {}", _pathToUtf8(path)); } else { fs = FileStream::openFile2(path, writeAccessRequested); } if (fs) { FSCVirtualFile_Host* vf = new FSCVirtualFile_Host(FSC_TYPE_FILE); vf->m_fs = fs; vf->m_isWritable = writeAccessRequested; vf->m_fileSize = fs->GetSize(); fscStatus = FSC_STATUS_OK; return vf; } } // attempt to open as directory if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) { std::error_code ec; bool isExistingDir = fs::is_directory(path, ec); if (isExistingDir) { FSCVirtualFile_Host* vf = new FSCVirtualFile_Host(FSC_TYPE_DIRECTORY); vf->m_path.reset(new std::filesystem::path(path)); fscStatus = FSC_STATUS_OK; return vf; } } fscStatus = FSC_STATUS_FILE_NOT_FOUND; return nullptr; } /* Device implementation */ class fscDeviceHostFSC : public fscDeviceC { public: FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override { *fscStatus = FSC_STATUS_OK; FSCVirtualFile* vf = FSCVirtualFile_Host::OpenFile(_utf8ToPath(path), accessFlags, *fscStatus); cemu_assert_debug((bool)vf == (*fscStatus == FSC_STATUS_OK)); return vf; } bool fscDeviceCreateDir(std::string_view path, void* ctx, sint32* fscStatus) override { fs::path dirPath = _utf8ToPath(path); if (fs::exists(dirPath)) { if (!fs::is_directory(dirPath)) cemuLog_log(LogType::Force, "CreateDir: {} already exists but is not a directory", path); *fscStatus = FSC_STATUS_ALREADY_EXISTS; return false; } std::error_code ec; bool r = fs::create_directories(dirPath, ec); if (!r) cemuLog_log(LogType::Force, "CreateDir: Failed to create {}", path); *fscStatus = FSC_STATUS_OK; return true; } bool fscDeviceRemoveFileOrDir(std::string_view path, void* ctx, sint32* fscStatus) override { *fscStatus = FSC_STATUS_OK; fs::path _path = _utf8ToPath(path); std::error_code ec; if (!fs::exists(_path, ec)) { *fscStatus = FSC_STATUS_FILE_NOT_FOUND; return false; } if (!fs::remove(_path, ec)) { cemu_assert_unimplemented(); // return correct error (e.g. if directory is non-empty) *fscStatus = FSC_STATUS_FILE_NOT_FOUND; } return true; } bool fscDeviceRename(std::string_view srcPath, std::string_view dstPath, void* ctx, sint32* fscStatus) override { *fscStatus = FSC_STATUS_OK; fs::path _srcPath = _utf8ToPath(srcPath); fs::path _dstPath = _utf8ToPath(dstPath); std::error_code ec; if (!fs::exists(_srcPath, ec)) { *fscStatus = FSC_STATUS_FILE_NOT_FOUND; return false; } fs::rename(_srcPath, _dstPath, ec); return true; } // singleton public: static fscDeviceHostFSC& instance() { static fscDeviceHostFSC _instance; return _instance; } }; bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority) { return fsc_mount(mountPath, hostTargetPath, &fscDeviceHostFSC::instance(), nullptr, priority) == FSC_STATUS_OK; } ================================================ FILE: src/Cafe/Filesystem/fscDeviceHostFS.h ================================================ #include "Cafe/Filesystem/fsc.h" class FSCVirtualFile_Host : public FSCVirtualFile { public: static FSCVirtualFile* OpenFile(const fs::path& path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus); ~FSCVirtualFile_Host() override; sint32 fscGetType() override; uint32 fscDeviceHostFSFile_getFileSize(); uint64 fscQueryValueU64(uint32 id) override; uint32 fscWriteData(void* buffer, uint32 size) override; uint32 fscReadData(void* buffer, uint32 size) override; void fscSetSeek(uint64 seek) override; uint64 fscGetSeek() override; void fscSetFileLength(uint64 endOffset) override; bool fscDirNext(FSCDirEntry* dirEntry) override; private: FSCVirtualFile_Host(uint32 type) : m_type(type) {}; private: uint32 m_type; // FSC_TYPE_* class FileStream* m_fs{}; // file uint64 m_seek{ 0 }; uint64 m_fileSize{ 0 }; bool m_isWritable{ false }; // directory std::unique_ptr<std::filesystem::path> m_path{}; std::unique_ptr<std::filesystem::directory_iterator> m_dirIterator{}; }; ================================================ FILE: src/Cafe/Filesystem/fscDeviceRedirect.cpp ================================================ #include "util/helpers/helpers.h" #include "Cafe/Filesystem/fscDeviceHostFS.h" #include "FST/fstUtil.h" struct RedirectEntry { RedirectEntry(const fs::path& dstPath, sint32 priority) : dstPath(dstPath), priority(priority) {} fs::path dstPath; sint32 priority; }; FSAFileTree<RedirectEntry> redirectTree; void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority) { // check if source already has a redirection RedirectEntry* existingEntry; if (redirectTree.getFile(virtualSourcePath, existingEntry)) { if (existingEntry->priority >= priority) return; // dont replace entries with equal or higher priority // unregister existing entry redirectTree.removeFile(virtualSourcePath); delete existingEntry; } RedirectEntry* entry = new RedirectEntry(targetFilePath, priority); redirectTree.addFile(virtualSourcePath, fileSize, entry); } class fscDeviceTypeRedirect : public fscDeviceC { FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override { RedirectEntry* redirectionEntry; if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && redirectTree.getFile(path, redirectionEntry)) return FSCVirtualFile_Host::OpenFile(redirectionEntry->dstPath, accessFlags, *fscStatus); FSCVirtualFile* dirIterator; if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR) && redirectTree.getDirectory(path, dirIterator)) return dirIterator; return nullptr; } public: static fscDeviceTypeRedirect& instance() { static fscDeviceTypeRedirect _instance; return _instance; } }; bool _redirectMapped = false; void fscDeviceRedirect_map() { if (_redirectMapped) return; fsc_mount("/", "/", &fscDeviceTypeRedirect::instance(), nullptr, FSC_PRIORITY_REDIRECT); _redirectMapped = true; } ================================================ FILE: src/Cafe/Filesystem/fscDeviceWua.cpp ================================================ #include "Cafe/Filesystem/fsc.h" #include <zarchive/zarchivereader.h> class FSCDeviceWuaFileCtx : public FSCVirtualFile { friend class fscDeviceWUAC; protected: FSCDeviceWuaFileCtx(ZArchiveReader* archive, ZArchiveNodeHandle fstFileHandle, uint32 fscType) { this->m_archive = archive; this->m_fscType = fscType; this->m_nodeHandle = fstFileHandle; this->m_seek = 0; }; public: sint32 fscGetType() override { return m_fscType; } uint32 fscDeviceWuaFile_getFileSize() { return (uint32)m_archive->GetFileSize(m_nodeHandle); } uint64 fscQueryValueU64(uint32 id) override { if (m_fscType == FSC_TYPE_FILE) { if (id == FSC_QUERY_SIZE) return fscDeviceWuaFile_getFileSize(); else if (id == FSC_QUERY_WRITEABLE) return 0; // WUD images are read-only else cemu_assert_error(); } else { cemu_assert_unimplemented(); } return 0; } uint32 fscWriteData(void* buffer, uint32 size) override { cemu_assert_error(); return 0; } uint32 fscReadData(void* buffer, uint32 size) override { if (m_fscType != FSC_TYPE_FILE) return 0; cemu_assert(size < (2ULL * 1024 * 1024 * 1024)); // single read operation larger than 2GiB not supported uint32 bytesLeft = fscDeviceWuaFile_getFileSize() - m_seek; uint32 bytesToRead = (std::min)(bytesLeft, (uint32)size); uint32 bytesSuccessfullyRead = (uint32)m_archive->ReadFromFile(m_nodeHandle, m_seek, bytesToRead, buffer); m_seek += bytesSuccessfullyRead; return bytesSuccessfullyRead; } void fscSetSeek(uint64 seek) override { if (m_fscType != FSC_TYPE_FILE) return; cemu_assert_debug(seek <= 0xFFFFFFFFULL); this->m_seek = (uint32)seek; } uint64 fscGetSeek() override { if (m_fscType != FSC_TYPE_FILE) return 0; return m_seek; } bool fscDirNext(FSCDirEntry* dirEntry) override { if (m_fscType != FSC_TYPE_DIRECTORY) return false; ZArchiveReader::DirEntry zarDirEntry; if (!m_archive->GetDirEntry(m_nodeHandle, m_iteratorIndex, zarDirEntry)) return false; m_iteratorIndex++; if (zarDirEntry.isDirectory) { dirEntry->isDirectory = true; dirEntry->isFile = false; dirEntry->fileSize = 0; } else if(zarDirEntry.isFile) { dirEntry->isDirectory = false; dirEntry->isFile = true; dirEntry->fileSize = (uint32)zarDirEntry.size; } else { cemu_assert_suspicious(); } std::memset(dirEntry->path, 0, sizeof(dirEntry->path)); std::strncpy(dirEntry->path, zarDirEntry.name.data(), std::min(sizeof(dirEntry->path) - 1, zarDirEntry.name.size())); return true; } private: ZArchiveReader* m_archive{nullptr}; sint32 m_fscType; ZArchiveNodeHandle m_nodeHandle; // file uint32 m_seek{0}; // directory uint32 m_iteratorIndex{0}; }; class fscDeviceWUAC : public fscDeviceC { FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override { ZArchiveReader* archive = (ZArchiveReader*)ctx; cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to WUA is not supported ZArchiveNodeHandle fileHandle = archive->LookUp(path, true, true); if (fileHandle == ZARCHIVE_INVALID_NODE) { *fscStatus = FSC_STATUS_FILE_NOT_FOUND; return nullptr; } if (archive->IsFile(fileHandle)) { if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) { *fscStatus = FSC_STATUS_FILE_NOT_FOUND; return nullptr; } *fscStatus = FSC_STATUS_OK; return new FSCDeviceWuaFileCtx(archive, fileHandle, FSC_TYPE_FILE); } else if (archive->IsDirectory(fileHandle)) { if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) { *fscStatus = FSC_STATUS_FILE_NOT_FOUND; return nullptr; } *fscStatus = FSC_STATUS_OK; return new FSCDeviceWuaFileCtx(archive, fileHandle, FSC_TYPE_DIRECTORY); } else { cemu_assert_suspicious(); } *fscStatus = FSC_STATUS_FILE_NOT_FOUND; return nullptr; } // singleton public: static fscDeviceWUAC& instance() { static fscDeviceWUAC _instance; return _instance; } }; bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, ZArchiveReader* archive, sint32 priority) { return fsc_mount(mountPath, destinationBaseDir, &fscDeviceWUAC::instance(), archive, priority) == FSC_STATUS_OK; } ================================================ FILE: src/Cafe/Filesystem/fscDeviceWud.cpp ================================================ #include "Cafe/Filesystem/fsc.h" #include "Cafe/Filesystem/FST/FST.h" class FSCDeviceWudFileCtx : public FSCVirtualFile { friend class fscDeviceWUDC; protected: FSCDeviceWudFileCtx(FSTVolume* _volume, FSTFileHandle _fstFileHandle) { this->m_volume = _volume; this->m_fscType = FSC_TYPE_FILE; this->m_fstFileHandle = _fstFileHandle; this->m_seek = 0; }; FSCDeviceWudFileCtx(FSTVolume* _volume, FSTDirectoryIterator _dirIterator) { this->m_volume = _volume; this->m_fscType = FSC_TYPE_DIRECTORY; this->m_dirIterator = _dirIterator; } public: sint32 fscGetType() override { return m_fscType; } uint32 fscDeviceWudFile_getFileSize() { return m_volume->GetFileSize(m_fstFileHandle); } uint64 fscQueryValueU64(uint32 id) override { if (m_fscType == FSC_TYPE_FILE) { if (id == FSC_QUERY_SIZE) return fscDeviceWudFile_getFileSize(); else if (id == FSC_QUERY_WRITEABLE) return 0; // WUD images are read-only else cemu_assert_error(); } else { cemu_assert_unimplemented(); } return 0; } uint32 fscWriteData(void* buffer, uint32 size) override { cemu_assert_error(); return 0; } uint32 fscReadData(void* buffer, uint32 size) override { if (m_fscType != FSC_TYPE_FILE) return 0; cemu_assert(size < (2ULL * 1024 * 1024 * 1024)); // single read operation larger than 2GiB not supported uint32 bytesLeft = fscDeviceWudFile_getFileSize() - m_seek; uint32 bytesToRead = (std::min)(bytesLeft, (uint32)size); uint32 bytesSuccessfullyRead = m_volume->ReadFile(m_fstFileHandle, m_seek, bytesToRead, buffer); m_seek += bytesSuccessfullyRead; return bytesSuccessfullyRead; } void fscSetSeek(uint64 seek) override { if (m_fscType != FSC_TYPE_FILE) return; cemu_assert_debug(seek <= 0xFFFFFFFFULL); this->m_seek = (uint32)seek; } uint64 fscGetSeek() override { if (m_fscType != FSC_TYPE_FILE) return 0; return m_seek; } bool fscDirNext(FSCDirEntry* dirEntry) override { if (m_fscType != FSC_TYPE_DIRECTORY) return false; FSTFileHandle entryItr; if (!m_volume->Next(m_dirIterator, entryItr)) return false; if (m_volume->IsDirectory(entryItr)) { dirEntry->isDirectory = true; dirEntry->isFile = false; dirEntry->fileSize = 0; } else { dirEntry->isDirectory = false; dirEntry->isFile = true; dirEntry->fileSize = m_volume->GetFileSize(entryItr); } auto path = m_volume->GetName(entryItr); std::memset(dirEntry->path, 0, sizeof(dirEntry->path)); std::strncpy(dirEntry->path, path.data(), std::min(sizeof(dirEntry->path) - 1, path.size())); return true; } private: FSTVolume* m_volume{nullptr}; sint32 m_fscType; FSTFileHandle m_fstFileHandle; // file uint32 m_seek{0}; // directory FSTDirectoryIterator m_dirIterator{}; }; class fscDeviceWUDC : public fscDeviceC { FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override { FSTVolume* mountedVolume = (FSTVolume*)ctx; cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to FST is never allowed if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) { FSTFileHandle fstFileHandle; if (mountedVolume->OpenFile(path, fstFileHandle, true) && !mountedVolume->HasLinkFlag(fstFileHandle)) { *fscStatus = FSC_STATUS_OK; return new FSCDeviceWudFileCtx(mountedVolume, fstFileHandle); } } if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) { FSTDirectoryIterator dirIterator; if (mountedVolume->OpenDirectoryIterator(path, dirIterator) && !mountedVolume->HasLinkFlag(dirIterator.GetDirHandle())) { *fscStatus = FSC_STATUS_OK; return new FSCDeviceWudFileCtx(mountedVolume, dirIterator); } } *fscStatus = FSC_STATUS_FILE_NOT_FOUND; return nullptr; } // singleton public: static fscDeviceWUDC& instance() { static fscDeviceWUDC _instance; return _instance; } }; bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destinationBaseDir, FSTVolume* mountedVolume, sint32 priority) { return fsc_mount(mountPath, destinationBaseDir, &fscDeviceWUDC::instance(), mountedVolume, priority) == FSC_STATUS_OK; } ================================================ FILE: src/Cafe/Filesystem/fscDeviceWuhb.cpp ================================================ #include "Filesystem/WUHB/WUHBReader.h" #include "Cafe/Filesystem/fsc.h" #include "Cafe/Filesystem/FST/FST.h" class FSCDeviceWuhbFileCtx : public FSCVirtualFile { public: FSCDeviceWuhbFileCtx(WUHBReader* reader, uint32 entryOffset, uint32 fscType) : m_wuhbReader(reader), m_entryOffset(entryOffset), m_fscType(fscType) { cemu_assert(entryOffset != ROMFS_ENTRY_EMPTY); if (fscType == FSC_TYPE_DIRECTORY) { romfs_direntry_t entry = reader->GetDirEntry(entryOffset); m_dirIterOffset = entry.dirListHead; m_fileIterOffset = entry.fileListHead; } } sint32 fscGetType() override { return m_fscType; } uint64 fscQueryValueU64(uint32 id) override { if (m_fscType == FSC_TYPE_FILE) { if (id == FSC_QUERY_SIZE) return m_wuhbReader->GetFileSize(m_entryOffset); else if (id == FSC_QUERY_WRITEABLE) return 0; // WUHB images are read-only else cemu_assert_error(); } else { cemu_assert_unimplemented(); } return 0; } uint32 fscWriteData(void* buffer, uint32 size) override { cemu_assert_error(); return 0; } uint32 fscReadData(void* buffer, uint32 size) override { if (m_fscType != FSC_TYPE_FILE) return 0; auto read = m_wuhbReader->ReadFromFile(m_entryOffset, m_seek, size, buffer); m_seek += read; return read; } void fscSetSeek(uint64 seek) override { m_seek = seek; } uint64 fscGetSeek() override { if (m_fscType != FSC_TYPE_FILE) return 0; return m_seek; } void fscSetFileLength(uint64 endOffset) override { cemu_assert_error(); } bool fscDirNext(FSCDirEntry* dirEntry) override { if (m_dirIterOffset != ROMFS_ENTRY_EMPTY) { romfs_direntry_t entry = m_wuhbReader->GetDirEntry(m_dirIterOffset); m_dirIterOffset = entry.listNext; if(entry.name_size > 0) { dirEntry->isDirectory = true; dirEntry->isFile = false; dirEntry->fileSize = 0; std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH); return true; } } if (m_fileIterOffset != ROMFS_ENTRY_EMPTY) { romfs_fentry_t entry = m_wuhbReader->GetFileEntry(m_fileIterOffset); m_fileIterOffset = entry.listNext; if(entry.name_size > 0) { dirEntry->isDirectory = false; dirEntry->isFile = true; dirEntry->fileSize = entry.size; std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH); return true; } } return false; } private: WUHBReader* m_wuhbReader{}; uint32 m_fscType; uint32 m_entryOffset = ROMFS_ENTRY_EMPTY; uint32 m_dirIterOffset = ROMFS_ENTRY_EMPTY; uint32 m_fileIterOffset = ROMFS_ENTRY_EMPTY; uint64 m_seek = 0; }; class fscDeviceWUHB : public fscDeviceC { FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override { WUHBReader* reader = (WUHBReader*)ctx; cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to WUHB is not supported bool isFile; uint32 table_offset = ROMFS_ENTRY_EMPTY; if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) { table_offset = reader->Lookup(path, false); isFile = false; } if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) { table_offset = reader->Lookup(path, true); isFile = true; } if (table_offset == ROMFS_ENTRY_EMPTY) { *fscStatus = FSC_STATUS_FILE_NOT_FOUND; return nullptr; } *fscStatus = FSC_STATUS_OK; return new FSCDeviceWuhbFileCtx(reader, table_offset, isFile ? FSC_TYPE_FILE : FSC_TYPE_DIRECTORY); } // singleton public: static fscDeviceWUHB& instance() { static fscDeviceWUHB _instance; return _instance; } }; bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, WUHBReader* wuhbReader, sint32 priority) { return fsc_mount(mountPath, destinationBaseDir, &fscDeviceWUHB::instance(), wuhbReader, priority) == FSC_STATUS_OK; } ================================================ FILE: src/Cafe/GamePatch.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "CafeSystem.h" #include "config/ActiveSettings.h" // Selectively add some patches based on network settings. void hleExport_breathOfTheWild_busyLoop(PPCInterpreter_t* hCPU) { uint32 queue7C = memory_readU32(hCPU->gpr[24] + 0x7C); uint32 queue80b = memory_readU8(hCPU->gpr[24] + 0x80); if (!(queue80b == 0 || hCPU->gpr[22] != 0 || queue7C > 0)) { PPCInterpreter_relinquishTimeslice(); } hCPU->gpr[6] = hCPU->gpr[29]; hCPU->instructionPointer += 4; } void hleExport_breathOfTheWild_busyLoop2(PPCInterpreter_t* hCPU) { uint32 queue7C = memory_readU32(hCPU->gpr[24] + 0x7C); uint32 queue80b = memory_readU8(hCPU->gpr[24] + 0x80); if (!(queue80b == 0 || hCPU->gpr[22] != 0 || queue7C > 0)) { PPCInterpreter_relinquishTimeslice(); } hCPU->gpr[12] = hCPU->gpr[29]; hCPU->instructionPointer += 4; } void hleExport_ffl_swapEndianFloatArray(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(valueArray, uint32, 0); ppcDefineParamS32(valueCount, 1); for (sint32 i = 0; i < valueCount; i++) { valueArray[i] = _swapEndianU32(valueArray[i]); } osLib_returnFromFunction(hCPU, 0); } typedef struct { std::atomic<uint32be> count; uint32be ownerThreadId; uint32 ukn08; }xcxCS_t; void hleExport_xcx_enterCriticalSection(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(xcxCS, xcxCS_t, 0); uint32 threadId = MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(); cemu_assert_debug(xcxCS->ukn08 != 0); cemu_assert_debug(threadId); if (xcxCS->ownerThreadId == (uint32be)threadId) { xcxCS->count.store(xcxCS->count.load() + 1); osLib_returnFromFunction(hCPU, 0); return; } // quick check uint32be newCount = xcxCS->count.load() + 1; uint32be expectedCount = 0; if(xcxCS->count.compare_exchange_strong(expectedCount, newCount)) { xcxCS->ownerThreadId = threadId; osLib_returnFromFunction(hCPU, 0); return; } // spinloop for a bit to reduce the time we occupy the scheduler lock (via PPCCore_switchToScheduler) while (true) { for (sint32 i = 0; i < 50; i++) { if (xcxCS->count.compare_exchange_strong(expectedCount, newCount)) { xcxCS->ownerThreadId = threadId; osLib_returnFromFunction(hCPU, 0); return; } _mm_pause(); } PPCCore_switchToScheduler(); } osLib_returnFromFunction(hCPU, 0); } bool mh3u_raceConditionWorkaround = true; void hleExport_mh3u_raceConditionWorkaround(PPCInterpreter_t* hCPU) // new style HLE method, does not need entry in hle_load (but can only be reached via BL and not the usual HLE instruction) { uint8 b = memory_readU8(hCPU->gpr[3] + 0x3E5); b ^= 1; if (mh3u_raceConditionWorkaround) { b = 0; mh3u_raceConditionWorkaround = false; } osLib_returnFromFunction(hCPU, b); } void hleExport_pmcs_yellowPaintStarCrashWorkaround(PPCInterpreter_t* hCPU) { hCPU->gpr[7] = hCPU->gpr[3] * 4; MPTR parentLR = memory_readU32(hCPU->gpr[1] + 0x4C); if (hCPU->gpr[3] >= 0x00800000) { hCPU->instructionPointer = parentLR; hCPU->gpr[1] += 0x48; hCPU->gpr[3] = 0; return; } hCPU->instructionPointer = hCPU->spr.LR; } uint8 hleSignature_wwhd_0173B2A0[] = {0x8D,0x43,0x00,0x01,0x7C,0xC9,0x52,0x78,0x55,0x2C,0x15,0xBA,0x7C,0x0C,0x28,0x2E,0x54,0xC8,0xC2,0x3E,0x7D,0x06,0x02,0x78,0x42,0x00,0xFF,0xE8,0x7C,0xC3,0x30,0xF8}; void hle_scan(uint8* data, sint32 dataLength, char* hleFunctionName) { sint32 functionIndex = osLib_getFunctionIndex("hle", hleFunctionName); if( functionIndex < 0 ) { debug_printf("HLE function unknown\n"); return; } uint8* scanStart = memory_getPointerFromVirtualOffset(0x01000000); uint8* scanEnd = scanStart + 0x0F000000 - dataLength; uint8* scanCurrent = scanStart; while( scanCurrent < scanEnd ) { if( memcmp(scanCurrent, data, dataLength) == 0 ) { uint32 offset = (uint32)(scanCurrent - scanStart) + 0x01000000; debug_printf("HLE signature for '%s' found at 0x%08x\n", hleFunctionName, offset); uint32 opcode = (1<<26)|(functionIndex+0x1000); // opcode for HLE: 0x1000 + FunctionIndex memory_write<uint32>(offset, opcode); break; } scanCurrent += 4; } } MPTR hle_locate(uint8* data, sint32 dataLength) { uint8* scanStart = memory_getPointerFromVirtualOffset(0x01000000); uint8* scanEnd = scanStart + 0x0F000000 - dataLength; uint8* scanCurrent = scanStart; while( scanCurrent < scanEnd ) { if( memcmp(scanCurrent, data, dataLength) == 0 ) { return memory_getVirtualOffsetFromPointer(scanCurrent); } scanCurrent += 4; } return MPTR_NULL; } bool compareMasked(uint8* mem, uint8* compare, uint8* mask, sint32 length) { while( length ) { uint8 m = *mask; if( (*mem&m) != (*compare&m) ) return false; mem++; compare++; mask++; length--; } return true; } MPTR hle_locate(uint8* data, uint8* mask, sint32 dataLength) { uint8* scanStart = memory_getPointerFromVirtualOffset(MEMORY_CODEAREA_ADDR); uint8* scanEnd = memory_getPointerFromVirtualOffset(RPLLoader_GetMaxCodeOffset() - dataLength); uint8* scanCurrent = scanStart; if( mask ) { if (dataLength >= 4 && *(uint32*)mask == 0xFFFFFFFF) { // fast path uint32 firstDword = *(uint32*)data; while (scanCurrent < scanEnd) { if (*(uint32*)scanCurrent == firstDword && compareMasked(scanCurrent, data, mask, dataLength)) { return memory_getVirtualOffsetFromPointer(scanCurrent); } scanCurrent += 4; } } else { #ifdef CEMU_DEBUG_ASSERT if (mask[0] != 0xFF) assert_dbg(); #endif uint8 firstByte = data[0]; while (scanCurrent < scanEnd) { if (scanCurrent[0] == firstByte && compareMasked(scanCurrent, data, mask, dataLength)) { return memory_getVirtualOffsetFromPointer(scanCurrent); } scanCurrent += 4; } } } else { while( scanCurrent < scanEnd ) { if( memcmp(scanCurrent, data, dataLength) == 0 ) { return memory_getVirtualOffsetFromPointer(scanCurrent); } scanCurrent += 4; } } return MPTR_NULL; } uint8 xcx_gpuHangDetection_degradeFramebuffer[] = {0x3B,0x39,0x00,0x01,0x28,0x19,0x4E,0x20,0x40,0x81,0x00,0x44}; uint8 xcx_framebufferReductionSignature[] = {0x80,0xC9,0x00,0x1C,0x38,0xA0,0x00,0x01,0x80,0x7E,0x00,0x80,0x80,0x9E,0x02,0xEC,0x80,0xE9,0x00,0x20,0x48,0x06,0x1E,0xD1,0x7E,0x73,0x1B,0x78}; uint8 xcx_framebufferReductionMask[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF}; uint8 botw_busyLoopSignature[] = {0x80,0xE6,0x00,0x00,0x2C,0x07,0x00,0x01,0x41,0x82,0xFF,0xF8,0x7D,0x00,0x30,0x28,0x2C,0x08,0x00,0x00,0x40,0x82,0xFF,0xF8,0x7C,0x00,0x30,0x6C,0x39,0x00,0x00,0x01,0x7D,0x00,0x31,0x2D,0x40,0x82,0xFF,0xE8 }; uint8 botw_busyLoopMask[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; uint8 botw_busyLoopSignature2[] = {0x80,0x0C,0x00,0x00,0x2C,0x00,0x00,0x01,0x41,0x82,0xFF,0xF8,0x7C,0xA0,0x60,0x28,0x2C,0x05,0x00,0x00,0x40,0x82,0xFF,0xF8,0x7C,0x00,0x60,0x6C,0x38,0x80,0x00,0x01,0x7C,0x80,0x61,0x2D,0x40,0x82,0xFF,0xE8}; uint8 botw_busyLoopMask2[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; uint8 botw_crashFuncSignature[] = { 0x94,0x21,0xFF,0xD8,0x7C,0x08,0x02,0xA6,0xBF,0x41,0x00,0x10,0x7C,0xC7,0x33,0x78,0x7C,0xBE,0x2B,0x78,0x90,0x01,0x00,0x2C,0x7C,0x9D,0x23,0x78,0x38,0x00,0x00,0x00,0x7F,0xC6,0xF3,0x78,0x38,0x81,0x00,0x0C,0x90,0x01,0x00,0x0C,0x38,0xA1,0x00,0x08 }; uint8 botw_crashFuncMask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; uint8 ffl_floatArrayEndianSwap[] = { 0x7C,0x08,0x02,0xA6,0x94,0x21,0xFF,0xE8,0x93,0xC1,0x00,0x10,0x7C,0x7E,0x1B,0x78,0x93,0xE1,0x00,0x14,0x93,0x81,0x00,0x08,0x7C,0x9F,0x23,0x78,0x93,0xA1,0x00,0x0C,0x90,0x01,0x00,0x1C,0x3B,0xA0,0x00,0x00,0x7C,0x1D,0xF8,0x40,0x40,0x80,0x00,0x20,0x57,0xBC,0x10,0x3A,0x7C,0x3E,0xE4,0x2E }; uint8 xcx_enterCriticalSectionSignature[] = { 0x94,0x21,0xFF,0xE0,0xBF,0x41,0x00,0x08,0x7C,0x08,0x02,0xA6,0x90,0x01,0x00,0x24,0x7C,0x7E,0x1B,0x78,0x80,0x1E,0x00,0x08,0x2C,0x00,0x00,0x00,0x41,0x82,0x00,0xC0,0x48,0x01,0xD7,0xA1,0x7C,0x7A,0x1B,0x79,0x41,0x82,0x00,0xB4,0x81,0x3E,0x00,0x04,0x7C,0x09,0xD0,0x40,0x40,0x82,0x00,0x2C,0x7D,0x20,0xF0,0x28,0x7C,0x00,0xF0,0x6C }; uint8 xcx_enterCriticalSectionMask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; uint8 smash4_softlockFixV0Signature[] = { 0x2C,0x03,0x00,0x00,0x41,0x82,0x00,0x20,0x38,0x60,0x00,0x0A,0x48,0x33,0xB8,0xAD,0x7F,0xA3,0xEB,0x78,0x7F,0xC4,0xF3,0x78,0x4B,0xFF,0xFF,0x09,0x2C,0x03,0x00,0x00 }; uint8 smash4_softlockFixV0Mask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF }; uint8 mh3u_raceConditionWorkaroundV0Signature[] = { 0x38,0x21,0x00,0x40,0x38,0x60,0x00,0x00,0x4E,0x80,0x00,0x20,0x80,0x7B,0xDB,0x9C,0x48,0x11,0x6B,0x81,0x2C,0x03,0x00,0x00 }; uint8 mh3u_raceConditionWorkaroundV0Mask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF }; uint8 pmcs_yellowPaintStarCrashV0Signature[] = { 0x94,0x21,0xFF,0xB8,0xBE,0x61,0x00,0x14,0x7C,0x08,0x02,0xA6,0x7C,0xB3,0x2B,0x78,0x90,0x01,0x00,0x4C,0x7C,0x9D,0x23,0x78,0x83,0x3D,0x00,0x0C,0x81,0x39,0x04,0xA8,0x54,0x67,0x10,0x3A,0x7F,0xC7,0x48,0x2E,0x83,0x1E,0x00,0xDC,0x82,0xF8,0x00,0x08,0x2C,0x17,0x00,0x00 }; //uint8 mh3u_raceConditionWorkaroundV0Mask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF }; uint8 bayo2_audioQueueFixSignature[] = { 0x80,0x03,0x00,0x3C,0x81,0x43,0x00,0x5C,0x81,0x83,0x00,0x40,0x55,0x48,0xB2,0xBE,0x3D,0x40,0x10,0x1D,0x7D,0x6C,0x42,0x14,0x39,0x4A,0x46,0xF0,0x7D,0x8B,0x00,0x50 }; uint8 bayo2_audioQueueFixMask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF }; uint8 sm3dw_dynFrameBufferResScale[] = { 0x94,0x21,0xFF,0xB8,0xBF,0x21,0x00,0x2C,0x7C,0x08,0x02,0xA6,0x90,0x01,0x00,0x4C,0x7C,0x7E,0x1B,0x78,0x81,0x7E,0x07,0xD8,0x38,0x80,0x00,0x02,0x38,0x6B,0x00,0x03 }; uint8 tww_waitFunc[] = { 0x7C,0x08,0x02,0xA6,0x94,0x21,0xFF,0xF0,0x93,0xE1,0x00,0x0C,0x7C,0x7F,0x1B,0x78,0x90,0x01,0x00,0x14,0x80,0x7F,0x02,0xE0,0x81,0x83,0x00,0x0C,0x80,0x0C,0x00,0x1C,0x7C,0x09,0x03,0xA6,0x38,0xA0,0x00,0x00,0x38,0x9F,0x03,0x68 }; static_assert(sizeof(xcx_enterCriticalSectionSignature) == sizeof(xcx_enterCriticalSectionMask), "xcx_enterCriticalSection signature and size mismatch"); static_assert(sizeof(bayo2_audioQueueFixSignature) == sizeof(bayo2_audioQueueFixMask), "bayo2_audioQueueFix signature and size mismatch"); uint8 cars3_avro_schema_incref[] = { 0x2C,0x03,0x00,0x00,0x94,0x21,0xFF,0xE8,0x41,0x82,0x00,0x40,0x39,0x03,0x00,0x08,0x39,0x41,0x00,0x08,0x91,0x01,0x00,0x08,0x7D,0x80,0x50,0x28,0x2C,0x0C,0xFF,0xFF,0x41,0x82,0x00,0x28,0x39,0x21,0x00,0x0C,0x38,0x0C,0x00,0x01,0x38,0xE0,0x00,0x01,0x91,0x01,0x00,0x0C,0x7C,0x00,0x49,0x2D }; // For USA titles: 000500301001610a / 000500301001410a uint8 miiverse_eshop_url_match_whitelist_func[] = { // For both titles, the code fully matches with the relative // branch targets, even if the absolute targets are different. 0x89,0x45,0x00,0x00, // lbz r10, 0x0(r5) 0x2c,0x0a,0x00,0x2e, // cmpwi r10, 0x2e 0x40,0x82,0x00,0x08, // bne LAB_020ff3a8 0x4b,0xff,0xff,0x5c // b FUN_020ff300 }; uint8 wave_libopenssl_ssl_verify_cert_chain[] = { 0x94,0x21,0xff,0x58,0x93,0xc1,0x00,0xa0,0x93,0xe1,0x00,0xa4,0x7c,0x9f,0x23,0x79,0x7c,0x7e,0x1b,0x78,0x90,0x01,0x00,0xac,0x41,0x82,0x00,0x14,0x7f,0xe3,0xfb,0x78 }; // rpl function for the above applets sint32 hleIndex_h000000001 = -1; sint32 hleIndex_h000000002 = -1; sint32 hleIndex_h000000003 = -1; sint32 hleIndex_h000000004 = -1; /* * Returns true for all HLE functions that do not jump to LR * Used by recompiler to determine function code flow */ bool GamePatch_IsNonReturnFunction(uint32 hleIndex) { if (hleIndex == hleIndex_h000000001) return true; if (hleIndex == hleIndex_h000000002) return true; if (hleIndex == hleIndex_h000000003) return false; if (hleIndex == hleIndex_h000000004) return false; return false; } void GamePatch_scan() { MPTR hleAddr; uint32 hleInstallStart = GetTickCount(); hleAddr = hle_locate(xcx_gpuHangDetection_degradeFramebuffer, NULL, sizeof(xcx_gpuHangDetection_degradeFramebuffer)); if( hleAddr ) { #ifdef CEMU_DEBUG_ASSERT cemuLog_log(LogType::Force, "HLE: XCX GPU hang detection"); #endif // remove the ADDI r25, r25, 1 instruction memory_writeU32(hleAddr, memory_readU32(hleAddr+4)); } hleAddr = hle_locate(xcx_framebufferReductionSignature, xcx_framebufferReductionMask, sizeof(xcx_framebufferReductionSignature)); if( hleAddr ) { #ifdef CEMU_DEBUG_ASSERT cemuLog_log(LogType::Force, "HLE: Prevent XCX rendertarget reduction"); #endif uint32 bl = memory_readU32(hleAddr+0x14); uint32 func_isReductionBuffer = hleAddr + 0x14 + (bl&0x3FFFFFC); // patch isReductionBuffer memory_writeU32(func_isReductionBuffer, 0x38600000); // LI R3, 0 memory_writeU32(func_isReductionBuffer+4, 0x4E800020); // BLR } hleIndex_h000000001 = osLib_getFunctionIndex("hle", "h000000001"); hleAddr = hle_locate(botw_busyLoopSignature, botw_busyLoopMask, sizeof(botw_busyLoopSignature)); if (hleAddr) { #ifdef CEMU_DEBUG_ASSERT cemuLog_log(LogType::Force, "HLE: Patch BotW busy loop 1 at 0x{:08x}", hleAddr); #endif sint32 functionIndex = hleIndex_h000000001; uint32 opcode = (1 << 26) | (functionIndex); // opcode for HLE: 0x1000 + FunctionIndex memory_write<uint32>(hleAddr - 4, opcode); } hleIndex_h000000002 = osLib_getFunctionIndex("hle", "h000000002"); hleAddr = hle_locate(botw_busyLoopSignature2, botw_busyLoopMask2, sizeof(botw_busyLoopSignature2)); if (hleAddr) { #ifdef CEMU_DEBUG_ASSERT cemuLog_log(LogType::Force, "HLE: Patch BotW busy loop 2 at 0x{:08x}", hleAddr); #endif sint32 functionIndex = hleIndex_h000000002; uint32 opcode = (1 << 26) | (functionIndex); // opcode for HLE: 0x1000 + FunctionIndex memory_write<uint32>(hleAddr - 4, opcode); } // FFL library float array endian conversion // original function needs invalid float values to remain intact between LFSX -> STFSX, which is not supported in recompiler mode hleIndex_h000000003 = osLib_getFunctionIndex("hle", "h000000003"); hleAddr = hle_locate(ffl_floatArrayEndianSwap, NULL, sizeof(ffl_floatArrayEndianSwap)); if (hleAddr) { cemuLog_logDebug(LogType::Force, "HLE: Hook FFL float array endian swap function at 0x{:08x}", hleAddr); sint32 functionIndex = hleIndex_h000000003; uint32 opcode = (1 << 26) | (functionIndex); // opcode for HLE: 0x1000 + FunctionIndex memory_write<uint32>(hleAddr, opcode); } // XCX freeze workaround //hleAddr = hle_locate(xcx_enterCriticalSectionSignature, xcx_enterCriticalSectionMask, sizeof(xcx_enterCriticalSectionSignature)); //if (hleAddr) //{ // cemuLog_logDebug(LogType::Force, "HLE: Hook XCX enterCriticalSection function at 0x{:08x}", hleAddr); // hleIndex_h000000004 = osLib_getFunctionIndex("hle", "h000000004"); // sint32 functionIndex = hleIndex_h000000004; // uint32 opcode = (1 << 26) | (functionIndex); // opcode for HLE: 0x1000 + FunctionIndex // memory_writeU32Direct(hleAddr, opcode); //} // MH3U race condition (tested for EU+US 1.2) hleAddr = hle_locate(mh3u_raceConditionWorkaroundV0Signature, mh3u_raceConditionWorkaroundV0Mask, sizeof(mh3u_raceConditionWorkaroundV0Mask)); if (hleAddr) { uint32 patchAddr = hleAddr + 0x10; cemuLog_log(LogType::Force, "HLE: Patch MH3U race condition candidate at 0x{:08x}", patchAddr); uint32 funcAddr = PPCInterpreter_makeCallableExportDepr(hleExport_mh3u_raceConditionWorkaround); // set absolute jump uint32 opc = 0x48000000; opc |= PPC_OPC_LK; opc |= PPC_OPC_AA; opc |= funcAddr; memory_writeU32(patchAddr, opc); } // Super Smash Bros softlock fix // fixes random softlocks that can occur after matches hleAddr = hle_locate(smash4_softlockFixV0Signature, smash4_softlockFixV0Mask, sizeof(smash4_softlockFixV0Signature)); if (hleAddr) { cemuLog_logDebug(LogType::Force, "Smash softlock fix: 0x{:08x}", hleAddr); memory_writeU32(hleAddr+0x20, memory_readU32(hleAddr+0x1C)); } // Color Splash Yellow paint star crash workaround // fixes the crash at the beginning of the dream sequence cutscene after collecting the yellow paint star hleAddr = hle_locate(pmcs_yellowPaintStarCrashV0Signature, nullptr, sizeof(pmcs_yellowPaintStarCrashV0Signature)); if (hleAddr) { cemuLog_logDebug(LogType::Force, "Color Splash crash fix: 0x{:08x}", hleAddr); uint32 funcAddr = PPCInterpreter_makeCallableExportDepr(hleExport_pmcs_yellowPaintStarCrashWorkaround); // set absolute jump uint32 opc = 0x48000000; opc |= PPC_OPC_LK; opc |= PPC_OPC_AA; opc |= funcAddr; memory_writeU32(hleAddr+0x20, opc); } // Bayonetta 2 sound queue patch (fixes audio starting to loop infinitely when there is stutter) hleAddr = hle_locate(bayo2_audioQueueFixSignature, bayo2_audioQueueFixMask, sizeof(bayo2_audioQueueFixSignature)); if (hleAddr) { // replace CMPL with CMP cemuLog_log(LogType::Force, "Patching Bayonetta 2 audio bug at: 0x{:08x}", hleAddr+0x34); uint32 opc = memory_readU32(hleAddr + 0x34); opc &= ~(0x3FF << 1); // turn CMPL to CMP memory_writeU32(hleAddr + 0x34, opc); } if (CafeSystem::GetRPXHashUpdated() == 0xb1c033dd) // Wind Waker US { uint32 p = memory_readU32(0x02813878); if (p == 0x40800018) { debug_printf("HLE: TWW US dsp kill channel patch\n"); uint32 li = 0x18; uint32 opcode = (li & 0x3FFFFFC) | (18 << 26); // replace BGE with B instruction memory_writeU32(0x02813878, opcode); } } else if (CafeSystem::GetRPXHashUpdated() == 0xCDC68ACD) // Wind Waker EU { uint32 p = memory_readU32(0x2814138); if (p == 0x40800018) { debug_printf("HLE: TWW EU dsp kill channel patch\n"); uint32 li = 0x18; uint32 opcode = (li & 0x3FFFFFC) | (18 << 26); // replace BGE with B instruction memory_writeU32(0x02814138, opcode); } } // disable SM3DW dynamic resolution scaling (fixes level 1-5 spamming lots of texture creates when gradually resizing framebuffer) hleAddr = hle_locate(sm3dw_dynFrameBufferResScale, nullptr, sizeof(sm3dw_dynFrameBufferResScale)); if (hleAddr) { cemuLog_log(LogType::Force, "Patching SM3DW dynamic resolution scaling at: 0x{:08x}", hleAddr); memory_writeU32(hleAddr, 0x4E800020); // BLR } // remove unnecessary lock from a wait function in TWW // this resolves a deadlock in singlecore mode hleAddr = hle_locate(tww_waitFunc, nullptr, sizeof(tww_waitFunc)); if (hleAddr) { cemuLog_log(LogType::Force, "Patching TWW race conditon at: 0x{:08x}", hleAddr); // NOP calls to Lock/Unlock mutex memory_writeU32(hleAddr + 0x34, 0x60000000); memory_writeU32(hleAddr + 0x48, 0x60000000); memory_writeU32(hleAddr + 0x50, 0x60000000); memory_writeU32(hleAddr + 0x64, 0x60000000); } // Patch function in Miiverse/eShop wave.rpx that matches // a domain against another to validate its whitelist. // This allows those browsers to load any domain. const NetworkService service = ActiveSettings::GetNetworkService(); if (service != NetworkService::Nintendo // Only patch for custom services. && CafeSystem::GetForegroundTitleArgStr().ends_with("wave.rpx") && (hleAddr = hle_locate(miiverse_eshop_url_match_whitelist_func, nullptr, sizeof(miiverse_eshop_url_match_whitelist_func)))) { cemuLog_log(LogType::Force, "Patching Miiverse/eShop whitelist check at: 0x{:08x}", hleAddr); // Always return 1. (Note that the matched pattern is not at the beginning but still works) memory_writeU32(hleAddr, 0x38600001); memory_writeU32(hleAddr + 0x4, 0x4e800020); // Note that the same applies to Account Settings, but // its version of this function differs a lot. // Search: 88 0c ff ff 2c 00 00 2e (lbz r0,-0x1(r12); cmpwi r0,0x2e) } // Additionally patch out SSL checks for libopenssl.rpl, used by the browser. if (IsNetworkServiceSSLDisabled(service) && RPLLoader_GetHandleByModuleName("libopenssl.rpl") != RPL_INVALID_HANDLE && (hleAddr = hle_locate(wave_libopenssl_ssl_verify_cert_chain, nullptr, sizeof(wave_libopenssl_ssl_verify_cert_chain)))) { cemuLog_log(LogType::Force, "Patching OpenSSL ssl_verify_cert_chain at: 0x{:08x}", hleAddr); // Reference: https://github.com/PretendoNetwork/Meowth/blob/meowth/src/patcher/patches/webkit_applets.cpp memory_writeU32(hleAddr + 0x28, 0x60000000); memory_writeU32(hleAddr + 0x40, 0x38600001); } uint32 hleInstallEnd = GetTickCount(); cemuLog_log(LogType::Force, "HLE scan time: {}ms", hleInstallEnd-hleInstallStart); } RunAtCemuBoot _loadGamePatchAPI([]() { osLib_addFunction("hle", "h000000001", hleExport_breathOfTheWild_busyLoop); osLib_addFunction("hle", "h000000002", hleExport_breathOfTheWild_busyLoop2); osLib_addFunction("hle", "h000000003", hleExport_ffl_swapEndianFloatArray); osLib_addFunction("hle", "h000000004", hleExport_xcx_enterCriticalSection); }); ================================================ FILE: src/Cafe/GamePatch.h ================================================ void GamePatch_scan(); bool GamePatch_IsNonReturnFunction(uint32 hleIndex); ================================================ FILE: src/Cafe/GameProfile/GameProfile.cpp ================================================ #include "Cafe/GameProfile/GameProfile.h" #include "util/helpers/helpers.h" #include "boost/nowide/convert.hpp" #include "config/ActiveSettings.h" #include "Common/FileStream.h" #include "util/IniParser/IniParser.h" #include "util/helpers/StringHelpers.h" #include "Cafe/CafeSystem.h" std::unique_ptr<GameProfile> g_current_game_profile = std::make_unique<GameProfile>(); struct gameProfileBooleanOption_t { bool isPresent = false; bool value; }; /* * Attempts to load a boolean option * If the option exists, true is returned. * The boolean is stored in *optionValue */ bool gameProfile_loadBooleanOption(IniParser* iniParser, char* optionName, gameProfileBooleanOption_t* option) { auto option_value = iniParser->FindOption(optionName); option->isPresent = false; option->value = false; if (!option_value) return false; // parse option if (boost::iequals(*option_value, "false") || boost::iequals(*option_value, "0")) { option->isPresent = true; option->value = false; return true; } else if (boost::iequals(*option_value, "true") || boost::iequals(*option_value, "1")) { option->isPresent = true; option->value = true; return true; } else cemuLog_log(LogType::Force, "Unknown value '{}' for option '{}' in game profile", *option_value, optionName); return false; } bool gameProfile_loadBooleanOption2(IniParser& iniParser, const char* optionName, bool& option) { auto option_value = iniParser.FindOption(optionName); if (!option_value) return false; if (boost::iequals(*option_value, "1") || boost::iequals(*option_value, "true")) { option = true; return true; } else if (boost::iequals(*option_value, "0") || boost::iequals(*option_value, "false")) { option = false; return true; } else cemuLog_log(LogType::Force, "Unknown value '{}' for option '{}' in game profile", *option_value, optionName); return false; } bool gameProfile_loadBooleanOption2(IniParser& iniParser, const char* optionName, std::optional<bool>& option) { bool tmp; const auto result = gameProfile_loadBooleanOption2(iniParser, optionName, tmp); if(result) option = tmp; return result; } /* * Attempts to load a integer option * Allows to specify min and max value (error is logged if out of range and default value is picked) */ bool gameProfile_loadIntegerOption(IniParser* iniParser, const char* optionName, gameProfileIntegerOption_t* option, sint32 defaultValue, sint32 minVal, sint32 maxVal) { auto option_value = iniParser->FindOption(optionName); option->isPresent = false; if (!option_value) { option->value = defaultValue; return false; } // parse option sint32 val = StringHelpers::ToInt(*option_value, defaultValue); if (val < minVal || val > maxVal) { cemuLog_log(LogType::Force, "Value '{}' is out of range for option '{}' in game profile", *option_value, optionName); option->value = defaultValue; return false; } option->isPresent = true; option->value = val; return true; } template <typename T> bool gameProfile_loadIntegerOption(IniParser& iniParser, const char* optionName, T& option, T minVal, T maxVal) { static_assert(std::is_integral_v<T>); auto option_value = iniParser.FindOption(optionName); if (!option_value) return false; // parse option try { T val = ConvertString<T>(*option_value); if (val < minVal || val > maxVal) { cemuLog_log(LogType::Force, "Value '{}' is out of range for option '{}' in game profile", *option_value, optionName); return false; } option = val; return true; } catch(std::exception&) { cemuLog_log(LogType::Force, "Value '{}' is out of range for option '{}' in game profile", *option_value, optionName); return false; } } template<typename T> bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T& option) { static_assert(std::is_enum_v<T>); auto option_value = iniParser.FindOption(optionName); if (!option_value) return false; for(const T& v : T()) { // test integer option if (boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value)) { option = v; return true; } // test enum name if(boost::iequals(fmt::format("{}", v), *option_value)) { option = v; return true; } } return false; } template<typename T> bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, std::optional<T>& option) { T tmp; const auto result = gameProfile_loadEnumOption(iniParser, optionName, tmp); if(result) option = tmp; return result; } void gameProfile_load() { g_current_game_profile->ResetOptional(); // reset with global values as optional g_current_game_profile->Load(CafeSystem::GetForegroundTitleId()); // apply some settings immediately ppcThreadQuantum = g_current_game_profile->GetThreadQuantum(); if (ppcThreadQuantum != GameProfile::kThreadQuantumDefault) cemuLog_log(LogType::Force, "Thread quantum set to {}", ppcThreadQuantum); } bool GameProfile::Load(uint64_t title_id) { auto gameProfilePath = ActiveSettings::GetConfigPath("gameProfiles/{:016x}.ini", title_id); std::optional<std::vector<uint8>> profileContents = FileStream::LoadIntoMemory(gameProfilePath); if (!profileContents) { gameProfilePath = ActiveSettings::GetDataPath("gameProfiles/default/{:016x}.ini", title_id); profileContents = FileStream::LoadIntoMemory(gameProfilePath); if (!profileContents) return false; m_is_default = true; } else m_is_default = false; m_is_loaded = true; // most official gameprofiles start with "# gamename" std::vector<char> game_name; if (profileContents->size() > 0 && profileContents->data()[0] == '#') { char c; size_t idx = 1; while (idx < profileContents->size() && (c = profileContents->data()[idx]) != '\n' && idx < 128) { game_name.emplace_back(c); idx++; } m_gameName = std::string(game_name.begin(), game_name.end()); trim(m_gameName.value()); } IniParser iniParser(*profileContents, _pathToUtf8(gameProfilePath)); // parse ini while (iniParser.NextSection()) { if (boost::iequals(iniParser.GetCurrentSectionName(), "General")) { gameProfile_loadBooleanOption2(iniParser, "loadSharedLibraries", m_loadSharedLibraries); gameProfile_loadBooleanOption2(iniParser, "startWithPadView", m_startWithPadView); } else if (boost::iequals(iniParser.GetCurrentSectionName(), "Graphics")) { gameProfileIntegerOption_t graphicsApi; gameProfile_loadIntegerOption(&iniParser, "graphics_api", &graphicsApi, -1, 0, 1); if (graphicsApi.value != -1) m_graphics_api = (GraphicAPI)graphicsApi.value; gameProfile_loadEnumOption(iniParser, "accurateShaderMul", m_accurateShaderMul); #if ENABLE_METAL gameProfile_loadBooleanOption2(iniParser, "shaderFastMath", m_shaderFastMath); gameProfile_loadEnumOption(iniParser, "metalBufferCacheMode2", m_metalBufferCacheMode); gameProfile_loadEnumOption(iniParser, "positionInvariance2", m_positionInvariance); #endif // legacy support auto option_precompiledShaders = iniParser.FindOption("precompiledShaders"); if (option_precompiledShaders) { if (boost::iequals(*option_precompiledShaders, "1") || boost::iequals(*option_precompiledShaders, "true")) m_precompiledShaders = PrecompiledShaderOption::Enable; else if (boost::iequals(*option_precompiledShaders, "0") || boost::iequals(*option_precompiledShaders, "false")) m_precompiledShaders = PrecompiledShaderOption::Disable; else m_precompiledShaders = PrecompiledShaderOption::Auto; } else m_precompiledShaders = PrecompiledShaderOption::Auto; } else if (boost::iequals(iniParser.GetCurrentSectionName(), "Audio")) { gameProfile_loadBooleanOption2(iniParser, "disableAudio", m_disableAudio); } else if (boost::iequals(iniParser.GetCurrentSectionName(), "CPU")) { gameProfile_loadIntegerOption(iniParser, "threadQuantum", m_threadQuantum, 1000U, 536870912U); if (!gameProfile_loadEnumOption(iniParser, "cpuMode", m_cpuMode)) { // try to load the old enum value strings std::optional<CPUModeLegacy> cpu_mode_legacy; if (gameProfile_loadEnumOption(iniParser, "cpuMode", cpu_mode_legacy) && cpu_mode_legacy.has_value()) { m_cpuMode = (CPUMode)cpu_mode_legacy.value(); if (m_cpuMode == CPUMode::DualcoreRecompiler) m_cpuMode = CPUMode::MulticoreRecompiler; } } } else if (boost::iequals(iniParser.GetCurrentSectionName(), "Controller")) { for (int i = 0; i < 8; ++i) { auto option_value = iniParser.FindOption(fmt::format("controller{}", (i + 1))); if (option_value) m_controllerProfile[i] = std::string(*option_value); } } } return true; } void GameProfile::Save(uint64_t title_id) { auto gameProfileDir = ActiveSettings::GetConfigPath("gameProfiles"); if (std::error_code ex_ec; !fs::exists(gameProfileDir, ex_ec)) fs::create_directories(gameProfileDir, ex_ec); auto gameProfilePath = gameProfileDir / fmt::format("{:016x}.ini", title_id); FileStream* fs = FileStream::createFile2(gameProfilePath); if (!fs) { cemuLog_log(LogType::Force, "Failed to write game profile"); return; } if (m_gameName) fs->writeLine(fmt::format("# {}\n", m_gameName.value()).c_str()); #define WRITE_OPTIONAL_ENTRY(__NAME) if (m_##__NAME) fs->writeLine(fmt::format("{} = {}", #__NAME, m_##__NAME.value()).c_str()); #define WRITE_ENTRY(__NAME) fs->writeLine(fmt::format("{} = {}", #__NAME, m_##__NAME).c_str()); #define WRITE_ENTRY_NUMBERED(__NAME, __NUM) fs->writeLine(fmt::format("{} = {}", #__NAME #__NUM, m_##__NAME).c_str()); fs->writeLine("[General]"); WRITE_OPTIONAL_ENTRY(loadSharedLibraries); WRITE_ENTRY(startWithPadView); fs->writeLine(""); fs->writeLine("[CPU]"); WRITE_OPTIONAL_ENTRY(cpuMode); WRITE_ENTRY(threadQuantum); fs->writeLine(""); fs->writeLine("[Graphics]"); WRITE_ENTRY(accurateShaderMul); #if ENABLE_METAL WRITE_ENTRY(shaderFastMath); WRITE_ENTRY_NUMBERED(metalBufferCacheMode, 2); WRITE_ENTRY_NUMBERED(positionInvariance, 2); #endif WRITE_OPTIONAL_ENTRY(precompiledShaders); WRITE_OPTIONAL_ENTRY(graphics_api); fs->writeLine(""); fs->writeLine("[Controller]"); for (int i = 0; i < 8; ++i) { if (m_controllerProfile[i]) fs->writeLine(fmt::format("controller{} = {}", (i + 1), m_controllerProfile[i].value()).c_str()); } fs->writeLine(""); #undef WRITE_OPTIONAL_ENTRY #undef WRITE_ENTRY #undef WRITE_ENTRY_NUMBERED delete fs; } void GameProfile::ResetOptional() { m_gameName.reset(); // general settings m_loadSharedLibraries.reset(); // true; m_startWithPadView = false; // graphic settings m_accurateShaderMul = AccurateShaderMulOption::True; #if ENABLE_METAL m_shaderFastMath = true; m_metalBufferCacheMode = MetalBufferCacheMode::Auto; m_positionInvariance = PositionInvariance::Auto; #endif // cpu settings m_threadQuantum = kThreadQuantumDefault; m_cpuMode.reset(); // CPUModeOption::kSingleCoreRecompiler; // audio m_disableAudio = false; // controller settings for (auto& profile : m_controllerProfile) profile.reset(); } void GameProfile::Reset() { m_gameName.reset(); // general settings m_loadSharedLibraries = true; m_startWithPadView = false; // graphic settings m_accurateShaderMul = AccurateShaderMulOption::True; #if ENABLE_METAL m_shaderFastMath = true; m_metalBufferCacheMode = MetalBufferCacheMode::Auto; m_positionInvariance = PositionInvariance::Auto; #endif m_precompiledShaders = PrecompiledShaderOption::Auto; // cpu settings m_threadQuantum = kThreadQuantumDefault; m_cpuMode = CPUMode::Auto; // audio m_disableAudio = false; // controller settings for (auto& profile : m_controllerProfile) profile.reset(); } ================================================ FILE: src/Cafe/GameProfile/GameProfile.h ================================================ #pragma once #include <optional> #include "config/CemuConfig.h" struct gameProfileIntegerOption_t { bool isPresent = false; sint32 value; }; class GameProfile { friend class GameProfileWindow; public: static const uint32 kThreadQuantumDefault = 45000; bool Load(uint64_t title_id); void Save(uint64_t title_id); void ResetOptional(); void Reset(); [[nodiscard]] uint64 GetTitleId() const { return m_title_id; } [[nodiscard]] bool IsLoaded() const { return m_is_loaded; } [[nodiscard]] bool IsDefaultProfile() const { return m_is_default; } [[nodiscard]] const std::optional<std::string>& GetGameName() const { return m_gameName; } [[nodiscard]] const std::optional<bool>& ShouldLoadSharedLibraries() const { return m_loadSharedLibraries; } [[nodiscard]] bool StartWithGamepadView() const { return m_startWithPadView; } [[nodiscard]] const std::optional<GraphicAPI>& GetGraphicsAPI() const { return m_graphics_api; } [[nodiscard]] const AccurateShaderMulOption& GetAccurateShaderMul() const { return m_accurateShaderMul; } #if ENABLE_METAL [[nodiscard]] bool GetShaderFastMath() const { return m_shaderFastMath; } [[nodiscard]] MetalBufferCacheMode GetBufferCacheMode() const { return m_metalBufferCacheMode; } [[nodiscard]] PositionInvariance GetPositionInvariance() const { return m_positionInvariance; } #endif [[nodiscard]] const std::optional<PrecompiledShaderOption>& GetPrecompiledShadersState() const { return m_precompiledShaders; } [[nodiscard]] uint32 GetThreadQuantum() const { return m_threadQuantum; } [[nodiscard]] const std::optional<CPUMode>& GetCPUMode() const { return m_cpuMode; } [[nodiscard]] bool IsAudioDisabled() const { return m_disableAudio; } [[nodiscard]] const std::array< std::optional<std::string>, 8>& GetControllerProfile() const { return m_controllerProfile; } private: uint64_t m_title_id = 0; bool m_is_loaded = false; bool m_is_default = true; std::optional<std::string> m_gameName{}; // general settings std::optional<bool> m_loadSharedLibraries{}; // = true; bool m_startWithPadView = false; // graphic settings std::optional<GraphicAPI> m_graphics_api{}; AccurateShaderMulOption m_accurateShaderMul = AccurateShaderMulOption::True; #if ENABLE_METAL bool m_shaderFastMath = true; MetalBufferCacheMode m_metalBufferCacheMode = MetalBufferCacheMode::Auto; PositionInvariance m_positionInvariance = PositionInvariance::Auto; #endif std::optional<PrecompiledShaderOption> m_precompiledShaders{}; // cpu settings uint32 m_threadQuantum = kThreadQuantumDefault; // values: 20000 45000 60000 80000 100000 std::optional<CPUMode> m_cpuMode{}; // = CPUModeOption::kSingleCoreRecompiler; // audio bool m_disableAudio = false; // controller settings std::array< std::optional<std::string>, 8> m_controllerProfile{}; }; extern std::unique_ptr<GameProfile> g_current_game_profile; void gameProfile_load(); ================================================ FILE: src/Cafe/GraphicPack/GraphicPack2.cpp ================================================ #include "Cafe/GraphicPack/GraphicPack2.h" #include "config/CemuConfig.h" #include "config/ActiveSettings.h" #include "openssl/sha.h" #include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" #include "Cafe/Filesystem/fsc.h" #include "boost/algorithm/string.hpp" #include "util/helpers/MapAdaptor.h" #include "util/helpers/StringParser.h" #include "Cafe/HW/Latte/Core/LatteTiming.h" #include "util/IniParser/IniParser.h" #include "util/helpers/StringHelpers.h" #include "Cafe/CafeSystem.h" #include "HW/Espresso/Debugger/Debugger.h" #include <cinttypes> std::vector<GraphicPackPtr> GraphicPack2::s_graphic_packs; std::vector<GraphicPackPtr> GraphicPack2::s_active_graphic_packs; std::atomic_bool GraphicPack2::s_isReady; #define GP_LEGACY_VERSION (2) void GraphicPack2::LoadGraphicPack(fs::path graphicPackPath) { fs::path rulesPath = graphicPackPath; rulesPath.append("rules.txt"); std::unique_ptr<FileStream> fs_rules(FileStream::openFile2(rulesPath)); if (!fs_rules) return; std::vector<uint8> rulesData; fs_rules->extract(rulesData); IniParser iniParser(rulesData, _pathToUtf8(rulesPath)); if (!iniParser.NextSection()) { cemuLog_log(LogType::Force, "{}: Does not contain any sections", _pathToUtf8(rulesPath)); return; } if (!boost::iequals(iniParser.GetCurrentSectionName(), "Definition")) { cemuLog_log(LogType::Force, "{}: [Definition] must be the first section", _pathToUtf8(rulesPath)); return; } auto option_version = iniParser.FindOption("version"); if (option_version) { sint32 versionNum = -1; auto [ptr, ec] = std::from_chars(option_version->data(), option_version->data() + option_version->size(), versionNum); if (ec != std::errc{}) { cemuLog_log(LogType::Force, "{}: Unable to parse version", _pathToUtf8(rulesPath)); return; } if (versionNum > GP_LEGACY_VERSION) { GraphicPack2::LoadGraphicPack(rulesPath, iniParser); return; } } cemuLog_log(LogType::Force, "{}: Outdated graphic pack", _pathToUtf8(rulesPath)); } void GraphicPack2::LoadAll() { std::error_code ec; fs::path basePath = ActiveSettings::GetUserDataPath("graphicPacks"); for (fs::recursive_directory_iterator it(basePath, ec); it != end(it); ++it) { if (!it->is_directory(ec)) continue; fs::path gfxPackPath = it->path(); if (fs::exists(gfxPackPath / "rules.txt", ec)) { LoadGraphicPack(gfxPackPath); it.disable_recursion_pending(); // dont recurse deeper in a gfx pack directory continue; } } } bool GraphicPack2::LoadGraphicPack(const fs::path& rulesPath, IniParser& rules) { try { auto gp = std::make_shared<GraphicPack2>(rulesPath, rules); // check if enabled and preset set const auto& config_entries = GetConfigHandle().data().graphic_pack_entries; // legacy absolute path checking for not breaking compatibility auto file = gp->GetRulesPath(); auto it = config_entries.find(file.lexically_normal()); if (it == config_entries.cend()) { // check for relative path it = config_entries.find(_utf8ToPath(gp->GetNormalizedPathString())); } if (it != config_entries.cend()) { bool enabled = true; for (auto& kv : it->second) { if (boost::iequals(kv.first, "_disabled")) { enabled = false; continue; } gp->SetActivePreset(kv.first, kv.second, false); } gp->SetEnabled(enabled); } gp->UpdatePresetVisibility(); gp->ValidatePresetSelections(); s_graphic_packs.emplace_back(gp); return true; } catch (const std::exception&) { return false; } } bool GraphicPack2::ActivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack) { if (graphic_pack->Activate()) { s_active_graphic_packs.push_back(graphic_pack); g_debuggerDispatcher.NotifyGraphicPacksModified(); return true; } return false; } bool GraphicPack2::DeactivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack) { if (!graphic_pack->IsActivated()) return false; const auto it = std::find_if(s_active_graphic_packs.begin(), s_active_graphic_packs.end(), [graphic_pack](const GraphicPackPtr& gp) { return gp->GetNormalizedPathString() == graphic_pack->GetNormalizedPathString(); } ); if (it == s_active_graphic_packs.end()) return false; graphic_pack->Deactivate(); s_active_graphic_packs.erase(it); g_debuggerDispatcher.NotifyGraphicPacksModified(); return true; } void GraphicPack2::ActivateForCurrentTitle() { uint64 titleId = CafeSystem::GetForegroundTitleId(); // activate graphic packs for (const auto& gp : GraphicPack2::GetGraphicPacks()) { if (!gp->IsEnabled()) continue; if (!gp->ContainsTitleId(titleId)) continue; if (GraphicPack2::ActivateGraphicPack(gp)) { if (gp->GetPresets().empty()) { cemuLog_log(LogType::Force, "Activate graphic pack: {}", gp->GetVirtualPath()); } else { std::string logLine; logLine.assign(fmt::format("Activate graphic pack: {} [Presets: ", gp->GetVirtualPath())); bool isFirst = true; for (auto& itr : gp->GetPresets()) { if (!itr->active) continue; if (isFirst) isFirst = false; else logLine.append(","); logLine.append(itr->name); } logLine.append("]"); cemuLog_log(LogType::Force, logLine); } } } s_isReady = true; } void GraphicPack2::Reset() { s_active_graphic_packs.clear(); s_isReady = false; } void GraphicPack2::ClearGraphicPacks() { s_graphic_packs.clear(); s_active_graphic_packs.clear(); } void GraphicPack2::WaitUntilReady() { while (!s_isReady) std::this_thread::sleep_for(std::chrono::milliseconds(5)); } std::unordered_map<std::string, GraphicPack2::PresetVar> GraphicPack2::ParsePresetVars(IniParser& rules) const { ExpressionParser parser; std::unordered_map<std::string, PresetVar> vars; for(auto& itr : rules.GetAllOptions()) { auto option_name = itr.first; auto option_value = itr.second; if (option_name.empty() || option_name[0] != '$') continue; VarType type = kDouble; std::string name(option_name); const auto index = name.find(':'); if(index != std::string::npos) { auto type_name = name.substr(index + 1); name = name.substr(0, index); trim(name); trim(type_name); if (type_name == "double") type = kDouble; else if (type_name == "int") type = kInt; } const double value = parser.Evaluate(option_value); vars.try_emplace(name, std::make_pair(type, value)); parser.AddConstant(name, value); } return vars; } GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) : m_rulesPath(std::move(rulesPath)) { // we're already in [Definition] auto option_version = rules.FindOption("version"); if (!option_version) throw std::exception(); m_version = StringHelpers::ToInt(*option_version, -1); if (m_version < 0) { cemuLog_log(LogType::Force, "{}: Invalid version", _pathToUtf8(m_rulesPath)); throw std::exception(); } auto option_rendererFilter = rules.FindOption("rendererFilter"); if (option_rendererFilter) { if (boost::iequals(*option_rendererFilter, "vulkan")) m_renderer_api = RendererAPI::Vulkan; else if (boost::iequals(*option_rendererFilter, "opengl")) m_renderer_api = RendererAPI::OpenGL; else if (boost::iequals(*option_rendererFilter, "metal")) m_renderer_api = RendererAPI::Metal; else cemuLog_log(LogType::Force, "Unknown value '{}' for rendererFilter option", *option_rendererFilter); } auto option_defaultEnabled = rules.FindOption("default"); if(option_defaultEnabled) { m_default_enabled = boost::iequals(*option_defaultEnabled, "true") || boost::iequals(*option_defaultEnabled, "1"); m_enabled = m_default_enabled; } auto option_allowRendertargetSizeOptimization = rules.FindOption("colorbufferOptimizationAware"); if (option_allowRendertargetSizeOptimization) m_allowRendertargetSizeOptimization = boost::iequals(*option_allowRendertargetSizeOptimization, "true") || boost::iequals(*option_allowRendertargetSizeOptimization, "1"); auto option_vendorFilter = rules.FindOption("vendorFilter"); if (option_vendorFilter) { if (boost::iequals(*option_vendorFilter, "amd")) m_gfx_vendor = GfxVendor::AMD; else if (boost::iequals(*option_vendorFilter, "intel")) m_gfx_vendor = GfxVendor::Intel; else if (boost::iequals(*option_vendorFilter, "mesa")) m_gfx_vendor = GfxVendor::Mesa; else if (boost::iequals(*option_vendorFilter, "nvidia")) m_gfx_vendor = GfxVendor::Nvidia; else if (boost::iequals(*option_vendorFilter, "apple")) m_gfx_vendor = GfxVendor::Apple; else cemuLog_log(LogType::Force, "Unknown value '{}' for vendorFilter", *option_vendorFilter); } auto option_path = rules.FindOption("path"); if (!option_path) { auto gp_name_log = rules.FindOption("name"); cemuLog_log(LogType::Force, "[Definition] section from '{}' graphic pack must contain option: path", gp_name_log.has_value() ? *gp_name_log : "Unknown"); throw std::exception(); } m_virtualPath = *option_path; auto option_gp_name = rules.FindOption("name"); if (option_gp_name) m_name = *option_gp_name; auto option_description = rules.FindOption("description"); if (option_description) { m_description = *option_description; std::replace(m_description.begin(), m_description.end(), '|', '\n'); } m_title_ids = ParseTitleIds(rules, "titleIds"); if(m_title_ids.empty() && !m_universal) throw std::exception(); auto option_fsPriority = rules.FindOption("fsPriority"); if (option_fsPriority) { std::string tmp(*option_fsPriority); m_fs_priority = std::stoi(tmp); } // load presets while (rules.NextSection()) { auto currentSectionName = rules.GetCurrentSectionName(); if (boost::iequals(currentSectionName, "Default")) { m_preset_vars = ParsePresetVars(rules); } else if (boost::iequals(currentSectionName, "Preset")) { const auto preset_name = rules.FindOption("name"); if (!preset_name) { cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", GetNormalizedPathString(), rules.GetCurrentSectionLineNumber()); continue; } const auto category = rules.FindOption("category"); const auto condition = rules.FindOption("condition"); const auto default_selected = rules.FindOption("default"); try { const auto vars = ParsePresetVars(rules); PresetPtr preset; if (category && condition) preset = std::make_shared<Preset>(*category, *preset_name, *condition, vars); else if (category) preset = std::make_shared<Preset>(*category, *preset_name, vars); else preset = std::make_shared<Preset>(*preset_name, vars); if (default_selected) preset->is_default = StringHelpers::ToInt(*default_selected) != 0; m_presets.emplace_back(preset); } catch (const std::exception & ex) { cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", GetNormalizedPathString(), *preset_name, ex.what()); } } else if (boost::iequals(currentSectionName, "RAM")) { for (uint32 i = 0; i < 32; i++) { char optionNameBuf[64]; *fmt::format_to(optionNameBuf, "mapping{}", i) = '\0'; const auto mappingOption = rules.FindOption(optionNameBuf); if (mappingOption) { if (m_version <= 5) { cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", GetNormalizedPathString(), optionNameBuf); throw std::exception(); } StringTokenParser parser(*mappingOption); uint32 addrStart = 0, addrEnd = 0; if (parser.parseU32(addrStart) && parser.matchWordI("-") && parser.parseU32(addrEnd) && parser.isEndOfString()) { if (addrEnd <= addrStart) { cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", GetNormalizedPathString(), addrStart, addrEnd, optionNameBuf); throw std::exception(); } else if ((addrStart & 0xFFF) != 0 || (addrEnd & 0xFFF) != 0) { cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", GetNormalizedPathString(), optionNameBuf); throw std::exception(); } else { m_ramMappings.emplace_back(addrStart, addrEnd); } } else { cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", GetNormalizedPathString(), optionNameBuf); throw std::exception(); } } } } } if (m_version >= 5) { // store by category std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map; // all vars must be defined in the default preset vars before std::vector<std::pair<std::string, std::string>> mismatchingPresetVars; for (const auto& presetEntry : m_presets) { tmp_map[presetEntry->category].emplace_back(presetEntry); for (auto& presetVar : presetEntry->variables) { const auto it = m_preset_vars.find(presetVar.first); if (it == m_preset_vars.cend()) { mismatchingPresetVars.emplace_back(presetEntry->name, presetVar.first); continue; } // overwrite var type with default var type presetVar.second.first = it->second.first; } } if(!mismatchingPresetVars.empty()) { cemuLog_log(LogType::Force, "Graphic pack \"{}\" contains preset variables which are not defined in the [Default] section:", GetNormalizedPathString()); for (const auto& [presetName, varName] : mismatchingPresetVars) cemuLog_log(LogType::Force, "Preset: {} Variable: {}", presetName, varName); throw std::exception(); } // have first entry be default active for every category if no default= is set for(auto entry : get_values(tmp_map)) { if (!entry.empty()) { const auto it = std::find_if(entry.cbegin(), entry.cend(), [](const PresetPtr& preset) { return preset->is_default; }); if (it != entry.cend()) (*it)->active = true; else (*entry.begin())->active = true; } } } else { // verify preset data to contain the same keys std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map; for (const auto& entry : m_presets) tmp_map[entry->category].emplace_back(entry); for (const auto& kv : tmp_map) { bool has_default = false; for (size_t i = 0; i + 1 < kv.second.size(); ++i) { auto& p1 = kv.second[i]; auto& p2 = kv.second[i + 1]; if (p1->variables.size() != p2->variables.size()) { cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString()); throw std::exception(); } std::set<std::string> keys1(get_keys(p1->variables).begin(), get_keys(p1->variables).end()); std::set<std::string> keys2(get_keys(p2->variables).begin(), get_keys(p2->variables).end()); if (keys1 != keys2) { cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString()); throw std::exception(); } if(p1->is_default) { if(has_default) cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", GetNormalizedPathString(), p1->name); p1->active = true; has_default = true; } } // have first entry by default active if no default is set if (!has_default) kv.second[0]->active = true; } } } // returns true if enabling, disabling (changeEnableState) or changing presets (changePreset) for the graphic pack requires restarting if the game is already running bool GraphicPack2::RequiresRestart(bool changeEnableState, bool changePreset) { if (!GetTextureRules().empty()) return true; return false; } bool GraphicPack2::Reload() { Deactivate(); return Activate(); } std::string GraphicPack2::GetNormalizedPathString() const { return _pathToUtf8(MakeRelativePath(ActiveSettings::GetUserDataPath(), GetRulesPath()).lexically_normal()); } bool GraphicPack2::ContainsTitleId(uint64_t title_id) const { if (m_universal) return true; const auto it = std::find_if(m_title_ids.begin(), m_title_ids.end(), [title_id](uint64 id) { return id == title_id; }); return it != m_title_ids.end(); } bool GraphicPack2::HasActivePreset() const { return std::any_of(m_presets.cbegin(), m_presets.cend(), [](const PresetPtr& preset) { return preset->active; }); } std::string GraphicPack2::GetActivePreset(std::string_view category) const { const auto it = std::find_if(m_presets.cbegin(), m_presets.cend(), [category](const PresetPtr& preset) { return preset->active && preset->category == category; }); return it != m_presets.cend() ? (*it)->name : std::string{ "" }; } void GraphicPack2::UpdatePresetVisibility() { // update visiblity of each preset std::for_each(m_presets.begin(), m_presets.end(), [this](PresetPtr& p) { p->visible = m_version >= 5 ? IsPresetVisible(p) : true; }); } void GraphicPack2::ValidatePresetSelections() { if (m_version < 5) return; // only applies to new categorized presets // if any preset is changed then other categories might be affected indirectly // // example: selecting the aspect ratio in a resolution graphic pack would change the available presets in the resolution category // how to handle: select the first available resolution (or the one marked as default) // // example: a preset category might be hidden entirely (e.g. due to a separate advanced options dropdown) // how to handle: leave the previously selected preset // // the logic is therefore as follows: // if there is a preset category with at least 1 visible preset entry then make sure one of those is actually selected // for completely hidden preset categories we leave the selection as-is std::vector<std::string> order; std::unordered_map<std::string, std::vector<GraphicPack2::PresetPtr>> categorizedPresets = GraphicPack2::GetCategorizedPresets(order); bool changedPresets = false; for (auto& categoryItr : categorizedPresets) { // get selection of this category size_t numVisiblePresets = 0; GraphicPack2::PresetPtr defaultSelection = nullptr; GraphicPack2::PresetPtr selectedPreset = nullptr; for (auto& presetItr : categoryItr.second) { if (presetItr->visible) { numVisiblePresets++; if (!defaultSelection || presetItr->is_default) // the preset marked as default becomes the default selection, otherwise pick first visible one defaultSelection = presetItr; } if (presetItr->active) { if (selectedPreset) { // multiple selections inside the same group are invalid presetItr->active = false; changedPresets = true; } else selectedPreset = presetItr; } } if (numVisiblePresets == 0) continue; // do not touch selection if (!selectedPreset) { // no selection at all if (defaultSelection) { selectedPreset = defaultSelection; selectedPreset->active = true; } continue; } // if the currently selected preset is invisible, update it to the preferred visible selection if (!selectedPreset->visible) { selectedPreset->active = false; defaultSelection->active = true; changedPresets = true; } } if (changedPresets) UpdatePresetVisibility(); } bool GraphicPack2::SetActivePreset(std::string_view category, std::string_view name, bool update_visibility) { // disable currently active preset std::for_each(m_presets.begin(), m_presets.end(), [category](PresetPtr& p) { if(p->category == category) p->active = false; }); if (name.empty()) return true; // enable new preset const auto it = std::find_if(m_presets.cbegin(), m_presets.cend(), [category, name](const PresetPtr& preset) { return preset->category == category && preset->name == name; }); bool result; if (it != m_presets.cend()) { (*it)->active = true; cemu_assert_debug(std::count_if(m_presets.cbegin(), m_presets.cend(), [category](const PresetPtr& p) { return p->category == category && p->active; }) == 1); result = true; } else result = false; if (update_visibility) { UpdatePresetVisibility(); ValidatePresetSelections(); } return result; } void GraphicPack2::LoadShaders() { fs::path path = GetRulesPath(); for (auto& it : fs::directory_iterator(path.remove_filename())) { if (!is_regular_file(it)) continue; try { const auto& p = it.path(); auto filename = p.filename().wstring(); uint64 shader_base_hash = 0; uint64 shader_aux_hash = 0; wchar_t shader_type[256]{}; if (filename.size() < 256 && swscanf(filename.c_str(), L"%" SCNx64 "_%" SCNx64 "_%ls", &shader_base_hash, &shader_aux_hash, shader_type) == 3) { bool isMetalShader = (shader_type[2] == '_' && shader_type[3] == 'm' && shader_type[4] == 's' && shader_type[5] == 'l'); if (shader_type[0] == 'p' && shader_type[1] == 's') m_custom_shaders.emplace_back(LoadShader(p, shader_base_hash, shader_aux_hash, GP_SHADER_TYPE::PIXEL, isMetalShader)); else if (shader_type[0] == 'v' && shader_type[1] == 's') m_custom_shaders.emplace_back(LoadShader(p, shader_base_hash, shader_aux_hash, GP_SHADER_TYPE::VERTEX, isMetalShader)); else if (shader_type[0] == 'g' && shader_type[1] == 's') m_custom_shaders.emplace_back(LoadShader(p, shader_base_hash, shader_aux_hash, GP_SHADER_TYPE::GEOMETRY, isMetalShader)); } else if (filename == L"output.glsl") { std::ifstream file(p); if (!file.is_open()) throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename()))); file.seekg(0, std::ios::end); m_output_shader_source.reserve(file.tellg()); file.seekg(0, std::ios::beg); m_output_shader_source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()); ApplyShaderPresets(m_output_shader_source); } else if (filename == L"upscaling.glsl") { std::ifstream file(p); if (!file.is_open()) throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename()))); file.seekg(0, std::ios::end); m_upscaling_shader_source.reserve(file.tellg()); file.seekg(0, std::ios::beg); m_upscaling_shader_source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()); ApplyShaderPresets(m_upscaling_shader_source); } else if (filename == L"downscaling.glsl") { std::ifstream file(p); if (!file.is_open()) throw std::runtime_error(fmt::format("can't open graphic pack file: {}", _pathToUtf8(p.filename()))); file.seekg(0, std::ios::end); m_downscaling_shader_source.reserve(file.tellg()); file.seekg(0, std::ios::beg); m_downscaling_shader_source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()); ApplyShaderPresets(m_downscaling_shader_source); } } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "graphicPack: error while loading custom shader: {}", ex.what()); } } } bool GraphicPack2::SetActivePreset(std::string_view name) { return SetActivePreset("", name); } bool GraphicPack2::IsPresetVisible(const PresetPtr& preset) const { if (preset->condition.empty()) return true; try { TExpressionParser<int> p; FillPresetConstants(p); return p.Evaluate(preset->condition) != 0; } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "error when trying to check visiblity of preset: {}", ex.what()); return false; } } std::optional<GraphicPack2::PresetVar> GraphicPack2::GetPresetVariable(const std::vector<PresetPtr>& presets, std::string_view var_name) const { // no priority and visibility filter if(m_version < 5) { for (const auto& preset : presets) { const auto it = std::find_if(preset->variables.cbegin(), preset->variables.cend(), [&var_name](auto p) { return p.first == var_name; }); if (it != preset->variables.cend()) return it->second; } return {}; } // visible > none visible > default for (const auto& preset : presets) { if (preset->visible) { const auto it = std::find_if(preset->variables.cbegin(), preset->variables.cend(), [&var_name](auto p) { return p.first == var_name; }); if (it != preset->variables.cend()) return it->second; } } for (const auto& preset : presets) { if (!preset->visible) { const auto it = std::find_if(preset->variables.cbegin(), preset->variables.cend(), [&var_name](auto p) { return p.first == var_name; }); if (it != preset->variables.cend()) return it->second; } } const auto it = std::find_if(m_preset_vars.cbegin(), m_preset_vars.cend(), [&var_name](auto p) { return p.first == var_name; }); if (it != m_preset_vars.cend()) { return it->second; } return {}; } void GraphicPack2::AddConstantsForCurrentPreset(ExpressionParser& ep) { if (m_version < 5) { for (const auto& preset : GetActivePresets()) { for (auto& v : preset->variables) { ep.AddConstant(v.first, v.second.second); } } } else { FillPresetConstants(ep); } } void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC, const char* virtualMountBase) { uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); uint64 aocTitleId = (currentTitleId & 0xFFFFFFFFull) | 0x0005000c00000000ull; for (auto& it : fs::recursive_directory_iterator(currentPath)) { if (fs::is_regular_file(it)) { fs::path virtualMountPath = fs::relative(it.path(), currentPath); if (isAOC) { virtualMountPath = fs::path(fmt::format("/vol/aoc{:016x}/", aocTitleId)) / virtualMountPath; } else { virtualMountPath = fs::path(virtualMountBase) / virtualMountPath; } fscDeviceRedirect_add(virtualMountPath.generic_string(), it.file_size(), it.path().generic_string(), m_fs_priority); } } } void GraphicPack2::LoadReplacedFiles() { if (m_patchedFilesLoaded) return; m_patchedFilesLoaded = true; fs::path gfxPackPath = GetRulesPath(); gfxPackPath = gfxPackPath.remove_filename(); // /content/ fs::path contentPath(gfxPackPath); contentPath.append("content"); std::error_code ec; if (fs::exists(contentPath, ec)) { // setup redirections fscDeviceRedirect_map(); _iterateReplacedFiles(contentPath, false, "vol/content/"); } // /aoc/ fs::path aocPath(gfxPackPath); aocPath.append("aoc"); if (fs::exists(aocPath, ec)) { uint64 aocTitleId = CafeSystem::GetForegroundTitleId(); aocTitleId = aocTitleId & 0xFFFFFFFFULL; aocTitleId |= 0x0005000c00000000ULL; // setup redirections fscDeviceRedirect_map(); _iterateReplacedFiles(aocPath, true, nullptr); } // /code/ fs::path codePath(gfxPackPath); codePath.append("code"); if (fs::exists(codePath, ec)) { // setup redirections fscDeviceRedirect_map(); _iterateReplacedFiles(codePath, false, CafeSystem::GetInternalVirtualCodeFolder().c_str()); } } bool GraphicPack2::Activate() { if (m_activated) return true; // check if gp should be loaded if (m_renderer_api.has_value() && m_renderer_api.value() != g_renderer->GetType()) return false; if (m_gfx_vendor.has_value()) { auto vendor = g_renderer->GetVendor(); if (m_gfx_vendor.value() != vendor) return false; } FileStream* fs_rules = FileStream::openFile2(m_rulesPath); if (!fs_rules) return false; std::vector<uint8> rulesData; fs_rules->extract(rulesData); delete fs_rules; IniParser rules({ (char*)rulesData.data(), rulesData.size()}, GetNormalizedPathString()); // load rules try { ExpressionParser parser; AddConstantsForCurrentPreset(parser); while (rules.NextSection()) { //const char* category_name = sPref_currentCategoryName(rules); std::string_view category_name = rules.GetCurrentSectionName(); if (boost::iequals(category_name, "TextureRedefine")) { TextureRule rule{}; ParseRule(parser, rules, "width", &rule.filter_settings.width); ParseRule(parser, rules, "height", &rule.filter_settings.height); ParseRule(parser, rules, "depth", &rule.filter_settings.depth); bool inMem1 = false; if (ParseRule(parser, rules, "inMEM1", &inMem1)) rule.filter_settings.inMEM1 = inMem1 ? TextureRule::FILTER_SETTINGS::MEM1_FILTER::INSIDE : TextureRule::FILTER_SETTINGS::MEM1_FILTER::OUTSIDE; rule.filter_settings.format_whitelist = ParseList<sint32>(parser, rules, "formats"); rule.filter_settings.format_blacklist = ParseList<sint32>(parser, rules, "formatsExcluded"); rule.filter_settings.tilemode_whitelist = ParseList<sint32>(parser, rules, "tilemodes"); rule.filter_settings.tilemode_blacklist = ParseList<sint32>(parser, rules, "tilemodesExcluded"); ParseRule(parser, rules, "overwriteWidth", &rule.overwrite_settings.width); ParseRule(parser, rules, "overwriteHeight", &rule.overwrite_settings.height); ParseRule(parser, rules, "overwriteDepth", &rule.overwrite_settings.depth); ParseRule(parser, rules, "overwriteFormat", &rule.overwrite_settings.format); float lod_bias; if(ParseRule(parser, rules, "overwriteLodBias", &lod_bias)) rule.overwrite_settings.lod_bias = (sint32)(lod_bias * 64.0f); if(ParseRule(parser, rules, "overwriteRelativeLodBias", &lod_bias)) rule.overwrite_settings.relative_lod_bias = (sint32)(lod_bias * 64.0f); sint32 anisotropyValue; if (ParseRule(parser, rules, "overwriteAnisotropy", &anisotropyValue)) { if (anisotropyValue == 1) rule.overwrite_settings.anistropic_value = 0; else if (anisotropyValue == 2) rule.overwrite_settings.anistropic_value = 1; else if (anisotropyValue == 4) rule.overwrite_settings.anistropic_value = 2; else if (anisotropyValue == 8) rule.overwrite_settings.anistropic_value = 3; else if (anisotropyValue == 16) rule.overwrite_settings.anistropic_value = 4; else cemuLog_log(LogType::Force, "Invalid value {} for overwriteAnisotropy in graphic pack {}. Only the values 1, 2, 4, 8 or 16 are allowed.", anisotropyValue, GetNormalizedPathString()); } m_texture_rules.emplace_back(rule); } else if (boost::iequals(category_name, "Control")) { ParseRule(parser, rules, "vsyncFrequency", &m_vsync_frequency); } else if (boost::iequals(category_name, "OutputShader")) { auto option_upscale = rules.FindOption("upscaleMagFilter"); if(option_upscale && boost::iequals(*option_upscale, "NearestNeighbor")) m_output_settings.upscale_filter = LatteTextureView::MagFilter::kNearestNeighbor; auto option_downscale = rules.FindOption("downscaleMinFilter"); if (option_downscale && boost::iequals(*option_downscale, "NearestNeighbor")) m_output_settings.downscale_filter = LatteTextureView::MagFilter::kNearestNeighbor; } } } catch(const std::exception& ex) { cemuLog_log(LogType::Force, ex.what()); return false; } // load shaders LoadShaders(); // load patches LoadPatchFiles(); // enable patch groups EnablePatches(); // load replaced files LoadReplacedFiles(); // set custom vsync if (HasCustomVSyncFrequency()) { sint32 customVsyncFreq = GetCustomVSyncFrequency(); sint32 globalCustomVsyncFreq = 0; if (LatteTiming_getCustomVsyncFrequency(globalCustomVsyncFreq)) { if (customVsyncFreq != globalCustomVsyncFreq) cemuLog_log(LogType::Force, "rules.txt error: Mismatching vsync frequency {} in graphic pack \'{}\'", customVsyncFreq, GetVirtualPath()); } else { cemuLog_log(LogType::Force, "Set vsync frequency to {} (graphic pack {})", customVsyncFreq, GetVirtualPath()); LatteTiming_setCustomVsyncFrequency(customVsyncFreq); } } m_activated = true; return true; } bool GraphicPack2::Deactivate() { if (!m_activated) return false; UnloadPatches(); m_activated = false; m_custom_shaders.clear(); m_texture_rules.clear(); m_output_shader.reset(); m_upscaling_shader.reset(); m_downscaling_shader.reset(); m_output_shader_ud.reset(); m_upscaling_shader_ud.reset(); m_downscaling_shader_ud.reset(); m_output_shader_source.clear(); m_upscaling_shader_source.clear(); m_downscaling_shader_source.clear(); if (HasCustomVSyncFrequency()) { m_vsync_frequency = -1; LatteTiming_disableCustomVsyncFrequency(); } return true; } const std::string* GraphicPack2::FindCustomShaderSource(uint64 shaderBaseHash, uint64 shaderAuxHash, GP_SHADER_TYPE type, bool isVulkanRenderer, bool isMetalRenderer) { for (const auto& gp : GraphicPack2::GetActiveGraphicPacks()) { const auto it = std::find_if(gp->m_custom_shaders.begin(), gp->m_custom_shaders.end(), [shaderBaseHash, shaderAuxHash, type](const auto& s) { return s.shader_base_hash == shaderBaseHash && s.shader_aux_hash == shaderAuxHash && s.type == type; }); if (it == gp->m_custom_shaders.end()) continue; if (isVulkanRenderer && (*it).isPreVulkanShader) continue; if (isMetalRenderer != (*it).isMetalShader) continue; return &it->source; } return nullptr; } std::unordered_map<std::string, std::vector<GraphicPack2::PresetPtr>> GraphicPack2::GetCategorizedPresets(std::vector<std::string>& order) const { order.clear(); std::unordered_map<std::string, std::vector<PresetPtr>> result; for(const auto& entry : m_presets) { result[entry->category].emplace_back(entry); const auto it = std::find(order.cbegin(), order.cend(), entry->category); if (it == order.cend()) order.emplace_back(entry->category); } return result; } bool GraphicPack2::HasShaders() const { return !GetCustomShaders().empty() || !m_output_shader_source.empty() || !m_upscaling_shader_source.empty() || !m_downscaling_shader_source.empty(); } RendererOutputShader* GraphicPack2::GetOuputShader(bool render_upside_down) { if(render_upside_down) { if (m_output_shader_ud) return m_output_shader_ud.get(); if (!m_output_shader_source.empty()) m_output_shader_ud = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_output_shader_source); return m_output_shader_ud.get(); } else { if (m_output_shader) return m_output_shader.get(); if (!m_output_shader_source.empty()) m_output_shader = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_output_shader_source); return m_output_shader.get(); } } RendererOutputShader* GraphicPack2::GetUpscalingShader(bool render_upside_down) { if (render_upside_down) { if (m_upscaling_shader_ud) return m_upscaling_shader_ud.get(); if (!m_upscaling_shader_source.empty()) m_upscaling_shader_ud = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_upscaling_shader_source); return m_upscaling_shader_ud.get(); } else { if (m_upscaling_shader) return m_upscaling_shader.get(); if (!m_upscaling_shader_source.empty()) m_upscaling_shader = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_upscaling_shader_source); return m_upscaling_shader.get(); } } RendererOutputShader* GraphicPack2::GetDownscalingShader(bool render_upside_down) { if (render_upside_down) { if (m_downscaling_shader_ud) return m_downscaling_shader_ud.get(); if (!m_downscaling_shader_source.empty()) m_downscaling_shader_ud = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_downscaling_shader_source); return m_downscaling_shader_ud.get(); } else { if (m_downscaling_shader) return m_downscaling_shader.get(); if (!m_downscaling_shader_source.empty()) m_downscaling_shader = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_downscaling_shader_source); return m_downscaling_shader.get(); } } std::vector<GraphicPack2::PresetPtr> GraphicPack2::GetActivePresets() const { std::vector<PresetPtr> result; result.reserve(m_presets.size()); std::copy_if(m_presets.cbegin(), m_presets.cend(), std::back_inserter(result), [](const PresetPtr& p) { return p->active; }); return result; } std::vector<uint64> GraphicPack2::ParseTitleIds(IniParser& rules, const char* option_name) { std::vector<uint64> result; auto option_text = rules.FindOption(option_name); if (!option_text) return result; if (*option_text == "*") { m_universal = true; return result; } for (auto& token : TokenizeView(*option_text, ',')) { try { result.emplace_back(ConvertString<uint64>(token, 16)); } catch (const std::invalid_argument&) {} } return result; } void GraphicPack2::ApplyShaderPresets(std::string& shader_source) const { const auto active_presets = GetActivePresets(); const std::regex regex(R"(\$[a-zA-Z_0-9]+)"); std::smatch match; size_t offset = 0; while (std::regex_search(shader_source.cbegin() + offset, shader_source.cend(), match, regex)) { if (active_presets.empty()) throw std::runtime_error("found variable in shader but no preset is active"); const auto str = match.str(); std::optional<PresetVar> var = GetPresetVariable(active_presets, str); if(!var) throw std::runtime_error("using an unknown preset variable in shader"); std::string new_value; if (var->first == kInt) new_value = fmt::format("{}", (int)var->second); else new_value = fmt::format("{:f}", var->second); shader_source.replace(match.position() + offset, match.length(), new_value); offset += match.position() + new_value.length(); } } GraphicPack2::CustomShader GraphicPack2::LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type, bool isMetalShader) const { CustomShader shader; std::ifstream file(path); if (!file.is_open()) throw std::runtime_error("can't open shader file"); file.seekg(0, std::ios::end); shader.source.reserve(file.tellg()); file.seekg(0, std::ios::beg); shader.source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()); ApplyShaderPresets(shader.source); shader.shader_base_hash = shader_base_hash; shader.shader_aux_hash = shader_aux_hash; shader.type = shader_type; shader.isPreVulkanShader = this->m_version <= 3; shader.isMetalShader = isMetalShader; return shader; } std::vector<std::pair<MPTR, MPTR>> GraphicPack2::GetActiveRAMMappings() { uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); std::vector<std::pair<MPTR, MPTR>> v; for (const auto& gp : GraphicPack2::GetGraphicPacks()) { if (!gp->IsEnabled()) continue; if (!gp->ContainsTitleId(currentTitleId)) continue; if (!gp->m_ramMappings.empty()) v.insert(v.end(), gp->m_ramMappings.begin(), gp->m_ramMappings.end()); } std::sort(v.begin(), v.end(), [](const std::pair<MPTR, MPTR>& a, const std::pair<MPTR, MPTR>& b) -> bool { return a.first < b.first; }); return v; } ================================================ FILE: src/Cafe/GraphicPack/GraphicPack2.h ================================================ #pragma once #include "util/helpers/helpers.h" #include "Cemu/ExpressionParser/ExpressionParser.h" #include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" #include "util/helpers/Serializer.h" #include "Cafe/OS/RPL/rpl.h" #include "Cemu/PPCAssembler/ppcAssembler.h" #include <variant> #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "GraphicPack2Patches.h" #include "util/IniParser/IniParser.h" class GraphicPack2 { public: enum class GP_SHADER_TYPE : uint8 { PIXEL = 0, VERTEX = 1, GEOMETRY = 2, }; enum { GFXPACK_VERSION_5 = 5, GFXPACK_VERSION_6 = 6, // added memory extensions GFXPACK_VERSION_7 = 7, // added fine-grained origin control in patch format (no more forced 4 byte alignment), .string directive (an alias to .byte) and support for more than one constant per data directive }; struct TextureRule { // filter (texture must match these settings) struct FILTER_SETTINGS { enum class MEM1_FILTER { BOTH, INSIDE, OUTSIDE, }; sint32 width = -1; sint32 height = -1; sint32 depth = -1; MEM1_FILTER inMEM1 = MEM1_FILTER::BOTH; std::vector<sint32> format_whitelist{}; std::vector<sint32> format_blacklist{}; std::vector<sint32> tilemode_whitelist{}; std::vector<sint32> tilemode_blacklist{}; } filter_settings; // overwrite (if match found, these settings are overwritten) struct OVERWRITE_SETTINGS { sint32 width = -1; sint32 height = -1; sint32 depth = -1; sint32 format = -1; sint32 lod_bias = -1; // in 1/64th steps sint32 relative_lod_bias = -1; // in 1/64th steps sint32 anistropic_value = -1; // 1<<n } overwrite_settings; }; struct CustomShader { std::string source; uint64 shader_base_hash; uint64 shader_aux_hash; GP_SHADER_TYPE type; bool isPreVulkanShader{}; // set to true for V3 packs since the shaders are not compatible with the Vulkan renderer bool isMetalShader{}; // set to true if the shader is written in Metal Shading Language }; enum VarType { kDouble = 0, kInt = 1, }; using PresetVar = std::pair<VarType, double>; struct Preset { std::string category; // preset category (empty for default) std::string name; // displayed name std::string condition; std::unordered_map<std::string, PresetVar> variables; bool active = false; // selected/active preset bool visible = true; // set by condition or true bool is_default = false; // selected by default Preset(std::string_view name, std::unordered_map<std::string, PresetVar> vars) : name(name), variables(std::move(vars)) {} Preset(std::string_view category, std::string_view name, std::unordered_map<std::string, PresetVar> vars) : category(category), name(name), variables(std::move(vars)) {} Preset(std::string_view category, std::string_view name, std::string_view condition, std::unordered_map<std::string, PresetVar> vars) : category(category), name(name), condition(condition), variables(std::move(vars)) {} }; using PresetPtr = std::shared_ptr<Preset>; GraphicPack2(fs::path rulesPath, IniParser& rules); bool IsEnabled() const { return m_enabled; } bool IsActivated() const { return m_activated; } sint32 GetVersion() const { return m_version; } const fs::path GetRulesPath() const { return m_rulesPath; } std::string GetNormalizedPathString() const; bool RequiresRestart(bool changeEnableState, bool changePreset); bool Reload(); bool HasName() const { return !m_name.empty(); } bool IsUniversal() const { return m_universal; } const std::string& GetName() const { return m_name.empty() ? m_virtualPath : m_name; } const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy const std::string& GetDescription() const { return m_description; } bool IsDefaultEnabled() const { return m_default_enabled; } bool AllowRendertargetSizeOptimization() const { return m_allowRendertargetSizeOptimization; } void SetEnabled(bool state) { m_enabled = state; } bool ContainsTitleId(uint64_t title_id) const; const std::vector<uint64_t>& GetTitleIds() const { return m_title_ids; } bool HasCustomVSyncFrequency() const { return m_vsync_frequency >= 1; } sint32 GetCustomVSyncFrequency() const { return m_vsync_frequency; } const std::vector<std::pair<MPTR, GPCallbackType>>& GetCallbacks() const { return m_callbacks; } // texture rules const std::vector<TextureRule>& GetTextureRules() const { return m_texture_rules; } // presets [[nodiscard]] bool HasActivePreset() const; [[nodiscard]] std::string GetActivePreset(std::string_view category = "") const; [[nodiscard]] std::vector<PresetPtr> GetActivePresets() const; [[nodiscard]] bool IsPresetVisible(const PresetPtr& preset) const; [[nodiscard]] std::optional<PresetVar> GetPresetVariable(const std::vector<PresetPtr>& presets, std::string_view var_name) const; void ValidatePresetSelections(); bool SetActivePreset(std::string_view category, std::string_view name, bool update_visibility = true); bool SetActivePreset(std::string_view name); void UpdatePresetVisibility(); void AddConstantsForCurrentPreset(ExpressionParser& ep); bool ResolvePresetConstant(const std::string& varname, double& value) const; [[nodiscard]] const std::vector<PresetPtr>& GetPresets() const { return m_presets; } [[nodiscard]] std::unordered_map<std::string, std::vector<PresetPtr>> GetCategorizedPresets(std::vector<std::string>& order) const; // shaders void LoadShaders(); bool HasShaders() const; const std::vector<CustomShader>& GetCustomShaders() const { return m_custom_shaders; } static const std::string* FindCustomShaderSource(uint64 shaderBaseHash, uint64 shaderAuxHash, GP_SHADER_TYPE type, bool isVulkanRenderer, bool isMetalRenderer); const std::string& GetOutputShaderSource() const { return m_output_shader_source; } const std::string& GetDownscalingShaderSource() const { return m_downscaling_shader_source; } const std::string& GetUpscalingShaderSource() const { return m_upscaling_shader_source; } RendererOutputShader* GetOuputShader(bool render_upside_down); RendererOutputShader* GetUpscalingShader(bool render_upside_down); RendererOutputShader* GetDownscalingShader(bool render_upside_down); LatteTextureView::MagFilter GetUpscalingMagFilter() const { return m_output_settings.upscale_filter; } LatteTextureView::MagFilter GetDownscalingMagFilter() const { return m_output_settings.downscale_filter; } // static methods static void LoadAll(); static const std::vector<std::shared_ptr<GraphicPack2>>& GetGraphicPacks() { return s_graphic_packs; } static const std::vector<std::shared_ptr<GraphicPack2>>& GetActiveGraphicPacks() { return s_active_graphic_packs; } static void LoadGraphicPack(fs::path graphicPackPath); static bool LoadGraphicPack(const fs::path& rulesPath, class IniParser& rules); static bool ActivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack); static bool DeactivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack); static void ClearGraphicPacks(); static void WaitUntilReady(); // wait until all graphic packs finished activation static void ActivateForCurrentTitle(); static void Reset(); private: bool Activate(); bool Deactivate(); static std::vector<std::shared_ptr<GraphicPack2>> s_graphic_packs; static std::vector<std::shared_ptr<GraphicPack2>> s_active_graphic_packs; static std::atomic_bool s_isReady; template<typename TType> void FillPresetConstants(TExpressionParser<TType>& parser) const { // fils preset variables with priority // active && visible > active > default const auto active_presets = GetActivePresets(); for(const auto& preset : active_presets) { if(preset->visible) { for (auto& var : preset->variables) parser.AddConstant(var.first, (TType)var.second.second); } } for(const auto& preset : active_presets) { if(!preset->visible) { for (auto& var : preset->variables) parser.TryAddConstant(var.first, (TType)var.second.second); } } for (auto& var : m_preset_vars) parser.TryAddConstant(var.first, (TType)var.second.second); } fs::path m_rulesPath; sint32 m_version; std::string m_name; std::string m_virtualPath; std::string m_description; bool m_default_enabled = false; bool m_allowRendertargetSizeOptimization = false; // gfx pack supports framebuffers with non-padded sizes, which is an optional optimization introduced with Cemu 2.0-74 // filter std::optional<RendererAPI> m_renderer_api; std::optional<GfxVendor> m_gfx_vendor; bool m_enabled = false; bool m_activated = false; // set if the graphic pack is currently used by the running game std::vector<uint64_t> m_title_ids; bool m_patchedFilesLoaded = false; // set to true once patched files are loaded bool m_universal = false; // set if this pack applies to every title id sint32 m_vsync_frequency = -1; sint32 m_fs_priority = 100; struct { LatteTextureView::MagFilter upscale_filter = LatteTextureView::MagFilter::kLinear; LatteTextureView::MagFilter downscale_filter = LatteTextureView::MagFilter::kLinear; } m_output_settings; std::vector<PresetPtr> m_presets; // default preset vars std::unordered_map<std::string, PresetVar> m_preset_vars; std::vector<CustomShader> m_custom_shaders; std::vector<TextureRule> m_texture_rules; std::string m_output_shader_source, m_upscaling_shader_source, m_downscaling_shader_source; std::unique_ptr<RendererOutputShader> m_output_shader, m_upscaling_shader, m_downscaling_shader, m_output_shader_ud, m_upscaling_shader_ud, m_downscaling_shader_ud; template<typename T> bool ParseRule(const ExpressionParser& parser, IniParser& iniParser, const char* option_name, T* value_out) const; template<typename T> std::vector<T> ParseList(const ExpressionParser& parser, IniParser& iniParser, const char* option_name) const; std::unordered_map<std::string, PresetVar> ParsePresetVars(IniParser& rules) const; std::vector<uint64> ParseTitleIds(IniParser& rules, const char* option_name); CustomShader LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type, bool isMetalShader) const; void ApplyShaderPresets(std::string& shader_source) const; void LoadReplacedFiles(); void _iterateReplacedFiles(const fs::path& currentPath, bool isAOC, const char* virtualMountBase); // ram mappings std::vector<std::pair<MPTR, MPTR>> m_ramMappings; // patches void LoadPatchFiles(); // loads Cemuhook or Cemu patches bool LoadCemuPatches(); void ParseCemuhookPatchesTxtInternal(MemStreamReader& patchesStream); bool ParseCemuPatchesTxtInternal(MemStreamReader& patchesStream); void CancelParsingPatches(); void ApplyPatchGroups(std::vector<PatchGroup*>& groups, const RPLModule* rpl); void UndoPatchGroups(std::vector<PatchGroup*>& groups, const RPLModule* rpl); void AddPatchGroup(PatchGroup* group); sint32 GetLengthWithoutComment(const char* str, size_t length); void LogPatchesSyntaxError(sint32 lineNumber, std::string_view errorMsg); std::vector<PatchGroup*> list_patchGroups; std::vector<std::pair<MPTR, GPCallbackType>> m_callbacks; static std::recursive_mutex mtx_patches; static std::vector<const RPLModule*> list_modules; public: static std::vector<std::pair<MPTR, MPTR>> GetActiveRAMMappings(); void EnablePatches(); void UnloadPatches(); bool HasPatches(); const std::vector<PatchGroup*>& GetPatchGroups(); void ApplyPatchesForModule(const RPLModule* rpl); void RevertPatchesForModule(const RPLModule* rpl); static void NotifyModuleLoaded(const RPLModule* rpl); static void NotifyModuleUnloaded(const RPLModule* rpl); }; using GraphicPackPtr = std::shared_ptr<GraphicPack2>; template <typename T> bool GraphicPack2::ParseRule(const ExpressionParser& parser, IniParser& iniParser, const char* option_name, T* value_out) const { auto option_value = iniParser.FindOption(option_name); if (option_value) { *value_out = parser.Evaluate<T>(*option_value); return true; } return false; } template <typename T> std::vector<T> GraphicPack2::ParseList(const ExpressionParser& parser, IniParser& iniParser, const char* option_name) const { std::vector<T> result; auto option_text = iniParser.FindOption(option_name); if (!option_text) return result; for(auto& token : Tokenize(*option_text, ',')) { try { result.emplace_back(parser.Evaluate<T>(token)); } catch (const std::invalid_argument&) {} } return result; } ================================================ FILE: src/Cafe/GraphicPack/GraphicPack2Patches.cpp ================================================ #include "Cafe/GraphicPack/GraphicPack2.h" #include "Cemu/Logging/CemuLogging.h" #include "Common/FileStream.h" #include "WindowSystem.h" #include "util/helpers/StringParser.h" #include "Cemu/PPCAssembler/ppcAssembler.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "boost/algorithm/string.hpp" // error handler void PatchErrorHandler::printError(class PatchGroup* patchGroup, sint32 lineNumber, std::string_view errorMsg) { if (m_anyErrorTriggered == false) { // stage error msg cemu_assert(m_gp); if (m_stage == STAGE::PARSER) cemuLog_writeLineToLog(fmt::format("An error occurred while trying to parse the patches for graphic pack \'{}\'", m_gp->GetName()), true, true); else if (m_stage == STAGE::APPLY) cemuLog_writeLineToLog(fmt::format("An error occurred while trying to apply the patches for graphic pack \'{}\'", m_gp->GetName()), true, true); } std::string msg; if (patchGroup == nullptr && lineNumber >= 0) msg.append(fmt::format("[Line {}] ", lineNumber)); else if (patchGroup && lineNumber >= 0) msg.append(fmt::format("[{}, Line {}] ", patchGroup->getName(), lineNumber)); else if (patchGroup && lineNumber < 0) msg.append(fmt::format("[{}] ", patchGroup->getName())); msg.append(errorMsg); cemuLog_writeLineToLog(msg, true, true); m_anyErrorTriggered = true; if (cemuLog_isLoggingEnabled(LogType::Patches)) errorMessages.emplace_back(msg); } void PatchErrorHandler::showStageErrorMessageBox() { std::string errorMsg; if (m_gp) { if (m_stage == STAGE::PARSER) errorMsg.assign(_tr("Failed to load patches for graphic pack \'{}\'", m_gp->GetName())); else errorMsg.assign(_tr("Failed to apply patches for graphic pack \'{}\'", m_gp->GetName())); } else { cemu_assert_debug(false); // graphic pack should always be set } if (cemuLog_isLoggingEnabled(LogType::Patches)) { errorMsg.append("\n \n") .append(_tr("Details:")) .append("\n"); for (auto& itr : errorMessages) { errorMsg.append(itr); errorMsg.append("\n"); } } WindowSystem::ShowErrorDialog(errorMsg, _tr("Graphic pack error"), WindowSystem::ErrorCategory::GRAPHIC_PACKS); } // loads Cemu-style patches (patch_<anything>.asm) // returns true if at least one file was found even if it could not be successfully parsed bool GraphicPack2::LoadCemuPatches() { bool foundPatches = false; fs::path path(m_rulesPath); path.remove_filename(); for (auto& p : fs::directory_iterator(path)) { auto& path = p.path(); if (fs::is_regular_file(p.status()) && path.has_filename()) { // check if filename matches std::string filename = _pathToUtf8(path.filename()); if (boost::istarts_with(filename, "patch_") && boost::iends_with(filename, ".asm")) { FileStream* patchFile = FileStream::openFile2(path); if (patchFile) { // read file std::vector<uint8> fileData; patchFile->extract(fileData); delete patchFile; MemStreamReader patchesStream(fileData.data(), (sint32)fileData.size()); // load Cemu style patch file if (!ParseCemuPatchesTxtInternal(patchesStream)) { cemuLog_log(LogType::Force, "Error while processing \"{}\". No patches for this graphic pack will be applied.", _pathToUtf8(path)); cemu_assert_debug(list_patchGroups.empty()); return true; // return true since a .asm patch was found even if we could not parse it } } else { cemuLog_log(LogType::Force, "Unable to load patch file \"{}\"", _pathToUtf8(path)); } foundPatches = true; } } } return foundPatches; } void GraphicPack2::LoadPatchFiles() { // order of loading patches: // 1) Load Cemu-style patches (patch_<name>.asm), stop here if at least one patch file exists // 2) Load Cemuhook patches.txt if (LoadCemuPatches()) return; // exit if at least one Cemu style patch file was found // fall back to Cemuhook patches.txt to guarantee backward compatibility fs::path path(m_rulesPath); path.remove_filename(); path.append("patches.txt"); FileStream* patchFile = FileStream::openFile2(path); if (patchFile == nullptr) return; // read file std::vector<uint8> fileData; patchFile->extract(fileData); delete patchFile; cemu_assert_debug(list_patchGroups.empty()); // parse MemStreamReader patchesStream(fileData.data(), (sint32)fileData.size()); ParseCemuhookPatchesTxtInternal(patchesStream); } void GraphicPack2::EnablePatches() { std::lock_guard<std::recursive_mutex> lock(mtx_patches); for (auto& itr : list_modules) ApplyPatchesForModule(itr); } void GraphicPack2::UnloadPatches() { if (list_patchGroups.empty()) return; std::lock_guard<std::recursive_mutex> lock(mtx_patches); // if any patch groups were applied then revert here // do this by calling RevertPatchesForModule for every module? for (auto& itr : list_modules) RevertPatchesForModule(itr); // delete all patches for (auto itr : list_patchGroups) delete itr; list_patchGroups.clear(); } bool GraphicPack2::HasPatches() { return !list_patchGroups.empty(); } const std::vector<PatchGroup*>& GraphicPack2::GetPatchGroups() { return list_patchGroups; } void GraphicPack2::ApplyPatchesForModule(const RPLModule* rpl) { if (list_patchGroups.empty()) return; // gather list of all patch groups that apply to this module std::vector<PatchGroup*> list_groups; for (auto itr : list_patchGroups) { if (itr->matchesCRC(rpl->patchCRC) || (itr->m_isRpxOnlyTarget && rpl->IsRPX())) list_groups.emplace_back(itr); } // apply all groups at once if (!list_groups.empty()) ApplyPatchGroups(list_groups, rpl); } void GraphicPack2::RevertPatchesForModule(const RPLModule* rpl) { if (list_patchGroups.empty()) return; // gather list of all patch groups that apply to this module std::vector<PatchGroup*> list_groups; for (auto itr : list_patchGroups) { if (itr->matchesCRC(rpl->patchCRC) || (itr->m_isRpxOnlyTarget && rpl->IsRPX())) list_groups.emplace_back(itr); } // undo all groups at once if (!list_groups.empty()) UndoPatchGroups(list_groups, rpl); } std::recursive_mutex GraphicPack2::mtx_patches; std::vector<const RPLModule*> GraphicPack2::list_modules; ================================================ FILE: src/Cafe/GraphicPack/GraphicPack2Patches.h ================================================ #pragma once class PatchGroup; #include "GraphicPackError.h" struct PatchContext_t { struct UnresolvedSymbol { sint32 lineNumber; PatchGroup* patchGroup; std::string symbolName; UnresolvedSymbol(sint32 _lineNumber, PatchGroup* _patchGroup, std::string _symbolName) : lineNumber(_lineNumber), patchGroup(_patchGroup), symbolName(_symbolName) {}; bool operator < (const UnresolvedSymbol &other) const { if (lineNumber == other.lineNumber) return symbolName.compare(other.symbolName); return lineNumber < other.lineNumber; } }; class GraphicPack2* graphicPack; //MEMPTR<void> codeCaveStart; //MEMPTR<void> codeCaveEnd; const RPLModule* matchedModule; std::unordered_map<std::string, uint32> map_values; // error information //std::unordered_set<std::string> unresolvedSymbols; std::set<UnresolvedSymbol> unresolvedSymbols; //std::unordered_multiset<sint32, std::greater<std::string>> unresolvedSymbols; // error handler PatchErrorHandler errorHandler{}; }; enum class PATCH_RESOLVE_RESULT { RESOLVED, // successfully resolved any expressions or relocations EXPRESSION_ERROR, // syntax error in expression (usually this should be detected during the parsing stage already) VALUE_ERROR, // expression evaluated but the result is not useable (e.g. branch target out of range) UNKNOWN_VARIABLE, // variable not known or referencing unresolved variable (try again) VARIABLE_CONFLICT, // a variable or label with the same name was already defined INVALID_ADDRESS, // attempted to relocate address that is not within any known section UNDEFINED_ERROR, // unexpected error }; enum class EXPRESSION_RESOLVE_RESULT { AVAILABLE, EXPRESSION_ERROR, UNKNOWN_VARIABLE }; class PatchEntry { public: PatchEntry() {}; virtual ~PatchEntry() {}; // apply relocation or evaluate any expressions for this entry virtual PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) = 0; }; // represents symbol assignment (always treated like an address) // <symbolName> = <expression> class PatchEntryCemuhookSymbolValue : public PatchEntry { public: PatchEntryCemuhookSymbolValue(sint32 lineNumber, const char* symbolName, const sint32 symbolNameLen, const char* expressionStr, const sint32 expressionLen) : PatchEntry(), m_lineNumber(lineNumber) { m_symbolName.assign(symbolName, symbolNameLen); m_expressionString.assign(expressionStr, expressionLen); } sint32 getLineNumber() { return m_lineNumber; } PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override; std::string& getSymbolName() { return m_symbolName; } private: sint32 m_lineNumber; std::string m_symbolName; std::string m_expressionString; uint32 m_resolvedValue; bool m_isResolved{}; }; enum class PATCHVARTYPE { DOUBLE, INT, // 32bit signed integer UINT, // 32bit unsigned integer or pointer //BOOL, // boolean }; // represents variable value assignment // unlike Cemu symbols these are treated as a // <symbolName> = <expression> class PatchEntryVariableValue : public PatchEntry { public: PatchEntryVariableValue(sint32 lineNumber, const char* symbolName, const sint32 symbolNameLen, PATCHVARTYPE varType, const char* expressionStr, const sint32 expressionLen) : PatchEntry(), m_lineNumber(lineNumber), m_varType(varType) { m_symbolName.assign(symbolName, symbolNameLen); m_expressionString.assign(expressionStr, expressionLen); } sint32 getLineNumber() { return m_lineNumber; } PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override; std::string& getSymbolName() { return m_symbolName; } //uint32 getSymbolValue() { return m_resolvedValue; } private: sint32 m_lineNumber; std::string m_symbolName; std::string m_expressionString; PATCHVARTYPE m_varType; std::variant<sint32, uint32, double> m_resolvedValue; bool m_isResolved{}; }; // represents a label class PatchEntryLabel : public PatchEntry { public: PatchEntryLabel(sint32 lineNumber, const char* symbolName, const sint32 symbolNameLen) : PatchEntry(), m_lineNumber(lineNumber) { m_symbolName.assign(symbolName, symbolNameLen); } sint32 getLineNumber() { return m_lineNumber; } PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override; std::string& getSymbolName() { return m_symbolName; } uint32 getSymbolValue() { return m_relocatedAddress; } void setAssignedVA(uint32 virtualAddress) { m_address = virtualAddress; } private: sint32 m_lineNumber; std::string m_symbolName; uint32 m_address; uint32 m_relocatedAddress; bool m_isResolved{}; }; // represents assembled code/data class PatchEntryInstruction : public PatchEntry { public: PatchEntryInstruction(sint32 lineNumber, uint32 patchAddr, std::span<uint8> data, std::vector<PPCAssemblerReloc>& list_relocs) : PatchEntry(), m_lineNumber(lineNumber), m_addr(patchAddr), m_size((uint32)data.size()), m_relocs(list_relocs) { sint32 dataLength = (sint32)data.size(); m_length = dataLength; m_data = new uint8[dataLength]; m_dataWithRelocs = new uint8[dataLength]; m_dataBackup = new uint8[dataLength]; memcpy(m_data, data.data(), dataLength); memcpy(m_dataWithRelocs, data.data(), dataLength); } ~PatchEntryInstruction() { if (m_data) delete[] m_data; if (m_dataWithRelocs) delete[] m_dataWithRelocs; if (m_dataBackup) delete[] m_dataBackup; } uint32 getAddr() const { return m_addr; } uint32 getRelocatedAddr() { return m_relocatedAddr; } uint32 getSize() const { return m_size; } PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override; PATCH_RESOLVE_RESULT resolveReloc(PatchContext_t& ctx, PPCAssemblerReloc* reloc); void applyPatch(); void undoPatch(); private: uint8* m_data{}; // original unrelocated data uint8* m_dataWithRelocs{}; // data with relocs applied uint8* m_dataBackup{}; // original data before patch was applied sint32 m_length{}; std::vector<PPCAssemblerReloc> m_relocs; uint32 m_lineNumber; uint32 m_addr; uint32 m_size; uint32 m_relocatedAddr; bool m_addrRelocated{}; }; enum class GPCallbackType { Entry }; class PatchGroup { friend class GraphicPack2; public: PatchGroup(class GraphicPack2* gp, const char* nameStr, sint32 nameLength) : graphicPack(gp) { name = std::string(nameStr, nameLength); } bool matchesCRC(uint32 crc) { for (auto& chk : list_moduleMatches) { if (chk == crc) return true; } return false; } uint32 getCodeCaveBase() { return codeCaveMem.GetMPTR(); } uint32 getCodeCaveSize() { return codeCaveSize; } std::string_view getName() { return name; } void setApplied() { m_isApplied = true; } void resetApplied() { m_isApplied = false; } bool isApplied() const { return m_isApplied; } private: class GraphicPack2* graphicPack; std::string name; std::vector<uint32> list_moduleMatches; std::vector<PatchEntry*> list_patches; std::vector<std::pair<std::string, GPCallbackType>> list_callbacks; uint32 codeCaveSize; MEMPTR<void> codeCaveMem; bool m_isApplied{}; bool m_isRpxOnlyTarget{}; }; ================================================ FILE: src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp ================================================ #include "Cafe/GraphicPack/GraphicPack2.h" #include "Common/FileStream.h" #include "Cemu/PPCAssembler/ppcAssembler.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h" bool _relocateAddress(PatchGroup* group, PatchContext_t* ctx, uint32 addr, uint32& relocatedAddress) { if (addr >= 0 && addr <= 1024 * 1024 * 8) { // codecave address relocatedAddress = group->getCodeCaveBase() + addr; return true; } // check if address is within module section for (sint32 i = 0; i < ctx->matchedModule->rplHeader.sectionTableEntryCount; i++) { auto sect = ctx->matchedModule->sectionTablePtr + i; if (addr >= sect->virtualAddress && addr < (sect->virtualAddress + sect->sectionSize)) { relocatedAddress = addr - sect->virtualAddress + memory_getVirtualOffsetFromPointer(ctx->matchedModule->sectionAddressTable2[i].ptr); return true; } } relocatedAddress = 0; return false; } struct { bool hasUnknownVariable; PatchContext_t* activePatchContext; PatchGroup* currentGroup; // additional error information tracking sint32 lineNumber; // line number of the expression being processed, negative if not available bool captureUnresolvedSymbols; }resolverState{}; bool GraphicPack2::ResolvePresetConstant(const std::string& varname, double& value) const { const auto var = GetPresetVariable(GetActivePresets(), varname); if (var) { value = var->second; return true; } return false; } template<typename T> T _expressionFuncHA(T input) { uint32 u32 = (uint32)input; u32 = (((u32 >> 16) + ((u32 & 0x8000) ? 1 : 0)) & 0xffff); return (T)u32; } template<typename T> T _expressionFuncHI(T input) { uint32 u32 = (uint32)input; u32 = (u32 >> 16) & 0xffff; return (T)u32; } template<typename T> T _expressionFuncLO(T input) { uint32 u32 = (uint32)input; u32 &= 0xffff; return (T)u32; } template<typename T> T _expressionFuncReloc(T input) { uint32 addr = (uint32)input; uint32 relocatedAddress = 0; if(!_relocateAddress(resolverState.currentGroup, resolverState.activePatchContext, addr, relocatedAddress)) { resolverState.activePatchContext->errorHandler.printError(resolverState.currentGroup, resolverState.lineNumber, fmt::format("reloc({0:#08x}): Address does not point to a known memory region", addr)); return (T)0; } return (T)relocatedAddress; } double _cbResolveConstant(std::string_view varname) { std::string varnameOnly; std::string tokenOnly; // detect suffix bool hasSuffix = false; const auto idx = varname.find('@'); if (idx != std::string_view::npos) { hasSuffix = true; varnameOnly = varname.substr(0, idx); tokenOnly = varname.substr(idx + 1); } else varnameOnly = varname; double value; if (varnameOnly.length() >= 1 && varnameOnly[0] == '$') { // resolve preset variable if (!resolverState.activePatchContext->graphicPack->ResolvePresetConstant(varnameOnly, value)) { resolverState.hasUnknownVariable = true; if (resolverState.captureUnresolvedSymbols) resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, varnameOnly); return 0.0; } } else if (varnameOnly.length() >= 7 && boost::iequals(varnameOnly.substr(0, 7), "import.")) { // resolve import std::string importName = varnameOnly.substr(7); // detect imports const auto idxDot = importName.find('.'); bool isValidImport = false; std::string_view importError = ""; if (idxDot != std::string_view::npos) { std::string moduleName = importName.substr(0, idxDot); std::string functionName = importName.substr(idxDot + 1); uint32 rplHandle = RPLLoader_GetHandleByModuleName(moduleName.c_str()); if (rplHandle == RPL_INVALID_HANDLE) { importError = " (module not found)"; } else { MPTR exportResult = RPLLoader_FindModuleOrHLEExport(rplHandle, false, functionName.c_str()); if (exportResult) { isValidImport = true; value = (double)exportResult; } else importError = " (function not found)"; } } else importError = " (invalid import syntax)"; // error output if (!isValidImport) { resolverState.hasUnknownVariable = true; if (resolverState.captureUnresolvedSymbols) { std::string detailedSymbolName; detailedSymbolName.assign(importName); detailedSymbolName.append(importError); resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, detailedSymbolName); } return 0.0; } } else { // resolve variable const auto v = resolverState.activePatchContext->map_values.find(varnameOnly); if (v == resolverState.activePatchContext->map_values.end()) { resolverState.hasUnknownVariable = true; if (resolverState.captureUnresolvedSymbols) resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, varnameOnly); return 0.0; } value = v->second; } if (hasSuffix) { std::transform(tokenOnly.cbegin(), tokenOnly.cend(), tokenOnly.begin(), tolower); if (tokenOnly == "ha") { value = _expressionFuncHA<double>(value); } else if (tokenOnly == "h" || tokenOnly == "hi") { value = _expressionFuncHI<double>(value); } else if (tokenOnly == "l" || tokenOnly == "lo") { value = _expressionFuncLO<double>(value); } else { // we treat unknown suffixes as unresolveable symbols resolverState.hasUnknownVariable = true; if (resolverState.captureUnresolvedSymbols) { std::string detailedSymbolName; detailedSymbolName.assign(varnameOnly); detailedSymbolName.append("@"); detailedSymbolName.append(tokenOnly); detailedSymbolName.append(" (invalid suffix)"); resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, detailedSymbolName); } return 0.0; } } return value; } double _cbResolveFunction(std::string_view funcname, double input) { std::string funcnameLC(funcname); std::transform(funcnameLC.cbegin(), funcnameLC.cend(), funcnameLC.begin(), tolower); double value = input; if (funcnameLC == "ha" || funcnameLC == "ha16") value = _expressionFuncHA<double>(value); else if (funcnameLC == "hi" || funcnameLC == "hi16") value = _expressionFuncHI<double>(value); else if (funcnameLC == "lo" || funcnameLC == "lo16") value = _expressionFuncLO<double>(value); else if (funcnameLC == "reloc") value = _expressionFuncReloc<double>(value); else { // unresolvable function resolverState.hasUnknownVariable = true; if (resolverState.captureUnresolvedSymbols) { std::string detailedSymbolName; detailedSymbolName.assign(funcname); detailedSymbolName.append("() (unknown function)"); resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, detailedSymbolName); } return 0.0; } return value; } template<typename T> EXPRESSION_RESOLVE_RESULT _resolveExpression(PatchContext_t& ctx, std::string& expressionString, T& result, sint32 associatedLineNumber = -1) { resolverState.lineNumber = associatedLineNumber; ExpressionParser ep; try { // add all the graphic pack constants ep.AddConstantCallback(_cbResolveConstant); ep.SetFunctionCallback(_cbResolveFunction); resolverState.hasUnknownVariable = false; result = (T)ep.Evaluate(expressionString); if (resolverState.hasUnknownVariable) return EXPRESSION_RESOLVE_RESULT::UNKNOWN_VARIABLE; } catch (const std::exception&) { cemu_assert_debug(false); ctx.errorHandler.printError(nullptr, -1, fmt::format("Unexpected error in expression \"{}\"", expressionString)); return EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR; } return EXPRESSION_RESOLVE_RESULT::AVAILABLE; } PATCH_RESOLVE_RESULT translateExpressionResult(EXPRESSION_RESOLVE_RESULT expressionResult) { if (expressionResult == EXPRESSION_RESOLVE_RESULT::AVAILABLE) return PATCH_RESOLVE_RESULT::RESOLVED; else if (expressionResult == EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR) return PATCH_RESOLVE_RESULT::EXPRESSION_ERROR; else if (expressionResult == EXPRESSION_RESOLVE_RESULT::UNKNOWN_VARIABLE) return PATCH_RESOLVE_RESULT::UNKNOWN_VARIABLE; cemu_assert(false); return PATCH_RESOLVE_RESULT::EXPRESSION_ERROR; } PATCH_RESOLVE_RESULT PatchEntryInstruction::resolveReloc(PatchContext_t& ctx, PPCAssemblerReloc* reloc) { MPTR finalRelocAddr = m_relocatedAddr + reloc->m_byteOffset; if (reloc->m_relocType == PPCASM_RELOC::FLOAT) { // resolve float expression float result; auto r = _resolveExpression<float>(ctx, reloc->m_expression, result, m_lineNumber); if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) { cemu_assert((reloc->m_byteOffset + sizeof(betype<float>)) <= m_length); *(betype<float>*)(m_dataWithRelocs + reloc->m_byteOffset) = result; DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::FLOAT); return PATCH_RESOLVE_RESULT::RESOLVED; } else return translateExpressionResult(r); } else if (reloc->m_relocType == PPCASM_RELOC::DOUBLE) { // resolve double expression double result; auto r = _resolveExpression<double>(ctx, reloc->m_expression, result, m_lineNumber); if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) { cemu_assert((reloc->m_byteOffset + sizeof(betype<double>)) <= m_length); *(betype<double>*)(m_dataWithRelocs + reloc->m_byteOffset) = result; DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::DOUBLE); return PATCH_RESOLVE_RESULT::RESOLVED; } else return translateExpressionResult(r); } else { // resolve uint32 expression uint32 result; auto r = _resolveExpression<uint32>(ctx, reloc->m_expression, result, m_lineNumber); if (r != EXPRESSION_RESOLVE_RESULT::AVAILABLE) return translateExpressionResult(r); if (reloc->m_relocType == PPCASM_RELOC::U32) { cemu_assert((reloc->m_byteOffset + sizeof(betype<uint32>)) <= m_length); *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset) = result; DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::U32); return PATCH_RESOLVE_RESULT::RESOLVED; } else if (reloc->m_relocType == PPCASM_RELOC::U16) { cemu_assert((reloc->m_byteOffset + sizeof(betype<uint16>)) <= m_length); *(betype<uint16>*)(m_dataWithRelocs + reloc->m_byteOffset) = (uint16)result; DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::U16); return PATCH_RESOLVE_RESULT::RESOLVED; } else if (reloc->m_relocType == PPCASM_RELOC::U8) { cemu_assert((reloc->m_byteOffset + sizeof(betype<uint8>)) <= m_length); *(betype<uint8>*)(m_dataWithRelocs + reloc->m_byteOffset) = (uint8)result; DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::U8); return PATCH_RESOLVE_RESULT::RESOLVED; } else if (reloc->m_relocType == PPCASM_RELOC::U32_MASKED_IMM) { cemu_assert((reloc->m_byteOffset + sizeof(betype<uint32>)) <= m_length); uint32 opcode = *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset); cemu_assert_debug(reloc->m_bitCount != 0); uint32 mask = 0xFFFFFFFF >> (32 - reloc->m_bitCount); mask <<= reloc->m_bitOffset; opcode &= ~mask; opcode |= ((result << reloc->m_bitOffset) & mask); *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset) = opcode; return PATCH_RESOLVE_RESULT::RESOLVED; } else if (reloc->m_relocType == PPCASM_RELOC::BRANCH_S26) { cemu_assert((reloc->m_byteOffset + sizeof(betype<uint32>)) <= m_length); uint32 opcode = *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset); if (opcode & 2) { // absolute if (result >= 0x3FFFFFC) { cemuLog_log(LogType::Force, "Target \'{}\' for branch at line {} out of range", reloc->m_expression, m_lineNumber); return PATCH_RESOLVE_RESULT::VALUE_ERROR; } opcode &= ~0x3FFFFFC; opcode |= (result & 0x3FFFFFC); } else { // relative uint32 instrAddr = this->getRelocatedAddr() + reloc->m_byteOffset; if (result < instrAddr) { // jump backwards uint32 jumpB = instrAddr - result; if (jumpB > 0x1FFFFFF) { ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); return PATCH_RESOLVE_RESULT::VALUE_ERROR; } opcode &= ~0x3FFFFFC; opcode |= ((~jumpB + 1) & 0x3FFFFFC); } else { // jump forwards uint32 jumpF = result - instrAddr; if (jumpF >= 0x1FFFFFF) { ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); return PATCH_RESOLVE_RESULT::VALUE_ERROR; } opcode &= ~0x3FFFFFC; opcode |= (jumpF & 0x3FFFFFC); } } *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset) = opcode; return PATCH_RESOLVE_RESULT::RESOLVED; } else if (reloc->m_relocType == PPCASM_RELOC::BRANCH_S16) { cemu_assert((reloc->m_byteOffset + sizeof(betype<uint32>)) <= m_length); uint32 opcode = *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset); uint32 instrAddr = this->getRelocatedAddr() + reloc->m_byteOffset; if (result < instrAddr) { // jump backwards uint32 jumpB = instrAddr - result; if (jumpB > 0x8000) { ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); return PATCH_RESOLVE_RESULT::VALUE_ERROR; } opcode &= ~0xFFFC; opcode |= ((~jumpB + 1) & 0xFFFC); } else { // jump forwards uint32 jumpF = result - instrAddr; if (jumpF >= 0x8000) { ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); return PATCH_RESOLVE_RESULT::VALUE_ERROR; } opcode &= ~0xFFFC; opcode |= (jumpF & 0xFFFC); } *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset) = opcode; return PATCH_RESOLVE_RESULT::RESOLVED; } // *internalCtx.opcode |= (relativeAddr & 0xFFFC); cemu_assert_debug(false); } return PATCH_RESOLVE_RESULT::UNDEFINED_ERROR; } PATCH_RESOLVE_RESULT PatchEntryInstruction::resolve(PatchContext_t& ctx) { // relocate patch address if (!m_addrRelocated) { if (_relocateAddress(resolverState.currentGroup, &ctx, m_addr, m_relocatedAddr) == false) { cemuLog_log(LogType::Force, "Patches: Address 0x{:08x} (line {}) is not within code cave or any module section", this->getAddr(), this->m_lineNumber); cemu_assert_debug(false); return PATCH_RESOLVE_RESULT::INVALID_ADDRESS; } m_addrRelocated = true; } // apply relocations to instruction for (auto& itr : this->m_relocs) { if(itr.isApplied()) continue; // evaluate expression and apply reloc to internal buffer auto r = resolveReloc(ctx, &itr); if (r == PATCH_RESOLVE_RESULT::RESOLVED) { itr.setApplied(); continue; } return r; } return PATCH_RESOLVE_RESULT::RESOLVED; } void PatchEntryInstruction::applyPatch() { const uint32 addr = getRelocatedAddr(); if (addr == 0) { cemu_assert_debug(false); return; } uint8* patchAddr = (uint8*)memory_base + addr; memcpy(m_dataBackup, patchAddr, m_length); memcpy(patchAddr, m_dataWithRelocs, m_length); PPCRecompiler_invalidateRange(addr, addr + m_length); } void PatchEntryInstruction::undoPatch() { const uint32 addr = getRelocatedAddr(); if (addr == 0) { cemu_assert_debug(false); return; } uint8* patchAddr = (uint8*)memory_base + addr; memcpy(patchAddr, m_dataBackup, m_length); PPCRecompiler_invalidateRange(addr, addr + m_length); rplSymbolStorage_removeRange(addr, m_length); DebugSymbolStorage::ClearRange(addr, m_length); } // returns true on success, false if variable with same name already exists bool registerU32Variable(PatchContext_t& ctx, std::string& name, uint32 value, PatchGroup* associatedPatchGroup, uint32 associatedLineNumber, bool isAddress) { cemuLog_log(LogType::Patches, "Resolved symbol {} with value 0x{:08x}", name.c_str(), value); if (ctx.map_values.find(name) != ctx.map_values.end()) { return false; } ctx.map_values[name] = value; // keep track of address symbols for the debugger rplSymbolStorage_store(ctx.graphicPack->GetName().data(), name.data(), value); return true; } PATCH_RESOLVE_RESULT PatchEntryCemuhookSymbolValue::resolve(PatchContext_t& ctx) { uint32 addr; auto r = _resolveExpression<uint32>(ctx, m_expressionString, addr, m_lineNumber); if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) { if (_relocateAddress(resolverState.currentGroup, &ctx, addr, m_resolvedValue)) { m_isResolved = true; // register variable if (!registerU32Variable(ctx, m_symbolName, m_resolvedValue, resolverState.currentGroup, getLineNumber(), true)) { if (resolverState.captureUnresolvedSymbols) ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Symbol {} is already defined", m_symbolName)); return PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT; } return PATCH_RESOLVE_RESULT::RESOLVED; } return PATCH_RESOLVE_RESULT::INVALID_ADDRESS; } return translateExpressionResult(r); } PATCH_RESOLVE_RESULT PatchEntryLabel::resolve(PatchContext_t& ctx) { if (_relocateAddress(resolverState.currentGroup, &ctx, m_address, m_relocatedAddress)) { m_isResolved = true; // register variable if (!registerU32Variable(ctx, m_symbolName, m_relocatedAddress, resolverState.currentGroup, getLineNumber(), true)) { if (resolverState.captureUnresolvedSymbols) ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Label {} is already defined", m_symbolName)); return PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT; } return PATCH_RESOLVE_RESULT::RESOLVED; } if(resolverState.captureUnresolvedSymbols) ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Address {:#08x} of label {} does not point to any module section or code cave", m_address, m_symbolName)); return PATCH_RESOLVE_RESULT::INVALID_ADDRESS; } PATCH_RESOLVE_RESULT PatchEntryVariableValue::resolve(PatchContext_t& ctx) { uint32 v; auto r = _resolveExpression<uint32>(ctx, m_expressionString, v, m_lineNumber); if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) { // register variable if (!registerU32Variable(ctx, m_symbolName, v, resolverState.currentGroup, getLineNumber(), false)) { if (resolverState.captureUnresolvedSymbols) ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Variable {} is already defined", m_symbolName)); return PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT; } return PATCH_RESOLVE_RESULT::RESOLVED; } return translateExpressionResult(r); } struct UnresolvedPatches_t { PatchGroup* patchGroup; std::vector<PatchEntry*> list_unresolvedPatches; }; // returns number of resolved entries bool _resolverPass(PatchContext_t& patchContext, std::vector<UnresolvedPatches_t>& unresolvedPatches, bool captureUnresolvedSymbols = false) { resolverState.captureUnresolvedSymbols = captureUnresolvedSymbols; sint32 numResolvedEntries = 0; for (auto& unresolvedGroup : unresolvedPatches) { resolverState.currentGroup = unresolvedGroup.patchGroup; auto& list_unresolvedPatches = unresolvedGroup.list_unresolvedPatches; for (auto it = list_unresolvedPatches.begin(); it != list_unresolvedPatches.end();) { auto r = (*it)->resolve(patchContext); if (r == PATCH_RESOLVE_RESULT::RESOLVED) { // remove from list it = list_unresolvedPatches.erase(it); numResolvedEntries++; continue; } else if (r == PATCH_RESOLVE_RESULT::UNKNOWN_VARIABLE) { // dependency on other not yet resolved entry, continue iterating it++; continue; } else if (r == PATCH_RESOLVE_RESULT::INVALID_ADDRESS || r == PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT) { // errors handled and printed inside resolve() it++; continue; } else { // unknown error patchContext.errorHandler.printError(resolverState.currentGroup, -1, "Internal error"); it++; } } } return numResolvedEntries; } void GraphicPack2::ApplyPatchGroups(std::vector<PatchGroup*>& groups, const RPLModule* rpl) { // init context information PatchContext_t patchContext{}; patchContext.graphicPack = this; patchContext.matchedModule = rpl; resolverState.activePatchContext = &patchContext; // setup error handler patchContext.errorHandler.setCurrentGraphicPack(this); patchContext.errorHandler.setStage(PatchErrorHandler::STAGE::APPLY); // no group can be applied more than once for (auto patchGroup : groups) { if (patchGroup->isApplied()) { patchContext.errorHandler.printError(patchGroup, -1, "Group already applied to a different module."); return; } } // allocate code cave for every group for (auto patchGroup : groups) { if (patchGroup->codeCaveSize > 0) { auto codeCaveMem = RPLLoader_AllocateCodeCaveMem(256, patchGroup->codeCaveSize); cemuLog_log(LogType::Force, "Applying patch group \'{}\' (Codecave: {:08x}-{:08x})", patchGroup->name, codeCaveMem.GetMPTR(), codeCaveMem.GetMPTR() + patchGroup->codeCaveSize); patchGroup->codeCaveMem = codeCaveMem; } else { cemuLog_log(LogType::Force, "Applying patch group \'{}\'", patchGroup->name); patchGroup->codeCaveMem = nullptr; } } // resolve the patch entries // this means: // - resolving the expressions for variables and registering them // - calculating relocated addresses // - applying relocations to temporary patch buffer // multiple passes may be necessary since forward and backward references are allowed as well as references across group boundaries // create a copy of all the patch references and keep the group association intact std::vector<UnresolvedPatches_t> unresolvedPatches; unresolvedPatches.resize(groups.size()); for (size_t i = 0; i < groups.size(); i++) { unresolvedPatches[i].patchGroup = groups[i]; unresolvedPatches[i].list_unresolvedPatches = groups[i]->list_patches; } auto isUnresolvedPatchesEmpty = [&unresolvedPatches]() { for (auto& itr : unresolvedPatches) if (!itr.list_unresolvedPatches.empty()) return false; return true; }; // resolve and relocate for (sint32 pass = 0; pass < 30; pass++) { bool isLastPass = (pass == 29); sint32 numResolvedEntries = _resolverPass(patchContext, unresolvedPatches, false); if (isUnresolvedPatchesEmpty()) break; if (numResolvedEntries == 0 || isLastPass) { // stuck due to reference to undefined variable or unresolvable cross-references // iterate all remaining expressions and output them to log // execute another resolver pass but capture all the unresolved variables this time patchContext.unresolvedSymbols.clear(); _resolverPass(patchContext, unresolvedPatches, true); // generate messages if(isLastPass) patchContext.errorHandler.printError(nullptr, -1, "Some symbols could not be resolved because the dependency chain is too deep"); for (auto& itr : patchContext.unresolvedSymbols) patchContext.errorHandler.printError(itr.patchGroup, itr.lineNumber, fmt::format("Unresolved symbol: {}", itr.symbolName)); patchContext.errorHandler.showStageErrorMessageBox(); return; } } if (!isUnresolvedPatchesEmpty() || patchContext.errorHandler.hasError()) { patchContext.errorHandler.showStageErrorMessageBox(); return; } // apply relocated patches for (auto patchGroup : groups) { for (auto& patch : patchGroup->list_patches) { PatchEntryInstruction* patchInstruction = dynamic_cast<PatchEntryInstruction*>(patch); if (patchInstruction == nullptr) continue; patchInstruction->applyPatch(); } for (const auto& [name, type] : patchGroup->list_callbacks) { auto it = patchContext.map_values.find(name); if (it != patchContext.map_values.end()) { m_callbacks.push_back(std::make_pair(it->second, type)); } else { patchContext.errorHandler.printError(patchGroup, -1, fmt::format("Failed to resolve .callback symbol: {}", name)); patchContext.errorHandler.showStageErrorMessageBox(); return; } } } // mark groups as applied for (auto patchGroup : groups) patchGroup->setApplied(); } void GraphicPack2::UndoPatchGroups(std::vector<PatchGroup*>& groups, const RPLModule* rpl) { // restore original data for (auto patchGroup : groups) { if (!patchGroup->isApplied()) continue; for (auto& patch : patchGroup->list_patches) { PatchEntryInstruction* patchInstruction = dynamic_cast<PatchEntryInstruction*>(patch); if (patchInstruction == nullptr) continue; patchInstruction->undoPatch(); } } // mark groups as not applied for (auto patchGroup : groups) patchGroup->resetApplied(); } void GraphicPack2::NotifyModuleLoaded(const RPLModule* rpl) { cemuLog_log(LogType::Force, "Loaded module \'{}\' with checksum 0x{:08x}", rpl->moduleName2, rpl->patchCRC); std::lock_guard<std::recursive_mutex> lock(mtx_patches); list_modules.emplace_back(rpl); // todo - iterate all active graphic packs and apply any matching patch groups } void GraphicPack2::NotifyModuleUnloaded(const RPLModule* rpl) { std::lock_guard<std::recursive_mutex> lock(mtx_patches); list_modules.erase(std::remove(list_modules.begin(), list_modules.end(), rpl), list_modules.end()); } ================================================ FILE: src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp ================================================ #include "Cafe/GraphicPack/GraphicPack2.h" #include "Common/FileStream.h" #include "util/helpers/StringParser.h" #include "Cemu/PPCAssembler/ppcAssembler.h" #include "Cafe/OS/RPL/rpl_structs.h" sint32 GraphicPack2::GetLengthWithoutComment(const char* str, size_t length) { sint32 index = 0; bool isInString = false; while (index < length) { const char c = str[index]; if (c == '\"') isInString = !isInString; else if (c == '#' || c == ';') { if (!isInString) return index; } index++; } return (sint32)length; } void GraphicPack2::LogPatchesSyntaxError(sint32 lineNumber, std::string_view errorMsg) { cemuLog_log(LogType::Force, "Syntax error while parsing patch for graphic pack '{}':", _pathToUtf8(this->GetRulesPath())); if(lineNumber >= 0) cemuLog_log(LogType::Force, fmt::format("Line {0}: {1}", lineNumber, errorMsg)); else cemuLog_log(LogType::Force, fmt::format("{0}", errorMsg)); list_patchGroups.clear(); } void GraphicPack2::CancelParsingPatches() { // unload everything, set error flag cemu_assert_debug(false); } void GraphicPack2::AddPatchGroup(PatchGroup* group) { if (group->list_moduleMatches.empty() && !group->m_isRpxOnlyTarget) { LogPatchesSyntaxError(-1, fmt::format("Group \"{}\" has no moduleMatches definition", group->name)); CancelParsingPatches(); delete group; return; } // calculate code cave size uint32 codeCaveMaxAddr = 0; for (auto& itr : group->list_patches) { PatchEntryInstruction* patchData = dynamic_cast<PatchEntryInstruction*>(itr); if (patchData) { uint32 patchAddr = patchData->getAddr(); if (patchAddr < 0x00100000) { // everything in low 1MB of memory we consider part of the code cave codeCaveMaxAddr = std::max(codeCaveMaxAddr, patchAddr + patchData->getSize()); } } } uint32 numEstimatedCodeCaveInstr = codeCaveMaxAddr / 4; if (group->list_patches.size() < (numEstimatedCodeCaveInstr / 8)) { // if less than 1/8th of the code cave is filled print a warning cemuLog_log(LogType::Force, "Graphic pack patches: Code cave for group [{}] in gfx pack \"{}\" ranges from 0 to 0x{:x} but has only few instructions. Is this intentional?", group->name, this->m_name, codeCaveMaxAddr); } group->codeCaveSize = codeCaveMaxAddr; list_patchGroups.emplace_back(group); } void GraphicPack2::ParseCemuhookPatchesTxtInternal(MemStreamReader& patchesStream) { sint32 lineNumber = 0; PatchGroup* currentGroup = nullptr; while (true) { auto lineStr = patchesStream.readLine(); lineNumber++; if (patchesStream.hasError()) break; // trim comment size_t lineLength = GetLengthWithoutComment(lineStr.data(), lineStr.size()); StringTokenParser parser(lineStr.data(), (sint32)lineLength); // skip whitespaces at the beginning parser.skipWhitespaces(); // parse line if (parser.isEndOfString()) continue; if (parser.compareCharacter(0, '[')) { // group parser.skipCharacters(1); // find end of group name const char* groupNameStr = parser.getCurrentPtr(); sint32 groupNameLength = parser.skipToCharacter(']'); if (groupNameLength < 0) { LogPatchesSyntaxError(lineNumber, "Expected ']'"); CancelParsingPatches(); return; } parser.skipCharacters(1); // skip the ']' parser.skipWhitespaces(); if (!parser.isEndOfString()) { LogPatchesSyntaxError(lineNumber, "Unexpected characters after ']'"); CancelParsingPatches(); return; } // begin new group if (currentGroup) { AddPatchGroup(currentGroup); } currentGroup = new PatchGroup(this, groupNameStr, groupNameLength); } else if (parser.compareCharacter(0, '0') && parser.compareCharacterI(1, 'x')) { // if the line starts with a hex address then it is a patched location uint32 patchedAddress; if (!parser.parseU32(patchedAddress)) { LogPatchesSyntaxError(lineNumber, "Malformed address"); CancelParsingPatches(); return; } if (parser.matchWordI("=") == false) { LogPatchesSyntaxError(lineNumber, "Expected '=' after address"); CancelParsingPatches(); return; } parser.skipWhitespaces(); parser.trimWhitespaces(); // assemble instruction std::string instrText(parser.getCurrentPtr(), parser.getCurrentLen()); PPCAssemblerInOut ctx{}; ctx.virtualAddress = patchedAddress; if (!ppcAssembler_assembleSingleInstruction(instrText.c_str(), &ctx)) { LogPatchesSyntaxError(lineNumber, fmt::format("Error in assembler: {}", ctx.errorMsg)); CancelParsingPatches(); return; } currentGroup->list_patches.emplace_back(new PatchEntryInstruction(lineNumber, patchedAddress, { ctx.outputData.data(), ctx.outputData.size() }, ctx.list_relocs)); } else if (parser.matchWordI("moduleMatches")) { if (currentGroup == nullptr) { LogPatchesSyntaxError(lineNumber, "Specified 'ModuleMatches' outside of a group"); CancelParsingPatches(); return; } if (parser.matchWordI("=") == false) { LogPatchesSyntaxError(lineNumber, "Expected '=' after ModuleMatches"); CancelParsingPatches(); return; } // read the checksums while (true) { uint32 checksum = 0; if (parser.parseU32(checksum) == false) { LogPatchesSyntaxError(lineNumber, "Invalid value for ModuleMatches"); CancelParsingPatches(); return; } currentGroup->list_moduleMatches.emplace_back(checksum); if (parser.matchWordI(",") == false) break; } parser.skipWhitespaces(); if (!parser.isEndOfString()) { LogPatchesSyntaxError(lineNumber, "Unexpected character in line"); CancelParsingPatches(); return; } continue; } else { // Cemuhook requires that user defined symbols start with _ but we are more lenient and allow them to start with letters too // the downside is that there is some ambiguity and parsing gets a little bit more complex // check for <symbolName> = pattern StringTokenParser bakParser; const char* symbolStr; sint32 symbolLen; parser.storeParserState(&bakParser); if (parser.parseSymbolName(symbolStr, symbolLen) && parser.matchWordI("=")) { // matches pattern: <symbolName> = ... parser.skipWhitespaces(); parser.trimWhitespaces(); const char* expressionStr = parser.getCurrentPtr(); sint32 expressionLen = parser.getCurrentLen(); // create entry for symbol value assignment currentGroup->list_patches.emplace_back(new PatchEntryCemuhookSymbolValue(lineNumber, symbolStr, symbolLen, expressionStr, expressionLen)); continue; } else { LogPatchesSyntaxError(lineNumber, fmt::format("Invalid syntax")); CancelParsingPatches(); return; } } } if (currentGroup) AddPatchGroup(currentGroup); } static inline uint32 INVALID_ORIGIN = 0xFFFFFFFF; bool GraphicPack2::ParseCemuPatchesTxtInternal(MemStreamReader& patchesStream) { sint32 lineNumber = 0; PatchGroup* currentGroup = nullptr; struct { void reset() { currentOrigin = INVALID_ORIGIN; codeCaveOrigin = 0; } void setOrigin(uint32 origin) { currentOrigin = origin; } void setOriginCodeCave() { currentOrigin = codeCaveOrigin; } bool isValidOrigin() { return currentOrigin != INVALID_ORIGIN; } void incrementOrigin(uint32 size) { currentOrigin += size; if (currentOrigin <= 32 * 1024 * 1024) codeCaveOrigin = std::max(codeCaveOrigin, currentOrigin); } uint32 currentOrigin{}; uint32 codeCaveOrigin{}; }originInfo; // labels dont get emitted immediately, instead they are assigned a VA after the next alignment zone std::vector<PatchEntryLabel*> scheduledLabels; // this is to prevent code like this from putting alignment bytes after the label. (The label 'sticks' to the data after it) // .byte 123 // Label: // BLR auto flushLabels = [&]() { // flush remaining labels for (auto& itr : scheduledLabels) { itr->setAssignedVA(originInfo.currentOrigin); currentGroup->list_patches.emplace_back(itr); } scheduledLabels.clear(); }; while (true) { size_t lineLength; auto lineStr = patchesStream.readLine(); lineNumber++; if (patchesStream.hasError()) break; // trim comment lineLength = GetLengthWithoutComment(lineStr.data(), lineStr.size()); StringTokenParser parser(lineStr.data(), (sint32)lineLength); // skip whitespaces at the beginning parser.skipWhitespaces(); // parse line if (parser.isEndOfString()) continue; if (parser.compareCharacter(0, '[')) { // group parser.skipCharacters(1); // find end of group name const char* groupNameStr = parser.getCurrentPtr(); sint32 groupNameLength = parser.skipToCharacter(']'); if (groupNameLength < 0) { LogPatchesSyntaxError(lineNumber, "Expected ']'"); CancelParsingPatches(); return false; } parser.skipCharacters(1); // skip the ']' parser.skipWhitespaces(); if (!parser.isEndOfString()) { LogPatchesSyntaxError(lineNumber, "Unexpected characters after ']'"); CancelParsingPatches(); return false; } // begin new group if (currentGroup) { flushLabels(); AddPatchGroup(currentGroup); } currentGroup = new PatchGroup(this, groupNameStr, groupNameLength); // reset origin tracking originInfo.reset(); continue; } else if (parser.matchWordI("moduleMatches")) { if (currentGroup == nullptr) { LogPatchesSyntaxError(lineNumber, "Specified 'ModuleMatches' outside of a group"); CancelParsingPatches(); return false; } if (parser.matchWordI("=") == false) { LogPatchesSyntaxError(lineNumber, "Expected '=' after ModuleMatches"); CancelParsingPatches(); return false; } // read the checksums while (true) { if (parser.matchWordI("rpx")) { currentGroup->m_isRpxOnlyTarget = true; break; } uint32 checksum = 0; if (parser.parseU32(checksum) == false) { LogPatchesSyntaxError(lineNumber, "Invalid value for ModuleMatches"); CancelParsingPatches(); return false; } currentGroup->list_moduleMatches.emplace_back(checksum); if (parser.matchWordI(",") == false) break; } parser.skipWhitespaces(); if (!parser.isEndOfString()) { LogPatchesSyntaxError(lineNumber, "Unexpected character"); CancelParsingPatches(); return false; } continue; } // if a line starts with <hex_address> = then it temporarily overwrites the origin for the current line uint32 overwriteOrigin = INVALID_ORIGIN; if (parser.compareCharacter(0, '0') && parser.compareCharacterI(1, 'x')) { uint32 patchedAddress; if (!parser.parseU32(patchedAddress)) { LogPatchesSyntaxError(lineNumber, "Malformed address"); CancelParsingPatches(); return false; } if (parser.matchWordI("=") == false) { LogPatchesSyntaxError(lineNumber, "Expected '=' after address"); CancelParsingPatches(); return false; } parser.skipWhitespaces(); parser.trimWhitespaces(); overwriteOrigin = patchedAddress; } // check for known directives if (parser.matchWordI(".origin")) { // .origin = <origin> directive if (overwriteOrigin != INVALID_ORIGIN) { LogPatchesSyntaxError(lineNumber, fmt::format(".origin directive must appear alone without <address> = prefix.")); CancelParsingPatches(); return false; } if (!parser.matchWordI("=")) { LogPatchesSyntaxError(lineNumber, fmt::format("Missing '=' after .origin")); CancelParsingPatches(); return false; } // parse origin uint32 originAddress; if (parser.matchWordI("codecave")) { // keyword codecave means we set the origin to the end of the current known codecave size originInfo.setOriginCodeCave(); } else if(parser.parseU32(originAddress)) { // hex address originInfo.setOrigin(originAddress); } else { LogPatchesSyntaxError(lineNumber, fmt::format("\'.origin =\' must be followed by the keyword codecave or a valid address")); CancelParsingPatches(); return false; } continue; } else if (parser.matchWordI(".callback")) { if (parser.matchWordI("entry")) { const char* symbolStr; sint32 symbolLen; if (parser.parseSymbolName(symbolStr, symbolLen)) { currentGroup->list_callbacks.push_back(std::make_pair(std::string(symbolStr, static_cast<size_t>(symbolLen)), GPCallbackType::Entry)); continue; } else { LogPatchesSyntaxError(lineNumber, "'.callback' must reference a symbol after the type"); CancelParsingPatches(); return false; } } else { LogPatchesSyntaxError(lineNumber, "Unrecognized type for '.callback'"); CancelParsingPatches(); return false; } } // next we attempt to parse symbol assignment // symbols can be labels or variables. The type is determined by what comes after the symbol name // <symbolName> = <expression> defines a variable // <symbolName>: defines a label StringTokenParser bakParser; const char* symbolStr; sint32 symbolLen; parser.storeParserState(&bakParser); // check for pattern <symbolName>: if (parser.parseSymbolName(symbolStr, symbolLen) && parser.matchWordI(":")) { // label parser.skipWhitespaces(); if (!parser.isEndOfString()) { LogPatchesSyntaxError(lineNumber, fmt::format("Unexpected characters after label")); CancelParsingPatches(); return false; } uint32 labelAddress; if (overwriteOrigin != INVALID_ORIGIN) labelAddress = overwriteOrigin; else { if (!originInfo.isValidOrigin()) { LogPatchesSyntaxError(lineNumber, fmt::format("Defined label has no address assigned or there is no active .origin")); CancelParsingPatches(); return false; } labelAddress = originInfo.currentOrigin; } if (overwriteOrigin == INVALID_ORIGIN) { // if label is part of code flow, delay emitting it until the next data instruction // this is so we can avoid generating alignment padding, whose size is unknown in advance, between labels and data instructions scheduledLabels.emplace_back(new PatchEntryLabel(lineNumber, symbolStr, symbolLen)); } else { PatchEntryLabel* patchLabel = new PatchEntryLabel(lineNumber, symbolStr, symbolLen); patchLabel->setAssignedVA(labelAddress); currentGroup->list_patches.emplace_back(patchLabel); } continue; } parser.restoreParserState(&bakParser); // check for pattern <symbolName> = if (parser.parseSymbolName(symbolStr, symbolLen) && parser.matchWordI("=")) { // variable definition parser.skipWhitespaces(); parser.trimWhitespaces(); const char* expressionStr = parser.getCurrentPtr(); sint32 expressionLen = parser.getCurrentLen(); // create entry for symbol/variable value assignment currentGroup->list_patches.emplace_back(new PatchEntryVariableValue(lineNumber, symbolStr, symbolLen, PATCHVARTYPE::UINT, expressionStr, expressionLen)); continue; } // if all patterns mismatch then we assume it's an assembly instruction parser.restoreParserState(&bakParser); std::string instrText(parser.getCurrentPtr(), parser.getCurrentLen()); PPCAssemblerInOut ctx{}; ctx.forceNoAlignment = overwriteOrigin != INVALID_ORIGIN; // dont auto-align when a fixed address is assigned if (overwriteOrigin != INVALID_ORIGIN) ctx.virtualAddress = overwriteOrigin; else if(originInfo.isValidOrigin()) ctx.virtualAddress = originInfo.currentOrigin; else { LogPatchesSyntaxError(lineNumber, fmt::format("Trying to assemble line but no address specified. (Declare .origin or prefix line with <address> = )")); CancelParsingPatches(); return false; } if (!ppcAssembler_assembleSingleInstruction(instrText.c_str(), &ctx)) { LogPatchesSyntaxError(lineNumber, fmt::format("Error in assembler: {}", ctx.errorMsg)); CancelParsingPatches(); return false; } cemu_assert_debug(ctx.alignmentRequirement != 0); if (overwriteOrigin == INVALID_ORIGIN) { originInfo.incrementOrigin((sint32)ctx.alignmentPaddingSize); // alignment padding originInfo.incrementOrigin((sint32)ctx.outputData.size()); // instruction size } // flush labels for (auto& itr : scheduledLabels) { itr->setAssignedVA(ctx.virtualAddressAligned); currentGroup->list_patches.emplace_back(itr); } scheduledLabels.clear(); // append instruction currentGroup->list_patches.emplace_back(new PatchEntryInstruction(lineNumber, ctx.virtualAddressAligned, { ctx.outputData.data(), ctx.outputData.size() }, ctx.list_relocs)); } flushLabels(); if (currentGroup) AddPatchGroup(currentGroup); return true; } ================================================ FILE: src/Cafe/GraphicPack/GraphicPackError.h ================================================ #pragma once class PatchErrorHandler { public: enum class STAGE { PARSER, APPLY, }; void setCurrentGraphicPack(class GraphicPack2* gp) { m_gp = gp; } void setStage(STAGE s) { m_stage = s; } void printError(class PatchGroup* patchGroup, sint32 lineNumber, std::string_view errorMsg); void showStageErrorMessageBox(); bool hasError() const { return m_anyErrorTriggered; }; class GraphicPack2* m_gp{}; bool m_anyErrorTriggered{}; STAGE m_stage{ STAGE::PARSER }; std::vector<std::string> errorMessages; // if patch logging is enabled also remember error msgs for the message box }; ================================================ FILE: src/Cafe/HW/ACR/ACR.cpp ================================================ #include "Cafe/HW/MMU/MMU.h" #include "Cafe/HW/Common/HwReg.h" namespace HW_ACR { struct { HWREG::ACR_VI_ADDR viAddr; HWREG::ACR_VI_CTRL viCtrl; }g_acr; /* Is this some kind of VI emulation interface? Pattern seen in Twilight Princess HD: - If Hollywood hardware then read/write old 16bit GC VI register directly - Otherwise these steps are performed: VICONTROL |= 1 VIADDR = registerIndex VIDATA = data VICONTROL &= ~1 All the register accesses here are 32bit */ /* 0x0D00021C | Accesses VI register currently selected by VIADDR */ HWREG::ACR_VI_DATA ACR_VIDATA_R32(PAddr addr) { cemuLog_logDebug(LogType::Force, "ACR_VIDATA read with selected reg {:08x}", g_acr.viAddr.get_ADDR()); return HWREG::ACR_VI_DATA(); } void ACR_VIDATA_W32(PAddr addr, HWREG::ACR_VI_DATA newValue) { cemuLog_logDebug(LogType::Force, "ACR_VIDATA write {:08x} with selected reg {:08x}", newValue.get_DATA(), g_acr.viAddr.get_ADDR()); } /* 0x0D000224 | Controls the selected VI register? */ HWREG::ACR_VI_ADDR ACR_VIADDR_R32(PAddr addr) { return g_acr.viAddr; } void ACR_VIADDR_W32(PAddr addr, HWREG::ACR_VI_ADDR newValue) { g_acr.viAddr = newValue; } /* 0x0D000228 | Some kind of VI interface control? */ HWREG::ACR_VI_CTRL ACR_VICONTROL_R32(PAddr addr) { return g_acr.viCtrl; } void ACR_VICONTROL_W32(PAddr addr, HWREG::ACR_VI_CTRL newValue) { g_acr.viCtrl = newValue; } RunAtCemuBoot _initACR([]() { MMU::RegisterMMIO_32<HWREG::ACR_VI_DATA, ACR_VIDATA_R32, ACR_VIDATA_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x21C); MMU::RegisterMMIO_32<HWREG::ACR_VI_ADDR, ACR_VIADDR_R32, ACR_VIADDR_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x224); MMU::RegisterMMIO_32<HWREG::ACR_VI_CTRL, ACR_VICONTROL_R32, ACR_VICONTROL_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x228); // init }); } ================================================ FILE: src/Cafe/HW/AI/AI.cpp ================================================ #include "Cafe/HW/MMU/MMU.h" namespace HW_AI { void AI_STATUS_W16(uint32 addr, uint16 value) { } RunAtCemuBoot _init([]() { //using MMIOFuncWrite16 = void (*)(uint32 addr, uint16 value); //using MMIOFuncWrite32 = void (*)(uint32 addr, uint32 value); //void RegisterMMIO_W16(MMIOFuncWrite16 ptr); }); } ================================================ FILE: src/Cafe/HW/AI/AI.h ================================================ ================================================ FILE: src/Cafe/HW/Common/HwReg.h ================================================ #pragma once namespace HWREG { #define REGISTER_FULL_FIELD(__regname) \ auto& set_##__regname(uint32 newValue) \ { \ v = newValue; \ return *this; \ } \ uint32 get_##__regname() const \ { \ return v; \ } #define REGISTER_BITFIELD(__regname, __bitIndex, __bitWidth) \ auto& set_##__regname(uint32 newValue) \ { \ cemu_assert_debug(newValue < (1u << (__bitWidth))); \ v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ v |= (newValue << (__bitIndex)); \ return *this; \ } \ uint32 get_##__regname() const \ { \ return (v >> (__bitIndex))&((1u << (__bitWidth)) - 1u); \ } #define REGISTER_BITFIELD_SIGNED(__regname, __bitIndex, __bitWidth) \ auto& set_##__regname(sint32 newValue) \ { \ cemu_assert_debug(newValue < (1 << ((__bitWidth)-1))); \ cemu_assert_debug(newValue >= -(1 << ((__bitWidth)-1))); \ v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ v |= (((uint32)newValue & ((1u << (__bitWidth)) - 1u)) << (__bitIndex)); \ return *this; \ } \ sint32 get_##__regname() const \ { \ sint32 r = (v >> (__bitIndex))&((1u << (__bitWidth)) - 1u); \ r = (r << (32 - (__bitWidth))); \ r = (r >> (32 - (__bitWidth))); \ return r; \ } #define REGISTER_BITFIELD_BOOL(__regname, __bitIndex) \ auto& set_##__regname(bool newValue) \ { \ if(newValue) \ v |= (1u << (__bitIndex)); \ else \ v &= ~(1u << (__bitIndex)); \ return *this; \ } \ bool get_##__regname() const \ { \ return (v&(1u << (__bitIndex))) != 0; \ } #define REGISTER_BITFIELD_TYPED(__regname, __bitIndex, __bitWidth, __typename) \ auto& set_##__regname(__typename newValue) \ { \ cemu_assert_debug(static_cast<uint32>(newValue) < (1u << (__bitWidth))); \ v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ v |= (static_cast<uint32>(newValue) << (__bitIndex)); \ return *this; \ } \ __typename get_##__regname() const \ { \ return static_cast<__typename>((v >> (__bitIndex))&((1u << (__bitWidth)) - 1u)); \ } #define REGISTER_BITFIELD_FLOAT(__regname) \ auto& set_##__regname(float newValue) \ { \ *(float*)&v = newValue; \ return *this; \ } \ float get_##__regname() const \ { \ return *(float*)&v; \ } class HWREG { public: uint32 getRawValue() const { return v; } uint32 getRawValueBE() const { return _swapEndianU32(v); } void setFromRaw(uint32 regValue) { v = regValue; } protected: uint32 v{}; }; /* ACR */ struct ACR_VI_DATA : HWREG // 0x0D00021C - official name unknown { REGISTER_FULL_FIELD(DATA); }; struct ACR_VI_ADDR : HWREG // 0x0D000224 - official name unknown { REGISTER_FULL_FIELD(ADDR); }; struct ACR_VI_CTRL : HWREG // 0x0D000228 - official name unknown { REGISTER_BITFIELD_BOOL(HAS_OWNERSHIP, 0); // exact purpose not understood // other fields unknown }; /* SI */ struct SICOUTBUF : HWREG // 0x6400/0x640C/0x6418/0x6424 { REGISTER_BITFIELD(OUTPUT1, 0, 8); REGISTER_BITFIELD(OUTPUT0, 8, 8); REGISTER_BITFIELD(CMD, 16, 8); }; struct SICINBUFH : HWREG // 0x6404/0x6410/0x641C/0x6428 { REGISTER_BITFIELD(INPUT3, 0, 8); REGISTER_BITFIELD(INPUT2, 8, 8); REGISTER_BITFIELD(INPUT1, 16, 8); REGISTER_BITFIELD(INPUT0, 24, 6); REGISTER_BITFIELD(ERRLATCH, 30, 1); REGISTER_BITFIELD(ERRSTAT, 31, 1); }; struct SICINBUFL : HWREG // 0x6408/0x6414/0x6420/0x642C { REGISTER_BITFIELD(INPUT4, 0, 8); REGISTER_BITFIELD(INPUT5, 8, 8); REGISTER_BITFIELD(INPUT6, 16, 8); REGISTER_BITFIELD(INPUT7, 24, 8); }; struct SIPOLL : HWREG // 0x6430 { REGISTER_BITFIELD(X, 16, 10); REGISTER_BITFIELD(Y, 8, 8); REGISTER_BITFIELD(EN, 4, 4); REGISTER_BITFIELD(VBCPY, 0, 4); }; struct SICOMCSR : HWREG // 0x6434 { REGISTER_BITFIELD(TCINT, 31, 1); REGISTER_BITFIELD(TCINTMASK, 30, 1); REGISTER_BITFIELD(COMERR, 29, 1); REGISTER_BITFIELD(RDSTINT, 28, 1); REGISTER_BITFIELD(RDSTINTMSK, 27, 1); REGISTER_BITFIELD(UKN_CHANNEL_NUM_MAYBE, 25, 2); REGISTER_BITFIELD(CHANNELENABLE, 24, 1); REGISTER_BITFIELD(OUTLNGTH, 16, 7); REGISTER_BITFIELD(INLNGTH, 8, 7); REGISTER_BITFIELD(COMMAND_ENABLE, 7, 1); REGISTER_BITFIELD(CALLBACK_ENABLE, 6, 1); REGISTER_BITFIELD(CHANNEL, 1, 2); REGISTER_BITFIELD(TRANSFER_START, 0, 1); }; struct SISR : HWREG // 0x6438 { REGISTER_BITFIELD(WR, 31, 1); // joy-channel 0 REGISTER_BITFIELD(RDST0, 29, 1); REGISTER_BITFIELD(WRST0, 28, 1); REGISTER_BITFIELD(NOREP0, 27, 1); REGISTER_BITFIELD(COLL0, 26, 1); REGISTER_BITFIELD(OVRUN0, 25, 1); REGISTER_BITFIELD(UNRUN0, 24, 1); // joy-channel 1 REGISTER_BITFIELD(RDST1, 21, 1); REGISTER_BITFIELD(WRST1, 20, 1); REGISTER_BITFIELD(NOREP1, 19, 1); REGISTER_BITFIELD(COLL1, 18, 1); REGISTER_BITFIELD(OVRUN1, 17, 1); REGISTER_BITFIELD(UNRUN1, 16, 1); // joy-channel 2 REGISTER_BITFIELD(RDST2, 13, 1); REGISTER_BITFIELD(WRST2, 12, 1); REGISTER_BITFIELD(NOREP2, 11, 1); REGISTER_BITFIELD(COLL2, 10, 1); REGISTER_BITFIELD(OVRUN2, 9, 1); REGISTER_BITFIELD(UNRUN2, 8, 1); // joy-channel 3 REGISTER_BITFIELD(RDST3, 5, 1); REGISTER_BITFIELD(WRST3, 4, 1); REGISTER_BITFIELD(NOREP3, 3, 1); REGISTER_BITFIELD(COLL3, 2, 1); REGISTER_BITFIELD(OVRUN3, 1, 1); REGISTER_BITFIELD(UNRUN3, 0, 1); }; } ================================================ FILE: src/Cafe/HW/Espresso/Const.h ================================================ #pragma once namespace Espresso { constexpr inline int CORE_COUNT = 3; constexpr inline uint64 CORE_CLOCK = 1243125000; constexpr inline uint64 BUS_CLOCK = 248625000; constexpr inline uint64 TIMER_CLOCK = BUS_CLOCK / 4; constexpr inline uint32 MEM_PAGE_SIZE = 0x20000; }; ================================================ FILE: src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.cpp ================================================ #include "Common/precompiled.h" #include "DebugSymbolStorage.h" FSpinlock DebugSymbolStorage::s_lock; std::unordered_map<MPTR, DEBUG_SYMBOL_TYPE> DebugSymbolStorage::s_typeStorage; ================================================ FILE: src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h ================================================ #pragma once #include "util/helpers/fspinlock.h" enum class DEBUG_SYMBOL_TYPE { UNDEFINED, CODE, // big-endian types U64, U32, U16, U8, S64, S32, S16, S8, FLOAT, DOUBLE, }; class DebugSymbolStorage { public: static void StoreDataType(MPTR address, DEBUG_SYMBOL_TYPE type) { s_lock.lock(); s_typeStorage[address] = type; s_lock.unlock(); } static DEBUG_SYMBOL_TYPE GetDataType(MPTR address) { s_lock.lock(); auto itr = s_typeStorage.find(address); if (itr == s_typeStorage.end()) { s_lock.unlock(); return DEBUG_SYMBOL_TYPE::UNDEFINED; } DEBUG_SYMBOL_TYPE t = itr->second; s_lock.unlock(); return t; } static void ClearRange(MPTR address, uint32 length) { if (length == 0) return; s_lock.lock(); for (;;) { auto itr = s_typeStorage.find(address); if (itr != s_typeStorage.end()) s_typeStorage.erase(itr); if (length <= 4) break; address += 4; length -= 4; } s_lock.unlock(); } private: static FSpinlock s_lock; static std::unordered_map<MPTR, DEBUG_SYMBOL_TYPE> s_typeStorage; }; ================================================ FILE: src/Cafe/HW/Espresso/Debugger/Debugger.cpp ================================================ #include "Common/precompiled.h" #include "Debugger.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cemu/PPCAssembler/ppcAssembler.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cemu/ExpressionParser/ExpressionParser.h" #include "Cafe/OS/libs/coreinit/coreinit.h" #include "OS/RPL/rpl.h" #include "util/helpers/helpers.h" #if BOOST_OS_WINDOWS #include <Windows.h> #endif DebuggerDispatcher g_debuggerDispatcher; debuggerState_t debuggerState{ }; DebuggerBreakpoint* debugger_getFirstBP(uint32 address) { for (auto& it : debuggerState.breakpoints) { if (it->address == address) return it; } return nullptr; } DebuggerBreakpoint* debugger_getFirstBP(uint32 address, uint8 bpType) { for (auto& it : debuggerState.breakpoints) { if (it->address == address) { DebuggerBreakpoint* bpItr = it; while (bpItr) { if (bpItr->bpType == bpType) return bpItr; bpItr = bpItr->next; } return nullptr; } } return nullptr; } bool debuggerBPChain_hasType(DebuggerBreakpoint* bp, uint8 bpType) { while (bp) { if (bp->bpType == bpType) return true; bp = bp->next; } return false; } void debuggerBPChain_add(uint32 address, DebuggerBreakpoint* bp) { bp->next = nullptr; DebuggerBreakpoint* existingBP = debugger_getFirstBP(address); if (existingBP) { while (existingBP->next) existingBP = existingBP->next; existingBP->next = bp; return; } // no breakpoint chain exists for this address debuggerState.breakpoints.push_back(bp); } uint32 debugger_getAddressOriginalOpcode(uint32 address) { auto bpItr = debugger_getFirstBP(address); while (bpItr) { if (bpItr->isExecuteBP()) return bpItr->originalOpcodeValue; bpItr = bpItr->next; } return memory_readU32(address); } void debugger_updateMemoryU32(uint32 address, uint32 newValue) { bool memChanged = false; if (newValue != memory_readU32(address)) memChanged = true; memory_writeU32(address, newValue); if(memChanged) PPCRecompiler_invalidateRange(address, address + 4); } void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore) { auto bpItr = debugger_getFirstBP(address); bool hasBP = false; uint32 originalOpcodeValue; while (bpItr) { if (bpItr->isExecuteBP()) { if (bpItr->enabled && forceRestore == false) { // write TW instruction to memory debugger_updateMemoryU32(address, DEBUGGER_BP_T_DEBUGGER_TW); return; } else { originalOpcodeValue = bpItr->originalOpcodeValue; hasBP = true; } } bpItr = bpItr->next; } if (hasBP) { // restore instruction debugger_updateMemoryU32(address, originalOpcodeValue); } } void debugger_createCodeBreakpoint(uint32 address, uint8 bpType) { // check if breakpoint already exists auto existingBP = debugger_getFirstBP(address); if (existingBP && debuggerBPChain_hasType(existingBP, bpType)) return; // breakpoint already exists // get original opcode at address uint32 originalOpcode = debugger_getAddressOriginalOpcode(address); // init breakpoint object DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, bpType, true); debuggerBPChain_add(address, bp); debugger_updateExecutionBreakpoint(address); } namespace coreinit { std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads(); } void debugger_updateMemoryBreakpoint(DebuggerBreakpoint* bp) { std::vector<std::thread::native_handle_type> schedulerThreadHandles = coreinit::OSGetSchedulerThreads(); #if BOOST_OS_WINDOWS debuggerState.activeMemoryBreakpoint = bp; for (auto& hThreadNH : schedulerThreadHandles) { HANDLE hThread = (HANDLE)hThreadNH; CONTEXT ctx{}; ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; SuspendThread(hThread); GetThreadContext(hThread, &ctx); if (debuggerState.activeMemoryBreakpoint) { ctx.Dr0 = (DWORD64)memory_getPointerFromVirtualOffset(bp->address); ctx.Dr1 = (DWORD64)memory_getPointerFromVirtualOffset(bp->address); // breakpoint 0 SetBits(ctx.Dr7, 0, 1, 1); // breakpoint #0 enabled: true SetBits(ctx.Dr7, 16, 2, 1); // breakpoint #0 condition: 1 (write) SetBits(ctx.Dr7, 18, 2, 3); // breakpoint #0 length: 3 (4 bytes) // breakpoint 1 SetBits(ctx.Dr7, 2, 1, 1); // breakpoint #1 enabled: true SetBits(ctx.Dr7, 20, 2, 3); // breakpoint #1 condition: 3 (read & write) SetBits(ctx.Dr7, 22, 2, 3); // breakpoint #1 length: 3 (4 bytes) } else { // breakpoint 0 SetBits(ctx.Dr7, 0, 1, 0); // breakpoint #0 enabled: false SetBits(ctx.Dr7, 16, 2, 0); // breakpoint #0 condition: 1 (write) SetBits(ctx.Dr7, 18, 2, 0); // breakpoint #0 length: 3 (4 bytes) // breakpoint 1 SetBits(ctx.Dr7, 2, 1, 0); // breakpoint #1 enabled: false SetBits(ctx.Dr7, 20, 2, 0); // breakpoint #1 condition: 3 (read & write) SetBits(ctx.Dr7, 22, 2, 0); // breakpoint #1 length: 3 (4 bytes) } SetThreadContext(hThread, &ctx); ResumeThread(hThread); } #else cemuLog_log(LogType::Force, "Debugger breakpoints are not supported"); #endif } void debugger_handleSingleStepException(uint64 dr6) { bool triggeredDR0 = GetBits(dr6, 0, 1); // write bool triggeredDR1 = GetBits(dr6, 1, 1); // read and write bool catchBP = false; if (triggeredDR0 && triggeredDR1) { // write (and read) access if (debuggerState.activeMemoryBreakpoint && debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_WRITE) catchBP = true; } else { // read access if (debuggerState.activeMemoryBreakpoint && debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_READ) catchBP = true; } if (catchBP) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if (debuggerState.logOnlyMemoryBreakpoints) { float memValueF = memory_readFloat(debuggerState.activeMemoryBreakpoint->address); uint32 memValue = memory_readU32(debuggerState.activeMemoryBreakpoint->address); cemuLog_log(LogType::Force, "[Debugger] 0x{:08X} was read/written! New Value: 0x{:08X} (float {}) IP: {:08X} LR: {:08X}", debuggerState.activeMemoryBreakpoint->address, memValue, memValueF, hCPU->instructionPointer, hCPU->spr.LR ); if (cemuLog_advancedPPCLoggingEnabled()) DebugLogStackTrace(coreinit::OSGetCurrentThread(), hCPU->gpr[1]); } else { debugger_createCodeBreakpoint(hCPU->instructionPointer + 4, DEBUGGER_BP_T_ONE_SHOT); } } } void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite) { // init breakpoint object uint8 bpType; if (onRead && onWrite) assert_dbg(); else if (onRead) bpType = DEBUGGER_BP_T_MEMORY_READ; else bpType = DEBUGGER_BP_T_MEMORY_WRITE; DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, 0xFFFFFFFF, bpType, true); debuggerBPChain_add(address, bp); // disable any already existing memory breakpoint if (debuggerState.activeMemoryBreakpoint) { debuggerState.activeMemoryBreakpoint->enabled = false; debuggerState.activeMemoryBreakpoint = nullptr; } // activate new breakpoint debugger_updateMemoryBreakpoint(bp); } void debugger_handleEntryBreakpoint(uint32 address) { if (!debuggerState.breakOnEntry) return; debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); } void debugger_deleteBreakpoint(DebuggerBreakpoint* bp) { for (auto& it : debuggerState.breakpoints) { if (it->address == bp->address) { // for execution breakpoints make sure the instruction is restored if (bp->isExecuteBP()) { bp->enabled = false; debugger_updateExecutionBreakpoint(bp->address); } // remove if (it == bp) { // remove first in list debuggerState.breakpoints.erase(std::remove(debuggerState.breakpoints.begin(), debuggerState.breakpoints.end(), bp), debuggerState.breakpoints.end()); DebuggerBreakpoint* nextBP = bp->next; if (nextBP) debuggerState.breakpoints.push_back(nextBP); } else { // remove from list DebuggerBreakpoint* bpItr = it; while (bpItr->next != bp) { bpItr = bpItr->next; } cemu_assert_debug(bpItr->next != bp); bpItr->next = bp->next; } delete bp; return; } } } void debugger_toggleExecuteBreakpoint(uint32 address) { auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_NORMAL); if (existingBP) { // delete existing breakpoint debugger_deleteBreakpoint(existingBP); } else { // create new execution breakpoint debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); } } void debugger_toggleLoggingBreakpoint(uint32 address) { auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_LOGGING); if (existingBP) { // delete existing breakpoint debugger_deleteBreakpoint(existingBP); } else { // create new logging breakpoint debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING); } } void debugger_forceBreak() { debuggerState.debugSession.shouldBreak = true; } bool debugger_isTrapped() { return debuggerState.debugSession.isTrapped; } void debugger_resume() { // if there is a breakpoint on the current instruction then do a single 'step into' to skip it debuggerState.debugSession.run = true; } void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp) { DebuggerBreakpoint* bpItr = debugger_getFirstBP(address); while (bpItr) { if (bpItr == bp) { if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_LOGGING) { bp->enabled = state; debugger_updateExecutionBreakpoint(address); g_debuggerDispatcher.UpdateViewThreadsafe(); } else if (bpItr->isMemBP()) { // disable other memory breakpoints for (auto& it : debuggerState.breakpoints) { DebuggerBreakpoint* bpItr2 = it; while (bpItr2) { if (bpItr2->isMemBP() && bpItr2 != bp) { bpItr2->enabled = false; } bpItr2 = bpItr2->next; } } bpItr->enabled = state; if (state) debugger_updateMemoryBreakpoint(bpItr); else debugger_updateMemoryBreakpoint(nullptr); g_debuggerDispatcher.UpdateViewThreadsafe(); } return; } bpItr = bpItr->next; } } void debugger_createPatch(uint32 address, std::span<uint8> patchData) { DebuggerPatch* patch = new DebuggerPatch(); patch->address = address; patch->length = patchData.size(); patch->data.resize(4); patch->origData.resize(4); memcpy(&patch->data.front(), patchData.data(), patchData.size()); memcpy(&patch->origData.front(), memory_getPointerFromVirtualOffset(address), patchData.size()); // get original data from breakpoints if ((address & 3) != 0) cemu_assert_debug(false); for (sint32 i = 0; i < patchData.size() / 4; i++) { DebuggerBreakpoint* bpItr = debugger_getFirstBP(address); while (bpItr) { if (bpItr->isExecuteBP()) { *(uint32*)(&patch->origData.front() + i * 4) = _swapEndianU32(bpItr->originalOpcodeValue); } bpItr = bpItr->next; } } // merge with existing patches if the ranges touch for(sint32 i=0; i<debuggerState.patches.size(); i++) { auto& patchItr = debuggerState.patches[i]; if (address + patchData.size() >= patchItr->address && address <= patchItr->address + patchItr->length) { uint32 newAddress = std::min(patch->address, patchItr->address); uint32 newEndAddress = std::max(patch->address + patch->length, patchItr->address + patchItr->length); uint32 newLength = newEndAddress - newAddress; DebuggerPatch* newPatch = new DebuggerPatch(); newPatch->address = newAddress; newPatch->length = newLength; newPatch->data.resize(newLength); newPatch->origData.resize(newLength); memcpy(&newPatch->data.front() + (address - newAddress), &patch->data.front(), patch->length); memcpy(&newPatch->data.front() + (patchItr->address - newAddress), &patchItr->data.front(), patchItr->length); memcpy(&newPatch->origData.front() + (address - newAddress), &patch->origData.front(), patch->length); memcpy(&newPatch->origData.front() + (patchItr->address - newAddress), &patchItr->origData.front(), patchItr->length); delete patch; patch = newPatch; delete patchItr; // remove currently iterated patch debuggerState.patches.erase(debuggerState.patches.begin()+i); i--; } } debuggerState.patches.push_back(patch); // apply patch (if breakpoints exist then update those instead of actual data) if ((address & 3) != 0) cemu_assert_debug(false); if ((patchData.size() & 3) != 0) cemu_assert_debug(false); for (sint32 i = 0; i < patchData.size() / 4; i++) { DebuggerBreakpoint* bpItr = debugger_getFirstBP(address); bool hasActiveExecuteBP = false; while (bpItr) { if (bpItr->isExecuteBP()) { bpItr->originalOpcodeValue = *(uint32be*)(patchData.data() + i * 4); if (bpItr->enabled) hasActiveExecuteBP = true; } bpItr = bpItr->next; } if (hasActiveExecuteBP == false) { memcpy(memory_getPointerFromVirtualOffset(address + i * 4), patchData.data() + i * 4, 4); PPCRecompiler_invalidateRange(address, address + 4); } } } bool debugger_hasPatch(uint32 address) { for (auto& patch : debuggerState.patches) { if (address + 4 > patch->address && address < patch->address + patch->length) return true; } return false; } void debugger_removePatch(uint32 address) { for (sint32 i = 0; i < debuggerState.patches.size(); i++) { auto& patch = debuggerState.patches[i]; if (address < patch->address || address >= (patch->address + patch->length)) continue; MPTR startAddress = patch->address; MPTR endAddress = patch->address + patch->length; // remove any breakpoints overlapping with the patch for (auto& bp : debuggerState.breakpoints) { if (bp->address + 4 > startAddress && bp->address < endAddress) { bp->enabled = false; debugger_updateExecutionBreakpoint(bp->address); } } // restore original data memcpy(MEMPTR<void>(startAddress).GetPtr(), patch->origData.data(), patch->length); PPCRecompiler_invalidateRange(startAddress, endAddress); // remove patch delete patch; debuggerState.patches.erase(debuggerState.patches.begin() + i); return; } } void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true) { bool isRecEnabled = ppcRecompilerEnabled; ppcRecompilerEnabled = false; uint32 initialIP = debuggerState.debugSession.instructionPointer; debugger_updateExecutionBreakpoint(initialIP, true); PPCInterpreterSlim_executeInstruction(hCPU); debugger_updateExecutionBreakpoint(initialIP); debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; if(updateDebuggerWindow) g_debuggerDispatcher.MoveIP(); ppcRecompilerEnabled = isRecEnabled; } bool debugger_stepOver(PPCInterpreter_t* hCPU) { bool isRecEnabled = ppcRecompilerEnabled; ppcRecompilerEnabled = false; // disassemble current instruction PPCDisassembledInstruction disasmInstr = { 0 }; uint32 initialIP = debuggerState.debugSession.instructionPointer; debugger_updateExecutionBreakpoint(initialIP, true); ppcAssembler_disassemble(initialIP, memory_readU32(initialIP), &disasmInstr); if (disasmInstr.ppcAsmCode != PPCASM_OP_BL && disasmInstr.ppcAsmCode != PPCASM_OP_BCTRL) { // nothing to skip, use step-into debugger_stepInto(hCPU); debugger_updateExecutionBreakpoint(initialIP); g_debuggerDispatcher.MoveIP(); ppcRecompilerEnabled = isRecEnabled; return false; } // create one-shot breakpoint at next instruction debugger_createCodeBreakpoint(initialIP + 4, DEBUGGER_BP_T_ONE_SHOT); // step over current instruction (to avoid breakpoint) debugger_stepInto(hCPU); g_debuggerDispatcher.MoveIP(); // restore breakpoints debugger_updateExecutionBreakpoint(initialIP); // run ppcRecompilerEnabled = isRecEnabled; return true; } void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU) { memcpy(debuggerState.debugSession.ppcSnapshot.gpr, hCPU->gpr, sizeof(uint32) * 32); memcpy(debuggerState.debugSession.ppcSnapshot.fpr, hCPU->fpr, sizeof(FPR_t) * 32); debuggerState.debugSession.ppcSnapshot.spr_lr = hCPU->spr.LR; for (uint32 i = 0; i < 32; i++) debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i]; } void debugger_enterTW(PPCInterpreter_t* hCPU) { // Currently, we don't support multiple threads inside the debugger. Spin loop a thread if we already paused for another breakpoint hit. while (debuggerState.debugSession.isTrapped) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } // handle logging points DebuggerBreakpoint* bp = debugger_getFirstBP(hCPU->instructionPointer); bool shouldBreak = debuggerBPChain_hasType(bp, DEBUGGER_BP_T_NORMAL) || debuggerBPChain_hasType(bp, DEBUGGER_BP_T_ONE_SHOT); while (bp) { if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled) { std::string comment = !bp->comment.empty() ? boost::nowide::narrow(bp->comment) : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address); auto replacePlaceholders = [&](const std::string& prefix, const auto& formatFunc) { size_t pos = 0; while ((pos = comment.find(prefix, pos)) != std::string::npos) { size_t endPos = comment.find('}', pos); if (endPos == std::string::npos) break; try { if (int regNum = ConvertString<int>(comment.substr(pos + prefix.length(), endPos - pos - prefix.length())); regNum >= 0 && regNum < 32) { std::string replacement = formatFunc(regNum); comment.replace(pos, endPos - pos + 1, replacement); pos += replacement.length(); } else { pos = endPos + 1; } } catch (...) { pos = endPos + 1; } } }; // Replace integer register placeholders {rX} replacePlaceholders("{r", [&](int regNum) { return fmt::format("0x{:08X}", hCPU->gpr[regNum]); }); // Replace floating point register placeholders {fX} replacePlaceholders("{f", [&](int regNum) { return fmt::format("{}", hCPU->fpr[regNum].fpr); }); std::string logName = "Breakpoint '" + comment + "'"; std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : ""); cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext); if (cemuLog_advancedPPCLoggingEnabled()) DebugLogStackTrace(coreinit::OSGetCurrentThread(), hCPU->gpr[1]); break; } bp = bp->next; } // return early if it's only a non-pausing logging breakpoint to prevent a modified debugger state and GUI updates if (!shouldBreak) { uint32 backupIP = debuggerState.debugSession.instructionPointer; debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; debugger_stepInto(hCPU, false); PPCInterpreterSlim_executeInstruction(hCPU); debuggerState.debugSession.instructionPointer = backupIP; return; } // handle breakpoints debuggerState.debugSession.isTrapped = true; debuggerState.debugSession.debuggedThreadMPTR = MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(); debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; debuggerState.debugSession.hCPU = hCPU; debugger_createPPCStateSnapshot(hCPU); // remove one-shot breakpoint if it exists DebuggerBreakpoint* singleshotBP = debugger_getFirstBP(debuggerState.debugSession.instructionPointer, DEBUGGER_BP_T_ONE_SHOT); if (singleshotBP) debugger_deleteBreakpoint(singleshotBP); g_debuggerDispatcher.NotifyDebugBreakpointHit(); g_debuggerDispatcher.UpdateViewThreadsafe(); // reset step control debuggerState.debugSession.stepInto = false; debuggerState.debugSession.stepOver = false; debuggerState.debugSession.run = false; while (debuggerState.debugSession.isTrapped) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); // check for step commands if (debuggerState.debugSession.stepOver) { if (debugger_stepOver(hCPU)) { debugger_createPPCStateSnapshot(hCPU); break; // if true is returned, continue with execution } debugger_createPPCStateSnapshot(hCPU); g_debuggerDispatcher.UpdateViewThreadsafe(); debuggerState.debugSession.stepOver = false; } if (debuggerState.debugSession.stepInto) { debugger_stepInto(hCPU); debugger_createPPCStateSnapshot(hCPU); g_debuggerDispatcher.UpdateViewThreadsafe(); debuggerState.debugSession.stepInto = false; continue; } if (debuggerState.debugSession.run) { debugger_createPPCStateSnapshot(hCPU); debugger_stepInto(hCPU, false); PPCInterpreterSlim_executeInstruction(hCPU); debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; debuggerState.debugSession.run = false; break; } } debuggerState.debugSession.isTrapped = false; debuggerState.debugSession.hCPU = nullptr; g_debuggerDispatcher.UpdateViewThreadsafe(); g_debuggerDispatcher.NotifyRun(); } void debugger_shouldBreak(PPCInterpreter_t* hCPU) { if(debuggerState.debugSession.shouldBreak // exclude emulator trampoline area && (hCPU->instructionPointer < MEMORY_CODE_TRAMPOLINE_AREA_ADDR || hCPU->instructionPointer > MEMORY_CODE_TRAMPOLINE_AREA_ADDR + MEMORY_CODE_TRAMPOLINE_AREA_SIZE)) { debuggerState.debugSession.shouldBreak = false; const uint32 address = (uint32)hCPU->instructionPointer; assert_dbg(); //debugger_createBreakpoint(address, DEBUGGER_BP_TYPE_ONE_SHOT); } } void debugger_addParserSymbols(class ExpressionParser& ep) { const auto module_count = RPLLoader_GetModuleCount(); const auto module_list = RPLLoader_GetModuleList(); std::vector<double> module_tmp(module_count); for (int i = 0; i < module_count; i++) { const auto module = module_list[i]; if (module) { module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR(); ep.AddConstant(module->moduleName2, module_tmp[i]); } } for (sint32 i = 0; i < 32; i++) ep.AddConstant(fmt::format("r{}", i), debuggerState.debugSession.ppcSnapshot.gpr[i]); } ================================================ FILE: src/Cafe/HW/Espresso/Debugger/Debugger.h ================================================ #pragma once #include <set> #include "Cafe/HW/Espresso/PPCState.h" #define DEBUGGER_BP_T_NORMAL 0 // normal breakpoint #define DEBUGGER_BP_T_ONE_SHOT 1 // normal breakpoint, deletes itself after trigger (used for stepping) #define DEBUGGER_BP_T_MEMORY_READ 2 // memory breakpoint #define DEBUGGER_BP_T_MEMORY_WRITE 3 // memory breakpoint #define DEBUGGER_BP_T_LOGGING 4 // logging breakpoint, prints the breakpoint comment and stack trace whenever hit #define DEBUGGER_BP_T_GDBSTUB 1 // breakpoint created by GDBStub #define DEBUGGER_BP_T_DEBUGGER 2 // breakpoint created by Cemu's debugger #define DEBUGGER_BP_T_GDBSTUB_TW 0x7C010008 #define DEBUGGER_BP_T_DEBUGGER_TW 0x7C020008 class DebuggerCallbacks { public: virtual void UpdateViewThreadsafe() {} virtual void NotifyDebugBreakpointHit() {} virtual void NotifyRun() {} virtual void MoveIP() {} virtual void NotifyModuleLoaded(void* module) {} virtual void NotifyModuleUnloaded(void* module) {} virtual void NotifyGraphicPacksModified() {} virtual ~DebuggerCallbacks() = default; }; class DebuggerDispatcher { private: static inline class DefaultDebuggerCallbacks : public DebuggerCallbacks { } s_defaultDebuggerCallbacks; DebuggerCallbacks* m_callbacks = &s_defaultDebuggerCallbacks; public: void SetDebuggerCallbacks(DebuggerCallbacks* debuggerCallbacks) { cemu_assert_debug(m_callbacks == &s_defaultDebuggerCallbacks); m_callbacks = debuggerCallbacks; } void ClearDebuggerCallbacks() { cemu_assert_debug(m_callbacks != &s_defaultDebuggerCallbacks); m_callbacks = &s_defaultDebuggerCallbacks; } void UpdateViewThreadsafe() { m_callbacks->UpdateViewThreadsafe(); } void NotifyDebugBreakpointHit() { m_callbacks->NotifyDebugBreakpointHit(); } void NotifyRun() { m_callbacks->NotifyRun(); } void MoveIP() { m_callbacks->MoveIP(); } void NotifyModuleLoaded(void* module) { m_callbacks->NotifyModuleLoaded(module); } void NotifyModuleUnloaded(void* module) { m_callbacks->NotifyModuleUnloaded(module); } void NotifyGraphicPacksModified() { m_callbacks->NotifyGraphicPacksModified(); } } extern g_debuggerDispatcher; struct DebuggerBreakpoint { uint32 address; uint32 originalOpcodeValue; mutable uint8 bpType; mutable bool enabled; mutable std::wstring comment; mutable uint8 dbType = DEBUGGER_BP_T_DEBUGGER; DebuggerBreakpoint(uint32 address, uint32 originalOpcode, uint8 bpType = 0, bool enabled = true, std::wstring comment = std::wstring()) :address(address), originalOpcodeValue(originalOpcode), bpType(bpType), enabled(enabled), comment(std::move(comment)) { next = nullptr; } bool operator<(const DebuggerBreakpoint& rhs) const { return address < rhs.address; } bool operator==(const DebuggerBreakpoint& rhs) const { return address == rhs.address; } bool isExecuteBP() const { return bpType == DEBUGGER_BP_T_NORMAL || bpType == DEBUGGER_BP_T_LOGGING || bpType == DEBUGGER_BP_T_ONE_SHOT; } bool isMemBP() const { return bpType == DEBUGGER_BP_T_MEMORY_READ || bpType == DEBUGGER_BP_T_MEMORY_WRITE; } DebuggerBreakpoint* next; }; struct DebuggerPatch { uint32 address; sint32 length; std::vector<uint8> data; std::vector<uint8> origData; }; struct PPCSnapshot { uint32 gpr[32]; FPR_t fpr[32]; uint8 cr[32]; uint32 spr_lr; }; typedef struct { bool breakOnEntry; bool logOnlyMemoryBreakpoints; // breakpoints std::vector<DebuggerBreakpoint*> breakpoints; std::vector<DebuggerPatch*> patches; DebuggerBreakpoint* activeMemoryBreakpoint; // debugging state struct { volatile bool shouldBreak; // debugger window requests a break asap volatile bool isTrapped; // if set, breakpoint is active and stepping through the code is possible uint32 debuggedThreadMPTR; volatile uint32 instructionPointer; PPCInterpreter_t* hCPU; // step control volatile bool stepOver; volatile bool stepInto; volatile bool run; // snapshot of PPC state PPCSnapshot ppcSnapshot; }debugSession; }debuggerState_t; extern debuggerState_t debuggerState; // new API DebuggerBreakpoint* debugger_getFirstBP(uint32 address); void debugger_createCodeBreakpoint(uint32 address, uint8 bpType); void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint void debugger_toggleLoggingBreakpoint(uint32 address); // create/remove logging breakpoint void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp); void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite); void debugger_handleEntryBreakpoint(uint32 address); void debugger_deleteBreakpoint(DebuggerBreakpoint* bp); void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = false); void debugger_createPatch(uint32 address, std::span<uint8> patchData); bool debugger_hasPatch(uint32 address); void debugger_removePatch(uint32 address); void debugger_forceBreak(); // force breakpoint at the next possible instruction bool debugger_isTrapped(); void debugger_resume(); void debugger_enterTW(PPCInterpreter_t* hCPU); void debugger_shouldBreak(PPCInterpreter_t* hCPU); void debugger_addParserSymbols(class ExpressionParser& ep); ================================================ FILE: src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp ================================================ #include "GDBBreakpoints.h" #include "Debugger.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #if defined(ARCH_X86_64) && BOOST_OS_LINUX #include <sys/ptrace.h> #include <sys/wait.h> #include <sys/user.h> DRType _GetDR(pid_t tid, int drIndex) { size_t drOffset = offsetof(struct user, u_debugreg) + drIndex * sizeof(user::u_debugreg[0]); long v; v = ptrace(PTRACE_PEEKUSER, tid, drOffset, nullptr); if (v == -1) perror("ptrace(PTRACE_PEEKUSER)"); return (DRType)v; } void _SetDR(pid_t tid, int drIndex, DRType newValue) { size_t drOffset = offsetof(struct user, u_debugreg) + drIndex * sizeof(user::u_debugreg[0]); long rc = ptrace(PTRACE_POKEUSER, tid, drOffset, newValue); if (rc == -1) perror("ptrace(PTRACE_POKEUSER)"); } DRType _ReadDR6() { pid_t tid = gettid(); // linux doesn't let us attach to the current thread / threads in the current thread group // we have to create a child process which then modifies the debug registers and quits pid_t child = fork(); if (child == -1) { perror("fork"); return 0; } if (child == 0) { if (ptrace(PTRACE_ATTACH, tid, nullptr, nullptr)) { perror("attach"); _exit(0); } waitpid(tid, NULL, 0); uint64_t dr6 = _GetDR(tid, 6); if (ptrace(PTRACE_DETACH, tid, nullptr, nullptr)) perror("detach"); // since the status code only uses the lower 8 bits, we have to discard the rest of DR6 // this should be fine though, since the lower 4 bits of DR6 contain all the bp conditions _exit(dr6 & 0xff); } // wait for child process int wstatus; waitpid(child, &wstatus, 0); return (DRType)WEXITSTATUS(wstatus); } #endif GDBServer::ExecutionBreakpoint::ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason) : m_address(address), m_removedAfterInterrupt(false), m_reason(std::move(reason)) { if (type == BreakpointType::BP_SINGLE) { this->m_pauseThreads = true; this->m_restoreAfterInterrupt = false; this->m_deleteAfterAnyInterrupt = false; this->m_pauseOnNextInterrupt = false; this->m_visible = visible; } else if (type == BreakpointType::BP_PERSISTENT) { this->m_pauseThreads = true; this->m_restoreAfterInterrupt = true; this->m_deleteAfterAnyInterrupt = false; this->m_pauseOnNextInterrupt = false; this->m_visible = visible; } else if (type == BreakpointType::BP_RESTORE_POINT) { this->m_pauseThreads = false; this->m_restoreAfterInterrupt = false; this->m_deleteAfterAnyInterrupt = false; this->m_pauseOnNextInterrupt = false; this->m_visible = false; } else if (type == BreakpointType::BP_STEP_POINT) { this->m_pauseThreads = false; this->m_restoreAfterInterrupt = false; this->m_deleteAfterAnyInterrupt = true; this->m_pauseOnNextInterrupt = true; this->m_visible = false; } this->m_origOpCode = memory_readU32(address); memory_writeU32(address, DEBUGGER_BP_T_GDBSTUB_TW); PPCRecompiler_invalidateRange(address, address + 4); } GDBServer::ExecutionBreakpoint::~ExecutionBreakpoint() { memory_writeU32(this->m_address, this->m_origOpCode); PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); } uint32 GDBServer::ExecutionBreakpoint::GetVisibleOpCode() const { if (this->m_visible) return memory_readU32(this->m_address); else return this->m_origOpCode; } void GDBServer::ExecutionBreakpoint::RemoveTemporarily() { memory_writeU32(this->m_address, this->m_origOpCode); PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); this->m_restoreAfterInterrupt = true; } void GDBServer::ExecutionBreakpoint::Restore() { memory_writeU32(this->m_address, DEBUGGER_BP_T_GDBSTUB_TW); PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); this->m_restoreAfterInterrupt = false; } namespace coreinit { #if BOOST_OS_LINUX std::vector<pid_t>& OSGetSchedulerThreadIds(); #endif std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads(); } GDBServer::AccessBreakpoint::AccessBreakpoint(MPTR address, AccessPointType type) : m_address(address), m_type(type) { #if defined(ARCH_X86_64) && BOOST_OS_WINDOWS for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) { HANDLE hThread = (HANDLE)hThreadNH; CONTEXT ctx{}; ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; SuspendThread(hThread); GetThreadContext(hThread, &ctx); // use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already ctx.Dr2 = (DWORD64)memory_getPointerFromVirtualOffset(address); ctx.Dr3 = (DWORD64)memory_getPointerFromVirtualOffset(address); // breakpoint 2 SetBits(ctx.Dr7, 4, 1, 1); // breakpoint #3 enabled: true SetBits(ctx.Dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write) SetBits(ctx.Dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes) // breakpoint 3 SetBits(ctx.Dr7, 6, 1, 1); // breakpoint #4 enabled: true SetBits(ctx.Dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write) SetBits(ctx.Dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes) SetThreadContext(hThread, &ctx); ResumeThread(hThread); } #elif defined(ARCH_X86_64) && BOOST_OS_LINUX // linux doesn't let us attach to threads which are in the same thread group as our current thread // we have to create a child process which then modifies the debug registers and quits pid_t child = fork(); if (child == -1) { perror("fork"); return; } if (child == 0) { for (pid_t tid : coreinit::OSGetSchedulerThreadIds()) { long rc = ptrace(PTRACE_ATTACH, tid, nullptr, nullptr); if (rc == -1) perror("ptrace(PTRACE_ATTACH)"); waitpid(tid, nullptr, 0); DRType dr7 = _GetDR(tid, 7); // use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already DRType dr2 = (uint64)memory_getPointerFromVirtualOffset(address); DRType dr3 = (uint64)memory_getPointerFromVirtualOffset(address); // breakpoint 2 SetBits(dr7, 4, 1, 1); // breakpoint #3 enabled: true SetBits(dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write) SetBits(dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes) // breakpoint 3 SetBits(dr7, 6, 1, 1); // breakpoint #4 enabled: true SetBits(dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write) SetBits(dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes) _SetDR(tid, 2, dr2); _SetDR(tid, 3, dr3); _SetDR(tid, 7, dr7); rc = ptrace(PTRACE_DETACH, tid, nullptr, nullptr); if (rc == -1) perror("ptrace(PTRACE_DETACH)"); } // exit child process _exit(0); } // wait for child process waitpid(child, nullptr, 0); #else cemuLog_log(LogType::Force, "Debugger read/write breakpoints are not supported on non-x86 CPUs yet."); #endif } GDBServer::AccessBreakpoint::~AccessBreakpoint() { #if defined(ARCH_X86_64) && BOOST_OS_WINDOWS for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) { HANDLE hThread = (HANDLE)hThreadNH; CONTEXT ctx{}; ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; SuspendThread(hThread); GetThreadContext(hThread, &ctx); // reset BP 2/3 to zero ctx.Dr2 = (DWORD64)0; ctx.Dr3 = (DWORD64)0; // breakpoint 2 SetBits(ctx.Dr7, 4, 1, 0); SetBits(ctx.Dr7, 24, 2, 0); SetBits(ctx.Dr7, 26, 2, 0); // breakpoint 3 SetBits(ctx.Dr7, 6, 1, 0); SetBits(ctx.Dr7, 28, 2, 0); SetBits(ctx.Dr7, 30, 2, 0); SetThreadContext(hThread, &ctx); ResumeThread(hThread); } #elif defined(ARCH_X86_64) && BOOST_OS_LINUX // linux doesn't let us attach to threads which are in the same thread group as our current thread // we have to create a child process which then modifies the debug registers and quits pid_t child = fork(); if (child == -1) { perror("fork"); return; } if (child == 0) { for (pid_t tid : coreinit::OSGetSchedulerThreadIds()) { long rc = ptrace(PTRACE_ATTACH, tid, nullptr, nullptr); if (rc == -1) perror("ptrace(PTRACE_ATTACH)"); waitpid(tid, nullptr, 0); DRType dr7 = _GetDR(tid, 7); // reset BP 2/3 to zero DRType dr2 = 0; DRType dr3 = 0; // breakpoint 2 SetBits(dr7, 4, 1, 0); SetBits(dr7, 24, 2, 0); SetBits(dr7, 26, 2, 0); // breakpoint 3 SetBits(dr7, 6, 1, 0); SetBits(dr7, 28, 2, 0); SetBits(dr7, 30, 2, 0); _SetDR(tid, 2, dr2); _SetDR(tid, 3, dr3); _SetDR(tid, 7, dr7); rc = ptrace(PTRACE_DETACH, tid, nullptr, nullptr); if (rc == -1) perror("ptrace(PTRACE_DETACH)"); } // exit child process _exit(0); } // wait for child process waitpid(child, nullptr, 0); #endif } ================================================ FILE: src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.h ================================================ #pragma once #include "GDBStub.h" #include <utility> #if defined(ARCH_X86_64) && BOOST_OS_LINUX #include <sys/types.h> // helpers for accessing debug register typedef unsigned long DRType; DRType _GetDR(pid_t tid, int drIndex); void _SetDR(pid_t tid, int drIndex, DRType newValue); DRType _ReadDR6(); #endif enum class BreakpointType { BP_SINGLE, BP_PERSISTENT, BP_RESTORE_POINT, BP_STEP_POINT }; class GDBServer::ExecutionBreakpoint { public: ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason); ~ExecutionBreakpoint(); [[nodiscard]] uint32 GetVisibleOpCode() const; [[nodiscard]] bool ShouldBreakThreads() const { return this->m_pauseThreads; }; [[nodiscard]] bool ShouldBreakThreadsOnNextInterrupt() { bool shouldPause = this->m_pauseOnNextInterrupt; this->m_pauseOnNextInterrupt = false; return shouldPause; }; [[nodiscard]] bool IsPersistent() const { return this->m_restoreAfterInterrupt; }; [[nodiscard]] bool IsSkipBreakpoint() const { return this->m_deleteAfterAnyInterrupt; }; [[nodiscard]] bool IsRemoved() const { return this->m_removedAfterInterrupt; }; [[nodiscard]] std::string GetReason() const { return m_reason; }; void RemoveTemporarily(); void Restore(); void PauseOnNextInterrupt() { this->m_pauseOnNextInterrupt = true; }; void WriteNewOpCode(uint32 newOpCode) { this->m_origOpCode = newOpCode; }; private: const MPTR m_address; std::string m_reason; uint32 m_origOpCode; bool m_visible; bool m_pauseThreads; // type bool m_pauseOnNextInterrupt; bool m_restoreAfterInterrupt; bool m_deleteAfterAnyInterrupt; bool m_removedAfterInterrupt; }; enum class AccessPointType { BP_WRITE = 2, BP_READ = 3, BP_BOTH = 4 }; class GDBServer::AccessBreakpoint { public: AccessBreakpoint(MPTR address, AccessPointType type); ~AccessBreakpoint(); MPTR GetAddress() const { return m_address; }; AccessPointType GetType() const { return m_type; }; private: const MPTR m_address; const AccessPointType m_type; }; ================================================ FILE: src/Cafe/HW/Espresso/Debugger/GDBStub.cpp ================================================ #include "GDBStub.h" #include "Debugger.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "GDBBreakpoints.h" #include "util/helpers/helpers.h" #include "util/ThreadPool/ThreadPool.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/EspressoISA.h" #include "Common/socket.h" #define GET_THREAD_ID(threadPtr) memory_getVirtualOffsetFromPointer(threadPtr) #define GET_THREAD_BY_ID(threadId) (OSThread_t*)memory_getPointerFromPhysicalOffset(threadId) static std::vector<MPTR> findNextInstruction(MPTR currAddress, uint32 lr, uint32 ctr) { using namespace Espresso; uint32 nextInstr = memory_readU32(currAddress); if (GetPrimaryOpcode(nextInstr) == PrimaryOpcode::B) { uint32 LI; bool AA, LK; decodeOp_B(nextInstr, LI, AA, LK); if (!AA) LI += currAddress; return {LI}; } if (GetPrimaryOpcode(nextInstr) == PrimaryOpcode::BC) { uint32 BD, BI; BOField BO{}; bool AA, LK; decodeOp_BC(nextInstr, BD, BO, BI, AA, LK); if (!LK) BD += currAddress; return {currAddress + 4, BD}; } if (GetPrimaryOpcode(nextInstr) == PrimaryOpcode::GROUP_19 && GetGroup19Opcode(nextInstr) == Opcode19::BCLR) { return {currAddress + 4, lr}; } if (GetPrimaryOpcode(nextInstr) == PrimaryOpcode::GROUP_19 && GetGroup19Opcode(nextInstr) == Opcode19::BCCTR) { return {currAddress + 4, ctr}; } return {currAddress + 4}; } template<typename F> static void selectThread(sint64 selectorId, F&& action_for_thread) { __OSLockScheduler(); cemu_assert_debug(activeThreadCount != 0); if (selectorId == -1) { for (sint32 i = 0; i < activeThreadCount; i++) { action_for_thread(GET_THREAD_BY_ID(activeThread[i])); } } else if (selectorId == 0) { // Use first thread if attempted to be stopped // todo: would this work better if it used main? action_for_thread(coreinit::OSGetDefaultThread(1)); } else if (selectorId > 0) { for (sint32 i = 0; i < activeThreadCount; i++) { auto* thread = GET_THREAD_BY_ID(activeThread[i]); if (GET_THREAD_ID(thread) == selectorId) { action_for_thread(thread); break; } } } __OSUnlockScheduler(); } template<typename F> static void selectAndBreakThread(sint64 selectorId, F&& action_for_thread) { __OSLockScheduler(); cemu_assert_debug(activeThreadCount != 0); std::vector<OSThread_t*> pausedThreads; if (selectorId == -1) { for (sint32 i = 0; i < activeThreadCount; i++) { coreinit::__OSSuspendThreadNolock(GET_THREAD_BY_ID(activeThread[i])); pausedThreads.emplace_back(GET_THREAD_BY_ID(activeThread[i])); } } else if (selectorId == 0) { // Use first thread if attempted to be stopped OSThread_t* thread = GET_THREAD_BY_ID(activeThread[0]); for (sint32 i = 0; i < activeThreadCount; i++) { if (GET_THREAD_ID(GET_THREAD_BY_ID(activeThread[i])) < GET_THREAD_ID(thread)) { thread = GET_THREAD_BY_ID(activeThread[i]); } } coreinit::__OSSuspendThreadNolock(thread); pausedThreads.emplace_back(thread); } else if (selectorId > 0) { for (sint32 i = 0; i < activeThreadCount; i++) { auto* thread = GET_THREAD_BY_ID(activeThread[i]); if (GET_THREAD_ID(thread) == selectorId) { coreinit::__OSSuspendThreadNolock(thread); pausedThreads.emplace_back(thread); break; } } } __OSUnlockScheduler(); for (OSThread_t* thread : pausedThreads) { while (coreinit::OSIsThreadRunning(thread)) std::this_thread::sleep_for(std::chrono::milliseconds(50)); action_for_thread(thread); } } static void selectAndResumeThread(sint64 selectorId) { __OSLockScheduler(); cemu_assert_debug(activeThreadCount != 0); if (selectorId == -1) { for (sint32 i = 0; i < activeThreadCount; i++) { coreinit::__OSResumeThreadInternal(GET_THREAD_BY_ID(activeThread[i]), 4); } } else if (selectorId == 0) { // Use first thread if attempted to be stopped coreinit::__OSResumeThreadInternal(coreinit::OSGetDefaultThread(1), 1); } else if (selectorId > 0) { for (sint32 i = 0; i < activeThreadCount; i++) { auto* thread = GET_THREAD_BY_ID(activeThread[i]); if (GET_THREAD_ID(thread) == selectorId) { coreinit::__OSResumeThreadInternal(thread, 1); break; } } } __OSUnlockScheduler(); } static void waitForBrokenThreads(std::unique_ptr<GDBServer::CommandContext> context, std::string_view reason) { // This should pause all threads except trapped thread // It should however wait for the trapped thread // The trapped thread should be paused by the trap word instruction handler (aka the running thread) std::vector<OSThread_t*> threadsList; __OSLockScheduler(); for (sint32 i = 0; i < activeThreadCount; i++) { threadsList.emplace_back(GET_THREAD_BY_ID(activeThread[i])); } __OSUnlockScheduler(); for (OSThread_t* thread : threadsList) { while (coreinit::OSIsThreadRunning(thread)) std::this_thread::sleep_for(std::chrono::milliseconds(50)); } context->QueueResponse(reason); } static void breakThreads(sint64 trappedThread) { __OSLockScheduler(); cemu_assert_debug(activeThreadCount != 0); // First, break other threads OSThread_t* mainThread = nullptr; for (sint32 i = 0; i < activeThreadCount; i++) { if (GET_THREAD_ID(GET_THREAD_BY_ID(activeThread[i])) == trappedThread) { mainThread = GET_THREAD_BY_ID(activeThread[i]); } else { coreinit::__OSSuspendThreadNolock(GET_THREAD_BY_ID(activeThread[i])); } } // Second, break trapped thread itself which should also pause execution of this handler // This will temporarily lift the scheduler lock until it's resumed from its suspension coreinit::__OSSuspendThreadNolock(mainThread); __OSUnlockScheduler(); } std::unique_ptr<GDBServer> g_gdbstub; GDBServer::GDBServer(uint16 port) : m_port(port) { #if BOOST_OS_WINDOWS WSADATA wsa; WSAStartup(MAKEWORD(2, 2), &wsa); #endif } GDBServer::~GDBServer() { if (m_client_socket != INVALID_SOCKET) { // close socket from other thread to forcefully stop accept() call closesocket(m_client_socket); m_client_socket = INVALID_SOCKET; } if (m_server_socket != INVALID_SOCKET) { closesocket(m_server_socket); } #if BOOST_OS_WINDOWS WSACleanup(); #endif m_stopRequested = false; m_thread.join(); } bool GDBServer::Initialize() { cemuLog_createLogFile(false); if (m_server_socket = socket(PF_INET, SOCK_STREAM, 0); m_server_socket == SOCKET_ERROR) return false; int reuseEnabled = TRUE; if (setsockopt(m_server_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&reuseEnabled, sizeof(reuseEnabled)) == SOCKET_ERROR) { closesocket(m_server_socket); m_server_socket = INVALID_SOCKET; return false; } int nodelayEnabled = TRUE; if (setsockopt(m_server_socket, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelayEnabled, sizeof(nodelayEnabled)) == SOCKET_ERROR) { closesocket(m_server_socket); m_server_socket = INVALID_SOCKET; return false; } memset(&m_server_addr, 0, sizeof(m_server_addr)); m_server_addr.sin_family = AF_INET; m_server_addr.sin_addr.s_addr = htonl(INADDR_ANY); m_server_addr.sin_port = htons(m_port); if (bind(m_server_socket, (sockaddr*)&m_server_addr, sizeof(m_server_addr)) == SOCKET_ERROR) { closesocket(m_server_socket); m_server_socket = INVALID_SOCKET; return false; } if (listen(m_server_socket, s_maxGDBClients) == SOCKET_ERROR) { closesocket(m_server_socket); m_server_socket = INVALID_SOCKET; return false; } m_thread = std::thread(std::bind(&GDBServer::ThreadFunc, this)); return true; } void GDBServer::ThreadFunc() { SetThreadName("GDBServer"); while (!m_stopRequested) { if (!m_client_connected) { cemuLog_logDebug(LogType::Force, "[GDBStub] Waiting for client to connect on port {}...", m_port); socklen_t client_addr_size = sizeof(m_client_addr); m_client_socket = accept(m_server_socket, (struct sockaddr*)&m_client_addr, &client_addr_size); m_client_connected = m_client_socket != SOCKET_ERROR; } else { auto receiveMessage = [&](char* buffer, const int32_t length) -> bool { if (recv(m_client_socket, buffer, length, 0) != SOCKET_ERROR) return false; return true; }; auto readChar = [&]() -> char { char ret = 0; recv(m_client_socket, &ret, 1, 0); return ret; }; char packetPrefix = readChar(); switch (packetPrefix) { case '+': case '-': break; case '\x03': { cemuLog_logDebug(LogType::Force, "[GDBStub] Received interrupt (pressed CTRL+C?) from client!"); selectAndBreakThread(-1, [](OSThread_t* thread) { }); auto thread_status = fmt::format("T05thread:{:08X};", GET_THREAD_ID(coreinit::OSGetDefaultThread(1))); if (this->m_resumed_context) { this->m_resumed_context->QueueResponse(thread_status); this->m_resumed_context.reset(); } else { auto response_full = fmt::format("+${}#{:02x}", thread_status, CommandContext::CalculateChecksum(thread_status)); send(m_client_socket, response_full.c_str(), (int)response_full.size(), 0); } break; } case '$': { std::string message; uint8 checkedSum = 0; for (uint32_t i = 1;; i++) { char c = readChar(); if (c == '#') break; checkedSum += static_cast<uint8>(c); message.push_back(c); if (i >= s_maxPacketSize) cemuLog_logDebug(LogType::Force, "[GDBStub] Received too big of a buffer: {}", message); } char checkSumStr[2]; receiveMessage(checkSumStr, 2); uint32_t checkSum = std::stoi(std::string(checkSumStr, sizeof(checkSumStr)), nullptr, 16); assert((checkedSum & 0xFF) == checkSum); HandleCommand(message); break; } default: // cemuLog_logDebug(LogType::Force, "[GDBStub] Unknown packet start: {}", packetPrefix); break; } } } if (m_client_socket != INVALID_SOCKET) closesocket(m_client_socket); } void GDBServer::HandleCommand(const std::string& command_str) { auto context = std::make_unique<CommandContext>(this, command_str); if (context->IsValid()) { // cemuLog_logDebug(LogType::Force, "[GDBStub] Extracted Command {}", fmt::join(context->GetArgs(), ",")); } switch (context->GetType()) { // Extended commands case CMDType::QUERY_GET: case CMDType::QUERY_SET: return HandleQuery(context); case CMDType::VCONT: return HandleVCont(context); // Regular commands case CMDType::IS_THREAD_RUNNING: return CMDIsThreadActive(context); case CMDType::SET_ACTIVE_THREAD: return CMDSetActiveThread(context); case CMDType::ACTIVE_THREAD_STATUS: return CMDGetThreadStatus(context); case CMDType::CONTINUE: return CMDContinue(context); case CMDType::ACTIVE_THREAD_STEP: break; case CMDType::REGISTER_READ: return CMDReadRegister(context); case CMDType::REGISTER_SET: return CMDWriteRegister(context); case CMDType::REGISTERS_READ: return CMDReadRegisters(context); case CMDType::REGISTERS_WRITE: return CMDWriteRegisters(context); case CMDType::MEMORY_READ: return CMDReadMemory(context); case CMDType::MEMORY_WRITE: return CMDWriteMemory(context); case CMDType::BREAKPOINT_SET: return CMDInsertBreakpoint(context); case CMDType::BREAKPOINT_REMOVE: return CMDDeleteBreakpoint(context); case CMDType::INVALID: default: return CMDNotFound(context); } CMDNotFound(context); } void GDBServer::HandleQuery(std::unique_ptr<CommandContext>& context) const { if (!context->IsValid()) return context->QueueResponse(RESPONSE_EMPTY); const auto& query_cmd = context->GetArgs()[0]; const auto& query_args = context->GetArgs().begin() + 1; if (query_cmd == "qSupported") { context->QueueResponse(s_supportedFeatures); } else if (query_cmd == "qAttached") { context->QueueResponse("1"); } else if (query_cmd == "qRcmd") { } else if (query_cmd == "qC") { context->QueueResponse("QC"); context->QueueResponse(std::to_string(m_activeThreadContinueSelector)); } else if (query_cmd == "qOffsets") { const auto module_count = RPLLoader_GetModuleCount(); const auto module_list = RPLLoader_GetModuleList(); for (sint32 i = 0; i < module_count; i++) { const RPLModule* rpl = module_list[i]; if (rpl->entrypoint == m_entry_point) { context->QueueResponse(fmt::format("TextSeg={:08X};DataSeg={:08X}", rpl->regionMappingBase_text.GetMPTR(), rpl->regionMappingBase_data)); } } } else if (query_cmd == "qfThreadInfo") { std::vector<std::string> threadIds; selectThread(-1, [&threadIds](OSThread_t* thread) { threadIds.emplace_back(fmt::format("{:08X}", memory_getVirtualOffsetFromPointer(thread))); }); context->QueueResponse(fmt::format("m{}", fmt::join(threadIds, ","))); } else if (query_cmd == "qsThreadInfo") { context->QueueResponse("l"); } else if (query_cmd == "qXfer") { auto& type = query_args[0]; if (type == "features") { auto& annex = query_args[1]; sint64 read_offset = std::stoul(query_args[2], nullptr, 16); sint64 read_length = std::stoul(query_args[3], nullptr, 16); if (annex == "target.xml") { if (read_offset >= GDBTargetXML.size()) context->QueueResponse("l"); else { auto paginated_str = GDBTargetXML.substr(read_offset, read_length); context->QueueResponse((paginated_str.size() == read_length) ? "m" : "l"); context->QueueResponse(paginated_str); } } else cemuLog_logDebug(LogType::Force, "[GDBStub] qXfer:features:read:{} isn't a known feature document", annex); } else if (type == "threads") { sint64 read_offset = std::stoul(query_args[1], nullptr, 16); sint64 read_length = std::stoul(query_args[2], nullptr, 16); std::string threads_res; threads_res += R"(<?xml version="1.0"?>)"; threads_res += "<threads>"; // note: clion seems to default to the first thread std::map<sint64, std::string> threads_list; selectThread(-1, [&threads_list](OSThread_t* thread) { std::string entry; entry += fmt::format(R"(<thread id="{:x}" core="{}")", GET_THREAD_ID(thread), thread->context.upir.value()); if (!thread->threadName.IsNull()) entry += fmt::format(R"( name="{}")", CommandContext::EscapeXMLString(thread->threadName.GetPtr())); // todo: could add a human-readable description of the thread here entry += fmt::format("></thread>"); threads_list.emplace(GET_THREAD_ID(thread), entry); }); for (auto& entry : threads_list) { threads_res += entry.second; } threads_res += "</threads>"; if (read_offset >= threads_res.size()) context->QueueResponse("l"); else { auto paginated_str = threads_res.substr(read_offset, read_length); context->QueueResponse((paginated_str.size() == read_length) ? "m" : "l"); context->QueueResponse(paginated_str); } } else if (type == "libraries") { sint64 read_offset = std::stoul(query_args[1], nullptr, 16); sint64 read_length = std::stoul(query_args[2], nullptr, 16); std::string library_list; library_list += R"(<?xml version="1.0"?>)"; library_list += "<library-list>"; const auto module_count = RPLLoader_GetModuleCount(); const auto module_list = RPLLoader_GetModuleList(); for (sint32 i = 0; i < module_count; i++) { library_list += fmt::format(R"(<library name="{}"><segment address="{:#x}"/></library>)", CommandContext::EscapeXMLString(module_list[i]->moduleName2), module_list[i]->regionMappingBase_text.GetMPTR()); } library_list += "</library-list>"; if (read_offset >= library_list.size()) context->QueueResponse("l"); else { auto paginated_str = library_list.substr(read_offset, read_length); context->QueueResponse((paginated_str.size() == read_length) ? "m" : "l"); context->QueueResponse(paginated_str); } } else { context->QueueResponse(RESPONSE_EMPTY); } } else { context->QueueResponse(RESPONSE_EMPTY); } } void GDBServer::HandleVCont(std::unique_ptr<CommandContext>& context) { if (!context->IsValid()) { cemuLog_logDebug(LogType::Force, "[GDBStub] Received unsupported vCont command: {}", context->GetCommand()); // cemu_assert_unimplemented(); return context->QueueResponse(RESPONSE_EMPTY); } const std::string& vcont_cmd = context->GetArgs()[0]; if (vcont_cmd == "vCont?") return context->QueueResponse("vCont;c;C;s;S"); else if (vcont_cmd != "vCont;") return context->QueueResponse(RESPONSE_EMPTY); m_resumed_context = std::move(context); bool resumedNoThreads = true; for (const auto operation : TokenizeView(m_resumed_context->GetArgs()[1], ';')) { // todo: this might have issues with the signal versions (C/S) // todo: test whether this works with multiple vCont;c:123123;c:123123 std::string_view operationType = operation.substr(0, operation.find(':')); sint64 threadSelector = operationType.size() == operation.size() ? -1 : std::stoll(std::string(operation.substr(operationType.size() + 1)), nullptr, 16); if (operationType == "c" || operationType.starts_with("C")) { selectAndResumeThread(threadSelector); resumedNoThreads = false; } else if (operationType == "s" || operationType.starts_with("S")) { selectThread(threadSelector, [this](OSThread_t* thread) { auto nextInstructions = findNextInstruction(thread->context.srr0, thread->context.lr, thread->context.ctr); for (MPTR nextInstr : nextInstructions) { auto bpIt = m_patchedInstructions.find(nextInstr); if (bpIt == m_patchedInstructions.end()) this->m_patchedInstructions.try_emplace(nextInstr, nextInstr, BreakpointType::BP_STEP_POINT, false, "swbreak:;"); else bpIt->second.PauseOnNextInterrupt(); } }); } } if (resumedNoThreads) { selectAndResumeThread(-1); cemuLog_logDebug(LogType::Force, "[GDBStub] Resumed all threads after skip instructions"); } } void GDBServer::CMDContinue(std::unique_ptr<CommandContext>& context) { m_resumed_context = std::move(context); selectAndResumeThread(m_activeThreadContinueSelector); } void GDBServer::CMDNotFound(std::unique_ptr<CommandContext>& context) { return context->QueueResponse(RESPONSE_EMPTY); } void GDBServer::CMDIsThreadActive(std::unique_ptr<CommandContext>& context) { sint64 threadSelector = std::stoll(context->GetArgs()[1], nullptr, 16); bool foundThread = false; selectThread(threadSelector, [&foundThread](OSThread_t* thread) { foundThread = true; }); if (foundThread) return context->QueueResponse(RESPONSE_OK); else return context->QueueResponse(RESPONSE_ERROR); } void GDBServer::CMDSetActiveThread(std::unique_ptr<CommandContext>& context) { sint64 threadSelector = std::stoll(context->GetArgs()[2], nullptr, 16); if (threadSelector >= 0) { bool foundThread = false; selectThread(threadSelector, [&foundThread](OSThread_t* thread) { foundThread = true; }); if (!foundThread) return context->QueueResponse(RESPONSE_ERROR); } if (context->GetArgs()[1] == "c") m_activeThreadContinueSelector = threadSelector; else m_activeThreadSelector = threadSelector; return context->QueueResponse(RESPONSE_OK); } void GDBServer::CMDGetThreadStatus(std::unique_ptr<CommandContext>& context) { selectThread(0, [&context](OSThread_t* thread) { context->QueueResponse(fmt::format("T05thread:{:08X};", memory_getVirtualOffsetFromPointer(thread))); }); } void GDBServer::CMDReadRegister(std::unique_ptr<CommandContext>& context) const { sint32 reg = std::stoi(context->GetArgs()[1], nullptr, 16); selectThread(m_activeThreadSelector, [reg, &context](OSThread_t* thread) { auto& cpu = thread->context; if (reg >= RegisterID::R0_START && reg <= RegisterID::R31_END) { return context->QueueResponse(fmt::format("{:08X}", CPU_swapEndianU32(cpu.gpr[reg]))); } else if (reg >= RegisterID::F0_START && reg <= RegisterID::F31_END) { return context->QueueResponse(fmt::format("{:016X}", cpu.fp_ps0[reg - RegisterID::F0_START].value())); } else if (reg == RegisterID::FPSCR) { return context->QueueResponse(fmt::format("{:08X}", cpu.fpscr.fpscr.value())); } else { switch (reg) { case RegisterID::PC: return context->QueueResponse(fmt::format("{:08X}", cpu.srr0)); case RegisterID::MSR: return context->QueueResponse("xxxxxxxx"); case RegisterID::CR: return context->QueueResponse(fmt::format("{:08X}", cpu.cr)); case RegisterID::LR: return context->QueueResponse(fmt::format("{:08X}", CPU_swapEndianU32(cpu.lr))); case RegisterID::CTR: return context->QueueResponse(fmt::format("{:08X}", cpu.ctr)); case RegisterID::XER: return context->QueueResponse(fmt::format("{:08X}", cpu.xer)); default: break; } } }); } void GDBServer::CMDWriteRegister(std::unique_ptr<CommandContext>& context) const { sint32 reg = std::stoi(context->GetArgs()[1], nullptr, 16); uint64 value = std::stoll(context->GetArgs()[2], nullptr, 16); selectThread(m_activeThreadSelector, [reg, value, &context](OSThread_t* thread) { auto& cpu = thread->context; if (reg >= RegisterID::R0_START && reg <= RegisterID::R31_END) { cpu.gpr[reg] = CPU_swapEndianU32(value); return context->QueueResponse(RESPONSE_OK); } else if (reg >= RegisterID::F0_START && reg <= RegisterID::F31_END) { // todo: figure out how to properly write to paired single registers cpu.fp_ps0[reg - RegisterID::F0_START] = uint64be{value}; return context->QueueResponse(RESPONSE_OK); } else if (reg == RegisterID::FPSCR) { cpu.fpscr.fpscr = uint32be{(uint32)value}; return context->QueueResponse(RESPONSE_OK); } else { switch (reg) { case RegisterID::PC: cpu.srr0 = value; return context->QueueResponse(RESPONSE_OK); case RegisterID::MSR: return context->QueueResponse(RESPONSE_ERROR); case RegisterID::CR: cpu.cr = value; return context->QueueResponse(RESPONSE_OK); case RegisterID::LR: cpu.lr = CPU_swapEndianU32(value); return context->QueueResponse(RESPONSE_OK); case RegisterID::CTR: cpu.ctr = value; return context->QueueResponse(RESPONSE_OK); case RegisterID::XER: cpu.xer = value; return context->QueueResponse(RESPONSE_OK); default: return context->QueueResponse(RESPONSE_ERROR); } } }); } void GDBServer::CMDReadRegisters(std::unique_ptr<CommandContext>& context) const { selectThread(m_activeThreadSelector, [&context](OSThread_t* thread) { for (uint32& reg : thread->context.gpr) { context->QueueResponse(fmt::format("{:08X}", CPU_swapEndianU32(reg))); } }); } void GDBServer::CMDWriteRegisters(std::unique_ptr<CommandContext>& context) const { selectThread(m_activeThreadSelector, [&context](OSThread_t* thread) { auto& registers = context->GetArgs()[1]; for (uint32 i = 0; i < 32; i++) { thread->context.gpr[i] = CPU_swapEndianU32(std::stoi(registers.substr(i * 2, 2), nullptr, 16)); } }); } void GDBServer::CMDReadMemory(std::unique_ptr<CommandContext>& context) { sint64 addr = std::stoul(context->GetArgs()[1], nullptr, 16); sint64 length = std::stoul(context->GetArgs()[2], nullptr, 16); // todo: handle cross-mmu-range memory requests if (!memory_isAddressRangeAccessible(addr, length)) return context->QueueResponse(RESPONSE_ERROR); std::string memoryRepr; uint8* values = memory_getPointerFromVirtualOffset(addr); for (sint64 i = 0; i < length; i++) { memoryRepr += fmt::format("{:02X}", values[i]); } auto patchesRange = m_patchedInstructions.lower_bound(addr); while (patchesRange != m_patchedInstructions.end() && patchesRange->first < (addr + length)) { auto replStr = fmt::format("{:02X}", patchesRange->second.GetVisibleOpCode()); memoryRepr[(patchesRange->first - addr) * 2] = replStr[0]; memoryRepr[(patchesRange->first - addr) * 2 + 1] = replStr[1]; patchesRange++; } return context->QueueResponse(memoryRepr); } void GDBServer::CMDWriteMemory(std::unique_ptr<CommandContext>& context) { sint64 addr = std::stoul(context->GetArgs()[1], nullptr, 16); sint64 length = std::stoul(context->GetArgs()[2], nullptr, 16); auto source = context->GetArgs()[3]; // todo: handle cross-mmu-range memory requests if (!memory_isAddressRangeAccessible(addr, length)) return context->QueueResponse(RESPONSE_ERROR); uint8* values = memory_getPointerFromVirtualOffset(addr); for (sint64 i = 0; i < length; i++) { uint8 hexValue; const std::from_chars_result result = std::from_chars(source.data() + (i * 2), (source.data() + (i * 2) + 2), hexValue, 16); if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) return context->QueueResponse(RESPONSE_ERROR); if (auto it = m_patchedInstructions.find(addr + i); it != m_patchedInstructions.end()) { uint32 newOpCode = it->second.GetVisibleOpCode(); uint32 byteIndex = 3 - ((addr + i) % 4); // inverted because of big endian, so address 0 is the highest byte newOpCode &= ~(0xFF << (byteIndex * 8)); // mask out the byte newOpCode |= ((uint32)hexValue << (byteIndex * 8)); // set new byte with OR it->second.WriteNewOpCode(newOpCode); } else { values[i] = hexValue; } } return context->QueueResponse(RESPONSE_OK); } void GDBServer::CMDInsertBreakpoint(std::unique_ptr<CommandContext>& context) { auto type = std::stoul(context->GetArgs()[1], nullptr, 16); MPTR addr = static_cast<MPTR>(std::stoul(context->GetArgs()[2], nullptr, 16)); if (type == 0 || type == 1) { auto bp = this->m_patchedInstructions.find(addr); if (bp != this->m_patchedInstructions.end()) this->m_patchedInstructions.erase(bp); this->m_patchedInstructions.try_emplace(addr, addr, BreakpointType::BP_PERSISTENT, type == 0, type == 0 ? "swbreak:;" : "hwbreak:;"); } else if (type == 2 || type == 3 || type == 4) { if (this->m_watch_point) return context->QueueResponse(RESPONSE_ERROR); this->m_watch_point = std::make_unique<AccessBreakpoint>(addr, (AccessPointType)type); } return context->QueueResponse(RESPONSE_OK); } void GDBServer::CMDDeleteBreakpoint(std::unique_ptr<CommandContext>& context) { auto type = std::stoul(context->GetArgs()[1], nullptr, 16); MPTR addr = static_cast<MPTR>(std::stoul(context->GetArgs()[2], nullptr, 16)); if (type == 0 || type == 1) { auto bp = this->m_patchedInstructions.find(addr); if (bp == this->m_patchedInstructions.end() || !bp->second.ShouldBreakThreads()) return context->QueueResponse(RESPONSE_ERROR); else this->m_patchedInstructions.erase(bp); } else if (type == 2 || type == 3 || type == 4) { if (!this->m_watch_point || this->m_watch_point->GetAddress() != addr) return context->QueueResponse(RESPONSE_ERROR); this->m_watch_point.reset(); } return context->QueueResponse(RESPONSE_OK); } // Internal functions for control void GDBServer::HandleTrapInstruction(PPCInterpreter_t* hCPU) { // First, restore any removed breakpoints for (auto& bp : m_patchedInstructions) { if (bp.second.IsRemoved()) bp.second.Restore(); } auto patchedBP = m_patchedInstructions.find(hCPU->instructionPointer); if (patchedBP == m_patchedInstructions.end()) return cemu_assert_suspicious(); // Secondly, delete one-shot breakpoints but also temporarily delete patched instruction to run original instruction OSThread_t* currThread = coreinit::OSGetCurrentThread(); std::string pauseReason = fmt::format("T05thread:{:08X};core:{:02X};{}", GET_THREAD_ID(currThread), PPCInterpreter_getCoreIndex(hCPU), patchedBP->second.GetReason()); bool pauseThreads = patchedBP->second.ShouldBreakThreads() || patchedBP->second.ShouldBreakThreadsOnNextInterrupt(); if (patchedBP->second.IsPersistent()) { // Insert new restore breakpoints at next possible instructions which restores breakpoints but won't pause the CPU std::vector<MPTR> nextInstructions = findNextInstruction(hCPU->instructionPointer, hCPU->spr.LR, hCPU->spr.CTR); for (MPTR nextInstr : nextInstructions) { if (!m_patchedInstructions.contains(nextInstr)) this->m_patchedInstructions.try_emplace(nextInstr, nextInstr, BreakpointType::BP_STEP_POINT, false, ""); } patchedBP->second.RemoveTemporarily(); } else { m_patchedInstructions.erase(patchedBP); } // Thirdly, delete any instructions that were generated by a skip instruction for (auto it = m_patchedInstructions.cbegin(), next_it = it; it != m_patchedInstructions.cend(); it = next_it) { ++next_it; if (it->second.IsSkipBreakpoint()) { m_patchedInstructions.erase(it); } } // Fourthly, the stub can insert breakpoints that are just meant to restore patched instructions, in which case we just want to continue if (pauseThreads) { cemuLog_logDebug(LogType::Force, "[GDBStub] Got trapped by a breakpoint!"); if (m_resumed_context) { // Spin up thread to signal when another GDB stub trap is found ThreadPool::FireAndForget(&waitForBrokenThreads, std::move(m_resumed_context), pauseReason); } breakThreads(GET_THREAD_ID(coreinit::OSGetCurrentThread())); cemuLog_logDebug(LogType::Force, "[GDBStub] Resumed from a breakpoint!"); } } void GDBServer::HandleAccessException(uint64 dr6) { bool triggeredWrite = GetBits(dr6, 2, 1); bool triggeredReadWrite = GetBits(dr6, 3, 1); std::string response; if (m_watch_point->GetType() == AccessPointType::BP_WRITE && triggeredWrite) response = fmt::format("watch:{:08X};", m_watch_point->GetAddress()); else if (m_watch_point->GetType() == AccessPointType::BP_READ && triggeredReadWrite && !triggeredWrite) response = fmt::format("rwatch:{:08X};", m_watch_point->GetAddress()); else if (m_watch_point->GetType() == AccessPointType::BP_BOTH && triggeredReadWrite) response = fmt::format("awatch:{:08X};", m_watch_point->GetAddress()); if (!response.empty()) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); cemuLog_logDebug(LogType::Force, "Received matching breakpoint exception: {}", response); auto nextInstructions = findNextInstruction(hCPU->instructionPointer, hCPU->spr.LR, hCPU->spr.CTR); for (MPTR nextInstr : nextInstructions) { auto bpIt = m_patchedInstructions.find(nextInstr); if (bpIt == m_patchedInstructions.end()) this->m_patchedInstructions.try_emplace(nextInstr, nextInstr, BreakpointType::BP_STEP_POINT, false, response); else bpIt->second.PauseOnNextInterrupt(); } } } void GDBServer::HandleEntryStop(uint32 entryAddress) { this->m_patchedInstructions.try_emplace(entryAddress, entryAddress, BreakpointType::BP_SINGLE, false, ""); m_entry_point = entryAddress; } ================================================ FILE: src/Cafe/HW/Espresso/Debugger/GDBStub.h ================================================ #pragma once #include "Common/precompiled.h" #include "Common/socket.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include <numeric> class GDBServer { public: explicit GDBServer(uint16 port); ~GDBServer(); bool Initialize(); bool IsConnected() { return m_client_connected; } void HandleEntryStop(uint32 entryAddress); void HandleTrapInstruction(PPCInterpreter_t* hCPU); void HandleAccessException(uint64 dr6); enum class CMDType : char { INVALID = '\0', // Extended commands QUERY_GET = 'q', QUERY_SET = 'Q', VCONT = 'v', // Normal commands CONTINUE = 'c', IS_THREAD_RUNNING = 'T', SET_ACTIVE_THREAD = 'H', ACTIVE_THREAD_STATUS = '?', ACTIVE_THREAD_STEP = 's', REGISTER_READ = 'p', REGISTER_SET = 'P', REGISTERS_READ = 'g', REGISTERS_WRITE = 'G', MEMORY_READ = 'm', MEMORY_WRITE = 'M', BREAKPOINT_SET = 'Z', BREAKPOINT_REMOVE = 'z', }; class CommandContext { public: CommandContext(const GDBServer* server, const std::string& command) : m_server(server), m_command(command) { std::smatch matches; std::regex_match(command, matches, m_regex); for (size_t i = 1; i < matches.size(); i++) { auto matchStr = matches[i].str(); if (!matchStr.empty()) m_args.emplace_back(std::move(matchStr)); } // send acknowledgement ahead of response send(m_server->m_client_socket, RESPONSE_ACK.data(), (int)RESPONSE_ACK.size(), 0); }; ~CommandContext() { // cemuLog_logDebug(LogType::Force, "[GDBStub] Received: {}", m_command); // cemuLog_logDebug(LogType::Force, "[GDBStub] Responded: +{}", m_response); auto response_data = EscapeMessage(m_response); auto response_full = fmt::format("${}#{:02x}", response_data, CalculateChecksum(response_data)); send(m_server->m_client_socket, response_full.c_str(), (int)response_full.size(), 0); } CommandContext(const CommandContext&) = delete; [[nodiscard]] const std::string& GetCommand() const { return m_command; }; [[nodiscard]] const std::vector<std::string>& GetArgs() const { return m_args; }; [[nodiscard]] bool IsValid() const { return !m_args.empty(); }; [[nodiscard]] CMDType GetType() const { return static_cast<CMDType>(m_command[0]); }; // Respond Utils static uint8 CalculateChecksum(std::string_view message_data) { return std::accumulate(message_data.begin(), message_data.end(), (uint8)0, std::plus<>()); } static std::string EscapeXMLString(std::string_view xml_data) { std::string escaped; escaped.reserve(xml_data.size()); for (char c : xml_data) { switch (c) { case '<': escaped += "<"; break; case '>': escaped += ">"; break; case '&': escaped += "&"; break; case '"': escaped += """; break; case '\'': escaped += "'"; break; default: escaped += c; break; } } return escaped; } static std::string EscapeMessage(std::string_view message) { std::string escaped; escaped.reserve(message.size()); for (char c : message) { if (c == '#' || c == '$' || c == '}' || c == '*') { escaped.push_back('}'); escaped.push_back((char)(c ^ 0x20)); } else escaped.push_back(c); } return escaped; } void QueueResponse(std::string_view data) { m_response += data; } private: const std::regex m_regex{ R"((?:)" R"((\?))" R"(|(vCont\?))" R"(|(vCont;)([a-zA-Z0-9-+=,\+:;]+))" R"(|(qAttached))" R"(|(qSupported):([a-zA-Z0-9-+=,\+;]+))" R"(|(qTStatus))" R"(|(qC))" R"(|(qXfer):((?:features)|(?:threads)|(?:libraries)):read:([\w\.]*):([0-9a-zA-Z]+),([0-9a-zA-Z]+))" R"(|(qfThreadInfo))" R"(|(qsThreadInfo))" R"(|(T)((?:-1)|(?:[0-9A-Fa-f]+)))" R"(|(D))" // Detach R"(|(H)(c|g)((?:-1)|(?:[0-9A-Fa-f]+)))" // Set active thread for other operations (not c) R"(|(c)([0-9A-Fa-f]+)?)" // (Legacy, supported by vCont) Continue all for active thread R"(|([Zz])([0-4]),([0-9A-Fa-f]+),([0-9]))" // Insert/delete breakpoints R"(|(g))" // Read registers for active thread R"(|(G)([0-9A-Fa-f]+))" // Write registers for active thread R"(|(p)([0-9A-Fa-f]+))" // Read register for active thread R"(|(P)([0-9A-Fa-f]+)=([0-9A-Fa-f]+))" // Write register for active thread R"(|(m)([0-9A-Fa-f]+),([0-9A-Fa-f]+))" // Read memory R"(|(M)([0-9A-Fa-f]+),([0-9A-Fa-f]+):([0-9A-Fa-f]+))" // Write memory // R"(|(X)([0-9A-Fa-f]+),([0-9A-Fa-f]+):([0-9A-Fa-f]+))" // Write memory R"())"}; const GDBServer* m_server; const std::string m_command; std::vector<std::string> m_args; std::string m_response; }; class ExecutionBreakpoint; std::map<MPTR, ExecutionBreakpoint> m_patchedInstructions; class AccessBreakpoint; std::unique_ptr<AccessBreakpoint> m_watch_point; private: static constexpr int s_maxGDBClients = 1; static constexpr std::string_view s_supportedFeatures = "PacketSize=4096;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+;swbreak+;hwbreak+;vContSupported+"; static constexpr size_t s_maxPacketSize = 1024 * 4; const uint16 m_port; enum RegisterID { R0_START = 0, R31_END = R0_START + 31, PC = 64, MSR = 65, CR = 66, LR = 67, CTR = 68, XER = 69, F0_START = 71, F31_END = F0_START + 31, FPSCR = 103 }; static constexpr std::string_view RESPONSE_EMPTY = ""; static constexpr std::string_view RESPONSE_ACK = "+"; static constexpr std::string_view RESPONSE_NACK = "-"; static constexpr std::string_view RESPONSE_OK = "OK"; static constexpr std::string_view RESPONSE_ERROR = "E01"; void ThreadFunc(); std::atomic_bool m_stopRequested; void HandleCommand(const std::string& command_str); void HandleQuery(std::unique_ptr<CommandContext>& context) const; void HandleVCont(std::unique_ptr<CommandContext>& context); // Commands sint64 m_activeThreadSelector = 0; sint64 m_activeThreadContinueSelector = 0; void CMDContinue(std::unique_ptr<CommandContext>& context); void CMDNotFound(std::unique_ptr<CommandContext>& context); void CMDIsThreadActive(std::unique_ptr<CommandContext>& context); void CMDSetActiveThread(std::unique_ptr<CommandContext>& context); void CMDGetThreadStatus(std::unique_ptr<CommandContext>& context); void CMDReadRegister(std::unique_ptr<CommandContext>& context) const; void CMDWriteRegister(std::unique_ptr<CommandContext>& context) const; void CMDReadRegisters(std::unique_ptr<CommandContext>& context) const; void CMDWriteRegisters(std::unique_ptr<CommandContext>& context) const; void CMDReadMemory(std::unique_ptr<CommandContext>& context); void CMDWriteMemory(std::unique_ptr<CommandContext>& context); void CMDInsertBreakpoint(std::unique_ptr<CommandContext>& context); void CMDDeleteBreakpoint(std::unique_ptr<CommandContext>& context); std::thread m_thread; std::atomic_bool m_resume_startup = false; MPTR m_entry_point{}; std::unique_ptr<CommandContext> m_resumed_context; std::atomic_bool m_client_connected; SOCKET m_server_socket = INVALID_SOCKET; sockaddr_in m_server_addr{}; SOCKET m_client_socket = INVALID_SOCKET; sockaddr_in m_client_addr{}; }; static constexpr std::string_view GDBTargetXML = R"(<?xml version="1.0"?> <!DOCTYPE target SYSTEM "gdb-target.dtd"> <target version="1.0"> <architecture>powerpc:common</architecture> <feature name="org.gnu.gdb.power.core"> <reg name="r0" bitsize="32" type="uint32"/> <reg name="r1" bitsize="32" type="uint32"/> <reg name="r2" bitsize="32" type="uint32"/> <reg name="r3" bitsize="32" type="uint32"/> <reg name="r4" bitsize="32" type="uint32"/> <reg name="r5" bitsize="32" type="uint32"/> <reg name="r6" bitsize="32" type="uint32"/> <reg name="r7" bitsize="32" type="uint32"/> <reg name="r8" bitsize="32" type="uint32"/> <reg name="r9" bitsize="32" type="uint32"/> <reg name="r10" bitsize="32" type="uint32"/> <reg name="r11" bitsize="32" type="uint32"/> <reg name="r12" bitsize="32" type="uint32"/> <reg name="r13" bitsize="32" type="uint32"/> <reg name="r14" bitsize="32" type="uint32"/> <reg name="r15" bitsize="32" type="uint32"/> <reg name="r16" bitsize="32" type="uint32"/> <reg name="r17" bitsize="32" type="uint32"/> <reg name="r18" bitsize="32" type="uint32"/> <reg name="r19" bitsize="32" type="uint32"/> <reg name="r20" bitsize="32" type="uint32"/> <reg name="r21" bitsize="32" type="uint32"/> <reg name="r22" bitsize="32" type="uint32"/> <reg name="r23" bitsize="32" type="uint32"/> <reg name="r24" bitsize="32" type="uint32"/> <reg name="r25" bitsize="32" type="uint32"/> <reg name="r26" bitsize="32" type="uint32"/> <reg name="r27" bitsize="32" type="uint32"/> <reg name="r28" bitsize="32" type="uint32"/> <reg name="r29" bitsize="32" type="uint32"/> <reg name="r30" bitsize="32" type="uint32"/> <reg name="r31" bitsize="32" type="uint32"/> <reg name="pc" bitsize="32" type="code_ptr" regnum="64"/> <reg name="msr" bitsize="32" type="uint32"/> <reg name="cr" bitsize="32" type="uint32"/> <reg name="lr" bitsize="32" type="code_ptr"/> <reg name="ctr" bitsize="32" type="uint32"/> <reg name="xer" bitsize="32" type="uint32"/> </feature> <feature name="org.gnu.gdb.power.fpu"> <reg name="f0" bitsize="64" type="ieee_double" regnum="71"/> <reg name="f1" bitsize="64" type="ieee_double"/> <reg name="f2" bitsize="64" type="ieee_double"/> <reg name="f3" bitsize="64" type="ieee_double"/> <reg name="f4" bitsize="64" type="ieee_double"/> <reg name="f5" bitsize="64" type="ieee_double"/> <reg name="f6" bitsize="64" type="ieee_double"/> <reg name="f7" bitsize="64" type="ieee_double"/> <reg name="f8" bitsize="64" type="ieee_double"/> <reg name="f9" bitsize="64" type="ieee_double"/> <reg name="f10" bitsize="64" type="ieee_double"/> <reg name="f11" bitsize="64" type="ieee_double"/> <reg name="f12" bitsize="64" type="ieee_double"/> <reg name="f13" bitsize="64" type="ieee_double"/> <reg name="f14" bitsize="64" type="ieee_double"/> <reg name="f15" bitsize="64" type="ieee_double"/> <reg name="f16" bitsize="64" type="ieee_double"/> <reg name="f17" bitsize="64" type="ieee_double"/> <reg name="f18" bitsize="64" type="ieee_double"/> <reg name="f19" bitsize="64" type="ieee_double"/> <reg name="f20" bitsize="64" type="ieee_double"/> <reg name="f21" bitsize="64" type="ieee_double"/> <reg name="f22" bitsize="64" type="ieee_double"/> <reg name="f23" bitsize="64" type="ieee_double"/> <reg name="f24" bitsize="64" type="ieee_double"/> <reg name="f25" bitsize="64" type="ieee_double"/> <reg name="f26" bitsize="64" type="ieee_double"/> <reg name="f27" bitsize="64" type="ieee_double"/> <reg name="f28" bitsize="64" type="ieee_double"/> <reg name="f29" bitsize="64" type="ieee_double"/> <reg name="f30" bitsize="64" type="ieee_double"/> <reg name="f31" bitsize="64" type="ieee_double"/> <reg name="fpscr" bitsize="32" group="float"/> </feature> </target>)"; extern std::unique_ptr<GDBServer> g_gdbstub; ================================================ FILE: src/Cafe/HW/Espresso/EspressoISA.h ================================================ #pragma once namespace Espresso { enum CR_BIT { CR_BIT_INDEX_LT = 0, CR_BIT_INDEX_GT = 1, CR_BIT_INDEX_EQ = 2, CR_BIT_INDEX_SO = 3, }; enum class PSQ_LOAD_TYPE // also store type { TYPE_F32 = 0, TYPE_UNUSED1 = 1, TYPE_UNUSED2 = 2, TYPE_UNUSED3 = 3, TYPE_U8 = 4, TYPE_U16 = 5, TYPE_S8 = 6, TYPE_S16 = 7, }; enum class PrimaryOpcode { // underscore at the end of the name means that this instruction always updates CR0 (as if RC bit is set) ZERO = 0, VIRTUAL_HLE = 1, // 3 = TWI GROUP_4 = 4, MULLI = 7, SUBFIC = 8, CMPLI = 10, CMPI = 11, ADDIC = 12, ADDIC_ = 13, ADDI = 14, ADDIS = 15, BC = 16, // conditional branch GROUP_17 = 17, // SC B = 18, // unconditional branch GROUP_19 = 19, RLWIMI = 20, RLWINM = 21, // 22 ? RLWNM = 23, ORI = 24, ORIS = 25, XORI = 26, XORIS = 27, ANDI_ = 28, ANDIS_ = 29, GROUP_31 = 31, LWZ = 32, LWZU = 33, LBZ = 34, LBZU = 35, STW = 36, STWU = 37, STB = 38, STBU = 39, LHZ = 40, LHZU = 41, LHA = 42, LHAU = 43, STH = 44, STHU = 45, LMW = 46, STMW = 47, LFS = 48, LFSU = 49, LFD = 50, LFDU = 51, STFS = 52, STFSU = 53, STFD = 54, STFDU = 55, PSQ_L = 56, PSQ_LU = 57, // 58 ? GROUP_59 = 59, PSQ_ST = 60, PSQ_STU = 61, // 62 ? GROUP_63 = 63, }; enum class Opcode19 { MCRF = 0, BCLR = 16, CRNOR = 33, RFI = 50, CRANDC = 129, ISYNC = 150, CRXOR = 193, CRAND = 257, CREQV = 289, CRORC = 417, CROR = 449, BCCTR = 528 }; enum class Opcode31 { TW = 4, MFTB = 371, }; inline PrimaryOpcode GetPrimaryOpcode(uint32 opcode) { return (PrimaryOpcode)(opcode >> 26); }; inline Opcode19 GetGroup19Opcode(uint32 opcode) { return (Opcode19)((opcode >> 1) & 0x3FF); }; inline Opcode31 GetGroup31Opcode(uint32 opcode) { return (Opcode31)((opcode >> 1) & 0x3FF); }; struct BOField { BOField() = default; BOField(uint8 bo) : bo(bo) {}; bool conditionInverted() const { return (bo & 8) == 0; } bool decrementerIgnore() const { return (bo & 4) != 0; } bool decrementerMustBeZero() const { return (bo & 2) != 0; } bool conditionIgnore() const { return (bo & 16) != 0; } bool branchAlways() { return conditionIgnore() && decrementerIgnore(); } uint8 bo; }; // returns true if LK bit is set, only valid for branch instructions inline bool DecodeLK(uint32 opcode) { return (opcode & 1) != 0; } inline void _decodeForm_I(uint32 opcode, uint32& LI, bool& AA, bool& LK) { LI = opcode & 0x3fffffc; if (LI & 0x02000000) LI |= 0xfc000000; AA = (opcode & 2) != 0; LK = (opcode & 1) != 0; } inline void _decodeForm_D_branch(uint32 opcode, uint32& BD, BOField& BO, uint32& BI, bool& AA, bool& LK) { BD = opcode & 0xfffc; if (BD & 0x8000) BD |= 0xffff0000; BO = { (uint8)((opcode >> 21) & 0x1F) }; BI = (opcode >> 16) & 0x1F; AA = (opcode & 2) != 0; LK = (opcode & 1) != 0; } inline void _decodeForm_D_SImm(uint32 opcode, uint32& rD, uint32& rA, uint32& imm) { rD = (opcode >> 21) & 0x1F; rA = (opcode >> 16) & 0x1F; imm = (uint32)(sint32)(sint16)(opcode & 0xFFFF); } inline void _decodeForm_XL(uint32 opcode, BOField& BO, uint32& BI, bool& LK) { BO = { (uint8)((opcode >> 21) & 0x1F) }; BI = (opcode >> 16) & 0x1F; LK = (opcode & 1) != 0; } inline void decodeOp_ADDI(uint32 opcode, uint32& rD, uint32& rA, uint32& imm) { _decodeForm_D_SImm(opcode, rD, rA, imm); } inline void decodeOp_B(uint32 opcode, uint32& LI, bool& AA, bool& LK) { // form I _decodeForm_I(opcode, LI, AA, LK); } inline void decodeOp_BC(uint32 opcode, uint32& BD, BOField& BO, uint32& BI, bool& AA, bool& LK) { // form D _decodeForm_D_branch(opcode, BD, BO, BI, AA, LK); } inline void decodeOp_BCSPR(uint32 opcode, BOField& BO, uint32& BI, bool& LK) // BCLR and BCSPR { // form XL (with BD field expected to be zero) _decodeForm_XL(opcode, BO, BI, LK); } } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp ================================================ static void PPCInterpreter_setXerOV(PPCInterpreter_t* hCPU, bool hasOverflow) { if (hasOverflow) { hCPU->xer_so = 1; hCPU->xer_ov = 1; } else { hCPU->xer_ov = 0; } } static bool checkAdditionOverflow(uint32 x, uint32 y, uint32 r) { /* x y r result (has overflow) 0 0 0 0 1 0 0 0 0 1 0 0 1 1 0 1 0 0 1 1 1 0 1 0 0 1 1 0 1 1 1 0 */ return (((x ^ r) & (y ^ r)) >> 31) != 0; } static void PPCInterpreter_ADD(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); hCPU->gpr[rD] = (int)hCPU->gpr[rA] + (int)hCPU->gpr[rB]; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDO(PPCInterpreter_t* hCPU, uint32 opcode) { // Don't Starve Giant Edition uses this instruction + BSO PPC_OPC_TEMPL3_XO(); uint32 result = hCPU->gpr[rA] + hCPU->gpr[rB]; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(hCPU->gpr[rA], hCPU->gpr[rB], result)); hCPU->gpr[rD] = (uint32)result; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDC(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 a = hCPU->gpr[rA]; hCPU->gpr[rD] = a + hCPU->gpr[rB]; if (hCPU->gpr[rD] < a) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDCO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; hCPU->gpr[rD] = a + b; if (hCPU->gpr[rD] < a) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; // set SO/OV PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, b, hCPU->gpr[rD])); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDE(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = a + b + ca; // update xer if (ppc_carry_3(a, b, ca)) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDEO(PPCInterpreter_t* hCPU, uint32 opcode) { // used by DS Virtual Console (Super Mario 64 DS) PPC_OPC_TEMPL3_XO(); uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = a + b + ca; // update xer carry if (ppc_carry_3(a, b, ca)) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, b, hCPU->gpr[rD])); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDI(PPCInterpreter_t* hCPU, uint32 opcode) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); hCPU->gpr[rD] = (rA ? (int)hCPU->gpr[rA] : 0) + (int)imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDIC(PPCInterpreter_t* hCPU, uint32 opcode) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); uint32 a = hCPU->gpr[rA]; hCPU->gpr[rD] = a + imm; // update XER if (hCPU->gpr[rD] < a) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDIC_(PPCInterpreter_t* hCPU, uint32 opcode) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); uint32 a = hCPU->gpr[rA]; hCPU->gpr[rD] = a + imm; // update XER if (hCPU->gpr[rD] < a) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDIS(PPCInterpreter_t* hCPU, uint32 opcode) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_Shift16(opcode, rD, rA, imm); hCPU->gpr[rD] = (rA ? hCPU->gpr[rA] : 0) + imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDZE(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); uint32 a = hCPU->gpr[rA]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = a + ca; if ((a == 0xffffffff) && ca) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDZEO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); uint32 a = hCPU->gpr[rA]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = a + ca; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, 0, hCPU->gpr[rD])); if ((a == 0xffffffff) && ca) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDME(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); uint32 a = hCPU->gpr[rA]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = a + ca + 0xffffffff; if (a || ca) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDMEO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); uint32 a = hCPU->gpr[rA]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = a + ca + 0xffffffff; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, 0xffffffff, hCPU->gpr[rD])); if (a || ca) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBF(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBFO(PPCInterpreter_t* hCPU, uint32 opcode) { // Seen in Don't Starve Giant Edition and Teslagrad // also used by DS Virtual Console (Super Mario 64 DS) PPC_OPC_TEMPL3_XO(); uint32 result = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~hCPU->gpr[rA], hCPU->gpr[rB], result)); hCPU->gpr[rD] = result; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBFC(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; hCPU->gpr[rD] = ~a + b + 1; // update xer if (ppc_carry_3(~a, b, 1)) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBFCO(PPCInterpreter_t* hCPU, uint32 opcode) { // used by DS Virtual Console (Super Mario 64 DS) PPC_OPC_TEMPL3_XO(); uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; hCPU->gpr[rD] = ~a + b + 1; // update carry if (ppc_carry_3(~a, b, 1)) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; // update xer SO/OV PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~a, b, hCPU->gpr[rD])); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBFIC(PPCInterpreter_t* hCPU, uint32 opcode) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); uint32 a = hCPU->gpr[rA]; hCPU->gpr[rD] = ~a + imm + 1; if (ppc_carry_3(~a, imm, 1)) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBFE(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = ~a + b + ca; // update xer carry if (ppc_carry_3(~a, b, ca)) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBFEO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; uint32 ca = hCPU->xer_ca; uint32 result = ~a + b + ca; hCPU->gpr[rD] = result; // update xer carry if (ppc_carry_3(~a, b, ca)) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~a, b, result)); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBFZE(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); uint32 a = hCPU->gpr[rA]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = ~a + ca; if (a == 0 && ca) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBFZEO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); uint32 a = hCPU->gpr[rA]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = ~a + ca; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~a, 0, hCPU->gpr[rD])); if (a == 0 && ca) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBFME(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); uint32 a = hCPU->gpr[rA]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = ~a + 0xFFFFFFFF + ca; // update xer carry if (ppc_carry_3(~a, 0xFFFFFFFF, ca)) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opcode & PPC_OPC_RC) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SUBFMEO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); uint32 a = hCPU->gpr[rA]; uint32 ca = hCPU->xer_ca; hCPU->gpr[rD] = ~a + 0xFFFFFFFF + ca; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~a, 0xFFFFFFFF, hCPU->gpr[rD])); // update xer carry if (ppc_carry_3(~a, 0xFFFFFFFF, ca)) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; if (opcode & PPC_OPC_RC) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_MULHW_(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); sint64 a = (sint32)hCPU->gpr[rA]; sint64 b = (sint32)hCPU->gpr[rB]; sint64 c = a * b; hCPU->gpr[rD] = ((uint64)c) >> 32; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_MULHWU_(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint64 a = hCPU->gpr[rA]; uint64 b = hCPU->gpr[rB]; uint64 c = a * b; hCPU->gpr[rD] = c >> 32; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_MULLW(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); sint64 result = (sint64)hCPU->gpr[rA] * (sint64)hCPU->gpr[rB]; hCPU->gpr[rD] = (uint32)result; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_MULLWO(PPCInterpreter_t* hCPU, uint32 opcode) { // Don't Starve Giant Edition uses this instruction + BSO // also used by FullBlast when a save file exists + it uses mfxer to access overflow result PPC_OPC_TEMPL3_XO(); sint64 result = (sint64)(sint32)hCPU->gpr[rA] * (sint64)(sint32)hCPU->gpr[rB]; hCPU->gpr[rD] = (uint32)result; PPCInterpreter_setXerOV(hCPU, result < -0x80000000ll || result > 0x7FFFFFFFLL); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_MULLI(PPCInterpreter_t* hCPU, uint32 opcode) { int rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); hCPU->gpr[rD] = hCPU->gpr[rA] * imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_DIVW(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); sint32 a = (sint32)hCPU->gpr[rA]; sint32 b = (sint32)hCPU->gpr[rB]; if (b == 0) hCPU->gpr[rD] = a < 0 ? 0xFFFFFFFF : 0; else if (a == 0x80000000 && b == 0xFFFFFFFF) hCPU->gpr[rD] = 0xFFFFFFFF; else hCPU->gpr[rD] = a / b; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_DIVWO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); sint32 a = (sint32)hCPU->gpr[rA]; sint32 b = (sint32)hCPU->gpr[rB]; if (b == 0) { PPCInterpreter_setXerOV(hCPU, true); hCPU->gpr[rD] = a < 0 ? 0xFFFFFFFF : 0; } else if(a == 0x80000000 && b == 0xFFFFFFFF) { PPCInterpreter_setXerOV(hCPU, true); hCPU->gpr[rD] = 0xFFFFFFFF; } else { hCPU->gpr[rD] = a / b; PPCInterpreter_setXerOV(hCPU, false); } if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_DIVWU(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; if (b == 0) hCPU->gpr[rD] = 0; else if (a == 0x80000000 && b == 0xFFFFFFFF) hCPU->gpr[rD] = 0; else hCPU->gpr[rD] = a / b; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_DIVWUO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; if (b == 0) { PPCInterpreter_setXerOV(hCPU, true); hCPU->gpr[rD] = 0; } else if(a == 0x80000000 && b == 0xFFFFFFFF) { PPCInterpreter_setXerOV(hCPU, false); hCPU->gpr[rD] = 0; } else { hCPU->gpr[rD] = a / b; PPCInterpreter_setXerOV(hCPU, false); } if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CREQV(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL_X_CR(); ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA) ^ ppc_getCRBit(hCPU, crB) ^ 1); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CRAND(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL_X_CR(); ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA)&ppc_getCRBit(hCPU, crB)); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CRANDC(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL_X_CR(); ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA)&(ppc_getCRBit(hCPU, crB) ^ 1)); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CRNAND(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL_X_CR(); ppc_setCRBit(hCPU, crD, (ppc_getCRBit(hCPU, crA)&ppc_getCRBit(hCPU, crB)) ^ 1); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CROR(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL_X_CR(); ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA) | ppc_getCRBit(hCPU, crB)); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CRORC(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL_X_CR(); ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA) | (ppc_getCRBit(hCPU, crB) ^ 1)); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CRNOR(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL_X_CR(); ppc_setCRBit(hCPU, crD, (ppc_getCRBit(hCPU, crA) | ppc_getCRBit(hCPU, crB)) ^ 1); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CRXOR(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL_X_CR(); ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA) ^ ppc_getCRBit(hCPU, crB)); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_NEG(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); hCPU->gpr[rD] = (uint32)-((sint32)hCPU->gpr[rA]); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_NEGO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); PPCInterpreter_setXerOV(hCPU, hCPU->gpr[rA] == 0x80000000); hCPU->gpr[rD] = (uint32)-((sint32)hCPU->gpr[rA]); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ANDX(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); hCPU->gpr[rA] = hCPU->gpr[rD] & hCPU->gpr[rB]; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ANDCX(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); hCPU->gpr[rA] = hCPU->gpr[rD] & ~hCPU->gpr[rB]; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ANDI_(PPCInterpreter_t* hCPU, uint32 opcode) { int rS, rA; uint32 imm; PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); hCPU->gpr[rA] = hCPU->gpr[rS] & imm; ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ANDIS_(PPCInterpreter_t* hCPU, uint32 opcode) { int rS, rA; uint32 imm; PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); hCPU->gpr[rA] = hCPU->gpr[rS] & imm; ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_NANDX(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); hCPU->gpr[rA] = ~(hCPU->gpr[rD] & hCPU->gpr[rB]); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_OR(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); hCPU->gpr[rA] = hCPU->gpr[rD] | hCPU->gpr[rB]; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ORC(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); hCPU->gpr[rA] = hCPU->gpr[rD] | ~hCPU->gpr[rB]; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ORI(PPCInterpreter_t* hCPU, uint32 opcode) { int rS, rA; uint32 imm; PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); hCPU->gpr[rA] = hCPU->gpr[rS] | imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ORIS(PPCInterpreter_t* hCPU, uint32 opcode) { int rS, rA; uint32 imm; PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); hCPU->gpr[rA] = hCPU->gpr[rS] | imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_NORX(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); hCPU->gpr[rA] = ~(hCPU->gpr[rD] | hCPU->gpr[rB]); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_XOR(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); hCPU->gpr[rA] = hCPU->gpr[rD] ^ hCPU->gpr[rB]; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_XORI(PPCInterpreter_t* hCPU, uint32 opcode) { int rS, rA; uint32 imm; PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); hCPU->gpr[rA] = hCPU->gpr[rS] ^ imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_XORIS(PPCInterpreter_t* hCPU, uint32 opcode) { int rS, rA; uint32 imm; PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); hCPU->gpr[rA] = hCPU->gpr[rS] ^ imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_EQV(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); hCPU->gpr[rA] = ~(hCPU->gpr[rD] ^ hCPU->gpr[rB]); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_RLWIMI(PPCInterpreter_t* hCPU, uint32 opcode) { int rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); uint32 v = ppc_word_rotl(hCPU->gpr[rS], SH); uint32 mask = ppc_mask(MB, ME); hCPU->gpr[rA] = (v & mask) | (hCPU->gpr[rA] & ~mask); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_RLWINM(PPCInterpreter_t* hCPU, uint32 opcode) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); uint32 v = ppc_word_rotl(hCPU->gpr[rS], SH); uint32 mask = ppc_mask(MB, ME); hCPU->gpr[rA] = v & mask; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_RLWNM(PPCInterpreter_t* hCPU, uint32 opcode) { int rS, rA, rB, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, rB, MB, ME); uint32 v = ppc_word_rotl(hCPU->gpr[rS], hCPU->gpr[rB]); uint32 mask = ppc_mask(MB, ME); hCPU->gpr[rA] = v & mask; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SLWX(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 s = hCPU->gpr[rB] & 0x3f; if (s > 31) hCPU->gpr[rA] = 0; else hCPU->gpr[rA] = hCPU->gpr[rD] << s; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SRAW(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 sh = hCPU->gpr[rB] & 0x3f; hCPU->gpr[rA] = hCPU->gpr[rD]; if (sh > 31) { hCPU->xer_ca = (hCPU->gpr[rA] >> 31) & 1; // copy sign bit to ca hCPU->gpr[rA] = (uint32)((sint32)hCPU->gpr[rA] >> 31); // fill all bits with sign bit } else { // ca is set when input is negative and non-zero bits are dropped by shift operation uint8 caBit = (hCPU->gpr[rA] >> 31) & 1; uint32 shiftedBits = hCPU->gpr[rA] & ~(0xFFFFFFFF << sh); caBit &= (shiftedBits != 0 ? 1 : 0); hCPU->xer_ca = caBit; hCPU->gpr[rA] = (uint32)((sint32)hCPU->gpr[rA] >> sh); } if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SRWX(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); uint32 v = hCPU->gpr[rB] & 0x3f; if (v > 31) hCPU->gpr[rA] = 0; else hCPU->gpr[rA] = hCPU->gpr[rD] >> v; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_SRAWI(PPCInterpreter_t* hCPU, uint32 opcode) { sint32 rS, rA; uint32 SH; PPC_OPC_TEMPL_X(opcode, rS, rA, SH); hCPU->gpr[rA] = hCPU->gpr[rS]; hCPU->xer_ca = 0; if (hCPU->gpr[rA] & 0x80000000) { uint32 ca = 0; for (uint32 i = 0; i < SH; i++) { if (hCPU->gpr[rA] & 1) ca = 1; hCPU->gpr[rA] >>= 1; hCPU->gpr[rA] |= 0x80000000; } if (ca) hCPU->xer_ca = 1; } else { if (SH > 31) hCPU->gpr[rA] = 0; else hCPU->gpr[rA] >>= SH; } if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static uint32 _CNTLZW(uint32 v) { uint32 result = 0; if (v == 0) return 32; if ((v & 0xFFFF0000) != 0) { result |= 16; v >>= 16; } if ((v & 0xFF00FF00) != 0) { result |= 8; v >>= 8; } if ((v & 0xF0F0F0F0) != 0) { result |= 4; v >>= 4; } if ((v & 0xCCCCCCCC) != 0) { result |= 2; v >>= 2; } if ((v & 0xAAAAAAAA) != 0) { result |= 1; } result = 31 - result; return result; } static void PPCInterpreter_CNTLZW(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); hCPU->gpr[rA] = _CNTLZW(hCPU->gpr[rD]); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_EXTSB(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); hCPU->gpr[rA] = (uint32)(sint32)(sint8)hCPU->gpr[rD]; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_EXTSH(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); hCPU->gpr[rA] = (uint32)(sint32)(sint16)hCPU->gpr[rD]; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CMP(PPCInterpreter_t* hCPU, uint32 opcode) { uint32 cr; sint32 rA, rB; PPC_OPC_TEMPL_X(opcode, cr, rA, rB); cr >>= 2; sint32 a = hCPU->gpr[rA]; sint32 b = hCPU->gpr[rB]; hCPU->cr[cr * 4 + 0] = 0; hCPU->cr[cr * 4 + 1] = 0; hCPU->cr[cr * 4 + 2] = 0; hCPU->cr[cr * 4 + 3] = 0; if (a < b) hCPU->cr[cr * 4 + CR_BIT_LT] = 1; else if (a > b) hCPU->cr[cr * 4 + CR_BIT_GT] = 1; else hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CMPL(PPCInterpreter_t* hCPU, uint32 opcode) { uint32 cr; int rA, rB; PPC_OPC_TEMPL_X(opcode, cr, rA, rB); cr >>= 2; uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; hCPU->cr[cr * 4 + 0] = 0; hCPU->cr[cr * 4 + 1] = 0; hCPU->cr[cr * 4 + 2] = 0; hCPU->cr[cr * 4 + 3] = 0; if (a < b) hCPU->cr[cr * 4 + CR_BIT_LT] = 1; else if (a > b) hCPU->cr[cr * 4 + CR_BIT_GT] = 1; else hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CMPI(PPCInterpreter_t* hCPU, uint32 opcode) { uint32 cr; int rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, cr, rA, imm); cr >>= 2; sint32 a = hCPU->gpr[rA]; sint32 b = imm; hCPU->cr[cr * 4 + 0] = 0; hCPU->cr[cr * 4 + 1] = 0; hCPU->cr[cr * 4 + 2] = 0; hCPU->cr[cr * 4 + 3] = 0; if (a < b) hCPU->cr[cr * 4 + CR_BIT_LT] = 1; else if (a > b) hCPU->cr[cr * 4 + CR_BIT_GT] = 1; else hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_CMPLI(PPCInterpreter_t* hCPU, uint32 opcode) { uint32 cr; int rA; uint32 imm; PPC_OPC_TEMPL_D_UImm(opcode, cr, rA, imm); cr >>= 2; uint32 a = hCPU->gpr[rA]; uint32 b = imm; hCPU->cr[cr * 4 + 0] = 0; hCPU->cr[cr * 4 + 1] = 0; hCPU->cr[cr * 4 + 2] = 0; hCPU->cr[cr * 4 + 3] = 0; if (a < b) hCPU->cr[cr * 4 + CR_BIT_LT] = 1; else if (a > b) hCPU->cr[cr * 4 + CR_BIT_GT] = 1; else hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so; PPCInterpreter_nextInstruction(hCPU); } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp ================================================ #include "../PPCState.h" #include "PPCInterpreterInternal.h" #include "PPCInterpreterHelper.h" #include<math.h> // floating point utility #include <limits> #include <array> const int ieee_double_e_bits = 11; // exponent bits const int ieee_double_m_bits = 52; // mantissa bits const int espresso_frsqrte_i_bits = 5; // index bits (the highest bit is the LSB of the exponent) typedef struct { uint32 offset; uint32 step; }espresso_frsqrte_entry_t; espresso_frsqrte_entry_t frsqrteLookupTable[32] = { {0x1a7e800, 0x568},{0x17cb800, 0x4f3},{0x1552800, 0x48d},{0x130c000, 0x435}, {0x10f2000, 0x3e7},{0xeff000, 0x3a2},{0xd2e000, 0x365},{0xb7c000, 0x32e}, {0x9e5000, 0x2fc},{0x867000, 0x2d0},{0x6ff000, 0x2a8},{0x5ab800, 0x283}, {0x46a000, 0x261},{0x339800, 0x243},{0x218800, 0x226},{0x105800, 0x20b}, {0x3ffa000, 0x7a4},{0x3c29000, 0x700},{0x38aa000, 0x670},{0x3572000, 0x5f2}, {0x3279000, 0x584},{0x2fb7000, 0x524},{0x2d26000, 0x4cc},{0x2ac0000, 0x47e}, {0x2881000, 0x43a},{0x2665000, 0x3fa},{0x2468000, 0x3c2},{0x2287000, 0x38e}, {0x20c1000, 0x35e},{0x1f12000, 0x332},{0x1d79000, 0x30a},{0x1bf4000, 0x2e6}, }; ATTR_MS_ABI double frsqrte_espresso(double input) { unsigned long long x = *(unsigned long long*)&input; // 0.0 and -0.0 if ((x << 1) == 0) { // result is inf or -inf x &= ~0x7FFFFFFFFFFFFFFF; x |= 0x7FF0000000000000; return *(double*)&x; } // get exponent uint32 e = (x >> ieee_double_m_bits) & ((1ull << ieee_double_e_bits) - 1ull); // NaN or INF if (e == 0x7FF) { if ((x&((1ull << ieee_double_m_bits) - 1)) == 0) { // negative INF returns +NaN if ((sint64)x < 0) { x = 0x7FF8000000000000; return *(double*)&x; } // positive INF returns +0.0 return 0.0; } // result is NaN with same sign and same mantissa (todo: verify) return *(double*)&x; } // negative number (other than -0.0) if ((sint64)x < 0) { // result is positive NaN x = 0x7FF8000000000000; return *(double*)&x; } // todo: handle denormals // get index (lsb of exponent, remaining bits of mantissa) uint32 idx = (x >> (ieee_double_m_bits - espresso_frsqrte_i_bits + 1ull))&((1 << espresso_frsqrte_i_bits) - 1); // get step multiplier uint32 stepMul = (x >> (ieee_double_m_bits - espresso_frsqrte_i_bits + 1 - 11))&((1 << 11) - 1); sint32 sum = frsqrteLookupTable[idx].offset - frsqrteLookupTable[idx].step * stepMul; e = 1023 - ((e - 1021) >> 1); x &= ~(((1ull << ieee_double_e_bits) - 1ull) << ieee_double_m_bits); x |= ((unsigned long long)e << ieee_double_m_bits); x &= ~((1ull << ieee_double_m_bits) - 1ull); x += ((unsigned long long)sum << 26ull); return *(double*)&x; } const int espresso_fres_i_bits = 5; // index bits const int espresso_fres_s_bits = 10; // step multiplier bits typedef struct { uint32 offset; uint32 step; }espresso_fres_entry_t; espresso_fres_entry_t fresLookupTable[32] = { // table calculated by fres_gen_table() {0x7ff800, 0x3e1}, {0x783800, 0x3a7}, {0x70ea00, 0x371}, {0x6a0800, 0x340}, {0x638800, 0x313}, {0x5d6200, 0x2ea}, {0x579000, 0x2c4}, {0x520800, 0x2a0}, {0x4cc800, 0x27f}, {0x47ca00, 0x261}, {0x430800, 0x245}, {0x3e8000, 0x22a}, {0x3a2c00, 0x212}, {0x360800, 0x1fb}, {0x321400, 0x1e5}, {0x2e4a00, 0x1d1}, {0x2aa800, 0x1be}, {0x272c00, 0x1ac}, {0x23d600, 0x19b}, {0x209e00, 0x18b}, {0x1d8800, 0x17c}, {0x1a9000, 0x16e}, {0x17ae00, 0x15b}, {0x14f800, 0x15b}, {0x124400, 0x143}, {0xfbe00, 0x143}, {0xd3800, 0x12d}, {0xade00, 0x12d}, {0x88400, 0x11a}, {0x65000, 0x11a}, {0x41c00, 0x108}, {0x20c00, 0x106} }; ATTR_MS_ABI double fres_espresso(double input) { // based on testing we know that fres uses only the first 15 bits of the mantissa // seee eeee eeee mmmm mmmm mmmm mmmx xxxx .... (s = sign, e = exponent, m = mantissa, x = not used) // the mantissa bits are interpreted as following: // 0000 0000 0000 iiii ifff ffff fff0 ... (i = table look up index , f = step multiplier) unsigned long long x = *(unsigned long long*)&input; // get index uint32 idx = (x >> (ieee_double_m_bits - espresso_fres_i_bits))&((1 << espresso_fres_i_bits) - 1); // get step multiplier uint32 stepMul = (x >> (ieee_double_m_bits - espresso_fres_i_bits - 10))&((1 << 10) - 1); uint32 sum = fresLookupTable[idx].offset - (fresLookupTable[idx].step * stepMul + 1) / 2; // get exponent uint32 e = (x >> ieee_double_m_bits) & ((1ull << ieee_double_e_bits) - 1ull); if (e == 0) { // todo? //x &= 0x7FFFFFFFFFFFFFFFull; x |= 0x7FF0000000000000ull; return *(double*)&x; } else if (e == 0x7ff) // NaN or INF { if ((x&((1ull << ieee_double_m_bits) - 1)) == 0) { // negative INF returns -0.0 if ((sint64)x < 0) { x = 0x8000000000000000; return *(double*)&x; } // positive INF returns +0.0 return 0.0; } // result is NaN with same sign and same mantissa (todo: verify) return *(double*)&x; } // todo - needs more testing (especially NaN and INF values) e = 2045 - e; x &= ~(((1ull << ieee_double_e_bits) - 1ull) << ieee_double_m_bits); x |= ((unsigned long long)e << ieee_double_m_bits); x &= ~((1ull << ieee_double_m_bits) - 1ull); x += ((unsigned long long)sum << 29ull); return *(double*)&x; } void fcmpu_espresso(PPCInterpreter_t* hCPU, int crfD, double a, double b) { uint32 c; ppc_setCRBit(hCPU, crfD + 0, 0); ppc_setCRBit(hCPU, crfD + 1, 0); ppc_setCRBit(hCPU, crfD + 2, 0); ppc_setCRBit(hCPU, crfD + 3, 0); if (IS_NAN(*(uint64*)&a) || IS_NAN(*(uint64*)&b)) { c = 1; ppc_setCRBit(hCPU, crfD + CR_BIT_SO, 1); } else if (a < b) { c = 8; ppc_setCRBit(hCPU, crfD + CR_BIT_LT, 1); } else if (a > b) { c = 4; ppc_setCRBit(hCPU, crfD + CR_BIT_GT, 1); } else { c = 2; ppc_setCRBit(hCPU, crfD + CR_BIT_EQ, 1); } if (IS_SNAN(*(uint64*)&a) || IS_SNAN(*(uint64*)&b)) hCPU->fpscr |= FPSCR_VXSNAN; hCPU->fpscr = (hCPU->fpscr & 0xffff0fff) | (c << 12); } void PPCInterpreter_FMR(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, rA, frB; PPC_OPC_TEMPL_X(Opcode, frD, rA, frB); PPC_ASSERT(rA==0); hCPU->fpr[frD].fpr = hCPU->fpr[frB].fpr; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FSEL(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); if ( hCPU->fpr[frA].fp0 >= -0.0f ) hCPU->fpr[frD] = hCPU->fpr[frC]; else hCPU->fpr[frD] = hCPU->fpr[frB]; PPC_ASSERT((Opcode & PPC_OPC_RC) != 0); // update CR1 flags PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FCTIWZ(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB; PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); PPC_ASSERT(frA==0); double b = hCPU->fpr[frB].fpr; uint64 v; if (b > (double)0x7FFFFFFF) { v = (uint64)0x7FFFFFFF; } else if (b < -(double)0x80000000) { v = (uint64)0x80000000; } else { v = (uint64)(uint32)(sint32)b; } hCPU->fpr[frD].guint = 0xFFF8000000000000ULL | v; if (v == 0 && ((*(uint64*)&b) >> 63)) hCPU->fpr[frD].guint |= 0x100000000ull; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FCTIW(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB; PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); PPC_ASSERT(frA==0); double b = hCPU->fpr[frB].fpr; uint64 v; if (b > (double)0x7FFFFFFF) { v = (uint64)0x7FFFFFFF; } else if (b < -(double)0x80000000) { v = (uint64)0x80000000; } else { // todo: Support for other rounding modes than NEAR double t = b + 0.5; sint32 i = (sint32)t; if (t - i < 0 || (t - i == 0 && b > 0)) { i--; } v = (uint64)i; } hCPU->fpr[frD].guint = 0xFFF8000000000000ULL | v; if (v == 0 && ((*(uint64*)&b) >> 63)) hCPU->fpr[frD].guint |= 0x100000000ull; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FNEG(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB; PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); PPC_ASSERT(frA==0); hCPU->fpr[frD].guint = hCPU->fpr[frB].guint ^ (1ULL << 63); PPC_ASSERT((Opcode & PPC_OPC_RC) != 0); // update CR1 flags PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FRSP(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB; PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); PPC_ASSERT(frA==0); if( PPC_PSE ) { hCPU->fpr[frD].fp0 = (float)hCPU->fpr[frB].fpr; hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; } else { hCPU->fpr[frD].fpr = (float)hCPU->fpr[frB].fpr; } PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FRSQRTE(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); PPC_ASSERT(frA==0 && frC==0); hCPU->fpr[frD].fpr = frsqrte_espresso(hCPU->fpr[frB].fpr); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FRES(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); PPC_ASSERT(frA==0 && frC==0); hCPU->fpr[frD].fpr = fres_espresso(hCPU->fpr[frB].fpr); if(PPC_PSE) hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; PPCInterpreter_nextInstruction(hCPU); } // Floating point ALU void PPCInterpreter_FABS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB; PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); PPC_ASSERT(frA==0); hCPU->fpr[frD].guint = hCPU->fpr[frB].guint & ~0x8000000000000000; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FNABS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB; PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); PPC_ASSERT(frA==0); hCPU->fpr[frD].guint = hCPU->fpr[frB].guint | 0x8000000000000000; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FADD(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); PPC_ASSERT(frC==0); hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr + hCPU->fpr[frB].fpr; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FDIV(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); PPC_ASSERT(frC==0); hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr / hCPU->fpr[frB].fpr; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FSUB(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); PPC_ASSERT(frC==0); hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr - hCPU->fpr[frB].fpr; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FMUL(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); PPC_ASSERT(frC == 0); hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FMADD(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr + hCPU->fpr[frB].fpr; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FNMADD(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); hCPU->fpr[frD].fpr = -(hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr + hCPU->fpr[frB].fpr); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FMSUB(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); hCPU->fpr[frD].fpr = (hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr - hCPU->fpr[frB].fpr); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FNMSUB(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); hCPU->fpr[frD].fpr = -(hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr - hCPU->fpr[frB].fpr); PPCInterpreter_nextInstruction(hCPU); } // Move void PPCInterpreter_MFFS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, rA, rB; PPC_OPC_TEMPL_X(Opcode, frD, rA, rB); PPC_ASSERT(rA==0 && rB==0); hCPU->fpr[frD].guint = (uint64)hCPU->fpscr; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_MTFSF(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frB; uint32 fm, FM; PPC_OPC_TEMPL_XFL(Opcode, frB, fm); FM = ((fm&0x80)?0xf0000000:0)|((fm&0x40)?0x0f000000:0)|((fm&0x20)?0x00f00000:0)|((fm&0x10)?0x000f0000:0)| ((fm&0x08)?0x0000f000:0)|((fm&0x04)?0x00000f00:0)|((fm&0x02)?0x000000f0:0)|((fm&0x01)?0x0000000f:0); hCPU->fpscr = (hCPU->fpr[frB].guint & FM) | (hCPU->fpscr & ~FM); PPC_ASSERT((Opcode & PPC_OPC_RC) != 0); // update CR1 flags static bool logFPSCRWriteOnce = false; if( logFPSCRWriteOnce == false ) { cemuLog_log(LogType::Force, "Unsupported write to FPSCR"); logFPSCRWriteOnce = true; } PPCInterpreter_nextInstruction(hCPU); } // single precision void PPCInterpreter_FADDS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); PPC_ASSERT(frB == 0); // todo: check for RC hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr + hCPU->fpr[frB].fpr); if (PPC_PSE) hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FSUBS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); PPC_ASSERT(frB == 0); hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr - hCPU->fpr[frB].fpr); if (PPC_PSE) hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FDIVS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); PPC_ASSERT(frB==0); hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr / hCPU->fpr[frB].fpr); if( PPC_PSE ) hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FMULS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); PPC_ASSERT(frB == 0); hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr * roundTo25BitAccuracy(hCPU->fpr[frC].fpr)); if (PPC_PSE) hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FMADDS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr * roundTo25BitAccuracy(hCPU->fpr[frC].fpr) + hCPU->fpr[frB].fpr); if (PPC_PSE) hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FNMADDS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); hCPU->fpr[frD].fpr = (float)-(hCPU->fpr[frA].fpr * roundTo25BitAccuracy(hCPU->fpr[frC].fpr) + hCPU->fpr[frB].fpr); if (PPC_PSE) hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FMSUBS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); hCPU->fpr[frD].fp0 = (float)(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0) - hCPU->fpr[frB].fp0); if (PPC_PSE) hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FNMSUBS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int frD, frA, frB, frC; PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); hCPU->fpr[frD].fp0 = (float)-(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0) - hCPU->fpr[frB].fp0); if (PPC_PSE) hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; PPCInterpreter_nextInstruction(hCPU); } // Compare void PPCInterpreter_FCMPO(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int crfD, frA, frB; PPC_OPC_TEMPL_X(Opcode, crfD, frA, frB); crfD >>= 2; hCPU->cr[crfD*4+0] = 0; hCPU->cr[crfD*4+1] = 0; hCPU->cr[crfD*4+2] = 0; hCPU->cr[crfD*4+3] = 0; uint32 c; if(IS_NAN(hCPU->fpr[frA].guint) || IS_NAN(hCPU->fpr[frB].guint)) { c = 1; hCPU->cr[crfD*4+CR_BIT_SO] = 1; } else if(hCPU->fpr[frA].fpr < hCPU->fpr[frB].fpr) { c = 8; hCPU->cr[crfD*4+CR_BIT_LT] = 1; } else if(hCPU->fpr[frA].fpr > hCPU->fpr[frB].fpr) { c = 4; hCPU->cr[crfD*4+CR_BIT_GT] = 1; } else { c = 2; hCPU->cr[crfD*4+CR_BIT_EQ] = 1; } hCPU->fpscr = (hCPU->fpscr & 0xffff0fff) | (c << 12); if (IS_SNAN (hCPU->fpr[frA].guint) || IS_SNAN (hCPU->fpr[frB].guint)) hCPU->fpscr |= FPSCR_VXSNAN; else if (!(hCPU->fpscr & FPSCR_VE) || IS_QNAN (hCPU->fpr[frA].guint) || IS_QNAN (hCPU->fpr[frB].guint)) hCPU->fpscr |= FPSCR_VXVC; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_FCMPU(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); int crfD, frA, frB; PPC_OPC_TEMPL_X(Opcode, crfD, frA, frB); cemu_assert_debug((crfD % 4) == 0); fcmpu_espresso(hCPU, crfD, hCPU->fpr[frA].fp0, hCPU->fpr[frB].fp0); PPCInterpreter_nextInstruction(hCPU); } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp ================================================ #include "../PPCState.h" #include "PPCInterpreterInternal.h" #include "PPCInterpreterHelper.h" std::unordered_set<std::string> s_unsupportedHLECalls; void PPCInterpreter_handleUnsupportedHLECall(PPCInterpreter_t* hCPU) { const char* libFuncName = (char*)memory_getPointerFromVirtualOffset(hCPU->instructionPointer + 8); std::string tempString = fmt::format("Unsupported lib call: {}", libFuncName); if (s_unsupportedHLECalls.find(tempString) == s_unsupportedHLECalls.end()) { cemuLog_log(LogType::UnsupportedAPI, "{}", tempString); s_unsupportedHLECalls.emplace(tempString); } hCPU->gpr[3] = 0; PPCInterpreter_nextInstruction(hCPU); } static constexpr size_t HLE_TABLE_CAPACITY = 0x4000; HLECALL s_ppcHleTable[HLE_TABLE_CAPACITY]{}; sint32 s_ppcHleTableWriteIndex = 0; std::mutex s_ppcHleTableMutex; HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName) { std::unique_lock _l(s_ppcHleTableMutex); if (s_ppcHleTableWriteIndex >= HLE_TABLE_CAPACITY) { cemuLog_log(LogType::Force, "HLE table is full"); cemu_assert(false); } for (sint32 i = 0; i < s_ppcHleTableWriteIndex; i++) { if (s_ppcHleTable[i] == hleCall) { return i; } } cemu_assert(s_ppcHleTableWriteIndex < HLE_TABLE_CAPACITY); s_ppcHleTable[s_ppcHleTableWriteIndex] = hleCall; HLEIDX funcIndex = s_ppcHleTableWriteIndex; s_ppcHleTableWriteIndex++; return funcIndex; } HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex) { if (funcIndex < 0 || funcIndex >= HLE_TABLE_CAPACITY) return nullptr; return s_ppcHleTable[funcIndex]; } std::mutex s_hleLogMutex; void PPCInterpreter_virtualHLE(PPCInterpreter_t* hCPU, unsigned int opcode) { uint32 hleFuncId = opcode & 0xFFFF; if (hleFuncId == 0xFFD0) [[unlikely]] { s_hleLogMutex.lock(); PPCInterpreter_handleUnsupportedHLECall(hCPU); s_hleLogMutex.unlock(); } else { // os lib function auto hleCall = PPCInterpreter_getHLECall(hleFuncId); cemu_assert(hleCall); hleCall(hCPU); } } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h ================================================ static uint32 ppc_cmp_and_mask[8] = { 0xfffffff0, 0xffffff0f, 0xfffff0ff, 0xffff0fff, 0xfff0ffff, 0xff0fffff, 0xf0ffffff, 0x0fffffff, }; #define ppc_word_rotl(_data, _n) (std::rotl<uint32>(_data,(_n)&0x1F)) static inline uint32 ppc_mask(int MB, int ME) { uint32 maskMB = 0xFFFFFFFF >> MB; uint32 maskME = 0xFFFFFFFF << (31-ME); uint32 mask2 = (MB <= ME) ? maskMB & maskME : maskMB | maskME; return mask2; } static inline bool ppc_carry_3(uint32 a, uint32 b, uint32 c) { if ((a+b) < a) { return true; } if ((a+b+c) < c) { return true; } return false; } #define PPC_getBits(__value, __index, __bitCount) ((__value>>(31-__index))&((1<<__bitCount)-1)) const static float LD_SCALE[] = { 1.000000f, 0.500000f, 0.250000f, 0.125000f, 0.062500f, 0.031250f, 0.015625f, 0.007813f, 0.003906f, 0.001953f, 0.000977f, 0.000488f, 0.000244f, 0.000122f, 0.000061f, 0.000031f, 0.000015f, 0.000008f, 0.000004f, 0.000002f, 0.000001f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 4294967296.000000f, 2147483648.000000f, 1073741824.000000f, 536870912.000000f, 268435456.000000f, 134217728.000000f, 67108864.000000f, 33554432.000000f, 16777216.000000f, 8388608.000000f, 4194304.000000f, 2097152.000000f, 1048576.000000f, 524288.000000f, 262144.000000f, 131072.000000f, 65536.000000f, 32768.000000f, 16384.000000f, 8192.000000f, 4096.000000f, 2048.000000f, 1024.000000f, 512.000000f, 256.000000f, 128.000000f, 64.000000f, 32.000000f, 16.000000f, 8.000000f, 4.000000f, 2.000000f }; const static float ST_SCALE[] = { 1.000000f, 2.000000f, 4.000000f, 8.000000f, 16.000000f, 32.000000f, 64.000000f, 128.000000f, 256.000000f, 512.000000f, 1024.000000f, 2048.000000f, 4096.000000f, 8192.000000f, 16384.000000f, 32768.000000f, 65536.000000f, 131072.000000f, 262144.000000f, 524288.000000f, 1048576.000000f, 2097152.000000f, 4194304.000000f, 8388608.000000f, 16777216.000000f, 33554432.000000f, 67108864.000000f, 134217728.000000f, 268435456.000000f, 536870912.000000f, 1073741824.000000f, 2147483648.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000001f, 0.000002f, 0.000004f, 0.000008f, 0.000015f, 0.000031f, 0.000061f, 0.000122f, 0.000244f, 0.000488f, 0.000977f, 0.001953f, 0.003906f, 0.007813f, 0.015625f, 0.031250f, 0.062500f, 0.125000f, 0.250000f, 0.500000f }; static float dequantize(uint32 data, sint32 type, uint8 scale) { float f; switch (type) { case 4: // u8 f = (float)(uint8)data; f *= LD_SCALE[scale]; break; case 5: // u16 f = (float)(uint16)data; f *= LD_SCALE[scale]; break; case 6: // s8 f = (float)(sint8)data; f *= LD_SCALE[scale]; break; case 7: // float f = (float)(sint16)data; f *= LD_SCALE[scale]; break; case 0: default: f = *((float *)&data); // scale does not apply when loading floats break; } return f; } static uint32 quantize(float data, sint32 type, uint8 scale) { uint32 val; switch (type) { case 4: // u8 data *= ST_SCALE[scale]; if (data < 0) data = 0; if (data > 255) data = 255; val = (uint8)(uint32)data; break; case 5: // u16 data *= ST_SCALE[scale]; if (data < 0) data = 0; if (data > 65535) data = 65535; val = (uint16)(uint32)data; break; case 6: // s8 data *= ST_SCALE[scale]; if (data < -128) data = -128; if (data > 127) data = 127; val = (sint8)(uint8)(sint32)(uint32)data; break; case 7: // s16 data *= ST_SCALE[scale]; if (data < -32768) data = -32768; if (data > 32767) data = 32767; val = (sint16)(uint16)(sint32)(uint32)data; break; case 0: // float default: // scale does not apply when storing floats *((float*)&val) = data; break; } return val; } #define _uint32_fastSignExtend(__v, __bits) (uint32)(((sint32)(__v)<<(31-(__bits)))>>(31-(__bits))); static inline uint64 ConvertToDoubleNoFTZ(uint32 value) { // http://www.freescale.com/files/product/doc/MPCFPE32B.pdf uint64 x = value; uint64 exp = (x >> 23) & 0xff; uint64 frac = x & 0x007fffff; if (exp > 0 && exp < 255) { uint64 y = !(exp >> 7); uint64 z = y << 61 | y << 60 | y << 59; return ((x & 0xc0000000) << 32) | z | ((x & 0x3fffffff) << 29); } else if (exp == 0 && frac != 0) // denormal { exp = 1023 - 126; do { frac <<= 1; exp -= 1; } while ((frac & 0x00800000) == 0); return ((x & 0x80000000) << 32) | (exp << 52) | ((frac & 0x007fffff) << 29); } else // QNaN, SNaN or Zero { uint64 y = exp >> 7; uint64 z = y << 61 | y << 60 | y << 59; return ((x & 0xc0000000) << 32) | z | ((x & 0x3fffffff) << 29); } } static inline uint32 ConvertToSingleNoFTZ(uint64 x) { uint32 exp = (x >> 52) & 0x7ff; if (exp > 896 || (x & ~0x8000000000000000ULL) == 0) { return ((x >> 32) & 0xc0000000) | ((x >> 29) & 0x3fffffff); } else if (exp >= 874) { uint32 t = (uint32)(0x80000000 | ((x & 0x000FFFFFFFFFFFFFULL) >> 21)); t = t >> (905 - exp); t |= (x >> 32) & 0x80000000; return t; } else { return ((x >> 32) & 0xc0000000) | ((x >> 29) & 0x3fffffff); } } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterImpl.cpp ================================================ #include "PPCInterpreterInternal.h" #include "PPCInterpreterHelper.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/HW/Espresso/Debugger/GDBStub.h" class PPCItpCafeOSUsermode { public: static const bool allowSupervisorMode = false; static const bool allowDSI = false; inline static uint32 memory_readCodeU32(PPCInterpreter_t* hCPU, uint32 address) { return _swapEndianU32(*(uint32*)(memory_base + address)); } inline static void ppcMem_writeDataDouble(PPCInterpreter_t* hCPU, uint32 address, double vf) { uint64 v = *(uint64*)&vf; uint32 v1 = v & 0xFFFFFFFF; uint32 v2 = v >> 32; uint8* ptr = memory_getPointerFromVirtualOffset(address); *(uint32*)(ptr + 4) = CPU_swapEndianU32(v1); *(uint32*)(ptr + 0) = CPU_swapEndianU32(v2); } inline static void ppcMem_writeDataU64(PPCInterpreter_t* hCPU, uint32 address, uint64 v) { *(uint64*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU64(v); } inline static void ppcMem_writeDataU32(PPCInterpreter_t* hCPU, uint32 address, uint32 v) { *(uint32*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU32(v); } inline static void ppcMem_writeDataU16(PPCInterpreter_t* hCPU, uint32 address, uint16 v) { *(uint16*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU16(v); } inline static void ppcMem_writeDataU8(PPCInterpreter_t* hCPU, uint32 address, uint8 v) { *(uint8*)(memory_getPointerFromVirtualOffset(address)) = v; } inline static double ppcMem_readDataDouble(PPCInterpreter_t* hCPU, uint32 address) { uint32 v[2]; v[1] = *(uint32*)(memory_getPointerFromVirtualOffset(address)); v[0] = *(uint32*)(memory_getPointerFromVirtualOffset(address) + 4); v[0] = CPU_swapEndianU32(v[0]); v[1] = CPU_swapEndianU32(v[1]); return *(double*)v; } inline static float ppcMem_readDataFloat(PPCInterpreter_t* hCPU, uint32 address) { uint32 v = *(uint32*)(memory_getPointerFromVirtualOffset(address)); v = CPU_swapEndianU32(v); return *(float*)&v; } inline static uint64 ppcMem_readDataU64(PPCInterpreter_t* hCPU, uint32 address) { uint64 v = *(uint64*)(memory_getPointerFromVirtualOffset(address)); return CPU_swapEndianU64(v); } inline static uint32 ppcMem_readDataU32(PPCInterpreter_t* hCPU, uint32 address) { uint32 v = *(uint32*)(memory_getPointerFromVirtualOffset(address)); return CPU_swapEndianU32(v); } inline static uint16 ppcMem_readDataU16(PPCInterpreter_t* hCPU, uint32 address) { uint16 v = *(uint16*)(memory_getPointerFromVirtualOffset(address)); return CPU_swapEndianU16(v); } inline static uint8 ppcMem_readDataU8(PPCInterpreter_t* hCPU, uint32 address) { return *(uint8*)(memory_getPointerFromVirtualOffset(address)); } inline static uint64 ppcMem_readDataFloatEx(PPCInterpreter_t* hCPU, uint32 addr) { return ConvertToDoubleNoFTZ(_swapEndianU32(*(uint32*)(memory_base + addr))); } inline static void ppcMem_writeDataFloatEx(PPCInterpreter_t* hCPU, uint32 addr, uint64 value) { *(uint32*)(memory_base + addr) = _swapEndianU32(ConvertToSingleNoFTZ(value)); } inline static uint64 getTB(PPCInterpreter_t* hCPU) { return PPCInterpreter_getMainCoreCycleCounter(); } }; uint32 debug_lastTranslatedHit; void generateDSIException(PPCInterpreter_t* hCPU, uint32 dataAddress) { // todo - check if we are already inside an interrupt handler (in which case the DSI exception is queued and not executed immediately?) // set flag to cancel current instruction hCPU->memoryException = true; hCPU->sprExtended.srr0 = hCPU->instructionPointer; hCPU->sprExtended.srr1 = hCPU->sprExtended.msr & 0x87C0FFFF; hCPU->sprExtended.dar = dataAddress; hCPU->sprExtended.msr &= ~0x04EF36; hCPU->instructionPointer = 0xFFF00300; uint32 dsisr = 0; dsisr |= (1<<(31-1)); // set if no TLB/BAT match found hCPU->sprExtended.dsisr = dsisr; } class PPCItpSupervisorWithMMU { public: static const bool allowSupervisorMode = true; static const bool allowDSI = true; inline static uint32 ppcMem_translateVirtualDataToPhysicalAddr(PPCInterpreter_t* hCPU, uint32 vAddr) { // check if address translation is disabled for data accesses if (GET_MSR_BIT(MSR_DR) == 0) { return vAddr; } #ifdef CEMU_DEBUG_ASSERT if (hCPU->memoryException) assert_dbg(); // should not be set anymore #endif // how to determine if BAT is valid: // BAT_entry_valid = (Vs & ~MSR[PR]) | (Vp & MSR[PR]) (The entry has separate enable flags for usermode and supervisor mode) for (sint32 i = 0; i < 8; i++) { // upper uint32 batU = hCPU->sprExtended.dbatU[i]; uint32 BEPI = ((batU >> 17) & 0x7FFF) << 17; uint32 Vp = (batU >> 0) & 1; uint32 Vs = (batU >> 1) & 1; uint32 BL = (((batU >> 2) & 0x7FF) ^ 0x7FF) << 17; BL |= 0xF0000000; if (Vs == 0) continue; // todo - check if in supervisor/usermode // lower uint32 batL = hCPU->sprExtended.dbatL[i]; uint32 PP = (batL >> 0) & 3; uint32 WIMG = (batL >> 3) & 0xF; uint32 BRPN = ((batL >> 17) & 0x7FFF) << 17; // check for match if ((vAddr&BL) == BEPI) { // match vAddr = (vAddr&~BL) | (BRPN&BL); debug_lastTranslatedHit = vAddr; return vAddr; } } // no match debug_lastTranslatedHit = 0xFFFFFFFF; // find segment uint32 segmentIndex = (vAddr>>28); //uint32 pageIndex = (vAddr >> 12) & 0xFFFF; // for 4KB pages // uint32 byteOffset = vAddr & 0xFFF; // for 4KB pages uint32 pageIndex = (vAddr >> 17) & 0x7FF; // for 128KB pages uint32 byteOffset = vAddr & 0x1FFFF; uint32 srValue = hCPU->sprExtended.sr[segmentIndex]; uint8 sr_ks = (srValue >> 30) & 1; // supervisor uint8 sr_kp = (srValue >> 29) & 1; // user mode uint8 sr_n = (srValue >> 28) & 1; // no-execute uint32 sr_vsid = (srValue & 0xFFFFFF); //uint32 vpn = pageIndex | (sr_vsid << 16); // 40bit virtual page number // look up in page table //uint32 lookupHash = (sr_vsid ^ pageIndex) & 0x7FFFF; // not correct for 4KB pages? sr_vsid must be shifted? //uint32 lookupHash = (sr_vsid ^ pageIndex) & 0x7FFFF; //uint32 lookupHash = ((sr_vsid>>8) ^ pageIndex) & 0x7FFFF; uint32 lookupHash = ((sr_vsid >> 0) ^ pageIndex) & 0x7FFFF; //lookupHash ^= 0x7FFFF; uint32 pageTableAddr = hCPU->sprExtended.sdr1&0xFFFF0000; uint32 pageTableMask = hCPU->sprExtended.sdr1&0x1FF; for (uint32 ch = 0; ch < 2; ch++) { uint32 ptegSelectLow = (lookupHash & 0x3FF); uint32 maskOR = (lookupHash >> 10) & pageTableMask; uint32* pteg = (uint32*)(memory_base + (pageTableAddr | (maskOR << 16)) + ptegSelectLow * 64); for (sint32 t = 0; t < 8; t++) { uint32 w0 = _swapEndianU32(pteg[0]); uint32 w1 = _swapEndianU32(pteg[1]); pteg += 2; if ((w0 & 0x80000000) == 0) continue; // entry not valid uint32 abPageIndex = (w0 >> 0) & 0x3F; uint8 h = (w0 >> 6) & 1; uint32 ptegVSID = (w0 >> 7) & 0xFFFFFF; if (abPageIndex == (pageIndex >> 5) && ptegVSID == sr_vsid && h == ch) { if (ch == 1) assert_dbg(); // match uint32 ptegPhysicalPage = (w1 >> 12) & 0xFFFFF; // replace page (128KB) vAddr = (vAddr & ~0xFFFE0000) | (ptegPhysicalPage << 12); return vAddr; } } // calculate hash 2 lookupHash = ~lookupHash; } cemuLog_logDebug(LogType::Force, "DSI exception at 0x{:08x} DataAddress {:08x}", hCPU->instructionPointer, vAddr); generateDSIException(hCPU, vAddr); // todo: Check hash func 1 // todo: Check protection bits // todo: Check supervisor/usermode bits // also use this function in all the mem stuff below // note: bat has higher priority than TLB // since iterating the bats and page table is too slow, we need to pre-process the data somehow. return vAddr; } inline static uint32 ppcMem_translateVirtualCodeToPhysicalAddr(PPCInterpreter_t* hCPU, uint32 vAddr) { // check if address translation is disabled for instruction accesses if (GET_MSR_BIT(MSR_IR) == 0) { return vAddr; } // how to determine if BAT is valid: // BAT_entry_valid = (Vs & ~MSR[PR]) | (Vp & MSR[PR]) (The entry has separate enable flags for usermode and supervisor mode) for (sint32 i = 0; i < 8; i++) { // upper uint32 batU = hCPU->sprExtended.ibatU[i]; uint32 BEPI = ((batU >> 17) & 0x7FFF) << 17; uint32 Vp = (batU >> 0) & 1; uint32 Vs = (batU >> 1) & 1; uint32 BL = (((batU >> 2) & 0x7FF) ^ 0x7FF) << 17; BL |= 0xF0000000; if (Vs == 0) continue; // todo - check if in supervisor/usermode // lower uint32 batL = hCPU->sprExtended.ibatL[i]; uint32 PP = (batL >> 0) & 3; uint32 WIMG = (batL >> 3) & 0xF; uint32 BRPN = ((batL >> 17) & 0x7FFF) << 17; // check for match if ((vAddr&BL) == BEPI) { // match vAddr = (vAddr&~BL) | (BRPN&BL); debug_lastTranslatedHit = vAddr; return vAddr; } } assert_dbg(); // no match // todo - throw exception if translation is enabled? return vAddr; } static uint32 memory_readCodeU32(PPCInterpreter_t* hCPU, uint32 address) { return _swapEndianU32(*(uint32*)(memory_base + ppcMem_translateVirtualCodeToPhysicalAddr(hCPU, address))); } inline static uint8* ppcMem_getDataPtr(PPCInterpreter_t* hCPU, uint32 vAddr) { return memory_base + ppcMem_translateVirtualDataToPhysicalAddr(hCPU, vAddr); } inline static void ppcMem_writeDataDouble(PPCInterpreter_t* hCPU, uint32 address, double vf) { uint64 v = *(uint64*)&vf; uint32 v1 = v & 0xFFFFFFFF; uint32 v2 = v >> 32; uint8* ptr = ppcMem_getDataPtr(hCPU, address); *(uint32*)(ptr + 4) = CPU_swapEndianU32(v1); *(uint32*)(ptr + 0) = CPU_swapEndianU32(v2); } inline static void ppcMem_writeDataU64(PPCInterpreter_t* hCPU, uint32 address, uint64 v) { *(uint64*)(ppcMem_getDataPtr(hCPU, address)) = CPU_swapEndianU64(v); } inline static void ppcMem_writeDataU32(PPCInterpreter_t* hCPU, uint32 address, uint32 v) { uint32 pAddr = ppcMem_translateVirtualDataToPhysicalAddr(hCPU, address); if (hCPU->memoryException) return; if (pAddr >= 0x0c000000 && pAddr < 0x0d100000) { cemu_assert_unimplemented(); return; } *(uint32*)(memory_base + pAddr) = CPU_swapEndianU32(v); } inline static void ppcMem_writeDataU16(PPCInterpreter_t* hCPU, uint32 address, uint16 v) { *(uint16*)(ppcMem_getDataPtr(hCPU, address)) = CPU_swapEndianU16(v); } inline static void ppcMem_writeDataU8(PPCInterpreter_t* hCPU, uint32 address, uint8 v) { *(uint8*)(ppcMem_getDataPtr(hCPU, address)) = v; } inline static double ppcMem_readDataDouble(PPCInterpreter_t* hCPU, uint32 address) { uint32 v[2]; v[1] = *(uint32*)(ppcMem_getDataPtr(hCPU, address)); v[0] = *(uint32*)(ppcMem_getDataPtr(hCPU, address) + 4); v[0] = CPU_swapEndianU32(v[0]); v[1] = CPU_swapEndianU32(v[1]); return *(double*)v; } inline static float ppcMem_readDataFloat(PPCInterpreter_t* hCPU, uint32 address) { uint32 v = *(uint32*)(ppcMem_getDataPtr(hCPU, address)); v = CPU_swapEndianU32(v); return *(float*)&v; } inline static uint64 ppcMem_readDataU64(PPCInterpreter_t* hCPU, uint32 address) { uint64 v = *(uint64*)(ppcMem_getDataPtr(hCPU, address)); return CPU_swapEndianU64(v); } inline static uint32 ppcMem_readDataU32(PPCInterpreter_t* hCPU, uint32 address) { uint32 pAddr = ppcMem_translateVirtualDataToPhysicalAddr(hCPU, address); if (hCPU->memoryException) return 0; if (pAddr >= 0x01FFF000 && pAddr < 0x02000000) { debug_printf("Access u32 boot param block 0x%08x IP %08x LR %08x\n", pAddr, hCPU->instructionPointer, hCPU->spr.LR); cemuLog_logDebug(LogType::Force, "Access u32 boot param block 0x{:08x} (org {:08x}) IP {:08x}", pAddr, address, hCPU->instructionPointer); } if (pAddr >= 0xFFEB73B0 && pAddr < (0xFFEB73B0+0x40C)) { debug_printf("Access cached u32 boot param block 0x%08x IP %08x LR %08x\n", pAddr, hCPU->instructionPointer, hCPU->spr.LR); cemuLog_logDebug(LogType::Force, "Access cached u32 boot param block 0x{:08x} (org {:08x}) IP {:08x}", pAddr, address, hCPU->instructionPointer); } if (pAddr >= 0x0c000000 && pAddr < 0x0d100000) { cemu_assert_unimplemented(); return 0; } uint32 v = *(uint32*)(memory_base + pAddr); return CPU_swapEndianU32(v); } inline static uint16 ppcMem_readDataU16(PPCInterpreter_t* hCPU, uint32 address) { uint16 v = *(uint16*)(ppcMem_getDataPtr(hCPU, address)); return CPU_swapEndianU16(v); } inline static uint8 ppcMem_readDataU8(PPCInterpreter_t* hCPU, uint32 address) { uint32 pAddr = ppcMem_translateVirtualDataToPhysicalAddr(hCPU, address); if (pAddr >= 0x0c000000 && pAddr < 0x0d100000) { cemu_assert_unimplemented(); return 0; } return *(uint8*)(memory_base + pAddr); } inline static uint64 ppcMem_readDataFloatEx(PPCInterpreter_t* hCPU, uint32 addr) { return ConvertToDoubleNoFTZ(_swapEndianU32(*(uint32*)(memory_base + addr))); } inline static void ppcMem_writeDataFloatEx(PPCInterpreter_t* hCPU, uint32 addr, uint64 value) { *(uint32*)(memory_base + addr) = _swapEndianU32(ConvertToSingleNoFTZ(value)); } inline static uint64 getTB(PPCInterpreter_t* hCPU) { return hCPU->global->tb / 20ULL; } }; template <typename ppcItpCtrl> class PPCInterpreterContainer { public: #include "PPCInterpreterSPR.hpp" #include "PPCInterpreterOPC.hpp" #include "PPCInterpreterLoadStore.hpp" #include "PPCInterpreterALU.hpp" static void executeInstruction(PPCInterpreter_t* hCPU) { if constexpr(ppcItpCtrl::allowSupervisorMode) { hCPU->global->tb++; } #ifdef __DEBUG_OUTPUT_INSTRUCTION debug_printf("%08x: ", hCPU->instructionPointer); #endif uint32 opcode = ppcItpCtrl::memory_readCodeU32(hCPU, hCPU->instructionPointer); switch ((opcode >> 26)) { case 0: debug_printf("ZERO[NOP] | 0x%08X\n", (unsigned int)hCPU->instructionPointer); #ifdef CEMU_DEBUG_ASSERT assert_dbg(); while (true) std::this_thread::sleep_for(std::chrono::seconds(1)); #endif hCPU->instructionPointer += 4; break; case 1: // virtual HLE PPCInterpreter_virtualHLE(hCPU, opcode); break; case 3: cemuLog_logDebug(LogType::Force, "Unsupported TWI instruction executed at {:08x}", hCPU->instructionPointer); PPCInterpreter_nextInstruction(hCPU); break; case 4: switch (PPC_getBits(opcode, 30, 5)) { case 0: // subcategory compare switch (PPC_getBits(opcode, 25, 5)) { case 0: // Sonic All Stars Racing PPCInterpreter_PS_CMPU0(hCPU, opcode); break; case 1: PPCInterpreter_PS_CMPO0(hCPU, opcode); break; case 2: // Assassin's Creed 3, Sonic All Stars Racing PPCInterpreter_PS_CMPU1(hCPU, opcode); break; default: cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4->0] at {:08x}", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); cemu_assert_unimplemented(); hCPU->instructionPointer += 4; break; } break; case 6: PPCInterpreter_PSQ_LX(hCPU, opcode); break; case 7: PPCInterpreter_PSQ_STX(hCPU, opcode); break; case 8: switch (PPC_getBits(opcode, 25, 5)) { case 1: PPCInterpreter_PS_NEG(hCPU, opcode); break; case 2: PPCInterpreter_PS_MR(hCPU, opcode); break; case 4: PPCInterpreter_PS_NABS(hCPU, opcode); break; case 8: PPCInterpreter_PS_ABS(hCPU, opcode); break; default: cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4->8] at {:08x}", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); cemu_assert_unimplemented(); hCPU->instructionPointer += 4; break; } break; case 10: PPCInterpreter_PS_SUM0(hCPU, opcode); break; case 11: PPCInterpreter_PS_SUM1(hCPU, opcode); break; case 12: PPCInterpreter_PS_MULS0(hCPU, opcode); break; case 13: PPCInterpreter_PS_MULS1(hCPU, opcode); break; case 14: PPCInterpreter_PS_MADDS0(hCPU, opcode); break; case 15: PPCInterpreter_PS_MADDS1(hCPU, opcode); break; case 16: // sub category - merge switch (PPC_getBits(opcode, 25, 5)) { case 16: PPCInterpreter_PS_MERGE00(hCPU, opcode); break; case 17: PPCInterpreter_PS_MERGE01(hCPU, opcode); break; case 18: PPCInterpreter_PS_MERGE10(hCPU, opcode); break; case 19: PPCInterpreter_PS_MERGE11(hCPU, opcode); break; default: cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4->16] at {:08x}", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); cemu_assert_unimplemented(); hCPU->instructionPointer += 4; break; } break; case 18: PPCInterpreter_PS_DIV(hCPU, opcode); break; case 20: PPCInterpreter_PS_SUB(hCPU, opcode); break; case 21: PPCInterpreter_PS_ADD(hCPU, opcode); break; case 22: PPCInterpreter_DCBZL(hCPU, opcode); break; case 23: PPCInterpreter_PS_SEL(hCPU, opcode); break; case 24: PPCInterpreter_PS_RES(hCPU, opcode); break; case 25: PPCInterpreter_PS_MUL(hCPU, opcode); break; case 26: // sub category with only one entry - RSQRTE PPCInterpreter_PS_RSQRTE(hCPU, opcode); break; case 28: PPCInterpreter_PS_MSUB(hCPU, opcode); break; case 29: PPCInterpreter_PS_MADD(hCPU, opcode); break; case 30: PPCInterpreter_PS_NMSUB(hCPU, opcode); break; case 31: PPCInterpreter_PS_NMADD(hCPU, opcode); break; default: cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4] at {:08x}", PPC_getBits(opcode, 30, 5), hCPU->instructionPointer); cemu_assert_unimplemented(); hCPU->instructionPointer += 4; break; } break; case 7: PPCInterpreter_MULLI(hCPU, opcode); break; case 8: PPCInterpreter_SUBFIC(hCPU, opcode); break; case 10: PPCInterpreter_CMPLI(hCPU, opcode); break; case 11: PPCInterpreter_CMPI(hCPU, opcode); break; case 12: PPCInterpreter_ADDIC(hCPU, opcode); break; case 13: PPCInterpreter_ADDIC_(hCPU, opcode); break; case 14: PPCInterpreter_ADDI(hCPU, opcode); break; case 15: PPCInterpreter_ADDIS(hCPU, opcode); break; case 16: PPCInterpreter_BCX(hCPU, opcode); break; case 17: if (PPC_getBits(opcode, 30, 1) == 1) { PPCInterpreter_SC(hCPU, opcode); } else { cemuLog_logDebug(LogType::Force, "Unsupported Opcode [0x17 --> 0x0]"); cemu_assert_unimplemented(); hCPU->instructionPointer += 4; } break; case 18: PPCInterpreter_BX(hCPU, opcode); break; case 19: // opcode category switch (PPC_getBits(opcode, 30, 10)) { case 0: PPCInterpreter_MCRF(hCPU, opcode); break; case 16: PPCInterpreter_BCLRX(hCPU, opcode); break; case 33: PPCInterpreter_CRNOR(hCPU, opcode); break; case 50: PPCInterpreter_RFI(hCPU, opcode); break; case 129: PPCInterpreter_CRANDC(hCPU, opcode); break; case 150: PPCInterpreter_ISYNC(hCPU, opcode); break; case 193: PPCInterpreter_CRXOR(hCPU, opcode); break; case 225: PPCInterpreter_CRNAND(hCPU, opcode); break; case 257: PPCInterpreter_CRAND(hCPU, opcode); break; case 289: PPCInterpreter_CREQV(hCPU, opcode); break; case 417: PPCInterpreter_CRORC(hCPU, opcode); break; case 449: PPCInterpreter_CROR(hCPU, opcode); break; case 528: PPCInterpreter_BCCTR(hCPU, opcode); break; default: cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [19] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); cemu_assert_unimplemented(); hCPU->instructionPointer += 4; break; } break; case 20: PPCInterpreter_RLWIMI(hCPU, opcode); break; case 21: PPCInterpreter_RLWINM(hCPU, opcode); break; case 23: PPCInterpreter_RLWNM(hCPU, opcode); break; case 24: PPCInterpreter_ORI(hCPU, opcode); break; case 25: PPCInterpreter_ORIS(hCPU, opcode); break; case 26: PPCInterpreter_XORI(hCPU, opcode); break; case 27: PPCInterpreter_XORIS(hCPU, opcode); break; case 28: PPCInterpreter_ANDI_(hCPU, opcode); break; case 29: PPCInterpreter_ANDIS_(hCPU, opcode); break; case 31: // opcode category switch (PPC_getBits(opcode, 30, 10)) { case 0: PPCInterpreter_CMP(hCPU, opcode); break; case 4: PPCInterpreter_TW(hCPU, opcode); break; case 8: PPCInterpreter_SUBFC(hCPU, opcode); break; case 10: PPCInterpreter_ADDC(hCPU, opcode); break; case 11: PPCInterpreter_MULHWU_(hCPU, opcode); break; case 19: PPCInterpreter_MFCR(hCPU, opcode); break; case 20: PPCInterpreter_LWARX(hCPU, opcode); break; case 23: PPCInterpreter_LWZX(hCPU, opcode); break; case 24: PPCInterpreter_SLWX(hCPU, opcode); break; case 26: PPCInterpreter_CNTLZW(hCPU, opcode); break; case 28: PPCInterpreter_ANDX(hCPU, opcode); break; case 32: PPCInterpreter_CMPL(hCPU, opcode); break; case 40: PPCInterpreter_SUBF(hCPU, opcode); break; case 54: PPCInterpreter_DCBST(hCPU, opcode); break; case 55: PPCInterpreter_LWZXU(hCPU, opcode); break; case 60: PPCInterpreter_ANDCX(hCPU, opcode); break; case 75: PPCInterpreter_MULHW_(hCPU, opcode); break; case 83: PPCInterpreter_MFMSR(hCPU, opcode); break; case 86: PPCInterpreter_DCBF(hCPU, opcode); break; case 87: PPCInterpreter_LBZX(hCPU, opcode); break; case 104: PPCInterpreter_NEG(hCPU, opcode); break; case 119: // Sonic Lost World PPCInterpreter_LBZXU(hCPU, opcode); break; case 124: PPCInterpreter_NORX(hCPU, opcode); break; case 136: PPCInterpreter_SUBFE(hCPU, opcode); break; case 138: PPCInterpreter_ADDE(hCPU, opcode); break; case 144: PPCInterpreter_MTCRF(hCPU, opcode); break; case 146: PPCInterpreter_MTMSR(hCPU, opcode); break; case 150: PPCInterpreter_STWCX(hCPU, opcode); break; case 151: PPCInterpreter_STWX(hCPU, opcode); break; case 183: PPCInterpreter_STWUX(hCPU, opcode); break; case 200: PPCInterpreter_SUBFZE(hCPU, opcode); break; case 202: PPCInterpreter_ADDZE(hCPU, opcode); break; case 210: PPCInterpreter_MTSR(hCPU, opcode); break; case 215: PPCInterpreter_STBX(hCPU, opcode); break; case 232: // Trine 2 PPCInterpreter_SUBFME(hCPU, opcode); break; case 234: PPCInterpreter_ADDME(hCPU, opcode); break; case 235: PPCInterpreter_MULLW(hCPU, opcode); break; case 247: PPCInterpreter_STBUX(hCPU, opcode); break; case 266: PPCInterpreter_ADD(hCPU, opcode); break; case 278: PPCInterpreter_DCBT(hCPU, opcode); break; case 279: PPCInterpreter_LHZX(hCPU, opcode); break; case 284: PPCInterpreter_EQV(hCPU, opcode); break; case 306: PPCInterpreter_TLBIE(hCPU, opcode); break; case 311: // Wii U Menu v177 (US) PPCInterpreter_LHZUX(hCPU, opcode); break; case 316: PPCInterpreter_XOR(hCPU, opcode); break; case 339: PPCInterpreter_MFSPR(hCPU, opcode); break; case 343: PPCInterpreter_LHAX(hCPU, opcode); break; case 371: PPCInterpreter_MFTB(hCPU, opcode); break; case 375: // Wii U Menu v177 (US) PPCInterpreter_LHAUX(hCPU, opcode); break; case 407: PPCInterpreter_STHX(hCPU, opcode); break; case 412: PPCInterpreter_ORC(hCPU, opcode); break; case 439: PPCInterpreter_STHUX(hCPU, opcode); break; case 444: PPCInterpreter_OR(hCPU, opcode); break; case 459: PPCInterpreter_DIVWU(hCPU, opcode); break; case 467: PPCInterpreter_MTSPR(hCPU, opcode); break; case 470: PPCInterpreter_DCBI(hCPU, opcode); break; case 476: PPCInterpreter_NANDX(hCPU, opcode); break; case 491: PPCInterpreter_DIVW(hCPU, opcode); break; case 512: PPCInterpreter_MCRXR(hCPU, opcode); break; case 520: // Affordable Space Adventures + other Unity games PPCInterpreter_SUBFCO(hCPU, opcode); break; case 522: PPCInterpreter_ADDCO(hCPU, opcode); break; case 523: // 11 | OE PPCInterpreter_MULHWU_(hCPU, opcode); // OE is ignored break; case 533: PPCInterpreter_LSWX(hCPU, opcode); break; case 534: PPCInterpreter_LWBRX(hCPU, opcode); break; case 535: PPCInterpreter_LFSX(hCPU, opcode); break; case 536: PPCInterpreter_SRWX(hCPU, opcode); break; case 552: PPCInterpreter_SUBFO(hCPU, opcode); break; case 566: PPCInterpreter_TLBSYNC(hCPU, opcode); break; case 567: PPCInterpreter_LFSUX(hCPU, opcode); break; case 587: // 75 | OE PPCInterpreter_MULHW_(hCPU, opcode); // OE is ignored for MULHW break; case 595: PPCInterpreter_MFSR(hCPU, opcode); break; case 597: PPCInterpreter_LSWI(hCPU, opcode); break; case 598: PPCInterpreter_SYNC(hCPU, opcode); break; case 599: PPCInterpreter_LFDX(hCPU, opcode); break; case 616: PPCInterpreter_NEGO(hCPU, opcode); break; case 631: PPCInterpreter_LFDUX(hCPU, opcode); break; case 648: // 136 | OE PPCInterpreter_SUBFEO(hCPU, opcode); break; case 650: // 138 | OE PPCInterpreter_ADDEO(hCPU, opcode); break; case 662: PPCInterpreter_STWBRX(hCPU, opcode); break; case 663: PPCInterpreter_STFSX(hCPU, opcode); break; case 661: PPCInterpreter_STSWX(hCPU, opcode); break; case 695: PPCInterpreter_STFSUX(hCPU, opcode); break; case 712: // 200 | OE PPCInterpreter_SUBFZEO(hCPU, opcode); break; case 714: // 202 | OE PPCInterpreter_ADDZEO(hCPU, opcode); break; case 725: PPCInterpreter_STSWI(hCPU, opcode); break; case 727: PPCInterpreter_STFDX(hCPU, opcode); break; case 744: // 232 | OE PPCInterpreter_SUBFMEO(hCPU, opcode); break; case 746: // 234 | OE PPCInterpreter_ADDMEO(hCPU, opcode); break; case 747: PPCInterpreter_MULLWO(hCPU, opcode); break; case 759: PPCInterpreter_STFDUX(hCPU, opcode); break; case 778: PPCInterpreter_ADDO(hCPU, opcode); break; case 790: PPCInterpreter_LHBRX(hCPU, opcode); break; case 792: PPCInterpreter_SRAW(hCPU, opcode); break; case 824: PPCInterpreter_SRAWI(hCPU, opcode); break; case 854: PPCInterpreter_EIEIO(hCPU, opcode); break; case 918: PPCInterpreter_STHBRX(hCPU, opcode); break; case 922: PPCInterpreter_EXTSH(hCPU, opcode); break; case 954: PPCInterpreter_EXTSB(hCPU, opcode); break; case 971: PPCInterpreter_DIVWUO(hCPU, opcode); break; case 982: PPCInterpreter_ICBI(hCPU, opcode); break; case 983: PPCInterpreter_STFIWX(hCPU, opcode); break; case 1003: PPCInterpreter_DIVWO(hCPU, opcode); break; case 1014: PPCInterpreter_DCBZ(hCPU, opcode); break; default: cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [31] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); cemu_assert_unimplemented(); hCPU->instructionPointer += 4; break; } break; case 32: PPCInterpreter_LWZ(hCPU, opcode); break; case 33: PPCInterpreter_LWZU(hCPU, opcode); break; case 34: PPCInterpreter_LBZ(hCPU, opcode); break; case 35: PPCInterpreter_LBZU(hCPU, opcode); break; case 36: PPCInterpreter_STW(hCPU, opcode); break; case 37: PPCInterpreter_STWU(hCPU, opcode); break; case 38: PPCInterpreter_STB(hCPU, opcode); break; case 39: PPCInterpreter_STBU(hCPU, opcode); break; case 40: PPCInterpreter_LHZ(hCPU, opcode); break; case 41: PPCInterpreter_LHZU(hCPU, opcode); break; case 42: PPCInterpreter_LHA(hCPU, opcode); break; case 43: PPCInterpreter_LHAU(hCPU, opcode); break; case 44: PPCInterpreter_STH(hCPU, opcode); break; case 45: PPCInterpreter_STHU(hCPU, opcode); break; case 46: PPCInterpreter_LMW(hCPU, opcode); break; case 47: PPCInterpreter_STMW(hCPU, opcode); break; case 48: PPCInterpreter_LFS(hCPU, opcode); break; case 49: PPCInterpreter_LFSU(hCPU, opcode); break; case 50: PPCInterpreter_LFD(hCPU, opcode); break; case 51: PPCInterpreter_LFDU(hCPU, opcode); break; case 52: PPCInterpreter_STFS(hCPU, opcode); break; case 53: PPCInterpreter_STFSU(hCPU, opcode); break; case 54: PPCInterpreter_STFD(hCPU, opcode); break; case 55: PPCInterpreter_STFDU(hCPU, opcode); break; case 56: PPCInterpreter_PSQ_L(hCPU, opcode); break; case 57: PPCInterpreter_PSQ_LU(hCPU, opcode); break; case 59: // opcode category switch (PPC_getBits(opcode, 30, 5)) { case 18: PPCInterpreter_FDIVS(hCPU, opcode); break; case 20: PPCInterpreter_FSUBS(hCPU, opcode); break; case 21: PPCInterpreter_FADDS(hCPU, opcode); break; case 24: PPCInterpreter_FRES(hCPU, opcode); break; case 25: PPCInterpreter_FMULS(hCPU, opcode); break; case 28: PPCInterpreter_FMSUBS(hCPU, opcode); break; case 29: PPCInterpreter_FMADDS(hCPU, opcode); break; case 30: PPCInterpreter_FNMSUBS(hCPU, opcode); break; case 31: PPCInterpreter_FNMADDS(hCPU, opcode); break; default: cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [59] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); cemu_assert_unimplemented(); hCPU->instructionPointer += 4; break; } break; case 60: PPCInterpreter_PSQ_ST(hCPU, opcode); break; case 61: PPCInterpreter_PSQ_STU(hCPU, opcode); break; case 63: // opcode category switch (PPC_getBits(opcode, 30, 5)) { case 0: PPCInterpreter_FCMPU(hCPU, opcode); break; case 12: PPCInterpreter_FRSP(hCPU, opcode); break; case 15: PPCInterpreter_FCTIWZ(hCPU, opcode); break; case 18: PPCInterpreter_FDIV(hCPU, opcode); break; case 20: PPCInterpreter_FSUB(hCPU, opcode); break; case 21: PPCInterpreter_FADD(hCPU, opcode); break; case 23: PPCInterpreter_FSEL(hCPU, opcode); break; case 25: PPCInterpreter_FMUL(hCPU, opcode); break; case 26: PPCInterpreter_FRSQRTE(hCPU, opcode); break; case 28: PPCInterpreter_FMSUB(hCPU, opcode); break; case 29: PPCInterpreter_FMADD(hCPU, opcode); break; case 30: PPCInterpreter_FNMSUB(hCPU, opcode); break; case 31: PPCInterpreter_FNMADD(hCPU, opcode); break; default: switch (PPC_getBits(opcode, 30, 10)) { case 14: PPCInterpreter_FCTIW(hCPU, opcode); break; case 32: PPCInterpreter_FCMPO(hCPU, opcode); break; case 38: PPCInterpreter_MTFSB1X(hCPU, opcode); break; case 40: PPCInterpreter_FNEG(hCPU, opcode); break; case 72: PPCInterpreter_FMR(hCPU, opcode); break; case 136: // Darksiders 2 PPCInterpreter_FNABS(hCPU, opcode); break; case 264: PPCInterpreter_FABS(hCPU, opcode); break; case 583: PPCInterpreter_MFFS(hCPU, opcode); break; case 711: PPCInterpreter_MTFSF(hCPU, opcode); break; default: cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [63] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); cemu_assert_unimplemented(); PPCInterpreter_nextInstruction(hCPU); break; } } break; default: cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} at {:08x}\n", PPC_getBits(opcode, 5, 6), (unsigned int)hCPU->instructionPointer); cemu_assert_unimplemented(); } } }; // Slim interpreter, trades some features for extra performance // Used when emulator runs in CafeOS HLE mode // Assumes the following: // - No MMU (linear memory with 1:1 mapping of physical to virtual) // - No interrupts // - Always runs in user mode // - Paired single mode is always enabled void PPCInterpreterSlim_executeInstruction(PPCInterpreter_t* hCPU) { PPCInterpreterContainer<PPCItpCafeOSUsermode>::executeInstruction(hCPU); } // Full interpreter, supports most PowerPC features // Used when emulator runs in LLE mode void PPCInterpreterFull_executeInstruction(PPCInterpreter_t* hCPU) { PPCInterpreterContainer<PPCItpSupervisorWithMMU>::executeInstruction(hCPU); } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h ================================================ #pragma once #include "Cafe/HW/Espresso/PPCState.h" // SPR constants #define SPR_XER 1 #define SPR_LR 8 #define SPR_CTR 9 #define SPR_DEC 22 #define SPR_SRR0 26 #define SPR_SRR1 27 #define SPR_HID0 1008 #define SPR_HID1 1009 #define SPR_HID2 920 #define SPR_TBL 268 #define SPR_TBU 269 #define SPR_DMAU 922 #define SPR_DMAL 923 // graphics quantization registers #define SPR_GQR0 912 #define SPR_GQR1 913 #define SPR_GQR2 914 #define SPR_GQR3 915 #define SPR_GQR4 916 #define SPR_GQR5 917 #define SPR_GQR6 918 #define SPR_GQR7 919 // user graphics quantization registers #define SPR_UGQR0 896 #define SPR_UGQR1 897 #define SPR_UGQR2 898 #define SPR_UGQR3 899 #define SPR_UGQR4 900 #define SPR_UGQR5 901 #define SPR_UGQR6 902 #define SPR_UGQR7 903 #define SPR_FPECR 1022 // used by the OS to store values #define SPR_PVR 287 // processor version, for Wii U this must be 0x7001xxxx - this register is only readable #define SPR_UPIR 1007 // core index #define SPR_SCR 947 // core control #define SPR_SDR1 25 // reversed CR bit indices #define CR_BIT_LT 0 #define CR_BIT_GT 1 #define CR_BIT_EQ 2 #define CR_BIT_SO 3 #define XER_BIT_CA (29) // carry bit index. To accelerate frequent access, this bit is stored as a separate uint8 #define XER_BIT_SO (31) // summary overflow, counterpart to CR SO #define XER_BIT_OV (30) // FPSCR #define FPSCR_VXSNAN (1<<24) #define FPSCR_VXVC (1<<19) #define MSR_SF (1<<31) #define MSR_UNKNOWN (1<<30) #define MSR_UNKNOWN2 (1<<27) #define MSR_VEC (1<<25) #define MSR_POW (1<<18) #define MSR_TGPR (1<<15) #define MSR_ILE (1<<16) #define MSR_EE (1<<15) #define MSR_PR (1<<14) #define MSR_FP (1<<13) #define MSR_ME (1<<12) #define MSR_FE0 (1<<11) #define MSR_SE (1<<10) #define MSR_BE (1<<9) #define MSR_FE1 (1<<8) #define MSR_IP (1<<6) #define MSR_IR (1<<5) #define MSR_DR (1<<4) #define MSR_PM (1<<2) #define MSR_RI (1<<1) #define MSR_LE (1) // helpers #define GET_MSR_BIT(__bit) ((hCPU->sprExtended.msr&(__bit))!=0) #define opHasRC() ((opcode & PPC_OPC_RC) != 0) // assume fixed values for PSE/LSQE. This optimization is possible because Wii U applications run only in user mode (todo - handle this correctly in LLE mode) //#define PPC_LSQE (hCPU->LSQE) //#define PPC_PSE (hCPU->PSE) #define PPC_LSQE (1) #define PPC_PSE (1) #define PPC_ASSERT(v) #define PPC_OPC_RC 1 #define PPC_OPC_OE (1<<10) #define PPC_OPC_LK 1 #define PPC_OPC_AA (1<<1) #define PPC_OPC_TEMPL_A(opc, rD, rA, rB, rC) {rD=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;rB=((opc)>>11)&0x1f;rC=((opc)>>6)&0x1f;} #define PPC_OPC_TEMPL_B(opc, BO, BI, BD) {BO=((opc)>>21)&0x1f;BI=((opc)>>16)&0x1f;BD=(uint32)(sint32)(sint16)((opc)&0xfffc);} #define PPC_OPC_TEMPL_D_SImm(opc, rD, rA, imm) {rD=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;imm=(uint32)(sint32)(sint16)((opc)&0xffff);} #define PPC_OPC_TEMPL_D_UImm(opc, rD, rA, imm) {rD=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;imm=(opc)&0xffff;} #define PPC_OPC_TEMPL_D_Shift16(opc, rD, rA, imm) {rD=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;imm=(opc)<<16;} #define PPC_OPC_TEMPL_I(opc, LI) {LI=(opc)&0x3fffffc;if (LI&0x02000000) LI |= 0xfc000000;} #define PPC_OPC_TEMPL_M(opc, rS, rA, SH, MB, ME) {rS=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;SH=((opc)>>11)&0x1f;MB=((opc)>>6)&0x1f;ME=((opc)>>1)&0x1f;} #define PPC_OPC_TEMPL_X(opc, rS, rA, rB) {rS=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;rB=((opc)>>11)&0x1f;} #define PPC_OPC_TEMPL_XFX(opc, rS, CRM) {rS=((opc)>>21)&0x1f;CRM=((opc)>>12)&0xff;} #define PPC_OPC_TEMPL_XO(opc, rS, rA, rB) {rS=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;rB=((opc)>>11)&0x1f;} #define PPC_OPC_TEMPL_XL(opc, BO, BI, BD) {BO=((opc)>>21)&0x1f;BI=((opc)>>16)&0x1f;BD=((opc)>>11)&0x1f;} #define PPC_OPC_TEMPL_XFL(opc, rB, FM) {rB=((opc)>>11)&0x1f;FM=((opc)>>17)&0xff;} #define PPC_OPC_TEMPL3_XO() sint32 rD, rA, rB; rD=((opcode)>>21)&0x1f;rA=((opcode)>>16)&0x1f;rB=((opcode)>>11)&0x1f #define PPC_OPC_TEMPL_X_CR() sint32 crD, crA, crB; crD=((opcode)>>21)&0x1f;crA=((opcode)>>16)&0x1f;crB=((opcode)>>11)&0x1f static inline void ppc_update_cr0(PPCInterpreter_t* hCPU, uint32 r) { cemu_assert_debug(hCPU->xer_so <= 1); hCPU->cr[CR_BIT_SO] = hCPU->xer_so; hCPU->cr[CR_BIT_LT] = ((r != 0) ? 1 : 0) & ((r & 0x80000000) ? 1 : 0); hCPU->cr[CR_BIT_EQ] = (r == 0); hCPU->cr[CR_BIT_GT] = hCPU->cr[CR_BIT_EQ] ^ hCPU->cr[CR_BIT_LT] ^ 1; // this works because EQ and LT can never be set at the same time. So the only case where GT becomes 1 is when LT=0 and EQ=0 } static inline uint8 ppc_getCRBit(PPCInterpreter_t* hCPU, uint32 r) { return hCPU->cr[r]; } static inline bool ppc_MTCRFMaskHasCRFieldSet(const uint32 mtcrfMask, const uint32 crIndex) { // 1000 0000 (0x80) -> cr0 // 0000 0001 (0x01) -> cr7 return (mtcrfMask & (1 << (7 - crIndex))) != 0; } // returns CR mask with CR0.LT in LSB static inline uint32 ppc_MTCRFMaskToCRBitMask(const uint32 mtcrfMask) { uint32 crMask = 0; for (uint32 crF = 0; crF < 8; crF++) { if (ppc_MTCRFMaskHasCRFieldSet(mtcrfMask, crF)) crMask |= (0xF << (crF * 4)); } return crMask; } static inline void ppc_setCRBit(PPCInterpreter_t* hCPU, uint32 r, uint8 v) { hCPU->cr[r] = v; } static inline void ppc_setCR(PPCInterpreter_t* hCPU, uint32 cr) { uint32 tempCr = cr; for (sint32 i = 31; i >= 0; i--) { ppc_setCRBit(hCPU, i, tempCr & 1); tempCr >>= 1; } } static inline uint32 ppc_getCR(PPCInterpreter_t* hCPU) { uint32 cr = 0; for (sint32 i = 0; i < 32; i++) { cr <<= 1; if (ppc_getCRBit(hCPU, i)) cr |= 1; } return cr; } // FPU helper #define IS_NAN(X) ((((X) & 0x000fffffffffffffULL) != 0) && (((X) & 0x7ff0000000000000ULL) == 0x7ff0000000000000ULL)) #define IS_QNAN(X) ((((X) & 0x000fffffffffffffULL) != 0) && (((X) & 0x7ff8000000000000ULL) == 0x7ff8000000000000ULL)) #define IS_SNAN(X) ((((X) & 0x000fffffffffffffULL) != 0) && (((X) & 0x7ff8000000000000ULL) == 0x7ff0000000000000ULL)) #define FPSCR_VE (1 << 7) inline double roundTo25BitAccuracy(double d) { uint64 v = *(uint64*)&d; v = (v & 0xFFFFFFFFF8000000ULL) + (v & 0x8000000ULL); return *(double*)&v; } ATTR_MS_ABI double fres_espresso(double input); ATTR_MS_ABI double frsqrte_espresso(double input); void fcmpu_espresso(PPCInterpreter_t* hCPU, int crfD, double a, double b); // OPC void PPCInterpreter_virtualHLE(PPCInterpreter_t* hCPU, unsigned int opcode); void PPCInterpreter_MFMSR(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_MTMSR(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_MFTB(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_MTFSB1X(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_MFCR(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_MCRF(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_MTCRF(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_MCRXR(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_TLBIE(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_TLBSYNC(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_DCBT(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_DCBST(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_DCBZL(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_DCBF(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_DCBI(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_DCBZ(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_ICBI(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_EIEIO(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_SC(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_SYNC(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_ISYNC(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_RFI(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_BX(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_BCX(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_BCLRX(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_BCCTR(PPCInterpreter_t* hCPU, uint32 Opcode); // FPU void PPCInterpreter_FCMPO(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FCMPU(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FMR(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FSEL(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FCTIWZ(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FCTIW(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FNEG(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FRSP(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FRSQRTE(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FRES(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FABS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FNABS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FADD(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FMUL(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FDIV(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FSUB(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FMADD(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FMSUB(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FMSUBS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FNMADD(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FNMSUB(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_MFFS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_MTFSF(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FDIVS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FMULS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FADDS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FSUBS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FMADDS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FNMADDS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_FNMSUBS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MERGE00(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MERGE01(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MERGE10(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MERGE11(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MR(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_NEG(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_ABS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_NABS(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_RES(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_RSQRTE(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_ADD(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_SUB(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MUL(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_DIV(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MADD(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_NMADD(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MADDS0(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MADDS1(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MSUB(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_NMSUB(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_SEL(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_SUM0(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_SUM1(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MULS0(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_MULS1(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_CMPO0(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_CMPU0(PPCInterpreter_t* hCPU, uint32 Opcode); void PPCInterpreter_PS_CMPU1(PPCInterpreter_t* hCPU, uint32 Opcode); ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp ================================================ #define _signExtend16To32(__v) ((uint32)(sint32)(sint16)(__v)) // store #define DSI_EXIT() \ if constexpr(ppcItpCtrl::allowDSI) \ { \ if (hCPU->memoryException) \ { \ hCPU->memoryException = false; \ return; \ } \ } static void PPCInterpreter_STW(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); if (rA != 0) { ppcItpCtrl::ppcMem_writeDataU32(hCPU, hCPU->gpr[rA] + imm, hCPU->gpr[rS]); } else { PPC_ASSERT(true); } PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STWU(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); ppcItpCtrl::ppcMem_writeDataU32(hCPU, hCPU->gpr[rA] + imm, hCPU->gpr[rS]); // check for rA != 0 ? hCPU->gpr[rA] += imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STWX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->gpr[rS]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STWCX(PPCInterpreter_t* hCPU, uint32 Opcode) { // http://www.ibm.com/developerworks/library/pa-atom/ sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; // check if we hold a reservation for the memory location // todo - this isnt accurate. STWCX can succeed even with a different EA if the reserved value remained untouched if (hCPU->reservedMemAddr == ea) { uint32be reservedValue = hCPU->reservedMemValue; // this is the value we expect in memory (if it does not match, STWCX fails) std::atomic<uint32be>* wordPtr; if constexpr(ppcItpCtrl::allowSupervisorMode) { wordPtr = _rawPtrToAtomic((uint32be*)(memory_base + ppcItpCtrl::ppcMem_translateVirtualDataToPhysicalAddr(hCPU, ea))); DSI_EXIT(); } else { wordPtr = _rawPtrToAtomic((uint32be*)memory_getPointerFromVirtualOffset(ea)); } uint32be newValue = hCPU->gpr[rS]; if (!wordPtr->compare_exchange_strong(reservedValue, newValue)) { // failed ppc_setCRBit(hCPU, CR_BIT_LT, 0); ppc_setCRBit(hCPU, CR_BIT_GT, 0); ppc_setCRBit(hCPU, CR_BIT_EQ, 0); } else { // success, new value has been written ppc_setCRBit(hCPU, CR_BIT_LT, 0); ppc_setCRBit(hCPU, CR_BIT_GT, 0); ppc_setCRBit(hCPU, CR_BIT_EQ, 1); } cemu_assert_debug(hCPU->xer_so <= 1); ppc_setCRBit(hCPU, CR_BIT_SO, hCPU->xer_so); // remove reservation hCPU->reservedMemAddr = 0; hCPU->reservedMemValue = 0; } else { // failed ppc_setCRBit(hCPU, CR_BIT_LT, 0); ppc_setCRBit(hCPU, CR_BIT_GT, 0); ppc_setCRBit(hCPU, CR_BIT_EQ, 0); } PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STWUX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->gpr[rS]); if (rA) hCPU->gpr[rA] += hCPU->gpr[rB]; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STWBRX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], _swapEndianU32(hCPU->gpr[rS])); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STMW(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rS, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); uint32 ea = (rA ? hCPU->gpr[rA] : 0) + imm; while (rS <= 31) { ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea, hCPU->gpr[rS]); rS++; ea += 4; } PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STH(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint16)hCPU->gpr[rS]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STHU(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint16)hCPU->gpr[rS]); if (rA) hCPU->gpr[rA] += imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STHX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint16)hCPU->gpr[rS]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STHUX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint16)hCPU->gpr[rS]); if (rA) hCPU->gpr[rA] += hCPU->gpr[rB]; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STHBRX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], _swapEndianU16((uint16)hCPU->gpr[rS])); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STB(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint8)hCPU->gpr[rS]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STBU(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); ppcItpCtrl::ppcMem_writeDataU8(hCPU, hCPU->gpr[rA] + imm, (uint8)hCPU->gpr[rS]); hCPU->gpr[rA] += imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STBX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint8)hCPU->gpr[rS]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STBUX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint8)hCPU->gpr[rS]); if (rA) hCPU->gpr[rA] += hCPU->gpr[rB]; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STSWI(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, nb; PPC_OPC_TEMPL_X(Opcode, rS, rA, nb); if (nb == 0) nb = 32; uint32 ea = rA ? hCPU->gpr[rA] : 0; uint32 r = 0; int i = 0; while (nb > 0) { if (i == 0) { r = rS < 32 ? hCPU->gpr[rS] : 0; // what happens if rS is out of bounds? rS++; rS %= 32; i = 4; } ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, (r >> 24)); r <<= 8; ea++; i--; nb--; } PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STSWX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); sint32 nb = hCPU->spr.XER&0x7F; if (nb == 0) { PPCInterpreter_nextInstruction(hCPU); return; } uint32 ea = rA ? hCPU->gpr[rA] : 0; ea += hCPU->gpr[rB]; uint32 r = 0; int i = 0; while (nb > 0) { if (i == 0) { r = rS < 32 ? hCPU->gpr[rS] : 0; // what happens if rS is out of bounds? rS++; rS %= 32; i = 4; } ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, (r >> 24)); r <<= 8; ea++; i--; nb--; } PPCInterpreter_nextInstruction(hCPU); } // load static void PPCInterpreter_LWZ(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); uint32 v = ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); DSI_EXIT(); hCPU->gpr[rD] = v; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LWZU(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); hCPU->gpr[rA] += imm; hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU32(hCPU, hCPU->gpr[rA]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LMW(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); uint32 ea = (rA ? hCPU->gpr[rA] : 0) + imm; while (rD <= 31) { hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU32(hCPU, ea); rD++; ea += 4; } PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LWZX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LWZXU(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU32(hCPU, ea); if (rA && rA != rD) hCPU->gpr[rA] = ea; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LWBRX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); hCPU->gpr[rD] = CPU_swapEndianU32(ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB])); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LWARX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU32(hCPU, ea); // set reservation hCPU->reservedMemAddr = ea; hCPU->reservedMemValue = hCPU->gpr[rD]; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LHZ(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LHZU(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); // FIXME: rA!=0 hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, hCPU->gpr[rA] + imm); hCPU->gpr[rA] += imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LHZX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LHZUX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, ea); if (rA && rA != rD) hCPU->gpr[rA] = ea; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LHBRX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); hCPU->gpr[rD] = CPU_swapEndianU16(ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB])); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LHA(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); hCPU->gpr[rD] = _signExtend16To32(hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LHAU(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); if (rA && rA != rD) hCPU->gpr[rA] += imm; hCPU->gpr[rD] = _signExtend16To32(hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LHAUX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, ea); if (rA && rA != rD) hCPU->gpr[rA] = ea; hCPU->gpr[rD] = _signExtend16To32(hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LHAX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); hCPU->gpr[rS] = ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); hCPU->gpr[rS] = _signExtend16To32(hCPU->gpr[rS]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LBZ(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LBZX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LBZXU(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU8(hCPU, ea); if (rA && rA != rD) hCPU->gpr[rA] = ea; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LBZU(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); PPC_ASSERT(rA == 0); uint8 r; uint32 ea = hCPU->gpr[rA] + imm; hCPU->gpr[rA] = ea; r = ppcItpCtrl::ppcMem_readDataU8(hCPU, ea); hCPU->gpr[rD] = r; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LSWI(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, nb; PPC_OPC_TEMPL_X(Opcode, rD, rA, nb); if (nb == 0) nb = 32; uint32 ea = rA ? hCPU->gpr[rA] : 0; uint32 r = 0; int i = 4; uint8 v; while (nb>0) { if (i == 0) { i = 4; if(rD < 32) hCPU->gpr[rD] = r; rD++; rD %= 32; r = 0; } v = ppcItpCtrl::ppcMem_readDataU8(hCPU, ea); r <<= 8; r |= v; ea++; i--; nb--; } while (i) { r <<= 8; i--; } if(rD < 32) hCPU->gpr[rD] = r; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LSWX(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); // byte count comes from XER uint32 nb = (hCPU->spr.XER>>0)&0x7F; if (nb == 0) { PPCInterpreter_nextInstruction(hCPU); return; // no-op } uint32 ea = rA ? hCPU->gpr[rA] : 0; ea += hCPU->gpr[rB]; uint32 r = 0; int i = 4; uint8 v; while (nb>0) { if (i == 0) { i = 4; if(rD < 32) hCPU->gpr[rD] = r; rD++; rD %= 32; r = 0; } v = ppcItpCtrl::ppcMem_readDataU8(hCPU, ea); r <<= 8; r |= v; ea++; i--; nb--; } while (i) { r <<= 8; i--; } if(rD < 32) hCPU->gpr[rD] = r; PPCInterpreter_nextInstruction(hCPU); } // floating point load static void PPCInterpreter_LFS(PPCInterpreter_t* hCPU, uint32 Opcode) //Copied { FPUCheckAvailable(); sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); uint64 val; //*(uint32*)&Val = ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA?hCPU->gpr[rA]:0)+imm); val = ppcItpCtrl::ppcMem_readDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); if (PPC_LSQE) hCPU->fpr[frD].fp0int = hCPU->fpr[frD].fp1int = val; else hCPU->fpr[frD].fp0int = val; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LFSX(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 rA, frD, rB; PPC_OPC_TEMPL_X(Opcode, frD, rA, rB); uint64 val; val = ppcItpCtrl::ppcMem_readDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); if (PPC_LSQE) hCPU->fpr[frD].fp0int = hCPU->fpr[frD].fp1int = val; else hCPU->fpr[frD].fp0int = val; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LFSUX(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 rA, frD, rB; PPC_OPC_TEMPL_X(Opcode, frD, rA, rB); uint64 Val; //*(uint32*)&Val = ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA?hCPU->gpr[rA]:0)+hCPU->gpr[rB]); Val = ppcItpCtrl::ppcMem_readDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); if (rA) hCPU->gpr[rA] += hCPU->gpr[rB]; if (PPC_LSQE) hCPU->fpr[frD].fp0int = hCPU->fpr[frD].fp1int = Val; else hCPU->fpr[frD].fp0int = Val;//ppcItpCtrl::ppcMem_readDataFloat((rA?hCPU->gpr[rA]:0)+hCPU->gpr[rB]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LFSU(PPCInterpreter_t* hCPU, uint32 Opcode) //Copied { FPUCheckAvailable(); sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); uint64 Val; //(uint32*)&Val = ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA?hCPU->gpr[rA]:0)+imm); Val = ppcItpCtrl::ppcMem_readDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); if (PPC_LSQE) hCPU->fpr[frD].fp0int = hCPU->fpr[frD].fp1int = Val; else hCPU->fpr[frD].fp0int = Val; if (rA) hCPU->gpr[rA] += imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LFD(PPCInterpreter_t* hCPU, uint32 Opcode) //Copied { FPUCheckAvailable(); sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); hCPU->fpr[frD].fpr = ppcItpCtrl::ppcMem_readDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm);//ppcItpCtrl::ppcMem_readDataQUAD((rA?hCPU->gpr[rA]:0)+imm); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LFDU(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); hCPU->fpr[frD].fpr = ppcItpCtrl::ppcMem_readDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm);//ppcItpCtrl::ppcMem_readDataQUAD((rA?hCPU->gpr[rA]:0)+imm); if (rA) hCPU->gpr[rA] += imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LFDX(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 rA, frD, rB; PPC_OPC_TEMPL_X(Opcode, frD, rA, rB); hCPU->fpr[frD].fpr = ppcItpCtrl::ppcMem_readDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_LFDUX(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 rA, frD, rB; PPC_OPC_TEMPL_X(Opcode, frD, rA, rB); hCPU->fpr[frD].fpr = ppcItpCtrl::ppcMem_readDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); if (rA) hCPU->gpr[rA] += hCPU->gpr[rB]; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STFS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); if (PPC_LSQE) ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, hCPU->fpr[frD].fp0int); else ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, hCPU->fpr[frD].fp0int); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STFSU(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); if (PPC_LSQE) ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, hCPU->fpr[frD].fp0int); else ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, hCPU->fpr[frD].fp0int); if (rA) hCPU->gpr[rA] += imm; PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STFSX(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 rA, frS, rB; PPC_OPC_TEMPL_X(Opcode, frS, rA, rB); if (PPC_LSQE) ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->fpr[frS].fp0int); else ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->fpr[frS].fp0int); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_STFSUX(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); // next instruction PPCInterpreter_nextInstruction(hCPU); int rA, frS, rB; PPC_OPC_TEMPL_X(Opcode, frS, rA, rB); if (PPC_LSQE) ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->fpr[frS].fp0int); else ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->fpr[frS].fp0int); if (rA) hCPU->gpr[rA] += hCPU->gpr[rB]; } static void PPCInterpreter_STFD(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); // next instruction PPCInterpreter_nextInstruction(hCPU); int rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); ppcItpCtrl::ppcMem_writeDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, hCPU->fpr[frD].fpr); // debug output #ifdef __DEBUG_OUTPUT_INSTRUCTION debug_printf("STFD f%d, %d(r%d)\n", frD, imm, rA); #endif } static void PPCInterpreter_STFDU(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); // next instruction PPCInterpreter_nextInstruction(hCPU); int rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); if (rA) { hCPU->gpr[rA] += imm; } else { PPC_ASSERT(true); } ppcItpCtrl::ppcMem_writeDataDouble(hCPU, hCPU->gpr[rA], hCPU->fpr[frD].fpr); // debug output #ifdef __DEBUG_OUTPUT_INSTRUCTION debug_printf("STFD f%d, %d(r%d)\n", frD, imm, rA); #endif } static void PPCInterpreter_STFDX(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); // next instruction PPCInterpreter_nextInstruction(hCPU); int rA, frS, rB; PPC_OPC_TEMPL_X(Opcode, frS, rA, rB); ppcItpCtrl::ppcMem_writeDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->fpr[frS].fpr); // debug output #ifdef __DEBUG_OUTPUT_INSTRUCTION debug_printf("STFD f%d, r%d+r%d\n", frS, rA, rB); #endif } static void PPCInterpreter_STFDUX(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); // next instruction PPCInterpreter_nextInstruction(hCPU); int rA, frS, rB; PPC_OPC_TEMPL_X(Opcode, frS, rA, rB); if (rA == 0) { ppcItpCtrl::ppcMem_writeDataDouble(hCPU, hCPU->gpr[rB], hCPU->fpr[frS].fpr); } else { hCPU->gpr[rA] += hCPU->gpr[rB]; ppcItpCtrl::ppcMem_writeDataDouble(hCPU, hCPU->gpr[rA], hCPU->fpr[frS].fpr); } } static void PPCInterpreter_STFIWX(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 rA, frS, rB; PPC_OPC_TEMPL_X(Opcode, frS, rA, rB); uint32 val = (uint32)hCPU->fpr[frS].fp0int; ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], val); // next instruction PPCInterpreter_nextInstruction(hCPU); } // paired single // ST_TYPE: // 4 - uint8 // 5 - uint16 // 6 - sint8 // 7 - sint16 // 0 - float32 #define LD_SCALE(n) ((hCPU->spr.UGQR[0+n] >> 24) & 0x3f) #define LD_TYPE(n) ((hCPU->spr.UGQR[0+n] >> 16) & 7) #define ST_SCALE(n) ((hCPU->spr.UGQR[0+n] >> 8) & 0x3f) #define ST_TYPE(n) ((hCPU->spr.UGQR[0+n] ) & 7) #define PSW (opcode & 0x8000) #define PSI ((opcode >> 12) & 7) #define PSWX (opcode & (1<<(7+3))) #define PSIX ((opcode >> 7) & 7) static void PPCInterpreter_PSQ_ST(PPCInterpreter_t* hCPU, unsigned int opcode) { FPUCheckAvailable(); sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); uint32 ea = _uint32_fastSignExtend(imm, 11); ea += (rA ? hCPU->gpr[rA] : 0); sint32 type = ST_TYPE(PSI); uint8 scale = (uint8)ST_SCALE(PSI); if (opcode & 0x8000) // PSW? { if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); } else { if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea + 1, quantize((float)hCPU->fpr[frD].fp1, type, scale)); else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea + 2, quantize((float)hCPU->fpr[frD].fp1, type, scale)); else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 4, quantize((float)hCPU->fpr[frD].fp1, type, scale)); } PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_PSQ_STU(PPCInterpreter_t* hCPU, unsigned int opcode) { FPUCheckAvailable(); sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); uint32 ea = _uint32_fastSignExtend(imm, 11); if (ea & 0x800) ea |= 0xfffff000; ea += (rA ? hCPU->gpr[rA] : 0); if (rA) hCPU->gpr[rA] = ea; sint32 type = ST_TYPE((opcode >> 12) & 0x7); uint8 scale = (uint8)ST_SCALE(PSI); if (opcode & 0x8000) //PSW? { if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); } else { if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea + 1, quantize((float)hCPU->fpr[frD].fp1, type, scale)); else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea + 2, quantize((float)hCPU->fpr[frD].fp1, type, scale)); else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 4, quantize((float)hCPU->fpr[frD].fp1, type, scale)); } PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_PSQ_STX(PPCInterpreter_t* hCPU, unsigned int opcode) { FPUCheckAvailable(); // next instruction PPCInterpreter_nextInstruction(hCPU); sint32 frD; uint32 rA, rB; frD = (opcode >> (31 - 10)) & 0x1F; rA = (opcode >> (31 - 15)) & 0x1F; rB = (opcode >> (31 - 20)) & 0x1F; uint32 EA = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; sint32 type = ST_TYPE(PSIX); uint8 scale = (uint8)ST_SCALE(PSIX); if (PSWX) { if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else ppcItpCtrl::ppcMem_writeDataU32(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); } else { if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); else ppcItpCtrl::ppcMem_writeDataU32(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, EA + 1, quantize((float)hCPU->fpr[frD].fp1, type, scale)); else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, EA + 2, quantize((float)hCPU->fpr[frD].fp1, type, scale)); else ppcItpCtrl::ppcMem_writeDataU32(hCPU, EA + 4, quantize((float)hCPU->fpr[frD].fp1, type, scale)); } } static void PPCInterpreter_PSQ_L(PPCInterpreter_t* hCPU, unsigned int opcode) { FPUCheckAvailable(); // next instruction PPCInterpreter_nextInstruction(hCPU); sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); uint32 EA, data0 = 0, data1 = 0; sint32 type = LD_TYPE(PSI); uint8 scale = (uint8)LD_SCALE(PSI); EA = _uint32_fastSignExtend(opcode, 11); if (rA) EA += hCPU->gpr[rA]; if (opcode & 0x8000) { if ((type == 4) || (type == 6)) *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); else if ((type == 5) || (type == 7)) *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); else *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); if (type == 6) if (data0 & 0x80) data0 |= 0xffffff00; if (type == 7) if (data0 & 0x8000) data0 |= 0xffff0000; hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); hCPU->fpr[frD].fp1 = 1.0f; } else { if ((type == 4) || (type == 6)) { *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); *(uint8*)&data1 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA + 1); } else if ((type == 5) || (type == 7)) { *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); *(uint16*)&data1 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA + 2); } else { *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); *(uint32*)&data1 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA + 4); } if (type == 6) { if (data0 & 0x80) data0 |= 0xffffff00; if (data1 & 0x80) data1 |= 0xffffff00; } if (type == 7) { if (data0 & 0x8000) data0 |= 0xffff0000; if (data1 & 0x8000) data1 |= 0xffff0000; } hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); hCPU->fpr[frD].fp1 = (double)dequantize(data1, type, scale); } } static void PPCInterpreter_PSQ_LU(PPCInterpreter_t* hCPU, unsigned int opcode) { FPUCheckAvailable(); // next instruction PPCInterpreter_nextInstruction(hCPU); int rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); uint32 EA = opcode & 0xfff, data0 = 0, data1 = 0; sint32 type = LD_TYPE(PSI); uint8 scale = (uint8)LD_SCALE(PSI); if (EA & 0x800) EA |= 0xfffff000; if (rA) { EA += hCPU->gpr[rA]; hCPU->gpr[rA] = EA; } if (opcode & 0x8000) { if ((type == 4) || (type == 6)) *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); else if ((type == 5) || (type == 7)) *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); else *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); if (type == 6) if (data0 & 0x80) data0 |= 0xffffff00; if (type == 7) if (data0 & 0x8000) data0 |= 0xffff0000; hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); hCPU->fpr[frD].fp1 = 1.0f; } else { if ((type == 4) || (type == 6)) *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); else if ((type == 5) || (type == 7)) *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); else *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); if (type == 6) if (data0 & 0x80) data0 |= 0xffffff00; if (type == 7) if (data0 & 0x8000) data0 |= 0xffff0000; if ((type == 4) || (type == 6)) *(uint8*)&data1 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA + 1); else if ((type == 5) || (type == 7)) *(uint16*)&data1 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA + 2); else *(uint32*)&data1 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA + 4); if (type == 6) if (data1 & 0x80) data1 |= 0xffffff00; if (type == 7) if (data1 & 0x8000) data1 |= 0xffff0000; hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); hCPU->fpr[frD].fp1 = (double)dequantize(data1, type, scale); } } static void PPCInterpreter_PSQ_LX(PPCInterpreter_t* hCPU, unsigned int opcode) { FPUCheckAvailable(); // next instruction PPCInterpreter_nextInstruction(hCPU); sint32 frD; uint32 rA, rB; frD = (opcode >> (32 - 11)) & 0x1F; rA = (opcode >> (32 - 16)) & 0x1F; rB = (opcode >> (32 - 21)) & 0x1F; uint32 EA = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; uint32 data0 = 0, data1 = 0; sint32 type = LD_TYPE(PSIX); uint8 scale = (uint8)LD_SCALE(PSIX); if (PSWX) { if ((type == 4) || (type == 6)) *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); else if ((type == 5) || (type == 7)) *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); else *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); if (type == 6) if (data0 & 0x80) data0 |= 0xffffff00; if (type == 7) if (data0 & 0x8000) data0 |= 0xffff0000; hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); hCPU->fpr[frD].fp1 = 1.0f; } else { if ((type == 4) || (type == 6)) *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); else if ((type == 5) || (type == 7)) *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); else *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); if (type == 6) if (data0 & 0x80) data0 |= 0xffffff00; if (type == 7) if (data0 & 0x8000) data0 |= 0xffff0000; if ((type == 4) || (type == 6)) *(uint8*)&data1 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA + 1); else if ((type == 5) || (type == 7)) *(uint16*)&data1 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA + 2); else *(uint32*)&data1 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA + 4); if (type == 6) if (data1 & 0x80) data1 |= 0xffffff00; if (type == 7) if (data1 & 0x8000) data1 |= 0xffff0000; hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); hCPU->fpr[frD].fp1 = (double)dequantize(data1, type, scale); } } // misc static void PPCInterpreter_DCBZ(PPCInterpreter_t* hCPU, uint32 Opcode) { int rA, rB; rA = (Opcode >> (31 - 15)) & 0x1F; rB = (Opcode >> (31 - 20)) & 0x1F; uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; ea &= ~31; if constexpr(ppcItpCtrl::allowSupervisorMode) { // todo - optimize ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 0, 0); DSI_EXIT(); ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 4, 0); ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 8, 0); ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 12, 0); ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 16, 0); ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 20, 0); ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 24, 0); ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 28, 0); } else { memset((void*)memory_getPointerFromVirtualOffset(ea), 0x00, 0x20); } // debug output #ifdef __DEBUG_OUTPUT_INSTRUCTION debug_printf("DCBZ\n"); #endif // next instruction PPCInterpreter_nextInstruction(hCPU); } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp ================================================ #include "PPCInterpreterInternal.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" thread_local PPCInterpreter_t* ppcInterpreterCurrentInstance; // main thread instruction counter and timing uint64 ppcMainThreadDECCycleValue = 0; // value that was set to dec register uint64 ppcMainThreadDECCycleStart = 0; // at which cycle the dec register was set, if == 0 -> dec is 0 uint64 ppcCyclesSince2000 = 0; uint64 ppcCyclesSince2000TimerClock = 0; uint64 ppcCyclesSince2000_UTC = 0; PPCInterpreter_t* PPCInterpreter_createInstance(unsigned int Entrypoint) { PPCInterpreter_t* pData; // create instance uint32 prefixAreaSize = 0x6000; // we need to allocate some bytes before the interpreter struct because the recompiler will use it as stack area (specifically when the exception handler is called) pData = (PPCInterpreter_t*)((uint8*)malloc(sizeof(PPCInterpreter_t)+prefixAreaSize)+prefixAreaSize); memset((void*)pData, 0x00, sizeof(PPCInterpreter_t)); // set instruction pointer to entrypoint pData->instructionPointer = (uint32)Entrypoint; // set initial register values pData->gpr[GPR_SP] = 0x00000000; pData->spr.LR = 0; // return instance return pData; } TLS_WORKAROUND_NOINLINE PPCInterpreter_t* PPCInterpreter_getCurrentInstance() { return ppcInterpreterCurrentInstance; } TLS_WORKAROUND_NOINLINE void PPCInterpreter_setCurrentInstance(PPCInterpreter_t* hCPU) { ppcInterpreterCurrentInstance = hCPU; } uint64 PPCInterpreter_getMainCoreCycleCounter() { return PPCTimer_getFromRDTSC(); } void PPCInterpreter_nextInstruction(PPCInterpreter_t* cpuInterpreter) { cpuInterpreter->instructionPointer += 4; } void PPCInterpreter_jumpToInstruction(PPCInterpreter_t* cpuInterpreter, uint32 newIP) { cpuInterpreter->instructionPointer = (uint32)newIP; } void PPCInterpreter_setDEC(PPCInterpreter_t* hCPU, uint32 newValue) { hCPU->sprExtended.DEC = newValue; ppcMainThreadDECCycleStart = PPCInterpreter_getMainCoreCycleCounter(); ppcMainThreadDECCycleValue = newValue; } uint32 PPCInterpreter_getXER(PPCInterpreter_t* hCPU) { uint32 xerValue = hCPU->spr.XER; xerValue &= ~(1 << XER_BIT_CA); xerValue &= ~(1 << XER_BIT_SO); xerValue &= ~(1 << XER_BIT_OV); if (hCPU->xer_ca) xerValue |= (1 << XER_BIT_CA); if (hCPU->xer_so) xerValue |= (1 << XER_BIT_SO); if (hCPU->xer_ov) xerValue |= (1 << XER_BIT_OV); return xerValue; } void PPCInterpreter_setXER(PPCInterpreter_t* hCPU, uint32 v) { const uint32 XER_MASK = 0xE0FFFFFF; // some bits are masked out. Figure out which ones exactly hCPU->spr.XER = v & XER_MASK; hCPU->xer_ca = (v >> XER_BIT_CA) & 1; hCPU->xer_so = (v >> XER_BIT_SO) & 1; hCPU->xer_ov = (v >> XER_BIT_OV) & 1; } uint32 PPCInterpreter_getCoreIndex(PPCInterpreter_t* hCPU) { return hCPU->spr.UPIR; }; uint32 PPCInterpreter_getCurrentCoreIndex() { return PPCInterpreter_getCurrentInstance()->spr.UPIR; }; uint8* PPCInterpreterGetStackPointer() { return memory_getPointerFromVirtualOffset(PPCInterpreter_getCurrentInstance()->gpr[1]); } uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); uint8* result = memory_getPointerFromVirtualOffset(hCPU->gpr[1] - offset); hCPU->gpr[1] -= offset; return result; } void PPCInterpreterModifyStackPointer(sint32 offset) { PPCInterpreter_getCurrentInstance()->gpr[1] -= offset; } uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(PPCInterpreter_t* hCPU)); // deprecated wrapper, use RPLLoader_MakePPCCallable directly uint32 PPCInterpreter_makeCallableExportDepr(void (*ppcCallableExport)(PPCInterpreter_t* hCPU)) { return RPLLoader_MakePPCCallable(ppcCallableExport); } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp ================================================ #include "../PPCState.h" #include "PPCInterpreterInternal.h" #include "PPCInterpreterHelper.h" #include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h" #include "../Recompiler/PPCRecompiler.h" #include <float.h> #include "Cafe/HW/Latte/Core/LatteBufferCache.h" void PPCInterpreter_MFMSR(PPCInterpreter_t* hCPU, uint32 Opcode) { cemuLog_logDebug(LogType::Force, "Rare instruction: MFMSR"); if (hCPU->sprExtended.msr & MSR_PR) { PPC_ASSERT(true); return; } int rD, rA, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); hCPU->gpr[rD] = hCPU->sprExtended.msr; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_MTMSR(PPCInterpreter_t* hCPU, uint32 Opcode) { cemuLog_logDebug(LogType::Force, "Rare instruction: MTMSR"); if (hCPU->sprExtended.msr & MSR_PR) { PPC_ASSERT(true); return; } int rS, rA, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); hCPU->sprExtended.msr = hCPU->gpr[rS]; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_MTFSB1X(PPCInterpreter_t* hCPU, uint32 Opcode) { cemuLog_logDebug(LogType::Force, "Rare instruction: MTFSB1X"); int crbD, n1, n2; PPC_OPC_TEMPL_X(Opcode, crbD, n1, n2); if (crbD != 1 && crbD != 2) { hCPU->fpscr |= 1 << (31 - crbD); } if (Opcode & PPC_OPC_RC) { // update cr1 flags PPC_ASSERT(true); } PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_MCRF(PPCInterpreter_t* hCPU, uint32 Opcode) { uint32 crD, crS, b; PPC_OPC_TEMPL_X(Opcode, crD, crS, b); crD >>= 2; crS >>= 2; for (sint32 i = 0; i<4; i++) ppc_setCRBit(hCPU, crD * 4 + i, ppc_getCRBit(hCPU, crS * 4 + i)); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_MFCR(PPCInterpreter_t* hCPU, uint32 Opcode) { // frequently used by GCC compiled code (e.g. SM64 port) int rD, rA, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); // in our array: cr0.LT is entry with index 0 // in GPR: cr0.LT is in MSB uint32 cr = 0; for (sint32 i = 0; i < 32; i++) { cr <<= 1; if (ppc_getCRBit(hCPU, i) != 0) cr |= 1; } hCPU->gpr[rD] = cr; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_MTCRF(PPCInterpreter_t* hCPU, uint32 Opcode) { // frequently used by GCC compiled code (e.g. SM64 port) // tested uint32 rS; uint32 crfMask; PPC_OPC_TEMPL_XFX(Opcode, rS, crfMask); for (sint32 crIndex = 0; crIndex < 8; crIndex++) { if (!ppc_MTCRFMaskHasCRFieldSet(crfMask, crIndex)) continue; uint32 crBitBase = crIndex * 4; uint8 nibble = (uint8)(hCPU->gpr[rS] >> (28 - crIndex * 4)); ppc_setCRBit(hCPU, crBitBase + 0, (nibble >> 3) & 1); ppc_setCRBit(hCPU, crBitBase + 1, (nibble >> 2) & 1); ppc_setCRBit(hCPU, crBitBase + 2, (nibble >> 1) & 1); ppc_setCRBit(hCPU, crBitBase + 3, (nibble >> 0) & 1); } PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_MCRXR(PPCInterpreter_t* hCPU, uint32 Opcode) { // used in Dont Starve: Giant Edition // also used frequently by Web Browser (webkit?) uint32 cr; cr = (Opcode >> (31 - 8)) & 7; cr >>= 2; uint32 xer = PPCInterpreter_getXER(hCPU); uint32 xerBits = (xer >> 28) & 0xF; // todo - is the order correct? ppc_setCRBit(hCPU, cr * 4 + 0, (xerBits >> 0) & 1); ppc_setCRBit(hCPU, cr * 4 + 1, (xerBits >> 1) & 1); ppc_setCRBit(hCPU, cr * 4 + 2, (xerBits >> 2) & 1); ppc_setCRBit(hCPU, cr * 4 + 3, (xerBits >> 3) & 1); // reset copied bits PPCInterpreter_setXER(hCPU, xer&~0xF0000000); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_TLBIE(PPCInterpreter_t* hCPU, uint32 Opcode) { int rS, rA, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_TLBSYNC(PPCInterpreter_t* hCPU, uint32 Opcode) { cemu_assert_unimplemented(); PPCInterpreter_nextInstruction(hCPU); } // branch instructions void PPCInterpreter_BX(PPCInterpreter_t* hCPU, uint32 Opcode) { uint32 li; PPC_OPC_TEMPL_I(Opcode, li); if ((Opcode & PPC_OPC_AA) == 0) li += (unsigned int)hCPU->instructionPointer; if (Opcode & PPC_OPC_LK) { // update LR and IP hCPU->spr.LR = (unsigned int)hCPU->instructionPointer + 4; hCPU->instructionPointer = li; PPCInterpreter_jumpToInstruction(hCPU, li); PPCRecompiler_attemptEnter(hCPU, li); return; } PPCInterpreter_jumpToInstruction(hCPU, li); } void PPCInterpreter_BCX(PPCInterpreter_t* hCPU, uint32 Opcode) { uint32 BO, BI, BD; PPC_OPC_TEMPL_B(Opcode, BO, BI, BD); if (!(BO & 4)) hCPU->spr.CTR--; bool bo2 = (BO & 2) != 0; bool bo8 = (BO & 8) != 0; // branch condition true bool cr = ppc_getCRBit(hCPU, BI) != 0; if (((BO & 4) || ((hCPU->spr.CTR != 0) ^ bo2)) && ((BO & 16) || (!(cr ^ bo8)))) { if (!(Opcode & PPC_OPC_AA)) { BD += (unsigned int)hCPU->instructionPointer; } else { // should never happen cemu_assert_unimplemented(); } if (Opcode & PPC_OPC_LK) hCPU->spr.LR = ((unsigned int)hCPU->instructionPointer) + 4; PPCInterpreter_jumpToInstruction(hCPU, BD); } else PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_BCLRX(PPCInterpreter_t* hCPU, uint32 Opcode) { uint32 BO, BI, BD; PPC_OPC_TEMPL_XL(Opcode, BO, BI, BD); PPC_ASSERT(BD == 0); if (!(BO & 4)) { if (hCPU->spr.CTR == 0) { PPC_ASSERT(true); cemuLog_logDebug(LogType::Force, "Decrementer underflow!"); } hCPU->spr.CTR--; } bool bo2 = (BO & 2) ? true : false; bool bo8 = (BO & 8) ? true : false; bool cr = ppc_getCRBit(hCPU, BI) != 0; if (((BO & 4) || ((hCPU->spr.CTR != 0) ^ bo2)) && ((BO & 16) || (!(cr ^ bo8)))) { BD = hCPU->spr.LR & 0xfffffffc; if (Opcode & PPC_OPC_LK) { hCPU->spr.LR = (unsigned int)hCPU->instructionPointer + 4; } PPCInterpreter_jumpToInstruction(hCPU, BD); PPCRecompiler_attemptEnter(hCPU, BD); return; } else { BD = (unsigned int)hCPU->instructionPointer + 4; PPCInterpreter_nextInstruction(hCPU); } } void PPCInterpreter_BCCTR(PPCInterpreter_t* hCPU, uint32 Opcode) { uint32 x = (unsigned int)hCPU->instructionPointer; uint32 BO, BI, BD; PPC_OPC_TEMPL_XL(Opcode, BO, BI, BD); PPC_ASSERT(BD == 0); PPC_ASSERT(!(BO & 2)); bool bo8 = (BO & 8) ? true : false; bool cr = ppc_getCRBit(hCPU, BI) != 0; if ((BO & 16) || (!(cr ^ bo8))) { if (Opcode & PPC_OPC_LK) { hCPU->spr.LR = (unsigned int)hCPU->instructionPointer + 4; hCPU->instructionPointer = (unsigned int)(hCPU->spr.CTR & 0xfffffffc); } else { hCPU->instructionPointer = (unsigned int)(hCPU->spr.CTR & 0xfffffffc); } PPCRecompiler_attemptEnter(hCPU, hCPU->instructionPointer); } else { hCPU->instructionPointer += 4; } } void PPCInterpreter_DCBT(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rB; rA = (Opcode >> (31 - 15)) & 0x1F; rB = (Opcode >> (31 - 20)) & 0x1F; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_DCBST(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rB; rA = (Opcode >> (31 - 15)) & 0x1F; rB = (Opcode >> (31 - 20)) & 0x1F; uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; LatteBufferCache_notifyDCFlush(ea, 32); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_DCBF(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rA, rB; rA = (Opcode >> (31 - 15)) & 0x1F; rB = (Opcode >> (31 - 20)) & 0x1F; uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; LatteBufferCache_notifyDCFlush(ea, 32); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_DCBZL(PPCInterpreter_t* hCPU, uint32 Opcode) //Undocumented { // no-op PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_DCBI(PPCInterpreter_t* hCPU, uint32 Opcode) { // no-op PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_ICBI(PPCInterpreter_t* hCPU, uint32 Opcode) { sint32 rD, rA, rB; PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; // invalidate range coreinit::codeGenHandleICBI(ea); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_EIEIO(PPCInterpreter_t* hCPU, uint32 Opcode) { // no effect // next instruction PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_SC(PPCInterpreter_t* hCPU, uint32 Opcode) { cemuLog_logDebug(LogType::Force, "SC executed at 0x{:08x}", hCPU->instructionPointer); // next instruction PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_SYNC(PPCInterpreter_t* hCPU, uint32 Opcode) { // no-op // next instruction PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_ISYNC(PPCInterpreter_t* hCPU, uint32 Opcode) { // no-op // next instruction PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_RFI(PPCInterpreter_t* hCPU, uint32 Opcode) { cemuLog_logDebug(LogType::Force, "RFI"); hCPU->sprExtended.msr &= ~(0x87C0FF73 | 0x00040000); hCPU->sprExtended.msr |= hCPU->sprExtended.srr1 & 0x87c0ff73; hCPU->sprExtended.msr |= MSR_RI; hCPU->instructionPointer = (unsigned int)(hCPU->sprExtended.srr0); } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.hpp ================================================ static void PPCInterpreter_MFSPR(PPCInterpreter_t* hCPU, uint32 opcode) { uint32 rD, spr1, spr2, spr; PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); spr = spr1 | (spr2 << 5); // copy SPR hCPU->gpr[rD] = PPCSpr_get(hCPU, spr); // next instruction PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_MTSPR(PPCInterpreter_t* hCPU, uint32 opcode) { uint32 rD, spr1, spr2, spr; PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); spr = spr1 | (spr2 << 5); PPCSpr_set(hCPU, spr, hCPU->gpr[rD]); // next instruction PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_MFSR(PPCInterpreter_t* hCPU, uint32 opcode) { uint32 rD, SR, rB; PPC_OPC_TEMPL_X(opcode, rD, SR, rB); hCPU->gpr[rD] = getSR(hCPU, SR & 0xF); // next instruction PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_MTSR(PPCInterpreter_t* hCPU, uint32 opcode) { uint32 rS, SR, rB; PPC_OPC_TEMPL_X(opcode, rS, SR, rB); setSR(hCPU, SR&0xF, hCPU->gpr[rS]); // next instruction PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_MFTB(PPCInterpreter_t* hCPU, uint32 opcode) { uint32 rD, spr1, spr2, spr; // get SPR ID PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); spr = spr1 | (spr2 << 5); // get core cycle counter uint64 coreTime = ppcItpCtrl::getTB(hCPU); switch (spr) { case 268: // TBL hCPU->gpr[rD] = (uint32)(coreTime & 0xFFFFFFFF); break; case 269: // TBU hCPU->gpr[rD] = (uint32)((coreTime >> 32) & 0xFFFFFFFF); break; default: assert_dbg(); } // next instruction PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_TW(PPCInterpreter_t* hCPU, uint32 opcode) { sint32 to, rA, rB; PPC_OPC_TEMPL_X(opcode, to, rA, rB); cemu_assert_debug(to == 0); if(to != 0) PPCInterpreter_nextInstruction(hCPU); if (rA == DEBUGGER_BP_T_DEBUGGER) debugger_enterTW(hCPU); else if (rA == DEBUGGER_BP_T_GDBSTUB) g_gdbstub->HandleTrapInstruction(hCPU); } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterPS.cpp ================================================ #include "PPCInterpreterInternal.h" // Gekko paired single math void PPCInterpreter_PS_ADD(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; hCPU->fpr[frD].fp0 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp0); hCPU->fpr[frD].fp1 = (float)(hCPU->fpr[frA].fp1 + hCPU->fpr[frB].fp1); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_SUB(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; hCPU->fpr[frD].fp0 = (float)(hCPU->fpr[frA].fp0 - hCPU->fpr[frB].fp0); hCPU->fpr[frD].fp1 = (float)(hCPU->fpr[frA].fp1 - hCPU->fpr[frB].fp1); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MUL(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frC; frC = (Opcode>>6)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; hCPU->fpr[frD].fp0 = flushDenormalToZero((float)(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0))); hCPU->fpr[frD].fp1 = flushDenormalToZero((float)(hCPU->fpr[frA].fp1 * roundTo25BitAccuracy(hCPU->fpr[frC].fp1))); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_DIV(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; hCPU->fpr[frD].fp0 = (float)(hCPU->fpr[frA].fp0 / hCPU->fpr[frB].fp0); hCPU->fpr[frD].fp1 = (float)(hCPU->fpr[frA].fp1 / hCPU->fpr[frB].fp1); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MADD(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB, frC; frC = (Opcode>>6)&0x1F; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; float s0 = (float)((float)(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0)) + hCPU->fpr[frB].fp0); float s1 = (float)((float)(hCPU->fpr[frA].fp1 * roundTo25BitAccuracy(hCPU->fpr[frC].fp1)) + hCPU->fpr[frB].fp1); hCPU->fpr[frD].fp0 = flushDenormalToZero(s0); hCPU->fpr[frD].fp1 = flushDenormalToZero(s1); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_NMADD(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB, frC; frC = (Opcode>>6)&0x1F; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; float s0 = (float)-(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0) + hCPU->fpr[frB].fp0); float s1 = (float)-(hCPU->fpr[frA].fp1 * roundTo25BitAccuracy(hCPU->fpr[frC].fp1) + hCPU->fpr[frB].fp1); hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MSUB(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB, frC; frC = (Opcode >> 6) & 0x1F; frB = (Opcode >> 11) & 0x1F; frA = (Opcode >> 16) & 0x1F; frD = (Opcode >> 21) & 0x1F; float s0 = (float)(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0) - hCPU->fpr[frB].fp0); float s1 = (float)(hCPU->fpr[frA].fp1 * roundTo25BitAccuracy(hCPU->fpr[frC].fp1) - hCPU->fpr[frB].fp1); hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_NMSUB(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB, frC; frC = (Opcode >> 6) & 0x1F; frB = (Opcode >> 11) & 0x1F; frA = (Opcode >> 16) & 0x1F; frD = (Opcode >> 21) & 0x1F; float s0 = (float)-(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0) - hCPU->fpr[frB].fp0); float s1 = (float)-(hCPU->fpr[frA].fp1 * roundTo25BitAccuracy(hCPU->fpr[frC].fp1) - hCPU->fpr[frB].fp1); hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MADDS0(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB, frC; frC = (Opcode>>6)&0x1F; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; double c = roundTo25BitAccuracy(hCPU->fpr[frC].fp0); float s0 = (float)(hCPU->fpr[frA].fp0 * c + hCPU->fpr[frB].fp0); float s1 = (float)(hCPU->fpr[frA].fp1 * c + hCPU->fpr[frB].fp1); hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MADDS1(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB, frC; frC = (Opcode>>6)&0x1F; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; double c = roundTo25BitAccuracy(hCPU->fpr[frC].fp1); float s0 = (float)(hCPU->fpr[frA].fp0 * c + hCPU->fpr[frB].fp0); float s1 = (float)(hCPU->fpr[frA].fp1 * c + hCPU->fpr[frB].fp1); hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_SEL(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB, frC; frC = (Opcode>>6)&0x1F; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; if( hCPU->fpr[frA].fp0 >= -0.0f ) hCPU->fpr[frD].fp0 = hCPU->fpr[frC].fp0; else hCPU->fpr[frD].fp0 = hCPU->fpr[frB].fp0; if( hCPU->fpr[frA].fp1 >= -0.0f ) hCPU->fpr[frD].fp1 = hCPU->fpr[frC].fp1; else hCPU->fpr[frD].fp1 = hCPU->fpr[frB].fp1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_SUM0(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB, frC; frC = (Opcode>>6)&0x1F; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; float s0 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); float s1 = (float)hCPU->fpr[frC].fp1; hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_SUM1(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB, frC; frC = (Opcode>>6)&0x1F; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; float s0 = (float)hCPU->fpr[frC].fp0; float s1 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MULS0(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frC; frC = (Opcode>>6)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; double c = roundTo25BitAccuracy(hCPU->fpr[frC].fp0); float s0 = (float)(hCPU->fpr[frA].fp0 * c); float s1 = (float)(hCPU->fpr[frA].fp1 * c); hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MULS1(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frC; frC = (Opcode>>6)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; double c = roundTo25BitAccuracy(hCPU->fpr[frC].fp1); float s0 = (float)(hCPU->fpr[frA].fp0 * c); float s1 = (float)(hCPU->fpr[frA].fp1 * c); hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MR(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frB; frB = (Opcode>>11)&0x1F; frD = (Opcode>>21)&0x1F; hCPU->fpr[frD].fp0 = hCPU->fpr[frB].fp0; hCPU->fpr[frD].fp1 = hCPU->fpr[frB].fp1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_NEG(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frB; frB = (Opcode>>11)&0x1F; frD = (Opcode>>21)&0x1F; hCPU->fpr[frD].fp0 = -hCPU->fpr[frB].fp0; hCPU->fpr[frD].fp1 = -hCPU->fpr[frB].fp1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_ABS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frB; frB = (Opcode>>11)&0x1F; frD = (Opcode>>21)&0x1F; hCPU->fpr[frD].fp0int = hCPU->fpr[frB].fp0int & ~(1ULL << 63); hCPU->fpr[frD].fp1int = hCPU->fpr[frB].fp1int & ~(1ULL << 63); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_NABS(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frB; frB = (Opcode>>11)&0x1F; frD = (Opcode>>21)&0x1F; hCPU->fpr[frD].fp0int = hCPU->fpr[frB].fp0int | (1ULL << 63); hCPU->fpr[frD].fp1int = hCPU->fpr[frB].fp1int | (1ULL << 63); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_RSQRTE(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frB; frB = (Opcode>>11)&0x1F; frD = (Opcode>>21)&0x1F; hCPU->fpr[frD].fp0 = (float)frsqrte_espresso(hCPU->fpr[frB].fp0); hCPU->fpr[frD].fp1 = (float)frsqrte_espresso(hCPU->fpr[frB].fp1); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MERGE00(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; double s0 = hCPU->fpr[frA].fp0; double s1 = hCPU->fpr[frB].fp0; hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MERGE01(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; double s0 = hCPU->fpr[frA].fp0; double s1 = hCPU->fpr[frB].fp1; hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MERGE10(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; double s0 = hCPU->fpr[frA].fp1; double s1 = hCPU->fpr[frB].fp0; hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_MERGE11(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frA, frB; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; frD = (Opcode>>21)&0x1F; double s0 = hCPU->fpr[frA].fp1; double s1 = hCPU->fpr[frB].fp1; hCPU->fpr[frD].fp0 = s0; hCPU->fpr[frD].fp1 = s1; PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_RES(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 frD, frB; frB = (Opcode>>11)&0x1F; frD = (Opcode>>21)&0x1F; hCPU->fpr[frD].fp0 = (float)fres_espresso(hCPU->fpr[frB].fp0); hCPU->fpr[frD].fp1 = (float)fres_espresso(hCPU->fpr[frB].fp1); PPCInterpreter_nextInstruction(hCPU); } // PS compare void PPCInterpreter_PS_CMPO0(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 crfD, frA, frB; uint32 c=0; frB = (Opcode>>11)&0x1F; frA = (Opcode>>16)&0x1F; crfD = (Opcode>>23)&0x7; double a = hCPU->fpr[frA].fp0; double b = hCPU->fpr[frB].fp0; ppc_setCRBit(hCPU, crfD*4+0, 0); ppc_setCRBit(hCPU, crfD*4+1, 0); ppc_setCRBit(hCPU, crfD*4+2, 0); ppc_setCRBit(hCPU, crfD*4+3, 0); if(IS_NAN(*(uint64*)&a) || IS_NAN(*(uint64*)&b)) { c = 1; ppc_setCRBit(hCPU, crfD*4+CR_BIT_SO, 1); } else if(a < b) { c = 8; ppc_setCRBit(hCPU, crfD*4+CR_BIT_LT, 1); } else if(a > b) { c = 4; ppc_setCRBit(hCPU, crfD*4+CR_BIT_GT, 1); } else { c = 2; ppc_setCRBit(hCPU, crfD*4+CR_BIT_EQ, 1); } hCPU->fpscr = (hCPU->fpscr & 0xffff0fff) | (c << 12); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_CMPU0(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 crfD, frA, frB; frB = (Opcode >> 11) & 0x1F; frA = (Opcode >> 16) & 0x1F; crfD = (Opcode >> 21) & (0x7<<2); fcmpu_espresso(hCPU, crfD, hCPU->fpr[frA].fp0, hCPU->fpr[frB].fp0); PPCInterpreter_nextInstruction(hCPU); } void PPCInterpreter_PS_CMPU1(PPCInterpreter_t* hCPU, uint32 Opcode) { FPUCheckAvailable(); sint32 crfD, frA, frB; frB = (Opcode >> 11) & 0x1F; frA = (Opcode >> 16) & 0x1F; crfD = (Opcode >> 21) & (0x7 << 2); double a = hCPU->fpr[frA].fp1; double b = hCPU->fpr[frB].fp1; fcmpu_espresso(hCPU, crfD, hCPU->fpr[frA].fp1, hCPU->fpr[frB].fp1); PPCInterpreter_nextInstruction(hCPU); } ================================================ FILE: src/Cafe/HW/Espresso/Interpreter/PPCInterpreterSPR.hpp ================================================ #define SPR_TBL_WRITE (284) #define SPR_TBU_WRITE (285) #define SPR_DBATU_0 (536) #define SPR_DBATU_1 (538) #define SPR_DBATU_2 (540) #define SPR_DBATU_3 (542) #define SPR_DBATU_4 (568) #define SPR_DBATU_5 (570) #define SPR_DBATU_6 (572) #define SPR_DBATU_7 (574) #define SPR_DBATL_0 (537) #define SPR_DBATL_1 (539) #define SPR_DBATL_2 (541) #define SPR_DBATL_3 (543) #define SPR_DBATL_4 (569) #define SPR_DBATL_5 (571) #define SPR_DBATL_6 (573) #define SPR_DBATL_7 (575) #define SPR_IBATU_0 (528) #define SPR_IBATU_1 (530) #define SPR_IBATU_2 (532) #define SPR_IBATU_3 (534) #define SPR_IBATU_4 (560) #define SPR_IBATU_5 (562) #define SPR_IBATU_6 (564) #define SPR_IBATU_7 (566) #define SPR_IBATL_0 (529) #define SPR_IBATL_1 (531) #define SPR_IBATL_2 (533) #define SPR_IBATL_3 (535) #define SPR_IBATL_4 (561) #define SPR_IBATL_5 (563) #define SPR_IBATL_6 (565) #define SPR_IBATL_7 (567) #define SPR_DSISR (18) #define SPR_DAR (19) #define SPR_SPRG0 (272) #define SPR_SPRG1 (273) #define SPR_SPRG2 (274) #define SPR_SPRG3 (275) //#define SPR_HID0 (1008) //#define SPR_HID2 (920) #define SPR_HID4 (1011) #define SPR_HID5 (944) #define SPR_L2CR (1017) // L2 cache control #define SPR_CAR (948) // global #define SPR_BCR (949) // global static uint32 getPVR(PPCInterpreter_t* hCPU) { return 0x70010101; // guessed } static uint32 getFPECR(PPCInterpreter_t* hCPU) { return hCPU->sprExtended.fpecr; } static void setFPECR(PPCInterpreter_t* hCPU, uint32 newValue) { hCPU->sprExtended.fpecr = newValue; } static void setDEC(PPCInterpreter_t* hCPU, uint32 newValue) { debug_printf("Set DEC to 0x%08x\n", newValue); //hCPU->sprExtended.fpecr = newValue; } static uint32 getSPRG(PPCInterpreter_t* hCPU, uint32 sprgIndex) { return hCPU->sprExtended.sprg[sprgIndex]; } static void setSPRG(PPCInterpreter_t* hCPU, uint32 sprgIndex, uint32 newValue) { hCPU->sprExtended.sprg[sprgIndex] = newValue; } static uint32 getDAR(PPCInterpreter_t* hCPU) { return hCPU->sprExtended.dar; } static uint32 getDSISR(PPCInterpreter_t* hCPU) { return hCPU->sprExtended.dsisr; } static uint32 getHID0(PPCInterpreter_t* hCPU) { return 0; // todo } static void setHID0(PPCInterpreter_t* hCPU, uint32 newValue) { // todo debug_printf("Set HID0 to 0x%08x\n", newValue); } static uint32 getHID1(PPCInterpreter_t* hCPU) { debug_printf("Get HID1 IP 0x%08x\n", hCPU->instructionPointer); return 0; // todo } static uint32 getHID2(PPCInterpreter_t* hCPU) { debug_printf("Get HID2 IP 0x%08x\n", hCPU->instructionPointer); return 0; // todo } static void setHID2(PPCInterpreter_t* hCPU, uint32 newValue) { // todo debug_printf("Set HID2 to 0x%08x\n", newValue); } static uint32 getHID4(PPCInterpreter_t* hCPU) { debug_printf("Get HID4 IP 0x%08x\n", hCPU->instructionPointer); return 0; // todo } static void setHID4(PPCInterpreter_t* hCPU, uint32 newValue) { // todo debug_printf("Set HID4 to 0x%08x\n", newValue); } static uint32 getHID5(PPCInterpreter_t* hCPU) { // Wii-U only debug_printf("Get HID5 IP 0x%08x\n", hCPU->instructionPointer); return 0; // todo } static void setHID5(PPCInterpreter_t* hCPU, uint32 newValue) { // Wii-U only // todo debug_printf("Set HID5 to 0x%08x\n", newValue); } static uint32 getSCR(PPCInterpreter_t* hCPU) { // WiiU mode only? return 0; // todo } static void setSCR(PPCInterpreter_t* hCPU, uint32 newValue) { uint32 previousSCR = hCPU->global->sprGlobal.scr; newValue |= (previousSCR&0x80000000); // this bit always sticks? if ((previousSCR&0x80000000) == 0 && (newValue & 0x80000000) != 0) { // this bit is used to disable bootrom mapping, but we use it to know when to copy the decrypted ancast image into kernel memory debug_printf("SCR MSB set. Unmap bootrom?\n"); //memcpy(memory_base + 0xFFE00000, memory_base + 0x08000000, 0x180000); // hack - clear low memory (where bootrom was mapped/loaded) memset(memory_base, 0, 0x4000); //// todo - normally IOSU sets up some stuff here (probably) // for debugging purposes make lowest page read-only #ifdef _WIN32 DWORD oldProtect; VirtualProtect(memory_base, 0x1000, PAGE_READONLY, &oldProtect); #endif } debug_printf("Set SCR to 0x%08x\n", newValue); hCPU->global->sprGlobal.scr = newValue; } // SCR probably has bits to control following: // disable bootrom (bit 0x80000000) // disable PPC OTP // bits to start the extra cores static uint32 getCAR(PPCInterpreter_t* hCPU) { // global // WiiU mode only return 0; // todo } static void setCAR(PPCInterpreter_t* hCPU, uint32 newValue) { // global // WiiU mode only debug_printf("Set CAR to 0x%08x\n", newValue); } static uint32 getBCR(PPCInterpreter_t* hCPU) { // global // WiiU mode only return 0; // todo } static void setBCR(PPCInterpreter_t* hCPU, uint32 newValue) { // global // WiiU mode only debug_printf("Set BCR to 0x%08x\n", newValue); } static uint32 getL2CR(PPCInterpreter_t* hCPU) { return 0; // todo } static void setL2CR(PPCInterpreter_t* hCPU, uint32 newValue) { // todo } static void setSRR0(PPCInterpreter_t* hCPU, uint32 newValue) { hCPU->sprExtended.srr0 = newValue; } static void setSRR1(PPCInterpreter_t* hCPU, uint32 newValue) { hCPU->sprExtended.srr1 = newValue; } static void setDMAU(PPCInterpreter_t* hCPU, uint32 newValue) { hCPU->sprExtended.dmaU = newValue; } static void setDMAL(PPCInterpreter_t* hCPU, uint32 newValue) { hCPU->sprExtended.dmaL = newValue; // LC DMA if(newValue &0x2 ) { uint32 transferLength = (((hCPU->sprExtended.dmaU>>0)&0x1F)<<2)|((newValue>>2)&3); uint32 memAddr = (hCPU->sprExtended.dmaU)&0xFFFFFFE0; uint32 cacheAddr = (newValue)&0xFFFFFFE0; if( transferLength == 0 ) transferLength = 128; transferLength *= 32; bool isLoad = ((newValue>>4)&1)!=0; if( (cacheAddr>>28) != 0xE ) { debug_printf("LCTransfer: Not a cache address\n"); cacheAddr = 0; } else { cacheAddr -= 0xE0000000; } if( isLoad == 0 ) { // locked cache -> memory debug_printf("L2->MEM %08x -> %08x size: 0x%x\n", memAddr, 0xE0000000 + cacheAddr, transferLength); memcpy(memory_getPointerFromVirtualOffset(memAddr), memory_base+0xE0000000+cacheAddr, transferLength); } else { // memory -> locked cache debug_printf("MEM->L2 %08x -> %08x size: 0x%x\n", 0xE0000000 + cacheAddr, memAddr, transferLength); memcpy(memory_base + 0xE0000000 + cacheAddr, memory_getPointerFromVirtualOffset(memAddr), transferLength); } newValue &= ~2; hCPU->sprExtended.dmaL = newValue; } } static void setDBATL(PPCInterpreter_t* hCPU, uint32 index, uint32 newValue) { debug_printf("Set DBATL%d to 0x%08x\n", index, newValue); hCPU->sprExtended.dbatL[index] = newValue; } static void setDBATU(PPCInterpreter_t* hCPU, uint32 index, uint32 newValue) { debug_printf("Set DBATU%d to 0x%08x\n", index, newValue); hCPU->sprExtended.dbatU[index] = newValue; } static void setIBATL(PPCInterpreter_t* hCPU, uint32 index, uint32 newValue) { debug_printf("Set IBATL%d to 0x%08x\n", index, newValue); hCPU->sprExtended.ibatL[index] = newValue; } static void setIBATU(PPCInterpreter_t* hCPU, uint32 index, uint32 newValue) { debug_printf("Set IBATU%d to 0x%08x\n", index, newValue); hCPU->sprExtended.ibatU[index] = newValue; } static uint32 getDBATL(PPCInterpreter_t* hCPU, uint32 index) { return hCPU->sprExtended.dbatL[index]; } static uint32 getDBATU(PPCInterpreter_t* hCPU, uint32 index) { return hCPU->sprExtended.dbatU[index]; } static uint32 getIBATL(PPCInterpreter_t* hCPU, uint32 index) { return hCPU->sprExtended.ibatL[index]; } static uint32 getIBATU(PPCInterpreter_t* hCPU, uint32 index) { return hCPU->sprExtended.ibatU[index]; } static void setSR(PPCInterpreter_t* hCPU, uint32 index, uint32 newValue) { debug_printf("Set SR%d to 0x%08x IP %08x LR %08x\n", index, newValue, hCPU->instructionPointer, hCPU->spr.LR); hCPU->sprExtended.sr[index] = newValue; } static uint32 getSR(PPCInterpreter_t* hCPU, uint32 index) { return hCPU->sprExtended.sr[index]; } static void setSDR1(PPCInterpreter_t* hCPU, uint32 newValue) { debug_printf("Set SDR1 to 0x%08x\n", newValue); hCPU->sprExtended.sdr1 = newValue; } static void setTBL(PPCInterpreter_t* hCPU, uint32 newValue) { if (newValue != 0) assert_dbg(); debug_printf("Reset TB\n"); hCPU->global->tb = 0; } static void setTBU(PPCInterpreter_t* hCPU, uint32 newValue) { if (newValue != 0) assert_dbg(); debug_printf("Reset TB\n"); hCPU->global->tb = 0; } static void PPCSprSupervisor_set(PPCInterpreter_t* hCPU, uint32 spr, uint32 newValue) { switch (spr) { case SPR_LR: hCPU->spr.LR = newValue; break; case SPR_CTR: hCPU->spr.CTR = newValue; break; case SPR_DEC: setDEC(hCPU, newValue); break; case SPR_XER: PPCInterpreter_setXER(hCPU, newValue); break; case SPR_UGQR0: case SPR_UGQR1: case SPR_UGQR2: case SPR_UGQR3: case SPR_UGQR4: case SPR_UGQR5: case SPR_UGQR6: case SPR_UGQR7: hCPU->spr.UGQR[spr - SPR_UGQR0] = newValue; break; // values above are user mode accessible case SPR_TBL_WRITE: // TBL setTBL(hCPU, newValue); break; case SPR_TBU_WRITE: // TBU setTBU(hCPU, newValue); break; case SPR_FPECR: setFPECR(hCPU, newValue); break; case SPR_HID0: setHID0(hCPU, newValue); break; case SPR_HID2: setHID2(hCPU, newValue); break; case SPR_HID4: setHID4(hCPU, newValue); break; case SPR_HID5: setHID5(hCPU, newValue); break; case SPR_L2CR: setL2CR(hCPU, newValue); break; case SPR_SRR0: setSRR0(hCPU, newValue); break; case SPR_SRR1: setSRR1(hCPU, newValue); break; case SPR_SPRG0: setSPRG(hCPU, 0, newValue); break; case SPR_SPRG1: setSPRG(hCPU, 1, newValue); break; case SPR_SPRG2: setSPRG(hCPU, 2, newValue); break; case SPR_SPRG3: setSPRG(hCPU, 3, newValue); break; case SPR_SCR: setSCR(hCPU, newValue); break; case SPR_CAR: setCAR(hCPU, newValue); break; case SPR_BCR: setBCR(hCPU, newValue); break; case SPR_DMAU: setDMAU(hCPU, newValue); break; case SPR_DMAL: setDMAL(hCPU, newValue); break; case SPR_DBATU_0: setDBATU(hCPU, 0, newValue); break; case SPR_DBATU_1: setDBATU(hCPU, 1, newValue); break; case SPR_DBATU_2: setDBATU(hCPU, 2, newValue); break; case SPR_DBATU_3: setDBATU(hCPU, 3, newValue); break; case SPR_DBATU_4: setDBATU(hCPU, 4, newValue); break; case SPR_DBATU_5: setDBATU(hCPU, 5, newValue); break; case SPR_DBATU_6: setDBATU(hCPU, 6, newValue); break; case SPR_DBATU_7: setDBATU(hCPU, 7, newValue); break; case SPR_DBATL_0: setDBATL(hCPU, 0, newValue); break; case SPR_DBATL_1: setDBATL(hCPU, 1, newValue); break; case SPR_DBATL_2: setDBATL(hCPU, 2, newValue); break; case SPR_DBATL_3: setDBATL(hCPU, 3, newValue); break; case SPR_DBATL_4: setDBATL(hCPU, 4, newValue); break; case SPR_DBATL_5: setDBATL(hCPU, 5, newValue); break; case SPR_DBATL_6: setDBATL(hCPU, 6, newValue); break; case SPR_DBATL_7: setDBATL(hCPU, 7, newValue); break; case SPR_IBATU_0: setIBATU(hCPU, 0, newValue); break; case SPR_IBATU_1: setIBATU(hCPU, 1, newValue); break; case SPR_IBATU_2: setIBATU(hCPU, 2, newValue); break; case SPR_IBATU_3: setIBATU(hCPU, 3, newValue); break; case SPR_IBATU_4: setIBATU(hCPU, 4, newValue); break; case SPR_IBATU_5: setIBATU(hCPU, 5, newValue); break; case SPR_IBATU_6: setIBATU(hCPU, 6, newValue); break; case SPR_IBATU_7: setIBATU(hCPU, 7, newValue); break; case SPR_IBATL_0: setIBATL(hCPU, 0, newValue); break; case SPR_IBATL_1: setIBATL(hCPU, 1, newValue); break; case SPR_IBATL_2: setIBATL(hCPU, 2, newValue); break; case SPR_IBATL_3: setIBATL(hCPU, 3, newValue); break; case SPR_IBATL_4: setIBATL(hCPU, 4, newValue); break; case SPR_IBATL_5: setIBATL(hCPU, 5, newValue); break; case SPR_IBATL_6: setIBATL(hCPU, 6, newValue); break; case SPR_IBATL_7: setIBATL(hCPU, 7, newValue); break; case SPR_SDR1: setSDR1(hCPU, newValue); break; case 0x3B8: // mmcr0 debug_printf("Write performance monitor SPR mmcr0 0x%08x", newValue); break; case 0x3B9: // PMC1 debug_printf("Write performance monitor SPR PMC1 0x%08x", newValue); break; case 0x3BA: // PMC2 debug_printf("Write performance monitor SPR PMC2 0x%08x", newValue); break; case 0x3BC: // mmcr1 debug_printf("Write performance monitor SPR mmcr1 0x%08x", newValue); break; case 0x3BD: // PMC3 debug_printf("Write performance monitor SPR PMC3 0x%08x", newValue); break; case 0x3BE: // PMC4 debug_printf("Write performance monitor SPR PMC4 0x%08x", newValue); break; default: debug_printf("[C%d] Set unhandled SPR 0x%x to %08x (supervisor mode)\n", hCPU->spr.UPIR, spr, newValue); #ifdef CEMU_DEBUG_ASSERT assert_dbg(); #endif break; } } static void PPCSpr_set(PPCInterpreter_t* hCPU, uint32 spr, uint32 newValue) { if constexpr(ppcItpCtrl::allowSupervisorMode) { // todo - check if in supervisor mode or user mode PPCSprSupervisor_set(hCPU, spr, newValue); return; } switch (spr) { case SPR_LR: hCPU->spr.LR = newValue; break; case SPR_CTR: hCPU->spr.CTR = newValue; break; case SPR_XER: PPCInterpreter_setXER(hCPU, newValue); break; case SPR_UGQR0: case SPR_UGQR1: case SPR_UGQR2: case SPR_UGQR3: case SPR_UGQR4: case SPR_UGQR5: case SPR_UGQR6: case SPR_UGQR7: hCPU->spr.UGQR[spr - SPR_UGQR0] = newValue; break; default: debug_printf("[C%d] Set unhandled SPR %d to %08x\n", hCPU->spr.UPIR, spr, newValue); #ifdef CEMU_DEBUG_ASSERT assert_dbg(); #endif break; } } static uint32 PPCSprSupervisor_get(PPCInterpreter_t* hCPU, uint32 spr) { uint32 v = 0; switch (spr) { case SPR_LR: v = hCPU->spr.LR; break; case SPR_CTR: v = hCPU->spr.CTR; break; case SPR_XER: v = PPCInterpreter_getXER(hCPU); break; case SPR_UPIR: v = hCPU->spr.UPIR; break; case SPR_UGQR0: case SPR_UGQR1: case SPR_UGQR2: case SPR_UGQR3: case SPR_UGQR4: case SPR_UGQR5: case SPR_UGQR6: case SPR_UGQR7: v = hCPU->spr.UGQR[spr - SPR_UGQR0]; break; // above are registers accessible in user mode case SPR_PVR: v = getPVR(hCPU); break; case SPR_HID0: v = getHID0(hCPU); break; case SPR_HID1: v = getHID1(hCPU); break; case SPR_HID2: v = getHID2(hCPU); break; case SPR_HID4: v = getHID4(hCPU); break; case SPR_HID5: v = getHID5(hCPU); break; case SPR_SCR: v = getSCR(hCPU); break; case SPR_CAR: v = getCAR(hCPU); break; case SPR_BCR: v = getBCR(hCPU); break; case SPR_DAR: v = getDAR(hCPU); break; case SPR_DSISR: v = getDSISR(hCPU); break; case SPR_L2CR: v = getL2CR(hCPU); break; case SPR_FPECR: v = getFPECR(hCPU); break; case SPR_SPRG0: v = getSPRG(hCPU, 0); break; case SPR_SPRG1: v = getSPRG(hCPU, 1); break; case SPR_SPRG2: v = getSPRG(hCPU, 2); break; case SPR_SPRG3: v = getSPRG(hCPU, 3); break; case SPR_DBATU_0: v = getDBATU(hCPU, 0); break; case SPR_DBATU_1: v = getDBATU(hCPU, 1); break; case SPR_DBATU_2: v = getDBATU(hCPU, 2); break; case SPR_DBATU_3: v = getDBATU(hCPU, 3); break; case SPR_DBATU_4: v = getDBATU(hCPU, 4); break; case SPR_DBATU_5: v = getDBATU(hCPU, 5); break; case SPR_DBATU_6: v = getDBATU(hCPU, 6); break; case SPR_DBATU_7: v = getDBATU(hCPU, 7); break; case SPR_DBATL_0: v = getDBATL(hCPU, 0); break; case SPR_DBATL_1: v = getDBATL(hCPU, 1); break; case SPR_DBATL_2: v = getDBATL(hCPU, 2); break; case SPR_DBATL_3: v = getDBATL(hCPU, 3); break; case SPR_DBATL_4: v = getDBATL(hCPU, 4); break; case SPR_DBATL_5: v = getDBATL(hCPU, 5); break; case SPR_DBATL_6: v = getDBATL(hCPU, 6); break; case SPR_DBATL_7: v = getDBATL(hCPU, 7); break; case SPR_IBATU_0: v = getIBATU(hCPU, 0); break; case SPR_IBATU_1: v = getIBATU(hCPU, 1); break; case SPR_IBATU_2: v = getIBATU(hCPU, 2); break; case SPR_IBATU_3: v = getIBATU(hCPU, 3); break; case SPR_IBATU_4: v = getIBATU(hCPU, 4); break; case SPR_IBATU_5: v = getIBATU(hCPU, 5); break; case SPR_IBATU_6: v = getIBATU(hCPU, 6); break; case SPR_IBATU_7: v = getIBATU(hCPU, 7); break; case SPR_IBATL_0: v = getIBATL(hCPU, 0); break; case SPR_IBATL_1: v = getIBATL(hCPU, 1); break; case SPR_IBATL_2: v = getIBATL(hCPU, 2); break; case SPR_IBATL_3: v = getIBATL(hCPU, 3); break; case SPR_IBATL_4: v = getIBATL(hCPU, 4); break; case SPR_IBATL_5: v = getIBATL(hCPU, 5); break; case SPR_IBATL_6: v = getIBATL(hCPU, 6); break; case SPR_IBATL_7: v = getIBATL(hCPU, 7); break; default: debug_printf("[C%d] Get unhandled SPR %d\n", hCPU->spr.UPIR, spr); #ifdef CEMU_DEBUG_ASSERT assert_dbg(); #endif break; } return v; } static uint32 PPCSpr_get(PPCInterpreter_t* hCPU, uint32 spr) { if constexpr(ppcItpCtrl::allowSupervisorMode) { // todo - check if in supervisor mode or user mode return PPCSprSupervisor_get(hCPU, spr); } uint32 v = 0; switch (spr) { case SPR_LR: v = hCPU->spr.LR; break; case SPR_CTR: v = hCPU->spr.CTR; break; case SPR_XER: v = PPCInterpreter_getXER(hCPU); break; case SPR_DEC: // special handling for DEC register { assert_dbg(); uint64 passedCycled = PPCInterpreter_getMainCoreCycleCounter() - ppcMainThreadDECCycleStart; if (passedCycled >= (uint64)ppcMainThreadDECCycleValue) v = 0; else v = (uint32)(ppcMainThreadDECCycleValue - passedCycled); } break; case SPR_UPIR: v = hCPU->spr.UPIR; break; case SPR_PVR: assert_dbg(); //v = hCPU->sprNew.PVR; break; case SPR_UGQR0: case SPR_UGQR1: case SPR_UGQR2: case SPR_UGQR3: case SPR_UGQR4: case SPR_UGQR5: case SPR_UGQR6: case SPR_UGQR7: v = hCPU->spr.UGQR[spr - SPR_UGQR0]; break; default: debug_printf("[C%d] Get unhandled SPR %d\n", hCPU->spr.UPIR, spr); #ifdef CEMU_DEBUG_ASSERT assert_dbg(); #endif break; } //if( spr == SPR_LR || spr == SPR_PVR || spr == SPR_UPIR || spr == SPR_SCR || (spr >= SPR_UGQR0 && spr <= SPR_UGQR7) ) //{ // // readable registers // v = hCPU->spr[spr]; //} //else if( spr == SPR_DEC ) //{ // // special handling for DEC register // uint64 passedCycled = PPCInterpreter_getMainCoreCycleCounter() - ppcMainThreadDECCycleStart; // if( passedCycled >= (uint64)ppcMainThreadDECCycleValue ) // v = 0; // else // v = ppcMainThreadDECCycleValue - passedCycled; //} //else if( spr == SPR_XER ) //{ // v = PPCInterpreter_getXER(hCPU); //} //else //{ // debug_printf("[C%d] Get unhandled SPR %d value: %08x\n", hCPU->spr[SPR_UPIR], spr, hCPU->spr[spr]); // v = hCPU->spr[spr]; //} return v; } ================================================ FILE: src/Cafe/HW/Espresso/PPCCallback.h ================================================ #pragma once #include "PPCState.h" struct PPCCoreCallbackData_t { sint32 gprCount = 0; sint32 floatCount = 0; sint32 stackCount = 0; }; inline void _PPCCoreCallback_writeGPRArg(PPCCoreCallbackData_t& data, PPCInterpreter_t* hCPU, uint32 value) { if (data.gprCount < 8) { hCPU->gpr[3 + data.gprCount] = value; data.gprCount++; } else { uint32 stackOffset = 8 + data.stackCount * 4; // PPCCore_executeCallbackInternal does -16*4 to save the current stack area stackOffset -= 16 * 4; memory_writeU32(hCPU->gpr[1] + stackOffset, value); data.stackCount++; } } // callback functions inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) { return PPCCore_executeCallbackInternal(function)->gpr[3]; } template <typename T, typename... TArgs> uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args) { // TODO float arguments on stack cemu_assert_debug(data.floatCount < 8); PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if constexpr (std::is_pointer_v<T>) { _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(currentArg).GetMPTR()); } else if constexpr (std::is_base_of_v<MEMPTRBase, std::remove_reference_t<T>>) { _PPCCoreCallback_writeGPRArg(data, hCPU, currentArg.GetMPTR()); } else if constexpr (std::is_reference_v<T>) { _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(¤tArg).GetMPTR()); } else if constexpr(std::is_enum_v<T>) { using TEnum = typename std::underlying_type<T>::type; return PPCCoreCallback<TEnum>(function, data, (TEnum)currentArg, std::forward<TArgs>(args)...); } else if constexpr (std::is_floating_point_v<T>) { hCPU->fpr[1 + data.floatCount].fpr = (double)currentArg; data.floatCount++; } else if constexpr (std::is_integral_v<T> && sizeof(T) == sizeof(uint64)) { hCPU->gpr[3 + data.gprCount] = (uint32)(currentArg >> 32); // high hCPU->gpr[3 + data.gprCount + 1] = (uint32)currentArg; // low data.gprCount += 2; } else { _PPCCoreCallback_writeGPRArg(data, hCPU, (uint32)currentArg); } return PPCCoreCallback(function, data, args...); } template <typename... TArgs> uint32 PPCCoreCallback(MPTR function, TArgs... args) { PPCCoreCallbackData_t data{}; return PPCCoreCallback(function, data, std::forward<TArgs>(args)...); } template <typename... TArgs> uint32 PPCCoreCallback(void* functionPtr, TArgs... args) { MEMPTR<void> _tmp{ functionPtr }; PPCCoreCallbackData_t data{}; return PPCCoreCallback(_tmp.GetMPTR(), data, std::forward<TArgs>(args)...); } ================================================ FILE: src/Cafe/HW/Espresso/PPCScheduler.cpp ================================================ #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cafe/CafeSystem.h" uint32 ppcThreadQuantum = 45000; // execute 45000 instructions before thread reschedule happens, this value can be overwritten by game profiles void PPCInterpreter_relinquishTimeslice() { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if( hCPU->remainingCycles >= 0 ) { hCPU->skippedCycles = hCPU->remainingCycles + 1; hCPU->remainingCycles = -1; } } void PPCCore_boostQuantum(sint32 numCycles) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->remainingCycles += numCycles; } void PPCCore_deboostQuantum(sint32 numCycles) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->remainingCycles -= numCycles; } namespace coreinit { void __OSThreadSwitchToNext(); } void PPCCore_switchToScheduler() { cemu_assert_debug(__OSHasSchedulerLock() == false); // scheduler lock must not be hold past thread time slice cemu_assert_debug(PPCInterpreter_getCurrentInstance()->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600); __OSLockScheduler(); coreinit::__OSThreadSwitchToNext(); __OSUnlockScheduler(); } void PPCCore_switchToSchedulerWithLock() { cemu_assert_debug(__OSHasSchedulerLock() == true); // scheduler lock must be hold cemu_assert_debug(PPCInterpreter_getCurrentInstance()->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600); coreinit::__OSThreadSwitchToNext(); } void _PPCCore_callbackExit(PPCInterpreter_t* hCPU) { PPCInterpreter_relinquishTimeslice(); hCPU->instructionPointer = 0; } PPCInterpreter_t* PPCCore_executeCallbackInternal(uint32 functionMPTR) { cemu_assert_debug(functionMPTR != 0); PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); // remember LR and instruction pointer uint32 lr = hCPU->spr.LR; uint32 ip = hCPU->instructionPointer; // save area hCPU->gpr[1] -= 16 * 4; // set LR hCPU->spr.LR = PPCInterpreter_makeCallableExportDepr(_PPCCore_callbackExit); // set instruction pointer hCPU->instructionPointer = functionMPTR; // execute code until we return from the function while (true) { hCPU->remainingCycles = ppcThreadQuantum; hCPU->skippedCycles = 0; if (hCPU->remainingCycles > 0) { // try to enter recompiler immediately PPCRecompiler_attemptEnter(hCPU, hCPU->instructionPointer); // execute any remaining instructions in interpreter while ((--hCPU->remainingCycles) >= 0) { PPCInterpreterSlim_executeInstruction(hCPU); }; } if (hCPU->instructionPointer == 0) { // restore remaining cycles hCPU->remainingCycles += hCPU->skippedCycles; hCPU->skippedCycles = 0; break; } coreinit::OSYieldThread(); } // save area hCPU->gpr[1] += 16 * 4; // restore LR and instruction pointer hCPU->spr.LR = lr; hCPU->instructionPointer = ip; return hCPU; } void PPCCore_init() { } ================================================ FILE: src/Cafe/HW/Espresso/PPCSchedulerLLE.cpp ================================================ struct PPCInterpreterLLEContext_t { uint8 padding[1024 * 128]; // reserved memory for stack (for recompiler mode) PPCInterpreter_t cores[3]; }; PPCInterpreterGlobal_t globalCPUState = { 0 }; void PPCCoreLLE_initCore(PPCInterpreter_t* hCPU, uint32 coreIndex) { hCPU->spr.UPIR = coreIndex; hCPU->global = &globalCPUState; } #define SCR_C2 (0x200000) // enable core 2 #define SCR_C1 (0x400000) // enable core 1 typedef struct { uint32be ukn000; uint32be ukn004; uint32be ukn008; uint32be ukn00C; uint32be ukn010; uint32be ukn014; uint32be busFreq; uint32be ukn01C; uint32be ukn020[4]; uint32be ukn030[4]; uint32be ukn040[4]; uint32be ukn050[4]; uint32be ukn060[4]; uint32be ukn070[4]; uint32be ukn080[4]; uint32be ukn090[4]; uint32be ukn0A0[4]; uint32be ukn0B0[4]; uint32be ukn0C0; struct { uint32be id; uint32be baseAddress; uint32be size; }ramInfo[3]; uint32 ukn0E8; uint32 ukn0EC; uint32 ukn0F0[4]; uint32 ukn100[8]; uint32 ukn120[8]; uint32 ukn140[8]; uint32 ukn160[8]; uint32 ukn180[8]; uint32 ukn1A0[8]; uint32 ukn1C0[8]; uint32 ukn1E0[8]; uint32 ukn200[8]; uint32 ukn220[8]; uint32 ukn240[8]; uint32 ukn260[8]; uint32 ukn280[8]; uint32 ukn2A0[8]; uint32 ukn2C0[8]; uint32 ukn2E0[8]; uint32 ukn300[8]; uint32 ukn320[8]; uint32 ukn340[8]; uint32 ukn360[8]; uint32 ukn380[8]; uint32be ukn3A0; uint32be ukn3A4; uint32be ukn3A8; uint32be ukn3AC; uint32be ukn3B0; uint32be smdpAreaPtr; uint32be ukn3B8; uint32be ukn3BC; uint32 ukn3C0[8]; uint32 ukn3E0[8]; uint32 ukn400; uint32 ukn404; uint32 ukn408; }ppcBootParamBlock_t; // for kernel 5.5.2 static_assert(offsetof(ppcBootParamBlock_t, ramInfo) == 0xC4, ""); static_assert(offsetof(ppcBootParamBlock_t, busFreq) == 0x18, ""); static_assert(offsetof(ppcBootParamBlock_t, smdpAreaPtr) == 0x3B4, ""); static_assert(offsetof(ppcBootParamBlock_t, ukn400) == 0x400, ""); void PPCCoreLLE_setupBootParamBlock() { ppcBootParamBlock_t* bootParamBlock = (ppcBootParamBlock_t*)memory_getPointerFromPhysicalOffset(0x01FFF000); memset(bootParamBlock, 0, sizeof(ppcBootParamBlock_t)); // setup RAM info //PPCBaseAddress 0x8000000 0x00000000 0x28000000 //PPCSize 0x120000 0x2000000 0xA8000000 bootParamBlock->ukn004 = 0x40C; bootParamBlock->busFreq = ESPRESSO_BUS_CLOCK; bootParamBlock->ramInfo[0].id = 0; bootParamBlock->ramInfo[0].baseAddress = 0x8000000; bootParamBlock->ramInfo[0].size = 0x120000; bootParamBlock->ramInfo[1].id = 1; bootParamBlock->ramInfo[1].baseAddress = 0x00000000; bootParamBlock->ramInfo[1].size = 0x2000000; bootParamBlock->ramInfo[2].id = 2; bootParamBlock->ramInfo[2].baseAddress = 0x28000000; bootParamBlock->ramInfo[2].size = 0xA8000000; } typedef struct { uint32be magic; uint32be count; uint32 _padding08[14]; /* +0x0040 */ uint32be commandsReadIndex; // written by IOSU uint32 _padding44[15]; /* +0x0080 */ uint32be commandsWriteIndex; uint32 _padding84[15]; /* +0x00C0 */ uint32be resultsReadIndex; uint32 _paddingC4[15]; /* +0x0100 */ uint32be resultsWriteIndex; // written by IOSU uint32 _padding104[15]; /* +0x0140 */ uint32be commandPtrs[0xC00]; /* +0x3140 */ uint32be resultPtrs[0xC00]; }smdpArea_t; static_assert(offsetof(smdpArea_t, commandsReadIndex) == 0x0040, ""); static_assert(offsetof(smdpArea_t, commandsWriteIndex) == 0x0080, ""); static_assert(offsetof(smdpArea_t, resultsReadIndex) == 0x00C0, ""); static_assert(offsetof(smdpArea_t, resultsWriteIndex) == 0x0100, ""); static_assert(offsetof(smdpArea_t, resultPtrs) == 0x3140, ""); typedef struct { uint32be type; uint32be ukn04; uint32be ukn08; uint32be ukn0C; uint32be ukn10; uint32be ukn14; uint32be ukn18; uint32be ukn1C; uint32be ukn20; uint32be ukn24; uint32be ukn28; uint32be ukn2C; }smdpCommand_t; void smdpArea_pushResult(smdpArea_t* smdpArea, MPTR result) { //smdpArea. smdpArea->resultPtrs[(uint32)smdpArea->resultsWriteIndex] = result; smdpArea->resultsWriteIndex = ((uint32)smdpArea->resultsWriteIndex + 1)%(uint32)smdpArea->count; } void smdpArea_processCommand(smdpArea_t* smdpArea, smdpCommand_t* cmd) { if (cmd->type == 1) { cmd->ukn08 = 1; // cmd->ukn2C ? cemuLog_logDebug(LogType::Force, "SMDP command received - todo"); smdpArea_pushResult(smdpArea, memory_getVirtualOffsetFromPointer(cmd)); } else { assert_dbg(); } } void smdpArea_thread() { while (true) { ppcBootParamBlock_t* bootParamBlock = (ppcBootParamBlock_t*)memory_getPointerFromPhysicalOffset(0x01FFF000); if(bootParamBlock->smdpAreaPtr != MPTR_NULL) { smdpArea_t* smdpArea = (smdpArea_t*)memory_getPointerFromPhysicalOffset(bootParamBlock->smdpAreaPtr); if (smdpArea->magic == 'smdp') { uint32 cmdReadIndex = smdpArea->commandsReadIndex; uint32 cmdWriteIndex = smdpArea->commandsWriteIndex; if (cmdReadIndex != cmdWriteIndex) { // new command smdpArea_processCommand(smdpArea, (smdpCommand_t*)memory_getPointerFromPhysicalOffset(smdpArea->commandPtrs[cmdReadIndex])); // increment read counter cmdReadIndex = (cmdReadIndex + 1) % (uint32)smdpArea->count; smdpArea->commandsReadIndex = cmdReadIndex; } } } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } void PPCCoreLLE_startSingleCoreScheduler(uint32 entrypoint) { PPCInterpreterLLEContext_t* cpuContext = (PPCInterpreterLLEContext_t*)malloc(sizeof(PPCInterpreterLLEContext_t)); memset(cpuContext, 0, sizeof(PPCInterpreterLLEContext_t)); PPCCoreLLE_setupBootParamBlock(); PPCCoreLLE_initCore(cpuContext->cores + 0, 0); PPCCoreLLE_initCore(cpuContext->cores + 1, 1); PPCCoreLLE_initCore(cpuContext->cores + 2, 2); cpuContext->cores[0].instructionPointer = entrypoint; cpuContext->cores[1].instructionPointer = 0xFFF00100; cpuContext->cores[2].instructionPointer = 0xFFF00100; // todo - calculate instruction pointer when core 1/2 is enabled (because entry point is determined by MSR exception vector bit) std::thread(smdpArea_thread).detach(); while (true) { for (uint32 coreIndex = 0; coreIndex < 3; coreIndex++) { PPCInterpreter_t* hCPU = cpuContext->cores+coreIndex; PPCInterpreter_setCurrentInstance(hCPU); if (coreIndex == 1) { // check SCR core 1 enable bit if ((globalCPUState.sprGlobal.scr&SCR_C1) == 0) continue; } else if (coreIndex == 2) { // check SCR core 2 enable bit if ((globalCPUState.sprGlobal.scr&SCR_C2) == 0) continue; } hCPU->remainingCycles = 10000; while ((--hCPU->remainingCycles) >= 0) { PPCInterpreterFull_executeInstruction(hCPU); }; } } assert_dbg(); } ================================================ FILE: src/Cafe/HW/Espresso/PPCState.h ================================================ #pragma once #include "Cafe/HW/MMU/MMU.h" enum { CPUException_NOTHING, CPUException_FPUUNAVAILABLE, CPUException_EXTERNAL, CPUException_SYSTEMCALL }; #define PPC_LWARX_RESERVATION_MAX (4) union FPR_t { double fpr; struct { double fp0; double fp1; }; struct { uint64 guint; }; struct { uint64 fp0int; uint64 fp1int; }; }; typedef struct { struct { uint32 scr; uint32 car; //uint32 bcr; }sprGlobal; uint64 tb; }PPCInterpreterGlobal_t; struct PPCInterpreter_t { uint32 instructionPointer; uint32 gpr[32]; FPR_t fpr[32]; uint32 fpscr; uint8 cr[32]; // 0 -> bit not set, 1 -> bit set (upper 7 bits of each byte must always be zero) (cr0 starts at index 0, cr1 at index 4 ..) uint8 xer_ca; // carry from xer uint8 xer_so; uint8 xer_ov; // thread remaining cycles sint32 remainingCycles; // if this value goes below zero, the next thread is scheduled sint32 skippedCycles; // number of skipped cycles struct { uint32 LR; uint32 CTR; uint32 XER; uint32 UPIR; uint32 UGQR[8]; }spr; // LWARX and STWCX uint32 reservedMemAddr; uint32 reservedMemValue; // temporary storage for recompiler FPR_t temporaryFPR[8]; uint32 temporaryGPR[4]; // deprecated, refactor backend dependency on this away uint32 temporaryGPR_reg[4]; // values below this are not used by Cafe OS usermode struct { uint32 fpecr; // is this the same register as fpscr ? uint32 DEC; uint32 srr0; uint32 srr1; uint32 PVR; uint32 msr; uint32 sprg[4]; // DSI/ISI uint32 dar; uint32 dsisr; // DMA uint32 dmaU; uint32 dmaL; // MMU uint32 dbatU[8]; uint32 dbatL[8]; uint32 ibatU[8]; uint32 ibatL[8]; uint32 sr[16]; uint32 sdr1; }sprExtended; uint8 LSQE; uint8 PSE; // global CPU values PPCInterpreterGlobal_t* global; // interpreter control bool memoryException; // core context (starts at 0xFFFFFF00?) /* 0xFFFFFFE4 */ uint32 coreInterruptMask; // extra variables for recompiler void* rspTemp; }; // parameter access (legacy C style) static uint32 PPCInterpreter_getCallParamU32(PPCInterpreter_t* hCPU, uint32 index) { if (index >= 8) return memory_readU32(hCPU->gpr[1] + 8 + (index - 8) * 4); return hCPU->gpr[3 + index]; } static uint64 PPCInterpreter_getCallParamU64(PPCInterpreter_t* hCPU, uint32 index) { uint64 v = ((uint64)PPCInterpreter_getCallParamU32(hCPU, index)) << 32ULL; v |= ((uint64)PPCInterpreter_getCallParamU32(hCPU, index+1)); return v; } #define ppcGetCallParamU32(__index) PPCInterpreter_getCallParamU32(hCPU, __index) #define ppcGetCallParamU16(__index) ((uint16)(PPCInterpreter_getCallParamU32(hCPU, __index)&0xFFFF)) #define ppcGetCallParamU8(__index) ((uint8)(PPCInterpreter_getCallParamU32(hCPU, __index)&0xFF)) #define ppcGetCallParamStruct(__index, __type) ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) // legacy way of accessing parameters #define ppcDefineParamU32(__name, __index) uint32 __name = PPCInterpreter_getCallParamU32(hCPU, __index) #define ppcDefineParamU16(__name, __index) uint16 __name = (uint16)PPCInterpreter_getCallParamU32(hCPU, __index) #define ppcDefineParamU32BEPtr(__name, __index) uint32be* __name = (uint32be*)((uint8*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) #define ppcDefineParamS32(__name, __index) sint32 __name = (sint32)PPCInterpreter_getCallParamU32(hCPU, __index) #define ppcDefineParamU64(__name, __index) uint64 __name = PPCInterpreter_getCallParamU64(hCPU, __index) #define ppcDefineParamMPTR(__name, __index) MPTR __name = (MPTR)PPCInterpreter_getCallParamU32(hCPU, __index) #define ppcDefineParamMEMPTR(__name, __type, __index) MEMPTR<__type> __name{PPCInterpreter_getCallParamU32(hCPU, __index)} #define ppcDefineParamU8(__name, __index) uint8 __name = (PPCInterpreter_getCallParamU32(hCPU, __index)&0xFF) #define ppcDefineParamStructPtr(__name, __type, __index) __type* __name = ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) #define ppcDefineParamTypePtr(__name, __type, __index) __type* __name = ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) #define ppcDefineParamPtr(__name, __type, __index) __type* __name = ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) #define ppcDefineParamStr(__name, __index) char* __name = ((char*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) #define ppcDefineParamUStr(__name, __index) uint8* __name = ((uint8*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) #define ppcDefineParamWStr(__name, __index) wchar_t* __name = ((wchar_t*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) #define ppcDefineParamWStrBE(__name, __index) uint16be* __name = ((uint16be*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) // GPR constants #define GPR_SP 1 // interpreter functions PPCInterpreter_t* PPCInterpreter_createInstance(unsigned int Entrypoint); PPCInterpreter_t* PPCInterpreter_getCurrentInstance(); void PPCInterpreter_setCurrentInstance(PPCInterpreter_t* hCPU); uint64 PPCInterpreter_getMainCoreCycleCounter(); void PPCInterpreter_nextInstruction(PPCInterpreter_t* cpuInterpreter); void PPCInterpreter_jumpToInstruction(PPCInterpreter_t* cpuInterpreter, uint32 newIP); void PPCInterpreterSlim_executeInstruction(PPCInterpreter_t* hCPU); void PPCInterpreterFull_executeInstruction(PPCInterpreter_t* hCPU); // misc uint32 PPCInterpreter_getXER(PPCInterpreter_t* hCPU); void PPCInterpreter_setXER(PPCInterpreter_t* hCPU, uint32 v); // Wii U clocks (deprecated. Moved to Espresso/Const.h) #define ESPRESSO_CORE_CLOCK 1243125000 #define ESPRESSO_BUS_CLOCK 248625000 #define ESPRESSO_TIMER_CLOCK (ESPRESSO_BUS_CLOCK/4) // 62156250 #define ESPRESSO_CORE_CLOCK_TO_TIMER_CLOCK(__cc) ((__cc)/20ULL) // interrupt vectors #define CPU_EXCEPTION_DSI 0x00000300 #define CPU_EXCEPTION_INTERRUPT 0x00000500 // todo: validate #define CPU_EXCEPTION_FPUUNAVAIL 0x00000800 // todo: validate #define CPU_EXCEPTION_SYSTEMCALL 0x00000C00 // todo: validate #define CPU_EXCEPTION_DECREMENTER 0x00000900 // todo: validate // FPU available check //#define FPUCheckAvailable() if ((hCPU->msr & MSR_FP) == 0) { IPTException(hCPU, CPU_EXCEPTION_FPUUNAVAIL); return; } #define FPUCheckAvailable() // since the emulated code always runs in usermode we can assume that MSR_FP is always set // spr void PPCSpr_set(PPCInterpreter_t* hCPU, uint32 spr, uint32 newValue); uint32 PPCSpr_get(PPCInterpreter_t* hCPU, uint32 spr); uint32 PPCInterpreter_getCoreIndex(PPCInterpreter_t* hCPU); uint32 PPCInterpreter_getCurrentCoreIndex(); // decrement register void PPCInterpreter_setDEC(PPCInterpreter_t* hCPU, uint32 newValue); // timing for main processor extern uint64 ppcCyclesSince2000; // on init this is set to the cycles that passed since 1.1.2000 extern uint64 ppcCyclesSince2000TimerClock; // on init this is set to the cycles that passed since 1.1.2000 / 20 extern uint64 ppcCyclesSince2000_UTC; extern uint64 ppcMainThreadDECCycleValue; // value that was set to dec register extern uint64 ppcMainThreadDECCycleStart; // at which cycle the dec register was set // PPC timer void PPCTimer_init(); void PPCTimer_waitForInit(); uint64 PPCTimer_getFromRDTSC(); uint64 PPCTimer_microsecondsToTsc(uint64 us); uint64 PPCTimer_tscToMicroseconds(uint64 us); uint64 PPCTimer_getRawTsc(); void PPCTimer_start(); // core info and control extern uint32 ppcThreadQuantum; uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset); uint8* PPCInterpreterGetStackPointer(); void PPCInterpreterModifyStackPointer(sint32 offset); uint32 PPCInterpreter_makeCallableExportDepr(void (*ppcCallableExport)(PPCInterpreter_t* hCPU)); static inline float flushDenormalToZero(float f) { uint32 v = *(uint32*)&f; return *(float*)&v; } // HLE interface using HLECALL = void(*)(PPCInterpreter_t*); using HLEIDX = sint32; HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName); HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex); // HLE scheduler void PPCInterpreter_relinquishTimeslice(); void PPCCore_boostQuantum(sint32 numCycles); void PPCCore_deboostQuantum(sint32 numCycles); void PPCCore_switchToScheduler(); void PPCCore_switchToSchedulerWithLock(); PPCInterpreter_t* PPCCore_executeCallbackInternal(uint32 functionMPTR); void PPCCore_init(); // LLE scheduler void PPCCoreLLE_startSingleCoreScheduler(uint32 entrypoint); ================================================ FILE: src/Cafe/HW/Espresso/PPCTimer.cpp ================================================ #include "Cafe/HW/Espresso/Const.h" #include "config/ActiveSettings.h" #include "util/helpers/fspinlock.h" #include "util/highresolutiontimer/HighResolutionTimer.h" #include "Common/cpu_features.h" #if defined(ARCH_X86_64) #include <immintrin.h> #pragma intrinsic(__rdtsc) #endif uint64 _rdtscLastMeasure = 0; uint64 _rdtscFrequency = 0; struct uint128_t { uint64 low; uint64 high; }; static_assert(sizeof(uint128_t) == 16); uint128_t _rdtscAcc{}; uint64 muldiv64(uint64 a, uint64 b, uint64 d) { uint64 diva = a / d; uint64 moda = a % d; uint64 divb = b / d; uint64 modb = b % d; return diva * b + moda * divb + moda * modb / d; } uint64 PPCTimer_estimateRDTSCFrequency() { #if defined(ARCH_X86_64) if (!g_CPUFeatures.x86.invariant_tsc) cemuLog_log(LogType::Force, "Invariant TSC not supported"); #endif _mm_mfence(); uint64 tscStart = __rdtsc(); unsigned int startTime = GetTickCount(); HRTick startTick = HighResolutionTimer::now().getTick(); // wait roughly 3 seconds while (true) { if ((GetTickCount() - startTime) >= 3000) break; std::this_thread::sleep_for(std::chrono::milliseconds(10)); } _mm_mfence(); HRTick stopTick = HighResolutionTimer::now().getTick(); uint64 tscEnd = __rdtsc(); // derive frequency approximation from measured time difference uint64 tsc_diff = tscEnd - tscStart; uint64 hrtFreq = 0; uint64 hrtDiff = HighResolutionTimer::getTimeDiffEx(startTick, stopTick, hrtFreq); uint64 tsc_freq = muldiv64(tsc_diff, hrtFreq, hrtDiff); // uint64 freqMultiplier = tsc_freq / hrtFreq; //cemuLog_log(LogType::Force, "RDTSC measurement test:"); //cemuLog_log(LogType::Force, "TSC-diff: 0x{:016x}", tsc_diff); //cemuLog_log(LogType::Force, "TSC-freq: 0x{:016x}", tsc_freq); //cemuLog_log(LogType::Force, "HPC-diff: 0x{:016x}", qpc_diff); //cemuLog_log(LogType::Force, "HPC-freq: 0x{:016x}", (uint64)qpc_freq.QuadPart); //cemuLog_log(LogType::Force, "Multiplier: 0x{:016x}", freqMultiplier); return tsc_freq; } int PPCTimer_initThread() { _rdtscFrequency = PPCTimer_estimateRDTSCFrequency(); return 0; } void PPCTimer_init() { std::thread t(PPCTimer_initThread); t.detach(); _rdtscLastMeasure = __rdtsc(); } uint64 _tickSummary = 0; void PPCTimer_start() { _rdtscLastMeasure = __rdtsc(); _tickSummary = 0; } uint64 PPCTimer_getRawTsc() { return __rdtsc(); } uint64 PPCTimer_microsecondsToTsc(uint64 us) { return (us * _rdtscFrequency) / 1000000ULL; } uint64 PPCTimer_tscToMicroseconds(uint64 us) { uint128_t r{}; r.low = _umul128(us, 1000000ULL, &r.high); uint64 remainder; const uint64 microseconds = _udiv128(r.high, r.low, _rdtscFrequency, &remainder); return microseconds; } bool PPCTimer_isReady() { return _rdtscFrequency != 0; } void PPCTimer_waitForInit() { while (!PPCTimer_isReady()) std::this_thread::sleep_for(std::chrono::milliseconds(10)); } FSpinlock sTimerSpinlock; // thread safe uint64 PPCTimer_getFromRDTSC() { sTimerSpinlock.lock(); _mm_mfence(); uint64 rdtscCurrentMeasure = __rdtsc(); uint64 rdtscDif = rdtscCurrentMeasure - _rdtscLastMeasure; // optimized max(rdtscDif, 0) without conditionals rdtscDif = rdtscDif & ~(uint64)((sint64)rdtscDif >> 63); uint128_t diff{}; diff.low = _umul128(rdtscDif, Espresso::CORE_CLOCK, &diff.high); if(rdtscCurrentMeasure > _rdtscLastMeasure) _rdtscLastMeasure = rdtscCurrentMeasure; // only travel forward in time uint8 c = 0; #if BOOST_OS_WINDOWS c = _addcarry_u64(c, _rdtscAcc.low, diff.low, &_rdtscAcc.low); _addcarry_u64(c, _rdtscAcc.high, diff.high, &_rdtscAcc.high); #else // requires casting because of long / long long nonesense c = _addcarry_u64(c, _rdtscAcc.low, diff.low, (unsigned long long*)&_rdtscAcc.low); _addcarry_u64(c, _rdtscAcc.high, diff.high, (unsigned long long*)&_rdtscAcc.high); #endif uint64 remainder; uint64 elapsedTick = _udiv128(_rdtscAcc.high, _rdtscAcc.low, _rdtscFrequency, &remainder); _rdtscAcc.low = remainder; _rdtscAcc.high = 0; // timer scaling elapsedTick <<= 3ull; // *8 uint8 timerShiftFactor = ActiveSettings::GetTimerShiftFactor(); elapsedTick >>= timerShiftFactor; _tickSummary += elapsedTick; sTimerSpinlock.unlock(); return _tickSummary; } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp ================================================ #include "BackendAArch64.h" #pragma push_macro("CSIZE") #undef CSIZE #include <xbyak_aarch64.h> #pragma pop_macro("CSIZE") #include <xbyak_aarch64_util.h> #include <cstddef> #include "../PPCRecompiler.h" #include "Common/precompiled.h" #include "Common/cpu_features.h" #include "HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "HW/Espresso/Interpreter/PPCInterpreterHelper.h" #include "HW/Espresso/PPCState.h" using namespace Xbyak_aarch64; constexpr uint32 TEMP_GPR_1_ID = 25; constexpr uint32 TEMP_GPR_2_ID = 26; constexpr uint32 PPC_RECOMPILER_INSTANCE_DATA_REG_ID = 27; constexpr uint32 MEMORY_BASE_REG_ID = 28; constexpr uint32 HCPU_REG_ID = 29; constexpr uint32 TEMP_FPR_ID = 31; struct FPReg { explicit FPReg(size_t index) : index(index), VReg(index), QReg(index), DReg(index), SReg(index), HReg(index), BReg(index) { } const size_t index; const VReg VReg; const QReg QReg; const DReg DReg; const SReg SReg; const HReg HReg; const BReg BReg; }; struct GPReg { explicit GPReg(size_t index) : index(index), XReg(index), WReg(index) { } const size_t index; const XReg XReg; const WReg WReg; }; static const XReg HCPU_REG{HCPU_REG_ID}, PPC_REC_INSTANCE_REG{PPC_RECOMPILER_INSTANCE_DATA_REG_ID}, MEM_BASE_REG{MEMORY_BASE_REG_ID}; static const GPReg TEMP_GPR1{TEMP_GPR_1_ID}; static const GPReg TEMP_GPR2{TEMP_GPR_2_ID}; static const GPReg LR{TEMP_GPR_2_ID}; static const FPReg TEMP_FPR{TEMP_FPR_ID}; static const util::Cpu s_cpu; class AArch64Allocator : public Allocator { private: #ifdef XBYAK_USE_MMAP_ALLOCATOR inline static MmapAllocator s_allocator; #else inline static Allocator s_allocator; #endif Allocator* m_allocatorImpl; bool m_freeDisabled = false; public: AArch64Allocator() : m_allocatorImpl(reinterpret_cast<Allocator*>(&s_allocator)) {} uint32* alloc(size_t size) override { return m_allocatorImpl->alloc(size); } void setFreeDisabled(bool disabled) { m_freeDisabled = disabled; } void free(uint32* p) override { if (!m_freeDisabled) m_allocatorImpl->free(p); } [[nodiscard]] bool useProtect() const override { return !m_freeDisabled && m_allocatorImpl->useProtect(); } }; struct UnconditionalJumpInfo { IMLSegment* target; }; struct ConditionalRegJumpInfo { IMLSegment* target; WReg regBool; bool mustBeTrue; }; struct NegativeRegValueJumpInfo { IMLSegment* target; WReg regValue; }; using JumpInfo = std::variant< UnconditionalJumpInfo, ConditionalRegJumpInfo, NegativeRegValueJumpInfo>; struct AArch64GenContext_t : CodeGenerator { explicit AArch64GenContext_t(Allocator* allocator = nullptr); void enterRecompilerCode(); void leaveRecompilerCode(); void r_name(IMLInstruction* imlInstruction); void name_r(IMLInstruction* imlInstruction); bool r_s32(IMLInstruction* imlInstruction); bool r_r(IMLInstruction* imlInstruction); bool r_r_s32(IMLInstruction* imlInstruction); bool r_r_s32_carry(IMLInstruction* imlInstruction); bool r_r_r(IMLInstruction* imlInstruction); bool r_r_r_carry(IMLInstruction* imlInstruction); void compare(IMLInstruction* imlInstruction); void compare_s32(IMLInstruction* imlInstruction); bool load(IMLInstruction* imlInstruction, bool indexed); bool store(IMLInstruction* imlInstruction, bool indexed); void atomic_cmp_store(IMLInstruction* imlInstruction); bool macro(IMLInstruction* imlInstruction); void call_imm(IMLInstruction* imlInstruction); bool fpr_load(IMLInstruction* imlInstruction, bool indexed); bool fpr_store(IMLInstruction* imlInstruction, bool indexed); void fpr_r_r(IMLInstruction* imlInstruction); void fpr_r_r_r(IMLInstruction* imlInstruction); void fpr_r_r_r_r(IMLInstruction* imlInstruction); void fpr_r(IMLInstruction* imlInstruction); void fpr_compare(IMLInstruction* imlInstruction); void cjump(IMLInstruction* imlInstruction, IMLSegment* imlSegment); void jump(IMLSegment* imlSegment); void conditionalJumpCycleCheck(IMLSegment* imlSegment); static constexpr size_t MAX_JUMP_INSTR_COUNT = 2; std::list<std::pair<size_t, JumpInfo>> jumps; void prepareJump(JumpInfo&& jumpInfo) { jumps.emplace_back(getSize(), jumpInfo); for (int i = 0; i < MAX_JUMP_INSTR_COUNT; ++i) nop(); } std::map<IMLSegment*, size_t> segmentStarts; void storeSegmentStart(IMLSegment* imlSegment) { segmentStarts[imlSegment] = getSize(); } bool processAllJumps() { for (auto jump : jumps) { auto jumpStart = jump.first; auto jumpInfo = jump.second; bool success = std::visit( [&, this](const auto& jump) { setSize(jumpStart); sint64 targetAddress = segmentStarts.at(jump.target); sint64 addressOffset = targetAddress - jumpStart; return handleJump(addressOffset, jump); }, jumpInfo); if (!success) { return false; } } return true; } bool handleJump(sint64 addressOffset, const UnconditionalJumpInfo& jump) { // in +/-128MB if (-0x8000000 <= addressOffset && addressOffset <= 0x7ffffff) { b(addressOffset); return true; } cemu_assert_suspicious(); return false; } bool handleJump(sint64 addressOffset, const ConditionalRegJumpInfo& jump) { bool mustBeTrue = jump.mustBeTrue; // in +/-32KB if (-0x8000 <= addressOffset && addressOffset <= 0x7fff) { if (mustBeTrue) tbnz(jump.regBool, 0, addressOffset); else tbz(jump.regBool, 0, addressOffset); return true; } // in +/-1MB if (-0x100000 <= addressOffset && addressOffset <= 0xfffff) { if (mustBeTrue) cbnz(jump.regBool, addressOffset); else cbz(jump.regBool, addressOffset); return true; } Label skipJump; if (mustBeTrue) tbz(jump.regBool, 0, skipJump); else tbnz(jump.regBool, 0, skipJump); addressOffset -= 4; // in +/-128MB if (-0x8000000 <= addressOffset && addressOffset <= 0x7ffffff) { b(addressOffset); L(skipJump); return true; } cemu_assert_suspicious(); return false; } bool handleJump(sint64 addressOffset, const NegativeRegValueJumpInfo& jump) { // in +/-32KB if (-0x8000 <= addressOffset && addressOffset <= 0x7fff) { tbnz(jump.regValue, 31, addressOffset); return true; } // in +/-1MB if (-0x100000 <= addressOffset && addressOffset <= 0xfffff) { tst(jump.regValue, 0x80000000); addressOffset -= 4; bne(addressOffset); return true; } Label skipJump; tbz(jump.regValue, 31, skipJump); addressOffset -= 4; // in +/-128MB if (-0x8000000 <= addressOffset && addressOffset <= 0x7ffffff) { b(addressOffset); L(skipJump); return true; } cemu_assert_suspicious(); return false; } }; template<std::derived_from<VRegSc> T> T fpReg(const IMLReg& imlReg) { cemu_assert_debug(imlReg.GetRegFormat() == IMLRegFormat::F64); auto regId = imlReg.GetRegID(); cemu_assert_debug(regId >= IMLArchAArch64::PHYSREG_FPR_BASE && regId < IMLArchAArch64::PHYSREG_FPR_BASE + IMLArchAArch64::PHYSREG_FPR_COUNT); return T(regId - IMLArchAArch64::PHYSREG_FPR_BASE); } template<std::derived_from<RReg> T> T gpReg(const IMLReg& imlReg) { auto regFormat = imlReg.GetRegFormat(); if (std::is_same_v<T, WReg>) cemu_assert_debug(regFormat == IMLRegFormat::I32); else if (std::is_same_v<T, XReg>) cemu_assert_debug(regFormat == IMLRegFormat::I64); else cemu_assert_unimplemented(); auto regId = imlReg.GetRegID(); cemu_assert_debug(regId >= IMLArchAArch64::PHYSREG_GPR_BASE && regId < IMLArchAArch64::PHYSREG_GPR_BASE + IMLArchAArch64::PHYSREG_GPR_COUNT); return T(regId - IMLArchAArch64::PHYSREG_GPR_BASE); } template<std::derived_from<VRegSc> To, std::derived_from<VRegSc> From> To aliasAs(const From& reg) { return To(reg.getIdx()); } template<std::derived_from<RReg> To, std::derived_from<RReg> From> To aliasAs(const From& reg) { return To(reg.getIdx()); } AArch64GenContext_t::AArch64GenContext_t(Allocator* allocator) : CodeGenerator(DEFAULT_MAX_CODE_SIZE, AutoGrow, allocator) { } constexpr uint64 ones(uint32 size) { return (size == 64) ? 0xffffffffffffffff : ((uint64)1 << size) - 1; } constexpr bool isAdrImmValidFPR(sint32 imm, uint32 bits) { uint32 times = bits / 8; uint32 sh = std::countr_zero(times); return (0 <= imm && imm <= 4095 * times) && ((uint64)imm & ones(sh)) == 0; } constexpr bool isAdrImmValidGPR(sint32 imm, uint32 bits = 32) { uint32 size = std::countr_zero(bits / 8u); sint32 times = 1 << size; return (0 <= imm && imm <= 4095 * times) && ((uint64)imm & ones(size)) == 0; } constexpr bool isAdrImmRangeValid(sint32 rangeStart, sint32 rangeOffset, sint32 bits, std::invocable<sint32, uint32> auto check) { for (sint32 i = rangeStart; i <= rangeStart + rangeOffset; i += bits / 8) if (!check(i, bits)) return false; return true; } constexpr bool isAdrImmRangeValidGPR(sint32 rangeStart, sint32 rangeOffset, sint32 bits = 32) { return isAdrImmRangeValid(rangeStart, rangeOffset, bits, isAdrImmValidGPR); } constexpr bool isAdrImmRangeValidFpr(sint32 rangeStart, sint32 rangeOffset, sint32 bits) { return isAdrImmRangeValid(rangeStart, rangeOffset, bits, isAdrImmValidFPR); } // Verify that all of the offsets for the PPCInterpreter_t members that we use in r_name/name_r have a valid imm value for AdrUimm static_assert(isAdrImmRangeValidGPR(offsetof(PPCInterpreter_t, gpr), sizeof(uint32) * 31)); static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, spr.LR))); static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, spr.CTR))); static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, spr.XER))); static_assert(isAdrImmRangeValidGPR(offsetof(PPCInterpreter_t, spr.UGQR), sizeof(PPCInterpreter_t::spr.UGQR[0]) * (SPR_UGQR7 - SPR_UGQR0))); static_assert(isAdrImmRangeValidGPR(offsetof(PPCInterpreter_t, temporaryGPR_reg), sizeof(uint32) * 3)); static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, xer_ca), 8)); static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, xer_so), 8)); static_assert(isAdrImmRangeValidGPR(offsetof(PPCInterpreter_t, cr), PPCREC_NAME_CR_LAST - PPCREC_NAME_CR, 8)); static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, reservedMemAddr))); static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, reservedMemValue))); static_assert(isAdrImmRangeValidFpr(offsetof(PPCInterpreter_t, fpr), sizeof(FPR_t) * 63, 64)); static_assert(isAdrImmRangeValidFpr(offsetof(PPCInterpreter_t, temporaryFPR), sizeof(FPR_t) * 7, 128)); void AArch64GenContext_t::r_name(IMLInstruction* imlInstruction) { uint32 name = imlInstruction->op_r_name.name; if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::I64) { XReg regRXReg = gpReg<XReg>(imlInstruction->op_r_name.regR); WReg regR = aliasAs<WReg>(regRXReg); if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) { ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0))); } else if (name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0 + 999) { uint32 sprIndex = (name - PPCREC_NAME_SPR0); if (sprIndex == SPR_LR) ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.LR))); else if (sprIndex == SPR_CTR) ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.CTR))); else if (sprIndex == SPR_XER) ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.XER))); else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0))); else cemu_assert_suspicious(); } else if (name >= PPCREC_NAME_TEMPORARY && name < PPCREC_NAME_TEMPORARY + 4) { ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY))); } else if (name == PPCREC_NAME_XER_CA) { ldrb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, xer_ca))); } else if (name == PPCREC_NAME_XER_SO) { ldrb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, xer_so))); } else if (name >= PPCREC_NAME_CR && name <= PPCREC_NAME_CR_LAST) { ldrb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR))); } else if (name == PPCREC_NAME_CPU_MEMRES_EA) { ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, reservedMemAddr))); } else if (name == PPCREC_NAME_CPU_MEMRES_VAL) { ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, reservedMemValue))); } else { cemu_assert_suspicious(); } } else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) { auto imlRegR = imlInstruction->op_r_name.regR; if (name >= PPCREC_NAME_FPR_HALF && name < (PPCREC_NAME_FPR_HALF + 64)) { uint32 regIndex = (name - PPCREC_NAME_FPR_HALF) / 2; uint32 pairIndex = (name - PPCREC_NAME_FPR_HALF) % 2; uint32 offset = offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * regIndex + (pairIndex ? sizeof(double) : 0); ldr(fpReg<DReg>(imlRegR), AdrUimm(HCPU_REG, offset)); } else if (name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) { ldr(fpReg<QReg>(imlRegR), AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0))); } else { cemu_assert_suspicious(); } } else { cemu_assert_suspicious(); } } void AArch64GenContext_t::name_r(IMLInstruction* imlInstruction) { uint32 name = imlInstruction->op_r_name.name; if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::I64) { XReg regRXReg = gpReg<XReg>(imlInstruction->op_r_name.regR); WReg regR = aliasAs<WReg>(regRXReg); if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) { str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0))); } else if (name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0 + 999) { uint32 sprIndex = (name - PPCREC_NAME_SPR0); if (sprIndex == SPR_LR) str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.LR))); else if (sprIndex == SPR_CTR) str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.CTR))); else if (sprIndex == SPR_XER) str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.XER))); else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0))); else cemu_assert_suspicious(); } else if (name >= PPCREC_NAME_TEMPORARY && name < PPCREC_NAME_TEMPORARY + 4) { str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY))); } else if (name == PPCREC_NAME_XER_CA) { strb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, xer_ca))); } else if (name == PPCREC_NAME_XER_SO) { strb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, xer_so))); } else if (name >= PPCREC_NAME_CR && name <= PPCREC_NAME_CR_LAST) { strb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR))); } else if (name == PPCREC_NAME_CPU_MEMRES_EA) { str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, reservedMemAddr))); } else if (name == PPCREC_NAME_CPU_MEMRES_VAL) { str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, reservedMemValue))); } else { cemu_assert_suspicious(); } } else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) { auto imlRegR = imlInstruction->op_r_name.regR; if (name >= PPCREC_NAME_FPR_HALF && name < (PPCREC_NAME_FPR_HALF + 64)) { uint32 regIndex = (name - PPCREC_NAME_FPR_HALF) / 2; uint32 pairIndex = (name - PPCREC_NAME_FPR_HALF) % 2; sint32 offset = offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * regIndex + pairIndex * sizeof(double); str(fpReg<DReg>(imlRegR), AdrUimm(HCPU_REG, offset)); } else if (name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) { str(fpReg<QReg>(imlRegR), AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0))); } else { cemu_assert_suspicious(); } } else { cemu_assert_suspicious(); } } bool AArch64GenContext_t::r_r(IMLInstruction* imlInstruction) { WReg regR = gpReg<WReg>(imlInstruction->op_r_r.regR); WReg regA = gpReg<WReg>(imlInstruction->op_r_r.regA); if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) { mov(regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_ENDIAN_SWAP) { rev(regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32) { sxtb(regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32) { sxth(regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_NOT) { mvn(regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_NEG) { neg(regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_CNTLZW) { clz(regR, regA); } else { cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_r_r(): Unsupported operation {:x}", imlInstruction->operation); return false; } return true; } bool AArch64GenContext_t::r_s32(IMLInstruction* imlInstruction) { sint32 imm32 = imlInstruction->op_r_immS32.immS32; WReg reg = gpReg<WReg>(imlInstruction->op_r_immS32.regR); if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) { mov(reg, imm32); } else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE) { ror(reg, reg, 32 - (imm32 & 0x1f)); } else { cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_r_s32(): Unsupported operation {:x}", imlInstruction->operation); return false; } return true; } bool AArch64GenContext_t::r_r_s32(IMLInstruction* imlInstruction) { WReg regR = gpReg<WReg>(imlInstruction->op_r_r_s32.regR); WReg regA = gpReg<WReg>(imlInstruction->op_r_r_s32.regA); sint32 immS32 = imlInstruction->op_r_r_s32.immS32; if (imlInstruction->operation == PPCREC_IML_OP_ADD) { add_imm(regR, regA, immS32, TEMP_GPR1.WReg); } else if (imlInstruction->operation == PPCREC_IML_OP_SUB) { sub_imm(regR, regA, immS32, TEMP_GPR1.WReg); } else if (imlInstruction->operation == PPCREC_IML_OP_AND) { mov(TEMP_GPR1.WReg, immS32); and_(regR, regA, TEMP_GPR1.WReg); } else if (imlInstruction->operation == PPCREC_IML_OP_OR) { mov(TEMP_GPR1.WReg, immS32); orr(regR, regA, TEMP_GPR1.WReg); } else if (imlInstruction->operation == PPCREC_IML_OP_XOR) { mov(TEMP_GPR1.WReg, immS32); eor(regR, regA, TEMP_GPR1.WReg); } else if (imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED) { mov(TEMP_GPR1.WReg, immS32); mul(regR, regA, TEMP_GPR1.WReg); } else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) { lsl(regR, regA, (uint32)immS32 & 0x1f); } else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) { lsr(regR, regA, (uint32)immS32 & 0x1f); } else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) { asr(regR, regA, (uint32)immS32 & 0x1f); } else { cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_r_r_s32(): Unsupported operation {:x}", imlInstruction->operation); cemu_assert_suspicious(); return false; } return true; } bool AArch64GenContext_t::r_r_s32_carry(IMLInstruction* imlInstruction) { WReg regR = gpReg<WReg>(imlInstruction->op_r_r_s32_carry.regR); WReg regA = gpReg<WReg>(imlInstruction->op_r_r_s32_carry.regA); WReg regCarry = gpReg<WReg>(imlInstruction->op_r_r_s32_carry.regCarry); sint32 immS32 = imlInstruction->op_r_r_s32_carry.immS32; if (imlInstruction->operation == PPCREC_IML_OP_ADD) { adds_imm(regR, regA, immS32, TEMP_GPR1.WReg); cset(regCarry, Cond::CS); } else if (imlInstruction->operation == PPCREC_IML_OP_ADD_WITH_CARRY) { mov(TEMP_GPR1.WReg, immS32); cmp(regCarry, 1); adcs(regR, regA, TEMP_GPR1.WReg); cset(regCarry, Cond::CS); } else { cemu_assert_suspicious(); return false; } return true; } bool AArch64GenContext_t::r_r_r(IMLInstruction* imlInstruction) { WReg regResult = gpReg<WReg>(imlInstruction->op_r_r_r.regR); XReg reg64Result = aliasAs<XReg>(regResult); WReg regOperand1 = gpReg<WReg>(imlInstruction->op_r_r_r.regA); WReg regOperand2 = gpReg<WReg>(imlInstruction->op_r_r_r.regB); if (imlInstruction->operation == PPCREC_IML_OP_ADD) { add(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_SUB) { sub(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_OR) { orr(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_AND) { and_(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_XOR) { eor(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED) { mul(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_SLW) { tst(regOperand2, 32); lsl(regResult, regOperand1, regOperand2); csel(regResult, regResult, wzr, Cond::EQ); } else if (imlInstruction->operation == PPCREC_IML_OP_SRW) { tst(regOperand2, 32); lsr(regResult, regOperand1, regOperand2); csel(regResult, regResult, wzr, Cond::EQ); } else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE) { neg(TEMP_GPR1.WReg, regOperand2); ror(regResult, regOperand1, TEMP_GPR1.WReg); } else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) { asr(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) { lsr(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) { lsl(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED) { sdiv(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_DIVIDE_UNSIGNED) { udiv(regResult, regOperand1, regOperand2); } else if (imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED) { smull(reg64Result, regOperand1, regOperand2); lsr(reg64Result, reg64Result, 32); } else if (imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED) { umull(reg64Result, regOperand1, regOperand2); lsr(reg64Result, reg64Result, 32); } else { cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_r_r_r(): Unsupported operation {:x}", imlInstruction->operation); return false; } return true; } bool AArch64GenContext_t::r_r_r_carry(IMLInstruction* imlInstruction) { WReg regR = gpReg<WReg>(imlInstruction->op_r_r_r_carry.regR); WReg regA = gpReg<WReg>(imlInstruction->op_r_r_r_carry.regA); WReg regB = gpReg<WReg>(imlInstruction->op_r_r_r_carry.regB); WReg regCarry = gpReg<WReg>(imlInstruction->op_r_r_r_carry.regCarry); if (imlInstruction->operation == PPCREC_IML_OP_ADD) { adds(regR, regA, regB); cset(regCarry, Cond::CS); } else if (imlInstruction->operation == PPCREC_IML_OP_ADD_WITH_CARRY) { cmp(regCarry, 1); adcs(regR, regA, regB); cset(regCarry, Cond::CS); } else { cemu_assert_suspicious(); return false; } return true; } Cond ImlCondToArm64Cond(IMLCondition condition) { switch (condition) { case IMLCondition::EQ: return Cond::EQ; case IMLCondition::NEQ: return Cond::NE; case IMLCondition::UNSIGNED_GT: return Cond::HI; case IMLCondition::UNSIGNED_LT: return Cond::LO; case IMLCondition::SIGNED_GT: return Cond::GT; case IMLCondition::SIGNED_LT: return Cond::LT; default: { cemu_assert_suspicious(); return Cond::EQ; } } } void AArch64GenContext_t::compare(IMLInstruction* imlInstruction) { WReg regR = gpReg<WReg>(imlInstruction->op_compare.regR); WReg regA = gpReg<WReg>(imlInstruction->op_compare.regA); WReg regB = gpReg<WReg>(imlInstruction->op_compare.regB); Cond cond = ImlCondToArm64Cond(imlInstruction->op_compare.cond); cmp(regA, regB); cset(regR, cond); } void AArch64GenContext_t::compare_s32(IMLInstruction* imlInstruction) { WReg regR = gpReg<WReg>(imlInstruction->op_compare.regR); WReg regA = gpReg<WReg>(imlInstruction->op_compare.regA); sint32 imm = imlInstruction->op_compare_s32.immS32; auto cond = ImlCondToArm64Cond(imlInstruction->op_compare.cond); cmp_imm(regA, imm, TEMP_GPR1.WReg); cset(regR, cond); } void AArch64GenContext_t::cjump(IMLInstruction* imlInstruction, IMLSegment* imlSegment) { auto regBool = gpReg<WReg>(imlInstruction->op_conditional_jump.registerBool); prepareJump(ConditionalRegJumpInfo{ .target = imlSegment->nextSegmentBranchTaken, .regBool = regBool, .mustBeTrue = imlInstruction->op_conditional_jump.mustBeTrue, }); } void AArch64GenContext_t::jump(IMLSegment* imlSegment) { prepareJump(UnconditionalJumpInfo{.target = imlSegment->nextSegmentBranchTaken}); } void AArch64GenContext_t::conditionalJumpCycleCheck(IMLSegment* imlSegment) { ldr(TEMP_GPR1.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, remainingCycles))); prepareJump(NegativeRegValueJumpInfo{ .target = imlSegment->nextSegmentBranchTaken, .regValue = TEMP_GPR1.WReg, }); } void* PPCRecompiler_virtualHLE(PPCInterpreter_t* ppcInterpreter, uint32 hleFuncId) { void* prevRSPTemp = ppcInterpreter->rspTemp; if (hleFuncId == 0xFFD0) { ppcInterpreter->remainingCycles -= 500; // let subtract about 500 cycles for each HLE call ppcInterpreter->gpr[3] = 0; PPCInterpreter_nextInstruction(ppcInterpreter); return PPCInterpreter_getCurrentInstance(); } else { auto hleCall = PPCInterpreter_getHLECall(hleFuncId); cemu_assert(hleCall != nullptr); hleCall(ppcInterpreter); } ppcInterpreter->rspTemp = prevRSPTemp; return PPCInterpreter_getCurrentInstance(); } bool AArch64GenContext_t::macro(IMLInstruction* imlInstruction) { if (imlInstruction->operation == PPCREC_IML_MACRO_B_TO_REG) { WReg branchDstReg = gpReg<WReg>(imlInstruction->op_macro.paramReg); mov(TEMP_GPR1.WReg, offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, branchDstReg, ShMod::LSL, 1); ldr(TEMP_GPR1.XReg, AdrExt(PPC_REC_INSTANCE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); mov(LR.WReg, branchDstReg); br(TEMP_GPR1.XReg); return true; } else if (imlInstruction->operation == PPCREC_IML_MACRO_BL) { uint32 newLR = imlInstruction->op_macro.param + 4; mov(TEMP_GPR1.WReg, newLR); str(TEMP_GPR1.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.LR))); uint32 newIP = imlInstruction->op_macro.param2; uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; mov(TEMP_GPR1.XReg, lookupOffset); ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); mov(LR.WReg, newIP); br(TEMP_GPR1.XReg); return true; } else if (imlInstruction->operation == PPCREC_IML_MACRO_B_FAR) { uint32 newIP = imlInstruction->op_macro.param2; uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; mov(TEMP_GPR1.XReg, lookupOffset); ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); mov(LR.WReg, newIP); br(TEMP_GPR1.XReg); return true; } else if (imlInstruction->operation == PPCREC_IML_MACRO_LEAVE) { uint32 currentInstructionAddress = imlInstruction->op_macro.param; mov(TEMP_GPR1.XReg, (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); // newIP = 0 special value for recompiler exit ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); mov(LR.WReg, currentInstructionAddress); br(TEMP_GPR1.XReg); return true; } else if (imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK) { brk(0xf000); return true; } else if (imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES) { uint32 cycleCount = imlInstruction->op_macro.param; AdrUimm adrCycles = AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, remainingCycles)); ldr(TEMP_GPR1.WReg, adrCycles); sub_imm(TEMP_GPR1.WReg, TEMP_GPR1.WReg, cycleCount, TEMP_GPR2.WReg); str(TEMP_GPR1.WReg, adrCycles); return true; } else if (imlInstruction->operation == PPCREC_IML_MACRO_HLE) { uint32 ppcAddress = imlInstruction->op_macro.param; uint32 funcId = imlInstruction->op_macro.param2; Label cyclesLeftLabel; // update instruction pointer mov(TEMP_GPR1.WReg, ppcAddress); str(TEMP_GPR1.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); // set parameters str(x30, AdrPreImm(sp, -16)); mov(x0, HCPU_REG); mov(w1, funcId); // call HLE function mov(TEMP_GPR1.XReg, (uint64)PPCRecompiler_virtualHLE); blr(TEMP_GPR1.XReg); mov(HCPU_REG, x0); ldr(x30, AdrPostImm(sp, 16)); // check if cycles where decreased beyond zero, if yes -> leave recompiler ldr(TEMP_GPR1.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, remainingCycles))); tbz(TEMP_GPR1.WReg, 31, cyclesLeftLabel); // check if negative mov(TEMP_GPR1.XReg, offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); ldr(LR.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); // branch to recompiler exit br(TEMP_GPR1.XReg); L(cyclesLeftLabel); // check if instruction pointer was changed // assign new instruction pointer to LR.WReg ldr(LR.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); mov(TEMP_GPR1.XReg, offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); add(TEMP_GPR1.XReg, TEMP_GPR1.XReg, LR.XReg, ShMod::LSL, 1); ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); // branch to [ppcRecompilerDirectJumpTable + PPCInterpreter_t::instructionPointer * 2] br(TEMP_GPR1.XReg); return true; } else { cemuLog_log(LogType::Recompiler, "Unknown recompiler macro operation %d\n", imlInstruction->operation); cemu_assert_suspicious(); } return false; } bool AArch64GenContext_t::load(IMLInstruction* imlInstruction, bool indexed) { cemu_assert_debug(imlInstruction->op_storeLoad.registerData.GetRegFormat() == IMLRegFormat::I32); cemu_assert_debug(imlInstruction->op_storeLoad.registerMem.GetRegFormat() == IMLRegFormat::I32); if (indexed) cemu_assert_debug(imlInstruction->op_storeLoad.registerMem2.GetRegFormat() == IMLRegFormat::I32); sint32 memOffset = imlInstruction->op_storeLoad.immS32; bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; bool switchEndian = imlInstruction->op_storeLoad.flags2.swapEndian; WReg memReg = gpReg<WReg>(imlInstruction->op_storeLoad.registerMem); WReg dataReg = gpReg<WReg>(imlInstruction->op_storeLoad.registerData); add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); if (indexed) add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, gpReg<WReg>(imlInstruction->op_storeLoad.registerMem2)); auto adr = AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW); if (imlInstruction->op_storeLoad.copyWidth == 32) { ldr(dataReg, adr); if (switchEndian) rev(dataReg, dataReg); } else if (imlInstruction->op_storeLoad.copyWidth == 16) { if (switchEndian) { ldrh(dataReg, adr); rev(dataReg, dataReg); if (signExtend) asr(dataReg, dataReg, 16); else lsr(dataReg, dataReg, 16); } else { if (signExtend) ldrsh(dataReg, adr); else ldrh(dataReg, adr); } } else if (imlInstruction->op_storeLoad.copyWidth == 8) { if (signExtend) ldrsb(dataReg, adr); else ldrb(dataReg, adr); } else { return false; } return true; } bool AArch64GenContext_t::store(IMLInstruction* imlInstruction, bool indexed) { cemu_assert_debug(imlInstruction->op_storeLoad.registerData.GetRegFormat() == IMLRegFormat::I32); cemu_assert_debug(imlInstruction->op_storeLoad.registerMem.GetRegFormat() == IMLRegFormat::I32); if (indexed) cemu_assert_debug(imlInstruction->op_storeLoad.registerMem2.GetRegFormat() == IMLRegFormat::I32); WReg dataReg = gpReg<WReg>(imlInstruction->op_storeLoad.registerData); WReg memReg = gpReg<WReg>(imlInstruction->op_storeLoad.registerMem); sint32 memOffset = imlInstruction->op_storeLoad.immS32; bool swapEndian = imlInstruction->op_storeLoad.flags2.swapEndian; add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); if (indexed) add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, gpReg<WReg>(imlInstruction->op_storeLoad.registerMem2)); AdrExt adr = AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW); if (imlInstruction->op_storeLoad.copyWidth == 32) { if (swapEndian) { rev(TEMP_GPR2.WReg, dataReg); str(TEMP_GPR2.WReg, adr); } else { str(dataReg, adr); } } else if (imlInstruction->op_storeLoad.copyWidth == 16) { if (swapEndian) { rev(TEMP_GPR2.WReg, dataReg); lsr(TEMP_GPR2.WReg, TEMP_GPR2.WReg, 16); strh(TEMP_GPR2.WReg, adr); } else { strh(dataReg, adr); } } else if (imlInstruction->op_storeLoad.copyWidth == 8) { strb(dataReg, adr); } else { return false; } return true; } void AArch64GenContext_t::atomic_cmp_store(IMLInstruction* imlInstruction) { WReg outReg = gpReg<WReg>(imlInstruction->op_atomic_compare_store.regBoolOut); WReg eaReg = gpReg<WReg>(imlInstruction->op_atomic_compare_store.regEA); WReg valReg = gpReg<WReg>(imlInstruction->op_atomic_compare_store.regWriteValue); WReg cmpValReg = gpReg<WReg>(imlInstruction->op_atomic_compare_store.regCompareValue); if (s_cpu.isAtomicSupported()) { mov(TEMP_GPR2.WReg, cmpValReg); add(TEMP_GPR1.XReg, MEM_BASE_REG, eaReg, ExtMod::UXTW); casal(TEMP_GPR2.WReg, valReg, AdrNoOfs(TEMP_GPR1.XReg)); cmp(TEMP_GPR2.WReg, cmpValReg); cset(outReg, Cond::EQ); } else { Label notEqual; Label storeFailed; add(TEMP_GPR1.XReg, MEM_BASE_REG, eaReg, ExtMod::UXTW); L(storeFailed); ldaxr(TEMP_GPR2.WReg, AdrNoOfs(TEMP_GPR1.XReg)); cmp(TEMP_GPR2.WReg, cmpValReg); bne(notEqual); stlxr(TEMP_GPR2.WReg, valReg, AdrNoOfs(TEMP_GPR1.XReg)); cbnz(TEMP_GPR2.WReg, storeFailed); L(notEqual); cset(outReg, Cond::EQ); } } bool AArch64GenContext_t::fpr_load(IMLInstruction* imlInstruction, bool indexed) { const IMLReg& dataReg = imlInstruction->op_storeLoad.registerData; SReg dataSReg = fpReg<SReg>(dataReg); DReg dataDReg = fpReg<DReg>(dataReg); WReg realRegisterMem = gpReg<WReg>(imlInstruction->op_storeLoad.registerMem); WReg indexReg = indexed ? gpReg<WReg>(imlInstruction->op_storeLoad.registerMem2) : wzr; sint32 adrOffset = imlInstruction->op_storeLoad.immS32; uint8 mode = imlInstruction->op_storeLoad.mode; if (mode == PPCREC_FPR_LD_MODE_SINGLE) { add_imm(TEMP_GPR1.WReg, realRegisterMem, adrOffset, TEMP_GPR1.WReg); if (indexed) add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); ldr(TEMP_GPR2.WReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); rev(TEMP_GPR2.WReg, TEMP_GPR2.WReg); fmov(dataSReg, TEMP_GPR2.WReg); if (imlInstruction->op_storeLoad.flags2.notExpanded) { // leave value as single } else { fcvt(dataDReg, dataSReg); } } else if (mode == PPCREC_FPR_LD_MODE_DOUBLE) { add_imm(TEMP_GPR1.WReg, realRegisterMem, adrOffset, TEMP_GPR1.WReg); if (indexed) add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); ldr(TEMP_GPR2.XReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); rev(TEMP_GPR2.XReg, TEMP_GPR2.XReg); fmov(dataDReg, TEMP_GPR2.XReg); } else { return false; } return true; } // store to memory bool AArch64GenContext_t::fpr_store(IMLInstruction* imlInstruction, bool indexed) { const IMLReg& dataImlReg = imlInstruction->op_storeLoad.registerData; DReg dataDReg = fpReg<DReg>(dataImlReg); SReg dataSReg = fpReg<SReg>(dataImlReg); WReg memReg = gpReg<WReg>(imlInstruction->op_storeLoad.registerMem); WReg indexReg = indexed ? gpReg<WReg>(imlInstruction->op_storeLoad.registerMem2) : wzr; sint32 memOffset = imlInstruction->op_storeLoad.immS32; uint8 mode = imlInstruction->op_storeLoad.mode; if (mode == PPCREC_FPR_ST_MODE_SINGLE) { add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); if (indexed) add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); if (imlInstruction->op_storeLoad.flags2.notExpanded) { // value is already in single format fmov(TEMP_GPR2.WReg, dataSReg); } else { fcvt(TEMP_FPR.SReg, dataDReg); fmov(TEMP_GPR2.WReg, TEMP_FPR.SReg); } rev(TEMP_GPR2.WReg, TEMP_GPR2.WReg); str(TEMP_GPR2.WReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); } else if (mode == PPCREC_FPR_ST_MODE_DOUBLE) { add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); if (indexed) add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); fmov(TEMP_GPR2.XReg, dataDReg); rev(TEMP_GPR2.XReg, TEMP_GPR2.XReg); str(TEMP_GPR2.XReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); } else if (mode == PPCREC_FPR_ST_MODE_UI32_FROM_PS0) { add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); if (indexed) add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); fmov(TEMP_GPR2.WReg, dataSReg); rev(TEMP_GPR2.WReg, TEMP_GPR2.WReg); str(TEMP_GPR2.WReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); } else { cemu_assert_suspicious(); cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_fpr_store(): Unsupported mode %d\n", mode); return false; } return true; } // FPR op FPR void AArch64GenContext_t::fpr_r_r(IMLInstruction* imlInstruction) { auto imlRegR = imlInstruction->op_fpr_r_r.regR; auto imlRegA = imlInstruction->op_fpr_r_r.regA; if (imlInstruction->operation == PPCREC_IML_OP_FPR_FLOAT_TO_INT) { fcvtzs(gpReg<WReg>(imlRegR), fpReg<DReg>(imlRegA)); return; } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_INT_TO_FLOAT) { scvtf(fpReg<DReg>(imlRegR), gpReg<WReg>(imlRegA)); return; } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT) { cemu_assert_debug(imlRegR.GetRegFormat() == IMLRegFormat::F64); // assuming target is always F64 for now // exact operation depends on size of types. Floats are automatically promoted to double if the target is F64 DReg regFprDReg = fpReg<DReg>(imlRegR); SReg regFprSReg = fpReg<SReg>(imlRegR); if (imlRegA.GetRegFormat() == IMLRegFormat::I32) { fmov(regFprSReg, gpReg<WReg>(imlRegA)); // float to double fcvt(regFprDReg, regFprSReg); } else if (imlRegA.GetRegFormat() == IMLRegFormat::I64) { fmov(regFprDReg, gpReg<XReg>(imlRegA)); } else { cemu_assert_unimplemented(); } return; } DReg regR = fpReg<DReg>(imlRegR); DReg regA = fpReg<DReg>(imlRegA); if (imlInstruction->operation == PPCREC_IML_OP_FPR_ASSIGN) { fmov(regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY) { fmul(regR, regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE) { fdiv(regR, regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD) { fadd(regR, regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SUB) { fsub(regR, regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_FCTIWZ) { fcvtzs(regR, regA); } else { cemu_assert_suspicious(); } } void AArch64GenContext_t::fpr_r_r_r(IMLInstruction* imlInstruction) { DReg regR = fpReg<DReg>(imlInstruction->op_fpr_r_r_r.regR); DReg regA = fpReg<DReg>(imlInstruction->op_fpr_r_r_r.regA); DReg regB = fpReg<DReg>(imlInstruction->op_fpr_r_r_r.regB); if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY) { fmul(regR, regA, regB); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD) { fadd(regR, regA, regB); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SUB) { fsub(regR, regA, regB); } else { cemu_assert_suspicious(); } } /* * FPR = op (fprA, fprB, fprC) */ void AArch64GenContext_t::fpr_r_r_r_r(IMLInstruction* imlInstruction) { DReg regR = fpReg<DReg>(imlInstruction->op_fpr_r_r_r_r.regR); DReg regA = fpReg<DReg>(imlInstruction->op_fpr_r_r_r_r.regA); DReg regB = fpReg<DReg>(imlInstruction->op_fpr_r_r_r_r.regB); DReg regC = fpReg<DReg>(imlInstruction->op_fpr_r_r_r_r.regC); if (imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT) { fcmp(regA, 0.0); fcsel(regR, regC, regB, Cond::GE); } else { cemu_assert_suspicious(); } } void AArch64GenContext_t::fpr_r(IMLInstruction* imlInstruction) { DReg regRDReg = fpReg<DReg>(imlInstruction->op_fpr_r.regR); SReg regRSReg = fpReg<SReg>(imlInstruction->op_fpr_r.regR); if (imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE) { fneg(regRDReg, regRDReg); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_LOAD_ONE) { fmov(regRDReg, 1.0); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ABS) { fabs(regRDReg, regRDReg); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS) { fabs(regRDReg, regRDReg); fneg(regRDReg, regRDReg); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM) { // convert to 32bit single fcvt(regRSReg, regRDReg); // convert back to 64bit double fcvt(regRDReg, regRSReg); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64) { // convert bottom to 64bit double fcvt(regRDReg, regRSReg); } else { cemu_assert_unimplemented(); } } Cond ImlFPCondToArm64Cond(IMLCondition cond) { switch (cond) { case IMLCondition::UNORDERED_GT: return Cond::GT; case IMLCondition::UNORDERED_LT: return Cond::MI; case IMLCondition::UNORDERED_EQ: return Cond::EQ; case IMLCondition::UNORDERED_U: return Cond::VS; default: { cemu_assert_suspicious(); return Cond::EQ; } } } void AArch64GenContext_t::fpr_compare(IMLInstruction* imlInstruction) { WReg regR = gpReg<WReg>(imlInstruction->op_fpr_compare.regR); DReg regA = fpReg<DReg>(imlInstruction->op_fpr_compare.regA); DReg regB = fpReg<DReg>(imlInstruction->op_fpr_compare.regB); auto cond = ImlFPCondToArm64Cond(imlInstruction->op_fpr_compare.cond); fcmp(regA, regB); cset(regR, cond); } void AArch64GenContext_t::call_imm(IMLInstruction* imlInstruction) { str(x30, AdrPreImm(sp, -16)); mov(TEMP_GPR1.XReg, imlInstruction->op_call_imm.callAddress); blr(TEMP_GPR1.XReg); ldr(x30, AdrPostImm(sp, 16)); } bool PPCRecompiler_generateAArch64Code(struct PPCRecFunction_t* PPCRecFunction, struct ppcImlGenContext_t* ppcImlGenContext) { AArch64Allocator allocator; AArch64GenContext_t aarch64GenContext{&allocator}; // generate iml instruction code bool codeGenerationFailed = false; for (IMLSegment* segIt : ppcImlGenContext->segmentList2) { if (codeGenerationFailed) break; segIt->x64Offset = aarch64GenContext.getSize(); aarch64GenContext.storeSegmentStart(segIt); for (size_t i = 0; i < segIt->imlList.size(); i++) { IMLInstruction* imlInstruction = segIt->imlList.data() + i; if (imlInstruction->type == PPCREC_IML_TYPE_R_NAME) { aarch64GenContext.r_name(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_NAME_R) { aarch64GenContext.name_r(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R) { if (!aarch64GenContext.r_r(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) { if (!aarch64GenContext.r_s32(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) { if (!aarch64GenContext.r_r_s32(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32_CARRY) { if (!aarch64GenContext.r_r_s32_carry(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) { if (!aarch64GenContext.r_r_r(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R_CARRY) { if (!aarch64GenContext.r_r_r_carry(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE) { aarch64GenContext.compare(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE_S32) { aarch64GenContext.compare_s32(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) { aarch64GenContext.cjump(imlInstruction, segIt); } else if (imlInstruction->type == PPCREC_IML_TYPE_JUMP) { aarch64GenContext.jump(segIt); } else if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) { aarch64GenContext.conditionalJumpCycleCheck(segIt); } else if (imlInstruction->type == PPCREC_IML_TYPE_MACRO) { if (!aarch64GenContext.macro(imlInstruction)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD) { if (!aarch64GenContext.load(imlInstruction, false)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED) { if (!aarch64GenContext.load(imlInstruction, true)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_STORE) { if (!aarch64GenContext.store(imlInstruction, false)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) { if (!aarch64GenContext.store(imlInstruction, true)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) { aarch64GenContext.atomic_cmp_store(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_CALL_IMM) { aarch64GenContext.call_imm(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_NO_OP) { // no op } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD) { if (!aarch64GenContext.fpr_load(imlInstruction, false)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) { if (!aarch64GenContext.fpr_load(imlInstruction, true)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE) { if (!aarch64GenContext.fpr_store(imlInstruction, false)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) { if (!aarch64GenContext.fpr_store(imlInstruction, true)) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R) { aarch64GenContext.fpr_r_r(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R) { aarch64GenContext.fpr_r_r_r(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R) { aarch64GenContext.fpr_r_r_r_r(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R) { aarch64GenContext.fpr_r(imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_COMPARE) { aarch64GenContext.fpr_compare(imlInstruction); } else { codeGenerationFailed = true; cemu_assert_suspicious(); cemuLog_log(LogType::Recompiler, "PPCRecompiler_generateAArch64Code(): Unsupported iml type {}", imlInstruction->type); } } } // handle failed code generation if (codeGenerationFailed) { return false; } if (!aarch64GenContext.processAllJumps()) { cemuLog_log(LogType::Recompiler, "PPCRecompiler_generateAArch64Code(): some jumps exceeded the +/-128MB offset."); return false; } aarch64GenContext.readyRE(); // set code PPCRecFunction->x86Code = aarch64GenContext.getCode<void*>(); PPCRecFunction->x86Size = aarch64GenContext.getMaxSize(); // set free disabled to skip freeing the code from the CodeGenerator destructor allocator.setFreeDisabled(true); return true; } void PPCRecompiler_cleanupAArch64Code(void* code, size_t size) { AArch64Allocator allocator; if (allocator.useProtect()) CodeArray::protect(code, size, CodeArray::PROTECT_RW); allocator.free(static_cast<uint32*>(code)); } void AArch64GenContext_t::enterRecompilerCode() { constexpr size_t STACK_SIZE = 160 /* x19 .. x30 + v8.d[0] .. v15.d[0] */; static_assert(STACK_SIZE % 16 == 0); sub(sp, sp, STACK_SIZE); mov(x9, sp); stp(x19, x20, AdrPostImm(x9, 16)); stp(x21, x22, AdrPostImm(x9, 16)); stp(x23, x24, AdrPostImm(x9, 16)); stp(x25, x26, AdrPostImm(x9, 16)); stp(x27, x28, AdrPostImm(x9, 16)); stp(x29, x30, AdrPostImm(x9, 16)); st4((v8.d - v11.d)[0], AdrPostImm(x9, 32)); st4((v12.d - v15.d)[0], AdrPostImm(x9, 32)); mov(HCPU_REG, x1); // call argument 2 mov(PPC_REC_INSTANCE_REG, (uint64)ppcRecompilerInstanceData); mov(MEM_BASE_REG, (uint64)memory_base); // branch to recFunc blr(x0); // call argument 1 mov(x9, sp); ldp(x19, x20, AdrPostImm(x9, 16)); ldp(x21, x22, AdrPostImm(x9, 16)); ldp(x23, x24, AdrPostImm(x9, 16)); ldp(x25, x26, AdrPostImm(x9, 16)); ldp(x27, x28, AdrPostImm(x9, 16)); ldp(x29, x30, AdrPostImm(x9, 16)); ld4((v8.d - v11.d)[0], AdrPostImm(x9, 32)); ld4((v12.d - v15.d)[0], AdrPostImm(x9, 32)); add(sp, sp, STACK_SIZE); ret(); } void AArch64GenContext_t::leaveRecompilerCode() { str(LR.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); ret(); } bool initializedInterfaceFunctions = false; AArch64GenContext_t enterRecompilerCode_ctx{}; AArch64GenContext_t leaveRecompilerCode_unvisited_ctx{}; AArch64GenContext_t leaveRecompilerCode_visited_ctx{}; void PPCRecompilerAArch64Gen_generateRecompilerInterfaceFunctions() { if (initializedInterfaceFunctions) return; initializedInterfaceFunctions = true; enterRecompilerCode_ctx.enterRecompilerCode(); enterRecompilerCode_ctx.readyRE(); PPCRecompiler_enterRecompilerCode = enterRecompilerCode_ctx.getCode<decltype(PPCRecompiler_enterRecompilerCode)>(); leaveRecompilerCode_unvisited_ctx.leaveRecompilerCode(); leaveRecompilerCode_unvisited_ctx.readyRE(); PPCRecompiler_leaveRecompilerCode_unvisited = leaveRecompilerCode_unvisited_ctx.getCode<decltype(PPCRecompiler_leaveRecompilerCode_unvisited)>(); leaveRecompilerCode_visited_ctx.leaveRecompilerCode(); leaveRecompilerCode_visited_ctx.readyRE(); PPCRecompiler_leaveRecompilerCode_visited = leaveRecompilerCode_visited_ctx.getCode<decltype(PPCRecompiler_leaveRecompilerCode_visited)>(); } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h ================================================ #pragma once #include "HW/Espresso/Recompiler/IML/IMLInstruction.h" #include "../PPCRecompiler.h" bool PPCRecompiler_generateAArch64Code(struct PPCRecFunction_t* PPCRecFunction, struct ppcImlGenContext_t* ppcImlGenContext); void PPCRecompiler_cleanupAArch64Code(void* code, size_t size); void PPCRecompilerAArch64Gen_generateRecompilerInterfaceFunctions(); // architecture specific constants namespace IMLArchAArch64 { static constexpr int PHYSREG_GPR_BASE = 0; static constexpr int PHYSREG_GPR_COUNT = 25; static constexpr int PHYSREG_FPR_BASE = PHYSREG_GPR_COUNT; static constexpr int PHYSREG_FPR_COUNT = 31; }; // namespace IMLArchAArch64 ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp ================================================ #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h" #include "../PPCRecompiler.h" #include "../PPCRecompilerIml.h" #include "BackendX64.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "util/MemMapper/MemMapper.h" #include "Common/cpu_features.h" #include <boost/container/static_vector.hpp> static x86Assembler64::GPR32 _reg32(IMLReg physReg) { cemu_assert_debug(physReg.GetRegFormat() == IMLRegFormat::I32); IMLRegID regId = physReg.GetRegID(); cemu_assert_debug(regId < 16); return (x86Assembler64::GPR32)regId; } static uint32 _reg64(IMLReg physReg) { cemu_assert_debug(physReg.GetRegFormat() == IMLRegFormat::I64); IMLRegID regId = physReg.GetRegID(); cemu_assert_debug(regId < 16); return regId; } uint32 _regF64(IMLReg physReg) { cemu_assert_debug(physReg.GetRegFormat() == IMLRegFormat::F64); IMLRegID regId = physReg.GetRegID(); cemu_assert_debug(regId >= IMLArchX86::PHYSREG_FPR_BASE && regId < IMLArchX86::PHYSREG_FPR_BASE+16); regId -= IMLArchX86::PHYSREG_FPR_BASE; return regId; } static x86Assembler64::GPR8_REX _reg8(IMLReg physReg) { cemu_assert_debug(physReg.GetRegFormat() == IMLRegFormat::I32); // for now these are represented as 32bit return (x86Assembler64::GPR8_REX)physReg.GetRegID(); } static x86Assembler64::GPR32 _reg32_from_reg8(x86Assembler64::GPR8_REX regId) { return (x86Assembler64::GPR32)regId; } static x86Assembler64::GPR8_REX _reg8_from_reg32(x86Assembler64::GPR32 regId) { return (x86Assembler64::GPR8_REX)regId; } static x86Assembler64::GPR8_REX _reg8_from_reg64(uint32 regId) { return (x86Assembler64::GPR8_REX)regId; } static x86Assembler64::GPR64 _reg64_from_reg32(x86Assembler64::GPR32 regId) { return (x86Assembler64::GPR64)regId; } X86Cond _x86Cond(IMLCondition imlCond) { switch (imlCond) { case IMLCondition::EQ: return X86_CONDITION_Z; case IMLCondition::NEQ: return X86_CONDITION_NZ; case IMLCondition::UNSIGNED_GT: return X86_CONDITION_NBE; case IMLCondition::UNSIGNED_LT: return X86_CONDITION_B; case IMLCondition::SIGNED_GT: return X86_CONDITION_NLE; case IMLCondition::SIGNED_LT: return X86_CONDITION_L; default: break; } cemu_assert_suspicious(); return X86_CONDITION_Z; } X86Cond _x86CondInverted(IMLCondition imlCond) { switch (imlCond) { case IMLCondition::EQ: return X86_CONDITION_NZ; case IMLCondition::NEQ: return X86_CONDITION_Z; case IMLCondition::UNSIGNED_GT: return X86_CONDITION_BE; case IMLCondition::UNSIGNED_LT: return X86_CONDITION_NB; case IMLCondition::SIGNED_GT: return X86_CONDITION_LE; case IMLCondition::SIGNED_LT: return X86_CONDITION_NL; default: break; } cemu_assert_suspicious(); return X86_CONDITION_Z; } X86Cond _x86Cond(IMLCondition imlCond, bool condIsInverted) { if (condIsInverted) return _x86CondInverted(imlCond); return _x86Cond(imlCond); } /* * Remember current instruction output offset for reloc * The instruction generated after this method has been called will be adjusted */ void PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext_t* x64GenContext, void* extraInfo = nullptr) { x64GenContext->relocateOffsetTable2.emplace_back(x64GenContext->emitter->GetWriteIndex(), extraInfo); } void PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext_t* x64GenContext, sint32 jumpInstructionOffset, sint32 destinationOffset) { uint8* instructionData = x64GenContext->emitter->GetBufferPtr() + jumpInstructionOffset; if (instructionData[0] == 0x0F && (instructionData[1] >= 0x80 && instructionData[1] <= 0x8F)) { // far conditional jump *(uint32*)(instructionData + 2) = (destinationOffset - (jumpInstructionOffset + 6)); } else if (instructionData[0] >= 0x70 && instructionData[0] <= 0x7F) { // short conditional jump sint32 distance = (sint32)((destinationOffset - (jumpInstructionOffset + 2))); cemu_assert_debug(distance >= -128 && distance <= 127); *(uint8*)(instructionData + 1) = (uint8)distance; } else if (instructionData[0] == 0xE9) { *(uint32*)(instructionData + 1) = (destinationOffset - (jumpInstructionOffset + 5)); } else if (instructionData[0] == 0xEB) { sint32 distance = (sint32)((destinationOffset - (jumpInstructionOffset + 2))); cemu_assert_debug(distance >= -128 && distance <= 127); *(uint8*)(instructionData + 1) = (uint8)distance; } else { assert_dbg(); } } void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFuncId) { void* prevRSPTemp = hCPU->rspTemp; if( hleFuncId == 0xFFD0 ) { hCPU->remainingCycles -= 500; // let subtract about 500 cycles for each HLE call hCPU->gpr[3] = 0; PPCInterpreter_nextInstruction(hCPU); return hCPU; } else { auto hleCall = PPCInterpreter_getHLECall(hleFuncId); cemu_assert(hleCall != nullptr); hleCall(hCPU); } hCPU->rspTemp = prevRSPTemp; return PPCInterpreter_getCurrentInstance(); } bool PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { if (imlInstruction->operation == PPCREC_IML_MACRO_B_TO_REG) { //x64Gen_int3(x64GenContext); uint32 branchDstReg = _reg32(imlInstruction->op_macro.paramReg); if(X86_REG_RDX != branchDstReg) x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RDX, branchDstReg); // potential optimization: Use branchDstReg directly if possible instead of moving to RDX/EDX // JMP [offset+RDX*(8/4)+R15] x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0xA4); x64Gen_writeU8(x64GenContext, 0x57); x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); return true; } else if( imlInstruction->operation == PPCREC_IML_MACRO_BL ) { // MOV DWORD [SPR_LinkRegister], newLR uint32 newLR = imlInstruction->op_macro.param + 4; x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.LR), newLR); // remember new instruction pointer in RDX uint32 newIP = imlInstruction->op_macro.param2; x64Gen_mov_reg64Low32_imm32(x64GenContext, X86_REG_RDX, newIP); // since RDX is constant we can use JMP [R15+const_offset] if jumpTableOffset+RDX*2 does not exceed the 2GB boundary uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; if (lookupOffset >= 0x80000000ULL) { // JMP [offset+RDX*(8/4)+R15] x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0xA4); x64Gen_writeU8(x64GenContext, 0x57); x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); } else { x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0xA7); x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); } return true; } else if( imlInstruction->operation == PPCREC_IML_MACRO_B_FAR ) { // remember new instruction pointer in RDX uint32 newIP = imlInstruction->op_macro.param2; x64Gen_mov_reg64Low32_imm32(x64GenContext, X86_REG_RDX, newIP); // Since RDX is constant we can use JMP [R15+const_offset] if jumpTableOffset+RDX*2 does not exceed the 2GB boundary uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; if (lookupOffset >= 0x80000000ULL) { // JMP [offset+RDX*(8/4)+R15] x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0xA4); x64Gen_writeU8(x64GenContext, 0x57); x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); } else { x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0xA7); x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); } return true; } else if( imlInstruction->operation == PPCREC_IML_MACRO_LEAVE ) { uint32 currentInstructionAddress = imlInstruction->op_macro.param; // remember PC value in REG_EDX x64Gen_mov_reg64Low32_imm32(x64GenContext, X86_REG_RDX, currentInstructionAddress); uint32 newIP = 0; // special value for recompiler exit uint64 lookupOffset = (uint64)&(((PPCRecompilerInstanceData_t*)NULL)->ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; // JMP [R15+offset] x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0xA7); x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); return true; } else if( imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK ) { x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, imlInstruction->op_macro.param2); x64Gen_int3(x64GenContext); return true; } else if( imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES ) { uint32 cycleCount = imlInstruction->op_macro.param; x64Gen_sub_mem32reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, remainingCycles), cycleCount); return true; } else if( imlInstruction->operation == PPCREC_IML_MACRO_HLE ) { uint32 ppcAddress = imlInstruction->op_macro.param; uint32 funcId = imlInstruction->op_macro.param2; // update instruction pointer x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer), ppcAddress); // set parameters x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RCX, REG_RESV_HCPU); x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RDX, funcId); // restore stackpointer from hCPU->rspTemp x64Emit_mov_reg64_mem64(x64GenContext, X86_REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); // reserve space on stack for call parameters x64Gen_sub_reg64_imm32(x64GenContext, X86_REG_RSP, 8*11); // must be uneven number in order to retain stack 0x10 alignment x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RBP, 0); // call HLE function x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RAX, (uint64)PPCRecompiler_virtualHLE); x64Gen_call_reg64(x64GenContext, X86_REG_RAX); // restore RSP to hCPU (from RAX, result of PPCRecompiler_virtualHLE) x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_HCPU, X86_REG_RAX); // MOV R15, ppcRecompilerInstanceData x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_RECDATA, (uint64)ppcRecompilerInstanceData); // MOV R13, memory_base x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_MEMBASE, (uint64)memory_base); // check if cycles where decreased beyond zero, if yes -> leave recompiler x64Gen_bt_mem8(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NOT_CARRY, 0); x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_RDX, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer)); // set EAX to 0 (we assume that ppcRecompilerDirectJumpTable[0] will be a recompiler escape function) x64Gen_xor_reg32_reg32(x64GenContext, X86_REG_RAX, X86_REG_RAX); // ADD RAX, REG_RESV_RECDATA x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, REG_RESV_RECDATA); // JMP [recompilerCallTable+EAX/4*8] x64Gen_jmp_memReg64(x64GenContext, X86_REG_RAX, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->emitter->GetWriteIndex()); // check if instruction pointer was changed // assign new instruction pointer to EAX x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_RAX, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer)); // remember instruction pointer in REG_EDX x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RDX, X86_REG_RAX); // EAX *= 2 x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, X86_REG_RAX); // ADD RAX, REG_RESV_RECDATA x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, REG_RESV_RECDATA); // JMP [ppcRecompilerDirectJumpTable+RAX/4*8] x64Gen_jmp_memReg64(x64GenContext, X86_REG_RAX, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); return true; } else { debug_printf("Unknown recompiler macro operation %d\n", imlInstruction->operation); assert_dbg(); } return false; } /* * Load from memory */ bool PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed) { cemu_assert_debug(imlInstruction->op_storeLoad.registerData.GetRegFormat() == IMLRegFormat::I32); cemu_assert_debug(imlInstruction->op_storeLoad.registerMem.GetRegFormat() == IMLRegFormat::I32); if (indexed) cemu_assert_debug(imlInstruction->op_storeLoad.registerMem2.GetRegFormat() == IMLRegFormat::I32); IMLRegID realRegisterData = imlInstruction->op_storeLoad.registerData.GetRegID(); IMLRegID realRegisterMem = imlInstruction->op_storeLoad.registerMem.GetRegID(); IMLRegID realRegisterMem2 = PPC_REC_INVALID_REGISTER; if( indexed ) realRegisterMem2 = imlInstruction->op_storeLoad.registerMem2.GetRegID(); if( indexed && realRegisterMem == realRegisterMem2 ) { return false; } if( indexed && realRegisterData == realRegisterMem2 ) { // for indexed memory access realRegisterData must not be the same register as the second memory register, // this can easily be worked around by swapping realRegisterMem and realRegisterMem2 std::swap(realRegisterMem, realRegisterMem2); } bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; bool switchEndian = imlInstruction->op_storeLoad.flags2.swapEndian; if( imlInstruction->op_storeLoad.copyWidth == 32 ) { if (indexed) { x64Gen_lea_reg64Low32_reg64Low32PlusReg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem, realRegisterMem2); } if( g_CPUFeatures.x86.movbe && switchEndian ) { if (indexed) { x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); } else { x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); } } else { if (indexed) { x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); if (switchEndian) x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); } else { x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); if (switchEndian) x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); } } } else if( imlInstruction->op_storeLoad.copyWidth == 16 ) { if (indexed) { x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } if(g_CPUFeatures.x86.movbe && switchEndian ) { x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); if( indexed && realRegisterMem != realRegisterData ) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } else { x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); if( indexed && realRegisterMem != realRegisterData ) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); if( switchEndian ) x64Gen_rol_reg64Low16_imm8(x64GenContext, realRegisterData, 8); } if( signExtend ) x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, realRegisterData, realRegisterData); else x64Gen_movZeroExtend_reg64Low32_reg64Low16(x64GenContext, realRegisterData, realRegisterData); } else if( imlInstruction->op_storeLoad.copyWidth == 8 ) { if( indexed ) x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); if( signExtend ) x64Gen_movSignExtend_reg64Low32_mem8Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); else x64Emit_movZX_reg32_mem8(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); if( indexed && realRegisterMem != realRegisterData ) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } else return false; return true; } /* * Write to memory */ bool PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed) { cemu_assert_debug(imlInstruction->op_storeLoad.registerData.GetRegFormat() == IMLRegFormat::I32); cemu_assert_debug(imlInstruction->op_storeLoad.registerMem.GetRegFormat() == IMLRegFormat::I32); if (indexed) cemu_assert_debug(imlInstruction->op_storeLoad.registerMem2.GetRegFormat() == IMLRegFormat::I32); IMLRegID realRegisterData = imlInstruction->op_storeLoad.registerData.GetRegID(); IMLRegID realRegisterMem = imlInstruction->op_storeLoad.registerMem.GetRegID(); IMLRegID realRegisterMem2 = PPC_REC_INVALID_REGISTER; if (indexed) realRegisterMem2 = imlInstruction->op_storeLoad.registerMem2.GetRegID(); if (indexed && realRegisterMem == realRegisterMem2) { return false; } if (indexed && realRegisterData == realRegisterMem2) { // for indexed memory access realRegisterData must not be the same register as the second memory register, // this can easily be worked around by swapping realRegisterMem and realRegisterMem2 std::swap(realRegisterMem, realRegisterMem2); } bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; bool swapEndian = imlInstruction->op_storeLoad.flags2.swapEndian; if (imlInstruction->op_storeLoad.copyWidth == 32) { uint32 valueRegister; if ((swapEndian == false || g_CPUFeatures.x86.movbe) && realRegisterMem != realRegisterData) { valueRegister = realRegisterData; } else { x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); valueRegister = REG_RESV_TEMP; } if (!g_CPUFeatures.x86.movbe && swapEndian) x64Gen_bswap_reg64Lower32bit(x64GenContext, valueRegister); if (indexed) x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); if (g_CPUFeatures.x86.movbe && swapEndian) x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); else x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); if (indexed) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } else if (imlInstruction->op_storeLoad.copyWidth == 16) { x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); if (swapEndian) x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); if (indexed) x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); if (indexed) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); // todo: Optimize this, e.g. by using MOVBE } else if (imlInstruction->op_storeLoad.copyWidth == 8) { if (indexed && realRegisterMem == realRegisterData) { x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); realRegisterData = REG_RESV_TEMP; } if (indexed) x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, realRegisterData); if (indexed) x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } else return false; return true; } void PPCRecompilerX64Gen_imlInstruction_atomic_cmp_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { auto regBoolOut = _reg32_from_reg8(_reg8(imlInstruction->op_atomic_compare_store.regBoolOut)); auto regEA = _reg32(imlInstruction->op_atomic_compare_store.regEA); auto regVal = _reg32(imlInstruction->op_atomic_compare_store.regWriteValue); auto regCmp = _reg32(imlInstruction->op_atomic_compare_store.regCompareValue); cemu_assert_debug(regBoolOut == X86_REG_EAX); cemu_assert_debug(regEA != X86_REG_EAX); cemu_assert_debug(regVal != X86_REG_EAX); cemu_assert_debug(regCmp != X86_REG_EAX); x64GenContext->emitter->MOV_dd(X86_REG_EAX, regCmp); x64GenContext->emitter->LockPrefix(); x64GenContext->emitter->CMPXCHG_dd_l(REG_RESV_MEMBASE, 0, _reg64_from_reg32(regEA), 1, regVal); x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_Z, regBoolOut); x64GenContext->emitter->AND_di32(regBoolOut, 1); // SETcc doesn't clear the upper bits so we do it manually here } void PPCRecompilerX64Gen_imlInstruction_call_imm(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { // the register allocator takes care of spilling volatile registers and moving parameters to the right registers, so we don't need to do any special handling here x64GenContext->emitter->SUB_qi8(X86_REG_RSP, 0x20); // reserve enough space for any parameters while keeping stack alignment of 16 intact x64GenContext->emitter->MOV_qi64(X86_REG_RAX, imlInstruction->op_call_imm.callAddress); x64GenContext->emitter->CALL_q(X86_REG_RAX); x64GenContext->emitter->ADD_qi8(X86_REG_RSP, 0x20); // a note about the stack pointer: // currently the code generated by generateEnterRecompilerCode makes sure the stack is 16 byte aligned, so we don't need to fix it up here } bool PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { auto regR = _reg32(imlInstruction->op_r_r.regR); auto regA = _reg32(imlInstruction->op_r_r.regA); if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) { // registerResult = registerA if (regR != regA) x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_ENDIAN_SWAP) { if (regA != regR) x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); // if movbe is available we can move and swap in a single instruction? x64Gen_bswap_reg64Lower32bit(x64GenContext, regR); } else if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32 ) { x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext, regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32) { x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, regR, reg32ToReg16(regA)); } else if( imlInstruction->operation == PPCREC_IML_OP_NOT ) { // copy register content if different registers if( regR != regA ) x64Gen_mov_reg64_reg64(x64GenContext, regR, regA); x64Gen_not_reg64Low32(x64GenContext, regR); } else if (imlInstruction->operation == PPCREC_IML_OP_NEG) { // copy register content if different registers if (regR != regA) x64Gen_mov_reg64_reg64(x64GenContext, regR, regA); x64Gen_neg_reg64Low32(x64GenContext, regR); } else if( imlInstruction->operation == PPCREC_IML_OP_CNTLZW ) { // count leading zeros // LZCNT instruction (part of SSE4, CPUID.80000001H:ECX.ABM[Bit 5]) if(g_CPUFeatures.x86.lzcnt) { x64Gen_lzcnt_reg64Low32_reg64Low32(x64GenContext, regR, regA); } else { x64Gen_test_reg64Low32_reg64Low32(x64GenContext, regA, regA); sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); x64Gen_bsr_reg64Low32_reg64Low32(x64GenContext, regR, regA); x64Gen_neg_reg64Low32(x64GenContext, regR); x64Gen_add_reg64Low32_imm32(x64GenContext, regR, 32-1); sint32 jumpInstructionOffset2 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->emitter->GetWriteIndex()); x64Gen_mov_reg64Low32_imm32(x64GenContext, regR, 32); PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->emitter->GetWriteIndex()); } } else if( imlInstruction->operation == PPCREC_IML_OP_X86_CMP) { x64GenContext->emitter->CMP_dd(regR, regA); } else { cemuLog_logDebug(LogType::Force, "PPCRecompilerX64Gen_imlInstruction_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); return false; } return true; } bool PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { auto regR = _reg32(imlInstruction->op_r_immS32.regR); if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN ) { x64Gen_mov_reg64Low32_imm32(x64GenContext, regR, (uint32)imlInstruction->op_r_immS32.immS32); } else if( imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE ) { cemu_assert_debug((imlInstruction->op_r_immS32.immS32 & 0x80) == 0); x64Gen_rol_reg64Low32_imm8(x64GenContext, regR, (uint8)imlInstruction->op_r_immS32.immS32); } else if( imlInstruction->operation == PPCREC_IML_OP_X86_CMP) { sint32 imm = imlInstruction->op_r_immS32.immS32; x64GenContext->emitter->CMP_di32(regR, imm); } else { cemuLog_logDebug(LogType::Force, "PPCRecompilerX64Gen_imlInstruction_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); return false; } return true; } bool PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { auto rRegResult = _reg32(imlInstruction->op_r_r_r.regR); auto rRegOperand1 = _reg32(imlInstruction->op_r_r_r.regA); auto rRegOperand2 = _reg32(imlInstruction->op_r_r_r.regB); if (imlInstruction->operation == PPCREC_IML_OP_ADD) { // registerResult = registerOperand1 + registerOperand2 if( (rRegResult == rRegOperand1) || (rRegResult == rRegOperand2) ) { // be careful not to overwrite the operand before we use it if( rRegResult == rRegOperand1 ) x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); else x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); } else { // copy operand1 to destination register before doing addition x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); } } else if( imlInstruction->operation == PPCREC_IML_OP_SUB ) { if( rRegOperand1 == rRegOperand2 ) { // result = operand1 - operand1 -> 0 x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); } else if( rRegResult == rRegOperand1 ) { // result = result - operand2 x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); } else if ( rRegResult == rRegOperand2 ) { // result = operand1 - result x64Gen_neg_reg64Low32(x64GenContext, rRegResult); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); } else { x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); } } else if (imlInstruction->operation == PPCREC_IML_OP_OR || imlInstruction->operation == PPCREC_IML_OP_AND || imlInstruction->operation == PPCREC_IML_OP_XOR) { if (rRegResult == rRegOperand2) std::swap(rRegOperand1, rRegOperand2); if (rRegResult != rRegOperand1) x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); if (imlInstruction->operation == PPCREC_IML_OP_OR) x64Gen_or_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); else if (imlInstruction->operation == PPCREC_IML_OP_AND) x64Gen_and_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); else x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); } else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED ) { // registerResult = registerOperand1 * registerOperand2 if( (rRegResult == rRegOperand1) || (rRegResult == rRegOperand2) ) { // be careful not to overwrite the operand before we use it if( rRegResult == rRegOperand1 ) x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); else x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); } else { // copy operand1 to destination register before doing multiplication x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); // add operand2 x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); } } else if( imlInstruction->operation == PPCREC_IML_OP_SLW || imlInstruction->operation == PPCREC_IML_OP_SRW ) { // registerResult = registerOperand1(rA) >> registerOperand2(rB) (up to 63 bits) if (g_CPUFeatures.x86.bmi2 && imlInstruction->operation == PPCREC_IML_OP_SRW) { // use BMI2 SHRX if available x64Gen_shrx_reg64_reg64_reg64(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); } else if (g_CPUFeatures.x86.bmi2 && imlInstruction->operation == PPCREC_IML_OP_SLW) { // use BMI2 SHLX if available x64Gen_shlx_reg64_reg64_reg64(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); x64Gen_and_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); // trim result to 32bit } else { // lazy and slow way to do shift by register without relying on ECX/CL or BMI2 x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); for (sint32 b = 0; b < 6; b++) { x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1 << b)); sint32 jumpInstructionOffset = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set if (b == 5) { x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); } else { if (imlInstruction->operation == PPCREC_IML_OP_SLW) x64Gen_shl_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1 << b)); else x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1 << b)); } PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->emitter->GetWriteIndex()); } x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); } } else if( imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE ) { // todo: Use BMI2 rotate if available // check if CL/ECX/RCX is available if( rRegResult != X86_REG_RCX && rRegOperand1 != X86_REG_RCX && rRegOperand2 != X86_REG_RCX ) { // swap operand 2 with RCX x64Gen_xchg_reg64_reg64(x64GenContext, X86_REG_RCX, rRegOperand2); // move operand 1 to temp register x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); // rotate x64Gen_rol_reg64Low32_cl(x64GenContext, REG_RESV_TEMP); // undo swap operand 2 with RCX x64Gen_xchg_reg64_reg64(x64GenContext, X86_REG_RCX, rRegOperand2); // copy to result register x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); } else { x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); // lazy and slow way to do shift by register without relying on ECX/CL for(sint32 b=0; b<5; b++) { x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1<<b)); sint32 jumpInstructionOffset = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set x64Gen_rol_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)); PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->emitter->GetWriteIndex()); } x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); } } else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S || imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U || imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) { if(g_CPUFeatures.x86.bmi2) { if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) x64Gen_sarx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) x64Gen_shrx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) x64Gen_shlx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); } else { cemu_assert_debug(rRegOperand2 == X86_REG_ECX); bool useTempReg = rRegResult == X86_REG_ECX && rRegOperand1 != X86_REG_ECX; auto origRegResult = rRegResult; if(useTempReg) { x64GenContext->emitter->MOV_dd(REG_RESV_TEMP, rRegOperand1); rRegResult = REG_RESV_TEMP; } if(rRegOperand1 != rRegResult) x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) x64GenContext->emitter->SAR_d_CL(rRegResult); else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) x64GenContext->emitter->SHR_d_CL(rRegResult); else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) x64GenContext->emitter->SHL_d_CL(rRegResult); if(useTempReg) x64GenContext->emitter->MOV_dd(origRegResult, REG_RESV_TEMP); } } else if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_DIVIDE_UNSIGNED ) { x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), X86_REG_EAX); x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), X86_REG_EDX); // mov operand 2 to temp register x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); // mov operand1 to EAX x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, X86_REG_EAX, rRegOperand1); // sign or zero extend EAX to EDX:EAX based on division sign mode if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED ) x64Gen_cdq(x64GenContext); else x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, X86_REG_EDX, X86_REG_EDX); // make sure we avoid division by zero x64Gen_test_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 3); // divide if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED ) x64Gen_idiv_reg64Low32(x64GenContext, REG_RESV_TEMP); else x64Gen_div_reg64Low32(x64GenContext, REG_RESV_TEMP); // result of division is now stored in EAX, move it to result register if( rRegResult != X86_REG_EAX ) x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, X86_REG_EAX); // restore EAX / EDX if( rRegResult != X86_REG_RAX ) x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EAX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); if( rRegResult != X86_REG_RDX ) x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EDX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); } else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED || imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED ) { x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), X86_REG_EAX); x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), X86_REG_EDX); // mov operand 2 to temp register x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); // mov operand1 to EAX x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, X86_REG_EAX, rRegOperand1); if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED ) { // zero extend EAX to EDX:EAX x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, X86_REG_EDX, X86_REG_EDX); } else { // sign extend EAX to EDX:EAX x64Gen_cdq(x64GenContext); } // multiply if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED ) x64Gen_imul_reg64Low32(x64GenContext, REG_RESV_TEMP); else x64Gen_mul_reg64Low32(x64GenContext, REG_RESV_TEMP); // result of multiplication is now stored in EDX:EAX, move it to result register if( rRegResult != X86_REG_EDX ) x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, X86_REG_EDX); // restore EAX / EDX if( rRegResult != X86_REG_RAX ) x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EAX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); if( rRegResult != X86_REG_RDX ) x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EDX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); } else { cemuLog_logDebug(LogType::Force, "PPCRecompilerX64Gen_imlInstruction_r_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); return false; } return true; } bool PPCRecompilerX64Gen_imlInstruction_r_r_r_carry(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { auto regR = _reg32(imlInstruction->op_r_r_r_carry.regR); auto regA = _reg32(imlInstruction->op_r_r_r_carry.regA); auto regB = _reg32(imlInstruction->op_r_r_r_carry.regB); auto regCarry = _reg32(imlInstruction->op_r_r_r_carry.regCarry); bool carryRegIsShared = regCarry == regA || regCarry == regB; cemu_assert_debug(regCarry != regR); // two outputs sharing the same register is undefined behavior switch (imlInstruction->operation) { case PPCREC_IML_OP_ADD: if (regB == regR) std::swap(regB, regA); if (regR != regA) x64GenContext->emitter->MOV_dd(regR, regA); if(!carryRegIsShared) x64GenContext->emitter->XOR_dd(regCarry, regCarry); x64GenContext->emitter->ADD_dd(regR, regB); x64GenContext->emitter->SETcc_b(X86_CONDITION_B, _reg8_from_reg32(regCarry)); // below condition checks carry flag if(carryRegIsShared) x64GenContext->emitter->AND_di8(regCarry, 1); // clear upper bits break; case PPCREC_IML_OP_ADD_WITH_CARRY: // assumes that carry is already correctly initialized as 0 or 1 if (regB == regR) std::swap(regB, regA); if (regR != regA) x64GenContext->emitter->MOV_dd(regR, regA); x64GenContext->emitter->BT_du8(regCarry, 0); // copy carry register to x86 carry flag x64GenContext->emitter->ADC_dd(regR, regB); x64GenContext->emitter->SETcc_b(X86_CONDITION_B, _reg8_from_reg32(regCarry)); break; default: cemu_assert_unimplemented(); return false; } return true; } bool PPCRecompilerX64Gen_IsSameCompare(IMLInstruction* imlInstructionA, IMLInstruction* imlInstructionB) { if(imlInstructionA->type != imlInstructionB->type) return false; if(imlInstructionA->type == PPCREC_IML_TYPE_COMPARE) return imlInstructionA->op_compare.regA == imlInstructionB->op_compare.regA && imlInstructionA->op_compare.regB == imlInstructionB->op_compare.regB; else if(imlInstructionA->type == PPCREC_IML_TYPE_COMPARE_S32) return imlInstructionA->op_compare_s32.regA == imlInstructionB->op_compare_s32.regA && imlInstructionA->op_compare_s32.immS32 == imlInstructionB->op_compare_s32.immS32; return false; } bool PPCRecompilerX64Gen_imlInstruction_compare_x(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, sint32& extraInstructionsProcessed) { extraInstructionsProcessed = 0; boost::container::static_vector<IMLInstruction*, 4> compareInstructions; compareInstructions.push_back(imlInstruction); for(sint32 i=1; i<4; i++) { IMLInstruction* nextIns = x64GenContext->GetNextInstruction(i); if(!nextIns || !PPCRecompilerX64Gen_IsSameCompare(imlInstruction, nextIns)) break; compareInstructions.push_back(nextIns); } auto OperandOverlapsWithR = [&](IMLInstruction* ins) -> bool { cemu_assert_debug(ins->type == PPCREC_IML_TYPE_COMPARE || ins->type == PPCREC_IML_TYPE_COMPARE_S32); if(ins->type == PPCREC_IML_TYPE_COMPARE) return _reg32_from_reg8(_reg8(ins->op_compare.regR)) == _reg32(ins->op_compare.regA) || _reg32_from_reg8(_reg8(ins->op_compare.regR)) == _reg32(ins->op_compare.regB); else /* PPCREC_IML_TYPE_COMPARE_S32 */ return _reg32_from_reg8(_reg8(ins->op_compare_s32.regR)) == _reg32(ins->op_compare_s32.regA); }; auto GetRegR = [](IMLInstruction* insn) { return insn->type == PPCREC_IML_TYPE_COMPARE ? _reg32_from_reg8(_reg8(insn->op_compare.regR)) : _reg32_from_reg8(_reg8(insn->op_compare_s32.regR)); }; // prefer XOR method for zeroing out registers if possible for(auto& it : compareInstructions) { if(OperandOverlapsWithR(it)) continue; auto regR = GetRegR(it); x64GenContext->emitter->XOR_dd(regR, regR); // zero bytes unaffected by SETcc } // emit the compare instruction if(imlInstruction->type == PPCREC_IML_TYPE_COMPARE) { auto regA = _reg32(imlInstruction->op_compare.regA); auto regB = _reg32(imlInstruction->op_compare.regB); x64GenContext->emitter->CMP_dd(regA, regB); } else if(imlInstruction->type == PPCREC_IML_TYPE_COMPARE_S32) { auto regA = _reg32(imlInstruction->op_compare_s32.regA); sint32 imm = imlInstruction->op_compare_s32.immS32; x64GenContext->emitter->CMP_di32(regA, imm); } // emit the SETcc instructions for(auto& it : compareInstructions) { auto regR = _reg8(it->op_compare.regR); X86Cond cond = _x86Cond(it->op_compare.cond); if(OperandOverlapsWithR(it)) x64GenContext->emitter->MOV_di32(_reg32_from_reg8(regR), 0); x64GenContext->emitter->SETcc_b(cond, regR); } extraInstructionsProcessed = (sint32)compareInstructions.size() - 1; return true; } bool PPCRecompilerX64Gen_imlInstruction_cjump2(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, IMLSegment* imlSegment) { auto regBool = _reg8(imlInstruction->op_conditional_jump.registerBool); bool mustBeTrue = imlInstruction->op_conditional_jump.mustBeTrue; x64GenContext->emitter->TEST_bb(regBool, regBool); PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, imlSegment->nextSegmentBranchTaken); x64GenContext->emitter->Jcc_j32(mustBeTrue ? X86_CONDITION_NZ : X86_CONDITION_Z, 0); return true; } void PPCRecompilerX64Gen_imlInstruction_x86_eflags_jcc(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, IMLSegment* imlSegment) { X86Cond cond = _x86Cond(imlInstruction->op_x86_eflags_jcc.cond, imlInstruction->op_x86_eflags_jcc.invertedCondition); PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, imlSegment->nextSegmentBranchTaken); x64GenContext->emitter->Jcc_j32(cond, 0); } bool PPCRecompilerX64Gen_imlInstruction_jump2(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, IMLSegment* imlSegment) { PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, imlSegment->nextSegmentBranchTaken); x64GenContext->emitter->JMP_j32(0); return true; } bool PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { auto regR = _reg32(imlInstruction->op_r_r_s32.regR); auto regA = _reg32(imlInstruction->op_r_r_s32.regA); uint32 immS32 = imlInstruction->op_r_r_s32.immS32; if( imlInstruction->operation == PPCREC_IML_OP_ADD ) { uint32 immU32 = (uint32)imlInstruction->op_r_r_s32.immS32; if(regR != regA) x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); x64Gen_add_reg64Low32_imm32(x64GenContext, regR, (uint32)immU32); } else if (imlInstruction->operation == PPCREC_IML_OP_SUB) { if (regR != regA) x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); x64Gen_sub_reg64Low32_imm32(x64GenContext, regR, immS32); } else if (imlInstruction->operation == PPCREC_IML_OP_AND || imlInstruction->operation == PPCREC_IML_OP_OR || imlInstruction->operation == PPCREC_IML_OP_XOR) { if (regR != regA) x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); if (imlInstruction->operation == PPCREC_IML_OP_AND) x64Gen_and_reg64Low32_imm32(x64GenContext, regR, immS32); else if (imlInstruction->operation == PPCREC_IML_OP_OR) x64Gen_or_reg64Low32_imm32(x64GenContext, regR, immS32); else // XOR x64Gen_xor_reg64Low32_imm32(x64GenContext, regR, immS32); } else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED ) { // registerResult = registerOperand * immS32 sint32 immS32 = (uint32)imlInstruction->op_r_r_s32.immS32; x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (sint64)immS32); // todo: Optimize if( regR != regA ) x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, regR, REG_RESV_TEMP); } else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT || imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U || imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) { if( regA != regR ) x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) x64Gen_shl_reg64Low32_imm8(x64GenContext, regR, imlInstruction->op_r_r_s32.immS32); else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) x64Gen_shr_reg64Low32_imm8(x64GenContext, regR, imlInstruction->op_r_r_s32.immS32); else // RIGHT_SHIFT_S x64Gen_sar_reg64Low32_imm8(x64GenContext, regR, imlInstruction->op_r_r_s32.immS32); } else { debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); return false; } return true; } bool PPCRecompilerX64Gen_imlInstruction_r_r_s32_carry(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { auto regR = _reg32(imlInstruction->op_r_r_s32_carry.regR); auto regA = _reg32(imlInstruction->op_r_r_s32_carry.regA); sint32 immS32 = imlInstruction->op_r_r_s32_carry.immS32; auto regCarry = _reg32(imlInstruction->op_r_r_s32_carry.regCarry); cemu_assert_debug(regCarry != regR); // we dont allow two different outputs sharing the same register bool delayCarryInit = regCarry == regA; switch (imlInstruction->operation) { case PPCREC_IML_OP_ADD: if(!delayCarryInit) x64GenContext->emitter->XOR_dd(regCarry, regCarry); if (regR != regA) x64GenContext->emitter->MOV_dd(regR, regA); x64GenContext->emitter->ADD_di32(regR, immS32); if(delayCarryInit) x64GenContext->emitter->MOV_di32(regCarry, 0); x64GenContext->emitter->SETcc_b(X86_CONDITION_B, _reg8_from_reg32(regCarry)); break; case PPCREC_IML_OP_ADD_WITH_CARRY: // assumes that carry is already correctly initialized as 0 or 1 cemu_assert_debug(regCarry != regR); if (regR != regA) x64GenContext->emitter->MOV_dd(regR, regA); x64GenContext->emitter->BT_du8(regCarry, 0); // copy carry register to x86 carry flag x64GenContext->emitter->ADC_di32(regR, immS32); x64GenContext->emitter->SETcc_b(X86_CONDITION_B, _reg8_from_reg32(regCarry)); break; default: cemu_assert_unimplemented(); return false; } return true; } bool PPCRecompilerX64Gen_imlInstruction_conditionalJumpCycleCheck(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { // some tests (all performed on a i7-4790K) // 1) DEC [mem] + JNS has significantly worse performance than BT + JNC (probably due to additional memory write and direct dependency) // 2) CMP [mem], 0 + JG has about equal (or slightly worse) performance than BT + JNC // BT x64Gen_bt_mem8(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative cemu_assert_debug(x64GenContext->currentSegment->GetBranchTaken()); PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, x64GenContext->currentSegment->GetBranchTaken()); x64Gen_jmpc_far(x64GenContext, X86_CONDITION_CARRY, 0); return true; } void PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { uint32 name = imlInstruction->op_r_name.name; if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::I64) { auto regR = _reg64(imlInstruction->op_r_name.regR); if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) { x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0)); } else if (name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0 + 999) { sint32 sprIndex = (name - PPCREC_NAME_SPR0); if (sprIndex == SPR_LR) x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.LR)); else if (sprIndex == SPR_CTR) x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.CTR)); else if (sprIndex == SPR_XER) x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.XER)); else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) { sint32 memOffset = offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0); x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, memOffset); } else assert_dbg(); } else if (name >= PPCREC_NAME_TEMPORARY && name < PPCREC_NAME_TEMPORARY + 4) { x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY)); } else if (name == PPCREC_NAME_XER_CA) { x64Emit_movZX_reg64_mem8(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_ca)); } else if (name == PPCREC_NAME_XER_SO) { x64Emit_movZX_reg64_mem8(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_so)); } else if (name >= PPCREC_NAME_CR && name <= PPCREC_NAME_CR_LAST) { x64Emit_movZX_reg64_mem8(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR)); } else if (name == PPCREC_NAME_CPU_MEMRES_EA) { x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemAddr)); } else if (name == PPCREC_NAME_CPU_MEMRES_VAL) { x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemValue)); } else assert_dbg(); } else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) { auto regR = _regF64(imlInstruction->op_r_name.regR); if (name >= PPCREC_NAME_FPR_HALF && name < (PPCREC_NAME_FPR_HALF + 64)) { sint32 regIndex = (name - PPCREC_NAME_FPR_HALF) / 2; sint32 pairIndex = (name - PPCREC_NAME_FPR_HALF) % 2; x64Gen_movsd_xmmReg_memReg64(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * regIndex + pairIndex * sizeof(double)); } else if (name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) { x64Gen_movupd_xmmReg_memReg128(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0)); } else { cemu_assert_debug(false); } } else DEBUG_BREAK; } void PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { uint32 name = imlInstruction->op_r_name.name; if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::I64) { auto regR = _reg64(imlInstruction->op_r_name.regR); if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) { x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0), regR); } else if (name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0 + 999) { uint32 sprIndex = (name - PPCREC_NAME_SPR0); if (sprIndex == SPR_LR) x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.LR), regR); else if (sprIndex == SPR_CTR) x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.CTR), regR); else if (sprIndex == SPR_XER) x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.XER), regR); else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) { sint32 memOffset = offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0); x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, memOffset, regR); } else assert_dbg(); } else if (name >= PPCREC_NAME_TEMPORARY && name < PPCREC_NAME_TEMPORARY + 4) { x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY), regR); } else if (name == PPCREC_NAME_XER_CA) { x64GenContext->emitter->MOV_bb_l(REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_ca), X86_REG_NONE, 0, _reg8_from_reg64(regR)); } else if (name == PPCREC_NAME_XER_SO) { x64GenContext->emitter->MOV_bb_l(REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_so), X86_REG_NONE, 0, _reg8_from_reg64(regR)); } else if (name >= PPCREC_NAME_CR && name <= PPCREC_NAME_CR_LAST) { x64GenContext->emitter->MOV_bb_l(REG_RESV_HCPU, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR), X86_REG_NONE, 0, _reg8_from_reg64(regR)); } else if (name == PPCREC_NAME_CPU_MEMRES_EA) { x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemAddr), regR); } else if (name == PPCREC_NAME_CPU_MEMRES_VAL) { x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemValue), regR); } else assert_dbg(); } else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) { auto regR = _regF64(imlInstruction->op_r_name.regR); uint32 name = imlInstruction->op_r_name.name; if (name >= PPCREC_NAME_FPR_HALF && name < (PPCREC_NAME_FPR_HALF + 64)) { sint32 regIndex = (name - PPCREC_NAME_FPR_HALF) / 2; sint32 pairIndex = (name - PPCREC_NAME_FPR_HALF) % 2; x64Gen_movsd_memReg64_xmmReg(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * regIndex + (pairIndex ? sizeof(double) : 0)); } else if (name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) { x64Gen_movupd_memReg128_xmmReg(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0)); } else { cemu_assert_debug(false); } } else DEBUG_BREAK; } uint8* codeMemoryBlock = nullptr; sint32 codeMemoryBlockIndex = 0; sint32 codeMemoryBlockSize = 0; std::mutex mtx_allocExecutableMemory; uint8* PPCRecompilerX86_allocateExecutableMemory(sint32 size) { std::lock_guard<std::mutex> lck(mtx_allocExecutableMemory); if( codeMemoryBlockIndex+size > codeMemoryBlockSize ) { // allocate new block codeMemoryBlockSize = std::max(1024*1024*4, size+1024); // 4MB (or more if the function is larger than 4MB) codeMemoryBlockIndex = 0; codeMemoryBlock = (uint8*)MemMapper::AllocateMemory(nullptr, codeMemoryBlockSize, MemMapper::PAGE_PERMISSION::P_RWX); } uint8* codeMem = codeMemoryBlock + codeMemoryBlockIndex; codeMemoryBlockIndex += size; // pad to 4 byte alignment while (codeMemoryBlockIndex & 3) { codeMemoryBlock[codeMemoryBlockIndex] = 0x90; codeMemoryBlockIndex++; } return codeMem; } bool PPCRecompiler_generateX64Code(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext) { x64GenContext_t x64GenContext{}; // generate iml instruction code bool codeGenerationFailed = false; for (IMLSegment* segIt : ppcImlGenContext->segmentList2) { x64GenContext.currentSegment = segIt; segIt->x64Offset = x64GenContext.emitter->GetWriteIndex(); for(size_t i=0; i<segIt->imlList.size(); i++) { x64GenContext.m_currentInstructionEmitIndex = i; IMLInstruction* imlInstruction = segIt->imlList.data() + i; if( imlInstruction->type == PPCREC_IML_TYPE_R_NAME ) { PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else if( imlInstruction->type == PPCREC_IML_TYPE_NAME_R ) { PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else if( imlInstruction->type == PPCREC_IML_TYPE_R_R ) { if( PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) { if (PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) { if (PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32_CARRY) { if (PPCRecompilerX64Gen_imlInstruction_r_r_s32_carry(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) { if (PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R_CARRY) { if (PPCRecompilerX64Gen_imlInstruction_r_r_r_carry(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) codeGenerationFailed = true; } else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE || imlInstruction->type == PPCREC_IML_TYPE_COMPARE_S32) { sint32 extraInstructionsProcessed; PPCRecompilerX64Gen_imlInstruction_compare_x(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, extraInstructionsProcessed); i += extraInstructionsProcessed; } else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) { if (PPCRecompilerX64Gen_imlInstruction_cjump2(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, segIt) == false) codeGenerationFailed = true; } else if(imlInstruction->type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) { PPCRecompilerX64Gen_imlInstruction_x86_eflags_jcc(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, segIt); } else if (imlInstruction->type == PPCREC_IML_TYPE_JUMP) { if (PPCRecompilerX64Gen_imlInstruction_jump2(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, segIt) == false) codeGenerationFailed = true; } else if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) { PPCRecompilerX64Gen_imlInstruction_conditionalJumpCycleCheck(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else if( imlInstruction->type == PPCREC_IML_TYPE_MACRO ) { if( PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) { codeGenerationFailed = true; } } else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD ) { if( PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) { codeGenerationFailed = true; } } else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED ) { if( PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) { codeGenerationFailed = true; } } else if( imlInstruction->type == PPCREC_IML_TYPE_STORE ) { if( PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) { codeGenerationFailed = true; } } else if( imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED ) { if( PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) { codeGenerationFailed = true; } } else if (imlInstruction->type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) { PPCRecompilerX64Gen_imlInstruction_atomic_cmp_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_CALL_IMM) { PPCRecompilerX64Gen_imlInstruction_call_imm(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else if( imlInstruction->type == PPCREC_IML_TYPE_NO_OP ) { // no op } else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD ) { if( PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) { codeGenerationFailed = true; } } else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED ) { if( PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) { codeGenerationFailed = true; } } else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE ) { if( PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) { codeGenerationFailed = true; } } else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED ) { if( PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) { codeGenerationFailed = true; } } else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R ) { PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R ) { PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R ) { PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R ) { PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_COMPARE) { PPCRecompilerX64Gen_imlInstruction_fpr_compare(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); } else { debug_printf("PPCRecompiler_generateX64Code(): Unsupported iml type 0x%x\n", imlInstruction->type); assert_dbg(); } } } // handle failed code generation if( codeGenerationFailed ) { return false; } // allocate executable memory uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.emitter->GetBuffer().size_bytes()); size_t baseAddress = (size_t)executableMemory; // fix relocs for(auto& relocIt : x64GenContext.relocateOffsetTable2) { // search for segment that starts with this offset uint32 ppcOffset = (uint32)(size_t)relocIt.extraInfo; uint32 x64Offset = 0xFFFFFFFF; IMLSegment* destSegment = (IMLSegment*)relocIt.extraInfo; x64Offset = destSegment->x64Offset; uint32 relocBase = relocIt.offset; uint8* relocInstruction = x64GenContext.emitter->GetBufferPtr()+relocBase; if( relocInstruction[0] == 0x0F && (relocInstruction[1] >= 0x80 && relocInstruction[1] <= 0x8F) ) { // Jcc relativeImm32 sint32 distanceNearJump = (sint32)((baseAddress + x64Offset) - (baseAddress + relocBase + 2)); if (distanceNearJump >= -128 && distanceNearJump < 127) // disabled { // convert to near Jcc *(uint8*)(relocInstruction + 0) = (uint8)(relocInstruction[1]-0x80 + 0x70); // patch offset *(uint8*)(relocInstruction + 1) = (uint8)distanceNearJump; // replace unused 4 bytes with NOP instruction relocInstruction[2] = 0x0F; relocInstruction[3] = 0x1F; relocInstruction[4] = 0x40; relocInstruction[5] = 0x00; } else { // patch offset *(uint32*)(relocInstruction + 2) = (uint32)((baseAddress + x64Offset) - (baseAddress + relocBase + 6)); } } else if( relocInstruction[0] == 0xE9 ) { // JMP relativeImm32 *(uint32*)(relocInstruction+1) = (uint32)((baseAddress+x64Offset)-(baseAddress+relocBase+5)); } else assert_dbg(); } // copy code to executable memory std::span<uint8> codeBuffer = x64GenContext.emitter->GetBuffer(); memcpy(executableMemory, codeBuffer.data(), codeBuffer.size_bytes()); // set code PPCRecFunction->x86Code = executableMemory; PPCRecFunction->x86Size = codeBuffer.size_bytes(); return true; } void PPCRecompilerX64Gen_generateEnterRecompilerCode() { x64GenContext_t x64GenContext{}; // start of recompiler entry function (15 regs) x64Gen_push_reg64(&x64GenContext, X86_REG_RAX); x64Gen_push_reg64(&x64GenContext, X86_REG_RCX); x64Gen_push_reg64(&x64GenContext, X86_REG_RDX); x64Gen_push_reg64(&x64GenContext, X86_REG_RBX); x64Gen_push_reg64(&x64GenContext, X86_REG_RBP); x64Gen_push_reg64(&x64GenContext, X86_REG_RDI); x64Gen_push_reg64(&x64GenContext, X86_REG_RSI); x64Gen_push_reg64(&x64GenContext, X86_REG_R8); x64Gen_push_reg64(&x64GenContext, X86_REG_R9); x64Gen_push_reg64(&x64GenContext, X86_REG_R10); x64Gen_push_reg64(&x64GenContext, X86_REG_R11); x64Gen_push_reg64(&x64GenContext, X86_REG_R12); x64Gen_push_reg64(&x64GenContext, X86_REG_R13); x64Gen_push_reg64(&x64GenContext, X86_REG_R14); x64Gen_push_reg64(&x64GenContext, X86_REG_R15); // 000000007775EF04 | E8 00 00 00 00 call +0x00 x64Gen_writeU8(&x64GenContext, 0xE8); x64Gen_writeU8(&x64GenContext, 0x00); x64Gen_writeU8(&x64GenContext, 0x00); x64Gen_writeU8(&x64GenContext, 0x00); x64Gen_writeU8(&x64GenContext, 0x00); //000000007775EF09 | 48 83 04 24 05 add qword ptr ss:[rsp],5 x64Gen_writeU8(&x64GenContext, 0x48); x64Gen_writeU8(&x64GenContext, 0x83); x64Gen_writeU8(&x64GenContext, 0x04); x64Gen_writeU8(&x64GenContext, 0x24); uint32 jmpPatchOffset = x64GenContext.emitter->GetWriteIndex(); x64Gen_writeU8(&x64GenContext, 0); // skip the distance until after the JMP x64Emit_mov_mem64_reg64(&x64GenContext, X86_REG_RDX, offsetof(PPCInterpreter_t, rspTemp), X86_REG_RSP); // MOV RSP, RDX (ppc interpreter instance) x64Gen_mov_reg64_reg64(&x64GenContext, REG_RESV_HCPU, X86_REG_RDX); // MOV R15, ppcRecompilerInstanceData x64Gen_mov_reg64_imm64(&x64GenContext, REG_RESV_RECDATA, (uint64)ppcRecompilerInstanceData); // MOV R13, memory_base x64Gen_mov_reg64_imm64(&x64GenContext, REG_RESV_MEMBASE, (uint64)memory_base); //JMP recFunc x64Gen_jmp_reg64(&x64GenContext, X86_REG_RCX); // call argument 1 x64GenContext.emitter->GetBuffer()[jmpPatchOffset] = (x64GenContext.emitter->GetWriteIndex() -(jmpPatchOffset-4)); //recompilerExit1: x64Gen_pop_reg64(&x64GenContext, X86_REG_R15); x64Gen_pop_reg64(&x64GenContext, X86_REG_R14); x64Gen_pop_reg64(&x64GenContext, X86_REG_R13); x64Gen_pop_reg64(&x64GenContext, X86_REG_R12); x64Gen_pop_reg64(&x64GenContext, X86_REG_R11); x64Gen_pop_reg64(&x64GenContext, X86_REG_R10); x64Gen_pop_reg64(&x64GenContext, X86_REG_R9); x64Gen_pop_reg64(&x64GenContext, X86_REG_R8); x64Gen_pop_reg64(&x64GenContext, X86_REG_RSI); x64Gen_pop_reg64(&x64GenContext, X86_REG_RDI); x64Gen_pop_reg64(&x64GenContext, X86_REG_RBP); x64Gen_pop_reg64(&x64GenContext, X86_REG_RBX); x64Gen_pop_reg64(&x64GenContext, X86_REG_RDX); x64Gen_pop_reg64(&x64GenContext, X86_REG_RCX); x64Gen_pop_reg64(&x64GenContext, X86_REG_RAX); // RET x64Gen_ret(&x64GenContext); uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.emitter->GetBuffer().size_bytes()); // copy code to executable memory memcpy(executableMemory, x64GenContext.emitter->GetBuffer().data(), x64GenContext.emitter->GetBuffer().size_bytes()); PPCRecompiler_enterRecompilerCode = (void ATTR_MS_ABI (*)(uint64,uint64))executableMemory; } void* PPCRecompilerX64Gen_generateLeaveRecompilerCode() { x64GenContext_t x64GenContext{}; // update instruction pointer // LR is in EDX x64Emit_mov_mem32_reg32(&x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer), X86_REG_EDX); // MOV RSP, [hCPU->rspTemp] x64Emit_mov_reg64_mem64(&x64GenContext, X86_REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); // RET x64Gen_ret(&x64GenContext); uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.emitter->GetBuffer().size_bytes()); // copy code to executable memory memcpy(executableMemory, x64GenContext.emitter->GetBuffer().data(), x64GenContext.emitter->GetBuffer().size_bytes()); return executableMemory; } void PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions() { PPCRecompilerX64Gen_generateEnterRecompilerCode(); PPCRecompiler_leaveRecompilerCode_unvisited = (void ATTR_MS_ABI (*)())PPCRecompilerX64Gen_generateLeaveRecompilerCode(); PPCRecompiler_leaveRecompilerCode_visited = (void ATTR_MS_ABI (*)())PPCRecompilerX64Gen_generateLeaveRecompilerCode(); cemu_assert_debug(PPCRecompiler_leaveRecompilerCode_unvisited != PPCRecompiler_leaveRecompilerCode_visited); } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h ================================================ #include "../PPCRecompiler.h" // todo - get rid of dependency #include "x86Emitter.h" struct x64RelocEntry_t { x64RelocEntry_t(uint32 offset, void* extraInfo) : offset(offset), extraInfo(extraInfo) {}; uint32 offset; void* extraInfo; }; struct x64GenContext_t { IMLSegment* currentSegment{}; x86Assembler64* emitter; sint32 m_currentInstructionEmitIndex; x64GenContext_t() { emitter = new x86Assembler64(); } ~x64GenContext_t() { delete emitter; } IMLInstruction* GetNextInstruction(sint32 relativeIndex = 1) { sint32 index = m_currentInstructionEmitIndex + relativeIndex; if(index < 0 || index >= (sint32)currentSegment->imlList.size()) return nullptr; return currentSegment->imlList.data() + index; } // relocate offsets std::vector<x64RelocEntry_t> relocateOffsetTable2; }; // reserved registers #define REG_RESV_TEMP (X86_REG_R14) #define REG_RESV_HCPU (X86_REG_RSP) #define REG_RESV_MEMBASE (X86_REG_R13) #define REG_RESV_RECDATA (X86_REG_R15) // reserved floating-point registers #define REG_RESV_FPR_TEMP (15) #define reg32ToReg16(__x) (__x) // deprecated // deprecated condition flags enum { X86_CONDITION_EQUAL, // or zero X86_CONDITION_NOT_EQUAL, // or not zero X86_CONDITION_SIGNED_LESS, // or not greater/equal X86_CONDITION_SIGNED_GREATER, // or not less/equal X86_CONDITION_SIGNED_LESS_EQUAL, // or not greater X86_CONDITION_SIGNED_GREATER_EQUAL, // or not less X86_CONDITION_UNSIGNED_BELOW, // or not above/equal X86_CONDITION_UNSIGNED_ABOVE, // or not below/equal X86_CONDITION_UNSIGNED_BELOW_EQUAL, // or not above X86_CONDITION_UNSIGNED_ABOVE_EQUAL, // or not below X86_CONDITION_CARRY, // carry flag must be set X86_CONDITION_NOT_CARRY, // carry flag must not be set X86_CONDITION_SIGN, // sign flag must be set X86_CONDITION_NOT_SIGN, // sign flag must not be set X86_CONDITION_PARITY, // parity flag must be set X86_CONDITION_NONE, // no condition, jump always }; bool PPCRecompiler_generateX64Code(struct PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext); void PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext_t* x64GenContext, sint32 jumpInstructionOffset, sint32 destinationOffset); void PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions(); void PPCRecompilerX64Gen_imlInstruction_fpr_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); void PPCRecompilerX64Gen_imlInstruction_fpr_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed); bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed); void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); void PPCRecompilerX64Gen_imlInstruction_fpr_compare(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); // ASM gen void x64Gen_writeU8(x64GenContext_t* x64GenContext, uint8 v); void x64Gen_writeU16(x64GenContext_t* x64GenContext, uint32 v); void x64Gen_writeU32(x64GenContext_t* x64GenContext, uint32 v); void x64Emit_mov_reg32_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset); void x64Emit_mov_mem32_reg32(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg); void x64Emit_mov_mem64_reg64(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg); void x64Emit_mov_reg64_mem64(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset); void x64Emit_mov_reg64_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset); void x64Emit_mov_mem32_reg64(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg); void x64Emit_mov_reg64_mem64(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset); void x64Emit_mov_reg32_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset); void x64Emit_mov_reg64b_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset); void x64Emit_movZX_reg32_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset); void x64Emit_movZX_reg64_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset); void x64Gen_movSignExtend_reg64Low32_mem8Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); void x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); void x64Gen_mov_mem64Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); void x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); void x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); void x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); void x64Gen_mov_mem32Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint32 dataImmU32); void x64Gen_mov_mem64Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint32 dataImmU32); void x64Gen_mov_mem8Reg64_imm8(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint8 dataImmU8); void x64Gen_mov_reg64_imm64(x64GenContext_t* x64GenContext, sint32 destRegister, uint64 immU64); void x64Gen_mov_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 destRegister, uint64 immU32); void x64Gen_mov_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_lea_reg64Low32_reg64Low32PlusReg64Low32(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64); void x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, uint32 conditionType, sint32 destRegister, sint32 srcRegister); void x64Gen_mov_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_xchg_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_movZeroExtend_reg64Low32_reg64Low16(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_movZeroExtend_reg64Low32_reg64Low8(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_or_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32); void x64Gen_and_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32); void x64Gen_mov_mem8Reg64_reg64Low8(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32); void x64Gen_add_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_add_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_add_reg64_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_add_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_sub_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_sub_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_sub_reg64_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_sub_mem32reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, sint32 memImmS32, uint64 immU32); void x64Gen_dec_mem32(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint32 memoryImmU32); void x64Gen_imul_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 operandRegister); void x64Gen_idiv_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister); void x64Gen_div_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister); void x64Gen_imul_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister); void x64Gen_mul_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister); void x64Gen_and_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_and_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_test_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_test_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_cmp_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, sint32 immS32); void x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_cmp_reg64Low32_mem32reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 memRegister, sint32 memImmS32); void x64Gen_or_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_or_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_xor_reg32_reg32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_xor_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_xor_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_rol_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); void x64Gen_rol_reg64Low32_cl(x64GenContext_t* x64GenContext, sint32 srcRegister); void x64Gen_rol_reg64Low16_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); void x64Gen_rol_reg64_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); void x64Gen_shl_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); void x64Gen_shr_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); void x64Gen_sar_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); void x64Gen_not_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister); void x64Gen_neg_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister); void x64Gen_cdq(x64GenContext_t* x64GenContext); void x64Gen_bswap_reg64Lower32bit(x64GenContext_t* x64GenContext, sint32 destRegister); void x64Gen_lzcnt_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_bsr_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_cmp_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, sint32 immS32); void x64Gen_setcc_mem8(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 memoryRegister, uint32 memoryImmU32); void x64Gen_setcc_reg64b(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 dataRegister); void x64Gen_bt_mem8(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint32 memoryImmU32, uint8 bitIndex); void x64Gen_cmc(x64GenContext_t* x64GenContext); void x64Gen_jmp_imm32(x64GenContext_t* x64GenContext, uint32 destImm32); void x64Gen_jmp_memReg64(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 immU32); void x64Gen_jmpc_far(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 relativeDest); void x64Gen_jmpc_near(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 relativeDest); void x64Gen_push_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister); void x64Gen_pop_reg64(x64GenContext_t* x64GenContext, sint32 destRegister); void x64Gen_jmp_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister); void x64Gen_call_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister); void x64Gen_ret(x64GenContext_t* x64GenContext); void x64Gen_int3(x64GenContext_t* x64GenContext); // floating-point (SIMD/SSE) gen void x64Gen_movaps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSource); void x64Gen_movupd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_movupd_memReg128_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_movddup_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_movddup_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_movhlps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_movsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_movsd_memReg64_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_movsd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_movlpd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc, uint8 imm8); void x64Gen_addsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_addpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_subsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_subpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_mulsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_mulpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_mulpd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_divsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_divpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_comisd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memoryReg, sint32 memImmS32); void x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_comiss_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memoryReg, sint32 memImmS32); void x64Gen_orps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32); void x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32); void x64Gen_andps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32); void x64Gen_andpd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_andps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_pcmpeqd_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32); void x64Gen_cvttpd2dq_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc); void x64Gen_cvtsi2sd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc); void x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_cvtpi2pd_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memReg, sint32 memImmS32); void x64Gen_cvtsd2si_reg64Low_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc); void x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc); void x64Gen_sqrtsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_sqrtpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_rcpss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_mulss_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_movd_xmmReg_reg64Low32(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc); void x64Gen_movd_reg64Low32_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc); void x64Gen_movq_xmmReg_reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc); void x64Gen_movq_reg64_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 xmmRegisterSrc); // AVX void x64Gen_avx_VPUNPCKHQDQ_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB); void x64Gen_avx_VUNPCKHPD_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB); void x64Gen_avx_VSUBPD_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB); // BMI void x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); void x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); void x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); void x64Gen_shrx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); void x64Gen_shrx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); void x64Gen_sarx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); void x64Gen_sarx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); void x64Gen_shlx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); void x64Gen_shlx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64AVX.cpp ================================================ #include "BackendX64.h" void _x64Gen_writeMODRMDeprecated(x64GenContext_t* x64GenContext, sint32 dataRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); void _x64Gen_vex128_nds(x64GenContext_t* x64GenContext, uint8 opcodeMap, uint8 additionalOperand, uint8 pp, uint8 vex_ext, uint8 vex_r, uint8 vex_b, uint8 opcode) { if(vex_b != 0) x64Gen_writeU8(x64GenContext, 0xC4); // three byte VEX else x64Gen_writeU8(x64GenContext, 0xC5); // two byte VEX if (vex_b != 0) { uint8 vex_x = 0; x64Gen_writeU8(x64GenContext, (vex_r ? 0x00 : 0x80) | (vex_x ? 0x00 : 0x40) | (vex_b ? 0x00 : 0x20) | 1); } x64Gen_writeU8(x64GenContext, (vex_ext<<7) | (((~additionalOperand)&0xF)<<3) | pp); x64Gen_writeU8(x64GenContext, opcode); } #define VEX_PP_0F 0 #define VEX_PP_66_0F 1 #define VEX_PP_F3_0F 2 #define VEX_PP_F2_0F 3 void x64Gen_avx_VPUNPCKHQDQ_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB) { _x64Gen_vex128_nds(x64GenContext, 0, srcRegisterA, VEX_PP_66_0F, dstRegister < 8 ? 1 : 0, (dstRegister >= 8 && srcRegisterB >= 8) ? 1 : 0, srcRegisterB < 8 ? 0 : 1, 0x6D); x64Gen_writeU8(x64GenContext, 0xC0 + (srcRegisterB & 7) + (dstRegister & 7) * 8); } void x64Gen_avx_VUNPCKHPD_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB) { _x64Gen_vex128_nds(x64GenContext, 0, srcRegisterA, VEX_PP_66_0F, dstRegister < 8 ? 1 : 0, (dstRegister >= 8 && srcRegisterB >= 8) ? 1 : 0, srcRegisterB < 8 ? 0 : 1, 0x15); x64Gen_writeU8(x64GenContext, 0xC0 + (srcRegisterB & 7) + (dstRegister & 7) * 8); } void x64Gen_avx_VSUBPD_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB) { _x64Gen_vex128_nds(x64GenContext, 0, srcRegisterA, VEX_PP_66_0F, dstRegister < 8 ? 1 : 0, (dstRegister >= 8 && srcRegisterB >= 8) ? 1 : 0, srcRegisterB < 8 ? 0 : 1, 0x5C); x64Gen_writeU8(x64GenContext, 0xC0 + (srcRegisterB & 7) + (dstRegister & 7) * 8); } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64BMI.cpp ================================================ #include "BackendX64.h" void _x64Gen_writeMODRMDeprecated(x64GenContext_t* x64GenContext, sint32 dataRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); void x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) { // MOVBE <dstReg64> (low dword), DWORD [<reg64> + <reg64> + <imm64>] if( dstRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x47); else if( memRegisterA64 >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x43); else if( dstRegister >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x42); else if( dstRegister >= 8 && memRegisterA64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( dstRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( memRegisterA64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); else if( memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x42); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x38); x64Gen_writeU8(x64GenContext, 0xF0); _x64Gen_writeMODRMDeprecated(x64GenContext, dstRegister, memRegisterA64, memRegisterB64, memImmS32); } void x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) { // MOVBE <dstReg64> (low word), WORD [<reg64> + <reg64> + <imm64>] // note: Unlike the 32bit version this instruction does not set the upper 32bits of the 64bit register to 0 x64Gen_writeU8(x64GenContext, 0x66); // 16bit prefix x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, dstRegister, memRegisterA64, memRegisterB64, memImmS32); } void x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister) { // MOVBE DWORD [<reg64> + <reg64> + <imm64>], <srcReg64> (low dword) if( srcRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x47); else if( memRegisterA64 >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x43); else if( srcRegister >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x42); else if( srcRegister >= 8 && memRegisterA64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( memRegisterA64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); else if( memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x42); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x38); x64Gen_writeU8(x64GenContext, 0xF1); _x64Gen_writeMODRMDeprecated(x64GenContext, srcRegister, memRegisterA64, memRegisterB64, memImmS32); } void x64Gen_shrx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) { // SHRX reg64, reg64, reg64 x64Gen_writeU8(x64GenContext, 0xC4); x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); x64Gen_writeU8(x64GenContext, 0xFB - registerB * 8); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); } void x64Gen_shrx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) { x64Gen_writeU8(x64GenContext, 0xC4); x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); x64Gen_writeU8(x64GenContext, 0x7B - registerB * 8); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); } void x64Gen_sarx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) { // SARX reg64, reg64, reg64 x64Gen_writeU8(x64GenContext, 0xC4); x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); x64Gen_writeU8(x64GenContext, 0xFA - registerB * 8); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); } void x64Gen_sarx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) { x64Gen_writeU8(x64GenContext, 0xC4); x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); x64Gen_writeU8(x64GenContext, 0x7A - registerB * 8); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); } void x64Gen_shlx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) { // SHLX reg64, reg64, reg64 x64Gen_writeU8(x64GenContext, 0xC4); x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); x64Gen_writeU8(x64GenContext, 0xF9 - registerB * 8); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); } void x64Gen_shlx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) { x64Gen_writeU8(x64GenContext, 0xC4); x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); x64Gen_writeU8(x64GenContext, 0x79 - registerB * 8); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp ================================================ #include "../PPCRecompiler.h" #include "../IML/IML.h" #include "BackendX64.h" #include "Common/cpu_features.h" uint32 _regF64(IMLReg physReg); uint32 _regI32(IMLReg r) { cemu_assert_debug(r.GetRegFormat() == IMLRegFormat::I32); return (uint32)r.GetRegID(); } static x86Assembler64::GPR32 _reg32(sint8 physRegId) { return (x86Assembler64::GPR32)physRegId; } static x86Assembler64::GPR8_REX _reg8(IMLReg r) { cemu_assert_debug(r.GetRegFormat() == IMLRegFormat::I32); // currently bool regs are implemented as 32bit registers return (x86Assembler64::GPR8_REX)r.GetRegID(); } static x86Assembler64::GPR32 _reg32_from_reg8(x86Assembler64::GPR8_REX regId) { return (x86Assembler64::GPR32)regId; } static x86Assembler64::GPR8_REX _reg8_from_reg32(x86Assembler64::GPR32 regId) { return (x86Assembler64::GPR8_REX)regId; } // load from memory bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed) { sint32 realRegisterXMM = _regF64(imlInstruction->op_storeLoad.registerData); sint32 realRegisterMem = _regI32(imlInstruction->op_storeLoad.registerMem); sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER; if( indexed ) realRegisterMem2 = _regI32(imlInstruction->op_storeLoad.registerMem2); uint8 mode = imlInstruction->op_storeLoad.mode; if( mode == PPCREC_FPR_LD_MODE_SINGLE ) { // load byte swapped single into temporary FPR if( indexed ) { x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); if(g_CPUFeatures.x86.movbe) x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); else x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); } else { if(g_CPUFeatures.x86.movbe) x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); else x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); } if(g_CPUFeatures.x86.movbe == false ) x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); x64Gen_movd_xmmReg_reg64Low32(x64GenContext, realRegisterXMM, REG_RESV_TEMP); if (imlInstruction->op_storeLoad.flags2.notExpanded) { // leave value as single } else { x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, realRegisterXMM); } } else if( mode == PPCREC_FPR_LD_MODE_DOUBLE ) { if( g_CPUFeatures.x86.avx ) { if( indexed ) { // calculate offset x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); // load value x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); x64Gen_movq_xmmReg_reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP); x64Gen_movsd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_FPR_TEMP); } else { x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); x64Gen_movq_xmmReg_reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP); x64Gen_movsd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_FPR_TEMP); } } else { if( indexed ) { // calculate offset x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); // load double low part to temporaryFPR x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); // calculate offset again x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); // load double high part to temporaryFPR x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+4); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); // load double from temporaryFPR x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); } else { // load double low part to temporaryFPR x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); // load double high part to temporaryFPR x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+4); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); // load double from temporaryFPR x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); } } } else { return false; } return true; } // store to memory bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed) { sint32 realRegisterXMM = _regF64(imlInstruction->op_storeLoad.registerData); sint32 realRegisterMem = _regI32(imlInstruction->op_storeLoad.registerMem); sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER; if( indexed ) realRegisterMem2 = _regI32(imlInstruction->op_storeLoad.registerMem2); uint8 mode = imlInstruction->op_storeLoad.mode; if( mode == PPCREC_FPR_ST_MODE_SINGLE ) { if (imlInstruction->op_storeLoad.flags2.notExpanded) { // value is already in single format x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, realRegisterXMM); } else { x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, realRegisterXMM); x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); } if(g_CPUFeatures.x86.movbe == false ) x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); if( indexed ) { if( realRegisterMem == realRegisterMem2 ) assert_dbg(); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } if(g_CPUFeatures.x86.movbe) x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); else x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); if( indexed ) { x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } } else if( mode == PPCREC_FPR_ST_MODE_DOUBLE ) { if( indexed ) { if( realRegisterMem == realRegisterMem2 ) assert_dbg(); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); // store double low part x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+4, REG_RESV_TEMP); // store double high part x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0, REG_RESV_TEMP); if( indexed ) { x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } } else if( mode == PPCREC_FPR_ST_MODE_UI32_FROM_PS0 ) { x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, realRegisterXMM); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); if( indexed ) { cemu_assert_debug(realRegisterMem == realRegisterMem2); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } else { x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); } } else { debug_printf("PPCRecompilerX64Gen_imlInstruction_fpr_store(): Unsupported mode %d\n", mode); return false; } return true; } // FPR op FPR void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { if( imlInstruction->operation == PPCREC_IML_OP_FPR_FLOAT_TO_INT ) { uint32 regGpr = _regI32(imlInstruction->op_fpr_r_r.regR); uint32 regFpr = _regF64(imlInstruction->op_fpr_r_r.regA); x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext, regGpr, regFpr); return; } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_INT_TO_FLOAT ) { uint32 regFpr = _regF64(imlInstruction->op_fpr_r_r.regR); uint32 regGpr = _regI32(imlInstruction->op_fpr_r_r.regA); x64Gen_cvtsi2sd_xmmReg_xmmReg(x64GenContext, regFpr, regGpr); return; } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT) { cemu_assert_debug(imlInstruction->op_fpr_r_r.regR.GetRegFormat() == IMLRegFormat::F64); // assuming target is always F64 for now cemu_assert_debug(imlInstruction->op_fpr_r_r.regA.GetRegFormat() == IMLRegFormat::I32); // supporting only 32bit floats as input for now // exact operation depends on size of types. Floats are automatically promoted to double if the target is F64 uint32 regFpr = _regF64(imlInstruction->op_fpr_r_r.regR); if (imlInstruction->op_fpr_r_r.regA.GetRegFormat() == IMLRegFormat::I32) { uint32 regGpr = _regI32(imlInstruction->op_fpr_r_r.regA); x64Gen_movq_xmmReg_reg64(x64GenContext, regFpr, regGpr); // using reg32 as reg64 param here is ok. We'll refactor later // float to double x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regFpr, regFpr); } else { cemu_assert_unimplemented(); } return; } uint32 regR = _regF64(imlInstruction->op_fpr_r_r.regR); uint32 regA = _regF64(imlInstruction->op_fpr_r_r.regA); if( imlInstruction->operation == PPCREC_IML_OP_FPR_ASSIGN ) { x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY ) { x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE ) { x64Gen_divsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ADD ) { x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB ) { x64Gen_subsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_FCTIWZ ) { x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext, REG_RESV_TEMP, regA); x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); // move to FPR register x64Gen_movq_xmmReg_reg64(x64GenContext, regR, REG_RESV_TEMP); } else { assert_dbg(); } } /* * FPR = op (fprA, fprB) */ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { uint32 regR = _regF64(imlInstruction->op_fpr_r_r_r.regR); uint32 regA = _regF64(imlInstruction->op_fpr_r_r_r.regA); uint32 regB = _regF64(imlInstruction->op_fpr_r_r_r.regB); if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY) { if (regR == regA) { x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regB); } else if (regR == regB) { x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else { x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regA); x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regB); } } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD) { // todo: Use AVX 3-operand VADDSD if available if (regR == regA) { x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regB); } else if (regR == regB) { x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else { x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regB); } } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB ) { if( regR == regA ) { x64Gen_subsd_xmmReg_xmmReg(x64GenContext, regR, regB); } else if( regR == regB ) { x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); x64Gen_subsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regB); x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); } else { x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regA); x64Gen_subsd_xmmReg_xmmReg(x64GenContext, regR, regB); } } else assert_dbg(); } /* * FPR = op (fprA, fprB, fprC) */ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { uint32 regR = _regF64(imlInstruction->op_fpr_r_r_r_r.regR); uint32 regA = _regF64(imlInstruction->op_fpr_r_r_r_r.regA); uint32 regB = _regF64(imlInstruction->op_fpr_r_r_r_r.regB); uint32 regC = _regF64(imlInstruction->op_fpr_r_r_r_r.regC); if( imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT ) { x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, regA, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, 0); // select C x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regC); sint32 jumpInstructionOffset2 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); // select B PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->emitter->GetWriteIndex()); x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regB); // end PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->emitter->GetWriteIndex()); } else assert_dbg(); } void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { uint32 regR = _regF64(imlInstruction->op_fpr_r.regR); if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE ) { x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom)); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_LOAD_ONE ) { x64Gen_movsd_xmmReg_memReg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble1_1)); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ABS ) { x64Gen_andps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_andAbsMaskBottom)); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS ) { x64Gen_orps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom)); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM ) { // convert to 32bit single x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, regR, regR); // convert back to 64bit double x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regR, regR); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64) { // convert bottom to 64bit double x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regR, regR); } else { cemu_assert_unimplemented(); } } void PPCRecompilerX64Gen_imlInstruction_fpr_compare(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { auto regR = _reg8(imlInstruction->op_fpr_compare.regR); auto regA = _regF64(imlInstruction->op_fpr_compare.regA); auto regB = _regF64(imlInstruction->op_fpr_compare.regB); x64GenContext->emitter->XOR_dd(_reg32_from_reg8(regR), _reg32_from_reg8(regR)); x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext, regA, regB); if (imlInstruction->op_fpr_compare.cond == IMLCondition::UNORDERED_GT) { // GT case can be covered with a single SETnbe which checks CF==0 && ZF==0 (unordered sets both) x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_NBE, regR); return; } else if (imlInstruction->op_fpr_compare.cond == IMLCondition::UNORDERED_U) { // unordered case can be checked via PF x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_PE, regR); return; } // remember unordered state auto regTmp = _reg32_from_reg8(_reg32(REG_RESV_TEMP)); x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_PO, regTmp); // by reversing the parity we can avoid having to XOR the value for masking the LT/EQ conditions X86Cond x86Cond; switch (imlInstruction->op_fpr_compare.cond) { case IMLCondition::UNORDERED_LT: x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_B, regR); break; case IMLCondition::UNORDERED_EQ: x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_Z, regR); break; default: cemu_assert_unimplemented(); } x64GenContext->emitter->AND_bb(_reg8_from_reg32(regR), _reg8_from_reg32(regTmp)); // if unordered (PF=1) then force LT/GT/EQ to zero } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64Gen.cpp ================================================ #include "BackendX64.h" // x86/x64 extension opcodes that could be useful: // ANDN // mulx, rorx, sarx, shlx, shrx // PDEP, PEXT void x64Gen_writeU8(x64GenContext_t* x64GenContext, uint8 v) { x64GenContext->emitter->_emitU8(v); } void x64Gen_writeU16(x64GenContext_t* x64GenContext, uint32 v) { x64GenContext->emitter->_emitU16(v); } void x64Gen_writeU32(x64GenContext_t* x64GenContext, uint32 v) { x64GenContext->emitter->_emitU32(v); } void x64Gen_writeU64(x64GenContext_t* x64GenContext, uint64 v) { x64GenContext->emitter->_emitU64(v); } #include "X64Emit.hpp" void _x64Gen_writeMODRMDeprecated(x64GenContext_t* x64GenContext, sint32 dataRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) { bool forceUseOffset = false; if ((memRegisterA64 & 7) == 5) { // RBP and R13 have no memImmS32 == 0 encoding, therefore we need to use a 1 byte offset with the value 0 forceUseOffset = true; } if (memRegisterB64 == X86_REG_NONE) { // memRegisterA64 + memImmS32 uint8 modRM = (dataRegister & 7) * 8 + (memRegisterA64 & 7); if (forceUseOffset && memImmS32 == 0) { // 1 byte offset modRM |= (1 << 6); } if (memImmS32 == 0) { // no offset modRM |= (0 << 6); } else if (memImmS32 >= -128 && memImmS32 <= 127) { // 1 byte offset modRM |= (1 << 6); } else { // 4 byte offset modRM |= (2 << 6); } x64Gen_writeU8(x64GenContext, modRM); // SIB byte if ((memRegisterA64 & 7) == 4) // RSP and R12 { x64Gen_writeU8(x64GenContext, 0x24); } // offset if (((modRM >> 6) & 3) == 0) ; // no offset else if (((modRM >> 6) & 3) == 1) x64Gen_writeU8(x64GenContext, (uint8)memImmS32); else if (((modRM >> 6) & 3) == 2) x64Gen_writeU32(x64GenContext, (uint32)memImmS32); else assert_dbg(); return; } // note: Swapping mem register A and mem register B does not work because the instruction prefix defines the register group which might not match (e.g. regA in r0-r8 range and regB in RAX-RDI range) if( (memRegisterA64&7) == 4 ) { assert_dbg(); //sint32 temp = memRegisterA64; //memRegisterA64 = memRegisterB64; //memRegisterB64 = temp; } //if( (memRegisterA64&7) == 5 ) //{ // sint32 temp = memRegisterA64; // memRegisterA64 = memRegisterB64; // memRegisterB64 = temp; //} if( (memRegisterA64&7) == 4 ) assert_dbg(); uint8 modRM = (0x04<<0)+((dataRegister&7)<<3); if( forceUseOffset && memImmS32 == 0 ) { // 1 byte offset modRM |= (1<<6); } if( memImmS32 == 0 ) { // no offset modRM |= (0<<6); } else if( memImmS32 >= -128 && memImmS32 <= 127 ) { // 1 byte offset modRM |= (1<<6); } else { // 4 byte offset modRM |= (2<<6); } x64Gen_writeU8(x64GenContext, modRM); // sib byte x64Gen_writeU8(x64GenContext, 0x00+(memRegisterA64&7)+(memRegisterB64&7)*8); // offset if( ((modRM>>6)&3) == 0 ) ; // no offset else if( ((modRM>>6)&3) == 1 ) x64Gen_writeU8(x64GenContext, (uint8)memImmS32); else if( ((modRM>>6)&3) == 2 ) x64Gen_writeU32(x64GenContext, (uint32)memImmS32); else assert_dbg(); } void x64Emit_mov_reg32_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset) { x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8B>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memReg64(memBaseReg64, memOffset)); } void x64Emit_mov_mem32_reg32(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg) { x64Gen_writeMODRM_dyn<x64_opc_1byte_rev<0x89>>(x64GenContext, x64MODRM_opr_memReg64(memBaseReg64, memOffset), x64MODRM_opr_reg64(srcReg)); } void x64Emit_mov_mem64_reg64(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg) { x64Gen_writeMODRM_dyn<x64_opc_1byte_rev<0x89, true>>(x64GenContext, x64MODRM_opr_memReg64(memBaseReg64, memOffset), x64MODRM_opr_reg64(srcReg)); } void x64Emit_mov_reg64_mem64(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset) { x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8B, true>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memReg64(memBaseReg64, memOffset)); } void x64Emit_mov_reg64_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset) { x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8B>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memReg64(memBaseReg64, memOffset)); } void x64Emit_mov_mem32_reg64(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg) { x64Gen_writeMODRM_dyn<x64_opc_1byte_rev<0x89>>(x64GenContext, x64MODRM_opr_memReg64(memBaseReg64, memOffset), x64MODRM_opr_reg64(srcReg)); } void x64Emit_mov_reg64_mem64(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset) { x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8B, true>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memRegPlusReg(memBaseReg64, memIndexReg64, memOffset)); } void x64Emit_mov_reg32_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset) { x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8B>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memRegPlusReg(memBaseReg64, memIndexReg64, memOffset)); } void x64Emit_mov_reg64b_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset) { x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8A>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memRegPlusReg(memBaseReg64, memIndexReg64, memOffset)); } void x64Emit_movZX_reg32_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset) { x64Gen_writeMODRM_dyn<x64_opc_2byte<0x0F,0xB6>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memRegPlusReg(memBaseReg64, memIndexReg64, memOffset)); } void x64Emit_movZX_reg64_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset) { x64Gen_writeMODRM_dyn<x64_opc_2byte<0x0F, 0xB6>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memReg64(memBaseReg64, memOffset)); } void x64Gen_movSignExtend_reg64Low32_mem8Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) { // MOVSX <dstReg64> (low dword), BYTE [<reg64> + <reg64> + <imm64>] if (dstRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8) x64Gen_writeU8(x64GenContext, 0x47); else if (memRegisterA64 >= 8 && memRegisterB64 >= 8) x64Gen_writeU8(x64GenContext, 0x43); else if (dstRegister >= 8 && memRegisterB64 >= 8) x64Gen_writeU8(x64GenContext, 0x42); else if (dstRegister >= 8 && memRegisterA64 >= 8) x64Gen_writeU8(x64GenContext, 0x45); else if (dstRegister >= 8) x64Gen_writeU8(x64GenContext, 0x44); else if (memRegisterA64 >= 8) x64Gen_writeU8(x64GenContext, 0x41); else if (memRegisterB64 >= 8) x64Gen_writeU8(x64GenContext, 0x42); else if (dstRegister >= 4) x64Gen_writeU8(x64GenContext, 0x40); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xBE); _x64Gen_writeMODRMDeprecated(x64GenContext, dstRegister, memRegisterA64, memRegisterB64, memImmS32); } void x64Gen_mov_mem64Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) { // MOV QWORD [<reg64> + <reg64> + <imm64>], <dstReg64> if( srcRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x47|8); else if( memRegisterA64 >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x43|8); else if( srcRegister >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x42|8); else if( srcRegister >= 8 && memRegisterA64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x45|8); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44|8); else if( memRegisterA64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x41|8); else if( memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x42|8); else x64Gen_writeU8(x64GenContext, 0x48); x64Gen_writeU8(x64GenContext, 0x89); _x64Gen_writeMODRMDeprecated(x64GenContext, srcRegister, memRegisterA64, memRegisterB64, memImmS32); } void x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) { // MOV <dstReg64> (low word), WORD [<reg64> + <reg64> + <imm64>] x64Gen_writeU8(x64GenContext, 0x66); // 16bit prefix x64Emit_mov_reg32_mem32(x64GenContext, dstRegister, memRegisterA64, memRegisterB64, memImmS32); } void x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister) { // MOV DWORD [<reg64> + <reg64> + <imm64>], <srcReg64> (low dword) if( srcRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x47); else if( memRegisterA64 >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x43); else if( srcRegister >= 8 && memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x42); else if( srcRegister >= 8 && memRegisterA64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( memRegisterA64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); else if( memRegisterB64 >= 8 ) x64Gen_writeU8(x64GenContext, 0x42); x64Gen_writeU8(x64GenContext, 0x89); _x64Gen_writeMODRMDeprecated(x64GenContext, srcRegister, memRegisterA64, memRegisterB64, memImmS32); } void x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister) { // MOV WORD [<reg64> + <reg64> + <imm64>], <srcReg64> (low dword) x64Gen_writeU8(x64GenContext, 0x66); // 16bit prefix x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, memRegisterA64, memRegisterB64, memImmS32, srcRegister); } void x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister) { // MOV BYTE [<reg64> + <reg64> + <imm64>], <srcReg64> (low byte) // when no REX byte is present: Source register can range from AL to BH // when a REX byte is present: Source register can range from AL to DIL or R8B to R15B // todo: We don't need the REX byte when when the source register is AL,BL,CL or DL and neither memRegister A or B are within r8 - r15 uint8 rexByte = 0x40; if( srcRegister >= 8 ) rexByte |= (1<<2); if( memRegisterA64 >= 8 ) rexByte |= (1<<0); if( memRegisterB64 >= 8 ) rexByte |= (1<<1); x64Gen_writeU8(x64GenContext, rexByte); x64Gen_writeU8(x64GenContext, 0x88); _x64Gen_writeMODRMDeprecated(x64GenContext, srcRegister, memRegisterA64, memRegisterB64, memImmS32); } void x64Gen_mov_mem32Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint32 dataImmU32) { // MOV DWORD [<memReg>+<memImmU32>], dataImmU32 if( (memRegister&7) == 4 ) { if( memRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); sint32 memImmS32 = (sint32)memImmU32; if( memImmS32 >= -128 && memImmS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0xC7); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint8)memImmU32); } else { x64Gen_writeU8(x64GenContext, 0xC7); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, memImmU32); } x64Gen_writeU32(x64GenContext, dataImmU32); } else { assert_dbg(); } } void x64Gen_mov_mem64Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint32 dataImmU32) { // MOV QWORD [<memReg>+<memImmU32>], dataImmU32 if( memRegister == X86_REG_R14 ) { sint32 memImmS32 = (sint32)memImmU32; if( memImmS32 == 0 ) { x64Gen_writeU8(x64GenContext, 0x49); x64Gen_writeU8(x64GenContext, 0xC7); x64Gen_writeU8(x64GenContext, 0x06); x64Gen_writeU32(x64GenContext, dataImmU32); } else if( memImmS32 >= -128 && memImmS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0x49); x64Gen_writeU8(x64GenContext, 0xC7); x64Gen_writeU8(x64GenContext, 0x46); x64Gen_writeU8(x64GenContext, (uint8)memImmS32); x64Gen_writeU32(x64GenContext, dataImmU32); } else { assert_dbg(); } } else { assert_dbg(); } } void x64Gen_mov_mem8Reg64_imm8(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint8 dataImmU8) { // MOV BYTE [<memReg64>+<memImmU32>], dataImmU8 if( memRegister == X86_REG_RSP ) { sint32 memImmS32 = (sint32)memImmU32; if( memImmS32 >= -128 && memImmS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0xC6); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint8)memImmU32); } else { x64Gen_writeU8(x64GenContext, 0xC6); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, memImmU32); } x64Gen_writeU8(x64GenContext, dataImmU8); } else { assert_dbg(); } } void x64Gen_mov_reg64_imm64(x64GenContext_t* x64GenContext, sint32 destRegister, uint64 immU64) { // MOV <destReg64>, <imm64> x64Gen_writeU8(x64GenContext, 0x48+(destRegister/8)); x64Gen_writeU8(x64GenContext, 0xB8+(destRegister%8)); x64Gen_writeU64(x64GenContext, immU64); } void x64Gen_mov_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 destRegister, uint64 immU32) { // todo: Emit shorter opcode if immU32 is 0 or falls in sint8 range? // MOV <destReg64>, <imm64> if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xB8+(destRegister&7)); x64Gen_writeU32(x64GenContext, (uint32)immU32); } void x64Gen_mov_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // MOV <destReg64>, <srcReg64> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4D); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x49); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4C); else x64Gen_writeU8(x64GenContext, 0x48); x64Gen_writeU8(x64GenContext, 0x89); x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); } void x64Gen_xchg_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // XCHG <destReg64>, <srcReg64> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4D); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x49); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4C); else x64Gen_writeU8(x64GenContext, 0x48); x64Gen_writeU8(x64GenContext, 0x87); x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); } void x64Gen_mov_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // MOV <destReg64_low32>, <srcReg64_low32> if (destRegister >= 8 && srcRegister >= 8) x64Gen_writeU8(x64GenContext, 0x45); else if (destRegister >= 8) x64Gen_writeU8(x64GenContext, 0x41); else if (srcRegister >= 8) x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x89); x64Gen_writeU8(x64GenContext, 0xC0 + (destRegister & 7) + (srcRegister & 7) * 8); } void x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, uint32 conditionType, sint32 destRegister, sint32 srcRegister) { // cMOVcc <destReg64_low32>, <srcReg64_low32> if (destRegister >= 8 && srcRegister >= 8) x64Gen_writeU8(x64GenContext, 0x45); else if (srcRegister >= 8) x64Gen_writeU8(x64GenContext, 0x41); else if (destRegister >= 8) x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x0F); if (conditionType == X86_CONDITION_CARRY || conditionType == X86_CONDITION_UNSIGNED_BELOW) x64Gen_writeU8(x64GenContext, 0x42); else if (conditionType == X86_CONDITION_NOT_CARRY || conditionType == X86_CONDITION_UNSIGNED_ABOVE_EQUAL) x64Gen_writeU8(x64GenContext, 0x43); else if (conditionType == X86_CONDITION_EQUAL) x64Gen_writeU8(x64GenContext, 0x44); else if (conditionType == X86_CONDITION_NOT_EQUAL) x64Gen_writeU8(x64GenContext, 0x45); else if (conditionType == X86_CONDITION_UNSIGNED_BELOW_EQUAL) x64Gen_writeU8(x64GenContext, 0x46); else if (conditionType == X86_CONDITION_UNSIGNED_ABOVE) x64Gen_writeU8(x64GenContext, 0x47); else if (conditionType == X86_CONDITION_SIGN) x64Gen_writeU8(x64GenContext, 0x48); else if (conditionType == X86_CONDITION_NOT_SIGN) x64Gen_writeU8(x64GenContext, 0x49); else if (conditionType == X86_CONDITION_PARITY) x64Gen_writeU8(x64GenContext, 0x4A); else if (conditionType == X86_CONDITION_SIGNED_LESS) x64Gen_writeU8(x64GenContext, 0x4C); else if (conditionType == X86_CONDITION_SIGNED_GREATER_EQUAL) x64Gen_writeU8(x64GenContext, 0x4D); else if (conditionType == X86_CONDITION_SIGNED_LESS_EQUAL) x64Gen_writeU8(x64GenContext, 0x4E); else if (conditionType == X86_CONDITION_SIGNED_GREATER) x64Gen_writeU8(x64GenContext, 0x4F); else { assert_dbg(); } x64Gen_writeU8(x64GenContext, 0xC0 + (destRegister & 7) * 8 + (srcRegister & 7)); } void x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // MOVSX <destReg64_lowDWORD>, <srcReg64_lowWORD> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4D); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4C); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xBF); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)+(destRegister&7)*8); } void x64Gen_movZeroExtend_reg64Low32_reg64Low16(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // MOVZX <destReg64_lowDWORD>, <srcReg64_lowWORD> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4D); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4C); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xB7); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)+(destRegister&7)*8); } void x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // MOVSX <destReg64_lowDWORD>, <srcReg64_lowBYTE> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4D); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4C); else if( srcRegister >= 4 ) x64Gen_writeU8(x64GenContext, 0x40); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xBE); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)+(destRegister&7)*8); } void x64Gen_movZeroExtend_reg64Low32_reg64Low8(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // MOVZX <destReg64_lowDWORD>, <srcReg64_lowBYTE> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4D); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x4C); else if( srcRegister >= 4 ) x64Gen_writeU8(x64GenContext, 0x40); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xB6); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)+(destRegister&7)*8); } void x64Gen_lea_reg64Low32_reg64Low32PlusReg64Low32(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64) { // MOV <reg32>, DWORD [<reg32> + <reg32>] if ((memRegisterA64 & 0x7) == 5) { // RBP // swap mem registers to get the shorter instruction encoding sint32 temp = memRegisterA64; memRegisterA64 = memRegisterB64; memRegisterB64 = temp; } if ((memRegisterA64 & 0x7) == 4) { // RSP // swap mem registers sint32 temp = memRegisterA64; memRegisterA64 = memRegisterB64; memRegisterB64 = temp; if ((memRegisterA64 & 0x7) == 4) assert_dbg(); // double RSP not supported } x64Gen_writeU8(x64GenContext, 0x67); if (dstRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8) x64Gen_writeU8(x64GenContext, 0x47); else if (dstRegister >= 8 && memRegisterA64 >= 8) x64Gen_writeU8(x64GenContext, 0x45); else if (dstRegister >= 8 && memRegisterB64 >= 8) x64Gen_writeU8(x64GenContext, 0x46); else if (dstRegister >= 8) x64Gen_writeU8(x64GenContext, 0x44); else if (memRegisterA64 >= 8 && memRegisterB64 >= 8) x64Gen_writeU8(x64GenContext, 0x43); else if (memRegisterB64 >= 8) x64Gen_writeU8(x64GenContext, 0x42); else if (memRegisterA64 >= 8) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x8D); _x64Gen_writeMODRMDeprecated(x64GenContext, dstRegister&0x7, memRegisterA64 & 0x7, memRegisterB64 & 0x7, 0); } void _x64_op_reg64Low_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32, uint8 opByte) { // OR <dstReg64> (low byte), BYTE [<reg64> + <imm64>] if (dstRegister >= 8 && memRegister64 >= 8) x64Gen_writeU8(x64GenContext, 0x45); if (dstRegister >= 8) x64Gen_writeU8(x64GenContext, 0x44); if (memRegister64 >= 8) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, opByte); _x64Gen_writeMODRMDeprecated(x64GenContext, dstRegister, memRegister64, X86_REG_NONE, memImmS32); } void x64Gen_or_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32) { _x64_op_reg64Low_mem8Reg64(x64GenContext, dstRegister, memRegister64, memImmS32, 0x0A); } void x64Gen_and_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32) { _x64_op_reg64Low_mem8Reg64(x64GenContext, dstRegister, memRegister64, memImmS32, 0x22); } void x64Gen_mov_mem8Reg64_reg64Low8(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32) { _x64_op_reg64Low_mem8Reg64(x64GenContext, dstRegister, memRegister64, memImmS32, 0x88); } void x64Gen_add_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // ADD <destReg>, <srcReg> x64Gen_writeU8(x64GenContext, 0x48+(destRegister/8)+(srcRegister/8)*4); x64Gen_writeU8(x64GenContext, 0x01); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)*8+(destRegister&7)); } void x64Gen_add_reg64_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) { sint32 immS32 = (sint32)immU32; if (srcRegister >= 8) x64Gen_writeU8(x64GenContext, 0x49); else x64Gen_writeU8(x64GenContext, 0x48); if (immS32 >= -128 && immS32 <= 127) { x64Gen_writeU8(x64GenContext, 0x83); x64Gen_writeU8(x64GenContext, 0xC0 + (srcRegister & 7)); x64Gen_writeU8(x64GenContext, (uint8)immS32); } else { x64Gen_writeU8(x64GenContext, 0x81); x64Gen_writeU8(x64GenContext, 0xC0 + (srcRegister & 7)); x64Gen_writeU32(x64GenContext, immU32); } } void x64Gen_add_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // ADD <destReg64_low32>, <srcReg64_low32> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x01); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)*8+(destRegister&7)); } void x64Gen_add_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) { sint32 immS32 = (sint32)immU32; if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS32 >= -128 && immS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0x83); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS32); } else { if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0x05); } else { x64Gen_writeU8(x64GenContext, 0x81); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); } x64Gen_writeU32(x64GenContext, immU32); } } void x64Gen_sub_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // SUB <destReg64_low32>, <srcReg64_low32> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x29); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)*8+(destRegister&7)); } void x64Gen_sub_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) { sint32 immS32 = (sint32)immU32; if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS32 >= -128 && immS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0x83); x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS32); } else { if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0x2D); } else { x64Gen_writeU8(x64GenContext, 0x81); x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); } x64Gen_writeU32(x64GenContext, immU32); } } void x64Gen_sub_reg64_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) { sint32 immS32 = (sint32)immU32; if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x49); else x64Gen_writeU8(x64GenContext, 0x48); if( immS32 >= -128 && immS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0x83); x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS32); } else { x64Gen_writeU8(x64GenContext, 0x81); x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); x64Gen_writeU32(x64GenContext, immU32); } } void x64Gen_sub_mem32reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, sint32 memImmS32, uint64 immU32) { // SUB <mem32_memReg64>, <imm32> sint32 immS32 = (sint32)immU32; if( memRegister == X86_REG_RSP ) { if( memImmS32 >= 128 ) { if( immS32 >= -128 && immS32 <= 127 ) { // 4 byte mem imm + 1 byte imm x64Gen_writeU8(x64GenContext, 0x83); x64Gen_writeU8(x64GenContext, 0xAC); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memImmS32); x64Gen_writeU8(x64GenContext, (uint8)immU32); } else { // 4 byte mem imm + 4 byte imm x64Gen_writeU8(x64GenContext, 0x81); x64Gen_writeU8(x64GenContext, 0xAC); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memImmS32); x64Gen_writeU32(x64GenContext, (uint32)immU32); } } else assert_dbg(); } else { assert_dbg(); } } void x64Gen_dec_mem32(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint32 memoryImmU32) { // DEC dword [<reg64>+imm] sint32 memoryImmS32 = (sint32)memoryImmU32; if (memoryRegister != X86_REG_RSP) assert_dbg(); // not supported yet if (memoryImmS32 >= -128 && memoryImmS32 <= 127) { x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0x4C); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint8)memoryImmU32); } else { x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0x8C); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, memoryImmU32); } } void x64Gen_imul_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 operandRegister) { // IMUL <destReg64_low32>, <operandReg64_low32> if( destRegister >= 8 && operandRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( operandRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xAF); x64Gen_writeU8(x64GenContext, 0xC0+(operandRegister&7)+(destRegister&7)*8); } void x64Gen_idiv_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister) { // IDIV <destReg64_low32> if( operandRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xF8+(operandRegister&7)); } void x64Gen_div_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister) { // DIV <destReg64_low32> if( operandRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xF0+(operandRegister&7)); } void x64Gen_imul_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister) { // IMUL <destReg64_low32> if( operandRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xE8+(operandRegister&7)); } void x64Gen_mul_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister) { // MUL <destReg64_low32> if( operandRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xE0+(operandRegister&7)); } void x64Gen_and_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) { sint32 immS32 = (sint32)immU32; if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS32 >= -128 && immS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0x83); x64Gen_writeU8(x64GenContext, 0xE0+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS32); } else { if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0x25); } else { x64Gen_writeU8(x64GenContext, 0x81); x64Gen_writeU8(x64GenContext, 0xE0+(srcRegister&7)); } x64Gen_writeU32(x64GenContext, immU32); } } void x64Gen_and_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // AND <destReg64_lowDWORD>, <srcReg64_lowDWORD> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x21); x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); } void x64Gen_test_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // TEST <destReg64_lowDWORD>, <srcReg64_lowDWORD> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x85); x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)*8+(srcRegister&7)); } void x64Gen_test_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) { sint32 immS32 = (sint32)immU32; if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0xA9); } else { x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); } x64Gen_writeU32(x64GenContext, immU32); } void x64Gen_cmp_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, sint32 immS32) { if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS32 >= -128 && immS32 <= 127 ) { // 83 F8 00 CMP EAX,0 x64Gen_writeU8(x64GenContext, 0x83); x64Gen_writeU8(x64GenContext, 0xF8+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS32); } else { if( srcRegister == X86_REG_RAX ) { // special RAX short form x64Gen_writeU8(x64GenContext, 0x3D); } else { x64Gen_writeU8(x64GenContext, 0x81); x64Gen_writeU8(x64GenContext, 0xF8+(srcRegister&7)); } x64Gen_writeU32(x64GenContext, (uint32)immS32); } } void x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // CMP <destReg64_lowDWORD>, <srcReg64_lowDWORD> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x39); x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); } void x64Gen_cmp_reg64Low32_mem32reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 memRegister, sint32 memImmS32) { // CMP <destReg64_lowDWORD>, DWORD [<memRegister>+<immS32>] if( memRegister == X86_REG_RSP ) { if( memImmS32 >= -128 && memImmS32 <= 127 ) assert_dbg(); // todo -> Shorter instruction form if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x3B); x64Gen_writeU8(x64GenContext, 0x84+(destRegister&7)*8); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memImmS32); } else { assert_dbg(); } } void x64Gen_or_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) { sint32 immS32 = (sint32)immU32; if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS32 >= -128 && immS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0x83); x64Gen_writeU8(x64GenContext, 0xC8+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS32); } else { if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0x0D); } else { x64Gen_writeU8(x64GenContext, 0x81); x64Gen_writeU8(x64GenContext, 0xC8+(srcRegister&7)); } x64Gen_writeU32(x64GenContext, immU32); } } void x64Gen_or_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // OR <destReg64_lowDWORD>, <srcReg64_lowDWORD> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x09); x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); } void x64Gen_xor_reg32_reg32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // XOR <destReg>, <srcReg> x64Gen_writeU8(x64GenContext, 0x33); x64Gen_writeU8(x64GenContext, 0xC0+srcRegister+destRegister*8); } void x64Gen_xor_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // XOR <destReg64_lowDWORD>, <srcReg64_lowDWORD> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x31); x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); } void x64Gen_xor_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) { sint32 immS32 = (sint32)immU32; if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS32 >= -128 && immS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0x83); x64Gen_writeU8(x64GenContext, 0xF0+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS32); } else { if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0x35); } else { x64Gen_writeU8(x64GenContext, 0x81); x64Gen_writeU8(x64GenContext, 0xF0+(srcRegister&7)); } x64Gen_writeU32(x64GenContext, immU32); } } void x64Gen_rol_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) { if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS8 == 1 ) { // short form for 1 bit ROL x64Gen_writeU8(x64GenContext, 0xD1); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); } else { x64Gen_writeU8(x64GenContext, 0xC1); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS8); } } void x64Gen_rol_reg64Low32_cl(x64GenContext_t* x64GenContext, sint32 srcRegister) { if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xD3); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); } void x64Gen_rol_reg64Low16_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) { x64Gen_writeU8(x64GenContext, 0x66); // 16bit prefix if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS8 == 1 ) { // short form for 1 bit ROL x64Gen_writeU8(x64GenContext, 0xD1); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); } else { x64Gen_writeU8(x64GenContext, 0xC1); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS8); } } void x64Gen_rol_reg64_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) { if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x49); else x64Gen_writeU8(x64GenContext, 0x48); if( immS8 == 1 ) { // short form for 1 bit ROL x64Gen_writeU8(x64GenContext, 0xD1); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); } else { x64Gen_writeU8(x64GenContext, 0xC1); x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS8); } } void x64Gen_shl_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) { if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS8 == 1 ) { // short form for 1 bit SHL x64Gen_writeU8(x64GenContext, 0xD1); x64Gen_writeU8(x64GenContext, 0xF0+(srcRegister&7)); } else { x64Gen_writeU8(x64GenContext, 0xC1); x64Gen_writeU8(x64GenContext, 0xF0+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS8); } } void x64Gen_shr_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) { if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS8 == 1 ) { // short form for 1 bit SHR x64Gen_writeU8(x64GenContext, 0xD1); x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); } else { x64Gen_writeU8(x64GenContext, 0xC1); x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS8); } } void x64Gen_sar_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) { if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); if( immS8 == 1 ) { // short form for 1 bit ROL x64Gen_writeU8(x64GenContext, 0xD1); x64Gen_writeU8(x64GenContext, 0xF8+(srcRegister&7)); } else { x64Gen_writeU8(x64GenContext, 0xC1); x64Gen_writeU8(x64GenContext, 0xF8+(srcRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immS8); } } void x64Gen_not_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister) { if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xD0+(destRegister&7)); } void x64Gen_neg_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister) { if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xD8+(destRegister&7)); } void x64Gen_cdq(x64GenContext_t* x64GenContext) { x64Gen_writeU8(x64GenContext, 0x99); } void x64Gen_bswap_reg64Lower32bit(x64GenContext_t* x64GenContext, sint32 destRegister) { if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xC8+(destRegister&7)); } void x64Gen_lzcnt_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // SSE4 // LZCNT <destReg>, <srcReg> x64Gen_writeU8(x64GenContext, 0xF3); if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xBD); x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)*8+(srcRegister&7)); } void x64Gen_bsr_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // BSR <destReg>, <srcReg> if( destRegister >= 8 && srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x45); else if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); else if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xBD); x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)*8+(srcRegister&7)); } void x64Gen_setcc_mem8(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 memoryRegister, uint32 memoryImmU32) { // SETcc [<reg64>+imm] sint32 memoryImmS32 = (sint32)memoryImmU32; if( memoryRegister != X86_REG_RSP ) assert_dbg(); // not supported if( memoryRegister >= 8 ) assert_dbg(); // not supported if( memoryImmS32 >= -128 && memoryImmS32 <= 127 ) { if( conditionType == X86_CONDITION_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x94); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_NOT_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x95); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_UNSIGNED_ABOVE ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x97); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_UNSIGNED_ABOVE_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x93); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_UNSIGNED_BELOW ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x92); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_UNSIGNED_BELOW_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x96); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_SIGNED_GREATER ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x9F); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_SIGNED_GREATER_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x9D); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_SIGNED_LESS ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x9C); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_SIGNED_LESS_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x9E); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_PARITY ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x9A); x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); } else assert_dbg(); } else { if( conditionType == X86_CONDITION_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x94); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_NOT_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x95); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_UNSIGNED_ABOVE ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x97); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_UNSIGNED_ABOVE_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x93); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_UNSIGNED_BELOW || conditionType == X86_CONDITION_CARRY ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x92); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_NOT_CARRY ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x93); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_UNSIGNED_BELOW_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x96); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_SIGNED_GREATER ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x9F); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_SIGNED_GREATER_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x9D); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_SIGNED_LESS ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x9C); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_SIGNED_LESS_EQUAL ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x9E); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_SIGN ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x98); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else if( conditionType == X86_CONDITION_PARITY ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x9A); x64Gen_writeU8(x64GenContext, 0x84); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); } else assert_dbg(); } } void x64Gen_setcc_reg64b(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 dataRegister) { // SETcc <reg64_low8> if (conditionType == X86_CONDITION_NOT_EQUAL) { if (dataRegister >= 8) x64Gen_writeU8(x64GenContext, 0x41); else if (dataRegister >= 4) x64Gen_writeU8(x64GenContext, 0x40); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x95); x64Gen_writeU8(x64GenContext, 0xC0 + (dataRegister & 7)); } else if (conditionType == X86_CONDITION_EQUAL) { if (dataRegister >= 8) x64Gen_writeU8(x64GenContext, 0x41); else if (dataRegister >= 4) x64Gen_writeU8(x64GenContext, 0x40); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x94); x64Gen_writeU8(x64GenContext, 0xC0 + (dataRegister & 7)); } else assert_dbg(); } void x64Gen_bt_mem8(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint32 memoryImmU32, uint8 bitIndex) { // BT [<reg64>+imm], bitIndex (bit test) sint32 memoryImmS32 = (sint32)memoryImmU32; if( memoryRegister != X86_REG_RSP ) assert_dbg(); // not supported yet if( memoryImmS32 >= -128 && memoryImmS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xBA); x64Gen_writeU8(x64GenContext, 0x64); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU8(x64GenContext, (uint8)memoryImmU32); x64Gen_writeU8(x64GenContext, bitIndex); } else { x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xBA); x64Gen_writeU8(x64GenContext, 0xA4); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, memoryImmU32); x64Gen_writeU8(x64GenContext, bitIndex); } } void x64Gen_cmc(x64GenContext_t* x64GenContext) { x64Gen_writeU8(x64GenContext, 0xF5); } void x64Gen_jmp_imm32(x64GenContext_t* x64GenContext, uint32 destImm32) { x64Gen_writeU8(x64GenContext, 0xE9); x64Gen_writeU32(x64GenContext, destImm32); } void x64Gen_jmp_memReg64(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 immU32) { if( memRegister == X86_REG_NONE ) { assert_dbg(); } if( memRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); sint32 immS32 = (sint32)immU32; if( immS32 == 0 ) { x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0x20+(memRegister&7)); } else if( immS32 >= -128 && immS32 <= 127 ) { x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0x60+(memRegister&7)); x64Gen_writeU8(x64GenContext, (uint8)immU32); } else { x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0xA0+(memRegister&7)); x64Gen_writeU32(x64GenContext, immU32); } } void x64Gen_jmpc_far(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 relativeDest) { // far JMPc #+relativeDest if( conditionType == X86_CONDITION_NONE ) { // E9 FFFFFFFF x64Gen_writeU8(x64GenContext, 0xE9); } else if( conditionType == X86_CONDITION_UNSIGNED_BELOW || conditionType == X86_CONDITION_CARRY ) { // 0F 82 FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x82); } else if( conditionType == X86_CONDITION_NOT_CARRY || conditionType == X86_CONDITION_UNSIGNED_ABOVE_EQUAL ) { // 0F 83 FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x83); } else if( conditionType == X86_CONDITION_EQUAL ) { // 0F 84 FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x84); } else if( conditionType == X86_CONDITION_NOT_EQUAL ) { // 0F 85 FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x85); } else if( conditionType == X86_CONDITION_UNSIGNED_BELOW_EQUAL ) { // 0F 86 FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x86); } else if( conditionType == X86_CONDITION_UNSIGNED_ABOVE ) { // 0F 87 FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x87); } else if( conditionType == X86_CONDITION_SIGN ) { // 0F 88 FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x88); } else if( conditionType == X86_CONDITION_NOT_SIGN ) { // 0F 89 FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x89); } else if( conditionType == X86_CONDITION_PARITY ) { // 0F 8A FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x8A); } else if( conditionType == X86_CONDITION_SIGNED_LESS ) { // 0F 8C FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x8C); } else if( conditionType == X86_CONDITION_SIGNED_GREATER_EQUAL ) { // 0F 8D FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x8D); } else if( conditionType == X86_CONDITION_SIGNED_LESS_EQUAL ) { // 0F 8E FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x8E); } else if( conditionType == X86_CONDITION_SIGNED_GREATER ) { // 0F 8F FFFFFFFF x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x8F); } else assert_dbg(); x64Gen_writeU32(x64GenContext, (uint32)relativeDest); } void x64Gen_jmpc_near(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 relativeDest) { // near JMPc #+relativeDest if (conditionType == X86_CONDITION_NONE) { x64Gen_writeU8(x64GenContext, 0xEB); } else if (conditionType == X86_CONDITION_UNSIGNED_BELOW || conditionType == X86_CONDITION_CARRY) { x64Gen_writeU8(x64GenContext, 0x72); } else if (conditionType == X86_CONDITION_NOT_CARRY || conditionType == X86_CONDITION_UNSIGNED_ABOVE_EQUAL) { x64Gen_writeU8(x64GenContext, 0x73); } else if (conditionType == X86_CONDITION_EQUAL) { x64Gen_writeU8(x64GenContext, 0x74); } else if (conditionType == X86_CONDITION_NOT_EQUAL) { x64Gen_writeU8(x64GenContext, 0x75); } else if (conditionType == X86_CONDITION_UNSIGNED_BELOW_EQUAL) { x64Gen_writeU8(x64GenContext, 0x76); } else if (conditionType == X86_CONDITION_UNSIGNED_ABOVE) { x64Gen_writeU8(x64GenContext, 0x77); } else if (conditionType == X86_CONDITION_SIGN) { x64Gen_writeU8(x64GenContext, 0x78); } else if (conditionType == X86_CONDITION_NOT_SIGN) { x64Gen_writeU8(x64GenContext, 0x79); } else if (conditionType == X86_CONDITION_PARITY) { x64Gen_writeU8(x64GenContext, 0x7A); } else if (conditionType == X86_CONDITION_SIGNED_LESS) { x64Gen_writeU8(x64GenContext, 0x7C); } else if (conditionType == X86_CONDITION_SIGNED_GREATER_EQUAL) { x64Gen_writeU8(x64GenContext, 0x7D); } else if (conditionType == X86_CONDITION_SIGNED_LESS_EQUAL) { x64Gen_writeU8(x64GenContext, 0x7E); } else if (conditionType == X86_CONDITION_SIGNED_GREATER) { x64Gen_writeU8(x64GenContext, 0x7F); } else assert_dbg(); x64Gen_writeU8(x64GenContext, (uint8)relativeDest); } void x64Gen_push_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister) { if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x50+(srcRegister&7)); } void x64Gen_pop_reg64(x64GenContext_t* x64GenContext, sint32 destRegister) { if( destRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0x58+(destRegister&7)); } void x64Gen_jmp_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister) { if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0xE0+(srcRegister&7)); } void x64Gen_call_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister) { if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, 0xFF); x64Gen_writeU8(x64GenContext, 0xD0+(srcRegister&7)); } void x64Gen_ret(x64GenContext_t* x64GenContext) { x64Gen_writeU8(x64GenContext, 0xC3); } void x64Gen_int3(x64GenContext_t* x64GenContext) { x64Gen_writeU8(x64GenContext, 0xCC); } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp ================================================ #include "BackendX64.h" void x64Gen_genSSEVEXPrefix2(x64GenContext_t* x64GenContext, sint32 xmmRegister1, sint32 xmmRegister2, bool use64BitMode) { if( xmmRegister1 < 8 && xmmRegister2 < 8 && use64BitMode == false ) return; uint8 v = 0x40; if( xmmRegister1 >= 8 ) v |= 0x01; if( xmmRegister2 >= 8 ) v |= 0x04; if( use64BitMode ) v |= 0x08; x64Gen_writeU8(x64GenContext, v); } void x64Gen_genSSEVEXPrefix1(x64GenContext_t* x64GenContext, sint32 xmmRegister, bool use64BitMode) { if( xmmRegister < 8 && use64BitMode == false ) return; uint8 v = 0x40; if( use64BitMode ) v |= 0x01; if( xmmRegister >= 8 ) v |= 0x04; x64Gen_writeU8(x64GenContext, v); } void x64Gen_movaps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSource) { // SSE // copy xmm register // MOVAPS <xmm>, <xmm> x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSource, xmmRegisterDest, false); // tested x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x28); // alternative encoding: 0x29, source and destination register are exchanged x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSource&7)); } void x64Gen_movupd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE2 // move two doubles from memory into xmm register // MOVUPD <xmm>, [<reg>+<imm>] if( memRegister == X86_REG_ESP ) { // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range // 66 0F 10 84 E4 23 01 00 00 x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegister, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x10); x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); x64Gen_writeU8(x64GenContext, 0xE4); x64Gen_writeU32(x64GenContext, memImmU32); } else if( memRegister == X86_REG_NONE ) { assert_dbg(); //x64Gen_writeU8(x64GenContext, 0x66); //x64Gen_writeU8(x64GenContext, 0x0F); //x64Gen_writeU8(x64GenContext, 0x10); //x64Gen_writeU8(x64GenContext, 0x05+(xmmRegister&7)*8); //x64Gen_writeU32(x64GenContext, memImmU32); } else { assert_dbg(); } } void x64Gen_movupd_memReg128_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE2 // move two doubles from memory into xmm register // MOVUPD [<reg>+<imm>], <xmm> if( memRegister == X86_REG_ESP ) { // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegister, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x11); x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); x64Gen_writeU8(x64GenContext, 0xE4); x64Gen_writeU32(x64GenContext, memImmU32); } else if( memRegister == X86_REG_NONE ) { assert_dbg(); //x64Gen_writeU8(x64GenContext, 0x66); //x64Gen_writeU8(x64GenContext, 0x0F); //x64Gen_writeU8(x64GenContext, 0x11); //x64Gen_writeU8(x64GenContext, 0x05+(xmmRegister&7)*8); //x64Gen_writeU32(x64GenContext, memImmU32); } else { assert_dbg(); } } void x64Gen_movddup_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE3 // move one double from memory into lower and upper half of a xmm register if( memRegister == X86_REG_RSP ) { // MOVDDUP <xmm>, [<reg>+<imm>] // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range x64Gen_writeU8(x64GenContext, 0xF2); if( xmmRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x44); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x12); x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); x64Gen_writeU8(x64GenContext, 0xE4); x64Gen_writeU32(x64GenContext, memImmU32); } else if( memRegister == X86_REG_R15 ) { // MOVDDUP <xmm>, [<reg>+<imm>] // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range // F2 41 0F 12 87 - 44 33 22 11 x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegister, true); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x12); x64Gen_writeU8(x64GenContext, 0x87+(xmmRegister&7)*8); x64Gen_writeU32(x64GenContext, memImmU32); } else if( memRegister == X86_REG_NONE ) { // MOVDDUP <xmm>, [<imm>] // 36 F2 0F 12 05 - 00 00 00 00 assert_dbg(); //x64Gen_writeU8(x64GenContext, 0x36); //x64Gen_writeU8(x64GenContext, 0xF2); //x64Gen_writeU8(x64GenContext, 0x0F); //x64Gen_writeU8(x64GenContext, 0x12); //x64Gen_writeU8(x64GenContext, 0x05+(xmmRegister&7)*8); //x64Gen_writeU32(x64GenContext, memImmU32); } else { assert_dbg(); } } void x64Gen_movddup_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE3 // move low double from xmm register into lower and upper half of a different xmm register x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x12); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_movhlps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE1 // move high double from xmm register into lower and upper half of a different xmm register x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x12); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_movsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // move lower double from xmm register into lower half of a different xmm register, leave other half untouched x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x10); // alternative encoding: 0x11, src and dest exchanged x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_movsd_memReg64_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE2 // move lower 64bits (double) of xmm register to memory location if( memRegister == X86_REG_NONE ) { // MOVSD [<imm>], <xmm> // F2 0F 11 05 - 45 23 01 00 assert_dbg(); //x64Gen_writeU8(x64GenContext, 0xF2); //x64Gen_genSSEVEXPrefix(x64GenContext, xmmRegister, 0, false); //x64Gen_writeU8(x64GenContext, 0x0F); //x64Gen_writeU8(x64GenContext, 0x11); //x64Gen_writeU8(x64GenContext, 0x05+xmmRegister*8); //x64Gen_writeU32(x64GenContext, memImmU32); } else if( memRegister == X86_REG_RSP ) { // MOVSD [RSP+<imm>], <xmm> // F2 0F 11 84 24 - 33 22 11 00 x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, 0, xmmRegister, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x11); x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, memImmU32); } else { assert_dbg(); } } void x64Gen_movsd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE2 if( memRegister == X86_REG_RSP ) { // MOVSD <xmm>, [RSP+<imm>] x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, 0, xmmRegister, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x10); x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, memImmU32); } else if( memRegister == 15 ) { // MOVSD <xmm>, [R15+<imm>] x64Gen_writeU8(x64GenContext, 0x36); x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, memRegister, xmmRegister, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x10); x64Gen_writeU8(x64GenContext, 0x87+(xmmRegister&7)*8); x64Gen_writeU32(x64GenContext, memImmU32); } else { assert_dbg(); } } void x64Gen_movlpd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE3 // move one double from memory into lower half of a xmm register, leave upper half unchanged(?) if( memRegister == X86_REG_NONE ) { // MOVLPD <xmm>, [<imm>] //x64Gen_writeU8(x64GenContext, 0x66); //x64Gen_writeU8(x64GenContext, 0x0F); //x64Gen_writeU8(x64GenContext, 0x12); //x64Gen_writeU8(x64GenContext, 0x05+(xmmRegister&7)*8); //x64Gen_writeU32(x64GenContext, memImmU32); assert_dbg(); } else if( memRegister == X86_REG_RSP ) { // MOVLPD <xmm>, [<reg64>+<imm>] // 66 0F 12 84 24 - 33 22 11 00 x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, 0, xmmRegister, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x12); x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, memImmU32); } else { assert_dbg(); } } void x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x14); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x15); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc, uint8 imm8) { // SSE2 // shuffled copy source to destination x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xC6); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); x64Gen_writeU8(x64GenContext, imm8); } void x64Gen_addsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // add bottom double of two xmm registers, leave upper quadword unchanged x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); // untested x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x58); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_addpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // add both doubles of two xmm registers x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x58); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_subsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // subtract bottom double of two xmm registers, leave upper quadword unchanged x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x5C); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_subpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // subtract both doubles of two xmm registers x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); // untested x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x5C); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_mulsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // multiply bottom double of two xmm registers, leave upper quadword unchanged x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x59); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_mulpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // multiply both doubles of two xmm registers x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); // untested x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x59); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_mulpd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE2 if (memRegister == X86_REG_NONE) { assert_dbg(); } else if (memRegister == X86_REG_R14) { x64Gen_writeU8(x64GenContext, 0x66); x64Gen_writeU8(x64GenContext, (xmmRegister < 8) ? 0x41 : 0x45); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x59); x64Gen_writeU8(x64GenContext, 0x86 + (xmmRegister & 7) * 8); x64Gen_writeU32(x64GenContext, memImmU32); } else { assert_dbg(); } } void x64Gen_divsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // divide bottom double of two xmm registers, leave upper quadword unchanged x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x5E); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_divpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // divide bottom and top double of two xmm registers x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x5E); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_comisd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // compare bottom doubles x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); // untested x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x2F); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memoryReg, sint32 memImmS32) { // SSE2 // compare bottom double with double from memory location if( memoryReg == X86_REG_R15 ) { x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x2F); x64Gen_writeU8(x64GenContext, 0x87+(xmmRegisterDest&7)*8); x64Gen_writeU32(x64GenContext, (uint32)memImmS32); } else assert_dbg(); } void x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // compare bottom doubles x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x2E); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_comiss_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memoryReg, sint32 memImmS32) { // SSE2 // compare bottom float with float from memory location if (memoryReg == X86_REG_R15) { x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x2F); x64Gen_writeU8(x64GenContext, 0x87 + (xmmRegisterDest & 7) * 8); x64Gen_writeU32(x64GenContext, (uint32)memImmS32); } else assert_dbg(); } void x64Gen_orps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32) { // SSE2 // and xmm register with 128 bit value from memory if( memReg == X86_REG_R15 ) { x64Gen_genSSEVEXPrefix2(x64GenContext, memReg, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x56); x64Gen_writeU8(x64GenContext, 0x87+(xmmRegisterDest&7)*8); x64Gen_writeU32(x64GenContext, (uint32)memImmS32); } else assert_dbg(); } void x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32) { // SSE2 // xor xmm register with 128 bit value from memory if( memReg == X86_REG_R15 ) { x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); // todo: should be x64Gen_genSSEVEXPrefix2() with memReg? x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x57); x64Gen_writeU8(x64GenContext, 0x87+(xmmRegisterDest&7)*8); x64Gen_writeU32(x64GenContext, (uint32)memImmS32); } else assert_dbg(); } void x64Gen_andpd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE2 if (memRegister == X86_REG_NONE) { assert_dbg(); } else if (memRegister == X86_REG_R14) { x64Gen_writeU8(x64GenContext, 0x66); x64Gen_writeU8(x64GenContext, (xmmRegister < 8) ? 0x41 : 0x45); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x54); x64Gen_writeU8(x64GenContext, 0x86 + (xmmRegister & 7) * 8); x64Gen_writeU32(x64GenContext, memImmU32); } else { assert_dbg(); } } void x64Gen_andps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32) { // SSE2 // and xmm register with 128 bit value from memory if( memReg == X86_REG_R15 ) { x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); // todo: should be x64Gen_genSSEVEXPrefix2() with memReg? x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x54); x64Gen_writeU8(x64GenContext, 0x87+(xmmRegisterDest&7)*8); x64Gen_writeU32(x64GenContext, (uint32)memImmS32); } else assert_dbg(); } void x64Gen_andps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // and xmm register with xmm register x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x54); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_pcmpeqd_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32) { // SSE2 // doubleword integer compare if( memReg == X86_REG_R15 ) { x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x76); x64Gen_writeU8(x64GenContext, 0x87+(xmmRegisterDest&7)*8); x64Gen_writeU32(x64GenContext, (uint32)memImmS32); } else assert_dbg(); } void x64Gen_cvttpd2dq_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // convert two doubles into two 32-bit integers in bottom part of xmm register, reset upper 64 bits of destination register x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0xE6); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc) { // SSE2 // convert double to truncated integer in general purpose register x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, registerDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x2C); x64Gen_writeU8(x64GenContext, 0xC0+(registerDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_cvtsi2sd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc) { // SSE2 x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, registerSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x2A); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(registerSrc&7)); } void x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // converts bottom 64bit double to bottom 32bit single x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x5A); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // converts two 64bit doubles to two 32bit singles in bottom half of register x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x5A); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // converts two 32bit singles to two 64bit doubles x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x5A); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // converts bottom 32bit single to bottom 64bit double x64Gen_writeU8(x64GenContext, 0xF3); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x5A); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_cvtpi2pd_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memReg, sint32 memImmS32) { // SSE2 // converts two signed 32bit integers to two doubles if( memReg == X86_REG_RSP ) { x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x2A); x64Gen_writeU8(x64GenContext, 0x84+(xmmRegisterDest&7)*8); x64Gen_writeU8(x64GenContext, 0x24); x64Gen_writeU32(x64GenContext, (uint32)memImmS32); } else { assert_dbg(); } } void x64Gen_cvtsd2si_reg64Low_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc) { // SSE2 // converts bottom 64bit double to 32bit signed integer in general purpose register, round based on float-point control x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, registerDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x2D); x64Gen_writeU8(x64GenContext, 0xC0+(registerDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc) { // SSE2 // converts bottom 64bit double to 32bit signed integer in general purpose register, always truncate x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, registerDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x2C); x64Gen_writeU8(x64GenContext, 0xC0+(registerDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_sqrtsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // calculates square root of bottom double x64Gen_writeU8(x64GenContext, 0xF2); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x51); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_sqrtpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // calculates square root of bottom and top double x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x51); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_rcpss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 // approximates reciprocal of bottom 32bit single x64Gen_writeU8(x64GenContext, 0xF3); x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x53); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); } void x64Gen_mulss_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE2 if( memRegister == X86_REG_NONE ) { assert_dbg(); } else if( memRegister == 15 ) { x64Gen_writeU8(x64GenContext, 0xF3); x64Gen_writeU8(x64GenContext, (xmmRegister<8)?0x41:0x45); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x59); x64Gen_writeU8(x64GenContext, 0x87+(xmmRegister&7)*8); x64Gen_writeU32(x64GenContext, memImmU32); } else { assert_dbg(); } } void x64Gen_movd_xmmReg_reg64Low32(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc) { // SSE2 // copy low 32bit of general purpose register into xmm register // MOVD <xmm>, <reg32> x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, registerSrc, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x6E); // alternative encoding: 0x29, source and destination register are exchanged x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(registerSrc&7)); } void x64Gen_movd_reg64Low32_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc) { // SSE2 // copy low 32bit of general purpose register into xmm register // MOVD <reg32>, <xmm> x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, registerDest, xmmRegisterSrc, false); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x7E); // alternative encoding: 0x29, source and destination register are exchanged x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterSrc&7)*8+(registerDest&7)); } void x64Gen_movq_xmmReg_reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc) { // SSE2 // copy general purpose register into xmm register // MOVD <xmm>, <reg64> x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, registerSrc, xmmRegisterDest, true); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x6E); // alternative encoding: 0x29, source and destination register are exchanged x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(registerSrc&7)); } void x64Gen_movq_reg64_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 xmmRegisterSrc) { // SSE2 // copy general purpose register into xmm register // MOVD <xmm>, <reg64> x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix2(x64GenContext, registerDst, xmmRegisterSrc, true); x64Gen_writeU8(x64GenContext, 0x0F); x64Gen_writeU8(x64GenContext, 0x7E); x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterSrc&7)*8+(registerDst&7)); } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendX64/X64Emit.hpp ================================================ template<uint8 op0, bool rex64Bit = false> class x64_opc_1byte { public: static void emitBytes(x64GenContext_t* x64GenContext) { // write out op0 x64Gen_writeU8(x64GenContext, op0); } static constexpr bool isRevOrder() { return false; } static constexpr bool hasRex64BitPrefix() { return rex64Bit; } }; template<uint8 op0, bool rex64Bit = false> class x64_opc_1byte_rev { public: static void emitBytes(x64GenContext_t* x64GenContext) { // write out op0 x64Gen_writeU8(x64GenContext, op0); } static constexpr bool isRevOrder() { return true; } static constexpr bool hasRex64BitPrefix() { return rex64Bit; } }; template<uint8 op0, uint8 op1, bool rex64Bit = false> class x64_opc_2byte { public: static void emitBytes(x64GenContext_t* x64GenContext) { x64Gen_writeU8(x64GenContext, op0); x64Gen_writeU8(x64GenContext, op1); } static constexpr bool isRevOrder() { return false; } static constexpr bool hasRex64BitPrefix() { return rex64Bit; } }; enum class MODRM_OPR_TYPE { REG, MEM }; class x64MODRM_opr_reg64 { public: x64MODRM_opr_reg64(uint8 reg) { this->reg = reg; } static constexpr MODRM_OPR_TYPE getType() { return MODRM_OPR_TYPE::REG; } const uint8 getReg() const { return reg; } private: uint8 reg; }; class x64MODRM_opr_memReg64 { public: x64MODRM_opr_memReg64(uint8 reg) { this->reg = reg; this->offset = 0; } x64MODRM_opr_memReg64(uint8 reg, sint32 offset) { this->reg = reg; this->offset = offset; } static constexpr MODRM_OPR_TYPE getType() { return MODRM_OPR_TYPE::MEM; } const uint8 getBaseReg() const { return reg; } const uint32 getOffset() const { return (uint32)offset; } static constexpr bool hasBaseReg() { return true; } static constexpr bool hasIndexReg() { return false; } private: uint8 reg; sint32 offset; }; class x64MODRM_opr_memRegPlusReg { public: x64MODRM_opr_memRegPlusReg(uint8 regBase, uint8 regIndex) { if ((regIndex & 7) == 4) { // cant encode RSP/R12 in index register, switch with base register // this only works if the scaler is 1 std::swap(regBase, regIndex); cemu_assert((regBase & 7) != 4); } this->regBase = regBase; this->regIndex = regIndex; this->offset = 0; } x64MODRM_opr_memRegPlusReg(uint8 regBase, uint8 regIndex, sint32 offset) { if ((regIndex & 7) == 4) { std::swap(regBase, regIndex); cemu_assert((regIndex & 7) != 4); } this->regBase = regBase; this->regIndex = regIndex; this->offset = offset; } static constexpr MODRM_OPR_TYPE getType() { return MODRM_OPR_TYPE::MEM; } const uint8 getBaseReg() const { return regBase; } const uint8 getIndexReg() { return regIndex; } const uint32 getOffset() const { return (uint32)offset; } static constexpr bool hasBaseReg() { return true; } static constexpr bool hasIndexReg() { return true; } private: uint8 regBase; uint8 regIndex; // multiplied by scaler which is fixed to 1 sint32 offset; }; template<class opcodeBytes, typename TA, typename TB> void _x64Gen_writeMODRM_internal(x64GenContext_t* x64GenContext, TA opA, TB opB) { static_assert(TA::getType() == MODRM_OPR_TYPE::REG); // REX prefix // 0100 WRXB if constexpr (TA::getType() == MODRM_OPR_TYPE::REG && TB::getType() == MODRM_OPR_TYPE::REG) { if (opA.getReg() & 8 || opB.getReg() & 8 || opcodeBytes::hasRex64BitPrefix()) { // opA -> REX.B // baseReg -> REX.R x64Gen_writeU8(x64GenContext, 0x40 | ((opA.getReg() & 8) ? (1 << 2) : 0) | ((opB.getReg() & 8) ? (1 << 0) : 0) | (opcodeBytes::hasRex64BitPrefix() ? (1 << 3) : 0)); } } else if constexpr (TA::getType() == MODRM_OPR_TYPE::REG && TB::getType() == MODRM_OPR_TYPE::MEM) { if constexpr (opB.hasBaseReg() && opB.hasIndexReg()) { if (opA.getReg() & 8 || opB.getBaseReg() & 8 || opB.getIndexReg() & 8 || opcodeBytes::hasRex64BitPrefix()) { // opA -> REX.B // baseReg -> REX.R // indexReg -> REX.X x64Gen_writeU8(x64GenContext, 0x40 | ((opA.getReg() & 8) ? (1 << 2) : 0) | ((opB.getBaseReg() & 8) ? (1 << 0) : 0) | ((opB.getIndexReg() & 8) ? (1 << 1) : 0) | (opcodeBytes::hasRex64BitPrefix() ? (1 << 3) : 0)); } } else if constexpr (opB.hasBaseReg()) { if (opA.getReg() & 8 || opB.getBaseReg() & 8 || opcodeBytes::hasRex64BitPrefix()) { // opA -> REX.B // baseReg -> REX.R x64Gen_writeU8(x64GenContext, 0x40 | ((opA.getReg() & 8) ? (1 << 2) : 0) | ((opB.getBaseReg() & 8) ? (1 << 0) : 0) | (opcodeBytes::hasRex64BitPrefix() ? (1 << 3) : 0)); } } else { if (opA.getReg() & 8 || opcodeBytes::hasRex64BitPrefix()) { // todo - verify // opA -> REX.B x64Gen_writeU8(x64GenContext, 0x40 | ((opA.getReg() & 8) ? (1 << 2) : 0) | (opcodeBytes::hasRex64BitPrefix() ? (1 << 3) : 0)); } } } // opcode opcodeBytes::emitBytes(x64GenContext); // modrm byte if constexpr (TA::getType() == MODRM_OPR_TYPE::REG && TB::getType() == MODRM_OPR_TYPE::REG) { // reg, reg x64Gen_writeU8(x64GenContext, 0xC0 + (opB.getReg() & 7) + ((opA.getReg() & 7) << 3)); } else if constexpr (TA::getType() == MODRM_OPR_TYPE::REG && TB::getType() == MODRM_OPR_TYPE::MEM) { if constexpr (TB::hasBaseReg() == false) // todo - also check for index reg and secondary sib reg { // form: [offset] // instruction is just offset cemu_assert(false); } else if constexpr (TB::hasIndexReg()) { // form: [base+index*scaler+offset], scaler is currently fixed to 1 cemu_assert((opB.getIndexReg() & 7) != 4); // RSP not allowed as index register const uint32 offset = opB.getOffset(); if (offset == 0 && (opB.getBaseReg() & 7) != 5) // RBP/R13 has special meaning in no-offset encoding { // [form: index*1+base] x64Gen_writeU8(x64GenContext, 0x00 + (4) + ((opA.getReg() & 7) << 3)); // SIB byte x64Gen_writeU8(x64GenContext, ((opB.getIndexReg()&7) << 3) + (opB.getBaseReg() & 7)); } else if (offset == (uint32)(sint32)(sint8)offset) { // [form: index*1+base+sbyte] x64Gen_writeU8(x64GenContext, 0x40 + (4) + ((opA.getReg() & 7) << 3)); // SIB byte x64Gen_writeU8(x64GenContext, ((opB.getIndexReg() & 7) << 3) + (opB.getBaseReg() & 7)); x64Gen_writeU8(x64GenContext, (uint8)offset); } else { // [form: index*1+base+sdword] x64Gen_writeU8(x64GenContext, 0x80 + (4) + ((opA.getReg() & 7) << 3)); // SIB byte x64Gen_writeU8(x64GenContext, ((opB.getIndexReg() & 7) << 3) + (opB.getBaseReg() & 7)); x64Gen_writeU32(x64GenContext, (uint32)offset); } } else { // form: [baseReg + offset] const uint32 offset = opB.getOffset(); if (offset == 0 && (opB.getBaseReg() & 7) != 5) // RBP/R13 has special meaning in no-offset encoding { // form: [baseReg] // if base reg is RSP/R12 we need to use SIB form of instruction if ((opB.getBaseReg() & 7) == 4) { x64Gen_writeU8(x64GenContext, 0x00 + (4) + ((opA.getReg() & 7) << 3)); // SIB byte [form: none*1+base] x64Gen_writeU8(x64GenContext, (4 << 3) + (opB.getBaseReg() & 7)); } else { x64Gen_writeU8(x64GenContext, 0x00 + (opB.getBaseReg() & 7) + ((opA.getReg() & 7) << 3)); } } else if (offset == (uint32)(sint32)(sint8)offset) { // form: [baseReg+sbyte] // if base reg is RSP/R12 we need to use SIB form of instruction if ((opB.getBaseReg() & 7) == 4) { x64Gen_writeU8(x64GenContext, 0x40 + (4) + ((opA.getReg() & 7) << 3)); // SIB byte [form: none*1+base] x64Gen_writeU8(x64GenContext, (4 << 3) + (opB.getBaseReg() & 7)); } else { x64Gen_writeU8(x64GenContext, 0x40 + (opB.getBaseReg() & 7) + ((opA.getReg() & 7) << 3)); } x64Gen_writeU8(x64GenContext, (uint8)offset); } else { // form: [baseReg+sdword] // if base reg is RSP/R12 we need to use SIB form of instruction if ((opB.getBaseReg() & 7) == 4) { x64Gen_writeU8(x64GenContext, 0x80 + (4) + ((opA.getReg() & 7) << 3)); // SIB byte [form: none*1+base] x64Gen_writeU8(x64GenContext, (4 << 3) + (opB.getBaseReg() & 7)); } else { x64Gen_writeU8(x64GenContext, 0x80 + (opB.getBaseReg() & 7) + ((opA.getReg() & 7) << 3)); } x64Gen_writeU32(x64GenContext, (uint32)offset); } } } else { assert_dbg(); } } template<class opcodeBytes, typename TA, typename TB> void x64Gen_writeMODRM_dyn(x64GenContext_t* x64GenContext, TA opLeft, TB opRight) { if constexpr (opcodeBytes::isRevOrder()) _x64Gen_writeMODRM_internal<opcodeBytes, TB, TA>(x64GenContext, opRight, opLeft); else _x64Gen_writeMODRM_internal<opcodeBytes, TA, TB>(x64GenContext, opLeft, opRight); } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/BackendX64/x86Emitter.h ================================================ #pragma once // x86-64 assembler/emitter // auto generated. Do not edit this file manually typedef unsigned long long u64; typedef unsigned int u32; typedef unsigned short u16; typedef unsigned char u8; typedef signed long long s64; typedef signed int s32; typedef signed short s16; typedef signed char s8; enum X86Reg : sint8 { X86_REG_NONE = -1, X86_REG_EAX = 0, X86_REG_ECX = 1, X86_REG_EDX = 2, X86_REG_EBX = 3, X86_REG_ESP = 4, X86_REG_EBP = 5, X86_REG_ESI = 6, X86_REG_EDI = 7, X86_REG_R8D = 8, X86_REG_R9D = 9, X86_REG_R10D = 10, X86_REG_R11D = 11, X86_REG_R12D = 12, X86_REG_R13D = 13, X86_REG_R14D = 14, X86_REG_R15D = 15, X86_REG_RAX = 0, X86_REG_RCX = 1, X86_REG_RDX = 2, X86_REG_RBX = 3, X86_REG_RSP = 4, X86_REG_RBP = 5, X86_REG_RSI = 6, X86_REG_RDI = 7, X86_REG_R8 = 8, X86_REG_R9 = 9, X86_REG_R10 = 10, X86_REG_R11 = 11, X86_REG_R12 = 12, X86_REG_R13 = 13, X86_REG_R14 = 14, X86_REG_R15 = 15 }; enum X86Cond : u8 { X86_CONDITION_O = 0, X86_CONDITION_NO = 1, X86_CONDITION_B = 2, X86_CONDITION_NB = 3, X86_CONDITION_Z = 4, X86_CONDITION_NZ = 5, X86_CONDITION_BE = 6, X86_CONDITION_NBE = 7, X86_CONDITION_S = 8, X86_CONDITION_NS = 9, X86_CONDITION_PE = 10, X86_CONDITION_PO = 11, X86_CONDITION_L = 12, X86_CONDITION_NL = 13, X86_CONDITION_LE = 14, X86_CONDITION_NLE = 15 }; class x86Assembler64 { private: std::vector<u8> m_buffer; public: u8* GetBufferPtr() { return m_buffer.data(); }; std::span<u8> GetBuffer() { return m_buffer; }; u32 GetWriteIndex() { return (u32)m_buffer.size(); }; void _emitU8(u8 v) { m_buffer.emplace_back(v); }; void _emitU16(u16 v) { size_t writeIdx = m_buffer.size(); m_buffer.resize(writeIdx + 2); *(u16*)(m_buffer.data() + writeIdx) = v; }; void _emitU32(u32 v) { size_t writeIdx = m_buffer.size(); m_buffer.resize(writeIdx + 4); *(u32*)(m_buffer.data() + writeIdx) = v; }; void _emitU64(u64 v) { size_t writeIdx = m_buffer.size(); m_buffer.resize(writeIdx + 8); *(u64*)(m_buffer.data() + writeIdx) = v; }; using GPR64 = X86Reg; using GPR32 = X86Reg; using GPR8_REX = X86Reg; void LockPrefix() { _emitU8(0xF0); }; void ADD_bb(GPR8_REX dst, GPR8_REX src) { if ((src >= 4) || (dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x00); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void ADD_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src >= 4) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x00); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADD_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst >= 4) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x02); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADD_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x01); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void ADD_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x01); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void ADD_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x01); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADD_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x01); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADD_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x03); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADD_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x03); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void OR_bb(GPR8_REX dst, GPR8_REX src) { if ((src >= 4) || (dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x08); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void OR_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src >= 4) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x08); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void OR_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst >= 4) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x0a); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void OR_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x09); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void OR_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x09); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void OR_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x09); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void OR_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x09); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void OR_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x0b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void OR_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x0b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADC_bb(GPR8_REX dst, GPR8_REX src) { if ((src >= 4) || (dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x10); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void ADC_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src >= 4) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x10); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADC_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst >= 4) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x12); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADC_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x11); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void ADC_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x11); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void ADC_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x11); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADC_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x11); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADC_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x13); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADC_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x13); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SBB_bb(GPR8_REX dst, GPR8_REX src) { if ((src >= 4) || (dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x18); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void SBB_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src >= 4) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x18); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SBB_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst >= 4) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x1a); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SBB_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x19); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void SBB_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x19); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void SBB_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x19); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SBB_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x19); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SBB_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x1b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SBB_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x1b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void AND_bb(GPR8_REX dst, GPR8_REX src) { if ((src >= 4) || (dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x20); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void AND_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src >= 4) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x20); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void AND_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst >= 4) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x22); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void AND_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x21); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void AND_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x21); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void AND_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x21); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void AND_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x21); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void AND_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x23); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void AND_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x23); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SUB_bb(GPR8_REX dst, GPR8_REX src) { if ((src >= 4) || (dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x28); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void SUB_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src >= 4) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x28); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SUB_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst >= 4) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x2a); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SUB_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x29); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void SUB_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x29); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void SUB_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x29); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SUB_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x29); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SUB_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x2b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SUB_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x2b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void XOR_bb(GPR8_REX dst, GPR8_REX src) { if ((src >= 4) || (dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x30); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void XOR_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src >= 4) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x30); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void XOR_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst >= 4) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x32); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void XOR_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x31); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void XOR_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x31); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void XOR_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x31); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void XOR_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x31); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void XOR_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x33); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void XOR_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x33); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void CMP_bb(GPR8_REX dst, GPR8_REX src) { if ((src >= 4) || (dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x38); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void CMP_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src >= 4) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x38); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void CMP_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst >= 4) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x3a); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void CMP_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x39); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void CMP_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x39); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void CMP_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x39); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void CMP_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x39); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void CMP_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x3b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void CMP_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x3b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void ADD_di32(GPR32 dst, s32 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x81); _emitU8((3 << 6) | ((0 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void ADD_qi32(GPR64 dst, s32 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x81); _emitU8((3 << 6) | ((0 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void ADD_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x81); _emitU8((mod << 6) | ((0 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void ADD_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x81); _emitU8((mod << 6) | ((0 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void OR_di32(GPR32 dst, s32 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x81); _emitU8((3 << 6) | ((1 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void OR_qi32(GPR64 dst, s32 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x81); _emitU8((3 << 6) | ((1 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void OR_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x81); _emitU8((mod << 6) | ((1 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void OR_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x81); _emitU8((mod << 6) | ((1 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void ADC_di32(GPR32 dst, s32 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x81); _emitU8((3 << 6) | ((2 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void ADC_qi32(GPR64 dst, s32 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x81); _emitU8((3 << 6) | ((2 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void ADC_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x81); _emitU8((mod << 6) | ((2 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void ADC_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x81); _emitU8((mod << 6) | ((2 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void SBB_di32(GPR32 dst, s32 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x81); _emitU8((3 << 6) | ((3 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void SBB_qi32(GPR64 dst, s32 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x81); _emitU8((3 << 6) | ((3 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void SBB_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x81); _emitU8((mod << 6) | ((3 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void SBB_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x81); _emitU8((mod << 6) | ((3 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void AND_di32(GPR32 dst, s32 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x81); _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void AND_qi32(GPR64 dst, s32 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x81); _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void AND_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x81); _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void AND_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x81); _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void SUB_di32(GPR32 dst, s32 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x81); _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void SUB_qi32(GPR64 dst, s32 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x81); _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void SUB_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x81); _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void SUB_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x81); _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void XOR_di32(GPR32 dst, s32 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x81); _emitU8((3 << 6) | ((6 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void XOR_qi32(GPR64 dst, s32 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x81); _emitU8((3 << 6) | ((6 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void XOR_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x81); _emitU8((mod << 6) | ((6 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void XOR_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x81); _emitU8((mod << 6) | ((6 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void CMP_di32(GPR32 dst, s32 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x81); _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void CMP_qi32(GPR64 dst, s32 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x81); _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); _emitU32((u32)imm); } void CMP_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x81); _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void CMP_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x81); _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void ADD_di8(GPR32 dst, s8 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x83); _emitU8((3 << 6) | ((0 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void ADD_qi8(GPR64 dst, s8 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x83); _emitU8((3 << 6) | ((0 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void ADD_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x83); _emitU8((mod << 6) | ((0 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void ADD_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x83); _emitU8((mod << 6) | ((0 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void OR_di8(GPR32 dst, s8 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x83); _emitU8((3 << 6) | ((1 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void OR_qi8(GPR64 dst, s8 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x83); _emitU8((3 << 6) | ((1 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void OR_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x83); _emitU8((mod << 6) | ((1 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void OR_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x83); _emitU8((mod << 6) | ((1 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void ADC_di8(GPR32 dst, s8 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x83); _emitU8((3 << 6) | ((2 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void ADC_qi8(GPR64 dst, s8 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x83); _emitU8((3 << 6) | ((2 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void ADC_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x83); _emitU8((mod << 6) | ((2 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void ADC_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x83); _emitU8((mod << 6) | ((2 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void SBB_di8(GPR32 dst, s8 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x83); _emitU8((3 << 6) | ((3 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void SBB_qi8(GPR64 dst, s8 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x83); _emitU8((3 << 6) | ((3 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void SBB_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x83); _emitU8((mod << 6) | ((3 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void SBB_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x83); _emitU8((mod << 6) | ((3 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void AND_di8(GPR32 dst, s8 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x83); _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void AND_qi8(GPR64 dst, s8 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x83); _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void AND_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x83); _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void AND_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x83); _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void SUB_di8(GPR32 dst, s8 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x83); _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void SUB_qi8(GPR64 dst, s8 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x83); _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void SUB_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x83); _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void SUB_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x83); _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void XOR_di8(GPR32 dst, s8 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x83); _emitU8((3 << 6) | ((6 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void XOR_qi8(GPR64 dst, s8 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x83); _emitU8((3 << 6) | ((6 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void XOR_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x83); _emitU8((mod << 6) | ((6 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void XOR_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x83); _emitU8((mod << 6) | ((6 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void CMP_di8(GPR32 dst, s8 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x83); _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void CMP_qi8(GPR64 dst, s8 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x83); _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void CMP_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x83); _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void CMP_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x83); _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void TEST_bb(GPR8_REX dst, GPR8_REX src) { if ((src >= 4) || (dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x84); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void TEST_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src >= 4) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x84); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void TEST_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x85); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void TEST_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x85); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void TEST_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x85); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void TEST_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x85); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void XCHG_bb(GPR8_REX dst, GPR8_REX src) { if ((dst >= 4) || (src >= 4)) { _emitU8(0x40 | ((src & 8) >> 3) | ((dst & 8) >> 1)); } _emitU8(0x86); _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); } void XCHG_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst >= 4) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x86); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void XCHG_dd(GPR32 dst, GPR32 src) { if (((dst & 8) != 0) || ((src & 8) != 0)) { _emitU8(0x40 | ((src & 8) >> 3) | ((dst & 8) >> 1)); } _emitU8(0x87); _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); } void XCHG_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((src & 8) >> 3) | ((dst & 8) >> 1)); _emitU8(0x87); _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); } void XCHG_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x87); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void XCHG_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x87); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void MOV_bb(GPR8_REX dst, GPR8_REX src) { if ((src >= 4) || (dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x88); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void MOV_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src >= 4) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x88); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void MOV_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst >= 4) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x8a); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void MOV_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x89); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void MOV_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x89); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void MOV_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x89); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void MOV_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x89); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void MOV_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x8b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void MOV_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x8b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void MOV_di32(GPR32 dst, s32 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0xb8 | ((dst) & 7)); _emitU32((u32)imm); } void MOV_qi64(GPR64 dst, s64 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0xb8 | ((dst) & 7)); _emitU64((u64)imm); } void CALL_q(GPR64 dst) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0xff); _emitU8((3 << 6) | ((2 & 7) << 3) | (dst & 7)); } void CALL_q_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0xff); _emitU8((mod << 6) | ((2 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void IMUL_ddi32(GPR32 dst, GPR32 src, s32 imm) { if (((dst & 8) != 0) || ((src & 8) != 0)) { _emitU8(0x40 | ((src & 8) >> 3) | ((dst & 8) >> 1)); } _emitU8(0x69); _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); _emitU32((u32)imm); } void IMUL_qqi32(GPR64 dst, GPR64 src, s32 imm) { _emitU8(0x48 | ((src & 8) >> 3) | ((dst & 8) >> 1)); _emitU8(0x69); _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); _emitU32((u32)imm); } void IMUL_ddi32_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x69); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void IMUL_qqi32_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x69); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU32((u32)imm); } void IMUL_ddi8(GPR32 dst, GPR32 src, s8 imm) { if (((dst & 8) != 0) || ((src & 8) != 0)) { _emitU8(0x40 | ((src & 8) >> 3) | ((dst & 8) >> 1)); } _emitU8(0x6b); _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); _emitU8((u8)imm); } void IMUL_qqi8(GPR64 dst, GPR64 src, s8 imm) { _emitU8(0x48 | ((src & 8) >> 3) | ((dst & 8) >> 1)); _emitU8(0x6b); _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); _emitU8((u8)imm); } void IMUL_ddi8_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((dst & 8) || (memReg & 8)) _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x6b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void IMUL_qqi8_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x6b); _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void SHL_b_CL(GPR8_REX dst) { if ((dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0xd2); _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); } void SHL_b_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0xd2); _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SHR_b_CL(GPR8_REX dst) { if ((dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0xd2); _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); } void SHR_b_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0xd2); _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SAR_b_CL(GPR8_REX dst) { if ((dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0xd2); _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); } void SAR_b_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0xd2); _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SHL_d_CL(GPR32 dst) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0xd3); _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); } void SHL_q_CL(GPR64 dst) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0xd3); _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); } void SHL_d_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0xd3); _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SHL_q_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0xd3); _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SHR_d_CL(GPR32 dst) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0xd3); _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); } void SHR_q_CL(GPR64 dst) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0xd3); _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); } void SHR_d_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0xd3); _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SHR_q_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0xd3); _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SAR_d_CL(GPR32 dst) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0xd3); _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); } void SAR_q_CL(GPR64 dst) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0xd3); _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); } void SAR_d_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0xd3); _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void SAR_q_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0xd3); _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void JMP_j32(s32 imm) { _emitU8(0xe9); _emitU32((u32)imm); } void Jcc_j32(X86Cond cond, s32 imm) { _emitU8(0x0f); _emitU8(0x80 | (u8)cond); _emitU32((u32)imm); } void SETcc_b(X86Cond cond, GPR8_REX dst) { if ((dst >= 4)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x0f); _emitU8(0x90 | (u8)cond); _emitU8((3 << 6) | (dst & 7)); } void SETcc_b_l(X86Cond cond, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x0f); _emitU8(0x90); _emitU8((mod << 6) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void CMPXCHG_dd(GPR32 dst, GPR32 src) { if (((src & 8) != 0) || ((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); } _emitU8(0x0f); _emitU8(0xb1); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void CMPXCHG_qq(GPR64 dst, GPR64 src) { _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); _emitU8(0x0f); _emitU8(0xb1); _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); } void CMPXCHG_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((src & 8) || (memReg & 8)) _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); } _emitU8(0x0f); _emitU8(0xb1); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void CMPXCHG_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x0f); _emitU8(0xb1); _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); } void BSWAP_d(GPR32 dst) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x0f); _emitU8(0xc8 | ((dst) & 7)); } void BSWAP_q(GPR64 dst) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x0f); _emitU8(0xc8 | ((dst) & 7)); } void BT_du8(GPR32 dst, u8 imm) { if (((dst & 8) != 0)) { _emitU8(0x40 | ((dst & 8) >> 3)); } _emitU8(0x0f); _emitU8(0xba); _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void BT_qu8(GPR64 dst, u8 imm) { _emitU8(0x48 | ((dst & 8) >> 3)); _emitU8(0x0f); _emitU8(0xba); _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); _emitU8((u8)imm); } void BT_du8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, u8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); } else { if ((memReg & 8)) _emitU8(0x40 | ((memReg & 8) >> 1)); } _emitU8(0x0f); _emitU8(0xba); _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } void BT_qu8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, u8 imm) { uint8 mod; if (offset == 0 && (memReg & 7) != 5) mod = 0; else if (offset == (s32)(s8)offset) mod = 1; else mod = 2; bool sib_use = (scaler != 0 && index != X86_REG_NONE); if ((memReg & 7) == 4) { cemu_assert_debug(index == X86_REG_NONE); index = memReg; sib_use = true; } if (sib_use) { _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); } else { _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); } _emitU8(0x0f); _emitU8(0xba); _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); if (sib_use) { _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); } if (mod == 1) _emitU8((u8)offset); else if (mod == 2) _emitU32((u32)offset); _emitU8((u8)imm); } }; ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IML.h ================================================ #pragma once #include "IMLInstruction.h" #include "IMLSegment.h" // optimizer passes void IMLOptimizer_OptimizeDirectFloatCopies(struct ppcImlGenContext_t* ppcImlGenContext); void IMLOptimizer_OptimizeDirectIntegerCopies(struct ppcImlGenContext_t* ppcImlGenContext); void PPCRecompiler_optimizePSQLoadAndStore(struct ppcImlGenContext_t* ppcImlGenContext); void IMLOptimizer_StandardOptimizationPass(ppcImlGenContext_t& ppcImlGenContext); // debug void IMLDebug_DisassembleInstruction(const IMLInstruction& inst, std::string& disassemblyLineOut); void IMLDebug_DumpSegment(struct ppcImlGenContext_t* ctx, IMLSegment* imlSegment, bool printLivenessRangeInfo = false); void IMLDebug_Dump(struct ppcImlGenContext_t* ppcImlGenContext, bool printLivenessRangeInfo = false); ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp ================================================ #include "IML.h" //#include "PPCRecompilerIml.h" #include "util/helpers/fixedSizeList.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp ================================================ #include "IML.h" #include "IMLInstruction.h" #include "IMLSegment.h" #include "IMLRegisterAllocatorRanges.h" #include "util/helpers/StringBuf.h" #include "../PPCRecompiler.h" const char* IMLDebug_GetOpcodeName(const IMLInstruction* iml) { static char _tempOpcodename[32]; uint32 op = iml->operation; if (op == PPCREC_IML_OP_ASSIGN) return "MOV"; else if (op == PPCREC_IML_OP_ADD) return "ADD"; else if (op == PPCREC_IML_OP_ADD_WITH_CARRY) return "ADC"; else if (op == PPCREC_IML_OP_SUB) return "SUB"; else if (op == PPCREC_IML_OP_OR) return "OR"; else if (op == PPCREC_IML_OP_AND) return "AND"; else if (op == PPCREC_IML_OP_XOR) return "XOR"; else if (op == PPCREC_IML_OP_LEFT_SHIFT) return "LSH"; else if (op == PPCREC_IML_OP_RIGHT_SHIFT_U) return "RSH"; else if (op == PPCREC_IML_OP_RIGHT_SHIFT_S) return "ARSH"; else if (op == PPCREC_IML_OP_LEFT_ROTATE) return "LROT"; else if (op == PPCREC_IML_OP_MULTIPLY_SIGNED) return "MULS"; else if (op == PPCREC_IML_OP_DIVIDE_SIGNED) return "DIVS"; else if (op == PPCREC_IML_OP_FPR_ASSIGN) return "FMOV"; else if (op == PPCREC_IML_OP_FPR_ADD) return "FADD"; else if (op == PPCREC_IML_OP_FPR_SUB) return "FSUB"; else if (op == PPCREC_IML_OP_FPR_MULTIPLY) return "FMUL"; else if (op == PPCREC_IML_OP_FPR_DIVIDE) return "FDIV"; else if (op == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64) return "F32TOF64"; else if (op == PPCREC_IML_OP_FPR_ABS) return "FABS"; else if (op == PPCREC_IML_OP_FPR_NEGATE) return "FNEG"; else if (op == PPCREC_IML_OP_FPR_NEGATIVE_ABS) return "FNABS"; else if (op == PPCREC_IML_OP_FPR_FLOAT_TO_INT) return "F2I"; else if (op == PPCREC_IML_OP_FPR_INT_TO_FLOAT) return "I2F"; else if (op == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT) return "BITMOVE"; sprintf(_tempOpcodename, "OP0%02x_T%d", iml->operation, iml->type); return _tempOpcodename; } std::string IMLDebug_GetRegName(IMLReg r) { std::string regName; uint32 regId = r.GetRegID(); switch (r.GetRegFormat()) { case IMLRegFormat::F32: regName.append("f"); break; case IMLRegFormat::F64: regName.append("fd"); break; case IMLRegFormat::I32: regName.append("i"); break; case IMLRegFormat::I64: regName.append("r"); break; default: DEBUG_BREAK; } regName.append(fmt::format("{}", regId)); return regName; } void IMLDebug_AppendRegisterParam(StringBuf& strOutput, IMLReg virtualRegister, bool isLast = false) { strOutput.add(IMLDebug_GetRegName(virtualRegister)); if (!isLast) strOutput.add(", "); } void IMLDebug_AppendS32Param(StringBuf& strOutput, sint32 val, bool isLast = false) { if (val < 0) { strOutput.add("-"); val = -val; } strOutput.addFmt("0x{:08x}", val); if (!isLast) strOutput.add(", "); } void IMLDebug_PrintLivenessRangeInfo(StringBuf& currentLineText, IMLSegment* imlSegment, sint32 offset) { // pad to 70 characters sint32 index = currentLineText.getLen(); while (index < 70) { currentLineText.add(" "); index++; } raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { if (subrangeItr->interval.start.GetInstructionIndexEx() == offset) { if(subrangeItr->interval.start.IsInstructionIndex() && !subrangeItr->interval.start.IsOnInputEdge()) currentLineText.add("."); else currentLineText.add("|"); currentLineText.addFmt("{:<4}", subrangeItr->GetVirtualRegister()); } else if (subrangeItr->interval.end.GetInstructionIndexEx() == offset) { if(subrangeItr->interval.end.IsInstructionIndex() && !subrangeItr->interval.end.IsOnOutputEdge()) currentLineText.add("* "); else currentLineText.add("| "); } else if (subrangeItr->interval.ContainsInstructionIndexEx(offset)) { currentLineText.add("| "); } else { currentLineText.add(" "); } index += 5; // next subrangeItr = subrangeItr->link_allSegmentRanges.next; } } std::string IMLDebug_GetSegmentName(ppcImlGenContext_t* ctx, IMLSegment* seg) { if (!ctx) { return "<NoNameWithoutCtx>"; } // find segment index for (size_t i = 0; i < ctx->segmentList2.size(); i++) { if (ctx->segmentList2[i] == seg) { return fmt::format("Seg{:04x}", i); } } return "<SegmentNotInCtx>"; } std::string IMLDebug_GetConditionName(IMLCondition cond) { switch (cond) { case IMLCondition::EQ: return "EQ"; case IMLCondition::NEQ: return "NEQ"; case IMLCondition::UNSIGNED_GT: return "UGT"; case IMLCondition::UNSIGNED_LT: return "ULT"; case IMLCondition::SIGNED_GT: return "SGT"; case IMLCondition::SIGNED_LT: return "SLT"; default: cemu_assert_unimplemented(); } return "ukn"; } void IMLDebug_DisassembleInstruction(const IMLInstruction& inst, std::string& disassemblyLineOut) { const sint32 lineOffsetParameters = 10;//18; StringBuf strOutput(1024); strOutput.reset(); if (inst.type == PPCREC_IML_TYPE_R_NAME || inst.type == PPCREC_IML_TYPE_NAME_R) { if (inst.type == PPCREC_IML_TYPE_R_NAME) strOutput.add("R_NAME"); else strOutput.add("NAME_R"); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); if(inst.type == PPCREC_IML_TYPE_R_NAME) IMLDebug_AppendRegisterParam(strOutput, inst.op_r_name.regR); strOutput.add("name_"); if (inst.op_r_name.name >= PPCREC_NAME_R0 && inst.op_r_name.name < (PPCREC_NAME_R0 + 999)) { strOutput.addFmt("r{}", inst.op_r_name.name - PPCREC_NAME_R0); } if (inst.op_r_name.name >= PPCREC_NAME_FPR_HALF && inst.op_r_name.name < (PPCREC_NAME_FPR_HALF + 32*2)) { strOutput.addFmt("f{}", inst.op_r_name.name - ((PPCREC_NAME_FPR_HALF - inst.op_r_name.name)/2)); if ((inst.op_r_name.name-PPCREC_NAME_FPR_HALF)&1) strOutput.add(".ps1"); else strOutput.add(".ps0"); } else if (inst.op_r_name.name >= PPCREC_NAME_SPR0 && inst.op_r_name.name < (PPCREC_NAME_SPR0 + 999)) { strOutput.addFmt("spr{}", inst.op_r_name.name - PPCREC_NAME_SPR0); } else if (inst.op_r_name.name >= PPCREC_NAME_CR && inst.op_r_name.name <= PPCREC_NAME_CR_LAST) strOutput.addFmt("cr{}", inst.op_r_name.name - PPCREC_NAME_CR); else if (inst.op_r_name.name == PPCREC_NAME_XER_CA) strOutput.add("xer.ca"); else if (inst.op_r_name.name == PPCREC_NAME_XER_SO) strOutput.add("xer.so"); else if (inst.op_r_name.name == PPCREC_NAME_XER_OV) strOutput.add("xer.ov"); else if (inst.op_r_name.name == PPCREC_NAME_CPU_MEMRES_EA) strOutput.add("cpuReservation.ea"); else if (inst.op_r_name.name == PPCREC_NAME_CPU_MEMRES_VAL) strOutput.add("cpuReservation.value"); else { strOutput.addFmt("name_ukn{}", inst.op_r_name.name); } if (inst.type != PPCREC_IML_TYPE_R_NAME) { strOutput.add(", "); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_name.regR, true); } } else if (inst.type == PPCREC_IML_TYPE_R_R) { strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r.regR); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r.regA, true); } else if (inst.type == PPCREC_IML_TYPE_R_R_R) { strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regR); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regA); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regB, true); } else if (inst.type == PPCREC_IML_TYPE_R_R_R_CARRY) { strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regR); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regA); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regB); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regCarry, true); } else if (inst.type == PPCREC_IML_TYPE_COMPARE) { strOutput.add("CMP "); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regA); IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regB); strOutput.addFmt("{}", IMLDebug_GetConditionName(inst.op_compare.cond)); strOutput.add(" -> "); IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regR, true); } else if (inst.type == PPCREC_IML_TYPE_COMPARE_S32) { strOutput.add("CMP "); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_compare_s32.regA); strOutput.addFmt("{}", inst.op_compare_s32.immS32); strOutput.addFmt(", {}", IMLDebug_GetConditionName(inst.op_compare_s32.cond)); strOutput.add(" -> "); IMLDebug_AppendRegisterParam(strOutput, inst.op_compare_s32.regR, true); } else if (inst.type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) { strOutput.add("CJUMP "); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_conditional_jump.registerBool, true); if (!inst.op_conditional_jump.mustBeTrue) strOutput.add("(inverted)"); } else if (inst.type == PPCREC_IML_TYPE_JUMP) { strOutput.add("JUMP"); } else if (inst.type == PPCREC_IML_TYPE_R_R_S32) { strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32.regR); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32.regA); IMLDebug_AppendS32Param(strOutput, inst.op_r_r_s32.immS32, true); } else if (inst.type == PPCREC_IML_TYPE_R_R_S32_CARRY) { strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regR); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regA); IMLDebug_AppendS32Param(strOutput, inst.op_r_r_s32_carry.immS32); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regCarry, true); } else if (inst.type == PPCREC_IML_TYPE_R_S32) { strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_r_immS32.regR); IMLDebug_AppendS32Param(strOutput, inst.op_r_immS32.immS32, true); } else if (inst.type == PPCREC_IML_TYPE_LOAD || inst.type == PPCREC_IML_TYPE_STORE || inst.type == PPCREC_IML_TYPE_LOAD_INDEXED || inst.type == PPCREC_IML_TYPE_STORE_INDEXED) { if (inst.type == PPCREC_IML_TYPE_LOAD || inst.type == PPCREC_IML_TYPE_LOAD_INDEXED) strOutput.add("LD_"); else strOutput.add("ST_"); if (inst.op_storeLoad.flags2.signExtend) strOutput.add("S"); else strOutput.add("U"); strOutput.addFmt("{}", inst.op_storeLoad.copyWidth); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_storeLoad.registerData); if (inst.type == PPCREC_IML_TYPE_LOAD_INDEXED || inst.type == PPCREC_IML_TYPE_STORE_INDEXED) strOutput.addFmt("[{}+{}]", IMLDebug_GetRegName(inst.op_storeLoad.registerMem), IMLDebug_GetRegName(inst.op_storeLoad.registerMem2)); else strOutput.addFmt("[{}+{}]", IMLDebug_GetRegName(inst.op_storeLoad.registerMem), inst.op_storeLoad.immS32); } else if (inst.type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) { strOutput.add("ATOMIC_ST_U32"); while ((sint32)strOutput.getLen() < lineOffsetParameters) strOutput.add(" "); IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regEA); IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regCompareValue); IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regWriteValue); IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regBoolOut, true); } else if (inst.type == PPCREC_IML_TYPE_NO_OP) { strOutput.add("NOP"); } else if (inst.type == PPCREC_IML_TYPE_MACRO) { if (inst.operation == PPCREC_IML_MACRO_B_TO_REG) { strOutput.addFmt("MACRO B_TO_REG {}", IMLDebug_GetRegName(inst.op_macro.paramReg)); } else if (inst.operation == PPCREC_IML_MACRO_BL) { strOutput.addFmt("MACRO BL 0x{:08x} -> 0x{:08x} cycles (depr): {}", inst.op_macro.param, inst.op_macro.param2, (sint32)inst.op_macro.paramU16); } else if (inst.operation == PPCREC_IML_MACRO_B_FAR) { strOutput.addFmt("MACRO B_FAR 0x{:08x} -> 0x{:08x} cycles (depr): {}", inst.op_macro.param, inst.op_macro.param2, (sint32)inst.op_macro.paramU16); } else if (inst.operation == PPCREC_IML_MACRO_LEAVE) { strOutput.addFmt("MACRO LEAVE ppc: 0x{:08x}", inst.op_macro.param); } else if (inst.operation == PPCREC_IML_MACRO_HLE) { strOutput.addFmt("MACRO HLE ppcAddr: 0x{:08x} funcId: 0x{:08x}", inst.op_macro.param, inst.op_macro.param2); } else if (inst.operation == PPCREC_IML_MACRO_COUNT_CYCLES) { strOutput.addFmt("MACRO COUNT_CYCLES cycles: {}", inst.op_macro.param); } else { strOutput.addFmt("MACRO ukn operation {}", inst.operation); } } else if (inst.type == PPCREC_IML_TYPE_FPR_LOAD) { strOutput.addFmt("{} = ", IMLDebug_GetRegName(inst.op_storeLoad.registerData)); if (inst.op_storeLoad.flags2.signExtend) strOutput.add("S"); else strOutput.add("U"); strOutput.addFmt("{} [{}+{}] mode {}", inst.op_storeLoad.copyWidth / 8, IMLDebug_GetRegName(inst.op_storeLoad.registerMem), inst.op_storeLoad.immS32, inst.op_storeLoad.mode); if (inst.op_storeLoad.flags2.notExpanded) { strOutput.addFmt(" <No expand>"); } } else if (inst.type == PPCREC_IML_TYPE_FPR_STORE) { if (inst.op_storeLoad.flags2.signExtend) strOutput.add("S"); else strOutput.add("U"); strOutput.addFmt("{} [t{}+{}]", inst.op_storeLoad.copyWidth / 8, inst.op_storeLoad.registerMem.GetRegID(), inst.op_storeLoad.immS32); strOutput.addFmt(" = {} mode {}", IMLDebug_GetRegName(inst.op_storeLoad.registerData), inst.op_storeLoad.mode); } else if (inst.type == PPCREC_IML_TYPE_FPR_R) { strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst)); strOutput.addFmt("{}", IMLDebug_GetRegName(inst.op_fpr_r.regR)); } else if (inst.type == PPCREC_IML_TYPE_FPR_R_R) { strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst)); strOutput.addFmt("{}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r.regA)); } else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R_R) { strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst)); strOutput.addFmt("{}, {}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regB), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regC)); } else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R) { strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst)); strOutput.addFmt("{}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regB)); } else if (inst.type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) { strOutput.addFmt("CYCLE_CHECK"); } else if (inst.type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) { strOutput.addFmt("X86_JCC {}", IMLDebug_GetConditionName(inst.op_x86_eflags_jcc.cond)); } else { strOutput.addFmt("Unknown iml type {}", inst.type); } disassemblyLineOut.assign(strOutput.c_str()); } void IMLDebug_DumpSegment(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, bool printLivenessRangeInfo) { StringBuf strOutput(4096); strOutput.addFmt("SEGMENT {} | PPC=0x{:08x} Loop-depth {}", IMLDebug_GetSegmentName(ctx, imlSegment), imlSegment->ppcAddress, imlSegment->loopDepth); if (imlSegment->isEnterable) { strOutput.addFmt(" ENTERABLE (0x{:08x})", imlSegment->enterPPCAddress); } if (imlSegment->deadCodeEliminationHintSeg) { strOutput.addFmt(" InheritOverwrite: {}", IMLDebug_GetSegmentName(ctx, imlSegment->deadCodeEliminationHintSeg)); } cemuLog_log(LogType::Force, "{}", strOutput.c_str()); if (printLivenessRangeInfo) { strOutput.reset(); IMLDebug_PrintLivenessRangeInfo(strOutput, imlSegment, RA_INTER_RANGE_START); cemuLog_log(LogType::Force, "{}", strOutput.c_str()); } //debug_printf("\n"); strOutput.reset(); std::string disassemblyLine; for (sint32 i = 0; i < imlSegment->imlList.size(); i++) { const IMLInstruction& inst = imlSegment->imlList[i]; // don't log NOP instructions if (inst.type == PPCREC_IML_TYPE_NO_OP) continue; strOutput.reset(); strOutput.addFmt("{:02x} ", i); //cemuLog_log(LogType::Force, "{:02x} ", i); disassemblyLine.clear(); IMLDebug_DisassembleInstruction(inst, disassemblyLine); strOutput.add(disassemblyLine); if (printLivenessRangeInfo) { IMLDebug_PrintLivenessRangeInfo(strOutput, imlSegment, i); } cemuLog_log(LogType::Force, "{}", strOutput.c_str()); } // all ranges if (printLivenessRangeInfo) { strOutput.reset(); strOutput.add("Ranges-VirtReg "); raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { strOutput.addFmt("v{:<4}", (uint32)subrangeItr->GetVirtualRegister()); subrangeItr = subrangeItr->link_allSegmentRanges.next; } cemuLog_log(LogType::Force, "{}", strOutput.c_str()); strOutput.reset(); strOutput.add("Ranges-PhysReg "); subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { strOutput.addFmt("p{:<4}", subrangeItr->GetPhysicalRegister()); subrangeItr = subrangeItr->link_allSegmentRanges.next; } cemuLog_log(LogType::Force, "{}", strOutput.c_str()); } // branch info strOutput.reset(); strOutput.add("Links from: "); for (sint32 i = 0; i < imlSegment->list_prevSegments.size(); i++) { if (i) strOutput.add(", "); strOutput.addFmt("{}", IMLDebug_GetSegmentName(ctx, imlSegment->list_prevSegments[i]).c_str()); } cemuLog_log(LogType::Force, "{}", strOutput.c_str()); if (imlSegment->nextSegmentBranchNotTaken) cemuLog_log(LogType::Force, "BranchNotTaken: {}", IMLDebug_GetSegmentName(ctx, imlSegment->nextSegmentBranchNotTaken).c_str()); if (imlSegment->nextSegmentBranchTaken) cemuLog_log(LogType::Force, "BranchTaken: {}", IMLDebug_GetSegmentName(ctx, imlSegment->nextSegmentBranchTaken).c_str()); if (imlSegment->nextSegmentIsUncertain) cemuLog_log(LogType::Force, "Dynamic target"); } void IMLDebug_Dump(ppcImlGenContext_t* ppcImlGenContext, bool printLivenessRangeInfo) { for (size_t i = 0; i < ppcImlGenContext->segmentList2.size(); i++) { IMLDebug_DumpSegment(ppcImlGenContext, ppcImlGenContext->segmentList2[i], printLivenessRangeInfo); cemuLog_log(LogType::Force, ""); } } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp ================================================ #include "IMLInstruction.h" #include "IML.h" #include "../PPCRecompiler.h" #include "../PPCRecompilerIml.h" // return true if an instruction has side effects on top of just reading and writing registers bool IMLInstruction::HasSideEffects() const { bool hasSideEffects = true; if(type == PPCREC_IML_TYPE_R_R || type == PPCREC_IML_TYPE_R_R_S32 || type == PPCREC_IML_TYPE_COMPARE || type == PPCREC_IML_TYPE_COMPARE_S32) hasSideEffects = false; // todo - add more cases return hasSideEffects; } void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const { registersUsed->readGPR1 = IMLREG_INVALID; registersUsed->readGPR2 = IMLREG_INVALID; registersUsed->readGPR3 = IMLREG_INVALID; registersUsed->readGPR4 = IMLREG_INVALID; registersUsed->writtenGPR1 = IMLREG_INVALID; registersUsed->writtenGPR2 = IMLREG_INVALID; if (type == PPCREC_IML_TYPE_R_NAME) { registersUsed->writtenGPR1 = op_r_name.regR; } else if (type == PPCREC_IML_TYPE_NAME_R) { registersUsed->readGPR1 = op_r_name.regR; } else if (type == PPCREC_IML_TYPE_R_R) { if (operation == PPCREC_IML_OP_X86_CMP) { // both operands are read only registersUsed->readGPR1 = op_r_r.regR; registersUsed->readGPR2 = op_r_r.regA; } else if ( operation == PPCREC_IML_OP_ASSIGN || operation == PPCREC_IML_OP_ENDIAN_SWAP || operation == PPCREC_IML_OP_CNTLZW || operation == PPCREC_IML_OP_NOT || operation == PPCREC_IML_OP_NEG || operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32 || operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32) { // result is written, operand is read registersUsed->writtenGPR1 = op_r_r.regR; registersUsed->readGPR1 = op_r_r.regA; } else cemu_assert_unimplemented(); } else if (type == PPCREC_IML_TYPE_R_S32) { cemu_assert_debug(operation != PPCREC_IML_OP_ADD && operation != PPCREC_IML_OP_SUB && operation != PPCREC_IML_OP_AND && operation != PPCREC_IML_OP_OR && operation != PPCREC_IML_OP_XOR); // deprecated, use r_r_s32 for these if (operation == PPCREC_IML_OP_LEFT_ROTATE) { // register operand is read and write registersUsed->readGPR1 = op_r_immS32.regR; registersUsed->writtenGPR1 = op_r_immS32.regR; } else if (operation == PPCREC_IML_OP_X86_CMP) { // register operand is read only registersUsed->readGPR1 = op_r_immS32.regR; } else { // register operand is write only // todo - use explicit lists, avoid default cases registersUsed->writtenGPR1 = op_r_immS32.regR; } } else if (type == PPCREC_IML_TYPE_R_R_S32) { registersUsed->writtenGPR1 = op_r_r_s32.regR; registersUsed->readGPR1 = op_r_r_s32.regA; } else if (type == PPCREC_IML_TYPE_R_R_S32_CARRY) { registersUsed->writtenGPR1 = op_r_r_s32_carry.regR; registersUsed->readGPR1 = op_r_r_s32_carry.regA; // some operations read carry switch (operation) { case PPCREC_IML_OP_ADD_WITH_CARRY: registersUsed->readGPR2 = op_r_r_s32_carry.regCarry; break; case PPCREC_IML_OP_ADD: break; default: cemu_assert_unimplemented(); } // carry is always written registersUsed->writtenGPR2 = op_r_r_s32_carry.regCarry; } else if (type == PPCREC_IML_TYPE_R_R_R) { // in all cases result is written and other operands are read only // with the exception of XOR, where if regA == regB then all bits are zeroed out. So we don't consider it a read registersUsed->writtenGPR1 = op_r_r_r.regR; if(!(operation == PPCREC_IML_OP_XOR && op_r_r_r.regA == op_r_r_r.regB)) { registersUsed->readGPR1 = op_r_r_r.regA; registersUsed->readGPR2 = op_r_r_r.regB; } } else if (type == PPCREC_IML_TYPE_R_R_R_CARRY) { registersUsed->writtenGPR1 = op_r_r_r_carry.regR; registersUsed->readGPR1 = op_r_r_r_carry.regA; registersUsed->readGPR2 = op_r_r_r_carry.regB; // some operations read carry switch (operation) { case PPCREC_IML_OP_ADD_WITH_CARRY: registersUsed->readGPR3 = op_r_r_r_carry.regCarry; break; case PPCREC_IML_OP_ADD: break; default: cemu_assert_unimplemented(); } // carry is always written registersUsed->writtenGPR2 = op_r_r_r_carry.regCarry; } else if (type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) { // no effect on registers } else if (type == PPCREC_IML_TYPE_NO_OP) { // no effect on registers } else if (type == PPCREC_IML_TYPE_MACRO) { if (operation == PPCREC_IML_MACRO_BL || operation == PPCREC_IML_MACRO_B_FAR || operation == PPCREC_IML_MACRO_LEAVE || operation == PPCREC_IML_MACRO_DEBUGBREAK || operation == PPCREC_IML_MACRO_COUNT_CYCLES || operation == PPCREC_IML_MACRO_HLE) { // no effect on registers } else if (operation == PPCREC_IML_MACRO_B_TO_REG) { cemu_assert_debug(op_macro.paramReg.IsValid()); registersUsed->readGPR1 = op_macro.paramReg; } else cemu_assert_unimplemented(); } else if (type == PPCREC_IML_TYPE_COMPARE) { registersUsed->readGPR1 = op_compare.regA; registersUsed->readGPR2 = op_compare.regB; registersUsed->writtenGPR1 = op_compare.regR; } else if (type == PPCREC_IML_TYPE_COMPARE_S32) { registersUsed->readGPR1 = op_compare_s32.regA; registersUsed->writtenGPR1 = op_compare_s32.regR; } else if (type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) { registersUsed->readGPR1 = op_conditional_jump.registerBool; } else if (type == PPCREC_IML_TYPE_JUMP) { // no registers affected } else if (type == PPCREC_IML_TYPE_LOAD) { registersUsed->writtenGPR1 = op_storeLoad.registerData; if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR1 = op_storeLoad.registerMem; } else if (type == PPCREC_IML_TYPE_LOAD_INDEXED) { registersUsed->writtenGPR1 = op_storeLoad.registerData; if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR1 = op_storeLoad.registerMem; if (op_storeLoad.registerMem2.IsValid()) registersUsed->readGPR2 = op_storeLoad.registerMem2; } else if (type == PPCREC_IML_TYPE_STORE) { registersUsed->readGPR1 = op_storeLoad.registerData; if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR2 = op_storeLoad.registerMem; } else if (type == PPCREC_IML_TYPE_STORE_INDEXED) { registersUsed->readGPR1 = op_storeLoad.registerData; if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR2 = op_storeLoad.registerMem; if (op_storeLoad.registerMem2.IsValid()) registersUsed->readGPR3 = op_storeLoad.registerMem2; } else if (type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) { registersUsed->readGPR1 = op_atomic_compare_store.regEA; registersUsed->readGPR2 = op_atomic_compare_store.regCompareValue; registersUsed->readGPR3 = op_atomic_compare_store.regWriteValue; registersUsed->writtenGPR1 = op_atomic_compare_store.regBoolOut; } else if (type == PPCREC_IML_TYPE_CALL_IMM) { if (op_call_imm.regParam0.IsValid()) registersUsed->readGPR1 = op_call_imm.regParam0; if (op_call_imm.regParam1.IsValid()) registersUsed->readGPR2 = op_call_imm.regParam1; if (op_call_imm.regParam2.IsValid()) registersUsed->readGPR3 = op_call_imm.regParam2; registersUsed->writtenGPR1 = op_call_imm.regReturn; } else if (type == PPCREC_IML_TYPE_FPR_LOAD) { // fpr load operation registersUsed->writtenGPR1 = op_storeLoad.registerData; // address is in gpr register if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR1 = op_storeLoad.registerMem; } else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) { // fpr load operation registersUsed->writtenGPR1 = op_storeLoad.registerData; // address is in gpr registers if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR1 = op_storeLoad.registerMem; if (op_storeLoad.registerMem2.IsValid()) registersUsed->readGPR2 = op_storeLoad.registerMem2; } else if (type == PPCREC_IML_TYPE_FPR_STORE) { // fpr store operation registersUsed->readGPR1 = op_storeLoad.registerData; if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR2 = op_storeLoad.registerMem; } else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) { // fpr store operation registersUsed->readGPR1 = op_storeLoad.registerData; // address is in gpr registers if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR2 = op_storeLoad.registerMem; if (op_storeLoad.registerMem2.IsValid()) registersUsed->readGPR3 = op_storeLoad.registerMem2; } else if (type == PPCREC_IML_TYPE_FPR_R_R) { // fpr operation if ( operation == PPCREC_IML_OP_FPR_ASSIGN || operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64 || operation == PPCREC_IML_OP_FPR_FCTIWZ ) { registersUsed->readGPR1 = op_fpr_r_r.regA; registersUsed->writtenGPR1 = op_fpr_r_r.regR; } else if (operation == PPCREC_IML_OP_FPR_MULTIPLY || operation == PPCREC_IML_OP_FPR_DIVIDE || operation == PPCREC_IML_OP_FPR_ADD || operation == PPCREC_IML_OP_FPR_SUB) { registersUsed->readGPR1 = op_fpr_r_r.regA; registersUsed->readGPR2 = op_fpr_r_r.regR; registersUsed->writtenGPR1 = op_fpr_r_r.regR; } else if (operation == PPCREC_IML_OP_FPR_FLOAT_TO_INT || operation == PPCREC_IML_OP_FPR_INT_TO_FLOAT || operation == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT) { registersUsed->writtenGPR1 = op_fpr_r_r.regR; registersUsed->readGPR1 = op_fpr_r_r.regA; } else cemu_assert_unimplemented(); } else if (type == PPCREC_IML_TYPE_FPR_R_R_R) { // fpr operation registersUsed->readGPR1 = op_fpr_r_r_r.regA; registersUsed->readGPR2 = op_fpr_r_r_r.regB; registersUsed->writtenGPR1 = op_fpr_r_r_r.regR; } else if (type == PPCREC_IML_TYPE_FPR_R_R_R_R) { // fpr operation registersUsed->readGPR1 = op_fpr_r_r_r_r.regA; registersUsed->readGPR2 = op_fpr_r_r_r_r.regB; registersUsed->readGPR3 = op_fpr_r_r_r_r.regC; registersUsed->writtenGPR1 = op_fpr_r_r_r_r.regR; } else if (type == PPCREC_IML_TYPE_FPR_R) { // fpr operation if (operation == PPCREC_IML_OP_FPR_NEGATE || operation == PPCREC_IML_OP_FPR_ABS || operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS || operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64 || operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM) { registersUsed->readGPR1 = op_fpr_r.regR; registersUsed->writtenGPR1 = op_fpr_r.regR; } else if (operation == PPCREC_IML_OP_FPR_LOAD_ONE) { registersUsed->writtenGPR1 = op_fpr_r.regR; } else cemu_assert_unimplemented(); } else if (type == PPCREC_IML_TYPE_FPR_COMPARE) { registersUsed->writtenGPR1 = op_fpr_compare.regR; registersUsed->readGPR1 = op_fpr_compare.regA; registersUsed->readGPR2 = op_fpr_compare.regB; } else if (type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) { // no registers read or written (except for the implicit eflags) } else { cemu_assert_unimplemented(); } } IMLReg replaceRegisterIdMultiple(IMLReg reg, const std::unordered_map<IMLRegID, IMLRegID>& translationTable) { if (reg.IsInvalid()) return reg; const auto& it = translationTable.find(reg.GetRegID()); cemu_assert_debug(it != translationTable.cend()); IMLReg alteredReg = reg; alteredReg.SetRegID(it->second); return alteredReg; } void IMLInstruction::RewriteGPR(const std::unordered_map<IMLRegID, IMLRegID>& translationTable) { if (type == PPCREC_IML_TYPE_R_NAME) { op_r_name.regR = replaceRegisterIdMultiple(op_r_name.regR, translationTable); } else if (type == PPCREC_IML_TYPE_NAME_R) { op_r_name.regR = replaceRegisterIdMultiple(op_r_name.regR, translationTable); } else if (type == PPCREC_IML_TYPE_R_R) { op_r_r.regR = replaceRegisterIdMultiple(op_r_r.regR, translationTable); op_r_r.regA = replaceRegisterIdMultiple(op_r_r.regA, translationTable); } else if (type == PPCREC_IML_TYPE_R_S32) { op_r_immS32.regR = replaceRegisterIdMultiple(op_r_immS32.regR, translationTable); } else if (type == PPCREC_IML_TYPE_R_R_S32) { op_r_r_s32.regR = replaceRegisterIdMultiple(op_r_r_s32.regR, translationTable); op_r_r_s32.regA = replaceRegisterIdMultiple(op_r_r_s32.regA, translationTable); } else if (type == PPCREC_IML_TYPE_R_R_S32_CARRY) { op_r_r_s32_carry.regR = replaceRegisterIdMultiple(op_r_r_s32_carry.regR, translationTable); op_r_r_s32_carry.regA = replaceRegisterIdMultiple(op_r_r_s32_carry.regA, translationTable); op_r_r_s32_carry.regCarry = replaceRegisterIdMultiple(op_r_r_s32_carry.regCarry, translationTable); } else if (type == PPCREC_IML_TYPE_R_R_R) { op_r_r_r.regR = replaceRegisterIdMultiple(op_r_r_r.regR, translationTable); op_r_r_r.regA = replaceRegisterIdMultiple(op_r_r_r.regA, translationTable); op_r_r_r.regB = replaceRegisterIdMultiple(op_r_r_r.regB, translationTable); } else if (type == PPCREC_IML_TYPE_R_R_R_CARRY) { op_r_r_r_carry.regR = replaceRegisterIdMultiple(op_r_r_r_carry.regR, translationTable); op_r_r_r_carry.regA = replaceRegisterIdMultiple(op_r_r_r_carry.regA, translationTable); op_r_r_r_carry.regB = replaceRegisterIdMultiple(op_r_r_r_carry.regB, translationTable); op_r_r_r_carry.regCarry = replaceRegisterIdMultiple(op_r_r_r_carry.regCarry, translationTable); } else if (type == PPCREC_IML_TYPE_COMPARE) { op_compare.regR = replaceRegisterIdMultiple(op_compare.regR, translationTable); op_compare.regA = replaceRegisterIdMultiple(op_compare.regA, translationTable); op_compare.regB = replaceRegisterIdMultiple(op_compare.regB, translationTable); } else if (type == PPCREC_IML_TYPE_COMPARE_S32) { op_compare_s32.regR = replaceRegisterIdMultiple(op_compare_s32.regR, translationTable); op_compare_s32.regA = replaceRegisterIdMultiple(op_compare_s32.regA, translationTable); } else if (type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) { op_conditional_jump.registerBool = replaceRegisterIdMultiple(op_conditional_jump.registerBool, translationTable); } else if (type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK || type == PPCREC_IML_TYPE_JUMP) { // no effect on registers } else if (type == PPCREC_IML_TYPE_NO_OP) { // no effect on registers } else if (type == PPCREC_IML_TYPE_MACRO) { if (operation == PPCREC_IML_MACRO_BL || operation == PPCREC_IML_MACRO_B_FAR || operation == PPCREC_IML_MACRO_LEAVE || operation == PPCREC_IML_MACRO_DEBUGBREAK || operation == PPCREC_IML_MACRO_HLE || operation == PPCREC_IML_MACRO_COUNT_CYCLES) { // no effect on registers } else if (operation == PPCREC_IML_MACRO_B_TO_REG) { op_macro.paramReg = replaceRegisterIdMultiple(op_macro.paramReg, translationTable); } else { cemu_assert_unimplemented(); } } else if (type == PPCREC_IML_TYPE_LOAD) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); if (op_storeLoad.registerMem.IsValid()) { op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); } } else if (type == PPCREC_IML_TYPE_LOAD_INDEXED) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); if (op_storeLoad.registerMem.IsValid()) op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); if (op_storeLoad.registerMem2.IsValid()) op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable); } else if (type == PPCREC_IML_TYPE_STORE) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); if (op_storeLoad.registerMem.IsValid()) op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); } else if (type == PPCREC_IML_TYPE_STORE_INDEXED) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); if (op_storeLoad.registerMem.IsValid()) op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); if (op_storeLoad.registerMem2.IsValid()) op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable); } else if (type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) { op_atomic_compare_store.regEA = replaceRegisterIdMultiple(op_atomic_compare_store.regEA, translationTable); op_atomic_compare_store.regCompareValue = replaceRegisterIdMultiple(op_atomic_compare_store.regCompareValue, translationTable); op_atomic_compare_store.regWriteValue = replaceRegisterIdMultiple(op_atomic_compare_store.regWriteValue, translationTable); op_atomic_compare_store.regBoolOut = replaceRegisterIdMultiple(op_atomic_compare_store.regBoolOut, translationTable); } else if (type == PPCREC_IML_TYPE_CALL_IMM) { op_call_imm.regReturn = replaceRegisterIdMultiple(op_call_imm.regReturn, translationTable); if (op_call_imm.regParam0.IsValid()) op_call_imm.regParam0 = replaceRegisterIdMultiple(op_call_imm.regParam0, translationTable); if (op_call_imm.regParam1.IsValid()) op_call_imm.regParam1 = replaceRegisterIdMultiple(op_call_imm.regParam1, translationTable); if (op_call_imm.regParam2.IsValid()) op_call_imm.regParam2 = replaceRegisterIdMultiple(op_call_imm.regParam2, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_LOAD) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_STORE) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_R) { op_fpr_r.regR = replaceRegisterIdMultiple(op_fpr_r.regR, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_R_R) { op_fpr_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r.regR, translationTable); op_fpr_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r.regA, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_R_R_R) { op_fpr_r_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r_r.regR, translationTable); op_fpr_r_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r_r.regA, translationTable); op_fpr_r_r_r.regB = replaceRegisterIdMultiple(op_fpr_r_r_r.regB, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_R_R_R_R) { op_fpr_r_r_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regR, translationTable); op_fpr_r_r_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regA, translationTable); op_fpr_r_r_r_r.regB = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regB, translationTable); op_fpr_r_r_r_r.regC = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regC, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_COMPARE) { op_fpr_compare.regA = replaceRegisterIdMultiple(op_fpr_compare.regA, translationTable); op_fpr_compare.regB = replaceRegisterIdMultiple(op_fpr_compare.regB, translationTable); op_fpr_compare.regR = replaceRegisterIdMultiple(op_fpr_compare.regR, translationTable); } else if (type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) { // no registers read or written (except for the implicit eflags) } else { cemu_assert_unimplemented(); } } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h ================================================ #pragma once using IMLRegID = uint16; // 16 bit ID using IMLPhysReg = sint32; // arbitrary value that is up to the architecture backend, usually this will be the register index. A value of -1 is reserved and means not assigned // format of IMLReg: // 0-15 (16 bit) IMLRegID // 19-23 (5 bit) Offset In elements, for SIMD registers // 24-27 (4 bit) IMLRegFormat RegFormat // 28-31 (4 bit) IMLRegFormat BaseFormat enum class IMLRegFormat : uint8 { INVALID_FORMAT, I64, I32, I16, I8, // I1 ? F64, F32, TYPE_COUNT, }; class IMLReg { public: IMLReg() { m_raw = 0; // 0 is invalid } IMLReg(IMLRegFormat baseRegFormat, IMLRegFormat regFormat, uint8 viewOffset, IMLRegID regId) { m_raw = 0; m_raw |= ((uint8)baseRegFormat << 28); m_raw |= ((uint8)regFormat << 24); m_raw |= (uint32)regId; } IMLReg(IMLReg&& baseReg, IMLRegFormat viewFormat, uint8 viewOffset, IMLRegID regId) { DEBUG_BREAK; //m_raw = 0; //m_raw |= ((uint8)baseRegFormat << 28); //m_raw |= ((uint8)viewFormat << 24); //m_raw |= (uint32)regId; } IMLReg(const IMLReg& other) : m_raw(other.m_raw) {} IMLRegFormat GetBaseFormat() const { return (IMLRegFormat)((m_raw >> 28) & 0xF); } IMLRegFormat GetRegFormat() const { return (IMLRegFormat)((m_raw >> 24) & 0xF); } IMLRegID GetRegID() const { cemu_assert_debug(GetBaseFormat() != IMLRegFormat::INVALID_FORMAT); cemu_assert_debug(GetRegFormat() != IMLRegFormat::INVALID_FORMAT); return (IMLRegID)(m_raw & 0xFFFF); } void SetRegID(IMLRegID regId) { cemu_assert_debug(regId <= 0xFFFF); m_raw &= ~0xFFFF; m_raw |= (uint32)regId; } bool IsInvalid() const { return GetBaseFormat() == IMLRegFormat::INVALID_FORMAT; } bool IsValid() const { return GetBaseFormat() != IMLRegFormat::INVALID_FORMAT; } bool IsValidAndSameRegID(IMLRegID regId) const { return IsValid() && GetRegID() == regId; } // compare all fields bool operator==(const IMLReg& other) const { return m_raw == other.m_raw; } private: uint32 m_raw; }; static const IMLReg IMLREG_INVALID(IMLRegFormat::INVALID_FORMAT, IMLRegFormat::INVALID_FORMAT, 0, 0); static const IMLRegID IMLRegID_INVALID(0xFFFF); using IMLName = uint32; enum { PPCREC_IML_OP_ASSIGN, // '=' operator PPCREC_IML_OP_ENDIAN_SWAP, // '=' operator with 32bit endian swap PPCREC_IML_OP_MULTIPLY_SIGNED, // '*' operator (signed multiply) PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, // unsigned 64bit multiply, store only high 32bit-word of result PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, // signed 64bit multiply, store only high 32bit-word of result PPCREC_IML_OP_DIVIDE_SIGNED, // '/' operator (signed divide) PPCREC_IML_OP_DIVIDE_UNSIGNED, // '/' operator (unsigned divide) // binary operation PPCREC_IML_OP_OR, // '|' operator PPCREC_IML_OP_AND, // '&' operator PPCREC_IML_OP_XOR, // '^' operator PPCREC_IML_OP_LEFT_ROTATE, // left rotate operator PPCREC_IML_OP_LEFT_SHIFT, // shift left operator PPCREC_IML_OP_RIGHT_SHIFT_U, // right shift operator (unsigned) PPCREC_IML_OP_RIGHT_SHIFT_S, // right shift operator (signed) // ppc PPCREC_IML_OP_SLW, // SLW (shift based on register by up to 63 bits) PPCREC_IML_OP_SRW, // SRW (shift based on register by up to 63 bits) PPCREC_IML_OP_CNTLZW, // FPU PPCREC_IML_OP_FPR_ASSIGN, PPCREC_IML_OP_FPR_LOAD_ONE, // load constant 1.0 into register PPCREC_IML_OP_FPR_ADD, PPCREC_IML_OP_FPR_SUB, PPCREC_IML_OP_FPR_MULTIPLY, PPCREC_IML_OP_FPR_DIVIDE, PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64, // expand f32 to f64 in-place PPCREC_IML_OP_FPR_NEGATE, PPCREC_IML_OP_FPR_ABS, // abs(fpr) PPCREC_IML_OP_FPR_NEGATIVE_ABS, // -abs(fpr) PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, // round 64bit double to 64bit double with 32bit float precision (in bottom half of xmm register) PPCREC_IML_OP_FPR_FCTIWZ, PPCREC_IML_OP_FPR_SELECT, // selectively copy bottom value from operand B or C based on value in operand A // Conversion (FPR_R_R) PPCREC_IML_OP_FPR_INT_TO_FLOAT, // convert integer value in gpr to floating point value in fpr PPCREC_IML_OP_FPR_FLOAT_TO_INT, // convert floating point value in fpr to integer value in gpr // Bitcast (FPR_R_R) PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT, // R_R_R + R_R_S32 PPCREC_IML_OP_ADD, // also R_R_R_CARRY PPCREC_IML_OP_SUB, // R_R only PPCREC_IML_OP_NOT, PPCREC_IML_OP_NEG, PPCREC_IML_OP_ASSIGN_S16_TO_S32, PPCREC_IML_OP_ASSIGN_S8_TO_S32, // R_R_R_carry PPCREC_IML_OP_ADD_WITH_CARRY, // similar to ADD but also adds carry bit (0 or 1) // X86 extension PPCREC_IML_OP_X86_CMP, // R_R and R_S32 PPCREC_IML_OP_INVALID }; #define PPCREC_IML_OP_FPR_COPY_PAIR (PPCREC_IML_OP_ASSIGN) enum { PPCREC_IML_MACRO_B_TO_REG, // branch to PPC address in register (used for BCCTR, BCLR) PPCREC_IML_MACRO_BL, // call to different function (can be within same function) PPCREC_IML_MACRO_B_FAR, // branch to different function PPCREC_IML_MACRO_COUNT_CYCLES, // decrease current remaining thread cycles by a certain amount PPCREC_IML_MACRO_HLE, // HLE function call PPCREC_IML_MACRO_LEAVE, // leaves recompiler and switches to interpeter // debugging PPCREC_IML_MACRO_DEBUGBREAK, // throws a debugbreak }; enum class IMLCondition : uint8 { EQ, NEQ, SIGNED_GT, SIGNED_LT, UNSIGNED_GT, UNSIGNED_LT, // floating point conditions UNORDERED_GT, // a > b, false if either is NaN UNORDERED_LT, // a < b, false if either is NaN UNORDERED_EQ, // a == b, false if either is NaN UNORDERED_U, // unordered (true if either operand is NaN) ORDERED_GT, ORDERED_LT, ORDERED_EQ, ORDERED_U }; enum { PPCREC_IML_TYPE_NONE, PPCREC_IML_TYPE_NO_OP, // no-op instruction PPCREC_IML_TYPE_R_R, // r* = (op) *r (can also be r* (op) *r) PPCREC_IML_TYPE_R_R_R, // r* = r* (op) r* PPCREC_IML_TYPE_R_R_R_CARRY, // r* = r* (op) r* (reads and/or updates carry) PPCREC_IML_TYPE_R_R_S32, // r* = r* (op) s32* PPCREC_IML_TYPE_R_R_S32_CARRY, // r* = r* (op) s32* (reads and/or updates carry) PPCREC_IML_TYPE_LOAD, // r* = [r*+s32*] PPCREC_IML_TYPE_LOAD_INDEXED, // r* = [r*+r*] PPCREC_IML_TYPE_STORE, // [r*+s32*] = r* PPCREC_IML_TYPE_STORE_INDEXED, // [r*+r*] = r* PPCREC_IML_TYPE_R_NAME, // r* = name PPCREC_IML_TYPE_NAME_R, // name* = r* PPCREC_IML_TYPE_R_S32, // r* (op) imm PPCREC_IML_TYPE_MACRO, PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK, // jumps only if remaining thread cycles < 0 // conditions and branches PPCREC_IML_TYPE_COMPARE, // r* = r* CMP[cond] r* PPCREC_IML_TYPE_COMPARE_S32, // r* = r* CMP[cond] imm PPCREC_IML_TYPE_JUMP, // jump always PPCREC_IML_TYPE_CONDITIONAL_JUMP, // jump conditionally based on boolean value in register // atomic PPCREC_IML_TYPE_ATOMIC_CMP_STORE, // function call PPCREC_IML_TYPE_CALL_IMM, // call to fixed immediate address // FPR PPCREC_IML_TYPE_FPR_LOAD, // r* = (bitdepth) [r*+s32*] (single or paired single mode) PPCREC_IML_TYPE_FPR_LOAD_INDEXED, // r* = (bitdepth) [r*+r*] (single or paired single mode) PPCREC_IML_TYPE_FPR_STORE, // (bitdepth) [r*+s32*] = r* (single or paired single mode) PPCREC_IML_TYPE_FPR_STORE_INDEXED, // (bitdepth) [r*+r*] = r* (single or paired single mode) PPCREC_IML_TYPE_FPR_R_R, PPCREC_IML_TYPE_FPR_R_R_R, PPCREC_IML_TYPE_FPR_R_R_R_R, PPCREC_IML_TYPE_FPR_R, PPCREC_IML_TYPE_FPR_COMPARE, // r* = r* CMP[cond] r* // X86 specific PPCREC_IML_TYPE_X86_EFLAGS_JCC, }; enum // IMLName { PPCREC_NAME_NONE, PPCREC_NAME_TEMPORARY = 1000, PPCREC_NAME_R0 = 2000, PPCREC_NAME_SPR0 = 3000, PPCREC_NAME_FPR_HALF = 4800, // Counts PS0 and PS1 separately. E.g. fp3.ps1 is at offset 3 * 2 + 1 PPCREC_NAME_TEMPORARY_FPR0 = 5000, // 0 to 7 PPCREC_NAME_XER_CA = 6000, // carry bit from XER PPCREC_NAME_XER_OV = 6001, // overflow bit from XER PPCREC_NAME_XER_SO = 6002, // summary overflow bit from XER PPCREC_NAME_CR = 7000, // CR register bits (31 to 0) PPCREC_NAME_CR_LAST = PPCREC_NAME_CR+31, PPCREC_NAME_CPU_MEMRES_EA = 8000, PPCREC_NAME_CPU_MEMRES_VAL = 8001 }; #define PPC_REC_INVALID_REGISTER 0xFF // deprecated. Use IMLREG_INVALID instead enum { // fpr load PPCREC_FPR_LD_MODE_SINGLE, PPCREC_FPR_LD_MODE_DOUBLE, // fpr store PPCREC_FPR_ST_MODE_SINGLE, PPCREC_FPR_ST_MODE_DOUBLE, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, // store raw low-32bit of PS0 }; struct IMLUsedRegisters { IMLUsedRegisters() {}; bool IsWrittenByRegId(IMLRegID regId) const { if (writtenGPR1.IsValid() && writtenGPR1.GetRegID() == regId) return true; if (writtenGPR2.IsValid() && writtenGPR2.GetRegID() == regId) return true; return false; } bool IsBaseGPRWritten(IMLReg imlReg) const { cemu_assert_debug(imlReg.IsValid()); auto regId = imlReg.GetRegID(); return IsWrittenByRegId(regId); } template<typename Fn> void ForEachWrittenGPR(Fn F) const { if (writtenGPR1.IsValid()) F(writtenGPR1); if (writtenGPR2.IsValid()) F(writtenGPR2); } template<typename Fn> void ForEachReadGPR(Fn F) const { if (readGPR1.IsValid()) F(readGPR1); if (readGPR2.IsValid()) F(readGPR2); if (readGPR3.IsValid()) F(readGPR3); if (readGPR4.IsValid()) F(readGPR4); } template<typename Fn> void ForEachAccessedGPR(Fn F) const { // GPRs if (readGPR1.IsValid()) F(readGPR1, false); if (readGPR2.IsValid()) F(readGPR2, false); if (readGPR3.IsValid()) F(readGPR3, false); if (readGPR4.IsValid()) F(readGPR4, false); if (writtenGPR1.IsValid()) F(writtenGPR1, true); if (writtenGPR2.IsValid()) F(writtenGPR2, true); } IMLReg readGPR1; IMLReg readGPR2; IMLReg readGPR3; IMLReg readGPR4; IMLReg writtenGPR1; IMLReg writtenGPR2; }; struct IMLInstruction { IMLInstruction() {} IMLInstruction(const IMLInstruction& other) { memcpy(this, &other, sizeof(IMLInstruction)); } uint8 type; uint8 operation; union { struct { uint8 _padding[7]; }padding; struct { IMLReg regR; IMLReg regA; }op_r_r; struct { IMLReg regR; IMLReg regA; IMLReg regB; }op_r_r_r; struct { IMLReg regR; IMLReg regA; IMLReg regB; IMLReg regCarry; }op_r_r_r_carry; struct { IMLReg regR; IMLReg regA; sint32 immS32; }op_r_r_s32; struct { IMLReg regR; IMLReg regA; IMLReg regCarry; sint32 immS32; }op_r_r_s32_carry; struct { IMLReg regR; IMLName name; }op_r_name; // alias op_name_r struct { IMLReg regR; sint32 immS32; }op_r_immS32; struct { uint32 param; uint32 param2; uint16 paramU16; IMLReg paramReg; }op_macro; struct { IMLReg registerData; IMLReg registerMem; IMLReg registerMem2; uint8 copyWidth; struct { bool swapEndian : 1; bool signExtend : 1; bool notExpanded : 1; // for floats }flags2; uint8 mode; // transfer mode sint32 immS32; }op_storeLoad; struct { uintptr_t callAddress; IMLReg regParam0; IMLReg regParam1; IMLReg regParam2; IMLReg regReturn; }op_call_imm; struct { IMLReg regR; IMLReg regA; }op_fpr_r_r; struct { IMLReg regR; IMLReg regA; IMLReg regB; }op_fpr_r_r_r; struct { IMLReg regR; IMLReg regA; IMLReg regB; IMLReg regC; }op_fpr_r_r_r_r; struct { IMLReg regR; }op_fpr_r; struct { IMLReg regR; // stores the boolean result of the comparison IMLReg regA; IMLReg regB; IMLCondition cond; }op_fpr_compare; struct { IMLReg regR; // stores the boolean result of the comparison IMLReg regA; IMLReg regB; IMLCondition cond; }op_compare; struct { IMLReg regR; // stores the boolean result of the comparison IMLReg regA; sint32 immS32; IMLCondition cond; }op_compare_s32; struct { IMLReg registerBool; bool mustBeTrue; }op_conditional_jump; struct { IMLReg regEA; IMLReg regCompareValue; IMLReg regWriteValue; IMLReg regBoolOut; }op_atomic_compare_store; // conditional operations (emitted if supported by target platform) struct { // r_s32 IMLReg regR; sint32 immS32; // condition uint8 crRegisterIndex; uint8 crBitIndex; bool bitMustBeSet; }op_conditional_r_s32; // X86 specific struct { IMLCondition cond; bool invertedCondition; }op_x86_eflags_jcc; }; bool IsSuffixInstruction() const { if (type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_BL || type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_B_FAR || type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_B_TO_REG || type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_LEAVE || type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_HLE || type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK || type == PPCREC_IML_TYPE_JUMP || type == PPCREC_IML_TYPE_CONDITIONAL_JUMP || type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) return true; return false; } // instruction setters void make_no_op() { type = PPCREC_IML_TYPE_NO_OP; operation = 0; } void make_r_name(IMLReg regR, IMLName name) { cemu_assert_debug(regR.GetBaseFormat() == regR.GetRegFormat()); // for name load/store instructions the register must match the base format type = PPCREC_IML_TYPE_R_NAME; operation = PPCREC_IML_OP_ASSIGN; op_r_name.regR = regR; op_r_name.name = name; } void make_name_r(IMLName name, IMLReg regR) { cemu_assert_debug(regR.GetBaseFormat() == regR.GetRegFormat()); // for name load/store instructions the register must match the base format type = PPCREC_IML_TYPE_NAME_R; operation = PPCREC_IML_OP_ASSIGN; op_r_name.regR = regR; op_r_name.name = name; } void make_debugbreak(uint32 currentPPCAddress = 0) { make_macro(PPCREC_IML_MACRO_DEBUGBREAK, 0, currentPPCAddress, 0, IMLREG_INVALID); } void make_macro(uint32 macroId, uint32 param, uint32 param2, uint16 paramU16, IMLReg regParam) { this->type = PPCREC_IML_TYPE_MACRO; this->operation = macroId; this->op_macro.param = param; this->op_macro.param2 = param2; this->op_macro.paramU16 = paramU16; this->op_macro.paramReg = regParam; } void make_cjump_cycle_check() { this->type = PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK; this->operation = 0; } void make_r_r(uint32 operation, IMLReg regR, IMLReg regA) { this->type = PPCREC_IML_TYPE_R_R; this->operation = operation; this->op_r_r.regR = regR; this->op_r_r.regA = regA; } void make_r_s32(uint32 operation, IMLReg regR, sint32 immS32) { this->type = PPCREC_IML_TYPE_R_S32; this->operation = operation; this->op_r_immS32.regR = regR; this->op_r_immS32.immS32 = immS32; } void make_r_r_r(uint32 operation, IMLReg regR, IMLReg regA, IMLReg regB) { this->type = PPCREC_IML_TYPE_R_R_R; this->operation = operation; this->op_r_r_r.regR = regR; this->op_r_r_r.regA = regA; this->op_r_r_r.regB = regB; } void make_r_r_r_carry(uint32 operation, IMLReg regR, IMLReg regA, IMLReg regB, IMLReg regCarry) { this->type = PPCREC_IML_TYPE_R_R_R_CARRY; this->operation = operation; this->op_r_r_r_carry.regR = regR; this->op_r_r_r_carry.regA = regA; this->op_r_r_r_carry.regB = regB; this->op_r_r_r_carry.regCarry = regCarry; } void make_r_r_s32(uint32 operation, IMLReg regR, IMLReg regA, sint32 immS32) { this->type = PPCREC_IML_TYPE_R_R_S32; this->operation = operation; this->op_r_r_s32.regR = regR; this->op_r_r_s32.regA = regA; this->op_r_r_s32.immS32 = immS32; } void make_r_r_s32_carry(uint32 operation, IMLReg regR, IMLReg regA, sint32 immS32, IMLReg regCarry) { this->type = PPCREC_IML_TYPE_R_R_S32_CARRY; this->operation = operation; this->op_r_r_s32_carry.regR = regR; this->op_r_r_s32_carry.regA = regA; this->op_r_r_s32_carry.immS32 = immS32; this->op_r_r_s32_carry.regCarry = regCarry; } void make_compare(IMLReg regA, IMLReg regB, IMLReg regR, IMLCondition cond) { this->type = PPCREC_IML_TYPE_COMPARE; this->operation = PPCREC_IML_OP_INVALID; this->op_compare.regR = regR; this->op_compare.regA = regA; this->op_compare.regB = regB; this->op_compare.cond = cond; } void make_compare_s32(IMLReg regA, sint32 immS32, IMLReg regR, IMLCondition cond) { this->type = PPCREC_IML_TYPE_COMPARE_S32; this->operation = PPCREC_IML_OP_INVALID; this->op_compare_s32.regR = regR; this->op_compare_s32.regA = regA; this->op_compare_s32.immS32 = immS32; this->op_compare_s32.cond = cond; } void make_conditional_jump(IMLReg regBool, bool mustBeTrue) { this->type = PPCREC_IML_TYPE_CONDITIONAL_JUMP; this->operation = PPCREC_IML_OP_INVALID; this->op_conditional_jump.registerBool = regBool; this->op_conditional_jump.mustBeTrue = mustBeTrue; } void make_jump() { this->type = PPCREC_IML_TYPE_JUMP; this->operation = PPCREC_IML_OP_INVALID; } // load from memory void make_r_memory(IMLReg regD, IMLReg regMem, sint32 immS32, uint32 copyWidth, bool signExtend, bool switchEndian) { this->type = PPCREC_IML_TYPE_LOAD; this->operation = 0; this->op_storeLoad.registerData = regD; this->op_storeLoad.registerMem = regMem; this->op_storeLoad.immS32 = immS32; this->op_storeLoad.copyWidth = copyWidth; this->op_storeLoad.flags2.swapEndian = switchEndian; this->op_storeLoad.flags2.signExtend = signExtend; } // store to memory void make_memory_r(IMLReg regS, IMLReg regMem, sint32 immS32, uint32 copyWidth, bool switchEndian) { this->type = PPCREC_IML_TYPE_STORE; this->operation = 0; this->op_storeLoad.registerData = regS; this->op_storeLoad.registerMem = regMem; this->op_storeLoad.immS32 = immS32; this->op_storeLoad.copyWidth = copyWidth; this->op_storeLoad.flags2.swapEndian = switchEndian; this->op_storeLoad.flags2.signExtend = false; } void make_atomic_cmp_store(IMLReg regEA, IMLReg regCompareValue, IMLReg regWriteValue, IMLReg regSuccessOutput) { this->type = PPCREC_IML_TYPE_ATOMIC_CMP_STORE; this->operation = 0; this->op_atomic_compare_store.regEA = regEA; this->op_atomic_compare_store.regCompareValue = regCompareValue; this->op_atomic_compare_store.regWriteValue = regWriteValue; this->op_atomic_compare_store.regBoolOut = regSuccessOutput; } void make_call_imm(uintptr_t callAddress, IMLReg param0, IMLReg param1, IMLReg param2, IMLReg regReturn) { this->type = PPCREC_IML_TYPE_CALL_IMM; this->operation = 0; this->op_call_imm.callAddress = callAddress; this->op_call_imm.regParam0 = param0; this->op_call_imm.regParam1 = param1; this->op_call_imm.regParam2 = param2; this->op_call_imm.regReturn = regReturn; } // FPR // load from memory void make_fpr_r_memory(IMLReg registerDestination, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian) { this->type = PPCREC_IML_TYPE_FPR_LOAD; this->operation = 0; this->op_storeLoad.registerData = registerDestination; this->op_storeLoad.registerMem = registerMemory; this->op_storeLoad.immS32 = immS32; this->op_storeLoad.mode = mode; this->op_storeLoad.flags2.swapEndian = switchEndian; } void make_fpr_r_memory_indexed(IMLReg registerDestination, IMLReg registerMemory1, IMLReg registerMemory2, uint32 mode, bool switchEndian) { this->type = PPCREC_IML_TYPE_FPR_LOAD_INDEXED; this->operation = 0; this->op_storeLoad.registerData = registerDestination; this->op_storeLoad.registerMem = registerMemory1; this->op_storeLoad.registerMem2 = registerMemory2; this->op_storeLoad.immS32 = 0; this->op_storeLoad.mode = mode; this->op_storeLoad.flags2.swapEndian = switchEndian; } // store to memory void make_fpr_memory_r(IMLReg registerSource, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian) { this->type = PPCREC_IML_TYPE_FPR_STORE; this->operation = 0; this->op_storeLoad.registerData = registerSource; this->op_storeLoad.registerMem = registerMemory; this->op_storeLoad.immS32 = immS32; this->op_storeLoad.mode = mode; this->op_storeLoad.flags2.swapEndian = switchEndian; } void make_fpr_memory_r_indexed(IMLReg registerSource, IMLReg registerMemory1, IMLReg registerMemory2, sint32 immS32, uint32 mode, bool switchEndian) { this->type = PPCREC_IML_TYPE_FPR_STORE_INDEXED; this->operation = 0; this->op_storeLoad.registerData = registerSource; this->op_storeLoad.registerMem = registerMemory1; this->op_storeLoad.registerMem2 = registerMemory2; this->op_storeLoad.immS32 = immS32; this->op_storeLoad.mode = mode; this->op_storeLoad.flags2.swapEndian = switchEndian; } void make_fpr_compare(IMLReg regA, IMLReg regB, IMLReg regR, IMLCondition cond) { this->type = PPCREC_IML_TYPE_FPR_COMPARE; this->operation = -999; this->op_fpr_compare.regR = regR; this->op_fpr_compare.regA = regA; this->op_fpr_compare.regB = regB; this->op_fpr_compare.cond = cond; } void make_fpr_r(sint32 operation, IMLReg registerResult) { // OP (fpr) this->type = PPCREC_IML_TYPE_FPR_R; this->operation = operation; this->op_fpr_r.regR = registerResult; } void make_fpr_r_r(sint32 operation, IMLReg registerResult, IMLReg registerOperand, sint32 crRegister=PPC_REC_INVALID_REGISTER) { // fpr OP fpr this->type = PPCREC_IML_TYPE_FPR_R_R; this->operation = operation; this->op_fpr_r_r.regR = registerResult; this->op_fpr_r_r.regA = registerOperand; } void make_fpr_r_r_r(sint32 operation, IMLReg registerResult, IMLReg registerOperand1, IMLReg registerOperand2, sint32 crRegister=PPC_REC_INVALID_REGISTER) { // fpr = OP (fpr,fpr) this->type = PPCREC_IML_TYPE_FPR_R_R_R; this->operation = operation; this->op_fpr_r_r_r.regR = registerResult; this->op_fpr_r_r_r.regA = registerOperand1; this->op_fpr_r_r_r.regB = registerOperand2; } void make_fpr_r_r_r_r(sint32 operation, IMLReg registerResult, IMLReg registerOperandA, IMLReg registerOperandB, IMLReg registerOperandC, sint32 crRegister=PPC_REC_INVALID_REGISTER) { // fpr = OP (fpr,fpr,fpr) this->type = PPCREC_IML_TYPE_FPR_R_R_R_R; this->operation = operation; this->op_fpr_r_r_r_r.regR = registerResult; this->op_fpr_r_r_r_r.regA = registerOperandA; this->op_fpr_r_r_r_r.regB = registerOperandB; this->op_fpr_r_r_r_r.regC = registerOperandC; } /* X86 specific */ void make_x86_eflags_jcc(IMLCondition cond, bool invertedCondition) { this->type = PPCREC_IML_TYPE_X86_EFLAGS_JCC; this->operation = -999; this->op_x86_eflags_jcc.cond = cond; this->op_x86_eflags_jcc.invertedCondition = invertedCondition; } void CheckRegisterUsage(IMLUsedRegisters* registersUsed) const; bool HasSideEffects() const; // returns true if the instruction has side effects beyond just reading and writing registers. Dead code elimination uses this to know if an instruction can be dropped when the regular register outputs are not used void RewriteGPR(const std::unordered_map<IMLRegID, IMLRegID>& translationTable); }; // architecture specific constants namespace IMLArchX86 { static constexpr int PHYSREG_GPR_BASE = 0; static constexpr int PHYSREG_FPR_BASE = 16; }; ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp ================================================ #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Recompiler/IML/IML.h" #include "Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h" #include "../PPCRecompiler.h" #include "../PPCRecompilerIml.h" #include "../BackendX64/BackendX64.h" #include "Common/FileStream.h" #include <boost/container/static_vector.hpp> #include <boost/container/small_vector.hpp> IMLReg _FPRRegFromID(IMLRegID regId) { return IMLReg(IMLRegFormat::F64, IMLRegFormat::F64, 0, regId); } void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, sint32 imlIndexLoad, IMLReg fprReg) { IMLRegID fprIndex = fprReg.GetRegID(); IMLInstruction* imlInstructionLoad = imlSegment->imlList.data() + imlIndexLoad; if (imlInstructionLoad->op_storeLoad.flags2.notExpanded) return; boost::container::static_vector<sint32, 4> trackedMoves; // only track up to 4 copies IMLUsedRegisters registersUsed; sint32 scanRangeEnd = std::min<sint32>(imlIndexLoad + 25, imlSegment->imlList.size()); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances) bool foundMatch = false; sint32 lastStore = -1; for (sint32 i = imlIndexLoad + 1; i < scanRangeEnd; i++) { IMLInstruction* imlInstruction = imlSegment->imlList.data() + i; if (imlInstruction->IsSuffixInstruction()) break; // check if FPR is stored if ((imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE) || (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE)) { if (imlInstruction->op_storeLoad.registerData.GetRegID() == fprIndex) { if (foundMatch == false) { // flag the load-single instruction as "don't expand" (leave single value as-is) imlInstructionLoad->op_storeLoad.flags2.notExpanded = true; } // also set the flag for the store instruction IMLInstruction* imlInstructionStore = imlInstruction; imlInstructionStore->op_storeLoad.flags2.notExpanded = true; foundMatch = true; lastStore = i + 1; continue; } } // if the FPR is copied then keep track of it. We can expand the copies instead of the original if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R && imlInstruction->operation == PPCREC_IML_OP_FPR_ASSIGN && imlInstruction->op_fpr_r_r.regA.GetRegID() == fprIndex) { if (imlInstruction->op_fpr_r_r.regR.GetRegID() == fprIndex) { // unexpected no-op break; } if (trackedMoves.size() >= trackedMoves.capacity()) { // we cant track any more moves, expand here lastStore = i; break; } trackedMoves.push_back(i); continue; } // check if FPR is overwritten imlInstruction->CheckRegisterUsage(®istersUsed); if (registersUsed.writtenGPR1.IsValidAndSameRegID(fprIndex) || registersUsed.writtenGPR2.IsValidAndSameRegID(fprIndex)) break; if (registersUsed.readGPR1.IsValidAndSameRegID(fprIndex)) break; if (registersUsed.readGPR2.IsValidAndSameRegID(fprIndex)) break; if (registersUsed.readGPR3.IsValidAndSameRegID(fprIndex)) break; if (registersUsed.readGPR4.IsValidAndSameRegID(fprIndex)) break; } if (foundMatch) { // insert expand instructions for each target register of a move sint32 positionBias = 0; for (auto& trackedMove : trackedMoves) { sint32 realPosition = trackedMove + positionBias; IMLInstruction* imlMoveInstruction = imlSegment->imlList.data() + realPosition; if (realPosition >= lastStore) break; // expand is inserted before this move else lastStore++; cemu_assert_debug(imlMoveInstruction->type == PPCREC_IML_TYPE_FPR_R_R && imlMoveInstruction->op_fpr_r_r.regA.GetRegID() == fprIndex); cemu_assert_debug(imlMoveInstruction->op_fpr_r_r.regA.GetRegFormat() == IMLRegFormat::F64); auto dstReg = imlMoveInstruction->op_fpr_r_r.regR; IMLInstruction* newExpand = PPCRecompiler_insertInstruction(imlSegment, realPosition+1); // one after the move newExpand->make_fpr_r(PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64, dstReg); positionBias++; } // insert expand instruction after store IMLInstruction* newExpand = PPCRecompiler_insertInstruction(imlSegment, lastStore); newExpand->make_fpr_r(PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64, _FPRRegFromID(fprIndex)); } } /* * Scans for patterns: * <Load sp float into register f> * <Random unrelated instructions> * <Store sp float from register f> * For these patterns the store and load is modified to work with un-extended values (float remains as float, no double conversion) * The float->double extension is then executed later * Advantages: * Keeps denormals and other special float values intact * Slightly improves performance */ void IMLOptimizer_OptimizeDirectFloatCopies(ppcImlGenContext_t* ppcImlGenContext) { for (IMLSegment* segIt : ppcImlGenContext->segmentList2) { for (sint32 i = 0; i < segIt->imlList.size(); i++) { IMLInstruction* imlInstruction = segIt->imlList.data() + i; if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE) { PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); } else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE) { PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); } } } } void PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, sint32 imlIndexLoad, IMLReg gprReg) { cemu_assert_debug(gprReg.GetBaseFormat() == IMLRegFormat::I64); // todo - proper handling required for non-standard sizes cemu_assert_debug(gprReg.GetRegFormat() == IMLRegFormat::I32); IMLRegID gprIndex = gprReg.GetRegID(); IMLInstruction* imlInstructionLoad = imlSegment->imlList.data() + imlIndexLoad; if ( imlInstructionLoad->op_storeLoad.flags2.swapEndian == false ) return; bool foundMatch = false; IMLUsedRegisters registersUsed; sint32 scanRangeEnd = std::min<sint32>(imlIndexLoad + 25, imlSegment->imlList.size()); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances) sint32 i = imlIndexLoad + 1; for (; i < scanRangeEnd; i++) { IMLInstruction* imlInstruction = imlSegment->imlList.data() + i; if (imlInstruction->IsSuffixInstruction()) break; // check if GPR is stored if ((imlInstruction->type == PPCREC_IML_TYPE_STORE && imlInstruction->op_storeLoad.copyWidth == 32 ) ) { if (imlInstruction->op_storeLoad.registerMem.GetRegID() == gprIndex) break; if (imlInstruction->op_storeLoad.registerData.GetRegID() == gprIndex) { IMLInstruction* imlInstructionStore = imlInstruction; if (foundMatch == false) { // switch the endian swap flag for the load instruction imlInstructionLoad->op_storeLoad.flags2.swapEndian = !imlInstructionLoad->op_storeLoad.flags2.swapEndian; foundMatch = true; } // switch the endian swap flag for the store instruction imlInstructionStore->op_storeLoad.flags2.swapEndian = !imlInstructionStore->op_storeLoad.flags2.swapEndian; // keep scanning continue; } } // check if GPR is accessed imlInstruction->CheckRegisterUsage(®istersUsed); if (registersUsed.readGPR1.IsValidAndSameRegID(gprIndex) || registersUsed.readGPR2.IsValidAndSameRegID(gprIndex) || registersUsed.readGPR3.IsValidAndSameRegID(gprIndex)) { break; } if (registersUsed.IsBaseGPRWritten(gprReg)) return; // GPR overwritten, we don't need to byte swap anymore } if (foundMatch) { PPCRecompiler_insertInstruction(imlSegment, i)->make_r_r(PPCREC_IML_OP_ENDIAN_SWAP, gprReg, gprReg); } } /* * Scans for patterns: * <Load sp integer into register r> * <Random unrelated instructions> * <Store sp integer from register r> * For these patterns the store and load is modified to work with non-swapped values * The big_endian->little_endian conversion is then executed later * Advantages: * Slightly improves performance */ void IMLOptimizer_OptimizeDirectIntegerCopies(ppcImlGenContext_t* ppcImlGenContext) { for (IMLSegment* segIt : ppcImlGenContext->segmentList2) { for (sint32 i = 0; i < segIt->imlList.size(); i++) { IMLInstruction* imlInstruction = segIt->imlList.data() + i; if (imlInstruction->type == PPCREC_IML_TYPE_LOAD && imlInstruction->op_storeLoad.copyWidth == 32 && imlInstruction->op_storeLoad.flags2.swapEndian ) { PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); } } } } IMLName PPCRecompilerImlGen_GetRegName(ppcImlGenContext_t* ppcImlGenContext, IMLReg reg); sint32 _getGQRIndexFromRegister(ppcImlGenContext_t* ppcImlGenContext, IMLReg gqrReg) { if (gqrReg.IsInvalid()) return -1; sint32 namedReg = PPCRecompilerImlGen_GetRegName(ppcImlGenContext, gqrReg); if (namedReg >= (PPCREC_NAME_SPR0 + SPR_UGQR0) && namedReg <= (PPCREC_NAME_SPR0 + SPR_UGQR7)) { return namedReg - (PPCREC_NAME_SPR0 + SPR_UGQR0); } else { cemu_assert_suspicious(); } return -1; } bool PPCRecompiler_isUGQRValueKnown(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, uint32& gqrValue) { // the default configuration is: // UGQR0 = 0x00000000 // UGQR2 = 0x00040004 // UGQR3 = 0x00050005 // UGQR4 = 0x00060006 // UGQR5 = 0x00070007 // but games are free to modify UGQR2 to UGQR7 it seems. // no game modifies UGQR0 so it's safe enough to optimize for the default value // Ideally we would do some kind of runtime tracking and second recompilation to create fast paths for PSQ_L/PSQ_ST but thats todo if (gqrIndex == 0) gqrValue = 0x00000000; else return false; return true; } // analyses register dependencies across the entire function // per segment this will generate information about which registers need to be preserved and which ones don't (e.g. are overwritten) class IMLOptimizerRegIOAnalysis { public: // constructor with segment pointer list as span IMLOptimizerRegIOAnalysis(std::span<IMLSegment*> segmentList, uint32 maxRegId) : m_segmentList(segmentList), m_maxRegId(maxRegId) { m_segRegisterInOutList.resize(segmentList.size()); } struct IMLSegmentRegisterInOut { // todo - since our register ID range is usually pretty small (<64) we could use integer bitmasks to accelerate this? There is a helper class used in RA code already std::unordered_set<IMLRegID> regWritten; // registers which are modified in this segment std::unordered_set<IMLRegID> regImported; // registers which are read in this segment before they are written (importing value from previous segments) std::unordered_set<IMLRegID> regForward; // registers which are not read or written in this segment, but are imported into a later segment (propagated info) }; // calculate which registers are imported (read-before-written) and forwarded (read-before-written by a later segment) per segment // then in a second step propagate the dependencies across linked segments void ComputeDepedencies() { std::vector<IMLSegmentRegisterInOut>& segRegisterInOutList = m_segRegisterInOutList; IMLSegmentRegisterInOut* segIO = segRegisterInOutList.data(); uint32 index = 0; for(auto& seg : m_segmentList) { seg->momentaryIndex = index; index++; for(auto& instr : seg->imlList) { IMLUsedRegisters registerUsage; instr.CheckRegisterUsage(®isterUsage); // registers are considered imported if they are read before being written in this seg registerUsage.ForEachReadGPR([&](IMLReg gprReg) { IMLRegID gprId = gprReg.GetRegID(); if (!segIO->regWritten.contains(gprId)) { segIO->regImported.insert(gprId); } }); registerUsage.ForEachWrittenGPR([&](IMLReg gprReg) { IMLRegID gprId = gprReg.GetRegID(); segIO->regWritten.insert(gprId); }); } segIO++; } // for every exit segment, import all registers for(auto& seg : m_segmentList) { if (!seg->nextSegmentIsUncertain) continue; if(seg->deadCodeEliminationHintSeg) continue; IMLSegmentRegisterInOut& segIO = segRegisterInOutList[seg->momentaryIndex]; for(uint32 i=0; i<=m_maxRegId; i++) { segIO.regImported.insert((IMLRegID)i); } } // broadcast dependencies across segment chains std::unordered_set<uint32> segIdsWhichNeedUpdate; for (uint32 i = 0; i < m_segmentList.size(); i++) { segIdsWhichNeedUpdate.insert(i); } while(!segIdsWhichNeedUpdate.empty()) { auto firstIt = segIdsWhichNeedUpdate.begin(); uint32 segId = *firstIt; segIdsWhichNeedUpdate.erase(firstIt); // forward regImported and regForward to earlier segments into their regForward, unless the register is written auto& curSeg = m_segmentList[segId]; IMLSegmentRegisterInOut& curSegIO = segRegisterInOutList[segId]; for(auto& prevSeg : curSeg->list_prevSegments) { IMLSegmentRegisterInOut& prevSegIO = segRegisterInOutList[prevSeg->momentaryIndex]; bool prevSegChanged = false; for(auto& regId : curSegIO.regImported) { if (!prevSegIO.regWritten.contains(regId)) prevSegChanged |= prevSegIO.regForward.insert(regId).second; } for(auto& regId : curSegIO.regForward) { if (!prevSegIO.regWritten.contains(regId)) prevSegChanged |= prevSegIO.regForward.insert(regId).second; } if(prevSegChanged) segIdsWhichNeedUpdate.insert(prevSeg->momentaryIndex); } // same for hint links for(auto& prevSeg : curSeg->list_deadCodeHintBy) { IMLSegmentRegisterInOut& prevSegIO = segRegisterInOutList[prevSeg->momentaryIndex]; bool prevSegChanged = false; for(auto& regId : curSegIO.regImported) { if (!prevSegIO.regWritten.contains(regId)) prevSegChanged |= prevSegIO.regForward.insert(regId).second; } for(auto& regId : curSegIO.regForward) { if (!prevSegIO.regWritten.contains(regId)) prevSegChanged |= prevSegIO.regForward.insert(regId).second; } if(prevSegChanged) segIdsWhichNeedUpdate.insert(prevSeg->momentaryIndex); } } } std::unordered_set<IMLRegID> GetRegistersNeededAtEndOfSegment(IMLSegment& seg) { std::unordered_set<IMLRegID> regsNeeded; if(seg.nextSegmentIsUncertain) { if(seg.deadCodeEliminationHintSeg) { auto& nextSegIO = m_segRegisterInOutList[seg.deadCodeEliminationHintSeg->momentaryIndex]; regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end()); regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end()); } else { // add all regs for(uint32 i = 0; i <= m_maxRegId; i++) regsNeeded.insert(i); } return regsNeeded; } if(seg.nextSegmentBranchTaken) { auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchTaken->momentaryIndex]; regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end()); regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end()); } if(seg.nextSegmentBranchNotTaken) { auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchNotTaken->momentaryIndex]; regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end()); regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end()); } return regsNeeded; } bool IsRegisterNeededAtEndOfSegment(IMLSegment& seg, IMLRegID regId) { if(seg.nextSegmentIsUncertain) { if(!seg.deadCodeEliminationHintSeg) return true; auto& nextSegIO = m_segRegisterInOutList[seg.deadCodeEliminationHintSeg->momentaryIndex]; if(nextSegIO.regImported.contains(regId)) return true; if(nextSegIO.regForward.contains(regId)) return true; return false; } if(seg.nextSegmentBranchTaken) { auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchTaken->momentaryIndex]; if(nextSegIO.regImported.contains(regId)) return true; if(nextSegIO.regForward.contains(regId)) return true; } if(seg.nextSegmentBranchNotTaken) { auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchNotTaken->momentaryIndex]; if(nextSegIO.regImported.contains(regId)) return true; if(nextSegIO.regForward.contains(regId)) return true; } return false; } private: std::span<IMLSegment*> m_segmentList; uint32 m_maxRegId; std::vector<IMLSegmentRegisterInOut> m_segRegisterInOutList; }; // scan backwards starting from index and return the index of the first found instruction which writes to the given register (by id) sint32 IMLUtil_FindInstructionWhichWritesRegister(IMLSegment& seg, sint32 startIndex, IMLReg reg, sint32 maxScanDistance = -1) { sint32 endIndex = std::max<sint32>(startIndex - maxScanDistance, 0); for (sint32 i = startIndex; i >= endIndex; i--) { IMLInstruction& imlInstruction = seg.imlList[i]; IMLUsedRegisters registersUsed; imlInstruction.CheckRegisterUsage(®istersUsed); if (registersUsed.IsBaseGPRWritten(reg)) return i; } return -1; } // returns true if the instruction can safely be moved while keeping ordering constraints and data dependencies intact // initialIndex is inclusive, targetIndex is exclusive bool IMLUtil_CanMoveInstructionTo(IMLSegment& seg, sint32 initialIndex, sint32 targetIndex) { boost::container::static_vector<IMLRegID, 8> regsWritten; boost::container::static_vector<IMLRegID, 8> regsRead; // get list of read and written registers IMLUsedRegisters registersUsed; seg.imlList[initialIndex].CheckRegisterUsage(®istersUsed); registersUsed.ForEachAccessedGPR([&](IMLReg reg, bool isWritten) { if (isWritten) regsWritten.push_back(reg.GetRegID()); else regsRead.push_back(reg.GetRegID()); }); // check all the instructions inbetween if(initialIndex < targetIndex) { sint32 scanStartIndex = initialIndex+1; // +1 to skip the moving instruction itself sint32 scanEndIndex = targetIndex; for (sint32 i = scanStartIndex; i < scanEndIndex; i++) { IMLUsedRegisters registersUsed; seg.imlList[i].CheckRegisterUsage(®istersUsed); // in order to be able to move an instruction past another instruction, any of the read registers must not be modified (written) // and any of it's written registers must not be read bool canMove = true; registersUsed.ForEachAccessedGPR([&](IMLReg reg, bool isWritten) { IMLRegID regId = reg.GetRegID(); if (!isWritten) canMove = canMove && std::find(regsWritten.begin(), regsWritten.end(), regId) == regsWritten.end(); else canMove = canMove && std::find(regsRead.begin(), regsRead.end(), regId) == regsRead.end(); }); if(!canMove) return false; } } else { cemu_assert_unimplemented(); // backwards scan is todo return false; } return true; } sint32 IMLUtil_CountRegisterReadsInRange(IMLSegment& seg, sint32 scanStartIndex, sint32 scanEndIndex, IMLRegID regId) { cemu_assert_debug(scanStartIndex <= scanEndIndex); cemu_assert_debug(scanEndIndex < seg.imlList.size()); sint32 count = 0; for (sint32 i = scanStartIndex; i <= scanEndIndex; i++) { IMLUsedRegisters registersUsed; seg.imlList[i].CheckRegisterUsage(®istersUsed); registersUsed.ForEachReadGPR([&](IMLReg reg) { if (reg.GetRegID() == regId) count++; }); } return count; } // move instruction from one index to another // instruction will be inserted before the instruction at targetIndex // returns the new instruction index of the moved instruction sint32 IMLUtil_MoveInstructionTo(IMLSegment& seg, sint32 initialIndex, sint32 targetIndex) { cemu_assert_debug(initialIndex != targetIndex); IMLInstruction temp = seg.imlList[initialIndex]; if (initialIndex < targetIndex) { cemu_assert_debug(targetIndex > 0); targetIndex--; for(size_t i=initialIndex; i<targetIndex; i++) seg.imlList[i] = seg.imlList[i+1]; seg.imlList[targetIndex] = temp; return targetIndex; } else { cemu_assert_unimplemented(); // testing needed std::copy(seg.imlList.begin() + targetIndex, seg.imlList.begin() + initialIndex, seg.imlList.begin() + targetIndex + 1); seg.imlList[targetIndex] = temp; return targetIndex; } } // x86 specific bool IMLOptimizerX86_ModifiesEFlags(IMLInstruction& inst) { // this is a very conservative implementation. There are more cases but this is good enough for now if(inst.type == PPCREC_IML_TYPE_NAME_R || inst.type == PPCREC_IML_TYPE_R_NAME) return false; if((inst.type == PPCREC_IML_TYPE_R_R || inst.type == PPCREC_IML_TYPE_R_S32) && inst.operation == PPCREC_IML_OP_ASSIGN) return false; return true; // if we dont know for sure, assume it does } void IMLOptimizer_DebugPrintSeg(ppcImlGenContext_t& ppcImlGenContext, IMLSegment& seg) { printf("----------------\n"); IMLDebug_DumpSegment(&ppcImlGenContext, &seg); fflush(stdout); } void IMLOptimizer_RemoveDeadCodeFromSegment(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg) { // algorithm works like this: // Calculate which registers need to be preserved at the end of each segment // Then for each segment: // - Iterate instructions backwards // - Maintain a list of registers which are read at a later point (initially this is the list from the first step) // - If an instruction only modifies registers which are not in the read list and has no side effects, then it is dead code and can be replaced with a no-op std::unordered_set<IMLRegID> regsNeeded = regIoAnalysis.GetRegistersNeededAtEndOfSegment(seg); // start with suffix instruction if(seg.HasSuffixInstruction()) { IMLInstruction& imlInstruction = seg.imlList[seg.GetSuffixInstructionIndex()]; IMLUsedRegisters registersUsed; imlInstruction.CheckRegisterUsage(®istersUsed); registersUsed.ForEachWrittenGPR([&](IMLReg reg) { regsNeeded.erase(reg.GetRegID()); }); registersUsed.ForEachReadGPR([&](IMLReg reg) { regsNeeded.insert(reg.GetRegID()); }); } // iterate instructions backwards for (sint32 i = seg.imlList.size() - (seg.HasSuffixInstruction() ? 2:1); i >= 0; i--) { IMLInstruction& imlInstruction = seg.imlList[i]; IMLUsedRegisters registersUsed; imlInstruction.CheckRegisterUsage(®istersUsed); // register read -> remove from overwritten list // register written -> add to overwritten list // check if this instruction only writes registers which will never be read bool onlyWritesRedundantRegisters = true; registersUsed.ForEachWrittenGPR([&](IMLReg reg) { if (regsNeeded.contains(reg.GetRegID())) onlyWritesRedundantRegisters = false; }); // check if any of the written registers are read after this point registersUsed.ForEachWrittenGPR([&](IMLReg reg) { regsNeeded.erase(reg.GetRegID()); }); registersUsed.ForEachReadGPR([&](IMLReg reg) { regsNeeded.insert(reg.GetRegID()); }); if(!imlInstruction.HasSideEffects() && onlyWritesRedundantRegisters) { imlInstruction.make_no_op(); } } } void IMLOptimizerX86_SubstituteCJumpForEflagsJump(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg) { // convert and optimize bool condition jumps to eflags condition jumps // - Moves eflag setter (e.g. cmp) closer to eflags consumer (conditional jump) if necessary. If not possible but required then exit early // - Since we only rely on eflags, the boolean register can be optimized out if DCE considers it unused // - Further detect and optimize patterns like DEC + CMP + JCC into fused ops (todo) // check if this segment ends with a conditional jump if(!seg.HasSuffixInstruction()) return; sint32 cjmpInstIndex = seg.GetSuffixInstructionIndex(); if(cjmpInstIndex < 0) return; IMLInstruction& cjumpInstr = seg.imlList[cjmpInstIndex]; if( cjumpInstr.type != PPCREC_IML_TYPE_CONDITIONAL_JUMP ) return; IMLReg regCondBool = cjumpInstr.op_conditional_jump.registerBool; bool invertedCondition = !cjumpInstr.op_conditional_jump.mustBeTrue; // find the instruction which sets the bool sint32 cmpInstrIndex = IMLUtil_FindInstructionWhichWritesRegister(seg, cjmpInstIndex-1, regCondBool, 20); if(cmpInstrIndex < 0) return; // check if its an instruction combo which can be optimized (currently only cmp + cjump) and get the condition IMLInstruction& condSetterInstr = seg.imlList[cmpInstrIndex]; IMLCondition cond; if(condSetterInstr.type == PPCREC_IML_TYPE_COMPARE) cond = condSetterInstr.op_compare.cond; else if(condSetterInstr.type == PPCREC_IML_TYPE_COMPARE_S32) cond = condSetterInstr.op_compare_s32.cond; else return; // check if instructions inbetween modify eflags sint32 indexEflagsSafeStart = -1; // index of the first instruction which does not modify eflags up to cjump for(sint32 i = cjmpInstIndex-1; i > cmpInstrIndex; i--) { if(IMLOptimizerX86_ModifiesEFlags(seg.imlList[i])) { indexEflagsSafeStart = i+1; break; } } if(indexEflagsSafeStart >= 0) { cemu_assert(indexEflagsSafeStart > 0); // there are eflags-modifying instructions inbetween the bool setter and cjump // try to move the eflags setter close enough to the cjump (to indexEflagsSafeStart) bool canMove = IMLUtil_CanMoveInstructionTo(seg, cmpInstrIndex, indexEflagsSafeStart); if(!canMove) { return; } else { cmpInstrIndex = IMLUtil_MoveInstructionTo(seg, cmpInstrIndex, indexEflagsSafeStart); } } // we can turn the jump into an eflags jump cjumpInstr.make_x86_eflags_jcc(cond, invertedCondition); if (IMLUtil_CountRegisterReadsInRange(seg, cmpInstrIndex, cjmpInstIndex, regCondBool.GetRegID()) > 1 || regIoAnalysis.IsRegisterNeededAtEndOfSegment(seg, regCondBool.GetRegID())) return; // bool register is used beyond the CMP, we can't drop it auto& cmpInstr = seg.imlList[cmpInstrIndex]; cemu_assert_debug(cmpInstr.type == PPCREC_IML_TYPE_COMPARE || cmpInstr.type == PPCREC_IML_TYPE_COMPARE_S32); if(cmpInstr.type == PPCREC_IML_TYPE_COMPARE) { IMLReg regA = cmpInstr.op_compare.regA; IMLReg regB = cmpInstr.op_compare.regB; seg.imlList[cmpInstrIndex].make_r_r(PPCREC_IML_OP_X86_CMP, regA, regB); } else { IMLReg regA = cmpInstr.op_compare_s32.regA; sint32 val = cmpInstr.op_compare_s32.immS32; seg.imlList[cmpInstrIndex].make_r_s32(PPCREC_IML_OP_X86_CMP, regA, val); } } void IMLOptimizer_StandardOptimizationPassForSegment(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg) { IMLOptimizer_RemoveDeadCodeFromSegment(regIoAnalysis, seg); #ifdef ARCH_X86_64 // x86 specific optimizations IMLOptimizerX86_SubstituteCJumpForEflagsJump(regIoAnalysis, seg); // this pass should be applied late since it creates invisible eflags dependencies (which would break further register dependency analysis) #endif } void IMLOptimizer_StandardOptimizationPass(ppcImlGenContext_t& ppcImlGenContext) { IMLOptimizerRegIOAnalysis regIoAnalysis(ppcImlGenContext.segmentList2, ppcImlGenContext.GetMaxRegId()); regIoAnalysis.ComputeDepedencies(); for (IMLSegment* segIt : ppcImlGenContext.segmentList2) { IMLOptimizer_StandardOptimizationPassForSegment(regIoAnalysis, *segIt); } } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp ================================================ #include "IML.h" #include "../PPCRecompiler.h" #include "../PPCRecompilerIml.h" #include "IMLRegisterAllocator.h" #include "IMLRegisterAllocatorRanges.h" #include "../BackendX64/BackendX64.h" #ifdef __aarch64__ #include "../BackendAArch64/BackendAArch64.h" #endif #include <boost/container/static_vector.hpp> #include <boost/container/small_vector.hpp> #include "Common/cpu_features.h" #define DEBUG_RA_EXTRA_VALIDATION 0 // if set to non-zero, additional expensive validation checks will be performed #define DEBUG_RA_INSTRUCTION_GEN 0 struct IMLRARegAbstractLiveness // preliminary liveness info. One entry per register and segment { IMLRARegAbstractLiveness(IMLRegFormat regBaseFormat, sint32 usageStart, sint32 usageEnd) : regBaseFormat(regBaseFormat), usageStart(usageStart), usageEnd(usageEnd) {}; void TrackInstruction(sint32 index) { usageStart = std::min<sint32>(usageStart, index); usageEnd = std::max<sint32>(usageEnd, index + 1); // exclusive index } sint32 usageStart; sint32 usageEnd; bool isProcessed{false}; IMLRegFormat regBaseFormat; }; struct IMLRegisterAllocatorContext { IMLRegisterAllocatorParameters* raParam; ppcImlGenContext_t* deprGenContext; // deprecated. Try to decouple IMLRA from other parts of IML/PPCRec std::unordered_map<IMLRegID, IMLRegFormat> regIdToBaseFormat; // first pass std::vector<std::unordered_map<IMLRegID, IMLRARegAbstractLiveness>> perSegmentAbstractRanges; // helper methods inline std::unordered_map<IMLRegID, IMLRARegAbstractLiveness>& GetSegmentAbstractRangeMap(IMLSegment* imlSegment) { return perSegmentAbstractRanges[imlSegment->momentaryIndex]; } inline IMLRegFormat GetBaseFormatByRegId(IMLRegID regId) const { auto it = regIdToBaseFormat.find(regId); cemu_assert_debug(it != regIdToBaseFormat.cend()); return it->second; } }; struct IMLFixedRegisters { struct Entry { Entry(IMLReg reg, IMLPhysRegisterSet physRegSet) : reg(reg), physRegSet(physRegSet) {} IMLReg reg; IMLPhysRegisterSet physRegSet; }; boost::container::small_vector<Entry, 4> listInput; // fixed register requirements for instruction input edge boost::container::small_vector<Entry, 4> listOutput; // fixed register requirements for instruction output edge }; static void SetupCallingConvention(const IMLInstruction* instruction, IMLFixedRegisters& fixedRegs, const IMLPhysReg intParamToPhysReg[3], const IMLPhysReg floatParamToPhysReg[3], const IMLPhysReg intReturnPhysReg, const IMLPhysReg floatReturnPhysReg, IMLPhysRegisterSet volatileRegisters) { sint32 numIntParams = 0, numFloatParams = 0; auto AddParameterMapping = [&](IMLReg reg) { if (!reg.IsValid()) return; if (reg.GetBaseFormat() == IMLRegFormat::I64) { IMLPhysRegisterSet ps; ps.SetAvailable(intParamToPhysReg[numIntParams]); fixedRegs.listInput.emplace_back(reg, ps); numIntParams++; } else if (reg.GetBaseFormat() == IMLRegFormat::F64) { IMLPhysRegisterSet ps; ps.SetAvailable(floatParamToPhysReg[numFloatParams]); fixedRegs.listInput.emplace_back(reg, ps); numFloatParams++; } else { cemu_assert_suspicious(); } }; AddParameterMapping(instruction->op_call_imm.regParam0); AddParameterMapping(instruction->op_call_imm.regParam1); AddParameterMapping(instruction->op_call_imm.regParam2); // return value if (instruction->op_call_imm.regReturn.IsValid()) { IMLRegFormat returnFormat = instruction->op_call_imm.regReturn.GetBaseFormat(); bool isIntegerFormat = returnFormat == IMLRegFormat::I64 || returnFormat == IMLRegFormat::I32 || returnFormat == IMLRegFormat::I16 || returnFormat == IMLRegFormat::I8; IMLPhysRegisterSet ps; if (isIntegerFormat) { ps.SetAvailable(intReturnPhysReg); volatileRegisters.SetReserved(intReturnPhysReg); } else { ps.SetAvailable(floatReturnPhysReg); volatileRegisters.SetReserved(floatReturnPhysReg); } fixedRegs.listOutput.emplace_back(instruction->op_call_imm.regReturn, ps); } // block volatile registers from being used on the output edge, this makes the register allocator store them during the call fixedRegs.listOutput.emplace_back(IMLREG_INVALID, volatileRegisters); } #if defined(__aarch64__) // aarch64 static void GetInstructionFixedRegisters(IMLInstruction* instruction, IMLFixedRegisters& fixedRegs) { fixedRegs.listInput.clear(); fixedRegs.listOutput.clear(); // The purpose of GetInstructionFixedRegisters() is to constraint virtual registers to specific physical registers for instructions which need it // on x86 this is used for instructions like SHL <reg>, CL where the CL register is hardwired. On aarch it's probably only necessary for setting up the calling convention if (instruction->type == PPCREC_IML_TYPE_CALL_IMM) { const IMLPhysReg intParamToPhysReg[3] = {IMLArchAArch64::PHYSREG_GPR_BASE + 0, IMLArchAArch64::PHYSREG_GPR_BASE + 1, IMLArchAArch64::PHYSREG_GPR_BASE + 2}; const IMLPhysReg floatParamToPhysReg[3] = {IMLArchAArch64::PHYSREG_FPR_BASE + 0, IMLArchAArch64::PHYSREG_FPR_BASE + 1, IMLArchAArch64::PHYSREG_FPR_BASE + 2}; IMLPhysRegisterSet volatileRegs; for (int i = 0; i <= 17; i++) // x0 to x17 are volatile volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_GPR_BASE + i); // v0-v7 & v16-v31 are volatile. For v8-v15 only the high 64 bits are volatile. for (int i = 0; i <= 7; i++) volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_FPR_BASE + i); for (int i = 16; i <= 31; i++) volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_FPR_BASE + i); SetupCallingConvention(instruction, fixedRegs, intParamToPhysReg, floatParamToPhysReg, IMLArchAArch64::PHYSREG_GPR_BASE + 0, IMLArchAArch64::PHYSREG_FPR_BASE + 0, volatileRegs); } } #else // x86-64 static void GetInstructionFixedRegisters(IMLInstruction* instruction, IMLFixedRegisters& fixedRegs) { fixedRegs.listInput.clear(); fixedRegs.listOutput.clear(); if (instruction->type == PPCREC_IML_TYPE_R_R_R) { if (instruction->operation == PPCREC_IML_OP_LEFT_SHIFT || instruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S || instruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) { if(!g_CPUFeatures.x86.bmi2) { IMLPhysRegisterSet ps; ps.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_ECX); fixedRegs.listInput.emplace_back(instruction->op_r_r_r.regB, ps); } } } else if (instruction->type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) { IMLPhysRegisterSet ps; ps.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_EAX); fixedRegs.listInput.emplace_back(IMLREG_INVALID, ps); // none of the inputs may use EAX fixedRegs.listOutput.emplace_back(instruction->op_atomic_compare_store.regBoolOut, ps); // but we output to EAX } else if (instruction->type == PPCREC_IML_TYPE_CALL_IMM) { const IMLPhysReg intParamToPhysReg[3] = {IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RCX, IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDX, IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R8}; const IMLPhysReg floatParamToPhysReg[3] = {IMLArchX86::PHYSREG_FPR_BASE + 0, IMLArchX86::PHYSREG_FPR_BASE + 1, IMLArchX86::PHYSREG_FPR_BASE + 2}; IMLPhysRegisterSet volatileRegs; volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RAX); volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RCX); volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDX); volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R8); volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R9); volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R10); volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R11); // YMM0-YMM5 are volatile for (int i = 0; i <= 5; i++) volatileRegs.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + i); // for YMM6-YMM15 only the upper 128 bits are volatile which we dont use SetupCallingConvention(instruction, fixedRegs, intParamToPhysReg, floatParamToPhysReg, IMLArchX86::PHYSREG_GPR_BASE + X86_REG_EAX, IMLArchX86::PHYSREG_FPR_BASE + 0, volatileRegs); } } #endif uint32 IMLRA_GetNextIterationIndex() { static uint32 recRACurrentIterationIndex = 0; recRACurrentIterationIndex++; return recRACurrentIterationIndex; } bool _detectLoop(IMLSegment* currentSegment, sint32 depth, uint32 iterationIndex, IMLSegment* imlSegmentLoopBase) { if (currentSegment == imlSegmentLoopBase) return true; if (currentSegment->raInfo.lastIterationIndex == iterationIndex) return currentSegment->raInfo.isPartOfProcessedLoop; if (depth >= 9) return false; currentSegment->raInfo.lastIterationIndex = iterationIndex; currentSegment->raInfo.isPartOfProcessedLoop = false; if (currentSegment->nextSegmentIsUncertain) return false; if (currentSegment->nextSegmentBranchNotTaken) { if (currentSegment->nextSegmentBranchNotTaken->momentaryIndex > currentSegment->momentaryIndex) { currentSegment->raInfo.isPartOfProcessedLoop |= _detectLoop(currentSegment->nextSegmentBranchNotTaken, depth + 1, iterationIndex, imlSegmentLoopBase); } } if (currentSegment->nextSegmentBranchTaken) { if (currentSegment->nextSegmentBranchTaken->momentaryIndex > currentSegment->momentaryIndex) { currentSegment->raInfo.isPartOfProcessedLoop |= _detectLoop(currentSegment->nextSegmentBranchTaken, depth + 1, iterationIndex, imlSegmentLoopBase); } } if (currentSegment->raInfo.isPartOfProcessedLoop) currentSegment->loopDepth++; return currentSegment->raInfo.isPartOfProcessedLoop; } void IMLRA_DetectLoop(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegmentLoopBase) { uint32 iterationIndex = IMLRA_GetNextIterationIndex(); imlSegmentLoopBase->raInfo.lastIterationIndex = iterationIndex; if (_detectLoop(imlSegmentLoopBase->nextSegmentBranchTaken, 0, iterationIndex, imlSegmentLoopBase)) { imlSegmentLoopBase->loopDepth++; } } void IMLRA_IdentifyLoop(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment) { if (imlSegment->nextSegmentIsUncertain) return; // check if this segment has a branch that links to itself (tight loop) if (imlSegment->nextSegmentBranchTaken == imlSegment) { // segment loops over itself imlSegment->loopDepth++; return; } // check if this segment has a branch that goes backwards (potential complex loop) if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->momentaryIndex < imlSegment->momentaryIndex) { IMLRA_DetectLoop(ppcImlGenContext, imlSegment); } } #define SUBRANGE_LIST_SIZE (128) sint32 IMLRA_CountDistanceUntilNextUse(raLivenessRange* subrange, raInstructionEdge startPosition) { for (sint32 i = 0; i < subrange->list_accessLocations.size(); i++) { if (subrange->list_accessLocations[i].pos >= startPosition) { auto& it = subrange->list_accessLocations[i]; cemu_assert_debug(it.IsRead() != it.IsWrite()); // an access location can be either read or write cemu_assert_debug(!startPosition.ConnectsToPreviousSegment() && !startPosition.ConnectsToNextSegment()); return it.pos.GetRaw() - startPosition.GetRaw(); } } cemu_assert_debug(subrange->imlSegment->imlList.size() < 10000); return 10001 * 2; } // returns -1 if there is no fixed register requirement on or after startPosition sint32 IMLRA_CountDistanceUntilFixedRegUsageInRange(IMLSegment* imlSegment, raLivenessRange* range, raInstructionEdge startPosition, sint32 physRegister, bool& hasFixedAccess) { hasFixedAccess = false; cemu_assert_debug(startPosition.IsInstructionIndex()); for (auto& fixedReqEntry : range->list_fixedRegRequirements) { if (fixedReqEntry.pos < startPosition) continue; if (fixedReqEntry.allowedReg.IsAvailable(physRegister)) { hasFixedAccess = true; return fixedReqEntry.pos.GetRaw() - startPosition.GetRaw(); } } cemu_assert_debug(range->interval.end.IsInstructionIndex()); return range->interval.end.GetRaw() - startPosition.GetRaw(); } sint32 IMLRA_CountDistanceUntilFixedRegUsage(IMLSegment* imlSegment, raInstructionEdge startPosition, sint32 maxDistance, IMLRegID ourRegId, sint32 physRegister) { cemu_assert_debug(startPosition.IsInstructionIndex()); raInstructionEdge lastPos2; lastPos2.Set(imlSegment->imlList.size(), false); raInstructionEdge endPos; endPos = startPosition + maxDistance; if (endPos > lastPos2) endPos = lastPos2; IMLFixedRegisters fixedRegs; if (startPosition.IsOnOutputEdge()) GetInstructionFixedRegisters(imlSegment->imlList.data() + startPosition.GetInstructionIndex(), fixedRegs); for (raInstructionEdge currentPos = startPosition; currentPos <= endPos; ++currentPos) { if (currentPos.IsOnInputEdge()) { GetInstructionFixedRegisters(imlSegment->imlList.data() + currentPos.GetInstructionIndex(), fixedRegs); } auto& fixedRegAccess = currentPos.IsOnInputEdge() ? fixedRegs.listInput : fixedRegs.listOutput; for (auto& fixedRegLoc : fixedRegAccess) { if (fixedRegLoc.reg.IsInvalid() || fixedRegLoc.reg.GetRegID() != ourRegId) { cemu_assert_debug(fixedRegLoc.reg.IsInvalid() || fixedRegLoc.physRegSet.HasExactlyOneAvailable()); // this whole function only makes sense when there is only one fixed register, otherwise there are extra permutations to consider. Except for IMLREG_INVALID which is used to indicate reserved registers if (fixedRegLoc.physRegSet.IsAvailable(physRegister)) return currentPos.GetRaw() - startPosition.GetRaw(); } } } return endPos.GetRaw() - startPosition.GetRaw(); } // count how many instructions there are until physRegister is used by any subrange or reserved for any fixed register requirement (returns 0 if register is in use at startIndex) sint32 PPCRecRA_countDistanceUntilNextLocalPhysRegisterUse(IMLSegment* imlSegment, raInstructionEdge startPosition, sint32 physRegister) { cemu_assert_debug(startPosition.IsInstructionIndex()); sint32 minDistance = (sint32)imlSegment->imlList.size() * 2 - startPosition.GetRaw(); // next raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { if (subrangeItr->GetPhysicalRegister() != physRegister) { subrangeItr = subrangeItr->link_allSegmentRanges.next; continue; } if (subrangeItr->interval.ContainsEdge(startPosition)) return 0; if (subrangeItr->interval.end < startPosition) { subrangeItr = subrangeItr->link_allSegmentRanges.next; continue; } cemu_assert_debug(startPosition <= subrangeItr->interval.start); sint32 currentDist = subrangeItr->interval.start.GetRaw() - startPosition.GetRaw(); minDistance = std::min(minDistance, currentDist); subrangeItr = subrangeItr->link_allSegmentRanges.next; } return minDistance; } struct IMLRALivenessTimeline { IMLRALivenessTimeline() { } // manually add an active range void AddActiveRange(raLivenessRange* subrange) { activeRanges.emplace_back(subrange); } void ExpireRanges(raInstructionEdge expireUpTo) { expiredRanges.clear(); size_t count = activeRanges.size(); for (size_t f = 0; f < count; f++) { raLivenessRange* liverange = activeRanges[f]; if (liverange->interval.end < expireUpTo) // this was <= but since end is not inclusive we need to use < { #ifdef CEMU_DEBUG_ASSERT if (!expireUpTo.ConnectsToNextSegment() && (liverange->subrangeBranchTaken || liverange->subrangeBranchNotTaken)) assert_dbg(); // infinite subranges should not expire #endif expiredRanges.emplace_back(liverange); // remove entry activeRanges[f] = activeRanges[count - 1]; f--; count--; } } if (count != activeRanges.size()) activeRanges.resize(count); } std::span<raLivenessRange*> GetExpiredRanges() { return {expiredRanges.data(), expiredRanges.size()}; } std::span<raLivenessRange*> GetActiveRanges() { return {activeRanges.data(), activeRanges.size()}; } raLivenessRange* GetActiveRangeByVirtualRegId(IMLRegID regId) { for (auto& it : activeRanges) if (it->virtualRegister == regId) return it; return nullptr; } raLivenessRange* GetActiveRangeByPhysicalReg(sint32 physReg) { cemu_assert_debug(physReg >= 0); for (auto& it : activeRanges) if (it->physicalRegister == physReg) return it; return nullptr; } boost::container::small_vector<raLivenessRange*, 64> activeRanges; private: boost::container::small_vector<raLivenessRange*, 16> expiredRanges; }; // mark occupied registers by any overlapping range as unavailable in physRegSet void PPCRecRA_MaskOverlappingPhysRegForGlobalRange(raLivenessRange* range2, IMLPhysRegisterSet& physRegSet) { auto clusterRanges = range2->GetAllSubrangesInCluster(); for (auto& subrange : clusterRanges) { IMLSegment* imlSegment = subrange->imlSegment; raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { if (subrange == subrangeItr) { // next subrangeItr = subrangeItr->link_allSegmentRanges.next; continue; } if (subrange->interval.IsOverlapping(subrangeItr->interval)) { if (subrangeItr->GetPhysicalRegister() >= 0) physRegSet.SetReserved(subrangeItr->GetPhysicalRegister()); } // next subrangeItr = subrangeItr->link_allSegmentRanges.next; } } } bool _livenessRangeStartCompare(raLivenessRange* lhs, raLivenessRange* rhs) { return lhs->interval.start < rhs->interval.start; } void _sortSegmentAllSubrangesLinkedList(IMLSegment* imlSegment) { raLivenessRange* subrangeList[4096 + 1]; sint32 count = 0; // disassemble linked list raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { cemu_assert(count < 4096); subrangeList[count] = subrangeItr; count++; // next subrangeItr = subrangeItr->link_allSegmentRanges.next; } if (count == 0) { imlSegment->raInfo.linkedList_allSubranges = nullptr; return; } // sort std::sort(subrangeList, subrangeList + count, _livenessRangeStartCompare); // reassemble linked list subrangeList[count] = nullptr; imlSegment->raInfo.linkedList_allSubranges = subrangeList[0]; subrangeList[0]->link_allSegmentRanges.prev = nullptr; subrangeList[0]->link_allSegmentRanges.next = subrangeList[1]; for (sint32 i = 1; i < count; i++) { subrangeList[i]->link_allSegmentRanges.prev = subrangeList[i - 1]; subrangeList[i]->link_allSegmentRanges.next = subrangeList[i + 1]; } // validate list #if DEBUG_RA_EXTRA_VALIDATION sint32 count2 = 0; subrangeItr = imlSegment->raInfo.linkedList_allSubranges; raInstructionEdge currentStartPosition; currentStartPosition.SetRaw(RA_INTER_RANGE_START); while (subrangeItr) { count2++; if (subrangeItr->interval2.start < currentStartPosition) assert_dbg(); currentStartPosition = subrangeItr->interval2.start; // next subrangeItr = subrangeItr->link_allSegmentRanges.next; } if (count != count2) assert_dbg(); #endif } std::unordered_map<IMLRegID, raLivenessRange*>& IMLRA_GetSubrangeMap(IMLSegment* imlSegment) { return imlSegment->raInfo.linkedList_perVirtualRegister; } raLivenessRange* IMLRA_GetSubrange(IMLSegment* imlSegment, IMLRegID regId) { auto it = imlSegment->raInfo.linkedList_perVirtualRegister.find(regId); if (it == imlSegment->raInfo.linkedList_perVirtualRegister.end()) return nullptr; return it->second; } struct raFixedRegRequirementWithVGPR { raFixedRegRequirementWithVGPR(raInstructionEdge pos, IMLPhysRegisterSet allowedReg, IMLRegID regId) : pos(pos), allowedReg(allowedReg), regId(regId) {} raInstructionEdge pos; IMLPhysRegisterSet allowedReg; IMLRegID regId; }; std::vector<raFixedRegRequirementWithVGPR> IMLRA_BuildSegmentInstructionFixedRegList(IMLSegment* imlSegment) { std::vector<raFixedRegRequirementWithVGPR> frrList; size_t index = 0; while (index < imlSegment->imlList.size()) { IMLFixedRegisters fixedRegs; GetInstructionFixedRegisters(&imlSegment->imlList[index], fixedRegs); raInstructionEdge pos; pos.Set(index, true); for (auto& fixedRegAccess : fixedRegs.listInput) { frrList.emplace_back(pos, fixedRegAccess.physRegSet, fixedRegAccess.reg.IsValid() ? fixedRegAccess.reg.GetRegID() : IMLRegID_INVALID); } pos = pos + 1; for (auto& fixedRegAccess : fixedRegs.listOutput) { frrList.emplace_back(pos, fixedRegAccess.physRegSet, fixedRegAccess.reg.IsValid() ? fixedRegAccess.reg.GetRegID() : IMLRegID_INVALID); } index++; } return frrList; } boost::container::small_vector<raLivenessRange*, 8> IMLRA_GetRangeWithFixedRegReservationOverlappingPos(IMLSegment* imlSegment, raInstructionEdge pos, IMLPhysReg physReg) { boost::container::small_vector<raLivenessRange*, 8> rangeList; for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) { if (!currentRange->interval.ContainsEdge(pos)) continue; IMLPhysRegisterSet allowedRegs; if (!currentRange->GetAllowedRegistersEx(allowedRegs)) continue; if (allowedRegs.IsAvailable(physReg)) rangeList.emplace_back(currentRange); } return rangeList; } void IMLRA_HandleFixedRegisters(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment) { // first pass - iterate over all ranges with fixed register requirements and split them if they cross the segment border // todo - this pass currently creates suboptimal results by splitting all ranges that cross the segment border if they have any fixed register requirement. This can be avoided in some cases for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange;) { IMLPhysRegisterSet allowedRegs; if(currentRange->list_fixedRegRequirements.empty()) { currentRange = currentRange->link_allSegmentRanges.next; continue; // since we run this pass for every segment we dont need to do global checks here for clusters which may not even have fixed register requirements } if (!currentRange->GetAllowedRegistersEx(allowedRegs)) { currentRange = currentRange->link_allSegmentRanges.next; continue; } if (currentRange->interval.ExtendsPreviousSegment() || currentRange->interval.ExtendsIntoNextSegment()) { raLivenessRange* nextRange = currentRange->link_allSegmentRanges.next; IMLRA_ExplodeRangeCluster(ppcImlGenContext, currentRange); currentRange = nextRange; continue; } currentRange = currentRange->link_allSegmentRanges.next; } // second pass - look for ranges with conflicting fixed register requirements and split these too (locally) for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) { IMLPhysRegisterSet allowedRegs; if (currentRange->list_fixedRegRequirements.empty()) continue; // we dont need to check whole clusters because the pass above guarantees that there are no ranges with fixed register requirements that extend outside of this segment if (!currentRange->GetAllowedRegistersEx(allowedRegs)) continue; if (allowedRegs.HasAnyAvailable()) continue; cemu_assert_unimplemented(); } // third pass - assign fixed registers, split ranges if needed std::vector<raFixedRegRequirementWithVGPR> frr = IMLRA_BuildSegmentInstructionFixedRegList(imlSegment); std::unordered_map<IMLPhysReg, IMLRegID> lastVGPR; for (size_t i = 0; i < frr.size(); i++) { raFixedRegRequirementWithVGPR& entry = frr[i]; // we currently only handle fixed register requirements with a single register // with one exception: When regId is IMLRegID_INVALID then the entry acts as a list of reserved registers cemu_assert_debug(entry.regId == IMLRegID_INVALID || entry.allowedReg.HasExactlyOneAvailable()); for (IMLPhysReg physReg = entry.allowedReg.GetFirstAvailableReg(); physReg >= 0; physReg = entry.allowedReg.GetNextAvailableReg(physReg + 1)) { // check if the assigned vGPR has changed bool vgprHasChanged = false; auto it = lastVGPR.find(physReg); if (it != lastVGPR.end()) vgprHasChanged = it->second != entry.regId; else vgprHasChanged = true; lastVGPR[physReg] = entry.regId; if (!vgprHasChanged) continue; boost::container::small_vector<raLivenessRange*, 8> overlappingRanges = IMLRA_GetRangeWithFixedRegReservationOverlappingPos(imlSegment, entry.pos, physReg); if (entry.regId != IMLRegID_INVALID) cemu_assert_debug(!overlappingRanges.empty()); // there should always be at least one range that overlaps corresponding to the fixed register requirement, except for IMLRegID_INVALID which is used to indicate reserved registers for (auto& range : overlappingRanges) { if (range->interval.start < entry.pos) { IMLRA_SplitRange(ppcImlGenContext, range, entry.pos, true); } } } } // finally iterate ranges and assign fixed registers for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) { IMLPhysRegisterSet allowedRegs; if (currentRange->list_fixedRegRequirements.empty()) continue; // we dont need to check whole clusters because the pass above guarantees that there are no ranges with fixed register requirements that extend outside of this segment if (!currentRange->GetAllowedRegistersEx(allowedRegs)) { cemu_assert_debug(currentRange->list_fixedRegRequirements.empty()); continue; } cemu_assert_debug(allowedRegs.HasExactlyOneAvailable()); currentRange->SetPhysicalRegister(allowedRegs.GetFirstAvailableReg()); } // DEBUG - check for collisions and make sure all ranges with fixed register requirements got their physical register assigned #if DEBUG_RA_EXTRA_VALIDATION for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) { IMLPhysRegisterSet allowedRegs; if (!currentRange->HasPhysicalRegister()) continue; for (raLivenessRange* currentRange2 = imlSegment->raInfo.linkedList_allSubranges; currentRange2; currentRange2 = currentRange2->link_allSegmentRanges.next) { if (currentRange == currentRange2) continue; if (currentRange->interval2.IsOverlapping(currentRange2->interval2)) { cemu_assert_debug(currentRange->GetPhysicalRegister() != currentRange2->GetPhysicalRegister()); } } } #endif } // we should not split ranges on instructions with tied registers (i.e. where a register encoded as a single parameter is both input and output) // otherwise the RA algorithm has to assign both ranges the same physical register (not supported yet) and the point of splitting to fit another range is nullified void IMLRA_MakeSafeSplitPosition(IMLSegment* imlSegment, raInstructionEdge& pos) { // we ignore the instruction for now and just always make it a safe split position cemu_assert_debug(pos.IsInstructionIndex()); if (pos.IsOnOutputEdge()) pos = pos - 1; } // convenience wrapper for IMLRA_MakeSafeSplitPosition void IMLRA_MakeSafeSplitDistance(IMLSegment* imlSegment, raInstructionEdge startPos, sint32& distance) { cemu_assert_debug(startPos.IsInstructionIndex()); cemu_assert_debug(distance >= 0); raInstructionEdge endPos = startPos + distance; IMLRA_MakeSafeSplitPosition(imlSegment, endPos); if (endPos < startPos) { distance = 0; return; } distance = endPos.GetRaw() - startPos.GetRaw(); } static void DbgVerifyAllRanges(IMLRegisterAllocatorContext& ctx); class RASpillStrategy { public: virtual void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) = 0; sint32 GetCost() { return strategyCost; } protected: void ResetCost() { strategyCost = INT_MAX; } sint32 strategyCost; }; class RASpillStrategy_LocalRangeHoleCutting : public RASpillStrategy { public: void Reset() { localRangeHoleCutting.distance = -1; localRangeHoleCutting.largestHoleSubrange = nullptr; ResetCost(); } void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& allowedRegs) { raInstructionEdge currentRangeStart = currentRange->interval.start; sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); cemu_assert_debug(localRangeHoleCutting.distance == -1); cemu_assert_debug(strategyCost == INT_MAX); if (!currentRangeStart.ConnectsToPreviousSegment()) { cemu_assert_debug(currentRangeStart.GetRaw() >= 0); for (auto candidate : timeline.activeRanges) { if (candidate->interval.ExtendsIntoNextSegment()) continue; // new checks (Oct 2024): if (candidate == currentRange) continue; if (candidate->GetPhysicalRegister() < 0) continue; if (!allowedRegs.IsAvailable(candidate->GetPhysicalRegister())) continue; sint32 distance2 = IMLRA_CountDistanceUntilNextUse(candidate, currentRangeStart); IMLRA_MakeSafeSplitDistance(imlSegment, currentRangeStart, distance2); if (distance2 < 2) continue; cemu_assert_debug(currentRangeStart.IsInstructionIndex()); distance2 = std::min<sint32>(distance2, imlSegment->imlList.size() * 2 - currentRangeStart.GetRaw()); // limit distance to end of segment // calculate split cost of candidate sint32 cost = IMLRA_CalculateAdditionalCostAfterSplit(candidate, currentRangeStart + distance2); // calculate additional split cost of currentRange if hole is not large enough if (distance2 < requiredSize2) { cost += IMLRA_CalculateAdditionalCostAfterSplit(currentRange, currentRangeStart + distance2); // we also slightly increase cost in relation to the remaining length (in order to make the algorithm prefer larger holes) cost += (requiredSize2 - distance2) / 10; } // compare cost with previous candidates if (cost < strategyCost) { strategyCost = cost; localRangeHoleCutting.distance = distance2; localRangeHoleCutting.largestHoleSubrange = candidate; } } } } void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override { cemu_assert_debug(strategyCost != INT_MAX); sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); raInstructionEdge currentRangeStart = currentRange->interval.start; raInstructionEdge holeStartPosition = currentRangeStart; raInstructionEdge holeEndPosition = currentRangeStart + localRangeHoleCutting.distance; raLivenessRange* collisionRange = localRangeHoleCutting.largestHoleSubrange; if (collisionRange->interval.start < holeStartPosition) { collisionRange = IMLRA_SplitRange(nullptr, collisionRange, holeStartPosition, true); cemu_assert_debug(!collisionRange || collisionRange->interval.start >= holeStartPosition); // verify if splitting worked at all, tail must be on or after the split point cemu_assert_debug(!collisionRange || collisionRange->interval.start >= holeEndPosition); // also verify that the trimmed hole is actually big enough } else { cemu_assert_unimplemented(); // we still need to trim? } // we may also have to cut the current range to fit partially into the hole if (requiredSize2 > localRangeHoleCutting.distance) { raLivenessRange* tailRange = IMLRA_SplitRange(nullptr, currentRange, currentRangeStart + localRangeHoleCutting.distance, true); if (tailRange) { cemu_assert_debug(tailRange->list_fixedRegRequirements.empty()); // we are not allowed to unassign fixed registers tailRange->UnsetPhysicalRegister(); } } // verify that the hole is large enough if (collisionRange) { cemu_assert_debug(!collisionRange->interval.IsOverlapping(currentRange->interval)); } } private: struct { sint32 distance; raLivenessRange* largestHoleSubrange; } localRangeHoleCutting; }; class RASpillStrategy_AvailableRegisterHole : public RASpillStrategy { // split current range (this is generally only a good choice when the current range is long but has few usages) public: void Reset() { ResetCost(); availableRegisterHole.distance = -1; availableRegisterHole.physRegister = -1; } void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& localAvailableRegsMask, const IMLPhysRegisterSet& allowedRegs) { sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); raInstructionEdge currentRangeStart = currentRange->interval.start; cemu_assert_debug(strategyCost == INT_MAX); availableRegisterHole.distance = -1; availableRegisterHole.physRegister = -1; if (currentRangeStart.GetRaw() >= 0) { if (localAvailableRegsMask.HasAnyAvailable()) { sint32 physRegItr = -1; while (true) { physRegItr = localAvailableRegsMask.GetNextAvailableReg(physRegItr + 1); if (physRegItr < 0) break; if (!allowedRegs.IsAvailable(physRegItr)) continue; // get size of potential hole for this register sint32 distance = PPCRecRA_countDistanceUntilNextLocalPhysRegisterUse(imlSegment, currentRangeStart, physRegItr); // some instructions may require the same register for another range, check the distance here sint32 distUntilFixedReg = IMLRA_CountDistanceUntilFixedRegUsage(imlSegment, currentRangeStart, distance, currentRange->GetVirtualRegister(), physRegItr); if (distUntilFixedReg < distance) distance = distUntilFixedReg; IMLRA_MakeSafeSplitDistance(imlSegment, currentRangeStart, distance); if (distance < 2) continue; // calculate additional cost due to split cemu_assert_debug(distance < requiredSize2); // should always be true otherwise previous step would have selected this register? sint32 cost = IMLRA_CalculateAdditionalCostAfterSplit(currentRange, currentRangeStart + distance); // add small additional cost for the remaining range (prefer larger holes) cost += ((requiredSize2 - distance) / 2) / 10; if (cost < strategyCost) { strategyCost = cost; availableRegisterHole.distance = distance; availableRegisterHole.physRegister = physRegItr; } } } } } void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override { cemu_assert_debug(strategyCost != INT_MAX); raInstructionEdge currentRangeStart = currentRange->interval.start; // use available register raLivenessRange* tailRange = IMLRA_SplitRange(nullptr, currentRange, currentRangeStart + availableRegisterHole.distance, true); if (tailRange) { cemu_assert_debug(tailRange->list_fixedRegRequirements.empty()); // we are not allowed to unassign fixed registers tailRange->UnsetPhysicalRegister(); } } private: struct { sint32 physRegister; sint32 distance; // size of hole } availableRegisterHole; }; class RASpillStrategy_ExplodeRange : public RASpillStrategy { public: void Reset() { ResetCost(); explodeRange.range = nullptr; explodeRange.distance = -1; } void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& allowedRegs) { raInstructionEdge currentRangeStart = currentRange->interval.start; if (currentRangeStart.ConnectsToPreviousSegment()) currentRangeStart.Set(0, true); sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); cemu_assert_debug(strategyCost == INT_MAX); explodeRange.range = nullptr; explodeRange.distance = -1; for (auto candidate : timeline.activeRanges) { if (!candidate->interval.ExtendsIntoNextSegment()) continue; // new checks (Oct 2024): if (candidate == currentRange) continue; if (candidate->GetPhysicalRegister() < 0) continue; if (!allowedRegs.IsAvailable(candidate->GetPhysicalRegister())) continue; sint32 distance = IMLRA_CountDistanceUntilNextUse(candidate, currentRangeStart); IMLRA_MakeSafeSplitDistance(imlSegment, currentRangeStart, distance); if (distance < 2) continue; sint32 cost = IMLRA_CalculateAdditionalCostOfRangeExplode(candidate); // if the hole is not large enough, add cost of splitting current subrange if (distance < requiredSize2) { cost += IMLRA_CalculateAdditionalCostAfterSplit(currentRange, currentRangeStart + distance); // add small additional cost for the remaining range (prefer larger holes) cost += ((requiredSize2 - distance) / 2) / 10; } // compare with current best candidate for this strategy if (cost < strategyCost) { strategyCost = cost; explodeRange.distance = distance; explodeRange.range = candidate; } } } void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override { raInstructionEdge currentRangeStart = currentRange->interval.start; if (currentRangeStart.ConnectsToPreviousSegment()) currentRangeStart.Set(0, true); sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); // explode range IMLRA_ExplodeRangeCluster(nullptr, explodeRange.range); // split current subrange if necessary if (requiredSize2 > explodeRange.distance) { raLivenessRange* tailRange = IMLRA_SplitRange(nullptr, currentRange, currentRangeStart + explodeRange.distance, true); if (tailRange) { cemu_assert_debug(tailRange->list_fixedRegRequirements.empty()); // we are not allowed to unassign fixed registers tailRange->UnsetPhysicalRegister(); } } } private: struct { raLivenessRange* range; sint32 distance; // size of hole // note: If we explode a range, we still have to check the size of the hole that becomes available, if too small then we need to add cost of splitting local subrange } explodeRange; }; class RASpillStrategy_ExplodeRangeInter : public RASpillStrategy { public: void Reset() { ResetCost(); explodeRange.range = nullptr; explodeRange.distance = -1; } void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& allowedRegs) { // explode the range with the least cost cemu_assert_debug(strategyCost == INT_MAX); cemu_assert_debug(explodeRange.range == nullptr && explodeRange.distance == -1); for (auto candidate : timeline.activeRanges) { if (!candidate->interval.ExtendsIntoNextSegment()) continue; // only select candidates that clash with current subrange if (candidate->GetPhysicalRegister() < 0 && candidate != currentRange) continue; // and also filter any that dont meet fixed register requirements if (!allowedRegs.IsAvailable(candidate->GetPhysicalRegister())) continue; sint32 cost; cost = IMLRA_CalculateAdditionalCostOfRangeExplode(candidate); // compare with current best candidate for this strategy if (cost < strategyCost) { strategyCost = cost; explodeRange.distance = INT_MAX; explodeRange.range = candidate; } } // add current range as a candidate too sint32 ownCost; ownCost = IMLRA_CalculateAdditionalCostOfRangeExplode(currentRange); if (ownCost < strategyCost) { strategyCost = ownCost; explodeRange.distance = INT_MAX; explodeRange.range = currentRange; } } void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override { cemu_assert_debug(strategyCost != INT_MAX); IMLRA_ExplodeRangeCluster(ctx, explodeRange.range); } private: struct { raLivenessRange* range; sint32 distance; // size of hole // note: If we explode a range, we still have to check the size of the hole that becomes available, if too small then we need to add cost of splitting local subrange }explodeRange; }; // filter any registers from candidatePhysRegSet which cannot be used by currentRange due to fixed register requirements within the range that it occupies void IMLRA_FilterReservedFixedRegisterRequirementsForSegment(IMLRegisterAllocatorContext& ctx, raLivenessRange* currentRange, IMLPhysRegisterSet& candidatePhysRegSet) { IMLSegment* seg = currentRange->imlSegment; if (seg->imlList.empty()) return; // there can be no fixed register requirements if there are no instructions raInstructionEdge firstPos = currentRange->interval.start; if (currentRange->interval.start.ConnectsToPreviousSegment()) firstPos.SetRaw(0); else if (currentRange->interval.start.ConnectsToNextSegment()) firstPos.Set(seg->imlList.size() - 1, false); raInstructionEdge lastPos = currentRange->interval.end; if (currentRange->interval.end.ConnectsToPreviousSegment()) lastPos.SetRaw(0); else if (currentRange->interval.end.ConnectsToNextSegment()) lastPos.Set(seg->imlList.size() - 1, false); cemu_assert_debug(firstPos <= lastPos); IMLRegID ourRegId = currentRange->GetVirtualRegister(); IMLFixedRegisters fixedRegs; if (firstPos.IsOnOutputEdge()) GetInstructionFixedRegisters(seg->imlList.data() + firstPos.GetInstructionIndex(), fixedRegs); for (raInstructionEdge currentPos = firstPos; currentPos <= lastPos; ++currentPos) { if (currentPos.IsOnInputEdge()) { GetInstructionFixedRegisters(seg->imlList.data() + currentPos.GetInstructionIndex(), fixedRegs); } auto& fixedRegAccess = currentPos.IsOnInputEdge() ? fixedRegs.listInput : fixedRegs.listOutput; for (auto& fixedRegLoc : fixedRegAccess) { if (fixedRegLoc.reg.IsInvalid() || fixedRegLoc.reg.GetRegID() != ourRegId) candidatePhysRegSet.RemoveRegisters(fixedRegLoc.physRegSet); } } } // filter out any registers along the range cluster void IMLRA_FilterReservedFixedRegisterRequirementsForCluster(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, raLivenessRange* currentRange, IMLPhysRegisterSet& candidatePhysRegSet) { cemu_assert_debug(currentRange->imlSegment == imlSegment); if (currentRange->interval.ExtendsPreviousSegment() || currentRange->interval.ExtendsIntoNextSegment()) { auto clusterRanges = currentRange->GetAllSubrangesInCluster(); for (auto& rangeIt : clusterRanges) { IMLRA_FilterReservedFixedRegisterRequirementsForSegment(ctx, rangeIt, candidatePhysRegSet); if (!candidatePhysRegSet.HasAnyAvailable()) break; } return; } IMLRA_FilterReservedFixedRegisterRequirementsForSegment(ctx, currentRange, candidatePhysRegSet); } bool IMLRA_AssignSegmentRegisters(IMLRegisterAllocatorContext& ctx, ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment) { // sort subranges ascending by start index _sortSegmentAllSubrangesLinkedList(imlSegment); IMLRALivenessTimeline livenessTimeline; raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; raInstructionEdge lastInstructionEdge; lastInstructionEdge.SetRaw(RA_INTER_RANGE_END); struct { RASpillStrategy_LocalRangeHoleCutting localRangeHoleCutting; RASpillStrategy_AvailableRegisterHole availableRegisterHole; RASpillStrategy_ExplodeRange explodeRange; // for ranges that connect to follow up segments: RASpillStrategy_ExplodeRangeInter explodeRangeInter; } strategy; while (subrangeItr) { raInstructionEdge currentRangeStart = subrangeItr->interval.start; // used to be currentIndex before refactor PPCRecRA_debugValidateSubrange(subrangeItr); livenessTimeline.ExpireRanges((currentRangeStart > lastInstructionEdge) ? lastInstructionEdge : currentRangeStart); // expire up to currentIndex (inclusive), but exclude infinite ranges // if subrange already has register assigned then add it to the active list and continue if (subrangeItr->GetPhysicalRegister() >= 0) { // verify if register is actually available #if DEBUG_RA_EXTRA_VALIDATION for (auto& liverangeItr : livenessTimeline.activeRanges) { // check for register mismatch cemu_assert_debug(liverangeItr->GetPhysicalRegister() != subrangeItr->GetPhysicalRegister()); } #endif livenessTimeline.AddActiveRange(subrangeItr); subrangeItr = subrangeItr->link_allSegmentRanges.next; continue; } // ranges with fixed register requirements should already have a phys register assigned if (!subrangeItr->list_fixedRegRequirements.empty()) { cemu_assert_debug(subrangeItr->HasPhysicalRegister()); } // find free register for current subrangeItr and segment IMLRegFormat regBaseFormat = ctx.GetBaseFormatByRegId(subrangeItr->GetVirtualRegister()); IMLPhysRegisterSet candidatePhysRegSet = ctx.raParam->GetPhysRegPool(regBaseFormat); cemu_assert_debug(candidatePhysRegSet.HasAnyAvailable()); // no valid pool provided for this register type IMLPhysRegisterSet allowedRegs = subrangeItr->GetAllowedRegisters(candidatePhysRegSet); cemu_assert_debug(allowedRegs.HasAnyAvailable()); // if zero regs are available, then this range needs to be split to avoid mismatching register requirements (do this in the initial pass to keep the code here simpler) candidatePhysRegSet &= allowedRegs; for (auto& liverangeItr : livenessTimeline.activeRanges) { cemu_assert_debug(liverangeItr->GetPhysicalRegister() >= 0); candidatePhysRegSet.SetReserved(liverangeItr->GetPhysicalRegister()); } // check intersections with other ranges and determine allowed registers IMLPhysRegisterSet localAvailableRegsMask = candidatePhysRegSet; // mask of registers that are currently not used (does not include range checks in other segments) if (candidatePhysRegSet.HasAnyAvailable()) { // check for overlaps on a global scale (subrangeItr can be part of a larger range cluster across multiple segments) PPCRecRA_MaskOverlappingPhysRegForGlobalRange(subrangeItr, candidatePhysRegSet); } // some target instructions may enforce specific registers (e.g. common on X86 where something like SHL <reg>, CL forces CL as the count register) // we determine the list of allowed registers here // this really only works if we assume single-register requirements (otherwise its better not to filter out early and instead allow register corrections later but we don't support this yet) if (candidatePhysRegSet.HasAnyAvailable()) { IMLRA_FilterReservedFixedRegisterRequirementsForCluster(ctx, imlSegment, subrangeItr, candidatePhysRegSet); } if (candidatePhysRegSet.HasAnyAvailable()) { // use free register subrangeItr->SetPhysicalRegisterForCluster(candidatePhysRegSet.GetFirstAvailableReg()); livenessTimeline.AddActiveRange(subrangeItr); subrangeItr = subrangeItr->link_allSegmentRanges.next; // next continue; } // there is no free register for the entire range // evaluate different strategies of splitting ranges to free up another register or shorten the current range strategy.localRangeHoleCutting.Reset(); strategy.availableRegisterHole.Reset(); strategy.explodeRange.Reset(); // cant assign register // there might be registers available, we just can't use them due to range conflicts RASpillStrategy* selectedStrategy = nullptr; auto SelectStrategyIfBetter = [&selectedStrategy](RASpillStrategy& newStrategy) { if (newStrategy.GetCost() == INT_MAX) return; if (selectedStrategy == nullptr || newStrategy.GetCost() < selectedStrategy->GetCost()) selectedStrategy = &newStrategy; }; if (!subrangeItr->interval.ExtendsIntoNextSegment()) { // range ends in current segment, use local strategies // evaluate strategy: Cut hole into local subrange strategy.localRangeHoleCutting.Evaluate(imlSegment, subrangeItr, livenessTimeline, allowedRegs); SelectStrategyIfBetter(strategy.localRangeHoleCutting); // evaluate strategy: Split current range to fit in available holes // todo - are checks required to avoid splitting on the suffix instruction? strategy.availableRegisterHole.Evaluate(imlSegment, subrangeItr, livenessTimeline, localAvailableRegsMask, allowedRegs); SelectStrategyIfBetter(strategy.availableRegisterHole); // evaluate strategy: Explode inter-segment ranges strategy.explodeRange.Evaluate(imlSegment, subrangeItr, livenessTimeline, allowedRegs); SelectStrategyIfBetter(strategy.explodeRange); } else // if subrangeItr->interval2.ExtendsIntoNextSegment() { strategy.explodeRangeInter.Reset(); strategy.explodeRangeInter.Evaluate(imlSegment, subrangeItr, livenessTimeline, allowedRegs); SelectStrategyIfBetter(strategy.explodeRangeInter); } // choose strategy if (selectedStrategy) { selectedStrategy->Apply(ppcImlGenContext, imlSegment, subrangeItr); } else { // none of the evulated strategies can be applied, this should only happen if the segment extends into the next segment(s) for which we have no good strategy cemu_assert_debug(subrangeItr->interval.ExtendsPreviousSegment()); // alternative strategy if we have no other choice: explode current range IMLRA_ExplodeRangeCluster(ppcImlGenContext, subrangeItr); } return false; } return true; } void IMLRA_AssignRegisters(IMLRegisterAllocatorContext& ctx, ppcImlGenContext_t* ppcImlGenContext) { // start with frequently executed segments first sint32 maxLoopDepth = 0; for (IMLSegment* segIt : ppcImlGenContext->segmentList2) { maxLoopDepth = std::max(maxLoopDepth, segIt->loopDepth); } // assign fixed registers first for (IMLSegment* segIt : ppcImlGenContext->segmentList2) IMLRA_HandleFixedRegisters(ppcImlGenContext, segIt); #if DEBUG_RA_EXTRA_VALIDATION // fixed registers are currently handled per-segment, but here we validate that they are assigned correctly on a global scope as well for (IMLSegment* imlSegment : ppcImlGenContext->segmentList2) { for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) { IMLPhysRegisterSet allowedRegs; if (!currentRange->GetAllowedRegistersEx(allowedRegs)) { cemu_assert_debug(currentRange->list_fixedRegRequirements.empty()); continue; } cemu_assert_debug(currentRange->HasPhysicalRegister() && allowedRegs.IsAvailable(currentRange->GetPhysicalRegister())); } } #endif while (true) { bool done = false; for (sint32 d = maxLoopDepth; d >= 0; d--) { for (IMLSegment* segIt : ppcImlGenContext->segmentList2) { if (segIt->loopDepth != d) continue; done = IMLRA_AssignSegmentRegisters(ctx, ppcImlGenContext, segIt); if (done == false) break; } if (done == false) break; } if (done) break; } } void IMLRA_ReshapeForRegisterAllocation(ppcImlGenContext_t* ppcImlGenContext) { // insert empty segments after every non-taken branch if the linked segment has more than one input // this gives the register allocator more room to create efficient spill code size_t segmentIndex = 0; while (segmentIndex < ppcImlGenContext->segmentList2.size()) { IMLSegment* imlSegment = ppcImlGenContext->segmentList2[segmentIndex]; if (imlSegment->nextSegmentIsUncertain) { segmentIndex++; continue; } if (imlSegment->nextSegmentBranchTaken == nullptr || imlSegment->nextSegmentBranchNotTaken == nullptr) { segmentIndex++; continue; } if (imlSegment->nextSegmentBranchNotTaken->list_prevSegments.size() <= 1) { segmentIndex++; continue; } if (imlSegment->nextSegmentBranchNotTaken->isEnterable) { segmentIndex++; continue; } PPCRecompilerIml_insertSegments(ppcImlGenContext, segmentIndex + 1, 1); IMLSegment* imlSegmentP0 = ppcImlGenContext->segmentList2[segmentIndex + 0]; IMLSegment* imlSegmentP1 = ppcImlGenContext->segmentList2[segmentIndex + 1]; IMLSegment* nextSegment = imlSegment->nextSegmentBranchNotTaken; IMLSegment_RemoveLink(imlSegmentP0, nextSegment); IMLSegment_SetLinkBranchNotTaken(imlSegmentP1, nextSegment); IMLSegment_SetLinkBranchNotTaken(imlSegmentP0, imlSegmentP1); segmentIndex++; } // detect loops for (size_t s = 0; s < ppcImlGenContext->segmentList2.size(); s++) { IMLSegment* imlSegment = ppcImlGenContext->segmentList2[s]; imlSegment->momentaryIndex = s; } for (size_t s = 0; s < ppcImlGenContext->segmentList2.size(); s++) { IMLSegment* imlSegment = ppcImlGenContext->segmentList2[s]; IMLRA_IdentifyLoop(ppcImlGenContext, imlSegment); } } IMLRARegAbstractLiveness* _GetAbstractRange(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, IMLRegID regId) { auto& segMap = ctx.GetSegmentAbstractRangeMap(imlSegment); auto it = segMap.find(regId); return it != segMap.end() ? &it->second : nullptr; } // scan instructions and establish register usage range for segment void IMLRA_CalculateSegmentMinMaxAbstractRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) { size_t instructionIndex = 0; IMLUsedRegisters gprTracking; auto& segDistMap = ctx.GetSegmentAbstractRangeMap(imlSegment); while (instructionIndex < imlSegment->imlList.size()) { imlSegment->imlList[instructionIndex].CheckRegisterUsage(&gprTracking); gprTracking.ForEachAccessedGPR([&](IMLReg gprReg, bool isWritten) { IMLRegID gprId = gprReg.GetRegID(); auto it = segDistMap.find(gprId); if (it == segDistMap.end()) { segDistMap.try_emplace(gprId, gprReg.GetBaseFormat(), (sint32)instructionIndex, (sint32)instructionIndex + 1); ctx.regIdToBaseFormat.try_emplace(gprId, gprReg.GetBaseFormat()); } else { it->second.TrackInstruction(instructionIndex); #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug(ctx.regIdToBaseFormat[gprId] == gprReg.GetBaseFormat()); // the base type per register always has to be the same #endif } }); instructionIndex++; } } void IMLRA_CalculateLivenessRanges(IMLRegisterAllocatorContext& ctx) { // for each register calculate min/max index of usage range within each segment size_t dbgIndex = 0; for (IMLSegment* segIt : ctx.deprGenContext->segmentList2) { cemu_assert_debug(segIt->momentaryIndex == dbgIndex); IMLRA_CalculateSegmentMinMaxAbstractRanges(ctx, segIt); dbgIndex++; } } raLivenessRange* PPCRecRA_convertToMappedRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, IMLRegID vGPR, IMLName name) { IMLRARegAbstractLiveness* abstractRange = _GetAbstractRange(ctx, imlSegment, vGPR); if (!abstractRange) return nullptr; if (abstractRange->isProcessed) { // return already existing segment raLivenessRange* existingRange = IMLRA_GetSubrange(imlSegment, vGPR); cemu_assert_debug(existingRange); return existingRange; } abstractRange->isProcessed = true; // create subrange cemu_assert_debug(IMLRA_GetSubrange(imlSegment, vGPR) == nullptr); cemu_assert_debug( (abstractRange->usageStart == abstractRange->usageEnd && (abstractRange->usageStart == RA_INTER_RANGE_START || abstractRange->usageStart == RA_INTER_RANGE_END)) || abstractRange->usageStart < abstractRange->usageEnd); // usageEnd is exclusive so it should always be larger sint32 inclusiveEnd = abstractRange->usageEnd; if (inclusiveEnd != RA_INTER_RANGE_START && inclusiveEnd != RA_INTER_RANGE_END) inclusiveEnd--; // subtract one, because usageEnd is exclusive, but the end value of the interval passed to createSubrange is inclusive raInterval interval; interval.SetInterval(abstractRange->usageStart, true, inclusiveEnd, true); raLivenessRange* subrange = IMLRA_CreateRange(ctx.deprGenContext, imlSegment, vGPR, name, interval.start, interval.end); // traverse forward if (abstractRange->usageEnd == RA_INTER_RANGE_END) { if (imlSegment->nextSegmentBranchTaken) { IMLRARegAbstractLiveness* branchTakenRange = _GetAbstractRange(ctx, imlSegment->nextSegmentBranchTaken, vGPR); if (branchTakenRange && branchTakenRange->usageStart == RA_INTER_RANGE_START) { subrange->subrangeBranchTaken = PPCRecRA_convertToMappedRanges(ctx, imlSegment->nextSegmentBranchTaken, vGPR, name); subrange->subrangeBranchTaken->previousRanges.push_back(subrange); cemu_assert_debug(subrange->subrangeBranchTaken->interval.ExtendsPreviousSegment()); } } if (imlSegment->nextSegmentBranchNotTaken) { IMLRARegAbstractLiveness* branchNotTakenRange = _GetAbstractRange(ctx, imlSegment->nextSegmentBranchNotTaken, vGPR); if (branchNotTakenRange && branchNotTakenRange->usageStart == RA_INTER_RANGE_START) { subrange->subrangeBranchNotTaken = PPCRecRA_convertToMappedRanges(ctx, imlSegment->nextSegmentBranchNotTaken, vGPR, name); subrange->subrangeBranchNotTaken->previousRanges.push_back(subrange); cemu_assert_debug(subrange->subrangeBranchNotTaken->interval.ExtendsPreviousSegment()); } } } // traverse backward if (abstractRange->usageStart == RA_INTER_RANGE_START) { for (auto& it : imlSegment->list_prevSegments) { IMLRARegAbstractLiveness* prevRange = _GetAbstractRange(ctx, it, vGPR); if (!prevRange) continue; if (prevRange->usageEnd == RA_INTER_RANGE_END) PPCRecRA_convertToMappedRanges(ctx, it, vGPR, name); } } return subrange; } void IMLRA_UpdateOrAddSubrangeLocation(raLivenessRange* subrange, raInstructionEdge pos) { if (subrange->list_accessLocations.empty()) { subrange->list_accessLocations.emplace_back(pos); return; } if(subrange->list_accessLocations.back().pos == pos) return; cemu_assert_debug(subrange->list_accessLocations.back().pos < pos); subrange->list_accessLocations.emplace_back(pos); } // take abstract range data and create LivenessRanges void IMLRA_ConvertAbstractToLivenessRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) { const std::unordered_map<IMLRegID, raLivenessRange*>& regToSubrange = IMLRA_GetSubrangeMap(imlSegment); auto AddOrUpdateFixedRegRequirement = [&](IMLRegID regId, sint32 instructionIndex, bool isInput, const IMLPhysRegisterSet& physRegSet) { raLivenessRange* subrange = regToSubrange.find(regId)->second; cemu_assert_debug(subrange); raFixedRegRequirement tmp; tmp.pos.Set(instructionIndex, isInput); tmp.allowedReg = physRegSet; if (subrange->list_fixedRegRequirements.empty() || subrange->list_fixedRegRequirements.back().pos != tmp.pos) subrange->list_fixedRegRequirements.push_back(tmp); }; // convert abstract min-max ranges to liveness range objects auto& segMap = ctx.GetSegmentAbstractRangeMap(imlSegment); for (auto& it : segMap) { if (it.second.isProcessed) continue; IMLRegID regId = it.first; PPCRecRA_convertToMappedRanges(ctx, imlSegment, regId, ctx.raParam->regIdToName.find(regId)->second); } // fill created ranges with read/write location indices // note that at this point there is only one range per register per segment // and the algorithm below relies on this size_t index = 0; IMLUsedRegisters gprTracking; while (index < imlSegment->imlList.size()) { imlSegment->imlList[index].CheckRegisterUsage(&gprTracking); raInstructionEdge pos((sint32)index, true); gprTracking.ForEachReadGPR([&](IMLReg gprReg) { IMLRegID gprId = gprReg.GetRegID(); raLivenessRange* subrange = regToSubrange.find(gprId)->second; IMLRA_UpdateOrAddSubrangeLocation(subrange, pos); }); pos = {(sint32)index, false}; gprTracking.ForEachWrittenGPR([&](IMLReg gprReg) { IMLRegID gprId = gprReg.GetRegID(); raLivenessRange* subrange = regToSubrange.find(gprId)->second; IMLRA_UpdateOrAddSubrangeLocation(subrange, pos); }); // check fixed register requirements IMLFixedRegisters fixedRegs; GetInstructionFixedRegisters(&imlSegment->imlList[index], fixedRegs); for (auto& fixedRegAccess : fixedRegs.listInput) { if (fixedRegAccess.reg != IMLREG_INVALID) AddOrUpdateFixedRegRequirement(fixedRegAccess.reg.GetRegID(), index, true, fixedRegAccess.physRegSet); } for (auto& fixedRegAccess : fixedRegs.listOutput) { if (fixedRegAccess.reg != IMLREG_INVALID) AddOrUpdateFixedRegRequirement(fixedRegAccess.reg.GetRegID(), index, false, fixedRegAccess.physRegSet); } index++; } } void IMLRA_extendAbstractRangeToEndOfSegment(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, IMLRegID regId) { auto& segDistMap = ctx.GetSegmentAbstractRangeMap(imlSegment); auto it = segDistMap.find(regId); if (it == segDistMap.end()) { sint32 startIndex; if (imlSegment->HasSuffixInstruction()) startIndex = imlSegment->GetSuffixInstructionIndex(); else startIndex = RA_INTER_RANGE_END; segDistMap.try_emplace((IMLRegID)regId, IMLRegFormat::INVALID_FORMAT, startIndex, RA_INTER_RANGE_END); } else { it->second.usageEnd = RA_INTER_RANGE_END; } } void IMLRA_extendAbstractRangeToBeginningOfSegment(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, IMLRegID regId) { auto& segDistMap = ctx.GetSegmentAbstractRangeMap(imlSegment); auto it = segDistMap.find(regId); if (it == segDistMap.end()) { segDistMap.try_emplace((IMLRegID)regId, IMLRegFormat::INVALID_FORMAT, RA_INTER_RANGE_START, RA_INTER_RANGE_START); } else { it->second.usageStart = RA_INTER_RANGE_START; } // propagate backwards for (auto& it : imlSegment->list_prevSegments) { IMLRA_extendAbstractRangeToEndOfSegment(ctx, it, regId); } } void IMLRA_connectAbstractRanges(IMLRegisterAllocatorContext& ctx, IMLRegID regId, IMLSegment** route, sint32 routeDepth) { #ifdef CEMU_DEBUG_ASSERT if (routeDepth < 2) assert_dbg(); #endif // extend starting range to end of segment IMLRA_extendAbstractRangeToEndOfSegment(ctx, route[0], regId); // extend all the connecting segments in both directions for (sint32 i = 1; i < (routeDepth - 1); i++) { IMLRA_extendAbstractRangeToEndOfSegment(ctx, route[i], regId); IMLRA_extendAbstractRangeToBeginningOfSegment(ctx, route[i], regId); } // extend the final segment towards the beginning IMLRA_extendAbstractRangeToBeginningOfSegment(ctx, route[routeDepth - 1], regId); } void _IMLRA_checkAndTryExtendRange(IMLRegisterAllocatorContext& ctx, IMLSegment* currentSegment, IMLRegID regID, sint32 distanceLeft, IMLSegment** route, sint32 routeDepth) { if (routeDepth >= 64) { cemuLog_logDebug(LogType::Force, "Recompiler RA route maximum depth exceeded\n"); return; } route[routeDepth] = currentSegment; IMLRARegAbstractLiveness* range = _GetAbstractRange(ctx, currentSegment, regID); if (!range) { // measure distance over entire segment distanceLeft -= (sint32)currentSegment->imlList.size(); if (distanceLeft > 0) { if (currentSegment->nextSegmentBranchNotTaken) _IMLRA_checkAndTryExtendRange(ctx, currentSegment->nextSegmentBranchNotTaken, regID, distanceLeft, route, routeDepth + 1); if (currentSegment->nextSegmentBranchTaken) _IMLRA_checkAndTryExtendRange(ctx, currentSegment->nextSegmentBranchTaken, regID, distanceLeft, route, routeDepth + 1); } return; } else { // measure distance to range if (range->usageStart == RA_INTER_RANGE_END) { if (distanceLeft < (sint32)currentSegment->imlList.size()) return; // range too far away } else if (range->usageStart != RA_INTER_RANGE_START && range->usageStart > distanceLeft) return; // out of range // found close range -> connect ranges IMLRA_connectAbstractRanges(ctx, regID, route, routeDepth + 1); } } void PPCRecRA_checkAndTryExtendRange(IMLRegisterAllocatorContext& ctx, IMLSegment* currentSegment, IMLRARegAbstractLiveness* range, IMLRegID regID) { cemu_assert_debug(range->usageEnd >= 0); // count instructions to end of initial segment sint32 instructionsUntilEndOfSeg; if (range->usageEnd == RA_INTER_RANGE_END) instructionsUntilEndOfSeg = 0; else instructionsUntilEndOfSeg = (sint32)currentSegment->imlList.size() - range->usageEnd; cemu_assert_debug(instructionsUntilEndOfSeg >= 0); sint32 remainingScanDist = 45 - instructionsUntilEndOfSeg; if (remainingScanDist <= 0) return; // can't reach end IMLSegment* route[64]; route[0] = currentSegment; if (currentSegment->nextSegmentBranchNotTaken) _IMLRA_checkAndTryExtendRange(ctx, currentSegment->nextSegmentBranchNotTaken, regID, remainingScanDist, route, 1); if (currentSegment->nextSegmentBranchTaken) _IMLRA_checkAndTryExtendRange(ctx, currentSegment->nextSegmentBranchTaken, regID, remainingScanDist, route, 1); } void PPCRecRA_mergeCloseRangesForSegmentV2(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) { auto& segMap = ctx.GetSegmentAbstractRangeMap(imlSegment); for (auto& it : segMap) { PPCRecRA_checkAndTryExtendRange(ctx, imlSegment, &(it.second), it.first); } #ifdef CEMU_DEBUG_ASSERT if (imlSegment->list_prevSegments.empty() == false && imlSegment->isEnterable) assert_dbg(); if ((imlSegment->nextSegmentBranchNotTaken != nullptr || imlSegment->nextSegmentBranchTaken != nullptr) && imlSegment->nextSegmentIsUncertain) assert_dbg(); #endif } void PPCRecRA_followFlowAndExtendRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) { std::vector<IMLSegment*> list_segments; std::vector<bool> list_processedSegment; size_t segmentCount = ctx.deprGenContext->segmentList2.size(); list_segments.reserve(segmentCount + 1); list_processedSegment.resize(segmentCount); auto markSegProcessed = [&list_processedSegment](IMLSegment* seg) { list_processedSegment[seg->momentaryIndex] = true; }; auto isSegProcessed = [&list_processedSegment](IMLSegment* seg) -> bool { return list_processedSegment[seg->momentaryIndex]; }; markSegProcessed(imlSegment); sint32 index = 0; list_segments.push_back(imlSegment); while (index < list_segments.size()) { IMLSegment* currentSegment = list_segments[index]; PPCRecRA_mergeCloseRangesForSegmentV2(ctx, currentSegment); // follow flow if (currentSegment->nextSegmentBranchNotTaken && !isSegProcessed(currentSegment->nextSegmentBranchNotTaken)) { markSegProcessed(currentSegment->nextSegmentBranchNotTaken); list_segments.push_back(currentSegment->nextSegmentBranchNotTaken); } if (currentSegment->nextSegmentBranchTaken && !isSegProcessed(currentSegment->nextSegmentBranchTaken)) { markSegProcessed(currentSegment->nextSegmentBranchTaken); list_segments.push_back(currentSegment->nextSegmentBranchTaken); } index++; } } void IMLRA_MergeCloseAbstractRanges(IMLRegisterAllocatorContext& ctx) { for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) { IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; if (!imlSegment->list_prevSegments.empty()) continue; // not an entry/standalone segment PPCRecRA_followFlowAndExtendRanges(ctx, imlSegment); } } void IMLRA_ExtendAbstractRangesOutOfLoops(IMLRegisterAllocatorContext& ctx) { for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) { IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; auto localLoopDepth = imlSegment->loopDepth; if (localLoopDepth <= 0) continue; // not inside a loop // look for loop exit bool hasLoopExit = false; if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->loopDepth < localLoopDepth) { hasLoopExit = true; } if (imlSegment->nextSegmentBranchNotTaken && imlSegment->nextSegmentBranchNotTaken->loopDepth < localLoopDepth) { hasLoopExit = true; } if (hasLoopExit == false) continue; // extend looping ranges into all exits (this allows the data flow analyzer to move stores out of the loop) auto& segMap = ctx.GetSegmentAbstractRangeMap(imlSegment); for (auto& it : segMap) { if (it.second.usageEnd != RA_INTER_RANGE_END) continue; if (imlSegment->nextSegmentBranchTaken) IMLRA_extendAbstractRangeToBeginningOfSegment(ctx, imlSegment->nextSegmentBranchTaken, it.first); if (imlSegment->nextSegmentBranchNotTaken) IMLRA_extendAbstractRangeToBeginningOfSegment(ctx, imlSegment->nextSegmentBranchNotTaken, it.first); } } } void IMLRA_ProcessFlowAndCalculateLivenessRanges(IMLRegisterAllocatorContext& ctx) { IMLRA_MergeCloseAbstractRanges(ctx); // extra pass to move register loads and stores out of loops IMLRA_ExtendAbstractRangesOutOfLoops(ctx); // calculate liveness ranges for (auto& segIt : ctx.deprGenContext->segmentList2) IMLRA_ConvertAbstractToLivenessRanges(ctx, segIt); } void IMLRA_AnalyzeSubrangeDataDependency(raLivenessRange* subrange) { bool isRead = false; bool isWritten = false; bool isOverwritten = false; for (auto& location : subrange->list_accessLocations) { if (location.IsRead()) { isRead = true; } if (location.IsWrite()) { if (isRead == false) isOverwritten = true; isWritten = true; } } subrange->_noLoad = isOverwritten; subrange->hasStore = isWritten; if (subrange->interval.ExtendsPreviousSegment()) subrange->_noLoad = true; } struct subrangeEndingInfo_t { raLivenessRange* subrangeList[SUBRANGE_LIST_SIZE]; sint32 subrangeCount; bool hasUndefinedEndings; }; void _findSubrangeWriteEndings(raLivenessRange* subrange, uint32 iterationIndex, sint32 depth, subrangeEndingInfo_t* info) { if (depth >= 30) { info->hasUndefinedEndings = true; return; } if (subrange->lastIterationIndex == iterationIndex) return; // already processed subrange->lastIterationIndex = iterationIndex; if (subrange->hasStoreDelayed) return; // no need to traverse this subrange IMLSegment* imlSegment = subrange->imlSegment; if (!subrange->interval.ExtendsIntoNextSegment()) { // ending segment if (info->subrangeCount >= SUBRANGE_LIST_SIZE) { info->hasUndefinedEndings = true; return; } else { info->subrangeList[info->subrangeCount] = subrange; info->subrangeCount++; } return; } // traverse next subranges in flow if (imlSegment->nextSegmentBranchNotTaken) { if (subrange->subrangeBranchNotTaken == nullptr) { info->hasUndefinedEndings = true; } else { _findSubrangeWriteEndings(subrange->subrangeBranchNotTaken, iterationIndex, depth + 1, info); } } if (imlSegment->nextSegmentBranchTaken) { if (subrange->subrangeBranchTaken == nullptr) { info->hasUndefinedEndings = true; } else { _findSubrangeWriteEndings(subrange->subrangeBranchTaken, iterationIndex, depth + 1, info); } } } static void IMLRA_AnalyzeRangeDataFlow(raLivenessRange* subrange) { if (!subrange->interval.ExtendsIntoNextSegment()) return; // analyze data flow across segments (if this segment has writes) if (subrange->hasStore) { subrangeEndingInfo_t writeEndingInfo; writeEndingInfo.subrangeCount = 0; writeEndingInfo.hasUndefinedEndings = false; _findSubrangeWriteEndings(subrange, IMLRA_GetNextIterationIndex(), 0, &writeEndingInfo); if (writeEndingInfo.hasUndefinedEndings == false) { // get cost of delaying store into endings sint32 delayStoreCost = 0; bool alreadyStoredInAllEndings = true; for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) { raLivenessRange* subrangeItr = writeEndingInfo.subrangeList[i]; if (subrangeItr->hasStore) continue; // this ending already stores, no extra cost alreadyStoredInAllEndings = false; sint32 storeCost = IMLRA_GetSegmentReadWriteCost(subrangeItr->imlSegment); delayStoreCost = std::max(storeCost, delayStoreCost); } if (alreadyStoredInAllEndings) { subrange->hasStore = false; subrange->hasStoreDelayed = true; } else if (delayStoreCost <= IMLRA_GetSegmentReadWriteCost(subrange->imlSegment)) { subrange->hasStore = false; subrange->hasStoreDelayed = true; for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) { raLivenessRange* subrangeItr = writeEndingInfo.subrangeList[i]; subrangeItr->hasStore = true; } } } } } void IMLRA_AnalyzeRangeDataFlow(ppcImlGenContext_t* ppcImlGenContext) { // this function is called after _AssignRegisters(), which means that all liveness ranges are already final and must not be modified anymore // track read/write dependencies per segment for (auto& seg : ppcImlGenContext->segmentList2) { raLivenessRange* subrange = seg->raInfo.linkedList_allSubranges; while (subrange) { IMLRA_AnalyzeSubrangeDataDependency(subrange); subrange = subrange->link_allSegmentRanges.next; } } // propagate information across segment boundaries for (auto& seg : ppcImlGenContext->segmentList2) { raLivenessRange* subrange = seg->raInfo.linkedList_allSubranges; while (subrange) { IMLRA_AnalyzeRangeDataFlow(subrange); subrange = subrange->link_allSegmentRanges.next; } } } /* Generate move instructions */ inline IMLReg _MakeNativeReg(IMLRegFormat baseFormat, IMLRegID regId) { return IMLReg(baseFormat, baseFormat, 0, regId); } // prepass for IMLRA_GenerateSegmentMoveInstructions which updates all virtual registers to their physical counterparts void IMLRA_RewriteRegisters(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) { std::unordered_map<IMLRegID, IMLRegID> virtId2PhysReg; boost::container::small_vector<raLivenessRange*, 64> activeRanges; raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; raInstructionEdge currentEdge; for (size_t i = 0; i < imlSegment->imlList.size(); i++) { currentEdge.Set(i, false); // set to instruction index on output edge // activate ranges which begin before or during this instruction while (currentRange && currentRange->interval.start <= currentEdge) { cemu_assert_debug(virtId2PhysReg.find(currentRange->GetVirtualRegister()) == virtId2PhysReg.end() || virtId2PhysReg[currentRange->GetVirtualRegister()] == currentRange->GetPhysicalRegister()); // check for register conflict virtId2PhysReg[currentRange->GetVirtualRegister()] = currentRange->GetPhysicalRegister(); activeRanges.push_back(currentRange); currentRange = currentRange->link_allSegmentRanges.next; } // rewrite registers imlSegment->imlList[i].RewriteGPR(virtId2PhysReg); // deactivate ranges which end during this instruction auto it = activeRanges.begin(); while (it != activeRanges.end()) { if ((*it)->interval.end <= currentEdge) { virtId2PhysReg.erase((*it)->GetVirtualRegister()); it = activeRanges.erase(it); } else ++it; } } } void IMLRA_GenerateSegmentMoveInstructions2(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) { IMLRA_RewriteRegisters(ctx, imlSegment); #if DEBUG_RA_INSTRUCTION_GEN cemuLog_log(LogType::Force, ""); cemuLog_log(LogType::Force, "[Seg before RA]"); IMLDebug_DumpSegment(nullptr, imlSegment, true); #endif bool hadSuffixInstruction = imlSegment->HasSuffixInstruction(); std::vector<IMLInstruction> rebuiltInstructions; sint32 numInstructionsWithoutSuffix = (sint32)imlSegment->imlList.size() - (imlSegment->HasSuffixInstruction() ? 1 : 0); if (imlSegment->imlList.empty()) { // empty segments need special handling (todo - look into merging this with the core logic below eventually) // store all ranges raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; while (currentRange) { if (currentRange->hasStore) rebuiltInstructions.emplace_back().make_name_r(currentRange->GetName(), _MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister())); currentRange = currentRange->link_allSegmentRanges.next; } // load ranges currentRange = imlSegment->raInfo.linkedList_allSubranges; while (currentRange) { if (!currentRange->_noLoad) { cemu_assert_debug(currentRange->interval.ExtendsIntoNextSegment()); rebuiltInstructions.emplace_back().make_r_name(_MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister()), currentRange->GetName()); } currentRange = currentRange->link_allSegmentRanges.next; } imlSegment->imlList = std::move(rebuiltInstructions); return; } // make sure that no range exceeds the suffix instruction input edge except if they need to be loaded for the next segment (todo - for those, set the start point accordingly?) { raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; raInstructionEdge edge; if (imlSegment->HasSuffixInstruction()) edge.Set(numInstructionsWithoutSuffix, true); else edge.Set(numInstructionsWithoutSuffix - 1, false); while (currentRange) { if (!currentRange->interval.IsNextSegmentOnly() && currentRange->interval.end > edge) { currentRange->interval.SetEnd(edge); } currentRange = currentRange->link_allSegmentRanges.next; } } #if DEBUG_RA_INSTRUCTION_GEN cemuLog_log(LogType::Force, ""); cemuLog_log(LogType::Force, "--- Intermediate liveness info ---"); { raLivenessRange* dbgRange = imlSegment->raInfo.linkedList_allSubranges; while (dbgRange) { cemuLog_log(LogType::Force, "Range i{}: {}-{}", dbgRange->GetVirtualRegister(), dbgRange->interval2.start.GetDebugString(), dbgRange->interval2.end.GetDebugString()); dbgRange = dbgRange->link_allSegmentRanges.next; } } #endif boost::container::small_vector<raLivenessRange*, 64> activeRanges; // first we add all the ranges that extend from the previous segment, some of these will end immediately at the first instruction so we might need to store them early raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; // make all ranges active that start on RA_INTER_RANGE_START while (currentRange && currentRange->interval.start.ConnectsToPreviousSegment()) { activeRanges.push_back(currentRange); currentRange = currentRange->link_allSegmentRanges.next; } // store all ranges that end before the first output edge (includes RA_INTER_RANGE_START) auto it = activeRanges.begin(); raInstructionEdge firstOutputEdge; firstOutputEdge.Set(0, false); while (it != activeRanges.end()) { if ((*it)->interval.end < firstOutputEdge) { raLivenessRange* storedRange = *it; if (storedRange->hasStore) rebuiltInstructions.emplace_back().make_name_r(storedRange->GetName(), _MakeNativeReg(ctx.regIdToBaseFormat[storedRange->GetVirtualRegister()], storedRange->GetPhysicalRegister())); it = activeRanges.erase(it); continue; } ++it; } sint32 numInstructions = (sint32)imlSegment->imlList.size(); for (sint32 i = 0; i < numInstructions; i++) { raInstructionEdge curEdge; // input edge curEdge.SetRaw(i * 2 + 1); // +1 to include ranges that start at the output of the instruction while (currentRange && currentRange->interval.start <= curEdge) { if (!currentRange->_noLoad) { rebuiltInstructions.emplace_back().make_r_name(_MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister()), currentRange->GetName()); } activeRanges.push_back(currentRange); currentRange = currentRange->link_allSegmentRanges.next; } // copy instruction rebuiltInstructions.push_back(imlSegment->imlList[i]); // output edge curEdge.SetRaw(i * 2 + 1 + 1); // also store ranges that end on the next input edge, we handle this by adding an extra 1 above auto it = activeRanges.begin(); while (it != activeRanges.end()) { if ((*it)->interval.end <= curEdge) { // range expires // todo - check hasStore raLivenessRange* storedRange = *it; if (storedRange->hasStore) { cemu_assert_debug(i != numInstructionsWithoutSuffix); // not allowed to emit after suffix rebuiltInstructions.emplace_back().make_name_r(storedRange->GetName(), _MakeNativeReg(ctx.regIdToBaseFormat[storedRange->GetVirtualRegister()], storedRange->GetPhysicalRegister())); } it = activeRanges.erase(it); continue; } ++it; } } // if there is no suffix instruction we currently need to handle the final loads here cemu_assert_debug(hadSuffixInstruction == imlSegment->HasSuffixInstruction()); if (imlSegment->HasSuffixInstruction()) { if (currentRange) { cemuLog_logDebug(LogType::Force, "[DEBUG] GenerateSegmentMoveInstructions() hit suffix path with non-null currentRange. Segment: {:08x}", imlSegment->ppcAddress); } for (auto& remainingRange : activeRanges) { cemu_assert_debug(!remainingRange->hasStore); } } else { for (auto& remainingRange : activeRanges) { cemu_assert_debug(!remainingRange->hasStore); // this range still needs to be stored } while (currentRange) { cemu_assert_debug(currentRange->interval.IsNextSegmentOnly()); cemu_assert_debug(!currentRange->_noLoad); rebuiltInstructions.emplace_back().make_r_name(_MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister()), currentRange->GetName()); currentRange = currentRange->link_allSegmentRanges.next; } } imlSegment->imlList = std::move(rebuiltInstructions); cemu_assert_debug(hadSuffixInstruction == imlSegment->HasSuffixInstruction()); #if DEBUG_RA_INSTRUCTION_GEN cemuLog_log(LogType::Force, ""); cemuLog_log(LogType::Force, "[Seg after RA]"); IMLDebug_DumpSegment(nullptr, imlSegment, false); #endif } void IMLRA_GenerateMoveInstructions(IMLRegisterAllocatorContext& ctx) { for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) { IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; IMLRA_GenerateSegmentMoveInstructions2(ctx, imlSegment); } } static void DbgVerifyFixedRegRequirements(IMLSegment* imlSegment) { #if DEBUG_RA_EXTRA_VALIDATION std::vector<raFixedRegRequirementWithVGPR> frr = IMLRA_BuildSegmentInstructionFixedRegList(imlSegment); for(auto& fixedReq : frr) { for (raLivenessRange* range = imlSegment->raInfo.linkedList_allSubranges; range; range = range->link_allSegmentRanges.next) { if (!range->interval2.ContainsEdge(fixedReq.pos)) continue; // verify if the requirement is compatible if(range->GetVirtualRegister() == fixedReq.regId) { cemu_assert(range->HasPhysicalRegister()); cemu_assert(fixedReq.allowedReg.IsAvailable(range->GetPhysicalRegister())); // virtual register matches, but not assigned the right physical register } else { cemu_assert(!fixedReq.allowedReg.IsAvailable(range->GetPhysicalRegister())); // virtual register does not match, but using the reserved physical register } } } #endif } static void DbgVerifyAllRanges(IMLRegisterAllocatorContext& ctx) { #if DEBUG_RA_EXTRA_VALIDATION for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) { IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; while (subrangeItr) { PPCRecRA_debugValidateSubrange(subrangeItr); subrangeItr = subrangeItr->link_allSegmentRanges.next; } } // check that no range validates register requirements for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) { DbgVerifyFixedRegRequirements(ctx.deprGenContext->segmentList2[s]); } #endif } void IMLRegisterAllocator_AllocateRegisters(ppcImlGenContext_t* ppcImlGenContext, IMLRegisterAllocatorParameters& raParam) { IMLRegisterAllocatorContext ctx; ctx.raParam = &raParam; ctx.deprGenContext = ppcImlGenContext; IMLRA_ReshapeForRegisterAllocation(ppcImlGenContext); ppcImlGenContext->UpdateSegmentIndices(); // update momentaryIndex of each segment ctx.perSegmentAbstractRanges.resize(ppcImlGenContext->segmentList2.size()); IMLRA_CalculateLivenessRanges(ctx); IMLRA_ProcessFlowAndCalculateLivenessRanges(ctx); IMLRA_AssignRegisters(ctx, ppcImlGenContext); DbgVerifyAllRanges(ctx); IMLRA_AnalyzeRangeDataFlow(ppcImlGenContext); IMLRA_GenerateMoveInstructions(ctx); IMLRA_DeleteAllRanges(ppcImlGenContext); } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h ================================================ #pragma once // container for storing a set of register indices // specifically optimized towards storing typical range of physical register indices (expected to be below 64) class IMLPhysRegisterSet { public: void SetAvailable(uint32 index) { cemu_assert_debug(index < 64); m_regBitmask |= ((uint64)1 << index); } void SetReserved(uint32 index) { cemu_assert_debug(index < 64); m_regBitmask &= ~((uint64)1 << index); } void SetAllAvailable() { m_regBitmask = ~0ull; } bool HasAllAvailable() const { return m_regBitmask == ~0ull; } bool IsAvailable(uint32 index) const { return (m_regBitmask & ((uint64)1 << index)) != 0; } IMLPhysRegisterSet& operator&=(const IMLPhysRegisterSet& other) { this->m_regBitmask &= other.m_regBitmask; return *this; } IMLPhysRegisterSet& operator=(const IMLPhysRegisterSet& other) { this->m_regBitmask = other.m_regBitmask; return *this; } void RemoveRegisters(const IMLPhysRegisterSet& other) { this->m_regBitmask &= ~other.m_regBitmask; } bool HasAnyAvailable() const { return m_regBitmask != 0; } bool HasExactlyOneAvailable() const { return m_regBitmask != 0 && (m_regBitmask & (m_regBitmask - 1)) == 0; } // returns index of first available register. Do not call when HasAnyAvailable() == false IMLPhysReg GetFirstAvailableReg() { cemu_assert_debug(m_regBitmask != 0); sint32 regIndex = 0; auto tmp = m_regBitmask; while ((tmp & 0xFF) == 0) { regIndex += 8; tmp >>= 8; } while ((tmp & 0x1) == 0) { regIndex++; tmp >>= 1; } return regIndex; } // returns index of next available register (search includes any register index >= startIndex) // returns -1 if there is no more register IMLPhysReg GetNextAvailableReg(sint32 startIndex) const { if (startIndex >= 64) return -1; uint32 regIndex = startIndex; auto tmp = m_regBitmask; tmp >>= regIndex; if (!tmp) return -1; while ((tmp & 0xFF) == 0) { regIndex += 8; tmp >>= 8; } while ((tmp & 0x1) == 0) { regIndex++; tmp >>= 1; } return regIndex; } sint32 CountAvailableRegs() const { return std::popcount(m_regBitmask); } private: uint64 m_regBitmask{ 0 }; }; struct IMLRegisterAllocatorParameters { inline IMLPhysRegisterSet& GetPhysRegPool(IMLRegFormat regFormat) { return perTypePhysPool[stdx::to_underlying(regFormat)]; } IMLPhysRegisterSet perTypePhysPool[stdx::to_underlying(IMLRegFormat::TYPE_COUNT)]; std::unordered_map<IMLRegID, IMLName> regIdToName; }; void IMLRegisterAllocator_AllocateRegisters(ppcImlGenContext_t* ppcImlGenContext, IMLRegisterAllocatorParameters& raParam); ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp ================================================ #include "../PPCRecompiler.h" #include "../PPCRecompilerIml.h" #include "IMLRegisterAllocatorRanges.h" #include "util/helpers/MemoryPool.h" uint32 IMLRA_GetNextIterationIndex(); IMLRegID raLivenessRange::GetVirtualRegister() const { return virtualRegister; } sint32 raLivenessRange::GetPhysicalRegister() const { return physicalRegister; } IMLName raLivenessRange::GetName() const { return name; } void raLivenessRange::SetPhysicalRegister(IMLPhysReg physicalRegister) { this->physicalRegister = physicalRegister; } void raLivenessRange::SetPhysicalRegisterForCluster(IMLPhysReg physicalRegister) { auto clusterRanges = GetAllSubrangesInCluster(); for(auto& range : clusterRanges) range->physicalRegister = physicalRegister; } boost::container::small_vector<raLivenessRange*, 128> raLivenessRange::GetAllSubrangesInCluster() { uint32 iterationIndex = IMLRA_GetNextIterationIndex(); boost::container::small_vector<raLivenessRange*, 128> subranges; subranges.push_back(this); this->lastIterationIndex = iterationIndex; size_t i = 0; while(i<subranges.size()) { raLivenessRange* cur = subranges[i]; i++; // check successors if(cur->subrangeBranchTaken && cur->subrangeBranchTaken->lastIterationIndex != iterationIndex) { cur->subrangeBranchTaken->lastIterationIndex = iterationIndex; subranges.push_back(cur->subrangeBranchTaken); } if(cur->subrangeBranchNotTaken && cur->subrangeBranchNotTaken->lastIterationIndex != iterationIndex) { cur->subrangeBranchNotTaken->lastIterationIndex = iterationIndex; subranges.push_back(cur->subrangeBranchNotTaken); } // check predecessors for(auto& prev : cur->previousRanges) { if(prev->lastIterationIndex != iterationIndex) { prev->lastIterationIndex = iterationIndex; subranges.push_back(prev); } } } return subranges; } void raLivenessRange::GetAllowedRegistersExRecursive(raLivenessRange* range, uint32 iterationIndex, IMLPhysRegisterSet& allowedRegs) { range->lastIterationIndex = iterationIndex; for (auto& it : range->list_fixedRegRequirements) allowedRegs &= it.allowedReg; // check successors if (range->subrangeBranchTaken && range->subrangeBranchTaken->lastIterationIndex != iterationIndex) GetAllowedRegistersExRecursive(range->subrangeBranchTaken, iterationIndex, allowedRegs); if (range->subrangeBranchNotTaken && range->subrangeBranchNotTaken->lastIterationIndex != iterationIndex) GetAllowedRegistersExRecursive(range->subrangeBranchNotTaken, iterationIndex, allowedRegs); // check predecessors for (auto& prev : range->previousRanges) { if (prev->lastIterationIndex != iterationIndex) GetAllowedRegistersExRecursive(prev, iterationIndex, allowedRegs); } }; bool raLivenessRange::GetAllowedRegistersEx(IMLPhysRegisterSet& allowedRegisters) { uint32 iterationIndex = IMLRA_GetNextIterationIndex(); allowedRegisters.SetAllAvailable(); GetAllowedRegistersExRecursive(this, iterationIndex, allowedRegisters); return !allowedRegisters.HasAllAvailable(); } IMLPhysRegisterSet raLivenessRange::GetAllowedRegisters(IMLPhysRegisterSet regPool) { IMLPhysRegisterSet fixedRegRequirements = regPool; if(interval.ExtendsPreviousSegment() || interval.ExtendsIntoNextSegment()) { auto clusterRanges = GetAllSubrangesInCluster(); for(auto& subrange : clusterRanges) { for(auto& fixedRegLoc : subrange->list_fixedRegRequirements) fixedRegRequirements &= fixedRegLoc.allowedReg; } return fixedRegRequirements; } for(auto& fixedRegLoc : list_fixedRegRequirements) fixedRegRequirements &= fixedRegLoc.allowedReg; return fixedRegRequirements; } void PPCRecRARange_addLink_perVirtualGPR(std::unordered_map<IMLRegID, raLivenessRange*>& root, raLivenessRange* subrange) { IMLRegID regId = subrange->GetVirtualRegister(); auto it = root.find(regId); if (it == root.end()) { // new single element root.try_emplace(regId, subrange); subrange->link_sameVirtualRegister.prev = nullptr; subrange->link_sameVirtualRegister.next = nullptr; } else { // insert in first position raLivenessRange* priorFirst = it->second; subrange->link_sameVirtualRegister.next = priorFirst; it->second = subrange; subrange->link_sameVirtualRegister.prev = nullptr; priorFirst->link_sameVirtualRegister.prev = subrange; } } void PPCRecRARange_addLink_allSegmentRanges(raLivenessRange** root, raLivenessRange* subrange) { subrange->link_allSegmentRanges.next = *root; if (*root) (*root)->link_allSegmentRanges.prev = subrange; subrange->link_allSegmentRanges.prev = nullptr; *root = subrange; } void PPCRecRARange_removeLink_perVirtualGPR(std::unordered_map<IMLRegID, raLivenessRange*>& root, raLivenessRange* subrange) { #ifdef CEMU_DEBUG_ASSERT raLivenessRange* cur = root.find(subrange->GetVirtualRegister())->second; bool hasRangeFound = false; while(cur) { if(cur == subrange) { hasRangeFound = true; break; } cur = cur->link_sameVirtualRegister.next; } cemu_assert_debug(hasRangeFound); #endif IMLRegID regId = subrange->GetVirtualRegister(); raLivenessRange* nextRange = subrange->link_sameVirtualRegister.next; raLivenessRange* prevRange = subrange->link_sameVirtualRegister.prev; raLivenessRange* newBase = prevRange ? prevRange : nextRange; if (prevRange) prevRange->link_sameVirtualRegister.next = subrange->link_sameVirtualRegister.next; if (nextRange) nextRange->link_sameVirtualRegister.prev = subrange->link_sameVirtualRegister.prev; if (!prevRange) { if (nextRange) { root.find(regId)->second = nextRange; } else { cemu_assert_debug(root.find(regId)->second == subrange); root.erase(regId); } } #ifdef CEMU_DEBUG_ASSERT subrange->link_sameVirtualRegister.prev = (raLivenessRange*)1; subrange->link_sameVirtualRegister.next = (raLivenessRange*)1; #endif } void PPCRecRARange_removeLink_allSegmentRanges(raLivenessRange** root, raLivenessRange* subrange) { raLivenessRange* tempPrev = subrange->link_allSegmentRanges.prev; if (subrange->link_allSegmentRanges.prev) subrange->link_allSegmentRanges.prev->link_allSegmentRanges.next = subrange->link_allSegmentRanges.next; else (*root) = subrange->link_allSegmentRanges.next; if (subrange->link_allSegmentRanges.next) subrange->link_allSegmentRanges.next->link_allSegmentRanges.prev = tempPrev; #ifdef CEMU_DEBUG_ASSERT subrange->link_allSegmentRanges.prev = (raLivenessRange*)1; subrange->link_allSegmentRanges.next = (raLivenessRange*)1; #endif } MemoryPoolPermanentObjects<raLivenessRange> memPool_livenessSubrange(4096); // startPosition and endPosition are inclusive raLivenessRange* IMLRA_CreateRange(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, IMLRegID virtualRegister, IMLName name, raInstructionEdge startPosition, raInstructionEdge endPosition) { raLivenessRange* range = memPool_livenessSubrange.acquireObj(); range->previousRanges.clear(); range->list_accessLocations.clear(); range->list_fixedRegRequirements.clear(); range->imlSegment = imlSegment; cemu_assert_debug(startPosition <= endPosition); range->interval.start = startPosition; range->interval.end = endPosition; // register mapping range->virtualRegister = virtualRegister; range->name = name; range->physicalRegister = -1; // default values range->hasStore = false; range->hasStoreDelayed = false; range->lastIterationIndex = 0; range->subrangeBranchNotTaken = nullptr; range->subrangeBranchTaken = nullptr; cemu_assert_debug(range->previousRanges.empty()); range->_noLoad = false; // add to segment linked lists PPCRecRARange_addLink_perVirtualGPR(imlSegment->raInfo.linkedList_perVirtualRegister, range); PPCRecRARange_addLink_allSegmentRanges(&imlSegment->raInfo.linkedList_allSubranges, range); return range; } void _unlinkSubrange(raLivenessRange* range) { IMLSegment* imlSegment = range->imlSegment; PPCRecRARange_removeLink_perVirtualGPR(imlSegment->raInfo.linkedList_perVirtualRegister, range); PPCRecRARange_removeLink_allSegmentRanges(&imlSegment->raInfo.linkedList_allSubranges, range); // unlink reverse references if(range->subrangeBranchTaken) range->subrangeBranchTaken->previousRanges.erase(std::find(range->subrangeBranchTaken->previousRanges.begin(), range->subrangeBranchTaken->previousRanges.end(), range)); if(range->subrangeBranchNotTaken) range->subrangeBranchNotTaken->previousRanges.erase(std::find(range->subrangeBranchNotTaken->previousRanges.begin(), range->subrangeBranchNotTaken->previousRanges.end(), range)); range->subrangeBranchTaken = (raLivenessRange*)(uintptr_t)-1; range->subrangeBranchNotTaken = (raLivenessRange*)(uintptr_t)-1; // remove forward references for(auto& prev : range->previousRanges) { if(prev->subrangeBranchTaken == range) prev->subrangeBranchTaken = nullptr; if(prev->subrangeBranchNotTaken == range) prev->subrangeBranchNotTaken = nullptr; } range->previousRanges.clear(); } void IMLRA_DeleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* range) { _unlinkSubrange(range); range->list_accessLocations.clear(); range->list_fixedRegRequirements.clear(); memPool_livenessSubrange.releaseObj(range); } void IMLRA_DeleteRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* range) { auto clusterRanges = range->GetAllSubrangesInCluster(); for (auto& subrange : clusterRanges) IMLRA_DeleteRange(ppcImlGenContext, subrange); } void IMLRA_DeleteAllRanges(ppcImlGenContext_t* ppcImlGenContext) { for(auto& seg : ppcImlGenContext->segmentList2) { raLivenessRange* cur; while ((cur = seg->raInfo.linkedList_allSubranges)) IMLRA_DeleteRange(ppcImlGenContext, cur); seg->raInfo.linkedList_allSubranges = nullptr; seg->raInfo.linkedList_perVirtualRegister.clear(); } } void IMLRA_MergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange, raLivenessRange* absorbedSubrange) { #ifdef CEMU_DEBUG_ASSERT PPCRecRA_debugValidateSubrange(subrange); PPCRecRA_debugValidateSubrange(absorbedSubrange); if (subrange->imlSegment != absorbedSubrange->imlSegment) assert_dbg(); cemu_assert_debug(subrange->interval.end == absorbedSubrange->interval.start); if (subrange->subrangeBranchTaken || subrange->subrangeBranchNotTaken) assert_dbg(); if (subrange == absorbedSubrange) assert_dbg(); #endif // update references subrange->subrangeBranchTaken = absorbedSubrange->subrangeBranchTaken; subrange->subrangeBranchNotTaken = absorbedSubrange->subrangeBranchNotTaken; absorbedSubrange->subrangeBranchTaken = nullptr; absorbedSubrange->subrangeBranchNotTaken = nullptr; if(subrange->subrangeBranchTaken) *std::find(subrange->subrangeBranchTaken->previousRanges.begin(), subrange->subrangeBranchTaken->previousRanges.end(), absorbedSubrange) = subrange; if(subrange->subrangeBranchNotTaken) *std::find(subrange->subrangeBranchNotTaken->previousRanges.begin(), subrange->subrangeBranchNotTaken->previousRanges.end(), absorbedSubrange) = subrange; // merge usage locations for (auto& accessLoc : absorbedSubrange->list_accessLocations) subrange->list_accessLocations.push_back(accessLoc); absorbedSubrange->list_accessLocations.clear(); // merge fixed reg locations #ifdef CEMU_DEBUG_ASSERT if(!subrange->list_fixedRegRequirements.empty() && !absorbedSubrange->list_fixedRegRequirements.empty()) { cemu_assert_debug(subrange->list_fixedRegRequirements.back().pos < absorbedSubrange->list_fixedRegRequirements.front().pos); } #endif for (auto& fixedReg : absorbedSubrange->list_fixedRegRequirements) subrange->list_fixedRegRequirements.push_back(fixedReg); absorbedSubrange->list_fixedRegRequirements.clear(); subrange->interval.end = absorbedSubrange->interval.end; PPCRecRA_debugValidateSubrange(subrange); IMLRA_DeleteRange(ppcImlGenContext, absorbedSubrange); } // remove all inter-segment connections from the range cluster and split it into local ranges. Ranges are trimmed and if they have no access location they will be removed void IMLRA_ExplodeRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* originRange) { cemu_assert_debug(originRange->interval.ExtendsPreviousSegment() || originRange->interval.ExtendsIntoNextSegment()); // only call this on ranges that span multiple segments auto clusterRanges = originRange->GetAllSubrangesInCluster(); for (auto& subrange : clusterRanges) { if (subrange->list_accessLocations.empty()) continue; raInterval interval; interval.SetInterval(subrange->list_accessLocations.front().pos, subrange->list_accessLocations.back().pos); raLivenessRange* newSubrange = IMLRA_CreateRange(ppcImlGenContext, subrange->imlSegment, subrange->GetVirtualRegister(), subrange->GetName(), interval.start, interval.end); // copy locations and fixed reg indices newSubrange->list_accessLocations = subrange->list_accessLocations; newSubrange->list_fixedRegRequirements = subrange->list_fixedRegRequirements; if(originRange->HasPhysicalRegister()) { cemu_assert_debug(subrange->list_fixedRegRequirements.empty()); // avoid unassigning a register from a range with a fixed register requirement } // validate if(!newSubrange->list_accessLocations.empty()) { cemu_assert_debug(newSubrange->list_accessLocations.front().pos >= newSubrange->interval.start); cemu_assert_debug(newSubrange->list_accessLocations.back().pos <= newSubrange->interval.end); } if(!newSubrange->list_fixedRegRequirements.empty()) { cemu_assert_debug(newSubrange->list_fixedRegRequirements.front().pos >= newSubrange->interval.start); // fixed register requirements outside of the actual access range probably means there is a mistake in GetInstructionFixedRegisters() cemu_assert_debug(newSubrange->list_fixedRegRequirements.back().pos <= newSubrange->interval.end); } } // delete the original range cluster IMLRA_DeleteRangeCluster(ppcImlGenContext, originRange); } #ifdef CEMU_DEBUG_ASSERT void PPCRecRA_debugValidateSubrange(raLivenessRange* range) { // validate subrange if (range->subrangeBranchTaken && range->subrangeBranchTaken->imlSegment != range->imlSegment->nextSegmentBranchTaken) assert_dbg(); if (range->subrangeBranchNotTaken && range->subrangeBranchNotTaken->imlSegment != range->imlSegment->nextSegmentBranchNotTaken) assert_dbg(); if(range->subrangeBranchTaken || range->subrangeBranchNotTaken) { cemu_assert_debug(range->interval.end.ConnectsToNextSegment()); } if(!range->previousRanges.empty()) { cemu_assert_debug(range->interval.start.ConnectsToPreviousSegment()); } // validate locations if (!range->list_accessLocations.empty()) { cemu_assert_debug(range->list_accessLocations.front().pos >= range->interval.start); cemu_assert_debug(range->list_accessLocations.back().pos <= range->interval.end); } // validate fixed reg requirements if (!range->list_fixedRegRequirements.empty()) { cemu_assert_debug(range->list_fixedRegRequirements.front().pos >= range->interval.start); cemu_assert_debug(range->list_fixedRegRequirements.back().pos <= range->interval.end); for(sint32 i = 0; i < (sint32)range->list_fixedRegRequirements.size()-1; i++) cemu_assert_debug(range->list_fixedRegRequirements[i].pos < range->list_fixedRegRequirements[i+1].pos); } } #else void PPCRecRA_debugValidateSubrange(raLivenessRange* range) {} #endif // trim start and end of range to match first and last read/write locations // does not trim start/endpoints which extend into the next/previous segment void IMLRA_TrimRangeToUse(raLivenessRange* range) { if(range->list_accessLocations.empty()) { // special case where we trim ranges extending from other segments to a single instruction edge cemu_assert_debug(!range->interval.start.IsInstructionIndex() || !range->interval.end.IsInstructionIndex()); if(range->interval.start.IsInstructionIndex()) range->interval.start = range->interval.end; if(range->interval.end.IsInstructionIndex()) range->interval.end = range->interval.start; return; } // trim start and end raInterval prevInterval = range->interval; if(range->interval.start.IsInstructionIndex()) range->interval.start = range->list_accessLocations.front().pos; if(range->interval.end.IsInstructionIndex()) range->interval.end = range->list_accessLocations.back().pos; // extra checks #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug(range->interval.start <= range->interval.end); for(auto& loc : range->list_accessLocations) { cemu_assert_debug(range->interval.ContainsEdge(loc.pos)); } cemu_assert_debug(prevInterval.ContainsWholeInterval(range->interval)); #endif } // split range at the given position // After the split there will be two ranges: // head -> subrange is shortened to end at splitIndex (exclusive) // tail -> a new subrange that ranges from splitIndex (inclusive) to the end of the original subrange // if head has a physical register assigned it will not carry over to tail // The return value is the tail range // If trimToUsage is true, the end of the head subrange and the start of the tail subrange will be shrunk to fit the read/write locations within. If there are no locations then the range will be deleted raLivenessRange* IMLRA_SplitRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange*& subrange, raInstructionEdge splitPosition, bool trimToUsage) { cemu_assert_debug(splitPosition.IsInstructionIndex()); cemu_assert_debug(!subrange->interval.IsNextSegmentOnly() && !subrange->interval.IsPreviousSegmentOnly()); cemu_assert_debug(subrange->interval.ContainsEdge(splitPosition)); // determine new intervals raInterval headInterval, tailInterval; headInterval.SetInterval(subrange->interval.start, splitPosition-1); tailInterval.SetInterval(splitPosition, subrange->interval.end); cemu_assert_debug(headInterval.start <= headInterval.end); cemu_assert_debug(tailInterval.start <= tailInterval.end); // create tail raLivenessRange* tailSubrange = IMLRA_CreateRange(ppcImlGenContext, subrange->imlSegment, subrange->GetVirtualRegister(), subrange->GetName(), tailInterval.start, tailInterval.end); tailSubrange->SetPhysicalRegister(subrange->GetPhysicalRegister()); // carry over branch targets and update reverse references tailSubrange->subrangeBranchTaken = subrange->subrangeBranchTaken; tailSubrange->subrangeBranchNotTaken = subrange->subrangeBranchNotTaken; subrange->subrangeBranchTaken = nullptr; subrange->subrangeBranchNotTaken = nullptr; if(tailSubrange->subrangeBranchTaken) *std::find(tailSubrange->subrangeBranchTaken->previousRanges.begin(), tailSubrange->subrangeBranchTaken->previousRanges.end(), subrange) = tailSubrange; if(tailSubrange->subrangeBranchNotTaken) *std::find(tailSubrange->subrangeBranchNotTaken->previousRanges.begin(), tailSubrange->subrangeBranchNotTaken->previousRanges.end(), subrange) = tailSubrange; // we assume that list_locations is ordered by instruction index and contains no duplicate indices, so lets check that here just in case #ifdef CEMU_DEBUG_ASSERT if(subrange->list_accessLocations.size() > 1) { for(size_t i=0; i<subrange->list_accessLocations.size()-1; i++) { cemu_assert_debug(subrange->list_accessLocations[i].pos < subrange->list_accessLocations[i+1].pos); } } #endif // split locations auto it = std::lower_bound( subrange->list_accessLocations.begin(), subrange->list_accessLocations.end(), splitPosition, [](const raAccessLocation& accessLoc, raInstructionEdge value) { return accessLoc.pos < value; } ); size_t originalCount = subrange->list_accessLocations.size(); tailSubrange->list_accessLocations.insert(tailSubrange->list_accessLocations.end(), it, subrange->list_accessLocations.end()); subrange->list_accessLocations.erase(it, subrange->list_accessLocations.end()); cemu_assert_debug(subrange->list_accessLocations.empty() || subrange->list_accessLocations.back().pos < splitPosition); cemu_assert_debug(tailSubrange->list_accessLocations.empty() || tailSubrange->list_accessLocations.front().pos >= splitPosition); cemu_assert_debug(subrange->list_accessLocations.size() + tailSubrange->list_accessLocations.size() == originalCount); // split fixed reg requirements for (sint32 i = 0; i < subrange->list_fixedRegRequirements.size(); i++) { raFixedRegRequirement* fixedReg = subrange->list_fixedRegRequirements.data() + i; if (tailInterval.ContainsEdge(fixedReg->pos)) { tailSubrange->list_fixedRegRequirements.push_back(*fixedReg); } } // remove tail fixed reg requirements from head for (sint32 i = 0; i < subrange->list_fixedRegRequirements.size(); i++) { raFixedRegRequirement* fixedReg = subrange->list_fixedRegRequirements.data() + i; if (!headInterval.ContainsEdge(fixedReg->pos)) { subrange->list_fixedRegRequirements.resize(i); break; } } // adjust intervals subrange->interval = headInterval; tailSubrange->interval = tailInterval; // trim to hole if(trimToUsage) { if(subrange->list_accessLocations.empty() && (subrange->interval.start.IsInstructionIndex() && subrange->interval.end.IsInstructionIndex())) { IMLRA_DeleteRange(ppcImlGenContext, subrange); subrange = nullptr; } else { IMLRA_TrimRangeToUse(subrange); } if(tailSubrange->list_accessLocations.empty() && (tailSubrange->interval.start.IsInstructionIndex() && tailSubrange->interval.end.IsInstructionIndex())) { IMLRA_DeleteRange(ppcImlGenContext, tailSubrange); tailSubrange = nullptr; } else { IMLRA_TrimRangeToUse(tailSubrange); } } // validation cemu_assert_debug(!subrange || subrange->interval.start <= subrange->interval.end); cemu_assert_debug(!tailSubrange || tailSubrange->interval.start <= tailSubrange->interval.end); cemu_assert_debug(!tailSubrange || tailSubrange->interval.start >= splitPosition); if (!trimToUsage) cemu_assert_debug(!tailSubrange || tailSubrange->interval.start == splitPosition); if(subrange) PPCRecRA_debugValidateSubrange(subrange); if(tailSubrange) PPCRecRA_debugValidateSubrange(tailSubrange); return tailSubrange; } sint32 IMLRA_GetSegmentReadWriteCost(IMLSegment* imlSegment) { sint32 v = imlSegment->loopDepth + 1; v *= 5; return v*v; // 25, 100, 225, 400 } // calculate additional cost of range that it would have after calling _ExplodeRange() on it sint32 IMLRA_CalculateAdditionalCostOfRangeExplode(raLivenessRange* subrange) { auto ranges = subrange->GetAllSubrangesInCluster(); sint32 cost = 0;//-PPCRecRARange_estimateTotalCost(ranges); for (auto& subrange : ranges) { if (subrange->list_accessLocations.empty()) continue; // this range would be deleted and thus has no cost sint32 segmentLoadStoreCost = IMLRA_GetSegmentReadWriteCost(subrange->imlSegment); bool hasAdditionalLoad = subrange->interval.ExtendsPreviousSegment(); bool hasAdditionalStore = subrange->interval.ExtendsIntoNextSegment(); if(hasAdditionalLoad && subrange->list_accessLocations.front().IsWrite()) // if written before read then a load isn't necessary { cemu_assert_debug(!subrange->list_accessLocations.front().IsRead()); cost += segmentLoadStoreCost; } if(hasAdditionalStore) { bool hasWrite = std::find_if(subrange->list_accessLocations.begin(), subrange->list_accessLocations.end(), [](const raAccessLocation& loc) { return loc.IsWrite(); }) != subrange->list_accessLocations.end(); if(!hasWrite) // ranges which don't modify their value do not need to be stored cost += segmentLoadStoreCost; } } // todo - properly calculating all the data-flow dependency based costs is more complex so this currently is an approximation return cost; } sint32 IMLRA_CalculateAdditionalCostAfterSplit(raLivenessRange* subrange, raInstructionEdge splitPosition) { // validation #ifdef CEMU_DEBUG_ASSERT if (subrange->interval.ExtendsIntoNextSegment()) assert_dbg(); #endif cemu_assert_debug(splitPosition.IsInstructionIndex()); sint32 cost = 0; // find split position in location list if (subrange->list_accessLocations.empty()) return 0; if (splitPosition <= subrange->list_accessLocations.front().pos) return 0; if (splitPosition > subrange->list_accessLocations.back().pos) return 0; size_t firstTailLocationIndex = 0; for (size_t i = 0; i < subrange->list_accessLocations.size(); i++) { if (subrange->list_accessLocations[i].pos >= splitPosition) { firstTailLocationIndex = i; break; } } std::span<raAccessLocation> headLocations{subrange->list_accessLocations.data(), firstTailLocationIndex}; std::span<raAccessLocation> tailLocations{subrange->list_accessLocations.data() + firstTailLocationIndex, subrange->list_accessLocations.size() - firstTailLocationIndex}; cemu_assert_debug(headLocations.empty() || headLocations.back().pos < splitPosition); cemu_assert_debug(tailLocations.empty() || tailLocations.front().pos >= splitPosition); sint32 segmentLoadStoreCost = IMLRA_GetSegmentReadWriteCost(subrange->imlSegment); auto CalculateCostFromLocationRange = [segmentLoadStoreCost](std::span<raAccessLocation> locations, bool trackLoadCost = true, bool trackStoreCost = true) -> sint32 { if(locations.empty()) return 0; sint32 cost = 0; if(locations.front().IsRead() && trackLoadCost) cost += segmentLoadStoreCost; // not overwritten, so there is a load cost bool hasWrite = std::find_if(locations.begin(), locations.end(), [](const raAccessLocation& loc) { return loc.IsWrite(); }) != locations.end(); if(hasWrite && trackStoreCost) cost += segmentLoadStoreCost; // modified, so there is a store cost return cost; }; sint32 baseCost = CalculateCostFromLocationRange(subrange->list_accessLocations); bool tailOverwritesValue = !tailLocations.empty() && !tailLocations.front().IsRead() && tailLocations.front().IsWrite(); sint32 newCost = CalculateCostFromLocationRange(headLocations) + CalculateCostFromLocationRange(tailLocations, !tailOverwritesValue, true); cemu_assert_debug(newCost >= baseCost); cost = newCost - baseCost; return cost; } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h ================================================ #pragma once #include "IMLRegisterAllocator.h" struct raLivenessSubrangeLink { struct raLivenessRange* prev; struct raLivenessRange* next; }; struct raInstructionEdge { friend struct raInterval; public: raInstructionEdge() { index = 0; } raInstructionEdge(sint32 instructionIndex, bool isInputEdge) { Set(instructionIndex, isInputEdge); } void Set(sint32 instructionIndex, bool isInputEdge) { if(instructionIndex == RA_INTER_RANGE_START || instructionIndex == RA_INTER_RANGE_END) { index = instructionIndex; return; } index = instructionIndex * 2 + (isInputEdge ? 0 : 1); cemu_assert_debug(index >= 0 && index < 0x100000*2); // make sure index value is sane } void SetRaw(sint32 index) { this->index = index; cemu_assert_debug(index == RA_INTER_RANGE_START || index == RA_INTER_RANGE_END || (index >= 0 && index < 0x100000*2)); // make sure index value is sane } // sint32 GetRaw() // { // this->index = index; // } std::string GetDebugString() { if(index == RA_INTER_RANGE_START) return "RA_START"; else if(index == RA_INTER_RANGE_END) return "RA_END"; std::string str = fmt::format("{}", GetInstructionIndex()); if(IsOnInputEdge()) str += "i"; else if(IsOnOutputEdge()) str += "o"; return str; } sint32 GetInstructionIndex() const { cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END); return index >> 1; } // returns instruction index or RA_INTER_RANGE_START/RA_INTER_RANGE_END sint32 GetInstructionIndexEx() const { if(index == RA_INTER_RANGE_START || index == RA_INTER_RANGE_END) return index; return index >> 1; } sint32 GetRaw() const { return index; } bool IsOnInputEdge() const { cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END); return (index&1) == 0; } bool IsOnOutputEdge() const { cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END); return (index&1) != 0; } bool ConnectsToPreviousSegment() const { return index == RA_INTER_RANGE_START; } bool ConnectsToNextSegment() const { return index == RA_INTER_RANGE_END; } bool IsInstructionIndex() const { return index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END; } // comparison operators bool operator>(const raInstructionEdge& other) const { return index > other.index; } bool operator<(const raInstructionEdge& other) const { return index < other.index; } bool operator<=(const raInstructionEdge& other) const { return index <= other.index; } bool operator>=(const raInstructionEdge& other) const { return index >= other.index; } bool operator==(const raInstructionEdge& other) const { return index == other.index; } raInstructionEdge operator+(sint32 offset) const { cemu_assert_debug(IsInstructionIndex()); cemu_assert_debug(offset >= 0 && offset < RA_INTER_RANGE_END); raInstructionEdge edge; edge.index = index + offset; return edge; } raInstructionEdge operator-(sint32 offset) const { cemu_assert_debug(IsInstructionIndex()); cemu_assert_debug(offset >= 0 && offset < RA_INTER_RANGE_END); raInstructionEdge edge; edge.index = index - offset; return edge; } raInstructionEdge& operator++() { cemu_assert_debug(IsInstructionIndex()); index++; return *this; } private: sint32 index; // can also be RA_INTER_RANGE_START or RA_INTER_RANGE_END, otherwise contains instruction index * 2 }; struct raAccessLocation { raAccessLocation(raInstructionEdge pos) : pos(pos) {} bool IsRead() const { return pos.IsOnInputEdge(); } bool IsWrite() const { return pos.IsOnOutputEdge(); } raInstructionEdge pos; }; struct raInterval { raInterval() { } raInterval(raInstructionEdge start, raInstructionEdge end) { SetInterval(start, end); } // isStartOnInput = Input+Output edge on first instruction. If false then only output // isEndOnOutput = Input+Output edge on last instruction. If false then only input void SetInterval(sint32 start, bool isStartOnInput, sint32 end, bool isEndOnOutput) { this->start.Set(start, isStartOnInput); this->end.Set(end, !isEndOnOutput); } void SetInterval(raInstructionEdge start, raInstructionEdge end) { cemu_assert_debug(start <= end); this->start = start; this->end = end; } void SetStart(const raInstructionEdge& edge) { start = edge; } void SetEnd(const raInstructionEdge& edge) { end = edge; } sint32 GetStartIndex() const { return start.GetInstructionIndex(); } sint32 GetEndIndex() const { return end.GetInstructionIndex(); } bool ExtendsPreviousSegment() const { return start.ConnectsToPreviousSegment(); } bool ExtendsIntoNextSegment() const { return end.ConnectsToNextSegment(); } bool IsNextSegmentOnly() const { return start.ConnectsToNextSegment() && end.ConnectsToNextSegment(); } bool IsPreviousSegmentOnly() const { return start.ConnectsToPreviousSegment() && end.ConnectsToPreviousSegment(); } // returns true if range is contained within a single segment bool IsLocal() const { return start.GetRaw() > RA_INTER_RANGE_START && end.GetRaw() < RA_INTER_RANGE_END; } bool ContainsInstructionIndex(sint32 instructionIndex) const { cemu_assert_debug(instructionIndex != RA_INTER_RANGE_START && instructionIndex != RA_INTER_RANGE_END); return instructionIndex >= start.GetInstructionIndexEx() && instructionIndex <= end.GetInstructionIndexEx(); } // similar to ContainsInstructionIndex, but allows RA_INTER_RANGE_START/END as input bool ContainsInstructionIndexEx(sint32 instructionIndex) const { if(instructionIndex == RA_INTER_RANGE_START) return start.ConnectsToPreviousSegment(); if(instructionIndex == RA_INTER_RANGE_END) return end.ConnectsToNextSegment(); return instructionIndex >= start.GetInstructionIndexEx() && instructionIndex <= end.GetInstructionIndexEx(); } bool ContainsEdge(const raInstructionEdge& edge) const { return edge >= start && edge <= end; } bool ContainsWholeInterval(const raInterval& other) const { return other.start >= start && other.end <= end; } bool IsOverlapping(const raInterval& other) const { return start <= other.end && end >= other.start; } sint32 GetPreciseDistance() { cemu_assert_debug(!start.ConnectsToNextSegment()); // how to handle this? if(start == end) return 1; cemu_assert_debug(!end.ConnectsToPreviousSegment() && !end.ConnectsToNextSegment()); if(start.ConnectsToPreviousSegment()) return end.GetRaw() + 1; return end.GetRaw() - start.GetRaw() + 1; // +1 because end is inclusive } //private: not making these directly accessible only forces us to create loads of verbose getters and setters raInstructionEdge start; raInstructionEdge end; }; struct raFixedRegRequirement { raInstructionEdge pos; IMLPhysRegisterSet allowedReg; }; struct raLivenessRange { IMLSegment* imlSegment; raInterval interval; // dirty state tracking bool _noLoad; bool hasStore; bool hasStoreDelayed; // next raLivenessRange* subrangeBranchTaken; raLivenessRange* subrangeBranchNotTaken; // reverse counterpart of BranchTaken/BranchNotTaken boost::container::small_vector<raLivenessRange*, 4> previousRanges; // processing uint32 lastIterationIndex; // instruction read/write locations std::vector<raAccessLocation> list_accessLocations; // ordered list of all raInstructionEdge indices which require a fixed register std::vector<raFixedRegRequirement> list_fixedRegRequirements; // linked list (subranges with same GPR virtual register) raLivenessSubrangeLink link_sameVirtualRegister; // linked list (all subranges for this segment) raLivenessSubrangeLink link_allSegmentRanges; // register info IMLRegID virtualRegister; IMLName name; // register allocator result IMLPhysReg physicalRegister; boost::container::small_vector<raLivenessRange*, 128> GetAllSubrangesInCluster(); bool GetAllowedRegistersEx(IMLPhysRegisterSet& allowedRegisters); // if the cluster has fixed register requirements in any instruction this returns the combined register mask. Otherwise returns false in which case allowedRegisters is left undefined IMLPhysRegisterSet GetAllowedRegisters(IMLPhysRegisterSet regPool); // return regPool with fixed register requirements filtered out IMLRegID GetVirtualRegister() const; sint32 GetPhysicalRegister() const; bool HasPhysicalRegister() const { return physicalRegister >= 0; } IMLName GetName() const; void SetPhysicalRegister(IMLPhysReg physicalRegister); void SetPhysicalRegisterForCluster(IMLPhysReg physicalRegister); void UnsetPhysicalRegister() { physicalRegister = -1; } private: void GetAllowedRegistersExRecursive(raLivenessRange* range, uint32 iterationIndex, IMLPhysRegisterSet& allowedRegs); }; raLivenessRange* IMLRA_CreateRange(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, IMLRegID virtualRegister, IMLName name, raInstructionEdge startPosition, raInstructionEdge endPosition); void IMLRA_DeleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange); void IMLRA_DeleteAllRanges(ppcImlGenContext_t* ppcImlGenContext); void IMLRA_ExplodeRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* originRange); void IMLRA_MergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange, raLivenessRange* absorbedSubrange); raLivenessRange* IMLRA_SplitRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange*& subrange, raInstructionEdge splitPosition, bool trimToUsage = false); void PPCRecRA_debugValidateSubrange(raLivenessRange* subrange); // cost estimation sint32 IMLRA_GetSegmentReadWriteCost(IMLSegment* imlSegment); sint32 IMLRA_CalculateAdditionalCostOfRangeExplode(raLivenessRange* subrange); //sint32 PPCRecRARange_estimateAdditionalCostAfterSplit(raLivenessRange* subrange, sint32 splitIndex); sint32 IMLRA_CalculateAdditionalCostAfterSplit(raLivenessRange* subrange, raInstructionEdge splitPosition); ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.cpp ================================================ #include "IMLInstruction.h" #include "IMLSegment.h" void IMLSegment::SetEnterable(uint32 enterAddress) { cemu_assert_debug(!isEnterable || enterPPCAddress == enterAddress); isEnterable = true; enterPPCAddress = enterAddress; } bool IMLSegment::HasSuffixInstruction() const { if (imlList.empty()) return false; const IMLInstruction& imlInstruction = imlList.back(); return imlInstruction.IsSuffixInstruction(); } sint32 IMLSegment::GetSuffixInstructionIndex() const { cemu_assert_debug(HasSuffixInstruction()); return (sint32)(imlList.size() - 1); } IMLInstruction* IMLSegment::GetLastInstruction() { if (imlList.empty()) return nullptr; return &imlList.back(); } void IMLSegment::SetLinkBranchNotTaken(IMLSegment* imlSegmentDst) { if (nextSegmentBranchNotTaken) nextSegmentBranchNotTaken->list_prevSegments.erase(std::find(nextSegmentBranchNotTaken->list_prevSegments.begin(), nextSegmentBranchNotTaken->list_prevSegments.end(), this)); nextSegmentBranchNotTaken = imlSegmentDst; if(imlSegmentDst) imlSegmentDst->list_prevSegments.push_back(this); } void IMLSegment::SetLinkBranchTaken(IMLSegment* imlSegmentDst) { if (nextSegmentBranchTaken) nextSegmentBranchTaken->list_prevSegments.erase(std::find(nextSegmentBranchTaken->list_prevSegments.begin(), nextSegmentBranchTaken->list_prevSegments.end(), this)); nextSegmentBranchTaken = imlSegmentDst; if (imlSegmentDst) imlSegmentDst->list_prevSegments.push_back(this); } IMLInstruction* IMLSegment::AppendInstruction() { IMLInstruction& inst = imlList.emplace_back(); memset(&inst, 0, sizeof(IMLInstruction)); return &inst; } void IMLSegment_SetLinkBranchNotTaken(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst) { // make sure segments aren't already linked if (imlSegmentSrc->nextSegmentBranchNotTaken == imlSegmentDst) return; // add as next segment for source if (imlSegmentSrc->nextSegmentBranchNotTaken != nullptr) assert_dbg(); imlSegmentSrc->nextSegmentBranchNotTaken = imlSegmentDst; // add as previous segment for destination imlSegmentDst->list_prevSegments.push_back(imlSegmentSrc); } void IMLSegment_SetLinkBranchTaken(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst) { // make sure segments aren't already linked if (imlSegmentSrc->nextSegmentBranchTaken == imlSegmentDst) return; // add as next segment for source if (imlSegmentSrc->nextSegmentBranchTaken != nullptr) assert_dbg(); imlSegmentSrc->nextSegmentBranchTaken = imlSegmentDst; // add as previous segment for destination imlSegmentDst->list_prevSegments.push_back(imlSegmentSrc); } void IMLSegment_RemoveLink(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst) { if (imlSegmentSrc->nextSegmentBranchNotTaken == imlSegmentDst) { imlSegmentSrc->nextSegmentBranchNotTaken = nullptr; } else if (imlSegmentSrc->nextSegmentBranchTaken == imlSegmentDst) { imlSegmentSrc->nextSegmentBranchTaken = nullptr; } else assert_dbg(); bool matchFound = false; for (sint32 i = 0; i < imlSegmentDst->list_prevSegments.size(); i++) { if (imlSegmentDst->list_prevSegments[i] == imlSegmentSrc) { imlSegmentDst->list_prevSegments.erase(imlSegmentDst->list_prevSegments.begin() + i); matchFound = true; break; } } if (matchFound == false) assert_dbg(); } /* * Replaces all links to segment orig with linkts to segment new */ void IMLSegment_RelinkInputSegment(IMLSegment* imlSegmentOrig, IMLSegment* imlSegmentNew) { while (imlSegmentOrig->list_prevSegments.size() != 0) { IMLSegment* prevSegment = imlSegmentOrig->list_prevSegments[0]; if (prevSegment->nextSegmentBranchNotTaken == imlSegmentOrig) { IMLSegment_RemoveLink(prevSegment, imlSegmentOrig); IMLSegment_SetLinkBranchNotTaken(prevSegment, imlSegmentNew); } else if (prevSegment->nextSegmentBranchTaken == imlSegmentOrig) { IMLSegment_RemoveLink(prevSegment, imlSegmentOrig); IMLSegment_SetLinkBranchTaken(prevSegment, imlSegmentNew); } else { assert_dbg(); } } } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h ================================================ #pragma once #include "IMLInstruction.h" #include <boost/container/small_vector.hpp> // special values to mark the index of ranges that reach across the segment border #define RA_INTER_RANGE_START (-1) #define RA_INTER_RANGE_END (0x70000000) struct IMLSegmentPoint { friend struct IMLSegmentInterval; sint32 index; struct IMLSegment* imlSegment; // do we really need to track this? SegmentPoints are always accessed via the segment that they are part of IMLSegmentPoint* next; IMLSegmentPoint* prev; // the index is the instruction index times two. // this gives us the ability to cover half an instruction with RA ranges // covering only the first half of an instruction (0-0) means that the register is read, but not preserved // covering first and the second half means the register is read and preserved // covering only the second half means the register is written but not read sint32 GetInstructionIndex() const { return index; } void SetInstructionIndex(sint32 index) { this->index = index; } void ShiftIfAfter(sint32 instructionIndex, sint32 shiftCount) { if (!IsPreviousSegment() && !IsNextSegment()) { if (GetInstructionIndex() >= instructionIndex) index += shiftCount; } } void DecrementByOneInstruction() { index--; } // the segment point can point beyond the first and last instruction which indicates that it is an infinite range reaching up to the previous or next segment bool IsPreviousSegment() const { return index == RA_INTER_RANGE_START; } bool IsNextSegment() const { return index == RA_INTER_RANGE_END; } // overload operand > and < bool operator>(const IMLSegmentPoint& other) const { return index > other.index; } bool operator<(const IMLSegmentPoint& other) const { return index < other.index; } bool operator==(const IMLSegmentPoint& other) const { return index == other.index; } bool operator!=(const IMLSegmentPoint& other) const { return index != other.index; } // overload comparison operands for sint32 bool operator>(const sint32 other) const { return index > other; } bool operator<(const sint32 other) const { return index < other; } bool operator<=(const sint32 other) const { return index <= other; } bool operator>=(const sint32 other) const { return index >= other; } }; struct IMLSegmentInterval { IMLSegmentPoint start; IMLSegmentPoint end; bool ContainsInstructionIndex(sint32 offset) const { return start <= offset && end > offset; } bool IsRangeOverlapping(const IMLSegmentInterval& other) { // todo - compare the raw index sint32 r1start = this->start.GetInstructionIndex(); sint32 r1end = this->end.GetInstructionIndex(); sint32 r2start = other.start.GetInstructionIndex(); sint32 r2end = other.end.GetInstructionIndex(); if (r1start < r2end && r1end > r2start) return true; if (this->start.IsPreviousSegment() && r1start == r2start) return true; if (this->end.IsNextSegment() && r1end == r2end) return true; return false; } bool ExtendsIntoPreviousSegment() const { return start.IsPreviousSegment(); } bool ExtendsIntoNextSegment() const { return end.IsNextSegment(); } bool IsNextSegmentOnly() const { if(!start.IsNextSegment()) return false; cemu_assert_debug(end.IsNextSegment()); return true; } bool IsPreviousSegmentOnly() const { if (!end.IsPreviousSegment()) return false; cemu_assert_debug(start.IsPreviousSegment()); return true; } sint32 GetDistance() const { // todo - assert if either start or end is outside the segment // we may also want to switch this to raw indices? return end.GetInstructionIndex() - start.GetInstructionIndex(); } }; struct PPCSegmentRegisterAllocatorInfo_t { // used during loop detection bool isPartOfProcessedLoop{}; sint32 lastIterationIndex{}; // linked lists struct raLivenessRange* linkedList_allSubranges{}; std::unordered_map<IMLRegID, struct raLivenessRange*> linkedList_perVirtualRegister; }; struct IMLSegment { sint32 momentaryIndex{}; // index in segment list, generally not kept up to date except if needed (necessary for loop detection) sint32 loopDepth{}; uint32 ppcAddress{}; // ppc address (0xFFFFFFFF if not associated with an address) uint32 x64Offset{}; // x64 code offset of segment start // list of intermediate instructions in this segment std::vector<IMLInstruction> imlList; // segment link IMLSegment* nextSegmentBranchNotTaken{}; // this is also the default for segments where there is no branch IMLSegment* nextSegmentBranchTaken{}; bool nextSegmentIsUncertain{}; std::vector<IMLSegment*> list_prevSegments{}; // source for overwrite analysis (if nextSegmentIsUncertain is true) // sometimes a segment is marked as an exit point, but for the purposes of dead code elimination we know the next segment IMLSegment* deadCodeEliminationHintSeg{}; std::vector<IMLSegment*> list_deadCodeHintBy{}; // enterable segments bool isEnterable{}; // this segment can be entered from outside the recompiler (no preloaded registers necessary) uint32 enterPPCAddress{}; // used if isEnterable is true // register allocator info PPCSegmentRegisterAllocatorInfo_t raInfo{}; // segment state API void SetEnterable(uint32 enterAddress); void SetLinkBranchNotTaken(IMLSegment* imlSegmentDst); void SetLinkBranchTaken(IMLSegment* imlSegmentDst); IMLSegment* GetBranchTaken() { return nextSegmentBranchTaken; } IMLSegment* GetBranchNotTaken() { return nextSegmentBranchNotTaken; } void SetNextSegmentForOverwriteHints(IMLSegment* seg) { cemu_assert_debug(!deadCodeEliminationHintSeg); deadCodeEliminationHintSeg = seg; if (seg) seg->list_deadCodeHintBy.push_back(this); } // instruction API IMLInstruction* AppendInstruction(); bool HasSuffixInstruction() const; sint32 GetSuffixInstructionIndex() const; IMLInstruction* GetLastInstruction(); // segment points IMLSegmentPoint* segmentPointList{}; }; void IMLSegment_SetLinkBranchNotTaken(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst); void IMLSegment_SetLinkBranchTaken(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst); void IMLSegment_RelinkInputSegment(IMLSegment* imlSegmentOrig, IMLSegment* imlSegmentNew); void IMLSegment_RemoveLink(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst); ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h ================================================ #pragma once #include "Cafe/HW/Espresso/EspressoISA.h" #include "Cafe/HW/MMU/MMU.h" bool GamePatch_IsNonReturnFunction(uint32 hleIndex); // utility class to determine shape of a function class PPCFunctionBoundaryTracker { public: struct PPCRange_t { PPCRange_t() = default; PPCRange_t(uint32 _startAddress) : startAddress(_startAddress) {}; uint32 startAddress{}; uint32 length{}; //bool isProcessed{false}; uint32 getEndAddress() const { return startAddress + length; }; }; public: ~PPCFunctionBoundaryTracker() { while (!map_ranges.empty()) { PPCRange_t* range = *map_ranges.begin(); delete range; map_ranges.erase(map_ranges.begin()); } } void trackStartPoint(MPTR startAddress) { processRange(startAddress, nullptr, nullptr); processBranchTargets(); } bool getRangeForAddress(uint32 address, PPCRange_t& range) { for (auto itr : map_ranges) { if (address >= itr->startAddress && address < (itr->startAddress + itr->length)) { range = *itr; return true; } } return false; } std::vector<PPCRange_t> GetRanges() { std::vector<PPCRange_t> r; for (auto& it : map_ranges) r.emplace_back(*it); return r; } bool ContainsAddress(uint32 addr) const { for (auto& it : map_ranges) { if (addr >= it->startAddress && addr < it->getEndAddress()) return true; } return false; } const std::set<uint32>& GetBranchTargets() const { return map_branchTargetsAll; } private: void addBranchDestination(PPCRange_t* sourceRange, MPTR address) { map_queuedBranchTargets.emplace(address); map_branchTargetsAll.emplace(address); } // process flow of instruction // returns false if the IP cannot increment past the current instruction bool processInstruction(PPCRange_t* range, MPTR address) { // parse instructions uint32 opcode = memory_readU32(address); switch (Espresso::GetPrimaryOpcode(opcode)) { case Espresso::PrimaryOpcode::ZERO: { if (opcode == 0) return false; // invalid instruction break; } case Espresso::PrimaryOpcode::VIRTUAL_HLE: { // end of function // is there a jump to a instruction after this one? uint32 hleFuncId = opcode & 0xFFFF; if (hleFuncId >= 0x1000 && hleFuncId < 0x4000) { if (GamePatch_IsNonReturnFunction(hleFuncId - 0x1000) == false) { return true; } } return false; } case Espresso::PrimaryOpcode::BC: { uint32 BD, BI; Espresso::BOField BO; bool AA, LK; Espresso::decodeOp_BC(opcode, BD, BO, BI, AA, LK); uint32 branchTarget = AA ? BD : BD + address; if (!LK) addBranchDestination(range, branchTarget); break; } case Espresso::PrimaryOpcode::B: { uint32 LI; bool AA, LK; Espresso::decodeOp_B(opcode, LI, AA, LK); uint32 branchTarget = AA ? LI : LI + address; if (!LK) { addBranchDestination(range, branchTarget); // if the next two or previous two instructions are branch instructions, we assume that they are destinations of a jump table // todo - can we make this more reliable by checking for BCTR or similar instructions first? // example: The Swapper 0x01B1FC04 if (PPCRecompilerCalcFuncSize_isUnconditionalBranchInstruction(memory_readU32(address + 4)) && PPCRecompilerCalcFuncSize_isUnconditionalBranchInstruction(memory_readU32(address + 8)) || PPCRecompilerCalcFuncSize_isUnconditionalBranchInstruction(memory_readU32(address - 8)) && PPCRecompilerCalcFuncSize_isUnconditionalBranchInstruction(memory_readU32(address - 4))) { return true; } return false; // current flow ends at unconditional branch instruction } break; } case Espresso::PrimaryOpcode::GROUP_19: switch (Espresso::GetGroup19Opcode(opcode)) { case Espresso::Opcode19::BCLR: { Espresso::BOField BO; uint32 BI; bool LK; Espresso::decodeOp_BCSPR(opcode, BO, BI, LK); if (BO.branchAlways() && !LK) { // unconditional BLR return false; } break; } case Espresso::Opcode19::BCCTR: if (opcode == 0x4E800420) { // unconditional BCTR // this instruction is often used for switch statements, therefore we should be wary of ending the function here // It's better to overestimate function size than to predict sizes that are too short // Currently we only end the function if the BCTR is followed by a NOP (alignment) or invalid instruction // todo: improve robustness, find better ways to handle false positives uint32 nextOpcode = memory_readU32(address + 4); if (nextOpcode == 0x60000000 || PPCRecompilerCalcFuncSize_isValidInstruction(nextOpcode) == false) { return false; } return true; } // conditional BCTR return true; default: break; } break; default: break; } return true; } void checkForCollisions() { #ifdef CEMU_DEBUG_ASSERT uint32 endOfPrevious = 0; for (auto itr : map_ranges) { if (endOfPrevious > itr->startAddress) { cemu_assert_debug(false); } endOfPrevious = itr->startAddress + itr->length; } #endif } // nextRange must point to the closest range after startAddress, or NULL if there is none void processRange(MPTR startAddress, PPCRange_t* previousRange, PPCRange_t* nextRange) { checkForCollisions(); cemu_assert_debug(previousRange == nullptr || (startAddress == (previousRange->startAddress + previousRange->length))); PPCRange_t* newRange; if (previousRange && (previousRange->startAddress + previousRange->length) == startAddress) { newRange = previousRange; } else { cemu_assert_debug(previousRange == nullptr); newRange = new PPCRange_t(startAddress); map_ranges.emplace(newRange); } // process instruction flow until it is interrupted by a non-conditional branch MPTR currentAddress = startAddress; MPTR endAddress = 0xFFFFFFFF; if (nextRange) endAddress = nextRange->startAddress; while (currentAddress < endAddress) { if (!processInstruction(newRange, currentAddress)) { currentAddress += 4; break; } currentAddress += 4; } newRange->length = currentAddress - newRange->startAddress; if (nextRange && currentAddress >= nextRange->startAddress) { // merge with next range newRange->length = (nextRange->startAddress + nextRange->length) - newRange->startAddress; map_ranges.erase(nextRange); delete nextRange; checkForCollisions(); return; } checkForCollisions(); } // find first unvisited branch target and start a new range there // return true if method should be called again bool processBranchTargetsSinglePass() { cemu_assert_debug(!map_ranges.empty()); auto rangeItr = map_ranges.begin(); PPCRange_t* previousRange = nullptr; for (std::set<uint32_t>::const_iterator targetItr = map_queuedBranchTargets.begin() ; targetItr != map_queuedBranchTargets.end(); ) { while (rangeItr != map_ranges.end() && ((*rangeItr)->startAddress + (*rangeItr)->length) <= (*targetItr)) { previousRange = *rangeItr; rangeItr++; if (rangeItr == map_ranges.end()) { // last range reached if ((previousRange->startAddress + previousRange->length) == *targetItr) processRange(*targetItr, previousRange, nullptr); else processRange(*targetItr, nullptr, nullptr); return true; } } if ((*targetItr) >= (*rangeItr)->startAddress && (*targetItr) < ((*rangeItr)->startAddress + (*rangeItr)->length)) { // delete visited targets targetItr = map_queuedBranchTargets.erase(targetItr); continue; } cemu_assert_debug((*rangeItr)->startAddress > (*targetItr)); if (previousRange && (previousRange->startAddress + previousRange->length) == *targetItr) processRange(*targetItr, previousRange, *rangeItr); // extend previousRange else processRange(*targetItr, nullptr, *rangeItr); return true; } return false; } void processBranchTargets() { while (processBranchTargetsSinglePass()); } private: bool PPCRecompilerCalcFuncSize_isUnconditionalBranchInstruction(uint32 opcode) { if (Espresso::GetPrimaryOpcode(opcode) == Espresso::PrimaryOpcode::B) { uint32 LI; bool AA, LK; Espresso::decodeOp_B(opcode, LI, AA, LK); if (!LK) return true; } return false; } bool PPCRecompilerCalcFuncSize_isValidInstruction(uint32 opcode) { if ((opcode >> 26) == 0) return false; return true; } private: struct RangePtrCmp { bool operator()(const PPCRange_t* lhs, const PPCRange_t* rhs) const { return lhs->startAddress < rhs->startAddress; } }; std::set<PPCRange_t*, RangePtrCmp> map_ranges; std::set<uint32> map_queuedBranchTargets; std::set<uint32> map_branchTargetsAll; }; ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp ================================================ #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "PPCFunctionBoundaryTracker.h" #include "PPCRecompiler.h" #include "PPCRecompilerIml.h" #include "Cafe/OS/RPL/rpl.h" #include "util/containers/RangeStore.h" #include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h" #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" #include "Common/ExceptionHandler/ExceptionHandler.h" #include "Common/cpu_features.h" #include "util/helpers/fspinlock.h" #include "util/helpers/helpers.h" #include "util/MemMapper/MemMapper.h" #include "IML/IML.h" #include "IML/IMLRegisterAllocator.h" #include "BackendX64/BackendX64.h" #ifdef __aarch64__ #include "BackendAArch64/BackendAArch64.h" #endif #include "util/highresolutiontimer/HighResolutionTimer.h" #define PPCREC_FORCE_SYNCHRONOUS_COMPILATION 0 // if 1, then function recompilation will block and execute on the thread that called PPCRecompiler_visitAddressNoBlock #define PPCREC_LOG_RECOMPILATION_RESULTS 0 struct PPCInvalidationRange { MPTR startAddress; uint32 size; PPCInvalidationRange(MPTR _startAddress, uint32 _size) : startAddress(_startAddress), size(_size) {}; }; struct { FSpinlock recompilerSpinlock; std::queue<MPTR> targetQueue; std::vector<PPCInvalidationRange> invalidationRanges; }PPCRecompilerState; RangeStore<PPCRecFunction_t*, uint32, 7703, 0x2000> rangeStore_ppcRanges; void ATTR_MS_ABI (*PPCRecompiler_enterRecompilerCode)(uint64 codeMem, uint64 ppcInterpreterInstance); void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_visited)(); void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_unvisited)(); PPCRecompilerInstanceData_t* ppcRecompilerInstanceData; #if PPCREC_FORCE_SYNCHRONOUS_COMPILATION static std::mutex s_singleRecompilationMutex; #endif bool ppcRecompilerEnabled = false; void PPCRecompiler_recompileAtAddress(uint32 address); // this function does never block and can fail if the recompiler lock cannot be acquired immediately void PPCRecompiler_visitAddressNoBlock(uint32 enterAddress) { #if PPCREC_FORCE_SYNCHRONOUS_COMPILATION if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited) return; PPCRecompilerState.recompilerSpinlock.lock(); if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited) { PPCRecompilerState.recompilerSpinlock.unlock(); return; } ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] = PPCRecompiler_leaveRecompilerCode_visited; PPCRecompilerState.recompilerSpinlock.unlock(); s_singleRecompilationMutex.lock(); if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] == PPCRecompiler_leaveRecompilerCode_visited) { PPCRecompiler_recompileAtAddress(enterAddress); } s_singleRecompilationMutex.unlock(); return; #endif // quick read-only check without lock if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited) return; // try to acquire lock if (!PPCRecompilerState.recompilerSpinlock.try_lock()) return; auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4]; if (funcPtr != PPCRecompiler_leaveRecompilerCode_unvisited) { // was visited since previous check PPCRecompilerState.recompilerSpinlock.unlock(); return; } // add to recompilation queue and flag as visited PPCRecompilerState.targetQueue.emplace(enterAddress); ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] = PPCRecompiler_leaveRecompilerCode_visited; PPCRecompilerState.recompilerSpinlock.unlock(); } void PPCRecompiler_recompileIfUnvisited(uint32 enterAddress) { if (ppcRecompilerEnabled == false) return; PPCRecompiler_visitAddressNoBlock(enterAddress); } void PPCRecompiler_enter(PPCInterpreter_t* hCPU, PPCREC_JUMP_ENTRY funcPtr) { #if BOOST_OS_WINDOWS uint32 prevState = _controlfp(0, 0); _controlfp(_RC_NEAR, _MCW_RC); PPCRecompiler_enterRecompilerCode((uint64)funcPtr, (uint64)hCPU); _controlfp(prevState, _MCW_RC); // debug recompiler exit - useful to find frequently executed functions which couldn't be recompiled #ifdef CEMU_DEBUG_ASSERT if (hCPU->remainingCycles > 0 && GetAsyncKeyState(VK_F4)) { auto t = std::chrono::high_resolution_clock::now(); auto dur = std::chrono::duration_cast<std::chrono::microseconds>(t.time_since_epoch()).count(); cemuLog_log(LogType::Force, "Recompiler exit: 0x{:08x} LR: 0x{:08x} Timestamp {}.{:04}", hCPU->instructionPointer, hCPU->spr.LR, dur / 1000LL, (dur % 1000LL)); } #endif #else PPCRecompiler_enterRecompilerCode((uint64)funcPtr, (uint64)hCPU); #endif // after leaving recompiler prematurely attempt to recompile the code at the new location if (hCPU->remainingCycles > 0) { PPCRecompiler_visitAddressNoBlock(hCPU->instructionPointer); } } void PPCRecompiler_attemptEnterWithoutRecompile(PPCInterpreter_t* hCPU, uint32 enterAddress) { cemu_assert_debug(hCPU->instructionPointer == enterAddress); if (ppcRecompilerEnabled == false) return; auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4]; if (funcPtr != PPCRecompiler_leaveRecompilerCode_unvisited && funcPtr != PPCRecompiler_leaveRecompilerCode_visited) { cemu_assert_debug(ppcRecompilerInstanceData != nullptr); PPCRecompiler_enter(hCPU, funcPtr); } } void PPCRecompiler_attemptEnter(PPCInterpreter_t* hCPU, uint32 enterAddress) { cemu_assert_debug(hCPU->instructionPointer == enterAddress); if (ppcRecompilerEnabled == false) return; if (hCPU->remainingCycles <= 0) return; auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4]; if (funcPtr == PPCRecompiler_leaveRecompilerCode_unvisited) { PPCRecompiler_visitAddressNoBlock(enterAddress); } else if (funcPtr != PPCRecompiler_leaveRecompilerCode_visited) { // enter cemu_assert_debug(ppcRecompilerInstanceData != nullptr); PPCRecompiler_enter(hCPU, funcPtr); } } bool PPCRecompiler_ApplyIMLPasses(ppcImlGenContext_t& ppcImlGenContext); PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PPCRange_t range, std::set<uint32>& entryAddresses, std::vector<std::pair<MPTR, uint32>>& entryPointsOut, PPCFunctionBoundaryTracker& boundaryTracker) { if (range.startAddress >= PPC_REC_CODE_AREA_END) { cemuLog_log(LogType::Force, "Attempting to recompile function outside of allowed code area"); return nullptr; } uint32 codeGenRangeStart; uint32 codeGenRangeSize = 0; coreinit::OSGetCodegenVirtAddrRangeInternal(codeGenRangeStart, codeGenRangeSize); if (codeGenRangeSize != 0) { if (range.startAddress >= codeGenRangeStart && range.startAddress < (codeGenRangeStart + codeGenRangeSize)) { if (coreinit::codeGenShouldAvoid()) { return nullptr; } } } PPCRecFunction_t* ppcRecFunc = new PPCRecFunction_t(); ppcRecFunc->ppcAddress = range.startAddress; ppcRecFunc->ppcSize = range.length; #if PPCREC_LOG_RECOMPILATION_RESULTS BenchmarkTimer bt; bt.Start(); #endif // generate intermediate code ppcImlGenContext_t ppcImlGenContext = { 0 }; ppcImlGenContext.debug_entryPPCAddress = range.startAddress; bool compiledSuccessfully = PPCRecompiler_generateIntermediateCode(ppcImlGenContext, ppcRecFunc, entryAddresses, boundaryTracker); if (compiledSuccessfully == false) { delete ppcRecFunc; return nullptr; } uint32 ppcRecLowerAddr = LaunchSettings::GetPPCRecLowerAddr(); uint32 ppcRecUpperAddr = LaunchSettings::GetPPCRecUpperAddr(); if (ppcRecLowerAddr != 0 && ppcRecUpperAddr != 0) { if (ppcRecFunc->ppcAddress < ppcRecLowerAddr || ppcRecFunc->ppcAddress > ppcRecUpperAddr) { delete ppcRecFunc; return nullptr; } } // apply passes if (!PPCRecompiler_ApplyIMLPasses(ppcImlGenContext)) { delete ppcRecFunc; return nullptr; } #if defined(ARCH_X86_64) // emit x64 code bool x64GenerationSuccess = PPCRecompiler_generateX64Code(ppcRecFunc, &ppcImlGenContext); if (x64GenerationSuccess == false) { return nullptr; } #elif defined(__aarch64__) bool aarch64GenerationSuccess = PPCRecompiler_generateAArch64Code(ppcRecFunc, &ppcImlGenContext); if (aarch64GenerationSuccess == false) { return nullptr; } #endif if (ActiveSettings::DumpRecompilerFunctionsEnabled()) { FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath(fmt::format("dump/recompiler/ppc_{:08x}.bin", ppcRecFunc->ppcAddress))); if (fs) { fs->writeData(ppcRecFunc->x86Code, ppcRecFunc->x86Size); delete fs; } } // collect list of PPC-->x64 entry points entryPointsOut.clear(); for(IMLSegment* imlSegment : ppcImlGenContext.segmentList2) { if (imlSegment->isEnterable == false) continue; uint32 ppcEnterOffset = imlSegment->enterPPCAddress; uint32 x64Offset = imlSegment->x64Offset; entryPointsOut.emplace_back(ppcEnterOffset, x64Offset); } #if PPCREC_LOG_RECOMPILATION_RESULTS bt.Stop(); uint32 codeHash = 0; for (uint32 i = 0; i < ppcRecFunc->x86Size; i++) { codeHash = _rotr(codeHash, 3); codeHash += ((uint8*)ppcRecFunc->x86Code)[i]; } cemuLog_log(LogType::Force, "[Recompiler] PPC 0x{:08x} -> x64: 0x{:x} Took {:.4}ms | Size {:04x} CodeHash {:08x}", (uint32)ppcRecFunc->ppcAddress, (uint64)(uintptr_t)ppcRecFunc->x86Code, bt.GetElapsedMilliseconds(), ppcRecFunc->x86Size, codeHash); #endif return ppcRecFunc; } void PPCRecompiler_NativeRegisterAllocatorPass(ppcImlGenContext_t& ppcImlGenContext) { IMLRegisterAllocatorParameters raParam; for (auto& it : ppcImlGenContext.mappedRegs) raParam.regIdToName.try_emplace(it.second.GetRegID(), it.first); #if defined(ARCH_X86_64) auto& gprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::I64); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RAX); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDX); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RBX); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RBP); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RSI); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDI); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R8); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R9); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R10); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R11); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R12); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RCX); // add XMM registers, except XMM15 which is the temporary register auto& fprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::F64); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 0); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 1); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 2); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 3); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 4); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 5); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 6); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 7); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 8); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 9); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 10); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 11); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 12); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 13); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 14); #elif defined(__aarch64__) auto& gprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::I64); for (auto i = IMLArchAArch64::PHYSREG_GPR_BASE; i < IMLArchAArch64::PHYSREG_GPR_BASE + IMLArchAArch64::PHYSREG_GPR_COUNT; i++) { if (i == IMLArchAArch64::PHYSREG_GPR_BASE + 18) continue; // Skip reserved platform register gprPhysPool.SetAvailable(i); } auto& fprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::F64); for (auto i = IMLArchAArch64::PHYSREG_FPR_BASE; i < IMLArchAArch64::PHYSREG_FPR_BASE + IMLArchAArch64::PHYSREG_FPR_COUNT; i++) fprPhysPool.SetAvailable(i); #endif IMLRegisterAllocator_AllocateRegisters(&ppcImlGenContext, raParam); } bool PPCRecompiler_ApplyIMLPasses(ppcImlGenContext_t& ppcImlGenContext) { // isolate entry points from function flow (enterable segments must not be the target of any other segment) // this simplifies logic during register allocation PPCRecompilerIML_isolateEnterableSegments(&ppcImlGenContext); // merge certain float load+store patterns IMLOptimizer_OptimizeDirectFloatCopies(&ppcImlGenContext); // delay byte swapping for certain load+store patterns IMLOptimizer_OptimizeDirectIntegerCopies(&ppcImlGenContext); IMLOptimizer_StandardOptimizationPass(ppcImlGenContext); PPCRecompiler_NativeRegisterAllocatorPass(ppcImlGenContext); return true; } bool PPCRecompiler_makeRecompiledFunctionActive(uint32 initialEntryPoint, PPCFunctionBoundaryTracker::PPCRange_t& range, PPCRecFunction_t* ppcRecFunc, std::vector<std::pair<MPTR, uint32>>& entryPoints) { // update jump table PPCRecompilerState.recompilerSpinlock.lock(); // check if the initial entrypoint is still flagged for recompilation // its possible that the range has been invalidated during the time it took to translate the function if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[initialEntryPoint / 4] != PPCRecompiler_leaveRecompilerCode_visited) { PPCRecompilerState.recompilerSpinlock.unlock(); return false; } // check if the current range got invalidated during the time it took to recompile it bool isInvalidated = false; for (auto& invRange : PPCRecompilerState.invalidationRanges) { MPTR rStartAddr = invRange.startAddress; MPTR rEndAddr = rStartAddr + invRange.size; for (auto& recFuncRange : ppcRecFunc->list_ranges) { if (recFuncRange.ppcAddress < (rEndAddr) && (recFuncRange.ppcAddress + recFuncRange.ppcSize) >= rStartAddr) { isInvalidated = true; break; } } } PPCRecompilerState.invalidationRanges.clear(); if (isInvalidated) { PPCRecompilerState.recompilerSpinlock.unlock(); return false; } // update jump table for (auto& itr : entryPoints) { ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[itr.first / 4] = (PPCREC_JUMP_ENTRY)((uint8*)ppcRecFunc->x86Code + itr.second); } // due to inlining, some entrypoints can get optimized away // therefore we reset all addresses that are still marked as visited (but not recompiled) // we dont remove the points from the queue but any address thats not marked as visited won't get recompiled // if they are reachable, the interpreter will queue them again for (uint32 v = range.startAddress; v <= (range.startAddress + range.length); v += 4) { auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[v / 4]; if (funcPtr == PPCRecompiler_leaveRecompilerCode_visited) ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[v / 4] = PPCRecompiler_leaveRecompilerCode_unvisited; } // register ranges for (auto& r : ppcRecFunc->list_ranges) { r.storedRange = rangeStore_ppcRanges.storeRange(ppcRecFunc, r.ppcAddress, r.ppcAddress + r.ppcSize); } PPCRecompilerState.recompilerSpinlock.unlock(); return true; } void PPCRecompiler_recompileAtAddress(uint32 address) { cemu_assert_debug(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[address / 4] == PPCRecompiler_leaveRecompilerCode_visited); // get size PPCFunctionBoundaryTracker funcBoundaries; funcBoundaries.trackStartPoint(address); // get range that encompasses address PPCFunctionBoundaryTracker::PPCRange_t range; if (funcBoundaries.getRangeForAddress(address, range) == false) { cemu_assert_debug(false); } // todo - use info from previously compiled ranges to determine full size of this function (and merge all the entryAddresses) // collect all currently known entry points for this range PPCRecompilerState.recompilerSpinlock.lock(); std::set<uint32> entryAddresses; entryAddresses.emplace(address); PPCRecompilerState.recompilerSpinlock.unlock(); std::vector<std::pair<MPTR, uint32>> functionEntryPoints; auto func = PPCRecompiler_recompileFunction(range, entryAddresses, functionEntryPoints, funcBoundaries); if (!func) { return; // recompilation failed } bool r = PPCRecompiler_makeRecompiledFunctionActive(address, range, func, functionEntryPoints); } std::thread s_threadRecompiler; std::atomic_bool s_recompilerThreadStopSignal{false}; void PPCRecompiler_thread() { SetThreadName("PPCRecompiler"); #if PPCREC_FORCE_SYNCHRONOUS_COMPILATION return; #endif while (true) { if(s_recompilerThreadStopSignal) return; std::this_thread::sleep_for(std::chrono::milliseconds(10)); // asynchronous recompilation: // 1) take address from queue // 2) check if address is still marked as visited // 3) if yes -> calculate size, gather all entry points, recompile and update jump table while (true) { PPCRecompilerState.recompilerSpinlock.lock(); if (PPCRecompilerState.targetQueue.empty()) { PPCRecompilerState.recompilerSpinlock.unlock(); break; } auto enterAddress = PPCRecompilerState.targetQueue.front(); PPCRecompilerState.targetQueue.pop(); auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4]; if (funcPtr != PPCRecompiler_leaveRecompilerCode_visited) { // only recompile functions if marked as visited PPCRecompilerState.recompilerSpinlock.unlock(); continue; } PPCRecompilerState.recompilerSpinlock.unlock(); PPCRecompiler_recompileAtAddress(enterAddress); if(s_recompilerThreadStopSignal) return; } } } #define PPC_REC_ALLOC_BLOCK_SIZE (4*1024*1024) // 4MB constexpr uint32 PPCRecompiler_GetNumAddressSpaceBlocks() { return (MEMORY_CODEAREA_ADDR + MEMORY_CODEAREA_SIZE + PPC_REC_ALLOC_BLOCK_SIZE - 1) / PPC_REC_ALLOC_BLOCK_SIZE; } std::bitset<PPCRecompiler_GetNumAddressSpaceBlocks()> ppcRecompiler_reservedBlockMask; void PPCRecompiler_reserveLookupTableBlock(uint32 offset) { uint32 blockIndex = offset / PPC_REC_ALLOC_BLOCK_SIZE; offset = blockIndex * PPC_REC_ALLOC_BLOCK_SIZE; if (ppcRecompiler_reservedBlockMask[blockIndex]) return; ppcRecompiler_reservedBlockMask[blockIndex] = true; void* p1 = MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->ppcRecompilerFuncTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), MemMapper::PAGE_PERMISSION::P_RW, true); void* p3 = MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), MemMapper::PAGE_PERMISSION::P_RW, true); if( !p1 || !p3 ) { cemuLog_log(LogType::Force, "Failed to allocate memory for recompiler (0x{:08x})", offset); cemu_assert(false); return; } for(uint32 i=0; i<PPC_REC_ALLOC_BLOCK_SIZE/4; i++) { ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4+i] = PPCRecompiler_leaveRecompilerCode_unvisited; } } void PPCRecompiler_allocateRange(uint32 startAddress, uint32 size) { if (ppcRecompilerInstanceData == nullptr) return; uint32 endAddress = (startAddress + size + PPC_REC_ALLOC_BLOCK_SIZE - 1) & ~(PPC_REC_ALLOC_BLOCK_SIZE-1); startAddress = (startAddress) & ~(PPC_REC_ALLOC_BLOCK_SIZE-1); startAddress = std::min(startAddress, (uint32)MEMORY_CODEAREA_ADDR + MEMORY_CODEAREA_SIZE); endAddress = std::min(endAddress, (uint32)MEMORY_CODEAREA_ADDR + MEMORY_CODEAREA_SIZE); for (uint32 i = startAddress; i < endAddress; i += PPC_REC_ALLOC_BLOCK_SIZE) { PPCRecompiler_reserveLookupTableBlock(i); } } struct ppcRecompilerFuncRange_t { MPTR ppcStart; uint32 ppcSize; void* x86Start; size_t x86Size; }; bool PPCRecompiler_findFuncRanges(uint32 addr, ppcRecompilerFuncRange_t* rangesOut, size_t* countInOut) { PPCRecompilerState.recompilerSpinlock.lock(); size_t countIn = *countInOut; size_t countOut = 0; rangeStore_ppcRanges.findRanges(addr, addr + 4, [rangesOut, countIn, &countOut](uint32 start, uint32 end, PPCRecFunction_t* func) { if (countOut < countIn) { rangesOut[countOut].ppcStart = start; rangesOut[countOut].ppcSize = (end-start); rangesOut[countOut].x86Start = func->x86Code; rangesOut[countOut].x86Size = func->x86Size; } countOut++; } ); PPCRecompilerState.recompilerSpinlock.unlock(); *countInOut = countOut; if (countOut > countIn) return false; return true; } extern "C" DLLEXPORT uintptr_t * PPCRecompiler_getJumpTableBase() { if (ppcRecompilerInstanceData == nullptr) return nullptr; return (uintptr_t*)ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable; } void PPCRecompiler_invalidateTableRange(uint32 offset, uint32 size) { if (ppcRecompilerInstanceData == nullptr) return; for (uint32 i = 0; i < size / 4; i++) { ppcRecompilerInstanceData->ppcRecompilerFuncTable[offset / 4 + i] = nullptr; ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset / 4 + i] = PPCRecompiler_leaveRecompilerCode_unvisited; } } void PPCRecompiler_deleteFunction(PPCRecFunction_t* func) { // assumes PPCRecompilerState.recompilerSpinlock is already held cemu_assert_debug(PPCRecompilerState.recompilerSpinlock.is_locked()); for (auto& r : func->list_ranges) { PPCRecompiler_invalidateTableRange(r.ppcAddress, r.ppcSize); if(r.storedRange) rangeStore_ppcRanges.deleteRange(r.storedRange); r.storedRange = nullptr; } // todo - free x86 code } void PPCRecompiler_invalidateRange(uint32 startAddr, uint32 endAddr) { if (ppcRecompilerEnabled == false) return; if (startAddr >= PPC_REC_CODE_AREA_SIZE) return; cemu_assert_debug(endAddr >= startAddr); PPCRecompilerState.recompilerSpinlock.lock(); uint32 rStart; uint32 rEnd; PPCRecFunction_t* rFunc; // mark range as unvisited for (uint64 currentAddr = (uint64)startAddr&~3; currentAddr < (uint64)(endAddr&~3); currentAddr += 4) ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[currentAddr / 4] = PPCRecompiler_leaveRecompilerCode_unvisited; // add entry to invalidation queue PPCRecompilerState.invalidationRanges.emplace_back(startAddr, endAddr-startAddr); while (rangeStore_ppcRanges.findFirstRange(startAddr, endAddr, rStart, rEnd, rFunc) ) { PPCRecompiler_deleteFunction(rFunc); } PPCRecompilerState.recompilerSpinlock.unlock(); } #if defined(ARCH_X86_64) void PPCRecompiler_initPlatform() { ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom[0] = 1ULL << 63ULL; ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom[1] = 0ULL; ppcRecompilerInstanceData->_x64XMM_xorNegateMaskPair[0] = 1ULL << 63ULL; ppcRecompilerInstanceData->_x64XMM_xorNegateMaskPair[1] = 1ULL << 63ULL; ppcRecompilerInstanceData->_x64XMM_xorNOTMask[0] = 0xFFFFFFFFFFFFFFFFULL; ppcRecompilerInstanceData->_x64XMM_xorNOTMask[1] = 0xFFFFFFFFFFFFFFFFULL; ppcRecompilerInstanceData->_x64XMM_andAbsMaskBottom[0] = ~(1ULL << 63ULL); ppcRecompilerInstanceData->_x64XMM_andAbsMaskBottom[1] = ~0ULL; ppcRecompilerInstanceData->_x64XMM_andAbsMaskPair[0] = ~(1ULL << 63ULL); ppcRecompilerInstanceData->_x64XMM_andAbsMaskPair[1] = ~(1ULL << 63ULL); ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[0] = ~(1 << 31); ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[1] = 0xFFFFFFFF; ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[2] = 0xFFFFFFFF; ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[3] = 0xFFFFFFFF; ppcRecompilerInstanceData->_x64XMM_singleWordMask[0] = 0xFFFFFFFFULL; ppcRecompilerInstanceData->_x64XMM_singleWordMask[1] = 0ULL; ppcRecompilerInstanceData->_x64XMM_constDouble1_1[0] = 1.0; ppcRecompilerInstanceData->_x64XMM_constDouble1_1[1] = 1.0; ppcRecompilerInstanceData->_x64XMM_constDouble0_0[0] = 0.0; ppcRecompilerInstanceData->_x64XMM_constDouble0_0[1] = 0.0; ppcRecompilerInstanceData->_x64XMM_constFloat0_0[0] = 0.0f; ppcRecompilerInstanceData->_x64XMM_constFloat0_0[1] = 0.0f; ppcRecompilerInstanceData->_x64XMM_constFloat1_1[0] = 1.0f; ppcRecompilerInstanceData->_x64XMM_constFloat1_1[1] = 1.0f; *(uint32*)&ppcRecompilerInstanceData->_x64XMM_constFloatMin[0] = 0x00800000; *(uint32*)&ppcRecompilerInstanceData->_x64XMM_constFloatMin[1] = 0x00800000; ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[0] = 0x7F800000; ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[1] = 0x7F800000; ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[2] = 0x7F800000; ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[3] = 0x7F800000; ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[0] = ~0x80000000; ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[1] = ~0x80000000; ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[2] = ~0x80000000; ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[3] = ~0x80000000; // mxcsr ppcRecompilerInstanceData->_x64XMM_mxCsr_ftzOn = 0x1F80 | 0x8000; ppcRecompilerInstanceData->_x64XMM_mxCsr_ftzOff = 0x1F80; } #else void PPCRecompiler_initPlatform() { } #endif void PPCRecompiler_init() { if (ActiveSettings::GetCPUMode() == CPUMode::SinglecoreInterpreter) { ppcRecompilerEnabled = false; return; } if (LaunchSettings::ForceInterpreter() || LaunchSettings::ForceMultiCoreInterpreter()) { cemuLog_log(LogType::Force, "Recompiler disabled. Command line --force-interpreter or force-multicore-interpreter was passed"); return; } if (ppcRecompilerInstanceData) { MemMapper::FreeReservation(ppcRecompilerInstanceData, sizeof(PPCRecompilerInstanceData_t)); ppcRecompilerInstanceData = nullptr; } debug_printf("Allocating %dMB for recompiler instance data...\n", (sint32)(sizeof(PPCRecompilerInstanceData_t) / 1024 / 1024)); ppcRecompilerInstanceData = (PPCRecompilerInstanceData_t*)MemMapper::ReserveMemory(nullptr, sizeof(PPCRecompilerInstanceData_t), MemMapper::PAGE_PERMISSION::P_RW); MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom), sizeof(PPCRecompilerInstanceData_t) - offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom), MemMapper::PAGE_PERMISSION::P_RW, true); #ifdef ARCH_X86_64 PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions(); #elif defined(__aarch64__) PPCRecompilerAArch64Gen_generateRecompilerInterfaceFunctions(); #endif PPCRecompiler_allocateRange(0, 0x1000); // the first entry is used for fallback to interpreter PPCRecompiler_allocateRange(mmuRange_TRAMPOLINE_AREA.getBase(), mmuRange_TRAMPOLINE_AREA.getSize()); PPCRecompiler_allocateRange(mmuRange_CODECAVE.getBase(), mmuRange_CODECAVE.getSize()); PPCRecompiler_initPlatform(); cemuLog_log(LogType::Force, "Recompiler initialized"); ppcRecompilerEnabled = true; // launch recompilation thread s_recompilerThreadStopSignal = false; s_threadRecompiler = std::thread(PPCRecompiler_thread); } void PPCRecompiler_Shutdown() { // shut down recompiler thread s_recompilerThreadStopSignal = true; if(s_threadRecompiler.joinable()) s_threadRecompiler.join(); // clean up queues while(!PPCRecompilerState.targetQueue.empty()) PPCRecompilerState.targetQueue.pop(); PPCRecompilerState.invalidationRanges.clear(); // clean range store rangeStore_ppcRanges.clear(); // clean up memory uint32 numBlocks = PPCRecompiler_GetNumAddressSpaceBlocks(); for(uint32 i=0; i<numBlocks; i++) { if(!ppcRecompiler_reservedBlockMask[i]) continue; // deallocate uint64 offset = i * PPC_REC_ALLOC_BLOCK_SIZE; MemMapper::FreeMemory(&(ppcRecompilerInstanceData->ppcRecompilerFuncTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), true); MemMapper::FreeMemory(&(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), true); // mark as unmapped ppcRecompiler_reservedBlockMask[i] = false; } } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h ================================================ #pragma once #define PPC_REC_CODE_AREA_START (0x00000000) // lower bound of executable memory area. Recompiler expects this address to be 0 #define PPC_REC_CODE_AREA_END (0x10000000) // upper bound of executable memory area #define PPC_REC_CODE_AREA_SIZE (PPC_REC_CODE_AREA_END - PPC_REC_CODE_AREA_START) #define PPC_REC_ALIGN_TO_4MB(__v) (((__v)+4*1024*1024-1)&~(4*1024*1024-1)) #define PPC_REC_MAX_VIRTUAL_GPR (40 + 32) // enough to store 32 GPRs + a few SPRs + temp registers (usually only 1-2) struct ppcRecRange_t { uint32 ppcAddress; uint32 ppcSize; void* storedRange; }; struct PPCRecFunction_t { uint32 ppcAddress; uint32 ppcSize; // ppc code size of function void* x86Code; // pointer to x86 code size_t x86Size; std::vector<ppcRecRange_t> list_ranges; }; #include "Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h" #include "Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h" struct IMLInstruction* PPCRecompilerImlGen_generateNewEmptyInstruction(struct ppcImlGenContext_t* ppcImlGenContext); struct ppcImlGenContext_t { class PPCFunctionBoundaryTracker* boundaryTracker; uint32* currentInstruction; uint32 ppcAddressOfCurrentInstruction; IMLSegment* currentOutputSegment; struct PPCBasicBlockInfo* currentBasicBlock{}; // fpr mode bool LSQE{ true }; bool PSE{ true }; // cycle counter uint32 cyclesSinceLastBranch; // used to track ppc cycles std::unordered_map<IMLName, IMLReg> mappedRegs; uint32 GetMaxRegId() const { if (mappedRegs.empty()) return 0; return mappedRegs.size()-1; } // list of segments std::vector<IMLSegment*> segmentList2; // code generation control bool hasFPUInstruction; // if true, PPCEnter macro will create FP_UNAVAIL checks -> Not needed in user mode // analysis info struct { bool modifiesGQR[8]; }tracking; // debug helpers uint32 debug_entryPPCAddress{0}; ~ppcImlGenContext_t() { for (IMLSegment* imlSegment : segmentList2) delete imlSegment; segmentList2.clear(); } // append raw instruction IMLInstruction& emitInst() { return *PPCRecompilerImlGen_generateNewEmptyInstruction(this); } IMLSegment* NewSegment() { IMLSegment* seg = new IMLSegment(); segmentList2.emplace_back(seg); return seg; } size_t GetSegmentIndex(IMLSegment* seg) { for (size_t i = 0; i < segmentList2.size(); i++) { if (segmentList2[i] == seg) return i; } cemu_assert_error(); return 0; } IMLSegment* InsertSegment(size_t index) { IMLSegment* newSeg = new IMLSegment(); segmentList2.insert(segmentList2.begin() + index, 1, newSeg); return newSeg; } std::span<IMLSegment*> InsertSegments(size_t index, size_t count) { segmentList2.insert(segmentList2.begin() + index, count, {}); for (size_t i = index; i < (index + count); i++) segmentList2[i] = new IMLSegment(); return { segmentList2.data() + index, count}; } void UpdateSegmentIndices() { for (size_t i = 0; i < segmentList2.size(); i++) segmentList2[i]->momentaryIndex = (sint32)i; } }; typedef void ATTR_MS_ABI (*PPCREC_JUMP_ENTRY)(); typedef struct { PPCRecFunction_t* ppcRecompilerFuncTable[PPC_REC_ALIGN_TO_4MB(PPC_REC_CODE_AREA_SIZE/4)]; // one virtual-function pointer for each potential ppc instruction PPCREC_JUMP_ENTRY ppcRecompilerDirectJumpTable[PPC_REC_ALIGN_TO_4MB(PPC_REC_CODE_AREA_SIZE/4)]; // lookup table for ppc offset to native code function // x64 data alignas(16) uint64 _x64XMM_xorNegateMaskBottom[2]; alignas(16) uint64 _x64XMM_xorNegateMaskPair[2]; alignas(16) uint64 _x64XMM_xorNOTMask[2]; alignas(16) uint64 _x64XMM_andAbsMaskBottom[2]; alignas(16) uint64 _x64XMM_andAbsMaskPair[2]; alignas(16) uint32 _x64XMM_andFloatAbsMaskBottom[4]; alignas(16) uint64 _x64XMM_singleWordMask[2]; alignas(16) double _x64XMM_constDouble1_1[2]; alignas(16) double _x64XMM_constDouble0_0[2]; alignas(16) float _x64XMM_constFloat0_0[2]; alignas(16) float _x64XMM_constFloat1_1[2]; alignas(16) float _x64XMM_constFloatMin[2]; alignas(16) uint32 _x64XMM_flushDenormalMask1[4]; alignas(16) uint32 _x64XMM_flushDenormalMaskResetSignBits[4]; // MXCSR uint32 _x64XMM_mxCsr_ftzOn; uint32 _x64XMM_mxCsr_ftzOff; }PPCRecompilerInstanceData_t; extern PPCRecompilerInstanceData_t* ppcRecompilerInstanceData; extern bool ppcRecompilerEnabled; void PPCRecompiler_init(); void PPCRecompiler_Shutdown(); void PPCRecompiler_allocateRange(uint32 startAddress, uint32 size); void PPCRecompiler_invalidateRange(uint32 startAddr, uint32 endAddr); extern void ATTR_MS_ABI (*PPCRecompiler_enterRecompilerCode)(uint64 codeMem, uint64 ppcInterpreterInstance); extern void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_visited)(); extern void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_unvisited)(); #define PPC_REC_INVALID_FUNCTION ((PPCRecFunction_t*)-1) // recompiler interface void PPCRecompiler_recompileIfUnvisited(uint32 enterAddress); void PPCRecompiler_attemptEnter(struct PPCInterpreter_t* hCPU, uint32 enterAddress); void PPCRecompiler_attemptEnterWithoutRecompile(struct PPCInterpreter_t* hCPU, uint32 enterAddress); ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h ================================================ bool PPCRecompiler_generateIntermediateCode(ppcImlGenContext_t& ppcImlGenContext, PPCRecFunction_t* PPCRecFunction, std::set<uint32>& entryAddresses, class PPCFunctionBoundaryTracker& boundaryTracker); IMLSegment* PPCIMLGen_CreateSplitSegmentAtEnd(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo); IMLSegment* PPCIMLGen_CreateNewSegmentAsBranchTarget(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo); void PPCIMLGen_AssertIfNotLastSegmentInstruction(ppcImlGenContext_t& ppcImlGenContext); IMLInstruction* PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext_t* ppcImlGenContext); void PPCRecompiler_pushBackIMLInstructions(IMLSegment* imlSegment, sint32 index, sint32 shiftBackCount); IMLInstruction* PPCRecompiler_insertInstruction(IMLSegment* imlSegment, sint32 index); void PPCRecompilerIml_insertSegments(ppcImlGenContext_t* ppcImlGenContext, sint32 index, sint32 count); void PPCRecompilerIml_setSegmentPoint(IMLSegmentPoint* segmentPoint, IMLSegment* imlSegment, sint32 index); void PPCRecompilerIml_removeSegmentPoint(IMLSegmentPoint* segmentPoint); // Register management IMLReg PPCRecompilerImlGen_LookupReg(ppcImlGenContext_t* ppcImlGenContext, IMLName mappedName, IMLRegFormat regFormat); IMLReg PPCRecompilerImlGen_loadRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName); // IML instruction generation void PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(ppcImlGenContext_t* ppcImlGenContext, IMLInstruction* imlInstruction, uint32 operation, IMLReg registerIndex, sint32 immS32, uint32 crRegisterIndex, uint32 crBitIndex, bool bitMustBeSet); // IML generation - FPU bool PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble); bool PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble); bool PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble); bool PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool hasUpdate, bool isDouble); bool PPCRecompilerImlGen_STFIWX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FMUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FDIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FNMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FMULS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FDIVS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FMADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FNMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FCMPO(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FCMPU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FMR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FNABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FRES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FRSP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FNEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FSEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FRSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FCTIWZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate); bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate); bool PPCRecompilerImlGen_PS_MULSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isVariant1); bool PPCRecompilerImlGen_PS_MADDSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isVariant1); bool PPCRecompilerImlGen_PS_ADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_SUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_MUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_DIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_MADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_NMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withNegative); bool PPCRecompilerImlGen_PS_SUM0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_SUM1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_NEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_ABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_RES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_RSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_MR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_SEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_MERGE00(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_MERGE01(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_MERGE10(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_MERGE11(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_CMPO0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_CMPU0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); // IML general void PPCRecompilerIML_isolateEnterableSegments(ppcImlGenContext_t* ppcImlGenContext); void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchTaken, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchNotTaken); void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchNotTaken); // no else segment void PPCIMLGen_CreateSegmentBranchedPathMultiple(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, IMLSegment** segmentsOut, IMLReg compareReg, sint32* compareValues, sint32 count, sint32 defaultCaseIndex); class IMLRedirectInstOutput { public: IMLRedirectInstOutput(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* outputSegment); ~IMLRedirectInstOutput(); private: ppcImlGenContext_t* m_context; IMLSegment* m_prevSegment; }; ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp ================================================ #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h" #include "Cafe/HW/Espresso/EspressoISA.h" #include "PPCRecompiler.h" #include "PPCRecompilerIml.h" #include "IML/IML.h" #include "IML/IMLRegisterAllocatorRanges.h" #include "PPCFunctionBoundaryTracker.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext); struct PPCBasicBlockInfo { PPCBasicBlockInfo(uint32 startAddress, const std::set<uint32>& entryAddresses) : startAddress(startAddress), lastAddress(startAddress) { isEnterable = entryAddresses.find(startAddress) != entryAddresses.end(); } uint32 startAddress; uint32 lastAddress; // inclusive bool isEnterable{ false }; bool hasContinuedFlow{ true }; // non-branch path goes to next segment, assumed by default bool hasBranchTarget{ false }; uint32 branchTarget{}; // associated IML segments IMLSegment* firstSegment{}; // first segment in chain, used as branch target for other segments IMLSegment* appendSegment{}; // last segment in chain, additional instructions should be appended to this segment void SetInitialSegment(IMLSegment* seg) { cemu_assert_debug(!firstSegment); cemu_assert_debug(!appendSegment); firstSegment = seg; appendSegment = seg; } IMLSegment* GetFirstSegmentInChain() { return firstSegment; } IMLSegment* GetSegmentForInstructionAppend() { return appendSegment; } }; IMLInstruction* PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext_t* ppcImlGenContext) { IMLInstruction& inst = ppcImlGenContext->currentOutputSegment->imlList.emplace_back(); memset(&inst, 0x00, sizeof(IMLInstruction)); return &inst; } void PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerDestination, IMLReg registerMemory1, IMLReg registerMemory2, uint32 copyWidth, bool signExtend, bool switchEndian) { cemu_assert_debug(registerMemory1.IsValid()); cemu_assert_debug(registerMemory2.IsValid()); cemu_assert_debug(registerDestination.IsValid()); IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_LOAD_INDEXED; imlInstruction->operation = 0; imlInstruction->op_storeLoad.registerData = registerDestination; imlInstruction->op_storeLoad.registerMem = registerMemory1; imlInstruction->op_storeLoad.registerMem2 = registerMemory2; imlInstruction->op_storeLoad.copyWidth = copyWidth; imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; imlInstruction->op_storeLoad.flags2.signExtend = signExtend; } void PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerDestination, IMLReg registerMemory1, IMLReg registerMemory2, uint32 copyWidth, bool signExtend, bool switchEndian) { cemu_assert_debug(registerMemory1.IsValid()); cemu_assert_debug(registerMemory2.IsValid()); cemu_assert_debug(registerDestination.IsValid()); IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_STORE_INDEXED; imlInstruction->operation = 0; imlInstruction->op_storeLoad.registerData = registerDestination; imlInstruction->op_storeLoad.registerMem = registerMemory1; imlInstruction->op_storeLoad.registerMem2 = registerMemory2; imlInstruction->op_storeLoad.copyWidth = copyWidth; imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; imlInstruction->op_storeLoad.flags2.signExtend = signExtend; } // create and fill two segments (branch taken and branch not taken) as a follow up to the current segment and then merge flow afterwards void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchTaken, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchNotTaken) { IMLSegment* currentWriteSegment = basicBlockInfo.GetSegmentForInstructionAppend(); std::span<IMLSegment*> segments = ppcImlGenContext.InsertSegments(ppcImlGenContext.GetSegmentIndex(currentWriteSegment) + 1, 3); IMLSegment* segBranchNotTaken = segments[0]; IMLSegment* segBranchTaken = segments[1]; IMLSegment* segMerge = segments[2]; // link the segments segMerge->SetLinkBranchTaken(currentWriteSegment->GetBranchTaken()); segMerge->SetLinkBranchNotTaken(currentWriteSegment->GetBranchNotTaken()); currentWriteSegment->SetLinkBranchTaken(segBranchTaken); currentWriteSegment->SetLinkBranchNotTaken(segBranchNotTaken); segBranchTaken->SetLinkBranchNotTaken(segMerge); segBranchNotTaken->SetLinkBranchTaken(segMerge); // generate code for branch taken segment ppcImlGenContext.currentOutputSegment = segBranchTaken; genSegmentBranchTaken(ppcImlGenContext); cemu_assert_debug(ppcImlGenContext.currentOutputSegment == segBranchTaken); // generate code for branch not taken segment ppcImlGenContext.currentOutputSegment = segBranchNotTaken; genSegmentBranchNotTaken(ppcImlGenContext); cemu_assert_debug(ppcImlGenContext.currentOutputSegment == segBranchNotTaken); ppcImlGenContext.emitInst().make_jump(); // make merge segment the new write segment ppcImlGenContext.currentOutputSegment = segMerge; basicBlockInfo.appendSegment = segMerge; } void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchNotTaken) { IMLSegment* currentWriteSegment = basicBlockInfo.GetSegmentForInstructionAppend(); std::span<IMLSegment*> segments = ppcImlGenContext.InsertSegments(ppcImlGenContext.GetSegmentIndex(currentWriteSegment) + 1, 2); IMLSegment* segBranchNotTaken = segments[0]; IMLSegment* segMerge = segments[1]; // link the segments segMerge->SetLinkBranchTaken(currentWriteSegment->GetBranchTaken()); segMerge->SetLinkBranchNotTaken(currentWriteSegment->GetBranchNotTaken()); currentWriteSegment->SetLinkBranchTaken(segMerge); currentWriteSegment->SetLinkBranchNotTaken(segBranchNotTaken); segBranchNotTaken->SetLinkBranchNotTaken(segMerge); // generate code for branch not taken segment ppcImlGenContext.currentOutputSegment = segBranchNotTaken; genSegmentBranchNotTaken(ppcImlGenContext); cemu_assert_debug(ppcImlGenContext.currentOutputSegment == segBranchNotTaken); // make merge segment the new write segment ppcImlGenContext.currentOutputSegment = segMerge; basicBlockInfo.appendSegment = segMerge; } IMLReg _GetRegTemporaryS8(ppcImlGenContext_t* ppcImlGenContext, uint32 index); IMLRedirectInstOutput::IMLRedirectInstOutput(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* outputSegment) : m_context(ppcImlGenContext) { m_prevSegment = ppcImlGenContext->currentOutputSegment; cemu_assert_debug(ppcImlGenContext->currentOutputSegment == ppcImlGenContext->currentBasicBlock->appendSegment); if (outputSegment == ppcImlGenContext->currentOutputSegment) { m_prevSegment = nullptr; return; } m_context->currentBasicBlock->appendSegment = outputSegment; m_context->currentOutputSegment = outputSegment; } IMLRedirectInstOutput::~IMLRedirectInstOutput() { if (m_prevSegment) { m_context->currentBasicBlock->appendSegment = m_prevSegment; m_context->currentOutputSegment = m_prevSegment; } } // compare values and branch to segment with same index in segmentsOut. The last segment doesn't actually have any comparison and just is the default case. Thus compareValues is one shorter than count void PPCIMLGen_CreateSegmentBranchedPathMultiple(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, IMLSegment** segmentsOut, IMLReg compareReg, sint32* compareValues, sint32 count, sint32 defaultCaseIndex) { IMLSegment* currentWriteSegment = basicBlockInfo.GetSegmentForInstructionAppend(); cemu_assert_debug(!currentWriteSegment->HasSuffixInstruction()); // must not already have a suffix instruction const sint32 numBranchSegments = count + 1; const sint32 numCaseSegments = count; std::span<IMLSegment*> segments = ppcImlGenContext.InsertSegments(ppcImlGenContext.GetSegmentIndex(currentWriteSegment) + 1, numBranchSegments - 1 + numCaseSegments + 1); IMLSegment** extraBranchSegments = segments.data(); IMLSegment** caseSegments = segments.data() + numBranchSegments - 1; IMLSegment* mergeSegment = segments[numBranchSegments - 1 + numCaseSegments]; // move links to the merge segment mergeSegment->SetLinkBranchTaken(currentWriteSegment->GetBranchTaken()); mergeSegment->SetLinkBranchNotTaken(currentWriteSegment->GetBranchNotTaken()); currentWriteSegment->SetLinkBranchTaken(nullptr); currentWriteSegment->SetLinkBranchNotTaken(nullptr); for (sint32 i=0; i<numCaseSegments; i++) segmentsOut[i] = caseSegments[i]; IMLReg tmpBoolReg = _GetRegTemporaryS8(&ppcImlGenContext, 2); // the first branch segment is the original current write segment auto GetBranchSegment = [&](sint32 index) { if (index == 0) return currentWriteSegment; else return extraBranchSegments[index - 1]; }; // link branch segments (taken: Link to case segment. NotTaken: Link to next branch segment. For the last one use a non-conditional jump) for (sint32 i=0; i<numBranchSegments; i++) { IMLSegment* seg = GetBranchSegment(i); if (i < numBranchSegments - 1) { cemu_assert_debug(i < numCaseSegments); seg->SetLinkBranchTaken(caseSegments[i]); seg->SetLinkBranchNotTaken(GetBranchSegment(i + 1)); seg->AppendInstruction()->make_compare_s32(compareReg, compareValues[i], tmpBoolReg, IMLCondition::EQ); seg->AppendInstruction()->make_conditional_jump(tmpBoolReg, true); } else { cemu_assert_debug(defaultCaseIndex < numCaseSegments); seg->SetLinkBranchTaken(caseSegments[defaultCaseIndex]); seg->AppendInstruction()->make_jump(); } } // link case segments for (sint32 i=0; i<numCaseSegments; i++) { IMLSegment* seg = caseSegments[i]; if (i < numCaseSegments - 1) { seg->SetLinkBranchTaken(mergeSegment); // -> Jumps are added after the instructions } else { seg->SetLinkBranchTaken(mergeSegment); } } ppcImlGenContext.currentOutputSegment = mergeSegment; basicBlockInfo.appendSegment = mergeSegment; } IMLReg PPCRecompilerImlGen_LookupReg(ppcImlGenContext_t* ppcImlGenContext, IMLName mappedName, IMLRegFormat regFormat) { auto it = ppcImlGenContext->mappedRegs.find(mappedName); if (it != ppcImlGenContext->mappedRegs.end()) return it->second; // create new reg entry IMLRegFormat baseFormat; if (regFormat == IMLRegFormat::F64) baseFormat = IMLRegFormat::F64; else if (regFormat == IMLRegFormat::I32) baseFormat = IMLRegFormat::I64; else { cemu_assert_suspicious(); } IMLRegID newRegId = ppcImlGenContext->mappedRegs.size(); IMLReg newReg(baseFormat, regFormat, 0, newRegId); ppcImlGenContext->mappedRegs.try_emplace(mappedName, newReg); return newReg; } IMLName PPCRecompilerImlGen_GetRegName(ppcImlGenContext_t* ppcImlGenContext, IMLReg reg) { for (auto& it : ppcImlGenContext->mappedRegs) { if (it.second.GetRegID() == reg.GetRegID()) return it.first; } cemu_assert(false); return 0; } uint32 PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) { DEBUG_BREAK; //if( mappedName == PPCREC_NAME_NONE ) //{ // debug_printf("PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(): Invalid mappedName parameter\n"); // return PPC_REC_INVALID_REGISTER; //} //for(uint32 i=0; i<255; i++) //{ // if( ppcImlGenContext->mappedFPRRegister[i] == PPCREC_NAME_NONE ) // { // ppcImlGenContext->mappedFPRRegister[i] = mappedName; // return i; // } //} return 0; } uint32 PPCRecompilerImlGen_findFPRRegisterByMappedName(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) { DEBUG_BREAK; //for(uint32 i=0; i<255; i++) //{ // if( ppcImlGenContext->mappedFPRRegister[i] == mappedName ) // { // return i; // } //} return PPC_REC_INVALID_REGISTER; } IMLReg PPCRecompilerImlGen_loadRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) { return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, mappedName, IMLRegFormat::I32); } IMLReg _GetRegGPR(ppcImlGenContext_t* ppcImlGenContext, uint32 index) { cemu_assert_debug(index < 32); return PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + index); } IMLReg _GetRegCR(ppcImlGenContext_t* ppcImlGenContext, uint32 index) { cemu_assert_debug(index < 32); return PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_CR + index); } IMLReg _GetRegCR(ppcImlGenContext_t* ppcImlGenContext, uint8 crReg, uint8 crBit) { cemu_assert_debug(crReg < 8); cemu_assert_debug(crBit < 4); return _GetRegCR(ppcImlGenContext, (crReg * 4) + crBit); } IMLReg _GetRegTemporary(ppcImlGenContext_t* ppcImlGenContext, uint32 index) { cemu_assert_debug(index < 4); return PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + index); } // get throw-away register // be careful to not collide with other temporary register IMLReg _GetRegTemporaryS8(ppcImlGenContext_t* ppcImlGenContext, uint32 index) { cemu_assert_debug(index < 4); return PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + index); } bool PPCRecompiler_canInlineFunction(MPTR functionPtr, sint32* functionInstructionCount) { for (sint32 i = 0; i < 6; i++) { uint32 opcode = memory_readU32(functionPtr + i * 4); switch ((opcode >> 26)) { case 14: // ADDI case 15: // ADDIS continue; case 19: // opcode category 19 switch (PPC_getBits(opcode, 30, 10)) { case 16: if (opcode == 0x4E800020) { *functionInstructionCount = i; return true; // BLR } return false; } return false; case 32: // LWZ case 33: // LWZU case 34: // LBZ case 35: // LBZU case 36: // STW case 37: // STWU case 38: // STB case 39: // STBU case 40: // LHZ case 41: // LHZU case 42: // LHA case 43: // LHAU case 44: // STH case 45: // STHU case 46: // LMW case 47: // STMW case 48: // LFS case 49: // LFSU case 50: // LFD case 51: // LFDU case 52: // STFS case 53: // STFSU case 54: // STFD case 55: // STFDU continue; default: return false; } } return false; } void PPCRecompiler_generateInlinedCode(ppcImlGenContext_t* ppcImlGenContext, uint32 startAddress, sint32 instructionCount) { for (sint32 i = 0; i < instructionCount; i++) { ppcImlGenContext->ppcAddressOfCurrentInstruction = startAddress + i * 4; ppcImlGenContext->cyclesSinceLastBranch++; if (PPCRecompiler_decodePPCInstruction(ppcImlGenContext)) { cemu_assert_suspicious(); } } // add range cemu_assert_unimplemented(); //ppcRecRange_t recRange; //recRange.ppcAddress = startAddress; //recRange.ppcSize = instructionCount*4 + 4; // + 4 because we have to include the BLR //ppcImlGenContext->functionRef->list_ranges.push_back(recRange); } // for handling RC bit of many instructions void PPCImlGen_UpdateCR0(ppcImlGenContext_t* ppcImlGenContext, IMLReg regR) { IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT::CR_BIT_INDEX_LT); IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT::CR_BIT_INDEX_GT); IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT::CR_BIT_INDEX_EQ); // todo - SO bit ppcImlGenContext->emitInst().make_compare_s32(regR, 0, crBitRegLT, IMLCondition::SIGNED_LT); ppcImlGenContext->emitInst().make_compare_s32(regR, 0, crBitRegGT, IMLCondition::SIGNED_GT); ppcImlGenContext->emitInst().make_compare_s32(regR, 0, crBitRegEQ, IMLCondition::EQ); //ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, crBitRegSO, 0); // todo - copy from XER //ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, registerR, registerR, 0, PPCREC_CR_MODE_LOGICAL); } void PPCRecompilerImlGen_TW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // split before and after to make sure the macro is in an isolated segment that we can make enterable PPCIMLGen_CreateSplitSegmentAtEnd(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock); ppcImlGenContext->currentOutputSegment->SetEnterable(ppcImlGenContext->ppcAddressOfCurrentInstruction); PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext)->make_macro(PPCREC_IML_MACRO_LEAVE, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, 0, IMLREG_INVALID); IMLSegment* middleSeg = PPCIMLGen_CreateSplitSegmentAtEnd(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock); middleSeg->SetLinkBranchTaken(nullptr); middleSeg->SetLinkBranchNotTaken(nullptr); } bool PPCRecompilerImlGen_MTSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { uint32 rD, spr1, spr2, spr; PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); spr = spr1 | (spr2<<5); IMLReg gprReg = _GetRegGPR(ppcImlGenContext, rD); if (spr == SPR_CTR || spr == SPR_LR) { IMLReg sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, sprReg, gprReg); } else if (spr >= SPR_UGQR0 && spr <= SPR_UGQR7) { IMLReg sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, sprReg, gprReg); ppcImlGenContext->tracking.modifiesGQR[spr - SPR_UGQR0] = true; } else return false; return true; } bool PPCRecompilerImlGen_MFSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { uint32 rD, spr1, spr2, spr; PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); spr = spr1 | (spr2<<5); IMLReg gprReg = _GetRegGPR(ppcImlGenContext, rD); if (spr == SPR_LR || spr == SPR_CTR) { IMLReg sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, gprReg, sprReg); } else if (spr >= SPR_UGQR0 && spr <= SPR_UGQR7) { IMLReg sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, gprReg, sprReg); } else return false; return true; } ATTR_MS_ABI uint32 PPCRecompiler_GetTBL() { return (uint32)coreinit::OSGetSystemTime(); } ATTR_MS_ABI uint32 PPCRecompiler_GetTBU() { return (uint32)(coreinit::OSGetSystemTime() >> 32); } bool PPCRecompilerImlGen_MFTB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { uint32 rD, spr1, spr2, spr; PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); spr = spr1 | (spr2<<5); if( spr == SPR_TBL || spr == SPR_TBU ) { IMLReg resultReg = _GetRegGPR(ppcImlGenContext, rD); ppcImlGenContext->emitInst().make_call_imm(spr == SPR_TBL ? (uintptr_t)PPCRecompiler_GetTBL : (uintptr_t)PPCRecompiler_GetTBU, IMLREG_INVALID, IMLREG_INVALID, IMLREG_INVALID, resultReg); return true; } return false; } void PPCRecompilerImlGen_MCRF(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { uint32 crD, crS, b; PPC_OPC_TEMPL_X(opcode, crD, crS, b); cemu_assert_debug((crD&3) == 0); cemu_assert_debug((crS&3) == 0); crD >>= 2; crS >>= 2; for (sint32 i = 0; i<4; i++) { IMLReg regCrSrcBit = _GetRegCR(ppcImlGenContext, crS * 4 + i); IMLReg regCrDstBit = _GetRegCR(ppcImlGenContext, crD * 4 + i); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regCrDstBit, regCrSrcBit); } } bool PPCRecompilerImlGen_MFCR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA, rB; PPC_OPC_TEMPL_X(opcode, rD, rA, rB); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regD, 0); for (sint32 i = 0; i < 32; i++) { IMLReg regCrBit = _GetRegCR(ppcImlGenContext, i); cemu_assert_debug(regCrBit.GetRegFormat() == IMLRegFormat::I32); // addition is only allowed between same-format regs ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_LEFT_SHIFT, regD, regD, 1); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regD, regD, regCrBit); } return true; } bool PPCRecompilerImlGen_MTCRF(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { uint32 rS; uint32 crMask; PPC_OPC_TEMPL_XFX(opcode, rS, crMask); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); uint32 crBitMask = ppc_MTCRFMaskToCRBitMask(crMask); for (sint32 f = 0; f < 32; f++) { if(((crBitMask >> f) & 1) == 0) continue; IMLReg regCrBit = _GetRegCR(ppcImlGenContext, f); cemu_assert_debug(regCrBit.GetRegFormat() == IMLRegFormat::I32); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_U, regTmp, regS, (31-f)); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regCrBit, regTmp, 1); } return true; } void PPCRecompilerImlGen_CMP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isUnsigned) { uint32 cr; int rA, rB; PPC_OPC_TEMPL_X(opcode, cr, rA, rB); cr >>= 2; IMLReg gprRegisterA = _GetRegGPR(ppcImlGenContext, rA); IMLReg gprRegisterB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regXerSO = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_SO); IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_LT); IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_GT); IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_EQ); IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_SO); ppcImlGenContext->emitInst().make_compare(gprRegisterA, gprRegisterB, crBitRegLT, isUnsigned ? IMLCondition::UNSIGNED_LT : IMLCondition::SIGNED_LT); ppcImlGenContext->emitInst().make_compare(gprRegisterA, gprRegisterB, crBitRegGT, isUnsigned ? IMLCondition::UNSIGNED_GT : IMLCondition::SIGNED_GT); ppcImlGenContext->emitInst().make_compare(gprRegisterA, gprRegisterB, crBitRegEQ, IMLCondition::EQ); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, crBitRegSO, regXerSO); } bool PPCRecompilerImlGen_CMPI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isUnsigned) { uint32 cr; int rA; uint32 imm; if (isUnsigned) { PPC_OPC_TEMPL_D_UImm(opcode, cr, rA, imm); } else { PPC_OPC_TEMPL_D_SImm(opcode, cr, rA, imm); } cr >>= 2; IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regXerSO = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_SO); IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_LT); IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_GT); IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_EQ); IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_SO); ppcImlGenContext->emitInst().make_compare_s32(regA, (sint32)imm, crBitRegLT, isUnsigned ? IMLCondition::UNSIGNED_LT : IMLCondition::SIGNED_LT); ppcImlGenContext->emitInst().make_compare_s32(regA, (sint32)imm, crBitRegGT, isUnsigned ? IMLCondition::UNSIGNED_GT : IMLCondition::SIGNED_GT); ppcImlGenContext->emitInst().make_compare_s32(regA, (sint32)imm, crBitRegEQ, IMLCondition::EQ); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, crBitRegSO, regXerSO); return true; } bool PPCRecompilerImlGen_B(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { uint32 li; PPC_OPC_TEMPL_I(opcode, li); uint32 jumpAddressDest = li; if( (opcode&PPC_OPC_AA) == 0 ) { jumpAddressDest = li + (unsigned int)ppcImlGenContext->ppcAddressOfCurrentInstruction; } if( opcode&PPC_OPC_LK ) { // function call ppcImlGenContext->emitInst().make_macro(PPCREC_IML_MACRO_BL, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch, IMLREG_INVALID); return true; } // is jump destination within recompiled function? if (ppcImlGenContext->boundaryTracker->ContainsAddress(jumpAddressDest)) ppcImlGenContext->emitInst().make_jump(); else ppcImlGenContext->emitInst().make_macro(PPCREC_IML_MACRO_B_FAR, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch, IMLREG_INVALID); return true; } bool PPCRecompilerImlGen_BC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { PPCIMLGen_AssertIfNotLastSegmentInstruction(*ppcImlGenContext); uint32 BO, BI, BD; PPC_OPC_TEMPL_B(opcode, BO, BI, BD); Espresso::BOField boField(BO); uint32 crRegister = BI/4; uint32 crBit = BI%4; uint32 jumpCondition = 0; bool conditionMustBeTrue = (BO&8)!=0; bool useDecrementer = (BO&4)==0; // bit not set -> decrement bool decrementerMustBeZero = (BO&2)!=0; // bit set -> branch if CTR = 0, bit not set -> branch if CTR != 0 bool ignoreCondition = (BO&16)!=0; IMLReg regCRBit; if (!ignoreCondition) regCRBit = _GetRegCR(ppcImlGenContext, crRegister, crBit); uint32 jumpAddressDest = BD; if( (opcode&PPC_OPC_AA) == 0 ) { jumpAddressDest = BD + (unsigned int)ppcImlGenContext->ppcAddressOfCurrentInstruction; } if( opcode&PPC_OPC_LK ) { if (useDecrementer) return false; // conditional function calls are not supported if( ignoreCondition == false ) { PPCBasicBlockInfo* currentBasicBlock = ppcImlGenContext->currentBasicBlock; IMLSegment* blSeg = PPCIMLGen_CreateNewSegmentAsBranchTarget(*ppcImlGenContext, *currentBasicBlock); ppcImlGenContext->emitInst().make_conditional_jump(regCRBit, conditionMustBeTrue); blSeg->AppendInstruction()->make_macro(PPCREC_IML_MACRO_BL, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch, IMLREG_INVALID); return true; } return false; } // generate iml instructions depending on flags if( useDecrementer ) { if( ignoreCondition == false ) return false; // not supported for the moment IMLReg ctrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0+SPR_CTR); IMLReg tmpBoolReg = _GetRegTemporaryS8(ppcImlGenContext, 1); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_SUB, ctrRegister, ctrRegister, 1); ppcImlGenContext->emitInst().make_compare_s32(ctrRegister, 0, tmpBoolReg, decrementerMustBeZero ? IMLCondition::EQ : IMLCondition::NEQ); ppcImlGenContext->emitInst().make_conditional_jump(tmpBoolReg, true); return true; } else { if( ignoreCondition ) { // branch always, no condition and no decrementer // not supported return false; } else { if (ppcImlGenContext->boundaryTracker->ContainsAddress(jumpAddressDest)) { // near jump ppcImlGenContext->emitInst().make_conditional_jump(regCRBit, conditionMustBeTrue); } else { // far jump debug_printf("PPCRecompilerImlGen_BC(): Far jump not supported yet"); return false; } } } return true; } // BCCTR or BCLR bool PPCRecompilerImlGen_BCSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 sprReg) { PPCIMLGen_AssertIfNotLastSegmentInstruction(*ppcImlGenContext); Espresso::BOField BO; uint32 BI; bool LK; Espresso::decodeOp_BCSPR(opcode, BO, BI, LK); uint32 crRegister = BI/4; uint32 crBit = BI%4; IMLReg regCRBit; if (!BO.conditionIgnore()) regCRBit = _GetRegCR(ppcImlGenContext, crRegister, crBit); IMLReg branchDestReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + sprReg); if (LK) { if (sprReg == SPR_LR) { // if the branch target is LR, then preserve it in a temporary cemu_assert_suspicious(); // this case needs testing IMLReg tmpRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, tmpRegister, branchDestReg); branchDestReg = tmpRegister; } IMLReg registerLR = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_LR); ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, registerLR, ppcImlGenContext->ppcAddressOfCurrentInstruction + 4); } if (!BO.decrementerIgnore()) { cemu_assert_unimplemented(); return false; } else if (!BO.conditionIgnore()) { // no decrementer but CR check cemu_assert_debug(ppcImlGenContext->currentBasicBlock->hasContinuedFlow); cemu_assert_debug(!ppcImlGenContext->currentBasicBlock->hasBranchTarget); PPCBasicBlockInfo* currentBasicBlock = ppcImlGenContext->currentBasicBlock; IMLSegment* bctrSeg = PPCIMLGen_CreateNewSegmentAsBranchTarget(*ppcImlGenContext, *currentBasicBlock); ppcImlGenContext->emitInst().make_conditional_jump(regCRBit, !BO.conditionInverted()); bctrSeg->AppendInstruction()->make_macro(PPCREC_IML_MACRO_B_TO_REG, 0, 0, 0, branchDestReg); } else { // branch always, no condition and no decrementer check cemu_assert_debug(!ppcImlGenContext->currentBasicBlock->hasContinuedFlow); cemu_assert_debug(!ppcImlGenContext->currentBasicBlock->hasBranchTarget); ppcImlGenContext->emitInst().make_macro(PPCREC_IML_MACRO_B_TO_REG, 0, 0, 0, branchDestReg); } return true; } bool PPCRecompilerImlGen_ISYNC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { return true; } bool PPCRecompilerImlGen_SYNC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { return true; } bool PPCRecompilerImlGen_ADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regD, regA, regB); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_ADDI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); if (rA != 0) { IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, regD, regA, imm); } else { ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regD, imm); } return true; } bool PPCRecompilerImlGen_ADDIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int rD, rA; uint32 imm; PPC_OPC_TEMPL_D_Shift16(opcode, rD, rA, imm); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); if (rA != 0) { IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, regD, regA, (sint32)imm); } else { ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regD, (sint32)imm); } return true; } bool PPCRecompilerImlGen_ADDC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // r = a + b -> update carry sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regRA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regRB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regRD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); ppcImlGenContext->emitInst().make_r_r_r_carry(PPCREC_IML_OP_ADD, regRD, regRA, regRB, regCa); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regRD); return true; } bool PPCRecompilerImlGen_ADDIC_(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool updateCR0) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); ppcImlGenContext->emitInst().make_r_r_s32_carry(PPCREC_IML_OP_ADD, regD, regA, (sint32)imm, regCa); if(updateCR0) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_ADDE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // r = a + b + carry -> update carry sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regRA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regRB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regRD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); ppcImlGenContext->emitInst().make_r_r_r_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regRD, regRA, regRB, regCa); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regRD); return true; } bool PPCRecompilerImlGen_ADDZE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // r = a + carry -> update carry sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regRA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regRD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); ppcImlGenContext->emitInst().make_r_r_s32_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regRD, regRA, 0, regCa); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regRD); return true; } bool PPCRecompilerImlGen_ADDME(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // r = a + 0xFFFFFFFF + carry -> update carry sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regRA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regRD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); ppcImlGenContext->emitInst().make_r_r_s32_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regRD, regRA, -1, regCa); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regRD); return true; } bool PPCRecompilerImlGen_SUBF(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); // rD = ~rA + rB + 1 IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_SUB, regD, regB, regA); if ((opcode & PPC_OPC_RC)) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_SUBFE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // d = ~a + b + ca; sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regA); ppcImlGenContext->emitInst().make_r_r_r_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regD, regTmp, regB, regCa); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_SUBFZE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // d = ~a + ca; sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regA); ppcImlGenContext->emitInst().make_r_r_s32_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regD, regTmp, 0, regCa); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_SUBFC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // d = ~a + b + 1; sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regA); ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCa, 1); // set input carry to simulate offset of 1 ppcImlGenContext->emitInst().make_r_r_r_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regD, regTmp, regB, regCa); if ((opcode & PPC_OPC_RC)) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_SUBFIC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // d = ~a + imm + 1 sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regA); ppcImlGenContext->emitInst().make_r_r_s32_carry(PPCREC_IML_OP_ADD, regD, regTmp, (sint32)imm + 1, regCa); return true; } bool PPCRecompilerImlGen_MULLI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_MULTIPLY_SIGNED, regD, regA, (sint32)imm); return true; } bool PPCRecompilerImlGen_MULLW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); if (opcode & PPC_OPC_OE) { return false; } ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_MULTIPLY_SIGNED, regD, regA, regB); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_MULHW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, regD, regA, regB); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_MULHWU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, regD, regA, regB); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_DIVW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regR = _GetRegGPR(ppcImlGenContext, rD); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_DIVIDE_SIGNED, regR, regA, regB); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regR); return true; } bool PPCRecompilerImlGen_DIVWU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_DIVIDE_UNSIGNED, regD, regA, regB); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_RLWINM(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); uint32 mask = ppc_mask(MB, ME); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); if( ME == (31-SH) && MB == 0 ) { // SLWI ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_LEFT_SHIFT, regA, regS, SH); } else if( SH == (32-MB) && ME == 31 ) { // SRWI ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_U, regA, regS, MB); } else { // general handler if (rA != rS) ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regA, regS); if (SH != 0) ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_LEFT_ROTATE, regA, SH); if (mask != 0xFFFFFFFF) ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regA, regA, (sint32)mask); } if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_RLWIMI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regR = _GetRegGPR(ppcImlGenContext, rA); IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); uint32 mask = ppc_mask(MB, ME); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regTmp, regS); if (SH) ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_LEFT_ROTATE, regTmp, SH); if (mask != 0) ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regR, regR, (sint32)~mask); if (mask != 0xFFFFFFFF) ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regTmp, regTmp, (sint32)mask); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regR, regR, regTmp); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regR); return true; } bool PPCRecompilerImlGen_RLWNM(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rS, rA, rB, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, rB, MB, ME); uint32 mask = ppc_mask(MB, ME); IMLReg regS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); IMLReg regA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_LEFT_ROTATE, regA, regS, regB); if( mask != 0xFFFFFFFF ) ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regA, regA, (sint32)mask); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_SRAW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // unlike SRAWI, for SRAW the shift range is 0-63 (masked to 6 bits) // but only shifts up to register bitwidth minus one are well defined in IML so this requires special handling for shifts >= 32 sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); IMLReg regA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); IMLReg regCarry = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); IMLReg regTmpShiftAmount = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); IMLReg regTmpCondBool = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 1); IMLReg regTmp1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 2); IMLReg regTmp2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 3); // load masked shift factor into temporary register ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regTmpShiftAmount, regB, 0x3F); ppcImlGenContext->emitInst().make_compare_s32(regTmpShiftAmount, 31, regTmpCondBool, IMLCondition::UNSIGNED_GT); ppcImlGenContext->emitInst().make_conditional_jump(regTmpCondBool, true); PPCIMLGen_CreateSegmentBranchedPath(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, [&](ppcImlGenContext_t& genCtx) { /* branch taken, shift size 32 or above */ genCtx.emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_S, regA, regS, 31); // shift the sign bit into all the bits genCtx.emitInst().make_compare_s32(regA, 0, regCarry, IMLCondition::NEQ); }, [&](ppcImlGenContext_t& genCtx) { /* branch not taken, shift size below 32 */ genCtx.emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_S, regTmp1, regS, 31); // signMask = input >> 31 (arithmetic shift) genCtx.emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regTmp2, 1); // shiftMask = ((1<<SH)-1) genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_LEFT_SHIFT, regTmp2, regTmp2, regTmpShiftAmount); genCtx.emitInst().make_r_r_s32(PPCREC_IML_OP_SUB, regTmp2, regTmp2, 1); genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_AND, regTmp1, regTmp1, regTmp2); // signMask & shiftMask & input genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_AND, regTmp1, regTmp1, regS); genCtx.emitInst().make_compare_s32(regTmp1, 0, regCarry, IMLCondition::NEQ); genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_RIGHT_SHIFT_S, regA, regS, regTmpShiftAmount); } ); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_SRAWI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int rS, rA; uint32 SH; PPC_OPC_TEMPL_X(opcode, rS, rA, SH); cemu_assert_debug(SH < 32); if (SH == 0) return false; // becomes a no-op (unless RC bit is set) but also sets ca bit to 0? IMLReg regS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rS); IMLReg regA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA); IMLReg regCarry = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); // calculate CA first ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_S, regTmp, regS, 31); // signMask = input >> 31 (arithmetic shift) ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_AND, regTmp, regTmp, regS); // testValue = input & signMask & ((1<<SH)-1) ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regTmp, regTmp, ((1 << SH) - 1)); ppcImlGenContext->emitInst().make_compare_s32(regTmp, 0, regCarry, IMLCondition::NEQ); // ca = (testValue != 0) // do the actual shift ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_S, regA, regS, (sint32)SH); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_SLW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_SLW, regA, regS, regB); if ((opcode & PPC_OPC_RC)) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_SRW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_SRW, regA, regS, regB); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_EXTSH(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN_S16_TO_S32, regA, regS); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_EXTSB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN_S8_TO_S32, regA, regS); if ((opcode & PPC_OPC_RC)) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_CNTLZW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_CNTLZW, regA, regS); if ((opcode & PPC_OPC_RC)) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_NEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NEG, regD, regA); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_LOAD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool signExtend, bool isBigEndian, bool updateAddrReg) { int rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); IMLReg regMemAddr; if (rA == 0) { if (updateAddrReg) return false; // invalid instruction form regMemAddr = _GetRegTemporary(ppcImlGenContext, 0); ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regMemAddr, 0); } else { if (updateAddrReg && rA == rD) return false; // invalid instruction form regMemAddr = _GetRegGPR(ppcImlGenContext, rA); } if (updateAddrReg) { ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, regMemAddr, regMemAddr, (sint32)imm); imm = 0; } IMLReg regDst = _GetRegGPR(ppcImlGenContext, rD); ppcImlGenContext->emitInst().make_r_memory(regDst, regMemAddr, (sint32)imm, bitWidth, signExtend, isBigEndian); return true; } void PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool signExtend, bool isBigEndian, bool updateAddrReg) { // if rA == rD, then the EA wont be stored to rA. We could set updateAddrReg to false in such cases but the end result is the same since the loaded value would overwrite rA sint32 rA, rD, rB; PPC_OPC_TEMPL_X(opcode, rD, rA, rB); updateAddrReg = updateAddrReg && (rA != 0); IMLReg regA = rA != 0 ? _GetRegGPR(ppcImlGenContext, rA) : IMLREG_INVALID; IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regDst = _GetRegGPR(ppcImlGenContext, rD); if (updateAddrReg) { ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regA, regA, regB); // use single register addressing regB = regA; regA = IMLREG_INVALID; } if(regA.IsValid()) PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, regDst, regA, regB, bitWidth, signExtend, isBigEndian); else ppcImlGenContext->emitInst().make_r_memory(regDst, regB, 0, bitWidth, signExtend, isBigEndian); } bool PPCRecompilerImlGen_STORE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool isBigEndian, bool updateAddrReg) { int rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); IMLReg regA; if (rA != 0) { regA = _GetRegGPR(ppcImlGenContext, rA); } else { if (updateAddrReg) return false; // invalid instruction form regA = _GetRegTemporary(ppcImlGenContext, 0); ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regA, 0); } IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); if (updateAddrReg) { if (rD == rA) { // make sure to keep source data intact regD = _GetRegTemporary(ppcImlGenContext, 0); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regD, regA); } ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, regA, regA, (sint32)imm); imm = 0; } ppcImlGenContext->emitInst().make_memory_r(regD, regA, (sint32)imm, bitWidth, isBigEndian); return true; } bool PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool isBigEndian, bool updateAddrReg) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regA = rA != 0 ? _GetRegGPR(ppcImlGenContext, rA) : IMLREG_INVALID; IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regSrc = _GetRegGPR(ppcImlGenContext, rS); if (updateAddrReg) { if(rA == 0) return false; // invalid instruction form if (regSrc == regA) { // make sure to keep source data intact regSrc = _GetRegTemporary(ppcImlGenContext, 0); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regSrc, regA); } ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regA, regA, regB); // use single register addressing regB = regA; regA = IMLREG_INVALID; } if (regA.IsInvalid()) ppcImlGenContext->emitInst().make_memory_r(regSrc, regB, 0, bitWidth, isBigEndian); else PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext, regSrc, regA, regB, bitWidth, false, isBigEndian); return true; } void PPCRecompilerImlGen_LMW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); cemu_assert_debug(rA != 0); sint32 index = 0; while (rD <= 31) { IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); // load word ppcImlGenContext->emitInst().make_r_memory(regD, regA, (sint32)imm + index * 4, 32, false, true); // next rD++; index++; } } void PPCRecompilerImlGen_STMW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rS, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rS, rA, imm); cemu_assert_debug(rA != 0); sint32 index = 0; while( rS <= 31 ) { IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); // store word ppcImlGenContext->emitInst().make_memory_r(regS, regA, (sint32)imm + index * 4, 32, true); // next rS++; index++; } } bool PPCRecompilerImlGen_LSWI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int rA, rD, nb; PPC_OPC_TEMPL_X(opcode, rD, rA, nb); if( nb == 0 ) nb = 32; if (rA == 0) { cemu_assert_unimplemented(); // special form where gpr is ignored and EA is 0 return false; } // potential optimization: On x86 unaligned access is allowed and we could handle the case nb==4 with a single memory read, and nb==2 with a memory read and shift IMLReg memReg = _GetRegGPR(ppcImlGenContext, rA); IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); uint32 memOffset = 0; while (nb > 0) { if (rD == rA) return false; cemu_assert(rD < 32); IMLReg regDst = _GetRegGPR(ppcImlGenContext, rD); // load bytes one-by-one for (sint32 b = 0; b < 4; b++) { ppcImlGenContext->emitInst().make_r_memory(regTmp, memReg, memOffset + b, 8, false, false); sint32 shiftAmount = (3 - b) * 8; if(shiftAmount) ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_LEFT_SHIFT, regTmp, regTmp, shiftAmount); if(b == 0) ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regDst, regTmp); else ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regDst, regDst, regTmp); nb--; if (nb == 0) break; } memOffset += 4; rD++; } return true; } bool PPCRecompilerImlGen_STSWI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int rA, rS, nb; PPC_OPC_TEMPL_X(opcode, rS, rA, nb); if( nb == 0 ) nb = 32; IMLReg regMem = _GetRegGPR(ppcImlGenContext, rA); IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); uint32 memOffset = 0; while (nb > 0) { if (rS == rA) return false; cemu_assert(rS < 32); IMLReg regSrc = _GetRegGPR(ppcImlGenContext, rS); // store bytes one-by-one for (sint32 b = 0; b < 4; b++) { ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regTmp, regSrc); sint32 shiftAmount = (3 - b) * 8; if (shiftAmount) ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_U, regTmp, regTmp, shiftAmount); ppcImlGenContext->emitInst().make_memory_r(regTmp, regMem, memOffset + b, 8, false); nb--; if (nb == 0) break; } memOffset += 4; rS++; } return true; } bool PPCRecompilerImlGen_LWARX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rA, rD, rB; PPC_OPC_TEMPL_X(opcode, rD, rA, rB); IMLReg regA = rA != 0 ? PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA) : IMLREG_INVALID; IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rB); IMLReg regD = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); IMLReg regMemResEA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_CPU_MEMRES_EA); IMLReg regMemResVal = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_CPU_MEMRES_VAL); // calculate EA if (regA.IsValid()) ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regMemResEA, regA, regB); else ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regMemResEA, regB); // load word ppcImlGenContext->emitInst().make_r_memory(regD, regMemResEA, 0, 32, false, true); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regMemResVal, regD); return true; } bool PPCRecompilerImlGen_STWCX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rA, rS, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regA = rA != 0 ? PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA) : IMLREG_INVALID; IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rB); IMLReg regData = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rS); IMLReg regTmpDataBE = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 2); IMLReg regTmpCompareBE = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 3); // calculate EA IMLReg regCalcEA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY); if (regA.IsValid()) ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regCalcEA, regA, regB); else ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regCalcEA, regB); // get CR bit regs and set LT, GT and SO immediately IMLReg regCrLT = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT_INDEX_LT); IMLReg regCrGT = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT_INDEX_GT); IMLReg regCrEQ = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT_INDEX_EQ); IMLReg regCrSO = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT_INDEX_SO); IMLReg regXerSO = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT_INDEX_SO); ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCrLT, 0); ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCrGT, 0); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regCrSO, regXerSO); // get regs for reservation address and value IMLReg regMemResEA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_CPU_MEMRES_EA); IMLReg regMemResVal = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_CPU_MEMRES_VAL); // compare calculated EA with reservation IMLReg regTmpBool = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 1); ppcImlGenContext->emitInst().make_compare(regCalcEA, regMemResEA, regTmpBool, IMLCondition::EQ); ppcImlGenContext->emitInst().make_conditional_jump(regTmpBool, true); PPCIMLGen_CreateSegmentBranchedPath(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, [&](ppcImlGenContext_t& genCtx) { /* branch taken, EA matching */ ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ENDIAN_SWAP, regTmpDataBE, regData); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ENDIAN_SWAP, regTmpCompareBE, regMemResVal); ppcImlGenContext->emitInst().make_atomic_cmp_store(regMemResEA, regTmpCompareBE, regTmpDataBE, regCrEQ); }, [&](ppcImlGenContext_t& genCtx) { /* branch not taken, EA mismatching */ ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCrEQ, 0); } ); // reset reservation // I found contradictory information of whether the reservation is cleared in all cases, so unit testing would be required // Most sources state that it is cleared on successful store. They don't explicitly mention what happens on failure // "The PowerPC 600 series, part 7: Atomic memory access and cache coherency" states that it is always cleared // There may also be different behavior between individual PPC architectures ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regMemResEA, 0); ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regMemResVal, 0); return true; } bool PPCRecompilerImlGen_DCBZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rA, rB; rA = (opcode>>16)&0x1F; rB = (opcode>>11)&0x1F; // prepare registers IMLReg regA = rA!=0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA):IMLREG_INVALID; IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); // load zero into a temporary register IMLReg regZero = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regZero, 0); // prepare EA and align it to cacheline IMLReg regMemResEA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 1); if(rA != 0) ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regMemResEA, regA, regB); else ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regMemResEA, regB); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regMemResEA, regMemResEA, ~31); // zero out the cacheline for(sint32 i = 0; i < 32; i += 4) ppcImlGenContext->emitInst().make_memory_r(regZero, regMemResEA, i, 32, false); return true; } bool PPCRecompilerImlGen_OR_NOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool complementResult) { int rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); if(rS == rB) // check for MR mnemonic ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regA, regS); else ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regA, regS, regB); if(complementResult) ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regA, regA); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_ORC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); // rA = rS | ~rB; IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regB); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regA, regS, regTmp); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_AND_NAND(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool complementResult) { int rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); if (regS == regB) ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regA, regS); else ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_AND, regA, regS, regB); if (complementResult) ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regA, regA); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_ANDC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); // rA = rS & ~rB; IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regB); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_AND, regA, regS, regTmp); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_XOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool complementResult) { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); if( rS == rB ) { ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regA, 0); } else { IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_XOR, regA, regS, regB); } if (complementResult) ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regA, regA); if (opcode & PPC_OPC_RC) PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } void PPCRecompilerImlGen_ANDI_ANDIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isShifted) { sint32 rS, rA; uint32 imm; if (isShifted) { PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); } else { PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); } IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regA, regS, (sint32)imm); // ANDI/ANDIS always updates cr0 PPCImlGen_UpdateCR0(ppcImlGenContext, regA); } void PPCRecompilerImlGen_ORI_ORIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isShifted) { sint32 rS, rA; uint32 imm; if (isShifted) { PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); } else { PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); } IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_OR, regA, regS, (sint32)imm); } void PPCRecompilerImlGen_XORI_XORIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isShifted) { sint32 rS, rA; uint32 imm; if (isShifted) { PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); } else { PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); } IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_XOR, regA, regS, (sint32)imm); } bool PPCRecompilerImlGen_CROR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regCrR, regCrA, regCrB); return true; } bool PPCRecompilerImlGen_CRORC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_XOR, regTmp, regCrB, 1); // invert crB ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regCrR, regCrA, regTmp); return true; } bool PPCRecompilerImlGen_CRAND(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_AND, regCrR, regCrA, regCrB); return true; } bool PPCRecompilerImlGen_CRANDC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_XOR, regTmp, regCrB, 1); // invert crB ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_AND, regCrR, regCrA, regTmp); return true; } bool PPCRecompilerImlGen_CRXOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); if (regCrA == regCrB) { ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCrR, 0); return true; } ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_XOR, regCrR, regCrA, regCrB); return true; } bool PPCRecompilerImlGen_CREQV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); if (regCrA == regCrB) { ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCrR, 1); return true; } IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_XOR, regTmp, regCrB, 1); // invert crB ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_XOR, regCrR, regCrA, regTmp); return true; } bool PPCRecompilerImlGen_HLE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { uint32 hleFuncId = opcode&0xFFFF; ppcImlGenContext->emitInst().make_macro(PPCREC_IML_MACRO_HLE, ppcImlGenContext->ppcAddressOfCurrentInstruction, hleFuncId, 0, IMLREG_INVALID); return true; } uint32 PPCRecompiler_iterateCurrentInstruction(ppcImlGenContext_t* ppcImlGenContext) { uint32 v = CPU_swapEndianU32(*(ppcImlGenContext->currentInstruction)); ppcImlGenContext->currentInstruction += 1; return v; } uint32 PPCRecompiler_getCurrentInstruction(ppcImlGenContext_t* ppcImlGenContext) { uint32 v = CPU_swapEndianU32(*(ppcImlGenContext->currentInstruction)); return v; } uint32 PPCRecompiler_getPreviousInstruction(ppcImlGenContext_t* ppcImlGenContext) { uint32 v = CPU_swapEndianU32(*(ppcImlGenContext->currentInstruction-1)); return v; } void PPCRecompilerIml_setSegmentPoint(IMLSegmentPoint* segmentPoint, IMLSegment* imlSegment, sint32 index) { segmentPoint->imlSegment = imlSegment; segmentPoint->SetInstructionIndex(index); if (imlSegment->segmentPointList) imlSegment->segmentPointList->prev = segmentPoint; segmentPoint->prev = nullptr; segmentPoint->next = imlSegment->segmentPointList; imlSegment->segmentPointList = segmentPoint; } void PPCRecompilerIml_removeSegmentPoint(IMLSegmentPoint* segmentPoint) { if (segmentPoint->prev) segmentPoint->prev->next = segmentPoint->next; else segmentPoint->imlSegment->segmentPointList = segmentPoint->next; if (segmentPoint->next) segmentPoint->next->prev = segmentPoint->prev; } /* * Insert multiple no-op instructions * Warning: Can invalidate any previous instruction pointers from the same segment */ void PPCRecompiler_pushBackIMLInstructions(IMLSegment* imlSegment, sint32 index, sint32 shiftBackCount) { cemu_assert_debug(index >= 0 && index <= imlSegment->imlList.size()); imlSegment->imlList.insert(imlSegment->imlList.begin() + index, shiftBackCount, {}); memset(imlSegment->imlList.data() + index, 0, sizeof(IMLInstruction) * shiftBackCount); // fill empty space with NOP instructions for (sint32 i = 0; i < shiftBackCount; i++) { imlSegment->imlList[index + i].type = PPCREC_IML_TYPE_NONE; } // update position of segment points if (imlSegment->segmentPointList) { IMLSegmentPoint* segmentPoint = imlSegment->segmentPointList; while (segmentPoint) { segmentPoint->ShiftIfAfter(index, shiftBackCount); segmentPoint = segmentPoint->next; } } } IMLInstruction* PPCRecompiler_insertInstruction(IMLSegment* imlSegment, sint32 index) { PPCRecompiler_pushBackIMLInstructions(imlSegment, index, 1); return imlSegment->imlList.data() + index; } IMLInstruction* PPCRecompiler_appendInstruction(IMLSegment* imlSegment) { size_t index = imlSegment->imlList.size(); imlSegment->imlList.emplace_back(); memset(imlSegment->imlList.data() + index, 0, sizeof(IMLInstruction)); return imlSegment->imlList.data() + index; } IMLSegment* PPCRecompilerIml_appendSegment(ppcImlGenContext_t* ppcImlGenContext) { IMLSegment* segment = new IMLSegment(); ppcImlGenContext->segmentList2.emplace_back(segment); return segment; } void PPCRecompilerIml_insertSegments(ppcImlGenContext_t* ppcImlGenContext, sint32 index, sint32 count) { ppcImlGenContext->segmentList2.insert(ppcImlGenContext->segmentList2.begin() + index, count, nullptr); for (sint32 i = 0; i < count; i++) ppcImlGenContext->segmentList2[index + i] = new IMLSegment(); } bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) { bool unsupportedInstructionFound = false; uint32 opcode = PPCRecompiler_iterateCurrentInstruction(ppcImlGenContext); switch ((opcode >> 26)) { case 1: if (PPCRecompilerImlGen_HLE(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 4: // opcode category - paired single switch (PPC_getBits(opcode, 30, 5)) { case 0: // subcategory compare switch (PPC_getBits(opcode, 25, 5)) { case 0: if( !PPCRecompilerImlGen_PS_CMPU0(ppcImlGenContext, opcode) ) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 1: if( !PPCRecompilerImlGen_PS_CMPO0(ppcImlGenContext, opcode) ) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 2: if( !PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext, opcode) ) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; default: unsupportedInstructionFound = true; break; } break; case 8: //Sub category - move/negate switch (PPC_getBits(opcode, 25, 5)) { case 1: // PS negate if (PPCRecompilerImlGen_PS_NEG(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 2: // PS move register if (PPCRecompilerImlGen_PS_MR(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 8: // PS abs if (PPCRecompilerImlGen_PS_ABS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; default: unsupportedInstructionFound = true; break; } break; case 10: if (PPCRecompilerImlGen_PS_SUM0(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 11: if (PPCRecompilerImlGen_PS_SUM1(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 12: // PS_MULS0 if (PPCRecompilerImlGen_PS_MULSX(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 13: // PS_MULS1 if (PPCRecompilerImlGen_PS_MULSX(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 14: // PS_MADDS0 if (PPCRecompilerImlGen_PS_MADDSX(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 15: // PS_MADDS1 if (PPCRecompilerImlGen_PS_MADDSX(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 16: // sub category - merge switch (PPC_getBits(opcode, 25, 5)) { case 16: if (PPCRecompilerImlGen_PS_MERGE00(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 17: if (PPCRecompilerImlGen_PS_MERGE01(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 18: if (PPCRecompilerImlGen_PS_MERGE10(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 19: if (PPCRecompilerImlGen_PS_MERGE11(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; default: unsupportedInstructionFound = true; break; } break; case 18: // divide paired if (PPCRecompilerImlGen_PS_DIV(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 20: // sub paired if (PPCRecompilerImlGen_PS_SUB(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 21: // add paired if (PPCRecompilerImlGen_PS_ADD(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 23: // select paired if (PPCRecompilerImlGen_PS_SEL(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 25: // multiply paired if (PPCRecompilerImlGen_PS_MUL(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 24: // reciprocal paired if (PPCRecompilerImlGen_PS_RES(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 26: // reciprocal squareroot paired if (PPCRecompilerImlGen_PS_RSQRTE(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 28: // PS_MSUB if (PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 29: // PS_MADD if (PPCRecompilerImlGen_PS_MADD(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 30: // PS_NMSUB if (PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 31: // PS_NMADD if (PPCRecompilerImlGen_PS_NMADD(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; default: unsupportedInstructionFound = true; break; } break; case 7: // MULLI PPCRecompilerImlGen_MULLI(ppcImlGenContext, opcode); break; case 8: // SUBFIC if (!PPCRecompilerImlGen_SUBFIC(ppcImlGenContext, opcode)) unsupportedInstructionFound = true; break; case 10: // CMPLI if (!PPCRecompilerImlGen_CMPI(ppcImlGenContext, opcode, true)) unsupportedInstructionFound = true; break; case 11: // CMPI if (!PPCRecompilerImlGen_CMPI(ppcImlGenContext, opcode, false)) unsupportedInstructionFound = true; break; case 12: // ADDIC if (PPCRecompilerImlGen_ADDIC_(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; break; case 13: // ADDIC. if (PPCRecompilerImlGen_ADDIC_(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; break; case 14: // ADDI if (PPCRecompilerImlGen_ADDI(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 15: // ADDIS if (PPCRecompilerImlGen_ADDIS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 16: // BC if (PPCRecompilerImlGen_BC(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 17: if (PPC_getBits(opcode, 30, 1) == 1) { // SC -> no-op } else { unsupportedInstructionFound = true; } break; case 18: // B if (PPCRecompilerImlGen_B(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 19: // opcode category 19 switch (PPC_getBits(opcode, 30, 10)) { case 0: PPCRecompilerImlGen_MCRF(ppcImlGenContext, opcode); break; case 16: // BCLR if (PPCRecompilerImlGen_BCSPR(ppcImlGenContext, opcode, SPR_LR) == false) unsupportedInstructionFound = true; break; case 129: if (PPCRecompilerImlGen_CRANDC(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 150: if (PPCRecompilerImlGen_ISYNC(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 193: if (PPCRecompilerImlGen_CRXOR(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 257: if (PPCRecompilerImlGen_CRAND(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 289: if (PPCRecompilerImlGen_CREQV(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 417: if (PPCRecompilerImlGen_CRORC(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 449: if (PPCRecompilerImlGen_CROR(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 528: // BCCTR if (PPCRecompilerImlGen_BCSPR(ppcImlGenContext, opcode, SPR_CTR) == false) unsupportedInstructionFound = true; break; default: unsupportedInstructionFound = true; break; } break; case 20: if (PPCRecompilerImlGen_RLWIMI(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 21: if (PPCRecompilerImlGen_RLWINM(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 23: if (PPCRecompilerImlGen_RLWNM(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 24: // ORI PPCRecompilerImlGen_ORI_ORIS(ppcImlGenContext, opcode, false); break; case 25: // ORIS PPCRecompilerImlGen_ORI_ORIS(ppcImlGenContext, opcode, true); break; case 26: // XORI PPCRecompilerImlGen_XORI_XORIS(ppcImlGenContext, opcode, false); break; case 27: // XORIS PPCRecompilerImlGen_XORI_XORIS(ppcImlGenContext, opcode, true); break; case 28: // ANDI PPCRecompilerImlGen_ANDI_ANDIS(ppcImlGenContext, opcode, false); break; case 29: // ANDIS PPCRecompilerImlGen_ANDI_ANDIS(ppcImlGenContext, opcode, true); break; case 31: // opcode category switch (PPC_getBits(opcode, 30, 10)) { case 0: PPCRecompilerImlGen_CMP(ppcImlGenContext, opcode, false); break; case 4: PPCRecompilerImlGen_TW(ppcImlGenContext, opcode); break; case 8: if (PPCRecompilerImlGen_SUBFC(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 10: if (PPCRecompilerImlGen_ADDC(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 11: if (PPCRecompilerImlGen_MULHWU(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 19: if (PPCRecompilerImlGen_MFCR(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 20: if (PPCRecompilerImlGen_LWARX(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 23: // LWZX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, true, false); break; case 24: if (PPCRecompilerImlGen_SLW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 26: if (PPCRecompilerImlGen_CNTLZW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 28: // AND if (!PPCRecompilerImlGen_AND_NAND(ppcImlGenContext, opcode, false)) unsupportedInstructionFound = true; break; case 32: PPCRecompilerImlGen_CMP(ppcImlGenContext, opcode, true); // CMPL break; case 40: if (PPCRecompilerImlGen_SUBF(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 54: // DBCST - Generates no code break; case 55: // LWZUX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, true, true); break; case 60: // ANDC if (!PPCRecompilerImlGen_ANDC(ppcImlGenContext, opcode)) unsupportedInstructionFound = true; break; case 75: if (PPCRecompilerImlGen_MULHW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 86: // DCBF -> No-Op break; case 87: // LBZX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 8, false, true, false); break; case 104: if (PPCRecompilerImlGen_NEG(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 119: // LBZUX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 8, false, true, true); break; case 124: // NOR if (!PPCRecompilerImlGen_OR_NOR(ppcImlGenContext, opcode, true)) unsupportedInstructionFound = true; break; case 136: if (PPCRecompilerImlGen_SUBFE(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 138: if (PPCRecompilerImlGen_ADDE(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 144: if( !PPCRecompilerImlGen_MTCRF(ppcImlGenContext, opcode)) unsupportedInstructionFound = true; break; case 150: if (!PPCRecompilerImlGen_STWCX(ppcImlGenContext, opcode)) unsupportedInstructionFound = true; break; case 151: // STWX if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 32, true, false)) unsupportedInstructionFound = true; break; case 183: // STWUX if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 32, true, true)) unsupportedInstructionFound = true; break; case 200: if (PPCRecompilerImlGen_SUBFZE(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 202: if (PPCRecompilerImlGen_ADDZE(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 215: // STBX if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 8, true, false)) unsupportedInstructionFound = true; break; case 234: if (PPCRecompilerImlGen_ADDME(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 235: if (PPCRecompilerImlGen_MULLW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 247: // STBUX if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 8, true, true)) unsupportedInstructionFound = true; break; case 266: if (PPCRecompilerImlGen_ADD(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 279: // LHZX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, true, false); break; case 284: // EQV (alias to NXOR) if (!PPCRecompilerImlGen_XOR(ppcImlGenContext, opcode, true)) unsupportedInstructionFound = true; break; case 311: // LHZUX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, true, true); break; case 316: // XOR if (!PPCRecompilerImlGen_XOR(ppcImlGenContext, opcode, false)) unsupportedInstructionFound = true; break; case 339: if (PPCRecompilerImlGen_MFSPR(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 343: // LHAX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, true, true, false); break; case 371: if (PPCRecompilerImlGen_MFTB(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 375: // LHAUX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, true, true, true); break; case 407: // STHX if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16, true, false)) unsupportedInstructionFound = true; break; case 412: if (PPCRecompilerImlGen_ORC(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 439: // STHUX if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16, true, true)) unsupportedInstructionFound = true; break; case 444: // OR if (!PPCRecompilerImlGen_OR_NOR(ppcImlGenContext, opcode, false)) unsupportedInstructionFound = true; break; case 459: PPCRecompilerImlGen_DIVWU(ppcImlGenContext, opcode); break; case 467: if (PPCRecompilerImlGen_MTSPR(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 476: // NAND if (!PPCRecompilerImlGen_AND_NAND(ppcImlGenContext, opcode, true)) unsupportedInstructionFound = true; break; case 491: if (PPCRecompilerImlGen_DIVW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 534: // LWBRX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, false, false); break; case 535: // LFSX if (PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext, opcode, false, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 536: if (PPCRecompilerImlGen_SRW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 567: // LFSUX if (PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext, opcode, true, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 597: if (PPCRecompilerImlGen_LSWI(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 598: PPCRecompilerImlGen_SYNC(ppcImlGenContext, opcode); break; case 599: // LFDX if (PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext, opcode, false, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 631: // LFDUX if (PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext, opcode, true, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 662: // STWBRX if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 32, false, false)) unsupportedInstructionFound = true; break; case 663: // STFSX if (PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext, opcode, false, false) == false) unsupportedInstructionFound = true; break; case 695: // STFSUX if (PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext, opcode, true, false) == false) unsupportedInstructionFound = true; break; case 725: if (PPCRecompilerImlGen_STSWI(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 727: // STFDX if (PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext, opcode, false, true) == false) unsupportedInstructionFound = true; break; case 759: // STFDUX if (PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext, opcode, true, true) == false) unsupportedInstructionFound = true; break; case 790: // LHBRX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, false, false); break; case 792: if (PPCRecompilerImlGen_SRAW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 824: if (PPCRecompilerImlGen_SRAWI(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 918: // STHBRX if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16, false, true)) unsupportedInstructionFound = true; break; case 922: if (PPCRecompilerImlGen_EXTSH(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 954: if (PPCRecompilerImlGen_EXTSB(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 983: if (PPCRecompilerImlGen_STFIWX(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; case 1014: if (PPCRecompilerImlGen_DCBZ(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; default: unsupportedInstructionFound = true; break; } break; case 32: // LWZ if(!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 32, false, true, false)) unsupportedInstructionFound = true; break; case 33: // LWZU if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 32, false, true, true)) unsupportedInstructionFound = true; break; case 34: // LBZ if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 8, false, true, false)) unsupportedInstructionFound = true; break; case 35: // LBZU if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 8, false, true, true)) unsupportedInstructionFound = true; break; case 36: // STW if(!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 32, true, false)) unsupportedInstructionFound = true; break; case 37: // STWU if (!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 32, true, true)) unsupportedInstructionFound = true; break; case 38: // STB if (!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 8, true, false)) unsupportedInstructionFound = true; break; case 39: // STBU if (!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 8, true, true)) unsupportedInstructionFound = true; break; case 40: // LHZ if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 16, false, true, false)) unsupportedInstructionFound = true; break; case 41: // LHZU if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 16, false, true, true)) unsupportedInstructionFound = true; break; case 42: // LHA if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 16, true, true, false)) unsupportedInstructionFound = true; break; case 43: // LHAU if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 16, true, true, true)) unsupportedInstructionFound = true; break; case 44: // STH if (!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 16, true, false)) unsupportedInstructionFound = true; break; case 45: // STHU if (!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 16, true, true)) unsupportedInstructionFound = true; break; case 46: PPCRecompilerImlGen_LMW(ppcImlGenContext, opcode); break; case 47: PPCRecompilerImlGen_STMW(ppcImlGenContext, opcode); break; case 48: // LFS if (PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext, opcode, false, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 49: // LFSU if (PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext, opcode, true, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 50: // LFD if (PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext, opcode, false, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 51: // LFDU if (PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext, opcode, true, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 52: // STFS if (PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext, opcode, false, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 53: // STFSU if (PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext, opcode, true, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 54: // STFD if (PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext, opcode, false, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 55: // STFDU if (PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext, opcode, true, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 56: if (PPCRecompilerImlGen_PSQ_L(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 57: if (PPCRecompilerImlGen_PSQ_L(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 59: // opcode category switch (PPC_getBits(opcode, 30, 5)) { case 18: if (PPCRecompilerImlGen_FDIVS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 20: if (PPCRecompilerImlGen_FSUBS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 21: if (PPCRecompilerImlGen_FADDS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 24: if (PPCRecompilerImlGen_FRES(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 25: if (PPCRecompilerImlGen_FMULS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 28: if (PPCRecompilerImlGen_FMSUBS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 29: if (PPCRecompilerImlGen_FMADDS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 30: if (PPCRecompilerImlGen_FNMSUBS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; default: unsupportedInstructionFound = true; break; } break; case 60: if (PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 61: if (PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 63: // opcode category switch (PPC_getBits(opcode, 30, 5)) { case 0: if (PPCRecompilerImlGen_FCMPU(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 12: if (PPCRecompilerImlGen_FRSP(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 15: if (PPCRecompilerImlGen_FCTIWZ(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 18: if (PPCRecompilerImlGen_FDIV(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 20: if (PPCRecompilerImlGen_FSUB(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 21: if (PPCRecompilerImlGen_FADD(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 23: if (PPCRecompilerImlGen_FSEL(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 25: if (PPCRecompilerImlGen_FMUL(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 26: if (PPCRecompilerImlGen_FRSQRTE(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 28: if (PPCRecompilerImlGen_FMSUB(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 29: if (PPCRecompilerImlGen_FMADD(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 30: if (PPCRecompilerImlGen_FNMSUB(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; default: switch (PPC_getBits(opcode, 30, 10)) { case 32: if (PPCRecompilerImlGen_FCMPO(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 40: if (PPCRecompilerImlGen_FNEG(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 72: if (PPCRecompilerImlGen_FMR(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 136: if (PPCRecompilerImlGen_FNABS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 264: if (PPCRecompilerImlGen_FABS(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; default: unsupportedInstructionFound = true; break; } break; } break; default: unsupportedInstructionFound = true; break; } return unsupportedInstructionFound; } // returns false if code flow is not interrupted bool PPCRecompiler_CheckIfInstructionEndsSegment(PPCFunctionBoundaryTracker& boundaryTracker, uint32 instructionAddress, uint32 opcode, bool& makeNextInstEnterable, bool& continueDefaultPath, bool& hasBranchTarget, uint32& branchTarget) { hasBranchTarget = false; branchTarget = 0xFFFFFFFF; makeNextInstEnterable = false; continueDefaultPath = false; switch (Espresso::GetPrimaryOpcode(opcode)) { case Espresso::PrimaryOpcode::VIRTUAL_HLE: { makeNextInstEnterable = true; hasBranchTarget = false; continueDefaultPath = false; return true; } case Espresso::PrimaryOpcode::BC: { uint32 BD, BI; Espresso::BOField BO; bool AA, LK; Espresso::decodeOp_BC(opcode, BD, BO, BI, AA, LK); if (!LK) { hasBranchTarget = true; branchTarget = (AA ? BD : BD) + instructionAddress; if (!boundaryTracker.ContainsAddress(branchTarget)) hasBranchTarget = false; // far jump } makeNextInstEnterable = LK; continueDefaultPath = true; return true; } case Espresso::PrimaryOpcode::B: { uint32 LI; bool AA, LK; Espresso::decodeOp_B(opcode, LI, AA, LK); if (!LK) { hasBranchTarget = true; branchTarget = AA ? LI : LI + instructionAddress; if (!boundaryTracker.ContainsAddress(branchTarget)) hasBranchTarget = false; // far jump } makeNextInstEnterable = LK; continueDefaultPath = false; return true; } case Espresso::PrimaryOpcode::GROUP_19: switch (Espresso::GetGroup19Opcode(opcode)) { case Espresso::Opcode19::BCLR: case Espresso::Opcode19::BCCTR: { Espresso::BOField BO; uint32 BI; bool LK; Espresso::decodeOp_BCSPR(opcode, BO, BI, LK); continueDefaultPath = !BO.conditionIgnore() || !BO.decrementerIgnore(); // if branch is always taken then there is no continued path makeNextInstEnterable = Espresso::DecodeLK(opcode); return true; } default: break; } break; case Espresso::PrimaryOpcode::GROUP_31: switch (Espresso::GetGroup31Opcode(opcode)) { default: break; } break; default: break; } return false; } void PPCRecompiler_DetermineBasicBlockRange(std::vector<PPCBasicBlockInfo>& basicBlockList, PPCFunctionBoundaryTracker& boundaryTracker, uint32 ppcStart, uint32 ppcEnd, const std::set<uint32>& combinedBranchTargets, const std::set<uint32>& entryAddresses) { cemu_assert_debug(ppcStart <= ppcEnd); uint32 currentAddr = ppcStart; PPCBasicBlockInfo* curBlockInfo = &basicBlockList.emplace_back(currentAddr, entryAddresses); uint32 basicBlockStart = currentAddr; while (currentAddr <= ppcEnd) { curBlockInfo->lastAddress = currentAddr; uint32 opcode = memory_readU32(currentAddr); bool nextInstIsEnterable = false; bool hasBranchTarget = false; bool hasContinuedFlow = false; uint32 branchTarget = 0; if (PPCRecompiler_CheckIfInstructionEndsSegment(boundaryTracker, currentAddr, opcode, nextInstIsEnterable, hasContinuedFlow, hasBranchTarget, branchTarget)) { curBlockInfo->hasBranchTarget = hasBranchTarget; curBlockInfo->branchTarget = branchTarget; curBlockInfo->hasContinuedFlow = hasContinuedFlow; // start new basic block, except if this is the last instruction if (currentAddr >= ppcEnd) break; curBlockInfo = &basicBlockList.emplace_back(currentAddr + 4, entryAddresses); curBlockInfo->isEnterable = curBlockInfo->isEnterable || nextInstIsEnterable; currentAddr += 4; continue; } currentAddr += 4; if (currentAddr <= ppcEnd) { if (combinedBranchTargets.find(currentAddr) != combinedBranchTargets.end()) { // instruction is branch target, start new basic block curBlockInfo = &basicBlockList.emplace_back(currentAddr, entryAddresses); } } } } std::vector<PPCBasicBlockInfo> PPCRecompiler_DetermineBasicBlockRange(PPCFunctionBoundaryTracker& boundaryTracker, const std::set<uint32>& entryAddresses) { cemu_assert(!entryAddresses.empty()); std::vector<PPCBasicBlockInfo> basicBlockList; const std::set<uint32> branchTargets = boundaryTracker.GetBranchTargets(); auto funcRanges = boundaryTracker.GetRanges(); std::set<uint32> combinedBranchTargets = branchTargets; combinedBranchTargets.insert(entryAddresses.begin(), entryAddresses.end()); for (auto& funcRangeIt : funcRanges) PPCRecompiler_DetermineBasicBlockRange(basicBlockList, boundaryTracker, funcRangeIt.startAddress, funcRangeIt.startAddress + funcRangeIt.length - 4, combinedBranchTargets, entryAddresses); // mark all segments that start at entryAddresses as enterable (debug code for verification, can be removed) size_t numMarkedEnterable = 0; for (auto& basicBlockIt : basicBlockList) { if (entryAddresses.find(basicBlockIt.startAddress) != entryAddresses.end()) { cemu_assert_debug(basicBlockIt.isEnterable); numMarkedEnterable++; } } cemu_assert_debug(numMarkedEnterable == entryAddresses.size()); // todo - inline BL, currently this is done in the instruction handler of BL but this will mean that instruction cycle increasing is ignored return basicBlockList; } bool PPCIMLGen_FillBasicBlock(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo) { ppcImlGenContext.currentOutputSegment = basicBlockInfo.GetSegmentForInstructionAppend(); ppcImlGenContext.currentInstruction = (uint32*)(memory_base + basicBlockInfo.startAddress); uint32* firstCurrentInstruction = ppcImlGenContext.currentInstruction; uint32* endCurrentInstruction = (uint32*)(memory_base + basicBlockInfo.lastAddress); while (ppcImlGenContext.currentInstruction <= endCurrentInstruction) { uint32 addressOfCurrentInstruction = (uint32)((uint8*)ppcImlGenContext.currentInstruction - memory_base); ppcImlGenContext.ppcAddressOfCurrentInstruction = addressOfCurrentInstruction; if (PPCRecompiler_decodePPCInstruction(&ppcImlGenContext)) { cemuLog_logDebug(LogType::Force, "PPCRecompiler: Unsupported instruction at 0x{:08x}", addressOfCurrentInstruction); ppcImlGenContext.currentOutputSegment = nullptr; return false; } } ppcImlGenContext.currentOutputSegment = nullptr; return true; } // returns split segment from which the continued segment is available via seg->GetBranchNotTaken() IMLSegment* PPCIMLGen_CreateSplitSegmentAtEnd(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo) { IMLSegment* writeSegment = basicBlockInfo.GetSegmentForInstructionAppend(); IMLSegment* continuedSegment = ppcImlGenContext.InsertSegment(ppcImlGenContext.GetSegmentIndex(writeSegment) + 1); continuedSegment->SetLinkBranchTaken(writeSegment->GetBranchTaken()); continuedSegment->SetLinkBranchNotTaken(writeSegment->GetBranchNotTaken()); writeSegment->SetLinkBranchNotTaken(continuedSegment); writeSegment->SetLinkBranchTaken(nullptr); if (ppcImlGenContext.currentOutputSegment == writeSegment) ppcImlGenContext.currentOutputSegment = continuedSegment; cemu_assert_debug(basicBlockInfo.appendSegment == writeSegment); basicBlockInfo.appendSegment = continuedSegment; return writeSegment; } // generates a new segment and sets it as branch target for the current write segment. Returns the created segment IMLSegment* PPCIMLGen_CreateNewSegmentAsBranchTarget(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo) { IMLSegment* writeSegment = basicBlockInfo.GetSegmentForInstructionAppend(); IMLSegment* branchTargetSegment = ppcImlGenContext.NewSegment(); cemu_assert_debug(!writeSegment->GetBranchTaken()); // must not have a target already writeSegment->SetLinkBranchTaken(branchTargetSegment); return branchTargetSegment; } // verify that current instruction is the last instruction of the active basic block void PPCIMLGen_AssertIfNotLastSegmentInstruction(ppcImlGenContext_t& ppcImlGenContext) { cemu_assert_debug(ppcImlGenContext.currentBasicBlock->lastAddress == ppcImlGenContext.ppcAddressOfCurrentInstruction); } bool PPCRecompiler_IsBasicBlockATightFiniteLoop(IMLSegment* imlSegment, PPCBasicBlockInfo& basicBlockInfo) { // if we detect a finite loop we can skip generating the cycle check // currently we only check for BDNZ loops since thats reasonably safe to rely on // however there are other forms of loops that can be classified as finite, // but detecting those involves analyzing PPC code and we dont have the infrastructure for that (e.g. IML has CheckRegisterUsage but we dont have an equivalent for PPC code) // base criteria, must jump to beginning of same segment if (imlSegment->nextSegmentBranchTaken != imlSegment) return false; uint32 opcode = *(uint32be*)(memory_base + basicBlockInfo.lastAddress); if (Espresso::GetPrimaryOpcode(opcode) != Espresso::PrimaryOpcode::BC) return false; uint32 BO, BI, BD; PPC_OPC_TEMPL_B(opcode, BO, BI, BD); Espresso::BOField boField(BO); if(!boField.conditionIgnore() || boField.branchAlways()) return false; if(boField.decrementerIgnore()) return false; return true; } void PPCRecompiler_HandleCycleCheckCount(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo) { IMLSegment* imlSegment = basicBlockInfo.GetFirstSegmentInChain(); if (!basicBlockInfo.hasBranchTarget) return; if (basicBlockInfo.branchTarget > basicBlockInfo.startAddress) return; if (PPCRecompiler_IsBasicBlockATightFiniteLoop(imlSegment, basicBlockInfo)) return; // make the segment enterable so execution can return after passing a check basicBlockInfo.GetFirstSegmentInChain()->SetEnterable(basicBlockInfo.startAddress); IMLSegment* splitSeg = PPCIMLGen_CreateSplitSegmentAtEnd(ppcImlGenContext, basicBlockInfo); splitSeg->AppendInstruction()->make_cjump_cycle_check(); IMLSegment* exitSegment = ppcImlGenContext.NewSegment(); splitSeg->SetLinkBranchTaken(exitSegment); exitSegment->AppendInstruction()->make_macro(PPCREC_IML_MACRO_LEAVE, basicBlockInfo.startAddress, 0, 0, IMLREG_INVALID); cemu_assert_debug(splitSeg->nextSegmentBranchNotTaken); // let the IML optimizer and RA know that the original segment should be used during analysis for dead code elimination exitSegment->SetNextSegmentForOverwriteHints(splitSeg->nextSegmentBranchNotTaken); } void PPCRecompiler_SetSegmentsUncertainFlow(ppcImlGenContext_t& ppcImlGenContext) { for (IMLSegment* segIt : ppcImlGenContext.segmentList2) { // handle empty segment if (segIt->imlList.empty()) { cemu_assert_debug(segIt->GetBranchNotTaken()); continue; } // check last instruction of segment IMLInstruction* imlInstruction = segIt->GetLastInstruction(); if (imlInstruction->type == PPCREC_IML_TYPE_MACRO) { auto macroType = imlInstruction->operation; switch (macroType) { case PPCREC_IML_MACRO_B_TO_REG: case PPCREC_IML_MACRO_BL: case PPCREC_IML_MACRO_B_FAR: case PPCREC_IML_MACRO_HLE: case PPCREC_IML_MACRO_LEAVE: segIt->nextSegmentIsUncertain = true; break; case PPCREC_IML_MACRO_DEBUGBREAK: case PPCREC_IML_MACRO_COUNT_CYCLES: break; default: cemu_assert_unimplemented(); } } } } bool PPCRecompiler_GenerateIML(ppcImlGenContext_t& ppcImlGenContext, PPCFunctionBoundaryTracker& boundaryTracker, std::set<uint32>& entryAddresses) { std::vector<PPCBasicBlockInfo> basicBlockList = PPCRecompiler_DetermineBasicBlockRange(boundaryTracker, entryAddresses); // create segments std::unordered_map<uint32, PPCBasicBlockInfo*> addrToBB; ppcImlGenContext.segmentList2.resize(basicBlockList.size()); for (size_t i = 0; i < basicBlockList.size(); i++) { PPCBasicBlockInfo& basicBlockInfo = basicBlockList[i]; IMLSegment* seg = new IMLSegment(); seg->ppcAddress = basicBlockInfo.startAddress; if(basicBlockInfo.isEnterable) seg->SetEnterable(basicBlockInfo.startAddress); ppcImlGenContext.segmentList2[i] = seg; cemu_assert_debug(addrToBB.find(basicBlockInfo.startAddress) == addrToBB.end()); basicBlockInfo.SetInitialSegment(seg); addrToBB.emplace(basicBlockInfo.startAddress, &basicBlockInfo); } // link segments for (size_t i = 0; i < basicBlockList.size(); i++) { PPCBasicBlockInfo& bbInfo = basicBlockList[i]; cemu_assert_debug(bbInfo.GetFirstSegmentInChain() == bbInfo.GetSegmentForInstructionAppend()); IMLSegment* seg = ppcImlGenContext.segmentList2[i]; if (bbInfo.hasBranchTarget) { PPCBasicBlockInfo* targetBB = addrToBB[bbInfo.branchTarget]; cemu_assert_debug(targetBB); IMLSegment_SetLinkBranchTaken(seg, targetBB->GetFirstSegmentInChain()); } if (bbInfo.hasContinuedFlow) { PPCBasicBlockInfo* targetBB = addrToBB[bbInfo.lastAddress + 4]; if (!targetBB) { cemuLog_log(LogType::Recompiler, "Recompiler was unable to link segment [0x{:08x}-0x{:08x}] to 0x{:08x}", bbInfo.startAddress, bbInfo.lastAddress, bbInfo.lastAddress + 4); return false; } cemu_assert_debug(targetBB); IMLSegment_SetLinkBranchNotTaken(seg, targetBB->GetFirstSegmentInChain()); } } // we assume that all unreachable segments are potentially enterable // todo - mark them as such // generate cycle counters // in theory we could generate these as part of FillBasicBlock() but in the future we might use more complex logic to emit fewer operations for (size_t i = 0; i < basicBlockList.size(); i++) { PPCBasicBlockInfo& basicBlockInfo = basicBlockList[i]; IMLSegment* seg = basicBlockInfo.GetSegmentForInstructionAppend(); uint32 ppcInstructionCount = (basicBlockInfo.lastAddress - basicBlockInfo.startAddress + 4) / 4; cemu_assert_debug(ppcInstructionCount > 0); PPCRecompiler_pushBackIMLInstructions(seg, 0, 1); seg->imlList[0].type = PPCREC_IML_TYPE_MACRO; seg->imlList[0].operation = PPCREC_IML_MACRO_COUNT_CYCLES; seg->imlList[0].op_macro.param = ppcInstructionCount; } // generate cycle check instructions // note: Introduces new segments for (size_t i = 0; i < basicBlockList.size(); i++) { PPCBasicBlockInfo& basicBlockInfo = basicBlockList[i]; PPCRecompiler_HandleCycleCheckCount(ppcImlGenContext, basicBlockInfo); } // fill in all the basic blocks // note: This step introduces new segments as is necessary for some instructions for (size_t i = 0; i < basicBlockList.size(); i++) { PPCBasicBlockInfo& basicBlockInfo = basicBlockList[i]; ppcImlGenContext.currentBasicBlock = &basicBlockInfo; if (!PPCIMLGen_FillBasicBlock(ppcImlGenContext, basicBlockInfo)) return false; ppcImlGenContext.currentBasicBlock = nullptr; } // mark segments with unknown jump destination (e.g. BLR and most macros) PPCRecompiler_SetSegmentsUncertainFlow(ppcImlGenContext); // debug - check segment graph #ifdef CEMU_DEBUG_ASSERT //for (size_t i = 0; i < basicBlockList.size(); i++) //{ // IMLSegment* seg = ppcImlGenContext.segmentList2[i]; // if (seg->list_prevSegments.empty()) // { // cemu_assert_debug(seg->isEnterable); // } //} // debug - check if suffix instructions are at the end of segments and if they are present for branching segments for (size_t segIndex = 0; segIndex < ppcImlGenContext.segmentList2.size(); segIndex++) { IMLSegment* seg = ppcImlGenContext.segmentList2[segIndex]; IMLSegment* nextSeg = (segIndex+1) < ppcImlGenContext.segmentList2.size() ? ppcImlGenContext.segmentList2[segIndex + 1] : nullptr; if (seg->imlList.size() > 0) { for (size_t f = 0; f < seg->imlList.size() - 1; f++) { if (seg->imlList[f].IsSuffixInstruction()) { debug_printf("---------------- SegmentDump (Suffix instruction at wrong pos in segment 0x%x):\n", (int)segIndex); IMLDebug_Dump(&ppcImlGenContext); DEBUG_BREAK; } } } if (seg->nextSegmentBranchTaken) { if (!seg->HasSuffixInstruction()) { debug_printf("---------------- SegmentDump (NoSuffixInstruction in segment 0x%x):\n", (int)segIndex); IMLDebug_Dump(&ppcImlGenContext); DEBUG_BREAK; } } if (seg->nextSegmentBranchNotTaken) { // if branch not taken, flow must continue to next segment in sequence cemu_assert_debug(seg->nextSegmentBranchNotTaken == nextSeg); } // more detailed checks based on actual suffix instruction if (seg->imlList.size() > 0) { IMLInstruction* inst = seg->GetLastInstruction(); if (inst->type == PPCREC_IML_TYPE_MACRO && inst->op_macro.param == PPCREC_IML_MACRO_B_FAR) { cemu_assert_debug(!seg->GetBranchTaken()); cemu_assert_debug(!seg->GetBranchNotTaken()); } if (inst->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) { cemu_assert_debug(seg->GetBranchTaken()); cemu_assert_debug(seg->GetBranchNotTaken()); } if (inst->type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) { if (!seg->GetBranchTaken() || !seg->GetBranchNotTaken()) { debug_printf("---------------- SegmentDump (Missing branch for conditional jump in segment 0x%x):\n", (int)segIndex); IMLDebug_Dump(&ppcImlGenContext); cemu_assert_error(); } } } segIndex++; } #endif // todos: // - basic block determination should look for the B(L) B(L) pattern. Or maybe just mark every bb without any input segments as an entry segment return true; } bool PPCRecompiler_generateIntermediateCode(ppcImlGenContext_t& ppcImlGenContext, PPCRecFunction_t* ppcRecFunc, std::set<uint32>& entryAddresses, PPCFunctionBoundaryTracker& boundaryTracker) { ppcImlGenContext.boundaryTracker = &boundaryTracker; if (!PPCRecompiler_GenerateIML(ppcImlGenContext, boundaryTracker, entryAddresses)) return false; // set range // todo - support non-continuous functions for the range tracking? ppcRecRange_t recRange; recRange.ppcAddress = ppcRecFunc->ppcAddress; recRange.ppcSize = ppcRecFunc->ppcSize; ppcRecFunc->list_ranges.push_back(recRange); return true; } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp ================================================ #include "Cafe/HW/Espresso/EspressoISA.h" #include "../Interpreter/PPCInterpreterInternal.h" #include "PPCRecompiler.h" #include "PPCRecompilerIml.h" #include "Cafe/GameProfile/GameProfile.h" #include "IML/IML.h" ATTR_MS_ABI double frsqrte_espresso(double input); ATTR_MS_ABI double fres_espresso(double input); IMLReg _GetRegCR(ppcImlGenContext_t* ppcImlGenContext, uint8 crReg, uint8 crBit); #define DefinePS0(name, regIndex) IMLReg name = _GetFPRRegPS0(ppcImlGenContext, regIndex); #define DefinePS1(name, regIndex) IMLReg name = _GetFPRRegPS1(ppcImlGenContext, regIndex); #define DefinePSX(name, regIndex, isPS1) IMLReg name = isPS1 ? _GetFPRRegPS1(ppcImlGenContext, regIndex) : _GetFPRRegPS0(ppcImlGenContext, regIndex); #define DefineTempFPR(name, index) IMLReg name = _GetFPRTemp(ppcImlGenContext, index); IMLReg _GetFPRRegPS0(ppcImlGenContext_t* ppcImlGenContext, uint32 regIndex) { cemu_assert_debug(regIndex < 32); return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, PPCREC_NAME_FPR_HALF + regIndex * 2 + 0, IMLRegFormat::F64); } IMLReg _GetFPRRegPS1(ppcImlGenContext_t* ppcImlGenContext, uint32 regIndex) { cemu_assert_debug(regIndex < 32); return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, PPCREC_NAME_FPR_HALF + regIndex * 2 + 1, IMLRegFormat::F64); } IMLReg _GetFPRTemp(ppcImlGenContext_t* ppcImlGenContext, uint32 index) { cemu_assert_debug(index < 4); return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0 + index, IMLRegFormat::F64); } IMLReg _GetFPRReg(ppcImlGenContext_t* ppcImlGenContext, uint32 regIndex, bool selectPS1) { cemu_assert_debug(regIndex < 32); return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, PPCREC_NAME_FPR_HALF + regIndex * 2 + (selectPS1 ? 1 : 0), IMLRegFormat::F64); } void PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext_t* ppcImlGenContext, IMLReg fprRegister, bool flushDenormals=false) { ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprRegister); if( flushDenormals ) assert_dbg(); } bool PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble) { sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); if (withUpdate) { // add imm to memory register cemu_assert_debug(rA != 0); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); imm = 0; // set imm to 0 so we dont add it twice } DefinePS0(fpPs0, frD); if (isDouble) { // LFD/LFDU ppcImlGenContext->emitInst().make_fpr_r_memory(fpPs0, gprRegister, imm, PPCREC_FPR_LD_MODE_DOUBLE, true); } else { // LFS/LFSU ppcImlGenContext->emitInst().make_fpr_r_memory(fpPs0, gprRegister, imm, PPCREC_FPR_LD_MODE_SINGLE, true); if( ppcImlGenContext->LSQE ) { DefinePS1(fpPs1, frD); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fpPs1, fpPs0); } } return true; } bool PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble) { sint32 rA, frD, rB; PPC_OPC_TEMPL_X(opcode, frD, rA, rB); if( rA == 0 ) { debugBreakpoint(); return false; } // get memory gpr registers IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); if (withUpdate) ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, gprRegister1, gprRegister1, gprRegister2); DefinePS0(fpPs0, frD); if (isDouble) { if (withUpdate) ppcImlGenContext->emitInst().make_fpr_r_memory(fpPs0, gprRegister1, 0, PPCREC_FPR_LD_MODE_DOUBLE, true); else ppcImlGenContext->emitInst().make_fpr_r_memory_indexed(fpPs0, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_DOUBLE, true); } else { if (withUpdate) ppcImlGenContext->emitInst().make_fpr_r_memory( fpPs0, gprRegister1, 0, PPCREC_FPR_LD_MODE_SINGLE, true); else ppcImlGenContext->emitInst().make_fpr_r_memory_indexed( fpPs0, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_SINGLE, true); if( ppcImlGenContext->LSQE ) { DefinePS1(fpPs1, frD); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fpPs1, fpPs0); } } return true; } bool PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble) { sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); DefinePS0(fpPs0, frD); if (withUpdate) { ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); imm = 0; } if (isDouble) ppcImlGenContext->emitInst().make_fpr_memory_r(fpPs0, gprRegister, imm, PPCREC_FPR_ST_MODE_DOUBLE, true); else ppcImlGenContext->emitInst().make_fpr_memory_r(fpPs0, gprRegister, imm, PPCREC_FPR_ST_MODE_SINGLE, true); return true; } bool PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool hasUpdate, bool isDouble) { sint32 rA, frS, rB; PPC_OPC_TEMPL_X(opcode, frS, rA, rB); if( rA == 0 ) { debugBreakpoint(); return false; } // get memory gpr registers IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); if (hasUpdate) { ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, gprRegister1, gprRegister1, gprRegister2); } DefinePS0(fpPs0, frS); auto mode = isDouble ? PPCREC_FPR_ST_MODE_DOUBLE : PPCREC_FPR_ST_MODE_SINGLE; if( ppcImlGenContext->LSQE ) { if (hasUpdate) ppcImlGenContext->emitInst().make_fpr_memory_r(fpPs0, gprRegister1, 0, mode, true); else ppcImlGenContext->emitInst().make_fpr_memory_r_indexed(fpPs0, gprRegister1, gprRegister2, 0, mode, true); } else { if (hasUpdate) ppcImlGenContext->emitInst().make_fpr_memory_r(fpPs0, gprRegister1, 0, mode, true); else ppcImlGenContext->emitInst().make_fpr_memory_r_indexed(fpPs0, gprRegister1, gprRegister2, 0, mode, true); } return true; } bool PPCRecompilerImlGen_STFIWX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rA, frS, rB; PPC_OPC_TEMPL_X(opcode, frS, rA, rB); DefinePS0(fpPs0, frS); IMLReg gprRegister1; IMLReg gprRegister2; if( rA != 0 ) { gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); } else { // rA is not used gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); gprRegister2 = IMLREG_INVALID; } if( rA != 0 ) ppcImlGenContext->emitInst().make_fpr_memory_r_indexed(fpPs0, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, true); else ppcImlGenContext->emitInst().make_fpr_memory_r(fpPs0, gprRegister1, 0, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, true); return true; } bool PPCRecompilerImlGen_FADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); PPC_ASSERT(frC==0); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprD, frD); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_ADD, fprD, fprA, fprB); return true; } bool PPCRecompilerImlGen_FSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); PPC_ASSERT(frC==0); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprD, frD); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_SUB, fprD, fprA, fprB); return true; } bool PPCRecompilerImlGen_FMUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB_unused, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB_unused, frC); if( frD == frC ) { // swap frA and frB sint32 temp = frA; frA = frC; frC = temp; } DefinePS0(fprA, frA); DefinePS0(fprC, frC); DefinePS0(fprD, frD); // move frA to frD (if different register) if( frD != frA ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // multiply bottom double of frD with bottom double of frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprD, fprC); return true; } bool PPCRecompilerImlGen_FDIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC_unused; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC_unused); PPC_ASSERT(frB==0); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprD, frD); if( frB == frD && frA != frB ) { DefineTempFPR(fprTemp, 0); // move frA to temporary register ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp, fprA); // divide bottom double of temporary register by bottom double of frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprTemp, fprB); // move result to frD ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprTemp); return true; } // move frA to frD (if different register) if( frD != frA ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // copy ps0 // divide bottom double of frD by bottom double of frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprD, fprB); return true; } bool PPCRecompilerImlGen_FMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprC, frC); DefinePS0(fprD, frD); // if frB is already in frD we need a temporary register to store the product of frA*frC if( frB == frD ) { DefineTempFPR(fprTemp, 0); // move frA to temporary register ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp, fprA); // multiply bottom double of temporary register with bottom double of frC ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp, fprC); // add result to frD ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprD, fprTemp); return true; } // if frC == frD -> swap registers, we assume that frC != frD if( frD == frC ) { // swap frA and frC IMLReg temp = fprA; fprA = fprC; fprC = temp; } // move frA to frD (if different register) if( frD != frA ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // always copy ps0 and ps1 // multiply bottom double of frD with bottom double of frC ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprD, fprC); // add frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprD, fprB); return true; } bool PPCRecompilerImlGen_FMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprC, frC); DefinePS0(fprD, frD); if( frB == frD ) { // if frB is already in frD we need a temporary register to store the product of frA*frC DefineTempFPR(fprTemp, 0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp, fprA); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp, fprC); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprTemp, fprB); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprTemp); return false; } if( frD == frC ) { // swap frA and frC IMLReg temp = fprA; fprA = fprC; fprC = temp; } // move frA to frD if( frD != frA ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // multiply bottom double of frD with bottom double of frC ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprD, fprC); // sub frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprD, fprB); return true; } bool PPCRecompilerImlGen_FNMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprC, frC); DefinePS0(fprD, frD); // if frB is already in frD we need a temporary register to store the product of frA*frC if( frB == frD ) { DefineTempFPR(fprTemp, 0); // move frA to temporary register ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp, fprA); // multiply bottom double of temporary register with bottom double of frC ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp, fprC); // sub frB from temporary register ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprTemp, fprB); // negate result ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprTemp); // move result to frD ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprTemp); return true; } // if frC == frD -> swap registers, we assume that frC != frD if( frD == frC ) { // swap frA and frC IMLReg temp = fprA; fprA = fprC; fprC = temp; } // move frA to frD (if different register) if( frD != frA ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // multiply bottom double of frD with bottom double of frC ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprD, fprC); // sub frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprD, fprB); // negate result ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprD); return true; } #define PSE_CopyResultToPs1() if( ppcImlGenContext->PSE ) \ { \ DefinePS1(fprDPS1, frD); \ ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDPS1, fprD); \ } bool PPCRecompilerImlGen_FMULS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB_unused, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB_unused, frC); if( frD == frC ) { // swap frA and frC sint32 temp = frA; frA = frC; frC = temp; } DefinePS0(fprA, frA); DefinePS0(fprC, frC); DefinePS0(fprD, frD); // move frA to frD (if different register) if( frD != frA ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // multiply bottom double of frD with bottom double of frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprD, fprC); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); // if paired single mode, copy frD ps0 to ps1 PSE_CopyResultToPs1(); return true; } bool PPCRecompilerImlGen_FDIVS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC_unused; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC_unused); PPC_ASSERT(frB==0); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprD, frD); if( frB == frD && frA != frB ) { DefineTempFPR(fprTemp, 0); // move frA to temporary register ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp, fprA); // divide bottom double of temporary register by bottom double of frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprTemp, fprB); // move result to frD ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprTemp); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); PSE_CopyResultToPs1(); return true; } // move frA to frD (if different register) if( frD != frA ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // subtract bottom double of frB from bottom double of frD ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprD, fprB); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); PSE_CopyResultToPs1(); return true; } bool PPCRecompilerImlGen_FADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); if( frD == frB ) { // swap frA and frB sint32 temp = frA; frA = frB; frB = temp; } DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprD, frD); // move frA to frD (if different register) if( frD != frA ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // add bottom double of frD and bottom double of frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprD, fprB); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); PSE_CopyResultToPs1(); return true; } bool PPCRecompilerImlGen_FSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); PPC_ASSERT(frB==0); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprD, frD); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_SUB, fprD, fprA, fprB); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); PSE_CopyResultToPs1(); return true; } bool PPCRecompilerImlGen_FMADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprC, frC); DefinePS0(fprD, frD); // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register IMLReg fprRegisterTemp; if( frD != frA && frD != frB && frD != frC ) fprRegisterTemp = fprD; else fprRegisterTemp = _GetFPRTemp(ppcImlGenContext, 0); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprRegisterTemp, fprA, fprC); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprRegisterTemp, fprB); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprRegisterTemp); // set result if( fprD != fprRegisterTemp ) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprRegisterTemp); } PSE_CopyResultToPs1(); return true; } bool PPCRecompilerImlGen_FMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprC, frC); DefinePS0(fprD, frD); IMLReg fprRegisterTemp; // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register if( frD != frA && frD != frB && frD != frC ) fprRegisterTemp = fprD; else fprRegisterTemp = _GetFPRTemp(ppcImlGenContext, 0); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprRegisterTemp, fprA, fprC); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprRegisterTemp, fprB); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprRegisterTemp); // set result if( fprD != fprRegisterTemp ) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprRegisterTemp); } PSE_CopyResultToPs1(); return true; } bool PPCRecompilerImlGen_FNMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprC, frC); DefinePS0(fprD, frD); IMLReg fprRegisterTemp; // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register if( frD != frA && frD != frB && frD != frC ) fprRegisterTemp = fprD; else fprRegisterTemp = _GetFPRTemp(ppcImlGenContext, 0); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprRegisterTemp, fprA, fprC); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprRegisterTemp, fprB); ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprRegisterTemp); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprRegisterTemp); // set result if( fprD != fprRegisterTemp ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprRegisterTemp); PSE_CopyResultToPs1(); return true; } bool PPCRecompilerImlGen_FCMPO(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // Not implemented return false; } bool PPCRecompilerImlGen_FCMPU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 crfD, frA, frB; PPC_OPC_TEMPL_X(opcode, crfD, frA, frB); crfD >>= 2; DefinePS0(fprA, frA); DefinePS0(fprB, frB); IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_LT); IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_GT); IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_EQ); IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_SO); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegLT, IMLCondition::UNORDERED_LT); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegGT, IMLCondition::UNORDERED_GT); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegEQ, IMLCondition::UNORDERED_EQ); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegSO, IMLCondition::UNORDERED_U); // todo: set fpscr return true; } bool PPCRecompilerImlGen_FMR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, rA, frB; PPC_OPC_TEMPL_X(opcode, frD, rA, frB); DefinePS0(fprB, frB); DefinePS0(fprD, frD); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprB); return true; } bool PPCRecompilerImlGen_FABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); DefinePS0(fprB, frB); DefinePS0(fprD, frD); if( frD != frB ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprB); ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_ABS, fprD); return true; } bool PPCRecompilerImlGen_FNABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); DefinePS0(fprB, frB); DefinePS0(fprD, frD); if( frD != frB ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprB); ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATIVE_ABS, fprD); return true; } bool PPCRecompilerImlGen_FRES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); DefinePS0(fprB, frB); DefinePS0(fprD, frD); ppcImlGenContext->emitInst().make_call_imm((uintptr_t)fres_espresso, fprB, IMLREG_INVALID, IMLREG_INVALID, fprD); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); PSE_CopyResultToPs1(); return true; } bool PPCRecompilerImlGen_FRSP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); DefinePS0(fprB, frB); DefinePS0(fprD, frD); if( fprD != fprB ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprB); ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprD); PSE_CopyResultToPs1(); return true; } bool PPCRecompilerImlGen_FNEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); if( opcode&PPC_OPC_RC ) return false; DefinePS0(fprB, frB); DefinePS0(fprD, frD); if( frD != frB ) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprB); ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprD); return true; } bool PPCRecompilerImlGen_FSEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); if( opcode&PPC_OPC_RC ) { return false; } DefinePS0(fprA, frA); DefinePS0(fprB, frB); DefinePS0(fprC, frC); DefinePS0(fprD, frD); ppcImlGenContext->emitInst().make_fpr_r_r_r_r(PPCREC_IML_OP_FPR_SELECT, fprD, fprA, fprB, fprC); return true; } bool PPCRecompilerImlGen_FRSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); DefinePS0(fprB, frB); DefinePS0(fprD, frD); ppcImlGenContext->emitInst().make_call_imm((uintptr_t)frsqrte_espresso, fprB, IMLREG_INVALID, IMLREG_INVALID, fprD); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); return true; } bool PPCRecompilerImlGen_FCTIWZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); DefinePS0(fprB, frB); DefinePS0(fprD, frD); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FCTIWZ, fprD, fprB); return true; } bool PPCRecompiler_isUGQRValueKnown(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, uint32& gqrValue); void PPCRecompilerImlGen_ClampInteger(ppcImlGenContext_t* ppcImlGenContext, IMLReg reg, sint32 clampMin, sint32 clampMax) { IMLReg regTmpCondBool = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 1); // min(reg, clampMax) ppcImlGenContext->emitInst().make_compare_s32(reg, clampMax, regTmpCondBool, IMLCondition::SIGNED_GT); ppcImlGenContext->emitInst().make_conditional_jump(regTmpCondBool, false); // condition needs to be inverted because we skip if the condition is true PPCIMLGen_CreateSegmentBranchedPath(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, [&](ppcImlGenContext_t& genCtx) { /* branch not taken */ genCtx.emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, reg, clampMax); } ); // max(reg, clampMin) ppcImlGenContext->emitInst().make_compare_s32(reg, clampMin, regTmpCondBool, IMLCondition::SIGNED_LT); ppcImlGenContext->emitInst().make_conditional_jump(regTmpCondBool, false); PPCIMLGen_CreateSegmentBranchedPath(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, [&](ppcImlGenContext_t& genCtx) { /* branch not taken */ genCtx.emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, reg, clampMin); } ); } void PPCRecompilerIMLGen_GetPSQScale(ppcImlGenContext_t* ppcImlGenContext, IMLReg gqrRegister, IMLReg fprRegScaleOut, bool isLoad) { IMLReg gprTmp2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 2); // extract scale factor and sign extend it constexpr sint32 scaleBitWidth = 6; ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_LEFT_SHIFT, gprTmp2, gqrRegister, 32 - ((isLoad ? 24 : 8) + scaleBitWidth)); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_S, gprTmp2, gprTmp2, (32 - 23) - scaleBitWidth); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, gprTmp2, gprTmp2, 0x1FF<<23); if (isLoad) ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NEG, gprTmp2, gprTmp2); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprTmp2, gprTmp2, 0x7F<<23); // gprTmp2 now holds the scale float bits, bitcast to float ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT, fprRegScaleOut, gprTmp2); } void PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, Espresso::PSQ_LOAD_TYPE loadType, bool readPS1, IMLReg gprA, sint32 imm, IMLReg fprDPS0, IMLReg fprDPS1) { if (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_F32) { ppcImlGenContext->emitInst().make_fpr_r_memory(fprDPS0, gprA, imm, PPCREC_FPR_LD_MODE_SINGLE, true); if(readPS1) { ppcImlGenContext->emitInst().make_fpr_r_memory(fprDPS1, gprA, imm + 4, PPCREC_FPR_LD_MODE_SINGLE, true); } } if (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_U16 || loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S16) { // get scale factor IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); IMLReg fprScaleReg = _GetFPRTemp(ppcImlGenContext, 2); PPCRecompilerIMLGen_GetPSQScale(ppcImlGenContext, gqrRegister, fprScaleReg, true); bool isSigned = (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S16); IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm, 16, isSigned, true); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS0, gprTmp); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDPS0, fprDPS0, fprScaleReg); if(readPS1) { ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm + 2, 16, isSigned, true); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS1, gprTmp); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDPS1, fprDPS1, fprScaleReg); } } else if (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_U8 || loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S8) { // get scale factor IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); IMLReg fprScaleReg = _GetFPRTemp(ppcImlGenContext, 2); PPCRecompilerIMLGen_GetPSQScale(ppcImlGenContext, gqrRegister, fprScaleReg, true); bool isSigned = (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S8); IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm, 8, isSigned, true); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS0, gprTmp); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDPS0, fprDPS0, fprScaleReg); if(readPS1) { ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm + 1, 8, isSigned, true); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS1, gprTmp); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDPS1, fprDPS1, fprScaleReg); } } } // PSQ_L and PSQ_LU bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate) { int rA, frD; uint32 immUnused; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); sint32 gqrIndex = ((opcode >> 12) & 7); uint32 imm = opcode & 0xFFF; if (imm & 0x800) imm |= ~0xFFF; bool readPS1 = (opcode & 0x8000) == false; IMLReg gprA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); DefinePS0(fprDPS0, frD); DefinePS1(fprDPS1, frD); if (!readPS1) { // if PS1 is not explicitly read then set it to 1.0 ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_LOAD_ONE, fprDPS1); } if (withUpdate) { ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprA, gprA, (sint32)imm); imm = 0; } uint32 knownGQRValue = 0; if ( !PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, knownGQRValue) ) { // generate complex dynamic handler when we dont know the GQR value ahead of time IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); IMLReg loadTypeReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); // extract the load type from the GQR register ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_U, loadTypeReg, gqrRegister, 16); ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, loadTypeReg, loadTypeReg, 0x7); IMLSegment* caseSegment[6]; sint32 compareValues[6] = {0, 4, 5, 6, 7}; PPCIMLGen_CreateSegmentBranchedPathMultiple(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, caseSegment, loadTypeReg, compareValues, 5, 0); for (sint32 i=0; i<5; i++) { IMLRedirectInstOutput outputToCase(ppcImlGenContext, caseSegment[i]); // while this is in scope, instructions go to caseSegment[i] PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext, gqrIndex, static_cast<Espresso::PSQ_LOAD_TYPE>(compareValues[i]), readPS1, gprA, imm, fprDPS0, fprDPS1); // create the case jump instructions here because we need to add it last caseSegment[i]->AppendInstruction()->make_jump(); } return true; } Espresso::PSQ_LOAD_TYPE type = static_cast<Espresso::PSQ_LOAD_TYPE>((knownGQRValue >> 16) & 0x7); sint32 scale = (knownGQRValue >> 24) & 0x3F; cemu_assert_debug(scale == 0); // known GQR values always use a scale of 0 (1.0f) if (scale != 0) return false; if (type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED1 || type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED2 || type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED3) { return false; } PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext, gqrIndex, type, readPS1, gprA, imm, fprDPS0, fprDPS1); return true; } void PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, Espresso::PSQ_LOAD_TYPE storeType, bool storePS1, IMLReg gprA, sint32 imm, IMLReg fprDPS0, IMLReg fprDPS1) { cemu_assert_debug(!storePS1 || fprDPS1.IsValid()); if (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_F32) { ppcImlGenContext->emitInst().make_fpr_memory_r(fprDPS0, gprA, imm, PPCREC_FPR_ST_MODE_SINGLE, true); if(storePS1) { ppcImlGenContext->emitInst().make_fpr_memory_r(fprDPS1, gprA, imm + 4, PPCREC_FPR_ST_MODE_SINGLE, true); } } else if (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_U16 || storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S16) { // get scale factor IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); IMLReg fprScaleReg = _GetFPRTemp(ppcImlGenContext, 2); PPCRecompilerIMLGen_GetPSQScale(ppcImlGenContext, gqrRegister, fprScaleReg, false); bool isSigned = (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S16); IMLReg fprTmp = _GetFPRTemp(ppcImlGenContext, 0); IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp, fprDPS0, fprScaleReg); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprTmp); if (isSigned) PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -32768, 32767); else PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, 0, 65535); ppcImlGenContext->emitInst().make_memory_r(gprTmp, gprA, imm, 16, true); if(storePS1) { ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp, fprDPS1, fprScaleReg); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprTmp); if (isSigned) PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -32768, 32767); else PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, 0, 65535); ppcImlGenContext->emitInst().make_memory_r(gprTmp, gprA, imm + 2, 16, true); } } else if (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_U8 || storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S8) { // get scale factor IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); IMLReg fprScaleReg = _GetFPRTemp(ppcImlGenContext, 2); PPCRecompilerIMLGen_GetPSQScale(ppcImlGenContext, gqrRegister, fprScaleReg, false); bool isSigned = (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S8); IMLReg fprTmp = _GetFPRTemp(ppcImlGenContext, 0); IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp, fprDPS0, fprScaleReg); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprTmp); if (isSigned) PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -128, 127); else PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, 0, 255); ppcImlGenContext->emitInst().make_memory_r(gprTmp, gprA, imm, 8, true); if(storePS1) { ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp, fprDPS1, fprScaleReg); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprTmp); if (isSigned) PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -128, 127); else PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, 0, 255); ppcImlGenContext->emitInst().make_memory_r(gprTmp, gprA, imm + 1, 8, true); } } } // PSQ_ST and PSQ_STU bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate) { int rA, frD; uint32 immUnused; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); uint32 imm = opcode & 0xFFF; if (imm & 0x800) imm |= ~0xFFF; sint32 gqrIndex = ((opcode >> 12) & 7); bool storePS1 = (opcode & 0x8000) == false; IMLReg gprA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); DefinePS0(fprDPS0, frD); IMLReg fprDPS1 = storePS1 ? _GetFPRRegPS1(ppcImlGenContext, frD) : IMLREG_INVALID; if (withUpdate) { ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprA, gprA, (sint32)imm); imm = 0; } uint32 gqrValue = 0; if ( !PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, gqrValue) ) { // generate complex dynamic handler when we dont know the GQR value ahead of time IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); IMLReg loadTypeReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); // extract the load type from the GQR register ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, loadTypeReg, gqrRegister, 0x7); IMLSegment* caseSegment[5]; sint32 compareValues[5] = {0, 4, 5, 6, 7}; PPCIMLGen_CreateSegmentBranchedPathMultiple(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, caseSegment, loadTypeReg, compareValues, 5, 0); for (sint32 i=0; i<5; i++) { IMLRedirectInstOutput outputToCase(ppcImlGenContext, caseSegment[i]); // while this is in scope, instructions go to caseSegment[i] PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext, gqrIndex, static_cast<Espresso::PSQ_LOAD_TYPE>(compareValues[i]), storePS1, gprA, imm, fprDPS0, fprDPS1); ppcImlGenContext->emitInst().make_jump(); // finalize case } return true; } Espresso::PSQ_LOAD_TYPE type = static_cast<Espresso::PSQ_LOAD_TYPE>((gqrValue >> 0) & 0x7); sint32 scale = (gqrValue >> 24) & 0x3F; cemu_assert_debug(scale == 0); // known GQR values always use a scale of 0 (1.0f) if (type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED1 || type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED2 || type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED3) { return false; } PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext, gqrIndex, type, storePS1, gprA, imm, fprDPS0, fprDPS1); return true; } // PS_MULS0 and PS_MULS1 bool PPCRecompilerImlGen_PS_MULSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isVariant1) { sint32 frD, frA, frC; frC = (opcode>>6)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(fprAps0, frA); DefinePS1(fprAps1, frA); DefinePSX(fprC, frC, isVariant1); DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefineTempFPR(fprTmp0, 0); DefineTempFPR(fprTmp1, 1); // todo - optimize cases where a temporary is not necessary // todo - round fprC to 25bit accuracy // copy ps0 and ps1 to temporary ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTmp0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTmp1, fprAps1); // multiply ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp0, fprC); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp1, fprC); // copy back to result ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTmp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTmp1); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } // PS_MADDS0 and PS_MADDS1 bool PPCRecompilerImlGen_PS_MADDSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isVariant1) { sint32 frD, frA, frB, frC; frC = (opcode>>6)&0x1F; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(fprAps0, frA); DefinePS1(fprAps1, frA); DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); DefinePSX(fprC, frC, isVariant1); DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefineTempFPR(fprTmp0, 0); DefineTempFPR(fprTmp1, 1); // todo - round C to 25bit // todo - optimize cases where a temporary is not necessary // copy ps0 and ps1 to temporary ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTmp0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTmp1, fprAps1); // multiply ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp0, fprC); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp1, fprC); // add ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTmp0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTmp1, fprBps1); // copy back to result ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTmp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTmp1); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_ADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 + hCPU->fpr[frB].fp1; DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefinePS0(fprAps0, frA); DefinePS1(fprAps1, frA); DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); if( frD == frA ) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); } else if( frD == frB ) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprAps1); } else { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprAps1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); } // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_SUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 - hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 - hCPU->fpr[frB].fp1; DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefinePS0(fprAps0, frA); DefinePS1(fprAps1, frA); DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_SUB, fprDps0, fprAps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_SUB, fprDps1, fprAps1, fprBps1); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_MUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frC; frC = (opcode >> 6) & 0x1F; frA = (opcode >> 16) & 0x1F; frD = (opcode >> 21) & 0x1F; DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefinePS0(fprAps0, frA); DefinePS1(fprAps1, frA); DefinePS0(fprCps0, frC); DefinePS1(fprCps1, frC); DefineTempFPR(fprTemp0, 0); DefineTempFPR(fprTemp1, 1); // todo: Optimize for when a temporary isnt necessary // todo: Round to 25bit? ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp0, fprCps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp1, fprCps1); if (frD == frA) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprTemp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprTemp1); } else { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp1, fprAps1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTemp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTemp1); } // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_DIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; frB = (opcode >> 11) & 0x1F; frA = (opcode >> 16) & 0x1F; frD = (opcode >> 21) & 0x1F; //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 / hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 / hCPU->fpr[frB].fp1; DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefinePS0(fprAps0, frA); DefinePS1(fprAps1, frA); DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); if (frD == frA) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprDps1, fprBps1); } else { DefineTempFPR(fprTemp0, 0); DefineTempFPR(fprTemp1, 1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp1, fprAps1); // we divide temporary by frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprTemp0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprTemp1, fprBps1); // copy result to frD ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTemp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTemp1); } // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_MADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; frC = (opcode>>6)&0x1F; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; //float s0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 + hCPU->fpr[frB].fp0); //float s1 = (float)(hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp1 + hCPU->fpr[frB].fp1); DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefinePS0(fprAps0, frA); DefinePS1(fprAps1, frA); DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); DefinePS0(fprCps0, frC); DefinePS1(fprCps1, frC); if (frD != frA && frD != frB) { if (frD == frC) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprCps0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprCps1, fprAps1); } else { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprAps1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprCps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprCps1); } ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); } else { DefineTempFPR(fprTemp0, 0); DefineTempFPR(fprTemp1, 1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp0, fprCps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp1, fprCps1); if( frD == frA && frD != frB ) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprTemp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprTemp1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); } else { // we multiply temporary by frA ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp1, fprAps1); // add frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTemp0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTemp1, fprBps1); // copy result to frD ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTemp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTemp1); } } // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_NMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; frC = (opcode>>6)&0x1F; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefinePS0(fprAps0, frA); DefinePS1(fprAps1, frA); DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); DefinePS0(fprCps0, frC); DefinePS1(fprCps1, frC); DefineTempFPR(fprTemp0, 0); DefineTempFPR(fprTemp1, 1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp0, fprCps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp1, fprCps1); // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register // if frD == frA and frD != frB we can multiply frD immediately and save a copy instruction if( frD == frA && frD != frB ) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprTemp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprTemp1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); } else { // we multiply temporary by frA ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp1, fprAps1); // add frB ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTemp0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTemp1, fprBps1); // copy result to frD ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTemp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTemp1); } // negate ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps0); ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps1); // adjust accuracy //PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); // Splatoon requires that we emulate flush-to-denormals for this instruction //ppcImlGenContext->emitInst().make_fpr_r(NULL,PPCREC_IML_OP_FPR_ROUND_FLDN_TO_SINGLE_PRECISION_PAIR, fprRegisterD, false); return true; } // PS_MSUB and PS_NMSUB bool PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withNegative) { sint32 frD, frA, frB, frC; frC = (opcode>>6)&0x1F; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefinePS0(fprAps0, frA); DefinePS1(fprAps1, frA); DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); DefinePS0(fprCps0, frC); DefinePS1(fprCps1, frC); if (frD != frA && frD != frB) { if (frD == frC) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprCps0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprCps1, fprAps1); } else { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprAps1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprCps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprCps1); } ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprDps1, fprBps1); } else { DefineTempFPR(fprTemp0, 0); DefineTempFPR(fprTemp1, 1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp0, fprCps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp1, fprCps1); if( frD == frA && frD != frB ) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprTemp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprTemp1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprDps1, fprBps1); } else { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp1, fprAps1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprTemp0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprTemp1, fprBps1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTemp0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTemp1); } } // negate result if (withNegative) { ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps0); ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps1); } // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_SUM0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; frC = (opcode>>6)&0x1F; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefinePS0(fprAps0, frA); DefinePS1(fprBps1, frB); DefinePS1(fprCps1, frC); if( frD == frA ) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps1); } else { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps1); } if (fprDps1 != fprCps1) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprCps1); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_SUM1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; frC = (opcode>>6)&0x1F; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); DefinePS0(fprAps0, frA); DefinePS1(fprBps1, frB); DefinePS0(fprCps0, frC); if (frB != frD) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); } else ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprCps0); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_NEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frB; frB = (opcode>>11)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); if (frB != frD) { // copy ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprBps1); } ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps0); ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps1); return true; } bool PPCRecompilerImlGen_PS_ABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frB; frB = (opcode>>11)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprBps1); ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_ABS, fprDps0); ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_ABS, fprDps1); return true; } bool PPCRecompilerImlGen_PS_RES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frB; frB = (opcode>>11)&0x1F; frD = (opcode>>21)&0x1F; //hCPU->fpr[frD].fp0 = (float)(1.0f / (float)hCPU->fpr[frB].fp0); //hCPU->fpr[frD].fp1 = (float)(1.0f / (float)hCPU->fpr[frB].fp1); DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); ppcImlGenContext->emitInst().make_call_imm((uintptr_t)fres_espresso, fprBps0, IMLREG_INVALID, IMLREG_INVALID, fprDps0); ppcImlGenContext->emitInst().make_call_imm((uintptr_t)fres_espresso, fprBps1, IMLREG_INVALID, IMLREG_INVALID, fprDps1); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_RSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frB; frB = (opcode>>11)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); ppcImlGenContext->emitInst().make_call_imm((uintptr_t)frsqrte_espresso, fprBps0, IMLREG_INVALID, IMLREG_INVALID, fprDps0); ppcImlGenContext->emitInst().make_call_imm((uintptr_t)frsqrte_espresso, fprBps1, IMLREG_INVALID, IMLREG_INVALID, fprDps1); // adjust accuracy PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } bool PPCRecompilerImlGen_PS_MR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frB; frB = (opcode>>11)&0x1F; frD = (opcode>>21)&0x1F; if( frB != frD ) { DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprBps1); } return true; } bool PPCRecompilerImlGen_PS_SEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB, frC; frC = (opcode>>6)&0x1F; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(fprAps0, frA); DefinePS1(fprAps1, frA); DefinePS0(fprBps0, frB); DefinePS1(fprBps1, frB); DefinePS0(fprCps0, frC); DefinePS1(fprCps1, frC); DefinePS0(fprDps0, frD); DefinePS1(fprDps1, frD); ppcImlGenContext->emitInst().make_fpr_r_r_r_r(PPCREC_IML_OP_FPR_SELECT, fprDps0, fprAps0, fprBps0, fprCps0); ppcImlGenContext->emitInst().make_fpr_r_r_r_r(PPCREC_IML_OP_FPR_SELECT, fprDps1, fprAps1, fprBps1, fprCps1); return true; } bool PPCRecompilerImlGen_PS_MERGE00(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(frpAps0, frA); DefinePS0(frpBps0, frB); DefinePS0(frpDps0, frD); DefinePS1(frpDps1, frD); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps1, frpBps0); if (frpDps0 != frpAps0) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps0, frpAps0); return true; } bool PPCRecompilerImlGen_PS_MERGE01(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS0(frpAps0, frA); DefinePS1(frpBps1, frB); DefinePS0(frpDps0, frD); DefinePS1(frpDps1, frD); if (frpDps0 != frpAps0) ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps0, frpAps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps1, frpBps1); return true; } bool PPCRecompilerImlGen_PS_MERGE10(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS1(frpAps1, frA); DefinePS0(frpBps0, frB); DefinePS0(frpDps0, frD); DefinePS1(frpDps1, frD); if (frD != frB) { ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps0, frpAps1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps1, frpBps0); } else { DefineTempFPR(frpTemp, 0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpTemp, frpBps0); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps0, frpAps1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps1, frpTemp); } return true; } bool PPCRecompilerImlGen_PS_MERGE11(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; DefinePS1(frpAps1, frA); DefinePS1(frpBps1, frB); DefinePS0(frpDps0, frD); DefinePS1(frpDps1, frD); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps0, frpAps1); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps1, frpBps1); return true; } bool PPCRecompilerImlGen_PS_CMPO0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { // Not implemented return false; } bool PPCRecompilerImlGen_PS_CMPU0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 crfD, frA, frB; frB = (opcode >> 11) & 0x1F; frA = (opcode >> 16) & 0x1F; crfD = (opcode >> 23) & 0x7; DefinePS0(fprA, frA); DefinePS0(fprB, frB); IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_LT); IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_GT); IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_EQ); IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_SO); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegLT, IMLCondition::UNORDERED_LT); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegGT, IMLCondition::UNORDERED_GT); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegEQ, IMLCondition::UNORDERED_EQ); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegSO, IMLCondition::UNORDERED_U); // todo: set fpscr return true; } bool PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 crfD, frA, frB; frB = (opcode >> 11) & 0x1F; frA = (opcode >> 16) & 0x1F; crfD = (opcode >> 23) & 0x7; DefinePS1(fprA, frA); DefinePS1(fprB, frB); IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_LT); IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_GT); IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_EQ); IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_SO); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegLT, IMLCondition::UNORDERED_LT); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegGT, IMLCondition::UNORDERED_GT); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegEQ, IMLCondition::UNORDERED_EQ); ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegSO, IMLCondition::UNORDERED_U); return true; } ================================================ FILE: src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp ================================================ #include "PPCRecompiler.h" #include "PPCRecompilerIml.h" void PPCRecompilerIML_isolateEnterableSegments(ppcImlGenContext_t* ppcImlGenContext) { size_t initialSegmentCount = ppcImlGenContext->segmentList2.size(); for (size_t i = 0; i < initialSegmentCount; i++) { IMLSegment* imlSegment = ppcImlGenContext->segmentList2[i]; if (imlSegment->list_prevSegments.empty() == false && imlSegment->isEnterable) { // spawn new segment at end PPCRecompilerIml_insertSegments(ppcImlGenContext, ppcImlGenContext->segmentList2.size(), 1); IMLSegment* entrySegment = ppcImlGenContext->segmentList2[ppcImlGenContext->segmentList2.size()-1]; entrySegment->isEnterable = true; entrySegment->enterPPCAddress = imlSegment->enterPPCAddress; // create jump instruction PPCRecompiler_pushBackIMLInstructions(entrySegment, 0, 1); entrySegment->imlList.data()[0].make_jump(); IMLSegment_SetLinkBranchTaken(entrySegment, imlSegment); // remove enterable flag from original segment imlSegment->isEnterable = false; imlSegment->enterPPCAddress = 0; } } } ================================================ FILE: src/Cafe/HW/Latte/Common/RegisterSerializer.cpp ================================================ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Common/RegisterSerializer.h" #include "Common/FileStream.h" #include <zstd.h> #include <zlib.h> // compression dictionary for pipeline Latte register data, initialized on boot ZSTD_CDict* s_c_regDict{}; ZSTD_DDict* s_d_regDict{}; struct GPURegSerializerMapping { uint32 regAddr; uint32 regCount; }; GPURegSerializerMapping g_gpuRegSerializerMapping_v1[] = { // tied to GPUCompactedRegisterState - DO NOT MODIFY // this list is based on the same list used internally by GX2ContextState, excluding ALU constants // Config register {0x2232, 0x2}, {0x2235, 0x1}, {0x223A, 0x1}, {0x2256, 0x1}, {0x22C8, 0x1}, {0x2300, 0x6}, {0x2310, 0xC}, {0x2363, 0x1}, {0x2404, 0x2}, {0x2542, 0x1}, {0x25C5, 0x1}, {0x260C, 0x1}, {0x2900, 0x48}, // sampler border color VS {0x2980, 0x48}, // sampler border color PS {0x2A00, 0x48}, // sampler border color GS // Context register {0xA000, 0x2}, {0xA003, 0x3}, {0xA00A, 0x4}, {0xA010, 0x38}, // color buffer registers + others {0xA050, 0x34}, // SQ_ALU_CONST_BUFFER_SIZE_PS_0 {0xA08C, 0x1}, {0xA08E, 0x4}, {0xA094, 0x40}, {0xA0D5, 0x1}, {0xA0E0, 0x20}, {0xA100, 0x9}, {0xA10C, 0x3}, {0xA10F, 0x60}, // mostly PA_CL_VPORT_* registers {0xA185, 0xA}, {0xA191, 0x27}, {0xA1E0, 0x9}, {0xA200, 0x1}, {0xA202, 0x7}, {0xA210, 0x29}, {0xA250, 0x34}, {0xA284, 0xC}, {0xA290, 0x1}, {0xA292, 0x2}, {0xA29B, 0x1}, {0xA2A1, 0x1}, {0xA2A5, 0x1}, {0xA2A8, 0x2}, {0xA2AC, 0x3}, {0xA2B4, 0x3}, {0xA2B8, 0x3}, {0xA2BC, 0x3}, {0xA2C0, 0x3}, {0xA2C8, 0x1}, {0xA2CA, 0x1}, {0xA2CC, 0x1}, {0xA2CE, 0x1}, {0xA300, 0x9}, {0xA30C, 0x1}, {0xA312, 0x1}, {0xA316, 0x2}, {0xA343, 0x2}, {0xA349, 0x3}, {0xA34C, 0x2}, {0xA351, 0x1}, {0xA37E, 0x6}, // Resource registers {0xE000, 0x70}, {0xE380, 0x70}, {0xE460, 0x70}, {0xE7E0, 0x70}, {0xE8B9, 0x7}, {0xE8C0, 0x70}, {0xE930, 0x70}, {0xECB0, 0x70}, {0xED89, 0x7}, // Sampler registers {0xF000, 0x36}, {0xF036, 0x36}, {0xF06C, 0x36}, // Loop const registers {0xF880, 0x60} }; namespace Latte { void StoreGPURegisterState(const LatteContextRegister& contextRegister, GPUCompactedRegisterState& registerStateOut) { uint32* regView = contextRegister.GetRawView(); // read mapped registers into linear array uint32* writePtr = registerStateOut.rawArray; for (auto& itr : g_gpuRegSerializerMapping_v1) { uint32* readPtr = contextRegister.GetRawView() + itr.regAddr; for (uint32 i = 0; i < itr.regCount; i++) { *writePtr = *readPtr; readPtr++; writePtr++; } } uint32 numStoredRegs = (uint32)(writePtr - registerStateOut.rawArray); cemu_assert(numStoredRegs * 4 == sizeof(registerStateOut.rawArray)); } void LoadGPURegisterState(LatteContextRegister& contextRegisterOut, const GPUCompactedRegisterState& registerState) { uint32* regView = contextRegisterOut.GetRawView(); // read mapped registers into linear array const uint32* readPtr = registerState.rawArray; for (auto& itr : g_gpuRegSerializerMapping_v1) { uint32* writePtr = contextRegisterOut.GetRawView() + itr.regAddr; for (uint32 i = 0; i < itr.regCount; i++) { *writePtr = *readPtr; writePtr++; readPtr++; } } uint32 numStoredRegs = (uint32)(readPtr - registerState.rawArray); cemu_assert(numStoredRegs * 4 == sizeof(registerState.rawArray)); } // register data is mostly zero words // this very simple RLE compression will collapse all the zero-byte ranges giving a pretty substantial compression ratio on its own // we can then further use zstd-dictionary compression on it to end up with ultra compact serialized data (50-300 bytes) void _CompressZeros(uint32* regArray, uint32 regCount, MemStreamWriter& memWriter) { uint32 index = 0; uint8 numZeroWords = 0; while (index < regCount) { if (regArray[index] == 0) { numZeroWords++; if (numZeroWords == 0x7F) { memWriter.writeBE<uint8>(0x80 | numZeroWords); numZeroWords = 0; } index++; continue; } if (numZeroWords != 0) { memWriter.writeBE<uint8>(0x80 | numZeroWords); numZeroWords = 0; } // count how many set words follow uint32 tempIndex = index + 1; uint32 scanEnd = std::min(index + 0x7F, regCount); while (tempIndex < scanEnd) { if (regArray[tempIndex] == 0) break; tempIndex++; } uint32 numSetWords = tempIndex - index; memWriter.writeBE<uint8>((uint8)numSetWords); // store word data while (index < tempIndex) { memWriter.writeBE<uint32>(regArray[index]); index++; } } if (numZeroWords != 0) memWriter.writeBE<uint8>(0x80 | numZeroWords); } bool _UncompressZeros(uint32* regArray, uint32 regCount, MemStreamReader& memReader) { uint32 index = 0; while (index < regCount) { uint8 pattern = memReader.readBE<uint8>(); uint8 count = pattern & 0x7F; if (count == 0) return false; if ((index + count) > regCount) return false; if (pattern & 0x80) { // zero words while (count--) { regArray[index] = 0; index++; } } else { // data words while (count--) { regArray[index] = memReader.readBE<uint32>(); index++; } } } return !memReader.hasError() && memReader.isEndOfStream(); } void SerializeRegisterState(GPUCompactedRegisterState& regState, MemStreamWriter& memWriter) { // convert regState into a platform independent representation in memory (we use a big-endian array) uint32be regArray[GPUCompactedRegisterState::NUM_REGS]; for (uint32 i = 0; i < GPUCompactedRegisterState::NUM_REGS; i++) regArray[i] = regState.rawArray[i]; // first, use simple RLE-style compression to get a more compact data representation MemStreamWriter tmpBufferWriter(1024 * 4); _CompressZeros(regState.rawArray, GPUCompactedRegisterState::NUM_REGS, tmpBufferWriter); auto rleCompressedData = tmpBufferWriter.getResult(); // second, compress using dictionary trained for RLE compressed register state uint8 buffer[8 * 1024]; ZSTD_CCtx* const cctx = ZSTD_createCCtx(); size_t compressedSize = ZSTD_compress_usingCDict(cctx, buffer, sizeof(buffer), rleCompressedData.data(), rleCompressedData.size(), s_c_regDict); ZSTD_freeCCtx(cctx); cemu_assert(!ZSTD_isError(compressedSize)); // serialize memWriter.writeBE<uint8>(0x01); // version memWriter.writeBE<uint16>((uint16)compressedSize); memWriter.writeData(buffer, compressedSize); } bool DeserializeRegisterState(GPUCompactedRegisterState& regState, MemStreamReader& memReader) { if (memReader.readBE<uint8>() != 1) return false; // unknown version // read compressed register data into buffer uint8 buffer[8 * 1024]; uint16 compressedSize = memReader.readBE<uint16>(); if (compressedSize >= sizeof(buffer)) return false; memReader.readData(buffer, compressedSize); if (memReader.hasError()) return false; // decompress using zstd with dictionary uint8 rleDataBuffer[8 * 1024]; ZSTD_DCtx* const dctx = ZSTD_createDCtx(); size_t rleDecompressedSize = ZSTD_decompress_usingDDict(dctx, rleDataBuffer, sizeof(rleDataBuffer), buffer, compressedSize, s_d_regDict); ZSTD_freeDCtx(dctx); if (rleDecompressedSize == 0 || rleDecompressedSize > sizeof(rleDataBuffer)) return false; // decompress RLE MemStreamReader rleReader(rleDataBuffer, (sint32)rleDecompressedSize); if (!_UncompressZeros(regState.rawArray, GPUCompactedRegisterState::NUM_REGS, rleReader)) return false; return true; } void UnitTestPipelineSerialization() { GPUCompactedRegisterState inputState{}; GPUCompactedRegisterState outputState{}; for (int i = 0; i < inputState.NUM_REGS; i++) { if ((0x1037778e >> ((i / 3) & 31)) & 1) inputState.rawArray[i] = i + i * 17 + i * 23; else inputState.rawArray[i] = 0; } // serialize and deserialize MemStreamWriter writer(1024 * 8); SerializeRegisterState(inputState, writer); auto writerRes = writer.getResult(); MemStreamReader reader(writerRes.data(), (sint32)writerRes.size()); bool r = DeserializeRegisterState(outputState, reader); cemu_assert_debug(r); cemu_assert_debug(memcmp(&inputState, &outputState, sizeof(GPUCompactedRegisterState)) == 0); } } extern const uint8 s_regDataDict[]; RunAtCemuBoot _loadPipelineCompressionDictionary([]() { // decompress and load dict static std::vector<uint8> s_dictData; s_dictData.resize(0x1B800); ZSTD_decompress(s_dictData.data(), s_dictData.size(), s_regDataDict, 0x5AD2); s_c_regDict = ZSTD_createCDict(s_dictData.data(), s_dictData.size(), 9); s_d_regDict = ZSTD_createDDict(s_dictData.data(), s_dictData.size()); cemu_assert_debug(s_c_regDict); cemu_assert_debug(s_d_regDict); Latte::UnitTestPipelineSerialization(); }); const uint8 s_regDataDict[] = // 0x5AD2 Uncompressed: 0x1B800 { 0x28, 0xB5, 0x2F, 0xFD, 0xA4, 0x00, 0xB8, 0x01, 0x00, 0x15, 0xD6, 0x02, 0xFA, 0x09, 0x2B, 0xA7, 0x4C, 0x10, 0x10, 0x4A, 0xE9, 0xB4, 0x06, 0xCA, 0xEB, 0xF5, 0x42, 0x3A, 0x7D, 0x5B, 0xF2, 0x57, 0x32, 0x13, 0x5C, 0x42, 0x9E, 0xFD, 0xC4, 0x0B, 0x41, 0xFD, 0xB8, 0x0E, 0xBF, 0x59, 0x28, 0xC3, 0x3A, 0xFA, 0x93, 0x0C, 0xC1, 0xA1, 0x65, 0x6E, 0x85, 0x63, 0x6E, 0xC6, 0x1A, 0x1B, 0xB7, 0xDD, 0x75, 0xCC, 0x29, 0xFE, 0x4F, 0x86, 0xAC, 0x64, 0x09, 0x23, 0xA1, 0xC4, 0x54, 0xD5, 0xCC, 0xA2, 0x7A, 0xA0, 0xCA, 0x6C, 0x23, 0xE2, 0xA2, 0x26, 0xB4, 0xC9, 0xDE, 0x5B, 0xA6, 0xEB, 0x09, 0x53, 0x0A, 0x9B, 0x0A, 0x60, 0xA6, 0x5E, 0xCB, 0xDE, 0x0F, 0x53, 0xEF, 0xE5, 0x90, 0xC2, 0x18, 0x63, 0xB3, 0x4C, 0xCD, 0x5E, 0x99, 0x9A, 0x79, 0x0D, 0x4C, 0x68, 0x82, 0x5C, 0xE8, 0x90, 0x49, 0x0B, 0xB7, 0x8C, 0x7B, 0x49, 0x73, 0x65, 0xE1, 0x42, 0x43, 0xD5, 0xC4, 0x9F, 0x8B, 0xDA, 0xA7, 0x35, 0x08, 0x83, 0x6E, 0xA8, 0x52, 0x17, 0xD3, 0x31, 0x07, 0xB9, 0x0C, 0x2E, 0x24, 0x4F, 0x71, 0xD5, 0x7B, 0xA7, 0xA4, 0x5F, 0xD4, 0xC8, 0x0D, 0x21, 0x1D, 0x98, 0xD7, 0x8E, 0xB9, 0xD1, 0x77, 0xFF, 0xE6, 0xFA, 0x4E, 0xEF, 0x9D, 0x3D, 0xFE, 0xD6, 0xF2, 0xC4, 0x1F, 0xC3, 0xB8, 0x87, 0xE7, 0x08, 0xC6, 0x8C, 0xDD, 0xE3, 0xE2, 0x83, 0x99, 0xE5, 0xFD, 0xEF, 0x96, 0xEA, 0xCF, 0x65, 0xFC, 0x21, 0xFA, 0xA6, 0x4D, 0xE8, 0xC3, 0x78, 0xDA, 0x6A, 0x9C, 0x48, 0x65, 0x5A, 0x96, 0x21, 0x51, 0xD2, 0x93, 0xE6, 0xFE, 0x7A, 0x25, 0x28, 0xA1, 0x71, 0x35, 0xF0, 0xD8, 0x30, 0xE4, 0xC8, 0x43, 0x11, 0xB7, 0x21, 0x56, 0x2C, 0xEF, 0x0D, 0x25, 0x28, 0xA5, 0x4A, 0x86, 0x20, 0xD2, 0x01, 0x69, 0xDA, 0x4B, 0x82, 0xA2, 0xF9, 0x40, 0x13, 0xFA, 0x64, 0x14, 0xEE, 0x2B, 0x6C, 0xB8, 0xA9, 0x30, 0x36, 0xC1, 0x38, 0x1B, 0x33, 0x4C, 0xDC, 0x5D, 0x20, 0x1F, 0xF0, 0x23, 0x3B, 0x32, 0x13, 0xDB, 0xAD, 0x30, 0x71, 0x9B, 0xA4, 0x32, 0x9A, 0xF8, 0x4C, 0xD1, 0xC4, 0x3E, 0xCE, 0xA0, 0xF6, 0x11, 0x03, 0x4A, 0xEF, 0x9B, 0x38, 0x9E, 0x96, 0x7A, 0xCC, 0xA7, 0xE4, 0xF2, 0x25, 0x72, 0x39, 0x96, 0xA6, 0xE5, 0x5B, 0x3C, 0x94, 0xEF, 0x69, 0x2A, 0x5F, 0x4E, 0xB6, 0x43, 0xD3, 0x64, 0x6D, 0x9A, 0x56, 0xD9, 0x35, 0x15, 0x79, 0xF1, 0x22, 0xDB, 0xAB, 0x30, 0x22, 0xEB, 0x32, 0xBF, 0xAC, 0x97, 0x5A, 0x7F, 0x43, 0x21, 0x9F, 0xE2, 0x9F, 0x95, 0x12, 0x35, 0xEC, 0x5F, 0xB9, 0x7F, 0x0D, 0x47, 0xB8, 0x92, 0xA8, 0x66, 0xCC, 0x47, 0x1A, 0x3E, 0x53, 0x4C, 0x38, 0x95, 0xC0, 0x5D, 0x3B, 0xA6, 0x21, 0x21, 0x0A, 0x5E, 0xF2, 0xE9, 0x04, 0x3D, 0xEB, 0x45, 0x4E, 0x8A, 0xC9, 0xD7, 0x3F, 0xD3, 0xE4, 0xA4, 0x52, 0x14, 0x54, 0x23, 0x7A, 0x5C, 0x37, 0xB4, 0x75, 0x7D, 0xFF, 0x13, 0x90, 0xE0, 0x42, 0x5F, 0x0C, 0x7F, 0xA8, 0x03, 0xE1, 0xC4, 0xF2, 0x4E, 0x10, 0x03, 0xAD, 0xF2, 0xE8, 0x5A, 0x9C, 0xF4, 0xDD, 0x2A, 0x2F, 0x22, 0x41, 0xE8, 0xDC, 0x4D, 0xF8, 0xC3, 0x76, 0x59, 0xB2, 0x01, 0xB3, 0x44, 0xF9, 0xC2, 0x75, 0x50, 0xF0, 0xEF, 0x60, 0x95, 0x3E, 0xF7, 0xD9, 0x3C, 0x60, 0x39, 0x32, 0xFD, 0xFB, 0xF3, 0xB4, 0xB0, 0x26, 0xBB, 0xC2, 0xD2, 0xCB, 0xE2, 0xE6, 0xEA, 0x2C, 0x6A, 0xAE, 0x6E, 0xD1, 0xB4, 0x3F, 0x24, 0x0B, 0x20, 0x38, 0xDA, 0x02, 0x1E, 0x47, 0xFF, 0xEC, 0xE1, 0x61, 0xF4, 0x9E, 0x31, 0xA3, 0xDB, 0x60, 0xFB, 0xB7, 0x7C, 0x5B, 0x22, 0xF5, 0x9B, 0x07, 0x52, 0x3F, 0x3D, 0x50, 0xFA, 0x99, 0x44, 0x29, 0x55, 0x62, 0x75, 0x9A, 0xC4, 0x0A, 0x8B, 0x03, 0x4A, 0xCB, 0x01, 0xA2, 0xF4, 0xDD, 0xA3, 0x61, 0x3A, 0x9B, 0x17, 0x1C, 0x6F, 0xD3, 0xD1, 0xD8, 0x05, 0xA0, 0x68, 0x3B, 0xB6, 0xA2, 0x83, 0x1A, 0x30, 0x3A, 0xE9, 0xE0, 0x01, 0xEC, 0x4A, 0x9E, 0x4E, 0x03, 0x06, 0x6F, 0x80, 0x39, 0x19, 0xD9, 0xCC, 0x15, 0x64, 0xB7, 0xED, 0x8D, 0x06, 0x09, 0x8B, 0x0E, 0xE3, 0x70, 0x84, 0x6B, 0x20, 0xA1, 0x2F, 0x30, 0x43, 0x43, 0xDD, 0x77, 0x72, 0xA6, 0xE7, 0xB8, 0xFA, 0x22, 0x45, 0xA3, 0x85, 0x55, 0xA0, 0x10, 0xD1, 0x28, 0x14, 0x14, 0x64, 0xBC, 0xFD, 0xA2, 0xED, 0xCB, 0xA0, 0xDB, 0x75, 0xDA, 0x80, 0x06, 0xCD, 0x8B, 0x9F, 0x02, 0x64, 0x79, 0xA1, 0x42, 0x01, 0xFA, 0x87, 0xCE, 0xBB, 0xA6, 0x78, 0x7C, 0x42, 0x3B, 0xB9, 0x30, 0xB6, 0x9D, 0x33, 0x66, 0x73, 0x61, 0x23, 0x64, 0x63, 0x47, 0xDF, 0x6B, 0x12, 0x33, 0xF9, 0x1C, 0x8F, 0xE8, 0xE8, 0x77, 0xEA, 0x33, 0xF3, 0x48, 0x40, 0x91, 0xBE, 0x39, 0x75, 0x82, 0x47, 0xC6, 0x17, 0xCE, 0x02, 0x62, 0x3F, 0x78, 0x64, 0xA0, 0x3A, 0xD2, 0x37, 0x31, 0x74, 0x93, 0x16, 0xBE, 0x2A, 0x7C, 0xF4, 0x69, 0x07, 0xC5, 0xCF, 0x3A, 0x28, 0x06, 0x72, 0x7B, 0x3B, 0xB2, 0x67, 0x92, 0xA1, 0x7A, 0xCA, 0xB6, 0x94, 0x69, 0x38, 0x9C, 0xE8, 0xED, 0xEA, 0xF1, 0x02, 0xC5, 0xD2, 0x41, 0x36, 0x8C, 0x47, 0xE8, 0xE8, 0x2B, 0x7A, 0x50, 0x4E, 0x05, 0xB6, 0xED, 0xB7, 0x6D, 0xCD, 0xFC, 0x66, 0x40, 0x58, 0xD6, 0x9B, 0x97, 0xB2, 0xF9, 0x81, 0x2E, 0x11, 0xE3, 0x01, 0x39, 0x7A, 0xE4, 0xB3, 0xA0, 0xBE, 0xD9, 0x58, 0x3F, 0x54, 0x8E, 0xCE, 0xE9, 0x7B, 0x1B, 0x9F, 0xF6, 0x2C, 0x7F, 0x83, 0x4B, 0x8F, 0x2B, 0x09, 0xB2, 0xBB, 0xB0, 0x71, 0xA2, 0xBB, 0x81, 0x84, 0x53, 0x09, 0xE4, 0xE1, 0xF4, 0xB7, 0x62, 0x8E, 0xF8, 0x9A, 0x00, 0xDC, 0x08, 0x4D, 0xC3, 0x18, 0x37, 0xC8, 0x53, 0xA0, 0x89, 0xA9, 0x0A, 0x6C, 0x2F, 0xFA, 0x9A, 0x2A, 0x49, 0x09, 0x3A, 0x51, 0xE0, 0x2D, 0x73, 0x45, 0x02, 0xD6, 0x54, 0xF2, 0x7B, 0x4F, 0x4B, 0xDF, 0x49, 0x39, 0xC2, 0xFB, 0xD6, 0xB4, 0x3A, 0xA0, 0x1E, 0x5D, 0x95, 0x5C, 0x17, 0x74, 0x53, 0x0D, 0xC8, 0xE4, 0x92, 0x12, 0x49, 0x2D, 0x03, 0x5E, 0x6F, 0x05, 0x22, 0xAF, 0x74, 0x07, 0x39, 0x16, 0x51, 0xB7, 0xBD, 0x22, 0x13, 0x78, 0x60, 0x15, 0x66, 0x10, 0xAE, 0xB9, 0x44, 0xC0, 0xB5, 0xC6, 0xC4, 0xBA, 0xF9, 0x02, 0x6B, 0x3A, 0x37, 0x55, 0xEB, 0x4D, 0x60, 0xE3, 0x4D, 0xD5, 0xC6, 0x21, 0x5E, 0x0F, 0xF9, 0x40, 0xD0, 0x9A, 0x4E, 0x4C, 0xD6, 0xC9, 0x35, 0x5B, 0x8F, 0xC3, 0xD6, 0x42, 0x30, 0x20, 0xB0, 0x4E, 0xB2, 0x21, 0xCC, 0xB8, 0x82, 0xD3, 0x41, 0xA3, 0x1D, 0xAC, 0xD7, 0xDA, 0x64, 0x01, 0x19, 0xB1, 0xFA, 0x80, 0x33, 0x94, 0xDE, 0x16, 0x70, 0x4E, 0x74, 0x56, 0x6D, 0x28, 0xC6, 0xD2, 0xFD, 0xA2, 0x35, 0x89, 0x7A, 0x9C, 0x50, 0x4E, 0x0E, 0x49, 0xDB, 0xC6, 0x9D, 0xC9, 0x49, 0xE2, 0x45, 0xDB, 0xA7, 0xCF, 0x8B, 0x21, 0x5C, 0x90, 0x4A, 0xD0, 0x17, 0x2F, 0xB8, 0x9A, 0x6A, 0x23, 0xCC, 0x06, 0x9D, 0x5F, 0x9C, 0xF6, 0xDB, 0xDC, 0x72, 0xC0, 0xC9, 0xFB, 0x1B, 0x22, 0x8F, 0xA6, 0x81, 0x1D, 0x14, 0x6D, 0x0D, 0xCC, 0x81, 0x1A, 0x53, 0x06, 0xAA, 0x81, 0x96, 0x08, 0xF4, 0x51, 0x23, 0x6D, 0x17, 0x18, 0xCB, 0xBB, 0x98, 0xC9, 0x29, 0x5B, 0x2E, 0x67, 0x8C, 0x8B, 0xEB, 0x47, 0x17, 0x07, 0xB2, 0xEE, 0x02, 0x24, 0xEB, 0x22, 0x26, 0x8F, 0xE0, 0x50, 0x0A, 0xB6, 0x73, 0xC8, 0x1D, 0x14, 0xEC, 0x40, 0xE7, 0xCD, 0x7B, 0x8D, 0xB7, 0xBF, 0x78, 0x83, 0x8C, 0x99, 0xDE, 0xA6, 0x81, 0xD5, 0x43, 0x85, 0x85, 0x09, 0xF7, 0x80, 0x85, 0x11, 0x91, 0x73, 0x11, 0x94, 0xB2, 0x5C, 0xB6, 0x33, 0x99, 0x61, 0xC5, 0x9D, 0x02, 0xE0, 0x82, 0x69, 0xC4, 0xC5, 0x41, 0x62, 0x7F, 0x42, 0x77, 0x68, 0x87, 0xA7, 0x99, 0x20, 0xD9, 0x54, 0xF4, 0x78, 0x85, 0xBC, 0x2D, 0x45, 0x8F, 0x26, 0xA4, 0x6E, 0x83, 0x40, 0x2D, 0x47, 0xF8, 0x10, 0xDB, 0xF8, 0xCC, 0x24, 0x46, 0x68, 0x01, 0xBC, 0x64, 0x48, 0x9C, 0xE9, 0x5D, 0x4D, 0x8A, 0xB8, 0xD0, 0x13, 0x5C, 0x7B, 0xBD, 0x20, 0xC0, 0xBD, 0x39, 0xDD, 0xE6, 0xE4, 0xEE, 0x07, 0xE5, 0xDC, 0xDE, 0xE0, 0x5A, 0xB9, 0x39, 0xB7, 0x16, 0x0D, 0x06, 0xCD, 0xFC, 0x9A, 0x99, 0x93, 0x65, 0xC5, 0xD6, 0x23, 0x42, 0x3F, 0x4A, 0x45, 0x4D, 0x83, 0x3F, 0xD0, 0x79, 0x51, 0x3C, 0x36, 0x08, 0xA6, 0xCE, 0xE6, 0x47, 0xDC, 0x3D, 0x69, 0xE7, 0x74, 0x3C, 0x7C, 0x96, 0xB8, 0x57, 0x4F, 0xAB, 0xAE, 0x40, 0xFC, 0xE3, 0xB0, 0xED, 0x67, 0xD3, 0x41, 0x00, 0xB6, 0x5A, 0x74, 0x08, 0x25, 0x30, 0x84, 0x63, 0xC7, 0xE5, 0x06, 0x58, 0x60, 0xAC, 0xE0, 0x60, 0x0B, 0x4D, 0x7B, 0xD9, 0xD1, 0x8F, 0x1D, 0x35, 0x6B, 0x1C, 0xDD, 0xA4, 0x71, 0x74, 0x7B, 0xB7, 0xEE, 0x2C, 0xD9, 0xFF, 0xF9, 0x2C, 0x9E, 0x12, 0x9E, 0x0A, 0xC5, 0xC1, 0xD6, 0x0F, 0xBA, 0xBC, 0x9F, 0x74, 0xAE, 0x77, 0x0E, 0x56, 0x0F, 0x29, 0x98, 0xE8, 0x6F, 0xAD, 0x0B, 0x4D, 0x09, 0xBE, 0xCA, 0x05, 0xBF, 0x40, 0x54, 0x4D, 0xD7, 0x3C, 0x32, 0xE1, 0x7F, 0xCB, 0xB0, 0xB3, 0x69, 0x58, 0x22, 0x13, 0x57, 0x37, 0x15, 0xEE, 0x02, 0xFD, 0x14, 0x1F, 0xA1, 0x8B, 0x7C, 0xD2, 0xE5, 0x3C, 0x80, 0xB1, 0x91, 0x4A, 0xDF, 0x76, 0x5E, 0x7D, 0x6B, 0x12, 0x16, 0x72, 0x89, 0xDD, 0x2C, 0xA1, 0x2B, 0x0E, 0xCF, 0xBB, 0xD6, 0x8F, 0x67, 0x2E, 0x20, 0xC4, 0x54, 0x2A, 0xE6, 0xAA, 0x49, 0xB1, 0x5A, 0x12, 0x39, 0x04, 0x7C, 0x43, 0xF3, 0x08, 0x87, 0x42, 0xA8, 0xA8, 0xF8, 0x50, 0x83, 0x60, 0x88, 0x24, 0x32, 0xF4, 0xD1, 0xFC, 0xE5, 0xFE, 0x6B, 0xDC, 0x17, 0x0E, 0x48, 0x05, 0x28, 0xA7, 0x8E, 0xA6, 0x6C, 0x8B, 0x87, 0x79, 0x37, 0xC1, 0xEA, 0x7C, 0xF4, 0x76, 0xAD, 0xCE, 0xFF, 0xEC, 0x51, 0x06, 0x33, 0xEE, 0x92, 0x10, 0xD0, 0x01, 0x6B, 0xDB, 0x80, 0x40, 0x5A, 0x3A, 0x72, 0x10, 0xB9, 0x2B, 0x28, 0x1F, 0x72, 0xC5, 0x83, 0x73, 0xE1, 0x6C, 0x73, 0x72, 0xF7, 0x11, 0x23, 0x33, 0xF0, 0xA8, 0x4F, 0x74, 0x34, 0xD2, 0xEE, 0x5F, 0x80, 0xB5, 0x19, 0xA8, 0x9D, 0xF6, 0x0A, 0x7F, 0x6E, 0xD1, 0x84, 0xE3, 0x77, 0x7B, 0xC7, 0x62, 0xCA, 0xE0, 0xEF, 0xC4, 0x01, 0xCC, 0xB6, 0xEF, 0x2F, 0xF5, 0x1F, 0x4E, 0x78, 0xFE, 0xC4, 0xD6, 0xE0, 0x5C, 0xBC, 0xBC, 0x4F, 0x9C, 0x84, 0x05, 0xC8, 0xFE, 0x41, 0x07, 0xD9, 0x4A, 0xA0, 0x2A, 0x5A, 0x8A, 0xA9, 0x9D, 0xD9, 0x96, 0xF0, 0x3B, 0x19, 0x6C, 0x73, 0xDB, 0xF0, 0x29, 0x82, 0x49, 0x85, 0xCC, 0xA9, 0x9A, 0xF4, 0x26, 0x3E, 0xB9, 0x38, 0x93, 0x2F, 0x5F, 0x6F, 0x22, 0x2D, 0x6F, 0x45, 0xC2, 0x36, 0x32, 0x79, 0x08, 0x3B, 0x6A, 0xA3, 0x22, 0x11, 0xDE, 0x2A, 0x68, 0x9C, 0xE9, 0x3A, 0xB4, 0xA0, 0x31, 0x10, 0xEE, 0x85, 0x17, 0x18, 0xA7, 0x02, 0x8F, 0xBA, 0x70, 0xE9, 0x17, 0x41, 0xA9, 0xFB, 0x80, 0x8E, 0x49, 0x11, 0x68, 0x38, 0x31, 0xEE, 0xCD, 0x48, 0xDB, 0x5E, 0x62, 0xDB, 0x0E, 0xBF, 0x6D, 0xFA, 0x26, 0xAE, 0xCC, 0xF3, 0x95, 0x01, 0x8D, 0x6C, 0xA3, 0x8C, 0x8C, 0xEB, 0x04, 0x42, 0x9F, 0x04, 0x01, 0x01, 0x83, 0xF2, 0x9E, 0xA9, 0x09, 0x95, 0x8D, 0x5A, 0x91, 0x51, 0x78, 0x99, 0xC4, 0xFD, 0xEC, 0xC0, 0xEF, 0xB2, 0x64, 0xFC, 0x32, 0x28, 0xD2, 0xF6, 0x15, 0x73, 0xF7, 0xC6, 0x94, 0x4B, 0x0E, 0xDD, 0x2A, 0x44, 0xFF, 0x85, 0xA3, 0xA1, 0x3D, 0xA6, 0x68, 0x5A, 0x27, 0xAC, 0x0B, 0xFE, 0xC3, 0x87, 0x45, 0xD7, 0x0F, 0xF6, 0x61, 0x8F, 0x40, 0x14, 0xCA, 0x4C, 0xDB, 0x16, 0x9B, 0x5D, 0x76, 0x77, 0x15, 0x3F, 0x9B, 0x01, 0x78, 0x48, 0x7D, 0x6F, 0x91, 0x7E, 0x87, 0x95, 0x24, 0x30, 0x52, 0x36, 0x48, 0x17, 0x58, 0x35, 0x9E, 0x60, 0x10, 0xD5, 0xC6, 0x03, 0x08, 0xA8, 0xD1, 0x9F, 0x81, 0x39, 0x50, 0x28, 0xDF, 0xB5, 0xE3, 0x03, 0x7A, 0xD0, 0xAE, 0x77, 0x69, 0xF9, 0x55, 0x3A, 0xC8, 0x95, 0xB4, 0x9C, 0xEB, 0x68, 0x79, 0xF1, 0xD6, 0xCF, 0x6E, 0x8D, 0xC8, 0xB2, 0xF1, 0x4B, 0x3A, 0x91, 0x49, 0x19, 0x8D, 0x8D, 0x8B, 0xE5, 0xAC, 0x71, 0x81, 0xC6, 0x49, 0xCD, 0x0C, 0x2E, 0xCC, 0x34, 0xC1, 0x56, 0x03, 0xD2, 0x0D, 0x21, 0xC0, 0xD8, 0x6D, 0xAB, 0x1C, 0x1E, 0x15, 0x90, 0x99, 0x21, 0xD2, 0x82, 0xB7, 0xC6, 0x6F, 0xA0, 0x9F, 0x0D, 0xF3, 0xE9, 0x47, 0x26, 0x2C, 0x4C, 0x01, 0x2D, 0x37, 0xE7, 0x54, 0x77, 0xB2, 0x04, 0xEE, 0x05, 0xFA, 0x41, 0x34, 0xA7, 0x88, 0x5B, 0x13, 0xA6, 0xFF, 0x6A, 0x44, 0xBD, 0xE9, 0x3A, 0x71, 0xF8, 0x0A, 0x1C, 0xBD, 0xC2, 0x61, 0xDB, 0xDE, 0x14, 0xBD, 0xAA, 0x1A, 0x8F, 0x17, 0x1B, 0x54, 0x10, 0x43, 0xDC, 0xA7, 0xAD, 0x19, 0x00, 0xDF, 0xF2, 0xD0, 0x5D, 0xEB, 0x3F, 0xEB, 0x21, 0x66, 0x24, 0xFA, 0x2C, 0x04, 0x31, 0xBC, 0xBC, 0xBF, 0x47, 0xC1, 0x9A, 0x30, 0x1A, 0x40, 0x24, 0xD3, 0xB8, 0x3B, 0xA3, 0x1D, 0x00, 0x88, 0x24, 0x13, 0x89, 0x36, 0x8A, 0x7D, 0xAD, 0xA6, 0xFF, 0x4D, 0x1B, 0x95, 0x2E, 0x49, 0xFB, 0x05, 0x21, 0x28, 0x98, 0x38, 0x57, 0x6A, 0x0C, 0x59, 0x9B, 0x0B, 0x0A, 0xB0, 0x18, 0x53, 0x07, 0x79, 0x27, 0xB0, 0x7B, 0x31, 0x4A, 0x56, 0x23, 0x78, 0x86, 0x95, 0xBC, 0x32, 0xB4, 0xC1, 0x9A, 0x16, 0x4B, 0x10, 0xC8, 0x90, 0xB5, 0xE8, 0x90, 0x18, 0x48, 0x24, 0x06, 0x40, 0x16, 0xE7, 0xBF, 0x08, 0x1E, 0x5A, 0xAC, 0x4E, 0xE4, 0xF0, 0xA7, 0xB1, 0xA6, 0x05, 0xFA, 0xE4, 0xBE, 0x3A, 0x8B, 0x64, 0xEC, 0xA2, 0x28, 0x2D, 0xB0, 0x9F, 0x74, 0xE2, 0x68, 0x39, 0xBC, 0x7C, 0x34, 0x65, 0xF0, 0xEF, 0x12, 0xC5, 0x5D, 0xA7, 0x1F, 0x17, 0xDF, 0x7D, 0xD1, 0x10, 0xF9, 0x07, 0x5C, 0xF7, 0x92, 0x68, 0x71, 0xFB, 0xCF, 0x4E, 0x34, 0xEA, 0x29, 0x37, 0x9E, 0x51, 0xDE, 0xA6, 0xEB, 0x39, 0x05, 0xFD, 0x8B, 0x61, 0xB8, 0x60, 0x31, 0x72, 0x2F, 0x55, 0x23, 0x06, 0xFF, 0x0E, 0x0A, 0x8E, 0xBA, 0x6D, 0xDD, 0x2C, 0x53, 0xE1, 0xB6, 0xE4, 0x66, 0xA2, 0x8A, 0x7F, 0x6F, 0x95, 0x90, 0xC2, 0xED, 0xDE, 0x45, 0xA2, 0x90, 0x7F, 0xC1, 0x22, 0x3E, 0x34, 0x3B, 0xDC, 0x80, 0x9D, 0xBE, 0xDA, 0xD0, 0x49, 0x03, 0xF9, 0xEE, 0xDF, 0x5C, 0xB1, 0x35, 0x97, 0xA4, 0x0A, 0x2F, 0xD7, 0xCD, 0x38, 0xB5, 0x6C, 0x2F, 0xF8, 0x76, 0xCE, 0x0A, 0x7F, 0xA4, 0xA0, 0xF9, 0xAD, 0xC0, 0x9B, 0x70, 0x42, 0xFA, 0x53, 0xE5, 0x44, 0x19, 0x0D, 0xF1, 0x11, 0x36, 0x2E, 0xD9, 0xDA, 0x06, 0x0C, 0x15, 0x6E, 0xCB, 0x88, 0x66, 0xA2, 0xDA, 0xFE, 0x90, 0x72, 0x72, 0x2B, 0x5D, 0x9D, 0x0A, 0x33, 0x5B, 0x72, 0xC4, 0xAE, 0x21, 0x17, 0xFF, 0x68, 0x43, 0x17, 0xED, 0x34, 0xE8, 0x60, 0x58, 0xAE, 0xFC, 0x4D, 0xAD, 0xC9, 0x1F, 0x26, 0xD0, 0xEF, 0x07, 0xEE, 0x60, 0x4E, 0x35, 0xFE, 0x88, 0x86, 0xFF, 0x74, 0x66, 0x94, 0xD3, 0xE5, 0x29, 0x6F, 0xEF, 0x7A, 0x4E, 0x2F, 0xF4, 0xAC, 0xDC, 0x6A, 0x1C, 0x01, 0x85, 0x5B, 0x39, 0xAB, 0x45, 0x2D, 0xFF, 0x8A, 0x45, 0xCE, 0xDD, 0xE4, 0x56, 0x3B, 0x16, 0x5A, 0x5C, 0x7C, 0xD6, 0x29, 0x65, 0x9B, 0xEF, 0xD9, 0xD5, 0x39, 0x9B, 0xFC, 0xD1, 0xA2, 0x42, 0x8B, 0x91, 0xAF, 0x3A, 0x15, 0xFF, 0x52, 0x20, 0x09, 0x33, 0x8E, 0x31, 0x26, 0x6C, 0xF5, 0x07, 0xFB, 0xD7, 0x29, 0xC2, 0x85, 0x5F, 0x2E, 0xED, 0x65, 0x72, 0x27, 0xBE, 0x80, 0x4A, 0xAD, 0x22, 0x59, 0x6F, 0x07, 0x41, 0x18, 0x70, 0x0E, 0xFF, 0x37, 0x5E, 0xC2, 0x1B, 0x14, 0xC4, 0x06, 0xBE, 0x91, 0xA1, 0x54, 0x1B, 0xFF, 0x47, 0xF3, 0x8A, 0x7F, 0x15, 0xA8, 0x46, 0x09, 0xC1, 0xA9, 0x5F, 0x74, 0x9D, 0xC2, 0xFF, 0x3E, 0x31, 0x16, 0x85, 0x84, 0x41, 0x35, 0x05, 0x7E, 0xFF, 0x27, 0xAE, 0x08, 0x6C, 0xDB, 0x76, 0x75, 0x4D, 0xD3, 0x3C, 0xB2, 0x7F, 0x59, 0x74, 0x52, 0x14, 0x0A, 0xFA, 0x21, 0x1E, 0xFC, 0xD7, 0x7F, 0x28, 0x74, 0x5E, 0x20, 0x3C, 0xE6, 0xCC, 0x8B, 0x27, 0x11, 0x11, 0x3A, 0x85, 0x47, 0x4E, 0xBA, 0xD3, 0x77, 0x9D, 0x1B, 0xB6, 0x36, 0x6B, 0x5A, 0x04, 0xF9, 0xFC, 0xF5, 0xA0, 0x35, 0xF3, 0x2F, 0x55, 0x10, 0xF2, 0xB4, 0x47, 0xB6, 0xEE, 0xBF, 0xBF, 0xCA, 0xA1, 0x2A, 0x70, 0x45, 0xDD, 0xBC, 0x2C, 0x7B, 0x17, 0xCA, 0x7C, 0xD2, 0x6C, 0x75, 0xFE, 0xEC, 0x52, 0xFD, 0x5B, 0xA1, 0x1D, 0x7A, 0x7F, 0x34, 0x6F, 0xA0, 0x1E, 0xFA, 0xB3, 0xC5, 0xF6, 0x57, 0x6D, 0xB0, 0xE0, 0x0A, 0x26, 0xA2, 0x11, 0x99, 0x18, 0xDC, 0xFB, 0xF5, 0xF9, 0x40, 0xBB, 0x57, 0xD9, 0x4F, 0x9B, 0x8F, 0x32, 0xFF, 0x26, 0xBF, 0x28, 0xB7, 0x13, 0x07, 0xCA, 0x66, 0x7B, 0x72, 0x69, 0x31, 0x18, 0x19, 0xFC, 0x86, 0x6E, 0xCF, 0x2E, 0xAF, 0x53, 0x08, 0xD5, 0xF7, 0x3A, 0x3A, 0xEA, 0x76, 0x85, 0xCF, 0xDE, 0x97, 0xAF, 0x75, 0xAA, 0xFF, 0x85, 0xCB, 0xD2, 0x7E, 0x24, 0x43, 0xD3, 0x37, 0x18, 0x9A, 0xC0, 0x09, 0xAD, 0x54, 0xD2, 0x23, 0x48, 0x67, 0xDD, 0x64, 0x07, 0xCD, 0x1E, 0x7D, 0x40, 0x1F, 0xF2, 0x29, 0x7E, 0xAC, 0x43, 0x7F, 0xF6, 0x85, 0xAE, 0x3B, 0x9A, 0xA1, 0x46, 0x3B, 0x6D, 0xF3, 0x5B, 0xAC, 0x67, 0xF2, 0x9D, 0x38, 0xA6, 0x4F, 0xEE, 0x51, 0xDA, 0x47, 0xE0, 0x42, 0x52, 0xA4, 0x3B, 0x1E, 0x3B, 0x4D, 0xC2, 0x22, 0x90, 0xDF, 0x09, 0x2A, 0x31, 0xBF, 0x05, 0xAA, 0x09, 0x1E, 0xCD, 0x98, 0x9C, 0xD8, 0x9F, 0x67, 0x4F, 0x34, 0xDD, 0xFC, 0x38, 0xC2, 0x67, 0xD6, 0x39, 0x71, 0x4C, 0x3E, 0x13, 0xE3, 0x8F, 0x67, 0x26, 0xA9, 0xBE, 0xFD, 0x1D, 0xA9, 0xFE, 0x9A, 0x10, 0x19, 0x20, 0xA5, 0x4F, 0xFE, 0x67, 0xD7, 0xD1, 0x86, 0x14, 0xAE, 0x93, 0xC9, 0x77, 0x84, 0xEA, 0x5D, 0x89, 0x91, 0x05, 0xA6, 0x96, 0x0C, 0x50, 0x86, 0x08, 0x26, 0x3E, 0xFC, 0x41, 0xC6, 0xEA, 0x7A, 0xC8, 0x25, 0x8D, 0xC5, 0x28, 0xDA, 0x32, 0x03, 0xC4, 0xA4, 0xC1, 0xC4, 0x19, 0x5E, 0x19, 0xB2, 0x1F, 0x23, 0xD6, 0x59, 0x17, 0x3A, 0x65, 0xDE, 0x66, 0x08, 0xC6, 0x25, 0x09, 0xA6, 0x6F, 0x32, 0x98, 0xB1, 0xEA, 0x1C, 0xC0, 0x70, 0x80, 0x09, 0xA8, 0x33, 0xE6, 0x4E, 0x38, 0x20, 0xE3, 0xAB, 0x97, 0x2A, 0x28, 0x47, 0x8C, 0xC1, 0x83, 0xC1, 0x0B, 0x2D, 0x7C, 0x29, 0x51, 0xE2, 0x4B, 0x8F, 0x27, 0x15, 0x14, 0x30, 0x3A, 0xE0, 0xE5, 0x83, 0x0D, 0xB6, 0x98, 0xD8, 0x50, 0xE4, 0x49, 0x65, 0x22, 0xEB, 0x80, 0x0A, 0x8E, 0x72, 0x5E, 0x8C, 0x65, 0x68, 0x44, 0xC5, 0x31, 0x91, 0x03, 0x95, 0xA1, 0x0D, 0x23, 0x5E, 0x38, 0x7A, 0x18, 0x50, 0x36, 0xBC, 0x21, 0x6C, 0xCB, 0xC8, 0xF5, 0x36, 0x25, 0x01, 0x12, 0x21, 0xB4, 0x29, 0xB5, 0xCC, 0xD8, 0x9C, 0xB8, 0x4E, 0xC8, 0x02, 0x33, 0x9E, 0xF9, 0x48, 0xB2, 0x60, 0x87, 0x21, 0xB6, 0x46, 0xF8, 0x0A, 0x4A, 0xDB, 0x05, 0x41, 0xBD, 0xD7, 0x26, 0xF5, 0x1E, 0x07, 0x52, 0xB7, 0x5F, 0xFD, 0x78, 0x36, 0x1D, 0x54, 0xD9, 0xFD, 0xA0, 0x88, 0x6A, 0xC8, 0x60, 0x28, 0xF9, 0x5A, 0x46, 0x9B, 0x18, 0x1A, 0x25, 0xF8, 0x1D, 0x06, 0x5B, 0xF4, 0xBE, 0x2C, 0x5A, 0x11, 0x42, 0x5A, 0x83, 0xE6, 0xEC, 0x43, 0xF7, 0xF6, 0x7B, 0x65, 0x06, 0x7D, 0x6F, 0xFF, 0xAD, 0xA7, 0x06, 0x43, 0xA8, 0x50, 0x88, 0xAC, 0x0F, 0x22, 0xEB, 0x67, 0xD5, 0xA1, 0x38, 0xE8, 0x2A, 0x7E, 0xA8, 0x0D, 0xA5, 0x51, 0x36, 0xEE, 0x5D, 0xBD, 0x53, 0x36, 0x12, 0x3C, 0x72, 0x81, 0xFF, 0x90, 0x22, 0x7C, 0x6E, 0x9D, 0xF8, 0x0B, 0xD5, 0x74, 0xF6, 0x56, 0xE8, 0xD9, 0xB4, 0xB0, 0xB1, 0x94, 0x00, 0xCB, 0x3D, 0x26, 0x8E, 0xDA, 0x8E, 0xA2, 0xB7, 0xE7, 0x86, 0xEB, 0xA8, 0x31, 0x5A, 0xFD, 0xE1, 0xD8, 0x18, 0xA1, 0x7C, 0x0F, 0x18, 0x21, 0xA2, 0xD2, 0xF6, 0xA8, 0xC2, 0xE2, 0x1E, 0x54, 0x58, 0xBC, 0x97, 0x17, 0x85, 0x05, 0x1A, 0x11, 0x9E, 0xB4, 0x58, 0x4C, 0xC5, 0x62, 0x3B, 0x25, 0x8B, 0xEB, 0xA4, 0x35, 0xC0, 0x1C, 0x9F, 0x22, 0x84, 0x09, 0x13, 0x22, 0x88, 0xD1, 0x1F, 0xBC, 0xBA, 0xC0, 0xC6, 0x2B, 0x3B, 0x1B, 0x8F, 0xEC, 0xB4, 0x8E, 0xCE, 0x81, 0xB3, 0x83, 0x83, 0xF1, 0xF9, 0x41, 0xB6, 0x22, 0xD0, 0x42, 0xCD, 0x6C, 0xAC, 0x25, 0xD3, 0xE1, 0x6C, 0x32, 0x36, 0x67, 0xEF, 0xE6, 0x6C, 0x3D, 0x3B, 0x9C, 0x4D, 0x4C, 0xA2, 0x0A, 0x90, 0x9D, 0x07, 0x37, 0x76, 0x5E, 0x91, 0xB1, 0xB7, 0xF4, 0x66, 0xF0, 0x2B, 0xC2, 0x82, 0xCA, 0xF9, 0x80, 0xB4, 0x1A, 0x1D, 0xA4, 0xB5, 0x66, 0xBB, 0x41, 0x07, 0x18, 0x59, 0xB0, 0x13, 0x5C, 0x1E, 0x35, 0x84, 0x21, 0x51, 0xD1, 0xD0, 0xAF, 0x20, 0x4E, 0xC9, 0x75, 0xD5, 0x28, 0x20, 0xE1, 0x11, 0x61, 0x9B, 0xB6, 0x2D, 0xDA, 0xAA, 0x3F, 0x45, 0x98, 0xA2, 0xA7, 0x7A, 0x68, 0x41, 0x31, 0x6B, 0xC1, 0x83, 0x74, 0x13, 0xC5, 0x12, 0xED, 0xE8, 0x7B, 0xEF, 0xF1, 0x12, 0x96, 0x82, 0x51, 0xB5, 0xF0, 0xC9, 0xA0, 0x07, 0xA4, 0xE6, 0xD2, 0x6A, 0x40, 0x32, 0x35, 0xAC, 0x33, 0xF1, 0x6A, 0x30, 0x23, 0x40, 0x0D, 0x5D, 0x6F, 0x59, 0x41, 0xE7, 0xBE, 0x42, 0xD6, 0xEF, 0x0C, 0x8C, 0xDF, 0x97, 0xA8, 0xEE, 0xAF, 0x7B, 0x24, 0xB4, 0x3B, 0x1B, 0xF4, 0xA6, 0x62, 0xF6, 0x6E, 0x34, 0xBE, 0x66, 0x01, 0x41, 0xC2, 0x0B, 0x5C, 0x3F, 0xE0, 0x08, 0x8B, 0x23, 0x70, 0x22, 0x68, 0xD0, 0x8E, 0x6A, 0x90, 0x7A, 0x8A, 0x04, 0x89, 0x18, 0xDA, 0xB6, 0xA9, 0xF3, 0xAE, 0x6F, 0x2A, 0xFE, 0x62, 0x98, 0x7A, 0xBD, 0xED, 0x8A, 0x31, 0x89, 0xEC, 0xF0, 0x4E, 0x58, 0x83, 0x55, 0xF1, 0x3F, 0x02, 0x1F, 0xB8, 0x9A, 0xAF, 0xF5, 0xB3, 0x10, 0xF8, 0xC0, 0xE5, 0x02, 0x89, 0x3B, 0x1D, 0xE0, 0xC8, 0xF3, 0x0F, 0x11, 0xC0, 0x18, 0x3D, 0x80, 0xCD, 0x13, 0xA6, 0x04, 0x58, 0x33, 0x7D, 0x94, 0x61, 0x00, 0xEB, 0x32, 0x39, 0xEC, 0xE6, 0xEC, 0xA0, 0x64, 0x63, 0xB5, 0xB2, 0x06, 0xAB, 0x9B, 0x7A, 0xAC, 0x6E, 0xBE, 0x45, 0x2D, 0xFD, 0x06, 0xB5, 0x34, 0x4C, 0x06, 0x61, 0xB5, 0xDD, 0x1D, 0xAB, 0x85, 0x18, 0xCC, 0x9A, 0xE2, 0xEF, 0xE6, 0x9E, 0xF4, 0x86, 0xD2, 0xA4, 0xE5, 0x92, 0x1C, 0xC0, 0x18, 0xA5, 0x63, 0x93, 0x3C, 0x8D, 0x7C, 0x13, 0x49, 0x6F, 0x6C, 0x5D, 0xC5, 0xDD, 0xD2, 0xF7, 0xE1, 0x64, 0xEB, 0x1A, 0x08, 0xB7, 0x75, 0xD5, 0x1B, 0x04, 0x97, 0x12, 0xFF, 0x89, 0x53, 0x89, 0xB7, 0x57, 0x88, 0xA6, 0x44, 0xA1, 0x8B, 0xA6, 0x6E, 0x42, 0xB4, 0x13, 0x30, 0x56, 0xA2, 0xB9, 0xB0, 0xCF, 0xAA, 0xB5, 0x48, 0xE9, 0x9D, 0x3C, 0x27, 0xA1, 0x7C, 0x20, 0x8C, 0xE9, 0x5B, 0xD3, 0x5A, 0x41, 0xDA, 0x2E, 0x04, 0x24, 0xA1, 0x18, 0x4E, 0xEA, 0x61, 0xE4, 0x44, 0xEF, 0x62, 0x7A, 0x52, 0x44, 0xD6, 0xDE, 0xEB, 0xA4, 0x5E, 0xD5, 0x49, 0x08, 0xCA, 0x0A, 0x7A, 0x87, 0x1E, 0x9D, 0xBB, 0xE8, 0x6C, 0x0E, 0x65, 0x3F, 0x96, 0x9B, 0x87, 0x40, 0xBA, 0x77, 0x95, 0x54, 0x30, 0x8A, 0xA6, 0x3A, 0xDD, 0x96, 0x33, 0x02, 0x69, 0x2B, 0x63, 0xFA, 0x5E, 0xC1, 0x70, 0xD0, 0xF7, 0xAA, 0xA7, 0x5D, 0x81, 0x60, 0x4A, 0xAC, 0x50, 0x82, 0x0A, 0x97, 0x30, 0xF6, 0x8B, 0xF7, 0x89, 0x74, 0xB9, 0xE0, 0xAC, 0x38, 0x35, 0xE5, 0xC7, 0xEB, 0x59, 0x07, 0xC4, 0x2C, 0x6D, 0xA9, 0x00, 0xCD, 0x71, 0xF2, 0x91, 0x0F, 0x39, 0x7A, 0x46, 0x19, 0x1F, 0x06, 0xFA, 0x4C, 0xA5, 0xDC, 0xD4, 0x58, 0x04, 0x98, 0x5A, 0xE3, 0x43, 0xB1, 0x08, 0x1F, 0x1F, 0x8E, 0xEA, 0x52, 0xC5, 0xC5, 0xAC, 0x85, 0x1E, 0xC6, 0x06, 0x2A, 0x71, 0xE0, 0x48, 0x9B, 0x6C, 0xAD, 0x4E, 0x1A, 0x61, 0xF5, 0xF6, 0x36, 0x1D, 0x0F, 0xA6, 0xA6, 0xA3, 0x64, 0x4A, 0xC9, 0x44, 0xB4, 0xD2, 0x88, 0xE8, 0x16, 0xF5, 0xE1, 0xC2, 0x71, 0x3A, 0x26, 0x1C, 0x07, 0x82, 0x5E, 0x69, 0x4A, 0xD1, 0x41, 0x72, 0x34, 0x1D, 0xB9, 0x14, 0x40, 0x60, 0xD1, 0x6D, 0xB0, 0x6D, 0xB3, 0x6C, 0x16, 0x50, 0xB4, 0x0C, 0x4D, 0x06, 0x8A, 0xA6, 0x72, 0x47, 0x13, 0xB5, 0x4C, 0x0A, 0x5A, 0xE3, 0x60, 0x4B, 0x49, 0xDF, 0xD4, 0xBA, 0x5A, 0x93, 0x3D, 0x77, 0xA9, 0x28, 0x7B, 0x8C, 0x4B, 0x19, 0xAE, 0xC0, 0xDB, 0x32, 0x65, 0x53, 0x84, 0xAB, 0xDB, 0x7A, 0x4F, 0x6F, 0x50, 0x8D, 0x6E, 0x46, 0x35, 0x18, 0xA3, 0xA1, 0x19, 0x03, 0xC1, 0x8F, 0x09, 0xDD, 0x51, 0x65, 0xDA, 0xFB, 0x2F, 0x59, 0x4F, 0x17, 0x00, 0x1E, 0x50, 0x64, 0x49, 0x42, 0xE9, 0xDA, 0xE8, 0x44, 0xC8, 0x25, 0x19, 0x23, 0xC5, 0x49, 0x0A, 0xB8, 0xE6, 0x9B, 0x80, 0xCB, 0x86, 0x96, 0xAE, 0x99, 0x25, 0x74, 0x6D, 0x43, 0x0A, 0x6B, 0x1B, 0x41, 0x68, 0x7D, 0xE5, 0xB4, 0x10, 0xCD, 0xCF, 0x46, 0x0F, 0x28, 0x7E, 0xF2, 0x80, 0x42, 0x98, 0x81, 0xFA, 0x52, 0xC7, 0x06, 0x64, 0x08, 0x6C, 0x00, 0x43, 0x4A, 0x12, 0x24, 0x46, 0xE2, 0xE2, 0x8C, 0x04, 0x8F, 0x54, 0x01, 0xA6, 0x1D, 0x01, 0xD5, 0xEC, 0x83, 0x0A, 0x80, 0xEA, 0x64, 0xDC, 0x03, 0x85, 0x44, 0x75, 0x42, 0x8D, 0x68, 0xDF, 0x22, 0x9A, 0x4B, 0xEA, 0x3D, 0xDD, 0xCC, 0x90, 0xD3, 0x19, 0xE5, 0x92, 0x1F, 0x39, 0xF1, 0x96, 0x40, 0x61, 0x46, 0x08, 0xF2, 0xB2, 0x09, 0x90, 0x97, 0x8D, 0x25, 0x2F, 0x5B, 0x26, 0x47, 0xEF, 0x9A, 0x46, 0xC3, 0x34, 0xCD, 0xBE, 0xD0, 0x34, 0x73, 0x0B, 0xFA, 0x34, 0x9D, 0x13, 0x7F, 0xB9, 0xCD, 0x89, 0xB3, 0xA0, 0x36, 0x86, 0xD6, 0x27, 0x8E, 0x84, 0xE8, 0xA4, 0x89, 0x98, 0x08, 0xAD, 0x60, 0x04, 0xB3, 0xD7, 0xE7, 0x06, 0x6A, 0x84, 0x3F, 0x0C, 0x85, 0xE2, 0x4D, 0x15, 0x92, 0xFF, 0xA0, 0x2B, 0xA4, 0x08, 0x2F, 0xF0, 0x16, 0x4D, 0xD5, 0x95, 0x2D, 0x53, 0x43, 0x50, 0xD1, 0x51, 0x60, 0xDA, 0xE2, 0xDB, 0xBC, 0x49, 0xCE, 0x8C, 0x93, 0xDC, 0x08, 0x95, 0x64, 0xB6, 0xE4, 0xB3, 0x9C, 0x0B, 0x72, 0xD6, 0x69, 0x89, 0xF9, 0x8B, 0x44, 0x5E, 0x3A, 0x4A, 0x17, 0x61, 0x58, 0x78, 0xF6, 0xBA, 0x82, 0x23, 0x09, 0x05, 0x00, 0x84, 0x47, 0x53, 0x88, 0xD3, 0x18, 0x28, 0xC0, 0x7D, 0xE6, 0x02, 0x55, 0x62, 0x69, 0xC1, 0x7F, 0xE2, 0xA8, 0x12, 0x4B, 0xC1, 0xCA, 0x9B, 0x15, 0x25, 0x14, 0x06, 0x19, 0x36, 0x4E, 0xF5, 0x0E, 0x14, 0xF2, 0x49, 0x0E, 0x4A, 0xA3, 0x51, 0x08, 0x50, 0x4D, 0xEC, 0x32, 0x80, 0xF2, 0x21, 0xDF, 0x01, 0x54, 0xCF, 0x23, 0x8A, 0x6E, 0xA7, 0x2E, 0x8A, 0xD2, 0x5B, 0x72, 0x3A, 0xC2, 0x8E, 0x5C, 0x9B, 0xE4, 0x38, 0x84, 0x40, 0x5E, 0xE6, 0x0E, 0xF2, 0x2C, 0x4A, 0x19, 0x9A, 0xDE, 0x13, 0x81, 0x83, 0x2B, 0x36, 0xCB, 0x19, 0xC3, 0xC4, 0x26, 0x40, 0xD3, 0x6C, 0x0C, 0x4D, 0xB7, 0x3D, 0x40, 0xD3, 0xC3, 0x68, 0xFA, 0x61, 0x00, 0xA2, 0xE2, 0xE2, 0x7F, 0xD7, 0xB9, 0xD1, 0xDB, 0xA1, 0xEB, 0xF6, 0xF5, 0x33, 0xB4, 0xE9, 0x63, 0xF9, 0x96, 0x91, 0x14, 0xF7, 0xD9, 0xBB, 0xBB, 0x33, 0x2C, 0x6D, 0x6B, 0x40, 0xA6, 0x09, 0x43, 0x17, 0x93, 0xD0, 0x31, 0x92, 0x92, 0x08, 0x77, 0xA2, 0x4C, 0x92, 0xC0, 0xAF, 0xA8, 0x93, 0xD8, 0xE7, 0x14, 0x78, 0xA1, 0x10, 0x85, 0x68, 0x8F, 0xFF, 0x66, 0xF7, 0x69, 0x02, 0x50, 0x7F, 0x1A, 0x90, 0x65, 0x28, 0xAD, 0x6B, 0xAD, 0x7C, 0xB9, 0xB2, 0x72, 0x4F, 0x08, 0x8C, 0x13, 0xEE, 0x4C, 0xCE, 0x83, 0x4D, 0xDE, 0xFE, 0x19, 0x23, 0x51, 0x09, 0xAA, 0x6F, 0x1C, 0x41, 0x25, 0x14, 0x17, 0x27, 0x08, 0x98, 0x25, 0xE3, 0xD0, 0x59, 0x41, 0xD5, 0x3C, 0x86, 0x54, 0xFD, 0x57, 0xA7, 0x28, 0xB8, 0x0C, 0xAB, 0xD2, 0x28, 0x45, 0x17, 0x2A, 0x23, 0x33, 0x2F, 0x46, 0x6C, 0xCC, 0xDE, 0x07, 0xCE, 0xB1, 0x6E, 0x6D, 0x7B, 0xD7, 0xCA, 0xBC, 0x0E, 0x65, 0x4E, 0x96, 0x6D, 0x21, 0x54, 0x2C, 0x3E, 0x84, 0x43, 0xAD, 0x08, 0xD5, 0x3C, 0xB4, 0x74, 0xEF, 0x0A, 0xA7, 0x6B, 0x0B, 0xD2, 0x02, 0xC8, 0x1A, 0x5C, 0x98, 0x43, 0x6B, 0x49, 0x77, 0x57, 0x42, 0x40, 0x08, 0x31, 0xB4, 0x76, 0xC0, 0x11, 0x23, 0x96, 0x83, 0x87, 0x13, 0x81, 0x2E, 0x5C, 0x3B, 0x70, 0x75, 0xC9, 0x61, 0x48, 0x9B, 0xE5, 0x43, 0x74, 0xB8, 0x79, 0xD8, 0x2C, 0xCF, 0x83, 0x83, 0x16, 0x14, 0x8A, 0xBD, 0x57, 0x3A, 0xFD, 0x08, 0x5D, 0x6A, 0x98, 0x15, 0xD3, 0x84, 0x89, 0x41, 0xAB, 0xC7, 0x10, 0x3C, 0x2A, 0xE6, 0x78, 0x53, 0x5C, 0x7D, 0x96, 0xB7, 0x0F, 0x69, 0xC1, 0x7C, 0x88, 0xD8, 0x90, 0x18, 0xF3, 0x21, 0x16, 0x6B, 0x97, 0x30, 0xE6, 0x5D, 0xC0, 0x98, 0xB7, 0x17, 0x8B, 0x73, 0x8D, 0x79, 0x9B, 0x56, 0x12, 0xA1, 0x9C, 0x21, 0xC4, 0x73, 0xF8, 0x30, 0x24, 0x0B, 0x9A, 0x1C, 0x55, 0x40, 0x01, 0x05, 0xEE, 0xCA, 0xA2, 0x50, 0x35, 0xEC, 0xA5, 0xFB, 0x48, 0xFF, 0x19, 0x23, 0xCF, 0x12, 0x0E, 0xA5, 0x3B, 0x11, 0x19, 0x24, 0x9C, 0xE1, 0xB1, 0x41, 0x2B, 0xEA, 0xFF, 0x16, 0x82, 0xA2, 0x6E, 0x8A, 0x2D, 0x27, 0xD6, 0x72, 0xA7, 0xD5, 0x75, 0xB1, 0xFC, 0x8A, 0xB1, 0x00, 0xE3, 0x41, 0xB5, 0x9C, 0x07, 0x94, 0x0B, 0x2D, 0x5D, 0x74, 0x78, 0x32, 0xD0, 0x34, 0xE2, 0x54, 0x1A, 0x86, 0xFB, 0x72, 0x74, 0x5C, 0x9C, 0x65, 0x15, 0x2E, 0x74, 0xD1, 0x54, 0x18, 0xEC, 0xD1, 0x24, 0x85, 0x5D, 0x5D, 0x8A, 0xF5, 0x68, 0xC0, 0xB2, 0xA9, 0x78, 0x3E, 0xE9, 0xBA, 0x4D, 0xD7, 0x8C, 0x2E, 0xB6, 0x23, 0xE4, 0xE2, 0x48, 0x06, 0x35, 0x4F, 0xB0, 0x25, 0x74, 0xEE, 0x82, 0x60, 0x31, 0xE0, 0x33, 0x31, 0x61, 0x9B, 0x15, 0x91, 0xA4, 0x04, 0x74, 0xD1, 0x13, 0x01, 0x62, 0xEB, 0x70, 0x5E, 0x82, 0x88, 0x31, 0x51, 0xAA, 0x9F, 0xB7, 0x65, 0x3C, 0x10, 0x0D, 0xF6, 0xF9, 0x07, 0x28, 0x6C, 0xD7, 0xD5, 0x54, 0x28, 0x0A, 0x3A, 0xE4, 0xFB, 0x96, 0x8F, 0x6F, 0x13, 0xF7, 0xC8, 0x75, 0x4E, 0xA5, 0x49, 0xD9, 0x42, 0x39, 0xB4, 0x59, 0xDA, 0xA6, 0x21, 0x11, 0xDC, 0x1C, 0x93, 0x39, 0xC7, 0xCC, 0xF1, 0x0E, 0x47, 0x30, 0x7D, 0xE8, 0x91, 0xE3, 0xE3, 0xD8, 0x58, 0x52, 0xCD, 0x68, 0x73, 0x7B, 0x7B, 0xC3, 0xB3, 0xCE, 0xDD, 0x7D, 0x56, 0xB5, 0x73, 0x9B, 0xFA, 0xDF, 0xE9, 0xF1, 0x02, 0x4F, 0x31, 0x8F, 0x86, 0xA3, 0x07, 0xAE, 0xFE, 0x05, 0x80, 0x40, 0x62, 0x3E, 0x1E, 0x13, 0x2F, 0x80, 0x40, 0xBA, 0x51, 0x85, 0xE2, 0x8A, 0x30, 0x29, 0x0C, 0x99, 0x07, 0x6E, 0xF8, 0x0A, 0x18, 0x41, 0x89, 0x03, 0x20, 0xA6, 0x8C, 0x09, 0x62, 0x8C, 0x59, 0x5B, 0x04, 0xB4, 0x54, 0x76, 0x81, 0x0F, 0x5E, 0xC0, 0x9E, 0x9C, 0x34, 0x50, 0xD8, 0x70, 0x3A, 0x53, 0x1B, 0xE3, 0x00, 0x59, 0xB9, 0x77, 0x2E, 0x4E, 0xD5, 0x8A, 0x81, 0x43, 0xB4, 0x44, 0x46, 0x8C, 0x47, 0x98, 0x6E, 0x2B, 0xF0, 0x5D, 0xA9, 0xAA, 0x9F, 0xC9, 0xFC, 0xC9, 0x31, 0x06, 0x0C, 0xB4, 0x9B, 0x22, 0x22, 0x67, 0xBA, 0x03, 0x56, 0x7B, 0x33, 0xBC, 0x8C, 0x02, 0x57, 0x22, 0xF0, 0xA6, 0x4F, 0x51, 0x7F, 0xD5, 0x99, 0xAA, 0xC5, 0x97, 0xB4, 0x47, 0x52, 0x25, 0xCF, 0xAD, 0x44, 0x1D, 0xE0, 0xC8, 0x44, 0x8F, 0x6F, 0xAF, 0xCE, 0x1D, 0x55, 0xD4, 0x68, 0x4D, 0xCB, 0x06, 0xC4, 0xE8, 0xC7, 0x56, 0x96, 0x11, 0x90, 0x00, 0xEA, 0xED, 0x51, 0x82, 0xA4, 0x68, 0x23, 0x40, 0x84, 0xE6, 0x5F, 0x11, 0x9F, 0x29, 0x05, 0x82, 0xAE, 0x83, 0x9E, 0x89, 0x07, 0x82, 0xAE, 0x34, 0xAE, 0xCD, 0x04, 0xE2, 0x6C, 0x08, 0x34, 0xBC, 0xD2, 0x25, 0x38, 0xAC, 0x5C, 0x5B, 0xFE, 0x87, 0xD8, 0x49, 0x81, 0xC6, 0xB5, 0x4C, 0x00, 0xC0, 0x62, 0x8F, 0x0A, 0x61, 0x76, 0x48, 0x71, 0x0C, 0x40, 0x16, 0x1B, 0x53, 0x10, 0x02, 0x69, 0x88, 0x30, 0x20, 0xBC, 0x84, 0xA1, 0x71, 0x17, 0xAA, 0x6F, 0xE1, 0x40, 0x60, 0x95, 0xE0, 0x93, 0xCB, 0xE6, 0x36, 0x56, 0xC2, 0xFE, 0x95, 0x0C, 0xFD, 0xC0, 0x3E, 0x7C, 0x22, 0x78, 0xCD, 0xF7, 0x64, 0xF1, 0xAC, 0xF8, 0xE8, 0x36, 0x16, 0x0E, 0xDB, 0x7B, 0x1A, 0x84, 0xFD, 0x83, 0x09, 0x83, 0x7F, 0x17, 0x18, 0x47, 0xBC, 0x99, 0xE4, 0xF5, 0xDE, 0x91, 0xD7, 0xCA, 0x85, 0xBF, 0x40, 0xF5, 0x9E, 0x0B, 0x54, 0x17, 0xD7, 0xE6, 0x8E, 0x99, 0x79, 0x1B, 0x78, 0x9D, 0x17, 0xC4, 0x8B, 0x7D, 0x2F, 0x26, 0x78, 0xED, 0x85, 0x04, 0x2F, 0xBD, 0xAD, 0x29, 0x5E, 0x6F, 0x49, 0xF1, 0x5A, 0x8A, 0x61, 0xC9, 0x4C, 0x93, 0x85, 0xD2, 0x55, 0xEE, 0x2F, 0x71, 0x06, 0xFD, 0xDC, 0xF3, 0x46, 0x0A, 0x2D, 0x12, 0x3F, 0x3E, 0x54, 0xDB, 0x96, 0x78, 0xBA, 0xA2, 0xC1, 0x2B, 0x5C, 0x8B, 0x6A, 0x90, 0x18, 0x04, 0x3A, 0x5D, 0x57, 0xD5, 0x30, 0xE7, 0x5A, 0x90, 0x22, 0x67, 0x77, 0x35, 0x18, 0x92, 0xAB, 0xC1, 0xDC, 0xAE, 0xFE, 0xC9, 0xD5, 0x7B, 0x70, 0x65, 0x86, 0xA8, 0x03, 0x86, 0xA2, 0xA0, 0x89, 0x16, 0x38, 0xD7, 0x8F, 0x24, 0xE9, 0xA0, 0x68, 0xC6, 0x6A, 0x14, 0x05, 0xF8, 0x30, 0x0E, 0xFD, 0xA6, 0xED, 0x8A, 0xAA, 0x40, 0xD5, 0x4A, 0x3A, 0xF0, 0x8B, 0xB3, 0x3A, 0x8E, 0xE8, 0x31, 0x75, 0xFE, 0x7C, 0x26, 0x5F, 0xC2, 0x8C, 0xDE, 0x6E, 0x91, 0x19, 0x70, 0x5F, 0xAE, 0x7F, 0x53, 0x96, 0x52, 0x1B, 0xE2, 0x1C, 0x43, 0xCF, 0x95, 0x1E, 0xB6, 0xB8, 0xC9, 0x83, 0x95, 0xC6, 0x4C, 0x0F, 0x16, 0xBA, 0xE5, 0x8D, 0x72, 0x36, 0xD3, 0xD3, 0x52, 0xA4, 0xCD, 0x90, 0xA9, 0xDB, 0x40, 0x60, 0x57, 0x84, 0xC8, 0x94, 0xF0, 0x67, 0xCB, 0x08, 0xF9, 0x16, 0x10, 0xB9, 0x64, 0xC4, 0x54, 0x96, 0x8C, 0xCF, 0x70, 0x06, 0x51, 0x39, 0x65, 0xAB, 0x64, 0x00, 0x0B, 0x1B, 0x07, 0x83, 0x4A, 0xC6, 0x87, 0xC4, 0x60, 0x0A, 0xBC, 0x55, 0xD4, 0xF4, 0x06, 0xDB, 0xE2, 0xAB, 0x38, 0x14, 0x29, 0x32, 0xAE, 0xF6, 0xC3, 0x28, 0x4C, 0x3D, 0x48, 0x8D, 0xAC, 0x67, 0x03, 0xEC, 0xBE, 0x54, 0x0A, 0x12, 0x57, 0xA7, 0x42, 0xB4, 0x46, 0x32, 0xD4, 0x87, 0xE2, 0xEC, 0x71, 0x34, 0x89, 0xBD, 0x33, 0x49, 0x9F, 0xB9, 0x42, 0xD9, 0x54, 0x70, 0x09, 0xE3, 0xB4, 0x86, 0x29, 0x57, 0xCF, 0x93, 0x33, 0x1C, 0x9E, 0x9C, 0xA1, 0x58, 0xDA, 0xF6, 0xF9, 0x43, 0xEA, 0x67, 0x8F, 0xEF, 0xD1, 0x94, 0x42, 0x57, 0xA5, 0x3E, 0x95, 0x7A, 0x74, 0xCF, 0x0E, 0x09, 0xA9, 0xDB, 0xD2, 0xAD, 0xA8, 0x93, 0x58, 0x51, 0x43, 0x8D, 0x02, 0x09, 0x82, 0xE9, 0x24, 0x9F, 0xA9, 0x29, 0x19, 0x13, 0xA8, 0xD7, 0x02, 0xBF, 0xB1, 0x92, 0x10, 0x9B, 0x8A, 0x42, 0x64, 0x00, 0x21, 0x29, 0x3F, 0x3F, 0xB7, 0x69, 0x08, 0x2B, 0x8A, 0xF4, 0xAB, 0x6B, 0xCE, 0xF3, 0x63, 0x79, 0x9E, 0x1E, 0x5C, 0x3D, 0x96, 0x73, 0x35, 0xC7, 0xB4, 0x96, 0x8F, 0x65, 0x39, 0xBF, 0x96, 0x3B, 0xBD, 0x82, 0x78, 0x5D, 0xCB, 0xF1, 0x52, 0x2C, 0x57, 0x99, 0x17, 0x05, 0xE6, 0xED, 0x28, 0x9F, 0x9C, 0x13, 0x98, 0x39, 0x4E, 0x85, 0x90, 0x04, 0x97, 0xC1, 0xC4, 0x84, 0xDE, 0x15, 0x4D, 0x4C, 0x36, 0x50, 0x34, 0x7B, 0x41, 0x93, 0xA3, 0xE9, 0x2A, 0x30, 0x78, 0x81, 0xC0, 0x55, 0xBE, 0xA4, 0xDB, 0xD4, 0x67, 0xAD, 0x98, 0x0A, 0x20, 0xE4, 0x35, 0x43, 0x7F, 0x52, 0x92, 0x28, 0x2A, 0xA5, 0xD0, 0xF5, 0x8B, 0x8D, 0x09, 0xDB, 0xA2, 0xAE, 0x50, 0xF4, 0x64, 0xBA, 0x33, 0x7E, 0x5A, 0x10, 0xC8, 0x25, 0xEB, 0x59, 0x64, 0x99, 0x13, 0x2C, 0x51, 0xB0, 0xD0, 0xC2, 0xDD, 0x0F, 0x56, 0x78, 0x60, 0x90, 0xD4, 0x36, 0x77, 0x06, 0xD5, 0x43, 0x76, 0xD1, 0x31, 0xA7, 0x43, 0x03, 0x1D, 0x22, 0xD8, 0x3D, 0x2D, 0x42, 0x95, 0x27, 0x32, 0xA8, 0xF7, 0x98, 0x50, 0x7B, 0x54, 0xE5, 0x7B, 0x52, 0xE5, 0xED, 0xCA, 0x3C, 0xE5, 0xA9, 0x18, 0xE5, 0x40, 0x52, 0x21, 0x54, 0x4C, 0xC4, 0x35, 0x3A, 0xA0, 0x5E, 0x83, 0x00, 0xD4, 0x6D, 0x6A, 0xC1, 0x1E, 0x2E, 0x5B, 0x5C, 0xB2, 0x92, 0x43, 0x30, 0x5C, 0xBA, 0x64, 0x2D, 0xF6, 0x7A, 0x43, 0x41, 0x30, 0xD7, 0x00, 0x2A, 0x3A, 0xC4, 0x69, 0xA0, 0x78, 0x19, 0x28, 0x6E, 0x53, 0xB6, 0x0D, 0x90, 0x9E, 0x7D, 0xB2, 0x20, 0xE3, 0xE2, 0x21, 0x25, 0x11, 0x3A, 0x87, 0x21, 0xA5, 0xD5, 0x6B, 0x4C, 0x29, 0x9F, 0x8B, 0x9B, 0xE4, 0x49, 0xCD, 0x1D, 0x77, 0x74, 0xD4, 0x79, 0x93, 0xDF, 0xF2, 0x69, 0x53, 0xB4, 0x84, 0x96, 0xC9, 0x3A, 0x97, 0x2A, 0x09, 0xFA, 0x03, 0xF2, 0xA9, 0x5B, 0xE2, 0x88, 0x38, 0x39, 0xB9, 0xBF, 0x8E, 0x9E, 0x39, 0xC3, 0x90, 0x83, 0x44, 0x2B, 0x90, 0x1D, 0x0B, 0xCC, 0x7C, 0x19, 0x30, 0x60, 0x4F, 0xF0, 0x21, 0xF6, 0x53, 0xA0, 0xE3, 0x38, 0x11, 0xCF, 0x64, 0x15, 0x38, 0x5E, 0x21, 0xA1, 0x4C, 0xE2, 0x19, 0xF4, 0xCC, 0x9A, 0x7C, 0x60, 0x66, 0xC1, 0xC5, 0x4A, 0x2E, 0xE5, 0xAC, 0xB0, 0xB6, 0x73, 0x1A, 0x8D, 0xD2, 0x04, 0xD6, 0x4A, 0x22, 0xAC, 0x8F, 0x3D, 0x14, 0x7D, 0x7C, 0x53, 0xB4, 0x5D, 0x0F, 0x25, 0x8E, 0x7E, 0xC2, 0xE1, 0x68, 0x11, 0xD4, 0x5C, 0xA6, 0xCF, 0xF1, 0x32, 0x45, 0x1C, 0xF7, 0xF4, 0x8E, 0xDE, 0xBB, 0x21, 0x4C, 0x38, 0xDC, 0x7B, 0x22, 0xBA, 0x2D, 0x22, 0xD1, 0xB6, 0xAA, 0xF9, 0xD1, 0xD3, 0x3C, 0x8D, 0x16, 0x65, 0xC3, 0x80, 0xE5, 0x7C, 0x05, 0x34, 0x01, 0x7D, 0x59, 0x22, 0x69, 0x78, 0x23, 0xE8, 0xC0, 0x29, 0xF4, 0xDF, 0x18, 0x35, 0xA4, 0x9F, 0x6E, 0x9F, 0xD5, 0x15, 0x98, 0x12, 0x47, 0x7F, 0x2E, 0x12, 0x79, 0x4B, 0x56, 0xF9, 0x0D, 0xFC, 0xF6, 0xE1, 0x71, 0x0A, 0x67, 0x5E, 0xCC, 0x06, 0x79, 0xE0, 0xE8, 0x21, 0x93, 0xC1, 0x5A, 0x44, 0x5B, 0xB7, 0x11, 0xF3, 0xEF, 0x70, 0x34, 0x49, 0x53, 0x75, 0xBF, 0x7B, 0x2A, 0x6D, 0xA8, 0x4A, 0xEF, 0x2C, 0xD5, 0xDF, 0x04, 0x02, 0xA4, 0x50, 0x2D, 0x6D, 0x7B, 0x75, 0x55, 0xD3, 0x71, 0xAB, 0xDA, 0x88, 0x82, 0x10, 0xF5, 0x0F, 0x1B, 0xB4, 0x34, 0x6B, 0x83, 0x12, 0x9A, 0x53, 0x54, 0x91, 0x72, 0xCB, 0x40, 0x31, 0xFA, 0xCD, 0x89, 0xD1, 0x6D, 0xC9, 0x26, 0x46, 0x27, 0x99, 0x18, 0x6D, 0xC7, 0x84, 0x25, 0x2B, 0xA2, 0xCA, 0xB1, 0xFC, 0x0C, 0x0E, 0xA4, 0xF1, 0x26, 0xE0, 0x9B, 0xD8, 0xA4, 0x07, 0x9C, 0x4A, 0x10, 0x95, 0x89, 0x99, 0xBE, 0xFD, 0xF5, 0x2A, 0xEA, 0xE4, 0xA7, 0xCF, 0x40, 0x71, 0xA9, 0x4A, 0xBC, 0xE8, 0x4F, 0x88, 0x40, 0xAD, 0x05, 0x44, 0x67, 0x0A, 0x77, 0xDF, 0x88, 0x36, 0xB4, 0x75, 0x6E, 0x80, 0x56, 0x3C, 0x39, 0x73, 0x19, 0x16, 0x77, 0x6D, 0x1B, 0x8A, 0xA7, 0x08, 0xAD, 0x61, 0x10, 0x3A, 0xEB, 0x8F, 0x21, 0x6E, 0x69, 0x3F, 0x4F, 0x67, 0x77, 0x42, 0x19, 0x50, 0x82, 0xB0, 0x30, 0x20, 0x17, 0x82, 0x21, 0x52, 0x18, 0xD1, 0xCA, 0xE3, 0x44, 0x56, 0x40, 0x06, 0x70, 0xCE, 0x4C, 0x89, 0x20, 0x79, 0x91, 0xD4, 0x44, 0x52, 0x40, 0xBD, 0x67, 0x91, 0xD4, 0xC8, 0x25, 0x2D, 0x47, 0x8A, 0x88, 0x5D, 0x54, 0xF7, 0xA5, 0xB8, 0x14, 0xF5, 0x1D, 0xF8, 0x42, 0xF7, 0xE5, 0xEE, 0xB4, 0x84, 0xE8, 0xFC, 0xE1, 0x1F, 0xD5, 0xD1, 0x27, 0x9C, 0xBB, 0xA5, 0xA5, 0xE0, 0xB9, 0x23, 0xF4, 0x21, 0xC0, 0xAE, 0x0F, 0x01, 0xE8, 0x1C, 0x39, 0x02, 0x72, 0xB4, 0x04, 0xFC, 0xAB, 0x29, 0xD5, 0x0B, 0xA0, 0x08, 0xAD, 0x0C, 0x3C, 0xA4, 0x65, 0xB8, 0x80, 0x80, 0xA4, 0xFD, 0x68, 0x24, 0x49, 0x3D, 0x91, 0x96, 0xBA, 0x43, 0x62, 0x23, 0xB7, 0x1D, 0x0C, 0x2B, 0x4E, 0xBE, 0xB0, 0x62, 0xAF, 0x99, 0xB7, 0xE4, 0x06, 0xC3, 0xD7, 0x6E, 0x2D, 0x7C, 0x95, 0x71, 0xFA, 0xBA, 0xCC, 0x89, 0xAF, 0x9F, 0xC8, 0x2C, 0xFD, 0x34, 0x86, 0x6A, 0x21, 0xA3, 0xA6, 0x1A, 0x42, 0x6F, 0x08, 0x20, 0xB9, 0x5B, 0x9A, 0x61, 0xA3, 0xBD, 0x20, 0x04, 0xE5, 0xD0, 0x74, 0xE0, 0x65, 0xDB, 0x17, 0xA3, 0x70, 0xF7, 0x75, 0x8D, 0x1A, 0x7A, 0x64, 0x39, 0x02, 0xFC, 0xD0, 0x33, 0x30, 0x26, 0x17, 0xDF, 0x1E, 0x45, 0x81, 0xD6, 0xBA, 0xCE, 0x47, 0x29, 0x18, 0x1C, 0xCE, 0x10, 0xB8, 0xE0, 0x66, 0x39, 0x0D, 0xD9, 0x45, 0x24, 0xA4, 0xB1, 0x1D, 0x15, 0x19, 0xBF, 0xE4, 0x26, 0xD6, 0x08, 0x1A, 0x2D, 0x60, 0xC5, 0xD6, 0x27, 0x9F, 0x36, 0xA0, 0x21, 0xE0, 0x39, 0xC4, 0x3B, 0x92, 0x1E, 0x01, 0xFF, 0xF1, 0x80, 0xD3, 0x1B, 0x19, 0x2D, 0x7E, 0x4A, 0x3A, 0x57, 0x74, 0xE0, 0xFA, 0x13, 0x65, 0xD4, 0xA5, 0x38, 0x7D, 0x75, 0x28, 0x05, 0x33, 0x98, 0x70, 0x4B, 0x8B, 0x8B, 0xB3, 0xCD, 0x94, 0x24, 0xC2, 0x93, 0x91, 0x48, 0xB9, 0xE5, 0x94, 0xFE, 0x70, 0x1C, 0x02, 0x34, 0x85, 0x92, 0x62, 0xA8, 0x7E, 0x2E, 0xC1, 0x81, 0x75, 0x07, 0x16, 0x40, 0x7F, 0x84, 0x5E, 0x87, 0x2A, 0xD0, 0x9A, 0xD6, 0x09, 0xA1, 0xB4, 0x04, 0x9B, 0xD2, 0x86, 0x30, 0x1D, 0xC6, 0x04, 0xE4, 0xD6, 0xC4, 0x9C, 0x1F, 0xC7, 0x2B, 0xF8, 0xA7, 0x90, 0x1F, 0x47, 0xA7, 0x14, 0x54, 0x58, 0x4E, 0x74, 0x66, 0x7B, 0xD0, 0xBE, 0x9D, 0x40, 0xF4, 0x1B, 0x09, 0xF7, 0xCD, 0xE8, 0x68, 0x1C, 0x49, 0x18, 0x9D, 0x22, 0x88, 0x87, 0x2E, 0x6C, 0xEB, 0x69, 0xEA, 0x26, 0x8C, 0xA2, 0xBF, 0x13, 0x3E, 0x7C, 0x14, 0xED, 0xA3, 0x47, 0x91, 0xB2, 0x2E, 0xEA, 0x22, 0x23, 0x1E, 0x91, 0x08, 0xB9, 0xB4, 0x9E, 0xF6, 0x5B, 0xA9, 0x92, 0x74, 0x02, 0x21, 0x24, 0x0D, 0x49, 0xE0, 0xB5, 0x9A, 0xBB, 0x7C, 0x10, 0xC4, 0xEB, 0x2A, 0xA1, 0x10, 0xD8, 0x9A, 0xCF, 0x1F, 0x2D, 0x64, 0x9D, 0x65, 0x45, 0x0F, 0x1B, 0x05, 0x06, 0x57, 0x25, 0x4C, 0xB4, 0xC5, 0x69, 0x6D, 0xDC, 0x80, 0x09, 0x41, 0x6B, 0x54, 0x69, 0x51, 0x98, 0xD3, 0x62, 0x79, 0xFF, 0x02, 0x14, 0x60, 0x81, 0x12, 0xC1, 0x83, 0xB0, 0x07, 0x21, 0x60, 0x5C, 0x95, 0x55, 0x0B, 0x15, 0x5B, 0x94, 0xD1, 0xFE, 0x2E, 0x70, 0x0A, 0x52, 0xCF, 0x50, 0x36, 0x7A, 0x38, 0x28, 0xD9, 0xE8, 0xE1, 0x29, 0xD7, 0xBB, 0x63, 0xFA, 0x0B, 0x0E, 0x18, 0x7A, 0x14, 0x94, 0x0E, 0xB0, 0xAF, 0xB7, 0xAA, 0xA8, 0x29, 0x70, 0x6D, 0xBB, 0xCA, 0xBC, 0x5D, 0x19, 0x8B, 0xAA, 0xDD, 0xD6, 0xE9, 0x5C, 0x2F, 0x02, 0xE3, 0x17, 0xDB, 0x4C, 0x46, 0x3D, 0x7D, 0x00, 0xB5, 0x18, 0x29, 0xC2, 0x62, 0x13, 0x38, 0xDC, 0x1E, 0xCC, 0x38, 0x55, 0x3D, 0x23, 0x66, 0xFC, 0xDD, 0xE9, 0x2E, 0xF1, 0x60, 0x5D, 0xB2, 0xC4, 0xAD, 0x6D, 0x57, 0xE2, 0x17, 0x0A, 0xD0, 0x5F, 0x01, 0x03, 0xA4, 0x51, 0x5F, 0xD4, 0xB8, 0xBF, 0x17, 0x23, 0xA8, 0xC4, 0xCE, 0xE0, 0xC2, 0xC5, 0x60, 0x89, 0xAB, 0x38, 0x3F, 0x2F, 0xF0, 0x00, 0xFF, 0x09, 0xB6, 0x26, 0xC5, 0x12, 0x00, 0x65, 0x1D, 0x4F, 0xA2, 0x7C, 0xE8, 0x79, 0xF6, 0x01, 0x27, 0x51, 0x4C, 0x66, 0x48, 0x7F, 0x86, 0x40, 0x17, 0xA5, 0x42, 0x09, 0x70, 0x25, 0xC8, 0x70, 0x2B, 0xA1, 0x02, 0x20, 0x54, 0x31, 0x51, 0xC3, 0x19, 0xF7, 0x85, 0x89, 0x8A, 0x9B, 0x52, 0x9E, 0x4C, 0xB1, 0x2E, 0x19, 0x01, 0xEC, 0x92, 0x61, 0xAE, 0xDE, 0x7B, 0x1E, 0x61, 0xFD, 0x96, 0x1E, 0x41, 0xEE, 0xE6, 0x77, 0x62, 0x37, 0xE6, 0xED, 0x3B, 0x31, 0xB3, 0x68, 0xE2, 0x8F, 0x1A, 0x5B, 0xCC, 0x36, 0x2B, 0x71, 0x4A, 0xFF, 0x06, 0x63, 0x93, 0x9C, 0x02, 0x02, 0x08, 0x2F, 0xDE, 0x8E, 0x6C, 0x58, 0x63, 0x1A, 0x7F, 0x55, 0x78, 0x18, 0x85, 0x89, 0x9A, 0xCE, 0x00, 0xA2, 0x96, 0x0E, 0x3A, 0x72, 0xA2, 0x40, 0x7A, 0x1F, 0xA4, 0xD8, 0xAE, 0x07, 0x2E, 0x33, 0xCD, 0xB6, 0x1E, 0xFB, 0xDB, 0x92, 0xD2, 0x95, 0xA9, 0x98, 0x89, 0x42, 0x0B, 0x41, 0x44, 0x26, 0x5D, 0x52, 0x85, 0x74, 0xA1, 0xB7, 0x81, 0xC2, 0x42, 0x1B, 0x27, 0xDC, 0xD2, 0xB9, 0xA8, 0x69, 0x66, 0x10, 0x2D, 0x46, 0xEE, 0x5D, 0x96, 0x05, 0x15, 0x09, 0x2F, 0x6C, 0x82, 0x0E, 0x67, 0x37, 0x28, 0x87, 0xD2, 0xF0, 0x23, 0x3C, 0x9C, 0x10, 0xC9, 0x91, 0x36, 0x19, 0xAA, 0xEA, 0x6B, 0xAA, 0x0E, 0x7F, 0x9C, 0x29, 0x12, 0x1F, 0xE1, 0x60, 0x90, 0x90, 0x9D, 0xD4, 0xA1, 0x85, 0xD0, 0xF1, 0xFE, 0x54, 0x71, 0x40, 0x95, 0xBD, 0x57, 0x31, 0xC4, 0xBA, 0x89, 0xF7, 0x18, 0x69, 0x87, 0xDC, 0x58, 0x3E, 0xA4, 0x46, 0x50, 0x93, 0x4E, 0xDF, 0x4C, 0xC6, 0xE3, 0x53, 0x9E, 0x09, 0x52, 0xA3, 0x47, 0x79, 0xD0, 0xC9, 0x09, 0x30, 0xCC, 0x9C, 0xE8, 0x66, 0xBF, 0x0C, 0x3B, 0xCA, 0xA3, 0x5C, 0x15, 0x88, 0xB6, 0xB7, 0x2F, 0x07, 0x00, 0xBA, 0xA9, 0xA9, 0x1E, 0x9F, 0x39, 0x39, 0x63, 0xDA, 0xEE, 0x6F, 0x42, 0x8F, 0x17, 0x65, 0x63, 0x96, 0x63, 0x4E, 0xBB, 0x28, 0xC6, 0x9C, 0xF6, 0x6B, 0x09, 0x1B, 0x13, 0xC6, 0x04, 0x53, 0x67, 0x26, 0xEC, 0x97, 0x3A, 0xC2, 0x0F, 0x41, 0xFD, 0xD3, 0xA8, 0x98, 0xF0, 0xAC, 0xFF, 0x3C, 0xB6, 0x2D, 0x65, 0x90, 0x3E, 0xB3, 0xD8, 0xB6, 0xC4, 0x05, 0x88, 0x11, 0x64, 0x9A, 0xD0, 0x40, 0x43, 0xCA, 0x83, 0x22, 0x96, 0xE4, 0xC8, 0x90, 0x5A, 0x9B, 0x12, 0xA3, 0x8A, 0xEA, 0xEF, 0x24, 0x2A, 0x2A, 0xE0, 0x09, 0xE4, 0x7F, 0xAB, 0xBB, 0xA0, 0xEB, 0xBA, 0x38, 0xDB, 0x5C, 0x84, 0x7A, 0xD6, 0x54, 0xCC, 0x27, 0x84, 0xCA, 0x4D, 0xB4, 0x00, 0x29, 0xD0, 0xB6, 0x24, 0x9C, 0x2E, 0xA9, 0x0C, 0x83, 0x2C, 0x1D, 0x2C, 0xAE, 0x80, 0xD7, 0xA8, 0xB6, 0x2A, 0x30, 0x5D, 0x4D, 0x1F, 0x16, 0xFD, 0xE5, 0xA0, 0xE6, 0x79, 0x45, 0x69, 0x0D, 0x8C, 0xD2, 0x09, 0xA1, 0x12, 0x15, 0x25, 0xA2, 0x22, 0xAD, 0x16, 0xBD, 0xC3, 0x0C, 0x1A, 0xBA, 0x4D, 0xFD, 0xFB, 0xEE, 0x84, 0x9E, 0x89, 0x67, 0x4B, 0x56, 0x0B, 0xA4, 0x63, 0x3C, 0x96, 0xB1, 0x42, 0x66, 0xC8, 0x04, 0x2C, 0x6D, 0x05, 0xE8, 0x11, 0x13, 0xB7, 0x96, 0x97, 0x97, 0x2C, 0x2A, 0x40, 0x9A, 0xEE, 0x0A, 0xE8, 0x13, 0x3B, 0xCB, 0x1E, 0x31, 0x26, 0x0C, 0x14, 0x3F, 0xCD, 0x8C, 0x2E, 0xC4, 0xE6, 0x6B, 0x21, 0x36, 0x0E, 0x0A, 0x96, 0xFB, 0x98, 0x60, 0x29, 0x26, 0xC6, 0x62, 0x90, 0x35, 0x4B, 0x41, 0xD6, 0x5B, 0x3B, 0x28, 0xDF, 0x33, 0xA3, 0x4C, 0xF3, 0xC7, 0x35, 0xCA, 0x32, 0x85, 0xCD, 0x64, 0x8C, 0x09, 0x20, 0x00, 0x12, 0x29, 0xC0, 0x6B, 0x9E, 0x8C, 0x88, 0x3B, 0x32, 0xA9, 0x3F, 0x24, 0x09, 0xEC, 0x9F, 0x91, 0x28, 0x00, 0xF6, 0xCF, 0x1B, 0xFA, 0x75, 0x02, 0x04, 0xD4, 0xA4, 0x98, 0x97, 0xB6, 0x18, 0xCD, 0x80, 0xA6, 0xF9, 0x5A, 0xA6, 0xD1, 0x90, 0x90, 0x71, 0x5A, 0x3E, 0xE8, 0xDC, 0xEB, 0x5E, 0x55, 0x6D, 0x17, 0x30, 0xED, 0x43, 0x0C, 0x01, 0x90, 0xD1, 0x42, 0xAB, 0x0C, 0xD6, 0x72, 0x48, 0x46, 0x70, 0x04, 0x95, 0xCC, 0x0E, 0xD7, 0x3D, 0x7A, 0x60, 0xE1, 0x76, 0x8C, 0x68, 0x86, 0xA8, 0x4A, 0x8A, 0x2D, 0xC8, 0x81, 0x8B, 0x8D, 0x39, 0x5D, 0x68, 0x6C, 0x30, 0x48, 0xD8, 0x13, 0x86, 0x88, 0x30, 0x51, 0x4B, 0xA8, 0xE0, 0x6C, 0x88, 0x58, 0x41, 0xEC, 0xEE, 0xD4, 0x00, 0x0B, 0x31, 0x16, 0x54, 0x1B, 0x7F, 0x55, 0x7D, 0x0D, 0x3A, 0x32, 0x54, 0xD1, 0xB2, 0x9F, 0x20, 0x3C, 0x3D, 0x54, 0x0F, 0x05, 0x23, 0x32, 0x7A, 0x26, 0x01, 0xBF, 0xD3, 0xF8, 0x1C, 0x19, 0xFD, 0x63, 0x47, 0x05, 0x80, 0xE4, 0x1A, 0xBF, 0x80, 0x64, 0xF5, 0xB6, 0xA4, 0x1D, 0x73, 0x7F, 0x79, 0xBE, 0x70, 0x9F, 0x75, 0x01, 0xC9, 0xD5, 0xFD, 0x3D, 0x0D, 0xCE, 0x8C, 0xF5, 0xF9, 0x6B, 0x83, 0xC1, 0x25, 0xCB, 0x33, 0x06, 0x63, 0xC7, 0x99, 0x31, 0xF6, 0x71, 0xD8, 0x62, 0x35, 0x00, 0x52, 0xB0, 0xD2, 0x7B, 0x7B, 0x62, 0xF5, 0x9B, 0x12, 0xAB, 0x9F, 0x40, 0xAE, 0x7E, 0xEE, 0xB8, 0x9A, 0x87, 0x19, 0xAA, 0x79, 0x94, 0xA1, 0xDA, 0x46, 0x09, 0x56, 0xDB, 0xB0, 0x52, 0x56, 0xE7, 0x78, 0x19, 0x10, 0xC7, 0xB7, 0xB6, 0x62, 0x35, 0x93, 0x62, 0x9B, 0x94, 0xCA, 0x06, 0x8C, 0x9F, 0x35, 0xFE, 0x80, 0xFC, 0x92, 0xD2, 0x5F, 0x4D, 0x0A, 0x10, 0x09, 0x61, 0xFE, 0x0B, 0x12, 0xAE, 0xD5, 0xBF, 0xE8, 0x39, 0x72, 0x50, 0x39, 0x1D, 0x90, 0x56, 0xCB, 0xE1, 0x33, 0x6C, 0x90, 0xD6, 0xB2, 0xA6, 0x99, 0x1C, 0x40, 0x18, 0xD8, 0xAF, 0x5D, 0x17, 0xC2, 0xE5, 0x94, 0xB8, 0x0B, 0x0B, 0x5E, 0x28, 0x44, 0xCF, 0xB0, 0x6D, 0xA8, 0x03, 0xFD, 0x4C, 0x4C, 0x55, 0xA0, 0x50, 0xFD, 0xAE, 0x41, 0x21, 0x9A, 0xAA, 0x43, 0xB8, 0x95, 0x4A, 0x90, 0xEA, 0x02, 0xBD, 0x82, 0x9F, 0xF0, 0x05, 0xBF, 0xEF, 0x91, 0x14, 0x95, 0xDB, 0xED, 0xAC, 0xB0, 0xBF, 0x32, 0xA1, 0xD2, 0x73, 0x09, 0xE6, 0x23, 0x9F, 0x9E, 0x7D, 0x53, 0x1C, 0xA2, 0x41, 0x88, 0x14, 0x0B, 0xCF, 0xB4, 0x57, 0x50, 0x9E, 0x68, 0xBB, 0x29, 0x28, 0x4F, 0xC0, 0x95, 0xE0, 0x56, 0x42, 0x01, 0x45, 0xFC, 0xEA, 0xB0, 0x59, 0x44, 0x40, 0xB5, 0xF8, 0x01, 0xAA, 0x84, 0x69, 0x98, 0x83, 0x01, 0x0D, 0xE5, 0x92, 0xCB, 0x25, 0x4B, 0x17, 0xE4, 0x43, 0x0B, 0xE4, 0xA4, 0xB6, 0x20, 0xEE, 0x0E, 0xF3, 0xF2, 0x36, 0xB8, 0xC6, 0x38, 0xA9, 0x8D, 0x47, 0xA0, 0x1C, 0x08, 0xA2, 0x59, 0xD6, 0x23, 0xEA, 0x34, 0x14, 0x87, 0x53, 0xEA, 0x35, 0xAC, 0xD4, 0x4F, 0x15, 0x48, 0xFD, 0x34, 0xC4, 0xEE, 0x89, 0xBC, 0x79, 0x87, 0x1C, 0xCF, 0x9B, 0x69, 0xE2, 0x70, 0xA2, 0x59, 0x77, 0xE2, 0xE6, 0xD1, 0xE9, 0xE6, 0xEA, 0xD4, 0x24, 0x16, 0xFC, 0x0A, 0x3E, 0x8F, 0xC8, 0x41, 0x8A, 0x07, 0x5A, 0x88, 0xEF, 0xD5, 0x69, 0x49, 0xDE, 0xE3, 0x7E, 0x7E, 0x8F, 0x8C, 0x04, 0x5E, 0xC6, 0x32, 0x5A, 0xB3, 0x79, 0xC5, 0x75, 0xAB, 0x08, 0x8A, 0xED, 0x36, 0x40, 0x71, 0xF2, 0x8A, 0xD6, 0x49, 0x18, 0x5B, 0x74, 0x6B, 0xC8, 0x75, 0x6F, 0x8D, 0x0D, 0xC5, 0xCB, 0xFB, 0x44, 0x54, 0x82, 0x82, 0x3C, 0x5C, 0x44, 0x28, 0x0A, 0xF5, 0x85, 0x8A, 0x5C, 0x4F, 0x43, 0x6B, 0x92, 0x24, 0x71, 0x46, 0x90, 0xBC, 0x04, 0x0E, 0x4C, 0xD1, 0x8B, 0x27, 0xB4, 0xD7, 0xA5, 0x7F, 0x7F, 0x56, 0xC1, 0x07, 0xE8, 0x62, 0x38, 0x2D, 0xC7, 0x39, 0xB0, 0x8C, 0xF7, 0x29, 0x27, 0xF9, 0x4B, 0x36, 0x4E, 0x06, 0xD7, 0x60, 0xB5, 0xE7, 0x94, 0xE2, 0x82, 0x36, 0x10, 0x90, 0x18, 0xD8, 0x42, 0x95, 0x2F, 0xFF, 0x3E, 0x44, 0x00, 0x03, 0xCD, 0x72, 0x51, 0x58, 0x33, 0xDA, 0x30, 0x16, 0x53, 0x30, 0x4A, 0xA7, 0x6C, 0x4A, 0x65, 0x53, 0x45, 0x97, 0x29, 0x8B, 0x0E, 0xB3, 0xBB, 0x72, 0xD9, 0x9A, 0x2E, 0xD4, 0xD2, 0x57, 0xB9, 0x1A, 0x01, 0x62, 0x01, 0xED, 0xAA, 0x7D, 0xAC, 0x6E, 0x32, 0x4A, 0x9F, 0xD5, 0x87, 0x80, 0xDF, 0x23, 0x2B, 0xAA, 0x96, 0x44, 0x64, 0x62, 0xF6, 0xC8, 0x84, 0xD0, 0xF3, 0xA1, 0xBB, 0xC3, 0x43, 0x0A, 0x2C, 0x74, 0xCD, 0x0E, 0x08, 0x40, 0x8A, 0x85, 0x12, 0xB6, 0xA5, 0x3F, 0x18, 0x21, 0x94, 0xB6, 0xE4, 0x19, 0x82, 0x67, 0x88, 0x9D, 0x91, 0xDF, 0x65, 0x65, 0x5C, 0x60, 0x98, 0x9A, 0xEF, 0x85, 0xA9, 0x7F, 0xA8, 0x32, 0x70, 0x9A, 0xEA, 0xCB, 0x89, 0x8D, 0xD0, 0x0E, 0xBA, 0x99, 0xE5, 0xCE, 0x52, 0xAA, 0x91, 0x4E, 0x40, 0x62, 0x84, 0x28, 0xCE, 0x02, 0x88, 0xC8, 0x49, 0x1C, 0x4A, 0x54, 0x1C, 0xA5, 0xB3, 0xB7, 0x1F, 0xF8, 0xA0, 0xD1, 0x45, 0xE8, 0x10, 0xA8, 0x0E, 0xD1, 0xA0, 0xFA, 0x4D, 0x3F, 0xF8, 0x80, 0xCA, 0x6A, 0x96, 0x1A, 0x79, 0x0F, 0x49, 0xDF, 0x52, 0x76, 0xF4, 0xA5, 0x67, 0x28, 0xC0, 0x1C, 0xC4, 0x67, 0xEE, 0xC8, 0xD6, 0x21, 0xA1, 0xC8, 0x30, 0x01, 0x81, 0xCC, 0x74, 0xAE, 0xFC, 0x6C, 0x9E, 0x9D, 0x64, 0xB0, 0xCC, 0x48, 0x20, 0x12, 0xAC, 0x5D, 0x5E, 0x1B, 0x39, 0x79, 0x57, 0x2E, 0x73, 0xE7, 0x7C, 0xBC, 0x98, 0x88, 0x08, 0xA8, 0x9E, 0x5D, 0xA8, 0x82, 0x50, 0x15, 0xC5, 0xFE, 0xD9, 0x25, 0x02, 0xAA, 0x3C, 0x20, 0x49, 0x80, 0x40, 0xE1, 0x01, 0x21, 0x63, 0x41, 0x1E, 0x9C, 0xA4, 0x6B, 0x84, 0x09, 0x08, 0x86, 0xBF, 0x15, 0xB5, 0x14, 0x69, 0x95, 0xE5, 0x1A, 0x58, 0xA1, 0x86, 0x6F, 0x87, 0xD9, 0x8E, 0xC0, 0x23, 0xB2, 0x73, 0xD3, 0x9E, 0x15, 0x7B, 0xEE, 0xD8, 0xCD, 0xF0, 0x84, 0x42, 0x35, 0xF3, 0x09, 0x95, 0xD1, 0x92, 0xFB, 0x84, 0xBA, 0x74, 0x7C, 0x78, 0xA1, 0x52, 0x93, 0xE0, 0x22, 0x56, 0x8C, 0x06, 0xB1, 0x82, 0x8D, 0x5E, 0x19, 0x3D, 0x2E, 0x76, 0x3F, 0x56, 0x1C, 0x8B, 0x3D, 0x84, 0x51, 0x6D, 0x80, 0x16, 0x03, 0x43, 0x9B, 0x75, 0xE2, 0xC3, 0xB8, 0xE0, 0x6B, 0xDB, 0x5E, 0x54, 0x05, 0xAA, 0xC4, 0x55, 0xA5, 0x4B, 0xC2, 0x84, 0x74, 0xCE, 0x10, 0xA6, 0x19, 0x1B, 0xF6, 0x44, 0xB1, 0xE3, 0x3B, 0x22, 0xD4, 0x8E, 0x5B, 0xAB, 0x05, 0x6E, 0x93, 0xBC, 0x1A, 0x8C, 0xCE, 0x2B, 0x63, 0xA4, 0x7C, 0x52, 0xB4, 0xB2, 0x49, 0x11, 0x8E, 0xA3, 0xF7, 0xA4, 0x71, 0x46, 0x75, 0xD8, 0xAA, 0x29, 0x10, 0x10, 0x48, 0x65, 0x87, 0xB8, 0xDD, 0xE4, 0x5B, 0x8D, 0x22, 0x61, 0x07, 0x85, 0x6B, 0xA1, 0xF2, 0xBB, 0x09, 0xB7, 0xA5, 0x80, 0xDF, 0x14, 0x0C, 0xB9, 0x88, 0x22, 0x44, 0xFB, 0xEB, 0x00, 0x3A, 0x8E, 0x26, 0xD9, 0x61, 0xEF, 0x6D, 0x47, 0xFF, 0x34, 0x22, 0xE3, 0x74, 0xE1, 0xDA, 0x20, 0xC1, 0xE7, 0xC3, 0x77, 0x94, 0x16, 0x81, 0xBF, 0x08, 0x28, 0x90, 0xDE, 0xAC, 0x68, 0x41, 0x18, 0x0F, 0xDA, 0xD0, 0xF4, 0x73, 0x86, 0xA6, 0x67, 0x95, 0xFC, 0x6E, 0x83, 0x17, 0x61, 0x01, 0x3E, 0x99, 0x51, 0x82, 0xBC, 0xB4, 0x30, 0x74, 0xF1, 0xD5, 0x86, 0x41, 0xB6, 0x99, 0x57, 0x90, 0x80, 0x31, 0x14, 0xED, 0x89, 0x63, 0x24, 0x0E, 0x47, 0x02, 0x28, 0xA4, 0xF3, 0xA2, 0xEE, 0xCC, 0xAF, 0x73, 0x46, 0x70, 0xED, 0x4C, 0x46, 0x0C, 0x18, 0x66, 0x65, 0x64, 0x63, 0xCC, 0x18, 0x64, 0xCC, 0xB1, 0x17, 0x82, 0x30, 0x54, 0xA3, 0x2A, 0x51, 0xE1, 0x5E, 0x9B, 0x71, 0x74, 0xF2, 0x78, 0x34, 0x9F, 0x96, 0xBE, 0xC1, 0xE4, 0xF4, 0xA9, 0x60, 0x7D, 0xEB, 0x6B, 0xFA, 0x36, 0x27, 0x3C, 0x92, 0x0D, 0x00, 0x43, 0x1D, 0x9C, 0x90, 0x0E, 0xE2, 0xCC, 0x99, 0x1B, 0xA0, 0x02, 0x0C, 0xE5, 0xD2, 0xDE, 0x1B, 0xC5, 0x4B, 0xC3, 0x0B, 0xAD, 0x4C, 0x4C, 0x58, 0x8A, 0xC2, 0x7F, 0xD1, 0xD2, 0xE0, 0x3A, 0xC5, 0x7F, 0x86, 0xA0, 0x81, 0x52, 0xAA, 0x02, 0x39, 0x03, 0x30, 0xA4, 0xED, 0x37, 0x00, 0x19, 0x76, 0xE0, 0x81, 0x06, 0x90, 0x15, 0x4C, 0x4C, 0x80, 0x90, 0x33, 0x46, 0x0D, 0x8D, 0x04, 0x7A, 0x68, 0xAB, 0x92, 0xF6, 0xB7, 0x21, 0xB3, 0xF6, 0x34, 0x70, 0xA3, 0x0B, 0x9E, 0xB2, 0xC0, 0x74, 0x20, 0x86, 0x09, 0xC6, 0x15, 0x14, 0x4D, 0xE7, 0xD0, 0x76, 0xEE, 0xEF, 0xCB, 0x9F, 0xDF, 0x87, 0xA7, 0x6F, 0x48, 0xD2, 0x13, 0x13, 0xC2, 0x81, 0x67, 0x85, 0x30, 0xFE, 0x05, 0x2A, 0xC0, 0xA4, 0x60, 0x75, 0x9D, 0xA7, 0x14, 0xA4, 0x01, 0x33, 0xF6, 0x54, 0xB5, 0x81, 0xDD, 0x3F, 0xEA, 0xA9, 0x92, 0x81, 0x05, 0xA7, 0x95, 0x8B, 0xB8, 0xEB, 0xE0, 0x0A, 0x9E, 0xB6, 0x1D, 0x6E, 0x48, 0x31, 0x07, 0x72, 0xB8, 0x21, 0x9F, 0x80, 0x7D, 0x70, 0xD2, 0x67, 0xD4, 0x8C, 0xAF, 0xE2, 0xCD, 0xD5, 0x2A, 0xD6, 0x5C, 0x5D, 0x06, 0x05, 0xD4, 0x65, 0xB0, 0x98, 0xCB, 0xAB, 0x59, 0x17, 0x57, 0xDF, 0x05, 0xBD, 0xB9, 0xAD, 0xB5, 0x29, 0x5F, 0xCB, 0x40, 0xF9, 0xA7, 0x7C, 0xCF, 0x11, 0xCA, 0xBC, 0x02, 0x94, 0xE7, 0x11, 0xF0, 0x14, 0x62, 0xF5, 0x33, 0x88, 0x11, 0xCA, 0xB9, 0xF8, 0x29, 0x37, 0x00, 0x01, 0xA0, 0xBF, 0xC9, 0xC7, 0xE9, 0x26, 0x95, 0xD3, 0xC1, 0xA0, 0xD9, 0x18, 0x8F, 0x5F, 0xF3, 0x0D, 0x7D, 0xCD, 0x32, 0x81, 0x77, 0x7E, 0x50, 0xBF, 0x46, 0x3E, 0x9A, 0x9F, 0x3F, 0x5E, 0x94, 0x9A, 0xDF, 0xC4, 0xBF, 0xD6, 0x19, 0xE3, 0xF8, 0xB8, 0x21, 0x17, 0x68, 0x81, 0xFE, 0x50, 0xFF, 0x32, 0x69, 0xF5, 0x0F, 0x0A, 0xA5, 0xCF, 0x77, 0xA0, 0x23, 0x40, 0xAB, 0x70, 0xF8, 0xB3, 0x9B, 0x0F, 0x76, 0x91, 0xF0, 0x83, 0x42, 0x78, 0x91, 0x0E, 0x28, 0x56, 0x20, 0x5F, 0xCD, 0x9E, 0xD7, 0x6A, 0xE2, 0xAC, 0x37, 0x24, 0x8E, 0xD7, 0x43, 0xE0, 0xDC, 0x78, 0xE3, 0xF5, 0x0D, 0x37, 0x79, 0x76, 0xF0, 0x3A, 0x4F, 0x1B, 0xAF, 0x9F, 0x80, 0xAC, 0x04, 0xB3, 0x9C, 0x43, 0x1F, 0xDD, 0xE7, 0x62, 0xB1, 0x02, 0xB8, 0x10, 0x6D, 0xD5, 0xAB, 0x81, 0x06, 0x1A, 0x04, 0x85, 0x4E, 0x35, 0xBD, 0x3F, 0xB4, 0x6F, 0xEC, 0xF1, 0x1A, 0xBF, 0x99, 0xB8, 0x21, 0x63, 0x0A, 0x27, 0xFC, 0xD9, 0x9B, 0xDB, 0x44, 0x5C, 0x52, 0xCE, 0x9E, 0x16, 0xB9, 0x33, 0x7D, 0x7D, 0x67, 0xFD, 0xB0, 0xA1, 0x74, 0x7B, 0xFA, 0x56, 0x80, 0xE8, 0x4F, 0xDD, 0x2B, 0xA2, 0x6F, 0x2F, 0x8F, 0xE6, 0x34, 0xF5, 0x37, 0x04, 0x5E, 0x70, 0xC2, 0x54, 0x94, 0x19, 0x58, 0x0C, 0x3A, 0xCA, 0xD1, 0xBC, 0x86, 0x26, 0x07, 0x86, 0x10, 0x44, 0x27, 0xCB, 0x2B, 0x43, 0x99, 0xD7, 0x85, 0xED, 0x6E, 0xD4, 0x79, 0xF2, 0xC7, 0x67, 0x85, 0xB6, 0xCE, 0x69, 0x4B, 0x98, 0x1A, 0x1E, 0x80, 0xF1, 0x81, 0x08, 0x00, 0x84, 0xE3, 0xEC, 0x4A, 0x75, 0x25, 0x45, 0x01, 0x2B, 0xD5, 0xCC, 0x25, 0x80, 0x52, 0x7B, 0xFE, 0x3E, 0x48, 0xE3, 0x65, 0x65, 0x8F, 0x29, 0x37, 0x4D, 0xD0, 0x7F, 0xBF, 0x07, 0xA8, 0x95, 0x14, 0xC8, 0xFC, 0x8A, 0x73, 0xE3, 0x67, 0x36, 0xB6, 0xA3, 0x18, 0x42, 0xDA, 0xF2, 0x90, 0xA9, 0x21, 0xE5, 0x00, 0x67, 0x9C, 0xE5, 0x17, 0x0F, 0x9C, 0xE5, 0x79, 0x82, 0x58, 0xF1, 0x7A, 0x58, 0x2E, 0xE7, 0x0E, 0x0F, 0x1E, 0x96, 0xAB, 0xF9, 0xE1, 0x2C, 0x88, 0x58, 0xD0, 0x55, 0xC4, 0x80, 0x2B, 0x05, 0x99, 0x73, 0xA3, 0x76, 0xBF, 0x26, 0x05, 0x00, 0x1B, 0xA5, 0xB8, 0xFB, 0xAC, 0x0D, 0x36, 0x4A, 0x77, 0xD6, 0xEA, 0x28, 0xA0, 0xC7, 0x89, 0x95, 0x04, 0x65, 0x80, 0x92, 0xC8, 0x74, 0x03, 0xB5, 0xAF, 0xE7, 0x8C, 0x21, 0x6D, 0x72, 0x70, 0xAB, 0x35, 0x9D, 0x64, 0x5F, 0xF9, 0x22, 0x01, 0x68, 0x42, 0xA6, 0x36, 0xFC, 0x59, 0xF3, 0x80, 0xCC, 0x04, 0x00, 0x72, 0x74, 0x4D, 0x77, 0xA3, 0x6B, 0x2B, 0x66, 0xD8, 0x2D, 0x5D, 0xA7, 0xA1, 0x76, 0x05, 0x68, 0x11, 0x04, 0x0B, 0x8B, 0x18, 0xBA, 0x62, 0x58, 0xC2, 0x46, 0x29, 0x4A, 0x0F, 0x1C, 0xC8, 0xF1, 0x01, 0xB5, 0x58, 0x05, 0x5E, 0xA7, 0x9E, 0xA1, 0x61, 0x25, 0xEB, 0xAA, 0x4C, 0x99, 0x97, 0xAB, 0xE5, 0x6E, 0x56, 0xCB, 0x45, 0x78, 0x32, 0xEE, 0x3D, 0x99, 0xB6, 0x98, 0xAC, 0x66, 0x2E, 0x59, 0x8D, 0xFC, 0xC9, 0xDB, 0x73, 0xE2, 0xE2, 0x1C, 0xAA, 0xAB, 0xE7, 0x4C, 0x5D, 0x6D, 0xB4, 0x21, 0x6A, 0x3A, 0x34, 0x51, 0xF3, 0xCD, 0xA4, 0xCA, 0x0C, 0xD3, 0x49, 0x84, 0xA6, 0x3E, 0xE4, 0x4C, 0x3D, 0xC8, 0x0F, 0x88, 0xD1, 0x63, 0xCB, 0x29, 0x45, 0x00, 0x14, 0x6C, 0x01, 0x59, 0x57, 0xC6, 0x87, 0x50, 0x02, 0xEB, 0xEA, 0x34, 0xEB, 0x6A, 0x6C, 0xED, 0xF3, 0x37, 0x45, 0xD5, 0xE9, 0xE8, 0x88, 0x2A, 0x93, 0x8F, 0x4A, 0x82, 0x33, 0x26, 0x6E, 0x33, 0x37, 0xA8, 0x71, 0x06, 0xAB, 0xB7, 0x29, 0x8E, 0x3B, 0xA3, 0x24, 0x9A, 0x9F, 0x06, 0x3D, 0xA2, 0x05, 0xAF, 0xBB, 0xD4, 0xE8, 0xC5, 0xEB, 0xF4, 0xB6, 0xA8, 0x4F, 0x52, 0xA1, 0x19, 0x5B, 0xED, 0xBE, 0x65, 0x87, 0x02, 0x18, 0x23, 0x38, 0x3D, 0xBD, 0x86, 0x45, 0x44, 0xC0, 0x47, 0x48, 0x8C, 0x37, 0x14, 0xE0, 0x50, 0x83, 0x30, 0x08, 0xE8, 0xEF, 0x69, 0x5B, 0xC2, 0x73, 0xE8, 0x59, 0xB3, 0x0B, 0x33, 0x5A, 0x47, 0x60, 0x34, 0xB0, 0xF2, 0xB0, 0x62, 0x2E, 0xC7, 0xA9, 0x8A, 0xB9, 0xD3, 0x0D, 0x1B, 0xCC, 0xD5, 0x7C, 0x30, 0x54, 0x83, 0x6A, 0x6D, 0xD5, 0x88, 0x0E, 0x1C, 0x60, 0x80, 0xD9, 0x90, 0x5C, 0x4F, 0xA2, 0xA3, 0x12, 0xF4, 0xC4, 0xF2, 0xB4, 0xB0, 0xC1, 0x99, 0xA2, 0x8D, 0x8F, 0xA7, 0xC0, 0x86, 0x46, 0x40, 0x54, 0xCC, 0xB5, 0xDC, 0x55, 0xF9, 0xA0, 0x32, 0xBC, 0x71, 0x0E, 0xD3, 0xBD, 0xFB, 0x43, 0x44, 0x71, 0x09, 0xBF, 0xC2, 0x21, 0x11, 0xA5, 0x12, 0xFC, 0x11, 0xB3, 0xD0, 0xD9, 0xB7, 0x97, 0xBF, 0x37, 0xA9, 0xA6, 0xCE, 0xFB, 0xBF, 0x9C, 0x3D, 0x44, 0x2C, 0x81, 0x9E, 0xD9, 0x40, 0x9F, 0x15, 0xAD, 0xCF, 0xEB, 0x03, 0x2A, 0x00, 0xA0, 0x3E, 0xB0, 0x87, 0x01, 0x11, 0x66, 0x4E, 0x94, 0x42, 0x98, 0x1B, 0x3B, 0xBB, 0xF3, 0x01, 0x0F, 0x4D, 0x6B, 0x03, 0x27, 0x62, 0x38, 0x69, 0x8F, 0x42, 0x46, 0x75, 0x4A, 0x8A, 0x46, 0x8D, 0x14, 0xDD, 0x02, 0xBA, 0x85, 0xEA, 0x2A, 0x79, 0xAC, 0x2B, 0x52, 0x95, 0x77, 0xC7, 0x8B, 0xB3, 0xBD, 0x47, 0x08, 0xD2, 0x7B, 0x26, 0xA8, 0x00, 0x69, 0x2E, 0x6B, 0x48, 0x7F, 0x86, 0xEC, 0xD9, 0x31, 0x23, 0x9E, 0x50, 0xF5, 0x42, 0x55, 0x44, 0x20, 0x47, 0x0B, 0x54, 0x03, 0xA7, 0x08, 0xD1, 0x55, 0x8F, 0x8D, 0xC8, 0x28, 0xD5, 0x17, 0xB3, 0x95, 0x79, 0x9D, 0xF0, 0xBF, 0x2A, 0x8E, 0xA2, 0x2C, 0x69, 0xAB, 0x6C, 0x79, 0x9F, 0x85, 0x2A, 0xDE, 0x22, 0x5B, 0xF8, 0x72, 0x5A, 0xFE, 0xA8, 0x28, 0x7E, 0x77, 0xAB, 0x8A, 0xD3, 0xB9, 0xFF, 0x44, 0x23, 0x42, 0x58, 0x1A, 0xFA, 0x3F, 0x15, 0x2F, 0x82, 0x03, 0x16, 0x5E, 0x28, 0xB2, 0x94, 0x61, 0x03, 0x16, 0xDE, 0xD6, 0xF2, 0x63, 0xD1, 0xB6, 0x56, 0x9C, 0x1B, 0x84, 0x6D, 0x29, 0xC8, 0x82, 0xB3, 0xAD, 0x71, 0xA0, 0x4D, 0x66, 0x6C, 0xDD, 0x24, 0x78, 0xCF, 0xD0, 0x2C, 0x0D, 0x6C, 0x5D, 0x86, 0xC5, 0xE5, 0x76, 0x1B, 0x4F, 0x97, 0xD5, 0x71, 0xE1, 0xFB, 0x80, 0xCB, 0xC0, 0x33, 0x17, 0xB3, 0x31, 0x5C, 0xE8, 0xB1, 0x28, 0xFE, 0xC9, 0x28, 0x42, 0xC2, 0xA2, 0xC4, 0x72, 0x0A, 0xD0, 0x06, 0xB5, 0x53, 0x07, 0x09, 0x68, 0x02, 0xF8, 0x58, 0x16, 0x5D, 0x03, 0xD0, 0x0F, 0xF2, 0xF1, 0x14, 0x5C, 0x68, 0x0D, 0x02, 0xD5, 0x51, 0xE1, 0x10, 0x3D, 0x08, 0x0D, 0x84, 0xF5, 0xEF, 0x01, 0x34, 0x37, 0xD6, 0x1F, 0xA1, 0x4D, 0x11, 0x8A, 0x2D, 0x3F, 0x62, 0x48, 0x93, 0x16, 0x43, 0xD4, 0xD8, 0x1D, 0x0B, 0x0F, 0x1F, 0x8F, 0xF5, 0xF0, 0x7A, 0xAC, 0xE9, 0xB5, 0x3A, 0xD3, 0xBC, 0x76, 0x36, 0xE0, 0xE5, 0x16, 0x65, 0x65, 0x3B, 0x61, 0x68, 0x37, 0x1F, 0x44, 0x13, 0x14, 0x05, 0x53, 0xE8, 0xE7, 0xE3, 0x75, 0xDF, 0x7A, 0x11, 0x67, 0x93, 0x6C, 0x70, 0x62, 0x87, 0x98, 0xD6, 0x66, 0xF0, 0xE9, 0xB5, 0x71, 0xBA, 0xE3, 0x74, 0x72, 0x07, 0x3A, 0x23, 0x9C, 0xA6, 0x2B, 0xC2, 0x69, 0xF8, 0x74, 0x13, 0x09, 0xA7, 0x9D, 0xF0, 0xC0, 0x31, 0x02, 0xFD, 0x00, 0xFA, 0x65, 0x4E, 0x3B, 0x5B, 0x54, 0x38, 0xB2, 0xF1, 0x15, 0xB7, 0x06, 0xC2, 0x11, 0x23, 0x41, 0x39, 0x33, 0x66, 0xDB, 0x32, 0xC9, 0xCA, 0xC6, 0x50, 0x2C, 0x76, 0xC9, 0x17, 0x0B, 0xE1, 0xD4, 0x55, 0xCB, 0x5A, 0x9D, 0x5A, 0x44, 0xA7, 0xF9, 0x6B, 0xC6, 0x34, 0x5D, 0x2C, 0x58, 0x13, 0x55, 0xB1, 0xDE, 0x3D, 0x0A, 0x1E, 0x51, 0xF1, 0xD3, 0x88, 0x8A, 0xDB, 0x0E, 0x62, 0x16, 0xA0, 0xEB, 0xAD, 0x1C, 0xB6, 0xA2, 0x54, 0x6C, 0xCC, 0xA1, 0xAB, 0xC8, 0x4E, 0x4D, 0xD7, 0x74, 0x69, 0x80, 0x84, 0x38, 0x18, 0xBE, 0x01, 0x8B, 0x65, 0xD1, 0x58, 0x4E, 0x39, 0x14, 0x41, 0x25, 0x22, 0x12, 0x23, 0x73, 0x71, 0xAB, 0x6F, 0xC2, 0x99, 0x0F, 0x93, 0x78, 0x85, 0xB4, 0x16, 0x72, 0x84, 0x78, 0x8D, 0x31, 0x6F, 0xA8, 0x55, 0xC4, 0x23, 0x72, 0x6B, 0xCE, 0x74, 0x00, 0x5C, 0x90, 0xA2, 0x4A, 0x6E, 0xF4, 0xB6, 0x28, 0xBA, 0xEA, 0x4C, 0xE7, 0x8E, 0xC3, 0xB7, 0x35, 0xCD, 0xFA, 0xDD, 0x6D, 0x9D, 0x3C, 0x97, 0x4B, 0x81, 0xBB, 0xED, 0xA4, 0xD8, 0xE2, 0x76, 0xC2, 0xCE, 0x46, 0xEA, 0x50, 0x07, 0x25, 0x38, 0x77, 0x82, 0x4F, 0xFA, 0xB0, 0x71, 0x8C, 0xE2, 0x84, 0x4F, 0xB6, 0xBD, 0xA3, 0xD7, 0xCD, 0x21, 0xD6, 0xC7, 0x16, 0x34, 0xF8, 0x64, 0x7A, 0x29, 0xE7, 0x8E, 0xF7, 0x29, 0xD8, 0xEC, 0x88, 0x9B, 0x0A, 0xF4, 0x8D, 0x22, 0x24, 0x57, 0x62, 0x49, 0xEA, 0xD9, 0xE9, 0x40, 0x9E, 0x87, 0x86, 0xFC, 0x2D, 0xA5, 0xE3, 0xC3, 0xCC, 0x0E, 0x34, 0x68, 0x59, 0x92, 0x52, 0x8E, 0x62, 0x83, 0x43, 0xDC, 0xCB, 0xDB, 0xC2, 0x10, 0x54, 0x48, 0x2E, 0x09, 0x05, 0xA7, 0x08, 0x83, 0x69, 0xDE, 0x54, 0x21, 0xD4, 0xF1, 0x08, 0x64, 0x91, 0x5D, 0x4B, 0x54, 0xE8, 0x8B, 0xD4, 0xC5, 0x67, 0xE1, 0x40, 0x79, 0xFE, 0xF3, 0x83, 0x19, 0xAA, 0xF0, 0x6C, 0x80, 0x97, 0x2E, 0x5D, 0x08, 0xF0, 0xC2, 0x77, 0x47, 0x6A, 0x3E, 0x20, 0x94, 0x6D, 0xBA, 0x54, 0xDD, 0x66, 0x4B, 0x15, 0xD1, 0xEA, 0x64, 0x86, 0x2A, 0xE2, 0x53, 0xB2, 0x10, 0x39, 0x51, 0x34, 0x07, 0x0C, 0x67, 0xAA, 0x36, 0x06, 0xD7, 0xBE, 0xA9, 0x33, 0xBD, 0x38, 0x98, 0x3C, 0x3F, 0x60, 0x12, 0xC6, 0x3A, 0x14, 0x4B, 0x4E, 0x4A, 0x6E, 0xF5, 0xB6, 0xE7, 0x3A, 0x43, 0x8F, 0xBE, 0x81, 0xCF, 0x0E, 0x4D, 0x25, 0x87, 0x16, 0x14, 0x04, 0x44, 0x95, 0x5C, 0x72, 0x9C, 0x0E, 0x0E, 0x36, 0x10, 0x09, 0x51, 0x66, 0x92, 0x5E, 0xAA, 0x0C, 0x52, 0xBC, 0x85, 0xF0, 0xF5, 0x10, 0x06, 0x3F, 0xD0, 0x29, 0x8C, 0x62, 0x14, 0x2A, 0x1A, 0xD5, 0xE7, 0x33, 0xCA, 0x4F, 0xDE, 0xF3, 0xE7, 0x3F, 0x8B, 0xA6, 0x31, 0xC1, 0x67, 0x02, 0x59, 0x92, 0xD0, 0xC5, 0xF7, 0x3B, 0xF4, 0x4E, 0xC5, 0x89, 0x20, 0x20, 0x88, 0x62, 0xCA, 0x41, 0xAE, 0x64, 0x8E, 0xF5, 0xA1, 0x39, 0x47, 0x79, 0xFC, 0x7A, 0x73, 0x09, 0xC7, 0x30, 0x4D, 0x3E, 0x50, 0x82, 0x4E, 0x97, 0x34, 0x1A, 0xAC, 0xB7, 0x3E, 0x8C, 0xCC, 0x48, 0x5B, 0xEB, 0x67, 0x92, 0x99, 0x00, 0x07, 0xD0, 0x9D, 0x80, 0x21, 0x1E, 0xEE, 0xED, 0x0D, 0x73, 0x7E, 0xCA, 0xC9, 0xE2, 0x97, 0x5C, 0xCF, 0x17, 0x47, 0x8F, 0xEC, 0x27, 0x89, 0xD0, 0x8A, 0x05, 0x4E, 0xAB, 0x18, 0xA3, 0x78, 0x87, 0x69, 0xA3, 0x80, 0x26, 0xB5, 0xEB, 0x7C, 0x76, 0xAC, 0x6D, 0xDB, 0xDA, 0xD0, 0x2C, 0x55, 0x4E, 0x2F, 0x60, 0x42, 0x16, 0x5A, 0x30, 0xF1, 0x38, 0xEF, 0xE9, 0xEF, 0xA7, 0x81, 0x0B, 0x32, 0x54, 0xB8, 0x11, 0x86, 0xE0, 0x5A, 0xF2, 0xBA, 0xBD, 0xB9, 0xB5, 0x2D, 0x30, 0x2E, 0xF8, 0x4F, 0x74, 0x12, 0xD1, 0x1C, 0x5E, 0xB5, 0xB3, 0x6A, 0xE7, 0x78, 0x44, 0xDB, 0xEE, 0x33, 0xFF, 0xD0, 0x50, 0xA7, 0x8C, 0x17, 0xBF, 0x00, 0xE5, 0x8D, 0x0A, 0x05, 0xCA, 0x14, 0xA9, 0x9F, 0x52, 0x96, 0x22, 0xF5, 0x50, 0x9A, 0x5B, 0x50, 0x3B, 0x1E, 0x5C, 0xB4, 0x92, 0x42, 0xA0, 0xDE, 0xA3, 0x00, 0xEA, 0xB6, 0x69, 0xE3, 0x4B, 0xD6, 0xC8, 0xAF, 0x18, 0x98, 0x3A, 0xB9, 0x63, 0x0A, 0x0D, 0x31, 0x49, 0xC8, 0x12, 0x14, 0x1E, 0x40, 0x8F, 0x29, 0x66, 0x0A, 0xC7, 0xBA, 0xAD, 0x2A, 0x8A, 0x87, 0x54, 0x37, 0x43, 0xD3, 0x70, 0x4E, 0xD6, 0xD1, 0x73, 0xB0, 0xEE, 0xD0, 0x87, 0x9C, 0xE3, 0x3E, 0xE2, 0x5C, 0x16, 0xEB, 0x68, 0xF6, 0x8A, 0x89, 0xB4, 0x00, 0xAA, 0xF4, 0x95, 0xA0, 0x81, 0xC8, 0xF2, 0x8A, 0xE0, 0xF7, 0xCB, 0x1A, 0x6F, 0x9F, 0xBA, 0x3D, 0x22, 0x7B, 0xDF, 0x06, 0xE5, 0xED, 0x90, 0x04, 0x61, 0xB4, 0x3D, 0x11, 0xC2, 0x35, 0xC4, 0x3A, 0x1D, 0xB1, 0xDC, 0x69, 0x0F, 0x5E, 0x9E, 0xE5, 0x78, 0x44, 0x8C, 0x0D, 0xB1, 0x7C, 0xCC, 0x4E, 0x55, 0x59, 0x09, 0x63, 0xC0, 0x53, 0x21, 0x88, 0x14, 0x41, 0xE4, 0xC5, 0x40, 0x38, 0x20, 0x71, 0x4D, 0xD1, 0x95, 0x0C, 0xCD, 0x6A, 0xD3, 0x67, 0x89, 0x10, 0xD5, 0x08, 0xD2, 0x08, 0xAE, 0xB5, 0x2E, 0x11, 0xBE, 0xE5, 0x4A, 0x11, 0x8A, 0x11, 0x25, 0xD0, 0xF3, 0x00, 0xC6, 0x4F, 0x00, 0x8D, 0x31, 0x97, 0xC5, 0xE4, 0x42, 0xAE, 0x0C, 0x24, 0x11, 0xB1, 0xC3, 0xC7, 0xB3, 0x73, 0x3B, 0xC6, 0x62, 0x65, 0xCE, 0x0A, 0x90, 0x3E, 0x70, 0xA8, 0xA0, 0x41, 0xAA, 0x10, 0xA6, 0x98, 0x01, 0x79, 0x76, 0x06, 0xA9, 0xE4, 0xCD, 0x04, 0xE5, 0x6F, 0x57, 0xD2, 0xB4, 0xA7, 0xD1, 0x1A, 0x4F, 0x6C, 0x9E, 0xA2, 0xFC, 0x3A, 0xA8, 0xAB, 0xD9, 0x24, 0xDB, 0x3C, 0x2A, 0xED, 0xCC, 0x9C, 0x66, 0x02, 0x94, 0x11, 0xAC, 0x53, 0x91, 0xE7, 0xF5, 0xD0, 0x74, 0xDE, 0x0E, 0x68, 0x43, 0x26, 0xFE, 0x33, 0x9A, 0x28, 0x20, 0x38, 0x40, 0xD0, 0xCC, 0x1D, 0x03, 0x40, 0x45, 0x17, 0xA0, 0x98, 0x17, 0xCA, 0x3A, 0x6C, 0x55, 0x27, 0x44, 0xE1, 0xCD, 0xFB, 0x06, 0x2F, 0x2A, 0x98, 0x70, 0xD9, 0x3A, 0x42, 0x46, 0x93, 0xA9, 0xF5, 0x95, 0xBC, 0xFA, 0xDA, 0xCE, 0xEA, 0x6B, 0xD4, 0x8E, 0x4D, 0xB1, 0x84, 0xFC, 0x2E, 0x38, 0xA2, 0x79, 0xEA, 0xAB, 0xCD, 0xF0, 0xF5, 0x44, 0x03, 0xEC, 0x9F, 0xBB, 0xD4, 0x85, 0x3F, 0x03, 0x55, 0xA1, 0x10, 0x0D, 0x0C, 0x91, 0x89, 0x93, 0x48, 0x26, 0x88, 0xD8, 0x21, 0x1F, 0xA8, 0xBD, 0xAF, 0xE1, 0x67, 0xCE, 0x52, 0xC8, 0x4E, 0x5A, 0x95, 0x14, 0x13, 0x48, 0x05, 0x79, 0x10, 0x79, 0xCD, 0x63, 0xC8, 0xEB, 0x3C, 0x3F, 0x5E, 0xE7, 0x51, 0xA2, 0x8B, 0x4D, 0x97, 0x25, 0xA8, 0x80, 0x44, 0x56, 0x0D, 0x86, 0x33, 0xDD, 0x81, 0x01, 0xE6, 0x50, 0x45, 0x19, 0xC6, 0x09, 0xD3, 0x15, 0xB5, 0x5B, 0x23, 0x82, 0x5B, 0x9F, 0x38, 0x14, 0x8B, 0xFE, 0x58, 0xEC, 0xAC, 0xD6, 0xEC, 0x06, 0x7E, 0x07, 0x02, 0x70, 0xF6, 0x8A, 0x1A, 0x19, 0x9E, 0xBB, 0x46, 0xB0, 0xB3, 0xD2, 0x5A, 0xEB, 0xAA, 0x06, 0xB2, 0x69, 0xB5, 0x1E, 0x50, 0x0C, 0x07, 0xA3, 0x1B, 0x38, 0x18, 0x01, 0x9F, 0xF4, 0x31, 0xC2, 0xD0, 0xF7, 0x07, 0x48, 0x07, 0x07, 0x9B, 0xCC, 0xE9, 0xEC, 0xBC, 0xE4, 0x67, 0x71, 0x90, 0xA6, 0x82, 0xB3, 0xD7, 0x8C, 0xD2, 0x52, 0xA5, 0xEB, 0xEC, 0x83, 0x6C, 0x77, 0x2B, 0xE8, 0x65, 0xD6, 0xDF, 0x4D, 0x12, 0x9D, 0x9D, 0x2C, 0x9E, 0x2D, 0xE6, 0xEC, 0x5D, 0x38, 0xBB, 0x4D, 0x85, 0x5C, 0x50, 0x20, 0x74, 0xF9, 0x68, 0xE0, 0x04, 0x5E, 0x41, 0xDD, 0x8A, 0xB3, 0xEB, 0x74, 0x27, 0x22, 0x02, 0x83, 0xA8, 0xAA, 0xFE, 0x56, 0xF0, 0x98, 0xF0, 0x82, 0x4C, 0x9F, 0x11, 0xCF, 0xE3, 0x11, 0x57, 0x6D, 0x3C, 0x9E, 0x16, 0x68, 0xDE, 0x33, 0x28, 0x3E, 0x53, 0xF5, 0xBB, 0xC2, 0x55, 0x41, 0x5B, 0x70, 0x88, 0xBE, 0x3E, 0x7D, 0xCE, 0xE5, 0x7E, 0xF4, 0xB2, 0x9A, 0xD6, 0xB3, 0x01, 0x89, 0x34, 0xBA, 0x5B, 0x35, 0x1D, 0x1F, 0xAA, 0xAF, 0x2E, 0xD1, 0x48, 0x7E, 0x23, 0x69, 0xE2, 0x44, 0xEF, 0x08, 0x3D, 0x48, 0xF9, 0x21, 0xC6, 0xF0, 0xC3, 0xB0, 0xB2, 0x1E, 0x9D, 0x28, 0x9E, 0xB1, 0x78, 0x91, 0x6A, 0x7C, 0xBD, 0x20, 0x5F, 0x69, 0x9B, 0x20, 0x61, 0xDF, 0xDB, 0x25, 0xDA, 0x22, 0x99, 0x16, 0x86, 0xA5, 0x10, 0x9C, 0x20, 0x46, 0x84, 0xE1, 0x0B, 0xD3, 0x27, 0x0C, 0x10, 0xDB, 0x82, 0xEF, 0x2B, 0x05, 0x5F, 0x32, 0x8D, 0xEC, 0xD9, 0x18, 0x43, 0x30, 0x21, 0x8F, 0x9E, 0xB9, 0x35, 0x04, 0xD3, 0x0C, 0x3A, 0x16, 0x28, 0x86, 0x91, 0x41, 0x07, 0x0B, 0x22, 0x34, 0xE0, 0x80, 0x83, 0x41, 0x42, 0xCA, 0x04, 0x41, 0xDB, 0x04, 0x65, 0x66, 0x10, 0x31, 0x66, 0x4C, 0xA2, 0x63, 0xE2, 0x80, 0x08, 0xA6, 0xE0, 0x6C, 0xC2, 0xB2, 0x9F, 0xA1, 0x48, 0x25, 0xA0, 0xAE, 0xE9, 0x63, 0x01, 0xA1, 0x0C, 0xBA, 0x2E, 0xF4, 0xE9, 0x62, 0xC5, 0x04, 0xAE, 0x32, 0xC7, 0xC8, 0xA2, 0xFC, 0xAC, 0xDE, 0xE7, 0xCF, 0xA4, 0xA2, 0x46, 0xCE, 0x95, 0x95, 0x13, 0x46, 0xD3, 0x43, 0xCC, 0x9A, 0xF0, 0x54, 0x54, 0x8D, 0xE5, 0xAA, 0xEA, 0xF6, 0x7E, 0xC6, 0x4F, 0x1D, 0x00, 0x66, 0x7F, 0x45, 0x47, 0x57, 0x29, 0xFE, 0x0E, 0xAB, 0xA0, 0xAB, 0x5B, 0xF9, 0x39, 0x75, 0xA6, 0x6B, 0x12, 0x7F, 0x74, 0x47, 0x36, 0x5A, 0xEB, 0x6D, 0x41, 0x0F, 0x93, 0x26, 0xA0, 0xFC, 0x9B, 0x90, 0xA0, 0xC4, 0x02, 0xF3, 0x1F, 0x20, 0xFA, 0x60, 0xED, 0x34, 0x52, 0x7F, 0x02, 0x6B, 0x21, 0x84, 0x79, 0x31, 0xCC, 0xC1, 0x08, 0xB9, 0x04, 0x6B, 0x93, 0xE3, 0x5B, 0x50, 0x62, 0x74, 0x0B, 0x12, 0xAC, 0x67, 0xE4, 0xE8, 0xA7, 0xF3, 0xA8, 0x2E, 0x68, 0x74, 0x5D, 0x5B, 0x34, 0x71, 0x8C, 0x8C, 0xD2, 0x63, 0x61, 0xC2, 0x9E, 0x3C, 0x39, 0xAE, 0x44, 0x6B, 0xA0, 0x0D, 0xAD, 0xCE, 0x2A, 0x4A, 0x3B, 0xA7, 0x28, 0xE9, 0xFD, 0x2C, 0x6D, 0x63, 0xB8, 0xB4, 0xD3, 0xFA, 0xDD, 0xCA, 0x25, 0x6E, 0x46, 0xEA, 0x36, 0xFA, 0xCF, 0x80, 0xFA, 0x1B, 0x45, 0x61, 0xE9, 0x93, 0x07, 0xE2, 0x54, 0x4C, 0x80, 0x05, 0xC3, 0x88, 0xD4, 0xB2, 0x58, 0x36, 0x83, 0x8C, 0x06, 0xA3, 0x1A, 0x8C, 0xA2, 0x0E, 0x50, 0xB3, 0xB3, 0x32, 0xD3, 0x29, 0x06, 0x50, 0x4B, 0x9F, 0x8F, 0x30, 0xBA, 0x2E, 0x29, 0x1B, 0xB8, 0x78, 0xBD, 0x49, 0x0F, 0x8C, 0x10, 0xE3, 0xE8, 0xDA, 0xB6, 0x35, 0x92, 0x0A, 0xE1, 0x50, 0x35, 0xF5, 0x93, 0x76, 0x2E, 0xB3, 0xF5, 0x62, 0x31, 0xA7, 0x3D, 0xF1, 0xCC, 0x30, 0x3B, 0xFE, 0x4A, 0x70, 0x86, 0x99, 0xE3, 0xE0, 0x90, 0xA1, 0xCC, 0x71, 0x8E, 0x1E, 0x0C, 0x95, 0x99, 0xE7, 0x0F, 0x69, 0x6B, 0x1C, 0x19, 0x3F, 0xF3, 0xF9, 0x86, 0x83, 0xA5, 0x25, 0xBA, 0x03, 0x07, 0x4B, 0x7E, 0x5E, 0x01, 0xD8, 0xA3, 0x00, 0x8B, 0x38, 0xAA, 0x8E, 0x17, 0x6A, 0xFE, 0xDE, 0x88, 0x3D, 0xC5, 0x94, 0xCA, 0x67, 0x93, 0xBE, 0xAE, 0x94, 0x26, 0x8C, 0x16, 0xAA, 0x20, 0x39, 0xD3, 0xAF, 0x67, 0x48, 0x72, 0x84, 0x12, 0xAC, 0x94, 0xB0, 0x1D, 0x2B, 0x32, 0x10, 0x79, 0x76, 0xD0, 0x9E, 0x64, 0x01, 0x72, 0x04, 0x21, 0x5B, 0x44, 0x0A, 0x90, 0xAB, 0x31, 0x55, 0x60, 0x09, 0xE3, 0xB3, 0x2B, 0xBB, 0x7A, 0x6F, 0xAC, 0x87, 0xD8, 0xD5, 0x3D, 0xBE, 0xAE, 0xCE, 0xA2, 0xC4, 0x83, 0xCA, 0x50, 0x8E, 0xC6, 0x50, 0xD2, 0xF1, 0x41, 0x39, 0x9D, 0x39, 0x2D, 0x94, 0xBB, 0x81, 0x29, 0x4F, 0xCE, 0x51, 0x9E, 0xDC, 0x53, 0xCE, 0x94, 0x5B, 0xD1, 0x08, 0xA1, 0x46, 0xE2, 0x95, 0x0C, 0x5F, 0xF8, 0x5D, 0xA2, 0x3E, 0xBB, 0x82, 0x7A, 0x79, 0x64, 0x13, 0x51, 0x32, 0xFC, 0x71, 0x15, 0x95, 0x99, 0x30, 0x81, 0xD4, 0x3C, 0xC7, 0x0B, 0xB0, 0xBB, 0xA7, 0x25, 0x02, 0xE4, 0xCF, 0x28, 0x0C, 0x70, 0xBC, 0x45, 0xEE, 0xD3, 0xB0, 0x42, 0x09, 0x37, 0xF6, 0x19, 0xBD, 0xEB, 0xA9, 0x0A, 0x0C, 0xAA, 0x4D, 0x84, 0xFE, 0xB4, 0xE8, 0xD0, 0xB4, 0xD1, 0x4D, 0x53, 0xD0, 0x09, 0x74, 0x0A, 0x9D, 0x75, 0x47, 0x48, 0xE4, 0x8D, 0xF1, 0x26, 0x3C, 0x47, 0xD6, 0x77, 0x24, 0x95, 0xA2, 0xF7, 0x0A, 0x50, 0x2E, 0x91, 0x39, 0x86, 0xE4, 0x60, 0x3E, 0x04, 0x87, 0xCA, 0x05, 0x4B, 0x07, 0xCC, 0xEF, 0x0D, 0xCB, 0xE5, 0xD8, 0xB0, 0xFC, 0xF2, 0xB0, 0xFC, 0xBE, 0xE1, 0x53, 0x73, 0xFA, 0xC4, 0xD2, 0x66, 0xAD, 0xD3, 0xE1, 0x14, 0xC4, 0x01, 0x03, 0xFA, 0x08, 0x1D, 0xB0, 0xB5, 0x6D, 0xEF, 0x40, 0x94, 0x38, 0x14, 0x32, 0x43, 0xF9, 0x0C, 0x47, 0x5F, 0x02, 0xE1, 0x0D, 0xA5, 0x3D, 0xAE, 0xA0, 0x6A, 0x16, 0x10, 0xEB, 0x40, 0xEE, 0x86, 0x9C, 0xB1, 0x4C, 0xD6, 0xC6, 0x30, 0x59, 0xC7, 0xA9, 0x29, 0x35, 0xB7, 0xA9, 0xD1, 0xF7, 0x9F, 0xD6, 0x6A, 0x40, 0x5C, 0xA2, 0x66, 0x6E, 0x45, 0xE9, 0x5D, 0x9D, 0xEC, 0x4B, 0x72, 0x62, 0x5E, 0xF0, 0x04, 0x04, 0xAA, 0xE0, 0x07, 0xD0, 0x6D, 0x92, 0x46, 0xC6, 0x59, 0x58, 0x1A, 0xA5, 0xF7, 0xAB, 0xAE, 0x2D, 0x10, 0x8E, 0xBD, 0x2D, 0xD2, 0x2A, 0x1A, 0x9C, 0x9E, 0xF6, 0xAE, 0x2A, 0xE9, 0xD2, 0xBC, 0x87, 0x49, 0xF2, 0x52, 0xA8, 0x78, 0x40, 0x55, 0xE3, 0xC8, 0xAF, 0x88, 0x3A, 0x7B, 0x71, 0xB7, 0xEB, 0x0E, 0x71, 0xB7, 0x4C, 0xFF, 0x1C, 0x13, 0x77, 0x22, 0xB5, 0xC1, 0x8E, 0x1D, 0x3F, 0x3A, 0x74, 0x90, 0xF0, 0x9A, 0xA1, 0x7C, 0x83, 0x04, 0x14, 0x6A, 0x44, 0x7B, 0xA6, 0x08, 0x2C, 0x35, 0xA7, 0xA6, 0x5C, 0xD4, 0x7B, 0x71, 0x51, 0x97, 0xD5, 0x41, 0x5E, 0x06, 0x08, 0xB7, 0xB2, 0xA6, 0xDD, 0x96, 0xF7, 0x45, 0xD3, 0x97, 0xC5, 0x60, 0x52, 0x33, 0xBF, 0xA4, 0xAE, 0x12, 0x1A, 0x73, 0xF4, 0xDD, 0x3E, 0x8B, 0x94, 0x84, 0x2F, 0xAD, 0x07, 0xA8, 0xA0, 0x45, 0x10, 0x40, 0x0A, 0xC9, 0x31, 0xD0, 0x92, 0xBC, 0x51, 0xD4, 0xFB, 0x63, 0xA6, 0x8A, 0x2A, 0x5C, 0x88, 0xD6, 0x5C, 0x67, 0x32, 0x35, 0x00, 0xF3, 0x85, 0x60, 0x99, 0xF8, 0x62, 0xD2, 0x00, 0xC7, 0x1B, 0x17, 0x00, 0x38, 0x4E, 0x08, 0x37, 0x32, 0x40, 0x67, 0x52, 0xD5, 0x78, 0x49, 0x58, 0x50, 0x8A, 0x24, 0x03, 0x48, 0x42, 0xF7, 0xA5, 0xEA, 0x24, 0x0B, 0x55, 0x01, 0x99, 0x30, 0xA8, 0x84, 0x8A, 0x51, 0xEA, 0xF5, 0x2C, 0x50, 0x5A, 0x54, 0x44, 0x00, 0x00, 0x14, 0x09, 0x63, 0xF6, 0x61, 0x18, 0x4D, 0x41, 0x0C, 0x83, 0x81, 0x34, 0x4C, 0x24, 0xDF, 0x01, 0x64, 0x45, 0x0B, 0x02, 0xC2, 0x86, 0x02, 0x83, 0x11, 0x60, 0x34, 0x40, 0x1C, 0x14, 0x06, 0x08, 0x60, 0x32, 0x10, 0xC9, 0x52, 0x01, 0x03, 0x31, 0x2C, 0x60, 0x00, 0x50, 0x08, 0x38, 0xC3, 0x98, 0xAA, 0xA2, 0x01, 0xB3, 0x94, 0x5C, 0x47, 0x1B, 0x3D, 0x1F, 0x18, 0x64, 0x4C, 0x44, 0x22, 0x31, 0x32, 0x44, 0x61, 0x54, 0x1F, 0xC8, 0x56, 0x86, 0x98, 0xBC, 0xC5, 0x53, 0x92, 0xF7, 0x36, 0x10, 0xE0, 0x2E, 0x91, 0x48, 0x77, 0xE3, 0x0C, 0x7F, 0x8A, 0x7E, 0xE3, 0xE7, 0x69, 0x94, 0xAC, 0x23, 0xB0, 0x3C, 0x45, 0x7A, 0x5A, 0x22, 0xC3, 0xA3, 0x5B, 0xC3, 0x93, 0x3E, 0xEA, 0x3B, 0xEC, 0xB8, 0xD3, 0xEF, 0xD5, 0x8D, 0x19, 0xE6, 0x2E, 0x52, 0x84, 0x2F, 0x73, 0x03, 0x39, 0x52, 0xFB, 0x0C, 0x20, 0x60, 0x85, 0x54, 0xAF, 0x16, 0x46, 0xF0, 0x8F, 0xEC, 0xFD, 0x58, 0x18, 0xEA, 0x14, 0x1D, 0x3E, 0xD2, 0x79, 0x61, 0xC5, 0x2E, 0x5B, 0x8A, 0x2A, 0xF6, 0x53, 0xDD, 0x33, 0x6E, 0xF8, 0x87, 0x00, 0x09, 0x68, 0x4C, 0x19, 0x22, 0xC6, 0x79, 0x8A, 0x2B, 0x8F, 0xEB, 0x45, 0x22, 0xC0, 0x01, 0x5F, 0x42, 0x5A, 0x76, 0x24, 0xF8, 0x24, 0x95, 0x39, 0x81, 0xF0, 0x30, 0x1E, 0xD8, 0x48, 0x6D, 0xE9, 0x0B, 0x15, 0x86, 0xAF, 0x6A, 0x5B, 0x44, 0x5E, 0xFE, 0x6F, 0x03, 0x38, 0xB3, 0x0D, 0x56, 0xE3, 0x9E, 0xBB, 0xED, 0x0A, 0x8F, 0x3B, 0xB1, 0x32, 0x4C, 0xAE, 0xC7, 0x2E, 0xA9, 0xE8, 0xD6, 0xC4, 0x61, 0x4D, 0xD0, 0x0D, 0xA9, 0x96, 0xAF, 0x3F, 0xF3, 0x9C, 0xA9, 0xBD, 0x03, 0x72, 0x38, 0x1E, 0x6B, 0x99, 0x41, 0xB0, 0xCB, 0x84, 0xD9, 0xBC, 0x43, 0x6E, 0xBB, 0x34, 0xA4, 0xC2, 0x92, 0xEF, 0xBD, 0xEA, 0x9C, 0x52, 0x88, 0x3B, 0x18, 0x25, 0xFF, 0x9C, 0x55, 0xAC, 0x9D, 0x36, 0x5B, 0xCD, 0x18, 0xE6, 0x96, 0x52, 0x93, 0x87, 0xEA, 0x6D, 0xFB, 0xAE, 0x9F, 0xB5, 0xB8, 0xDE, 0xF8, 0x50, 0x02, 0x44, 0xF0, 0x1F, 0x02, 0xC0, 0x09, 0xF5, 0x98, 0xCE, 0xC7, 0x11, 0xC8, 0xD5, 0x8F, 0xEE, 0xA8, 0x50, 0x42, 0xAA, 0x60, 0x9D, 0x70, 0x56, 0x0A, 0xF6, 0x54, 0x45, 0xCB, 0xF9, 0x71, 0x1D, 0xCA, 0xE7, 0x80, 0xC7, 0xB0, 0xAE, 0x33, 0x3E, 0x11, 0x00, 0x5D, 0x6F, 0xA7, 0x43, 0xDC, 0x7E, 0x33, 0xAD, 0xA4, 0xE2, 0x9B, 0xC0, 0x09, 0xD5, 0x6B, 0xA4, 0x49, 0x1A, 0xD4, 0xF7, 0xFC, 0x3E, 0x00, 0x70, 0xD8, 0x3E, 0xFE, 0x6A, 0x60, 0x5C, 0x58, 0xC3, 0xE9, 0xC6, 0x4E, 0x16, 0x6F, 0xB2, 0xC6, 0x79, 0x4D, 0x14, 0x37, 0x79, 0x28, 0xD5, 0xE5, 0xE7, 0xF6, 0x66, 0xE5, 0x22, 0x1D, 0x7D, 0xC1, 0xFD, 0x1B, 0x32, 0xB9, 0x45, 0x01, 0x00, 0x3D, 0x26, 0x25, 0x35, 0x00, 0x33, 0xA0, 0x83, 0xCF, 0xCC, 0xCD, 0xA7, 0x8E, 0x48, 0xC5, 0xFA, 0x67, 0x2D, 0x87, 0xB9, 0xA1, 0x0E, 0x01, 0xA4, 0x33, 0x8A, 0x0D, 0x98, 0xDC, 0xA1, 0x99, 0xCA, 0x5C, 0x75, 0xEE, 0x3B, 0xB3, 0x9A, 0x4E, 0xDB, 0x66, 0x48, 0xA2, 0x3B, 0x0E, 0x6C, 0x87, 0xF9, 0xA0, 0x64, 0x86, 0xA0, 0x99, 0x50, 0xAC, 0xB3, 0xF3, 0xC9, 0xDE, 0x76, 0x59, 0xC6, 0x3B, 0xB1, 0x78, 0x47, 0x9F, 0xFA, 0xCF, 0x86, 0x2B, 0xBB, 0x3C, 0x23, 0x60, 0x5A, 0x7E, 0xD3, 0x7A, 0xB3, 0xE3, 0xEE, 0xE7, 0xEE, 0x2F, 0xD1, 0x44, 0x6C, 0xD0, 0x1A, 0x61, 0x12, 0x17, 0x18, 0x18, 0x36, 0x5F, 0x98, 0x40, 0x86, 0x59, 0xF3, 0x31, 0x6D, 0xB3, 0xCD, 0xDB, 0x16, 0x2C, 0x3E, 0x5F, 0x39, 0x59, 0xC0, 0x51, 0xE5, 0x19, 0x41, 0xAD, 0x41, 0x83, 0x54, 0x5F, 0xB6, 0x9F, 0xB4, 0xA4, 0x74, 0x79, 0xD6, 0xF8, 0x4B, 0x5C, 0x7F, 0xFF, 0x4F, 0x32, 0x7E, 0xF1, 0xB8, 0xD1, 0x2F, 0x1A, 0x59, 0x7C, 0x2C, 0x89, 0x39, 0xDF, 0xD6, 0x1A, 0x45, 0xDB, 0x16, 0x05, 0x24, 0x24, 0x3F, 0x27, 0xC9, 0xEE, 0x2D, 0xCE, 0x82, 0x09, 0xD6, 0x4B, 0x0C, 0xC7, 0x4D, 0xBF, 0x17, 0x9C, 0xFD, 0xFE, 0x88, 0x07, 0x3C, 0x4F, 0xE9, 0x3C, 0x42, 0x64, 0xF5, 0x70, 0xD4, 0x00, 0x08, 0x51, 0x4D, 0x22, 0x4B, 0x90, 0xEA, 0x93, 0x17, 0x87, 0x6B, 0x32, 0x93, 0x40, 0xC5, 0x8F, 0xFB, 0x16, 0x12, 0xC7, 0x7F, 0x70, 0xD0, 0x92, 0xC7, 0xAE, 0x18, 0x10, 0xEB, 0xD0, 0x1C, 0xAB, 0xD2, 0x28, 0x27, 0xFE, 0x65, 0x41, 0x8F, 0x8A, 0x93, 0x79, 0xC3, 0xE4, 0xC3, 0xD8, 0xAA, 0x2F, 0x7C, 0x0B, 0x2B, 0x04, 0xFA, 0xC5, 0x3F, 0x42, 0x47, 0x15, 0x96, 0x1C, 0x14, 0x57, 0x39, 0x30, 0x14, 0xD3, 0xD0, 0x9C, 0x95, 0x91, 0x85, 0xE5, 0xEF, 0x98, 0xBC, 0xDF, 0xAC, 0x8A, 0x62, 0x81, 0x50, 0x68, 0x50, 0xA2, 0x48, 0x41, 0xE6, 0x35, 0xEB, 0x7F, 0x31, 0xDB, 0xD5, 0x16, 0xE1, 0x2E, 0x2F, 0x36, 0x1C, 0x7A, 0xF3, 0xFF, 0x9A, 0x5F, 0x10, 0xA4, 0x7A, 0x8F, 0xF0, 0x4B, 0xCE, 0xFF, 0xDD, 0x67, 0x7D, 0x1D, 0x6F, 0x1A, 0x5B, 0x06, 0x86, 0xC6, 0x3C, 0x47, 0x3B, 0xC3, 0x03, 0x70, 0xBA, 0x3A, 0xBE, 0xF0, 0x4C, 0xA5, 0x32, 0xE0, 0x28, 0xB9, 0x3E, 0x5B, 0xBD, 0xE1, 0x0D, 0xD7, 0xB3, 0xE7, 0xC8, 0xEB, 0x18, 0x59, 0x53, 0xC7, 0x26, 0xEB, 0xEC, 0x23, 0xBE, 0x07, 0x29, 0x04, 0x88, 0x77, 0x27, 0xBF, 0x62, 0x25, 0x24, 0xB4, 0x1B, 0x6E, 0x4B, 0xEF, 0x6E, 0xE5, 0x32, 0xE9, 0x29, 0xEA, 0x88, 0xCF, 0x2D, 0x00, 0x8D, 0xCB, 0x6D, 0x34, 0x6E, 0xE9, 0x28, 0x33, 0x68, 0xB1, 0x5B, 0x11, 0xDE, 0x60, 0x02, 0x45, 0x05, 0x2B, 0x89, 0xE8, 0x9A, 0x01, 0x62, 0x17, 0x44, 0x1D, 0x1C, 0x34, 0xA4, 0xD9, 0x92, 0x90, 0xE7, 0xAF, 0x3A, 0xFC, 0xA0, 0x57, 0x32, 0x4E, 0x2F, 0x7A, 0x2C, 0xF1, 0xF2, 0x93, 0x15, 0xD8, 0xCB, 0x8B, 0x7B, 0x1A, 0x57, 0x48, 0x58, 0xBA, 0x19, 0x34, 0x44, 0x9D, 0xD1, 0x34, 0x09, 0x36, 0xB3, 0xFC, 0x76, 0x43, 0x57, 0x31, 0x19, 0x5E, 0xFA, 0xD6, 0x48, 0x31, 0xD3, 0xCA, 0x95, 0x77, 0x8B, 0x99, 0xD4, 0x68, 0xB9, 0xBE, 0x4C, 0x5E, 0x8E, 0x9E, 0xD1, 0x0A, 0xE3, 0x2F, 0xA9, 0x8E, 0xBE, 0x77, 0xC9, 0x5C, 0xB9, 0xD9, 0xF5, 0xA1, 0x86, 0xBF, 0x75, 0x96, 0xF3, 0x8D, 0x26, 0x5D, 0x4D, 0x7B, 0x96, 0x9D, 0x1F, 0x4D, 0x11, 0x3A, 0xC9, 0xF7, 0xA6, 0xBD, 0xE2, 0xBC, 0xD4, 0x5B, 0xDB, 0x7D, 0xF3, 0xAA, 0xE8, 0x3B, 0xC9, 0xD9, 0x20, 0x4A, 0x2A, 0x09, 0x1B, 0x36, 0x9B, 0xA0, 0x0C, 0x0A, 0x33, 0x4C, 0x82, 0xB7, 0x6B, 0xD5, 0xC7, 0x30, 0x61, 0x12, 0xC9, 0xA7, 0x7D, 0x81, 0x95, 0x87, 0x63, 0x3F, 0xCE, 0x7D, 0x9B, 0xD0, 0x4F, 0xE3, 0x42, 0x39, 0xFD, 0xE4, 0x8D, 0x3C, 0x12, 0x5F, 0x6E, 0x24, 0xD3, 0xE6, 0x97, 0xA3, 0x68, 0x4E, 0xFB, 0xDA, 0x4A, 0x56, 0x31, 0xB7, 0x88, 0xF1, 0x20, 0x51, 0x97, 0x3A, 0x51, 0xB5, 0x3E, 0x52, 0x61, 0x36, 0x12, 0xC3, 0xFF, 0x07, 0x11, 0xD2, 0x33, 0x8B, 0x82, 0xD6, 0x8C, 0x45, 0x02, 0x89, 0xB7, 0x7B, 0x13, 0x3E, 0x50, 0xBB, 0x7E, 0xEC, 0x24, 0xC4, 0x77, 0x56, 0x7D, 0x74, 0x86, 0x61, 0x0E, 0x46, 0xCF, 0x7B, 0x7B, 0xAA, 0x3B, 0x79, 0x8B, 0x7C, 0xDD, 0xE7, 0xB0, 0x22, 0xCC, 0xCE, 0x3E, 0xA6, 0x63, 0xBA, 0x9F, 0xEA, 0xC7, 0x65, 0x4B, 0xF7, 0xAB, 0x90, 0xFE, 0xE7, 0x34, 0x08, 0x8D, 0xE2, 0x81, 0x91, 0x07, 0xB9, 0xE4, 0x34, 0x28, 0x6E, 0x83, 0xFC, 0xD0, 0x34, 0x8D, 0xC3, 0x10, 0x7C, 0x1A, 0x3C, 0xF3, 0x44, 0x45, 0x2C, 0x23, 0xE5, 0xAF, 0x43, 0xA7, 0x42, 0x61, 0x3F, 0xD7, 0xBB, 0xE5, 0x85, 0x9F, 0xF5, 0xE0, 0xBB, 0x89, 0xF2, 0xD6, 0xF5, 0xFA, 0x4C, 0x8D, 0xC0, 0x30, 0x5E, 0x4F, 0x15, 0x8E, 0x67, 0xF7, 0x45, 0xAD, 0xE4, 0xBD, 0x28, 0x8E, 0xE7, 0xDA, 0x0E, 0x56, 0x84, 0xE8, 0xFA, 0x5D, 0xF7, 0x49, 0x3E, 0x9F, 0x6C, 0x96, 0xDF, 0xFD, 0x23, 0x9F, 0xC5, 0x58, 0x0C, 0x44, 0xAD, 0x96, 0x54, 0xC4, 0x4B, 0x0D, 0x76, 0x3E, 0x3E, 0xAD, 0xA3, 0x27, 0x66, 0x97, 0x9C, 0x41, 0x0A, 0x61, 0x07, 0x14, 0x35, 0x26, 0xF4, 0xE8, 0xCF, 0xC6, 0x94, 0xDA, 0x69, 0xB1, 0x93, 0x6C, 0xC7, 0x01, 0x17, 0x89, 0x7F, 0x8E, 0x9B, 0x6B, 0xDA, 0xC7, 0xE5, 0x00, 0xBD, 0xD7, 0x01, 0x20, 0x6A, 0x0B, 0x7F, 0x6B, 0x0E, 0x4E, 0x12, 0xB2, 0x6E, 0x16, 0x77, 0x86, 0x3E, 0x3D, 0xF7, 0x08, 0x80, 0xA9, 0x71, 0xA3, 0xCE, 0x10, 0xA3, 0xEE, 0xBB, 0x19, 0xBA, 0x73, 0x94, 0x98, 0x6D, 0x0A, 0x7A, 0x7F, 0xE0, 0x80, 0xE5, 0x6E, 0xC1, 0xE0, 0x86, 0x31, 0xD9, 0xB3, 0xA5, 0x57, 0x6D, 0x62, 0xED, 0x7D, 0xF1, 0x19, 0x7F, 0xD7, 0xCC, 0xA0, 0xDF, 0xC7, 0x0C, 0x43, 0x6C, 0x75, 0x99, 0x59, 0x8F, 0xA6, 0xEE, 0x2C, 0x2E, 0xE7, 0x3E, 0x31, 0xC6, 0x36, 0xC1, 0x69, 0x8E, 0xED, 0x42, 0xBB, 0x62, 0xFC, 0xA5, 0x25, 0x29, 0xAF, 0xBE, 0x4D, 0x4B, 0x91, 0x5B, 0x94, 0xAD, 0x5B, 0x1C, 0xFD, 0x39, 0x8E, 0xA5, 0x94, 0xD1, 0xD2, 0xE8, 0xE8, 0xB1, 0xB0, 0xA4, 0x29, 0x58, 0xBB, 0xDA, 0x7E, 0xFC, 0x24, 0xA1, 0x21, 0x3A, 0x69, 0xEE, 0x5C, 0xE4, 0x08, 0x58, 0x3D, 0xBC, 0xF9, 0xB8, 0xE4, 0xBF, 0x4A, 0xC5, 0x51, 0x70, 0xAE, 0x03, 0x81, 0x93, 0xD5, 0x62, 0xB9, 0xB7, 0x96, 0x92, 0x17, 0x5E, 0xAF, 0xE8, 0xAA, 0x82, 0x60, 0x30, 0xB0, 0xE3, 0xF8, 0xFB, 0xA5, 0xBE, 0x62, 0x4E, 0xB5, 0x2B, 0xC2, 0x1D, 0x1F, 0xEA, 0xC3, 0x09, 0xB6, 0x6E, 0xAC, 0xE0, 0xCE, 0x01, 0x9F, 0x97, 0xDE, 0xD0, 0x52, 0x92, 0xDC, 0xD4, 0xFC, 0x8A, 0xA2, 0x7E, 0xDB, 0x10, 0x4E, 0x37, 0xD6, 0x83, 0xD9, 0x36, 0xFB, 0xA8, 0xC7, 0xD6, 0xD3, 0x0F, 0xB4, 0x32, 0x05, 0x4E, 0x9B, 0x47, 0x89, 0x5E, 0x0D, 0xF0, 0x9A, 0x49, 0x05, 0x38, 0xDF, 0xDD, 0x3E, 0x33, 0x35, 0x41, 0x64, 0x17, 0xCD, 0xE6, 0xC1, 0x85, 0xA4, 0xBB, 0xC6, 0x43, 0x90, 0x71, 0x9A, 0x5B, 0x5A, 0xE2, 0x97, 0x2D, 0x0F, 0xCC, 0xE0, 0xA6, 0x8E, 0x22, 0x51, 0x38, 0xFB, 0x54, 0xBC, 0x92, 0x27, 0x4C, 0x2E, 0x44, 0x33, 0xE0, 0xE3, 0xD5, 0xE0, 0xA8, 0xAB, 0x9B, 0xDC, 0xEF, 0x11, 0x3D, 0x86, 0x4E, 0xF4, 0x83, 0xEB, 0x27, 0xF4, 0x55, 0x21, 0x0C, 0x49, 0x26, 0xB4, 0x42, 0x3B, 0x81, 0x46, 0x8B, 0x6C, 0x3A, 0x29, 0xB3, 0x61, 0x34, 0x22, 0x65, 0xAB, 0x56, 0x0D, 0xD9, 0xC3, 0x9E, 0x33, 0xFD, 0xD5, 0xFC, 0xF1, 0xDD, 0xDA, 0xD6, 0xA9, 0x32, 0x21, 0x0E, 0x9E, 0x5C, 0x5A, 0xFF, 0x15, 0x07, 0x37, 0x96, 0x74, 0x1F, 0x67, 0x89, 0x2D, 0x91, 0xD5, 0x50, 0x07, 0x65, 0x07, 0x4F, 0x1E, 0x04, 0xDF, 0x56, 0xD8, 0x30, 0x98, 0x6F, 0xCD, 0xE0, 0x79, 0xF3, 0xE4, 0x6A, 0xEA, 0x22, 0xD9, 0x7E, 0x7F, 0x6E, 0x87, 0x04, 0x6C, 0x6F, 0x5A, 0x88, 0x62, 0x12, 0xB0, 0x3A, 0x68, 0xA3, 0xB5, 0x96, 0x2A, 0x26, 0x98, 0x36, 0x54, 0x07, 0x83, 0x14, 0x3C, 0xC8, 0x30, 0x09, 0xE2, 0xE0, 0xE2, 0x79, 0xFA, 0x7B, 0xC5, 0x85, 0x6E, 0xE9, 0x1C, 0xF9, 0xC6, 0xC9, 0xDE, 0xCB, 0xA8, 0x02, 0x09, 0x24, 0x84, 0x8E, 0x45, 0xE4, 0x72, 0x81, 0x13, 0x89, 0x24, 0xD2, 0x66, 0x6F, 0xD9, 0xF0, 0x9A, 0xF8, 0x82, 0x02, 0x71, 0x6C, 0x83, 0x7F, 0x2F, 0xC9, 0x4A, 0x88, 0xA1, 0x2A, 0x8C, 0x04, 0x38, 0xB4, 0xEF, 0x98, 0x4B, 0x54, 0x4A, 0xC1, 0xCE, 0x29, 0x43, 0x73, 0xA1, 0x23, 0x70, 0xFC, 0xDA, 0x44, 0x15, 0x37, 0xFC, 0x53, 0x4D, 0xB3, 0x52, 0xD1, 0x8A, 0x2B, 0x2D, 0x60, 0xE7, 0xDE, 0x29, 0x0A, 0xC6, 0x67, 0x64, 0x8C, 0x2A, 0xD7, 0x79, 0x0B, 0x44, 0x95, 0x3A, 0xF7, 0x6E, 0x02, 0x24, 0xAE, 0x6E, 0xB8, 0x64, 0x45, 0xD8, 0xD6, 0xE7, 0xCA, 0x1C, 0x27, 0x0E, 0x48, 0xC0, 0x38, 0xC0, 0x9D, 0x6A, 0xFB, 0x91, 0x7B, 0x1D, 0xF7, 0x90, 0xA0, 0x77, 0xA0, 0x9B, 0x4B, 0xE5, 0xDE, 0x60, 0xC4, 0x53, 0xF0, 0x28, 0xC8, 0x9E, 0xB5, 0x77, 0x5B, 0x61, 0x84, 0xAC, 0xEA, 0x59, 0x89, 0x3F, 0x6E, 0x03, 0x8A, 0xEB, 0x37, 0x69, 0xC8, 0x68, 0x74, 0x10, 0xBD, 0xE9, 0xB1, 0x1F, 0x25, 0x38, 0xBD, 0x26, 0x05, 0x66, 0xB8, 0x44, 0x70, 0x34, 0xBD, 0x2A, 0xEE, 0x5D, 0x81, 0xB1, 0x2D, 0x13, 0xE1, 0x04, 0x7D, 0x82, 0xE3, 0x68, 0x67, 0x91, 0x60, 0xB9, 0x7B, 0x36, 0x89, 0x80, 0x84, 0x3D, 0x85, 0x0A, 0xFE, 0xC3, 0xFF, 0x94, 0xAA, 0x7B, 0x86, 0x07, 0x74, 0x22, 0xC1, 0x8B, 0x56, 0x67, 0xC7, 0xEC, 0x41, 0x4F, 0x17, 0xC1, 0x02, 0x77, 0x42, 0x36, 0x4B, 0x0D, 0xF3, 0xC3, 0xB5, 0x71, 0x52, 0x86, 0x54, 0x0A, 0xB6, 0x60, 0x00, 0x85, 0x76, 0xC3, 0x40, 0x42, 0xBA, 0x15, 0x21, 0x08, 0x3D, 0xC8, 0xD5, 0x09, 0x53, 0xCC, 0x21, 0xFF, 0x19, 0x95, 0xF0, 0x52, 0xBC, 0x4A, 0x5A, 0xEC, 0x4A, 0xB7, 0x21, 0x9E, 0x03, 0x4A, 0x72, 0xF5, 0x90, 0xE4, 0x61, 0xC8, 0x8F, 0x3A, 0x3F, 0xE3, 0x5D, 0x2B, 0xE9, 0x7A, 0x48, 0xA4, 0xDC, 0x0B, 0x78, 0xEC, 0x7E, 0x8B, 0xBF, 0x14, 0x40, 0xFF, 0x26, 0x75, 0x07, 0x5E, 0x9A, 0xFC, 0xCD, 0xCD, 0x9C, 0xC5, 0x02, 0xC6, 0x72, 0xB4, 0xA9, 0x4C, 0x06, 0x6E, 0x9B, 0xD4, 0xE2, 0x5F, 0x22, 0x04, 0x13, 0x86, 0x6D, 0x39, 0xDB, 0x63, 0x4B, 0xF6, 0x07, 0x83, 0xEA, 0x80, 0xC9, 0xAC, 0x83, 0xA9, 0x6D, 0x14, 0x30, 0x53, 0x73, 0x62, 0x79, 0x40, 0xB8, 0x7D, 0x0D, 0x8D, 0x03, 0xA6, 0x21, 0xA0, 0x4F, 0x90, 0x0D, 0xC4, 0xA0, 0x3C, 0xEB, 0x0E, 0xD1, 0xB0, 0x7A, 0xCD, 0x3A, 0x49, 0xC9, 0x8D, 0x20, 0xE4, 0x94, 0x68, 0x8D, 0x94, 0xA7, 0x50, 0x67, 0xB6, 0xF4, 0xC8, 0x40, 0x7E, 0x58, 0xAD, 0xB8, 0x8E, 0xBA, 0xCE, 0x08, 0x5D, 0x54, 0xFE, 0xB3, 0xD1, 0x90, 0xB1, 0x5D, 0xDF, 0x21, 0xFA, 0x50, 0x46, 0x7A, 0x88, 0xD3, 0x96, 0x17, 0x01, 0xE8, 0xC9, 0xD3, 0xCA, 0x1F, 0x23, 0x2C, 0xD4, 0xF8, 0x62, 0xE5, 0xB3, 0x78, 0x59, 0xB8, 0xF6, 0x69, 0x85, 0x48, 0xC5, 0xE4, 0xC1, 0x73, 0x47, 0xB4, 0x92, 0xAE, 0xDD, 0xD1, 0xEC, 0xBE, 0x57, 0xCA, 0x51, 0xAB, 0xAF, 0xAD, 0xA8, 0x19, 0x84, 0xB5, 0xCF, 0xA4, 0xD7, 0xAF, 0xE6, 0xF5, 0xFA, 0xBE, 0x3B, 0x6A, 0xE2, 0x37, 0x03, 0x44, 0xDD, 0x02, 0x93, 0x05, 0x74, 0x9F, 0xD0, 0x98, 0xD9, 0x49, 0x3E, 0x2D, 0x4E, 0xA6, 0x6F, 0x2C, 0x05, 0xDF, 0x45, 0xC8, 0xCA, 0xD1, 0x84, 0xF6, 0x32, 0xA8, 0x1F, 0x7A, 0xF9, 0xF4, 0xBF, 0xC1, 0x03, 0x61, 0x47, 0x65, 0xEA, 0xAE, 0x74, 0x1D, 0x91, 0xD2, 0x40, 0x9D, 0x54, 0x5F, 0x55, 0xCA, 0x4F, 0xF0, 0x3A, 0x9A, 0x74, 0xD4, 0x92, 0x67, 0xA7, 0x82, 0x3B, 0xAA, 0xBF, 0x49, 0xFF, 0x11, 0x4B, 0x3E, 0xF5, 0x59, 0x12, 0xB1, 0x19, 0xEC, 0xC3, 0xCD, 0x9D, 0x28, 0x4D, 0x46, 0x37, 0x2A, 0x56, 0xC6, 0x27, 0x21, 0x5D, 0x3F, 0x86, 0x0A, 0x1F, 0xEE, 0xFF, 0x0B, 0x28, 0x38, 0xD9, 0x67, 0xA3, 0x80, 0x28, 0x6F, 0x71, 0x9E, 0xA6, 0x6B, 0x1E, 0x91, 0x72, 0xEF, 0x0F, 0xB3, 0x60, 0x2D, 0x5B, 0xF4, 0x1C, 0xFC, 0x09, 0x8B, 0x72, 0x3C, 0x41, 0x27, 0x19, 0x65, 0xAD, 0x32, 0x28, 0x6E, 0x8E, 0x46, 0xFA, 0xA1, 0x48, 0x63, 0xD2, 0x64, 0x1B, 0xD4, 0x3B, 0xD3, 0xB9, 0x31, 0xE1, 0x10, 0xAA, 0x3A, 0x46, 0x19, 0xF4, 0x8E, 0xD7, 0xF9, 0x40, 0x11, 0xE2, 0x6A, 0x62, 0xCE, 0x83, 0xD3, 0x8A, 0xE4, 0xD0, 0x97, 0xC0, 0x50, 0x8A, 0x3E, 0x92, 0x34, 0x26, 0x9C, 0x58, 0x54, 0x70, 0xAE, 0x13, 0x23, 0xFD, 0x8E, 0x95, 0xBC, 0xF6, 0xA9, 0x4A, 0x59, 0xDC, 0xD6, 0x8C, 0x2C, 0xCF, 0x97, 0x49, 0x1B, 0x13, 0xB9, 0x0E, 0x1B, 0xBC, 0x62, 0x34, 0xE4, 0x3E, 0x84, 0xD8, 0x63, 0x92, 0x2F, 0x46, 0xB4, 0xDA, 0x5C, 0xB6, 0x63, 0xD0, 0x86, 0x24, 0x8C, 0x50, 0xB3, 0x18, 0xE1, 0xFE, 0x70, 0x65, 0x54, 0xCD, 0xC6, 0x0C, 0x14, 0x57, 0x74, 0xFA, 0x60, 0xB1, 0xE3, 0xE4, 0xBA, 0x0C, 0x36, 0xA2, 0x8D, 0x48, 0x2F, 0x8F, 0x59, 0xAF, 0xD1, 0xC8, 0xC8, 0x60, 0x2F, 0x4C, 0x42, 0xF4, 0x1F, 0xCD, 0x8C, 0x5C, 0xB2, 0xCA, 0xB3, 0xBE, 0x1F, 0xF9, 0x76, 0xEC, 0x84, 0xA7, 0x3C, 0x36, 0xCB, 0x6F, 0x3D, 0x58, 0x6A, 0x19, 0xE1, 0x2A, 0x34, 0xFA, 0xE0, 0xB0, 0x65, 0xDF, 0x90, 0x61, 0xC1, 0x21, 0x3B, 0xE5, 0x84, 0xCD, 0x8A, 0x32, 0xFE, 0x0F, 0x68, 0x78, 0x8F, 0x77, 0xCD, 0xE4, 0x3B, 0xBC, 0x38, 0xCE, 0x3E, 0x32, 0x97, 0x70, 0x7F, 0xFA, 0x63, 0x6C, 0x2C, 0x50, 0xD7, 0x54, 0x4A, 0x83, 0x7A, 0xCA, 0x51, 0x46, 0xFE, 0x8B, 0xB5, 0xCC, 0x6B, 0x64, 0x82, 0xF8, 0x17, 0xE1, 0xE0, 0x48, 0x25, 0xD2, 0x0E, 0x0D, 0x29, 0xBD, 0x79, 0x50, 0x57, 0x11, 0xD1, 0x48, 0x8D, 0x19, 0xF7, 0x9D, 0x56, 0xA4, 0x01, 0xCE, 0x3A, 0x2F, 0x6B, 0x6F, 0x14, 0xF8, 0x59, 0x68, 0x4C, 0x16, 0xDE, 0x33, 0x09, 0x4D, 0xB5, 0xD7, 0x14, 0xC0, 0xAB, 0x4D, 0x90, 0x19, 0x3E, 0x6C, 0x97, 0x85, 0x2F, 0x5E, 0x81, 0xB0, 0x52, 0x2B, 0x7D, 0xCC, 0xF1, 0x3A, 0xC0, 0x95, 0x9D, 0xF2, 0xB2, 0x97, 0xE2, 0x42, 0x26, 0xEA, 0xC2, 0xD7, 0x96, 0xA0, 0x24, 0x47, 0xA3, 0xBB, 0x2C, 0x8F, 0xC6, 0x7B, 0x3F, 0xDF, 0x0C, 0x08, 0x6D, 0x93, 0x2F, 0x26, 0xB8, 0x8C, 0xE5, 0x30, 0x72, 0x8D, 0x8A, 0x2D, 0xE6, 0x29, 0x95, 0x2F, 0x43, 0x75, 0xFC, 0xD9, 0x6B, 0xCF, 0x1A, 0x29, 0xBB, 0xF3, 0xE6, 0x8F, 0x43, 0x0E, 0x12, 0x07, 0x9F, 0xB1, 0x76, 0x45, 0xB3, 0xEC, 0x6B, 0x5A, 0xD6, 0x77, 0x89, 0xCF, 0x7A, 0x9A, 0x26, 0x34, 0xA6, 0x69, 0x51, 0x7C, 0x07, 0x3D, 0xA3, 0xA2, 0xAA, 0x57, 0xF5, 0x5A, 0x9E, 0x85, 0x4F, 0x37, 0x53, 0x9D, 0x6A, 0x44, 0x26, 0x54, 0xE2, 0x7D, 0x1D, 0xD5, 0xD7, 0x50, 0x00, 0xF9, 0x5B, 0x9D, 0x8D, 0x25, 0xA4, 0x78, 0xF3, 0x14, 0x87, 0xBA, 0x89, 0xBB, 0x54, 0x72, 0xEC, 0xA7, 0xE9, 0xF3, 0x07, 0xE1, 0x5E, 0x3A, 0xCC, 0x91, 0x9E, 0x8F, 0x61, 0x41, 0xCC, 0x28, 0xBA, 0x98, 0xC8, 0x98, 0xFC, 0x13, 0x24, 0x44, 0x48, 0x7F, 0x5E, 0x46, 0x45, 0x22, 0x44, 0x51, 0x1D, 0xF2, 0x02, 0x0C, 0x19, 0x47, 0x5B, 0xE4, 0x7B, 0xE8, 0x7D, 0xE9, 0xB0, 0x9D, 0xB8, 0x6C, 0xF7, 0x7B, 0x04, 0x6F, 0x24, 0xD7, 0x58, 0xF6, 0x09, 0x50, 0x0D, 0x2B, 0xCC, 0x85, 0x81, 0xD3, 0xD4, 0x02, 0x8E, 0xFC, 0xFD, 0xC0, 0x04, 0x5C, 0x39, 0x48, 0x08, 0xB1, 0x5D, 0xF1, 0xF0, 0x37, 0xCC, 0xD4, 0x0D, 0x78, 0xE9, 0x6F, 0xA2, 0x8B, 0x02, 0xC7, 0xA0, 0x1D, 0xD8, 0x8F, 0xA6, 0xC3, 0x4E, 0x44, 0x0D, 0xF5, 0x0E, 0xDE, 0x9E, 0x6A, 0xBE, 0xC9, 0xE1, 0x7B, 0x71, 0x42, 0x1F, 0x2D, 0x37, 0x29, 0xCC, 0xCE, 0xE7, 0x38, 0x0C, 0xF2, 0x73, 0x1C, 0x5C, 0x45, 0xA3, 0x2E, 0x94, 0xB1, 0x24, 0x94, 0x9D, 0x65, 0xAA, 0xB7, 0x87, 0x47, 0x41, 0x3A, 0x85, 0xDE, 0x05, 0xB7, 0x4A, 0xAF, 0x9E, 0x15, 0x94, 0xA1, 0x87, 0xE1, 0x91, 0x70, 0x40, 0x71, 0x56, 0x39, 0xCE, 0xC9, 0x47, 0x1D, 0x8D, 0x80, 0x95, 0x7D, 0x3B, 0x9E, 0xA9, 0xA6, 0x93, 0x19, 0xEB, 0x23, 0x1D, 0x3B, 0x72, 0xBA, 0x6F, 0x01, 0xFE, 0xC9, 0x56, 0x50, 0xE2, 0xFD, 0x55, 0x04, 0x41, 0xA7, 0x75, 0x34, 0xC9, 0x58, 0xBE, 0x43, 0x12, 0x63, 0x54, 0x20, 0xA8, 0x5E, 0x24, 0xE4, 0x92, 0x4D, 0x28, 0x55, 0x45, 0x32, 0x65, 0xCF, 0x12, 0x1E, 0x50, 0x37, 0xE0, 0x86, 0xCD, 0xA7, 0xDD, 0x91, 0x9E, 0xC6, 0xD1, 0xDE, 0xA9, 0xCA, 0x57, 0x70, 0x06, 0xEE, 0xD8, 0x90, 0x61, 0xF0, 0x79, 0x84, 0x4E, 0xCC, 0x19, 0xCA, 0xDC, 0x1E, 0xB7, 0x78, 0x90, 0x23, 0x0D, 0x83, 0xDB, 0x48, 0xA0, 0xD7, 0x86, 0x38, 0x6B, 0x6A, 0x55, 0xFE, 0x89, 0x4F, 0xE5, 0xDA, 0x0C, 0x5B, 0xB3, 0x8C, 0x35, 0x00, 0x0A, 0x17, 0xFD, 0x77, 0x00, 0x0E, 0x1F, 0x22, 0x67, 0xCB, 0x80, 0x21, 0xC9, 0x4E, 0x57, 0xAB, 0x50, 0x0A, 0xBA, 0x9B, 0xF6, 0x15, 0x97, 0xEC, 0x72, 0xDA, 0x5F, 0xF7, 0x5E, 0x28, 0x82, 0xD9, 0xDE, 0x86, 0x05, 0xB8, 0x7A, 0xF6, 0x8D, 0x7B, 0x4F, 0x76, 0x35, 0xB9, 0x85, 0x6A, 0xF2, 0xDD, 0x12, 0xC1, 0x44, 0xE5, 0xDC, 0xC7, 0x74, 0xF3, 0x55, 0x7D, 0x74, 0x18, 0x5A, 0x41, 0x3D, 0xE9, 0xEA, 0x90, 0x37, 0x22, 0x77, 0x47, 0x61, 0xD2, 0x02, 0x91, 0x4C, 0x8B, 0x24, 0x36, 0x00, 0xD7, 0xD1, 0x89, 0xF3, 0x4E, 0x5D, 0x60, 0x7B, 0x36, 0xF7, 0x0F, 0x9A, 0x72, 0x51, 0x3D, 0xD7, 0x8B, 0xE5, 0x8F, 0xEB, 0x09, 0x65, 0x96, 0xC5, 0x2B, 0x0D, 0x25, 0xC3, 0xF0, 0x89, 0x86, 0xD4, 0xBA, 0xB6, 0x7B, 0x4F, 0x2A, 0x8A, 0xAB, 0x10, 0xC4, 0x2E, 0xE5, 0x8C, 0xE2, 0x91, 0xB9, 0x9D, 0x09, 0x08, 0x45, 0x8B, 0xB3, 0xFC, 0xED, 0x44, 0x38, 0xE8, 0xA5, 0x90, 0x00, 0x48, 0x76, 0xA7, 0x6C, 0x44, 0x60, 0x10, 0xE2, 0x7F, 0x05, 0xD0, 0x91, 0x30, 0xDE, 0x3D, 0xE2, 0xBC, 0xCC, 0xED, 0xEE, 0x53, 0xA3, 0xEF, 0xAA, 0x29, 0xE1, 0x1E, 0xFC, 0x45, 0x7E, 0x98, 0x7C, 0x13, 0xB1, 0xF4, 0x92, 0x0A, 0x92, 0x4E, 0xDD, 0x90, 0xD1, 0x07, 0x45, 0xA3, 0x2B, 0x5F, 0xDB, 0xBF, 0x53, 0x18, 0xDC, 0x9F, 0x62, 0x64, 0xF1, 0x64, 0x6A, 0xF6, 0xCC, 0x8B, 0x10, 0x21, 0x72, 0xD2, 0x12, 0x22, 0xDA, 0xA4, 0xAE, 0xE0, 0x14, 0x9D, 0x01, 0xDD, 0x5F, 0x63, 0x71, 0x41, 0x21, 0x76, 0x43, 0x22, 0x63, 0xE0, 0xBF, 0x70, 0x2B, 0xA2, 0x7D, 0x42, 0xEE, 0x2B, 0x23, 0x19, 0x7C, 0x7A, 0xA3, 0xCC, 0xFE, 0xC6, 0x7E, 0x94, 0xC6, 0xF8, 0xD2, 0xE2, 0xB1, 0x54, 0xEE, 0xD7, 0xEA, 0x9A, 0x20, 0x89, 0x2B, 0x8C, 0x70, 0x64, 0x39, 0x01, 0x64, 0x13, 0x72, 0xE8, 0xCC, 0xC8, 0xC0, 0x89, 0x1A, 0x64, 0x30, 0xF7, 0x33, 0x62, 0xE4, 0x09, 0x24, 0x0A, 0x83, 0x56, 0x18, 0x7A, 0x6C, 0x5A, 0x9E, 0x43, 0x3A, 0x6E, 0xC6, 0xF3, 0x0B, 0x6B, 0xEE, 0x07, 0x6C, 0x08, 0x64, 0x28, 0xEF, 0xA1, 0x72, 0xDA, 0x56, 0x36, 0x5E, 0xE7, 0xBD, 0x31, 0x0C, 0x58, 0x7F, 0x46, 0x07, 0x9A, 0x9B, 0x74, 0x84, 0x7E, 0x7F, 0x11, 0x05, 0x04, 0x87, 0x40, 0xB3, 0xA6, 0xB9, 0x18, 0x6B, 0x4F, 0xE5, 0xD9, 0x8E, 0xF2, 0x44, 0x33, 0xBE, 0x14, 0x48, 0x30, 0x6A, 0x9D, 0x7A, 0x8B, 0xED, 0xFE, 0x6C, 0xA1, 0x0C, 0x55, 0xC5, 0xB4, 0x43, 0xCD, 0xEC, 0x72, 0x88, 0x5E, 0xD5, 0x6B, 0xCF, 0xB8, 0xBE, 0x6C, 0xC7, 0xFA, 0xFE, 0x6A, 0x97, 0xB9, 0x59, 0x01, 0xE5, 0xD3, 0xDE, 0x82, 0xB8, 0x3E, 0x9B, 0xF6, 0xDC, 0x66, 0x62, 0xA8, 0x29, 0xBA, 0x05, 0x0E, 0x67, 0x64, 0x0B, 0x5F, 0xAB, 0x29, 0xC9, 0x18, 0x14, 0x23, 0x54, 0x52, 0x25, 0xF4, 0x69, 0xBB, 0xAB, 0xC3, 0x14, 0x53, 0x99, 0x96, 0x94, 0x40, 0x03, 0x3C, 0x96, 0x42, 0x19, 0xAC, 0x2B, 0xA6, 0x83, 0x60, 0xD1, 0xC8, 0xA7, 0x1C, 0x6C, 0x62, 0x4D, 0xD8, 0xA4, 0x6E, 0x5E, 0xC1, 0x57, 0x7B, 0x17, 0x33, 0xB6, 0xCF, 0x91, 0x18, 0x94, 0x8F, 0x2C, 0x14, 0xE5, 0x6F, 0x60, 0x5B, 0x41, 0xCB, 0x5F, 0xDA, 0x17, 0x23, 0xB6, 0xE4, 0x4B, 0x6F, 0xD9, 0xFC, 0x16, 0xB3, 0x24, 0x1C, 0x98, 0x40, 0x41, 0x2B, 0xBE, 0x15, 0x6D, 0xF3, 0x6A, 0x98, 0x3A, 0x7A, 0x99, 0xE8, 0xB4, 0x62, 0x51, 0xBA, 0x01, 0x43, 0x19, 0x2E, 0xDB, 0xFB, 0x21, 0x36, 0x70, 0x6C, 0xEE, 0xEC, 0x41, 0xD4, 0x4B, 0x9C, 0xE7, 0xE8, 0x82, 0xB1, 0xFB, 0xAE, 0x17, 0x28, 0x48, 0xCE, 0xCF, 0xE3, 0xCF, 0x2A, 0xF2, 0x9E, 0x9D, 0x24, 0xE8, 0x84, 0x1E, 0x05, 0xDD, 0x73, 0x57, 0x52, 0x79, 0xC7, 0xD6, 0xA8, 0xBF, 0xB3, 0x03, 0x3E, 0xD4, 0x5E, 0xCE, 0x74, 0xC3, 0x59, 0x69, 0x27, 0x5C, 0xE7, 0x5A, 0xE5, 0x82, 0x15, 0x5B, 0xD8, 0x18, 0x98, 0x36, 0x47, 0xBF, 0x16, 0x8D, 0x0D, 0xD1, 0x43, 0x9F, 0xE4, 0x4E, 0xAD, 0x42, 0x92, 0xE1, 0x0B, 0xA1, 0x81, 0x36, 0x38, 0xAA, 0x2F, 0x78, 0xC7, 0x9B, 0x41, 0xB1, 0xF3, 0x30, 0xE1, 0x42, 0x61, 0x10, 0xF5, 0x8F, 0xFB, 0xEC, 0xA9, 0xEC, 0xB8, 0x10, 0xAD, 0xC6, 0xC4, 0xA7, 0xA6, 0x4B, 0x6A, 0x6C, 0xDC, 0x02, 0x20, 0x42, 0x08, 0xBD, 0x6F, 0xC5, 0xF6, 0x8F, 0xC9, 0x2C, 0xA3, 0x18, 0x8C, 0x0B, 0xA1, 0x9D, 0x29, 0x25, 0x3E, 0xFE, 0x7C, 0xF4, 0xDD, 0xEF, 0x39, 0xE1, 0x96, 0x85, 0x9C, 0xA1, 0xE6, 0x6E, 0xC6, 0x0A, 0x31, 0xEC, 0xFB, 0x45, 0x85, 0xA0, 0x23, 0xFE, 0xCD, 0x1C, 0x49, 0x4F, 0xA0, 0x10, 0xD7, 0xFC, 0x74, 0x5B, 0xF9, 0xB6, 0xB2, 0x11, 0xD9, 0x9E, 0x8B, 0x26, 0xB1, 0x36, 0x8F, 0xA0, 0x6E, 0xC7, 0x39, 0xED, 0x88, 0xF3, 0x21, 0x7B, 0xBB, 0x9A, 0xF9, 0x65, 0x76, 0x8F, 0x68, 0x72, 0xB3, 0xEE, 0x0D, 0xAA, 0xCD, 0xB7, 0xFA, 0x0A, 0x47, 0x6F, 0xB0, 0x21, 0x4A, 0xB9, 0xC8, 0x7A, 0xA1, 0x6D, 0x88, 0x50, 0xF7, 0xE9, 0x2E, 0xF5, 0x64, 0x51, 0x44, 0xDE, 0x21, 0x3C, 0xC8, 0x31, 0x68, 0x42, 0x73, 0x41, 0x32, 0xC5, 0xFD, 0xCE, 0xE7, 0x16, 0xD9, 0x34, 0xFF, 0xAC, 0x20, 0x1B, 0x52, 0x2A, 0x9F, 0xD8, 0x71, 0xC3, 0x96, 0xB9, 0x88, 0x81, 0x28, 0x22, 0x07, 0x76, 0xB8, 0x0E, 0x0A, 0x49, 0xA0, 0x15, 0x67, 0xF8, 0x75, 0x88, 0xA2, 0xC3, 0x1F, 0xCB, 0xFD, 0x5F, 0x83, 0x28, 0x51, 0xA5, 0x77, 0xCB, 0x21, 0x39, 0x43, 0x93, 0xB6, 0x9F, 0x00, 0x7D, 0x07, 0x1A, 0xF3, 0x98, 0x23, 0x4B, 0x73, 0x96, 0xB1, 0xAA, 0x39, 0x24, 0x7C, 0x3D, 0xAD, 0x2C, 0xFE, 0xB9, 0x91, 0x18, 0xE1, 0x4B, 0x4D, 0x83, 0x72, 0x5C, 0x7B, 0xCE, 0xFC, 0x4F, 0xAC, 0x9C, 0x0F, 0xC5, 0x0B, 0x3B, 0x40, 0x0E, 0xB8, 0x6C, 0x65, 0xF7, 0x16, 0xF2, 0x5D, 0x40, 0x1F, 0xE9, 0x4E, 0xFD, 0x7A, 0x9E, 0x86, 0xDC, 0x6D, 0xFD, 0x0E, 0x4F, 0x72, 0xCB, 0x3E, 0x77, 0xCA, 0x33, 0xB7, 0xB3, 0xA0, 0xF0, 0xE2, 0x5C, 0xA9, 0xF0, 0x7D, 0xC0, 0x53, 0x0A, 0x0D, 0x27, 0x67, 0x62, 0x98, 0x7D, 0x9C, 0xEA, 0x76, 0xDF, 0x30, 0x1A, 0x57, 0x8C, 0xEA, 0x04, 0x0F, 0x48, 0x35, 0x1F, 0x26, 0x82, 0x6A, 0x87, 0x89, 0xCA, 0x49, 0xAF, 0xEC, 0x44, 0x1E, 0xD0, 0xDF, 0x33, 0x77, 0x03, 0xA7, 0xEA, 0x7E, 0x63, 0x82, 0x8C, 0xC7, 0x57, 0x7A, 0x49, 0x52, 0x21, 0xF5, 0x14, 0x98, 0x51, 0x8B, 0x6C, 0xBE, 0x3E, 0xC3, 0xF4, 0xE8, 0x86, 0x6C, 0xDE, 0x28, 0xEB, 0x1A, 0xE2, 0x20, 0x80, 0xB0, 0xDA, 0x7A, 0x5E, 0x9C, 0xDC, 0x95, 0x1C, 0x7B, 0xD7, 0xFA, 0x4B, 0xE9, 0x0C, 0xE6, 0x69, 0x72, 0xA6, 0x61, 0x08, 0xCF, 0x89, 0xE6, 0x25, 0xEE, 0x4C, 0x2C, 0x0D, 0x88, 0x71, 0x63, 0x72, 0x25, 0x2E, 0xB7, 0x6A, 0x55, 0x48, 0x22, 0x41, 0xA6, 0x1D, 0x0F, 0x98, 0x8C, 0xA3, 0x04, 0x08, 0x73, 0x87, 0x77, 0x8F, 0xA0, 0xA5, 0x33, 0xC9, 0xB6, 0x32, 0x3D, 0xE3, 0xD9, 0x41, 0x1D, 0x4B, 0x98, 0x29, 0x59, 0xEF, 0x1F, 0xC6, 0x87, 0xC5, 0x9F, 0x40, 0xD4, 0xB2, 0x47, 0x85, 0x03, 0x7F, 0x45, 0x1F, 0x9B, 0x65, 0x12, 0x19, 0xEB, 0xDD, 0xFA, 0x85, 0x5F, 0x39, 0x44, 0x66, 0x00, 0x29, 0x98, 0x2C, 0x60, 0xD2, 0x41, 0x38, 0x41, 0xA8, 0x17, 0xD3, 0x05, 0xC6, 0xB3, 0xBD, 0x8F, 0x38, 0x82, 0x0F, 0x18, 0xDC, 0xF9, 0xB2, 0xF1, 0xE2, 0x81, 0xA8, 0x61, 0x07, 0x37, 0x92, 0x5C, 0x85, 0xA8, 0xAE, 0x97, 0x76, 0xD5, 0xB8, 0x71, 0x3F, 0xF4, 0x17, 0x8B, 0x78, 0x5B, 0xBE, 0xD9, 0x7D, 0x63, 0xFD, 0xFD, 0xD8, 0xCA, 0x88, 0x00, 0x10, 0xA3, 0x9D, 0xF1, 0x42, 0xEE, 0x19, 0xFE, 0x2D, 0xB8, 0x73, 0x48, 0x48, 0xC9, 0x77, 0xD2, 0x67, 0xA7, 0xDB, 0xD1, 0x7C, 0xAC, 0x39, 0xD8, 0x44, 0xB6, 0x63, 0x19, 0x4C, 0xE6, 0x46, 0xEA, 0x36, 0x78, 0xDC, 0x9F, 0x58, 0x21, 0xF3, 0xCB, 0x3E, 0xD8, 0xCB, 0x94, 0x10, 0x3E, 0x51, 0xC9, 0x76, 0x71, 0x0D, 0xAF, 0xB5, 0x91, 0x42, 0x44, 0x99, 0xE8, 0x68, 0x41, 0x70, 0x37, 0x47, 0x0A, 0xFE, 0x9C, 0x4F, 0x36, 0x2B, 0x85, 0xAA, 0xFD, 0x49, 0xC5, 0xFB, 0x65, 0x2B, 0x31, 0x9A, 0x3D, 0x3E, 0xE7, 0x9E, 0xF5, 0x09, 0x72, 0xB5, 0xD1, 0xC2, 0xE2, 0x1B, 0x21, 0x9A, 0xDE, 0x5A, 0x2F, 0x96, 0xCC, 0x74, 0x10, 0xDE, 0x7C, 0x7D, 0xC9, 0xB8, 0x4D, 0xF1, 0x1D, 0x6C, 0xB4, 0x56, 0x29, 0x3E, 0x58, 0x7E, 0x6D, 0xDF, 0x7E, 0x7D, 0x53, 0xD2, 0xAD, 0x76, 0x40, 0x8D, 0xAC, 0xF4, 0x6B, 0x9F, 0x82, 0x0E, 0xCD, 0xAE, 0x1E, 0x89, 0x94, 0x8B, 0x2F, 0xD2, 0x7B, 0x89, 0xAE, 0x3C, 0xC7, 0x1B, 0x74, 0x85, 0xA7, 0x2E, 0x7B, 0x93, 0x17, 0xE9, 0xF0, 0xCD, 0x6D, 0x93, 0x3D, 0x1C, 0xA0, 0x7B, 0xCA, 0xA5, 0x18, 0x0B, 0x39, 0xE9, 0x39, 0x3D, 0x11, 0xFB, 0x03, 0xA5, 0xD1, 0x34, 0xBA, 0x25, 0x2D, 0xF3, 0xE3, 0x23, 0x82, 0x5A, 0x9D, 0x0A, 0x57, 0x60, 0x76, 0x8D, 0x7E, 0xA5, 0x5C, 0x9B, 0x64, 0xBD, 0x82, 0x97, 0x6A, 0x7C, 0x0B, 0xC5, 0xA4, 0xF9, 0x87, 0x70, 0xCC, 0x3A, 0xFE, 0x6A, 0x2B, 0x11, 0x5D, 0x5E, 0xF2, 0x7F, 0x85, 0xDC, 0x54, 0x37, 0xF7, 0x1E, 0x34, 0xC8, 0x0E, 0xC5, 0x3C, 0x81, 0x82, 0xAE, 0x4B, 0xA7, 0xCB, 0x34, 0x19, 0xF0, 0xCD, 0xD5, 0x29, 0x1C, 0x6F, 0xF6, 0x74, 0xE1, 0xF3, 0x51, 0x36, 0x6D, 0x26, 0xFF, 0xB6, 0xC6, 0xCD, 0xCE, 0x71, 0x15, 0xC0, 0x8A, 0xCC, 0xD0, 0xAF, 0x61, 0x97, 0x7B, 0xB0, 0xB8, 0x27, 0x56, 0x20, 0x16, 0x06, 0x94, 0xFF, 0xF1, 0x99, 0x57, 0xB4, 0xCA, 0xB9, 0x9B, 0x83, 0x7C, 0x9B, 0x57, 0xDB, 0xB7, 0x74, 0xC1, 0x66, 0x61, 0xD3, 0xCE, 0x3B, 0xBF, 0xD3, 0x0D, 0xF3, 0xB3, 0x3B, 0xC8, 0x40, 0xE0, 0xCB, 0xED, 0x9D, 0xF0, 0x9B, 0x31, 0xC6, 0xCB, 0x8F, 0xBB, 0xFB, 0xF3, 0x41, 0xF5, 0x35, 0xFA, 0xBC, 0xAA, 0xD6, 0x2B, 0x68, 0x47, 0x82, 0x38, 0xBB, 0x4F, 0xC6, 0x79, 0xD9, 0x9D, 0x46, 0xE2, 0x20, 0xF4, 0xA8, 0x6A, 0x0A, 0x46, 0xCA, 0x4F, 0x29, 0x68, 0xF3, 0x9E, 0xDB, 0x7C, 0x01, 0x1A, 0xBA, 0xCB, 0x16, 0xC1, 0x6C, 0x7A, 0x83, 0xDE, 0xE3, 0x20, 0xEA, 0xF8, 0xC7, 0x7B, 0x28, 0x39, 0x20, 0xFB, 0xEC, 0xED, 0xAB, 0xB0, 0x1F, 0x06, 0xE2, 0x93, 0xFE, 0x7A, 0x9D, 0x2C, 0x79, 0xF6, 0xE8, 0xAF, 0xFF, 0xD3, 0x3A, 0xAF, 0xA3, 0xE7, 0x47, 0xA4, 0x62, 0xAF, 0xCD, 0x1E, 0xB0, 0x62, 0x4C, 0xBA, 0xB9, 0x0A, 0x98, 0xDC, 0x95, 0x94, 0xEB, 0x5F, 0x55, 0xA6, 0xE9, 0xD2, 0xF1, 0xEA, 0xD5, 0x2F, 0x84, 0x95, 0xAD, 0x50, 0xDF, 0x31, 0x16, 0x8A, 0xA3, 0x14, 0x4B, 0x9A, 0x49, 0x92, 0xE0, 0x1A, 0x09, 0x64, 0x27, 0x4E, 0x56, 0xEF, 0x15, 0x12, 0x3F, 0x41, 0x7E, 0xB7, 0x79, 0x26, 0xD0, 0xB3, 0x07, 0xF6, 0x5A, 0x36, 0x94, 0xDF, 0x88, 0x0A, 0x4F, 0x54, 0x70, 0xCE, 0x30, 0x22, 0x01, 0x6D, 0x49, 0xCC, 0x19, 0xB1, 0xFA, 0x1A, 0x9E, 0x78, 0xDA, 0x2E, 0x14, 0xC5, 0xED, 0xC9, 0x05, 0x8C, 0x62, 0xD2, 0x3D, 0x99, 0x16, 0x2C, 0x5A, 0x59, 0x17, 0xDC, 0x0B, 0xC7, 0x06, 0x1B, 0x89, 0xDA, 0x51, 0x0D, 0xAA, 0x95, 0x3D, 0x8E, 0x38, 0xBC, 0x34, 0xA6, 0xCB, 0x6A, 0x9A, 0x71, 0x80, 0xD8, 0x8A, 0xE1, 0x79, 0x03, 0x97, 0x9F, 0xCC, 0xFF, 0xE2, 0xB3, 0xC2, 0x87, 0x57, 0xDA, 0x8A, 0xAD, 0x15, 0x2A, 0x8D, 0x05, 0xA9, 0xBA, 0x7A, 0x50, 0x48, 0x7E, 0x02, 0x43, 0x34, 0xC6, 0xE1, 0xB3, 0x02, 0x8C, 0x8B, 0xB2, 0x18, 0x22, 0xDD, 0xDC, 0x8C, 0xA4, 0x56, 0xAA, 0x7E, 0x20, 0xDA, 0x38, 0x49, 0xFC, 0x2F, 0x91, 0x3F, 0x29, 0x48, 0xEA, 0x56, 0xEF, 0x26, 0x15, 0x5F, 0xFF, 0x30, 0x49, 0x72, 0x8C, 0x8E, 0x39, 0x6D, 0x64, 0x5A, 0x83, 0x69, 0x46, 0x90, 0x88, 0xBA, 0x06, 0x58, 0x02, 0xE5, 0x36, 0xF6, 0xE4, 0x58, 0x97, 0xF2, 0xA8, 0xB0, 0x42, 0x7B, 0xFA, 0x80, 0xEE, 0x14, 0xC5, 0x83, 0x1A, 0xE5, 0x9A, 0xB0, 0x64, 0x50, 0xB8, 0xE4, 0xBC, 0xAA, 0xA7, 0xBF, 0xD5, 0x24, 0x44, 0x77, 0xAE, 0x4D, 0x09, 0x8D, 0xE0, 0x8E, 0xD2, 0xAD, 0x6B, 0xC7, 0x4E, 0xBE, 0x40, 0xF1, 0xB6, 0xCA, 0x76, 0x57, 0xEF, 0x06, 0x2C, 0x05, 0x0B, 0x01, 0x9A, 0x3F, 0x52, 0x9A, 0xA0, 0xB3, 0x9B, 0x0D, 0xAD, 0x0C, 0xD2, 0x95, 0x3D, 0x5D, 0xAA, 0x7E, 0x40, 0x3B, 0xCA, 0x4D, 0x85, 0x28, 0x75, 0x97, 0x53, 0x9D, 0x1D, 0x6C, 0xE2, 0xEA, 0x01, 0xE0, 0x61, 0x52, 0xF0, 0xB6, 0xAC, 0x7D, 0x81, 0xE5, 0x78, 0x27, 0xC4, 0xC2, 0x9F, 0xB4, 0x13, 0x21, 0x87, 0xC8, 0xEF, 0x88, 0xCB, 0xB5, 0x72, 0x6B, 0x11, 0xFA, 0xA3, 0xA7, 0x96, 0x76, 0x92, 0x8B, 0x7E, 0x47, 0x6A, 0x7E, 0x64, 0x70, 0x1A, 0xE5, 0x1D, 0x69, 0x76, 0x73, 0x7D, 0x58, 0x07, 0x27, 0x9C, 0x33, 0x5A, 0xDA, 0xD5, 0xBA, 0xD5, 0xBA, 0x79, 0x45, 0x0E, 0x20, 0x27, 0xD1, 0xEA, 0xE4, 0x24, 0x69, 0x8E, 0x52, 0xCF, 0xF4, 0x1C, 0xEF, 0xFE, 0x54, 0x48, 0xC0, 0x01, 0x8D, 0xA3, 0xF5, 0x3B, 0x7C, 0x1D, 0xB2, 0x18, 0x17, 0x15, 0x70, 0x38, 0xF4, 0x7D, 0xE9, 0xCE, 0xEB, 0xC0, 0xD0, 0x03, 0x59, 0xE8, 0x65, 0x03, 0x8F, 0xB0, 0x43, 0xCF, 0xC3, 0x42, 0x59, 0xE7, 0x24, 0x17, 0x42, 0x30, 0x55, 0xCB, 0xCA, 0xA6, 0x44, 0x10, 0xE5, 0x71, 0x8E, 0x34, 0x22, 0xF7, 0x64, 0x23, 0x3C, 0xE0, 0xC8, 0x2D, 0xC5, 0xCA, 0x21, 0x0A, 0x43, 0xD1, 0x8B, 0x40, 0x20, 0x3E, 0x6B, 0xBB, 0xC0, 0x1C, 0xC1, 0xF0, 0x02, 0x13, 0x4E, 0xF8, 0xEF, 0x9A, 0xA3, 0x63, 0x8B, 0x0A, 0xD6, 0xCB, 0x68, 0xB2, 0xC6, 0x13, 0xC2, 0x95, 0xF9, 0x80, 0x7A, 0x5C, 0x3B, 0xDD, 0x50, 0xDE, 0xCD, 0x57, 0x14, 0x4E, 0xB0, 0x34, 0x83, 0x89, 0xF5, 0xC5, 0xCF, 0x2A, 0x3F, 0x56, 0x4F, 0xE2, 0x8A, 0x54, 0x77, 0x55, 0x6B, 0x27, 0xC7, 0xFA, 0x04, 0x95, 0x8F, 0xF9, 0x47, 0x0A, 0xA0, 0x88, 0xD4, 0x46, 0x2A, 0x67, 0xAA, 0xC2, 0xEF, 0x2A, 0xC4, 0xCE, 0x7F, 0xA0, 0xB1, 0x52, 0x33, 0x4B, 0x7C, 0x7E, 0x1C, 0x17, 0xFB, 0xCC, 0xE1, 0x92, 0x53, 0xB3, 0x9F, 0x02, 0xA0, 0xF2, 0x7B, 0x89, 0x7C, 0x11, 0xC0, 0x93, 0xBB, 0x8C, 0x3F, 0x80, 0x5E, 0x24, 0xCB, 0xDA, 0xD0, 0xAF, 0xBA, 0xB8, 0x90, 0xFD, 0x8F, 0xAA, 0xF2, 0x36, 0x5A, 0x14, 0xD1, 0xEB, 0x7F, 0xA9, 0x21, 0x7F, 0x6E, 0xAB, 0xB4, 0x70, 0xCF, 0x21, 0x10, 0x78, 0x04, 0xE7, 0x32, 0xAA, 0x60, 0x1B, 0xEC, 0xF0, 0x1C, 0x07, 0xE6, 0xAF, 0x42, 0xBD, 0x8B, 0x2A, 0xA7, 0x3C, 0xB2, 0xFD, 0xE7, 0xCF, 0x8F, 0x19, 0x3D, 0x09, 0x8B, 0x9A, 0x98, 0xF7, 0x38, 0x42, 0x59, 0x98, 0x56, 0x3D, 0xD1, 0xCB, 0x9C, 0x6C, 0x2F, 0x45, 0xF6, 0x0A, 0x4E, 0x92, 0x89, 0xD3, 0xAF, 0x6E, 0xBF, 0x1C, 0xB4, 0xC1, 0x5B, 0x41, 0x21, 0x6D, 0xD4, 0x95, 0x17, 0x8B, 0xDB, 0x20, 0xA1, 0x16, 0x76, 0xFA, 0xFF, 0xB7, 0xEC, 0x1D, 0x70, 0x04, 0x45, 0xFD, 0x18, 0xD4, 0x7D, 0x9A, 0x17, 0x55, 0x01, 0x0A, 0xE3, 0x1B, 0xDB, 0x8C, 0x5F, 0x3F, 0x48, 0x18, 0x99, 0x08, 0x93, 0xA6, 0xAD, 0xED, 0x48, 0xDA, 0xFE, 0x8F, 0xF2, 0xEF, 0x4C, 0x8E, 0xFE, 0x6C, 0xC2, 0x46, 0xE3, 0xEC, 0x73, 0xD9, 0x77, 0x83, 0x6F, 0xE9, 0x5C, 0xA6, 0xB6, 0x60, 0x10, 0xD8, 0x03, 0x0A, 0xD0, 0xC7, 0xB4, 0x7D, 0xFE, 0x85, 0xDA, 0xE1, 0xF3, 0x85, 0xB4, 0xC3, 0x67, 0xAA, 0xFB, 0x6C, 0x84, 0xB8, 0x7E, 0xDA, 0x13, 0x95, 0x18, 0x2A, 0x0E, 0xF1, 0xAC, 0x3F, 0x93, 0x50, 0x20, 0xF6, 0x84, 0x0C, 0xAE, 0x0A, 0x6C, 0x42, 0xDE, 0xA5, 0x97, 0x9C, 0x74, 0xEC, 0xED, 0xBE, 0x11, 0xE2, 0xBE, 0x57, 0x02, 0x3E, 0xDB, 0xD1, 0x1C, 0xC4, 0x4B, 0xB7, 0x4B, 0xA7, 0xB9, 0x74, 0xC8, 0xC2, 0x6C, 0x39, 0x79, 0x6B, 0x17, 0x21, 0xCB, 0xDB, 0xB3, 0x71, 0xB1, 0xD2, 0xA4, 0x06, 0x11, 0x98, 0x6D, 0xE8, 0x85, 0x30, 0x1A, 0x46, 0x47, 0x7D, 0x87, 0x7D, 0x03, 0xBD, 0x64, 0xCD, 0x7C, 0x5C, 0x54, 0xBB, 0xE3, 0x2A, 0x3C, 0x4C, 0xB0, 0xAF, 0x6A, 0xBF, 0x18, 0x40, 0xC4, 0x3A, 0x9E, 0x84, 0x01, 0x56, 0xB0, 0xF5, 0xD4, 0xE0, 0xD2, 0x95, 0x7E, 0x85, 0x34, 0xDF, 0xC3, 0xAB, 0xFE, 0x5C, 0xA6, 0x8C, 0xA5, 0x96, 0xAB, 0x51, 0x53, 0xEE, 0x80, 0x18, 0x8A, 0x87, 0x61, 0x91, 0x3E, 0x93, 0x01, 0x85, 0xE8, 0x85, 0x84, 0x7A, 0x12, 0x29, 0x58, 0xFF, 0xE7, 0xE0, 0x3D, 0x97, 0x1C, 0x94, 0x29, 0xFB, 0xB2, 0x1B, 0xEB, 0xD1, 0x20, 0xAA, 0x52, 0x12, 0xA0, 0x0B, 0xC5, 0xB4, 0xBF, 0x4C, 0xFB, 0xBF, 0x98, 0xCF, 0xD9, 0xE7, 0x96, 0xEE, 0xD2, 0x26, 0x0B, 0x04, 0x0E, 0xE5, 0x37, 0xA5, 0xF7, 0x36, 0x35, 0x97, 0xFA, 0x35, 0xD2, 0xC0, 0xA2, 0x5B, 0x2B, 0x25, 0x29, 0xF0, 0x18, 0x3B, 0xE6, 0x0C, 0xE4, 0x52, 0x7F, 0x48, 0x15, 0xD0, 0x68, 0x3D, 0x6F, 0x68, 0x32, 0xB8, 0xF1, 0xCD, 0x96, 0x6D, 0x48, 0x33, 0xEA, 0x57, 0x84, 0xCE, 0x1C, 0x93, 0x22, 0x8E, 0xC0, 0x6E, 0x76, 0x0D, 0x87, 0x2E, 0x36, 0xCE, 0x2C, 0x65, 0xBD, 0xA9, 0xBF, 0xB4, 0x88, 0xAB, 0x83, 0x74, 0x15, 0xC4, 0xDF, 0xA7, 0x4B, 0x7D, 0xB3, 0xB8, 0x72, 0x8C, 0x57, 0x9C, 0xE8, 0xBB, 0xB3, 0x1D, 0xF1, 0xE4, 0x11, 0x64, 0xEA, 0xB4, 0x17, 0xCF, 0x1F, 0xBF, 0x1D, 0xC0, 0xDE, 0x70, 0xF8, 0xAF, 0x39, 0xFF, 0x30, 0x41, 0x7F, 0x5F, 0x21, 0x06, 0x09, 0x08, 0x95, 0x1E, 0x6D, 0x47, 0x01, 0xF5, 0xA0, 0xD5, 0xF7, 0xA9, 0xF3, 0xB4, 0x63, 0x8D, 0xB3, 0xBC, 0x0F, 0xC8, 0x83, 0xAD, 0x22, 0x31, 0x74, 0x1C, 0xD7, 0xAB, 0x23, 0x74, 0x89, 0xBF, 0x3D, 0xBD, 0xF5, 0x47, 0xAB, 0x82, 0x75, 0x81, 0x3D, 0x8C, 0xC9, 0x6E, 0x6F, 0x36, 0x45, 0x9E, 0x36, 0x1D, 0xE2, 0xF8, 0x92, 0x27, 0xAB, 0x4B, 0x5B, 0xDD, 0xC2, 0x55, 0x3A, 0x1C, 0x3E, 0xE4, 0xFA, 0xEF, 0xE5, 0xCB, 0xE6, 0x6A, 0xB3, 0xF8, 0x6E, 0x9C, 0x65, 0x08, 0x92, 0x78, 0xF9, 0x27, 0x34, 0x40, 0xDE, 0xF3, 0xA3, 0xDC, 0x2B, 0xD8, 0x68, 0x15, 0x61, 0xF1, 0x1E, 0xBA, 0xA4, 0x7A, 0x74, 0x13, 0x48, 0x84, 0xA5, 0x79, 0x4F, 0xDE, 0xBD, 0xFE, 0xF1, 0xBE, 0xB1, 0x1B, 0xE9, 0x85, 0x7B, 0x5D, 0xFA, 0x34, 0x2D, 0x53, 0xA7, 0xEE, 0x71, 0x9D, 0xC8, 0xF6, 0xFA, 0xB1, 0x5D, 0xAD, 0x06, 0x76, 0x2C, 0xE5, 0x04, 0x3F, 0x0F, 0x5F, 0x19, 0xD6, 0x56, 0x7B, 0xAB, 0xFB, 0x5E, 0xFB, 0x2B, 0x0A, 0x52, 0xAB, 0x5B, 0xBB, 0x71, 0x69, 0x7C, 0xB4, 0x57, 0x29, 0x4D, 0x08, 0x86, 0x2B, 0x9E, 0xFE, 0xAC, 0x9F, 0x73, 0xBE, 0x93, 0x23, 0x2A, 0xC4, 0x7B, 0xEE, 0xF2, 0x9A, 0x71, 0xC1, 0x65, 0x14, 0xD6, 0xDC, 0x8A, 0x7E, 0x32, 0x28, 0x68, 0xB7, 0xD0, 0x0E, 0xE7, 0x29, 0xBC, 0xD4, 0x54, 0x60, 0xB2, 0xC4, 0x7B, 0x3A, 0x92, 0x1B, 0xF0, 0x61, 0xDB, 0x01, 0x13, 0xE7, 0xF2, 0x9A, 0xFE, 0xB8, 0x8E, 0x3A, 0xC8, 0x09, 0x9A, 0x3E, 0x70, 0x05, 0x60, 0x81, 0x64, 0xDF, 0x84, 0xEF, 0x29, 0x92, 0xEF, 0xA4, 0x32, 0x53, 0x27, 0x52, 0xE2, 0x7B, 0x81, 0x0B, 0x0A, 0x83, 0xE8, 0x73, 0xF2, 0x36, 0xC7, 0xA4, 0xDC, 0xBF, 0xB6, 0xBB, 0x60, 0x61, 0x7B, 0xD0, 0x3B, 0xA8, 0xB1, 0x5A, 0x0C, 0x46, 0xEB, 0x68, 0x68, 0x1F, 0xA9, 0x70, 0xA9, 0x5A, 0xBB, 0xAF, 0xDD, 0xFD, 0xEC, 0xEB, 0xB8, 0x75, 0x8D, 0x2A, 0x82, 0xD2, 0xF0, 0xA6, 0x75, 0xB7, 0xEF, 0x33, 0x5D, 0x6B, 0xA2, 0x6A, 0x6A, 0xA1, 0x22, 0x7A, 0x08, 0xA5, 0x7B, 0xAD, 0x52, 0x5E, 0xB6, 0x0E, 0x5F, 0xF6, 0xD6, 0xB9, 0xD5, 0x30, 0x4C, 0xFB, 0xF1, 0x80, 0xA2, 0x24, 0x4A, 0xA0, 0x9E, 0x61, 0xAB, 0xC2, 0xD3, 0x99, 0x4C, 0xB4, 0xBE, 0xC1, 0x64, 0xD1, 0xF2, 0xF1, 0x61, 0xB8, 0x74, 0x45, 0x0C, 0xAD, 0xD0, 0x38, 0x9A, 0x7B, 0xB7, 0x1F, 0x59, 0x20, 0x46, 0x85, 0x63, 0x8C, 0xA2, 0x25, 0xB4, 0x81, 0x8C, 0x11, 0x13, 0xE7, 0x3A, 0x4A, 0x24, 0x94, 0x6F, 0x34, 0x0E, 0x13, 0x8E, 0x5A, 0xED, 0xC5, 0x4E, 0x9E, 0x2C, 0x2E, 0xEF, 0x4C, 0x00, 0xD6, 0xB4, 0x96, 0xE4, 0x1F, 0xCF, 0x66, 0x4B, 0xA0, 0xC2, 0xD3, 0xF3, 0x18, 0x05, 0xBA, 0xAB, 0x7E, 0x32, 0xF7, 0x54, 0xBA, 0x64, 0xE7, 0xC3, 0x44, 0x95, 0x4C, 0x62, 0xF9, 0x6F, 0x50, 0x27, 0xEB, 0xB1, 0xFE, 0xD8, 0xBD, 0xD8, 0x44, 0x4D, 0x48, 0xC4, 0xBC, 0xB5, 0x4E, 0xF7, 0x2B, 0x3F, 0xC8, 0x15, 0x34, 0xC7, 0x8E, 0x83, 0x91, 0xFD, 0x73, 0xBA, 0x27, 0x4D, 0xA3, 0x4B, 0x4A, 0x3E, 0x13, 0xCE, 0x7B, 0xBF, 0xDD, 0x2F, 0x25, 0x22, 0xEA, 0xC7, 0x24, 0x41, 0x0B, 0x23, 0xFD, 0x1F, 0x2E, 0x57, 0x19, 0xF8, 0xD6, 0x87, 0x3E, 0x3A, 0xE8, 0x57, 0x00, 0xB7, 0xF2, 0xC2, 0xC5, 0x79, 0x5B, 0x4D, 0x7F, 0xE5, 0xF8, 0x10, 0x8D, 0xFC, 0x02, 0xC8, 0x0D, 0xDA, 0x9A, 0x85, 0xF0, 0xBD, 0xA2, 0x8D, 0x5A, 0x6A, 0x94, 0xB6, 0x96, 0x2B, 0xD4, 0x4E, 0x77, 0x2F, 0x4F, 0x94, 0xAB, 0xD1, 0x4F, 0xDD, 0xA7, 0x53, 0x65, 0xB1, 0x6B, 0x95, 0x4C, 0x3F, 0x41, 0x50, 0x62, 0x49, 0x54, 0x2D, 0x1D, 0x4A, 0xE5, 0x45, 0x19, 0xBF, 0x37, 0xE8, 0x25, 0xE1, 0xF6, 0x77, 0x2B, 0x59, 0x17, 0x72, 0x58, 0xE6, 0x42, 0x39, 0x9F, 0x8D, 0xB1, 0x62, 0x42, 0xE1, 0x31, 0x4C, 0x2F, 0x00, 0xCB, 0x1F, 0xFB, 0x30, 0x1C, 0x8A, 0x4C, 0x2A, 0xF9, 0x6E, 0x63, 0x90, 0x2B, 0x41, 0xC5, 0x3C, 0x87, 0xB6, 0x3F, 0x83, 0xC8, 0x16, 0x9D, 0x9E, 0x15, 0x8D, 0x8F, 0xB7, 0x75, 0xB7, 0x1E, 0x8B, 0xA5, 0x08, 0xBE, 0x26, 0x74, 0xF0, 0x8D, 0x10, 0x0C, 0x4C, 0x2E, 0x33, 0x79, 0x85, 0x30, 0xF2, 0xC3, 0xD1, 0x3F, 0x11, 0x4A, 0x02, 0x35, 0x33, 0x67, 0xAE, 0x38, 0x01, 0x9A, 0x88, 0xC9, 0x08, 0x50, 0x9C, 0x70, 0x66, 0x4B, 0xB8, 0xF7, 0x0D, 0x83, 0xA8, 0x0C, 0x04, 0xD2, 0xFD, 0x7E, 0x77, 0xAE, 0xCD, 0xAC, 0x47, 0xBD, 0x6E, 0x53, 0x5D, 0xC5, 0xB9, 0xB3, 0x1A, 0x66, 0x7D, 0x27, 0xE4, 0x14, 0xA3, 0xF2, 0xF2, 0x98, 0xD3, 0x37, 0x70, 0xD1, 0xDE, 0x27, 0x72, 0x2B, 0x2A, 0xA6, 0xB1, 0x1B, 0x50, 0x8F, 0x82, 0xB1, 0x86, 0x26, 0x0A, 0x30, 0x76, 0xA3, 0x82, 0x89, 0x22, 0x48, 0x5E, 0xA2, 0xCF, 0x3B, 0xC7, 0x1E, 0x0D, 0x41, 0x10, 0x6F, 0xF2, 0x53, 0x17, 0x42, 0x7F, 0x83, 0xB3, 0xC9, 0x39, 0x6B, 0x69, 0x18, 0xF3, 0x41, 0x5A, 0x97, 0xD9, 0x54, 0x1A, 0x70, 0x5F, 0x8C, 0x94, 0xB0, 0x83, 0x32, 0xF4, 0x5B, 0x3A, 0x19, 0xD1, 0x8E, 0xF1, 0x3B, 0x4B, 0x07, 0x58, 0x16, 0x0F, 0x2F, 0xB6, 0x55, 0xDC, 0xCD, 0x76, 0xDC, 0xF7, 0x0B, 0xE5, 0xAC, 0x55, 0x03, 0x84, 0xAE, 0xD0, 0xEE, 0x9D, 0x74, 0x92, 0xA1, 0xA3, 0x97, 0x3B, 0x22, 0x82, 0xDF, 0x93, 0x26, 0x2A, 0x2E, 0xD2, 0xF9, 0xF3, 0x24, 0x5F, 0x6B, 0x13, 0x50, 0x54, 0x7C, 0x75, 0xF4, 0x34, 0x2C, 0x56, 0xA7, 0x03, 0x8E, 0xAA, 0xBE, 0xA5, 0x0C, 0xD4, 0xAA, 0x15, 0x8C, 0xEC, 0x1E, 0xB1, 0xCA, 0x67, 0x8D, 0x91, 0x0C, 0xB3, 0x09, 0xC4, 0xE5, 0xBE, 0x9F, 0xFA, 0x35, 0x21, 0x0C, 0x10, 0xDD, 0x5A, 0xB1, 0x40, 0xE9, 0xC1, 0x53, 0xD1, 0x84, 0x13, 0x7F, 0x36, 0x49, 0xEC, 0xAF, 0x63, 0x46, 0x72, 0x13, 0xD0, 0x35, 0x90, 0xFB, 0xF9, 0x95, 0xCB, 0xF3, 0xB1, 0x24, 0x3C, 0x3A, 0xC2, 0x20, 0x89, 0x27, 0xCB, 0x28, 0x92, 0xCB, 0x1F, 0x6F, 0x0F, 0x05, 0x55, 0x8A, 0x50, 0xE9, 0x9E, 0x54, 0x69, 0x8A, 0x74, 0xC2, 0x98, 0x01, 0x30, 0x7B, 0x25, 0x61, 0xF3, 0xF5, 0x60, 0xFF, 0x1A, 0xFA, 0x3E, 0x11, 0xDB, 0xCC, 0x9D, 0x17, 0xCC, 0x42, 0xED, 0x10, 0xE2, 0x23, 0x0B, 0x7B, 0x9E, 0x9B, 0x7A, 0x52, 0xD5, 0xFE, 0x51, 0xD3, 0x2E, 0x14, 0xF5, 0x58, 0xF6, 0x9E, 0x0D, 0x2B, 0xA6, 0x4F, 0x51, 0xB2, 0xE4, 0xC7, 0x96, 0x83, 0x26, 0xC4, 0xD9, 0x86, 0xF8, 0x7B, 0x65, 0xA2, 0x93, 0xF1, 0xEB, 0x00, 0xBA, 0x20, 0x23, 0x9C, 0x59, 0xBC, 0x79, 0x95, 0xFF, 0xA7, 0xD9, 0xAF, 0x3C, 0xF7, 0xBF, 0x63, 0x15, 0xA1, 0xDD, 0xFA, 0x41, 0x39, 0xE4, 0xAA, 0xBA, 0xB5, 0x9E, 0x14, 0x86, 0x9D, 0x05, 0x56, 0x81, 0x6A, 0x22, 0x7C, 0x3D, 0x76, 0x89, 0x06, 0xC4, 0x61, 0xE4, 0x39, 0xF0, 0x4E, 0x66, 0x95, 0x1B, 0x09, 0x0D, 0x74, 0x5C, 0x6C, 0x03, 0x4E, 0xCA, 0xD5, 0x32, 0xD0, 0x03, 0x2F, 0x09, 0xB9, 0x12, 0x32, 0x29, 0x44, 0x38, 0x97, 0x7E, 0xC0, 0x5F, 0x66, 0x74, 0x3B, 0xB9, 0x98, 0x53, 0x94, 0x83, 0x7C, 0xAA, 0xC7, 0xCE, 0x74, 0xC8, 0x80, 0x97, 0xB3, 0x80, 0x97, 0x7A, 0x66, 0xE5, 0xC9, 0x50, 0x20, 0xC9, 0xD1, 0x84, 0xDC, 0xDC, 0x5C, 0x07, 0x43, 0x60, 0x97, 0xD9, 0xA5, 0x6E, 0x8A, 0x7B, 0x98, 0xDB, 0x1C, 0x75, 0xE5, 0x58, 0x7F, 0x61, 0x68, 0xFC, 0xEE, 0x32, 0x58, 0x6C, 0xF7, 0xA9, 0x0B, 0x56, 0x41, 0x2C, 0x65, 0x16, 0xBD, 0xE1, 0xD6, 0xC1, 0xA0, 0xE2, 0x10, 0x61, 0xDB, 0xC2, 0x8D, 0x5A, 0x9A, 0x70, 0x95, 0xD8, 0x72, 0xB0, 0x41, 0x88, 0x40, 0xFD, 0x7D, 0xDE, 0x9E, 0x07, 0x79, 0xBA, 0xFD, 0x9E, 0xF9, 0x67, 0xB7, 0x07, 0x13, 0x27, 0x13, 0xDC, 0xD9, 0x5A, 0xAD, 0x0D, 0x42, 0xA9, 0x7B, 0x52, 0x17, 0x85, 0x49, 0x23, 0xE0, 0x10, 0x7D, 0xB3, 0xF5, 0x36, 0x21, 0x2F, 0x01, 0xE9, 0x7B, 0x32, 0x75, 0x3C, 0xEC, 0x83, 0x4C, 0xA3, 0x56, 0xD6, 0x4F, 0x84, 0xE2, 0xF5, 0xF9, 0x7D, 0x0D, 0x12, 0x0A, 0x7D, 0xA2, 0xAA, 0x19, 0xF4, 0x53, 0x4C, 0x2F, 0x4A, 0xB4, 0x03, 0x22, 0x2E, 0xBE, 0x6D, 0x1F, 0x2A, 0xD1, 0x98, 0x81, 0xC1, 0xEF, 0xEB, 0x0E, 0xD6, 0x8B, 0x27, 0x47, 0x73, 0x61, 0x78, 0x3D, 0xBB, 0xE1, 0x92, 0xAA, 0x07, 0xF5, 0x9F, 0x62, 0x51, 0xFB, 0xDC, 0x1C, 0x74, 0x6B, 0x9E, 0x0E, 0xDF, 0xCF, 0xB6, 0x71, 0x8A, 0x5E, 0x8E, 0x83, 0x00, 0xEA, 0x43, 0xF8, 0x48, 0xCF, 0xCA, 0x71, 0x3F, 0x85, 0xB8, 0x00, 0xAE, 0x5D, 0xC1, 0xD9, 0x3B, 0xAE, 0x60, 0xB8, 0x0F, 0xE3, 0xE6, 0xDB, 0x23, 0x10, 0x93, 0x6C, 0x04, 0xE4, 0xFB, 0x36, 0x24, 0xF4, 0xB8, 0xE0, 0xA0, 0xBF, 0x8D, 0xA6, 0x34, 0x46, 0x0B, 0x37, 0x9C, 0x67, 0xE7, 0x5A, 0x65, 0xE7, 0x2D, 0x64, 0x4B, 0x5D, 0x83, 0x86, 0xEC, 0xA2, 0x64, 0x3C, 0xB7, 0x78, 0xEE, 0xD3, 0x29, 0xAF, 0x86, 0x0C, 0xB4, 0xDF, 0x69, 0x8F, 0xAF, 0x33, 0x4B, 0x52, 0x82, 0xCF, 0x2A, 0xB2, 0x5B, 0x5F, 0x18, 0x26, 0xA7, 0x23, 0x1F, 0x8B, 0x3C, 0x90, 0xCD, 0x75, 0x6B, 0x07, 0x4A, 0x1F, 0xF3, 0x60, 0xB6, 0x7D, 0xE0, 0x0A, 0xBD, 0x99, 0x8B, 0xEA, 0x3F, 0xF9, 0x03, 0x10, 0x0B, 0x36, 0xBD, 0xBC, 0x5A, 0xA3, 0x86, 0x37, 0x8F, 0x8D, 0x74, 0x44, 0x39, 0xAB, 0xC1, 0x35, 0x30, 0x92, 0xB3, 0xB5, 0xDA, 0xD4, 0xC6, 0x05, 0x3E, 0xB6, 0xAD, 0x09, 0x19, 0x27, 0x89, 0x25, 0xFB, 0x17, 0x0C, 0xE4, 0x8A, 0xE1, 0x0A, 0xF1, 0xBA, 0x3A, 0x88, 0x6D, 0x0F, 0xF7, 0x8A, 0x9F, 0xE0, 0x96, 0x74, 0x19, 0xFF, 0xC4, 0x09, 0xFB, 0xA3, 0x4F, 0x2B, 0x50, 0xCD, 0x7F, 0x1A, 0x71, 0x17, 0x59, 0x81, 0xC1, 0x1A, 0xA4, 0x0A, 0x50, 0xB0, 0x82, 0x5F, 0x7C, 0x26, 0xD7, 0xEB, 0xBC, 0x91, 0xF2, 0x05, 0xEF, 0xCC, 0x04, 0xED, 0x61, 0xC5, 0x46, 0xF7, 0x0E, 0x0D, 0xBB, 0x3B, 0x60, 0x27, 0x95, 0x40, 0xCD, 0xA8, 0xE1, 0xCA, 0xEA, 0x9F, 0x70, 0x06, 0x23, 0xDA, 0x1D, 0xDB, 0x93, 0x3C, 0x9F, 0x7A, 0x95, 0x34, 0x63, 0xB1, 0xEF, 0x8C, 0xB5, 0x24, 0xD5, 0x0B, 0xA3, 0xBA, 0x64, 0x76, 0x7B, 0x17, 0xD7, 0xE9, 0x19, 0xF4, 0xBE, 0xCA, 0x65, 0x7D, 0x39, 0x54, 0x07, 0x2E, 0xE4, 0xD3, 0x2E, 0x95, 0x32, 0x9F, 0x76, 0x1B, 0x89, 0x69, 0xD3, 0x4C, 0x1E, 0x44, 0xBC, 0x81, 0xCA, 0xB8, 0xD2, 0xDC, 0x6D, 0x46, 0xD3, 0xC9, 0xF8, 0xA2, 0xD4, 0x30, 0xD9, 0xF4, 0x30, 0xA0, 0x36, 0xBE, 0x92, 0x2C, 0x65, 0x6B, 0x7F, 0x7A, 0x6B, 0xB7, 0x4E, 0xF1, 0x69, 0xDD, 0x02, 0x44, 0xCB, 0xB5, 0x36, 0x20, 0x23, 0x21, 0xA4, 0x48, 0x15, 0xA0, 0x53, 0xAE, 0x98, 0x93, 0x6C, 0x74, 0xFA, 0xCD, 0x9C, 0x42, 0x6B, 0x59, 0xC8, 0x3B, 0x2F, 0xDA, 0xFF, 0xEA, 0x6D, 0xBD, 0xF2, 0xBA, 0xA4, 0xFA, 0x8E, 0xCC, 0x23, 0x34, 0x9E, 0x49, 0x78, 0x83, 0x8E, 0x02, 0xB4, 0xEB, 0x13, 0x27, 0x06, 0xB3, 0x27, 0xD3, 0x14, 0x44, 0x2E, 0x55, 0x16, 0x73, 0x7C, 0x03, 0x47, 0x1F, 0x77, 0xC7, 0x6A, 0xF6, 0x79, 0xA9, 0xE9, 0x29, 0xEA, 0x84, 0x9D, 0x57, 0x16, 0xF3, 0x76, 0x9F, 0x6B, 0xD6, 0x5D, 0xB2, 0xC8, 0x46, 0x0C, 0x56, 0x4B, 0x5E, 0x04, 0xA3, 0xA6, 0xBB, 0x37, 0x81, 0x7D, 0xC8, 0x49, 0xF4, 0xC1, 0x0A, 0x64, 0x13, 0x67, 0x1A, 0x5D, 0xB5, 0x16, 0x74, 0xB1, 0x28, 0x12, 0xE1, 0xF6, 0xED, 0x62, 0x23, 0x4B, 0x08, 0x77, 0x7C, 0x30, 0xAF, 0x8D, 0xAA, 0x91, 0xEF, 0xE0, 0x88, 0x4F, 0xA1, 0xB9, 0x44, 0xA8, 0x15, 0x7B, 0xC3, 0xE9, 0xA5, 0x60, 0x11, 0x53, 0x54, 0x18, 0xFF, 0xB2, 0x48, 0x8C, 0xC2, 0x03, 0x9D, 0x0C, 0x82, 0xB7, 0x3D, 0x0B, 0x7D, 0xAA, 0xEA, 0x50, 0x00, 0x53, 0x41, 0xF5, 0xF7, 0xDA, 0xF4, 0x61, 0x5B, 0x8C, 0x89, 0x63, 0x89, 0xFB, 0x97, 0xCA, 0xCE, 0xAE, 0x80, 0xB9, 0x6C, 0x46, 0xE1, 0x31, 0x8A, 0xF9, 0xA9, 0xFE, 0x3C, 0xC5, 0xB0, 0xE3, 0x04, 0xA8, 0x14, 0x06, 0x54, 0x52, 0x3E, 0x9E, 0x5A, 0x47, 0x67, 0x43, 0x2F, 0x09, 0xAF, 0xB0, 0x67, 0x69, 0x87, 0x1F, 0xEF, 0xDF, 0x91, 0xC4, 0x48, 0x21, 0x41, 0xB0, 0xC5, 0x91, 0x8F, 0xE3, 0x48, 0xA7, 0x4E, 0x3A, 0xBF, 0x22, 0x3F, 0xCC, 0xAE, 0x70, 0x94, 0x8A, 0xAB, 0x34, 0xFC, 0x1A, 0x83, 0x8B, 0xAE, 0x1B, 0xA7, 0xC7, 0x99, 0x7C, 0x45, 0x60, 0x6F, 0x81, 0x07, 0xE2, 0x95, 0x7D, 0xE1, 0x55, 0x79, 0xC3, 0xA0, 0x0D, 0xBA, 0x9F, 0x22, 0x5A, 0x44, 0x79, 0x6F, 0x06, 0x57, 0x89, 0x06, 0x97, 0x7F, 0x67, 0x71, 0xBB, 0xEF, 0xFF, 0xE4, 0x3E, 0x9F, 0x63, 0x36, 0xFD, 0x74, 0xDB, 0x90, 0x49, 0x0D, 0xA5, 0xF8, 0xAD, 0x60, 0x78, 0x08, 0x05, 0x8C, 0xFF, 0xF5, 0xD6, 0x5C, 0xEA, 0x1E, 0x7E, 0x3A, 0x7B, 0xDE, 0xD7, 0x7F, 0xE8, 0x66, 0xFD, 0x53, 0x3F, 0x98, 0x2C, 0x2B, 0xBC, 0x5D, 0x36, 0x40, 0xAF, 0xCC, 0xFC, 0x08, 0x00, 0x2D, 0x39, 0xA6, 0x8B, 0x03, 0xFC, 0x3F, 0x94, 0x72, 0x34, 0xC0, 0x7E, 0xB8, 0x68, 0x83, 0xB6, 0x5B, 0x90, 0x15, 0x32, 0x65, 0x7E, 0x36, 0x71, 0x4B, 0x15, 0x54, 0x7E, 0x0D, 0x71, 0x92, 0xB7, 0xF9, 0x35, 0x90, 0x28, 0x89, 0x8D, 0x29, 0x47, 0x78, 0xBF, 0xDC, 0x4F, 0x15, 0x51, 0x23, 0xBF, 0x7F, 0xBB, 0x0A, 0xAB, 0xD6, 0xC3, 0xAF, 0x50, 0xE7, 0x58, 0x8E, 0x31, 0x93, 0x47, 0xAF, 0xF8, 0xCE, 0xC6, 0xA8, 0x8F, 0xC2, 0x89, 0xE9, 0x69, 0x23, 0x5A, 0xDA, 0x11, 0x6F, 0x2A, 0x74, 0x17, 0xE2, 0x9D, 0x53, 0x36, 0x14, 0xE4, 0x6E, 0xCF, 0xC5, 0x26, 0x06, 0x96, 0x3A, 0xC4, 0xED, 0x1A, 0x8B, 0xF6, 0xF4, 0x7E, 0x9D, 0x6D, 0x24, 0x81, 0x23, 0xCA, 0xAB, 0x07, 0xB4, 0x11, 0x8E, 0x14, 0xCE, 0x63, 0x82, 0x6C, 0xF4, 0x64, 0x02, 0xA3, 0x32, 0x81, 0x07, 0x82, 0xBC, 0x1D, 0xB0, 0x04, 0xB4, 0x4C, 0xD8, 0x83, 0x48, 0xFE, 0xA5, 0x89, 0xD4, 0xF6, 0xBD, 0xB6, 0xBF, 0xB6, 0x0D, 0xFE, 0x7B, 0x2B, 0x29, 0x62, 0x91, 0xE6, 0x13, 0xA8, 0x4C, 0xC2, 0xBB, 0x01, 0x27, 0x0C, 0xF4, 0x91, 0xC8, 0xD2, 0x25, 0xF8, 0x2C, 0xE9, 0xE9, 0x25, 0x59, 0x92, 0xB0, 0xEB, 0x19, 0xBF, 0xD1, 0x02, 0x9E, 0x08, 0x44, 0xAB, 0x54, 0xBE, 0x54, 0x1E, 0xBB, 0x17, 0x29, 0xD8, 0xDF, 0x65, 0x79, 0x01, 0x0B, 0x5D, 0x32, 0xB2, 0xA5, 0x32, 0x6E, 0x7B, 0x0F, 0x7E, 0xB5, 0x45, 0x52, 0xD3, 0x12, 0x11, 0xE0, 0xDD, 0x82, 0x24, 0x70, 0xD1, 0x9C, 0x46, 0xBB, 0x42, 0xB0, 0x35, 0x09, 0x5B, 0xAC, 0xC9, 0x99, 0x21, 0x64, 0xF9, 0xD1, 0x0E, 0xCA, 0xB0, 0xB8, 0x21, 0x5B, 0xF0, 0xA0, 0x4D, 0x64, 0x42, 0xCD, 0x00, 0x40, 0xB6, 0xD7, 0x32, 0x0B, 0x49, 0x8C, 0xB1, 0x34, 0x81, 0x97, 0x32, 0xA9, 0xB8, 0x69, 0xC4, 0x45, 0x5E, 0x06, 0xD1, 0x76, 0x6F, 0xB2, 0x81, 0x8F, 0x35, 0xD6, 0xDD, 0x64, 0x3D, 0x38, 0xA3, 0x17, 0x05, 0xAF, 0x57, 0x1C, 0xDD, 0xA1, 0x73, 0x39, 0x6E, 0x1E, 0xFA, 0x8A, 0x5C, 0xEC, 0x92, 0x83, 0xB7, 0xC0, 0x51, 0xA4, 0x13, 0xE2, 0xCA, 0x2F, 0x38, 0x11, 0xFE, 0x81, 0x88, 0x01, 0x06, 0x98, 0x5E, 0xCF, 0xFD, 0xC7, 0x69, 0x1D, 0xE5, 0xEF, 0x86, 0x52, 0x46, 0x74, 0x3F, 0xCF, 0x09, 0xBD, 0xED, 0x03, 0x45, 0x7C, 0x8A, 0xBB, 0x25, 0x2B, 0x4C, 0x0F, 0xE7, 0xC9, 0x04, 0x24, 0x6C, 0xDE, 0xF2, 0x79, 0xDA, 0xC9, 0xAD, 0x2A, 0xF9, 0xDC, 0x6B, 0xAF, 0x54, 0xDC, 0x60, 0x7F, 0x9F, 0x20, 0x65, 0x03, 0x26, 0xAD, 0xE7, 0x94, 0xCB, 0xDA, 0x7E, 0x9A, 0xC5, 0x48, 0xD2, 0x9E, 0x4D, 0x66, 0x0D, 0x0C, 0xEA, 0x90, 0x7A, 0xCB, 0x3F, 0x98, 0x18, 0x51, 0xFD, 0xC8, 0x29, 0x35, 0xF6, 0xCD, 0x7D, 0xDB, 0x28, 0xD2, 0x6D, 0x7F, 0xC9, 0x31, 0x0A, 0x5D, 0x78, 0xE9, 0xFE, 0x2D, 0x66, 0x52, 0x23, 0xC4, 0xD2, 0x1B, 0xFC, 0x6B, 0x50, 0xD1, 0xF3, 0xE5, 0x50, 0xB0, 0x14, 0xDD, 0xFF, 0x8A, 0xAE, 0x09, 0x4D, 0x13, 0x6D, 0xEF, 0xBA, 0xB1, 0x79, 0xD9, 0x0B, 0x21, 0x6D, 0x2C, 0xA0, 0x85, 0x6B, 0x49, 0x6B, 0x87, 0x13, 0x20, 0x03, 0x5F, 0xAC, 0xAF, 0xF1, 0xFC, 0x28, 0x0D, 0x37, 0x97, 0x37, 0xC7, 0x7A, 0x97, 0xBE, 0x50, 0x6E, 0x41, 0xB0, 0xEA, 0xA7, 0xD7, 0x44, 0x23, 0x62, 0xD9, 0xA8, 0x23, 0x12, 0x5D, 0x37, 0xDF, 0xC7, 0x6B, 0x43, 0xE6, 0xA8, 0x50, 0xF5, 0x46, 0x03, 0x53, 0x24, 0x4E, 0x82, 0x84, 0xB5, 0x25, 0xD6, 0x9F, 0x51, 0x96, 0x1C, 0xCE, 0xA1, 0x93, 0x04, 0xAA, 0x25, 0x54, 0xA9, 0x66, 0xF4, 0xBE, 0xBD, 0x9D, 0xF5, 0xEA, 0xE5, 0x24, 0x52, 0x26, 0xDC, 0xBF, 0x50, 0x4B, 0x2C, 0xA2, 0x65, 0x1B, 0x5B, 0x53, 0x9F, 0xFD, 0xA2, 0x81, 0x0C, 0xDD, 0xAF, 0xD1, 0xF6, 0xB8, 0x7D, 0x88, 0xD2, 0xBB, 0x85, 0xD6, 0x58, 0x11, 0x42, 0x57, 0x1E, 0x25, 0x50, 0xD9, 0xD1, 0x0F, 0x9A, 0x54, 0xB8, 0x29, 0x1F, 0x23, 0x99, 0x15, 0xBF, 0xF6, 0xED, 0x47, 0xC8, 0x5F, 0x72, 0xC0, 0x89, 0xD5, 0xD3, 0x00, 0xFD, 0x60, 0x4D, 0x42, 0xFB, 0x61, 0xE6, 0x4D, 0xA1, 0xF8, 0x78, 0x9D, 0xDF, 0x9D, 0x06, 0x36, 0xC4, 0x0A, 0x27, 0x45, 0xB1, 0x6F, 0xA8, 0xE3, 0x94, 0x0E, 0x33, 0x5A, 0x1F, 0xE8, 0x3A, 0x7F, 0xB7, 0x09, 0xC6, 0x91, 0xDF, 0xF3, 0x6E, 0x12, 0x69, 0xE3, 0xCF, 0xDA, 0xF1, 0x12, 0x5A, 0x6B, 0xDE, 0x65, 0x45, 0x0A, 0xD9, 0xE7, 0x24, 0x1C, 0xD4, 0x09, 0x7D, 0x1E, 0x15, 0xBD, 0xB8, 0x44, 0x79, 0xF3, 0x84, 0x5E, 0x51, 0xBF, 0x2C, 0x15, 0xA4, 0x02, 0x23, 0x21, 0x22, 0x0D, 0x2B, 0xA5, 0xB7, 0xFC, 0x3B, 0xE4, 0x7A, 0xAA, 0x0C, 0x8E, 0x5C, 0x55, 0xE2, 0x34, 0xE6, 0x02, 0xD1, 0x7F, 0x3D, 0x25, 0x35, 0x99, 0xC0, 0xDD, 0x57, 0x0C, 0x62, 0x5C, 0xDB, 0x80, 0x06, 0x18, 0x31, 0x3A, 0x0E, 0xCC, 0x4A, 0x3B, 0x4C, 0x8D, 0xD9, 0x0E, 0x98, 0x1C, 0xE8, 0x27, 0x75, 0x53, 0xBC, 0x44, 0xAC, 0x26, 0xCC, 0x62, 0x21, 0x45, 0x5D, 0x24, 0x70, 0xB3, 0x70, 0x82, 0xFA, 0x54, 0xB5, 0x1A, 0x08, 0x82, 0x30, 0x25, 0x01, 0x49, 0x00, 0x76, 0xC6, 0xFB, 0xED, 0x14, 0x23, 0x15, 0x3D, 0x5A, 0x85, 0xB7, 0x88, 0x91, 0x2E, 0x04, 0xB7, 0xCC, 0x62, 0x0A, 0x89, 0x6B, 0xD6, 0x43, 0xBD, 0x82, 0x6F, 0xEB, 0xB1, 0x2E, 0x25, 0x48, 0xC6, 0x3A, 0x47, 0xA2, 0xA1, 0xBA, 0xCC, 0x71, 0x85, 0xD6, 0xBC, 0x4C, 0x51, 0x11, 0x24, 0x32, 0xF3, 0x08, 0x16, 0xD7, 0x67, 0xC9, 0x1E, 0x64, 0xCA, 0x7D, 0x39, 0xAD, 0xF9, 0x05, 0x70, 0x4C, 0x7B, 0x85, 0x50, 0x98, 0x0C, 0x51, 0x1B, 0xB9, 0xA2, 0xD0, 0x1D, 0x6E, 0xA8, 0xF3, 0x8E, 0x28, 0x68, 0x3A, 0xC5, 0x57, 0x9A, 0x88, 0x1B, 0xC5, 0xF9, 0x12, 0x85, 0x52, 0xDA, 0x92, 0x79, 0x05, 0x3F, 0x9E, 0xCD, 0x03, 0xC1, 0xCC, 0xB4, 0x71, 0xE9, 0x41, 0xFD, 0xB1, 0x16, 0xF1, 0xB6, 0x7C, 0x18, 0x38, 0x91, 0x50, 0xF0, 0xA9, 0xA5, 0x6F, 0xA9, 0xF3, 0x09, 0x57, 0x2E, 0xDC, 0xF6, 0x08, 0x6C, 0xC7, 0xF0, 0x7E, 0x68, 0x48, 0x68, 0x3C, 0x66, 0x4A, 0xDA, 0x76, 0xC6, 0xE9, 0x72, 0x61, 0xDF, 0x4D, 0x53, 0x70, 0x17, 0xEC, 0x46, 0xC2, 0x64, 0x04, 0xB1, 0x68, 0x8D, 0x42, 0x0C, 0xFE, 0x6D, 0xCC, 0xFE, 0x2D, 0xA7, 0x19, 0x48, 0xAC, 0x96, 0x97, 0x1D, 0x09, 0x38, 0x7E, 0x7A, 0xE2, 0x84, 0x63, 0xA3, 0xD0, 0xD4, 0x7E, 0x9C, 0x92, 0xE7, 0x3F, 0xFD, 0x01, 0xF7, 0x86, 0x4C, 0xDF, 0x4B, 0x01, 0x5F, 0x93, 0xEA, 0x5D, 0xFB, 0xB2, 0x8E, 0xEB, 0xEA, 0x72, 0x2A, 0xB2, 0xA7, 0x19, 0xD8, 0x8E, 0x80, 0xD4, 0x84, 0x05, 0x8C, 0xD9, 0x2B, 0xCF, 0xCC, 0x36, 0x6C, 0xBD, 0xCD, 0xEF, 0x55, 0x90, 0x38, 0x66, 0xDF, 0x4F, 0x0C, 0x5E, 0x66, 0x75, 0x44, 0x6C, 0xD3, 0xD7, 0x8F, 0xE7, 0xF9, 0x66, 0x13, 0xDE, 0xA0, 0x5F, 0xAE, 0x57, 0x2B, 0x13, 0x8E, 0xAE, 0x56, 0xBB, 0x90, 0xB9, 0xFB, 0x98, 0x11, 0x83, 0x33, 0x2B, 0xF1, 0x65, 0x91, 0x7A, 0x15, 0xFB, 0x74, 0x3C, 0xEF, 0x1F, 0x25, 0x34, 0xB0, 0x07, 0x05, 0x37, 0xD1, 0xF7, 0xF2, 0xC0, 0x8D, 0x47, 0x82, 0xD6, 0x5E, 0x13, 0x08, 0x58, 0x90, 0xF2, 0x3A, 0x88, 0xC0, 0x8F, 0x27, 0xAA, 0x84, 0x03, 0x20, 0x71, 0xE1, 0xF3, 0x34, 0x25, 0xBE, 0xC5, 0x3F, 0x6F, 0x0C, 0x95, 0x36, 0xDB, 0x77, 0x3A, 0xF7, 0xA9, 0x51, 0x24, 0x7E, 0x04, 0xB0, 0x13, 0xD7, 0xC4, 0x29, 0x20, 0x38, 0x42, 0x5B, 0x1E, 0x9A, 0xEB, 0x3C, 0xB1, 0xC9, 0xCF, 0x48, 0x1F, 0xF1, 0x86, 0xD2, 0x6B, 0x83, 0xF9, 0x63, 0x6D, 0xEE, 0xB1, 0x8A, 0x5C, 0x28, 0x69, 0x96, 0x9A, 0x38, 0x53, 0x25, 0xF5, 0xAC, 0x61, 0xD8, 0x0A, 0x60, 0xC8, 0xD5, 0xA6, 0xFB, 0xA6, 0x10, 0x27, 0x55, 0x6C, 0xAE, 0xEA, 0x8E, 0x65, 0x7A, 0x11, 0x94, 0x17, 0x5F, 0x90, 0x3E, 0x28, 0x48, 0x08, 0xC0, 0x35, 0x9B, 0x3E, 0xE8, 0x42, 0xC0, 0x94, 0x9D, 0xDD, 0x83, 0xCD, 0xED, 0xB3, 0x52, 0xD3, 0xE9, 0xD8, 0x89, 0x98, 0xA0, 0xC3, 0xFE, 0x49, 0x17, 0x80, 0x99, 0x56, 0x84, 0xB9, 0x4E, 0x02, 0x4B, 0x27, 0xC6, 0xA8, 0xD8, 0x11, 0xFB, 0x89, 0xE0, 0xB0, 0x5A, 0xA8, 0xC1, 0xC9, 0x0B, 0x04, 0xC5, 0x23, 0xA4, 0x35, 0x5A, 0x5E, 0xB7, 0x24, 0xA6, 0x4E, 0x92, 0x15, 0x00, 0x2B, 0xF9, 0x9F, 0xBC, 0x6F, 0x16, 0xF4, 0xFE, 0xBF, 0xDD, 0x09, 0xB1, 0x36, 0x9D, 0xFF, 0x1F, 0x91, 0x8B, 0xAC, 0xAD, 0x44, 0x19, 0x19, 0xEF, 0xAD, 0x2A, 0xE8, 0xF3, 0x85, 0x5C, 0xDB, 0xF8, 0x27, 0xFD, 0xAC, 0x71, 0xB0, 0x5E, 0x20, 0x53, 0xAE, 0xA4, 0x3C, 0xFE, 0x8A, 0xC0, 0xEB, 0xDB, 0x45, 0xBA, 0x09, 0xCC, 0xA5, 0x0B, 0x02, 0xBE, 0xEC, 0x60, 0x55, 0x76, 0x0D, 0x6F, 0xB7, 0x07, 0xBF, 0xA7, 0xCD, 0x8D, 0xB4, 0x6A, 0x33, 0xF7, 0xCB, 0x2C, 0x9D, 0xA0, 0x14, 0x13, 0xC4, 0x6C, 0xD7, 0xE5, 0x8D, 0xE4, 0x0F, 0x76, 0x22, 0x99, 0x6B, 0x9D, 0xF6, 0xFC, 0x7E, 0x51, 0x90, 0xBF, 0x9D, 0xD2, 0xF1, 0xC8, 0x5B, 0xFE, 0x59, 0x52, 0x5C, 0x41, 0x7F, 0x81, 0xB3, 0xE6, 0xBC, 0x46, 0x0B, 0xB4, 0xFA, 0xC0, 0xA2, 0x1D, 0x4B, 0xE8, 0x2F, 0x01, 0x16, 0x01, 0x72, 0xB4, 0x43, 0x30, 0x22, 0xD5, 0xF2, 0xD8, 0x1B, 0x36, 0xD9, 0x09, 0x49, 0x46, 0x6E, 0xBF, 0x09, 0x93, 0x8C, 0xAB, 0x68, 0xF5, 0xF8, 0xC1, 0xAD, 0xA8, 0x52, 0xD9, 0x11, 0x00, 0x23, 0x1F, 0x25, 0x26, 0xC5, 0x65, 0x06, 0x11, 0xA5, 0xB3, 0xF7, 0x61, 0xCF, 0x71, 0xFA, 0xFD, 0xEC, 0xD6, 0x5A, 0x06, 0xB4, 0xF6, 0xD5, 0x0A, 0xE5, 0x3E, 0xE1, 0xC1, 0xC7, 0xD9, 0xF0, 0xB2, 0xD9, 0xB5, 0xF4, 0x82, 0x09, 0xD1, 0xED, 0x29, 0xA9, 0xF2, 0xD0, 0x52, 0xD5, 0x5C, 0x0A, 0x20, 0xB5, 0x9E, 0xEF, 0xDF, 0x40, 0xB4, 0x91, 0x97, 0xAB, 0x31, 0x3F, 0x72, 0x88, 0xF3, 0xFD, 0x11, 0xD2, 0xDF, 0x29, 0xBC, 0x41, 0x71, 0xF5, 0x1E, 0x55, 0xB8, 0xED, 0xEC, 0x74, 0x0A, 0x22, 0x6E, 0x4B, 0xD9, 0x53, 0xE5, 0x2F, 0xCF, 0x8A, 0xD7, 0xA0, 0x0F, 0x3B, 0x36, 0x17, 0x68, 0xF3, 0x9C, 0x98, 0xA7, 0x4D, 0x21, 0xCE, 0x9A, 0xA0, 0xB2, 0xCA, 0x3F, 0x0D, 0xF5, 0x7F, 0x6B, 0x8C, 0x9E, 0xCC, 0x20, 0xB0, 0x7C, 0xAE, 0x16, 0xC0, 0xBA, 0x09, 0x1B, 0xDD, 0x07, 0x07, 0xF6, 0xC3, 0x8A, 0x0B, 0x38, 0x08, 0xD3, 0xE0, 0x30, 0x46, 0x6E, 0x7C, 0x03, 0x5E, 0x62, 0xFB, 0xCE, 0x1F, 0x85, 0x84, 0xF7, 0xFF, 0xEB, 0xAE, 0x9A, 0xDC, 0x6E, 0xAC, 0xAC, 0x38, 0x9A, 0x5B, 0xCA, 0x7D, 0x6E, 0x40, 0x27, 0x7D, 0x9F, 0x72, 0x38, 0xEE, 0xC6, 0x3D, 0xE7, 0x4F, 0x8A, 0x45, 0xBE, 0x38, 0xE6, 0x06, 0x87, 0x61, 0x0A, 0x1D, 0x0A, 0x4C, 0x57, 0x82, 0x75, 0x40, 0x1A, 0x44, 0x7E, 0x79, 0xF2, 0x13, 0x90, 0xAB, 0xF6, 0xDE, 0x4C, 0xBA, 0x48, 0x34, 0xF1, 0x92, 0x1F, 0xA0, 0xE8, 0x57, 0x68, 0x33, 0xB5, 0x07, 0xCA, 0x95, 0x00, 0xEC, 0xD8, 0xE1, 0x94, 0x44, 0x85, 0xD6, 0x61, 0x6C, 0x32, 0xBB, 0x37, 0x9C, 0x79, 0x6D, 0xBF, 0x8E, 0x54, 0x31, 0xDB, 0x4C, 0xB7, 0x6D, 0x34, 0xC0, 0xEA, 0x94, 0xF0, 0x50, 0xC5, 0xE5, 0xA6, 0x24, 0xDB, 0x72, 0x55, 0x9A, 0x50, 0x23, 0xE9, 0x71, 0xC7, 0x7C, 0x05, 0xAA, 0x03, 0x86, 0x5E, 0x37, 0x65, 0xF9, 0x07, 0x4F, 0x6E, 0x09, 0x9D, 0x11, 0x44, 0x19, 0xAD, 0xAC, 0xC6, 0x66, 0x23, 0x60, 0x3A, 0xBD, 0x12, 0xEA, 0x47, 0x26, 0x11, 0x20, 0xAA, 0x7B, 0xF3, 0xF2, 0xFE, 0xDA, 0x42, 0x77, 0x14, 0xFD, 0x90, 0xC7, 0xD5, 0xF4, 0xB2, 0x9A, 0xD5, 0x15, 0x4E, 0x0C, 0x1E, 0x9B, 0x08, 0x3C, 0xA8, 0x3D, 0xB3, 0x05, 0x8C, 0xA6, 0x90, 0x36, 0x22, 0xCE, 0x3D, 0x48, 0x30, 0xCE, 0xDF, 0xD4, 0xE7, 0x5D, 0x78, 0xA8, 0x0B, 0x7D, 0x4A, 0xFE, 0x0F, 0x48, 0xCE, 0x35, 0xE3, 0xB1, 0x71, 0x9F, 0x06, 0x82, 0xA5, 0xCD, 0xC2, 0x85, 0xF0, 0xBC, 0x82, 0x5C, 0xE0, 0x54, 0x4E, 0x3B, 0x0B, 0x0D, 0x20, 0x1F, 0xD3, 0x1F, 0x3A, 0x7A, 0xD3, 0x11, 0xBE, 0x37, 0x99, 0x5C, 0x93, 0x79, 0x85, 0xC5, 0x69, 0xAC, 0x58, 0x1D, 0x4E, 0x61, 0xAB, 0xB8, 0xDF, 0x5B, 0xB8, 0x81, 0x3A, 0x1A, 0x88, 0xFC, 0x1A, 0x73, 0x67, 0xB5, 0x68, 0x5F, 0xEB, 0x7F, 0x4D, 0xAD, 0xB1, 0xEC, 0x88, 0x2F, 0xB3, 0xC0, 0xB4, 0x5B, 0xBF, 0x01, 0x51, 0x06, 0x0B, 0xF6, 0x1A, 0x80, 0x7A, 0xF1, 0xB9, 0xFB, 0x3B, 0xDE, 0xAA, 0xCA, 0x07, 0x51, 0x04, 0x74, 0x95, 0x08, 0xED, 0x1B, 0xA1, 0xD6, 0x87, 0xBC, 0x4D, 0xCC, 0x71, 0x49, 0x7B, 0x68, 0x39, 0x0B, 0x3D, 0xFF, 0x4C, 0x10, 0xD1, 0x77, 0x1A, 0x6F, 0xAE, 0xA2, 0xA4, 0x6B, 0x83, 0x55, 0xC9, 0x94, 0x2F, 0xC5, 0x5C, 0x74, 0xF5, 0xB1, 0x31, 0x5C, 0x8F, 0xD0, 0x77, 0xCC, 0xCF, 0x62, 0xDD, 0x68, 0xB6, 0xCD, 0x95, 0x1B, 0xA6, 0x3E, 0x61, 0xCC, 0x1F, 0xC0, 0xC7, 0xFC, 0x24, 0xD6, 0x1E, 0xF3, 0x2E, 0x98, 0x79, 0x3D, 0xDF, 0x15, 0x58, 0x62, 0xD1, 0xE6, 0x1F, 0x7F, 0x39, 0x86, 0x1A, 0x39, 0x17, 0xCE, 0x53, 0x27, 0x5A, 0x43, 0x39, 0xDD, 0x88, 0x03, 0xDB, 0xDA, 0x55, 0x5B, 0xB6, 0x9C, 0x20, 0xF5, 0xBD, 0xEF, 0xD6, 0xB2, 0xCC, 0x98, 0xF9, 0xC1, 0xB6, 0x53, 0xCB, 0xE7, 0xDA, 0x0D, 0x1C, 0x58, 0x07, 0xFB, 0x1A, 0xF1, 0x4C, 0x1E, 0x0C, 0xA6, 0x2A, 0x1B, 0x73, 0xE2, 0xAC, 0xE3, 0x16, 0x2D, 0x54, 0x3E, 0x29, 0x6B, 0x8C, 0xB3, 0x78, 0x9A, 0xD6, 0xCE, 0x66, 0xBB, 0x80, 0x5E, 0x48, 0x1B, 0x24, 0xC1, 0x9E, 0x92, 0x9C, 0xAB, 0xB2, 0x08, 0x0F, 0xD8, 0xAD, 0xDA, 0xC5, 0xB9, 0x1E, 0x4A, 0x19, 0xB4, 0xCB, 0x40, 0xB1, 0x7B, 0x10, 0x5B, 0x80, 0x46, 0x06, 0x8A, 0xED, 0xFB, 0xB6, 0x6D, 0xDA, 0xF1, 0x03, 0xAA, 0xDD, 0x9A, 0xC6, 0xFB, 0x26, 0xDE, 0x82, 0x4F, 0x55, 0x54, 0xF8, 0x38, 0xDF, 0xC9, 0xDB, 0x8A, 0x1B, 0x81, 0x15, 0x51, 0x6F, 0x0B, 0xFC, 0xF9, 0x72, 0x71, 0x82, 0xC3, 0x37, 0xC2, 0xFB, 0xDD, 0x61, 0x01, 0xBC, 0xBB, 0x85, 0xAF, 0xC3, 0x36, 0x34, 0xE5, 0xA2, 0xF8, 0x9F, 0xEF, 0x1E, 0xAA, 0xE1, 0xE6, 0x2A, 0xE7, 0xBA, 0x87, 0xB1, 0x8E, 0xE5, 0x4F, 0x36, 0x0D, 0xF9, 0x85, 0x34, 0x9A, 0x1D, 0xDE, 0xFD, 0xC5, 0x8C, 0xB5, 0xCC, 0x7E, 0x4E, 0xEE, 0x84, 0x5E, 0x03, 0x18, 0x01, 0xB5, 0x2F, 0xF1, 0xD9, 0x84, 0xBB, 0x2F, 0x10, 0x85, 0xFF, 0xA6, 0x01, 0x9B, 0xF8, 0xB0, 0x22, 0x09, 0x3D, 0x9D, 0x78, 0x02, 0x14, 0x52, 0xDB, 0x7D, 0xAB, 0x7C, 0x22, 0xC8, 0x16, 0x3C, 0x1B, 0x2F, 0xB7, 0xC7, 0x9B, 0xE4, 0x27, 0xB2, 0xD6, 0xB5, 0x2D, 0xB4, 0x3E, 0xE2, 0xE5, 0x65, 0x93, 0x78, 0x1B, 0x1D, 0xB5, 0x2F, 0xDF, 0x8E, 0xAA, 0xBF, 0xD8, 0x4A, 0xF0, 0x54, 0x0A, 0x1A, 0x96, 0x37, 0xE3, 0xFF, 0x3B, 0x2B, 0xD4, 0xE2, 0x81, 0x85, 0xE8, 0xD6, 0xFE, 0x90, 0xFE, 0x0D, 0xB9, 0x18, 0x86, 0x40, 0x93, 0xB3, 0x8C, 0xBD, 0xCF, 0xB4, 0x5A, 0x99, 0x14, 0xBD, 0x85, 0xE8, 0x22, 0x9C, 0x3E, 0x25, 0x3A, 0xE1, 0x82, 0x59, 0x27, 0x16, 0x4C, 0x01, 0x7A, 0x8A, 0x04, 0xED, 0xEA, 0x19, 0xF9, 0x11, 0xC9, 0x11, 0x65, 0x0C, 0x9F, 0xEA, 0xF0, 0xE2, 0x9F, 0xD1, 0x43, 0xF2, 0xC0, 0x83, 0xAF, 0x46, 0x9A, 0x47, 0xD8, 0xE5, 0x35, 0xC1, 0xC7, 0x74, 0x7F, 0x2A, 0xE5, 0x46, 0x80, 0x43, 0x67, 0x83, 0xE0, 0xFB, 0x54, 0x44, 0x76, 0xEB, 0x55, 0xE3, 0xD7, 0xAE, 0x9F, 0x11, 0xF6, 0xB8, 0x51, 0x87, 0x66, 0x2B, 0x32, 0x6D, 0x0E, 0x72, 0xC0, 0xAD, 0x63, 0x08, 0x1C, 0x78, 0xFF, 0x1B, 0xD9, 0xBF, 0x0D, 0x9B, 0x05, 0xA3, 0xCA, 0x47, 0x69, 0x93, 0x9B, 0x2C, 0x41, 0x25, 0x19, 0x5E, 0xC1, 0x22, 0x34, 0x04, 0x4F, 0xB3, 0x6C, 0x1F, 0x40, 0x26, 0x54, 0xED, 0x4B, 0x77, 0xD6, 0x11, 0xCE, 0xC3, 0x9E, 0x8B, 0x75, 0xDD, 0x73, 0xDD, 0x65, 0x38, 0xFC, 0xA8, 0x34, 0x48, 0x3E, 0x5A, 0x08, 0xED, 0xA1, 0x17, 0x45, 0x30, 0x99, 0xEA, 0x99, 0x4A, 0xDF, 0x3C, 0x53, 0x78, 0xE4, 0x39, 0xF7, 0xEA, 0xBF, 0x97, 0xB0, 0x34, 0x70, 0x00, 0x03, 0x23, 0xC5, 0xA6, 0x6F, 0xCF, 0xB4, 0x00, 0xA2, 0xFA, 0xDA, 0xEF, 0x7F, 0x80, 0xF1, 0xCD, 0x7B, 0x30, 0x7A, 0xEE, 0xB4, 0x88, 0x37, 0x26, 0x33, 0xCD, 0x5B, 0x9E, 0x38, 0x42, 0x09, 0xC5, 0x64, 0x87, 0x34, 0x40, 0x80, 0x3F, 0x79, 0xE9, 0xA2, 0x4A, 0x51, 0x5F, 0x5D, 0x86, 0x6C, 0x9E, 0x56, 0x5B, 0x07, 0x07, 0x93, 0x3F, 0x60, 0x27, 0xA5, 0xA8, 0x00, 0x0B, 0x44, 0xB7, 0x19, 0x1D, 0xE9, 0x70, 0x72, 0x7B, 0x61, 0x90, 0xA6, 0xCD, 0xB1, 0xFF, 0x8B, 0x0B, 0x9A, 0x60, 0x66, 0xBE, 0xB8, 0x19, 0x95, 0x40, 0x31, 0x65, 0x3B, 0x37, 0x03, 0x8A, 0x9F, 0x86, 0x27, 0x38, 0xB1, 0x08, 0xDF, 0x09, 0xF8, 0xE5, 0x82, 0x4E, 0x9C, 0x93, 0x90, 0xB1, 0x44, 0x7B, 0xA1, 0xB1, 0x38, 0x64, 0xC0, 0x61, 0xF9, 0x85, 0xE2, 0xAC, 0x94, 0xE5, 0xA8, 0xA1, 0x4E, 0x18, 0xFC, 0xA5, 0x5F, 0xF2, 0x38, 0xE3, 0x1D, 0xDD, 0x0B, 0x5B, 0x61, 0x92, 0x46, 0xB5, 0xA1, 0x46, 0x1D, 0xE3, 0x89, 0xB6, 0xD4, 0x8B, 0x2E, 0x43, 0x0A, 0x68, 0xA9, 0xF4, 0x5A, 0xC1, 0xEE, 0x5A, 0xDC, 0xC1, 0x93, 0x50, 0x9A, 0x11, 0x12, 0x91, 0x6C, 0xD4, 0x6F, 0x19, 0x72, 0x33, 0xA7, 0x56, 0xAD, 0x5E, 0x16, 0xAC, 0xAA, 0xBE, 0x33, 0x1C, 0xBC, 0x92, 0x64, 0x31, 0xA6, 0xF3, 0xE4, 0x2C, 0x55, 0xA7, 0x84, 0x59, 0x9A, 0xD6, 0x22, 0xAF, 0x55, 0x5F, 0x64, 0x69, 0x3D, 0xC7, 0xED, 0x70, 0x0A, 0x76, 0xE9, 0xE8, 0x10, 0xB2, 0x84, 0x6F, 0x19, 0x2A, 0xC4, 0x2D, 0xA7, 0x2C, 0x5D, 0xE0, 0xA0, 0x4C, 0x6B, 0x51, 0x9F, 0x5A, 0xC0, 0xF4, 0xC5, 0x63, 0x0B, 0x41, 0xA0, 0x6C, 0xCD, 0x2D, 0x99, 0xD7, 0xBA, 0x7E, 0x87, 0xC5, 0x09, 0x0B, 0x6E, 0x16, 0x10, 0x31, 0x73, 0x09, 0x8F, 0x8F, 0x15, 0xC1, 0x3F, 0x54, 0x6D, 0x78, 0xCF, 0xAE, 0x42, 0xFC, 0x11, 0x6E, 0x2E, 0xE6, 0x44, 0xE3, 0xC6, 0x96, 0xAD, 0xC9, 0x08, 0x90, 0x37, 0xF0, 0xFA, 0xF7, 0x11, 0xB8, 0x2B, 0x60, 0xAB, 0x24, 0x3D, 0xC3, 0x38, 0x72, 0x5F, 0x48, 0x6C, 0x6F, 0x72, 0x1E, 0xB6, 0x3F, 0xE1, 0x75, 0xF9, 0xE2, 0x53, 0x9E, 0xE3, 0x63, 0x62, 0xF7, 0x22, 0x7A, 0xB6, 0xF4, 0xB8, 0x59, 0xDA, 0x3A, 0x3E, 0x8E, 0x01, 0xC6, 0xB2, 0x65, 0xBC, 0xAF, 0x5B, 0x2C, 0xCE, 0x9F, 0x66, 0x70, 0xFA, 0x83, 0x1E, 0xBB, 0x5E, 0x33, 0xCD, 0xDF, 0xFB, 0xEB, 0xDE, 0x58, 0x70, 0xEB, 0x2A, 0x09, 0x50, 0xC3, 0xF1, 0x8E, 0x6A, 0xAA, 0x31, 0x17, 0x37, 0x92, 0x01, 0xA2, 0x58, 0x68, 0xF4, 0xF4, 0x48, 0xE1, 0xFA, 0xC7, 0xD6, 0x73, 0xEC, 0x8E, 0x24, 0x69, 0xF6, 0x34, 0x49, 0x2A, 0x25, 0x28, 0xE2, 0x1D, 0x03, 0x10, 0xC2, 0xF2, 0xB1, 0x67, 0x0A, 0xE1, 0x07, 0xF0, 0xAC, 0x0A, 0x65, 0x71, 0x1D, 0x2F, 0x40, 0x3E, 0x96, 0x47, 0xF0, 0x8F, 0x17, 0x28, 0x6B, 0xB0, 0x8F, 0xF2, 0xE8, 0xD0, 0x54, 0x7E, 0x38, 0x26, 0xF3, 0xD8, 0x30, 0xCD, 0xC0, 0x47, 0x66, 0x45, 0x1C, 0x3F, 0xC0, 0xCD, 0xCC, 0x94, 0x9C, 0xE7, 0x55, 0x1D, 0x6F, 0x80, 0xEE, 0x23, 0x69, 0xE2, 0xCC, 0xDB, 0x27, 0x76, 0x80, 0x52, 0xAD, 0x27, 0xF6, 0xB3, 0xFB, 0x43, 0x26, 0x1D, 0x1F, 0x68, 0xBB, 0x20, 0x4D, 0x02, 0x4C, 0xF9, 0xFE, 0x7C, 0xE5, 0xDD, 0xC7, 0xF9, 0x5B, 0xB6, 0x3D, 0xF2, 0xB6, 0x2F, 0xBF, 0xE1, 0xFB, 0x58, 0xEF, 0x6E, 0x82, 0x95, 0xA7, 0x03, 0x47, 0xD1, 0x72, 0x2D, 0xDE, 0x2E, 0x4C, 0xC0, 0x3D, 0x4B, 0xEE, 0xF5, 0xAB, 0x7A, 0x5E, 0xCD, 0xEC, 0x19, 0x46, 0xCF, 0x72, 0xD9, 0x47, 0x3A, 0x28, 0x2A, 0xEA, 0x1F, 0x84, 0x36, 0x00, 0x95, 0xF0, 0x22, 0xD7, 0x11, 0x31, 0x9C, 0xDE, 0xDB, 0x5E, 0x6C, 0x98, 0x53, 0x1F, 0xC5, 0x14, 0x03, 0x59, 0xFE, 0xD0, 0x91, 0x42, 0xA3, 0x45, 0xBB, 0x37, 0xFB, 0x55, 0xC3, 0x97, 0x84, 0x5F, 0x4E, 0x42, 0x17, 0x3B, 0x51, 0xCF, 0xAD, 0x41, 0xC6, 0x57, 0x25, 0xFC, 0xB7, 0xF2, 0xA6, 0xEE, 0x23, 0xB7, 0x73, 0x77, 0x44, 0x5B, 0xFC, 0xDD, 0x41, 0x71, 0x7C, 0x3E, 0xDB, 0x73, 0x71, 0x21, 0x89, 0x62, 0x11, 0x49, 0xCD, 0xFC, 0x34, 0x82, 0x5C, 0x1D, 0xD4, 0xAD, 0x20, 0xBF, 0x5E, 0xE2, 0xA8, 0xB2, 0x96, 0xB2, 0xAB, 0xC3, 0x5A, 0x7F, 0xBB, 0x86, 0x22, 0xD2, 0xF9, 0x69, 0xA2, 0x95, 0x14, 0x64, 0x86, 0x87, 0x37, 0x1F, 0x81, 0x64, 0xC2, 0x95, 0x87, 0x43, 0x5C, 0xE0, 0xDD, 0x7B, 0x9E, 0xE4, 0xCE, 0x73, 0xF5, 0xF0, 0x5A, 0xF5, 0x7E, 0xC3, 0x66, 0x27, 0x40, 0xF5, 0xCC, 0xDB, 0xD3, 0x43, 0x69, 0x49, 0x6D, 0x30, 0xF6, 0xAD, 0xB8, 0x5F, 0xA8, 0xDB, 0x34, 0x47, 0x0A, 0x86, 0x4D, 0x05, 0x4C, 0x11, 0x80, 0x51, 0x96, 0x5B, 0xF9, 0x9E, 0x9B, 0x40, 0x49, 0x2D, 0xE1, 0x83, 0x9C, 0xD3, 0xCA, 0xE3, 0x7E, 0x1F, 0x58, 0x52, 0x34, 0x10, 0xF5, 0x00, 0x08, 0xF0, 0xF8, 0xF5, 0x53, 0x71, 0xFA, 0x67, 0xC3, 0xB1, 0xFF, 0x91, 0xFC, 0xE8, 0x52, 0x31, 0xA9, 0x09, 0x76, 0xF8, 0x62, 0x8E, 0x61, 0x94, 0x3C, 0x21, 0x4C, 0xD0, 0xB5, 0x84, 0xE9, 0x25, 0x6C, 0xD3, 0x57, 0x2D, 0xD0, 0x66, 0xC0, 0x76, 0x45, 0x78, 0x25, 0xDA, 0xC6, 0xAD, 0x02, 0x6F, 0xD5, 0x65, 0x7A, 0x27, 0x47, 0x6B, 0xB6, 0x9C, 0xF6, 0x64, 0x61, 0x89, 0x95, 0x58, 0xBD, 0x13, 0x7C, 0xD8, 0x6A, 0xB0, 0x17, 0x91, 0xD7, 0x08, 0x8C, 0x69, 0x44, 0x40, 0x29, 0xDF, 0x51, 0x9D, 0x1D, 0x59, 0xF0, 0x28, 0x74, 0x58, 0x7A, 0x97, 0xE2, 0x88, 0x04, 0x87, 0x18, 0xA7, 0x1C, 0xEB, 0xB3, 0x72, 0x3A, 0x3A, 0x4F, 0x8F, 0x66, 0x53, 0xFB, 0xCA, 0xC8, 0x5D, 0x7F, 0x98, 0x98, 0x6F, 0x36, 0x0A, 0x6D, 0x3D, 0x49, 0xA0, 0xD2, 0x47, 0x81, 0x84, 0x3C, 0x24, 0x7E, 0xC8, 0xD8, 0x87, 0x62, 0xA0, 0x3F, 0x34, 0xA7, 0x95, 0xAD, 0x18, 0x7C, 0x05, 0xAF, 0xD2, 0x6C, 0x11, 0x0F, 0x4F, 0x54, 0xD4, 0xC8, 0x6E, 0x11, 0xA9, 0x19, 0x9F, 0x20, 0xB8, 0xED, 0xB6, 0x11, 0xFF, 0x67, 0xF5, 0xF6, 0x93, 0x2D, 0xED, 0x9C, 0xEC, 0x6D, 0xAF, 0xEF, 0xBC, 0xC2, 0x3F, 0xDF, 0xB1, 0xBB, 0x34, 0xB3, 0xF5, 0xC1, 0x2A, 0xBC, 0x8F, 0x74, 0xC1, 0x57, 0xD5, 0xA3, 0xB8, 0x6C, 0x1F, 0x7E, 0x9F, 0x69, 0x15, 0xF0, 0x6B, 0x76, 0xEE, 0x5E, 0xCE, 0x4D, 0x63, 0x19, 0xC8, 0xD3, 0x03, 0x76, 0x62, 0x72, 0xCF, 0xF7, 0x44, 0xF4, 0x0D, 0x1B, 0x39, 0x25, 0x02, 0x9D, 0x0A, 0x98, 0xD6, 0x84, 0x44, 0x15, 0x18, 0x1D, 0x27, 0x37, 0x99, 0x97, 0xE3, 0x89, 0xD8, 0x57, 0x1D, 0xF8, 0xC9, 0x1C, 0xD5, 0x48, 0x2C, 0x73, 0xD9, 0x6E, 0x33, 0xDD, 0xAB, 0x6A, 0x97, 0x53, 0x2E, 0xDF, 0x5D, 0xEE, 0xF4, 0x55, 0x34, 0x88, 0x17, 0x41, 0x2C, 0x85, 0xE3, 0xFE, 0x02, 0xFE, 0xD1, 0xF6, 0xBF, 0x63, 0xA1, 0xEE, 0xAF, 0xF9, 0x7E, 0xDE, 0xD4, 0x75, 0xA1, 0x23, 0x23, 0x76, 0x78, 0x71, 0x02, 0xBD, 0x43, 0xA3, 0xC2, 0x10, 0x84, 0xFE, 0x1A, 0x7D, 0x98, 0x10, 0x8D, 0x30, 0x69, 0x50, 0x68, 0x9B, 0x8E, 0x8A, 0x75, 0x18, 0x6D, 0x35, 0x9E, 0xF0, 0xB9, 0x10, 0xF1, 0x42, 0x24, 0x4A, 0x9A, 0xA8, 0xC0, 0x91, 0xE5, 0xC6, 0x7F, 0xE0, 0x13, 0x6E, 0x81, 0xBD, 0xA5, 0x9B, 0x3D, 0xA5, 0xEE, 0x88, 0x60, 0x32, 0x2E, 0xF5, 0xA2, 0x06, 0x3E, 0x62, 0xCC, 0x0A, 0x48, 0xB4, 0x70, 0xE8, 0xC5, 0xA6, 0xF5, 0x7E, 0x99, 0x74, 0xDB, 0x6E, 0xFD, 0x88, 0x8C, 0x03, 0x5D, 0x19, 0x28, 0xB8, 0xCF, 0x0A, 0xA4, 0xEE, 0xCD, 0x02, 0xD5, 0x62, 0xCE, 0x0C, 0x7B, 0xE5, 0x8B, 0xD8, 0x43, 0xEE, 0xF6, 0x00, 0xDB, 0xFF, 0x2A, 0xB9, 0x8E, 0x5D, 0x71, 0x58, 0x70, 0xAA, 0x13, 0x84, 0xC7, 0xC8, 0x76, 0x22, 0xD0, 0x3B, 0xD7, 0xB8, 0x90, 0xAD, 0xC0, 0x2D, 0xC1, 0xDD, 0x6F, 0x90, 0xAC, 0xB0, 0x77, 0xD6, 0xA9, 0xAC, 0x06, 0x92, 0x5E, 0x7B, 0x2E, 0x1D, 0x96, 0x74, 0xD9, 0x20, 0x96, 0xD6, 0x2F, 0xFD, 0xCD, 0x3F, 0xE2, 0x3A, 0xCD, 0xD6, 0x1C, 0x17, 0x61, 0xCE, 0xC3, 0xF2, 0xBC, 0xAB, 0xA8, 0xE7, 0xE9, 0xC2, 0xD5, 0x4F, 0x9D, 0x3E, 0xD3, 0x08, 0xC4, 0xDD, 0x45, 0x61, 0xEB, 0xB8, 0xDE, 0x07, 0xD5, 0x7D, 0x25, 0xAF, 0x2C, 0x7C, 0x82, 0x9D, 0xD8, 0x33, 0x52, 0xD8, 0x51, 0x7F, 0x7E, 0x2C, 0xDF, 0xDD, 0xCE, 0x59, 0x30, 0x9C, 0x7C, 0x52, 0x39, 0xCD, 0x19, 0x2A, 0x31, 0x5C, 0xDA, 0x35, 0xC8, 0x05, 0x2A, 0x28, 0xCD, 0x1E, 0x86, 0xCC, 0xD8, 0x65, 0x04, 0x65, 0xFE, 0x76, 0x4F, 0xA8, 0x31, 0xBB, 0x65, 0x1C, 0xD0, 0xB7, 0x00, 0x99, 0xFC, 0x1B, 0x62, 0xC6, 0xE9, 0x3C, 0xAE, 0xC4, 0xBA, 0x84, 0x22, 0xC9, 0x0D, 0x06, 0x10, 0xE6, 0x02, 0x3C, 0x51, 0x55, 0x75, 0xB3, 0x0F, 0x45, 0x9E, 0x4A, 0xCB, 0xA5, 0x8C, 0x1A, 0x7D, 0x78, 0x58, 0x5C, 0x33, 0x17, 0x38, 0x1D, 0xA3, 0x33, 0x58, 0x31, 0x55, 0xE6, 0x08, 0xB0, 0x89, 0xF8, 0xAD, 0xF6, 0x11, 0x79, 0xC3, 0xB7, 0x2E, 0x15, 0x78, 0xFE, 0x93, 0x45, 0x5F, 0xDC, 0x15, 0xFC, 0xF7, 0x2B, 0xA4, 0x43, 0xB4, 0x62, 0xDF, 0xC4, 0xDE, 0xB5, 0xFA, 0xA1, 0x99, 0xD6, 0x82, 0xE1, 0xE8, 0x71, 0xB9, 0xCF, 0x6B, 0x22, 0xAA, 0x22, 0xA0, 0x7E, 0x62, 0x4B, 0x8E, 0x4A, 0x3D, 0x37, 0x86, 0xA2, 0x65, 0xB2, 0x73, 0x49, 0x22, 0xBC, 0x27, 0xAD, 0x0D, 0xC5, 0x69, 0xC3, 0x3C, 0x00, 0xAF, 0x8E, 0x44, 0xE4, 0xCA, 0x8B, 0xB2, 0x33, 0x7D, 0x33, 0xA3, 0x2D, 0xE6, 0xB2, 0x1C, 0xD8, 0x8C, 0xF1, 0x6E, 0x30, 0x0E, 0x13, 0x5C, 0x60, 0xC8, 0x99, 0xBC, 0x8B, 0xED, 0x61, 0xEA, 0x35, 0xF4, 0xA9, 0x59, 0xAF, 0x69, 0x9B, 0x81, 0x79, 0x03, 0xC4, 0x36, 0x46, 0x3F, 0x92, 0x78, 0x99, 0x50, 0x09, 0xD4, 0x90, 0xB0, 0x08, 0xAB, 0x22, 0x46, 0xB5, 0xFF, 0x14, 0xC1, 0x43, 0x00, 0xAA, 0xE3, 0xBC, 0x30, 0x38, 0x39, 0x0E, 0x71, 0xAD, 0xFC, 0x01, 0x44, 0x7F, 0x32, 0x0F, 0xEE, 0xB8, 0xAE, 0xB2, 0x73, 0x6F, 0xD4, 0x7E, 0xCB, 0xA7, 0x04, 0x85, 0xEA, 0x36, 0xFD, 0x73, 0x60, 0xFC, 0x85, 0xE5, 0x9B, 0xCE, 0x94, 0xFD, 0xF2, 0x12, 0x11, 0x2C, 0xCC, 0x4F, 0x4E, 0xA5, 0x95, 0x32, 0x81, 0x76, 0xD8, 0xEC, 0x88, 0x50, 0x0F, 0x93, 0x76, 0xF0, 0x59, 0x39, 0x8E, 0xB3, 0x96, 0x3F, 0x50, 0x13, 0x36, 0xD8, 0xB9, 0xD8, 0xBA, 0x17, 0x59, 0xD1, 0xCF, 0x66, 0x83, 0x23, 0x38, 0xE7, 0xEB, 0x61, 0xAC, 0x13, 0x3E, 0xD6, 0xB4, 0xAA, 0x5C, 0xD0, 0xB2, 0xEC, 0xCB, 0xE3, 0x6D, 0x42, 0xEA, 0xD1, 0xE7, 0xBE, 0x8A, 0x59, 0x6E, 0x34, 0x7E, 0x44, 0x00, 0x99, 0x47, 0xA2, 0x45, 0x4D, 0xBA, 0x5F, 0x99, 0x13, 0x31, 0x4D, 0x11, 0x8D, 0x01, 0xBA, 0x6B, 0x88, 0x7C, 0xEE, 0x87, 0xF2, 0xE7, 0x0F, 0x73, 0xEB, 0x57, 0x4B, 0x77, 0x40, 0x52, 0xD3, 0xAC, 0x81, 0x1A, 0x3B, 0xFD, 0xA4, 0x2C, 0x83, 0x11, 0x2A, 0xF7, 0x53, 0xDF, 0x28, 0x15, 0xE2, 0xCA, 0xAC, 0x2B, 0xAC, 0xDA, 0x68, 0xE0, 0x19, 0x42, 0x8E, 0xC8, 0x78, 0xD4, 0x11, 0x8B, 0xEF, 0xF6, 0xDC, 0x26, 0xEA, 0x67, 0x93, 0xEC, 0xC7, 0xEA, 0xEF, 0x8D, 0xF7, 0xE1, 0xA8, 0x94, 0xD3, 0x35, 0xF1, 0x14, 0x38, 0x8A, 0x63, 0xD1, 0x42, 0xD1, 0x7B, 0x26, 0xBF, 0x3C, 0xB2, 0x8A, 0x6E, 0xBC, 0xA2, 0x73, 0x63, 0x6A, 0x95, 0x2A, 0x9B, 0x5F, 0xC3, 0x85, 0x38, 0x29, 0xDD, 0xF5, 0x68, 0x43, 0xBE, 0xC2, 0x6A, 0x56, 0x28, 0xC8, 0x20, 0xCA, 0x84, 0x0E, 0x23, 0xE4, 0x3D, 0x5F, 0x80, 0xBE, 0xE4, 0xDA, 0x0E, 0xBC, 0x76, 0x84, 0x48, 0x15, 0x05, 0xA3, 0x56, 0x8C, 0xAE, 0x5E, 0x41, 0xE7, 0x57, 0x6D, 0xF8, 0xD1, 0x1C, 0x9A, 0xC7, 0x41, 0x1D, 0xE2, 0xA9, 0x03, 0x45, 0xDA, 0x88, 0x69, 0x99, 0x1B, 0x45, 0xF8, 0x28, 0x72, 0xCC, 0xD8, 0xCE, 0x79, 0x10, 0xA7, 0x33, 0xF1, 0xC9, 0xD5, 0x01, 0x5A, 0x7A, 0x4F, 0xE7, 0xDC, 0x71, 0x47, 0x1C, 0xB3, 0x86, 0x62, 0x08, 0xA2, 0x6D, 0x19, 0xD7, 0xD3, 0x14, 0xEF, 0x84, 0x86, 0x9C, 0xAC, 0xAE, 0x03, 0x9A, 0xA5, 0xD1, 0x91, 0x8E, 0x02, 0x4A, 0x70, 0x94, 0x51, 0x92, 0xD9, 0xAF, 0x7A, 0x32, 0x91, 0xD2, 0x1F, 0x0B, 0x16, 0xA0, 0x52, 0xF8, 0x3B, 0x98, 0xD2, 0xC2, 0x4B, 0xC0, 0xC2, 0x6B, 0x2E, 0xDE, 0xD6, 0x98, 0xC0, 0x1F, 0x6E, 0x13, 0x56, 0xCC, 0x37, 0x8B, 0x31, 0x26, 0x5E, 0xC8, 0x53, 0xC6, 0xD2, 0x1F, 0xAB, 0x79, 0x40, 0xE8, 0xFD, 0x25, 0xE1, 0x7F, 0x9E, 0xE4, 0xC0, 0x3F, 0xCF, 0xBF, 0x74, 0x7A, 0x80, 0x9D, 0x1F, 0x86, 0xD3, 0xEA, 0x9D, 0xB9, 0x88, 0x0F, 0x34, 0x82, 0x28, 0xD4, 0xB1, 0xF2, 0x86, 0xE4, 0x8D, 0xF0, 0xFE, 0xBD, 0xEF, 0x86, 0xD3, 0xE0, 0xB9, 0x60, 0xBE, 0xC4, 0x77, 0x70, 0x04, 0x7B, 0xD4, 0xCB, 0xA6, 0x25, 0x5E, 0xF4, 0x50, 0x35, 0x27, 0xF7, 0x57, 0xAA, 0xEA, 0x29, 0x04, 0x1E, 0xC1, 0xB1, 0xE4, 0xC3, 0x8C, 0x85, 0xF8, 0x69, 0xEF, 0xC0, 0x04, 0x18, 0x32, 0x66, 0xDD, 0x9B, 0x60, 0x8F, 0x9F, 0x2D, 0xC1, 0x00, 0xDD, 0x84, 0x39, 0x38, 0x51, 0xA4, 0xCC, 0xC2, 0x0E, 0x3C, 0xC3, 0x7B, 0x70, 0xBF, 0xC1, 0x73, 0x2E, 0x3C, 0x98, 0x15, 0x63, 0x20, 0x33, 0x61, 0x9C, 0x39, 0x85, 0x1D, 0x26, 0xFD, 0x45, 0xC0, 0x16, 0x55, 0x29, 0x63, 0xA7, 0x07, 0x48, 0x5C, 0x0B, 0x54, 0xE7, 0xBD, 0xF0, 0x1A, 0x53, 0x13, 0xA7, 0xDD, 0x08, 0xF1, 0x02, 0x1E, 0x19, 0x4E, 0x3E, 0xE3, 0xF8, 0x10, 0x1F, 0x71, 0x4D, 0xB8, 0x80, 0x55, 0x09, 0x7B, 0x20, 0xF3, 0x31, 0xD1, 0x66, 0x90, 0xFF, 0x72, 0x41, 0x04, 0xAA, 0xEB, 0x72, 0xA7, 0x8F, 0x79, 0x4B, 0xFF, 0x9B, 0xF8, 0x61, 0x2A, 0x96, 0xB4, 0xB1, 0x3D, 0x08, 0x52, 0x6F, 0x1F, 0xB3, 0xD8, 0x9E, 0xEC, 0x39, 0xCF, 0x7A, 0xF9, 0x09, 0x02, 0xBF, 0xCA, 0x8B, 0x5C, 0xD0, 0xBB, 0xA7, 0x64, 0xE3, 0x0D, 0x95, 0xE8, 0x74, 0xE4, 0xD5, 0xDD, 0x37, 0xC0, 0xCB, 0xDD, 0x67, 0xD7, 0xEF, 0x41, 0x51, 0x26, 0x77, 0x60, 0x93, 0xB2, 0xE7, 0x9E, 0x76, 0x0C, 0xDA, 0x75, 0x4D, 0x31, 0x5F, 0x64, 0xD4, 0xA7, 0x9A, 0x0A, 0x36, 0x29, 0x0F, 0x66, 0xC2, 0x3E, 0x9D, 0x38, 0xFE, 0x36, 0xC3, 0x46, 0x1D, 0xB0, 0x2B, 0xE4, 0x49, 0x17, 0xC6, 0x10, 0x19, 0x03, 0xEF, 0x54, 0x72, 0xFD, 0x0A, 0x98, 0x88, 0x56, 0xE6, 0x5D, 0xC1, 0xB1, 0x99, 0x76, 0xE4, 0xDE, 0x95, 0x6E, 0x22, 0xB1, 0xA6, 0xAB, 0xD0, 0x17, 0xA3, 0x50, 0x39, 0x0F, 0x2F, 0x24, 0xBD, 0x0F, 0x88, 0xCE, 0xE0, 0x60, 0x09, 0x62, 0xF2, 0x4F, 0x5B, 0x2E, 0x98, 0x15, 0x29, 0x7C, 0xE7, 0xAF, 0x38, 0x71, 0xAC, 0xE2, 0xA4, 0x37, 0x17, 0x91, 0xF1, 0xEB, 0x90, 0xA7, 0xAE, 0x6A, 0xA8, 0x27, 0x49, 0xB8, 0x30, 0x29, 0x56, 0x6F, 0x44, 0x4A, 0x92, 0xE4, 0x85, 0xFA, 0x88, 0xFE, 0xAD, 0x0A, 0xC1, 0x2C, 0x04, 0x56, 0xD9, 0x82, 0xC2, 0x78, 0x55, 0xDB, 0x62, 0xB3, 0xCE, 0xDA, 0xCE, 0x12, 0xB2, 0xBD, 0x5A, 0xA0, 0x5F, 0xFE, 0x10, 0xA1, 0x06, 0x43, 0xB2, 0xF3, 0x1C, 0x86, 0x75, 0xFF, 0xE8, 0x72, 0xFB, 0xCC, 0xEB, 0x89, 0xBE, 0x77, 0x0D, 0xBE, 0x98, 0x04, 0x16, 0x36, 0x59, 0x22, 0x7E, 0x83, 0x94, 0xE3, 0xE8, 0x32, 0x9D, 0x94, 0x1B, 0x3D, 0x62, 0x57, 0x41, 0x3F, 0x1C, 0x4F, 0x89, 0xE9, 0x4D, 0x2F, 0x17, 0xC1, 0x2B, 0xA1, 0x62, 0xE3, 0x95, 0xEA, 0x5A, 0x9D, 0x1C, 0xD7, 0x10, 0xE6, 0x26, 0xDF, 0xD5, 0x75, 0xB4, 0xF9, 0x21, 0xFA, 0x44, 0x6B, 0x51, 0xD5, 0x53, 0x3F, 0x0C, 0x7F, 0xE1, 0x3F, 0xEB, 0x5E, 0x52, 0x29, 0xD9, 0x6D, 0xF9, 0xC2, 0xD4, 0xDC, 0x0B, 0x22, 0xD3, 0x28, 0xBC, 0xDD, 0xD4, 0x71, 0x1C, 0xB5, 0xFF, 0xC4, 0xEE, 0xBD, 0x1A, 0x9F, 0xF9, 0x4D, 0x4D, 0x57, 0x07, 0xF8, 0xFD, 0x21, 0x32, 0x08, 0xE7, 0xB2, 0x75, 0x1C, 0xEC, 0x1C, 0x63, 0x32, 0x4C, 0x7C, 0x75, 0xB0, 0x27, 0xD5, 0xCB, 0x6C, 0xF3, 0x88, 0x21, 0x61, 0x77, 0xDC, 0x8A, 0x26, 0xD6, 0x32, 0xF3, 0xAB, 0x69, 0xFB, 0xB6, 0x8C, 0xC2, 0x61, 0x8D, 0xC2, 0x78, 0x91, 0xD3, 0x15, 0x16, 0x48, 0xFA, 0xF6, 0x4F, 0xBE, 0x81, 0xB8, 0x3D, 0x52, 0xD8, 0xEE, 0x92, 0x42, 0xE1, 0xEC, 0xA8, 0x79, 0x96, 0x2F, 0x7F, 0xFD, 0xC5, 0x2E, 0xC5, 0xBA, 0xC1, 0xD1, 0x0B, 0x39, 0x28, 0xBC, 0x5F, 0x05, 0xE3, 0x73, 0xDF, 0x3D, 0xDF, 0x5B, 0xDB, 0x85, 0x8E, 0x4B, 0x52, 0x98, 0x76, 0x68, 0xE9, 0xBE, 0xFE, 0x14, 0x28, 0x00, 0x67, 0x6E, 0xB2, 0x7D, 0x67, 0xCB, 0xF6, 0xFF, 0xFF, 0xAB, 0x42, 0x83, 0x35, 0x61, 0x65, 0x64, 0x79, 0x15, 0x93, 0xE1, 0x97, 0xF8, 0x3F, 0xC8, 0xAB, 0x1D, 0x7F, 0xBF, 0xD0, 0x22, 0x8A, 0x94, 0x6E, 0xF3, 0xCF, 0x74, 0x93, 0xEE, 0xA1, 0xFB, 0xDC, 0xAE, 0x9F, 0x6F, 0x84, 0x10, 0x42, 0xD6, 0xE7, 0x78, 0xC5, 0x02, 0x68, 0xC9, 0x17, 0x66, 0x50, 0xD3, 0xE9, 0x2E, 0x98, 0x77, 0x20, 0x50, 0x24, 0x4A, 0x71, 0x91, 0x2E, 0x11, 0x6E, 0x98, 0x7A, 0xB3, 0x55, 0x7B, 0xF5, 0x5D, 0x81, 0x45, 0xEC, 0x79, 0x90, 0x54, 0x7C, 0x12, 0x41, 0x23, 0xEF, 0xB8, 0x07, 0x6F, 0x0D, 0x40, 0x01, 0x7D, 0x45, 0x76, 0x80, 0x50, 0x34, 0x16, 0x47, 0x50, 0x4C, 0xD1, 0x35, 0xC4, 0x86, 0x36, 0x2E, 0xAC, 0xA0, 0xEF, 0x5E, 0x67, 0xD7, 0x91, 0xCE, 0x56, 0xA0, 0xE5, 0x53, 0xBB, 0x5A, 0x8C, 0x63, 0x15, 0x06, 0xE0, 0x01, 0xA9, 0x1C, 0xDF, 0xAC, 0x7A, 0xE7, 0x8D, 0x3B, 0x87, 0xE5, 0x1B, 0x47, 0x4C, 0x69, 0x96, 0xB1, 0x52, 0xE8, 0xAE, 0x21, 0x45, 0x6E, 0x60, 0x67, 0x82, 0x41, 0x0F, 0x8C, 0x5D, 0x77, 0xDB, 0x8B, 0xF4, 0x95, 0x16, 0xD1, 0x62, 0x12, 0xB6, 0x8D, 0x7D, 0xF3, 0x05, 0xCF, 0xA1, 0xA4, 0xE0, 0xCA, 0x75, 0x11, 0x01, 0xA1, 0x6F, 0x60, 0x31, 0xD6, 0x94, 0x54, 0xE3, 0x1B, 0xE6, 0x1C, 0xD6, 0x05, 0xA6, 0xBF, 0x1F, 0xF1, 0xEC, 0x6B, 0x9F, 0x28, 0x89, 0x7B, 0x92, 0x59, 0xD2, 0xF7, 0x69, 0x7C, 0xCE, 0xA3, 0x9B, 0x78, 0xDE, 0x15, 0x21, 0x1F, 0x77, 0x0E, 0xF9, 0x33, 0x96, 0x44, 0x76, 0x90, 0x3E, 0x3E, 0xBF, 0x0C, 0xBC, 0x01, 0x0D, 0x61, 0x0C, 0x8F, 0x55, 0xA4, 0xC4, 0x0E, 0xC2, 0xF7, 0x23, 0x1A, 0x85, 0x33, 0x53, 0xAA, 0x54, 0x1D, 0xEC, 0xFA, 0x0F, 0xD4, 0x89, 0x87, 0x15, 0xB5, 0x74, 0xB8, 0xA0, 0x74, 0x1D, 0xA0, 0x29, 0xE3, 0x16, 0x0B, 0x6C, 0x3F, 0x2D, 0xA6, 0xFD, 0x90, 0x63, 0x27, 0x7E, 0x0C, 0xC0, 0x2E, 0x84, 0x81, 0x9C, 0xBC, 0x06, 0x06, 0xA2, 0x11, 0x49, 0x3B, 0xC5, 0x13, 0xF7, 0x33, 0x24, 0x54, 0x89, 0x1E, 0x16, 0x01, 0x0B, 0x30, 0xBD, 0xBD, 0x9B, 0x7D, 0xB1, 0x1B, 0xFA, 0x3F, 0x59, 0x94, 0xA6, 0xF9, 0x27, 0x84, 0x30, 0x4E, 0x3A, 0x72, 0x61, 0x73, 0x56, 0x1E, 0xFE, 0xA9, 0x32, 0x7E, 0x34, 0xDF, 0xF2, 0xCF, 0x9F, 0x32, 0x59, 0x87, 0xC8, 0x93, 0x7C, 0x3F, 0x26, 0x6B, 0x34, 0xE0, 0xB3, 0xC3, 0xEE, 0x38, 0xF3, 0x50, 0x30, 0xB4, 0xC1, 0x60, 0x48, 0xA6, 0x37, 0xD3, 0xE1, 0x77, 0xB6, 0x54, 0x79, 0x8F, 0x2A, 0xBF, 0xDA, 0x43, 0xC6, 0xCE, 0x03, 0xFE, 0xDE, 0x08, 0x2B, 0x18, 0x6C, 0x94, 0x6E, 0x0E, 0x7D, 0xDD, 0xFD, 0xB2, 0xD8, 0x85, 0xAD, 0x75, 0xC9, 0x2C, 0x6E, 0x40, 0xAA, 0xC1, 0x28, 0x9D, 0x52, 0x21, 0xEA, 0x19, 0xB8, 0x56, 0x2E, 0x0B, 0xE9, 0x4A, 0xF8, 0xFD, 0x28, 0xD3, 0xDF, 0xA7, 0x1E, 0x77, 0xB1, 0x76, 0x29, 0x9A, 0xA9, 0x7C, 0xD6, 0x99, 0xF9, 0xCE, 0x7A, 0x38, 0x96, 0xFF, 0x90, 0xF4, 0xAE, 0x04, 0x59, 0x9D, 0x57, 0x49, 0x63, 0x71, 0x7A, 0xA8, 0x3A, 0x68, 0xF5, 0x8D, 0xAC, 0x19, 0x36, 0xD8, 0x12, 0xE6, 0x6A, 0x58, 0x96, 0x0F, 0xC1, 0x4C, 0xF0, 0x6B, 0xBE, 0xF2, 0x92, 0xFA, 0x1A, 0xC5, 0x43, 0x72, 0x3E, 0x9A, 0x09, 0x0D, 0x16, 0xAA, 0xDF, 0x43, 0x42, 0x95, 0x1F, 0x14, 0x25, 0xFA, 0x05, 0x45, 0x41, 0xE1, 0x7C, 0x00, 0xDC, 0x9D, 0x34, 0x51, 0x27, 0x27, 0xBE, 0x7A, 0xE2, 0x63, 0x2E, 0xAC, 0x4A, 0xDA, 0x0E, 0x80, 0x34, 0x9A, 0x43, 0x2D, 0xC8, 0xCF, 0x52, 0x23, 0x10, 0xCC, 0x3D, 0x30, 0x76, 0x85, 0xA7, 0x36, 0xE7, 0x22, 0x5C, 0xC1, 0x00, 0x31, 0x31, 0x30, 0x61, 0xB9, 0xA7, 0xE9, 0xCB, 0x19, 0x0F, 0x68, 0x86, 0x1E, 0x9E, 0xAE, 0xB6, 0x63, 0xF2, 0x7E, 0x50, 0xAC, 0x4B, 0x96, 0x07, 0x2A, 0x5A, 0x9F, 0xE7, 0x10, 0x2F, 0x1F, 0xE0, 0xEA, 0xE4, 0xB0, 0x03, 0x31, 0xEB, 0xF4, 0x40, 0x16, 0x97, 0x4E, 0x40, 0x85, 0xD7, 0x1B, 0xB7, 0x50, 0xF8, 0xD4, 0x04, 0x0E, 0xFA, 0x89, 0x30, 0x85, 0x61, 0x69, 0xCD, 0xC1, 0x2A, 0xAE, 0xC2, 0x9A, 0xAF, 0xA6, 0x55, 0x41, 0xCD, 0x51, 0xCB, 0xFD, 0x0E, 0x3F, 0xEA, 0x93, 0x7C, 0x9A, 0x1D, 0x0B, 0xEF, 0x75, 0x92, 0xB9, 0xD6, 0x6C, 0x29, 0x53, 0xDF, 0x7D, 0x69, 0xDB, 0x0D, 0x97, 0xF1, 0x66, 0x74, 0x22, 0x0B, 0x34, 0x12, 0x69, 0x88, 0xB2, 0x0D, 0xD4, 0xD3, 0xFB, 0xC1, 0xAA, 0x2C, 0x74, 0xB2, 0x20, 0x2C, 0x42, 0x55, 0x01, 0x4D, 0x46, 0xAA, 0xEB, 0x62, 0xF1, 0x71, 0x4C, 0xA7, 0xA9, 0xCE, 0x23, 0x4C, 0x63, 0xB8, 0x13, 0xF8, 0x2F, 0xE6, 0x22, 0x4D, 0x2A, 0x7D, 0x45, 0xFC, 0xC5, 0x6C, 0xA7, 0x2A, 0x75, 0xBB, 0x2D, 0xA0, 0x85, 0x87, 0xCA, 0x47, 0xB3, 0x15, 0xFB, 0xC4, 0x6C, 0x0E, 0x1B, 0x54, 0xEF, 0x03, 0x4C, 0x43, 0xD9, 0xD9, 0x73, 0xC8, 0x10, 0x46, 0xE5, 0x19, 0x38, 0x32, 0xCD, 0xB2, 0xDB, 0x8F, 0xBD, 0x08, 0xA3, 0x33, 0x01, 0x41, 0x1F, 0x6A, 0x02, 0x07, 0x88, 0xAE, 0x98, 0x6C, 0x26, 0x05, 0xF0, 0xA6, 0x46, 0x95, 0xE0, 0xD8, 0xB6, 0xF9, 0x56, 0xFF, 0xB7, 0xBD, 0x5D, 0x77, 0xFC, 0x16, 0xF2, 0xEF, 0xDE, 0xFF, 0x15, 0x14, 0x28, 0xDB, 0x67, 0x69, 0xA6, 0x7B, 0xFA, 0x17, 0xAE, 0x35, 0x1A, 0x6E, 0x4C, 0x08, 0x16, 0x7E, 0x31, 0x4E, 0xF5, 0x67, 0x9F, 0x35, 0x8C, 0x7A, 0x78, 0xFC, 0x4F, 0x7B, 0x1B, 0x97, 0xED, 0x08, 0x68, 0xFB, 0xE6, 0x6D, 0x7E, 0xDE, 0xBC, 0x30, 0xA1, 0xD1, 0xB9, 0x46, 0xEA, 0xC2, 0x4D, 0x1B, 0x9B, 0xDA, 0x35, 0x9F, 0x84, 0x47, 0x0E, 0x3E, 0x3C, 0xC2, 0x47, 0x33, 0x84, 0xEA, 0x94, 0x0A, 0x0D, 0xDA, 0x1F, 0xCB, 0xF3, 0x0E, 0xDC, 0x33, 0x82, 0xDF, 0xF9, 0x6A, 0x06, 0x78, 0xDE, 0xFA, 0x0F, 0xB9, 0xD8, 0xF0, 0xE8, 0x62, 0x25, 0x7A, 0x40, 0x4F, 0x5F, 0xC3, 0x5E, 0x45, 0x20, 0x9D, 0x45, 0x43, 0x46, 0xC8, 0x3D, 0x2B, 0x06, 0x33, 0x02, 0x7E, 0x40, 0xFF, 0x63, 0x11, 0x7D, 0xE4, 0x49, 0xFB, 0xB2, 0xEB, 0x45, 0xEA, 0x3E, 0x43, 0x92, 0x67, 0xBD, 0xD1, 0xF0, 0x6C, 0xE0, 0x71, 0x07, 0x87, 0xD5, 0xFF, 0x72, 0x37, 0xD9, 0x6C, 0x54, 0x64, 0x92, 0x25, 0xEE, 0x13, 0x3C, 0x78, 0xBE, 0x63, 0x47, 0xA1, 0xE0, 0x54, 0x9E, 0x64, 0xE7, 0x81, 0x02, 0xD7, 0x6E, 0xC5, 0xA3, 0xE7, 0xA4, 0x8E, 0x3C, 0xC4, 0x80, 0xAD, 0xDC, 0xE6, 0x60, 0x25, 0x4D, 0xE9, 0xFD, 0xF9, 0xC2, 0x04, 0x46 }; ================================================ FILE: src/Cafe/HW/Latte/Common/RegisterSerializer.h ================================================ #pragma once #include "util/helpers/Serializer.h" namespace Latte { struct GPUCompactedRegisterState { static constexpr int const NUM_REGS = 1854; // tied to g_gpuRegSerializerMapping_v1 uint32 rawArray[NUM_REGS]; }; // convert GPU register state into compacted representation. Stores almost all registers, excluding ALU consts void StoreGPURegisterState(const LatteContextRegister& contextRegister, GPUCompactedRegisterState& registerStateOut); void LoadGPURegisterState(LatteContextRegister& contextRegisterOut, const GPUCompactedRegisterState& registerState); void SerializeRegisterState(GPUCompactedRegisterState& regState, MemStreamWriter& memWriter); bool DeserializeRegisterState(GPUCompactedRegisterState& regState, MemStreamReader& memReader); } ================================================ FILE: src/Cafe/HW/Latte/Common/ShaderSerializer.cpp ================================================ #include "Cafe/HW/Latte/Common/ShaderSerializer.h" #include <boost/container/small_vector.hpp> #include <zstd.h> #include <zlib.h> // compression dictionary for Latte shader code, initialized on boot ZSTD_CDict* s_c_shaderDict{}; ZSTD_DDict* s_d_shaderDict{}; namespace Latte { void SerializeShaderProgram(void* shaderProg, uint32 size, MemStreamWriter& memWriter) { memWriter.writeBE<uint8>(1); // version // compress shader using zstd level 6 boost::container::small_vector<uint8, 4096> compressedBuf; compressedBuf.resize(ZSTD_compressBound(size)); ZSTD_CCtx* const cctx = ZSTD_createCCtx(); size_t compressedSize = ZSTD_compress_usingCDict(cctx, compressedBuf.data(), compressedBuf.size(), shaderProg, size, s_c_shaderDict); ZSTD_freeCCtx(cctx); cemu_assert(!ZSTD_isError(compressedSize)); memWriter.writeBE<uint32>(size); memWriter.writeBE<uint32>((uint32)compressedSize); memWriter.writeData(compressedBuf.data(), compressedSize); } bool DeserializeShaderProgram(std::vector<uint8>& progData, MemStreamReader& memReader) { if (memReader.readBE<uint8>() != 1) return false; // unknown version uint32 progSize = memReader.readBE<uint32>(); uint32 compressedShaderSize = memReader.readBE<uint32>(); if (memReader.hasError()) return false; if (progSize == 0 || progSize >= 1024 * 1024 * 128) return false; if (compressedShaderSize == 0 || compressedShaderSize >= 1024 * 1024 * 128) return false; progData.resize(progSize); auto compressedShaderData = memReader.readDataNoCopy(compressedShaderSize); if (memReader.hasError()) return false; // decompress ZSTD_DCtx* const dctx = ZSTD_createDCtx(); size_t decompressedSize = ZSTD_decompress_usingDDict(dctx, progData.data(), progData.size(), compressedShaderData.data(), compressedShaderData.size(), s_d_shaderDict); ZSTD_freeDCtx(dctx); if (decompressedSize != progSize) return false; return true; } }; extern const uint8 s_shaderDict[]; RunAtCemuBoot _loadShaderCompressionDictionary([]() { // decompress and load dict static std::vector<uint8> s_shaderDictData; s_shaderDictData.resize(0x1B800); ZSTD_decompress(s_shaderDictData.data(), s_shaderDictData.size(), s_shaderDict, 0xA985); s_c_shaderDict = ZSTD_createCDict(s_shaderDictData.data(), s_shaderDictData.size(), 6); s_d_shaderDict = ZSTD_createDDict(s_shaderDictData.data(), s_shaderDictData.size()); cemu_assert_debug(s_c_shaderDict); cemu_assert_debug(s_d_shaderDict); }); const uint8 s_shaderDict[0xA985] = // 0xA985 Uncompressed: 0x1B800 { 0x28, 0xB5, 0x2F, 0xFD, 0xA4, 0x00, 0xB8, 0x01, 0x00, 0x54, 0xD4, 0x02, 0x9A, 0xE6, 0xF3, 0xBF, 0x54, 0x20, 0x9C, 0x11, 0x6D, 0x3A, 0x8F, 0x30, 0xAF, 0xC4, 0x16, 0xAE, 0x9D, 0x11, 0xC3, 0x09, 0x13, 0xCD, 0xE1, 0x18, 0xAB, 0xC1, 0x05, 0xD6, 0x62, 0x83, 0x83, 0xE3, 0x80, 0x9F, 0x58, 0x48, 0x33, 0xD7, 0x72, 0xE6, 0x42, 0x40, 0x91, 0x79, 0x32, 0xFE, 0x24, 0xD4, 0xDA, 0x80, 0x21, 0xFE, 0x5C, 0x06, 0xA3, 0x44, 0x05, 0x2A, 0x0C, 0x87, 0x01, 0x8D, 0x01, 0xD0, 0x98, 0xD0, 0x33, 0x17, 0xC6, 0xCD, 0x33, 0x4C, 0xC8, 0x8A, 0xBB, 0x0E, 0x18, 0x70, 0x25, 0x4C, 0x59, 0x51, 0xD4, 0xB6, 0xFF, 0x7F, 0x5B, 0xDB, 0x29, 0xCB, 0x0B, 0x3A, 0x0C, 0xB1, 0x0B, 0xA7, 0xCF, 0x4E, 0x5F, 0x7E, 0x07, 0x71, 0x26, 0xA6, 0x14, 0x31, 0x8F, 0xB3, 0x2E, 0x78, 0x3A, 0xCD, 0xC7, 0xBF, 0x54, 0xB1, 0x4B, 0x66, 0x16, 0xA3, 0x5D, 0x0B, 0x83, 0x55, 0xEE, 0x66, 0x63, 0x3C, 0x77, 0x52, 0xFA, 0x57, 0xE6, 0x6A, 0x95, 0xDA, 0xBD, 0xB2, 0xB9, 0xB7, 0xD5, 0xBC, 0xB3, 0x7F, 0xED, 0x6C, 0x89, 0x5C, 0xA8, 0x0F, 0xAE, 0x56, 0x49, 0xBF, 0xF3, 0x67, 0x0D, 0x8F, 0x5A, 0x43, 0xA9, 0x0F, 0x20, 0xE5, 0x52, 0xAA, 0xAA, 0x82, 0x83, 0x54, 0x53, 0x17, 0x73, 0x06, 0xC4, 0xF8, 0x9D, 0x96, 0x12, 0xAB, 0xF7, 0xFD, 0x2E, 0x4B, 0x39, 0xA9, 0x0B, 0x0E, 0x5E, 0x22, 0x1F, 0xCC, 0x8C, 0x83, 0x17, 0xF5, 0xC1, 0xA4, 0x2A, 0x22, 0x9C, 0x12, 0x43, 0xF1, 0x46, 0x45, 0x77, 0xDB, 0x6E, 0xD8, 0x51, 0x94, 0xF3, 0xF6, 0xEC, 0x38, 0x58, 0x34, 0xF8, 0x25, 0xAA, 0xF4, 0x98, 0x5D, 0x2E, 0x62, 0x4B, 0x50, 0x80, 0x4B, 0xBF, 0xFB, 0x51, 0x23, 0xB6, 0x05, 0xE8, 0x93, 0xE9, 0x88, 0xD7, 0x95, 0xFD, 0x95, 0xD1, 0xFE, 0x19, 0x75, 0xE8, 0x0F, 0x7E, 0xB4, 0x53, 0x08, 0x91, 0xC9, 0xF7, 0x75, 0x8A, 0x29, 0xDE, 0xD0, 0xB3, 0x05, 0xE9, 0x19, 0x9D, 0xFF, 0x25, 0x3F, 0xED, 0xD2, 0xCD, 0x2E, 0xB7, 0x46, 0xCC, 0x4F, 0x04, 0xA6, 0x06, 0x36, 0xA9, 0x1C, 0xE0, 0x41, 0x5D, 0xE3, 0x9A, 0x8E, 0xCF, 0x49, 0xC0, 0xE6, 0x26, 0xB2, 0x62, 0x32, 0x73, 0x80, 0xE7, 0xD7, 0xB8, 0xC9, 0xA5, 0x4D, 0x2A, 0x6D, 0xA2, 0x7D, 0x12, 0xBF, 0x1D, 0xF6, 0x44, 0x6B, 0xB0, 0x50, 0x95, 0x1F, 0x8D, 0x23, 0x52, 0x4D, 0x28, 0xD8, 0xE3, 0x99, 0xD6, 0xB0, 0x14, 0xE3, 0xF1, 0xE2, 0xD3, 0xF1, 0xF8, 0x2A, 0xA6, 0xC6, 0xAF, 0x24, 0x72, 0xEF, 0xF5, 0x10, 0xF9, 0xFF, 0x6F, 0xDD, 0xA5, 0x4E, 0x04, 0x3F, 0x7A, 0x5F, 0x9E, 0xEE, 0x9D, 0xB4, 0x3D, 0x93, 0x85, 0xF5, 0x55, 0xEB, 0xD4, 0xE7, 0xCE, 0xF1, 0x9C, 0x49, 0x7D, 0xB4, 0x33, 0xCB, 0xB2, 0x87, 0x5A, 0xE6, 0x95, 0x59, 0xA6, 0x44, 0x1F, 0x2C, 0xEB, 0x92, 0x2C, 0xE3, 0xCE, 0xEC, 0x2A, 0x39, 0x9B, 0x4D, 0x0B, 0x49, 0x7D, 0x5D, 0x2F, 0xCD, 0xB1, 0x36, 0x3D, 0xA3, 0xAB, 0xD7, 0xF5, 0xF4, 0x2E, 0xB5, 0x72, 0xE5, 0xCF, 0x8A, 0x4B, 0x71, 0x85, 0x62, 0x1A, 0x2C, 0x2B, 0xFF, 0xA5, 0x33, 0xE5, 0xE8, 0x43, 0x63, 0xF7, 0xF9, 0xC1, 0x93, 0xCE, 0x80, 0x43, 0xAA, 0xFD, 0x05, 0x87, 0xD4, 0xFF, 0xD4, 0x65, 0x04, 0xC1, 0x21, 0x30, 0x94, 0x46, 0xCC, 0x87, 0xE0, 0x4C, 0x03, 0x53, 0x17, 0x18, 0xBD, 0x2C, 0xDD, 0x6F, 0x56, 0x4C, 0x52, 0xAE, 0x09, 0xFF, 0x96, 0x56, 0x93, 0xDF, 0x9B, 0x54, 0x50, 0x5D, 0x4D, 0x40, 0x7B, 0x83, 0x9F, 0x82, 0xF3, 0x0D, 0x8C, 0x09, 0x67, 0xA6, 0x60, 0x13, 0x35, 0xDF, 0xFD, 0xF8, 0x35, 0xB5, 0x50, 0xBC, 0xC2, 0xD2, 0x51, 0xCA, 0x42, 0xAE, 0x5C, 0xCD, 0x06, 0x2D, 0xA4, 0x45, 0x6E, 0xD4, 0x6A, 0x2A, 0x98, 0xAC, 0xDA, 0xE9, 0x35, 0xAF, 0xC7, 0xDA, 0xA0, 0x67, 0x94, 0x22, 0xDA, 0x3F, 0xF4, 0x31, 0xAD, 0x7D, 0x34, 0xAE, 0x59, 0xBA, 0xFF, 0xD4, 0x3E, 0xD3, 0x15, 0x3C, 0x0E, 0xC2, 0xA6, 0x0D, 0xEE, 0x6D, 0x7E, 0x7F, 0xB8, 0xA6, 0x44, 0xFF, 0xF8, 0x0E, 0x28, 0x29, 0x88, 0xB7, 0x20, 0xF7, 0x7E, 0xC9, 0xEF, 0xCB, 0x1B, 0xE1, 0x78, 0x54, 0xB2, 0xFC, 0x18, 0x1F, 0x08, 0x41, 0x5E, 0x4D, 0xD1, 0x08, 0x87, 0x28, 0xAE, 0xA6, 0x26, 0xC2, 0x59, 0xC8, 0xA4, 0x3D, 0xB4, 0xF9, 0x9B, 0x8B, 0x54, 0xA7, 0x65, 0x8E, 0x22, 0xB1, 0x67, 0x16, 0xF8, 0x5B, 0xBA, 0xAD, 0x59, 0xB6, 0xFC, 0xD8, 0xFC, 0x25, 0xF5, 0xFF, 0xFF, 0x46, 0x88, 0x39, 0x56, 0xA8, 0x41, 0x8C, 0x25, 0xE4, 0x10, 0x6B, 0x82, 0x0E, 0x29, 0xE3, 0x2B, 0x03, 0x1D, 0x80, 0xB8, 0xE8, 0x99, 0xB6, 0x82, 0x22, 0xD4, 0x67, 0xD9, 0xB5, 0x6B, 0x6F, 0xDA, 0x9D, 0x00, 0x22, 0xD5, 0xB3, 0x7E, 0x91, 0xFA, 0x8A, 0xBA, 0x5D, 0xC1, 0x08, 0x66, 0xA4, 0x41, 0x0A, 0x3A, 0xA8, 0xC0, 0x0F, 0xEC, 0xB0, 0x86, 0x95, 0x1D, 0x19, 0x58, 0xE9, 0x11, 0x00, 0x12, 0x98, 0xDC, 0xE4, 0x3F, 0x69, 0x3B, 0xE9, 0xC7, 0xED, 0x4F, 0x44, 0x0F, 0x1D, 0xA5, 0x92, 0x25, 0x31, 0xB4, 0x96, 0xF1, 0x8F, 0xFA, 0x27, 0xA1, 0xFA, 0xA1, 0xD0, 0x6D, 0x77, 0xFC, 0xB2, 0x8D, 0x64, 0xDF, 0x7C, 0x5F, 0x3E, 0xD2, 0x5E, 0xC9, 0x7F, 0xB9, 0x48, 0x36, 0x4B, 0xBE, 0x2F, 0x7A, 0x0D, 0x07, 0x46, 0x5F, 0x59, 0xC9, 0xA4, 0x9C, 0x78, 0x32, 0xBE, 0x04, 0x33, 0x79, 0x34, 0x2F, 0x91, 0xCB, 0x1F, 0x67, 0x9E, 0x84, 0x4F, 0x5C, 0xC9, 0x39, 0x98, 0xF6, 0x9D, 0x59, 0xF4, 0xD7, 0xAB, 0x86, 0x5F, 0x14, 0x55, 0x3C, 0x0E, 0x8C, 0xE4, 0x99, 0x2C, 0x51, 0x70, 0xC7, 0x31, 0x49, 0xA1, 0xE9, 0x83, 0xAF, 0x67, 0xBC, 0x94, 0x2B, 0x77, 0xB0, 0x32, 0x43, 0x40, 0x63, 0x48, 0x07, 0x5A, 0x83, 0xF2, 0x4D, 0x81, 0x7B, 0x89, 0x1E, 0xE5, 0x81, 0xF2, 0xBF, 0x29, 0x8B, 0x67, 0x8B, 0x0F, 0x75, 0x20, 0x92, 0x82, 0x28, 0xE1, 0xEB, 0x1D, 0xA5, 0x58, 0xFC, 0x60, 0x32, 0x48, 0x98, 0x91, 0x5B, 0x75, 0xE1, 0x83, 0xAF, 0x1C, 0x41, 0xFA, 0x99, 0x54, 0xA6, 0x64, 0xCA, 0xE0, 0x64, 0xBC, 0x6F, 0x58, 0xAC, 0x08, 0x69, 0x0E, 0xFE, 0x29, 0x2C, 0x56, 0x78, 0x3D, 0xB5, 0xE6, 0xDC, 0x8E, 0xCD, 0x94, 0xF2, 0x78, 0xB8, 0x83, 0x37, 0xEC, 0x54, 0x11, 0xED, 0x21, 0x49, 0xC6, 0xB6, 0x56, 0x6B, 0x7F, 0x52, 0xB5, 0xD6, 0x85, 0xAC, 0xD1, 0x45, 0x9E, 0xC1, 0x7D, 0x15, 0xF3, 0x65, 0xBB, 0x74, 0xAF, 0xB8, 0x4D, 0x56, 0x5A, 0xB9, 0x02, 0xFC, 0x9B, 0xBC, 0xD1, 0x21, 0x6F, 0x45, 0x41, 0x39, 0x31, 0xC9, 0x73, 0x7C, 0x7B, 0xA2, 0x42, 0x7F, 0xC3, 0x1E, 0x22, 0x36, 0x5F, 0xE3, 0xED, 0x53, 0x2C, 0xDF, 0x9D, 0xBE, 0x05, 0xF5, 0xC1, 0x9A, 0x1C, 0x96, 0xC3, 0x97, 0xE8, 0xA3, 0x37, 0x45, 0x9E, 0x79, 0x98, 0x88, 0x7B, 0x35, 0x4C, 0xF4, 0xBF, 0x71, 0x50, 0x4D, 0x66, 0xEE, 0xC6, 0xC5, 0x49, 0xE5, 0xDA, 0x4C, 0x6F, 0x12, 0x3C, 0x3C, 0x58, 0x68, 0x1F, 0xFE, 0xFC, 0x11, 0x81, 0xE6, 0x5C, 0x90, 0x5C, 0xAE, 0xB7, 0x73, 0x25, 0x2F, 0xA5, 0xB8, 0xE0, 0x68, 0x2A, 0x17, 0x2F, 0x8D, 0xC1, 0x45, 0x1F, 0x87, 0xE9, 0xC3, 0xFD, 0xD0, 0xF1, 0x2B, 0x9F, 0x83, 0x8B, 0x99, 0x63, 0x95, 0xF3, 0x58, 0x7A, 0xFB, 0x0F, 0x79, 0x59, 0x4A, 0x03, 0x66, 0xFF, 0xF7, 0x69, 0x98, 0xD6, 0xEB, 0xA9, 0x62, 0x87, 0x60, 0x6C, 0x66, 0xBF, 0xFB, 0x65, 0x4C, 0x94, 0xD3, 0x22, 0xF7, 0xFF, 0xBB, 0x94, 0x4A, 0xC0, 0xEE, 0x3F, 0x36, 0x4F, 0x5E, 0x0F, 0x97, 0xE9, 0xC7, 0x11, 0xEA, 0x97, 0xF8, 0x95, 0x1E, 0xF7, 0xF5, 0x0C, 0x99, 0xD3, 0x1D, 0x57, 0xC6, 0xB3, 0x45, 0x9E, 0xF9, 0xBB, 0x46, 0xA7, 0x56, 0xB9, 0xFA, 0xEC, 0xC1, 0x32, 0xCE, 0xEE, 0xAA, 0xF8, 0xD7, 0xE6, 0x47, 0x95, 0x5E, 0xF9, 0x36, 0x8D, 0x09, 0xFD, 0x3F, 0x74, 0x9D, 0xC1, 0xFF, 0xF4, 0x5D, 0x6A, 0x59, 0xAD, 0x4B, 0xAA, 0xFF, 0x07, 0x8E, 0x99, 0x41, 0xF2, 0xA0, 0xD5, 0x7E, 0x95, 0xAD, 0x4B, 0x36, 0x84, 0xCB, 0x67, 0x4E, 0xC7, 0x4A, 0x46, 0xE1, 0x78, 0x21, 0x8A, 0xE4, 0x7F, 0xF0, 0x43, 0x52, 0xB3, 0xD9, 0xBF, 0x38, 0x21, 0x1D, 0xDC, 0xBF, 0xD8, 0xA5, 0x23, 0xDD, 0xA5, 0xFE, 0x31, 0x38, 0x7E, 0xF7, 0x27, 0x43, 0xEE, 0xCC, 0x9E, 0x0E, 0x9B, 0x49, 0x8E, 0x67, 0xFC, 0x74, 0xB4, 0xE4, 0x1F, 0x38, 0x61, 0x10, 0x43, 0x1D, 0x2B, 0x63, 0x97, 0xBA, 0x18, 0xFF, 0xD3, 0xD1, 0xFB, 0x62, 0xCB, 0x0D, 0xFE, 0x5E, 0x8C, 0xF8, 0xA1, 0x8C, 0x72, 0x3D, 0x75, 0x24, 0xB9, 0x3B, 0x64, 0xF0, 0xF6, 0x3F, 0xD9, 0xA5, 0x98, 0x78, 0x3F, 0x7A, 0x7F, 0x10, 0xAE, 0xF9, 0x3B, 0xC3, 0xB3, 0x01, 0xDD, 0x3D, 0xFD, 0x4B, 0x3F, 0x19, 0x7C, 0x05, 0x4F, 0xBA, 0x15, 0xFD, 0x37, 0x6C, 0xC8, 0x1F, 0xA8, 0xE2, 0xDB, 0xE8, 0xBA, 0x54, 0x96, 0x2E, 0xF7, 0x9D, 0xC6, 0x92, 0x19, 0xDD, 0xD2, 0x96, 0xF8, 0xDC, 0xC4, 0x70, 0x29, 0x73, 0x20, 0x64, 0xC6, 0xF4, 0xC1, 0xA3, 0x06, 0x94, 0x19, 0x5D, 0xE8, 0x8C, 0x18, 0x18, 0xDC, 0x32, 0xB2, 0x98, 0x54, 0x84, 0xAC, 0x8C, 0xBF, 0xE2, 0xB3, 0x35, 0xD5, 0x91, 0x9A, 0x2C, 0xDD, 0x67, 0x5C, 0x0C, 0x4F, 0x96, 0x8E, 0x14, 0xF2, 0x13, 0xF3, 0x39, 0x4F, 0x2E, 0xAF, 0xE5, 0xB7, 0x2A, 0xB3, 0xCA, 0x18, 0x42, 0x28, 0x97, 0x44, 0x88, 0xD7, 0x5C, 0xF3, 0xC0, 0x5E, 0xE6, 0x89, 0x5C, 0x9D, 0x99, 0x0A, 0xD1, 0x71, 0xB5, 0xCA, 0x13, 0x48, 0x0A, 0xB1, 0x11, 0x61, 0x3A, 0x43, 0x35, 0x5E, 0x21, 0x29, 0x96, 0x7F, 0x46, 0x9F, 0xB0, 0x59, 0x44, 0x89, 0x63, 0x8E, 0x62, 0x23, 0xAE, 0xDF, 0x8D, 0x4E, 0x96, 0xE8, 0x2A, 0x76, 0x1C, 0x98, 0x16, 0x5C, 0xCE, 0x4D, 0xF7, 0x50, 0x35, 0x93, 0x25, 0xD8, 0xC1, 0xBB, 0x42, 0x38, 0x45, 0xE7, 0xB7, 0x5B, 0xA6, 0x5F, 0x67, 0xCB, 0x8D, 0xC3, 0x73, 0x0A, 0xCF, 0x77, 0x94, 0xE3, 0x81, 0x88, 0xFF, 0xD3, 0xD5, 0x69, 0xF2, 0x5A, 0x60, 0x33, 0xE5, 0x7E, 0x4E, 0xDC, 0x0D, 0x9E, 0xBF, 0xFA, 0x39, 0x81, 0xE5, 0x09, 0xC4, 0x25, 0x98, 0x76, 0xED, 0xFB, 0x2B, 0xD7, 0xE9, 0x7F, 0x6B, 0x5F, 0x8B, 0x61, 0x18, 0x3D, 0x1B, 0xE2, 0x71, 0x5E, 0xA3, 0x57, 0x7E, 0xC4, 0x69, 0x33, 0x88, 0xEB, 0xA0, 0x9B, 0x41, 0xDF, 0xC7, 0xEF, 0xBC, 0x4F, 0x1D, 0x43, 0xD3, 0x47, 0x8C, 0xD5, 0xD1, 0x33, 0xB7, 0xF1, 0xFF, 0x57, 0x6E, 0xAE, 0x50, 0x7F, 0x95, 0x32, 0x71, 0x25, 0x0B, 0x8B, 0xF6, 0xF6, 0x83, 0x5D, 0xB2, 0x5A, 0x88, 0x7F, 0xCF, 0x95, 0x5B, 0xB8, 0x46, 0x6B, 0x61, 0xF9, 0x4F, 0x27, 0x7B, 0x65, 0xF7, 0xFF, 0x4D, 0x24, 0xA5, 0x81, 0xCF, 0x98, 0x87, 0x23, 0x46, 0xD7, 0xDA, 0xBF, 0x6F, 0x40, 0xC4, 0x08, 0xB4, 0xEF, 0xCE, 0x03, 0x8E, 0xD1, 0x57, 0x86, 0x3C, 0x57, 0x88, 0x6B, 0x85, 0x5E, 0x8A, 0x5A, 0xB0, 0x38, 0xFD, 0xD6, 0x37, 0xE5, 0x60, 0x95, 0xA3, 0xD9, 0x4E, 0x94, 0xD3, 0x79, 0x0E, 0x21, 0xC0, 0x60, 0xE2, 0x81, 0xF1, 0xDC, 0x25, 0x1D, 0x2D, 0x4C, 0xE7, 0x54, 0x26, 0x1F, 0xF7, 0xCA, 0xDE, 0xCE, 0x26, 0x52, 0x1C, 0x58, 0xFE, 0x8F, 0xE8, 0x76, 0x04, 0x53, 0xDE, 0xF4, 0xE6, 0xF8, 0xC5, 0x60, 0x23, 0x9D, 0x4B, 0x99, 0x8C, 0xE7, 0xDA, 0xE4, 0xAF, 0x63, 0x98, 0x73, 0xA0, 0xAF, 0xCA, 0xB3, 0xBC, 0x30, 0x1A, 0xDB, 0x1E, 0x3F, 0x73, 0x33, 0x85, 0x3B, 0xA3, 0x8F, 0x0A, 0x58, 0x96, 0x12, 0x43, 0xE1, 0xBA, 0x0C, 0x32, 0x73, 0xE3, 0x40, 0xE4, 0xEA, 0x95, 0x85, 0x3D, 0x07, 0xCA, 0xF0, 0xFF, 0xB7, 0xCB, 0xE4, 0xBD, 0x25, 0x72, 0x3C, 0x23, 0x88, 0xDF, 0xF5, 0xCB, 0x1C, 0xA6, 0xCE, 0xFA, 0x61, 0x5A, 0x7C, 0x1D, 0xA3, 0xC5, 0x63, 0x5A, 0x28, 0xB9, 0x90, 0x30, 0x8C, 0x61, 0x89, 0x83, 0x45, 0x57, 0xC4, 0x0F, 0xB5, 0xBD, 0x36, 0x6F, 0xA7, 0xC2, 0x36, 0xCE, 0xDC, 0x56, 0x3E, 0x41, 0xC0, 0x6A, 0xD2, 0x42, 0xEC, 0x5D, 0x56, 0x32, 0xB5, 0x6B, 0xC7, 0xF7, 0x7F, 0x24, 0xC2, 0x70, 0x01, 0x0B, 0x94, 0xA8, 0xD1, 0x22, 0x44, 0x12, 0x56, 0x98, 0x64, 0xCF, 0xFC, 0x01, 0x02, 0xB8, 0x2D, 0xC1, 0xB4, 0xE4, 0xCA, 0x17, 0xA7, 0xC5, 0xF2, 0x97, 0x4A, 0x1C, 0x3B, 0x57, 0x42, 0xBB, 0x24, 0xDA, 0x17, 0x13, 0x51, 0x9C, 0x17, 0x89, 0x3B, 0x79, 0x48, 0x9E, 0xC9, 0x8A, 0x14, 0x2E, 0x3F, 0x8B, 0x52, 0x98, 0x0C, 0xAF, 0xBC, 0x48, 0xC0, 0x44, 0x7C, 0x50, 0xC0, 0x46, 0xC1, 0xA0, 0x4A, 0xC6, 0x2D, 0xCB, 0x13, 0x84, 0x95, 0x5E, 0x32, 0x28, 0x8E, 0x29, 0x03, 0xFC, 0x1B, 0x97, 0x5E, 0xFF, 0x7F, 0x83, 0xF8, 0x63, 0x78, 0x9E, 0xE7, 0x79, 0x33, 0x34, 0xA9, 0x16, 0x85, 0x7B, 0x15, 0x6A, 0xE9, 0x0D, 0xBF, 0x7B, 0xDA, 0x27, 0x3D, 0x08, 0x72, 0x2E, 0xF8, 0x1C, 0xE2, 0xC3, 0xD1, 0x73, 0x60, 0x99, 0x79, 0xE8, 0x35, 0x49, 0x2F, 0x49, 0x29, 0x59, 0x65, 0xE5, 0x2C, 0xD2, 0xC1, 0x23, 0x6F, 0x30, 0xDF, 0x75, 0x69, 0x65, 0xA4, 0xAB, 0x31, 0xB2, 0x8F, 0xEC, 0x8D, 0xB1, 0x89, 0x5F, 0x75, 0x91, 0x39, 0x16, 0x9D, 0x70, 0x87, 0xCD, 0xB4, 0x61, 0x32, 0xC6, 0x4A, 0x3C, 0x9A, 0xD7, 0x48, 0xAE, 0xE8, 0x5C, 0x9B, 0xBF, 0x52, 0x8D, 0x33, 0x70, 0xD0, 0x47, 0xEE, 0x2C, 0x1D, 0x31, 0x0A, 0x4F, 0xCC, 0x93, 0x3D, 0xAE, 0x65, 0xF4, 0xCE, 0x98, 0x28, 0x34, 0x4F, 0xD1, 0x27, 0x5A, 0x61, 0xC9, 0x39, 0x8C, 0x9D, 0xB6, 0x14, 0xEB, 0xB4, 0xD7, 0x93, 0xE4, 0xD5, 0xA1, 0xE7, 0xA1, 0xBD, 0x59, 0x8E, 0x31, 0xE9, 0x40, 0x33, 0xC7, 0xA8, 0x1A, 0xAB, 0xB7, 0xC1, 0xD5, 0xFF, 0x37, 0x4E, 0x92, 0xD5, 0x49, 0x31, 0x8D, 0xCD, 0x1E, 0xCD, 0x69, 0xB6, 0xE5, 0xF0, 0x2C, 0xA2, 0xA3, 0xA9, 0x1F, 0xCD, 0x2F, 0x11, 0x27, 0xB7, 0x4D, 0x2B, 0x5D, 0xD1, 0x6B, 0x1D, 0x9E, 0xA5, 0x1E, 0xF6, 0xEB, 0xB1, 0x9D, 0x56, 0x78, 0xBD, 0xF8, 0x38, 0xF4, 0xF9, 0x17, 0xFB, 0xFD, 0xFF, 0xFF, 0x3F, 0xBE, 0x10, 0x3C, 0x13, 0x68, 0xBB, 0x5F, 0xC0, 0x97, 0x73, 0x25, 0x34, 0xA5, 0x8B, 0x7A, 0xFF, 0xA6, 0x2F, 0x87, 0xDC, 0x73, 0xFB, 0xF9, 0x6F, 0xAC, 0x2D, 0x3C, 0xE1, 0x2B, 0xB9, 0x76, 0x3B, 0x1D, 0x0D, 0x2E, 0xD2, 0xC7, 0xF1, 0x49, 0xC1, 0xCD, 0xA4, 0xC2, 0xB9, 0x4C, 0xC2, 0x94, 0xE9, 0x2B, 0xB9, 0x36, 0x79, 0x2D, 0x95, 0x89, 0x2B, 0x55, 0x26, 0x95, 0x2B, 0xCE, 0xAC, 0x5A, 0x26, 0x2E, 0xB7, 0x46, 0x8C, 0x74, 0x6B, 0xFE, 0xF3, 0xFF, 0x2B, 0x61, 0xFE, 0x78, 0x13, 0x67, 0x6C, 0x8D, 0x93, 0x84, 0x55, 0x67, 0x9D, 0x88, 0x0F, 0xC9, 0x3B, 0xC7, 0x92, 0xCF, 0xF8, 0x0F, 0x41, 0x57, 0xB8, 0xC0, 0x78, 0x63, 0x7D, 0x65, 0x9A, 0x70, 0x25, 0x0B, 0x17, 0x20, 0xF8, 0x53, 0xF8, 0x1A, 0x3F, 0x5A, 0x32, 0x47, 0x8F, 0x8B, 0x65, 0xEE, 0x34, 0x40, 0xF7, 0x55, 0x2C, 0xE3, 0x72, 0x01, 0xA1, 0x8A, 0x88, 0xD0, 0x07, 0x44, 0xF6, 0x70, 0x70, 0xB9, 0xE0, 0x4C, 0x2A, 0x0A, 0x4D, 0xD1, 0x78, 0x0E, 0x88, 0x25, 0x7B, 0x3D, 0x17, 0x29, 0xD7, 0x63, 0x43, 0x94, 0x2B, 0x29, 0xAB, 0xC1, 0x73, 0xFB, 0x68, 0x3E, 0x5E, 0x1A, 0xB1, 0x93, 0xEE, 0x77, 0x2F, 0xE1, 0xD8, 0x02, 0x34, 0xCF, 0xEA, 0x21, 0x3E, 0x27, 0xA0, 0xEB, 0xAC, 0xD3, 0x38, 0x4F, 0x5C, 0x9A, 0x74, 0xFB, 0xAC, 0x23, 0x1C, 0x4D, 0x8B, 0x1F, 0x2A, 0x7C, 0xFE, 0xCC, 0x9A, 0x2F, 0x2C, 0xC4, 0x9D, 0xDB, 0x91, 0x63, 0xF3, 0x07, 0x71, 0xB9, 0x45, 0xC8, 0x92, 0xB5, 0x40, 0x0F, 0x93, 0xBD, 0x96, 0xCC, 0xF5, 0x6F, 0xCF, 0x88, 0xC9, 0xE0, 0xDF, 0xF8, 0xEF, 0xD1, 0xFD, 0xDF, 0x27, 0xC2, 0x56, 0xF0, 0x19, 0xB6, 0xFF, 0x4B, 0x47, 0xD0, 0xE6, 0x21, 0xA2, 0x5D, 0x23, 0x3A, 0x2E, 0x7F, 0x8A, 0x9A, 0x05, 0x96, 0x2F, 0xCE, 0x2B, 0xDF, 0xCC, 0x20, 0x48, 0x04, 0x2B, 0x83, 0xBD, 0xB1, 0x5E, 0x3A, 0x10, 0xEE, 0xD9, 0x62, 0xF5, 0xA2, 0xEE, 0x53, 0x8F, 0xAA, 0xE3, 0x4B, 0x67, 0xED, 0xFF, 0xA5, 0x2F, 0xC1, 0xF4, 0x30, 0xD1, 0xED, 0x97, 0xE8, 0x67, 0xE9, 0xDF, 0x14, 0x99, 0xF8, 0x1D, 0x39, 0x23, 0xF6, 0xB0, 0xE7, 0x41, 0x27, 0x6D, 0x36, 0xFF, 0xBF, 0x94, 0x23, 0x7B, 0x61, 0x7C, 0xE4, 0x7A, 0x6A, 0x3E, 0xB9, 0x89, 0x4D, 0xAD, 0x16, 0x3E, 0xF1, 0xF1, 0x64, 0xB5, 0xF8, 0x56, 0xED, 0x7F, 0x9F, 0x5E, 0x0B, 0xCD, 0xB3, 0x48, 0x1C, 0x1E, 0x2D, 0xA4, 0x25, 0x1E, 0xED, 0xEB, 0x51, 0xC7, 0x37, 0x4B, 0xBB, 0xB1, 0xB3, 0x46, 0x3F, 0xCF, 0x1A, 0xDD, 0x8E, 0xC2, 0x1E, 0x36, 0xFF, 0x39, 0xD0, 0xBE, 0x27, 0xB6, 0x4C, 0x7F, 0xA3, 0xA9, 0xF8, 0x64, 0xB4, 0x69, 0xF7, 0x0F, 0xF1, 0x2E, 0x6D, 0x19, 0x68, 0xDF, 0x6F, 0x95, 0x86, 0xBB, 0x37, 0x7D, 0x6B, 0xDE, 0x53, 0x1A, 0x47, 0x6F, 0x7F, 0x73, 0x45, 0xAA, 0x8B, 0xFA, 0xE5, 0x11, 0xB7, 0xD8, 0xFC, 0x14, 0xCA, 0x23, 0x5D, 0x48, 0x8A, 0xDF, 0x0A, 0x37, 0x14, 0x39, 0xE2, 0xA2, 0x26, 0xFA, 0x4C, 0xB4, 0x59, 0xFF, 0x3F, 0x8C, 0xB2, 0xF5, 0x8A, 0xCD, 0xDF, 0x36, 0xBB, 0xFF, 0x3F, 0xC1, 0x30, 0x7A, 0x00, 0x16, 0x04, 0x25, 0x1F, 0x20, 0x8D, 0xE1, 0xD4, 0x80, 0x2B, 0xC6, 0xB0, 0xC3, 0x0A, 0x6E, 0xA0, 0x1C, 0x25, 0x13, 0x5D, 0x44, 0x2D, 0xF9, 0x1F, 0x09, 0xA2, 0x93, 0x71, 0x19, 0x19, 0x8F, 0x7E, 0x17, 0x3A, 0xE9, 0x33, 0x9E, 0xFF, 0xD0, 0x65, 0x6C, 0xA8, 0xFE, 0x11, 0x94, 0x2E, 0x7B, 0x16, 0x22, 0x51, 0x56, 0x60, 0x2F, 0x43, 0xEB, 0xD1, 0x47, 0xA3, 0x1F, 0xDB, 0x2F, 0xBA, 0xC9, 0x47, 0x36, 0x88, 0xEE, 0xE4, 0x3F, 0x41, 0xA1, 0x3C, 0x54, 0xA1, 0x38, 0xE9, 0x23, 0x93, 0x0E, 0x25, 0x8C, 0x33, 0xFC, 0x74, 0xA9, 0xBE, 0x18, 0x25, 0x96, 0xFD, 0x49, 0xF9, 0xFE, 0x7F, 0x49, 0x35, 0xFE, 0x67, 0xDD, 0x07, 0x9E, 0x2C, 0xEC, 0x77, 0xA8, 0x52, 0xE7, 0xB0, 0xC7, 0x9E, 0x28, 0x4B, 0x86, 0x7E, 0x39, 0x9B, 0xE9, 0x6D, 0xC3, 0xE4, 0xED, 0x54, 0xF1, 0xA5, 0x13, 0x0A, 0x0E, 0xB5, 0xE5, 0x73, 0x37, 0x50, 0xFC, 0x0D, 0x38, 0x4D, 0x35, 0x9D, 0xD5, 0x42, 0x3F, 0x24, 0xBD, 0x32, 0x7A, 0x3D, 0xCF, 0xB4, 0x93, 0xA1, 0xDC, 0xCE, 0xC5, 0xDF, 0x9A, 0x72, 0xA8, 0xE3, 0x8E, 0x9F, 0x1A, 0x6C, 0xF2, 0xCF, 0x32, 0x7A, 0x3B, 0x40, 0x8C, 0xEF, 0x72, 0xD0, 0x6B, 0x21, 0xFE, 0x9E, 0x6B, 0x1D, 0x2C, 0xFA, 0x5F, 0xD3, 0x57, 0x6A, 0x31, 0x7A, 0x9C, 0x8E, 0x9A, 0xBF, 0xD2, 0x79, 0xF7, 0x5F, 0x0C, 0xBB, 0xF4, 0x63, 0xFA, 0x4E, 0x1B, 0xED, 0xE9, 0x60, 0xC9, 0x5A, 0xA9, 0x8B, 0x13, 0x67, 0x9E, 0xF5, 0xDE, 0x4C, 0xBF, 0x11, 0xAC, 0x31, 0x64, 0xD5, 0xFC, 0xF8, 0x74, 0xDC, 0x13, 0xC5, 0x49, 0x72, 0xA2, 0xF7, 0xF1, 0x83, 0x2B, 0xC1, 0x1F, 0xF7, 0x46, 0xD8, 0x0C, 0xDA, 0xD3, 0x97, 0x78, 0xFE, 0xFD, 0x50, 0x99, 0xDE, 0x2D, 0x3F, 0x42, 0x6F, 0x7F, 0x70, 0xA6, 0xAD, 0x5E, 0xBF, 0x23, 0xB9, 0x23, 0xA7, 0xD0, 0xE7, 0x1D, 0x5D, 0x75, 0x31, 0x7E, 0xE7, 0x59, 0x2F, 0x95, 0xF9, 0x56, 0x7A, 0xCA, 0xFD, 0x64, 0xCE, 0x88, 0xCD, 0x74, 0xE5, 0x93, 0x71, 0x15, 0x38, 0x32, 0x98, 0x14, 0xE0, 0xC8, 0xC0, 0x9D, 0xA9, 0x2C, 0xA4, 0xB1, 0xE2, 0x4B, 0x9D, 0xF1, 0x4F, 0x29, 0x8E, 0x17, 0xBF, 0x0F, 0x65, 0xB4, 0xB7, 0xCC, 0xD1, 0x7C, 0xC9, 0x71, 0x5A, 0x99, 0x6B, 0x11, 0xAB, 0x4B, 0x9D, 0x85, 0x60, 0x5C, 0xFC, 0x2A, 0x0C, 0xF9, 0x2B, 0xDF, 0x9D, 0x25, 0x43, 0xDE, 0xDB, 0x67, 0x38, 0x66, 0xFE, 0xFF, 0x24, 0x6E, 0x28, 0xF9, 0xF1, 0x9D, 0x43, 0x74, 0x34, 0xBF, 0xC3, 0x4B, 0x5A, 0x64, 0x7C, 0xBB, 0xE5, 0x7B, 0x02, 0xFD, 0xB5, 0x3F, 0x73, 0x04, 0x7F, 0xF2, 0x25, 0x9F, 0x13, 0xD6, 0x3A, 0x3B, 0xD7, 0x27, 0x8F, 0xC6, 0xE6, 0x77, 0x36, 0x94, 0x2B, 0x7F, 0x84, 0x72, 0x2F, 0xD4, 0x3F, 0x04, 0x7D, 0x68, 0xBE, 0x7B, 0x17, 0x43, 0xE2, 0xFF, 0xE7, 0x92, 0x64, 0xEF, 0xFC, 0x91, 0xAF, 0x4E, 0x14, 0xE7, 0x95, 0x40, 0x52, 0xA7, 0x7F, 0x45, 0xC4, 0xDC, 0x79, 0xF2, 0xCC, 0x4D, 0xC8, 0xFF, 0x97, 0x2F, 0xC1, 0x52, 0xE0, 0x1B, 0xD0, 0x75, 0xE9, 0x41, 0x21, 0x1B, 0x1A, 0xE8, 0x38, 0xE6, 0xF8, 0x21, 0xF1, 0x4A, 0xE6, 0x89, 0xD3, 0x91, 0xCA, 0x28, 0x8E, 0x79, 0xBA, 0x85, 0x22, 0x3C, 0x41, 0x67, 0xF4, 0xFB, 0x42, 0x95, 0xBD, 0x63, 0xB2, 0x77, 0x82, 0x0E, 0xFA, 0x6B, 0x46, 0xEE, 0x47, 0x2A, 0xFB, 0x63, 0xF2, 0xFF, 0x67, 0x39, 0x2C, 0x6D, 0x74, 0xB4, 0x54, 0x36, 0x7E, 0x10, 0x7F, 0x09, 0x07, 0x79, 0xA6, 0x8D, 0xEE, 0x38, 0x65, 0x73, 0x65, 0x10, 0xDD, 0x4B, 0x65, 0xD0, 0x5F, 0x44, 0x77, 0x69, 0x4B, 0x81, 0x7F, 0xBF, 0x74, 0x66, 0xDF, 0x8F, 0x1B, 0xFA, 0x6E, 0xC9, 0x9E, 0x11, 0x16, 0xFD, 0x59, 0x32, 0x67, 0xBE, 0xA3, 0x5D, 0x2A, 0xF5, 0xFF, 0x76, 0x6F, 0xC0, 0xBB, 0x54, 0x35, 0x30, 0x95, 0xE1, 0x8B, 0xFA, 0xA5, 0xFF, 0xF6, 0x7F, 0xFD, 0x4F, 0xCF, 0x80, 0x97, 0x21, 0x52, 0x68, 0x00, 0xC0, 0xA2, 0xC0, 0x4A, 0xE7, 0x12, 0xFB, 0xB6, 0x90, 0x44, 0x0D, 0x30, 0xFF, 0x0D, 0x20, 0x44, 0x48, 0x47, 0xFF, 0x0D, 0xFF, 0x74, 0xA3, 0xE9, 0x5E, 0x0C, 0x90, 0xE1, 0xC4, 0x17, 0x54, 0xB8, 0x44, 0xD1, 0x81, 0x1F, 0xB8, 0x5C, 0x48, 0x6E, 0xA5, 0x63, 0xB0, 0x61, 0xC8, 0x0F, 0x2D, 0x47, 0xAC, 0x20, 0x0A, 0x2F, 0x51, 0x88, 0xB6, 0x2E, 0xA4, 0x81, 0xCC, 0x8A, 0xD0, 0x04, 0x35, 0x48, 0xC1, 0x0C, 0xD0, 0x96, 0x05, 0x8D, 0x17, 0x0B, 0xA0, 0xC1, 0x06, 0x56, 0x43, 0x19, 0x62, 0x31, 0xC8, 0xB1, 0x01, 0x0A, 0xFC, 0x70, 0x09, 0x22, 0x0C, 0x20, 0xEC, 0xF8, 0x10, 0xA4, 0x81, 0x19, 0x31, 0x28, 0xB9, 0xC3, 0x69, 0x0D, 0x01, 0x78, 0xA9, 0x80, 0x8E, 0x07, 0x9C, 0x94, 0x80, 0x73, 0x85, 0x48, 0x48, 0xE0, 0xC3, 0x44, 0x87, 0x95, 0x20, 0x48, 0x43, 0xC8, 0x31, 0x60, 0x02, 0x1B, 0x5A, 0x50, 0x64, 0x0A, 0x27, 0x52, 0x30, 0x13, 0x00, 0x22, 0xC2, 0x1C, 0xCA, 0xA0, 0x31, 0x41, 0x6D, 0xBC, 0x90, 0x40, 0x77, 0x4D, 0xA4, 0xE0, 0x28, 0x00, 0x03, 0x3F, 0x38, 0x5A, 0x46, 0x04, 0x8F, 0xA9, 0xE0, 0xEB, 0xB7, 0xA3, 0xE6, 0x37, 0x4C, 0xBD, 0x65, 0xED, 0x9A, 0x7A, 0xEB, 0xBB, 0x8F, 0xFB, 0x07, 0xEB, 0xFF, 0x04, 0xFC, 0x47, 0xC0, 0xFF, 0x03, 0xFC, 0x6F, 0xC1, 0x27, 0x09, 0xC4, 0xF4, 0xE2, 0xFC, 0xCF, 0x32, 0x78, 0x07, 0x1C, 0xFF, 0x87, 0xB6, 0xF1, 0x2E, 0xC9, 0xE4, 0x07, 0xBC, 0x76, 0x70, 0x05, 0x78, 0x51, 0xF7, 0x0C, 0x27, 0xEA, 0xDD, 0xF9, 0x4F, 0x92, 0xAD, 0x84, 0xEC, 0x2E, 0xB2, 0xCB, 0x82, 0xCA, 0x33, 0x60, 0x8B, 0x5C, 0x72, 0x2C, 0x0A, 0xC9, 0xBA, 0x59, 0x25, 0xA7, 0xAE, 0x1C, 0x85, 0xE2, 0xA3, 0xDC, 0x2E, 0x50, 0x1E, 0x06, 0x94, 0x93, 0x2C, 0xF1, 0xE4, 0xF2, 0x95, 0x3B, 0xDC, 0x11, 0x84, 0x63, 0x66, 0x8E, 0xC6, 0x8E, 0x49, 0x40, 0x21, 0x80, 0x2D, 0x92, 0xE8, 0x3F, 0x89, 0x1F, 0xC7, 0xB9, 0x45, 0xC6, 0x5A, 0xE4, 0x97, 0xB4, 0x18, 0x95, 0x00, 0x80, 0x15, 0x3A, 0x48, 0x42, 0x86, 0x00, 0x3B, 0x76, 0x24, 0x71, 0x84, 0x21, 0x86, 0xD8, 0xF2, 0xD7, 0x63, 0x79, 0x96, 0x78, 0x3B, 0x5F, 0x72, 0xEE, 0xB5, 0xE4, 0x57, 0xEC, 0xD8, 0x81, 0x04, 0xBE, 0x3F, 0xF8, 0xB7, 0xD7, 0x53, 0x97, 0xBC, 0x64, 0xDE, 0x81, 0x3B, 0x5F, 0x2A, 0x51, 0x03, 0x01, 0x29, 0x48, 0xC2, 0x0A, 0x92, 0x8F, 0x7F, 0x01, 0xFF, 0x71, 0x5A, 0x28, 0xA5, 0x12, 0x25, 0x09, 0x24, 0xC2, 0x60, 0xC2, 0x86, 0x3B, 0x6D, 0x5D, 0x72, 0x8F, 0x72, 0xFF, 0xCE, 0xA6, 0x4B, 0x1B, 0xE9, 0x12, 0xDE, 0xD1, 0xE9, 0x5F, 0xF0, 0x9C, 0xC8, 0x15, 0xEC, 0x84, 0x79, 0x68, 0x88, 0xC4, 0x1C, 0xFB, 0x3E, 0xE3, 0x47, 0xB9, 0xF3, 0x33, 0x7F, 0xFF, 0x3B, 0xB2, 0xA3, 0x6E, 0x9F, 0x1C, 0x96, 0x9F, 0x4C, 0xE6, 0x5C, 0x20, 0x93, 0x4F, 0xFF, 0xFF, 0x9E, 0x23, 0x7F, 0x7C, 0xF0, 0xE7, 0x4F, 0xCC, 0xE7, 0xE4, 0xA1, 0xF8, 0x84, 0xBD, 0x4C, 0x1D, 0x17, 0xBD, 0x67, 0xAB, 0xB4, 0x51, 0xA5, 0xFE, 0xB3, 0x5F, 0x82, 0x40, 0xA0, 0x39, 0x5E, 0x33, 0xE8, 0x5E, 0x5A, 0x44, 0xC7, 0xD5, 0x12, 0xDD, 0x2C, 0x45, 0xE0, 0x8B, 0xF0, 0x88, 0x54, 0x60, 0xA2, 0x22, 0x2F, 0x91, 0x8A, 0x69, 0x33, 0x92, 0x2B, 0x1F, 0x91, 0x72, 0xAF, 0xF1, 0x9C, 0x8F, 0xD8, 0xAB, 0x2F, 0x6C, 0x32, 0x89, 0x64, 0xBC, 0xDC, 0x17, 0xAA, 0x4C, 0x43, 0xF2, 0xE3, 0xC9, 0xDE, 0x17, 0x29, 0xD1, 0x9B, 0x27, 0x73, 0x3C, 0x19, 0x3D, 0xF9, 0x79, 0x57, 0x64, 0x22, 0x39, 0x95, 0x88, 0x91, 0xA8, 0x46, 0xF2, 0x22, 0x69, 0xF1, 0x5C, 0x4C, 0x3C, 0x12, 0x98, 0xE8, 0xF5, 0xCA, 0x75, 0xC2, 0xF5, 0xEF, 0xD5, 0xEB, 0xAB, 0x33, 0xDB, 0xCF, 0xF4, 0x8C, 0xF0, 0x8C, 0xDC, 0x17, 0x9D, 0x5F, 0x82, 0x9D, 0xBE, 0x1F, 0x1D, 0xD4, 0xA5, 0xD8, 0x29, 0xFF, 0xAF, 0xF1, 0x93, 0x83, 0x4D, 0xF6, 0x44, 0x4E, 0x87, 0x2A, 0x73, 0x4B, 0x74, 0xEE, 0x50, 0x47, 0x2A, 0xE3, 0x92, 0x19, 0xF4, 0x5F, 0xD1, 0xA7, 0x86, 0x94, 0xA8, 0x43, 0x1D, 0xAD, 0xFD, 0x38, 0x7F, 0x5D, 0x48, 0x69, 0x6F, 0x9C, 0x3D, 0x1D, 0x2A, 0x11, 0xFD, 0x2B, 0x73, 0x3A, 0x6C, 0x44, 0xB4, 0x5F, 0xB9, 0xFE, 0x21, 0x49, 0x71, 0x6C, 0xFC, 0xDD, 0x17, 0xE8, 0x16, 0xC9, 0xEE, 0x92, 0x81, 0xA4, 0x11, 0x1F, 0x3C, 0x6A, 0x40, 0x19, 0x8A, 0xA9, 0x10, 0x31, 0x30, 0xB8, 0x85, 0x85, 0xDE, 0x1B, 0x11, 0xE2, 0x9E, 0x34, 0xB2, 0x51, 0x21, 0x8C, 0xD4, 0x5F, 0x88, 0xFF, 0x82, 0x40, 0x33, 0x5C, 0x8A, 0xCB, 0x49, 0x8A, 0xCA, 0x28, 0x85, 0x5E, 0x4B, 0x39, 0x97, 0xCD, 0x64, 0xCF, 0xED, 0x92, 0x89, 0x2A, 0x99, 0x15, 0x29, 0xF7, 0x33, 0x8A, 0x77, 0xE5, 0x23, 0xFE, 0x9F, 0xC9, 0xBD, 0xC4, 0x7F, 0x3D, 0xB1, 0xD1, 0xCB, 0x89, 0x8D, 0x5C, 0x4F, 0x6C, 0x0C, 0x5F, 0xE2, 0xCB, 0xF1, 0xE5, 0xD8, 0xDF, 0x6F, 0xC0, 0x32, 0xCD, 0x81, 0x8D, 0x60, 0xE3, 0xDF, 0x1B, 0xDD, 0x34, 0xA7, 0xB3, 0x97, 0x73, 0xB8, 0x63, 0xC7, 0xC6, 0x31, 0xE9, 0xD7, 0x53, 0xC7, 0x97, 0x5E, 0x94, 0xC4, 0xF1, 0x62, 0x7C, 0x92, 0x11, 0x3C, 0x11, 0x39, 0xEC, 0xF2, 0x06, 0x7D, 0x27, 0xED, 0x7E, 0x6F, 0x90, 0xC7, 0x9D, 0x98, 0x8E, 0xC0, 0x35, 0x50, 0xDC, 0x06, 0xFA, 0xD0, 0x08, 0xE5, 0xA0, 0x91, 0x3E, 0x91, 0xD8, 0x48, 0x6A, 0xA4, 0xD7, 0x88, 0xEA, 0xF4, 0x96, 0xCD, 0x6E, 0xC4, 0xC6, 0xF4, 0x15, 0x9B, 0x81, 0x5F, 0xA8, 0x46, 0xD2, 0x88, 0x2A, 0xC3, 0x54, 0x08, 0x55, 0x76, 0xBF, 0x68, 0xF1, 0x46, 0x54, 0xFF, 0x59, 0x2C, 0xF9, 0x37, 0x93, 0xAC, 0x5A, 0x39, 0x9A, 0x5A, 0x5F, 0xCD, 0x5B, 0xBE, 0x62, 0xD1, 0x26, 0x92, 0x29, 0x98, 0x7E, 0x34, 0x37, 0xFD, 0x8E, 0xBF, 0x22, 0x3B, 0x92, 0x29, 0x77, 0xBC, 0x17, 0x6D, 0x27, 0x32, 0xE5, 0xF4, 0xA5, 0x2B, 0x7A, 0xA7, 0x15, 0xFF, 0x13, 0x77, 0xAE, 0x9C, 0xC2, 0x9D, 0xE7, 0x0F, 0x8C, 0x2A, 0x72, 0xA8, 0x9A, 0x9F, 0x82, 0x4F, 0xDC, 0x4D, 0xCD, 0x61, 0xCE, 0x96, 0x7F, 0x53, 0x47, 0x98, 0x7C, 0x99, 0x53, 0x4A, 0x4B, 0x06, 0xB7, 0xFC, 0xC1, 0x4E, 0x5E, 0x29, 0x3E, 0x2C, 0x47, 0xEF, 0x1C, 0x7F, 0x63, 0xFD, 0x3E, 0x67, 0x8A, 0x2F, 0x4F, 0xD9, 0xDB, 0xB1, 0x94, 0xA3, 0xE9, 0xA0, 0xAF, 0x7C, 0xC6, 0x9F, 0x9E, 0x2C, 0xC1, 0xAA, 0x78, 0xD6, 0xCE, 0x96, 0xA9, 0xD8, 0xAC, 0x4A, 0xBF, 0x30, 0x7E, 0xE2, 0x72, 0xEC, 0x48, 0xBB, 0x9E, 0x2D, 0x93, 0x3D, 0x56, 0x0C, 0x69, 0x7E, 0xEA, 0x2F, 0x8C, 0x8D, 0x7A, 0x97, 0x72, 0x39, 0x35, 0x77, 0x98, 0x7E, 0x3B, 0xDB, 0xE9, 0x5A, 0x8D, 0x33, 0x29, 0x48, 0x0E, 0x91, 0x89, 0x81, 0xC1, 0x2D, 0x22, 0xC6, 0xE2, 0x1D, 0xB2, 0x89, 0xE2, 0xC5, 0xE2, 0x8F, 0x78, 0x66, 0xF1, 0x58, 0xFC, 0x4C, 0x11, 0x93, 0xA2, 0x67, 0x8E, 0x23, 0xF8, 0x6D, 0xD3, 0xC6, 0x67, 0xAE, 0xBB, 0xDB, 0x5D, 0xFB, 0x4C, 0xAF, 0x04, 0x13, 0xB9, 0x9F, 0x6C, 0xBE, 0x05, 0x75, 0xFF, 0x9F, 0x72, 0x67, 0x6E, 0x21, 0xDF, 0x5E, 0xD0, 0x69, 0xC7, 0x02, 0xFE, 0xA7, 0x25, 0x33, 0xE3, 0xF0, 0xE4, 0x0B, 0x93, 0x1D, 0x67, 0x07, 0x57, 0x32, 0xFC, 0xEE, 0x66, 0x5D, 0xB3, 0xD2, 0xC6, 0x69, 0x13, 0xE6, 0x6F, 0x4C, 0xCA, 0x73, 0x1A, 0x83, 0x2F, 0x05, 0xD4, 0x04, 0xDA, 0xA0, 0x82, 0x17, 0x0A, 0x64, 0x11, 0xF3, 0x49, 0xC2, 0x72, 0xC4, 0x17, 0xB0, 0x00, 0x81, 0xC9, 0x56, 0x76, 0xC9, 0x14, 0x71, 0x66, 0xCD, 0xF1, 0x66, 0xB5, 0x7C, 0xF6, 0xF2, 0x32, 0x3C, 0x58, 0xF8, 0xE9, 0xFB, 0xE5, 0xFE, 0x9C, 0x59, 0x86, 0x38, 0xCA, 0x8F, 0x88, 0xB2, 0x7B, 0xE9, 0x88, 0x12, 0xFF, 0x6F, 0x56, 0x0D, 0x09, 0xDE, 0xC0, 0x37, 0x9E, 0xE9, 0xBD, 0x71, 0xFA, 0x1B, 0x5F, 0x59, 0x59, 0x59, 0xA1, 0xE4, 0x85, 0xC4, 0xD0, 0x19, 0x31, 0xB4, 0x25, 0xC8, 0x0D, 0x12, 0x24, 0xC8, 0x9F, 0x5D, 0xFC, 0x04, 0x2B, 0xAC, 0xD0, 0x81, 0x77, 0x1A, 0x11, 0xD2, 0xD4, 0x25, 0xB4, 0xCF, 0x99, 0xD9, 0xEC, 0xF1, 0x5F, 0x99, 0x3F, 0x9A, 0xD6, 0xE8, 0xB9, 0x52, 0x38, 0xEC, 0xF9, 0x1D, 0x48, 0xAC, 0x95, 0x60, 0xFF, 0x37, 0xB3, 0xB4, 0xC9, 0x37, 0x2F, 0x11, 0x44, 0x8A, 0x8E, 0xF3, 0x5E, 0x19, 0x0A, 0x7D, 0x3D, 0x7E, 0x07, 0x5A, 0x72, 0x89, 0xFE, 0x77, 0x7D, 0x8E, 0x24, 0xB4, 0x28, 0xC6, 0x71, 0x55, 0xD0, 0x75, 0x1C, 0x58, 0xCE, 0x2D, 0x73, 0x5E, 0x66, 0xD0, 0xCA, 0x54, 0xEC, 0x45, 0x4E, 0x91, 0xD1, 0xBE, 0x02, 0x92, 0xD1, 0x01, 0xDC, 0x3A, 0x22, 0x78, 0x69, 0xCA, 0xB9, 0xD8, 0x48, 0x57, 0x3F, 0x25, 0x59, 0xEA, 0x22, 0x83, 0x2D, 0xA3, 0xBD, 0xC0, 0xFE, 0x50, 0xFC, 0xB8, 0x33, 0xE8, 0xE6, 0xF9, 0xE6, 0x9A, 0xE0, 0x07, 0x65, 0xF8, 0xDF, 0xAC, 0x2D, 0x74, 0xAD, 0xB3, 0x59, 0xE8, 0x72, 0x71, 0x78, 0xCA, 0xD2, 0x2C, 0xC1, 0x6F, 0xE7, 0x8F, 0x63, 0xF6, 0x93, 0xE8, 0xA2, 0x07, 0x2C, 0x2C, 0x51, 0x85, 0x2F, 0x50, 0xC1, 0xE5, 0x40, 0x76, 0x09, 0xA6, 0x17, 0x47, 0xA7, 0xB9, 0x52, 0xC3, 0x5A, 0x56, 0x84, 0xE5, 0x34, 0x02, 0xFF, 0xAB, 0x48, 0x13, 0x4E, 0x9A, 0xE9, 0x6F, 0xA0, 0x87, 0xCE, 0xEC, 0x33, 0x4C, 0x6F, 0xBE, 0xDB, 0xD2, 0xAE, 0xE4, 0xB1, 0xF4, 0x03, 0xFF, 0x3D, 0x08, 0x86, 0x08, 0xC0, 0x18, 0x58, 0xE0, 0xBD, 0x96, 0x0C, 0xD9, 0x81, 0x0D, 0x94, 0xD0, 0x83, 0x16, 0x5B, 0x28, 0x72, 0x30, 0x86, 0xA5, 0x23, 0xAB, 0x78, 0xC1, 0x5E, 0x66, 0xAB, 0xF6, 0xE0, 0x1A, 0x5B, 0x65, 0x4A, 0x35, 0xDE, 0x2A, 0x52, 0x63, 0x0F, 0x3D, 0x56, 0x51, 0xD3, 0x03, 0x98, 0xEF, 0xAB, 0xA8, 0x19, 0x8F, 0x7B, 0x00, 0x47, 0x5C, 0xBF, 0x13, 0x06, 0xDF, 0x9A, 0x79, 0xA7, 0x76, 0x92, 0xF5, 0x9A, 0x69, 0xDC, 0x59, 0xE2, 0x69, 0x4C, 0xBB, 0xEC, 0x7F, 0x77, 0x76, 0x3C, 0x37, 0x14, 0xFE, 0xBD, 0x63, 0x5B, 0xD4, 0x9F, 0xE1, 0x95, 0xF1, 0x26, 0xE5, 0x26, 0xE5, 0xA4, 0x2C, 0x37, 0x29, 0x37, 0x60, 0xCA, 0x89, 0x10, 0x9E, 0xE6, 0xB7, 0x46, 0xDD, 0x6E, 0xCD, 0xCC, 0xEC, 0x35, 0x4C, 0x8F, 0x9F, 0x1D, 0x81, 0x04, 0x17, 0x4B, 0x87, 0x0D, 0x6A, 0x82, 0xD4, 0xD4, 0x60, 0x04, 0xA1, 0x0E, 0x0E, 0x1A, 0x97, 0x08, 0x32, 0x26, 0x3E, 0x90, 0x4D, 0x00, 0x04, 0x59, 0x04, 0x40, 0x50, 0xE6, 0x28, 0xE2, 0x2D, 0x06, 0xD0, 0x89, 0xA9, 0xE1, 0x32, 0xD1, 0xE1, 0x6B, 0x59, 0x39, 0x64, 0x15, 0x0E, 0x4D, 0x94, 0x39, 0xD0, 0xF0, 0x53, 0x02, 0xAE, 0x94, 0x31, 0xF0, 0xC0, 0x08, 0x4B, 0xB4, 0x4C, 0x32, 0x02, 0xE5, 0x4D, 0x08, 0x92, 0x40, 0x22, 0x07, 0x04, 0x80, 0xFC, 0x99, 0x0B, 0xC7, 0x05, 0x28, 0x60, 0xC4, 0x04, 0x1C, 0xF0, 0x32, 0x04, 0xB6, 0x48, 0x78, 0x10, 0x78, 0x80, 0x11, 0x08, 0x28, 0x62, 0x5B, 0x40, 0x01, 0x3C, 0x08, 0x1C, 0x40, 0x08, 0x16, 0x21, 0x4A, 0x2A, 0x39, 0x80, 0x08, 0xC0, 0x0F, 0x34, 0x6A, 0x0F, 0x47, 0xA2, 0xEC, 0x64, 0xF4, 0xD0, 0x3A, 0x8A, 0xA5, 0x26, 0x5C, 0x0B, 0x2B, 0x51, 0xA2, 0x95, 0x22, 0x07, 0x09, 0x94, 0xDD, 0xEF, 0x06, 0xD7, 0x16, 0x50, 0xB0, 0x65, 0x0E, 0x22, 0x88, 0x5A, 0xF6, 0x3A, 0xA4, 0xFF, 0x53, 0x40, 0x51, 0x05, 0x6A, 0x07, 0x0C, 0x8E, 0x90, 0xE1, 0x88, 0x4D, 0x55, 0xA0, 0x7A, 0x60, 0x0D, 0x6A, 0xF8, 0x51, 0xDE, 0xFF, 0x6F, 0x07, 0xBD, 0xC7, 0x55, 0x4C, 0xEC, 0x68, 0xE9, 0x83, 0x47, 0x0D, 0x48, 0x7F, 0x70, 0xE7, 0x28, 0xD3, 0xD1, 0x1B, 0x03, 0x83, 0xA9, 0x77, 0xAE, 0x4E, 0xA0, 0x96, 0xF1, 0x96, 0xF7, 0xD2, 0xCF, 0xAC, 0x62, 0x80, 0x2F, 0xB7, 0x7C, 0xCB, 0x4A, 0x7F, 0x96, 0xEE, 0xF7, 0x61, 0xFF, 0x02, 0x79, 0x94, 0x3B, 0xAB, 0x18, 0x2F, 0x9D, 0x42, 0x20, 0xE5, 0xF5, 0x3C, 0x26, 0xF0, 0xA0, 0x0E, 0x74, 0xA1, 0xC8, 0x8C, 0x1F, 0x10, 0xA1, 0xC8, 0x07, 0x52, 0x5C, 0xA4, 0xE1, 0x0D, 0x59, 0xE8, 0xA6, 0x14, 0xB8, 0x0F, 0xD5, 0xCB, 0xB0, 0xD9, 0x37, 0x93, 0x67, 0x59, 0x1C, 0x42, 0xCF, 0xFE, 0xD6, 0x81, 0x81, 0x9E, 0xC2, 0xA7, 0x27, 0xDB, 0xCC, 0x42, 0xF0, 0xFD, 0x8C, 0x85, 0xEA, 0x12, 0x11, 0x4D, 0x96, 0x4C, 0x14, 0xBE, 0x3D, 0x7D, 0x73, 0x3E, 0xF9, 0x92, 0xDC, 0x4F, 0x36, 0xB3, 0xEF, 0x30, 0xA8, 0x1F, 0xE8, 0xF8, 0x3F, 0x88, 0x23, 0xE3, 0x98, 0xA8, 0xFB, 0xAC, 0x51, 0xA5, 0xD3, 0x88, 0x97, 0x38, 0xF6, 0x5D, 0xB2, 0x34, 0x3A, 0x19, 0xB6, 0x3A, 0xF3, 0xBF, 0x13, 0xF5, 0x32, 0x28, 0x49, 0x45, 0x95, 0x99, 0x77, 0x9E, 0xD4, 0x7D, 0xA0, 0xDF, 0xDB, 0x89, 0x40, 0x54, 0x3E, 0xE5, 0x66, 0x55, 0x34, 0xA6, 0x65, 0x13, 0x3F, 0x36, 0x35, 0x3A, 0x98, 0xBF, 0x39, 0x11, 0x56, 0x14, 0x83, 0xB5, 0x98, 0x17, 0x2C, 0x08, 0x03, 0xCF, 0x13, 0x2F, 0x26, 0x28, 0x40, 0x00, 0x25, 0x7A, 0x20, 0x91, 0x63, 0x85, 0x01, 0x15, 0x87, 0x77, 0x63, 0x00, 0xA0, 0xB6, 0xA2, 0x09, 0x11, 0x82, 0xAB, 0x01, 0xE0, 0x03, 0x6D, 0xE8, 0x11, 0xC6, 0x1A, 0x04, 0x70, 0x06, 0x0D, 0x94, 0xC1, 0x0B, 0x20, 0xB8, 0x70, 0x81, 0x24, 0x5A, 0xF8, 0x41, 0x0A, 0x14, 0x78, 0x21, 0x81, 0xA9, 0x36, 0xB8, 0x78, 0xC1, 0x01, 0x90, 0x24, 0xA2, 0xC8, 0x41, 0x6A, 0x40, 0x3C, 0x19, 0x61, 0xAA, 0xD1, 0x82, 0x22, 0x56, 0x10, 0xA6, 0x98, 0x00, 0x87, 0x0E, 0x1A, 0x1E, 0x9C, 0x40, 0x80, 0x07, 0x3A, 0x40, 0x80, 0xDA, 0xC9, 0xAC, 0x64, 0x03, 0x28, 0x22, 0xBB, 0x91, 0x55, 0x38, 0x04, 0xD1, 0xD2, 0x91, 0x42, 0x91, 0x18, 0x13, 0xD4, 0x03, 0x00, 0xBD, 0x3E, 0x50, 0xEE, 0x0E, 0x29, 0x6F, 0xE0, 0xE1, 0xC4, 0x02, 0x67, 0xA2, 0xB4, 0x56, 0x92, 0xE0, 0xC2, 0x81, 0x85, 0xC9, 0x15, 0xAE, 0x15, 0x1E, 0x8C, 0x6E, 0x85, 0x4B, 0xE6, 0x16, 0x0A, 0xDA, 0xFD, 0xE4, 0x27, 0x59, 0xF6, 0xEC, 0x2B, 0x2C, 0x93, 0xA3, 0x94, 0x6E, 0x8E, 0x90, 0x0C, 0xD6, 0x90, 0xB8, 0x52, 0x9C, 0x90, 0x82, 0x16, 0x11, 0x44, 0x30, 0xC2, 0xA3, 0xB5, 0xDD, 0xD5, 0xA2, 0x55, 0x61, 0x82, 0x16, 0x10, 0xB7, 0xDD, 0x5A, 0x90, 0x22, 0x3E, 0x36, 0x8B, 0x69, 0x6D, 0x46, 0x27, 0xD2, 0x44, 0x33, 0x5A, 0xD5, 0xB4, 0xAA, 0x44, 0x04, 0x75, 0xA0, 0x43, 0xF6, 0xE3, 0x01, 0x36, 0x84, 0x91, 0x81, 0xFA, 0xB3, 0x89, 0x96, 0x8E, 0x16, 0x33, 0x45, 0xD0, 0xF9, 0x91, 0x7C, 0x45, 0xC6, 0x44, 0xCB, 0xC8, 0x1D, 0xAC, 0x88, 0x00, 0x0E, 0x88, 0xD8, 0xC1, 0x06, 0x07, 0x99, 0x02, 0x02, 0x40, 0xED, 0x80, 0x56, 0x8A, 0x04, 0xAE, 0x1C, 0x17, 0x28, 0xA2, 0xBC, 0x05, 0x86, 0x68, 0x75, 0x31, 0x4C, 0xB4, 0xEC, 0x8F, 0x3B, 0x8B, 0x00, 0x8C, 0x02, 0x96, 0xA0, 0x83, 0x03, 0x5A, 0x8C, 0x68, 0xC0, 0x18, 0x2D, 0x05, 0x68, 0xD5, 0x03, 0x18, 0x20, 0x0E, 0xA5, 0x1B, 0xAD, 0x0B, 0xC0, 0xF5, 0x20, 0x82, 0xE8, 0xD6, 0xC4, 0x28, 0x57, 0xB2, 0x1E, 0x02, 0xD0, 0xCA, 0xC1, 0xFF, 0xD3, 0x9F, 0x96, 0x96, 0xBD, 0xC5, 0x4D, 0xD7, 0x50, 0xBE, 0xE0, 0x34, 0xD5, 0xF4, 0x0A, 0xAE, 0x18, 0x4C, 0xB7, 0xA0, 0xFF, 0x53, 0x5A, 0xAD, 0xCF, 0x90, 0x69, 0x4A, 0x45, 0x91, 0xF3, 0xA4, 0x98, 0xE6, 0x61, 0x87, 0xAE, 0x3C, 0x82, 0x0D, 0x32, 0x5A, 0x78, 0x0C, 0x54, 0x38, 0x59, 0x02, 0xA9, 0x75, 0x84, 0x10, 0x5C, 0x31, 0x39, 0x88, 0xA2, 0x4F, 0xFF, 0x8B, 0x4A, 0x1C, 0xEF, 0x70, 0x3F, 0x08, 0x14, 0x7F, 0x6D, 0xB1, 0x74, 0x34, 0xC6, 0x7C, 0x9F, 0xCC, 0x9E, 0x65, 0x2A, 0x38, 0x22, 0xBA, 0x0C, 0x94, 0x6E, 0xF4, 0x6B, 0xD9, 0x7E, 0xA4, 0x1D, 0x01, 0x42, 0xB4, 0xB7, 0xD4, 0x06, 0xEE, 0xB1, 0x23, 0xD9, 0x2D, 0xF9, 0xBE, 0xE7, 0xE0, 0xFF, 0x1F, 0x25, 0x18, 0x35, 0x5F, 0x34, 0x04, 0xA3, 0x8A, 0x5F, 0x4F, 0x3D, 0xBD, 0xC4, 0x23, 0x72, 0x3D, 0xDB, 0x68, 0x02, 0xB9, 0x2F, 0x20, 0x30, 0x20, 0x30, 0xD2, 0xF7, 0x22, 0x79, 0x3D, 0xA4, 0x73, 0x3E, 0x19, 0xFF, 0xA7, 0x10, 0x2E, 0x95, 0xC9, 0xF7, 0x3F, 0x40, 0xBC, 0x9C, 0xE7, 0x30, 0x0C, 0x11, 0xB6, 0x19, 0x8E, 0x69, 0x5F, 0xB3, 0x67, 0x97, 0xEB, 0xB9, 0x92, 0x0F, 0xF1, 0xC2, 0xF6, 0xE6, 0x33, 0x7A, 0xE7, 0x88, 0xF1, 0xC9, 0xD7, 0x68, 0xF7, 0xA1, 0x35, 0x0F, 0xED, 0xF6, 0xF9, 0xF3, 0xFC, 0xCF, 0x88, 0x8D, 0x40, 0xCC, 0x43, 0x57, 0x0F, 0x9A, 0x39, 0xAB, 0xCB, 0xF4, 0x67, 0x5D, 0x88, 0xCF, 0xA8, 0xFF, 0x87, 0x3D, 0x5B, 0x46, 0x39, 0x31, 0xB2, 0x16, 0x68, 0x6A, 0x3E, 0x0D, 0x99, 0xEC, 0xF5, 0x3C, 0xA3, 0x2B, 0x31, 0x32, 0xF7, 0x25, 0x97, 0x87, 0x58, 0xD6, 0x9C, 0xDE, 0xB1, 0xFC, 0x17, 0x34, 0x65, 0x31, 0x06, 0x7D, 0x22, 0x01, 0x62, 0x90, 0x3C, 0x39, 0xA4, 0x46, 0xEF, 0x2F, 0x7A, 0xF4, 0x9B, 0x99, 0xBB, 0xA2, 0xE6, 0xCE, 0x42, 0x45, 0x0A, 0x73, 0xAE, 0x22, 0x7D, 0x21, 0xAF, 0x0C, 0x3D, 0xF4, 0x04, 0xD7, 0xE3, 0x7F, 0x44, 0xF6, 0x35, 0x8E, 0xEC, 0x5E, 0x05, 0xBF, 0x0A, 0xB8, 0x13, 0x91, 0x81, 0xD9, 0x33, 0xC5, 0x59, 0xCD, 0xF4, 0x66, 0x26, 0xE9, 0x99, 0x49, 0xF2, 0xFA, 0x2B, 0x2D, 0x3E, 0x3A, 0x3E, 0xA4, 0xEF, 0x37, 0x0B, 0x3F, 0x1A, 0x9B, 0xEF, 0xD1, 0x24, 0xC0, 0x92, 0xB2, 0xB0, 0xA4, 0x66, 0xC9, 0x40, 0x15, 0x14, 0xE0, 0x01, 0xA0, 0xE0, 0x1A, 0x00, 0x05, 0x4F, 0x00, 0x0A, 0x70, 0xDF, 0xDF, 0x8B, 0x75, 0xBC, 0x56, 0x3C, 0x16, 0xCF, 0x59, 0xEB, 0x9D, 0xDF, 0x3D, 0xF3, 0xA1, 0x2D, 0x07, 0x5D, 0x04, 0x8F, 0xFF, 0x56, 0x09, 0xA2, 0x6F, 0x12, 0xC9, 0xDA, 0x9B, 0x49, 0xDC, 0xE3, 0xFD, 0xCA, 0xFC, 0x07, 0x5D, 0xFB, 0x4E, 0x1D, 0xED, 0xFE, 0x87, 0xBF, 0x24, 0xA3, 0xE1, 0x1B, 0xF5, 0x74, 0x6F, 0xD8, 0xD3, 0x39, 0x9E, 0xFF, 0x37, 0x36, 0xB1, 0x57, 0x21, 0xFC, 0xB2, 0x85, 0xEE, 0xCB, 0xC5, 0xDB, 0xFF, 0xA5, 0x8E, 0xA7, 0x96, 0xE5, 0xBF, 0x67, 0x05, 0xCD, 0xC6, 0x05, 0xC7, 0x4B, 0xDC, 0xDE, 0x36, 0xF8, 0xDF, 0x28, 0x5E, 0xCE, 0x43, 0xA1, 0x5A, 0x55, 0x1A, 0x07, 0x36, 0xDA, 0xDB, 0x36, 0xEC, 0x39, 0xA5, 0x3F, 0x68, 0xD6, 0x4B, 0x61, 0x49, 0xD6, 0x7E, 0x39, 0xBF, 0xB7, 0xE7, 0x6E, 0x50, 0x28, 0xF4, 0x37, 0x14, 0x35, 0x04, 0xC2, 0xB4, 0x5A, 0xC9, 0x93, 0xBF, 0x64, 0xE9, 0xD7, 0xE3, 0x41, 0x0D, 0x9A, 0x21, 0xAF, 0xE7, 0x40, 0xF4, 0x9E, 0x93, 0x40, 0x0D, 0x9D, 0x03, 0x4D, 0xED, 0xE4, 0xA9, 0x28, 0x20, 0xAC, 0xDE, 0x73, 0x03, 0xCD, 0xCA, 0x81, 0xF1, 0xB1, 0xCF, 0x97, 0x16, 0xC6, 0x6C, 0x07, 0xC2, 0x42, 0x59, 0x9E, 0xE9, 0xAC, 0xBB, 0xA7, 0xFD, 0x53, 0x3C, 0x96, 0xAE, 0x09, 0x77, 0x57, 0xBB, 0xE0, 0x8A, 0x76, 0xF0, 0x2C, 0xAE, 0xBC, 0x08, 0x76, 0xE0, 0x7F, 0xBF, 0x42, 0x38, 0xE6, 0x2C, 0xDA, 0xD9, 0xEC, 0xBE, 0xD8, 0xD1, 0x3B, 0x0C, 0xA2, 0xFF, 0xAC, 0xB3, 0x6A, 0x76, 0xFC, 0xB3, 0xBA, 0x7B, 0x56, 0xB9, 0x23, 0x99, 0x72, 0x5D, 0x88, 0x2B, 0xD6, 0xE9, 0x33, 0x42, 0xA8, 0xDE, 0xA4, 0x0D, 0xA9, 0x83, 0x85, 0xAE, 0x85, 0xBD, 0x40, 0x24, 0x36, 0xA2, 0xA6, 0xA9, 0xBB, 0x42, 0x9E, 0x39, 0xC4, 0x45, 0xA2, 0xD1, 0x22, 0xFD, 0x6B, 0x55, 0xB5, 0x34, 0x54, 0xA1, 0x21, 0x2A, 0x12, 0x8D, 0x14, 0xE9, 0x76, 0x0D, 0x93, 0x6C, 0xD9, 0x67, 0x56, 0xAF, 0x43, 0xAC, 0x16, 0xED, 0xBF, 0xDD, 0xE9, 0x9D, 0xB9, 0xF8, 0x7C, 0x85, 0xEE, 0x7F, 0x16, 0xD8, 0x39, 0x29, 0xA6, 0x77, 0x39, 0x50, 0xB0, 0xD6, 0xA6, 0xCB, 0xD2, 0x19, 0xBF, 0x63, 0xC5, 0xB3, 0x5A, 0x50, 0x2E, 0x78, 0x85, 0x16, 0x71, 0xA9, 0x2A, 0x72, 0xCF, 0x26, 0x9C, 0x7E, 0xBE, 0xFB, 0x2A, 0xC9, 0x88, 0x4D, 0x51, 0x68, 0x85, 0xEE, 0x0E, 0xEF, 0x5A, 0xA3, 0x4B, 0xAA, 0x88, 0xCA, 0x2D, 0xC2, 0x75, 0x8A, 0xF4, 0x3E, 0x85, 0xA5, 0xCE, 0x8F, 0xCF, 0x33, 0xCF, 0xFD, 0x45, 0x5C, 0x0C, 0xE5, 0x90, 0x12, 0x4F, 0xDA, 0x8C, 0x1C, 0x6B, 0xF4, 0x28, 0xE7, 0x85, 0x48, 0x57, 0xAA, 0x1F, 0x07, 0x9B, 0x08, 0x55, 0xED, 0x54, 0xFD, 0xCB, 0x90, 0xF9, 0xD2, 0x14, 0x96, 0xA8, 0xCF, 0x43, 0x62, 0x63, 0x4F, 0xAA, 0x44, 0xEE, 0xFC, 0x11, 0x14, 0xA6, 0xC2, 0x8A, 0xE8, 0x4D, 0x15, 0x2B, 0xBD, 0xB1, 0x26, 0x0E, 0xC9, 0x47, 0xFA, 0x16, 0x7F, 0xC5, 0x45, 0xE4, 0x4D, 0xE8, 0x3B, 0x6D, 0xEA, 0xED, 0x1F, 0xFE, 0xE8, 0xB0, 0x05, 0x9D, 0xE8, 0x9B, 0x22, 0xA5, 0x7A, 0x3B, 0x36, 0x44, 0xDC, 0x17, 0x2D, 0x59, 0xC3, 0x09, 0x43, 0xC5, 0x7F, 0x7E, 0x69, 0x8C, 0xF1, 0x73, 0x59, 0x6B, 0x73, 0x3A, 0x55, 0x9D, 0x38, 0xD4, 0x47, 0x73, 0x65, 0xF3, 0x97, 0xF8, 0xDB, 0x87, 0x67, 0x88, 0xA4, 0x49, 0xB4, 0xCD, 0xD3, 0xE7, 0x43, 0x3B, 0xBA, 0x7A, 0xE5, 0xDB, 0xEE, 0xA5, 0x63, 0x23, 0x2A, 0x52, 0xAF, 0xCA, 0xD3, 0x4B, 0x49, 0xC4, 0x73, 0x66, 0x2E, 0xA5, 0xF2, 0x16, 0xB9, 0x43, 0x21, 0xA4, 0x7E, 0xAA, 0x12, 0x3F, 0x1B, 0x94, 0x6B, 0xA5, 0x57, 0xE6, 0xC0, 0xA8, 0x23, 0x0D, 0xCD, 0x21, 0xBA, 0x67, 0x7C, 0xB9, 0x31, 0xEA, 0xC3, 0xB3, 0xFC, 0xCA, 0xFB, 0x9F, 0xFC, 0x45, 0xD0, 0x16, 0xC1, 0x2F, 0xE2, 0x71, 0x3A, 0x4A, 0xE6, 0x09, 0xF7, 0x41, 0x24, 0x2A, 0xD2, 0xC9, 0x16, 0xCF, 0x48, 0x8A, 0x74, 0xEE, 0x04, 0x4D, 0xA2, 0xB4, 0x25, 0x9F, 0xAB, 0x23, 0xF8, 0x99, 0xA2, 0x26, 0x79, 0x5D, 0x24, 0x6C, 0xBA, 0x82, 0x4C, 0xCE, 0x20, 0x7B, 0xA6, 0xCC, 0x9D, 0xF7, 0x4E, 0x28, 0xC0, 0xAE, 0x62, 0x1B, 0xBA, 0xDF, 0xFE, 0xAD, 0x5F, 0xDA, 0x44, 0x21, 0x17, 0x4D, 0x65, 0x49, 0x5A, 0x47, 0x15, 0x2D, 0x65, 0x4F, 0xD6, 0x91, 0x6A, 0xFD, 0xFE, 0xF3, 0x9F, 0x6D, 0x05, 0x3F, 0xA2, 0xFF, 0xFD, 0x10, 0x24, 0xAF, 0x0F, 0x74, 0xAD, 0x83, 0xFA, 0x36, 0x63, 0x3E, 0xF8, 0x26, 0x1D, 0xDC, 0x4E, 0xC2, 0xFF, 0x29, 0x60, 0xED, 0x1C, 0x56, 0x34, 0x46, 0xD9, 0x98, 0xED, 0xA2, 0x2A, 0x0E, 0x7E, 0xCC, 0xA1, 0x8E, 0x83, 0x7E, 0x8C, 0x08, 0x67, 0xD2, 0xEB, 0x9B, 0xF4, 0x2E, 0x06, 0x06, 0xB3, 0x68, 0x2A, 0x97, 0xD5, 0x5D, 0xAD, 0x63, 0x55, 0x13, 0xDA, 0x5D, 0x03, 0x3B, 0x15, 0x2B, 0xD5, 0xF5, 0xA6, 0xF2, 0xC4, 0xC7, 0xEE, 0xF0, 0xF6, 0x16, 0xCD, 0xDC, 0xB8, 0x92, 0xBE, 0x49, 0x65, 0xDA, 0xDD, 0x8D, 0x03, 0xE3, 0x41, 0xCD, 0x82, 0xD9, 0x58, 0xCE, 0x5A, 0xD9, 0x38, 0x09, 0x3C, 0x88, 0xEC, 0x62, 0x28, 0x7E, 0xF6, 0x8E, 0x85, 0x9E, 0x85, 0x45, 0x60, 0x97, 0x5D, 0x4E, 0x15, 0x93, 0xE7, 0x84, 0x75, 0x45, 0xA7, 0x54, 0xE6, 0x84, 0x96, 0x60, 0x19, 0xCE, 0xAB, 0xD6, 0x7B, 0x8B, 0x98, 0x71, 0x4D, 0x51, 0xAF, 0x0F, 0xA5, 0x9F, 0x81, 0xCD, 0xB0, 0x22, 0x86, 0x89, 0x65, 0x57, 0xE5, 0x53, 0xFF, 0x62, 0xD2, 0x55, 0x55, 0x57, 0xE8, 0xEF, 0xEF, 0x72, 0xFF, 0xEF, 0x24, 0xE2, 0x65, 0x0B, 0xF3, 0x38, 0x35, 0x15, 0x93, 0x83, 0x31, 0x9D, 0xAA, 0xEF, 0xFA, 0x19, 0x5C, 0x7F, 0x50, 0x23, 0x4B, 0x66, 0x7E, 0x89, 0xF9, 0x5D, 0xF0, 0x66, 0x12, 0xEE, 0x90, 0x3E, 0x73, 0x0A, 0xAF, 0x7C, 0x97, 0x09, 0x71, 0xA2, 0x72, 0x3D, 0x56, 0x0B, 0xD5, 0x44, 0x6F, 0x6A, 0x55, 0xE1, 0x72, 0xB6, 0x91, 0xA6, 0xD4, 0x3A, 0x73, 0x93, 0x10, 0x4E, 0x63, 0x13, 0xC2, 0x87, 0xA4, 0xBC, 0xDE, 0xBC, 0xBE, 0x01, 0x55, 0xAD, 0x0D, 0xC2, 0xE7, 0x89, 0x88, 0x8C, 0x09, 0xE8, 0x24, 0x52, 0xE3, 0x92, 0x35, 0x58, 0x4E, 0xEF, 0x79, 0xD0, 0x6B, 0xFC, 0xC4, 0x49, 0x60, 0xCB, 0x4D, 0xA4, 0x9A, 0xAE, 0x79, 0xBA, 0xF9, 0xED, 0x14, 0xF6, 0xD8, 0x4E, 0xF8, 0xFE, 0xDE, 0x9F, 0x91, 0x12, 0xD9, 0x4C, 0xB1, 0xD3, 0xD5, 0x56, 0x2F, 0xD3, 0xC1, 0x77, 0xD9, 0xCA, 0x3C, 0x3D, 0x8B, 0xB0, 0x64, 0x61, 0x9D, 0x72, 0x30, 0xB1, 0x75, 0xBA, 0x29, 0x3E, 0x34, 0x75, 0x4E, 0x16, 0x56, 0x6C, 0x04, 0x3F, 0x2B, 0x5C, 0xF3, 0x4C, 0x21, 0xDC, 0x67, 0x61, 0x18, 0xFF, 0xC2, 0xA8, 0x5E, 0x4E, 0x15, 0xE9, 0x14, 0xB6, 0xCC, 0x63, 0x55, 0x3A, 0x82, 0x97, 0x7E, 0x56, 0xA8, 0x2B, 0x1A, 0xD7, 0x73, 0xEF, 0x14, 0xCF, 0xAA, 0xEF, 0x4E, 0xE9, 0xFF, 0x90, 0x3B, 0x5B, 0xC6, 0x95, 0x6B, 0x7F, 0xC6, 0x58, 0xB5, 0x96, 0x1B, 0xB2, 0x8D, 0xD5, 0xFE, 0x76, 0xAC, 0xD6, 0x65, 0x34, 0x9D, 0x54, 0x6E, 0x3A, 0xE1, 0x7E, 0x36, 0x9D, 0x4C, 0xF4, 0x27, 0xD0, 0x94, 0x31, 0xC5, 0xE4, 0x9C, 0x3F, 0x33, 0x49, 0x79, 0xAF, 0xCF, 0x95, 0xDC, 0x79, 0xA2, 0xFE, 0xE2, 0xE8, 0xE5, 0xFE, 0xEE, 0xFE, 0xDA, 0xCF, 0xA9, 0x4C, 0x53, 0x24, 0x3B, 0x3B, 0xB6, 0x48, 0x02, 0x12, 0x6A, 0x84, 0x40, 0xB1, 0x70, 0xD1, 0x50, 0x1F, 0x50, 0x80, 0xF6, 0x80, 0xF2, 0x00, 0x06, 0xA4, 0x37, 0x78, 0xA1, 0x3B, 0x34, 0x40, 0x75, 0xD0, 0x90, 0xC0, 0x89, 0x1C, 0x1D, 0xE0, 0x80, 0x08, 0x2A, 0x60, 0x04, 0xA5, 0x40, 0x0C, 0x08, 0x92, 0x60, 0x00, 0x45, 0x82, 0xE6, 0x48, 0x40, 0xE6, 0x04, 0x65, 0x00, 0x02, 0x68, 0x47, 0x5F, 0x80, 0x07, 0x0D, 0xE0, 0x76, 0xC8, 0xF1, 0x3A, 0x9C, 0x0E, 0x77, 0x0E, 0x1D, 0x60, 0x83, 0x00, 0xD6, 0x10, 0xDB, 0x80, 0x19, 0x00, 0x70, 0x2F, 0xC8, 0x90, 0x81, 0x2D, 0xA4, 0x3C, 0xC9, 0xB5, 0xC0, 0x65, 0x4D, 0x5C, 0x0A, 0x28, 0xF7, 0x81, 0x00, 0x5A, 0x1B, 0xB0, 0x34, 0x06, 0x5F, 0x55, 0xE2, 0x78, 0x16, 0x63, 0xBE, 0x06, 0x2E, 0x3B, 0x83, 0x1C, 0x3C, 0x80, 0x38, 0x49, 0xA6, 0x0F, 0xBE, 0xC5, 0xA0, 0xA7, 0xF4, 0x48, 0x89, 0x8A, 0xEC, 0x19, 0x10, 0xE2, 0xB5, 0xFC, 0x9F, 0x76, 0x24, 0xE9, 0xD7, 0x46, 0xBF, 0xE8, 0x55, 0xF4, 0x87, 0xBA, 0x83, 0x97, 0xE2, 0x4B, 0xEF, 0xA5, 0xBF, 0x14, 0xC4, 0x21, 0x8C, 0xB3, 0x8B, 0xB3, 0x63, 0x11, 0x08, 0x1E, 0x83, 0xBF, 0xE0, 0x0F, 0x3E, 0xC3, 0xB6, 0xD6, 0x5A, 0x73, 0xBE, 0xA4, 0xF1, 0xFF, 0x2E, 0xB9, 0xC4, 0x97, 0xFE, 0xEF, 0xDF, 0x80, 0xED, 0x1A, 0xF8, 0x23, 0xF2, 0xF9, 0xDB, 0xF8, 0x49, 0x10, 0x5C, 0x62, 0xC4, 0xC7, 0xFF, 0xEF, 0x0D, 0xFD, 0xFF, 0x4D, 0x69, 0xA1, 0x8E, 0x0E, 0xE1, 0x9B, 0xE0, 0x8B, 0x6C, 0x56, 0xA2, 0xDF, 0x24, 0x12, 0x38, 0x8E, 0x14, 0xE1, 0x65, 0xFC, 0x9F, 0xB5, 0xA6, 0xE4, 0x43, 0x62, 0xB3, 0x44, 0x47, 0xEA, 0x7F, 0xFF, 0x3B, 0x7F, 0x80, 0x5D, 0x0D, 0x61, 0x2B, 0xEA, 0x97, 0x32, 0x8D, 0x25, 0x93, 0x71, 0xE7, 0x93, 0xBC, 0xD4, 0xB2, 0x18, 0x65, 0x2B, 0xE9, 0x72, 0x49, 0xCE, 0x67, 0x09, 0xEA, 0xF5, 0x38, 0x66, 0x96, 0x05, 0x5C, 0x04, 0x8F, 0xC6, 0xFE, 0x2C, 0x59, 0xF2, 0x29, 0x01, 0xC6, 0x8E, 0x1D, 0xFF, 0xFF, 0xD7, 0x68, 0xDA, 0x56, 0xC3, 0xD9, 0xF6, 0x7E, 0x4B, 0xEA, 0xDA, 0xD0, 0x7F, 0x13, 0x43, 0x95, 0xAF, 0x4D, 0x4A, 0xEA, 0x45, 0x31, 0x8C, 0xDE, 0x16, 0xBF, 0x9E, 0x67, 0x72, 0xE5, 0xCA, 0xBE, 0x82, 0x65, 0xCA, 0xC5, 0x4E, 0xA4, 0x47, 0xF3, 0x91, 0x72, 0xA6, 0xF8, 0xD5, 0x99, 0xAD, 0x90, 0x2F, 0x8D, 0x9A, 0x9F, 0x18, 0x37, 0xE7, 0x99, 0xBC, 0x4B, 0xF3, 0x7A, 0x5E, 0x4F, 0x4B, 0xED, 0x95, 0x85, 0x34, 0x1F, 0x79, 0x47, 0xDE, 0x2D, 0x8D, 0x6B, 0xFB, 0x32, 0x28, 0x89, 0xC6, 0xD1, 0xDC, 0x04, 0x6A, 0x33, 0x2C, 0x55, 0xBC, 0x60, 0x2F, 0x73, 0x6C, 0x89, 0x5C, 0xAE, 0x58, 0x8E, 0xA1, 0x4F, 0x96, 0x12, 0x89, 0x2A, 0xB7, 0x87, 0xA6, 0xB7, 0x8A, 0x1A, 0x91, 0x48, 0x2F, 0xF7, 0x42, 0xAE, 0x60, 0xFB, 0x99, 0xE2, 0xB5, 0x18, 0xB3, 0x68, 0x60, 0x77, 0xAD, 0xE8, 0xC1, 0xAE, 0x78, 0x7A, 0x4F, 0xA5, 0xAC, 0xDB, 0x7C, 0x31, 0x73, 0x28, 0x9B, 0xC3, 0x9E, 0x7F, 0xB0, 0xD3, 0xED, 0x4A, 0xF1, 0xA8, 0x8B, 0x4D, 0xB6, 0xB4, 0x0C, 0x5B, 0xED, 0x3E, 0xE3, 0xCC, 0x31, 0xEB, 0xBB, 0xBA, 0xF5, 0xB2, 0x7A, 0xE5, 0x39, 0xF2, 0x50, 0x5C, 0x61, 0x2F, 0xB3, 0x7A, 0xE7, 0xF3, 0x46, 0xBF, 0x97, 0xAD, 0xFF, 0xCD, 0x5E, 0x99, 0x67, 0x52, 0x2E, 0xE7, 0xA2, 0xB7, 0x0F, 0xA5, 0x94, 0xA3, 0x2E, 0x4D, 0x87, 0xBA, 0x5A, 0x65, 0xC5, 0x37, 0xA3, 0x17, 0xAC, 0x37, 0x1F, 0xD3, 0xE8, 0x6F, 0x09, 0xDE, 0x45, 0xF1, 0xBD, 0xB7, 0xA4, 0xF7, 0xE5, 0x4F, 0xC1, 0x7B, 0x4D, 0x1A, 0x05, 0xF3, 0x7D, 0x23, 0x54, 0x2F, 0xFE, 0xEE, 0x3F, 0xA0, 0xFB, 0x2C, 0x57, 0x0E, 0xC4, 0xFF, 0x27, 0x29, 0x73, 0x04, 0x71, 0x01, 0x6B, 0xB2, 0xC0, 0xC9, 0x05, 0x0B, 0x10, 0x21, 0x45, 0x07, 0x99, 0x17, 0xA6, 0x80, 0x42, 0xCA, 0x36, 0xFA, 0xA8, 0xBE, 0xD2, 0x96, 0xAE, 0x43, 0x0F, 0xD1, 0x1E, 0x6A, 0x54, 0x32, 0x90, 0x66, 0x3C, 0x6A, 0x40, 0x10, 0xAC, 0xA9, 0xE1, 0x11, 0xCA, 0xCE, 0xC3, 0x8A, 0x2C, 0x18, 0xCA, 0x6A, 0xEC, 0xE8, 0x96, 0x07, 0x8F, 0x1A, 0xB0, 0x92, 0xAA, 0xB5, 0x35, 0xA0, 0x25, 0x69, 0x40, 0xFA, 0xD2, 0xAB, 0x26, 0x94, 0x81, 0x27, 0xAF, 0xD1, 0xC2, 0x7F, 0x62, 0xF8, 0x50, 0x1E, 0xA1, 0x7F, 0xA1, 0x5A, 0x5D, 0x3E, 0xE2, 0x8B, 0x4F, 0xB7, 0x7A, 0x98, 0x7F, 0x2E, 0xEA, 0xC5, 0x50, 0xAE, 0x75, 0x22, 0x57, 0x37, 0xAA, 0xF8, 0xDF, 0xB0, 0x27, 0xF7, 0x68, 0x6E, 0x53, 0x61, 0xDB, 0xFF, 0xC5, 0x9A, 0xAC, 0xC9, 0x37, 0xF1, 0x06, 0x85, 0x6C, 0xB0, 0x69, 0x9A, 0xF6, 0x4D, 0xE1, 0x62, 0x7B, 0x3B, 0xA7, 0x39, 0x5B, 0x46, 0xE2, 0x84, 0x42, 0xA1, 0x94, 0xCB, 0xAA, 0xBC, 0xCA, 0xFD, 0xED, 0x53, 0xFD, 0x50, 0x96, 0x36, 0x8A, 0x65, 0x39, 0xCA, 0xB6, 0x9D, 0x83, 0x82, 0x47, 0x0D, 0xA8, 0x85, 0xE5, 0x59, 0x24, 0xA5, 0xFF, 0xF1, 0x46, 0xEF, 0xED, 0xC9, 0xDB, 0x4D, 0x3B, 0x7D, 0xF9, 0xC9, 0x76, 0xF4, 0x96, 0xE5, 0x17, 0x3D, 0x48, 0xE5, 0x81, 0x1A, 0x4C, 0x0C, 0x59, 0x4E, 0x93, 0x02, 0xE4, 0xA1, 0xF9, 0xE0, 0x51, 0x03, 0x56, 0xD0, 0x9E, 0xDC, 0x85, 0x26, 0xB3, 0x01, 0xC1, 0x10, 0x97, 0xF3, 0xD3, 0xFF, 0xD7, 0x6C, 0x73, 0xD6, 0x6C, 0xA5, 0x87, 0x70, 0xFD, 0x29, 0xA7, 0x0A, 0xE2, 0xE2, 0x52, 0x2F, 0xE2, 0xB1, 0xF2, 0xF4, 0xE9, 0xB0, 0xC9, 0x2F, 0x99, 0x9A, 0x6B, 0x14, 0x5B, 0x48, 0x4D, 0x05, 0xCF, 0xC5, 0x6C, 0x3E, 0xD5, 0x59, 0xFF, 0xC9, 0xDC, 0x09, 0x52, 0x23, 0x9D, 0x61, 0x23, 0x62, 0x11, 0xD4, 0xC2, 0xE6, 0x33, 0x73, 0x4E, 0xE0, 0xA8, 0xA3, 0xEF, 0xFE, 0xC7, 0x72, 0x7E, 0xA9, 0xB4, 0xAB, 0xD2, 0x74, 0x31, 0x34, 0x45, 0x2C, 0xDA, 0x2D, 0xE2, 0x85, 0xE4, 0x20, 0xC8, 0xD8, 0x53, 0xE6, 0x50, 0xC3, 0x6F, 0xD1, 0xCC, 0xF5, 0x3B, 0x8D, 0xE3, 0xA1, 0xFF, 0x1B, 0x3C, 0xDD, 0xC2, 0xE6, 0xA5, 0xFD, 0xDA, 0x77, 0xFE, 0x4D, 0x71, 0x7D, 0x7B, 0x3A, 0x4A, 0x3A, 0x83, 0x07, 0x41, 0x9E, 0xE9, 0x4B, 0xE6, 0xF8, 0xA5, 0xE0, 0xE6, 0xF4, 0x95, 0xCE, 0xF8, 0x93, 0xFD, 0x9B, 0xA1, 0x93, 0xCD, 0xB0, 0xC9, 0x3E, 0xBA, 0x32, 0x55, 0xF9, 0x2B, 0xD8, 0xF8, 0xD3, 0xFF, 0xF6, 0x0F, 0xCF, 0x7F, 0x3A, 0x6E, 0x46, 0x4F, 0x07, 0x0F, 0xC4, 0x5C, 0xD6, 0xFF, 0xAA, 0xAC, 0x84, 0x01, 0xE1, 0x4E, 0x51, 0x0B, 0x90, 0x1C, 0x9A, 0x40, 0x5E, 0xFA, 0xA1, 0x26, 0x90, 0xD3, 0x19, 0x36, 0x03, 0x92, 0xCA, 0x80, 0x5C, 0x21, 0xB5, 0x3F, 0xE3, 0xD9, 0x38, 0x8E, 0xE3, 0x38, 0xE6, 0x9C, 0xBF, 0x11, 0x6C, 0xC4, 0xC1, 0x46, 0xA5, 0xD0, 0x7D, 0x42, 0xEF, 0x56, 0xA8, 0xFF, 0x32, 0xD4, 0xC1, 0x5E, 0xA6, 0xEB, 0x97, 0xD5, 0x2A, 0x47, 0xAA, 0xA7, 0x46, 0xEF, 0xD1, 0xFF, 0xD7, 0xB8, 0x6B, 0x84, 0x9F, 0xF3, 0x0F, 0x1C, 0x58, 0xBD, 0x19, 0xCE, 0xEB, 0x3F, 0xDF, 0x69, 0xBF, 0xC5, 0x1D, 0x0B, 0xDD, 0x33, 0xF4, 0x3F, 0xE4, 0xD2, 0xDC, 0x4F, 0x56, 0xA1, 0xF4, 0x3C, 0xDD, 0x76, 0x04, 0x39, 0x0E, 0x46, 0xDE, 0x9F, 0xE4, 0x68, 0x7E, 0x5F, 0x6C, 0x39, 0x03, 0xDF, 0x60, 0xBB, 0x3C, 0xD3, 0x3F, 0xFF, 0x07, 0xFB, 0x7E, 0x15, 0xEB, 0xB3, 0x4B, 0x3E, 0xE4, 0xFF, 0xF3, 0xA5, 0x93, 0xF4, 0x4E, 0x17, 0x29, 0xF0, 0xDF, 0xFD, 0xDB, 0xDF, 0x2F, 0x6C, 0x44, 0x3F, 0x05, 0x9F, 0xF1, 0xBB, 0xAF, 0x46, 0x9F, 0xF9, 0x12, 0xFF, 0xD4, 0x93, 0xDE, 0x3C, 0xE8, 0xBB, 0x26, 0xF2, 0xDD, 0x71, 0x9B, 0x95, 0xFF, 0x4D, 0x3A, 0xCB, 0xAA, 0xA3, 0xBF, 0xBB, 0x0A, 0x67, 0x83, 0x5A, 0xD9, 0xA0, 0xF0, 0x45, 0x15, 0xB9, 0x99, 0x66, 0x0F, 0xB2, 0x9C, 0x04, 0x9E, 0x59, 0x46, 0x23, 0x70, 0x74, 0xAA, 0x3A, 0x09, 0x61, 0x27, 0x2E, 0xA5, 0x97, 0x3A, 0x11, 0x5D, 0x96, 0x8F, 0x0C, 0xC6, 0xA8, 0x58, 0x42, 0x97, 0xE2, 0xD0, 0x3F, 0xC5, 0x6B, 0xD3, 0xDA, 0x02, 0x1F, 0xC2, 0xF6, 0xFF, 0x2A, 0x95, 0xA9, 0xD0, 0xD5, 0x8B, 0x74, 0xFC, 0x7A, 0x7A, 0x83, 0x34, 0xD3, 0xFF, 0x29, 0xF9, 0x4C, 0xA5, 0x5F, 0x69, 0xAF, 0x62, 0xF5, 0x33, 0x48, 0x33, 0x5B, 0xFC, 0x70, 0x01, 0x55, 0x40, 0xD0, 0x65, 0xC6, 0xC6, 0xF4, 0xA9, 0x7C, 0x66, 0x5A, 0x27, 0x97, 0xCB, 0x95, 0x27, 0x95, 0xEB, 0x3A, 0xD1, 0xEB, 0x3A, 0x7D, 0xFD, 0x56, 0x16, 0x9A, 0xE2, 0x67, 0x9A, 0xA2, 0xC7, 0xC1, 0x32, 0x67, 0x92, 0xE4, 0x33, 0xF0, 0x7E, 0xE6, 0xB5, 0x11, 0x7F, 0x7B, 0xF4, 0x5A, 0x0B, 0x5B, 0x60, 0x29, 0xF5, 0x76, 0xAA, 0x49, 0xE6, 0x76, 0xAE, 0x9D, 0xDB, 0x22, 0x87, 0x4D, 0xDB, 0x27, 0xC3, 0xFF, 0xEF, 0x55, 0x23, 0x77, 0xFE, 0x27, 0x6F, 0x23, 0x8A, 0xA7, 0xD3, 0x57, 0x66, 0x30, 0xC9, 0x56, 0x35, 0x7C, 0x7F, 0x44, 0x2A, 0x31, 0xDB, 0xC1, 0x13, 0x97, 0xF6, 0x99, 0x31, 0x91, 0x36, 0x8F, 0xE8, 0xDA, 0xD4, 0x76, 0xFA, 0xF2, 0x7D, 0x42, 0x3B, 0xFB, 0x76, 0x2A, 0x2C, 0xD3, 0x12, 0x0F, 0xF9, 0x95, 0xAC, 0x7B, 0xFF, 0x3C, 0xAC, 0xB7, 0xA3, 0x8E, 0x17, 0x6F, 0x8E, 0x46, 0x5F, 0x92, 0xBF, 0xC6, 0x84, 0xAE, 0x68, 0x2B, 0x22, 0x92, 0x45, 0xE4, 0x9D, 0xB4, 0x45, 0xA3, 0x97, 0xBC, 0xA2, 0xD8, 0xE8, 0x1C, 0xA9, 0xDC, 0x18, 0xC9, 0x85, 0xE5, 0x2C, 0x93, 0x0B, 0x9E, 0x24, 0x7A, 0x49, 0x3F, 0xF8, 0x9D, 0x77, 0x39, 0x0B, 0x89, 0x5C, 0x21, 0xD1, 0x4B, 0xC2, 0x9D, 0x41, 0x1B, 0xF9, 0xD2, 0x7E, 0xC0, 0x7F, 0x07, 0xF4, 0x4F, 0xC1, 0x77, 0x33, 0xCF, 0x30, 0x98, 0xDD, 0x9F, 0xEC, 0x47, 0xE4, 0x83, 0xE6, 0x3B, 0x8D, 0x06, 0xEC, 0xF9, 0x2A, 0x7F, 0xA5, 0xAF, 0x28, 0xF0, 0x29, 0xAC, 0xC2, 0xE3, 0x5D, 0x46, 0x83, 0x62, 0x93, 0x5F, 0x88, 0x37, 0xD9, 0x3C, 0x1A, 0xC8, 0xAA, 0xB6, 0xCF, 0x7A, 0x9F, 0x70, 0x0F, 0x29, 0xF1, 0x2B, 0x9E, 0xD3, 0xD5, 0x54, 0xE3, 0xDF, 0x40, 0xE1, 0x99, 0x75, 0xCF, 0xEE, 0x95, 0x2B, 0x36, 0x85, 0x85, 0x5A, 0xDA, 0xFF, 0x4D, 0x0D, 0xA5, 0xFC, 0x41, 0xBF, 0x41, 0xE0, 0x12, 0x36, 0x21, 0xDA, 0x3A, 0x19, 0x2F, 0xA1, 0xCA, 0xF4, 0x2E, 0xF1, 0x93, 0xBF, 0x44, 0x8F, 0xFE, 0x1F, 0x9B, 0x85, 0xAE, 0x51, 0x47, 0x5B, 0x86, 0x62, 0xCB, 0x52, 0x8D, 0x52, 0xEC, 0xB9, 0xF3, 0x66, 0x9B, 0xA2, 0x57, 0x3E, 0xF9, 0xDA, 0x8C, 0xED, 0x8F, 0xEA, 0x2D, 0xBB, 0x11, 0xC1, 0x14, 0xDF, 0x48, 0x78, 0x89, 0xCE, 0xE4, 0x10, 0x3C, 0x72, 0x34, 0x36, 0x08, 0x7D, 0x63, 0x43, 0x9C, 0x08, 0x02, 0x25, 0xFF, 0x9F, 0x95, 0xDD, 0xAC, 0x4A, 0x41, 0x72, 0xB1, 0x7C, 0xD6, 0xFC, 0xC5, 0xDE, 0xA1, 0xBA, 0x25, 0x9B, 0xB1, 0x01, 0x29, 0xBA, 0x5B, 0x48, 0x49, 0xC0, 0x37, 0x09, 0x2A, 0x35, 0x09, 0x3F, 0xD7, 0xA3, 0x5B, 0xD6, 0x21, 0x7C, 0x08, 0x77, 0xD5, 0x1A, 0xFD, 0x43, 0x00, 0x77, 0x6C, 0xA1, 0xCB, 0x22, 0xC3, 0x13, 0xC2, 0x3B, 0xAC, 0xA8, 0x4B, 0x75, 0x94, 0x82, 0xC2, 0xAA, 0x92, 0x0D, 0x8A, 0xEB, 0x4D, 0x8C, 0x51, 0x07, 0xF5, 0x10, 0x96, 0x0C, 0x76, 0x5F, 0xD4, 0x66, 0xA2, 0x47, 0xD5, 0x91, 0x72, 0x47, 0x4C, 0xDC, 0xCB, 0xB1, 0x20, 0x81, 0x23, 0x25, 0x59, 0x4C, 0x5D, 0x4C, 0xF1, 0xBF, 0x73, 0x88, 0x4A, 0xC9, 0x74, 0x75, 0x03, 0x85, 0xCF, 0x58, 0x42, 0xF1, 0x1E, 0x4D, 0x1F, 0x4B, 0x66, 0x0C, 0x28, 0xB8, 0xD4, 0xE9, 0xF6, 0x61, 0xBF, 0xFB, 0xCA, 0x52, 0x0F, 0xC1, 0x81, 0xC5, 0x4B, 0x67, 0x4E, 0x1D, 0xE9, 0xAA, 0xC5, 0x6F, 0xE7, 0xA7, 0xB3, 0x5A, 0xA8, 0xB7, 0xC3, 0x86, 0xF8, 0x2E, 0x07, 0xE5, 0x50, 0xF6, 0xF4, 0x77, 0x62, 0xFE, 0x7C, 0x56, 0x47, 0x46, 0xCF, 0x88, 0x2A, 0xFF, 0x2D, 0x23, 0xD7, 0x37, 0xFF, 0xEB, 0xB3, 0x48, 0x1A, 0x2D, 0x59, 0xA8, 0xE3, 0xF9, 0x4B, 0x01, 0x8B, 0xFE, 0x2D, 0xA2, 0xA7, 0xE3, 0xBF, 0x8D, 0x1F, 0xBF, 0x88, 0xF0, 0x2B, 0x6F, 0x4F, 0x19, 0x4E, 0x12, 0x13, 0xC7, 0xF3, 0xC3, 0x3B, 0x41, 0xCB, 0x89, 0x96, 0xBC, 0x38, 0x71, 0x5D, 0x48, 0xD5, 0x77, 0x4B, 0x67, 0x0F, 0x33, 0x95, 0xFD, 0x82, 0xBF, 0x43, 0x20, 0xD4, 0x4C, 0x39, 0x9E, 0x2D, 0x74, 0xF2, 0x2B, 0xFD, 0x57, 0x31, 0x0C, 0xFE, 0xFB, 0x29, 0x2D, 0x85, 0xC1, 0xF4, 0x87, 0x32, 0xCA, 0xA9, 0xF2, 0x05, 0x65, 0xB4, 0x93, 0x57, 0xFE, 0x9B, 0x77, 0x5A, 0xD3, 0x11, 0x6B, 0xF3, 0x3D, 0x53, 0x0C, 0x61, 0x9D, 0x91, 0x2F, 0x9D, 0xA6, 0x7B, 0x1F, 0x54, 0x67, 0x43, 0x4D, 0x61, 0x69, 0x9E, 0xF3, 0x22, 0x73, 0x48, 0x56, 0xE5, 0x89, 0xB3, 0x19, 0xFC, 0x8C, 0xD8, 0x88, 0x3F, 0x3A, 0xFB, 0xEA, 0x3C, 0x71, 0x67, 0x7F, 0xD1, 0x40, 0x48, 0x70, 0x4A, 0x6F, 0x98, 0xFC, 0xE9, 0x80, 0xC9, 0x9E, 0x1A, 0x52, 0xD9, 0x0F, 0x93, 0x39, 0x1D, 0x1F, 0x6F, 0x73, 0xD8, 0xDF, 0x0D, 0x91, 0xB0, 0xE8, 0xBF, 0x92, 0xF1, 0xBD, 0xA1, 0xF0, 0xBC, 0x9D, 0x71, 0x1B, 0xCB, 0x67, 0xC4, 0xA7, 0x38, 0xB6, 0x8B, 0x1D, 0x67, 0x7A, 0xFC, 0xCE, 0x25, 0x31, 0x7D, 0x05, 0x34, 0x72, 0x5E, 0x8C, 0xB4, 0xB7, 0xF7, 0x28, 0x8A, 0xBF, 0xF8, 0x9A, 0xC3, 0xED, 0x72, 0x4D, 0x6F, 0x1F, 0x9A, 0x31, 0xEB, 0x8D, 0x71, 0x72, 0x1F, 0x7B, 0x7C, 0x12, 0xC1, 0x62, 0xB5, 0xCF, 0xE8, 0x97, 0x52, 0x6B, 0xC9, 0x9A, 0x13, 0x17, 0xD4, 0x26, 0xAA, 0xF0, 0x70, 0x7A, 0x4E, 0xF5, 0xDF, 0xB8, 0x88, 0x72, 0xB1, 0xBB, 0x64, 0x3F, 0xB9, 0x8A, 0xFD, 0xD2, 0x8B, 0xFD, 0x33, 0xF4, 0xF5, 0xFC, 0xD4, 0xB4, 0x43, 0x63, 0xD3, 0x6B, 0x2A, 0x34, 0xB0, 0x73, 0xA8, 0x53, 0x33, 0x35, 0xED, 0x7F, 0x6E, 0x38, 0xEF, 0x81, 0x51, 0xFE, 0xD9, 0xB9, 0x6E, 0x9F, 0x90, 0x47, 0xF3, 0xD3, 0x29, 0x18, 0xE2, 0x8C, 0xF0, 0x7C, 0x32, 0x15, 0x0A, 0x85, 0x3B, 0xAF, 0xA7, 0xA0, 0x16, 0xE2, 0x50, 0xCF, 0x4D, 0xA6, 0xB4, 0xB0, 0xE7, 0xD9, 0x29, 0xD6, 0xBE, 0x2F, 0xD8, 0x90, 0x67, 0x84, 0x07, 0x59, 0xED, 0x31, 0x2D, 0xCC, 0xB9, 0xE9, 0xF4, 0x86, 0x70, 0x0A, 0x59, 0xB5, 0x77, 0x6F, 0x4A, 0x55, 0x1A, 0x97, 0xF3, 0xA0, 0x26, 0x6B, 0x42, 0x33, 0x42, 0x64, 0x62, 0x60, 0xF0, 0x89, 0xA4, 0x3C, 0x42, 0x2D, 0x9A, 0x4B, 0x39, 0x83, 0x45, 0xC8, 0xCA, 0x3D, 0x7D, 0xE7, 0xBC, 0x90, 0x6D, 0x86, 0x1D, 0x55, 0x84, 0xD4, 0xD3, 0x3F, 0xE3, 0xA7, 0xDF, 0x38, 0x50, 0xAF, 0xCC, 0xFF, 0x68, 0xA7, 0xCF, 0x89, 0x9E, 0xA7, 0xA6, 0x58, 0xE6, 0x23, 0xAB, 0xC3, 0x23, 0x47, 0x63, 0x5A, 0x3E, 0x7A, 0xE5, 0x78, 0x3B, 0x92, 0x94, 0x25, 0xF4, 0xCC, 0xAA, 0x63, 0xF7, 0x91, 0x84, 0x09, 0x81, 0x2B, 0xA1, 0x4F, 0x8C, 0x20, 0x94, 0xC6, 0x91, 0x2B, 0x1A, 0xD8, 0xBD, 0xB5, 0xD8, 0x61, 0xB7, 0xF5, 0xF5, 0x83, 0x3D, 0xAB, 0x36, 0xAA, 0x54, 0xD7, 0xBF, 0x7C, 0x6C, 0x12, 0x86, 0xB2, 0xD8, 0x73, 0x3F, 0x40, 0x3E, 0x78, 0xD4, 0x80, 0xF5, 0x2D, 0x1C, 0x8D, 0xD1, 0x28, 0x29, 0x93, 0x51, 0x92, 0xE2, 0x9A, 0xA9, 0xFC, 0xC4, 0x37, 0x5B, 0x23, 0x78, 0xAF, 0x59, 0x2D, 0xFD, 0xFF, 0x6B, 0xF8, 0x3A, 0x80, 0x05, 0xBB, 0xC5, 0x31, 0xA3, 0x7B, 0x45, 0xA3, 0x5F, 0xD1, 0xDF, 0x79, 0xF4, 0x1D, 0xF8, 0xFF, 0xF0, 0xFF, 0x07, 0xFF, 0x1F, 0x0E, 0x84, 0xFF, 0x03, 0xD5, 0x25, 0x61, 0x63, 0xA0, 0xAF, 0x2F, 0x90, 0x77, 0x82, 0x0B, 0xC4, 0xE9, 0x68, 0xA0, 0x1F, 0xBF, 0xF1, 0xF9, 0x4E, 0x74, 0x4B, 0xD1, 0x4D, 0x51, 0xD3, 0xD2, 0x4B, 0xF5, 0x3E, 0x07, 0xBE, 0xEF, 0x8F, 0x7D, 0xF2, 0x78, 0x83, 0xFE, 0xA0, 0x31, 0x28, 0xE8, 0xDE, 0x17, 0x36, 0xDB, 0x5C, 0x89, 0x05, 0xB4, 0xC0, 0x06, 0xFE, 0x02, 0xBE, 0x00, 0xB2, 0x4B, 0xB7, 0xC1, 0x41, 0xEE, 0x2B, 0xC8, 0xCD, 0x20, 0xE0, 0x83, 0xFC, 0x3F, 0xC9, 0xDB, 0x97, 0x1E, 0x02, 0x6F, 0xF3, 0x52, 0xD4, 0xBD, 0x74, 0x6D, 0x58, 0x64, 0x4A, 0xCD, 0xF8, 0xF3, 0xE8, 0x9E, 0xFF, 0x77, 0x21, 0xF0, 0x19, 0xFE, 0xBB, 0x26, 0x65, 0x72, 0xBC, 0x33, 0xF4, 0xFA, 0x19, 0x1B, 0xF9, 0x92, 0xFE, 0x2A, 0x9B, 0x76, 0x10, 0xCC, 0x4E, 0xDB, 0x05, 0x7C, 0x0B, 0xA5, 0x42, 0x41, 0x12, 0x03, 0x5D, 0x09, 0xA8, 0x82, 0xB3, 0x94, 0x5B, 0xB1, 0x26, 0x1A, 0x85, 0xCA, 0x1C, 0x3A, 0xA6, 0x0D, 0x4A, 0x82, 0x67, 0x60, 0x28, 0xC2, 0x93, 0x42, 0xEA, 0x81, 0x99, 0xE9, 0xE8, 0x4A, 0x2D, 0xBE, 0xB9, 0x98, 0x59, 0x11, 0x45, 0x72, 0xF0, 0x8C, 0x04, 0xA7, 0x59, 0x28, 0x6F, 0x52, 0x50, 0x7A, 0x60, 0x52, 0x57, 0x59, 0x43, 0xA9, 0x28, 0xCD, 0xCB, 0xB0, 0xC2, 0xA4, 0x23, 0x70, 0x39, 0xF1, 0x14, 0x17, 0x93, 0x4A, 0x00, 0x88, 0xD6, 0x16, 0xD4, 0x87, 0x22, 0x3E, 0xA7, 0x3D, 0x08, 0x62, 0x23, 0x8A, 0x1A, 0x2D, 0x22, 0x66, 0xE0, 0x82, 0xD9, 0xD1, 0x43, 0xD9, 0xCD, 0xCC, 0x70, 0x31, 0x4F, 0x82, 0x12, 0x88, 0x03, 0x60, 0xC6, 0x7C, 0x07, 0x32, 0x0E, 0xB8, 0xBE, 0x13, 0xFA, 0x0A, 0xB8, 0xCE, 0x44, 0xCA, 0xAB, 0x4B, 0x1F, 0x66, 0xFB, 0x5F, 0x7E, 0x7C, 0x1F, 0x25, 0xC0, 0xA2, 0x08, 0x1B, 0x6D, 0xA0, 0x42, 0x11, 0x20, 0x40, 0x39, 0xD0, 0x43, 0x1C, 0x58, 0x2D, 0x24, 0xFF, 0x49, 0xDD, 0xF1, 0x4A, 0xFE, 0x0D, 0x96, 0x3C, 0xFA, 0x8C, 0xE7, 0xFB, 0xF3, 0x7C, 0xB2, 0x0C, 0x7C, 0x11, 0xDF, 0xC0, 0x7B, 0xD5, 0xB9, 0x58, 0x1D, 0xDF, 0xF3, 0xA0, 0x6E, 0xCE, 0x87, 0xBE, 0xC7, 0x0F, 0x0E, 0x0F, 0x35, 0x69, 0xB8, 0x01, 0x6A, 0x06, 0xBD, 0x9B, 0xA0, 0x77, 0x82, 0xE2, 0x93, 0x99, 0x12, 0x64, 0x56, 0x0F, 0x43, 0xC3, 0x30, 0x8E, 0xB7, 0xF7, 0x3B, 0x86, 0xDE, 0xEE, 0x4D, 0x8F, 0x29, 0xD7, 0x03, 0x33, 0x02, 0xDA, 0x3F, 0x2B, 0xF1, 0x88, 0x42, 0xDA, 0xFF, 0xCE, 0x5C, 0x60, 0x26, 0x76, 0x33, 0xFF, 0xD3, 0x94, 0x9B, 0x35, 0x68, 0x03, 0xEE, 0x0C, 0xFA, 0x9F, 0xCB, 0xD0, 0xA5, 0xED, 0x43, 0x3B, 0xE6, 0x7A, 0x3A, 0x25, 0x8D, 0xA3, 0xF3, 0x98, 0x0A, 0x53, 0x8F, 0xAB, 0x02, 0xC8, 0x00, 0x46, 0xD0, 0x05, 0x55, 0xA7, 0xE6, 0xCE, 0x46, 0xBE, 0xD3, 0x09, 0x9D, 0xE6, 0x4A, 0x34, 0x25, 0xAA, 0x80, 0xD2, 0x52, 0xC2, 0x3C, 0x1D, 0xC7, 0x1C, 0xCB, 0xA0, 0x5B, 0x66, 0xDD, 0xCF, 0x20, 0x1F, 0x20, 0x22, 0xF5, 0x17, 0x71, 0x17, 0xA7, 0xCA, 0xE6, 0x48, 0xFF, 0x26, 0x58, 0xF1, 0x04, 0x0E, 0x24, 0x72, 0x50, 0x83, 0x00, 0x27, 0xDB, 0xA6, 0x86, 0x1F, 0x8D, 0x8F, 0x20, 0xDC, 0xFF, 0xE7, 0x37, 0xD0, 0xA4, 0xD8, 0x7C, 0x46, 0x0E, 0x2A, 0xA5, 0x2D, 0x94, 0x32, 0x88, 0xB3, 0xC9, 0xA7, 0x15, 0xB4, 0x94, 0xB8, 0xB5, 0xE0, 0xE9, 0x49, 0x4B, 0x47, 0x82, 0x10, 0x41, 0xC7, 0xE4, 0x57, 0xB8, 0x4C, 0xFE, 0x3F, 0x7C, 0x06, 0x69, 0x52, 0x5A, 0x26, 0xF8, 0xA5, 0x13, 0x9D, 0x78, 0x41, 0x48, 0xFF, 0x1F, 0xF0, 0x83, 0x87, 0xF0, 0xFF, 0x7F, 0xD0, 0x16, 0xCB, 0xE1, 0xDB, 0x4B, 0xC7, 0x63, 0x1B, 0x55, 0xAA, 0xED, 0x7D, 0x9C, 0x47, 0x0D, 0x68, 0xAD, 0x8E, 0x8B, 0xFB, 0x4D, 0x4B, 0xC6, 0x4B, 0xAB, 0x44, 0xFF, 0x10, 0x30, 0x02, 0x06, 0x13, 0x07, 0x68, 0x3C, 0x84, 0x47, 0x6C, 0x41, 0x09, 0x3E, 0x10, 0xA1, 0x27, 0x65, 0x0A, 0xD7, 0x16, 0xF5, 0xE2, 0xB8, 0xB8, 0x67, 0x16, 0xDC, 0xA9, 0x23, 0x92, 0xEC, 0x3C, 0x3A, 0x3D, 0x1D, 0xBD, 0xE4, 0x46, 0x14, 0x40, 0x0A, 0x3A, 0xBF, 0x68, 0x09, 0x4E, 0x17, 0xA2, 0x7B, 0x8E, 0x3F, 0x73, 0xFE, 0x4F, 0xED, 0x9F, 0x7D, 0x3B, 0x36, 0x5B, 0x3D, 0x57, 0xE5, 0xB4, 0x70, 0xC7, 0x96, 0xBB, 0x6B, 0xFD, 0x68, 0x5A, 0xD5, 0xDB, 0xF1, 0x4C, 0xCF, 0xC3, 0x66, 0x1A, 0x03, 0x58, 0x59, 0xF9, 0xC1, 0x02, 0x36, 0x7C, 0xB8, 0x81, 0x12, 0x93, 0x6D, 0xD9, 0xC9, 0x12, 0x37, 0xA3, 0x2E, 0x5E, 0xC8, 0x00, 0x17, 0xB6, 0x20, 0x00, 0x2A, 0x40, 0x40, 0x0A, 0x2F, 0x20, 0x96, 0xD0, 0x80, 0x2D, 0x94, 0xF0, 0x43, 0x16, 0x0B, 0xD0, 0x11, 0x42, 0x8B, 0x66, 0xA3, 0xC8, 0x01, 0xC6, 0x90, 0x00, 0x4F, 0xAC, 0xA0, 0x00, 0x02, 0x3B, 0x4C, 0x50, 0xAA, 0xA9, 0x01, 0x82, 0x20, 0x9C, 0xF0, 0x40, 0x8A, 0x05, 0x6C, 0x54, 0x80, 0x09, 0x1E, 0x70, 0x0F, 0x4A, 0x24, 0x21, 0x43, 0xEB, 0xCA, 0x29, 0xEE, 0xC1, 0x2A, 0x52, 0xAE, 0x99, 0x21, 0x0D, 0xD7, 0xCC, 0xD4, 0x4A, 0xD3, 0xAB, 0xCB, 0xEF, 0x82, 0x8C, 0xBA, 0x2A, 0xB5, 0xE9, 0xC7, 0xBC, 0xE4, 0xC1, 0x43, 0x30, 0xE6, 0x45, 0x20, 0xE0, 0x21, 0x03, 0xA3, 0x08, 0x28, 0xC0, 0x09, 0x29, 0xE4, 0x28, 0x00, 0x0A, 0x7A, 0xE0, 0xF4, 0x00, 0xC6, 0x49, 0x96, 0x4C, 0x16, 0x23, 0x82, 0xC1, 0x26, 0x60, 0x37, 0x98, 0x7F, 0x73, 0x5B, 0x4E, 0x2F, 0xCB, 0xB3, 0x68, 0xEC, 0x89, 0xDE, 0xC0, 0x88, 0x6E, 0x49, 0x03, 0x23, 0xC2, 0x65, 0x4A, 0x0D, 0x89, 0x5C, 0xCE, 0x33, 0xBD, 0xA9, 0xA1, 0x30, 0x26, 0xFA, 0x5C, 0x58, 0xF4, 0x86, 0x9D, 0xD0, 0xC8, 0xAE, 0x8C, 0x2C, 0x22, 0x15, 0xD0, 0x9A, 0xE4, 0x1A, 0x44, 0xAA, 0xA5, 0x60, 0x8D, 0x9B, 0x5C, 0x9B, 0xB9, 0x1D, 0xCB, 0xB2, 0xDB, 0xEF, 0x7E, 0xAC, 0x5E, 0x16, 0xBC, 0x31, 0xC1, 0x3F, 0xDD, 0x8F, 0xFF, 0x79, 0x7A, 0x16, 0x18, 0x63, 0x1C, 0x13, 0x39, 0xD7, 0xC9, 0x67, 0x7C, 0x79, 0x7A, 0x83, 0x57, 0xFB, 0x82, 0x50, 0x4F, 0xD9, 0x2F, 0xC7, 0xA3, 0x85, 0x71, 0xB0, 0x5C, 0xB8, 0x62, 0x1B, 0xB1, 0xA5, 0x60, 0xEC, 0xCB, 0xBC, 0x1D, 0x19, 0xB7, 0x23, 0x7E, 0x3B, 0xA9, 0x31, 0xFB, 0x82, 0x90, 0x9D, 0x6A, 0x0D, 0xCD, 0x6E, 0x0C, 0x7B, 0x6A, 0x96, 0x55, 0x8F, 0x04, 0x43, 0xA3, 0x73, 0xA1, 0xD1, 0xC1, 0x2F, 0x74, 0xC1, 0xD1, 0x4F, 0x43, 0x1F, 0x33, 0xD1, 0x3D, 0xAD, 0x09, 0x2E, 0x33, 0x91, 0x35, 0xA9, 0x19, 0x77, 0x86, 0x35, 0x34, 0x2B, 0x56, 0xF9, 0x48, 0x71, 0xAC, 0x57, 0xA7, 0x56, 0xD2, 0x34, 0x18, 0xAC, 0x1D, 0xC4, 0xD7, 0x33, 0x47, 0xA1, 0x8E, 0x3E, 0x89, 0xB8, 0xDA, 0xDC, 0xCA, 0x95, 0xB1, 0x6C, 0x5E, 0x0C, 0x73, 0x8E, 0x97, 0x1C, 0x27, 0xEC, 0x51, 0xFD, 0x43, 0x1F, 0xCE, 0xC7, 0xF4, 0x6D, 0xD9, 0x73, 0xD5, 0xD1, 0xB6, 0xC4, 0x4F, 0x25, 0x5E, 0x9E, 0x20, 0xF7, 0xC0, 0xD7, 0x4F, 0xE4, 0xA5, 0x74, 0xEA, 0x1B, 0xF4, 0xC9, 0x9E, 0x29, 0xFA, 0x45, 0x1E, 0x98, 0x2F, 0xA7, 0x43, 0xA7, 0xFB, 0xDE, 0x60, 0x53, 0x29, 0x0E, 0xEB, 0x14, 0xF2, 0xCA, 0x10, 0x58, 0xE9, 0xCF, 0xC5, 0xF2, 0x17, 0xF2, 0xBE, 0xE8, 0x4C, 0x39, 0x9E, 0xCC, 0xBD, 0xC6, 0x7F, 0x21, 0x10, 0x7B, 0x2E, 0x60, 0x89, 0xE7, 0x60, 0x23, 0xE7, 0x82, 0x1C, 0x88, 0x94, 0x83, 0xE5, 0x9A, 0x43, 0xFC, 0x90, 0xC7, 0x95, 0x33, 0x6C, 0xE8, 0xA3, 0xF7, 0x95, 0x3B, 0xF8, 0xD0, 0x17, 0xBA, 0x21, 0xEE, 0xE4, 0x62, 0x2B, 0x1F, 0xEE, 0x2C, 0xC1, 0x70, 0x4A, 0x26, 0xCA, 0xEB, 0xAF, 0xBF, 0x6E, 0xAD, 0x3D, 0x59, 0x6D, 0xDF, 0xE5, 0xE4, 0xE4, 0x64, 0x53, 0xF9, 0xF6, 0x6F, 0xB7, 0x28, 0x28, 0xF4, 0x3F, 0x4A, 0xEB, 0xF7, 0x23, 0x15, 0xF3, 0x9D, 0x56, 0xEA, 0x32, 0x1C, 0xD3, 0xE4, 0xF8, 0xBF, 0x07, 0x6F, 0x31, 0xB6, 0x94, 0xC2, 0x58, 0x7C, 0x4F, 0x0A, 0xAA, 0x27, 0xE1, 0x5F, 0xD2, 0x7F, 0x97, 0x25, 0x6C, 0x48, 0x5D, 0x32, 0x7B, 0x65, 0xA2, 0x82, 0x39, 0x1F, 0x13, 0x8A, 0x4D, 0x3C, 0xF3, 0x2F, 0x67, 0x56, 0xDE, 0xD1, 0xE6, 0x01, 0xEA, 0xAB, 0xD6, 0xB3, 0x7D, 0xEB, 0x82, 0x31, 0xAE, 0x2F, 0xFF, 0x4C, 0x6F, 0xCF, 0x4D, 0xBF, 0x41, 0x1D, 0x3D, 0xAB, 0xA3, 0xF0, 0xCC, 0x61, 0x45, 0x8A, 0x63, 0xAE, 0xB8, 0x28, 0xEC, 0xD8, 0x37, 0x68, 0x09, 0x99, 0xCE, 0x43, 0x4B, 0x08, 0x9B, 0x1C, 0x07, 0xE0, 0xB3, 0x93, 0x42, 0x7C, 0xF2, 0xB3, 0x7C, 0x03, 0xFC, 0xF3, 0x80, 0xFF, 0x2D, 0xE3, 0x21, 0x06, 0x04, 0x15, 0x2D, 0xC4, 0x06, 0x15, 0x9F, 0x61, 0x8E, 0xB0, 0x79, 0x9A, 0x54, 0x99, 0xEB, 0xE3, 0x29, 0x84, 0xE0, 0x3D, 0xEC, 0xAC, 0xB4, 0xC5, 0x77, 0x36, 0x13, 0x1D, 0xE9, 0xCE, 0x4D, 0x3B, 0xD5, 0xF4, 0xEE, 0x3D, 0xC4, 0xC7, 0x14, 0x21, 0xEA, 0xEB, 0xB9, 0x22, 0x0F, 0x75, 0x45, 0x1C, 0xEA, 0x8A, 0x3E, 0x9A, 0x77, 0x31, 0xE7, 0x62, 0x0E, 0x92, 0xD4, 0xC7, 0xCF, 0xF2, 0x71, 0x1C, 0x04, 0x3B, 0x92, 0x97, 0x09, 0x7B, 0xED, 0xD9, 0x4F, 0xBB, 0x67, 0xE0, 0x10, 0xEA, 0xA8, 0x59, 0xCF, 0x3E, 0xED, 0x86, 0x61, 0xB3, 0x77, 0xD7, 0xBA, 0x5B, 0xA9, 0xA2, 0xDF, 0x97, 0x5A, 0x4C, 0x7D, 0x83, 0x38, 0xB3, 0x22, 0xA5, 0xE1, 0x52, 0xDA, 0x77, 0x9E, 0xDF, 0xA5, 0x06, 0x9A, 0x1A, 0x4B, 0x7B, 0x9C, 0x4D, 0x69, 0xBD, 0xF2, 0xD1, 0xC0, 0xFE, 0x9E, 0x78, 0xFE, 0xCA, 0xA5, 0xDD, 0x7E, 0xDF, 0xFF, 0xFE, 0xE0, 0x62, 0xDA, 0x5D, 0xF9, 0x71, 0xC1, 0xB0, 0x62, 0x87, 0x4D, 0x66, 0xAF, 0x49, 0xAE, 0xC9, 0x77, 0x2B, 0x19, 0xE5, 0x7C, 0xB0, 0xA0, 0x1D, 0x0D, 0x16, 0x72, 0xBC, 0x31, 0x19, 0xEA, 0x8B, 0xC9, 0xBC, 0x8A, 0xE2, 0x50, 0x20, 0x86, 0x65, 0x1F, 0x16, 0x9E, 0x46, 0x83, 0x05, 0xF5, 0x96, 0x1F, 0xF8, 0x7F, 0x36, 0xE4, 0x07, 0xCD, 0x54, 0xA7, 0x37, 0xAF, 0xB9, 0x98, 0xB4, 0x45, 0x67, 0x05, 0xE7, 0xE0, 0xD0, 0x43, 0x6B, 0x6B, 0x43, 0x4C, 0x69, 0xC3, 0xC8, 0x54, 0x3F, 0x15, 0x5C, 0x2F, 0xC0, 0x62, 0x6B, 0xBD, 0x94, 0x1D, 0x4E, 0x8E, 0xFF, 0x05, 0x7F, 0xD5, 0x14, 0x97, 0x3C, 0x4E, 0x43, 0x48, 0xFF, 0x05, 0xE5, 0x0B, 0xCC, 0x77, 0xA0, 0xEC, 0xE2, 0x54, 0x04, 0xDD, 0x2D, 0x39, 0x64, 0xD5, 0x7A, 0x6F, 0x22, 0x53, 0x64, 0x4E, 0xA3, 0x48, 0xA6, 0xB4, 0x8C, 0x75, 0x24, 0x53, 0xB6, 0x93, 0x1D, 0xC5, 0x70, 0x0A, 0x3C, 0x7E, 0x56, 0xD8, 0x4C, 0xBF, 0xB9, 0x16, 0xFF, 0xE6, 0xE3, 0x61, 0xC6, 0xB0, 0x57, 0xCC, 0x8B, 0xA7, 0xCF, 0x96, 0xC3, 0x1E, 0x9D, 0x9B, 0xFE, 0x5C, 0x79, 0xA2, 0xA7, 0x9B, 0xC8, 0xC4, 0xC0, 0x60, 0x13, 0x95, 0xFE, 0x7C, 0x44, 0xCF, 0x3C, 0x4F, 0xB2, 0x1D, 0x99, 0xD0, 0x90, 0x82, 0x27, 0x19, 0xAB, 0x35, 0x1A, 0x71, 0xA6, 0xBD, 0xA9, 0xA3, 0x94, 0x9B, 0x1C, 0x26, 0x86, 0x30, 0xFB, 0xE5, 0x6C, 0xA3, 0x73, 0x3D, 0xF5, 0x84, 0xFE, 0xA4, 0x90, 0xE6, 0x63, 0xD3, 0x16, 0xE1, 0xB3, 0x92, 0xBC, 0x9D, 0xCD, 0x24, 0x74, 0x5C, 0xBE, 0x57, 0x44, 0xBF, 0xA2, 0x6A, 0xC3, 0x9E, 0xFA, 0x4D, 0xA1, 0x66, 0xD7, 0xE6, 0xFE, 0xAD, 0x2D, 0x2C, 0xBF, 0xA1, 0x99, 0xD2, 0x7E, 0x2D, 0xE8, 0x15, 0x15, 0x68, 0xDF, 0xC7, 0x2C, 0x75, 0xF4, 0x8F, 0x54, 0x2E, 0xB0, 0x85, 0x28, 0xFE, 0x69, 0x8B, 0x68, 0xE9, 0x27, 0x44, 0xBB, 0xD6, 0x10, 0x38, 0x9E, 0x4C, 0x55, 0x32, 0x25, 0x9A, 0xC9, 0xC0, 0x81, 0x28, 0x16, 0x71, 0x2F, 0x55, 0x04, 0xFE, 0xF7, 0x22, 0xF1, 0x7F, 0xBD, 0xAA, 0x86, 0x53, 0x38, 0xD0, 0x31, 0x7E, 0xE2, 0x40, 0x94, 0x33, 0x4F, 0x38, 0xB2, 0x52, 0x12, 0x86, 0xE2, 0x92, 0x79, 0x91, 0xE8, 0xB7, 0xA2, 0x99, 0x87, 0x51, 0xC7, 0x53, 0x5C, 0x69, 0x8C, 0xE8, 0xDE, 0x79, 0xB2, 0xD9, 0xFA, 0xE3, 0x9A, 0x45, 0xDC, 0x4E, 0x0D, 0xCF, 0xE3, 0x63, 0x91, 0x47, 0x63, 0x2A, 0xD7, 0x82, 0x07, 0xD5, 0xEC, 0x31, 0x24, 0x4C, 0xD5, 0xA1, 0xF5, 0xE4, 0x5E, 0x19, 0xDA, 0x8F, 0x81, 0xC1, 0xA1, 0x83, 0x17, 0xFF, 0x5E, 0x17, 0xF9, 0xA6, 0xEF, 0xD2, 0x59, 0xFA, 0x0F, 0xDC, 0x04, 0x1A, 0xA8, 0x1B, 0x03, 0x81, 0xF8, 0x02, 0xD1, 0x1F, 0x52, 0x2E, 0x82, 0x6D, 0xAC, 0x6B, 0xE6, 0x49, 0x90, 0x8B, 0x4C, 0x93, 0x1E, 0xBA, 0x92, 0x32, 0xD3, 0x8E, 0x4A, 0x98, 0xD3, 0x0F, 0x60, 0x6A, 0x6B, 0x76, 0x0D, 0x6B, 0x25, 0x11, 0x76, 0xC8, 0xE8, 0x43, 0x62, 0x9C, 0x62, 0x00, 0xFF, 0xC4, 0x52, 0xA4, 0x3B, 0xA2, 0x7B, 0xA3, 0xB7, 0x14, 0xC1, 0x0D, 0x1E, 0xE1, 0xD1, 0xCC, 0xB3, 0xCB, 0x53, 0x1E, 0xFA, 0xB3, 0x68, 0x1A, 0xA5, 0x28, 0x8B, 0x0F, 0x69, 0x89, 0x40, 0x1E, 0x27, 0x95, 0x4C, 0x44, 0x33, 0x11, 0x08, 0x93, 0x31, 0x78, 0xC1, 0x83, 0x44, 0xC8, 0xD1, 0xB4, 0xEC, 0xD2, 0x27, 0xED, 0xB2, 0xFC, 0x32, 0x22, 0x8D, 0xE2, 0x5A, 0x56, 0x41, 0xD9, 0xFC, 0x1A, 0x57, 0xFB, 0xCD, 0x88, 0xD4, 0x13, 0x91, 0x95, 0xD3, 0xB5, 0x61, 0x33, 0x73, 0xCF, 0x2C, 0xE3, 0x68, 0x7E, 0x6F, 0x8B, 0xF8, 0x8B, 0xF8, 0xC6, 0xC4, 0xC0, 0x60, 0x4B, 0x0E, 0x59, 0x19, 0xB5, 0x95, 0x5B, 0xEB, 0xA8, 0x69, 0xBF, 0x75, 0x01, 0x4F, 0x63, 0xD6, 0x8E, 0x4D, 0x13, 0x78, 0xAC, 0x52, 0xE3, 0x73, 0xA2, 0xA5, 0x76, 0xFB, 0x32, 0x31, 0x27, 0xCF, 0x84, 0xC1, 0xDA, 0xFB, 0x22, 0xBE, 0xE0, 0x49, 0xFB, 0xCC, 0xF0, 0xD4, 0xC1, 0xF6, 0x17, 0x4F, 0xDA, 0xB9, 0x9C, 0x0E, 0x22, 0xB9, 0xD8, 0xC8, 0x9D, 0x63, 0x77, 0x5E, 0xBD, 0xC4, 0xB0, 0xDC, 0xB1, 0xD9, 0xDB, 0x34, 0x43, 0xBF, 0x1E, 0x35, 0x53, 0xC1, 0x95, 0x72, 0x3D, 0xCF, 0x5E, 0x41, 0x17, 0x6C, 0xA9, 0x77, 0xD2, 0xD4, 0xEC, 0x61, 0x18, 0x5B, 0xF6, 0x9A, 0xBD, 0xA0, 0xEC, 0xA3, 0xD9, 0x60, 0xAA, 0x57, 0x6F, 0x8A, 0x59, 0xFD, 0x7A, 0xAA, 0x57, 0xFE, 0xFF, 0x8F, 0x6B, 0x48, 0x53, 0xCD, 0xF1, 0x1E, 0x3C, 0x0B, 0x26, 0x96, 0xB1, 0x1D, 0x3D, 0x13, 0xC5, 0x99, 0x31, 0xDD, 0x5D, 0xCD, 0x23, 0x39, 0x96, 0xEE, 0x35, 0xEC, 0xCE, 0xB2, 0xBA, 0x6B, 0x5C, 0x68, 0x26, 0xC9, 0x8E, 0x42, 0x58, 0x54, 0x71, 0xA8, 0x56, 0x53, 0x2B, 0x57, 0x70, 0xDA, 0x04, 0x3E, 0x0F, 0x4E, 0x33, 0x99, 0xBE, 0x0E, 0x2C, 0xC6, 0xF8, 0x02, 0x07, 0x00, 0xA4, 0xE0, 0x62, 0x00, 0x4B, 0x14, 0x96, 0x10, 0x85, 0x32, 0x5C, 0x80, 0x75, 0x8A, 0x2F, 0xBD, 0x97, 0xBE, 0x64, 0x52, 0x10, 0xB3, 0xAC, 0xE4, 0x9F, 0xFF, 0xC9, 0xDF, 0x05, 0x45, 0x62, 0x4E, 0x51, 0xD0, 0xBF, 0xAC, 0x0D, 0x95, 0xD3, 0x84, 0x8C, 0x4A, 0xE9, 0x48, 0x17, 0xBE, 0x08, 0x80, 0x90, 0x17, 0xDE, 0x01, 0x0D, 0x2D, 0x09, 0x19, 0x28, 0x39, 0xB4, 0xED, 0x24, 0x09, 0x6A, 0x92, 0xC4, 0x49, 0x48, 0x68, 0x16, 0x09, 0x3A, 0x42, 0xE2, 0x22, 0x25, 0xB2, 0x90, 0x12, 0x27, 0xB1, 0xFD, 0x9F, 0x80, 0xAE, 0xE6, 0x38, 0x89, 0xBF, 0xA3, 0x94, 0x02, 0xD3, 0xDC, 0x24, 0xC5, 0xED, 0x61, 0x4A, 0x31, 0x13, 0x83, 0x2A, 0xAA, 0x5C, 0x7A, 0xD8, 0x51, 0xA5, 0x12, 0x83, 0xCD, 0xA4, 0x3A, 0xCD, 0x36, 0xFE, 0xA0, 0x14, 0xB4, 0xC7, 0xC5, 0x3E, 0xA8, 0x02, 0x33, 0x60, 0x4F, 0x40, 0xF0, 0x0B, 0x90, 0xCB, 0x04, 0x90, 0xBF, 0x54, 0xD3, 0x0F, 0x02, 0xE1, 0xAE, 0xB8, 0x89, 0x24, 0x5B, 0x52, 0x84, 0x71, 0xD8, 0x46, 0x29, 0x3E, 0x94, 0x35, 0x49, 0xE1, 0xA9, 0xC8, 0x1A, 0x29, 0xBE, 0x30, 0xAA, 0x2A, 0x44, 0x6D, 0x9A, 0xEA, 0x44, 0xC9, 0x16, 0x10, 0x8F, 0xC8, 0x33, 0x20, 0xDC, 0xCE, 0xB9, 0xD3, 0x96, 0x2A, 0x9C, 0xC2, 0x66, 0x6A, 0xB2, 0x06, 0x08, 0x67, 0x8E, 0x9E, 0x6B, 0xE4, 0x0E, 0x03, 0xE4, 0xE0, 0xBB, 0x0E, 0xAF, 0xE9, 0x63, 0x31, 0x79, 0xE5, 0xCA, 0xC6, 0xA1, 0x6C, 0x27, 0x05, 0xA7, 0x02, 0x41, 0x6B, 0xFA, 0x72, 0x2C, 0x07, 0xC4, 0xEB, 0xBA, 0xD1, 0xF3, 0xA6, 0xB0, 0xF3, 0xD9, 0xC2, 0x7F, 0x66, 0xD7, 0xFC, 0x35, 0x5C, 0x17, 0x33, 0xCD, 0x94, 0xA9, 0x13, 0x1C, 0x73, 0x52, 0x01, 0x5D, 0x62, 0x77, 0x9A, 0x28, 0x0E, 0x8F, 0x78, 0xCE, 0x1C, 0xBF, 0xB3, 0xC4, 0x33, 0x7A, 0x2F, 0x0E, 0xFC, 0xFB, 0x2F, 0xEE, 0xC5, 0xBD, 0xBE, 0x69, 0x2B, 0x65, 0xFA, 0x54, 0xA5, 0x55, 0xE9, 0x77, 0x96, 0x73, 0x35, 0xE9, 0xCA, 0xC2, 0xD5, 0x29, 0x7E, 0x49, 0x64, 0x4C, 0x2A, 0x3E, 0x78, 0x6C, 0x77, 0xEA, 0x63, 0xC4, 0x7D, 0x3E, 0x26, 0xEF, 0xC5, 0xC5, 0x4E, 0x9C, 0x8E, 0x38, 0x33, 0x43, 0x3F, 0xEA, 0x95, 0x60, 0x8A, 0xA3, 0xF8, 0x2C, 0x32, 0x24, 0x79, 0x64, 0xD4, 0x13, 0x83, 0x47, 0xC6, 0xFD, 0x94, 0x59, 0xF8, 0xFA, 0xCA, 0x9B, 0x79, 0x14, 0x26, 0xE3, 0x3A, 0x03, 0xCD, 0x93, 0xCB, 0x53, 0xA2, 0xD7, 0x27, 0x7A, 0xD5, 0x69, 0xE6, 0xEF, 0xC2, 0xFE, 0x18, 0xEF, 0xFE, 0xA6, 0xA7, 0x36, 0xC3, 0xDB, 0x9B, 0x5E, 0xFB, 0xA6, 0x1C, 0x8B, 0xB4, 0x19, 0xE7, 0x93, 0x85, 0xED, 0xE3, 0x93, 0xFD, 0x37, 0xB3, 0x36, 0xB3, 0xD0, 0x55, 0xD6, 0xF7, 0xDB, 0xE6, 0x5C, 0xEB, 0xDD, 0x5B, 0x45, 0xC1, 0x12, 0xA4, 0xB8, 0xBB, 0x95, 0xDD, 0x3E, 0xEB, 0x6F, 0x65, 0x4F, 0x9D, 0x45, 0xC1, 0x67, 0xAE, 0x77, 0x76, 0xC1, 0x77, 0x1F, 0x93, 0x25, 0x1D, 0xBA, 0x04, 0x1F, 0x04, 0x5F, 0x2E, 0x05, 0x55, 0xE1, 0x63, 0x97, 0xF3, 0xAC, 0x22, 0xF9, 0x2F, 0x2F, 0x58, 0x2A, 0x9D, 0x3C, 0xF8, 0x93, 0x2E, 0x46, 0x54, 0x28, 0xA4, 0x8A, 0x91, 0x14, 0xCA, 0xE6, 0x12, 0xCB, 0xD7, 0x48, 0xA3, 0xBC, 0xF4, 0xDA, 0x56, 0x3E, 0x5F, 0xC5, 0xFE, 0x97, 0xA0, 0xFF, 0x0D, 0x26, 0x0E, 0x8C, 0x87, 0x92, 0x87, 0x3E, 0x0F, 0x60, 0x63, 0x22, 0xE0, 0x6B, 0xAD, 0x0D, 0x9F, 0x9E, 0xAE, 0xCE, 0xA0, 0x17, 0xF1, 0xFF, 0xF8, 0xAC, 0x7F, 0x2D, 0x99, 0xE2, 0x1B, 0x96, 0xDE, 0xA8, 0xF4, 0xC6, 0xE9, 0x9F, 0x9D, 0xC4, 0xE1, 0x19, 0x8D, 0x70, 0x4A, 0x26, 0xA5, 0xCF, 0x7A, 0x85, 0xE5, 0xD9, 0xEF, 0xE8, 0xFF, 0xFB, 0xC7, 0x59, 0x46, 0x4D, 0xCF, 0x42, 0x57, 0x7E, 0xC3, 0x86, 0x34, 0x73, 0x47, 0xA3, 0x1F, 0x51, 0x6F, 0x13, 0x41, 0x3A, 0x0E, 0x9B, 0xDD, 0xFE, 0x03, 0x58, 0x4D, 0x7B, 0x09, 0xAE, 0x23, 0x10, 0x6C, 0x0D, 0x1F, 0xD1, 0x97, 0x88, 0x3E, 0xD8, 0x35, 0x03, 0xCB, 0xF2, 0xB2, 0x6A, 0x44, 0x07, 0x45, 0x2F, 0x87, 0xC8, 0xA0, 0x6E, 0x49, 0x23, 0x06, 0xE5, 0x32, 0x03, 0xFE, 0x34, 0x30, 0x6A, 0x08, 0x0C, 0xAA, 0x64, 0x92, 0x48, 0x15, 0xB0, 0x86, 0x25, 0xF1, 0xDF, 0x5D, 0xE3, 0xE4, 0xFD, 0x9B, 0xA4, 0x29, 0xC7, 0x97, 0xD6, 0x78, 0xE3, 0xBA, 0xC6, 0xEC, 0xEA, 0x88, 0x2F, 0x17, 0xA8, 0x94, 0xDE, 0x60, 0xA2, 0x9E, 0x6A, 0xC6, 0x44, 0xAD, 0x2F, 0xA2, 0x2A, 0x92, 0x34, 0x7F, 0xA7, 0x35, 0x0F, 0xEA, 0xD2, 0xF3, 0xD3, 0x8D, 0xCC, 0x56, 0x2D, 0xCB, 0xB6, 0xAA, 0xA9, 0x80, 0x51, 0xC7, 0x26, 0xF2, 0x93, 0x17, 0x87, 0x5B, 0x5A, 0xE3, 0xA7, 0xB0, 0x1C, 0x9F, 0x72, 0x7B, 0xDB, 0x38, 0x08, 0x07, 0xDF, 0xFD, 0x35, 0x41, 0xF0, 0xA6, 0xF0, 0x8C, 0xAC, 0x71, 0xC5, 0xD7, 0x10, 0x4F, 0xE4, 0xD2, 0x7B, 0x80, 0x8B, 0x94, 0xDB, 0xD9, 0x46, 0x7A, 0xC5, 0x4D, 0x4D, 0xA4, 0x85, 0x86, 0x54, 0x6C, 0x26, 0xFA, 0xE5, 0xD4, 0xF1, 0x35, 0x5A, 0x4E, 0x5E, 0x8F, 0x3D, 0x9D, 0x48, 0xCB, 0x89, 0x03, 0xE3, 0xF6, 0xEB, 0xB1, 0xE3, 0x57, 0x48, 0x47, 0xEC, 0x1F, 0x63, 0x84, 0xF0, 0xC3, 0x46, 0xF0, 0xCC, 0xC3, 0x18, 0x81, 0x87, 0x11, 0x6A, 0x8C, 0x10, 0x1E, 0x34, 0xC2, 0x57, 0x12, 0x89, 0xA9, 0x11, 0x23, 0xD2, 0x9B, 0x15, 0x52, 0xDF, 0x43, 0xD5, 0xCA, 0x95, 0x1A, 0x4E, 0xB9, 0x8D, 0x3F, 0xD9, 0x7D, 0xA8, 0x4A, 0xD8, 0x05, 0xC9, 0xE5, 0x1C, 0x18, 0x1F, 0xD1, 0x9B, 0x98, 0x4A, 0x8D, 0x34, 0x90, 0x99, 0xE2, 0x43, 0xF4, 0xA5, 0xEE, 0xD7, 0x5B, 0xD4, 0x90, 0xFD, 0x2D, 0xBB, 0x0C, 0xE2, 0x14, 0x36, 0x7F, 0xE8, 0x52, 0x17, 0x3D, 0x47, 0xC7, 0xEA, 0x7E, 0x4C, 0x41, 0xE0, 0x39, 0xFC, 0xAD, 0x8E, 0xB4, 0x97, 0x40, 0x1D, 0xEA, 0xAD, 0xFC, 0x3F, 0xE6, 0xDE, 0x09, 0x83, 0x0F, 0xFE, 0xFF, 0xFF, 0x0B, 0x99, 0xF1, 0xDF, 0xD2, 0x1E, 0xC1, 0xF5, 0x08, 0x08, 0x1E, 0xF9, 0xCB, 0xFF, 0x3F, 0x4B, 0x66, 0x04, 0xDA, 0x8F, 0x46, 0x5A, 0x0D, 0x85, 0xC0, 0xC7, 0x7C, 0x4E, 0x9E, 0x63, 0x10, 0xF6, 0x32, 0x75, 0xFE, 0xFF, 0x9F, 0xD1, 0xCF, 0xAC, 0xE9, 0x66, 0xCF, 0x59, 0xE9, 0x34, 0xFD, 0x6F, 0x92, 0xD3, 0x39, 0x81, 0xF5, 0x3F, 0x77, 0x07, 0xF1, 0x2B, 0xED, 0xA8, 0x3E, 0x26, 0xEE, 0x65, 0x5A, 0x9D, 0x38, 0x5C, 0xEB, 0x74, 0xCF, 0x99, 0xB1, 0x2F, 0x96, 0x22, 0xDE, 0xFF, 0xEF, 0x62, 0xFC, 0x70, 0xC9, 0x79, 0xE2, 0x29, 0x2C, 0x6D, 0xCC, 0xC7, 0x8E, 0x9F, 0x77, 0x1A, 0x96, 0x2D, 0x17, 0x2E, 0x45, 0x2C, 0x32, 0x05, 0x54, 0x02, 0x66, 0x57, 0xE2, 0xE9, 0x64, 0x8E, 0xEB, 0x8B, 0x6E, 0xBB, 0x6B, 0xED, 0x67, 0xA5, 0x34, 0x57, 0x69, 0x9A, 0xB4, 0x4C, 0xF9, 0xB1, 0x99, 0xA9, 0x00, 0x5F, 0x0A, 0x07, 0x31, 0x88, 0x80, 0x81, 0x01, 0x9F, 0x2F, 0x6E, 0xC4, 0x20, 0x03, 0x86, 0x2F, 0xDC, 0x80, 0x67, 0x02, 0x57, 0xB0, 0xC2, 0x0B, 0xCB, 0xC4, 0x37, 0x18, 0x10, 0xC2, 0xA2, 0x22, 0x8A, 0x07, 0xA4, 0x7E, 0xD8, 0xD0, 0xA0, 0x08, 0x19, 0xC4, 0x60, 0x0A, 0x1F, 0x38, 0x52, 0xD0, 0x43, 0x05, 0x1A, 0x20, 0x09, 0x05, 0x0C, 0xA0, 0x7C, 0x02, 0x3A, 0x96, 0x07, 0x0C, 0x60, 0x45, 0x00, 0xAE, 0x0B, 0x11, 0x05, 0x00, 0x2A, 0xF5, 0xC1, 0xB5, 0x75, 0x14, 0xE6, 0xA9, 0x49, 0x79, 0x44, 0x03, 0x39, 0x65, 0xCB, 0x27, 0xD0, 0x26, 0x10, 0x37, 0x38, 0x5F, 0x60, 0x00, 0x28, 0xB0, 0x6D, 0xDD, 0xDB, 0xAC, 0x6E, 0x07, 0x55, 0x74, 0x2E, 0x45, 0x52, 0x90, 0xD9, 0x60, 0xE6, 0x88, 0x07, 0xCC, 0x10, 0xE1, 0x03, 0xF0, 0xF7, 0x2C, 0xCF, 0x20, 0xF0, 0x28, 0xB6, 0xEB, 0xD2, 0x1C, 0xF1, 0xCD, 0xA0, 0xFB, 0xF3, 0x1C, 0x88, 0xBC, 0x2A, 0x7A, 0xDE, 0xBC, 0xA8, 0xBC, 0xA8, 0x2F, 0xE2, 0xBA, 0x4C, 0xE5, 0xDF, 0x3E, 0xBD, 0x0F, 0xBA, 0x2C, 0xEF, 0x5E, 0x6E, 0xE2, 0xDD, 0xC6, 0x73, 0x2F, 0x24, 0x12, 0xA9, 0x02, 0x23, 0x86, 0x3B, 0x21, 0x52, 0x17, 0xC1, 0x39, 0xEC, 0xA1, 0xA4, 0xAB, 0xE0, 0xCC, 0x9D, 0x55, 0x38, 0x69, 0x29, 0x9C, 0xFC, 0x51, 0x00, 0x9F, 0x80, 0xD0, 0x47, 0x2A, 0xC0, 0x06, 0xF5, 0x86, 0x90, 0x42, 0x15, 0xD0, 0x51, 0x30, 0xC7, 0x66, 0x12, 0xE7, 0x34, 0xBD, 0x39, 0x4D, 0x9F, 0xD3, 0x94, 0x6C, 0xB5, 0x5A, 0x34, 0x44, 0x69, 0x02, 0x60, 0xF4, 0x59, 0xC0, 0xE8, 0x31, 0x68, 0x80, 0x10, 0x27, 0x20, 0xBC, 0x2A, 0xE1, 0x1F, 0x62, 0xF3, 0xD7, 0xBC, 0x59, 0xC7, 0xFF, 0x6F, 0x5C, 0xF3, 0x9B, 0xE6, 0xE3, 0xFD, 0xC6, 0x9D, 0x65, 0x0D, 0xCD, 0x9F, 0xDE, 0xBF, 0xF3, 0x9D, 0x9F, 0xB4, 0xDB, 0xBC, 0xC3, 0x73, 0x8C, 0xE3, 0x5C, 0x8F, 0x8B, 0xC7, 0xDB, 0x7D, 0x37, 0x86, 0x42, 0xB5, 0x82, 0xEF, 0xF9, 0xB8, 0x51, 0xFA, 0xA3, 0x5C, 0xA5, 0x69, 0x9A, 0x88, 0xCB, 0xF9, 0x53, 0xB5, 0x56, 0x28, 0x66, 0x54, 0xDA, 0x2F, 0x6C, 0xEC, 0x99, 0xD6, 0xCE, 0x73, 0xEF, 0x7C, 0x46, 0xD9, 0xB6, 0x99, 0x46, 0x43, 0x4A, 0x7B, 0xD0, 0x36, 0xDE, 0x34, 0x49, 0x68, 0xB0, 0xA9, 0xA4, 0x19, 0x8B, 0x06, 0x3B, 0xD3, 0x35, 0x0C, 0xA1, 0xE1, 0x23, 0x99, 0x85, 0xF8, 0xB5, 0x5F, 0x95, 0x73, 0x0D, 0x48, 0xA2, 0xC9, 0x61, 0x4F, 0xC6, 0xDF, 0xBA, 0x9C, 0x7D, 0x3D, 0xB6, 0x2C, 0x7A, 0x48, 0x6B, 0x74, 0xCF, 0xFF, 0xBF, 0x8E, 0xED, 0x57, 0xA1, 0x4B, 0xFC, 0x31, 0x30, 0xB8, 0x25, 0xBB, 0x24, 0x52, 0xC8, 0x46, 0xB3, 0xFB, 0x8C, 0x84, 0xCD, 0x8B, 0x34, 0x96, 0xB3, 0x9F, 0xF4, 0x97, 0x73, 0xC3, 0x37, 0x7A, 0x36, 0x9D, 0x0F, 0x49, 0x8D, 0xB2, 0x1E, 0xAB, 0x73, 0x1A, 0x3D, 0xA2, 0x7F, 0x87, 0xD2, 0xFE, 0x83, 0x4C, 0x0C, 0x4C, 0x87, 0x7F, 0x58, 0x42, 0x35, 0xB6, 0xB0, 0xB0, 0x56, 0x50, 0x7E, 0x7F, 0x10, 0xA2, 0x57, 0x29, 0x1A, 0x86, 0x61, 0xB3, 0x38, 0x8D, 0x72, 0x84, 0x4D, 0xF7, 0x9E, 0x7E, 0x7D, 0x78, 0xAD, 0x28, 0x3F, 0x5C, 0xBB, 0x3F, 0x49, 0xD9, 0xE9, 0x83, 0x17, 0xF4, 0x93, 0x7D, 0xAD, 0x4A, 0x71, 0x2F, 0x4D, 0xA2, 0xAE, 0x55, 0x8B, 0xBB, 0x40, 0x07, 0x75, 0x5F, 0xE3, 0x76, 0x9E, 0x9F, 0x95, 0xDA, 0x4F, 0xDF, 0x59, 0xD7, 0x51, 0xE3, 0x62, 0x5C, 0x67, 0x8D, 0x4A, 0xD5, 0x54, 0x87, 0xFE, 0xF5, 0x64, 0x60, 0xE8, 0x19, 0x98, 0xF1, 0xD0, 0xBE, 0x24, 0x2A, 0xCD, 0x43, 0x3D, 0xE8, 0xD9, 0xE5, 0xE1, 0xA8, 0xA3, 0x56, 0x3A, 0xCF, 0x44, 0xAD, 0x64, 0x37, 0x63, 0xE1, 0x5A, 0x21, 0xAE, 0xCB, 0xFE, 0x4A, 0x66, 0xBE, 0xCC, 0x1C, 0x17, 0x4A, 0x88, 0x4C, 0x25, 0xA1, 0x1D, 0x9A, 0xB1, 0xEF, 0x7E, 0x74, 0x0D, 0x8A, 0x4B, 0xF7, 0x2E, 0x6B, 0x5B, 0xBD, 0x9C, 0xDB, 0xFF, 0x34, 0xB3, 0x37, 0x14, 0xFA, 0x4D, 0xA8, 0x57, 0x88, 0xE2, 0xCC, 0x0C, 0x65, 0xDC, 0xEA, 0x95, 0x65, 0x9C, 0x99, 0x3D, 0xDB, 0xC6, 0xCE, 0x4C, 0x5C, 0xB9, 0x58, 0x46, 0xBF, 0x92, 0xF5, 0x7D, 0x6E, 0x8A, 0x43, 0x6F, 0x31, 0xCE, 0xA2, 0x19, 0x4C, 0xFB, 0x76, 0x7E, 0xFF, 0x73, 0x2F, 0x35, 0x77, 0x57, 0xAD, 0xA3, 0x57, 0x03, 0x2F, 0xFE, 0x0F, 0x59, 0x4D, 0x7B, 0x66, 0x9A, 0x9A, 0x13, 0x46, 0x8A, 0xE4, 0xD0, 0x40, 0x7D, 0x09, 0xA3, 0xC2, 0x05, 0x2A, 0x50, 0xF9, 0x08, 0xA9, 0xC1, 0x02, 0x68, 0x78, 0x22, 0x08, 0x0B, 0x9B, 0x0E, 0x7C, 0x60, 0x02, 0xD8, 0xF8, 0xFE, 0xFF, 0x7F, 0xD1, 0x70, 0xFB, 0x86, 0xE5, 0x44, 0xEF, 0x3B, 0xD5, 0xE9, 0xA2, 0xB6, 0x20, 0x7C, 0x08, 0x4C, 0xE8, 0x28, 0x1B, 0x84, 0xD3, 0xC0, 0x21, 0xFA, 0x29, 0xC0, 0x2F, 0x27, 0x96, 0xA9, 0xA8, 0x84, 0x84, 0x95, 0x13, 0x53, 0xAB, 0x5A, 0x50, 0x39, 0x85, 0x48, 0x00, 0x86, 0x04, 0x01, 0x00, 0x73, 0xB5, 0x01, 0x1C, 0x10, 0x8D, 0x46, 0x82, 0x28, 0xCB, 0x33, 0x51, 0x7E, 0xC4, 0x04, 0xCB, 0x5E, 0x4B, 0x1F, 0x68, 0x46, 0x01, 0xA5, 0x98, 0x21, 0x00, 0x20, 0x20, 0x00, 0x01, 0x04, 0x12, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x82, 0x81, 0xEA, 0x39, 0x44, 0xC2, 0xF2, 0x59, 0xD3, 0x1C, 0xB0, 0xF7, 0x45, 0xF7, 0x8F, 0x49, 0xE0, 0xF9, 0x60, 0xB4, 0x8C, 0x56, 0x81, 0x2F, 0xF9, 0xC4, 0x5E, 0xD4, 0x1C, 0x36, 0xD4, 0xA1, 0xBB, 0x6A, 0x81, 0xDB, 0x65, 0x4A, 0xAA, 0xAF, 0xB3, 0x94, 0x1D, 0x68, 0x74, 0x01, 0xDB, 0xE4, 0x91, 0x4F, 0x5A, 0x5E, 0x1C, 0x19, 0xE4, 0x6C, 0xA4, 0x67, 0x13, 0x01, 0xF7, 0x09, 0x4D, 0xB0, 0x61, 0x7D, 0xE8, 0xA7, 0xBE, 0x50, 0x99, 0x55, 0x97, 0x31, 0xC7, 0x6C, 0xC9, 0xB4, 0xE7, 0xEC, 0x55, 0x3F, 0x39, 0x75, 0x53, 0x36, 0xDA, 0xC8, 0x7E, 0x4E, 0xFB, 0xE2, 0x8E, 0x0D, 0xF8, 0x92, 0xB5, 0x69, 0x07, 0x86, 0x00, 0x82, 0x75, 0x52, 0x1E, 0x9F, 0x7A, 0x70, 0xBA, 0xC4, 0x20, 0xB5, 0xD5, 0xA9, 0xF5, 0x85, 0xD9, 0x60, 0x81, 0xD2, 0xB0, 0x76, 0x23, 0x42, 0xDA, 0x82, 0x25, 0x8A, 0x7C, 0xE1, 0x02, 0xDB, 0xBA, 0xEE, 0x15, 0x59, 0xC0, 0x29, 0x45, 0xEC, 0x6B, 0x37, 0xD8, 0xAF, 0x7D, 0x2C, 0xB0, 0xEB, 0xF1, 0x89, 0xD0, 0x36, 0xBB, 0x87, 0x1D, 0xAB, 0xD8, 0xD4, 0x4D, 0xE7, 0x3A, 0xB8, 0xCD, 0x84, 0x6E, 0x59, 0x5D, 0x94, 0x21, 0x4D, 0xEF, 0x6C, 0x8E, 0x76, 0x10, 0x72, 0xAB, 0xDF, 0xEA, 0xD5, 0x14, 0x70, 0x36, 0xBB, 0xA3, 0x82, 0x9D, 0xF3, 0x58, 0xA5, 0x7B, 0x0B, 0x14, 0x6A, 0x83, 0xCD, 0xED, 0x00, 0x40, 0xEC, 0xF2, 0xDA, 0xD2, 0x7F, 0xCA, 0x04, 0xB7, 0x92, 0x0B, 0xB7, 0x57, 0xA4, 0x3D, 0x36, 0x7D, 0xF5, 0xEF, 0x7B, 0xBA, 0xFF, 0xED, 0x62, 0xFE, 0xC1, 0xE8, 0x7E, 0x32, 0xBE, 0x7F, 0x24, 0x5B, 0x94, 0xCD, 0xBB, 0x4A, 0xF6, 0xEA, 0x67, 0x4E, 0xF6, 0x49, 0x25, 0x87, 0x82, 0xB2, 0x27, 0x2C, 0x46, 0xD2, 0xEB, 0xD6, 0x81, 0xB4, 0x8B, 0xF1, 0x0E, 0xF5, 0x70, 0xBA, 0x3A, 0xF5, 0xFF, 0x21, 0xB8, 0x51, 0x7F, 0x94, 0x59, 0x88, 0xD5, 0xBA, 0x16, 0x1C, 0x17, 0x35, 0x65, 0xD2, 0x64, 0xC5, 0x93, 0x56, 0x02, 0x3B, 0xDE, 0x4A, 0xB0, 0xE0, 0xEB, 0x55, 0x4B, 0x25, 0x7C, 0x8E, 0xA6, 0x04, 0x68, 0xA3, 0x43, 0xDA, 0x12, 0xDB, 0x33, 0x1B, 0xB2, 0xC8, 0x8D, 0xB3, 0xB2, 0x17, 0x16, 0xF6, 0x13, 0xF5, 0xE6, 0x09, 0xD0, 0xED, 0x87, 0x06, 0x79, 0x9A, 0x02, 0x8D, 0x52, 0xC7, 0xD0, 0x2B, 0x48, 0xD1, 0x94, 0x8D, 0x7C, 0x68, 0x38, 0x3E, 0x72, 0xD4, 0x1D, 0x34, 0xCB, 0x0C, 0xF6, 0xA8, 0x21, 0xA5, 0x95, 0xD6, 0x34, 0xE6, 0x60, 0xA9, 0x48, 0x19, 0x1C, 0xC6, 0x8B, 0x82, 0xF8, 0xA9, 0xAE, 0x23, 0x41, 0xCA, 0x18, 0x34, 0x3D, 0x34, 0x16, 0x05, 0x96, 0x52, 0xA1, 0x07, 0x4D, 0xD4, 0xDE, 0xB1, 0x24, 0x0F, 0x8D, 0xBA, 0xB7, 0x6C, 0xD0, 0x83, 0x86, 0x05, 0xC9, 0x5D, 0x3B, 0xF7, 0xC1, 0xD0, 0x00, 0xB2, 0xF1, 0x7B, 0xD1, 0x50, 0x50, 0xD4, 0x48, 0xC5, 0x28, 0xEA, 0xC7, 0x2B, 0x64, 0x58, 0x01, 0xB6, 0x60, 0x4B, 0x99, 0xA4, 0x8B, 0x26, 0x33, 0xC7, 0x85, 0xA0, 0x60, 0x0D, 0xB3, 0x79, 0x74, 0xD6, 0xC8, 0x15, 0x41, 0x5C, 0xA3, 0xB2, 0x16, 0x7A, 0xD0, 0x1C, 0x7E, 0xCF, 0x4A, 0xDB, 0x9C, 0x51, 0x78, 0x2F, 0xE0, 0x87, 0xA6, 0xF5, 0xA4, 0x81, 0xE5, 0xC4, 0x73, 0x3B, 0x5E, 0xD1, 0x0B, 0x33, 0x33, 0x27, 0x88, 0xAD, 0x2D, 0x3E, 0x1A, 0x41, 0xDA, 0x83, 0xC6, 0xB1, 0x53, 0x34, 0xE3, 0xC9, 0x2E, 0x4F, 0x2D, 0x68, 0xA5, 0x39, 0x0B, 0xA0, 0xCE, 0xDF, 0xDA, 0xE1, 0xCC, 0x6B, 0x7F, 0xF2, 0x4A, 0x1E, 0x1A, 0xA9, 0xCB, 0x38, 0xAD, 0x1D, 0xC2, 0xD0, 0x79, 0x3B, 0xD3, 0xDA, 0xE4, 0x97, 0x72, 0xAD, 0x92, 0x7E, 0x9F, 0xFB, 0xF5, 0xF5, 0xD6, 0x7C, 0x6B, 0xE6, 0x34, 0x73, 0x24, 0x65, 0xDD, 0xC7, 0xCF, 0x1E, 0x9A, 0x62, 0xED, 0x50, 0xC3, 0x69, 0x5B, 0xCB, 0xC1, 0xFB, 0xEE, 0x5F, 0xD1, 0x0F, 0xC8, 0xCD, 0x68, 0x45, 0x54, 0x5F, 0x67, 0x1E, 0xC9, 0xC0, 0x79, 0x43, 0x96, 0x24, 0xF4, 0xD0, 0x08, 0xAB, 0x50, 0x4C, 0x99, 0x10, 0x54, 0xDC, 0x31, 0xB2, 0xF3, 0xA9, 0x99, 0x99, 0x23, 0x43, 0xA9, 0xE6, 0xC7, 0xF7, 0x2E, 0x26, 0xF5, 0x8C, 0xB9, 0x75, 0xB9, 0xB8, 0x6A, 0x62, 0x6F, 0xAB, 0x36, 0x9C, 0x99, 0x33, 0xD4, 0x14, 0xC2, 0xA5, 0x06, 0xC8, 0x5C, 0xD8, 0x45, 0x3A, 0x21, 0x94, 0x5B, 0x30, 0x74, 0xFA, 0xE7, 0x8B, 0x7E, 0x7B, 0x11, 0x06, 0x86, 0x47, 0xE2, 0x00, 0xF5, 0x40, 0x86, 0x89, 0xD1, 0xD7, 0x79, 0xD4, 0xE5, 0x5E, 0xAC, 0x65, 0x24, 0xBD, 0x81, 0x50, 0x79, 0xE1, 0xD4, 0xBA, 0xC8, 0xA1, 0xFD, 0x84, 0xDA, 0x26, 0x56, 0x64, 0x02, 0x1A, 0xCA, 0xE4, 0xC5, 0x55, 0x11, 0x5C, 0x60, 0xC1, 0xB5, 0xB2, 0xB9, 0xB8, 0x03, 0xAA, 0x1E, 0x08, 0xD6, 0x1F, 0x08, 0x61, 0x47, 0x1D, 0xC3, 0xA1, 0xE2, 0x29, 0x2C, 0x59, 0x8B, 0xDC, 0x76, 0xEB, 0x88, 0x87, 0x3A, 0xB6, 0x1E, 0x84, 0x5C, 0xD4, 0xEF, 0xC0, 0xA7, 0xBE, 0xE8, 0x91, 0xC2, 0x83, 0xB0, 0x7A, 0x03, 0xD6, 0x6B, 0xCC, 0x48, 0x88, 0xA5, 0xB5, 0x6F, 0xB3, 0x34, 0x8C, 0x3B, 0x33, 0x8E, 0xC0, 0x2F, 0xE7, 0x43, 0xF4, 0xA5, 0xE7, 0xD0, 0x98, 0xAB, 0xEF, 0xBF, 0xB5, 0xA5, 0x17, 0x6B, 0x9A, 0x8D, 0x98, 0xAD, 0x10, 0x8E, 0x43, 0xB7, 0x22, 0x0E, 0x87, 0xFB, 0xBD, 0xAD, 0x98, 0xDD, 0xA2, 0x0C, 0x9F, 0x00, 0xAC, 0xD8, 0x44, 0x96, 0x50, 0x43, 0xC4, 0x09, 0x1D, 0xFC, 0xD0, 0x7A, 0xCA, 0x6E, 0x97, 0xCD, 0xBE, 0xBA, 0x3E, 0x31, 0x1B, 0xB1, 0x1D, 0x83, 0xE5, 0x79, 0xEC, 0xA2, 0x41, 0xC9, 0x6C, 0x33, 0x0E, 0x7B, 0x0C, 0x3F, 0x31, 0xCD, 0x80, 0x97, 0x5E, 0xD8, 0xE8, 0x6D, 0xA2, 0xAE, 0xB4, 0x35, 0xF7, 0x79, 0x28, 0x6B, 0xF3, 0xB2, 0x00, 0x32, 0x97, 0xB3, 0x8C, 0xA1, 0xC2, 0x6E, 0xAF, 0xB8, 0xDF, 0xE5, 0x3A, 0x0E, 0x80, 0xA3, 0xB3, 0xBA, 0x1C, 0x67, 0xB0, 0x72, 0xDE, 0x70, 0x7A, 0x68, 0x1D, 0x73, 0xE8, 0xBA, 0xA7, 0xC9, 0x3A, 0xD8, 0x90, 0x45, 0xFE, 0xE6, 0xE4, 0x7A, 0xE1, 0x75, 0xF6, 0xB8, 0x5A, 0x38, 0x97, 0xF3, 0x40, 0xF4, 0x43, 0x3F, 0x4B, 0x20, 0xB6, 0xEE, 0x65, 0x8D, 0x9F, 0x51, 0x19, 0xC1, 0xB9, 0x66, 0x29, 0x91, 0x8D, 0x89, 0xFF, 0x3E, 0x86, 0x9F, 0xD9, 0xDD, 0x27, 0xCB, 0x42, 0x52, 0x6D, 0x42, 0x68, 0x22, 0x5A, 0xAB, 0x79, 0x75, 0x94, 0x53, 0xBD, 0xA0, 0xD8, 0x1D, 0xF3, 0x6A, 0x07, 0xB1, 0xDE, 0x29, 0x36, 0xDE, 0x96, 0x22, 0x17, 0x63, 0x74, 0xAF, 0xFD, 0x46, 0x43, 0x95, 0xE2, 0xD2, 0xC6, 0x74, 0xF8, 0x47, 0x8C, 0xA2, 0x86, 0x56, 0x7F, 0xEA, 0x1F, 0xE5, 0xE6, 0x91, 0xB3, 0x95, 0x15, 0xA9, 0x5E, 0x55, 0xDB, 0x17, 0x6D, 0xCB, 0x7A, 0xAB, 0xB0, 0xB8, 0x69, 0x0A, 0x43, 0x93, 0xA2, 0xDE, 0x6A, 0x13, 0x78, 0xC9, 0x2A, 0xAE, 0xC9, 0x6C, 0xDD, 0xC8, 0x0D, 0x29, 0xD4, 0xBA, 0x1B, 0x07, 0xEF, 0x9B, 0x49, 0x34, 0xA6, 0x0E, 0xF3, 0xC1, 0x54, 0xC8, 0x7C, 0x26, 0x14, 0x4F, 0x2D, 0x17, 0xED, 0xCD, 0x05, 0x47, 0xBF, 0x77, 0xF4, 0x8F, 0x80, 0x72, 0xCE, 0x2A, 0x83, 0xFC, 0x85, 0x78, 0xFB, 0x40, 0xE9, 0x28, 0xF6, 0xAC, 0x47, 0x5F, 0x3D, 0x75, 0x19, 0xC9, 0xBE, 0x15, 0x94, 0x03, 0x61, 0xD6, 0x46, 0xBC, 0xF1, 0xE2, 0x8D, 0xCC, 0x6B, 0xF5, 0x2B, 0xFB, 0xA3, 0x25, 0xD2, 0x53, 0x1A, 0x8E, 0x28, 0x92, 0x32, 0x3A, 0xC0, 0x6F, 0x53, 0xDB, 0x86, 0x3A, 0x36, 0x35, 0xFD, 0x5C, 0xD0, 0x4A, 0xDC, 0x19, 0xEC, 0x50, 0xCE, 0x5D, 0x31, 0x6C, 0x24, 0xCE, 0x0D, 0x57, 0x53, 0x58, 0xC1, 0x9F, 0xBD, 0xF9, 0x08, 0xC9, 0xBF, 0x42, 0x7C, 0x04, 0xF4, 0xA6, 0xCF, 0xF3, 0xFB, 0x7C, 0xEC, 0xF2, 0x30, 0x2C, 0x2D, 0x32, 0xD8, 0x02, 0x2E, 0x1D, 0xB6, 0x64, 0x7E, 0x0B, 0x98, 0x05, 0x3C, 0x5C, 0x06, 0x51, 0x28, 0x9D, 0x31, 0x9A, 0x15, 0x79, 0x22, 0x74, 0xDC, 0x48, 0x4A, 0x1D, 0x83, 0x19, 0xD8, 0x91, 0xF4, 0x05, 0xF6, 0xBC, 0x85, 0x5E, 0x6C, 0x44, 0xAD, 0x3B, 0xE5, 0x12, 0xA7, 0xE2, 0x95, 0xD4, 0xB9, 0xC0, 0xB0, 0x1C, 0x44, 0xBC, 0x2D, 0x24, 0xD6, 0xBB, 0xB6, 0x39, 0x8C, 0x15, 0x91, 0xF0, 0xCB, 0x77, 0x82, 0x19, 0xB1, 0x66, 0x06, 0xD3, 0x85, 0x38, 0xEE, 0x06, 0x8E, 0x37, 0x70, 0xA3, 0x14, 0xCF, 0x33, 0x73, 0x1A, 0xF2, 0xDA, 0x0E, 0xA7, 0x68, 0xA3, 0x7D, 0xF6, 0xE9, 0xCC, 0xBE, 0xDB, 0xC7, 0x4E, 0xDD, 0x36, 0xC5, 0x3B, 0x15, 0x2D, 0x31, 0x1F, 0x21, 0xBA, 0xC1, 0xD8, 0x1C, 0x44, 0x21, 0x37, 0x3B, 0xC0, 0x26, 0x69, 0x5A, 0xE9, 0xCC, 0xD7, 0x54, 0xA3, 0x26, 0x97, 0x61, 0x3D, 0x65, 0x07, 0x6B, 0xDE, 0xC4, 0x70, 0x99, 0x69, 0x79, 0x5B, 0xFB, 0xC2, 0x2E, 0x77, 0xF2, 0xD1, 0x13, 0xA7, 0x9E, 0x61, 0xDA, 0xB4, 0x31, 0x59, 0x7C, 0x80, 0x63, 0xE2, 0xB7, 0x22, 0x65, 0x4E, 0x85, 0xB4, 0x5E, 0xE3, 0xC0, 0xB7, 0x62, 0x30, 0x87, 0xDD, 0x53, 0x83, 0x36, 0xCE, 0x1D, 0xCF, 0x99, 0x72, 0x37, 0xCD, 0x1C, 0x2F, 0x25, 0xF0, 0x0B, 0xC2, 0x30, 0x45, 0x0D, 0x9E, 0x4B, 0x82, 0x56, 0xC8, 0xA3, 0xFE, 0xDD, 0xC5, 0x98, 0x16, 0x98, 0x3B, 0x68, 0xC0, 0x38, 0xF2, 0x9F, 0x48, 0xEA, 0x2B, 0x3A, 0x2D, 0x25, 0x25, 0x81, 0xEA, 0x10, 0xE8, 0x30, 0x73, 0x89, 0xA5, 0x8B, 0x6B, 0xDF, 0xAA, 0x37, 0xB8, 0xD3, 0x52, 0x78, 0xCF, 0x2F, 0xF7, 0x76, 0x98, 0x97, 0xF8, 0xC9, 0x57, 0x41, 0x91, 0xAA, 0x31, 0xE3, 0x2D, 0x28, 0x2E, 0x33, 0xD1, 0x39, 0x92, 0x9D, 0xFC, 0xF0, 0xF1, 0x63, 0x50, 0x20, 0x0E, 0xC4, 0xA6, 0x76, 0x00, 0xEE, 0xA3, 0x56, 0xC7, 0x5A, 0xEF, 0xA9, 0x7F, 0x6F, 0x08, 0x84, 0xB3, 0x75, 0x01, 0x45, 0x7B, 0x89, 0x88, 0x7A, 0x32, 0x71, 0x35, 0x28, 0x7D, 0x58, 0xF4, 0x6C, 0x9B, 0x96, 0x69, 0x9B, 0x3D, 0x48, 0xA8, 0x72, 0x5A, 0x14, 0xE2, 0x11, 0x77, 0x71, 0x85, 0x41, 0xB3, 0x79, 0x69, 0x0A, 0x0D, 0x91, 0xC1, 0xD4, 0x63, 0x20, 0x80, 0x01, 0x76, 0xE7, 0x0A, 0x52, 0x92, 0x66, 0x68, 0x2F, 0xE5, 0x2E, 0xEC, 0xAE, 0x8F, 0xC0, 0x43, 0x02, 0x8A, 0x5D, 0x52, 0x9B, 0x08, 0xB7, 0x6C, 0x90, 0x74, 0x57, 0x79, 0x59, 0x9D, 0x6E, 0x6B, 0x32, 0x93, 0x5B, 0x5E, 0x58, 0x14, 0xDA, 0xD0, 0xF5, 0xC0, 0x3F, 0x6A, 0x07, 0x84, 0x57, 0x2E, 0xAA, 0x48, 0x84, 0xF5, 0x96, 0x88, 0xB9, 0x42, 0x78, 0xB7, 0xF2, 0x31, 0x4F, 0xF8, 0x81, 0x7E, 0xD5, 0x3E, 0xD8, 0xB9, 0xAC, 0xF3, 0x7E, 0xFC, 0x7A, 0xBA, 0x09, 0xE7, 0x4B, 0x18, 0x59, 0x58, 0x1F, 0x4E, 0xFC, 0xF5, 0x55, 0x77, 0xCA, 0x2C, 0x65, 0xB3, 0x5C, 0xF4, 0x1D, 0x64, 0x98, 0xA1, 0x32, 0x31, 0x33, 0x88, 0x80, 0xCE, 0xEC, 0x22, 0x3E, 0xDE, 0x56, 0x83, 0x2B, 0xAF, 0x49, 0xA9, 0x71, 0xB0, 0x7A, 0x1E, 0x9D, 0x57, 0x0E, 0xC0, 0x23, 0xC5, 0x1E, 0x7A, 0xE3, 0xD0, 0x5E, 0x23, 0x12, 0xED, 0x8E, 0x90, 0xBB, 0xFB, 0xF4, 0xDA, 0xAF, 0x1B, 0x07, 0x22, 0x2D, 0x76, 0xE5, 0x70, 0x09, 0x30, 0x11, 0x5B, 0xED, 0x90, 0xD3, 0xC2, 0x91, 0x14, 0x71, 0xE5, 0x5E, 0xFC, 0x08, 0xFA, 0x46, 0x31, 0x1D, 0xA3, 0xDC, 0xEC, 0x0A, 0x5F, 0xFA, 0xDB, 0xB8, 0xAF, 0x52, 0xD6, 0x5C, 0xAE, 0x44, 0x91, 0xA8, 0x32, 0x8E, 0x21, 0x49, 0x04, 0x06, 0x0E, 0x10, 0x9E, 0x74, 0x25, 0x39, 0xD1, 0x27, 0xD4, 0xB1, 0x8D, 0x85, 0x36, 0xC5, 0x49, 0x9F, 0x05, 0xF4, 0x4D, 0x80, 0xD6, 0x75, 0x8D, 0x87, 0xB7, 0xA8, 0xD6, 0x30, 0xA5, 0x8B, 0x96, 0x8A, 0xA6, 0x47, 0xCC, 0x2D, 0x76, 0x29, 0x13, 0x2E, 0xF5, 0xB3, 0x23, 0x70, 0x4B, 0xE9, 0xDC, 0x72, 0xA8, 0x94, 0x98, 0x05, 0xAE, 0x69, 0x1A, 0x2D, 0xA1, 0x1F, 0x59, 0xBD, 0xD2, 0x28, 0xA3, 0x5C, 0xCA, 0x0A, 0x25, 0x1E, 0x97, 0x95, 0x96, 0xC9, 0x05, 0x59, 0x8C, 0xDF, 0x12, 0x11, 0x68, 0x81, 0x62, 0x02, 0xB7, 0x06, 0x78, 0x11, 0x44, 0x9C, 0x04, 0x2D, 0xAE, 0x91, 0xD6, 0x87, 0xEE, 0x20, 0xF5, 0xD0, 0x15, 0xB4, 0xD6, 0xFB, 0x96, 0x88, 0xF9, 0x00, 0x3B, 0xAE, 0xDE, 0xD3, 0xA0, 0xD5, 0xAA, 0x7C, 0x60, 0xE0, 0x7B, 0x76, 0xE0, 0xE5, 0xDF, 0x18, 0x3D, 0x83, 0xB9, 0x01, 0x35, 0xEC, 0x68, 0x01, 0x7E, 0x03, 0xB0, 0xE9, 0xC3, 0x61, 0xAD, 0x93, 0xE4, 0xDB, 0x1B, 0xAF, 0xE9, 0xE0, 0xAD, 0xF3, 0x90, 0x99, 0xDD, 0x0C, 0x9E, 0x32, 0xBB, 0x48, 0x7E, 0xBA, 0x67, 0x1E, 0x21, 0x73, 0xB7, 0xC5, 0x60, 0x5E, 0x2F, 0x33, 0x1E, 0x63, 0x64, 0xD8, 0x49, 0x73, 0x88, 0x9A, 0x8F, 0xE1, 0x74, 0xAA, 0x69, 0x08, 0xE8, 0xAD, 0xB1, 0x41, 0xC4, 0x9A, 0x70, 0x3F, 0x37, 0x5F, 0x4B, 0x47, 0x7B, 0xCC, 0xAE, 0x0D, 0x19, 0x5A, 0x17, 0x4D, 0xC9, 0x3B, 0xB4, 0x9C, 0x33, 0x57, 0xC0, 0xDD, 0x96, 0x79, 0xCC, 0xA2, 0x53, 0xF8, 0xDD, 0x75, 0xE7, 0xA5, 0x2A, 0x19, 0x9E, 0x4D, 0xBD, 0x5A, 0x85, 0x01, 0x19, 0x3E, 0x6C, 0xE0, 0x58, 0x4B, 0x6F, 0xCD, 0x47, 0xB0, 0x2C, 0x5F, 0x0E, 0xA2, 0xBD, 0xDF, 0xA8, 0xF1, 0xDA, 0x89, 0x43, 0x8E, 0xF9, 0x19, 0xA3, 0x17, 0xBD, 0x5C, 0x2B, 0x23, 0x11, 0xD5, 0xB4, 0xCF, 0x95, 0x60, 0xDC, 0xEF, 0xB4, 0x79, 0xA4, 0x3D, 0x01, 0xFE, 0x77, 0x73, 0x4B, 0x2F, 0xF6, 0x00, 0xBC, 0x18, 0xD1, 0x24, 0x6F, 0xD5, 0x6E, 0x97, 0x3E, 0xDE, 0x9A, 0xE2, 0x5D, 0x0C, 0x92, 0xA1, 0x7F, 0xF5, 0xFB, 0x75, 0x7D, 0x8E, 0x16, 0xB6, 0x49, 0x70, 0x5A, 0x45, 0x77, 0x6E, 0xAC, 0x41, 0xC0, 0x67, 0x3C, 0xB1, 0x0F, 0xC1, 0x5A, 0x7C, 0x6F, 0x66, 0x11, 0xE8, 0x3F, 0x70, 0xCC, 0x1E, 0x40, 0xE2, 0x1C, 0x70, 0xF0, 0x06, 0x65, 0x33, 0x95, 0x00, 0x35, 0x23, 0x6A, 0x23, 0xB7, 0xF8, 0x58, 0x0F, 0xAD, 0x2D, 0x6B, 0x61, 0x39, 0x4B, 0xAF, 0x4F, 0x24, 0xAC, 0x74, 0x3E, 0x30, 0x70, 0xD2, 0x66, 0x0A, 0xC1, 0x98, 0xB7, 0x22, 0xBB, 0x70, 0x05, 0x09, 0x2F, 0xB6, 0xCE, 0xC5, 0xB6, 0x3B, 0x88, 0x7A, 0xAA, 0x77, 0x10, 0xE9, 0xE3, 0xA7, 0x13, 0x3E, 0x9E, 0x50, 0xE0, 0x1D, 0x22, 0x61, 0x70, 0xAD, 0x1F, 0xC7, 0x8F, 0x85, 0xF1, 0x36, 0x2C, 0xEA, 0xBC, 0x6D, 0x3D, 0xC4, 0xB3, 0x12, 0xD1, 0x92, 0xAF, 0xA7, 0x74, 0x4A, 0x16, 0x7A, 0x4B, 0xE0, 0x07, 0x6A, 0x6D, 0x50, 0xFD, 0x99, 0x83, 0x06, 0xCC, 0x17, 0x4E, 0x05, 0xBC, 0x97, 0x79, 0x63, 0x10, 0x8D, 0x5D, 0xAA, 0x62, 0xE5, 0xBB, 0x1B, 0x4C, 0xFA, 0x5D, 0xF4, 0x89, 0x90, 0xBD, 0xD1, 0xD5, 0xE1, 0x2A, 0xCC, 0x02, 0x47, 0x4E, 0x26, 0xC0, 0xBD, 0x86, 0xA0, 0xCA, 0xFA, 0x92, 0x1C, 0xB9, 0xDE, 0x65, 0xF7, 0x8F, 0xAC, 0xCE, 0x4B, 0x36, 0x6A, 0x05, 0x5D, 0x8E, 0xFA, 0x77, 0xB2, 0x89, 0x57, 0x62, 0x66, 0xE7, 0x28, 0x3D, 0x7D, 0x72, 0xE0, 0x51, 0x20, 0x9D, 0xDB, 0xD7, 0x39, 0x27, 0x89, 0x02, 0xAA, 0x5E, 0x64, 0x4F, 0xD4, 0x9E, 0xB8, 0x1D, 0x0F, 0x58, 0xEC, 0x34, 0x00, 0xCD, 0x81, 0xA3, 0xD7, 0xDE, 0xE0, 0xC2, 0x17, 0x61, 0xBB, 0x71, 0x38, 0xCB, 0x86, 0xCB, 0xBD, 0xFB, 0x3B, 0x7F, 0x4E, 0xB7, 0x01, 0xDF, 0x84, 0x6E, 0x2A, 0xEE, 0x15, 0x3C, 0x53, 0x20, 0x65, 0x2B, 0xC5, 0xDA, 0x40, 0x85, 0xE9, 0xC2, 0x53, 0x25, 0x95, 0x15, 0xEB, 0xB7, 0x70, 0xA0, 0x85, 0x60, 0x06, 0xFE, 0x0F, 0x61, 0x30, 0x00, 0x77, 0x19, 0x27, 0xED, 0xB7, 0xBA, 0x5C, 0x92, 0xD7, 0xEF, 0xE1, 0xBE, 0x0D, 0xB4, 0x4C, 0x45, 0x17, 0x2C, 0xD5, 0x6C, 0x74, 0xC7, 0x9C, 0xE9, 0x3C, 0x2B, 0x28, 0xD0, 0xBA, 0x03, 0xD0, 0x6F, 0x3C, 0x11, 0xBA, 0xEB, 0x8B, 0xA7, 0x16, 0x1B, 0xFB, 0x71, 0x59, 0x42, 0x93, 0xB8, 0x86, 0xEA, 0xE4, 0xE0, 0xB5, 0xBA, 0xD9, 0x08, 0xA9, 0x3B, 0xB4, 0x6D, 0xCE, 0xD7, 0xD8, 0x8A, 0x35, 0x70, 0x7D, 0x63, 0x34, 0x57, 0x25, 0xD4, 0x6D, 0x63, 0x26, 0x17, 0xF4, 0x66, 0x5B, 0x10, 0x0F, 0x97, 0x04, 0xCF, 0xD5, 0x76, 0xB6, 0xF4, 0x32, 0x8F, 0xF7, 0x2B, 0x34, 0x26, 0xBA, 0xF1, 0xE0, 0xF3, 0x9A, 0x9A, 0x38, 0xE4, 0xE9, 0xF5, 0xD1, 0xDC, 0x69, 0xDA, 0x78, 0x27, 0xCC, 0x74, 0x4A, 0xA0, 0x7F, 0x5B, 0x16, 0x9B, 0xEF, 0x97, 0xEF, 0xCE, 0xEC, 0x5C, 0xE5, 0x4D, 0xA4, 0x41, 0xBA, 0xD9, 0x99, 0xAE, 0x41, 0x14, 0xA8, 0x91, 0x1E, 0xC4, 0x1C, 0xF1, 0x98, 0xA0, 0x49, 0x67, 0xFC, 0x79, 0x0C, 0x1C, 0x2B, 0xAB, 0xF6, 0xF1, 0xBD, 0x46, 0x45, 0x80, 0xEF, 0x58, 0x91, 0x71, 0x9A, 0x6A, 0x99, 0xEC, 0xA7, 0x91, 0xB1, 0x49, 0xAD, 0x58, 0xE2, 0xA6, 0xE9, 0x3E, 0x36, 0x6F, 0x9D, 0x9B, 0x1F, 0x52, 0x54, 0xED, 0x25, 0xEE, 0x6E, 0x7E, 0xEB, 0xEC, 0xD1, 0x97, 0x08, 0x7A, 0x5A, 0x47, 0x57, 0xF0, 0x45, 0x0A, 0x79, 0xF8, 0x87, 0x9F, 0x6E, 0x1D, 0xF4, 0x5B, 0xDE, 0x8C, 0x17, 0x34, 0x74, 0x12, 0x3B, 0x4C, 0xDB, 0x9B, 0xBE, 0x5E, 0x2D, 0x2C, 0x74, 0x5A, 0x10, 0xB9, 0x72, 0x75, 0x7F, 0x1C, 0x2B, 0xA2, 0x6B, 0x13, 0x40, 0x4C, 0xE1, 0xAC, 0x68, 0xA0, 0x35, 0x2E, 0xA4, 0xEE, 0x71, 0x95, 0x19, 0x31, 0x50, 0xA3, 0xE3, 0x52, 0x38, 0x17, 0x96, 0x26, 0xF0, 0x85, 0xEE, 0xFE, 0x69, 0x60, 0xF4, 0xE0, 0x6D, 0x6E, 0x00, 0x0B, 0x36, 0x3B, 0x1E, 0x67, 0xF9, 0x0F, 0xED, 0x2B, 0x14, 0xF1, 0x64, 0xAB, 0xB8, 0x83, 0x0D, 0xB2, 0x0C, 0x53, 0x73, 0xD9, 0x7E, 0x35, 0x4C, 0x40, 0xA6, 0x90, 0x69, 0x68, 0xF6, 0x8E, 0xC7, 0xE5, 0xBB, 0x7E, 0x51, 0x21, 0x66, 0xFD, 0xC3, 0x69, 0x35, 0xDC, 0x86, 0x76, 0x4B, 0x0D, 0x08, 0x4C, 0xA2, 0xE9, 0xF1, 0x61, 0xAA, 0x9E, 0x0C, 0xAD, 0x8C, 0xAB, 0x2E, 0x16, 0x04, 0x2F, 0xC4, 0xDA, 0x88, 0xE4, 0x30, 0x4F, 0x66, 0xB9, 0x13, 0xFC, 0xA1, 0x26, 0xF8, 0x61, 0xFD, 0xB0, 0x62, 0x0A, 0x1F, 0xA1, 0x94, 0x46, 0x4A, 0xAF, 0x3B, 0x06, 0x72, 0x23, 0x01, 0xAF, 0xAF, 0x75, 0x4A, 0x1F, 0x97, 0x17, 0x3E, 0xAA, 0x6C, 0x21, 0x5C, 0xA9, 0xE5, 0x03, 0xBC, 0xEF, 0x7C, 0x5C, 0xD6, 0xFA, 0xE8, 0x1C, 0x90, 0x8F, 0xDC, 0xE8, 0x52, 0xA9, 0x48, 0x3C, 0x4F, 0x17, 0x49, 0x2C, 0x9B, 0xBC, 0x36, 0xF7, 0xCC, 0x02, 0x7B, 0x45, 0xA7, 0x38, 0x46, 0xA7, 0xA4, 0x96, 0x3F, 0xBA, 0x6F, 0x1F, 0xB1, 0x3E, 0xA5, 0x93, 0x54, 0x0B, 0x7E, 0xDA, 0xD5, 0x8F, 0x5D, 0x5C, 0x61, 0x13, 0x4B, 0xA4, 0x39, 0xD0, 0x40, 0x8A, 0x8D, 0x09, 0x04, 0x5D, 0x90, 0xE2, 0x37, 0xAD, 0x0E, 0x1C, 0xCD, 0x5C, 0xF9, 0x3A, 0x82, 0xBA, 0x09, 0x3A, 0x54, 0xC6, 0xF7, 0x05, 0x2A, 0x62, 0xB3, 0xCC, 0xCA, 0x28, 0xD0, 0x9E, 0x35, 0x73, 0xF8, 0xDE, 0xE4, 0x77, 0x8C, 0x2D, 0xA9, 0xF0, 0xE6, 0x0A, 0x42, 0xCC, 0x93, 0xF6, 0x6A, 0xC7, 0x47, 0x0D, 0x7C, 0xDE, 0xD5, 0x4A, 0x84, 0xFA, 0x36, 0xEF, 0xBF, 0x0C, 0x7E, 0xDF, 0x5D, 0x61, 0xB9, 0x4F, 0x2F, 0x53, 0x28, 0x72, 0x0C, 0x73, 0x26, 0x5E, 0xD7, 0xA2, 0x9E, 0xD5, 0x43, 0xDA, 0x0B, 0xC7, 0xB8, 0x4A, 0xF9, 0x71, 0x59, 0xB5, 0x06, 0x19, 0xE3, 0xEE, 0xFD, 0xDE, 0x18, 0x5B, 0x19, 0x00, 0x27, 0xB6, 0x31, 0x10, 0x6F, 0x05, 0xA0, 0x9C, 0xE0, 0xB4, 0x9F, 0x06, 0x51, 0x4D, 0xD1, 0xBC, 0x3F, 0x92, 0x09, 0x20, 0x2F, 0x34, 0xEA, 0x2C, 0x6C, 0x4F, 0x51, 0x47, 0x09, 0x32, 0xC1, 0x0A, 0xBB, 0x6D, 0x45, 0x41, 0x7F, 0xCE, 0xC1, 0x08, 0xB3, 0x62, 0x7D, 0xF7, 0x2B, 0xFD, 0xA1, 0xD8, 0xD4, 0x2D, 0x79, 0x64, 0x4E, 0xD0, 0xE1, 0xF6, 0x54, 0xAE, 0x2E, 0xB3, 0x8D, 0x03, 0x40, 0x5C, 0xFA, 0x7B, 0xB3, 0xD1, 0x65, 0x0A, 0xF3, 0x13, 0x5B, 0xFE, 0x21, 0x3A, 0xDB, 0x2A, 0xEA, 0x65, 0xF0, 0xB0, 0xC5, 0x46, 0xD3, 0x41, 0xC1, 0x57, 0x80, 0x0B, 0x1B, 0xF7, 0xE9, 0x95, 0xF8, 0xC5, 0xCF, 0x03, 0x18, 0xC1, 0x7A, 0xA1, 0x8F, 0x96, 0xB4, 0xA2, 0xAF, 0x56, 0x30, 0x34, 0x6E, 0x6D, 0x81, 0xBA, 0x25, 0x30, 0xF7, 0xC8, 0x5D, 0xB9, 0xD5, 0x42, 0x99, 0x40, 0x56, 0xA3, 0xCD, 0x11, 0x5B, 0x42, 0x80, 0x81, 0xDE, 0xCB, 0x27, 0x9E, 0xA3, 0xEF, 0xB6, 0x6F, 0xD9, 0x5A, 0x5E, 0xBD, 0x94, 0xAC, 0xA5, 0x01, 0x58, 0x07, 0x10, 0x4F, 0xC7, 0xBC, 0xDC, 0x6D, 0x87, 0x31, 0x6A, 0x18, 0xC9, 0xA5, 0xBE, 0x20, 0x98, 0x97, 0xA5, 0xFA, 0xA5, 0xD9, 0x4E, 0xBB, 0x5F, 0x66, 0xB3, 0x53, 0x5F, 0x35, 0xB9, 0x9B, 0xCF, 0xAD, 0xB1, 0x8C, 0xDA, 0x60, 0x8B, 0x88, 0x69, 0x09, 0x4E, 0x75, 0x6A, 0x2F, 0x92, 0x89, 0xBE, 0x7A, 0x6A, 0xFF, 0x1C, 0x70, 0xBB, 0x97, 0xD4, 0x5E, 0x12, 0x7C, 0x8B, 0x4C, 0x65, 0xBB, 0x54, 0x55, 0xE8, 0x39, 0x94, 0xEE, 0xDE, 0xEF, 0xD6, 0x0E, 0x8F, 0xEB, 0x73, 0x53, 0x83, 0xAD, 0xDF, 0xB3, 0xB9, 0x1D, 0x49, 0xC1, 0x69, 0x89, 0xCF, 0x61, 0x05, 0x15, 0x3C, 0x85, 0x48, 0x62, 0x0B, 0x9B, 0x12, 0x0D, 0x81, 0x41, 0x8B, 0xE1, 0x9F, 0x2D, 0x5A, 0xF8, 0x1D, 0x58, 0xA6, 0x20, 0x38, 0xD5, 0x36, 0x1B, 0xB6, 0xF4, 0x7E, 0x75, 0xEC, 0xBE, 0xAD, 0x88, 0x5D, 0xB5, 0x14, 0x65, 0x00, 0xA1, 0x0C, 0x7C, 0x95, 0x49, 0xA1, 0xB1, 0x94, 0x20, 0x2E, 0x56, 0x5E, 0x03, 0xE9, 0x45, 0x49, 0x1E, 0xA5, 0x08, 0xEE, 0xFC, 0x6A, 0x4B, 0x98, 0xC5, 0x11, 0xC0, 0x65, 0xEF, 0x4D, 0x5D, 0xB3, 0xD4, 0xAB, 0x57, 0x7B, 0x3B, 0x74, 0xD7, 0x16, 0x6E, 0x0F, 0x15, 0xDF, 0xB9, 0x70, 0xCE, 0x17, 0xD6, 0x18, 0xE4, 0xC4, 0xC9, 0xD3, 0x74, 0x66, 0x8D, 0xCB, 0x5A, 0x63, 0x38, 0xAC, 0x40, 0x85, 0x10, 0xD2, 0xA1, 0x6D, 0x42, 0xAD, 0xA2, 0xBE, 0x97, 0x80, 0xE7, 0x7E, 0x3F, 0x88, 0x8F, 0xF4, 0xB2, 0x9B, 0xA7, 0xA6, 0x2D, 0x46, 0xE2, 0x1A, 0x3E, 0x40, 0xBB, 0xDF, 0x18, 0xC0, 0xED, 0xA7, 0x6E, 0xF0, 0x6D, 0x85, 0xE0, 0x6E, 0x53, 0xCB, 0xDF, 0x6D, 0xB8, 0xB9, 0xA9, 0xDB, 0x3B, 0xBC, 0xD4, 0x54, 0x1E, 0xE3, 0x8E, 0xA3, 0xB9, 0x54, 0x6F, 0xC3, 0x00, 0xEF, 0x39, 0x4C, 0x48, 0xD5, 0x35, 0x27, 0xA0, 0xDC, 0xB3, 0x86, 0xDE, 0x04, 0x1E, 0x6A, 0xC7, 0xE2, 0x18, 0x7D, 0xA7, 0x89, 0xFE, 0x1A, 0x15, 0xDF, 0x47, 0xC8, 0x30, 0x77, 0x5C, 0xBB, 0x3A, 0x82, 0x23, 0x5E, 0xAD, 0x63, 0xBA, 0x5D, 0x56, 0x46, 0x86, 0x29, 0xC9, 0x40, 0xFE, 0x2B, 0xB2, 0x28, 0x50, 0xF3, 0xE5, 0x59, 0x8A, 0xD6, 0xDB, 0x91, 0xB6, 0x48, 0x4C, 0xD1, 0x54, 0x69, 0x00, 0x9A, 0xE5, 0x26, 0x92, 0xF5, 0x73, 0xDF, 0x28, 0x3A, 0xB0, 0x2C, 0xDD, 0x6A, 0xC4, 0xA2, 0xC1, 0x7B, 0x95, 0x5B, 0x99, 0x16, 0x6D, 0x29, 0xB9, 0x53, 0x76, 0x99, 0xCC, 0xB9, 0xDB, 0x10, 0xE8, 0x4D, 0x23, 0x3D, 0x4F, 0xEC, 0xF7, 0x18, 0xEC, 0x09, 0xD7, 0xDA, 0x85, 0x2C, 0x85, 0xE9, 0xAE, 0x25, 0xC3, 0x18, 0xFC, 0x46, 0x32, 0xDE, 0xCC, 0x15, 0x68, 0x6A, 0x6F, 0xD0, 0x31, 0xCD, 0x5D, 0xD8, 0xF8, 0x3B, 0x4E, 0x41, 0x9F, 0xC0, 0x5F, 0xBA, 0x11, 0xF0, 0xC1, 0xAF, 0x68, 0x06, 0x98, 0x15, 0xB7, 0x75, 0x45, 0xCD, 0xE3, 0x8E, 0xBA, 0x1A, 0x01, 0x43, 0xCE, 0x73, 0x90, 0x3E, 0x90, 0x0B, 0x48, 0x6E, 0xD6, 0x08, 0x82, 0x0B, 0x51, 0x2C, 0x3A, 0xE2, 0x0C, 0xD6, 0xA2, 0x4D, 0x43, 0xE1, 0xD9, 0x9F, 0x41, 0x5B, 0x93, 0x6C, 0xE1, 0xAE, 0x55, 0x0B, 0xBC, 0x25, 0x86, 0x1F, 0xCA, 0x32, 0xDE, 0x1B, 0x16, 0x07, 0x91, 0x50, 0x12, 0x16, 0xB5, 0x3C, 0x55, 0x1A, 0x73, 0x64, 0x11, 0x5C, 0xE4, 0x5D, 0x27, 0x69, 0xAF, 0x74, 0x4A, 0xE2, 0xDF, 0x99, 0x0D, 0x10, 0x3B, 0x5C, 0x89, 0x31, 0x9B, 0x7F, 0xA2, 0xB0, 0x38, 0x87, 0x69, 0xD0, 0x89, 0x30, 0x92, 0x3A, 0x84, 0xC0, 0x56, 0x4C, 0xCD, 0x4C, 0x49, 0x06, 0x5E, 0x72, 0x1E, 0xC6, 0x01, 0xCF, 0xCD, 0x43, 0xB9, 0x4B, 0x38, 0x42, 0xB6, 0x9D, 0x20, 0x9C, 0x15, 0x88, 0x7C, 0xEA, 0x8A, 0xE3, 0x2A, 0xDC, 0x09, 0x20, 0x16, 0x63, 0x58, 0x02, 0x7D, 0x02, 0x72, 0x97, 0x2D, 0x46, 0x59, 0xEB, 0xE9, 0x33, 0x59, 0xA9, 0xCF, 0x7C, 0x73, 0xCD, 0xC6, 0xB3, 0x29, 0xD6, 0xB4, 0xD1, 0x19, 0xF7, 0xF6, 0x4D, 0x8E, 0xDB, 0xD9, 0xFA, 0xDC, 0x7C, 0x7F, 0x26, 0x9E, 0xFA, 0x53, 0xD0, 0xA3, 0x3B, 0xB6, 0x6F, 0xEC, 0x03, 0x40, 0x12, 0xE3, 0xCE, 0x94, 0x6F, 0x38, 0xEC, 0x42, 0x7B, 0xC5, 0x71, 0x09, 0x33, 0xC5, 0xF1, 0x66, 0xA4, 0x8D, 0xAB, 0xF5, 0xE5, 0x32, 0xDE, 0x64, 0x3B, 0xFF, 0xA2, 0x8C, 0x58, 0xD0, 0x29, 0xF3, 0x33, 0xFF, 0x7D, 0xEF, 0x37, 0x9B, 0xF8, 0xF8, 0x0D, 0xCC, 0xE5, 0x0D, 0x3B, 0x15, 0xAD, 0x25, 0xE7, 0x14, 0x2A, 0xD8, 0xD8, 0x67, 0xD3, 0x91, 0xBE, 0xEB, 0x5F, 0x74, 0x4A, 0x83, 0x60, 0x30, 0x61, 0x61, 0x20, 0x6A, 0x1E, 0xF7, 0xDA, 0x31, 0x4C, 0xA9, 0xED, 0x2B, 0x1A, 0xFD, 0x7E, 0x03, 0xDB, 0xFE, 0x39, 0xED, 0x51, 0x58, 0x4C, 0x3D, 0xCF, 0xFB, 0xFA, 0xAC, 0x76, 0xF2, 0xCA, 0x73, 0x4F, 0x2A, 0x0B, 0x7C, 0xB0, 0xF0, 0xD1, 0xB2, 0x0D, 0xA6, 0xCE, 0x33, 0xEC, 0x71, 0x1E, 0xF3, 0x31, 0xAF, 0x02, 0x32, 0xA8, 0x5B, 0x38, 0x47, 0x29, 0xE9, 0x0E, 0xB2, 0x2B, 0x8A, 0xC4, 0xA4, 0x0B, 0x6F, 0xFB, 0xFC, 0x6C, 0x6F, 0x5C, 0xE6, 0xB8, 0x8B, 0x5C, 0xF3, 0x23, 0x76, 0xB4, 0x99, 0xCF, 0xE7, 0xA4, 0x0F, 0xBA, 0x88, 0x73, 0x18, 0xA5, 0xE7, 0xDF, 0x7D, 0x7C, 0x9B, 0x5E, 0x1E, 0xCD, 0xFA, 0x61, 0x8D, 0x4C, 0x1D, 0xF6, 0xF1, 0xFD, 0x59, 0xF5, 0xB8, 0xE4, 0x1B, 0x36, 0x2D, 0x7A, 0xD5, 0xC3, 0x7E, 0xD0, 0x53, 0x76, 0x14, 0x0F, 0x0B, 0xE4, 0x43, 0x53, 0x4D, 0x3E, 0x2C, 0xD4, 0x62, 0xF2, 0x8B, 0x38, 0x40, 0xF6, 0x81, 0xB8, 0x54, 0x93, 0x4A, 0x6B, 0x1C, 0xA4, 0x82, 0xEF, 0xA3, 0xD1, 0x04, 0x4E, 0xDF, 0xB8, 0x1B, 0x01, 0xB1, 0xFF, 0xFA, 0x55, 0xFB, 0x01, 0xD5, 0xC4, 0x34, 0x4A, 0xE3, 0xE7, 0xE2, 0x25, 0xBD, 0x78, 0xA5, 0x84, 0x1A, 0x0E, 0xF5, 0xE3, 0x2B, 0xAA, 0x70, 0x17, 0x14, 0x91, 0x1C, 0xE4, 0x92, 0xD5, 0xAF, 0x38, 0x13, 0xB8, 0x85, 0xF5, 0x31, 0x59, 0xE9, 0x3B, 0x2C, 0xD8, 0xE9, 0x13, 0x53, 0x02, 0xB5, 0x87, 0x4D, 0xE6, 0x8A, 0x35, 0x8D, 0xE3, 0xD0, 0x70, 0x0F, 0xA1, 0xD9, 0x03, 0x60, 0x3E, 0x1A, 0x81, 0x4A, 0xFB, 0xDA, 0x32, 0x77, 0x3A, 0xFF, 0x30, 0x11, 0xF4, 0x86, 0xBC, 0xBB, 0x85, 0x65, 0xA5, 0xB7, 0x15, 0x35, 0x79, 0x8E, 0xF3, 0x41, 0x2F, 0xB9, 0xB6, 0xFF, 0x9C, 0x99, 0x86, 0x02, 0x00, 0xA4, 0x64, 0x97, 0x52, 0x83, 0xC1, 0x4B, 0xDE, 0xD8, 0x6C, 0xAC, 0x21, 0xF9, 0x47, 0xA5, 0xAB, 0xD5, 0xD3, 0x70, 0xBC, 0x49, 0xC6, 0x4B, 0x5A, 0x01, 0x01, 0x71, 0xFD, 0x89, 0x8B, 0xDF, 0x6B, 0xB4, 0xEA, 0x27, 0xDC, 0x2E, 0xEA, 0x47, 0x77, 0xCB, 0x73, 0x55, 0x93, 0x36, 0xA1, 0xE5, 0xCE, 0xED, 0x92, 0x92, 0x29, 0x6A, 0x7B, 0x12, 0x62, 0x0B, 0xC1, 0x48, 0x17, 0x14, 0x27, 0xCB, 0x07, 0x9C, 0x93, 0x2D, 0xC8, 0xE5, 0x03, 0x5F, 0x55, 0x82, 0x04, 0x36, 0x06, 0x30, 0x66, 0xE8, 0xC9, 0x9A, 0xD5, 0xBE, 0xD5, 0x1A, 0xF1, 0x86, 0x9E, 0xFD, 0x36, 0xE7, 0xC3, 0x87, 0x92, 0x79, 0xD5, 0xF8, 0x3A, 0xAC, 0x5B, 0x98, 0x63, 0xDE, 0x87, 0x6C, 0xC2, 0x84, 0x0E, 0xC0, 0x4D, 0x15, 0x8C, 0xC8, 0x09, 0xE9, 0xED, 0x24, 0x77, 0x6D, 0x10, 0xEF, 0x2A, 0x63, 0x58, 0xD4, 0x91, 0x01, 0xA1, 0x0D, 0x91, 0xC3, 0x60, 0x44, 0x08, 0x69, 0x07, 0x78, 0xAA, 0x0F, 0x50, 0x06, 0x0A, 0xAF, 0xC1, 0xB8, 0xD4, 0xDE, 0x6C, 0x0D, 0x94, 0x47, 0x04, 0xB2, 0xC3, 0x6A, 0x03, 0x2C, 0x75, 0x19, 0xDA, 0x31, 0x8B, 0x57, 0xD7, 0xA7, 0x53, 0x7A, 0x2B, 0xCF, 0xD8, 0xC7, 0x14, 0xDF, 0xB6, 0xE2, 0x82, 0x3E, 0xF4, 0x4A, 0x01, 0xCB, 0xD0, 0xF4, 0xE0, 0x91, 0x83, 0x57, 0x54, 0xB3, 0x77, 0x5D, 0x74, 0xCC, 0x01, 0xCB, 0x2B, 0x43, 0x72, 0x3D, 0xAE, 0x63, 0x18, 0x26, 0x02, 0x0F, 0xE5, 0x7E, 0xB1, 0xAA, 0x94, 0x8D, 0xE7, 0x3A, 0xC2, 0x79, 0x19, 0xB2, 0xF1, 0xBC, 0x17, 0x58, 0xEB, 0x82, 0xC3, 0xD1, 0x00, 0xF0, 0x01, 0xDC, 0xF6, 0x82, 0x04, 0xD1, 0xEC, 0xCE, 0xD3, 0x44, 0x6E, 0x49, 0x70, 0xF0, 0x74, 0x5C, 0xA7, 0x95, 0xDF, 0xBB, 0x97, 0x7A, 0x76, 0x56, 0xBC, 0x06, 0x39, 0xEB, 0x4E, 0xAC, 0xBB, 0x56, 0x57, 0xF6, 0x3F, 0x47, 0xE6, 0xDC, 0x53, 0x59, 0x18, 0xBC, 0xA7, 0x0B, 0x27, 0x39, 0x59, 0xFC, 0xDB, 0x97, 0xA5, 0x47, 0xCB, 0x7C, 0x7B, 0x51, 0xAF, 0xAE, 0x24, 0xA1, 0xF0, 0x64, 0xDB, 0x0B, 0xDE, 0xBF, 0xC6, 0x99, 0x6D, 0x82, 0xC1, 0xD2, 0xAC, 0x83, 0x10, 0xF8, 0x98, 0xE7, 0x31, 0x62, 0x80, 0x2A, 0x83, 0x9D, 0x97, 0xA2, 0xF6, 0xF2, 0xF4, 0x03, 0xCB, 0x04, 0x02, 0x5C, 0xE5, 0xAC, 0x94, 0x4C, 0x45, 0x6D, 0x16, 0x20, 0x2E, 0x95, 0xA8, 0x29, 0x65, 0x13, 0x37, 0x17, 0xC9, 0xCB, 0x85, 0xE1, 0xA1, 0x00, 0x5D, 0x08, 0x36, 0xE2, 0x1A, 0x3D, 0xD0, 0xD9, 0xB1, 0x3E, 0x90, 0xD8, 0x69, 0xD0, 0x2E, 0xDD, 0x90, 0x14, 0x53, 0xC7, 0x78, 0x3A, 0xFD, 0x13, 0xB9, 0xA2, 0x11, 0xA0, 0x0C, 0xFC, 0x29, 0x30, 0x1E, 0x97, 0x8F, 0xAB, 0x35, 0x6B, 0x69, 0x87, 0x8B, 0x9F, 0xB9, 0x38, 0xD6, 0x31, 0x89, 0xEF, 0xE0, 0x36, 0xAC, 0x17, 0x97, 0x22, 0xD5, 0xDC, 0x3B, 0x0C, 0x00, 0xFB, 0x63, 0xBA, 0x07, 0x59, 0x9A, 0x2E, 0xE0, 0x52, 0x13, 0x4C, 0xCD, 0x10, 0x2C, 0x4E, 0x8D, 0x14, 0x0F, 0x8B, 0x8F, 0x22, 0x4D, 0xAC, 0x24, 0x01, 0xA4, 0xE7, 0x01, 0x35, 0x93, 0xDE, 0x07, 0x2A, 0xDF, 0x13, 0x59, 0x23, 0xEF, 0x51, 0xD9, 0xF3, 0xF7, 0x41, 0x43, 0x7B, 0x10, 0xA5, 0xA0, 0x94, 0xE5, 0x36, 0x48, 0xF1, 0x65, 0xB3, 0xAB, 0xE6, 0x01, 0x5C, 0x3A, 0x90, 0x65, 0xF7, 0xF3, 0xEE, 0x8B, 0x89, 0xB9, 0xBB, 0x40, 0xBE, 0xEA, 0x9E, 0xB8, 0xFD, 0xEF, 0xA1, 0xBF, 0x75, 0x55, 0x54, 0xB7, 0xB6, 0x58, 0x2D, 0xDA, 0x95, 0xA5, 0xD8, 0xF7, 0x96, 0x53, 0x0B, 0xD9, 0xAF, 0xC0, 0x51, 0x9B, 0x51, 0x9D, 0x57, 0x9A, 0xD0, 0x98, 0x4F, 0xDE, 0x07, 0x24, 0x79, 0x07, 0x14, 0xB4, 0xE6, 0xC2, 0x1E, 0x59, 0x85, 0xE2, 0x33, 0xAB, 0xD7, 0x62, 0x46, 0xB4, 0x76, 0x06, 0x6C, 0xC3, 0xB6, 0xC4, 0x7C, 0x26, 0x77, 0x46, 0x25, 0x17, 0xC1, 0x22, 0x55, 0x68, 0x30, 0xB7, 0xC6, 0xBF, 0x24, 0x86, 0xE7, 0x3D, 0xF8, 0x00, 0x60, 0xA6, 0x95, 0xA9, 0x89, 0xA1, 0xB8, 0xF4, 0x94, 0x9C, 0x4B, 0xF7, 0x1D, 0x2C, 0x4C, 0x30, 0xE4, 0xD3, 0x36, 0x38, 0xBB, 0xB2, 0x6E, 0xE9, 0x79, 0x03, 0x62, 0x3D, 0xB3, 0xBF, 0x76, 0xE8, 0x2E, 0x5C, 0xC0, 0x70, 0xE8, 0xEA, 0x00, 0xC6, 0xCD, 0x8C, 0x34, 0xA6, 0x39, 0x16, 0x1F, 0x85, 0xC9, 0x88, 0x81, 0x1F, 0x1F, 0x3D, 0x2C, 0x35, 0xC4, 0x62, 0x64, 0x26, 0xF1, 0xDF, 0x45, 0x49, 0x1A, 0xBE, 0x68, 0x62, 0x83, 0xA1, 0x34, 0x70, 0x68, 0x4E, 0x73, 0x6A, 0x0C, 0x0E, 0xDA, 0x8D, 0x61, 0x13, 0x7C, 0x62, 0x8C, 0x5C, 0xC6, 0xFD, 0x47, 0x63, 0x54, 0x4A, 0xB8, 0x4F, 0x33, 0xDC, 0xDC, 0x99, 0xE5, 0x0A, 0xFF, 0x0E, 0x42, 0x51, 0xC2, 0xC4, 0xDD, 0x28, 0xEE, 0x2D, 0xC7, 0xFE, 0x5C, 0xA2, 0xDB, 0xB2, 0xCE, 0x10, 0x66, 0x90, 0x23, 0x22, 0x89, 0xA4, 0x74, 0x00, 0x1A, 0x1F, 0x27, 0x8F, 0xA8, 0x36, 0xD7, 0x05, 0x76, 0x51, 0xD9, 0xBC, 0xB0, 0x35, 0x3D, 0xD1, 0x0D, 0xC8, 0xB1, 0x17, 0x96, 0x51, 0xDF, 0x7A, 0x68, 0x26, 0x04, 0x03, 0xDD, 0x78, 0xCD, 0x13, 0x7C, 0x0D, 0xD8, 0x64, 0xB5, 0xD9, 0x9F, 0xC0, 0xC0, 0xBE, 0x12, 0xD6, 0x12, 0xE9, 0xB7, 0x65, 0xA3, 0xB2, 0x9B, 0x14, 0xFA, 0x77, 0x4B, 0x9D, 0xDD, 0xE9, 0x43, 0x8E, 0x8C, 0xBE, 0xAD, 0x32, 0x03, 0x24, 0xC4, 0x33, 0x16, 0x12, 0x3C, 0xDD, 0x78, 0x63, 0xDD, 0x6E, 0x2B, 0x32, 0x4D, 0x22, 0x19, 0x04, 0xF2, 0xD6, 0xC1, 0x46, 0x59, 0xF0, 0x46, 0x7A, 0x38, 0x17, 0xD0, 0xE3, 0x40, 0x2E, 0xC7, 0x8A, 0xC6, 0x7D, 0xDA, 0x4A, 0x41, 0x26, 0xC5, 0x1A, 0xF6, 0xC3, 0xC7, 0x9C, 0x23, 0xD7, 0x64, 0x39, 0xC1, 0x50, 0x68, 0xB2, 0xC2, 0xF8, 0x27, 0xEA, 0x22, 0xFC, 0x12, 0xA2, 0x27, 0x5F, 0x9B, 0xDE, 0x5C, 0x99, 0x8D, 0x72, 0x9C, 0x09, 0x19, 0xEF, 0x7F, 0xDB, 0x35, 0xB6, 0x84, 0xCD, 0x2F, 0x80, 0xA2, 0x24, 0xAF, 0x1A, 0x71, 0x0D, 0x09, 0xE0, 0xBC, 0x6C, 0x1D, 0x30, 0x22, 0xF6, 0x50, 0xA4, 0x65, 0x3E, 0xB2, 0x6A, 0xCC, 0x45, 0xB3, 0x06, 0x4A, 0x67, 0xA5, 0x6F, 0x21, 0x1E, 0x9B, 0x6C, 0x4D, 0x08, 0x32, 0x8D, 0x17, 0xED, 0x78, 0x04, 0x98, 0x17, 0x6C, 0xB3, 0x51, 0x18, 0xDF, 0xF0, 0x79, 0xDA, 0x3F, 0x13, 0xDF, 0x32, 0x38, 0x85, 0x98, 0x64, 0xBD, 0x5A, 0x37, 0x39, 0x58, 0x56, 0x83, 0x3B, 0x84, 0xB8, 0x71, 0x42, 0x7F, 0xBC, 0xCB, 0x82, 0xA9, 0xDB, 0x0E, 0x8A, 0xB4, 0x05, 0x24, 0x4B, 0xC4, 0x0F, 0x09, 0xC3, 0x35, 0x0A, 0x96, 0x6B, 0x46, 0xF0, 0x0F, 0x18, 0xC3, 0xFB, 0x5C, 0xC6, 0x22, 0x2A, 0xC1, 0x2C, 0xE6, 0x2E, 0xDB, 0xC5, 0x8A, 0x8F, 0x65, 0xD5, 0x63, 0x97, 0x8F, 0x5B, 0x77, 0xED, 0x96, 0x78, 0x83, 0x0C, 0x0A, 0xCE, 0xD1, 0xF2, 0x4C, 0xA4, 0xA4, 0xD0, 0x09, 0x33, 0x66, 0xE9, 0xD2, 0x98, 0x59, 0x66, 0xBA, 0x76, 0x72, 0x3E, 0x89, 0xCB, 0xA4, 0x4A, 0xE6, 0x84, 0x4D, 0x1A, 0x20, 0x8E, 0xDE, 0x0B, 0x04, 0x85, 0x33, 0x2C, 0xBE, 0xF4, 0xDB, 0x3B, 0x2B, 0x64, 0xD9, 0x76, 0xD4, 0xF4, 0x6F, 0x0F, 0xF3, 0x83, 0x29, 0x3F, 0xA5, 0xCC, 0x8C, 0xE7, 0x93, 0x46, 0x0E, 0xFD, 0x1F, 0xA6, 0xD5, 0x04, 0xD1, 0x85, 0x17, 0xB2, 0xD5, 0xBE, 0x3D, 0xC0, 0x33, 0xC0, 0x52, 0x8A, 0x5C, 0xF6, 0x73, 0xBD, 0x85, 0xE0, 0x02, 0xA7, 0x47, 0x4E, 0x60, 0x2F, 0x76, 0x94, 0x02, 0x01, 0xC7, 0x27, 0x81, 0x0B, 0xEB, 0xEA, 0x49, 0x29, 0xFA, 0x02, 0x73, 0x6E, 0xB1, 0xEC, 0x82, 0x29, 0x29, 0x6F, 0x3E, 0xF2, 0xB2, 0x1F, 0xD5, 0x66, 0x54, 0x00, 0x52, 0xE1, 0x1C, 0xDA, 0xF0, 0xCF, 0x90, 0x83, 0x6B, 0xFF, 0xED, 0x3D, 0x69, 0x91, 0xA4, 0x47, 0x82, 0x88, 0x09, 0x5A, 0x63, 0x75, 0x44, 0x88, 0xF4, 0x51, 0x85, 0xE0, 0x6F, 0x85, 0x2A, 0x48, 0x14, 0x3B, 0x09, 0x8F, 0x80, 0xE6, 0xF8, 0x09, 0x5C, 0xC6, 0x7B, 0x45, 0x85, 0x4F, 0x57, 0x9E, 0x58, 0x9A, 0x35, 0x5F, 0x94, 0x70, 0x3B, 0x11, 0x49, 0x1D, 0xB8, 0x46, 0xB7, 0x79, 0x8E, 0x2C, 0x48, 0xDB, 0x3C, 0x34, 0x4E, 0xD2, 0x17, 0x19, 0xA1, 0xDA, 0x48, 0x40, 0x08, 0x6E, 0xCE, 0x66, 0xC3, 0x70, 0xB0, 0x01, 0x21, 0xD5, 0x97, 0x9B, 0xAB, 0x65, 0x96, 0x05, 0x75, 0x97, 0xFB, 0x2E, 0x4B, 0x0C, 0x31, 0xFD, 0x8E, 0x67, 0x76, 0xF4, 0x5B, 0x06, 0xF2, 0xB3, 0xA6, 0xDF, 0x56, 0xF6, 0x5D, 0xA5, 0xDF, 0xBE, 0x01, 0x6A, 0x71, 0xC6, 0x1D, 0xFE, 0xBB, 0x08, 0xA8, 0x06, 0x59, 0x4F, 0x6D, 0x92, 0x13, 0xD3, 0xCA, 0xCA, 0xFF, 0xBA, 0x3A, 0xF2, 0xEA, 0xFD, 0xEE, 0x37, 0xA8, 0xB5, 0x4C, 0x42, 0xBA, 0x48, 0x4A, 0x98, 0xA6, 0x92, 0x75, 0xB7, 0xE6, 0x77, 0x2A, 0xA9, 0x6A, 0xE3, 0x6B, 0xC4, 0x72, 0x98, 0xE4, 0xA1, 0x6F, 0x44, 0x0E, 0x42, 0x2C, 0xAD, 0x80, 0xE8, 0xA7, 0x5B, 0x82, 0xA4, 0x2D, 0x7A, 0x09, 0xFD, 0xB8, 0xBE, 0x9E, 0x64, 0xE7, 0x18, 0x21, 0xBB, 0x7D, 0x58, 0xE7, 0xC7, 0x02, 0x8A, 0x54, 0xCA, 0xCA, 0xEF, 0xBC, 0xD0, 0x0D, 0xFE, 0xCB, 0x79, 0x06, 0x38, 0xEE, 0xD2, 0xB8, 0xF9, 0x40, 0x08, 0x6C, 0x80, 0xCE, 0xDC, 0xDA, 0xA0, 0x2E, 0x22, 0x4B, 0x10, 0xCC, 0xEE, 0x01, 0xDA, 0x9F, 0xD7, 0x31, 0x84, 0x26, 0xDD, 0xE0, 0x92, 0x60, 0xA5, 0xB9, 0xA7, 0x23, 0xF7, 0xF2, 0x07, 0x06, 0x07, 0x80, 0x9F, 0x20, 0x5A, 0x1C, 0x68, 0x81, 0xE8, 0x27, 0xC4, 0xF0, 0x54, 0x03, 0x9B, 0x1A, 0xAB, 0xD6, 0x43, 0x6B, 0xDB, 0x23, 0x10, 0x79, 0x94, 0x3F, 0xB6, 0xDF, 0xAA, 0x43, 0x53, 0xF0, 0x16, 0x8F, 0x31, 0xDA, 0xB7, 0xB3, 0xC5, 0x1E, 0xE5, 0x7B, 0x1F, 0x12, 0x38, 0x4D, 0x6A, 0x41, 0x06, 0x0E, 0x32, 0x1C, 0x12, 0x61, 0x91, 0x2C, 0x95, 0x87, 0xE2, 0x04, 0x2E, 0x34, 0x7C, 0x81, 0x43, 0x82, 0x2D, 0xC6, 0xA1, 0x15, 0x83, 0xEB, 0xC7, 0x29, 0x14, 0x8F, 0x03, 0xDB, 0xC8, 0x3A, 0x79, 0x38, 0x99, 0x06, 0xF2, 0x4C, 0x8A, 0xF2, 0x1D, 0x51, 0x5A, 0x06, 0xE7, 0xDB, 0x6C, 0x86, 0x54, 0xFE, 0x59, 0x97, 0x6A, 0x59, 0x54, 0xD4, 0x6D, 0xE5, 0xE8, 0xCD, 0x25, 0xE2, 0xA5, 0xF8, 0x1D, 0x58, 0xC9, 0x97, 0xA1, 0x08, 0x2E, 0xE7, 0xEE, 0x80, 0x1B, 0xE2, 0x75, 0x42, 0x6C, 0x6D, 0xEF, 0x4C, 0xA3, 0x79, 0xB1, 0xB8, 0x41, 0x76, 0x0D, 0x62, 0xD7, 0x18, 0x00, 0xE0, 0x9B, 0x42, 0xA7, 0x48, 0x43, 0x39, 0x84, 0x54, 0xB0, 0xCB, 0x43, 0x03, 0xB5, 0xCB, 0x38, 0x87, 0x7C, 0xD9, 0xB2, 0x87, 0x4F, 0x46, 0xB4, 0x3F, 0x5F, 0x32, 0xC1, 0xFA, 0x8D, 0x1C, 0x9C, 0x9D, 0xE1, 0x71, 0xF4, 0xE0, 0x90, 0xE4, 0x44, 0x87, 0xA1, 0x38, 0x8E, 0x80, 0xA0, 0xED, 0xCC, 0x21, 0xC0, 0x0F, 0x27, 0xE4, 0x60, 0xDA, 0xE7, 0xC4, 0x94, 0x85, 0x50, 0x5B, 0x48, 0x9A, 0x03, 0x4B, 0x4E, 0x67, 0x2B, 0x09, 0xAE, 0xA2, 0xF7, 0xB8, 0x91, 0xB0, 0x3A, 0x72, 0x98, 0xEC, 0xDD, 0x9D, 0x87, 0xA2, 0xA4, 0x43, 0x57, 0xE4, 0xEE, 0xFB, 0xA2, 0x1E, 0x03, 0x38, 0x65, 0x0E, 0x66, 0xBC, 0x81, 0xC8, 0x9D, 0x6F, 0x70, 0x30, 0xDF, 0xF5, 0x31, 0x95, 0x26, 0x85, 0xDB, 0x64, 0x08, 0x5A, 0x1F, 0xCF, 0xF5, 0x4E, 0x1F, 0xD7, 0x6B, 0xCA, 0x76, 0xCC, 0xEE, 0xF9, 0x8C, 0x6E, 0xB7, 0x40, 0xCA, 0xF2, 0xD6, 0x63, 0xBF, 0x43, 0x88, 0x92, 0x99, 0x6B, 0xCB, 0x11, 0xCC, 0x8D, 0x63, 0xE3, 0x4D, 0x30, 0x0C, 0x05, 0x2C, 0xAA, 0x0F, 0x92, 0x7E, 0xA5, 0xF1, 0xB4, 0x21, 0x87, 0xE7, 0x9D, 0x41, 0x9F, 0xFD, 0x88, 0x7B, 0x8C, 0xFA, 0xD2, 0xCC, 0xAE, 0xDD, 0x97, 0x38, 0xAD, 0x4C, 0xE7, 0x9C, 0x3D, 0xE4, 0x64, 0x40, 0xF2, 0xD0, 0x0E, 0x48, 0xCB, 0xD7, 0x0E, 0xFD, 0x32, 0x54, 0x42, 0x2D, 0x76, 0x87, 0xA2, 0xEC, 0x62, 0x7C, 0xB7, 0x0E, 0x4F, 0x39, 0x25, 0x9C, 0x0F, 0x47, 0xCF, 0x22, 0x84, 0x35, 0x18, 0x5A, 0x6C, 0x85, 0x48, 0x15, 0xD0, 0x7C, 0x7D, 0x56, 0xB0, 0xC9, 0x42, 0x94, 0x11, 0xDB, 0xB8, 0x59, 0x36, 0xE6, 0x06, 0x38, 0x71, 0xF0, 0xE6, 0x62, 0x60, 0x11, 0xB4, 0xB3, 0xEA, 0xB9, 0x62, 0x12, 0x6F, 0x8D, 0xAC, 0xD8, 0x6B, 0xAD, 0x9E, 0x65, 0xA5, 0x4A, 0x33, 0x25, 0xFA, 0x33, 0xDD, 0x0D, 0x9A, 0x80, 0xCA, 0xBB, 0x78, 0x54, 0xCF, 0xDA, 0x6C, 0xAF, 0xBC, 0x07, 0x43, 0x12, 0xCE, 0x60, 0x56, 0xB0, 0x64, 0x52, 0x37, 0xD3, 0x39, 0xF7, 0xB5, 0x24, 0x66, 0x94, 0x05, 0x4F, 0xEA, 0xA2, 0x95, 0x0C, 0x97, 0x15, 0xD0, 0x00, 0x80, 0x87, 0xBE, 0x27, 0xAA, 0x2C, 0x80, 0x77, 0xDC, 0xFA, 0xBA, 0xDB, 0xA1, 0x1C, 0xFB, 0x2B, 0xB4, 0xFA, 0x61, 0x53, 0xD5, 0xD3, 0xA7, 0x9D, 0x99, 0xAC, 0xC9, 0xBE, 0x27, 0x0A, 0x9C, 0xB0, 0xF2, 0x1E, 0x96, 0xF3, 0xCD, 0xA4, 0xE9, 0x36, 0x30, 0xB9, 0xF3, 0xE3, 0x7E, 0x80, 0x62, 0x20, 0xFF, 0xA5, 0xC9, 0x26, 0x40, 0xB5, 0x9E, 0x04, 0x96, 0x4C, 0xDD, 0x98, 0xA1, 0x22, 0x53, 0x7E, 0x5D, 0x32, 0x4D, 0x34, 0x57, 0xDD, 0xE1, 0x44, 0xF6, 0x8C, 0x07, 0x04, 0x86, 0x75, 0xB2, 0x7E, 0x09, 0x35, 0x06, 0x50, 0x84, 0x72, 0xE9, 0xA2, 0x56, 0x79, 0xBC, 0x11, 0x3E, 0x7D, 0x5D, 0x02, 0x18, 0x27, 0x4F, 0x41, 0x58, 0x02, 0x21, 0x52, 0x22, 0x8D, 0x0E, 0xF1, 0xB6, 0x4F, 0x82, 0xE6, 0x60, 0x0E, 0x04, 0xD1, 0x53, 0xF5, 0x1A, 0xF0, 0xA4, 0x5B, 0xF5, 0x04, 0x99, 0xE4, 0x19, 0xA2, 0xE7, 0x8C, 0x98, 0x75, 0x0A, 0x8E, 0x02, 0x70, 0x52, 0x33, 0x18, 0x9B, 0x55, 0x20, 0x0B, 0xD0, 0x49, 0x44, 0x23, 0x89, 0x42, 0x45, 0x26, 0xA7, 0x2C, 0x5B, 0x6B, 0xEF, 0xD9, 0x6F, 0xE2, 0x5A, 0x59, 0x78, 0xB3, 0xAF, 0x61, 0x79, 0x94, 0x57, 0xD7, 0x05, 0x27, 0xB5, 0xD4, 0x95, 0xAE, 0x8F, 0xA0, 0x4C, 0xFA, 0xC1, 0x52, 0x6B, 0xDC, 0xA0, 0x1D, 0xBD, 0x04, 0x06, 0x9D, 0xAF, 0xDC, 0xB1, 0x29, 0x2A, 0x18, 0xE1, 0x15, 0x11, 0xC0, 0xEF, 0xA6, 0x94, 0xB6, 0x35, 0xDC, 0x8E, 0xA6, 0x9B, 0x45, 0x20, 0xCF, 0x1C, 0x7A, 0xDE, 0x78, 0xF4, 0x9E, 0xB5, 0x59, 0x3D, 0xFE, 0x91, 0xBA, 0xA9, 0x5A, 0x1C, 0x53, 0xBD, 0x05, 0xCA, 0x9A, 0x1B, 0x53, 0x7B, 0x41, 0x99, 0x63, 0xD6, 0xD2, 0x0F, 0x81, 0x03, 0xA6, 0xA6, 0x1E, 0x49, 0xF7, 0x88, 0x89, 0x4E, 0x83, 0xC8, 0x60, 0xE2, 0x61, 0x56, 0xC2, 0x7A, 0xC3, 0xA6, 0xA9, 0x36, 0x95, 0x4F, 0x4F, 0x5B, 0x08, 0x82, 0x0B, 0xDF, 0xA6, 0x14, 0x7B, 0x4A, 0xDA, 0x42, 0xB4, 0x9B, 0x42, 0x77, 0xEF, 0xB6, 0x10, 0xB4, 0x00, 0x56, 0x3E, 0x63, 0xD6, 0xBA, 0x06, 0xE4, 0x95, 0x60, 0xAB, 0xFF, 0x49, 0x8F, 0xFC, 0x12, 0xB2, 0x90, 0x20, 0xA8, 0x26, 0x39, 0xBE, 0xA1, 0x1C, 0xB7, 0xCB, 0xD4, 0xEE, 0xA6, 0x6C, 0xDB, 0x1A, 0x62, 0x0B, 0x65, 0x3D, 0xC0, 0xA4, 0xBD, 0x50, 0xA8, 0x52, 0xEB, 0xB5, 0x4F, 0x16, 0xD6, 0x9F, 0x72, 0xBF, 0xA8, 0x5C, 0xC9, 0xCF, 0xAA, 0x54, 0x8D, 0xC4, 0x84, 0x8C, 0x16, 0x2F, 0x8E, 0x6A, 0xC6, 0x61, 0xE1, 0x2D, 0x08, 0x4C, 0xB4, 0x71, 0x6F, 0x97, 0xD4, 0xE9, 0xFD, 0xA2, 0x89, 0xBE, 0xEC, 0xCE, 0x48, 0xD1, 0x16, 0xFD, 0xFA, 0x82, 0xA5, 0x8D, 0xBA, 0xC0, 0xB4, 0x97, 0xB6, 0x80, 0xA6, 0x81, 0x01, 0x70, 0x2D, 0xA1, 0xE1, 0x90, 0xA0, 0xDE, 0x3B, 0xA4, 0xA7, 0xCD, 0xAC, 0xF9, 0xAA, 0x47, 0xE2, 0x84, 0x60, 0x37, 0x51, 0x9C, 0x19, 0xBE, 0x6F, 0x0C, 0x88, 0x42, 0xC5, 0x1F, 0xA3, 0x68, 0x57, 0xB9, 0x7B, 0x5C, 0x10, 0x94, 0xA2, 0x89, 0xF0, 0x31, 0xB9, 0x72, 0x52, 0x61, 0x62, 0x8E, 0x7D, 0xC5, 0xEF, 0xB4, 0xA0, 0x72, 0xA5, 0x18, 0xA1, 0x1F, 0xA5, 0x0A, 0x8C, 0x0B, 0x7A, 0x5B, 0xDA, 0xB2, 0x72, 0xE9, 0xB9, 0x5E, 0x5C, 0xA3, 0xF8, 0x4C, 0xD1, 0x5D, 0xC9, 0x13, 0xC5, 0x8C, 0xB7, 0x2F, 0x43, 0x0F, 0x5B, 0x7B, 0xE5, 0x56, 0x9A, 0xAF, 0x79, 0xFA, 0x00, 0x12, 0x3D, 0x21, 0x6F, 0xC7, 0x54, 0xD5, 0x4C, 0x30, 0x94, 0xF7, 0x4C, 0x5C, 0xB8, 0xB8, 0x11, 0xF7, 0xBD, 0x0E, 0xC3, 0x2D, 0x8A, 0x2A, 0xD6, 0xC5, 0x7C, 0xDE, 0x9E, 0x26, 0x86, 0x4D, 0x50, 0x78, 0x66, 0xA8, 0xAF, 0x4C, 0xBB, 0x8A, 0x45, 0x5E, 0x90, 0xFA, 0x42, 0xB6, 0xAD, 0x54, 0x36, 0x80, 0xD8, 0xBE, 0x06, 0xE3, 0x7E, 0x3E, 0xC5, 0x10, 0xFE, 0xEC, 0x26, 0xFC, 0x0E, 0x75, 0x39, 0x6B, 0x16, 0xE7, 0x52, 0xAD, 0xEB, 0x62, 0x35, 0x25, 0xEB, 0x26, 0x3C, 0x1F, 0xFE, 0xD2, 0xFE, 0x7C, 0x0E, 0x39, 0x01, 0x6B, 0x25, 0x5D, 0xBA, 0xF5, 0xB2, 0xDB, 0x73, 0x6A, 0x67, 0x8E, 0xEC, 0x9E, 0x09, 0xA5, 0xD1, 0xD2, 0xB6, 0xA7, 0xC2, 0x34, 0x62, 0x2B, 0x1E, 0x17, 0x07, 0x65, 0x80, 0xCB, 0x60, 0xA9, 0x8D, 0x8C, 0xBD, 0x0B, 0x58, 0x1B, 0x66, 0xF1, 0x16, 0xD5, 0x9A, 0xF0, 0xAA, 0x54, 0xCE, 0xF6, 0x84, 0x8F, 0x45, 0xC6, 0xA3, 0xF6, 0x1C, 0x16, 0xFD, 0xF4, 0xBE, 0x0E, 0x15, 0xF1, 0x7C, 0xFC, 0x90, 0xDE, 0x47, 0xA7, 0xD0, 0xB3, 0x00, 0x59, 0x88, 0xCB, 0xBD, 0x65, 0x1B, 0xF2, 0x12, 0xB4, 0x92, 0xDF, 0xC0, 0xA1, 0xCE, 0xE3, 0xB2, 0x92, 0x41, 0xE6, 0xFB, 0x18, 0x2F, 0x60, 0x9B, 0xCD, 0x48, 0xBA, 0x65, 0x07, 0xFE, 0x3F, 0xB6, 0x14, 0x37, 0x3D, 0x22, 0x9C, 0x9A, 0x7F, 0x48, 0xED, 0xAA, 0x25, 0x41, 0xF3, 0x0C, 0xCD, 0xC5, 0x87, 0x99, 0x89, 0xF3, 0xEB, 0x87, 0xC9, 0x38, 0x5D, 0x41, 0xF3, 0x3D, 0x32, 0x78, 0xAE, 0xB9, 0x4B, 0xC1, 0xC8, 0x4B, 0x30, 0x5D, 0x24, 0x00, 0x15, 0x37, 0x45, 0x59, 0x16, 0xED, 0xAC, 0x0B, 0x8F, 0x7D, 0x02, 0xFB, 0xD9, 0x97, 0xF3, 0xAD, 0x6A, 0x3B, 0x25, 0xB7, 0x9E, 0xF9, 0x30, 0x34, 0x4A, 0xCF, 0xBE, 0x3E, 0x13, 0x4F, 0xA7, 0x5C, 0xF3, 0x84, 0x68, 0x5B, 0xC2, 0xD5, 0xF7, 0x67, 0x80, 0x43, 0x44, 0xA6, 0xC1, 0x96, 0xCB, 0x52, 0xB4, 0xD6, 0x9B, 0xCD, 0x37, 0x5B, 0xB6, 0x46, 0x61, 0x07, 0x17, 0x4E, 0x35, 0xAC, 0x52, 0x18, 0x67, 0x63, 0xCC, 0x4C, 0x0C, 0x3A, 0x5B, 0xFC, 0x8B, 0x20, 0x31, 0x58, 0xA7, 0xF2, 0xC5, 0x24, 0x88, 0x00, 0x61, 0xC3, 0x27, 0x0D, 0x7A, 0x27, 0x7B, 0x4D, 0x69, 0x97, 0xE3, 0xA6, 0xB8, 0x17, 0x6E, 0xEA, 0x4E, 0xBA, 0x3B, 0x71, 0xDB, 0x08, 0x50, 0xD8, 0x11, 0xD7, 0x1A, 0x53, 0xAA, 0xBA, 0xFC, 0x03, 0x57, 0x9C, 0xC5, 0x73, 0x26, 0xCB, 0xD7, 0xD6, 0xD9, 0x01, 0xD5, 0x9D, 0xEF, 0x97, 0x1B, 0xE3, 0xB0, 0x7D, 0x7A, 0x3A, 0xD8, 0xAB, 0xD4, 0x17, 0x9F, 0x8D, 0x18, 0x97, 0x65, 0xFD, 0x1A, 0x62, 0x56, 0x96, 0x9B, 0x4D, 0xFB, 0xDB, 0xAD, 0x39, 0x8C, 0x11, 0x76, 0xEB, 0xB0, 0x67, 0x9D, 0xD1, 0x90, 0x43, 0x02, 0x1E, 0xD8, 0xC8, 0x86, 0x40, 0x9C, 0x83, 0x0B, 0x6F, 0x2E, 0x36, 0xE5, 0xD6, 0x26, 0x58, 0xC3, 0x77, 0x8C, 0x06, 0x20, 0xB3, 0xFA, 0xBF, 0xA7, 0xB4, 0x9E, 0xEE, 0x86, 0x25, 0x9A, 0x95, 0xB0, 0x42, 0xC4, 0xEF, 0xC1, 0xCC, 0xF1, 0x5F, 0xDC, 0xF4, 0x3D, 0x6C, 0x8B, 0x9C, 0xBA, 0x22, 0x65, 0xB2, 0xC9, 0x46, 0x0C, 0x17, 0xB9, 0xF3, 0xC1, 0xBE, 0xA5, 0xC4, 0x4C, 0xC1, 0x31, 0x52, 0xD4, 0xB5, 0x37, 0xFF, 0xD1, 0xC2, 0xAA, 0xD4, 0xB5, 0xE0, 0x22, 0xAC, 0xBC, 0xE4, 0x77, 0x99, 0xB7, 0x20, 0x97, 0x8E, 0x57, 0xA0, 0x60, 0x03, 0xDA, 0x39, 0xAD, 0x36, 0xC1, 0xF0, 0x81, 0x5C, 0x53, 0x26, 0xC4, 0xA5, 0xC0, 0xE4, 0x7B, 0x17, 0xAC, 0x78, 0xF2, 0x48, 0x95, 0x9A, 0xD0, 0x7A, 0x3B, 0xB5, 0x34, 0x49, 0x53, 0xD6, 0x59, 0x58, 0xAE, 0x6A, 0x58, 0xB4, 0x99, 0x87, 0xC3, 0xE0, 0x00, 0x33, 0x1C, 0x00, 0x53, 0xAF, 0x18, 0xC3, 0x43, 0x5E, 0xF8, 0x2A, 0xF8, 0x01, 0xD7, 0x38, 0xB5, 0x69, 0x48, 0xB9, 0xE8, 0x76, 0x52, 0xD2, 0x8D, 0x70, 0x35, 0xA0, 0x0C, 0x8E, 0x3C, 0xE0, 0xA8, 0x73, 0x5E, 0x2A, 0x20, 0xA2, 0xF3, 0x17, 0x9B, 0xB0, 0xF7, 0x3F, 0x5A, 0x9F, 0x0A, 0x1F, 0x5E, 0x5E, 0x95, 0x3F, 0x88, 0xEA, 0x56, 0xD6, 0xD9, 0xD9, 0x5B, 0x9E, 0x34, 0x82, 0x4B, 0xC6, 0x72, 0xCB, 0x63, 0x4B, 0x22, 0xC0, 0x02, 0x10, 0x87, 0x91, 0xCB, 0x4D, 0x85, 0x20, 0x4F, 0x81, 0xFE, 0x35, 0xD1, 0x74, 0xEE, 0x81, 0x6D, 0xB9, 0x9A, 0xB6, 0x04, 0x4C, 0x39, 0xAE, 0x15, 0x5C, 0xF7, 0x05, 0x96, 0x28, 0x6B, 0x4B, 0x6B, 0x0C, 0xA8, 0x9B, 0x6B, 0xD2, 0xF0, 0x11, 0xA4, 0x1A, 0x87, 0xC6, 0x10, 0x15, 0x12, 0x4D, 0x10, 0x5E, 0xB3, 0xB1, 0x37, 0x73, 0x0A, 0xFB, 0x9F, 0x6F, 0xAB, 0xE8, 0x93, 0x30, 0xA0, 0x9F, 0xF9, 0x05, 0xFE, 0x28, 0x99, 0x8C, 0x1E, 0x0A, 0xD8, 0x01, 0x0B, 0xF7, 0xD7, 0xCE, 0x7C, 0xA3, 0xC4, 0x55, 0x07, 0x1A, 0x94, 0xB4, 0xA2, 0x12, 0x72, 0x9E, 0x9C, 0x80, 0x37, 0x68, 0xA8, 0xB7, 0x87, 0x65, 0x4C, 0x6D, 0x58, 0xAA, 0x57, 0x34, 0x67, 0x5E, 0x9D, 0xAD, 0x25, 0x34, 0x86, 0xA5, 0xE1, 0x4E, 0x0E, 0xA1, 0x81, 0x82, 0x66, 0xFE, 0x63, 0x76, 0xCE, 0x09, 0xDF, 0x65, 0xD3, 0xDB, 0x03, 0xB5, 0x88, 0xA5, 0xD5, 0x44, 0x4C, 0xD1, 0xC5, 0x80, 0x22, 0x76, 0xD1, 0x9F, 0x6E, 0x9E, 0xF3, 0x84, 0xB1, 0x21, 0x6F, 0x8A, 0x35, 0x61, 0x3A, 0x27, 0x91, 0x82, 0xA6, 0xC3, 0x82, 0xE0, 0xD7, 0xC9, 0xAA, 0x5A, 0x47, 0x9C, 0x7C, 0xD3, 0x68, 0x8E, 0x4A, 0x04, 0xF3, 0x0F, 0x65, 0xC0, 0xFB, 0x89, 0x35, 0xFE, 0x51, 0x61, 0x69, 0xDF, 0xC0, 0x44, 0x32, 0x26, 0x80, 0xF2, 0xD7, 0xF8, 0x36, 0xC1, 0x78, 0x53, 0x54, 0xD4, 0xD0, 0x4E, 0xB3, 0x1B, 0xB3, 0x09, 0xBF, 0x2C, 0xF2, 0xC6, 0x43, 0x3C, 0x0F, 0x68, 0x70, 0x15, 0x1F, 0xC6, 0x17, 0xB7, 0xA3, 0xA3, 0x9D, 0x8E, 0xC7, 0x70, 0xB9, 0x91, 0xCC, 0x59, 0xA8, 0x57, 0x4E, 0xF6, 0x2E, 0xD7, 0x69, 0x01, 0xBA, 0xAA, 0xEC, 0x42, 0x80, 0x39, 0xE5, 0x5F, 0x00, 0x75, 0x0A, 0xF4, 0xA5, 0xB6, 0x7A, 0x37, 0x06, 0x6E, 0x3E, 0xFE, 0x6C, 0x7B, 0x26, 0x4C, 0x0D, 0xFF, 0xA6, 0x31, 0x4F, 0x63, 0x7E, 0x4F, 0x61, 0x5E, 0xB8, 0x26, 0xED, 0x70, 0xE2, 0x4F, 0x4E, 0xEF, 0x7E, 0x55, 0x97, 0x1B, 0x0B, 0x08, 0xB3, 0x80, 0x18, 0xEE, 0xB5, 0x9E, 0x0F, 0x86, 0xB5, 0xDA, 0x38, 0x56, 0x75, 0x56, 0x08, 0xB6, 0x8E, 0x1A, 0xCB, 0x60, 0xE5, 0x16, 0xC3, 0x67, 0xB5, 0x2D, 0xF5, 0x10, 0x22, 0x18, 0x85, 0xBC, 0x6F, 0x65, 0xE0, 0x07, 0x22, 0x82, 0xB1, 0xFC, 0x56, 0xF1, 0xEA, 0x1D, 0xE2, 0xE5, 0x39, 0x9E, 0x8F, 0x23, 0x9F, 0x9A, 0x4B, 0x11, 0xB0, 0xA2, 0x77, 0x9E, 0x79, 0x84, 0xBC, 0xC4, 0x7C, 0xC6, 0x32, 0x5D, 0x19, 0x15, 0xFB, 0x41, 0x94, 0xFD, 0xCC, 0x48, 0xBC, 0x73, 0x29, 0x77, 0x64, 0x66, 0xA3, 0xDB, 0xA3, 0x65, 0x6D, 0xDE, 0x4B, 0x60, 0xA1, 0xA1, 0xDF, 0x3A, 0x74, 0x99, 0x0A, 0x04, 0x17, 0xAF, 0x30, 0x26, 0xB1, 0xC4, 0xC3, 0xC6, 0xA1, 0x99, 0x5D, 0x7C, 0xF4, 0xCD, 0xEF, 0x27, 0x6E, 0xFB, 0x13, 0xBE, 0x5F, 0x59, 0x9C, 0x71, 0x6F, 0x51, 0x49, 0x7A, 0x5F, 0x03, 0xE8, 0x83, 0x97, 0x33, 0xFB, 0x0B, 0x27, 0xE4, 0x0A, 0xF3, 0xA8, 0x7A, 0x10, 0x4A, 0x0C, 0xF3, 0x1F, 0xA6, 0x0E, 0xBF, 0x40, 0x84, 0x72, 0x8D, 0x57, 0xB2, 0xD9, 0xB5, 0x88, 0xDB, 0x80, 0x1A, 0x2A, 0x34, 0xB8, 0x5E, 0xC2, 0x86, 0x7B, 0x94, 0x1F, 0xE0, 0xCC, 0xA2, 0x16, 0x4D, 0x1A, 0xF7, 0xF6, 0x48, 0x1B, 0x40, 0x5A, 0x2B, 0xEF, 0xA5, 0x31, 0x98, 0x61, 0xE5, 0x16, 0x32, 0xEB, 0x58, 0x8F, 0x51, 0xF2, 0x6F, 0xE1, 0x27, 0x28, 0x43, 0x18, 0x5D, 0xD2, 0x30, 0x3C, 0x25, 0xD6, 0x6E, 0x00, 0x3B, 0x7F, 0xED, 0x43, 0x1E, 0xD8, 0x1F, 0x07, 0x91, 0xEE, 0x3B, 0x30, 0x89, 0x14, 0x9B, 0xDD, 0x62, 0xD0, 0xA1, 0xE2, 0xF3, 0x9A, 0xC9, 0x52, 0xCF, 0x14, 0x13, 0xB3, 0x24, 0x75, 0x97, 0xB7, 0xF6, 0x52, 0x36, 0xFC, 0x66, 0x82, 0x10, 0xBF, 0xB2, 0x22, 0x0D, 0x64, 0x78, 0x1F, 0x4F, 0x61, 0x5E, 0x6C, 0xC6, 0x60, 0x5B, 0x6E, 0x38, 0x5D, 0xCF, 0xA9, 0x6F, 0x2B, 0x8D, 0x73, 0x78, 0xB8, 0xE3, 0x0F, 0xDE, 0xC1, 0xA5, 0x71, 0xD9, 0x15, 0x31, 0x3A, 0x4F, 0xA9, 0x5A, 0x4A, 0xB1, 0x97, 0x7D, 0x87, 0x4B, 0x45, 0xA3, 0x45, 0x9B, 0xFA, 0xF6, 0x62, 0x5E, 0x2C, 0xC3, 0xC8, 0x93, 0x42, 0x8A, 0xA8, 0x8F, 0x83, 0x57, 0xC4, 0x63, 0xD8, 0x0F, 0xA9, 0x16, 0x6C, 0x06, 0xF9, 0xEA, 0x75, 0x99, 0x95, 0x12, 0xB4, 0x46, 0x0D, 0x24, 0xF5, 0x8C, 0x67, 0x70, 0x22, 0x42, 0x35, 0x5E, 0x6F, 0xC3, 0x3F, 0xA4, 0xD6, 0xBC, 0xA9, 0xB7, 0xB9, 0x6F, 0x34, 0xD5, 0x5A, 0x88, 0x54, 0xDC, 0x03, 0x17, 0xF7, 0xDE, 0xE6, 0x65, 0x2E, 0xF7, 0x5E, 0xFF, 0xFE, 0xEE, 0x58, 0x62, 0x16, 0xE6, 0x32, 0xC3, 0x62, 0x2E, 0xD9, 0xF4, 0x34, 0x9D, 0x0E, 0x33, 0x07, 0x59, 0xFD, 0x75, 0x0C, 0x98, 0x01, 0xC3, 0x65, 0xED, 0xEA, 0x1E, 0xB2, 0xA3, 0x74, 0x90, 0xFF, 0xE7, 0x16, 0xC8, 0x3F, 0x75, 0xC3, 0xA5, 0x9D, 0xB8, 0x8F, 0x5B, 0x82, 0xDC, 0x64, 0xA8, 0x49, 0x52, 0x4A, 0xD4, 0xCD, 0x8F, 0x4D, 0x33, 0x6E, 0x05, 0x6F, 0x64, 0x5C, 0x0F, 0x30, 0x96, 0x74, 0xA5, 0xB6, 0x6F, 0x52, 0xD3, 0x0C, 0xFA, 0x41, 0x9E, 0x4E, 0x45, 0x7B, 0xBD, 0x3E, 0x0F, 0x69, 0x84, 0xA4, 0xF8, 0x11, 0xD2, 0x14, 0x43, 0xCC, 0xDA, 0xFE, 0xE1, 0xD0, 0x54, 0x5A, 0x45, 0xC4, 0x31, 0xA9, 0xDF, 0x07, 0xB6, 0x69, 0x9C, 0x63, 0xDE, 0x6A, 0xB4, 0xE5, 0xB9, 0x92, 0xDA, 0x1B, 0xD4, 0xD3, 0x2C, 0x06, 0xCD, 0xD1, 0xEA, 0x01, 0x44, 0xB1, 0x15, 0x35, 0x64, 0x9F, 0x21, 0xF7, 0x6B, 0xAA, 0x97, 0x99, 0x29, 0xC7, 0x91, 0x3D, 0x6F, 0x35, 0x77, 0x71, 0x9F, 0x35, 0xC2, 0xBE, 0xFC, 0x48, 0xE0, 0xB2, 0xED, 0x0E, 0x6D, 0xE8, 0x3F, 0xB8, 0x04, 0x73, 0xC5, 0x24, 0x3D, 0x26, 0xF3, 0x1E, 0x5C, 0x1C, 0xD4, 0x39, 0x1A, 0x7F, 0x8F, 0xA8, 0x68, 0x1E, 0xDB, 0xF2, 0x92, 0x0F, 0x8F, 0x34, 0x6B, 0x82, 0x3A, 0xEF, 0x94, 0x80, 0xCE, 0xCB, 0xEF, 0xF3, 0x22, 0x6C, 0x99, 0x7B, 0x6C, 0xCC, 0x27, 0x0F, 0x84, 0xEC, 0x1C, 0x56, 0x14, 0x3C, 0xD9, 0x84, 0x1B, 0x26, 0x5A, 0x88, 0xF1, 0xA6, 0x0C, 0x86, 0xF8, 0x62, 0xED, 0x64, 0xB5, 0x8D, 0xD4, 0x06, 0x98, 0xD6, 0xF2, 0xB5, 0xEC, 0xB7, 0x40, 0x96, 0xEC, 0xAA, 0xAA, 0x5B, 0x0E, 0xC9, 0x7F, 0x84, 0xDB, 0xB2, 0x91, 0x15, 0x86, 0xA3, 0xFA, 0x1D, 0xE8, 0xEF, 0xBA, 0x87, 0xFD, 0x70, 0xE3, 0x89, 0xFE, 0x2A, 0xA5, 0x41, 0x74, 0xB9, 0xF2, 0x70, 0xED, 0x12, 0xB6, 0xA2, 0x42, 0x00, 0x9F, 0x17, 0xFB, 0x43, 0xB3, 0x6A, 0x59, 0xEA, 0x20, 0xD9, 0xD1, 0x9C, 0x5C, 0x84, 0x71, 0x57, 0x01, 0x7C, 0x3C, 0x20, 0x61, 0x08, 0xAC, 0xB7, 0xB0, 0xEE, 0x29, 0x45, 0xBF, 0x87, 0xDB, 0xC8, 0x33, 0xB1, 0x8A, 0xCC, 0x96, 0xC8, 0xB2, 0xF8, 0x89, 0x0B, 0x81, 0xEF, 0xAA, 0x67, 0xFB, 0xC9, 0xD5, 0xFC, 0xFE, 0x3D, 0xEC, 0x78, 0x83, 0x3A, 0xF6, 0x13, 0x43, 0x6E, 0x47, 0xA2, 0xDE, 0x48, 0x5B, 0x79, 0xCF, 0x1F, 0x1C, 0xAD, 0xA4, 0x2C, 0x15, 0x81, 0x12, 0xDC, 0x2F, 0x80, 0x6F, 0x7A, 0x32, 0x03, 0x52, 0x3B, 0xB0, 0x97, 0x41, 0xD5, 0xAB, 0x55, 0x2E, 0xEA, 0xE9, 0xBB, 0xEB, 0x5B, 0x3E, 0xD5, 0x2E, 0xBC, 0xCE, 0x7E, 0x2D, 0x52, 0xF3, 0x37, 0xF2, 0x53, 0x89, 0x5E, 0x56, 0x66, 0x74, 0xBF, 0xE2, 0x35, 0xAA, 0xEA, 0x45, 0xE9, 0x6F, 0xFC, 0x1E, 0x15, 0x8E, 0x13, 0x5A, 0xC6, 0xA2, 0x0B, 0x72, 0x89, 0x2B, 0x5E, 0x14, 0xC6, 0x41, 0x36, 0x0B, 0xA0, 0xA4, 0x2B, 0x7F, 0x7E, 0xD0, 0x90, 0xFE, 0x4F, 0x4E, 0x7B, 0xD8, 0x5B, 0x06, 0xEE, 0x0F, 0x62, 0xB6, 0x54, 0xDB, 0x17, 0x9B, 0x16, 0xEA, 0x72, 0xAC, 0x58, 0x9D, 0x88, 0xD0, 0xE6, 0x14, 0x82, 0x57, 0x89, 0xEA, 0x3A, 0x3D, 0x23, 0xE9, 0x14, 0x14, 0x0D, 0x95, 0x24, 0x7E, 0xAF, 0x4F, 0x39, 0x3E, 0x51, 0x74, 0x06, 0xCC, 0x58, 0xA6, 0x50, 0xF0, 0xF8, 0x1B, 0xD6, 0x48, 0xEB, 0xBD, 0x68, 0x34, 0xD8, 0x19, 0xA6, 0x83, 0x41, 0x37, 0xDD, 0x41, 0x42, 0x1E, 0x5F, 0xFF, 0x0D, 0xEA, 0x25, 0x42, 0x55, 0x39, 0xAA, 0x75, 0x88, 0x0F, 0xC8, 0x04, 0x47, 0x7A, 0xCA, 0xD0, 0x88, 0x72, 0x74, 0x95, 0x1B, 0xCA, 0xEB, 0xF6, 0x11, 0x03, 0xA1, 0x0C, 0x61, 0x9E, 0x4F, 0x2E, 0x6A, 0x60, 0x64, 0x7E, 0x5B, 0x12, 0xC4, 0x38, 0xFD, 0x66, 0xBA, 0xBB, 0xAC, 0x1B, 0xF4, 0xAB, 0x7D, 0x35, 0x4E, 0xAB, 0x47, 0x3F, 0xDA, 0xD5, 0x0C, 0x0D, 0x61, 0x85, 0xEF, 0x78, 0x74, 0x27, 0x1D, 0x39, 0x07, 0x2B, 0x8C, 0x45, 0x24, 0xC6, 0x3A, 0xD4, 0x29, 0x71, 0xEE, 0xF0, 0xAA, 0xDB, 0xE0, 0x4A, 0x5E, 0xB2, 0xEC, 0x91, 0xA6, 0xA6, 0xC8, 0xBC, 0x60, 0x24, 0x58, 0x77, 0xBB, 0x5C, 0x07, 0x14, 0x39, 0x52, 0xE0, 0xA0, 0xCC, 0x15, 0xA2, 0xC9, 0xA7, 0x69, 0x6F, 0xBC, 0x61, 0x38, 0xD3, 0x36, 0xBC, 0xFC, 0x22, 0x96, 0x07, 0xB5, 0xE7, 0xFA, 0xA8, 0x9E, 0x99, 0x8F, 0xDB, 0x62, 0xB0, 0x49, 0xF4, 0xD3, 0xC9, 0xFF, 0x7D, 0xA3, 0x12, 0xEF, 0x61, 0x13, 0xF3, 0x81, 0x80, 0xB3, 0xCD, 0x74, 0x21, 0x12, 0x0F, 0x36, 0x67, 0x52, 0xCE, 0xD1, 0x1D, 0x23, 0xA5, 0x35, 0x6B, 0x5F, 0x74, 0x62, 0x8A, 0xAD, 0xED, 0x36, 0x27, 0xF8, 0x7D, 0xE6, 0xED, 0x78, 0xE4, 0xD2, 0x4D, 0x56, 0xC5, 0xE1, 0x29, 0x54, 0x2D, 0x3E, 0x80, 0x97, 0xD0, 0xDC, 0x1F, 0x89, 0x05, 0xAE, 0xE8, 0xB3, 0x21, 0xD5, 0xF4, 0x0B, 0xBB, 0x73, 0xF6, 0x1A, 0x6A, 0xD5, 0x2E, 0xD4, 0xE3, 0x37, 0x93, 0x04, 0x91, 0x52, 0x5E, 0xCC, 0x8E, 0xC5, 0xD5, 0x0A, 0xB7, 0x11, 0xA7, 0xB3, 0xA1, 0xB1, 0xE0, 0x06, 0x61, 0xF7, 0xD6, 0xB4, 0x0C, 0x07, 0x27, 0x55, 0x67, 0x51, 0xC0, 0x17, 0xD5, 0x44, 0xFB, 0xF7, 0xE6, 0x40, 0xAD, 0x58, 0x78, 0x6D, 0xA5, 0x81, 0x60, 0x84, 0x9A, 0xB0, 0xEC, 0x9A, 0x7A, 0xEF, 0x5F, 0xEF, 0xD6, 0x86, 0x8C, 0x66, 0xC6, 0xFE, 0x1B, 0x12, 0x4F, 0x87, 0xC1, 0xB1, 0x1A, 0x85, 0x65, 0xE0, 0x44, 0xC6, 0x7F, 0xD3, 0xA4, 0x2C, 0x84, 0xF6, 0x77, 0x70, 0x98, 0x43, 0xB2, 0x83, 0xB8, 0xD4, 0x6B, 0xAD, 0xD1, 0xC8, 0x38, 0x98, 0x06, 0x43, 0xA9, 0xAB, 0x01, 0x02, 0x7D, 0x69, 0x01, 0x12, 0x65, 0x58, 0x5E, 0x3C, 0xE7, 0xCD, 0x57, 0xD9, 0x18, 0x24, 0xA1, 0x96, 0x8B, 0x64, 0x92, 0x5E, 0xBF, 0x6D, 0x8D, 0x8C, 0xF4, 0xE2, 0x35, 0x28, 0xBB, 0xE2, 0x9A, 0x26, 0x08, 0xA6, 0xBD, 0x66, 0x09, 0x06, 0x41, 0x7D, 0x43, 0x52, 0xBC, 0x17, 0x97, 0x7D, 0x98, 0x34, 0x9D, 0x84, 0x62, 0x57, 0xC8, 0xDE, 0x7F, 0x85, 0xEE, 0xE9, 0x54, 0xD7, 0x11, 0x99, 0xFB, 0xA6, 0xB6, 0x4F, 0x4B, 0x06, 0x9D, 0xDC, 0x3C, 0xF6, 0x6E, 0xB6, 0xAC, 0x93, 0x6F, 0x4E, 0x1E, 0x9E, 0x5A, 0xB2, 0xC3, 0x6F, 0xC6, 0x99, 0x9C, 0xDA, 0x25, 0x7D, 0x2E, 0x98, 0xDE, 0x0A, 0x7D, 0x75, 0xFF, 0xEE, 0xC9, 0x1E, 0xC2, 0x1E, 0x49, 0xE2, 0xCD, 0x06, 0x2D, 0x42, 0x27, 0x12, 0xC2, 0x73, 0x2B, 0x4A, 0x72, 0x8A, 0x70, 0x92, 0x91, 0x26, 0xE1, 0xDD, 0x30, 0xCA, 0x1D, 0x27, 0x88, 0x99, 0x18, 0x8B, 0xBA, 0x05, 0xC6, 0x8C, 0x46, 0x01, 0xE2, 0x83, 0x84, 0xBE, 0xE7, 0x71, 0x56, 0xD2, 0xF9, 0xDF, 0x66, 0x45, 0xAB, 0x05, 0x30, 0x78, 0xE7, 0xE9, 0xCB, 0xF4, 0x10, 0x80, 0xB6, 0x50, 0x7F, 0xD7, 0x9E, 0x5D, 0x93, 0xD0, 0x6F, 0xD7, 0x1E, 0x99, 0xC3, 0xC7, 0xA0, 0x96, 0x7F, 0xE3, 0xAF, 0xF3, 0x71, 0x8A, 0x3A, 0x7B, 0xE0, 0xA9, 0x77, 0x0E, 0x9F, 0xB2, 0xD7, 0x31, 0x34, 0x25, 0xE9, 0x2B, 0x4A, 0x5C, 0x44, 0xAC, 0x27, 0x49, 0xFF, 0xAE, 0x91, 0x17, 0xB3, 0xFA, 0xF6, 0x72, 0x96, 0xC8, 0x5C, 0x4F, 0x78, 0x1C, 0x55, 0x74, 0xD3, 0x78, 0xD9, 0x2E, 0xE6, 0xA7, 0xEA, 0x29, 0x0E, 0xAA, 0x71, 0xAC, 0x77, 0xF6, 0xA2, 0x5A, 0x00, 0x4C, 0xF8, 0xAA, 0x2E, 0xC7, 0x48, 0xD1, 0xC7, 0x0C, 0xB2, 0x4F, 0xC7, 0xCA, 0xCC, 0xC7, 0xDF, 0xDC, 0x18, 0x8C, 0x29, 0x5B, 0x22, 0xB1, 0x01, 0x1B, 0x54, 0x47, 0x5B, 0x98, 0x88, 0x49, 0x65, 0xB6, 0x55, 0xF5, 0x7B, 0xD3, 0x5D, 0x37, 0x1A, 0xD5, 0x23, 0xF0, 0x5C, 0x2F, 0x96, 0xAB, 0xD9, 0xCE, 0x6A, 0xA5, 0x9A, 0xAE, 0xDE, 0xB2, 0x46, 0x55, 0xF6, 0x6F, 0x4E, 0xA1, 0x4C, 0x4E, 0xF2, 0x32, 0xCC, 0x89, 0xA3, 0x0F, 0x16, 0xFD, 0x6C, 0x73, 0x07, 0x84, 0x9B, 0x6D, 0xC7, 0x3A, 0x9F, 0x04, 0xFA, 0x31, 0xB8, 0x31, 0xD2, 0x9E, 0x1A, 0xE8, 0x36, 0x05, 0xEA, 0xD5, 0x88, 0x83, 0x56, 0xB4, 0x2B, 0x87, 0xFD, 0xDF, 0x52, 0xFB, 0xBA, 0x6E, 0x7B, 0x6C, 0x18, 0xCE, 0xAD, 0xD1, 0xF0, 0xBD, 0xC5, 0xF7, 0x42, 0x96, 0x83, 0xD9, 0x70, 0x24, 0xF9, 0x32, 0x26, 0x2F, 0x69, 0x94, 0x05, 0xB2, 0x92, 0xB9, 0x00, 0x68, 0x8F, 0x25, 0xF2, 0x33, 0x3D, 0x6D, 0x3E, 0x58, 0xE0, 0x0B, 0x33, 0xA8, 0x0A, 0x58, 0x2F, 0x46, 0x49, 0x52, 0x2D, 0x7C, 0x5B, 0xF8, 0x51, 0x29, 0x6C, 0xE1, 0xB4, 0x80, 0xAF, 0x31, 0x03, 0xCC, 0xF5, 0xCD, 0x80, 0x13, 0x07, 0x96, 0xB4, 0x92, 0x29, 0x60, 0xBB, 0x95, 0xF4, 0x21, 0xA3, 0x82, 0xB2, 0x33, 0x70, 0x19, 0x24, 0x9E, 0xA0, 0x53, 0xBE, 0x0C, 0xC7, 0x0F, 0x36, 0x7B, 0xC7, 0xFA, 0xB5, 0x2C, 0xC8, 0xA4, 0x61, 0xD6, 0xA4, 0x79, 0x11, 0x98, 0x47, 0x43, 0xF0, 0xA5, 0x33, 0x3C, 0xF3, 0xFB, 0x7D, 0xF2, 0x22, 0x22, 0x80, 0x48, 0xE3, 0xFA, 0x2D, 0x5B, 0x73, 0xCB, 0x60, 0xE6, 0x2A, 0xCB, 0x1A, 0xF8, 0x66, 0xC0, 0x7D, 0xB0, 0xEE, 0x36, 0x9C, 0xD5, 0x00, 0x10, 0x9A, 0x6A, 0xF1, 0xB9, 0x32, 0x99, 0xF7, 0xEA, 0xB8, 0x29, 0xCB, 0xFA, 0x9E, 0xC6, 0xB1, 0x2C, 0x4F, 0x9A, 0x54, 0xC8, 0x96, 0x86, 0x1E, 0xCF, 0x79, 0x32, 0x4E, 0x81, 0x6C, 0x70, 0x7C, 0x68, 0x68, 0x91, 0xFC, 0x17, 0xF7, 0xBC, 0x12, 0x4C, 0x6F, 0xB3, 0x23, 0xBF, 0xF1, 0xDE, 0xEE, 0x47, 0x0E, 0xD4, 0xC3, 0xD5, 0x2C, 0x56, 0xA2, 0x0E, 0x96, 0xF2, 0x79, 0x34, 0x52, 0x53, 0xB8, 0x63, 0xD0, 0x6D, 0x53, 0x3E, 0x82, 0xD9, 0x2A, 0xB8, 0xE6, 0xC0, 0x9E, 0x43, 0x36, 0xEA, 0xE2, 0xD3, 0xD6, 0xB3, 0x81, 0x1C, 0x53, 0xD7, 0x9A, 0xEC, 0xDD, 0x19, 0x23, 0x5A, 0x64, 0x17, 0xE9, 0x87, 0xF8, 0x2B, 0xCF, 0x77, 0xAB, 0x09, 0x8A, 0x3B, 0xD2, 0xF1, 0x0B, 0x6D, 0xE0, 0x28, 0xA7, 0x04, 0x36, 0x2C, 0x66, 0xBB, 0x6A, 0x9E, 0xAD, 0x6B, 0xA3, 0xE6, 0x9E, 0x14, 0xFA, 0x25, 0xAE, 0xA6, 0x7E, 0x59, 0xFD, 0x2A, 0x2C, 0x6C, 0x6D, 0x42, 0xF7, 0xF7, 0x2C, 0x6B, 0xEB, 0xC3, 0xA0, 0xB1, 0x07, 0x2C, 0x86, 0xA9, 0xD1, 0x2D, 0xEB, 0x3D, 0x02, 0xF2, 0xD0, 0x33, 0x79, 0xE4, 0x72, 0x0A, 0x4A, 0xCC, 0xBC, 0x5A, 0x22, 0xCD, 0xB5, 0x35, 0x61, 0xEA, 0xB6, 0x7D, 0xEE, 0xA5, 0xAD, 0xE2, 0x49, 0x5B, 0x94, 0xE4, 0x67, 0x72, 0x87, 0x8A, 0xC9, 0xAC, 0xE2, 0x96, 0xE0, 0x7B, 0x0A, 0x8D, 0xB5, 0xFF, 0x35, 0x99, 0xF1, 0x71, 0xFA, 0x77, 0x00, 0x60, 0x1A, 0x39, 0x64, 0x3F, 0x86, 0x86, 0x5A, 0xEC, 0xD3, 0xD7, 0xCF, 0xC1, 0xA0, 0x3E, 0xDB, 0xA7, 0xED, 0xC6, 0x24, 0x63, 0x96, 0x29, 0xAF, 0xCB, 0x49, 0x24, 0x08, 0xC5, 0x0D, 0xE0, 0x52, 0x95, 0x89, 0xB3, 0xA8, 0xDD, 0x4D, 0x66, 0xAC, 0x7C, 0x7A, 0x15, 0xA5, 0x81, 0xBE, 0xB9, 0x7E, 0x1C, 0x82, 0xA5, 0xD5, 0x45, 0x86, 0xE6, 0x06, 0x5C, 0x60, 0xC7, 0x0F, 0xD3, 0x51, 0xF1, 0x17, 0x34, 0x6D, 0x43, 0xB1, 0x98, 0x52, 0x11, 0x24, 0x3B, 0x9E, 0x5A, 0xE9, 0xD1, 0xEE, 0x7C, 0x64, 0xF5, 0xBB, 0x4B, 0xBC, 0xC3, 0x43, 0x5F, 0x80, 0x27, 0xF2, 0x1A, 0xFC, 0xEA, 0xA0, 0x5C, 0x25, 0xA4, 0x63, 0x16, 0x0B, 0xAA, 0x30, 0x2E, 0x4E, 0x68, 0x9D, 0xE0, 0xEE, 0x38, 0x28, 0x80, 0xC7, 0x22, 0x10, 0x19, 0x55, 0x7F, 0x4C, 0xFE, 0xD0, 0xBA, 0x38, 0x4A, 0x1E, 0xD0, 0x42, 0xE3, 0x57, 0x57, 0xD5, 0x3B, 0xC3, 0xE7, 0x58, 0xA4, 0x7E, 0x29, 0x83, 0x9B, 0xDF, 0x16, 0x45, 0x1D, 0xE1, 0xDC, 0xE2, 0xE2, 0xAB, 0x0E, 0x45, 0x3F, 0x34, 0x0C, 0x47, 0x88, 0xEB, 0x0A, 0xF0, 0x55, 0x38, 0x7D, 0xCD, 0x73, 0x73, 0xEA, 0x59, 0xAB, 0xA2, 0xFA, 0xF5, 0x20, 0x9B, 0x64, 0xCE, 0xB5, 0x6B, 0x0C, 0x40, 0x25, 0xC7, 0x3B, 0x03, 0xED, 0xA9, 0x4C, 0x8F, 0x47, 0x44, 0x2B, 0x18, 0x6B, 0x96, 0xF0, 0x1B, 0xAD, 0x6B, 0x3C, 0x7E, 0xA7, 0x60, 0xB5, 0x1F, 0x51, 0x17, 0x05, 0xF3, 0xD1, 0xF5, 0x2A, 0x5C, 0xE6, 0xC4, 0x81, 0xBF, 0x91, 0xBB, 0x52, 0xA5, 0x7B, 0x4B, 0xB3, 0x19, 0x8D, 0x13, 0xDB, 0xA6, 0x31, 0xF8, 0x9F, 0x36, 0x21, 0x83, 0xF3, 0x0E, 0xF5, 0x13, 0x3B, 0xD4, 0x87, 0xE2, 0xE1, 0x83, 0x3D, 0x56, 0x1A, 0x75, 0x4B, 0x80, 0x33, 0x22, 0xF1, 0x5F, 0xCA, 0x53, 0xD0, 0x36, 0x6E, 0x4D, 0x4B, 0x95, 0xA2, 0x15, 0xB5, 0x73, 0x5F, 0x97, 0x6B, 0xCE, 0xE1, 0x16, 0x3B, 0x8E, 0xBD, 0xF3, 0x54, 0x8B, 0xC4, 0x10, 0x47, 0x10, 0x3B, 0x5D, 0xA4, 0xC9, 0x2E, 0xCB, 0x32, 0x21, 0x1D, 0x1E, 0xE2, 0x03, 0xA1, 0x5F, 0x8B, 0x0B, 0x65, 0xCC, 0x11, 0x46, 0x73, 0xF8, 0x51, 0x4F, 0xBA, 0x27, 0xAD, 0x85, 0xC7, 0xAB, 0xD0, 0xA2, 0x90, 0x79, 0x27, 0x23, 0xF8, 0x1A, 0x94, 0x79, 0x82, 0xC3, 0x17, 0x2B, 0xA8, 0x3E, 0x59, 0x2A, 0x64, 0xCD, 0x16, 0xDD, 0xBB, 0xA3, 0x0C, 0x34, 0xDE, 0xAE, 0xAF, 0x5D, 0xDE, 0x90, 0x31, 0x7F, 0x9A, 0x69, 0x14, 0x65, 0xAC, 0xC0, 0xED, 0xE9, 0xB8, 0xDD, 0x59, 0x4F, 0x37, 0xA7, 0xEC, 0x5A, 0xA8, 0x96, 0xC2, 0xF8, 0x2A, 0xE6, 0xB4, 0xDD, 0xE7, 0xF8, 0x44, 0x5B, 0x39, 0x4B, 0x47, 0xCE, 0x0C, 0x33, 0xB1, 0x99, 0xB2, 0x32, 0x05, 0x7E, 0xFD, 0x12, 0x5B, 0xF8, 0xD2, 0x9D, 0x20, 0x94, 0x8D, 0x19, 0xFA, 0x82, 0x61, 0xB3, 0x99, 0x7C, 0x8C, 0x15, 0x0E, 0x0D, 0x9C, 0xFC, 0xC1, 0xDC, 0x0C, 0x79, 0x69, 0x07, 0xD8, 0x13, 0x86, 0xDA, 0xA4, 0xF0, 0xCF, 0x5B, 0xDE, 0xF7, 0xB9, 0x71, 0x7C, 0x13, 0xA6, 0xAF, 0x31, 0x88, 0x1E, 0x3B, 0xFC, 0x96, 0xDF, 0xE5, 0x63, 0x80, 0x69, 0x87, 0x09, 0xD8, 0x46, 0xBB, 0x28, 0x18, 0x34, 0x1A, 0x03, 0xE8, 0xF4, 0x46, 0x46, 0x8B, 0xD5, 0x3C, 0xD6, 0x02, 0xE3, 0xE2, 0x1A, 0x9A, 0xC7, 0x97, 0x2A, 0x62, 0x60, 0x6A, 0xBD, 0x60, 0xFD, 0x5D, 0x4A, 0x68, 0x92, 0x56, 0x8F, 0x86, 0xAD, 0x57, 0x36, 0x1B, 0xD6, 0x6C, 0x57, 0xB8, 0x41, 0x13, 0x1D, 0x13, 0xB8, 0xB1, 0x60, 0xE2, 0xB5, 0x9A, 0x8F, 0xE3, 0x56, 0xAC, 0xD2, 0xC6, 0x62, 0xBA, 0xA8, 0x66, 0x4D, 0xDA, 0x58, 0x9A, 0xCE, 0x8F, 0xCD, 0x55, 0xF6, 0x19, 0xF1, 0x2C, 0xED, 0x38, 0x66, 0xAD, 0x88, 0x56, 0x2F, 0xA3, 0x42, 0x93, 0x55, 0x33, 0x06, 0x35, 0x2B, 0xDE, 0x1E, 0xD9, 0x1B, 0x07, 0x5C, 0x98, 0xD0, 0x74, 0xE7, 0x56, 0x97, 0x4F, 0xD1, 0xA4, 0x17, 0xB2, 0xB3, 0x32, 0x70, 0xDF, 0x91, 0xEC, 0xC2, 0xA6, 0x86, 0xBC, 0x4E, 0xFC, 0x17, 0xA6, 0x13, 0x50, 0x0C, 0x1E, 0x3C, 0x50, 0xDF, 0x6C, 0x14, 0x74, 0xBE, 0xF5, 0x20, 0xB8, 0xBF, 0xA0, 0xF7, 0x3E, 0x32, 0x5F, 0x22, 0x9B, 0x15, 0x74, 0xBC, 0xD5, 0x42, 0xCE, 0x19, 0xAA, 0x5C, 0x01, 0xB1, 0x77, 0xC3, 0x50, 0x56, 0xCD, 0x8C, 0x61, 0xBA, 0x20, 0xFB, 0xD5, 0x76, 0x1D, 0x57, 0xA3, 0xBB, 0xFC, 0xFB, 0x07, 0x1F, 0x43, 0x07, 0x39, 0x70, 0xAC, 0x82, 0x6A, 0x41, 0xDE, 0x41, 0x3B, 0xE4, 0xC4, 0x3A, 0xD6, 0x4D, 0xF0, 0x77, 0x54, 0xC7, 0x35, 0x26, 0x98, 0x47, 0x71, 0xEC, 0x2F, 0x02, 0x5F, 0x89, 0x4B, 0xB0, 0xF3, 0xA5, 0xC7, 0x99, 0xB7, 0x2D, 0x65, 0xE3, 0x42, 0x07, 0x17, 0x22, 0xD8, 0x96, 0x0A, 0x01, 0xB3, 0x26, 0xA1, 0x64, 0xCF, 0x4D, 0xFA, 0x59, 0x54, 0xD9, 0x52, 0x6D, 0xAE, 0x62, 0x3D, 0x96, 0x1B, 0x49, 0x6C, 0x38, 0x30, 0x16, 0xE9, 0xAE, 0xB8, 0x8E, 0xDF, 0x2A, 0x11, 0x33, 0x09, 0x79, 0x60, 0x0E, 0xCC, 0xB8, 0x7D, 0xDF, 0x80, 0x76, 0x05, 0x89, 0xDF, 0x96, 0xD6, 0x0E, 0x89, 0x5E, 0x72, 0x1D, 0x31, 0x9B, 0xA2, 0xED, 0x41, 0x7F, 0x41, 0x2F, 0xCB, 0x31, 0xD9, 0x8A, 0x36, 0x90, 0xDB, 0xD1, 0x26, 0xE5, 0x29, 0x7C, 0x37, 0x99, 0xFA, 0xDF, 0x6C, 0xF1, 0x49, 0x0B, 0xE2, 0xD8, 0xB7, 0x53, 0x34, 0xEF, 0xDF, 0x9A, 0xBC, 0xCF, 0x23, 0x32, 0xD1, 0x3C, 0xA7, 0x4A, 0xF4, 0x48, 0x1A, 0xE6, 0x4C, 0x8D, 0x04, 0x3D, 0x7B, 0x1D, 0x63, 0xAB, 0xB4, 0xB8, 0xA1, 0x23, 0x71, 0xAB, 0x15, 0x01, 0x00, 0xC6, 0x02, 0xAD, 0x86, 0x31, 0xDA, 0x07, 0x1C, 0x17, 0xBD, 0xED, 0xF0, 0x2E, 0x7C, 0xA2, 0x64, 0x7B, 0x45, 0x4E, 0x5E, 0x76, 0x8E, 0x07, 0x26, 0x2B, 0x4E, 0x4B, 0x8B, 0x31, 0xE4, 0xB1, 0xD8, 0xCD, 0x5C, 0xE7, 0x68, 0xEF, 0x87, 0x5D, 0x57, 0xA6, 0x02, 0xDE, 0x64, 0xE5, 0x04, 0xB2, 0xC8, 0x6F, 0xE6, 0x4B, 0xE0, 0x30, 0x38, 0x94, 0xFE, 0xC0, 0x0D, 0xE7, 0x10, 0xCB, 0x98, 0x59, 0x9F, 0xE8, 0xBC, 0x78, 0x3C, 0x67, 0xDE, 0x4B, 0x72, 0x6A, 0x40, 0x85, 0xFF, 0xCE, 0x72, 0xC9, 0x74, 0x44, 0x6F, 0xC8, 0x4D, 0xA5, 0xC9, 0x6B, 0xBB, 0x0E, 0xF5, 0x26, 0x89, 0x06, 0x23, 0xA2, 0xF0, 0x30, 0x01, 0xD4, 0x0C, 0xE9, 0xE9, 0x8D, 0x71, 0x77, 0x41, 0x1D, 0x7A, 0x71, 0x79, 0x63, 0x86, 0xC9, 0x0D, 0x17, 0x67, 0x62, 0x6A, 0x24, 0x64, 0xC8, 0xBD, 0xA4, 0xED, 0x7B, 0xF2, 0x35, 0x59, 0x53, 0x09, 0x70, 0x56, 0xF2, 0xBB, 0x9B, 0x33, 0xEF, 0xFE, 0x25, 0x31, 0x65, 0x74, 0x5A, 0xC8, 0x54, 0xD7, 0xE9, 0x10, 0x88, 0x16, 0x60, 0xB7, 0x4C, 0x9E, 0x8F, 0x9F, 0xFA, 0x02, 0x5E, 0x78, 0xF2, 0x4D, 0x07, 0x42, 0x35, 0xAE, 0x55, 0x3E, 0xA2, 0xBA, 0x6C, 0xB6, 0xBD, 0x87, 0xD8, 0xD8, 0xC8, 0x1F, 0xCA, 0xD5, 0xA8, 0xCD, 0x5D, 0x05, 0xF5, 0xFA, 0x69, 0xDE, 0x0E, 0x0C, 0x5F, 0x24, 0x0C, 0x4C, 0x36, 0xB1, 0x00, 0x3B, 0x61, 0x74, 0xDB, 0x81, 0xFB, 0x41, 0x8D, 0x16, 0xDB, 0x46, 0x8C, 0x75, 0x53, 0x31, 0x87, 0x48, 0x03, 0x26, 0xE7, 0xDA, 0x56, 0xD5, 0x59, 0x13, 0xAC, 0x49, 0x25, 0x6E, 0xE7, 0x9F, 0xCD, 0xDE, 0x17, 0xC7, 0xA0, 0x04, 0x3E, 0x80, 0x3B, 0x61, 0x28, 0x1F, 0xDA, 0xA7, 0x97, 0xE2, 0x99, 0x0D, 0x9B, 0x34, 0xDB, 0x67, 0x41, 0x0E, 0xC1, 0x65, 0x6F, 0xE8, 0xD4, 0x92, 0x02, 0xBF, 0x86, 0xC2, 0x99, 0x31, 0x58, 0xB2, 0x16, 0xBE, 0x8D, 0xAA, 0x7C, 0xF8, 0xC2, 0xF1, 0x93, 0xBB, 0x06, 0x2B, 0xE8, 0x59, 0x77, 0x49, 0xDD, 0x71, 0x62, 0xC3, 0xBC, 0x9F, 0x3E, 0x6D, 0x7B, 0x04, 0x4D, 0x96, 0x60, 0x86, 0xDB, 0xDB, 0x70, 0xCA, 0x04, 0x2A, 0x21, 0xBA, 0xFF, 0x9A, 0x6D, 0xC8, 0x31, 0x04, 0xA0, 0x1D, 0xE7, 0x34, 0xE5, 0xD6, 0x21, 0xF7, 0x55, 0x2B, 0xCE, 0x01, 0x62, 0x4E, 0xCF, 0x2E, 0xAC, 0x5F, 0x6A, 0xAD, 0xE3, 0x72, 0xC1, 0x65, 0x34, 0xF1, 0xB5, 0xF3, 0x37, 0xDE, 0x52, 0xB2, 0x89, 0x0C, 0xA5, 0x12, 0x96, 0x46, 0xCE, 0x2E, 0x2E, 0x0C, 0xAA, 0x2E, 0xA2, 0x96, 0xFF, 0xDA, 0xDE, 0x1A, 0x2D, 0x02, 0x65, 0x5A, 0xA1, 0x87, 0x3B, 0x09, 0x3C, 0x7D, 0x44, 0xCD, 0x4A, 0x36, 0xEC, 0xA5, 0x25, 0x4D, 0x17, 0x49, 0x03, 0xD4, 0xEE, 0xB7, 0x80, 0x8D, 0x70, 0xD7, 0x2F, 0x42, 0xE7, 0x65, 0x16, 0x99, 0x02, 0xAF, 0x85, 0xC5, 0x91, 0x82, 0xBE, 0x37, 0x93, 0x5B, 0x2B, 0x2C, 0x6E, 0x49, 0x71, 0x17, 0x4E, 0xBA, 0xA7, 0x64, 0x63, 0x16, 0x53, 0xF5, 0x66, 0xB9, 0xE6, 0xCC, 0x49, 0xC6, 0xDE, 0x8C, 0xC6, 0x26, 0x85, 0xDD, 0x49, 0x4E, 0xF6, 0x67, 0x93, 0xEA, 0xC5, 0xF2, 0x5B, 0x61, 0xC7, 0x3B, 0xBC, 0x36, 0x51, 0xB1, 0x79, 0x7C, 0x17, 0xE9, 0x80, 0x3D, 0xE2, 0xDC, 0x99, 0xB8, 0xA7, 0x74, 0x0C, 0xEB, 0xE0, 0xE6, 0xC6, 0x32, 0xA6, 0xE3, 0x03, 0xB3, 0xE5, 0xEF, 0x33, 0x48, 0xE7, 0x03, 0x74, 0xEC, 0x55, 0x6C, 0x3C, 0x7A, 0x93, 0x3D, 0x8C, 0x00, 0xD3, 0xB0, 0x58, 0x31, 0x89, 0x26, 0xC3, 0x66, 0x8B, 0xDC, 0xAC, 0xCF, 0x46, 0x38, 0xBE, 0xE2, 0xB7, 0x4B, 0xB5, 0xCB, 0xF4, 0x48, 0x64, 0x24, 0x48, 0x3D, 0x73, 0x89, 0x2F, 0x36, 0xC8, 0x49, 0x09, 0xEC, 0x8C, 0x9E, 0x39, 0xDC, 0xA7, 0x41, 0x50, 0x7E, 0x8C, 0xA4, 0x08, 0xB7, 0x54, 0xCB, 0x50, 0x63, 0xD2, 0xB6, 0x52, 0x27, 0xEA, 0x80, 0x54, 0x2F, 0xB9, 0xE5, 0x30, 0x3B, 0x03, 0x07, 0x1A, 0x90, 0x14, 0x72, 0x98, 0xC7, 0x99, 0xF8, 0x54, 0xA8, 0x57, 0xDB, 0x0F, 0x1A, 0x47, 0x63, 0xE2, 0xDD, 0xC7, 0x98, 0x6A, 0xEC, 0x6C, 0xDA, 0x01, 0x92, 0xA8, 0x62, 0x12, 0xD9, 0xBA, 0x36, 0x4C, 0x3B, 0x83, 0x39, 0xEE, 0xD7, 0x9C, 0x7E, 0x66, 0x1D, 0x7A, 0xBC, 0xFF, 0x4D, 0x2E, 0xC0, 0x1A, 0x95, 0x31, 0x29, 0xC2, 0xA2, 0xEA, 0x1E, 0xFA, 0x16, 0x70, 0xE1, 0xB6, 0x18, 0xF7, 0x65, 0x90, 0x0C, 0x6C, 0xB0, 0x47, 0xB1, 0x5C, 0xE3, 0xC7, 0x66, 0xF5, 0xEC, 0xE5, 0x79, 0x68, 0xC1, 0x72, 0x4F, 0xFC, 0xEA, 0x1A, 0xC5, 0xB3, 0x0A, 0xC4, 0x81, 0xF1, 0xA2, 0x44, 0xCC, 0xE8, 0x57, 0x4A, 0xD2, 0xB2, 0x71, 0x04, 0xA4, 0x1E, 0x21, 0xEA, 0x50, 0xFF, 0x62, 0x1B, 0x7C, 0xA6, 0x8F, 0xE8, 0x38, 0x02, 0xC6, 0xE0, 0xFE, 0x08, 0x5A, 0x9B, 0x9C, 0x32, 0x08, 0x60, 0xD8, 0xBE, 0x2A, 0xBA, 0x71, 0x53, 0x03, 0x84, 0x36, 0x45, 0x59, 0x6D, 0xE4, 0x6C, 0x06, 0xFC, 0x45, 0x6B, 0xDE, 0x47, 0x89, 0x72, 0xD5, 0x66, 0x62, 0xD4, 0xDA, 0x74, 0x91, 0xE9, 0x93, 0x2F, 0xEA, 0x78, 0x8F, 0x0A, 0x7C, 0xFC, 0x77, 0xFA, 0x17, 0x68, 0x96, 0xD0, 0xC6, 0xF5, 0x32, 0x70, 0xD3, 0xFA, 0xE6, 0x4A, 0x2A, 0x52, 0x1F, 0xEF, 0x20, 0x2E, 0xE9, 0xBC, 0x58, 0xCF, 0x17, 0xCB, 0xC0, 0xF1, 0x46, 0x32, 0x02, 0x6F, 0xB3, 0xA1, 0x72, 0x55, 0x58, 0x5B, 0x75, 0x94, 0xBA, 0xD7, 0xCB, 0xA5, 0xFD, 0x82, 0xDF, 0x4E, 0x29, 0xCB, 0x4B, 0xD7, 0x5C, 0xCE, 0xA7, 0x69, 0x10, 0xFF, 0xDD, 0xD0, 0xB5, 0x8E, 0xC0, 0x27, 0xE4, 0x5A, 0xD3, 0x4A, 0xBB, 0x43, 0xEB, 0x6D, 0xF7, 0xEF, 0x57, 0x86, 0x3D, 0xC7, 0x42, 0xB2, 0x8D, 0x62, 0x65, 0xEB, 0x7C, 0xD1, 0x76, 0xFE, 0x47, 0x65, 0x7F, 0xC8, 0xF7, 0x29, 0x10, 0xDC, 0xEF, 0x94, 0xFF, 0x75, 0x07, 0xFA, 0x75, 0x22, 0xCF, 0xAC, 0x66, 0xBC, 0x7D, 0xBB, 0x6B, 0x57, 0x37, 0xEA, 0x8A, 0x20, 0x27, 0xC8, 0x8E, 0x5E, 0xCA, 0x33, 0x33, 0xB9, 0x5F, 0x42, 0x18, 0x9A, 0x1C, 0xEC, 0x6D, 0x8E, 0x63, 0xCF, 0xD7, 0x1F, 0x01, 0xD5, 0x1C, 0x0B, 0x7A, 0x12, 0xE3, 0x26, 0xC4, 0x0B, 0xE7, 0x7C, 0x5B, 0xF3, 0xC9, 0x9B, 0xD3, 0x97, 0xF1, 0x2C, 0x25, 0xF4, 0x29, 0xE0, 0xAE, 0x61, 0xC1, 0x24, 0xE2, 0xDD, 0x26, 0xCC, 0xD4, 0x7D, 0xCA, 0x27, 0x07, 0xE5, 0x64, 0xE3, 0xE9, 0x95, 0x8C, 0x1E, 0xE1, 0xF7, 0x32, 0x3D, 0x13, 0xF5, 0x3F, 0xC9, 0x93, 0x57, 0xC7, 0x0E, 0xC2, 0x41, 0x37, 0x53, 0x41, 0x74, 0x90, 0xFF, 0x7C, 0xD3, 0x07, 0x14, 0x6A, 0xC3, 0x66, 0xA7, 0x25, 0x20, 0x2C, 0xC9, 0x5E, 0x69, 0x79, 0x38, 0xAF, 0xF0, 0xDE, 0x86, 0x2F, 0x89, 0xB8, 0xCA, 0x2C, 0xFC, 0x2F, 0x66, 0x3A, 0xF2, 0x8D, 0x1B, 0x4C, 0x0E, 0xF7, 0x42, 0x1A, 0xF2, 0x3D, 0xD6, 0xFF, 0x5F, 0xA6, 0x31, 0x70, 0xFC, 0x90, 0x14, 0xDC, 0x79, 0xE6, 0xDC, 0x09, 0xC2, 0x45, 0x50, 0xF3, 0x6E, 0x25, 0xED, 0x50, 0xDB, 0xDE, 0xBE, 0x3E, 0xF7, 0xBE, 0xF2, 0xA1, 0xCE, 0x08, 0xF6, 0xB1, 0x49, 0xED, 0xC5, 0xB3, 0xF7, 0x4F, 0x6D, 0x1C, 0x70, 0x62, 0xFC, 0xAF, 0x03, 0x0B, 0x33, 0x95, 0x53, 0x48, 0x78, 0x48, 0x7B, 0x0A, 0x3E, 0xC3, 0xEC, 0xF8, 0x7F, 0xF6, 0x13, 0x29, 0x9E, 0xCE, 0x63, 0x25, 0xE9, 0x68, 0x75, 0xCF, 0xBC, 0x3C, 0xFF, 0x4C, 0x6E, 0x6C, 0xD7, 0x7D, 0xF0, 0xF5, 0xD1, 0xEF, 0x5F, 0x98, 0xE4, 0x53, 0x52, 0x34, 0x7E, 0xF6, 0x40, 0xE6, 0x7D, 0xC9, 0x67, 0x8C, 0xE5, 0x3E, 0xC8, 0xF7, 0x10, 0x07, 0x04, 0x76, 0xBC, 0xDB, 0xB1, 0xE9, 0xF5, 0xAF, 0xCE, 0xA7, 0x21, 0xBF, 0xE7, 0xE8, 0x3F, 0xAA, 0xEA, 0x81, 0x54, 0x69, 0xBD, 0x0F, 0x36, 0x46, 0xB6, 0xA5, 0xDD, 0xB2, 0x3E, 0x03, 0xFE, 0xDF, 0x2D, 0xCD, 0xE0, 0x3D, 0xEA, 0xE8, 0x25, 0xA0, 0xCE, 0x0A, 0x9B, 0x1C, 0x99, 0xE7, 0x52, 0x46, 0xF2, 0x46, 0x11, 0x1F, 0x47, 0x7B, 0xF4, 0xC0, 0x7B, 0x75, 0x68, 0x5E, 0xD1, 0x32, 0x20, 0xEB, 0xFA, 0x21, 0xBD, 0x6A, 0xB1, 0x79, 0x2C, 0x02, 0x3B, 0x98, 0x6B, 0x27, 0xD4, 0x49, 0x85, 0xD7, 0x19, 0x53, 0x2D, 0xF3, 0xAA, 0x0B, 0xED, 0x6B, 0x5B, 0x5E, 0x6E, 0x7C, 0xC9, 0x3D, 0xCB, 0xC4, 0xCA, 0x7E, 0xC9, 0x86, 0xA9, 0xF1, 0x9D, 0xB1, 0x7A, 0xE4, 0x49, 0xFB, 0xBE, 0x9A, 0x28, 0x83, 0x1C, 0xF3, 0x80, 0x9E, 0x15, 0x33, 0xEB, 0xBE, 0x63, 0xE2, 0x3C, 0xDC, 0xFE, 0x79, 0x8F, 0x04, 0xAB, 0xDA, 0x36, 0xA5, 0x27, 0xBF, 0x90, 0x3A, 0xCB, 0xA7, 0xBA, 0xAC, 0x98, 0xA0, 0x36, 0x88, 0x7B, 0xED, 0x2A, 0xF7, 0xDF, 0x78, 0xD0, 0x16, 0x86, 0x31, 0xEC, 0xA4, 0x29, 0xCC, 0x72, 0xE9, 0x68, 0x50, 0x54, 0x43, 0x7E, 0xB7, 0xEE, 0x54, 0xBA, 0xB8, 0xD0, 0x44, 0x08, 0x4B, 0xBB, 0xB6, 0x6C, 0x96, 0x62, 0xD7, 0xAE, 0x61, 0xCE, 0x61, 0x9F, 0x89, 0xDD, 0x54, 0xB6, 0x69, 0xBC, 0xF6, 0xC5, 0x72, 0x03, 0x32, 0x71, 0xB6, 0xD7, 0x7D, 0x48, 0x20, 0x18, 0x30, 0x68, 0xD9, 0x58, 0x7B, 0x8B, 0xAE, 0x59, 0x5D, 0x98, 0xA6, 0x2F, 0x92, 0x47, 0x2D, 0xDD, 0x94, 0x4E, 0x65, 0xC1, 0x57, 0x8D, 0xC7, 0xE9, 0x15, 0x13, 0xB2, 0x58, 0x30, 0x5A, 0x6B, 0x87, 0x75, 0x9B, 0xA7, 0xD4, 0x0A, 0x88, 0x02, 0x62, 0x22, 0xB6, 0x04, 0xB3, 0xC8, 0xC3, 0x1F, 0x82, 0x96, 0x79, 0x08, 0xBF, 0xE9, 0xA6, 0xEB, 0x09, 0x0B, 0x18, 0x57, 0x7A, 0x7B, 0x49, 0x7E, 0xCB, 0xF2, 0x5D, 0x2C, 0xC7, 0xD9, 0xE1, 0x26, 0x6F, 0x56, 0x87, 0x15, 0x99, 0xC2, 0x76, 0xDF, 0x56, 0xA5, 0xB3, 0x2E, 0xAD, 0xFC, 0xDF, 0xB4, 0xB3, 0x1B, 0xF3, 0xB9, 0xA1, 0xA8, 0x65, 0x14, 0xDE, 0x25, 0xE3, 0x5C, 0x0B, 0x80, 0x69, 0x65, 0x77, 0xC6, 0x78, 0xDC, 0x29, 0x87, 0x2C, 0x24, 0xBF, 0xEB, 0xD1, 0x24, 0x69, 0x56, 0x5C, 0xFC, 0x67, 0x3B, 0xB2, 0x77, 0x27, 0x6F, 0xBF, 0x80, 0x8E, 0xFB, 0x2D, 0x96, 0xC1, 0xC2, 0xAF, 0xEF, 0xF4, 0x3A, 0x8E, 0x2E, 0xCD, 0x59, 0x3E, 0x07, 0xF3, 0xED, 0xC4, 0x2E, 0xAF, 0x4B, 0xD4, 0x27, 0x86, 0x38, 0xEB, 0xC2, 0x8B, 0xDF, 0x7B, 0xBD, 0x9C, 0x91, 0xC1, 0xF7, 0x03, 0x45, 0x77, 0x02, 0x5A, 0xD8, 0x3E, 0x89, 0x54, 0x20, 0xA0, 0x4D, 0x49, 0x3A, 0xA7, 0x58, 0x4E, 0x19, 0xE6, 0xC0, 0x69, 0x69, 0x0B, 0x72, 0x28, 0xEE, 0x0C, 0xB0, 0xC0, 0x1B, 0xC6, 0x80, 0x3F, 0x5E, 0x71, 0xCC, 0x85, 0xA1, 0x51, 0x16, 0xB7, 0x9D, 0x08, 0x59, 0xFB, 0xE1, 0x87, 0x1E, 0xA0, 0x63, 0x93, 0x84, 0xFC, 0x0F, 0x83, 0x54, 0xD1, 0x99, 0x03, 0x64, 0xCD, 0x51, 0x28, 0x28, 0x95, 0x9B, 0xD8, 0x87, 0xA6, 0x86, 0xC4, 0x82, 0x62, 0x58, 0xBE, 0x03, 0x9B, 0x2E, 0xE4, 0xBF, 0xB4, 0xB5, 0xEF, 0x1E, 0xA5, 0xD0, 0xDF, 0xAD, 0x91, 0x4D, 0xB6, 0x4C, 0x01, 0x84, 0x08, 0x85, 0x08, 0xA4, 0x08, 0xE7, 0xA7, 0x58, 0x4A, 0x6D, 0xE7, 0x23, 0xDC, 0x2A, 0x23, 0x4F, 0xB9, 0x50, 0x94, 0x73, 0xB4, 0x44, 0x6A, 0x79, 0xFA, 0x92, 0x65, 0xBD, 0xE8, 0x68, 0x78, 0x82, 0x12, 0x39, 0x9E, 0x9F, 0x2C, 0x70, 0x02, 0x3D, 0xDE, 0xFA, 0x77, 0x7E, 0x4A, 0x35, 0x8F, 0x35, 0xFF, 0xF7, 0x68, 0x5C, 0x9C, 0x9B, 0x3F, 0x1F, 0x9A, 0xA7, 0x97, 0xA5, 0x71, 0xA9, 0xA9, 0xDA, 0xDC, 0xA6, 0xFA, 0xA0, 0x25, 0x71, 0xB4, 0xB7, 0xCD, 0x54, 0x81, 0x3E, 0xA0, 0xED, 0xFD, 0x6D, 0xA7, 0x6B, 0x41, 0xA8, 0x17, 0x17, 0x94, 0x96, 0x52, 0xB9, 0xCE, 0xA4, 0x12, 0x6A, 0x45, 0x52, 0x41, 0x99, 0xF8, 0xD7, 0x2F, 0xB9, 0x88, 0x6A, 0xB6, 0x97, 0x89, 0xE7, 0xA5, 0xEE, 0x66, 0x70, 0xF8, 0xD0, 0x9B, 0xE7, 0x4A, 0x45, 0xAE, 0xEF, 0x5D, 0xDD, 0x3D, 0x0E, 0x64, 0x7A, 0xC5, 0x5C, 0x5A, 0x07, 0x69, 0xE4, 0x81, 0xBF, 0x34, 0xAF, 0x57, 0xE0, 0x4B, 0xB1, 0x55, 0x4E, 0xE5, 0xE5, 0xC1, 0x99, 0x7A, 0xCF, 0xD5, 0x3E, 0xEF, 0x69, 0x9E, 0x7B, 0x71, 0xF6, 0x65, 0xBB, 0xFE, 0x7C, 0xD5, 0xDB, 0x57, 0xD1, 0x5F, 0xAD, 0xAE, 0x26, 0xF2, 0xCA, 0x7B, 0x08, 0x3C, 0xE6, 0xDA, 0xDD, 0xE8, 0xBA, 0x28, 0x83, 0xCA, 0x0E, 0x08, 0x00, 0x06, 0x8E, 0x12, 0x1C, 0x0D, 0x59, 0x17, 0xA4, 0x50, 0x86, 0x19, 0x80, 0xF0, 0xC4, 0xFF, 0xA3, 0xC7, 0xD1, 0x8C, 0xAF, 0x22, 0x26, 0x23, 0xC6, 0xF5, 0xEB, 0xFA, 0xC8, 0x3B, 0x9C, 0x9C, 0xAB, 0x65, 0x3F, 0x74, 0x86, 0xD0, 0x91, 0x5B, 0x77, 0xD0, 0xE9, 0xFD, 0x61, 0xDB, 0x5B, 0x68, 0x88, 0xDB, 0x3F, 0x78, 0x5C, 0x83, 0xB1, 0x71, 0xF9, 0xBE, 0x84, 0xC7, 0x4D, 0x3B, 0xB1, 0x74, 0xE2, 0x56, 0xD9, 0xB8, 0x57, 0x3C, 0x86, 0x36, 0x3C, 0x3D, 0xAC, 0xF5, 0xCD, 0xBE, 0x9A, 0x3B, 0x9B, 0xA4, 0x7B, 0x87, 0xE4, 0xB6, 0x37, 0x32, 0xD3, 0xF0, 0x88, 0x68, 0x8F, 0x8F, 0xC8, 0x04, 0x83, 0xF8, 0x14, 0x29, 0x67, 0x46, 0x06, 0x83, 0xE1, 0x8C, 0x98, 0x94, 0x13, 0x69, 0x6C, 0xD0, 0x3D, 0x91, 0x89, 0xA7, 0xCF, 0xBE, 0x97, 0x79, 0x64, 0xBE, 0x14, 0x62, 0xC4, 0x5F, 0x55, 0x85, 0x8D, 0x7E, 0x3A, 0x4D, 0xF8, 0x16, 0xA7, 0xEC, 0xB3, 0x50, 0xF0, 0xC0, 0x15, 0x3C, 0x81, 0xDB, 0x6C, 0xB8, 0xB2, 0xCE, 0xD2, 0xF2, 0x22, 0x3D, 0x3B, 0xDA, 0x18, 0xC9, 0xD2, 0x23, 0xD4, 0x5A, 0x13, 0x26, 0x87, 0x7B, 0xCD, 0x4C, 0x74, 0x4C, 0x7A, 0x30, 0x6D, 0x87, 0x44, 0xC6, 0x4D, 0x3A, 0x54, 0xD6, 0x49, 0xEE, 0xD3, 0x9B, 0x62, 0x7E, 0x23, 0xAB, 0x1C, 0x54, 0xE5, 0xAC, 0xB1, 0x37, 0xC6, 0x02, 0x53, 0x72, 0xDB, 0xA4, 0x27, 0xFE, 0x28, 0xF9, 0xA8, 0x32, 0x36, 0x78, 0x22, 0x95, 0x19, 0xE2, 0xFE, 0x25, 0x25, 0x1F, 0xB6, 0x5B, 0xCE, 0xB0, 0xDE, 0xA8, 0xD2, 0xFF, 0x3D, 0xB7, 0xC6, 0x19, 0x74, 0x67, 0x46, 0x14, 0xB7, 0xDA, 0x12, 0x92, 0xE4, 0x3C, 0xB7, 0x7D, 0x6E, 0x34, 0x45, 0x92, 0xDA, 0x27, 0xB9, 0xCD, 0x90, 0x8F, 0x4C, 0x5D, 0x88, 0x81, 0xDA, 0xCF, 0x4F, 0x45, 0x8F, 0xF5, 0xEF, 0x18, 0xA1, 0x70, 0xC8, 0xB5, 0xB5, 0xFE, 0x84, 0xBE, 0x47, 0x56, 0xA9, 0xDB, 0x1F, 0x3E, 0x59, 0xBC, 0x2B, 0x15, 0xE1, 0x7C, 0x45, 0x0F, 0xF5, 0x13, 0x38, 0x2A, 0x4F, 0x7E, 0x99, 0x64, 0xB0, 0x69, 0xDF, 0x46, 0x63, 0xCE, 0xD4, 0xAA, 0x57, 0x09, 0xE5, 0xD4, 0x31, 0x84, 0x00, 0x04, 0x1E, 0x3C, 0x19, 0x9E, 0xED, 0xAB, 0x01, 0xCD, 0x9E, 0x3E, 0x3E, 0x14, 0xD3, 0x62, 0x33, 0xC1, 0xB1, 0x94, 0x60, 0xB5, 0x78, 0xDF, 0x04, 0xE7, 0xB6, 0xFE, 0xCD, 0x71, 0xFF, 0x55, 0x1B, 0x26, 0x6A, 0x71, 0x93, 0x31, 0xB4, 0x30, 0x29, 0x6D, 0x18, 0x0A, 0x97, 0x52, 0xB8, 0x00, 0x29, 0xDD, 0x8D, 0x20, 0xBE, 0x42, 0xA2, 0xF3, 0x62, 0x87, 0x9C, 0x53, 0x19, 0xA4, 0x17, 0x30, 0x0C, 0x01, 0xE8, 0x6E, 0xEC, 0xD4, 0xC0, 0x79, 0x16, 0x78, 0xF0, 0xAE, 0x07, 0x1F, 0x60, 0x57, 0xCC, 0x9C, 0xAE, 0x81, 0x0D, 0x18, 0xB8, 0x40, 0x62, 0x61, 0x68, 0x0D, 0x23, 0x4C, 0x94, 0x40, 0x0C, 0x16, 0x0C, 0x55, 0x80, 0x29, 0xC1, 0x04, 0x5A, 0xBE, 0x88, 0xF9, 0x69, 0xC4, 0x26, 0xFD, 0xAE, 0xE2, 0x3D, 0x49, 0x5E, 0x4C, 0xC6, 0x7B, 0x7F, 0xF7, 0xFF, 0xAB, 0x97, 0xD8, 0x4C, 0xCA, 0xC1, 0x3D, 0xB0, 0xA0, 0x53, 0x68, 0x51, 0x5F, 0x29, 0x4A, 0x49, 0xF4, 0xB2, 0xD7, 0xD2, 0x7A, 0xDA, 0x56, 0x00, 0xDD, 0x57, 0xA0, 0x54, 0xAF, 0x44, 0x1D, 0xBF, 0x55, 0x24, 0x55, 0x98, 0xAE, 0x5C, 0xFE, 0x73, 0xC4, 0x1F, 0x1E, 0x0A, 0xFD, 0x92, 0x65, 0x83, 0xA1, 0xA0, 0x30, 0x42, 0xE1, 0x5F, 0xF9, 0xF4, 0xE0, 0x89, 0x96, 0x2E, 0x90, 0x87, 0x52, 0xF5, 0x6C, 0x55, 0xC5, 0x73, 0xE2, 0x3E, 0xBD, 0x0F, 0x75, 0x6F, 0xF6, 0xFF, 0xA0, 0xB7, 0xBF, 0x47, 0x8D, 0xA0, 0x2C, 0xBC, 0xFF, 0x16, 0x65, 0x46, 0xFB, 0xFE, 0x67, 0xDE, 0x87, 0xE0, 0xCE, 0xDA, 0xF4, 0x70, 0xB6, 0xDA, 0xD0, 0xB4, 0xC5, 0x40, 0xEE, 0x3D, 0xB3, 0x41, 0xDA, 0x2C, 0xBF, 0xA4, 0x20, 0x98, 0xAD, 0x80, 0xB8, 0xD9, 0x9D, 0x04, 0x49, 0x1F, 0x57, 0xBD, 0x4B, 0xAB, 0xA8, 0x56, 0xEB, 0xBB, 0x54, 0x74, 0xB1, 0x50, 0x5E, 0x5D, 0xF4, 0x7B, 0xBD, 0xFE, 0x1D, 0x84, 0x90, 0x3E, 0x5A, 0x36, 0xFE, 0x3D, 0x79, 0xEE, 0xA7, 0x50, 0x10, 0x8E, 0x2D, 0xA5, 0x75, 0x43, 0x5D, 0xAC, 0xDE, 0x4F, 0xCF, 0x42, 0xFA, 0xE5, 0xB8, 0x29, 0x01, 0x17, 0x20, 0x14, 0x61, 0x02, 0x50, 0xE0, 0x88, 0x01, 0x0E, 0x3A, 0xEC, 0xAC, 0x6A, 0xFD, 0xA7, 0x67, 0xD2, 0xC9, 0x2E, 0xCF, 0x5C, 0xEC, 0x6D, 0x72, 0x7B, 0xEC, 0x76, 0x2D, 0xB1, 0xAB, 0xE2, 0x39, 0x77, 0xE3, 0x65, 0x62, 0x26, 0xAA, 0x3E, 0xFB, 0x17, 0x00, 0xE3, 0x94, 0x49, 0x37, 0x63, 0x83, 0xC3, 0xA9, 0xEE, 0xFF, 0x8C, 0xA5, 0xF4, 0xB5, 0x52, 0x14, 0x2D, 0xCD, 0x8E, 0x7B, 0xFD, 0xBD, 0x09, 0x26, 0x87, 0xCB, 0x98, 0xF8, 0x4D, 0xBC, 0xEF, 0x6D, 0xFA, 0xA0, 0x38, 0x0F, 0xA3, 0xC0, 0xCB, 0x6F, 0xE3, 0xED, 0x97, 0x61, 0xEA, 0xB0, 0x25, 0x78, 0xF7, 0xFB, 0xEF, 0xB1, 0xD8, 0xBF, 0x7A, 0x7F, 0x96, 0xA2, 0xBF, 0x82, 0x10, 0x17, 0x5C, 0xEB, 0x6C, 0xCA, 0x66, 0xA2, 0x23, 0xE4, 0xE5, 0xA7, 0xC0, 0x15, 0x3F, 0x12, 0xF5, 0x67, 0xF9, 0xAF, 0xA5, 0xFE, 0xDB, 0x74, 0x36, 0x3E, 0x1D, 0xE7, 0x85, 0xFE, 0xA7, 0x27, 0xEA, 0x61, 0x2F, 0x6A, 0x5E, 0xD9, 0x22, 0x1A, 0x4C, 0xA6, 0x83, 0xBA, 0x9E, 0x15, 0x70, 0xA8, 0x4E, 0x70, 0x80, 0xCD, 0xB4, 0xE3, 0xDD, 0xA1, 0x79, 0x73, 0x2C, 0xD4, 0x1D, 0xF9, 0xB7, 0xD0, 0x47, 0xCE, 0x04, 0x86, 0xD4, 0x29, 0x63, 0xCB, 0xC0, 0x38, 0x4E, 0x19, 0x13, 0xE6, 0xFB, 0xFF, 0x07, 0xFC, 0x0A, 0x11, 0xE2, 0x43, 0x16, 0xC6, 0x32, 0x58, 0x95, 0x53, 0x86, 0xF7, 0x8C, 0xD3, 0xE9, 0x5B, 0x16, 0x02, 0x7F, 0x0A, 0xD0, 0xFF, 0x09, 0xD3, 0x17, 0x2C, 0x53, 0xE7, 0x00, 0xEC, 0x1E, 0x76, 0x8F, 0x15, 0x8F, 0x1E, 0x26, 0xAF, 0xA7, 0x3E, 0x75, 0x9B, 0x83, 0xC3, 0x54, 0x9E, 0x7F, 0x1F, 0x82, 0xBC, 0x84, 0x87, 0x08, 0x56, 0x90, 0x34, 0x3E, 0xE8, 0x67, 0x58, 0x2E, 0x4F, 0xCE, 0x23, 0x99, 0xA5, 0xFB, 0xBF, 0x94, 0x11, 0x46, 0x18, 0xD1, 0x49, 0x1C, 0x87, 0x43, 0xE2, 0x08, 0x0F, 0x08, 0xD5, 0x25, 0x96, 0x58, 0x82, 0xDA, 0x7A, 0x2B, 0xCE, 0xEA, 0x7D, 0x93, 0x20, 0x1C, 0xFE, 0x99, 0x4F, 0x5D, 0xF8, 0xB7, 0x98, 0x4A, 0x7E, 0x64, 0x4A, 0x4B, 0xB7, 0x87, 0xC9, 0x17, 0x1B, 0x97, 0x22, 0x22, 0x6B, 0x8C, 0x5C, 0xCD, 0x4C, 0x5E, 0x86, 0xDD, 0x94, 0xE9, 0x0B, 0x6E, 0xDB, 0x84, 0x44, 0xA1, 0x20, 0x2F, 0xB8, 0x2D, 0xA5, 0xF2, 0xE2, 0xD2, 0x4F, 0x6F, 0x68, 0x7A, 0x77, 0x5E, 0x89, 0xDE, 0xB2, 0x5F, 0x08, 0x93, 0x67, 0xFA, 0xE2, 0xA5, 0xA7, 0xE7, 0x44, 0x0C, 0x67, 0x6D, 0x12, 0x86, 0xD9, 0xFB, 0x58, 0xF2, 0xB2, 0xEA, 0xFC, 0x8F, 0x1C, 0xF9, 0x4D, 0x73, 0xFC, 0xB7, 0x22, 0x77, 0xD3, 0xC8, 0xB7, 0x1F, 0xC2, 0xBD, 0xEA, 0x5C, 0x13, 0x91, 0xE9, 0xE5, 0xE4, 0x68, 0x3B, 0x18, 0x47, 0xA9, 0xE9, 0xD3, 0x78, 0x38, 0x6D, 0xC8, 0x96, 0xD9, 0x74, 0xC1, 0x59, 0x9E, 0x78, 0x53, 0x17, 0x6C, 0x15, 0xB3, 0x29, 0x35, 0xC7, 0xB8, 0x14, 0xBB, 0x36, 0x28, 0x6A, 0xF2, 0x5A, 0x56, 0xDB, 0x0B, 0xCA, 0xA5, 0x4C, 0xBA, 0xE0, 0x6A, 0x6A, 0x02, 0xA5, 0x68, 0xD0, 0xD5, 0x54, 0x4F, 0x35, 0xC6, 0xCD, 0x2C, 0xC3, 0xB8, 0xA6, 0x9A, 0xE2, 0xE1, 0x47, 0xC1, 0x52, 0xAD, 0x72, 0x7A, 0x0E, 0x22, 0x19, 0x82, 0xFD, 0x4B, 0xFA, 0xFF, 0x58, 0x8C, 0x37, 0x51, 0x41, 0x7D, 0x74, 0x2A, 0xB2, 0x6C, 0x94, 0x16, 0xED, 0x89, 0x0C, 0x7F, 0x5F, 0xF2, 0xC6, 0x2E, 0x1D, 0x15, 0xBB, 0x5E, 0x73, 0x4D, 0x98, 0xAB, 0xA3, 0xE4, 0xF2, 0xED, 0xA4, 0x5E, 0x0C, 0x19, 0x13, 0xAA, 0x72, 0x16, 0xBA, 0x71, 0xE7, 0x8A, 0x89, 0x11, 0xBF, 0x9D, 0x50, 0xEE, 0x40, 0xE7, 0x98, 0xB6, 0x68, 0x5E, 0xD6, 0xD1, 0x97, 0xC8, 0xFA, 0xE2, 0x52, 0x45, 0x28, 0xD9, 0x4F, 0x4B, 0x2A, 0x54, 0x04, 0xC3, 0xD2, 0xF2, 0xF3, 0x17, 0x7D, 0x5C, 0xE5, 0xF8, 0x27, 0x25, 0x54, 0xC4, 0x4B, 0x94, 0x5C, 0x74, 0x8D, 0x8B, 0xAF, 0xAC, 0x94, 0x8D, 0xA6, 0x41, 0xFC, 0x11, 0x79, 0x64, 0x95, 0x3F, 0xE8, 0x23, 0x2A, 0x55, 0x22, 0xAF, 0xA3, 0x24, 0xE7, 0x56, 0xDB, 0xA8, 0x5B, 0x6B, 0x5F, 0xE5, 0x4F, 0xBE, 0xBD, 0xB7, 0x97, 0xEE, 0xC7, 0xC5, 0x51, 0x3D, 0x2B, 0x81, 0x08, 0xDC, 0x3F, 0x7E, 0xCD, 0x3B, 0x95, 0x3F, 0xFC, 0x81, 0x33, 0x9A, 0x30, 0x33, 0x5B, 0x77, 0x5D, 0x97, 0x89, 0x5E, 0xD9, 0xC7, 0xCD, 0xAF, 0x54, 0xAE, 0x78, 0xFD, 0x57, 0x3D, 0xF9, 0x3E, 0x2E, 0xC6, 0x67, 0xD8, 0x8B, 0xC5, 0xE2, 0x7D, 0x67, 0x6F, 0x34, 0xBD, 0xAF, 0x70, 0x25, 0x15, 0x1A, 0x27, 0x18, 0x92, 0x13, 0xD5, 0x20, 0xD1, 0x49, 0xB7, 0xFE, 0xDB, 0xAB, 0x68, 0xDD, 0x18, 0xE3, 0xAF, 0xD1, 0x8B, 0x21, 0x8A, 0xCB, 0x4A, 0x63, 0xE4, 0x8D, 0xEA, 0x0F, 0x6E, 0x79, 0x24, 0xA2, 0x28, 0x6E, 0xD5, 0xD1, 0xC5, 0xF7, 0xAC, 0x5A, 0xCF, 0x05, 0xC8, 0x7F, 0xBF, 0xA3, 0x46, 0xB4, 0xF7, 0xBB, 0x74, 0x2F, 0x27, 0xB5, 0xD4, 0xD3, 0xD7, 0x30, 0x76, 0xCA, 0xA8, 0x31, 0x06, 0x13, 0x60, 0x18, 0xA0, 0x17, 0xA0, 0x50, 0xA2, 0x01, 0x1C, 0x50, 0x21, 0x00, 0x92, 0x93, 0xFC, 0x9F, 0xF1, 0x5F, 0x42, 0x4B, 0x74, 0x91, 0x25, 0x43, 0x83, 0x84, 0xFD, 0x97, 0xFC, 0x47, 0x26, 0xAB, 0xB3, 0xDC, 0x60, 0x5B, 0xAA, 0x8F, 0xE2, 0x8E, 0x32, 0x36, 0x1E, 0x7B, 0x43, 0x1E, 0x6A, 0x76, 0x77, 0xB6, 0xDC, 0x04, 0x03, 0x79, 0xAA, 0x41, 0xCD, 0x9B, 0x39, 0x73, 0x2D, 0xD0, 0x27, 0x9F, 0xB8, 0xA9, 0x53, 0x79, 0xC2, 0x24, 0x73, 0x4E, 0xB3, 0xE9, 0x21, 0x79, 0x62, 0xFE, 0xC4, 0xED, 0xD9, 0xCF, 0xEC, 0x21, 0x68, 0xF3, 0x1B, 0x99, 0x0D, 0x04, 0x55, 0x4B, 0x9E, 0x68, 0xBA, 0x82, 0xE6, 0xF5, 0x6F, 0x8C, 0x5E, 0x35, 0x73, 0xAD, 0xEF, 0x9B, 0xCF, 0xE3, 0xDD, 0x0C, 0x08, 0x1A, 0x0F, 0xCC, 0x7A, 0xE0, 0x84, 0x39, 0x49, 0xC6, 0x35, 0x86, 0xC8, 0x29, 0xD3, 0x83, 0x21, 0x92, 0x7A, 0xBF, 0xF7, 0x4E, 0xF3, 0x3B, 0xD5, 0xEA, 0x99, 0x52, 0xDD, 0x2D, 0x2F, 0x47, 0xED, 0x03, 0xB2, 0xAA, 0x9A, 0x1C, 0x20, 0x7B, 0x95, 0x4E, 0x8E, 0xBD, 0x71, 0xE3, 0xE7, 0xF0, 0x79, 0xB0, 0xB3, 0xFC, 0xDE, 0x48, 0xA6, 0x2A, 0x72, 0xE7, 0x2C, 0x72, 0x69, 0x37, 0x66, 0xEA, 0x15, 0x2B, 0xA2, 0xEC, 0xFF, 0x9F, 0xDE, 0x05, 0xE2, 0x52, 0x45, 0xB3, 0x69, 0x47, 0x58, 0x57, 0xF9, 0x6C, 0xB3, 0x5F, 0xE2, 0x28, 0xC7, 0x36, 0xDE, 0x55, 0x29, 0x71, 0xA6, 0xAE, 0x74, 0x35, 0xEF, 0xEA, 0xAE, 0xFB, 0xF0, 0x72, 0x47, 0xA3, 0xCB, 0xA0, 0x87, 0xFE, 0x4E, 0x94, 0x15, 0x61, 0xB5, 0xD4, 0xBC, 0x1B, 0xE7, 0x92, 0xF1, 0xCD, 0x24, 0xAE, 0xDE, 0xAA, 0x24, 0x05, 0x52, 0x49, 0x0A, 0xB0, 0x7E, 0x84, 0x8C, 0x35, 0xE0, 0xA4, 0x6A, 0x5E, 0x7B, 0x22, 0x64, 0xFA, 0x43, 0x27, 0xD5, 0xFD, 0x3A, 0x7C, 0x4C, 0xA0, 0x51, 0xAD, 0x3F, 0xF1, 0x3F, 0xD2, 0xE3, 0x6C, 0xD0, 0xBD, 0x09, 0xCF, 0x88, 0x19, 0x8B, 0x03, 0xFD, 0xB3, 0xAE, 0x6A, 0x1B, 0x3C, 0x8B, 0x30, 0x5C, 0x63, 0x42, 0x5E, 0xFA, 0x4B, 0x91, 0xA9, 0xCA, 0x77, 0xD3, 0x40, 0xEE, 0xDD, 0xC9, 0x3E, 0x42, 0x9A, 0xC3, 0x9D, 0x26, 0x17, 0x67, 0xDB, 0x7D, 0x7F, 0x81, 0x13, 0x8C, 0xB3, 0xD4, 0xC8, 0x9D, 0x4E, 0xEA, 0x48, 0xAD, 0x71, 0x24, 0x72, 0x1D, 0x89, 0xD9, 0x37, 0x12, 0x53, 0x3F, 0xFE, 0x3B, 0x32, 0x3B, 0xCE, 0xEE, 0x85, 0x5F, 0x1B, 0x42, 0x58, 0x62, 0x78, 0x42, 0xE1, 0x0F, 0x49, 0x92, 0x21, 0x7C, 0x43, 0xFB, 0xFB, 0x6A, 0x6F, 0x63, 0xDB, 0xA6, 0xF2, 0x5A, 0x1D, 0x95, 0x46, 0xEB, 0x51, 0xC6, 0x7E, 0x5F, 0xDD, 0xFA, 0xDF, 0x6A, 0x79, 0xB9, 0x8C, 0xFD, 0x8F, 0x40, 0x9B, 0x81, 0x4A, 0xCE, 0x55, 0x8E, 0xBA, 0x2A, 0x86, 0x1E, 0x0B, 0x72, 0x87, 0x03, 0x8C, 0x57, 0xFD, 0x2D, 0x20, 0xD0, 0xE1, 0x4B, 0x7D, 0x04, 0xEA, 0xBB, 0x1D, 0x91, 0x37, 0xB0, 0x06, 0xFB, 0xC8, 0x42, 0x2F, 0x49, 0x12, 0xAF, 0x13, 0x4A, 0x47, 0xA8, 0x27, 0x14, 0x3F, 0x9F, 0x58, 0x3D, 0x1E, 0x4F, 0x27, 0x5E, 0xD4, 0xEB, 0x6A, 0x68, 0x66, 0xE4, 0x9B, 0x14, 0xB3, 0xC3, 0x92, 0xB8, 0x54, 0x12, 0x5D, 0x45, 0xD2, 0x49, 0x27, 0xA9, 0x82, 0x47, 0xCC, 0x0E, 0x9B, 0x23, 0xD6, 0x78, 0x71, 0xA9, 0x2B, 0xA5, 0xAB, 0x8A, 0x1A, 0x94, 0x24, 0x55, 0x05, 0x16, 0x6B, 0x54, 0x11, 0xE5, 0x6B, 0x9C, 0xE4, 0xC4, 0x5E, 0xB6, 0x25, 0x3F, 0x62, 0x2C, 0x89, 0xD1, 0xD6, 0xF8, 0xE3, 0xB1, 0xE4, 0x48, 0x68, 0xEB, 0xF9, 0x70, 0x14, 0xC2, 0x1C, 0xB7, 0x7A, 0x9E, 0x17, 0x45, 0x22, 0x7B, 0x29, 0xB4, 0x1E, 0x15, 0xCF, 0x8C, 0x59, 0x63, 0xEA, 0x29, 0xB4, 0x31, 0x48, 0xB2, 0x4F, 0x34, 0x45, 0x5A, 0x27, 0xD2, 0xBC, 0x19, 0x99, 0x57, 0x33, 0xBB, 0x28, 0x85, 0xC9, 0x7E, 0x42, 0x41, 0x40, 0x5F, 0x9C, 0x53, 0x8C, 0xA2, 0x17, 0x33, 0x29, 0x46, 0x91, 0x7E, 0x31, 0xD6, 0x30, 0x15, 0x35, 0xA3, 0x18, 0xFF, 0xF8, 0xCA, 0xE3, 0x54, 0xE6, 0xF7, 0x29, 0x1E, 0x3F, 0xF3, 0x78, 0x85, 0x27, 0x83, 0x33, 0x9D, 0x27, 0xD2, 0xAE, 0xE3, 0x89, 0xEF, 0xEC, 0xCF, 0xC9, 0xB8, 0x47, 0x42, 0x42, 0x22, 0x63, 0x52, 0xEA, 0x46, 0x38, 0x23, 0x7A, 0xD1, 0x8A, 0x49, 0x96, 0x8C, 0xFF, 0xFD, 0x96, 0x92, 0xA5, 0x55, 0x33, 0xA8, 0xEA, 0x7D, 0xA8, 0x28, 0x70, 0x5C, 0x37, 0xCA, 0xD2, 0xC4, 0xFC, 0x42, 0xC5, 0xC3, 0xB1, 0xFD, 0x3C, 0xF7, 0x21, 0x93, 0xD7, 0xA9, 0xBA, 0xA1, 0xEC, 0x1B, 0x9A, 0xDE, 0x59, 0xFA, 0x33, 0x7D, 0xA8, 0x6C, 0x9E, 0xF0, 0xDF, 0xE1, 0x5C, 0x8D, 0x5A, 0x33, 0x33, 0xCE, 0x3B, 0x38, 0xA0, 0x19, 0x3A, 0x8E, 0xE5, 0xDC, 0x29, 0x6F, 0xE3, 0xCD, 0x88, 0xBF, 0xD9, 0x7B, 0x35, 0x23, 0x49, 0x2D, 0x49, 0x32, 0x1C, 0x92, 0x49, 0xC2, 0x5E, 0xB4, 0x7A, 0xD0, 0xD1, 0x20, 0x0E, 0x9D, 0xCE, 0xD9, 0xC5, 0x83, 0x42, 0xE2, 0xD5, 0x81, 0x86, 0x17, 0xC8, 0x59, 0x91, 0x40, 0x08, 0x70, 0x20, 0x03, 0x16, 0x50, 0x00, 0x03, 0x7C, 0x20, 0x7C, 0x7F, 0x93, 0x57, 0x13, 0xC5, 0xFC, 0x66, 0x00, 0xBD, 0xD1, 0x5F, 0x90, 0xC5, 0x63, 0xAC, 0x1E, 0x5B, 0x7F, 0x11, 0xE7, 0x19, 0x1F, 0x20, 0x98, 0xA0, 0x8C, 0x20, 0x70, 0x31, 0x6A, 0x00, 0x15, 0x04, 0xE0, 0x40, 0x0E, 0x7E, 0x20, 0x03, 0x1B, 0x36, 0xA0, 0x27, 0xEE, 0xC4, 0x9D, 0x4E, 0x14, 0x85, 0xE2, 0x50, 0x9F, 0x8F, 0x0A, 0xA8, 0x3F, 0xAF, 0xC9, 0xF4, 0x33, 0xBB, 0x54, 0xA6, 0xF3, 0xA6, 0x8E, 0x99, 0x5F, 0x84, 0x12, 0xCF, 0xEA, 0x0A, 0xF4, 0xE0, 0x8F, 0xAB, 0x5B, 0x5F, 0x42, 0xB7, 0x62, 0x42, 0x10, 0x70, 0x28, 0xA1, 0x04, 0x00, 0x68, 0xC2, 0x02, 0xC2, 0x10, 0x01, 0x0E, 0xD2, 0x80, 0x06, 0x29, 0x18, 0x00, 0xC4, 0x6F, 0x30, 0x0E, 0x8E, 0x13, 0x38, 0x12, 0x37, 0x18, 0xC7, 0x53, 0xF5, 0xC5, 0x45, 0xAA, 0xC8, 0x5A, 0xA9, 0x88, 0x92, 0xA3, 0x75, 0xE3, 0x64, 0xFD, 0xAA, 0xA4, 0x92, 0x4B, 0xAD, 0x2E, 0x7B, 0x50, 0x9D, 0xCD, 0x20, 0xAD, 0xBB, 0x10, 0x9C, 0x1E, 0x26, 0xE3, 0x79, 0x3C, 0x6B, 0x7E, 0x7D, 0x6A, 0xB5, 0x0E, 0x4C, 0x75, 0x0D, 0xB4, 0x2C, 0x8B, 0xFE, 0xA7, 0x2D, 0xF8, 0xF1, 0x39, 0xF4, 0x4E, 0x8C, 0xBF, 0xF6, 0x57, 0xBA, 0x4B, 0xFE, 0xBF, 0x89, 0x6E, 0xC5, 0x07, 0x3E, 0x78, 0x20, 0xE1, 0x07, 0x33, 0x90, 0xC0, 0x1B, 0x34, 0x00, 0x44, 0xC3, 0x46, 0xB6, 0x01, 0x94, 0x22, 0x48, 0x4E, 0xF2, 0x5F, 0x5E, 0xCE, 0xF8, 0x0C, 0x0B, 0xDE, 0xA2, 0xD1, 0xC4, 0x2F, 0xAF, 0x5F, 0xA2, 0xAB, 0xDE, 0x60, 0x1C, 0xFD, 0x4D, 0x44, 0xFD, 0x44, 0x2B, 0x95, 0xDD, 0x60, 0xDE, 0x9D, 0x37, 0x52, 0xC4, 0x26, 0x54, 0x44, 0x41, 0x54, 0xE5, 0x68, 0x0B, 0xEB, 0x3E, 0x54, 0x41, 0x14, 0x07, 0x18, 0x6C, 0x8A, 0xF6, 0x41, 0x48, 0x5A, 0xEF, 0x56, 0xAF, 0xE7, 0xDA, 0x78, 0x28, 0x78, 0xEE, 0xDF, 0x2F, 0xFE, 0x7F, 0x06, 0xF3, 0x67, 0x4D, 0xA2, 0x11, 0x88, 0x9B, 0x16, 0x28, 0xC5, 0x8D, 0xF4, 0x78, 0x19, 0xBF, 0x68, 0x7B, 0x5C, 0x5E, 0x5C, 0xAA, 0x0A, 0x49, 0xF5, 0x46, 0x23, 0x2B, 0x22, 0xC9, 0x88, 0x7A, 0x23, 0x20, 0x92, 0xEB, 0x8D, 0x6A, 0x06, 0xC6, 0xB9, 0x9F, 0x6F, 0x2F, 0x2E, 0x2E, 0x23, 0xFF, 0x7E, 0x47, 0x57, 0x19, 0x49, 0xFD, 0xC0, 0x46, 0x71, 0x44, 0x25, 0xAE, 0x62, 0xDC, 0x91, 0xA3, 0xAF, 0x62, 0x09, 0x5A, 0xB6, 0x7A, 0x56, 0xFD, 0xED, 0x09, 0x15, 0x5D, 0xD2, 0x9E, 0x7F, 0x33, 0xC1, 0xAC, 0x5C, 0x0D, 0x88, 0xE7, 0xD2, 0xA7, 0x3E, 0x7B, 0x9A, 0x54, 0xBA, 0x2C, 0x26, 0xEF, 0xB5, 0x8B, 0xDB, 0xE7, 0x49, 0x3D, 0xE7, 0xE5, 0xF2, 0x8C, 0xD4, 0xFF, 0xE7, 0x91, 0xC7, 0x80, 0xB8, 0x53, 0xFA, 0x21, 0x1C, 0x4C, 0x4C, 0x31, 0xDC, 0xD8, 0xAC, 0x04, 0x2A, 0x89, 0xBF, 0x9C, 0xC9, 0xB3, 0xC9, 0xE1, 0xBA, 0x6B, 0xDD, 0x50, 0x36, 0x3D, 0xEE, 0xFF, 0xAF, 0xD7, 0x59, 0xCE, 0x35, 0x69, 0x3F, 0x2F, 0xA3, 0xAD, 0x3C, 0xE6, 0xDD, 0x24, 0xFE, 0x12, 0x3B, 0xCD, 0xB5, 0x6E, 0x94, 0x05, 0x89, 0x46, 0xFF, 0x7F, 0xEF, 0xF0, 0x79, 0xE0, 0xFA, 0x4B, 0x3F, 0xEE, 0xB5, 0xFB, 0x86, 0xEC, 0x90, 0x4B, 0xBD, 0xFB, 0x3C, 0x98, 0xBE, 0x1C, 0xEE, 0x54, 0xC8, 0xC7, 0xC6, 0x7E, 0x43, 0xDA, 0x4B, 0xF7, 0x4A, 0xA4, 0x77, 0x51, 0x72, 0x29, 0x79, 0x44, 0xD6, 0x08, 0x3D, 0x0B, 0x56, 0xBE, 0xA8, 0xBB, 0xB1, 0x02, 0x8F, 0x77, 0x88, 0xC0, 0x3C, 0x28, 0xE1, 0x1E, 0x41, 0xDC, 0x40, 0x30, 0x12, 0x34, 0x38, 0x02, 0x3F, 0x1D, 0x3B, 0x00, 0x08, 0x9C, 0xF8, 0xB7, 0x54, 0x36, 0xA2, 0xCB, 0x71, 0x46, 0xC1, 0xD9, 0x04, 0x48, 0x89, 0x19, 0xBE, 0x6E, 0x8B, 0x12, 0x98, 0x84, 0x77, 0x24, 0x00, 0xC9, 0x71, 0xC2, 0xBA, 0x35, 0xE3, 0x87, 0xFE, 0x83, 0xF4, 0xA8, 0xA9, 0x55, 0x62, 0x06, 0x76, 0xE9, 0xDE, 0x07, 0xE1, 0x07, 0xE9, 0x7B, 0x6E, 0xD1, 0x3F, 0x83, 0x1A, 0xD6, 0xDA, 0x20, 0xF9, 0x5C, 0x55, 0xA2, 0xE6, 0x32, 0x35, 0xF1, 0xE1, 0x39, 0x83, 0xEE, 0x52, 0xA2, 0xC6, 0xE2, 0xB6, 0x97, 0xA6, 0x84, 0x77, 0x65, 0x42, 0x3A, 0xB2, 0x8D, 0x77, 0x4D, 0x22, 0x33, 0x7C, 0x13, 0xEF, 0xBA, 0x49, 0xC8, 0x0C, 0xD7, 0xAA, 0xEF, 0xEA, 0x6E, 0xF6, 0xC5, 0xD5, 0xA1, 0x8E, 0xD6, 0xD1, 0xF3, 0xD0, 0x44, 0xF6, 0xF0, 0xFF, 0x5F, 0xB8, 0xE4, 0x3D, 0xFC, 0xEF, 0xBF, 0xB2, 0xE9, 0x27, 0x5A, 0xFD, 0x26, 0xAC, 0x4B, 0x44, 0x25, 0x49, 0x49, 0x09, 0xDA, 0x12, 0xC9, 0xB5, 0xB4, 0x02, 0xBD, 0x7B, 0x6A, 0xDD, 0xFE, 0x12, 0x91, 0xC8, 0xF8, 0x5F, 0xA2, 0x38, 0xAA, 0xD9, 0x52, 0x34, 0xCE, 0x33, 0x68, 0xB6, 0x53, 0x8B, 0xE6, 0x87, 0x1A, 0x8E, 0x55, 0x74, 0xD4, 0x95, 0x49, 0xA9, 0x49, 0xF2, 0x8A, 0x6C, 0x71, 0x2F, 0xB6, 0xB0, 0x7D, 0x69, 0xFD, 0x9A, 0x48, 0xC3, 0xBF, 0x5E, 0x5E, 0x6C, 0x41, 0x7F, 0x93, 0x10, 0xEF, 0xD2, 0xFA, 0x57, 0xD6, 0x16, 0xFF, 0xD7, 0x8F, 0x7D, 0xBF, 0xCB, 0xC5, 0x3C, 0x2E, 0x10, 0x31, 0x17, 0x10, 0x96, 0x6F, 0xAD, 0x15, 0x73, 0x05, 0xCD, 0xE9, 0xFF, 0xAF, 0xBA, 0x78, 0xE2, 0x5D, 0x4C, 0xF4, 0xF1, 0x18, 0x2B, 0x15, 0xD4, 0x47, 0x6C, 0xCE, 0x56, 0xFA, 0xD5, 0x59, 0x48, 0xAF, 0xBA, 0xA6, 0xD0, 0x5C, 0x2A, 0x64, 0xE6, 0x76, 0x4D, 0xCA, 0x87, 0x15, 0xAB, 0x08, 0x1F, 0xF4, 0xA3, 0xF3, 0x85, 0xA2, 0x4A, 0xA5, 0x7A, 0x0D, 0x1B, 0xAA, 0x1A, 0x24, 0x15, 0xFC, 0xF9, 0x63, 0xC8, 0x47, 0xA6, 0x29, 0xDD, 0x29, 0x2C, 0x9E, 0x66, 0x04, 0x74, 0x12, 0xEC, 0x95, 0x98, 0x12, 0xB5, 0x88, 0x53, 0xF0, 0x22, 0x48, 0xFA, 0x59, 0xE8, 0x75, 0x42, 0x2F, 0xA6, 0xD7, 0xFF, 0xEB, 0x38, 0x64, 0x39, 0x3C, 0x39, 0xE4, 0x43, 0x1D, 0x22, 0x16, 0xD1, 0x89, 0x88, 0x75, 0x48, 0xEB, 0x24, 0x5A, 0x6C, 0x53, 0xA7, 0x69, 0xDA, 0x33, 0x6D, 0xA1, 0x2D, 0xC2, 0x5D, 0xD3, 0xB5, 0x56, 0xBE, 0xC8, 0x16, 0xE9, 0x77, 0x9B, 0x60, 0xE4, 0xCB, 0xB3, 0x25, 0x5E, 0x3F, 0x61, 0xE2, 0xE8, 0x24, 0x97, 0xDB, 0xDA, 0x90, 0xD4, 0x7D, 0x13, 0x0C, 0x0D, 0xB6, 0x56, 0x55, 0xE1, 0x6A, 0x30, 0x4D, 0x9C, 0x3E, 0xBF, 0xF8, 0x37, 0xD4, 0x7F, 0x3B, 0xE4, 0x74, 0x1B, 0x93, 0x2E, 0x4E, 0xAF, 0xF6, 0x1D, 0x8A, 0xCB, 0x6C, 0x50, 0x8C, 0x31, 0x88, 0xA3, 0x32, 0x33, 0xE7, 0xF4, 0x8E, 0xF7, 0x7E, 0xB0, 0xEE, 0x66, 0x4C, 0x88, 0xDB, 0xDC, 0x91, 0xDC, 0x94, 0xA7, 0x4F, 0x9C, 0x4D, 0x5B, 0x62, 0x76, 0xDB, 0xA7, 0xCE, 0xA4, 0x01, 0xFA, 0x76, 0xCA, 0xCD, 0x6E, 0x00, 0x90, 0x85, 0x05, 0x59, 0xD3, 0xFB, 0x2C, 0xF9, 0xFF, 0x9C, 0xA9, 0xE7, 0xD2, 0x3A, 0x07, 0x75, 0x5D, 0xFD, 0xCF, 0x6B, 0x1D, 0x89, 0x0D, 0x4B, 0x0E, 0xAC, 0x19, 0x2C, 0x59, 0x34, 0x48, 0x15, 0x01, 0x48, 0xFF, 0x55, 0x76, 0x5F, 0xB4, 0x8C, 0xA8, 0x40, 0x0B, 0x1A, 0x88, 0xC1, 0x11, 0x02, 0xF8, 0xC1, 0x05, 0x65, 0x98, 0xEC, 0xC8, 0x46, 0x50, 0xC2, 0x82, 0xA7, 0x87, 0xC7, 0xC3, 0x41, 0x55, 0xCA, 0xE1, 0x74, 0x33, 0x28, 0x8E, 0xD2, 0x0D, 0x8B, 0x1D, 0xA8, 0x6A, 0xEF, 0x76, 0x8A, 0xAA, 0x9B, 0xF6, 0x71, 0x74, 0xAB, 0x2F, 0xC9, 0x6E, 0xF4, 0x0F, 0xFD, 0x3A, 0x9F, 0x8B, 0x88, 0x30, 0x78, 0xD1, 0x85, 0xC1, 0x28, 0xE3, 0x48, 0x5E, 0xD6, 0xE5, 0x6D, 0x1B, 0xEC, 0xFE, 0xDD, 0x8B, 0x5C, 0xA3, 0xA7, 0x4C, 0x78, 0xF8, 0x37, 0x1F, 0x61, 0x1B, 0x8F, 0xFF, 0x72, 0xDA, 0x4B, 0xF8, 0x3B, 0xBE, 0xAD, 0xA3, 0xDA, 0x35, 0x60, 0xC4, 0xB4, 0x01, 0x57, 0xAD, 0xE7, 0x31, 0xAE, 0xD3, 0xEF, 0x0D, 0x7C, 0xCF, 0x0D, 0xF4, 0xDD, 0xE9, 0xAD, 0xD7, 0xDD, 0x7D, 0x76, 0x17, 0x4A, 0x7A, 0xC4, 0x75, 0x34, 0xCB, 0xC9, 0x4F, 0xA7, 0xD3, 0xE9, 0x74, 0x3A, 0x9D, 0xC8, 0x0C, 0xEA, 0x29, 0x34, 0x27, 0x2B, 0xA4, 0xB9, 0x54, 0x15, 0x32, 0xB3, 0x31, 0x15, 0x42, 0xB3, 0x3D, 0x88, 0x3C, 0x5D, 0x48, 0x0D, 0xF7, 0x14, 0x79, 0x32, 0xF5, 0x48, 0x9D, 0xA4, 0x3A, 0x9E, 0x4E, 0xF8, 0xE4, 0x2A, 0xA0, 0xD3, 0xC9, 0x27, 0xCE, 0x89, 0x88, 0x14, 0x11, 0x3C, 0x85, 0x4E, 0x44, 0x5C, 0xA5, 0x73, 0x22, 0xE2, 0xD3, 0xE7, 0x14, 0x80, 0x54, 0x00, 0x78, 0xFA, 0x6C, 0x01, 0x08, 0xC0, 0xA7, 0xD0, 0x96, 0x11, 0xF1, 0x81, 0x92, 0x9A, 0x41, 0xA4, 0x40, 0x61, 0x6B, 0xA5, 0x50, 0x5A, 0x65, 0x07, 0xA7, 0x05, 0x71, 0x02, 0xE5, 0x38, 0x3B, 0x38, 0x53, 0x10, 0x1F, 0x03, 0x48, 0x69, 0x44, 0xA4, 0x18, 0x00, 0x07, 0x49, 0x0D, 0xA0, 0x53, 0x82, 0xB8, 0x29, 0xC4, 0x89, 0x01, 0x3C, 0x48, 0x10, 0x97, 0x09, 0xF1, 0x21, 0x44, 0x2A, 0xC5, 0x72, 0x93, 0x88, 0x14, 0x42, 0x70, 0x10, 0xCB, 0x99, 0x88, 0xA4, 0x84, 0xE8, 0x14, 0x7B, 0x35, 0x21, 0x4E, 0x08, 0xF1, 0x20, 0xF6, 0x66, 0x7D, 0xFA, 0x4F, 0x04, 0xA9, 0x4D, 0xDE, 0xCB, 0xD5, 0xB2, 0xF9, 0x11, 0x91, 0x73, 0xB5, 0x16, 0xE2, 0xE7, 0xD7, 0xC8, 0xB6, 0xEB, 0xBA, 0xEE, 0xFB, 0xBE, 0x5B, 0xE3, 0xF9, 0xF5, 0xF4, 0xC4, 0x02, 0x49, 0xFD, 0x40, 0x2B, 0x10, 0x27, 0x7E, 0x38, 0x49, 0xFD, 0x60, 0x1B, 0x08, 0x8F, 0x98, 0x1D, 0x36, 0x07, 0x77, 0x02, 0xC4, 0xA5, 0xAE, 0x94, 0x80, 0x9C, 0x50, 0x20, 0x18, 0x77, 0xF2, 0x0B, 0x24, 0x9E, 0x9C, 0xEB, 0x68, 0xC7, 0x73, 0xDA, 0xD1, 0x0E, 0xE7, 0xB4, 0xA3, 0x34, 0xDC, 0xCD, 0xD0, 0xFE, 0x2D, 0xBD, 0xB9, 0xD9, 0xC1, 0x28, 0x96, 0x8C, 0x97, 0xF0, 0xFF, 0x3E, 0x8A, 0xE5, 0xBF, 0x8A, 0x12, 0x55, 0xF6, 0xFD, 0x6D, 0x63, 0xF4, 0xD4, 0x31, 0x11, 0xCF, 0x2B, 0x8C, 0x9C, 0x82, 0xC2, 0x44, 0xA0, 0x7B, 0x98, 0x4F, 0x71, 0x3E, 0xF7, 0xBF, 0xC8, 0x14, 0x3E, 0x88, 0x31, 0x05, 0x15, 0x63, 0x4C, 0x91, 0x05, 0x35, 0xF6, 0x21, 0x7E, 0x9A, 0xBA, 0xE2, 0x8A, 0x2B, 0xA6, 0x98, 0x62, 0x8A, 0x5B, 0x33, 0xF7, 0x6F, 0x93, 0x4F, 0x61, 0xE3, 0x59, 0xF4, 0xA2, 0xE6, 0xEE, 0xB9, 0x89, 0xD0, 0x0F, 0x3E, 0xBF, 0x9C, 0xE9, 0x81, 0x0F, 0x6C, 0x2F, 0x88, 0x50, 0x92, 0xD3, 0xB7, 0x7A, 0x71, 0x84, 0xBB, 0x6C, 0xFD, 0xD2, 0xCC, 0xE5, 0xDE, 0x74, 0xA8, 0x4D, 0x2C, 0xA0, 0xE1, 0x7E, 0x7A, 0x6B, 0xA6, 0x04, 0xCD, 0xFD, 0xEF, 0x4D, 0x44, 0x54, 0xB8, 0x6A, 0xD3, 0x75, 0x2F, 0x77, 0xF9, 0x6D, 0xFD, 0x14, 0xB2, 0xC2, 0x59, 0x1E, 0x4C, 0xB2, 0xD7, 0x84, 0xFC, 0xE0, 0x6E, 0xBA, 0x71, 0x07, 0x34, 0x22, 0x2B, 0xEE, 0x53, 0x4E, 0xDE, 0x78, 0x5F, 0x7F, 0xB3, 0xCF, 0x14, 0xF4, 0x48, 0xBD, 0x38, 0xE1, 0xAB, 0xBC, 0x45, 0x2F, 0x9A, 0x5F, 0x33, 0x6D, 0xEB, 0x7D, 0x4B, 0x99, 0x2D, 0xAE, 0xCD, 0x16, 0x33, 0x6D, 0x8B, 0xD9, 0x7A, 0xFF, 0x56, 0x4A, 0x9E, 0x2B, 0xDE, 0xE7, 0x56, 0x4A, 0x9C, 0x2B, 0x5E, 0xE7, 0x72, 0x26, 0x92, 0x6A, 0x7E, 0xFB, 0xEB, 0xC4, 0x14, 0xBD, 0x7F, 0x4E, 0xC7, 0xC8, 0x70, 0xE3, 0xC8, 0xA8, 0xBC, 0xFB, 0xAF, 0x66, 0x48, 0xCD, 0xB6, 0x45, 0x40, 0x28, 0xCE, 0x2F, 0x15, 0xCE, 0x65, 0xAD, 0xFA, 0x9A, 0xFB, 0xFB, 0x95, 0xC8, 0x73, 0x2B, 0xE9, 0xA7, 0x82, 0xE6, 0x4A, 0xCC, 0x70, 0xFF, 0xEC, 0xA9, 0xA1, 0xE7, 0xFF, 0x51, 0xFC, 0x7D, 0x9A, 0xC8, 0x59, 0x2A, 0x76, 0x58, 0xDE, 0xF6, 0xAB, 0xD2, 0xE7, 0x8C, 0x48, 0x9F, 0x42, 0xCF, 0x11, 0x7F, 0x4D, 0x3D, 0x56, 0xCC, 0x35, 0xF1, 0x81, 0xCF, 0x7C, 0xE2, 0x7D, 0xA2, 0x56, 0x33, 0xDB, 0x9B, 0x41, 0x8F, 0x85, 0x17, 0x97, 0x51, 0x82, 0xF0, 0xA2, 0xFE, 0xCD, 0xAF, 0xB9, 0xD6, 0x67, 0xA3, 0x3F, 0xBD, 0xFB, 0x55, 0x3A, 0xD7, 0x3E, 0xE7, 0x14, 0x68, 0x0A, 0xBF, 0xAD, 0x2A, 0x72, 0x42, 0xA1, 0xEE, 0x17, 0x6C, 0xBC, 0xB8, 0x2A, 0x98, 0x93, 0xC1, 0x26, 0x43, 0xAE, 0x76, 0xC5, 0xFC, 0xAE, 0xEC, 0x67, 0x7E, 0x27, 0x15, 0xD0, 0xA5, 0x01, 0x6D, 0xD4, 0xDA, 0x93, 0x09, 0xD5, 0x23, 0x6A, 0xEC, 0xA2, 0x3E, 0xBD, 0x08, 0x14, 0x64, 0x4D, 0xCB, 0xD4, 0xE9, 0xA0, 0xB4, 0x2E, 0xEB, 0x66, 0x97, 0x83, 0xBA, 0xE9, 0xDB, 0xE8, 0xEF, 0x6D, 0xE9, 0xDB, 0xB7, 0x56, 0x1C, 0x60, 0xAE, 0xC5, 0x6D, 0x72, 0x30, 0x4E, 0xDB, 0x81, 0xE9, 0xED, 0xBD, 0xC3, 0x3A, 0x1F, 0xE7, 0xEA, 0xAE, 0x3F, 0xE7, 0xCA, 0x79, 0xEA, 0x31, 0x96, 0x0E, 0x1A, 0xE3, 0x6F, 0xF2, 0x46, 0x6A, 0x67, 0x03, 0x88, 0x8F, 0x30, 0x1D, 0xF1, 0x5A, 0x93, 0x91, 0x22, 0x56, 0x75, 0x71, 0xE9, 0x4D, 0xDC, 0xFE, 0xD9, 0x45, 0x5B, 0xF9, 0x6B, 0x1D, 0x77, 0x89, 0xB0, 0x0C, 0x67, 0x9B, 0xDE, 0x8F, 0x0B, 0x67, 0x5A, 0x61, 0x13, 0x8F, 0x56, 0xAF, 0x44, 0x45, 0xB3, 0x45, 0x32, 0x0B, 0xBA, 0x3C, 0xED, 0x6B, 0x45, 0x86, 0xAD, 0x32, 0xF2, 0x1E, 0xFA, 0x78, 0xFB, 0xA8, 0x91, 0x08, 0x44, 0xD0, 0x98, 0x88, 0xF7, 0x13, 0x48, 0x9E, 0x40, 0x8C, 0xE1, 0xAB, 0xF7, 0x39, 0xE0, 0x43, 0x3E, 0xE8, 0x94, 0xE7, 0xCF, 0xF9, 0x85, 0x1B, 0x57, 0x06, 0xBF, 0x5D, 0x18, 0xE3, 0x2B, 0xED, 0x7C, 0x19, 0xFF, 0xCE, 0xE5, 0x67, 0xF9, 0xD7, 0xD0, 0x05, 0x21, 0xF6, 0x40, 0xE3, 0x5D, 0x0A, 0xED, 0x91, 0xFE, 0x33, 0xA9, 0xA3, 0x98, 0x28, 0x47, 0x7F, 0x09, 0x40, 0x7E, 0x44, 0xD1, 0x8B, 0xF7, 0x0C, 0xC8, 0x0E, 0xE7, 0x57, 0x07, 0xFA, 0xB4, 0x3D, 0xEC, 0x3F, 0x9F, 0x93, 0xE0, 0xC0, 0x0F, 0x3E, 0x40, 0x83, 0x51, 0x10, 0x12, 0xAC, 0x68, 0xD0, 0x02, 0x31, 0xD0, 0xA1, 0x88, 0xCD, 0x03, 0xDD, 0xFF, 0x93, 0x48, 0xD6, 0x0F, 0x47, 0x70, 0x64, 0x64, 0x44, 0xFE, 0x23, 0x2C, 0x28, 0x72, 0xC4, 0x1A, 0xB1, 0x18, 0xE5, 0x23, 0x96, 0x65, 0x59, 0xD6, 0xC8, 0xC8, 0x88, 0xC5, 0x25, 0x4B, 0xEA, 0xB8, 0xC5, 0xC2, 0x47, 0x8C, 0x4E, 0x52, 0x0D, 0x80, 0xCC, 0x45, 0x8C, 0x6E, 0xFD, 0x15, 0x72, 0xB6, 0x76, 0xF4, 0x3D, 0x2E, 0xDF, 0xDD, 0xDD, 0xC9, 0x3A, 0xE4, 0xD2, 0x8F, 0xA9, 0x7E, 0xAA, 0x1F, 0x63, 0xF5, 0x3B, 0xBE, 0xFF, 0x0A, 0xAB, 0x05, 0x62, 0xEC, 0x63, 0x9D, 0x0D, 0xE2, 0x17, 0x97, 0xD0, 0xD7, 0xF7, 0x10, 0x7D, 0xE2, 0xF1, 0x97, 0xDD, 0xC5, 0x3F, 0xE8, 0x61, 0x4E, 0xE1, 0xB7, 0xFF, 0xA6, 0xE6, 0xCE, 0x85, 0xDC, 0x6E, 0xFA, 0x07, 0x3D, 0x68, 0xF2, 0xE0, 0x48, 0x68, 0x33, 0xC2, 0x4D, 0xA7, 0xFF, 0x06, 0x05, 0x12, 0xA6, 0x4A, 0xF6, 0xD1, 0x7B, 0xBF, 0x76, 0x27, 0x72, 0x50, 0x9E, 0x6E, 0x27, 0xFD, 0xFF, 0x8E, 0xB6, 0x93, 0xFD, 0xAC, 0xC0, 0xB9, 0x43, 0x5A, 0x81, 0xCD, 0x2B, 0x52, 0xF3, 0x6A, 0xC7, 0x88, 0xB4, 0x63, 0x54, 0xC3, 0xC0, 0xB6, 0x96, 0x6F, 0x24, 0xF2, 0x46, 0x92, 0x0F, 0x5D, 0xD4, 0x30, 0x38, 0xFC, 0xFD, 0xCF, 0xB5, 0x60, 0xF7, 0x7D, 0xAB, 0x42, 0xCF, 0xA2, 0x73, 0xC6, 0x6A, 0x15, 0xF7, 0xF9, 0x13, 0x37, 0x7E, 0x6A, 0x3E, 0xC7, 0x8A, 0xDD, 0x59, 0x5B, 0x55, 0x90, 0x95, 0xCD, 0xEF, 0x93, 0x8B, 0xB8, 0xA5, 0x48, 0x2A, 0xA6, 0x9D, 0x73, 0x29, 0x8D, 0xCE, 0x9F, 0x88, 0x62, 0x91, 0xF9, 0x90, 0xCC, 0x89, 0x8C, 0xE4, 0x3E, 0x7B, 0x52, 0xDB, 0xA7, 0x05, 0x7E, 0x92, 0xB9, 0x16, 0xC6, 0x4C, 0xCA, 0x6A, 0x1D, 0xE9, 0xDC, 0xF9, 0x09, 0xEC, 0xF6, 0xEC, 0x69, 0x26, 0xC9, 0x55, 0xEC, 0xAC, 0x5B, 0xB9, 0xA8, 0x3C, 0x99, 0xCC, 0x4B, 0xD9, 0xF7, 0xC7, 0xF3, 0x33, 0x8D, 0x04, 0xC9, 0xAB, 0xF0, 0x7F, 0x4F, 0xF4, 0x9C, 0x0A, 0xE2, 0x41, 0x5C, 0x00, 0x29, 0x01, 0x70, 0x90, 0x94, 0x00, 0x3A, 0xA5, 0x39, 0x01, 0x3C, 0x48, 0x73, 0x26, 0x22, 0x3E, 0x00, 0x90, 0x4A, 0x91, 0x1C, 0x00, 0x38, 0x88, 0x4C, 0x01, 0xA0, 0x53, 0xE4, 0x05, 0xC0, 0x83, 0xC8, 0xFB, 0xD6, 0xB2, 0xAE, 0x76, 0x77, 0x76, 0x41, 0xB7, 0xD6, 0xF7, 0xAD, 0xDD, 0xCD, 0x96, 0x92, 0x51, 0xA9, 0xA5, 0x36, 0x48, 0x7D, 0xE3, 0x38, 0x58, 0x06, 0x89, 0xCB, 0xC8, 0xDC, 0xB8, 0x4C, 0x95, 0x75, 0xB2, 0x58, 0x58, 0x57, 0x38, 0x78, 0x62, 0x49, 0x87, 0x24, 0xB6, 0xFF, 0x67, 0x15, 0x74, 0xB6, 0x04, 0x61, 0x28, 0x40, 0x16, 0xAC, 0x97, 0x1B, 0x4C, 0xE9, 0x2B, 0x4A, 0x3F, 0x85, 0x62, 0xB5, 0xB7, 0xD5, 0xD2, 0x80, 0x97, 0x9B, 0xAA, 0x2F, 0x72, 0x1C, 0x0A, 0x28, 0x4E, 0x0E, 0x15, 0x81, 0x26, 0xC7, 0xBD, 0x51, 0x96, 0x4E, 0x0E, 0xA5, 0x79, 0xA1, 0x67, 0xD8, 0x76, 0x99, 0x1C, 0xAA, 0x7A, 0x1B, 0x9D, 0x24, 0x12, 0xBD, 0xF6, 0x09, 0x49, 0x9F, 0x3F, 0x0D, 0xBB, 0x77, 0x58, 0x13, 0x92, 0x66, 0xCF, 0x2B, 0x63, 0xB5, 0xE4, 0x71, 0x47, 0xC3, 0x5B, 0x02, 0xDB, 0x4E, 0x97, 0x80, 0x01, 0x82, 0xAF, 0xD2, 0xB2, 0xF8, 0xE6, 0x9B, 0x65, 0xB1, 0xC8, 0x48, 0x49, 0x63, 0xCD, 0x58, 0x6D, 0x9D, 0xA2, 0x60, 0x3A, 0x6D, 0xC5, 0xCC, 0xA8, 0x87, 0xC7, 0x63, 0x98, 0xE7, 0xE0, 0x5C, 0x97, 0x9D, 0xC9, 0x7B, 0xF6, 0xC5, 0xEC, 0xB0, 0xD3, 0x7B, 0x71, 0xA9, 0x5A, 0xD6, 0xD5, 0x3E, 0x49, 0xA6, 0xE5, 0xBD, 0x68, 0x54, 0xA1, 0xD7, 0x51, 0xA4, 0x4E, 0x03, 0xD1, 0x23, 0x58, 0x2B, 0x64, 0x92, 0xF5, 0x7A, 0x51, 0x28, 0x14, 0xB6, 0x85, 0xA2, 0xC8, 0xED, 0x7F, 0xD6, 0xF1, 0xBC, 0x34, 0xC3, 0x07, 0x94, 0x19, 0x2D, 0x96, 0x0F, 0xD0, 0xF2, 0xC1, 0xE4, 0x39, 0xA1, 0x84, 0x93, 0x47, 0x27, 0x9C, 0x80, 0x82, 0x15, 0x4F, 0x00, 0x11, 0x74, 0xB3, 0xF8, 0xC4, 0x9F, 0x08, 0x22, 0x28, 0x26, 0xA1, 0xAD, 0xE0, 0x99, 0x31, 0x61, 0x6C, 0x24, 0xE1, 0x40, 0x78, 0x33, 0x74, 0xB0, 0x60, 0x3F, 0xF9, 0xC1, 0xCE, 0xD8, 0xB3, 0x92, 0x68, 0x96, 0x44, 0x27, 0xB9, 0xC8, 0xE9, 0x22, 0x6D, 0xD7, 0x53, 0x92, 0x1D, 0x8D, 0x52, 0x3F, 0x68, 0x70, 0x50, 0x5A, 0x66, 0x70, 0x2E, 0xF3, 0x22, 0xFC, 0xA3, 0x71, 0xEC, 0xBB, 0x22, 0xF4, 0x1B, 0xB2, 0xD6, 0x2B, 0x52, 0x69, 0x6C, 0xC1, 0x4E, 0x2F, 0xF9, 0x8C, 0x1F, 0x9F, 0x71, 0x03, 0xE7, 0xF1, 0x31, 0x97, 0x6A, 0x76, 0x17, 0x5D, 0x22, 0xDE, 0x96, 0x89, 0xDB, 0x4B, 0x54, 0x66, 0xFC, 0x87, 0xCA, 0x0C, 0xCA, 0x1D, 0x01, 0x18, 0x7F, 0x1F, 0x26, 0xEF, 0x55, 0x09, 0xD9, 0xD2, 0x03, 0xBD, 0xC6, 0x4C, 0x19, 0x79, 0xDF, 0x41, 0x82, 0x00, 0xA1, 0x81, 0xCC, 0xF3, 0xE3, 0x52, 0x7D, 0xA8, 0xE3, 0xC4, 0x19, 0x17, 0xB7, 0x0E, 0x23, 0xB9, 0x77, 0x11, 0xD6, 0x04, 0xE3, 0x5B, 0x93, 0x6E, 0x59, 0x2E, 0xE4, 0x89, 0xAB, 0x8C, 0x44, 0xF1, 0xC9, 0x23, 0x3F, 0x66, 0x32, 0x18, 0xF7, 0x8F, 0xB0, 0x4C, 0x53, 0xC4, 0x11, 0xBF, 0x2D, 0xDF, 0x93, 0x54, 0x54, 0x56, 0x6C, 0x61, 0xD4, 0x76, 0xAC, 0x2D, 0xC6, 0x39, 0x13, 0xDF, 0x8F, 0x27, 0x30, 0x72, 0xF1, 0xF3, 0x46, 0x6E, 0xA9, 0x37, 0xC0, 0xDA, 0x3E, 0x73, 0xE2, 0x4C, 0xBC, 0x17, 0xA3, 0x8C, 0x5E, 0x9D, 0x41, 0x91, 0x27, 0x52, 0xED, 0x09, 0x79, 0x53, 0x2B, 0x95, 0x68, 0xD2, 0x6E, 0xB0, 0x98, 0xFD, 0x9C, 0x7B, 0xBC, 0x16, 0x26, 0xEB, 0x25, 0xD2, 0x1F, 0x2C, 0x6A, 0x99, 0x16, 0x3F, 0xD6, 0xEF, 0xC7, 0xB0, 0xFD, 0x0E, 0xA6, 0xD3, 0x67, 0xF3, 0x82, 0xDC, 0x3E, 0x0C, 0xE3, 0xB8, 0x4D, 0xBA, 0x80, 0xC9, 0x9D, 0x1D, 0xC9, 0xB1, 0x06, 0xF3, 0xDE, 0xA6, 0x29, 0x8C, 0xC8, 0xDA, 0x0E, 0x66, 0x65, 0x3F, 0xE5, 0xE9, 0x95, 0x6C, 0x56, 0x5A, 0x9A, 0x45, 0x7F, 0xD2, 0x39, 0x95, 0x79, 0x5A, 0xBD, 0xB2, 0x36, 0xEA, 0x84, 0xF2, 0x9A, 0x67, 0x9B, 0xDD, 0x14, 0xFA, 0x5A, 0x25, 0x74, 0x65, 0x9E, 0x59, 0x6D, 0x08, 0x42, 0x48, 0xA2, 0x10, 0xC2, 0xFF, 0x67, 0xA9, 0x57, 0x42, 0xAD, 0xCF, 0x22, 0x8A, 0x73, 0x16, 0xBF, 0xF7, 0x45, 0x06, 0xD4, 0xC0, 0x86, 0x6A, 0x85, 0xEF, 0x08, 0x1F, 0x2C, 0xE1, 0x80, 0x14, 0x94, 0x0C, 0x41, 0x04, 0x03, 0x22, 0x88, 0x37, 0xF8, 0x38, 0x6E, 0x3D, 0x87, 0x37, 0xAE, 0xDD, 0xC6, 0xDD, 0xC6, 0x1D, 0xA9, 0x5E, 0xF4, 0x7A, 0x3C, 0x57, 0xC3, 0x34, 0x33, 0x8D, 0xB1, 0x84, 0xD9, 0x17, 0x53, 0xCD, 0x83, 0x1F, 0xD3, 0x3B, 0x6E, 0xE1, 0x29, 0x4D, 0xC2, 0x88, 0x11, 0x78, 0x80, 0x03, 0x46, 0x6C, 0x20, 0x87, 0x05, 0x4A, 0xB2, 0x8B, 0x63, 0x3B, 0x8E, 0x21, 0x7C, 0x02, 0xF7, 0x71, 0x24, 0xC4, 0x38, 0x8E, 0xFB, 0xA8, 0x14, 0x96, 0x70, 0x46, 0xD5, 0x4A, 0x56, 0x81, 0xA7, 0x90, 0x5A, 0xB6, 0xDD, 0x32, 0xE7, 0x50, 0xB7, 0x25, 0x67, 0x19, 0x8C, 0x6B, 0x6B, 0xE1, 0x89, 0x7E, 0xBC, 0xEA, 0xAB, 0xC6, 0x80, 0xE2, 0x67, 0x54, 0x95, 0xDF, 0xE6, 0x0F, 0xE9, 0xF2, 0x1C, 0xC3, 0x09, 0x13, 0x18, 0x9E, 0x13, 0xA6, 0x6E, 0xAE, 0x66, 0xDF, 0x0C, 0x37, 0x61, 0x62, 0x34, 0xC1, 0x5C, 0x0B, 0x42, 0x65, 0x37, 0x92, 0xE6, 0x72, 0x11, 0x4F, 0x4C, 0xCA, 0x31, 0xD2, 0xC8, 0x10, 0x51, 0xAC, 0x5A, 0xA1, 0xB2, 0x1D, 0x6C, 0x1B, 0x4D, 0x98, 0x92, 0xD1, 0xE5, 0x37, 0x6A, 0x0C, 0xEE, 0xCD, 0xE3, 0x31, 0xA7, 0x0F, 0x02, 0xAA, 0x35, 0x25, 0xB0, 0xCB, 0x1D, 0x4F, 0xD6, 0x44, 0xFD, 0xA4, 0xD3, 0x76, 0x00, 0xBD, 0xD0, 0xE4, 0x8B, 0xF7, 0x0C, 0xAA, 0x9E, 0x33, 0x7D, 0x7D, 0xA1, 0x1E, 0x53, 0x89, 0xD2, 0xE9, 0x36, 0x43, 0x4E, 0xA6, 0x5F, 0xD3, 0x2F, 0xE5, 0x79, 0x36, 0x0F, 0xD4, 0x7B, 0xAE, 0x50, 0x16, 0xBB, 0x5A, 0x8F, 0xDF, 0xAB, 0x7C, 0x54, 0x5E, 0xD4, 0xEE, 0x17, 0xBC, 0x6A, 0xE6, 0x6A, 0xDB, 0xCF, 0xA7, 0xD9, 0xB7, 0x76, 0xE4, 0xE9, 0x72, 0xC6, 0x93, 0xD5, 0xAA, 0x70, 0xE9, 0x87, 0x1E, 0xEF, 0x8C, 0x1B, 0xA9, 0x66, 0xA4, 0x79, 0x91, 0x0A, 0x7D, 0x76, 0x13, 0x48, 0xE6, 0xF1, 0x64, 0x6A, 0xC3, 0x4C, 0xE4, 0x39, 0xC3, 0x69, 0x9D, 0xB1, 0xEA, 0x93, 0xF9, 0x90, 0x6B, 0x1F, 0xAF, 0xA9, 0x66, 0x4A, 0xA0, 0x41, 0xFD, 0x31, 0xDB, 0xAD, 0x8E, 0x9F, 0x40, 0x5C, 0xA6, 0x11, 0x31, 0xE1, 0xE9, 0x22, 0xD1, 0xD4, 0x43, 0x3F, 0xC6, 0x69, 0x3D, 0x68, 0xE2, 0xBB, 0x23, 0xBF, 0xEF, 0xE3, 0x35, 0x75, 0x5E, 0xF6, 0xF5, 0xF8, 0x61, 0x3D, 0x88, 0xBD, 0x71, 0xD4, 0x4D, 0x1F, 0xFF, 0x68, 0x3D, 0xDB, 0xB9, 0x6C, 0xBE, 0xCD, 0xB7, 0xAD, 0xB5, 0x36, 0xBB, 0xF5, 0xCC, 0x94, 0x0D, 0xCB, 0x66, 0x49, 0xEF, 0x6B, 0x9F, 0x56, 0xC6, 0x52, 0x39, 0x8C, 0xB3, 0x94, 0xB3, 0x92, 0xAB, 0xE9, 0x9C, 0x8E, 0x9F, 0xC4, 0x9D, 0x04, 0xA4, 0x37, 0x69, 0x2E, 0x37, 0xF3, 0x46, 0x4C, 0x41, 0x99, 0x4D, 0x0E, 0xCE, 0x61, 0x45, 0xB9, 0xAE, 0xFA, 0x73, 0x5D, 0xD7, 0x4F, 0xCE, 0x10, 0x66, 0x47, 0x81, 0xD6, 0xA3, 0x27, 0xDD, 0xF3, 0xBA, 0xFB, 0x6B, 0x7D, 0xC7, 0x2C, 0xB2, 0x18, 0xD7, 0x94, 0x4B, 0xA6, 0x39, 0xFF, 0xAD, 0x1E, 0xBA, 0x89, 0xBC, 0xB5, 0xF5, 0x24, 0x08, 0xEB, 0x45, 0x0C, 0x77, 0xC4, 0x7B, 0x57, 0xB5, 0xE7, 0xC1, 0x21, 0xC9, 0x04, 0x72, 0xC5, 0xD9, 0x0A, 0x92, 0x16, 0x3E, 0x7C, 0x32, 0xD2, 0x09, 0xB1, 0x01, 0x08, 0xD0, 0x08, 0x47, 0x28, 0x3B, 0xA2, 0x87, 0xCD, 0x84, 0x85, 0x34, 0x4C, 0x23, 0x50, 0xA1, 0x48, 0x6A, 0xC7, 0x04, 0x52, 0xC0, 0x85, 0x94, 0x3A, 0xD4, 0x21, 0xFE, 0xD6, 0x2F, 0xFF, 0x4A, 0x4F, 0xAB, 0xBC, 0xB5, 0x4B, 0xF7, 0x2D, 0x2B, 0xD7, 0x56, 0x1A, 0x6F, 0xAD, 0x9A, 0x18, 0x74, 0xEB, 0xBD, 0x50, 0x7C, 0xFF, 0xCD, 0xD2, 0xB1, 0xB0, 0x04, 0x59, 0x2C, 0x41, 0x90, 0x25, 0xE8, 0x27, 0x5D, 0x4F, 0x20, 0xEB, 0x04, 0x08, 0x9E, 0x00, 0xF1, 0x24, 0xE1, 0xA6, 0xC1, 0xF6, 0x32, 0x5B, 0x8B, 0x1B, 0x32, 0x3F, 0xCE, 0xA0, 0xAF, 0x3D, 0xDB, 0x4F, 0xAC, 0x03, 0x48, 0xFF, 0xDF, 0xF1, 0xA7, 0x4C, 0x3A, 0xC1, 0x88, 0xD6, 0x85, 0x95, 0x49, 0x38, 0x59, 0x8B, 0x96, 0x29, 0x28, 0xC2, 0x2A, 0x6F, 0xD1, 0x1F, 0x46, 0x86, 0xB0, 0xB3, 0xB6, 0x90, 0x53, 0x92, 0x05, 0x19, 0xE0, 0xD0, 0x28, 0x00, 0x09, 0x92, 0x8C, 0xE3, 0x86, 0x88, 0x01, 0x30, 0x28, 0x22, 0x8B, 0x04, 0x68, 0x41, 0x0A, 0x0A, 0xB4, 0xB1, 0x03, 0x00, 0x41, 0xA8, 0xE0, 0x18, 0x0A, 0x3E, 0x18, 0xA4, 0xE1, 0x8B, 0x1B, 0x86, 0x11, 0x74, 0xF1, 0x85, 0x3C, 0x68, 0xE1, 0x03, 0x42, 0x76, 0xEE, 0x10, 0x85, 0x0D, 0x40, 0xE1, 0x09, 0x2F, 0x56, 0x11, 0x1D, 0x0B, 0x40, 0xA1, 0x83, 0x96, 0x1C, 0x54, 0x1A, 0x8C, 0x20, 0x06, 0x4D, 0x48, 0xC0, 0x54, 0x87, 0x15, 0x40, 0x40, 0x09, 0x98, 0x31, 0x92, 0x68, 0x80, 0x08, 0x42, 0x30, 0x07, 0x0D, 0x64, 0x19, 0xC0, 0x00, 0x02, 0x2C, 0x31, 0x90, 0x28, 0xD9, 0x80, 0x00, 0x10, 0x5D, 0x0E, 0x87, 0xD1, 0xFD, 0xE0, 0x01, 0x22, 0xC6, 0x05, 0x7E, 0x5E, 0x31, 0xF9, 0x99, 0x08, 0x22, 0x30, 0x32, 0x84, 0x10, 0xB2, 0x91, 0x86, 0x2E, 0x00, 0x61, 0x85, 0x1F, 0x9A, 0xB0, 0x04, 0x92, 0x4E, 0x0F, 0x48, 0xC8, 0x99, 0x81, 0x33, 0x82, 0xF3, 0x07, 0xE9, 0xB0, 0x20, 0xF0, 0xF8, 0xAB, 0x77, 0xD8, 0xD6, 0x0F, 0xD9, 0x67, 0xDD, 0x87, 0x86, 0x3F, 0x35, 0x7F, 0x96, 0x68, 0xC2, 0x01, 0x07, 0x25, 0x21, 0x10, 0xF4, 0x20, 0xCC, 0xA5, 0x4C, 0xB4, 0x44, 0xD7, 0xBC, 0xA6, 0xF1, 0xBA, 0xB0, 0xCF, 0x9B, 0xE1, 0xCD, 0x8E, 0x44, 0x89, 0x0B, 0x83, 0xF0, 0xE7, 0x1E, 0xD2, 0xC8, 0x69, 0x2D, 0x82, 0xBF, 0xEB, 0x34, 0x23, 0x4A, 0xAF, 0xD7, 0xEB, 0xF0, 0xB7, 0xE1, 0x6D, 0x7C, 0x2C, 0x68, 0x2D, 0x13, 0x77, 0x17, 0x25, 0x05, 0xC9, 0xC4, 0x0B, 0xA2, 0x60, 0x9C, 0x55, 0x45, 0xCF, 0x96, 0x38, 0xEA, 0xE2, 0x1A, 0x91, 0x63, 0xCE, 0xB1, 0xFE, 0xA1, 0x1B, 0x2D, 0xFE, 0xA5, 0x25, 0x13, 0x2D, 0x2D, 0x85, 0x23, 0xAD, 0x2E, 0x91, 0xDE, 0x96, 0xEC, 0x81, 0x59, 0xB2, 0x47, 0xB4, 0x1E, 0xB9, 0xA1, 0x0C, 0x94, 0x81, 0x5F, 0x64, 0xDB, 0xE3, 0xD3, 0xC3, 0xE3, 0x45, 0xAE, 0x39, 0x3A, 0x39, 0x38, 0x1D, 0x53, 0x9A, 0x1A, 0x9A, 0x99, 0x8E, 0xA7, 0x41, 0x42, 0x1C, 0x04, 0xF4, 0xC5, 0x7F, 0x2F, 0x39, 0x64, 0x5C, 0x60, 0x85, 0xF2, 0x92, 0x81, 0x32, 0xF0, 0xD1, 0x02, 0x41, 0xAB, 0xCE, 0x0F, 0xCE, 0xEE, 0xB2, 0xE2, 0x65, 0x69, 0xDF, 0x2F, 0xAF, 0xB6, 0x2C, 0x7E, 0x84, 0x65, 0xBC, 0x70, 0x2D, 0x80, 0x14, 0xFD, 0x5F, 0xE4, 0xF3, 0x73, 0xF8, 0x95, 0xEA, 0xB0, 0xAD, 0x0F, 0xBA, 0x28, 0xCA, 0x75, 0xFA, 0xFA, 0xFA, 0x53, 0x96, 0x7F, 0x77, 0x04, 0xE9, 0xCA, 0xF1, 0x22, 0x88, 0x58, 0xC2, 0x0A, 0x4B, 0x05, 0x4F, 0xC0, 0x22, 0x00, 0x10, 0xCB, 0x40, 0x12, 0x65, 0x9C, 0x21, 0x08, 0x97, 0x8F, 0xB8, 0x89, 0x25, 0x5C, 0xB7, 0x8E, 0xA1, 0xFC, 0x1F, 0xCD, 0x91, 0xFC, 0xE1, 0x47, 0xA2, 0xD4, 0x08, 0xF2, 0x47, 0x17, 0x49, 0x6E, 0x8D, 0x82, 0xA1, 0x6C, 0x0F, 0x47, 0x8F, 0x72, 0xEF, 0xC3, 0xA1, 0x54, 0x2E, 0xF7, 0x4B, 0xEE, 0x5D, 0xDD, 0xE8, 0xA4, 0x75, 0xFB, 0x89, 0xFC, 0xC6, 0x4E, 0xDA, 0x8F, 0xFC, 0x5B, 0xDE, 0x0C, 0x66, 0x84, 0x99, 0x5A, 0xD4, 0xEC, 0xF8, 0xCC, 0xA7, 0xF6, 0xD9, 0xCF, 0x9D, 0x3F, 0x17, 0xBF, 0xB3, 0x43, 0xB3, 0x33, 0x75, 0x17, 0x08, 0xE0, 0xE9, 0xE2, 0xDE, 0x3F, 0x8F, 0xC0, 0xF3, 0x23, 0x80, 0xE7, 0x9E, 0xB3, 0xF4, 0x59, 0x9F, 0xF4, 0x6B, 0x3D, 0x54, 0xA2, 0xB2, 0xF7, 0x10, 0x9D, 0xBC, 0x1A, 0x95, 0x1C, 0x0A, 0xA5, 0xDD, 0x5C, 0x03, 0x78, 0x8A, 0xCF, 0xED, 0xE0, 0x35, 0x82, 0x23, 0xA0, 0xC8, 0xA5, 0x9A, 0x39, 0xDD, 0x96, 0xBC, 0xC1, 0x88, 0xF7, 0xB7, 0x23, 0xD9, 0x8E, 0x1C, 0xE1, 0xC7, 0x8C, 0xDC, 0xA4, 0x47, 0x5A, 0xD2, 0x99, 0x78, 0x91, 0xCC, 0x93, 0xF3, 0x87, 0x86, 0xC5, 0x9D, 0xCB, 0xC9, 0x24, 0x9F, 0x73, 0x92, 0xDC, 0xD3, 0xE3, 0x4C, 0xB8, 0x00, 0x81, 0xCC, 0x46, 0x0B, 0x26, 0x59, 0x80, 0x80, 0xF6, 0x13, 0xB9, 0x4D, 0x73, 0x78, 0xFD, 0x5B, 0x44, 0xE5, 0xFB, 0x22, 0x99, 0x5D, 0x0F, 0x4A, 0x2B, 0x5D, 0x09, 0x3F, 0x34, 0x46, 0xC0, 0x08, 0x00, 0x12, 0x09, 0x49, 0x13, 0x1E, 0xEB, 0x43, 0x44, 0xB0, 0xB7, 0xB8, 0xDE, 0x01, 0x92, 0x5F, 0xE5, 0xF4, 0x41, 0x56, 0x4E, 0x0C, 0x71, 0x93, 0x2E, 0xA5, 0xC6, 0x7A, 0x9F, 0x0C, 0x0C, 0x40, 0x80, 0x3E, 0xB5, 0x02, 0x08, 0xFC, 0x2C, 0x10, 0xF8, 0xBD, 0xD0, 0x08, 0xFD, 0x65, 0x94, 0xC8, 0x28, 0xE7, 0xD4, 0x57, 0x7C, 0xA2, 0xC3, 0x07, 0xA2, 0xB3, 0x9D, 0xE8, 0xDC, 0x86, 0x07, 0x9A, 0x3E, 0x5E, 0x99, 0x3C, 0x48, 0xDA, 0xB6, 0xED, 0x26, 0xD6, 0x0C, 0x22, 0x81, 0x70, 0x7E, 0xF0, 0xB4, 0x1B, 0xB2, 0x34, 0x6F, 0x94, 0x79, 0x24, 0x1E, 0x16, 0x85, 0x23, 0x48, 0xC0, 0xFE, 0x5D, 0xE4, 0x63, 0x5C, 0x0C, 0xEC, 0x5D, 0x15, 0xF8, 0x09, 0xB8, 0x52, 0x28, 0x51, 0x10, 0xFD, 0x66, 0x0B, 0x94, 0x25, 0x90, 0x54, 0x18, 0x69, 0x51, 0x98, 0x68, 0xC1, 0xDC, 0xCA, 0xBE, 0x8B, 0xF8, 0x63, 0xCE, 0xBA, 0xD9, 0x44, 0xC9, 0x6F, 0xDC, 0x52, 0x48, 0x6A, 0x0A, 0x29, 0x85, 0x3F, 0x85, 0xF6, 0xE3, 0xFF, 0x3B, 0xDB, 0xF4, 0x7C, 0x05, 0xDA, 0xCF, 0xD4, 0xEF, 0xC7, 0xD8, 0x5A, 0x2B, 0x6D, 0xD7, 0x07, 0x22, 0xC7, 0x6D, 0x6E, 0x6F, 0x9A, 0x0B, 0x6A, 0xB6, 0xF7, 0x8E, 0x99, 0x1E, 0x9A, 0x14, 0xA8, 0xE1, 0xFA, 0x3B, 0x36, 0x2B, 0x6F, 0xD1, 0x36, 0x09, 0xF9, 0x61, 0x75, 0x5B, 0xEE, 0xD2, 0xA2, 0xA4, 0xF8, 0xFF, 0x97, 0xCF, 0x3E, 0x13, 0x57, 0x7B, 0x07, 0xF7, 0xAC, 0xEB, 0xEC, 0x35, 0x25, 0x7E, 0x58, 0xEF, 0x98, 0x8D, 0x3E, 0x23, 0xB2, 0xB2, 0xFA, 0x31, 0x9C, 0xED, 0xBB, 0xC3, 0xDE, 0xF9, 0x99, 0xAA, 0xBD, 0x6C, 0x78, 0xAE, 0x2B, 0x5A, 0x2D, 0x52, 0xD9, 0x9D, 0x76, 0x7F, 0xF7, 0xEB, 0xBA, 0x7E, 0x5D, 0xD7, 0x45, 0x7F, 0x5D, 0xD7, 0xF5, 0x7A, 0x23, 0xA2, 0x37, 0xF3, 0x46, 0x74, 0x9C, 0xCD, 0x9E, 0xF3, 0x11, 0xF2, 0x39, 0x4D, 0xA1, 0xAD, 0x3B, 0x09, 0x71, 0x24, 0x54, 0x17, 0x5B, 0xC7, 0x2A, 0xEF, 0xAA, 0xAF, 0xF3, 0xBC, 0x53, 0xE4, 0x66, 0x84, 0xB5, 0xC4, 0x92, 0xD1, 0xE1, 0xDC, 0x7B, 0x37, 0x55, 0x8A, 0xDD, 0x47, 0x9C, 0xAB, 0xFE, 0xD9, 0xFC, 0x13, 0x7E, 0xDC, 0x93, 0x1D, 0xC6, 0x04, 0x78, 0x75, 0x49, 0x00, 0x21, 0x8A, 0xC3, 0x2C, 0x6E, 0xF1, 0x73, 0x40, 0xE0, 0x0C, 0x54, 0xFC, 0xE0, 0x8A, 0x0F, 0x38, 0x61, 0x0D, 0x28, 0x2C, 0xBA, 0x70, 0x42, 0x53, 0x01, 0x07, 0x31, 0x95, 0xDE, 0x60, 0x35, 0xA7, 0x62, 0xE6, 0xFA, 0x8D, 0xAC, 0xB9, 0x0F, 0x7D, 0x65, 0x30, 0xD7, 0x84, 0xA1, 0x02, 0xE7, 0xDA, 0x58, 0x28, 0x84, 0x50, 0x07, 0x8A, 0xBC, 0x5D, 0x83, 0x42, 0x7A, 0x46, 0xF9, 0xD0, 0x6B, 0xBC, 0x8A, 0x08, 0xAB, 0x50, 0x81, 0x19, 0xCD, 0xBD, 0xC7, 0x37, 0x55, 0xA3, 0x04, 0x4D, 0x30, 0xB3, 0x86, 0xAC, 0x02, 0xDB, 0xAA, 0x58, 0xD9, 0x98, 0xD9, 0x9E, 0xE5, 0x27, 0x59, 0xF1, 0x08, 0xBF, 0xC9, 0x8F, 0x20, 0xC5, 0x6F, 0xFB, 0xBE, 0x7A, 0xDB, 0x0E, 0xBB, 0xD5, 0xA6, 0x5C, 0xA5, 0xDB, 0xC6, 0x1F, 0x17, 0xB4, 0xA1, 0x4E, 0x4F, 0x0E, 0x74, 0x6E, 0xA3, 0x74, 0x2B, 0x55, 0x6E, 0xAB, 0xF7, 0xBC, 0xD5, 0xAC, 0xD3, 0xB6, 0x18, 0xB7, 0x4C, 0x58, 0x0F, 0xEF, 0x0E, 0xDA, 0xD8, 0x43, 0xAA, 0x75, 0x7A, 0xC8, 0x21, 0xBA, 0x8E, 0xC8, 0xA3, 0xB4, 0x6D, 0xDF, 0x56, 0xFA, 0x8E, 0x75, 0x9C, 0xB0, 0x15, 0xEF, 0xFE, 0x11, 0x4C, 0x58, 0x40, 0xF7, 0x12, 0xDE, 0x75, 0xD5, 0x87, 0x1B, 0xC8, 0xC1, 0x88, 0x32, 0xA4, 0x40, 0x06, 0x07, 0x09, 0x36, 0xA0, 0x21, 0x02, 0x1D, 0x06, 0x12, 0x20, 0x04, 0x24, 0xE0, 0x71, 0xD4, 0xB8, 0x01, 0x27, 0x70, 0x28, 0x56, 0xD8, 0x04, 0x1E, 0x45, 0x9D, 0x24, 0x2C, 0x2D, 0x98, 0xC4, 0x4E, 0x5A, 0x1E, 0x49, 0x6C, 0x81, 0xC8, 0x91, 0x58, 0xF9, 0xD5, 0x88, 0x0E, 0x4F, 0xEF, 0x4D, 0xD2, 0x61, 0x13, 0x3B, 0x91, 0x7D, 0xCC, 0x83, 0x53, 0x3C, 0x98, 0x05, 0x46, 0xF2, 0x09, 0x4C, 0x64, 0x98, 0x5B, 0x23, 0x23, 0x23, 0x15, 0xD6, 0x57, 0x08, 0x6B, 0x8B, 0x84, 0x35, 0x68, 0x4B, 0x84, 0x35, 0xDE, 0x72, 0x58, 0xA3, 0x46, 0x8D, 0x1A, 0x35, 0x20, 0x84, 0x2E, 0x35, 0x5E, 0x6B, 0xBC, 0xC7, 0x5F, 0x6A, 0xBC, 0x40, 0x17, 0x78, 0xAB, 0xD6, 0x24, 0x48, 0x68, 0x05, 0xC1, 0x7C, 0xED, 0xC0, 0x94, 0x3D, 0xB0, 0xD4, 0xC3, 0x3B, 0x04, 0x51, 0xA0, 0xEA, 0x98, 0x03, 0x15, 0x60, 0xAC, 0x21, 0x02, 0x66, 0x80, 0x81, 0x00, 0x0A, 0x60, 0x80, 0x00, 0xFC, 0x94, 0xD6, 0x96, 0xA0, 0x1B, 0xDA, 0x12, 0x04, 0xE4, 0xF4, 0x84, 0xBA, 0x29, 0xE1, 0x80, 0x94, 0x7C, 0x06, 0x75, 0xA6, 0x2E, 0x91, 0xDD, 0x8F, 0xAE, 0x6F, 0xB2, 0x0F, 0x48, 0xE7, 0x79, 0xDD, 0x9F, 0xDF, 0xF6, 0x78, 0x82, 0x89, 0xBC, 0xC5, 0xCB, 0xC9, 0xFC, 0x6A, 0x5D, 0x80, 0xC1, 0x4E, 0x32, 0x5A, 0x59, 0x0F, 0x5B, 0xB6, 0x39, 0xAE, 0x36, 0xD7, 0x5C, 0xC7, 0x3F, 0x72, 0x75, 0x33, 0xF5, 0x77, 0xCE, 0x83, 0x95, 0x3E, 0x8B, 0x57, 0x98, 0x05, 0xC3, 0x2F, 0x54, 0x67, 0xD0, 0xE2, 0x94, 0x05, 0x11, 0x3B, 0xF8, 0x01, 0x0F, 0x62, 0x70, 0x24, 0xC1, 0x08, 0xA8, 0x94, 0x15, 0xA7, 0x2F, 0x2E, 0xF5, 0x74, 0xDB, 0x7E, 0xDF, 0xFF, 0xFF, 0xBB, 0xC0, 0xFF, 0x87, 0xB4, 0x72, 0x1E, 0xB7, 0x71, 0x1C, 0x11, 0x31, 0x68, 0x3D, 0xA7, 0xC9, 0x29, 0x31, 0x93, 0xE4, 0x7A, 0xFA, 0x95, 0xDB, 0xDF, 0x0B, 0x8D, 0x44, 0x69, 0x6D, 0xF7, 0x5B, 0xEC, 0xD8, 0xB9, 0x5F, 0x7F, 0xCB, 0x3E, 0x60, 0x4F, 0xF5, 0xB1, 0xFF, 0xEF, 0xEE, 0x04, 0xA2, 0xDF, 0xE2, 0x52, 0xFB, 0xE9, 0x0D, 0xD9, 0xA6, 0xF5, 0x2E, 0xC4, 0x26, 0x31, 0xEC, 0x99, 0x62, 0xD7, 0xAB, 0xF5, 0xDE, 0xBF, 0x71, 0x5F, 0x57, 0xD6, 0xE5, 0x7F, 0xBD, 0xFD, 0x78, 0xC4, 0x7C, 0x87, 0x7D, 0xE9, 0xF4, 0xBF, 0xBC, 0xF1, 0xFE, 0x37, 0x52, 0x8A, 0x45, 0x97, 0x56, 0xBF, 0xB5, 0x72, 0x22, 0x85, 0x22, 0xE2, 0x44, 0x3E, 0xC9, 0x65, 0xBD, 0x30, 0xF1, 0xC6, 0x3B, 0x83, 0xFE, 0x21, 0xA0, 0xF9, 0x59, 0x96, 0x95, 0xC1, 0xF4, 0xB3, 0xE0, 0x7C, 0xB6, 0x93, 0x18, 0xB6, 0x41, 0x3B, 0xB2, 0x69, 0xAC, 0x4C, 0xE8, 0xD2, 0xEA, 0x03, 0xC1, 0x86, 0xD0, 0x1E, 0xE9, 0x02, 0xC9, 0x71, 0x59, 0xA4, 0xC3, 0xC1, 0xAE, 0x9A, 0x8B, 0x51, 0xE1, 0x6F, 0x97, 0xD4, 0x73, 0x25, 0x14, 0x45, 0x7B, 0x43, 0xA1, 0xB8, 0x90, 0x37, 0x55, 0x52, 0xDA, 0x5E, 0xB6, 0x5A, 0x42, 0xB7, 0x6F, 0x5B, 0x89, 0xBC, 0x25, 0xA5, 0xDB, 0x71, 0xB3, 0xE3, 0x32, 0x6F, 0xDB, 0x3E, 0xCE, 0xA4, 0xE7, 0x7A, 0xB7, 0x49, 0xF5, 0x43, 0x9D, 0x22, 0xBD, 0x44, 0x2E, 0xC6, 0xA8, 0x81, 0xF1, 0x53, 0xEF, 0x1C, 0x65, 0x63, 0x36, 0x91, 0x48, 0xB4, 0x55, 0x2B, 0x12, 0x89, 0x36, 0x11, 0xB5, 0x22, 0xD1, 0x76, 0x45, 0xA2, 0xF9, 0xC4, 0x4F, 0x6D, 0x72, 0x89, 0xA8, 0x7A, 0x6F, 0xE3, 0x38, 0x6E, 0x3B, 0x37, 0x1B, 0x72, 0xB3, 0xBB, 0x65, 0x59, 0xA2, 0xF3, 0x88, 0x59, 0xF3, 0xFB, 0xFF, 0xFD, 0x0A, 0xBB, 0x26, 0x1C, 0xD1, 0xEA, 0x3D, 0x9F, 0x1D, 0xD7, 0x6B, 0x41, 0xC7, 0x62, 0xD6, 0x75, 0x8B, 0x44, 0xA2, 0x18, 0xE3, 0xFB, 0xA2, 0xBE, 0xE8, 0x6F, 0x91, 0xA8, 0x7A, 0x16, 0xBD, 0xE8, 0xDE, 0x68, 0x34, 0x4A, 0xB5, 0x8C, 0xDC, 0x8F, 0xDC, 0x31, 0xD6, 0xEF, 0xFB, 0xAE, 0xAB, 0x7E, 0xFF, 0x77, 0xFA, 0x5D, 0x8F, 0x57, 0x9B, 0xF0, 0xB4, 0xAA, 0xE7, 0xD6, 0x5B, 0x11, 0xF5, 0x44, 0xA2, 0xCC, 0xFB, 0xFF, 0xEE, 0x24, 0xEE, 0x1B, 0x32, 0x9A, 0x8C, 0x59, 0x8C, 0x8F, 0xD5, 0xF3, 0x3C, 0x96, 0xE7, 0x7B, 0x9E, 0xC7, 0x8F, 0xBF, 0x55, 0xC3, 0x4D, 0x53, 0x53, 0x28, 0x5E, 0xED, 0xA3, 0x9D, 0xBD, 0xEA, 0x3B, 0xFA, 0xCE, 0x83, 0x57, 0xEB, 0xB0, 0x80, 0xBC, 0x2D, 0x18, 0xBC, 0xB4, 0xB8, 0x05, 0xDB, 0x74, 0xDC, 0x83, 0x0D, 0x33, 0x1C, 0xD8, 0x31, 0xC4, 0x78, 0xAE, 0x54, 0x67, 0x45, 0xF8, 0x3D, 0xDF, 0x62, 0x6A, 0xA5, 0x54, 0x2C, 0xDB, 0x73, 0x69, 0x37, 0xE7, 0xCE, 0xF4, 0xA2, 0x41, 0xD7, 0x94, 0x41, 0xBC, 0x4F, 0xC5, 0x19, 0xA0, 0xAB, 0xC6, 0xF7, 0xA7, 0xCC, 0x4F, 0xE7, 0x5F, 0xD7, 0x08, 0x85, 0x42, 0x79, 0x70, 0x84, 0x42, 0xA1, 0xE6, 0x0E, 0x16, 0x33, 0x42, 0xA1, 0x50, 0xA6, 0x9D, 0xEF, 0xA2, 0x3E, 0x5B, 0xE2, 0x4B, 0x1D, 0x69, 0x1D, 0x8A, 0xCA, 0x8D, 0x34, 0x42, 0x65, 0xDD, 0x76, 0xB9, 0xC5, 0x6B, 0x74, 0xB7, 0x4D, 0xE5, 0x32, 0x7D, 0xF6, 0xD2, 0x74, 0x6E, 0xE6, 0x62, 0xE5, 0xBB, 0xB5, 0x30, 0xBE, 0xBD, 0xE3, 0x15, 0xF4, 0xD4, 0xF4, 0x6E, 0xE4, 0xFE, 0x2F, 0x0D, 0xCE, 0x20, 0x1E, 0xA8, 0x3D, 0xB3, 0x2C, 0xB6, 0x4E, 0xA1, 0x55, 0xA3, 0xA5, 0xB7, 0x2E, 0xEB, 0x61, 0x5E, 0x3D, 0x6E, 0x45, 0x2F, 0xFD, 0x14, 0xDD, 0x14, 0x45, 0xBF, 0x25, 0x5D, 0x44, 0x0F, 0x65, 0x65, 0x20, 0xD4, 0x0A, 0xF0, 0xB5, 0xCF, 0xD2, 0x83, 0x8E, 0x50, 0xDC, 0xAA, 0xE7, 0x3F, 0xA0, 0x7B, 0x88, 0x27, 0x71, 0x85, 0x79, 0xE3, 0xE3, 0x3D, 0xD0, 0x11, 0x71, 0xF9, 0xCF, 0xC1, 0x5F, 0x39, 0xC6, 0xE7, 0x2E, 0xC4, 0xDC, 0xEF, 0x52, 0x57, 0xCE, 0x4C, 0xA2, 0xCC, 0x93, 0x74, 0xA6, 0xF5, 0xAF, 0xFF, 0x50, 0xEA, 0x47, 0x84, 0x25, 0x11, 0xFE, 0x10, 0xD6, 0x53, 0xFA, 0x53, 0x4B, 0x55, 0x5F, 0x8F, 0x89, 0xF7, 0x52, 0xF2, 0xB1, 0x3F, 0xFF, 0x25, 0x94, 0xDE, 0xE8, 0x23, 0xD1, 0xE1, 0xE0, 0x9C, 0x63, 0xC8, 0x90, 0x49, 0xCD, 0x06, 0x72, 0x30, 0x44, 0x88, 0x2D, 0x4C, 0x00, 0x93, 0x01, 0x05, 0x04, 0x51, 0x03, 0x89, 0xFF, 0x92, 0xF4, 0xDA, 0xD2, 0xD2, 0xC2, 0xD1, 0xC0, 0x09, 0xE9, 0x87, 0xD8, 0xE4, 0x19, 0xCC, 0x2F, 0x99, 0x2B, 0xED, 0x3F, 0xB2, 0xC9, 0x06, 0x7F, 0x12, 0xDC, 0xF1, 0x1D, 0xE0, 0xEC, 0xFB, 0xF9, 0x2E, 0x5C, 0x65, 0xC9, 0x9B, 0x5F, 0xB9, 0xDF, 0x2F, 0xCC, 0x7F, 0x47, 0x43, 0xDB, 0xEB, 0x40, 0xD3, 0x8E, 0xEF, 0x08, 0x64, 0xD5, 0xA0, 0x5F, 0x9E, 0x2F, 0xAB, 0x85, 0x5D, 0x11, 0x36, 0xE0, 0xC0, 0x13, 0x8A, 0x94, 0x01, 0x08, 0x16, 0x73, 0x78, 0xC2, 0x90, 0x9D, 0xD6, 0x86, 0x2A, 0x8C, 0xBE, 0x8C, 0xBC, 0xBC, 0xE6, 0x47, 0xF1, 0x30, 0xD5, 0x23, 0x1F, 0xB1, 0x7C, 0xE8, 0x19, 0xA7, 0xC1, 0x13, 0x23, 0xDD, 0xB7, 0xA0, 0xC0, 0x43, 0x18, 0xAF, 0x71, 0x19, 0x37, 0x79, 0xA6, 0x06, 0x23, 0x27, 0x7F, 0xB2, 0x75, 0xA1, 0x5F, 0x8D, 0x79, 0x68, 0x52, 0x3C, 0x34, 0x50, 0x83, 0xC9, 0x60, 0x0E, 0x6F, 0x02, 0x84, 0xA9, 0x52, 0xDD, 0xFD, 0x8C, 0x11, 0x77, 0x8D, 0x3A, 0x9A, 0xE5, 0x84, 0xE4, 0x64, 0xA8, 0x9A, 0xF4, 0xC4, 0x93, 0x9D, 0x48, 0x83, 0x77, 0x4E, 0x23, 0x8A, 0xDF, 0x53, 0x0F, 0xC8, 0xB2, 0x2F, 0xF7, 0x0E, 0xBB, 0x8F, 0x3A, 0x92, 0x48, 0xA5, 0x9F, 0x72, 0xC0, 0x5A, 0x56, 0xEA, 0xA0, 0xB6, 0x4C, 0xA8, 0x44, 0xFA, 0xD5, 0x3D, 0x13, 0x4D, 0x1F, 0x90, 0x26, 0x4E, 0xE2, 0x33, 0x95, 0x5A, 0x0F, 0x95, 0x10, 0x6B, 0x5D, 0x90, 0x8F, 0x68, 0x7E, 0x70, 0xC5, 0x69, 0xA2, 0xCE, 0x21, 0xAA, 0x25, 0x37, 0xE3, 0xC7, 0x9B, 0xB2, 0x4F, 0x35, 0x41, 0x42, 0xE3, 0x85, 0xCE, 0x4D, 0x90, 0xB0, 0xCC, 0x87, 0x6A, 0x0F, 0x77, 0x62, 0xE2, 0x83, 0x04, 0xB6, 0xE9, 0x41, 0x42, 0x8C, 0x05, 0x73, 0x6C, 0x85, 0x91, 0x9C, 0x54, 0x20, 0xEC, 0xD5, 0x7E, 0x2C, 0x19, 0x97, 0x76, 0xD7, 0xBB, 0xEB, 0xB2, 0xAB, 0x31, 0x20, 0xA9, 0x08, 0x3C, 0xA1, 0x67, 0x4A, 0x06, 0x8B, 0x37, 0x22, 0x06, 0x4C, 0x45, 0x94, 0xD8, 0xEF, 0x56, 0x2B, 0xC8, 0xA2, 0xCB, 0x79, 0xAC, 0xDD, 0x2B, 0x17, 0xEF, 0x54, 0x06, 0xE2, 0x78, 0xD7, 0xD7, 0xFF, 0x9C, 0x3B, 0xE9, 0xF0, 0x54, 0x5A, 0x8D, 0x5E, 0xF4, 0xE7, 0x43, 0x97, 0xE7, 0x96, 0xF8, 0x76, 0xD2, 0x1F, 0x23, 0x62, 0x66, 0x51, 0xB5, 0xCC, 0x2C, 0x62, 0x6A, 0x99, 0x45, 0x97, 0x6F, 0x79, 0x7E, 0x37, 0xBD, 0x52, 0x9A, 0xAF, 0x2E, 0x6A, 0xFD, 0xA7, 0x2A, 0xF4, 0xF3, 0xAF, 0x3F, 0xBD, 0x6E, 0x5C, 0xAD, 0x28, 0x8A, 0x7A, 0x77, 0xBA, 0xBC, 0xC2, 0xB9, 0x2E, 0x13, 0x37, 0x1B, 0x9F, 0xF8, 0xAA, 0x8B, 0xDC, 0xCE, 0x9F, 0x68, 0x52, 0x77, 0xDD, 0x99, 0xD3, 0xDC, 0x4C, 0x60, 0xC8, 0xED, 0xD2, 0xE0, 0x70, 0x54, 0xCC, 0x7E, 0x80, 0xAE, 0x39, 0xFB, 0x84, 0x83, 0x2A, 0xC5, 0xAC, 0x63, 0x9C, 0x45, 0xF8, 0x50, 0xA4, 0x25, 0x8F, 0xB1, 0xA4, 0xC2, 0x1F, 0x31, 0xFE, 0x87, 0x0B, 0xCC, 0x3A, 0x08, 0x3F, 0x3B, 0x19, 0x21, 0x84, 0xD3, 0x7B, 0x81, 0xD6, 0xA5, 0x5A, 0x25, 0xC8, 0x59, 0xF4, 0x96, 0x15, 0xAF, 0x65, 0xFD, 0x34, 0xBD, 0xFA, 0xAE, 0x9F, 0xD3, 0x9C, 0xF4, 0x5D, 0xD4, 0xFB, 0xAD, 0x6A, 0x41, 0xFE, 0xFE, 0x5A, 0xBF, 0xE3, 0xB0, 0x1E, 0x08, 0x76, 0xFE, 0x40, 0x4D, 0x99, 0x41, 0xB5, 0x4A, 0x67, 0xB3, 0x29, 0x7D, 0x37, 0x9A, 0xB2, 0xFD, 0xD6, 0x0C, 0xD7, 0xD6, 0xEA, 0xA1, 0xAD, 0x5A, 0xCD, 0x7D, 0x83, 0x1E, 0xFC, 0xE1, 0x9F, 0x2A, 0x91, 0x6C, 0x3F, 0xF4, 0x2C, 0x88, 0xFB, 0x87, 0x6C, 0x90, 0x1A, 0xE6, 0x1A, 0x04, 0xBB, 0x34, 0xC8, 0x83, 0x34, 0xFF, 0x2B, 0x31, 0x9A, 0x7A, 0x74, 0x64, 0x7D, 0x79, 0xD6, 0x95, 0xE0, 0xFC, 0x4E, 0x65, 0x54, 0x4A, 0x2E, 0xC2, 0xB0, 0x63, 0xF0, 0x50, 0x65, 0x35, 0x3A, 0x66, 0x5D, 0xEB, 0xD8, 0x10, 0x9B, 0x75, 0xFD, 0x23, 0x27, 0x9A, 0xC6, 0x3F, 0xC3, 0x03, 0x41, 0x2E, 0x11, 0xC9, 0xAF, 0x27, 0x8E, 0x91, 0x5E, 0x21, 0xC4, 0x36, 0xC8, 0x73, 0x69, 0x1F, 0xC9, 0xEC, 0xB9, 0x4C, 0xA9, 0xEE, 0xB9, 0x1A, 0xCF, 0xC9, 0xE0, 0xF6, 0x68, 0x8A, 0xAA, 0xA4, 0xFE, 0x14, 0xD7, 0x93, 0xBE, 0xD4, 0x15, 0x2A, 0x57, 0xCD, 0x39, 0x89, 0x9A, 0x4E, 0x17, 0x87, 0x3A, 0x7D, 0x24, 0x75, 0xC6, 0x08, 0x94, 0xE6, 0x7D, 0x55, 0x1B, 0x81, 0x33, 0x91, 0x81, 0x27, 0x6A, 0x57, 0x01, 0xF5, 0x30, 0x7D, 0xA2, 0x49, 0x46, 0x5E, 0xD3, 0x97, 0x2A, 0x6D, 0x91, 0xDD, 0xFC, 0x24, 0x83, 0xCD, 0x48, 0xC9, 0x2D, 0x4C, 0x40, 0x18, 0x96, 0xB5, 0xAA, 0xA3, 0x98, 0x32, 0x1F, 0x66, 0xEA, 0x97, 0xAB, 0x7D, 0xB1, 0x91, 0xF8, 0x2E, 0xCF, 0x71, 0x4D, 0x0D, 0xF3, 0x6F, 0xA4, 0x01, 0x8D, 0x82, 0x46, 0xA7, 0xE1, 0x1E, 0x07, 0xEF, 0x55, 0x9E, 0xE2, 0xE9, 0xDA, 0x17, 0xC6, 0x17, 0x96, 0xD6, 0x23, 0xB7, 0xF1, 0xB9, 0x44, 0x33, 0xBE, 0xA7, 0x74, 0x4E, 0xFF, 0xF7, 0x53, 0xD4, 0x09, 0xB4, 0x3B, 0x67, 0xBD, 0xAB, 0x76, 0x3A, 0x28, 0x77, 0xAA, 0x25, 0x29, 0x64, 0x4C, 0x9C, 0xF8, 0xF2, 0x26, 0xC9, 0x3D, 0xC9, 0x35, 0xAC, 0xEA, 0x93, 0xC7, 0x18, 0x4F, 0xAC, 0x3D, 0xF9, 0x09, 0x8D, 0xFF, 0x1F, 0x0D, 0x16, 0x4A, 0x83, 0x69, 0xCE, 0xC8, 0xB5, 0x13, 0x67, 0xC4, 0x5E, 0x95, 0x37, 0xA2, 0xF2, 0x9E, 0x75, 0x33, 0x0F, 0xD7, 0xC3, 0x7E, 0xEE, 0xEE, 0x66, 0x60, 0xE4, 0xE1, 0x1B, 0xCF, 0x2F, 0xAE, 0xA6, 0xA2, 0x63, 0xEA, 0xE1, 0x58, 0x9A, 0xEA, 0xCC, 0x25, 0x30, 0x95, 0x6B, 0x35, 0x26, 0x8E, 0x8A, 0xF1, 0x5A, 0xA6, 0xEF, 0x43, 0x5A, 0xD6, 0xA5, 0x6B, 0xEA, 0x95, 0xAB, 0x89, 0xB2, 0xCE, 0x13, 0xFD, 0x37, 0xF9, 0xA4, 0xB4, 0x50, 0xF4, 0xFB, 0x2A, 0x53, 0x81, 0x83, 0xE2, 0x1B, 0x25, 0xEC, 0xD4, 0xC8, 0xB1, 0x9D, 0xDE, 0xA6, 0x4C, 0x7C, 0xA4, 0xAC, 0x5F, 0x47, 0xB3, 0x7D, 0x34, 0x8A, 0x23, 0x2E, 0xC0, 0xCB, 0x25, 0xAF, 0x1C, 0x28, 0xD4, 0xCA, 0xB6, 0xDD, 0x4B, 0x42, 0xD4, 0x05, 0x24, 0xFF, 0xC9, 0xCF, 0xCF, 0x2B, 0xAD, 0xB8, 0x94, 0xDC, 0x6C, 0x5E, 0x04, 0x8C, 0xBB, 0xF9, 0x12, 0x9C, 0x4D, 0x98, 0x68, 0xFA, 0xD1, 0xBA, 0x70, 0xFA, 0x9F, 0xE8, 0xB1, 0x65, 0xF1, 0x68, 0x34, 0x1A, 0x8D, 0x46, 0x23, 0x96, 0x8A, 0xBA, 0x24, 0xEA, 0x92, 0xFF, 0x24, 0xAA, 0x7A, 0x1B, 0xDF, 0x44, 0x66, 0xD2, 0xC3, 0x49, 0xAA, 0xF0, 0xFE, 0x5F, 0xA9, 0x5C, 0x1E, 0xE9, 0x17, 0x08, 0xBC, 0x5B, 0xE1, 0x07, 0xF6, 0xAE, 0xD8, 0x03, 0xAB, 0xAB, 0xF1, 0x01, 0xCD, 0xD7, 0xCD, 0x7C, 0xFE, 0x88, 0x7E, 0xBF, 0xBC, 0xDA, 0x27, 0x63, 0xF2, 0x05, 0xB7, 0x51, 0xEF, 0xB7, 0x55, 0x13, 0xA8, 0x0B, 0x2E, 0xFB, 0x91, 0x5B, 0xAA, 0x9B, 0xA7, 0x17, 0xAF, 0xD6, 0xB3, 0x4D, 0xAB, 0xFE, 0x9A, 0xFE, 0xD1, 0x25, 0x77, 0xA0, 0x78, 0xCD, 0xEB, 0xF3, 0xA9, 0x10, 0xA3, 0xD1, 0x3F, 0x29, 0xC4, 0xE8, 0x8B, 0xA3, 0xD2, 0x90, 0x9C, 0xAB, 0xA9, 0x76, 0xE4, 0x27, 0x39, 0xCD, 0xA4, 0xF1, 0x70, 0x2A, 0x21, 0x13, 0x76, 0x93, 0x65, 0x7B, 0xBC, 0xD8, 0xB4, 0x1E, 0x79, 0xAB, 0xCD, 0x10, 0x53, 0x91, 0x5B, 0xAD, 0xBC, 0x66, 0x59, 0x6B, 0xFF, 0x78, 0xE9, 0x37, 0x92, 0xD6, 0xFB, 0xBF, 0x14, 0xE2, 0xFF, 0xFF, 0x8F, 0xDE, 0x09, 0x71, 0x02, 0xE4, 0xE4, 0xE3, 0xDE, 0x43, 0x68, 0x0B, 0x6A, 0x93, 0x1B, 0x44, 0xE6, 0x0C, 0xC3, 0x9F, 0x99, 0xFF, 0x48, 0x7B, 0xF4, 0x98, 0x79, 0x8C, 0xDD, 0x33, 0x97, 0x99, 0x99, 0xFB, 0xF6, 0xCC, 0xB6, 0x6B, 0x4E, 0xF3, 0x99, 0xFE, 0x5F, 0x4E, 0xDB, 0xA5, 0xAE, 0xBC, 0x74, 0x95, 0xBF, 0x29, 0xA5, 0x35, 0x4C, 0xC3, 0x33, 0x7C, 0xDA, 0xAC, 0xC3, 0x39, 0x8C, 0xC3, 0x5D, 0x4A, 0x4A, 0x0A, 0xA7, 0x7A, 0x65, 0x85, 0xDF, 0x2B, 0xCC, 0x5F, 0x61, 0xE6, 0x7B, 0x6C, 0x53, 0x52, 0x52, 0x52, 0x58, 0xC5, 0x63, 0xEE, 0x56, 0x61, 0xBE, 0x0A, 0x73, 0xAB, 0x30, 0xF3, 0x6F, 0xE3, 0xBF, 0xAD, 0xFC, 0xCA, 0x36, 0x15, 0x04, 0x7B, 0x8A, 0x7C, 0x90, 0xF8, 0x94, 0x0B, 0xBD, 0x32, 0x4A, 0x10, 0xD0, 0xE7, 0xD3, 0x73, 0x7A, 0x83, 0x72, 0xA3, 0x73, 0x94, 0x9C, 0xDF, 0xE0, 0x1C, 0x85, 0xD6, 0x22, 0xB6, 0x47, 0xED, 0x41, 0x7B, 0x50, 0xD4, 0x7B, 0xD4, 0x50, 0x94, 0x4D, 0xA1, 0xE1, 0x52, 0x35, 0xA5, 0x86, 0x63, 0x9A, 0xD2, 0xDB, 0x6F, 0x3A, 0xD4, 0x53, 0x66, 0xB8, 0xA3, 0x74, 0x28, 0x21, 0x52, 0x2A, 0x1C, 0x14, 0x4F, 0x1E, 0xD4, 0x55, 0x3C, 0xA8, 0x4F, 0x20, 0x54, 0x10, 0xA9, 0x20, 0x02, 0x96, 0xAA, 0xA8, 0x64, 0x26, 0xD6, 0x54, 0xB1, 0x8C, 0x90, 0xA1, 0x91, 0x01, 0x80, 0x09, 0x10, 0x00, 0xF3, 0x85, 0x01, 0x14, 0x0A, 0x89, 0x83, 0x51, 0x18, 0x05, 0xE2, 0x44, 0x9A, 0x7E, 0x94, 0xE3, 0x87, 0xDB, 0x09, 0x26, 0x50, 0x0D, 0xC6, 0x90, 0x24, 0x0F, 0x21, 0x03, 0x8C, 0x01, 0x8A, 0x08, 0x40, 0x30, 0x00, 0x03, 0x00, 0x08, 0x80, 0x00, 0x0C, 0x44, 0x3B, 0x95, 0xDE, 0x98, 0x71, 0xE3, 0xE0, 0x3E, 0x45, 0x4C, 0x2B, 0x47, 0x4C, 0x4E, 0x64, 0xEA, 0xDB, 0xCA, 0x12, 0x15, 0xCB, 0xB4, 0x1B, 0xF7, 0x96, 0x90, 0xA3, 0xC5, 0xD1, 0x0C, 0x66, 0x2C, 0x10, 0xBB, 0x18, 0xB4, 0x2D, 0x0F, 0x06, 0x48, 0x7B, 0x95, 0xE1, 0x1A, 0x05, 0x7B, 0x95, 0x53, 0x71, 0xC1, 0x34, 0xE9, 0xA9, 0x2B, 0x31, 0x8D, 0x97, 0xC9, 0x33, 0xE0, 0x4C, 0x71, 0x03, 0x79, 0x55, 0xF0, 0x80, 0x90, 0x8F, 0x0A, 0x65, 0xC5, 0xCC, 0x7B, 0xCD, 0xB1, 0x03, 0x43, 0xE3, 0x8D, 0x36, 0x99, 0x6D, 0x72, 0xB7, 0x90, 0x50, 0xEE, 0x71, 0x27, 0x6A, 0xB1, 0x99, 0x53, 0x77, 0x1D, 0xC3, 0xAB, 0xA3, 0x9D, 0xAC, 0x72, 0xD5, 0x39, 0x7B, 0x8E, 0xB4, 0xD8, 0xC2, 0x71, 0x25, 0x8B, 0xBB, 0x9E, 0xA2, 0x71, 0x91, 0x67, 0xD3, 0x3A, 0xDB, 0xAF, 0xE8, 0x50, 0x09, 0x7C, 0x67, 0x27, 0x9B, 0xB4, 0x4E, 0x1C, 0x18, 0x2A, 0xD8, 0x8F, 0x05, 0x21, 0x07, 0xAD, 0x44, 0x0B, 0x68, 0xDE, 0xC5, 0xF6, 0xC8, 0x52, 0x3A, 0x8C, 0xE7, 0xC3, 0x42, 0xD1, 0xBC, 0x78, 0xC9, 0x0B, 0xF7, 0xC3, 0x82, 0x52, 0xFF, 0x13, 0xD3, 0x83, 0xA1, 0x8D, 0x23, 0x9A, 0xA3, 0x94, 0x46, 0x88, 0xE4, 0x83, 0x77, 0xE1, 0xC2, 0x60, 0x32, 0x8E, 0x25, 0x4D, 0xEC, 0x9D, 0x50, 0xE5, 0xE5, 0x4E, 0x98, 0x5C, 0x8D, 0xCD, 0x70, 0x81, 0x95, 0x58, 0xA5, 0xD6, 0x22, 0xAC, 0x96, 0x70, 0x5B, 0x44, 0x25, 0xD4, 0x2E, 0x43, 0x33, 0xDD, 0x6D, 0x7C, 0x62, 0x38, 0x03, 0xA8, 0x02, 0x32, 0xF7, 0xD9, 0xB0, 0xBC, 0x59, 0x89, 0x59, 0x74, 0x14, 0xEE, 0x8D, 0x84, 0x58, 0xE4, 0xD4, 0x2A, 0xEC, 0x6C, 0xCA, 0xAB, 0x72, 0x50, 0x58, 0x7C, 0x04, 0x48, 0xF8, 0x7F, 0x6D, 0x02, 0x6F, 0x29, 0x0E, 0xA0, 0x61, 0x85, 0x4A, 0x88, 0x31, 0x54, 0x9F, 0xE7, 0x30, 0x91, 0xF9, 0x28, 0xDF, 0x86, 0xCD, 0x17, 0x0A, 0xA2, 0xA1, 0xDF, 0x27, 0xF0, 0xDF, 0x04, 0xA0, 0x02, 0x22, 0xF7, 0xA4, 0xA5, 0xDD, 0x12, 0x52, 0xBF, 0x34, 0xF9, 0x56, 0x80, 0x09, 0xFB, 0x7F, 0xAA, 0x93, 0x06, 0xA0, 0xAB, 0x9B, 0xB6, 0x02, 0x3D, 0x70, 0x2F, 0x20, 0x76, 0x07, 0xB8, 0xF6, 0x22, 0x4C, 0x76, 0xC5, 0x33, 0x66, 0xA1, 0xFC, 0x8A, 0x0C, 0xAF, 0x97, 0x29, 0xD4, 0xAB, 0x02, 0x9C, 0x4E, 0x6A, 0xF6, 0x0E, 0x7F, 0xB8, 0x26, 0x71, 0x49, 0xD9, 0x64, 0x88, 0x20, 0xA4, 0xC6, 0x21, 0xEA, 0x6E, 0xED, 0x68, 0x92, 0xB5, 0x08, 0x08, 0x23, 0x31, 0x18, 0x33, 0x7D, 0x62, 0x6D, 0x13, 0xE5, 0xF0, 0x98, 0x05, 0x7C, 0x5A, 0xBE, 0xF6, 0x8D, 0xFD, 0x9F, 0x44, 0x2B, 0xFC, 0x47, 0xD6, 0x20, 0xE1, 0x2D, 0xAA, 0x3F, 0x99, 0x12, 0xF0, 0x75, 0x9D, 0xD4, 0x65, 0xF6, 0xEE, 0xFB, 0xA7, 0xD4, 0x53, 0x34, 0xE6, 0xC8, 0x5C, 0x8C, 0xDC, 0x01, 0x5A, 0xC5, 0x27, 0x31, 0xC9, 0xD2, 0x3B, 0x2D, 0x1B, 0x68, 0x92, 0x22, 0x4A, 0xBF, 0x77, 0x78, 0x51, 0xAD, 0x20, 0xBC, 0x97, 0xAB, 0x0B, 0xFA, 0xF1, 0xA6, 0xDC, 0x91, 0x47, 0x5B, 0x27, 0xCC, 0x22, 0xAA, 0xF1, 0xCF, 0xCD, 0x5D, 0x18, 0xFA, 0x73, 0xEB, 0xF4, 0xCE, 0xF4, 0x50, 0x30, 0x6C, 0x2D, 0xD4, 0xF6, 0xA6, 0x0A, 0xB6, 0xB3, 0x13, 0xDF, 0xD6, 0x6F, 0x82, 0x0A, 0xBC, 0x41, 0x87, 0x71, 0x7E, 0x4D, 0xF4, 0x29, 0x85, 0xDC, 0x24, 0xD4, 0x58, 0xF8, 0x80, 0x0A, 0x35, 0x11, 0x84, 0xDC, 0xFE, 0xF8, 0x41, 0x62, 0x9D, 0x10, 0x65, 0x84, 0xB4, 0x55, 0x74, 0xCF, 0x52, 0xBE, 0x2E, 0xB7, 0x2A, 0xA6, 0x8A, 0xB1, 0xDD, 0xE2, 0xEB, 0x95, 0x3E, 0xDA, 0x19, 0xC8, 0x09, 0xD4, 0x9A, 0xDA, 0xE0, 0x94, 0x97, 0xDB, 0x80, 0x63, 0x19, 0xEC, 0xB3, 0xCA, 0x71, 0x6E, 0xBA, 0x28, 0xA6, 0xA4, 0x18, 0x19, 0x46, 0xB6, 0x84, 0x38, 0x4B, 0xD3, 0x84, 0xBF, 0x08, 0x20, 0x86, 0x1E, 0xEC, 0xEC, 0x54, 0x05, 0xBC, 0x49, 0x2E, 0x51, 0xE4, 0x7F, 0x44, 0x46, 0xFB, 0xB9, 0xD5, 0xCA, 0xCC, 0x18, 0x55, 0xBC, 0x8A, 0x34, 0x39, 0xED, 0x92, 0x2C, 0x39, 0x0A, 0x36, 0xB7, 0x05, 0xCD, 0xF6, 0x47, 0x92, 0x03, 0xE4, 0x3A, 0x3C, 0x1F, 0x5A, 0x85, 0xD5, 0x32, 0x1D, 0x89, 0xCA, 0xCD, 0xDB, 0xEB, 0xCE, 0x87, 0xE0, 0x41, 0x9B, 0xB9, 0xA0, 0xEC, 0x53, 0x96, 0x3D, 0xC5, 0x86, 0xD8, 0x22, 0x47, 0xA8, 0x99, 0x7D, 0xEF, 0x47, 0xC3, 0xB6, 0xDD, 0xA0, 0xD1, 0x44, 0x7B, 0xAD, 0xB2, 0x31, 0xA1, 0xE7, 0x91, 0x54, 0xD3, 0x93, 0xB9, 0x32, 0x07, 0xDC, 0xF2, 0xC2, 0x9C, 0x69, 0x02, 0x1C, 0x47, 0x21, 0x59, 0x0E, 0x5E, 0x16, 0x49, 0x7D, 0x52, 0x95, 0xE7, 0xB4, 0x5A, 0x97, 0x13, 0xA5, 0x20, 0xB3, 0x0A, 0x82, 0x58, 0x98, 0xD3, 0x5E, 0x25, 0x45, 0xF6, 0xF1, 0x07, 0x0A, 0x6B, 0xF6, 0xDA, 0xE4, 0x6C, 0x4F, 0xA3, 0x84, 0x9B, 0xE9, 0x90, 0xED, 0x79, 0x22, 0xB9, 0x26, 0x0D, 0x10, 0xB9, 0xA3, 0x9B, 0x4B, 0x9B, 0x10, 0xE9, 0x15, 0xEE, 0xC2, 0xE6, 0xE2, 0x14, 0x8D, 0x43, 0xA7, 0x96, 0xDD, 0xA0, 0x41, 0x9F, 0xE2, 0x1D, 0x03, 0xB0, 0x43, 0x80, 0x37, 0x5E, 0x97, 0xEB, 0x16, 0xE4, 0x22, 0xA3, 0xA8, 0x77, 0x16, 0xBC, 0xF1, 0xD1, 0xA9, 0x9B, 0x11, 0xEB, 0xA3, 0x81, 0x02, 0x5E, 0xDD, 0x0C, 0xA4, 0xF5, 0x85, 0x65, 0x06, 0x11, 0x76, 0xF0, 0x6A, 0x37, 0x6F, 0x28, 0x6F, 0x94, 0xC5, 0x25, 0x9A, 0x63, 0xD5, 0xB2, 0xBD, 0xC6, 0xCB, 0x51, 0xEF, 0x0C, 0x61, 0x45, 0x11, 0xC1, 0x9D, 0x3C, 0x9D, 0xEB, 0x12, 0x25, 0x83, 0xED, 0xD2, 0xC5, 0xAE, 0xDC, 0x6F, 0x44, 0xA8, 0xE0, 0xBC, 0xA2, 0x77, 0xB8, 0x0F, 0x18, 0x22, 0x67, 0x7B, 0xCD, 0xE2, 0x89, 0x67, 0x67, 0x17, 0xFD, 0x63, 0x64, 0x80, 0xF6, 0xBE, 0xB2, 0xCA, 0xF8, 0xD1, 0x1B, 0x39, 0xD7, 0x66, 0xB8, 0x7E, 0x25, 0x9C, 0x2C, 0x0B, 0xC3, 0xCE, 0xE5, 0xA1, 0x74, 0x79, 0x7C, 0xDF, 0x07, 0xC1, 0x84, 0x82, 0x56, 0xB7, 0x34, 0x80, 0xDF, 0xBB, 0x85, 0x81, 0x0C, 0x04, 0x30, 0x78, 0x1F, 0x44, 0x64, 0x03, 0xB9, 0x89, 0x48, 0x92, 0x1E, 0xC8, 0x9F, 0xC5, 0x6D, 0x63, 0x0D, 0x8E, 0xF7, 0x71, 0x09, 0x41, 0xD1, 0x25, 0x58, 0x35, 0xDE, 0x4E, 0x8E, 0xBA, 0x8D, 0x38, 0x54, 0xAF, 0x80, 0x75, 0x54, 0x69, 0x6E, 0x4E, 0x4B, 0xE4, 0xCB, 0x89, 0xFE, 0x05, 0x7A, 0x59, 0xB2, 0xB9, 0xA7, 0xD3, 0x61, 0x89, 0xDB, 0x7D, 0x08, 0x79, 0xFE, 0xE6, 0x0F, 0x1E, 0x80, 0x95, 0xCD, 0x02, 0x4A, 0xA0, 0xC6, 0xCC, 0x55, 0x93, 0x96, 0xA2, 0xFA, 0x29, 0x45, 0xC7, 0xCF, 0x43, 0x2C, 0x2D, 0x9F, 0x33, 0xC8, 0x54, 0x72, 0xAC, 0x69, 0xAF, 0xF7, 0xCA, 0xFF, 0x43, 0x94, 0x78, 0xDE, 0xAF, 0xF3, 0x84, 0xFB, 0xBB, 0x9F, 0x65, 0x8E, 0x52, 0x14, 0x44, 0xF1, 0x30, 0x16, 0x42, 0xFA, 0x89, 0x8D, 0x8D, 0x0D, 0x3A, 0x66, 0x93, 0x27, 0x5C, 0x4D, 0x32, 0x3D, 0x7C, 0xD2, 0x7B, 0xDA, 0xFC, 0xD1, 0xE0, 0xB0, 0xB6, 0x40, 0xD1, 0xB9, 0xAD, 0x8D, 0xC4, 0xBF, 0x09, 0x9E, 0xE8, 0x25, 0xC2, 0xEA, 0x36, 0xC2, 0xAD, 0x07, 0x7F, 0x51, 0xBD, 0xD9, 0x4F, 0x8B, 0x2D, 0x60, 0xB6, 0xA7, 0x0A, 0x1E, 0x8E, 0xFD, 0x79, 0x80, 0x77, 0x39, 0x39, 0x3F, 0x92, 0xED, 0x95, 0x6A, 0xFB, 0x11, 0x71, 0x18, 0xA4, 0x3C, 0x37, 0x3D, 0xBB, 0xAD, 0x12, 0x81, 0x81, 0xA2, 0xDF, 0x6C, 0x3C, 0xE4, 0xAE, 0x74, 0x3B, 0x7F, 0x4F, 0x3B, 0x2A, 0x16, 0xB9, 0xA8, 0xA7, 0xB4, 0xD1, 0xF1, 0x5F, 0x4D, 0xA0, 0x97, 0xF2, 0x6B, 0x00, 0x4D, 0x8E, 0xFF, 0xDE, 0x97, 0xD9, 0x2E, 0x49, 0x8A, 0x99, 0x07, 0xDB, 0xEE, 0x26, 0x7B, 0xAC, 0x1A, 0xFB, 0x04, 0xD1, 0x44, 0xA9, 0xAC, 0x1A, 0xCA, 0x1F, 0x66, 0x41, 0xD5, 0x51, 0xEB, 0x3C, 0xD8, 0xCC, 0x66, 0x14, 0x0D, 0xCF, 0xAD, 0x1A, 0x8C, 0x4E, 0xE4, 0x3A, 0xF5, 0xD7, 0xC6, 0x2E, 0x8F, 0xEB, 0xE7, 0x43, 0xB7, 0x46, 0x30, 0xDD, 0x44, 0x8F, 0xB2, 0x90, 0x2B, 0xCE, 0xC3, 0x0F, 0x4E, 0xD0, 0x8E, 0xA9, 0xC0, 0xCD, 0x96, 0x69, 0xB1, 0xA4, 0x8F, 0x1F, 0x6E, 0x43, 0x1F, 0x17, 0x13, 0xDE, 0xEB, 0xD2, 0xB1, 0xED, 0x01, 0xD5, 0x58, 0x22, 0xD7, 0x93, 0x77, 0x37, 0x25, 0x29, 0x0D, 0x07, 0x1E, 0x28, 0xA6, 0x96, 0x03, 0xD4, 0x4A, 0xCD, 0x5A, 0x4B, 0x3D, 0x28, 0x5A, 0xF2, 0xB3, 0xC1, 0x6E, 0x99, 0xAA, 0x78, 0x08, 0x8D, 0x77, 0xB3, 0x0A, 0x86, 0xBF, 0x41, 0x42, 0x96, 0x4E, 0x35, 0xA1, 0xC5, 0x43, 0xB5, 0xAE, 0x38, 0xE7, 0xBB, 0x46, 0xB7, 0x79, 0x19, 0x56, 0x7C, 0x0B, 0x4F, 0xFC, 0x97, 0xFD, 0xD4, 0xCC, 0x5E, 0xFD, 0x51, 0xB5, 0x7A, 0xD7, 0x6C, 0x5C, 0x45, 0x7E, 0x20, 0x3A, 0xA7, 0x6F, 0xF8, 0xD4, 0x30, 0xBB, 0x78, 0xB5, 0xA2, 0x25, 0x9E, 0x75, 0x93, 0x3B, 0xB0, 0x05, 0x90, 0x55, 0xCA, 0x57, 0xDB, 0x97, 0xC5, 0x87, 0xF8, 0x6C, 0xA1, 0x20, 0x9F, 0xBB, 0xD5, 0x54, 0xB1, 0xD4, 0xD6, 0xD6, 0xCA, 0xF3, 0xBA, 0xE6, 0x0B, 0x66, 0xFA, 0x40, 0xFD, 0xAB, 0xE6, 0x87, 0xC2, 0x80, 0x0F, 0xAE, 0xA4, 0x33, 0xC0, 0xC3, 0x58, 0x42, 0x51, 0xC8, 0x23, 0xA0, 0x4C, 0x2A, 0x62, 0x05, 0x1B, 0x97, 0x26, 0x66, 0x1D, 0x81, 0x2B, 0x1B, 0xA8, 0x4D, 0x69, 0x9E, 0x16, 0xB0, 0x4A, 0xDA, 0x78, 0xF2, 0x00, 0x78, 0x71, 0x9E, 0xB4, 0x9E, 0x9C, 0xE8, 0xE7, 0xDC, 0x28, 0xE1, 0x92, 0xC6, 0xDB, 0xBB, 0xAE, 0x36, 0x81, 0x51, 0x65, 0x3E, 0xFF, 0xD4, 0x5E, 0x22, 0x07, 0x16, 0x6A, 0xA8, 0x01, 0x20, 0x0B, 0x14, 0xB7, 0x50, 0xAF, 0xE7, 0x89, 0x6C, 0x0D, 0x6D, 0xA8, 0x5F, 0x32, 0x41, 0xA2, 0xD6, 0x0A, 0x8B, 0x1E, 0xDB, 0xEB, 0x1C, 0xC0, 0x12, 0x5B, 0xBF, 0x19, 0xAF, 0x4E, 0x7D, 0x34, 0x33, 0x8E, 0x33, 0x71, 0x1F, 0xF1, 0x45, 0x69, 0x68, 0x7D, 0xC1, 0x66, 0x07, 0xC5, 0x58, 0xEF, 0x78, 0x58, 0x0A, 0x87, 0x9B, 0x6C, 0xA6, 0xF2, 0xF7, 0x9A, 0x70, 0x93, 0xFE, 0x2D, 0x6E, 0xAE, 0x0F, 0x43, 0x7F, 0x70, 0xCF, 0x86, 0x95, 0x41, 0x26, 0xAC, 0xA1, 0xC1, 0x87, 0xBE, 0x74, 0x68, 0xC2, 0x94, 0xEC, 0x51, 0x5D, 0xFA, 0xB0, 0x32, 0x34, 0xA7, 0x3F, 0x17, 0x1C, 0xA4, 0xB3, 0x42, 0x6C, 0x76, 0xF2, 0x3D, 0x11, 0x51, 0x57, 0x9F, 0xEF, 0x36, 0x73, 0x0A, 0xD3, 0x1E, 0x09, 0xD1, 0x74, 0x62, 0xC4, 0xB8, 0xF4, 0x1C, 0x14, 0x7B, 0xAC, 0x12, 0x4D, 0x8D, 0x1C, 0xCD, 0x67, 0xF6, 0x2D, 0x0F, 0x2F, 0x0C, 0x54, 0x78, 0x0C, 0x39, 0xCD, 0xBC, 0x50, 0x2C, 0xCB, 0x1F, 0xA8, 0x93, 0xFF, 0x77, 0x38, 0x8B, 0x19, 0xEF, 0x56, 0x7A, 0xD8, 0xB8, 0xB4, 0x9F, 0x4D, 0xDD, 0xC9, 0x1F, 0x09, 0x2B, 0x0E, 0xC0, 0x84, 0x95, 0x0B, 0xCF, 0xB3, 0x6C, 0x83, 0xCD, 0x1E, 0x4E, 0x1B, 0x3A, 0x55, 0x81, 0xA7, 0x48, 0x9B, 0x74, 0x93, 0xEC, 0x9B, 0x65, 0x62, 0x11, 0x2C, 0x92, 0x01, 0x54, 0xB6, 0x9E, 0x63, 0x56, 0xA4, 0x0F, 0xCB, 0x82, 0xE0, 0x9B, 0x3A, 0x33, 0x6E, 0x86, 0x0C, 0x4C, 0xB3, 0x02, 0xF8, 0x0F, 0x43, 0xC5, 0x81, 0xF0, 0x50, 0x39, 0xB6, 0x1D, 0xD7, 0xEF, 0xEF, 0xB1, 0xF6, 0x00, 0x53, 0x56, 0x48, 0xE7, 0x44, 0x6A, 0x1F, 0xCB, 0xC9, 0x98, 0x57, 0xA4, 0xE5, 0xBE, 0xD4, 0x64, 0xD8, 0x6B, 0xBF, 0x4D, 0x7A, 0x74, 0xE1, 0x36, 0x08, 0xC8, 0x19, 0xF4, 0xC0, 0x28, 0xCA, 0xB3, 0x9E, 0x87, 0xFA, 0x78, 0x83, 0x49, 0xED, 0x3E, 0x84, 0xF9, 0x54, 0xD6, 0x8D, 0x4F, 0x8C, 0xC8, 0xC4, 0x77, 0xCB, 0x56, 0xE9, 0x1E, 0x73, 0x26, 0x81, 0xFE, 0xE0, 0x9E, 0xF0, 0x3E, 0x32, 0x42, 0x06, 0x29, 0x20, 0xC7, 0x88, 0xE6, 0x7F, 0x75, 0x1F, 0xED, 0x4F, 0x87, 0x82, 0x2C, 0x35, 0x06, 0x20, 0x12, 0x80, 0x27, 0x5A, 0x44, 0x1C, 0x34, 0x66, 0xE2, 0x2E, 0x2D, 0xD5, 0xAB, 0x33, 0xC0, 0xDD, 0x4A, 0xE3, 0x30, 0x75, 0x19, 0xF8, 0x7F, 0xF8, 0x16, 0x04, 0xD6, 0xBE, 0xFA, 0xF6, 0x37, 0xF1, 0xFB, 0x55, 0x76, 0x84, 0xBE, 0x90, 0x7F, 0xDF, 0x49, 0x9A, 0x84, 0xD6, 0x4D, 0x0D, 0x2B, 0x1F, 0x5D, 0x94, 0xD4, 0x4A, 0x66, 0x26, 0x66, 0x52, 0x36, 0x15, 0xF8, 0x69, 0x4E, 0x9B, 0x5E, 0xAE, 0x35, 0x60, 0x43, 0x92, 0xF8, 0x8A, 0xF3, 0x19, 0xED, 0xCA, 0x48, 0xFE, 0x60, 0x15, 0x55, 0x68, 0x37, 0x7A, 0xF7, 0xA1, 0xE5, 0x12, 0x5A, 0xDC, 0x32, 0xFE, 0x65, 0x43, 0x7D, 0x9F, 0xE8, 0x6C, 0x92, 0xDD, 0xD3, 0x3F, 0x1A, 0x0B, 0x95, 0xAD, 0x74, 0xDE, 0x16, 0x77, 0xA5, 0xBA, 0x88, 0x67, 0x35, 0x1C, 0xA3, 0xBE, 0xB9, 0x0C, 0xC1, 0x38, 0x6E, 0x31, 0x0A, 0x16, 0xD0, 0x43, 0x03, 0xE3, 0x3B, 0xF0, 0xFA, 0x1E, 0xCE, 0xB2, 0x0A, 0x92, 0x60, 0x85, 0x1F, 0xBB, 0xD7, 0xBB, 0x73, 0xD7, 0xA8, 0x29, 0x09, 0x4E, 0x19, 0x58, 0x35, 0x7C, 0x4F, 0x52, 0x2C, 0x1F, 0xA6, 0xE1, 0x13, 0x5A, 0x9F, 0x34, 0x6A, 0xB5, 0x44, 0x7D, 0xC0, 0xE5, 0x47, 0xF8, 0x79, 0x4D, 0xBC, 0xBF, 0xB9, 0xD0, 0x7D, 0xD9, 0xA9, 0x83, 0x6F, 0xAB, 0xBB, 0xF9, 0xAE, 0xFE, 0x0D, 0x85, 0xE2, 0x98, 0x22, 0x84, 0x58, 0xC1, 0xD8, 0xF3, 0x7F, 0xC0, 0x40, 0xA8, 0xE3, 0xFB, 0x1C, 0x9D, 0xE7, 0xA5, 0xE7, 0xEC, 0x33, 0x75, 0x25, 0x52, 0xC4, 0xC6, 0x5B, 0x71, 0xFD, 0x69, 0x20, 0xA5, 0x42, 0x1C, 0x54, 0xCF, 0x5B, 0x21, 0x30, 0x4E, 0xE3, 0x71, 0xAA, 0x76, 0x34, 0x1D, 0x2F, 0x80, 0xCF, 0xCB, 0x83, 0xEF, 0x04, 0xF4, 0xC5, 0x0F, 0x53, 0x42, 0x03, 0xFD, 0xF2, 0x47, 0xD0, 0xB9, 0x0B, 0x53, 0xEB, 0xC1, 0x11, 0xBA, 0xCB, 0x73, 0x60, 0x75, 0xE5, 0xF8, 0x43, 0x50, 0xBE, 0xCB, 0xF6, 0xC7, 0xA5, 0x18, 0xD4, 0xD9, 0x0F, 0xA8, 0x2B, 0xC7, 0x2E, 0x5F, 0xF8, 0x6F, 0xCD, 0x2D, 0xD2, 0x78, 0x6F, 0xCD, 0x78, 0x0B, 0x70, 0x22, 0xFA, 0x85, 0x93, 0xB5, 0x44, 0xB6, 0x4B, 0x65, 0x1F, 0x49, 0x07, 0x2E, 0x77, 0xE9, 0xF0, 0x5A, 0xBC, 0xE6, 0x76, 0xDD, 0x03, 0xF1, 0x86, 0x66, 0x4B, 0x34, 0x72, 0x75, 0x89, 0x02, 0x31, 0x27, 0xC7, 0x9A, 0x39, 0x91, 0xBE, 0xBC, 0xC9, 0x5F, 0x83, 0xE1, 0x29, 0x43, 0x73, 0x6C, 0x4E, 0xB9, 0x4B, 0x0F, 0xD6, 0xD5, 0x5C, 0x55, 0xA1, 0x26, 0x95, 0xAA, 0x29, 0xD0, 0xD4, 0x2C, 0xBC, 0x94, 0x9C, 0xEA, 0xED, 0x02, 0x68, 0x7D, 0x67, 0xB0, 0xC0, 0xF4, 0x9D, 0x19, 0xA7, 0x6D, 0x6B, 0x5C, 0xEC, 0x5F, 0x44, 0xCF, 0xC9, 0x07, 0x66, 0x70, 0xD6, 0x78, 0x86, 0x36, 0xA7, 0x19, 0x09, 0xC6, 0x83, 0x61, 0xF2, 0xAD, 0x24, 0x52, 0x02, 0x6B, 0x2C, 0x33, 0xEE, 0x39, 0x2F, 0x41, 0xD8, 0x15, 0x28, 0xA1, 0x33, 0x8A, 0x58, 0x9D, 0x01, 0x7C, 0xB2, 0x28, 0xB1, 0x2D, 0x93, 0x4F, 0xCD, 0x46, 0xA2, 0x59, 0xB4, 0x18, 0xBB, 0xE5, 0x08, 0xC1, 0xC9, 0x23, 0x00, 0x73, 0xB3, 0x1A, 0xF9, 0x01, 0xC9, 0x22, 0xF2, 0xDB, 0xEA, 0x01, 0xD2, 0x35, 0x7C, 0x2C, 0x69, 0x12, 0xB8, 0x0C, 0x2C, 0x3C, 0x4E, 0xA7, 0x04, 0xD4, 0x80, 0x8C, 0xF2, 0x2A, 0x79, 0x88, 0x6E, 0x86, 0x64, 0x21, 0xF2, 0x56, 0x13, 0xC2, 0x6C, 0x41, 0xB6, 0xB8, 0xC5, 0xFA, 0xC1, 0x78, 0xB3, 0x28, 0x1E, 0x1B, 0xA8, 0x17, 0xC8, 0xDC, 0x8F, 0x5B, 0x83, 0x41, 0xED, 0x81, 0x32, 0x6C, 0x03, 0x91, 0x3B, 0xCA, 0x3A, 0xD3, 0x3A, 0x9D, 0x87, 0x74, 0x33, 0x6D, 0xAD, 0x4C, 0x3E, 0x39, 0x65, 0xDF, 0x00, 0x50, 0x4D, 0x5D, 0x18, 0x25, 0x8E, 0xAF, 0xE7, 0xEE, 0x22, 0x43, 0xEA, 0x30, 0x20, 0x98, 0x58, 0xEB, 0x21, 0x5E, 0xE2, 0x54, 0xB8, 0x1A, 0x51, 0xA6, 0x79, 0x83, 0xD4, 0xE6, 0xD0, 0x73, 0xDA, 0xC9, 0xD1, 0x53, 0xFC, 0x0A, 0x0C, 0xDA, 0x35, 0x0F, 0x0A, 0xC4, 0xDF, 0x65, 0x60, 0xD2, 0x96, 0x4B, 0x16, 0xE5, 0x99, 0xAB, 0x71, 0x27, 0x43, 0xDF, 0x48, 0xB6, 0x2F, 0xFB, 0x3F, 0x7E, 0x0C, 0x46, 0xE9, 0x3E, 0x1C, 0x07, 0xBA, 0xB2, 0x6E, 0x2A, 0x77, 0xB4, 0x46, 0x3A, 0x6E, 0x22, 0x8C, 0x29, 0xA6, 0x55, 0x0C, 0xC6, 0x3E, 0x42, 0x26, 0x5B, 0xF5, 0x14, 0x38, 0xF5, 0x26, 0x1E, 0xA4, 0xC1, 0x9A, 0x87, 0xA5, 0xCB, 0x4C, 0x8E, 0x22, 0x72, 0x43, 0x8A, 0xF8, 0x65, 0x25, 0x17, 0xBD, 0xAB, 0x07, 0xAF, 0x55, 0x9C, 0x7D, 0x24, 0xA2, 0x16, 0x82, 0xA7, 0xBA, 0xB2, 0x20, 0x8D, 0xA8, 0x4D, 0x99, 0x87, 0x85, 0xE1, 0x2E, 0x2E, 0x2F, 0xA7, 0xC8, 0x23, 0xDA, 0x4A, 0xEE, 0x24, 0xFE, 0x53, 0x27, 0xB9, 0x3F, 0x4C, 0x4F, 0x1C, 0x92, 0xF7, 0xA1, 0x2A, 0x0A, 0xB3, 0xE9, 0x58, 0xA0, 0xD9, 0x38, 0x13, 0x9E, 0x6A, 0xC2, 0x70, 0x72, 0xDA, 0x0A, 0xDC, 0xD1, 0x75, 0x92, 0x7B, 0xE2, 0x58, 0xBC, 0xB8, 0xA9, 0xBD, 0xE8, 0xA3, 0x50, 0x6D, 0x0F, 0x28, 0x34, 0xEA, 0x2D, 0x90, 0x56, 0x48, 0x25, 0xFE, 0xC8, 0xF4, 0x92, 0xD3, 0xB7, 0x22, 0x77, 0xAD, 0x7E, 0xDD, 0xEF, 0x1F, 0x82, 0x03, 0x9E, 0x17, 0x94, 0xFC, 0x84, 0xCC, 0x42, 0xDE, 0x4B, 0x3D, 0x06, 0xAA, 0x7B, 0x8D, 0x63, 0xA5, 0x63, 0x27, 0x77, 0xD3, 0xB2, 0xD3, 0x69, 0x15, 0xC2, 0x6E, 0x05, 0xBC, 0x34, 0x94, 0x6B, 0xAC, 0xEF, 0xDA, 0x9D, 0xBA, 0x45, 0xC4, 0xDC, 0x94, 0xF0, 0x86, 0xDB, 0x38, 0x35, 0x28, 0x23, 0xB9, 0x90, 0x3F, 0xDE, 0x4C, 0xEE, 0xB2, 0x9D, 0x43, 0x72, 0x58, 0xBD, 0xE4, 0xA2, 0xBA, 0x62, 0x98, 0x59, 0xB5, 0x59, 0x19, 0xD2, 0x66, 0x7A, 0x27, 0x90, 0xD9, 0x02, 0xD8, 0xAC, 0xC6, 0x59, 0x39, 0x4F, 0x7A, 0x31, 0x4E, 0x23, 0xC2, 0x83, 0x8E, 0x72, 0xA4, 0xAD, 0xAD, 0x76, 0x95, 0xE4, 0xD0, 0x3A, 0xF1, 0xBC, 0x54, 0x6E, 0xC2, 0x6F, 0x65, 0xAA, 0x31, 0xC4, 0x6D, 0x73, 0xAB, 0x42, 0x15, 0x74, 0x90, 0x68, 0xDC, 0x6D, 0xEF, 0x72, 0xB1, 0xB4, 0xA5, 0x30, 0xF4, 0xEA, 0x8B, 0x18, 0x1D, 0xCE, 0xD8, 0x66, 0xA1, 0x88, 0x74, 0x8F, 0x47, 0x80, 0x2A, 0x8D, 0x95, 0xF0, 0x86, 0x23, 0x89, 0xB1, 0xA3, 0x34, 0x83, 0xF8, 0x89, 0x8C, 0xF9, 0xEB, 0x26, 0xCA, 0x3E, 0x64, 0x03, 0x61, 0xFB, 0x85, 0xCC, 0x0B, 0x1C, 0xA6, 0x05, 0xEA, 0x09, 0x9C, 0xA8, 0x50, 0xB6, 0x8B, 0x5E, 0x9E, 0x3E, 0x3B, 0x8E, 0xAD, 0x7D, 0x0D, 0x5A, 0x6E, 0x27, 0xC9, 0x4E, 0xFF, 0x53, 0x06, 0xAB, 0x40, 0x1F, 0x32, 0x57, 0xCA, 0xD6, 0x3E, 0x1F, 0xF1, 0xED, 0x21, 0xF6, 0xAF, 0x6D, 0x65, 0x14, 0xF5, 0x47, 0x26, 0xDC, 0x9B, 0x90, 0x66, 0xE5, 0x32, 0x4D, 0x92, 0xB5, 0xD9, 0x85, 0x48, 0x18, 0x17, 0x96, 0x5F, 0xC2, 0xD5, 0xF7, 0xD0, 0x11, 0xBE, 0xE5, 0x55, 0x00, 0x94, 0xBD, 0x69, 0x7B, 0x17, 0x7B, 0xF1, 0xB9, 0xA8, 0x46, 0x5E, 0xD1, 0x6D, 0x2F, 0x36, 0xB1, 0x60, 0x0C, 0x96, 0x3C, 0x0D, 0x35, 0x17, 0x70, 0x74, 0x5B, 0xA5, 0x1B, 0x99, 0x8E, 0x74, 0xED, 0x89, 0x25, 0x29, 0x97, 0xBF, 0x7A, 0x91, 0x45, 0x69, 0xA1, 0x6B, 0x61, 0xB4, 0x47, 0x50, 0xED, 0x15, 0xC0, 0xB6, 0x06, 0x36, 0x33, 0x12, 0x16, 0xDA, 0x0A, 0x46, 0x4F, 0xCC, 0x7B, 0xC7, 0x8A, 0x45, 0x96, 0x27, 0x1C, 0x46, 0x85, 0x08, 0xD7, 0x36, 0xB7, 0xC7, 0xA0, 0xD7, 0xC0, 0x60, 0x2B, 0x6C, 0x6B, 0x6F, 0xD1, 0xBF, 0x5B, 0xF7, 0xA5, 0xC0, 0x48, 0xE2, 0x88, 0xA3, 0x4C, 0x06, 0xDC, 0x97, 0xD2, 0x91, 0xF8, 0x85, 0x1C, 0x72, 0x7F, 0x37, 0x05, 0x70, 0x4E, 0xEA, 0x0A, 0x89, 0x04, 0xA7, 0x64, 0x7D, 0x95, 0x85, 0x35, 0x81, 0x0F, 0xBC, 0xA0, 0x04, 0x80, 0xD9, 0x58, 0x43, 0x8F, 0x51, 0xEB, 0x29, 0x72, 0xA3, 0xEB, 0xB8, 0x0E, 0x2C, 0x94, 0x0E, 0xAD, 0xCF, 0x29, 0xA5, 0xF0, 0xDF, 0x6D, 0xCA, 0x88, 0x7B, 0x2E, 0xDD, 0x3C, 0x26, 0x79, 0xED, 0x67, 0x71, 0xAC, 0x44, 0xA5, 0xFA, 0xA8, 0x79, 0x7C, 0x79, 0x35, 0x5D, 0xCB, 0x79, 0xA4, 0x56, 0xEB, 0x76, 0xD8, 0x4D, 0x98, 0xFF, 0x71, 0xA8, 0x46, 0x7F, 0x26, 0x98, 0x4F, 0xA0, 0xC8, 0x47, 0x00, 0xFB, 0x21, 0xE6, 0xD1, 0x11, 0x3C, 0xD4, 0xCF, 0x89, 0xF4, 0x68, 0xAA, 0x2A, 0xFA, 0x48, 0xE7, 0x48, 0x10, 0xC7, 0xB8, 0x90, 0x17, 0xA3, 0x0C, 0x0F, 0x08, 0x6E, 0xE4, 0xC9, 0x75, 0xA0, 0x89, 0x4F, 0xE1, 0x0A, 0x83, 0xE3, 0x6E, 0x10, 0x80, 0xE4, 0xC2, 0x05, 0xF5, 0x29, 0x31, 0x30, 0x57, 0xA7, 0xDC, 0x5F, 0x99, 0xD5, 0x1E, 0x1E, 0x07, 0x8C, 0xD1, 0x61, 0x1B, 0xB2, 0xB3, 0x2C, 0xA8, 0x33, 0xC9, 0x4C, 0x39, 0x56, 0xBA, 0xFC, 0x65, 0xE2, 0xB9, 0xB6, 0x1C, 0x56, 0x24, 0x59, 0xA0, 0x0F, 0x62, 0xB4, 0xE3, 0xE6, 0xF4, 0x24, 0x08, 0xA6, 0x4B, 0xC7, 0xA5, 0x09, 0xB2, 0x0E, 0xD6, 0x41, 0x4F, 0x89, 0x97, 0x0D, 0xA1, 0xE4, 0x10, 0x3D, 0x2E, 0x36, 0xC2, 0x7A, 0x73, 0xD6, 0x30, 0x05, 0xA1, 0xC7, 0x08, 0x27, 0x28, 0x79, 0x8C, 0xC8, 0xE3, 0xE5, 0x1B, 0x7F, 0x22, 0xCD, 0x9C, 0x6A, 0x66, 0xE9, 0x63, 0xB2, 0x42, 0x72, 0x23, 0x3B, 0xB6, 0xF5, 0x08, 0xF5, 0x26, 0xD4, 0x22, 0x8E, 0x9B, 0xFA, 0x42, 0x5C, 0x47, 0x33, 0x0F, 0x7C, 0xDF, 0x0A, 0x97, 0x80, 0xF0, 0x84, 0xC6, 0xBB, 0xDA, 0x09, 0xD7, 0xBA, 0x79, 0x17, 0x06, 0xD2, 0xDA, 0x34, 0x0A, 0x95, 0x83, 0x65, 0xFA, 0x92, 0xC3, 0x52, 0xF9, 0xFA, 0x96, 0xCB, 0xC0, 0xE7, 0x53, 0x7F, 0xFF, 0x3F, 0x7C, 0x8B, 0x92, 0xA3, 0x69, 0xBC, 0x46, 0xBD, 0x0E, 0xC9, 0xE7, 0x1E, 0x28, 0x62, 0xEB, 0xE4, 0xB8, 0x28, 0x0C, 0x82, 0x1C, 0xA4, 0xC9, 0x92, 0x4F, 0xDE, 0x66, 0x87, 0x9C, 0x7A, 0x1A, 0x77, 0x16, 0xD1, 0x78, 0xCB, 0x98, 0x2F, 0xFA, 0x2B, 0x06, 0xF0, 0x30, 0x73, 0xE7, 0xD8, 0xB3, 0x0D, 0xAE, 0x57, 0xCB, 0xB6, 0xF1, 0x1B, 0xCB, 0xA5, 0xCB, 0x2C, 0xDC, 0x09, 0xEB, 0x9F, 0xCB, 0xBF, 0x81, 0x8F, 0x8B, 0x2E, 0xD0, 0x3A, 0x01, 0x2E, 0xF8, 0xB5, 0x3E, 0xC8, 0x19, 0xB6, 0x1B, 0x44, 0xF9, 0xB4, 0x10, 0x6E, 0xC6, 0x8D, 0xDB, 0x9F, 0xB5, 0x38, 0xCF, 0x3D, 0x50, 0x8A, 0xC0, 0x93, 0xA5, 0x7A, 0x71, 0x69, 0x2A, 0xFF, 0x83, 0xBE, 0xDC, 0xC7, 0xBE, 0xAD, 0xED, 0x15, 0x0E, 0xB6, 0xEF, 0x13, 0xD8, 0x75, 0x72, 0xC1, 0xEF, 0x2F, 0x14, 0x96, 0x1E, 0x03, 0x6F, 0x49, 0x60, 0x45, 0xA6, 0x47, 0x37, 0x31, 0xCE, 0x7F, 0x7C, 0x60, 0x06, 0x3C, 0xB8, 0x8B, 0x14, 0x64, 0xF6, 0xFD, 0xA6, 0xA0, 0xC5, 0x17, 0xCD, 0x5E, 0xF5, 0xFE, 0x25, 0x5E, 0xA7, 0xAA, 0xAB, 0x1F, 0x1D, 0xC1, 0xEC, 0xEE, 0x63, 0x57, 0x5A, 0xFC, 0xA7, 0x33, 0x5D, 0x9E, 0x04, 0xB3, 0xE0, 0x50, 0x2A, 0x7F, 0x2C, 0x0A, 0xB4, 0xAA, 0x12, 0x6A, 0xD0, 0x54, 0xA7, 0x0A, 0xCE, 0x98, 0x5A, 0x1C, 0xA1, 0xF4, 0x51, 0x03, 0xEA, 0xAD, 0x89, 0xCA, 0xB9, 0x0D, 0x19, 0x9F, 0x37, 0xD7, 0x67, 0x98, 0xBF, 0xE8, 0xA2, 0x98, 0xC1, 0x9F, 0x7E, 0xF7, 0x0F, 0x9B, 0x5B, 0x91, 0xD1, 0x3D, 0xC1, 0x06, 0x17, 0x81, 0xFA, 0x54, 0x95, 0xC4, 0xEB, 0xBA, 0xEA, 0x46, 0x82, 0xEA, 0xDA, 0x2A, 0x7E, 0xD0, 0xE7, 0x8C, 0xDA, 0x78, 0xA1, 0xB2, 0xF4, 0xE5, 0x4A, 0xD3, 0x75, 0x68, 0xBE, 0xEB, 0x7E, 0x98, 0x58, 0x83, 0xC1, 0xD6, 0x68, 0x37, 0xF3, 0x09, 0xF6, 0x53, 0x25, 0x0A, 0x71, 0xDA, 0x29, 0x5F, 0x4A, 0xCD, 0x14, 0x33, 0x06, 0x88, 0xD0, 0x83, 0xC6, 0x4D, 0x09, 0x1D, 0xFC, 0xDF, 0x59, 0x4F, 0x94, 0xB5, 0xA3, 0xF7, 0x02, 0x04, 0x86, 0x41, 0x3A, 0xE7, 0x74, 0xF4, 0xFA, 0xB7, 0xF5, 0xB3, 0xD6, 0x9B, 0x9D, 0x58, 0xB8, 0xE7, 0xFF, 0x00, 0xD4, 0xB1, 0xAB, 0x96, 0x62, 0x15, 0x1D, 0x26, 0xB0, 0x10, 0x42, 0x47, 0x29, 0xD3, 0x67, 0x93, 0xC7, 0x34, 0xD4, 0xBB, 0x08, 0x2A, 0x54, 0x45, 0x39, 0x77, 0x92, 0x7B, 0x7C, 0xEC, 0xF2, 0x55, 0xCF, 0x0F, 0xAA, 0x22, 0x87, 0xFF, 0xF3, 0x45, 0xA7, 0xA5, 0x2F, 0xB6, 0xF4, 0x89, 0xE3, 0xE2, 0x38, 0xC4, 0x7A, 0xA6, 0x38, 0x6D, 0xCB, 0x5E, 0x28, 0x58, 0x46, 0xBF, 0x58, 0xE0, 0xD7, 0xA6, 0x23, 0x43, 0xCE, 0x3F, 0x0D, 0x7F, 0x26, 0xAF, 0x9A, 0x8B, 0x4C, 0x92, 0x6E, 0xBB, 0xFD, 0x92, 0x56, 0x70, 0xAB, 0xB6, 0xF3, 0x8C, 0x08, 0xF2, 0xA9, 0x03, 0x96, 0x03, 0x1F, 0xFB, 0xBB, 0xCB, 0xAE, 0x69, 0xA6, 0x0D, 0x5A, 0x0A, 0x71, 0x05, 0xB0, 0xE6, 0x3F, 0x24, 0x1D, 0xC6, 0xCB, 0xCC, 0x3F, 0x33, 0xF5, 0xE8, 0x3F, 0x10, 0x36, 0x0E, 0xB5, 0x11, 0x0D, 0xFD, 0x27, 0xDC, 0x1A, 0xEB, 0x2D, 0x0E, 0x62, 0xCF, 0xD4, 0x59, 0x03, 0x57, 0xB2, 0x71, 0x11, 0x39, 0x5A, 0xCD, 0x8A, 0xDD, 0xA8, 0x63, 0x1B, 0xE9, 0xFF, 0x8A, 0x1D, 0x71, 0xC0, 0xAF, 0xC2, 0xB2, 0x69, 0x88, 0xE0, 0xB5, 0xA7, 0x03, 0x10, 0x95, 0x64, 0xC5, 0x3C, 0xF2, 0xBC, 0xC9, 0x20, 0xF5, 0x25, 0x5B, 0x1B, 0xAE, 0x5E, 0x4A, 0x5D, 0xB0, 0xA6, 0xCA, 0x68, 0x41, 0x89, 0x2C, 0xE6, 0x2D, 0xAF, 0xD2, 0x04, 0xC9, 0x88, 0x89, 0xDC, 0x39, 0xED, 0xC9, 0xB6, 0xDD, 0xEC, 0xE7, 0x6D, 0xA0, 0x83, 0x0F, 0xCE, 0xB4, 0xC1, 0x80, 0x3D, 0x2B, 0x21, 0xC1, 0x4D, 0xC6, 0x27, 0x13, 0xA2, 0xB9, 0x92, 0xDD, 0xB9, 0xBD, 0xE5, 0x65, 0xAF, 0xA1, 0xBF, 0x9F, 0x04, 0x7D, 0x02, 0x33, 0xE8, 0x9F, 0x25, 0x8D, 0x7C, 0xA5, 0x35, 0x80, 0x58, 0xF1, 0xF2, 0x1B, 0x9D, 0xDF, 0x48, 0xA3, 0x9B, 0x3F, 0x8E, 0xCF, 0xAB, 0x54, 0x03, 0x78, 0xFC, 0x1B, 0x8F, 0xA3, 0xDD, 0x28, 0x99, 0x4B, 0xEB, 0x89, 0x4E, 0xAF, 0x60, 0xB6, 0x75, 0x7D, 0x89, 0x78, 0x0D, 0x9D, 0x80, 0x82, 0x95, 0x50, 0x4B, 0xBF, 0xBA, 0xF7, 0x58, 0x8B, 0x62, 0x88, 0x68, 0xE5, 0x59, 0xE4, 0xDF, 0xE8, 0x6C, 0xA3, 0xF6, 0xA4, 0xEB, 0x15, 0x8F, 0xE6, 0xCB, 0x3E, 0xD3, 0x1E, 0xEC, 0x59, 0x34, 0x7C, 0x8B, 0x16, 0xD7, 0x3C, 0x16, 0x17, 0x4B, 0xAC, 0x8D, 0xEF, 0x38, 0x63, 0x9A, 0xF7, 0x42, 0x64, 0xA0, 0x80, 0x1B, 0x60, 0xDA, 0x2B, 0x5E, 0x31, 0x20, 0x3F, 0x96, 0xFD, 0x9B, 0xCD, 0x27, 0x7D, 0x44, 0x72, 0x12, 0xCF, 0xA2, 0xDB, 0x2F, 0x1F, 0x20, 0xB9, 0x1F, 0x75, 0x68, 0x7E, 0xAB, 0x40, 0xD7, 0xCD, 0xB7, 0xC8, 0x85, 0x73, 0x58, 0x3D, 0x16, 0x35, 0x6F, 0x45, 0x45, 0x9D, 0xCB, 0x98, 0x5A, 0x94, 0x66, 0xA0, 0x4E, 0x39, 0x22, 0x2E, 0xAB, 0x35, 0x1F, 0x1D, 0x5D, 0x32, 0xA8, 0xD6, 0x9B, 0x97, 0x9B, 0x4C, 0xF6, 0x20, 0x2A, 0x80, 0xB0, 0xC8, 0x4F, 0x9B, 0x64, 0xAE, 0x96, 0x8C, 0xEA, 0xA3, 0x67, 0x38, 0x20, 0xDA, 0xFF, 0xF5, 0xA1, 0xFD, 0xA8, 0xF4, 0x97, 0xB5, 0x02, 0xF8, 0x37, 0xE8, 0xB7, 0xB2, 0xBA, 0x38, 0x1E, 0xD5, 0x6C, 0x0A, 0x67, 0x29, 0x0B, 0x32, 0xB5, 0x90, 0x73, 0xDA, 0x0B, 0xD2, 0xC1, 0x59, 0x19, 0xBE, 0x25, 0x99, 0x77, 0xE9, 0xD4, 0x10, 0x33, 0x13, 0xD2, 0x92, 0xB1, 0x09, 0x47, 0x3A, 0xBD, 0x4A, 0x66, 0xEA, 0x07, 0x9F, 0xC4, 0xE5, 0x55, 0x8E, 0x55, 0xB2, 0x55, 0x3A, 0xEB, 0x1F, 0xD1, 0x3A, 0x65, 0xAF, 0x3E, 0xF4, 0x0B, 0x34, 0xA5, 0x02, 0xBB, 0xD3, 0x86, 0x4D, 0x54, 0xE1, 0xEA, 0x14, 0x83, 0x40, 0x19, 0x80, 0x68, 0x17, 0x22, 0xF6, 0x52, 0x25, 0x46, 0xB5, 0x0C, 0x7F, 0xE3, 0xC7, 0xF8, 0x42, 0x59, 0x95, 0x65, 0x4D, 0x1A, 0x7C, 0x6B, 0x45, 0xB9, 0xC5, 0x7F, 0x9A, 0x96, 0xD7, 0xA3, 0x5B, 0xC5, 0x90, 0x8B, 0xBC, 0x7E, 0x31, 0x07, 0x44, 0x4B, 0xE2, 0xEE, 0xB4, 0x33, 0xCD, 0x0D, 0x0F, 0x6B, 0x88, 0x4E, 0x57, 0x24, 0x1C, 0x00, 0xAE, 0x7D, 0x28, 0xEB, 0x0E, 0x4C, 0x8E, 0xAA, 0xA0, 0xB3, 0xA3, 0xAF, 0x9C, 0x86, 0x7F, 0xE3, 0x7F, 0x2D, 0x25, 0xA4, 0x09, 0xD6, 0x73, 0x95, 0x49, 0x98, 0x37, 0xF3, 0x58, 0xE6, 0x5C, 0x24, 0x34, 0x87, 0xEA, 0x87, 0xCE, 0x76, 0x2F, 0x0A, 0xAC, 0x0F, 0x9D, 0x85, 0xAA, 0x09, 0x6E, 0x06, 0x9C, 0x28, 0x9A, 0xCB, 0x91, 0xEF, 0x19, 0xF0, 0x7A, 0x0C, 0xC0, 0xCE, 0xAC, 0x6A, 0xA7, 0x20, 0x51, 0x35, 0xD7, 0x7B, 0xFB, 0xD1, 0x1C, 0x0F, 0xC4, 0xCE, 0x65, 0x21, 0x63, 0x41, 0x35, 0x55, 0x2E, 0x20, 0x07, 0x43, 0x39, 0xBB, 0x86, 0x60, 0xB8, 0x87, 0x4C, 0x0B, 0x78, 0x35, 0xBA, 0x91, 0x96, 0xAF, 0x0D, 0xAA, 0x79, 0x6B, 0x1B, 0x75, 0xC2, 0x5B, 0x2F, 0x85, 0x04, 0x40, 0xED, 0x96, 0x63, 0xF2, 0x36, 0xF1, 0x0A, 0x94, 0x6D, 0x01, 0x90, 0x7D, 0x1F, 0x8B, 0x7D, 0x08, 0xD7, 0x9F, 0xA1, 0x23, 0xB2, 0xBF, 0xA3, 0x80, 0x6C, 0x90, 0x67, 0x70, 0xF0, 0xC7, 0x0A, 0x6D, 0xAD, 0x40, 0x8B, 0x33, 0xA6, 0xAD, 0x6F, 0x2A, 0xBE, 0xE2, 0x70, 0x4A, 0xFA, 0x31, 0x6B, 0x3B, 0x62, 0x47, 0xDE, 0x5B, 0x13, 0xD0, 0x85, 0x63, 0x18, 0xE0, 0x9C, 0x61, 0x29, 0x2B, 0x13, 0x64, 0x44, 0x36, 0x01, 0x97, 0xED, 0xB6, 0xF0, 0xCC, 0x8E, 0xAD, 0xDE, 0x6B, 0xC7, 0x7B, 0xF5, 0x84, 0x08, 0xA3, 0xB7, 0x96, 0xBA, 0x0F, 0x20, 0xC1, 0xE2, 0x40, 0x31, 0x6A, 0x25, 0x06, 0xC4, 0x35, 0x4F, 0x04, 0x19, 0xD6, 0x86, 0xDC, 0x07, 0x48, 0x5F, 0xC3, 0xEA, 0x3B, 0x20, 0xBB, 0x59, 0xFE, 0x05, 0x45, 0xC3, 0xDE, 0x08, 0x44, 0xF6, 0x19, 0xE9, 0x95, 0x8A, 0xDE, 0xA6, 0xD9, 0x96, 0x59, 0x7E, 0x7C, 0xBF, 0x00, 0x48, 0x36, 0xBD, 0xB0, 0xB4, 0x22, 0xBE, 0xD0, 0xE5, 0x40, 0x8C, 0x3B, 0x6E, 0x11, 0xDA, 0x24, 0xBA, 0x09, 0xEE, 0x6C, 0xB0, 0xD5, 0x87, 0x01, 0xD9, 0xBE, 0x02, 0xE1, 0x80, 0x89, 0x3F, 0x57, 0xB1, 0xF6, 0x53, 0xF8, 0x59, 0x6F, 0x99, 0x25, 0xF0, 0x10, 0xD9, 0x8F, 0x56, 0xC7, 0x05, 0x4B, 0xE8, 0xE2, 0x3C, 0xE0, 0x6C, 0x42, 0xA2, 0x08, 0x92, 0xED, 0x06, 0x64, 0xBF, 0x0C, 0x91, 0x7D, 0xAE, 0x64, 0x0B, 0xEE, 0x88, 0x54, 0xB2, 0xC8, 0xE6, 0xF1, 0x42, 0x75, 0x40, 0x64, 0x7B, 0x23, 0x75, 0x81, 0x17, 0x90, 0x7D, 0x21, 0x79, 0xF2, 0x39, 0xD1, 0xD6, 0x26, 0xDE, 0xFB, 0xF4, 0x7B, 0x2A, 0xFC, 0x00, 0xD0, 0x16, 0x8B, 0x0B, 0x62, 0x95, 0xC9, 0x86, 0xE1, 0xB2, 0x0D, 0x7B, 0x04, 0x2D, 0xE2, 0x33, 0x4F, 0x48, 0x7D, 0x06, 0x94, 0x67, 0x0F, 0x01, 0x40, 0x7B, 0xFA, 0x50, 0x25, 0x2C, 0xBA, 0xB5, 0x65, 0x0E, 0x00, 0xAA, 0x60, 0x08, 0xD6, 0xE1, 0xB8, 0x55, 0xF3, 0x41, 0x21, 0xF9, 0x1F, 0xE3, 0x71, 0x00, 0x6B, 0x8D, 0x59, 0x7A, 0xA3, 0xD1, 0x48, 0x6B, 0x29, 0x82, 0x55, 0x3F, 0x7A, 0x62, 0x3A, 0xC5, 0xFE, 0x23, 0x0E, 0xFC, 0xAA, 0x1D, 0xC3, 0x7A, 0xD6, 0x2B, 0xB0, 0xDD, 0x36, 0x8D, 0x3B, 0x78, 0x15, 0x08, 0xB1, 0x3F, 0xC4, 0xD2, 0x46, 0xCD, 0x18, 0x4F, 0xAC, 0x73, 0x0D, 0xE3, 0x63, 0xB1, 0xF2, 0xB9, 0xDA, 0x5A, 0x4A, 0x09, 0xCD, 0x6E, 0x8D, 0xBA, 0x14, 0x6A, 0x3A, 0xC5, 0xA3, 0x5A, 0x2F, 0x30, 0xE4, 0xD4, 0xFE, 0xB0, 0xC4, 0x33, 0xF3, 0xDF, 0xA8, 0xD4, 0x40, 0xD2, 0xB8, 0x78, 0x2B, 0xC8, 0x03, 0x45, 0x6A, 0x14, 0x7A, 0xCA, 0x77, 0x1B, 0xCE, 0x09, 0x64, 0x74, 0x9E, 0xAB, 0x0A, 0x00, 0x9B, 0xCC, 0x2B, 0xF4, 0x6E, 0xD0, 0xD3, 0xC0, 0xEA, 0xC6, 0x3F, 0x88, 0xDE, 0x39, 0xF8, 0xA9, 0x62, 0x3C, 0xCD, 0x1C, 0x00, 0x75, 0x8B, 0xA7, 0xB3, 0x02, 0x7F, 0xEB, 0x34, 0xBA, 0x46, 0xF0, 0xCC, 0xC9, 0xB3, 0xB3, 0xF8, 0xBD, 0xEB, 0x18, 0x9F, 0x35, 0x1A, 0xFE, 0xFE, 0x6F, 0xB7, 0x38, 0xC4, 0x30, 0xA0, 0xB7, 0xA8, 0xE0, 0x03, 0x58, 0xE4, 0x79, 0x93, 0x0F, 0xD6, 0xAD, 0x8B, 0x05, 0xDE, 0x6D, 0xA1, 0x79, 0x34, 0xA8, 0x0F, 0x49, 0x23, 0x46, 0xE5, 0xFA, 0x24, 0x4F, 0xD7, 0x1F, 0xAD, 0xCF, 0xA4, 0x81, 0x06, 0x3C, 0x27, 0x41, 0x8C, 0xFA, 0x7C, 0x87, 0xAF, 0xBD, 0x76, 0xD1, 0x7C, 0x03, 0x8B, 0x46, 0x60, 0x62, 0x72, 0xCB, 0xC6, 0xF0, 0xD2, 0x6C, 0xDA, 0x22, 0xED, 0xF0, 0xBF, 0xE8, 0x90, 0x6F, 0x29, 0xB4, 0x87, 0x73, 0x4D, 0x64, 0x96, 0x06, 0x91, 0x7E, 0x4F, 0xE4, 0xAF, 0x04, 0x0D, 0xEE, 0x6E, 0xD3, 0x86, 0x7E, 0x77, 0x83, 0x1E, 0x6B, 0xCD, 0xA1, 0x7D, 0x01, 0x19, 0x9B, 0x31, 0x36, 0x8F, 0x71, 0xEE, 0xFA, 0x7B, 0xB6, 0xDF, 0xA4, 0x3F, 0x4E, 0x25, 0xE9, 0x21, 0x37, 0xFB, 0xBE, 0x8E, 0x7D, 0x05, 0x8A, 0x27, 0x38, 0x92, 0x11, 0xC9, 0xCC, 0x7B, 0x4F, 0x13, 0x81, 0x86, 0xE1, 0xC2, 0x37, 0xBA, 0xA1, 0xBA, 0xF0, 0x37, 0x95, 0x59, 0xD1, 0x67, 0xA2, 0xC7, 0x2B, 0xEB, 0xFE, 0xBA, 0x10, 0x07, 0x02, 0x6E, 0x8D, 0x3E, 0xB4, 0x09, 0x69, 0xEA, 0x5B, 0xC4, 0x40, 0x9E, 0xF6, 0xDB, 0x8D, 0xFE, 0xF2, 0xF6, 0x34, 0x8A, 0xFC, 0x33, 0x7E, 0x8D, 0x95, 0x24, 0xD8, 0xA3, 0xE0, 0xC2, 0x5F, 0x04, 0xA0, 0x93, 0x1A, 0x13, 0x2B, 0x58, 0xB2, 0x4A, 0x80, 0x75, 0x4A, 0x4F, 0x62, 0xB4, 0x9D, 0x04, 0xA3, 0xF3, 0x9E, 0xF1, 0xBF, 0x19, 0x19, 0xE9, 0x56, 0x0B, 0x5C, 0x32, 0x5A, 0xAA, 0x8A, 0xF0, 0xC9, 0x18, 0x15, 0x84, 0xDF, 0xF8, 0x0F, 0x3A, 0x0C, 0x27, 0x8C, 0x19, 0xC7, 0xEF, 0x75, 0x35, 0x09, 0x27, 0x7E, 0xD3, 0xF2, 0x21, 0x6A, 0xB5, 0x4E, 0x27, 0xC0, 0x48, 0x3D, 0x68, 0x1F, 0x8C, 0x4F, 0x2B, 0xEA, 0xC8, 0x77, 0x32, 0x71, 0x7D, 0xAA, 0x3E, 0x0E, 0xE9, 0x62, 0x0F, 0xA9, 0xFA, 0x4D, 0x98, 0xE5, 0x47, 0xEB, 0x04, 0x61, 0xB3, 0x04, 0xD2, 0xE5, 0x83, 0xBF, 0x4D, 0x74, 0xF4, 0xCA, 0x37, 0xB7, 0x0E, 0x3B, 0x4E, 0x1B, 0x6B, 0xD1, 0xB7, 0xD2, 0xAB, 0x3F, 0xC1, 0x71, 0xCE, 0xB4, 0x0D, 0x1B, 0x06, 0x35, 0x6F, 0x43, 0x2A, 0xC9, 0x35, 0x2B, 0x91, 0x0C, 0xDD, 0xB7, 0x35, 0xF6, 0x33, 0xB7, 0x1B, 0xFF, 0xE8, 0x06, 0x35, 0x4B, 0x42, 0x28, 0x85, 0x35, 0x2D, 0x41, 0x99, 0x86, 0x8C, 0x1C, 0xE6, 0xF7, 0xC0, 0x06, 0x43, 0x84, 0xC0, 0x9D, 0xBF, 0x5E, 0x2D, 0xE0, 0x22, 0x9C, 0x55, 0xF1, 0x89, 0xCC, 0xAB, 0xBF, 0x43, 0x71, 0x8F, 0xEE, 0xD9, 0x24, 0x65, 0x09, 0x28, 0xF8, 0x64, 0xAC, 0xCE, 0x3E, 0x0C, 0x44, 0x9C, 0x33, 0xE7, 0xCF, 0x2D, 0x73, 0x97, 0x94, 0xE3, 0x90, 0x55, 0xD9, 0x00, 0x72, 0x75, 0xA8, 0x54, 0x92, 0x81, 0xC6, 0x52, 0xCB, 0x63, 0x03, 0xBA, 0x13, 0x94, 0xCB, 0x18, 0xAC, 0x38, 0xD3, 0x5B, 0x6D, 0x5E, 0xB3, 0x29, 0xB6, 0x06, 0x8E, 0xED, 0xB4, 0xEF, 0x43, 0x6D, 0x8F, 0x84, 0xF6, 0x44, 0xB4, 0xA2, 0xBE, 0xBE, 0x90, 0x96, 0xAB, 0x51, 0x69, 0xDB, 0x93, 0x4E, 0xE5, 0xE0, 0xF1, 0x9F, 0x05, 0x40, 0x70, 0xDA, 0x7B, 0x96, 0x77, 0x6E, 0xD3, 0xF4, 0x6F, 0x6B, 0x59, 0x93, 0x32, 0x1C, 0x0B, 0xB0, 0xDD, 0x5F, 0x7A, 0x8A, 0x6C, 0x42, 0x31, 0x3C, 0xB2, 0x91, 0x73, 0x43, 0x5D, 0xAC, 0x53, 0x05, 0x22, 0xD2, 0x63, 0xFB, 0x41, 0xFE, 0xFA, 0xC3, 0xBE, 0xD0, 0x72, 0x53, 0xC6, 0x57, 0x34, 0xDB, 0xDB, 0xD2, 0x05, 0xB2, 0x07, 0x23, 0x3D, 0xEB, 0x16, 0xBF, 0x28, 0x41, 0x50, 0x8C, 0x43, 0x22, 0x82, 0x1C, 0x69, 0x89, 0x38, 0x09, 0xE4, 0x78, 0xBF, 0x5C, 0x72, 0xC0, 0xCA, 0x1B, 0xDC, 0xE5, 0xDE, 0x89, 0xA2, 0x7F, 0x9F, 0x01, 0xAB, 0x5C, 0x29, 0xE8, 0x95, 0xDA, 0x0D, 0xEB, 0xA1, 0x42, 0xFE, 0x2E, 0x47, 0x93, 0x14, 0x1B, 0x99, 0x07, 0x64, 0xFA, 0xD3, 0xB9, 0x5D, 0x44, 0xC1, 0x74, 0x39, 0x47, 0x46, 0x80, 0x31, 0x5D, 0x66, 0x35, 0x2B, 0x0B, 0xDE, 0x2F, 0x7F, 0x51, 0x31, 0xF0, 0x6C, 0x9A, 0xD9, 0xE3, 0xE4, 0x99, 0x8F, 0x2F, 0xB3, 0xBC, 0x87, 0x27, 0xF3, 0x78, 0x8C, 0xC9, 0xF5, 0x9A, 0x6B, 0x23, 0xF5, 0xC1, 0x91, 0x74, 0x04, 0x04, 0x7C, 0xDD, 0x4D, 0x23, 0x6C, 0xE4, 0x1C, 0x67, 0xA2, 0x6B, 0x90, 0x73, 0xE7, 0xD2, 0x85, 0x59, 0x92, 0xA3, 0xF6, 0x1E, 0x5B, 0xB6, 0x73, 0x9B, 0xFB, 0xD6, 0x2B, 0x6C, 0x60, 0xB0, 0x7F, 0xE6, 0x9C, 0xB6, 0x09, 0x2F, 0xD6, 0x91, 0x64, 0xE5, 0x14, 0x65, 0x98, 0xE2, 0x87, 0x9E, 0x3E, 0x14, 0xD2, 0x69, 0x5B, 0x3E, 0x5C, 0x0E, 0xC1, 0x3F, 0x6E, 0xF5, 0xE6, 0xB7, 0x3B, 0x02, 0x7D, 0xCC, 0xCD, 0x8B, 0x43, 0xBD, 0x11, 0x37, 0xCB, 0x70, 0x23, 0xA6, 0xCA, 0x34, 0x0A, 0x30, 0x43, 0x40, 0xB7, 0x63, 0x95, 0x7F, 0x11, 0xC5, 0x4F, 0x85, 0x1A, 0x7C, 0x39, 0x71, 0xBC, 0xA2, 0x24, 0x20, 0xA6, 0x12, 0x6F, 0x5A, 0x0C, 0x69, 0x0C, 0x9D, 0x37, 0x01, 0x35, 0xBD, 0x1E, 0x18, 0xE1, 0xE7, 0x18, 0x78, 0xFC, 0xAE, 0xC8, 0x67, 0xF1, 0x1E, 0xB7, 0xB1, 0x4D, 0x79, 0xF1, 0x94, 0xDC, 0x3D, 0x67, 0xA8, 0xB7, 0xDC, 0x26, 0xC4, 0xF8, 0x9A, 0x83, 0xBC, 0x24, 0x3A, 0x0A, 0xB3, 0xF4, 0x46, 0x61, 0x04, 0x2E, 0x03, 0x55, 0x18, 0xC5, 0x28, 0xB5, 0x68, 0x8D, 0x74, 0x51, 0x5A, 0xC6, 0x4D, 0x5F, 0xAA, 0x58, 0x86, 0x99, 0xC9, 0x63, 0x6E, 0xB2, 0xC8, 0x87, 0xE5, 0x36, 0xC1, 0xA3, 0x66, 0xAF, 0x79, 0x77, 0x2C, 0xB9, 0x11, 0xD2, 0x1B, 0xF5, 0x21, 0xFE, 0x21, 0x89, 0x6A, 0xEC, 0x71, 0x83, 0x27, 0x0E, 0xD1, 0x31, 0x6E, 0x24, 0x20, 0x9F, 0x6E, 0x28, 0x95, 0x17, 0x7F, 0x76, 0x15, 0x70, 0xE9, 0x44, 0x22, 0xDD, 0x84, 0x61, 0x10, 0x3F, 0x5C, 0x5B, 0xF6, 0xC6, 0x4C, 0x4E, 0xE3, 0xCF, 0x6C, 0xF5, 0x50, 0x44, 0x29, 0x89, 0x04, 0xED, 0xA9, 0xC1, 0x2F, 0xE2, 0x65, 0x2B, 0x87, 0xCA, 0xD9, 0x9B, 0xFA, 0xAA, 0xE7, 0x5B, 0x87, 0x2B, 0x9C, 0x88, 0x84, 0xA3, 0x27, 0x6B, 0xF9, 0xFE, 0x1E, 0xD3, 0xC7, 0x41, 0x36, 0xFA, 0xC7, 0xDA, 0x46, 0x9E, 0x68, 0xD9, 0x64, 0xF8, 0x11, 0x0D, 0xA3, 0x12, 0x6C, 0x38, 0x97, 0x4D, 0xDE, 0xF9, 0xEE, 0xE3, 0xB5, 0x8F, 0x17, 0x6F, 0x82, 0x65, 0x0F, 0x17, 0x79, 0x3D, 0x97, 0x38, 0x1C, 0x41, 0x73, 0xCA, 0xD9, 0x10, 0xCC, 0xFB, 0xD8, 0xB6, 0x97, 0x90, 0x42, 0x36, 0xAA, 0x9B, 0x45, 0x56, 0x5B, 0x16, 0xF0, 0xA2, 0x3E, 0x36, 0x8E, 0x84, 0x0B, 0x08, 0xB8, 0xC5, 0x9F, 0x53, 0xB6, 0x06, 0xA9, 0x12, 0x75, 0x61, 0xDB, 0x95, 0x26, 0x8F, 0x7C, 0x04, 0x86, 0x1E, 0x6E, 0x27, 0x96, 0xB0, 0x27, 0xA0, 0x1E, 0x20, 0xBD, 0x9F, 0x6E, 0x89, 0x31, 0x8F, 0x87, 0xB1, 0x96, 0x49, 0xB4, 0x8D, 0x0D, 0x7D, 0x87, 0xDC, 0xAF, 0x15, 0xC4, 0xEA, 0x28, 0x47, 0xBA, 0x7A, 0xDF, 0x5F, 0x49, 0xC4, 0xC2, 0xDC, 0xDA, 0xFD, 0x7B, 0xF3, 0xC5, 0x8F, 0x41, 0xFF, 0xE5, 0x1D, 0x96, 0x25, 0xC7, 0x9A, 0x54, 0xC8, 0x02, 0x40, 0x50, 0x7E, 0xCE, 0x37, 0x5C, 0x3C, 0xD5, 0xB4, 0xB4, 0x4B, 0x9C, 0x50, 0x4D, 0xC3, 0xD2, 0x88, 0xE8, 0x6A, 0xDF, 0x18, 0x81, 0xD4, 0x0D, 0x18, 0x6E, 0x1F, 0x69, 0x80, 0x22, 0x29, 0x03, 0xBE, 0xFB, 0x0F, 0x61, 0x98, 0x3F, 0x37, 0x1C, 0x94, 0x87, 0xA8, 0x5E, 0x89, 0x52, 0x2A, 0xBC, 0x88, 0xC7, 0xE6, 0xA9, 0x25, 0xF3, 0x96, 0x72, 0x6E, 0xB6, 0x2F, 0x49, 0x50, 0xE4, 0xEE, 0x85, 0xC2, 0x7E, 0x87, 0xBF, 0x2C, 0x7A, 0x40, 0xFE, 0xBB, 0x34, 0x85, 0xB4, 0xEF, 0x3B, 0xDD, 0xB3, 0x50, 0x03, 0x7F, 0x0A, 0x1B, 0x25, 0x64, 0x23, 0x64, 0x63, 0x46, 0xF0, 0xD8, 0xBB, 0x3F, 0xA3, 0x5C, 0x91, 0x6F, 0xAA, 0x2C, 0x42, 0xF2, 0x7C, 0x43, 0xB7, 0x20, 0xAF, 0x00, 0xB7, 0xD8, 0xA8, 0x13, 0x69, 0x61, 0x2B, 0xC5, 0x8C, 0xDB, 0xE5, 0x1F, 0x17, 0xE7, 0x52, 0xD0, 0xFD, 0x87, 0x0B, 0xE8, 0x62, 0xEA, 0x11, 0x42, 0xDB, 0x14, 0x1F, 0xEA, 0xE2, 0x25, 0x6A, 0xA6, 0x6F, 0x50, 0xDB, 0xB0, 0xC7, 0x7E, 0x5C, 0x58, 0x70, 0xE7, 0x32, 0xA0, 0x04, 0x20, 0x7C, 0xBF, 0x29, 0x74, 0x4D, 0xA7, 0x72, 0x78, 0xF8, 0x34, 0x9D, 0x25, 0xCA, 0x45, 0xE2, 0xF9, 0xDC, 0xD5, 0x6A, 0xD9, 0x39, 0x98, 0x8B, 0x3F, 0xB6, 0x8E, 0x2B, 0x9F, 0xE0, 0x92, 0x75, 0xE2, 0x51, 0xDC, 0xC9, 0x10, 0x73, 0x0A, 0x03, 0x06, 0x8C, 0x7B, 0xA8, 0x09, 0x76, 0x92, 0x70, 0xCE, 0x10, 0xC9, 0x5E, 0xDB, 0xE4, 0x1C, 0x7A, 0x7E, 0x8B, 0x33, 0xD7, 0xEA, 0x55, 0xA5, 0x32, 0xAF, 0xF1, 0x95, 0x1C, 0x23, 0x21, 0xCE, 0x25, 0x06, 0x30, 0xA9, 0x7D, 0xC2, 0x30, 0x17, 0x0D, 0xCC, 0xF0, 0x60, 0x81, 0x50, 0xC8, 0x66, 0xC0, 0x84, 0x35, 0x12, 0x6C, 0xC2, 0xFE, 0x1B, 0xA0, 0xC1, 0x71, 0xFE, 0xA5, 0x65, 0x3B, 0x0D, 0xD5, 0x24, 0xFE, 0x78, 0x36, 0x9A, 0xCD, 0xD5, 0x0A, 0x58, 0x07, 0xFF, 0x1A, 0x63, 0x44, 0x94, 0x2B, 0x78, 0x81, 0x37, 0x23, 0x28, 0x96, 0x37, 0x83, 0x85, 0xEC, 0x3B, 0xA0, 0xBE, 0xA0, 0xCF, 0xB1, 0xEB, 0xB1, 0x63, 0x93, 0xD0, 0xA1, 0xBD, 0xE6, 0xD4, 0xD3, 0xDA, 0x90, 0x13, 0xBF, 0x55, 0x6A, 0x7A, 0x87, 0x55, 0xF0, 0xE1, 0x0F, 0x21, 0x21, 0x5E, 0x8C, 0xFE, 0xA3, 0x35, 0xA9, 0x17, 0x9A, 0x52, 0xE3, 0xEB, 0x30, 0x11, 0x07, 0xA6, 0x0C, 0xF2, 0x5E, 0xE2, 0x91, 0x31, 0xFB, 0x68, 0xFC, 0x82, 0xA2, 0xCC, 0xC0, 0x92, 0x2C, 0x01, 0xB8, 0x58, 0xF4, 0x45, 0xD0, 0x46, 0x58, 0xA6, 0x40, 0x5F, 0x03, 0x3A, 0x28, 0x3E, 0x8D, 0x67, 0xE7, 0xEA, 0xB7, 0xED, 0xBE, 0xBF, 0x21, 0xDA, 0xDB, 0xD2, 0xB7, 0x0A, 0x61, 0x43, 0x88, 0xC8, 0x3B, 0x69, 0x0E, 0x3D, 0x8E, 0xD0, 0x6B, 0xC1, 0xAC, 0x33, 0x81, 0x6B, 0xC5, 0xB3, 0x6E, 0x21, 0x3F, 0xC1, 0x03, 0x94, 0x9C, 0x55, 0x6B, 0x74, 0x33, 0x4E, 0x41, 0x76, 0x7B, 0xB2, 0x47, 0x05, 0xC3, 0x9F, 0xD0, 0x82, 0xA2, 0xB7, 0x9B, 0xE1, 0x86, 0xD0, 0x59, 0x57, 0x25, 0xB0, 0x0A, 0x77, 0x53, 0x2A, 0x0E, 0x27, 0x11, 0xCD, 0xC9, 0xF7, 0x45, 0xFD, 0xDC, 0x49, 0x99, 0xC2, 0xBB, 0xA2, 0x48, 0x5C, 0x01, 0xBE, 0x2F, 0xBE, 0x6C, 0x68, 0xC1, 0x8E, 0x81, 0x10, 0x47, 0xB6, 0x80, 0x70, 0x1D, 0x18, 0x3C, 0xF6, 0x4B, 0xBA, 0x98, 0xB5, 0x13, 0xD8, 0xEF, 0x2A, 0xA4, 0x89, 0xFA, 0xEA, 0xB3, 0x08, 0x72, 0xE2, 0x26, 0x40, 0xD4, 0x19, 0xE4, 0x93, 0xB9, 0xA2, 0x21, 0x47, 0x00, 0x02, 0xCD, 0xD0, 0xA2, 0x89, 0xD7, 0x2F, 0x8D, 0xDA, 0xCF, 0xB2, 0x8B, 0x31, 0x91, 0xA5, 0x5F, 0x5E, 0x09, 0x2F, 0xAF, 0x13, 0x8B, 0x2A, 0x23, 0x86, 0x82, 0xCD, 0x67, 0x9B, 0x46, 0x1D, 0x91, 0xA8, 0x6B, 0x41, 0x32, 0x1A, 0x8F, 0x8A, 0x53, 0xD1, 0x05, 0xB2, 0x02, 0x09, 0xC8, 0x88, 0xBB, 0xF6, 0x15, 0xF2, 0xF9, 0xD5, 0xC3, 0x64, 0x7F, 0x5D, 0x2F, 0xCC, 0xA7, 0xD6, 0x03, 0xCD, 0x6A, 0x6B, 0xDA, 0xA3, 0xC5, 0x07, 0xA4, 0x8E, 0x53, 0x04, 0xFC, 0x8A, 0xFD, 0x12, 0x13, 0x8A, 0x7D, 0x41, 0x6D, 0x4A, 0xF1, 0x43, 0xC7, 0xC9, 0xF5, 0xDB, 0x8A, 0xE1, 0x43, 0xAD, 0xB8, 0xA4, 0x51, 0xCA, 0x7A, 0x12, 0x8B, 0x02, 0x47, 0x1F, 0x54, 0x8B, 0x1A, 0x8C, 0x27, 0x9F, 0xE4, 0x22, 0xE2, 0x1D, 0x8C, 0x04, 0x23, 0x21, 0x84, 0xFA, 0x68, 0xB6, 0x8A, 0xE9, 0xC2, 0xB2, 0x98, 0xDE, 0x41, 0x0C, 0x17, 0x5A, 0xEF, 0x2F, 0xB3, 0x74, 0x02, 0x0B, 0xDC, 0x4B, 0xC8, 0xE9, 0xDE, 0xE2, 0x94, 0xE5, 0x62, 0x2D, 0x59, 0x63, 0x74, 0x10, 0xBE, 0xDA, 0x36, 0xD9, 0x2A, 0x14, 0x22, 0xD0, 0x25, 0x1F, 0xEE, 0xC4, 0x45, 0x28, 0xD1, 0x34, 0x02, 0xF8, 0xFA, 0xDC, 0xE0, 0xF2, 0xB5, 0xA3, 0xB1, 0xAC, 0xB6, 0xE1, 0xA6, 0x4C, 0x90, 0x76, 0xF4, 0xFB, 0xEC, 0x83, 0x45, 0xEF, 0xE5, 0xEC, 0x0C, 0xDE, 0xE6, 0xAD, 0xB5, 0x85, 0x68, 0xDB, 0x63, 0xE6, 0x32, 0x6E, 0xE1, 0x2C, 0xE8, 0xF0, 0x52, 0xC4, 0xB9, 0xB7, 0x13, 0x7D, 0xE8, 0x5F, 0x9D, 0x74, 0xE1, 0x1C, 0x06, 0x3B, 0x49, 0x58, 0x0C, 0x17, 0x19, 0x75, 0xB1, 0x96, 0x9D, 0xCF, 0x09, 0x9A, 0xFC, 0xD8, 0x45, 0x70, 0x48, 0x96, 0x15, 0x69, 0xBD, 0xAC, 0x7D, 0x79, 0xD3, 0x52, 0xE7, 0xED, 0xC4, 0x33, 0x5E, 0x26, 0x82, 0x89, 0xAA, 0x71, 0x53, 0xFE, 0x0B, 0x12, 0x84, 0x81, 0x94, 0x6B, 0x45, 0x47, 0x32, 0x8C, 0x7B, 0xFC, 0x56, 0xD1, 0xEC, 0xFD, 0x1F, 0x9D, 0x16, 0x5A, 0x84, 0xDC, 0xE0, 0xA9, 0x3C, 0x9D, 0x34, 0x11, 0xBE, 0x16, 0x02, 0x90, 0x38, 0x3F, 0x5B, 0x85, 0x1D, 0x4E, 0x99, 0x46, 0x24, 0x51, 0x32, 0x5F, 0xF2, 0x35, 0xB3, 0xC9, 0x4A, 0x58, 0x5D, 0x3A, 0x0E, 0xEA, 0xC8, 0x0C, 0xED, 0xF6, 0x67, 0x0C, 0xAF, 0xD8, 0x42, 0x0C, 0xEC, 0xAF, 0x72, 0x86, 0x28, 0x2A, 0x41, 0xAE, 0xED, 0xA2, 0x0A, 0x53, 0x7C, 0xE7, 0x04, 0xA5, 0x28, 0x6E, 0x90, 0xAF, 0xF4, 0x02, 0x79, 0x3D, 0x3A, 0x58, 0xB9, 0x33, 0x3A, 0x56, 0x7E, 0x79, 0xDB, 0x17, 0x42, 0xB1, 0x85, 0xBB, 0xD1, 0x1C, 0xC7, 0x92, 0x0B, 0x04, 0x5C, 0xF9, 0x4B, 0xF6, 0x94, 0x2B, 0x53, 0x30, 0xD4, 0x33, 0x5E, 0x35, 0x7B, 0x1B, 0x26, 0x22, 0xCD, 0x16, 0xAE, 0x2D, 0xC6, 0x4E, 0x7A, 0xC3, 0xC5, 0xF8, 0x46, 0x51, 0x28, 0xB2, 0xA7, 0x39, 0x4E, 0x80, 0xBD, 0x66, 0x2B, 0xD8, 0xDB, 0xAC, 0x41, 0x50, 0xED, 0xDE, 0xD8, 0xB5, 0xFF, 0xCC, 0xF4, 0x2C, 0x90, 0x99, 0xD8, 0x90, 0x65, 0xD6, 0x46, 0xCC, 0x9C, 0xC5, 0x7B, 0x0A, 0x1B, 0x2D, 0xF5, 0x08, 0xDC, 0xF7, 0xE3, 0x23, 0x47, 0x5F, 0x58, 0x56, 0x74, 0x6F, 0xBD, 0x42, 0x92, 0x15, 0x81, 0x0F, 0x75, 0x2C, 0xD2, 0x73, 0xEB, 0xD6, 0x49, 0xDF, 0xB4, 0x44, 0xF0, 0x19, 0xC1, 0x65, 0x6F, 0x3E, 0x85, 0x8B, 0xD2, 0x50, 0xBC, 0xB4, 0xD6, 0xFC, 0x39, 0x83, 0xB8, 0xB2, 0x8D, 0xD1, 0x71, 0x0F, 0x32, 0xE3, 0x22, 0x3C, 0xD8, 0xE6, 0x96, 0xBC, 0xB9, 0xCF, 0xE4, 0xA6, 0x7A, 0x43, 0xCA, 0xA8, 0xB7, 0xB4, 0x3B, 0xB4, 0xAA, 0xA7, 0x5D, 0xEE, 0xB4, 0xD5, 0xDF, 0x6C, 0xE0, 0x80, 0x93, 0x75, 0x12, 0xD5, 0x25, 0xA6, 0x20, 0x49, 0x6E, 0xD2, 0x04, 0x3A, 0xFB, 0x21, 0x5B, 0x5B, 0x90, 0xEB, 0xB8, 0xD3, 0xD0, 0xB2, 0x51, 0xB9, 0xDF, 0xA6, 0x9A, 0xD6, 0x46, 0x48, 0x72, 0x8F, 0x9B, 0x8D, 0x05, 0x7F, 0x99, 0xB0, 0x2E, 0xD4, 0x50, 0x72, 0x97, 0x0F, 0x80, 0x0D, 0x6C, 0x74, 0x6C, 0x41, 0x4C, 0x4D, 0xEE, 0xF4, 0x1F, 0xB3, 0xA7, 0x94, 0xB9, 0x65, 0xF2, 0xA3, 0x90, 0xAB, 0x99, 0xD5, 0xEF, 0x1B, 0xD5, 0xD7, 0xE4, 0xC6, 0x2A, 0x73, 0x67, 0x47, 0xFA, 0x92, 0xD2, 0x56, 0x1A, 0xF8, 0x1B, 0xFF, 0xB5, 0x39, 0x70, 0x06, 0xC7, 0xE9, 0xE4, 0x16, 0x62, 0xEC, 0x56, 0x29, 0x79, 0xC1, 0x38, 0x17, 0x8B, 0x14, 0x6C, 0xE9, 0x99, 0x08, 0xF6, 0xBC, 0xFE, 0xEF, 0x61, 0x55, 0x51, 0x99, 0x9B, 0xC4, 0x7B, 0x71, 0xC5, 0xF5, 0xE0, 0x2D, 0xDA, 0xB5, 0xD1, 0xCD, 0x00, 0x2E, 0xFF, 0xA0, 0xDF, 0xBB, 0xC7, 0x0A, 0xBB, 0x37, 0x36, 0xEF, 0xDD, 0xE5, 0x51, 0xC7, 0x73, 0xC0, 0x35, 0xD2, 0x05, 0x89, 0x3D, 0x61, 0x79, 0xD9, 0xD5, 0x9C, 0xD8, 0xA1, 0xE8, 0x43, 0x4F, 0x91, 0xD4, 0xF3, 0x8F, 0x32, 0x2F, 0x42, 0xF9, 0xA5, 0x84, 0x0B, 0x23, 0xEC, 0xC3, 0x69, 0x1A, 0xE7, 0xCB, 0x9F, 0x44, 0xD9, 0x7B, 0xAD, 0x7B, 0x6F, 0x48, 0x55, 0xCE, 0x21, 0x0E, 0x73, 0x59, 0xD6, 0x28, 0xA3, 0xE8, 0x67, 0xA7, 0x55, 0xEC, 0xE3, 0x39, 0xAF, 0x8A, 0x0E, 0x9F, 0x2E, 0xDF, 0x9E, 0xBC, 0x40, 0x0C, 0x4C, 0x92, 0x70, 0xB6, 0x68, 0xD5, 0x59, 0xAB, 0x5E, 0xF4, 0x6F, 0x9D, 0xB5, 0xE7, 0x52, 0x73, 0xAC, 0xE9, 0x39, 0x3A, 0x9E, 0xD4, 0x98, 0xB7, 0x22, 0x01, 0x46, 0x83, 0x9A, 0xA4, 0x62, 0x0C, 0xA8, 0x80, 0x22, 0xF7, 0x93, 0x40, 0x10, 0x23, 0x53, 0x04, 0xB1, 0x00, 0x51, 0xA7, 0xFF, 0x84, 0x1C, 0x47, 0x3C, 0xEF, 0xEE, 0x65, 0xB1, 0xBC, 0x04, 0xEC, 0xB9, 0x20, 0x24, 0xD5, 0x1B, 0x9A, 0xAE, 0x36, 0x0F, 0x0D, 0x6A, 0x4D, 0x0A, 0x31, 0x91, 0xD1, 0x1C, 0x05, 0x4C, 0x3A, 0x20, 0xA7, 0xC1, 0x8C, 0xD3, 0xF6, 0xC6, 0xBB, 0x82, 0xD1, 0x48, 0xF2, 0x06, 0x8D, 0x28, 0xC8, 0xE9, 0x6C, 0xAB, 0xB7, 0xD1, 0x9D, 0x72, 0x92, 0xA0, 0xC7, 0x09, 0x81, 0x7B, 0x6A, 0x2A, 0xF7, 0xE5, 0x88, 0xE9, 0xEF, 0x82, 0xD3, 0xEC, 0x0B, 0xAA, 0x77, 0x4E, 0x32, 0xFF, 0x50, 0x47, 0xEA, 0x81, 0x49, 0xAA, 0x81, 0xD5, 0x7E, 0x7B, 0x41, 0xED, 0x99, 0x4B, 0xCB, 0x9A, 0x2F, 0x9C, 0xFE, 0x86, 0x98, 0x5E, 0xB3, 0x4A, 0x81, 0xFE, 0x66, 0xFA, 0xCF, 0x40, 0x77, 0xBE, 0xE2, 0x90, 0xD5, 0xB9, 0xA2, 0x0E, 0xDD, 0xB0, 0x3C, 0xB9, 0xCE, 0xE0, 0xC0, 0xA3, 0xA4, 0x09, 0xA2, 0x06, 0xB5, 0x31, 0x46, 0xF6, 0x9E, 0x3E, 0x33, 0xB0, 0x1A, 0x71, 0xDD, 0x6A, 0x36, 0x1D, 0x2A, 0x23, 0x18, 0x9F, 0xAD, 0x67, 0x5A, 0x9F, 0x94, 0x0C, 0xA0, 0xB4, 0x19, 0xA7, 0x68, 0xF6, 0xEE, 0x76, 0x5B, 0xBC, 0xC1, 0x9C, 0x4D, 0x02, 0xB9, 0x74, 0xC5, 0x32, 0xC4, 0x82, 0xE7, 0x71, 0xA1, 0x73, 0x8A, 0xCD, 0xAC, 0x8C, 0x86, 0xF6, 0x41, 0xB6, 0xAD, 0x89, 0xFB, 0x02, 0x29, 0xAB, 0xE3, 0xE0, 0x90, 0x6B, 0x1A, 0xE1, 0xF4, 0xDA, 0xFD, 0x1C, 0xAE, 0x8B, 0x51, 0x23, 0x8C, 0x10, 0x0B, 0xD7, 0x50, 0xFB, 0x95, 0x08, 0xC1, 0x3E, 0x70, 0x56, 0xFE, 0x0D, 0x49, 0xC7, 0x93, 0x99, 0xC5, 0x9A, 0xF7, 0x55, 0xC8, 0xEB, 0x28, 0xDF, 0x57, 0x3C, 0x8B, 0x23, 0x1B, 0x29, 0xA2, 0x98, 0x33, 0xEC, 0xF9, 0x7B, 0xE4, 0x1B, 0xEA, 0x2E, 0xAB, 0x31, 0xC2, 0xBB, 0x8B, 0x1D, 0x6C, 0x6D, 0x47, 0x2A, 0xBA, 0xFD, 0xF8, 0x9E, 0x48, 0x86, 0x24, 0x73, 0x0D, 0x61, 0x14, 0xEE, 0x19, 0x56, 0x2F, 0xC5, 0xC2, 0x02, 0x2A, 0x5E, 0xC5, 0xAC, 0x64, 0xBF, 0xF8, 0xAF, 0xB4, 0x43, 0xB6, 0x3D, 0xAC, 0xFB, 0x29, 0x25, 0x56, 0x3A, 0x16, 0x5B, 0x7C, 0x70, 0x27, 0x2C, 0xA6, 0xB8, 0x5C, 0x59, 0x3B, 0xDC, 0x89, 0x41, 0xE3, 0x32, 0x30, 0x9C, 0x50, 0xEB, 0xF3, 0xC1, 0x11, 0xD6, 0x0B, 0xF3, 0x6C, 0xC0, 0x38, 0xE8, 0x57, 0x20, 0x99, 0xC6, 0x66, 0x52, 0xA6, 0xEB, 0x70, 0xF2, 0xE5, 0xE9, 0x3A, 0xE4, 0x8E, 0x99, 0xEA, 0xC4, 0x4D, 0x67, 0x69, 0xEE, 0xF1, 0xEE, 0x79, 0x02, 0x6B, 0xBC, 0xEF, 0xA9, 0x54, 0x99, 0x43, 0xF2, 0x2F, 0xB7, 0xD5, 0x0E, 0xF3, 0x18, 0x4A, 0x67, 0xCD, 0xEE, 0x86, 0x67, 0x18, 0xA1, 0x30, 0x5A, 0xFB, 0x08, 0xA3, 0x99, 0x93, 0xD6, 0x70, 0x4C, 0x0A, 0x3C, 0xC0, 0x5B, 0x45, 0x06, 0xD3, 0x88, 0x5F, 0x37, 0xC5, 0x43, 0x7D, 0x8A, 0x3B, 0xC1, 0x50, 0xEA, 0x94, 0x45, 0xE8, 0x25, 0x9E, 0xE3, 0xDE, 0xC1, 0x82, 0xA9, 0x69, 0xD2, 0x72, 0x16, 0xAD, 0x29, 0x3A, 0x8D, 0xF8, 0xAD, 0x48, 0x81, 0x15, 0xF9, 0xE8, 0x28, 0xB9, 0x3D, 0x75, 0x7A, 0x89, 0x67, 0x62, 0x6A, 0x13, 0x6E, 0x06, 0x64, 0x97, 0xBE, 0x50, 0x33, 0xCD, 0xAC, 0x87, 0xAD, 0xA8, 0xE0, 0x06, 0xAC, 0x22, 0x50, 0x04, 0x9A, 0x72, 0xED, 0xBE, 0xE3, 0x31, 0x3B, 0x43, 0x59, 0x33, 0xEF, 0xF7, 0x8B, 0x15, 0xDD, 0xE9, 0xAC, 0x4D, 0xAE, 0xAC, 0x86, 0x19, 0x9C, 0xA4, 0xD9, 0xDE, 0x9B, 0x81, 0x44, 0x25, 0xA1, 0x76, 0x2F, 0x80, 0xF6, 0x63, 0x71, 0xE0, 0x33, 0x41, 0x5D, 0xE6, 0x02, 0x3F, 0x7A, 0x4B, 0xFE, 0xB6, 0x83, 0xB2, 0x19, 0x66, 0xAF, 0x6D, 0x8D, 0x71, 0x63, 0xE2, 0x80, 0x8A, 0x68, 0xB5, 0x57, 0x14, 0x09, 0x75, 0x80, 0x0D, 0x98, 0xD8, 0xB1, 0x32, 0xED, 0xCE, 0xE7, 0x62, 0x95, 0x6B, 0x40, 0x69, 0x96, 0x99, 0x8F, 0xF8, 0x55, 0xE1, 0x5D, 0xDC, 0x82, 0xB9, 0xEB, 0xB8, 0xB4, 0x29, 0x10, 0x44, 0x77, 0x93, 0xCD, 0x41, 0x6A, 0xF3, 0x02, 0xC6, 0x94, 0x91, 0x9B, 0x79, 0x7B, 0x72, 0x6E, 0x9A, 0xC0, 0xB9, 0xBD, 0xD1, 0x94, 0x20, 0xF2, 0x79, 0x13, 0x66, 0x1B, 0x68, 0x3B, 0xF3, 0x67, 0xE6, 0x3B, 0xBE, 0xB8, 0xD9, 0x85, 0x1C, 0xB7, 0x2D, 0xB8, 0x54, 0xAE, 0x9C, 0x6C, 0x42, 0xA1, 0xB1, 0x45, 0x03, 0x70, 0x15, 0x19, 0x17, 0xDE, 0x42, 0x6D, 0x32, 0x7A, 0xEF, 0x4A, 0xE5, 0xEC, 0x6C, 0xA6, 0xFC, 0x98, 0x15, 0xE1, 0xB4, 0x1D, 0xFA, 0x87, 0xA0, 0xC7, 0x49, 0xEC, 0x97, 0x84, 0xC6, 0x6B, 0x85, 0xDD, 0xD6, 0xDA, 0x3E, 0xD9, 0x02, 0x63, 0xE8, 0xF7, 0x7B, 0x54, 0xCC, 0x3C, 0x62, 0xED, 0x30, 0x77, 0x7A, 0x5C, 0xE8, 0x26, 0xA9, 0xC5, 0x18, 0xF0, 0x28, 0xC0, 0x3E, 0xF1, 0x0C, 0x3D, 0x66, 0x1C, 0x40, 0x79, 0xEE, 0x4F, 0x5E, 0xA7, 0xE1, 0xA0, 0x4E, 0xBF, 0x81, 0xBE, 0x8F, 0x59, 0xB6, 0xDD, 0x2F, 0xD7, 0x66, 0x8D, 0x45, 0xE9, 0x03, 0xDF, 0x41, 0x86, 0x46, 0x58, 0x26, 0xE4, 0xE8, 0x6D, 0xFD, 0xB9, 0xD6, 0xB9, 0xD1, 0x31, 0xD8, 0xC1, 0xCC, 0x23, 0x45, 0xB1, 0x08, 0x9A, 0xB0, 0xA6, 0x0C, 0xE7, 0xB6, 0x2F, 0x2B, 0xC9, 0x08, 0xCC, 0xC5, 0x9E, 0x32, 0x92, 0xA2, 0xE7, 0x27, 0x86, 0xFD, 0x6E, 0x42, 0x80, 0xC0, 0xFD, 0xC8, 0x87, 0xAA, 0x30, 0x30, 0x9E, 0x58, 0x46, 0x08, 0xA2, 0x40, 0xD7, 0x7B, 0xE2, 0xCF, 0x00, 0x46, 0x02, 0x64, 0x42, 0x36, 0x72, 0x0E, 0x61, 0xE0, 0xC5, 0x10, 0x04, 0x99, 0x9B, 0xB5, 0xED, 0x0C, 0x70, 0x8C, 0xEA, 0x72, 0x38, 0x99, 0x37, 0xD8, 0xE0, 0x51, 0xF1, 0x30, 0xA3, 0x44, 0xED, 0x4B, 0x7C, 0x9B, 0x9C, 0xA8, 0xAC, 0x6C, 0x63, 0xD8, 0xB9, 0x2C, 0x0F, 0xA3, 0xBA, 0xA5, 0xDD, 0x54, 0x0C, 0x13, 0xA8, 0xBF, 0xF1, 0x38, 0x50, 0xCF, 0xAE, 0xA1, 0xC5, 0xBD, 0x1E, 0x8E, 0xE1, 0xE5, 0xC0, 0xAA, 0x93, 0x6C, 0xC5, 0xB1, 0xB9, 0xC9, 0x69, 0xBC, 0xA7, 0xC6, 0xAA, 0xE0, 0x95, 0xC5, 0xCE, 0xAD, 0x36, 0x19, 0xAF, 0x39, 0x2F, 0xE7, 0xDA, 0xCF, 0x83, 0x99, 0x22, 0x91, 0x73, 0xD1, 0x69, 0xDF, 0x28, 0x3E, 0x44, 0x6C, 0x3D, 0x05, 0xB2, 0x3C, 0x1A, 0x69, 0x29, 0x54, 0x9A, 0x25, 0x3C, 0x7B, 0x81, 0x19, 0xBF, 0x7F, 0xF7, 0xFC, 0x74, 0xD7, 0xFD, 0xBF, 0x7F, 0x9A, 0x6B, 0x8A, 0x04, 0xE2, 0xE0, 0x9E, 0x23, 0x13, 0x63, 0xC6, 0x64, 0xFB, 0x85, 0x0E, 0xDA, 0x2D, 0xC9, 0x32, 0x58, 0xD1, 0x30, 0x64, 0xFA, 0x49, 0xD7, 0x54, 0x21, 0x1B, 0xDF, 0x25, 0x33, 0x00, 0xFB, 0xDC, 0x5B, 0x49, 0xC1, 0x57, 0x12, 0xDB, 0xEE, 0xFA, 0xEB, 0x80, 0x0F, 0xBF, 0xD8, 0xF3, 0xB2, 0x33, 0xE6, 0xF7, 0x1F, 0xD7, 0x3E, 0x97, 0x69, 0x8E, 0xF1, 0x07, 0xB1, 0x0B, 0x37, 0xC1, 0xCD, 0x5D, 0xCE, 0x93, 0x43, 0x87, 0xE7, 0x75, 0x1B, 0x77, 0x43, 0xED, 0x73, 0x0B, 0x5A, 0x17, 0x55, 0xB7, 0xA2, 0xD5, 0x56, 0xE8, 0xFA, 0xC8, 0x96, 0xB1, 0x41, 0x6E, 0x8B, 0xB1, 0x39, 0x5F, 0xD6, 0x6C, 0x89, 0x1D, 0x9E, 0x8F, 0x11, 0xBB, 0x3F, 0x0F, 0xD4, 0x27, 0xA7, 0x8C, 0x08, 0xF2, 0xF0, 0x95, 0x14, 0xF8, 0x3D, 0xBC, 0x39, 0xF2, 0xBA, 0xEE, 0x3C, 0x39, 0xB6, 0x7A, 0xE0, 0x7B, 0x0E, 0xF0, 0x4D, 0x24, 0x13, 0x98, 0xFB, 0x76, 0x9D, 0x4C, 0x94, 0xB1, 0xE0, 0xAF, 0xC3, 0x15, 0x18, 0xFA, 0x77, 0x26, 0x01, 0xB4, 0x64, 0x02, 0xE5, 0xCA, 0x80, 0x5A, 0x5F, 0xC2, 0x59, 0x2C, 0x84, 0x6B, 0x20, 0x48, 0xA6, 0xB4, 0x08, 0xAB, 0x51, 0x7A, 0xF4, 0x78, 0xEC, 0x37, 0xB7, 0x3E, 0xAC, 0x6A, 0x6B, 0x9B, 0x36, 0x2F, 0x91, 0x98, 0xD4, 0xC9, 0xC1, 0x69, 0xC2, 0xB5, 0x4F, 0xB0, 0x88, 0x08, 0xFB, 0x71, 0xD9, 0x51, 0x0C, 0x08, 0x7F, 0x1F, 0x66, 0x9E, 0xDA, 0xC5, 0xA5, 0x26, 0x6A, 0x86, 0x6C, 0xBA, 0xA7, 0xC8, 0x5A, 0x4F, 0x8E, 0x50, 0x3B, 0xE1, 0xB7, 0x68, 0xBA, 0x34, 0x36, 0x54, 0x56, 0x10, 0xEA, 0x1A, 0x7C, 0x9E, 0xA9, 0xBD, 0x16, 0x3B, 0x39, 0xA0, 0x7B, 0x6E, 0xD3, 0xA4, 0xF3, 0xC9, 0x87, 0x0D, 0xD0, 0x11, 0xE7, 0x9F, 0x95, 0xAA, 0x2E, 0x66, 0xF7, 0x06, 0xF3, 0x43, 0xC0, 0x29, 0x15, 0xCF, 0xEA, 0xC8, 0x8D, 0x21, 0x23, 0x8D, 0x5D, 0x64, 0x1C, 0xBF, 0xEA, 0x4B, 0x2C, 0xDC, 0x1A, 0x44, 0x94, 0x10, 0x30, 0xE4, 0x6F, 0x20, 0xFA, 0x5E, 0x41, 0x8D, 0x49, 0xE4, 0x8F, 0x25, 0xA0, 0x1B, 0x3E, 0x02, 0x98, 0x84, 0xC6, 0x62, 0xF7, 0xD6, 0xC1, 0x16, 0x13, 0x39, 0xB3, 0xC4, 0x3A, 0xB4, 0xBC, 0xA6, 0x9B, 0x27, 0x0A, 0x1C, 0x6A, 0x4F, 0x4F, 0x01, 0xEB, 0x38, 0x3C, 0xCD, 0xAD, 0x6B, 0x53, 0xC4, 0xBE, 0xB4, 0xEE, 0x41, 0x8F, 0x69, 0xD7, 0x02, 0xB3, 0x75, 0x08, 0x96, 0xC6, 0xB3, 0xB6, 0xC4, 0x88, 0xA9, 0x58, 0xD1, 0xE4, 0x4C, 0x8A, 0x39, 0x83, 0x79, 0xEF, 0x02, 0x2C, 0x2F, 0xF5, 0x43, 0x2D, 0x35, 0xAE, 0x27, 0x93, 0xF3, 0x6D, 0x46, 0x22, 0x66, 0xC5, 0x55, 0x71, 0x8C, 0x68, 0xA8, 0xE6, 0x16, 0x94, 0xDB, 0xBA, 0x51, 0x90, 0x59, 0xBC, 0x77, 0x45, 0xE4, 0xBE, 0x82, 0x21, 0x6C, 0x3A, 0x73, 0x93, 0xCF, 0x7E, 0xC5, 0xC5, 0x64, 0x2C, 0x47, 0x93, 0x27, 0x1F, 0x7B, 0xED, 0x55, 0x16, 0xC1, 0x6D, 0x89, 0x0F, 0xC5, 0x09, 0xD8, 0x44, 0xB4, 0x4F, 0x01, 0xA8, 0x5A, 0x2B, 0xEF, 0xF5, 0x84, 0x11, 0x8E, 0x45, 0xD7, 0x96, 0x9A, 0x6F, 0x60, 0x5A, 0x30, 0x42, 0x78, 0x66, 0xC3, 0x57, 0x88, 0x25, 0x8B, 0xF3, 0xEA, 0xDA, 0xAA, 0xF4, 0xBD, 0x5C, 0x9D, 0xB2, 0x81, 0xE0, 0x88, 0x78, 0xCB, 0x4D, 0xC2, 0xFB, 0x1B, 0xDD, 0x79, 0x5F, 0xBD, 0x08, 0x2F, 0x79, 0x91, 0xAB, 0x62, 0xC3, 0xED, 0x72, 0xA5, 0xD2, 0x61, 0xE4, 0x08, 0xE2, 0x6D, 0x48, 0xD7, 0xD7, 0x65, 0x0A, 0xB1, 0xB5, 0x06, 0xCD, 0x9A, 0x36, 0x51, 0x92, 0x03, 0x46, 0x55, 0x2E, 0x24, 0x94, 0x0C, 0x44, 0x35, 0x28, 0xC4, 0x0F, 0xC2, 0xF5, 0xB3, 0xB4, 0x2D, 0x5D, 0x16, 0xF0, 0x27, 0x87, 0xC7, 0xA5, 0x48, 0x0B, 0x9A, 0x93, 0x69, 0x0B, 0x8E, 0xCF, 0xF6, 0xBF, 0xE4, 0x67, 0x8F, 0x4A, 0x63, 0x9E, 0x6C, 0x74, 0x4A, 0x1F, 0xAD, 0x21, 0xFE, 0x51, 0xD7, 0x6A, 0x65, 0x9F, 0x31, 0x6B, 0x34, 0x72, 0x18, 0xBF, 0x09, 0x37, 0x09, 0x7F, 0x4C, 0x63, 0x79, 0xD4, 0x5C, 0x57, 0xF2, 0x71, 0xD1, 0x8B, 0xBD, 0xC0, 0x35, 0xB8, 0xA4, 0x25, 0x80, 0xFF, 0x7D, 0x16, 0x52, 0x0F, 0x0E, 0x14, 0xCE, 0x4F, 0xF8, 0xCB, 0x9C, 0x50, 0xD4, 0x17, 0x8A, 0x7F, 0x4C, 0x9F, 0x82, 0x40, 0x61, 0x28, 0x95, 0xB2, 0x1A, 0x63, 0x30, 0xC3, 0x5B, 0x6C, 0x3A, 0xDB, 0x5F, 0xEC, 0x5F, 0x30, 0xF0, 0x81, 0x05, 0xFC, 0xFD, 0xE8, 0x7D, 0x3A, 0xFF, 0xBB, 0xF7, 0x53, 0x91, 0x44, 0x27, 0x05, 0x1E, 0x4E, 0x34, 0xDF, 0x03, 0x25, 0x09, 0x71, 0x53, 0x9A, 0x70, 0xE3, 0xAC, 0x34, 0x12, 0x7A, 0xC3, 0xB9, 0xC2, 0x1C, 0x48, 0x68, 0x39, 0x88, 0x71, 0x26, 0x44, 0xE9, 0x98, 0x69, 0xE6, 0x6F, 0x5C, 0x56, 0x97, 0xAA, 0xD0, 0x5D, 0xC0, 0xAA, 0x95, 0xEC, 0x0D, 0xC1, 0x65, 0xB1, 0xEA, 0x69, 0x66, 0xC4, 0xBA, 0x0A, 0x9F, 0xEA, 0x64, 0xB9, 0xF3, 0xFB, 0x1F, 0x26, 0xDB, 0x9F, 0xD7, 0x02, 0x9E, 0x42, 0x33, 0xEB, 0x56, 0xAB, 0x1D, 0xB5, 0xFB, 0xC5, 0x6D, 0x20, 0x98, 0xA4, 0x3E, 0xE7, 0xE5, 0xC1, 0x3C, 0xB1, 0x05, 0xA0, 0x25, 0x1A, 0x1E, 0x28, 0x6D, 0x08, 0xE1, 0xD5, 0xAE, 0x90, 0xEF, 0x6F, 0xFA, 0xB5, 0x42, 0x58, 0xEE, 0xD4, 0xA2, 0x90, 0xA0, 0x65, 0xFC, 0xCA, 0xA2, 0x64, 0x00, 0xB7, 0x69, 0x72, 0xEF, 0xFD, 0x2A, 0x44, 0xF5, 0xAF, 0x64, 0x80, 0x0F, 0x79, 0x20, 0x9A, 0xB8, 0x6B, 0x7F, 0x9E, 0x0F, 0xB3, 0x18, 0x1A, 0xCC, 0x19, 0x17, 0x2B, 0xB8, 0x2C, 0xCA, 0xA5, 0x8E, 0xC5, 0x1B, 0xB1, 0xEC, 0xF6, 0x9C, 0x3C, 0x20, 0x10, 0x04, 0x6F, 0x35, 0x26, 0x8D, 0x0C, 0x3B, 0xB3, 0x8C, 0x08, 0xE3, 0xDE, 0x2C, 0x09, 0x1E, 0xEB, 0x33, 0x08, 0x38, 0x28, 0xA3, 0x4F, 0x32, 0xD1, 0xDD, 0x1C, 0x51, 0x38, 0x94, 0x07, 0xC0, 0xF8, 0x4C, 0x0E, 0x88, 0x95, 0x6B, 0x24, 0xC3, 0xE3, 0x24, 0xD1, 0xD8, 0xF8, 0xD0, 0xA0, 0x5A, 0xFE, 0xF0, 0x1C, 0xDF, 0x03, 0x62, 0xE1, 0x61, 0xFF, 0x43, 0x19, 0xA6, 0x85, 0x6E, 0xE3, 0xF6, 0xF7, 0x6A, 0xF9, 0x48, 0x42, 0xA2, 0xC5, 0x04, 0xB5, 0x42, 0xC2, 0xFB, 0xC0, 0xF8, 0xC1, 0x60, 0x95, 0xFE, 0x87, 0xA9, 0xE1, 0xBD, 0x05, 0x03, 0xB4, 0xB0, 0xC7, 0x60, 0x7C, 0x1C, 0x62, 0xBC, 0x0B, 0x0C, 0x6D, 0xE3, 0x8E, 0x98, 0x93, 0x2B, 0xF4, 0x75, 0xF8, 0x3F, 0x18, 0xF3, 0x07, 0x62, 0xEE, 0x7E, 0x38, 0x26, 0x76, 0x40, 0xEC, 0x17, 0x93, 0xC2, 0xD1, 0xC5, 0x5A, 0xD9, 0xA3, 0x15, 0x74, 0x3F, 0xF0, 0xA8, 0x54, 0xEA, 0x8C, 0x3A, 0xDF, 0x51, 0xC7, 0xFD, 0xF5, 0xFA, 0x0F, 0x83, 0x9C, 0x58, 0x5D, 0x81, 0x64, 0xFA, 0xED, 0x82, 0x80, 0x1C, 0x5B, 0x36, 0xA5, 0x44, 0x5B, 0x77, 0x91, 0x7E, 0x42, 0x05, 0xF5, 0x33, 0x95, 0xCC, 0xC8, 0x09, 0x96, 0xC3, 0x4D, 0x72, 0x1D, 0xA3, 0x4C, 0x57, 0x40, 0xDA, 0xB8, 0x7E, 0xF8, 0xCF, 0xFA, 0xF0, 0xB7, 0x13, 0xD0, 0xCF, 0x86, 0xAF, 0x60, 0x6F, 0xCE, 0x74, 0x5E, 0xC3, 0xC5, 0x5F, 0xB6, 0x2D, 0x99, 0xBA, 0x2D, 0x68, 0x5C, 0xFA, 0x89, 0x44, 0xFB, 0xDD, 0x58, 0x6D, 0x5F, 0xB9, 0xBE, 0xC6, 0x83, 0xEC, 0xAA, 0x1B, 0x95, 0x66, 0x53, 0xA3, 0x6F, 0xCD, 0x08, 0x58, 0x09, 0xB4, 0x2D, 0xF7, 0x14, 0x31, 0x03, 0xEA, 0xB5, 0x87, 0xE1, 0x87, 0xE5, 0x1F, 0x06, 0x42, 0xAD, 0xE0, 0x1A, 0x76, 0x95, 0x8C, 0x72, 0x8A, 0xDD, 0x41, 0xD5, 0xB3, 0x6D, 0x55, 0x6C, 0xAB, 0x38, 0x0A, 0x0F, 0xE4, 0xC2, 0x08, 0xA6, 0x91, 0xE2, 0x2A, 0x49, 0x29, 0x1F, 0x24, 0x98, 0x6F, 0x43, 0x81, 0xD5, 0x9C, 0x15, 0xE7, 0x7B, 0x14, 0x84, 0x74, 0xC9, 0x92, 0x59, 0xE6, 0xA2, 0xB4, 0xF0, 0xE2, 0x8E, 0x56, 0xA6, 0x8F, 0xB1, 0xAE, 0x8E, 0x83, 0x55, 0xB2, 0x56, 0xB9, 0xCA, 0x17, 0x05, 0x2D, 0x46, 0x3A, 0x6B, 0x32, 0xC2, 0x89, 0x56, 0xFB, 0x65, 0x59, 0x2B, 0xA9, 0x38, 0xD6, 0xE9, 0x51, 0xB8, 0x33, 0x4E, 0x3E, 0x30, 0x41, 0x30, 0x8D, 0x8A, 0xF5, 0x2C, 0xEC, 0xC0, 0xDA, 0xA7, 0x30, 0x24, 0x23, 0x5F, 0x2B, 0x5D, 0xE6, 0x9A, 0x27, 0xF6, 0x91, 0x0A, 0x78, 0x26, 0xFE, 0xA5, 0xA3, 0xFD, 0x81, 0xDB, 0x8D, 0xA8, 0x24, 0xE6, 0xD2, 0xEA, 0xBA, 0xD0, 0xD3, 0x93, 0x29, 0x7A, 0x16, 0xA8, 0x77, 0xD8, 0xE9, 0x56, 0x2C, 0xB8, 0x72, 0x69, 0x65, 0x99, 0x8B, 0x9D, 0x00, 0xA0, 0x0D, 0x81, 0x6C, 0xC2, 0x3A, 0x2F, 0x62, 0x13, 0xC6, 0xFA, 0x5A, 0x1E, 0x97, 0x5B, 0x66, 0xD3, 0xD5, 0xD4, 0x33, 0x0B, 0x47, 0xC0, 0x62, 0x92, 0x7E, 0x8A, 0x31, 0x56, 0xCD, 0x61, 0x5C, 0x98, 0xE8, 0x5F, 0x01, 0x6F, 0xA1, 0x10, 0xB0, 0x90, 0xC4, 0xF0, 0xC5, 0x66, 0x69, 0x62, 0x51, 0x09, 0x57, 0x59, 0x3D, 0x66, 0xFF, 0x9F, 0x7C, 0x0C, 0x48, 0x23, 0x19, 0xCC, 0x5A, 0x73, 0x66, 0x30, 0x95, 0xCB, 0xE8, 0x88, 0xD3, 0xB2, 0x8D, 0xD6, 0x72, 0x8D, 0xDB, 0x33, 0x40, 0x91, 0x77, 0xCD, 0x54, 0x51, 0xAF, 0x8C, 0xBE, 0x63, 0x86, 0x18, 0x01, 0x4D, 0xAF, 0xF7, 0x09, 0xF0, 0xD8, 0x92, 0x33, 0x7A, 0x46, 0x6A, 0x68, 0x4E, 0x4F, 0x64, 0x22, 0xB2, 0xC5, 0x0D, 0x58, 0x66, 0x29, 0xA9, 0x62, 0x14, 0x58, 0x4F, 0x2D, 0xAC, 0xE3, 0x2B, 0xF6, 0x9F, 0x76, 0x26, 0x3F, 0x2C, 0x3B, 0xE0, 0x42, 0x53, 0xED, 0x37, 0x48, 0x51, 0x87, 0x2C, 0x38, 0x3B, 0x27, 0xA9, 0x98, 0xAE, 0xAD, 0xD6, 0xA7, 0x09, 0xCB, 0x14, 0x2A, 0x7A, 0xB7, 0x27, 0x82, 0x6E, 0x0E, 0xA2, 0xA6, 0xB9, 0xEF, 0x61, 0x2D, 0x2A, 0xC6, 0x35, 0xC2, 0x1B, 0xE4, 0xCE, 0x3F, 0x2E, 0x3F, 0x42, 0x18, 0x0D, 0x63, 0xED, 0x9B, 0x60, 0x3E, 0xE8, 0x14, 0x16, 0x0B, 0x21, 0x4C, 0x8E, 0x2E, 0x67, 0x23, 0x07, 0x43, 0xD7, 0xBD, 0xC1, 0x67, 0xD8, 0x65, 0xAE, 0x8F, 0x60, 0xE8, 0x2E, 0x1F, 0x34, 0x92, 0xBA, 0x11, 0x9F, 0xF3, 0xDA, 0x63, 0xC2, 0xD5, 0x28, 0xF0, 0x81, 0x52, 0x5D, 0x4D, 0x3A, 0xFC, 0x1B, 0xED, 0xFD, 0x15, 0x6F, 0xC0, 0xBB, 0xED, 0xFF, 0xAF, 0x20, 0xBE, 0x80, 0xA9, 0xDD, 0xED, 0x41, 0x92, 0x74, 0x32, 0x41, 0xC3, 0x1C, 0x4D, 0x36, 0x36, 0xBC, 0x60, 0x77, 0x61, 0xB5, 0xE1, 0x61, 0x7D, 0xD1, 0x38, 0x20, 0x63, 0x57, 0x1C, 0x9F, 0x86, 0xAC, 0x0E, 0x79, 0xAA, 0x4D, 0x88, 0xCD, 0x2F, 0xCF, 0x4E, 0x03, 0x0E, 0xF3, 0x21, 0x1B, 0xAA, 0x01, 0x88, 0x97, 0x71, 0x86, 0xD6, 0xAB, 0xD3, 0x15, 0x7F, 0xCF, 0x9E, 0xF5, 0xB6, 0x91, 0x84, 0x61, 0xB5, 0xBF, 0xC2, 0x74, 0xC7, 0x33, 0x0F, 0x74, 0x1D, 0xDE, 0xF9, 0x86, 0x69, 0x9C, 0x5B, 0xFB, 0x52, 0xA6, 0xBE, 0xD5, 0xBC, 0xD1, 0xE3, 0x40, 0x05, 0xC6, 0x17, 0x11, 0x87, 0xBE, 0x08, 0xDA, 0xBD, 0x9D, 0xFF, 0x89, 0x3B, 0x63, 0x77, 0xE5, 0xB8, 0x30, 0x41, 0x09, 0x70, 0x79, 0xB1, 0x1E, 0x57, 0x58, 0xD7, 0xEF, 0xEF, 0xD0, 0x8C, 0x63, 0xD2, 0xEA, 0xB1, 0xEA, 0x0A, 0x70, 0x56, 0x92, 0x14, 0xCF, 0xA5, 0x95, 0x5E, 0xFA, 0xEA, 0x0B, 0xA3, 0x1B, 0x76, 0x0E, 0x8C, 0x0A, 0x69, 0x3B, 0x86, 0x78, 0x32, 0xDF, 0x5D, 0x10, 0x23, 0x7E, 0x69, 0xD8, 0x3C, 0x1C, 0xD8, 0x8B, 0x50, 0x95, 0x7B, 0x11, 0x76, 0xFC, 0xB7, 0x1B, 0xDB, 0xF8, 0x8A, 0xBF, 0x76, 0x8E, 0x21, 0xCB, 0x58, 0x39, 0x73, 0x07, 0x78, 0x75, 0x85, 0xD4, 0xEE, 0x60, 0x57, 0xDF, 0xE5, 0xFB, 0xEF, 0x66, 0x38, 0xC6, 0xA6, 0x01, 0xAD, 0x13, 0x5F, 0x16, 0x04, 0x33, 0xDD, 0x9C, 0x21, 0xE1, 0xD4, 0xFD, 0x72, 0x26, 0x0C, 0xA4, 0x2D, 0x22, 0x80, 0x56, 0x24, 0x69, 0x0E, 0xD4, 0x5E, 0xB3, 0x2B, 0x79, 0x6A, 0xE4, 0x9D, 0x70, 0x66, 0xFA, 0x73, 0xA2, 0xDD, 0x90, 0x51, 0x33, 0xB0, 0x0C, 0x4A, 0xAF, 0x0B, 0xDE, 0x62, 0x8B, 0x45, 0x8F, 0xA1, 0x1E, 0xE4, 0xEA, 0xE2, 0x42, 0xAA, 0x43, 0x11, 0xA3, 0x84, 0xE8, 0x7D, 0x91, 0x88, 0x15, 0x72, 0xDD, 0x50, 0x31, 0xA9, 0xA5, 0x17, 0x1B, 0x8C, 0xC8, 0x68, 0x7D, 0xCF, 0x0F, 0xE4, 0x66, 0xFF, 0x63, 0x30, 0xAA, 0x19, 0x4F, 0x50, 0xF6, 0xA4, 0xEC, 0x45, 0xD0, 0x69, 0x5A, 0x22, 0x0A, 0x0F, 0xA6, 0x9C, 0x74, 0x0D, 0x4B, 0x12, 0x14, 0xC0, 0x5B, 0xFB, 0xCE, 0x0D, 0xAD, 0xB1, 0xBD, 0x92, 0x1C, 0x98, 0x80, 0xDA, 0xD1, 0xAE, 0x5D, 0x7C, 0xE6, 0x4B, 0x52, 0x37, 0xDE, 0x70, 0x67, 0x14, 0x04, 0x5C, 0xEF, 0x9D, 0xD3, 0xAF, 0x05, 0x8E, 0xA8, 0x9D, 0x97, 0xDE, 0x9D, 0xD9, 0x19, 0xBE, 0x34, 0x95, 0x4D, 0x02, 0x15, 0x95, 0x79, 0xDD, 0x59, 0x0B, 0x89, 0x60, 0x71, 0xC3, 0x64, 0x40, 0x21, 0x26, 0x90, 0xA6, 0x80, 0xDE, 0x06, 0x91, 0xC7, 0x94, 0x58, 0x11, 0x5C, 0x64, 0x5F, 0xB5, 0x0D, 0x6A, 0x11, 0xE0, 0x54, 0xF0, 0x5C, 0xC8, 0x4D, 0xEA, 0x52, 0x2B, 0xCE, 0x3F, 0x1E, 0xFF, 0x21, 0x1A, 0xAF, 0x28, 0x17, 0x69, 0xC8, 0x8F, 0x1C, 0xC5, 0x72, 0x8A, 0x5C, 0x36, 0xCF, 0x40, 0x68, 0xDE, 0x3D, 0x9F, 0xED, 0x4A, 0x16, 0xCD, 0x5B, 0x54, 0x03, 0xD8, 0x9B, 0x07, 0x36, 0xB2, 0x40, 0xD0, 0x73, 0x6D, 0xCA, 0x6E, 0xEA, 0xAD, 0xA4, 0x90, 0x25, 0xFE, 0x7F, 0xCA, 0x1E, 0xF2, 0xF2, 0xC1, 0xDA, 0xAE, 0x4A, 0x7F, 0x60, 0xE2, 0xE0, 0xE3, 0xD5, 0x69, 0xD3, 0xF9, 0xE1, 0x8F, 0x4F, 0xBB, 0x5F, 0xF6, 0xAA, 0x74, 0x32, 0x69, 0x42, 0x88, 0x6E, 0xB9, 0x22, 0x2D, 0xF6, 0xE2, 0x17, 0xAA, 0xC5, 0x3E, 0x6E, 0xB5, 0x45, 0x05, 0xE5, 0x59, 0x26, 0xAC, 0x94, 0x61, 0x70, 0xFC, 0x72, 0xA3, 0xCE, 0x68, 0x9F, 0xAA, 0x3E, 0xB1, 0x49, 0x7F, 0x02, 0x4C, 0x9B, 0xCA, 0x72, 0xFF, 0x46, 0x46, 0x11, 0xF9, 0x3D, 0x29, 0x43, 0x27, 0xD3, 0x7A, 0x40, 0x5D, 0xFC, 0x52, 0x91, 0xC2, 0xD9, 0x94, 0x16, 0x13, 0xF3, 0x9C, 0xD3, 0x36, 0x1A, 0x0A, 0xD8, 0x5A, 0x50, 0x06, 0xD4, 0xF9, 0xDC, 0x2B, 0x6A, 0xE7, 0x71, 0x33, 0x07, 0xBD, 0xDB, 0x97, 0x6D, 0xB8, 0x23, 0x73, 0x80, 0xF1, 0x17, 0x6C, 0xF5, 0x47, 0x11, 0x68, 0x66, 0xCD, 0x4A, 0x14, 0xDB, 0x76, 0x14, 0xE0, 0x52, 0x8A, 0xB2, 0x6A, 0x18, 0x4E, 0xC7, 0xB5, 0x90, 0xC1, 0x2A, 0xB2, 0xD6, 0xA7, 0xAD, 0x53, 0xBE, 0xA6, 0xDF, 0xCB, 0xC6, 0xC5, 0xF1, 0x4D, 0x76, 0x69, 0xC1, 0x91, 0x0A, 0xA2, 0x86, 0x42, 0xAC, 0xFA, 0xD1, 0x79, 0x35, 0xDB, 0x5F, 0xB8, 0x50, 0xC7, 0x4E, 0xB7, 0xD6, 0x22, 0xD5, 0x1D, 0x1B, 0xCB, 0xE7, 0xCA, 0x21, 0x06, 0x25, 0xB7, 0x73, 0xCE, 0xA2, 0x0D, 0x33, 0xE5, 0xAA, 0xDD, 0x59, 0xB7, 0x9F, 0x89, 0x42, 0xEE, 0xE7, 0xB6, 0x82, 0xDA, 0x36, 0x18, 0x21, 0xC8, 0x89, 0x61, 0x30, 0x0F, 0xD1, 0x8D, 0xE0, 0xF1, 0x8E, 0x9B, 0xE9, 0x16, 0x76, 0x49, 0x77, 0x30, 0x3D, 0x85, 0xE9, 0xDC, 0x52, 0x28, 0x0D, 0x38, 0xEE, 0x2D, 0x7F, 0xBD, 0x08, 0x54, 0x87, 0x71, 0x91, 0x73, 0x30, 0xCC, 0x58, 0xAD, 0x67, 0xDC, 0xF8, 0x12, 0x55, 0xC2, 0x14, 0xBA, 0x15, 0xFA, 0xD2, 0x79, 0xC6, 0xB6, 0x3A, 0x5C, 0x52, 0x10, 0xCE, 0xE9, 0xBC, 0xF6, 0xFA, 0xAA, 0x0E, 0xB3, 0x37, 0x23, 0x1E, 0x16, 0x75, 0x57, 0x62, 0xF5, 0x67, 0x2E, 0xC6, 0x3E, 0x46, 0x17, 0x77, 0xE1, 0x33, 0x85, 0x52, 0x08, 0x82, 0xF6, 0xA4, 0xE1, 0xD8, 0x8E, 0x83, 0xA7, 0x7B, 0xB1, 0xBE, 0x7D, 0x4C, 0xE8, 0x2E, 0x67, 0x58, 0x4C, 0x3E, 0x82, 0x85, 0x78, 0xBB, 0x7C, 0xDA, 0xC9, 0xB0, 0xBF, 0x6B, 0x17, 0x14, 0x8A, 0x0F, 0xB3, 0x76, 0x21, 0xAF, 0x54, 0x9C, 0x35, 0xE1, 0x02, 0x35, 0x3B, 0x85, 0xC6, 0x25, 0x67, 0x43, 0xA5, 0x07, 0x60, 0x81, 0x52, 0x66, 0x57, 0x4C, 0xF2, 0x81, 0x55, 0x9E, 0xED, 0xCF, 0x12, 0x8D, 0x79, 0x02, 0x8A, 0x80, 0xE4, 0xBC, 0x75, 0xCC, 0xE3, 0x61, 0x6B, 0xFA, 0x2F, 0xF0, 0x8A, 0xB7, 0xDD, 0x9E, 0xBD, 0x48, 0xC5, 0x9F, 0x12, 0xCD, 0x32, 0x96, 0x35, 0x1D, 0x87, 0xC1, 0x79, 0xFD, 0xE3, 0x5E, 0x2B, 0x70, 0x65, 0x1B, 0x89, 0x00, 0xA6, 0x82, 0xE0, 0xB6, 0xD1, 0x2C, 0xC6, 0x42, 0xB0, 0x2E, 0x36, 0x79, 0x01, 0x7F, 0xFA, 0xF1, 0x99, 0x33, 0x5D, 0x8B, 0xD6, 0x16, 0xE1, 0x75, 0x70, 0x0A, 0x51, 0xC7, 0xED, 0x1C, 0x23, 0x85, 0xC9, 0xB0, 0x74, 0xFF, 0x47, 0x15, 0x8E, 0xE9, 0x21, 0x80, 0x64, 0x17, 0x96, 0xA1, 0xC6, 0xA1, 0xF2, 0xCD, 0x79, 0xF1, 0x00, 0x3C, 0x14, 0x45, 0xA1, 0x49, 0x12, 0x0E, 0xFD, 0xD9, 0x56, 0x23, 0xA5, 0x58, 0xB2, 0x9B, 0x27, 0xBF, 0x75, 0xFA, 0xC2, 0x14, 0xBB, 0xD9, 0x0E, 0x98, 0x28, 0x59, 0x55, 0xF1, 0xC0, 0xD5, 0x27, 0x62, 0xE6, 0xFE, 0x82, 0x0E, 0xD8, 0xBF, 0xE4, 0xB7, 0x9E, 0x45, 0x74, 0x89, 0x01, 0x35, 0x14, 0x6E, 0xBE, 0x14, 0x44, 0x0B, 0x3A, 0x60, 0x1B, 0x18, 0xF0, 0x18, 0x88, 0xAD, 0x5A, 0xD4, 0xF1, 0xCC, 0x25, 0x74, 0x8F, 0xF4, 0x9E, 0x47, 0x96, 0xB5, 0xC7, 0xFE, 0xCE, 0x62, 0xA3, 0x98, 0xBD, 0x35, 0x55, 0xF5, 0x64, 0x2F, 0x62, 0x97, 0x6C, 0x25, 0x82, 0xC6, 0x3C, 0xCE, 0xD9, 0x16, 0xB9, 0x4F, 0x4C, 0x3A, 0xFB, 0xF9, 0x7F, 0x6C, 0x9A, 0x90, 0x3C, 0x80, 0xB2, 0x86, 0x5B, 0x0B, 0xC9, 0x7B, 0x2A, 0x06, 0xA7, 0xCC, 0xBB, 0xD6, 0x31, 0x9F, 0xDD, 0x0D, 0x76, 0x53, 0xD2, 0x66, 0x7C, 0xE3, 0x0E, 0xE7, 0x0B, 0x18, 0xFF, 0x59, 0x5C, 0xF2, 0xEC, 0x21, 0xDE, 0x6B, 0xF0, 0x5A, 0x36, 0xF9, 0xA3, 0x9A, 0xE7, 0xFB, 0xE8, 0xC0, 0x2D, 0x0B, 0x65, 0x47, 0xC4, 0x25, 0x81, 0x13, 0xFC, 0x0E, 0x41, 0x0E, 0x1B, 0xE1, 0x19, 0x58, 0x06, 0x3E, 0x41, 0xF3, 0xF7, 0x82, 0x84, 0x70, 0x30, 0x93, 0x7F, 0x52, 0x59, 0xC0, 0xF5, 0xDF, 0x06, 0x40, 0xF3, 0xB8, 0x60, 0x60, 0x9F, 0x01, 0x58, 0xFF, 0x9F, 0x1B, 0x88, 0x7B, 0xF9, 0x68, 0x6F, 0xC1, 0x12, 0x30, 0x9C, 0x4B, 0x52, 0x4B, 0x58, 0x82, 0x27, 0xCD, 0x66, 0xE7, 0x58, 0x6C, 0xA1, 0xB9, 0x25, 0x6C, 0x06, 0x02, 0x39, 0xA4, 0x4E, 0xA0, 0xCC, 0x4C, 0x07, 0x76, 0x62, 0x39, 0xB9, 0x28, 0xBD, 0xAE, 0x64, 0x59, 0xEB, 0xCD, 0xFC, 0x64, 0x3A, 0x84, 0x56, 0xDE, 0x9A, 0xB3, 0xE8, 0x06, 0xC3, 0xBA, 0x30, 0xED, 0x4A, 0x01, 0x5B, 0x26, 0xE4, 0xFB, 0xAE, 0x81, 0x8E, 0xFF, 0xF9, 0x75, 0x4A, 0xDE, 0x77, 0xF4, 0xFD, 0x45, 0x93, 0xBE, 0x43, 0x57, 0xE3, 0x87, 0x0F, 0x3A, 0xE9, 0x9A, 0x8A, 0x30, 0x0C, 0xBC, 0x4E, 0x62, 0x89, 0x83, 0x89, 0x93, 0x5F, 0xE1, 0x39, 0x68, 0x9A, 0x04, 0x9E, 0x87, 0xF5, 0xA1, 0x5A, 0x7C, 0x96, 0x22, 0x1F, 0x86, 0x71, 0x9D, 0xCC, 0xB9, 0x4A, 0x7B, 0xF8, 0x19, 0xFF, 0x50, 0x66, 0xDB, 0xE4, 0x26, 0x54, 0xD9, 0xEE, 0x78, 0x00, 0xA0, 0x73, 0x71, 0x10, 0xB5, 0x1A, 0x31, 0x62, 0x22, 0xE7, 0xC6, 0xC2, 0x12, 0x09, 0xDC, 0xB5, 0x05, 0x0A, 0x0F, 0xA1, 0x5D, 0x4A, 0x52, 0x50, 0xB0, 0xB8, 0x47, 0x4F, 0x3D, 0x10, 0xC9, 0x05, 0xEE, 0x22, 0x86, 0xC6, 0x83, 0xD0, 0xAC, 0x28, 0x4D, 0x30, 0xE9, 0xD9, 0xAD, 0x9F, 0xDA, 0xA3, 0x9C, 0xDB, 0xE2, 0x32, 0x64, 0x4A, 0xD6, 0xE8, 0xD3, 0x99, 0x07, 0x8F, 0x17, 0x76, 0x6F, 0x69, 0x93, 0xB4, 0x65, 0x37, 0xE2, 0x5E, 0xC4, 0xCD, 0xC1, 0x5E, 0xE7, 0xB9, 0xFD, 0xD0, 0xEC, 0xCD, 0x9F, 0x99, 0x0B, 0xEE, 0x6B, 0xB3, 0x24, 0x06, 0x0F, 0x99, 0x5E, 0x87, 0x73, 0xAE, 0xA7, 0x4E, 0x56, 0x35, 0x6B, 0xB5, 0x22, 0x38, 0x10, 0x36, 0xEB, 0xCC, 0xF7, 0xDA, 0x84, 0x07, 0x6A, 0x0A, 0xE5, 0xA8, 0x34, 0xB6, 0x5D, 0xDC, 0xB3, 0x0D, 0xBE, 0xC8, 0xB1, 0x3A, 0x7A, 0xA7, 0x96, 0x8B, 0x19, 0xFB, 0x26, 0xD4, 0xE9, 0x43, 0x59, 0x0C, 0x31, 0x92, 0xB7, 0x46, 0xD7, 0xB6, 0x03, 0x56, 0xA7, 0x25, 0x11, 0xA2, 0x37, 0x9E, 0xBC, 0x2B, 0xB7, 0xE6, 0xC5, 0x64, 0xBD, 0x16, 0x56, 0x88, 0x3E, 0xBB, 0x8C, 0x20, 0x63, 0x4F, 0x3C, 0x4C, 0x8A, 0x19, 0x63, 0x4B, 0xAB, 0x0B, 0xF9, 0x6F, 0xDF, 0x62, 0x0B, 0x9D, 0x5B, 0x07, 0x33, 0xC6, 0xFC, 0xAD, 0x8C, 0xAE, 0x03, 0x98, 0x44, 0xDC, 0x34, 0x26, 0x39, 0x99, 0xCB, 0x8C, 0xCC, 0xE9, 0xCE, 0x77, 0x00, 0x09, 0x48, 0xEA, 0xE2, 0xDC, 0x11, 0x1C, 0xE5, 0x20, 0x30, 0xA1, 0x3E, 0xB7, 0x96, 0x19, 0xFA, 0x78, 0x33, 0x79, 0x2B, 0xA8, 0xCC, 0x69, 0x2C, 0x8D, 0x1B, 0x61, 0xF6, 0x3E, 0x68, 0xBF, 0xCF, 0x95, 0xCC, 0x4A, 0x5C, 0x25, 0x92, 0xE9, 0x6F, 0x1D, 0xCB, 0xE8, 0x9F, 0x37, 0x8E, 0x59, 0x1A, 0x72, 0xCC, 0x35, 0x53, 0xFE, 0x4F, 0x50, 0xCA, 0x13, 0x22, 0x32, 0x1D, 0x77, 0x3A, 0xEA, 0x12, 0xE6, 0x8A, 0xF3, 0x31, 0x5B, 0x27, 0x53, 0x32, 0x3F, 0xCB, 0x86, 0x5D, 0xC3, 0xEE, 0xD3, 0x66, 0x19, 0xCF, 0xAE, 0x21, 0xE0, 0x21, 0xD4, 0x97, 0xD7, 0x52, 0x93, 0xB7, 0xDD, 0x67, 0xEC, 0xFA, 0x99, 0x71, 0x07, 0x1C, 0x95, 0x07, 0x70, 0xFA, 0xE5, 0x55, 0x1A, 0xF3, 0xF8, 0x53, 0x48, 0x43, 0x93, 0xAB, 0x0B, 0xA2, 0x4B, 0x08, 0xB4, 0xE3, 0xFC, 0x4F, 0x94, 0xFB, 0x8C, 0xA3, 0x1E, 0x49, 0xF3, 0x90, 0x31, 0xCE, 0x0E, 0x6C, 0x4E, 0x6C, 0x15, 0x74, 0x57, 0x64, 0x35, 0xF6, 0x77, 0x83, 0x6E, 0xB2, 0x8F, 0xFE, 0xA2, 0x31, 0x09, 0xBA, 0x77, 0xBB, 0xBF, 0x03, 0x4E, 0x8D, 0xC9, 0x7E, 0x25, 0xA2, 0x80, 0xEA, 0xBA, 0xB2, 0x7C, 0x11, 0x93, 0xB3, 0x5C, 0xCC, 0x2C, 0xC3, 0x24, 0xBC, 0x51, 0x4F, 0x08, 0xE7, 0x98, 0x87, 0x73, 0x64, 0x7D, 0xE9, 0xA9, 0x18, 0x16, 0x9C, 0x48, 0x12, 0x6E, 0x18, 0x57, 0x20, 0xA9, 0xBD, 0xD0, 0xEE, 0xDD, 0x22, 0xF9, 0xAC, 0xC7, 0x6C, 0xBA, 0x08, 0xAB, 0xA7, 0xFB, 0x7F, 0x1B, 0x03, 0x24, 0x7F, 0xE4, 0x2E, 0x6C, 0x22, 0xBD, 0x5E, 0x7A, 0x36, 0xD5, 0x41, 0x4D, 0x37, 0x4F, 0xDA, 0x86, 0xDA, 0x84, 0x72, 0x45, 0x0D, 0x9E, 0xE4, 0x79, 0xA6, 0x8F, 0xF6, 0x01, 0x1A, 0x6E, 0xFE, 0xC3, 0x3E, 0x9A, 0x02, 0xBD, 0x67, 0x91, 0x6B, 0xF0, 0x32, 0x81, 0xF7, 0x7D, 0xED, 0x63, 0x0B, 0xA2, 0x18, 0x83, 0xE1, 0x7B, 0x08, 0x46, 0x6E, 0xD4, 0x05, 0x77, 0xA1, 0xE1, 0xFF, 0xFE, 0x9D, 0x1A, 0x0E, 0x43, 0x6D, 0x0E, 0xFE, 0x30, 0x75, 0x92, 0x91, 0x8A, 0xFD, 0xD5, 0x57, 0xAD, 0xD4, 0x4E, 0x3A, 0x6B, 0xBF, 0x0F, 0x32, 0x2B, 0xAF, 0x65, 0x8E, 0xED, 0xC3, 0xD0, 0xED, 0x9E, 0x9C, 0x16, 0xAF, 0x5B, 0x1D, 0xAB, 0xDD, 0x68, 0xA8, 0x93, 0xFA, 0xA2, 0xC6, 0xE7, 0x67, 0x63, 0x27, 0xDB, 0x1C, 0x87, 0x2E, 0x9A, 0xCE, 0x73, 0xB0, 0xF3, 0x85, 0xE1, 0x13, 0x4E, 0x62, 0xF0, 0x85, 0xBD, 0x1D, 0x51, 0x9D, 0xD0, 0x09, 0x56, 0x47, 0x27, 0x04, 0x07, 0x3B, 0xA8, 0x51, 0x26, 0x4F, 0xA0, 0xFB, 0xB2, 0x96, 0x96, 0xFF, 0xB3, 0xB9, 0xC2, 0x24, 0xAD, 0x33, 0x68, 0xBF, 0x4D, 0xC7, 0x79, 0x2F, 0xDB, 0xF4, 0x4E, 0x4D, 0x88, 0x0B, 0xF2, 0x46, 0x6A, 0x2F, 0x7A, 0xFD, 0x8C, 0x23, 0x91, 0x07, 0x4B, 0x7A, 0x70, 0x59, 0x4C, 0x72, 0x7C, 0x94, 0x02, 0xC3, 0xDB, 0x0F, 0xED, 0x83, 0x9D, 0x31, 0xDB, 0x38, 0x8E, 0xDF, 0x9A, 0x64, 0x82, 0x22, 0x6E, 0x47, 0x6C, 0x98, 0x9E, 0x9C, 0xB3, 0x79, 0xE6, 0x11, 0x19, 0x5E, 0x04, 0xE7, 0x0F, 0x03, 0x4E, 0xEC, 0x40, 0x86, 0x08, 0xF5, 0xF5, 0x51, 0x9E, 0x1F, 0x08, 0xEE, 0x52, 0xB3, 0xBD, 0xD9, 0x3C, 0xAA, 0x3C, 0xA2, 0x0A, 0x58, 0xB2, 0x75, 0xEF, 0xDB, 0x07, 0xCF, 0xE2, 0x68, 0x3A, 0x58, 0x2A, 0xF1, 0xB6, 0xA8, 0xDB, 0x4C, 0xA7, 0x4F, 0xA8, 0xD3, 0x6B, 0x97, 0xB1, 0xDC, 0xC9, 0x64, 0xAA, 0x4C, 0xF8, 0x44, 0x9C, 0x46, 0x37, 0xB6, 0xC3, 0x26, 0x3C, 0x2A, 0x5D, 0x13, 0x70, 0xFD, 0x81, 0x0C, 0x75, 0xA2, 0x91, 0x4E, 0x3A, 0x4E, 0xF1, 0xA0, 0x0C, 0xB2, 0x8D, 0xC9, 0x81, 0x90, 0xEC, 0x3C, 0x2B, 0xD5, 0x79, 0x95, 0x8F, 0x66, 0xAC, 0x85, 0x3D, 0xA1, 0x59, 0xDE, 0x86, 0x3A, 0x2F, 0xCE, 0x46, 0xB3, 0xF8, 0xDB, 0x0E, 0xFE, 0x09, 0xD0, 0xA9, 0x68, 0x0D, 0xC0, 0x19, 0xB9, 0x2A, 0xB5, 0x4A, 0xF6, 0x91, 0x7E, 0x04, 0x50, 0x72, 0x5D, 0x68, 0x70, 0x24, 0x47, 0x49, 0xC9, 0x4E, 0xD6, 0xB1, 0xE3, 0x04, 0x09, 0x9A, 0xC2, 0x5A, 0x2C, 0x36, 0x6D, 0xEA, 0x9A, 0x60, 0x16, 0x3E, 0x27, 0xC4, 0x1C, 0x39, 0x03, 0xAB, 0x21, 0x5B, 0x0A, 0x5F, 0xBA, 0x9D, 0xDD, 0x80, 0xFC, 0x67, 0xFC, 0xCE, 0x72, 0xCF, 0x42, 0xC9, 0x41, 0xA1, 0xF2, 0x4B, 0x77, 0x75, 0xFC, 0x7A, 0xEA, 0x3C, 0xBF, 0x39, 0x8E, 0xB1, 0x4E, 0xCD, 0x34, 0x36, 0xF1, 0x4D, 0xEE, 0x92, 0x55, 0xC8, 0xAA, 0xD6, 0x64, 0x60, 0x8B, 0x0B, 0xB5, 0xBF, 0xBB, 0x76, 0x5D, 0xF2, 0x36, 0xC9, 0x88, 0x29, 0x89, 0x02, 0x1E, 0xEB, 0xF6, 0x05, 0x63, 0x28, 0xED, 0x4B, 0x94, 0x49, 0xE8, 0x0D, 0xD9, 0xAD, 0x1B, 0x26, 0x0A, 0x12, 0xDC, 0x37, 0xF8, 0xBB, 0x34, 0x16, 0xE4, 0xF2, 0xD2, 0xFD, 0xAD, 0x59, 0x71, 0xD2, 0x02, 0xA5, 0x03, 0x80, 0x40, 0x4B, 0xA3, 0xED, 0x89, 0x0F, 0x6A, 0x89, 0x22, 0x8B, 0x16, 0xB9, 0x24, 0x0E, 0xAE, 0x3C, 0xDB, 0x4F, 0xB0, 0x37, 0x49, 0x73, 0xE3, 0x4F, 0x41, 0xB5, 0x74, 0x40, 0xDD, 0xED, 0xC3, 0xA7, 0xE6, 0xB6, 0x75, 0x7A, 0xC8, 0xDD, 0x60, 0x37, 0x75, 0x5D, 0x38, 0x6A, 0x8B, 0x64, 0x46, 0xF3, 0xAD, 0x82, 0x0D, 0x50, 0x24, 0x63, 0x53, 0x3A, 0xDD, 0x53, 0x2D, 0x8E, 0x75, 0xA2, 0x91, 0x6D, 0x45, 0x08, 0xE3, 0xE7, 0xE5, 0xEA, 0x39, 0x98, 0xEF, 0x34, 0x44, 0x5F, 0x36, 0x7E, 0x4F, 0xE6, 0x07, 0xD4, 0x4A, 0x89, 0x98, 0x19, 0xB8, 0x87, 0x3B, 0x52, 0x0C, 0x67, 0xD9, 0x0E, 0xC4, 0x6A, 0x47, 0x25, 0xF9, 0xB4, 0x6E, 0x2E, 0x65, 0x06, 0x09, 0xB3, 0xA6, 0x80, 0x87, 0x68, 0xCF, 0x80, 0x71, 0x27, 0x24, 0xAD, 0x26, 0xF5, 0xF9, 0x66, 0x2B, 0x0D, 0x07, 0xC0, 0xDA, 0xE4, 0x85, 0xA6, 0xA6, 0x30, 0xBE, 0x7C, 0xA9, 0xB7, 0x18, 0x41, 0x78, 0xFD, 0x54, 0xDF, 0xB8, 0xB7, 0x30, 0x2D, 0x43, 0xB2, 0xEA, 0x90, 0x40, 0xB5, 0x40, 0x10, 0x12, 0x67, 0xDD, 0xA3, 0xDA, 0x98, 0x3E, 0x74, 0xCE, 0xAC, 0x5E, 0x0C, 0xCF, 0x4E, 0x7A, 0x68, 0x75, 0x7E, 0x3A, 0xB8, 0xAB, 0x78, 0x4D, 0x44, 0x78, 0x87, 0x63, 0x6D, 0x4E, 0x53, 0xB4, 0x2B, 0x97, 0x42, 0xAE, 0xD5, 0x44, 0xDD, 0xB5, 0x74, 0x79, 0xC9, 0x7E, 0x72, 0xA1, 0x6A, 0xC0, 0x36, 0xBA, 0xC2, 0x7A, 0xD8, 0xEA, 0x12, 0xD0, 0x66, 0x14, 0x36, 0x82, 0xA7, 0xD8, 0x34, 0x1D, 0xB8, 0xD8, 0xE2, 0x95, 0x48, 0x22, 0x90, 0x7E, 0xF6, 0x7E, 0x80, 0x51, 0xAF, 0x0C, 0x65, 0xD6, 0xE1, 0x9F, 0xB2, 0x4D, 0xDB, 0xF6, 0x6C, 0xD2, 0x08, 0x13, 0x47, 0xDF, 0x64, 0x14, 0xCA, 0xDE, 0x2C, 0x89, 0xBB, 0x9C, 0x07, 0xCE, 0x99, 0xF7, 0x0E, 0x39, 0xCE, 0x9F, 0xF8, 0x9B, 0x87, 0xCA, 0xA2, 0xF0, 0xDB, 0x91, 0x71, 0x1C, 0x32, 0x57, 0xAF, 0xEB, 0x2D, 0xB5, 0xA4, 0xF7, 0x8C, 0xEB, 0xC7, 0xEA, 0xA9, 0xF2, 0x0E, 0x36, 0xD5, 0x57, 0x57, 0x79, 0x30, 0x39, 0x8D, 0x60, 0x00, 0x94, 0xF3, 0x9E, 0xF0, 0x43, 0xD7, 0xE4, 0x48, 0xE6, 0xF3, 0xC9, 0x89, 0x3B, 0x9E, 0x38, 0x59, 0x8F, 0xC8, 0x22, 0x80, 0xBE, 0x9B, 0x4A, 0xDF, 0x52, 0xF5, 0x01, 0x50, 0x1B, 0x72, 0xE3, 0xD2, 0xF7, 0x0B, 0x4C, 0xD7, 0x23, 0x62, 0x11, 0x9C, 0x74, 0x48, 0xF4, 0xCD, 0xBA, 0xD4, 0x7C, 0x88, 0x9E, 0xF6, 0xFB, 0x34, 0xFA, 0x30, 0x73, 0x35, 0x2D, 0xE3, 0x85, 0x09, 0x14, 0x7A, 0x14, 0x3E, 0xC2, 0xEF, 0xA2, 0x89, 0x3D, 0x79, 0x01, 0x43, 0x4E, 0x1C, 0x84, 0x08, 0xB8, 0x65, 0xE6, 0x21, 0x81, 0x33, 0xC3, 0x8A }; ================================================ FILE: src/Cafe/HW/Latte/Common/ShaderSerializer.h ================================================ #pragma once #include "util/helpers/Serializer.h" namespace Latte { void SerializeShaderProgram(void* shaderProg, uint32 size, MemStreamWriter& memWriter); bool DeserializeShaderProgram(std::vector<uint8>& progData, MemStreamReader& memReader); }; ================================================ FILE: src/Cafe/HW/Latte/Core/FetchShader.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/ISA/LatteInstructions.h" #include "HW/Latte/Renderer/Renderer.h" #include "util/containers/LookupTableL3.h" #include "util/helpers/fspinlock.h" #if ENABLE_METAL #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" #endif #include <openssl/sha.h> /* SHA1_DIGEST_LENGTH */ #include <openssl/evp.h> /* EVP_Digest */ uint32 LatteShaderRecompiler_getAttributeSize(LatteParsedFetchShaderAttribute_t* attrib) { if (attrib->format == FMT_32_32_32_32 || attrib->format == FMT_32_32_32_32_FLOAT) return 4 * 4; else if (attrib->format == FMT_32_32_32 || attrib->format == FMT_32_32_32_FLOAT) return 3 * 4; else if (attrib->format == FMT_32_32 || attrib->format == FMT_32_32_FLOAT) return 2 * 4; else if (attrib->format == FMT_32 || attrib->format == FMT_32_FLOAT) return 1 * 4; else if (attrib->format == FMT_16_16_16_16 || attrib->format == FMT_16_16_16_16_FLOAT) return 4 * 2; else if (attrib->format == FMT_16_16 || attrib->format == FMT_16_16_FLOAT) return 2 * 2; else if (attrib->format == FMT_16 || attrib->format == FMT_16_FLOAT) return 1 * 2; else if (attrib->format == FMT_8_8_8_8) return 4 * 1; else if (attrib->format == FMT_8_8) return 2 * 1; else if (attrib->format == FMT_8) return 1 * 1; else if (attrib->format == FMT_2_10_10_10) return 4; else cemu_assert_unimplemented(); return 0; } uint32 LatteShaderRecompiler_getAttributeAlignment(LatteParsedFetchShaderAttribute_t* attrib) { if (attrib->format == FMT_32_32_32_32 || attrib->format == FMT_32_32_32_32_FLOAT) return 4; else if (attrib->format == FMT_32_32_32 || attrib->format == FMT_32_32_32_FLOAT) return 4; else if (attrib->format == FMT_32_32 || attrib->format == FMT_32_32_FLOAT) return 4; else if (attrib->format == FMT_32 || attrib->format == FMT_32_FLOAT) return 4; else if (attrib->format == FMT_16_16_16_16 || attrib->format == FMT_16_16_16_16_FLOAT) return 2; else if (attrib->format == FMT_16_16 || attrib->format == FMT_16_16_FLOAT) return 2; else if (attrib->format == FMT_16 || attrib->format == FMT_16_FLOAT) return 2; else if (attrib->format == FMT_8_8_8_8) return 1; else if (attrib->format == FMT_8_8) return 1; else if (attrib->format == FMT_8) return 1; else if (attrib->format == FMT_2_10_10_10) return 4; else cemu_assert_unimplemented(); return 4; } void LatteShader_calculateFSKey(LatteFetchShader* fetchShader) { uint64 key = 0; for (sint32 g = 0; g < fetchShader->bufferGroups.size(); g++) { LatteParsedFetchShaderBufferGroup_t& group = fetchShader->bufferGroups[g]; for (sint32 f = 0; f < group.attribCount; f++) { LatteParsedFetchShaderAttribute_t* attrib = group.attrib + f; key += (uint64)attrib->endianSwap; key = std::rotl<uint64>(key, 3); key += (uint64)attrib->nfa; key = std::rotl<uint64>(key, 3); key += (uint64)(attrib->isSigned?1:0); key = std::rotl<uint64>(key, 1); key += (uint64)attrib->format; key = std::rotl<uint64>(key, 7); key += (uint64)attrib->fetchType; key = std::rotl<uint64>(key, 8); key += (uint64)attrib->ds[0]; key = std::rotl<uint64>(key, 2); key += (uint64)attrib->ds[1]; key = std::rotl<uint64>(key, 2); key += (uint64)attrib->ds[2]; key = std::rotl<uint64>(key, 2); key += (uint64)attrib->ds[3]; key = std::rotl<uint64>(key, 2); key += (uint64)(attrib->aluDivisor+1); key = std::rotl<uint64>(key, 2); key += (uint64)attrib->attributeBufferIndex; key = std::rotl<uint64>(key, 8); key += (uint64)attrib->semanticId; key = std::rotl<uint64>(key, 8); if (g_renderer->GetType() == RendererAPI::Metal) { key += (uint64)attrib->offset; key = std::rotl<uint64>(key, 7); } else { key += (uint64)(attrib->offset & 3); key = std::rotl<uint64>(key, 2); } } } // todo - also hash invalid buffer groups? #if ENABLE_METAL if (g_renderer->GetType() == RendererAPI::Metal) { for (sint32 g = 0; g < fetchShader->bufferGroups.size(); g++) { LatteParsedFetchShaderBufferGroup_t& group = fetchShader->bufferGroups[g]; key += (uint64)group.attributeBufferIndex; key = std::rotl<uint64>(key, 5); } } #endif fetchShader->key = key; } uint32 LatteParsedFetchShaderBufferGroup_t::getCurrentBufferStride(uint32* contextRegister) const { uint32 bufferIndex = this->attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; uint32 bufferStride = (contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; return bufferStride; } void LatteFetchShader::CalculateFetchShaderVkHash() { // calculate SHA1 of all states that are part of the Vulkan graphics pipeline EVP_MD_CTX *ctx = EVP_MD_CTX_new(); EVP_DigestInit(ctx, EVP_sha1()); for(auto& group : bufferGroups) { // offsets for (sint32 t = 0; t < group.attribCount; t++) { uint32 offset = group.attrib[t].offset; EVP_DigestUpdate(ctx, &t, sizeof(t)); EVP_DigestUpdate(ctx, &offset, sizeof(offset)); } } uint8 shaDigest[SHA_DIGEST_LENGTH]; EVP_DigestFinal_ex(ctx, shaDigest, NULL); EVP_MD_CTX_free(ctx); // fold SHA1 hash into a 64bit value uint64 h = *(uint64*)(shaDigest + 0); h += *(uint64*)(shaDigest + 8); h += (uint64)*(uint32*)(shaDigest + 16); this->vkPipelineHashFragment = h; } #if ENABLE_METAL void LatteFetchShader::CheckIfVerticesNeedManualFetchMtl(uint32* contextRegister) { for (sint32 g = 0; g < bufferGroups.size(); g++) { LatteParsedFetchShaderBufferGroup_t& group = bufferGroups[g]; uint32 bufferIndex = group.attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; uint32 bufferStride = (contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; if (bufferStride % 4 != 0) mtlFetchVertexManually = true; for (sint32 f = 0; f < group.attribCount; f++) { auto& attr = group.attrib[f]; if (attr.offset + GetMtlVertexFormatSize(attr.format) > bufferStride) mtlFetchVertexManually = true; } } } #endif void _fetchShaderDecompiler_parseInstruction_VTX_SEMANTIC(LatteFetchShader* parsedFetchShader, uint32* contextRegister, const LatteClauseInstruction_VTX* instr) { uint32 semanticId = instr->getFieldSEM_SEMANTIC_ID(); // location (attribute index inside shader) uint32 bufferId = instr->getField_BUFFER_ID(); // the index used for GX2SetAttribBuffer (+0xA0) LatteConst::VertexFetchType2 fetchType = instr->getField_FETCH_TYPE(); auto srcSelX = instr->getField_SRC_SEL_X(); auto dsx = instr->getField_DST_SEL(0); auto dsy = instr->getField_DST_SEL(1); auto dsz = instr->getField_DST_SEL(2); auto dsw = instr->getField_DST_SEL(3); auto dataFormat = instr->getField_DATA_FORMAT(); uint32 offset = instr->getField_OFFSET(); auto nfa = instr->getField_NUM_FORMAT_ALL(); bool isSigned = instr->getField_FORMAT_COMP_ALL() == LatteClauseInstruction_VTX::FORMAT_COMP::COMP_SIGNED; auto endianSwap = instr->getField_ENDIAN_SWAP(); // get buffer cemu_assert_debug(bufferId >= 0xA0 && bufferId < 0xB0); uint32 bufferIndex = (bufferId - 0xA0); // get or add new attribute group (by buffer index) LatteParsedFetchShaderBufferGroup_t* attribGroup = nullptr; if (LatteFetchShader::isValidBufferIndex(bufferIndex)) { auto bufferGroupItr = std::find_if(parsedFetchShader->bufferGroups.begin(), parsedFetchShader->bufferGroups.end(), [bufferIndex](LatteParsedFetchShaderBufferGroup_t& bufferGroup) {return bufferGroup.attributeBufferIndex == bufferIndex; }); if (bufferGroupItr != parsedFetchShader->bufferGroups.end()) attribGroup = &(*bufferGroupItr); } else { auto bufferGroupItr = std::find_if(parsedFetchShader->bufferGroupsInvalid.begin(), parsedFetchShader->bufferGroupsInvalid.end(), [bufferIndex](LatteParsedFetchShaderBufferGroup_t& bufferGroup) {return bufferGroup.attributeBufferIndex == bufferIndex; }); if (bufferGroupItr != parsedFetchShader->bufferGroupsInvalid.end()) attribGroup = &(*bufferGroupItr); } // create new group if none found if (attribGroup == nullptr) { if (LatteFetchShader::isValidBufferIndex(bufferIndex)) attribGroup = &parsedFetchShader->bufferGroups.emplace_back(); else attribGroup = &parsedFetchShader->bufferGroupsInvalid.emplace_back(); attribGroup->attributeBufferIndex = bufferIndex; attribGroup->minOffset = offset; attribGroup->maxOffset = offset; } // add attribute sint32 groupAttribIndex = attribGroup->attribCount; if (attribGroup->attribCount < (groupAttribIndex + 1)) { attribGroup->attribCount = (groupAttribIndex + 1); attribGroup->attrib = (LatteParsedFetchShaderAttribute_t*)realloc(attribGroup->attrib, sizeof(LatteParsedFetchShaderAttribute_t) * attribGroup->attribCount); } attribGroup->attrib[groupAttribIndex].semanticId = semanticId; attribGroup->attrib[groupAttribIndex].format = (uint8)dataFormat; attribGroup->attrib[groupAttribIndex].fetchType = fetchType; attribGroup->attrib[groupAttribIndex].nfa = (uint8)nfa; attribGroup->attrib[groupAttribIndex].isSigned = isSigned; attribGroup->attrib[groupAttribIndex].offset = offset; attribGroup->attrib[groupAttribIndex].ds[0] = (uint8)dsx; attribGroup->attrib[groupAttribIndex].ds[1] = (uint8)dsy; attribGroup->attrib[groupAttribIndex].ds[2] = (uint8)dsz; attribGroup->attrib[groupAttribIndex].ds[3] = (uint8)dsw; attribGroup->attrib[groupAttribIndex].attributeBufferIndex = bufferIndex; attribGroup->attrib[groupAttribIndex].endianSwap = endianSwap; attribGroup->minOffset = (std::min)(attribGroup->minOffset, offset); attribGroup->maxOffset = (std::max)(attribGroup->maxOffset, offset); // get alu divisor if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_X) { cemu_assert_debug(fetchType != LatteConst::VertexFetchType2::INSTANCE_DATA); // aluDivisor 0 in combination with instanced data is not allowed? attribGroup->attrib[groupAttribIndex].aluDivisor = -1; } else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_W) { cemu_assert_debug(fetchType == LatteConst::VertexFetchType2::INSTANCE_DATA); // using constant divisor 1 with per-vertex data seems strange? (divisor is instance-only) // aluDivisor is constant 1 attribGroup->attrib[groupAttribIndex].aluDivisor = 1; } else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_Y) { // use alu divisor 1 attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[Latte::REGADDR::VGT_INSTANCE_STEP_RATE_0]; cemu_assert_debug(attribGroup->attrib[groupAttribIndex].aluDivisor > 0); } else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_Z) { // use alu divisor 2 attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[Latte::REGADDR::VGT_INSTANCE_STEP_RATE_1]; cemu_assert_debug(attribGroup->attrib[groupAttribIndex].aluDivisor > 0); } } void _fetchShaderDecompiler_parseVTXClause(LatteFetchShader* parsedFetchShader, uint32* contextRegister, std::span<uint8> clauseCode, size_t numInstructions) { const LatteClauseInstruction_VTX* instr = (LatteClauseInstruction_VTX*)clauseCode.data(); const LatteClauseInstruction_VTX* end = instr + numInstructions; while (instr < end) { if (instr->getField_VTX_INST() == LatteClauseInstruction_VTX::VTX_INST::_VTX_INST_SEMANTIC) { _fetchShaderDecompiler_parseInstruction_VTX_SEMANTIC(parsedFetchShader, contextRegister, instr); } else { assert_dbg(); } instr++; } } void _fetchShaderDecompiler_parseCF(LatteFetchShader* parsedFetchShader, uint32* contextRegister, std::span<uint8> programCode) { size_t maxCountCFInstructions = programCode.size_bytes() / sizeof(LatteCFInstruction); const LatteCFInstruction* cfInstruction = (LatteCFInstruction*)programCode.data(); const LatteCFInstruction* end = cfInstruction + maxCountCFInstructions; while (cfInstruction < end) { if (cfInstruction->getField_Opcode() == LatteCFInstruction::INST_VTX_TC) { auto vtxInstruction = cfInstruction->getParserIfOpcodeMatch<LatteCFInstruction_DEFAULT>(); cemu_assert_debug(vtxInstruction->getField_COND() == LatteCFInstruction::CF_COND::CF_COND_ACTIVE); _fetchShaderDecompiler_parseVTXClause(parsedFetchShader, contextRegister, vtxInstruction->getClauseCode(programCode), vtxInstruction->getField_COUNT()); } else if (cfInstruction->getField_Opcode() == LatteCFInstruction::INST_RETURN) { cemu_assert_debug(!cfInstruction->getField_END_OF_PROGRAM()); return; } else { cemu_assert_debug(false); // unhandled / unexpected CF instruction } if (cfInstruction->getField_END_OF_PROGRAM()) { cemu_assert_debug(false); // unusual for fetch shader? They should end with a return instruction break; } cfInstruction++; } cemu_assert_debug(false); // program must be terminated with an instruction that has EOP set? } // parse fetch shader and create LatteFetchShader object // also registers the fs in the cache (s_fetchShaderByHash) // can be assumed to be thread-safe, if called simultaneously on the same fetch shader only one shader will become registered. The others will be destroyed LatteFetchShader* LatteShaderRecompiler_createFetchShader(LatteFetchShader::CacheHash fsHash, uint32* contextRegister, uint32* fsProgramCode, uint32 fsProgramSize) { LatteFetchShader* newFetchShader = new LatteFetchShader(); newFetchShader->m_cacheHash = fsHash; if( (fsProgramSize&0xF) != 0 ) debugBreakpoint(); uint32 index = 0; // if the first instruction is a CF instruction then parse shader properly // otherwise fall back to our broken legacy method (where we assumed fetch shaders had no CF program) // this workaround is required to make sure old shader caches dont break // from old fetch shader gen (CF part missing): // {0x0000a001, 0x27961000, 0x00020000, 0x00000000} // {0x0000a001, 0x2c151002, 0x00020000, 0x00000000, 0x0000a001, 0x068d1000, 0x0000000c, ...} // {0x0000a001, 0x2c151000, 0x00020000, 0x00000000} // {0x0300aa21, 0x28cd1006, 0x00000000, 0x00000000, 0x0300ab21, 0x28cd1007, 0x00000000, ...} // shaders shipped with games (e.g. BotW): // {0x00000002, 0x01800400, 0x00000000, 0x8a000000, 0x1c00a001, 0x280d1000, 0x00090000, ...} // {0x00000002, 0x01800000, 0x00000000, 0x8a000000, 0x1c00a001, 0x27961000, 0x000a0000, ...} // {0x00000002, 0x01800c00, 0x00000000, 0x8a000000, 0x2c00a001, 0x2c151000, 0x000a0000, ...} // size 0x50 // {0x00000002, 0x01801000, 0x00000000, 0x8a000000, 0x1c00a001, 0x280d1000, 0x00090000, ...} // size 0x60 // {0x00000002, 0x01801c00, 0x00000000, 0x8a000000, 0x1c00a001, 0x280d1000, 0x00090000, ...} // size 0x90 // our new implementation: // {0x00000002, 0x01800400, 0x00000000, 0x8a000000, 0x0000a001, 0x2c151000, 0x00020000, ...} // for ALU instructions everything except the 01 is dynamic newFetchShader->bufferGroups.reserve(16); if (fsProgramSize == 0) { // empty fetch shader, seen in Minecraft // these only make sense when vertex shader does not call FS? LatteShader_calculateFSKey(newFetchShader); newFetchShader->CalculateFetchShaderVkHash(); #if ENABLE_METAL newFetchShader->CheckIfVerticesNeedManualFetchMtl(contextRegister); #endif return newFetchShader; } if ((fsProgramCode[0] & 1) == 0 && fsProgramCode[0] <= 0x30 && (fsProgramCode[1]&~((3 << 10)| (1 << 19))) == 0x01800000) { // very likely a CF instruction _fetchShaderDecompiler_parseCF(newFetchShader, contextRegister, { (uint8*)fsProgramCode, fsProgramSize }); } else { while (index < (fsProgramSize / 4)) { uint32 dword0 = fsProgramCode[index]; uint32 opcode = dword0 & 0x1F; index++; if (opcode == VTX_INST_MEM) { // this might be the clause initialization instruction? (Seems to be the first instruction always) // todo - upon further investigation, it seems like fetch shaders also start with a CF program. Our implementation doesnt emit one right now uint32 opcode2 = (dword0 >> 8) & 7; index += 3; } else if (opcode == VTX_INST_SEMANTIC) { _fetchShaderDecompiler_parseInstruction_VTX_SEMANTIC(newFetchShader, contextRegister, (const LatteClauseInstruction_VTX*)(fsProgramCode + index - 1)); index += 3; } } } newFetchShader->bufferGroups.shrink_to_fit(); // calculate group information // VBO offsets and stride uint32 vboOffset = 0; for (auto& bufferGroup : newFetchShader->bufferGroups) { for(sint32 i=0; i< bufferGroup.attribCount; i++) { uint32 attribSize = LatteShaderRecompiler_getAttributeSize(bufferGroup.attrib+i); uint32 attribAlignment = LatteShaderRecompiler_getAttributeAlignment(bufferGroup.attrib+i); // fix alignment vboOffset = (vboOffset+attribAlignment-1)&~(attribAlignment-1); vboOffset += attribSize; // index type if(bufferGroup.attrib[i].fetchType == LatteConst::VERTEX_DATA) bufferGroup.hasVtxIndexAccess = true; else if (bufferGroup.attrib[i].fetchType == LatteConst::INSTANCE_DATA) bufferGroup.hasInstanceIndexAccess = true; } // fix alignment of whole vertex if(bufferGroup.attribCount > 0 ) { uint32 attribAlignment = LatteShaderRecompiler_getAttributeAlignment(bufferGroup.attrib+0); vboOffset = (vboOffset+attribAlignment-1)&~(attribAlignment-1); } bufferGroup.vboStride = vboOffset; } LatteShader_calculateFSKey(newFetchShader); newFetchShader->CalculateFetchShaderVkHash(); #if ENABLE_METAL newFetchShader->CheckIfVerticesNeedManualFetchMtl(contextRegister); #endif // register in cache // its possible that during multi-threaded shader cache loading, two identical (same hash) fetch shaders get created simultaneously // we catch and handle this case here. RegisterInCache() is atomic and if another fetch shader is already registered, we abandon the local instance LatteFetchShader* registeredFS = newFetchShader->RegisterInCache(fsHash); if (registeredFS) { delete newFetchShader; newFetchShader = registeredFS; } else { newFetchShader->m_isRegistered = true; } return newFetchShader; } LatteFetchShader::~LatteFetchShader() { UnregisterInCache(); } struct FetchShaderLookupInfo { LatteFetchShader* fetchShader; uint32 programSize; uint32 lastFrameAccessed; }; LookupTableL3<8, 8, 8, FetchShaderLookupInfo*> g_fetchShaderLookupCache; LatteFetchShader::CacheHash LatteFetchShader::CalculateCacheHash(void* programCode, uint32 programSize) { uint32* programCodeU32 = (uint32*)programCode; uint64 progHash1 = 0; uint64 progHash2 = 0; for (uint32 i = 0; i < programSize / 4; i++) { uint32 temp = programCodeU32[i]; progHash1 += (uint64)temp; progHash2 ^= (uint64)temp; progHash1 = (progHash1 << 3) | (progHash1 >> 61); progHash2 = (progHash2 >> 7) | (progHash2 << 57); } // todo - we should incorporate the value of VGT_INSTANCE_STEP_RATE_0/1 into the hash since it affects the generated LatteFetchShader object // However, this would break compatibility with shader caches and gfx packs due to altering the shader base hashes return progHash1 + progHash2; } LatteFetchShader* LatteFetchShader::FindInCacheByHash(LatteFetchShader::CacheHash fsHash) { // does not hold s_fetchShaderCache for better performance. Be careful not to call this while another thread invokes RegisterInCache() auto itr = s_fetchShaderByHash.find(fsHash); if (itr == s_fetchShaderByHash.end()) return nullptr; return itr->second; } void* _getFSProgramPtr() { return memory_getPointerFromPhysicalOffset(LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 0] << 8); } uint32 _getFSProgramSize() { return LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 1] << 3; } LatteFetchShader* LatteFetchShader::FindByGPUState() { // retrieve fetch shader that matches the currently set GPU context registers uint32 fsPhysAddr24 = LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 0]; cemu_assert_debug(fsPhysAddr24 < 0x1000000); // should only contain the upper 24 bit of the address in the lower 24 bit of the register FetchShaderLookupInfo* lookupInfo = g_fetchShaderLookupCache.lookup(fsPhysAddr24); if (lookupInfo) { // return fetch shader if still the same uint32 fsSize = _getFSProgramSize(); uint32 framesSinceLastAccess = LatteGPUState.frameCounter - lookupInfo->lastFrameAccessed; if (lookupInfo->programSize == fsSize && framesSinceLastAccess == 0) { lookupInfo->lastFrameAccessed = LatteGPUState.frameCounter; return lookupInfo->fetchShader; } // update lookup info CacheHash fsHash = CalculateCacheHash(_getFSProgramPtr(), _getFSProgramSize()); LatteFetchShader* fetchShader = FindInCacheByHash(fsHash); if (!fetchShader) { fetchShader = LatteShaderRecompiler_createFetchShader(fsHash, LatteGPUState.contextNew.GetRawView(), (uint32*)_getFSProgramPtr(), _getFSProgramSize()); cemu_assert(fetchShader); } lookupInfo->fetchShader = fetchShader; lookupInfo->programSize = fsSize; lookupInfo->lastFrameAccessed = LatteGPUState.frameCounter; return fetchShader; } else { // try to find fetch shader by data hash CacheHash fsHash = CalculateCacheHash(_getFSProgramPtr(), _getFSProgramSize()); LatteFetchShader* fetchShader = FindInCacheByHash(fsHash); if (!fetchShader) { fetchShader = LatteShaderRecompiler_createFetchShader(fsHash, LatteGPUState.contextNew.GetRawView(), (uint32*)_getFSProgramPtr(), _getFSProgramSize()); cemu_assert(fetchShader); } // create new lookup entry lookupInfo = new FetchShaderLookupInfo(); lookupInfo->fetchShader = fetchShader; lookupInfo->programSize = _getFSProgramSize(); lookupInfo->lastFrameAccessed = LatteGPUState.frameCounter; g_fetchShaderLookupCache.store(fsPhysAddr24, lookupInfo); #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug(g_fetchShaderLookupCache.lookup(fsPhysAddr24) == lookupInfo); #endif } return lookupInfo->fetchShader; } FSpinlock s_spinlockFetchShaderCache; LatteFetchShader* LatteFetchShader::RegisterInCache(CacheHash fsHash) { s_spinlockFetchShaderCache.lock(); auto itr = s_fetchShaderByHash.find(fsHash); if (itr != s_fetchShaderByHash.end()) { LatteFetchShader* fs = itr->second; s_spinlockFetchShaderCache.unlock(); return fs; } s_fetchShaderByHash.emplace(fsHash, this); s_spinlockFetchShaderCache.unlock(); return nullptr; } void LatteFetchShader::UnregisterInCache() { if (!m_isRegistered) return; s_spinlockFetchShaderCache.lock(); auto itr = s_fetchShaderByHash.find(m_cacheHash); cemu_assert(itr == s_fetchShaderByHash.end()); s_fetchShaderByHash.erase(itr); s_spinlockFetchShaderCache.unlock(); } std::unordered_map<LatteFetchShader::CacheHash, LatteFetchShader*> LatteFetchShader::s_fetchShaderByHash; ================================================ FILE: src/Cafe/HW/Latte/Core/FetchShader.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteConst.h" struct LatteParsedFetchShaderAttribute_t { uint8 semanticId; uint8 format; LatteConst::VertexFetchType2 fetchType; uint8 nfa; uint8 isSigned; LatteConst::VertexFetchEndianMode endianSwap; uint8 ds[4]; // destination component select sint32 aluDivisor; uint32 offset; uint32 attributeBufferIndex; }; struct LatteParsedFetchShaderBufferGroup_t { uint32 attributeBufferIndex{}; // index of buffer (0 to 15 are valid) LatteParsedFetchShaderAttribute_t* attrib{}; // attributes for this buffer sint32 attribCount{}; // offset range of attributes uint32 minOffset{}; uint32 maxOffset{}; // output uint32 vboStride{}; // calculated info bool hasVtxIndexAccess{}; bool hasInstanceIndexAccess{}; uint32 getCurrentBufferStride(uint32* contextRegister) const; }; struct LatteFetchShader { using CacheHash = uint64; ~LatteFetchShader(); std::vector<LatteParsedFetchShaderBufferGroup_t> bufferGroups; std::vector<LatteParsedFetchShaderBufferGroup_t> bufferGroupsInvalid; // groups with buffer index not being a valid buffer (dst components of these can affect shader code, but no actual vertex imports are done) uint64 key{}; // Vulkan uint64 vkPipelineHashFragment{}; // hash of all fetch shader state that influences the Vulkan graphics pipeline // Metal bool mtlFetchVertexManually{}; // cache info CacheHash m_cacheHash{}; bool m_isRegistered{}; // if true, fetch shader is referenced by cache (RegisterInCache() succeeded) void CalculateFetchShaderVkHash(); #if ENABLE_METAL void CheckIfVerticesNeedManualFetchMtl(uint32* contextRegister); #endif uint64 getVkPipelineHashFragment() const { return vkPipelineHashFragment; }; static bool isValidBufferIndex(const uint32 index) { return index < 0x10; }; // cache LatteFetchShader* RegisterInCache(CacheHash fsHash); // Fails if another fetch shader object is already registered with the same fsHash. Returns the previously registered fetch shader or null void UnregisterInCache(); // fetch shader cache (move these to separate Cache class?) static CacheHash CalculateCacheHash(void* programCode, uint32 programSize); static LatteFetchShader* FindInCacheByHash(CacheHash fsHash); static LatteFetchShader* FindByGPUState(); static std::unordered_map<CacheHash, LatteFetchShader*> s_fetchShaderByHash; }; LatteFetchShader* LatteShaderRecompiler_createFetchShader(LatteFetchShader::CacheHash fsHash, uint32* contextRegister, uint32* fsProgramCode, uint32 fsProgramSize); ================================================ FILE: src/Cafe/HW/Latte/Core/Latte.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "util/VirtualHeap/VirtualHeap.h" struct LatteTextureDefinition; class LatteTexture; class LatteTextureView; struct gx2GPUSharedArea_t { volatile uint32 flipRequestCountBE; // counts how many buffer swaps were requested volatile uint32 flipExecuteCountBE; // counts how many buffer swaps were executed volatile uint32 swapInterval; // vsync swap interval (0 means vsync is deactivated) }; struct LatteGPUState_t { union { uint32 contextRegister[LATTE_MAX_REGISTER]; LatteContextRegister contextNew; }; MPTR contextRegisterShadowAddr[LATTE_MAX_REGISTER]; // context control uint32 contextControl0; uint32 contextControl1; // optional features bool allowFramebufferSizeOptimization{false}; // allow using scissor box as size hint to determine non-padded rendertarget size // stats uint32 frameCounter; uint32 flipCounter; // increased by one everytime a vsync + flip happens uint32 currentDrawCallTick; // set to current time at the beginning of a drawcall uint32 drawCallCounter; // increased after every drawcall uint32 textureBindCounter; // increased at the beginning of _updateTextures() std::atomic<uint64> flipRequestCount; // timer & vsync uint64 timer_frequency; // contains frequency of HPC uint64 timer_bootUp; // contains the timestamp of when the GPU thread timer was initialized uint64 timer_nextVSync; // shared gx2GPUSharedArea_t* sharedArea; // quick reference to shared area MPTR sharedAreaAddr; // other uint32 gx2InitCalled; // incremented every time GX2Init() is called // OpenGL control uint32 glVendor; // GLVENDOR_* bool isDRCPrimary = false; // temporary (replace with proper solution later) bool tvBufferUsesSRGB; bool drcBufferUsesSRGB; float tvGamma = 0.0f; float drcGamma = 0.0f; // draw state bool activeShaderHasError; // if try, at least one currently bound shader stage has an error and cannot be used for drawing bool repeatTextureInitialization; // if set during rendertarget or texture initialization, repeat the process (textures likely have been invalidated) bool requiresTextureBarrier; // set if glTextureBarrier should be called // OSScreen struct { struct { bool isEnabled; MPTR physPtr; std::atomic<uint32> flipRequestCount; std::atomic<uint32> flipExecuteCount; }screen[2]; }osScreen; }; extern LatteGPUState_t LatteGPUState; // texture #include "Cafe/HW/Latte/Core/LatteTexture.h" // texture loader void LatteTextureLoader_estimateAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, uint32& addrStart, uint32& addrEnd); // render target #define RENDER_TARGET_TV (1 << 0) #define RENDER_TARGET_DRC (1 << 2) void LatteRenderTarget_updateScissorBox(); void LatteRenderTarget_trackUpdates(); void LatteRenderTarget_getScreenImageArea(sint32* x, sint32* y, sint32* width, sint32* height, sint32* fullWidth, sint32* fullHeight, bool padView = false); void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPadView); void LatteRenderTarget_GetCurrentVirtualViewportSize(sint32* viewportWidth, sint32* viewportHeight); void LatteRenderTarget_itHLESwapScanBuffer(); void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, Latte::E_GX2SURFFMT colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, Latte::E_GX2SURFFMT depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil); void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget); void LatteRenderTarget_unloadAll(); // surface copy void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 srcSwizzle, Latte::E_GX2SURFFMT srcSurfaceFormat, sint32 srcWidth, sint32 srcHeight, sint32 srcDepth, uint32 srcPitch, sint32 srcSlice, Latte::E_DIM srcDim, Latte::E_HWTILEMODE srcTilemode, sint32 srcAA, sint32 srcLevel, MPTR dstPhysAddr, MPTR dstMipAddr, uint32 dstSwizzle, Latte::E_GX2SURFFMT dstSurfaceFormat, sint32 dstWidth, sint32 dstHeight, sint32 dstDepth, uint32 dstPitch, sint32 dstSlice, Latte::E_DIM dstDim, Latte::E_HWTILEMODE dstTilemode, sint32 dstAA, sint32 dstLevel); // texture cache void LatteTC_Init(); void LatteTC_RegisterTexture(LatteTexture* tex); void LatteTC_UnregisterTexture(LatteTexture* tex); uint32 LatteTexture_CalculateTextureDataHash(LatteTexture* hostTexture); void LatteTexture_ReloadData(LatteTexture* hostTexture); bool LatteTC_HasTextureChanged(LatteTexture* hostTexture, bool force = false); void LatteTC_ResetTextureChangeTracker(LatteTexture* hostTexture, bool force = false); void LatteTC_MarkTextureStillInUse(LatteTexture* texture); // lets the texture garbage collector know the texture is still in use at the time of this function call void LatteTC_CleanupUnusedTextures(); std::vector<LatteTexture*> LatteTC_GetDeleteableTextures(); void LatteTC_UnloadAllTextures(); // texture readback void LatteTextureReadback_Initate(LatteTextureView* textureView); void LatteTextureReadback_StartTransfer(LatteTextureView* textureView); bool LatteTextureReadback_Update(bool forceStart = false); void LatteTextureReadback_NotifyTextureDeletion(LatteTexture* texture); void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish); // query void LatteQuery_Init(); void LatteQuery_BeginOcclusionQuery(MPTR queryMPTR); void LatteQuery_EndOcclusionQuery(MPTR queryMPTR); void LatteQuery_UpdateFinishedQueries(); void LatteQuery_UpdateFinishedQueriesForceFinishAll(); void LatteQuery_CancelActiveGPU7Queries(); // streamout void LatteStreamout_InitCache(); sint32 LatteStreamout_GetRingBufferSize(); void LatteStreamout_PrepareDrawcall(uint32 count, uint32 instanceCount); void LatteStreamout_FinishDrawcall(bool useDirectMemoryMode); // timing void LatteTiming_Init(); void LatteTiming_HandleTimedVsync(); // command processor void LatteCP_ProcessRingbuffer(); // buffer cache bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount); void LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader, float* uniformData); void LatteRenderTarget_updateViewport(); #define LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE (4096) // maximum size for uniform blocks (in vec4s). On Nvidia hardware 4096 is the maximum (64K / 16 = 4096) all other vendors have much higher limits //static uint32 glTempError; //#define catchOpenGLError() glFinish(); if( (glTempError = glGetError()) != 0 ) { printf("OpenGL error 0x%x: %s : %d timestamp %08x\n", glTempError, __FILE__, __LINE__, GetTickCount()); __debugbreak(); } #define catchOpenGLError() // Latte emulation control void Latte_Start(); void Latte_Stop(); bool Latte_GetStopSignal(); // returns true if stop was requested or if in stopped state void LatteThread_Exit(); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp ================================================ #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" void LatteThread_Exit(); SlimRWLock swl_gpuAsyncCommands; typedef struct { uint32 type; union { struct { MPTR physAddr; MPTR mipAddr; uint32 swizzle; sint32 format; sint32 width; sint32 height; sint32 depth; uint32 pitch; uint32 slice; sint32 dim; Latte::E_HWTILEMODE tilemode; sint32 aa; sint32 level; }forceTextureReadback; struct { uint64 shaderBaseHash; uint64 shaderAuxHash; LatteConst::ShaderType shaderType; }deleteShader; }; }LatteAsyncCommand_t; #define ASYNC_CMD_FORCE_TEXTURE_READBACK 1 #define ASYNC_CMD_DELETE_SHADER 2 std::queue<LatteAsyncCommand_t> LatteAsyncCommandQueue; void LatteAsyncCommands_queueForceTextureReadback(MPTR physAddr, MPTR mipAddr, uint32 swizzle, sint32 format, sint32 width, sint32 height, sint32 depth, uint32 pitch, uint32 slice, sint32 dim, Latte::E_HWTILEMODE tilemode, sint32 aa, sint32 level) { LatteAsyncCommand_t asyncCommand = {}; // setup command asyncCommand.type = ASYNC_CMD_FORCE_TEXTURE_READBACK; asyncCommand.forceTextureReadback.physAddr = physAddr; asyncCommand.forceTextureReadback.mipAddr = mipAddr; asyncCommand.forceTextureReadback.swizzle = swizzle; asyncCommand.forceTextureReadback.format = format; asyncCommand.forceTextureReadback.width = width; asyncCommand.forceTextureReadback.height = height; asyncCommand.forceTextureReadback.depth = depth; asyncCommand.forceTextureReadback.pitch = pitch; asyncCommand.forceTextureReadback.slice = slice; asyncCommand.forceTextureReadback.dim = dim; asyncCommand.forceTextureReadback.tilemode = tilemode; asyncCommand.forceTextureReadback.aa = aa; asyncCommand.forceTextureReadback.level = level; swl_gpuAsyncCommands.LockWrite(); LatteAsyncCommandQueue.push(asyncCommand); swl_gpuAsyncCommands.UnlockWrite(); } void LatteAsyncCommands_queueDeleteShader(uint64 shaderBaseHash, uint64 shaderAuxHash, LatteConst::ShaderType shaderType) { LatteAsyncCommand_t asyncCommand = {}; // setup command asyncCommand.type = ASYNC_CMD_DELETE_SHADER; asyncCommand.deleteShader.shaderBaseHash = shaderBaseHash; asyncCommand.deleteShader.shaderAuxHash = shaderAuxHash; asyncCommand.deleteShader.shaderType = shaderType; swl_gpuAsyncCommands.LockWrite(); LatteAsyncCommandQueue.push(asyncCommand); swl_gpuAsyncCommands.UnlockWrite(); } void LatteAsyncCommands_waitUntilAllProcessed() { while (LatteAsyncCommandQueue.empty() == false) { _mm_pause(); } } /* * Called by the GPU command processor frequently */ void LatteAsyncCommands_checkAndExecute() { // quick check if queue is empty (requires no lock) if (Latte_GetStopSignal()) LatteThread_Exit(); if (LatteAsyncCommandQueue.empty()) return; swl_gpuAsyncCommands.LockWrite(); while (LatteAsyncCommandQueue.empty() == false) { // get first command in queue LatteAsyncCommand_t asyncCommand = LatteAsyncCommandQueue.front(); swl_gpuAsyncCommands.UnlockWrite(); if (asyncCommand.type == ASYNC_CMD_FORCE_TEXTURE_READBACK) { cemu_assert_debug(asyncCommand.forceTextureReadback.level == 0); // implement mip swizzle and verify LatteTextureView* textureView = LatteTC_GetTextureSliceViewOrTryCreate(asyncCommand.forceTextureReadback.physAddr, asyncCommand.forceTextureReadback.mipAddr, (Latte::E_GX2SURFFMT)asyncCommand.forceTextureReadback.format, asyncCommand.forceTextureReadback.tilemode, asyncCommand.forceTextureReadback.width, asyncCommand.forceTextureReadback.height, asyncCommand.forceTextureReadback.depth, asyncCommand.forceTextureReadback.pitch, 0, asyncCommand.forceTextureReadback.slice, asyncCommand.forceTextureReadback.level); if (textureView != nullptr) { LatteTexture_UpdateDataToLatest(textureView->baseTexture); // start transfer LatteTextureReadback_StartTransfer(textureView); // wait until finished LatteTextureReadback_UpdateFinishedTransfers(true); } else { cemuLog_logDebug(LogType::Force, "Texture not found for readback"); } } else if (asyncCommand.type == ASYNC_CMD_DELETE_SHADER) { LatteSHRC_RemoveFromCacheByHash(asyncCommand.deleteShader.shaderBaseHash, asyncCommand.deleteShader.shaderAuxHash, asyncCommand.deleteShader.shaderType); } else { cemu_assert_unimplemented(); } swl_gpuAsyncCommands.LockWrite(); LatteAsyncCommandQueue.pop(); } swl_gpuAsyncCommands.UnlockWrite(); } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteAsyncCommands.h ================================================ #pragma once void LatteAsyncCommands_queueForceTextureReadback(MPTR physAddr, MPTR mipAddr, uint32 swizzle, sint32 format, sint32 width, sint32 height, sint32 depth, uint32 pitch, uint32 slice, sint32 dim, Latte::E_HWTILEMODE tilemode, sint32 aa, sint32 level); void LatteAsyncCommands_queueDeleteShader(uint64 shaderBaseHash, uint64 shaderAuxHash, LatteConst::ShaderType shaderType); void LatteAsyncCommands_waitUntilAllProcessed(); void LatteAsyncCommands_checkAndExecute(); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteBufferCache.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "util/ChunkedHeap/ChunkedHeap.h" #include "util/helpers/fspinlock.h" #include "config/ActiveSettings.h" #define CACHE_PAGE_SIZE 0x400 #define CACHE_PAGE_SIZE_M1 (CACHE_PAGE_SIZE-1) uint32 g_currentCacheChronon = 0; template<typename TRangeData, typename TNodeObject> class IntervalTree2 { // TNodeObject will be interfaced with via callbacks to static methods // static TNodeObject* Create(TRangeData rangeBegin, TRangeData rangeEnd, std::span<TNodeObject*> overlappingObjects) // Create a new node with the given range. overlappingObjects contains all the nodes that are replaced by this operation. The callee has to delete all objects in overlappingObjects (Delete callback wont be invoked) // static void Delete(TNodeObject* nodeObject) // Delete a node object. Replacement operations won't trigger this callback and instead pass the objects to Create() // static void Resize(TNodeObject* nodeObject, TRangeData rangeBegin, TRangeData rangeEnd) // Shrink or extend an existing range // static TNodeObject* Split(TNodeObject* nodeObject, TRangeData firstRangeBegin, TRangeData firstRangeEnd, TRangeData secondRangeBegin, TRangeData secondRangeEnd) // Cut a hole into an existing range and split it in two. Should return the newly created node object after the hole static_assert(!std::is_pointer_v<TNodeObject>, "TNodeObject must be a non-pointer type"); struct InternalRange { InternalRange() = default; InternalRange(TRangeData _rangeBegin, TRangeData _rangeEnd) : rangeBegin(_rangeBegin), rangeEnd(_rangeEnd) { cemu_assert_debug(_rangeBegin < _rangeEnd); }; TRangeData rangeBegin; TRangeData rangeEnd; bool operator<(const InternalRange& rhs) const { // use <= instead of < because ranges are allowed to touch (e.g. 10-20 and 20-30 dont get merged) return this->rangeEnd <= rhs.rangeBegin; } }; std::map<InternalRange, TNodeObject*> m_map; std::vector<TNodeObject*> m_tempObjectArray; public: TNodeObject* getRange(TRangeData rangeBegin, TRangeData rangeEnd) { auto itr = m_map.find(InternalRange(rangeBegin, rangeEnd)); if (itr == m_map.cend()) return nullptr; if (rangeBegin < (*itr).first.rangeBegin) return nullptr; if (rangeEnd > (*itr).first.rangeEnd) return nullptr; return (*itr).second; } TNodeObject* getRangeByPoint(TRangeData rangeOffset) { auto itr = m_map.find(InternalRange(rangeOffset, rangeOffset+1)); // todo - better to use custom comparator instead of +1? if (itr == m_map.cend()) return nullptr; cemu_assert_debug(rangeOffset >= (*itr).first.rangeBegin); cemu_assert_debug(rangeOffset < (*itr).first.rangeEnd); return (*itr).second; } void addRange(TRangeData rangeBegin, TRangeData rangeEnd) { if (rangeEnd == rangeBegin) return; InternalRange range(rangeBegin, rangeEnd); auto itr = m_map.find(range); if (itr == m_map.cend()) { // new entry m_map.emplace(range, TNodeObject::Create(rangeBegin, rangeEnd, std::span<TNodeObject*>())); } else { // overlap detected if (rangeBegin >= (*itr).first.rangeBegin && rangeEnd <= (*itr).first.rangeEnd) return; // do nothing if added range is already covered rangeBegin = (std::min)(rangeBegin, (*itr).first.rangeBegin); // DEBUG - make sure this is the start point of the merge process (the first entry that starts below minValue) #ifdef CEMU_DEBUG_ASSERT if (itr != m_map.cbegin()) { // check previous result auto itrCopy = itr; --itrCopy; if ((*itrCopy).first.rangeEnd > rangeBegin) { assert_dbg(); // n-1 entry is also overlapping rangeBegin = (std::min)(rangeBegin, (*itrCopy).first.rangeBegin); } } #endif // DEBUG - END // collect and remove all overlapping ranges size_t count = 0; while (itr != m_map.cend() && (*itr).first.rangeBegin < rangeEnd) { rangeEnd = (std::max)(rangeEnd, (*itr).first.rangeEnd); if (m_tempObjectArray.size() <= count) m_tempObjectArray.resize(count + 8); m_tempObjectArray[count] = (*itr).second; count++; auto tempItr = itr; ++itr; m_map.erase(tempItr); } // create callback TNodeObject* newObject = TNodeObject::Create(rangeBegin, rangeEnd, std::span<TNodeObject*>(m_tempObjectArray.data(), count)); m_map.emplace(InternalRange(rangeBegin, rangeEnd), newObject); } } void removeRange(TRangeData rangeBegin, TRangeData rangeEnd) { InternalRange range(rangeBegin, rangeEnd); auto itr = m_map.find(range); if (itr == m_map.cend()) return; cemu_assert_debug(itr == m_map.lower_bound(range)); while (itr != m_map.cend() && (*itr).first.rangeBegin < rangeEnd) { if ((*itr).first.rangeBegin >= rangeBegin && (*itr).first.rangeEnd <= rangeEnd) { // delete entire range auto itrCopy = itr; TNodeObject* t = (*itr).second; ++itr; m_map.erase(itrCopy); TNodeObject::Delete(t); continue; } if (rangeBegin > (*itr).first.rangeBegin && rangeEnd < (*itr).first.rangeEnd) { // cut hole into existing range TRangeData firstRangeBegin = (*itr).first.rangeBegin; TRangeData firstRangeEnd = rangeBegin; TRangeData secondRangeBegin = rangeEnd; TRangeData secondRangeEnd = (*itr).first.rangeEnd; TNodeObject* newObject = TNodeObject::Split((*itr).second, firstRangeBegin, firstRangeEnd, secondRangeBegin, secondRangeEnd); // modify key auto nh = m_map.extract(itr); nh.key().rangeBegin = firstRangeBegin; nh.key().rangeEnd = firstRangeEnd; m_map.insert(std::move(nh)); // insert new object after hole m_map.emplace(InternalRange(secondRangeBegin, secondRangeEnd), newObject); return; // done } // shrink (trim either beginning or end) TRangeData newRangeBegin; TRangeData newRangeEnd; if ((rangeBegin <= (*itr).first.rangeBegin && rangeEnd < (*itr).first.rangeEnd)) { // trim from beginning newRangeBegin = (std::max)((*itr).first.rangeBegin, rangeEnd); newRangeEnd = (*itr).first.rangeEnd; } else if ((rangeBegin > (*itr).first.rangeBegin && rangeEnd >= (*itr).first.rangeEnd)) { // trim from end newRangeBegin = (*itr).first.rangeBegin; newRangeEnd = (std::min)((*itr).first.rangeEnd, rangeBegin); } else { assert_dbg(); // should not happen } TNodeObject::Resize((*itr).second, newRangeBegin, newRangeEnd); // modify key and increment iterator auto itrCopy = itr; ++itr; auto nh = m_map.extract(itrCopy); nh.key().rangeBegin = newRangeBegin; nh.key().rangeEnd = newRangeEnd; m_map.insert(std::move(nh)); } } // remove existing range that matches given begin and end void removeRangeSingle(TRangeData rangeBegin, TRangeData rangeEnd) { InternalRange range(rangeBegin, rangeEnd); auto itr = m_map.find(range); cemu_assert_debug(itr != m_map.cend()); if (itr == m_map.cend()) return; cemu_assert_debug((*itr).first.rangeBegin == rangeBegin && (*itr).first.rangeEnd == rangeEnd); // delete entire range TNodeObject* t = (*itr).second; m_map.erase(itr); TNodeObject::Delete(t); } // remove existing range that matches given begin and end without calling delete callback void removeRangeSingleWithoutCallback(TRangeData rangeBegin, TRangeData rangeEnd) { InternalRange range(rangeBegin, rangeEnd); auto itr = m_map.find(range); cemu_assert_debug(itr != m_map.cend()); if (itr == m_map.cend()) return; cemu_assert_debug((*itr).first.rangeBegin == rangeBegin && (*itr).first.rangeEnd == rangeEnd); // delete entire range TNodeObject* t = (*itr).second; m_map.erase(itr); } void splitRange(TRangeData rangeOffset) { // not well tested removeRange(rangeOffset, rangeOffset+1); } template<typename TFunc> void forEachOverlapping(TRangeData rangeBegin, TRangeData rangeEnd, TFunc f) { InternalRange range(rangeBegin, rangeEnd); auto itr = m_map.find(range); if (itr == m_map.cend()) return; cemu_assert_debug(itr == m_map.lower_bound(range)); while (itr != m_map.cend() && (*itr).first.rangeBegin < rangeEnd) { f((*itr).second, rangeBegin, rangeEnd); ++itr; } } void validate() { if (m_map.empty()) return; auto itr = m_map.begin(); if ((*itr).first.rangeBegin > (*itr).first.rangeEnd) assert_dbg(); TRangeData currentLoc = (*itr).first.rangeEnd; ++itr; while (itr != m_map.end()) { if ((*itr).first.rangeBegin >= (*itr).first.rangeEnd) assert_dbg(); // negative or zero size ranges are not allowed if (currentLoc > (*itr).first.rangeBegin) assert_dbg(); // stored ranges must not overlap currentLoc = (*itr).first.rangeEnd; ++itr; } } bool empty() const { return m_map.empty(); } const std::map<InternalRange, TNodeObject*>& getAll() const { return m_map; }; }; std::unique_ptr<VHeap> g_gpuBufferHeap = nullptr; std::vector<uint8> s_pageUploadBuffer; std::vector<class BufferCacheNode*> s_allCacheNodes; void LatteBufferCache_removeSingleNodeFromTree(BufferCacheNode* node); class BufferCacheNode { static inline constexpr uint64 c_streamoutSig0 = 0xF0F0F0F0155C5B6Aull; static inline constexpr uint64 c_streamoutSig1 = 0x8BE6336411814F4Full; public: // returns false if not enough space is available bool allocateCacheMemory() { cemu_assert_debug(m_hasCacheAlloc == false); cemu_assert_debug(m_rangeEnd > m_rangeBegin); m_hasCacheAlloc = g_gpuBufferHeap->allocOffset(m_rangeEnd - m_rangeBegin, CACHE_PAGE_SIZE, m_cacheOffset); return m_hasCacheAlloc; } void ReleaseCacheMemoryImmediately() { if (m_hasCacheAlloc) { g_gpuBufferHeap->freeOffset(m_cacheOffset); m_hasCacheAlloc = false; } } uint32 getBufferOffset(MPTR physAddr) const { cemu_assert_debug(m_hasCacheAlloc); cemu_assert_debug(physAddr >= m_rangeBegin); cemu_assert_debug(physAddr < m_rangeEnd); uint32 relOffset = physAddr - m_rangeBegin; return m_cacheOffset + relOffset; } void writeStreamout(MPTR rangeBegin, MPTR rangeEnd) { if ((rangeBegin & 0xF)) { cemuLog_logDebugOnce(LogType::Force, "writeStreamout(): RangeBegin not aligned to 16. Begin {:08x} End {:08x}", rangeBegin, rangeEnd); rangeBegin = (rangeBegin + 0xF) & ~0xF; rangeEnd = std::max(rangeBegin, rangeEnd); } if (rangeEnd & 0xF) { // todo - add support for 4 byte granularity for streamout writes and cache // used by Affordable Space Adventures and YWW Level 1-8 // also used by CoD Ghosts (8 byte granularity) //cemuLog_logDebug(LogType::Force, "Streamout write size is not aligned to 16 bytes"); rangeEnd &= ~0xF; } //cemu_assert_debug((rangeEnd & 0xF) == 0); rangeBegin = std::max(rangeBegin, m_rangeBegin); rangeEnd = std::min(rangeEnd, m_rangeEnd); if (rangeBegin >= rangeEnd) return; sint32 numPages = getPageCountFromRange(rangeBegin, rangeEnd); sint32 pageIndex = getPageIndexFromAddr(rangeBegin); cemu_assert_debug((m_rangeBegin + pageIndex * CACHE_PAGE_SIZE) <= rangeBegin); cemu_assert_debug((m_rangeBegin + (pageIndex + numPages) * CACHE_PAGE_SIZE) >= rangeEnd); for (sint32 i = 0; i < numPages; i++) { pageWriteStreamoutSignatures(pageIndex, rangeBegin, rangeEnd); pageIndex++; //pageInfo->hasStreamoutData = true; //pageInfo++; } if (numPages > 0) m_hasStreamoutData = true; } void checkAndSyncModifications(MPTR rangeBegin, MPTR rangeEnd, bool uploadData) { cemu_assert_debug(rangeBegin >= m_rangeBegin); cemu_assert_debug(rangeEnd <= m_rangeEnd); cemu_assert_debug(rangeBegin < m_rangeEnd); cemu_assert_debug((rangeBegin % CACHE_PAGE_SIZE) == 0); cemu_assert_debug((rangeEnd % CACHE_PAGE_SIZE) == 0); sint32 basePageIndex = getPageIndexFromAddrAligned(rangeBegin); sint32 numPages = getPageCountFromRangeAligned(rangeBegin, rangeEnd); uint8* pagePtr = memory_getPointerFromPhysicalOffset(rangeBegin); sint32 uploadPageBegin = -1; CachePageInfo* pageInfo = m_pageInfo.data() + basePageIndex; for (sint32 i = 0; i < numPages; i++) { if (pageInfo->hasStreamoutData) { // first upload any pending sequence of pages if (uploadPageBegin != -1) { // upload range if (uploadData) uploadPages(uploadPageBegin, basePageIndex + i); uploadPageBegin = -1; } // check if hash changed uint64 pageHash = hashPage(pagePtr); if (pageInfo->hash != pageHash) { pageInfo->hash = pageHash; // for pages that contain streamout data we do uploads with a much smaller granularity // and skip uploading any data that is marked with streamout filler bytes if (!uploadPageWithStreamoutFiltered(basePageIndex + i)) pageInfo->hasStreamoutData = false; // all streamout data was replaced } pagePtr += CACHE_PAGE_SIZE; pageInfo++; continue; } uint64 pageHash = hashPage(pagePtr); pagePtr += CACHE_PAGE_SIZE; if (pageInfo->hash != pageHash) { if (uploadPageBegin == -1) uploadPageBegin = i + basePageIndex; pageInfo->hash = pageHash; } else { if (uploadPageBegin != -1) { // upload range if (uploadData) uploadPages(uploadPageBegin, basePageIndex + i); uploadPageBegin = -1; } } pageInfo++; } if (uploadPageBegin != -1) { if (uploadData) uploadPages(uploadPageBegin, basePageIndex + numPages); } } void checkAndSyncModifications(bool uploadData) { checkAndSyncModifications(m_rangeBegin, m_rangeEnd, uploadData); m_lastModifyCheckCronon = g_currentCacheChronon; m_hasInvalidation = false; } void checkAndSyncModificationsIfChrononChanged(MPTR reservePhysAddress, uint32 reserveSize) { if (m_lastModifyCheckCronon != g_currentCacheChronon) { m_lastModifyCheckCronon = g_currentCacheChronon; checkAndSyncModifications(m_rangeBegin, m_rangeEnd, true); m_hasInvalidation = false; } if (m_hasInvalidation) { // ideally we would only upload the pages that intersect both the reserve range and the invalidation range // but this would require complex per-page tracking of invalidation. Since this is on a hot path we do a cheap approximation // where we only track one continous invalidation range // try to bound uploads to the reserve range within the invalidation uint32 resRangeBegin = reservePhysAddress & ~CACHE_PAGE_SIZE_M1; uint32 resRangeEnd = ((reservePhysAddress + reserveSize) + CACHE_PAGE_SIZE_M1) & ~CACHE_PAGE_SIZE_M1; uint32 uploadBegin = std::max(m_invalidationRangeBegin, resRangeBegin); uint32 uploadEnd = std::min(resRangeEnd, m_invalidationRangeEnd); if (uploadBegin >= uploadEnd) return; // reserve range not within invalidation or range is zero sized if (uploadBegin == m_invalidationRangeBegin) { m_invalidationRangeBegin = uploadEnd; checkAndSyncModifications(uploadBegin, uploadEnd, true); } if (uploadEnd == m_invalidationRangeEnd) { m_invalidationRangeEnd = uploadBegin; checkAndSyncModifications(uploadBegin, uploadEnd, true); } else { // upload all of invalidation checkAndSyncModifications(m_invalidationRangeBegin, m_invalidationRangeEnd, true); m_invalidationRangeBegin = m_invalidationRangeEnd; } if(m_invalidationRangeEnd <= m_invalidationRangeBegin) m_hasInvalidation = false; } } void invalidate(MPTR rangeBegin, MPTR rangeEnd) { rangeBegin = std::max(rangeBegin, m_rangeBegin); rangeEnd = std::min(rangeEnd, m_rangeEnd); if (rangeBegin >= rangeEnd) return; if (m_hasInvalidation) { m_invalidationRangeBegin = std::min(m_invalidationRangeBegin, rangeBegin); m_invalidationRangeEnd = std::max(m_invalidationRangeEnd, rangeEnd); } else { m_invalidationRangeBegin = rangeBegin; m_invalidationRangeEnd = rangeEnd; m_hasInvalidation = true; } cemu_assert_debug(m_invalidationRangeBegin >= m_rangeBegin); cemu_assert_debug(m_invalidationRangeEnd <= m_rangeEnd); cemu_assert_debug(m_invalidationRangeBegin < m_invalidationRangeEnd); m_invalidationRangeBegin = m_invalidationRangeBegin & ~CACHE_PAGE_SIZE_M1; m_invalidationRangeEnd = (m_invalidationRangeEnd + CACHE_PAGE_SIZE_M1) & ~CACHE_PAGE_SIZE_M1; } void flagInUse() { m_lastDrawcall = LatteGPUState.drawCallCounter; m_lastFrame = LatteGPUState.frameCounter; } bool isInUse() const { return m_lastDrawcall == LatteGPUState.drawCallCounter; } // returns true if the range does not contain any GPU-cache-only data and can be fully restored from RAM bool isRAMOnly() const { return !m_hasStreamoutData; } MPTR GetRangeBegin() const { return m_rangeBegin; } MPTR GetRangeEnd() const { return m_rangeEnd; } uint32 GetDrawcallAge() const { return LatteGPUState.drawCallCounter - m_lastDrawcall; }; uint32 GetFrameAge() const { return LatteGPUState.frameCounter - m_lastFrame; }; bool HasStreamoutData() const { return m_hasStreamoutData; }; private: struct CachePageInfo { uint64 hash{ 0 }; bool hasStreamoutData{ false }; }; MPTR m_rangeBegin; MPTR m_rangeEnd; // (exclusive) bool m_hasCacheAlloc{ false }; uint32 m_cacheOffset{ 0 }; // usage uint32 m_lastDrawcall; uint32 m_lastFrame; uint32 m_arrayIndex; // state tracking uint32 m_lastModifyCheckCronon{ g_currentCacheChronon - 1 }; std::vector<CachePageInfo> m_pageInfo; bool m_hasStreamoutData{ false }; // invalidation bool m_hasInvalidation{false}; MPTR m_invalidationRangeBegin; MPTR m_invalidationRangeEnd; BufferCacheNode(MPTR rangeBegin, MPTR rangeEnd): m_rangeBegin(rangeBegin), m_rangeEnd(rangeEnd) { flagInUse(); cemu_assert_debug(rangeBegin < rangeEnd); size_t numPages = getPageCountFromRangeAligned(rangeBegin, rangeEnd); m_pageInfo.resize(numPages); // append to array m_arrayIndex = (uint32)s_allCacheNodes.size(); s_allCacheNodes.emplace_back(this); }; ~BufferCacheNode() { if (m_hasCacheAlloc) g_deallocateQueue.emplace_back(m_cacheOffset); // release after current drawcall // remove from array auto temp = s_allCacheNodes.back(); s_allCacheNodes.pop_back(); if (this != temp) { s_allCacheNodes[m_arrayIndex] = temp; temp->m_arrayIndex = m_arrayIndex; } } uint32 getPageIndexFromAddrAligned(uint32 offset) const { cemu_assert_debug((offset % CACHE_PAGE_SIZE) == 0); return (offset - m_rangeBegin) / CACHE_PAGE_SIZE; } uint32 getPageIndexFromAddr(uint32 offset) const { offset &= ~CACHE_PAGE_SIZE_M1; return (offset - m_rangeBegin) / CACHE_PAGE_SIZE; } uint32 getPageCountFromRangeAligned(MPTR rangeBegin, MPTR rangeEnd) const { cemu_assert_debug((rangeBegin % CACHE_PAGE_SIZE) == 0); cemu_assert_debug((rangeEnd % CACHE_PAGE_SIZE) == 0); cemu_assert_debug(rangeBegin <= rangeEnd); return (rangeEnd - rangeBegin) / CACHE_PAGE_SIZE; } uint32 getPageCountFromRange(MPTR rangeBegin, MPTR rangeEnd) const { rangeEnd = (rangeEnd + CACHE_PAGE_SIZE_M1) & ~CACHE_PAGE_SIZE_M1; rangeBegin &= ~CACHE_PAGE_SIZE_M1; cemu_assert_debug(rangeBegin <= rangeEnd); return (rangeEnd - rangeBegin) / CACHE_PAGE_SIZE; } void syncFromRAM(MPTR rangeBegin, MPTR rangeEnd) { cemu_assert_debug(rangeBegin >= m_rangeBegin); cemu_assert_debug(rangeEnd <= m_rangeEnd); cemu_assert_debug(rangeEnd > rangeBegin); cemu_assert_debug(m_hasCacheAlloc); // reset write tracking checkAndSyncModifications(rangeBegin, rangeEnd, false); g_renderer->bufferCache_upload(memory_getPointerFromPhysicalOffset(rangeBegin), rangeEnd - rangeBegin, getBufferOffset(rangeBegin)); } void syncFromNode(BufferCacheNode* srcNode) { // get shared range MPTR rangeBegin = std::max(m_rangeBegin, srcNode->m_rangeBegin); MPTR rangeEnd = std::min(m_rangeEnd, srcNode->m_rangeEnd); cemu_assert_debug(rangeBegin < rangeEnd); g_renderer->bufferCache_copy(srcNode->getBufferOffset(rangeBegin), this->getBufferOffset(rangeBegin), rangeEnd - rangeBegin); // copy page checksums and information sint32 numPages = getPageCountFromRangeAligned(rangeBegin, rangeEnd); CachePageInfo* pageInfoDst = this->m_pageInfo.data() + this->getPageIndexFromAddrAligned(rangeBegin); CachePageInfo* pageInfoSrc = srcNode->m_pageInfo.data() + srcNode->getPageIndexFromAddrAligned(rangeBegin); for (sint32 i = 0; i < numPages; i++) { pageInfoDst[i] = pageInfoSrc[i]; if (pageInfoSrc[i].hasStreamoutData) m_hasStreamoutData = true; } } void uploadPages(uint32 firstPage, uint32 lastPagePlusOne) { cemu_assert_debug(lastPagePlusOne > firstPage); uint32 uploadRangeBegin = m_rangeBegin + firstPage * CACHE_PAGE_SIZE; uint32 uploadRangeEnd = m_rangeBegin + lastPagePlusOne * CACHE_PAGE_SIZE; cemu_assert_debug(uploadRangeEnd > uploadRangeBegin); // make sure uploaded pages and hashes match uint32 numPages = lastPagePlusOne - firstPage; if (s_pageUploadBuffer.size() < (numPages * CACHE_PAGE_SIZE)) s_pageUploadBuffer.resize(numPages * CACHE_PAGE_SIZE); // todo - improve performance by merging memcpy + hashPage() ? memcpy(s_pageUploadBuffer.data(), memory_getPointerFromPhysicalOffset(uploadRangeBegin), numPages * CACHE_PAGE_SIZE); for (uint32 i = 0; i < numPages; i++) { m_pageInfo[firstPage + i].hash = hashPage(s_pageUploadBuffer.data() + i * CACHE_PAGE_SIZE); } g_renderer->bufferCache_upload(s_pageUploadBuffer.data(), uploadRangeEnd - uploadRangeBegin, getBufferOffset(uploadRangeBegin)); } // upload only non-streamout data of a single page // returns true if at least one streamout 16-byte block is present // also updates the page hash to match the uploaded data (strict match) sint32 uploadPageWithStreamoutFiltered(uint32 pageIndex) { uint8 pageCopy[CACHE_PAGE_SIZE]; memcpy(pageCopy, memory_getPointerFromPhysicalOffset(m_rangeBegin + pageIndex * CACHE_PAGE_SIZE), CACHE_PAGE_SIZE); MPTR pageBase = m_rangeBegin + pageIndex * CACHE_PAGE_SIZE; sint32 blockBegin = -1; uint64* pagePtrU64 = (uint64*)pageCopy; m_pageInfo[pageIndex].hash = hashPage(pageCopy); bool hasStreamoutBlocks = false; for (sint32 i = 0; i < CACHE_PAGE_SIZE / 16; i++) { if (pagePtrU64[0] == c_streamoutSig0 && pagePtrU64[1] == c_streamoutSig1) { hasStreamoutBlocks = true; if (blockBegin != -1) { uint32 uploadRelRangeBegin = blockBegin * 16; uint32 uploadRelRangeEnd = i * 16; cemu_assert_debug(uploadRelRangeEnd > uploadRelRangeBegin); g_renderer->bufferCache_upload(pageCopy + uploadRelRangeBegin, uploadRelRangeEnd - uploadRelRangeBegin, getBufferOffset(pageBase + uploadRelRangeBegin)); blockBegin = -1; } pagePtrU64 += 2; continue; } else if (blockBegin == -1) blockBegin = i; pagePtrU64 += 2; } if (blockBegin != -1) { uint32 uploadRelRangeBegin = blockBegin * 16; uint32 uploadRelRangeEnd = CACHE_PAGE_SIZE; cemu_assert_debug(uploadRelRangeEnd > uploadRelRangeBegin); g_renderer->bufferCache_upload(pageCopy + uploadRelRangeBegin, uploadRelRangeEnd - uploadRelRangeBegin, getBufferOffset(pageBase + uploadRelRangeBegin)); blockBegin = -1; } return hasStreamoutBlocks; } void shrink(MPTR newRangeBegin, MPTR newRangeEnd) { cemu_assert_debug(newRangeBegin >= m_rangeBegin); cemu_assert_debug(newRangeEnd >= m_rangeEnd); cemu_assert_debug(newRangeEnd > m_rangeBegin); assert_dbg(); // todo (resize page array) m_rangeBegin = newRangeBegin; m_rangeEnd = newRangeEnd; } static uint64 hashPage(uint8* mem) { static const uint64 k0 = 0x55F23EAD; static const uint64 k1 = 0x185FDC6D; static const uint64 k2 = 0xF7431F49; static const uint64 k3 = 0xA4C7AE9D; cemu_assert_debug((CACHE_PAGE_SIZE % 32) == 0); const uint64* ptr = (const uint64*)mem; const uint64* end = ptr + (CACHE_PAGE_SIZE / sizeof(uint64)); uint64 h0 = 0; uint64 h1 = 0; uint64 h2 = 0; uint64 h3 = 0; while (ptr < end) { h0 = std::rotr(h0, 7); h1 = std::rotr(h1, 7); h2 = std::rotr(h2, 7); h3 = std::rotr(h3, 7); h0 += ptr[0] * k0; h1 += ptr[1] * k1; h2 += ptr[2] * k2; h3 += ptr[3] * k3; ptr += 4; } return h0 + h1 + h2 + h3; } // flag page as having streamout data, also write streamout signatures to page memory // also incrementally updates the page hash to include the written signatures, this prevents signature writes from triggering a data upload void pageWriteStreamoutSignatures(uint32 pageIndex, MPTR rangeBegin, MPTR rangeEnd) { uint32 pageRangeBegin = m_rangeBegin + pageIndex * CACHE_PAGE_SIZE; uint32 pageRangeEnd = pageRangeBegin + CACHE_PAGE_SIZE; rangeBegin = std::max(pageRangeBegin, rangeBegin); rangeEnd = std::min(pageRangeEnd, rangeEnd); cemu_assert_debug(rangeEnd > rangeBegin); cemu_assert_debug(rangeBegin >= pageRangeBegin); cemu_assert_debug(rangeEnd <= pageRangeEnd); cemu_assert_debug((rangeBegin & 0xF) == 0); cemu_assert_debug((rangeEnd & 0xF) == 0); auto pageInfo = m_pageInfo.data() + pageIndex; pageInfo->hasStreamoutData = true; // if the whole page is replaced we can use a cached hash if (pageRangeBegin == rangeBegin && pageRangeEnd == rangeEnd) { uint64* pageMem = (uint64*)memory_getPointerFromPhysicalOffset(rangeBegin); uint32 numBlocks = (rangeEnd - rangeBegin) / 16; for (uint32 i = 0; i < numBlocks; i++) { pageMem[0] = c_streamoutSig0; pageMem[1] = c_streamoutSig1; pageMem += 2; } pageInfo->hash = c_fullStreamoutPageHash; return; } uint64* pageMem = (uint64*)memory_getPointerFromPhysicalOffset(rangeBegin); uint32 numBlocks = (rangeEnd - rangeBegin) / 16; uint32 indexHashBlock = (rangeBegin - pageRangeBegin) / sizeof(uint64); for (uint32 i = 0; i < numBlocks; i++) { pageMem[0] = c_streamoutSig0; pageMem[1] = c_streamoutSig1; pageMem += 2; } pageInfo->hash = 0; // reset hash } static uint64 genStreamoutPageHash() { uint8 pageMem[CACHE_PAGE_SIZE]; uint64* pageMemU64 = (uint64*)pageMem; for (uint32 i = 0; i < sizeof(pageMem) / sizeof(uint64) / 2; i++) { pageMemU64[0] = c_streamoutSig0; pageMemU64[1] = c_streamoutSig1; pageMemU64 += 2; } return hashPage(pageMem); } static inline uint64 c_fullStreamoutPageHash = genStreamoutPageHash(); static std::vector<uint32> g_deallocateQueue; public: static void UnloadAll() { size_t i = 0; while (i < s_allCacheNodes.size()) { BufferCacheNode* node = s_allCacheNodes[i]; node->ReleaseCacheMemoryImmediately(); LatteBufferCache_removeSingleNodeFromTree(node); delete node; } for(auto& it : s_allCacheNodes) delete it; s_allCacheNodes.clear(); g_deallocateQueue.clear(); } static void ProcessDeallocations() { for(auto& itr : g_deallocateQueue) g_gpuBufferHeap->freeOffset(itr); g_deallocateQueue.clear(); } // drops everything from the cache that isn't considered in use or unrestorable (ranges with streamout) static void CleanupCacheAggressive(MPTR excludedRangeBegin, MPTR excludedRangeEnd) { size_t i = 0; while (i < s_allCacheNodes.size()) { BufferCacheNode* node = s_allCacheNodes[i]; if (node->isInUse()) { i++; continue; } if(!node->isRAMOnly()) { i++; continue; } if(node->GetRangeBegin() < excludedRangeEnd && node->GetRangeEnd() > excludedRangeBegin) { i++; continue; } // delete range if (node->m_hasCacheAlloc) cemu_assert_debug(!node->isInUse()); node->ReleaseCacheMemoryImmediately(); LatteBufferCache_removeSingleNodeFromTree(node); delete node; } } /* callbacks from IntervalTree */ static BufferCacheNode* Create(MPTR rangeBegin, MPTR rangeEnd, std::span<BufferCacheNode*> overlappingObjects) { auto newRange = new BufferCacheNode(rangeBegin, rangeEnd); if (!newRange->allocateCacheMemory()) { // not enough memory available, try to drop ram-only ranges from the ones we replace for (size_t i = 0; i < overlappingObjects.size(); i++) { BufferCacheNode* nodeItr = overlappingObjects[i]; if (!nodeItr->isInUse() && nodeItr->isRAMOnly()) { nodeItr->ReleaseCacheMemoryImmediately(); delete nodeItr; overlappingObjects[i] = nullptr; } } // retry allocation if (!newRange->allocateCacheMemory()) { cemuLog_log(LogType::Force, "Out-of-memory in GPU buffer (trying to allocate: {}KB) Cleaning up cache...", (rangeEnd - rangeBegin + 1023) / 1024); CleanupCacheAggressive(rangeBegin, rangeEnd); if (!newRange->allocateCacheMemory()) { cemuLog_log(LogType::Force, "Failed to free enough memory in GPU buffer"); cemu_assert(false); } } } newRange->syncFromRAM(rangeBegin, rangeEnd); // possible small optimization: only load the ranges from RAM which are not overwritten by ->syncFromNode() for (auto itr : overlappingObjects) { if(itr == nullptr) continue; newRange->syncFromNode(itr); delete itr; } return newRange; } static void Delete(BufferCacheNode* nodeObject) { delete nodeObject; } static void Resize(BufferCacheNode* nodeObject, MPTR rangeBegin, MPTR rangeEnd) { nodeObject->shrink(rangeBegin, rangeEnd); } static BufferCacheNode* Split(BufferCacheNode* nodeObject, MPTR firstRangeBegin, MPTR firstRangeEnd, MPTR secondRangeBegin, MPTR secondRangeEnd) { auto newRange = new BufferCacheNode(secondRangeBegin, secondRangeEnd); // todo - add support for splitting BufferCacheNode memory allocations, then we dont need to do a separate allocation if (!newRange->allocateCacheMemory()) { cemuLog_log(LogType::Force, "Out-of-memory in GPU buffer during split operation"); cemu_assert(false); } newRange->syncFromNode(nodeObject); nodeObject->shrink(firstRangeBegin, firstRangeEnd); return newRange; } }; std::vector<uint32> BufferCacheNode::g_deallocateQueue; IntervalTree2<MPTR, BufferCacheNode> g_gpuBufferCache; void LatteBufferCache_removeSingleNodeFromTree(BufferCacheNode* node) { g_gpuBufferCache.removeRangeSingleWithoutCallback(node->GetRangeBegin(), node->GetRangeEnd()); } BufferCacheNode* LatteBufferCache_reserveRange(MPTR physAddress, uint32 size) { MPTR rangeStart = physAddress - (physAddress % CACHE_PAGE_SIZE); MPTR rangeEnd = (physAddress + size + CACHE_PAGE_SIZE_M1) & ~CACHE_PAGE_SIZE_M1; auto range = g_gpuBufferCache.getRange(rangeStart, rangeEnd); if (!range) { g_gpuBufferCache.addRange(rangeStart, rangeEnd); range = g_gpuBufferCache.getRange(rangeStart, rangeEnd); cemu_assert_debug(range); } cemu_assert_debug(range->GetRangeBegin() <= physAddress); cemu_assert_debug(range->GetRangeEnd() >= (physAddress + size)); return range; } uint32 LatteBufferCache_retrieveDataInCache(MPTR physAddress, uint32 size) { auto range = LatteBufferCache_reserveRange(physAddress, size); range->flagInUse(); range->checkAndSyncModificationsIfChrononChanged(physAddress, size); return range->getBufferOffset(physAddress); } void LatteBufferCache_copyStreamoutDataToCache(MPTR physAddress, uint32 size, uint32 streamoutBufferOffset) { if (size == 0) return; cemu_assert_debug(size >= 16); auto range = LatteBufferCache_reserveRange(physAddress, size); range->flagInUse(); g_renderer->bufferCache_copyStreamoutToMainBuffer(streamoutBufferOffset, range->getBufferOffset(physAddress), size); // write streamout signatures, flag affected pages range->writeStreamout(physAddress, (physAddress + size)); } void LatteBufferCache_invalidate(MPTR physAddress, uint32 size) { if (size == 0) return; g_gpuBufferCache.forEachOverlapping(physAddress, physAddress + size, [](BufferCacheNode* node, MPTR invalidationRangeBegin, MPTR invalidationRangeEnd) { node->invalidate(invalidationRangeBegin, invalidationRangeEnd); } ); } // optimized version of LatteBufferCache_invalidate() if physAddress points to the beginning of a page void LatteBufferCache_invalidatePage(MPTR physAddress) { cemu_assert_debug((physAddress & CACHE_PAGE_SIZE_M1) == 0); BufferCacheNode* node = g_gpuBufferCache.getRangeByPoint(physAddress); if (node) node->invalidate(physAddress, physAddress+CACHE_PAGE_SIZE); } void LatteBufferCache_processDeallocations() { BufferCacheNode::ProcessDeallocations(); } void LatteBufferCache_init(size_t bufferSize) { cemu_assert_debug(g_gpuBufferCache.empty()); g_gpuBufferHeap.reset(new VHeap(nullptr, (uint32)bufferSize)); g_renderer->bufferCache_init((uint32)bufferSize); } void LatteBufferCache_UnloadAll() { BufferCacheNode::UnloadAll(); } void LatteBufferCache_getStats(uint32& heapSize, uint32& allocationSize, uint32& allocNum) { g_gpuBufferHeap->getStats(heapSize, allocationSize, allocNum); } FSpinlock g_spinlockDCFlushQueue; class SparseBitset { static inline constexpr size_t TABLE_MASK = 0xFF; public: bool Empty() const { return m_numNonEmptyVectors == 0; } void Set(uint32 index) { auto& v = m_bits[index & TABLE_MASK]; if (std::find(v.cbegin(), v.cend(), index) != v.end()) return; if (v.empty()) { m_nonEmptyVectors[m_numNonEmptyVectors] = &v; m_numNonEmptyVectors++; } v.emplace_back(index); } template<typename TFunc> void ForAllAndClear(TFunc callbackFunc) { auto vCurrent = m_nonEmptyVectors + 0; auto vEnd = m_nonEmptyVectors + m_numNonEmptyVectors; while (vCurrent < vEnd) { std::vector<uint32>* vec = *vCurrent; vCurrent++; for (const auto& it : *vec) callbackFunc(it); vec->clear(); } m_numNonEmptyVectors = 0; } void Clear() { auto vCurrent = m_nonEmptyVectors + 0; auto vEnd = m_nonEmptyVectors + m_numNonEmptyVectors; while (vCurrent < vEnd) { std::vector<uint32>* vec = *vCurrent; vCurrent++; vec->clear(); } m_numNonEmptyVectors = 0; } private: std::vector<uint32> m_bits[TABLE_MASK + 1]; std::vector<uint32>* m_nonEmptyVectors[TABLE_MASK + 1]; size_t m_numNonEmptyVectors{ 0 }; }; SparseBitset* s_DCFlushQueue = new SparseBitset(); SparseBitset* s_DCFlushQueueAlternate = new SparseBitset(); void LatteBufferCache_notifyDCFlush(MPTR address, uint32 size) { if (address == 0 || size == 0xFFFFFFFF) return; // global flushes are ignored for now uint32 firstPage = address / CACHE_PAGE_SIZE; uint32 lastPage = (address + size - 1) / CACHE_PAGE_SIZE; g_spinlockDCFlushQueue.lock(); for (uint32 i = firstPage; i <= lastPage; i++) s_DCFlushQueue->Set(i); g_spinlockDCFlushQueue.unlock(); } void LatteBufferCache_processDCFlushQueue() { if (s_DCFlushQueue->Empty()) // quick check to avoid locking if there is no work to do return; g_spinlockDCFlushQueue.lock(); std::swap(s_DCFlushQueue, s_DCFlushQueueAlternate); g_spinlockDCFlushQueue.unlock(); s_DCFlushQueueAlternate->ForAllAndClear([](uint32 index) {LatteBufferCache_invalidatePage(index * CACHE_PAGE_SIZE); }); } void LatteBufferCache_notifyDrawDone() { } void LatteBufferCache_notifySwapTVScanBuffer() { if( ActiveSettings::FlushGPUCacheOnSwap() ) g_currentCacheChronon++; } void LatteBufferCache_incrementalCleanup() { static uint32 s_counter = 0; if (s_allCacheNodes.empty()) return; s_counter++; s_counter %= (uint32)s_allCacheNodes.size(); auto range = s_allCacheNodes[s_counter]; if (range->HasStreamoutData()) { // currently we never delete streamout ranges // todo - check if streamout pages got overwritten + if the range would lose the hasStreamoutData flag return; } uint32 heapSize; uint32 allocationSize; uint32 allocNum; g_gpuBufferHeap->getStats(heapSize, allocationSize, allocNum); if (allocationSize >= (heapSize * 4 / 5)) { // heap is 80% filled if (range->GetFrameAge() >= 2) { g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); } } else if (allocationSize >= (heapSize * 3 / 4)) { // heap is 75-100% filled if (range->GetFrameAge() >= 4) { g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); } } else if (allocationSize >= (heapSize / 2)) { // if heap is 50-75% filled if (range->GetFrameAge() >= 20) { g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); } } else { // heap is under 50% capacity if (range->GetFrameAge() >= 500) { g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); } } } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteBufferCache.h ================================================ #pragma once void LatteBufferCache_init(size_t bufferSize); void LatteBufferCache_UnloadAll(); uint32 LatteBufferCache_retrieveDataInCache(MPTR physAddress, uint32 size); void LatteBufferCache_copyStreamoutDataToCache(MPTR physAddress, uint32 size, uint32 streamoutBufferOffset); void LatteBufferCache_invalidate(MPTR physAddress, uint32 size); void LatteBufferCache_notifyDCFlush(MPTR address, uint32 size); void LatteBufferCache_processDCFlushQueue(); void LatteBufferCache_processDeallocations(); void LatteBufferCache_incrementalCleanup(); void LatteBufferCache_getStats(uint32& heapSize, uint32& allocationSize, uint32& allocNum); void LatteBufferCache_notifySwapTVScanBuffer(); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteBufferData.cpp ================================================ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" template<int vectorLen> void rectGenerate4thVertex(uint32be* output, uint32be* input0, uint32be* input1, uint32be* input2) { float* v = (float*)output; for (sint32 i = 0; i < vectorLen; i++) output[vectorLen * 0 + i] = _swapEndianU32(input0[i]); for (sint32 i = 0; i < vectorLen; i++) output[vectorLen * 1 + i] = _swapEndianU32(input1[i]); for (sint32 i = 0; i < vectorLen; i++) output[vectorLen * 2 + i] = _swapEndianU32(input2[i]); float minX = std::min(v[vectorLen * 0 + 0], std::min(v[vectorLen * 1 + 0], v[vectorLen * 2 + 0])); float maxX = std::max(v[vectorLen * 0 + 0], std::max(v[vectorLen * 1 + 0], v[vectorLen * 2 + 0])); float minY = std::min(v[vectorLen * 0 + 1], std::min(v[vectorLen * 1 + 1], v[vectorLen * 2 + 1]));; float maxY = std::max(v[vectorLen * 0 + 1], std::max(v[vectorLen * 1 + 1], v[vectorLen * 2 + 1]));; float totalX = minX; totalX += maxY; float halfX = totalX / 2.0f; float totalY = minY; totalY += maxY; float halfY = totalY / 2.0f; sint32 countX = ((v[vectorLen * 0 + 0] < halfX) ? 1 : 0) + ((v[vectorLen * 1 + 0] < halfX) ? 1 : 0) + ((v[vectorLen * 2 + 0] < halfX) ? 1 : 0); sint32 countY = ((v[vectorLen * 0 + 1] < halfY) ? 1 : 0) + ((v[vectorLen * 1 + 1] < halfY) ? 1 : 0) + ((v[vectorLen * 2 + 1] < halfY) ? 1 : 0); if (countX < 2) v[vectorLen * 3 + 0] = minX; else v[vectorLen * 3 + 0] = maxX; if (countY < 2) v[vectorLen * 3 + 1] = minY; else v[vectorLen * 3 + 1] = maxY; if (vectorLen >= 3) v[vectorLen * 3 + 2] = v[vectorLen * 0 + 2]; // z from v0 if (vectorLen >= 4) v[vectorLen * 3 + 3] = v[vectorLen * 0 + 3]; // w from v0 // order of rectangle vertices is // v0 v1 // v2 v3 for (sint32 f = 0; f < vectorLen*4; f++) output[f] = _swapEndianU32(output[f]); } #define ATTRIBUTE_CACHE_RING_SIZE (128) // up to 128 entries can be cached void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* uniformData) { uint32 shaderAluConst; uint32 shaderUniformRegisterOffset; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: shaderAluConst = 0x400; shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START; break; case LatteConst::ShaderType::Pixel: shaderAluConst = 0; shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START; break; case LatteConst::ShaderType::Geometry: shaderAluConst = 0; // geometry shader has no ALU const shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START; break; default: cemu_assert_debug(false); } // sourced from uniform registers uint32* aluConstBase = LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst; for (auto it : shader->list_remappedUniformEntries_register) { uint64* uniformRegData = (uint64*)(aluConstBase + it.indexOffset / 4); uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); regDest[0] = uniformRegData[0]; regDest[1] = uniformRegData[1]; } // sourced from uniform buffers for (auto& bufferGroup : shader->list_remappedUniformEntries_bufferGroups) { MPTR physicalAddr = LatteGPUState.contextRegister[shaderUniformRegisterOffset + bufferGroup.kcacheBankIdOffset / 4]; if (physicalAddr) { uint8* uniformBase = memory_base + physicalAddr; for (auto& it : bufferGroup.entries) { uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); uint64* uniformEntrySrc = (uint64*)(uniformBase + it.indexOffset); memcpy(regDest, uniformEntrySrc, 16); } } else { for (auto& it : bufferGroup.entries) { uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); regDest[0] = 0; regDest[1] = 0; } } } } void LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType) { if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { for(const auto& buf : shader->list_quickBufferList) { sint32 i = buf.index; MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0]; uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1; if (physicalAddr == MPTR_NULL) [[unlikely]] { g_renderer->buffer_bindUniformBuffer(shaderType, i, 0, 0); continue; } uniformSize = std::min<uint32>(uniformSize, buf.size); uint32 bindOffset = LatteBufferCache_retrieveDataInCache(physicalAddr, uniformSize); g_renderer->buffer_bindUniformBuffer(shaderType, i, bindOffset, uniformSize); } } } // upload vertex and uniform buffers bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount) { static uint32 s_syncBufferCounter = 0; s_syncBufferCounter++; if (s_syncBufferCounter >= 30) { LatteBufferCache_incrementalCleanup(); s_syncBufferCounter = 0; } LatteBufferCache_processDCFlushQueue(); // process queued deallocations from previous drawcall LatteBufferCache_processDeallocations(); // sync and bind vertex buffers LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); if (!parsedFetchShader) return false; for (auto& bufferGroup : parsedFetchShader->bufferGroups) { uint32 bufferIndex = bufferGroup.attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0]; uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1; uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; if (bufferAddress == MPTR_NULL) { g_renderer->buffer_bindVertexBuffer(bufferIndex, 0, 0); continue; } // dont rely on buffer size given by game uint32 fixedBufferSize = 0; if (bufferGroup.hasVtxIndexAccess) fixedBufferSize = bufferStride * (maxIndex + 1) + bufferGroup.maxOffset; if (bufferGroup.hasInstanceIndexAccess) { uint32 fixedBufferSizeInstance = bufferStride * ((baseInstance + instanceCount) + 1) + bufferGroup.maxOffset; fixedBufferSize = std::max(fixedBufferSize, fixedBufferSizeInstance); } if (fixedBufferSize == 0 || bufferStride == 0) fixedBufferSize += 128; #if BOOST_OS_MACOS if(bufferStride % 4 != 0) { if (g_renderer->GetType() == RendererAPI::Vulkan) { if (VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance()) { auto fixedBuffer = vkRenderer->buffer_genStrideWorkaroundVertexBuffer(bufferAddress, fixedBufferSize, bufferStride); vkRenderer->buffer_bindVertexStrideWorkaroundBuffer(fixedBuffer.first, fixedBuffer.second, bufferIndex, fixedBufferSize); continue; } } } #endif uint32 bindOffset = LatteBufferCache_retrieveDataInCache(bufferAddress, fixedBufferSize); g_renderer->buffer_bindVertexBuffer(bufferIndex, bindOffset, fixedBufferSize); } // sync uniform buffers LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); if (vertexShader) LatteBufferCache_syncGPUUniformBuffers(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex); LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); if (geometryShader) LatteBufferCache_syncGPUUniformBuffers(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry); LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); if (pixelShader) LatteBufferCache_syncGPUUniformBuffers(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel); return true; } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteCachedFBO.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "util/math/vector2.h" class LatteCachedFBO { public: LatteCachedFBO(uint64 key); uint32 calculateNumColorBuffers() { uint32 n = 0; for (sint32 i = 0; i < 8; i++) if (colorBuffer[i].texture) n++; return n; } bool hasDepthBuffer() { return depthBuffer.texture; } std::vector<LatteTexture*>& GetTextures() { return m_referencedTextures; } virtual ~LatteCachedFBO() {}; private: void calculateEffectiveRenderAreaSize() { Vector2i rtEffectiveSize; rtEffectiveSize.x = 0; rtEffectiveSize.y = 0; sint32 numViews = 0; // derive extent from color buffers for (sint32 i = 0; i < 8; i++) { if(colorBuffer[i].texture == nullptr) continue; sint32 effectiveWidth, effectiveHeight; colorBuffer[i].texture->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, colorBuffer[i].texture->firstMip); if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0) { rtEffectiveSize.x = effectiveWidth; rtEffectiveSize.y = effectiveHeight; } if (effectiveWidth < rtEffectiveSize.x) { cemuLog_logDebug(LogType::Force, "Framebuffer has color texture with smaller effective width ({} -> {})", rtEffectiveSize.x, effectiveWidth); rtEffectiveSize.x = effectiveWidth; } if (effectiveHeight < rtEffectiveSize.y) { cemuLog_logDebug(LogType::Force, "Framebuffer has color texture with smaller effective height ({} -> {})", rtEffectiveSize.y, effectiveHeight); rtEffectiveSize.y = effectiveHeight; } numViews++; } // derive extent from depth buffer if (depthBuffer.texture) { sint32 effectiveWidth, effectiveHeight; depthBuffer.texture->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBuffer.texture->firstMip); if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0) { rtEffectiveSize.x = effectiveWidth; rtEffectiveSize.y = effectiveHeight; } if (effectiveWidth < rtEffectiveSize.x) { cemuLog_logDebug(LogType::Force, "Framebuffer has depth texture with smaller effective width ({} -> {})", rtEffectiveSize.x, effectiveWidth); rtEffectiveSize.x = effectiveWidth; } if (effectiveHeight < rtEffectiveSize.y) { cemuLog_logDebug(LogType::Force, "Framebuffer has depth texture with smaller effective height ({} -> {})", rtEffectiveSize.y, effectiveHeight); rtEffectiveSize.y = effectiveHeight; } numViews++; } if (numViews == 0) { // empty FBO m_size = Vector2i(32, 32); return; } cemu_assert_debug(rtEffectiveSize.x != 0); cemu_assert_debug(rtEffectiveSize.y != 0); m_size = rtEffectiveSize; } public: uint64 key; Vector2i m_size; struct { LatteTextureView* texture{}; }colorBuffer[8]{}; struct { LatteTextureView* texture{}; bool hasStencil{}; }depthBuffer{}; uint32 drawBuffersMask{}; std::vector<LatteTexture*> m_referencedTextures; // color and depth views combined }; class LatteMRT { public: static void NotifyTextureDeletion(LatteTexture* texture); // GPU state static LatteTextureView* GetColorAttachmentTexture(uint32 index, bool createNew, bool checkForTextureChanges); static uint8 GetActiveColorBufferMask(const LatteDecompilerShader* pixelShader, const struct LatteContextRegister& lcr); static bool GetActiveDepthBufferMask(const struct LatteContextRegister& lcr); static Latte::E_GX2SURFFMT GetColorBufferFormat(const uint32 index, const LatteContextRegister& lcr); static Latte::E_GX2SURFFMT GetDepthBufferFormat(const struct LatteContextRegister& lcr); // FBO state management static void SetColorAttachment(uint32 index, LatteTextureView* view); static void SetDepthAndStencilAttachment(LatteTextureView* view, bool hasStencil); static LatteTextureView* GetColorAttachment(uint32 index); static LatteTextureView* GetDepthAttachment(); static void ApplyCurrentState(); static bool UpdateCurrentFBO(); // update FBO with info from current GPU state // helper functions static void BindColorBufferOnly(LatteTextureView* view); static void BindDepthBufferOnly(LatteTextureView* view); static void GetCurrentFragCoordScale(float* coordScale); static void GetVirtualViewportDimensions(sint32& width, sint32& height); // returns the width and height of the current GPU viewport (unaffected by graphic pack rules) // todo - move this into FBO destructor (?) static void DeleteCachedFBO(LatteCachedFBO* cfbo); private: static LatteCachedFBO* CreateCachedFBO(uint64 key); }; ================================================ FILE: src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp ================================================ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/OS/libs/gx2/GX2.h" // for write gatherer and special state. Get rid of dependency #include "Cafe/OS/libs/gx2/GX2_Misc.h" // for GX2::sGX2MainCoreIndex. Legacy dependency #include "Cafe/OS/libs/gx2/GX2_Event.h" // for notification callbacks #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteIndices.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/TCL/TCL.h" // TCL currently handles the GPU command ringbuffer #include "Cafe/CafeSystem.h" #include <boost/container/small_vector.hpp> void LatteCP_DebugPrintCmdBuffer(uint32be* bufferPtr, uint32 size); #define CP_TIMER_RECHECK 1024 //#define LATTE_CP_LOGGING typedef uint32be* LatteCMDPtr; #define LatteReadCMD() ((uint32)*(cmd++)) #define LatteSkipCMD(_nWords) cmd += (_nWords) void LatteThread_HandleOSScreen(); void LatteThread_Exit(); class DrawPassContext { struct CmdQueuePos { CmdQueuePos(LatteCMDPtr current, LatteCMDPtr start, LatteCMDPtr end) : current(current), start(start), end(end) {}; LatteCMDPtr current; LatteCMDPtr start; LatteCMDPtr end; }; public: bool isWithinDrawPass() const { return m_drawPassActive; } void beginDrawPass() { m_drawPassActive = true; m_isFirstDraw = true; m_vertexBufferChanged = true; m_uniformBufferChanged = true; g_renderer->draw_beginSequence(); } void executeDraw(uint32 count, bool isAutoIndex, MPTR physIndices) { uint32 baseVertex = LatteGPUState.contextRegister[mmSQ_VTX_BASE_VTX_LOC]; uint32 baseInstance = LatteGPUState.contextRegister[mmSQ_VTX_START_INST_LOC]; uint32 numInstances = LatteGPUState.contextNew.VGT_DMA_NUM_INSTANCES.get_NUM_INSTANCES(); if (!isAutoIndex) { cemu_assert_debug(physIndices != MPTR_NULL); if (physIndices == MPTR_NULL) return; auto indexType = LatteGPUState.contextNew.VGT_DMA_INDEX_TYPE.get_INDEX_TYPE(); g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, physIndices, indexType, m_isFirstDraw); } else { g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, MPTR_NULL, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::AUTO, m_isFirstDraw); } performanceMonitor.cycle[performanceMonitor.cycleIndex].drawCallCounter++; if (!m_isFirstDraw) performanceMonitor.cycle[performanceMonitor.cycleIndex].fastDrawCallCounter++; m_isFirstDraw = false; m_vertexBufferChanged = false; m_uniformBufferChanged = false; } void endDrawPass() { g_renderer->draw_endSequence(); m_drawPassActive = false; } void notifyModifiedVertexBuffer() { m_vertexBufferChanged = true; } void notifyModifiedUniformBuffer() { m_uniformBufferChanged = true; } // command buffer processing position void PushCurrentCommandQueuePos(LatteCMDPtr current, LatteCMDPtr start, LatteCMDPtr end) { m_queuePosStack.emplace_back(current, start, end); } bool PopCurrentCommandQueuePos(LatteCMDPtr& current, LatteCMDPtr& start, LatteCMDPtr& end) { if (m_queuePosStack.empty()) return false; const auto& it = m_queuePosStack.back(); current = it.current; start = it.start; end = it.end; m_queuePosStack.pop_back(); return true; } private: bool m_drawPassActive{ false }; bool m_isFirstDraw{false}; bool m_vertexBufferChanged{ false }; bool m_uniformBufferChanged{ false }; boost::container::small_vector<CmdQueuePos, 4> m_queuePosStack; }; void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx); // called whenever the GPU runs out of commands or hits a wait condition (semaphores, HLE waits) void LatteCP_signalEnterWait() { // based on the assumption that games won't do a rugpull and swap out buffer data in the middle of an uninterrupted sequence of drawcalls, // we only flush caches when the GPU goes idle or has to wait for any operation LatteIndices_invalidateAll(); } /* * Read a U32 from the command buffer * If no data is available then wait in a busy loop */ uint32 LatteCP_readU32Deprc() { // no display list active while (true) { uint32 cmdWord; if ( TCL::TCLGPUReadRBWord(cmdWord) ) return cmdWord; g_renderer->NotifyLatteCommandProcessorIdle(); // let the renderer know in case it wants to flush any commands performanceMonitor.gpuTime_idleTime.beginMeasuring(); // no command data available, spin in a busy loop for a bit then check again for (sint32 busy = 0; busy < 80; busy++) { _mm_pause(); } LatteThread_HandleOSScreen(); // check if new frame was presented via OSScreen API if ( TCL::TCLGPUReadRBWord(cmdWord) ) return cmdWord; if (Latte_GetStopSignal()) LatteThread_Exit(); // still no command data available, do some other tasks LatteTiming_HandleTimedVsync(); LatteAsyncCommands_checkAndExecute(); std::this_thread::yield(); performanceMonitor.gpuTime_idleTime.endMeasuring(); } UNREACHABLE; } template<uint32 readU32()> void LatteCP_skipWords(uint32 wordsToSkip) { while (wordsToSkip) { readU32(); wordsToSkip--; } } LatteCMDPtr LatteCP_itSurfaceSync(LatteCMDPtr cmd) { uint32 invalidationFlags = LatteReadCMD(); uint32 size = LatteReadCMD() << 8; MPTR addressPhys = LatteReadCMD() << 8; uint32 pollInterval = LatteReadCMD(); if (addressPhys == MPTR_NULL || size == 0xFFFFFFFF) return cmd; // block global invalidations because they are too expensive if (invalidationFlags & 0x800000) { // invalidate uniform or attribute buffer LatteBufferCache_invalidate(addressPhys, size); } return cmd; } // called from TCL command queue. Executes a memory command buffer void LatteCP_itIndirectBufferDepr(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 3); uint32 physicalAddress = LatteReadCMD(); uint32 physicalAddressHigh = LatteReadCMD(); // unused uint32 sizeInU32s = LatteReadCMD(); #ifdef LATTE_CP_LOGGING if (GetAsyncKeyState('A')) LatteCP_DebugPrintCmdBuffer(MEMPTR<uint32be>(physicalAddress), displayListSize); #endif if (sizeInU32s > 0) { DrawPassContext drawPassCtx; uint32be* buf = MEMPTR<uint32be>(physicalAddress).GetPtr(); drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInU32s); LatteCP_processCommandBuffer(drawPassCtx); if (drawPassCtx.isWithinDrawPass()) drawPassCtx.endDrawPass(); } } // pushes the command buffer to the stack void LatteCP_itIndirectBuffer(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& drawPassCtx) { cemu_assert_debug(nWords == 3); uint32 physicalAddress = LatteReadCMD(); uint32 physicalAddressHigh = LatteReadCMD(); // unused uint32 sizeInDWords = LatteReadCMD(); if (sizeInDWords > 0) { uint32 displayListSize = sizeInDWords * 4; uint32be* buf = MEMPTR<uint32be>(physicalAddress).GetPtr(); drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInDWords); } } LatteCMDPtr LatteCP_itStreamoutBufferUpdate(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 5); uint32 updateControl = LatteReadCMD(); uint32 physicalAddressWrite = LatteReadCMD(); uint32 ukn1 = LatteReadCMD(); uint32 physicalAddressRead = LatteReadCMD(); uint32 ukn3 = LatteReadCMD(); uint32 mode = (updateControl >> 1) & 3; uint32 soIndex = (updateControl >> 8) & 3; if (mode == 0) { // reset pointer MPTR virtualAddress = memory_physicalToVirtual(physicalAddressRead); uint32 bufferOffset = 0; LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_OFFSET_0 + 4 * soIndex] = bufferOffset; } else if (mode == 3) { // store current offset to memory MPTR virtualAddress = memory_physicalToVirtual(physicalAddressWrite); uint32 bufferOffset = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_OFFSET_0 + 4 * soIndex]; memory_writeU32(virtualAddress + 0x00, bufferOffset); } else { cemu_assert_unimplemented(); } return cmd; } template<uint32 registerBaseMode> void LatteCP_itSetRegistersGeneric_handleSpecialRanges(uint32 registerStartIndex, uint32 registerEndIndex) { if constexpr (registerBaseMode == IT_SET_CONTEXT_REG) { if (registerStartIndex <= mmSQ_VTX_SEMANTIC_CLEAR && registerEndIndex >= mmSQ_VTX_SEMANTIC_CLEAR) { for (uint32 i = 0; i < 32; i++) { LatteGPUState.contextRegister[mmSQ_VTX_SEMANTIC_0 + i] = 0xFF; } } } } template<uint32 TRegisterBase> LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords) { uint32 registerOffset = LatteReadCMD(); uint32 registerIndex = TRegisterBase + registerOffset; uint32 registerStartIndex = registerIndex; uint32 registerEndIndex = registerStartIndex + nWords; #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug((registerIndex + nWords) <= LATTE_MAX_REGISTER); #endif uint32* outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex); if (LatteGPUState.contextControl0 == 0x80000077) { // state shadowing enabled uint32* shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex; sint32 indexCounter = 0; while (--nWords) { uint32 dataWord = LatteReadCMD(); MPTR regShadowAddr = shadowAddrs[indexCounter]; if (regShadowAddr) *(uint32*)(memory_base + regShadowAddr) = _swapEndianU32(dataWord); outputReg[indexCounter] = dataWord; indexCounter++; } } else { // state shadowing disabled sint32 indexCounter = 0; while (--nWords) { *outputReg = LatteReadCMD(); outputReg++; } } // some register writes trigger special behavior LatteCP_itSetRegistersGeneric_handleSpecialRanges<TRegisterBase>(registerStartIndex, registerEndIndex); return cmd; } template<uint32 TRegisterBase, typename TRegRangeCallback> LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords, TRegRangeCallback cbRegRange) { uint32 registerOffset = LatteReadCMD(); uint32 registerIndex = TRegisterBase + registerOffset; uint32 registerStartIndex = registerIndex; uint32 registerEndIndex = registerStartIndex + nWords; #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug((registerIndex + nWords) <= LATTE_MAX_REGISTER); #endif cbRegRange(registerStartIndex, registerEndIndex); uint32* outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex); if (LatteGPUState.contextControl0 == 0x80000077) { // state shadowing enabled uint32* shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex; sint32 indexCounter = 0; while (--nWords) { uint32 dataWord = LatteReadCMD(); MPTR regShadowAddr = shadowAddrs[indexCounter]; if (regShadowAddr) *(uint32*)(memory_base + regShadowAddr) = _swapEndianU32(dataWord); outputReg[indexCounter] = dataWord; indexCounter++; } } else { // state shadowing disabled sint32 indexCounter = 0; while (--nWords) { *outputReg = LatteReadCMD(); outputReg++; } } // some register writes trigger special behavior LatteCP_itSetRegistersGeneric_handleSpecialRanges<TRegisterBase>(registerStartIndex, registerEndIndex); return cmd; } LatteCMDPtr LatteCP_itIndexType(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 1); LatteGPUState.contextNew.VGT_DMA_INDEX_TYPE.set_INDEX_TYPE((Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE)LatteReadCMD()); return cmd; } LatteCMDPtr LatteCP_itNumInstances(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 1); uint32 numInstances = LatteReadCMD(); if (numInstances == 0) numInstances = 1; LatteGPUState.contextNew.VGT_DMA_NUM_INSTANCES.set_NUM_INSTANCES(numInstances); return cmd; } LatteCMDPtr LatteCP_itWaitRegMem(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 6); uint32 word0 = LatteReadCMD(); uint32 word1 = LatteReadCMD(); uint32 word2 = LatteReadCMD(); uint32 word3 = LatteReadCMD(); uint32 word4 = LatteReadCMD(); uint32 word5 = LatteReadCMD(); uint32 compareOp = (word0) & 7; uint32 physAddr = word1 & ~3; cemu_assert_debug((physAddr&3) == 0); uint32 fenceValue = word3; uint32 fenceMask = word4; uint32* fencePtr = (uint32*)memory_getPointerFromPhysicalOffset(physAddr); const uint32 GPU7_WAIT_MEM_OP_ALWAYS = 0; const uint32 GPU7_WAIT_MEM_OP_LESS = 1; const uint32 GPU7_WAIT_MEM_OP_LEQUAL = 2; const uint32 GPU7_WAIT_MEM_OP_EQUAL = 3; const uint32 GPU7_WAIT_MEM_OP_NOTEQUAL = 4; const uint32 GPU7_WAIT_MEM_OP_GEQUAL = 5; const uint32 GPU7_WAIT_MEM_OP_GREATER = 6; const uint32 GPU7_WAIT_MEM_OP_NEVER = 7; LatteCP_signalEnterWait(); bool stalls = false; if ((word0 & 0x10) != 0) { // wait for memory address performanceMonitor.gpuTime_fenceTime.beginMeasuring(); while (true) { uint32 fenceMemValue = _swapEndianU32(*fencePtr); fenceMemValue &= fenceMask; if (compareOp == GPU7_WAIT_MEM_OP_LESS) { if (fenceMemValue < fenceValue) break; } else if (compareOp == GPU7_WAIT_MEM_OP_LEQUAL) { if (fenceMemValue <= fenceValue) break; } else if (compareOp == GPU7_WAIT_MEM_OP_EQUAL) { if (fenceMemValue == fenceValue) break; } else if (compareOp == GPU7_WAIT_MEM_OP_NOTEQUAL) { if (fenceMemValue != fenceValue) break; } else if (compareOp == GPU7_WAIT_MEM_OP_GEQUAL) { if (fenceMemValue >= fenceValue) break; } else if (compareOp == GPU7_WAIT_MEM_OP_GREATER) { if (fenceMemValue > fenceValue) break; } else if (compareOp == GPU7_WAIT_MEM_OP_ALWAYS) { break; } else if (compareOp == GPU7_WAIT_MEM_OP_NEVER) { cemuLog_logOnce(LogType::Force, "Latte: WAIT_MEM_OP_NEVER encountered"); break; } else assert_dbg(); if (!stalls) { g_renderer->NotifyLatteCommandProcessorIdle(); stalls = true; } // check if any GPU events happened LatteTiming_HandleTimedVsync(); LatteAsyncCommands_checkAndExecute(); } performanceMonitor.gpuTime_fenceTime.endMeasuring(); } else { // wait for register debugBreakpoint(); } return cmd; } LatteCMDPtr LatteCP_itMemWrite(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 4); uint32 word0 = LatteReadCMD(); uint32 word1 = LatteReadCMD(); uint32 word2 = LatteReadCMD(); uint32 word3 = LatteReadCMD(); MPTR valuePhysAddr = (word0 & ~3); if (valuePhysAddr == 0) { cemuLog_log(LogType::Force, "GPU: Invalid itMemWrite to null pointer"); return cmd; } uint32be* memPtr = (uint32be*)memory_getPointerFromPhysicalOffset(valuePhysAddr); if (word1 == 0x40000) { // write U32 stdx::atomic_ref<uint32be> atomicRef(*memPtr); atomicRef.store(word2); } else if (word1 == 0x00000) { // write U64 // note: The U32s are swapped here, but needs verification. Also, it seems like the two U32 halves are written independently and the U64 as a whole is not atomic -> investiagte stdx::atomic_ref<uint64be> atomicRef(*(uint64be*)memPtr); atomicRef.store(((uint64le)word2 << 32) | word3); } else if (word1 == 0x20000) { // write U64 (little endian) stdx::atomic_ref<uint64le> atomicRef(*(uint64le*)memPtr); atomicRef.store(((uint64le)word3 << 32) | word2); } else cemu_assert_unimplemented(); return cmd; } LatteCMDPtr LatteCP_itEventWriteEOP(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 5); uint32 word0 = LatteReadCMD(); uint32 word1 = LatteReadCMD(); uint32 word2 = LatteReadCMD(); uint32 word3 = LatteReadCMD(); // value low bits uint32 word4 = LatteReadCMD(); // value high bits cemu_assert_debug(word2 == 0x40000000 || word2 == 0x42000000); if (word0 == 0x504 && (word2&0x40000000)) // todo - figure out the flags { stdx::atomic_ref<uint64be> atomicRef(*(uint64be*)memory_getPointerFromPhysicalOffset(word1)); uint64 val = ((uint64)word4 << 32) | word3; atomicRef.store(val); } else { cemu_assert_unimplemented(); } bool triggerInterrupt = (word2 & 0x2000000) != 0; if (triggerInterrupt) { // todo - timestamp interrupt } TCL::TCLGPUNotifyNewRetirementTimestamp(); return cmd; } LatteCMDPtr LatteCP_itMemSemaphore(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 2); MPTR semaphorePhysicalAddress = LatteReadCMD(); uint32 semaphoreControl = LatteReadCMD(); uint8 SEM_SIGNAL = (semaphoreControl >> 29) & 7; std::atomic<uint64le>* semaphoreData = _rawPtrToAtomic((uint64le*)memory_getPointerFromPhysicalOffset(semaphorePhysicalAddress)); static_assert(sizeof(std::atomic<uint64le>) == sizeof(uint64le)); if (SEM_SIGNAL == 6) { // signal semaphoreData->fetch_add(1); } else if(SEM_SIGNAL == 7) { // wait LatteCP_signalEnterWait(); size_t loopCount = 0; while (true) { uint64le oldVal = semaphoreData->load(); if (oldVal == 0) { loopCount++; if (loopCount > 2000) std::this_thread::yield(); continue; } if (semaphoreData->compare_exchange_strong(oldVal, oldVal - 1)) break; } } else { cemu_assert_debug(false); } return cmd; } LatteCMDPtr LatteCP_itContextControl(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 2); uint32 word0 = LatteReadCMD(); uint32 word1 = LatteReadCMD(); LatteGPUState.contextControl0 = word0; LatteGPUState.contextControl1 = word1; return cmd; } LatteCMDPtr LatteCP_itLoadReg(LatteCMDPtr cmd, uint32 nWords, uint32 regBase) { if (nWords < 2 || (nWords & 1) != 0) { cemuLog_logDebug(LogType::Force, "itLoadReg: Invalid nWords value"); return cmd; } MPTR physAddressRegArea = LatteReadCMD(); uint32 waitForIdle = LatteReadCMD(); uint32 loadEntries = (nWords - 2) / 2; uint32 regShadowMemAddr = physAddressRegArea; for (uint32 i = 0; i < loadEntries; i++) { uint32 regOffset = LatteReadCMD(); uint32 regCount = LatteReadCMD(); cemu_assert_debug(regCount != 0); uint32 regAddr = regBase + regOffset; for (uint32 f = 0; f < regCount; f++) { LatteGPUState.contextRegisterShadowAddr[regAddr] = regShadowMemAddr; LatteGPUState.contextRegister[regAddr] = memory_read<uint32>(regShadowMemAddr); regAddr++; regShadowMemAddr += 4; } } return cmd; } bool conditionalRenderActive = false; LatteCMDPtr LatteCP_itSetPredication(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 2); MPTR physQueryInfo = LatteReadCMD(); uint32 flags = LatteReadCMD(); uint32 queryTypeFlag = (flags >> 13) & 7; uint32 pixelsMustPassFlag = (flags >> 31) & 1; uint32 dontWaitFlag = (flags >> 1) & 19; if (queryTypeFlag == 0) { // disable conditional render if (conditionalRenderActive == false) debug_printf("conditionalRenderActive already inactive\n"); conditionalRenderActive = false; } else { // enable conditonal render if (conditionalRenderActive == true) debug_printf("conditionalRenderActive already active\n"); conditionalRenderActive = true; } return cmd; } LatteCMDPtr LatteCP_itDrawIndex2(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& drawPassCtx) { cemu_assert_debug(nWords == 5); uint32 ukn1 = LatteReadCMD(); MPTR physIndices = LatteReadCMD(); uint32 ukn2 = LatteReadCMD(); uint32 count = LatteReadCMD(); uint32 ukn3 = LatteReadCMD(); LatteGPUState.currentDrawCallTick = GetTickCount(); drawPassCtx.executeDraw(count, false, physIndices); return cmd; } LatteCMDPtr LatteCP_itDrawIndexAuto(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& drawPassCtx) { cemu_assert_debug(nWords == 2); uint32 count = LatteReadCMD(); uint32 ukn = LatteReadCMD(); LatteGPUState.currentDrawCallTick = GetTickCount(); // todo - better way to identify compute drawcalls if ((LatteGPUState.contextRegister[mmSQ_CONFIG] >> 24) == 0xE4) { uint32 vsProgramCode = ((LatteGPUState.contextRegister[mmSQ_PGM_START_ES] & 0xFFFFFF) << 8); uint32 vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_ES + 1] << 3; cemuLog_logDebug(LogType::Force, "Compute {} {:08x} {:08x} (unsupported)", count, vsProgramCode, vsProgramSize); } else { drawPassCtx.executeDraw(count, true, MPTR_NULL); } return cmd; } MPTR _tempIndexArrayMPTR = MPTR_NULL; LatteCMDPtr LatteCP_itDrawImmediate(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& drawPassCtx) { uint32 count = LatteReadCMD(); uint32 ukn1 = LatteReadCMD(); // reserve array for index data if (_tempIndexArrayMPTR == MPTR_NULL) _tempIndexArrayMPTR = coreinit_allocFromSysArea(0x4000 * sizeof(uint32), 0x4); LatteGPUState.currentDrawCallTick = GetTickCount(); // calculate size of index data in packet and read indices uint32 numIndexU32s; auto indexType = LatteGPUState.contextNew.VGT_DMA_INDEX_TYPE.get_INDEX_TYPE(); if (indexType == Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::U16_BE) { // 16bit indices numIndexU32s = (count + 1) / 2; memcpy(memory_getPointerFromVirtualOffset(_tempIndexArrayMPTR), cmd, numIndexU32s * sizeof(uint32)); LatteSkipCMD(numIndexU32s); // swap pairs uint32* indexDataU32 = (uint32*)memory_getPointerFromVirtualOffset(_tempIndexArrayMPTR); for (uint32 i = 0; i < numIndexU32s; i++) { indexDataU32[i] = (indexDataU32[i] >> 16) | (indexDataU32[i] << 16); } LatteIndices_invalidate(memory_getPointerFromVirtualOffset(_tempIndexArrayMPTR), numIndexU32s * sizeof(uint32)); } else if (indexType == Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::U32_BE) { // 32bit indices cemu_assert_debug(false); // testing needed numIndexU32s = count; memcpy(memory_getPointerFromVirtualOffset(_tempIndexArrayMPTR), cmd, numIndexU32s * sizeof(uint32)); LatteSkipCMD(numIndexU32s); LatteIndices_invalidate(memory_getPointerFromVirtualOffset(_tempIndexArrayMPTR), numIndexU32s * sizeof(uint32)); } else { cemuLog_log(LogType::Force, "itDrawImmediate - Unsupported index type"); return cmd; } cemu_assert_debug(nWords == (2 + numIndexU32s)); // verify packet size drawPassCtx.executeDraw(count, false, _tempIndexArrayMPTR); return cmd; } LatteCMDPtr LatteCP_itHLESampleTimer(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 1); MPTR timerMPTR = (MPTR)LatteReadCMD(); memory_writeU64(timerMPTR, coreinit::OSGetSystemTime()); return cmd; } LatteCMDPtr LatteCP_itHLESpecialState(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 2); uint32 stateId = LatteReadCMD(); uint32 stateValue = LatteReadCMD(); if (stateId > GX2_SPECIAL_STATE_COUNT) { cemu_assert_suspicious(); } else { LatteGPUState.contextNew.GetSpecialStateValues()[stateId] = stateValue; } return cmd; } LatteCMDPtr LatteCP_itHLEBeginOcclusionQuery(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 1); MPTR queryMPTR = (MPTR)LatteReadCMD(); LatteQuery_BeginOcclusionQuery(queryMPTR); return cmd; } LatteCMDPtr LatteCP_itHLEEndOcclusionQuery(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 1); MPTR queryMPTR = (MPTR)LatteReadCMD(); LatteQuery_EndOcclusionQuery(queryMPTR); return cmd; } LatteCMDPtr LatteCP_itHLEBottomOfPipeCB(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 3); MPTR timestampMPTR = (uint32)LatteReadCMD(); uint32 timestampHigh = (uint32)LatteReadCMD(); uint32 timestampLow = (uint32)LatteReadCMD(); uint64 timestamp = ((uint64)timestampHigh << 32ULL) | (uint64)timestampLow; // write timestamp *(uint32*)memory_getPointerFromPhysicalOffset(timestampMPTR) = _swapEndianU32((uint32)(timestamp >> 32)); *(uint32*)memory_getPointerFromPhysicalOffset(timestampMPTR + 4) = _swapEndianU32((uint32)timestamp); // send event GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::TIMESTAMP_BOTTOM); return cmd; } // GPU-side handler for GX2CopySurface/GX2CopySurfaceEx and similar LatteCMDPtr LatteCP_itHLECopySurfaceNew(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 26); // src MPTR srcPhysAddr = LatteReadCMD(); MPTR srcMipAddr = LatteReadCMD(); uint32 srcSwizzle = LatteReadCMD(); Latte::E_GX2SURFFMT srcSurfaceFormat = (Latte::E_GX2SURFFMT)LatteReadCMD(); sint32 srcWidth = LatteReadCMD(); sint32 srcHeight = LatteReadCMD(); sint32 srcDepth = LatteReadCMD(); uint32 srcPitch = LatteReadCMD(); uint32 srcSlice = LatteReadCMD(); Latte::E_DIM srcDim = (Latte::E_DIM)LatteReadCMD(); Latte::E_HWTILEMODE srcTilemode = (Latte::E_HWTILEMODE)LatteReadCMD(); sint32 srcAA = LatteReadCMD(); sint32 srcLevel = LatteReadCMD(); // dst MPTR dstPhysAddr = LatteReadCMD(); MPTR dstMipAddr = LatteReadCMD(); uint32 dstSwizzle = LatteReadCMD(); Latte::E_GX2SURFFMT dstSurfaceFormat = (Latte::E_GX2SURFFMT)LatteReadCMD(); sint32 dstWidth = LatteReadCMD(); sint32 dstHeight = LatteReadCMD(); sint32 dstDepth = LatteReadCMD(); uint32 dstPitch = LatteReadCMD(); uint32 dstSlice = LatteReadCMD(); Latte::E_DIM dstDim = (Latte::E_DIM)LatteReadCMD(); Latte::E_HWTILEMODE dstTilemode = (Latte::E_HWTILEMODE)LatteReadCMD(); sint32 dstAA = LatteReadCMD(); sint32 dstLevel = LatteReadCMD(); LatteSurfaceCopy_copySurfaceNew(srcPhysAddr, srcMipAddr, srcSwizzle, srcSurfaceFormat, srcWidth, srcHeight, srcDepth, srcPitch, srcSlice, srcDim, srcTilemode, srcAA, srcLevel, dstPhysAddr, dstMipAddr, dstSwizzle, dstSurfaceFormat, dstWidth, dstHeight, dstDepth, dstPitch, dstSlice, dstDim, dstTilemode, dstAA, dstLevel); return cmd; } LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 23); uint32 clearMask = LatteReadCMD(); // color (1), depth (2), stencil (4) // color buffer MPTR colorBufferMPTR = LatteReadCMD(); // physical address for color buffer Latte::E_GX2SURFFMT colorBufferFormat = (Latte::E_GX2SURFFMT)LatteReadCMD(); Latte::E_HWTILEMODE colorBufferTilemode = (Latte::E_HWTILEMODE)LatteReadCMD(); uint32 colorBufferWidth = LatteReadCMD(); uint32 colorBufferHeight = LatteReadCMD(); uint32 colorBufferPitch = LatteReadCMD(); uint32 colorBufferViewFirstSlice = LatteReadCMD(); uint32 colorBufferViewNumSlice = LatteReadCMD(); // depth buffer MPTR depthBufferMPTR = LatteReadCMD(); // physical address for depth buffer Latte::E_GX2SURFFMT depthBufferFormat = (Latte::E_GX2SURFFMT)LatteReadCMD(); Latte::E_HWTILEMODE depthBufferTileMode = (Latte::E_HWTILEMODE)LatteReadCMD(); uint32 depthBufferWidth = LatteReadCMD(); uint32 depthBufferHeight = LatteReadCMD(); uint32 depthBufferPitch = LatteReadCMD(); uint32 depthBufferViewFirstSlice = LatteReadCMD(); uint32 depthBufferViewNumSlice = LatteReadCMD(); float r = (float)LatteReadCMD() / 255.0f; float g = (float)LatteReadCMD() / 255.0f; float b = (float)LatteReadCMD() / 255.0f; float a = (float)LatteReadCMD() / 255.0f; float clearDepth; *(uint32*)&clearDepth = LatteReadCMD(); uint32 clearStencil = LatteReadCMD(); LatteRenderTarget_itHLEClearColorDepthStencil( clearMask, colorBufferMPTR, colorBufferFormat, colorBufferTilemode, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferViewFirstSlice, colorBufferViewNumSlice, depthBufferMPTR, depthBufferFormat, depthBufferTileMode, depthBufferWidth, depthBufferHeight, depthBufferPitch, depthBufferViewFirstSlice, depthBufferViewNumSlice, r, g, b, a, clearDepth, clearStencil); return cmd; } LatteCMDPtr LatteCP_itHLERequestSwapBuffers(LatteCMDPtr cmd, uint32 nWords) { catchOpenGLError(); cemu_assert_debug(nWords == 1); MPTR reserved1 = LatteReadCMD(); // request flip counter increase (will be increased on next flip) LatteGPUState.flipRequestCount.fetch_add(1); return cmd; } LatteCMDPtr LatteCP_itHLESwapScanBuffer(LatteCMDPtr cmd, uint32 nWords) { catchOpenGLError(); cemu_assert_debug(nWords == 1); MPTR reserved1 = LatteReadCMD(); // reserved LatteRenderTarget_itHLESwapScanBuffer(); return cmd; } LatteCMDPtr LatteCP_itHLEWaitForFlip(LatteCMDPtr cmd, uint32 nWords) { catchOpenGLError(); cemu_assert_debug(nWords == 1); MPTR reserved1 = LatteReadCMD(); // reserved // wait for flip uint32 currentFlipCount = LatteGPUState.flipCounter; while (true) { _mm_pause(); if (currentFlipCount != LatteGPUState.flipCounter) { break; } // check if any GPU events happened LatteTiming_HandleTimedVsync(); std::this_thread::yield(); } return cmd; } LatteCMDPtr LatteCP_itHLECopyColorBufferToScanBuffer(LatteCMDPtr cmd, uint32 nWords) { MPTR colorBufferPtr = LatteReadCMD(); // physical address uint32 colorBufferWidth = LatteReadCMD(); uint32 colorBufferHeight = LatteReadCMD(); uint32 colorBufferPitch = LatteReadCMD(); Latte::E_HWTILEMODE colorBufferTilemode = (Latte::E_HWTILEMODE)LatteReadCMD(); uint32 colorBufferSwizzle = LatteReadCMD(); uint32 colorBufferSliceIndex = LatteReadCMD(); uint32 colorBufferFormat = LatteReadCMD(); uint32 renderTarget = LatteReadCMD(); LatteRenderTarget_itHLECopyColorBufferToScanBuffer(colorBufferPtr, colorBufferWidth, colorBufferHeight, colorBufferSliceIndex, colorBufferFormat, colorBufferPitch, colorBufferTilemode, colorBufferSwizzle, renderTarget); return cmd; } void LatteCP_dumpCommandBufferError(LatteCMDPtr cmdStart, LatteCMDPtr cmdEnd, LatteCMDPtr cmdError) { cemuLog_log(LogType::Force, "Detected error in GPU command buffer"); cemuLog_log(LogType::Force, "Dumping contents and info"); cemuLog_log(LogType::Force, "Buffer 0x{0:08x} Size 0x{1:08x}", memory_getVirtualOffsetFromPointer(cmdStart), memory_getVirtualOffsetFromPointer(cmdEnd)); cemuLog_log(LogType::Force, "Error at 0x{0:08x}", memory_getVirtualOffsetFromPointer(cmdError)); for (LatteCMDPtr p = cmdStart; p < cmdEnd; p += 4) { if(cmdError >= p && cmdError < (p+4) ) cemuLog_log(LogType::Force, "0x{0:08x}: {1:08x} {2:08x} {3:08x} {4:08x} <<<<<", memory_getVirtualOffsetFromPointer(p), p[0], p[1], p[2], p[3]); else cemuLog_log(LogType::Force, "0x{0:08x}: {1:08x} {2:08x} {3:08x} {4:08x}", memory_getVirtualOffsetFromPointer(p), p[0], p[1], p[2], p[3]); } cemuLog_waitForFlush(); cemu_assert_debug(false); } // any drawcalls issued without changing textures, framebuffers, shader or other complex states can be done quickly without having to reinitialize the entire pipeline state // we implement this optimization by having a specialized version of LatteCP_processCommandBuffer, called right after drawcalls, which only implements commands that dont interfere with fast drawing. Other commands will cause this function to return to the complex and generic parser void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCtx) { cemu_assert_debug(drawPassCtx.isWithinDrawPass()); // quit early if there are parameters set which are generally incompatible with fast drawing if (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0) { drawPassCtx.endDrawPass(); return; } // check for other special states? while (true) { LatteCMDPtr cmd, cmdStart, cmdEnd; if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) { drawPassCtx.endDrawPass(); return; } while (cmd < cmdEnd) { LatteCMDPtr cmdBeforeCommand = cmd; uint32 itHeader = LatteReadCMD(); uint32 itHeaderType = (itHeader >> 30) & 3; if (itHeaderType == 3) { uint32 itCode = (itHeader >> 8) & 0xFF; uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; LatteCMDPtr cmdData = cmd; cmd += nWords; switch (itCode) { case IT_SET_RESOURCE: // attribute buffers, uniform buffers or texture units { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_RESOURCE>(cmdData, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd) { if ((registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7)) || (registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7)) || (registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7))) drawPassCtx.endDrawPass(); // texture updates end the current draw sequence else if (registerStart >= mmSQ_VTX_ATTRIBUTE_BLOCK_START && registerEnd <= mmSQ_VTX_ATTRIBUTE_BLOCK_END) drawPassCtx.notifyModifiedVertexBuffer(); else drawPassCtx.notifyModifiedUniformBuffer(); }); if (!drawPassCtx.isWithinDrawPass()) { drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); return; } break; } case IT_SET_ALU_CONST: // uniform register { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_ALU_CONST>(cmdData, nWords); break; } case IT_SET_CTL_CONST: { LatteCP_itSetRegistersGeneric<mmSQ_VTX_BASE_VTX_LOC>(cmdData, nWords); break; } case IT_SET_CONFIG_REG: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_CONFIG>(cmdData, nWords); break; } case IT_INDEX_TYPE: { LatteCP_itIndexType(cmdData, nWords); break; } case IT_NUM_INSTANCES: { LatteCP_itNumInstances(cmdData, nWords); break; } case IT_DRAW_INDEX_2: { LatteCP_itDrawIndex2(cmdData, nWords, drawPassCtx); break; } case IT_SET_CONTEXT_REG: { drawPassCtx.endDrawPass(); drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); return; } case IT_INDIRECT_BUFFER_PRIV: { drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); LatteCP_itIndirectBuffer(cmdData, nWords, drawPassCtx); if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) // switch to sub buffer cemu_assert_debug(false); //if (!drawPassCtx.isWithinDrawPass()) // return cmdData; break; } default: // unsupported command for fast draw drawPassCtx.endDrawPass(); drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); return; } } else if (itHeaderType == 2) { // filler packet } else { // unsupported command for fast draw drawPassCtx.endDrawPass(); drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd); return; } } } if (drawPassCtx.isWithinDrawPass()) drawPassCtx.endDrawPass(); } void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx) { while (true) { LatteCMDPtr cmd, cmdStart, cmdEnd; if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) break; uint32 itHeader; while (cmd < cmdEnd) { itHeader = LatteReadCMD(); uint32 itHeaderType = (itHeader >> 30) & 3; if (itHeaderType == 3) { uint32 itCode = (itHeader >> 8) & 0xFF; uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; LatteCMDPtr cmdData = cmd; cmd += nWords; switch (itCode) { case IT_SET_CONTEXT_REG: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_CONTEXT>(cmdData, nWords); } break; case IT_SET_RESOURCE: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_RESOURCE>(cmdData, nWords); } break; case IT_SET_ALU_CONST: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_ALU_CONST>(cmdData, nWords); } break; case IT_SET_CTL_CONST: { LatteCP_itSetRegistersGeneric<mmSQ_VTX_BASE_VTX_LOC>(cmdData, nWords); } break; case IT_SET_SAMPLER: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_SAMPLER>(cmdData, nWords); } break; case IT_SET_CONFIG_REG: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_CONFIG>(cmdData, nWords); } break; case IT_SET_LOOP_CONST: { // todo } break; case IT_SURFACE_SYNC: { LatteCP_itSurfaceSync(cmdData); } break; case IT_INDIRECT_BUFFER_PRIV: { drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); LatteCP_itIndirectBuffer(cmdData, nWords, drawPassCtx); if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) // switch to sub buffer cemu_assert_debug(false); } break; case IT_STRMOUT_BUFFER_UPDATE: { LatteCP_itStreamoutBufferUpdate(cmdData, nWords); } break; case IT_INDEX_TYPE: { LatteCP_itIndexType(cmdData, nWords); } break; case IT_NUM_INSTANCES: { LatteCP_itNumInstances(cmdData, nWords); } break; case IT_DRAW_INDEX_2: { drawPassCtx.beginDrawPass(); LatteCP_itDrawIndex2(cmdData, nWords, drawPassCtx); // enter fast draw mode drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); LatteCP_processCommandBuffer_continuousDrawPass(drawPassCtx); cemu_assert_debug(!drawPassCtx.isWithinDrawPass()); if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) return; } break; case IT_DRAW_INDEX_AUTO: { drawPassCtx.beginDrawPass(); LatteCP_itDrawIndexAuto(cmdData, nWords, drawPassCtx); // enter fast draw mode drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd); LatteCP_processCommandBuffer_continuousDrawPass(drawPassCtx); cemu_assert_debug(!drawPassCtx.isWithinDrawPass()); if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) return; } break; case IT_DRAW_INDEX_IMMD: { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); LatteCP_itDrawImmediate(cmdData, nWords, drawPassCtx); drawPassCtx.endDrawPass(); break; } case IT_WAIT_REG_MEM: { LatteCP_itWaitRegMem(cmdData, nWords); LatteTiming_HandleTimedVsync(); LatteAsyncCommands_checkAndExecute(); break; } case IT_MEM_WRITE: { LatteCP_itMemWrite(cmdData, nWords); break; } case IT_CONTEXT_CONTROL: { LatteCP_itContextControl(cmdData, nWords); break; } case IT_MEM_SEMAPHORE: { LatteCP_itMemSemaphore(cmdData, nWords); break; } case IT_LOAD_CONFIG_REG: { LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_CONFIG); break; } case IT_LOAD_CONTEXT_REG: { LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_CONTEXT); break; } case IT_LOAD_ALU_CONST: { LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_ALU_CONST); break; } case IT_LOAD_LOOP_CONST: { LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_LOOP_CONST); break; } case IT_LOAD_RESOURCE: { LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_RESOURCE); break; } case IT_LOAD_SAMPLER: { LatteCP_itLoadReg(cmdData, nWords, LATTE_REG_BASE_SAMPLER); break; } case IT_SET_PREDICATION: { LatteCP_itSetPredication(cmdData, nWords); break; } case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER: { LatteCP_itHLECopyColorBufferToScanBuffer(cmdData, nWords); break; } case IT_HLE_TRIGGER_SCANBUFFER_SWAP: { LatteCP_signalEnterWait(); LatteCP_itHLESwapScanBuffer(cmdData, nWords); break; } case IT_HLE_WAIT_FOR_FLIP: { LatteCP_signalEnterWait(); LatteCP_itHLEWaitForFlip(cmdData, nWords); break; } case IT_HLE_REQUEST_SWAP_BUFFERS: { LatteCP_itHLERequestSwapBuffers(cmdData, nWords); break; } case IT_HLE_CLEAR_COLOR_DEPTH_STENCIL: { LatteCP_itHLEClearColorDepthStencil(cmdData, nWords); break; } case IT_HLE_COPY_SURFACE_NEW: { LatteCP_itHLECopySurfaceNew(cmdData, nWords); break; } case IT_HLE_SAMPLE_TIMER: { LatteCP_itHLESampleTimer(cmdData, nWords); break; } case IT_HLE_SPECIAL_STATE: { LatteCP_itHLESpecialState(cmdData, nWords); break; } case IT_HLE_BEGIN_OCCLUSION_QUERY: { LatteCP_itHLEBeginOcclusionQuery(cmdData, nWords); break; } case IT_HLE_END_OCCLUSION_QUERY: { LatteCP_itHLEEndOcclusionQuery(cmdData, nWords); break; } case IT_HLE_BOTTOM_OF_PIPE_CB: { LatteCP_itHLEBottomOfPipeCB(cmdData, nWords); break; } case IT_HLE_SYNC_ASYNC_OPERATIONS: { LatteTextureReadback_UpdateFinishedTransfers(true); LatteQuery_UpdateFinishedQueriesForceFinishAll(); break; } default: debug_printf("Unhandled IT %02x\n", itCode); cemu_assert_debug(false); break; } } else if (itHeaderType == 2) { // filler packet // has no body } else if (itHeaderType == 0) { uint32 registerBase = (itHeader & 0xFFFF); uint32 registerCount = ((itHeader >> 16) & 0x3FFF) + 1; if (registerBase == 0x304A) { GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::TIMESTAMP_TOP); LatteSkipCMD(registerCount); } else if (registerBase == 0x304B) { LatteSkipCMD(registerCount); } else { LatteCP_dumpCommandBufferError(cmdStart, cmdEnd, cmd); cemu_assert_debug(false); } } else { debug_printf("invalid itHeaderType %08x\n", itHeaderType); LatteCP_dumpCommandBufferError(cmdStart, cmdEnd, cmd); cemu_assert_debug(false); } } cemu_assert_debug(cmd == cmdEnd); } } void LatteCP_ProcessRingbuffer() { sint32 timerRecheck = 0; // estimates how much CP processing time has elapsed based on the executed commands, if the value exceeds CP_TIMER_RECHECK then _handleTimers() is called uint32be tmpBuffer[128]; while (true) { uint32 itHeader = LatteCP_readU32Deprc(); uint32 itHeaderType = (itHeader >> 30) & 3; if (itHeaderType == 3) { uint32 itCode = (itHeader >> 8) & 0xFF; uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; cemu_assert(nWords < 128); for (sint32 i=0; i<nWords; i++) { uint32 word = LatteCP_readU32Deprc(); tmpBuffer[i] = word; } LatteCMDPtr cmd = (LatteCMDPtr)tmpBuffer; switch (itCode) { case IT_SURFACE_SYNC: { LatteCP_itSurfaceSync(cmd); timerRecheck += CP_TIMER_RECHECK / 512; } break; case IT_SET_CONTEXT_REG: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_CONTEXT>(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; } break; case IT_SET_RESOURCE: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_RESOURCE>(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; } break; case IT_SET_ALU_CONST: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_ALU_CONST>(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_SET_CTL_CONST: { LatteCP_itSetRegistersGeneric<mmSQ_VTX_BASE_VTX_LOC>(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_SET_SAMPLER: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_SAMPLER>(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_SET_CONFIG_REG: { LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_CONFIG>(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_INDIRECT_BUFFER_PRIV: { LatteCP_itIndirectBufferDepr(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_STRMOUT_BUFFER_UPDATE: { LatteCP_itStreamoutBufferUpdate(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_INDEX_TYPE: { LatteCP_itIndexType(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 1024; break; } case IT_NUM_INSTANCES: { LatteCP_itNumInstances(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 1024; break; } case IT_DRAW_INDEX_2: { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); LatteCP_itDrawIndex2(cmd, nWords, drawPassCtx); drawPassCtx.endDrawPass(); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_DRAW_INDEX_AUTO: { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); LatteCP_itDrawIndexAuto(cmd, nWords, drawPassCtx); drawPassCtx.endDrawPass(); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_DRAW_INDEX_IMMD: { DrawPassContext drawPassCtx; drawPassCtx.beginDrawPass(); LatteCP_itDrawImmediate(cmd, nWords, drawPassCtx); drawPassCtx.endDrawPass(); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_WAIT_REG_MEM: { LatteCP_itWaitRegMem(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 16; break; } case IT_MEM_WRITE: { LatteCP_itMemWrite(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 128; break; } case IT_CONTEXT_CONTROL: { LatteCP_itContextControl(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 128; break; } case IT_MEM_SEMAPHORE: { LatteCP_itMemSemaphore(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 128; break; } case IT_LOAD_CONFIG_REG: { LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONFIG); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_LOAD_CONTEXT_REG: { LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONTEXT); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_LOAD_ALU_CONST: { LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_ALU_CONST); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_LOAD_LOOP_CONST: { LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_LOOP_CONST); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_LOAD_RESOURCE: { LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_RESOURCE); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_LOAD_SAMPLER: { LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_SAMPLER); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_SET_LOOP_CONST: { // todo break; } case IT_SET_PREDICATION: { LatteCP_itSetPredication(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_EVENT_WRITE_EOP: { LatteCP_itEventWriteEOP(cmd, nWords); break; } case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER: { LatteCP_itHLECopyColorBufferToScanBuffer(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_HLE_TRIGGER_SCANBUFFER_SWAP: { LatteCP_signalEnterWait(); LatteCP_itHLESwapScanBuffer(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_HLE_WAIT_FOR_FLIP: { LatteCP_signalEnterWait(); LatteCP_itHLEWaitForFlip(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 1; break; } case IT_HLE_REQUEST_SWAP_BUFFERS: { LatteCP_itHLERequestSwapBuffers(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 32; break; } case IT_HLE_CLEAR_COLOR_DEPTH_STENCIL: { LatteCP_itHLEClearColorDepthStencil(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 128; break; } case IT_HLE_COPY_SURFACE_NEW: { LatteCP_itHLECopySurfaceNew(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 128; break; } case IT_HLE_SAMPLE_TIMER: { LatteCP_itHLESampleTimer(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_SPECIAL_STATE: { LatteCP_itHLESpecialState(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_BEGIN_OCCLUSION_QUERY: { LatteCP_itHLEBeginOcclusionQuery(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_END_OCCLUSION_QUERY: { LatteCP_itHLEEndOcclusionQuery(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 512; break; } case IT_HLE_BOTTOM_OF_PIPE_CB: { LatteCP_itHLEBottomOfPipeCB(cmd, nWords); break; } case IT_HLE_SYNC_ASYNC_OPERATIONS: { //LatteCP_skipWords<LatteCP_readU32Deprc>(nWords); LatteTextureReadback_UpdateFinishedTransfers(true); LatteQuery_UpdateFinishedQueriesForceFinishAll(); break; } default: cemu_assert_debug(false); } } else if (itHeaderType == 2) { // filler packet, skip this cemu_assert_debug(itHeader == 0x80000000); } else if (itHeaderType == 0) { uint32 registerBase = (itHeader & 0xFFFF); uint32 registerCount = ((itHeader >> 16) & 0x3FFF) + 1; if (registerBase == 0x304A) { GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::TIMESTAMP_TOP); LatteCP_skipWords<LatteCP_readU32Deprc>(registerCount); } else if (registerBase == 0x304B) { LatteCP_skipWords<LatteCP_readU32Deprc>(registerCount); } else { cemu_assert_debug(false); } } else { debug_printf("invalid itHeaderType %08x\n", itHeaderType); cemu_assert_debug(false); } if (timerRecheck >= CP_TIMER_RECHECK) { LatteTiming_HandleTimedVsync(); LatteAsyncCommands_checkAndExecute(); timerRecheck = 0; } } } #ifdef LATTE_CP_LOGGING void LatteCP_DebugPrintCmdBuffer(uint32be* bufferPtr, uint32 size) { uint32be* bufferPtrInitial = bufferPtr; uint32be* bufferPtrEnd = bufferPtr + (size/4); while (bufferPtr < bufferPtrEnd) { std::string strPrefix = fmt::format("[PM4 Buf {:08x} Offs {:04x}]", MEMPTR<void>(bufferPtr).GetMPTR(), (bufferPtr - bufferPtrInitial) * 4); uint32 itHeader = *bufferPtr; bufferPtr++; uint32 itHeaderType = (itHeader >> 30) & 3; if (itHeaderType == 3) { uint32 itCode = (itHeader >> 8) & 0xFF; uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; uint32be* cmdData = bufferPtr; bufferPtr += nWords; switch (itCode) { case IT_SURFACE_SYNC: { cemuLog_log(LogType::Force, "{} IT_SURFACE_SYNC", strPrefix); break; } case IT_SET_CONTEXT_REG: { std::string regVals; for (uint32 i = 0; i < std::min<uint32>(nWords - 1, 8); i++) regVals.append(fmt::format("{:08x} ", cmdData[1 + i].value())); cemuLog_log(LogType::Force, "{} IT_SET_CONTEXT_REG Reg {:04x} RegValues {}", strPrefix, cmdData[0].value(), regVals); } case IT_SET_RESOURCE: { std::string regVals; for (uint32 i = 0; i < std::min<uint32>(nWords - 1, 8); i++) regVals.append(fmt::format("{:08x} ", cmdData[1+i].value())); cemuLog_log(LogType::Force, "{} IT_SET_RESOURCE Reg {:04x} RegValues {}", strPrefix, cmdData[0].value(), regVals); break; } case IT_SET_ALU_CONST: { cemuLog_log(LogType::Force, "{} IT_SET_ALU_CONST", strPrefix); break; } case IT_SET_CTL_CONST: { cemuLog_log(LogType::Force, "{} IT_SET_CTL_CONST", strPrefix); break; } case IT_SET_SAMPLER: { cemuLog_log(LogType::Force, "{} IT_SET_SAMPLER", strPrefix); break; } case IT_SET_CONFIG_REG: { cemuLog_log(LogType::Force, "{} IT_SET_CONFIG_REG", strPrefix); break; } case IT_INDIRECT_BUFFER_PRIV: { if (nWords != 3) { cemuLog_log(LogType::Force, "{} IT_INDIRECT_BUFFER_PRIV (malformed!)", strPrefix); } else { uint32 physicalAddress = cmdData[0]; uint32 physicalAddressHigh = cmdData[1]; uint32 sizeInDWords = cmdData[2]; cemuLog_log(LogType::Force, "{} IT_INDIRECT_BUFFER_PRIV Addr {:08x} Size {:08x}", strPrefix, physicalAddress, sizeInDWords*4); LatteCP_DebugPrintCmdBuffer(MEMPTR<uint32be>(physicalAddress), sizeInDWords * 4); } break; } case IT_STRMOUT_BUFFER_UPDATE: { cemuLog_log(LogType::Force, "{} IT_STRMOUT_BUFFER_UPDATE", strPrefix); break; } case IT_INDEX_TYPE: { cemuLog_log(LogType::Force, "{} IT_INDEX_TYPE", strPrefix); break; } case IT_NUM_INSTANCES: { cemuLog_log(LogType::Force, "{} IT_NUM_INSTANCES", strPrefix); break; } case IT_DRAW_INDEX_2: { if (nWords != 5) { cemuLog_log(LogType::Force, "{} IT_DRAW_INDEX_2 (malformed!)", strPrefix); } else { uint32 ukn1 = cmdData[0]; MPTR physIndices = cmdData[1]; uint32 ukn2 = cmdData[2]; uint32 count = cmdData[3]; uint32 ukn3 = cmdData[4]; cemuLog_log(LogType::Force, "{} IT_DRAW_INDEX_2 | Count {}", strPrefix, count); } break; } case IT_DRAW_INDEX_AUTO: { cemuLog_log(LogType::Force, "{} IT_DRAW_INDEX_AUTO", strPrefix); break; } case IT_DRAW_INDEX_IMMD: { cemuLog_log(LogType::Force, "{} IT_DRAW_INDEX_IMMD", strPrefix); break; } case IT_WAIT_REG_MEM: { cemuLog_log(LogType::Force, "{} IT_WAIT_REG_MEM", strPrefix); break; } case IT_MEM_WRITE: { cemuLog_log(LogType::Force, "{} IT_MEM_WRITE", strPrefix); break; } case IT_CONTEXT_CONTROL: { cemuLog_log(LogType::Force, "{} IT_CONTEXT_CONTROL", strPrefix); break; } case IT_MEM_SEMAPHORE: { cemuLog_log(LogType::Force, "{} IT_MEM_SEMAPHORE", strPrefix); break; } case IT_LOAD_CONFIG_REG: { cemuLog_log(LogType::Force, "{} IT_LOAD_CONFIG_REG", strPrefix); break; } case IT_LOAD_CONTEXT_REG: { cemuLog_log(LogType::Force, "{} IT_LOAD_CONTEXT_REG", strPrefix); break; } case IT_LOAD_ALU_CONST: { cemuLog_log(LogType::Force, "{} IT_LOAD_ALU_CONST", strPrefix); break; } case IT_LOAD_LOOP_CONST: { cemuLog_log(LogType::Force, "{} IT_LOAD_LOOP_CONST", strPrefix); break; } case IT_LOAD_RESOURCE: { cemuLog_log(LogType::Force, "{} IT_LOAD_RESOURCE", strPrefix); break; } case IT_LOAD_SAMPLER: { cemuLog_log(LogType::Force, "{} IT_LOAD_SAMPLER", strPrefix); break; } case IT_SET_LOOP_CONST: { cemuLog_log(LogType::Force, "{} IT_SET_LOOP_CONST", strPrefix); break; } case IT_SET_PREDICATION: { cemuLog_log(LogType::Force, "{} IT_SET_PREDICATION", strPrefix); break; } case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER: { cemuLog_log(LogType::Force, "{} IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER", strPrefix); break; } case IT_HLE_TRIGGER_SCANBUFFER_SWAP: { cemuLog_log(LogType::Force, "{} IT_HLE_TRIGGER_SCANBUFFER_SWAP", strPrefix); break; } case IT_HLE_WAIT_FOR_FLIP: { cemuLog_log(LogType::Force, "{} IT_HLE_WAIT_FOR_FLIP", strPrefix); break; } case IT_HLE_REQUEST_SWAP_BUFFERS: { cemuLog_log(LogType::Force, "{} IT_HLE_REQUEST_SWAP_BUFFERS", strPrefix); break; } case IT_HLE_CLEAR_COLOR_DEPTH_STENCIL: { cemuLog_log(LogType::Force, "{} IT_HLE_CLEAR_COLOR_DEPTH_STENCIL", strPrefix); break; } case IT_HLE_COPY_SURFACE_NEW: { cemuLog_log(LogType::Force, "{} IT_HLE_COPY_SURFACE_NEW", strPrefix); break; } case IT_HLE_SAMPLE_TIMER: { cemuLog_log(LogType::Force, "{} IT_HLE_SAMPLE_TIMER", strPrefix); break; } case IT_HLE_SPECIAL_STATE: { cemuLog_log(LogType::Force, "{} IT_HLE_SPECIAL_STATE", strPrefix); break; } case IT_HLE_BEGIN_OCCLUSION_QUERY: { cemuLog_log(LogType::Force, "{} IT_HLE_BEGIN_OCCLUSION_QUERY", strPrefix); break; } case IT_HLE_END_OCCLUSION_QUERY: { cemuLog_log(LogType::Force, "{} IT_HLE_END_OCCLUSION_QUERY", strPrefix); break; } case IT_HLE_BOTTOM_OF_PIPE_CB: { cemuLog_log(LogType::Force, "{} IT_HLE_BOTTOM_OF_PIPE_CB", strPrefix); break; } case IT_HLE_SYNC_ASYNC_OPERATIONS: { cemuLog_log(LogType::Force, "{} IT_HLE_SYNC_ASYNC_OPERATIONS", strPrefix); break; } default: cemuLog_log(LogType::Force, "{} Unsupported operation code", strPrefix); return; } } else if (itHeaderType == 2) { // filler packet } else if (itHeaderType == 0) { uint32 registerBase = (itHeader & 0xFFFF); uint32 registerCount = ((itHeader >> 16) & 0x3FFF) + 1; LatteCP_skipWords<LatteCP_readU32Deprc>(registerCount); cemuLog_log(LogType::Force, "[LatteCP] itType=0 registerBase={:04x}", registerBase); } else { cemuLog_log(LogType::Force, "Invalid itHeaderType %08x\n", itHeaderType); return; } } } #endif ================================================ FILE: src/Cafe/HW/Latte/Core/LatteConst.h ================================================ #pragma once #include "Cafe/HW/Latte/ISA/LatteReg.h" // todo - this file contains legacy C-style defines, modernize and merge into LatteReg.h // GPU7/Latte hardware info #define LATTE_NUM_GPR 128 #define LATTE_NUM_STREAMOUT_BUFFER 4 #define LATTE_NUM_COLOR_TARGET 8 #define LATTE_NUM_MAX_TEX_UNITS 18 // number of available texture units per shader stage (this might be higher than 18? BotW is the only game which uses more than 16?) #define LATTE_NUM_MAX_UNIFORM_BUFFERS 16 // number of supported uniform buffer binding locations #define LATTE_VS_ATTRIBUTE_LIMIT 32 // todo: verify #define LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS 256 // should this be 128 since there are only 128 GPRs? #define LATTE_MAX_VERTEX_BUFFERS 16 // Cemu-specific constants #define LATTE_CEMU_PS_TEX_UNIT_BASE 0 #define LATTE_CEMU_VS_TEX_UNIT_BASE 32 #define LATTE_CEMU_GS_TEX_UNIT_BASE 64 // vertex formats #define FMT_INVALID 0x00 #define FMT_8 0x01 #define FMT_4_4 0x02 #define FMT_3_3_2 0x03 #define FMT_16 0x05 #define FMT_16_FLOAT 0x06 #define FMT_8_8 0x07 #define FMT_5_6_5 0x08 #define FMT_6_5_5 0x09 #define FMT_1_5_5_5 0x0A #define FMT_4_4_4_4 0x0B #define FMT_5_5_5_1 0x0C #define FMT_32 0x0D #define FMT_32_FLOAT 0x0E #define FMT_16_16 0x0F #define FMT_16_16_FLOAT 0x10 #define FMT_8_24 0x11 #define FMT_8_24_FLOAT 0x12 #define FMT_24_8 0x13 #define FMT_24_8_FLOAT 0x14 #define FMT_10_11_11 0x15 #define FMT_10_11_11_FLOAT 0x16 #define FMT_11_11_10 0x17 #define FMT_11_11_10_FLOAT 0x18 #define FMT_2_10_10_10 0x19 #define FMT_8_8_8_8 0x1A #define FMT_10_10_10_2 0x1B #define FMT_X24_8_32_FLOAT 0x1C #define FMT_32_32 0x1D #define FMT_32_32_FLOAT 0x1E #define FMT_16_16_16_16 0x1F #define FMT_16_16_16_16_FLOAT 0x20 #define FMT_32_32_32_32 0x22 #define FMT_32_32_32_32_FLOAT 0x23 #define FMT_1 0x25 #define FMT_GB_GR 0x27 #define FMT_BG_RG 0x28 #define FMT_32_AS_8 0x29 #define FMT_32_AS_8_8 0x2A #define FMT_5_9_9_9_SHAREDEXP 0x2B #define FMT_8_8_8 0x2C #define FMT_16_16_16 0x2D #define FMT_16_16_16_FLOAT 0x2E #define FMT_32_32_32 0x2F #define FMT_32_32_32_FLOAT 0x30 #define LATTE_NFA_2 2 #define LATTE_NFA_3 3 #define LATTE_VTX_UNSIGNED 0 #define LATTE_VTX_SIGNED 1 // OpenGL constants #define GLVENDOR_UNKNOWN (0) #define GLVENDOR_AMD (1) // AMD/ATI #define GLVENDOR_NVIDIA (2) #define GLVENDOR_INTEL (5) #define GLVENDOR_APPLE (6) // decompiler #define LATTE_DECOMPILER_DTYPE_UNDETERMINED (0) // data type is unknown #define LATTE_DECOMPILER_DTYPE_UNSIGNED_INT (1) // 32bit unsigned integer #define LATTE_DECOMPILER_DTYPE_SIGNED_INT (2) // 32bit signed integer #define LATTE_DECOMPILER_DTYPE_FLOAT (3) // 32bit IEEE float #define LATTE_DECOMPILER_UNIFORM_MODE_NONE (0) // no uniform access at all #define LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED (1) // use remapped uniform array #define LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE (2) // load full cfile (uniform registers) #define LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK (3) // load full uniform banks (uniform buffers) #define LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX (0xFF) #define LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION (0x40000000) // gl_FragCoord #define LATTE_DECOMPILER_SAMPLER_NONE (0xFF) using LattePrimitiveMode = Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE; using LatteIndexType = Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE; namespace LatteConst { enum class ShaderType : uint32 { Reserved = 0, // shaders for drawing FirstRender = 1, Vertex = 1, Pixel = 2, Geometry = 3, LastRender = 3, // compute shader Compute = 4, TotalCount = 5 }; enum class VertexFetchNFA { NUM_FORMAT_NORMALIZED, NUM_FORMAT_INT, NUM_FORMAT_SCALED, }; enum class VertexFetchEndianMode { SWAP_NONE = 0, // little endian SWAP_U16 = 1, // U16 big endian SWAP_U32 = 2, // U32 big endian // helper for GX2 API SWAP_DEFAULT = 3, }; enum class VertexFetchFormat : uint32 { // some formats are for texture fetches only VTX_FMT_INVALID = 0x00, VTX_FMT_8 = 0x01, VTX_FMT_8_8 = 0x07, VTX_FMT_8_8_8 = 0x2C, VTX_FMT_8_8_8_8 = 0x1A, VTX_FMT_32_32 = 0x1D, VTX_FMT_32_32_FLOAT = 0x1E, VTX_FMT_16_16_16 = 0x2D, VTX_FMT_16_16_16_FLOAT = 0x2E, VTX_FMT_32_32_32 = 0x2F, VTX_FMT_32_32_32_FLOAT = 0x30 }; enum class VertexFetchDstSel : uint8 { X = 0, Y = 1, Z = 2, W = 3, CONST_0F = 4, CONST_1F = 5, UNUSED = 6, MASKED = 7 }; // used in VTX_WORD0 enum VertexFetchType2 : uint8 { VERTEX_DATA = 0, INSTANCE_DATA = 1, NO_INDEX_OFFSET_DATA = 2, }; }; #define LATTE_MAX_REGISTER (0x10000) ================================================ FILE: src/Cafe/HW/Latte/Core/LatteDefaultShaders.cpp ================================================ #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteDefaultShaders.h" #include "util/helpers/StringBuf.h" LatteDefaultShader_t* _copyShader_depthToColor; LatteDefaultShader_t* _copyShader_colorToDepth; void LatteDefaultShader_pixelCopyShader_generateVSBody(StringBuf* vs) { vs->add("#version 420\r\n"); vs->add("out vec2 passUV;\r\n"); vs->add("uniform vec4 uf_vertexOffsets[4];\r\n"); vs->add("\r\n"); vs->add("void main(){\r\n"); vs->add("int vID = gl_VertexID;\r\n"); vs->add("passUV = uf_vertexOffsets[vID].zw;\r\n"); vs->add("gl_Position = vec4(uf_vertexOffsets[vID].xy, 0.0, 1.0);\r\n"); vs->add("}\r\n"); } GLuint gxShaderDepr_compileRaw(StringBuf* strSourceVS, StringBuf* strSourceFS); GLuint gxShaderDepr_compileRaw(const std::string& vertex_source, const std::string& fragment_source); LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_depthToColor() { if (_copyShader_depthToColor != 0) return _copyShader_depthToColor; catchOpenGLError(); LatteDefaultShader_t* defaultShader = (LatteDefaultShader_t*)malloc(sizeof(LatteDefaultShader_t)); memset(defaultShader, 0, sizeof(LatteDefaultShader_t)); StringBuf fCStr_vertexShader(1024 * 16); LatteDefaultShader_pixelCopyShader_generateVSBody(&fCStr_vertexShader); StringBuf fCStr_defaultFragShader(1024 * 16); fCStr_defaultFragShader.add("#version 420\r\n"); fCStr_defaultFragShader.add("in vec2 passUV;\r\n"); fCStr_defaultFragShader.add("uniform sampler2D textureSrc;\r\n"); fCStr_defaultFragShader.add("layout(location = 0) out vec4 colorOut0;\r\n"); fCStr_defaultFragShader.add("\r\n"); fCStr_defaultFragShader.add("void main(){\r\n"); fCStr_defaultFragShader.add("colorOut0 = vec4(texture(textureSrc, passUV).r,0.0,0.0,1.0);\r\n"); fCStr_defaultFragShader.add("}\r\n"); defaultShader->glProgamId = gxShaderDepr_compileRaw(&fCStr_vertexShader, &fCStr_defaultFragShader); catchOpenGLError(); defaultShader->copyShaderUniforms.uniformLoc_textureSrc = glGetUniformLocation(defaultShader->glProgamId, "textureSrc"); defaultShader->copyShaderUniforms.uniformLoc_vertexOffsets = glGetUniformLocation(defaultShader->glProgamId, "uf_vertexOffsets"); _copyShader_depthToColor = defaultShader; catchOpenGLError(); return defaultShader; } LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_colorToDepth() { if (_copyShader_colorToDepth != 0) return _copyShader_colorToDepth; catchOpenGLError(); LatteDefaultShader_t* defaultShader = (LatteDefaultShader_t*)malloc(sizeof(LatteDefaultShader_t)); memset(defaultShader, 0, sizeof(LatteDefaultShader_t)); StringBuf fCStr_vertexShader(1024 * 16); LatteDefaultShader_pixelCopyShader_generateVSBody(&fCStr_vertexShader); StringBuf fCStr_defaultFragShader(1024 * 16); fCStr_defaultFragShader.add("#version 420\r\n"); fCStr_defaultFragShader.add("in vec2 passUV;\r\n"); fCStr_defaultFragShader.add("uniform sampler2D textureSrc;\r\n"); fCStr_defaultFragShader.add("layout(location = 0) out vec4 colorOut0;\r\n"); fCStr_defaultFragShader.add("\r\n"); fCStr_defaultFragShader.add("void main(){\r\n"); fCStr_defaultFragShader.add("gl_FragDepth = texture(textureSrc, passUV).r;\r\n"); fCStr_defaultFragShader.add("}\r\n"); defaultShader->glProgamId = gxShaderDepr_compileRaw(&fCStr_vertexShader, &fCStr_defaultFragShader); defaultShader->copyShaderUniforms.uniformLoc_textureSrc = glGetUniformLocation(defaultShader->glProgamId, "textureSrc"); defaultShader->copyShaderUniforms.uniformLoc_vertexOffsets = glGetUniformLocation(defaultShader->glProgamId, "uf_vertexOffsets"); _copyShader_colorToDepth = defaultShader; catchOpenGLError(); return defaultShader; } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteDefaultShaders.h ================================================ typedef struct { GLuint glProgamId; struct { GLuint uniformLoc_textureSrc; GLuint uniformLoc_vertexOffsets; }copyShaderUniforms; }LatteDefaultShader_t; LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_depthToColor(); LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_colorToDepth(); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteDraw.h ================================================ #pragma once #include "Common/GLInclude/GLInclude.h" void LatteDraw_cleanupAfterFrame(); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteGSCopyShaderParser.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" void LatteGSCopyShaderParser_addFetchedParam(LatteParsedGSCopyShader* shaderContext, uint32 offset, uint32 gprIndex) { if( shaderContext->numParam >= GPU7_COPY_SHADER_MAX_PARAMS ) { debug_printf("Copy shader: Too many fetched parameters\n"); cemu_assert_suspicious(); return; } shaderContext->paramMapping[shaderContext->numParam].exportParam = 0xFF; shaderContext->paramMapping[shaderContext->numParam].offset = offset; shaderContext->paramMapping[shaderContext->numParam].gprIndex = gprIndex; shaderContext->numParam++; } void LatteGSCopyShaderParser_assignRegisterParameterOutput(LatteParsedGSCopyShader* shaderContext, uint32 gprIndex, uint32 exportType, uint32 exportParam) { // scan backwards to catch the most recently added entry in case a register has multiple entries for(sint32 i=shaderContext->numParam-1; i>=0; i--) { if( shaderContext->paramMapping[i].gprIndex == gprIndex ) { if( shaderContext->paramMapping[i].exportParam != 0xFF ) cemu_assert_debug(false); if( exportParam >= 0x100 ) cemu_assert_debug(false); shaderContext->paramMapping[i].exportType = (uint8)exportType; shaderContext->paramMapping[i].exportParam = (uint8)exportParam; return; } } cemu_assert_debug(false); // register is exported but never initialized? } void LatteGSCopyShaderParser_addStreamWrite(LatteParsedGSCopyShader* shaderContext, uint32 bufferIndex, uint32 exportSourceGPR, uint32 exportArrayBase, uint32 memWriteArraySize, uint32 memWriteCompMask) { // get info about current state of GPR for (sint32 i = shaderContext->numParam - 1; i >= 0; i--) { if (shaderContext->paramMapping[i].gprIndex == exportSourceGPR) { LatteGSCopyShaderStreamWrite_t streamWrite; streamWrite.bufferIndex = (uint8)bufferIndex; streamWrite.offset = shaderContext->paramMapping[i].offset; streamWrite.exportArrayBase = exportArrayBase; streamWrite.memWriteArraySize = memWriteArraySize; streamWrite.memWriteCompMask = memWriteCompMask; shaderContext->list_streamWrites.push_back(streamWrite); return; } } cemu_assert_debug(false); // GPR not initialized? } bool LatteGSCopyShaderParser_getExportTypeByOffset(LatteParsedGSCopyShader* shaderContext, uint32 offset, uint32* exportType, uint32* exportParam) { for(sint32 i=0; i<shaderContext->numParam; i++) { if( shaderContext->paramMapping[i].offset == offset ) { *exportType = shaderContext->paramMapping[i].exportType; *exportParam = shaderContext->paramMapping[i].exportParam; return true; } } return false; } bool LatteGSCopyShaderParser_parseClauseVtx(LatteParsedGSCopyShader* shaderContext, uint8* programData, uint32 programSize, uint32 addr, uint32 count) { for(uint32 i=0; i<count; i++) { uint32 instructionAddr = addr*2+i*4; uint32 word0 = *(uint32*)(programData+instructionAddr*4+0); uint32 word1 = *(uint32*)(programData+instructionAddr*4+4); uint32 word2 = *(uint32*)(programData+instructionAddr*4+8); uint32 word3 = *(uint32*)(programData+instructionAddr*4+12); uint32 inst0_4 = (word0>>0)&0x1F; if( inst0_4 == GPU7_TEX_INST_VFETCH ) { // data fetch uint32 fetchType = (word0>>5)&3; uint32 bufferId = (word0>>8)&0xFF; uint32 offset = (word2>>0)&0xFFFF; uint32 endianSwap = (word2>>16)&0x3; uint32 constNoStride = (word2>>18)&0x1; uint32 srcGpr = (word0>>16)&0x7F; uint32 srcRel = (word0>>23)&1; if( srcRel != 0 ) debugBreakpoint(); uint32 destGpr = (word1>>0)&0x7F; uint32 destRel = (word1>>7)&1; if( destRel != 0 ) debugBreakpoint(); uint32 dstSelX = (word1>>9)&0x7; uint32 dstSelY = (word1>>12)&0x7; uint32 dstSelZ = (word1>>15)&0x7; uint32 dstSelW = (word1>>18)&0x7; uint32 srcSelX = (word0>>24)&0x3; uint32 srcSelY = 0; uint32 srcSelZ = 0; uint32 srcSelW = 0; if( bufferId != 0x9F ) { debugBreakpoint(); // data not fetched from GS ring buffer return false; } if( endianSwap != 0 ) debugBreakpoint(); if( fetchType != 2 ) debugBreakpoint(); if( srcSelX != 0 || srcGpr != 0 ) debugBreakpoint(); if( dstSelX != 0 || dstSelY != 1 || dstSelZ != 2 || dstSelW != 3 ) debugBreakpoint(); // remember imported parameter LatteGSCopyShaderParser_addFetchedParam(shaderContext, offset, destGpr); } else { return false; } } return true; } LatteParsedGSCopyShader* LatteGSCopyShaderParser_parse(uint8* programData, uint32 programSize) { cemu_assert_debug((programSize & 3) == 0); LatteParsedGSCopyShader* shaderContext = new LatteParsedGSCopyShader(); shaderContext->numParam = 0; // parse control flow instructions for(uint32 i=0; i<programSize/8; i++) { uint32 cfWord0 = *(uint32*)(programData+i*8+0); uint32 cfWord1 = *(uint32*)(programData+i*8+4); uint32 cf_inst23_7 = (cfWord1>>23)&0x7F; // check the bigger opcode fields first if( cf_inst23_7 < 0x40 ) // at 0x40 the bits overlap with the ALU instruction encoding { bool isEndOfProgram = ((cfWord1>>21)&1)!=0; uint32 addr = cfWord0&0xFFFFFFFF; uint32 count = (cfWord1>>10)&7; if( ((cfWord1>>19)&1) != 0 ) count |= 0x8; count++; if( cf_inst23_7 == GPU7_CF_INST_CALL_FS ) { // nop } else if( cf_inst23_7 == GPU7_CF_INST_NOP ) { // nop if( ((cfWord1>>0)&7) != 0 ) debugBreakpoint(); // pop count is not zero, } else if( cf_inst23_7 == GPU7_CF_INST_EXPORT || cf_inst23_7 == GPU7_CF_INST_EXPORT_DONE ) { // export uint32 edType = (cfWord0>>13)&0x3; uint32 edIndexGpr = (cfWord0>>23)&0x7F; uint32 edRWRel = (cfWord0>>22)&1; if( edRWRel != 0 || edIndexGpr != 0 ) debugBreakpoint(); // set export component selection uint8 exportComponentSel[4]; exportComponentSel[0] = (cfWord1>>0)&0x7; exportComponentSel[1] = (cfWord1>>3)&0x7; exportComponentSel[2] = (cfWord1>>6)&0x7; exportComponentSel[3] = (cfWord1>>9)&0x7; // set export array base, index and burstcount (export field) uint32 exportArrayBase = (cfWord0>>0)&0x1FFF; uint32 exportBurstCount = (cfWord1>>17)&0xF; // set export source GPR and type uint32 exportSourceGPR = (cfWord0>>15)&0x7F; uint32 exportType = edType; if (exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION && exportComponentSel[0] == 4 && exportComponentSel[1] == 4 && exportComponentSel[2] == 4 && exportComponentSel[3] == 4) { // aka gl_Position = vec4(0.0) // this instruction form is generated when the original shader doesn't assign gl_Position a value? } else if (exportComponentSel[0] != 0 || exportComponentSel[1] != 1 || exportComponentSel[2] != 2 || exportComponentSel[3] != 3) { cemu_assert_debug(false); } else { // register as param for (uint32 f = 0; f < exportBurstCount + 1; f++) { LatteGSCopyShaderParser_assignRegisterParameterOutput(shaderContext, exportSourceGPR + f, exportType, exportArrayBase + f); } } } else if( cf_inst23_7 == GPU7_CF_INST_VTX ) { LatteGSCopyShaderParser_parseClauseVtx(shaderContext, programData, programSize, addr, count); } else if (cf_inst23_7 == GPU7_CF_INST_MEM_STREAM0_WRITE || cf_inst23_7 == GPU7_CF_INST_MEM_STREAM1_WRITE ) { // streamout uint32 bufferIndex; if (cf_inst23_7 == GPU7_CF_INST_MEM_STREAM0_WRITE) bufferIndex = 0; else if (cf_inst23_7 == GPU7_CF_INST_MEM_STREAM1_WRITE) bufferIndex = 1; else cemu_assert_debug(false); uint32 exportArrayBase = (cfWord0 >> 0) & 0x1FFF; uint32 memWriteArraySize = (cfWord1 >> 0) & 0xFFF; uint32 memWriteCompMask = (cfWord1 >> 12) & 0xF; uint32 exportSourceGPR = (cfWord0 >> 15) & 0x7F; LatteGSCopyShaderParser_addStreamWrite(shaderContext, bufferIndex, exportSourceGPR, exportArrayBase, memWriteArraySize, memWriteCompMask); } else { cemuLog_log(LogType::Force, "Copyshader: Unknown 23_7 clause 0x{:x} found", cf_inst23_7); cemu_assert_debug(false); } if( isEndOfProgram ) { break; } } else { // ALU clauses not supported debug_printf("Copyshader has ALU clause?\n"); cemu_assert_debug(false); delete shaderContext; return nullptr; } } // verify if all registers are exported for(sint32 i=0; i<shaderContext->numParam; i++) { if( shaderContext->paramMapping[i].exportParam == 0xFF ) debugBreakpoint(); } return shaderContext; } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteIndices.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Common/cpu_features.h" #if defined(ARCH_X86_64) && defined(__GNUC__) #include <immintrin.h> #elif defined(__aarch64__) #include <arm_neon.h> #endif struct { struct CacheEntry { // input data const void* lastPtr; uint32 lastCount; LattePrimitiveMode lastPrimitiveMode; LatteIndexType lastIndexType; uint64 lastUsed; // output uint32 indexMin; uint32 indexMax; Renderer::INDEX_TYPE renderIndexType; uint32 outputCount; Renderer::IndexAllocation indexAllocation; }; std::array<CacheEntry, 8> entry; uint64 currentUsageCounter{0}; }LatteIndexCache{}; void LatteIndices_invalidate(const void* memPtr, uint32 size) { for(auto& entry : LatteIndexCache.entry) { if (entry.lastPtr >= memPtr && (entry.lastPtr < ((uint8*)memPtr + size)) ) { if(entry.lastPtr != nullptr) g_renderer->indexData_releaseIndexMemory(entry.indexAllocation); entry.lastPtr = nullptr; entry.lastCount = 0; } } } void LatteIndices_invalidateAll() { for(auto& entry : LatteIndexCache.entry) { if (entry.lastPtr != nullptr) g_renderer->indexData_releaseIndexMemory(entry.indexAllocation); entry.lastPtr = nullptr; entry.lastCount = 0; } } uint64 LatteIndices_GetNextUsageIndex() { return LatteIndexCache.currentUsageCounter++; } uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, LatteIndexType indexType, uint32 count) { if (primitiveMode == LattePrimitiveMode::QUADS) { sint32 numQuads = count / 4; if (indexType == LatteIndexType::AUTO) { if(count <= 0xFFFF) return numQuads * 6 * sizeof(uint16); return numQuads * 6 * sizeof(uint32); } if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) return numQuads * 6 * sizeof(uint16); if (indexType == LatteIndexType::U32_BE || indexType == LatteIndexType::U32_LE) return numQuads * 6 * sizeof(uint32); cemu_assert_suspicious(); return 0; } else if (primitiveMode == LattePrimitiveMode::QUAD_STRIP) { if (count <= 3) { return 0; } sint32 numQuads = (count-2) / 2; if (indexType == LatteIndexType::AUTO) { if (count <= 0xFFFF) return numQuads * 6 * sizeof(uint16); return numQuads * 6 * sizeof(uint32); } if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) return numQuads * 6 * sizeof(uint16); if (indexType == LatteIndexType::U32_BE || indexType == LatteIndexType::U32_LE) return numQuads * 6 * sizeof(uint32); cemu_assert_suspicious(); return 0; } else if (primitiveMode == LattePrimitiveMode::LINE_LOOP) { count++; // one extra vertex to reconnect the LINE_STRIP to the beginning if (indexType == LatteIndexType::AUTO) { if (count <= 0xFFFF) return count * sizeof(uint16); return count * sizeof(uint32); } if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) return count * sizeof(uint16); if (indexType == LatteIndexType::U32_BE || indexType == LatteIndexType::U32_LE) return count * sizeof(uint32); cemu_assert_suspicious(); return 0; } else if (primitiveMode == LattePrimitiveMode::TRIANGLE_FAN && g_renderer->GetType() == RendererAPI::Metal) { if (indexType == LatteIndexType::AUTO) { if (count <= 0xFFFF) return count * sizeof(uint16); return count * sizeof(uint32); } if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) return count * sizeof(uint16); if (indexType == LatteIndexType::U32_BE || indexType == LatteIndexType::U32_LE) return count * sizeof(uint32); cemu_assert_suspicious(); return 0; } else if(indexType == LatteIndexType::AUTO) return 0; else if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) return count * sizeof(uint16); else if (indexType == LatteIndexType::U32_BE || indexType == LatteIndexType::U32_LE) return count * sizeof(uint32); return 0; } template<typename T> void LatteIndices_convertBE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { const betype<T>* src = (betype<T>*)indexDataInput; T* dst = (T*)indexDataOutput; for (uint32 i = 0; i < count; i++) { T v = *src; *dst = v; indexMin = std::min(indexMin, (uint32)v); indexMax = std::max(indexMax, (uint32)v); dst++; src++; } } template<typename T> void LatteIndices_convertLE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { const T* src = (T*)indexDataInput; T* dst = (T*)indexDataOutput; for (uint32 i = 0; i < count; i++) { T v = *src; *dst = v; indexMin = std::min(indexMin, (uint32)v); indexMax = std::max(indexMax, (uint32)v); dst++; src++; } } template<typename T> void LatteIndices_unpackQuadsAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { sint32 numQuads = count / 4; const betype<T>* src = (betype<T>*)indexDataInput; T* dst = (T*)indexDataOutput; for (sint32 i = 0; i < numQuads; i++) { T idx0 = src[0]; T idx1 = src[1]; T idx2 = src[2]; T idx3 = src[3]; indexMin = std::min(indexMin, (uint32)idx0); indexMax = std::max(indexMax, (uint32)idx0); indexMin = std::min(indexMin, (uint32)idx1); indexMax = std::max(indexMax, (uint32)idx1); indexMin = std::min(indexMin, (uint32)idx2); indexMax = std::max(indexMax, (uint32)idx2); indexMin = std::min(indexMin, (uint32)idx3); indexMax = std::max(indexMax, (uint32)idx3); dst[0] = idx0; dst[1] = idx1; dst[2] = idx2; dst[3] = idx0; dst[4] = idx2; dst[5] = idx3; src += 4; dst += 6; } } template<typename T> void LatteIndices_generateAutoQuadIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { sint32 numQuads = count / 4; const betype<T>* src = (betype<T>*)indexDataInput; T* dst = (T*)indexDataOutput; for (sint32 i = 0; i < numQuads; i++) { T idx0 = i * 4 + 0; T idx1 = i * 4 + 1; T idx2 = i * 4 + 2; T idx3 = i * 4 + 3; dst[0] = idx0; dst[1] = idx1; dst[2] = idx2; dst[3] = idx0; dst[4] = idx2; dst[5] = idx3; src += 4; dst += 6; } indexMin = 0; indexMax = std::max(count, 1u) - 1; } template<typename T> void LatteIndices_unpackQuadStripAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { if (count <= 3) return; sint32 numQuads = (count - 2) / 2; const betype<T>* src = (betype<T>*)indexDataInput; T* dst = (T*)indexDataOutput; for (sint32 i = 0; i < numQuads; i++) { T idx0 = src[0]; T idx1 = src[1]; T idx2 = src[2]; T idx3 = src[3]; indexMin = std::min(indexMin, (uint32)idx0); indexMax = std::max(indexMax, (uint32)idx0); indexMin = std::min(indexMin, (uint32)idx1); indexMax = std::max(indexMax, (uint32)idx1); indexMin = std::min(indexMin, (uint32)idx2); indexMax = std::max(indexMax, (uint32)idx2); indexMin = std::min(indexMin, (uint32)idx3); indexMax = std::max(indexMax, (uint32)idx3); dst[0] = idx0; dst[1] = idx1; dst[2] = idx2; dst[3] = idx2; dst[4] = idx1; dst[5] = idx3; src += 2; dst += 6; } } template<typename T> void LatteIndices_unpackLineLoopAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { if (count <= 0) return; const betype<T>* src = (betype<T>*)indexDataInput; T firstIndex = *src; T* dst = (T*)indexDataOutput; for (sint32 i = 0; i < (sint32)count; i++) { T idx = *src; indexMin = std::min(indexMin, (uint32)idx); indexMax = std::max(indexMax, (uint32)idx); *dst = idx; src++; dst++; } *dst = firstIndex; } template<typename T> void LatteIndices_generateAutoQuadStripIndices(void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { if (count <= 3) return; sint32 numQuads = (count - 2) / 2; T* dst = (T*)indexDataOutput; for (sint32 i = 0; i < numQuads; i++) { T idx0 = i * 2 + 0; T idx1 = i * 2 + 1; T idx2 = i * 2 + 2; T idx3 = i * 2 + 3; dst[0] = idx0; dst[1] = idx1; dst[2] = idx2; dst[3] = idx2; dst[4] = idx1; dst[5] = idx3; dst += 6; } indexMin = 0; indexMax = std::max(count, 1u) - 1; } template<typename T> void LatteIndices_generateAutoLineLoopIndices(void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { if (count == 0) return; T* dst = (T*)indexDataOutput; for (sint32 i = 0; i < (sint32)count; i++) { *dst = (T)i; dst++; } *dst = 0; dst++; indexMin = 0; indexMax = std::max(count, 1u) - 1; } template<typename T> void LatteIndices_unpackTriangleFanAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { const betype<T>* src = (betype<T>*)indexDataInput; T* dst = (T*)indexDataOutput; // TODO: check this for (sint32 i = 0; i < count; i++) { uint32 i0; if (i % 2 == 0) i0 = i / 2; else i0 = count - 1 - i / 2; T idx = src[i0]; indexMin = std::min(indexMin, (uint32)idx); indexMax = std::max(indexMax, (uint32)idx); dst[i] = idx; } } template<typename T> void LatteIndices_generateAutoTriangleFanIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { const betype<T>* src = (betype<T>*)indexDataInput; T* dst = (T*)indexDataOutput; for (sint32 i = 0; i < count; i++) { T idx = i; if (idx % 2 == 0) idx = idx / 2; else idx = count - 1 - idx / 2; dst[i] = idx; } indexMin = 0; indexMax = std::max(count, 1u) - 1; } #if defined(ARCH_X86_64) ATTRIBUTE_AVX2 void LatteIndices_fastConvertU16_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { // using AVX + AVX2 we can process 16 indices at a time const uint16* indicesU16BE = (const uint16*)indexDataInput; uint16* indexOutput = (uint16*)indexDataOutput; sint32 count16 = count >> 4; sint32 countRemaining = count & 15; if (count16) { __m256i mMin = _mm256_set_epi16((sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF); __m256i mMax = _mm256_set_epi16(0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000); __m256i mShuffle16Swap = _mm256_set_epi8(30, 31, 28, 29, 26, 27, 24, 25, 22, 23, 20, 21, 18, 19, 16, 17, 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); do { __m256i mIndexData = _mm256_loadu_si256((const __m256i*)indicesU16BE); indicesU16BE += 16; _mm_prefetch((const char*)indicesU16BE, _MM_HINT_T0); // endian swap mIndexData = _mm256_shuffle_epi8(mIndexData, mShuffle16Swap); _mm256_store_si256((__m256i*)indexOutput, mIndexData); mMin = _mm256_min_epu16(mIndexData, mMin); mMax = _mm256_max_epu16(mIndexData, mMax); indexOutput += 16; } while (--count16); // fold 32 to 16 byte mMin = _mm256_min_epu16(mMin, _mm256_permute2x128_si256(mMin, mMin, 1)); mMax = _mm256_max_epu16(mMax, _mm256_permute2x128_si256(mMax, mMax, 1)); // fold 16 to 8 byte mMin = _mm256_min_epu16(mMin, _mm256_shuffle_epi32(mMin, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); mMax = _mm256_max_epu16(mMax, _mm256_shuffle_epi32(mMax, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); uint16* mMinU16 = (uint16*)&mMin; uint16* mMaxU16 = (uint16*)&mMax; indexMin = std::min(indexMin, (uint32)mMinU16[0]); indexMin = std::min(indexMin, (uint32)mMinU16[1]); indexMin = std::min(indexMin, (uint32)mMinU16[2]); indexMin = std::min(indexMin, (uint32)mMinU16[3]); indexMax = std::max(indexMax, (uint32)mMaxU16[0]); indexMax = std::max(indexMax, (uint32)mMaxU16[1]); indexMax = std::max(indexMax, (uint32)mMaxU16[2]); indexMax = std::max(indexMax, (uint32)mMaxU16[3]); } // process remaining indices uint32 _minIndex = 0xFFFFFFFF; uint32 _maxIndex = 0; for (sint32 i = countRemaining; (--i) >= 0;) { uint16 idx = _swapEndianU16(*indicesU16BE); *indexOutput = idx; indexOutput++; indicesU16BE++; _maxIndex = std::max(_maxIndex, (uint32)idx); _minIndex = std::min(_minIndex, (uint32)idx); } // update min/max indexMax = std::max(indexMax, _maxIndex); indexMin = std::min(indexMin, _minIndex); } ATTRIBUTE_SSE41 void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { // SSSE3 & SSE4.1 optimized decoding const uint16* indicesU16BE = (const uint16*)indexDataInput; uint16* indexOutput = (uint16*)indexDataOutput; sint32 count8 = count >> 3; sint32 countRemaining = count & 7; if (count8) { __m128i mMin = _mm_set_epi16((short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF); __m128i mMax = _mm_set_epi16(0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000); __m128i mTemp; __m128i* mRawIndices = (__m128i*)indicesU16BE; indicesU16BE += count8 * 8; __m128i* mOutputIndices = (__m128i*)indexOutput; indexOutput += count8 * 8; __m128i shufmask = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); while (count8--) { mTemp = _mm_loadu_si128(mRawIndices); mRawIndices++; mTemp = _mm_shuffle_epi8(mTemp, shufmask); mMin = _mm_min_epu16(mMin, mTemp); mMax = _mm_max_epu16(mMax, mTemp); _mm_store_si128(mOutputIndices, mTemp); mOutputIndices++; } uint16* mMaxU16 = (uint16*)&mMax; uint16* mMinU16 = (uint16*)&mMin; indexMax = std::max(indexMax, (uint32)mMaxU16[0]); indexMax = std::max(indexMax, (uint32)mMaxU16[1]); indexMax = std::max(indexMax, (uint32)mMaxU16[2]); indexMax = std::max(indexMax, (uint32)mMaxU16[3]); indexMax = std::max(indexMax, (uint32)mMaxU16[4]); indexMax = std::max(indexMax, (uint32)mMaxU16[5]); indexMax = std::max(indexMax, (uint32)mMaxU16[6]); indexMax = std::max(indexMax, (uint32)mMaxU16[7]); indexMin = std::min(indexMin, (uint32)mMinU16[0]); indexMin = std::min(indexMin, (uint32)mMinU16[1]); indexMin = std::min(indexMin, (uint32)mMinU16[2]); indexMin = std::min(indexMin, (uint32)mMinU16[3]); indexMin = std::min(indexMin, (uint32)mMinU16[4]); indexMin = std::min(indexMin, (uint32)mMinU16[5]); indexMin = std::min(indexMin, (uint32)mMinU16[6]); indexMin = std::min(indexMin, (uint32)mMinU16[7]); } uint32 _minIndex = 0xFFFFFFFF; uint32 _maxIndex = 0; for (sint32 i = countRemaining; (--i) >= 0;) { uint16 idx = _swapEndianU16(*indicesU16BE); *indexOutput = idx; indexOutput++; indicesU16BE++; _maxIndex = std::max(_maxIndex, (uint32)idx); _minIndex = std::min(_minIndex, (uint32)idx); } indexMax = std::max(indexMax, _maxIndex); indexMin = std::min(indexMin, _minIndex); } ATTRIBUTE_AVX2 void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { // using AVX + AVX2 we can process 8 indices at a time const uint32* indicesU32BE = (const uint32*)indexDataInput; uint32* indexOutput = (uint32*)indexDataOutput; sint32 count8 = count >> 3; sint32 countRemaining = count & 7; if (count8) { __m256i mMin = _mm256_set_epi32((sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF); __m256i mMax = _mm256_set_epi32(0, 0, 0, 0, 0, 0, 0, 0); __m256i mShuffle32Swap = _mm256_set_epi8(28,29,30,31, 24,25,26,27, 20,21,22,23, 16,17,18,19, 12,13,14,15, 8,9,10,11, 4,5,6,7, 0,1,2,3); // unaligned do { __m256i mIndexData = _mm256_loadu_si256((const __m256i*)indicesU32BE); indicesU32BE += 8; _mm_prefetch((const char*)indicesU32BE, _MM_HINT_T0); // endian swap mIndexData = _mm256_shuffle_epi8(mIndexData, mShuffle32Swap); _mm256_store_si256((__m256i*)indexOutput, mIndexData); mMin = _mm256_min_epu32(mIndexData, mMin); mMax = _mm256_max_epu32(mIndexData, mMax); indexOutput += 8; } while (--count8); // fold 32 to 16 byte mMin = _mm256_min_epu32(mMin, _mm256_permute2x128_si256(mMin, mMin, 1)); mMax = _mm256_max_epu32(mMax, _mm256_permute2x128_si256(mMax, mMax, 1)); // fold 16 to 8 byte mMin = _mm256_min_epu32(mMin, _mm256_shuffle_epi32(mMin, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); mMax = _mm256_max_epu32(mMax, _mm256_shuffle_epi32(mMax, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); uint32* mMinU32 = (uint32*)&mMin; uint32* mMaxU32 = (uint32*)&mMax; indexMin = std::min(indexMin, (uint32)mMinU32[0]); indexMin = std::min(indexMin, (uint32)mMinU32[1]); indexMax = std::max(indexMax, (uint32)mMaxU32[0]); indexMax = std::max(indexMax, (uint32)mMaxU32[1]); } // process remaining indices uint32 _minIndex = 0xFFFFFFFF; uint32 _maxIndex = 0; for (sint32 i = countRemaining; (--i) >= 0;) { uint32 idx = _swapEndianU32(*indicesU32BE); *indexOutput = idx; indexOutput++; indicesU32BE++; _maxIndex = std::max(_maxIndex, (uint32)idx); _minIndex = std::min(_minIndex, (uint32)idx); } // update min/max indexMax = std::max(indexMax, _maxIndex); indexMin = std::min(indexMin, _minIndex); } #elif defined(__aarch64__) void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { const uint16* indicesU16BE = (const uint16*)indexDataInput; uint16* indexOutput = (uint16*)indexDataOutput; sint32 count8 = count >> 3; sint32 countRemaining = count & 7; if (count8) { uint16x8_t mMin = vdupq_n_u16(0xFFFF); uint16x8_t mMax = vdupq_n_u16(0x0000); uint16x8_t mTemp; uint16x8_t* mRawIndices = (uint16x8_t*) indicesU16BE; indicesU16BE += count8 * 8; uint16x8_t* mOutputIndices = (uint16x8_t*) indexOutput; indexOutput += count8 * 8; while (count8--) { mTemp = vld1q_u16((uint16*)mRawIndices); mRawIndices++; mTemp = vrev16q_u8(mTemp); mMin = vminq_u16(mMin, mTemp); mMax = vmaxq_u16(mMax, mTemp); vst1q_u16((uint16*)mOutputIndices, mTemp); mOutputIndices++; } uint16* mMaxU16 = (uint16*)&mMax; uint16* mMinU16 = (uint16*)&mMin; for (int i = 0; i < 8; ++i) { indexMax = std::max(indexMax, (uint32)mMaxU16[i]); indexMin = std::min(indexMin, (uint32)mMinU16[i]); } } // process remaining indices uint32 _minIndex = 0xFFFFFFFF; uint32 _maxIndex = 0; for (sint32 i = countRemaining; (--i) >= 0;) { uint16 idx = _swapEndianU16(*indicesU16BE); *indexOutput = idx; indexOutput++; indicesU16BE++; _maxIndex = std::max(_maxIndex, (uint32)idx); _minIndex = std::min(_minIndex, (uint32)idx); } // update min/max indexMax = std::max(indexMax, _maxIndex); indexMin = std::min(indexMin, _minIndex); } void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) { const uint32* indicesU32BE = (const uint32*)indexDataInput; uint32* indexOutput = (uint32*)indexDataOutput; sint32 count8 = count >> 2; sint32 countRemaining = count & 3; if (count8) { uint32x4_t mMin = vdupq_n_u32(0xFFFFFFFF); uint32x4_t mMax = vdupq_n_u32(0x00000000); uint32x4_t mTemp; uint32x4_t* mRawIndices = (uint32x4_t*) indicesU32BE; indicesU32BE += count8 * 4; uint32x4_t* mOutputIndices = (uint32x4_t*) indexOutput; indexOutput += count8 * 4; while (count8--) { mTemp = vld1q_u32((uint32*)mRawIndices); mRawIndices++; mTemp = vrev32q_u8(mTemp); mMin = vminq_u32(mMin, mTemp); mMax = vmaxq_u32(mMax, mTemp); vst1q_u32((uint32*)mOutputIndices, mTemp); mOutputIndices++; } uint32* mMaxU32 = (uint32*)&mMax; uint32* mMinU32 = (uint32*)&mMin; for (int i = 0; i < 4; ++i) { indexMax = std::max(indexMax, mMaxU32[i]); indexMin = std::min(indexMin, mMinU32[i]); } } // process remaining indices uint32 _minIndex = 0xFFFFFFFF; uint32 _maxIndex = 0; for (sint32 i = countRemaining; (--i) >= 0;) { uint32 idx = _swapEndianU32(*indicesU32BE); *indexOutput = idx; indexOutput++; indicesU32BE++; _maxIndex = std::max(_maxIndex, idx); _minIndex = std::min(_minIndex, idx); } // update min/max indexMax = std::max(indexMax, _maxIndex); indexMin = std::min(indexMin, _minIndex); } #endif template<typename T> void _LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, uint32 count, uint32 primitiveRestartIndex, uint32& indexMin, uint32& indexMax) { cemu_assert_debug(count != 0); const betype<T>* idxPtrT = (betype<T>*)indexData; T _indexMin = *idxPtrT; T _indexMax = *idxPtrT; cemu_assert_debug(primitiveRestartIndex <= std::numeric_limits<T>::max()); T restartIndexT = (T)primitiveRestartIndex; while (count) { T idx = *idxPtrT; if (idx != restartIndexT) { _indexMin = std::min(_indexMin, idx); _indexMax = std::max(_indexMax, idx); } idxPtrT++; count--; } indexMin = _indexMin; indexMax = _indexMax; } // calculate min and max index while taking primitive restart into account // fallback implementation in case the fast path gives us invalid results void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIndexType indexType, uint32 count, uint32& indexMin, uint32& indexMax) { if (count == 0) { indexMin = 0; indexMax = 0; return; } uint32 primitiveRestartIndex = LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.get_RESTART_INDEX(); if (indexType == LatteIndexType::U16_BE) { _LatteIndices_alternativeCalculateIndexMinMax<uint16>(indexData, count, primitiveRestartIndex, indexMin, indexMax); } else if (indexType == LatteIndexType::U32_BE) { _LatteIndices_alternativeCalculateIndexMinMax<uint32>(indexData, count, primitiveRestartIndex, indexMin, indexMax); } else { cemu_assert_debug(false); } } void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation) { // what this should do: // [x] use fast SIMD-based index decoding // [x] unpack QUAD indices to triangle indices // [x] calculate min and max index, be careful about primitive restart index // [x] decode data directly into coherent memory buffer? // [ ] better cache implementation, allow to cache across frames // reuse from cache if data didn't change auto cacheEntry = std::find_if(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [indexData, count, primitiveMode, indexType](const auto& entry) { return entry.lastPtr == indexData && entry.lastCount == count && entry.lastPrimitiveMode == primitiveMode && entry.lastIndexType == indexType; }); if (cacheEntry != LatteIndexCache.entry.end()) { indexMin = cacheEntry->indexMin; indexMax = cacheEntry->indexMax; renderIndexType = cacheEntry->renderIndexType; outputCount = cacheEntry->outputCount; indexAllocation = cacheEntry->indexAllocation; cacheEntry->lastUsed = LatteIndices_GetNextUsageIndex(); return; } outputCount = 0; if (indexType == LatteIndexType::AUTO) renderIndexType = Renderer::INDEX_TYPE::NONE; else if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) renderIndexType = Renderer::INDEX_TYPE::U16; else if (indexType == LatteIndexType::U32_BE) renderIndexType = Renderer::INDEX_TYPE::U32; else cemu_assert_debug(false); uint32 primitiveRestartIndex = LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.get_RESTART_INDEX(); // calculate index output size uint32 indexOutputSize = LatteIndices_calculateIndexOutputSize(primitiveMode, indexType, count); if (indexOutputSize == 0) { outputCount = count; indexMin = 0; indexMax = std::max(count, 1u)-1; renderIndexType = Renderer::INDEX_TYPE::NONE; indexAllocation = {}; return; // no indices } // query index buffer from renderer indexAllocation = g_renderer->indexData_reserveIndexMemory(indexOutputSize); void* indexOutputPtr = indexAllocation.mem; // decode indices indexMin = std::numeric_limits<uint32>::max(); indexMax = std::numeric_limits<uint32>::min(); if (primitiveMode == LattePrimitiveMode::QUADS) { // unpack quads into triangles if (indexType == LatteIndexType::AUTO) { if (count <= 0xFFFF) { LatteIndices_generateAutoQuadIndices<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); renderIndexType = Renderer::INDEX_TYPE::U16; } else { LatteIndices_generateAutoQuadIndices<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); renderIndexType = Renderer::INDEX_TYPE::U32; } } else if (indexType == LatteIndexType::U16_BE) LatteIndices_unpackQuadsAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); else if (indexType == LatteIndexType::U32_BE) LatteIndices_unpackQuadsAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); else cemu_assert_debug(false); outputCount = count / 4 * 6; } else if (primitiveMode == LattePrimitiveMode::QUAD_STRIP) { // unpack quad strip into triangles if (indexType == LatteIndexType::AUTO) { if (count <= 0xFFFF) { LatteIndices_generateAutoQuadStripIndices<uint16>(indexOutputPtr, count, indexMin, indexMax); renderIndexType = Renderer::INDEX_TYPE::U16; } else { LatteIndices_generateAutoQuadStripIndices<uint32>(indexOutputPtr, count, indexMin, indexMax); renderIndexType = Renderer::INDEX_TYPE::U32; } } else if (indexType == LatteIndexType::U16_BE) LatteIndices_unpackQuadStripAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); else if (indexType == LatteIndexType::U32_BE) LatteIndices_unpackQuadStripAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); else cemu_assert_debug(false); if (count >= 2) outputCount = (count - 2) / 2 * 6; else outputCount = 0; } else if (primitiveMode == LattePrimitiveMode::LINE_LOOP) { // unpack line loop into line strip with extra reconnecting vertex if (indexType == LatteIndexType::AUTO) { if (count <= 0xFFFF) { LatteIndices_generateAutoLineLoopIndices<uint16>(indexOutputPtr, count, indexMin, indexMax); renderIndexType = Renderer::INDEX_TYPE::U16; } else { LatteIndices_generateAutoLineLoopIndices<uint32>(indexOutputPtr, count, indexMin, indexMax); renderIndexType = Renderer::INDEX_TYPE::U32; } } else if (indexType == LatteIndexType::U16_BE) LatteIndices_unpackLineLoopAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); else if (indexType == LatteIndexType::U32_BE) LatteIndices_unpackLineLoopAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); else cemu_assert_debug(false); outputCount = count + 1; } else if (primitiveMode == LattePrimitiveMode::TRIANGLE_FAN && g_renderer->GetType() == RendererAPI::Metal) { if (indexType == LatteIndexType::AUTO) { if (count <= 0xFFFF) { LatteIndices_generateAutoTriangleFanIndices<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); renderIndexType = Renderer::INDEX_TYPE::U16; } else { LatteIndices_generateAutoTriangleFanIndices<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); renderIndexType = Renderer::INDEX_TYPE::U32; } } else if (indexType == LatteIndexType::U16_BE) LatteIndices_unpackTriangleFanAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); else if (indexType == LatteIndexType::U32_BE) LatteIndices_unpackTriangleFanAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); else cemu_assert_debug(false); outputCount = count; } else { if (indexType == LatteIndexType::U16_BE) { #if defined(ARCH_X86_64) if (g_CPUFeatures.x86.avx2) LatteIndices_fastConvertU16_AVX2(indexData, indexOutputPtr, count, indexMin, indexMax); else if (g_CPUFeatures.x86.sse4_1 && g_CPUFeatures.x86.ssse3) LatteIndices_fastConvertU16_SSE41(indexData, indexOutputPtr, count, indexMin, indexMax); else LatteIndices_convertBE<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); #elif defined(__aarch64__) LatteIndices_fastConvertU16_NEON(indexData, indexOutputPtr, count, indexMin, indexMax); #else LatteIndices_convertBE<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); #endif } else if (indexType == LatteIndexType::U32_BE) { #if defined(ARCH_X86_64) if (g_CPUFeatures.x86.avx2) LatteIndices_fastConvertU32_AVX2(indexData, indexOutputPtr, count, indexMin, indexMax); else LatteIndices_convertBE<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); #elif defined(__aarch64__) LatteIndices_fastConvertU32_NEON(indexData, indexOutputPtr, count, indexMin, indexMax); #else LatteIndices_convertBE<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); #endif } else if (indexType == LatteIndexType::U16_LE) { LatteIndices_convertLE<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); } else if (indexType == LatteIndexType::U32_LE) { LatteIndices_convertLE<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); } else cemu_assert_debug(false); outputCount = count; } // the above algorithms use a simplistic approach to get indexMin/indexMax // here we make sure primitive restart indices dont influence the index range if (primitiveRestartIndex == indexMin || primitiveRestartIndex == indexMax) { // recalculate index range but filter out primitive restart index LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax); } g_renderer->indexData_uploadIndexMemory(indexAllocation); performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += indexOutputSize; // get least recently used cache entry auto lruEntry = std::min_element(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [](const auto& a, const auto& b) { return a.lastUsed < b.lastUsed; }); // invalidate previous allocation if(lruEntry->lastPtr != nullptr) g_renderer->indexData_releaseIndexMemory(lruEntry->indexAllocation); // update cache lruEntry->lastPtr = indexData; lruEntry->lastCount = count; lruEntry->lastPrimitiveMode = primitiveMode; lruEntry->lastIndexType = indexType; lruEntry->indexMin = indexMin; lruEntry->indexMax = indexMax; lruEntry->renderIndexType = renderIndexType; lruEntry->outputCount = outputCount; lruEntry->indexAllocation = indexAllocation; lruEntry->lastUsed = LatteIndices_GetNextUsageIndex(); } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteIndices.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" void LatteIndices_invalidate(const void* memPtr, uint32 size); void LatteIndices_invalidateAll(); void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteOverlay.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "WindowSystem.h" #include "config/CemuConfig.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "config/ActiveSettings.h" #include <imgui.h> #include "resource/IconsFontAwesome5.h" #include "imgui/imgui_extension.h" #include "input/InputManager.h" #include "util/SystemInfo/SystemInfo.h" #include <cinttypes> struct OverlayStats { OverlayStats() {}; int processor_count = 1; ProcessorTime processor_time_cemu; std::vector<ProcessorTime> processor_times; double fps{}; uint32 draw_calls_per_frame{}; uint32 fast_draw_calls_per_frame{}; float cpu_usage{}; // cemu cpu usage in % std::vector<float> cpu_per_core; // global cpu usage in % per core uint32 ram_usage{}; // ram usage in MB int vramUsage{}, vramTotal{}; // vram usage in mb } g_state{}; extern std::atomic_int g_compiled_shaders_total; extern std::atomic_int g_compiled_shaders_async; std::atomic_int g_compiling_pipelines; std::atomic_int g_compiling_pipelines_async; std::atomic_uint64_t g_compiling_pipelines_syncTimeSum; extern std::mutex g_friend_notification_mutex; extern std::vector< std::pair<std::string, int> > g_friend_notifications; std::mutex g_notification_mutex; std::vector< std::pair<std::string, int> > g_notifications; void LatteOverlay_pushNotification(const std::string& text, sint32 duration) { std::unique_lock lock(g_notification_mutex); g_notifications.emplace_back(text, duration); } struct OverlayList { std::wstring text; float pos_x = 0; float pos_y = 0; float width; OverlayList(std::wstring text, float width) : text(std::move(text)), width(width) {} }; const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; const float kBackgroundAlpha = 0.65f; void LatteOverlay_renderOverlay(ImVec2& position, ImVec2& pivot, sint32 direction, float fontSize, bool pad) { auto& config = GetConfig(); const auto font = ImGui_GetFont(fontSize); ImGui::PushFont(font); const ImVec4 color = ImGui::ColorConvertU32ToFloat4(config.overlay.text_color); ImGui::PushStyleColor(ImGuiCol_Text, color); // stats overlay if (config.overlay.fps || config.overlay.drawcalls || config.overlay.cpu_usage || config.overlay.cpu_per_core_usage || config.overlay.ram_usage) { ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); if (ImGui::Begin("Stats overlay", nullptr, kPopupFlags)) { if (config.overlay.fps) ImGui::Text("FPS: %.2lf", g_state.fps); if (config.overlay.drawcalls) ImGui::Text("Draws/f: %d (fast: %d)", g_state.draw_calls_per_frame, g_state.fast_draw_calls_per_frame); if (config.overlay.cpu_usage) ImGui::Text("CPU: %.2lf%%", g_state.cpu_usage); if (config.overlay.cpu_per_core_usage) { for (sint32 i = 0; i < g_state.processor_count; ++i) { ImGui::Text("CPU #%d: %.2lf%%", i + 1, g_state.cpu_per_core[i]); } } if (config.overlay.ram_usage) ImGui::Text("RAM: %dMB", g_state.ram_usage); if(config.overlay.vram_usage && g_state.vramUsage != -1 && g_state.vramTotal != -1) ImGui::Text("VRAM: %dMB / %dMB", g_state.vramUsage, g_state.vramTotal); if (config.overlay.debug) { // general debug info ImGui::Text("--- Debug info ---"); ImGui::Text("IndexUploadPerFrame: %dKB", (performanceMonitor.stats.indexDataUploadPerFrame+1023)/1024); // backend specific info g_renderer->AppendOverlayDebugInfo(); } position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; } ImGui::End(); } ImGui::PopStyleColor(); ImGui::PopFont(); } void LatteOverlay_RenderNotifications(ImVec2& position, ImVec2& pivot, sint32 direction, float fontSize, bool pad) { auto& config = GetConfig(); const auto font = ImGui_GetFont(fontSize); ImGui::PushFont(font); const ImVec4 color = ImGui::ColorConvertU32ToFloat4(config.notification.text_color); ImGui::PushStyleColor(ImGuiCol_Text, color); // selected controller profiles in the beginning if (config.notification.controller_profiles) { static bool s_init_overlay = false; if (!s_init_overlay) { static std::chrono::steady_clock::time_point s_started = tick_cached(); const auto now = tick_cached(); if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_started).count() <= 5000) { // active account ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); if (ImGui::Begin("Active account", nullptr, kPopupFlags)) { ImGui::TextUnformatted((const char*)ICON_FA_USER); ImGui::SameLine(); static std::string s_mii_name; if (s_mii_name.empty()) { auto tmp_view = Account::GetAccount(ActiveSettings::GetPersistentId()).GetMiiName(); std::wstring tmp{ tmp_view }; s_mii_name = boost::nowide::narrow(tmp); } ImGui::TextUnformatted(s_mii_name.c_str()); position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; } ImGui::End(); // controller std::vector<std::pair<int, std::string>> profiles; auto& input_manager = InputManager::instance(); for (int i = 0; i < InputManager::kMaxController; ++i) { const auto controller = input_manager.get_controller(i); if (!controller) continue; const auto& profile_name = controller->get_profile_name(); if (profile_name.empty()) continue; profiles.emplace_back(i, profile_name); } if (!profiles.empty()) { ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); if (ImGui::Begin("Controller profile names", nullptr, kPopupFlags)) { auto it = profiles.cbegin(); ImGui::TextUnformatted((const char*)ICON_FA_GAMEPAD); ImGui::SameLine(); ImGui::Text("Player %d: %s", it->first + 1, it->second.c_str()); for (++it; it != profiles.cend(); ++it) { ImGui::Separator(); ImGui::TextUnformatted((const char*)ICON_FA_GAMEPAD); ImGui::SameLine(); ImGui::Text("Player %d: %s", it->first + 1, it->second.c_str()); } position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; } ImGui::End(); } else s_init_overlay = true; } else s_init_overlay = true; } } if (config.notification.friends) { static std::vector< std::pair<std::string, std::chrono::steady_clock::time_point> > s_friend_list; std::unique_lock lock(g_friend_notification_mutex); if (!g_friend_notifications.empty()) { const auto tick = tick_cached(); for (const auto& entry : g_friend_notifications) { s_friend_list.emplace_back(entry.first, tick + std::chrono::milliseconds(entry.second)); } g_friend_notifications.clear(); } if (!s_friend_list.empty()) { ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); if (ImGui::Begin("Friends overlay", nullptr, kPopupFlags)) { const auto tick = tick_cached(); for (auto it = s_friend_list.cbegin(); it != s_friend_list.cend();) { ImGui::TextUnformatted(it->first.c_str(), it->first.c_str() + it->first.size()); if (tick >= it->second) it = s_friend_list.erase(it); else ++it; } position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; } ImGui::End(); } } // low battery warning if (config.notification.controller_battery) { std::vector<int> batteries; auto& input_manager = InputManager::instance(); for (int i = 0; i < InputManager::kMaxController; ++i) { const auto controller = input_manager.get_controller(i); if (!controller) continue; if (controller->is_battery_low()) batteries.emplace_back(i); } if (!batteries.empty()) { static std::chrono::steady_clock::time_point s_last_tick = tick_cached(); static bool s_blink_state = false; const auto now = tick_cached(); if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 750) { s_blink_state = !s_blink_state; s_last_tick = now; } ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); if (ImGui::Begin("Low battery overlay", nullptr, kPopupFlags)) { auto it = batteries.cbegin(); ImGui::TextUnformatted((const char*)(s_blink_state ? ICON_FA_BATTERY_EMPTY : ICON_FA_BATTERY_QUARTER)); ImGui::SameLine(); ImGui::Text("Player %d", *it + 1); for (++it; it != batteries.cend(); ++it) { ImGui::Separator(); ImGui::TextUnformatted((const char*)(s_blink_state ? ICON_FA_BATTERY_EMPTY : ICON_FA_BATTERY_QUARTER)); ImGui::SameLine(); ImGui::Text("Player %d", *it + 1); } position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; } ImGui::End(); } } if (config.notification.shader_compiling) { static int32_t s_shader_count = 0; static int32_t s_shader_count_async = 0; if (s_shader_count > 0 || g_compiled_shaders_total > 0) { const int tmp = g_compiled_shaders_total.exchange(0); const int tmpAsync = g_compiled_shaders_async.exchange(0); s_shader_count += tmp; s_shader_count_async += tmpAsync; static std::chrono::steady_clock::time_point s_last_tick = tick_cached(); const auto now = tick_cached(); if (tmp > 0) s_last_tick = now; if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 2500) { s_shader_count = 0; s_shader_count_async = 0; } if (s_shader_count > 0) { ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); if (ImGui::Begin("Compiling shaders overlay", nullptr, kPopupFlags)) { ImRotateStart(); ImGui::TextUnformatted((const char*)ICON_FA_SPINNER); const auto ticks = std::chrono::time_point_cast<std::chrono::milliseconds>(now); ImRotateEnd(0.001f * ticks.time_since_epoch().count()); ImGui::SameLine(); if (s_shader_count_async > 0 && GetConfig().async_compile) // the latter condition is to never show async count when async isn't enabled. Since it can be confusing to the user { if(s_shader_count > 1) ImGui::Text("Compiled %d new shaders... (%d async)", s_shader_count, s_shader_count_async); else ImGui::Text("Compiled %d new shader... (%d async)", s_shader_count, s_shader_count_async); } else { if (s_shader_count > 1) ImGui::Text("Compiled %d new shaders...", s_shader_count); else ImGui::Text("Compiled %d new shader...", s_shader_count); } position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; } ImGui::End(); } } static int32_t s_pipeline_count = 0; static int32_t s_pipeline_count_async = 0; if (s_pipeline_count > 0 || g_compiling_pipelines > 0) { const int tmp = g_compiling_pipelines.exchange(0); const int tmpAsync = g_compiling_pipelines_async.exchange(0); s_pipeline_count += tmp; s_pipeline_count_async += tmpAsync; static std::chrono::steady_clock::time_point s_last_tick = tick_cached(); const auto now = tick_cached(); if (tmp > 0) s_last_tick = now; if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 2500) { s_pipeline_count = 0; s_pipeline_count_async = 0; } if (s_pipeline_count > 0) { ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); if (ImGui::Begin("Compiling pipeline overlay", nullptr, kPopupFlags)) { ImRotateStart(); ImGui::TextUnformatted((const char*)ICON_FA_SPINNER); const auto ticks = std::chrono::time_point_cast<std::chrono::milliseconds>(now); ImRotateEnd(0.001f * ticks.time_since_epoch().count()); ImGui::SameLine(); #ifdef CEMU_DEBUG_ASSERT uint64 totalTime = g_compiling_pipelines_syncTimeSum / 1000000ull; if (s_pipeline_count_async > 0) { if (s_pipeline_count > 1) ImGui::Text("Compiled %d new pipelines... (%d async) TotalSync: %" PRIu64 "ms", s_pipeline_count, s_pipeline_count_async, totalTime); else ImGui::Text("Compiled %d new pipeline... (%d async) TotalSync: %" PRIu64 "ms", s_pipeline_count, s_pipeline_count_async, totalTime); } else { if (s_pipeline_count > 1) ImGui::Text("Compiled %d new pipelines... TotalSync: %" PRIu64 "ms", s_pipeline_count, totalTime); else ImGui::Text("Compiled %d new pipeline... TotalSync: %" PRIu64 "ms", s_pipeline_count, totalTime); } #else if (s_pipeline_count_async > 0) { if (s_pipeline_count > 1) ImGui::Text("Compiled %d new pipelines... (%d async)", s_pipeline_count, s_pipeline_count_async); else ImGui::Text("Compiled %d new pipeline... (%d async)", s_pipeline_count, s_pipeline_count_async); } else { if (s_pipeline_count > 1) ImGui::Text("Compiled %d new pipelines...", s_pipeline_count); else ImGui::Text("Compiled %d new pipeline...", s_pipeline_count); } #endif position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; } ImGui::End(); } } } // misc notifications static std::vector< std::pair<std::string, std::chrono::steady_clock::time_point> > s_misc_notifications; std::unique_lock misc_lock(g_notification_mutex); if (!g_notifications.empty()) { const auto tick = tick_cached(); for (const auto& entry : g_notifications) { s_misc_notifications.emplace_back(entry.first, tick + std::chrono::milliseconds(entry.second)); } g_notifications.clear(); } misc_lock.unlock(); if (!s_misc_notifications.empty()) { ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); if (ImGui::Begin("Misc notifications", nullptr, kPopupFlags)) { const auto tick = tick_cached(); for (auto it = s_misc_notifications.cbegin(); it != s_misc_notifications.cend();) { ImGui::TextUnformatted(it->first.c_str(), it->first.c_str() + it->first.size()); if (tick >= it->second) it = s_misc_notifications.erase(it); else ++it; } position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; } ImGui::End(); } ImGui::PopStyleColor(); ImGui::PopFont(); } void LatteOverlay_translateScreenPosition(ScreenPosition pos, const Vector2f& window_size, ImVec2& position, ImVec2& pivot, sint32& direction) { switch (pos) { case ScreenPosition::kTopLeft: position = { 10, 10 }; pivot = { 0, 0 }; direction = 1; break; case ScreenPosition::kTopCenter: position = { window_size.x / 2.0f, 10 }; pivot = { 0.5f, 0 }; direction = 1; break; case ScreenPosition::kTopRight: position = { window_size.x - 10, 10 }; pivot = { 1, 0 }; direction = 1; break; case ScreenPosition::kBottomLeft: position = { 10, window_size.y - 10 }; pivot = { 0, 1 }; direction = -1; break; case ScreenPosition::kBottomCenter: position = { window_size.x / 2.0f, window_size.y - 10 }; pivot = { 0.5f, 1 }; direction = -1; break; case ScreenPosition::kBottomRight: position = { window_size.x - 10, window_size.y - 10 }; pivot = { 1, 1 }; direction = -1; break; default: UNREACHABLE; } } void LatteOverlay_render(bool pad_view) { const auto& config = GetConfig(); if(config.overlay.position == ScreenPosition::kDisabled && config.notification.position == ScreenPosition::kDisabled) return; sint32 w = 0, h = 0; if (pad_view && WindowSystem::IsPadWindowOpen()) WindowSystem::GetPadWindowPhysSize(w, h); else WindowSystem::GetWindowPhysSize(w, h); if (w == 0 || h == 0) return; const Vector2f window_size{ (float)w,(float)h }; float fontDPIScale = !pad_view ? WindowSystem::GetWindowDPIScale() : WindowSystem::GetPadDPIScale(); float overlayFontSize = 14.0f * (float)config.overlay.text_scale / 100.0f * fontDPIScale; // test if fonts are already precached if (!ImGui_GetFont(overlayFontSize)) return; float notificationsFontSize = 14.0f * (float)config.notification.text_scale / 100.0f * fontDPIScale; if (!ImGui_GetFont(notificationsFontSize)) return; ImVec2 position{}, pivot{}; sint32 direction{}; if (config.overlay.position != ScreenPosition::kDisabled) { LatteOverlay_translateScreenPosition(config.overlay.position, window_size, position, pivot, direction); LatteOverlay_renderOverlay(position, pivot, direction, overlayFontSize, pad_view); } if (config.notification.position != ScreenPosition::kDisabled) { if(config.overlay.position != config.notification.position) LatteOverlay_translateScreenPosition(config.notification.position, window_size, position, pivot, direction); LatteOverlay_RenderNotifications(position, pivot, direction, notificationsFontSize, pad_view); } } void LatteOverlay_init() { g_state.processor_count = GetProcessorCount(); g_state.processor_times.resize(g_state.processor_count); g_state.cpu_per_core.resize(g_state.processor_count); } static void UpdateStats_CemuCpu() { ProcessorTime now; QueryProcTime(now); double cpu = ProcessorTime::Compare(g_state.processor_time_cemu, now); cpu /= g_state.processor_count; g_state.cpu_usage = cpu * 100; g_state.processor_time_cemu = now; } static void UpdateStats_CpuPerCore() { std::vector<ProcessorTime> now(g_state.processor_count); QueryCoreTimes(g_state.processor_count, now); for (int32_t i = 0; i < g_state.processor_count; ++i) { double cpu = ProcessorTime::Compare(g_state.processor_times[i], now[i]); g_state.cpu_per_core[i] = cpu * 100; g_state.processor_times[i] = now[i]; } } void LatteOverlay_updateStats(double fps, sint32 drawcalls, sint32 fastDrawcalls) { if (GetConfig().overlay.position == ScreenPosition::kDisabled) return; g_state.fps = fps; g_state.draw_calls_per_frame = drawcalls; g_state.fast_draw_calls_per_frame = fastDrawcalls; UpdateStats_CemuCpu(); UpdateStats_CpuPerCore(); // update ram g_state.ram_usage = (QueryRamUsage() / 1000) / 1000; // update vram g_renderer->GetVRAMInfo(g_state.vramUsage, g_state.vramTotal); } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteOverlay.h ================================================ #pragma once void LatteOverlay_init(); void LatteOverlay_render(bool pad_view); void LatteOverlay_updateStats(double fps, sint32 drawcalls, sint32 fastDrawcalls); void LatteOverlay_pushNotification(const std::string& text, sint32 duration); ================================================ FILE: src/Cafe/HW/Latte/Core/LattePM4.h ================================================ #pragma once #define IT_SET_PREDICATION 0x20 #define IT_DRAW_INDEX_2 0x27 #define IT_CONTEXT_CONTROL 0x28 #define IT_INDEX_TYPE 0x2A #define IT_DRAW_INDEX_AUTO 0x2D #define IT_DRAW_INDEX_IMMD 0x2E #define IT_NUM_INSTANCES 0x2F #define IT_INDIRECT_BUFFER_PRIV 0x32 #define IT_STRMOUT_BUFFER_UPDATE 0x34 #define IT_MEM_SEMAPHORE 0x39 #define IT_WAIT_REG_MEM 0x3C #define IT_MEM_WRITE 0x3D #define IT_SURFACE_SYNC 0x43 #define IT_EVENT_WRITE 0x46 #define IT_EVENT_WRITE_EOP 0x47 // end of pipe #define IT_LOAD_CONFIG_REG 0x60 #define IT_LOAD_CONTEXT_REG 0x61 #define IT_LOAD_ALU_CONST 0x62 #define IT_LOAD_BOOL_CONST 0x63 // not used? #define IT_LOAD_LOOP_CONST 0x64 #define IT_LOAD_RESOURCE 0x65 #define IT_LOAD_SAMPLER 0x66 #define IT_SET_CONFIG_REG 0x68 #define IT_SET_CONTEXT_REG 0x69 #define IT_SET_ALU_CONST 0x6A #define IT_SET_LOOP_CONST 0x6C #define IT_SET_RESOURCE 0x6D #define IT_SET_SAMPLER 0x6E #define IT_SET_CTL_CONST 0x6F #define IT_STRMOUT_BASE_UPDATE 0x72 #define IT_SET_ALL_CONTEXTS 0x74 #define LATTE_REG_BASE_CONFIG 0x2000 #define LATTE_REG_BASE_CONTEXT 0xA000 #define LATTE_REG_BASE_ALU_CONST 0xC000 #define LATTE_REG_BASE_LOOP_CONST 0xF880 #define LATTE_REG_BASE_RESOURCE 0xE000 #define LATTE_REG_BASE_SAMPLER 0xF000 // emulator only #define IT_HLE_COPY_SURFACE_NEW 0xEE #define IT_HLE_SYNC_ASYNC_OPERATIONS 0xEF #define IT_HLE_REQUEST_SWAP_BUFFERS 0xF0 #define IT_HLE_WAIT_FOR_FLIP 0xF1 #define IT_HLE_BOTTOM_OF_PIPE_CB 0xF2 #define IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER 0xF3 #define IT_HLE_CLEAR_COLOR_DEPTH_STENCIL 0xF5 #define IT_HLE_SAMPLE_TIMER 0xF7 #define IT_HLE_TRIGGER_SCANBUFFER_SWAP 0xF8 #define IT_HLE_SPECIAL_STATE 0xF9 #define IT_HLE_BEGIN_OCCLUSION_QUERY 0xFA #define IT_HLE_END_OCCLUSION_QUERY 0xFB #define pm4HeaderType3(__itCode, __dataDWordCount) (0xC0000000|((uint32)(__itCode)<<8)|((uint32)((__dataDWordCount)-1)<<16)) #define pm4HeaderType2Filler() (0x80000000) ================================================ FILE: src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp ================================================ #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "WindowSystem.h" performanceMonitor_t performanceMonitor{}; void LattePerformanceMonitor_frameEnd() { // per-frame stats performanceMonitor.gpuTime_shaderCreate.frameFinished(); performanceMonitor.gpuTime_frameTime.frameFinished(); performanceMonitor.gpuTime_idleTime.frameFinished(); performanceMonitor.gpuTime_fenceTime.frameFinished(); performanceMonitor.gpuTime_dcStageTextures.frameFinished(); performanceMonitor.gpuTime_dcStageVertexMgr.frameFinished(); performanceMonitor.gpuTime_dcStageShaderAndUniformMgr.frameFinished(); performanceMonitor.gpuTime_dcStageIndexMgr.frameFinished(); performanceMonitor.gpuTime_dcStageMRT.frameFinished(); performanceMonitor.gpuTime_dcStageDrawcallAPI.frameFinished(); performanceMonitor.gpuTime_waitForAsync.frameFinished(); uint32 elapsedTime = GetTickCount() - performanceMonitor.cycle[performanceMonitor.cycleIndex].lastUpdate; if (elapsedTime >= 1000) { bool isFirstUpdate = performanceMonitor.cycle[performanceMonitor.cycleIndex].lastUpdate == 0; // sum up raw stats uint32 totalElapsedTime = GetTickCount() - performanceMonitor.cycle[(performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES].lastUpdate; uint32 totalElapsedTimeFPS = GetTickCount() - performanceMonitor.cycle[(performanceMonitor.cycleIndex + PERFORMANCE_MONITOR_TRACK_CYCLES - 1) % PERFORMANCE_MONITOR_TRACK_CYCLES].lastUpdate; uint32 elapsedFrames = 0; uint32 elapsedFrames2S = 0; // elapsed frames for last two entries (seconds) uint64 skippedCycles = 0; uint64 vertexDataUploaded = 0; uint64 vertexDataCached = 0; uint64 uniformBankUploadedData = 0; uint64 uniformBankUploadedCount = 0; uint64 indexDataUploaded = 0; uint64 indexDataCached = 0; uint32 frameCounter = 0; uint32 drawCallCounter = 0; uint32 fastDrawCallCounter = 0; uint32 shaderBindCounter = 0; uint32 recompilerLeaveCount = 0; uint32 threadLeaveCount = 0; for (sint32 i = 0; i < PERFORMANCE_MONITOR_TRACK_CYCLES; i++) { elapsedFrames += performanceMonitor.cycle[i].frameCounter; skippedCycles += performanceMonitor.cycle[i].skippedCycles; vertexDataUploaded += performanceMonitor.cycle[i].vertexDataUploaded; vertexDataCached += performanceMonitor.cycle[i].vertexDataCached; uniformBankUploadedData += performanceMonitor.cycle[i].uniformBankUploadedData; uniformBankUploadedCount += performanceMonitor.cycle[i].uniformBankUploadedCount; indexDataUploaded += performanceMonitor.cycle[i].indexDataUploaded; indexDataCached += performanceMonitor.cycle[i].indexDataCached; frameCounter += performanceMonitor.cycle[i].frameCounter; drawCallCounter += performanceMonitor.cycle[i].drawCallCounter; fastDrawCallCounter += performanceMonitor.cycle[i].fastDrawCallCounter; shaderBindCounter += performanceMonitor.cycle[i].shaderBindCount; recompilerLeaveCount += performanceMonitor.cycle[i].recompilerLeaveCount; threadLeaveCount += performanceMonitor.cycle[i].threadLeaveCount; } elapsedFrames = std::max<uint32>(elapsedFrames, 1); elapsedFrames2S = performanceMonitor.cycle[(performanceMonitor.cycleIndex + PERFORMANCE_MONITOR_TRACK_CYCLES - 0) % PERFORMANCE_MONITOR_TRACK_CYCLES].frameCounter; elapsedFrames2S += performanceMonitor.cycle[(performanceMonitor.cycleIndex + PERFORMANCE_MONITOR_TRACK_CYCLES - 1) % PERFORMANCE_MONITOR_TRACK_CYCLES].frameCounter; elapsedFrames2S = std::max<uint32>(elapsedFrames2S, 1); // calculate stats uint64 passedCycles = PPCInterpreter_getMainCoreCycleCounter() - performanceMonitor.cycle[(performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES].lastCycleCount; passedCycles -= skippedCycles; uint64 vertexDataUploadPerFrame = (vertexDataUploaded / (uint64)elapsedFrames); vertexDataUploadPerFrame /= 1024ULL; uint64 vertexDataCachedPerFrame = (vertexDataCached / (uint64)elapsedFrames); vertexDataCachedPerFrame /= 1024ULL; uint64 uniformBankDataUploadedPerFrame = (uniformBankUploadedData / (uint64)elapsedFrames); uniformBankDataUploadedPerFrame /= 1024ULL; uint32 uniformBankCountUploadedPerFrame = (uint32)(uniformBankUploadedCount / (uint64)elapsedFrames); uint64 indexDataUploadPerFrame = (indexDataUploaded / (uint64)elapsedFrames); double fps = (double)elapsedFrames2S * 1000.0 / (double)totalElapsedTimeFPS; uint32 shaderBindsPerFrame = shaderBindCounter / elapsedFrames; passedCycles = passedCycles * 1000ULL / totalElapsedTime; uint32 rlps = (uint32)((uint64)recompilerLeaveCount * 1000ULL / (uint64)totalElapsedTime); uint32 tlps = (uint32)((uint64)threadLeaveCount * 1000ULL / (uint64)totalElapsedTime); // set stats performanceMonitor.stats.indexDataUploadPerFrame = indexDataUploadPerFrame; // next counter cycle sint32 nextCycleIndex = (performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES; performanceMonitor.cycle[nextCycleIndex].drawCallCounter = 0; performanceMonitor.cycle[nextCycleIndex].fastDrawCallCounter = 0; performanceMonitor.cycle[nextCycleIndex].frameCounter = 0; performanceMonitor.cycle[nextCycleIndex].shaderBindCount = 0; performanceMonitor.cycle[nextCycleIndex].lastCycleCount = PPCInterpreter_getMainCoreCycleCounter(); performanceMonitor.cycle[nextCycleIndex].skippedCycles = 0; performanceMonitor.cycle[nextCycleIndex].vertexDataUploaded = 0; performanceMonitor.cycle[nextCycleIndex].vertexDataCached = 0; performanceMonitor.cycle[nextCycleIndex].uniformBankUploadedData = 0; performanceMonitor.cycle[nextCycleIndex].uniformBankUploadedCount = 0; performanceMonitor.cycle[nextCycleIndex].indexDataUploaded = 0; performanceMonitor.cycle[nextCycleIndex].indexDataCached = 0; performanceMonitor.cycle[nextCycleIndex].recompilerLeaveCount = 0; performanceMonitor.cycle[nextCycleIndex].threadLeaveCount = 0; performanceMonitor.cycleIndex = nextCycleIndex; // next update in 1 second performanceMonitor.cycle[performanceMonitor.cycleIndex].lastUpdate = GetTickCount(); if (isFirstUpdate) { LatteOverlay_updateStats(0.0, 0, 0); WindowSystem::UpdateWindowTitles(false, false, 0.0); } else { LatteOverlay_updateStats(fps, drawCallCounter / elapsedFrames, fastDrawCallCounter / elapsedFrames); WindowSystem::UpdateWindowTitles(false, false, fps); } } } void LattePerformanceMonitor_frameBegin() { performanceMonitor.vk.numDrawBarriersPerFrame.reset(); performanceMonitor.vk.numBeginRenderpassPerFrame.reset(); } ================================================ FILE: src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h ================================================ #pragma once #define PERFORMANCE_MONITOR_TRACK_CYCLES (5) // one cycle lasts one second // todo - replace PPCTimer with HighResolutionTimer.h uint64 PPCTimer_getRawTsc(); uint64 PPCTimer_tscToMicroseconds(uint64 us); class LattePerfStatTimer { public: void beginMeasuring() { timerStart = PPCTimer_getRawTsc(); } void endMeasuring() { uint64 dif = PPCTimer_getRawTsc() - timerStart; currentSum += dif; } void frameFinished() { previousFrame = currentSum; currentSum = 0; } uint64 getPreviousFrameValue() { return previousFrame; } private: uint64 currentSum{}; uint64 previousFrame{}; uint64 timerStart{}; }; class LattePerfStatCounter { public: void increment() { m_value++; } void decrement() { cemu_assert_debug(m_value > 0); m_value--; } void decrement(uint32 count) { cemu_assert_debug(count <= m_value); m_value -= count; } uint32 get() { return m_value; } void reset() { m_value = 0; } private: std::atomic_uint32_t m_value{}; }; typedef struct { struct { // CPU uint64 lastCycleCount; uint64 skippedCycles; uint32 recompilerLeaveCount; // increased everytime the recompiler switches back to interpreter uint32 threadLeaveCount; // increased everytime a thread gives up it's timeslice // GPU uint32 lastUpdate; uint32 frameCounter; uint32 drawCallCounter; uint32 fastDrawCallCounter; uint32 shaderBindCount; uint64 vertexDataUploaded; // amount of vertex data uploaded to GPU (bytes) uint64 vertexDataCached; // amount of vertex data reused from GPU cache (bytes) uint64 uniformBankUploadedData; // amount of uniform buffer data (excluding remapped uniforms) uploaded to GPU uint64 uniformBankUploadedCount; // number of separate uploads for uniformBankDataUploaded uint64 indexDataUploaded; uint64 indexDataCached; }cycle[PERFORMANCE_MONITOR_TRACK_CYCLES]; sint32 cycleIndex; // new stats LattePerfStatTimer gpuTime_frameTime; LattePerfStatTimer gpuTime_shaderCreate; LattePerfStatTimer gpuTime_idleTime; // time spent waiting for new commands from CPU LattePerfStatTimer gpuTime_fenceTime; // time spent waiting for fence condition LattePerfStatTimer gpuTime_dcStageTextures; // drawcall texture/mrt setup LattePerfStatTimer gpuTime_dcStageVertexMgr; // drawcall vertex setup and upload LattePerfStatTimer gpuTime_dcStageShaderAndUniformMgr; // drawcall shader setup and uniform management/upload LattePerfStatTimer gpuTime_dcStageIndexMgr; // drawcall index data setup and upload LattePerfStatTimer gpuTime_dcStageMRT; // drawcall render target API LattePerfStatTimer gpuTime_dcStageDrawcallAPI; // drawcall api call LattePerfStatTimer gpuTime_waitForAsync; // waiting for operations to complete (e.g. GX2DrawDone or force texture readback) Also includes texture readback and occlusion query polling logic // generic uint32 numCompiledVS; // number of compiled vertex shader programs uint32 numCompiledGS; // number of compiled geometry shader programs uint32 numCompiledPS; // number of compiled pixel shader programs // Vulkan struct { LattePerfStatCounter numDescriptorSets; LattePerfStatCounter numDescriptorDynUniformBuffers; LattePerfStatCounter numDescriptorStorageBuffers; LattePerfStatCounter numDescriptorSamplerTextures; LattePerfStatCounter numGraphicPipelines; LattePerfStatCounter numImages; LattePerfStatCounter numImageViews; LattePerfStatCounter numSamplers; LattePerfStatCounter numRenderPass; LattePerfStatCounter numFramebuffer; // per frame LattePerfStatCounter numDrawBarriersPerFrame; LattePerfStatCounter numBeginRenderpassPerFrame; }vk; // calculated stats (per frame) struct { uint32 indexDataUploadPerFrame; }stats; }performanceMonitor_t; extern performanceMonitor_t performanceMonitor; void LattePerformanceMonitor_frameEnd(); void LattePerformanceMonitor_frameBegin(); #define beginPerfMonProfiling(__obj) if( THasProfiling ) __obj.beginMeasuring() #define endPerfMonProfiling(__obj) if( THasProfiling ) __obj.endMeasuring() ================================================ FILE: src/Cafe/HW/Latte/Core/LatteQuery.cpp ================================================ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteQueryObject.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #define GPU7_QUERY_TYPE_OCCLUSION (1) uint64 queryEventCounter = 1; struct LatteGX2QueryInformation { MPTR queryMPTR; uint64 queryEventStart; uint64 queryEventEnd; uint64 sampleSum; bool queryEnded; }; std::vector<LatteGX2QueryInformation*> list_activeGX2Queries2; std::vector<LatteQueryObject*> list_queriesInFlight; uint64 latestQueryFinishedEventId = 0; LatteQueryObject* _currentlyActiveRendererQuery = {0}; uint64 LatteQuery_getNextEventId() { uint64 ev = queryEventCounter; queryEventCounter++; return ev; } void LatteQuery_begin(LatteQueryObject* queryObject, uint64 eventId) { queryObject->queryEventStart = eventId; queryObject->begin(); } void LatteQuery_end(LatteQueryObject* queryObject, uint64 eventId) { cemu_assert_debug(!queryObject->queryEnded); queryObject->queryEnded = true; queryObject->queryEventEnd = eventId; queryObject->end(); } LatteQueryObject* LatteQuery_createSamplePassedQuery() { return g_renderer->occlusionQuery_create(); } void LatteQuery_finishGX2Query(LatteGX2QueryInformation* gx2Query) { uint32* queryObjectData = (uint32*)memory_getPointerFromVirtualOffset(gx2Query->queryMPTR); *(uint64*)(queryObjectData + 0) = 0; *(uint64*)(queryObjectData + 2) = gx2Query->sampleSum; *(uint64*)(queryObjectData + 4) = 0; *(uint64*)(queryObjectData + 6) = 0; *(uint64*)(queryObjectData + 8) = 0; // overwrites the 'OCPU' magic constant letting GX2QueryGetOcclusionResult know that the query is finished (for CPU queries) } void LatteQuery_UpdateFinishedQueries() { g_renderer->occlusionQuery_updateState(); for(uint32 i=0; i<list_queriesInFlight.size(); i++) { LatteQueryObject* queryObject = list_queriesInFlight[i]; cemu_assert_debug(queryObject->queryEnded); if( queryObject->queryEnded == false ) continue; // check if result is available uint64 numSamplesPassed; if (!queryObject->getResult(numSamplesPassed)) break; cemu_assert_debug(latestQueryFinishedEventId < queryObject->queryEventEnd); latestQueryFinishedEventId = queryObject->queryEventEnd; // add number of passed samples to all gx2 queries that were active at the time for (auto& it : list_activeGX2Queries2) { if (queryObject->queryEventStart >= it->queryEventStart && queryObject->queryEventEnd <= it->queryEventEnd) it->sampleSum += numSamplesPassed; } list_queriesInFlight.erase(list_queriesInFlight.begin() + i); i--; g_renderer->occlusionQuery_destroy(queryObject); } // check for finished GX2 queries for (sint32 i = 0; i < list_activeGX2Queries2.size(); i++) { auto gx2Query = list_activeGX2Queries2[i]; if (gx2Query->queryEnded && latestQueryFinishedEventId >= gx2Query->queryEventEnd) { LatteQuery_finishGX2Query(gx2Query); free(gx2Query); list_activeGX2Queries2.erase(list_activeGX2Queries2.begin() + i); i--; } } } void LatteQuery_UpdateFinishedQueriesForceFinishAll() { cemu_assert_debug(_currentlyActiveRendererQuery == nullptr); g_renderer->occlusionQuery_flush(); // guarantees that all query commands have been submitted and finished processing while (true) { LatteQuery_UpdateFinishedQueries(); if (list_queriesInFlight.empty()) break; } } sint32 checkQueriesCounter = 0; void LatteQuery_endActiveRendererQuery(uint64 currentEventId) { if (_currentlyActiveRendererQuery != nullptr) { LatteQuery_end(_currentlyActiveRendererQuery, currentEventId); list_queriesInFlight.emplace_back(_currentlyActiveRendererQuery); _currentlyActiveRendererQuery = nullptr; } } void LatteQuery_BeginOcclusionQuery(MPTR queryMPTR) { if (checkQueriesCounter < 7) { checkQueriesCounter++; } else { LatteQuery_UpdateFinishedQueries(); checkQueriesCounter = 0; } for(auto& it : list_activeGX2Queries2) { if (it->queryMPTR == queryMPTR) { debug_printf("itHLEBeginOcclusionQuery: Query 0x%08x is already active\n", queryMPTR); return; } } uint64 currentEventId = LatteQuery_getNextEventId(); // end any currently active query LatteQuery_endActiveRendererQuery(currentEventId); // create GX2 query binding LatteGX2QueryInformation* queryBinding = (LatteGX2QueryInformation*)malloc(sizeof(LatteGX2QueryInformation)); memset(queryBinding, 0x00, sizeof(LatteGX2QueryInformation)); queryBinding->queryEventStart = currentEventId; queryBinding->queryMPTR = queryMPTR; list_activeGX2Queries2.emplace_back(queryBinding); // start renderer query LatteQueryObject* queryObject = LatteQuery_createSamplePassedQuery(); LatteQuery_begin(queryObject, currentEventId); _currentlyActiveRendererQuery = queryObject; } void LatteQuery_EndOcclusionQuery(MPTR queryMPTR) { if (queryMPTR == MPTR_NULL) return; uint64 currentEventId = LatteQuery_getNextEventId(); // mark query binding as ended for(auto& it : list_activeGX2Queries2) { if (it->queryMPTR == queryMPTR) { it->queryEventEnd = currentEventId; it->queryEnded = true; break; } } // end currently active renderer query LatteQuery_endActiveRendererQuery(currentEventId); // check if there are still active GX2 queries bool hasActiveGX2Query = false; for (auto& it : list_activeGX2Queries2) { if (!it->queryEnded) { hasActiveGX2Query = true; break; } } // start a new renderer query if there are still active GX2 queries if (hasActiveGX2Query) { LatteQueryObject* queryObject = LatteQuery_createSamplePassedQuery(); LatteQuery_begin(queryObject, currentEventId); list_queriesInFlight.emplace_back(queryObject); _currentlyActiveRendererQuery = queryObject; catchOpenGLError(); } } void LatteQuery_CancelActiveGPU7Queries() { cemu_assert_debug(_currentlyActiveRendererQuery == nullptr); } void LatteQuery_Init() { } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteQueryObject.h ================================================ #pragma once class LatteQueryObject { public: virtual bool getResult(uint64& numSamplesPassed) = 0; virtual void begin() = 0; virtual void end() = 0; uint32 index{}; bool queryEnded{}; // set to true once the query is ended, but the result may not be available for some time after this uint64 queryEventStart{}; uint64 queryEventEnd{}; }; ================================================ FILE: src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp ================================================ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Cafe/HW/Latte/Core/LatteCachedFBO.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "config/ActiveSettings.h" #include "WindowSystem.h" #include "Cafe/OS/libs/erreula/erreula.h" #include "input/InputManager.h" #include "Cafe/OS/libs/swkbd/swkbd.h" uint32 prevScissorX = 0; uint32 prevScissorY = 0; uint32 prevScissorWidth = 0; uint32 prevScissorHeight = 0; bool hasValidFramebufferAttached = false; struct LatteMRTQuad { sint32 width; sint32 height; }; struct { LatteMRTQuad currentRenderSize; LatteMRTQuad currentEffectiveSize; struct { sint32 width; sint32 height; }currentGuestViewport; bool renderTargetIsResized; // tracking sint32 rtUpdateListCount; LatteTextureView* rtUpdateList[64]; sint32 rtUpdateListSlice[64]; sint32 rtUpdateListMip[64]; }sLatteRenderTargetState; struct { struct { LatteTextureView* view{}; }colorBuffer[8]; struct { LatteTextureView* view{}; bool hasStencil{false}; }depthBuffer; }sLatteCurrentRendertargets{}; LatteCachedFBO::LatteCachedFBO(uint64 key) : key(key) { for (sint32 i = 0; i < 8; i++) { LatteTextureView* colorTexView = sLatteCurrentRendertargets.colorBuffer[i].view; colorBuffer[i].texture = colorTexView; if (colorTexView) { vectorAppendUnique(colorTexView->list_associatedFbo, this); m_referencedTextures.emplace_back(colorTexView->baseTexture); } } if (sLatteCurrentRendertargets.depthBuffer.view) { LatteTextureView* depthTexView = sLatteCurrentRendertargets.depthBuffer.view; depthBuffer.texture = depthTexView; depthBuffer.hasStencil = sLatteCurrentRendertargets.depthBuffer.hasStencil; if (depthTexView) { vectorAppendUnique(depthTexView->list_associatedFbo, this); m_referencedTextures.emplace_back(depthTexView->baseTexture); } } calculateEffectiveRenderAreaSize(); } void LatteMRT::NotifyTextureDeletion(LatteTexture* texture) { for (sint32 i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; i++) { if (sLatteCurrentRendertargets.colorBuffer[i].view && sLatteCurrentRendertargets.colorBuffer[i].view->baseTexture == texture) { sLatteCurrentRendertargets.colorBuffer[i].view = nullptr; } } } LatteCachedFBO* LatteMRT::CreateCachedFBO(uint64 key) { return g_renderer->rendertarget_createCachedFBO(key); } void LatteMRT::DeleteCachedFBO(LatteCachedFBO* cfbo) { // color textures for (sint32 i = 0; i < 8; i++) { if (cfbo->colorBuffer[i].texture == NULL) continue; cfbo->colorBuffer[i].texture->list_fboLookup.erase(std::remove(cfbo->colorBuffer[i].texture->list_fboLookup.begin(), cfbo->colorBuffer[i].texture->list_fboLookup.end(), cfbo), cfbo->colorBuffer[i].texture->list_fboLookup.end()); cfbo->colorBuffer[i].texture->list_associatedFbo.erase(std::remove(cfbo->colorBuffer[i].texture->list_associatedFbo.begin(), cfbo->colorBuffer[i].texture->list_associatedFbo.end(), cfbo), cfbo->colorBuffer[i].texture->list_associatedFbo.end()); } // depth texture if (cfbo->depthBuffer.texture) { cfbo->depthBuffer.texture->list_fboLookup.erase(std::remove(cfbo->depthBuffer.texture->list_fboLookup.begin(), cfbo->depthBuffer.texture->list_fboLookup.end(), cfbo), cfbo->depthBuffer.texture->list_fboLookup.end()); cfbo->depthBuffer.texture->list_associatedFbo.erase(std::remove(cfbo->depthBuffer.texture->list_associatedFbo.begin(), cfbo->depthBuffer.texture->list_associatedFbo.end(), cfbo), cfbo->depthBuffer.texture->list_associatedFbo.end()); } g_renderer->rendertarget_deleteCachedFBO(cfbo); delete cfbo; } LatteCachedFBO* g_emptyFBO = nullptr; void LatteMRT::SetColorAttachment(uint32 index, LatteTextureView* view) { sLatteCurrentRendertargets.colorBuffer[index].view = view; } void LatteMRT::SetDepthAndStencilAttachment(LatteTextureView* view, bool hasStencil) { sLatteCurrentRendertargets.depthBuffer.view = view; sLatteCurrentRendertargets.depthBuffer.hasStencil = hasStencil; } LatteTextureView* LatteMRT::GetColorAttachment(uint32 index) { cemu_assert_debug(index < 8); return sLatteCurrentRendertargets.colorBuffer[index].view; } LatteTextureView* LatteMRT::GetDepthAttachment() { return sLatteCurrentRendertargets.depthBuffer.view; } void LatteMRT::ApplyCurrentState() { uint64 key = 0; LatteTextureView* fboLookupView = NULL; for (sint32 i = 0; i < 8; i++) { LatteTextureView* colorView = sLatteCurrentRendertargets.colorBuffer[i].view; if (colorView) { key += ((uint64)colorView); key = std::rotl<uint64>(key, 5); fboLookupView = colorView; } key = std::rotl<uint64>(key, 7); } if (sLatteCurrentRendertargets.depthBuffer.view) { key += ((uint64)sLatteCurrentRendertargets.depthBuffer.view); key = std::rotl<uint64>(key, 5); key += (sLatteCurrentRendertargets.depthBuffer.hasStencil); if (fboLookupView == NULL) { fboLookupView = sLatteCurrentRendertargets.depthBuffer.view; } } // use fboLookupTexture to find cached FBO if (fboLookupView == nullptr) { if (!g_emptyFBO) g_emptyFBO = CreateCachedFBO(key); g_renderer->rendertarget_bindFramebufferObject(g_emptyFBO); return; } // look for FBO for (auto& fbo : fboLookupView->list_fboLookup) { if (fbo->key == key) { // found matching FBO g_renderer->rendertarget_bindFramebufferObject(fbo); return; } } // create new cached FBO LatteCachedFBO* cfbo = CreateCachedFBO(key); g_renderer->rendertarget_bindFramebufferObject(cfbo); fboLookupView->list_fboLookup.push_back(cfbo); // some extra checks to verify that looked up fbo matches active buffers cemu_assert_debug(cfbo->colorBuffer[0].texture == sLatteCurrentRendertargets.colorBuffer[0].view); cemu_assert_debug(cfbo->colorBuffer[1].texture == sLatteCurrentRendertargets.colorBuffer[1].view); cemu_assert_debug(cfbo->colorBuffer[2].texture == sLatteCurrentRendertargets.colorBuffer[2].view); cemu_assert_debug(cfbo->depthBuffer.texture == sLatteCurrentRendertargets.depthBuffer.view); } void LatteMRT::BindColorBufferOnly(LatteTextureView* view) { cemu_assert_debug(!view->baseTexture->isDepth); SetColorAttachment(0, view); for (sint32 i = 1; i < 8; i++) SetColorAttachment(i, nullptr); SetDepthAndStencilAttachment(nullptr, false); ApplyCurrentState(); } void LatteMRT::BindDepthBufferOnly(LatteTextureView* view) { cemu_assert_debug(view->baseTexture->isDepth); for (sint32 i = 0; i < 8; i++) SetColorAttachment(i, nullptr); SetDepthAndStencilAttachment(view, view->baseTexture->hasStencil); ApplyCurrentState(); } LatteTextureView* LatteMRT_CreateDepthBuffer(MPTR depthBufferPhysMem, uint32 width, uint32 height, uint32 pitch, Latte::E_HWTILEMODE tileMode, Latte::E_GX2SURFFMT format, uint32 swizzle, sint32 viewSlice) { LatteTextureView* textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, viewSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); LatteMRT::SetDepthAndStencilAttachment(textureView, textureView->baseTexture->hasStencil); return textureView; } sint32 _depthBufferSizeWarningCount = 0; LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createNew, bool checkForTextureChanges) { uint32* colorBufferRegBase = LatteGPUState.contextRegister+(mmCB_COLOR0_BASE + index); uint32 regColorBufferBase = colorBufferRegBase[mmCB_COLOR0_BASE - mmCB_COLOR0_BASE] & 0xFFFFFF00; // the low 8 bits are ignored? How to Survive seems to rely on this uint32 regColorSize = colorBufferRegBase[mmCB_COLOR0_SIZE - mmCB_COLOR0_BASE]; uint32 regColorInfo = colorBufferRegBase[mmCB_COLOR0_INFO - mmCB_COLOR0_BASE]; uint32 regColorView = colorBufferRegBase[mmCB_COLOR0_VIEW - mmCB_COLOR0_BASE]; // decode color buffer reg info Latte::E_HWTILEMODE colorBufferTileMode = (Latte::E_HWTILEMODE)((regColorInfo >> 8) & 0xF); uint32 numberType = (regColorInfo >> 12) & 7; Latte::E_GX2SURFFMT colorBufferFormat = GetColorBufferFormat(index, LatteGPUState.contextNew); MPTR colorBufferPhysMem = regColorBufferBase; uint32 colorBufferSwizzle = 0; if ( Latte::TM_IsMacroTiled(colorBufferTileMode) ) { colorBufferSwizzle = colorBufferPhysMem & 0x700; colorBufferPhysMem = colorBufferPhysMem & ~(7 << 8); } // get view slice and view slice num uint32 viewFirstSlice = (regColorView & 0x7FF); uint32 viewNumSlices = ((regColorView >> 13) & 0x7FF) - viewFirstSlice + 1; if (viewNumSlices != 1) { debug_printf("viewNumSlices is not 1! (%d)\n", viewNumSlices); } uint32 colorBufferPitch = (((regColorSize >> 0) & 0x3FF) + 1); colorBufferPitch <<= 3; uint32 pitchHeight = (((regColorSize >> 10) & 0xFFFFF) + 1); pitchHeight <<= 6; uint32 colorBufferHeight = pitchHeight / colorBufferPitch; uint32 colorBufferWidth = colorBufferPitch; // colorbuffer width/height has to be padded to 8/32 alignment but the actual resolution might be smaller // use the scissor box as a clue to figure out the original resolution if possible if(LatteGPUState.allowFramebufferSizeOptimization) { uint32 scissorBoxWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X(); uint32 scissorBoxHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y(); if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth) colorBufferWidth = scissorBoxWidth; if (((colorBufferHeight + 31) & ~31) == colorBufferHeight) colorBufferHeight = scissorBoxHeight; } // log resolution changes if the above heuristic takes effect // this is useful to find resolutions which need to be updated in gfx pack texture rules #if 0 uint32 colorBufferHeight2 = pitchHeight / colorBufferPitch; static std::unordered_set<uint64> s_foundColorBufferResMappings; if (colorBufferPitch != colorBufferWidth || colorBufferHeight != colorBufferHeight2) { // only log unique, source and dest resolution. Encode into a key with 16 bits per component uint64 resHash = (uint64)colorBufferWidth | ((uint64)colorBufferHeight << 16) | ((uint64)colorBufferPitch << 32) | ((uint64)colorBufferHeight2 << 48); if( !s_foundColorBufferResMappings.contains(resHash) ) { s_foundColorBufferResMappings.insert(resHash); cemuLog_log(LogType::Force, "[COLORBUFFER-DBG] Using res {}x{} instead of {}x{}", colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferHeight2); } } #endif bool colorBufferWasFound = false; sint32 viewFirstMip = 0; // todo cemu_assert_debug(viewNumSlices == 1); LatteTextureView* colorBufferView = LatteTextureViewLookupCache::lookupSliceEx(colorBufferPhysMem, colorBufferWidth, colorBufferHeight, colorBufferPitch, viewFirstMip, viewFirstSlice, colorBufferFormat, false); if (colorBufferView == nullptr) { // create color buffer view colorBufferView = LatteTexture_CreateMapping(colorBufferPhysMem, 0, colorBufferWidth, colorBufferHeight, (viewFirstSlice + viewNumSlices), colorBufferPitch, colorBufferTileMode, colorBufferSwizzle>>8, viewFirstMip, 1, viewFirstSlice, viewNumSlices, (Latte::E_GX2SURFFMT)colorBufferFormat, (viewFirstSlice + viewNumSlices)>1? Latte::E_DIM::DIM_2D_ARRAY: Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false, true); LatteGPUState.repeatTextureInitialization = true; checkForTextureChanges = false; } if (colorBufferView->baseTexture->swizzle != colorBufferSwizzle) { colorBufferView->baseTexture->lastRenderTargetSwizzle = colorBufferSwizzle; } // check for texture changes if (checkForTextureChanges) LatteTexture_UpdateDataToLatest(colorBufferView->baseTexture); // mark as used LatteTC_MarkTextureStillInUse(colorBufferView->baseTexture); return colorBufferView; } // get mask of all used color buffers uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShader, const LatteContextRegister& lcr) { const uint32* regView = lcr.GetRawView(); uint8 colorBufferMask = 0; for (uint32 i = 0; i < 8; i++) { if (regView[mmCB_COLOR0_BASE + i] != MPTR_NULL) colorBufferMask |= (1 << i); } // check if color buffer output is active const Latte::LATTE_CB_COLOR_CONTROL& colorControlReg = lcr.CB_COLOR_CONTROL; uint32 colorBufferDisable = colorControlReg.get_SPECIAL_OP() == Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::DISABLE; if (colorBufferDisable) return 0; cemu_assert_debug(colorControlReg.get_DEGAMMA_ENABLE() == false); // not supported // combine color buffer mask with pixel output mask from pixel shader colorBufferMask &= (pixelShader ? pixelShader->pixelColorOutputMask : 0); // combine color buffer mask with color channel mask from mmCB_TARGET_MASK (disable render buffer if all colors are blocked) uint32 channelTargetMask = lcr.CB_TARGET_MASK.get_MASK(); for (uint32 i = 0; i < 8; i++) { if (((channelTargetMask >> (i * 4)) & 0xF) == 0) colorBufferMask &= ~(1 << i); } // render targets smaller than the scissor size are not allowed // this fixes a few render issues in Cemu but we dont know if this matches HW behavior cemu_assert_debug(lcr.PA_SC_GENERIC_SCISSOR_TL.get_WINDOW_OFFSET_DISABLE() == true); // todo (not exposed by GX2 API) uint32 scissorAccessWidth = lcr.PA_SC_GENERIC_SCISSOR_BR.get_BR_X(); uint32 scissorAccessHeight = lcr.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y(); for (uint32 i = 0; i < 8; i++) { if( (colorBufferMask&(1<<i)) == 0 ) continue; // get width/height uint32 regColorSize = regView[mmCB_COLOR0_SIZE + i]; uint32 regColorInfo = regView[mmCB_COLOR0_INFO + i]; // decode color buffer reg info uint32 colorBufferPitch = (((regColorSize >> 0) & 0x3FF) + 1); colorBufferPitch <<= 3; uint32 pitchHeight = (((regColorSize >> 10) & 0xFFFFF) + 1); pitchHeight <<= 6; uint32 colorBufferHeight = pitchHeight / colorBufferPitch; uint32 colorBufferWidth = colorBufferPitch; if ((colorBufferWidth < (sint32)scissorAccessWidth) || (colorBufferHeight < (sint32)scissorAccessHeight)) { // log this? colorBufferMask &= ~(1<<i); } } return colorBufferMask; } // returns true if depth/stencil buffer is used bool LatteMRT::GetActiveDepthBufferMask(const LatteContextRegister& lcr) { bool depthBufferMask = true; // if depth test is not used then detach the depth buffer bool depthEnable = lcr.DB_DEPTH_CONTROL.get_Z_ENABLE(); bool stencilTestEnable = lcr.DB_DEPTH_CONTROL.get_STENCIL_ENABLE(); bool backStencilEnable = lcr.DB_DEPTH_CONTROL.get_BACK_STENCIL_ENABLE(); if (!depthEnable && !stencilTestEnable && !backStencilEnable) depthBufferMask = false; return depthBufferMask; } const uint32 _colorBufferFormatBits[] = { 0, // 0 0x200, // 1 0, // 2 0, // 3 0x100, // 4 0x300, // 5 0x400, // 6 0x800, // 7 }; Latte::E_GX2SURFFMT LatteMRT::GetColorBufferFormat(const uint32 index, const LatteContextRegister& lcr) { cemu_assert_debug(index < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS); uint32 regColorInfo = lcr.GetRawView()[mmCB_COLOR0_INFO + index]; uint32 colorBufferFormat = (regColorInfo >> 2) & 0x3F; // base HW format uint32 numberType = (regColorInfo >> 12) & 7; colorBufferFormat |= _colorBufferFormatBits[numberType]; return (Latte::E_GX2SURFFMT)colorBufferFormat; } // return GX2 format of current depth buffer Latte::E_GX2SURFFMT LatteMRT::GetDepthBufferFormat(const LatteContextRegister& lcr) { uint32 regDepthBufferInfo = lcr.GetRawView()[mmDB_DEPTH_INFO]; switch (regDepthBufferInfo & 7) { case 1: return Latte::E_GX2SURFFMT::D16_UNORM; case 3: return Latte::E_GX2SURFFMT::D24_S8_UNORM; case 5: return Latte::E_GX2SURFFMT::D24_S8_FLOAT; case 6: return Latte::E_GX2SURFFMT::D32_FLOAT; case 7: return Latte::E_GX2SURFFMT::D32_S8_FLOAT; default: debug_printf("Invalid DB_DEPTH_INFO depthbuffer format (%d)\n", (regDepthBufferInfo & 7)); break; } return Latte::E_GX2SURFFMT::D16_UNORM; } bool LatteMRT::UpdateCurrentFBO() { catchOpenGLError(); sLatteRenderTargetState.rtUpdateListCount = 0; // combine color buffer mask with pixel output mask from pixel shader LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); uint8 colorBufferMask = GetActiveColorBufferMask(pixelShader, LatteGPUState.contextNew); bool depthBufferMask = GetActiveDepthBufferMask(LatteGPUState.contextNew); bool hasResizedTexture = false; // set to true if any of the color buffers or the depth buffer reference a resized texture (via graphic pack texture rules) sLatteRenderTargetState.renderTargetIsResized = false; // real size LatteMRTQuad* rtRealSize = &sLatteRenderTargetState.currentRenderSize; rtRealSize->width = 0; rtRealSize->height = 0; // effective size (differs from real size only if graphic pack rules overwrite texture sizes) LatteMRTQuad* rtEffectiveSize = &sLatteRenderTargetState.currentEffectiveSize; rtEffectiveSize->width = 0; rtEffectiveSize->height = 0; // get scissor box cemu_assert_debug(LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_TL.get_WINDOW_OFFSET_DISABLE() == true); // todo (not exposed by GX2 API?) uint32 scissorX = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_TL.get_TL_X(); uint32 scissorY = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_TL.get_TL_Y(); uint32 scissorWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X() - scissorX; uint32 scissorHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y() - scissorY; uint32 scissorAccessWidth = scissorX + scissorWidth; uint32 scissorAccessHeight = scissorY + scissorHeight; // color buffers for (uint32 i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; i++) { if (((colorBufferMask)&(1 << i)) == 0) { // unbind SetColorAttachment(i, nullptr); continue; } LatteTextureView* colorAttachmentView = GetColorAttachmentTexture(i, true, true); SetColorAttachment(i, colorAttachmentView); // after the drawcall mark the texture as updated sLatteRenderTargetState.rtUpdateList[sLatteRenderTargetState.rtUpdateListCount] = colorAttachmentView; sLatteRenderTargetState.rtUpdateListCount++; sint32 colorAttachmentWidth, colorAttachmentHeight; colorAttachmentView->baseTexture->GetSize(colorAttachmentWidth, colorAttachmentHeight, colorAttachmentView->firstMip); // set effective size sint32 effectiveWidth, effectiveHeight; colorAttachmentView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, colorAttachmentView->firstMip); if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) { rtEffectiveSize->width = effectiveWidth; rtEffectiveSize->height = effectiveHeight; } else if (rtEffectiveSize->width != effectiveWidth && rtEffectiveSize->height != effectiveHeight) { cemuLog_logDebug(LogType::Force, "Color buffer size mismatch ({}x{}). Effective size: {}x{} Real size: {}x{} Mismatching texture: {:08x} {}x{} fmt {:04x}", rtEffectiveSize->width, rtEffectiveSize->height, effectiveWidth, effectiveHeight, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, colorAttachmentView->baseTexture->physAddress, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, (uint32)colorAttachmentView->baseTexture->format); } // currently the first color attachment defines the size of the current render target if (rtRealSize->width == 0 && rtRealSize->height == 0) { rtRealSize->width = colorAttachmentWidth; rtRealSize->height = colorAttachmentHeight; } if (colorAttachmentView) continue; } // depth buffer if (depthBufferMask) { uint32 regDepthBuffer = LatteGPUState.contextRegister[mmDB_HTILE_DATA_BASE]; uint32 regDepthSize = LatteGPUState.contextRegister[mmDB_DEPTH_SIZE]; uint32 regDepthBufferInfo = LatteGPUState.contextRegister[mmDB_DEPTH_INFO]; // get format and tileMode from info reg Latte::E_GX2SURFFMT depthBufferFormat = GetDepthBufferFormat(LatteGPUState.contextNew); Latte::E_HWTILEMODE depthBufferTileMode = (Latte::E_HWTILEMODE)((regDepthBufferInfo >> 15) & 0xF); MPTR depthBufferPhysMem = regDepthBuffer << 8; uint32 depthBufferPitch = (((regDepthSize >> 0) & 0x3FF) + 1); uint32 depthBufferHeight = ((((regDepthSize >> 10) & 0xFFFFF) + 1) / depthBufferPitch); depthBufferPitch <<= 3; depthBufferHeight <<= 3; uint32 depthBufferWidth = depthBufferPitch; if (depthBufferWidth < 2) { debug_printf("depthBufferWidth has invalid value %d\n", depthBufferWidth); depthBufferWidth = 2; } if (depthBufferHeight < 2) { debug_printf("depthBufferHeight has invalid value %d\n", depthBufferHeight); depthBufferHeight = 2; } bool blockDepthBuffer = false; if (scissorAccessWidth > depthBufferPitch || scissorAccessHeight > depthBufferHeight) { SetDepthAndStencilAttachment(nullptr, false); blockDepthBuffer = true; // set effective size if( rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0 ) { rtEffectiveSize->width = rtRealSize->width; rtEffectiveSize->height = rtRealSize->height; } } if (blockDepthBuffer == false) { if (rtRealSize->width == 0) { rtRealSize->width = depthBufferWidth; rtRealSize->height = depthBufferHeight; } uint32 regDepthView = LatteGPUState.contextRegister[mmDB_DEPTH_VIEW]; uint32 depthBufferViewFirstSlice = (regDepthView & 0x7FF); uint32 depthBufferViewNumSlices = ((regDepthView >> 13) & 0x7FF) - depthBufferViewFirstSlice + 1; cemu_assert_debug(depthBufferViewNumSlices == 1); // binding multiple layers makes no sense? uint32 depthBufferSwizzle = 0; if (Latte::TM_IsMacroTiled(depthBufferTileMode)) { depthBufferSwizzle = (depthBufferPhysMem >> 8) & 7; depthBufferPhysMem = depthBufferPhysMem & ~(7 << 8); } if (depthBufferPhysMem != MPTR_NULL) { LatteTextureView* depthBufferView = LatteTextureViewLookupCache::lookupSliceEx(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, depthBufferViewFirstSlice, depthBufferFormat, true); if (!depthBufferView) { // create new depth buffer view and if it doesn't exist then also create the texture depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true, true); LatteGPUState.repeatTextureInitialization = true; } else { // check for texture changes LatteTexture_UpdateDataToLatest(depthBufferView->baseTexture); } // set effective size sint32 effectiveWidth, effectiveHeight; depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBufferView->firstMip); if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) { rtEffectiveSize->width = effectiveWidth; rtEffectiveSize->height = effectiveHeight; } else if (rtEffectiveSize->width > effectiveWidth && rtEffectiveSize->height > effectiveHeight) { if (_depthBufferSizeWarningCount < 100) { cemuLog_logDebug(LogType::Force, "Depth buffer size too small. Effective size: {}x{} Real size: {}x{} Mismatching texture: {:08x} {}x{} fmt {:04x}", effectiveWidth, effectiveHeight, depthBufferView->baseTexture->width, depthBufferView->baseTexture->height, depthBufferView->baseTexture->physAddress, depthBufferView->baseTexture->width, depthBufferView->baseTexture->height, (uint32)depthBufferView->baseTexture->format); _depthBufferSizeWarningCount++; } } LatteTC_MarkTextureStillInUse(depthBufferView->baseTexture); // after the drawcall mark the texture as updated sLatteRenderTargetState.rtUpdateList[sLatteRenderTargetState.rtUpdateListCount] = depthBufferView; sLatteRenderTargetState.rtUpdateListCount++; SetDepthAndStencilAttachment(depthBufferView, depthBufferView->baseTexture->hasStencil); } } else { SetDepthAndStencilAttachment(nullptr, false); } } else { SetDepthAndStencilAttachment(nullptr, false); } catchOpenGLError(); if (colorBufferMask || depthBufferMask) { hasValidFramebufferAttached = true; } else { hasValidFramebufferAttached = false; return true; } if( rtEffectiveSize->width != rtRealSize->width || rtEffectiveSize->height != rtRealSize->height ) { //debug_printf("RenderTarget rescaled. Real: %dx%d Resized: %dx%d\n", rtRealSize->width, rtRealSize->height, rtEffectiveSize->width, rtEffectiveSize->height); sLatteRenderTargetState.renderTargetIsResized = true; } if (sLatteRenderTargetState.currentEffectiveSize.width == 0) { debug_printf("Render target effective size is 0. May indicate a bug in Cemu or invalid color/depth buffers\n"); return false; } return true; } // return a vec4 with xy being the scale ratio for render target extend and zw being the virtual viewport dimensions void LatteMRT::GetCurrentFragCoordScale(float* coordScale) { if (sLatteRenderTargetState.renderTargetIsResized) { coordScale[0] = (float)sLatteRenderTargetState.currentRenderSize.width / (float)sLatteRenderTargetState.currentEffectiveSize.width; coordScale[1] = (float)sLatteRenderTargetState.currentRenderSize.height / (float)sLatteRenderTargetState.currentEffectiveSize.height; coordScale[2] = sLatteRenderTargetState.currentGuestViewport.width; coordScale[3] = sLatteRenderTargetState.currentGuestViewport.height; } else { coordScale[0] = 1.0f; coordScale[1] = 1.0f; coordScale[2] = sLatteRenderTargetState.currentGuestViewport.width; coordScale[3] = sLatteRenderTargetState.currentGuestViewport.height; } } void LatteMRT::GetVirtualViewportDimensions(sint32& width, sint32& height) { width = sLatteRenderTargetState.currentGuestViewport.width; height = sLatteRenderTargetState.currentGuestViewport.height; } // flag all FBO textures as updated via GPU // also handle texture readback void LatteRenderTarget_trackUpdates() { // after the drawcall, mark the render target textures as dynamically updated uint64 eventCounter = LatteTexture_getNextUpdateEventCounter(); for (sint32 i = 0; i < sLatteRenderTargetState.rtUpdateListCount; i++) { LatteTextureView* texView = sLatteRenderTargetState.rtUpdateList[i]; LatteTexture* baseTexture = texView->baseTexture; LatteTexture_TrackTextureGPUWrite(baseTexture, texView->firstSlice, texView->firstMip, eventCounter); // texture readback if (baseTexture->enableReadback) { LatteTextureReadback_Initate(texView); } } } void LatteRenderTarget_itHLESwapScanBuffer() { performanceMonitor.cycle[performanceMonitor.cycleIndex].frameCounter++; if(LatteGPUState.frameCounter > 5) performanceMonitor.gpuTime_frameTime.endMeasuring(); LattePerformanceMonitor_frameEnd(); LatteGPUState.frameCounter++; g_renderer->SwapBuffers(true, true); catchOpenGLError(); performanceMonitor.gpuTime_frameTime.beginMeasuring(); LatteTC_CleanupUnusedTextures(); LatteDraw_cleanupAfterFrame(); LatteQuery_CancelActiveGPU7Queries(); LatteBufferCache_notifySwapTVScanBuffer(); LattePerformanceMonitor_frameBegin(); } void LatteRenderTarget_applyTextureColorClear(LatteTexture* texture, uint32 sliceIndex, uint32 mipIndex, float r, float g, float b, float a, uint64 eventCounter) { if (texture->isDepth) { cemuLog_logDebug(LogType::Force, "Unsupported clear depth as color"); } else { g_renderer->texture_clearColorSlice(texture, sliceIndex, mipIndex, r, g, b, a); LatteTexture_MarkDynamicTextureAsChanged(texture->baseView, sliceIndex, mipIndex, eventCounter); } } void LatteRenderTarget_applyTextureDepthClear(LatteTexture* texture, uint32 sliceIndex, uint32 mipIndex, bool hasDepthClear, bool hasStencilClear, float depthValue, uint8 stencilValue, uint64 eventCounter) { if(texture->isDepth) { g_renderer->texture_clearDepthSlice(texture, sliceIndex, mipIndex, hasDepthClear, hasStencilClear, depthValue, stencilValue); } else { // clearing a color texture using depth clear if (hasStencilClear) return; // operation likely not intended as a color clear //cemu_assert_debug(!hasStencilClear); if (hasDepthClear) { g_renderer->texture_clearColorSlice(texture, sliceIndex, mipIndex, depthValue, depthValue, depthValue, depthValue); } } LatteTexture_MarkDynamicTextureAsChanged(texture->baseView, sliceIndex, mipIndex, eventCounter); } void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, Latte::E_GX2SURFFMT colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, Latte::E_GX2SURFFMT depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil) { uint32 depthBufferMipIndex = 0; // todo uint32 colorBufferMipIndex = 0; // todo bool hasColorClear = (clearMask & 1); bool hasDepthClear = (clearMask & 2); bool hasStencilClear = (clearMask & 4); // extract swizzle offset from pointer uint32 colorBufferSwizzle = 0; uint32 depthBufferSwizzle = 0; if (Latte::TM_IsMacroTiled(colorBufferTilemode)) { colorBufferSwizzle = (colorBufferMPTR >> 8) & 7; colorBufferMPTR = colorBufferMPTR & ~(7 << 8); } if (Latte::TM_IsMacroTiled(depthBufferTileMode)) { depthBufferSwizzle = (depthBufferMPTR >> 8) & 7; depthBufferMPTR = depthBufferMPTR & ~(7 << 8); } cemu_assert_debug(colorBufferViewNumSlice <= 1); cemu_assert_debug(depthBufferViewNumSlice <= 1); // clear color buffer (if flag set) uint64 eventCounter = LatteTexture_getNextUpdateEventCounter(); if ((clearMask & 1) != 0 && colorBufferMPTR != MPTR_NULL && colorBufferWidth > 0 && colorBufferHeight > 0) { // clear color sint32 searchIndex = 0; bool targetFound = false; while (true) { LatteTextureView* colorView = LatteTC_LookupTextureByData(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, 0, 1, colorBufferViewFirstSlice, 1, &searchIndex); if (!colorView) break; if (Latte::GetFormatBits(colorBufferFormat) != Latte::GetFormatBits(colorView->baseTexture->format)) continue; if (colorView->baseTexture->pitch == colorBufferPitch && colorView->baseTexture->height == colorBufferHeight) targetFound = true; LatteRenderTarget_applyTextureColorClear(colorView->baseTexture, colorBufferViewFirstSlice, colorBufferMipIndex, r, g, b, a, eventCounter); } if (targetFound == false) { // create new texture with matching format cemu_assert_debug(colorBufferViewNumSlice <= 1); LatteTextureView* newColorView = LatteTexture_CreateMapping(colorBufferMPTR, MPTR_NULL, colorBufferWidth, colorBufferHeight, colorBufferViewFirstSlice+1, colorBufferPitch, colorBufferTilemode, colorBufferSwizzle, 0, 1, colorBufferViewFirstSlice, 1, colorBufferFormat, colorBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false); LatteRenderTarget_applyTextureColorClear(newColorView->baseTexture, colorBufferViewFirstSlice, colorBufferMipIndex, r, g, b, a, eventCounter); } } // clear depth or stencil buffer (if flag set) if ((hasDepthClear || hasStencilClear) && depthBufferMPTR != MPTR_NULL) { std::vector<LatteTexture*> list_depthClearTextures; LatteTC_LookupTexturesByPhysAddr(depthBufferMPTR, list_depthClearTextures); bool foundMatchingDepthBuffer = false; // todo - support for clearing depth mips? cemu_assert_debug(depthBufferViewNumSlice == 1); for (auto& texItr : list_depthClearTextures) { // only clear depth buffers if they are smaller if (texItr->pitch > depthBufferPitch) continue; if (depthBufferViewFirstSlice >= texItr->depth) continue; // slice out of range if (texItr->pitch == depthBufferPitch && texItr->height == depthBufferHeight) foundMatchingDepthBuffer = true; // todo - calculate actual sliceIndex and mipIndex since the textures in list_depthClearTextures dont necessarily share the same base LatteRenderTarget_applyTextureDepthClear(texItr, depthBufferViewFirstSlice, depthBufferMipIndex, hasDepthClear, hasStencilClear, clearDepth, clearStencil, eventCounter); } if (foundMatchingDepthBuffer == false) { LatteTextureView* newDepthBufferView = LatteMRT_CreateDepthBuffer(depthBufferMPTR, depthBufferWidth, depthBufferHeight, depthBufferPitch, depthBufferTileMode, (Latte::E_GX2SURFFMT)depthBufferFormat, depthBufferSwizzle, depthBufferViewFirstSlice); LatteRenderTarget_applyTextureDepthClear(newDepthBufferView->baseTexture, depthBufferViewFirstSlice, depthBufferMipIndex, hasDepthClear, hasStencilClear, clearDepth, clearStencil, eventCounter); } } } sint32 _currentOutputImageWidth = 0; sint32 _currentOutputImageHeight = 0; void LatteRenderTarget_getScreenImageArea(sint32* x, sint32* y, sint32* width, sint32* height, sint32* fullWidth, sint32* fullHeight, bool padView) { int w, h; if(padView && WindowSystem::IsPadWindowOpen()) WindowSystem::GetPadWindowPhysSize(w, h); else WindowSystem::GetWindowPhysSize(w, h); sint32 scaledOutputX; sint32 scaledOutputY; if (GetConfig().fullscreen_scaling == kKeepAspectRatio) { // calculate maximum possible resolution with intact aspect ratio scaledOutputX = w; scaledOutputY = _currentOutputImageHeight * w / std::max(_currentOutputImageWidth, 1); if (scaledOutputY > h) { scaledOutputX = _currentOutputImageWidth * h / std::max(_currentOutputImageHeight, 1); scaledOutputY = h; } } else { scaledOutputX = w; scaledOutputY = h; } *x = (w - scaledOutputX) / 2; *y = (h - scaledOutputY) / 2; *width = scaledOutputX; *height = scaledOutputY; if (fullWidth) *fullWidth = w; if (fullHeight) *fullHeight = h; } void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPadView) { // make sure texture is updated to latest data in cache LatteTexture_UpdateDataToLatest(textureView->baseTexture); // mark source texture as still in use LatteTC_MarkTextureStillInUse(textureView->baseTexture); sint32 effectiveWidth, effectiveHeight; textureView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); _currentOutputImageWidth = effectiveWidth; _currentOutputImageHeight = effectiveHeight; sint32 imageX, imageY; sint32 imageWidth, imageHeight; sint32 fullscreenWidth, fullscreenHeight; LatteRenderTarget_getScreenImageArea(&imageX, &imageY, &imageWidth, &imageHeight, &fullscreenWidth, &fullscreenHeight, isPadView); bool clearBackground = false; if (imageWidth != fullscreenWidth || imageHeight != fullscreenHeight) clearBackground = true; const bool renderUpsideDown = ActiveSettings::RenderUpsideDownEnabled(); // force disable bicubic scaling if output resolution is equal/smaller than input resolution const bool downscaling = (imageWidth <= effectiveWidth || imageHeight <= effectiveHeight); // check for graphic pack shaders RendererOutputShader* shader = nullptr; LatteTextureView::MagFilter filter = LatteTextureView::MagFilter::kLinear; for(const auto& gp : GraphicPack2::GetActiveGraphicPacks()) { if(downscaling) { shader = gp->GetDownscalingShader(renderUpsideDown); if (shader) { filter = gp->GetDownscalingMagFilter(); break; } } else { shader = gp->GetUpscalingShader(renderUpsideDown); if (shader) { filter = gp->GetUpscalingMagFilter(); break; } } shader = gp->GetOuputShader(renderUpsideDown); if (shader) { filter = downscaling ? gp->GetDownscalingMagFilter() : gp->GetUpscalingMagFilter(); break; } } if (shader == nullptr) { sint32 scaling_filter = downscaling ? GetConfig().downscale_filter : GetConfig().upscale_filter; if (scaling_filter == kLinearFilter) { if(renderUpsideDown) shader = RendererOutputShader::s_copy_shader_ud; else shader = RendererOutputShader::s_copy_shader; filter = LatteTextureView::MagFilter::kLinear; } else if (scaling_filter == kBicubicFilter) { if (renderUpsideDown) shader = RendererOutputShader::s_bicubic_shader_ud; else shader = RendererOutputShader::s_bicubic_shader; filter = LatteTextureView::MagFilter::kLinear; } else if (scaling_filter == kBicubicHermiteFilter) { if (renderUpsideDown) shader = RendererOutputShader::s_hermit_shader_ud; else shader = RendererOutputShader::s_hermit_shader; filter = LatteTextureView::MagFilter::kLinear; } else if (scaling_filter == kNearestNeighborFilter) { if (renderUpsideDown) shader = RendererOutputShader::s_copy_shader_ud; else shader = RendererOutputShader::s_copy_shader; filter = LatteTextureView::MagFilter::kNearestNeighbor; } } cemu_assert(shader); g_renderer->DrawBackbufferQuad(textureView, shader, filter==LatteTextureView::MagFilter::kLinear, imageX, imageY, imageWidth, imageHeight, isPadView, clearBackground); g_renderer->HandleScreenshotRequest(textureView, isPadView); if (!g_renderer->ImguiBegin(!isPadView)) return; swkbd_render(!isPadView); nn::erreula::render(!isPadView); LatteOverlay_render(isPadView); g_renderer->ImguiEnd(); } void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget) { cemu_assert_debug(colorBufferSliceIndex == 0); // todo - support for non-zero slice LatteTextureView* texView = LatteTC_GetTextureSliceViewOrTryCreate(colorBufferPtr, MPTR_NULL, (Latte::E_GX2SURFFMT)colorBufferFormat, colorBufferTilemode, colorBufferWidth, colorBufferHeight, 1, colorBufferPitch, colorBufferSwizzle, 0, 0, true); if (!texView) { return; } auto getVPADScreenActive = [](size_t n) -> std::pair<bool, bool> { auto controller = InputManager::instance().get_vpad_controller(n); if (!controller) return {false,false}; auto pressed = controller->is_screen_active(); auto toggle = controller->is_screen_active_toggle(); return {pressed && !toggle, pressed && toggle}; }; const bool tabPressed = WindowSystem::IsKeyDown(WindowSystem::PlatformKeyCodes::TAB); const bool ctrlPressed = WindowSystem::IsKeyDown(WindowSystem::PlatformKeyCodes::LCONTROL); const auto [vpad0Active, vpad0Toggle] = getVPADScreenActive(0); const auto [vpad1Active, vpad1Toggle] = getVPADScreenActive(1); const bool altScreenRequested = (!ctrlPressed && tabPressed) || vpad0Active || vpad1Active; const bool togglePressed = (ctrlPressed && tabPressed) || vpad0Toggle || vpad1Toggle; static bool togglePressedLast = false; bool& isDRCPrimary = LatteGPUState.isDRCPrimary; if(togglePressed && !togglePressedLast) isDRCPrimary = !isDRCPrimary; togglePressedLast = togglePressed; bool showDRC = swkbd_hasKeyboardInputHook() == false && (isDRCPrimary ^ altScreenRequested); if ((renderTarget & RENDER_TARGET_DRC) && g_renderer->IsPadWindowActive()) LatteRenderTarget_copyToBackbuffer(texView, true); if (((renderTarget & RENDER_TARGET_TV) && !showDRC) || ((renderTarget & RENDER_TARGET_DRC) && showDRC)) LatteRenderTarget_copyToBackbuffer(texView, false); } // returns the current size of the virtual viewport (not the same as effective size, which can be influenced by texture rules) void LatteRenderTarget_GetCurrentVirtualViewportSize(sint32* viewportWidth, sint32* viewportHeight) { *viewportWidth = sLatteRenderTargetState.currentGuestViewport.width; *viewportHeight = sLatteRenderTargetState.currentGuestViewport.height; } void LatteRenderTarget_updateViewport() { float vpWidth = LatteGPUState.contextNew.PA_CL_VPORT_XSCALE.get_SCALE() / 0.5f; float vpX = LatteGPUState.contextNew.PA_CL_VPORT_XOFFSET.get_OFFSET() - LatteGPUState.contextNew.PA_CL_VPORT_XSCALE.get_SCALE(); float vpHeight = LatteGPUState.contextNew.PA_CL_VPORT_YSCALE.get_SCALE() / -0.5f; float vpY = LatteGPUState.contextNew.PA_CL_VPORT_YOFFSET.get_OFFSET() + LatteGPUState.contextNew.PA_CL_VPORT_YSCALE.get_SCALE(); bool halfZ = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_CLIP_SPACE_DEF(); // calculate near/far float farZ; float nearZ; float s = LatteGPUState.contextNew.PA_CL_VPORT_ZSCALE.get_SCALE(); float b = LatteGPUState.contextNew.PA_CL_VPORT_ZOFFSET.get_OFFSET(); if (halfZ == false) { farZ = s + b; nearZ = b - s; } else { farZ = s + b; nearZ = b; } sLatteRenderTargetState.currentGuestViewport.width = vpWidth; sLatteRenderTargetState.currentGuestViewport.height = vpHeight; if (sLatteRenderTargetState.renderTargetIsResized) { vpX *= ((float)sLatteRenderTargetState.currentEffectiveSize.width / (float)sLatteRenderTargetState.currentRenderSize.width); vpY *= ((float)sLatteRenderTargetState.currentEffectiveSize.height / (float)sLatteRenderTargetState.currentRenderSize.height); vpWidth *= ((float)sLatteRenderTargetState.currentEffectiveSize.width / (float)sLatteRenderTargetState.currentRenderSize.width); vpHeight *= ((float)sLatteRenderTargetState.currentEffectiveSize.height / (float)sLatteRenderTargetState.currentRenderSize.height); } g_renderer->renderTarget_setViewport(vpX, vpY, vpWidth, vpHeight, nearZ, farZ, halfZ); } void LatteRenderTarget_updateScissorBox() { // update scissor box uint32 scissorX = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_TL.get_TL_X(); uint32 scissorY = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_TL.get_TL_Y(); uint32 scissorWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X() - scissorX; uint32 scissorHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y() - scissorY; if( sLatteRenderTargetState.renderTargetIsResized ) { scissorX = (sint32)((float)scissorX * ((float)sLatteRenderTargetState.currentEffectiveSize.width / (float)sLatteRenderTargetState.currentRenderSize.width)); scissorY = (sint32)((float)scissorY * ((float)sLatteRenderTargetState.currentEffectiveSize.height / (float)sLatteRenderTargetState.currentRenderSize.height)); scissorWidth = (sint32)((float)scissorWidth * ((float)sLatteRenderTargetState.currentEffectiveSize.width / (float)sLatteRenderTargetState.currentRenderSize.width)); scissorHeight = (sint32)((float)scissorHeight * ((float)sLatteRenderTargetState.currentEffectiveSize.height / (float)sLatteRenderTargetState.currentRenderSize.height)); } if( scissorX != prevScissorX || scissorY != prevScissorY || scissorWidth != prevScissorWidth || scissorHeight != prevScissorHeight ) { g_renderer->renderTarget_setScissor(scissorX, scissorY, scissorWidth, scissorHeight); prevScissorX = scissorX; prevScissorY = scissorY; prevScissorWidth = scissorWidth; prevScissorHeight = scissorHeight; } } void LatteRenderTarget_unloadAll() { if (g_emptyFBO) { LatteMRT::DeleteCachedFBO(g_emptyFBO); g_emptyFBO = nullptr; } } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteRingBuffer.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteRingBuffer.h" LatteRingBuffer_t* LatteRingBuffer_create(uint8* data, uint32 size) { LatteRingBuffer_t* rb = (LatteRingBuffer_t*)malloc(sizeof(LatteRingBuffer_t)); rb->data = data; rb->size = size; rb->writeIndex = 0; return rb; } uint8* LatteRingBuffer_allocate(LatteRingBuffer_t* rb, sint32 size, sint32 alignment) { #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug(size < rb->size); #endif // align rb->writeIndex = (rb->writeIndex + alignment - 1)&~(alignment-1); // handle wrap-around if ((rb->writeIndex + size) >= rb->size) rb->writeIndex = 0; // allocate range uint8* data = rb->data + rb->writeIndex; rb->writeIndex += size; return data; } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteRingBuffer.h ================================================ typedef struct { uint8* data; sint32 size; sint32 writeIndex; }LatteRingBuffer_t; LatteRingBuffer_t* LatteRingBuffer_create(uint8* data, uint32 size); uint8* LatteRingBuffer_allocate(LatteRingBuffer_t* rb, sint32 size, sint32 alignment); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteShader.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/OS/libs/gx2/GX2.h" // todo - remove dependency #include "Cafe/GraphicPack/GraphicPack2.h" #include "HW/Latte/Core/Latte.h" #include "HW/Latte/Renderer/Renderer.h" #include "util/helpers/StringParser.h" #include "config/ActiveSettings.h" #include "Cafe/GameProfile/GameProfile.h" #include "util/containers/flat_hash_map.hpp" #if ENABLE_METAL #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" #endif #include <cinttypes> // experimental new decompiler (WIP) #include "util/Zir/EmitterGLSL/ZpIREmitGLSL.h" #include "util/Zir/Core/ZpIRDebug.h" #include "Cafe/HW/Latte/Transcompiler/LatteTC.h" #include "Cafe/HW/Latte/ShaderInfo/ShaderInfo.h" struct _ShaderHashCache { uint64 prevHash1; uint64 prevHash2; uint32* prevProgramCode; uint32 prevProgramSize; }; _ShaderHashCache hashCacheVS = { 0 }; _ShaderHashCache hashCacheGS = { 0 }; _ShaderHashCache hashCachePS = { 0 }; LatteFetchShader* _activeFetchShader = nullptr; LatteDecompilerShader* _activeVertexShader = nullptr; LatteDecompilerShader* _activeGeometryShader = nullptr; LatteDecompilerShader* _activePixelShader = nullptr; // runtime shader cache using SHRC_CACHE_TYPE = ska::flat_hash_map<uint64, LatteDecompilerShader*>; SHRC_CACHE_TYPE sVertexShaders(512); SHRC_CACHE_TYPE sGeometryShaders(512); SHRC_CACHE_TYPE sPixelShaders(512); uint64 _shaderBaseHash_vs; uint64 _shaderBaseHash_gs; uint64 _shaderBaseHash_ps; std::atomic_int g_compiled_shaders_total = 0; std::atomic_int g_compiled_shaders_async = 0; LatteFetchShader* LatteSHRC_GetActiveFetchShader() { return _activeFetchShader; } LatteDecompilerShader* LatteSHRC_GetActiveVertexShader() { return _activeVertexShader; } LatteDecompilerShader* LatteSHRC_GetActiveGeometryShader() { return _activeGeometryShader; } LatteDecompilerShader* LatteSHRC_GetActivePixelShader() { return _activePixelShader; } inline ska::flat_hash_map<uint64, LatteDecompilerShader*>& LatteSHRC_GetCacheByType(LatteConst::ShaderType shaderType) { if (shaderType == LatteConst::ShaderType::Vertex) return sVertexShaders; else if (shaderType == LatteConst::ShaderType::Geometry) return sGeometryShaders; cemu_assert_debug(shaderType == LatteConst::ShaderType::Pixel); return sPixelShaders; } // calculate hash from shader binary // this algorithm could be more efficient since we could leverage the fact that the size is always aligned to 8 byte // but since this is baked into the shader names used for gfx packs and shader caches we can't really change this void _calcShaderHashGeneric(uint32* programCode, uint32 programSize, uint64& outputHash1, uint64& outputHash2) { outputHash1 = 0; outputHash2 = 0; for (uint32 i = 0; i < programSize / 4; i++) { uint32 temp = programCode[i]; outputHash1 += (uint64)temp; outputHash2 ^= (uint64)temp; outputHash1 = (outputHash1 << 3) | (outputHash1 >> 61); outputHash2 = (outputHash2 >> 7) | (outputHash2 << 57); } } void _calculateShaderProgramHash(uint32* programCode, uint32 programSize, _ShaderHashCache* hashCache, uint64* outputHash1, uint64* outputHash2) { uint64 progHash1 = 0; uint64 progHash2 = 0; if (!programCode) { hashCache->prevProgramCode = NULL; hashCache->prevProgramSize = 0; hashCache->prevHash1 = 0; hashCache->prevHash2 = 0; } else if (hashCache->prevProgramCode != programCode || hashCache->prevProgramSize != programSize) { _calcShaderHashGeneric(programCode, programSize, progHash1, progHash2); hashCache->prevProgramCode = programCode; hashCache->prevProgramSize = programSize; hashCache->prevHash1 = progHash1; hashCache->prevHash2 = progHash2; } else { progHash1 = hashCache->prevHash1; progHash2 = hashCache->prevHash2; } *outputHash1 = progHash1; *outputHash2 = progHash2; } void LatteSHRC_ResetCachedShaderHash() { hashCacheVS.prevProgramCode = 0; hashCacheVS.prevProgramSize = 0; hashCacheGS.prevProgramCode = 0; hashCacheGS.prevProgramSize = 0; hashCachePS.prevProgramCode = 0; hashCachePS.prevProgramSize = 0; } LatteShaderPSInputTable _activePSImportTable; LatteShaderPSInputTable* LatteSHRC_GetPSInputTable() { return &_activePSImportTable; } void LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader) { bool removed = false; auto& cache = LatteSHRC_GetCacheByType(shader->shaderType); // remove from hashtable auto baseIt = cache.find(shader->baseHash); if (baseIt == cache.end()) { cemu_assert_suspicious(); // deleting from runtime cache but shader is not present? } else if (baseIt->second == shader) { cemu_assert_debug(baseIt->second == shader); cache.erase(baseIt); if (shader->next) { cemu_assert_debug(shader->baseHash == shader->next->baseHash); cache.emplace(shader->baseHash, shader->next); } shader->next = 0; removed = true; } else { // remove from chain LatteDecompilerShader* shaderChain = baseIt->second; while (shaderChain->next) { if (shaderChain->next == shader) { shaderChain->next = shaderChain->next->next; removed = true; break; } } } cemu_assert(removed); } void LatteSHRC_RemoveFromCacheByHash(uint64 shader_base_hash, uint64 shader_aux_hash, LatteConst::ShaderType type) { LatteDecompilerShader* shader = nullptr; if (type == LatteConst::ShaderType::Vertex) shader = LatteSHRC_FindVertexShader(shader_base_hash, shader_aux_hash); else if (type == LatteConst::ShaderType::Geometry) shader = LatteSHRC_FindGeometryShader(shader_base_hash, shader_aux_hash); else if (type == LatteConst::ShaderType::Pixel) shader = LatteSHRC_FindPixelShader(shader_base_hash, shader_aux_hash); if (shader) LatteSHRC_RemoveFromCache(shader); } void LatteShader_free(LatteDecompilerShader* shader) { LatteSHRC_RemoveFromCache(shader); if (shader->shader) delete shader->shader; shader->shader = nullptr; delete shader; } void LatteShader_CreatePSInputTable(LatteShaderPSInputTable* psInputTable, uint32* contextRegisters) { // PS control uint32 psControl0 = contextRegisters[mmSPI_PS_IN_CONTROL_0]; uint32 spi0_positionEnable = (psControl0 >> 8) & 1; uint32 spi0_positionCentroid = (psControl0 >> 9) & 1; cemu_assert_debug(spi0_positionCentroid == 0); // controls gl_FragCoord uint32 spi0_positionAddr = (psControl0 >> 10) & 0x1F; // controls gl_FragCoord uint32 spi0_paramGen = (psControl0 >> 15) & 0xF; // used for gl_PointCoords uint32 spi0_paramGenAddr = (psControl0 >> 19) & 0x7F; sint32 importIndex = 0; //cemu_assert_debug(((psControl0>>26)&3) == 1); // BARYC_SAMPLE_CNTL //cemu_assert_debug((psControl0&(1 << 28)) == 0); // PERSP_GRADIENT_ENA //cemu_assert_debug((psControl0&(1 << 29)) == 0); // LINEAR_GRADIENT_ENA // if LINEAR_GRADIENT_ENA_bit is enabled, the pixel shader accesses gl_ClipSize? // VS/GS parameters uint32 numPSInputs = contextRegisters[mmSPI_PS_IN_CONTROL_0] & 0x3F; uint64 key = 0; if (spi0_positionEnable) { key += (uint64)spi0_positionAddr + 1; } // parameter gen if (spi0_paramGen != 0) { key += std::rotr<uint64>(spi0_paramGen, 7); key += std::rotr<uint64>(spi0_paramGenAddr, 3); psInputTable->paramGen = spi0_paramGen; psInputTable->paramGenGPR = spi0_paramGenAddr; } else { psInputTable->paramGen = 0; } // semantic imports from vertex shader #ifdef CEMU_DEBUG_ASSERT uint8 semanticMask[256 / 8] = { 0 }; #endif cemu_assert_debug(numPSInputs <= GPU7_PS_MAX_INPUTS); numPSInputs = std::min<uint32>(numPSInputs, GPU7_PS_MAX_INPUTS); for (uint32 f = 0; f < numPSInputs; f++) { uint32 psInputControl = contextRegisters[mmSPI_PS_INPUT_CNTL_0 + f]; uint32 psSemanticId = (psInputControl & 0xFF); uint8 defaultValue = (psInputControl>>8)&3; // default: // 0 -> 0.0 0.0 0.0 0.0 // 1 -> 0.0 0.0 0.0 1.0 // 2 -> 1.0 1.0 1.0 0.0 // 3 -> 1.0 1.0 1.0 1.0 cemu_assert_debug(defaultValue <= 1); uint32 uknBits = psInputControl & ~((0xFF)|(0x3<<8) | (1 << 10) | (1 << 12)); uknBits &= ~0x800; // FLAT_SHADE //cemu_assert_debug(uknBits == 0); //cemu_assert_debug(((psInputControl >> 11) & 1) == 0); // centroid //cemu_assert_debug(((psInputControl >> 17) & 1) == 0); // point sprite coord cemu_assert_debug(psSemanticId != 0xFF); key += (uint64)psInputControl; key = std::rotl<uint64>(key, 7); if (spi0_positionEnable && f == spi0_positionAddr) { psInputTable->import[f].semanticId = LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION; psInputTable->import[f].isFlat = false; psInputTable->import[f].isNoPerspective = false; key += (uint64)0x33; } else { #ifdef CEMU_DEBUG_ASSERT if (semanticMask[psSemanticId >> 3] & (1 << (psSemanticId & 7))) { cemuLog_logDebug(LogType::Force, "SemanticId already used"); } semanticMask[psSemanticId >> 3] |= (1 << (psSemanticId & 7)); #endif psInputTable->import[f].semanticId = psSemanticId; psInputTable->import[f].isFlat = (psInputControl&(1 << 10)) != 0; psInputTable->import[f].isNoPerspective = (psInputControl&(1 << 12)) != 0; } } psInputTable->key = key; psInputTable->count = numPSInputs; } // both vertex and geometry/pixel shader depend on PS inputs // we prepare the PS import info in advance void LatteShader_UpdatePSInputs(uint32* contextRegisters) { LatteShader_CreatePSInputTable(&_activePSImportTable, contextRegisters); } void LatteShader_CreateRendererShader(LatteDecompilerShader* shader, bool compileAsync) { if (shader->hasError ) { cemuLog_log(LogType::Force, "Unable to compile shader {:016x}", shader->baseHash); return; } GraphicPack2::GP_SHADER_TYPE gpShaderType; RendererShader::ShaderType shaderType; if (shader->shaderType == LatteConst::ShaderType::Vertex) { shaderType = RendererShader::ShaderType::kVertex; gpShaderType = GraphicPack2::GP_SHADER_TYPE::VERTEX; } else if (shader->shaderType == LatteConst::ShaderType::Geometry) { shaderType = RendererShader::ShaderType::kGeometry; gpShaderType = GraphicPack2::GP_SHADER_TYPE::GEOMETRY; } else if (shader->shaderType == LatteConst::ShaderType::Pixel) { shaderType = RendererShader::ShaderType::kFragment; gpShaderType = GraphicPack2::GP_SHADER_TYPE::PIXEL; } // check if a custom shader is present std::string shaderSrc; const std::string* customShaderSrc = GraphicPack2::FindCustomShaderSource(shader->baseHash, shader->auxHash, gpShaderType, g_renderer->GetType() == RendererAPI::Vulkan, g_renderer->GetType() == RendererAPI::Metal); if (customShaderSrc) { shaderSrc.assign(*customShaderSrc); shader->isCustomShader = true; } else shaderSrc.assign(shader->strBuf_shaderSource->c_str()); if (shaderType == RendererShader::ShaderType::kVertex && (shader->baseHash == 0x15bc7edf9de2ed30 || shader->baseHash == 0x83a697d61a3b9202 || shader->baseHash == 0x97bc44a5028381c6 || shader->baseHash == 0x24838b84d15a1da1)) { cemuLog_logDebug(LogType::Force, "Filtered shader to avoid AMD crash"); shader->shader = nullptr; shader->hasError = true; return; } // create shader shader->shader = g_renderer->shader_create(shaderType, shader->baseHash, shader->auxHash, shaderSrc, true, shader->isCustomShader); if (shader->shader == nullptr) shader->hasError = true; // after renderer shader creation we can throw away any intermediate info LatteShader_CleanupAfterCompile(shader); } void LatteShader_FinishCompilation(LatteDecompilerShader* shader) { if (shader->hasError) { cemuLog_logDebug(LogType::Force, "LatteShader_finishCompilation(): Skipped because of error in shader {:x}", shader->baseHash); return; } shader->shader->WaitForCompiled(); LatteShader_prepareSeparableUniforms(shader); LatteShader_CleanupAfterCompile(shader); } void LatteSHRC_RegisterShader(LatteDecompilerShader* shader, uint64 baseHash, uint64 auxHash) { auto& cache = LatteSHRC_GetCacheByType(shader->shaderType); shader->baseHash = baseHash; shader->auxHash = auxHash; auto it = cache.find(baseHash); if (it == cache.end()) { shader->next = nullptr; cache.emplace(shader->baseHash, shader); } else { shader->next = it->second->next; it->second->next = shader; } } LatteDecompilerShader* LatteSHRC_GetFromChain(LatteDecompilerShader* baseShader, uint64 baseHash, uint64 auxHash) { while (baseShader && baseShader->auxHash != auxHash) baseShader = baseShader->next; return baseShader; } LatteDecompilerShader* LatteSHRC_Get(SHRC_CACHE_TYPE& cache, uint64 baseHash, uint64 auxHash) { auto it = cache.find(baseHash); if (it == cache.end()) return nullptr; LatteDecompilerShader* baseShader = it->second; if (!baseShader) return nullptr; while (baseShader && baseShader->auxHash != auxHash) baseShader = baseShader->next; return baseShader; } LatteDecompilerShader* LatteSHRC_FindVertexShader(uint64 baseHash, uint64 auxHash) { return LatteSHRC_Get(sVertexShaders, baseHash, auxHash); } LatteDecompilerShader* LatteSHRC_FindGeometryShader(uint64 baseHash, uint64 auxHash) { return LatteSHRC_Get(sGeometryShaders, baseHash, auxHash); } LatteDecompilerShader* LatteSHRC_FindPixelShader(uint64 baseHash, uint64 auxHash) { return LatteSHRC_Get(sPixelShaders, baseHash, auxHash); } // update the currently active fetch shader void LatteShaderSHRC_UpdateFetchShader() { _activeFetchShader = LatteFetchShader::FindByGPUState(); } void LatteShader_CleanupAfterCompile(LatteDecompilerShader* shader) { if (shader->strBuf_shaderSource) { delete shader->strBuf_shaderSource; shader->strBuf_shaderSource = nullptr; } } void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShader* shader) { if (!ActiveSettings::DumpShadersEnabled()) return; const char* suffix = ""; if (shader->shaderType == LatteConst::ShaderType::Vertex) suffix = "vs"; else if (shader->shaderType == LatteConst::ShaderType::Geometry) suffix = "gs"; else if (shader->shaderType == LatteConst::ShaderType::Pixel) suffix = "ps"; FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix)); if (fs) { if (shader->strBuf_shaderSource) fs->writeData(shader->strBuf_shaderSource->c_str(), shader->strBuf_shaderSource->getLen()); delete fs; } } void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uint8* programCode, uint32 programLen) { if (!ActiveSettings::DumpShadersEnabled()) return; const char* suffix = ""; if (type == SHADER_DUMP_TYPE_FETCH) suffix = "fs"; else if (type == SHADER_DUMP_TYPE_VERTEX) suffix = "vs"; else if (type == SHADER_DUMP_TYPE_GEOMETRY) suffix = "gs"; else if (type == SHADER_DUMP_TYPE_PIXEL) suffix = "ps"; else if (type == SHADER_DUMP_TYPE_COPY) suffix = "copy"; else if (type == SHADER_DUMP_TYPE_COMPUTE) suffix = "compute"; FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix)); if (fs) { fs->writeData(programCode, programLen); delete fs; } } void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader) { uint32* vsProgramCode = (uint32*)vertexShaderPtr; // update hash from vertex shader data uint64 vsHash1 = 0; uint64 vsHash2 = 0; _calculateShaderProgramHash(vsProgramCode, vertexShaderSize, &hashCacheVS, &vsHash1, &vsHash2); uint64 vsHash = vsHash1 + vsHash2 + _activeFetchShader->key + _activePSImportTable.key + (usesGeometryShader ? 0x1111ULL : 0ULL); uint32 tmp = LatteGPUState.contextNew.PA_CL_VTE_CNTL.getRawValue() ^ 0x43F; vsHash += tmp; auto primitiveType = LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); // TODO: include always in the hash in case of geometry shader or rect shader on Metal if (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS) { vsHash += 13ULL; } else if (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS) { // required for Vulkan since we have to write the pointsize in the shader vsHash += 71ULL; } vsHash += (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] ? 21 : 0); // halfZ if (LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_CLIP_SPACE_DEF()) vsHash += 0x1537; #if ENABLE_METAL if (g_renderer->GetType() == RendererAPI::Metal) { bool isRectVertexShader = (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS); if ((usesGeometryShader || isRectVertexShader) || _activeFetchShader->mtlFetchVertexManually) { for (sint32 g = 0; g < _activeFetchShader->bufferGroups.size(); g++) { LatteParsedFetchShaderBufferGroup_t& group = _activeFetchShader->bufferGroups[g]; uint32 bufferIndex = group.attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; vsHash += (uint64)bufferStride; vsHash = std::rotl<uint64>(vsHash, 7); } } if (!(usesGeometryShader || isRectVertexShader)) { if (LatteGPUState.contextNew.IsRasterizationEnabled()) vsHash += 51ULL; // Vertex fetch if (_activeFetchShader->mtlFetchVertexManually) vsHash += 349ULL; } } #endif _shaderBaseHash_vs = vsHash; } void LatteSHRC_UpdateGSBaseHash(uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize) { // update hash from geometry shader data uint64 gsHash1 = 0; uint64 gsHash2 = 0; _calculateShaderProgramHash((uint32*)geometryShaderPtr, geometryShaderSize, &hashCacheGS, &gsHash1, &gsHash2); // get geometry shader uint64 gsHash = gsHash1 + gsHash2; gsHash += (uint64)_activeVertexShader->ringParameterCount; gsHash += (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] ? 21 : 0); _shaderBaseHash_gs = gsHash; } void LatteSHRC_UpdatePSBaseHash(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader) { uint32* psProgramCode = (uint32*)pixelShaderPtr; // update hash from pixel shader data uint64 psHash1 = 0; uint64 psHash2 = 0; _calculateShaderProgramHash(psProgramCode, pixelShaderSize, &hashCachePS, &psHash1, &psHash2); // get vertex shader uint64 psHash = psHash1 + psHash2 + _activePSImportTable.key + (usesGeometryShader ? hashCacheGS.prevHash1 : 0ULL); _shaderBaseHash_ps = psHash; } uint64 LatteSHRC_CalcVSAuxHash(LatteDecompilerShader* vertexShader, uint32* contextRegisters) { // todo - include texture types in aux hash similar to how it is already done in pixel shader // or maybe there is a way to figure out the proper texture types? uint64 auxHash = 0; if(vertexShader->hasStreamoutBufferWrite) { // hash stride for streamout buffers for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if(!vertexShader->streamoutBufferWriteMask[i]) continue; uint32 bufferStride = contextRegisters[mmVGT_STRMOUT_VTX_STRIDE_0 + i * 4]; auxHash = std::rotl<uint64>(auxHash, 7); auxHash += (uint64)bufferStride; } } // textures can affect the shader. Either by their type (2D, 3D, cubemap) or by their format (float vs integer) uint64 auxHashTex = 0; for (uint8 i = 0; i < vertexShader->textureUnitListCount; i++) { uint8 t = vertexShader->textureUnitList[i]; uint32 word4 = contextRegisters[Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS + t * 7 + 4]; if ((word4 & 0x300) == 0x100) { // integer format auxHashTex = std::rotl<uint64>(auxHashTex, 7); auxHashTex += 0x333; } } return auxHash + auxHashTex; } uint64 LatteSHRC_CalcGSAuxHash(LatteDecompilerShader* geometryShader) { // todo - include texture types in aux hash similar to how it is already done in pixel shader return 0; } uint64 LatteSHRC_CalcPSAuxHash(LatteDecompilerShader* pixelShader, uint32* contextRegisters) { uint64 auxHash = 0; // CB_SHADER_MASK can remap pixel shader outputs auxHash = (auxHash >> 3) | (auxHash << 61); auxHash += (uint64)contextRegisters[mmCB_SHADER_MASK]; // alpha test uint8 alphaTestFunc = contextRegisters[Latte::REGADDR::SX_ALPHA_TEST_CONTROL] & 0x7; uint8 alphaTestEnable = (contextRegisters[Latte::REGADDR::SX_ALPHA_TEST_CONTROL] >> 3) & 1; if (alphaTestEnable) { auxHash += (uint64)alphaTestFunc; auxHash = (auxHash >> 3) | (auxHash << 61); auxHash += 1; } // texture types (2D, 3D, cubemap etc.) affect the shader too for (uint8 i = 0; i < pixelShader->textureUnitListCount; i++) { uint8 t = pixelShader->textureUnitList[i]; uint32 word0 = contextRegisters[Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS + t * 7 + 0]; uint32 dim = (word0 & 7); auxHash = (auxHash << 3) | (auxHash >> 61); auxHash += (uint64)dim; } #if ENABLE_METAL if (g_renderer->GetType() == RendererAPI::Metal) { // Textures as render targets for (uint32 i = 0; i < pixelShader->textureUnitListCount; i++) { uint8 t = pixelShader->textureUnitList[i]; auxHash = std::rotl<uint64>(auxHash, 11); auxHash += (uint64)pixelShader->textureRenderTargetIndex[t]; } // Color buffers for (uint8 i = 0; i < LATTE_NUM_COLOR_TARGET; i++) { auto format = LatteMRT::GetColorBufferFormat(i, LatteGPUState.contextNew); uint8 dataType = (uint8)GetMtlPixelFormatInfo(format, false).dataType; auxHash = std::rotl<uint64>(auxHash, 7); auxHash += (uint64)dataType; } // Depth buffer bool hasDepthBuffer = LatteMRT::GetActiveDepthBufferMask(LatteGPUState.contextNew); if (hasDepthBuffer) { auxHash = std::rotl<uint64>(auxHash, 5); auxHash += 13u; } } #endif return auxHash; } LatteDecompilerShader* LatteShader_CreateShaderFromDecompilerOutput(LatteDecompilerOutput_t& decompilerOutput, uint64 baseHash, bool calculateAuxHash, uint64 optionalAuxHash, uint32* contextRegister) { LatteDecompilerShader* shader = decompilerOutput.shader; shader->baseHash = baseHash; // copy resource mapping // HACK if (g_renderer->GetType() == RendererAPI::OpenGL) shader->resourceMapping = decompilerOutput.resourceMappingGL; else if (g_renderer->GetType() == RendererAPI::Vulkan) shader->resourceMapping = decompilerOutput.resourceMappingVK; #if ENABLE_METAL else shader->resourceMapping = decompilerOutput.resourceMappingMTL; #endif // copy texture info shader->textureUnitMask2 = decompilerOutput.textureUnitMask; // copy streamout info shader->streamoutBufferWriteMask = decompilerOutput.streamoutBufferWriteMask; shader->hasStreamoutBufferWrite = decompilerOutput.streamoutBufferWriteMask.any(); // copy uniform offsets // for OpenGL these are retrieved in _prepareSeparableUniforms() // HACK if (g_renderer->GetType() == RendererAPI::Vulkan || g_renderer->GetType() == RendererAPI::Metal) { shader->uniform.loc_remapped = decompilerOutput.uniformOffsetsVK.offset_remapped; shader->uniform.loc_uniformRegister = decompilerOutput.uniformOffsetsVK.offset_uniformRegister; shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsVK.count_uniformRegister; shader->uniform.loc_windowSpaceToClipSpaceTransform = decompilerOutput.uniformOffsetsVK.offset_windowSpaceToClipSpaceTransform; shader->uniform.loc_alphaTestRef = decompilerOutput.uniformOffsetsVK.offset_alphaTestRef; shader->uniform.loc_pointSize = decompilerOutput.uniformOffsetsVK.offset_pointSize; shader->uniform.loc_fragCoordScale = decompilerOutput.uniformOffsetsVK.offset_fragCoordScale; for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++) { if (decompilerOutput.uniformOffsetsVK.offset_texScale[t] >= 0) { LatteUniformTextureScaleEntry_t entry = { 0 }; entry.texUnit = t; entry.uniformLocation = decompilerOutput.uniformOffsetsVK.offset_texScale[t]; shader->uniform.list_ufTexRescale.push_back(entry); } } shader->uniform.loc_verticesPerInstance = decompilerOutput.uniformOffsetsVK.offset_verticesPerInstance; for (sint32 t = 0; t < LATTE_NUM_STREAMOUT_BUFFER; t++) shader->uniform.loc_streamoutBufferBase[t] = decompilerOutput.uniformOffsetsVK.offset_streamoutBufferBase[t]; shader->uniform.uniformRangeSize = decompilerOutput.uniformOffsetsVK.offset_endOfBlock; } else { shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsGL.count_uniformRegister; } // calculate aux hash if (calculateAuxHash) { if (decompilerOutput.shaderType == LatteConst::ShaderType::Vertex) { uint64 vsAuxHash = LatteSHRC_CalcVSAuxHash(shader, contextRegister); shader->auxHash = vsAuxHash; } else if (decompilerOutput.shaderType == LatteConst::ShaderType::Geometry) { uint64 gsAuxHash = LatteSHRC_CalcGSAuxHash(shader); shader->auxHash = gsAuxHash; } else if (decompilerOutput.shaderType == LatteConst::ShaderType::Pixel) { uint64 psAuxHash = LatteSHRC_CalcPSAuxHash(shader, contextRegister); shader->auxHash = psAuxHash; } else cemu_assert_debug(false); } else { shader->auxHash = optionalAuxHash; } return shader; } void LatteShader_GetDecompilerOptions(LatteDecompilerOptions& options, LatteConst::ShaderType shaderType, bool geometryShaderEnabled) { options.usesGeometryShader = geometryShaderEnabled; options.spirvInstrinsics.hasRoundingModeRTEFloat32 = false; options.useTFViaSSBO = g_renderer->UseTFViaSSBO(); if (g_renderer->GetType() == RendererAPI::Vulkan) { options.spirvInstrinsics.hasRoundingModeRTEFloat32 = VulkanRenderer::GetInstance()->HasSPRIVRoundingModeRTE32(); } options.strictMul = g_current_game_profile->GetAccurateShaderMul() != AccurateShaderMulOption::False; } LatteDecompilerShader* LatteShader_CompileSeparableVertexShader2(uint64 baseHash, uint64& vsAuxHash, uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader) { /* Analyze shader to gather general information about inputs/outputs */ Latte::ShaderDescription shaderDescription; if (!shaderDescription.analyzeShaderCode(vertexShaderPtr, vertexShaderSize, LatteConst::ShaderType::Vertex)) { assert_dbg(); return nullptr; } /* Create context dependent IO info for this shader */ //Latte::ShaderInstanceInfo assert_dbg(); // todo - Use ShaderInstanceInfo when generating the GLSL (GLSL::Emit() should take a 'GLSLInfoSource' class which has a bunch of virtual methods for retrieving uniform names etc. We then override this class and plug in logic using ShaderInstanceInfo /* Translate R600Plus to GLSL */ ZpIR::DebugPrinter irDebugPrinter; LatteTCGenIR genIR; genIR.setVertexShaderContext(fetchShader, LatteGPUState.contextRegister + mmSQ_VTX_SEMANTIC_0); auto irObj = genIR.transcompileLatteToIR(vertexShaderPtr, vertexShaderSize, LatteTCGenIR::VERTEX); // debug output (before register allocation) irDebugPrinter.setShowPhysicalRegisters(false); irDebugPrinter.debugPrint(irObj); // register allocation ZirPass::RegisterAllocatorForGLSL ra(irObj); ra.applyPass(); // debug output (after register allocation) irDebugPrinter.setShowPhysicalRegisters(true); irDebugPrinter.setPhysicalRegisterNameSource(ZirPass::RegisterAllocatorForGLSL::DebugPrintHelper_getPhysRegisterName); irDebugPrinter.debugPrint(irObj); // gen GLSL StringBuf glslSourceBuffer(64 * 1024); // emit GLSL header assert_dbg(); // todo // emit main ZirEmitter::GLSL emitter; emitter.Emit(irObj, &glslSourceBuffer); // debug copy to string std::string dbg; dbg.insert(0, glslSourceBuffer.c_str(), glslSourceBuffer.getLen()); assert_dbg(); return nullptr; } // compile new vertex shader (relies partially on current state) LatteDecompilerShader* LatteShader_CompileSeparableVertexShader(uint64 baseHash, uint64& vsAuxHash, uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader) { // new decompiler test //LatteShader_CompileSeparableVertexShader2(baseHash, vsAuxHash, vertexShaderPtr, vertexShaderSize, usesGeometryShader, fetchShader); // legacy decompiler LatteDecompilerOptions options; LatteShader_GetDecompilerOptions(options, LatteConst::ShaderType::Vertex, usesGeometryShader); LatteDecompilerOutput_t decompilerOutput{}; LatteDecompiler_DecompileVertexShader(_shaderBaseHash_vs, LatteGPUState.contextRegister, vertexShaderPtr, vertexShaderSize, fetchShader, options, &decompilerOutput); LatteDecompilerShader* vertexShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, baseHash, true, 0, LatteGPUState.contextRegister); vsAuxHash = vertexShader->auxHash; if (vertexShader->hasError == false) { uint8* fsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset(LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 0] << 8); uint32 fsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 1] << 3; LatteShaderCache_writeSeparableVertexShader(vertexShader->baseHash, vertexShader->auxHash, fsProgramCode, fsProgramSize, vertexShaderPtr, vertexShaderSize, LatteGPUState.contextRegister, usesGeometryShader); } LatteShader_DumpShader(vertexShader->baseHash, vertexShader->auxHash, vertexShader); LatteShader_DumpRawShader(vertexShader->baseHash, vertexShader->auxHash, SHADER_DUMP_TYPE_VERTEX, vertexShaderPtr, vertexShaderSize); LatteShader_CreateRendererShader(vertexShader, false); performanceMonitor.numCompiledVS++; if (g_renderer->GetType() == RendererAPI::OpenGL) { if (vertexShader->shader) vertexShader->shader->PreponeCompilation(true); LatteShader_FinishCompilation(vertexShader); } LatteSHRC_RegisterShader(vertexShader, vertexShader->baseHash, vertexShader->auxHash); return vertexShader; } LatteDecompilerShader* LatteShader_CompileSeparableGeometryShader(uint64 baseHash, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize) { LatteDecompilerOptions options; LatteShader_GetDecompilerOptions(options, LatteConst::ShaderType::Geometry, true); LatteDecompilerOutput_t decompilerOutput{}; LatteDecompiler_DecompileGeometryShader(_shaderBaseHash_gs, LatteGPUState.contextRegister, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, _activeVertexShader->ringParameterCount, options, &decompilerOutput); LatteDecompilerShader* geometryShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, baseHash, true, 0, LatteGPUState.contextRegister); if (geometryShader->hasError == false) { LatteShaderCache_writeSeparableGeometryShader(geometryShader->baseHash, geometryShader->auxHash, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, LatteGPUState.contextRegister, LatteGPUState.contextNew.GetSpecialStateValues(), _activeVertexShader->ringParameterCount); } LatteShader_DumpShader(geometryShader->baseHash, geometryShader->auxHash, geometryShader); LatteShader_DumpRawShader(geometryShader->baseHash, geometryShader->auxHash, SHADER_DUMP_TYPE_GEOMETRY, geometryShaderPtr, geometryShaderSize); LatteShader_DumpRawShader(geometryShader->baseHash, geometryShader->auxHash, SHADER_DUMP_TYPE_COPY, geometryCopyShader, geometryCopyShaderSize); LatteShader_CreateRendererShader(geometryShader, false); performanceMonitor.numCompiledGS++; if (g_renderer->GetType() == RendererAPI::OpenGL) { if (geometryShader->shader) geometryShader->shader->PreponeCompilation(true); LatteShader_FinishCompilation(geometryShader); } LatteSHRC_RegisterShader(geometryShader, geometryShader->baseHash, geometryShader->auxHash); return geometryShader; } LatteDecompilerShader* LatteShader_CompileSeparablePixelShader(uint64 baseHash, uint64& psAuxHash, uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader) { LatteDecompilerOptions options; LatteShader_GetDecompilerOptions(options, LatteConst::ShaderType::Pixel, usesGeometryShader); LatteDecompilerOutput_t decompilerOutput{}; LatteDecompiler_DecompilePixelShader(baseHash, LatteGPUState.contextRegister, pixelShaderPtr, pixelShaderSize, options, &decompilerOutput); LatteDecompilerShader* pixelShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, baseHash, true, 0, LatteGPUState.contextRegister); psAuxHash = pixelShader->auxHash; LatteShader_DumpShader(_shaderBaseHash_ps, psAuxHash, pixelShader); LatteShader_DumpRawShader(_shaderBaseHash_ps, psAuxHash, SHADER_DUMP_TYPE_PIXEL, pixelShaderPtr, pixelShaderSize); LatteShader_CreateRendererShader(pixelShader, false); performanceMonitor.numCompiledPS++; if (pixelShader->hasError == false) { LatteShaderCache_writeSeparablePixelShader(_shaderBaseHash_ps, psAuxHash, pixelShaderPtr, pixelShaderSize, LatteGPUState.contextRegister, usesGeometryShader); } if (g_renderer->GetType() == RendererAPI::OpenGL) { if (pixelShader->shader) pixelShader->shader->PreponeCompilation(true); LatteShader_FinishCompilation(pixelShader); } LatteSHRC_RegisterShader(pixelShader, _shaderBaseHash_ps, psAuxHash); return pixelShader; } void LatteSHRC_UpdateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader) { // todo - should include VTX_SEMANTIC table in state LatteSHRC_UpdateVSBaseHash(vertexShaderPtr, vertexShaderSize, usesGeometryShader); uint64 vsAuxHash = 0; auto itBaseShader = sVertexShaders.find(_shaderBaseHash_vs); LatteDecompilerShader* vertexShader = nullptr; if (itBaseShader != sVertexShaders.end()) { vsAuxHash = LatteSHRC_CalcVSAuxHash(itBaseShader->second, LatteGPUState.contextRegister); vertexShader = LatteSHRC_GetFromChain(itBaseShader->second, _shaderBaseHash_vs, vsAuxHash); } if (!vertexShader) vertexShader = LatteShader_CompileSeparableVertexShader(_shaderBaseHash_vs, vsAuxHash, vertexShaderPtr, vertexShaderSize, usesGeometryShader, _activeFetchShader); if (vertexShader->hasError) { LatteGPUState.activeShaderHasError = true; return; } _activeVertexShader = vertexShader; } void LatteSHRC_UpdateGeometryShader(bool usesGeometryShader, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize) { if (!usesGeometryShader || !_activeVertexShader) { _shaderBaseHash_gs = 0; _activeGeometryShader = nullptr; return; } LatteSHRC_UpdateGSBaseHash(geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize); auto itBaseShader = sGeometryShaders.find(_shaderBaseHash_gs); LatteDecompilerShader* geometryShader; if (itBaseShader != sGeometryShaders.end()) { // geometry shader already known geometryShader = itBaseShader->second; cemu_assert_debug(LatteSHRC_CalcGSAuxHash(geometryShader) == 0); } else { // decompile geometry shader geometryShader = LatteShader_CompileSeparableGeometryShader(_shaderBaseHash_gs, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize); } if (geometryShader->hasError) { LatteGPUState.activeShaderHasError = true; return; } _activeGeometryShader = geometryShader; } void LatteSHRC_UpdatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader) { LatteSHRC_UpdatePSBaseHash(pixelShaderPtr, pixelShaderSize, usesGeometryShader); uint64 psAuxHash = 0; auto itBaseShader = sPixelShaders.find(_shaderBaseHash_ps); LatteDecompilerShader* pixelShader = nullptr; if (itBaseShader != sPixelShaders.end()) { psAuxHash = LatteSHRC_CalcPSAuxHash(itBaseShader->second, LatteGPUState.contextRegister); pixelShader = LatteSHRC_GetFromChain(itBaseShader->second, _shaderBaseHash_ps, psAuxHash); } if (!pixelShader) pixelShader = LatteShader_CompileSeparablePixelShader(_shaderBaseHash_ps, psAuxHash, pixelShaderPtr, pixelShaderSize, usesGeometryShader); if (pixelShader->hasError) { LatteGPUState.activeShaderHasError = true; return; } _activePixelShader = pixelShader; } void LatteSHRC_UpdateActiveShaders() { // check if geometry shader is used auto gsMode = LatteGPUState.contextNew.VGT_GS_MODE.get_MODE(); cemu_assert_debug(LatteGPUState.contextNew.VGT_GS_MODE.get_ES_PASSTHRU() == false); // todo: Support for ES passthrough and cut mode in mmVGT_GS_MODE bool geometryShaderUsed = false; if (gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::OFF) { geometryShaderUsed = false; } else if (gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::SCENARIO_G) { // could also be compute shader? geometryShaderUsed = true; } else { cemu_assert_debug(false); } // get shader programs uint8* psProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_PS] & 0xFFFFFF) << 8); uint32 psProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_PS + 1] << 3; uint8* gsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_GS] & 0xFFFFFF) << 8); uint32 gsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_GS + 1] << 3; uint8* vsProgramCode; uint32 vsProgramSize; uint8* copyProgramCode = NULL; uint32 copyProgramSize = 0; if (geometryShaderUsed) { vsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_ES] & 0xFFFFFF) << 8); vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_ES + 1] << 3; copyProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_VS] & 0xFFFFFF) << 8); if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) { copyProgramCode = NULL; debug_printf("copyProgram is NULL but used. Might be because of unsupported vertex/geometry mode?"); } copyProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_VS + 1] << 3; } else { if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) { debug_printf("No vertex shader program set\n"); LatteGPUState.activeShaderHasError = true; return; } vsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_VS] & 0xFFFFFF) << 8); vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_VS + 1] << 3; } // set new shaders LatteGPUState.activeShaderHasError = false; LatteShader_UpdatePSInputs(LatteGPUState.contextRegister); LatteShaderSHRC_UpdateFetchShader(); LatteSHRC_UpdateVertexShader(vsProgramCode, vsProgramSize, geometryShaderUsed); if (LatteGPUState.activeShaderHasError) return; LatteSHRC_UpdateGeometryShader(geometryShaderUsed, gsProgramCode, gsProgramSize, copyProgramCode, copyProgramSize); if (LatteGPUState.activeShaderHasError) return; LatteSHRC_UpdatePixelShader(psProgramCode, psProgramSize, geometryShaderUsed); if (LatteGPUState.activeShaderHasError) return; } // returns the sampler base index for the given shader type sint32 LatteDecompiler_getTextureSamplerBaseIndex(LatteConst::ShaderType shaderType) { uint32 samplerId = LATTE_DECOMPILER_SAMPLER_NONE; if (shaderType == LatteConst::ShaderType::Vertex) return Latte::SAMPLER_BASE_INDEX_VERTEX; else if (shaderType == LatteConst::ShaderType::Pixel) return Latte::SAMPLER_BASE_INDEX_PIXEL; else if (shaderType == LatteConst::ShaderType::Geometry) return Latte::SAMPLER_BASE_INDEX_GEOMETRY; else cemu_assert_suspicious(); return 0; } void LatteSHRC_Init() { cemu_assert_debug(sVertexShaders.empty()); cemu_assert_debug(sGeometryShaders.empty()); cemu_assert_debug(sPixelShaders.empty()); } void LatteSHRC_UnloadAll() { while(!sVertexShaders.empty()) LatteShader_free(sVertexShaders.begin()->second); cemu_assert_debug(sVertexShaders.empty()); while(!sGeometryShaders.empty()) LatteShader_free(sGeometryShaders.begin()->second); cemu_assert_debug(sGeometryShaders.empty()); while(!sPixelShaders.empty()) LatteShader_free(sPixelShaders.begin()->second); cemu_assert_debug(sPixelShaders.empty()); } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteShader.h ================================================ #pragma once #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" void LatteSHRC_Init(); void LatteSHRC_UnloadAll(); void LatteSHRC_ResetCachedShaderHash(); void LatteShaderSHRC_UpdateFetchShader(); void LatteSHRC_UpdateActiveShaders(); struct LatteFetchShader* LatteSHRC_GetActiveFetchShader(); LatteDecompilerShader* LatteSHRC_GetActiveVertexShader(); LatteDecompilerShader* LatteSHRC_GetActiveGeometryShader(); LatteDecompilerShader* LatteSHRC_GetActivePixelShader(); LatteDecompilerShader* LatteSHRC_FindVertexShader(uint64 baseHash, uint64 auxHash); LatteDecompilerShader* LatteSHRC_FindGeometryShader(uint64 baseHash, uint64 auxHash); LatteDecompilerShader* LatteSHRC_FindPixelShader(uint64 baseHash, uint64 auxHash); #define GPU7_PS_MAX_INPUTS 32 struct LatteShaderPSInputTable { struct psImport_t { uint32 semanticId; bool isFlat; bool isNoPerspective; }; psImport_t import[GPU7_PS_MAX_INPUTS]{}; // GPR is index sint32 count; uint64 key; uint8 paramGen; uint8 paramGenGPR; // returns semantic id of vertex shader output by index // the returned semanticId may not have a match in the pixel shader (use hasPSImportForSemanticId to determine matched exports) static sint32 getVertexShaderOutParamSemanticId(uint32* contextRegisters, sint32 index) { cemu_assert_debug(index >= 0 && index < 32); uint32 vsSemanticId = (contextRegisters[mmSPI_VS_OUT_ID_0 + (index / 4)] >> (8 * (index % 4))) & 0xFF; return (sint32)vsSemanticId; } bool hasPSImportForSemanticId(sint32 semanticId) { for (sint32 i = 0; i < this->count; i++) { if (this->import[i].semanticId == semanticId) return true; } return false; } psImport_t* getPSImportBySemanticId(sint32 semanticId) { if (semanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) return nullptr; // get import based on semanticId sint32 psInputIndex = -1; for (sint32 f = 0; f < this->count; f++) { if (this->import[f].semanticId == semanticId) return this->import + f; } return nullptr; } sint32 getPSImportLocationBySemanticId(sint32 semanticId) { if (semanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) return -1; // get import based on semanticId sint32 psInputIndex = -1; for (sint32 f = 0; f < this->count; f++) { if (this->import[f].semanticId == semanticId) return f; } return -1; } }; void LatteShader_CreatePSInputTable(LatteShaderPSInputTable* psInputTable, uint32* contextRegisters); void LatteShader_UpdatePSInputs(uint32* contextRegisters); LatteShaderPSInputTable* LatteSHRC_GetPSInputTable(); void LatteShader_free(LatteDecompilerShader* shader); void LatteSHRC_RemoveFromCacheByHash(uint64 shader_base_hash, uint64 shader_aux_hash, LatteConst::ShaderType type); extern uint64 _shaderBaseHash_fs; extern uint64 _shaderBaseHash_vs; extern uint64 _shaderBaseHash_gs; extern uint64 _shaderBaseHash_ps; void LatteShader_GetDecompilerOptions(struct LatteDecompilerOptions& options, LatteConst::ShaderType shaderType, bool geometryShaderEnabled); LatteDecompilerShader* LatteShader_CreateShaderFromDecompilerOutput(LatteDecompilerOutput_t& decompilerOutput, uint64 baseHash, bool calculateAuxHash, uint64 optionalAuxHash, uint32* contextRegister); void LatteShader_CreateRendererShader(LatteDecompilerShader* shader, bool compileAsync); void LatteShader_FinishCompilation(LatteDecompilerShader* shader); void LatteShader_prepareSeparableUniforms(LatteDecompilerShader* shader); void LatteSHRC_RegisterShader(LatteDecompilerShader* shader, uint64 baseHash, uint64 auxHash); void LatteShader_CleanupAfterCompile(LatteDecompilerShader* shader); #define SHADER_DUMP_TYPE_FETCH 0 #define SHADER_DUMP_TYPE_VERTEX 1 #define SHADER_DUMP_TYPE_GEOMETRY 2 #define SHADER_DUMP_TYPE_PIXEL 3 #define SHADER_DUMP_TYPE_COPY 4 #define SHADER_DUMP_TYPE_COMPUTE 5 void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShader* shader); void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uint8* programCode, uint32 programLen); // shader cache file void LatteShaderCache_Load(); void LatteShaderCache_Close(); void LatteShaderCache_writeSeparableVertexShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* fetchShader, uint32 fetchShaderSize, uint8* vertexShader, uint32 vertexShaderSize, uint32* contextRegisters, bool usesGeometryShader); void LatteShaderCache_writeSeparableGeometryShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* geometryShader, uint32 geometryShaderSize, uint8* gsCopyShader, uint32 gsCopyShaderSize, uint32* contextRegisters, uint32* hleSpecialState, uint32 vsRingParameterCount); void LatteShaderCache_writeSeparablePixelShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* pixelShader, uint32 pixelShaderSize, uint32* contextRegisters, bool usesGeometryShader); // todo - refactor this sint32 LatteDecompiler_getTextureSamplerBaseIndex(LatteConst::ShaderType shaderType); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteShaderAssembly.h ================================================ #pragma once // CF export #define GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION (60) #define GPU7_DECOMPILER_CF_EXPORT_POINT_SIZE (61) // CF instruction set #define GPU7_CF_INST_NOP (0x00) #define GPU7_CF_INST_TEX (0x01) #define GPU7_CF_INST_VTX (0x02) // used only in GS copy program? #define GPU7_CF_INST_LOOP_END (0x05) #define GPU7_CF_INST_LOOP_START_DX10 (0x06) #define GPU7_CF_INST_LOOP_START_NO_AL (0x07) // (Seen in Project Zero, Injustice: Gods Among Us) #define GPU7_CF_INST_LOOP_BREAK (0x09) #define GPU7_CF_INST_JUMP (0x0A) #define GPU7_CF_INST_ELSE (0x0D) #define GPU7_CF_INST_POP (0x0E) #define GPU7_CF_INST_ELSE_AFTER (0x0F) #define GPU7_CF_INST_CALL (0x12) #define GPU7_CF_INST_CALL_FS (0x13) #define GPU7_CF_INST_RETURN (0x14) #define GPU7_CF_INST_EMIT_VERTEX (0x15) // only available in geometry shader #define GPU7_CF_INST_MEM_STREAM0_WRITE (0x20) // for stream out #define GPU7_CF_INST_MEM_STREAM1_WRITE (0x21) // for stream out #define GPU7_CF_INST_MEM_RING_WRITE (0x26) // used to pass data to/from geometry shader #define GPU7_CF_INST_EXPORT (0x27) #define GPU7_CF_INST_EXPORT_DONE (0x28) #define GPU7_CF_INST_ALU_MASK (0x10000) // this mask is used to make sure that there is no collision between any GPU7_CF_INST_* values (e.g. GPU7_CF_INST_ALU_BREAK and GPU7_CF_INST_POP would collide due to different encoding but identical value) #define GPU7_CF_INST_ALU (0x08 | GPU7_CF_INST_ALU_MASK) #define GPU7_CF_INST_ALU_PUSH_BEFORE (0x09 | GPU7_CF_INST_ALU_MASK) #define GPU7_CF_INST_ALU_POP_AFTER (0x0A | GPU7_CF_INST_ALU_MASK) #define GPU7_CF_INST_ALU_POP2_AFTER (0x0B | GPU7_CF_INST_ALU_MASK) #define GPU7_CF_INST_ALU_BREAK (0x0E | GPU7_CF_INST_ALU_MASK) #define GPU7_CF_INST_ALU_ELSE_AFTER (0x0F | GPU7_CF_INST_ALU_MASK) //#define INDEX_NONE (-1) // used to indicate absolute access #define GPU7_INDEX_AR_X (0) #define GPU7_INDEX_AR_Y (1) #define GPU7_INDEX_AR_Z (2) #define GPU7_INDEX_AR_W (3) #define GPU7_INDEX_LOOP (4) #define GPU7_INDEX_GLOBAL (5) #define GPU7_INDEX_GLOBAL_AR_X (6) #define GPU7_TEX_INST_VFETCH (0x00) #define GPU7_TEX_INST_MEM (0x02) #define GPU7_TEX_INST_LD (0x03) #define GPU7_TEX_INST_GET_TEXTURE_RESINFO (0x04) #define GPU7_TEX_INST_GET_COMP_TEX_LOD (0x06) #define GPU7_TEX_INST_GET_GRADIENTS_H (0x07) #define GPU7_TEX_INST_GET_GRADIENTS_V (0x08) #define GPU7_TEX_INST_SET_GRADIENTS_H (0x0B) #define GPU7_TEX_INST_SET_GRADIENTS_V (0x0C) #define GPU7_TEX_INST_SET_CUBEMAP_INDEX (0x0E) #define GPU7_TEX_INST_FETCH4 (0x0F) #define GPU7_TEX_INST_SAMPLE (0x10) // LOD from hw #define GPU7_TEX_INST_SAMPLE_L (0x11) // LOD from srcGpr.w #define GPU7_TEX_INST_SAMPLE_LB (0x12) // todo: Accesses LOD_BIAS field of instruction #define GPU7_TEX_INST_SAMPLE_LZ (0x13) // LOD is 0.0 #define GPU7_TEX_INST_SAMPLE_G (0x14) #define GPU7_TEX_INST_SAMPLE_C (0x18) // sample & compare, LOD from hw #define GPU7_TEX_INST_SAMPLE_C_L (0x19) // sample & compare, LOD from srcGpr.w #define GPU7_TEX_INST_SAMPLE_C_LZ (0x1B) // sample & compare, LOD is 0.0 #define GPU7_TEX_UNNORMALIZED (0) #define GPU7_TEX_NORMALIZED (1) #define GPU7_ALU_SRC_UNUSED (-1) // special value which indicates that the ALU operand is not used #define GPU7_ALU_SRC_IS_GPR(__v) ((__v)>=0 && (__v)<128) // 0-127 #define GPU7_ALU_SRC_IS_CBANK0(__v) ((__v)>=128 && (__v)<160) // 128-159 #define GPU7_ALU_SRC_IS_CBANK1(__v) ((__v)>=160 && (__v)<192) // 160-191 #define GPU7_ALU_SRC_IS_CONST_0F(__v) ((__v)==248) // 248 #define GPU7_ALU_SRC_IS_CONST_1F(__v) ((__v)==249) // 249 #define GPU7_ALU_SRC_IS_CONST_1I(__v) ((__v)==250) // 250 #define GPU7_ALU_SRC_IS_CONST_M1I(__v) ((__v)==251) // 251 (0xFB) #define GPU7_ALU_SRC_IS_CONST_0_5F(__v) ((__v)==252) // 252 (0xFC) #define GPU7_ALU_SRC_IS_LITERAL(__v) ((__v)==253) // 253 (0xFD) #define GPU7_ALU_SRC_IS_PV(__v) ((__v)==254) // 254 (0xFE) #define GPU7_ALU_SRC_IS_PS(__v) ((__v)==255) // 255 (0xFF) #define GPU7_ALU_SRC_IS_CFILE(__v) ((__v)>=256 && (__v)<512) // 256-511 (uniform register) #define GPU7_ALU_SRC_IS_ANY_CONST(__v) ((__v)>=248 && (__v)<253) // special macro to handle all GPU7_ALU_SRC_IS_CONST_* #define GPU7_ALU_SRC_GET_GPR_INDEX(__v) ((__v)) #define GPU7_ALU_SRC_GET_CFILE_INDEX(__v) ((__v)-256) #define GPU7_ALU_SRC_GET_CBANK0_INDEX(__v) ((__v)-128) #define GPU7_ALU_SRC_GET_CBANK1_INDEX(__v) ((__v)-160) #define GPU7_ALU_SRC_LITERAL (0xFD) ================================================ FILE: src/Cafe/HW/Latte/Core/LatteShaderCache.cpp ================================================ #include "Cafe/CafeSystem.h" #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cemu/FileCache/FileCache.h" #include "Cafe/GameProfile/GameProfile.h" #include "WindowSystem.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" #include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" #if ENABLE_METAL #include "Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalPipelineCache.h" #endif #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h" #include <imgui.h> #include "imgui/imgui_extension.h" #include "config/ActiveSettings.h" #include "Cafe/TitleList/GameInfo.h" #include "util/helpers/SystemException.h" #include "Cafe/HW/Latte/Common/RegisterSerializer.h" #include "Cafe/HW/Latte/Common/ShaderSerializer.h" #include "util/helpers/Serializer.h" #include <audio/IAudioAPI.h> #include <util/bootSound/BootSoundReader.h> #include <thread> #if BOOST_OS_WINDOWS #include <psapi.h> #endif #define SHADER_CACHE_COMPILE_QUEUE_SIZE (32) struct { sint32 compiledShaderCount; // number of loaded shaders sint32 vertexShaderCount; sint32 geometryShaderCount; sint32 pixelShaderCount; }shaderCacheScreenStats; struct { ImTextureID textureTVId; ImTextureID textureDRCId; // shader loading sint32 loadedShaderFiles; sint32 shaderFileCount; // pipeline loading uint32 loadedPipelines; sint32 pipelineFileCount; }g_shaderCacheLoaderState; FileCache* s_shaderCacheGeneric = nullptr; // contains hardware and version independent shader information #define SHADER_CACHE_GENERIC_EXTRA_VERSION 2 // changing this constant will invalidate all hardware-independent cache files #define SHADER_CACHE_TYPE_VERTEX (0) #define SHADER_CACHE_TYPE_GEOMETRY (1) #define SHADER_CACHE_TYPE_PIXEL (2) bool LatteShaderCache_readSeparableShader(uint8* shaderInfoData, sint32 shaderInfoSize); void LatteShaderCache_LoadPipelineCache(uint64 cacheTitleId); bool LatteShaderCache_updatePipelineLoadingProgress(); void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines); struct { struct { LatteDecompilerShader* shader; }entry[SHADER_CACHE_COMPILE_QUEUE_SIZE]; sint32 count; }shaderCompileQueue; void LatteShaderCache_initCompileQueue() { shaderCompileQueue.count = 0; } void LatteShaderCache_addToCompileQueue(LatteDecompilerShader* shader) { cemu_assert(shaderCompileQueue.count < SHADER_CACHE_COMPILE_QUEUE_SIZE); shaderCompileQueue.entry[shaderCompileQueue.count].shader = shader; shaderCompileQueue.count++; } void LatteShaderCache_removeFromCompileQueue(sint32 index) { for (sint32 i = index; i<shaderCompileQueue.count-1; i++) shaderCompileQueue.entry[i].shader = shaderCompileQueue.entry[i + 1].shader; shaderCompileQueue.count--; } /* * Process entries from compile queue until there are equal or less entries * left than specified by maxRemainingEntries */ void LatteShaderCache_updateCompileQueue(sint32 maxRemainingEntries) { while (true) { if (shaderCompileQueue.count <= maxRemainingEntries) break; auto shader = shaderCompileQueue.entry[0].shader; if (shader) LatteShader_FinishCompilation(shader); LatteShaderCache_removeFromCompileQueue(0); } } typedef struct { unsigned char imageTypeCode; short int imageWidth; short int imageHeight; unsigned char bitCount; std::vector<uint8> imageData; } TGAFILE; bool LoadTGAFile(const std::vector<uint8>& buffer, TGAFILE *tgaFile) { if (buffer.size() <= 18) return false; tgaFile->imageTypeCode = buffer[2]; if (tgaFile->imageTypeCode != 2 && tgaFile->imageTypeCode != 3) return false; tgaFile->imageWidth = *(uint16*)(buffer.data() + 12); tgaFile->imageHeight = *(uint16*)(buffer.data() + 14); tgaFile->bitCount = buffer[16]; // Color mode -> 3 = BGR, 4 = BGRA. const uint8 colorMode = tgaFile->bitCount / 8; if (colorMode != 3) return false; const uint32 imageSize = tgaFile->imageWidth * tgaFile->imageHeight * colorMode; if (imageSize + 18 >= buffer.size()) return false; tgaFile->imageData.resize(imageSize); std::copy(buffer.data() + 18, buffer.data() + 18 + imageSize, tgaFile->imageData.begin()); // Change from BGR to RGB so OpenGL can read the image data. for (uint32 imageIdx = 0; imageIdx < imageSize; imageIdx += colorMode) { std::swap(tgaFile->imageData[imageIdx], tgaFile->imageData[imageIdx + 2]); } return true; } class BootSoundPlayer { public: BootSoundPlayer() = default; ~BootSoundPlayer() { m_stopRequested = true; } void StartSound() { if (!m_bootSndPlayThread.joinable()) { m_fadeOutRequested = false; m_stopRequested = false; m_bootSndPlayThread = std::thread{[this]() { StreamBootSound(); }}; } } void FadeOutSound() { m_fadeOutRequested = true; } void ApplyFadeOutEffect(std::span<sint16> samples, uint64& fadeOutSample, uint64 fadeOutDuration) { for (size_t i = 0; i < samples.size(); i += 2) { const float decibel = (float)fadeOutSample / fadeOutDuration * -60.0f; const float volumeFactor = pow(10, decibel / 20); samples[i] *= volumeFactor; samples[i + 1] *= volumeFactor; fadeOutSample++; } } void StreamBootSound() { SetThreadName("bootsnd"); constexpr sint32 sampleRate = 48'000; constexpr sint32 bitsPerSample = 16; constexpr sint32 samplesPerBlock = sampleRate / 10; // block is 1/10th of a second constexpr sint32 nChannels = 2; static_assert(bitsPerSample % 8 == 0, "bits per sample is not a multiple of 8"); AudioAPIPtr bootSndAudioDev; try { bootSndAudioDev = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::TV, sampleRate, nChannels, samplesPerBlock, bitsPerSample); if(!bootSndAudioDev) return; } catch (const std::runtime_error& ex) { cemuLog_log(LogType::Force, "Failed to initialise audio device for bootup sound"); return; } bootSndAudioDev->SetAudioDelayOverride(4); bootSndAudioDev->Play(); std::string sndPath = fmt::format("{}/meta/{}", CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()), "bootSound.btsnd"); sint32 fscStatus = FSC_STATUS_UNDEFINED; if(!fsc_doesFileExist(sndPath.c_str())) return; FSCVirtualFile* bootSndFileHandle = fsc_open(sndPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); if(!bootSndFileHandle) { cemuLog_log(LogType::Force, "failed to open bootSound.btsnd"); return; } constexpr sint32 audioBlockSize = samplesPerBlock * (bitsPerSample/8) * nChannels; BootSoundReader bootSndFileReader(bootSndFileHandle, audioBlockSize); uint64 fadeOutSample = 0; // track how far into the fadeout constexpr uint64 fadeOutDuration = sampleRate * 2; // fadeout should last 2 seconds while(fadeOutSample < fadeOutDuration && !m_stopRequested) { while (bootSndAudioDev->NeedAdditionalBlocks()) { sint16* data = bootSndFileReader.getSamples(); if(data == nullptr) { // break outer loop m_stopRequested = true; break; } if(m_fadeOutRequested) ApplyFadeOutEffect({data, samplesPerBlock * nChannels}, fadeOutSample, fadeOutDuration); bootSndAudioDev->FeedBlock(data); } // sleep for the duration of a single block std::this_thread::sleep_for(std::chrono::milliseconds(samplesPerBlock / (sampleRate/ 1'000))); } if(bootSndFileHandle) fsc_close(bootSndFileHandle); } private: std::thread m_bootSndPlayThread; std::atomic_bool m_fadeOutRequested = false; std::atomic_bool m_stopRequested = false; }; static BootSoundPlayer g_bootSndPlayer; void LatteShaderCache_finish() { if (g_renderer->GetType() == RendererAPI::Vulkan) RendererShaderVk::ShaderCacheLoading_end(); else if (g_renderer->GetType() == RendererAPI::OpenGL) RendererShaderGL::ShaderCacheLoading_end(); #if ENABLE_METAL else if (g_renderer->GetType() == RendererAPI::Metal) RendererShaderMtl::ShaderCacheLoading_end(); #endif } uint32 LatteShaderCache_getShaderCacheExtraVersion(uint64 titleId) { // encode the titleId in the version to prevent users from swapping caches between titles const uint32 cacheFileVersion = 1; uint32 extraVersion = ((uint32)(titleId >> 32) + ((uint32)titleId) * 3) + cacheFileVersion + 0xe97af1ad; return extraVersion; } uint32 LatteShaderCache_getPipelineCacheExtraVersion(uint64 titleId) { const uint32 cacheFileVersion = 1; uint32 extraVersion = ((uint32)(titleId >> 32) + ((uint32)titleId) * 3) + cacheFileVersion; return extraVersion; } void LatteShaderCache_drawBackgroundImage(ImTextureID texture, int width, int height) { // clear framebuffers and clean up const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoBringToFrontOnFocus; auto& io = ImGui::GetIO(); ImGui::SetNextWindowPos({0, 0}, ImGuiCond_Always); ImGui::SetNextWindowSize(io.DisplaySize, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {0, 0}); if (ImGui::Begin("Background texture", nullptr, kPopupFlags)) { if (texture) { float imageDisplayWidth = io.DisplaySize.x; float imageDisplayHeight = height * imageDisplayWidth / width; float paddingLeftAndRight = 0.0f; float paddingTopAndBottom = (io.DisplaySize.y - imageDisplayHeight) / 2.0f; if (imageDisplayHeight > io.DisplaySize.y) { imageDisplayHeight = io.DisplaySize.y; imageDisplayWidth = width * imageDisplayHeight / height; paddingLeftAndRight = (io.DisplaySize.x - imageDisplayWidth) / 2.0f; paddingTopAndBottom = 0.0f; } ImGui::GetWindowDrawList()->AddImage(texture, ImVec2(paddingLeftAndRight, paddingTopAndBottom), ImVec2(io.DisplaySize.x - paddingLeftAndRight, io.DisplaySize.y - paddingTopAndBottom), {0, 1}, {1, 0}); } } ImGui::End(); ImGui::PopStyleVar(2); } void LatteShaderCache_Load() { shaderCacheScreenStats.compiledShaderCount = 0; shaderCacheScreenStats.vertexShaderCount = 0; shaderCacheScreenStats.geometryShaderCount = 0; shaderCacheScreenStats.pixelShaderCount = 0; uint64 cacheTitleId = CafeSystem::GetForegroundTitleId(); const auto timeLoadStart = now_cached(); // remember current amount of committed memory #if BOOST_OS_WINDOWS PROCESS_MEMORY_COUNTERS pmc1; GetProcessMemoryInfo(GetCurrentProcess(), &pmc1, sizeof(PROCESS_MEMORY_COUNTERS)); LONGLONG totalMem1 = pmc1.PagefileUsage; #endif // init shader parallel compile queue LatteShaderCache_initCompileQueue(); // create directories std::error_code ec; fs::create_directories(ActiveSettings::GetCachePath("shaderCache/transferable"), ec); fs::create_directories(ActiveSettings::GetCachePath("shaderCache/precompiled"), ec); // initialize renderer specific caches if (g_renderer->GetType() == RendererAPI::Vulkan) RendererShaderVk::ShaderCacheLoading_begin(cacheTitleId); else if (g_renderer->GetType() == RendererAPI::OpenGL) RendererShaderGL::ShaderCacheLoading_begin(cacheTitleId); #if ENABLE_METAL else if (g_renderer->GetType() == RendererAPI::Metal) RendererShaderMtl::ShaderCacheLoading_begin(cacheTitleId); #endif // get cache file name fs::path pathGeneric; if (g_renderer->GetType() == RendererAPI::Metal) pathGeneric = ActiveSettings::GetCachePath("shaderCache/transferable/{:016x}_mtlshaders.bin", cacheTitleId); else pathGeneric = ActiveSettings::GetCachePath("shaderCache/transferable/{:016x}_shaders.bin", cacheTitleId); // calculate extraVersion for transferable and precompiled shader cache uint32 transferableExtraVersion = SHADER_CACHE_GENERIC_EXTRA_VERSION; s_shaderCacheGeneric = FileCache::Open(pathGeneric, false, transferableExtraVersion); // legacy extra version (1.25.0 - 1.25.1b) if(!s_shaderCacheGeneric) s_shaderCacheGeneric = FileCache::Open(pathGeneric, true, LatteShaderCache_getShaderCacheExtraVersion(cacheTitleId)); if(!s_shaderCacheGeneric) { // no shader cache available yet cemuLog_log(LogType::Force, "Unable to open or create shader cache file \"{}\"", _pathToUtf8(pathGeneric)); LatteShaderCache_finish(); return; } s_shaderCacheGeneric->UseCompression(false); // load/compile cached shaders sint32 entryCount = s_shaderCacheGeneric->GetMaximumFileIndex(); g_shaderCacheLoaderState.shaderFileCount = s_shaderCacheGeneric->GetFileCount(); g_shaderCacheLoaderState.loadedShaderFiles = 0; // get game background loading image auto loadBackgroundTexture = [](bool isTV, ImTextureID& out) { TGAFILE file{}; out = nullptr; std::string fileName = isTV ? "bootTvTex.tga" : "bootDRCTex.tga"; std::string texPath = fmt::format("{}/meta/{}", CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()), fileName); sint32 status; auto fscfile = fsc_open(texPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &status); if (fscfile) { uint32 size = fsc_getFileSize(fscfile); if (size > 0) { std::vector<uint8> tmpData(size); fsc_readFile(fscfile, tmpData.data(), size); const bool backgroundLoaded = LoadTGAFile(tmpData, &file); if (backgroundLoaded) out = g_renderer->GenerateTexture(file.imageData, { file.imageWidth, file.imageHeight }); } fsc_close(fscfile); } }; loadBackgroundTexture(true, g_shaderCacheLoaderState.textureTVId); loadBackgroundTexture(false, g_shaderCacheLoaderState.textureDRCId); if(GetConfig().play_boot_sound) g_bootSndPlayer.StartSound(); sint32 numLoadedShaders = 0; uint32 loadIndex = 0; auto LoadShadersUpdate = [&]() -> bool { if (loadIndex >= (uint32)s_shaderCacheGeneric->GetMaximumFileIndex()) return false; LatteShaderCache_updateCompileQueue(SHADER_CACHE_COMPILE_QUEUE_SIZE - 2); uint64 name1; uint64 name2; std::vector<uint8> fileData; if (!s_shaderCacheGeneric->GetFileByIndex(loadIndex, &name1, &name2, fileData)) { loadIndex++; return true; } g_shaderCacheLoaderState.loadedShaderFiles++; if (LatteShaderCache_readSeparableShader(fileData.data(), fileData.size()) == false) { // something is wrong with the stored shader, remove entry from shader cache files cemuLog_log(LogType::Force, "Shader cache entry {} invalid, deleting...", loadIndex); s_shaderCacheGeneric->DeleteFile({name1, name2 }); } numLoadedShaders++; loadIndex++; return true; }; LatteShaderCache_ShowProgress(LoadShadersUpdate, false); LatteShaderCache_updateCompileQueue(0); // write load time and RAM usage to log file (in dev build) #if BOOST_OS_WINDOWS const auto timeLoadEnd = now_cached(); const auto timeLoad = std::chrono::duration_cast<std::chrono::milliseconds>(timeLoadEnd - timeLoadStart).count(); PROCESS_MEMORY_COUNTERS pmc2; GetProcessMemoryInfo(GetCurrentProcess(), &pmc2, sizeof(PROCESS_MEMORY_COUNTERS)); LONGLONG totalMem2 = pmc2.PagefileUsage; LONGLONG memCommited = totalMem2 - totalMem1; cemuLog_log(LogType::Force, "Shader cache loaded with {} shaders. Commited mem {}MB. Took {}ms", numLoadedShaders, (sint32)(memCommited/1024/1024), timeLoad); #endif LatteShaderCache_finish(); // if Vulkan or Metal then also load pipeline cache if (g_renderer->GetType() == RendererAPI::Vulkan || g_renderer->GetType() == RendererAPI::Metal) LatteShaderCache_LoadPipelineCache(cacheTitleId); g_renderer->BeginFrame(true); if (g_renderer->ImguiBegin(true)) { LatteShaderCache_drawBackgroundImage(g_shaderCacheLoaderState.textureTVId, 1280, 720); g_renderer->ImguiEnd(); } g_renderer->BeginFrame(false); if (g_renderer->ImguiBegin(false)) { LatteShaderCache_drawBackgroundImage(g_shaderCacheLoaderState.textureDRCId, 854, 480); g_renderer->ImguiEnd(); } g_renderer->SwapBuffers(true, true); if (g_shaderCacheLoaderState.textureTVId) g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureTVId); if (g_shaderCacheLoaderState.textureDRCId) g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureDRCId); g_bootSndPlayer.FadeOutSound(); if(Latte_GetStopSignal()) LatteThread_Exit(); } void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines) { const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_AlwaysAutoResize; const auto textColor = 0xFF888888; auto lastFrameUpdate = tick_cached(); while (true) { if (Latte_GetStopSignal()) break; // thread stop requested, cancel shader loading bool r = loadUpdateFunc(); if (!r) break; // in order to slightly speed up shader loading, we don't update the display if little time passed // this also avoids delayed loading in case third party software caps the framerate at 30 if ((tick_cached() - lastFrameUpdate) < std::chrono::milliseconds(1000 / 20)) // -> aim for 20 FPS continue; int w, h; WindowSystem::GetWindowPhysSize(w, h); const Vector2f window_size{ (float)w,(float)h }; ImGui_GetFont(window_size.y / 32.0f); // = 24 by default ImGui_GetFont(window_size.y / 48.0f); // = 16 g_renderer->BeginFrame(true); if (g_renderer->ImguiBegin(true)) { auto& io = ImGui::GetIO(); // render background texture LatteShaderCache_drawBackgroundImage(g_shaderCacheLoaderState.textureTVId, 1280, 720); const auto progress_font = ImGui_GetFont(window_size.y / 32.0f); // = 24 by default const auto shader_count_font = ImGui_GetFont(window_size.y / 48.0f); // = 16 ImVec2 position = { window_size.x / 2.0f, window_size.y / 2.0f }; ImVec2 pivot = { 0.5f, 0.5f }; ImVec2 progress_size = { io.DisplaySize.x * 0.5f, 0 }; ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(progress_size, ImGuiCond_Always); ImGui::SetNextWindowBgAlpha(0.8f); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, textColor); ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); ImGui::PushFont(progress_font); std::string titleText = "Shader progress"; if (ImGui::Begin(titleText.c_str(), nullptr, kPopupFlags)) { const float width = ImGui::GetWindowSize().x / 2.0f; std::string text; if (isPipelines) { text = "Loading cached pipelines..."; } else { if (shaderCacheScreenStats.compiledShaderCount >= 3) text = "Compiling cached shaders..."; else text = "Loading cached shaders..."; } ImGui::SetCursorPosX(width - ImGui::CalcTextSize(text.c_str()).x / 2); ImGui::Text("%s", text.c_str()); float percentLoaded; if(isPipelines) percentLoaded = (float)g_shaderCacheLoaderState.loadedPipelines / (float)g_shaderCacheLoaderState.pipelineFileCount; else percentLoaded = (float)g_shaderCacheLoaderState.loadedShaderFiles / (float)g_shaderCacheLoaderState.shaderFileCount; ImGui::ProgressBar(percentLoaded, { -1, 0 }, ""); if (isPipelines) text = fmt::format("{}/{} ({}%)", g_shaderCacheLoaderState.loadedPipelines, g_shaderCacheLoaderState.pipelineFileCount, (int)(percentLoaded * 100)); else text = fmt::format("{}/{} ({}%)", g_shaderCacheLoaderState.loadedShaderFiles, g_shaderCacheLoaderState.shaderFileCount, (int)(percentLoaded * 100)); ImGui::SetCursorPosX(width - ImGui::CalcTextSize(text.c_str()).x / 2); ImGui::Text("%s", text.c_str()); } ImGui::End(); ImGui::PopFont(); ImGui::PopStyleColor(2); if (!isPipelines) { position = { 10, window_size.y - 10 }; pivot = { 0, 1 }; ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(0.8f); ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); ImGui::PushFont(shader_count_font); if (ImGui::Begin("Shader count", nullptr, kPopupFlags)) { const float offset = shader_count_font->FallbackAdvanceX * 25.f; ImGui::Text("Vertex shaders"); ImGui::SameLine(offset); ImGui::Text("%d", shaderCacheScreenStats.vertexShaderCount); ImGui::Text("Pixel shaders"); ImGui::SameLine(offset); ImGui::Text("%d", shaderCacheScreenStats.pixelShaderCount); ImGui::Text("Geometry shaders"); ImGui::SameLine(offset); ImGui::Text("%d", shaderCacheScreenStats.geometryShaderCount); } ImGui::End(); ImGui::PopStyleColor(); ImGui::PopFont(); } g_renderer->ImguiEnd(); lastFrameUpdate = tick_cached(); } g_renderer->BeginFrame(false); if (g_renderer->ImguiBegin(false)) { LatteShaderCache_drawBackgroundImage(g_shaderCacheLoaderState.textureDRCId, 854, 480); g_renderer->ImguiEnd(); } // finish frame g_renderer->SwapBuffers(true, true); } } void LatteShaderCache_LoadPipelineCache(uint64 cacheTitleId) { if (g_renderer->GetType() == RendererAPI::Vulkan) g_shaderCacheLoaderState.pipelineFileCount = VulkanPipelineStableCache::GetInstance().BeginLoading(cacheTitleId); #if ENABLE_METAL else if (g_renderer->GetType() == RendererAPI::Metal) g_shaderCacheLoaderState.pipelineFileCount = MetalPipelineCache::GetInstance().BeginLoading(cacheTitleId); #endif g_shaderCacheLoaderState.loadedPipelines = 0; LatteShaderCache_ShowProgress(LatteShaderCache_updatePipelineLoadingProgress, true); if (g_renderer->GetType() == RendererAPI::Vulkan) VulkanPipelineStableCache::GetInstance().EndLoading(); #if ENABLE_METAL else if (g_renderer->GetType() == RendererAPI::Metal) MetalPipelineCache::GetInstance().EndLoading(); #endif } bool LatteShaderCache_updatePipelineLoadingProgress() { uint32 pipelinesMissingShaders = 0; if (g_renderer->GetType() == RendererAPI::Vulkan) return VulkanPipelineStableCache::GetInstance().UpdateLoading(g_shaderCacheLoaderState.loadedPipelines, pipelinesMissingShaders); #if ENABLE_METAL else if (g_renderer->GetType() == RendererAPI::Metal) return MetalPipelineCache::GetInstance().UpdateLoading(g_shaderCacheLoaderState.loadedPipelines, pipelinesMissingShaders); #endif return false; } uint64 LatteShaderCache_getShaderNameInTransferableCache(uint64 baseHash, uint32 shaderType) { baseHash &= ~(7ULL << 61ULL); baseHash |= ((uint64)shaderType << 61ULL); return baseHash; } void LatteShaderCache_writeSeparableVertexShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* fetchShader, uint32 fetchShaderSize, uint8* vertexShader, uint32 vertexShaderSize, uint32* contextRegisters, bool usesGeometryShader) { if (!s_shaderCacheGeneric) return; MemStreamWriter streamWriter(128 * 1024); // header streamWriter.writeBE<uint8>(1 | (SHADER_CACHE_TYPE_VERTEX << 4)); // version and type (shared field) streamWriter.writeBE<uint64>(shaderBaseHash); streamWriter.writeBE<uint64>(shaderAuxHash); streamWriter.writeBE<uint8>(usesGeometryShader ? 1 : 0); // register state Latte::GPUCompactedRegisterState compactRegState; Latte::StoreGPURegisterState(*(LatteContextRegister*)contextRegisters, compactRegState); Latte::SerializeRegisterState(compactRegState, streamWriter); // fetch shader Latte::SerializeShaderProgram(fetchShader, fetchShaderSize, streamWriter); // vertex shader Latte::SerializeShaderProgram(vertexShader, vertexShaderSize, streamWriter); // write to cache uint64 shaderCacheName = LatteShaderCache_getShaderNameInTransferableCache(shaderBaseHash, SHADER_CACHE_TYPE_VERTEX); std::span<uint8> dataBlob = streamWriter.getResult(); s_shaderCacheGeneric->AddFileAsync({shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size()); } void LatteShaderCache_writeSeparableGeometryShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* geometryShader, uint32 geometryShaderSize, uint8* gsCopyShader, uint32 gsCopyShaderSize, uint32* contextRegisters, uint32* hleSpecialState, uint32 vsRingParameterCount) { if (!s_shaderCacheGeneric) return; MemStreamWriter streamWriter(128 * 1024); // header streamWriter.writeBE<uint8>(1 | (SHADER_CACHE_TYPE_GEOMETRY << 4)); // version and type (shared field) streamWriter.writeBE<uint64>(shaderBaseHash); streamWriter.writeBE<uint64>(shaderAuxHash); cemu_assert_debug(vsRingParameterCount < 0x10000); streamWriter.writeBE<uint16>(vsRingParameterCount); // register state Latte::GPUCompactedRegisterState compactRegState; Latte::StoreGPURegisterState(*(LatteContextRegister*)contextRegisters, compactRegState); Latte::SerializeRegisterState(compactRegState, streamWriter); // geometry copy shader Latte::SerializeShaderProgram(gsCopyShader, gsCopyShaderSize, streamWriter); // geometry shader Latte::SerializeShaderProgram(geometryShader, geometryShaderSize, streamWriter); // write to cache uint64 shaderCacheName = LatteShaderCache_getShaderNameInTransferableCache(shaderBaseHash, SHADER_CACHE_TYPE_GEOMETRY); std::span<uint8> dataBlob = streamWriter.getResult(); s_shaderCacheGeneric->AddFileAsync({shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size()); } void LatteShaderCache_writeSeparablePixelShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* pixelShader, uint32 pixelShaderSize, uint32* contextRegisters, bool usesGeometryShader) { if (!s_shaderCacheGeneric) return; MemStreamWriter streamWriter(128 * 1024); streamWriter.writeBE<uint8>(1 | (SHADER_CACHE_TYPE_PIXEL << 4)); // version and type (shared field) streamWriter.writeBE<uint64>(shaderBaseHash); streamWriter.writeBE<uint64>(shaderAuxHash); streamWriter.writeBE<uint8>(usesGeometryShader ? 1 : 0); // register state Latte::GPUCompactedRegisterState compactRegState; Latte::StoreGPURegisterState(*(LatteContextRegister*)contextRegisters, compactRegState); Latte::SerializeRegisterState(compactRegState, streamWriter); // pixel shader Latte::SerializeShaderProgram(pixelShader, pixelShaderSize, streamWriter); // write to cache uint64 shaderCacheName = LatteShaderCache_getShaderNameInTransferableCache(shaderBaseHash, SHADER_CACHE_TYPE_PIXEL); std::span<uint8> dataBlob = streamWriter.getResult(); s_shaderCacheGeneric->AddFileAsync({shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size()); } void LatteShaderCache_loadOrCompileSeparableShader(LatteDecompilerShader* shader, uint64 shaderBaseHash, uint64 shaderAuxHash) { RendererShader::ShaderType shaderType; if (shader->shaderType == LatteConst::ShaderType::Vertex) { shaderType = RendererShader::ShaderType::kVertex; shaderCacheScreenStats.vertexShaderCount++; } else if (shader->shaderType == LatteConst::ShaderType::Geometry) { shaderType = RendererShader::ShaderType::kGeometry; shaderCacheScreenStats.geometryShaderCount++; } else if (shader->shaderType == LatteConst::ShaderType::Pixel) { shaderType = RendererShader::ShaderType::kFragment; shaderCacheScreenStats.pixelShaderCount++; } // compile shader shaderCacheScreenStats.compiledShaderCount++; LatteShader_CreateRendererShader(shader, true); if (shader->shader == nullptr) return; LatteShaderCache_addToCompileQueue(shader); } bool LatteShaderCache_readSeparableVertexShader(MemStreamReader& streamReader, uint8 version) { auto lcr = std::make_unique<LatteContextRegister>(); if (version != 1) return false; uint64 shaderBaseHash = streamReader.readBE<uint64>(); uint64 shaderAuxHash = streamReader.readBE<uint64>(); bool usesGeometryShader = streamReader.readBE<uint8>() != 0; // context registers Latte::GPUCompactedRegisterState regState; if (!Latte::DeserializeRegisterState(regState, streamReader)) return false; Latte::LoadGPURegisterState(*lcr, regState); if (streamReader.hasError()) return false; // fetch shader std::vector<uint8> fetchShaderData; if (!Latte::DeserializeShaderProgram(fetchShaderData, streamReader)) return false; if (streamReader.hasError()) return false; // vertex shader std::vector<uint8> vertexShaderData; if (!Latte::DeserializeShaderProgram(vertexShaderData, streamReader)) return false; if (streamReader.hasError() || !streamReader.isEndOfStream()) return false; // update PS inputs (affects VS shader outputs) LatteShader_UpdatePSInputs(lcr->GetRawView()); // get fetch shader LatteFetchShader::CacheHash fsHash = LatteFetchShader::CalculateCacheHash((uint32*)fetchShaderData.data(), fetchShaderData.size()); LatteFetchShader* fetchShader = LatteShaderRecompiler_createFetchShader(fsHash, lcr->GetRawView(), (uint32*)fetchShaderData.data(), fetchShaderData.size()); // determine decompiler options LatteDecompilerOptions options; LatteShader_GetDecompilerOptions(options, LatteConst::ShaderType::Vertex, usesGeometryShader); // decompile vertex shader LatteDecompilerOutput_t decompilerOutput{}; LatteDecompiler_DecompileVertexShader(shaderBaseHash, lcr->GetRawView(), vertexShaderData.data(), vertexShaderData.size(), fetchShader, options, &decompilerOutput); LatteDecompilerShader* vertexShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, shaderBaseHash, false, shaderAuxHash, lcr->GetRawView()); // compile LatteShader_DumpShader(shaderBaseHash, shaderAuxHash, vertexShader); LatteShader_DumpRawShader(shaderBaseHash, shaderAuxHash, SHADER_DUMP_TYPE_VERTEX, vertexShaderData.data(), vertexShaderData.size()); LatteShaderCache_loadOrCompileSeparableShader(vertexShader, shaderBaseHash, shaderAuxHash); LatteSHRC_RegisterShader(vertexShader, shaderBaseHash, shaderAuxHash); return true; } bool LatteShaderCache_readSeparableGeometryShader(MemStreamReader& streamReader, uint8 version) { if (version != 1) return false; auto lcr = std::make_unique<LatteContextRegister>(); uint64 shaderBaseHash = streamReader.readBE<uint64>(); uint64 shaderAuxHash = streamReader.readBE<uint64>(); uint32 vsRingParameterCount = streamReader.readBE<uint16>(); // context registers Latte::GPUCompactedRegisterState regState; if (!Latte::DeserializeRegisterState(regState, streamReader)) return false; Latte::LoadGPURegisterState(*lcr, regState); if (streamReader.hasError()) return false; // geometry copy shader std::vector<uint8> geometryCopyShaderData; if (!Latte::DeserializeShaderProgram(geometryCopyShaderData, streamReader)) return false; // geometry shader std::vector<uint8> geometryShaderData; if (!Latte::DeserializeShaderProgram(geometryShaderData, streamReader)) return false; if (streamReader.hasError() || !streamReader.isEndOfStream()) return false; // update PS inputs LatteShader_UpdatePSInputs(lcr->GetRawView()); // determine decompiler options LatteDecompilerOptions options; LatteShader_GetDecompilerOptions(options, LatteConst::ShaderType::Geometry, true); // decompile geometry shader LatteDecompilerOutput_t decompilerOutput{}; LatteDecompiler_DecompileGeometryShader(shaderBaseHash, lcr->GetRawView(), geometryShaderData.data(), geometryShaderData.size(), geometryCopyShaderData.data(), geometryCopyShaderData.size(), vsRingParameterCount, options, &decompilerOutput); LatteDecompilerShader* geometryShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, shaderBaseHash, false, shaderAuxHash, lcr->GetRawView()); // compile LatteShader_DumpShader(shaderBaseHash, shaderAuxHash, geometryShader); LatteShader_DumpRawShader(shaderBaseHash, shaderAuxHash, SHADER_DUMP_TYPE_GEOMETRY, geometryShaderData.data(), geometryShaderData.size()); LatteShaderCache_loadOrCompileSeparableShader(geometryShader, shaderBaseHash, shaderAuxHash); LatteSHRC_RegisterShader(geometryShader, shaderBaseHash, shaderAuxHash); return true; } bool LatteShaderCache_readSeparablePixelShader(MemStreamReader& streamReader, uint8 version) { if (version != 1) return false; auto lcr = std::make_unique<LatteContextRegister>(); uint64 shaderBaseHash = streamReader.readBE<uint64>(); uint64 shaderAuxHash = streamReader.readBE<uint64>(); bool usesGeometryShader = streamReader.readBE<uint8>() != 0; // context registers Latte::GPUCompactedRegisterState regState; if (!Latte::DeserializeRegisterState(regState, streamReader)) return false; Latte::LoadGPURegisterState(*lcr, regState); if (streamReader.hasError()) return false; // pixel shader std::vector<uint8> pixelShaderData; if (!Latte::DeserializeShaderProgram(pixelShaderData, streamReader)) return false; if (streamReader.hasError() || !streamReader.isEndOfStream()) return false; // update PS inputs LatteShader_UpdatePSInputs(lcr->GetRawView()); // determine decompiler options LatteDecompilerOptions options; LatteShader_GetDecompilerOptions(options, LatteConst::ShaderType::Pixel, usesGeometryShader); // decompile pixel shader LatteDecompilerOutput_t decompilerOutput{}; LatteDecompiler_DecompilePixelShader(shaderBaseHash, lcr->GetRawView(), pixelShaderData.data(), pixelShaderData.size(), options, &decompilerOutput); LatteDecompilerShader* pixelShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, shaderBaseHash, false, shaderAuxHash, lcr->GetRawView()); // compile LatteShader_DumpShader(shaderBaseHash, shaderAuxHash, pixelShader); LatteShader_DumpRawShader(shaderBaseHash, shaderAuxHash, SHADER_DUMP_TYPE_PIXEL, pixelShaderData.data(), pixelShaderData.size()); LatteShaderCache_loadOrCompileSeparableShader(pixelShader, shaderBaseHash, shaderAuxHash); LatteSHRC_RegisterShader(pixelShader, shaderBaseHash, shaderAuxHash); return true; } // read shader info from shader cache bool LatteShaderCache_readSeparableShader(uint8* shaderInfoData, sint32 shaderInfoSize) { if (shaderInfoSize < 8) return false; MemStreamReader streamReader(shaderInfoData, shaderInfoSize); uint8 versionAndType = streamReader.readBE<uint8>(); uint8 version = versionAndType & 0xF; uint8 type = (versionAndType >> 4) & 0xF; if (type == SHADER_CACHE_TYPE_VERTEX) return LatteShaderCache_readSeparableVertexShader(streamReader, version); else if (type == SHADER_CACHE_TYPE_GEOMETRY) return LatteShaderCache_readSeparableGeometryShader(streamReader, version); else if (type == SHADER_CACHE_TYPE_PIXEL) return LatteShaderCache_readSeparablePixelShader(streamReader, version); return false; } void LatteShaderCache_Close() { if(s_shaderCacheGeneric) { delete s_shaderCacheGeneric; s_shaderCacheGeneric = nullptr; } if (g_renderer->GetType() == RendererAPI::Vulkan) RendererShaderVk::ShaderCacheLoading_Close(); else if (g_renderer->GetType() == RendererAPI::OpenGL) RendererShaderGL::ShaderCacheLoading_Close(); #if ENABLE_METAL else if (g_renderer->GetType() == RendererAPI::Metal) RendererShaderMtl::ShaderCacheLoading_Close(); #endif // if Vulkan or Metal then also close pipeline cache if (g_renderer->GetType() == RendererAPI::Vulkan) VulkanPipelineStableCache::GetInstance().Close(); #if ENABLE_METAL else if (g_renderer->GetType() == RendererAPI::Metal) MetalPipelineCache::GetInstance().Close(); #endif } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteShaderCache.h ================================================ #pragma once uint32 LatteShaderCache_getShaderCacheExtraVersion(uint64 titleId); uint32 LatteShaderCache_getPipelineCacheExtraVersion(uint64 titleId); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteShaderGL.cpp ================================================ #include "Common/GLInclude/GLInclude.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" #include "util/helpers/StringBuf.h" bool gxShader_checkIfSuccessfullyLinked(GLuint glProgram) { int status = -1; glGetProgramiv(glProgram, GL_LINK_STATUS, &status); if( status == GL_TRUE ) return true; // in debug mode, get and print shader error log char infoLog[48*1024]; uint32 infoLogLength, tempLength; glGetProgramiv(glProgram, GL_INFO_LOG_LENGTH, (GLint *)&infoLogLength); tempLength = sizeof(infoLog)-1; glGetProgramInfoLog(glProgram, std::min(tempLength, infoLogLength), (GLsizei*)&tempLength, (GLcharARB*)infoLog); infoLog[tempLength] = '\0'; cemuLog_log(LogType::Force, "Link error in raw shader"); cemuLog_log(LogType::Force, infoLog); return false; } void LatteShader_prepareSeparableUniforms(LatteDecompilerShader* shader) { if (g_renderer->GetType() != RendererAPI::OpenGL) return; auto shaderGL = (RendererShaderGL*)shader->shader; // setup uniform info if (shader->shaderType == LatteConst::ShaderType::Vertex) { shader->uniform.loc_remapped = glGetUniformLocation(shaderGL->GetProgram(), "uf_remappedVS"); shader->uniform.loc_uniformRegister = glGetUniformLocation(shaderGL->GetProgram(), "uf_uniformRegisterVS"); } else if (shader->shaderType == LatteConst::ShaderType::Geometry) { shader->uniform.loc_remapped = glGetUniformLocation(shaderGL->GetProgram(), "uf_remappedGS"); shader->uniform.loc_uniformRegister = glGetUniformLocation(shaderGL->GetProgram(), "uf_uniformRegisterGS"); } else if (shader->shaderType == LatteConst::ShaderType::Pixel) { shader->uniform.loc_remapped = glGetUniformLocation(shaderGL->GetProgram(), "uf_remappedPS"); shader->uniform.loc_uniformRegister = glGetUniformLocation(shaderGL->GetProgram(), "uf_uniformRegisterPS"); } catchOpenGLError(); shader->uniform.loc_windowSpaceToClipSpaceTransform = glGetUniformLocation(shaderGL->GetProgram(), "uf_windowSpaceToClipSpaceTransform"); shader->uniform.loc_alphaTestRef = glGetUniformLocation(shaderGL->GetProgram(), "uf_alphaTestRef"); shader->uniform.loc_pointSize = glGetUniformLocation(shaderGL->GetProgram(), "uf_pointSize"); shader->uniform.loc_fragCoordScale = glGetUniformLocation(shaderGL->GetProgram(), "uf_fragCoordScale"); cemu_assert_debug(shader->uniform.list_ufTexRescale.empty()); for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++) { char ufName[64]; sprintf(ufName, "uf_tex%dScale", t); GLint uniformLocation = glGetUniformLocation(shaderGL->GetProgram(), ufName); if (uniformLocation >= 0) { LatteUniformTextureScaleEntry_t entry = { 0 }; entry.texUnit = t; entry.uniformLocation = uniformLocation; shader->uniform.list_ufTexRescale.push_back(entry); } } } GLuint gpu7ShaderGLDepr_compileShader(const std::string& source, uint32_t type) { cemu_assert(type == GL_VERTEX_SHADER || type == GL_FRAGMENT_SHADER); const GLuint shader_object = glCreateShader(type); const char *c_str = source.c_str(); const GLint size = (GLint)source.size(); glShaderSource(shader_object, 1, &c_str, &size); glCompileShader(shader_object); GLint log_length; glGetShaderiv(shader_object, GL_INFO_LOG_LENGTH, &log_length); if (log_length > 0) { char log[2048]{}; GLsizei log_size; glGetShaderInfoLog(shader_object, std::min(log_length, (GLint)sizeof(log) - 1), &log_size, log); cemuLog_log(LogType::Force, "Error/Warning in vertex shader:"); cemuLog_log(LogType::Force, log); } return shader_object; } GLuint gpu7ShaderGLDepr_compileVertexShader(const std::string& source) { return gpu7ShaderGLDepr_compileShader(source, GL_VERTEX_SHADER); } GLuint gpu7ShaderGLDepr_compileFragmentShader(const std::string& source) { return gpu7ShaderGLDepr_compileShader(source, GL_FRAGMENT_SHADER); } GLuint gpu7ShaderGLDepr_compileVertexShader(const char* shaderSource, sint32 shaderSourceLength) { uint32 shaderObject = glCreateShader(GL_VERTEX_SHADER); GLchar* srcPtr = (GLchar*)shaderSource; GLint srcLen = shaderSourceLength; glShaderSource(shaderObject, 1, &srcPtr, &srcLen); glCompileShader(shaderObject); uint32 shaderLogLengthInfo, shaderLogLen; glGetShaderiv(shaderObject, GL_INFO_LOG_LENGTH, (GLint *)&shaderLogLengthInfo); if (shaderLogLengthInfo > 0) { char messageLog[2048]{}; glGetShaderInfoLog(shaderObject, std::min<uint32>(shaderLogLengthInfo, sizeof(messageLog) - 1), (GLsizei*)&shaderLogLen, (GLcharARB*)messageLog); cemuLog_log(LogType::Force, "Error/Warning in vertex shader:"); cemuLog_log(LogType::Force, messageLog); } return shaderObject; } GLuint gpu7ShaderGLDepr_compileFragmentShader(const char* shaderSource, sint32 shaderSourceLength) { uint32 shaderObject = glCreateShader(GL_FRAGMENT_SHADER); GLchar* srcPtr = (GLchar*)shaderSource; GLint srcLen = shaderSourceLength; glShaderSource(shaderObject, 1, &srcPtr, &srcLen); glCompileShader(shaderObject); uint32 shaderLogLengthInfo, shaderLogLen; char messageLog[2048]; glGetShaderiv(shaderObject, GL_INFO_LOG_LENGTH, (GLint *)&shaderLogLengthInfo); if (shaderLogLengthInfo > 0) { memset(messageLog, 0, sizeof(messageLog)); glGetShaderInfoLog(shaderObject, std::min<uint32>(shaderLogLengthInfo, sizeof(messageLog) - 1), (GLsizei*)&shaderLogLen, (GLcharARB*)messageLog); cemuLog_log(LogType::Force, "Error/Warning in fragment shader:"); cemuLog_log(LogType::Force, messageLog); } return shaderObject; } GLuint gxShaderDepr_compileRaw(StringBuf* strSourceVS, StringBuf* strSourceFS) { GLuint glShaderProgram = glCreateProgram(); GLuint vertexShader = gpu7ShaderGLDepr_compileVertexShader(strSourceVS->c_str(), strSourceVS->getLen()); glAttachShader(glShaderProgram, vertexShader); GLuint fragmentShader = gpu7ShaderGLDepr_compileFragmentShader(strSourceFS->c_str(), strSourceFS->getLen()); glAttachShader(glShaderProgram, fragmentShader); glLinkProgram(glShaderProgram); if( gxShader_checkIfSuccessfullyLinked(glShaderProgram) == false ) { return 0; } return glShaderProgram; } GLuint gxShaderDepr_compileRaw(const std::string& vertex_source, const std::string& fragment_source) { const GLuint programm = glCreateProgram(); auto vertex_shader = std::async(std::launch::deferred, gpu7ShaderGLDepr_compileShader, vertex_source, GL_VERTEX_SHADER); auto fragment_shader = std::async(std::launch::deferred, gpu7ShaderGLDepr_compileShader, fragment_source, GL_FRAGMENT_SHADER); glAttachShader(programm, vertex_shader.get()); glAttachShader(programm, fragment_shader.get()); glLinkProgram(programm); return gxShader_checkIfSuccessfullyLinked(programm) ? programm : 0; } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteSoftware.cpp ================================================ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #define GPU7_ENDIAN_8IN32 2 typedef struct { union { float f[4]; uint32 u32[4]; sint32 s32[4]; }; }LatteReg_t; #define REG_AR (128) typedef struct { LatteReg_t reg[128+1]; union { uint32 u32[5]; sint32 s32[5]; float f[5]; }pvps; union { uint32 u32[5]; sint32 s32[5]; float f[5]; }pvpsUpdate; void* cfilePtr; LatteReg_t* literalPtr; // cbank LatteReg_t* cbank0; LatteReg_t* cbank1; // vertex shader exports LatteReg_t export_pos; uint32* shaderBase; sint32 shaderSize; // shaders LatteFetchShader* fetchShader; }LatteSoftwareExecContext_t; LatteSoftwareExecContext_t LatteSWCtx; char _tempStringBuf[2048]; sint32 tempStringIndex = 0; char* getTempString() { tempStringIndex = (tempStringIndex + 1) % 10; return _tempStringBuf + tempStringIndex * (sizeof(_tempStringBuf) / 10); } const char* _getSrcName(uint32 srcSel, uint32 srcChan) { if (GPU7_ALU_SRC_IS_GPR(srcSel)) { char* tempStr = getTempString(); sprintf(tempStr, "R%d", srcSel & 0x7F); if (srcChan == 0) strcat(tempStr, ".x"); else if (srcChan == 1) strcat(tempStr, ".y"); else if (srcChan == 2) strcat(tempStr, ".z"); else strcat(tempStr, ".w"); return tempStr; } else if (GPU7_ALU_SRC_IS_CFILE(srcSel)) { return "CFILE"; } else if (GPU7_ALU_SRC_IS_PV(srcSel)) { return "PV"; } else if (GPU7_ALU_SRC_IS_PS(srcSel)) { return "PS"; } else if (GPU7_ALU_SRC_IS_CBANK0(srcSel)) { return "CBANK0"; } else if (GPU7_ALU_SRC_IS_CBANK1(srcSel)) { return "CBANK1"; } else if (GPU7_ALU_SRC_IS_CONST_0F(srcSel)) { return "0.0"; } else if (GPU7_ALU_SRC_IS_CONST_1F(srcSel)) { return "1.0"; } else if (GPU7_ALU_SRC_IS_CONST_0_5F(srcSel)) { return "0.5"; } else if (GPU7_ALU_SRC_IS_LITERAL(srcSel)) { return "LITERAL"; } else { cemu_assert_unimplemented(); } return "UKN"; } sint32 _getSrc_genericS32(uint32 srcSel, uint32 srcChan, uint32 srcRel, uint32 indexMode) { sint32 v = 0; if (GPU7_ALU_SRC_IS_GPR(srcSel)) { cemu_assert_debug(srcRel == 0); v = LatteSWCtx.reg[GPU7_ALU_SRC_GET_GPR_INDEX(srcSel)].s32[srcChan]; } else if (GPU7_ALU_SRC_IS_CFILE(srcSel)) { if (srcRel) { if (indexMode <= GPU7_INDEX_AR_W) { v = ((sint32*)LatteSWCtx.cfilePtr)[LatteSWCtx.reg[REG_AR].s32[indexMode] * 4 + GPU7_ALU_SRC_GET_CFILE_INDEX(srcSel) * 4 + srcChan]; } else cemu_assert_debug(false); } else { v = ((sint32*)LatteSWCtx.cfilePtr)[GPU7_ALU_SRC_GET_CFILE_INDEX(srcSel) * 4 + srcChan]; } } else if (GPU7_ALU_SRC_IS_PV(srcSel)) { cemu_assert_debug(srcRel == 0); v = LatteSWCtx.pvps.s32[srcChan]; } else if (GPU7_ALU_SRC_IS_PS(srcSel)) { cemu_assert_debug(srcRel == 0); v = LatteSWCtx.pvps.s32[4]; } else if (GPU7_ALU_SRC_IS_CBANK0(srcSel)) { if (srcRel) { if (indexMode <= GPU7_INDEX_AR_W) { v = LatteSWCtx.cbank0[LatteSWCtx.reg[REG_AR].s32[indexMode] + GPU7_ALU_SRC_GET_CBANK0_INDEX(srcSel)].s32[srcChan]; } else assert_dbg(); } else { v = LatteSWCtx.cbank0[GPU7_ALU_SRC_GET_CBANK0_INDEX(srcSel)].s32[srcChan]; } } else if (GPU7_ALU_SRC_IS_CBANK1(srcSel)) { if (srcRel) { if (indexMode <= GPU7_INDEX_AR_W) { v = LatteSWCtx.cbank1[LatteSWCtx.reg[REG_AR].s32[indexMode] + GPU7_ALU_SRC_GET_CBANK1_INDEX(srcSel)].s32[srcChan]; } else assert_dbg(); } else { v = LatteSWCtx.cbank1[GPU7_ALU_SRC_GET_CBANK1_INDEX(srcSel)].s32[srcChan]; } } else if (GPU7_ALU_SRC_IS_CONST_0F(srcSel)) { cemu_assert_debug(srcRel == 0); v = 0; // 0.0f } else if (GPU7_ALU_SRC_IS_CONST_1F(srcSel)) { cemu_assert_debug(srcRel == 0); v = 0x3f800000; // 1.0f } else if (GPU7_ALU_SRC_IS_CONST_0_5F(srcSel)) { cemu_assert_debug(srcRel == 0); v = 0x3f000000; // 0.5f } else if (GPU7_ALU_SRC_IS_LITERAL(srcSel)) { v = LatteSWCtx.literalPtr->s32[srcChan]; } else assert_dbg(); return v; } sint32 _getSrc_s32(uint32 srcSel, uint32 srcChan, uint32 srcNeg, uint32 srcAbs, uint32 srcRel, uint32 indexMode) { sint32 v = _getSrc_genericS32(srcSel, srcChan, srcRel, indexMode); cemu_assert_debug(srcNeg == 0); cemu_assert_debug(srcAbs == 0); return v; } float _getSrc_f(uint32 srcSel, uint32 srcChan, uint32 srcNeg, uint32 srcAbs, uint32 srcRel, uint32 indexMode) { float v = 0; *(sint32*)&v = _getSrc_genericS32(srcSel, srcChan, srcRel, indexMode); if (srcAbs) // todo - how does this interact with srcNeg v = fabs(v); if (srcNeg) v = -v; return v; } #define _updateGPR_S32(__gprIdx,__channel,__v) {gprUpdate[updateQueueLength].gprIndex = __gprIdx; gprUpdate[updateQueueLength].channel = __channel; gprUpdate[updateQueueLength].s32 = __v; updateQueueLength++;} #define _updateGPR_F(__gprIdx,__channel,__v) {gprUpdate[updateQueueLength].gprIndex = __gprIdx; gprUpdate[updateQueueLength].channel = __channel; gprUpdate[updateQueueLength].f = __v; updateQueueLength++;} #define _updatePVPS_S32(__pvIndex, __v) {LatteSWCtx.pvpsUpdate.s32[__pvIndex] = __v;} #define _updatePVPS_F(__pvIndex, __v) {LatteSWCtx.pvpsUpdate.f[__pvIndex] = __v;} float LatteSoftware_omod(uint32 omod, float f) { switch (omod) { case ALU_OMOD_NONE: return f; case ALU_OMOD_MUL2: return f * 2.0f; case ALU_OMOD_MUL4: return f * 4.0f; case ALU_OMOD_DIV2: return f / 2.0f; } cemu_assert_debug(false); return 0.0f; } #ifdef CEMU_DEBUG_ASSERT #define _clamp(__v) if(destClamp != 0) cemu_assert_unimplemented() #else #define _clamp(__v) // todo #endif #define _omod(__v) __v = LatteSoftware_omod(omod, __v) bool LatteDecompiler_IsALUTransInstruction(bool isOP3, uint32 opcode); void LatteSoftware_setupCBankPointers(uint32 cBank0Index, uint32 cBank1Index, uint32 cBank0AddrBase, uint32 cBank1AddrBase) { MPTR cBank0Ptr = LatteGPUState.contextRegister[mmSQ_VTX_UNIFORM_BLOCK_START + cBank0Index * 7 + 0]; MPTR cBank1Ptr = LatteGPUState.contextRegister[mmSQ_VTX_UNIFORM_BLOCK_START + cBank1Index * 7 + 0]; LatteSWCtx.cbank0 = (LatteReg_t*)memory_getPointerFromPhysicalOffset(cBank0Ptr + cBank0AddrBase); LatteSWCtx.cbank1 = (LatteReg_t*)memory_getPointerFromPhysicalOffset(cBank1Ptr + cBank1AddrBase); } void LatteSoftware_executeALUClause(uint32 cfType, uint32 addr, uint32 count, uint32 cBank0Index, uint32 cBank1Index, uint32 cBank0AddrBase, uint32 cBank1AddrBase) { cemu_assert_debug(cfType == GPU7_CF_INST_ALU); // todo - handle other ALU clauses uint32* aluWordPtr = LatteSWCtx.shaderBase + addr * 2; uint32* aluWordPtrEnd = aluWordPtr + count * 2; LatteSoftware_setupCBankPointers(cBank0Index, cBank1Index, cBank0AddrBase, cBank1AddrBase); struct { sint16 gprIndex; sint16 channel; union { float f; uint32 u32; sint32 s32; }; }gprUpdate[16]; sint32 updateQueueLength = 0; uint32 aluUnitWriteMask = 0; uint8 literalAccessMask = 0; while (aluWordPtr < aluWordPtrEnd) { // calculate number of instructions in group sint32 groupInstructionCount; for (sint32 i = 0; i < 6; i++) { if (aluWordPtr[i * 2] & 0x80000000) { groupInstructionCount = i + 1; break; } cemu_assert_debug(i < 5); } LatteSWCtx.literalPtr = (LatteReg_t*)(aluWordPtr + groupInstructionCount*2); // process group bool hasReductionInstruction = false; float reductionResult = 0.0f; for (sint32 s = 0; s < groupInstructionCount; s++) { uint32 aluWord0 = aluWordPtr[0]; uint32 aluWord1 = aluWordPtr[1]; aluWordPtr += 2; uint32 alu_inst13_5 = (aluWord1 >> 13) & 0x1F; // parameters from ALU word 0 (shared for ALU OP2 and OP3) uint32 src0Sel = (aluWord0 >> 0) & 0x1FF; // source selection uint32 src1Sel = (aluWord0 >> 13) & 0x1FF; uint32 src0Rel = (aluWord0 >> 9) & 0x1; // relative addressing mode uint32 src1Rel = (aluWord0 >> 22) & 0x1; uint32 src0Chan = (aluWord0 >> 10) & 0x3; // component selection x/y/z/w uint32 src1Chan = (aluWord0 >> 23) & 0x3; uint32 src0Neg = (aluWord0 >> 12) & 0x1; // negate input uint32 src1Neg = (aluWord0 >> 25) & 0x1; uint32 indexMode = (aluWord0 >> 26) & 7; uint32 predSel = (aluWord0 >> 29) & 3; if (GPU7_ALU_SRC_IS_LITERAL(src0Sel)) literalAccessMask |= (1 << src0Chan); if (GPU7_ALU_SRC_IS_LITERAL(src1Sel)) literalAccessMask |= (1 << src1Chan); if (alu_inst13_5 >= 0x8) { // op3 // parameters from ALU word 1 uint32 src2Sel = (aluWord1 >> 0) & 0x1FF; // source selection uint32 src2Rel = (aluWord1 >> 9) & 0x1; // relative addressing mode uint32 src2Chan = (aluWord1 >> 10) & 0x3; // component selection x/y/z/w uint32 src2Neg = (aluWord1 >> 12) & 0x1; // negate input if (GPU7_ALU_SRC_IS_LITERAL(src2Sel)) literalAccessMask |= (1 << src2Chan); uint32 destGpr = (aluWord1 >> 21) & 0x7F; uint32 destRel = (aluWord1 >> 28) & 1; uint32 destElem = (aluWord1 >> 29) & 3; uint32 destClamp = (aluWord1 >> 31) & 1; uint32 aluUnit = destElem; if (aluUnitWriteMask&(1 << destElem)) { aluUnit = 4; // PV } else aluUnitWriteMask |= (1 << destElem); switch (alu_inst13_5) { case ALU_OP3_INST_CMOVE: { float f = _getSrc_f(src0Sel, src0Chan, src0Neg, 0, src0Rel, indexMode); sint32 result; if (f == 0.0f) result = _getSrc_s32(src1Sel, src1Chan, src1Neg, 0, src1Rel, indexMode); else result = _getSrc_s32(src2Sel, src2Chan, src2Neg, 0, src2Rel, indexMode); cemu_assert_debug(destClamp == 0); _updateGPR_S32(destGpr, destElem, result); _updatePVPS_S32(aluUnit, result); break; } case ALU_OP3_INST_CMOVGT: { float f = _getSrc_f(src0Sel, src0Chan, src0Neg, 0, src0Rel, indexMode); sint32 result; if (f > 0.0f) result = _getSrc_s32(src1Sel, src1Chan, src1Neg, 0, src1Rel, indexMode); else result = _getSrc_s32(src2Sel, src2Chan, src2Neg, 0, src2Rel, indexMode); cemu_assert_debug(destClamp == 0); _updateGPR_S32(destGpr, destElem, result); _updatePVPS_S32(aluUnit, result); break; } case ALU_OP3_INST_MULADD: { float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, 0, src0Rel, indexMode); float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, 0, src1Rel, indexMode); float f2 = _getSrc_f(src2Sel, src2Chan, src2Neg, 0, src2Rel, indexMode); float f = f0*f1+f2; _updateGPR_F(destGpr, destElem, f); _updatePVPS_F(aluUnit, f); break; } default: cemu_assert_debug(false); } } else { uint32 alu_inst7_11 = (aluWord1 >> 7) & 0x7FF; uint32 src0Abs = (aluWord1 >> 0) & 1; uint32 src1Abs = (aluWord1 >> 1) & 1; uint32 updateExecuteMask = (aluWord1 >> 2) & 1; uint32 updatePredicate = (aluWord1 >> 3) & 1; uint32 writeMask = (aluWord1 >> 4) & 1; uint32 omod = (aluWord1 >> 5) & 3; uint32 destGpr = (aluWord1 >> 21) & 0x7F; uint32 destRel = (aluWord1 >> 28) & 1; uint32 destElem = (aluWord1 >> 29) & 3; uint32 destClamp = (aluWord1 >> 31) & 1; uint32 aluUnit = destElem; if (LatteDecompiler_IsALUTransInstruction(false, alu_inst7_11)) aluUnit = 4; if (aluUnitWriteMask&(1 << destElem)) { aluUnit = 4; // PV } else aluUnitWriteMask |= (1 << destElem); switch (alu_inst7_11) { case ALU_OP2_INST_NOP: { break; } case ALU_OP2_INST_MOV: { if (src0Neg || src0Abs || omod != 0) { float v = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); _omod(v); _clamp(f); if (writeMask) _updateGPR_F(destGpr, destElem, v); _updatePVPS_F(aluUnit, v); } else { sint32 v = _getSrc_s32(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); // todo - omod/clamp for float moves if (writeMask) _updateGPR_S32(destGpr, destElem, v); _updatePVPS_S32(aluUnit, v); } break; } case ALU_OP2_INST_ADD: { float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, src1Abs, src1Rel, indexMode); float f = f0 + f1; _omod(f); _clamp(f); if (writeMask) _updateGPR_F(destGpr, destElem, f); _updatePVPS_F(aluUnit, f); break; } case ALU_OP2_INST_MUL: case ALU_OP2_INST_MUL_IEEE: { float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, src1Abs, src1Rel, indexMode); float f = f0 * f1; if (f0 == 0.0f || f1 == 0.0f) f = 0.0f; _omod(f); _clamp(f); if (writeMask) _updateGPR_F(destGpr, destElem, f); _updatePVPS_F(aluUnit, f); break; } case ALU_OP2_INST_TRUNC: { float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); float f = truncf(f0); _omod(f); _clamp(f); if (writeMask) _updateGPR_F(destGpr, destElem, f); _updatePVPS_F(aluUnit, f); break; } case ALU_OP2_INST_RECIP_IEEE: { float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); float f = 1.0f / f0; _omod(f); _clamp(f); if (writeMask) _updateGPR_F(destGpr, destElem, f); _updatePVPS_F(aluUnit, f); break; } case ALU_OP2_INST_SETGT: { float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, src1Abs, src1Rel, indexMode); float f = (f0 > f1) ? 1.0f : 0.0f; _omod(f); _clamp(f); if (writeMask) _updateGPR_F(destGpr, destElem, f); _updatePVPS_F(aluUnit, f); break; } case ALU_OP2_INST_SETGE: { float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, src1Abs, src1Rel, indexMode); float f = (f0 >= f1) ? 1.0f : 0.0f; _omod(f); _clamp(f); if (writeMask) _updateGPR_F(destGpr, destElem, f); _updatePVPS_F(aluUnit, f); break; } case ALU_OP2_INST_INT_TO_FLOAT: { sint32 v = _getSrc_s32(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); float f = (float)v; _omod(f); _clamp(f); if (writeMask) _updateGPR_F(destGpr, destElem, f); _updatePVPS_F(aluUnit, f); break; } case ALU_OP2_INST_FLT_TO_INT: { float v = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); sint32 f = (sint32)v; if (writeMask) _updateGPR_S32(destGpr, destElem, f); _updatePVPS_S32(aluUnit, f); break; } case ALU_OP2_INST_MOVA_FLOOR: { float f = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); f = floor(f); f = std::min(std::max(f, -256.0f), 255.0f); // omod, clamp? _updateGPR_S32(REG_AR, destElem, (sint32)f); if (writeMask) _updateGPR_F(destGpr, destElem, f); _updatePVPS_F(aluUnit, f); break; } case ALU_OP2_INST_DOT4: { float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, src1Abs, src1Rel, indexMode); float f = f0 * f1; reductionResult += f; _omod(f); _clamp(f); if (writeMask) { _updateGPR_F(destGpr, destElem, f); } _updatePVPS_F(aluUnit, f); hasReductionInstruction = true; break; } default: cemu_assert_debug(false); } } } // apply updates if (hasReductionInstruction == false) { for (sint32 i = 0; i < updateQueueLength; i++) { LatteSWCtx.reg[gprUpdate[i].gprIndex].s32[gprUpdate[i].channel] = gprUpdate[i].s32; } LatteSWCtx.pvps.s32[0] = LatteSWCtx.pvpsUpdate.s32[0]; LatteSWCtx.pvps.s32[1] = LatteSWCtx.pvpsUpdate.s32[1]; LatteSWCtx.pvps.s32[2] = LatteSWCtx.pvpsUpdate.s32[2]; LatteSWCtx.pvps.s32[3] = LatteSWCtx.pvpsUpdate.s32[3]; LatteSWCtx.pvps.s32[4] = LatteSWCtx.pvpsUpdate.s32[4]; } else { for (sint32 i = 0; i < updateQueueLength; i++) { LatteSWCtx.reg[gprUpdate[i].gprIndex].f[gprUpdate[i].channel] = reductionResult; } LatteSWCtx.pvps.f[0] = reductionResult; LatteSWCtx.pvps.f[1] = reductionResult; LatteSWCtx.pvps.f[2] = reductionResult; LatteSWCtx.pvps.f[3] = reductionResult; } updateQueueLength = 0; // skip literals if (literalAccessMask&(3 << 0)) aluWordPtr += 2; if (literalAccessMask&(3 << 2)) { cemu_assert_debug((literalAccessMask &3) != 0); aluWordPtr += 2; } // reset group state tracking variables aluUnitWriteMask = 0; literalAccessMask = 0; } } sint32 _getRegValueByCompSel(uint32 gprIndex, uint32 compSel) { if (compSel < 4) return LatteSWCtx.reg[gprIndex].s32[compSel]; cemu_assert_unimplemented(); return 0; } void LatteSoftware_singleRun() { uint32* cfWords = LatteSWCtx.shaderBase; sint32 instructionIndex = 0; while (true) { uint32 cfWord0 = cfWords[instructionIndex + 0]; uint32 cfWord1 = cfWords[instructionIndex + 1]; instructionIndex += 2; uint32 cf_inst23_7 = (cfWord1 >> 23) & 0x7F; if (cf_inst23_7 < 0x40) // starting at 0x40 the bits overlap with the ALU instruction encoding { bool isEndOfProgram = ((cfWord1 >> 21) & 1) != 0; uint32 addr = cfWord0 & 0xFFFFFFFF; uint32 count = (cfWord1 >> 10) & 7; if (((cfWord1 >> 19) & 1) != 0) count |= 0x8; count++; if (cf_inst23_7 == GPU7_CF_INST_CALL_FS) { } else if (cf_inst23_7 == GPU7_CF_INST_NOP) { // nop if (((cfWord1 >> 0) & 7) != 0) cemu_assert_debug(false); // pop count is not zero } else if (cf_inst23_7 == GPU7_CF_INST_EXPORT || cf_inst23_7 == GPU7_CF_INST_EXPORT_DONE) { // export uint32 edType = (cfWord0 >> 13) & 0x3; uint32 edIndexGpr = (cfWord0 >> 23) & 0x7F; uint32 edRWRel = (cfWord0 >> 22) & 1; if (edRWRel != 0 || edIndexGpr != 0) cemu_assert_debug(false); uint8 exportComponentSel[4]; exportComponentSel[0] = (cfWord1 >> 0) & 0x7; exportComponentSel[1] = (cfWord1 >> 3) & 0x7; exportComponentSel[2] = (cfWord1 >> 6) & 0x7; exportComponentSel[3] = (cfWord1 >> 9) & 0x7; uint32 exportArrayBase = (cfWord0 >> 0) & 0x1FFF; uint32 exportBurstCount = (cfWord1 >> 17) & 0xF; uint32 exportSourceGPR = (cfWord0 >> 15) & 0x7F; uint32 memWriteElemSize = (cfWord0>>29)&3; // unused cemu_assert_debug(exportBurstCount == 0); if (edType == 1 && exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION) { LatteSWCtx.export_pos.s32[0] = _getRegValueByCompSel(exportSourceGPR, exportComponentSel[0]); LatteSWCtx.export_pos.s32[1] = _getRegValueByCompSel(exportSourceGPR, exportComponentSel[1]); LatteSWCtx.export_pos.s32[2] = _getRegValueByCompSel(exportSourceGPR, exportComponentSel[2]); LatteSWCtx.export_pos.s32[3] = _getRegValueByCompSel(exportSourceGPR, exportComponentSel[3]); } else { // unhandled export cemu_assert_unimplemented(); } } else if (cf_inst23_7 == GPU7_CF_INST_TEX) { cemu_assert_unimplemented(); } else if (cf_inst23_7 == GPU7_CF_INST_ELSE || cf_inst23_7 == GPU7_CF_INST_POP) { cemu_assert_unimplemented(); } else if (cf_inst23_7 == GPU7_CF_INST_JUMP) { cemu_assert_unimplemented(); } else if (cf_inst23_7 == GPU7_CF_INST_LOOP_START_DX10 || cf_inst23_7 == GPU7_CF_INST_LOOP_END) { cemu_assert_unimplemented(); } else if (cf_inst23_7 == GPU7_CF_INST_LOOP_BREAK) { cemu_assert_unimplemented(); } else if (cf_inst23_7 == GPU7_CF_INST_MEM_STREAM0_WRITE || cf_inst23_7 == GPU7_CF_INST_MEM_STREAM1_WRITE) { cemu_assert_unimplemented(); } else if (cf_inst23_7 == GPU7_CF_INST_MEM_RING_WRITE) { cemu_assert_unimplemented(); } else if (cf_inst23_7 == GPU7_CF_INST_EMIT_VERTEX) { cemu_assert_unimplemented(); } else { cemu_assert_unimplemented(); } if (isEndOfProgram) { return; } } else { // ALU instruction uint32 cf_inst26_4 = ((cfWord1 >> 26) & 0xF) | GPU7_CF_INST_ALU_MASK; if (cf_inst26_4 == GPU7_CF_INST_ALU || cf_inst26_4 == GPU7_CF_INST_ALU_PUSH_BEFORE || cf_inst26_4 == GPU7_CF_INST_ALU_POP_AFTER || cf_inst26_4 == GPU7_CF_INST_ALU_POP2_AFTER || cf_inst26_4 == GPU7_CF_INST_ALU_BREAK || cf_inst26_4 == GPU7_CF_INST_ALU_ELSE_AFTER) { uint32 addr = (cfWord0 >> 0) & 0x3FFFFF; uint32 count = ((cfWord1 >> 18) & 0x7F) + 1; uint32 cBank0Index = (cfWord0 >> 22) & 0xF; uint32 cBank1Index = (cfWord0 >> 26) & 0xF; uint32 cBank0AddrBase = ((cfWord1 >> 2) & 0xFF) * 16; uint32 cBank1AddrBase = ((cfWord1 >> 10) & 0xFF) * 16; LatteSoftware_executeALUClause(cf_inst26_4, addr, count, cBank0Index, cBank1Index, cBank0AddrBase, cBank1AddrBase); } else { cemu_assert_unimplemented(); } } } cemu_assert_debug(false); } template<int endianMode> uint32 _readVtxU32(void* ptr) { uint32 v = *(uint32*)ptr; if constexpr (endianMode == GPU7_ENDIAN_8IN32) v = _swapEndianU32(v); return v; } template<int endianMode, int nfa> void _readAttr_FLOAT_32_32(void* ptr, LatteReg_t& output) { output.s32[0] = _readVtxU32<endianMode>((uint32*)ptr); output.s32[1] = _readVtxU32<endianMode>((uint32*)ptr + 1); output.s32[2] = 0; output.s32[3] = 0; } template<int endianMode, int nfa> void _readAttr_FLOAT_32_32_32(void* ptr, LatteReg_t& output) { output.s32[0] = _readVtxU32<endianMode>((uint32*)ptr); output.s32[1] = _readVtxU32<endianMode>((uint32*)ptr + 1); output.s32[2] = _readVtxU32<endianMode>((uint32*)ptr + 2); output.s32[3] = 0; } template<int endianMode, int nfa> void _readAttr_FLOAT_32_32_32_32(void* ptr, LatteReg_t& output) { output.s32[0] = _readVtxU32<endianMode>((uint32*)ptr); output.s32[1] = _readVtxU32<endianMode>((uint32*)ptr + 1); output.s32[2] = _readVtxU32<endianMode>((uint32*)ptr + 2); output.s32[3] = _readVtxU32<endianMode>((uint32*)ptr + 3); } #define _fmtKey(__fmt, __endianSwap, __nfa, __isSigned) ((__endianSwap)|((__nfa)<<2)|((__isSigned)<<4)|((__fmt)<<5)) void LatteSoftware_loadVertexAttributes(sint32 index) { LatteSWCtx.reg[0].s32[0] = index; for (auto& bufferGroup : LatteSWCtx.fetchShader->bufferGroups) { for (sint32 f = 0; f < bufferGroup.attribCount; f++) { auto attrib = bufferGroup.attrib + f; // calculate element index sint32 elementIndex = index; // todo - handle instance index and attr divisor // get buffer uint32 bufferIndex = attrib->attributeBufferIndex; if (bufferIndex >= 0x10) { continue; } uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0]; uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1; uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; if (bufferAddress == MPTR_NULL) { debug_printf("Warning: Attribute uses NULL buffer during software emulation\n"); continue; } // translate semanticId to gpr index uint32 gprIndex = 0xFFFFFFFF; for (sint32 f = 0; f < 32; f++) { if (LatteGPUState.contextRegister[mmSQ_VTX_SEMANTIC_0 + f] == attrib->semanticId) { gprIndex = f; break; } } if (gprIndex == 0xFFFFFFFF) continue; // attribute is not mapped to VS input gprIndex = gprIndex + 1; sint32 formatKey = _fmtKey((sint32)attrib->format, (sint32)attrib->endianSwap, (sint32)attrib->nfa, (sint32)attrib->isSigned); void* inputData = memory_getPointerFromPhysicalOffset(bufferAddress + elementIndex * bufferStride); LatteReg_t attrData; switch (formatKey) { case _fmtKey(FMT_32_32_FLOAT, GPU7_ENDIAN_8IN32, LATTE_NFA_2, LATTE_VTX_UNSIGNED): _readAttr_FLOAT_32_32<GPU7_ENDIAN_8IN32, LATTE_NFA_2>(inputData, attrData); break; case _fmtKey(FMT_32_32_32_FLOAT, GPU7_ENDIAN_8IN32, LATTE_NFA_2, LATTE_VTX_UNSIGNED): _readAttr_FLOAT_32_32_32<GPU7_ENDIAN_8IN32, LATTE_NFA_2>(inputData, attrData); break; case _fmtKey(FMT_32_32_32_32_FLOAT, GPU7_ENDIAN_8IN32, LATTE_NFA_2, LATTE_VTX_UNSIGNED): _readAttr_FLOAT_32_32_32_32<GPU7_ENDIAN_8IN32, LATTE_NFA_2>(inputData, attrData); break; default: cemu_assert_debug(false); } LatteReg_t* gprOutput = LatteSWCtx.reg+gprIndex; for (uint32 f = 0; f < 4; f++) { if (attrib->ds[f] < 4) gprOutput->s32[f] = attrData.s32[f]; else if (attrib->ds[f] == 4) gprOutput->s32[f] = 0; else if (attrib->ds[f] == 5) gprOutput->f[f] = 1.0; else cemu_assert_debug(false); } } } } float* LatteSoftware_getPositionExport() { return LatteSWCtx.export_pos.f; } void LatteSoftware_executeVertex(sint32 index) { LatteSoftware_loadVertexAttributes(index); LatteSoftware_singleRun(); } void LatteSoftware_setupVertexShader(LatteFetchShader* fetchShader, void* shaderPtr, sint32 size) { LatteSWCtx.fetchShader = fetchShader; LatteSWCtx.shaderBase = (uint32*)shaderPtr; LatteSWCtx.shaderSize = size; LatteSWCtx.cfilePtr = (void*)(LatteGPUState.contextRegister + LATTE_REG_BASE_ALU_CONST + 0x400); } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteSoftware.h ================================================ #pragma once void LatteSoftware_setupVertexShader(struct LatteFetchShader* fetchShader, void* shaderPtr, sint32 size); void LatteSoftware_executeVertex(sint32 index); float* LatteSoftware_getPositionExport(); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteStreamoutGPU.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "util/containers/IntervalBucketContainer.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Core/LatteRingBuffer.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" struct { sint32 currentRingbufferOffset; VirtualBufferHeap_t* mainBufferHeap; }streamoutManager; sint32 LatteStreamout_GetRingBufferSize() { return 8 * 1024 * 1024; // 8MB } sint32 LatteStreamout_allocateGPURingbufferMem(sint32 size) { // pad size to 256 byte alignment size = (size + 255)&~255; // get next offset if ((streamoutManager.currentRingbufferOffset + size) > LatteStreamout_GetRingBufferSize()) { streamoutManager.currentRingbufferOffset = 0; } sint32 allocOffset = streamoutManager.currentRingbufferOffset; streamoutManager.currentRingbufferOffset += size; return allocOffset; } void LatteStreamout_InitCache() { streamoutManager.currentRingbufferOffset = 0; streamoutManager.mainBufferHeap = nullptr; } bool _transformFeedbackIsActive = false; struct { uint32 vertexCount; uint32 instanceCount; uint32 streamoutWriteMask; struct { bool isActive; sint32 ringBufferOffset; uint32 rangeAddr; uint32 rangeSize; // size of written streamout data, bounded by buffer size }streamoutBufferWrite[LATTE_NUM_STREAMOUT_BUFFER]; }activeStreamoutOperation; uint32 LatteStreamout_getNumberOfWrittenVertices() { // todo: Currently we only handle GX2_POINTS return activeStreamoutOperation.vertexCount * activeStreamoutOperation.instanceCount; } // returns the number of bytes that are written into the buffer by the current draw operation (ignoring buffer maximum size) uint32 LatteStreamout_getBufferWriteRangeSize(uint32 streamoutBufferIndex) { uint32 bufferStride = LatteGPUState.contextRegister[mmVGT_STRMOUT_VTX_STRIDE_0 + streamoutBufferIndex * 4] << 2; uint32 bufferSize = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_SIZE_0 + streamoutBufferIndex * 4] << 2; uint32 writeSize = LatteStreamout_getNumberOfWrittenVertices() * bufferStride; if (bufferSize < writeSize) writeSize = bufferSize; return writeSize; } void LatteStreamout_PrepareDrawcall(uint32 count, uint32 instanceCount) { if (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] == 0) { _transformFeedbackIsActive = false; return; // streamout inactive } // get active vertex shader LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); // if a geometry shader is used calculate how many vertices it outputs LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); sint32 maxVerticesInGS = 1; if (geometryShader) { uint32 gsOutPrimType = LatteGPUState.contextRegister[mmVGT_GS_OUT_PRIM_TYPE]; uint32 bytesPerVertex = LatteGPUState.contextRegister[mmSQ_GS_VERT_ITEMSIZE] * 4; maxVerticesInGS = ((LatteGPUState.contextRegister[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF) * 4) / bytesPerVertex; cemu_assert_debug(maxVerticesInGS > 0); } // setup active streamout operation struct activeStreamoutOperation.vertexCount = count * maxVerticesInGS; activeStreamoutOperation.instanceCount = instanceCount; // get mask of all written streamout buffers uint32 streamoutWriteMask = 0; if (geometryShader) { #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug(vertexShader->streamoutBufferWriteMask.any() == false); #endif for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) if (geometryShader->streamoutBufferWriteMask[i]) streamoutWriteMask |= (1 << i); } else { for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) if (vertexShader->streamoutBufferWriteMask[i]) streamoutWriteMask |= (1 << i); } activeStreamoutOperation.streamoutWriteMask = streamoutWriteMask; // bind streamout buffers for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if ((streamoutWriteMask&(1 << i)) == 0) { activeStreamoutOperation.streamoutBufferWrite[i].isActive = false; continue; } uint32 bufferBaseMPTR = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_BASE_0 + i * 4] << 8; uint32 bufferSize = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_SIZE_0 + i * 4] << 2; uint32 bufferOffset = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_OFFSET_0 + i * 4]; uint32 streamoutWriteSize = LatteStreamout_getBufferWriteRangeSize(i); uint32 rangeAddr = bufferBaseMPTR + bufferOffset; sint32 ringBufferOffset = LatteStreamout_allocateGPURingbufferMem(streamoutWriteSize); // allocate memory for the entire streamout write // calculate write size after bounding it to the buffer uint32 remainingBytesToWrite = bufferOffset > bufferSize ? 0 : (bufferSize - bufferOffset); uint32 rangeSize = std::min(streamoutWriteSize, remainingBytesToWrite); activeStreamoutOperation.streamoutBufferWrite[i].isActive = true; activeStreamoutOperation.streamoutBufferWrite[i].ringBufferOffset = ringBufferOffset; activeStreamoutOperation.streamoutBufferWrite[i].rangeAddr = rangeAddr; activeStreamoutOperation.streamoutBufferWrite[i].rangeSize = rangeSize; g_renderer->streamout_setupXfbBuffer(i, ringBufferOffset, rangeAddr, rangeSize); } g_renderer->streamout_begin(); _transformFeedbackIsActive = true; } void LatteStreamout_FinishDrawcall(bool useDirectMemoryMode) { if (_transformFeedbackIsActive) { _transformFeedbackIsActive = false; for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if ((activeStreamoutOperation.streamoutWriteMask&(1 << i)) == 0) continue; if (activeStreamoutOperation.streamoutBufferWrite[i].rangeSize > 0) { if(useDirectMemoryMode) g_renderer->bufferCache_copyStreamoutToMainBuffer(activeStreamoutOperation.streamoutBufferWrite[i].ringBufferOffset, activeStreamoutOperation.streamoutBufferWrite[i].rangeAddr, activeStreamoutOperation.streamoutBufferWrite[i].rangeSize); else LatteBufferCache_copyStreamoutDataToCache(activeStreamoutOperation.streamoutBufferWrite[i].rangeAddr, activeStreamoutOperation.streamoutBufferWrite[i].rangeSize, activeStreamoutOperation.streamoutBufferWrite[i].ringBufferOffset); } // advance streamout offset uint32 newOffset = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_OFFSET_0 + i * 4] + activeStreamoutOperation.streamoutBufferWrite[i].rangeSize; LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_OFFSET_0 + i * 4] = newOffset; } g_renderer->streamout_rendererFinishDrawcall(); } } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp ================================================ #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteDefaultShaders.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 srcSwizzle, Latte::E_GX2SURFFMT srcSurfaceFormat, sint32 srcWidth, sint32 srcHeight, sint32 srcDepth, uint32 srcPitch, sint32 srcSlice, Latte::E_DIM srcDim, Latte::E_HWTILEMODE srcTilemode, sint32 srcAA, sint32 srcLevel, MPTR dstPhysAddr, MPTR dstMipAddr, uint32 dstSwizzle, Latte::E_GX2SURFFMT dstSurfaceFormat, sint32 dstWidth, sint32 dstHeight, sint32 dstDepth, uint32 dstPitch, sint32 dstSlice, Latte::E_DIM dstDim, Latte::E_HWTILEMODE dstTilemode, sint32 dstAA, sint32 dstLevel) { // check if source is within valid mip range if (srcDim == Latte::E_DIM::DIM_3D && (srcDepth >> srcLevel) == 0 && (srcWidth >> srcLevel) == 0 && (srcHeight >> srcLevel) == 0) return; else if ((srcWidth >> srcLevel) == 0 && (srcHeight >> srcLevel) == 0) return; // look up source texture LatteTexture* sourceTexture = nullptr; LatteTextureView* sourceView = LatteTC_GetTextureSliceViewOrTryCreate(srcPhysAddr, srcMipAddr, srcSurfaceFormat, srcTilemode, srcWidth, srcHeight, srcDepth, srcPitch, srcSwizzle, srcSlice, srcLevel); if (sourceView == nullptr) { debug_printf("HLECopySurface(): Source texture is not in list of dynamic textures\n"); return; } sourceTexture = sourceView->baseTexture; if (sourceTexture->reloadFromDynamicTextures) { LatteTexture_UpdateCacheFromDynamicTextures(sourceTexture); sourceTexture->reloadFromDynamicTextures = false; } // look up destination texture LatteTexture* destinationTexture = nullptr; LatteTextureView* destinationView = LatteTextureViewLookupCache::lookupSlice(dstPhysAddr, dstWidth, dstHeight, dstPitch, dstLevel, dstSlice, dstSurfaceFormat); if (destinationView) destinationTexture = destinationView->baseTexture; // create destination texture if it doesnt exist if (!destinationTexture) { LatteTexture* renderTargetConf = nullptr; destinationView = LatteTexture_CreateMapping(dstPhysAddr, dstMipAddr, dstWidth, dstHeight, dstDepth, dstPitch, dstTilemode, dstSwizzle, dstLevel, 1, dstSlice, 1, dstSurfaceFormat, dstDim, Latte::IsMSAA(dstDim) ? Latte::E_DIM::DIM_2D_MSAA : Latte::E_DIM::DIM_2D, false); destinationTexture = destinationView->baseTexture; } // copy texture if (sourceTexture && destinationTexture) { // mark source and destination texture as still in use LatteTC_MarkTextureStillInUse(destinationTexture); LatteTC_MarkTextureStillInUse(sourceTexture); sint32 realSrcSlice = srcSlice; if (LatteTexture_doesEffectiveRescaleRatioMatch(sourceTexture, sourceView->firstMip, destinationTexture, destinationView->firstMip)) { // adjust copy size sint32 copyWidth = std::max(srcWidth >> srcLevel, 1); sint32 copyHeight = std::max(srcHeight >> srcLevel, 1); // use the smaller width/height as copy size copyWidth = std::min(copyWidth, std::max(dstWidth >> dstLevel, 1)); copyHeight = std::min(copyHeight, std::max(dstHeight >> dstLevel, 1)); sint32 effectiveCopyWidth = copyWidth; sint32 effectiveCopyHeight = copyHeight; LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); // copy slice if (sourceView->baseTexture->isDepth != destinationView->baseTexture->isDepth) g_renderer->surfaceCopy_copySurfaceWithFormatConversion(sourceTexture, sourceView->firstMip, sourceView->firstSlice, destinationTexture, destinationView->firstMip, destinationView->firstSlice, copyWidth, copyHeight); else g_renderer->texture_copyImageSubData(sourceTexture, sourceView->firstMip, 0, 0, realSrcSlice, destinationTexture, destinationView->firstMip, 0, 0, destinationView->firstSlice, effectiveCopyWidth, effectiveCopyHeight, 1); const uint64 eventCounter = LatteTexture_getNextUpdateEventCounter(); LatteTexture_MarkDynamicTextureAsChanged(destinationTexture->baseView, destinationView->firstSlice, destinationView->firstMip, eventCounter); } else { debug_printf("gx2CP_itHLECopySurface(): Copy texture with non-matching effective size\n"); } LatteTC_ResetTextureChangeTracker(destinationTexture); // flag texture as updated destinationTexture->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter(); destinationTexture->isUpdatedOnGPU = true; // todo - also track update flag per-slice } else debug_printf("Source or destination texture does not exist\n"); // download destination texture if it matches known accessed formats if (destinationTexture->width == 8 && destinationTexture->height == 8 && destinationTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1) { cemuLog_logDebug(LogType::Force, "Texture readback after copy for Bayonetta 2 (phys: 0x{:08x})", destinationTexture->physAddress); LatteTextureReadback_Initate(destinationView); } } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTexture.cpp ================================================ #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include <boost/container/small_vector.hpp> struct TexMemOccupancyEntry { uint32 addrStart; uint32 addrEnd; LatteTextureSliceMipInfo* sliceMipInfo; }; #define TEX_OCCUPANCY_BUCKET_COUNT (0x800) // each bucket covers a range of 2MB #define TEX_OCCUPANCY_BUCKET_SIZE (0x100000000/TEX_OCCUPANCY_BUCKET_COUNT) #define loopItrMemOccupancyBuckets(__startAddr, __endAddr) for(sint32 startBucketIndex = ((__startAddr)/TEX_OCCUPANCY_BUCKET_SIZE), bucketIndex=startBucketIndex; bucketIndex<=((__endAddr-1)/TEX_OCCUPANCY_BUCKET_SIZE); bucketIndex++) std::vector<TexMemOccupancyEntry> list_texMemOccupancyBucket[TEX_OCCUPANCY_BUCKET_COUNT]; std::atomic_bool s_refreshTextureQueryList; std::vector<LatteTextureInformation> s_cacheInfoList; std::vector<LatteTextureInformation> LatteTexture_QueryCacheInfo() { // raise request flag to refresh cache s_refreshTextureQueryList.store(true); // wait until cleared or until timeout occurred auto begin = std::chrono::high_resolution_clock::now(); while (true) { if (!s_refreshTextureQueryList) break; auto dur = std::chrono::high_resolution_clock::now() - begin; auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count(); if (ms >= 1000) // dont stall more than one second return std::vector<LatteTextureInformation>(); } return s_cacheInfoList; } void LatteTexture_RefreshInfoCache() { if (!s_refreshTextureQueryList) return; std::vector<LatteTextureInformation> infoCache; std::set<LatteTexture*> visitedTextures; std::unordered_set<LatteTextureView*> allViews = LatteTextureViewLookupCache::GetAllViews(); for (auto& it : allViews) { LatteTexture* baseTexture = it->baseTexture; if(visitedTextures.find(baseTexture) != visitedTextures.end()) continue; visitedTextures.emplace(baseTexture); // add cache info auto& entry = infoCache.emplace_back(); entry.physAddress = baseTexture->physAddress; entry.physMipAddress = baseTexture->physMipAddress; entry.width = baseTexture->width; entry.height = baseTexture->height; entry.depth = baseTexture->depth; entry.pitch = baseTexture->pitch; entry.mipLevels = baseTexture->mipLevels; entry.format = baseTexture->format; entry.isDepth = baseTexture->isDepth; entry.dim = baseTexture->dim; entry.tileMode = baseTexture->tileMode; entry.lastAccessTick = baseTexture->lastAccessTick; entry.lastAccessFrameCount = baseTexture->lastAccessFrameCount; entry.isUpdatedOnGPU = baseTexture->isUpdatedOnGPU; // overwrite info entry.overwriteInfo.hasResolutionOverwrite = baseTexture->overwriteInfo.hasResolutionOverwrite; entry.overwriteInfo.width = baseTexture->overwriteInfo.width; entry.overwriteInfo.height = baseTexture->overwriteInfo.height; entry.overwriteInfo.depth = baseTexture->overwriteInfo.depth; // count number of alternative views entry.alternativeViewCount = 0; // views for (auto& viewItr : baseTexture->views) { if(viewItr == baseTexture->baseView) continue; auto& viewEntry = entry.views.emplace_back(); viewEntry.physAddress = viewItr->baseTexture->physAddress; viewEntry.physMipAddress = viewItr->baseTexture->physMipAddress; viewEntry.width = viewItr->baseTexture->width; viewEntry.height = viewItr->baseTexture->height; viewEntry.pitch = viewItr->baseTexture->pitch; viewEntry.firstMip = viewItr->firstMip; viewEntry.numMip = viewItr->numMip; viewEntry.firstSlice = viewItr->firstSlice; viewEntry.numSlice = viewItr->numSlice; viewEntry.format = viewItr->format; viewEntry.dim = viewItr->dim; } } std::swap(s_cacheInfoList, infoCache); s_refreshTextureQueryList.store(false); } void LatteTexture_AddTexMemOccupancyInterval(LatteTextureSliceMipInfo* sliceMipInfo) { TexMemOccupancyEntry entry; entry.addrStart = sliceMipInfo->addrStart; entry.addrEnd = sliceMipInfo->addrEnd; entry.sliceMipInfo = sliceMipInfo; loopItrMemOccupancyBuckets(entry.addrStart, entry.addrEnd) list_texMemOccupancyBucket[bucketIndex].push_back(entry); } void LatteTexture_RegisterTextureMemoryOccupancy(LatteTexture* texture) { sint32 mipLevels = texture->mipLevels; sint32 sliceCount = texture->depth; for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { sint32 mipSliceCount; if (texture->Is3DTexture()) mipSliceCount = std::max(1, sliceCount >> mipIndex); else mipSliceCount = sliceCount; for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) { LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); LatteTexture_AddTexMemOccupancyInterval(sliceMipInfo); } } } void LatteTexture_RemoveTexMemOccupancyInterval(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo) { loopItrMemOccupancyBuckets(sliceMipInfo->addrStart, sliceMipInfo->addrEnd) { for (sint32 i = 0; i < list_texMemOccupancyBucket[bucketIndex].size(); i++) { if (list_texMemOccupancyBucket[bucketIndex][i].sliceMipInfo->texture == texture) { list_texMemOccupancyBucket[bucketIndex].erase(list_texMemOccupancyBucket[bucketIndex].begin() + i); i--; continue; } } } } void LatteTexture_UnregisterTextureMemoryOccupancy(LatteTexture* texture) { sint32 mipLevels = texture->mipLevels; sint32 sliceCount = texture->depth; for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { sint32 mipSliceCount; if (texture->Is3DTexture()) mipSliceCount = std::max(1, sliceCount >> mipIndex); else mipSliceCount = sliceCount; for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) { LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); LatteTexture_RemoveTexMemOccupancyInterval(texture, sliceMipInfo); } } } // calculate the actually accessed data range // the resulting range is an estimate and may be smaller than the actual slice size (but not larger) void LatteTexture_EstimateMipSliceAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, LatteTextureSliceMipInfo* sliceMipInfo) { uint32 estAddrStart; uint32 estAddrEnd; LatteTextureLoader_estimateAccessedDataRange(texture, sliceIndex, mipIndex, estAddrStart, estAddrEnd); cemu_assert_debug(estAddrStart >= sliceMipInfo->addrStart); cemu_assert_debug(estAddrEnd <= sliceMipInfo->addrEnd); cemu_assert_debug(estAddrStart <= estAddrEnd); sliceMipInfo->estDataAddrStart = estAddrStart; sliceMipInfo->estDataAddrEnd = estAddrEnd; } void LatteTexture_InitSliceAndMipInfo(LatteTexture* texture) { cemu_assert_debug(texture->mipLevels > 0); cemu_assert_debug(texture->depth > 0); sint32 mipSliceCount = texture->GetSliceMipArraySize(); texture->sliceMipInfo = new LatteTextureSliceMipInfo[mipSliceCount](); // todo - mipLevels can be greater than maximum possible mip count. How to handle this? Probably should differentiate between mipLevels and effective mip levels sint32 mipLevels = texture->mipLevels; sint32 sliceCount = texture->depth; for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { sint32 mipSliceCount; if (texture->Is3DTexture()) { mipSliceCount = std::max(1, sliceCount >> mipIndex); } else mipSliceCount = sliceCount; for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) { uint32 calcSliceAddr; uint32 calcSliceSize; sint32 calcSubSliceIndex; LatteAddrLib::CalculateMipAndSliceAddr(texture->physAddress, texture->physMipAddress, texture->format, texture->width, texture->height, texture->depth, texture->dim, texture->tileMode, texture->swizzle, 0, mipIndex, sliceIndex, &calcSliceAddr, &calcSliceSize, &calcSubSliceIndex); LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); sliceMipInfo->addrStart = calcSliceAddr; sliceMipInfo->addrEnd = calcSliceAddr + calcSliceSize; sliceMipInfo->subIndex = calcSubSliceIndex; sliceMipInfo->dataChecksum = 0; sliceMipInfo->sliceIndex = sliceIndex; sliceMipInfo->mipIndex = mipIndex; sliceMipInfo->texture = texture; // get additional slice/mip info LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; LatteAddrLib::GX2CalculateSurfaceInfo(texture->format, texture->width, texture->height, texture->depth, texture->dim, Latte::MakeGX2TileMode(texture->tileMode), 0, mipIndex, &surfaceInfo); sliceMipInfo->tileMode = surfaceInfo.hwTileMode; if (mipIndex == 0) sliceMipInfo->pitch = texture->pitch; // for the base level, use the pitch value configured in hardware else sliceMipInfo->pitch = surfaceInfo.pitch; LatteTexture_EstimateMipSliceAccessedDataRange(texture, sliceIndex, mipIndex, sliceMipInfo); } } } // if this function returns false, textures will not be synchronized even if their data overlaps bool LatteTexture_IsFormatViewCompatible(Latte::E_GX2SURFFMT formatA, Latte::E_GX2SURFFMT formatB) { if(formatA == formatB) return true; // if the format is identical then compatibility must be guaranteed (otherwise we can't create the necessary default view of a texture) // todo - find a better way to handle this for (sint32 swap = 0; swap < 2; swap++) { // other formats // seems like format 0x19 (RGB10_A2) has issues on OpenGL Intel and AMD when copying texture data Latte::E_HWSURFFMT hwFormatA = Latte::GetHWFormat(formatA); Latte::E_HWSURFFMT hwFormatB = Latte::GetHWFormat(formatB); if (hwFormatA == Latte::E_HWSURFFMT::HWFMT_2_10_10_10 && formatB == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT) return false; if (formatA == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT && hwFormatB == Latte::E_HWSURFFMT::HWFMT_2_10_10_10) return false; if (hwFormatA == Latte::E_HWSURFFMT::HWFMT_2_10_10_10 && formatB == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM) return false; if (formatA == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && hwFormatB == Latte::E_HWSURFFMT::HWFMT_2_10_10_10) return false; // format A1B5G5R5 views are not compatible with other 16-bit formats in OpenGL if (formatA == Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM || formatB == Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM) return false; // used in N64 VC (E.g. Super Mario 64) // used in Smash if (formatA == Latte::E_GX2SURFFMT::D24_S8_UNORM && formatB == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM) return false; if (formatA == Latte::E_GX2SURFFMT::R32_FLOAT && formatB == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM) return false; // loop again with swapped vars Latte::E_GX2SURFFMT temp = formatA; formatA = formatB; formatB = temp; } return true; } bool LatteTexture_IsTexelSizeCompatibleFormat(Latte::E_GX2SURFFMT formatA, Latte::E_GX2SURFFMT formatB) { // handle some special cases where formats are incompatible regardless of equal bpp if (formatA == Latte::E_GX2SURFFMT::D24_S8_UNORM && formatB == Latte::E_GX2SURFFMT::D32_FLOAT) return false; if (Latte::IsCompressedFormat(formatA) && Latte::IsCompressedFormat(formatB)) { if (Latte::GetHWFormat(formatA) != Latte::GetHWFormat(formatB)) return false; // compressed formats with different encodings are considered incompatible } return Latte::GetFormatBits((Latte::E_GX2SURFFMT)formatA) == Latte::GetFormatBits((Latte::E_GX2SURFFMT)formatB); } void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, sint32 mipCount, sint32 sliceCount) { cemu_assert_debug(mipCount != 0); cemu_assert_debug(sliceCount != 0); sint32 effectiveCopyWidth = srcTexture->width; sint32 effectiveCopyHeight = srcTexture->height; if (LatteTexture_doesEffectiveRescaleRatioMatch(dstTexture, 0, srcTexture, 0)) { // adjust copy size LatteTexture_scaleToEffectiveSize(dstTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); } else { sint32 effectiveWidth_dst, effectiveHeight_dst; srcTexture->GetEffectiveSize(effectiveWidth_dst, effectiveHeight_dst, 0); sint32 effectiveWidth_src, effectiveHeight_src; dstTexture->GetEffectiveSize(effectiveWidth_src, effectiveHeight_src, 0); debug_printf("texture_copyData(): Effective size mismatch\n"); cemuLog_logDebug(LogType::Force, "texture_copyData(): Effective size mismatch (due to texture rule)"); cemuLog_logDebug(LogType::Force, "Destination: origResolution {:04}x{:04} effectiveResolution {:04}x{:04} fmt {:04x} mipIndex {}", srcTexture->width, srcTexture->height, effectiveWidth_dst, effectiveHeight_dst, (uint32)dstTexture->format, 0); cemuLog_logDebug(LogType::Force, "Source: origResolution {:04}x{:04} effectiveResolution {:04}x{:04} fmt {:04x} mipIndex {}", srcTexture->width, srcTexture->height, effectiveWidth_src, effectiveHeight_src, (uint32)srcTexture->format, 0); return; } for (sint32 mipIndex = 0; mipIndex < mipCount; mipIndex++) { sint32 sliceCopyWidth = std::max(effectiveCopyWidth >> mipIndex, 1); sint32 sliceCopyHeight = std::max(effectiveCopyHeight >> mipIndex, 1); g_renderer->texture_copyImageSubData(srcTexture, mipIndex, 0, 0, 0, dstTexture, mipIndex, 0, 0, 0, sliceCopyWidth, sliceCopyHeight, sliceCount); sint32 mipSliceCount = sliceCount; if (dstTexture->Is3DTexture()) mipSliceCount >>= mipIndex; for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) { LatteTextureSliceMipInfo* srcTexSliceInfo = srcTexture->sliceMipInfo + srcTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex); LatteTextureSliceMipInfo* dstTexSliceInfo = dstTexture->sliceMipInfo + dstTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex); dstTexSliceInfo->lastDynamicUpdate = srcTexSliceInfo->lastDynamicUpdate; } } } template<bool bothMustMatch> bool LatteTexture_DoesWidthHeightMatch(Latte::E_GX2SURFFMT format1, uint32 width1, uint32 height1, Latte::E_GX2SURFFMT format2, uint32 width2, uint32 height2) { if (Latte::IsCompressedFormat(format1)) { width1 <<= 2; height1 <<= 2; } if (Latte::IsCompressedFormat(format2)) { width2 <<= 2; height2 <<= 2; } if constexpr(bothMustMatch) return width1 == width2 && height1 == height2; else return width1 == width2 || height1 == height2; } void LatteTexture_CopySlice(LatteTexture* srcTexture, sint32 srcSlice, sint32 srcMip, LatteTexture* dstTexture, sint32 dstSlice, sint32 dstMip, sint32 srcX, sint32 srcY, sint32 dstX, sint32 dstY, sint32 width, sint32 height) { if (srcTexture->isDepth != dstTexture->isDepth) { g_renderer->surfaceCopy_copySurfaceWithFormatConversion(srcTexture, srcMip, srcSlice, dstTexture, dstMip, dstSlice, width, height); return; } // rescale copy size sint32 effectiveCopyWidth = width; sint32 effectiveCopyHeight = height; LatteTexture_scaleToEffectiveSize(srcTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); sint32 effectiveSrcX = srcX; sint32 effectiveSrcY = srcY; LatteTexture_scaleToEffectiveSize(srcTexture, &effectiveSrcX, &effectiveSrcY, 0); sint32 effectiveDstX = dstX; sint32 effectiveDstY = dstY; LatteTexture_scaleToEffectiveSize(dstTexture, &effectiveDstX, &effectiveDstY, 0); // check if rescale is compatible if (LatteTexture_doesEffectiveRescaleRatioMatch(dstTexture, 0, srcTexture, 0) == false) { sint32 effectiveWidth_src = srcTexture->overwriteInfo.hasResolutionOverwrite ? srcTexture->overwriteInfo.width : srcTexture->width; sint32 effectiveHeight_src = srcTexture->overwriteInfo.hasResolutionOverwrite ? srcTexture->overwriteInfo.height : srcTexture->height; sint32 effectiveWidth_dst = dstTexture->overwriteInfo.hasResolutionOverwrite ? dstTexture->overwriteInfo.width : dstTexture->width; sint32 effectiveHeight_dst = dstTexture->overwriteInfo.hasResolutionOverwrite ? dstTexture->overwriteInfo.height : dstTexture->height; if (cemuLog_isLoggingEnabled(LogType::TextureCache)) { cemuLog_log(LogType::Force, "_copySlice(): Unable to sync textures with mismatching scale ratio (due to texture rule)"); float ratioWidth_src = (float)effectiveWidth_src / (float)srcTexture->width; float ratioHeight_src = (float)effectiveHeight_src / (float)srcTexture->height; float ratioWidth_dst = (float)effectiveWidth_dst / (float)dstTexture->width; float ratioHeight_dst = (float)effectiveHeight_dst / (float)dstTexture->height; cemuLog_log(LogType::Force, "Source: {:08x} origResolution {:4}/{:4} effectiveResolution {:4}/{:4} fmt {:04x} mipIndex {} ratioW/H: {:.4}/{:.4}", srcTexture->physAddress, srcTexture->width, srcTexture->height, effectiveWidth_src, effectiveHeight_src, (uint32)srcTexture->format, srcMip, ratioWidth_src, ratioHeight_src); cemuLog_log(LogType::Force, "Destination: {:08x} origResolution {:4}/{:4} effectiveResolution {:4}/{:4} fmt {:04x} mipIndex {} ratioW/H: {:.4}/{:.4}", dstTexture->physAddress, dstTexture->width, dstTexture->height, effectiveWidth_dst, effectiveHeight_dst, (uint32)dstTexture->format, dstMip, ratioWidth_dst, ratioHeight_dst); } //cemuLog_logDebug(LogType::Force, "If these textures are not meant to share data you can ignore this"); return; } // todo - store 'lastUpdated' value per slice/mip and copy it's value when copying the slice data g_renderer->texture_copyImageSubData(srcTexture, srcMip, effectiveSrcX, effectiveSrcY, srcSlice, dstTexture, dstMip, effectiveDstX, effectiveDstY, dstSlice, effectiveCopyWidth, effectiveCopyHeight, 1); } bool LatteTexture_GetSubtextureSliceAndMip(LatteTexture* baseTexture, LatteTexture* mipTexture, sint32* baseSliceIndex, sint32* baseMipIndex) { LatteTextureSliceMipInfo* mipTextureSliceInfo = mipTexture->sliceMipInfo + mipTexture->GetSliceMipArrayIndex(0, 0); // todo - this can be optimized by first determining the mip level from pitch for (sint32 mipIndex = 0; mipIndex < baseTexture->mipLevels; mipIndex++) { sint32 sliceCount; if (baseTexture->Is3DTexture()) sliceCount = std::max(baseTexture->depth >> mipIndex, 1); else sliceCount = baseTexture->depth; for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++) { LatteTextureSliceMipInfo* sliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex); if (sliceMipInfo->addrStart == mipTextureSliceInfo->addrStart && sliceMipInfo->subIndex == mipTextureSliceInfo->subIndex) { *baseSliceIndex = sliceIndex; *baseMipIndex = mipIndex; return true; } // todo - support overlapping textures with a non-zero y-offset } } return false; } // if a texture shares memory with another texture then flag those textures as invalidated (on next use, synchronize data) void LatteTexture_MarkDynamicTextureAsChanged(LatteTextureView* textureView, sint32 sliceIndex, sint32 mipIndex, uint64 eventCounter) { LatteTexture* baseTexture = textureView->baseTexture; baseTexture->lastWriteEventCounter = eventCounter; sint32 aSliceIndex = textureView->firstSlice + sliceIndex; sint32 aMipIndex = textureView->firstMip + mipIndex; LatteTextureSliceMipInfo* baseSliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(aSliceIndex, aMipIndex); baseSliceMipInfo->lastDynamicUpdate = eventCounter; LatteTexture_MarkConnectedTexturesForReloadFromDynamicTextures(textureView->baseTexture); } void LatteTexture_SyncSlice(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex) { sint32 srcWidth = srcTexture->width; sint32 srcHeight = srcTexture->height; sint32 dstWidth = dstTexture->width; sint32 dstHeight = dstTexture->height; if(srcTexture->overwriteInfo.hasFormatOverwrite != dstTexture->overwriteInfo.hasFormatOverwrite) return; // dont sync: format overwrite state needs to match. Not strictly necessary but it simplifies logic down the road else if(srcTexture->overwriteInfo.hasFormatOverwrite && srcTexture->overwriteInfo.format != dstTexture->overwriteInfo.format) return; // both are overwritten but with different formats if (srcMipIndex == 0 && dstMipIndex == 0 && (srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED || srcTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1) && srcTexture->height > dstTexture->height && (srcTexture->height % dstTexture->height) == 0) { bool isMatch = srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED; if (srcTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1 && srcTexture->width == 32) { // special case for CoD BO2, where 1024x32 and 32x32x32 textures share memory isMatch = true; } if (isMatch && srcTexture->IsCompressedFormat() == false && dstTexture->IsCompressedFormat() == false) { sint32 virtualSlices = srcTexture->height / dstTexture->height; if (dstTexture->depth == virtualSlices) { // special case for Ninja Gaiden // it initializes a 24x24x24 texture array as a 24x576x1 2D texture (using tilemode 1) sint32 copyWidth = std::min(srcWidth, dstWidth); sint32 copyHeight = std::min(srcHeight, dstHeight); for (sint32 slice = 0; slice < virtualSlices; slice++) LatteTexture_CopySlice(srcTexture, srcSliceIndex, srcMipIndex, dstTexture, dstSliceIndex + slice, dstMipIndex, 0, slice * dstTexture->height, 0, 0, copyWidth, copyHeight); } return; } } bool srcIsCompressed = srcTexture->IsCompressedFormat(); bool dstIsCompressed = dstTexture->IsCompressedFormat(); if (srcIsCompressed != dstIsCompressed) { // convert into unit of source texture if (srcIsCompressed == false) { // destination compressed, source uncompressed (integer format) dstWidth >>= 2; dstHeight >>= 2; } else { // destination uncompressed (integer format), source compressed dstWidth <<= 2; dstHeight <<= 2; } } srcWidth = std::max(srcWidth >> srcMipIndex, 1); srcHeight = std::max(srcHeight >> srcMipIndex, 1); dstWidth = std::max(dstWidth >> dstMipIndex, 1); dstHeight = std::max(dstHeight >> dstMipIndex, 1); sint32 copyWidth = std::min(srcWidth, dstWidth); sint32 copyHeight = std::min(srcHeight, dstHeight); LatteTexture_CopySlice(srcTexture, srcSliceIndex, srcMipIndex, dstTexture, dstSliceIndex, dstMipIndex, 0, 0, 0, 0, copyWidth, copyHeight); } void LatteTexture_UpdateTextureFromDynamicChanges(LatteTexture* texture) { // note: Currently this function assumes that only one other texture is updated per slice/mip (if multiple overlap, we should merge the one with the latest timestamp the latest of each individually) for (auto& texRel : texture->list_compatibleRelations) { LatteTexture* baseTexture = texRel->baseTexture; LatteTexture* subTexture = texRel->subTexture; for (sint32 cMipIndex = 0; cMipIndex < texRel->mipCount; cMipIndex++) { sint32 mipSliceCount = texRel->sliceCount; if (texRel->baseTexture->Is3DTexture()) { cemu_assert_debug(cMipIndex == 0); // values above 0 need testing mipSliceCount >>= cMipIndex; } for (sint32 cSliceIndex = 0; cSliceIndex < mipSliceCount; cSliceIndex++) { LatteTextureSliceMipInfo* baseSliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(texRel->baseSliceIndex + cSliceIndex, texRel->baseMipIndex + cMipIndex); LatteTextureSliceMipInfo* subSliceMipInfo = subTexture->sliceMipInfo + subTexture->GetSliceMipArrayIndex(cSliceIndex, cMipIndex); if (texture == baseTexture) { // baseTexture is target texture if (baseSliceMipInfo->lastDynamicUpdate < subSliceMipInfo->lastDynamicUpdate) { LatteTexture_SyncSlice(subTexture, cSliceIndex, cMipIndex, baseTexture, texRel->baseSliceIndex + cSliceIndex, texRel->baseMipIndex + cMipIndex); baseSliceMipInfo->lastDynamicUpdate = subSliceMipInfo->lastDynamicUpdate; if(subTexture->isUpdatedOnGPU) texture->isUpdatedOnGPU = true; } } else { // subTexture is target texture if (subSliceMipInfo->lastDynamicUpdate < baseSliceMipInfo->lastDynamicUpdate) { LatteTexture_SyncSlice(baseTexture, texRel->baseSliceIndex + cSliceIndex, texRel->baseMipIndex + cMipIndex, subTexture, cSliceIndex, cMipIndex); subSliceMipInfo->lastDynamicUpdate = baseSliceMipInfo->lastDynamicUpdate; if (baseTexture->isUpdatedOnGPU) texture->isUpdatedOnGPU = true; } } } } } } bool _LatteTexture_IsTileModeCompatible(LatteTexture* texture1, sint32 mipIndex1, LatteTexture* texture2, sint32 mipIndex2) { if (mipIndex1 == 0 && mipIndex2 == 0) return texture1->tileMode == texture2->tileMode; LatteTextureSliceMipInfo* texture1SliceInfo = texture1->sliceMipInfo + texture1->GetSliceMipArrayIndex(0, mipIndex1); LatteTextureSliceMipInfo* texture2SliceInfo = texture2->sliceMipInfo + texture2->GetSliceMipArrayIndex(0, mipIndex2); if (texture1SliceInfo->tileMode == texture2SliceInfo->tileMode) return true; return false; } bool __LatteTexture_IsBlockedFormatRelation(LatteTexture* texture1, LatteTexture* texture2) { if (texture1->isDepth && texture2->isDepth == false) { // necessary for Smash? (currently our depth to color copy always converts and the depth ends up in R only) if (texture1->format == Latte::E_GX2SURFFMT::D32_FLOAT && Latte::GetHWFormat(texture2->format) == Latte::E_HWSURFFMT::HWFMT_8_8_8_8) return true; } // Vulkan has stricter rules if (g_renderer->GetType() == RendererAPI::Vulkan) { // found in Smash (Wii Fit Stage) if (texture1->format == Latte::E_GX2SURFFMT::D32_FLOAT && Latte::GetHWFormat(texture2->format) == Latte::E_HWSURFFMT::HWFMT_8_24) return true; } return false; } bool LatteTexture_IsBlockedFormatRelation(LatteTexture* texture1, LatteTexture* texture2) { if (__LatteTexture_IsBlockedFormatRelation(texture1, texture2)) return true; return __LatteTexture_IsBlockedFormatRelation(texture2, texture1); } // called if two textures are known to overlap in memory // this function then tries to figure out the details and registers the relation in texture*->list_compatibleRelations void LatteTexture_TrackTextureRelation(LatteTexture* texture1, LatteTexture* texture2) { // make sure texture 2 is always at texture 1 mip level 0 or beyond if (texture1->physAddress > texture2->physAddress) return LatteTexture_TrackTextureRelation(texture2, texture1); // check if this texture relation is already tracked cemu_assert_debug(texture1->physAddress != 0); cemu_assert_debug(texture2->physAddress != 0); for (auto& it : texture1->list_compatibleRelations) { if (it->baseTexture == texture1 && it->subTexture == texture2) return; // association already known } // check for blocked format combination if (LatteTexture_IsBlockedFormatRelation(texture1, texture2)) return; if (texture1->physAddress == texture2->physAddress && false) { // both textures overlap at mip level 0 cemu_assert_debug(texture1->swizzle == texture2->swizzle); cemu_assert_debug(texture1->tileMode == texture2->tileMode); if (LatteTexture_DoesWidthHeightMatch<false>(texture1->format, texture1->width, texture1->height, texture2->format, texture2->width, texture2->height)) { cemu_assert_unimplemented(); } } else { sint32 baseSliceIndex; sint32 baseMipIndex; if (texture1->physAddress == texture2->physAddress) { baseSliceIndex = 0; baseMipIndex = 0; } else { if (LatteTexture_GetSubtextureSliceAndMip(texture1, texture2, &baseSliceIndex, &baseMipIndex) == false) { return; } } sint32 sharedMipLevels = 1; // todo - support for multiple shared mip levels // check if pitch is compatible LatteTextureSliceMipInfo* texture1SliceInfo = texture1->sliceMipInfo + texture1->GetSliceMipArrayIndex(baseSliceIndex, baseMipIndex); LatteTextureSliceMipInfo* texture2SliceInfo = texture2->sliceMipInfo + texture2->GetSliceMipArrayIndex(0, 0); if (_LatteTexture_IsTileModeCompatible(texture1, baseMipIndex, texture2, 0) == false) return; // not compatible if (texture1SliceInfo->pitch != texture2SliceInfo->pitch) return; // not compatible // calculate compatible depth range sint32 baseRemainingDepth = texture1->GetMipDepth(baseMipIndex) - baseSliceIndex; cemu_assert_debug(baseRemainingDepth >= 0); sint32 compatibleDepthRange = std::min(baseRemainingDepth, texture2->depth); cemu_assert_debug(compatibleDepthRange > 0); // create association LatteTextureRelation* rel = (LatteTextureRelation*)malloc(sizeof(LatteTextureRelation)); memset(rel, 0, sizeof(LatteTextureRelation)); rel->baseTexture = texture1; rel->subTexture = texture2; rel->baseMipIndex = baseMipIndex; rel->baseSliceIndex = baseSliceIndex; rel->mipCount = sharedMipLevels; rel->sliceCount = compatibleDepthRange; rel->yOffset = 0; // todo texture1->list_compatibleRelations.push_back(rel); texture2->list_compatibleRelations.push_back(rel); } } void LatteTexture_TrackDataOverlap(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo, TexMemOccupancyEntry& occupancy) { // todo - handle tile thickness and z offset // todo - check address range overlap auto& occMipSliceInfo = occupancy.sliceMipInfo; if ((sliceMipInfo->addrEnd > occMipSliceInfo->addrStart && sliceMipInfo->addrStart < occMipSliceInfo->addrEnd) == false) return; // check if this overlap is already tracked for (auto& it : sliceMipInfo->list_dataOverlap) { if (it.destMipSliceInfo == occupancy.sliceMipInfo) return; } // register texture->dest LatteTextureSliceMipDataOverlap_t overlapEntry; overlapEntry.destMipSliceInfo = occupancy.sliceMipInfo; overlapEntry.destTexture = occupancy.sliceMipInfo->texture; sliceMipInfo->list_dataOverlap.push_back(overlapEntry); // register dest->texture LatteTextureSliceMipDataOverlap_t overlapEntry2; overlapEntry2.destMipSliceInfo = sliceMipInfo; overlapEntry2.destTexture = sliceMipInfo->texture; occupancy.sliceMipInfo->list_dataOverlap.push_back(overlapEntry2); } void _LatteTexture_RemoveDataOverlapTracking(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo, LatteTextureSliceMipDataOverlap_t& dataOverlap) { LatteTexture* destTexture = dataOverlap.destTexture; LatteTextureSliceMipInfo* destSliceMipInfo = dataOverlap.destMipSliceInfo; // delete from dest for (auto it = destSliceMipInfo->list_dataOverlap.begin(); it != destSliceMipInfo->list_dataOverlap.end();) { if (it->destTexture == texture) it = destSliceMipInfo->list_dataOverlap.erase(it); else if (it->destTexture == destTexture) cemu_assert_unimplemented(); else it++; } } void LatteTexture_DeleteDataOverlapTracking(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo) { for(auto& it : sliceMipInfo->list_dataOverlap) _LatteTexture_RemoveDataOverlapTracking(texture, sliceMipInfo, it); sliceMipInfo->list_dataOverlap.resize(0); } void LatteTexture_DeleteDataOverlapTracking(LatteTexture* texture) { sint32 mipLevels = texture->mipLevels; sint32 sliceCount = texture->depth; for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { sint32 mipSliceCount; if (texture->Is3DTexture()) mipSliceCount = std::max(1, sliceCount >> mipIndex); else mipSliceCount = sliceCount; for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) { LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); LatteTexture_DeleteDataOverlapTracking(texture, sliceMipInfo); } } } void LatteTexture_GatherTextureRelations(LatteTexture* texture) { for (sint32 mipIndex = 0; mipIndex < texture->mipLevels; mipIndex++) { sint32 mipSliceCount; if (texture->Is3DTexture()) mipSliceCount = std::max(1, texture->depth >> mipIndex); else mipSliceCount = texture->depth; for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) { LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); loopItrMemOccupancyBuckets(sliceMipInfo->addrStart, sliceMipInfo->addrEnd) { for (auto& occupancy : list_texMemOccupancyBucket[bucketIndex]) { LatteTexture* itrTexture = occupancy.sliceMipInfo->texture; if (itrTexture == texture) continue; // ignore self if (sliceMipInfo->addrEnd >= occupancy.addrStart && sliceMipInfo->addrStart < occupancy.addrEnd) { if (sliceMipInfo->addrStart == occupancy.addrStart && sliceMipInfo->subIndex == occupancy.sliceMipInfo->subIndex) { // overlapping with zero x/y offset if (sliceMipInfo->pitch == occupancy.sliceMipInfo->pitch && LatteTexture_IsTexelSizeCompatibleFormat(texture->format, itrTexture->format) && sliceMipInfo->tileMode == occupancy.sliceMipInfo->tileMode && LatteTexture_IsFormatViewCompatible(texture->format, itrTexture->format)) { LatteTexture_TrackTextureRelation(texture, itrTexture); } else { // pitch not compatible or format not compatible } } else { LatteTexture_TrackDataOverlap(texture, sliceMipInfo, occupancy); } } } } } } } void LatteTexture_DeleteTextureRelations(LatteTexture* texture) { while (texture->list_compatibleRelations.empty() == false) { LatteTextureRelation* rel = texture->list_compatibleRelations[0]; rel->baseTexture->list_compatibleRelations.erase(std::find(rel->baseTexture->list_compatibleRelations.begin(), rel->baseTexture->list_compatibleRelations.end(), rel)); rel->subTexture->list_compatibleRelations.erase(std::find(rel->subTexture->list_compatibleRelations.begin(), rel->subTexture->list_compatibleRelations.end(), rel)); free(rel); } texture->list_compatibleRelations.clear(); } enum VIEWCOMPATIBILITY { VIEW_COMPATIBLE, // subtexture can be represented as view into base texture VIEW_BASE_TOO_SMALL, // base texture must be extended (depth or mip levels) to fit sub texture VIEW_NOT_COMPATIBLE, }; bool IsDimensionCompatibleForGX2View(Latte::E_DIM baseDim, Latte::E_DIM viewDim) { // Note that some combinations depend on the exact view/slice index and count which we currently ignore (like a 3D view of a 3D texture) bool isCompatible = (baseDim == viewDim) || (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D) || (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) || (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D) || (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D_ARRAY) || (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_CUBEMAP) || (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D_ARRAY); if(isCompatible) return true; // these combinations have been seen in use by games and are considered incompatible: // (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_3D) -> Not allowed on OpenGL // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_MSAA) // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_1D) // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_3D) // (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D) // (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_3D) -> Only compatible if the same depth and shared at mip/slice 0 // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_CUBEMAP) // (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D) // (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_2D) return false; } VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseTexture, uint32 physAddr, sint32 width, sint32 height, sint32 pitch, Latte::E_DIM dimView, Latte::E_GX2SURFFMT format, bool isDepth, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32& relativeMipIndex, sint32& relativeSliceIndex) { relativeMipIndex = 0; relativeSliceIndex = 0; if (baseTexture->overwriteInfo.hasFormatOverwrite) { // if the base format is overwritten, then we only allow aliasing if the view format matches the base format if (baseTexture->format != format) return VIEW_NOT_COMPATIBLE; } if (LatteTexture_IsFormatViewCompatible(baseTexture->format, format) == false) return VIEW_NOT_COMPATIBLE; if (baseTexture->physAddress == physAddr && baseTexture->pitch == pitch) { if (baseTexture->isDepth != isDepth) return VIEW_NOT_COMPATIBLE; // depth and non-depth formats are never compatible (on OpenGL) if (!LatteTexture_IsTexelSizeCompatibleFormat(baseTexture->format, format) || baseTexture->width != width || baseTexture->height != height) return VIEW_NOT_COMPATIBLE; // 3D views are only compatible on Vulkan if they match the base texture in regards to mip and slice count bool isCompatible3DView = dimView == Latte::E_DIM::DIM_3D && baseTexture->dim == dimView && firstSlice == 0 && firstMip == 0 && baseTexture->mipLevels == numMip && baseTexture->depth == numSlice; if (!isCompatible3DView && !IsDimensionCompatibleForGX2View(baseTexture->dim, dimView)) return VIEW_NOT_COMPATIBLE; if (baseTexture->isDepth && baseTexture->format != format) { // depth view with different format cemuLog_logDebug(LogType::Force, "_createMapping(): Incompatible depth view format"); return VIEW_NOT_COMPATIBLE; } // AMD has a bug on OpenGL where it ignores the internal format of texture views when they are bound as render targets, // as a result we cant use texture views when they have a different format if (baseTexture->format != format) return VIEW_NOT_COMPATIBLE; if ((firstMip + numMip) > baseTexture->mipLevels || (firstSlice + numSlice) > baseTexture->depth) { // view has more slices or mips than existing texture return VIEW_BASE_TOO_SMALL; } return VIEW_COMPATIBLE; } else { if (numMip > 1) return VIEW_NOT_COMPATIBLE; if (baseTexture->Is3DTexture()) return VIEW_NOT_COMPATIBLE; // todo - add support for mapping views into 3D textures // if phys address or pitch differs then it might be pointing to a mip for (sint32 m = 0; m < baseTexture->mipLevels; m++) { auto sliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(0, m); // check pitch if(sliceMipInfo->pitch != pitch) continue; // check all slices if(LatteAddrLib::TM_IsThickAndMacroTiled(baseTexture->tileMode)) continue; // todo - check only every 4th slice? for (sint32 s=0; s<baseTexture->GetMipDepth(m); s++) { sliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(s, m); if (sliceMipInfo->addrStart != physAddr || sliceMipInfo->pitch != pitch) continue; if (baseTexture->isDepth != isDepth) return VIEW_NOT_COMPATIBLE; if (baseTexture->GetMipWidth(m) != width || baseTexture->GetMipHeight(m) != height) return VIEW_NOT_COMPATIBLE; if (!LatteTexture_IsTexelSizeCompatibleFormat(baseTexture->format, format) ) return VIEW_NOT_COMPATIBLE; if (!IsDimensionCompatibleForGX2View(baseTexture->dim, dimView)) return VIEW_NOT_COMPATIBLE; if (baseTexture->isDepth && baseTexture->format != format) { // depth view with different format cemuLog_logDebug(LogType::Force, "_createMapping(): Incompatible depth view format"); return VIEW_NOT_COMPATIBLE; } // AMD has a bug on OpenGL where it ignores the internal format of texture views when they are bound as render targets, // as a result we cant use texture views when they have a different format if (baseTexture->format != format) return VIEW_NOT_COMPATIBLE; if ((m + firstMip + numMip) > baseTexture->mipLevels || (s + firstSlice + numSlice) > baseTexture->depth) { relativeMipIndex = m; relativeSliceIndex = s; return VIEW_BASE_TOO_SMALL; } relativeMipIndex = m; relativeSliceIndex = s; return VIEW_COMPATIBLE; } } } return VIEW_NOT_COMPATIBLE; } // deletes any related textures that have become redundant (aka textures that can also be represented entirely as a view into the new texture) void LatteTexture_DeleteAbsorbedSubtextures(LatteTexture* texture) { for(size_t i=0; i<texture->list_compatibleRelations.size(); i++) { LatteTextureRelation* textureRelation = texture->list_compatibleRelations[i]; LatteTexture* relatedTexture = (textureRelation->baseTexture!=texture)? textureRelation->baseTexture:textureRelation->subTexture; sint32 relativeMipIndex; sint32 relativeSliceIndex; if (LatteTexture_CanTextureBeRepresentedAsView(texture, relatedTexture->physAddress, relatedTexture->width, relatedTexture->height, relatedTexture->pitch, relatedTexture->dim, relatedTexture->format, relatedTexture->isDepth, 0, relatedTexture->mipLevels, 0, relatedTexture->depth, relativeMipIndex, relativeSliceIndex) == VIEW_COMPATIBLE) { LatteTexture_Delete(relatedTexture); LatteGPUState.repeatTextureInitialization = true; } } } void LatteTexture_RecreateTextureWithDifferentMipSliceCount(LatteTexture* texture, MPTR physMipAddr, sint32 newMipCount, sint32 newDepth) { Latte::E_DIM newDim = texture->dim; if (newDim == Latte::E_DIM::DIM_2D && newDepth > 1) newDim = Latte::E_DIM::DIM_2D_ARRAY; else if (newDim == Latte::E_DIM::DIM_1D && newDepth > 1) newDim = Latte::E_DIM::DIM_1D_ARRAY; LatteTextureView* view = LatteTexture_CreateTexture(newDim, texture->physAddress, physMipAddr, texture->format, texture->width, texture->height, newDepth, texture->pitch, newMipCount, texture->swizzle, texture->tileMode, texture->isDepth); cemu_assert(!(view->baseTexture->mipLevels <= 1 && physMipAddr == MPTR_NULL && newMipCount > 1)); // copy data from old texture if its dynamically updated if (texture->isUpdatedOnGPU) { LatteTexture_copyData(texture, view->baseTexture, texture->mipLevels, texture->depth); view->baseTexture->isUpdatedOnGPU = true; } // remove old texture LatteTexture_Delete(texture); // gather texture relations for new texture LatteTexture_GatherTextureRelations(view->baseTexture); LatteTexture_UpdateTextureFromDynamicChanges(view->baseTexture); // todo - inherit 'isUpdatedOnGPU' flag for each mip/slice // delete any individual smaller slices/mips that have become redundant LatteTexture_DeleteAbsorbedSubtextures(view->baseTexture); } // create new texture representation // if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible (todo - we should differentiate between Latte compatible views and renderer compatible) // the returned view will map to the provided mip and slice range within the created texture, this is to match the behavior of lookupSliceEx LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dimBase, Latte::E_DIM dimView, bool isDepth, bool allowCreateNewDataTexture) { if (format == Latte::E_GX2SURFFMT::INVALID_FORMAT) { cemuLog_logDebug(LogType::Force, "LatteTexture_CreateMapping(): Invalid format"); return nullptr; } // note: When creating an existing texture, we only allow mip and slice expansion at the end cemu_assert_debug(depth); cemu_assert_debug(!(depth > 1 && dimBase == Latte::E_DIM::DIM_2D)); cemu_assert_debug(!(numSlice > 1 && dimView == Latte::E_DIM::DIM_2D)); // todo, depth and numSlice are redundant sint32 sliceCount = firstSlice + numSlice; boost::container::small_vector<LatteTexture*, 16> list_overlappingTextures; for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++) { sint32 mipIndex = 0; uint32 calcSliceAddrStart; uint32 calcSliceSize; sint32 calcSubSliceIndex; LatteAddrLib::CalculateMipAndSliceAddr(physAddr, physMipAddr, format, width, height, depth, dimBase, tileMode, swizzle, 0, mipIndex, sliceIndex, &calcSliceAddrStart, &calcSliceSize, &calcSubSliceIndex); uint32 calcSliceAddrEnd = calcSliceAddrStart + calcSliceSize; // attempt to create view in already existing texture first (we may have to recreate the texture with new specifications) loopItrMemOccupancyBuckets(calcSliceAddrStart, calcSliceAddrEnd) { for (auto& occupancy : list_texMemOccupancyBucket[bucketIndex]) { if (calcSliceAddrEnd >= occupancy.addrStart && calcSliceAddrStart < occupancy.addrEnd) { if (calcSliceAddrStart == occupancy.addrStart) { // overlapping with zero x/y offset if (std::find(list_overlappingTextures.begin(), list_overlappingTextures.end(), occupancy.sliceMipInfo->texture) == list_overlappingTextures.end()) { list_overlappingTextures.push_back(occupancy.sliceMipInfo->texture); } } else { // overlapping but not matching directly // todo - check if they match with a y offset } } } } } // try to merge textures if possible for (auto& tex : list_overlappingTextures) { sint32 relativeMipIndex; sint32 relativeSliceIndex; VIEWCOMPATIBILITY viewCompatibility = LatteTexture_CanTextureBeRepresentedAsView(tex, physAddr, width, height, pitch, dimView, format, isDepth, firstMip, numMip, firstSlice, numSlice, relativeMipIndex, relativeSliceIndex); if (viewCompatibility == VIEW_NOT_COMPATIBLE) { allowCreateNewDataTexture = true; continue; } if (viewCompatibility == VIEW_BASE_TOO_SMALL) { if (relativeMipIndex != 0 || relativeSliceIndex != 0) { // not yet supported allowCreateNewDataTexture = true; continue; } // new mapping has more slices/mips than known texture -> expand texture sint32 newDepth = std::max(relativeSliceIndex + firstSlice + numSlice, std::max(depth, tex->depth)); sint32 newMipCount = std::max(relativeMipIndex + firstMip + numMip, tex->mipLevels); uint32 newPhysMipAddr; if ((relativeMipIndex + firstMip + numMip) > 1) { newPhysMipAddr = physMipAddr; } else { newPhysMipAddr = tex->physMipAddress; } LatteTexture_RecreateTextureWithDifferentMipSliceCount(tex, newPhysMipAddr, newMipCount, newDepth); return LatteTexture_CreateMapping(physAddr, physMipAddr, width, height, depth, pitch, tileMode, swizzle, firstMip, numMip, firstSlice, numSlice, format, dimBase, dimView, isDepth); } else if(viewCompatibility == VIEW_COMPATIBLE) { LatteTextureView* view = tex->GetOrCreateView(dimView, format, relativeMipIndex + firstMip, numMip, relativeSliceIndex + firstSlice, numSlice); if (relativeMipIndex != 0 || relativeSliceIndex != 0) { // for accesses to mips/slices using a physAddress offset we manually need to create a new view lookup // by default views only create a lookup for the base texture physAddress view->CreateLookupForSubTexture(relativeMipIndex, relativeSliceIndex); #ifdef CEMU_DEBUG_ASSERT LatteTextureView* testView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, firstMip, numMip, firstSlice, numSlice, format, dimView); cemu_assert(testView); #endif } return view; } else { cemu_assert_debug(false); } } // create new texture if (allowCreateNewDataTexture == false) return nullptr; LatteTextureView* view = LatteTexture_CreateTexture(dimBase, physAddr, physMipAddr, format, width, height, depth, pitch, firstMip + numMip, swizzle, tileMode, isDepth); LatteTexture* newTexture = view->baseTexture; LatteTexture_GatherTextureRelations(view->baseTexture); LatteTexture_UpdateTextureFromDynamicChanges(view->baseTexture); // delete any individual smaller slices/mips that have become redundant LatteTexture_DeleteAbsorbedSubtextures(view->baseTexture); // create view sint32 relativeMipIndex; sint32 relativeSliceIndex; VIEWCOMPATIBILITY viewCompatibility = LatteTexture_CanTextureBeRepresentedAsView(newTexture, physAddr, width, height, pitch, dimView, format, isDepth, firstMip, numMip, firstSlice, numSlice, relativeMipIndex, relativeSliceIndex); cemu_assert(viewCompatibility == VIEW_COMPATIBLE); return view->baseTexture->GetOrCreateView(dimView, format, relativeMipIndex + firstMip, numMip, relativeSliceIndex + firstSlice, numSlice); } LatteTextureView* LatteTC_LookupTextureByData(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32* searchIndex) { cemu_assert_debug(firstMip == 0); sint32 cSearchIndex = 0; loopItrMemOccupancyBuckets(physAddr, physAddr+1) { auto& bucket = list_texMemOccupancyBucket[bucketIndex]; for (sint32 i = 0; i < bucket.size(); i++) { if (bucket[i].addrStart == physAddr) { LatteTexture* tex = bucket[i].sliceMipInfo->texture; if (tex->physAddress == physAddr && tex->pitch == pitch) { if (firstSlice >= 0 && firstSlice < (tex->depth)) { if (cSearchIndex >= *searchIndex) { (*searchIndex)++; return tex->baseView; } cSearchIndex++; } } } } } return nullptr; } void LatteTC_LookupTexturesByPhysAddr(MPTR physAddr, std::vector<LatteTexture*>& list_textures) { sint32 cSearchIndex = 0; loopItrMemOccupancyBuckets(physAddr, physAddr + 1) { for (sint32 i = 0; i < list_texMemOccupancyBucket[bucketIndex].size(); i++) { if (list_texMemOccupancyBucket[bucketIndex][i].addrStart == physAddr) { LatteTexture* tex = list_texMemOccupancyBucket[bucketIndex][i].sliceMipInfo->texture; if (tex->physAddress == physAddr) { vectorAppendUnique(list_textures, tex); } } } } } LatteTextureView* LatteTC_GetTextureSliceViewOrTryCreate(MPTR srcImagePtr, MPTR srcMipPtr, Latte::E_GX2SURFFMT srcFormat, Latte::E_HWTILEMODE srcTileMode, uint32 srcWidth, uint32 srcHeight, uint32 srcDepth, uint32 srcPitch, uint32 srcSwizzle, uint32 srcSlice, uint32 srcMip, const bool requireExactResolution) { LatteTextureView* sourceView; if(requireExactResolution == false) sourceView = LatteTextureViewLookupCache::lookupSliceMinSize(srcImagePtr, srcWidth, srcHeight, srcPitch, srcMip, srcSlice, srcFormat); else sourceView = LatteTextureViewLookupCache::lookupSlice(srcImagePtr, srcWidth, srcHeight, srcPitch, srcMip, srcSlice, srcFormat); if (sourceView) return sourceView; return LatteTexture_CreateMapping(srcImagePtr, srcMipPtr, srcWidth, srcHeight, srcDepth, srcPitch, srcTileMode, srcSwizzle, srcMip, 1, srcSlice, 1, srcFormat, srcDepth > 1 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false, false); } void LatteTexture_UpdateDataToLatest(LatteTexture* texture) { if (LatteTC_HasTextureChanged(texture)) LatteTexture_ReloadData(texture); if (texture->reloadFromDynamicTextures) { LatteTexture_UpdateCacheFromDynamicTextures(texture); texture->reloadFromDynamicTextures = false; } } LatteTextureSliceMipInfo* LatteTexture::GetSliceMipArrayEntry(sint32 sliceIndex, sint32 mipIndex) { return sliceMipInfo + GetSliceMipArrayIndex(sliceIndex, mipIndex); } std::vector<LatteTexture*> sAllTextures; // entries can be nullptr std::vector<size_t> sAllTextureFreeIndices; void _AddTextureToGlobalList(LatteTexture* tex) { if (sAllTextureFreeIndices.empty()) { tex->globalListIndex = sAllTextures.size(); sAllTextures.emplace_back(tex); return; } size_t index = sAllTextureFreeIndices.back(); sAllTextureFreeIndices.pop_back(); sAllTextures[index] = tex; tex->globalListIndex = index; } void _RemoveTextureFromGlobalList(LatteTexture* tex) { cemu_assert_debug(tex->globalListIndex >= 0 && tex->globalListIndex < sAllTextures.size()); cemu_assert_debug(sAllTextures[tex->globalListIndex] == tex); if (tex->globalListIndex + 1 == sAllTextures.size()) { // if the index is at the end, make the list smaller instead of freeing the index sAllTextures.pop_back(); return; } sAllTextures[tex->globalListIndex] = nullptr; sAllTextureFreeIndices.emplace_back(tex->globalListIndex); } std::vector<LatteTexture*>& LatteTexture::GetAllTextures() { return sAllTextures; } bool LatteTexture_GX2FormatHasStencil(bool isDepth, Latte::E_GX2SURFFMT format) { if (!isDepth) return false; return format == Latte::E_GX2SURFFMT::D24_S8_UNORM || format == Latte::E_GX2SURFFMT::D24_S8_FLOAT || format == Latte::E_GX2SURFFMT::D32_S8_FLOAT; } LatteTexture::LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) { _AddTextureToGlobalList(this); if (depth < 1) depth = 1; // setup texture object this->physAddress = physAddress; this->dim = dim; this->format = format; this->width = width; this->height = height; this->depth = depth; this->swizzle = swizzle; this->pitch = pitch; this->mipLevels = mipLevels; this->tileMode = tileMode; this->isDepth = isDepth; this->hasStencil = LatteTexture_GX2FormatHasStencil(isDepth, format); this->physMipAddress = physMipAddress; this->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter(); this->lastWriteEventCounter = LatteTexture_getNextUpdateEventCounter(); // handle graphic pack overwrite rules for (const auto& gp : GraphicPack2::GetActiveGraphicPacks()) { for (const auto& rule : gp->GetTextureRules()) { if (!rule.filter_settings.format_whitelist.empty() && std::find(rule.filter_settings.format_whitelist.begin(), rule.filter_settings.format_whitelist.end(), (uint32)format) == rule.filter_settings.format_whitelist.end()) continue; if (!rule.filter_settings.format_blacklist.empty() && std::find(rule.filter_settings.format_blacklist.begin(), rule.filter_settings.format_blacklist.end(), (uint32)format) != rule.filter_settings.format_blacklist.end()) continue; if (!rule.filter_settings.tilemode_whitelist.empty() && std::find(rule.filter_settings.tilemode_whitelist.begin(), rule.filter_settings.tilemode_whitelist.end(), (int)tileMode) == rule.filter_settings.tilemode_whitelist.end()) continue; if (!rule.filter_settings.tilemode_blacklist.empty() && std::find(rule.filter_settings.tilemode_blacklist.begin(), rule.filter_settings.tilemode_blacklist.end(), (int)tileMode) != rule.filter_settings.tilemode_blacklist.end()) continue; if (rule.filter_settings.width != -1 && rule.filter_settings.width != width) continue; if (rule.filter_settings.height != -1 && rule.filter_settings.height != height) continue; if (rule.filter_settings.depth != -1 && rule.filter_settings.depth != depth) continue; if (rule.filter_settings.inMEM1 == GraphicPack2::TextureRule::FILTER_SETTINGS::MEM1_FILTER::OUTSIDE && mmuRange_MEM1.containsAddress(this->physAddress)) continue; if (rule.filter_settings.inMEM1 == GraphicPack2::TextureRule::FILTER_SETTINGS::MEM1_FILTER::INSIDE && !mmuRange_MEM1.containsAddress(this->physAddress)) continue; this->overwriteInfo.width = width; this->overwriteInfo.height = height; this->overwriteInfo.depth = depth; if (rule.overwrite_settings.width != -1) { this->overwriteInfo.hasResolutionOverwrite = true; this->overwriteInfo.width = rule.overwrite_settings.width; } if (rule.overwrite_settings.height != -1) { this->overwriteInfo.hasResolutionOverwrite = true; this->overwriteInfo.height = rule.overwrite_settings.height; } if (rule.overwrite_settings.depth != -1) { this->overwriteInfo.hasResolutionOverwrite = true; this->overwriteInfo.depth = rule.overwrite_settings.depth; } if (rule.overwrite_settings.format != -1) { this->overwriteInfo.hasFormatOverwrite = true; this->overwriteInfo.format = rule.overwrite_settings.format; } if (rule.overwrite_settings.lod_bias != -1) { this->overwriteInfo.hasLodBias = true; this->overwriteInfo.lodBias = rule.overwrite_settings.lod_bias; } if (rule.overwrite_settings.relative_lod_bias != -1) { this->overwriteInfo.hasRelativeLodBias = true; this->overwriteInfo.relativeLodBias = rule.overwrite_settings.relative_lod_bias; } if (rule.overwrite_settings.anistropic_value != -1) { this->overwriteInfo.anisotropicLevel = rule.overwrite_settings.anistropic_value; } } } // determine if this texture should ever be mirrored to CPU RAM if (this->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED) { this->enableReadback = true; } // calculate number of potential mip levels (from effective size) sint32 effectiveWidth = width; sint32 effectiveHeight = height; sint32 effectiveDepth = depth; if (this->overwriteInfo.hasResolutionOverwrite) { effectiveWidth = this->overwriteInfo.width; effectiveHeight = this->overwriteInfo.height; effectiveDepth = this->overwriteInfo.depth; } this->maxPossibleMipLevels = 1; if (dim != Latte::E_DIM::DIM_3D) { for (sint32 i = 0; i < 20; i++) { if ((effectiveWidth >> i) <= 1 && (effectiveHeight >> i) <= 1) { this->maxPossibleMipLevels = i + 1; break; } } } else { for (sint32 i = 0; i < 20; i++) { if ((effectiveWidth >> i) <= 1 && (effectiveHeight >> i) <= 1 && (effectiveDepth >> i) <= 1) { this->maxPossibleMipLevels = i + 1; break; } } } } LatteTexture::~LatteTexture() { _RemoveTextureFromGlobalList(this); cemu_assert_debug(baseView == nullptr); cemu_assert_debug(views.empty()); }; // sync texture data between overlapping textures void LatteTexture_UpdateCacheFromDynamicTextures(LatteTexture* textureDest) { LatteTexture_UpdateTextureFromDynamicChanges(textureDest); } void LatteTexture_MarkConnectedTexturesForReloadFromDynamicTextures(LatteTexture* texture) { for (auto& it : texture->list_compatibleRelations) { if (texture == it->baseTexture) it->subTexture->reloadFromDynamicTextures = true; else it->baseTexture->reloadFromDynamicTextures = true; } } void LatteTexture_TrackTextureGPUWrite(LatteTexture* texture, uint32 slice, uint32 mip, uint64 eventCounter) { LatteTexture_MarkDynamicTextureAsChanged(texture->baseView, slice, mip, eventCounter); LatteTC_ResetTextureChangeTracker(texture); texture->isUpdatedOnGPU = true; texture->lastUnflushedRTDrawcallIndex = LatteGPUState.drawCallCounter; } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTexture.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteConst.h" struct LatteSamplerState { uint8 clampS; uint8 clampT; uint8 clampR; uint32 filterMag; // openGL constant uint32 filterMin; // openGL constant uint8 maxAniso; uint8 maxMipLevels; uint8 depthCompareMode; uint8 depthCompareFunc; uint16 minLod; uint16 maxLod; sint16 lodBias; uint8 borderType; float borderColor[4]; }; #include "Cafe/HW/Latte/Core/LatteTextureView.h" class LatteTexture { public: LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); virtual ~LatteTexture(); virtual void AllocateOnHost() = 0; LatteTextureView* GetOrCreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) { for (auto& itr : views) { if (itr->firstMip == firstMip && itr->numMip == mipCount && itr->firstSlice == firstSlice && itr->numSlice == sliceCount && itr->dim == dim && itr->format == format) return itr; } return CreateView(dim, format, firstMip, mipCount, firstSlice, sliceCount); } LatteTextureView* GetOrCreateView(sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) { return GetOrCreateView(this->dim, this->format, firstMip, mipCount, firstSlice, sliceCount); } bool IsCompressedFormat() const { return Latte::IsCompressedFormat(format); } uint32 GetBPP() const { return Latte::GetFormatBits(format); } bool Is3DTexture() const { return dim == Latte::E_DIM::DIM_3D; }; void GetSize(sint32& width, sint32& height, sint32 mipLevel) const { width = std::max(1, this->width >> mipLevel); height = std::max(1, this->height >> mipLevel); } // similar to GetSize, but returns the real size of the texture taking into account any resolution overwrite by gfx pack rules void GetEffectiveSize(sint32& effectiveWidth, sint32& effectiveHeight, sint32 mipLevel) const { if( overwriteInfo.hasResolutionOverwrite ) { effectiveWidth = overwriteInfo.width; effectiveHeight = overwriteInfo.height; } else { effectiveWidth = this->width; effectiveHeight = this->height; } effectiveWidth = std::max(1, effectiveWidth >> mipLevel); effectiveHeight = std::max(1, effectiveHeight >> mipLevel); } sint32 GetMipDepth(sint32 mipIndex) { cemu_assert_debug(mipIndex >= 0 && mipIndex < this->mipLevels); if (Is3DTexture()) return std::max(depth >> mipIndex, 1); return depth; } sint32 GetMipWidth(sint32 mipIndex) { cemu_assert_debug(mipIndex >= 0 && mipIndex < this->mipLevels); return std::max(width >> mipIndex, 1); } sint32 GetMipHeight(sint32 mipIndex) { cemu_assert_debug(mipIndex >= 0 && mipIndex < this->mipLevels); return std::max(height >> mipIndex, 1); } // return the size necessary for a 1D array to store one entry per slice/mip combo (using getSliceMipArrayIndex) sint32 GetSliceMipArraySize() { cemu_assert_debug(this->depth > 0); cemu_assert_debug(this->mipLevels > 0); return this->depth * this->mipLevels; } // calculate index within slice/mip array sint32 GetSliceMipArrayIndex(sint32 sliceIndex, sint32 mipIndex) { cemu_assert_debug(sliceIndex < depth); cemu_assert_debug(mipIndex < mipLevels); // to keep the computation fast, we ignore that 3D textures have decreasing slice count per mip and leave some indices empty return this->depth * mipIndex + sliceIndex; } struct LatteTextureSliceMipInfo* GetSliceMipArrayEntry(sint32 sliceIndex, sint32 mipIndex); static std::vector<LatteTexture*>& GetAllTextures(); // note: can contain nullptr entries protected: virtual LatteTextureView* CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) = 0; public: // Latte texture info MPTR physAddress; MPTR physMipAddress; Latte::E_DIM dim; Latte::E_GX2SURFFMT format; Latte::E_HWTILEMODE tileMode; sint32 width; sint32 height; sint32 depth; sint32 pitch; sint32 mipLevels; // number of used mip levels (must be at least 1) sint32 maxPossibleMipLevels{}; // number of mips that can potentially be used (uses resolution from texture rules) uint32 swizzle; uint32 lastRenderTargetSwizzle{}; // set to rt swizzle bits whenever the texture is being rendered to // data info bool isDataDefined{}; bool isDepth; bool hasStencil{}; // for depth textures // info per mip/slice struct LatteTextureSliceMipInfo* sliceMipInfo{}; // physical offsets for start and end of data (calculated on texture load) MPTR texDataPtrLow{}; MPTR texDataPtrHigh{}; uint32 texDataHash2{}; // state bool isUpdatedOnGPU{ false }; // set if any GPU-side operation modified this texture and strict one-way RAM->VRAM memory mirroring no longer applies bool enableReadback{ false }; // if true, texture will be mirrored back to CPU RAM under specific circumstances // invalidation bool forceInvalidate{}; // cache control bool reloadFromDynamicTextures{}; // last update (dynamic) uint64 lastWriteEventCounter; uint64 lastUpdateEventCounter; uint32 lastUpdateFrameCounter{}; uint32 reloadCount{}; // last update (from RAM data) uint32 lastDataUpdateFrameCounter{}; // optimization bool useLightHash{}; // overwrite info struct { // resolution bool hasResolutionOverwrite; sint32 width; sint32 height; sint32 depth; // format bool hasFormatOverwrite; sint32 format; // lod bias sint16 lodBias; // in 1/64th steps bool hasLodBias; // relative lod bias sint16 relativeLodBias; // in 1/64th steps bool hasRelativeLodBias; // anisotropic sint8 anisotropicLevel{ -1 }; // 1<<n, 0 is disabled }overwriteInfo{}; // for detecting multiple references during the same drawcall uint32 lockTextureUpdateId{}; // set to current texture update id whenever texture is bound and used uint32 lockCount{}; // usage uint32 lastAccessTick{}; uint32 lastAccessFrameCount{}; // detection of render feedback loops (see OpenGL 4.5 spec, 9.3) uint32 lastUnflushedRTDrawcallIndex{}; // views LatteTextureView* baseView{}; std::vector<LatteTextureView*> views; // list of all views created for this texture, including baseView // texture relations (overlapping memory) std::vector<struct LatteTextureRelation*> list_compatibleRelations; // list of other data-compatible textures that share the same memory ranges // index in global texture list size_t globalListIndex; }; struct LatteTextureDefinition // todo - actually use this in LatteTexture class { MPTR physAddress; MPTR physMipAddress; Latte::E_DIM dim; Latte::E_GX2SURFFMT format; uint32 width; uint32 height; uint32 depth; uint32 pitch; uint32 mipLevels; uint32 swizzle; Latte::E_HWTILEMODE tileMode; bool isDepth; LatteTextureDefinition() = default; LatteTextureDefinition(const LatteTexture* tex) { physAddress = tex->physAddress; physMipAddress = tex->physMipAddress; dim = tex->dim; format = tex->format; width = tex->width; height = tex->height; depth = tex->depth; pitch = tex->pitch; mipLevels = tex->mipLevels; swizzle = tex->swizzle; tileMode = tex->tileMode; isDepth = tex->isDepth; } }; class LatteTextureView; struct LatteTextureSliceMipDataOverlap_t { LatteTexture* destTexture; struct LatteTextureSliceMipInfo* destMipSliceInfo; }; struct LatteTextureSliceMipInfo { uint32 addrStart; // same as physAddr if this mip were it's own texture uint32 addrEnd; uint32 subIndex; // for thick tiles, multiple (4) slices can be interleaved into the same data range. This stores the index LatteTexture* texture; sint32 sliceIndex; sint32 mipIndex; // change tracking uint32 dataChecksum; uint64 lastDynamicUpdate; // format info Latte::E_HWTILEMODE tileMode; sint32 pitch; // data overlap tracking uint32 estDataAddrStart; uint32 estDataAddrEnd; std::vector<LatteTextureSliceMipDataOverlap_t> list_dataOverlap; }; struct LatteTextureRelation { LatteTexture* baseTexture; LatteTexture* subTexture; // texture which is contained within baseTexture sint32 baseSliceIndex; sint32 baseMipIndex; sint32 sliceCount; sint32 mipCount; //sint32 xOffset; sint32 yOffset; }; // used if external modules want to retrieve texture cache information struct LatteTextureViewInformation { MPTR physAddress; MPTR physMipAddress; sint32 width; sint32 height; sint32 pitch; sint32 firstMip; sint32 numMip; sint32 firstSlice; sint32 numSlice; Latte::E_GX2SURFFMT format; Latte::E_DIM dim; }; struct LatteTextureInformation { MPTR physAddress; MPTR physMipAddress; sint32 width; sint32 height; sint32 depth; sint32 pitch; sint32 mipLevels; Latte::E_GX2SURFFMT format; bool isDepth; Latte::E_DIM dim; Latte::E_HWTILEMODE tileMode; // access uint32 lastAccessTick; uint32 lastAccessFrameCount; bool isUpdatedOnGPU; // misc uint32 alternativeViewCount; // overwrite info struct { // resolution bool hasResolutionOverwrite; sint32 width; sint32 height; sint32 depth; }overwriteInfo{}; std::vector<LatteTextureViewInformation> views; }; void LatteTexture_init(); void LatteTexture_updateTextures(); std::vector<LatteTextureInformation> LatteTexture_QueryCacheInfo(); float* LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType shaderType, sint32 texUnit); LatteTextureView* LatteTexture_CreateTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); void LatteTexture_Delete(LatteTexture* texture); void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData); sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture); bool LatteTexture_doesEffectiveRescaleRatioMatch(LatteTexture* texture1, sint32 mipLevel1, LatteTexture* texture2, sint32 mipLevel2); void LatteTexture_scaleToEffectiveSize(LatteTexture* texture, sint32* x, sint32* y, sint32 mipLevel); uint64 LatteTexture_getNextUpdateEventCounter(); void LatteTexture_UpdateCacheFromDynamicTextures(LatteTexture* texture); void LatteTexture_MarkConnectedTexturesForReloadFromDynamicTextures(LatteTexture* texture); void LatteTexture_TrackTextureGPUWrite(LatteTexture* texture, uint32 slice, uint32 mip, uint64 eventCounter); void LatteTexture_InitSliceAndMipInfo(LatteTexture* texture); void LatteTexture_RegisterTextureMemoryOccupancy(LatteTexture* texture); void LatteTexture_UnregisterTextureMemoryOccupancy(LatteTexture* texture); void LatteTexture_DeleteTextureRelations(LatteTexture* texture); void LatteTexture_DeleteDataOverlapTracking(LatteTexture* texture); LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dimBase, Latte::E_DIM dimView, bool isDepth, bool allowCreateNewDataTexture = true); LatteTextureView* LatteTC_LookupTextureByData(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32* searchIndex); void LatteTC_LookupTexturesByPhysAddr(MPTR physAddr, std::vector<LatteTexture*>& list_textures); LatteTextureView* LatteTC_GetTextureSliceViewOrTryCreate(MPTR srcImagePtr, MPTR srcMipPtr, Latte::E_GX2SURFFMT srcFormat, Latte::E_HWTILEMODE srcTileMode, uint32 srcWidth, uint32 srcHeight, uint32 srcDepth, uint32 srcPitch, uint32 srcSwizzle, uint32 srcSlice, uint32 srcMip, const bool requireExactResolution = false); void LatteTexture_MarkDynamicTextureAsChanged(LatteTextureView* textureView, sint32 sliceIndex, sint32 mipIndex, uint64 eventCounter); void LatteTexture_UpdateTextureFromDynamicChanges(LatteTexture* texture); void LatteTexture_UpdateDataToLatest(LatteTexture* texture); ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTextureCache.cpp ================================================ #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Common/cpu_features.h" std::unordered_set<LatteTexture*> g_allTextures; void LatteTC_Init() { cemu_assert_debug(g_allTextures.empty()); } void LatteTC_RegisterTexture(LatteTexture* tex) { g_allTextures.emplace(tex); } void LatteTC_UnregisterTexture(LatteTexture* tex) { g_allTextures.erase(tex); } // sample few uint64s uniformly over memory range uint32 _quickStochasticHash(void* texData, uint32 memRange) { uint64* texDataU64 = (uint64*)texData; uint64 hashVal = 0; memRange /= sizeof(uint64); uint32 memStep = memRange / 37; // use prime here to avoid memStep aligning nicely with pitch of texture, leading to sampling only along the border of a texture for (sint32 i = 0; i < 37; i++) { hashVal += *texDataU64; hashVal = (hashVal << 3) | (hashVal >> 61); texDataU64 += memStep; } return (uint32)hashVal ^ (uint32)(hashVal >> 32); } uint32 LatteTexture_CalculateTextureDataHash(LatteTexture* hostTexture) { if( hostTexture->texDataPtrHigh == hostTexture->texDataPtrLow ) { return 0; } if (hostTexture->format == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT) { // this is an exotic format that usually isn't generated or updated CPU-side // therefore as an optimization we can risk to only check a minimal amount of bytes at the beginning of the texture data // updates which change the entire texture should still be detected this way // this also helps with a bug in BotW which seems to fill the empty areas of the textures with other data which causes unnecessary invalidations and texture reloads // Wonderful 101 generates this format in a 8x8x8 3D texture using tiling aperture if (hostTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THICK && hostTexture->depth == 8 && hostTexture->width == 8 && hostTexture->height == 8) { // special case for Wonderful 101 uint32* texDataU32 = (uint32*)memory_getPointerFromPhysicalOffset(hostTexture->texDataPtrLow); return texDataU32[0] ^ texDataU32[0x100/4] ^ texDataU32[0x200/4] ^ texDataU32[0x300/4]; // check the first thick slice (each slice has 0x400 bytes, with 0x100 bytes between layers) } uint32* texDataU32 = (uint32*)memory_getPointerFromPhysicalOffset(hostTexture->texDataPtrLow); return texDataU32[0] ^ texDataU32[1] ^ texDataU32[2] ^ texDataU32[3]; } uint32 memRange = hostTexture->texDataPtrHigh - hostTexture->texDataPtrLow; uint32* texDataU32 = (uint32*)memory_getPointerFromPhysicalOffset(hostTexture->texDataPtrLow); uint32 hashVal = 0; uint32 pixelCount = hostTexture->width*hostTexture->height; bool isCompressedFormat = hostTexture->IsCompressedFormat(); if (isCompressedFormat || hostTexture->useLightHash) { // check only 32 samples of the texture if (memRange < 256) { memRange /= sizeof(uint32); while (memRange--) { hashVal += *texDataU32; hashVal = (hashVal << 3) | (hashVal >> 29); texDataU32++; } } else { hashVal = _quickStochasticHash(texDataU32, memRange); } return hashVal; } if( pixelCount <= (700*700) ) { // small texture size bool isCompressedFormat = hostTexture->IsCompressedFormat(); if( isCompressedFormat == false || memRange < 0x200 ) { memRange /= (4*sizeof(uint32)); while( memRange-- ) { hashVal += *texDataU32; hashVal = (hashVal<<3)|(hashVal>>29); texDataU32 += 4; } } else { memRange /= (32*sizeof(uint32)); while( memRange-- ) { hashVal += *texDataU32; hashVal = (hashVal<<3)|(hashVal>>29); texDataU32 += 32; } } } else if( pixelCount <= (1200*1200) ) { // medium texture size bool isCompressedFormat = hostTexture->IsCompressedFormat(); if( isCompressedFormat == false ) { memRange /= (12*sizeof(uint32)); while( memRange-- ) { hashVal += *texDataU32; hashVal = (hashVal<<3)|(hashVal>>29); texDataU32 += 12; } } else { memRange /= (96*sizeof(uint32)); while( memRange-- ) { hashVal += *texDataU32; hashVal = (hashVal<<3)|(hashVal>>29); texDataU32 += 96; } } } else { // huge texture size bool isCompressedFormat = hostTexture->IsCompressedFormat(); if( isCompressedFormat == false ) { #if BOOST_OS_WINDOWS if (g_CPUFeatures.x86.avx2) { __m256i h256 = { 0 }; __m256i* readPtr = (__m256i*)texDataU32; memRange /= (288); while (memRange--) { __m256i temp = _mm256_load_si256(readPtr); readPtr += (288 / 32); h256 = _mm256_xor_si256(h256, temp); } #ifdef __clang__ hashVal = h256[0] + h256[1] + h256[2] + h256[3] + h256[4] + h256[5] + h256[6] + h256[7]; #else hashVal = h256.m256i_u32[0] + h256.m256i_u32[1] + h256.m256i_u32[2] + h256.m256i_u32[3] + h256.m256i_u32[4] + h256.m256i_u32[5] + h256.m256i_u32[6] + h256.m256i_u32[7]; #endif } #else if( false ) {} #endif else { memRange /= (32 * sizeof(uint64)); uint64 h64 = 0; uint64* texDataU64 = (uint64*)texDataU32; while (memRange--) { h64 += *texDataU64; h64 = (h64 << 3) | (h64 >> 61); texDataU64 += 32; } hashVal = (h64 & 0xFFFFFFFF) + (h64 >> 32); } } else { memRange /= (512*sizeof(uint32)); while( memRange-- ) { hashVal += *texDataU32; hashVal = (hashVal<<3)|(hashVal>>29); texDataU32 += 512; } } } return hashVal; } uint64 _botwLargeTexHax = 0; bool LatteTC_HasTextureChanged(LatteTexture* hostTexture, bool force) { if (hostTexture->forceInvalidate) { force = true; debug_printf("Force invalidate 0x%08x\n", hostTexture->physAddress); hostTexture->forceInvalidate = false; } // if texture is written by GPU operations we switch to a faster hash implementation if (hostTexture->isUpdatedOnGPU && hostTexture->useLightHash == false) { hostTexture->useLightHash = true; // update hash hostTexture->texDataHash2 = LatteTexture_CalculateTextureDataHash(hostTexture); } // only check each texture for updates once a frame // todo: Instead of relying on frames, it would be better to recheck only after any GPU wait operation occurred. if( hostTexture->lastDataUpdateFrameCounter == LatteGPUState.frameCounter && force == false) return false; hostTexture->lastDataUpdateFrameCounter = LatteGPUState.frameCounter; // we assume that certain texture properties indicate that the texture will never be written by the CPU if (hostTexture->width == 1280 && hostTexture->format != Latte::E_GX2SURFFMT::R8_UNORM && force == false) { // todo - remove this or find a better way to handle excluded texture invalidation checks (maybe via game profile?) return false; } // workaround for corrupted terrain texture in BotW after video playback // probably would be fixed if we added support for invalidating individual slices/mips of a texture uint32 texDataHash = LatteTexture_CalculateTextureDataHash(hostTexture); if( texDataHash != hostTexture->texDataHash2 ) { hostTexture->texDataHash2 = texDataHash; if (hostTexture->depth == 83 && hostTexture->width == 1024 && hostTexture->height == 1024) { _botwLargeTexHax = LatteGPUState.frameCounter; } return true; } if (_botwLargeTexHax != 0 && hostTexture->depth == 83 && hostTexture->width == 1024 && hostTexture->height == 1024 && _botwLargeTexHax != LatteGPUState.frameCounter) { _botwLargeTexHax = 0; return true; } return false; } void LatteTC_ResetTextureChangeTracker(LatteTexture* hostTexture, bool force) { if( hostTexture->lastDataUpdateFrameCounter == LatteGPUState.frameCounter && force == false) return; hostTexture->lastDataUpdateFrameCounter = LatteGPUState.frameCounter; LatteTC_HasTextureChanged(hostTexture, true); } /* * This function should be called whenever the texture is still used in some form (any kind of access counts) * The purpose of this function is to prevent garbage collection of textures that are still actively used */ void LatteTC_MarkTextureStillInUse(LatteTexture* texture) { texture->lastAccessTick = LatteGPUState.currentDrawCallTick; texture->lastAccessFrameCount = LatteGPUState.frameCounter; } // check if a texture has been overwritten by another texture using GPU-writes bool LatteTC_IsTextureDataOverwritten(LatteTexture* texture) { // check overlaps sint32 mipLevels = texture->mipLevels; sint32 sliceCount = texture->depth; mipLevels = std::min(mipLevels, 3); // only check first 3 mip levels for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) { sint32 mipSliceCount; if (texture->Is3DTexture()) mipSliceCount = std::max(1, sliceCount >> mipIndex); else mipSliceCount = sliceCount; for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) { LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); bool isSliceMipOutdated = false; for (auto& overlapData : sliceMipInfo->list_dataOverlap) { if (sliceMipInfo->lastDynamicUpdate < overlapData.destMipSliceInfo->lastDynamicUpdate) { isSliceMipOutdated = true; break; } } if (isSliceMipOutdated == false) return false; } } return true; } void LatteTexture_Delete(LatteTexture* texture) { LatteTC_UnregisterTexture(texture); LatteMRT::NotifyTextureDeletion(texture); LatteTextureReadback_NotifyTextureDeletion(texture); LatteTexture_DeleteTextureRelations(texture); // delete views while (!texture->views.empty()) delete texture->views[0]; cemu_assert_debug(texture->views.empty()); cemu_assert_debug(texture->baseView == nullptr); // free data overlap tracking LatteTexture_DeleteDataOverlapTracking(texture); // remove from lists LatteTexture_UnregisterTextureMemoryOccupancy(texture); // free memory if (texture->sliceMipInfo) { delete[] texture->sliceMipInfo; texture->sliceMipInfo = nullptr; } delete texture; } /* * Checks if the texture can be dropped from the cache and if yes, delete it * Returns true if the texture was deleted */ bool LatteTC_CleanupCheckTexture(LatteTexture* texture, uint32 currentTick) { uint32 currentFrameCount = LatteGPUState.frameCounter; uint32 ticksSinceLastAccess = currentTick - texture->lastAccessTick; uint32 framesSinceLastAccess = currentFrameCount - texture->lastAccessFrameCount; if( !texture->isUpdatedOnGPU ) { // RAM-only textures are safe to be deleted since we can always restore them from RAM if( ticksSinceLastAccess >= (120*1000) && framesSinceLastAccess >= 2000 ) { LatteTexture_Delete(texture); return true; } } if ((LatteGPUState.currentDrawCallTick - texture->lastAccessTick) >= 100 && LatteTC_IsTextureDataOverwritten(texture)) { LatteTexture_Delete(texture); return true; } // if unused for more than 5 seconds, start deleting views since they are cheap to recreate if (ticksSinceLastAccess >= 5 * 1000 && framesSinceLastAccess >= 30) { for (sint32 i = 0; i < 3; i++) { if (texture->views.size() <= 1) break; LatteTextureView* view = texture->views[0]; if (view == texture->baseView) view = texture->views[1]; delete view; } } return false; } void LatteTexture_RefreshInfoCache(); /* * Scans for unused textures and deletes them * Called at the end of every frame */ void LatteTC_CleanupUnusedTextures() { static size_t currentScanIndex = 0; uint32 currentTick = GetTickCount(); sint32 maxDelete = 10; std::vector<LatteTexture*>& allTextures = LatteTexture::GetAllTextures(); if (!allTextures.empty()) { for (sint32 c = 0; c < 25; c++) { if (currentScanIndex >= allTextures.size()) currentScanIndex = 0; LatteTexture* texItr = allTextures[currentScanIndex]; currentScanIndex++; if (!texItr) continue; if (LatteTC_CleanupCheckTexture(texItr, currentTick)) { maxDelete--; if (maxDelete <= 0) break; // deleting can be an expensive operation, dont delete too many at once to avoid micro stutter if (allTextures.empty()) break; } } } LatteTexture_RefreshInfoCache(); // find a better place to call this from? } std::vector<LatteTexture*> LatteTC_GetDeleteableTextures() { std::vector<LatteTexture*> texList; uint32 currentFrameCount = LatteGPUState.frameCounter; for (auto& itr : g_allTextures) { if(itr->lastAccessFrameCount == 0) continue; // not initialized uint32 framesSinceLastAccess = currentFrameCount - itr->lastAccessFrameCount; if(framesSinceLastAccess < 3) continue; if (itr->isUpdatedOnGPU) { if (LatteTC_IsTextureDataOverwritten(itr)) texList.emplace_back(itr); } else { texList.emplace_back(itr); } } return texList; } void LatteTC_UnloadAllTextures() { std::vector<LatteTexture*> allTexturesCopy = LatteTexture::GetAllTextures(); for (auto& itr : allTexturesCopy) { if(itr) LatteTexture_Delete(itr); } LatteRenderTarget_unloadAll(); } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp ================================================ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" struct TexScaleXY { float xy[2]; }; struct { TexScaleXY perUnit[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; // stores actualResolution/effectiveResolution ratio for each texture }LatteTextureScale[static_cast<size_t>(LatteConst::ShaderType::TotalCount)] = { }; float* LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType shaderType, sint32 texUnit) { cemu_assert_debug(texUnit >= 0 && texUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE); return LatteTextureScale[static_cast<size_t>(shaderType)].perUnit[texUnit].xy; } void LatteTexture_setEffectiveTextureScale(LatteConst::ShaderType shaderType, sint32 texUnit, float u, float v) { cemu_assert_debug(texUnit >= 0 && texUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE); float* t = LatteTextureScale[static_cast<size_t>(shaderType)].perUnit[texUnit].xy; t[0] = u; t[1] = v; } void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, bool dumpTex); void LatteTexture_ReloadData(LatteTexture* tex) { tex->reloadCount++; for(sint32 mip=0; mip<tex->mipLevels; mip++) { if(tex->dim == Latte::E_DIM::DIM_2D_ARRAY || tex->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA ) { sint32 numSlices = std::max(tex->depth, 1); for(sint32 s=0; s<numSlices; s++) LatteTextureLoader_UpdateTextureSliceData(tex, s, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); } else if( tex->dim == Latte::E_DIM::DIM_CUBEMAP ) { cemu_assert_debug((tex->depth % 6) == 0); sint32 numFullCubeMaps = tex->depth/6; // number of cubemaps (if numFullCubeMaps is >1 then this texture is a cubemap array) for(sint32 s=0; s<numFullCubeMaps*6; s++) LatteTextureLoader_UpdateTextureSliceData(tex, s, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); } else if( tex->dim == Latte::E_DIM::DIM_3D ) { sint32 mipDepth = std::max(tex->depth>>mip, 1); for(sint32 s=0; s<mipDepth; s++) { LatteTextureLoader_UpdateTextureSliceData(tex, s, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); } } else { // load slice 0 LatteTextureLoader_UpdateTextureSliceData(tex, 0, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); } } tex->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter(); } LatteTextureView* LatteTexture_CreateTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) { const auto tex = g_renderer->texture_createTextureEx(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); // init slice/mip info array LatteTexture_InitSliceAndMipInfo(tex); LatteTexture_RegisterTextureMemoryOccupancy(tex); cemu_assert_debug(mipLevels != 0); LatteTexture_ReloadData(tex); LatteTC_MarkTextureStillInUse(tex); LatteTC_RegisterTexture(tex); // create initial view that maps to the whole texture tex->baseView = tex->GetOrCreateView(0, tex->mipLevels, 0, tex->depth); return tex->baseView; } Latte::E_GX2SURFFMT LatteTexture_ReconstructGX2Format(const Latte::LATTE_SQ_TEX_RESOURCE_WORD1_N& texUnitWord1, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N& texUnitWord4) { Latte::E_GX2SURFFMT gx2Format = (Latte::E_GX2SURFFMT)texUnitWord1.get_DATA_FORMAT(); auto nfa = texUnitWord4.get_NUM_FORM_ALL(); if (nfa == Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_SCALED) gx2Format |= Latte::E_GX2SURFFMT::FMT_BIT_FLOAT; else if (nfa == Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_INT) gx2Format |= Latte::E_GX2SURFFMT::FMT_BIT_INT; if(texUnitWord4.get_FORCE_DEGAMMA()) gx2Format |= Latte::E_GX2SURFFMT::FMT_BIT_SRGB; if (texUnitWord4.get_FORMAT_COMP_X() == Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_FORMAT_COMP::COMP_SIGNED) gx2Format |= Latte::E_GX2SURFFMT::FMT_BIT_SIGNED; return gx2Format; } void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, uint32 glBackendBaseTexUnit, _LatteRegisterSetTextureUnit* texRegBase) { for (sint32 z = 0; z < shaderContext->textureUnitListCount; z++) { sint32 textureIndex = shaderContext->textureUnitList[z]; const auto& texRegister = texRegBase[textureIndex]; // get physical address of texture data MPTR physAddr = (texRegister.word2.get_BASE_ADDRESS() << 8); if (physAddr == MPTR_NULL) continue; // invalid data MPTR physMipAddr = (texRegister.word3.get_MIP_ADDRESS() << 8); // word0 const auto word0 = texRegister.word0; auto dim = word0.get_DIM(); uint32 pitch = (word0.get_PITCH() + 1) << 3; uint32 width = word0.get_WIDTH() + 1; auto tileMode = word0.get_TILE_MODE(); // word1 const auto word1 = texRegister.word1; uint32 depth = word1.get_DEPTH(); if (dim == Latte::E_DIM::DIM_2D_ARRAY || dim == Latte::E_DIM::DIM_3D || dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA || dim == Latte::E_DIM::DIM_1D_ARRAY) { depth = depth + 1; } else { if (dim == Latte::E_DIM::DIM_CUBEMAP) depth = 6 * (depth + 1); if (depth == 0) depth = 1; } uint32 height = word1.get_HEIGHT() + 1; if (dim == Latte::E_DIM::DIM_1D || dim == Latte::E_DIM::DIM_1D_ARRAY) height = 1; if (Latte::IsCompressedFormat(word1.get_DATA_FORMAT())) pitch /= 4; // view slice const auto word4 = texRegister.word4; const auto word5 = texRegister.word5; uint32 viewFirstSlice = word5.get_BASE_ARRAY(); uint32 viewNumSlices = word5.get_LAST_ARRAY() + 1 - viewFirstSlice; uint32 viewFirstMip = word4.get_BASE_LEVEL(); uint32 viewNumMips = word5.get_LAST_LEVEL() + 1 - viewFirstMip; cemu_assert_debug(viewNumMips != 0); Latte::E_GX2SURFFMT format = LatteTexture_ReconstructGX2Format(word1, word4); // todo - AA if (dim == Latte::E_DIM::DIM_2D_MSAA) { // MSAA only supports one mip level? // without this we encounter a crash in The Mysterious Cities of Gold: Secret Paths due to it setting mip count to 2 and leaving mip pointer on an invalid uninitialized value viewFirstMip = 0; viewNumMips = 1; } // swizzle uint32 swizzle = 0; if (Latte::TM_IsMacroTiled(tileMode)) { // extract swizzle bits from pointer if macro-tiled swizzle = (physAddr & 0x700); physAddr &= ~0x700; } bool isDepthSampler = shaderContext->textureUsesDepthCompare[textureIndex]; // look for already existing texture LatteTextureView* textureView; if (!isDepthSampler) textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim); else textureView = LatteTextureViewLookupCache::lookupWithColorOrDepthType(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true); if (!textureView) { // view not found, create a new mapping which will also create a new texture if necessary textureView = LatteTexture_CreateMapping(physAddr, physMipAddr, width, height, depth, pitch, tileMode, swizzle, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, dim, isDepthSampler); if (textureView == nullptr) continue; LatteGPUState.repeatTextureInitialization = true; } if (g_renderer->GetType() == RendererAPI::OpenGL) { // on OpenGL, texture views and sampler parameters are tied together (we are avoiding sampler objects due to driver bugs) // in order to emulate different sampler parameters when a texture is bound multiple times we create extra views OpenGLRenderer* rendererGL = static_cast<OpenGLRenderer*>(g_renderer.get()); // if this texture is bound multiple times then use alternative views if (textureView->lastTextureBindIndex == LatteGPUState.textureBindCounter) { LatteTextureViewGL* textureViewGL = (LatteTextureViewGL*)textureView; // get next unused alternative texture view while (true) { textureViewGL = textureViewGL->GetAlternativeView(); if (textureViewGL->lastTextureBindIndex != LatteGPUState.textureBindCounter) break; } textureView = textureViewGL; } textureView->lastTextureBindIndex = LatteGPUState.textureBindCounter; rendererGL->renderstate_updateTextureSettingsGL(shaderContext, textureView, textureIndex + glBackendBaseTexUnit, word4, textureIndex, isDepthSampler); } g_renderer->texture_setLatteTexture(textureView, textureIndex + glBackendBaseTexUnit); // update if data changed bool swizzleChanged = false; if (textureView->baseTexture->swizzle != swizzle) { debug_printf("BaseSwizzle diff prev %08x new %08x rt %08x tm %d\n", textureView->baseTexture->swizzle, swizzle, textureView->baseTexture->lastRenderTargetSwizzle, textureView->baseTexture->tileMode); if (swizzle == textureView->baseTexture->lastRenderTargetSwizzle) { // last render to texture updated the swizzle and we can assume the texture data is still valid textureView->baseTexture->swizzle = textureView->baseTexture->lastRenderTargetSwizzle; } else { // reload texture swizzleChanged = true; } } else if ((viewFirstMip + viewNumMips) > 1 && (textureView->baseTexture->physMipAddress != physMipAddr)) { debug_printf("MipPhys/Swizzle change diff prev %08x new %08x tm %d\n", textureView->baseTexture->physMipAddress, physMipAddr, textureView->baseTexture->tileMode); swizzleChanged = true; cemu_assert_debug(physMipAddr != MPTR_NULL); } // check for changes if (LatteTC_HasTextureChanged(textureView->baseTexture) || swizzleChanged) { debug_printf("Reload texture 0x%08x res %dx%d memRange %08x-%08x SwizzleChange: %s\n", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height, textureView->baseTexture->texDataPtrLow, textureView->baseTexture->texDataPtrHigh, swizzleChanged ? "yes" : "no"); // update swizzle / changed mip address if (swizzleChanged) { textureView->baseTexture->swizzle = swizzle; if ((viewFirstMip + viewNumMips) > 1) { textureView->baseTexture->physMipAddress = physMipAddr; } } debug_printf("Reload reason: Data-change when bound as texture (new hash 0x%08x)\n", textureView->baseTexture->texDataHash2); LatteTexture_ReloadData(textureView->baseTexture); } LatteTexture* baseTexture = textureView->baseTexture; if (baseTexture->reloadFromDynamicTextures) { LatteTexture_UpdateCacheFromDynamicTextures(baseTexture); baseTexture->reloadFromDynamicTextures = false; } LatteTC_MarkTextureStillInUse(baseTexture); // check if barrier is necessary if ((sint32)(LatteGPUState.drawCallCounter - baseTexture->lastUnflushedRTDrawcallIndex) < 2) { LatteGPUState.requiresTextureBarrier = true; baseTexture->lastUnflushedRTDrawcallIndex = 0; } // update scale float texScaleU, texScaleV; if (baseTexture->overwriteInfo.hasResolutionOverwrite == false) { texScaleU = 1.0f; texScaleV = 1.0f; } else { texScaleU = (float)baseTexture->overwriteInfo.width / (float)baseTexture->width; texScaleV = (float)baseTexture->overwriteInfo.height / (float)baseTexture->height; } LatteTexture_setEffectiveTextureScale(shaderContext->shaderType, textureIndex, texScaleU, texScaleV); } } // initialize textures used by the current drawcall // Sets LatteGPUState.repeatTextureInitialization to true if a new texture mapping was created (indicating that this function must be called again) // also sets LatteGPUState.requiresTextureBarrier to true if texture barrier is required void LatteTexture_updateTextures() { LatteGPUState.textureBindCounter++; // pixel shader LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); if (pixelShader) LatteTexture_updateTexturesForStage(pixelShader, LATTE_CEMU_PS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_PS); // vertex shader LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); cemu_assert_debug(vertexShader != nullptr); LatteTexture_updateTexturesForStage(vertexShader, LATTE_CEMU_VS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_VS); // geometry shader LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); if (geometryShader) LatteTexture_updateTexturesForStage(geometryShader, LATTE_CEMU_GS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_GS); } sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture) { if (texture->overwriteInfo.hasResolutionOverwrite) return texture->overwriteInfo.width; return texture->width; } // returns true if the two textures have the same rescale factor bool LatteTexture_doesEffectiveRescaleRatioMatch(LatteTexture* texture1, sint32 mipLevel1, LatteTexture* texture2, sint32 mipLevel2) { double widthRatio1 = (double)LatteTexture_getEffectiveWidth(texture1) / (double)texture1->width; double widthRatio2 = (double)LatteTexture_getEffectiveWidth(texture2) / (double)texture2->width; // the difference between the factors must be less than 5% double diff = widthRatio1 / widthRatio2; if (abs(1.0 - diff) > 0.05) { return false; } return true; } void LatteTexture_scaleToEffectiveSize(LatteTexture* texture, sint32* x, sint32* y, sint32 mipLevel) { if( texture->overwriteInfo.hasResolutionOverwrite == false ) return; *x = *x * std::max(1,texture->overwriteInfo.width>>mipLevel) / std::max(1,texture->width>>mipLevel); *y = *y * std::max(1,texture->overwriteInfo.height>>mipLevel) / std::max(1, texture->height>>mipLevel); } uint64 _textureUpdateEventCounter = 1; uint64 LatteTexture_getNextUpdateEventCounter() { uint64 counter = _textureUpdateEventCounter; _textureUpdateEventCounter++; return counter; } void LatteTexture_init() { } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" #include "config/ActiveSettings.h" #include "Cafe/CafeSystem.h" //#define BENCHMARK_TEXTURE_DECODING // if defined, time it takes to decode textures will be measured and logged to log.txt #ifdef BENCHMARK_TEXTURE_DECODING uint64 textureDecodeBenchmark_perFormatSum[0x40] = { 0 }; // duration sum per texture format (hw format) - in microseconds uint64 textureDecodeBenchmark_totalSum = 0; #endif void LatteTextureLoader_begin(LatteTextureLoaderCtx* textureLoader, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle) { textureLoader->physAddress = physImagePtr; textureLoader->physMipAddress = physMipPtr; textureLoader->sliceIndex = sliceIndex; cemu_assert_debug(mipLevels != 0); textureLoader->mipLevels = std::max<uint32>(1, mipLevels); textureLoader->tileMode = tileMode; textureLoader->bpp = Latte::GetFormatBits(format); textureLoader->stepX = 1; textureLoader->stepY = 1; if (Latte::IsCompressedFormat(format)) { textureLoader->stepX = 4; textureLoader->stepY = 4; } textureLoader->pipeSwizzle = (swizzle >> 8) & 1; textureLoader->bankSwizzle = ((swizzle >> 9) & 3); uint32 surfaceAA = 0; // todo if (mipIndex > 0 && Latte::TM_IsMacroTiled(tileMode)) { // separate swizzle from mip pointer if mip chain is not macro-tiled (and thus not swizzled) LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; LatteAddrLib::GX2CalculateSurfaceInfo(format, width, height, depth, dim, Latte::MakeGX2TileMode(tileMode), surfaceAA, 1, &surfaceInfo); if (Latte::TM_IsMacroTiled(surfaceInfo.hwTileMode)) { uint32 mipSwizzle = physMipPtr&0x700; physMipPtr &= ~0x700; textureLoader->physMipAddress = physMipPtr; textureLoader->pipeSwizzle = (mipSwizzle >> 8) & 1; textureLoader->bankSwizzle = ((mipSwizzle >> 9) & 3); } } // calculate surface info uint32 level = mipIndex; LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; LatteAddrLib::GX2CalculateSurfaceInfo(format, width, height, depth, dim, Latte::MakeGX2TileMode(tileMode), surfaceAA, level, &surfaceInfo); textureLoader->levelOffset = LatteAddrLib::CalculateMipOffset(format, width, height, depth, dim, (Latte::E_HWTILEMODE)tileMode, swizzle, surfaceAA, level); textureLoader->tileMode = surfaceInfo.hwTileMode; textureLoader->minOffsetOutdated = 0; textureLoader->maxOffsetOutdated = (sint32)surfaceInfo.surfSize; textureLoader->surfaceInfoHeight = surfaceInfo.height; textureLoader->surfaceInfoDepth = surfaceInfo.depth; // correct handling for LINEAR_ALIGNED pitch alignment is still not fully understood: //seems like sometimes there is a conditional pitch alignment to 0x40 OR there is no pitch alignment at all and we have a bug somewhere else uint64 titleId = CafeSystem::GetForegroundTitleId(); titleId &= ~0x300ULL; if (tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED && titleId == (0x000500301001200aULL)) { // examples of titles that use linear textures: // Minecraft - Uses sprite atlases with mips and linear tilemode. Expects padding of pitch for smaller mips to be 0x40 // Browser - Linear pitch must be used as-is, padding/alignment will break textures (uses a weird way to calculate pitch by using GX2CalcSurface on a texture with tileMode 0/4) // BotW - uses linear textures as render targets. With the smallest resolution being 3x3 with no pitch alignment expected at all (pitch = 3)? -> Not possible because both textures and rendertargets require a minimum alignment of 8 for pitch? surfaceInfo.pitch = std::max<uint32>(1, pitch >> mipIndex); } textureLoader->width = width >> (mipIndex); textureLoader->width = std::max(textureLoader->width, 1); textureLoader->height = height >> (mipIndex); textureLoader->height = std::max(textureLoader->height, 1); textureLoader->pitch = surfaceInfo.pitch; // calculate start address if (level == 0) textureLoader->inputData = (uint8*)memory_getPointerFromPhysicalOffset(physImagePtr); else textureLoader->inputData = (uint8*)memory_getPointerFromPhysicalOffset(physMipPtr) + textureLoader->levelOffset; SetupCachedSurfaceAddrInfo(&textureLoader->computeAddrInfo, textureLoader->sliceIndex, 0, textureLoader->bpp, textureLoader->pitch, surfaceInfo.height, depth, 1 * 1, textureLoader->tileMode, false, textureLoader->pipeSwizzle, textureLoader->bankSwizzle); } uint8* LatteTextureLoader_GetInput(LatteTextureLoaderCtx* textureLoader, sint32 x, sint32 y) { // calculate address of input tile uint32 offset = 0; if (textureLoader->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_GENERAL || textureLoader->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED) offset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x / textureLoader->stepX, y / textureLoader->stepY, textureLoader->sliceIndex, 0, textureLoader->bpp, textureLoader->pitch, textureLoader->surfaceInfoHeight, textureLoader->surfaceInfoDepth); else if (textureLoader->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1 || textureLoader->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THICK) offset = LatteAddrLib::ComputeSurfaceAddrFromCoordMicroTiled(x / textureLoader->stepX, y / textureLoader->stepY, textureLoader->sliceIndex, textureLoader->bpp, textureLoader->pitch, textureLoader->surfaceInfoHeight, (Latte::E_HWTILEMODE)textureLoader->tileMode, false); else offset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiledCached(x / textureLoader->stepX, y / textureLoader->stepY, &textureLoader->computeAddrInfo); uint8* blockData = textureLoader->inputData + offset; return blockData; } /* * Optimized version which assumes tileMode == 1 * Also does not do any min/max offset tracking */ uint8* LatteTextureLoader_getInputLinearOptimized(LatteTextureLoaderCtx* textureLoader, sint32 x, sint32 y) { // calculate address of input tile uint32 bitPos = 0; uint32 offset = 0; offset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x / textureLoader->stepX, y / textureLoader->stepY, textureLoader->sliceIndex, 0, textureLoader->bpp, textureLoader->pitch, textureLoader->surfaceInfoHeight, textureLoader->surfaceInfoDepth); return textureLoader->inputData + offset; } #define LatteTextureLoader_getInputLinearOptimized_(__textureLoader,__x,__y,__stepX,__stepY,__bpp,__sliceIndex,__numSlices,__sample,__pitch,__height) (textureLoader->inputData+((__x/__stepX) + __pitch * (__y/__stepY) + (__sliceIndex + __numSlices * __sample) * __height * __pitch)*(__bpp/8)) void decodeBC1Block(uint8* inputData, float* output4x4RGBA) { // read colors uint16 c0 = *(uint16*)(inputData + 0); uint16 c1 = *(uint16*)(inputData + 2); // decode colors (RGB565 -> RGB888) float r[4]; float g[4]; float b[4]; float a[4]; b[0] = (float)((c0 >> 0) & 0x1F) / 31.0f; b[1] = (float)((c1 >> 0) & 0x1F) / 31.0f; g[0] = (float)((c0 >> 5) & 0x3F) / 63.0f; g[1] = (float)((c1 >> 5) & 0x3F) / 63.0f; r[0] = (float)((c0 >> 11) & 0x1F) / 31.0f; r[1] = (float)((c1 >> 11) & 0x1F) / 31.0f; a[0] = 1.0f; a[1] = 1.0f; a[2] = 1.0f; if (c0 > c1) { r[2] = (r[0] * 2.0f + r[1]) / 3.0f; r[3] = (r[0] * 1.0f + r[1] * 2.0f) / 3.0f; g[2] = (g[0] * 2.0f + g[1]) / 3.0f; g[3] = (g[0] * 1.0f + g[1] * 2.0f) / 3.0f; b[2] = (b[0] * 2.0f + b[1]) / 3.0f; b[3] = (b[0] * 1.0f + b[1] * 2.0f) / 3.0f; a[3] = 1.0f; } else { r[2] = (r[0] + r[1]) / 2.0f; r[3] = 0.0f; g[2] = (g[0] + g[1]) / 2.0f; g[3] = 0.0f; b[2] = (b[0] + b[1]) / 2.0f; b[3] = 0.0f; a[3] = 0.0f; } uint8* indexData = inputData + 4; float* colorOutputRGBA = output4x4RGBA; for (sint32 row = 0; row < 4; row++) { uint8 i0 = ((*indexData) >> 0) & 3; uint8 i1 = ((*indexData) >> 2) & 3; uint8 i2 = ((*indexData) >> 4) & 3; uint8 i3 = ((*indexData) >> 6) & 3; colorOutputRGBA[0] = r[i0]; colorOutputRGBA[1] = g[i0]; colorOutputRGBA[2] = b[i0]; colorOutputRGBA[3] = a[i0]; colorOutputRGBA += 4; colorOutputRGBA[0] = r[i1]; colorOutputRGBA[1] = g[i1]; colorOutputRGBA[2] = b[i1]; colorOutputRGBA[3] = a[i1]; colorOutputRGBA += 4; colorOutputRGBA[0] = r[i2]; colorOutputRGBA[1] = g[i2]; colorOutputRGBA[2] = b[i2]; colorOutputRGBA[3] = a[i2]; colorOutputRGBA += 4; colorOutputRGBA[0] = r[i3]; colorOutputRGBA[1] = g[i3]; colorOutputRGBA[2] = b[i3]; colorOutputRGBA[3] = a[i3]; colorOutputRGBA += 4; indexData++; } } void decodeBC2Block_UNORM(uint8* inputData, float* imageRGBA) { uint32 color0 = *(uint16*)(inputData + 8); uint32 color1 = *(uint16*)(inputData + 10); uint32 colorIndices = *(uint32*)(inputData + 12); uint8 r0 = (color0 >> 11) & 0x1F; uint8 g0 = (color0 >> 5) & 0x3F; uint8 b0 = (color0 >> 0) & 0x1F; uint8 r1 = (color1 >> 11) & 0x1F; uint8 g1 = (color1 >> 5) & 0x3F; uint8 b1 = (color1 >> 0) & 0x1F; float r[4]; float g[4]; float b[4]; r[0] = (float)r0 / 31.0f; r[1] = (float)r1 / 31.0f; r[2] = (r[0] * 2.0f + r[1]) / 3.0f; r[3] = (r[0] + r[1] * 2.0f) / 3.0f; g[0] = (float)g0 / 63.0f; g[1] = (float)g1 / 63.0f; g[2] = (g[0] * 2.0f + g[1]) / 3.0f; g[3] = (g[0] + g[1] * 2.0f) / 3.0f; b[0] = (float)b0 / 31.0f; b[1] = (float)b1 / 31.0f; b[2] = (b[0] * 2.0f + b[1]) / 3.0f; b[3] = (b[0] + b[1] * 2.0f) / 3.0f; for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { uint8 colorIndex = (colorIndices >> (2 * (px + 4 * py))) & 0x03; sint32 pixelOffset = (px + py * 4) * 4; imageRGBA[pixelOffset + 0] = r[colorIndex]; imageRGBA[pixelOffset + 1] = g[colorIndex]; imageRGBA[pixelOffset + 2] = b[colorIndex]; } } // decode alpha uint8* alphaData = (uint8*)(inputData + 0); for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { uint32 alphaIndex = (px + py * 4); uint8 alphaCode = (alphaData[alphaIndex / 2] >> ((alphaIndex & 1) * 4)) & 0xF; alphaCode |= (alphaCode << 4); sint32 pixelOffset = (px + py * 4) * 4; imageRGBA[pixelOffset + 3] = (float)alphaCode / 255.0f; // alpha } } } void decodeBC3Block_UNORM(uint8* inputData, float* imageRGBA) { uint32 color0 = *(uint16*)(inputData + 8); uint32 color1 = *(uint16*)(inputData + 10); uint32 colorIndices = *(uint32*)(inputData + 12); uint8 r0 = (color0 >> 11) & 0x1F; uint8 g0 = (color0 >> 5) & 0x3F; uint8 b0 = (color0 >> 0) & 0x1F; uint8 r1 = (color1 >> 11) & 0x1F; uint8 g1 = (color1 >> 5) & 0x3F; uint8 b1 = (color1 >> 0) & 0x1F; float r[4]; float g[4]; float b[4]; r[0] = (float)r0 / 31.0f; r[1] = (float)r1 / 31.0f; r[2] = (r[0] * 2.0f + r[1]) / 3.0f; r[3] = (r[0] + r[1] * 2.0f) / 3.0f; g[0] = (float)g0 / 63.0f; g[1] = (float)g1 / 63.0f; g[2] = (g[0] * 2.0f + g[1]) / 3.0f; g[3] = (g[0] + g[1] * 2.0f) / 3.0f; b[0] = (float)b0 / 31.0f; b[1] = (float)b1 / 31.0f; b[2] = (b[0] * 2.0f + b[1]) / 3.0f; b[3] = (b[0] + b[1] * 2.0f) / 3.0f; for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { uint8 colorIndex = (colorIndices >> (2 * (px + 4 * py))) & 0x03; sint32 pixelOffset = (px + py * 4) * 4; imageRGBA[pixelOffset + 0] = r[colorIndex]; imageRGBA[pixelOffset + 1] = g[colorIndex]; imageRGBA[pixelOffset + 2] = b[colorIndex]; //imageRGBA[pixelOffset+3] = 1.0f; // alpha } } // decode alpha uint8 alpha0 = *(uint8*)(inputData + 0); uint8 alpha1 = *(uint8*)(inputData + 1); uint32 alphaCodeRow[2] = { 0 }; alphaCodeRow[0] |= ((*(uint8*)(inputData + 2)) << 0); alphaCodeRow[0] |= ((*(uint8*)(inputData + 3)) << 8); alphaCodeRow[0] |= ((*(uint8*)(inputData + 4)) << 16); alphaCodeRow[1] |= ((*(uint8*)(inputData + 5)) << 0); alphaCodeRow[1] |= ((*(uint8*)(inputData + 6)) << 8); alphaCodeRow[1] |= ((*(uint8*)(inputData + 7)) << 16); float a[8]; a[0] = (float)alpha0 / 255.0f; a[1] = (float)alpha1 / 255.0f; if (alpha0 > alpha1) { // 6 interpolated alpha values. a[2] = (a[0] * 6.0f + a[1] * 1.0f) / 7.0f; a[3] = (a[0] * 5.0f + a[1] * 2.0f) / 7.0f; a[4] = (a[0] * 4.0f + a[1] * 3.0f) / 7.0f; a[5] = (a[0] * 3.0f + a[1] * 4.0f) / 7.0f; a[6] = (a[0] * 2.0f + a[1] * 5.0f) / 7.0f; a[7] = (a[0] * 1.0f + a[1] * 6.0f) / 7.0f; } else { // 4 interpolated alpha values. a[2] = (a[0] * 4.0f + a[1] * 1.0f) / 5.0f; a[3] = (a[0] * 3.0f + a[1] * 2.0f) / 5.0f; a[4] = (a[0] * 2.0f + a[1] * 3.0f) / 5.0f; a[5] = (a[0] * 1.0f + a[1] * 4.0f) / 5.0f; a[6] = 0.0f; a[7] = 1.0f; } for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { uint8 alphaCode = (alphaCodeRow[py / 2] >> 3 * (px + 4 * (py & 1))) & 0x07; sint32 pixelOffset = (px + py * 4) * 4; imageRGBA[pixelOffset + 3] = a[alphaCode]; // alpha } } } void decodeBC4Block_UNORM(uint8* blockStorage, float* rOutput) { uint8* blockInput = (uint8*)blockStorage; float red[8]; red[0] = ((float)(*(uint8*)(blockInput + 0))) / 255.0f; red[1] = ((float)(*(uint8*)(blockInput + 1))) / 255.0f; if (blockInput[0] > blockInput[1]) { // 6 interpolated color values red[2] = (6 * red[0] + 1 * red[1]) / 7.0f; // bit code 010 red[3] = (5 * red[0] + 2 * red[1]) / 7.0f; // bit code 011 red[4] = (4 * red[0] + 3 * red[1]) / 7.0f; // bit code 100 red[5] = (3 * red[0] + 4 * red[1]) / 7.0f; // bit code 101 red[6] = (2 * red[0] + 5 * red[1]) / 7.0f; // bit code 110 red[7] = (1 * red[0] + 6 * red[1]) / 7.0f; // bit code 111 } else { // 4 interpolated color values red[2] = (4 * red[0] + 1 * red[1]) / 5.0f; // bit code 010 red[3] = (3 * red[0] + 2 * red[1]) / 5.0f; // bit code 011 red[4] = (2 * red[0] + 3 * red[1]) / 5.0f; // bit code 100 red[5] = (1 * red[0] + 4 * red[1]) / 5.0f; // bit code 101 red[6] = 0.0f; // bit code 110 red[7] = 1.0f; // bit code 111 } uint8* bitIndices = blockInput + 2; uint32 redRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); uint32 redRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); uint8 pRed[16]; for (sint32 i = 0; i < 8; i++) { pRed[i] = (redRow0 >> (i * 3)) & 7; pRed[i + 8] = (redRow1 >> (i * 3)) & 7; } float* pixelOutput = rOutput; for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { float c = red[pRed[px + py * 4]]; *pixelOutput = c; pixelOutput++; } } } void decodeBC5Block_UNORM(uint8* blockStorage, float* rgOutput) { uint8* blockInput = (uint8*)blockStorage; float red[8]; float green[8]; red[0] = ((float)(*(uint8*)(blockInput + 0))) / 255.0f; red[1] = ((float)(*(uint8*)(blockInput + 1))) / 255.0f; if (red[0] > red[1]) { // 6 interpolated color values red[2] = (6 * red[0] + 1 * red[1]) / 7.0f; // bit code 010 red[3] = (5 * red[0] + 2 * red[1]) / 7.0f; // bit code 011 red[4] = (4 * red[0] + 3 * red[1]) / 7.0f; // bit code 100 red[5] = (3 * red[0] + 4 * red[1]) / 7.0f; // bit code 101 red[6] = (2 * red[0] + 5 * red[1]) / 7.0f; // bit code 110 red[7] = (1 * red[0] + 6 * red[1]) / 7.0f; // bit code 111 } else { // 4 interpolated color values red[2] = (4 * red[0] + 1 * red[1]) / 5.0f; // bit code 010 red[3] = (3 * red[0] + 2 * red[1]) / 5.0f; // bit code 011 red[4] = (2 * red[0] + 3 * red[1]) / 5.0f; // bit code 100 red[5] = (1 * red[0] + 4 * red[1]) / 5.0f; // bit code 101 red[6] = 0.0f; // bit code 110 red[7] = 1.0f; // bit code 111 } green[0] = ((float)(*(uint8*)(blockInput + 8))) / 255.0f; green[1] = ((float)(*(uint8*)(blockInput + 9))) / 255.0f; if (green[0] > green[1]) { // 6 interpolated color values green[2] = (6 * green[0] + 1 * green[1]) / 7.0f; // bit code 010 green[3] = (5 * green[0] + 2 * green[1]) / 7.0f; // bit code 011 green[4] = (4 * green[0] + 3 * green[1]) / 7.0f; // bit code 100 green[5] = (3 * green[0] + 4 * green[1]) / 7.0f; // bit code 101 green[6] = (2 * green[0] + 5 * green[1]) / 7.0f; // bit code 110 green[7] = (1 * green[0] + 6 * green[1]) / 7.0f; // bit code 111 } else { // 4 interpolated color values green[2] = (4 * green[0] + 1 * green[1]) / 5.0f; // bit code 010 green[3] = (3 * green[0] + 2 * green[1]) / 5.0f; // bit code 011 green[4] = (2 * green[0] + 3 * green[1]) / 5.0f; // bit code 100 green[5] = (1 * green[0] + 4 * green[1]) / 5.0f; // bit code 101 green[6] = 0.0f; // bit code 110 green[7] = 1.0f; // bit code 111 } uint8* bitIndices = blockInput + 2; uint32 redRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); uint32 redRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); bitIndices = blockInput + 8 + 2; uint32 greenRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); uint32 greenRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); uint8 pRed[16]; uint8 pGreen[16]; for (sint32 i = 0; i < 8; i++) { pRed[i] = (redRow0 >> (i * 3)) & 7; pRed[i + 8] = (redRow1 >> (i * 3)) & 7; pGreen[i] = (greenRow0 >> (i * 3)) & 7; pGreen[i + 8] = (greenRow1 >> (i * 3)) & 7; } float* pixelOutput = rgOutput; for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { float c = red[pRed[px + py * 4]]; *pixelOutput = c; pixelOutput++; c = green[pGreen[px + py * 4]]; *pixelOutput = c; pixelOutput++; } } } void decodeBC5Block_SNORM(uint8* blockStorage, float* rgOutput) // todo - can merge this with the UNORM implementation by using a template? { uint8* blockInput = (uint8*)blockStorage; float red[8]; float green[8]; red[0] = ((float)(*(sint8*)(blockInput + 0)) + 128.0f) / 255.0f; red[1] = ((float)(*(sint8*)(blockInput + 1)) + 128.0f) / 255.0f; red[0] = (red[0] * 2.0f - 1.0f); red[1] = (red[1] * 2.0f - 1.0f); if (red[0] > red[1]) { // 6 interpolated color values red[2] = (6 * red[0] + 1 * red[1]) / 7.0f; // bit code 010 red[3] = (5 * red[0] + 2 * red[1]) / 7.0f; // bit code 011 red[4] = (4 * red[0] + 3 * red[1]) / 7.0f; // bit code 100 red[5] = (3 * red[0] + 4 * red[1]) / 7.0f; // bit code 101 red[6] = (2 * red[0] + 5 * red[1]) / 7.0f; // bit code 110 red[7] = (1 * red[0] + 6 * red[1]) / 7.0f; // bit code 111 } else { // 4 interpolated color values red[2] = (4 * red[0] + 1 * red[1]) / 5.0f; // bit code 010 red[3] = (3 * red[0] + 2 * red[1]) / 5.0f; // bit code 011 red[4] = (2 * red[0] + 3 * red[1]) / 5.0f; // bit code 100 red[5] = (1 * red[0] + 4 * red[1]) / 5.0f; // bit code 101 red[6] = -1.0f; // bit code 110 red[7] = 1.0f; // bit code 111 } green[0] = ((float)(*(sint8*)(blockInput + 8)) + 128.0f) / 255.0f; green[1] = ((float)(*(sint8*)(blockInput + 9)) + 128.0f) / 255.0f; green[0] = (green[0] * 2.0f - 1.0f); green[1] = (green[1] * 2.0f - 1.0f); if (green[0] > green[1]) { // 6 interpolated color values green[2] = (6 * green[0] + 1 * green[1]) / 7.0f; // bit code 010 green[3] = (5 * green[0] + 2 * green[1]) / 7.0f; // bit code 011 green[4] = (4 * green[0] + 3 * green[1]) / 7.0f; // bit code 100 green[5] = (3 * green[0] + 4 * green[1]) / 7.0f; // bit code 101 green[6] = (2 * green[0] + 5 * green[1]) / 7.0f; // bit code 110 green[7] = (1 * green[0] + 6 * green[1]) / 7.0f; // bit code 111 } else { // 4 interpolated color values green[2] = (4 * green[0] + 1 * green[1]) / 5.0f; // bit code 010 green[3] = (3 * green[0] + 2 * green[1]) / 5.0f; // bit code 011 green[4] = (2 * green[0] + 3 * green[1]) / 5.0f; // bit code 100 green[5] = (1 * green[0] + 4 * green[1]) / 5.0f; // bit code 101 green[6] = -1.0f; // bit code 110 green[7] = 1.0f; // bit code 111 } uint8* bitIndices = blockInput + 2; uint32 redRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); uint32 redRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); bitIndices = blockInput + 8 + 2; uint32 greenRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); uint32 greenRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); uint8 pRed[16]; uint8 pGreen[16]; for (sint32 i = 0; i < 8; i++) { pRed[i] = (redRow0 >> (i * 3)) & 7; pRed[i + 8] = (redRow1 >> (i * 3)) & 7; pGreen[i] = (greenRow0 >> (i * 3)) & 7; pGreen[i + 8] = (greenRow1 >> (i * 3)) & 7; } for (sint32 py = 0; py < 4; py++) { float* pixelOutput = rgOutput + (py * 4) * 2; for (sint32 px = 0; px < 4; px++) { float c = red[pRed[px + py * 4]]; pixelOutput[0] = c; c = green[pGreen[px + py * 4]]; pixelOutput[1] = c; pixelOutput += 2; } } } void LatteTextureLoader_loadTextureDataIntoSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, sint32 mipLevels, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) { if (mipIndex == 0) { cemu_assert_debug(width == hostTexture->width); cemu_assert_debug(height == hostTexture->height); cemu_assert_debug(depth == hostTexture->depth); } cemu_assert_debug(mipLevels == hostTexture->mipLevels); if (hostTexture->overwriteInfo.hasResolutionOverwrite || hostTexture->overwriteInfo.hasFormatOverwrite) { // todo - ideally, we should scale/convert the data to the new format and resolution g_renderer->texture_clearSlice(hostTexture, sliceIndex, mipIndex); } else { g_renderer->texture_loadSlice(hostTexture, width, height, depth, pixelData, sliceIndex, mipIndex, compressedImageSize); } } void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, bool dumpTex) { LatteTextureLoaderCtx textureLoader = { 0 }; Latte::E_GX2SURFFMT format = tex->format; LatteTextureLoader_begin(&textureLoader, sliceIndex, mipIndex, physImagePtr, physMipPtr, format, dim, width, height, depth, mipLevels, pitch, tileMode, swizzle); // enable texture dumping textureLoader.dump = ActiveSettings::DumpTexturesEnabled(); if (textureLoader.dump) { uint32 dumpSize = (((textureLoader.width + 4)&~4) * ((textureLoader.height + 4)&~4)) * 4; textureLoader.dumpRGBA = (uint8*)malloc(dumpSize); memset(textureLoader.dumpRGBA, 0x00, dumpSize); } // query texture decoder from renderer TextureDecoder* texDecoder = nullptr; texDecoder = g_renderer->texture_chooseDecodedFormat(format, tex->isDepth, dim, width, height); if (tex->isDataDefined == false) { tex->AllocateOnHost(); tex->isDataDefined = true; // if decoder is not set then clear texture // on Vulkan this is used to make sure the texture is no longer in UNDEFINED layout if (!texDecoder) { if(tex->isDepth) g_renderer->texture_clearDepthSlice(tex, 0, 0, true, tex->hasStencil, 0.0f, 0); else g_renderer->texture_clearColorSlice(tex, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f); } } if (texDecoder == nullptr) return; textureLoader.decodedTexelCountX = texDecoder->getTexelCountX(&textureLoader); textureLoader.decodedTexelCountY = texDecoder->getTexelCountY(&textureLoader); // allocate memory for decoded texture uint32 imageSize = texDecoder->calculateImageSize(&textureLoader); uint8* pixelData = (uint8*)g_renderer->texture_acquireTextureUploadBuffer(imageSize); // decode texture (if data is required) #ifdef BENCHMARK_TEXTURE_DECODING LARGE_INTEGER benchmark_begin; LARGE_INTEGER benchmark_end; LARGE_INTEGER benchmark_freq; QueryPerformanceCounter(&benchmark_begin); #endif if (tex->overwriteInfo.hasFormatOverwrite == false && tex->overwriteInfo.hasResolutionOverwrite == false) { texDecoder->decode(&textureLoader, pixelData); } #ifdef BENCHMARK_TEXTURE_DECODING QueryPerformanceCounter(&benchmark_end); QueryPerformanceFrequency(&benchmark_freq); uint64 benchmarkResultMicroSeconds = (benchmark_end.QuadPart - benchmark_begin.QuadPart) * 1000000ULL / benchmark_freq.QuadPart; textureDecodeBenchmark_perFormatSum[(int)tex->format & 0x3F] += benchmarkResultMicroSeconds; textureDecodeBenchmark_totalSum += benchmarkResultMicroSeconds; cemuLog_log(LogType::Force, "TexDecode {:04}x{:04}x{:04} Fmt {:04x} Dim {} TileMode {:02x} Took {:03}.{:03}ms Sum(format) {:06}ms Sum(total) {:06}ms", textureLoader.width, textureLoader.height, textureLoader.surfaceInfoDepth, (int)tex->format, (int)tex->dim, textureLoader.tileMode, (uint32)(benchmarkResultMicroSeconds / 1000ULL), (uint32)(benchmarkResultMicroSeconds % 1000ULL), (uint32)(textureDecodeBenchmark_perFormatSum[tex->gx2Format & 0x3F] / 1000ULL), (uint32)(textureDecodeBenchmark_totalSum / 1000ULL)); #endif // convert texture to RGBA when dumping is enabled if (textureLoader.dump) { for (sint32 y = 0; y < textureLoader.height; y++) { sint32 pixelOffset = (y * textureLoader.width) * 4; uint8* pixelOutput = textureLoader.dumpRGBA + pixelOffset; for (sint32 x = 0; x < textureLoader.width; x++) { uint8* blockData = LatteTextureLoader_GetInput(&textureLoader, x, y); texDecoder->decodePixelToRGBA(blockData, pixelOutput, x % textureLoader.stepX, y % textureLoader.stepY); pixelOutput += 4; } } } // update texture data offsets and hashes // this has to be done before the texture data is decoded & uploaded to prevent a race condition where updates during upload are missed if (mipIndex == 0 || (tex->texDataPtrLow == 0 && tex->texDataPtrHigh == 0)) { tex->texDataPtrLow = physImagePtr + textureLoader.minOffsetOutdated; // always zero tex->texDataPtrHigh = physImagePtr + textureLoader.maxOffsetOutdated; // currently set to surface size LatteTC_ResetTextureChangeTracker(tex, true); } // load slice //debug_printf("[Load Slice] Addr: %08x MIP: %02d Slice: %02d Res %04x/%04x Texel Res %04x/%04x Fmt %04x Tm %d\n", textureLoader.physAddress, mipIndex, sliceIndex, textureLoader.width, textureLoader.height, textureLoader.texelCountX, textureLoader.texelCountY, (int)format, tileMode); LatteTextureLoader_loadTextureDataIntoSlice(tex, textureLoader.width, textureLoader.height, depth, mipLevels, pixelData, sliceIndex, mipIndex, imageSize); // write texture dump if (textureLoader.dump) { fs::path path = ActiveSettings::GetUserDataPath("dump/textures"); path /= fmt::format("{:08x}_fmt{:04x}_slice{:d}_mip{:02d}_{:d}x{:d}_tm{:02d}.tga", physImagePtr, (uint32)tex->format, sliceIndex, mipIndex, tex->width, tex->height, tileMode); tga_write_rgba(path, textureLoader.width, textureLoader.height, textureLoader.dumpRGBA); free(textureLoader.dumpRGBA); } // clean up g_renderer->texture_releaseTextureUploadBuffer(pixelData); catchOpenGLError(); } template<typename copyType> void optimizedLinearReadbackWriteLoop(LatteTextureLoaderCtx* textureLoader, uint8* linearPixelData) { uint32 pitch = textureLoader->width; // optimized for linear for (sint32 y = 0; y < textureLoader->height; y++) { sint32 yc = y; sint32 pixelOffset = yc * pitch; copyType* rowPixelData = (copyType*)(linearPixelData + pixelOffset * sizeof(copyType)); copyType* blockData = (copyType*)LatteTextureLoader_getInputLinearOptimized_(textureLoader, 0, y, 1, 1, sizeof(copyType) * 8, 0, 1, 0, textureLoader->pitch, textureLoader->height); if constexpr (sizeof(copyType) == 4) { memcpy_dwords(blockData, rowPixelData, textureLoader->width); } else { for (sint32 x = 0; x < textureLoader->width; x++) { *blockData = *rowPixelData; rowPixelData++; blockData++; } } } } void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData) { LatteTextureLoaderCtx textureLoader = { 0 }; LatteTextureLoader_begin(&textureLoader, sliceIndex, mipIndex, textureData->physAddress, textureData->physMipAddress, textureData->format, textureData->dim, textureData->width, textureData->height, textureData->depth, textureData->mipLevels, textureData->pitch, textureData->tileMode, textureData->swizzle); #ifdef CEMU_DEBUG_ASSERT if (textureData->depth != 1) cemuLog_log(LogType::Force, "_writeReadbackTextureToMemory(): Texture has multiple slices (not supported)"); #endif if (textureLoader.physAddress == MPTR_NULL) { cemuLog_log(LogType::Force, "_writeReadbackTextureToMemory(): Texture has invalid address"); return; } cemuLog_log(LogType::TextureReadback, "[TextureReadback-Write] PhysAddr {:08x} Res {}x{} Fmt {} Slice {} Mip {}", textureData->physAddress, textureData->width, textureData->height, textureData->format, sliceIndex, mipIndex); if (textureData->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED) { uint32 pitch = textureLoader.width; if (textureData->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || textureData->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) { optimizedLinearReadbackWriteLoop<uint32>(&textureLoader, linearPixelData); } else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) { optimizedLinearReadbackWriteLoop<uint64>(&textureLoader, linearPixelData); } else if (textureData->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) { for (sint32 y = 0; y < textureLoader.height; y += textureLoader.stepY) { sint32 yc = y; sint32 pixelOffset = (0 + yc * pitch) * 16; for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX) { uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y); (*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0); (*(uint32*)(blockData + 4)) = *(uint32*)(linearPixelData + pixelOffset + 4); (*(uint32*)(blockData + 8)) = *(uint32*)(linearPixelData + pixelOffset + 8); (*(uint32*)(blockData + 12)) = *(uint32*)(linearPixelData + pixelOffset + 12); pixelOffset += 16; } } } else if (textureData->format == Latte::E_GX2SURFFMT::R32_FLOAT) { for (sint32 y = 0; y < textureLoader.height; y += textureLoader.stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX) { uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y); sint32 pixelOffset = (x + yc * pitch) * 4; (*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0); } } } else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) { for (sint32 y = 0; y < textureLoader.height; y += textureLoader.stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX) { uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y); sint32 pixelOffset = (x + yc * pitch) * 8; (*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0); (*(uint32*)(blockData + 4)) = *(uint32*)(linearPixelData + pixelOffset + 4); } } } else if (textureData->format == Latte::E_GX2SURFFMT::R8_G8_UNORM) { optimizedLinearReadbackWriteLoop<uint16>(&textureLoader, linearPixelData); } else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) { cemu_assert_unimplemented(); } else if (textureData->format == Latte::E_GX2SURFFMT::R16_UNORM) { optimizedLinearReadbackWriteLoop<uint16>(&textureLoader, linearPixelData); } else { cemuLog_logDebug(LogType::Force, "Linear texture readback unsupported for format 0x{:04x}", (uint32)textureData->format); debugBreakpoint(); } return; } // generic and slow decode loops Latte::E_HWSURFFMT hwFormat = Latte::GetHWFormat(textureData->format); if (hwFormat == Latte::E_HWSURFFMT::HWFMT_8_8_8_8) { // used in Bayonetta 2 for (sint32 y = 0; y < textureLoader.height; y++) { uint8* pixelInput = linearPixelData + (y * textureLoader.width) * 4; for (sint32 x = 0; x < textureLoader.width; x++) { uint8* outputData = LatteTextureLoader_GetInput(&textureLoader, x, y); *(uint32*)(outputData + 0) = *(uint32*)pixelInput; pixelInput += 4; } } } else if (hwFormat == Latte::E_HWSURFFMT::HWFMT_32_FLOAT) { // required by Wind Waker for direct access to depth buffer // Bayonetta 2 also uses this but it converts the depth buffer to a color texture first for (sint32 y = 0; y < textureLoader.height; y++) { uint8* pixelInput = linearPixelData + (y * textureLoader.width) * 4; for (sint32 x = 0; x < textureLoader.width; x++) { uint8* outputData = LatteTextureLoader_GetInput(&textureLoader, x, y); *(uint32*)(outputData + 0) = *(uint32*)pixelInput; pixelInput += 4; } } } else { cemuLog_logDebug(LogType::Force, "Texture readback unsupported format {:04x} for tileMode 0x{:02x}", (uint32)textureData->format, textureData->tileMode); } } void LatteTextureLoader_estimateAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, uint32& addrStart, uint32& addrEnd) { LatteTextureLoaderCtx textureLoader = { 0 }; LatteTextureLoader_begin(&textureLoader, sliceIndex, mipIndex, texture->physAddress, texture->physMipAddress, texture->format, texture->dim, texture->width, texture->height, texture->depth, texture->mipLevels, texture->pitch, texture->tileMode, texture->swizzle); cemu_assert_debug(textureLoader.width > 0); cemu_assert_debug(textureLoader.height > 0); // estimate data range by checking addresses of corner pixels // this isn't very reliable, find a better solution uint32 estimatedMinAddr = 0xFFFFFFFF; uint32 estimatedMaxAddr = 0x00000000; uint32 tempAddr; tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, 0, 0)); estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, textureLoader.width - 1, 0)); estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, 0, textureLoader.height - 1)); estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, textureLoader.width - 1, textureLoader.height - 1)); estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); addrStart = estimatedMinAddr; addrEnd = estimatedMaxAddr; } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTextureLoader.h ================================================ #pragma once #include "util/helpers/ClassWrapper.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" #include "util/ImageWriter/tga.h" struct LatteTextureLoaderCtx { uint32 physAddress; uint32 physMipAddress; sint32 width; sint32 height; sint32 pitch; // stored elements per row uint32 mipLevels; uint32 sliceIndex; sint32 stepX; sint32 stepY; uint32 pipeSwizzle; uint32 bankSwizzle; Latte::E_HWTILEMODE tileMode; uint32 bpp; uint8* inputData; sint32 minOffsetOutdated; sint32 maxOffsetOutdated; // calculated info uint32 surfaceInfoHeight; uint32 surfaceInfoDepth; // mip uint32 levelOffset; // relative to physMipAddress // info for decoded texture sint32 decodedTexelCountX; sint32 decodedTexelCountY; // decoder LatteAddrLib::CachedSurfaceAddrInfo computeAddrInfo; // debug dump texture bool dump; uint8* dumpRGBA; }; uint8* LatteTextureLoader_GetInput(LatteTextureLoaderCtx* textureLoader, sint32 x, sint32 y); #include "Cafe/HW/Latte/LatteAddrLib/AddrLibFastDecode.h" void decodeBC1Block(uint8* inputData, float* output4x4RGBA); void decodeBC2Block_UNORM(uint8* inputData, float* imageRGBA); void decodeBC3Block_UNORM(uint8* inputData, float* imageRGBA); void decodeBC4Block_UNORM(uint8* blockStorage, float* rOutput); void decodeBC5Block_UNORM(uint8* blockStorage, float* rgOutput); void decodeBC5Block_SNORM(uint8* blockStorage, float* rgOutput); inline void BC1_GetPixel(uint8* inputData, sint32 x, sint32 y, uint8 rgba[4]) { // read colors uint16 c0 = *(uint16*)(inputData + 0); uint16 c1 = *(uint16*)(inputData + 2); // decode colors (RGB565 -> RGB888) float r[4]; float g[4]; float b[4]; float a[4]; b[0] = (float)((c0 >> 0) & 0x1F) / 31.0f; b[1] = (float)((c1 >> 0) & 0x1F) / 31.0f; g[0] = (float)((c0 >> 5) & 0x3F) / 63.0f; g[1] = (float)((c1 >> 5) & 0x3F) / 63.0f; r[0] = (float)((c0 >> 11) & 0x1F) / 31.0f; r[1] = (float)((c1 >> 11) & 0x1F) / 31.0f; a[0] = 1.0f; a[1] = 1.0f; a[2] = 1.0f; if (c0 > c1) { r[2] = (r[0] * 2.0f + r[1]) / 3.0f; r[3] = (r[0] * 1.0f + r[1] * 2.0f) / 3.0f; g[2] = (g[0] * 2.0f + g[1]) / 3.0f; g[3] = (g[0] * 1.0f + g[1] * 2.0f) / 3.0f; b[2] = (b[0] * 2.0f + b[1]) / 3.0f; b[3] = (b[0] * 1.0f + b[1] * 2.0f) / 3.0f; a[3] = 1.0f; } else { r[2] = (r[0] + r[1]) / 2.0f; r[3] = 0.0f; g[2] = (g[0] + g[1]) / 2.0f; g[3] = 0.0f; b[2] = (b[0] + b[1]) / 2.0f; b[3] = 0.0f; a[3] = 0.0f; } uint8* indexData = inputData + 4; uint8 i = (indexData[y] >> (x * 2)) & 3; rgba[0] = (uint8)(r[i] * 255.0f); rgba[1] = (uint8)(g[i] * 255.0f); rgba[2] = (uint8)(b[i] * 255.0f); rgba[3] = (uint8)(a[i] * 255.0f); } // decodes a specific GPU7 texture format into a native linear format that can be used by the render API class TextureDecoder { public: // properties virtual sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) = 0; virtual sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) { return textureLoader->width; } virtual sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) { return textureLoader->height; } // image size virtual sint32 calculateImageSize(LatteTextureLoaderCtx* textureLoader) { return getTexelCountX(textureLoader) * getTexelCountY(textureLoader) * getBytesPerTexel(textureLoader); } // decode loop virtual void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) = 0; virtual void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) = 0; }; class TextureDecoder_R16_G16_B16_A16_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R16_G16_B16_A16_FLOAT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { // todo: dump 16 bit float formats properly float r16f = (float)(*((uint16*)blockData + 0)); float g16f = (float)(*((uint16*)blockData + 1)); float b16f = (float)(*((uint16*)blockData + 2)); float a16f = (float)(*((uint16*)blockData + 3)); *(outputPixel + 0) = (uint8)(r16f * 255.0f); *(outputPixel + 1) = (uint8)(g16f * 255.0f); *(outputPixel + 2) = (uint8)(b16f * 255.0f); *(outputPixel + 3) = (uint8)(a16f * 255.0f); } }; class TextureDecoder_R16_G16_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R16_G16_FLOAT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2 * 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { // todo: dump 16 bit float formats properly float r16f = (float)(*((uint16*)blockData + 0)); float g16f = (float)(*((uint16*)blockData + 1)); *(outputPixel + 0) = (uint8)(r16f * 255.0f); *(outputPixel + 1) = (uint8)(g16f * 255.0f); *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R16_SNORM : public TextureDecoder, public SingletonClass<TextureDecoder_R16_SNORM> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 1 * 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8) / 2 + 128; *(outputPixel + 1) = 0; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R16_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R16_FLOAT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 1 * 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { // todo: dump 16 bit float formats properly float r16f = (float)(*((uint16*)blockData + 0)); *(outputPixel + 0) = (uint8)(r16f * 255.0f); *(outputPixel + 1) = 0; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R32_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_FLOAT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 1 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float r32f = *((float*)blockData + 0); *(outputPixel + 0) = (uint8)(r32f * 255.0f); *(outputPixel + 1) = 0; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R32_G32_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_G32_FLOAT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float r32f = *((float*)blockData + 0); float g32f = *((float*)blockData + 1); *(outputPixel + 0) = (uint8)(r32f * 255.0f); *(outputPixel + 1) = (uint8)(g32f * 255.0f); *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R32_G32_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_G32_UINT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = (uint8)(*(((uint32*)blockData) + 0) >> 24); *(outputPixel + 1) = (uint8)(*(((uint32*)blockData) + 1) >> 24); *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R32_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_UINT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 1 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = (uint8)(*(((uint32*)blockData) + 0) >> 24); *(outputPixel + 1) = 0; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R16_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R16_UINT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 1 * 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8); *(outputPixel + 1) = 0; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R8_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R8_UINT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 1; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint8, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = *(blockData + 0); *(outputPixel + 1) = 0; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R32_G32_B32_A32_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_G32_B32_A32_FLOAT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 2, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float r32f = *((float*)blockData + 0); float g32f = *((float*)blockData + 1); float b32f = *((float*)blockData + 2); float a32f = *((float*)blockData + 3); *(outputPixel + 0) = (uint8)(r32f * 255.0f); *(outputPixel + 1) = (uint8)(g32f * 255.0f); *(outputPixel + 2) = (uint8)(b32f * 255.0f); *(outputPixel + 3) = (uint8)(a32f * 255.0f); } }; class TextureDecoder_R32_G32_B32_A32_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_G32_B32_A32_UINT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { // note - before 1.15.4 this format was implemented as big-endian //optimizedDecodeLoops<uint64, 2, false>(textureLoader, outputData); for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 4 * sizeof(uint32); // write to target buffer *(uint32*)(outputData + pixelOffset + 0) = _swapEndianU32(*(uint32*)(blockData + 0)); *(uint32*)(outputData + pixelOffset + 4) = _swapEndianU32(*(uint32*)(blockData + 4)); *(uint32*)(outputData + pixelOffset + 8) = _swapEndianU32(*(uint32*)(blockData + 8)); *(uint32*)(outputData + pixelOffset + 12) = _swapEndianU32(*(uint32*)(blockData + 12)); // todo: Verify if this format is big-endian } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = (uint8)(*(((uint32*)blockData) + 0) >> 24); *(outputPixel + 1) = (uint8)(*(((uint32*)blockData) + 1) >> 24); *(outputPixel + 2) = (uint8)(*(((uint32*)blockData) + 2) >> 24); *(outputPixel + 3) = (uint8)(*(((uint32*)blockData) + 3) >> 24); } }; class TextureDecoder_R16_G16_B16_A16_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R16_G16_B16_A16_UINT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { // note - before 1.15.4 this format was implemented as big-endian //optimizedDecodeLoops<uint64, 1, false>(textureLoader, outputData); for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 4 * sizeof(uint16); // write to target buffer *(uint16*)(outputData + pixelOffset + 0) = _swapEndianU16(*(uint16*)(blockData + 0)); *(uint16*)(outputData + pixelOffset + 2) = _swapEndianU16(*(uint16*)(blockData + 2)); *(uint16*)(outputData + pixelOffset + 4) = _swapEndianU16(*(uint16*)(blockData + 4)); *(uint16*)(outputData + pixelOffset + 6) = _swapEndianU16(*(uint16*)(blockData + 6)); // todo: Verify if this format is big-endian } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8); *(outputPixel + 1) = (uint8)(*(((uint16*)blockData) + 1) >> 8); *(outputPixel + 2) = (uint8)(*(((uint16*)blockData) + 2) >> 8); *(outputPixel + 3) = (uint8)(*(((uint16*)blockData) + 3) >> 8); } }; class TextureDecoder_R8_G8_B8_A8_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R8_G8_B8_A8_UINT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 1; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = *(blockData + 0); *(outputPixel + 1) = *(blockData + 1); *(outputPixel + 2) = *(blockData + 2); *(outputPixel + 3) = *(blockData + 3); } }; class TextureDecoder_R24_X8 : public TextureDecoder, public SingletonClass<TextureDecoder_R24_X8> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 1 * sizeof(uint32); *(uint32*)(outputData + pixelOffset + 0) = 0; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { } }; class TextureDecoder_X24_G8_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_X24_G8_UINT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 1 * sizeof(uint32); *(uint32*)(outputData + pixelOffset + 0) = 0; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { } }; class TextureDecoder_D32_S8_UINT_X24 : public TextureDecoder, public SingletonClass<TextureDecoder_D32_S8_UINT_X24> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 8; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { } }; class TextureDecoder_R4_G4_UNORM_To_RGBA4 : public TextureDecoder, public SingletonClass<TextureDecoder_R4_G4_UNORM_To_RGBA4> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 2; uint8 v = (*(uint8*)(blockData + 0)); *(uint8*)(outputData + pixelOffset + 0) = 0; // OpenGL has no RG4 format so we use RGBA4 instead and these two values (blue + alpha) are always set to zero *(uint8*)(outputData + pixelOffset + 1) = ((v >> 4) & 0xF) | ((v << 4) & 0xF0); // todo: Is this nibble swap correct? } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint8 v0 = *(blockData + 0); uint8 c0 = (v0 & 0xF); uint8 c1 = (v0 >> 4) & 0xF; c0 = (c0 << 4) | c0; c1 = (c1 << 4) | c1; *(outputPixel + 0) = c0; *(outputPixel + 1) = c1; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R4_G4_UNORM_To_ABGR4 : public TextureDecoder, public SingletonClass<TextureDecoder_R4_G4_UNORM_To_ABGR4> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 2; uint8 v = (*(uint8*)(blockData + 0)); *(uint8*)(outputData + pixelOffset + 0) = 0; *(uint8*)(outputData + pixelOffset + 1) = v; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint8 v0 = *(blockData + 0); uint8 c0 = (v0 & 0xF); uint8 c1 = (v0 >> 4) & 0xF; c0 = (c0 << 4) | c0; c1 = (c1 << 4) | c1; *(outputPixel + 0) = c0; *(outputPixel + 1) = c1; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R4G4_UNORM_To_RGBA8 : public TextureDecoder, public SingletonClass<TextureDecoder_R4G4_UNORM_To_RGBA8> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 4; uint8 v0 = (*(uint8*)(blockData + 0)); uint8 red4 = (v0 >> 4) & 0xF; uint8 green4 = (v0 & 0xF); red4 = (red4 << 4) | red4; green4 = (green4 << 4) | green4; *(uint8*)(outputData + pixelOffset + 0) = red4; *(uint8*)(outputData + pixelOffset + 1) = green4; *(uint8*)(outputData + pixelOffset + 2) = 0; *(uint8*)(outputData + pixelOffset + 3) = 255; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint8 v0 = *(blockData + 0); uint8 red4 = (v0 >> 4) & 0xF; uint8 green4 = (v0 & 0xF); red4 = (red4 << 4) | red4; green4 = (green4 << 4) | green4; *(outputPixel + 0) = red4; *(outputPixel + 1) = green4; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R4G4_UNORM_To_RG8 : public TextureDecoder, public SingletonClass<TextureDecoder_R4G4_UNORM_To_RG8> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 2; uint8 v0 = (*(uint8*)(blockData + 0)); uint8 red4 = (v0 >> 4) & 0xF; uint8 green4 = (v0 & 0xF); red4 = (red4 << 4) | red4; green4 = (green4 << 4) | green4; *(uint8*)(outputData + pixelOffset + 0) = red4; *(uint8*)(outputData + pixelOffset + 1) = green4; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint8 v0 = *(blockData + 0); uint8 red4 = (v0 >> 4) & 0xF; uint8 green4 = (v0 & 0xF); red4 = (red4 << 4) | red4; green4 = (green4 << 4) | green4; *(outputPixel + 0) = red4; *(outputPixel + 1) = green4; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R4_G4_B4_A4_UNORM : public TextureDecoder, public SingletonClass<TextureDecoder_R4_G4_B4_A4_UNORM> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 2; uint8 v0 = (*(uint8*)(blockData + 0)); uint8 v1 = (*(uint8*)(blockData + 1)); *(uint8*)(outputData + pixelOffset + 0) = ((v1 >> 4) & 0xF) | ((v1 << 4) & 0xF0); // todo: Verify *(uint8*)(outputData + pixelOffset + 1) = ((v0 >> 4) & 0xF) | ((v0 << 4) & 0xF0); // todo: Verify } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint8 v0 = *(blockData + 0); uint8 v1 = *(blockData + 1); uint8 c0 = (v0 & 0xF); uint8 c1 = (v0 >> 4) & 0xF; uint8 c2 = (v1 & 0xF); uint8 c3 = (v1 >> 4) & 0xF; c0 = (c0 << 4) | c0; c1 = (c1 << 4) | c1; c2 = (c2 << 4) | c2; c3 = (c3 << 4) | c3; *(outputPixel + 0) = c0; *(outputPixel + 1) = c1; *(outputPixel + 2) = c2; *(outputPixel + 3) = c3; } }; class TextureDecoder_R4G4B4A4_UNORM_To_RGBA8 : public TextureDecoder, public SingletonClass<TextureDecoder_R4G4B4A4_UNORM_To_RGBA8> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 4; uint8 v0 = (*(uint8*)(blockData + 0)); uint8 v1 = (*(uint8*)(blockData + 1)); uint8 red4 = (v0 & 0xF); uint8 green4 = (v0 >> 4) & 0xF; uint8 blue4 = (v1) & 0xF; uint8 alpha4 = (v1 >> 4) & 0xF; red4 = (red4 << 4) | red4; green4 = (green4 << 4) | green4; blue4 = (blue4 << 4) | blue4; alpha4 = (alpha4 << 4) | alpha4; *(uint8*)(outputData + pixelOffset + 0) = red4; *(uint8*)(outputData + pixelOffset + 1) = green4; *(uint8*)(outputData + pixelOffset + 2) = blue4; *(uint8*)(outputData + pixelOffset + 3) = alpha4; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint8 v0 = *(blockData + 0); uint8 v1 = *(blockData + 1); uint8 red4 = (v0 & 0xF); uint8 green4 = (v0 >> 4) & 0xF; uint8 blue4 = (v1) & 0xF; uint8 alpha4 = (v1 >> 4) & 0xF; red4 = (red4 << 4) | red4; green4 = (green4 << 4) | green4; blue4 = (blue4 << 4) | blue4; alpha4 = (alpha4 << 4) | alpha4; *(outputPixel + 0) = red4; *(outputPixel + 1) = green4; *(outputPixel + 2) = blue4; *(outputPixel + 3) = alpha4; } }; class TextureDecoder_R8_G8_B8_A8 : public TextureDecoder, public SingletonClass<TextureDecoder_R8_G8_B8_A8> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = *(blockData + 0); *(outputPixel + 1) = *(blockData + 1); *(outputPixel + 2) = *(blockData + 2); *(outputPixel + 3) = *(blockData + 3); } }; class TextureDecoder_D24_S8 : public TextureDecoder, public SingletonClass<TextureDecoder_D24_S8> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint32 d24 = (*(uint32*)blockData) & 0xFFFFFF; uint8 s8 = (*(uint32*)blockData >> 24) & 0xFF; *(outputPixel + 0) = (uint8)(d24 >> 16); *(outputPixel + 1) = (uint8)(d24 >> 16); *(outputPixel + 2) = (uint8)(d24 >> 16); *(outputPixel + 3) = s8; } }; class TextureDecoder_NullData32 : public TextureDecoder, public SingletonClass<TextureDecoder_NullData32> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { memset(outputData, 0, sizeof(uint32) * getTexelCountX(textureLoader) * getTexelCountY(textureLoader)); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { } }; class TextureDecoder_NullData64 : public TextureDecoder, public SingletonClass<TextureDecoder_NullData64> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 8; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { memset(outputData, 0, sizeof(uint64) * getTexelCountX(textureLoader) * getTexelCountY(textureLoader)); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { } }; class TextureDecoder_R8 : public TextureDecoder, public SingletonClass<TextureDecoder_R8> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 1; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint8, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = *(blockData + 0); *(outputPixel + 1) = 0; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R8_G8 : public TextureDecoder, public SingletonClass<TextureDecoder_R8_G8> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = *(blockData + 0); *(outputPixel + 1) = *(blockData + 1); *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R4_G4 : public TextureDecoder, public SingletonClass<TextureDecoder_R4_G4> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 1; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint8, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint8 v0 = (*(uint8*)(blockData + 0)); uint8 c0 = (v0 & 0xF); uint8 c1 = (v0 >> 4) & 0xF; c0 = (c0 << 4) | c0; c1 = (c1 << 4) | c1; *(outputPixel + 0) = c0; *(outputPixel + 1) = c1; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R16_UNORM : public TextureDecoder, public SingletonClass<TextureDecoder_R16_UNORM> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8); *(outputPixel + 1) = 0; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R16_G16_B16_A16 : public TextureDecoder, public SingletonClass<TextureDecoder_R16_G16_B16_A16> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8); *(outputPixel + 1) = (uint8)(*(((uint16*)blockData) + 1) >> 8); *(outputPixel + 2) = (uint8)(*(((uint16*)blockData) + 2) >> 8); *(outputPixel + 3) = (uint8)(*(((uint16*)blockData) + 3) >> 8); } }; class TextureDecoder_R16_G16 : public TextureDecoder, public SingletonClass<TextureDecoder_R16_G16> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2 * 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8); *(outputPixel + 1) = (uint8)(*(((uint16*)blockData) + 1) >> 8); *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_R5_G6_B5 : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G6_B5> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 colorData = (*(uint16*)blockData); uint8 red5 = (colorData >> 0) & 0x1F; uint8 green6 = (colorData >> 5) & 0x3F; uint8 blue5 = (colorData >> 11) & 0x1F; *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); *(outputPixel + 1) = (green6 << 2) | (green6 >> 4); *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); *(outputPixel + 3) = 255; } }; class TextureDecoder_R5_G6_B5_swappedRB : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G6_B5_swappedRB> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 2; uint16 colorData = (*(uint16*)(blockData + 0)); // colorData >> 0 -> red // colorData >> 5 -> green // colorData >> 11 -> blue colorData = ((colorData >> 11) & 0x1F) | (colorData & (0x3F << 5)) | ((colorData << 11) & (0x1F << 11)); //// swap order of components ////uint8 red5 = (colorData>>0)&0x1F; ////uint8 green5 = (colorData>>5)&0x1F; ////uint8 blue5 = (colorData>>10)&0x1F; ////uint8 alpha1 = (colorData>>15)&0x1; ////colorData = blue5|(green5<<5)|(red5<<10)|(alpha1<<15); ////*(uint16*)(outputData+pixelOffset+0) = colorData; //uint8 red5 = (colorData >> 11) & 0x1F; //uint8 green5 = (colorData >> 6) & 0x1F; //uint8 blue5 = (colorData >> 1) & 0x1F; //uint8 alpha1 = (colorData >> 0) & 0x1; ////colorData = blue5|(green5<<5)|(red5<<10)|(alpha1<<15); ////colorData = (blue5<<11)|(green5<<6)|(red5<<1)|(alpha1<<0); * (uint16*)(outputData + pixelOffset + 0) = colorData; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 colorData = (*(uint16*)blockData); uint8 red5 = (colorData >> 0) & 0x1F; uint8 green6 = (colorData >> 5) & 0x3F; uint8 blue5 = (colorData >> 11) & 0x1F; *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); *(outputPixel + 1) = (green6 << 2) | (green6 >> 4); *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); *(outputPixel + 3) = 255; } }; class TextureDecoder_R5G6B5_UNORM_To_RGBA8 : public TextureDecoder, public SingletonClass<TextureDecoder_R5G6B5_UNORM_To_RGBA8> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 4; uint16 v0 = (*(uint16*)(blockData + 0)); uint8 c0 = (v0 & 0x1F); uint8 c1 = (v0 >> 5) & 0x3F; uint8 c2 = (v0 >> 11) & 0x1F; c0 = (c0 << 3) | c0 >> 3;// blue c1 = (c1 << 2) | c1 >> 4;// green c2 = (c2 << 3) | c2 >> 3;// red *(uint8*)(outputData + pixelOffset + 0) = c0;// blue *(uint8*)(outputData + pixelOffset + 1) = c1;// green *(uint8*)(outputData + pixelOffset + 2) = c2;// red *(uint8*)(outputData + pixelOffset + 3) = 255;//alpha } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 v0 = *(uint16*)(blockData + 0); uint8 c0 = (v0 & 0x1F);// red uint8 c1 = (v0 >> 5) & 0x3F;// green uint8 c2 = (v0 >> 11) & 0x1F; // blue c0 = (c0 << 3) | c0 >> 3; c1 = (c1 << 2) | c1 >> 4; c2 = (c2 << 3) | c2 >> 3; *(outputPixel + 0) = c0;// red *(outputPixel + 1) = c1;// green *(outputPixel + 2) = c2;// blue *(outputPixel + 3) = 255;// alpha } }; class TextureDecoder_R5_G5_B5_A1_UNORM : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G5_B5_A1_UNORM> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 colorData = (*(uint16*)blockData); uint8 red5 = (colorData >> 0) & 0x1F; uint8 green5 = (colorData >> 5) & 0x1F; uint8 blue5 = (colorData >> 10) & 0x1F; uint8 alpha1 = (colorData >> 11) & 0x1; *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); *(outputPixel + 3) = (alpha1 << 3); } }; class uint16_R5_G5_B5_A1_swapRB { public: void operator=(const uint16_R5_G5_B5_A1_swapRB& v) { internalVal = ((v.internalVal >> 10) & (0x1F << 0)) | ((v.internalVal << 10) & (0x1F << 10)) | (v.internalVal & ((0x1F << 5) | 0x8000)); } uint16 internalVal; }; static_assert(sizeof(uint16_R5_G5_B5_A1_swapRB) == 2); class TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint16_R5_G5_B5_A1_swapRB, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 colorData = (*(uint16*)blockData); uint8 red5 = (colorData >> 0) & 0x1F; uint8 green5 = (colorData >> 5) & 0x1F; uint8 blue5 = (colorData >> 10) & 0x1F; uint8 alpha1 = (colorData >> 11) & 0x1; *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); *(outputPixel + 3) = (alpha1 << 3); } }; class TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB_To_RGBA8 : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB_To_RGBA8> { public: //2656 sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint16* blockData = (uint16*)LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 4; uint32 colorData = (*(uint16*)(blockData + 0)); // swap order of components uint8 red = (colorData >> 0) & 0x1F; uint8 green = (colorData >> 5) & 0x1F; uint8 blue = (colorData >> 10) & 0x1F; uint8 alpha = (colorData >> 15) & 0x1; red = red << 3 | red >> 2; green = green << 3 | green >> 2; blue = blue << 3 | blue >> 2; alpha = alpha * 0xff; // MSB...LSB : ABGR colorData = (alpha << 24) | (blue << 16) | (green << 8) | red; *(uint32*)(outputData + pixelOffset + 0) = colorData; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 colorData = (*(uint16*)blockData); uint8 red = (colorData >> 0) & 0x1F; uint8 green = (colorData >> 5) & 0x1F; uint8 blue = (colorData >> 10) & 0x1F; uint8 alpha = (colorData >> 15) & 0x1; *(outputPixel + 0) = (red << 3) | (red >> 2); *(outputPixel + 1) = (green << 3) | (green >> 2); *(outputPixel + 2) = (blue << 3) | (blue >> 2); *(outputPixel + 3) = alpha * 0xff; } }; class uint16_R5_G5_B5_A1_swapOpenGL { public: void operator=(const uint16_R5_G5_B5_A1_swapOpenGL& v) { uint16 red = (v.internalVal >> 0) & 0x1F; uint16 green = (v.internalVal >> 5) & 0x1F; uint16 blue = (v.internalVal >> 10) & 0x1F; uint16 alpha = (v.internalVal >> 15) & 0x1; internalVal = (red << 11) | (green << 6) | (blue << 1) | alpha; } uint16 internalVal; }; static_assert(sizeof(uint16_R5_G5_B5_A1_swapOpenGL) == 2); class TextureDecoder_R5_G5_B5_A1_UNORM_swappedOpenGL : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G5_B5_A1_UNORM_swappedOpenGL> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint16_R5_G5_B5_A1_swapOpenGL, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 colorData = (*(uint16*)blockData); uint8 red5 = (colorData >> 0) & 0x1F; uint8 green5 = (colorData >> 5) & 0x1F; uint8 blue5 = (colorData >> 10) & 0x1F; uint8 alpha1 = (colorData >> 11) & 0x1; *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); *(outputPixel + 3) = (alpha1 << 3); } }; class TextureDecoder_A1_B5_G5_R5_UNORM : public TextureDecoder, public SingletonClass<TextureDecoder_A1_B5_G5_R5_UNORM> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 colorData = (*(uint16*)blockData); uint8 red5 = (colorData >> 11) & 0x1F; uint8 green5 = (colorData >> 6) & 0x1F; uint8 blue5 = (colorData >> 1) & 0x1F; uint8 alpha1 = (colorData >> 0) & 0x1; *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); *(outputPixel + 3) = (alpha1 << 3); } }; class TextureDecoder_A1_B5_G5_R5_UNORM_vulkan : public TextureDecoder, public SingletonClass<TextureDecoder_A1_B5_G5_R5_UNORM_vulkan> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 2; uint16 colorData = (*(uint16*)(blockData + 0)); // swap order of components uint8 red5 = (colorData >> 11) & 0x1F; uint8 green5 = (colorData >> 6) & 0x1F; uint8 blue5 = (colorData >> 1) & 0x1F; uint8 alpha1 = (colorData >> 0) & 0x1; colorData = blue5 | (green5 << 5) | (red5 << 10) | (alpha1 << 15); *(uint16*)(outputData + pixelOffset + 0) = colorData; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 colorData = (*(uint16*)blockData); uint8 red5 = (colorData >> 11) & 0x1F; uint8 green5 = (colorData >> 6) & 0x1F; uint8 blue5 = (colorData >> 1) & 0x1F; uint8 alpha1 = (colorData >> 0) & 0x1; *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); *(outputPixel + 3) = (alpha1 << 3); } }; class TextureDecoder_A1_B5_G5_R5_UNORM_vulkan_To_RGBA8 : public TextureDecoder, public SingletonClass<TextureDecoder_A1_B5_G5_R5_UNORM_vulkan_To_RGBA8> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint16* blockData = (uint16*)LatteTextureLoader_GetInput(textureLoader, x, y); sint32 pixelOffset = (x + yc * textureLoader->width) * 4; uint32 colorData = (*(uint16*)(blockData + 0)); // swap order of components uint8 red = (colorData >> 11) & 0x1F; uint8 green = (colorData >> 6) & 0x1F; uint8 blue = (colorData >> 1) & 0x1F; uint8 alpha = (colorData >> 0) & 0x1; red = red << 3 | red >> 2; green = green << 3 | green >> 2; blue = blue << 3 | blue >> 2; alpha = alpha * 0xff; // MSB...LSB ABGR colorData = red | (green << 8) | (blue << 16) | (alpha << 24); *(uint32*)(outputData + pixelOffset + 0) = colorData; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 colorData = (*(uint16*)blockData); uint8 red5 = (colorData >> 11) & 0x1F; uint8 green5 = (colorData >> 6) & 0x1F; uint8 blue5 = (colorData >> 1) & 0x1F; uint8 alpha1 = (colorData >> 0) & 0x1; *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); *(outputPixel + 3) = (alpha1 << 3); } }; class TextureDecoder_R10_G10_B10_A2_UNORM : public TextureDecoder, public SingletonClass<TextureDecoder_R10_G10_B10_A2_UNORM> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 r10 = ((*(uint32*)blockData) >> 0) & 0x3FF; uint16 g10 = ((*(uint32*)blockData) >> 10) & 0x3FF; uint16 b10 = ((*(uint32*)blockData) >> 20) & 0x3FF; uint8 a2 = ((*(uint32*)blockData) >> 30) & 0x3; *(outputPixel + 0) = (uint8)(r10 >> 6); *(outputPixel + 1) = (uint8)(g10 >> 6); *(outputPixel + 2) = (uint8)(b10 >> 6); *(outputPixel + 3) = a2; } }; class TextureDecoder_R10_G10_B10_A2_SNORM_To_RGBA16 : public TextureDecoder, public SingletonClass<TextureDecoder_R10_G10_B10_A2_SNORM_To_RGBA16> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { // todo - implement for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; sint32 pixelOffset = (yc * textureLoader->width) * (2 * 4); sint16* pixelOutput = (sint16*)(outputData + pixelOffset); for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); uint32 v = (*(uint32*)(blockData + 0)); *pixelOutput = 0; pixelOffset++; *pixelOutput = 0; pixelOffset++; *pixelOutput = 0; pixelOffset++; *pixelOutput = 0; pixelOffset++; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint16 r10 = ((*(uint32*)blockData) >> 0) & 0x3FF; uint16 g10 = ((*(uint32*)blockData) >> 10) & 0x3FF; uint16 b10 = ((*(uint32*)blockData) >> 20) & 0x3FF; uint8 a2 = ((*(uint32*)blockData) >> 30) & 0x3; *(outputPixel + 0) = (uint8)(r10 >> 6) / 2 + 128; *(outputPixel + 1) = (uint8)(g10 >> 6) / 2 + 128; *(outputPixel + 2) = (uint8)(b10 >> 6) / 2 + 128; *(outputPixel + 3) = a2 / 2 + 128; } }; class TextureDecoder_A2_B10_G10_R10_UNORM_To_RGBA16 : public TextureDecoder, public SingletonClass<TextureDecoder_A2_B10_G10_R10_UNORM_To_RGBA16> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 2; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { // todo - implement for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 yc = y; sint32 pixelOffset = (yc * textureLoader->width) * (2 * 4); sint16* pixelOutput = (sint16*)(outputData + pixelOffset); for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); uint32 v = (*(uint32*)(blockData + 0)); *pixelOutput = 0; pixelOffset++; *pixelOutput = 0; pixelOffset++; *pixelOutput = 0; pixelOffset++; *pixelOutput = 0; pixelOffset++; } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { uint8 a2 = ((*(uint32*)blockData) >> 0) & 0x3; uint16 r10 = ((*(uint32*)blockData) >> 2) & 0x3FF; uint16 g10 = ((*(uint32*)blockData) >> 12) & 0x3FF; uint16 b10 = ((*(uint32*)blockData) >> 22) & 0x3FF; *(outputPixel + 0) = (uint8)(r10 >> 6); *(outputPixel + 1) = (uint8)(g10 >> 6); *(outputPixel + 2) = (uint8)(b10 >> 6); *(outputPixel + 3) = a2; } }; class TextureDecoder_R11_G11_B10_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R11_G11_B10_FLOAT> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { // todo: add dumping support } }; class TextureDecoder_BC1_UNORM_uncompress_generic : public TextureDecoder { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 blockSizeX = (std::min)(4, textureLoader->width - x); sint32 blockSizeY = (std::min)(4, textureLoader->height - y); // decode 4x4 pixels at once float rgbaBlock[4 * 4 * 4]; decodeBC1Block(blockData, rgbaBlock); for (sint32 py = 0; py < blockSizeY; py++) { sint32 yc = y + py; for (sint32 px = 0; px < blockSizeX; px++) { sint32 pixelOffset = (x + px + yc * textureLoader->width) * 16; // write to target buffer float red = rgbaBlock[(px + py * 4) * 4 + 0]; float green = rgbaBlock[(px + py * 4) * 4 + 1]; float blue = rgbaBlock[(px + py * 4) * 4 + 2]; float alpha = rgbaBlock[(px + py * 4) * 4 + 3]; *(float*)(outputData + pixelOffset + 0) = red; *(float*)(outputData + pixelOffset + 4) = green; *(float*)(outputData + pixelOffset + 8) = blue; *(float*)(outputData + pixelOffset + 12) = alpha; } } } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { BC1_GetPixel(blockData, blockOffsetX, blockOffsetY, outputPixel); } }; class TextureDecoder_BC1_UNORM_uncompress : public TextureDecoder_BC1_UNORM_uncompress_generic, public SingletonClass<TextureDecoder_BC1_UNORM_uncompress> { // reuse TextureDecoder_BC1_UNORM_uncompress_generic }; class TextureDecoder_BC1_SRGB_uncompress : public TextureDecoder_BC1_UNORM_uncompress_generic, public SingletonClass<TextureDecoder_BC1_SRGB_uncompress> { // reuse TextureDecoder_BC1_UNORM_uncompress_generic }; class TextureDecoder_BC1 : public TextureDecoder, public SingletonClass<TextureDecoder_BC1> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 8; } sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) override { return (textureLoader->width + 3) / 4; } sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) override { return (textureLoader->height + 3) / 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 1, false, true>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { BC1_GetPixel(blockData, blockOffsetX, blockOffsetY, outputPixel); } }; class TextureDecoder_BC2 : public TextureDecoder, public SingletonClass<TextureDecoder_BC2> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 4; } sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) override { return (textureLoader->width + 3) / 4; } sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) override { return (textureLoader->height + 3) / 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 2, false, true>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float rgbaBlock[4 * 4 * 4]; decodeBC2Block_UNORM(blockData, rgbaBlock); float red = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 0]; float green = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 1]; float blue = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 2]; float alpha = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 3]; *(outputPixel + 0) = (uint8)(red * 255.0f); *(outputPixel + 1) = (uint8)(green * 255.0f); *(outputPixel + 2) = (uint8)(blue * 255.0f); *(outputPixel + 3) = (uint8)(alpha * 255.0f); } }; class TextureDecoder_BC2_UNORM_uncompress : public TextureDecoder, public SingletonClass<TextureDecoder_BC2_UNORM_uncompress> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 blockSizeX = (std::min)(4, textureLoader->width - x); sint32 blockSizeY = (std::min)(4, textureLoader->height - y); // decode 4x4 pixels at once float rgbaBlock[4 * 4 * 4]; decodeBC2Block_UNORM(blockData, rgbaBlock); for (sint32 py = 0; py < blockSizeY; py++) { sint32 yc = y + py; for (sint32 px = 0; px < blockSizeX; px++) { sint32 pixelOffset = (x + px + yc * textureLoader->width) * 16; // write to target buffer float red = rgbaBlock[(px + py * 4) * 4 + 0]; float green = rgbaBlock[(px + py * 4) * 4 + 1]; float blue = rgbaBlock[(px + py * 4) * 4 + 2]; float alpha = rgbaBlock[(px + py * 4) * 4 + 3]; *(float*)(outputData + pixelOffset + 0) = red; *(float*)(outputData + pixelOffset + 4) = green; *(float*)(outputData + pixelOffset + 8) = blue; *(float*)(outputData + pixelOffset + 12) = alpha; } } } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float rgbaBlock[4 * 4 * 4]; decodeBC2Block_UNORM(blockData, rgbaBlock); float red = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 0]; float green = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 1]; float blue = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 2]; float alpha = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 3]; *(outputPixel + 0) = (uint8)(red * 255.0f); *(outputPixel + 1) = (uint8)(green * 255.0f); *(outputPixel + 2) = (uint8)(blue * 255.0f); *(outputPixel + 3) = (uint8)(alpha * 255.0f); } }; class TextureDecoder_BC2_SRGB_uncompress : public TextureDecoder, public SingletonClass<TextureDecoder_BC2_SRGB_uncompress> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { // todo - apply srgb conversion for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 blockSizeX = (std::min)(4, textureLoader->width - x); sint32 blockSizeY = (std::min)(4, textureLoader->height - y); // decode 4x4 pixels at once float rgbaBlock[4 * 4 * 4]; decodeBC2Block_UNORM(blockData, rgbaBlock); for (sint32 py = 0; py < blockSizeY; py++) { sint32 yc = y + py; for (sint32 px = 0; px < blockSizeX; px++) { sint32 pixelOffset = (x + px + yc * textureLoader->width) * 16; // write to target buffer float red = rgbaBlock[(px + py * 4) * 4 + 0]; float green = rgbaBlock[(px + py * 4) * 4 + 1]; float blue = rgbaBlock[(px + py * 4) * 4 + 2]; float alpha = rgbaBlock[(px + py * 4) * 4 + 3]; *(float*)(outputData + pixelOffset + 0) = red; *(float*)(outputData + pixelOffset + 4) = green; *(float*)(outputData + pixelOffset + 8) = blue; *(float*)(outputData + pixelOffset + 12) = alpha; } } } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { // todo - apply srgb conversion float rgbaBlock[4 * 4 * 4]; decodeBC2Block_UNORM(blockData, rgbaBlock); float red = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 0]; float green = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 1]; float blue = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 2]; float alpha = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 3]; *(outputPixel + 0) = (uint8)(red * 255.0f); *(outputPixel + 1) = (uint8)(green * 255.0f); *(outputPixel + 2) = (uint8)(blue * 255.0f); *(outputPixel + 3) = (uint8)(alpha * 255.0f); } }; class TextureDecoder_BC3_uncompress_generic : public TextureDecoder { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 4 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 blockSizeX = (std::min)(4, textureLoader->width - x); sint32 blockSizeY = (std::min)(4, textureLoader->height - y); // decode 4x4 pixels at once float rgbaBlock[4 * 4 * 4]; decodeBC3Block_UNORM(blockData, rgbaBlock); for (sint32 py = 0; py < blockSizeY; py++) { sint32 yc = y + py; for (sint32 px = 0; px < blockSizeX; px++) { sint32 pixelOffset = (x + px + yc * textureLoader->width) * 16; // write to target buffer float red = rgbaBlock[(px + py * 4) * 4 + 0]; float green = rgbaBlock[(px + py * 4) * 4 + 1]; float blue = rgbaBlock[(px + py * 4) * 4 + 2]; float alpha = rgbaBlock[(px + py * 4) * 4 + 3]; *(float*)(outputData + pixelOffset + 0) = red; *(float*)(outputData + pixelOffset + 4) = green; *(float*)(outputData + pixelOffset + 8) = blue; *(float*)(outputData + pixelOffset + 12) = alpha; } } } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float rgbaBlock[4 * 4 * 4]; decodeBC3Block_UNORM(blockData, rgbaBlock); float red = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 0]; float green = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 1]; float blue = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 2]; float alpha = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 3]; *(outputPixel + 0) = (uint8)(red * 255.0f); *(outputPixel + 1) = (uint8)(green * 255.0f); *(outputPixel + 2) = (uint8)(blue * 255.0f); *(outputPixel + 3) = (uint8)(alpha * 255.0f); } }; class TextureDecoder_BC3_UNORM_uncompress : public TextureDecoder_BC3_uncompress_generic, public SingletonClass<TextureDecoder_BC3_UNORM_uncompress> { // reuse TextureDecoder_BC3_uncompress_generic }; class TextureDecoder_BC3_SRGB_uncompress : public TextureDecoder_BC3_uncompress_generic, public SingletonClass<TextureDecoder_BC3_SRGB_uncompress> { // reuse TextureDecoder_BC3_uncompress_generic }; class TextureDecoder_BC3 : public TextureDecoder, public SingletonClass<TextureDecoder_BC3> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 16; } sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) override { return (textureLoader->width + 3) / 4; } sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) override { return (textureLoader->height + 3) / 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 2, false, true>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float rgbaBlock[4 * 4 * 4]; decodeBC3Block_UNORM(blockData, rgbaBlock); float red = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 0]; float green = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 1]; float blue = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 2]; float alpha = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 3]; *(outputPixel + 0) = (uint8)(red * 255.0f); *(outputPixel + 1) = (uint8)(green * 255.0f); *(outputPixel + 2) = (uint8)(blue * 255.0f); *(outputPixel + 3) = (uint8)(alpha * 255.0f); } }; class TextureDecoder_BC4_UNORM_uncompress : public TextureDecoder, public SingletonClass<TextureDecoder_BC4_UNORM_uncompress> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 blockSizeX = (std::min)(4, textureLoader->width - x); sint32 blockSizeY = (std::min)(4, textureLoader->height - y); // decode 4x4 pixels at once float rBlock[4 * 4 * 1]; decodeBC4Block_UNORM(blockData, rBlock); for (sint32 py = 0; py < blockSizeY; py++) { sint32 yc = y + py; for (sint32 px = 0; px < blockSizeX; px++) { sint32 pixelOffset = (x + px + yc * textureLoader->width) * 8; // write to target buffer float red = rBlock[(px + py * 4) * 1 + 0]; *(float*)(outputData + pixelOffset + 0) = red; *(float*)(outputData + pixelOffset + 4) = 0.0f; } } } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float rBlock[4 * 4 * 1]; decodeBC4Block_UNORM(blockData, rBlock); float red = rBlock[(blockOffsetX + blockOffsetY * 4) * 1 + 0]; *(outputPixel + 0) = (uint8)(red * 255.0f); *(outputPixel + 1) = 0; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_BC4 : public TextureDecoder, public SingletonClass<TextureDecoder_BC4> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 8; } sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) override { return (textureLoader->width + 3) / 4; } sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) override { return (textureLoader->height + 3) / 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 1, false, true>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float rBlock[4 * 4 * 1]; decodeBC4Block_UNORM(blockData, rBlock); float red = rBlock[(blockOffsetX + blockOffsetY * 4) * 1 + 0]; *(outputPixel + 0) = (uint8)(red * 255.0f); *(outputPixel + 1) = 0; *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_BC5_UNORM_uncompress : public TextureDecoder, public SingletonClass<TextureDecoder_BC5_UNORM_uncompress> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 blockSizeX = (std::min)(4, textureLoader->width - x); sint32 blockSizeY = (std::min)(4, textureLoader->height - y); // decode 4x4 pixels at once float rgBlock[4 * 4 * 2]; decodeBC5Block_UNORM(blockData, rgBlock); for (sint32 py = 0; py < blockSizeY; py++) { sint32 yc = y + py; for (sint32 px = 0; px < blockSizeX; px++) { sint32 pixelOffset = (x + px + yc * textureLoader->width) * 8; // write to target buffer float red = rgBlock[(px + py * 4) * 2 + 0]; float green = rgBlock[(px + py * 4) * 2 + 1]; *(float*)(outputData + pixelOffset + 0) = red; *(float*)(outputData + pixelOffset + 4) = green; } } } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float rgBlock[4 * 4 * 2]; decodeBC5Block_UNORM(blockData, rgBlock); float red = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 0]; float green = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 1]; *(outputPixel + 0) = (uint8)(red * 255.0f); *(outputPixel + 1) = (uint8)(green * 255.0f); *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_BC5_SNORM_uncompress : public TextureDecoder, public SingletonClass<TextureDecoder_BC5_SNORM_uncompress> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 2 * 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); sint32 blockSizeX = (std::min)(4, textureLoader->width - x); sint32 blockSizeY = (std::min)(4, textureLoader->height - y); // decode 4x4 pixels at once float rgBlock[4 * 4 * 2]; decodeBC5Block_SNORM(blockData, rgBlock); for (sint32 py = 0; py < blockSizeY; py++) { sint32 yc = y + py; for (sint32 px = 0; px < blockSizeX; px++) { sint32 pixelOffset = (x + px + yc * textureLoader->width) * 8; // write to target buffer float red = rgBlock[(px + py * 4) * 2 + 0]; float green = rgBlock[(px + py * 4) * 2 + 1]; *(float*)(outputData + pixelOffset + 0) = red; *(float*)(outputData + pixelOffset + 4) = green; } } } } } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { // todo: fix BC5 SNORM dumping float rgBlock[4 * 4 * 2]; decodeBC5Block_SNORM(blockData, rgBlock); float red = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 0]; float green = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 1]; *(outputPixel + 0) = (uint8)((0.5f + red * 0.5f) * 255.0f); *(outputPixel + 1) = (uint8)((0.5f + green * 0.5f) * 255.0f); *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; class TextureDecoder_BC5 : public TextureDecoder, public SingletonClass<TextureDecoder_BC5> { public: sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override { return 16; } sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) override { return (textureLoader->width + 3) / 4; } sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) override { return (textureLoader->height + 3) / 4; } void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override { optimizedDecodeLoops<uint64, 2, false, true>(textureLoader, outputData); } void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override { float rgBlock[4 * 4 * 2]; decodeBC5Block_UNORM(blockData, rgBlock); float red = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 0]; float green = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 1]; *(outputPixel + 0) = (uint8)(red * 255.0f); *(outputPixel + 1) = (uint8)(green * 255.0f); *(outputPixel + 2) = 0; *(outputPixel + 3) = 255; } }; ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTextureReadback.cpp ================================================ #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Common/GLInclude/GLInclude.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" #define LOG_READBACK_TIME struct LatteTextureReadbackQueueEntry { HRTick initiateTime; uint32 lastUpdateDrawcallIndex; LatteTextureView* textureView; }; std::vector<LatteTextureReadbackQueueEntry> sTextureScheduledReadbacks; // readbacks that have been queued but the actual transfer has not yet been started std::queue<LatteTextureReadbackInfo*> sTextureActiveReadbackQueue; // readbacks in flight void LatteTextureReadback_StartTransfer(LatteTextureView* textureView) { cemuLog_log(LogType::TextureReadback, "[TextureReadback-Start] PhysAddr {:08x} Res {}x{} Fmt {} Slice {} Mip {}", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height, textureView->baseTexture->format, textureView->firstSlice, textureView->firstMip); HRTick currentTick = HighResolutionTimer().now().getTick(); // create info entry and store in ordered linked list LatteTextureReadbackInfo* readbackInfo = g_renderer->texture_createReadback(textureView); sTextureActiveReadbackQueue.push(readbackInfo); readbackInfo->StartTransfer(); readbackInfo->transferStartTime = currentTick; } /* * Checks for queued transfers and starts them if at least five drawcalls have passed since the last write * Called after a draw sequence is completed * Returns true if at least one transfer was started */ bool LatteTextureReadback_Update(bool forceStart) { bool hasStartedTransfer = false; for (size_t i = 0; i < sTextureScheduledReadbacks.size(); i++) { LatteTextureReadbackQueueEntry& entry = sTextureScheduledReadbacks[i]; uint32 numElapsedDrawcalls = LatteGPUState.drawCallCounter - entry.lastUpdateDrawcallIndex; if (forceStart || numElapsedDrawcalls >= 5) { #ifdef LOG_READBACK_TIME double elapsedSecondsSinceInitiate = HighResolutionTimer::getTimeDiff(entry.initiateTime, HighResolutionTimer().now().getTick()); cemuLog_log(LogType::TextureReadback, "[TextureReadback-Update] Starting transfer for {:08x} after {} elapsed drawcalls. Time since initiate: {:.4} Force-start: {}", entry.textureView->baseTexture->physAddress, numElapsedDrawcalls, elapsedSecondsSinceInitiate, forceStart?"yes":"no"); #endif LatteTextureReadback_StartTransfer(entry.textureView); // remove element vectorRemoveByIndex(sTextureScheduledReadbacks, i); i--; hasStartedTransfer = true; } } return hasStartedTransfer; } /* * Called when a texture is deleted */ void LatteTextureReadback_NotifyTextureDeletion(LatteTexture* texture) { // delete from queue for (size_t i = 0; i < sTextureScheduledReadbacks.size(); i++) { LatteTextureReadbackQueueEntry& entry = sTextureScheduledReadbacks[i]; if (entry.textureView->baseTexture == texture) { vectorRemoveByIndex(sTextureScheduledReadbacks, i); break; } } } void LatteTextureReadback_Initate(LatteTextureView* textureView) { // currently we don't support readback for resized textures if (textureView->baseTexture->overwriteInfo.hasResolutionOverwrite) { cemuLog_log(LogType::Force, "Texture readback is not supported for textures with modified resolution. Texture: {:08x} {}x{}", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height); return; } // check if texture isn't already queued for transfer for (size_t i = 0; i < sTextureScheduledReadbacks.size(); i++) { LatteTextureReadbackQueueEntry& entry = sTextureScheduledReadbacks[i]; if (entry.textureView == textureView) { entry.lastUpdateDrawcallIndex = LatteGPUState.drawCallCounter; return; } } // queue LatteTextureReadbackQueueEntry queueEntry; queueEntry.initiateTime = HighResolutionTimer().now().getTick(); queueEntry.textureView = textureView; queueEntry.lastUpdateDrawcallIndex = LatteGPUState.drawCallCounter; sTextureScheduledReadbacks.emplace_back(queueEntry); } void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish) { if (forceFinish) { // start any delayed transfers LatteTextureReadback_Update(true); } performanceMonitor.gpuTime_waitForAsync.beginMeasuring(); while (!sTextureActiveReadbackQueue.empty()) { LatteTextureReadbackInfo* readbackInfo = sTextureActiveReadbackQueue.front(); if (forceFinish) { if (!readbackInfo->IsFinished()) { readbackInfo->waitStartTime = HighResolutionTimer().now().getTick(); #ifdef LOG_READBACK_TIME if (cemuLog_isLoggingEnabled(LogType::TextureReadback)) { double elapsedSecondsTransfer = HighResolutionTimer::getTimeDiff(readbackInfo->transferStartTime, HighResolutionTimer().now().getTick()); cemuLog_log(LogType::TextureReadback, "[Texture-Readback] Force-finish: {:08x} Res {:}/{:} TM {:} FMT {:04x} Transfer time so far: {:.4}ms", readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.tileMode, (uint32)readbackInfo->hostTextureCopy.format, elapsedSecondsTransfer * 1000.0); } #endif readbackInfo->forceFinish = true; readbackInfo->ForceFinish(); // rerun logic since ->ForceFinish() can recurively call this function and thus modify the queue continue; } } else { if (!readbackInfo->IsFinished()) break; readbackInfo->waitStartTime = HighResolutionTimer().now().getTick(); } // performance testing #ifdef LOG_READBACK_TIME if (cemuLog_isLoggingEnabled(LogType::TextureReadback)) { HRTick currentTick = HighResolutionTimer().now().getTick(); double elapsedSecondsTransfer = HighResolutionTimer::getTimeDiff(readbackInfo->transferStartTime, currentTick); double elapsedSecondsWaiting = HighResolutionTimer::getTimeDiff(readbackInfo->waitStartTime, currentTick); cemuLog_log(LogType::TextureReadback, "[Texture-Readback] {:08x} Res {}/{} TM {} FMT {:04x} ReadbackLatency: {:6.3}ms WaitTime: {:6.3}ms ForcedWait {}", readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.tileMode, (uint32)readbackInfo->hostTextureCopy.format, elapsedSecondsTransfer * 1000.0, elapsedSecondsWaiting * 1000.0, readbackInfo->forceFinish ? "yes" : "no"); } #endif uint8* pixelData = readbackInfo->GetData(); LatteTextureLoader_writeReadbackTextureToMemory(&readbackInfo->hostTextureCopy, 0, 0, pixelData); readbackInfo->ReleaseData(); // get the original texture if it still exists and invalidate the current data hash LatteTextureView* origTexView = LatteTextureViewLookupCache::lookupSlice(readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.pitch, 0, 0, readbackInfo->hostTextureCopy.format); if (origTexView) LatteTC_ResetTextureChangeTracker(origTexView->baseTexture, true); delete readbackInfo; // remove from queue cemu_assert_debug(!sTextureActiveReadbackQueue.empty()); cemu_assert_debug(readbackInfo == sTextureActiveReadbackQueue.front()); sTextureActiveReadbackQueue.pop(); } performanceMonitor.gpuTime_waitForAsync.endMeasuring(); } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "util/highresolutiontimer/HighResolutionTimer.h" class LatteTextureReadbackInfo { public: LatteTextureReadbackInfo(LatteTextureView* textureView) : hostTextureCopy(textureView->baseTexture), m_textureView(textureView) {} virtual ~LatteTextureReadbackInfo() = default; virtual void StartTransfer() = 0; virtual bool IsFinished() = 0; virtual void ForceFinish() {}; virtual uint8* GetData() = 0; virtual void ReleaseData() {}; HRTick transferStartTime; HRTick waitStartTime; bool forceFinish{ false }; // set to true if not finished in time for dependent operation // texture info LatteTextureDefinition hostTextureCopy{}; protected: LatteTextureView* m_textureView; uint32 m_image_size = 0; }; ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTextureView.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Cafe/HW/Latte/Core/LatteTextureView.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/GraphicPack/GraphicPack2.h" LatteTextureView::LatteTextureView(LatteTexture* texture, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, bool registerView) { this->baseTexture = texture; this->firstMip = firstMip; this->numMip = mipCount; this->firstSlice = firstSlice; this->numSlice = sliceCount; this->dim = dim; this->format = format; if (registerView) { texture->views.emplace_back(this); LatteTextureViewLookupCache::Add(this); } } LatteTextureView::~LatteTextureView() { // unregister view LatteTextureViewLookupCache::RemoveAll(this); // remove from texture vectorRemoveByValue(baseTexture->views, this); if (baseTexture->baseView == this) baseTexture->baseView = nullptr; // delete all associated FBOs while (!list_associatedFbo.empty()) LatteMRT::DeleteCachedFBO(list_associatedFbo[0]); } void LatteTextureView::CreateLookupForSubTexture(uint32 mipStart, uint32 sliceStart) { cemu_assert_debug(mipStart != 0 || sliceStart != 0); // This function should never be called with both parameters zero. Every view creates a base lookup on construction LatteTextureViewLookupCache::Add(this, mipStart, sliceStart); } /* View lookup cache */ struct LatteTexViewLookupDesc { LatteTexViewLookupDesc(LatteTextureView* view) : view(view) { this->physAddr = view->baseTexture->physAddress; this->physMipAddr = view->baseTexture->physMipAddress; this->width = view->baseTexture->width; this->height = view->baseTexture->height; this->pitch = view->baseTexture->pitch; this->firstMip = view->firstMip; this->numMip = view->numMip; this->firstSlice = view->firstSlice; this->numSlice = view->numSlice; this->format = view->format; this->dim = view->dim; this->isDepth = view->baseTexture->isDepth; } void SetParametersForSubTexture(sint32 baseMip, sint32 baseSlice) { cemu_assert_debug(baseMip >= 0); cemu_assert_debug(baseSlice >= 0); LatteTextureSliceMipInfo* sliceMipInfo = view->baseTexture->GetSliceMipArrayEntry(baseSlice, baseMip); physAddr = sliceMipInfo->addrStart; pitch = sliceMipInfo->pitch; cemu_assert_debug(format == view->baseTexture->format); // if the format is different then width/height calculation might differ. This only affects the case where an integer format is mapped onto a compressed format or vice versa. width = view->baseTexture->GetMipWidth(baseMip); height = view->baseTexture->GetMipHeight(baseMip); // adjust firstMip and firstSlice to be relative to base of subtexture cemu_assert(firstMip >= baseMip); cemu_assert(firstSlice >= baseSlice); firstMip -= baseMip; firstSlice -= baseSlice; } // key data for looking up views MPTR physAddr; MPTR physMipAddr; sint32 width; sint32 height; sint32 pitch; sint32 firstMip; sint32 numMip; sint32 firstSlice; sint32 numSlice; Latte::E_GX2SURFFMT format; Latte::E_DIM dim; bool isDepth; // associated view LatteTextureView* view; }; struct LatteTexViewBucket { std::vector<LatteTexViewLookupDesc> list; }; #define TEXTURE_VIEW_BUCKETS (1061) inline uint32 _getViewBucketKey(MPTR physAddress, uint32 width, uint32 height, uint32 pitch) { return (physAddress + width * 7 + height * 11 + pitch * 13) % TEXTURE_VIEW_BUCKETS; } inline uint32 _getViewBucketKeyNoRes(MPTR physAddress, uint32 pitch) { return (physAddress + pitch * 13) % TEXTURE_VIEW_BUCKETS; } LatteTexViewBucket texViewBucket[TEXTURE_VIEW_BUCKETS] = { }; LatteTexViewBucket texViewBucket_nores[TEXTURE_VIEW_BUCKETS] = { }; void LatteTextureViewLookupCache::Add(LatteTextureView* view, uint32 baseMip, uint32 baseSlice) { LatteTexViewLookupDesc desc(view); if (baseMip != 0 || baseSlice != 0) desc.SetParametersForSubTexture(baseMip, baseSlice); // generic bucket uint32 key = _getViewBucketKey(desc.physAddr, desc.width, desc.height, desc.pitch); texViewBucket[key].list.emplace_back(desc); vectorAppendUnique(view->viewLookUpCacheKeys, key); // resolution-independent bucket key = _getViewBucketKeyNoRes(desc.physAddr, desc.pitch); texViewBucket_nores[key].list.push_back(desc); vectorAppendUnique(view->viewLookUpCacheKeysNoRes, key); } void LatteTextureViewLookupCache::RemoveAll(LatteTextureView* view) { for (auto& key : view->viewLookUpCacheKeys) { auto& bucket = texViewBucket[key].list; bucket.erase(std::remove_if(bucket.begin(), bucket.end(), [view](const LatteTexViewLookupDesc& v) { return v.view == view; }), bucket.end()); } for (auto& key : view->viewLookUpCacheKeysNoRes) { auto& bucket = texViewBucket_nores[key].list; bucket.erase(std::remove_if(bucket.begin(), bucket.end(), [view](const LatteTexViewLookupDesc& v) { return v.view == view; }), bucket.end()); } } LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim) { // todo - add tileMode param to this and the other lookup functions? uint32 key = _getViewBucketKey(physAddr, width, height, pitch); key %= TEXTURE_VIEW_BUCKETS; for (auto& it : texViewBucket[key].list) { if (it.format == format && it.dim == dim && it.width == width && it.height == height && it.pitch == pitch && it.physAddr == physAddr && it.firstMip == firstMip && it.numMip == numMip && it.firstSlice == firstSlice && it.numSlice == numSlice ) { return it.view; } } return nullptr; } LatteTextureView* LatteTextureViewLookupCache::lookupWithColorOrDepthType(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth) { cemu_assert_debug(firstSlice == 0); uint32 key = _getViewBucketKey(physAddr, width, height, pitch); key %= TEXTURE_VIEW_BUCKETS; for (auto& it : texViewBucket[key].list) { if (it.format == format && it.dim == dim && it.width == width && it.height == height && it.pitch == pitch && it.physAddr == physAddr && it.firstMip == firstMip && it.numMip == numMip && it.firstSlice == firstSlice && it.numSlice == numSlice && it.isDepth == isDepth ) { return it.view; } } return nullptr; } // look up view with unspecified mipCount and sliceCount LatteTextureView* LatteTextureViewLookupCache::lookupSlice(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format) { uint32 key = _getViewBucketKey(physAddr, width, height, pitch); key %= TEXTURE_VIEW_BUCKETS; for (auto& it : texViewBucket[key].list) { if (it.width == width && it.height == height && it.pitch == pitch && it.physAddr == physAddr && it.format == format) { if (firstSlice == it.firstSlice && firstMip == it.firstMip) return it.view; } } return nullptr; } // look up view with unspecified mipCount/sliceCount and only minimum width and height given LatteTextureView* LatteTextureViewLookupCache::lookupSliceMinSize(MPTR physAddr, sint32 minWidth, sint32 minHeight, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format) { uint32 key = _getViewBucketKeyNoRes(physAddr, pitch); key %= TEXTURE_VIEW_BUCKETS; for (auto& it : texViewBucket_nores[key].list) { if (it.width >= minWidth && it.height >= minHeight && it.pitch == pitch && it.physAddr == physAddr && it.format == format) { if (firstSlice == it.firstSlice && firstMip == it.firstMip) return it.view; } } return nullptr; } // similar to lookupSlice but also compares isDepth LatteTextureView* LatteTextureViewLookupCache::lookupSliceEx(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format, bool isDepth) { cemu_assert_debug(firstMip == 0); uint32 key = _getViewBucketKey(physAddr, width, height, pitch); key %= TEXTURE_VIEW_BUCKETS; for (auto& it : texViewBucket[key].list) { if (it.width == width && it.height == height && it.pitch == pitch && it.physAddr == physAddr && it.format == format && it.isDepth == isDepth) { if (firstSlice == it.firstSlice && firstMip == it.firstMip) return it.view; } } return nullptr; } std::unordered_set<LatteTextureView*> LatteTextureViewLookupCache::GetAllViews() { std::unordered_set<LatteTextureView*> viewSet; for (uint32 i = 0; i < TEXTURE_VIEW_BUCKETS; i++) { for (auto& it : texViewBucket[i].list) viewSet.emplace(it.view); } return viewSet; } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTextureView.h ================================================ #pragma once class LatteTextureView { public: enum class MagFilter { kLinear, kNearestNeighbor, }; LatteTextureView(class LatteTexture* texture, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, bool registerView = true); virtual ~LatteTextureView(); void CreateLookupForSubTexture(uint32 mipStart, uint32 sliceStart); class LatteTexture* baseTexture; // view definition // note that a view can be addressed by more than one combination of physAddress/firstMip/firstSlice // thus the firstMip and firstSlice members of this class may not match the ones that were set in the GPU registers when this view was looked up sint32 firstMip; sint32 numMip; sint32 firstSlice; sint32 numSlice; Latte::E_GX2SURFFMT format; // format of view, can differ from base texture Latte::E_DIM dim; // dimension of view // state uint32 lastTextureBindIndex = 0; // FBO association std::vector<class LatteCachedFBO*> list_fboLookup; // only set for the first color texture of each FBO, or the depth texture if no color textures are present std::vector<class LatteCachedFBO*> list_associatedFbo; // list of cached fbos that reference this texture view // view lookup cache std::vector<uint32> viewLookUpCacheKeys; std::vector<uint32> viewLookUpCacheKeysNoRes; }; class LatteTextureViewLookupCache { public: static void Add(LatteTextureView* view, uint32 baseMip = 0, uint32 baseSlice = 0); static void RemoveAll(LatteTextureView* view); static LatteTextureView* lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim); static LatteTextureView* lookupWithColorOrDepthType(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth); static LatteTextureView* lookupSlice(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format); static LatteTextureView* lookupSliceMinSize(MPTR physAddr, sint32 minWidth, sint32 minHeight, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format); static LatteTextureView* lookupSliceEx(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format, bool isDepth); static std::unordered_set<LatteTextureView*> GetAllViews(); }; ================================================ FILE: src/Cafe/HW/Latte/Core/LatteThread.cpp ================================================ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/OS/libs/gx2/GX2.h" // todo - remove dependency #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "WindowSystem.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "util/helpers/helpers.h" #include <imgui.h> #include "config/ActiveSettings.h" #include "Cafe/CafeSystem.h" LatteGPUState_t LatteGPUState = {}; std::atomic_bool sLatteThreadRunning = false; std::atomic_bool sLatteThreadFinishedInit = false; void LatteThread_Exit(); void Latte_LoadInitialRegisters() { LatteGPUState.contextNew.CB_TARGET_MASK.set_MASK(0xFFFFFFFF); LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.set_RESTART_INDEX(0xFFFFFFFF); LatteGPUState.contextNew.VGT_DMA_NUM_INSTANCES.set_NUM_INSTANCES(1); LatteGPUState.contextRegister[Latte::REGADDR::PA_CL_CLIP_CNTL] = 0; *(float*)&LatteGPUState.contextRegister[mmDB_DEPTH_CLEAR] = 1.0f; } extern bool gx2WriteGatherInited; LatteTextureView* osScreenTVTex[2] = { nullptr }; LatteTextureView* osScreenDRCTex[2] = { nullptr }; LatteTextureView* LatteHandleOSScreen_getOrCreateScreenTex(MPTR physAddress, uint32 width, uint32 height, uint32 pitch) { LatteTextureView* texView = LatteTextureViewLookupCache::lookup(physAddress, width, height, 1, pitch, 0, 1, 0, 1, Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM, Latte::E_DIM::DIM_2D); if (texView) return texView; return LatteTexture_CreateTexture(Latte::E_DIM::DIM_2D, physAddress, 0, Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM, width, height, 1, pitch, 1, 0, Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED, false); } void LatteHandleOSScreen_prepareTextures() { osScreenTVTex[0] = LatteHandleOSScreen_getOrCreateScreenTex(LatteGPUState.osScreen.screen[0].physPtr, 1280, 720, 1280); osScreenTVTex[1] = LatteHandleOSScreen_getOrCreateScreenTex(LatteGPUState.osScreen.screen[0].physPtr + 1280 * 720 * 4, 1280, 720, 1280); osScreenDRCTex[0] = LatteHandleOSScreen_getOrCreateScreenTex(LatteGPUState.osScreen.screen[1].physPtr, 854, 480, 0x380); osScreenDRCTex[1] = LatteHandleOSScreen_getOrCreateScreenTex(LatteGPUState.osScreen.screen[1].physPtr + 896 * 480 * 4, 854, 480, 0x380); } void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPadView); bool LatteHandleOSScreen_TV() { if (!LatteGPUState.osScreen.screen[0].isEnabled) return false; if (LatteGPUState.osScreen.screen[0].flipExecuteCount == LatteGPUState.osScreen.screen[0].flipRequestCount) return false; LatteHandleOSScreen_prepareTextures(); sint32 bufferDisplayTV = (LatteGPUState.osScreen.screen[0].flipRequestCount & 1) ^ 1; sint32 bufferDisplayDRC = (LatteGPUState.osScreen.screen[1].flipRequestCount & 1) ^ 1; const uint32 bufferIndexTV = (bufferDisplayTV); const uint32 bufferIndexDRC = bufferDisplayDRC; LatteTexture_ReloadData(osScreenTVTex[bufferIndexTV]->baseTexture); // TV screen LatteRenderTarget_copyToBackbuffer(osScreenTVTex[bufferIndexTV]->baseTexture->baseView, false); if (LatteGPUState.osScreen.screen[0].flipExecuteCount != LatteGPUState.osScreen.screen[0].flipRequestCount) LatteGPUState.osScreen.screen[0].flipExecuteCount.store(LatteGPUState.osScreen.screen[0].flipRequestCount); return true; } bool LatteHandleOSScreen_DRC() { if (!LatteGPUState.osScreen.screen[1].isEnabled) return false; if (LatteGPUState.osScreen.screen[1].flipExecuteCount == LatteGPUState.osScreen.screen[1].flipRequestCount) return false; LatteHandleOSScreen_prepareTextures(); sint32 bufferDisplayDRC = (LatteGPUState.osScreen.screen[1].flipRequestCount & 1) ^ 1; const uint32 bufferIndexDRC = bufferDisplayDRC; LatteTexture_ReloadData(osScreenDRCTex[bufferIndexDRC]->baseTexture); // GamePad screen LatteRenderTarget_copyToBackbuffer(osScreenDRCTex[bufferIndexDRC]->baseTexture->baseView, true); if (LatteGPUState.osScreen.screen[1].flipExecuteCount != LatteGPUState.osScreen.screen[1].flipRequestCount) LatteGPUState.osScreen.screen[1].flipExecuteCount.store(LatteGPUState.osScreen.screen[1].flipRequestCount); return true; } void LatteThread_HandleOSScreen() { bool swapTV = LatteHandleOSScreen_TV(); bool swapDRC = LatteHandleOSScreen_DRC(); if(swapTV || swapDRC) g_renderer->SwapBuffers(swapTV, swapDRC); } int Latte_ThreadEntry() { SetThreadName("LatteThread"); sint32 w,h; WindowSystem::GetWindowPhysSize(w,h); // renderer g_renderer->Initialize(); RendererOutputShader::InitializeStatic(); LatteTiming_Init(); LatteTexture_init(); LatteTC_Init(); LatteBufferCache_init(164 * 1024 * 1024); LatteQuery_Init(); LatteSHRC_Init(); LatteStreamout_InitCache(); g_renderer->renderTarget_setViewport(0, 0, w, h, 0.0f, 1.0f); // enable GLSL gl_PointSize support // glEnable(GL_PROGRAM_POINT_SIZE); // breaks shader caching on AMD (as of 2018) LatteGPUState.glVendor = GLVENDOR_UNKNOWN; switch(g_renderer->GetVendor()) { case GfxVendor::AMD: LatteGPUState.glVendor = GLVENDOR_AMD; break; case GfxVendor::Intel: LatteGPUState.glVendor = GLVENDOR_INTEL; break; case GfxVendor::Nvidia: LatteGPUState.glVendor = GLVENDOR_NVIDIA; break; case GfxVendor::Apple: LatteGPUState.glVendor = GLVENDOR_APPLE; default: break; } sLatteThreadFinishedInit = true; // register debug handler if (cemuLog_isLoggingEnabled(LogType::OpenGLLogging)) g_renderer->EnableDebugMode(); // wait till a game is started while( true ) { if( CafeSystem::IsTitleRunning() ) break; g_renderer->DrawEmptyFrame(true); g_renderer->DrawEmptyFrame(false); g_renderer->CancelScreenshotRequest(); // keep the screenshot request queue empty std::this_thread::sleep_for(std::chrono::milliseconds(1000/60)); } g_renderer->DrawEmptyFrame(true); // before doing anything with game specific shaders, we need to wait for graphic packs to finish loading GraphicPack2::WaitUntilReady(); // if legacy packs are enabled we cannot use the colorbuffer resolution optimization LatteGPUState.allowFramebufferSizeOptimization = true; for(auto& pack : GraphicPack2::GetActiveGraphicPacks()) { if(pack->AllowRendertargetSizeOptimization()) continue; for(auto& rule : pack->GetTextureRules()) { if(rule.filter_settings.width >= 0 || rule.filter_settings.height >= 0 || rule.filter_settings.depth >= 0 || rule.overwrite_settings.width >= 0 || rule.overwrite_settings.height >= 0 || rule.overwrite_settings.depth >= 0) { LatteGPUState.allowFramebufferSizeOptimization = false; cemuLog_log(LogType::Force, "Graphic pack \"{}\" prevents rendertarget size optimization. This warning can be ignored and is intended for graphic pack developers", pack->GetName()); break; } } } // load disk shader cache LatteShaderCache_Load(); // init registers Latte_LoadInitialRegisters(); // let CPU thread know the GPU is done initializing g_isGPUInitFinished = true; // wait until CPU has called GX2Init() while (LatteGPUState.gx2InitCalled == 0) { std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); LatteThread_HandleOSScreen(); if (Latte_GetStopSignal()) LatteThread_Exit(); } LatteCP_ProcessRingbuffer(); cemu_assert_debug(false); // should never reach return 0; } std::thread sLatteThread; std::mutex sLatteThreadStateMutex; // initializes GPU thread which in turn also activates graphic packs // does not return until the thread finished initialization void Latte_Start() { std::unique_lock _lock(sLatteThreadStateMutex); cemu_assert_debug(!sLatteThreadRunning); sLatteThreadRunning = true; sLatteThreadFinishedInit = false; sLatteThread = std::thread(Latte_ThreadEntry); // wait until initialized while (!sLatteThreadFinishedInit) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } void Latte_Stop() { std::unique_lock _lock(sLatteThreadStateMutex); if (!sLatteThreadRunning) return; sLatteThreadRunning = false; _lock.unlock(); sLatteThread.join(); } bool Latte_GetStopSignal() { return !sLatteThreadRunning; } void LatteThread_Exit() { if (g_renderer) g_renderer->Shutdown(); // clean up vertex/uniform cache LatteBufferCache_UnloadAll(); // clean up texture cache LatteTC_UnloadAllTextures(); // clean up runtime shader cache LatteSHRC_UnloadAll(); // close disk cache LatteShaderCache_Close(); RendererOutputShader::ShutdownStatic(); // destroy renderer but make sure that g_renderer remains valid until the destructor has finished if (g_renderer) { Renderer* renderer = g_renderer.get(); delete renderer; g_renderer.release(); } // reset GPU7 state std::memset(&LatteGPUState, 0, sizeof(LatteGPUState)); #if BOOST_OS_WINDOWS ExitThread(0); #else pthread_exit(nullptr); #endif cemu_assert_unimplemented(); } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTiming.cpp ================================================ #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/OS/libs/gx2/GX2_Event.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h" #include "util/highresolutiontimer/HighResolutionTimer.h" #include "config/CemuConfig.h" #include "Cafe/CafeSystem.h" sint32 s_customVsyncFrequency = -1; void LatteTiming_NotifyHostVSync(); // calculate time between vsync events in timer units // standard rate on Wii U is 59.94, however to prevent tearing and microstutter on ~60Hz displays it is better if we slightly overshoot 60 Hz // can be modified by graphic packs HRTick LatteTime_CalculateTimeBetweenVSync() { // 59.94 -> 60 * 0.999 HRTick tick = HighResolutionTimer::getFrequency(); if (s_customVsyncFrequency > 0) { tick /= (uint64)s_customVsyncFrequency; } else { tick *= 1000ull; tick /= 1002ull; tick /= 60ull; } return tick; } void LatteTiming_setCustomVsyncFrequency(sint32 frequency) { s_customVsyncFrequency = frequency; } void LatteTiming_disableCustomVsyncFrequency() { s_customVsyncFrequency = -1; } bool LatteTiming_getCustomVsyncFrequency(sint32& customFrequency) { sint32 t = s_customVsyncFrequency; if (t <= 0) return false; customFrequency = t; return true; } bool s_usingHostDrivenVSync = false; void LatteTiming_EnableHostDrivenVSync() { if (s_usingHostDrivenVSync) return; VsyncDriver_startThread(LatteTiming_NotifyHostVSync); s_usingHostDrivenVSync = true; } bool LatteTiming_IsUsingHostDrivenVSync() { return s_usingHostDrivenVSync; } void LatteTiming_Init() { LatteGPUState.timer_frequency = HighResolutionTimer::getFrequency(); LatteGPUState.timer_bootUp = HighResolutionTimer::now().getTick(); LatteGPUState.timer_nextVSync = LatteGPUState.timer_bootUp + LatteTime_CalculateTimeBetweenVSync(); } void LatteTiming_signalVsync() { static uint32 s_vsyncIntervalCounter = 0; if (!LatteGPUState.gx2InitCalled) return; s_vsyncIntervalCounter++; uint32 swapInterval = 1; if (LatteGPUState.sharedArea) swapInterval = LatteGPUState.sharedArea->swapInterval; // flip if (s_vsyncIntervalCounter >= swapInterval) { if (LatteGPUState.sharedArea) { // hack/workaround - only execute flip if GX2SwapScanBuffers() isn't lagging behind uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); if (currentTitleId == 0x00050000101c9500 || currentTitleId == 0x00050000101c9400 || currentTitleId == 0x0005000e101c9300) { uint32 currentFlipRequestCount = _swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE); uint32 currentFlipExecuteCount = _swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE); if ((currentFlipRequestCount >= currentFlipExecuteCount) || (currentFlipExecuteCount - currentFlipRequestCount < 4)) { LatteGPUState.sharedArea->flipExecuteCountBE = _swapEndianU32(_swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE) + 1); } LatteGPUState.flipCounter++; } else { // old code for all other games if (LatteGPUState.flipRequestCount > 0) { LatteGPUState.flipRequestCount.fetch_sub(1); LatteGPUState.sharedArea->flipExecuteCountBE = _swapEndianU32(_swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE) + 1); } } } GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::FLIP); s_vsyncIntervalCounter = 0; } // vsync GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::VSYNC); } HRTick s_lastHostVsync = 0; // notify when host vsync event is triggered (on renderer canvas) void LatteTiming_NotifyHostVSync() { if (!LatteTiming_IsUsingHostDrivenVSync()) return; auto nowTimePoint = HighResolutionTimer::now().getTick(); auto dif = nowTimePoint - s_lastHostVsync; auto vsyncPeriod = LatteTime_CalculateTimeBetweenVSync(); if (dif < vsyncPeriod) { // skip return; } uint64 elapsedPeriods = dif / vsyncPeriod; if (elapsedPeriods >= 10) { s_lastHostVsync = nowTimePoint; } else s_lastHostVsync += vsyncPeriod; LatteTiming_signalVsync(); } // handle timed vsync event void LatteTiming_HandleTimedVsync() { // simulate VSync uint64 currentTimer = HighResolutionTimer::now().getTick(); if( currentTimer >= LatteGPUState.timer_nextVSync ) { if(!LatteTiming_IsUsingHostDrivenVSync()) LatteTiming_signalVsync(); // even if vsync is delegated to the host device, we still use this virtual vsync timer to check finished states LatteQuery_UpdateFinishedQueries(); LatteTextureReadback_UpdateFinishedTransfers(false); // update vsync timer uint64 vsyncTime = LatteTime_CalculateTimeBetweenVSync(); uint64 missedVsyncCount = (currentTimer - LatteGPUState.timer_nextVSync) / vsyncTime; if (missedVsyncCount >= 2) { LatteGPUState.timer_nextVSync += vsyncTime*(missedVsyncCount+1ULL); } else LatteGPUState.timer_nextVSync += vsyncTime; } } ================================================ FILE: src/Cafe/HW/Latte/Core/LatteTiming.h ================================================ #pragma once void LatteTiming_setCustomVsyncFrequency(sint32 frequency); void LatteTiming_disableCustomVsyncFrequency(); bool LatteTiming_getCustomVsyncFrequency(sint32& customFrequency); void LatteTiming_EnableHostDrivenVSync(); ================================================ FILE: src/Cafe/HW/Latte/ISA/LatteInstructions.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteConst.h" namespace Latte { using GPRType = uint8; }; class LatteCFInstruction { public: enum class CF_COND { CF_COND_ACTIVE = 0, CF_COND_FALSE = 1, CF_COND_BOOL = 2, CF_COND_NOT_BOOL = 3, }; enum OPCODE { // SQ_CF_INST_* INST_NOP = 0x00, INST_TEX = 0x01, INST_VTX = 0x02, // vertex fetch clause, used only in GS copy program? INST_VTX_TC = 0x03, // vertex fetch clause, through texture cache INST_LOOP_START = 0x04, // DX9 style loop INST_LOOP_END = 0x05, INST_LOOP_START_DX10 = 0x06, INST_LOOP_BREAK = 0x09, INST_JUMP = 0x0A, INST_ELSE = 0x0D, INST_POP = 0x0E, INST_ELSE_AFTER = 0x0F, INST_CALL = 0x12, INST_CALL_FS = 0x13, INST_RETURN = 0x14, INST_EMIT_VERTEX = 0x15, // only available in geometry shader INST_MEM_STREAM0_WRITE = 0x20, // for stream out (index selects buffer) INST_MEM_STREAM1_WRITE = 0x21, INST_MEM_STREAM2_WRITE = 0x22, INST_MEM_STREAM3_WRITE = 0x23, INST_MEM_RING_WRITE = 0x26, // used to pass data to/from geometry shader INST_EXPORT = 0x27, INST_EXPORT_DONE = 0x28, // last export // ALU instructions MASK_ALU = 0x40, // mask to differentiate ALU instructions INST_ALU = (0x08 | MASK_ALU), INST_ALU_PUSH_BEFORE = (0x09 | MASK_ALU), INST_ALU_POP_AFTER = (0x0A | MASK_ALU), INST_ALU_POP2_AFTER = (0x0B | MASK_ALU), // reserved INST_ALU_CONTINUE = (0x0D | MASK_ALU), INST_ALU_BREAK = (0x0E | MASK_ALU), INST_ALU_ELSE_AFTER = (0x0F | MASK_ALU), }; OPCODE getField_Opcode() const { uint32 cf_inst23_7 = (word1 >> 23) & 0x7F; // check the bigger opcode fields first if (cf_inst23_7 < 0x40) // starting at 0x40 the bits overlap with the ALU instruction encoding { // cf_inst23_7 is opcode return (OPCODE)cf_inst23_7; } uint32 cf_inst26_4 = ((word1 >> 26) & 0xF); // cf_inst26_4 is ALU opcode return (OPCODE)(cf_inst26_4 | OPCODE::MASK_ALU); } bool getField_END_OF_PROGRAM() const { // shared by all CF instruction types except ALU cemu_assert_debug((getField_Opcode() & OPCODE::MASK_ALU) != OPCODE::MASK_ALU); return ((word1 >> 21) & 1) != 0; } const class LatteCFInstruction_ALU* getParser_ALU() const { cemu_assert_debug((getField_Opcode() & OPCODE::MASK_ALU) == OPCODE::MASK_ALU); return (const LatteCFInstruction_ALU*)this; } // EXPORT is for: // SQ_CF_INST_MEM_STREAM0 - SQ_CF_INST_MEM_STREAM3 // SQ_CF_INST_MEM_SCRATCH // SQ_CF_INST_MEM_REDUCTION // SQ_CF_INST_MEM_RING // SQ_CF_INST_EXPORT // SQ_CF_INST_EXPORT_DONE const class LatteCFInstruction_EXPORT_IMPORT* getParser_EXPORT() const { return (const LatteCFInstruction_EXPORT_IMPORT*)this; } template<typename TCFEncoding> const TCFEncoding* getParserIfOpcodeMatch() const { auto opcode = getField_Opcode(); if (TCFEncoding::MatchesOpcode(opcode)) return (const TCFEncoding*)this; return nullptr; } // writing void setField_Opcode(OPCODE opcode) { cemu_assert_debug(((uint32)opcode & (uint32)OPCODE::MASK_ALU) == 0); word1 &= ~(0xF << 23); word1 |= ((uint32)opcode << 23); } protected: uint32 word0; uint32 word1; }; // default encoding, CF_DWORD0 + CF_DWORD1 // used for opcodes: See list in MatchesOpcode() class LatteCFInstruction_DEFAULT : public LatteCFInstruction { public: LatteCFInstruction_DEFAULT() { word0 = 0; word1 = 0; } static bool MatchesOpcode(const OPCODE opcode) { return opcode == OPCODE::INST_NOP || opcode == OPCODE::INST_VTX || opcode == OPCODE::INST_VTX_TC || opcode == OPCODE::INST_TEX || opcode == OPCODE::INST_CALL_FS || opcode == OPCODE::INST_CALL || opcode == OPCODE::INST_RETURN || opcode == OPCODE::INST_LOOP_START || opcode == OPCODE::INST_LOOP_END || opcode == OPCODE::INST_LOOP_START_DX10 || //opcode == OPCODE::INST_LOOP_CONTINUE || //opcode == OPCODE::INST_LOOP_BREAK || opcode == OPCODE::INST_JUMP || //opcode == OPCODE::INST_PUSH || //opcode == OPCODE::INST_PUSH_ELSE || opcode == OPCODE::INST_ELSE || opcode == OPCODE::INST_POP || //opcode == OPCODE::INST_POP_JUMP || opcode == OPCODE::INST_JUMP || //opcode == OPCODE::INST_POP_PUSH || //opcode == OPCODE::INST_PUSH || //opcode == OPCODE::INST_POP_PUSH_ELSE || //opcode == OPCODE::INST_PUSH_ELSE || opcode == OPCODE::INST_EMIT_VERTEX //opcode == OPCODE::INST_EMIT_CUT_VERTEX || //opcode == OPCODE::INST_CUT_VERTEX || //opcode == OPCODE::INST_KILL ; } // returns offset in bytes uint32 getField_ADDR() const // returns offset in bytes { return word0 << 3; } uint32 getField_POP_COUNT() const { return (word1 >> 0) & 7; } uint32 getField_CF_CONST() const { return (word1 >> 3) & 0x1F; } CF_COND getField_COND() const { return (CF_COND)((word1 >> 8) & 0x3); } uint32 getField_COUNT() const { uint32 count = (word1 >> 10) & 0x7; // R600 field count |= ((word1 >> 16)&0x8); // R700 has an extra bit at 19 return count + 1; } uint32 getField_CALL_COUNT() const { return (word1 >> 13) & 0x3F; } uint32 getField_VALID_PIXEL_MODE() const { return (word1 >> 22) & 1; } uint32 getField_WHOLE_QUAD_MODE() const { return (word1 >> 30) & 1; } uint32 getField_BARRIER() const { return (word1 >> 31) & 1; } std::span<uint8> getClauseCode(std::span<uint8> programCode) const { cemu_assert_debug(getField_Opcode() == LatteCFInstruction::INST_VTX || getField_Opcode() == LatteCFInstruction::INST_VTX_TC); cemu_assert_debug(getField_ADDR() <= programCode.size()); cemu_assert_debug((programCode.size() - getField_ADDR()) >= getField_COUNT() * 16); return programCode.subspan(getField_ADDR(), getField_COUNT() * 16); } // writing void setField_ADDR(uint32 addrInBytes) // in bytes { word0 = addrInBytes >> 3; } void setField_COUNT(uint32 count) { cemu_assert_debug(count > 0 && count <= 16); count--; word1 &= ~((0x7 << 10) | (1 << 19)); word1 |= ((count & 0x7) << 10); word1 |= ((count << 16) & (1<<19)); } void setField_BARRIER(bool isEnabled) { if (isEnabled) word1 |= (1 << 31); else word1 &= ~(1 << 31); } }; // CF_ALLOC_EXPORT_DWORD0 + CF_ALLOC_EXPORT_DWORD1_BUF / CF_ALLOC_EXPORT_DWORD1_SWIZ // this has two different encoding. Use isEncodingBUF() to determine which fields are valid class LatteCFInstruction_EXPORT_IMPORT : public LatteCFInstruction // CF_ALLOC_EXPORT_DWORD1_SWIZ { public: static bool MatchesOpcode(const OPCODE opcode) { return opcode == OPCODE::INST_MEM_STREAM0_WRITE || opcode == OPCODE::INST_MEM_STREAM1_WRITE || opcode == OPCODE::INST_MEM_STREAM2_WRITE || opcode == OPCODE::INST_MEM_STREAM3_WRITE || //opcode == OPCODE::INST_MEM_SCRATCH || //opcode == OPCODE::INST_MEM_REDUCTION || opcode == OPCODE::INST_MEM_RING_WRITE || opcode == OPCODE::INST_EXPORT || opcode == OPCODE::INST_EXPORT_DONE; } enum EXPORT_TYPE : uint8 { PIXEL = 0, POSITION = 1, PARAMETER = 2, UNUSED_VAL = 3 }; enum COMPSEL : uint8 { X, Y, Z, W, CONST_0F, CONST_1F, RESERVED, MASKED }; EXPORT_TYPE getField_TYPE() const { return (EXPORT_TYPE)((word0 >> 13) & 0x3); } uint32 getField_ARRAY_BASE() const { return (word0 >> 0) & 0x1FFF; } uint32 getField_INDEX_GPR() const { return (word0 >> 23) & 0x7F; } // read/write GPR (source/destination) uint32 getField_RW_GPR() const { return (word0 >> 15) & 0x7F; } // if true, RW_GPR is indexed bool getField_RW_REL() const { return ((word0 >> 22) & 0x1) != 0; } uint8 getField_BURST_COUNT() const { return ((word1 >> 17) & 0xF) + 1; } uint8 getField_ELEM_SIZE() const { return ((word0 >> 30) & 0x3) + 1; } // word1 bits 0-15 differ depending on BUF/SWIZ encoding // returns true if BUF encoding is used (BUF fields valid, SWIZ invalid). Otherwise SWIZ encoding is used (BUF fields invalid, SWIZ fields valid) bool isEncodingBUF() const { return ((word1 >> 12) & 0xF) != 0; } // fields specific to SWIZ encoding COMPSEL getSwizField_SEL_X() const { cemu_assert_debug(!isEncodingBUF()); return (COMPSEL)((word1 >> 0) & 0x7); } COMPSEL getSwizField_SEL_Y() const { cemu_assert_debug(!isEncodingBUF()); return (COMPSEL)((word1 >> 3) & 0x7); } COMPSEL getSwizField_SEL_Z() const { cemu_assert_debug(!isEncodingBUF()); return (COMPSEL)((word1 >> 6) & 0x7); } COMPSEL getSwizField_SEL_W() const { cemu_assert_debug(!isEncodingBUF()); return (COMPSEL)((word1 >> 9) & 0x7); } // fields specific to BUF encoding (word1 bits 0-15) uint32 getBufField_ARRAY_SIZE() const { cemu_assert_debug(isEncodingBUF()); return (word1 >> 0) & 0xFFF; } // applies only to writes uint32 getBufField_COMP_MASK() const { cemu_assert_debug(isEncodingBUF()); return (word1 >> 12) & 0xF; } // these are not specific to EXPORT instr? Move to LatteCFInstruction? bool getValidPixelMode() const { return ((word1 >> 22) & 0x1) != 0; } bool getWholeQuadMode() const { return ((word1 >> 30) & 0x1) != 0; } }; // encoding for CF_ALU_DWORD0 + CF_ALU_DWORD1 // used for: // CF_INST_ALU // CF_INST_ALU_PUSH_BEFORE // CF_INST_ALU_POP_AFTER // CF_INST_ALU_POP2_AFTER // CF_INST_ALU_CONTINUE // CF_INST_ALU_BREAK // CF_INST_ALU_ELSE_AFTER class LatteCFInstruction_ALU : public LatteCFInstruction { public: static bool MatchesOpcode(const OPCODE opcode) { return opcode == OPCODE::INST_ALU || opcode == OPCODE::INST_ALU_PUSH_BEFORE || opcode == OPCODE::INST_ALU_POP_AFTER || opcode == OPCODE::INST_ALU_POP2_AFTER || opcode == OPCODE::INST_ALU_CONTINUE || opcode == OPCODE::INST_ALU_BREAK || opcode == OPCODE::INST_ALU_ELSE_AFTER; } uint32 getField_ADDR() const { return (word0 >> 0) & 0x3FFFFF; } uint32 getField_COUNT() const { return ((word1 >> 18) & 0x7F) + 1; } uint32 getField_KCACHE_BANK0() const { return (word0 >> 22) & 0xF; } uint32 getField_KCACHE_BANK1() const { return (word0 >> 26) & 0xF; } uint32 getField_KCACHE_ADDR0() const { return (word1 >> 2) & 0xFF; } uint32 getField_KCACHE_ADDR1() const { return (word1 >> 10) & 0xFF; } bool getField_USES_WATERFALL() const { return ((word1 >> 25)&1) !=0; } // todo - KCACHE_MODE0, KCACHE_MODE1, WHOLE_QUAD_MODE, BARRIER }; static_assert(sizeof(LatteCFInstruction) == 8); static_assert(sizeof(LatteCFInstruction_DEFAULT) == 8); static_assert(sizeof(LatteCFInstruction_EXPORT_IMPORT) == 8); static_assert(sizeof(LatteCFInstruction_ALU) == 8); /* Latte instructions */ class LatteClauseInstruction_VTX // used by CF VTX and VTX_TC clauses { public: LatteClauseInstruction_VTX() { word0 = 0; word1 = 0; word2 = 0; word3 = 0; } // VTX_DWORD0 enum class VTX_INST { _VTX_INST_FETCH = 0, _VTX_INST_SEMANTIC = 1, _VTX_INST_MEM = 2 }; enum class SRC_SEL { SEL_X = 0, SEL_Y = 1, SEL_Z = 2, SEL_W = 3, }; enum class DST_SEL { SEL_X = 0, SEL_Y = 1, SEL_Z = 2, SEL_W = 3, SEL_0 = 4, // constant 0.0 SEL_1 = 5, // constant 1.0 SEL_RESERVED = 6, SEL_MASK = 7, }; enum class NUM_FORMAT_ALL // NFA { NUM_FORMAT_NORM = 0, // normalized to float (-1.0 to 1.0 for signed, 0.0 to 1.0 for unsigned) NUM_FORMAT_INT = 1, // interpreted as integer NUM_FORMAT_SCALED = 2, }; enum class FORMAT_COMP { COMP_UNSIGNED = 0, COMP_SIGNED = 1, }; enum class SRF_MODE { SRF_MODE_ZERO_CLAMP_MINUS_ONE = 0, SRF_MODE_NO_ZERO = 1, }; // fields todo: // FETCH_WHOLE_QUAD // MEGA_FETCH_COUNT // MEGA_FETCH (word2) // ALT_CONST (word2) VTX_INST getField_VTX_INST() const // alias opcode { return (VTX_INST)((word0 >> 0) & 0x1F); } LatteConst::VertexFetchType2 getField_FETCH_TYPE() const { return (LatteConst::VertexFetchType2)((word0 >> 5) & 0x3); } uint32 getField_BUFFER_ID() const { return (word0 >> 8) & 0xFF; } uint32 getField_SRC_GPR() const { return (word0 >> 16) & 0x7F; } bool getField_SRC_REL() const { return ((word0 >> 23) & 1) != 0; } SRC_SEL getField_SRC_SEL_X() const { return (SRC_SEL)((word0 >> 24) & 0x3); } // WORD1 depends on instruction type but some fields are shared // VTX_DWORD1 / VTX_DWORD1_GPR / VTX_DWORD1_SEM DST_SEL getField_DST_SEL(uint32 index) const // shared field { cemu_assert_debug(index <= 3); return (DST_SEL)((word1 >> (9 + index*3)) & 0x7); } bool getField_USE_CONST_FIELDS() const // shared field { return ((word1 >> 21) & 1) != 0; } LatteConst::VertexFetchFormat getField_DATA_FORMAT() const // shared field { return (LatteConst::VertexFetchFormat)((word1 >> 22) & 0x3F); } NUM_FORMAT_ALL getField_NUM_FORMAT_ALL() const // shared field { return (NUM_FORMAT_ALL)((word1 >> 28) & 3); } FORMAT_COMP getField_FORMAT_COMP_ALL() const // shared field { return (FORMAT_COMP)((word1 >> 30) & 1); } SRF_MODE getField_SRF_MODE_ALL() const // shared field { return (SRF_MODE)((word1 >> 30) & 1); } // VTX_DWORD1_SEM specific fields (VTX_INST_SEMANTIC) uint32 getFieldSEM_SEMANTIC_ID() const { return ((word1 >> 0) & 0xFF); } // VTX_DWORD1_GPR specific fields (VTX_INST_FETCH?) // todo // VTX_DWORD2 uint32 getField_OFFSET() const // shared field { return ((word2 >> 0) & 0xFFFF); } LatteConst::VertexFetchEndianMode getField_ENDIAN_SWAP() const // shared field { return (LatteConst::VertexFetchEndianMode)((word2 >> 16) & 0x3); } bool getField_CONST_BUF_NO_STRIDE() const // shared field { return ((word2 >> 18) & 0x1) != 0; } // writing LatteClauseInstruction_VTX& setField_VTX_INST(VTX_INST inst) { word0 &= ~(0x1F << 0); word0 |= ((uint32)inst << 0); return *this; } LatteClauseInstruction_VTX& setField_FETCH_TYPE(LatteConst::VertexFetchType2 fetchType) { word0 &= ~(0x3 << 5); word0 |= ((uint32)fetchType << 5); return *this; } LatteClauseInstruction_VTX& setField_BUFFER_ID(uint32 bufferId) { word0 &= ~(0xFF << 8); word0 |= (bufferId << 8); return *this; } LatteClauseInstruction_VTX& setField_SRC_GPR(uint32 srcGPR) { word0 &= ~(0x7F << 16); word0 |= (srcGPR << 16); return *this; } LatteClauseInstruction_VTX& setField_SRC_REL(bool isRel) { if(isRel) word0 |= (1 << 23); else word0 &= ~(0x1 << 23); return *this; } LatteClauseInstruction_VTX& setField_SRC_SEL_X(SRC_SEL srcSel) { word0 &= ~(0x3 << 24); word0 |= ((uint32)srcSel << 24); return *this; } LatteClauseInstruction_VTX& setFieldSEM_SEMANTIC_ID(uint32 semanticId) { cemu_assert_debug(semanticId <= 0xFF); word1 &= ~(0xFF); word1 |= ((uint32)semanticId); return *this; } LatteClauseInstruction_VTX& setField_OFFSET(uint32 offset) { cemu_assert_debug(offset < 0x10000); word2 &= ~(0xFFFF << 0); word2 |= ((uint32)offset); return *this; } LatteClauseInstruction_VTX& setField_DATA_FORMAT(LatteConst::VertexFetchFormat fetchFormat) { word1 &= ~(0x3F << 22); word1 |= ((uint32)fetchFormat << 22); return *this; } LatteClauseInstruction_VTX& setField_NUM_FORMAT_ALL(NUM_FORMAT_ALL nfa) { word1 &= ~(0x3 << 28); word1 |= ((uint32)nfa << 28); return *this; } LatteClauseInstruction_VTX& setField_FORMAT_COMP_ALL(FORMAT_COMP componentFormat) { word1 &= ~(0x1 << 30); word1 |= ((uint32)componentFormat << 30); return *this; } LatteClauseInstruction_VTX& setField_DST_SEL(uint32 index, DST_SEL dstSel) { cemu_assert_debug(index <= 3); word1 &= ~(0x7 << (9 + index * 3)); word1 |= ((uint32)dstSel << (9 + index * 3)); return *this; } LatteClauseInstruction_VTX& setField_ENDIAN_SWAP(LatteConst::VertexFetchEndianMode endianSwap) { word2 &= ~(0x3 << 16); word2 |= ((uint32)endianSwap << 16); return *this; } protected: uint32 word0; uint32 word1; uint32 word2; uint32 word3; // not used }; static_assert(sizeof(LatteClauseInstruction_VTX) == 16); class LatteClauseInstruction_ALU { public: enum OPCODE_OP2 { ADD = 0x00, MUL = 0x01, MUL_IEEE = 0x02, //#define ALU_OP2_INST_MAX (0x003) //#define ALU_OP2_INST_MIN (0x004) //#define ALU_OP2_INST_MAX_DX10 (0x005) //#define ALU_OP2_INST_SETE (0x008) //#define ALU_OP2_INST_SETGT (0x009) //#define ALU_OP2_INST_SETGE (0x00A) //#define ALU_OP2_INST_SETNE (0x00B) //#define ALU_OP2_INST_SETE_DX10 (0x00C) //#define ALU_OP2_INST_SETGT_DX10 (0x00D) //#define ALU_OP2_INST_SETGE_DX10 (0x00E) //#define ALU_OP2_INST_SETNE_DX10 (0x00F) //#define ALU_OP2_INST_FLOOR (0x014) //#define ALU_OP2_INST_FRACT (0x010) //#define ALU_OP2_INST_TRUNC (0x011) //#define ALU_OP2_INST_RNDNE (0x013) //#define ALU_OP2_INST_MOVA_FLOOR (0x016) // changes address register //#define ALU_OP2_INST_MOVA_INT (0x018) // changes address register MOV = 0x19, //#define ALU_OP2_INST_NOP (0x01A) //#define ALU_OP2_INST_PRED_SETE (0x020) //#define ALU_OP2_INST_PRED_SETGT (0x021) //#define ALU_OP2_INST_PRED_SETGE (0x022) //#define ALU_OP2_INST_PRED_SETNE (0x023) //#define ALU_OP2_INST_AND_INT (0x030) // integer instruction //#define ALU_OP2_INST_OR_INT (0x031) // integer instruction //#define ALU_OP2_INST_XOR_INT (0x032) // integer instruction //#define ALU_OP2_INST_NOT_INT (0x033) // integer instruction //#define ALU_OP2_INST_ADD_INT (0x034) // integer instruction //#define ALU_OP2_INST_SUB_INT (0x035) // integer instruction //#define ALU_OP2_INST_MAX_INT (0x036) // integer instruction //#define ALU_OP2_INST_MIN_INT (0x037) // integer instruction //#define ALU_OP2_INST_SETE_INT (0x03A) // integer instruction //#define ALU_OP2_INST_SETGT_INT (0x03B) // integer instruction //#define ALU_OP2_INST_SETGE_INT (0x03C) // integer instruction //#define ALU_OP2_INST_SETNE_INT (0x03D) // integer instruction //#define ALU_OP2_INST_SETGT_UINT (0x03E) // integer instruction //#define ALU_OP2_INST_SETGE_UINT (0x03F) // integer instruction //#define ALU_OP2_INST_PRED_SETE_INT (0x042) // integer instruction //#define ALU_OP2_INST_PRED_SETGT_INT (0x043) // integer instruction //#define ALU_OP2_INST_PRED_SETGE_INT (0x044) // integer instruction //#define ALU_OP2_INST_PRED_SETNE_INT (0x045) // integer instruction //#define ALU_OP2_INST_KILLE (0x02C) //#define ALU_OP2_INST_KILLGT (0x02D) //#define ALU_OP2_INST_KILLGE (0x02E) //#define ALU_OP2_INST_KILLE_INT (0x046) //#define ALU_OP2_INST_KILLGT_INT (0x047) //#define ALU_OP2_INST_KILLNE_INT (0x049) DOT4 = 0x50, //#define ALU_OP2_INST_DOT4_IEEE (0x051) CUBE = 0x52, EXP_IEEE = 0x61, LOG_CLAMPED = 0x62, LOG_IEEE = 0x63, SQRT_IEEE = 0x6A, SIN = 0x06E, COS = 0x06F, RECIP_FF = 0x65, RECIP_IEEE = 0x66, RECIPSQRT_CLAMPED = 0x67, RECIPSQRT_FF = 0x68, RECIPSQRT_IEEE = 0x69, FLT_TO_INT = 0x6B, INT_TO_FLOAT = 0x6C, UINT_TO_FLOAT = 0x6D, ASHR_INT = 0x70, LSHR_INT = 0x71, LSHL_INT = 0x72, MULLO_INT = 0x73, MULLO_UINT = 0x75, FLT_TO_UINT = 0x79, }; bool isOP3() const { uint32 alu_inst13_5 = (word1 >> 13) & 0x1F; return alu_inst13_5 >= 0x8; } OPCODE_OP2 getOP2Code() const { uint32 alu_inst7_11 = (word1 >> 7) & 0x7FF; return (OPCODE_OP2)alu_inst7_11; } bool isLastInGroup() const { return (word0 & 0x80000000) != 0; } const class LatteClauseInstruction_ALU_OP2* getOP2Instruction() const { return (const class LatteClauseInstruction_ALU_OP2*)this; } protected: const uint32 word0 = 0; const uint32 word1 = 0; }; class LatteALUSrcSel { public: LatteALUSrcSel(const uint16 op) : m_op(op) {}; bool isGPR() const { return m_op < 128; }; bool isAnyConst() const { return m_op >= 248 && m_op <= 252; }; bool isConst_0F() const { return m_op == 248; }; bool isLiteral() const { return m_op == 253; }; bool isCFile() const { return m_op >= 256; }; uint32 getGPR() const { return m_op; }; uint32 getCFile() const { return (m_op & 0xFF); }; private: const uint16 m_op; // 0 - 511 }; class LatteClauseInstruction_ALU_OP2 : public LatteClauseInstruction_ALU { public: uint32 getDestGpr() const { return (word1 >> 21) & 0x7F; } uint32 getDestElem() const { return (word1 >> 29) & 3; } bool getDestRel() const { return ((word1 >> 28) & 1) != 0; } bool getDestClamp() const { return ((word1 >> 31) & 1) != 0; } bool getWriteMask() const { return ((word1 >> 4) & 1) != 0; } uint8 getOMod() const // use enum? { return (word1 >> 5) & 3; } const LatteALUSrcSel getSrc0Sel() const { return LatteALUSrcSel((word0 >> 0) & 0x1FF); } const uint8 getSrc0Chan() const { return (word0 >> 10) & 0x3; } const LatteALUSrcSel getSrc1Sel() const { return LatteALUSrcSel((word0 >> 13) & 0x1FF); } const uint8 getSrc1Chan() const { return (word0 >> 23) & 0x3; } bool isSrc0Neg() const { return ((word0 >> 12) & 0x1) != 0; } bool isSrc1Neg() const { return ((word0 >> 25) & 0x1) != 0; } bool isSrc0Rel() const { return ((word0 >> 9) & 0x1) != 0; } bool isSrc1Rel() const { return ((word0 >> 22) & 0x1) != 0; } uint8 getIndexMode() const { return (word0 >> 26) & 7;; } bool isTranscedentalUnit() const { const uint32 op2 = this->getOP2Code(); switch (op2) { case COS: case SIN: case RECIP_FF: // todo: verify case RECIP_IEEE: // todo: verify case RECIPSQRT_IEEE: // todo: verify case RECIPSQRT_CLAMPED: // todo: verify case RECIPSQRT_FF: // todo: verify case MULLO_INT: case MULLO_UINT: case FLT_TO_INT: case FLT_TO_UINT: case INT_TO_FLOAT: case UINT_TO_FLOAT: case LOG_CLAMPED: case LOG_IEEE: case EXP_IEEE: case SQRT_IEEE: return true; default: break; } return false; } }; static_assert(sizeof(LatteClauseInstruction_ALU) == 8); ================================================ FILE: src/Cafe/HW/Latte/ISA/LatteReg.h ================================================ #pragma once namespace Latte { // common enums enum class E_DIM : uint32 // shared between Latte backend and GX2 code { DIM_1D = 0, DIM_2D = 1, DIM_3D = 2, DIM_CUBEMAP = 3, DIM_1D_ARRAY = 4, DIM_2D_ARRAY = 5, DIM_2D_MSAA = 6, DIM_2D_ARRAY_MSAA = 7 }; enum class E_AAMODE : uint32 // shared between Latte backend and GX2 code { AA_1X = 0, AA_2X = 1, AA_4X = 2, AA_8X = 3, }; enum class E_HWTILEMODE { TM_LINEAR_GENERAL = 0, // linear (pitch must be aligned to 8?) TM_LINEAR_ALIGNED = 1, // pitch must be multiple of 64 pixels? TM_1D_TILED_THIN1 = 2, // no macro tiling, 8x8 micro tiles TM_1D_TILED_THICK = 3, // no macro tiling, 8x8x4 micro tiles TM_2D_TILED_THIN1 = 4, // 4x2 TM_2D_TILED_THIN2 = 5, // 2x4 TM_2D_TILED_THIN4 = 6, // 1x8 TM_2D_TILED_THICK = 7, // 4x2x1 TM_2B_TILED_THIN1 = 8, // 4x2 TM_2B_TILED_THIN2 = 9, // 2x4 TM_2B_TILED_THIN4 = 10, // 1x8 TM_2B_TILED_THICK = 11, // 4x2x1 TM_3D_TILED_THIN1 = 12, // 4x2 TM_3D_TILED_THICK = 13, // 4x2x1 TM_3B_TILED_THIN1 = 14, // 4x2 TM_3B_TILED_THICK = 15, // 4x2x1 }; enum class E_GX2TILEMODE : uint32 { // same as E_TILEMODE but contains additional options with special meaning TM_LINEAR_GENERAL = 0, TM_LINEAR_ALIGNED = 1, // micro-tiled TM_1D_TILED_THIN1 = 2, TM_1D_TILED_THICK = 3, // macro-tiled TM_2D_TILED_THIN1 = 4, TM_2D_TILED_THIN2 = 5, TM_2D_TILED_THIN4 = 6, TM_2D_TILED_THICK = 7, TM_2B_TILED_THIN1 = 8, TM_2B_TILED_THIN2 = 9, TM_2B_TILED_THIN4 = 10, TM_2B_TILED_THICK = 11, TM_3D_TILED_THIN1 = 12, TM_3D_TILED_THICK = 13, TM_3B_TILED_THIN1 = 14, TM_3B_TILED_THICK = 15, // special TM_LINEAR_SPECIAL = 16, TM_32_SPECIAL = 32, }; inline E_HWTILEMODE MakeHWTileMode(const E_GX2TILEMODE gx2Tilemode) { return (E_HWTILEMODE)((uint32)gx2Tilemode & 0xF); } inline E_GX2TILEMODE MakeGX2TileMode(const E_HWTILEMODE hwTilemode) { return (E_GX2TILEMODE)hwTilemode; } inline bool TM_IsMacroTiled(const E_HWTILEMODE tm) { return (uint32)tm >= 4; } inline bool TM_IsMacroTiled(const E_GX2TILEMODE tm) { return (uint32)tm >= 4 && (uint32)tm != 16; } inline bool TM_IsBankSwapped(const E_HWTILEMODE tileMode) { return tileMode == E_HWTILEMODE::TM_2B_TILED_THIN1 || tileMode == E_HWTILEMODE::TM_2B_TILED_THIN2 || tileMode == E_HWTILEMODE::TM_2B_TILED_THIN4 || tileMode == E_HWTILEMODE::TM_2B_TILED_THICK || tileMode == E_HWTILEMODE::TM_3B_TILED_THIN1 || tileMode == E_HWTILEMODE::TM_3B_TILED_THICK; } enum class E_HWSURFFMT { INVALID_FORMAT = 0, // hardware formats only HWFMT_8 = 0x1, HWFMT_4_4 = 0x2, HWFMT_3_3_2 = 0x3, HWFMT_16 = 0x5, HWFMT_16_FLOAT = 0x6, HWFMT_8_8 = 0x7, HWFMT_5_6_5 = 0x8, HWFMT_6_5_5 = 0x9, HWFMT_1_5_5_5 = 0xA, HWFMT_4_4_4_4 = 0xB, HWFMT_5_5_5_1 = 0xC, HWFMT_32 = 0xD, HWFMT_32_FLOAT = 0xE, HWFMT_16_16 = 0xF, HWFMT_16_16_FLOAT = 0x10, HWFMT_8_24 = 0x11, HWFMT_8_24_FLOAT = 0x12, HWFMT_24_8 = 0x13, HWFMT_24_8_FLOAT = 0x14, HWFMT_10_11_11 = 0x15, HWFMT_10_11_11_FLOAT = 0x16, HWFMT_11_11_10 = 0x17, HWFMT_11_11_10_FLOAT = 0x18, HWFMT_2_10_10_10 = 0x19, HWFMT_8_8_8_8 = 0x1A, HWFMT_10_10_10_2 = 0x1B, HWFMT_X24_8_32_FLOAT = 0x1C, HWFMT_32_32 = 0x1D, HWFMT_32_32_FLOAT = 0x1E, HWFMT_16_16_16_16 = 0x1F, HWFMT_16_16_16_16_FLOAT = 0x20, HWFMT_32_32_32_32 = 0x22, HWFMT_32_32_32_32_FLOAT = 0x23, HWFMT_BC1 = 0x31, HWFMT_BC2 = 0x32, HWFMT_BC3 = 0x33, HWFMT_BC4 = 0x34, HWFMT_BC5 = 0x35, // these formats exist in R600/R700 documentation, but GX2 doesn't seem to handle them. Are they supported? U_HWFMT_BC6 = 0x36, U_HWFMT_BC7 = 0x37, U_HWFMT_32_32_32 = 0x2F, U_HWFMT_32_32_32_FLOAT = 0x30, }; enum class E_GX2SURFFMT // GX2 surface format { INVALID_FORMAT = 0, // base hardware formats (shared with E_HWSURFFMT) HWFMT_8 = 0x1, HWFMT_4_4 = 0x2, HWFMT_3_3_2 = 0x3, HWFMT_16 = 0x5, HWFMT_16_FLOAT = 0x6, HWFMT_8_8 = 0x7, HWFMT_5_6_5 = 0x8, HWFMT_6_5_5 = 0x9, HWFMT_1_5_5_5 = 0xA, HWFMT_4_4_4_4 = 0xB, HWFMT_5_5_5_1 = 0xC, HWFMT_32 = 0xD, HWFMT_32_FLOAT = 0xE, HWFMT_16_16 = 0xF, HWFMT_16_16_FLOAT = 0x10, HWFMT_8_24 = 0x11, HWFMT_8_24_FLOAT = 0x12, HWFMT_24_8 = 0x13, HWFMT_24_8_FLOAT = 0x14, HWFMT_10_11_11 = 0x15, HWFMT_10_11_11_FLOAT = 0x16, HWFMT_11_11_10 = 0x17, HWFMT_11_11_10_FLOAT = 0x18, HWFMT_2_10_10_10 = 0x19, HWFMT_8_8_8_8 = 0x1A, HWFMT_10_10_10_2 = 0x1B, HWFMT_X24_8_32_FLOAT = 0x1C, HWFMT_32_32 = 0x1D, HWFMT_32_32_FLOAT = 0x1E, HWFMT_16_16_16_16 = 0x1F, HWFMT_16_16_16_16_FLOAT = 0x20, HWFMT_32_32_32_32 = 0x22, HWFMT_32_32_32_32_FLOAT = 0x23, HWFMT_BC1 = 0x31, HWFMT_BC2 = 0x32, HWFMT_BC3 = 0x33, HWFMT_BC4 = 0x34, HWFMT_BC5 = 0x35, // GX2 extra format bits FMT_BIT_INT = 0x100, FMT_BIT_SIGNED = 0x200, FMT_BIT_SRGB = 0x400, FMT_BIT_FLOAT = 0x800, // GX2 formats R4_G4_UNORM = HWFMT_4_4, R5_G6_B5_UNORM = HWFMT_5_6_5, R5_G5_B5_A1_UNORM = HWFMT_1_5_5_5, R4_G4_B4_A4_UNORM = HWFMT_4_4_4_4, A1_B5_G5_R5_UNORM = HWFMT_5_5_5_1, R8_UNORM = HWFMT_8, R8_SNORM = (HWFMT_8 | FMT_BIT_SIGNED), R8_UINT = (HWFMT_8 | FMT_BIT_INT), R8_SINT = (HWFMT_8 | FMT_BIT_INT | FMT_BIT_SIGNED), R8_G8_UNORM = HWFMT_8_8, R8_G8_SNORM = (HWFMT_8_8 | FMT_BIT_SIGNED), R8_G8_UINT = (HWFMT_8_8 | FMT_BIT_INT), R8_G8_SINT = (HWFMT_8_8 | FMT_BIT_INT | FMT_BIT_SIGNED), R8_G8_B8_A8_UNORM = HWFMT_8_8_8_8, R8_G8_B8_A8_SNORM = (HWFMT_8_8_8_8 | FMT_BIT_SIGNED), R8_G8_B8_A8_UINT = (HWFMT_8_8_8_8 | FMT_BIT_INT), R8_G8_B8_A8_SINT = (HWFMT_8_8_8_8 | FMT_BIT_INT | FMT_BIT_SIGNED), R8_G8_B8_A8_SRGB = (HWFMT_8_8_8_8 | FMT_BIT_SRGB), R10_G10_B10_A2_UNORM = HWFMT_2_10_10_10, R10_G10_B10_A2_SNORM = (HWFMT_2_10_10_10 | FMT_BIT_SIGNED), R10_G10_B10_A2_UINT = (HWFMT_2_10_10_10 | FMT_BIT_INT), R10_G10_B10_A2_SINT = (HWFMT_2_10_10_10 | FMT_BIT_INT | FMT_BIT_SIGNED), R10_G10_B10_A2_SRGB = (HWFMT_2_10_10_10 | FMT_BIT_SRGB), A2_B10_G10_R10_UNORM = HWFMT_10_10_10_2, A2_B10_G10_R10_UINT = (HWFMT_10_10_10_2 | FMT_BIT_INT), R16_UNORM = HWFMT_16, R16_SNORM = (HWFMT_16 | FMT_BIT_SIGNED), R16_UINT = (HWFMT_16 | FMT_BIT_INT), R16_SINT = (HWFMT_16 | FMT_BIT_INT | FMT_BIT_SIGNED), R16_FLOAT = (HWFMT_16_FLOAT | FMT_BIT_FLOAT), R16_G16_UNORM = HWFMT_16_16, R16_G16_SNORM = (HWFMT_16_16 | FMT_BIT_SIGNED), R16_G16_UINT = (HWFMT_16_16 | FMT_BIT_INT), R16_G16_SINT = (HWFMT_16_16 | FMT_BIT_INT | FMT_BIT_SIGNED), R16_G16_FLOAT = (HWFMT_16_16_FLOAT | FMT_BIT_FLOAT), R16_G16_B16_A16_UNORM = HWFMT_16_16_16_16, R16_G16_B16_A16_SNORM = (HWFMT_16_16_16_16 | FMT_BIT_SIGNED), R16_G16_B16_A16_UINT = (HWFMT_16_16_16_16 | FMT_BIT_INT), R16_G16_B16_A16_SINT = (HWFMT_16_16_16_16 | FMT_BIT_INT | FMT_BIT_SIGNED), R16_G16_B16_A16_FLOAT = (HWFMT_16_16_16_16_FLOAT | FMT_BIT_FLOAT), R24_X8_UNORM = (HWFMT_8_24), R24_X8_FLOAT = (HWFMT_8_24 | FMT_BIT_FLOAT), X24_G8_UINT = (HWFMT_8_24 | FMT_BIT_INT), R32_X8_FLOAT = (HWFMT_X24_8_32_FLOAT | FMT_BIT_FLOAT), // R32_X8_FLOAT X32_G8_UINT_X24 = (HWFMT_X24_8_32_FLOAT | FMT_BIT_INT), // X32_G8_UINT R11_G11_B10_FLOAT = (HWFMT_10_11_11_FLOAT | FMT_BIT_FLOAT), // 32bit component formats do not support SNORM/UNORM (at least GX2 doesnt expose it) R32_UINT = (HWFMT_32 | FMT_BIT_INT), R32_SINT = (HWFMT_32 | FMT_BIT_INT | FMT_BIT_SIGNED), R32_FLOAT = (HWFMT_32_FLOAT | FMT_BIT_FLOAT), R32_G32_UINT = (HWFMT_32_32 | FMT_BIT_INT), R32_G32_SINT = (HWFMT_32_32 | FMT_BIT_INT | FMT_BIT_SIGNED), R32_G32_FLOAT = (HWFMT_32_32_FLOAT | FMT_BIT_FLOAT), R32_G32_B32_A32_UINT = (HWFMT_32_32_32_32 | FMT_BIT_INT), R32_G32_B32_A32_SINT = (HWFMT_32_32_32_32 | FMT_BIT_INT | FMT_BIT_SIGNED), R32_G32_B32_A32_FLOAT = (HWFMT_32_32_32_32_FLOAT | FMT_BIT_FLOAT), // depth D24_S8_UNORM = (HWFMT_8_24), D24_S8_FLOAT = (HWFMT_8_24 | FMT_BIT_FLOAT), D32_S8_FLOAT = (HWFMT_X24_8_32_FLOAT | FMT_BIT_FLOAT), D16_UNORM = HWFMT_16, D32_FLOAT = (HWFMT_32_FLOAT | FMT_BIT_FLOAT), // compressed formats BC1_UNORM = (HWFMT_BC1), BC1_SRGB = (HWFMT_BC1 | FMT_BIT_SRGB), BC2_UNORM = (HWFMT_BC2), BC2_SRGB = (HWFMT_BC2 | FMT_BIT_SRGB), BC3_UNORM = (HWFMT_BC3), BC3_SRGB = (HWFMT_BC3 | FMT_BIT_SRGB), BC4_UNORM = (HWFMT_BC4), BC4_SNORM = (HWFMT_BC4 | FMT_BIT_SIGNED), BC5_UNORM = (HWFMT_BC5), BC5_SNORM = (HWFMT_BC5 | FMT_BIT_SIGNED), // special NV12_UNORM = 0x81, }; DEFINE_ENUM_FLAG_OPERATORS(E_GX2SURFFMT); inline uint32 GetFormatBits(const Latte::E_HWSURFFMT hwFmt) { const uint8 sBitsTable[0x40] = { 0x00,0x08,0x08,0x00,0x00,0x10,0x10,0x10, 0x10,0x10,0x10,0x10,0x10,0x20,0x20,0x20, 0x20,0x20,0x00,0x20,0x00,0x00,0x20,0x00, 0x00,0x20,0x20,0x20,0x40,0x40,0x40,0x40, 0x40,0x00,0x80,0x80,0x00,0x00,0x00,0x10, 0x10,0x20,0x20,0x20,0x00,0x00,0x00,0x60, 0x60,0x40,0x80,0x80,0x40,0x80,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; cemu_assert((uint32)hwFmt < 0x40); return sBitsTable[(uint32)hwFmt]; } inline uint32 GetFormatBits(const Latte::E_GX2SURFFMT gx2Fmt) { return GetFormatBits((Latte::E_HWSURFFMT)((uint32)gx2Fmt & 0x3F)); } inline E_HWSURFFMT GetHWFormat(E_GX2SURFFMT format) { return (E_HWSURFFMT)((uint32)format & 0x3F); } inline bool IsCompressedFormat(Latte::E_HWSURFFMT format) { return (uint32)format >= 0x31 && (uint32)format <= 0x35; } inline bool IsCompressedFormat(Latte::E_GX2SURFFMT format) { return IsCompressedFormat((Latte::E_HWSURFFMT)((uint32)format & 0x3F)); } inline bool IsMSAA(Latte::E_DIM dim) { return dim == E_DIM::DIM_2D_MSAA || dim == E_DIM::DIM_2D_ARRAY_MSAA; } enum GPU_LIMITS { NUM_VERTEX_BUFFERS = 16, NUM_TEXTURES_PER_STAGE = 18, NUM_SAMPLERS_PER_STAGE = 18, // is this 16 or 18? NUM_COLOR_ATTACHMENTS = 8, }; enum REGADDR { VGT_PRIMITIVE_TYPE = 0x2256, // each stage has 12 sets of 4 border color registers TD_PS_SAMPLER0_BORDER_RED = 0x2900, TD_PS_SAMPLER0_BORDER_GREEN = 0x2901, TD_PS_SAMPLER0_BORDER_BLUE = 0x2902, TD_PS_SAMPLER0_BORDER_ALPHA = 0x2903, TD_VS_SAMPLER0_BORDER_RED = 0x2980, TD_VS_SAMPLER0_BORDER_GREEN = 0x2981, TD_VS_SAMPLER0_BORDER_BLUE = 0x2982, TD_VS_SAMPLER0_BORDER_ALPHA = 0x2983, TD_GS_SAMPLER0_BORDER_RED = 0x2A00, TD_GS_SAMPLER0_BORDER_GREEN = 0x2A01, TD_GS_SAMPLER0_BORDER_BLUE = 0x2A02, TD_GS_SAMPLER0_BORDER_ALPHA = 0x2A03, DB_STENCIL_CLEAR = 0xA00A, DB_DEPTH_CLEAR = 0xA00B, CB_TARGET_MASK = 0xA08E, PA_SC_GENERIC_SCISSOR_TL = 0xA090, PA_SC_GENERIC_SCISSOR_BR = 0xA091, SQ_VTX_SEMANTIC_0 = 0xA0E0, SQ_VTX_SEMANTIC_31 = 0xA0FF, VGT_MULTI_PRIM_IB_RESET_INDX = 0xA103, SX_ALPHA_TEST_CONTROL = 0xA104, CB_BLEND_RED = 0xA105, CB_BLEND_GREEN = 0xA106, CB_BLEND_BLUE = 0xA107, CB_BLEND_ALPHA = 0xA108, DB_STENCILREFMASK = 0xA10C, DB_STENCILREFMASK_BF = 0xA10D, SX_ALPHA_REF = 0xA10E, PA_CL_VPORT_XSCALE = 0xA10F, PA_CL_VPORT_XOFFSET = 0xA110, PA_CL_VPORT_YSCALE = 0xA111, PA_CL_VPORT_YOFFSET = 0xA112, PA_CL_VPORT_ZSCALE = 0xA113, PA_CL_VPORT_ZOFFSET = 0xA114, SPI_VS_OUT_ID_0 = 0xA185, SPI_VS_OUT_CONFIG = 0xA1B1, CB_BLEND0_CONTROL = 0xA1E0, // first CB_BLEND7_CONTROL = 0xA1E7, // last DB_DEPTH_CONTROL = 0xA200, CB_COLOR_CONTROL = 0xA202, PA_CL_CLIP_CNTL = 0xA204, PA_SU_SC_MODE_CNTL = 0xA205, PA_CL_VTE_CNTL = 0xA206, PA_CL_VS_OUT_CNTL = 0xA207, // shader program descriptors: SQ_PGM_START_PS = 0xA210, SQ_PGM_RESOURCES_PS = 0xA214, SQ_PGM_EXPORTS_PS = 0xA215, SQ_PGM_START_VS = 0xA216, SQ_PGM_RESOURCES_VS = 0xA21A, SQ_PGM_START_GS = 0xA21B, SQ_PGM_RESOURCES_GS = 0xA21F, SQ_PGM_START_ES = 0xA220, SQ_PGM_RESOURCES_ES = 0xA224, SQ_PGM_START_FS = 0xA225, SQ_PGM_RESOURCES_FS = 0xA229, SQ_VTX_SEMANTIC_CLEAR = 0xA238, PA_SU_POINT_SIZE = 0xA280, PA_SU_POINT_MINMAX = 0xA281, VGT_GS_MODE = 0xA290, VGT_DMA_INDEX_TYPE = 0xA29F, // todo - verify offset VGT_PRIMITIVEID_EN = 0xA2A1, VGT_DMA_NUM_INSTANCES = 0xA2A2, VGT_MULTI_PRIM_IB_RESET_EN = 0xA2A5, VGT_INSTANCE_STEP_RATE_0 = 0xA2A8, VGT_INSTANCE_STEP_RATE_1 = 0xA2A9, VGT_STRMOUT_BUFFER_SIZE_0 = 0xA2B4, VGT_STRMOUT_VTX_STRIDE_0 = 0xA2B5, VGT_STRMOUT_BUFFER_BASE_0 = 0xA2B6, VGT_STRMOUT_BUFFER_OFFSET_0 = 0xA2B7, VGT_STRMOUT_BUFFER_SIZE_1 = 0xA2B8, VGT_STRMOUT_VTX_STRIDE_1 = 0xA2B9, VGT_STRMOUT_BUFFER_BASE_1 = 0xA2BA, VGT_STRMOUT_BUFFER_OFFSET_1 = 0xA2BB, VGT_STRMOUT_BUFFER_SIZE_2 = 0xA2BC, VGT_STRMOUT_VTX_STRIDE_2 = 0xA2BD, VGT_STRMOUT_BUFFER_BASE_2 = 0xA2BE, VGT_STRMOUT_BUFFER_OFFSET_2 = 0xA2BF, VGT_STRMOUT_BUFFER_SIZE_3 = 0xA2C0, VGT_STRMOUT_VTX_STRIDE_3 = 0xA2C1, VGT_STRMOUT_BUFFER_BASE_3 = 0xA2C2, VGT_STRMOUT_BUFFER_OFFSET_3 = 0xA2C3, VGT_STRMOUT_BASE_OFFSET_0 = 0xA2C4, VGT_STRMOUT_BASE_OFFSET_1 = 0xA2C5, VGT_STRMOUT_BASE_OFFSET_2 = 0xA2C6, VGT_STRMOUT_BASE_OFFSET_3 = 0xA2C7, VGT_STRMOUT_BUFFER_EN = 0xA2C8, // HiZ early stencil test? DB_SRESULTS_COMPARE_STATE0 = 0xA34A, DB_SRESULTS_COMPARE_STATE1 = 0xA34B, PA_SU_POLY_OFFSET_CLAMP = 0xA37F, PA_SU_POLY_OFFSET_FRONT_SCALE = 0xA380, PA_SU_POLY_OFFSET_FRONT_OFFSET = 0xA381, PA_SU_POLY_OFFSET_BACK_SCALE = 0xA382, PA_SU_POLY_OFFSET_BACK_OFFSET = 0xA383, // texture units SQ_TEX_RESOURCE_WORD0_N_PS = 0xE000, SQ_TEX_RESOURCE_WORD0_N_VS = 0xE460, SQ_TEX_RESOURCE_WORD0_N_GS = 0xE930, SQ_TEX_RESOURCE_WORD_FIRST = SQ_TEX_RESOURCE_WORD0_N_PS, SQ_TEX_RESOURCE_WORD_LAST = (SQ_TEX_RESOURCE_WORD0_N_GS + GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7 - 1), // there are 54 samplers with 3 registers each. 18 (actually only 16?) per stage. For stage indices see SAMPLER_BASE_INDEX_* SQ_TEX_SAMPLER_WORD0_0 = 0xF000, SQ_TEX_SAMPLER_WORD1_0 = 0xF001, SQ_TEX_SAMPLER_WORD2_0 = 0xF002, }; inline constexpr int SAMPLER_BASE_INDEX_PIXEL = 0; inline constexpr int SAMPLER_BASE_INDEX_VERTEX = 18; inline constexpr int SAMPLER_BASE_INDEX_GEOMETRY = 36; #define LATTE_BITFIELD(__regname, __bitIndex, __bitWidth) \ auto& set_##__regname(uint32 newValue) \ { \ cemu_assert_debug(newValue < (1u << (__bitWidth))); \ v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ v |= (newValue << (__bitIndex)); \ return *this; \ } \ uint32 get_##__regname() const \ { \ return (v >> (__bitIndex))&((1u << (__bitWidth)) - 1u); \ } #define LATTE_BITFIELD_SIGNED(__regname, __bitIndex, __bitWidth) \ auto& set_##__regname(sint32 newValue) \ { \ cemu_assert_debug(newValue < (1 << ((__bitWidth)-1))); \ cemu_assert_debug(newValue >= -(1 << ((__bitWidth)-1))); \ v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ v |= (((uint32)newValue & ((1u << (__bitWidth)) - 1u)) << (__bitIndex)); \ return *this; \ } \ sint32 get_##__regname() const \ { \ sint32 r = (v >> (__bitIndex))&((1u << (__bitWidth)) - 1u); \ r = (r << (32 - (__bitWidth))); \ r = (r >> (32 - (__bitWidth))); \ return r; \ } #define LATTE_BITFIELD_BOOL(__regname, __bitIndex) \ auto& set_##__regname(bool newValue) \ { \ if(newValue) \ v |= (1u << (__bitIndex)); \ else \ v &= ~(1u << (__bitIndex)); \ return *this; \ } \ bool get_##__regname() const \ { \ return (v&(1u << (__bitIndex))) != 0; \ } #define LATTE_BITFIELD_TYPED(__regname, __bitIndex, __bitWidth, __typename) \ auto& set_##__regname(__typename newValue) \ { \ cemu_assert_debug(static_cast<uint32>(newValue) < (1u << (__bitWidth))); \ v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ v |= (static_cast<uint32>(newValue) << (__bitIndex)); \ return *this; \ } \ __typename get_##__regname() const \ { \ return static_cast<__typename>((v >> (__bitIndex))&((1u << (__bitWidth)) - 1u)); \ } #define LATTE_BITFIELD_FULL_TYPED(__regname, __typename) \ auto& set_##__regname(__typename newValue) \ { \ v = (static_cast<uint32>(newValue)); \ return *this; \ } \ __typename get_##__regname() const \ { \ return static_cast<__typename>(v); \ } #define LATTE_BITFIELD_FLOAT(__regname) \ auto& set_##__regname(float newValue) \ { \ *(float*)&v = newValue; \ return *this; \ } \ float get_##__regname() const \ { \ return *(float*)&v; \ } class LATTEREG { public: uint32 getRawValue() const { return v; } uint32 getRawValueBE() const { return _swapEndianU32(v); } protected: uint32 v{}; }; // shared enums enum class E_COMPAREFUNC // used by depth test func and alpha test func { NEVER, LESS, EQUAL, LEQUAL, GREATER, NOTEQUAL, GEQUAL, ALWAYS }; enum class E_ENDIAN_SWAP { SWAP_NONE = 0, }; struct LATTE_VGT_PRIMITIVE_TYPE : LATTEREG // 0x2256 { enum class E_PRIMITIVE_TYPE { NONE = 0x0, POINTS = 0x1, LINES = 0x2, LINE_STRIP = 0x3, TRIANGLES = 0x4, TRIANGLE_FAN = 0x5, TRIANGLE_STRIP = 0x6, LINES_ADJACENT = 0xA, LINE_STRIP_ADJACENT = 0xB, TRIANGLES_ADJACENT = 0xC, TRIANGLE_STRIP_ADJACENT = 0xD, RECTS = 0x11, LINE_LOOP = 0x12, QUADS = 0x13, QUAD_STRIP = 0x14, }; LATTE_BITFIELD_FULL_TYPED(PRIMITIVE_MODE, E_PRIMITIVE_TYPE); }; struct LATTE_TD_BORDER_COLOR : LATTEREG // 0x2900 - 0x2A47 { LATTE_BITFIELD_FLOAT(channelValue); }; struct LATTE_DB_STENCIL_CLEAR : LATTEREG // 0xA00A { LATTE_BITFIELD(clearValue, 0, 8); }; struct LATTE_DB_DEPTH_CLEAR : LATTEREG // 0xA00B { LATTE_BITFIELD_FLOAT(clearValue); }; struct LATTE_CB_TARGET_MASK : LATTEREG // 0xA08E { LATTE_BITFIELD_FULL_TYPED(MASK, uint32); }; struct LATTE_PA_SC_GENERIC_SCISSOR_TL : LATTEREG // 0xA090 { LATTE_BITFIELD(TL_X, 0, 15); LATTE_BITFIELD(TL_Y, 16, 15); LATTE_BITFIELD_BOOL(WINDOW_OFFSET_DISABLE, 31); }; struct LATTE_PA_SC_GENERIC_SCISSOR_BR : LATTEREG // 0xA091 { LATTE_BITFIELD(BR_X, 0, 15); LATTE_BITFIELD(BR_Y, 16, 15); }; struct LATTE_VGT_MULTI_PRIM_IB_RESET_INDX : LATTEREG // 0xA103 { LATTE_BITFIELD_FULL_TYPED(RESTART_INDEX, uint32); }; struct LATTE_SX_ALPHA_TEST_CONTROL : LATTEREG // 0xA104 { using E_ALPHA_FUNC = E_COMPAREFUNC; LATTE_BITFIELD_TYPED(ALPHA_FUNC, 0, 3, E_ALPHA_FUNC); LATTE_BITFIELD_BOOL(ALPHA_TEST_ENABLE, 3); LATTE_BITFIELD_BOOL(ALPHA_TEST_BYPASS, 8); }; struct LATTE_CB_BLEND_RED : LATTEREG // 0xA105 { LATTE_BITFIELD_FLOAT(RED); }; struct LATTE_CB_BLEND_GREEN : LATTEREG // 0xA106 { LATTE_BITFIELD_FLOAT(GREEN); }; struct LATTE_CB_BLEND_BLUE : LATTEREG // 0xA107 { LATTE_BITFIELD_FLOAT(BLUE); }; struct LATTE_CB_BLEND_ALPHA : LATTEREG // 0xA108 { LATTE_BITFIELD_FLOAT(ALPHA); }; struct LATTE_DB_STENCILREFMASK : LATTEREG // 0xA10C { LATTE_BITFIELD(STENCILREF_F, 0, 8); LATTE_BITFIELD(STENCILMASK_F, 8, 8); LATTE_BITFIELD(STENCILWRITEMASK_F, 16, 8); }; struct LATTE_DB_STENCILREFMASK_BF : LATTEREG // 0xA10D { LATTE_BITFIELD(STENCILREF_B, 0, 8); LATTE_BITFIELD(STENCILMASK_B, 8, 8); LATTE_BITFIELD(STENCILWRITEMASK_B, 16, 8); }; struct LATTE_SX_ALPHA_REF : LATTEREG // 0xA10E { LATTE_BITFIELD_FLOAT(ALPHA_TEST_REF); }; struct LATTE_PA_CL_VPORT_XSCALE : LATTEREG // 0xA10F { LATTE_BITFIELD_FLOAT(SCALE); }; struct LATTE_PA_CL_VPORT_XOFFSET : LATTEREG // 0xA110 { LATTE_BITFIELD_FLOAT(OFFSET); }; struct LATTE_PA_CL_VPORT_YSCALE : LATTEREG // 0xA111 { LATTE_BITFIELD_FLOAT(SCALE); }; struct LATTE_PA_CL_VPORT_YOFFSET : LATTEREG // 0xA112 { LATTE_BITFIELD_FLOAT(OFFSET); }; struct LATTE_PA_CL_VPORT_ZSCALE : LATTEREG // 0xA113 { LATTE_BITFIELD_FLOAT(SCALE); }; struct LATTE_PA_CL_VPORT_ZOFFSET : LATTEREG // 0xA114 { LATTE_BITFIELD_FLOAT(OFFSET); }; struct LATTE_CB_BLENDN_CONTROL : LATTEREG // 0xA1E0 - 0xA1E7 { enum class E_BLENDFACTOR { BLEND_ZERO = 0x00, BLEND_ONE = 0x01, BLEND_SRC_COLOR = 0x02, BLEND_ONE_MINUS_SRC_COLOR = 0x03, BLEND_SRC_ALPHA = 0x04, BLEND_ONE_MINUS_SRC_ALPHA = 0x05, BLEND_DST_ALPHA = 0x06, BLEND_ONE_MINUS_DST_ALPHA = 0x07, BLEND_DST_COLOR = 0x08, BLEND_ONE_MINUS_DST_COLOR = 0x09, BLEND_SRC_ALPHA_SATURATE = 0x0A, BLEND_BOTH_SRC_ALPHA = 0x0B, BLEND_BOTH_INV_SRC_ALPHA = 0x0C, BLEND_CONST_COLOR = 0x0D, BLEND_ONE_MINUS_CONST_COLOR = 0x0E, BLEND_SRC1_COLOR = 0x0F, BLEND_INV_SRC1_COLOR = 0x10, BLEND_SRC1_ALPHA = 0x11, BLEND_INV_SRC1_ALPHA = 0x12, BLEND_CONST_ALPHA = 0x13, BLEND_ONE_MINUS_CONST_ALPHA = 0x14 }; enum class E_COMBINEFUNC { DST_PLUS_SRC = 0, SRC_MINUS_DST = 1, MIN_DST_SRC = 2, MAX_DST_SRC = 3, DST_MINUS_SRC = 4 }; LATTE_BITFIELD_TYPED(COLOR_SRCBLEND, 0, 5, E_BLENDFACTOR); LATTE_BITFIELD_TYPED(COLOR_COMB_FCN, 5, 3, E_COMBINEFUNC); LATTE_BITFIELD_TYPED(COLOR_DSTBLEND, 8, 5, E_BLENDFACTOR); LATTE_BITFIELD_BOOL(OPACITY_WEIGHT, 13); LATTE_BITFIELD_TYPED(ALPHA_SRCBLEND, 16, 5, E_BLENDFACTOR); LATTE_BITFIELD_TYPED(ALPHA_COMB_FCN, 21, 3, E_COMBINEFUNC); LATTE_BITFIELD_TYPED(ALPHA_DSTBLEND, 24, 5, E_BLENDFACTOR); LATTE_BITFIELD_BOOL(SEPARATE_ALPHA_BLEND, 29); }; struct LATTE_DB_DEPTH_CONTROL : LATTEREG // 0xA200 { using E_ZFUNC = E_COMPAREFUNC; using E_STENCILFUNC = E_COMPAREFUNC; enum class E_STENCILACTION { KEEP, ZERO, REPLACE, INCR, DECR, INVERT, INCR_WRAP, DECR_WRAP, }; LATTE_BITFIELD_BOOL(STENCIL_ENABLE, 0); LATTE_BITFIELD_BOOL(Z_ENABLE, 1); LATTE_BITFIELD_BOOL(Z_WRITE_ENABLE, 2); // bit 3 is unused? LATTE_BITFIELD_TYPED(Z_FUNC, 4, 3, E_ZFUNC); LATTE_BITFIELD_BOOL(BACK_STENCIL_ENABLE, 7); LATTE_BITFIELD_TYPED(STENCIL_FUNC_F, 8, 3, E_STENCILFUNC); LATTE_BITFIELD_TYPED(STENCIL_FAIL_F, 11, 3, E_STENCILACTION); LATTE_BITFIELD_TYPED(STENCIL_ZPASS_F, 14, 3, E_STENCILACTION); LATTE_BITFIELD_TYPED(STENCIL_ZFAIL_F, 17, 3, E_STENCILACTION); LATTE_BITFIELD_TYPED(STENCIL_FUNC_B, 20, 3, E_STENCILFUNC); LATTE_BITFIELD_TYPED(STENCIL_FAIL_B, 23, 3, E_STENCILACTION); LATTE_BITFIELD_TYPED(STENCIL_ZPASS_B, 26, 3, E_STENCILACTION); LATTE_BITFIELD_TYPED(STENCIL_ZFAIL_B, 29, 3, E_STENCILACTION); }; struct LATTE_CB_COLOR_CONTROL : LATTEREG // 0xA202 { enum class E_SPECIALOP { NORMAL = 0, // use state to render DISABLE = 1, // dont write color results }; enum class E_LOGICOP { CLEAR = 0x00, COPY = 0xCC, OR = 0xEE, SET = 0xFF, }; LATTE_BITFIELD_BOOL(FOG_ENABLE, 0); LATTE_BITFIELD_BOOL(MULTIWRITE_ENABLE, 1); LATTE_BITFIELD_BOOL(DITHER_ENABLE, 2); LATTE_BITFIELD_BOOL(DEGAMMA_ENABLE, 3); LATTE_BITFIELD_TYPED(SPECIAL_OP, 4, 3, E_SPECIALOP); LATTE_BITFIELD_BOOL(PER_MRT_BLEND, 7); LATTE_BITFIELD(BLEND_MASK, 8, 8); LATTE_BITFIELD_TYPED(ROP, 16, 8, E_LOGICOP); // aka logic op }; struct LATTE_PA_CL_CLIP_CNTL : LATTEREG // 0xA204 { // todo - other fields // see R6xx_3D_Registers.pdf LATTE_BITFIELD_BOOL(CLIP_DISABLE, 16); LATTE_BITFIELD_BOOL(DX_CLIP_SPACE_DEF, 19); // GX2 calls this flag HalfZ LATTE_BITFIELD_BOOL(DX_RASTERIZATION_KILL, 22); LATTE_BITFIELD_BOOL(DX_LINEAR_ATTR_CLIP_ENA, 24); // what does this do? LATTE_BITFIELD_BOOL(ZCLIP_NEAR_DISABLE, 26); LATTE_BITFIELD_BOOL(ZCLIP_FAR_DISABLE, 27); }; struct LATTE_PA_CL_VTE_CNTL : LATTEREG // 0xA206 { LATTE_BITFIELD_BOOL(VPORT_X_SCALE_ENA, 0); LATTE_BITFIELD_BOOL(VPORT_X_OFFSET_ENA, 1); LATTE_BITFIELD_BOOL(VPORT_Y_SCALE_ENA, 2); LATTE_BITFIELD_BOOL(VPORT_Y_OFFSET_ENA, 3); LATTE_BITFIELD_BOOL(VPORT_Z_SCALE_ENA, 4); LATTE_BITFIELD_BOOL(VPORT_Z_OFFSET_ENA, 5); LATTE_BITFIELD_BOOL(VTX_XY_FMT, 8); LATTE_BITFIELD_BOOL(VTX_Z_FMT, 9); LATTE_BITFIELD_BOOL(VTX_W0_FMT, 10); }; struct LATTE_PA_CL_VS_OUT_CNTL : LATTEREG // 0xA207 { LATTE_BITFIELD(CLIP_DIST_ENA_MASK, 0, 8); LATTE_BITFIELD(CULL_DIST_ENA_MASK, 8, 8); }; struct LATTE_PA_SU_POINT_SIZE : LATTEREG // 0xA280 { LATTE_BITFIELD(HEIGHT, 0, 16); LATTE_BITFIELD(WIDTH, 16, 16); }; struct LATTE_PA_SU_POINT_MINMAX : LATTEREG // 0xA281 { LATTE_BITFIELD(MIN_SIZE, 0, 16); LATTE_BITFIELD(MAX_SIZE, 16, 16); }; struct LATTE_VGT_GS_MODE : LATTEREG // 0xA290 { enum class E_MODE { OFF = 0, SCENARIO_A = 1, SCENARIO_B = 2, SCENARIO_G = 3 }; enum class E_CUT_MODE { CUT_1024 = 0, CUT_512 = 1, CUT_256 = 2, CUT_128 = 3, }; enum class E_COMPUTE_MODE { OFF = 0, UKN_1 = 1, UKN_2 = 2, ON = 3, }; LATTE_BITFIELD_TYPED(MODE, 0, 2, E_MODE); LATTE_BITFIELD_BOOL(ES_PASSTHRU, 2); LATTE_BITFIELD_TYPED(CUT_MODE, 3, 2, E_CUT_MODE); LATTE_BITFIELD_TYPED(COMPUTE_MODE, 14, 2, E_COMPUTE_MODE); LATTE_BITFIELD_BOOL(PARTIAL_THD_AT_EOI, 17); }; struct LATTE_VGT_DMA_INDEX_TYPE : LATTEREG // 0xA29F { enum class E_INDEX_TYPE { U16_LE = 0, // U16 U32_LE = 1, // U32 U16_BE = 4, // U16 + SwapU16 U32_BE = 9, // U32 + SwapU32 // index 0 -> U16 // index 1 -> U32 // bit 0x2 -> swap U16 // bit 0x4 -> swap U32 // bit 0x8 -> swap U32 (machine word?) AUTO = 0xFFFF, // helper value for tracking auto-generated indices. Not part of the actual enum }; LATTE_BITFIELD_FULL_TYPED(INDEX_TYPE, E_INDEX_TYPE); }; struct LATTE_VGT_PRIMITIVEID_EN : LATTEREG // 0xA2A1 { LATTE_BITFIELD_BOOL(PRIMITIVEID_EN, 0); }; struct LATTE_VGT_DMA_NUM_INSTANCES : LATTEREG // 0xA2A2 { LATTE_BITFIELD_FULL_TYPED(NUM_INSTANCES, uint32); }; struct LATTE_VGT_MULTI_PRIM_IB_RESET_EN : LATTEREG // 0xA2A5 { LATTE_BITFIELD_BOOL(RESET_EN, 0); }; struct LATTE_VGT_INSTANCE_STEP_RATE_X : LATTEREG // 0xA2A8-0xA2A9 { LATTE_BITFIELD_FULL_TYPED(STEP_RATE, uint32); }; struct LATTE_VGT_STRMOUT_BUFFER_SIZE_X : LATTEREG // 0xA2B4 + index * 4 { LATTE_BITFIELD_FULL_TYPED(SIZE, uint32); }; struct LATTE_VGT_STRMOUT_STRIDE_X : LATTEREG // 0xA2B5 + index * 4 { LATTE_BITFIELD_FULL_TYPED(STRIDE, uint32); }; struct LATTE_VGT_STRMOUT_BUFFER_BASE_X : LATTEREG // 0xA2B6 + index * 4 { LATTE_BITFIELD_FULL_TYPED(BASE, uint32); }; struct LATTE_VGT_STRMOUT_BUFFER_OFFSET_X : LATTEREG // 0xA2B7 + index * 4 { LATTE_BITFIELD_FULL_TYPED(BUFFER_OFFSET, uint32); }; struct LATTE_VGT_STRMOUT_BASE_OFFSET_X : LATTEREG // 0xA2C4-0xA2C7 { LATTE_BITFIELD_FULL_TYPED(BASE_OFFSET, uint32); }; struct LATTE_VGT_STRMOUT_BUFFER_EN : LATTEREG // 0xA2C8 { LATTE_BITFIELD_BOOL(BUFFER_ENABLE_0, 0); LATTE_BITFIELD_BOOL(BUFFER_ENABLE_1, 1); LATTE_BITFIELD_BOOL(BUFFER_ENABLE_2, 2); LATTE_BITFIELD_BOOL(BUFFER_ENABLE_3, 3); }; struct LATTE_PA_SU_POLY_OFFSET_CLAMP : LATTEREG // 0xA37F { LATTE_BITFIELD_FLOAT(CLAMP); }; struct LATTE_PA_SU_POLY_OFFSET_FRONT_SCALE : LATTEREG // 0xA380 { LATTE_BITFIELD_FLOAT(SCALE); }; struct LATTE_PA_SU_POLY_OFFSET_FRONT_OFFSET : LATTEREG // 0xA381 { LATTE_BITFIELD_FLOAT(OFFSET); }; struct LATTE_PA_SU_POLY_OFFSET_BACK_SCALE : LATTEREG // 0xA382 { LATTE_BITFIELD_FLOAT(SCALE); }; struct LATTE_PA_SU_POLY_OFFSET_BACK_OFFSET : LATTEREG // 0xA383 { LATTE_BITFIELD_FLOAT(OFFSET); }; struct LATTE_SQ_VTX_SEMANTIC_CLEAR : LATTEREG // 0xA238 { LATTE_BITFIELD_FULL_TYPED(CLEAR_MASK, uint32); // probably a bitmask }; struct LATTE_SQ_VTX_SEMANTIC_X : LATTEREG // 0xA0E0 - 0xA0FF { LATTE_BITFIELD(SEMANTIC_ID, 0, 8); }; struct LATTE_SQ_TEX_RESOURCE_WORD0_N : LATTEREG // 0xE000 + index * 7 { LATTE_BITFIELD_TYPED(DIM, 0, 3, E_DIM); LATTE_BITFIELD_TYPED(TILE_MODE, 3, 4, E_HWTILEMODE); LATTE_BITFIELD_BOOL(TILE_TYPE, 7); LATTE_BITFIELD(PITCH, 8, 11); LATTE_BITFIELD(WIDTH, 19, 13); }; struct LATTE_SQ_TEX_RESOURCE_WORD1_N : LATTEREG // 0xE001 + index * 7 { LATTE_BITFIELD(HEIGHT, 0, 13); LATTE_BITFIELD(DEPTH, 13, 13); LATTE_BITFIELD_TYPED(DATA_FORMAT, 26, 6, E_HWSURFFMT); }; struct LATTE_SQ_TEX_RESOURCE_WORD2_N : LATTEREG // 0xE002 + index * 7 { LATTE_BITFIELD_FULL_TYPED(BASE_ADDRESS, uint32); }; struct LATTE_SQ_TEX_RESOURCE_WORD3_N : LATTEREG // 0xE003 + index * 7 { LATTE_BITFIELD_FULL_TYPED(MIP_ADDRESS, uint32); }; struct LATTE_SQ_TEX_RESOURCE_WORD4_N : LATTEREG // 0xE004 + index * 7 { enum class E_FORMAT_COMP { COMP_UNSIGNED = 0, COMP_SIGNED = 1, COMP_UNSIGNED_BIASED = 2, }; enum class E_NUM_FORMAT_ALL { NUM_FORMAT_NORM = 0, NUM_FORMAT_INT = 1, NUM_FORMAT_SCALED = 2, }; enum class E_SRF_MODE_ALL { SRF_MODE_ZERO_CLAMP_MINUS_ONE = 0, SRF_MODE_NO_ZERO = 1, }; // using E_ENDIAN_SWAP = E_ENDIAN_SWAP; enum class E_SEL { SEL_X = 0, SEL_Y = 1, SEL_Z = 2, SEL_W = 3, SEL_0 = 4, SEL_1 = 5 }; LATTE_BITFIELD_TYPED(FORMAT_COMP_X, 0, 2, E_FORMAT_COMP); LATTE_BITFIELD_TYPED(FORMAT_COMP_Y, 2, 2, E_FORMAT_COMP); LATTE_BITFIELD_TYPED(FORMAT_COMP_Z, 4, 2, E_FORMAT_COMP); LATTE_BITFIELD_TYPED(FORMAT_COMP_W, 6, 2, E_FORMAT_COMP); LATTE_BITFIELD_TYPED(NUM_FORM_ALL, 8, 2, E_NUM_FORMAT_ALL); LATTE_BITFIELD_TYPED(SRF_MODE_ALL, 10, 1, E_SRF_MODE_ALL); LATTE_BITFIELD_BOOL(FORCE_DEGAMMA, 11); LATTE_BITFIELD_TYPED(ENDIAN_SWAP, 12, 2, E_ENDIAN_SWAP); LATTE_BITFIELD(REQUEST_SIZE, 14, 2); LATTE_BITFIELD_TYPED(DST_SEL_X, 16, 3, E_SEL); LATTE_BITFIELD_TYPED(DST_SEL_Y, 19, 3, E_SEL); LATTE_BITFIELD_TYPED(DST_SEL_Z, 22, 3, E_SEL); LATTE_BITFIELD_TYPED(DST_SEL_W, 25, 3, E_SEL); LATTE_BITFIELD(BASE_LEVEL, 28, 4); }; struct LATTE_SQ_TEX_RESOURCE_WORD5_N : LATTEREG // 0xE005 + index * 7 { LATTE_BITFIELD(LAST_LEVEL, 0, 4); // for MSAA textures, this stores the AA level LATTE_BITFIELD(BASE_ARRAY, 4, 13); LATTE_BITFIELD(LAST_ARRAY, 17, 13); LATTE_BITFIELD_BOOL(UKN_BIT_30, 30); // may be a 2 bit value? }; struct LATTE_SQ_TEX_RESOURCE_WORD6_N : LATTEREG // 0xE006 + index * 7 { enum class E_MPEG_CLAMP { UKN = 0, }; enum class E_TYPE { VTX_INVALID_TEXTURE = 0, VTX_INVALID_BUFFER = 1, VTX_VALID_TEXTURE = 2, VTX_VALID_BUFFER = 3, }; // unsure if these are correct LATTE_BITFIELD_TYPED(MPEG_CLAMP, 0, 2, E_MPEG_CLAMP); LATTE_BITFIELD(MAX_ANISO, 2, 3); LATTE_BITFIELD(PERF_MODULATION, 5, 3); LATTE_BITFIELD_BOOL(INTERLACED, 8); LATTE_BITFIELD_TYPED(TYPE, 30, 2, E_TYPE); }; struct LATTE_SQ_TEX_SAMPLER_WORD0_0 : LATTEREG // 0xF000+n*3 - 0xF??? { enum class E_CLAMP { WRAP = 0, MIRROR = 1, CLAMP_LAST_TEXEL = 2, MIRROR_ONCE_LAST_TEXEL = 3, CLAMP_HALF_BORDER = 4, MIRROR_ONCE_HALF_BORDER = 5, CLAMP_BORDER = 6, MIRROR_ONCE_BORDER = 7, }; enum class E_XY_FILTER { POINT = 0, BILINEAR = 1, BICUBIC = 2, // 3 unused ? ANISO_POINT = 4, ANISO_BILINEAR = 5, // 6, 7 unused ? }; enum class E_Z_FILTER { NONE = 0, POINT = 1, LINEAR = 2, // 3 is unused ? }; enum class E_BORDER_COLOR_TYPE { TRANSPARENT_BLACK = 0, OPAQUE_BLACK = 1, OPAQUE_WHITE = 2, REGISTER = 3, }; enum class E_DEPTH_COMPARE { NEVER = 0, LESS = 1, EQUAL = 2, LEQUAL = 3, GREATER = 4, NOTEQUAL = 5, GEQUAL = 6, ALWAYS = 7 }; enum class E_CHROMA_KEY { DISABLE = 0, KILL = 1, BLEND = 2, // 3 is unused }; LATTE_BITFIELD_TYPED(CLAMP_X, 0, 3, E_CLAMP); LATTE_BITFIELD_TYPED(CLAMP_Y, 3, 3, E_CLAMP); LATTE_BITFIELD_TYPED(CLAMP_Z, 6, 3, E_CLAMP); LATTE_BITFIELD_TYPED(XY_MAG_FILTER, 9, 3, E_XY_FILTER); LATTE_BITFIELD_TYPED(XY_MIN_FILTER, 12, 3, E_XY_FILTER); LATTE_BITFIELD_TYPED(Z_FILTER, 15, 2, E_Z_FILTER); LATTE_BITFIELD_TYPED(MIP_FILTER, 17, 2, E_Z_FILTER); LATTE_BITFIELD(MAX_ANISO_RATIO, 19, 3); LATTE_BITFIELD_TYPED(BORDER_COLOR_TYPE, 22, 2, E_BORDER_COLOR_TYPE); LATTE_BITFIELD_BOOL(POINT_SAMPLING_CLAMP, 24); LATTE_BITFIELD_BOOL(TEX_ARRAY_OVERRIDE, 25); LATTE_BITFIELD_TYPED(DEPTH_COMPARE_FUNCTION, 26, 3, E_DEPTH_COMPARE); LATTE_BITFIELD_TYPED(CHROMA_KEY, 28, 2, E_CHROMA_KEY); LATTE_BITFIELD_BOOL(LOD_USES_MINOR_AXIS, 25); }; struct LATTE_SQ_TEX_SAMPLER_WORD1_0 : LATTEREG // 0xF001+n*3 - 0xF??? { LATTE_BITFIELD(MIN_LOD, 0, 10); LATTE_BITFIELD(MAX_LOD, 10, 10); LATTE_BITFIELD_SIGNED(LOD_BIAS, 20, 12); }; struct LATTE_SQ_TEX_SAMPLER_WORD2_0 : LATTEREG // 0xF002+n*3 - 0xF??? { enum E_SAMPLER_TYPE { UKN0 = 0, UKN1 = 1, }; LATTE_BITFIELD(LOD_BIAS_SECONDARY, 0, 12); LATTE_BITFIELD_BOOL(MC_COORD_TRUNCATE, 12); LATTE_BITFIELD_BOOL(FORCE_DEGAMMA, 13); LATTE_BITFIELD_BOOL(HIGH_PRECISION_FILTER, 14); // PERF_MIP at 15, 3 bits // PERF_Z at 18, 3 bits // bit 21 is unused? LATTE_BITFIELD(ANISO_BIAS, 22, 6); // is size correct? //LATTE_BITFIELD_BOOL(FETCH_4, 26); overlaps with ANISO_BIAS //LATTE_BITFIELD_BOOL(SAMPLE_IS_PCF, 27); overlaps with ANISO_BIAS LATTE_BITFIELD_TYPED(TYPE, 31, 1, E_SAMPLER_TYPE); }; struct LATTE_SQ_PGM_START_X : LATTEREG // 0xA210 / 0xA216 / 0xA21B / 0xA220 / 0xA225 { LATTE_BITFIELD_FULL_TYPED(PGM_START, uint32); }; struct LATTE_SQ_PGM_RESOURCES_PS : LATTEREG // 0xA214 { LATTE_BITFIELD(NUM_GPRS, 0, 8); LATTE_BITFIELD(NUM_STACK_ENTRIES, 8, 8); LATTE_BITFIELD_BOOL(DX10_CLAMP, 21); // if true, CLAMP modifier in shaders will return 0 for NaN LATTE_BITFIELD(FETCH_CACHE_LINES, 24, 3); LATTE_BITFIELD_BOOL(UNCACHED_FIRST_INST, 28); LATTE_BITFIELD_BOOL(CLAMP_CONSTS, 31); }; struct LATTE_SQ_PGM_RESOURCES_VS : LATTEREG // 0xA21A { LATTE_BITFIELD(NUM_GPRS, 0, 8); LATTE_BITFIELD(NUM_STACK_ENTRIES, 8, 8); LATTE_BITFIELD_BOOL(DX10_CLAMP, 21); // if true, CLAMP modifier in shaders will return 0 for NaN LATTE_BITFIELD(FETCH_CACHE_LINES, 24, 3); LATTE_BITFIELD_BOOL(UNCACHED_FIRST_INST, 28); }; struct LATTE_SQ_PGM_RESOURCES_GS : LATTEREG // 0xA21F { LATTE_BITFIELD(NUM_GPRS, 0, 8); LATTE_BITFIELD(NUM_STACK_ENTRIES, 8, 8); LATTE_BITFIELD_BOOL(DX10_CLAMP, 21); // if true, CLAMP modifier in shaders will return 0 for NaN }; struct LATTE_SQ_PGM_RESOURCES_ES : LATTEREG // 0xA224 { LATTE_BITFIELD(NUM_GPRS, 0, 8); LATTE_BITFIELD(NUM_STACK_ENTRIES, 8, 8); LATTE_BITFIELD_BOOL(DX10_CLAMP, 21); // if true, CLAMP modifier in shaders will return 0 for NaN }; struct LATTE_SQ_PGM_RESOURCES_FS : LATTEREG // 0xA229 { LATTE_BITFIELD(NUM_GPRS, 0, 8); LATTE_BITFIELD(NUM_STACK_ENTRIES, 8, 8); LATTE_BITFIELD_BOOL(DX10_CLAMP, 21); // if true, CLAMP modifier in shaders will return 0 for NaN }; struct LATTE_SQ_XX_ITEMSIZE : LATTEREG // 0xA227 - 0xA2XX { // used by: // SQ_ESGS_RING_ITEMSIZE // SQ_GSVS_RING_ITEMSIZE // SQ_ESTMP_RING_ITEMSIZE // SQ_GSTMP_RING_ITEMSIZE // SQ_VSTMP_RING_ITEMSIZE // SQ_PSTMP_RING_ITEMSIZE // SQ_FBUF_RING_ITEMSIZE // SQ_REDUC_RING_ITEMSIZE LATTE_BITFIELD(ITEMSIZE, 0, 15); }; struct LATTE_PA_SU_SC_MODE_CNTL : LATTEREG // 0xA205 { enum class E_FRONTFACE { CCW = 0, CW = 1 }; enum class E_POLYGONMODE { UKN0 = 0, // default - render triangles }; enum class E_PTYPE { POINTS = 0, LINES = 1, TRIANGLES = 2, }; LATTE_BITFIELD_BOOL(CULL_FRONT, 0); LATTE_BITFIELD_BOOL(CULL_BACK, 1); LATTE_BITFIELD_TYPED(FRONT_FACE, 2, 1, E_FRONTFACE); LATTE_BITFIELD_TYPED(POLYGON_MODE, 3, 2, E_POLYGONMODE); LATTE_BITFIELD_TYPED(FRONT_POLY_MODE, 5, 3, E_PTYPE); LATTE_BITFIELD_TYPED(BACK_POLY_MODE, 8, 3, E_PTYPE); LATTE_BITFIELD_BOOL(OFFSET_FRONT_ENABLED, 11); LATTE_BITFIELD_BOOL(OFFSET_BACK_ENABLED, 12); LATTE_BITFIELD_BOOL(OFFSET_PARA_ENABLED, 13); // offset enable for lines and points? // additional fields? }; struct LATTE_SPI_VS_OUT_CONFIG : LATTEREG // 0xA1B1 { LATTE_BITFIELD_BOOL(VS_PER_COMPONENT, 0); LATTE_BITFIELD(VS_EXPORT_COUNT, 1, 5); LATTE_BITFIELD_BOOL(EXPORTS_FOG, 8); LATTE_BITFIELD(VS_OUT_FOG_VEC_ADDR, 9, 5); }; struct LATTE_SPI_VS_OUT_ID_N : LATTEREG // 0xA185 - 0xA18E(?) - 0xA1B2 - 0xA1B3 { uint8 get_SEMANTIC(sint32 index) { cemu_assert_debug(index < 4); return (uint8)((v >> (index * 8)) & 0xFF); } void set_SEMANTIC(sint32 index, uint8 value) { cemu_assert_debug(index < 4); v &= ~(0xFF << (index * 8)); v |= (value & 0xFF) << (index * 8); } }; }; struct _LatteRegisterSetTextureUnit { Latte::LATTE_SQ_TEX_RESOURCE_WORD0_N word0; Latte::LATTE_SQ_TEX_RESOURCE_WORD1_N word1; Latte::LATTE_SQ_TEX_RESOURCE_WORD2_N word2; Latte::LATTE_SQ_TEX_RESOURCE_WORD3_N word3; Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N word4; Latte::LATTE_SQ_TEX_RESOURCE_WORD5_N word5; Latte::LATTE_SQ_TEX_RESOURCE_WORD6_N word6; }; static_assert(sizeof(_LatteRegisterSetTextureUnit) == 28); struct _LatteRegisterSetSampler { Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0 WORD0; Latte::LATTE_SQ_TEX_SAMPLER_WORD1_0 WORD1; Latte::LATTE_SQ_TEX_SAMPLER_WORD2_0 WORD2; }; static_assert(sizeof(_LatteRegisterSetSampler) == 12); struct _LatteRegisterSetSamplerBorderColor { Latte::LATTE_TD_BORDER_COLOR red; Latte::LATTE_TD_BORDER_COLOR green; Latte::LATTE_TD_BORDER_COLOR blue; Latte::LATTE_TD_BORDER_COLOR alpha; }; static_assert(sizeof(_LatteRegisterSetSamplerBorderColor) == 16); struct _LatteRegisterSetStreamoutBuffer { Latte::LATTE_VGT_STRMOUT_BUFFER_SIZE_X SIZE; Latte::LATTE_VGT_STRMOUT_STRIDE_X STRIDE; Latte::LATTE_VGT_STRMOUT_BUFFER_BASE_X BASE; Latte::LATTE_VGT_STRMOUT_BUFFER_OFFSET_X BUFFER_OFFSET; }; static_assert(sizeof(_LatteRegisterSetStreamoutBuffer) == 16); struct LatteContextRegister { uint8 padding0[0x08958]; /* +0x08958 */ Latte::LATTE_VGT_PRIMITIVE_TYPE VGT_PRIMITIVE_TYPE; uint8 padding5[0x0A400 - 0x0895C]; /* +0x0A400 */ _LatteRegisterSetSamplerBorderColor TD_PS_SAMPLER_BORDER_COLOR[Latte::GPU_LIMITS::NUM_SAMPLERS_PER_STAGE]; uint8 padding6[0x0A600 - 0x0A520]; /* +0x0A600 */ _LatteRegisterSetSamplerBorderColor TD_VS_SAMPLER_BORDER_COLOR[Latte::GPU_LIMITS::NUM_SAMPLERS_PER_STAGE]; uint8 padding7[0x0A800 - 0x0A720]; /* +0x0A800 */ _LatteRegisterSetSamplerBorderColor TD_GS_SAMPLER_BORDER_COLOR[Latte::GPU_LIMITS::NUM_SAMPLERS_PER_STAGE]; uint8 padding8[0x28238 - 0x0A920]; /* +0x28238 */ Latte::LATTE_CB_TARGET_MASK CB_TARGET_MASK; uint8 padding_2823C[4]; /* +0x28240 */ Latte::LATTE_PA_SC_GENERIC_SCISSOR_TL PA_SC_GENERIC_SCISSOR_TL; /* +0x28244 */ Latte::LATTE_PA_SC_GENERIC_SCISSOR_BR PA_SC_GENERIC_SCISSOR_BR; uint8 padding_28248[0x28380 - 0x28248]; /* +0x28380 */ Latte::LATTE_SQ_VTX_SEMANTIC_X SQ_VTX_SEMANTIC_X[32]; /* +0x28400 */ uint8 padding_28400[0x2840C - 0x28400]; /* +0x2840C */ Latte::LATTE_VGT_MULTI_PRIM_IB_RESET_INDX VGT_MULTI_PRIM_IB_RESET_INDX; /* +0x28410 */ Latte::LATTE_SX_ALPHA_TEST_CONTROL SX_ALPHA_TEST_CONTROL; /* +0x28414 */ Latte::LATTE_CB_BLEND_RED CB_BLEND_RED; /* +0x28418 */ Latte::LATTE_CB_BLEND_GREEN CB_BLEND_GREEN; /* +0x2841C */ Latte::LATTE_CB_BLEND_BLUE CB_BLEND_BLUE; /* +0x28420 */ Latte::LATTE_CB_BLEND_ALPHA CB_BLEND_ALPHA; uint8 padding_28424[0x28430 - 0x28424]; /* +0x28430 */ Latte::LATTE_DB_STENCILREFMASK DB_STENCILREFMASK; /* +0x28434 */ Latte::LATTE_DB_STENCILREFMASK_BF DB_STENCILREFMASK_BF; /* +0x28438 */ Latte::LATTE_SX_ALPHA_REF SX_ALPHA_REF; /* +0x2843C */ Latte::LATTE_PA_CL_VPORT_XSCALE PA_CL_VPORT_XSCALE; /* +0x28440 */ Latte::LATTE_PA_CL_VPORT_XOFFSET PA_CL_VPORT_XOFFSET; /* +0x28444 */ Latte::LATTE_PA_CL_VPORT_YSCALE PA_CL_VPORT_YSCALE; /* +0x28448 */ Latte::LATTE_PA_CL_VPORT_YOFFSET PA_CL_VPORT_YOFFSET; /* +0x2844C */ Latte::LATTE_PA_CL_VPORT_ZSCALE PA_CL_VPORT_ZSCALE; /* +0x28450 */ Latte::LATTE_PA_CL_VPORT_ZOFFSET PA_CL_VPORT_ZOFFSET; uint8 padding_28450[0x28614 - 0x28454]; /* +0x28614 */ Latte::LATTE_SPI_VS_OUT_ID_N LATTE_SPI_VS_OUT_ID_N[10]; uint8 padding_2863C[0x286C4 - 0x2863C]; /* +0x286C4 */ Latte::LATTE_SPI_VS_OUT_CONFIG SPI_VS_OUT_CONFIG; uint8 padding_286C8[0x28780 - 0x286C8]; /* +0x28780 */ Latte::LATTE_CB_BLENDN_CONTROL CB_BLENDN_CONTROL[8]; uint8 padding_287A0[0x28800 - 0x287A0]; /* +0x28800 */ Latte::LATTE_DB_DEPTH_CONTROL DB_DEPTH_CONTROL; uint8 padding_28804[4]; /* +0x28808 */ Latte::LATTE_CB_COLOR_CONTROL CB_COLOR_CONTROL; uint8 padding_2880C[4]; /* +0x28810 */ Latte::LATTE_PA_CL_CLIP_CNTL PA_CL_CLIP_CNTL; /* +0x28814 */ Latte::LATTE_PA_SU_SC_MODE_CNTL PA_SU_SC_MODE_CNTL; /* +0x28818 */ Latte::LATTE_PA_CL_VTE_CNTL PA_CL_VTE_CNTL; /* +0x2881C */ Latte::LATTE_PA_CL_VS_OUT_CNTL PA_CL_VS_OUT_CNTL; uint8 padding_2881C[0x28840 - 0x28820]; /* +0x28840 */ Latte::LATTE_SQ_PGM_START_X SQ_PGM_START_PS; /* +0x28844 */ uint32 ukn28844; // PS size /* +0x28848 */ uint32 ukn28848; /* +0x2884C */ uint32 ukn2884C; /* +0x28850 */ Latte::LATTE_SQ_PGM_RESOURCES_PS SQ_PGM_RESOURCES_PS; /* +0x28854 */ uint32 ukn28854; // SQ_PGM_EXPORTS_PS /* +0x28858 */ Latte::LATTE_SQ_PGM_START_X SQ_PGM_START_VS; /* +0x2885C */ uint32 ukn2885C; // VS size /* +0x28860 */ uint32 ukn28860; /* +0x28864 */ uint32 ukn28864; /* +0x28868 */ Latte::LATTE_SQ_PGM_RESOURCES_VS SQ_PGM_RESOURCES_VS; /* +0x2886C */ Latte::LATTE_SQ_PGM_START_X SQ_PGM_START_GS; /* +0x28870 */ uint32 ukn28870; // GS size /* +0x28874 */ uint32 ukn28874; /* +0x28878 */ uint32 ukn28878; /* +0x2887C */ Latte::LATTE_SQ_PGM_RESOURCES_GS SQ_PGM_RESOURCES_GS; /* +0x28880 */ Latte::LATTE_SQ_PGM_START_X SQ_PGM_START_ES; /* +0x28884 */ uint32 ukn28884; // ES size /* +0x28888 */ uint32 ukn28888; /* +0x2888C */ uint32 ukn2888C; /* +0x28890 */ Latte::LATTE_SQ_PGM_RESOURCES_ES SQ_PGM_RESOURCES_ES; /* +0x28894 */ Latte::LATTE_SQ_PGM_START_X SQ_PGM_START_FS; /* +0x28898 */ uint32 ukn28898; // FS size /* +0x2889C */ uint32 ukn2889C; /* +0x288A0 */ uint32 ukn288A0; /* +0x288A4 */ Latte::LATTE_SQ_PGM_RESOURCES_FS SQ_PGM_RESOURCES_FS; /* +0x288A8 */ Latte::LATTE_SQ_XX_ITEMSIZE SQ_ESGS_RING_ITEMSIZE; /* +0x288AC */ Latte::LATTE_SQ_XX_ITEMSIZE SQ_GSVS_RING_ITEMSIZE; /* +0x288B0 */ Latte::LATTE_SQ_XX_ITEMSIZE SQ_ESTMP_RING_ITEMSIZE; /* +0x288B4 */ Latte::LATTE_SQ_XX_ITEMSIZE SQ_GSTMP_RING_ITEMSIZE; /* +0x288B8 */ Latte::LATTE_SQ_XX_ITEMSIZE SQ_VSTMP_RING_ITEMSIZE; uint8 padding_288BC[0x288E0 - 0x288BC]; /* +0x288E0 */ Latte::LATTE_SQ_VTX_SEMANTIC_CLEAR SQ_VTX_SEMANTIC_CLEAR; uint8 padding_288E4[0x28A00 - 0x288E4]; /* +0x28A00 */ Latte::LATTE_PA_SU_POINT_SIZE PA_SU_POINT_SIZE; /* +0x28A04 */ Latte::LATTE_PA_SU_POINT_MINMAX PA_SU_POINT_MINMAX; uint8 padding_28A08[0x28A40 - 0x28A08]; /* +0x28A40 */ Latte::LATTE_VGT_GS_MODE VGT_GS_MODE; uint8 padding_28A44[0x28A7C - 0x28A44]; /* +0x28A7C */ Latte::LATTE_VGT_DMA_INDEX_TYPE VGT_DMA_INDEX_TYPE; /* +0x28A80 */ uint32 ukn28A80; /* +0x28A84 */ Latte::LATTE_VGT_PRIMITIVEID_EN VGT_PRIMITIVEID_EN; /* +0x28A88 */ Latte::LATTE_VGT_DMA_NUM_INSTANCES VGT_DMA_NUM_INSTANCES; /* +0x28A8C */ uint32 ukn28A8C; /* +0x28A90 */ uint32 ukn28A90; /* +0x28A94 */ Latte::LATTE_VGT_MULTI_PRIM_IB_RESET_EN VGT_MULTI_PRIM_IB_RESET_EN; /* +0x28A98 */ uint32 ukn28A98; /* +0x28A9C */ uint32 ukn28A9C; /* +0x28AA0 */ Latte::LATTE_VGT_INSTANCE_STEP_RATE_X VGT_INSTANCE_STEP_RATE_0; /* +0x28AA4 */ Latte::LATTE_VGT_INSTANCE_STEP_RATE_X VGT_INSTANCE_STEP_RATE_1; uint8 padding_28AA8[0x28AD0 - 0x28AA8]; /* +0x28AD0 */ _LatteRegisterSetStreamoutBuffer VGT_STRMOUT_BUFFER_X[4]; /* +0x28B10 */ Latte::LATTE_VGT_STRMOUT_BASE_OFFSET_X VGT_STRMOUT_BASE_OFFSET_X[4]; /* +0x28B20 */ Latte::LATTE_VGT_STRMOUT_BUFFER_EN VGT_STRMOUT_BUFFER_EN; uint8 padding_28B24[0x28DFC - 0x28B24]; /* +0x28DFC */ Latte::LATTE_PA_SU_POLY_OFFSET_CLAMP PA_SU_POLY_OFFSET_CLAMP; /* +0x28E00 */ Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_SCALE PA_SU_POLY_OFFSET_FRONT_SCALE; /* +0x28E04 */ Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_OFFSET PA_SU_POLY_OFFSET_FRONT_OFFSET; /* +0x28E08 */ Latte::LATTE_PA_SU_POLY_OFFSET_BACK_SCALE PA_SU_POLY_OFFSET_BACK_SCALE; /* +0x28E0C */ Latte::LATTE_PA_SU_POLY_OFFSET_BACK_OFFSET PA_SU_POLY_OFFSET_BACK_OFFSET; uint8 padding_28E10[0x38000 - 0x28E10]; // texture units are mapped as following (18 sets each): // 0xE000 0x38000 -> pixel shader // 0xE460 0x39180 -> vertex shader / compute shader // 0xE930 0x3A4C0 -> geometry shader // there is register space for exactly 160 ps tex units and 176 vs tex units. It's unknown how many GS units there are. /* +0x38000 */ _LatteRegisterSetTextureUnit SQ_TEX_START_PS[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; _LatteRegisterSetTextureUnit _DUMMY_TEX_UNITS_PS[160 - Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; /* +0x39180 */ _LatteRegisterSetTextureUnit SQ_TEX_START_VS[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; _LatteRegisterSetTextureUnit _DUMMY_TEX_UNITS_VS[176 - Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; /* +0x3A4C0 */ _LatteRegisterSetTextureUnit SQ_TEX_START_GS[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; uint8 padding_3A6B8[0x3C000 - 0x3A6B8]; /* +0x3C000 */ _LatteRegisterSetSampler SQ_TEX_SAMPLER[18 * 3]; /* +0x3C288 */ uint8 padding_3C288[0x40000 - 0x3C288]; /* +0x40000 */ // special state registers uint32 hleSpecialState[9]; // deprecated uint32* GetRawView() const { return (uint32*)padding0; } uint32* GetSpecialStateValues() const { return (uint32*)hleSpecialState; } bool IsRasterizationEnabled() const { bool rasterizationEnabled = !PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL(); // GX2SetSpecialState(0, true) enables DX_RASTERIZATION_KILL, but still expects depth writes to happen? -> Research which stages are disabled by DX_RASTERIZATION_KILL exactly // for now we use a workaround: if (!PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA()) rasterizationEnabled = true; // Culling both front and back faces effectively disables rasterization uint32 cullFront = PA_SU_SC_MODE_CNTL.get_CULL_FRONT(); uint32 cullBack = PA_SU_SC_MODE_CNTL.get_CULL_BACK(); if (cullFront && cullBack) rasterizationEnabled = false; return rasterizationEnabled; } }; static_assert(sizeof(LatteContextRegister) == 0x10000 * 4 + 9 * 4); static_assert(offsetof(LatteContextRegister, VGT_PRIMITIVE_TYPE) == Latte::REGADDR::VGT_PRIMITIVE_TYPE * 4); static_assert(offsetof(LatteContextRegister, TD_PS_SAMPLER_BORDER_COLOR) == Latte::REGADDR::TD_PS_SAMPLER0_BORDER_RED * 4); static_assert(offsetof(LatteContextRegister, TD_VS_SAMPLER_BORDER_COLOR) == Latte::REGADDR::TD_VS_SAMPLER0_BORDER_RED * 4); static_assert(offsetof(LatteContextRegister, TD_GS_SAMPLER_BORDER_COLOR) == Latte::REGADDR::TD_GS_SAMPLER0_BORDER_RED * 4); static_assert(offsetof(LatteContextRegister, CB_TARGET_MASK) == Latte::REGADDR::CB_TARGET_MASK * 4); static_assert(offsetof(LatteContextRegister, PA_SC_GENERIC_SCISSOR_TL) == Latte::REGADDR::PA_SC_GENERIC_SCISSOR_TL * 4); static_assert(offsetof(LatteContextRegister, PA_SC_GENERIC_SCISSOR_BR) == Latte::REGADDR::PA_SC_GENERIC_SCISSOR_BR * 4); static_assert(offsetof(LatteContextRegister, VGT_MULTI_PRIM_IB_RESET_INDX) == Latte::REGADDR::VGT_MULTI_PRIM_IB_RESET_INDX * 4); static_assert(offsetof(LatteContextRegister, VGT_PRIMITIVEID_EN) == Latte::REGADDR::VGT_PRIMITIVEID_EN * 4); static_assert(offsetof(LatteContextRegister, VGT_DMA_NUM_INSTANCES) == Latte::REGADDR::VGT_DMA_NUM_INSTANCES * 4); static_assert(offsetof(LatteContextRegister, VGT_MULTI_PRIM_IB_RESET_EN) == Latte::REGADDR::VGT_MULTI_PRIM_IB_RESET_EN * 4); static_assert(offsetof(LatteContextRegister, VGT_INSTANCE_STEP_RATE_0) == Latte::REGADDR::VGT_INSTANCE_STEP_RATE_0 * 4); static_assert(offsetof(LatteContextRegister, VGT_INSTANCE_STEP_RATE_1) == Latte::REGADDR::VGT_INSTANCE_STEP_RATE_1 * 4); static_assert(offsetof(LatteContextRegister, VGT_STRMOUT_BUFFER_X) == Latte::REGADDR::VGT_STRMOUT_BUFFER_SIZE_0 * 4); static_assert(offsetof(LatteContextRegister, VGT_STRMOUT_BASE_OFFSET_X) == Latte::REGADDR::VGT_STRMOUT_BASE_OFFSET_0 * 4); static_assert(offsetof(LatteContextRegister, VGT_STRMOUT_BUFFER_EN) == Latte::REGADDR::VGT_STRMOUT_BUFFER_EN * 4); static_assert(offsetof(LatteContextRegister, SX_ALPHA_TEST_CONTROL) == Latte::REGADDR::SX_ALPHA_TEST_CONTROL * 4); static_assert(offsetof(LatteContextRegister, DB_STENCILREFMASK) == Latte::REGADDR::DB_STENCILREFMASK * 4); static_assert(offsetof(LatteContextRegister, DB_STENCILREFMASK_BF) == Latte::REGADDR::DB_STENCILREFMASK_BF * 4); static_assert(offsetof(LatteContextRegister, SX_ALPHA_REF) == Latte::REGADDR::SX_ALPHA_REF * 4); static_assert(offsetof(LatteContextRegister, CB_BLEND_RED) == Latte::REGADDR::CB_BLEND_RED * 4); static_assert(offsetof(LatteContextRegister, CB_BLEND_GREEN) == Latte::REGADDR::CB_BLEND_GREEN * 4); static_assert(offsetof(LatteContextRegister, CB_BLEND_BLUE) == Latte::REGADDR::CB_BLEND_BLUE * 4); static_assert(offsetof(LatteContextRegister, CB_BLEND_ALPHA) == Latte::REGADDR::CB_BLEND_ALPHA * 4); static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_XSCALE) == Latte::REGADDR::PA_CL_VPORT_XSCALE * 4); static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_XOFFSET) == Latte::REGADDR::PA_CL_VPORT_XOFFSET * 4); static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_YSCALE) == Latte::REGADDR::PA_CL_VPORT_YSCALE * 4); static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_YOFFSET) == Latte::REGADDR::PA_CL_VPORT_YOFFSET * 4); static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_ZSCALE) == Latte::REGADDR::PA_CL_VPORT_ZSCALE * 4); static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_ZOFFSET) == Latte::REGADDR::PA_CL_VPORT_ZOFFSET * 4); static_assert(offsetof(LatteContextRegister, PA_CL_CLIP_CNTL) == Latte::REGADDR::PA_CL_CLIP_CNTL * 4); static_assert(offsetof(LatteContextRegister, PA_SU_SC_MODE_CNTL) == Latte::REGADDR::PA_SU_SC_MODE_CNTL * 4); static_assert(offsetof(LatteContextRegister, PA_CL_VTE_CNTL) == Latte::REGADDR::PA_CL_VTE_CNTL * 4); static_assert(offsetof(LatteContextRegister, PA_CL_VS_OUT_CNTL) == Latte::REGADDR::PA_CL_VS_OUT_CNTL * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POINT_SIZE) == Latte::REGADDR::PA_SU_POINT_SIZE * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POINT_MINMAX) == Latte::REGADDR::PA_SU_POINT_MINMAX * 4); static_assert(offsetof(LatteContextRegister, CB_BLENDN_CONTROL) == Latte::REGADDR::CB_BLEND0_CONTROL * 4); static_assert(offsetof(LatteContextRegister, DB_DEPTH_CONTROL) == Latte::REGADDR::DB_DEPTH_CONTROL * 4); static_assert(offsetof(LatteContextRegister, CB_COLOR_CONTROL) == Latte::REGADDR::CB_COLOR_CONTROL * 4); static_assert(offsetof(LatteContextRegister, VGT_GS_MODE) == Latte::REGADDR::VGT_GS_MODE * 4); static_assert(offsetof(LatteContextRegister, VGT_DMA_INDEX_TYPE) == Latte::REGADDR::VGT_DMA_INDEX_TYPE * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_CLAMP) == Latte::REGADDR::PA_SU_POLY_OFFSET_CLAMP * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_FRONT_SCALE) == Latte::REGADDR::PA_SU_POLY_OFFSET_FRONT_SCALE * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_FRONT_OFFSET) == Latte::REGADDR::PA_SU_POLY_OFFSET_FRONT_OFFSET * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_BACK_SCALE) == Latte::REGADDR::PA_SU_POLY_OFFSET_BACK_SCALE * 4); static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_BACK_OFFSET) == Latte::REGADDR::PA_SU_POLY_OFFSET_BACK_OFFSET * 4); static_assert(offsetof(LatteContextRegister, SQ_VTX_SEMANTIC_X) == Latte::REGADDR::SQ_VTX_SEMANTIC_0 * 4); static_assert(offsetof(LatteContextRegister, SQ_VTX_SEMANTIC_CLEAR) == Latte::REGADDR::SQ_VTX_SEMANTIC_CLEAR * 4); static_assert(offsetof(LatteContextRegister, SQ_TEX_START_PS) == Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS * 4); static_assert(offsetof(LatteContextRegister, SQ_TEX_START_VS) == Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS * 4); static_assert(offsetof(LatteContextRegister, SQ_TEX_START_GS) == Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS * 4); static_assert(offsetof(LatteContextRegister, SQ_TEX_SAMPLER) == Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 * 4); static_assert(offsetof(LatteContextRegister, SQ_PGM_START_PS) == Latte::REGADDR::SQ_PGM_START_PS * 4); static_assert(offsetof(LatteContextRegister, SQ_PGM_RESOURCES_PS) == Latte::REGADDR::SQ_PGM_RESOURCES_PS * 4); static_assert(offsetof(LatteContextRegister, SQ_PGM_START_VS) == Latte::REGADDR::SQ_PGM_START_VS * 4); static_assert(offsetof(LatteContextRegister, SQ_PGM_RESOURCES_VS) == Latte::REGADDR::SQ_PGM_RESOURCES_VS * 4); static_assert(offsetof(LatteContextRegister, SQ_PGM_START_FS) == Latte::REGADDR::SQ_PGM_START_FS * 4); static_assert(offsetof(LatteContextRegister, SQ_PGM_RESOURCES_FS) == Latte::REGADDR::SQ_PGM_RESOURCES_FS * 4); static_assert(offsetof(LatteContextRegister, SQ_PGM_START_ES) == Latte::REGADDR::SQ_PGM_START_ES * 4); static_assert(offsetof(LatteContextRegister, SQ_PGM_RESOURCES_ES) == Latte::REGADDR::SQ_PGM_RESOURCES_ES * 4); static_assert(offsetof(LatteContextRegister, SQ_PGM_START_GS) == Latte::REGADDR::SQ_PGM_START_GS * 4); static_assert(offsetof(LatteContextRegister, SQ_PGM_RESOURCES_GS) == Latte::REGADDR::SQ_PGM_RESOURCES_GS * 4); static_assert(offsetof(LatteContextRegister, SPI_VS_OUT_CONFIG) == Latte::REGADDR::SPI_VS_OUT_CONFIG * 4); static_assert(offsetof(LatteContextRegister, LATTE_SPI_VS_OUT_ID_N) == Latte::REGADDR::SPI_VS_OUT_ID_0 * 4); ================================================ FILE: src/Cafe/HW/Latte/ISA/RegDefines.h ================================================ #pragma once // todo - this file is superseded by LatteReg.h #include "Cafe/HW/Latte/ISA/LatteReg.h" #define mmPA_CL_VS_OUT_CNTL 0xA207 #define mmPA_SU_LINE_CNTL 0xA282 #define mmPA_SU_POLY_OFFSET_DB_FMT_CNTL 0xA37E #define mmPA_SC_WINDOW_OFFSET 0xA080 #define mmPA_SC_AA_CONFIG 0xA301 #define mmPA_SC_AA_MASK 0xA312 #define mmPA_SC_AA_SAMPLE_LOCS_MCTX 0xA307 #define mmPA_SC_AA_SAMPLE_LOCS_8S_WD1_MCTX 0xA308 #define mmPA_SC_LINE_STIPPLE 0xA283 #define mmPA_SC_LINE_CNTL 0xA300 #define mmPA_SC_SCREEN_SCISSOR_TL 0xA00C #define mmPA_SC_SCREEN_SCISSOR_BR 0xA00D #define mmPA_SC_WINDOW_SCISSOR_TL 0xA081 #define mmPA_SC_WINDOW_SCISSOR_BR 0xA082 #define mmPA_SC_CLIPRECT_RULE 0xA083 #define mmPA_SC_CLIPRECT_0_TL 0xA084 #define mmPA_SC_CLIPRECT_0_BR 0xA085 #define mmPA_SC_CLIPRECT_1_TL 0xA086 #define mmPA_SC_CLIPRECT_1_BR 0xA087 #define mmPA_SC_CLIPRECT_2_TL 0xA088 #define mmPA_SC_CLIPRECT_2_BR 0xA089 #define mmPA_SC_CLIPRECT_3_TL 0xA08A #define mmPA_SC_CLIPRECT_3_BR 0xA08B #define mmPA_SC_EDGERULE 0xA08C #define mmPA_SC_MODE_CNTL 0xA293 #define mmPA_SC_MPASS_PS_CNTL 0xA292 #define mmVGT_DRAW_INITIATOR 0xA1FC #define mmVGT_EVENT_INITIATOR 0xA2A4 #define mmVGT_EVENT_ADDRESS_REG 0xA1FE #define mmVGT_DMA_BASE_HI 0xA1F9 #define mmVGT_DMA_BASE 0xA1FA #define mmVGT_DMA_INDEX_TYPE 0xA29F #define mmVGT_DMA_NUM_INSTANCES 0xA2A2 #define mmVGT_DMA_SIZE 0xA29D #define mmVGT_IMMED_DATA 0xA1FD #define mmVGT_INDEX_TYPE 0x2257 #define mmVGT_NUM_INDICES 0x225C #define mmVGT_NUM_INSTANCES 0x225D #define mmVGT_PRIMITIVE_TYPE 0x2256 #define mmVGT_PRIMITIVEID_EN 0xA2A1 #define mmVGT_VTX_CNT_EN 0xA2AE #define mmVGT_REUSE_OFF 0xA2AD #define mmVGT_MAX_VTX_INDX 0xA100 #define mmVGT_MIN_VTX_INDX 0xA101 #define mmVGT_INDX_OFFSET 0xA102 #define mmVGT_VERTEX_REUSE_BLOCK_CNTL 0xA316 #define mmVGT_OUT_DEALLOC_CNTL 0xA317 #define mmVGT_MULTI_PRIM_IB_RESET_EN 0xA2A5 #define mmVGT_ENHANCE 0xA294 #define mmVGT_OUTPUT_PATH_CNTL 0xA284 #define mmVGT_HOS_CNTL 0xA285 #define mmVGT_HOS_MAX_TESS_LEVEL 0xA286 #define mmVGT_HOS_MIN_TESS_LEVEL 0xA287 #define mmVGT_HOS_REUSE_DEPTH 0xA288 #define mmVGT_GROUP_PRIM_TYPE 0xA289 #define mmVGT_GROUP_FIRST_DECR 0xA28A #define mmVGT_GROUP_DECR 0xA28B #define mmVGT_GROUP_VECT_0_CNTL 0xA28C #define mmVGT_GROUP_VECT_1_CNTL 0xA28D #define mmVGT_GROUP_VECT_0_FMT_CNTL 0xA28E #define mmVGT_GROUP_VECT_1_FMT_CNTL 0xA28F #define mmVGT_GS_OUT_PRIM_TYPE 0xA29B #define mmVGT_STRMOUT_EN 0xA2AC #define mmVGT_STRMOUT_BUFFER_SIZE_0 0xA2B4 #define mmVGT_STRMOUT_BUFFER_SIZE_1 0xA2B8 #define mmVGT_STRMOUT_BUFFER_SIZE_2 0xA2BC #define mmVGT_STRMOUT_BUFFER_SIZE_3 0xA2C0 #define mmVGT_STRMOUT_BUFFER_OFFSET_0 0xA2B7 #define mmVGT_STRMOUT_BUFFER_OFFSET_1 0xA2BB #define mmVGT_STRMOUT_BUFFER_OFFSET_2 0xA2BF #define mmVGT_STRMOUT_BUFFER_OFFSET_3 0xA2C3 #define mmVGT_STRMOUT_VTX_STRIDE_0 0xA2B5 #define mmVGT_STRMOUT_VTX_STRIDE_1 0xA2B9 #define mmVGT_STRMOUT_VTX_STRIDE_2 0xA2BD #define mmVGT_STRMOUT_VTX_STRIDE_3 0xA2C1 #define mmVGT_STRMOUT_BUFFER_BASE_0 0xA2B6 #define mmVGT_STRMOUT_BUFFER_BASE_1 0xA2BA #define mmVGT_STRMOUT_BUFFER_BASE_2 0xA2BE #define mmVGT_STRMOUT_BUFFER_BASE_3 0xA2C2 #define mmVGT_STRMOUT_BUFFER_EN 0xA2C8 #define mmVGT_STRMOUT_BASE_OFFSET_0 0xA2C4 #define mmVGT_STRMOUT_BASE_OFFSET_1 0xA2C5 #define mmVGT_STRMOUT_BASE_OFFSET_2 0xA2C6 #define mmVGT_STRMOUT_BASE_OFFSET_3 0xA2C7 #define mmVGT_STRMOUT_BASE_OFFSET_HI_0 0xA2D1 #define mmVGT_STRMOUT_BASE_OFFSET_HI_1 0xA2D2 #define mmVGT_STRMOUT_BASE_OFFSET_HI_2 0xA2D3 #define mmVGT_STRMOUT_BASE_OFFSET_HI_3 0xA2D4 #define mmVGT_STRMOUT_DRAW_OPAQUE_OFFSET 0xA2CA #define mmVGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE 0xA2CB #define mmVGT_STRMOUT_DRAW_OPAQUE_VERTEX_STRIDE 0xA2CC #define mmSQ_PGM_START_PS 0xA210 #define mmSQ_PGM_CF_OFFSET_PS 0xA233 #define mmSQ_PGM_RESOURCES_PS 0xA214 #define mmSQ_PGM_EXPORTS_PS 0xA215 #define mmSQ_PGM_START_VS 0xA216 #define mmSQ_PGM_CF_OFFSET_VS 0xA234 #define mmSQ_PGM_RESOURCES_VS 0xA21A #define mmSQ_PGM_START_GS 0xA21B #define mmSQ_PGM_CF_OFFSET_GS 0xA235 #define mmSQ_PGM_RESOURCES_GS 0xA21F #define mmSQ_PGM_START_ES 0xA220 #define mmSQ_PGM_CF_OFFSET_ES 0xA236 #define mmSQ_PGM_RESOURCES_ES 0xA224 #define mmSQ_PGM_START_FS 0xA225 #define mmSQ_PGM_CF_OFFSET_FS 0xA237 #define mmSQ_PGM_RESOURCES_FS 0xA229 #define mmSQ_ESGS_RING_ITEMSIZE 0xA22A #define mmSQ_GSVS_RING_ITEMSIZE 0xA22B #define mmSQ_ESTMP_RING_ITEMSIZE 0xA22C #define mmSQ_GSTMP_RING_ITEMSIZE 0xA22D #define mmSQ_VSTMP_RING_ITEMSIZE 0xA22E #define mmSQ_PSTMP_RING_ITEMSIZE 0xA22F #define mmSQ_FBUF_RING_ITEMSIZE 0xA230 #define mmSQ_REDUC_RING_ITEMSIZE 0xA231 #define mmSQ_GS_VERT_ITEMSIZE 0xA232 #define mmSQ_VTX_SEMANTIC_CLEAR 0xA238 #define mmSQ_VTX_SEMANTIC_0 0xA0E0 #define mmSQ_VTX_SEMANTIC_1 0xA0E1 #define mmSQ_VTX_SEMANTIC_2 0xA0E2 #define mmSQ_VTX_SEMANTIC_3 0xA0E3 #define mmSQ_VTX_SEMANTIC_4 0xA0E4 #define mmSQ_VTX_SEMANTIC_5 0xA0E5 #define mmSQ_VTX_SEMANTIC_6 0xA0E6 #define mmSQ_VTX_SEMANTIC_7 0xA0E7 #define mmSQ_VTX_SEMANTIC_8 0xA0E8 #define mmSQ_VTX_SEMANTIC_9 0xA0E9 #define mmSQ_VTX_SEMANTIC_10 0xA0EA #define mmSQ_VTX_SEMANTIC_11 0xA0EB #define mmSQ_VTX_SEMANTIC_12 0xA0EC #define mmSQ_VTX_SEMANTIC_13 0xA0ED #define mmSQ_VTX_SEMANTIC_14 0xA0EE #define mmSQ_VTX_SEMANTIC_15 0xA0EF #define mmSQ_VTX_SEMANTIC_16 0xA0F0 #define mmSQ_VTX_SEMANTIC_17 0xA0F1 #define mmSQ_VTX_SEMANTIC_18 0xA0F2 #define mmSQ_VTX_SEMANTIC_19 0xA0F3 #define mmSQ_VTX_SEMANTIC_20 0xA0F4 #define mmSQ_VTX_SEMANTIC_21 0xA0F5 #define mmSQ_VTX_SEMANTIC_22 0xA0F6 #define mmSQ_VTX_SEMANTIC_23 0xA0F7 #define mmSQ_VTX_SEMANTIC_24 0xA0F8 #define mmSQ_VTX_SEMANTIC_25 0xA0F9 #define mmSQ_VTX_SEMANTIC_26 0xA0FA #define mmSQ_VTX_SEMANTIC_27 0xA0FB #define mmSQ_VTX_SEMANTIC_28 0xA0FC #define mmSQ_VTX_SEMANTIC_29 0xA0FD #define mmSQ_VTX_SEMANTIC_30 0xA0FE #define mmSQ_VTX_SEMANTIC_31 0xA0FF #define mmSPI_VS_OUT_ID_0 0xA185 #define mmSPI_VS_OUT_ID_1 0xA186 #define mmSPI_VS_OUT_ID_2 0xA187 #define mmSPI_VS_OUT_ID_3 0xA188 #define mmSPI_VS_OUT_ID_4 0xA189 #define mmSPI_VS_OUT_ID_5 0xA18A #define mmSPI_VS_OUT_ID_6 0xA18B #define mmSPI_VS_OUT_ID_7 0xA18C #define mmSPI_VS_OUT_ID_8 0xA18D #define mmSPI_VS_OUT_ID_9 0xA18E #define mmSPI_PS_INPUT_CNTL_0 0xA191 #define mmSPI_PS_INPUT_CNTL_1 0xA192 #define mmSPI_PS_INPUT_CNTL_2 0xA193 #define mmSPI_PS_INPUT_CNTL_3 0xA194 #define mmSPI_PS_INPUT_CNTL_4 0xA195 #define mmSPI_PS_INPUT_CNTL_5 0xA196 #define mmSPI_PS_INPUT_CNTL_6 0xA197 #define mmSPI_PS_INPUT_CNTL_7 0xA198 #define mmSPI_PS_INPUT_CNTL_8 0xA199 #define mmSPI_PS_INPUT_CNTL_9 0xA19A #define mmSPI_PS_INPUT_CNTL_10 0xA19B #define mmSPI_PS_INPUT_CNTL_11 0xA19C #define mmSPI_PS_INPUT_CNTL_12 0xA19D #define mmSPI_PS_INPUT_CNTL_13 0xA19E #define mmSPI_PS_INPUT_CNTL_14 0xA19F #define mmSPI_PS_INPUT_CNTL_15 0xA1A0 #define mmSPI_PS_INPUT_CNTL_16 0xA1A1 #define mmSPI_PS_INPUT_CNTL_17 0xA1A2 #define mmSPI_PS_INPUT_CNTL_18 0xA1A3 #define mmSPI_PS_INPUT_CNTL_19 0xA1A4 #define mmSPI_PS_INPUT_CNTL_20 0xA1A5 #define mmSPI_PS_INPUT_CNTL_21 0xA1A6 #define mmSPI_PS_INPUT_CNTL_22 0xA1A7 #define mmSPI_PS_INPUT_CNTL_23 0xA1A8 #define mmSPI_PS_INPUT_CNTL_24 0xA1A9 #define mmSPI_PS_INPUT_CNTL_25 0xA1AA #define mmSPI_PS_INPUT_CNTL_26 0xA1AB #define mmSPI_PS_INPUT_CNTL_27 0xA1AC #define mmSPI_PS_INPUT_CNTL_28 0xA1AD #define mmSPI_PS_INPUT_CNTL_29 0xA1AE #define mmSPI_PS_INPUT_CNTL_30 0xA1AF #define mmSPI_PS_INPUT_CNTL_31 0xA1B0 #define mmSPI_VS_OUT_CONFIG 0xA1B1 #define mmSPI_THREAD_GROUPING 0xA1B2 #define mmSPI_PS_IN_CONTROL_0 0xA1B3 #define mmSPI_PS_IN_CONTROL_1 0xA1B4 #define mmSPI_INTERP_CONTROL_0 0xA1B5 #define mmSPI_INPUT_Z 0xA1B6 #define mmDB_DEPTH_BASE 0xA003 #define mmDB_DEPTH_INFO 0xA004 #define mmDB_HTILE_DATA_BASE 0xA005 #define mmDB_DEPTH_SIZE 0xA000 #define mmDB_DEPTH_VIEW 0xA001 #define mmDB_RENDER_CONTROL 0xA343 #define mmDB_RENDER_OVERRIDE 0xA344 #define mmDB_SHADER_CONTROL 0xA203 #define mmDB_STENCIL_CLEAR 0xA00A #define mmDB_DEPTH_CLEAR 0xA00B #define mmDB_HTILE_SURFACE 0xA349 #define mmDB_PRELOAD_CONTROL 0xA34C #define mmDB_PREFETCH_LIMIT 0xA34D #define mmDB_STENCILREFMASK 0xA10C #define mmDB_STENCILREFMASK_BF 0xA10D #define mmDB_SRESULTS_COMPARE_STATE0 0xA34A #define mmDB_SRESULTS_COMPARE_STATE1 0xA34B #define mmDB_ALPHA_TO_MASK 0xA351 #define mmCB_CLRCMP_CONTROL 0xA30C #define mmCB_CLRCMP_SRC 0xA30D #define mmCB_CLRCMP_DST 0xA30E #define mmCB_CLRCMP_MSK 0xA30F #define mmCB_COLOR0_BASE 0xA010 #define mmCB_COLOR1_BASE 0xA011 #define mmCB_COLOR2_BASE 0xA012 #define mmCB_COLOR3_BASE 0xA013 #define mmCB_COLOR4_BASE 0xA014 #define mmCB_COLOR5_BASE 0xA015 #define mmCB_COLOR6_BASE 0xA016 #define mmCB_COLOR7_BASE 0xA017 #define mmCB_COLOR0_SIZE 0xA018 #define mmCB_COLOR1_SIZE 0xA019 #define mmCB_COLOR2_SIZE 0xA01A #define mmCB_COLOR3_SIZE 0xA01B #define mmCB_COLOR4_SIZE 0xA01C #define mmCB_COLOR5_SIZE 0xA01D #define mmCB_COLOR6_SIZE 0xA01E #define mmCB_COLOR7_SIZE 0xA01F #define mmCB_COLOR0_VIEW 0xA020 #define mmCB_COLOR1_VIEW 0xA021 #define mmCB_COLOR2_VIEW 0xA022 #define mmCB_COLOR3_VIEW 0xA023 #define mmCB_COLOR4_VIEW 0xA024 #define mmCB_COLOR5_VIEW 0xA025 #define mmCB_COLOR6_VIEW 0xA026 #define mmCB_COLOR7_VIEW 0xA027 #define mmCB_COLOR0_INFO 0xA028 #define mmCB_COLOR1_INFO 0xA029 #define mmCB_COLOR2_INFO 0xA02A #define mmCB_COLOR3_INFO 0xA02B #define mmCB_COLOR4_INFO 0xA02C #define mmCB_COLOR5_INFO 0xA02D #define mmCB_COLOR6_INFO 0xA02E #define mmCB_COLOR7_INFO 0xA02F #define mmCB_COLOR0_TILE 0xA030 #define mmCB_COLOR1_TILE 0xA031 #define mmCB_COLOR2_TILE 0xA032 #define mmCB_COLOR3_TILE 0xA033 #define mmCB_COLOR4_TILE 0xA034 #define mmCB_COLOR5_TILE 0xA035 #define mmCB_COLOR6_TILE 0xA036 #define mmCB_COLOR7_TILE 0xA037 #define mmCB_COLOR0_FRAG 0xA038 #define mmCB_COLOR1_FRAG 0xA039 #define mmCB_COLOR2_FRAG 0xA03A #define mmCB_COLOR3_FRAG 0xA03B #define mmCB_COLOR4_FRAG 0xA03C #define mmCB_COLOR5_FRAG 0xA03D #define mmCB_COLOR6_FRAG 0xA03E #define mmCB_COLOR7_FRAG 0xA03F #define mmCB_COLOR0_MASK 0xA040 #define mmCB_COLOR1_MASK 0xA041 #define mmCB_COLOR2_MASK 0xA042 #define mmCB_COLOR3_MASK 0xA043 #define mmCB_COLOR4_MASK 0xA044 #define mmCB_COLOR5_MASK 0xA045 #define mmCB_COLOR6_MASK 0xA046 #define mmCB_COLOR7_MASK 0xA047 #define mmCB_SHADER_MASK 0xA08F #define mmCB_SHADER_CONTROL 0xA1E8 #define mmSQ_VTX_BASE_VTX_LOC 0xF3FC // baseVertex #define mmSQ_VTX_START_INST_LOC 0xF3FD // baseInstance #define mmSQ_CONFIG 0x2300 // used by GX2SetShaderModeEx #define mmSQ_GPR_RESOURCE_MGMT_1 0x2301 #define mmSQ_THREAD_RESOURCE_MGMT 0x2303 #define mmSQ_STACK_RESOURCE_MGMT_1 0x2304 #define mmSQ_STACK_RESOURCE_MGMT_2 0x2305 #define mmSQ_ESGS_RING_BASE 0x2310 #define mmSQ_ESGS_RING_SIZE 0x2311 #define mmSQ_GSVS_RING_BASE 0x2312 #define mmSQ_GSVS_RING_SIZE 0x2313 #define mmSQ_ESTMP_RING_BASE 0x2314 #define mmSQ_ESTMP_RING_SIZE 0x2315 #define mmSQ_GSTMP_RING_BASE 0x2316 #define mmSQ_GSTMP_RING_SIZE 0x2317 #define mmSQ_TEX_RESOURCE_WORD0 0xE000 #define mmSQ_ALU_CONSTANT0_0 0xC000 #define mmSQ_VTX_ATTRIBUTE_BLOCK_START (mmSQ_TEX_RESOURCE_WORD0+0x8C0) #define mmSQ_VTX_ATTRIBUTE_BLOCK_END (mmSQ_VTX_ATTRIBUTE_BLOCK_START + 7*16) #define mmSQ_VTX_UNIFORM_BLOCK_START (mmSQ_TEX_RESOURCE_WORD0+0x7E0) // 7 dwords for each uniform block #define mmSQ_VTX_UNIFORM_BLOCK_END (mmSQ_VTX_UNIFORM_BLOCK_START + 7*16 - 1) #define mmSQ_PS_UNIFORM_BLOCK_START (mmSQ_TEX_RESOURCE_WORD0+0x250) // 7 dwords for each uniform block #define mmSQ_PS_UNIFORM_BLOCK_END (mmSQ_PS_UNIFORM_BLOCK_START + 7*16 - 1) #define mmSQ_GS_UNIFORM_BLOCK_START (mmSQ_TEX_RESOURCE_WORD0+0xCB0) // 7 dwords for each uniform block #define mmSQ_GS_UNIFORM_BLOCK_END (mmSQ_GS_UNIFORM_BLOCK_START + 7*16 - 1) #define mmSQ_CS_DISPATCH_PARAMS (mmSQ_TEX_RESOURCE_WORD0+0x865) ================================================ FILE: src/Cafe/HW/Latte/LatteAddrLib/AddrLibFastDecode.h ================================================ #pragma once #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" template<typename texelBaseType, int texelBaseTypeCount, bool isEncodeDirection, bool isCompressed> void optimizedDecodeLoop_tm04_numSamples1_8x8(LatteTextureLoaderCtx* textureLoader, uint8* outputData, sint32 texelCountX, sint32 texelCountY) { uint16* tableBase = textureLoader->computeAddrInfo.microTilePixelIndexTable + ((textureLoader->computeAddrInfo.slice & 7) << 6); for (sint32 yt = 0; yt < texelCountY; yt += 8) { for (sint32 xt = 0; xt < texelCountX; xt += 8) { sint32 baseOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(xt, yt, &textureLoader->computeAddrInfo); // this is only 10-20% of execution time for (sint32 ry = 0; ry < 8; ry++) { sint32 pixelOffset = ((yt + ry)*textureLoader->decodedTexelCountX + (xt)) * (sizeof(texelBaseType)*texelBaseTypeCount); texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); uint16* pixelOffsets = tableBase + (ry << 3); for (sint32 rx = 0; rx < 8; rx++) { uint32 pixelIndex = *pixelOffsets; pixelOffsets++; uint32 pixelOffset = pixelIndex * sizeof(texelBaseType)*texelBaseTypeCount; uint32 elemOffset = pixelOffset; if ((sizeof(texelBaseType)*texelBaseTypeCount * 8 * 8) > 256) { // separate group bytes, for small formats this step is not necessary since elemOffset is never over 0xFF (maximum is 8*8*bpp) elemOffset = (elemOffset & 0xFF) | ((elemOffset&~0xFF) << 3); } sint32 offset = baseOffset + elemOffset; uint8* blockData = textureLoader->inputData + offset; // copy as-is if (texelBaseTypeCount == 1) { if (isEncodeDirection) *(texelBaseType*)blockData = *blockOutput; else *blockOutput = *(texelBaseType*)blockData; blockOutput++; } else if (texelBaseTypeCount == 2) { if (isEncodeDirection) { ((texelBaseType*)blockData)[0] = blockOutput[0]; ((texelBaseType*)blockData)[1] = blockOutput[1]; } else { blockOutput[0] = ((texelBaseType*)blockData)[0]; blockOutput[1] = ((texelBaseType*)blockData)[1]; } blockOutput += 2; } else assert_dbg(); } } } } } template<typename texelBaseType, int texelBaseTypeCount, bool isEncodeDirection, bool isCompressed> void optimizedDecodeLoop_tm04_numSamples1_8x8_optimizedRowCopy(LatteTextureLoaderCtx* textureLoader, uint8* outputData, sint32 texelCountX, sint32 texelCountY) { uint16* tableBase = textureLoader->computeAddrInfo.microTilePixelIndexTable + ((textureLoader->computeAddrInfo.slice & 7) << 6); for (sint32 yt = 0; yt < texelCountY; yt += 8) { for (sint32 xt = 0; xt < texelCountX; xt += 8) { sint32 baseOffset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(xt, yt, &textureLoader->computeAddrInfo); // this is only 10-20% of execution time for (sint32 ry = 0; ry < 8; ry++) { sint32 pixelOffset = ((yt + ry)*textureLoader->decodedTexelCountX + (xt)) * (sizeof(texelBaseType)*texelBaseTypeCount); texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); uint16* pixelOffsets = tableBase + (ry << 3); uint32 pixelIndex = *pixelOffsets; pixelOffsets++; uint32 elemOffset = pixelIndex * sizeof(texelBaseType)*texelBaseTypeCount; if ((sizeof(texelBaseType)*texelBaseTypeCount * 8 * 8) > 256) { // separate group bytes, for small formats this step is not necessary since elemOffset is never over 0xFF (maximum is 8*8*bpp) elemOffset = (elemOffset & 0xFF) | ((elemOffset&~0xFF) << 3); } sint32 offset = baseOffset + elemOffset; texelBaseType* blockData = (texelBaseType*)(textureLoader->inputData + offset); // x-to-offset translation table (for bpp = 64) // 0 -> 0 // 1 -> 1 // 2 -> 4 // 3 -> 5 // 4 -> 8 // 5 -> 9 // 6 -> 12 // 7 -> 13 // x-to-offset translation table (for bpp = 32) // 0 -> 0 // 1 -> 1 // 2 -> 2 // 3 -> 3 // 4 -> 8 // 5 -> 9 // 6 -> 10 // 7 -> 11 if ((sizeof(texelBaseType)*texelBaseTypeCount) == 8) { // bpp = 64 if (texelBaseTypeCount == 1) { if (isEncodeDirection) { blockData[0] = blockOutput[0]; blockData[1] = blockOutput[1]; blockData[4] = blockOutput[2]; blockData[5] = blockOutput[3]; blockData[8] = blockOutput[4]; blockData[9] = blockOutput[5]; blockData[12] = blockOutput[6]; blockData[13] = blockOutput[7]; } else { blockOutput[0] = blockData[0]; blockOutput[1] = blockData[1]; blockOutput[2] = blockData[4]; blockOutput[3] = blockData[5]; blockOutput[4] = blockData[8]; blockOutput[5] = blockData[9]; blockOutput[6] = blockData[12]; blockOutput[7] = blockData[13]; } blockOutput += 8; } else assert_dbg(); } else if ((sizeof(texelBaseType)*texelBaseTypeCount) == 4) { // bpp = 32 if (texelBaseTypeCount == 1) { uint64* blockOutput64 = (uint64*)blockOutput; uint64* blockData64 = (uint64*)blockData; if (isEncodeDirection) { blockData64[0] = blockOutput64[0]; blockData64[1] = blockOutput64[1]; blockData64[4] = blockOutput64[2]; blockData64[5] = blockOutput64[3]; } else { blockOutput64[0] = blockData64[0]; blockOutput64[1] = blockData64[1]; blockOutput64[2] = blockData64[4]; blockOutput64[3] = blockData64[5]; } blockOutput += 8; } else cemu_assert_unimplemented(); } else if ((sizeof(texelBaseType)*texelBaseTypeCount) == 1) { // bpp = 8 if (texelBaseTypeCount == 1) { uint64* blockOutput64 = (uint64*)blockOutput; uint64* blockData64 = (uint64*)blockData; if (isEncodeDirection) blockData64[0] = blockOutput64[0]; else blockOutput64[0] = blockData64[0]; blockOutput += 8; } else cemu_assert_unimplemented(); } else cemu_assert_unimplemented(); } } } } template<typename texelBaseType, int texelBaseTypeCount, bool isEncodeDirection, bool isCompressed> void optimizedDecodeLoops(LatteTextureLoaderCtx* textureLoader, uint8* outputData) { sint32 texelCountX; sint32 texelCountY; if (isCompressed) { texelCountX = (textureLoader->width + 3) / 4; texelCountY = (textureLoader->height + 3) / 4; } else { texelCountX = textureLoader->width; texelCountY = textureLoader->height; } if (textureLoader->tileMode == Latte::E_HWTILEMODE::TM_2D_TILED_THIN1 && textureLoader->computeAddrInfo.numSamples == 1) { sint32 texelCountOrigX = texelCountX; sint32 texelCountOrigY = texelCountY; texelCountX &= ~7; texelCountY &= ~7; // full tiles (assuming tileMode=4 and numSamples=1) // only recalculate tile related offset at the beginning of each block // calculate offsets in loop // unsure if this variant is faster: if (textureLoader->computeAddrInfo.microTileType == 0 && (sizeof(texelBaseType)*texelBaseTypeCount) == 8) { optimizedDecodeLoop_tm04_numSamples1_8x8_optimizedRowCopy<texelBaseType, texelBaseTypeCount, isEncodeDirection, isCompressed>(textureLoader, outputData, texelCountX, texelCountY); } else if (textureLoader->computeAddrInfo.microTileType == 0 && (sizeof(texelBaseType)*texelBaseTypeCount) == 4) { optimizedDecodeLoop_tm04_numSamples1_8x8_optimizedRowCopy<texelBaseType, texelBaseTypeCount, isEncodeDirection, isCompressed>(textureLoader, outputData, texelCountX, texelCountY); } else if (textureLoader->computeAddrInfo.microTileType == 0 && (sizeof(texelBaseType)*texelBaseTypeCount) == 1) { optimizedDecodeLoop_tm04_numSamples1_8x8_optimizedRowCopy<texelBaseType, texelBaseTypeCount, isEncodeDirection, isCompressed>(textureLoader, outputData, texelCountX, texelCountY); } else { optimizedDecodeLoop_tm04_numSamples1_8x8<texelBaseType, texelBaseTypeCount, isEncodeDirection, isCompressed>(textureLoader, outputData, texelCountX, texelCountY); } // the above code only handles full 8x8 pixel blocks, for uneven sizes we need to process the remaining pixels here // right border for (sint32 yt = 0; yt < texelCountY; yt++) { sint32 pixelOffset = (yt*textureLoader->decodedTexelCountX + texelCountX) * (sizeof(texelBaseType)*texelBaseTypeCount); texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); for (sint32 xt = texelCountX; xt < texelCountOrigX; xt++) { sint32 offset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(xt, yt, &textureLoader->computeAddrInfo); uint8* blockData = textureLoader->inputData + offset; // copy as-is if (texelBaseTypeCount == 1) { if (isEncodeDirection) *(texelBaseType*)blockData = *blockOutput; else *blockOutput = *(texelBaseType*)blockData; blockOutput++; } else if (texelBaseTypeCount == 2) { if (isEncodeDirection) { ((texelBaseType*)blockData)[0] = blockOutput[0]; ((texelBaseType*)blockData)[1] = blockOutput[1]; } else { blockOutput[0] = ((texelBaseType*)blockData)[0]; blockOutput[1] = ((texelBaseType*)blockData)[1]; } blockOutput += 2; } } } // bottom border (with bottom right corner) for (sint32 yt = texelCountY; yt < texelCountOrigY; yt++) { sint32 pixelOffset = (yt*textureLoader->decodedTexelCountX) * (sizeof(texelBaseType)*texelBaseTypeCount); texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); for (sint32 xt = 0; xt < texelCountOrigX; xt++) { sint32 offset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(xt, yt, &textureLoader->computeAddrInfo); uint8* blockData = textureLoader->inputData + offset; // copy as-is if (texelBaseTypeCount == 1) { if (isEncodeDirection) *(texelBaseType*)blockData = *blockOutput; else *blockOutput = *(texelBaseType*)blockData; blockOutput++; } else if (texelBaseTypeCount == 2) { if (isEncodeDirection) { ((texelBaseType*)blockData)[0] = blockOutput[0]; ((texelBaseType*)blockData)[1] = blockOutput[1]; } else { blockOutput[0] = ((texelBaseType*)blockData)[0]; blockOutput[1] = ((texelBaseType*)blockData)[1]; } blockOutput += 2; } } } } else if (textureLoader->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED) { // optimized handler for linear textures uint32 sliceOffset = textureLoader->sliceIndex * textureLoader->height * textureLoader->pitch; for (sint32 y = 0; y < texelCountY; y++) { sint32 pixelOffset = (y*textureLoader->decodedTexelCountX) * (sizeof(texelBaseType)*texelBaseTypeCount); texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); texelBaseType* blockData = (texelBaseType*)(textureLoader->inputData + (textureLoader->pitch * y + sliceOffset) * (sizeof(texelBaseType)*texelBaseTypeCount)); for (sint32 x = 0; x < texelCountX; x++) { // copy as-is if (texelBaseTypeCount == 1) { if(isEncodeDirection) *(texelBaseType*)blockData = *blockOutput; else *blockOutput = *(texelBaseType*)blockData; blockData++; blockOutput++; } else if (texelBaseTypeCount == 2) { if (isEncodeDirection) { ((texelBaseType*)blockData)[0] = blockOutput[0]; ((texelBaseType*)blockData)[1] = blockOutput[1]; } else { blockOutput[0] = ((texelBaseType*)blockData)[0]; blockOutput[1] = ((texelBaseType*)blockData)[1]; } blockData += 2; blockOutput += 2; } } } } else { // generic handler for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) { sint32 pixelOffset = ((y / textureLoader->stepY)*textureLoader->decodedTexelCountX) * (sizeof(texelBaseType)*texelBaseTypeCount); texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) { uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); // copy as-is if (texelBaseTypeCount == 1) { if (isEncodeDirection) *(texelBaseType*)blockData = *blockOutput; else *blockOutput = *(texelBaseType*)blockData; blockOutput++; } else if (texelBaseTypeCount == 2) { if (isEncodeDirection) { ((texelBaseType*)blockData)[0] = blockOutput[0]; ((texelBaseType*)blockData)[1] = blockOutput[1]; } else { blockOutput[0] = ((texelBaseType*)blockData)[0]; blockOutput[1] = ((texelBaseType*)blockData)[1]; } blockOutput += 2; } } } } } ================================================ FILE: src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.cpp ================================================ #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" #include "Cafe/OS/libs/gx2/GX2_Surface.h" #include <bit> /* Info: - Extra samples for AA are stored in their own micro-tiles Macro-Tiling: - Contains one micro-tile for every combination of bank/channel select - Since there are 4 bank and 2 pipe bits this means 4*2 = 8 micro tiles (or 8*4 for thick?). But the arrangement varies per tilemode (aspect ratio) Allowed layouts: 1x8, 2x4, 4x2 - Address format: .... aaaaabbc aaaaaaaa A = offset, b = bank, c = channel - Channel/Bank bits are determined by: channel0 = x[3] ^ y[3] bank0 = x[3] ^ y[5] bank1 = x[4] ^ y[4] */ using namespace Latte; namespace LatteAddrLib { enum class COMPUTE_SURFACE_RESULT { RESULT_OK = 0, UNKNOWN_FORMAT = 3, BAD_SIZE_FIELD = 6, }; const uint32 m_configFlags = (1 << 29); uint32 GetSliceComputingFlags() { return (m_configFlags >> 26) & 3; } uint32 GetFillSizeFieldsFlags() { return (m_configFlags >> 25) & 1; } bool GetFlagUseTileIndex() { return ((m_configFlags >> 24) & 1) != 0; } bool GetFlagNoCubeMipSlicesPad() { return ((m_configFlags >> 28) & 1) != 0; } bool GetFlagNo1DTiledMSAA() { return ((m_configFlags >> 29) & 1) != 0; } bool IsPow2(uint32 dim) { return (dim & (dim - 1)) == 0; } uint32 PowTwoAlign(uint32 x, uint32 align) { return (x + align - 1) & ~(align - 1); } uint32 NextPow2(uint32 dim) { return std::bit_ceil<uint32>(dim); } uint32 GetBitsPerPixel(E_HWSURFFMT format, uint32* pElemMode, uint32* pExpandX, uint32* pExpandY) { uint32 bpp; uint32 elemMode = 3; switch (format) { case E_HWSURFFMT::INVALID_FORMAT: bpp = 0; *pExpandX = 1; *pExpandY = 1; break; case E_HWSURFFMT::HWFMT_8: case E_HWSURFFMT::HWFMT_4_4: case E_HWSURFFMT::HWFMT_3_3_2: bpp = 8; *pExpandX = 1; *pExpandY = 1; break; case E_HWSURFFMT::HWFMT_16: case E_HWSURFFMT::HWFMT_16_FLOAT: case E_HWSURFFMT::HWFMT_8_8: case E_HWSURFFMT::HWFMT_5_6_5: case E_HWSURFFMT::HWFMT_6_5_5: case E_HWSURFFMT::HWFMT_1_5_5_5: case E_HWSURFFMT::HWFMT_4_4_4_4: bpp = 16; *pExpandX = 1; *pExpandY = 1; break; case E_HWSURFFMT::HWFMT_5_5_5_1: bpp = 16; *pExpandX = 1; *pExpandY = 1; break; case E_HWSURFFMT::HWFMT_32: case E_HWSURFFMT::HWFMT_32_FLOAT: case E_HWSURFFMT::HWFMT_16_16: case E_HWSURFFMT::HWFMT_16_16_FLOAT: case E_HWSURFFMT::HWFMT_24_8: case E_HWSURFFMT::HWFMT_24_8_FLOAT: case E_HWSURFFMT::HWFMT_10_11_11: case E_HWSURFFMT::HWFMT_11_11_10: case E_HWSURFFMT::HWFMT_2_10_10_10: case E_HWSURFFMT::HWFMT_8_8_8_8: case E_HWSURFFMT::HWFMT_8_24: case E_HWSURFFMT::HWFMT_8_24_FLOAT: case E_HWSURFFMT::HWFMT_10_11_11_FLOAT: case E_HWSURFFMT::HWFMT_11_11_10_FLOAT: case E_HWSURFFMT::HWFMT_10_10_10_2: bpp = 32; *pExpandX = 1; *pExpandY = 1; break; case E_HWSURFFMT::HWFMT_32_32: case E_HWSURFFMT::HWFMT_32_32_FLOAT: case E_HWSURFFMT::HWFMT_16_16_16_16: case E_HWSURFFMT::HWFMT_16_16_16_16_FLOAT: case E_HWSURFFMT::HWFMT_X24_8_32_FLOAT: bpp = 64; *pExpandX = 1; *pExpandY = 1; break; case E_HWSURFFMT::HWFMT_32_32_32_32: case E_HWSURFFMT::HWFMT_32_32_32_32_FLOAT: bpp = 128; *pExpandX = 1; *pExpandY = 1; break; case E_HWSURFFMT::HWFMT_BC1: elemMode = 9; bpp = 64; *pExpandX = 4; *pExpandY = 4; break; case E_HWSURFFMT::HWFMT_BC2: elemMode = 10; bpp = 128; *pExpandX = 4; *pExpandY = 4; break; case E_HWSURFFMT::HWFMT_BC3: elemMode = 11; bpp = 128; *pExpandX = 4; *pExpandY = 4; break; case E_HWSURFFMT::HWFMT_BC4: elemMode = 12; bpp = 64; *pExpandX = 4; *pExpandY = 4; break; case E_HWSURFFMT::HWFMT_BC5: case E_HWSURFFMT::U_HWFMT_BC6: case E_HWSURFFMT::U_HWFMT_BC7: elemMode = 13; bpp = 128; *pExpandX = 4; *pExpandY = 4; break; default: cemu_assert_suspicious(); bpp = 0; *pExpandX = 1; *pExpandY = 1; break; } *pElemMode = elemMode; return bpp; } void AdjustSurfaceInfo(uint32 elemMode, uint32 expandX, uint32 expandY, uint32* pBpp, uint32* pWidth, uint32* pHeight) { bool isBCFormat = false; if (pBpp) { uint32 bpp = *pBpp; uint32 packedBits; switch (elemMode) { case 4: packedBits = bpp / expandX / expandY; break; case 5: case 6: packedBits = expandY * expandX * bpp; break; case 7: case 8: packedBits = *pBpp; break; case 9: case 12: packedBits = 64; isBCFormat = true; break; case 10: case 11: case 13: packedBits = 128; isBCFormat = true; break; case 0: case 1: case 2: case 3: packedBits = *pBpp; break; default: packedBits = *pBpp; break; } *pBpp = packedBits; } if (pWidth) { if (pHeight) { uint32 width = *pWidth; uint32 height = *pHeight; if (expandX > 1 || expandY > 1) { uint32 widthAligned; uint32 heightAligned; if (elemMode == 4) { widthAligned = expandX * width; heightAligned = expandY * height; } else if (isBCFormat) { widthAligned = width / expandX; heightAligned = height / expandY; } else { widthAligned = (width + expandX - 1) / expandX; heightAligned = (height + expandY - 1) / expandY; } *pWidth = std::max<uint32>(widthAligned, 1); *pHeight = std::max<uint32>(heightAligned, 1); } } } } void ComputeMipLevelDimensions(uint32* pWidth, uint32* pHeight, uint32* pNumSlices, AddrSurfaceFlags flags, Latte::E_HWSURFFMT format, uint32 mipLevel) { bool isBCn = (uint32)format >= (uint32)Latte::E_HWSURFFMT::HWFMT_BC1 && (uint32)format <= (uint32)Latte::E_HWSURFFMT::U_HWFMT_BC7; if (isBCn && (mipLevel == 0 || flags.inputIsBase)) { *pWidth = PowTwoAlign(*pWidth, 4); *pHeight = PowTwoAlign(*pHeight, 4); } if (isBCn) { if (mipLevel != 0) { uint32 width = *pWidth; uint32 height = *pHeight; uint32 slices = *pNumSlices; if (flags.inputIsBase) { if (!flags.dimCube) slices >>= mipLevel; width = std::max<uint32>(width >> mipLevel, 1); height = std::max<uint32>(height >> mipLevel, 1); slices = std::max<uint32>(slices, 1); } *pWidth = NextPow2(width); *pHeight = NextPow2(height); *pNumSlices = slices; } } else if (mipLevel && flags.inputIsBase) { uint32 width = *pWidth; uint32 height = *pHeight; uint32 slices = *pNumSlices; width >>= mipLevel; height >>= mipLevel; if (!flags.dimCube) // dim 3D slices >>= mipLevel; width = std::max<uint32>(1, width); height = std::max<uint32>(1, height); slices = std::max<uint32>(1, slices); if (format != E_HWSURFFMT::U_HWFMT_32_32_32 && format != E_HWSURFFMT::U_HWFMT_32_32_32_FLOAT) { width = NextPow2(width); height = NextPow2(height); slices = NextPow2(slices); } *pWidth = width; *pHeight = height; *pNumSlices = slices; } } E_HWTILEMODE ConvertTileModeToNonBankSwappedMode(E_HWTILEMODE tileMode) { switch (tileMode) { case E_HWTILEMODE::TM_2B_TILED_THIN1: return E_HWTILEMODE::TM_2D_TILED_THIN1; case E_HWTILEMODE::TM_2B_TILED_THIN2: return E_HWTILEMODE::TM_2D_TILED_THIN2; case E_HWTILEMODE::TM_2B_TILED_THIN4: return E_HWTILEMODE::TM_2D_TILED_THIN4; case E_HWTILEMODE::TM_2B_TILED_THICK: return E_HWTILEMODE::TM_2D_TILED_THICK; case E_HWTILEMODE::TM_3B_TILED_THIN1: return E_HWTILEMODE::TM_3D_TILED_THIN1; case E_HWTILEMODE::TM_3B_TILED_THICK: return E_HWTILEMODE::TM_3D_TILED_THICK; default: break; } return tileMode; } uint32 _CalculateSurfaceTileSlices(E_HWTILEMODE tileMode, uint32 bpp, uint32 numSamples) { uint32 bytePerSample = ((bpp << 6) + 7) >> 3; uint32 tileSlices = 1; if (TM_GetThickness(tileMode) > 1) numSamples = 4; if (bytePerSample) { uint32 samplePerTile = m_splitSize / bytePerSample; if (samplePerTile) { tileSlices = numSamples / samplePerTile; if (!(numSamples / samplePerTile)) tileSlices = 1; } } return tileSlices; } uint32 ComputeSurfaceRotationFromTileMode(E_HWTILEMODE tileMode) { switch (tileMode) { case E_HWTILEMODE::TM_2D_TILED_THIN1: case E_HWTILEMODE::TM_2D_TILED_THIN2: case E_HWTILEMODE::TM_2D_TILED_THIN4: case E_HWTILEMODE::TM_2D_TILED_THICK: case E_HWTILEMODE::TM_2B_TILED_THIN1: case E_HWTILEMODE::TM_2B_TILED_THIN2: case E_HWTILEMODE::TM_2B_TILED_THIN4: case E_HWTILEMODE::TM_2B_TILED_THICK: return m_pipes * ((m_banks >> 1) - 1); case E_HWTILEMODE::TM_3D_TILED_THIN1: case E_HWTILEMODE::TM_3D_TILED_THICK: case E_HWTILEMODE::TM_3B_TILED_THIN1: case E_HWTILEMODE::TM_3B_TILED_THICK: if (m_pipes >= 4) return (m_pipes >> 1) - 1; return 1; default: break; } return 0; } E_HWTILEMODE _ComputeSurfaceMipLevelTileMode(E_HWTILEMODE baseTileMode, uint32 bpp, uint32 level, uint32 width, uint32 height, uint32 numSlices, uint32 numSamples, bool isDepth, bool noRecursive) { E_HWTILEMODE result; E_HWTILEMODE expTileMode = baseTileMode; uint32 tileSlices = _CalculateSurfaceTileSlices(baseTileMode, bpp, numSamples); switch (baseTileMode) { case E_HWTILEMODE::TM_1D_TILED_THIN1: if (numSamples > 1 && GetFlagNo1DTiledMSAA()) expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; break; case E_HWTILEMODE::TM_1D_TILED_THICK: if (numSamples > 1 || isDepth) expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; if (numSamples == 2 || numSamples == 4) expTileMode = E_HWTILEMODE::TM_2D_TILED_THICK; break; case E_HWTILEMODE::TM_2D_TILED_THIN2: if (2 * m_pipeInterleaveBytes > m_splitSize) expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; break; case E_HWTILEMODE::TM_2D_TILED_THIN4: if (4 * m_pipeInterleaveBytes > m_splitSize) expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN2; break; case E_HWTILEMODE::TM_2D_TILED_THICK: if (numSamples > 1 || tileSlices > 1 || isDepth) expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; break; case E_HWTILEMODE::TM_2B_TILED_THIN2: if (2 * m_pipeInterleaveBytes > m_splitSize) expTileMode = E_HWTILEMODE::TM_2B_TILED_THIN1; break; case E_HWTILEMODE::TM_2B_TILED_THIN4: if (4 * m_pipeInterleaveBytes > m_splitSize) expTileMode = E_HWTILEMODE::TM_2B_TILED_THIN2; break; case E_HWTILEMODE::TM_2B_TILED_THICK: if (numSamples > 1 || tileSlices > 1 || isDepth) expTileMode = E_HWTILEMODE::TM_2B_TILED_THIN1; break; case E_HWTILEMODE::TM_3D_TILED_THICK: if (numSamples > 1 || tileSlices > 1 || isDepth) expTileMode = E_HWTILEMODE::TM_3D_TILED_THIN1; break; case E_HWTILEMODE::TM_3B_TILED_THICK: if (numSamples > 1 || tileSlices > 1 || isDepth) expTileMode = E_HWTILEMODE::TM_3B_TILED_THIN1; break; default: expTileMode = baseTileMode; break; } uint32 rotation = ComputeSurfaceRotationFromTileMode(expTileMode); if (!(rotation % m_pipes)) { switch (expTileMode) { case E_HWTILEMODE::TM_3D_TILED_THIN1: expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; break; case E_HWTILEMODE::TM_3D_TILED_THICK: expTileMode = E_HWTILEMODE::TM_2D_TILED_THICK; break; case E_HWTILEMODE::TM_3B_TILED_THIN1: expTileMode = E_HWTILEMODE::TM_2B_TILED_THIN1; break; case E_HWTILEMODE::TM_3B_TILED_THICK: expTileMode = E_HWTILEMODE::TM_2B_TILED_THICK; break; default: break; } } if (noRecursive) { result = expTileMode; } else { if (bpp == 96 || bpp == 48 || bpp == 24) bpp /= 3u; uint32 widthAligned = NextPow2(width); uint32 heightAligned = NextPow2(height); uint32 numSlicesAligned = NextPow2(numSlices); if (level) { expTileMode = ConvertTileModeToNonBankSwappedMode(expTileMode); uint32 thickness = TM_GetThickness(expTileMode); uint32 microTileBytes = (numSamples * bpp * (thickness << 6) + 7) >> 3; uint32 widthAlignFactor; if (microTileBytes >= m_pipeInterleaveBytes) widthAlignFactor = 1; else widthAlignFactor = m_pipeInterleaveBytes / microTileBytes; uint32 macroTileWidth = 8 * m_banks; uint32 macroTileHeight = 8 * m_pipes; switch (expTileMode) { case E_HWTILEMODE::TM_2D_TILED_THIN1: case E_HWTILEMODE::TM_3D_TILED_THIN1: if (widthAligned < widthAlignFactor * macroTileWidth || heightAligned < macroTileHeight) expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; break; case E_HWTILEMODE::TM_2D_TILED_THIN2: macroTileWidth >>= 1; macroTileHeight *= 2; if (widthAligned < widthAlignFactor * macroTileWidth || heightAligned < macroTileHeight) expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; break; case E_HWTILEMODE::TM_2D_TILED_THIN4: macroTileWidth >>= 2; macroTileHeight *= 4; if (widthAligned < widthAlignFactor * macroTileWidth || heightAligned < macroTileHeight) expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; break; case E_HWTILEMODE::TM_2D_TILED_THICK: case E_HWTILEMODE::TM_3D_TILED_THICK: if (widthAligned < widthAlignFactor * macroTileWidth || heightAligned < macroTileHeight) expTileMode = E_HWTILEMODE::TM_1D_TILED_THICK; break; default: break; } if (expTileMode == E_HWTILEMODE::TM_1D_TILED_THICK) { if (numSlicesAligned < 4) expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; } else if (expTileMode == E_HWTILEMODE::TM_2D_TILED_THICK) { if (numSlicesAligned < 4) expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; } else if (expTileMode == E_HWTILEMODE::TM_3D_TILED_THICK && numSlicesAligned < 4) { expTileMode = E_HWTILEMODE::TM_3D_TILED_THIN1; } result = _ComputeSurfaceMipLevelTileMode(expTileMode, bpp, level, widthAligned, heightAligned, numSlicesAligned, numSamples, isDepth, true); } else { result = expTileMode; } } return result; } uint32 _ComputeMacroTileAspectRatio(E_HWTILEMODE tileMode) { switch (tileMode) { case E_HWTILEMODE::TM_2B_TILED_THIN1: case E_HWTILEMODE::TM_3D_TILED_THIN1: case E_HWTILEMODE::TM_3B_TILED_THIN1: return 1; case E_HWTILEMODE::TM_2D_TILED_THIN2: case E_HWTILEMODE::TM_2B_TILED_THIN2: return 2; case E_HWTILEMODE::TM_2D_TILED_THIN4: case E_HWTILEMODE::TM_2B_TILED_THIN4: return 4; default: break; } return 1; } void _AdjustPitchAlignment(AddrSurfaceFlags flags, uint32& pitchAlign) { if (flags.display) pitchAlign = PowTwoAlign(pitchAlign, 32); } void _ComputeSurfaceAlignmentsMacroTiled(E_HWTILEMODE tileMode, uint32 bpp, AddrSurfaceFlags flags, uint32 numSamples, uint32* pBaseAlign, uint32* pPitchAlign, uint32* pHeightAlign, uint32* pMacroWidth, uint32* pMacroHeight) { uint32 aspectRatio = _ComputeMacroTileAspectRatio(tileMode); uint32 thickness = TM_GetThickness(tileMode); if (bpp == 96 || bpp == 48 || bpp == 24) bpp /= 3; if (bpp == 3) bpp = 1; uint32 macroTileWidth = (8 * m_banks) / aspectRatio; uint32 macroTileHeight = aspectRatio * 8 * m_pipes; uint32 pitchAlign = std::max(macroTileWidth, macroTileWidth * (m_pipeInterleaveBytes / bpp / (8 * thickness) / numSamples)); _AdjustPitchAlignment(flags, *pPitchAlign); uint32 heightAlign = macroTileHeight; uint32 macroTileBytes = numSamples * ((bpp * macroTileHeight * macroTileWidth + 7) >> 3); if (m_chipFamily == 1 && numSamples == 1) macroTileBytes *= 2; uint32 baseAlign; if (thickness == 1) baseAlign = std::max(macroTileBytes, (numSamples * heightAlign * bpp * pitchAlign + 7) >> 3); else baseAlign = std::max(m_pipeInterleaveBytes, (4 * heightAlign * bpp * pitchAlign + 7) >> 3); uint32 microTileBytes = (thickness * numSamples * (bpp << 6) + 7) >> 3; uint32 splits; if (microTileBytes < m_splitSize) splits = 1; else splits = microTileBytes / m_splitSize; baseAlign /= splits; *pBaseAlign = baseAlign; *pPitchAlign = pitchAlign; *pHeightAlign = heightAlign; *pMacroWidth = macroTileWidth; *pMacroHeight = macroTileHeight; } uint32 ComputeSurfaceBankSwappedWidth(E_HWTILEMODE tileMode, uint32 bpp, uint32 numSamples, uint32 pitch) { uint32 bankSwapWidth = 0; uint32 slicesPerTile = 1; uint32 bytesPerSample = 8 * bpp; uint32 samplesPerTile = m_splitSize / bytesPerSample; if (m_splitSize / bytesPerSample) { slicesPerTile = numSamples / samplesPerTile; if (!(numSamples / samplesPerTile)) slicesPerTile = 1; } if (TM_IsThickAndMacroTiled(tileMode) == 1) numSamples = 4; if (TM_IsBankSwapped(tileMode)) { uint32 bytesPerTileSlice = numSamples * bytesPerSample / slicesPerTile; uint32 aspectRatioFactor = _ComputeMacroTileAspectRatio(tileMode); uint32 swapTiles = std::max<uint32>((m_swapSize >> 1) / bpp, 1); uint32 swapWidth = swapTiles * 8 * m_banks; uint32 heightBytes = numSamples * aspectRatioFactor * m_pipes * bpp / slicesPerTile; uint32 swapMax = m_pipes * m_banks * m_rowSize / heightBytes; uint32 swapMin = m_pipeInterleaveBytes * 8 * m_banks / bytesPerTileSlice; uint32 swapVal; if (swapMax >= swapWidth) swapVal = std::max(swapWidth, swapMin); else swapVal = swapMax; for (bankSwapWidth = swapVal; bankSwapWidth >= 2 * pitch; bankSwapWidth >>= 1); } return bankSwapWidth; } void PadDimensions(E_HWTILEMODE tileMode, uint32 padDims, int isCube, int cubeAsArray, uint32* pPitch, uint32 pitchAlign, uint32* pHeight, uint32 heightAlign, uint32* pSlices, uint32 sliceAlign) { uint32 thickness = TM_GetThickness(tileMode); if (!padDims) padDims = 3; if (IsPow2(pitchAlign)) { *pPitch = PowTwoAlign(*pPitch, pitchAlign); } else { *pPitch = pitchAlign + *pPitch - 1; *pPitch /= pitchAlign; *pPitch *= pitchAlign; } if (padDims > 1) *pHeight = PowTwoAlign(*pHeight, heightAlign); if (padDims > 2 || thickness > 1) { if (isCube && (!GetFlagNoCubeMipSlicesPad() || cubeAsArray)) *pSlices = NextPow2(*pSlices); if (thickness > 1) *pSlices = PowTwoAlign(*pSlices, sliceAlign); } } void _ComputeSurfaceAlignmentsMicroTiled(E_HWTILEMODE tileMode, uint32 bpp, AddrSurfaceFlags flags, uint32 numSamples, uint32& baseAlignOut, uint32& pitchAlignOut, uint32& heightAlignOut) { if (bpp == 96 || bpp == 48 || bpp == 24) bpp /= 3u; uint32 thickness = TM_GetThickness(tileMode); baseAlignOut = m_pipeInterleaveBytes; pitchAlignOut = std::max(8u, m_pipeInterleaveBytes / bpp / numSamples / thickness); heightAlignOut = 8; _AdjustPitchAlignment(flags, pitchAlignOut); } void _ComputeSurfaceAlignmentsLinear(E_HWTILEMODE tileMode, uint32 bpp, AddrSurfaceFlags flags, uint32* pBaseAlign, uint32* pPitchAlign, uint32* pHeightAlign) { cemu_assert_debug(tileMode == E_HWTILEMODE::TM_LINEAR_GENERAL || tileMode == E_HWTILEMODE::TM_LINEAR_ALIGNED); if (tileMode == E_HWTILEMODE::TM_LINEAR_ALIGNED) { uint32 pixelsPerPipeInterleave = 8 * m_pipeInterleaveBytes / bpp; *pBaseAlign = m_pipeInterleaveBytes; *pPitchAlign = std::max<uint32>(64, pixelsPerPipeInterleave); *pHeightAlign = 1; } else if (tileMode == E_HWTILEMODE::TM_LINEAR_GENERAL) { *pBaseAlign = 1; *pPitchAlign = 1; *pHeightAlign = 1; } _AdjustPitchAlignment(flags, *pPitchAlign); } void _ComputeSurfaceInfoLinear(E_HWTILEMODE tileMode, uint32 bpp, uint32 numSamples, uint32 pitch, uint32 height, uint32 numSlices, uint32 mipLevel, uint32 padDims, AddrSurfaceFlags flags, AddrSurfaceInfo_OUT* pOut) { uint32 heightAlign; uint32 pitchAlign; uint32 baseAlign; uint32 expPitch = pitch; uint32 expHeight = height; uint32 expNumSlices = numSlices; _ComputeSurfaceAlignmentsLinear(tileMode, bpp, flags, &baseAlign, &pitchAlign, &heightAlign); if (flags.linearWA && mipLevel == 0) { expPitch /= 3u; expPitch = NextPow2(expPitch); } if (mipLevel) { expPitch = NextPow2(expPitch); expHeight = NextPow2(expHeight); if (flags.dimCube) { expNumSlices = numSlices; if (numSlices <= 1) padDims = 2; else padDims = 0; } else { expNumSlices = NextPow2(numSlices); } } uint32 microTileThickness = TM_GetThickness(tileMode); PadDimensions(tileMode, padDims, flags.dimCube, flags.cubeAsArray, &expPitch, pitchAlign, &expHeight, heightAlign, &expNumSlices, microTileThickness); if (flags.linearWA && mipLevel == 0) expPitch *= 3; uint32 slices = expNumSlices * numSamples / microTileThickness; pOut->pitch = expPitch; pOut->height = expHeight; pOut->depth = expNumSlices; pOut->surfSize = (((uint64)expHeight * expPitch * slices * bpp * numSamples + 7) / 8); pOut->baseAlign = baseAlign; pOut->pitchAlign = pitchAlign; pOut->heightAlign = heightAlign; pOut->depthAlign = microTileThickness; } void _ComputeSurfaceInfoMicroTiled(E_HWTILEMODE tileMode, uint32 bpp, uint32 numSamples, uint32 pitch, uint32 height, uint32 numSlices, uint32 mipLevel, uint32 padDims, AddrSurfaceFlags flags, AddrSurfaceInfo_OUT* pOut) { E_HWTILEMODE expTileMode = tileMode; uint32 expPitch = pitch; uint32 expHeight = height; uint32 expNumSlices = numSlices; uint32 microTileThickness = TM_GetThickness(tileMode); if (mipLevel) { expPitch = NextPow2(pitch); expHeight = NextPow2(height); if (flags.dimCube) { expNumSlices = numSlices; if (numSlices <= 1) padDims = 2; else padDims = 0; } else { expNumSlices = NextPow2(numSlices); } if (expTileMode == E_HWTILEMODE::TM_1D_TILED_THICK && expNumSlices < 4) { expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; microTileThickness = 1; } } uint32 heightAlign; uint32 pitchAlign; uint32 baseAlign; _ComputeSurfaceAlignmentsMicroTiled(expTileMode, bpp, flags, numSamples, /* outputs: */ baseAlign, pitchAlign, heightAlign); PadDimensions(expTileMode, padDims, flags.dimCube, flags.cubeAsArray, &expPitch, pitchAlign, &expHeight, heightAlign, &expNumSlices, microTileThickness); pOut->pitch = expPitch; pOut->height = expHeight; pOut->depth = expNumSlices; pOut->surfSize = (((uint64)expHeight * expPitch * expNumSlices * bpp * numSamples + 7) / 8); pOut->hwTileMode = expTileMode; pOut->baseAlign = baseAlign; pOut->pitchAlign = pitchAlign; pOut->heightAlign = heightAlign; pOut->depthAlign = microTileThickness; } void _ComputeSurfaceInfoMacroTiled(E_HWTILEMODE tileMode, E_HWTILEMODE baseTileMode, uint32 bpp, uint32 numSamples, uint32 pitch, uint32 height, uint32 numSlices, uint32 mipLevel, uint32 padDims, AddrSurfaceFlags flags, AddrSurfaceInfo_OUT* pOut) { uint32 macroWidth, macroHeight; uint32 baseAlign, heightAlign, pitchAlign; uint32 expPitch = pitch; uint32 expHeight = height; uint32 expNumSlices = numSlices; E_HWTILEMODE expTileMode = tileMode; uint32 microTileThickness = TM_GetThickness(tileMode); if (mipLevel) { expPitch = NextPow2(pitch); expHeight = NextPow2(height); expNumSlices = NextPow2(numSlices); if (flags.dimCube) { // cubemap expNumSlices = numSlices; padDims = numSlices <= 1 ? 2 : 0; } if (expTileMode == E_HWTILEMODE::TM_2D_TILED_THICK && expNumSlices < 4) { expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; microTileThickness = 1; } } uint32 pitchAlignFactor = std::max<uint32>((m_pipeInterleaveBytes >> 3) / bpp, 1); if (tileMode != baseTileMode && mipLevel != 0 && TM_IsThickAndMacroTiled(baseTileMode) && !TM_IsThickAndMacroTiled(tileMode)) { _ComputeSurfaceAlignmentsMacroTiled(baseTileMode, bpp, flags, numSamples, &baseAlign, &pitchAlign, &heightAlign, ¯oWidth, ¯oHeight); if (expPitch < pitchAlign * pitchAlignFactor || expHeight < heightAlign) { _ComputeSurfaceInfoMicroTiled(E_HWTILEMODE::TM_1D_TILED_THIN1, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags, pOut); return; } } _ComputeSurfaceAlignmentsMacroTiled(tileMode, bpp, flags, numSamples, &baseAlign, &pitchAlign, &heightAlign, ¯oWidth, ¯oHeight); uint32 bankSwappedWidth = ComputeSurfaceBankSwappedWidth(tileMode, bpp, numSamples, pitch); if (bankSwappedWidth > pitchAlign) pitchAlign = bankSwappedWidth; PadDimensions(tileMode, padDims, flags.dimCube, flags.cubeAsArray, &expPitch, pitchAlign, &expHeight, heightAlign, &expNumSlices, microTileThickness); pOut->pitch = expPitch; pOut->height = expHeight; pOut->depth = expNumSlices; pOut->surfSize = (((uint64)expHeight * expPitch * expNumSlices * bpp * numSamples + 7) / 8); pOut->hwTileMode = expTileMode; pOut->baseAlign = baseAlign; pOut->pitchAlign = pitchAlign; pOut->heightAlign = heightAlign; pOut->depthAlign = microTileThickness; } COMPUTE_SURFACE_RESULT ComputeSurfaceInfoEx(const AddrSurfaceInfo_IN* pIn, AddrSurfaceInfo_OUT* pOut) { Latte::E_HWTILEMODE tileMode = pIn->tileMode; Latte::E_HWTILEMODE baseTileMode = tileMode; uint32 bpp = pIn->bpp; uint32 numSamples = std::max<uint32>(pIn->numSamples, 1); uint32 pitch = pIn->width; uint32 height = pIn->height; uint32 numSlices = pIn->numSlices; uint32 mipLevel = pIn->mipLevel; AddrSurfaceFlags flags = pIn->flags; uint32 padDims = 0; if (flags.dimCube && mipLevel == 0) padDims = 2; if (flags.fmask) tileMode = ConvertTileModeToNonBankSwappedMode(tileMode); else tileMode = _ComputeSurfaceMipLevelTileMode(tileMode, bpp, mipLevel, pitch, height, numSlices, numSamples, flags.depth, false); switch (tileMode) { case E_HWTILEMODE::TM_LINEAR_GENERAL: case E_HWTILEMODE::TM_LINEAR_ALIGNED: _ComputeSurfaceInfoLinear(tileMode, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags, pOut); pOut->hwTileMode = tileMode; break; case E_HWTILEMODE::TM_1D_TILED_THIN1: case E_HWTILEMODE::TM_1D_TILED_THICK: _ComputeSurfaceInfoMicroTiled(tileMode, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags, pOut); break; case E_HWTILEMODE::TM_2D_TILED_THIN1: case E_HWTILEMODE::TM_2D_TILED_THIN2: case E_HWTILEMODE::TM_2D_TILED_THIN4: case E_HWTILEMODE::TM_2D_TILED_THICK: case E_HWTILEMODE::TM_2B_TILED_THIN1: case E_HWTILEMODE::TM_2B_TILED_THIN2: case E_HWTILEMODE::TM_2B_TILED_THIN4: case E_HWTILEMODE::TM_2B_TILED_THICK: case E_HWTILEMODE::TM_3D_TILED_THIN1: case E_HWTILEMODE::TM_3D_TILED_THICK: case E_HWTILEMODE::TM_3B_TILED_THIN1: case E_HWTILEMODE::TM_3B_TILED_THICK: _ComputeSurfaceInfoMacroTiled(tileMode, baseTileMode, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags, pOut); break; default: return COMPUTE_SURFACE_RESULT::UNKNOWN_FORMAT; } return COMPUTE_SURFACE_RESULT::RESULT_OK; } void RestoreSurfaceInfo(uint32 elemMode, uint32 expandX, uint32 expandY, uint32*pBpp, uint32 *pWidth, uint32*pHeight) { if (pBpp) { uint32 bpp = *pBpp; uint32 originalBits; switch (elemMode) { case 4: originalBits = expandY * expandX * bpp; break; case 5: case 6: originalBits = bpp / expandX / expandY; break; case 7: case 8: originalBits = *pBpp; break; case 9: case 12: originalBits = 64; break; case 10: case 11: case 13: originalBits = 128; break; case 0: case 1: case 2: case 3: originalBits = *pBpp; break; default: originalBits = *pBpp; break; } *pBpp = originalBits; } if (pWidth && pHeight) { uint32 width = *pWidth; uint32 height = *pHeight; if (expandX > 1 || expandY > 1) { if (elemMode == 4) { width /= expandX; height /= expandY; } else { width *= expandX; height *= expandY; } } *pWidth = std::max<uint32>(width, 1); *pHeight = std::max<uint32>(height, 1); } } COMPUTE_SURFACE_RESULT ComputeSurfaceInfo(AddrSurfaceInfo_IN* pIn, AddrSurfaceInfo_OUT* pOut) { if (GetFillSizeFieldsFlags() == 1 && (pIn->size != sizeof(AddrSurfaceInfo_IN) || pOut->size != sizeof(AddrSurfaceInfo_OUT))) return COMPUTE_SURFACE_RESULT::BAD_SIZE_FIELD; cemu_assert_debug(pIn->bpp <= 128); ComputeMipLevelDimensions(&pIn->width, &pIn->height, &pIn->numSlices, pIn->flags, pIn->format, pIn->mipLevel); uint32 width = pIn->width; uint32 height = pIn->height; uint32 bpp = pIn->bpp; uint32 elemMode; uint32 expandX = 1; uint32 expandY = 1; cemu_assert_debug(pIn->tileIndex == 0 && pIn->pTileInfo == nullptr); pOut->pixelBits = pIn->bpp; if (pIn->format != E_HWSURFFMT::INVALID_FORMAT) { bpp = GetBitsPerPixel(pIn->format, &elemMode, &expandX, &expandY); if (pIn->tileMode == E_HWTILEMODE::TM_LINEAR_ALIGNED && elemMode == 4 && expandX == 3) pIn->flags.linearWA = true; AdjustSurfaceInfo(elemMode, expandX, expandY, &bpp, &width, &height); pIn->width = width; pIn->height = height; pIn->bpp = bpp; } else if (pIn->bpp != 0) { pIn->width = std::max<uint32>(pIn->width, 1); pIn->height = std::max<uint32>(pIn->height, 1); } else return COMPUTE_SURFACE_RESULT::UNKNOWN_FORMAT; COMPUTE_SURFACE_RESULT r = ComputeSurfaceInfoEx(pIn, pOut); if (r != COMPUTE_SURFACE_RESULT::RESULT_OK) return r; pOut->bpp = pIn->bpp; pOut->pixelPitch = pOut->pitch; pOut->pixelHeight = pOut->height; if (pIn->format != E_HWSURFFMT::INVALID_FORMAT && (!pIn->flags.linearWA || pIn->mipLevel == 0)) { RestoreSurfaceInfo(elemMode, expandX, expandY, &bpp, &pOut->pixelPitch, &pOut->pixelHeight); } uint32 sliceFlags = GetSliceComputingFlags(); if (sliceFlags) { if (sliceFlags == 1) pOut->sliceSize = (pOut->height * pOut->pitch * pOut->bpp * pIn->numSamples + 7) / 8; } else if (pIn->flags.dim3D) { pOut->sliceSize = (uint32)(pOut->surfSize); } else { if(pOut->surfSize == 0 && pOut->depth == 0) pOut->sliceSize = 0; // edge case for (1D)_ARRAY textures with res 0/0/0 else pOut->sliceSize = (uint32)(pOut->surfSize / pOut->depth); if (pIn->slice == pIn->numSlices - 1 && pIn->numSlices > 1) pOut->sliceSize += pOut->sliceSize * (pOut->depth - pIn->numSlices); } pOut->pitchTileMax = (pOut->pitch >> 3) - 1; pOut->heightTileMax = (pOut->height >> 3) - 1; pOut->sliceTileMax = ((pOut->height * pOut->pitch >> 6) - 1); return COMPUTE_SURFACE_RESULT::RESULT_OK; } void GX2CalculateSurfaceInfo(Latte::E_GX2SURFFMT surfaceFormat, uint32 surfaceWidth, uint32 surfaceHeight, uint32 surfaceDepth, E_DIM surfaceDim, E_GX2TILEMODE surfaceTileMode, uint32 surfaceAA, uint32 level, AddrSurfaceInfo_OUT* pSurfOut, bool optimizeForDepthBuffer, bool optimizeForScanBuffer) { AddrSurfaceInfo_IN surfInfoIn = { 0 }; Latte::E_HWSURFFMT hwFormat = Latte::GetHWFormat(surfaceFormat); memset(pSurfOut, 0, sizeof(AddrSurfaceInfo_OUT)); pSurfOut->size = sizeof(AddrSurfaceInfo_OUT); if (surfaceTileMode == E_GX2TILEMODE::TM_LINEAR_SPECIAL) { uint32 numSamples = 1 << surfaceAA; uint32 blockSize = IsCompressedFormat(hwFormat) ? 4 : 1; uint32 width = ((surfaceWidth >> level) + blockSize - 1) & ~(blockSize - 1); pSurfOut->bpp = Latte::GetFormatBits(hwFormat); pSurfOut->pitch = width / blockSize; pSurfOut->pixelBits = pSurfOut->bpp; pSurfOut->baseAlign = 1; pSurfOut->pitchAlign = 1; pSurfOut->heightAlign = 1; pSurfOut->depthAlign = 1; switch (surfaceDim) { case E_DIM::DIM_1D: pSurfOut->height = 1; pSurfOut->depth = 1; break; case E_DIM::DIM_2D: pSurfOut->height = std::max<uint32>(surfaceHeight >> level, 1); pSurfOut->depth = 1; break; case E_DIM::DIM_3D: pSurfOut->height = std::max<uint32>(surfaceHeight >> level, 1); pSurfOut->depth = std::max<uint32>(surfaceDepth >> level, 1); break; case E_DIM::DIM_CUBEMAP: pSurfOut->height = std::max<uint32>(surfaceHeight >> level, 1); pSurfOut->depth = std::max<uint32>(surfaceDepth, 6); break; case E_DIM::DIM_1D_ARRAY: pSurfOut->height = 1; pSurfOut->depth = surfaceDepth; break; case E_DIM::DIM_2D_ARRAY: pSurfOut->height = std::max<uint32>(surfaceHeight >> level, 1); pSurfOut->depth = surfaceDepth; break; default: break; } pSurfOut->height = (~(blockSize - 1) & (pSurfOut->height + blockSize - 1)) / (uint64)blockSize; pSurfOut->pixelPitch = ~(blockSize - 1) & ((surfaceWidth >> level) + blockSize - 1); pSurfOut->pixelPitch = std::max<uint32>(pSurfOut->pixelPitch, blockSize); pSurfOut->pixelHeight = ~(blockSize - 1) & ((surfaceHeight >> level) + blockSize - 1); pSurfOut->pixelHeight = std::max<uint32>(pSurfOut->pixelHeight, blockSize); pSurfOut->pitch = std::max<uint32>(pSurfOut->pitch, 1); pSurfOut->height = std::max<uint32>(pSurfOut->height, 1); pSurfOut->surfSize = pSurfOut->bpp * numSamples * pSurfOut->depth * pSurfOut->height * pSurfOut->pitch >> 3; if (surfaceDim == E_DIM::DIM_3D) pSurfOut->sliceSize = (uint32)pSurfOut->surfSize; else { if(pSurfOut->surfSize == 0 && pSurfOut->depth == 0) pSurfOut->sliceSize = 0; else pSurfOut->sliceSize = (uint32)(pSurfOut->surfSize / pSurfOut->depth); } pSurfOut->pitchTileMax = (pSurfOut->pitch >> 3) - 1; pSurfOut->heightTileMax = (pSurfOut->height >> 3) - 1; pSurfOut->sliceTileMax = (pSurfOut->height * pSurfOut->pitch >> 6) - 1; } else { memset(&surfInfoIn, 0, sizeof(AddrSurfaceInfo_IN)); surfInfoIn.size = sizeof(AddrSurfaceInfo_IN); if (!IsValidHWTileMode((E_HWTILEMODE)surfaceTileMode)) { // cemuLog_log(LogType::Force, "Unexpected TileMode {} in AddrLib", (uint32)surfaceTileMode); surfaceTileMode = (E_GX2TILEMODE)((uint32)surfaceTileMode & 0xF); } surfInfoIn.tileMode = MakeHWTileMode(surfaceTileMode); surfInfoIn.format = hwFormat; surfInfoIn.bpp = Latte::GetFormatBits(hwFormat); surfInfoIn.numSamples = 1 << surfaceAA; surfInfoIn.numFrags = surfInfoIn.numSamples; surfInfoIn.width = std::max<uint32>(surfaceWidth >> level, 1); switch (surfaceDim) { case E_DIM::DIM_1D: surfInfoIn.height = 1; surfInfoIn.numSlices = 1; break; case E_DIM::DIM_2D: surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); surfInfoIn.numSlices = 1; break; case E_DIM::DIM_3D: surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); surfInfoIn.numSlices = std::max<uint32>(surfaceDepth >> level, 1); surfInfoIn.flags.dim3D = true; break; case E_DIM::DIM_CUBEMAP: surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); surfInfoIn.numSlices = std::max<uint32>(surfaceDepth, 6); surfInfoIn.flags.dimCube = true; break; case E_DIM::DIM_1D_ARRAY: surfInfoIn.height = 1; surfInfoIn.numSlices = surfaceDepth; break; case E_DIM::DIM_2D_ARRAY: surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); surfInfoIn.numSlices = surfaceDepth; break; case E_DIM::DIM_2D_MSAA: surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); surfInfoIn.numSlices = 1; break; case E_DIM::DIM_2D_ARRAY_MSAA: surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); surfInfoIn.numSlices = surfaceDepth; break; default: break; } surfInfoIn.slice = 0; surfInfoIn.mipLevel = level; surfInfoIn.flags.inputIsBase = (level == 0); surfInfoIn.flags.depth = optimizeForDepthBuffer; surfInfoIn.flags.display = optimizeForScanBuffer; ComputeSurfaceInfo(&surfInfoIn, pSurfOut); } } uint32 CalculateMipOffset(Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, E_DIM dim, E_HWTILEMODE tileMode, uint32 swizzle, uint32 surfaceAA, sint32 mipIndex) { cemu_assert_debug(IsValidHWTileMode(tileMode)); AddrSurfaceInfo_OUT surfaceInfo; uint32 currentMipOffset = 0; E_HWTILEMODE lastTileMode = tileMode; uint32 prevSize = 0; for (sint32 level = 1; level <= mipIndex; level++) { GX2CalculateSurfaceInfo(format, width, height, depth, dim, MakeGX2TileMode(tileMode), surfaceAA, level, &surfaceInfo); if (level) { uint32 pad = 0; if (TM_IsMacroTiled(lastTileMode) && !TM_IsMacroTiled(surfaceInfo.hwTileMode)) { if (level > 1) pad = swizzle & 0xFFFF; } pad += (surfaceInfo.baseAlign - (currentMipOffset % surfaceInfo.baseAlign)) % surfaceInfo.baseAlign; currentMipOffset = currentMipOffset + pad + prevSize; } else { currentMipOffset = prevSize; } lastTileMode = surfaceInfo.hwTileMode; prevSize = (uint32)surfaceInfo.surfSize; } return currentMipOffset; } // Calculate aligned address and size of a given slice and mip level // For thick-tiled surfaces this returns the area of the whole thick tile (4 slices per thick tile) and the relative slice index within the tile is returned in subSliceIndex void CalculateMipAndSliceAddr(uint32 physAddr, uint32 physMipAddr, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, Latte::E_DIM dim, Latte::E_HWTILEMODE tileMode, uint32 swizzle, uint32 surfaceAA, sint32 mipIndex, sint32 sliceIndex, uint32* outputSliceOffset, uint32* outputSliceSize, sint32* subSliceIndex) { cemu_assert_debug((uint32)tileMode < 16); // only hardware tilemodes allowed AddrSurfaceInfo_OUT surfaceInfo; uint32 currentMipOffset = 0; Latte::E_HWTILEMODE lastTileMode = tileMode; uint32 prevSize = 0; for (sint32 level = 1; level <= mipIndex; level++) { GX2CalculateSurfaceInfo(format, width, height, depth, dim, MakeGX2TileMode(tileMode), surfaceAA, level, &surfaceInfo); // extract swizzle from mip-pointer if macro tiled if (level == 1 && TM_IsMacroTiled(surfaceInfo.hwTileMode)) { swizzle = physMipAddr & 0x700; physMipAddr &= ~0x700; } cemu_assert_debug(IsValidHWTileMode(surfaceInfo.hwTileMode)); if (level) { uint32 pad = 0; if (TM_IsMacroTiled(lastTileMode) && !TM_IsMacroTiled(surfaceInfo.hwTileMode)) { if (level > 1) pad = swizzle & 0xFFFF; } pad += (surfaceInfo.baseAlign - (currentMipOffset % surfaceInfo.baseAlign)) % surfaceInfo.baseAlign; currentMipOffset = currentMipOffset + pad + prevSize; } else { currentMipOffset = prevSize; } lastTileMode = surfaceInfo.hwTileMode; prevSize = (uint32)surfaceInfo.surfSize; } // calculate slice offset if( mipIndex == 0 ) // make sure surfaceInfo is initialized GX2CalculateSurfaceInfo(format, width, height, depth, dim, MakeGX2TileMode(tileMode), surfaceAA, 0, &surfaceInfo); uint32 sliceOffset = 0; uint32 sliceSize = 0; // surfaceInfo.sliceSize isn't always correct (especially when depth is misaligned with 4 for THICK tile modes?) so we calculate it manually // this formula only works because both pitch and height are aligned to micro/macro blocks by GX2CalculateSurfaceInfo, normally we would have to use the tile dimensions to calculate the size uint32 correctedSliceSize = surfaceInfo.pitch*surfaceInfo.height*surfaceInfo.bpp / 8; if (TM_IsThick(surfaceInfo.hwTileMode)) { // 4 slices are interleaved sliceOffset = (sliceIndex&~3) * correctedSliceSize; sliceSize = correctedSliceSize * 4; *subSliceIndex = sliceIndex & 3; } else { sliceOffset = sliceIndex * correctedSliceSize; sliceSize = correctedSliceSize; *subSliceIndex = 0; } if (mipIndex) { sliceOffset += physMipAddr; } else { sliceOffset += physAddr; } *outputSliceOffset = currentMipOffset + sliceOffset; *outputSliceSize = sliceSize; } }; ================================================ FILE: src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h ================================================ #pragma once #include "Cafe/HW/Latte/ISA/LatteReg.h" namespace LatteAddrLib { static const uint32 m_banks = 4; static const uint32 m_banksBitcount = 2; static const uint32 m_pipes = 2; static const uint32 m_pipesBitcount = 1; static const uint32 m_pipeInterleaveBytes = 256; static const uint32 m_pipeInterleaveBytesBitcount = 8; static const uint32 m_rowSize = 2048; static const uint32 m_swapSize = 256; static const uint32 m_splitSize = 2048; static const uint32 m_chipFamily = 2; union AddrSurfaceFlags { uint32 rawValue; struct { bool color : 1; bool depth : 1; bool stencil : 1; bool uknFlag4 : 1; bool dimCube : 1; bool dim3D : 1; bool fmask : 1; bool cubeAsArray : 1; bool uknFlag8 : 1; bool linearWA : 1; bool uknFlag10 : 1; bool uknFlag11 : 1; bool inputIsBase : 1; bool display : 1; }; }; struct AddrTileInfo { uint32 banks; uint32 bankWidth; uint32 bankHeight; uint32 macroAspectRatio; uint32 tileSplitBytes; uint32 pipeConfig; }; struct AddrSurfaceInfo_IN { uint32 size; Latte::E_HWTILEMODE tileMode; Latte::E_HWSURFFMT format; uint32 bpp; uint32 numSamples; uint32 width; uint32 height; uint32 numSlices; uint32 slice; uint32 mipLevel; AddrSurfaceFlags flags; uint32 numFrags; AddrTileInfo* pTileInfo; sint32 tileIndex; }; struct AddrSurfaceInfo_OUT { uint32 size; uint32 pitch; uint32 height; uint32 depth; uint64 surfSize; Latte::E_HWTILEMODE hwTileMode; uint32 baseAlign; uint32 pitchAlign; uint32 heightAlign; uint32 depthAlign; uint32 bpp; uint32 pixelPitch; uint32 pixelHeight; uint32 pixelBits; uint32 sliceSize; uint32 pitchTileMax; uint32 heightTileMax; uint32 sliceTileMax; AddrTileInfo* pTileInfo; uint32 tileType; sint32 tileIndex; }; inline bool IsHWTileMode(Latte::E_GX2TILEMODE tileMode) { return (uint32)tileMode < 16; } inline bool IsValidHWTileMode(Latte::E_HWTILEMODE tileMode) { return (uint32)tileMode < 16; } inline bool TM_IsThick(Latte::E_HWTILEMODE tileMode) { return tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THICK || tileMode == Latte::E_HWTILEMODE::TM_2D_TILED_THICK || tileMode == Latte::E_HWTILEMODE::TM_2B_TILED_THICK || tileMode == Latte::E_HWTILEMODE::TM_3D_TILED_THICK || tileMode == Latte::E_HWTILEMODE::TM_3B_TILED_THICK; } inline uint32 TM_GetThickness(Latte::E_HWTILEMODE tileMode) { return TM_IsThick(tileMode) ? 4 : 1; } inline bool TM_IsThickAndMacroTiled(Latte::E_HWTILEMODE tileMode) { return tileMode == Latte::E_HWTILEMODE::TM_2D_TILED_THICK || tileMode == Latte::E_HWTILEMODE::TM_2B_TILED_THICK || tileMode == Latte::E_HWTILEMODE::TM_3D_TILED_THICK || tileMode == Latte::E_HWTILEMODE::TM_3B_TILED_THICK; } uint32 ComputeSurfaceRotationFromTileMode(Latte::E_HWTILEMODE tileMode); uint32 ComputeSurfaceBankSwappedWidth(Latte::E_HWTILEMODE tileMode, uint32 bpp, uint32 numSamples, uint32 pitch); void GX2CalculateSurfaceInfo(Latte::E_GX2SURFFMT surfaceFormat, uint32 surfaceWidth, uint32 surfaceHeight, uint32 surfaceDepth, Latte::E_DIM surfaceDim, Latte::E_GX2TILEMODE surfaceTileMode, uint32 surfaceAA, uint32 level, AddrSurfaceInfo_OUT* pSurfOut, bool optimizeForDepthBuffer = false, bool optimizeForScanBuffer = false); uint32 CalculateMipOffset(Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, Latte::E_DIM dim, Latte::E_HWTILEMODE tileMode, uint32 swizzle, uint32 surfaceAA, sint32 mipIndex); void CalculateMipAndSliceAddr(uint32 physAddr, uint32 physMipAddr, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, Latte::E_DIM dim, Latte::E_HWTILEMODE tileMode, uint32 swizzle, uint32 surfaceAA, sint32 mipIndex, sint32 sliceIndex, uint32* outputSliceOffset, uint32* outputSliceSize, sint32* subSliceIndex); /* Pixel access */ uint32 ComputeSurfaceAddrFromCoordLinear(uint32 x, uint32 y, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 numSlices); uint32 ComputeSurfaceAddrFromCoordMicroTiled(uint32 x, uint32 y, uint32 slice, uint32 bpp, uint32 pitch, uint32 height, Latte::E_HWTILEMODE tileMode, bool isDepth); uint32 ComputeSurfaceAddrFromCoordMacroTiled(uint32 x, uint32 y, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 numSamples, Latte::E_HWTILEMODE tileMode, bool isDepth, uint32 pipeSwizzle, uint32 bankSwizzle); uint32 _ComputePixelIndexWithinMicroTile(uint32 x, uint32 y, uint32 z, uint32 bpp, Latte::E_HWTILEMODE tileMode, uint32 microTileType); // addr lib (optimized access) struct CachedSurfaceAddrInfo { uint32 slice; uint32 sample; uint32 bpp; uint32 pitch; uint32 height; uint32 depth; uint32 numSamples; // for AA Latte::E_HWTILEMODE tileMode; int isDepth; uint32 pipeSwizzle; uint32 bankSwizzle; uint32 pixelOffsetMul; uint32 bytesPerPixel; // calculated data uint32 microTileThickness; uint32 microTileBits; uint32 microTileBytes; uint32 microTileType; uint32 rotation; // bank rotation uint32 macroTilePitch; uint32 macroTileHeight; uint32 macroTilePitchBits; uint32 macroTileHeightBits; uint32 macroTilesPerRow; uint32 macroTileBytes; uint32 bankSwapWidth; uint32 sliceBytes; uint32 sliceIn; // const uint32 c0; // micro tile pixel index table uint16 microTilePixelIndexTable[8 * 8 * 8]; }; void SetupCachedSurfaceAddrInfo(CachedSurfaceAddrInfo* info, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 depth, uint32 numSamples, Latte::E_HWTILEMODE tileMode, int isDepth, uint32 pipeSwizzle, uint32 bankSwizzle); uint32 ComputeSurfaceAddrFromCoordMacroTiledCached(uint32 x, uint32 y, CachedSurfaceAddrInfo* info); uint32 ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(uint32 x, uint32 y, CachedSurfaceAddrInfo* info); }; ================================================ FILE: src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib_Coord.cpp ================================================ #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" #include "Cafe/OS/libs/gx2/GX2_Surface.h" using namespace Latte; namespace LatteAddrLib { #if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD unsigned char _BitScanReverse(uint32* _Index, uint32 _Mask) { if (!_Mask) return 0; *_Index = 31 - __builtin_clzl(_Mask); return 1; } #endif static const uint32 bankSwapOrder[] = { 0, 1, 3, 2 }; uint32 _GetMicroTileType(bool isDepth) { return isDepth ? 1 : 0; } uint32 _ComputePixelIndexWithinMicroTile(uint32 x, uint32 y, uint32 z, uint32 bpp, E_HWTILEMODE tileMode, uint32 microTileType) { cemu_assert_debug(microTileType == 0 || microTileType == 1); uint32 pixelBit0, pixelBit1, pixelBit2, pixelBit3, pixelBit4, pixelBit5, pixelBit6, pixelBit7, pixelBit8; pixelBit6 = 0; pixelBit7 = 0; pixelBit8 = 0; uint32 thickness = LatteAddrLib::TM_GetThickness(tileMode); if (microTileType) { pixelBit0 = x & 1; pixelBit1 = y & 1; pixelBit2 = (x & 2) >> 1; pixelBit3 = (y & 2) >> 1; pixelBit4 = (x & 4) >> 2; pixelBit5 = (y & 4) >> 2; } else { switch (bpp) { case 8: pixelBit0 = x & 1; pixelBit1 = (x & 2) >> 1; pixelBit2 = (x & 4) >> 2; pixelBit3 = (y & 2) >> 1; pixelBit4 = y & 1; pixelBit5 = (y & 4) >> 2; break; case 0x10: pixelBit0 = x & 1; pixelBit1 = (x & 2) >> 1; pixelBit2 = (x & 4) >> 2; pixelBit3 = y & 1; pixelBit4 = (y & 2) >> 1; pixelBit5 = (y & 4) >> 2; break; case 0x20: case 0x60: pixelBit0 = x & 1; pixelBit1 = (x & 2) >> 1; pixelBit2 = y & 1; pixelBit3 = (x & 4) >> 2; pixelBit4 = (y & 2) >> 1; pixelBit5 = (y & 4) >> 2; break; case 0x40: pixelBit0 = x & 1; pixelBit1 = y & 1; pixelBit2 = (x & 2) >> 1; pixelBit3 = (x & 4) >> 2; pixelBit4 = (y & 2) >> 1; pixelBit5 = (y & 4) >> 2; break; case 0x80: pixelBit0 = y & 1; pixelBit1 = x & 1; pixelBit2 = (x & 2) >> 1; pixelBit3 = (x & 4) >> 2; pixelBit4 = (y & 2) >> 1; pixelBit5 = (y & 4) >> 2; break; default: pixelBit0 = x & 1; pixelBit1 = (x & 2) >> 1; pixelBit2 = y & 1; pixelBit3 = (x & 4) >> 2; pixelBit4 = (y & 2) >> 1; pixelBit5 = (y & 4) >> 2; break; } } if (thickness > 1) { pixelBit6 = z & 1; pixelBit7 = (z & 2) >> 1; } return (pixelBit8 << 8) | (pixelBit7 << 7) | (pixelBit6 << 6) | (pixelBit5 << 5) | (pixelBit4 << 4) | (pixelBit3 << 3) | (pixelBit2 << 2) | (pixelBit1 << 1) | pixelBit0; } uint32 _ComputePipeFromCoordWoRotation(uint32 x, uint32 y) { // hardcoded to assume 2 pipes uint32 pipe; pipe = ((y >> 3) ^ (x >> 3)) & 1; return pipe; } uint32 _ComputeBankFromCoordWoRotation(uint32 x, uint32 y) { uint32 bank; if (m_banks == 4) { uint32 bankNew = (y >> 4) & 3; bankNew = ((bankNew >> 1) | (bankNew << 1)); // swap lowest two bits bankNew ^= (x >> 3); bankNew &= 3; bank = bankNew; } else if (m_banks == 8) { cemu_assert_unimplemented(); bank = 0; } else { bank = 0; } return bank; } uint32 ComputeSurfaceAddrFromCoordLinear(uint32 x, uint32 y, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 numSlices) { uint32 pixelIndex = x + pitch * y + (slice + numSlices * sample) * height * pitch; return (pixelIndex * bpp) / 8; } uint32 ComputeSurfaceAddrFromCoordMicroTiled(uint32 x, uint32 y, uint32 slice, uint32 bpp, uint32 pitch, uint32 height, Latte::E_HWTILEMODE tileMode, bool isDepth) { uint32 microTileThickness = (tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THICK) ? 4 : 1; uint32 microTilesPerRow = pitch >> 3; uint32 microTileIndexX = x >> 3; uint32 microTileIndexY = y >> 3; uint32 microTileBytes = microTileThickness * (((bpp << 6) + 7) >> 3); // each tile is 8x8 or 8x8x4 uint32 microTileOffset = microTileBytes * (uint64)((x >> 3) + (pitch >> 3) * (y >> 3)); uint32 sliceBytes = (height * (uint64)pitch * microTileThickness * bpp + 7) / 8; uint32 sliceOffset = sliceBytes * (slice / microTileThickness); uint32 pixelIndex = _ComputePixelIndexWithinMicroTile(x, y, slice, bpp, tileMode, _GetMicroTileType(isDepth)); uint32 pixelOffset = bpp * pixelIndex; pixelOffset >>= 3; return pixelOffset + microTileOffset + sliceOffset; } uint32 ComputeSurfaceAddrFromCoordMacroTiled(uint32 x, uint32 y, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 numSamples, Latte::E_HWTILEMODE tileMode, bool isDepth, uint32 pipeSwizzle, uint32 bankSwizzle) { uint32 microTileThickness = LatteAddrLib::TM_GetThickness((E_HWTILEMODE)tileMode); uint32 microTileBits = numSamples * bpp * (microTileThickness * (8 * 8)); uint32 microTileBytes = microTileBits >> 3; uint32 pixelIndex = _ComputePixelIndexWithinMicroTile(x, y, slice, bpp, tileMode, _GetMicroTileType(isDepth)); uint32 sampleOffset, pixelOffset; if (isDepth) { sampleOffset = bpp * sample; pixelOffset = numSamples * bpp * pixelIndex; } else { sampleOffset = sample * (microTileBits / numSamples); pixelOffset = bpp * pixelIndex; } uint32 elemOffset = pixelOffset + sampleOffset; uint32 bytesPerSample = microTileBytes / numSamples; uint32 sampleSlice, numSampleSplits; if (numSamples <= 1 || microTileBytes <= m_splitSize) { numSampleSplits = 1; sampleSlice = 0; } else { uint32 samplesPerSlice = m_splitSize / bytesPerSample; numSampleSplits = numSamples / samplesPerSlice; numSamples = samplesPerSlice; sampleSlice = elemOffset / (microTileBits / numSampleSplits); elemOffset %= microTileBits / numSampleSplits; } elemOffset >>= 3; uint32 pipe = _ComputePipeFromCoordWoRotation(x, y); uint32 bank = _ComputeBankFromCoordWoRotation(x, y); uint32 bankPipe = pipe + m_pipes * bank; uint32 rotation = ComputeSurfaceRotationFromTileMode(tileMode); uint32 swizzle = pipeSwizzle + m_pipes * bankSwizzle; uint32 sliceIn = slice; if (TM_IsThickAndMacroTiled(tileMode)) sliceIn >>= 2; bankPipe ^= m_pipes * sampleSlice * ((m_banks >> 1) + 1) ^ (swizzle + sliceIn * rotation); bankPipe %= m_pipes * m_banks; pipe = bankPipe % m_pipes; bank = bankPipe / m_pipes; uint64 sliceBytes = (((uint64)height * pitch * microTileThickness * bpp * numSamples + 7) / 8); uint64 sliceOffset = sliceBytes * ((sampleSlice + numSampleSplits * slice) / microTileThickness); uint32 macroTilePitch = 8 * m_banks; uint32 macroTileHeight = 8 * m_pipes; switch (tileMode) { case Latte::E_HWTILEMODE::TM_2D_TILED_THIN2: case Latte::E_HWTILEMODE::TM_2B_TILED_THIN2: macroTilePitch >>= 1; macroTileHeight <<= 1; break; case Latte::E_HWTILEMODE::TM_2D_TILED_THIN4: case Latte::E_HWTILEMODE::TM_2B_TILED_THIN4: macroTilePitch >>= 2; macroTileHeight <<= 2; break; default: break; } uint32 macroTilesPerRow = pitch / macroTilePitch; uint32 macroTileBytes = (numSamples * microTileThickness * bpp * macroTileHeight * macroTilePitch + 7) >> 3; uint32 macroTileIndexX = x / macroTilePitch; uint32 macroTileIndexY = y / macroTileHeight; uint32 macroTileOffset = (x / macroTilePitch + pitch / macroTilePitch * (y / macroTileHeight)) * macroTileBytes; if (TM_IsBankSwapped(tileMode)) { uint32 bankSwapWidth = ComputeSurfaceBankSwappedWidth(tileMode, bpp, numSamples, pitch); uint32 swapIndex = macroTilePitch * macroTileIndexX / bankSwapWidth; uint32 bankMask = m_banks - 1; bank ^= bankSwapOrder[swapIndex & bankMask]; } uint32 pipeOffset = (pipe << m_pipeInterleaveBytesBitcount); uint32 bankOffset = (bank << (m_pipesBitcount + m_pipeInterleaveBytesBitcount)); uint32 numSwizzleBits = (m_banksBitcount + m_pipesBitcount); uint32 macroSliceOffset = (uint32)((macroTileOffset + sliceOffset) >> numSwizzleBits); macroSliceOffset += elemOffset; uint32 macroSliceOffsetHigh = macroSliceOffset & ~((1 << m_pipeInterleaveBytesBitcount) - 1); uint32 macroSliceOffsetLow = macroSliceOffset & ((1 << m_pipeInterleaveBytesBitcount) - 1); uint32 finalMacroTileOffset = (macroSliceOffsetHigh << numSwizzleBits) | macroSliceOffsetLow; return finalMacroTileOffset | pipeOffset | bankOffset; } void SetupCachedSurfaceAddrInfo(CachedSurfaceAddrInfo* info, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 depth, uint32 numSamples, E_HWTILEMODE tileMode, int isDepth, uint32 pipeSwizzle, uint32 bankSwizzle) { info->slice = slice; info->sample = sample; info->bpp = bpp; info->pitch = pitch; info->height = height; info->depth = depth; info->numSamples = numSamples; info->tileMode = tileMode; info->isDepth = isDepth; info->pipeSwizzle = pipeSwizzle; info->bankSwizzle = bankSwizzle; // calculate static info info->microTileThickness = LatteAddrLib::TM_GetThickness((E_HWTILEMODE)tileMode); info->microTileBits = info->numSamples * info->bpp * (info->microTileThickness * (8 * 8)); info->microTileBytes = info->microTileBits >> 3; info->microTileType = (info->isDepth != 0) ? 1 : 0; cemu_assert_debug(sample == 0); // non-zero not supported info->rotation = ComputeSurfaceRotationFromTileMode((E_HWTILEMODE)tileMode); // macro tile info->macroTilePitch = 8 * m_banks; info->macroTileHeight = 8 * m_pipes; switch (info->tileMode) { case E_HWTILEMODE::TM_2D_TILED_THIN2: case E_HWTILEMODE::TM_2B_TILED_THIN2: info->macroTilePitch >>= 1; info->macroTileHeight <<= 1; break; case E_HWTILEMODE::TM_2D_TILED_THIN4: case E_HWTILEMODE::TM_2B_TILED_THIN4: info->macroTilePitch >>= 2; info->macroTileHeight <<= 2; break; default: break; } _BitScanReverse((DWORD*)&info->macroTilePitchBits, info->macroTilePitch); _BitScanReverse((DWORD*)&info->macroTileHeightBits, info->macroTileHeight); info->macroTilesPerRow = info->pitch / info->macroTilePitch; info->macroTileBytes = (info->numSamples * info->microTileThickness * info->bpp * info->macroTileHeight * info->macroTilePitch + 7) >> 3; // slice info->sliceBytes = (info->height * (uint64)info->pitch * info->microTileThickness * info->bpp * info->numSamples + 7) / 8; info->sliceIn = info->slice; if (TM_IsThickAndMacroTiled(tileMode)) info->sliceIn >>= 2; // bank swap if (TM_IsBankSwapped(tileMode)) info->bankSwapWidth = ComputeSurfaceBankSwappedWidth(tileMode, info->bpp, info->numSamples, info->pitch); // pixel offset multiplier if (info->isDepth) { info->pixelOffsetMul = info->numSamples * info->bpp; } else { info->pixelOffsetMul = info->bpp; } info->bytesPerPixel = info->pixelOffsetMul >> 3; // table for micro tile offset calculation (we could pre-generate these) for (sint32 z = 0; z < 8; z++) { for (sint32 y = 0; y < 8; y++) { for (sint32 x = 0; x < 8; x++) { uint16 v = _ComputePixelIndexWithinMicroTile(x, y, z, info->bpp, info->tileMode, info->microTileType); info->microTilePixelIndexTable[x + y * 8 + z * 8 * 8] = v; } } } // other constant values uint32 swizzle = info->pipeSwizzle + m_pipes * info->bankSwizzle; info->c0 = (swizzle + info->sliceIn * info->rotation); } uint32 ComputeSurfaceAddrFromCoordMacroTiledCached(uint32 x, uint32 y, CachedSurfaceAddrInfo* info) { uint32 numSamples = info->numSamples; uint32 pixelIndex = (uint32)info->microTilePixelIndexTable[(x & 7) + ((y & 7) << 3) + ((info->slice & 7) << 6)]; uint32 pixelOffset = pixelIndex * info->pixelOffsetMul; uint32 bytesPerSample = info->microTileBytes / numSamples; uint32 sampleSlice, numSampleSplits, samplesPerSlice; if (numSamples <= 1 || info->microTileBytes <= m_splitSize) { samplesPerSlice = numSamples; numSampleSplits = 1; sampleSlice = 0; } else { samplesPerSlice = m_splitSize / bytesPerSample; numSampleSplits = numSamples / samplesPerSlice; numSamples = samplesPerSlice; sampleSlice = pixelOffset / (info->microTileBits / numSampleSplits); pixelOffset %= info->microTileBits / numSampleSplits; } pixelOffset >>= 3; uint32 pipe = _ComputePipeFromCoordWoRotation(x, y); uint32 bank = _ComputeBankFromCoordWoRotation(x, y); uint32 bankPipe = pipe + m_pipes * bank; bankPipe ^= m_pipes * sampleSlice * ((m_banks >> 1) + 1) ^ info->c0; bankPipe %= m_pipes * m_banks; pipe = bankPipe % m_pipes; bank = bankPipe / m_pipes; uint32 sliceOffset = info->sliceBytes * ((sampleSlice + numSampleSplits * info->slice) / info->microTileThickness); uint32 macroTileIndexX = x >> info->macroTilePitchBits; uint32 macroTileIndexY = y >> info->macroTileHeightBits; uint32 macroTileOffset = (macroTileIndexX + (info->pitch >> info->macroTilePitchBits) * macroTileIndexY) * info->macroTileBytes; if (TM_IsBankSwapped(info->tileMode)) { uint32 swapIndex = info->macroTilePitch * macroTileIndexX / info->bankSwapWidth; bank ^= bankSwapOrder[swapIndex & (m_banks - 1)]; } uint32 pipeOffset = (pipe << m_pipeInterleaveBytesBitcount); uint32 bankOffset = (bank << (m_pipesBitcount + m_pipeInterleaveBytesBitcount)); uint32 numSwizzleBits = (m_banksBitcount + m_pipesBitcount); uint32 macroSliceOffset = (uint32)((macroTileOffset + sliceOffset) >> numSwizzleBits); macroSliceOffset += pixelOffset; uint32 macroSliceOffsetHigh = macroSliceOffset & ~((1 << m_pipeInterleaveBytesBitcount) - 1); uint32 macroSliceOffsetLow = macroSliceOffset & ((1 << m_pipeInterleaveBytesBitcount) - 1); uint32 finalMacroTileOffset = (macroSliceOffsetHigh << numSwizzleBits) | macroSliceOffsetLow; return finalMacroTileOffset | pipeOffset | bankOffset; } /* * Optimized routine with following assumptions: * tileMode is 4 * samples is 1 */ uint32 ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(uint32 x, uint32 y, CachedSurfaceAddrInfo* info) { uint32 pixelIndex = (uint32)info->microTilePixelIndexTable[(x & 7) + ((y & 7) << 3) + ((info->slice & 7) << 6)]; uint32 pixelOffset = pixelIndex * info->pixelOffsetMul; pixelOffset >>= 3; // bits to bytes uint32 pipe = _ComputePipeFromCoordWoRotation(x, y); // pipe = ((y >> 3) ^ (x >> 3)) & 1; uint32 bank = _ComputeBankFromCoordWoRotation(x, y); // based on (x>>3)&3 and (y>>4)&3 pipe ^= (info->c0 >> 0) & 1; bank ^= (info->c0 >> 1) & 3; uint32 sliceOffset = info->sliceBytes * (info->slice / info->microTileThickness); uint32 macroTileIndexX = x >> info->macroTilePitchBits; uint32 macroTileIndexY = y >> info->macroTileHeightBits; uint32 macroTileOffset = (macroTileIndexX + (info->pitch >> info->macroTilePitchBits) * macroTileIndexY) * info->macroTileBytes; uint32 pipeOffset = (pipe << m_pipeInterleaveBytesBitcount); uint32 bankOffset = (bank << (m_pipesBitcount + m_pipeInterleaveBytesBitcount)); uint32 numSwizzleBits = (m_banksBitcount + m_pipesBitcount); uint32 macroSliceOffset = (uint32)((macroTileOffset + sliceOffset) >> numSwizzleBits); macroSliceOffset += pixelOffset; uint32 macroSliceOffsetHigh = macroSliceOffset & ~((1 << m_pipeInterleaveBytesBitcount) - 1); uint32 macroSliceOffsetLow = macroSliceOffset & ((1 << m_pipeInterleaveBytesBitcount) - 1); uint32 finalMacroTileOffset = (macroSliceOffsetHigh << numSwizzleBits) | macroSliceOffsetLow; return finalMacroTileOffset | pipeOffset | bankOffset; } }; ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "util/helpers/helpers.h" // parse instruction and if valid append it to instructionList bool LatteDecompiler_ParseCFInstruction(LatteDecompilerShaderContext* shaderContext, uint32 cfIndex, uint32 cfWord0, uint32 cfWord1, bool* endOfProgram, std::vector<LatteDecompilerCFInstruction>& instructionList) { LatteDecompilerShader* shaderObj = shaderContext->shader; uint32 cf_inst23_7 = (cfWord1 >> 23) & 0x7F; if (cf_inst23_7 < 0x40) // starting at 0x40 the bits overlap with the ALU instruction encoding { *endOfProgram = ((cfWord1 >> 21) & 1) != 0; uint32 addr = cfWord0 & 0xFFFFFFFF; uint32 count = (cfWord1 >> 10) & 7; if (((cfWord1 >> 19) & 1) != 0) count |= 0x8; count++; if (cf_inst23_7 == GPU7_CF_INST_CALL_FS) { // nop return true; } else if (cf_inst23_7 == GPU7_CF_INST_NOP) { // nop if (((cfWord1 >> 0) & 7) != 0) debugBreakpoint(); // pop count is not zero return true; } else if (cf_inst23_7 == GPU7_CF_INST_EXPORT || cf_inst23_7 == GPU7_CF_INST_EXPORT_DONE) { // export uint32 edType = (cfWord0 >> 13) & 0x3; uint32 edIndexGpr = (cfWord0 >> 23) & 0x7F; uint32 edRWRel = (cfWord0 >> 22) & 1; if (edRWRel != 0 || edIndexGpr != 0) debugBreakpoint(); LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address cfInstruction.type = cf_inst23_7; cfInstruction.cfAddr = cfIndex; // set cond cfInstruction.cfCond = (cfWord1 >> 8) & 3; // set export component selection cfInstruction.exportComponentSel[0] = (cfWord1 >> 0) & 0x7; cfInstruction.exportComponentSel[1] = (cfWord1 >> 3) & 0x7; cfInstruction.exportComponentSel[2] = (cfWord1 >> 6) & 0x7; cfInstruction.exportComponentSel[3] = (cfWord1 >> 9) & 0x7; // set export array base, index and burstcount cfInstruction.exportArrayBase = (cfWord0 >> 0) & 0x1FFF; cfInstruction.exportBurstCount = (cfWord1 >> 17) & 0xF; // set export source GPR and type cfInstruction.exportSourceGPR = (cfWord0 >> 15) & 0x7F; cfInstruction.exportType = edType; //cfInstruction->memWriteElemSize = (cfWord0>>29)&3; // unused return true; } else if (cf_inst23_7 == GPU7_CF_INST_TEX) { LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address cfInstruction.type = cf_inst23_7; cfInstruction.cfAddr = cfIndex; // set cond cfInstruction.cfCond = (cfWord1 >> 8) & 3; // set TEX clause related values cfInstruction.addr = addr; // index of first instruction in 64bit words cfInstruction.count = count; // number of instructions (each instruction is 128bit) // todo: CF_CONST and COND field and maybe other fields? return true; } else if (cf_inst23_7 == GPU7_CF_INST_ELSE || cf_inst23_7 == GPU7_CF_INST_POP) { LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address cfInstruction.type = cf_inst23_7; cfInstruction.cfAddr = cfIndex; // set cond and popCount cfInstruction.cfCond = (cfWord1 >> 8) & 3; cfInstruction.popCount = (cfWord1 >> 0) & 7; // set TEX clause related values cfInstruction.addr = addr; // index of first instruction in 64bit words cfInstruction.count = count; // number of instructions (each instruction is 128bit) // todo: CF_CONST return true; } else if (cf_inst23_7 == GPU7_CF_INST_JUMP) { // ignored (we use ALU/IF/ELSE/PUSH/POP clauses to determine code flow) return true; } else if (cf_inst23_7 == GPU7_CF_INST_LOOP_START_DX10 || cf_inst23_7 == GPU7_CF_INST_LOOP_END || cf_inst23_7 == GPU7_CF_INST_LOOP_START_NO_AL) { LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address cfInstruction.type = cf_inst23_7; cfInstruction.cfAddr = cfIndex; // set cond and popCount cfInstruction.cfCond = (cfWord1 >> 8) & 3; cfInstruction.popCount = (cfWord1 >> 0) & 7; // set TEX clause related values cfInstruction.addr = addr; // index of first instruction in 64bit words cfInstruction.count = count; // number of instructions (each instruction is 128bit) return true; } else if (cf_inst23_7 == GPU7_CF_INST_LOOP_BREAK) { LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address cfInstruction.type = cf_inst23_7; cfInstruction.cfAddr = cfIndex; // set cond and popCount cfInstruction.cfCond = (cfWord1 >> 8) & 3; cfInstruction.popCount = (cfWord1 >> 0) & 7; // set clause related values cfInstruction.addr = addr; // index of first instruction in 64bit words cfInstruction.count = count; // number of instructions (each instruction is 128bit) return true; } else if (cf_inst23_7 == GPU7_CF_INST_MEM_STREAM0_WRITE || cf_inst23_7 == GPU7_CF_INST_MEM_STREAM1_WRITE) { LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // todo: Correctly read all the STREAM0_WRITE specific fields // set type and address cfInstruction.type = cf_inst23_7; cfInstruction.cfAddr = cfIndex; // set export array base cfInstruction.exportArrayBase = (cfWord0 >> 0) & 0x1FFF; cfInstruction.memWriteArraySize = (cfWord1 >> 0) & 0xFFF; cfInstruction.memWriteCompMask = (cfWord1 >> 12) & 0xF; // set export source GPR and type cfInstruction.exportSourceGPR = (cfWord0 >> 15) & 0x7F; return true; } else if (cf_inst23_7 == GPU7_CF_INST_MEM_RING_WRITE) { // this CF instruction is only available when the geometry shader stage is active LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address cfInstruction.type = cf_inst23_7; cfInstruction.cfAddr = cfIndex; // set export array base cfInstruction.exportArrayBase = (cfWord0 >> 0) & 0x1FFF; cfInstruction.memWriteArraySize = (cfWord1 >> 0) & 0xFFF; cfInstruction.memWriteCompMask = (cfWord1 >> 12) & 0xF; cfInstruction.memWriteElemSize = ((cfWord0 >> 30) & 0x3); cfInstruction.exportBurstCount = (cfWord1 >> 17) & 0xF; // set export source GPR and type cfInstruction.exportSourceGPR = (cfWord0 >> 15) & 0x7F; return true; } else if (cf_inst23_7 == GPU7_CF_INST_EMIT_VERTEX) { // this CF instruction is only available when the geometry shader stage is active LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address cfInstruction.type = cf_inst23_7; cfInstruction.cfAddr = cfIndex; return true; } else if (cf_inst23_7 == GPU7_CF_INST_CALL) { // CALL subroutine LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); uint32 callCount = (cfWord1 >> 13) & 0x3F; cfInstruction.addr = addr; // index of call destination in 64bit words cfInstruction.count = callCount; // store callCount in count cfInstruction.type = cf_inst23_7; cfInstruction.cfAddr = cfIndex; // remember subroutine bool subroutineIsKnown = false; for (auto& it : shaderContext->list_subroutines) { if (it.cfAddr == addr) { subroutineIsKnown = true; break; } } if (subroutineIsKnown == false) { LatteDecompilerSubroutineInfo subroutineInfo = { 0 }; subroutineInfo.cfAddr = addr; shaderContext->list_subroutines.push_back(subroutineInfo); } return true; } else if (cf_inst23_7 == GPU7_CF_INST_RETURN) { LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address cfInstruction.type = cf_inst23_7; cfInstruction.cfAddr = cfIndex; // set cond and popCount cfInstruction.cfCond = (cfWord1 >> 8) & 3; cfInstruction.popCount = (cfWord1 >> 0) & 7; // todo - other fields? return true; } else { debug_printf("Unknown 23_7 clause 0x%x found\n", cf_inst23_7); shaderObj->hasError = true; return false; } } else { // ALU instruction uint32 cf_inst26_4 = ((cfWord1 >> 26) & 0xF) | GPU7_CF_INST_ALU_MASK; if (cf_inst26_4 == GPU7_CF_INST_ALU || cf_inst26_4 == GPU7_CF_INST_ALU_PUSH_BEFORE || cf_inst26_4 == GPU7_CF_INST_ALU_POP_AFTER || cf_inst26_4 == GPU7_CF_INST_ALU_POP2_AFTER || cf_inst26_4 == GPU7_CF_INST_ALU_BREAK || cf_inst26_4 == GPU7_CF_INST_ALU_ELSE_AFTER) { LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address cfInstruction.type = cf_inst26_4; cfInstruction.cfAddr = cfIndex; // CF_ALU_* has no cond field cfInstruction.cfCond = 0; // set ALU clause related values cfInstruction.addr = (cfWord0 >> 0) & 0x3FFFFF; // index of first instruction in 64bit words cfInstruction.count = ((cfWord1 >> 18) & 0x7F) + 1; // number of instructions (each instruction is 64bit) // set constant file/bank values cfInstruction.cBank0Index = (cfWord0 >> 22) & 0xF; cfInstruction.cBank1Index = (cfWord0 >> 26) & 0xF; cfInstruction.cBank0AddrBase = ((cfWord1 >> 2) & 0xFF) * 16; cfInstruction.cBank1AddrBase = ((cfWord1 >> 10) & 0xFF) * 16; return true; } else { debug_printf("Unknown 26_4 clause 0x%x found\n", cf_inst26_4); shaderObj->hasError = true; return false; } } cemu_assert_unimplemented(); // should not reach return false; } void LatteDecompiler_ParseCFSubroutine(LatteDecompilerShaderContext* shaderContext, uint8* programData, uint32 programSize, LatteDecompilerSubroutineInfo* subroutineInfo) { LatteDecompilerShader* shaderObj = shaderContext->shader; // parse control flow instructions for (uint32 i = subroutineInfo->cfAddr; i < programSize / 8; i++) { uint32 cfWord0 = *(uint32*)(programData + i * 8 + 0); uint32 cfWord1 = *(uint32*)(programData + i * 8 + 4); bool isEndOfProgram = false; if( !LatteDecompiler_ParseCFInstruction(shaderContext, i, cfWord0, cfWord1, &isEndOfProgram, subroutineInfo->instructions) ) continue; cemu_assert_debug(!isEndOfProgram); // should never be encountered in a subroutine? if (shaderObj->hasError) return; auto& cfInstruction = subroutineInfo->instructions.back(); if (cfInstruction.type == GPU7_CF_INST_RETURN) return; // todo - should check if this return statement is conditional } cemu_assert_debug(false); // should not reach (subroutines have to end with RETURN) } void LatteDecompiler_ParseCF(LatteDecompilerShaderContext* shaderContext, uint8* programData, uint32 programSize) { LatteDecompilerShader* shaderObj = shaderContext->shader; // parse control flow instructions for main entry point bool endOfProgram = false; for (uint32 i = 0; i < programSize / 8; i++) { uint32 cfWord0 = *(uint32*)(programData + i * 8 + 0); uint32 cfWord1 = *(uint32*)(programData + i * 8 + 4); LatteDecompiler_ParseCFInstruction(shaderContext, i, cfWord0, cfWord1, &endOfProgram, shaderContext->cfInstructions); if (endOfProgram) break; } // parse CF instructions for subroutines for (auto& subroutineInfo : shaderContext->list_subroutines) { LatteDecompiler_ParseCFSubroutine(shaderContext, programData, programSize, &subroutineInfo); } } // returns true if the given op2/op3 ALU instruction is always executed on the transcendental unit bool LatteDecompiler_IsALUTransInstruction(bool isOP3, uint32 opcode) { if( isOP3 == true ) return false; // OP3 has no transcendental instructions? if( opcode == ALU_OP2_INST_COS || opcode == ALU_OP2_INST_SIN || opcode == ALU_OP2_INST_RECIP_FF || opcode == ALU_OP2_INST_RECIP_IEEE || opcode == ALU_OP2_INST_RECIPSQRT_IEEE || opcode == ALU_OP2_INST_RECIPSQRT_CLAMPED || opcode == ALU_OP2_INST_RECIPSQRT_FF || opcode == ALU_OP2_INST_MULLO_INT || opcode == ALU_OP2_INST_MULLO_UINT || opcode == ALU_OP2_INST_FLT_TO_INT || opcode == ALU_OP2_INST_FLT_TO_UINT || opcode == ALU_OP2_INST_INT_TO_FLOAT || opcode == ALU_OP2_INST_UINT_TO_FLOAT || opcode == ALU_OP2_INST_LOG_CLAMPED || opcode == ALU_OP2_INST_LOG_IEEE || opcode == ALU_OP2_INST_EXP_IEEE || opcode == ALU_OP2_INST_UINT_TO_FLOAT || opcode == ALU_OP2_INST_SQRT_IEEE ) { // transcendental return true; } else if( opcode == ALU_OP2_INST_MOV || opcode == ALU_OP2_INST_ADD || opcode == ALU_OP2_INST_NOP || opcode == ALU_OP2_INST_MUL || opcode == ALU_OP2_INST_DOT4 || opcode == ALU_OP2_INST_DOT4_IEEE || opcode == ALU_OP2_INST_MAX || // Not sure if MIN/MAX are non-transcendental? opcode == ALU_OP2_INST_MIN || opcode == ALU_OP2_INST_AND_INT || opcode == ALU_OP2_INST_OR_INT || opcode == ALU_OP2_INST_XOR_INT || opcode == ALU_OP2_INST_NOT_INT || opcode == ALU_OP2_INST_ADD_INT || opcode == ALU_OP2_INST_SUB_INT || opcode == ALU_OP2_INST_SETGT || opcode == ALU_OP2_INST_SETGE || opcode == ALU_OP2_INST_SETNE || opcode == ALU_OP2_INST_SETE || opcode == ALU_OP2_INST_SETE_INT || opcode == ALU_OP2_INST_SETNE_INT || opcode == ALU_OP2_INST_SETGT_INT || opcode == ALU_OP2_INST_SETGE_INT || opcode == ALU_OP2_INST_SETGE_UINT || opcode == ALU_OP2_INST_SETGT_UINT || opcode == ALU_OP2_INST_MAX_DX10 || opcode == ALU_OP2_INST_MIN_DX10 || opcode == ALU_OP2_INST_PRED_SETE || opcode == ALU_OP2_INST_PRED_SETNE || opcode == ALU_OP2_INST_PRED_SETGE || opcode == ALU_OP2_INST_PRED_SETGT || opcode == ALU_OP2_INST_PRED_SETE_INT || opcode == ALU_OP2_INST_PRED_SETNE_INT || opcode == ALU_OP2_INST_PRED_SETGT_INT || opcode == ALU_OP2_INST_PRED_SETGE_INT || opcode == ALU_OP2_INST_KILLE_INT || opcode == ALU_OP2_INST_KILLGT_INT || opcode == ALU_OP2_INST_KILLNE_INT || opcode == ALU_OP2_INST_KILLGT || opcode == ALU_OP2_INST_KILLGE || opcode == ALU_OP2_INST_KILLE || opcode == ALU_OP2_INST_MUL_IEEE || opcode == ALU_OP2_INST_FLOOR || opcode == ALU_OP2_INST_FRACT || opcode == ALU_OP2_INST_TRUNC || opcode == ALU_OP2_INST_LSHL_INT || opcode == ALU_OP2_INST_ASHR_INT || opcode == ALU_OP2_INST_LSHR_INT || opcode == ALU_OP2_INST_MAX_INT || opcode == ALU_OP2_INST_MIN_INT || opcode == ALU_OP2_INST_MAX_UINT || opcode == ALU_OP2_INST_MIN_UINT || opcode == ALU_OP2_INST_MOVA_FLOOR || opcode == ALU_OP2_INST_MOVA_INT || opcode == ALU_OP2_INST_SETE_DX10 || opcode == ALU_OP2_INST_SETNE_DX10 || opcode == ALU_OP2_INST_SETGT_DX10 || opcode == ALU_OP2_INST_SETGE_DX10 || opcode == ALU_OP2_INST_RNDNE || opcode == ALU_OP2_INST_CUBE // reduction instruction ) { // not transcendental return false; } else { debug_printf("_isALUTransInstruction(): Unknown instruction 0x%x (%s)\n", opcode, isOP3?"op3":"op2"); } // ALU.Trans instructions: // [x] FLT_TO_INT // [x] FLT_TO_UINT // [x] INT_TO_FLT // MULHI_INT // MULHI_UINT // [x] MULLO_INT // [x] MULLO_UINT // RECIP_INT // RECIP_UINT // [x] UINT_TO_FLT // [x] COS // [x] EXP_IEEE // [x] LOG_CLAMPED // [x] LOG_IEEE // MUL_LIT // MUL_LIT_D2 // MUL_LIT_M2 // MUL_LIT_M4 // RECIP_CLAMPED // [x] RECIP_FF // [x] RECIP_IEEE // [x] RECIPSQRT_CLAMPED // [x] RECIPSQRT_FF // [x] RECIPSQRT_IEEE // [x] SIN // [x] SQRT_IEEE return false; } void LatteDecompiler_ParseALUClause(LatteDecompilerShader* shaderContext, LatteDecompilerCFInstruction* cfInstruction, uint8* programData, uint32 programSize) { sint32 instructionGroupIndex = 0; sint32 indexInGroup = 0; // index of instruction within instruction group uint32 elementsWrittenMask = 0; // used to determine ALU/Trans unit for instructions uint8 literalMask = 0; // mask of used literals for current instruction group sint32 parserIndex = 0; while( parserIndex < cfInstruction->count ) { uint32 aluWord0 = *(uint32*)(programData+(cfInstruction->addr+parserIndex)*8+0); uint32 aluWord1 = *(uint32*)(programData+(cfInstruction->addr+parserIndex)*8+4); parserIndex++; bool isLastInGroup = (aluWord0&0x80000000) != 0; uint32 alu_inst13_5 = (aluWord1>>13)&0x1F; // parameters from ALU word 0 (shared for ALU OP2 and OP3) uint32 src0Sel = (aluWord0>>0)&0x1FF; // source selection uint32 src1Sel = (aluWord0>>13)&0x1FF; uint32 src0Rel = (aluWord0>>9)&0x1; // relative addressing mode uint32 src1Rel = (aluWord0>>22)&0x1; uint32 src0Chan = (aluWord0>>10)&0x3; // component selection x/y/z/w uint32 src1Chan = (aluWord0>>23)&0x3; uint32 src0Neg = (aluWord0>>12)&0x1; // negate input uint32 src1Neg = (aluWord0>>25)&0x1; uint32 indexMode = (aluWord0>>26)&7; uint32 predSel = (aluWord0>>29)&3; if( predSel != 0 ) debugBreakpoint(); if( alu_inst13_5 >= 0x8 ) { // op3 // parameters from ALU word 1 uint32 src2Sel = (aluWord1>>0)&0x1FF; // source selection uint32 src2Rel = (aluWord1>>9)&0x1; // relative addressing mode uint32 src2Chan = (aluWord1>>10)&0x3; // component selection x/y/z/w uint32 src2Neg = (aluWord1>>12)&0x1; // negate input uint32 destGpr = (aluWord1>>21)&0x7F; uint32 destRel = (aluWord1>>28)&1; uint32 destElem = (aluWord1>>29)&3; uint32 destClamp = (aluWord1>>31)&1; LatteDecompilerALUInstruction aluInstruction; aluInstruction.cfInstruction = cfInstruction; aluInstruction.isOP3 = true; aluInstruction.opcode = alu_inst13_5; aluInstruction.instructionGroupIndex = instructionGroupIndex; aluInstruction.indexMode = indexMode; aluInstruction.destGpr = destGpr; aluInstruction.destRel = destRel; aluInstruction.destElem = destElem; aluInstruction.destClamp = destClamp; aluInstruction.writeMask = 1; aluInstruction.omod = 0; // op3 has no omod aluInstruction.sourceOperand[0].sel = src0Sel; aluInstruction.sourceOperand[0].rel = src0Rel; aluInstruction.sourceOperand[0].abs = 0; aluInstruction.sourceOperand[0].neg = src0Neg; aluInstruction.sourceOperand[0].chan = src0Chan; aluInstruction.sourceOperand[1].sel = src1Sel; aluInstruction.sourceOperand[1].rel = src1Rel; aluInstruction.sourceOperand[1].abs = 0; aluInstruction.sourceOperand[1].neg = src1Neg; aluInstruction.sourceOperand[1].chan = src1Chan; aluInstruction.sourceOperand[2].sel = src2Sel; aluInstruction.sourceOperand[2].rel = src2Rel; aluInstruction.sourceOperand[2].abs = 0; aluInstruction.sourceOperand[2].neg = src2Neg; aluInstruction.sourceOperand[2].chan = src2Chan; // check for literal access if( GPU7_ALU_SRC_IS_LITERAL(src0Sel) ) literalMask |= (1<<src0Chan); if( GPU7_ALU_SRC_IS_LITERAL(src1Sel) ) literalMask |= (1<<src1Chan); if( GPU7_ALU_SRC_IS_LITERAL(src2Sel) ) literalMask |= (1<<src2Chan); // determine used ALU unit (x,y,z,w,t) uint32 aluUnit = destElem; if( aluUnit < 4 && (elementsWrittenMask & (1<<aluUnit)) != 0 ) { aluUnit = 4; // ALU unit already used, this instruction uses the transcendental unit } elementsWrittenMask |= (1<<aluUnit); aluInstruction.aluUnit = aluUnit; aluInstruction.indexInGroup = indexInGroup; aluInstruction.isLastInstructionOfGroup = isLastInGroup; // add instruction to list of sub-instructions cfInstruction->instructionsALU.emplace_back(aluInstruction); } else { uint32 alu_inst7_11 = (aluWord1>>7)&0x7FF; uint32 src0Abs = (aluWord1>>0)&1; uint32 src1Abs = (aluWord1>>1)&1; uint32 updateExecuteMask = (aluWord1>>2)&1; uint32 updatePredicate = (aluWord1>>3)&1; uint32 writeMask = (aluWord1>>4)&1; uint32 omod = (aluWord1>>5)&3; uint32 destGpr = (aluWord1>>21)&0x7F; uint32 destRel = (aluWord1>>28)&1; uint32 destElem = (aluWord1>>29)&3; uint32 destClamp = (aluWord1>>31)&1; LatteDecompilerALUInstruction aluInstruction; aluInstruction.cfInstruction = cfInstruction; aluInstruction.isOP3 = false; aluInstruction.opcode = alu_inst7_11; aluInstruction.instructionGroupIndex = instructionGroupIndex; aluInstruction.indexMode = indexMode; aluInstruction.destGpr = destGpr; aluInstruction.destRel = destRel; aluInstruction.destElem = destElem; aluInstruction.destClamp = destClamp; aluInstruction.writeMask = writeMask; aluInstruction.updateExecuteMask = updateExecuteMask; aluInstruction.updatePredicate = updatePredicate; aluInstruction.omod = omod; aluInstruction.sourceOperand[0].sel = src0Sel; aluInstruction.sourceOperand[0].rel = src0Rel; aluInstruction.sourceOperand[0].abs = src0Abs; aluInstruction.sourceOperand[0].neg = src0Neg; aluInstruction.sourceOperand[0].chan = src0Chan; aluInstruction.sourceOperand[1].sel = src1Sel; aluInstruction.sourceOperand[1].rel = src1Rel; aluInstruction.sourceOperand[1].abs = src1Abs; aluInstruction.sourceOperand[1].neg = src1Neg; aluInstruction.sourceOperand[1].chan = src1Chan; aluInstruction.sourceOperand[2].sel = 0xFFFFFFFF; // check for literal access if( GPU7_ALU_SRC_IS_LITERAL(src0Sel) ) literalMask |= (1<<src0Chan); if( GPU7_ALU_SRC_IS_LITERAL(src1Sel) ) literalMask |= (1<<src1Chan); // determine ALU unit (x,y,z,w,t) uint32 aluUnit = destElem; // some instructions always use the transcendental unit bool isTranscendentalOperation = LatteDecompiler_IsALUTransInstruction(false, alu_inst7_11); if( isTranscendentalOperation ) aluUnit = 4; if( aluUnit < 4 && (elementsWrittenMask & (1<<aluUnit)) != 0 ) { aluUnit = 4; // ALU unit already used, this instruction uses the transcendental unit } elementsWrittenMask |= (1<<aluUnit); aluInstruction.aluUnit = aluUnit; aluInstruction.indexInGroup = indexInGroup; aluInstruction.isLastInstructionOfGroup = isLastInGroup; // add instruction to list of sub-instructions cfInstruction->instructionsALU.emplace_back(aluInstruction); } indexInGroup++; if( isLastInGroup ) { // load literal data if( literalMask ) { bool useLiteralDataXY = false; bool useLiteralDataZW = false; if( (literalMask&(1|2)) ) { useLiteralDataXY = true; } if( (literalMask&(4|8)) ) { useLiteralDataXY = true; useLiteralDataZW = true; } uint32 literalWords[4] = {0}; literalWords[0] = *(uint32*)(programData+(cfInstruction->addr+parserIndex)*8+0); literalWords[1] = *(uint32*)(programData+(cfInstruction->addr+parserIndex)*8+4); if( useLiteralDataZW ) { literalWords[2] = *(uint32*)(programData+(cfInstruction->addr+parserIndex+1)*8+0); literalWords[3] = *(uint32*)(programData+(cfInstruction->addr+parserIndex+1)*8+4); } if( useLiteralDataZW ) parserIndex += 2; else parserIndex += 1; // set literal data for all instructions of the current instruction group for(auto& aluInstructionItr : reverse_itr(cfInstruction->instructionsALU) ) { if( aluInstructionItr.instructionGroupIndex != instructionGroupIndex ) break; aluInstructionItr.literalData.w[0] = literalWords[0]; aluInstructionItr.literalData.w[1] = literalWords[1]; aluInstructionItr.literalData.w[2] = literalWords[2]; aluInstructionItr.literalData.w[3] = literalWords[3]; } } // reset instruction group related tracking variables literalMask = 0; elementsWrittenMask = 0; indexInGroup = 0; // start next group instructionGroupIndex++; } } } /* * Parse TEX clause */ void LatteDecompiler_ParseTEXClause(LatteDecompilerShader* shaderContext, LatteDecompilerCFInstruction* cfInstruction, uint8* programData, uint32 programSize) { for(sint32 i=0; i<cfInstruction->count; i++) { // each instruction is 128bit uint32 instructionAddr = cfInstruction->addr*2+i*4; uint32 word0 = *(uint32*)(programData+instructionAddr*4+0); uint32 word1 = *(uint32*)(programData+instructionAddr*4+4); uint32 word2 = *(uint32*)(programData+instructionAddr*4+8); uint32 word3 = *(uint32*)(programData+instructionAddr*4+12); uint32 inst0_4 = (word0>>0)&0x1F; if (inst0_4 == GPU7_TEX_INST_SAMPLE || inst0_4 == GPU7_TEX_INST_SAMPLE_L || inst0_4 == GPU7_TEX_INST_SAMPLE_LZ || inst0_4 == GPU7_TEX_INST_SAMPLE_LB || inst0_4 == GPU7_TEX_INST_SAMPLE_C || inst0_4 == GPU7_TEX_INST_SAMPLE_C_L || inst0_4 == GPU7_TEX_INST_SAMPLE_C_LZ || inst0_4 == GPU7_TEX_INST_FETCH4 || inst0_4 == GPU7_TEX_INST_SAMPLE_G || inst0_4 == GPU7_TEX_INST_LD || inst0_4 == GPU7_TEX_INST_GET_TEXTURE_RESINFO || inst0_4 == GPU7_TEX_INST_GET_COMP_TEX_LOD) { uint32 fetchType = (word0 >> 5) & 3; uint32 bufferId = (word0 >> 8) & 0xFF; uint32 samplerId = (word2 >> 15) & 0x1F; uint32 srcGpr = (word0 >> 16) & 0x7F; uint32 srcRel = (word0 >> 23) & 1; if (srcRel != 0) debugBreakpoint(); uint32 destGpr = (word1 >> 0) & 0x7F; uint32 destRel = (word1 >> 7) & 1; if (destRel != 0) debugBreakpoint(); uint32 dstSelX = (word1 >> 9) & 0x7; uint32 dstSelY = (word1 >> 12) & 0x7; uint32 dstSelZ = (word1 >> 15) & 0x7; uint32 dstSelW = (word1 >> 18) & 0x7; uint32 coordTypeX = (word1 >> 28) & 1; uint32 coordTypeY = (word1 >> 29) & 1; uint32 coordTypeZ = (word1 >> 30) & 1; uint32 coordTypeW = (word1 >> 31) & 1; uint32 srcSelX = (word2 >> 20) & 0x7; uint32 srcSelY = (word2 >> 23) & 0x7; uint32 srcSelZ = (word2 >> 26) & 0x7; uint32 srcSelW = (word2 >> 29) & 0x7; uint32 offsetX = (word2 >> 0) & 0x1F; uint32 offsetY = (word2 >> 5) & 0x1F; uint32 offsetZ = (word2 >> 10) & 0x1F; sint8 lodBias = (word2 >> 21) & 0x7F; if ((lodBias&0x40) != 0) lodBias |= 0x80; // bufferID -> Texture index // samplerId -> Sampler index sint32 textureIndex = bufferId - 0x00; // create new tex instruction LatteDecompilerTEXInstruction texInstruction; texInstruction.cfInstruction = cfInstruction; texInstruction.opcode = inst0_4; texInstruction.textureFetch.textureIndex = textureIndex; texInstruction.textureFetch.samplerIndex = samplerId; texInstruction.dstSel[0] = dstSelX; texInstruction.dstSel[1] = dstSelY; texInstruction.dstSel[2] = dstSelZ; texInstruction.dstSel[3] = dstSelW; texInstruction.textureFetch.srcSel[0] = srcSelX; texInstruction.textureFetch.srcSel[1] = srcSelY; texInstruction.textureFetch.srcSel[2] = srcSelZ; texInstruction.textureFetch.srcSel[3] = srcSelW; texInstruction.textureFetch.offsetX = (sint8)((offsetX & 0x10) ? (offsetX | 0xE0) : (offsetX)); texInstruction.textureFetch.offsetY = (sint8)((offsetY & 0x10) ? (offsetY | 0xE0) : (offsetY)); texInstruction.textureFetch.offsetZ = (sint8)((offsetZ & 0x10) ? (offsetZ | 0xE0) : (offsetZ)); texInstruction.dstGpr = destGpr; texInstruction.srcGpr = srcGpr; texInstruction.textureFetch.unnormalized[0] = coordTypeX == 0; texInstruction.textureFetch.unnormalized[1] = coordTypeY == 0; texInstruction.textureFetch.unnormalized[2] = coordTypeZ == 0; texInstruction.textureFetch.unnormalized[3] = coordTypeW == 0; texInstruction.textureFetch.lodBias = (sint8)lodBias; cfInstruction->instructionsTEX.emplace_back(texInstruction); } else if( inst0_4 == GPU7_TEX_INST_SET_CUBEMAP_INDEX ) { // todo: check if the encoding of fields matches with that of GPU7_TEX_INST_SAMPLE* (it should, according to AMD doc) uint32 fetchType = (word0>>5)&3; uint32 bufferId = (word0>>8)&0xFF; uint32 samplerId = (word2>>15)&0x1F; uint32 srcGpr = (word0>>16)&0x7F; uint32 srcRel = (word0>>23)&1; if( srcRel != 0 ) debugBreakpoint(); uint32 destGpr = (word1>>0)&0x7F; uint32 destRel = (word1>>7)&1; if( destRel != 0 ) debugBreakpoint(); uint32 dstSelX = (word1>>9)&0x7; uint32 dstSelY = (word1>>12)&0x7; uint32 dstSelZ = (word1>>15)&0x7; uint32 dstSelW = (word1>>18)&0x7; uint32 srcSelX = (word2>>20)&0x7; uint32 srcSelY = (word2>>23)&0x7; uint32 srcSelZ = (word2>>26)&0x7; uint32 srcSelW = (word2>>29)&0x7; sint32 textureIndex = bufferId-0x00; // create new tex instruction LatteDecompilerTEXInstruction texInstruction; texInstruction.cfInstruction = cfInstruction; texInstruction.opcode = inst0_4; texInstruction.textureFetch.textureIndex = textureIndex; texInstruction.textureFetch.samplerIndex = samplerId; texInstruction.dstSel[0] = dstSelX; texInstruction.dstSel[1] = dstSelY; texInstruction.dstSel[2] = dstSelZ; texInstruction.dstSel[3] = dstSelW; texInstruction.textureFetch.srcSel[0] = srcSelX; texInstruction.textureFetch.srcSel[1] = srcSelY; texInstruction.textureFetch.srcSel[2] = srcSelZ; texInstruction.textureFetch.srcSel[3] = srcSelW; texInstruction.dstGpr = destGpr; texInstruction.srcGpr = srcGpr; cfInstruction->instructionsTEX.emplace_back(texInstruction); } else if (inst0_4 == GPU7_TEX_INST_GET_GRADIENTS_H || inst0_4 == GPU7_TEX_INST_GET_GRADIENTS_V) { uint32 fetchType = (word0 >> 5) & 3; uint32 bufferId = (word0 >> 8) & 0xFF; uint32 samplerId = (word2 >> 15) & 0x1F; uint32 srcGpr = (word0 >> 16) & 0x7F; uint32 srcRel = (word0 >> 23) & 1; if (srcRel != 0) debugBreakpoint(); uint32 destGpr = (word1 >> 0) & 0x7F; uint32 destRel = (word1 >> 7) & 1; if (destRel != 0) debugBreakpoint(); uint32 dstSelX = (word1 >> 9) & 0x7; uint32 dstSelY = (word1 >> 12) & 0x7; uint32 dstSelZ = (word1 >> 15) & 0x7; uint32 dstSelW = (word1 >> 18) & 0x7; uint32 coordTypeX = (word1 >> 28) & 1; uint32 coordTypeY = (word1 >> 29) & 1; uint32 coordTypeZ = (word1 >> 30) & 1; uint32 coordTypeW = (word1 >> 31) & 1; cemu_assert_debug(coordTypeX != GPU7_TEX_UNNORMALIZED); cemu_assert_debug(coordTypeY != GPU7_TEX_UNNORMALIZED); cemu_assert_debug(coordTypeZ != GPU7_TEX_UNNORMALIZED); cemu_assert_debug(coordTypeW != GPU7_TEX_UNNORMALIZED); uint32 srcSelX = (word2 >> 20) & 0x7; uint32 srcSelY = (word2 >> 23) & 0x7; uint32 srcSelZ = (word2 >> 26) & 0x7; uint32 srcSelW = (word2 >> 29) & 0x7; uint32 offsetX = (word2 >> 0) & 0x1F; uint32 offsetY = (word2 >> 5) & 0x1F; uint32 offsetZ = (word2 >> 10) & 0x1F; cemu_assert_debug(offsetX == 0); cemu_assert_debug(offsetY == 0); cemu_assert_debug(offsetZ == 0); // create new tex instruction LatteDecompilerTEXInstruction texInstruction; texInstruction.cfInstruction = cfInstruction; texInstruction.opcode = inst0_4; texInstruction.dstSel[0] = dstSelX; texInstruction.dstSel[1] = dstSelY; texInstruction.dstSel[2] = dstSelZ; texInstruction.dstSel[3] = dstSelW; texInstruction.textureFetch.srcSel[0] = srcSelX; texInstruction.textureFetch.srcSel[1] = srcSelY; texInstruction.textureFetch.srcSel[2] = srcSelZ; texInstruction.textureFetch.srcSel[3] = srcSelW; texInstruction.dstGpr = destGpr; texInstruction.srcGpr = srcGpr; cfInstruction->instructionsTEX.emplace_back(texInstruction); } else if (inst0_4 == GPU7_TEX_INST_SET_GRADIENTS_H || inst0_4 == GPU7_TEX_INST_SET_GRADIENTS_V) { uint32 bufferId = (word0 >> 8) & 0xFF; uint32 samplerId = (word2 >> 15) & 0x1F; uint32 srcGpr = (word0 >> 16) & 0x7F; uint32 srcRel = (word0 >> 23) & 1; if (srcRel != 0) debugBreakpoint(); uint32 coordTypeX = (word1 >> 28) & 1; uint32 coordTypeY = (word1 >> 29) & 1; uint32 coordTypeZ = (word1 >> 30) & 1; uint32 coordTypeW = (word1 >> 31) & 1; cemu_assert_debug(coordTypeX != GPU7_TEX_UNNORMALIZED); cemu_assert_debug(coordTypeY != GPU7_TEX_UNNORMALIZED); cemu_assert_debug(coordTypeZ != GPU7_TEX_UNNORMALIZED); cemu_assert_debug(coordTypeW != GPU7_TEX_UNNORMALIZED); uint32 srcSelX = (word2 >> 20) & 0x7; uint32 srcSelY = (word2 >> 23) & 0x7; uint32 srcSelZ = (word2 >> 26) & 0x7; uint32 srcSelW = (word2 >> 29) & 0x7; sint32 textureIndex = bufferId - 0x00; // create new tex instruction LatteDecompilerTEXInstruction texInstruction; texInstruction.cfInstruction = cfInstruction; texInstruction.opcode = inst0_4; texInstruction.textureFetch.textureIndex = textureIndex; texInstruction.textureFetch.samplerIndex = samplerId; texInstruction.textureFetch.srcSel[0] = srcSelX; texInstruction.textureFetch.srcSel[1] = srcSelY; texInstruction.textureFetch.srcSel[2] = srcSelZ; texInstruction.textureFetch.srcSel[3] = srcSelW; texInstruction.srcGpr = srcGpr; texInstruction.dstGpr = 0xFFFFFFFF; cfInstruction->instructionsTEX.emplace_back(texInstruction); } else if( inst0_4 == GPU7_TEX_INST_VFETCH ) { // this uses the VTX_WORD* encoding uint32 fetchType = (word0>>5)&3; uint32 bufferId = (word0>>8)&0xFF; uint32 offset = (word2>>0)&0xFFFF; uint32 endianSwap = (word2>>16)&0x3; uint32 constNoStride = (word2>>18)&0x1; uint32 srcGpr = (word0>>16)&0x7F; uint32 srcRel = (word0>>23)&1; if( srcRel != 0 ) debugBreakpoint(); uint32 destGpr = (word1>>0)&0x7F; uint32 destRel = (word1>>7)&1; if( destRel != 0 ) debugBreakpoint(); uint32 dstSelX = (word1>>9)&0x7; uint32 dstSelY = (word1>>12)&0x7; uint32 dstSelZ = (word1>>15)&0x7; uint32 dstSelW = (word1>>18)&0x7; uint32 srcSelX = (word0>>24)&0x3; uint32 srcSelY = 0; uint32 srcSelZ = 0; uint32 srcSelW = 0; // create new tex instruction LatteDecompilerTEXInstruction texInstruction; texInstruction.cfInstruction = cfInstruction; texInstruction.opcode = inst0_4; texInstruction.textureFetch.textureIndex = bufferId; texInstruction.textureFetch.samplerIndex = 0; texInstruction.textureFetch.offset = offset; texInstruction.dstSel[0] = dstSelX; texInstruction.dstSel[1] = dstSelY; texInstruction.dstSel[2] = dstSelZ; texInstruction.dstSel[3] = dstSelW; texInstruction.textureFetch.srcSel[0] = srcSelX; texInstruction.textureFetch.srcSel[1] = srcSelY; texInstruction.textureFetch.srcSel[2] = srcSelZ; texInstruction.textureFetch.srcSel[3] = srcSelW; texInstruction.dstGpr = destGpr; texInstruction.srcGpr = srcGpr; cfInstruction->instructionsTEX.emplace_back(texInstruction); } else if (inst0_4 == GPU7_TEX_INST_MEM) { // memory access // MEM_RD_WORD0 uint32 elementSize = (word0 >> 5) & 3; uint32 memOp = (word0 >> 8) & 7; uint8 indexed = (word0 >> 12) & 1; uint32 srcGPR = (word0 >> 16) & 0x7F; uint8 srcREL = (word0 >> 23) & 1; uint8 srcSelX = (word0 >> 24) & 3; // MEM_RD_WORD1 uint32 dstGPR = (word1 >> 0) & 0x7F; uint8 dstREL = (word1 >> 7) & 1; uint8 dstSelX = (word1 >> 9) & 7; uint8 dstSelY = (word1 >> 12) & 7; uint8 dstSelZ = (word1 >> 15) & 7; uint8 dstSelW = (word1 >> 18) & 7; uint8 dataFormat = (word1 >> 22) & 0x3F; uint8 nfa = (word1 >> 28) & 3; uint8 isSigned = (word1 >> 30) & 1; uint8 srfMode = (word1 >> 31) & 1; // MEM_RD_WORD2 uint32 arrayBase = (word2 & 0x1FFF); uint8 endianSwap = (word2 >> 16) & 3; uint32 arraySize = (word2 >> 20) & 0xFFF; if (memOp == 2) { // read from scatter buffer (SSBO) LatteDecompilerTEXInstruction texInstruction; texInstruction.cfInstruction = cfInstruction; texInstruction.opcode = inst0_4; cemu_assert_debug(srcREL == 0 || dstREL == 0); // unsupported relative access texInstruction.memRead.arrayBase = arrayBase; texInstruction.srcGpr = srcGPR; texInstruction.dstGpr = dstGPR; texInstruction.memRead.srcSelX = srcSelX; texInstruction.dstSel[0] = dstSelX; texInstruction.dstSel[1] = dstSelY; texInstruction.dstSel[2] = dstSelZ; texInstruction.dstSel[3] = dstSelW; texInstruction.memRead.format = dataFormat; texInstruction.memRead.nfa = nfa; texInstruction.memRead.isSigned = isSigned; cfInstruction->instructionsTEX.emplace_back(texInstruction); } else { cemu_assert_unimplemented(); } } else { cemuLog_logDebug(LogType::Force, "Unsupported tex instruction {}", inst0_4); shaderContext->hasError = true; break; } } cemu_assert_debug(cfInstruction->instructionsALU.empty()); // clause may only contain texture instructions } // iterate all CF instructions and parse clause sub-instructions (if present) void LatteDecompiler_ParseClauses(LatteDecompilerShaderContext* decompilerContext, uint8* programData, uint32 programSize, std::vector<LatteDecompilerCFInstruction> &list_instructions) { LatteDecompilerShader* shader = decompilerContext->shader; for (auto& cfInstruction : list_instructions) { if (cfInstruction.type == GPU7_CF_INST_ALU || cfInstruction.type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction.type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_BREAK || cfInstruction.type == GPU7_CF_INST_ALU_ELSE_AFTER) { LatteDecompiler_ParseALUClause(shader, &cfInstruction, programData, programSize); } else if (cfInstruction.type == GPU7_CF_INST_TEX) { LatteDecompiler_ParseTEXClause(shader, &cfInstruction, programData, programSize); } else if (cfInstruction.type == GPU7_CF_INST_EXPORT || cfInstruction.type == GPU7_CF_INST_EXPORT_DONE) { // no sub-instructions } else if (cfInstruction.type == GPU7_CF_INST_ELSE || cfInstruction.type == GPU7_CF_INST_POP) { // no sub-instructions } else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { // no sub-instructions } else if (cfInstruction.type == GPU7_CF_INST_LOOP_BREAK) { // no sub-instructions } else if (cfInstruction.type == GPU7_CF_INST_MEM_STREAM0_WRITE || cfInstruction.type == GPU7_CF_INST_MEM_STREAM1_WRITE) { // no sub-instructions } else if (cfInstruction.type == GPU7_CF_INST_MEM_RING_WRITE) { // no sub-instructions } else if (cfInstruction.type == GPU7_CF_INST_CALL) { // no sub-instructions } else if (cfInstruction.type == GPU7_CF_INST_RETURN) { // no sub-instructions } else if (cfInstruction.type == GPU7_CF_INST_EMIT_VERTEX) { // no sub-instructions } else { debug_printf("_parseClauses(): Unsupported clause 0x%x\n", cfInstruction.type); cemu_assert_unimplemented(); } } } // iterate all CF instructions and parse sub-instructions void LatteDecompiler_ParseClauses(LatteDecompilerShaderContext* shaderContext, uint8* programData, uint32 programSize) { LatteDecompilerShader* shader = shaderContext->shader; LatteDecompiler_ParseClauses(shaderContext, programData, programSize, shaderContext->cfInstructions); // parse subroutines for (auto& subroutineInfo : shaderContext->list_subroutines) { LatteDecompiler_ParseClauses(shaderContext, programData, programSize, subroutineInfo.instructions); } } void _LatteDecompiler_GenerateDataForFastAccess(LatteDecompilerShader* shader) { if (shader->hasError) return; for (size_t i = 0; i < shader->list_remappedUniformEntries.size(); i++) { LatteDecompilerRemappedUniformEntry_t* entry = shader->list_remappedUniformEntries.data() + i; if (entry->isRegister) { LatteFastAccessRemappedUniformEntry_register_t entryReg; entryReg.indexOffset = entry->index * 16; entryReg.mappedIndexOffset = entry->mappedIndex * 16; shader->list_remappedUniformEntries_register.push_back(entryReg); } else { LatteFastAccessRemappedUniformEntry_buffer_t entryBuf; uint32 kcacheBankIdOffset = entry->kcacheBankId* (7 * 4); entryBuf.indexOffset = entry->index * 16; entryBuf.mappedIndexOffset = entry->mappedIndex * 16; // find or create buffer group auto bufferGroup = std::find_if(shader->list_remappedUniformEntries_bufferGroups.begin(), shader->list_remappedUniformEntries_bufferGroups.end(), [kcacheBankIdOffset](const LatteDecompilerShader::_RemappedUniformBufferGroup& v) { return v.kcacheBankIdOffset == kcacheBankIdOffset; }); if (bufferGroup != shader->list_remappedUniformEntries_bufferGroups.end()) { (*bufferGroup).entries.emplace_back(entryBuf); } else { shader->list_remappedUniformEntries_bufferGroups.emplace_back(kcacheBankIdOffset).entries.emplace_back(entryBuf); } } } } void _LatteDecompiler_Process(LatteDecompilerShaderContext* shaderContext, uint8* programData, uint32 programSize) { // parse control flow instructions if (shaderContext->shader->hasError == false) LatteDecompiler_ParseCF(shaderContext, programData, programSize); // parse individual clauses if (shaderContext->shader->hasError == false) LatteDecompiler_ParseClauses(shaderContext, programData, programSize); // analyze if (shaderContext->shader->hasError == false) LatteDecompiler_analyze(shaderContext, shaderContext->shader); if (shaderContext->shader->hasError == false) LatteDecompiler_analyzeDataTypes(shaderContext); // emit code if (shaderContext->shader->hasError == false) { if (g_renderer->GetType() == RendererAPI::OpenGL || g_renderer->GetType() == RendererAPI::Vulkan) LatteDecompiler_emitGLSLShader(shaderContext, shaderContext->shader); #if ENABLE_METAL else LatteDecompiler_emitMSLShader(shaderContext, shaderContext->shader); #endif } LatteDecompiler_cleanup(shaderContext); // fast access _LatteDecompiler_GenerateDataForFastAccess(shaderContext->shader); } void LatteDecompiler_InitContext(LatteDecompilerShaderContext& dCtx, const LatteDecompilerOptions& options, LatteDecompilerOutput_t* output, LatteConst::ShaderType shaderType, uint64 shaderBaseHash, uint32* contextRegisters) { dCtx.output = output; dCtx.shaderType = shaderType; dCtx.options = &options; dCtx.shaderBaseHash = shaderBaseHash; dCtx.contextRegisters = contextRegisters; dCtx.contextRegistersNew = (LatteContextRegister*)contextRegisters; output->shaderType = shaderType; } void LatteDecompiler_DecompileVertexShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, struct LatteFetchShader* fetchShader, LatteDecompilerOptions& options, LatteDecompilerOutput_t* output) { cemu_assert_debug(fetchShader); cemu_assert_debug((programSize & 3) == 0); performanceMonitor.gpuTime_shaderCreate.beginMeasuring(); // prepare decompiler context LatteDecompilerShaderContext shaderContext = { 0 }; LatteDecompiler_InitContext(shaderContext, options, output, LatteConst::ShaderType::Vertex, shaderBaseHash, contextRegisters); shaderContext.fetchShader = fetchShader; // prepare shader (deprecated) LatteDecompilerShader* shader = new LatteDecompilerShader(LatteConst::ShaderType::Vertex); shader->compatibleFetchShader = shaderContext.fetchShader; output->shaderType = LatteConst::ShaderType::Vertex; shaderContext.shader = shader; output->shader = shader; for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { shader->textureUnitSamplerAssignment[i] = LATTE_DECOMPILER_SAMPLER_NONE; shader->textureUsesDepthCompare[i] = false; } // parse & compile _LatteDecompiler_Process(&shaderContext, programData, programSize); performanceMonitor.gpuTime_shaderCreate.endMeasuring(); } void LatteDecompiler_DecompileGeometryShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, uint8* gsCopyProgramData, uint32 gsCopyProgramSize, uint32 vsRingParameterCount, LatteDecompilerOptions& options, LatteDecompilerOutput_t* output) { cemu_assert_debug((programSize & 3) == 0); performanceMonitor.gpuTime_shaderCreate.beginMeasuring(); // prepare decompiler context LatteDecompilerShaderContext shaderContext = { 0 }; LatteDecompiler_InitContext(shaderContext, options, output, LatteConst::ShaderType::Geometry, shaderBaseHash, contextRegisters); // prepare shader LatteDecompilerShader* shader = new LatteDecompilerShader(LatteConst::ShaderType::Geometry); shader->ringParameterCountFromPrevStage = vsRingParameterCount; output->shaderType = LatteConst::ShaderType::Geometry; shaderContext.shader = shader; output->shader = shader; if (gsCopyProgramData == NULL) { shader->hasError = true; } else { shaderContext.parsedGSCopyShader = LatteGSCopyShaderParser_parse(gsCopyProgramData, gsCopyProgramSize); } for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { shader->textureUnitSamplerAssignment[i] = LATTE_DECOMPILER_SAMPLER_NONE; shader->textureUsesDepthCompare[i] = false; } // parse & compile _LatteDecompiler_Process(&shaderContext, programData, programSize); performanceMonitor.gpuTime_shaderCreate.endMeasuring(); } void LatteDecompiler_DecompilePixelShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, LatteDecompilerOptions& options, LatteDecompilerOutput_t* output) { cemu_assert_debug((programSize & 3) == 0); performanceMonitor.gpuTime_shaderCreate.beginMeasuring(); // prepare decompiler context LatteDecompilerShaderContext shaderContext = { 0 }; LatteDecompiler_InitContext(shaderContext, options, output, LatteConst::ShaderType::Pixel, shaderBaseHash, contextRegisters); shaderContext.contextRegisters = contextRegisters; // prepare shader LatteDecompilerShader* shader = new LatteDecompilerShader(LatteConst::ShaderType::Pixel); output->shaderType = LatteConst::ShaderType::Pixel; shaderContext.shader = shader; output->shader = shader; for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { shader->textureUnitSamplerAssignment[i] = LATTE_DECOMPILER_SAMPLER_NONE; shader->textureUsesDepthCompare[i] = false; } // parse & compile _LatteDecompiler_Process(&shaderContext, programData, programSize); performanceMonitor.gpuTime_shaderCreate.endMeasuring(); } void LatteDecompiler_cleanup(LatteDecompilerShaderContext* shaderContext) { shaderContext->cfInstructions.clear(); } ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Renderer/RendererShader.h" #include <boost/container/static_vector.hpp> namespace LatteDecompiler { enum class DataType { UNDEFINED = 0, U32 = 0, S32 = 0, FLOAT = 0 }; }; // decompiler shader types typedef struct { bool isRegister; // if true -> Uniform register, if false -> Uniform buffer uint8 kcacheBankId; // uniform buffer id (if uniform buffer) uint32 index; // uniform address (in 4-DWORD tuples) uint32 mappedIndex; // index in remapped uniform array }LatteDecompilerRemappedUniformEntry_t; typedef struct { uint32 indexOffset; // uniform address (in 4-DWORD tuples) uint32 mappedIndexOffset; // index in remapped uniform array }LatteFastAccessRemappedUniformEntry_register_t; typedef struct { uint16 indexOffset; // uniform address (in 4-DWORD tuples) uint16 mappedIndexOffset; // index in remapped uniform array }LatteFastAccessRemappedUniformEntry_buffer_t; typedef struct { uint32 texUnit; sint32 uniformLocation; float currentValue[2]; }LatteUniformTextureScaleEntry_t; struct LatteDecompilerShaderResourceMapping { LatteDecompilerShaderResourceMapping() { std::fill(textureUnitToBindingPoint, textureUnitToBindingPoint + LATTE_NUM_MAX_TEX_UNITS, -1); std::fill(uniformBuffersBindingPoint, uniformBuffersBindingPoint + LATTE_NUM_MAX_UNIFORM_BUFFERS, -1); std::fill(attributeMapping, attributeMapping + LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS, -1); } static const sint8 UNUSED_BINDING = -1; // most of this is for Vulkan sint8 setIndex{}; // texture sint8 textureUnitToBindingPoint[LATTE_NUM_MAX_TEX_UNITS]; // uniform buffer sint8 uniformVarsBufferBindingPoint{-1}; // special block for uniform registers/remapped array/custom variables sint8 uniformBuffersBindingPoint[LATTE_NUM_MAX_UNIFORM_BUFFERS]; // shader storage buffer for transform feedback (if alternative mode is used) sint8 tfStorageBindingPoint{-1}; // attributes (vertex shader only) sint8 attributeMapping[LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS]; // Metal exclusive sint8 verticesPerInstanceBinding{-1}; sint8 indexBufferBinding{-1}; sint8 indexTypeBinding{-1}; sint32 getTextureCount() { sint32 count = 0; for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (textureUnitToBindingPoint[i] >= 0) count++; } return count; } sint32 getTextureUnitFromBindingPoint(sint8 bindingPoint) { for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (textureUnitToBindingPoint[i] == bindingPoint) return i; } cemu_assert_debug(false); return -1; } // returns -1 if no there is no texture binding point sint32 getTextureBaseBindingPoint() { sint32 bindingPoint = 9999; for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (textureUnitToBindingPoint[i] >= 0) bindingPoint = std::min(bindingPoint, (sint32)textureUnitToBindingPoint[i]); } if (bindingPoint == 9999) return -1; return bindingPoint; } bool getUniformBufferBindingRange(sint32& minBinding, sint32& maxBinding) { sint32 minBindingPoint = 9999; sint32 maxBindingPoint = -9999; if (uniformVarsBufferBindingPoint >= 0) { minBindingPoint = std::min(minBindingPoint, (sint32)uniformVarsBufferBindingPoint); maxBindingPoint = std::max(maxBindingPoint, (sint32)uniformVarsBufferBindingPoint); } for (sint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if (uniformBuffersBindingPoint[i] >= 0) { minBindingPoint = std::min(minBindingPoint, (sint32)uniformBuffersBindingPoint[i]); maxBindingPoint = std::max(maxBindingPoint, (sint32)uniformBuffersBindingPoint[i]); } } if (minBindingPoint == 9999) return false; minBinding = minBindingPoint; maxBinding = maxBindingPoint; return true; } sint32 getTFStorageBufferBindingPoint() { return tfStorageBindingPoint; } bool hasUniformBuffers() { for (sint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if (uniformBuffersBindingPoint[i] >= 0) return true; } return false; } sint32 getAttribHostShaderIndex(uint32 semanticId) { cemu_assert_debug(semanticId < 0x100); return attributeMapping[semanticId]; } }; struct LatteDecompilerShader { LatteDecompilerShader(LatteConst::ShaderType shaderType) : shaderType(shaderType) {} LatteDecompilerShader* next{nullptr}; LatteConst::ShaderType shaderType; uint64 baseHash{0}; uint64 auxHash{0}; // vertex shader struct LatteFetchShader* compatibleFetchShader{}; // error tracking bool hasError{false}; // if set, the shader cannot be used // compact resource lists for optimized access struct QuickBufferEntry { uint32 index : 8; uint32 size : 24; }; boost::container::static_vector<QuickBufferEntry, LATTE_NUM_MAX_UNIFORM_BUFFERS> list_quickBufferList; uint8 textureUnitList[LATTE_NUM_MAX_TEX_UNITS]; uint8 textureUnitListCount{ 0 }; // input Latte::E_DIM textureUnitDim[LATTE_NUM_MAX_TEX_UNITS]{}; // dimension of texture unit, from the currently set texture bool textureIsIntegerFormat[LATTE_NUM_MAX_TEX_UNITS]{}; // analyzer stage (uniforms) uint8 uniformMode{0}; // determines how uniforms are managed within the shader (see LATTE_DECOMPILER_UNIFORM_MODE_* constants) uint64 uniformDataHash64[2]{0}; // used to avoid redundant calls to glUniform* std::vector<LatteDecompilerRemappedUniformEntry_t> list_remappedUniformEntries; // analyzer stage (textures) std::bitset<LATTE_NUM_MAX_TEX_UNITS> textureUnitMask2; uint16 textureUnitSamplerAssignment[LATTE_NUM_MAX_TEX_UNITS]{ 0 }; // LATTE_DECOMPILER_SAMPLER_NONE means undefined bool textureUsesDepthCompare[LATTE_NUM_MAX_TEX_UNITS]{}; uint8 textureRenderTargetIndex[LATTE_NUM_MAX_TEX_UNITS]; // analyzer stage (pixel outputs) uint32 pixelColorOutputMask{ 0 }; // from LSB to MSB, 1 bit per written output. 1 if written (indices of color attachments) // analyzer stage (depth output) bool depthMask{ false }; // analyzer stage (geometry shader parameters/inputs) uint32 ringParameterCount{ 0 }; uint32 ringParameterCountFromPrevStage{ 0 }; // used in geometry shader to hold VS ringParameterCount // analyzer stage (misc) std::bitset<LATTE_NUM_STREAMOUT_BUFFER> streamoutBufferWriteMask; bool hasStreamoutBufferWrite{ false }; // output code class StringBuf* strBuf_shaderSource{ nullptr }; // separable shaders RendererShader* shader{ nullptr }; bool isCustomShader{ false }; uint32 outputParameterMask{ 0 }; // resource mapping (binding points) LatteDecompilerShaderResourceMapping resourceMapping{}; // uniforms struct { sint32 loc_remapped; // uf_remappedVS/uf_remappedGS/uf_remappedPS sint32 loc_uniformRegister; // uf_uniformRegisterVS/uf_uniformRegisterGS/uf_uniformRegisterPS sint32 count_uniformRegister; sint32 loc_windowSpaceToClipSpaceTransform; // uf_windowSpaceToClipSpaceTransform sint32 loc_alphaTestRef; // uf_alphaTestRef sint32 loc_pointSize; // uf_pointSize sint32 loc_fragCoordScale; std::vector<LatteUniformTextureScaleEntry_t> list_ufTexRescale; // list of mappings for uf_tex*Scale <-> uniform location float ufCurrentValueAlphaTestRef; float ufCurrentValueFragCoordScale[2]; sint32 loc_verticesPerInstance; sint32 loc_streamoutBufferBase[LATTE_NUM_STREAMOUT_BUFFER]; uint32 uniformRangeSize; // entire size of uniform variable block }uniform{ 0 }; // fast access struct _RemappedUniformBufferGroup { _RemappedUniformBufferGroup(uint32 _kcacheBankIdOffset) : kcacheBankIdOffset(_kcacheBankIdOffset) {}; uint32 kcacheBankIdOffset; std::vector<LatteFastAccessRemappedUniformEntry_buffer_t> entries; }; std::vector<LatteFastAccessRemappedUniformEntry_register_t> list_remappedUniformEntries_register; std::vector<_RemappedUniformBufferGroup> list_remappedUniformEntries_bufferGroups; }; struct LatteDecompilerOutputUniformOffsets { sint32 offset_remapped; sint32 offset_uniformRegister; sint32 count_uniformRegister; // in vec4 sint32 offset_alphaTestRef; sint32 offset_pointSize; sint32 offset_fragCoordScale; sint32 offset_windowSpaceToClipSpaceTransform; sint32 offset_texScale[LATTE_NUM_MAX_TEX_UNITS]; sint32 offset_verticesPerInstance{-1}; sint32 offset_streamoutBufferBase[LATTE_NUM_STREAMOUT_BUFFER]{ -1, -1, -1, -1 }; sint32 offset_endOfBlock; // stores size of uniform variable block LatteDecompilerOutputUniformOffsets() { offset_remapped = -1; offset_uniformRegister = -1; count_uniformRegister = 0; offset_alphaTestRef = -1; offset_pointSize = -1; offset_fragCoordScale = -1; offset_windowSpaceToClipSpaceTransform = -1; for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) offset_texScale[i] = -1; offset_endOfBlock = 0; } }; struct LatteDecompilerOptions { bool usesGeometryShader{ false }; // floating point math bool strictMul{}; // if true, 0*anything=0 rule is emulated // Vulkan-specific bool useTFViaSSBO{ false }; struct { bool hasRoundingModeRTEFloat32{ false }; }spirvInstrinsics; }; struct LatteDecompilerOutput_t { LatteDecompilerShader* shader; LatteConst::ShaderType shaderType; // texture info std::bitset<LATTE_NUM_MAX_TEX_UNITS> textureUnitMask; // streamout info std::bitset<LATTE_NUM_STREAMOUT_BUFFER> streamoutBufferWriteMask; uint32 streamoutBufferStride[LATTE_NUM_STREAMOUT_BUFFER]{}; // uniform locations LatteDecompilerOutputUniformOffsets uniformOffsetsGL; LatteDecompilerOutputUniformOffsets uniformOffsetsVK; // mapping and binding information LatteDecompilerShaderResourceMapping resourceMappingGL; LatteDecompilerShaderResourceMapping resourceMappingVK; LatteDecompilerShaderResourceMapping resourceMappingMTL; }; struct LatteDecompilerSubroutineInfo; void LatteDecompiler_DecompileVertexShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, struct LatteFetchShader* fetchShader, LatteDecompilerOptions& options, LatteDecompilerOutput_t* output); void LatteDecompiler_DecompileGeometryShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, uint8* gsCopyProgramData, uint32 gsCopyProgramSize, uint32 vsRingParameterCount, LatteDecompilerOptions& options, LatteDecompilerOutput_t* output); void LatteDecompiler_DecompilePixelShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, LatteDecompilerOptions& options, LatteDecompilerOutput_t* output); // specialized shader parsers #define GPU7_COPY_SHADER_MAX_PARAMS (32) struct LatteGSCopyShaderStreamWrite_t { uint8 bufferIndex; uint16 offset; // offset in ring buffer from GS uint32 exportArrayBase; uint32 memWriteArraySize; uint32 memWriteCompMask; }; struct LatteParsedGSCopyShader { struct { uint16 offset; uint16 gprIndex; uint8 exportType; uint8 exportParam; }paramMapping[GPU7_COPY_SHADER_MAX_PARAMS]; sint32 numParam; // streamout writes std::vector<LatteGSCopyShaderStreamWrite_t> list_streamWrites; }; LatteParsedGSCopyShader* LatteGSCopyShaderParser_parse(uint8* programData, uint32 programSize); bool LatteGSCopyShaderParser_getExportTypeByOffset(LatteParsedGSCopyShader* shaderContext, uint32 offset, uint32* exportType, uint32* exportParam); ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Common/MemPtr.h" #include "HW/Latte/ISA/LatteReg.h" #if ENABLE_METAL #include "HW/Latte/Renderer/Metal/MetalCommon.h" #endif // Defined in LatteTextureLegacy.cpp Latte::E_GX2SURFFMT LatteTexture_ReconstructGX2Format(const Latte::LATTE_SQ_TEX_RESOURCE_WORD1_N& texUnitWord1, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N& texUnitWord4); /* * Return index of used color attachment based on shader pixel export index (0-7) */ sint32 LatteDecompiler_getColorOutputIndexFromExportIndex(LatteDecompilerShaderContext* shaderContext, sint32 exportIndex) { sint32 colorOutputIndex = -1; sint32 outputCounter = 0; uint32 cbShaderMask = shaderContext->contextRegisters[mmCB_SHADER_MASK]; uint32 cbShaderControl = shaderContext->contextRegisters[mmCB_SHADER_CONTROL]; for(sint32 m=0; m<8; m++) { uint32 outputMask = (cbShaderMask>>(m*4))&0xF; if( outputMask == 0 ) continue; cemu_assert_debug(outputMask == 0xF); // mask is unsupported if( outputCounter == exportIndex ) { colorOutputIndex = m; break; } outputCounter++; } cemu_assert_debug(colorOutputIndex != -1); // real outputs and outputs defined via mask do not match up return colorOutputIndex; } void _remapUniformAccess(LatteDecompilerShaderContext* shaderContext, bool isRegisterUniform, uint32 kcacheBankId, uint32 uniformIndex) { auto& list_uniformMapping = shaderContext->shader->list_remappedUniformEntries; for(uint32 i=0; i<list_uniformMapping.size(); i++) { LatteDecompilerRemappedUniformEntry_t* ufMapping = list_uniformMapping.data()+i; if( isRegisterUniform ) { if( ufMapping->isRegister == true && ufMapping->index == uniformIndex ) { return; } } else { if( ufMapping->isRegister == false && ufMapping->kcacheBankId == kcacheBankId && ufMapping->index == uniformIndex ) { return; } } } // add new mapping LatteDecompilerRemappedUniformEntry_t newMapping = {0}; if( isRegisterUniform ) { newMapping.isRegister = true; newMapping.index = uniformIndex; newMapping.mappedIndex = (uint32)list_uniformMapping.size(); } else { newMapping.isRegister = false; newMapping.kcacheBankId = kcacheBankId; newMapping.index = uniformIndex; newMapping.mappedIndex = (uint32)list_uniformMapping.size(); } list_uniformMapping.emplace_back(newMapping); } /* * Returns true if the instruction takes integer operands or returns a integer value */ bool _isIntegerInstruction(const LatteDecompilerALUInstruction& aluInstruction) { if (aluInstruction.isOP3 == false) { // OP2 switch (aluInstruction.opcode) { case ALU_OP2_INST_ADD: case ALU_OP2_INST_MUL: case ALU_OP2_INST_MUL_IEEE: case ALU_OP2_INST_MAX: case ALU_OP2_INST_MIN: case ALU_OP2_INST_FLOOR: case ALU_OP2_INST_FRACT: case ALU_OP2_INST_TRUNC: case ALU_OP2_INST_MOV: case ALU_OP2_INST_NOP: case ALU_OP2_INST_DOT4: case ALU_OP2_INST_DOT4_IEEE: case ALU_OP2_INST_CUBE: case ALU_OP2_INST_EXP_IEEE: case ALU_OP2_INST_LOG_CLAMPED: case ALU_OP2_INST_LOG_IEEE: case ALU_OP2_INST_SQRT_IEEE: case ALU_OP2_INST_SIN: case ALU_OP2_INST_COS: case ALU_OP2_INST_RNDNE: case ALU_OP2_INST_MAX_DX10: case ALU_OP2_INST_MIN_DX10: case ALU_OP2_INST_SETGT: case ALU_OP2_INST_SETGE: case ALU_OP2_INST_SETNE: case ALU_OP2_INST_SETE: case ALU_OP2_INST_PRED_SETE: case ALU_OP2_INST_PRED_SETGT: case ALU_OP2_INST_PRED_SETGE: case ALU_OP2_INST_PRED_SETNE: case ALU_OP2_INST_KILLE: case ALU_OP2_INST_KILLGT: case ALU_OP2_INST_KILLGE: case ALU_OP2_INST_RECIP_FF: case ALU_OP2_INST_RECIP_IEEE: case ALU_OP2_INST_RECIPSQRT_CLAMPED: case ALU_OP2_INST_RECIPSQRT_FF: case ALU_OP2_INST_RECIPSQRT_IEEE: return false; case ALU_OP2_INST_FLT_TO_INT: case ALU_OP2_INST_INT_TO_FLOAT: case ALU_OP2_INST_UINT_TO_FLOAT: case ALU_OP2_INST_ASHR_INT: case ALU_OP2_INST_LSHR_INT: case ALU_OP2_INST_LSHL_INT: case ALU_OP2_INST_MULLO_INT: case ALU_OP2_INST_MULLO_UINT: case ALU_OP2_INST_FLT_TO_UINT: case ALU_OP2_INST_AND_INT: case ALU_OP2_INST_OR_INT: case ALU_OP2_INST_XOR_INT: case ALU_OP2_INST_NOT_INT: case ALU_OP2_INST_ADD_INT: case ALU_OP2_INST_SUB_INT: case ALU_OP2_INST_MAX_INT: case ALU_OP2_INST_MIN_INT: case ALU_OP2_INST_MAX_UINT: case ALU_OP2_INST_MIN_UINT: case ALU_OP2_INST_SETE_INT: case ALU_OP2_INST_SETGT_INT: case ALU_OP2_INST_SETGE_INT: case ALU_OP2_INST_SETNE_INT: case ALU_OP2_INST_SETGT_UINT: case ALU_OP2_INST_SETGE_UINT: case ALU_OP2_INST_PRED_SETE_INT: case ALU_OP2_INST_PRED_SETGT_INT: case ALU_OP2_INST_PRED_SETGE_INT: case ALU_OP2_INST_PRED_SETNE_INT: case ALU_OP2_INST_KILLE_INT: case ALU_OP2_INST_KILLGT_INT: case ALU_OP2_INST_KILLNE_INT: case ALU_OP2_INST_MOVA_FLOOR: case ALU_OP2_INST_MOVA_INT: return true; // these return an integer result but are usually used only for conditionals case ALU_OP2_INST_SETE_DX10: case ALU_OP2_INST_SETGT_DX10: case ALU_OP2_INST_SETGE_DX10: case ALU_OP2_INST_SETNE_DX10: return true; default: #ifdef CEMU_DEBUG_ASSERT debug_printf("_isIntegerInstruction(): OP3=%s opcode=%02x\n", aluInstruction.isOP3 ? "true" : "false", aluInstruction.opcode); cemu_assert_debug(false); #endif break; } } else { // OP3 switch (aluInstruction.opcode) { case ALU_OP3_INST_MULADD: case ALU_OP3_INST_MULADD_D2: case ALU_OP3_INST_MULADD_M2: case ALU_OP3_INST_MULADD_M4: case ALU_OP3_INST_MULADD_IEEE: case ALU_OP3_INST_CMOVE: case ALU_OP3_INST_CMOVGT: case ALU_OP3_INST_CMOVGE: return false; case ALU_OP3_INST_CNDE_INT: case ALU_OP3_INST_CNDGT_INT: case ALU_OP3_INST_CMOVGE_INT: return true; default: #ifdef CEMU_DEBUG_ASSERT debug_printf("_isIntegerInstruction(): OP3=%s opcode=%02x\n", aluInstruction.isOP3?"true":"false", aluInstruction.opcode); #endif break; } } return false; } /* * Analyze ALU CF instruction and all instructions within the ALU clause */ void LatteDecompiler_analyzeALUClause(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { // check if this shader has any clause that potentially modifies the pixel execution state if( cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction->type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction->type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction->type == GPU7_CF_INST_ALU_BREAK || cfInstruction->type == GPU7_CF_INST_ALU_ELSE_AFTER ) { shaderContext->analyzer.modifiesPixelActiveState = true; } // analyze ALU instructions for(auto& aluInstruction : cfInstruction->instructionsALU) { // ignore NOP instruction if( !aluInstruction.isOP3 && aluInstruction.opcode == ALU_OP2_INST_NOP ) continue; // check for CUBE instruction if( !aluInstruction.isOP3 && aluInstruction.opcode == ALU_OP2_INST_CUBE ) { shaderContext->analyzer.hasRedcCUBE = true; } // check for integer instruction if (_isIntegerInstruction(aluInstruction)) shaderContext->analyzer.usesIntegerValues = true; // process all available operands (inputs) for(sint32 f=0; f<3; f++) { // check input for uniform access if( aluInstruction.sourceOperand[f].sel == 0xFFFFFFFF ) continue; // source operand not set/used // about uniform register and buffer access tracking: // for absolute indices we can determine a maximum size that is accessed // relative accesses are tricky because the upper bound of accessed indices is unknown // worst case we have to load the full file (256 * 16 byte entries) or for buffers an arbitrary upper bound (64KB in our case) if( GPU7_ALU_SRC_IS_CFILE(aluInstruction.sourceOperand[f].sel) ) { if (aluInstruction.sourceOperand[f].rel) { shaderContext->analyzer.uniformRegisterAccessTracker.TrackAccess(GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel), true); } else { _remapUniformAccess(shaderContext, true, 0, GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel)); shaderContext->analyzer.uniformRegisterAccessTracker.TrackAccess(GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel), false); } } else if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction.sourceOperand[f].sel) ) { // uniform bank 0 (uniform buffer with index cfInstruction->cBank0Index) uint32 uniformBufferIndex = cfInstruction->cBank0Index; cemu_assert(uniformBufferIndex < LATTE_NUM_MAX_UNIFORM_BUFFERS); uint32 offset = GPU7_ALU_SRC_GET_CBANK0_INDEX(aluInstruction.sourceOperand[f].sel)+cfInstruction->cBank0AddrBase; _remapUniformAccess(shaderContext, false, uniformBufferIndex, offset); shaderContext->analyzer.uniformBufferAccessTracker[uniformBufferIndex].TrackAccess(offset, aluInstruction.sourceOperand[f].rel); } else if( GPU7_ALU_SRC_IS_CBANK1(aluInstruction.sourceOperand[f].sel) ) { // uniform bank 1 (uniform buffer with index cfInstruction->cBank1Index) uint32 uniformBufferIndex = cfInstruction->cBank1Index; cemu_assert(uniformBufferIndex < LATTE_NUM_MAX_UNIFORM_BUFFERS); uint32 offset = GPU7_ALU_SRC_GET_CBANK1_INDEX(aluInstruction.sourceOperand[f].sel)+cfInstruction->cBank1AddrBase; _remapUniformAccess(shaderContext, false, uniformBufferIndex, offset); shaderContext->analyzer.uniformBufferAccessTracker[uniformBufferIndex].TrackAccess(offset, aluInstruction.sourceOperand[f].rel); } else if( GPU7_ALU_SRC_IS_GPR(aluInstruction.sourceOperand[f].sel) ) { sint32 gprIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction.sourceOperand[f].sel); shaderContext->analyzer.gprUseMask[gprIndex/8] |= (1<<(gprIndex%8)); if( aluInstruction.sourceOperand[f].rel != 0 ) { // if indexed register access is used, all possibly referenced registers are stored to a separate array at the beginning of the group shaderContext->analyzer.usesRelativeGPRRead = true; continue; } } } if( aluInstruction.destRel != 0 ) shaderContext->analyzer.usesRelativeGPRWrite = true; shaderContext->analyzer.gprUseMask[aluInstruction.destGpr/8] |= (1<<(aluInstruction.destGpr%8)); } } // analyze TEX CF instruction and all instructions within the TEX clause void LatteDecompiler_analyzeTEXClause(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { LatteDecompilerShader* shader = shaderContext->shader; for(auto& texInstruction : cfInstruction->instructionsTEX) { if( texInstruction.opcode == GPU7_TEX_INST_SAMPLE || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_L || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_LB || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_LZ || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_L || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_LZ || texInstruction.opcode == GPU7_TEX_INST_FETCH4 || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_G || texInstruction.opcode == GPU7_TEX_INST_LD ) { if (texInstruction.textureFetch.textureIndex < 0 || texInstruction.textureFetch.textureIndex >= LATTE_NUM_MAX_TEX_UNITS) { cemuLog_logDebug(LogType::Force, "Shader {:16x} has out of bounds texture access (texture {})", shaderContext->shader->baseHash, (sint32)texInstruction.textureFetch.textureIndex); continue; } if( texInstruction.textureFetch.samplerIndex < 0 || texInstruction.textureFetch.samplerIndex >= 0x12 ) cemu_assert_debug(false); if(shaderContext->output->textureUnitMask[texInstruction.textureFetch.textureIndex] && shader->textureUnitSamplerAssignment[texInstruction.textureFetch.textureIndex] != texInstruction.textureFetch.samplerIndex && shader->textureUnitSamplerAssignment[texInstruction.textureFetch.textureIndex] != LATTE_DECOMPILER_SAMPLER_NONE ) { cemu_assert_debug(false); } shaderContext->output->textureUnitMask[texInstruction.textureFetch.textureIndex] = true; shader->textureUnitSamplerAssignment[texInstruction.textureFetch.textureIndex] = texInstruction.textureFetch.samplerIndex; if( texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_L || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_LZ) shader->textureUsesDepthCompare[texInstruction.textureFetch.textureIndex] = true; bool useTexelCoords = false; if (texInstruction.opcode == GPU7_TEX_INST_SAMPLE && (texInstruction.textureFetch.unnormalized[0] && texInstruction.textureFetch.unnormalized[1] && texInstruction.textureFetch.unnormalized[2] && texInstruction.textureFetch.unnormalized[3])) useTexelCoords = true; else if (texInstruction.opcode == GPU7_TEX_INST_LD) useTexelCoords = true; if (useTexelCoords) { shaderContext->analyzer.texUnitUsesTexelCoordinates.set(texInstruction.textureFetch.textureIndex); } } else if( texInstruction.opcode == GPU7_TEX_INST_GET_COMP_TEX_LOD || texInstruction.opcode == GPU7_TEX_INST_GET_TEXTURE_RESINFO ) { if( texInstruction.textureFetch.textureIndex < 0 || texInstruction.textureFetch.textureIndex >= LATTE_NUM_MAX_TEX_UNITS ) debugBreakpoint(); if( texInstruction.textureFetch.samplerIndex != 0 ) debugBreakpoint(); // sampler is ignored and should be 0 shaderContext->output->textureUnitMask[texInstruction.textureFetch.textureIndex] = true; } else if( texInstruction.opcode == GPU7_TEX_INST_SET_CUBEMAP_INDEX ) { // no analysis required } else if (texInstruction.opcode == GPU7_TEX_INST_GET_GRADIENTS_H || texInstruction.opcode == GPU7_TEX_INST_GET_GRADIENTS_V) { // no analysis required } else if (texInstruction.opcode == GPU7_TEX_INST_SET_GRADIENTS_H || texInstruction.opcode == GPU7_TEX_INST_SET_GRADIENTS_V) { shaderContext->analyzer.hasGradientLookup = true; } else if( texInstruction.opcode == GPU7_TEX_INST_VFETCH ) { // VFETCH is used to access uniform buffers dynamically if( texInstruction.textureFetch.textureIndex >= 0x80 && texInstruction.textureFetch.textureIndex <= 0x8F ) { uint32 uniformBufferIndex = texInstruction.textureFetch.textureIndex - 0x80; shaderContext->analyzer.uniformBufferAccessTracker[uniformBufferIndex].TrackAccess(0, true); } else if( texInstruction.textureFetch.textureIndex == 0x9F && shader->shaderType == LatteConst::ShaderType::Geometry ) { // instruction to read geometry shader input from ringbuffer } else debugBreakpoint(); } else if (texInstruction.opcode == GPU7_TEX_INST_MEM) { // SSBO access shaderContext->analyzer.hasSSBORead = true; } else debugBreakpoint(); // mark read and written registers as used if(texInstruction.dstGpr < LATTE_NUM_GPR) shaderContext->analyzer.gprUseMask[texInstruction.dstGpr/8] |= (1<<(texInstruction.dstGpr%8)); if(texInstruction.srcGpr < LATTE_NUM_GPR) shaderContext->analyzer.gprUseMask[texInstruction.srcGpr/8] |= (1<<(texInstruction.srcGpr%8)); } } /* * Analyze export CF instruction */ void LatteDecompiler_analyzeExport(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { LatteDecompilerShader* shader = shaderContext->shader; if( shader->shaderType == LatteConst::ShaderType::Pixel ) { if (cfInstruction->exportType == 0 && cfInstruction->exportArrayBase < 8) { // remember color outputs that are written for(uint32 i=0; i<(cfInstruction->exportBurstCount+1); i++) { sint32 colorOutputIndex = LatteDecompiler_getColorOutputIndexFromExportIndex(shaderContext, cfInstruction->exportArrayBase+i); shader->pixelColorOutputMask |= (1<<colorOutputIndex); } } else if (cfInstruction->exportType == 0 && cfInstruction->exportArrayBase == 61) { #if ENABLE_METAL // Only check for depth buffer mask on Metal, as its not in the PS hash on other backends if (g_renderer->GetType() != RendererAPI::Metal || LatteMRT::GetActiveDepthBufferMask(*shaderContext->contextRegistersNew)) shader->depthMask = true; #endif } else debugBreakpoint(); } else if (shader->shaderType == LatteConst::ShaderType::Vertex) { if (cfInstruction->exportType == 2 && cfInstruction->exportArrayBase < 32) { shaderContext->shader->outputParameterMask |= (1<<cfInstruction->exportArrayBase); } else if (cfInstruction->exportType == 1 && cfInstruction->exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_POINT_SIZE) { shaderContext->analyzer.writesPointSize = true; } } // mark input GPRs as used for(uint32 i=0; i<(cfInstruction->exportBurstCount+1); i++) { shaderContext->analyzer.gprUseMask[(cfInstruction->exportSourceGPR+i)/8] |= (1<<((cfInstruction->exportSourceGPR+i)%8)); } } void LatteDecompiler_analyzeSubroutine(LatteDecompilerShaderContext* shaderContext, uint32 cfAddr) { // analyze CF and clauses up to RET statement // todo - find cfInstruction index from cfAddr cemu_assert_debug(false); for(auto& cfInstruction : shaderContext->cfInstructions) { if (cfInstruction.type == GPU7_CF_INST_ALU || cfInstruction.type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction.type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_BREAK || cfInstruction.type == GPU7_CF_INST_ALU_ELSE_AFTER) { LatteDecompiler_analyzeALUClause(shaderContext, &cfInstruction); } else if (cfInstruction.type == GPU7_CF_INST_TEX) { LatteDecompiler_analyzeTEXClause(shaderContext, &cfInstruction); } else if (cfInstruction.type == GPU7_CF_INST_EXPORT || cfInstruction.type == GPU7_CF_INST_EXPORT_DONE) { LatteDecompiler_analyzeExport(shaderContext, &cfInstruction); } else if (cfInstruction.type == GPU7_CF_INST_ELSE || cfInstruction.type == GPU7_CF_INST_POP) { shaderContext->analyzer.modifiesPixelActiveState = true; } else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { shaderContext->analyzer.modifiesPixelActiveState = true; } else if (cfInstruction.type == GPU7_CF_INST_LOOP_BREAK) { shaderContext->analyzer.modifiesPixelActiveState = true; } else if (cfInstruction.type == GPU7_CF_INST_EMIT_VERTEX) { // nothing to analyze } else if (cfInstruction.type == GPU7_CF_INST_CALL) { cemu_assert_debug(false); // CALLs inside subroutines are still todo } else { cemu_assert_unimplemented(); } } } namespace LatteDecompiler { void _initTextureBindingPointsGL(LatteDecompilerShaderContext* decompilerContext) { // for OpenGL we use the relative texture unit index for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (!decompilerContext->output->textureUnitMask[i]) continue; sint32 textureBindingPoint; if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) textureBindingPoint = i + LATTE_CEMU_VS_TEX_UNIT_BASE; else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) textureBindingPoint = i + LATTE_CEMU_GS_TEX_UNIT_BASE; else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) textureBindingPoint = i + LATTE_CEMU_PS_TEX_UNIT_BASE; decompilerContext->output->resourceMappingGL.textureUnitToBindingPoint[i] = textureBindingPoint; } } void _initTextureBindingPointsVK(LatteDecompilerShaderContext* decompilerContext) { // for Vulkan we use consecutive indices for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (!decompilerContext->output->textureUnitMask[i]) continue; decompilerContext->output->resourceMappingVK.textureUnitToBindingPoint[i] = decompilerContext->currentBindingPointVK; decompilerContext->currentBindingPointVK++; } } #if ENABLE_METAL void _initTextureBindingPointsMTL(LatteDecompilerShaderContext* decompilerContext) { // for Vulkan we use consecutive indices for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (!decompilerContext->output->textureUnitMask[i] || decompilerContext->shader->textureRenderTargetIndex[i] != 255) continue; decompilerContext->output->resourceMappingMTL.textureUnitToBindingPoint[i] = decompilerContext->currentTextureBindingPointMTL; decompilerContext->currentTextureBindingPointMTL++; } } #endif void _initHasUniformVarBlock(LatteDecompilerShaderContext* decompilerContext) { decompilerContext->hasUniformVarBlock = false; if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) decompilerContext->hasUniformVarBlock = true; else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) decompilerContext->hasUniformVarBlock = true; bool hasAnyViewportScaleDisabled = !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_SCALE_ENA() || !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_SCALE_ENA() || !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_SCALE_ENA(); // we currently only support all on/off. Individual component scaling is not supported cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_SCALE_ENA() == !hasAnyViewportScaleDisabled); cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_SCALE_ENA() == !hasAnyViewportScaleDisabled); cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_SCALE_ENA() == !hasAnyViewportScaleDisabled); cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA() == !hasAnyViewportScaleDisabled); cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_OFFSET_ENA() == !hasAnyViewportScaleDisabled); cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_OFFSET_ENA() == !hasAnyViewportScaleDisabled); if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && hasAnyViewportScaleDisabled) decompilerContext->hasUniformVarBlock = true; // uf_windowSpaceToClipSpaceTransform bool alphaTestEnable = decompilerContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_TEST_ENABLE(); if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && alphaTestEnable != 0) decompilerContext->hasUniformVarBlock = true; // uf_alphaTestRef if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) decompilerContext->hasUniformVarBlock = true; // uf_fragCoordScale if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->analyzer.outputPointSize && decompilerContext->analyzer.writesPointSize == false) decompilerContext->hasUniformVarBlock = true; // uf_pointSize if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry && decompilerContext->analyzer.outputPointSize && decompilerContext->analyzer.writesPointSize == false) decompilerContext->hasUniformVarBlock = true; // uf_pointSize if (decompilerContext->analyzer.useSSBOForStreamout && (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && !decompilerContext->options->usesGeometryShader) || (decompilerContext->shaderType == LatteConst::ShaderType::Geometry)) { decompilerContext->hasUniformVarBlock = true; // uf_verticesPerInstance and uf_streamoutBufferBase* } #if ENABLE_METAL if (g_renderer->GetType() == RendererAPI::Metal) { bool usesGeometryShader = UseGeometryShader(*decompilerContext->contextRegistersNew, decompilerContext->options->usesGeometryShader); if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && usesGeometryShader) decompilerContext->hasUniformVarBlock = true; // uf_verticesPerInstance } #endif } void _initUniformBindingPoints(LatteDecompilerShaderContext* decompilerContext) { // check if uniform vars block has at least one variable _initHasUniformVarBlock(decompilerContext); if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) { for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++) { if (decompilerContext->analyzer.texUnitUsesTexelCoordinates.test(t) == false) continue; decompilerContext->hasUniformVarBlock = true; // uf_tex%dScale } } // assign binding point to uniform var block if (decompilerContext->hasUniformVarBlock) { decompilerContext->output->resourceMappingVK.uniformVarsBufferBindingPoint = decompilerContext->currentBindingPointVK; decompilerContext->currentBindingPointVK++; decompilerContext->output->resourceMappingMTL.uniformVarsBufferBindingPoint = decompilerContext->currentBufferBindingPointMTL; decompilerContext->currentBufferBindingPointMTL++; } // assign binding points to uniform buffers if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { // for Vulkan we use consecutive indices for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if (!decompilerContext->analyzer.uniformBufferAccessTracker[i].HasAccess()) continue; sint32 uniformBindingPoint = i; if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) uniformBindingPoint += 64; else if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) uniformBindingPoint += 0; else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) uniformBindingPoint += 32; decompilerContext->output->resourceMappingVK.uniformBuffersBindingPoint[i] = decompilerContext->currentBindingPointVK; decompilerContext->currentBindingPointVK++; decompilerContext->output->resourceMappingMTL.uniformBuffersBindingPoint[i] = decompilerContext->currentBufferBindingPointMTL; decompilerContext->currentBufferBindingPointMTL++; } // for OpenGL we use the relative buffer index for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if (!decompilerContext->analyzer.uniformBufferAccessTracker[i].HasAccess()) continue; sint32 uniformBindingPoint = i; if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) uniformBindingPoint += 64; else if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) uniformBindingPoint += 0; else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) uniformBindingPoint += 32; decompilerContext->output->resourceMappingGL.uniformBuffersBindingPoint[i] = uniformBindingPoint; } } // shader storage buffer for alternative transform feedback path if (decompilerContext->analyzer.useSSBOForStreamout) { decompilerContext->output->resourceMappingVK.tfStorageBindingPoint = decompilerContext->currentBindingPointVK; decompilerContext->currentBindingPointVK++; decompilerContext->output->resourceMappingMTL.tfStorageBindingPoint = decompilerContext->currentBufferBindingPointMTL; decompilerContext->currentBufferBindingPointMTL++; } } void _initAttributeBindingPoints(LatteDecompilerShaderContext* decompilerContext) { if (decompilerContext->shaderType != LatteConst::ShaderType::Vertex) return; // create input attribute binding mapping // OpenGL and Vulkan use consecutive indices starting at 0 sint8 bindingIndex = 0; for (sint32 i = 0; i < LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS; i++) { if (decompilerContext->analyzer.inputAttributSemanticMask[i]) { decompilerContext->output->resourceMappingGL.attributeMapping[i] = bindingIndex; decompilerContext->output->resourceMappingVK.attributeMapping[i] = bindingIndex; decompilerContext->output->resourceMappingMTL.attributeMapping[i] = bindingIndex; bindingIndex++; } } } } /* * Analyze the shader program * This will help to determine: * 1) Uniform usage * 2) Texture usage * 3) Data types * 4) CF stack and execution flow */ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader) { // analyze render state shaderContext->analyzer.isPointsPrimitive = shaderContext->contextRegistersNew->VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE() == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS; shaderContext->analyzer.hasStreamoutEnable = shaderContext->contextRegisters[mmVGT_STRMOUT_EN] != 0; // set if the shader is used for transform feedback operations if (shaderContext->shaderType == LatteConst::ShaderType::Vertex && !shaderContext->options->usesGeometryShader) shaderContext->analyzer.outputPointSize = shaderContext->analyzer.isPointsPrimitive; else if (shaderContext->shaderType == LatteConst::ShaderType::Geometry) { uint32 gsOutPrimType = shaderContext->contextRegisters[mmVGT_GS_OUT_PRIM_TYPE]; if (gsOutPrimType == 0) // points shaderContext->analyzer.outputPointSize = true; } // analyze input attributes for vertex/geometry shader if (shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Geometry) { if(shaderContext->fetchShader) { LatteFetchShader* parsedFetchShader = shaderContext->fetchShader; for(auto& bufferGroup : parsedFetchShader->bufferGroups) { for (sint32 i = 0; i < bufferGroup.attribCount; i++) { uint8 semanticId = bufferGroup.attrib[i].semanticId; if (semanticId == 0xFF) { // unused attribute? Found in Hot Wheels: World's best driver continue; } cemu_assert_debug(semanticId < 0x80); shaderContext->analyzer.inputAttributSemanticMask[semanticId] = true; } } } } // list of subroutines (call destinations) std::vector<uint32> list_subroutineAddrs; // analyze CF and clauses for(auto& cfInstruction : shaderContext->cfInstructions) { if (cfInstruction.type == GPU7_CF_INST_ALU || cfInstruction.type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction.type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_BREAK || cfInstruction.type == GPU7_CF_INST_ALU_ELSE_AFTER) { LatteDecompiler_analyzeALUClause(shaderContext, &cfInstruction); } else if (cfInstruction.type == GPU7_CF_INST_TEX) { LatteDecompiler_analyzeTEXClause(shaderContext, &cfInstruction); } else if (cfInstruction.type == GPU7_CF_INST_EXPORT || cfInstruction.type == GPU7_CF_INST_EXPORT_DONE) { LatteDecompiler_analyzeExport(shaderContext, &cfInstruction); } else if (cfInstruction.type == GPU7_CF_INST_ELSE || cfInstruction.type == GPU7_CF_INST_POP) { shaderContext->analyzer.modifiesPixelActiveState = true; } else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { shaderContext->analyzer.modifiesPixelActiveState = true; shaderContext->analyzer.hasLoops = true; } else if (cfInstruction.type == GPU7_CF_INST_LOOP_BREAK) { shaderContext->analyzer.modifiesPixelActiveState = true; shaderContext->analyzer.hasLoops = true; } else if (cfInstruction.type == GPU7_CF_INST_MEM_STREAM0_WRITE || cfInstruction.type == GPU7_CF_INST_MEM_STREAM1_WRITE) { uint32 streamoutBufferIndex; if (cfInstruction.type == GPU7_CF_INST_MEM_STREAM0_WRITE) streamoutBufferIndex = 0; else if (cfInstruction.type == GPU7_CF_INST_MEM_STREAM1_WRITE) streamoutBufferIndex = 1; else cemu_assert_debug(false); shaderContext->analyzer.hasStreamoutWrite = true; cemu_assert(streamoutBufferIndex < shaderContext->output->streamoutBufferWriteMask.size()); shaderContext->output->streamoutBufferWriteMask[streamoutBufferIndex] = true; uint32 vectorWriteSize = 0; for (sint32 f = 0; f < 4; f++) { if ((cfInstruction.memWriteCompMask & (1 << f)) != 0) vectorWriteSize = (f + 1) * 4; shaderContext->output->streamoutBufferStride[f] = shaderContext->contextRegisters[mmVGT_STRMOUT_VTX_STRIDE_0 + f * 4] << 2; } cemu_assert_debug((cfInstruction.exportArrayBase * 4 + vectorWriteSize) <= shaderContext->output->streamoutBufferStride[streamoutBufferIndex]); } else if (cfInstruction.type == GPU7_CF_INST_MEM_RING_WRITE) { // track number of parameters that are output (simplified by just tracking the offset of the last one) if (cfInstruction.memWriteElemSize != 3) debugBreakpoint(); if (cfInstruction.exportBurstCount != 0 && cfInstruction.memWriteElemSize != 3) { debugBreakpoint(); } uint32 dwordWriteCount = (cfInstruction.exportBurstCount + 1) * 4; uint32 numRingParameter = (cfInstruction.exportArrayBase + dwordWriteCount) / 4; shader->ringParameterCount = std::max(shader->ringParameterCount, numRingParameter); // mark input GPRs as used for (uint32 i = 0; i < (cfInstruction.exportBurstCount + 1); i++) { shaderContext->analyzer.gprUseMask[(cfInstruction.exportSourceGPR + i) / 8] |= (1 << ((cfInstruction.exportSourceGPR + i) % 8)); } } else if (cfInstruction.type == GPU7_CF_INST_EMIT_VERTEX) { shaderContext->analyzer.numEmitVertex++; } else if (cfInstruction.type == GPU7_CF_INST_CALL) { // CALL instruction does not need analyzing // and subroutines are analyzed separately } else cemu_assert_unimplemented(); } // analyze subroutines for (auto subroutineAddr : list_subroutineAddrs) { LatteDecompiler_analyzeSubroutine(shaderContext, subroutineAddr); } // decide which uniform mode to use bool hasAnyDynamicBufferAccess = false; bool hasAnyBufferAccess = false; for(auto& it : shaderContext->analyzer.uniformBufferAccessTracker) { if( it.HasRelativeAccess() ) hasAnyDynamicBufferAccess = true; if( it.HasAccess() ) hasAnyBufferAccess = true; } if (hasAnyDynamicBufferAccess) { shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK; } else if(shaderContext->analyzer.uniformRegisterAccessTracker.HasRelativeAccess() ) { shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE; } else if(hasAnyBufferAccess || shaderContext->analyzer.uniformRegisterAccessTracker.HasAccess() ) { shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED; } else { shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_NONE; } // generate compact list of uniform buffers (for faster access) cemu_assert_debug(shader->list_quickBufferList.empty()); for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if( !shaderContext->analyzer.uniformBufferAccessTracker[i].HasAccess() ) continue; LatteDecompilerShader::QuickBufferEntry entry; entry.index = i; entry.size = shaderContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(shaderContext->shaderBaseHash, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE) * 16; shader->list_quickBufferList.push_back(entry); } // get dimension of each used texture _LatteRegisterSetTextureUnit* texRegs = nullptr; if( shader->shaderType == LatteConst::ShaderType::Vertex ) texRegs = shaderContext->contextRegistersNew->SQ_TEX_START_VS; else if( shader->shaderType == LatteConst::ShaderType::Pixel ) texRegs = shaderContext->contextRegistersNew->SQ_TEX_START_PS; else if( shader->shaderType == LatteConst::ShaderType::Geometry ) texRegs = shaderContext->contextRegistersNew->SQ_TEX_START_GS; for(sint32 i=0; i<LATTE_NUM_MAX_TEX_UNITS; i++) { if (!shaderContext->output->textureUnitMask[i]) { // texture unit not used shader->textureUnitDim[i] = (Latte::E_DIM)0xFF; continue; } auto& texUnit = texRegs[i]; auto dim = texUnit.word0.get_DIM(); shader->textureUnitDim[i] = dim; if(dim == Latte::E_DIM::DIM_CUBEMAP) shaderContext->analyzer.hasCubeMapTexture = true; shader->textureIsIntegerFormat[i] = texUnit.word4.get_NUM_FORM_ALL() == Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_INT; } // generate list of used texture units shader->textureUnitListCount = 0; for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (shaderContext->output->textureUnitMask[i]) { shader->textureUnitList[shader->textureUnitListCount] = i; shader->textureUnitListCount++; } shader->textureRenderTargetIndex[i] = 255; } // check if textures are used as render targets if (shader->shaderType == LatteConst::ShaderType::Pixel) { struct { sint32 index; MPTR physAddr; Latte::E_GX2SURFFMT format; Latte::E_HWTILEMODE tileMode; } colorBuffers[LATTE_NUM_COLOR_TARGET]{}; uint8 colorBufferMask = LatteMRT::GetActiveColorBufferMask(shader, *shaderContext->contextRegistersNew); sint32 colorBufferCount = 0; for (sint32 i = 0; i < LATTE_NUM_COLOR_TARGET; i++) { auto& colorBuffer = colorBuffers[colorBufferCount]; if (((colorBufferMask) & (1 << i)) == 0) continue; // color buffer not enabled uint32* colorBufferRegBase = shaderContext->contextRegisters + (mmCB_COLOR0_BASE + i); uint32 regColorBufferBase = colorBufferRegBase[mmCB_COLOR0_BASE - mmCB_COLOR0_BASE] & 0xFFFFFF00; // the low 8 bits are ignored? How to Survive seems to rely on this uint32 regColorInfo = colorBufferRegBase[mmCB_COLOR0_INFO - mmCB_COLOR0_BASE]; MPTR colorBufferPhysMem = regColorBufferBase; Latte::E_HWTILEMODE colorBufferTileMode = (Latte::E_HWTILEMODE)((regColorInfo >> 8) & 0xF); Latte::E_GX2SURFFMT colorBufferFormat = LatteMRT::GetColorBufferFormat(i, *shaderContext->contextRegistersNew); colorBuffer = {i, colorBufferPhysMem, colorBufferFormat, colorBufferTileMode}; colorBufferCount++; } for (sint32 i = 0; i < shader->textureUnitListCount; i++) { sint32 textureIndex = shader->textureUnitList[i]; const auto& texRegister = texRegs[textureIndex]; // get physical address of texture data MPTR physAddr = (texRegister.word2.get_BASE_ADDRESS() << 8); if (physAddr == MPTR_NULL) continue; // invalid data auto tileMode = texRegister.word0.get_TILE_MODE(); // Check for dimension auto dim = shader->textureUnitDim[textureIndex]; // TODO: 2D arrays could be supported as well if (dim != Latte::E_DIM::DIM_2D) continue; // Check for mip level auto lastMip = texRegister.word5.get_LAST_LEVEL(); // TODO: multiple mip levels could be supported as well if (lastMip != 0) continue; Latte::E_GX2SURFFMT format = LatteTexture_ReconstructGX2Format(texRegister.word1, texRegister.word4); // Check if the texture is used as render target for (sint32 j = 0; j < colorBufferCount; j++) { const auto& colorBuffer = colorBuffers[j]; if (physAddr == colorBuffer.physAddr && format == colorBuffer.format && tileMode == colorBuffer.tileMode) { shader->textureRenderTargetIndex[textureIndex] = colorBuffer.index; break; } } } } // for geometry shaders check the copy shader for stream writes if (shader->shaderType == LatteConst::ShaderType::Geometry && shaderContext->parsedGSCopyShader->list_streamWrites.empty() == false) { shaderContext->analyzer.hasStreamoutWrite = true; if (shaderContext->contextRegisters[mmVGT_STRMOUT_EN] != 0) shaderContext->analyzer.hasStreamoutEnable = true; for (auto& it : shaderContext->parsedGSCopyShader->list_streamWrites) { shaderContext->output->streamoutBufferWriteMask[it.bufferIndex] = true; uint32 vectorWriteSize = 0; for (sint32 f = 0; f < 4; f++) { if ((it.memWriteCompMask&(1 << f)) != 0) vectorWriteSize = (f + 1) * 4; } shaderContext->output->streamoutBufferStride[it.bufferIndex] = std::max(shaderContext->output->streamoutBufferStride[it.bufferIndex], it.exportArrayBase * 4 + vectorWriteSize); } } // analyze input attributes again (if shader has relative GPR read) if(shaderContext->analyzer.usesRelativeGPRRead && (shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Geometry) ) { if(shaderContext->fetchShader) { LatteFetchShader* parsedFetchShader = shaderContext->fetchShader; for(auto& bufferGroup : parsedFetchShader->bufferGroups) { for (sint32 i = 0; i < bufferGroup.attribCount; i++) { uint32 registerIndex; // get register index based on vtx semantic table uint32 attributeShaderLoc = 0xFFFFFFFF; for (sint32 f = 0; f < 32; f++) { if (shaderContext->contextRegisters[mmSQ_VTX_SEMANTIC_0 + f] == bufferGroup.attrib[i].semanticId) { attributeShaderLoc = f; break; } } if (attributeShaderLoc == 0xFFFFFFFF) continue; // attribute is not mapped to VS input registerIndex = attributeShaderLoc + 1; shaderContext->analyzer.gprUseMask[registerIndex / 8] |= (1 << (registerIndex % 8)); } } } } else if (shaderContext->analyzer.usesRelativeGPRRead && shader->shaderType == LatteConst::ShaderType::Pixel) { // mark pixel shader inputs as used if there is any relative GPR access LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); for (sint32 i = 0; i < psInputTable->count; i++) { shaderContext->analyzer.gprUseMask[i / 8] |= (1 << (i % 8)); } } // analyze CF stack sint32 cfCurrentStackDepth = 0; sint32 cfCurrentMaxStackDepth = 0; for(auto& cfInstruction : shaderContext->cfInstructions) { if (cfInstruction.type == GPU7_CF_INST_ALU) { // no effect on stack depth cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_ALU_PUSH_BEFORE ) { cfCurrentStackDepth++; cfCurrentMaxStackDepth = std::max(cfCurrentMaxStackDepth, cfCurrentStackDepth); cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_ALU_POP_AFTER) { cfInstruction.activeStackDepth = cfCurrentStackDepth; cfCurrentStackDepth--; } else if (cfInstruction.type == GPU7_CF_INST_ALU_POP2_AFTER) { cfInstruction.activeStackDepth = cfCurrentStackDepth; cfCurrentStackDepth -= 2; } else if (cfInstruction.type == GPU7_CF_INST_ALU_BREAK ) { cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_ALU_ELSE_AFTER) { if (cfInstruction.popCount != 0) debugBreakpoint(); cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_ELSE ) { //if (cfInstruction.popCount != 0) // debugBreakpoint(); -> Only relevant when ELSE jump is taken cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_POP) { cfInstruction.activeStackDepth = cfCurrentStackDepth; cfCurrentStackDepth -= cfInstruction.popCount; if (cfCurrentStackDepth < 0) debugBreakpoint(); } else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { // no effect on stack depth cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_LOOP_BREAK) { // since we assume that the break is not taken (for all pixels), we also don't need to worry about the stack depth adjustment cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_TEX) { // no effect on stack depth cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_EXPORT || cfInstruction.type == GPU7_CF_INST_EXPORT_DONE) { // no effect on stack depth cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_MEM_STREAM0_WRITE || cfInstruction.type == GPU7_CF_INST_MEM_STREAM1_WRITE) { // no effect on stack depth cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_MEM_RING_WRITE) { // no effect on stack depth cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_EMIT_VERTEX) { // no effect on stack depth cfInstruction.activeStackDepth = cfCurrentStackDepth; } else if (cfInstruction.type == GPU7_CF_INST_CALL) { // no effect on stack depth cfInstruction.activeStackDepth = cfCurrentStackDepth; } else { cemu_assert_debug(false); } } shaderContext->analyzer.activeStackMaxDepth = cfCurrentMaxStackDepth; if (cfCurrentStackDepth != 0) { debug_printf("cfCurrentStackDepth is not zero after all CF instructions. depth is %d\n", cfCurrentStackDepth); cemu_assert_debug(false); } if(list_subroutineAddrs.empty() == false) cemuLog_logDebug(LogType::Force, "Todo - analyze shader subroutine CF stack"); // TF mode if (shaderContext->options->useTFViaSSBO && shaderContext->output->streamoutBufferWriteMask.any()) { shaderContext->analyzer.useSSBOForStreamout = true; } // assign binding points if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) shaderContext->output->resourceMappingVK.setIndex = 0; else if (shaderContext->shaderType == LatteConst::ShaderType::Pixel) shaderContext->output->resourceMappingVK.setIndex = 1; else if (shaderContext->shaderType == LatteConst::ShaderType::Geometry) shaderContext->output->resourceMappingVK.setIndex = 2; LatteDecompiler::_initTextureBindingPointsGL(shaderContext); LatteDecompiler::_initTextureBindingPointsVK(shaderContext); #if ENABLE_METAL LatteDecompiler::_initTextureBindingPointsMTL(shaderContext); #endif LatteDecompiler::_initUniformBindingPoints(shaderContext); LatteDecompiler::_initAttributeBindingPoints(shaderContext); shaderContext->output->resourceMappingMTL.verticesPerInstanceBinding = shaderContext->currentBufferBindingPointMTL++; shaderContext->output->resourceMappingMTL.indexBufferBinding = shaderContext->currentBufferBindingPointMTL++; shaderContext->output->resourceMappingMTL.indexTypeBinding = shaderContext->currentBufferBindingPointMTL++; } ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/OS/libs/gx2/GX2.h" // todo - remove dependency #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "config/ActiveSettings.h" #include "util/helpers/StringBuf.h" #include <bitset> #include <boost/container/small_vector.hpp> #define _CRLF "\r\n" void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContext, StringBuf* src, LatteParsedFetchShaderAttribute_t* attrib); /* * Variable names: * R0-R127 temp * Most variables are multi-typed and the respective type is appended to the name * Type suffixes are: f (float), i (32bit int), ui (unsigned 32bit int) * Examples: R13ui.x, tempf.z */ // local prototypes void _emitTypeConversionPrefix(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType); void _emitTypeConversionSuffix(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType); void LatteDecompiler_emitClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, bool isSubroutine); const char* _getShaderUniformBlockInterfaceName(LatteConst::ShaderType mode) { switch (mode) { case LatteConst::ShaderType::Vertex: return "uniformBlockVS"; case LatteConst::ShaderType::Pixel: return "uniformBlockPS"; case LatteConst::ShaderType::Geometry: return "uniformBlockGS"; default: break; } cemu_assert_unimplemented(); return nullptr; } const char* _getShaderUniformBlockVariableName(LatteConst::ShaderType mode) { switch (mode) { case LatteConst::ShaderType::Vertex: return "uf_blockVS"; case LatteConst::ShaderType::Pixel: return "uf_blockPS"; case LatteConst::ShaderType::Geometry: return "uf_blockGS"; default: break; } cemu_assert_unimplemented(); return nullptr; } const char* _getTextureUnitVariablePrefixName(LatteConst::ShaderType mode) { switch (mode) { case LatteConst::ShaderType::Vertex: return "textureUnitVS"; case LatteConst::ShaderType::Pixel: return "textureUnitPS"; case LatteConst::ShaderType::Geometry: return "textureUnitGS"; } cemu_assert_unimplemented(); return nullptr; } const char* _getElementStrByIndex(uint32 channel) { switch (channel) { case 0: return "x"; case 1: return "y"; case 2: return "z"; case 3: return "w"; } return "UNDEFINED"; } char _tempGenString[64][256]; uint32 _tempGenStringIndex = 0; char* _getTempString() { char* str = _tempGenString[_tempGenStringIndex]; _tempGenStringIndex = (_tempGenStringIndex+1)%64; return str; } static char* _getActiveMaskVarName(LatteDecompilerShaderContext* shaderContext, sint32 index) { char* varName = _getTempString(); if (shaderContext->isSubroutine) sprintf(varName, "activeMaskStackSub%04x[%d]", shaderContext->subroutineInfo->cfAddr, index); else sprintf(varName, "activeMaskStack[%d]", index); return varName; } static char* _getActiveMaskCVarName(LatteDecompilerShaderContext* shaderContext, sint32 index) { char* varName = _getTempString(); if (shaderContext->isSubroutine) sprintf(varName, "activeMaskStackCSub%04x[%d]", shaderContext->subroutineInfo->cfAddr, index); else sprintf(varName, "activeMaskStackC[%d]", index); return varName; } static char* _getRegisterVarName(LatteDecompilerShaderContext* shaderContext, uint32 index, sint32 destRelIndexMode=-1) { auto type = shaderContext->typeTracker.defaultDataType; char* tempStr = _getTempString(); if (shaderContext->typeTracker.useArrayGPRs == false) { if (type == LATTE_DECOMPILER_DTYPE_SIGNED_INT) sprintf(tempStr, "R%di", index); else if (type == LATTE_DECOMPILER_DTYPE_FLOAT) sprintf(tempStr, "R%df", index); } else { char destRelOffset[32]; if (destRelIndexMode >= 0) { if (destRelIndexMode == GPU7_INDEX_AR_X) strcpy(destRelOffset, "ARi.x"); else if (destRelIndexMode == GPU7_INDEX_AR_Y) strcpy(destRelOffset, "ARi.y"); else if (destRelIndexMode == GPU7_INDEX_AR_Z) strcpy(destRelOffset, "ARi.z"); else if (destRelIndexMode == GPU7_INDEX_AR_W) strcpy(destRelOffset, "ARi.w"); else debugBreakpoint(); if (type == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { sprintf(tempStr, "Ri[%d+%s]", index, destRelOffset); } else if (type == LATTE_DECOMPILER_DTYPE_FLOAT) { sprintf(tempStr, "Rf[%d+%s]", index, destRelOffset); } } else { if (type == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { sprintf(tempStr, "Ri[%d]", index); } else if (type == LATTE_DECOMPILER_DTYPE_FLOAT) { sprintf(tempStr, "Rf[%d]", index); } } } return tempStr; } static void _appendRegisterTypeSuffix(StringBuf* src, sint32 dataType) { if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->add("i"); else if (dataType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) src->add("ui"); else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->add("f"); else cemu_assert_unimplemented(); } // appends x/y/z/w static void _appendChannel(StringBuf* src, sint32 channelIndex) { cemu_assert_debug(channelIndex >= 0 && channelIndex <= 3); switch (channelIndex) { case 0: src->add("x"); return; case 1: src->add("y"); return; case 2: src->add("z"); return; case 3: src->add("w"); return; } } // appends .x/.y/.z/.w static void _appendChannelAccess(StringBuf* src, sint32 channelIndex) { cemu_assert_debug(channelIndex >= 0 && channelIndex <= 3); switch (channelIndex) { case 0: src->add(".x"); return; case 1: src->add(".y"); return; case 2: src->add(".z"); return; case 3: src->add(".w"); return; } } static void _appendPVPS(LatteDecompilerShaderContext* shaderContext, StringBuf* src, uint32 groupIndex, uint8 aluUnit) { cemu_assert_debug(aluUnit < 5); if (aluUnit == 4) { src->addFmt("PS{}", (groupIndex & 1)); _appendRegisterTypeSuffix(src, shaderContext->typeTracker.defaultDataType); return; } src->addFmt("PV{}", (groupIndex & 1)); _appendRegisterTypeSuffix(src, shaderContext->typeTracker.defaultDataType); _appendChannel(src, aluUnit); } std::string _FormatFloatAsGLSLConstant(float f) { char floatAsStr[64]; size_t floatAsStrLen = fmt::format_to_n(floatAsStr, 64, "{:#}", f).size; size_t floatAsStrLenOrg = floatAsStrLen; if(floatAsStrLen > 0 && floatAsStr[floatAsStrLen-1] == '.') { floatAsStr[floatAsStrLen] = '0'; floatAsStrLen++; } cemu_assert(floatAsStrLen < 50); // constant suspiciously long? floatAsStr[floatAsStrLen] = '\0'; cemu_assert_debug(floatAsStrLen >= 3); // shortest possible form is "0.0" return floatAsStr; } // tracks PV/PS and register backups struct ALUClauseTemporariesState { struct PVPSAlias { enum class LOCATION_TYPE : uint8 { LOCATION_NONE, LOCATION_GPR, LOCATION_PVPS, }; LOCATION_TYPE location{ LOCATION_TYPE::LOCATION_NONE }; uint8 index; // GPR index or temporary index uint8 aluUnit; // x,y,z,w (or 5 for PS) void SetLocationGPR(uint8 gprIndex, uint8 channel) { cemu_assert_debug(channel < 4); this->location = LOCATION_TYPE::LOCATION_GPR; this->index = gprIndex; this->aluUnit = channel; } void SetLocationPSPVTemporary(uint8 aluUnit, uint32 groupIndex) { cemu_assert_debug(aluUnit < 5); this->location = LOCATION_TYPE::LOCATION_PVPS; this->index = groupIndex & 1; this->aluUnit = aluUnit; } }; struct GPRTemporary { GPRTemporary(uint8 gprIndex, uint8 channel, uint8 backupVarIndex) : gprIndex(gprIndex), channel(channel), backupVarIndex(backupVarIndex) {} uint8 gprIndex; uint8 channel; uint8 backupVarIndex; }; void TrackGroupOutputPVPS(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstr, size_t numInstr) { // unset current for (auto& it : m_pvps) it.location = PVPSAlias::LOCATION_TYPE::LOCATION_NONE; for (size_t i = 0; i < numInstr; i++) { LatteDecompilerALUInstruction& inst = aluInstr[i]; if (!inst.isOP3 && inst.opcode == ALU_OP2_INST_NOP) continue; // skip NOP instruction if (inst.writeMask == 0) { // map to temporary m_pvps[inst.aluUnit].SetLocationPSPVTemporary(inst.aluUnit, aluInstr->instructionGroupIndex); } else { // map to GPR if(inst.destRel == 0) // is PV/PS set for indexed writes? m_pvps[inst.aluUnit].SetLocationGPR(inst.destGpr, inst.destElem); } } } bool HasPVPS(uint8 aluUnitIndex) const { cemu_assert_debug(aluUnitIndex < 5); return m_pvps[aluUnitIndex].location != PVPSAlias::LOCATION_TYPE::LOCATION_NONE; } void EmitPVPSAccess(LatteDecompilerShaderContext* shaderContext, uint8 aluUnitIndex, uint32 currentGroupIndex) const { switch (m_pvps[aluUnitIndex].location) { case PVPSAlias::LOCATION_TYPE::LOCATION_GPR: { sint32 temporaryIndex = GetTemporaryForGPR(m_pvps[aluUnitIndex].index, m_pvps[aluUnitIndex].aluUnit); if (temporaryIndex < 0) { shaderContext->shaderSource->add(_getRegisterVarName(shaderContext, m_pvps[aluUnitIndex].index, -1)); _appendChannelAccess(shaderContext->shaderSource, m_pvps[aluUnitIndex].aluUnit); } else { // use temporary instead of GPR shaderContext->shaderSource->addFmt("backupReg{}", temporaryIndex); _appendRegisterTypeSuffix(shaderContext->shaderSource, shaderContext->typeTracker.defaultDataType); } break; } case PVPSAlias::LOCATION_TYPE::LOCATION_PVPS: _appendPVPS(shaderContext, shaderContext->shaderSource, currentGroupIndex-1, m_pvps[aluUnitIndex].aluUnit); break; default: cemuLog_log(LogType::Force, "Shader {:016x} accesses PV/PS without writing to it", shaderContext->shaderBaseHash); cemu_assert_suspicious(); break; } } /* * Check for GPR channels which are modified before they are read within the same group * These registers need to be copied to a temporary */ void CreateGPRTemporaries(LatteDecompilerShaderContext* shaderContext, std::span<LatteDecompilerALUInstruction> aluInstructions) { uint8 registerChannelWriteMask[(LATTE_NUM_GPR * 4 + 7) / 8] = { 0 }; m_gprTemporaries.clear(); for (auto& aluInstruction : aluInstructions) { // ignore NOP instructions if (aluInstruction.isOP3 == false && aluInstruction.opcode == ALU_OP2_INST_NOP) continue; cemu_assert_debug(aluInstruction.destElem <= 3); // check if any previously written register is read for (sint32 f = 0; f < 3; f++) { uint32 readGPRIndex; uint32 readGPRChannel; if (GPU7_ALU_SRC_IS_GPR(aluInstruction.sourceOperand[f].sel)) { readGPRIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction.sourceOperand[f].sel); cemu_assert_debug(aluInstruction.sourceOperand[f].chan <= 3); readGPRChannel = aluInstruction.sourceOperand[f].chan; } else if (GPU7_ALU_SRC_IS_PV(aluInstruction.sourceOperand[f].sel) || GPU7_ALU_SRC_IS_PS(aluInstruction.sourceOperand[f].sel)) { uint8 aluUnitIndex = 0; if (GPU7_ALU_SRC_IS_PV(aluInstruction.sourceOperand[f].sel)) aluUnitIndex = aluInstruction.sourceOperand[f].chan; else aluUnitIndex = 4; // if aliased to a GPR, then consider it a GPR read if(m_pvps[aluUnitIndex].location != PVPSAlias::LOCATION_TYPE::LOCATION_GPR) continue; readGPRIndex = m_pvps[aluUnitIndex].index; readGPRChannel = m_pvps[aluUnitIndex].aluUnit; } else continue; // track GPR read if ((registerChannelWriteMask[(readGPRIndex * 4 + aluInstruction.sourceOperand[f].chan) / 8] & (1 << ((readGPRIndex * 4 + aluInstruction.sourceOperand[f].chan) % 8))) != 0) { // register is overwritten by previous instruction, a temporary variable is required if (GetTemporaryForGPR(readGPRIndex, readGPRChannel) < 0) m_gprTemporaries.emplace_back(readGPRIndex, readGPRChannel, m_gprTemporaries.size()); } } // track write if (aluInstruction.writeMask != 0) registerChannelWriteMask[(aluInstruction.destGpr * 4 + aluInstruction.destElem) / 8] |= (1 << ((aluInstruction.destGpr * 4 + aluInstruction.destElem) % 8)); } // output code to move GPRs into temporaries StringBuf* src = shaderContext->shaderSource; for (auto& it : m_gprTemporaries) { src->addFmt("backupReg{}", it.backupVarIndex); _appendRegisterTypeSuffix(src, shaderContext->typeTracker.defaultDataType); src->add(" = "); src->add(_getRegisterVarName(shaderContext, it.gprIndex)); _appendChannelAccess(src, it.channel); src->add(";" _CRLF); } } // returns -1 if none present sint32 GetTemporaryForGPR(uint8 gprIndex, uint8 channel) const { for (auto& it : m_gprTemporaries) { if (it.gprIndex == gprIndex && it.channel == channel) return (sint32)it.backupVarIndex; } return -1; } private: PVPSAlias m_pvps[5]{}; boost::container::small_vector<GPRTemporary, 4> m_gprTemporaries; }; sint32 _getVertexShaderOutParamSemanticId(uint32* contextRegisters, sint32 index) // deprecated - move to LatteShaderPSInputTable { uint32 vsSemanticId = (contextRegisters[mmSPI_VS_OUT_ID_0 + (index / 4)] >> (8 * (index % 4))) & 0xFF; // check if export exists since exports are generated based on PS inputs LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); for (sint32 i = 0; i < psInputTable->count; i++) { if(psInputTable->import[i].semanticId == vsSemanticId) return vsSemanticId; } return 0xFF; } sint32 _getInputRegisterDataType(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) { return shaderContext->typeTracker.defaultDataType; } sint32 _getALUInstructionOutputDataType(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction) { return shaderContext->typeTracker.defaultDataType; } // returns true if the ALU instruction is a OP2 reduction instruction bool _isReductionInstruction(LatteDecompilerALUInstruction* aluInstruction) { return aluInstruction->isOP3 == false && (aluInstruction->opcode == ALU_OP2_INST_DOT4 || aluInstruction->opcode == ALU_OP2_INST_DOT4_IEEE || aluInstruction->opcode == ALU_OP2_INST_CUBE); } /* * Writes the name of the output variable and channel * E.g. R5f.x or tempf.x if writeMask is 0 */ void _emitInstructionOutputVariableName(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction) { auto src = shaderContext->shaderSource; sint32 outputDataType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); if( aluInstruction->writeMask == 0 ) { // does not output to GPR if( !_isReductionInstruction(aluInstruction) ) { // output to PV/PS _appendPVPS(shaderContext, src, aluInstruction->instructionGroupIndex, aluInstruction->aluUnit); return; } else { // output to temp src->add("temp"); _appendRegisterTypeSuffix(src, outputDataType); } _appendChannelAccess(src, aluInstruction->aluUnit); } else { // output to GPR. Aliasing to PV/PS happens at the end of the group src->add(_getRegisterVarName(shaderContext, aluInstruction->destGpr, aluInstruction->destRel==0?-1:aluInstruction->indexMode)); _appendChannelAccess(src, aluInstruction->destElem); } } void _emitInstructionPVPSOutputVariableName(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction) { _appendPVPS(shaderContext, shaderContext->shaderSource, aluInstruction->instructionGroupIndex, aluInstruction->aluUnit); } void _emitRegisterAccessCode(LatteDecompilerShaderContext* shaderContext, sint32 gprIndex, sint32 channel0, sint32 channel1, sint32 channel2, sint32 channel3, sint32 dataType = -1) { StringBuf* src = shaderContext->shaderSource; sint32 registerElementDataType = shaderContext->typeTracker.defaultDataType; cemu_assert_debug(gprIndex >= 0 && gprIndex <= 127); if (dataType >= 0) { _emitTypeConversionPrefix(shaderContext, registerElementDataType, dataType); } if (shaderContext->typeTracker.useArrayGPRs) src->add("R"); else src->addFmt("R{}", gprIndex); _appendRegisterTypeSuffix(src, registerElementDataType); if (shaderContext->typeTracker.useArrayGPRs) src->addFmt("[{}]", gprIndex); src->add("."); sint32 channelArray[4]; channelArray[0] = channel0; channelArray[1] = channel1; channelArray[2] = channel2; channelArray[3] = channel3; for (sint32 i = 0; i < 4; i++) { if (channelArray[i] >= 0 && channelArray[i] <= 3) src->add(_getElementStrByIndex(channelArray[i])); else if (channelArray[i] == -1) { // channel not used } else { cemu_assert_unimplemented(); } } if (dataType >= 0) _emitTypeConversionSuffix(shaderContext, registerElementDataType, dataType); } // optimized variant of _emitRegisterAccessCode for raw one channel reads void _emitRegisterChannelAccessCode(LatteDecompilerShaderContext* shaderContext, sint32 gprIndex, sint32 channel, sint32 dataType) { cemu_assert_debug(gprIndex >= 0 && gprIndex <= 127); cemu_assert_debug(channel >= 0 && channel < 4); StringBuf* src = shaderContext->shaderSource; sint32 registerElementDataType = shaderContext->typeTracker.defaultDataType; _emitTypeConversionPrefix(shaderContext, registerElementDataType, dataType); if (shaderContext->typeTracker.useArrayGPRs) src->add("R"); else src->addFmt("R{}", gprIndex); _appendRegisterTypeSuffix(src, registerElementDataType); if (shaderContext->typeTracker.useArrayGPRs) src->addFmt("[{}]", gprIndex); src->add("."); src->add(_getElementStrByIndex(channel)); _emitTypeConversionSuffix(shaderContext, registerElementDataType, dataType); } void _emitALURegisterInputAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) { StringBuf* src = shaderContext->shaderSource; sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); cemu_assert_debug(GPU7_ALU_SRC_IS_GPR(aluInstruction->sourceOperand[operandIndex].sel)); sint32 gprIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction->sourceOperand[operandIndex].sel); sint32 temporaryIndex = shaderContext->aluPVPSState->GetTemporaryForGPR(gprIndex, aluInstruction->sourceOperand[operandIndex].chan); if(temporaryIndex >= 0) { // access via backup variable src->addFmt("backupReg{}", temporaryIndex); _appendRegisterTypeSuffix(src, currentRegisterElementType); } else { // access via register variable _emitRegisterAccessCode(shaderContext, gprIndex, aluInstruction->sourceOperand[operandIndex].chan, -1, -1, -1); } } void _emitPVPSAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, uint8 aluUnitIndex) { cemu_assert_debug(aluInstruction->instructionGroupIndex > 0); // PV/PS is uninitialized for group 0 // PV/PS vars are currently always using the default type (shaderContext->typeTracker.defaultDataType) shaderContext->aluPVPSState->EmitPVPSAccess(shaderContext, aluUnitIndex, aluInstruction->instructionGroupIndex); } /* * Emits the expression used for calculating the index for uniform access * For static access, this is a number * For dynamic access, this is AR.* + base */ void _emitUniformAccessIndexCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) { StringBuf* src = shaderContext->shaderSource; bool isUniformRegister = GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel); sint32 uniformOffset = 0; // index into array, for relative accesses this is the base offset if( isUniformRegister ) { uniformOffset = GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction->sourceOperand[operandIndex].sel); } else { if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) ) { uniformOffset = GPU7_ALU_SRC_GET_CBANK0_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank0AddrBase; } else { uniformOffset = GPU7_ALU_SRC_GET_CBANK1_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank1AddrBase; } } if( aluInstruction->sourceOperand[operandIndex].rel != 0 ) { if (aluInstruction->indexMode == GPU7_INDEX_AR_X) src->addFmt("ARi.x+{}", uniformOffset); else if (aluInstruction->indexMode == GPU7_INDEX_AR_Y) src->addFmt("ARi.y+{}", uniformOffset); else if (aluInstruction->indexMode == GPU7_INDEX_AR_Z) src->addFmt("ARi.z+{}", uniformOffset); else if (aluInstruction->indexMode == GPU7_INDEX_AR_W) src->addFmt("ARi.w+{}", uniformOffset); else cemu_assert_unimplemented(); } else { src->addFmt("{}", uniformOffset); } } void _emitUniformAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, sint32 requiredType) { StringBuf* src = shaderContext->shaderSource; if(shaderContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED ) { // uniform registers or buffers are accessed statically with predictable offsets // find entry in remapped uniform if( aluInstruction->sourceOperand[operandIndex].rel != 0 ) debugBreakpoint(); bool isUniformRegister = GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel); sint32 uniformOffset = 0; // index into array sint32 uniformBufferIndex = 0; if( isUniformRegister ) { uniformOffset = GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction->sourceOperand[operandIndex].sel); uniformBufferIndex = 0; } else { if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) ) { uniformOffset = GPU7_ALU_SRC_GET_CBANK0_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank0AddrBase; uniformBufferIndex = aluInstruction->cfInstruction->cBank0Index; } else { uniformOffset = GPU7_ALU_SRC_GET_CBANK1_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank1AddrBase; uniformBufferIndex = aluInstruction->cfInstruction->cBank1Index; } } LatteDecompilerRemappedUniformEntry_t* remappedUniformEntry = NULL; for(size_t i=0; i< shaderContext->shader->list_remappedUniformEntries.size(); i++) { LatteDecompilerRemappedUniformEntry_t* remappedUniformEntryItr = shaderContext->shader->list_remappedUniformEntries.data() + i; if( remappedUniformEntryItr->isRegister && isUniformRegister ) { if( remappedUniformEntryItr->index == uniformOffset ) { remappedUniformEntry = remappedUniformEntryItr; break; } } else { if( remappedUniformEntryItr->kcacheBankId == uniformBufferIndex && remappedUniformEntryItr->index == uniformOffset ) { remappedUniformEntry = remappedUniformEntryItr; break; } } } cemu_assert_debug(remappedUniformEntry); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); if(shaderContext->shader->shaderType == LatteConst::ShaderType::Vertex ) src->addFmt("uf_remappedVS[{}]", remappedUniformEntry->mappedIndex); else if(shaderContext->shader->shaderType == LatteConst::ShaderType::Pixel ) src->addFmt("uf_remappedPS[{}]", remappedUniformEntry->mappedIndex); else if(shaderContext->shader->shaderType == LatteConst::ShaderType::Geometry ) src->addFmt("uf_remappedGS[{}]", remappedUniformEntry->mappedIndex); else debugBreakpoint(); _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); } else if( shaderContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE ) { // uniform registers are accessed with unpredictable (dynamic) offset _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); if(shaderContext->shader->shaderType == LatteConst::ShaderType::Vertex ) src->add("uf_uniformRegisterVS["); else if (shaderContext->shader->shaderType == LatteConst::ShaderType::Pixel) src->add("uf_uniformRegisterPS["); else if(shaderContext->shader->shaderType == LatteConst::ShaderType::Geometry ) src->add("uf_uniformRegisterGS["); else debugBreakpoint(); _emitUniformAccessIndexCode(shaderContext, aluInstruction, operandIndex); src->add("]"); _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); } else if( shaderContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK ) { // uniform buffers are available as a whole bool isUniformRegister = GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel); if( isUniformRegister ) debugBreakpoint(); sint32 uniformBufferIndex = 0; if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) ) { uniformBufferIndex = aluInstruction->cfInstruction->cBank0Index; } else { uniformBufferIndex = aluInstruction->cfInstruction->cBank1Index; } _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); src->addFmt("{}{}[", _getShaderUniformBlockVariableName(shaderContext->shader->shaderType), uniformBufferIndex); _emitUniformAccessIndexCode(shaderContext, aluInstruction, operandIndex); src->addFmt("]"); _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); } else debugBreakpoint(); } // Generates (slow) code to read an indexed GPR void _emitCodeToReadRelativeGPR(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, sint32 requiredType) { StringBuf* src = shaderContext->shaderSource; uint32 gprBaseIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction->sourceOperand[operandIndex].sel); cemu_assert_debug(aluInstruction->sourceOperand[operandIndex].rel != 0); if( shaderContext->typeTracker.useArrayGPRs ) { _emitTypeConversionPrefix(shaderContext, shaderContext->typeTracker.defaultDataType, requiredType); src->add(_getRegisterVarName(shaderContext, gprBaseIndex, aluInstruction->indexMode)); _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); _emitTypeConversionSuffix(shaderContext, shaderContext->typeTracker.defaultDataType, requiredType); return; } char indexAccessCode[64]; if (aluInstruction->indexMode == GPU7_INDEX_AR_X) sprintf(indexAccessCode, "ARi.x"); else if (aluInstruction->indexMode == GPU7_INDEX_AR_Y) sprintf(indexAccessCode, "ARi.y"); else if (aluInstruction->indexMode == GPU7_INDEX_AR_Z) sprintf(indexAccessCode, "ARi.z"); else if (aluInstruction->indexMode == GPU7_INDEX_AR_W) sprintf(indexAccessCode, "ARi.w"); else cemu_assert_unimplemented(); if( LATTE_DECOMPILER_DTYPE_SIGNED_INT != requiredType ) _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); // generated code looks like this: // result = ((lookupIndex==0)?GPR5:(lookupIndex==1)?GPR6:(lookupIndex==2)?GPR7:...:(lookupIndex==122)?GPR127:0) src->add("("); for(sint32 i=gprBaseIndex; i<LATTE_NUM_GPR; i++) { // only emit access code for registers which are potentially written if((shaderContext->analyzer.gprUseMask[i / 8] & (1 << (i % 8))) == 0 ) continue; src->addFmt("({}=={})?", indexAccessCode, i-gprBaseIndex); // code to access gpr uint32 gprIndex = i; src->add(_getRegisterVarName(shaderContext, i)); _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); src->add(":"); } src->add("0)"); if( LATTE_DECOMPILER_DTYPE_SIGNED_INT != requiredType ) _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); } void _emitOperandInputCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, sint32 requiredType) { StringBuf* src = shaderContext->shaderSource; if( operandIndex < 0 || operandIndex >= 3 ) debugBreakpoint(); sint32 requiredTypeOut = requiredType; if( requiredType != LATTE_DECOMPILER_DTYPE_FLOAT && (aluInstruction->sourceOperand[operandIndex].abs != 0 || aluInstruction->sourceOperand[operandIndex].neg != 0) ) { // we need to apply float operations on the input but it's not read as a float // force internal required type to float and then cast it back to whatever type is actually required requiredType = LATTE_DECOMPILER_DTYPE_FLOAT; } if( requiredTypeOut != requiredType ) _emitTypeConversionPrefix(shaderContext, requiredType, requiredTypeOut); if( aluInstruction->sourceOperand[operandIndex].neg != 0 ) src->add("-("); if( aluInstruction->sourceOperand[operandIndex].abs != 0 ) src->add("abs("); if( GPU7_ALU_SRC_IS_GPR(aluInstruction->sourceOperand[operandIndex].sel) ) { if( aluInstruction->sourceOperand[operandIndex].rel != 0 ) { _emitCodeToReadRelativeGPR(shaderContext, aluInstruction, operandIndex, requiredType); } else { uint32 gprIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction->sourceOperand[operandIndex].sel); if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { // signed int 32bit sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); // write code for register input _emitTypeConversionPrefix(shaderContext, currentRegisterElementType, requiredType); _emitALURegisterInputAccessCode(shaderContext, aluInstruction, operandIndex); _emitTypeConversionSuffix(shaderContext, currentRegisterElementType, requiredType); } else if( requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) { // unsigned int 32bit sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { // need to convert from int to uint src->add("uint("); } else if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) { // no extra work necessary } else debugBreakpoint(); // write code for register input _emitALURegisterInputAccessCode(shaderContext, aluInstruction, operandIndex); if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { src->add(")"); } } else if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) { // float 32bit sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { // need to convert (not cast) from int bits to float src->add("intBitsToFloat("); } else if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_FLOAT ) { // no extra work necessary } else debugBreakpoint(); // write code for register input _emitALURegisterInputAccessCode(shaderContext, aluInstruction, operandIndex); if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { src->add(")"); } } else debugBreakpoint(); } } else if( GPU7_ALU_SRC_IS_CONST_0F(aluInstruction->sourceOperand[operandIndex].sel) ) { if(requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT || requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) src->add("0"); else if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) src->add("0.0"); } else if( GPU7_ALU_SRC_IS_CONST_1F(aluInstruction->sourceOperand[operandIndex].sel) ) { _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); src->add("1.0"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); } else if( GPU7_ALU_SRC_IS_CONST_0_5F(aluInstruction->sourceOperand[operandIndex].sel) ) { _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); src->add("0.5"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); } else if( GPU7_ALU_SRC_IS_CONST_1I(aluInstruction->sourceOperand[operandIndex].sel) ) { if (requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->add("int(1)"); else if (requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) src->add("uint(1)"); else cemu_assert_suspicious(); } else if( GPU7_ALU_SRC_IS_CONST_M1I(aluInstruction->sourceOperand[operandIndex].sel) ) { if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) src->add("int(-1)"); else cemu_assert_suspicious(); } else if( GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[operandIndex].sel) ) { if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) src->addFmt("0x{:x}", aluInstruction->literalData.w[aluInstruction->sourceOperand[operandIndex].chan]); else if( requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) src->addFmt("uint(0x{:x})", aluInstruction->literalData.w[aluInstruction->sourceOperand[operandIndex].chan]); else if (requiredType == LATTE_DECOMPILER_DTYPE_FLOAT) { uint32 constVal = aluInstruction->literalData.w[aluInstruction->sourceOperand[operandIndex].chan]; sint32 exponent = (constVal >> 23) & 0xFF; exponent -= 127; if ((constVal & 0xFF) == 0 && exponent >= -10 && exponent <= 10) { src->add(_FormatFloatAsGLSLConstant(*(float*)&constVal)); } else src->addFmt("intBitsToFloat(0x{:08x})", constVal); } } else if( GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel) ) { _emitUniformAccessCode(shaderContext, aluInstruction, operandIndex, requiredType); } else if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) || GPU7_ALU_SRC_IS_CBANK1(aluInstruction->sourceOperand[operandIndex].sel) ) { _emitUniformAccessCode(shaderContext, aluInstruction, operandIndex, requiredType); } else if( GPU7_ALU_SRC_IS_PV(aluInstruction->sourceOperand[operandIndex].sel) ) { sint32 currentPVDataType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); _emitTypeConversionPrefix(shaderContext, currentPVDataType, requiredType); _emitPVPSAccessCode(shaderContext, aluInstruction, operandIndex, aluInstruction->sourceOperand[operandIndex].chan); _emitTypeConversionSuffix(shaderContext, currentPVDataType, requiredType); } else if( GPU7_ALU_SRC_IS_PS(aluInstruction->sourceOperand[operandIndex].sel) ) { sint32 currentPSDataType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); _emitTypeConversionPrefix(shaderContext, currentPSDataType, requiredType); _emitPVPSAccessCode(shaderContext, aluInstruction, operandIndex, 4); _emitTypeConversionSuffix(shaderContext, currentPSDataType, requiredType); } else { cemuLog_log(LogType::Force, "Unsupported shader ALU operand sel {:#x}\n", aluInstruction->sourceOperand[operandIndex].sel); debugBreakpoint(); } if( aluInstruction->sourceOperand[operandIndex].abs != 0 ) src->add(")"); if( aluInstruction->sourceOperand[operandIndex].neg != 0 ) src->add(")"); if( requiredTypeOut != requiredType ) _emitTypeConversionSuffix(shaderContext, requiredType, requiredTypeOut); } void _emitTypeConversionPrefix(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType) { if( sourceType == destinationType ) return; StringBuf* src = shaderContext->shaderSource; if (sourceType == LATTE_DECOMPILER_DTYPE_FLOAT && destinationType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->add("floatBitsToInt("); else if (sourceType == LATTE_DECOMPILER_DTYPE_FLOAT && destinationType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) src->add("floatBitsToUint("); else if( sourceType == LATTE_DECOMPILER_DTYPE_SIGNED_INT && destinationType == LATTE_DECOMPILER_DTYPE_FLOAT ) src->add("intBitsToFloat("); else if( sourceType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT && destinationType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) src->add("int("); else if( sourceType == LATTE_DECOMPILER_DTYPE_SIGNED_INT && destinationType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) src->add("uint("); else cemu_assert_debug(false); } void _emitTypeConversionSuffix(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType) { if( sourceType == destinationType ) return; StringBuf* src = shaderContext->shaderSource; src->add(")"); } template<int TDataType> void _emitALUOperationBinary(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, const char* operandStr) { StringBuf* src = shaderContext->shaderSource; sint32 outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, TDataType, outputType); _emitOperandInputCode(shaderContext, aluInstruction, 0, TDataType); src->add((char*)operandStr); _emitOperandInputCode(shaderContext, aluInstruction, 1, TDataType); _emitTypeConversionSuffix(shaderContext, TDataType, outputType); src->add(";" _CRLF); } static bool _isSameGPROperand(LatteDecompilerALUInstruction* aluInstruction, sint32 opIndexA, sint32 opIndexB) { if (aluInstruction->sourceOperand[opIndexA].sel != aluInstruction->sourceOperand[opIndexB].sel) return false; if (!GPU7_ALU_SRC_IS_GPR(aluInstruction->sourceOperand[opIndexA].sel)) return false; if (aluInstruction->sourceOperand[opIndexA].chan != aluInstruction->sourceOperand[opIndexB].chan) return false; if (aluInstruction->sourceOperand[opIndexA].abs != aluInstruction->sourceOperand[opIndexB].abs) return false; if (aluInstruction->sourceOperand[opIndexA].neg != aluInstruction->sourceOperand[opIndexB].neg) return false; if (aluInstruction->sourceOperand[opIndexA].rel != aluInstruction->sourceOperand[opIndexB].rel) return false; return true; } static bool _operandHasModifiers(LatteDecompilerALUInstruction* aluInstruction, sint32 opIndex) { return aluInstruction->sourceOperand[opIndex].abs != 0 || aluInstruction->sourceOperand[opIndex].neg != 0; } void _emitALUOP2InstructionCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, LatteDecompilerALUInstruction* aluInstruction) { StringBuf* src = shaderContext->shaderSource; sint32 outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); // data type of output if( aluInstruction->opcode == ALU_OP2_INST_MOV ) { bool requiresFloatMove = false; requiresFloatMove = aluInstruction->sourceOperand[0].abs != 0 || aluInstruction->sourceOperand[0].neg != 0; if( requiresFloatMove ) { // abs/neg operations are applied to source operand, do float based move _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitOperandInputCode(shaderContext, aluInstruction, 0, outputType); src->add(";" _CRLF); } } else if( aluInstruction->opcode == ALU_OP2_INST_MOVA_FLOOR ) { cemu_assert_debug(aluInstruction->writeMask == 0); cemu_assert_debug(aluInstruction->omod == 0); src->add("tempResultf = "); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(";" _CRLF); src->add("tempResultf = floor(tempResultf);" _CRLF); src->add("tempResultf = clamp(tempResultf, -256.0, 255.0);" _CRLF); // set AR if( aluInstruction->destElem == 0 ) src->add("ARi.x = int(tempResultf);" _CRLF); else if( aluInstruction->destElem == 1 ) src->add("ARi.y = int(tempResultf);" _CRLF); else if( aluInstruction->destElem == 2 ) src->add("ARi.z = int(tempResultf);" _CRLF); else src->add("ARi.w = int(tempResultf);" _CRLF); // set output _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); if( outputType != LATTE_DECOMPILER_DTYPE_SIGNED_INT ) debugBreakpoint(); // todo src->add("floatBitsToInt(tempResultf)"); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_MOVA_INT ) { cemu_assert_debug(aluInstruction->writeMask == 0); cemu_assert_debug(aluInstruction->omod == 0); src->add("tempResulti = "); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(";" _CRLF); src->add("tempResulti = clamp(tempResulti, -256, 255);" _CRLF); // set AR if( aluInstruction->destElem == 0 ) src->add("ARi.x = tempResulti;" _CRLF); else if( aluInstruction->destElem == 1 ) src->add("ARi.y = tempResulti;" _CRLF); else if( aluInstruction->destElem == 2 ) src->add("ARi.z = tempResulti;" _CRLF); else src->add("ARi.w = tempResulti;" _CRLF); // set output _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); if( outputType != LATTE_DECOMPILER_DTYPE_SIGNED_INT ) debugBreakpoint(); // todo src->add("tempResulti"); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_ADD ) { _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_FLOAT>(shaderContext, aluInstruction, " + "); } else if( aluInstruction->opcode == ALU_OP2_INST_MUL ) { // 0*anything is always 0 _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); // if any operand is a non-zero literal or constant we can use standard multiplication bool useDefaultMul = false; if (GPU7_ALU_SRC_IS_CONST_0F(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_CONST_0F(aluInstruction->sourceOperand[1].sel)) { // result is always zero src->add("0.0"); } else { // multiply if (GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[1].sel) || GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[1].sel)) { useDefaultMul = true; } if (shaderContext->options->strictMul && useDefaultMul == false) { src->add("mul_nonIEEE("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(", "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); } else { _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(" * "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); } } _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_MUL_IEEE ) { // 0*anything according to IEEE rules _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_FLOAT>(shaderContext, aluInstruction, " * "); } else if (aluInstruction->opcode == ALU_OP2_INST_RECIP_IEEE) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("1.0"); src->add(" / "); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if (aluInstruction->opcode == ALU_OP2_INST_RECIP_FF) { // untested (BotW bombs) src->add("tempResultf = 1.0 / ("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(");" _CRLF); // INF becomes 0.0 src->add("if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) == 0 ) tempResultf = 0.0;" _CRLF); // -INF becomes -0.0 src->add("else if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) != 0 ) tempResultf = -0.0;" _CRLF); // assign result to output _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("tempResultf"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_IEEE || aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_CLAMPED || aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_FF ) { // todo: This should be correct but testing is needed src->add("tempResultf = 1.0 / sqrt("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(");" _CRLF); if (aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_CLAMPED) { // note: if( -INF < 0.0 ) does not resolve to true src->add("if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) != 0 ) tempResultf = -3.40282347E+38F;" _CRLF); src->add("else if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) == 0 ) tempResultf = 3.40282347E+38F;" _CRLF); } else if (aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_FF) { // untested (BotW bombs) src->add("if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) != 0 ) tempResultf = -0.0;" _CRLF); src->add("else if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) == 0 ) tempResultf = 0.0;" _CRLF); } // assign result to output _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("tempResultf"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_MAX || aluInstruction->opcode == ALU_OP2_INST_MIN || aluInstruction->opcode == ALU_OP2_INST_MAX_DX10 || aluInstruction->opcode == ALU_OP2_INST_MIN_DX10 ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); if( aluInstruction->opcode == ALU_OP2_INST_MAX ) src->add("max"); else if( aluInstruction->opcode == ALU_OP2_INST_MIN ) src->add("min"); else if (aluInstruction->opcode == ALU_OP2_INST_MAX_DX10) src->add("max"); else if (aluInstruction->opcode == ALU_OP2_INST_MIN_DX10) src->add("min"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(", "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_FLOOR || aluInstruction->opcode == ALU_OP2_INST_FRACT || aluInstruction->opcode == ALU_OP2_INST_TRUNC ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); if( aluInstruction->opcode == ALU_OP2_INST_FLOOR ) src->add("floor"); else if( aluInstruction->opcode == ALU_OP2_INST_FRACT ) src->add("fract"); else src->add("trunc"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_LOG_CLAMPED || aluInstruction->opcode == ALU_OP2_INST_LOG_IEEE ) { src->add("tempResultf = max(0.0, "); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(");" _CRLF); src->add("tempResultf = log2(tempResultf);" _CRLF); if( aluInstruction->opcode == ALU_OP2_INST_LOG_CLAMPED ) { src->add("if( isinf(tempResultf) == true ) tempResultf = -3.40282347E+38F;" _CRLF); } // assign result to output _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("tempResultf"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_RNDNE ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("roundEven"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_EXP_IEEE ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("exp2"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SQRT_IEEE ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("sqrt"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SIN || aluInstruction->opcode == ALU_OP2_INST_COS ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); if( aluInstruction->opcode == ALU_OP2_INST_SIN ) src->add("sin"); else src->add("cos"); src->add("(("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")/0.1591549367)"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_FLT_TO_INT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("int"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_FLT_TO_UINT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT, outputType); src->add("uint"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_INT_TO_FLOAT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("float("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_UINT_TO_FLOAT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("float("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if (aluInstruction->opcode == ALU_OP2_INST_AND_INT) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " & "); else if (aluInstruction->opcode == ALU_OP2_INST_OR_INT) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " | "); else if (aluInstruction->opcode == ALU_OP2_INST_XOR_INT) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " ^ "); else if( aluInstruction->opcode == ALU_OP2_INST_NOT_INT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("~("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_ADD_INT ) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " + "); else if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MIN_INT || aluInstruction->opcode == ALU_OP2_INST_MAX_UINT || aluInstruction->opcode == ALU_OP2_INST_MIN_UINT) { // not verified bool isUnsigned = aluInstruction->opcode == ALU_OP2_INST_MAX_UINT || aluInstruction->opcode == ALU_OP2_INST_MIN_UINT; auto opType = isUnsigned ? LATTE_DECOMPILER_DTYPE_UNSIGNED_INT : LATTE_DECOMPILER_DTYPE_SIGNED_INT; _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, opType, outputType); if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MAX_UINT ) src->add("max("); else src->add("min("); _emitOperandInputCode(shaderContext, aluInstruction, 0, opType); src->add(", "); _emitOperandInputCode(shaderContext, aluInstruction, 1, opType); _emitTypeConversionSuffix(shaderContext, opType, outputType); src->add(");" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SUB_INT ) { // note: The AMD doc says src1 is on the left side but tests indicate otherwise. It's src0 - src1. _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " - "); } else if (aluInstruction->opcode == ALU_OP2_INST_MULLO_INT) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " * "); else if (aluInstruction->opcode == ALU_OP2_INST_MULLO_UINT) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_UNSIGNED_INT>(shaderContext, aluInstruction, " * "); else if( aluInstruction->opcode == ALU_OP2_INST_LSHL_INT ) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " << "); else if( aluInstruction->opcode == ALU_OP2_INST_LSHR_INT ) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " >> "); else if( aluInstruction->opcode == ALU_OP2_INST_ASHR_INT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(" >> "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SETGT || aluInstruction->opcode == ALU_OP2_INST_SETGE || aluInstruction->opcode == ALU_OP2_INST_SETNE || aluInstruction->opcode == ALU_OP2_INST_SETE ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); if( aluInstruction->opcode == ALU_OP2_INST_SETGT ) src->add(" > "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGE ) src->add(" >= "); else if (aluInstruction->opcode == ALU_OP2_INST_SETNE) src->add(" != "); else if (aluInstruction->opcode == ALU_OP2_INST_SETE) src->add(" == "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")?1.0:0.0"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_DX10 || aluInstruction->opcode == ALU_OP2_INST_SETE_DX10 || aluInstruction->opcode == ALU_OP2_INST_SETNE_DX10 || aluInstruction->opcode == ALU_OP2_INST_SETGE_DX10 ) { if( aluInstruction->omod != 0 ) debugBreakpoint(); _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("(("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); if( aluInstruction->opcode == ALU_OP2_INST_SETE_DX10 ) src->add(" == "); else if( aluInstruction->opcode == ALU_OP2_INST_SETNE_DX10 ) src->add(" != "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_DX10 ) src->add(" > "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGE_DX10 ) src->add(" >= "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")?-1:0)"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";"); src->add(_CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SETE_INT || aluInstruction->opcode == ALU_OP2_INST_SETNE_INT || aluInstruction->opcode == ALU_OP2_INST_SETGT_INT || aluInstruction->opcode == ALU_OP2_INST_SETGE_INT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); if( aluInstruction->opcode == ALU_OP2_INST_SETE_INT ) src->add(" == "); else if( aluInstruction->opcode == ALU_OP2_INST_SETNE_INT ) src->add(" != "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_INT ) src->add(" > "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGE_INT ) src->add(" >= "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")?-1:0"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SETGE_UINT || aluInstruction->opcode == ALU_OP2_INST_SETGT_UINT ) { // todo: Unsure if the result is unsigned or signed _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT); if( aluInstruction->opcode == ALU_OP2_INST_SETGE_UINT ) src->add(" >= "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_UINT ) src->add(" > "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT); src->add(")?int(0xFFFFFFFF):int(0x0)"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE_INT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETE_INT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE_INT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT_INT ) { cemu_assert_debug(aluInstruction->writeMask == 0); bool isIntPred = (aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE_INT) || (aluInstruction->opcode == ALU_OP2_INST_PRED_SETE_INT) || (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE_INT) || (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT_INT); src->add("predResult"); src->add(" = ("); _emitOperandInputCode(shaderContext, aluInstruction, 0, isIntPred?LATTE_DECOMPILER_DTYPE_SIGNED_INT:LATTE_DECOMPILER_DTYPE_FLOAT); if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETE_INT) src->add(" == "); else if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT_INT) src->add(" > "); else if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE_INT) src->add(" >= "); else if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE_INT) src->add(" != "); else cemu_assert_debug(false); _emitOperandInputCode(shaderContext, aluInstruction, 1, isIntPred?LATTE_DECOMPILER_DTYPE_SIGNED_INT:LATTE_DECOMPILER_DTYPE_FLOAT); src->add(");" _CRLF); // handle result of predicate instruction based on current ALU clause type if( cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE ) { src->addFmt("{} = predResult;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); src->addFmt("{} = predResult == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); } else if( cfInstruction->type == GPU7_CF_INST_ALU_BREAK ) { // leave current loop src->add("if( predResult == false ) break;" _CRLF); } else cemu_assert_debug(false); } else if( aluInstruction->opcode == ALU_OP2_INST_KILLE_INT || aluInstruction->opcode == ALU_OP2_INST_KILLNE_INT || aluInstruction->opcode == ALU_OP2_INST_KILLGT_INT) { src->add("if( "); src->add(" ("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); if( aluInstruction->opcode == ALU_OP2_INST_KILLE_INT ) src->add(" == "); else if (aluInstruction->opcode == ALU_OP2_INST_KILLNE_INT) src->add(" != "); else if (aluInstruction->opcode == ALU_OP2_INST_KILLGT_INT) src->add(" > "); else debugBreakpoint(); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")"); src->add(") discard;"); src->add(_CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_KILLGT || aluInstruction->opcode == ALU_OP2_INST_KILLGE || aluInstruction->opcode == ALU_OP2_INST_KILLE ) { src->add("if( "); src->add(" ("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); if( aluInstruction->opcode == ALU_OP2_INST_KILLGT ) src->add(" > "); else if( aluInstruction->opcode == ALU_OP2_INST_KILLGE ) src->add(" >= "); else if( aluInstruction->opcode == ALU_OP2_INST_KILLE ) src->add(" == "); else debugBreakpoint(); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); src->add(") discard;"); src->add(_CRLF); } else { src->add("Unsupported instruction;" _CRLF); debug_printf("Unsupported ALU op2 instruction 0x%x\n", aluInstruction->opcode); shaderContext->shader->hasError = true; } } void _emitALUOP3InstructionCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, LatteDecompilerALUInstruction* aluInstruction) { StringBuf* src = shaderContext->shaderSource; cemu_assert_debug(aluInstruction->destRel == 0); // todo sint32 outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); /* check for common no-op or mov-like instructions */ if (aluInstruction->opcode == ALU_OP3_INST_CMOVGE || aluInstruction->opcode == ALU_OP3_INST_CMOVE || aluInstruction->opcode == ALU_OP3_INST_CMOVGT || aluInstruction->opcode == ALU_OP3_INST_CNDE_INT || aluInstruction->opcode == ALU_OP3_INST_CNDGT_INT || aluInstruction->opcode == ALU_OP3_INST_CMOVGE_INT) { if (_isSameGPROperand(aluInstruction, 1, 2) && !_operandHasModifiers(aluInstruction, 1)) { // the condition is irrelevant as both operands are the same _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitOperandInputCode(shaderContext, aluInstruction, 1, outputType); src->add(";" _CRLF); return; } } /* generic handlers */ if( aluInstruction->opcode == ALU_OP3_INST_MULADD || aluInstruction->opcode == ALU_OP3_INST_MULADD_D2 || aluInstruction->opcode == ALU_OP3_INST_MULADD_M2 || aluInstruction->opcode == ALU_OP3_INST_MULADD_M4 || aluInstruction->opcode == ALU_OP3_INST_MULADD_IEEE ) { // todo: The difference between MULADD and MULADD IEEE is that the former has 0*anything=0 rule similar to MUL/MUL_IEEE? _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); if (aluInstruction->opcode != ALU_OP3_INST_MULADD) // avoid unnecessary parenthesis to improve code readability slightly src->add("("); bool useDefaultMul = false; if (GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[1].sel) || GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[1].sel)) { useDefaultMul = true; } if (aluInstruction->opcode == ALU_OP3_INST_MULADD_IEEE) useDefaultMul = true; if (shaderContext->options->strictMul && useDefaultMul == false) { src->add("mul_nonIEEE("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); } else { _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(" * "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); } src->add(" + "); _emitOperandInputCode(shaderContext, aluInstruction, 2, LATTE_DECOMPILER_DTYPE_FLOAT); if(aluInstruction->opcode != ALU_OP3_INST_MULADD) src->add(")"); if( aluInstruction->opcode == ALU_OP3_INST_MULADD_D2 ) src->add("/2.0"); else if( aluInstruction->opcode == ALU_OP3_INST_MULADD_M2 ) src->add("*2.0"); else if( aluInstruction->opcode == ALU_OP3_INST_MULADD_M4 ) src->add("*4.0"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if(aluInstruction->opcode == ALU_OP3_INST_CNDE_INT || aluInstruction->opcode == ALU_OP3_INST_CNDGT_INT || aluInstruction->opcode == ALU_OP3_INST_CMOVGE_INT) { bool requiresFloatResult = (aluInstruction->sourceOperand[1].neg != 0) || (aluInstruction->sourceOperand[2].neg != 0); _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("(("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); if (aluInstruction->opcode == ALU_OP3_INST_CNDE_INT) src->add(" == "); else if (aluInstruction->opcode == ALU_OP3_INST_CNDGT_INT) src->add(" > "); else if (aluInstruction->opcode == ALU_OP3_INST_CMOVGE_INT) src->add(" >= "); src->add("0)?("); _emitOperandInputCode(shaderContext, aluInstruction, 1, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add("):("); _emitOperandInputCode(shaderContext, aluInstruction, 2, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add("))"); _emitTypeConversionSuffix(shaderContext, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP3_INST_CMOVGE || aluInstruction->opcode == ALU_OP3_INST_CMOVE || aluInstruction->opcode == ALU_OP3_INST_CMOVGT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("(("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); if (aluInstruction->opcode == ALU_OP3_INST_CMOVE) src->add(" == "); else if (aluInstruction->opcode == ALU_OP3_INST_CMOVGE) src->add(" >= "); else if (aluInstruction->opcode == ALU_OP3_INST_CMOVGT) src->add(" > "); src->add("0.0)?("); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add("):("); _emitOperandInputCode(shaderContext, aluInstruction, 2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add("))"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else { src->add("Unsupported instruction;" _CRLF); debug_printf("Unsupported ALU op3 instruction 0x%x\n", aluInstruction->opcode); shaderContext->shader->hasError = true; } } void _emitALUReductionInstructionCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluRedcInstruction[4]) { StringBuf* src = shaderContext->shaderSource; if( aluRedcInstruction[0]->isOP3 == false && (aluRedcInstruction[0]->opcode == ALU_OP2_INST_DOT4 || aluRedcInstruction[0]->opcode == ALU_OP2_INST_DOT4_IEEE) ) { // todo: Figure out and implement the difference between normal DOT4 and DOT4_IEEE sint32 outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[0]); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); // dot(vec4(op0),vec4(op1)) src->add("dot(vec4("); _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("),vec4("); _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("))"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluRedcInstruction[0]->isOP3 == false && (aluRedcInstruction[0]->opcode == ALU_OP2_INST_CUBE) ) { /* * How the CUBE instruction works (guessed mostly, based on DirectX/OpenGL spec): Input: vec4, 3d direction vector (can be unnormalized) + w component (which can be ignored, since it only scales the vector but does not affect the direction) First we figure out the major axis (closest axis-aligned vector). There are six possible vectors: +rx 0 -rx 1 +ry 2 -ry 3 +rz 4 -rz 5 The major axis vector is calculated by looking at the largest (absolute) 3d vector component and then setting the other components to 0.0 The value that remains in the axis vector is referred to as 'MajorAxis' by the AMD documentation. The S,T coordinates are taken from the other two components. Example: -0.5,0.2,0.4 -> -rx -> -0.5,0.0,0.0 MajorAxis: -0.5, S: 0.2 T: 0.4 The CUBE reduction instruction requires a specific mapping for the input vector: src0 = Rn.zzxy src1 = Rn.yxzz It's probably related to the way the instruction works internally? If we look at the individual components per ALU unit: z y -> Compare y/z z x -> Compare x/z x z -> Compare x/z y z -> Compare y/z */ sint32 outputType; src->add("redcCUBE("); src->add("vec4("); _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("),"); src->add("vec4("); _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("),"); src->add("cubeMapSTM,cubeMapFaceId);" _CRLF); // dst.X (S) outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[0]); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("cubeMapSTM.x"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); // dst.Y (T) outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[1]); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[1]); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("cubeMapSTM.y"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); // dst.Z (MajorAxis) outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[2]); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[2]); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("cubeMapSTM.z"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); // dst.W (FaceId) outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[3]); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[3]); src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("cubeMapFaceId"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else cemu_assert_unimplemented(); } void _emitALUClauseRegisterBackupCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, sint32 startIndex) { sint32 instructionGroupIndex = cfInstruction->instructionsALU[startIndex].instructionGroupIndex; size_t groupSize = 1; while ((startIndex + groupSize) < cfInstruction->instructionsALU.size()) { if (instructionGroupIndex != cfInstruction->instructionsALU[startIndex + groupSize].instructionGroupIndex) break; groupSize++; } shaderContext->aluPVPSState->CreateGPRTemporaries(shaderContext, { cfInstruction->instructionsALU.data() + startIndex, groupSize }); } /* bool _isPVUsedInNextGroup(LatteDecompilerCFInstruction* cfInstruction, sint32 startIndex, sint32 pvUnit) { sint32 currentGroupIndex = cfInstruction->instructionsALU[startIndex].instructionGroupIndex; for (sint32 i = startIndex + 1; i < (sint32)cfInstruction->instructionsALU.size(); i++) { LatteDecompilerALUInstruction& aluInstructionItr = cfInstruction->instructionsALU[i]; if(aluInstructionItr.instructionGroupIndex == currentGroupIndex ) continue; if ((sint32)aluInstructionItr.instructionGroupIndex > currentGroupIndex + 1) return false; // check OP code type if (aluInstructionItr.isOP3) { // op0 if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[0].sel)) { uint32 chan = aluInstructionItr.sourceOperand[0].chan; if (pvUnit == chan) return true; } // op1 if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[1].sel)) { uint32 chan = aluInstructionItr.sourceOperand[1].chan; if (pvUnit == chan) return true; } // op2 if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[2].sel)) { uint32 chan = aluInstructionItr.sourceOperand[2].chan; if (pvUnit == chan) return true; } } else { // op0 if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[0].sel)) { uint32 chan = aluInstructionItr.sourceOperand[0].chan; if (pvUnit == chan) return true; } // op1 if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[1].sel)) { uint32 chan = aluInstructionItr.sourceOperand[1].chan; if (pvUnit == chan) return true; } // todo: Not all operations use both operands } } return false; } */ void _emitVec3(LatteDecompilerShaderContext* shaderContext, uint32 dataType, LatteDecompilerALUInstruction* aluInst0, sint32 opIdx0, LatteDecompilerALUInstruction* aluInst1, sint32 opIdx1, LatteDecompilerALUInstruction* aluInst2, sint32 opIdx2) { StringBuf* src = shaderContext->shaderSource; if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) { src->add("vec3("); _emitOperandInputCode(shaderContext, aluInst0, opIdx0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluInst1, opIdx1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluInst2, opIdx2, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); } else if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { src->add("ivec3("); _emitOperandInputCode(shaderContext, aluInst0, opIdx0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(","); _emitOperandInputCode(shaderContext, aluInst1, opIdx1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(","); _emitOperandInputCode(shaderContext, aluInst2, opIdx2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")"); } else cemu_assert_unimplemented(); } void _emitGPRVectorAssignment(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction** aluInstructions, sint32 count) { StringBuf* src = shaderContext->shaderSource; // output var name (GPR) src->add(_getRegisterVarName(shaderContext, aluInstructions[0]->destGpr, -1)); src->add("."); for (sint32 f = 0; f < count; f++) { src->add(_getElementStrByIndex(aluInstructions[f]->destElem)); } src->add(" = "); } void _emitALUClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { ALUClauseTemporariesState pvpsState; shaderContext->aluPVPSState = &pvpsState; StringBuf* src = shaderContext->shaderSource; LatteDecompilerALUInstruction* aluRedcInstruction[4]; size_t groupStartIndex = 0; for(size_t i=0; i<cfInstruction->instructionsALU.size(); i++) { LatteDecompilerALUInstruction& aluInstruction = cfInstruction->instructionsALU[i]; if( aluInstruction.indexInGroup == 0 ) { src->addFmt("// {}" _CRLF, aluInstruction.instructionGroupIndex); // apply PV/PS updates for previous group if (i > 0) { pvpsState.TrackGroupOutputPVPS(shaderContext, cfInstruction->instructionsALU.data() + groupStartIndex, i - groupStartIndex); } groupStartIndex = i; // backup registers which are read after being written _emitALUClauseRegisterBackupCode(shaderContext, cfInstruction, i); } // detect reduction instructions and use a special handler bool isReductionOperation = _isReductionInstruction(&aluInstruction); if( isReductionOperation ) { cemu_assert_debug((i + 4) <= cfInstruction->instructionsALU.size()); aluRedcInstruction[0] = &aluInstruction; aluRedcInstruction[1] = &cfInstruction->instructionsALU[i + 1]; aluRedcInstruction[2] = &cfInstruction->instructionsALU[i + 2]; aluRedcInstruction[3] = &cfInstruction->instructionsALU[i + 3]; if( aluRedcInstruction[0]->isOP3 != aluRedcInstruction[1]->isOP3 || aluRedcInstruction[1]->isOP3 != aluRedcInstruction[2]->isOP3 || aluRedcInstruction[2]->isOP3 != aluRedcInstruction[3]->isOP3 ) debugBreakpoint(); if( aluRedcInstruction[0]->opcode != aluRedcInstruction[1]->opcode || aluRedcInstruction[1]->opcode != aluRedcInstruction[2]->opcode || aluRedcInstruction[2]->opcode != aluRedcInstruction[3]->opcode ) debugBreakpoint(); if( aluRedcInstruction[0]->omod != aluRedcInstruction[1]->omod || aluRedcInstruction[1]->omod != aluRedcInstruction[2]->omod || aluRedcInstruction[2]->omod != aluRedcInstruction[3]->omod ) debugBreakpoint(); if( aluRedcInstruction[0]->destClamp != aluRedcInstruction[1]->destClamp || aluRedcInstruction[1]->destClamp != aluRedcInstruction[2]->destClamp || aluRedcInstruction[2]->destClamp != aluRedcInstruction[3]->destClamp ) debugBreakpoint(); _emitALUReductionInstructionCode(shaderContext, aluRedcInstruction); i += 3; // skip the instructions that are part of the reduction operation } else /* not a reduction operation */ { if( aluInstruction.isOP3 ) { // op3 _emitALUOP3InstructionCode(shaderContext, cfInstruction, &aluInstruction); } else { // op2 if( aluInstruction.opcode == ALU_OP2_INST_NOP ) continue; // skip NOP instruction _emitALUOP2InstructionCode(shaderContext, cfInstruction, &aluInstruction); } } // handle omod sint32 outputDataType = _getALUInstructionOutputDataType(shaderContext, &aluInstruction); if( aluInstruction.omod != ALU_OMOD_NONE ) { if( outputDataType == LATTE_DECOMPILER_DTYPE_FLOAT ) { _emitInstructionOutputVariableName(shaderContext, &aluInstruction); if( aluInstruction.omod == ALU_OMOD_MUL2 ) src->add(" *= 2.0;" _CRLF); else if( aluInstruction.omod == ALU_OMOD_MUL4 ) src->add(" *= 4.0;" _CRLF); else if( aluInstruction.omod == ALU_OMOD_DIV2 ) src->add(" /= 2.0;" _CRLF); } else if( outputDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(" = "); src->add("floatBitsToInt(intBitsToFloat("); _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(")"); if( aluInstruction.omod == 1 ) src->add(" * 2.0"); else if( aluInstruction.omod == 2 ) src->add(" * 4.0"); else if( aluInstruction.omod == 3 ) src->add(" / 2.0"); src->add(");" _CRLF); } else { cemu_assert_unimplemented(); } } // handle clamp if( aluInstruction.destClamp != 0 ) { if( outputDataType == LATTE_DECOMPILER_DTYPE_FLOAT ) { _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(" = clamp("); _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(", 0.0, 1.0);" _CRLF); } else if( outputDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(" = clampFI32("); _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(");" _CRLF); } else { cemu_assert_unimplemented(); } } // handle result broadcasting for reduction instructions if( isReductionOperation ) { // reduction operations set all four PV components (todo: Needs further research. According to AMD docs, dot4 only sets PV.x? update: Unlike DOT4, CUBE sets all PV elements accordingly to their GPR output?) if( aluRedcInstruction[0]->opcode == ALU_OP2_INST_CUBE ) { // CUBE for (sint32 f = 0; f < 4; f++) { if (aluRedcInstruction[f]->writeMask != 0) continue; _emitInstructionPVPSOutputVariableName(shaderContext, aluRedcInstruction[f]); src->add(" = "); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); src->add(";" _CRLF); } } else { // DOT4, DOT4_IEEE, etc. // reduction operation result is only set for output in redc[0], we also need to update redc[1] to redc[3] for(sint32 f=0; f<4; f++) { if( aluRedcInstruction[f]->writeMask == 0 ) _emitInstructionPVPSOutputVariableName(shaderContext, aluRedcInstruction[f]); else { if (f == 0) continue; _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[f]); } src->add(" = "); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); src->add(";" _CRLF); } } } } shaderContext->aluPVPSState = nullptr; } /* * Emits code to access one component (xyzw) of the texture coordinate input vector */ void _emitTEXSampleCoordInputComponent(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction, sint32 componentIndex, sint32 interpretSrcAsType) { cemu_assert(componentIndex >= 0 && componentIndex < 4); cemu_assert_debug(interpretSrcAsType == LATTE_DECOMPILER_DTYPE_SIGNED_INT || interpretSrcAsType == LATTE_DECOMPILER_DTYPE_FLOAT); StringBuf* src = shaderContext->shaderSource; sint32 elementSel = texInstruction->textureFetch.srcSel[componentIndex]; if (elementSel < 4) { _emitRegisterChannelAccessCode(shaderContext, texInstruction->srcGpr, elementSel, interpretSrcAsType); return; } const char* resultElemTable[4] = {"x","y","z","w"}; if(interpretSrcAsType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { if( elementSel == 4 ) src->add("floatBitsToInt(0.0)"); else if( elementSel == 5 ) src->add("floatBitsToInt(1.0)"); } else if(interpretSrcAsType == LATTE_DECOMPILER_DTYPE_FLOAT ) { if( elementSel == 4 ) src->add("0.0"); else if( elementSel == 5 ) src->add("1.0"); } } const char* _texGprAccessElemTable[8] = {"x","y","z","w","_","_","_","_"}; char* _getTexGPRAccess(LatteDecompilerShaderContext* shaderContext, sint32 gprIndex, uint32 dataType, sint8 selX, sint8 selY, sint8 selZ, sint8 selW, char* tempBuffer) { // intBitsToFloat(R{}i.w) *tempBuffer = '\0'; uint8 elemCount = (selX > 0 ? 1 : 0) + (selY > 0 ? 1 : 0) + (selZ > 0 ? 1 : 0) + (selW > 0 ? 1 : 0); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) ; // no conversion else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) strcat(tempBuffer, "intBitsToFloat("); else cemu_assert_unimplemented(); strcat(tempBuffer, _getRegisterVarName(shaderContext, gprIndex)); // _texGprAccessElemTable strcat(tempBuffer, "."); if (selX >= 0) strcat(tempBuffer, _texGprAccessElemTable[selX]); if (selY >= 0) strcat(tempBuffer, _texGprAccessElemTable[selY]); if (selZ >= 0) strcat(tempBuffer, _texGprAccessElemTable[selZ]); if (selW >= 0) strcat(tempBuffer, _texGprAccessElemTable[selW]); if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) ; // no conversion else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) strcat(tempBuffer, ")"); else cemu_assert_unimplemented(); } else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) { if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) cemu_assert_unimplemented(); else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) ; // no conversion else cemu_assert_unimplemented(); strcat(tempBuffer, _getRegisterVarName(shaderContext, gprIndex)); // _texGprAccessElemTable strcat(tempBuffer, "."); if (selX >= 0) strcat(tempBuffer, _texGprAccessElemTable[selX]); if (selY >= 0) strcat(tempBuffer, _texGprAccessElemTable[selY]); if (selZ >= 0) strcat(tempBuffer, _texGprAccessElemTable[selZ]); if (selW >= 0) strcat(tempBuffer, _texGprAccessElemTable[selW]); if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) cemu_assert_unimplemented(); else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) ; // no conversion else cemu_assert_unimplemented(); } else cemu_assert_unimplemented(); return tempBuffer; } void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; if (texInstruction->textureFetch.textureIndex < 0 || texInstruction->textureFetch.textureIndex >= LATTE_NUM_MAX_TEX_UNITS) { // skip out of bounds texture unit access return; } auto texDim = shaderContext->shader->textureUnitDim[texInstruction->textureFetch.textureIndex]; char tempBuffer0[32]; char tempBuffer1[32]; src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); const char* resultElemTable[4] = {"x","y","z","w"}; sint32 numWrittenElements = 0; for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[f]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { debugBreakpoint(); } } // texture sampler opcode uint32 texOpcode = texInstruction->opcode; if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) { // vertex shader forces LOD to zero, but certain sampler types don't support textureLod(...) API if (texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) texOpcode = GPU7_TEX_INST_SAMPLE_C; } // check if offset is used bool hasOffset = false; if( texInstruction->textureFetch.offsetX != 0 || texInstruction->textureFetch.offsetY != 0 || texInstruction->textureFetch.offsetZ != 0 ) hasOffset = true; // emit sample code if (shaderContext->shader->textureIsIntegerFormat[texInstruction->textureFetch.textureIndex]) { // integer samplers if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) // uint to int { if(numWrittenElements == 1) src->add(" = int("); else shaderContext->shaderSource->addFmt(" = ivec{}(", numWrittenElements); } else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->add(" = uintBitsToFloat("); } else { // float samplers if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->add(" = floatBitsToInt("); else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->add(" = ("); } bool unnormalizationHandled = false; bool useTexelCoordinates = false; // handle illegal combinations if (texOpcode == GPU7_TEX_INST_FETCH4 && (texDim == Latte::E_DIM::DIM_1D || texDim == Latte::E_DIM::DIM_1D_ARRAY)) { // fetch4 is not allowed on 1D textures // seen in YWW during boss fight of Level 1-4 // todo - investigate what this returns on actual HW if (numWrittenElements == 1) shaderContext->shaderSource->add("0.0"); else shaderContext->shaderSource->addFmt("vec{}(0.0)", numWrittenElements); shaderContext->shaderSource->add(");" _CRLF); return; } if (texOpcode == GPU7_TEX_INST_SAMPLE && (texInstruction->textureFetch.unnormalized[0] && texInstruction->textureFetch.unnormalized[1] && texInstruction->textureFetch.unnormalized[2] && texInstruction->textureFetch.unnormalized[3]) ) { // texture is likely a RECT if (hasOffset) cemu_assert_unimplemented(); src->add("texelFetch("); unnormalizationHandled = true; useTexelCoordinates = true; } else if( texOpcode == GPU7_TEX_INST_FETCH4 ) { if( hasOffset ) cemu_assert_unimplemented(); src->add("textureGather("); } else if( texOpcode == GPU7_TEX_INST_LD ) { if( hasOffset ) cemu_assert_unimplemented(); src->add("texelFetch("); unnormalizationHandled = true; useTexelCoordinates = true; } else if( texOpcode == GPU7_TEX_INST_SAMPLE_L ) { // sample with LOD value set in gpr.w (replaces computed LOD value) if( hasOffset ) src->add("textureLodOffset("); else src->add("textureLod("); } else if (texOpcode == GPU7_TEX_INST_SAMPLE_LZ) { // sample with LOD set to 0.0 (replaces computed LOD value) if (hasOffset) src->add("textureLodOffset("); else src->add("textureLod("); } else if (texOpcode == GPU7_TEX_INST_SAMPLE_LB) { // sample with LOD biased // note: AMD doc says LOD bias is calculated from instruction LOD_BIAS field. But it appears that LOD bias is taken from input register. Might actually be both? if (hasOffset) src->add("textureOffset("); else src->add("texture("); } else if (texOpcode == GPU7_TEX_INST_SAMPLE) { if (hasOffset) src->add("textureOffset("); else src->add("texture("); } else if (texOpcode == GPU7_TEX_INST_SAMPLE_C_L) { // sample with LOD value set in gpr.w (replaces computed LOD value) if (hasOffset) src->add("textureLodOffset("); else src->add("textureLod("); } else if (texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) { // sample with LOD set to 0.0 (replaces computed LOD value) if (hasOffset) src->add("textureLodOffset("); else src->add("textureLod("); } else if (texOpcode == GPU7_TEX_INST_SAMPLE_C) { if (hasOffset) src->add("textureOffset("); else src->add("texture("); } else if (texOpcode == GPU7_TEX_INST_SAMPLE_G) { if (hasOffset) cemu_assert_unimplemented(); src->add("textureGrad("); } else { if( hasOffset ) cemu_assert_unimplemented(); cemu_assert_unimplemented(); src->add("texture("); } src->addFmt("{}{}, ", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); // for textureGather() add shift (todo: depends on rounding mode set in sampler registers?) if (texOpcode == GPU7_TEX_INST_FETCH4) { if (texDim == Latte::E_DIM::DIM_2D) { //src->addFmt2("(vec2(-0.1) / vec2(textureSize({}{},0).xy)) + ", gpu7Decompiler_getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureIndex); // vec2(-0.00001) is minimum to break Nvidia // vec2(0.0001) is minimum to fix shadows on Intel, also fixes it on AMD (Windows and Linux) // todo - emulating coordinate rounding mode correctly is tricky // GX2 supports two modes: Truncate or rounding according to DX9 rules // Vulkan uses truncate mode when point sampling (min and mag is both nearest) otherwise it uses rounding // adding a small fixed bias is enough to avoid vendor-specific cases where small inaccuracies cause the number to get rounded down due to truncation src->addFmt("vec2(0.0001) + "); } } const sint32 texCoordDataType = (texOpcode == GPU7_TEX_INST_LD) ? LATTE_DECOMPILER_DTYPE_SIGNED_INT : LATTE_DECOMPILER_DTYPE_FLOAT; if(useTexelCoordinates) { // handle integer coordinates for texelFetch if (texDim == Latte::E_DIM::DIM_2D || texDim == Latte::E_DIM::DIM_2D_MSAA) { src->add("ivec2("); src->add("vec2("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, texCoordDataType); src->addFmt(", "); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, texCoordDataType); src->addFmt(")*uf_tex{}Scale", texInstruction->textureFetch.textureIndex); // close vec2 and scale src->add("), 0"); // close ivec2 and lod param // todo - lod } else if (texDim == Latte::E_DIM::DIM_1D) { // VC DS games forget to initialize textures and use texel fetch on an uninitialized texture (a dim of 0 maps to 1D) src->add("int("); src->add("float("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, (texOpcode == GPU7_TEX_INST_LD) ? LATTE_DECOMPILER_DTYPE_SIGNED_INT : LATTE_DECOMPILER_DTYPE_FLOAT); src->addFmt(")*uf_tex{}Scale.x", texInstruction->textureFetch.textureIndex); src->add("), 0"); // todo - lod } else cemu_assert_debug(false); } else /* useTexelCoordinates == false */ { // float coordinates if ( (texOpcode == GPU7_TEX_INST_SAMPLE_C || texOpcode == GPU7_TEX_INST_SAMPLE_C_L || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) ) { // shadow sampler if (texDim == Latte::E_DIM::DIM_2D_ARRAY) { // 3 coords + compare value (as vec4) src->add("vec4("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_FLOAT); src->addFmt(",{})", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer0)); } else if (texDim == Latte::E_DIM::DIM_CUBEMAP) { // 2 coords + faceId if (texInstruction->textureFetch.srcSel[0] >= 4 || texInstruction->textureFetch.srcSel[1] >= 4) { debugBreakpoint(); } src->add("vec4("); src->addFmt("redcCUBEReverse({},", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0)); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->addFmt(")"); src->addFmt(",cubeMapArrayIndex{})", texInstruction->textureFetch.textureIndex); // cubemap index } else if (texDim == Latte::E_DIM::DIM_1D) { // 1 coord + 1 unused coord (per GLSL spec) + compare value if (texInstruction->textureFetch.srcSel[0] >= 4) { debugBreakpoint(); } src->addFmt("vec3({},0.0,{})", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], -1, -1, -1, tempBuffer0), _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer1)); } else { // 2 coords + compare value (as vec3) if (texInstruction->textureFetch.srcSel[0] >= 4 && texInstruction->textureFetch.srcSel[1] >= 4) { debugBreakpoint(); } src->addFmt("vec3({}, {})", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0), _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer1)); } } else if( texDim == Latte::E_DIM::DIM_3D || texDim == Latte::E_DIM::DIM_2D_ARRAY ) { // 3 coords src->add("vec3("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); } else if( texDim == Latte::E_DIM::DIM_CUBEMAP ) { // 2 coords + faceId cemu_assert_debug(texInstruction->textureFetch.srcSel[0] < 4); cemu_assert_debug(texInstruction->textureFetch.srcSel[1] < 4); src->add("vec4("); src->addFmt("redcCUBEReverse({},", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0)); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")"); src->addFmt(",cubeMapArrayIndex{})", texInstruction->textureFetch.textureIndex); // cubemap index } else if( texDim == Latte::E_DIM::DIM_1D ) { // 1 coord src->add(_getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], -1, -1, -1, tempBuffer0)); } else { // 2 coords src->add("vec2("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); // avoid truncate to effectively round downwards on texel edges if (ActiveSettings::ForceSamplerRoundToPrecision()) src->addFmt("+ vec2(1.0)/vec2(textureSize({}{}, 0))/512.0", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); } // lod or lod bias parameter if( texOpcode == GPU7_TEX_INST_SAMPLE_L || texOpcode == GPU7_TEX_INST_SAMPLE_LB || texOpcode == GPU7_TEX_INST_SAMPLE_C_L) { src->add(","); if(texOpcode == GPU7_TEX_INST_SAMPLE_LB) src->add(_FormatFloatAsGLSLConstant((float)texInstruction->textureFetch.lodBias / 16.0f)); else _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 3, LATTE_DECOMPILER_DTYPE_FLOAT); } else if( texOpcode == GPU7_TEX_INST_SAMPLE_LZ || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ ) { src->add(",0.0"); } } // gradient parameters if (texOpcode == GPU7_TEX_INST_SAMPLE_G) { if (texDim == Latte::E_DIM::DIM_2D || texDim == Latte::E_DIM::DIM_1D ) { src->add(",gradH.xy,gradV.xy"); } else { cemu_assert_unimplemented(); } } // offset if( texOpcode == GPU7_TEX_INST_SAMPLE_L || texOpcode == GPU7_TEX_INST_SAMPLE_LZ || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ || texOpcode == GPU7_TEX_INST_SAMPLE || texOpcode == GPU7_TEX_INST_SAMPLE_C ) { if( hasOffset ) { uint8 offsetComponentCount = 0; if( texDim == Latte::E_DIM::DIM_1D ) offsetComponentCount = 1; else if( texDim == Latte::E_DIM::DIM_2D ) offsetComponentCount = 2; else if( texDim == Latte::E_DIM::DIM_3D ) offsetComponentCount = 3; else if( texDim == Latte::E_DIM::DIM_2D_ARRAY ) offsetComponentCount = 2; else cemu_assert_unimplemented(); if( (texInstruction->textureFetch.offsetX&1) ) cemu_assert_unimplemented(); if( (texInstruction->textureFetch.offsetY&1) ) cemu_assert_unimplemented(); if ((texInstruction->textureFetch.offsetZ & 1)) cemu_assert_unimplemented(); if( offsetComponentCount == 1 ) src->addFmt(",{}", texInstruction->textureFetch.offsetX/2); else if( offsetComponentCount == 2 ) src->addFmt(",ivec2({},{})", texInstruction->textureFetch.offsetX/2, texInstruction->textureFetch.offsetY/2, texInstruction->textureFetch.offsetZ/2); else if( offsetComponentCount == 3 ) src->addFmt(",ivec3({},{},{})", texInstruction->textureFetch.offsetX/2, texInstruction->textureFetch.offsetY/2, texInstruction->textureFetch.offsetZ/2); } } // lod bias if( texOpcode == GPU7_TEX_INST_SAMPLE_C || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ ) { src->add(")"); if (numWrittenElements > 1) { // result is copied into multiple channels src->add("."); for (sint32 f = 0; f < numWrittenElements; f++) { cemu_assert_debug(texInstruction->dstSel[f] == 0); // only x component is defined src->add("x"); } } } else { src->add(")."); for (sint32 f = 0; f < 4; f++) { if( texInstruction->dstSel[f] < 4 ) { uint8 elemIndex = texInstruction->dstSel[f]; if (texOpcode == GPU7_TEX_INST_FETCH4) { // GLSL's textureGather() and GPU7's FETCH4 instruction have a different order of elements // xyzw: top-left, top-right, bottom-right, bottom-left // textureGather xyzw // fetch4 yzxw // translate index from fetch4 to textureGather order static uint8 fetchToGather[4] = { 2, // x -> z 0, // y -> x 1, // z -> y 3, // w -> w }; elemIndex = fetchToGather[elemIndex]; } src->add(resultElemTable[elemIndex]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { cemu_assert_unimplemented(); } } } src->add(");"); // debug #ifdef CEMU_DEBUG_ASSERT if(texInstruction->opcode == GPU7_TEX_INST_LD ) src->add(" // TEX_INST_LD"); else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE ) src->add(" // TEX_INST_SAMPLE"); else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_L ) src->add(" // TEX_INST_SAMPLE_L"); else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_LZ ) src->add(" // TEX_INST_SAMPLE_LZ"); else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_C ) src->add(" // TEX_INST_SAMPLE_C"); else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_G ) src->add(" // TEX_INST_SAMPLE_G"); else src->addFmt(" // 0x{:02x}", texInstruction->opcode); if (texInstruction->opcode != texOpcode) src->addFmt(" (applied as 0x{:02x})", texOpcode); src->addFmt(" OffsetXYZ {:02x} {:02x} {:02x}", (uint8)texInstruction->textureFetch.offsetX&0xFF, (uint8)texInstruction->textureFetch.offsetY&0xFF, (uint8)texInstruction->textureFetch.offsetZ&0xFF); #endif src->add("" _CRLF); } void _emitTEXGetTextureResInfoCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; src->addFmt("R{}", texInstruction->dstGpr); src->add("i"); src->add("."); const char* resultElemTable[4] = {"x","y","z","w"}; sint32 numWrittenElements = 0; for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[f]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { cemu_assert_unimplemented(); } } // todo - mip index parameter? auto texDim = shaderContext->shader->textureUnitDim[texInstruction->textureFetch.textureIndex]; if (texDim == Latte::E_DIM::DIM_1D) src->addFmt(" = ivec4(textureSize({}{}, 0),1,1,1).", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); else if (texDim == Latte::E_DIM::DIM_1D_ARRAY) src->addFmt(" = ivec4(textureSize({}{}, 0),1,1).", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); else if (texDim == Latte::E_DIM::DIM_2D || texDim == Latte::E_DIM::DIM_2D_MSAA) src->addFmt(" = ivec4(textureSize({}{}, 0),1,1).", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); else if (texDim == Latte::E_DIM::DIM_2D_ARRAY) src->addFmt(" = ivec4(textureSize({}{}, 0),1).", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); else { cemu_assert_debug(false); src->addFmt(" = ivec4(textureSize({}{}, 0),1,1).", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); } for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[texInstruction->dstSel[f]]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { debugBreakpoint(); } } src->add(";" _CRLF); } void _emitTEXGetCompTexLodCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); const char* resultElemTable[4] = {"x","y","z","w"}; sint32 numWrittenElements = 0; for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[f]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { debugBreakpoint(); } } src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); if( shaderContext->shader->textureUnitDim[texInstruction->textureFetch.textureIndex] == Latte::E_DIM::DIM_CUBEMAP ) { // 3 coordinates if(shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("vec4(textureQueryLod({}{}, {}.{}{}{}),0.0,0.0)", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]], resultElemTable[texInstruction->textureFetch.srcSel[2]]); else src->addFmt("vec4(textureQueryLod({}{}, intBitsToFloat({}.{}{}{})),0.0,0.0)", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]], resultElemTable[texInstruction->textureFetch.srcSel[2]]); } else { if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("vec4(textureQueryLod({}{}, {}.{}{}),0.0,0.0)", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]]); else src->addFmt("vec4(textureQueryLod({}{}, intBitsToFloat({}.{}{})),0.0,0.0)", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]]); debugBreakpoint(); } _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); src->add("."); for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[texInstruction->dstSel[f]]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { debugBreakpoint(); } } src->add(";" _CRLF); } void _emitTEXSetCubemapIndexCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; src->addFmt("cubeMapArrayIndex{}", texInstruction->textureFetch.textureIndex); const char* resultElemTable[4] = {"x","y","z","w"}; if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt(" = intBitsToFloat(R{}i.{});" _CRLF, texInstruction->srcGpr, resultElemTable[texInstruction->textureFetch.srcSel[0]]); else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt(" = R{}f.{};" _CRLF, texInstruction->srcGpr, resultElemTable[texInstruction->textureFetch.srcSel[0]]); else cemu_assert_unimplemented(); } void _emitTEXGetGradientsHV(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; sint32 componentCount = 0; for (sint32 i = 0; i < 4; i++) { if(texInstruction->dstSel[i] == 7) continue; componentCount++; } src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); const char* resultElemTable[4] = { "x","y","z","w" }; sint32 numWrittenElements = 0; for (sint32 f = 0; f < 4; f++) { if (texInstruction->dstSel[f] < 4) { src->add(resultElemTable[f]); numWrittenElements++; } else if (texInstruction->dstSel[f] == 7) { // masked and not written } else { debugBreakpoint(); } } const char* funcName; if (texInstruction->opcode == GPU7_TEX_INST_GET_GRADIENTS_H) funcName = "dFdx"; else funcName = "dFdy"; src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); src->addFmt("{}(", funcName); _emitRegisterAccessCode(shaderContext, texInstruction->srcGpr, (componentCount >= 1) ? texInstruction->textureFetch.srcSel[0] : -1, (componentCount >= 2) ? texInstruction->textureFetch.srcSel[1] : -1, (componentCount >= 3) ? texInstruction->textureFetch.srcSel[2] : -1, (componentCount >= 4)?texInstruction->textureFetch.srcSel[3]:-1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); src->add(";" _CRLF); } void _emitTEXSetGradientsHV(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; if (texInstruction->opcode == GPU7_TEX_INST_SET_GRADIENTS_H) src->add("gradH = "); else src->add("gradV = "); _emitRegisterAccessCode(shaderContext, texInstruction->srcGpr, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], texInstruction->textureFetch.srcSel[2], texInstruction->textureFetch.srcSel[3], LATTE_DECOMPILER_DTYPE_FLOAT); src->add(";" _CRLF); } void _emitGSReadInputVFetchCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); const char* resultElemTable[4] = {"x","y","z","w"}; sint32 numWrittenElements = 0; for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[f]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { cemu_assert_unimplemented(); } } src->add(" = "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); src->add("(v2g["); if (texInstruction->textureFetch.srcSel[0] >= 4) cemu_assert_unimplemented(); if (texInstruction->textureFetch.srcSel[1] >= 4) cemu_assert_unimplemented(); // todo: Index type src->add("0"); src->addFmt("].passV2GParameter{}.", texInstruction->textureFetch.offset/16); for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[texInstruction->dstSel[f]]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { cemu_assert_unimplemented(); } } src->add(")"); _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); src->add(";" _CRLF); } sint32 _writeDestMaskXYZW(LatteDecompilerShaderContext* shaderContext, sint8* dstSel) { StringBuf* src = shaderContext->shaderSource; const char* resultElemTable[4] = { "x","y","z","w" }; sint32 numWrittenElements = 0; for (sint32 f = 0; f < 4; f++) { if (dstSel[f] < 4) { src->add(resultElemTable[f]); numWrittenElements++; } else if (dstSel[f] == 7) { // masked and not written } else { cemu_assert_unimplemented(); } } return numWrittenElements; } void _emitTEXVFetchCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { // handle special case where geometry shader reads input attributes from vertex shader via ringbuffer StringBuf* src = shaderContext->shaderSource; if( texInstruction->textureFetch.textureIndex == 0x9F && shaderContext->shaderType == LatteConst::ShaderType::Geometry ) { _emitGSReadInputVFetchCode(shaderContext, texInstruction); return; } src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); _writeDestMaskXYZW(shaderContext, texInstruction->dstSel); const char* resultElemTable[4] = {"x","y","z","w"}; src->add(" = "); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->add("floatBitsToInt("); else src->add("("); src->addFmt("{}{}[", _getShaderUniformBlockVariableName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex - 0x80); if( shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) src->addFmt("{}.{}", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]]); else src->addFmt("floatBitsToInt({}.{})", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]]); src->add("]."); for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[texInstruction->dstSel[f]]); } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { debugBreakpoint(); } } src->add(");" _CRLF); } void _emitTEXReadMemCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); sint32 count = _writeDestMaskXYZW(shaderContext, texInstruction->dstSel); src->add(" = "); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->add("floatBitsToInt("); else src->add("("); sint32 readCount; if (texInstruction->memRead.format == FMT_32_FLOAT) { readCount = 1; // todo src->add("0.0"); } else if (texInstruction->memRead.format == FMT_32_32_FLOAT) { readCount = 2; // todo src->add("vec2(0.0,0.0)"); } else if (texInstruction->memRead.format == FMT_32_32_32_FLOAT) { readCount = 3; // todo src->add("vec3(0.0,0.0,0.0)"); } else { cemu_assert_unimplemented(); } if (count < readCount) { if (count == 1) src->add(".x"); else if (count == 2) src->add(".xy"); else if (count == 3) src->add(".xyz"); } src->add(");" _CRLF); } void _emitTEXClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { cemu_assert_debug(cfInstruction->instructionsALU.empty()); for(auto& texInstruction : cfInstruction->instructionsTEX) { if( texInstruction.opcode == GPU7_TEX_INST_SAMPLE || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_L || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_LB || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_LZ || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_L || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_LZ || texInstruction.opcode == GPU7_TEX_INST_FETCH4 || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_G || texInstruction.opcode == GPU7_TEX_INST_LD ) _emitTEXSampleTextureCode(shaderContext, &texInstruction); else if( texInstruction.opcode == GPU7_TEX_INST_GET_TEXTURE_RESINFO ) _emitTEXGetTextureResInfoCode(shaderContext, &texInstruction); else if( texInstruction.opcode == GPU7_TEX_INST_GET_COMP_TEX_LOD ) _emitTEXGetCompTexLodCode(shaderContext, &texInstruction); else if( texInstruction.opcode == GPU7_TEX_INST_SET_CUBEMAP_INDEX ) _emitTEXSetCubemapIndexCode(shaderContext, &texInstruction); else if (texInstruction.opcode == GPU7_TEX_INST_GET_GRADIENTS_H || texInstruction.opcode == GPU7_TEX_INST_GET_GRADIENTS_V) _emitTEXGetGradientsHV(shaderContext, &texInstruction); else if (texInstruction.opcode == GPU7_TEX_INST_SET_GRADIENTS_H || texInstruction.opcode == GPU7_TEX_INST_SET_GRADIENTS_V) _emitTEXSetGradientsHV(shaderContext, &texInstruction); else if (texInstruction.opcode == GPU7_TEX_INST_VFETCH) _emitTEXVFetchCode(shaderContext, &texInstruction); else if (texInstruction.opcode == GPU7_TEX_INST_MEM) _emitTEXReadMemCode(shaderContext, &texInstruction); else cemu_assert_unimplemented(); } } // generate the code for reading the source input GPR (or constants) for exports void _emitExportGPRReadCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, sint32 requiredType, uint32 burstIndex) { StringBuf* src = shaderContext->shaderSource; uint32 numOutputs = 4; if( cfInstruction->type == GPU7_CF_INST_MEM_RING_WRITE ) { numOutputs = (cfInstruction->memWriteCompMask&1)?1:0; numOutputs += (cfInstruction->memWriteCompMask&2)?1:0; numOutputs += (cfInstruction->memWriteCompMask&4)?1:0; numOutputs += (cfInstruction->memWriteCompMask&8)?1:0; } if (requiredType == LATTE_DECOMPILER_DTYPE_FLOAT) { if(numOutputs == 1) src->add("float("); else src->addFmt("vec{}(", numOutputs); } else if (requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { if (numOutputs == 1) src->add("int("); else src->addFmt("ivec{}(", numOutputs); } else cemu_assert_unimplemented(); sint32 actualOutputs = 0; for(sint32 i=0; i<4; i++) { // todo: Use type of register element based on information from type tracker (currently we assume it's always a signed integer) uint32 exportSel = 0; if( cfInstruction->type == GPU7_CF_INST_MEM_RING_WRITE ) { exportSel = i; if( (cfInstruction->memWriteCompMask&(1<<i)) == 0 ) continue; // dont output } else { exportSel = cfInstruction->exportComponentSel[i]; } if( actualOutputs > 0 ) src->add(", "); actualOutputs++; if( exportSel < 4 ) { _emitRegisterAccessCode(shaderContext, cfInstruction->exportSourceGPR+burstIndex, exportSel, -1, -1, -1, requiredType); } else if (exportSel == 4) { // constant zero src->add("0"); } else if (exportSel == 5) { // constant one src->add("1.0"); } else if( exportSel == 7 ) { // element masked (which means 0 is exported?) src->add("0"); } else { cemu_assert_debug(false); src->add("0"); } } if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) src->add(")"); else if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) src->add(")"); else cemu_assert_unimplemented(); } void _emitExportCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { StringBuf* src = shaderContext->shaderSource; src->add("// export" _CRLF); if(shaderContext->shaderType == LatteConst::ShaderType::Vertex ) { if( cfInstruction->exportBurstCount != 0 ) debugBreakpoint(); if (cfInstruction->exportType == 1 && cfInstruction->exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION) { // export position // GX2 special state 0 disables rasterizer viewport offset and scaling (probably, exact mechanism is not known). Handle this here bool hasAnyViewportScaleDisabled = !shaderContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_SCALE_ENA() || !shaderContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_SCALE_ENA() || !shaderContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_SCALE_ENA(); if (hasAnyViewportScaleDisabled) { src->add("vec4 finalPos = "); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); src->add(";" _CRLF); src->add("finalPos.xy = finalPos.xy * uf_windowSpaceToClipSpaceTransform - vec2(1.0,1.0);"); src->add("SET_POSITION(finalPos);"); } else { src->add("SET_POSITION("); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); src->add(");" _CRLF); } } else if (cfInstruction->exportType == 1 && cfInstruction->exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_POINT_SIZE ) { // export gl_PointSize if (shaderContext->analyzer.outputPointSize) { cemu_assert_debug(shaderContext->analyzer.writesPointSize); src->add("gl_PointSize = ("); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); src->add(").x"); src->add(";" _CRLF); } } else if( cfInstruction->exportType == 2 && cfInstruction->exportArrayBase < 32 ) { // export parameter sint32 paramIndex = cfInstruction->exportArrayBase; uint32 vsSemanticId = _getVertexShaderOutParamSemanticId(shaderContext->contextRegisters, paramIndex); if (vsSemanticId != 0xFF) { src->addFmt("passParameterSem{} = ", vsSemanticId); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); src->add(";" _CRLF); } else { src->add("// skipped export to semanticId 255" _CRLF); } } else cemu_assert_unimplemented(); } else if(shaderContext->shaderType == LatteConst::ShaderType::Pixel ) { if( cfInstruction->exportType == 0 && cfInstruction->exportArrayBase < 8 ) { for(uint32 i=0; i<(cfInstruction->exportBurstCount+1); i++) { sint32 pixelColorOutputIndex = LatteDecompiler_getColorOutputIndexFromExportIndex(shaderContext, cfInstruction->exportArrayBase+i); // if color output is for target 0, then also handle alpha test bool alphaTestEnable = shaderContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_TEST_ENABLE(); auto alphaTestFunc = shaderContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_FUNC(); if( pixelColorOutputIndex == 0 && alphaTestEnable && alphaTestFunc == Latte::E_COMPAREFUNC::NEVER ) { // never pass alpha test src->add("discard;" _CRLF); } else if( pixelColorOutputIndex == 0 && alphaTestEnable && alphaTestFunc != Latte::E_COMPAREFUNC::ALWAYS) { src->add("if( (("); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, i); src->add(").a "); switch( alphaTestFunc ) { case Latte::E_COMPAREFUNC::LESS: src->add("<"); break; case Latte::E_COMPAREFUNC::EQUAL: src->add("=="); break; case Latte::E_COMPAREFUNC::LEQUAL: src->add("<="); break; case Latte::E_COMPAREFUNC::GREATER: src->add(">"); break; case Latte::E_COMPAREFUNC::NOTEQUAL: src->add("!="); break; case Latte::E_COMPAREFUNC::GEQUAL: src->add(">="); break; } src->add(" uf_alphaTestRef"); src->add(") == false) discard;" _CRLF); } // pixel color output src->addFmt("passPixelColor{} = ", pixelColorOutputIndex); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, i); src->add(";" _CRLF); if( cfInstruction->exportArrayBase+i >= 8 ) cemu_assert_unimplemented(); } } else if( cfInstruction->exportType == 0 && cfInstruction->exportArrayBase == 61 ) { // pixel depth or gl_FragStencilRefARB if( cfInstruction->exportBurstCount > 0 ) cemu_assert_unimplemented(); if (cfInstruction->exportComponentSel[0] == 7) { cemu_assert_unimplemented(); // gl_FragDepth ? } if (cfInstruction->exportComponentSel[1] != 7) { cemu_assert_unimplemented(); // exporting to gl_FragStencilRefARB } if (cfInstruction->exportComponentSel[2] != 7) { cemu_assert_unimplemented(); // ukn } if (cfInstruction->exportComponentSel[3] != 7) { cemu_assert_unimplemented(); // ukn } src->add("gl_FragDepth = "); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); src->add(".x"); src->add(";" _CRLF); } else cemu_assert_unimplemented(); } } void _emitXYZWByMask(StringBuf* src, uint32 mask) { if( (mask&(1<<0)) != 0 ) src->add("x"); if( (mask&(1<<1)) != 0 ) src->add("y"); if( (mask&(1<<2)) != 0 ) src->add("z"); if( (mask&(1<<3)) != 0 ) src->add("w"); } void _emitCFRingWriteCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { StringBuf* src = shaderContext->shaderSource; // calculate parameter output (based on ring buffer output offset relative to GS unit) uint32 bytesPerVertex = shaderContext->contextRegisters[mmSQ_GS_VERT_ITEMSIZE] * 4; bytesPerVertex = std::max(bytesPerVertex, (uint32)1); // avoid division by zero uint32 parameterOffset = ((cfInstruction->exportArrayBase * 4) % bytesPerVertex); // for geometry shaders with streamout, MEM_RING_WRITE is used to pass the data to the copy shader, which then uses STREAM*_WRITE if (shaderContext->shaderType == LatteConst::ShaderType::Geometry && shaderContext->analyzer.hasStreamoutEnable) { // if streamout is enabled, we generate transform feedback output code instead of the normal gs output for (uint32 burstIndex = 0; burstIndex < (cfInstruction->exportBurstCount + 1); burstIndex++) { parameterOffset = ((cfInstruction->exportArrayBase * 4 + burstIndex*0x10) % bytesPerVertex); // find matching stream write in copy shader LatteGSCopyShaderStreamWrite_t* streamWrite = nullptr; for (auto& it : shaderContext->parsedGSCopyShader->list_streamWrites) { if (it.offset == parameterOffset) { streamWrite = ⁢ break; } } if (streamWrite == nullptr) { cemu_assert_suspicious(); return; } for (sint32 i = 0; i < 4; i++) { if ((cfInstruction->memWriteCompMask&(1 << i)) == 0) continue; if (shaderContext->options->useTFViaSSBO) { uint32 u32Offset = streamWrite->exportArrayBase + i; src->addFmt("sb_buffer[sbBase{} + {}]", streamWrite->bufferIndex, u32Offset); } else { src->addFmt("sb{}[{}]", streamWrite->bufferIndex, streamWrite->exportArrayBase + i); } src->add(" = "); _emitTypeConversionPrefix(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->addFmt("{}.", _getRegisterVarName(shaderContext, cfInstruction->exportSourceGPR+burstIndex)); if (i == 0) src->add("x"); else if (i == 1) src->add("y"); else if (i == 2) src->add("z"); else if (i == 3) src->add("w"); _emitTypeConversionSuffix(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(";" _CRLF); } } return; } if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) { if (cfInstruction->memWriteElemSize != 3) cemu_assert_unimplemented(); if ((cfInstruction->exportArrayBase & 3) != 0) cemu_assert_unimplemented(); for (sint32 burstIndex = 0; burstIndex < (sint32)(cfInstruction->exportBurstCount + 1); burstIndex++) { src->addFmt("v2g.passV2GParameter{}.", (cfInstruction->exportArrayBase) / 4 + burstIndex); _emitXYZWByMask(src, cfInstruction->memWriteCompMask); src->addFmt(" = "); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_SIGNED_INT, burstIndex); src->add(";" _CRLF); } } else if (shaderContext->shaderType == LatteConst::ShaderType::Geometry) { cemu_assert_debug(cfInstruction->memWriteElemSize == 3); //if (cfInstruction->memWriteElemSize != 3) // debugBreakpoint(); cemu_assert_debug((cfInstruction->exportArrayBase & 3) == 0); for (uint32 burstIndex = 0; burstIndex < (cfInstruction->exportBurstCount + 1); burstIndex++) { uint32 parameterExportType = 0; uint32 parameterExportBase = 0; if (LatteGSCopyShaderParser_getExportTypeByOffset(shaderContext->parsedGSCopyShader, parameterOffset + burstIndex * (cfInstruction->memWriteElemSize+1)*4, ¶meterExportType, ¶meterExportBase) == false) { cemu_assert_debug(false); shaderContext->hasError = true; return; } if (parameterExportType == 1 && parameterExportBase == GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION) { src->add("{" _CRLF); src->addFmt("vec4 pos = vec4(0.0,0.0,0.0,1.0);" _CRLF); src->addFmt("pos."); _emitXYZWByMask(src, cfInstruction->memWriteCompMask); src->addFmt(" = "); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, burstIndex); src->add(";" _CRLF); src->add("SET_POSITION(pos);" _CRLF); src->add("}" _CRLF); } else if (parameterExportType == 2 && parameterExportBase < 16) { src->addFmt("passG2PParameter{}.", parameterExportBase); _emitXYZWByMask(src, cfInstruction->memWriteCompMask); src->addFmt(" = "); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, burstIndex); src->add(";" _CRLF); } else cemu_assert_debug(false); } } else debugBreakpoint(); // todo } void _emitStreamWriteCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { StringBuf* src = shaderContext->shaderSource; if (shaderContext->analyzer.hasStreamoutEnable == false) { #ifdef CEMU_DEBUG_ASSERT src->add("// omitted streamout write" _CRLF); #endif return; } uint32 streamoutBufferIndex; if (cfInstruction->type == GPU7_CF_INST_MEM_STREAM0_WRITE) streamoutBufferIndex = 0; else if (cfInstruction->type == GPU7_CF_INST_MEM_STREAM1_WRITE) streamoutBufferIndex = 1; else cemu_assert_unimplemented(); if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) { uint32 arraySize = cfInstruction->memWriteArraySize + 1; for (sint32 i = 0; i < (sint32)arraySize; i++) { if ((cfInstruction->memWriteCompMask&(1 << i)) == 0) continue; if (shaderContext->options->useTFViaSSBO) { uint32 u32Offset = cfInstruction->exportArrayBase + i; src->addFmt("sb_buffer[sbBase{} + {}]", streamoutBufferIndex, u32Offset); } else { src->addFmt("sb{}[{}]", streamoutBufferIndex, cfInstruction->exportArrayBase + i); } src->add(" = "); _emitTypeConversionPrefix(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(_getRegisterVarName(shaderContext, cfInstruction->exportSourceGPR)); _appendChannelAccess(src, i); _emitTypeConversionSuffix(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(";" _CRLF); } } else cemu_assert_debug(false); } void _emitCFCall(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { StringBuf* src = shaderContext->shaderSource; uint32 subroutineAddr = cfInstruction->addr; LatteDecompilerSubroutineInfo* subroutineInfo = nullptr; // find subroutine for (auto& subroutineItr : shaderContext->list_subroutines) { if (subroutineItr.cfAddr == subroutineAddr) { subroutineInfo = &subroutineItr; break; } } if (subroutineInfo == nullptr) { cemu_assert_debug(false); return; } // inline function if (shaderContext->isSubroutine) { cemu_assert_debug(false); // inlining with cascaded function calls not supported return; } // init CF stack variables src->addFmt("activeMaskStackSub{:04x}[0] = true;" _CRLF, subroutineInfo->cfAddr); src->addFmt("activeMaskStackCSub{:04x}[0] = true;" _CRLF, subroutineInfo->cfAddr); src->addFmt("activeMaskStackCSub{:04x}[1] = true;" _CRLF, subroutineInfo->cfAddr); shaderContext->isSubroutine = true; shaderContext->subroutineInfo = subroutineInfo; for(auto& cfInstruction : subroutineInfo->instructions) LatteDecompiler_emitClauseCode(shaderContext, &cfInstruction, true); shaderContext->isSubroutine = false; shaderContext->subroutineInfo = nullptr; } void LatteDecompiler_emitClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, bool isSubroutine) { StringBuf* src = shaderContext->shaderSource; if( cfInstruction->type == GPU7_CF_INST_ALU || cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction->type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction->type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction->type == GPU7_CF_INST_ALU_BREAK || cfInstruction->type == GPU7_CF_INST_ALU_ELSE_AFTER ) { // emit ALU code if (shaderContext->analyzer.modifiesPixelActiveState) { if(cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE) src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - 1)); else src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); } if (cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE) { src->addFmt("{} = {};" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth-1)); src->addFmt("{} = {};" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); } _emitALUClauseCode(shaderContext, cfInstruction); if( shaderContext->analyzer.modifiesPixelActiveState ) src->add("}" _CRLF); cemu_assert_debug(!(shaderContext->analyzer.modifiesPixelActiveState == false && cfInstruction->type != GPU7_CF_INST_ALU)); // handle ELSE case of PUSH_BEFORE if( cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE ) { src->add("else {" _CRLF); src->addFmt("{} = false;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); src->addFmt("{} = false;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); src->add("}" _CRLF); } // post clause handler if( cfInstruction->type == GPU7_CF_INST_ALU_POP_AFTER ) { src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - 1), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - 1), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - 1)); } else if( cfInstruction->type == GPU7_CF_INST_ALU_POP2_AFTER ) { src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - 2), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - 2), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - 2)); } else if( cfInstruction->type == GPU7_CF_INST_ALU_ELSE_AFTER ) { // no condition test // pop stack if( cfInstruction->popCount != 0 ) debugBreakpoint(); // else operation src->addFmt("{} = {} == false;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); } } else if( cfInstruction->type == GPU7_CF_INST_TEX ) { // emit TEX code if (shaderContext->analyzer.modifiesPixelActiveState) { src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth+1)); } _emitTEXClauseCode(shaderContext, cfInstruction); if (shaderContext->analyzer.modifiesPixelActiveState) { src->add("}" _CRLF); } } else if( cfInstruction->type == GPU7_CF_INST_EXPORT || cfInstruction->type == GPU7_CF_INST_EXPORT_DONE ) { // emit export code _emitExportCode(shaderContext, cfInstruction); } else if( cfInstruction->type == GPU7_CF_INST_ELSE ) { // todo: Condition test, popCount? src->addFmt("{} = {} == false;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); } else if( cfInstruction->type == GPU7_CF_INST_POP ) { src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - cfInstruction->popCount), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount)); } else if( cfInstruction->type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction->type == GPU7_CF_INST_LOOP_START_NO_AL) { // start of loop // if pixel is disabled, then skip loop if (ActiveSettings::ShaderPreventInfiniteLoopsEnabled()) { // with iteration limit to prevent infinite loops src->addFmt("int loopCounter{} = 0;" _CRLF, (sint32)cfInstruction->cfAddr); src->addFmt("while( {} == true && loopCounter{} < 500 )" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), (sint32)cfInstruction->cfAddr); src->add("{" _CRLF); src->addFmt("loopCounter{}++;" _CRLF, (sint32)cfInstruction->cfAddr); } else { src->addFmt("while( {} == true )" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); src->add("{" _CRLF); } } else if( cfInstruction->type == GPU7_CF_INST_LOOP_END ) { // this might not always work if( cfInstruction->popCount != 0 ) debugBreakpoint(); src->add("}" _CRLF); } else if( cfInstruction->type == GPU7_CF_INST_LOOP_BREAK ) { if( cfInstruction->popCount != 0 ) debugBreakpoint(); if (shaderContext->analyzer.modifiesPixelActiveState) { src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); } // note: active stack level is set to the same level as the loop begin. popCount is ignored src->add("break;" _CRLF); if (shaderContext->analyzer.modifiesPixelActiveState) src->add("}" _CRLF); } else if( cfInstruction->type == GPU7_CF_INST_MEM_STREAM0_WRITE || cfInstruction->type == GPU7_CF_INST_MEM_STREAM1_WRITE ) { _emitStreamWriteCode(shaderContext, cfInstruction); } else if( cfInstruction->type == GPU7_CF_INST_MEM_RING_WRITE ) { _emitCFRingWriteCode(shaderContext, cfInstruction); } else if( cfInstruction->type == GPU7_CF_INST_EMIT_VERTEX ) { if( shaderContext->analyzer.modifiesPixelActiveState ) src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); // write point size if (shaderContext->analyzer.outputPointSize && shaderContext->analyzer.writesPointSize == false) src->add("gl_PointSize = uf_pointSize;" _CRLF); // emit vertex src->add("EmitVertex();" _CRLF); // increment transform feedback pointer if (shaderContext->analyzer.useSSBOForStreamout) { for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if (!shaderContext->output->streamoutBufferWriteMask[i]) continue; cemu_assert_debug((shaderContext->output->streamoutBufferStride[i] & 3) == 0); src->addFmt("sbBase{} += {};" _CRLF, i, shaderContext->output->streamoutBufferStride[i] / 4); } } if( shaderContext->analyzer.modifiesPixelActiveState ) src->add("}" _CRLF); } else if (cfInstruction->type == GPU7_CF_INST_CALL) { _emitCFCall(shaderContext, cfInstruction); } else if (cfInstruction->type == GPU7_CF_INST_RETURN) { // todo (handle properly) } else { cemu_assert_debug(false); } } void LatteDecompiler_emitGLSLHelperFunctions(LatteDecompilerShaderContext* shaderContext, StringBuf* fCStr_shaderSource) { if( shaderContext->analyzer.hasRedcCUBE ) { fCStr_shaderSource->add("void redcCUBE(vec4 src0, vec4 src1, out vec3 stm, out int faceId)\r\n" "{\r\n" "// stm -> x .. s, y .. t, z .. MajorAxis*2.0\r\n" "vec3 inputCoord = normalize(vec3(src1.y, src1.x, src0.x));\r\n" "float rx = inputCoord.x;\r\n" "float ry = inputCoord.y;\r\n" "float rz = inputCoord.z;\r\n" "if( abs(rx) > abs(ry) && abs(rx) > abs(rz) )\r\n" "{\r\n" "stm.z = rx*2.0;\r\n" "stm.xy = vec2(ry,rz); \r\n" "if( rx >= 0.0 )\r\n" "{\r\n" "faceId = 0;\r\n" "}\r\n" "else\r\n" "{\r\n" "faceId = 1;\r\n" "}\r\n" "}\r\n" "else if( abs(ry) > abs(rx) && abs(ry) > abs(rz) )\r\n" "{\r\n" "stm.z = ry*2.0;\r\n" "stm.xy = vec2(rx,rz); \r\n" "if( ry >= 0.0 )\r\n" "{\r\n" "faceId = 2;\r\n" "}\r\n" "else\r\n" "{\r\n" "faceId = 3;\r\n" "}\r\n" "}\r\n" "else //if( abs(rz) > abs(ry) && abs(rz) > abs(rx) )\r\n" "{\r\n" "stm.z = rz*2.0;\r\n" "stm.xy = vec2(rx,ry); \r\n" "if( rz >= 0.0 )\r\n" "{\r\n" "faceId = 4;\r\n" "}\r\n" "else\r\n" "{\r\n" "faceId = 5;\r\n" "}\r\n" "}\r\n" "}\r\n"); } if( shaderContext->analyzer.hasCubeMapTexture ) { fCStr_shaderSource->add("vec3 redcCUBEReverse(vec2 st, int faceId)\r\n" "{\r\n" "st.yx = st.xy;\r\n" "vec3 v;\r\n" "float majorAxis = 1.0;\r\n" "if( faceId == 0 )\r\n" "{\r\n" "v.yz = (st-vec2(1.5))*(majorAxis*2.0);\r\n" "v.x = 1.0;\r\n" "}\r\n" "else if( faceId == 1 )\r\n" "{\r\n" "v.yz = (st-vec2(1.5))*(majorAxis*2.0);\r\n" "v.x = -1.0;\r\n" "}\r\n" "else if( faceId == 2 )\r\n" "{\r\n" "v.xz = (st-vec2(1.5))*(majorAxis*2.0);\r\n" "v.y = 1.0;\r\n" "}\r\n" "else if( faceId == 3 )\r\n" "{\r\n" "v.xz = (st-vec2(1.5))*(majorAxis*2.0);\r\n" "v.y = -1.0;\r\n" "}\r\n" "else if( faceId == 4 )\r\n" "{\r\n" "v.xy = (st-vec2(1.5))*(majorAxis*2.0);\r\n" "v.z = 1.0;\r\n" "}\r\n" "else\r\n" "{\r\n" "v.xy = (st-vec2(1.5))*(majorAxis*2.0);\r\n" "v.z = -1.0;\r\n" "}\r\n" "return v;\r\n" "}\r\n"); } // clamp fCStr_shaderSource->add("" "int clampFI32(int v)\r\n" "{\r\n" "if( v == 0x7FFFFFFF )\r\n" " return floatBitsToInt(1.0);\r\n" "else if( v == 0xFFFFFFFF )\r\n" " return floatBitsToInt(0.0);\r\n" "return floatBitsToInt(clamp(intBitsToFloat(v), 0.0, 1.0));\r\n" "}\r\n"); // mul non-ieee way (0*NaN/INF => 0.0) if (shaderContext->options->strictMul) { // things we tried: //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ return mix(a*b,0.0,a==0.0||b==0.0); }" STR_LINEBREAK); //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ return mix(vec2(a*b,0.0),vec2(0.0,0.0),(equal(vec2(a),vec2(0.0,0.0))||equal(vec2(b),vec2(0.0,0.0)))).x; }" STR_LINEBREAK); //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ if( a == 0.0 || b == 0.0 ) return 0.0; return a*b; }" STR_LINEBREAK); //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){float r = a*b;r = intBitsToFloat(floatBitsToInt(r)&(((floatBitsToInt(a) != 0) && (floatBitsToInt(b) != 0))?0xFFFFFFFF:0));return r;}" STR_LINEBREAK); works // for "min" it used to be: float mul_nonIEEE(float a, float b){ return min(a*b,min(abs(a)*3.40282347E+38F,abs(b)*3.40282347E+38F)); } if( LatteGPUState.glVendor == GLVENDOR_NVIDIA && !ActiveSettings::DumpShadersEnabled()) fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){return mix(0.0, a*b, (a != 0.0) && (b != 0.0));}" _CRLF); // compiles faster on Nvidia and also results in lower RAM usage (OpenGL) else fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ if( a == 0.0 || b == 0.0 ) return 0.0; return a*b; }" _CRLF); // DXKV-like: fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ return (b==0.0 ? 0.0 : a) * (a==0.0 ? 0.0 : b); }" _CRLF); } } #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp" void LatteDecompiler_emitAttributeImport(LatteDecompilerShaderContext* shaderContext, LatteParsedFetchShaderAttribute_t& attrib) { auto src = shaderContext->shaderSource; static const char* dsMappingTableFloat[6] = { "int(attrDecoder.x)", "int(attrDecoder.y)", "int(attrDecoder.z)", "int(attrDecoder.w)", /*"floatBitsToInt(0.0)"*/ "0", /*"floatBitsToInt(1.0)"*/ "0x3f800000" }; static const char* dsMappingTableInt[6] = { "int(attrDecoder.x)", "int(attrDecoder.y)", "int(attrDecoder.z)", "int(attrDecoder.w)", "0", "1" }; // get register index based on vtx semantic table uint32 attributeShaderLoc = 0xFFFFFFFF; for (sint32 f = 0; f < 32; f++) { if (shaderContext->contextRegisters[mmSQ_VTX_SEMANTIC_0 + f] == attrib.semanticId) { attributeShaderLoc = f; break; } } if (attributeShaderLoc == 0xFFFFFFFF) return; // attribute is not mapped to VS input uint32 registerIndex = attributeShaderLoc + 1; // R0 is skipped // is register used? if ((shaderContext->analyzer.gprUseMask[registerIndex / 8] & (1 << (registerIndex % 8))) == 0) { src->addFmt("// skipped unused attribute for r{}" _CRLF, registerIndex); return; } LatteDecompiler_emitAttributeDecodeGLSL(shaderContext->shader, src, &attrib); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt("{} = ivec4(", _getRegisterVarName(shaderContext, registerIndex)); else src->addFmt("{} = vec4(", _getRegisterVarName(shaderContext, registerIndex)); for (sint32 f = 0; f < 4; f++) { uint8 ds = attrib.ds[f]; if (f > 0) src->add(", "); _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); if (ds >= 6) { cemu_assert_unimplemented(); ds = 4; // read as 0.0 } if (attrib.nfa != 1) { src->add(dsMappingTableFloat[ds]); } else { src->add(dsMappingTableInt[ds]); } _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); } src->add(");" _CRLF); } void LatteDecompiler_emitGLSLShader(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader) { StringBuf* src = new StringBuf(1024*1024*12); // reserve 12MB for generated source (we resize-to-fit at the end) shaderContext->shaderSource = src; // GLSL shader header src->add("#version 430" _CRLF); // 430 is required for shader storage (Vulkan alternative TF path) src->add("#extension GL_ARB_texture_gather : enable" _CRLF); src->add("#extension GL_ARB_separate_shader_objects : enable" _CRLF); if (shaderContext->analyzer.hasStreamoutWrite || shaderContext->options->usesGeometryShader ) src->add("#extension GL_ARB_enhanced_layouts : enable" _CRLF); // debug info src->addFmt("// shader {:016x}" _CRLF, shaderContext->shaderBaseHash); #ifdef CEMU_DEBUG_ASSERT src->addFmt("// usesIntegerValues: {}" _CRLF, shaderContext->analyzer.usesIntegerValues?"true":"false"); src->addFmt(_CRLF); #endif // header part (definitions for inputs and outputs) LatteDecompiler::emitHeader(shaderContext); // helper functions LatteDecompiler_emitGLSLHelperFunctions(shaderContext, src); // start of main src->add("void main()" _CRLF); src->add("{" _CRLF); // variable definition if (shaderContext->typeTracker.useArrayGPRs == false) { // each register is a separate variable for (sint32 i = 0; i < 128; i++) { if (shaderContext->analyzer.usesRelativeGPRRead || (shaderContext->analyzer.gprUseMask[i / 8] & (1 << (i & 7))) != 0) { if (shaderContext->typeTracker.genIntReg) src->addFmt("ivec4 R{}i = ivec4(0);" _CRLF, i); else if (shaderContext->typeTracker.genFloatReg) src->addFmt("vec4 R{}f = vec4(0.0);" _CRLF, i); } } } else { // registers are represented using a single large array if (shaderContext->typeTracker.genIntReg) src->addFmt("ivec4 Ri[128];" _CRLF); else if (shaderContext->typeTracker.genFloatReg) src->addFmt("vec4 Rf[128];" _CRLF); for (sint32 i = 0; i < 128; i++) { if (shaderContext->typeTracker.genIntReg) src->addFmt("Ri[{}] = ivec4(0);" _CRLF, i); else if (shaderContext->typeTracker.genFloatReg) src->addFmt("Rf[{}] = vec4(0.0);" _CRLF, i); } } if( shader->shaderType == LatteConst::ShaderType::Vertex ) src->addFmt("uvec4 attrDecoder;" _CRLF); if (shaderContext->typeTracker.genIntReg) src->addFmt("int backupReg0i, backupReg1i, backupReg2i, backupReg3i, backupReg4i;" _CRLF); if (shaderContext->typeTracker.genFloatReg) src->addFmt("float backupReg0f, backupReg1f, backupReg2f, backupReg3f, backupReg4f;" _CRLF); if (shaderContext->typeTracker.genIntReg) { src->addFmt("int PV0ix = 0, PV0iy = 0, PV0iz = 0, PV0iw = 0, PV1ix = 0, PV1iy = 0, PV1iz = 0, PV1iw = 0;" _CRLF); src->addFmt("int PS0i = 0, PS1i = 0;" _CRLF); src->addFmt("ivec4 tempi = ivec4(0);" _CRLF); } if (shaderContext->typeTracker.genFloatReg) { src->addFmt("float PV0fx = 0.0, PV0fy = 0.0, PV0fz = 0.0, PV0fw = 0.0, PV1fx = 0.0, PV1fy = 0.0, PV1fz = 0.0, PV1fw = 0.0;" _CRLF); src->addFmt("float PS0f = 0.0, PS1f = 0.0;" _CRLF); src->addFmt("vec4 tempf = vec4(0.0);" _CRLF); } if (shaderContext->analyzer.hasGradientLookup) { src->add("vec4 gradH;" _CRLF); src->add("vec4 gradV;" _CRLF); } src->add("float tempResultf;" _CRLF); src->add("int tempResulti;" _CRLF); src->add("ivec4 ARi = ivec4(0);" _CRLF); src->add("bool predResult = true;" _CRLF); if(shaderContext->analyzer.modifiesPixelActiveState ) { src->addFmt("bool activeMaskStack[{}];" _CRLF, shaderContext->analyzer.activeStackMaxDepth+1); src->addFmt("bool activeMaskStackC[{}];" _CRLF, shaderContext->analyzer.activeStackMaxDepth+2); for (sint32 i = 0; i < shaderContext->analyzer.activeStackMaxDepth; i++) { src->addFmt("activeMaskStack[{}] = false;" _CRLF, i); } for (sint32 i = 0; i < shaderContext->analyzer.activeStackMaxDepth+1; i++) { src->addFmt("activeMaskStackC[{}] = false;" _CRLF, i); } src->addFmt("activeMaskStack[0] = true;" _CRLF); src->addFmt("activeMaskStackC[0] = true;" _CRLF); src->addFmt("activeMaskStackC[1] = true;" _CRLF); // generate vars for each subroutine for (auto& subroutineInfo : shaderContext->list_subroutines) { sint32 subroutineMaxStackDepth = 0; src->addFmt("bool activeMaskStackSub{:04x}[{}];" _CRLF, subroutineInfo.cfAddr, subroutineMaxStackDepth + 1); src->addFmt("bool activeMaskStackCSub{:04x}[{}];" _CRLF, subroutineInfo.cfAddr, subroutineMaxStackDepth + 2); } } // helper variables for cube maps (todo: Only emit when used) if (shaderContext->analyzer.hasRedcCUBE) { src->add("vec3 cubeMapSTM;" _CRLF); src->add("int cubeMapFaceId;" _CRLF); } for(sint32 i=0; i<LATTE_NUM_MAX_TEX_UNITS; i++) { if(!shaderContext->output->textureUnitMask[i]) continue; if( shader->textureUnitDim[i] != Latte::E_DIM::DIM_CUBEMAP ) continue; src->addFmt("float cubeMapArrayIndex{} = 0.0;" _CRLF, i); } // init base offset for streamout buffer writes if (shaderContext->analyzer.useSSBOForStreamout && (shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Geometry)) { for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if(!shaderContext->output->streamoutBufferWriteMask[i]) continue; cemu_assert_debug((shaderContext->output->streamoutBufferStride[i]&3) == 0); if (shader->shaderType == LatteConst::ShaderType::Vertex) // vertex shader src->addFmt("int sbBase{} = uf_streamoutBufferBase{}/4 + (gl_VertexID + uf_verticesPerInstance * gl_InstanceID)*{};" _CRLF, i, i, shaderContext->output->streamoutBufferStride[i] / 4); else // geometry shader { uint32 gsOutPrimType = shaderContext->contextRegisters[mmVGT_GS_OUT_PRIM_TYPE]; uint32 bytesPerVertex = shaderContext->contextRegisters[mmSQ_GS_VERT_ITEMSIZE] * 4; uint32 maxVerticesInGS = ((shaderContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF) * 4) / bytesPerVertex; cemu_assert_debug(gsOutPrimType == 0); // currently we only properly handle GS output primitive points src->addFmt("int sbBase{} = uf_streamoutBufferBase{}/4 + (gl_PrimitiveIDIn * {})*{};" _CRLF, i, i, maxVerticesInGS, shaderContext->output->streamoutBufferStride[i] / 4); } } } // code to load inputs from previous stage if( shader->shaderType == LatteConst::ShaderType::Vertex ) { if( (shaderContext->analyzer.gprUseMask[0/8]&(1<<(0%8))) != 0 ) { if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt("{} = ivec4(gl_VertexID, 0, 0, gl_InstanceID);" _CRLF, _getRegisterVarName(shaderContext, 0)); else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{} = floatBitsToInt(ivec4(gl_VertexID, 0, 0, gl_InstanceID));" _CRLF, _getRegisterVarName(shaderContext, 0)); else cemu_assert_unimplemented(); } LatteFetchShader* parsedFetchShader = shaderContext->fetchShader; for(auto& bufferGroup : parsedFetchShader->bufferGroups) { for(sint32 i=0; i<bufferGroup.attribCount; i++) LatteDecompiler_emitAttributeImport(shaderContext, bufferGroup.attrib[i]); } for (auto& bufferGroup : parsedFetchShader->bufferGroupsInvalid) { // these attributes point to non-existent buffers // todo - figure out how the hardware actually handles this, currently we assume the input values are zero for (sint32 i = 0; i < bufferGroup.attribCount; i++) LatteDecompiler_emitAttributeImport(shaderContext, bufferGroup.attrib[i]); } } else if (shader->shaderType == LatteConst::ShaderType::Pixel) { LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); uint32 psControl0 = shaderContext->contextRegisters[mmSPI_PS_IN_CONTROL_0]; uint32 psControl1 = shaderContext->contextRegisters[mmSPI_PS_IN_CONTROL_1]; uint32 spiInterpControl = shaderContext->contextRegisters[mmSPI_INTERP_CONTROL_0]; uint8 spriteEnable = (spiInterpControl >> 1) & 1; cemu_assert_debug(spriteEnable == 0); uint8 frontFace_enabled = (psControl1 >> 8) & 1; uint8 frontFace_chan = (psControl1 >> 9) & 3; uint8 frontFace_allBits = (psControl1 >> 11) & 1; uint8 frontFace_regIndex = (psControl1 >> 12) & 0x1F; // handle param_gen if (psInputTable->paramGen != 0) { cemu_assert_debug((psInputTable->paramGen) == 1); // handle the other bits (the same set of coordinates with different perspective/projection settings?) uint32 paramGenGPRIndex = psInputTable->paramGenGPR; if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{} = gl_PointCoord.xyxy;" _CRLF, _getRegisterVarName(shaderContext, paramGenGPRIndex)); else src->addFmt("{} = floatBitsToInt(gl_PointCoord.xyxy);" _CRLF, _getRegisterVarName(shaderContext, paramGenGPRIndex)); } for (sint32 i = 0; i < psInputTable->count; i++) { uint32 psControl0 = shaderContext->contextRegisters[mmSPI_PS_IN_CONTROL_0]; uint32 spi0_paramGen = (psControl0 >> 15) & 0xF; sint32 gprIndex = i;// +spi0_paramGen + paramRegOffset; if ((shaderContext->analyzer.gprUseMask[gprIndex / 8] & (1 << (gprIndex % 8))) == 0 && shaderContext->analyzer.usesRelativeGPRRead == false) continue; uint32 psInputSemanticId = psInputTable->import[i].semanticId; if (psInputSemanticId == LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION) { if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{} = GET_FRAGCOORD();" _CRLF, _getRegisterVarName(shaderContext, gprIndex)); else src->addFmt("{} = floatBitsToInt(GET_FRAGCOORD());" _CRLF, _getRegisterVarName(shaderContext, gprIndex)); continue; } if (shaderContext->options->usesGeometryShader) { // import from geometry shader if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt("{} = floatBitsToInt(passG2PParameter{});" _CRLF, _getRegisterVarName(shaderContext, gprIndex), psInputSemanticId & 0x7F); else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{} = passG2PParameter{};" _CRLF, _getRegisterVarName(shaderContext, gprIndex), psInputSemanticId & 0x7F); else cemu_assert_unimplemented(); } else { // import from vertex shader if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt("{} = floatBitsToInt(passParameterSem{});" _CRLF, _getRegisterVarName(shaderContext, gprIndex), psInputSemanticId); else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{} = passParameterSem{};" _CRLF, _getRegisterVarName(shaderContext, gprIndex), psInputSemanticId); else cemu_assert_unimplemented(); } } // front facing attribute if (frontFace_enabled) { if ((shaderContext->analyzer.gprUseMask[0 / 8] & (1 << (0 % 8))) != 0) { if (frontFace_allBits) cemu_assert_debug(false); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt("{}.{} = floatBitsToInt(gl_FrontFacing?1.0:0.0);" _CRLF, _getRegisterVarName(shaderContext, frontFace_regIndex), _getElementStrByIndex(frontFace_chan)); else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{}.{} = gl_FrontFacing?1.0:0.0;" _CRLF, _getRegisterVarName(shaderContext, frontFace_regIndex), _getElementStrByIndex(frontFace_chan)); else cemu_assert_debug(false); } } } for(auto& cfInstruction : shaderContext->cfInstructions) LatteDecompiler_emitClauseCode(shaderContext, &cfInstruction, false); if( shader->shaderType == LatteConst::ShaderType::Geometry ) src->add("EndPrimitive();" _CRLF); // vertex shader should write renderstate point size at the end if required but not modified by shader if (shaderContext->analyzer.outputPointSize && shaderContext->analyzer.writesPointSize == false) { if (shader->shaderType == LatteConst::ShaderType::Vertex && shaderContext->options->usesGeometryShader == false) src->add("gl_PointSize = uf_pointSize;" _CRLF); } // end of shader main src->add("}" _CRLF); src->shrink_to_fit(); shader->strBuf_shaderSource = src; } ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "util/helpers/StringBuf.h" #define _CRLF "\r\n" void _readLittleEndianAttributeU32x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = attrDataSem{};" _CRLF, attributeInputIndex); } void _readLittleEndianAttributeU32x3(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = uvec4(attrDataSem{}.xyz,0);" _CRLF, attributeInputIndex); } void _readLittleEndianAttributeU32x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = uvec4(attrDataSem{}.xy,0,0);" _CRLF, attributeInputIndex); } void _readLittleEndianAttributeU32x1(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = uvec4(attrDataSem{}.x,0,0,0);" _CRLF, attributeInputIndex); } void _readLittleEndianAttributeU16x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = uvec4(attrDataSem{}.xy,0,0);" _CRLF, attributeInputIndex); } void _readLittleEndianAttributeU16x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = attrDataSem{};" _CRLF, attributeInputIndex); } void _readBigEndianAttributeU32x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = attrDataSem{};" _CRLF, attributeInputIndex); src->add("attrDecoder = (attrDecoder>>24)|((attrDecoder>>8)&0xFF00)|((attrDecoder<<8)&0xFF0000)|((attrDecoder<<24));" _CRLF); } void _readBigEndianAttributeU32x3(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.xyz = attrDataSem{}.xyz;" _CRLF, attributeInputIndex); src->add("attrDecoder.xyz = (attrDecoder.xyz>>24)|((attrDecoder.xyz>>8)&0xFF00)|((attrDecoder.xyz<<8)&0xFF0000)|((attrDecoder.xyz<<24));" _CRLF); src->add("attrDecoder.w = 0;" _CRLF); } void _readBigEndianAttributeU32x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.xy = attrDataSem{}.xy;" _CRLF, attributeInputIndex); src->add("attrDecoder.xy = (attrDecoder.xy>>24)|((attrDecoder.xy>>8)&0xFF00)|((attrDecoder.xy<<8)&0xFF0000)|((attrDecoder.xy<<24));" _CRLF); src->add("attrDecoder.z = 0;" _CRLF); src->add("attrDecoder.w = 0;" _CRLF); } void _readBigEndianAttributeU32x1(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.x = attrDataSem{}.x;" _CRLF, attributeInputIndex); src->add("attrDecoder.x = (attrDecoder.x>>24)|((attrDecoder.x>>8)&0xFF00)|((attrDecoder.x<<8)&0xFF0000)|((attrDecoder.x<<24));" _CRLF); src->add("attrDecoder.y = 0;" _CRLF); src->add("attrDecoder.z = 0;" _CRLF); src->add("attrDecoder.w = 0;" _CRLF); } void _readBigEndianAttributeU16x1(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.xy = attrDataSem{}.xy;" _CRLF, attributeInputIndex); src->add("attrDecoder.x = ((attrDecoder.x>>8)&0xFF)|((attrDecoder.x<<8)&0xFF00);" _CRLF); src->add("attrDecoder.y = 0;" _CRLF); src->add("attrDecoder.z = 0;" _CRLF); src->add("attrDecoder.w = 0;" _CRLF); } void _readBigEndianAttributeU16x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.xy = attrDataSem{}.xy;" _CRLF, attributeInputIndex); src->add("attrDecoder.xy = ((attrDecoder.xy>>8)&0xFF)|((attrDecoder.xy<<8)&0xFF00);" _CRLF); src->add("attrDecoder.z = 0;" _CRLF); src->add("attrDecoder.w = 0;" _CRLF); } void _readBigEndianAttributeU16x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.xyzw = attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); src->add("attrDecoder = ((attrDecoder>>8)&0xFF)|((attrDecoder<<8)&0xFF00);" _CRLF); } void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContext, StringBuf* src, LatteParsedFetchShaderAttribute_t* attrib) { if (attrib->attributeBufferIndex >= Latte::GPU_LIMITS::NUM_VERTEX_BUFFERS) { src->add("attrDecoder = ivec4(0);" _CRLF); return; } uint32 attributeInputIndex = attrib->semanticId; if( attrib->endianSwap == LatteConst::VertexFetchEndianMode::SWAP_U32 ) { if( attrib->format == FMT_32_32_32_32_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU32x4(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_32_32_32_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU32x3(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_32_32_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU32x2(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_32_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_2_10_10_10 && attrib->nfa == 0 ) { _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); // Bayonetta 2 uses this format to store normals src->add("attrDecoder.xyzw = uvec4((attrDecoder.x>>0)&0x3FF,(attrDecoder.x>>10)&0x3FF,(attrDecoder.x>>20)&0x3FF,(attrDecoder.x>>30)&0x3);" _CRLF); if (attrib->isSigned != 0) { src->add("if( (attrDecoder.x&0x200) != 0 ) attrDecoder.x |= 0xFFFFFC00;" _CRLF); src->add("if( (attrDecoder.y&0x200) != 0 ) attrDecoder.y |= 0xFFFFFC00;" _CRLF); src->add("if( (attrDecoder.z&0x200) != 0 ) attrDecoder.z |= 0xFFFFFC00;" _CRLF); src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/511.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/511.0,-1.0));" _CRLF); src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/511.0,-1.0));" _CRLF); } else { src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/1023.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/1023.0,-1.0));" _CRLF); src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/1023.0,-1.0));" _CRLF); } src->add("attrDecoder.w = floatBitsToUint(float(attrDecoder.w));" _CRLF); // unsure? } else if( attrib->format == FMT_32_32_32_32 && attrib->nfa == 1 && attrib->isSigned == 0 ) { _readBigEndianAttributeU32x4(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_32_32_32 && attrib->nfa == 1 && attrib->isSigned == 0 ) { _readBigEndianAttributeU32x3(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_32_32 && attrib->nfa == 1 && attrib->isSigned == 0 ) { _readBigEndianAttributeU32x2(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_32 && attrib->nfa == 1 && attrib->isSigned == 0) { _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_32 && attrib->nfa == 1 && attrib->isSigned == 1) { // we can just read the signed s32 as a u32 since no sign-extension is necessary _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned == 0 ) { // seen in Minecraft Wii U Edition src->addFmt("attrDecoder.xyzw = floatBitsToUint(vec4(attrDataSem{}.wzyx)/255.0);" _CRLF, attributeInputIndex); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned != 0 ) { // seen in Minecraft Wii U Edition src->addFmt("attrDecoder.xyzw = attrDataSem{}.wzyx;" _CRLF, attributeInputIndex); src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.z&0x80) != 0 ) attrDecoder.z |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.w&0x80) != 0 ) attrDecoder.w |= 0xFFFFFF00;" _CRLF); src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/127.0,-1.0));" _CRLF); src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/127.0,-1.0));" _CRLF); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 1 && attrib->isSigned == 0 ) { // seen in Minecraft Wii U Edition src->addFmt("attrDecoder.xyzw = attrDataSem{}.wzyx;" _CRLF, attributeInputIndex); } else if (attrib->format == FMT_8_8_8_8 && attrib->nfa == 2 && attrib->isSigned == 0) { // seen in Ben 10 Omniverse src->addFmt("attrDecoder.xyzw = floatBitsToUint(vec4(attrDataSem{}.wzyx));" _CRLF, attributeInputIndex); } else { cemuLog_log(LogType::Force, "_emitAttributeDecodeGLSL(): Unsupported fmt {:02x} nfa {} signed {} endian {}\n", attrib->format, attrib->nfa, attrib->isSigned, attrib->endianSwap); cemu_assert_unimplemented(); } } else if( attrib->endianSwap == LatteConst::VertexFetchEndianMode::SWAP_NONE ) { if( attrib->format == FMT_32_32_32_32_FLOAT && attrib->nfa == 2 ) { _readLittleEndianAttributeU32x4(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_32_32_32_FLOAT && attrib->nfa == 2) { _readLittleEndianAttributeU32x3(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_32_32_FLOAT && attrib->nfa == 2) { // seen in Cities of Gold _readLittleEndianAttributeU32x2(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_32 && attrib->nfa == 1 && attrib->isSigned == 0) { // seen in Nano Assault Neo _readLittleEndianAttributeU32x1(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_2_10_10_10 && attrib->nfa == 0 && attrib->isSigned == 0) { // seen in Fast Racing Neo _readLittleEndianAttributeU32x1(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xyzw = uvec4((attrDecoder.x>>0)&0x3FF,(attrDecoder.x>>10)&0x3FF,(attrDecoder.x>>20)&0x3FF,(attrDecoder.x>>30)&0x3);" _CRLF); src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/1023.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/1023.0,-1.0));" _CRLF); src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/1023.0,-1.0));" _CRLF); src->add("attrDecoder.w = floatBitsToUint(float(attrDecoder.w));" _CRLF); // todo - is this correct? } else if (attrib->format == FMT_16_16_16_16 && attrib->nfa == 0 && attrib->isSigned != 0) { // seen in CoD ghosts _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/32767.0,-1.0));" _CRLF); } else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 2 && attrib->isSigned == 1 ) { // seen in Rabbids Land _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.xyzw = floatBitsToUint(vec4(ivec4(attrDecoder)));" _CRLF); } else if (attrib->format == FMT_16_16_16_16_FLOAT && attrib->nfa == 2) { // seen in Giana Sisters: Twisted Dreams _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xyzw = floatBitsToInt(vec4(unpackHalf2x16(attrDecoder.x|(attrDecoder.y<<16)),unpackHalf2x16(attrDecoder.z|(attrDecoder.w<<16))));" _CRLF); } else if (attrib->format == FMT_16_16 && attrib->nfa == 0 && attrib->isSigned != 0) { // seen in Nano Assault Neo _readLittleEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); } else if (attrib->format == FMT_16_16_FLOAT && attrib->nfa == 2) { // seen in Giana Sisters: Twisted Dreams _readLittleEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xy = floatBitsToUint(unpackHalf2x16(attrDecoder.x|(attrDecoder.y<<16)));" _CRLF); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned == 0 ) { src->addFmt("attrDecoder.xyzw = floatBitsToUint(vec4(attrDataSem{}.xyzw)/255.0);" _CRLF, attributeInputIndex); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned != 0 ) { src->addFmt("attrDecoder.xyzw = attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.z&0x80) != 0 ) attrDecoder.z |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.w&0x80) != 0 ) attrDecoder.w |= 0xFFFFFF00;" _CRLF); src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/127.0,-1.0));" _CRLF); src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/127.0,-1.0));" _CRLF); } else if (attrib->format == FMT_8_8_8_8 && attrib->nfa == 1 && attrib->isSigned == 0) { src->addFmt("attrDecoder.xyzw = attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); } else if (attrib->format == FMT_8_8_8_8 && attrib->nfa == 1 && attrib->isSigned != 0) { // seen in Sonic Lost World src->addFmt("attrDecoder.xyzw = attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.z&0x80) != 0 ) attrDecoder.z |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.w&0x80) != 0 ) attrDecoder.w |= 0xFFFFFF00;" _CRLF); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 2 && attrib->isSigned == 0 ) { // seen in One Piece src->addFmt("attrDecoder.xyzw = floatBitsToInt(vec4(attrDataSem{}.xyzw));" _CRLF, attributeInputIndex); } else if (attrib->format == FMT_8_8 && attrib->nfa == 0 && attrib->isSigned == 0) { if( (attrib->offset&3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL ) { // AMD workaround src->addFmt("attrDecoder.xy = floatBitsToUint(vec2(attrDataSem{}.zw)/255.0);" _CRLF, attributeInputIndex); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } else { src->addFmt("attrDecoder.xy = floatBitsToUint(vec2(attrDataSem{}.xy)/255.0);" _CRLF, attributeInputIndex); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } } else if (attrib->format == FMT_8_8 && attrib->nfa == 2 && attrib->isSigned == 0) { // seen in BotW if ((attrib->offset & 3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL) { // AMD workaround src->addFmt("attrDecoder.xy = floatBitsToUint(vec2(attrDataSem{}.zw));" _CRLF, attributeInputIndex); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } else { src->addFmt("attrDecoder.xy = floatBitsToUint(vec2(attrDataSem{}.xy));" _CRLF, attributeInputIndex); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } } else if (attrib->format == FMT_8_8 && attrib->nfa == 0 && attrib->isSigned != 0) { if ((attrib->offset & 3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL) { // AMD workaround src->addFmt("attrDecoder.xy = attrDataSem{}.zw;" _CRLF, attributeInputIndex); src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } else { src->addFmt("attrDecoder.xy = attrDataSem{}.xy;" _CRLF, attributeInputIndex); src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } } else if (attrib->format == FMT_8_8 && attrib->nfa == 1 && attrib->isSigned == 0) { if ((attrib->offset & 3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL) { // AMD workaround src->addFmt("attrDecoder.xyzw = uvec4(attrDataSem{}.zw,0,0);" _CRLF, attributeInputIndex); } else { src->addFmt("attrDecoder.xyzw = uvec4(attrDataSem{}.xy,0,0);" _CRLF, attributeInputIndex); } } else if( attrib->format == FMT_8 && attrib->nfa == 0 && attrib->isSigned == 0 ) { // seen in Pikmin 3 src->addFmt("attrDecoder.x = floatBitsToUint(float(attrDataSem{}.x)/255.0);" _CRLF, attributeInputIndex); src->add("attrDecoder.yzw = uvec3(0);" _CRLF); } else if( attrib->format == FMT_8 && attrib->nfa == 1 && attrib->isSigned == 0 ) { src->addFmt("attrDecoder.xyzw = uvec4(attrDataSem{}.x,0,0,0);" _CRLF, attributeInputIndex); } else { cemuLog_log(LogType::Force, "_emitAttributeDecodeGLSL(): Unsupported fmt {:02x} nfa {} signed {} endian {}\n", attrib->format, attrib->nfa, attrib->isSigned, attrib->endianSwap); cemu_assert_debug(false); } } else if( attrib->endianSwap == LatteConst::VertexFetchEndianMode::SWAP_U16 ) { if( attrib->format == FMT_16_16_16_16_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xyzw = floatBitsToInt(vec4(unpackHalf2x16(attrDecoder.x|(attrDecoder.y<<16)),unpackHalf2x16(attrDecoder.z|(attrDecoder.w<<16))));" _CRLF); } else if (attrib->format == FMT_16_16_16_16 && attrib->nfa == 0 && attrib->isSigned != 0) { _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/32767.0,-1.0));" _CRLF); } else if (attrib->format == FMT_16_16_16_16 && attrib->nfa == 0 && attrib->isSigned == 0) { // seen in BotW _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("attrDecoder.x = floatBitsToUint(float(int(attrDecoder.x))/65535.0);" _CRLF); src->add("attrDecoder.y = floatBitsToUint(float(int(attrDecoder.y))/65535.0);" _CRLF); src->add("attrDecoder.z = floatBitsToUint(float(int(attrDecoder.z))/65535.0);" _CRLF); src->add("attrDecoder.w = floatBitsToUint(float(int(attrDecoder.w))/65535.0);" _CRLF); } else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 2 && attrib->isSigned != 0 ) { // seen in Minecraft Wii U Edition _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.x = floatBitsToUint(float(int(attrDecoder.x)));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(float(int(attrDecoder.y)));" _CRLF); src->add("attrDecoder.z = floatBitsToUint(float(int(attrDecoder.z)));" _CRLF); src->add("attrDecoder.w = floatBitsToUint(float(int(attrDecoder.w)));" _CRLF); } else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 1 && attrib->isSigned != 0 ) { // seen in Minecraft Wii U Edition _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); } else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 1 && attrib->isSigned == 0 ) { _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_16_16_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xy = floatBitsToUint(unpackHalf2x16(attrDecoder.x|(attrDecoder.y<<16)));" _CRLF); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } else if( attrib->format == FMT_16_16 && attrib->nfa == 0 && attrib->isSigned == 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xy = floatBitsToUint(vec2(float(attrDecoder.x), float(attrDecoder.y))/65535.0);" _CRLF); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } else if( attrib->format == FMT_16_16 && attrib->nfa == 0 && attrib->isSigned != 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } else if( attrib->format == FMT_16_16 && attrib->nfa == 1 && attrib->isSigned == 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_16_16 && attrib->nfa == 1 && attrib->isSigned != 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } else if( attrib->format == FMT_16_16 && attrib->nfa == 2 && attrib->isSigned == 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xy = floatBitsToUint(vec2(float(attrDecoder.x), float(attrDecoder.y)));" _CRLF); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } else if( attrib->format == FMT_16_16 && attrib->nfa == 2 && attrib->isSigned != 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.xy = floatBitsToUint(vec2(float(int(attrDecoder.x)), float(int(attrDecoder.y))));" _CRLF); src->add("attrDecoder.zw = uvec2(0);" _CRLF); } else if (attrib->format == FMT_16 && attrib->nfa == 1 && attrib->isSigned == 0) { _readBigEndianAttributeU16x1(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_16 && attrib->nfa == 0 && attrib->isSigned == 0) { // seen in CoD ghosts _readBigEndianAttributeU16x1(shaderContext, src, attributeInputIndex); src->add("attrDecoder.x = floatBitsToUint(float(int(attrDecoder.x))/65535.0);" _CRLF); } else { cemuLog_logDebug(LogType::Force, "_emitAttributeDecodeGLSL(): Unsupported fmt {:02x} nfa {} signed {} endian {}", attrib->format, attrib->nfa, attrib->isSigned, attrib->endianSwap); } } else { cemu_assert_debug(false); } } ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp ================================================ #pragma once namespace LatteDecompiler { void _emitUniformVariables(LatteDecompilerShaderContext* decompilerContext, RendererAPI rendererType, LatteDecompilerOutputUniformOffsets& uniformOffsets) { LatteDecompilerShaderResourceMapping& resourceMapping = (rendererType == RendererAPI::Vulkan) ? decompilerContext->output->resourceMappingVK : decompilerContext->output->resourceMappingGL; if (rendererType == RendererAPI::Vulkan) { // for Vulkan uniform vars are in a uniform buffer if (decompilerContext->hasUniformVarBlock) { cemu_assert_debug(resourceMapping.uniformVarsBufferBindingPoint >= 0); decompilerContext->shaderSource->addFmt("layout(set = {}, binding = {}) uniform ufBlock" _CRLF "{{" _CRLF, (sint32)resourceMapping.setIndex, (sint32)resourceMapping.uniformVarsBufferBindingPoint); } } uint32 uniformCurrentOffset = 0; auto shader = decompilerContext->shader; auto shaderType = decompilerContext->shader->shaderType; auto shaderSrc = decompilerContext->shaderSource; if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) { // uniform registers or buffers are accessed statically with predictable offsets // this allows us to remap the used entries into a more compact array if (shaderType == LatteConst::ShaderType::Vertex) shaderSrc->addFmt("uniform ivec4 uf_remappedVS[{}];" _CRLF, (sint32)shader->list_remappedUniformEntries.size()); else if (shaderType == LatteConst::ShaderType::Pixel) shaderSrc->addFmt("uniform ivec4 uf_remappedPS[{}];" _CRLF, (sint32)shader->list_remappedUniformEntries.size()); else if (shaderType == LatteConst::ShaderType::Geometry) shaderSrc->addFmt("uniform ivec4 uf_remappedGS[{}];" _CRLF, (sint32)shader->list_remappedUniformEntries.size()); else debugBreakpoint(); uniformOffsets.offset_remapped = uniformCurrentOffset; uniformCurrentOffset += 16 * shader->list_remappedUniformEntries.size(); } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) { uint32 cfileSize = decompilerContext->analyzer.uniformRegisterAccessTracker.DetermineSize(decompilerContext->shaderBaseHash, 256); // full or partial uniform register file has to be present if (shaderType == LatteConst::ShaderType::Vertex) shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterVS[{}];" _CRLF, cfileSize); else if (shaderType == LatteConst::ShaderType::Pixel) shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterPS[{}];" _CRLF, cfileSize); else if (shaderType == LatteConst::ShaderType::Geometry) shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterGS[{}];" _CRLF, cfileSize); uniformOffsets.offset_uniformRegister = uniformCurrentOffset; uniformOffsets.count_uniformRegister = cfileSize; uniformCurrentOffset += 16 * cfileSize; } // special uniforms bool hasAnyViewportScaleDisabled = !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_SCALE_ENA() || !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_SCALE_ENA() || !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_SCALE_ENA(); if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && hasAnyViewportScaleDisabled) { // aka GX2 special state 0 uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; shaderSrc->add("uniform vec2 uf_windowSpaceToClipSpaceTransform;" _CRLF); uniformOffsets.offset_windowSpaceToClipSpaceTransform = uniformCurrentOffset; uniformCurrentOffset += 8; } bool alphaTestEnable = decompilerContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_TEST_ENABLE(); if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && alphaTestEnable) { uniformCurrentOffset = (uniformCurrentOffset + 3)&~3; shaderSrc->add("uniform float uf_alphaTestRef;" _CRLF); uniformOffsets.offset_alphaTestRef = uniformCurrentOffset; uniformCurrentOffset += 4; } if (decompilerContext->analyzer.outputPointSize && decompilerContext->analyzer.writesPointSize == false) { if ((decompilerContext->shaderType == LatteConst::ShaderType::Vertex && !decompilerContext->options->usesGeometryShader) || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { uniformCurrentOffset = (uniformCurrentOffset + 3)&~3; shaderSrc->add("uniform float uf_pointSize;" _CRLF); uniformOffsets.offset_pointSize = uniformCurrentOffset; uniformCurrentOffset += 4; } } // define uf_fragCoordScale which holds the xy scale for render target resolution vs effective resolution if (shader->shaderType == LatteConst::ShaderType::Pixel) { if (rendererType == RendererAPI::OpenGL) { uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; shaderSrc->add("uniform vec2 uf_fragCoordScale;" _CRLF); uniformOffsets.offset_fragCoordScale = uniformCurrentOffset; uniformCurrentOffset += 8; } else { // in Vulkan uf_fragCoordScale stores the origin in zw uniformCurrentOffset = (uniformCurrentOffset + 15)&~15; shaderSrc->add("uniform vec4 uf_fragCoordScale;" _CRLF); uniformOffsets.offset_fragCoordScale = uniformCurrentOffset; uniformCurrentOffset += 16; } } // provide scale factor for every texture that is accessed via texel coordinates (texelFetch) for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++) { if (decompilerContext->analyzer.texUnitUsesTexelCoordinates.test(t) == false) continue; uniformCurrentOffset = (uniformCurrentOffset + 7) & ~7; shaderSrc->addFmt("uniform vec2 uf_tex{}Scale;" _CRLF, t); uniformOffsets.offset_texScale[t] = uniformCurrentOffset; uniformCurrentOffset += 8; } // define uf_verticesPerInstance + uf_streamoutBufferBaseX if (decompilerContext->analyzer.useSSBOForStreamout && (shader->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->options->usesGeometryShader == false) || (shader->shaderType == LatteConst::ShaderType::Geometry) ) { shaderSrc->add("uniform int uf_verticesPerInstance;" _CRLF); uniformOffsets.offset_verticesPerInstance = uniformCurrentOffset; uniformCurrentOffset += 4; for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if (decompilerContext->output->streamoutBufferWriteMask[i]) { shaderSrc->addFmt("uniform int uf_streamoutBufferBase{};" _CRLF, i); uniformOffsets.offset_streamoutBufferBase[i] = uniformCurrentOffset; uniformCurrentOffset += 4; } } } uniformOffsets.offset_endOfBlock = uniformCurrentOffset; if (rendererType == RendererAPI::Vulkan) { if (decompilerContext->hasUniformVarBlock) shaderSrc->add("};" _CRLF); // end of push-constant block } } void _emitUniformBuffers(LatteDecompilerShaderContext* decompilerContext) { auto shaderSrc = decompilerContext->shaderSource; // uniform buffer definition if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if (!decompilerContext->analyzer.uniformBufferAccessTracker[i].HasAccess()) continue; cemu_assert_debug(decompilerContext->output->resourceMappingGL.uniformBuffersBindingPoint[i] >= 0); cemu_assert_debug(decompilerContext->output->resourceMappingVK.uniformBuffersBindingPoint[i] >= 0); shaderSrc->addFmt("UNIFORM_BUFFER_LAYOUT({}, {}, {}) ", (sint32)decompilerContext->output->resourceMappingGL.uniformBuffersBindingPoint[i], (sint32)decompilerContext->output->resourceMappingVK.setIndex, (sint32)decompilerContext->output->resourceMappingVK.uniformBuffersBindingPoint[i]); shaderSrc->addFmt("uniform {}{}" _CRLF, _getShaderUniformBlockInterfaceName(decompilerContext->shaderType), i); shaderSrc->add("{" _CRLF); shaderSrc->addFmt("vec4 {}{}[{}];" _CRLF, _getShaderUniformBlockVariableName(decompilerContext->shaderType), i, decompilerContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(decompilerContext->shaderBaseHash, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE)); shaderSrc->add("};" _CRLF _CRLF); shaderSrc->add(_CRLF); } } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) { // already generated in _emitUniformVariables } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) { // already generated in _emitUniformVariables } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_NONE) { // no uniforms used } else { cemu_assert_debug(false); } } void _emitTextureDefinitions(LatteDecompilerShaderContext* shaderContext) { auto src = shaderContext->shaderSource; // texture sampler definition for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (!shaderContext->output->textureUnitMask[i]) continue; src->addFmt("TEXTURE_LAYOUT({}, {}, {}) ", (sint32)shaderContext->output->resourceMappingGL.textureUnitToBindingPoint[i], (sint32)shaderContext->output->resourceMappingVK.setIndex, (sint32)shaderContext->output->resourceMappingVK.textureUnitToBindingPoint[i]); src->add("uniform "); if (shaderContext->shader->textureIsIntegerFormat[i]) { // integer samplers if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_1D) src->add("usampler1D"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D || shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D_MSAA) src->add("usampler2D"); else cemu_assert_unimplemented(); } else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D || shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D_MSAA) src->add("sampler2D"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_1D) src->add("sampler1D"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D_ARRAY) src->add("sampler2DArray"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_CUBEMAP) src->add("samplerCubeArray"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_3D) src->add("sampler3D"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_1D) { cemu_assert_unimplemented(); src->add("sampler2D"); } else { cemu_assert_unimplemented(); } if (shaderContext->shader->textureUsesDepthCompare[i]) src->add("Shadow"); // shadow sampler src->addFmt(" {}{};", _getTextureUnitVariablePrefixName(shaderContext->shaderType), i); src->add(_CRLF); } } void _emitAttributes(LatteDecompilerShaderContext* decompilerContext) { auto shaderSrc = decompilerContext->shaderSource; if (decompilerContext->shader->shaderType == LatteConst::ShaderType::Vertex) { // attribute inputs for (uint32 i = 0; i < LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS; i++) { if (decompilerContext->analyzer.inputAttributSemanticMask[i]) { cemu_assert_debug(decompilerContext->output->resourceMappingGL.attributeMapping[i] >= 0); cemu_assert_debug(decompilerContext->output->resourceMappingVK.attributeMapping[i] >= 0); cemu_assert_debug(decompilerContext->output->resourceMappingGL.attributeMapping[i] == decompilerContext->output->resourceMappingVK.attributeMapping[i]); shaderSrc->addFmt("ATTR_LAYOUT({}, {}) in uvec4 attrDataSem{};" _CRLF, (sint32)decompilerContext->output->resourceMappingVK.setIndex, (sint32)decompilerContext->output->resourceMappingVK.attributeMapping[i], i); } } } } void _emitHeaderMacros(LatteDecompilerShaderContext* decompilerContext) { auto src = decompilerContext->shaderSource; // OpenGL/Vulkan ifdefs src->add("#ifdef VULKAN" _CRLF); // Vulkan defines src->add("#define ATTR_LAYOUT(__vkSet, __location) layout(set = __vkSet, location = __location)" _CRLF); src->add("#define UNIFORM_BUFFER_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(set = __vkSet, binding = __vkLocation, std140)" _CRLF); src->add("#define TEXTURE_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(set = __vkSet, binding = __vkLocation)" _CRLF); if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { src->add("#define gl_VertexID gl_VertexIndex" _CRLF); src->add("#define gl_InstanceID gl_InstanceIndex" _CRLF); if (decompilerContext->analyzer.hasStreamoutWrite) src->add("#define XFB_BLOCK_LAYOUT(__bufferIndex, __stride, __location) layout(location = __location, xfb_buffer = __bufferIndex, xfb_stride = __stride, xfb_offset = 0)" _CRLF); if (decompilerContext->contextRegistersNew->PA_CL_CLIP_CNTL.get_DX_CLIP_SPACE_DEF()) { src->add("#define SET_POSITION(_v) gl_Position = _v" _CRLF); } else { src->add("#define SET_POSITION(_v) gl_Position = _v; gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0" _CRLF); } if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { if (decompilerContext->options->usesGeometryShader) src->add("#define V2G_LAYOUT layout(location = 0)" _CRLF); } } else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) { src->add("#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale.xy,gl_FragCoord.z, 1.0/gl_FragCoord.w)" _CRLF); } if (decompilerContext->options->spirvInstrinsics.hasRoundingModeRTEFloat32) { src->add("#extension GL_EXT_spirv_intrinsics: enable" _CRLF); src->add("spirv_execution_mode(capabilities = [4467], extensions = [\"SPV_KHR_float_controls\"], 4462, 16);" _CRLF); src->add("spirv_execution_mode(capabilities = [4467], extensions = [\"SPV_KHR_float_controls\"], 4462, 32);" _CRLF); src->add("spirv_execution_mode(capabilities = [4467], extensions = [\"SPV_KHR_float_controls\"], 4462, 64);" _CRLF); } src->add("#else" _CRLF); // OpenGL defines src->add("#define ATTR_LAYOUT(__vkSet, __location) layout(location = __location)" _CRLF); src->add("#define UNIFORM_BUFFER_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(binding = __glLocation, std140) " _CRLF); src->add("#define TEXTURE_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(binding = __glLocation)" _CRLF); if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { if (decompilerContext->analyzer.hasStreamoutWrite) src->add("#define XFB_BLOCK_LAYOUT(__bufferIndex, __stride, __location) layout(xfb_buffer = __bufferIndex, xfb_stride = __stride)" _CRLF); src->add("#define SET_POSITION(_v) gl_Position = _v\r\n"); if (decompilerContext->options->usesGeometryShader) src->add("#define V2G_LAYOUT" _CRLF); } else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) { src->add("#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale,gl_FragCoord.zw)" _CRLF); } src->add("#endif" _CRLF); // geometry shader input/output primitive info if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { auto drawPrimitiveMode = decompilerContext->contextRegistersNew->VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); const char* gsInputPrimitive = ""; if (drawPrimitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS) gsInputPrimitive = "points"; else if (drawPrimitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLES) gsInputPrimitive = "triangles"; else if (drawPrimitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_STRIP) gsInputPrimitive = "lines_adjacency"; else { debug_printf("drawPrimitiveMode %d\n", drawPrimitiveMode); cemu_assert_debug(false); } // note: The input primitive type is not stored with the geometry shader object (and registers). Therefore we have to rely on whatever primitive mode was set for the draw call. src->addFmt("layout({}) in;" _CRLF, gsInputPrimitive); uint32 gsOutPrimType = decompilerContext->contextRegisters[mmVGT_GS_OUT_PRIM_TYPE]; uint32 bytesPerVertex = decompilerContext->contextRegisters[mmSQ_GS_VERT_ITEMSIZE] * 4; uint32 maxVerticesInGS = ((decompilerContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF) * 4) / bytesPerVertex; if (!decompilerContext->analyzer.hasLoops && decompilerContext->analyzer.numEmitVertex < maxVerticesInGS) maxVerticesInGS = decompilerContext->analyzer.numEmitVertex; src->add("layout ("); if (gsOutPrimType == 0) src->add("points"); else if (gsOutPrimType == 1) src->add("line_strip"); else if (gsOutPrimType == 2) src->add("triangle_strip"); else { cemu_assert_debug(false); } src->addFmt(", max_vertices={}) out;" _CRLF, (sint32)maxVerticesInGS); } } void _emitVSExports(LatteDecompilerShaderContext* shaderContext) { auto* src = shaderContext->shaderSource; LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); auto parameterMask = shaderContext->shader->outputParameterMask; for (uint32 i = 0; i < 32; i++) { if ((parameterMask&(1 << i)) == 0) continue; uint32 vsSemanticId = _getVertexShaderOutParamSemanticId(shaderContext->contextRegisters, i); if (vsSemanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) continue; // get import based on semanticId sint32 psInputIndex = -1; for (sint32 f = 0; f < psInputTable->count; f++) { if (psInputTable->import[f].semanticId == vsSemanticId) { psInputIndex = f; break; } } if (psInputIndex == -1) continue; // no ps input src->addFmt("layout(location = {}) ", psInputIndex); if (psInputTable->import[psInputIndex].isFlat) src->add("flat "); if (psInputTable->import[psInputIndex].isNoPerspective) src->add("noperspective "); src->add("out"); src->addFmt(" vec4 passParameterSem{};" _CRLF, psInputTable->import[psInputIndex].semanticId); } } void _emitPSImports(LatteDecompilerShaderContext* shaderContext) { auto* src = shaderContext->shaderSource; LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); for (sint32 i = 0; i < psInputTable->count; i++) { if (psInputTable->import[i].semanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) continue; src->addFmt("layout(location = {}) ", i); if (psInputTable->import[i].isFlat) src->add("flat "); if (psInputTable->import[i].isNoPerspective) src->add("noperspective "); src->add("in"); src->addFmt(" vec4 passParameterSem{};" _CRLF, psInputTable->import[i].semanticId); } } void _emitMisc(LatteDecompilerShaderContext* decompilerContext) { auto src = decompilerContext->shaderSource; // per-vertex output (VS or GS) if ((decompilerContext->shaderType == LatteConst::ShaderType::Vertex && !decompilerContext->options->usesGeometryShader) || (decompilerContext->shaderType == LatteConst::ShaderType::Geometry)) { src->add("out gl_PerVertex" _CRLF); src->add("{" _CRLF); src->add(" vec4 gl_Position;" _CRLF); if (decompilerContext->analyzer.outputPointSize) src->add(" float gl_PointSize;" _CRLF); src->add("};" _CRLF); } // varyings (variables passed from vertex to pixel shader, only if geometry stage is disabled if (decompilerContext->options->usesGeometryShader == false) { if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) { _emitVSExports(decompilerContext); } else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) { _emitPSImports(decompilerContext); } } else { if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { // parameters shared between vertex shader and geometry shader src->add("V2G_LAYOUT "); if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) src->add("out Vertex" _CRLF); else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) src->add("in Vertex" _CRLF); src->add("{" _CRLF); uint32 ringParameterCountVS2GS = 0; if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) { ringParameterCountVS2GS = decompilerContext->shader->ringParameterCount; } else { ringParameterCountVS2GS = decompilerContext->shader->ringParameterCountFromPrevStage; } for (uint32 f = 0; f < ringParameterCountVS2GS; f++) src->addFmt(" ivec4 passV2GParameter{};" _CRLF, f); if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) src->add("}v2g;" _CRLF); else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) src->add("}v2g[];" _CRLF); } if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { // parameters shared between geometry and pixel shader uint32 ringItemSize = decompilerContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF; if ((ringItemSize & 0xF) != 0) debugBreakpoint(); if (((decompilerContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF) & 0xF) != 0) debugBreakpoint(); for (sint32 p = 0; p < decompilerContext->parsedGSCopyShader->numParam; p++) { if (decompilerContext->parsedGSCopyShader->paramMapping[p].exportType != 2) continue; src->addFmt("layout(location = {}) out vec4 passG2PParameter{};" _CRLF, decompilerContext->parsedGSCopyShader->paramMapping[p].exportParam & 0x7F, (sint32)decompilerContext->parsedGSCopyShader->paramMapping[p].exportParam); } } else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) { // pixel shader with geometry shader LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); for (sint32 i = 0; i < psInputTable->count; i++) { if (psInputTable->import[i].semanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) continue; uint32 location = psInputTable->import[i].semanticId & 0x7F; // todo - the range above 128 has special meaning? src->addFmt("layout(location = {}) ", location); if (psInputTable->import[i].isFlat) src->add("flat "); if (psInputTable->import[i].isNoPerspective) src->add("noperspective "); if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) src->add("out"); else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) src->add("in"); else debugBreakpoint(); src->addFmt(" vec4 passG2PParameter{};" _CRLF, (sint32)location); } } } // output defines if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) { // generate pixel outputs for pixel shader for (uint32 i = 0; i < LATTE_NUM_COLOR_TARGET; i++) { if ((decompilerContext->shader->pixelColorOutputMask&(1 << i)) != 0) { src->addFmt("layout(location = {}) out vec4 passPixelColor{};" _CRLF, i, i); } } } // streamout buffer (transform feedback) if ((decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) && decompilerContext->analyzer.hasStreamoutEnable) { if (decompilerContext->options->useTFViaSSBO) { if (decompilerContext->analyzer.useSSBOForStreamout && decompilerContext->analyzer.hasStreamoutWrite) { src->addFmt("layout(set = {}, binding = {}) buffer StreamoutBuffer" _CRLF, decompilerContext->output->resourceMappingVK.setIndex, decompilerContext->output->resourceMappingVK.getTFStorageBufferBindingPoint()); src->add("{" _CRLF); src->add("int sb_buffer[];" _CRLF); src->add("};" _CRLF); } } else { sint32 locationOffset = 0; // glslang wants a location for xfb outputs for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if (!decompilerContext->output->streamoutBufferWriteMask[i]) continue; uint32 bufferStride = decompilerContext->output->streamoutBufferStride[i]; src->addFmt("XFB_BLOCK_LAYOUT({}, {}, {}) out XfbBlock{} " _CRLF, i, bufferStride, locationOffset, i); src->add("{" _CRLF); src->addFmt("layout(xfb_buffer = {}, xfb_offset = 0) int sb{}[{}];" _CRLF, i, i, decompilerContext->output->streamoutBufferStride[i] / 4); src->add("};" _CRLF); locationOffset += (decompilerContext->output->streamoutBufferStride[i] / 4); } } } } void emitHeader(LatteDecompilerShaderContext* decompilerContext) { const bool dump_shaders_enabled = ActiveSettings::DumpShadersEnabled(); if(dump_shaders_enabled) decompilerContext->shaderSource->add("// start of shader inputs/outputs, predetermined by Cemu. Do not touch" _CRLF); // macros _emitHeaderMacros(decompilerContext); // uniform variables decompilerContext->shaderSource->add("#ifdef VULKAN" _CRLF); _emitUniformVariables(decompilerContext, RendererAPI::Vulkan, decompilerContext->output->uniformOffsetsVK); decompilerContext->shaderSource->add("#else" _CRLF); _emitUniformVariables(decompilerContext, RendererAPI::OpenGL, decompilerContext->output->uniformOffsetsGL); decompilerContext->shaderSource->add("#endif" _CRLF); // uniform buffers _emitUniformBuffers(decompilerContext); // textures _emitTextureDefinitions(decompilerContext); // attributes _emitAttributes(decompilerContext); // misc stuff _emitMisc(decompilerContext); if (dump_shaders_enabled) decompilerContext->shaderSource->add("// end of shader inputs/outputs" _CRLF); } } ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSL.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/OS/libs/gx2/GX2.h" // todo - remove dependency #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" #include "config/ActiveSettings.h" #include "util/helpers/StringBuf.h" #include <bitset> #include <boost/container/small_vector.hpp> #define _CRLF "\r\n" void LatteDecompiler_emitAttributeDecodeMSL(LatteDecompilerShader* shaderContext, StringBuf* src, LatteParsedFetchShaderAttribute_t* attrib); /* * Variable names: * R0-R127 temp * Most variables are multi-typed and the respective type is appended to the name * Type suffixes are: f (float), i (32bit int), ui (unsigned 32bit int) * Examples: R13ui.x, tempf.z */ // local prototypes void _emitTypeConversionPrefixMSL(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType, sint32 componentCount = 1); void _emitTypeConversionSuffixMSL(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType); void LatteDecompiler_emitClauseCodeMSL(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, bool isSubroutine); static const char* _getElementStrByIndex(uint32 channel) { switch (channel) { case 0: return "x"; case 1: return "y"; case 2: return "z"; case 3: return "w"; } return "UNDEFINED"; } static char _tempGenString[64][256]; static uint32 _tempGenStringIndex = 0; static char* _getTempString() { char* str = _tempGenString[_tempGenStringIndex]; _tempGenStringIndex = (_tempGenStringIndex+1)%64; return str; } static char* _getActiveMaskVarName(LatteDecompilerShaderContext* shaderContext, sint32 index) { char* varName = _getTempString(); if (shaderContext->isSubroutine) sprintf(varName, "activeMaskStackSub%04x[%d]", shaderContext->subroutineInfo->cfAddr, index); else sprintf(varName, "activeMaskStack[%d]", index); return varName; } static char* _getActiveMaskCVarName(LatteDecompilerShaderContext* shaderContext, sint32 index) { char* varName = _getTempString(); if (shaderContext->isSubroutine) sprintf(varName, "activeMaskStackCSub%04x[%d]", shaderContext->subroutineInfo->cfAddr, index); else sprintf(varName, "activeMaskStackC[%d]", index); return varName; } static char* _getRegisterVarName(LatteDecompilerShaderContext* shaderContext, uint32 index, sint32 destRelIndexMode=-1) { auto type = shaderContext->typeTracker.defaultDataType; char* tempStr = _getTempString(); if (shaderContext->typeTracker.useArrayGPRs == false) { if (type == LATTE_DECOMPILER_DTYPE_SIGNED_INT) sprintf(tempStr, "R%di", index); else if (type == LATTE_DECOMPILER_DTYPE_FLOAT) sprintf(tempStr, "R%df", index); } else { char destRelOffset[32]; if (destRelIndexMode >= 0) { if (destRelIndexMode == GPU7_INDEX_AR_X) strcpy(destRelOffset, "ARi.x"); else if (destRelIndexMode == GPU7_INDEX_AR_Y) strcpy(destRelOffset, "ARi.y"); else if (destRelIndexMode == GPU7_INDEX_AR_Z) strcpy(destRelOffset, "ARi.z"); else if (destRelIndexMode == GPU7_INDEX_AR_W) strcpy(destRelOffset, "ARi.w"); else debugBreakpoint(); if (type == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { sprintf(tempStr, "Ri[%d+%s]", index, destRelOffset); } else if (type == LATTE_DECOMPILER_DTYPE_FLOAT) { sprintf(tempStr, "Rf[%d+%s]", index, destRelOffset); } } else { if (type == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { sprintf(tempStr, "Ri[%d]", index); } else if (type == LATTE_DECOMPILER_DTYPE_FLOAT) { sprintf(tempStr, "Rf[%d]", index); } } } return tempStr; } static void _appendRegisterTypeSuffix(StringBuf* src, sint32 dataType) { if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->add("i"); else if (dataType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) src->add("ui"); else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->add("f"); else cemu_assert_unimplemented(); } // appends x/y/z/w static void _appendChannel(StringBuf* src, sint32 channelIndex) { cemu_assert_debug(channelIndex >= 0 && channelIndex <= 3); switch (channelIndex) { case 0: src->add("x"); return; case 1: src->add("y"); return; case 2: src->add("z"); return; case 3: src->add("w"); return; } } // appends .x/.y/.z/.w static void _appendChannelAccess(StringBuf* src, sint32 channelIndex) { cemu_assert_debug(channelIndex >= 0 && channelIndex <= 3); switch (channelIndex) { case 0: src->add(".x"); return; case 1: src->add(".y"); return; case 2: src->add(".z"); return; case 3: src->add(".w"); return; } } static void _appendPVPS(LatteDecompilerShaderContext* shaderContext, StringBuf* src, uint32 groupIndex, uint8 aluUnit) { cemu_assert_debug(aluUnit < 5); if (aluUnit == 4) { src->addFmt("PS{}", (groupIndex & 1)); _appendRegisterTypeSuffix(src, shaderContext->typeTracker.defaultDataType); return; } src->addFmt("PV{}", (groupIndex & 1)); _appendRegisterTypeSuffix(src, shaderContext->typeTracker.defaultDataType); _appendChannel(src, aluUnit); } std::string _FormatFloatAsConstant(float f) { char floatAsStr[64]; size_t floatAsStrLen = fmt::format_to_n(floatAsStr, 64, "{:#}", f).size; size_t floatAsStrLenOrg = floatAsStrLen; if(floatAsStrLen > 0 && floatAsStr[floatAsStrLen-1] == '.') { floatAsStr[floatAsStrLen] = '0'; floatAsStrLen++; } cemu_assert(floatAsStrLen < 50); // constant suspiciously long? floatAsStr[floatAsStrLen] = '\0'; cemu_assert_debug(floatAsStrLen >= 3); // shortest possible form is "0.0" return floatAsStr; } // tracks PV/PS and register backups struct ALUClauseTemporariesState { struct PVPSAlias { enum class LOCATION_TYPE : uint8 { LOCATION_NONE, LOCATION_GPR, LOCATION_PVPS, }; LOCATION_TYPE location{ LOCATION_TYPE::LOCATION_NONE }; uint8 index; // GPR index or temporary index uint8 aluUnit; // x,y,z,w (or 5 for PS) void SetLocationGPR(uint8 gprIndex, uint8 channel) { cemu_assert_debug(channel < 4); this->location = LOCATION_TYPE::LOCATION_GPR; this->index = gprIndex; this->aluUnit = channel; } void SetLocationPSPVTemporary(uint8 aluUnit, uint32 groupIndex) { cemu_assert_debug(aluUnit < 5); this->location = LOCATION_TYPE::LOCATION_PVPS; this->index = groupIndex & 1; this->aluUnit = aluUnit; } }; struct GPRTemporary { GPRTemporary(uint8 gprIndex, uint8 channel, uint8 backupVarIndex) : gprIndex(gprIndex), channel(channel), backupVarIndex(backupVarIndex) {} uint8 gprIndex; uint8 channel; uint8 backupVarIndex; }; void TrackGroupOutputPVPS(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstr, size_t numInstr) { // unset current for (auto& it : m_pvps) it.location = PVPSAlias::LOCATION_TYPE::LOCATION_NONE; for (size_t i = 0; i < numInstr; i++) { LatteDecompilerALUInstruction& inst = aluInstr[i]; if (!inst.isOP3 && inst.opcode == ALU_OP2_INST_NOP) continue; // skip NOP instruction if (inst.writeMask == 0) { // map to temporary m_pvps[inst.aluUnit].SetLocationPSPVTemporary(inst.aluUnit, aluInstr->instructionGroupIndex); } else { // map to GPR if(inst.destRel == 0) // is PV/PS set for indexed writes? m_pvps[inst.aluUnit].SetLocationGPR(inst.destGpr, inst.destElem); } } } bool HasPVPS(uint8 aluUnitIndex) const { cemu_assert_debug(aluUnitIndex < 5); return m_pvps[aluUnitIndex].location != PVPSAlias::LOCATION_TYPE::LOCATION_NONE; } void EmitPVPSAccess(LatteDecompilerShaderContext* shaderContext, uint8 aluUnitIndex, uint32 currentGroupIndex) const { switch (m_pvps[aluUnitIndex].location) { case PVPSAlias::LOCATION_TYPE::LOCATION_GPR: { sint32 temporaryIndex = GetTemporaryForGPR(m_pvps[aluUnitIndex].index, m_pvps[aluUnitIndex].aluUnit); if (temporaryIndex < 0) { shaderContext->shaderSource->add(_getRegisterVarName(shaderContext, m_pvps[aluUnitIndex].index, -1)); _appendChannelAccess(shaderContext->shaderSource, m_pvps[aluUnitIndex].aluUnit); } else { // use temporary instead of GPR shaderContext->shaderSource->addFmt("backupReg{}", temporaryIndex); _appendRegisterTypeSuffix(shaderContext->shaderSource, shaderContext->typeTracker.defaultDataType); } break; } case PVPSAlias::LOCATION_TYPE::LOCATION_PVPS: _appendPVPS(shaderContext, shaderContext->shaderSource, currentGroupIndex-1, m_pvps[aluUnitIndex].aluUnit); break; default: cemuLog_log(LogType::Force, "Shader {:016x} accesses PV/PS without writing to it", shaderContext->shaderBaseHash); cemu_assert_suspicious(); break; } } /* * Check for GPR channels which are modified before they are read within the same group * These registers need to be copied to a temporary */ void CreateGPRTemporaries(LatteDecompilerShaderContext* shaderContext, std::span<LatteDecompilerALUInstruction> aluInstructions) { uint8 registerChannelWriteMask[(LATTE_NUM_GPR * 4 + 7) / 8] = { 0 }; m_gprTemporaries.clear(); for (auto& aluInstruction : aluInstructions) { // ignore NOP instructions if (aluInstruction.isOP3 == false && aluInstruction.opcode == ALU_OP2_INST_NOP) continue; cemu_assert_debug(aluInstruction.destElem <= 3); // check if any previously written register is read for (sint32 f = 0; f < 3; f++) { uint32 readGPRIndex; uint32 readGPRChannel; if (GPU7_ALU_SRC_IS_GPR(aluInstruction.sourceOperand[f].sel)) { readGPRIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction.sourceOperand[f].sel); cemu_assert_debug(aluInstruction.sourceOperand[f].chan <= 3); readGPRChannel = aluInstruction.sourceOperand[f].chan; } else if (GPU7_ALU_SRC_IS_PV(aluInstruction.sourceOperand[f].sel) || GPU7_ALU_SRC_IS_PS(aluInstruction.sourceOperand[f].sel)) { uint8 aluUnitIndex = 0; if (GPU7_ALU_SRC_IS_PV(aluInstruction.sourceOperand[f].sel)) aluUnitIndex = aluInstruction.sourceOperand[f].chan; else aluUnitIndex = 4; // if aliased to a GPR, then consider it a GPR read if(m_pvps[aluUnitIndex].location != PVPSAlias::LOCATION_TYPE::LOCATION_GPR) continue; readGPRIndex = m_pvps[aluUnitIndex].index; readGPRChannel = m_pvps[aluUnitIndex].aluUnit; } else continue; // track GPR read if ((registerChannelWriteMask[(readGPRIndex * 4 + aluInstruction.sourceOperand[f].chan) / 8] & (1 << ((readGPRIndex * 4 + aluInstruction.sourceOperand[f].chan) % 8))) != 0) { // register is overwritten by previous instruction, a temporary variable is required if (GetTemporaryForGPR(readGPRIndex, readGPRChannel) < 0) m_gprTemporaries.emplace_back(readGPRIndex, readGPRChannel, m_gprTemporaries.size()); } } // track write if (aluInstruction.writeMask != 0) registerChannelWriteMask[(aluInstruction.destGpr * 4 + aluInstruction.destElem) / 8] |= (1 << ((aluInstruction.destGpr * 4 + aluInstruction.destElem) % 8)); } // output code to move GPRs into temporaries StringBuf* src = shaderContext->shaderSource; for (auto& it : m_gprTemporaries) { src->addFmt("backupReg{}", it.backupVarIndex); _appendRegisterTypeSuffix(src, shaderContext->typeTracker.defaultDataType); src->add(" = "); src->add(_getRegisterVarName(shaderContext, it.gprIndex)); _appendChannelAccess(src, it.channel); src->add(";" _CRLF); } } // returns -1 if none present sint32 GetTemporaryForGPR(uint8 gprIndex, uint8 channel) const { for (auto& it : m_gprTemporaries) { if (it.gprIndex == gprIndex && it.channel == channel) return (sint32)it.backupVarIndex; } return -1; } private: PVPSAlias m_pvps[5]{}; boost::container::small_vector<GPRTemporary, 4> m_gprTemporaries; }; sint32 _getVertexShaderOutParamSemanticId(uint32* contextRegisters, sint32 index); sint32 _getInputRegisterDataType(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex); sint32 _getALUInstructionOutputDataType(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction); bool _isReductionInstruction(LatteDecompilerALUInstruction* aluInstruction); /* * Writes the name of the output variable and channel * E.g. R5f.x or tempf.x if writeMask is 0 */ static void _emitInstructionOutputVariableName(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction) { auto src = shaderContext->shaderSource; sint32 outputDataType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); if( aluInstruction->writeMask == 0 ) { // does not output to GPR if( !_isReductionInstruction(aluInstruction) ) { // output to PV/PS _appendPVPS(shaderContext, src, aluInstruction->instructionGroupIndex, aluInstruction->aluUnit); return; } else { // output to temp src->add("temp"); _appendRegisterTypeSuffix(src, outputDataType); } _appendChannelAccess(src, aluInstruction->aluUnit); } else { // output to GPR. Aliasing to PV/PS happens at the end of the group src->add(_getRegisterVarName(shaderContext, aluInstruction->destGpr, aluInstruction->destRel==0?-1:aluInstruction->indexMode)); _appendChannelAccess(src, aluInstruction->destElem); } } static void _emitInstructionPVPSOutputVariableName(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction) { _appendPVPS(shaderContext, shaderContext->shaderSource, aluInstruction->instructionGroupIndex, aluInstruction->aluUnit); } static void _emitRegisterAccessCode(LatteDecompilerShaderContext* shaderContext, sint32 gprIndex, sint32 channel0, sint32 channel1, sint32 channel2, sint32 channel3, sint32 dataType = -1) { StringBuf* src = shaderContext->shaderSource; sint32 registerElementDataType = shaderContext->typeTracker.defaultDataType; cemu_assert_debug(gprIndex >= 0 && gprIndex <= 127); sint32 channelArray[4]; channelArray[0] = channel0; channelArray[1] = channel1; channelArray[2] = channel2; channelArray[3] = channel3; sint32 numComponents = 0; for (sint32 i = 0; i < 4; i++) { if (channelArray[i] >= 0 && channelArray[i] <= 3) numComponents++; } if (dataType >= 0) { _emitTypeConversionPrefixMSL(shaderContext, registerElementDataType, dataType, numComponents); } if (shaderContext->typeTracker.useArrayGPRs) src->add("R"); else src->addFmt("R{}", gprIndex); _appendRegisterTypeSuffix(src, registerElementDataType); if (shaderContext->typeTracker.useArrayGPRs) src->addFmt("[{}]", gprIndex); src->add("."); for (sint32 i = 0; i < 4; i++) { if (channelArray[i] >= 0 && channelArray[i] <= 3) src->add(_getElementStrByIndex(channelArray[i])); else if (channelArray[i] == -1) { // channel not used } else { cemu_assert_unimplemented(); } } if (dataType >= 0) _emitTypeConversionSuffixMSL(shaderContext, registerElementDataType, dataType); } // optimized variant of _emitRegisterAccessCode for raw one channel reads static void _emitRegisterChannelAccessCode(LatteDecompilerShaderContext* shaderContext, sint32 gprIndex, sint32 channel, sint32 dataType) { cemu_assert_debug(gprIndex >= 0 && gprIndex <= 127); cemu_assert_debug(channel >= 0 && channel < 4); StringBuf* src = shaderContext->shaderSource; sint32 registerElementDataType = shaderContext->typeTracker.defaultDataType; _emitTypeConversionPrefixMSL(shaderContext, registerElementDataType, dataType); if (shaderContext->typeTracker.useArrayGPRs) src->add("R"); else src->addFmt("R{}", gprIndex); _appendRegisterTypeSuffix(src, registerElementDataType); if (shaderContext->typeTracker.useArrayGPRs) src->addFmt("[{}]", gprIndex); src->add("."); src->add(_getElementStrByIndex(channel)); _emitTypeConversionSuffixMSL(shaderContext, registerElementDataType, dataType); } static void _emitALURegisterInputAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) { StringBuf* src = shaderContext->shaderSource; sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); cemu_assert_debug(GPU7_ALU_SRC_IS_GPR(aluInstruction->sourceOperand[operandIndex].sel)); sint32 gprIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction->sourceOperand[operandIndex].sel); sint32 temporaryIndex = shaderContext->aluPVPSState->GetTemporaryForGPR(gprIndex, aluInstruction->sourceOperand[operandIndex].chan); if(temporaryIndex >= 0) { // access via backup variable src->addFmt("backupReg{}", temporaryIndex); _appendRegisterTypeSuffix(src, currentRegisterElementType); } else { // access via register variable _emitRegisterAccessCode(shaderContext, gprIndex, aluInstruction->sourceOperand[operandIndex].chan, -1, -1, -1); } } static void _emitPVPSAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, uint8 aluUnitIndex) { cemu_assert_debug(aluInstruction->instructionGroupIndex > 0); // PV/PS is uninitialized for group 0 // PV/PS vars are currently always using the default type (shaderContext->typeTracker.defaultDataType) shaderContext->aluPVPSState->EmitPVPSAccess(shaderContext, aluUnitIndex, aluInstruction->instructionGroupIndex); } /* * Emits the expression used for calculating the index for uniform access * For static access, this is a number * For dynamic access, this is AR.* + base */ static void _emitUniformAccessIndexCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) { StringBuf* src = shaderContext->shaderSource; bool isUniformRegister = GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel); sint32 uniformOffset = 0; // index into array, for relative accesses this is the base offset if( isUniformRegister ) { uniformOffset = GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction->sourceOperand[operandIndex].sel); } else { if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) ) { uniformOffset = GPU7_ALU_SRC_GET_CBANK0_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank0AddrBase; } else { uniformOffset = GPU7_ALU_SRC_GET_CBANK1_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank1AddrBase; } } if( aluInstruction->sourceOperand[operandIndex].rel != 0 ) { if (aluInstruction->indexMode == GPU7_INDEX_AR_X) src->addFmt("ARi.x+{}", uniformOffset); else if (aluInstruction->indexMode == GPU7_INDEX_AR_Y) src->addFmt("ARi.y+{}", uniformOffset); else if (aluInstruction->indexMode == GPU7_INDEX_AR_Z) src->addFmt("ARi.z+{}", uniformOffset); else if (aluInstruction->indexMode == GPU7_INDEX_AR_W) src->addFmt("ARi.w+{}", uniformOffset); else cemu_assert_unimplemented(); } else { src->addFmt("{}", uniformOffset); } } static void _emitUniformAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, sint32 requiredType) { StringBuf* src = shaderContext->shaderSource; if(shaderContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED ) { // uniform registers or buffers are accessed statically with predictable offsets // find entry in remapped uniform if( aluInstruction->sourceOperand[operandIndex].rel != 0 ) debugBreakpoint(); bool isUniformRegister = GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel); sint32 uniformOffset = 0; // index into array sint32 uniformBufferIndex = 0; if( isUniformRegister ) { uniformOffset = GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction->sourceOperand[operandIndex].sel); uniformBufferIndex = 0; } else { if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) ) { uniformOffset = GPU7_ALU_SRC_GET_CBANK0_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank0AddrBase; uniformBufferIndex = aluInstruction->cfInstruction->cBank0Index; } else { uniformOffset = GPU7_ALU_SRC_GET_CBANK1_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank1AddrBase; uniformBufferIndex = aluInstruction->cfInstruction->cBank1Index; } } LatteDecompilerRemappedUniformEntry_t* remappedUniformEntry = NULL; for(size_t i=0; i< shaderContext->shader->list_remappedUniformEntries.size(); i++) { LatteDecompilerRemappedUniformEntry_t* remappedUniformEntryItr = shaderContext->shader->list_remappedUniformEntries.data() + i; if( remappedUniformEntryItr->isRegister && isUniformRegister ) { if( remappedUniformEntryItr->index == uniformOffset ) { remappedUniformEntry = remappedUniformEntryItr; break; } } else { if( remappedUniformEntryItr->kcacheBankId == uniformBufferIndex && remappedUniformEntryItr->index == uniformOffset ) { remappedUniformEntry = remappedUniformEntryItr; break; } } } cemu_assert_debug(remappedUniformEntry); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); src->addFmt("supportBuffer.remapped[{}]", remappedUniformEntry->mappedIndex); _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); } else if( shaderContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE ) { // uniform registers are accessed with unpredictable (dynamic) offset _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); src->add("supportBuffer.uniformRegister["); _emitUniformAccessIndexCode(shaderContext, aluInstruction, operandIndex); src->add("]"); _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); } else if( shaderContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK ) { // uniform buffers are available as a whole bool isUniformRegister = GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel); if( isUniformRegister ) debugBreakpoint(); sint32 uniformBufferIndex = 0; if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) ) { uniformBufferIndex = aluInstruction->cfInstruction->cBank0Index; } else { uniformBufferIndex = aluInstruction->cfInstruction->cBank1Index; } _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); src->addFmt("ubuff{}.d[", uniformBufferIndex); _emitUniformAccessIndexCode(shaderContext, aluInstruction, operandIndex); src->addFmt("]"); _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); } else debugBreakpoint(); } // Generates (slow) code to read an indexed GPR static void _emitCodeToReadRelativeGPR(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, sint32 requiredType) { StringBuf* src = shaderContext->shaderSource; uint32 gprBaseIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction->sourceOperand[operandIndex].sel); cemu_assert_debug(aluInstruction->sourceOperand[operandIndex].rel != 0); if( shaderContext->typeTracker.useArrayGPRs ) { _emitTypeConversionPrefixMSL(shaderContext, shaderContext->typeTracker.defaultDataType, requiredType); src->add(_getRegisterVarName(shaderContext, gprBaseIndex, aluInstruction->indexMode)); _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); _emitTypeConversionSuffixMSL(shaderContext, shaderContext->typeTracker.defaultDataType, requiredType); return; } char indexAccessCode[64]; if (aluInstruction->indexMode == GPU7_INDEX_AR_X) sprintf(indexAccessCode, "ARi.x"); else if (aluInstruction->indexMode == GPU7_INDEX_AR_Y) sprintf(indexAccessCode, "ARi.y"); else if (aluInstruction->indexMode == GPU7_INDEX_AR_Z) sprintf(indexAccessCode, "ARi.z"); else if (aluInstruction->indexMode == GPU7_INDEX_AR_W) sprintf(indexAccessCode, "ARi.w"); else cemu_assert_unimplemented(); if( LATTE_DECOMPILER_DTYPE_SIGNED_INT != requiredType ) _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); // generated code looks like this: // result = ((lookupIndex==0)?GPR5:(lookupIndex==1)?GPR6:(lookupIndex==2)?GPR7:...:(lookupIndex==122)?GPR127:0) src->add("("); for(sint32 i=gprBaseIndex; i<LATTE_NUM_GPR; i++) { // only emit access code for registers which are potentially written if((shaderContext->analyzer.gprUseMask[i / 8] & (1 << (i % 8))) == 0 ) continue; src->addFmt("({}=={})?", indexAccessCode, i-gprBaseIndex); // code to access gpr uint32 gprIndex = i; src->add(_getRegisterVarName(shaderContext, i)); _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); src->add(":"); } src->add("0)"); if( LATTE_DECOMPILER_DTYPE_SIGNED_INT != requiredType ) _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); } static void _emitOperandInputCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, sint32 requiredType) { StringBuf* src = shaderContext->shaderSource; if( operandIndex < 0 || operandIndex >= 3 ) debugBreakpoint(); sint32 requiredTypeOut = requiredType; if( requiredType != LATTE_DECOMPILER_DTYPE_FLOAT && (aluInstruction->sourceOperand[operandIndex].abs != 0 || aluInstruction->sourceOperand[operandIndex].neg != 0) ) { // we need to apply float operations on the input but it's not read as a float // force internal required type to float and then cast it back to whatever type is actually required requiredType = LATTE_DECOMPILER_DTYPE_FLOAT; } if( requiredTypeOut != requiredType ) _emitTypeConversionPrefixMSL(shaderContext, requiredType, requiredTypeOut); if( aluInstruction->sourceOperand[operandIndex].neg != 0 ) src->add("-("); if( aluInstruction->sourceOperand[operandIndex].abs != 0 ) src->add("abs("); if( GPU7_ALU_SRC_IS_GPR(aluInstruction->sourceOperand[operandIndex].sel) ) { if( aluInstruction->sourceOperand[operandIndex].rel != 0 ) { _emitCodeToReadRelativeGPR(shaderContext, aluInstruction, operandIndex, requiredType); } else { uint32 gprIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction->sourceOperand[operandIndex].sel); if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { // signed int 32bit sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); // write code for register input _emitTypeConversionPrefixMSL(shaderContext, currentRegisterElementType, requiredType); _emitALURegisterInputAccessCode(shaderContext, aluInstruction, operandIndex); _emitTypeConversionSuffixMSL(shaderContext, currentRegisterElementType, requiredType); } else if( requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) { // unsigned int 32bit sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { // need to convert from int to uint src->add("uint("); } else if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) { // no extra work necessary } else debugBreakpoint(); // write code for register input _emitALURegisterInputAccessCode(shaderContext, aluInstruction, operandIndex); if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { src->add(")"); } } else if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) { // float 32bit sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { // need to convert (not cast) from int bits to float src->add("as_type<float>("); // TODO: correct? } else if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_FLOAT ) { // no extra work necessary } else debugBreakpoint(); // write code for register input _emitALURegisterInputAccessCode(shaderContext, aluInstruction, operandIndex); if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { src->add(")"); } } else debugBreakpoint(); } } else if( GPU7_ALU_SRC_IS_CONST_0F(aluInstruction->sourceOperand[operandIndex].sel) ) { if(requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT || requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) src->add("0"); else if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) src->add("0.0"); } else if( GPU7_ALU_SRC_IS_CONST_1F(aluInstruction->sourceOperand[operandIndex].sel) ) { _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); src->add("1.0"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); } else if( GPU7_ALU_SRC_IS_CONST_0_5F(aluInstruction->sourceOperand[operandIndex].sel) ) { _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); src->add("0.5"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); } else if( GPU7_ALU_SRC_IS_CONST_1I(aluInstruction->sourceOperand[operandIndex].sel) ) { if (requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->add("int(1)"); else if (requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) src->add("uint(1)"); else cemu_assert_suspicious(); } else if( GPU7_ALU_SRC_IS_CONST_M1I(aluInstruction->sourceOperand[operandIndex].sel) ) { if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) src->add("int(-1)"); else cemu_assert_suspicious(); } else if( GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[operandIndex].sel) ) { if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) src->addFmt("int(0x{:x})", aluInstruction->literalData.w[aluInstruction->sourceOperand[operandIndex].chan]); else if( requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) src->addFmt("uint(0x{:x})", aluInstruction->literalData.w[aluInstruction->sourceOperand[operandIndex].chan]); else if (requiredType == LATTE_DECOMPILER_DTYPE_FLOAT) { uint32 constVal = aluInstruction->literalData.w[aluInstruction->sourceOperand[operandIndex].chan]; sint32 exponent = (constVal >> 23) & 0xFF; exponent -= 127; if ((constVal & 0xFF) == 0 && exponent >= -10 && exponent <= 10) { src->add(_FormatFloatAsConstant(*(float*)&constVal)); } else src->addFmt("as_type<float>(0x{:08x})", constVal); } } else if( GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel) ) { _emitUniformAccessCode(shaderContext, aluInstruction, operandIndex, requiredType); } else if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) || GPU7_ALU_SRC_IS_CBANK1(aluInstruction->sourceOperand[operandIndex].sel) ) { _emitUniformAccessCode(shaderContext, aluInstruction, operandIndex, requiredType); } else if( GPU7_ALU_SRC_IS_PV(aluInstruction->sourceOperand[operandIndex].sel) ) { sint32 currentPVDataType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); _emitTypeConversionPrefixMSL(shaderContext, currentPVDataType, requiredType); _emitPVPSAccessCode(shaderContext, aluInstruction, operandIndex, aluInstruction->sourceOperand[operandIndex].chan); _emitTypeConversionSuffixMSL(shaderContext, currentPVDataType, requiredType); } else if( GPU7_ALU_SRC_IS_PS(aluInstruction->sourceOperand[operandIndex].sel) ) { sint32 currentPSDataType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); _emitTypeConversionPrefixMSL(shaderContext, currentPSDataType, requiredType); _emitPVPSAccessCode(shaderContext, aluInstruction, operandIndex, 4); _emitTypeConversionSuffixMSL(shaderContext, currentPSDataType, requiredType); } else { cemuLog_log(LogType::Force, "Unsupported shader ALU operand sel {:#x}\n", aluInstruction->sourceOperand[operandIndex].sel); debugBreakpoint(); } if( aluInstruction->sourceOperand[operandIndex].abs != 0 ) src->add(")"); if( aluInstruction->sourceOperand[operandIndex].neg != 0 ) src->add(")"); if( requiredTypeOut != requiredType ) _emitTypeConversionSuffixMSL(shaderContext, requiredType, requiredTypeOut); } void _emitTypeConversionPrefixMSL(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType, sint32 componentCount) { if( sourceType == destinationType ) return; StringBuf* src = shaderContext->shaderSource; if (destinationType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { if (componentCount == 1) src->add("as_type<int>("); else src->addFmt("as_type<int{}>(", componentCount); } else if (destinationType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) { if (componentCount == 1) src->add("as_type<uint>("); else src->addFmt("as_type<uint{}>(", componentCount); } else if (destinationType == LATTE_DECOMPILER_DTYPE_FLOAT) { if (componentCount == 1) src->add("as_type<float>("); else src->addFmt("as_type<float{}>(", componentCount); } else cemu_assert_debug(false); } void _emitTypeConversionSuffixMSL(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType) { if( sourceType == destinationType ) return; StringBuf* src = shaderContext->shaderSource; src->add(")"); } template<int TDataType> static void _emitALUOperationBinary(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, const char* operandStr) { StringBuf* src = shaderContext->shaderSource; sint32 outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, TDataType, outputType); _emitOperandInputCode(shaderContext, aluInstruction, 0, TDataType); src->add((char*)operandStr); _emitOperandInputCode(shaderContext, aluInstruction, 1, TDataType); _emitTypeConversionSuffixMSL(shaderContext, TDataType, outputType); src->add(";" _CRLF); } static bool _isSameGPROperand(LatteDecompilerALUInstruction* aluInstruction, sint32 opIndexA, sint32 opIndexB) { if (aluInstruction->sourceOperand[opIndexA].sel != aluInstruction->sourceOperand[opIndexB].sel) return false; if (!GPU7_ALU_SRC_IS_GPR(aluInstruction->sourceOperand[opIndexA].sel)) return false; if (aluInstruction->sourceOperand[opIndexA].chan != aluInstruction->sourceOperand[opIndexB].chan) return false; if (aluInstruction->sourceOperand[opIndexA].abs != aluInstruction->sourceOperand[opIndexB].abs) return false; if (aluInstruction->sourceOperand[opIndexA].neg != aluInstruction->sourceOperand[opIndexB].neg) return false; if (aluInstruction->sourceOperand[opIndexA].rel != aluInstruction->sourceOperand[opIndexB].rel) return false; return true; } static bool _operandHasModifiers(LatteDecompilerALUInstruction* aluInstruction, sint32 opIndex) { return aluInstruction->sourceOperand[opIndex].abs != 0 || aluInstruction->sourceOperand[opIndex].neg != 0; } static void _emitALUOP2InstructionCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, LatteDecompilerALUInstruction* aluInstruction) { StringBuf* src = shaderContext->shaderSource; sint32 outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); // data type of output if( aluInstruction->opcode == ALU_OP2_INST_MOV ) { bool requiresFloatMove = false; requiresFloatMove = aluInstruction->sourceOperand[0].abs != 0 || aluInstruction->sourceOperand[0].neg != 0; if( requiresFloatMove ) { // abs/neg operations are applied to source operand, do float based move _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitOperandInputCode(shaderContext, aluInstruction, 0, outputType); src->add(";" _CRLF); } } else if( aluInstruction->opcode == ALU_OP2_INST_MOVA_FLOOR ) { cemu_assert_debug(aluInstruction->writeMask == 0); cemu_assert_debug(aluInstruction->omod == 0); src->add("tempResultf = "); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(";" _CRLF); src->add("tempResultf = floor(tempResultf);" _CRLF); src->add("tempResultf = clamp(tempResultf, -256.0, 255.0);" _CRLF); // set AR if( aluInstruction->destElem == 0 ) src->add("ARi.x = int(tempResultf);" _CRLF); else if( aluInstruction->destElem == 1 ) src->add("ARi.y = int(tempResultf);" _CRLF); else if( aluInstruction->destElem == 2 ) src->add("ARi.z = int(tempResultf);" _CRLF); else src->add("ARi.w = int(tempResultf);" _CRLF); // set output _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); if( outputType != LATTE_DECOMPILER_DTYPE_SIGNED_INT ) debugBreakpoint(); // todo src->add("as_type<int>(tempResultf)"); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_MOVA_INT ) { cemu_assert_debug(aluInstruction->writeMask == 0); cemu_assert_debug(aluInstruction->omod == 0); src->add("tempResulti = "); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(";" _CRLF); src->add("tempResulti = clamp(tempResulti, -256, 255);" _CRLF); // set AR if( aluInstruction->destElem == 0 ) src->add("ARi.x = tempResulti;" _CRLF); else if( aluInstruction->destElem == 1 ) src->add("ARi.y = tempResulti;" _CRLF); else if( aluInstruction->destElem == 2 ) src->add("ARi.z = tempResulti;" _CRLF); else src->add("ARi.w = tempResulti;" _CRLF); // set output _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); if( outputType != LATTE_DECOMPILER_DTYPE_SIGNED_INT ) debugBreakpoint(); // todo src->add("tempResulti"); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_ADD ) { _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_FLOAT>(shaderContext, aluInstruction, " + "); } else if( aluInstruction->opcode == ALU_OP2_INST_MUL ) { // 0*anything is always 0 _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); // if any operand is a non-zero literal or constant we can use standard multiplication bool useDefaultMul = false; if (GPU7_ALU_SRC_IS_CONST_0F(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_CONST_0F(aluInstruction->sourceOperand[1].sel)) { // result is always zero src->add("0.0"); } else { // multiply if (GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[1].sel) || GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[1].sel)) { useDefaultMul = true; } if (shaderContext->options->strictMul && useDefaultMul == false) { src->add("mul_nonIEEE("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(", "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); } else { _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(" * "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); } } _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_MUL_IEEE ) { // 0*anything according to IEEE rules _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_FLOAT>(shaderContext, aluInstruction, " * "); } else if (aluInstruction->opcode == ALU_OP2_INST_RECIP_IEEE) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("1.0"); src->add(" / "); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if (aluInstruction->opcode == ALU_OP2_INST_RECIP_FF) { // untested (BotW bombs) src->add("tempResultf = 1.0 / ("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(");" _CRLF); // INF becomes 0.0 src->add("if( isinf(tempResultf) == true && (as_type<int>(tempResultf)&0x80000000) == 0 ) tempResultf = 0.0;" _CRLF); // -INF becomes -0.0 src->add("else if( isinf(tempResultf) == true && (as_type<int>(tempResultf)&0x80000000) != 0 ) tempResultf = -0.0;" _CRLF); // assign result to output _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("tempResultf"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_IEEE || aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_CLAMPED || aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_FF ) { // todo: This should be correct but testing is needed src->add("tempResultf = 1.0 / sqrt("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(");" _CRLF); if (aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_CLAMPED) { // note: if( -INF < 0.0 ) does not resolve to true src->add("if( isinf(tempResultf) == true && (as_type<int>(tempResultf)&0x80000000) != 0 ) tempResultf = -3.40282347E+38F;" _CRLF); src->add("else if( isinf(tempResultf) == true && (as_type<int>(tempResultf)&0x80000000) == 0 ) tempResultf = 3.40282347E+38F;" _CRLF); } else if (aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_FF) { // untested (BotW bombs) src->add("if( isinf(tempResultf) == true && (as_type<int>(tempResultf)&0x80000000) != 0 ) tempResultf = -0.0;" _CRLF); src->add("else if( isinf(tempResultf) == true && (as_type<int>(tempResultf)&0x80000000) == 0 ) tempResultf = 0.0;" _CRLF); } // assign result to output _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("tempResultf"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_MAX || aluInstruction->opcode == ALU_OP2_INST_MIN || aluInstruction->opcode == ALU_OP2_INST_MAX_DX10 || aluInstruction->opcode == ALU_OP2_INST_MIN_DX10 ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); if( aluInstruction->opcode == ALU_OP2_INST_MAX ) src->add("max"); else if( aluInstruction->opcode == ALU_OP2_INST_MIN ) src->add("min"); else if (aluInstruction->opcode == ALU_OP2_INST_MAX_DX10) src->add("max"); else if (aluInstruction->opcode == ALU_OP2_INST_MIN_DX10) src->add("min"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(", "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_FLOOR || aluInstruction->opcode == ALU_OP2_INST_FRACT || aluInstruction->opcode == ALU_OP2_INST_TRUNC ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); if( aluInstruction->opcode == ALU_OP2_INST_FLOOR ) src->add("floor"); else if( aluInstruction->opcode == ALU_OP2_INST_FRACT ) src->add("fract"); else src->add("trunc"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_LOG_CLAMPED || aluInstruction->opcode == ALU_OP2_INST_LOG_IEEE ) { src->add("tempResultf = max(0.0, "); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(");" _CRLF); src->add("tempResultf = log2(tempResultf);" _CRLF); if( aluInstruction->opcode == ALU_OP2_INST_LOG_CLAMPED ) { src->add("if( isinf(tempResultf) == true ) tempResultf = -3.40282347E+38F;" _CRLF); } // assign result to output _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("tempResultf"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_RNDNE ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("rint("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_EXP_IEEE ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("exp2"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SQRT_IEEE ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("sqrt"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SIN || aluInstruction->opcode == ALU_OP2_INST_COS ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); if( aluInstruction->opcode == ALU_OP2_INST_SIN ) src->add("sin"); else src->add("cos"); src->add("(("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")/0.1591549367)"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_FLT_TO_INT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("int"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_FLT_TO_UINT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT, outputType); src->add("uint"); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_INT_TO_FLOAT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("float("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_UINT_TO_FLOAT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("float("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if (aluInstruction->opcode == ALU_OP2_INST_AND_INT) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " & "); else if (aluInstruction->opcode == ALU_OP2_INST_OR_INT) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " | "); else if (aluInstruction->opcode == ALU_OP2_INST_XOR_INT) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " ^ "); else if( aluInstruction->opcode == ALU_OP2_INST_NOT_INT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("~("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_ADD_INT ) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " + "); else if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MIN_INT || aluInstruction->opcode == ALU_OP2_INST_MAX_UINT || aluInstruction->opcode == ALU_OP2_INST_MIN_UINT) { // not verified bool isUnsigned = aluInstruction->opcode == ALU_OP2_INST_MAX_UINT || aluInstruction->opcode == ALU_OP2_INST_MIN_UINT; auto opType = isUnsigned ? LATTE_DECOMPILER_DTYPE_UNSIGNED_INT : LATTE_DECOMPILER_DTYPE_SIGNED_INT; _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, opType, outputType); if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MAX_UINT ) src->add("max("); else src->add("min("); _emitOperandInputCode(shaderContext, aluInstruction, 0, opType); src->add(", "); _emitOperandInputCode(shaderContext, aluInstruction, 1, opType); _emitTypeConversionSuffixMSL(shaderContext, opType, outputType); src->add(");" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SUB_INT ) { // note: The AMD doc says src1 is on the left side but tests indicate otherwise. It's src0 - src1. _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " - "); } else if (aluInstruction->opcode == ALU_OP2_INST_MULLO_INT) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " * "); else if (aluInstruction->opcode == ALU_OP2_INST_MULLO_UINT) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_UNSIGNED_INT>(shaderContext, aluInstruction, " * "); else if( aluInstruction->opcode == ALU_OP2_INST_LSHL_INT ) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " << "); else if( aluInstruction->opcode == ALU_OP2_INST_LSHR_INT ) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " >> "); else if( aluInstruction->opcode == ALU_OP2_INST_ASHR_INT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(" >> "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SETGT || aluInstruction->opcode == ALU_OP2_INST_SETGE || aluInstruction->opcode == ALU_OP2_INST_SETNE || aluInstruction->opcode == ALU_OP2_INST_SETE ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); if( aluInstruction->opcode == ALU_OP2_INST_SETGT ) src->add(" > "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGE ) src->add(" >= "); else if (aluInstruction->opcode == ALU_OP2_INST_SETNE) src->add(" != "); else if (aluInstruction->opcode == ALU_OP2_INST_SETE) src->add(" == "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")?1.0:0.0"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_DX10 || aluInstruction->opcode == ALU_OP2_INST_SETE_DX10 || aluInstruction->opcode == ALU_OP2_INST_SETNE_DX10 || aluInstruction->opcode == ALU_OP2_INST_SETGE_DX10 ) { if( aluInstruction->omod != 0 ) debugBreakpoint(); _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("(("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); if( aluInstruction->opcode == ALU_OP2_INST_SETE_DX10 ) src->add(" == "); else if( aluInstruction->opcode == ALU_OP2_INST_SETNE_DX10 ) src->add(" != "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_DX10 ) src->add(" > "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGE_DX10 ) src->add(" >= "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")?-1:0)"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";"); src->add(_CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SETE_INT || aluInstruction->opcode == ALU_OP2_INST_SETNE_INT || aluInstruction->opcode == ALU_OP2_INST_SETGT_INT || aluInstruction->opcode == ALU_OP2_INST_SETGE_INT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); if( aluInstruction->opcode == ALU_OP2_INST_SETE_INT ) src->add(" == "); else if( aluInstruction->opcode == ALU_OP2_INST_SETNE_INT ) src->add(" != "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_INT ) src->add(" > "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGE_INT ) src->add(" >= "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")?-1:0"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SETGE_UINT || aluInstruction->opcode == ALU_OP2_INST_SETGT_UINT ) { // todo: Unsure if the result is unsigned or signed _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT); if( aluInstruction->opcode == ALU_OP2_INST_SETGE_UINT ) src->add(" >= "); else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_UINT ) src->add(" > "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT); src->add(")?int(0xFFFFFFFF):int(0x0)"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE_INT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETE_INT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE_INT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT_INT ) { cemu_assert_debug(aluInstruction->writeMask == 0); bool isIntPred = (aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE_INT) || (aluInstruction->opcode == ALU_OP2_INST_PRED_SETE_INT) || (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE_INT) || (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT_INT); src->add("predResult"); src->add(" = ("); _emitOperandInputCode(shaderContext, aluInstruction, 0, isIntPred?LATTE_DECOMPILER_DTYPE_SIGNED_INT:LATTE_DECOMPILER_DTYPE_FLOAT); if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETE_INT) src->add(" == "); else if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT_INT) src->add(" > "); else if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE_INT) src->add(" >= "); else if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE_INT) src->add(" != "); else cemu_assert_debug(false); _emitOperandInputCode(shaderContext, aluInstruction, 1, isIntPred?LATTE_DECOMPILER_DTYPE_SIGNED_INT:LATTE_DECOMPILER_DTYPE_FLOAT); src->add(");" _CRLF); // handle result of predicate instruction based on current ALU clause type if( cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE ) { src->addFmt("{} = predResult;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); src->addFmt("{} = predResult == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); } else if( cfInstruction->type == GPU7_CF_INST_ALU_BREAK ) { // leave current loop src->add("if( predResult == false ) break;" _CRLF); } else cemu_assert_debug(false); } else if( aluInstruction->opcode == ALU_OP2_INST_KILLE_INT || aluInstruction->opcode == ALU_OP2_INST_KILLNE_INT || aluInstruction->opcode == ALU_OP2_INST_KILLGT_INT) { src->add("if( "); src->add(" ("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); if( aluInstruction->opcode == ALU_OP2_INST_KILLE_INT ) src->add(" == "); else if (aluInstruction->opcode == ALU_OP2_INST_KILLNE_INT) src->add(" != "); else if (aluInstruction->opcode == ALU_OP2_INST_KILLGT_INT) src->add(" > "); else debugBreakpoint(); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")"); src->add(") discard_fragment();"); src->add(_CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_KILLGT || aluInstruction->opcode == ALU_OP2_INST_KILLGE || aluInstruction->opcode == ALU_OP2_INST_KILLE ) { src->add("if( "); src->add(" ("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); if( aluInstruction->opcode == ALU_OP2_INST_KILLGT ) src->add(" > "); else if( aluInstruction->opcode == ALU_OP2_INST_KILLGE ) src->add(" >= "); else if( aluInstruction->opcode == ALU_OP2_INST_KILLE ) src->add(" == "); else debugBreakpoint(); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); src->add(") discard_fragment();"); src->add(_CRLF); } else { src->add("Unsupported instruction;" _CRLF); debug_printf("Unsupported ALU op2 instruction 0x%x\n", aluInstruction->opcode); shaderContext->shader->hasError = true; } } static void _emitALUOP3InstructionCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, LatteDecompilerALUInstruction* aluInstruction) { StringBuf* src = shaderContext->shaderSource; cemu_assert_debug(aluInstruction->destRel == 0); // todo sint32 outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); /* check for common no-op or mov-like instructions */ if (aluInstruction->opcode == ALU_OP3_INST_CMOVGE || aluInstruction->opcode == ALU_OP3_INST_CMOVE || aluInstruction->opcode == ALU_OP3_INST_CMOVGT || aluInstruction->opcode == ALU_OP3_INST_CNDE_INT || aluInstruction->opcode == ALU_OP3_INST_CNDGT_INT || aluInstruction->opcode == ALU_OP3_INST_CMOVGE_INT) { if (_isSameGPROperand(aluInstruction, 1, 2) && !_operandHasModifiers(aluInstruction, 1)) { // the condition is irrelevant as both operands are the same _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitOperandInputCode(shaderContext, aluInstruction, 1, outputType); src->add(";" _CRLF); return; } } /* generic handlers */ if( aluInstruction->opcode == ALU_OP3_INST_MULADD || aluInstruction->opcode == ALU_OP3_INST_MULADD_D2 || aluInstruction->opcode == ALU_OP3_INST_MULADD_M2 || aluInstruction->opcode == ALU_OP3_INST_MULADD_M4 || aluInstruction->opcode == ALU_OP3_INST_MULADD_IEEE ) { // todo: The difference between MULADD and MULADD IEEE is that the former has 0*anything=0 rule similar to MUL/MUL_IEEE? _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); if (aluInstruction->opcode != ALU_OP3_INST_MULADD) // avoid unnecessary parenthesis to improve code readability slightly src->add("("); bool useDefaultMul = false; if (GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[1].sel) || GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[1].sel)) { useDefaultMul = true; } if (aluInstruction->opcode == ALU_OP3_INST_MULADD_IEEE) useDefaultMul = true; if (shaderContext->options->strictMul && useDefaultMul == false) { src->add("mul_nonIEEE("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); } else { _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(" * "); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); } src->add(" + "); _emitOperandInputCode(shaderContext, aluInstruction, 2, LATTE_DECOMPILER_DTYPE_FLOAT); if(aluInstruction->opcode != ALU_OP3_INST_MULADD) src->add(")"); if( aluInstruction->opcode == ALU_OP3_INST_MULADD_D2 ) src->add("/2.0"); else if( aluInstruction->opcode == ALU_OP3_INST_MULADD_M2 ) src->add("*2.0"); else if( aluInstruction->opcode == ALU_OP3_INST_MULADD_M4 ) src->add("*4.0"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if(aluInstruction->opcode == ALU_OP3_INST_CNDE_INT || aluInstruction->opcode == ALU_OP3_INST_CNDGT_INT || aluInstruction->opcode == ALU_OP3_INST_CMOVGE_INT) { bool requiresFloatResult = (aluInstruction->sourceOperand[1].neg != 0) || (aluInstruction->sourceOperand[2].neg != 0); _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("(("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); if (aluInstruction->opcode == ALU_OP3_INST_CNDE_INT) src->add(" == "); else if (aluInstruction->opcode == ALU_OP3_INST_CNDGT_INT) src->add(" > "); else if (aluInstruction->opcode == ALU_OP3_INST_CMOVGE_INT) src->add(" >= "); src->add("0)?("); _emitOperandInputCode(shaderContext, aluInstruction, 1, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add("):("); _emitOperandInputCode(shaderContext, aluInstruction, 2, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add("))"); _emitTypeConversionSuffixMSL(shaderContext, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else if( aluInstruction->opcode == ALU_OP3_INST_CMOVGE || aluInstruction->opcode == ALU_OP3_INST_CMOVE || aluInstruction->opcode == ALU_OP3_INST_CMOVGT ) { _emitInstructionOutputVariableName(shaderContext, aluInstruction); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("(("); _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); if (aluInstruction->opcode == ALU_OP3_INST_CMOVE) src->add(" == "); else if (aluInstruction->opcode == ALU_OP3_INST_CMOVGE) src->add(" >= "); else if (aluInstruction->opcode == ALU_OP3_INST_CMOVGT) src->add(" > "); src->add("0.0)?("); _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add("):("); _emitOperandInputCode(shaderContext, aluInstruction, 2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add("))"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else { src->add("Unsupported instruction;" _CRLF); debug_printf("Unsupported ALU op3 instruction 0x%x\n", aluInstruction->opcode); shaderContext->shader->hasError = true; } } static void _emitALUReductionInstructionCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluRedcInstruction[4]) { StringBuf* src = shaderContext->shaderSource; if( aluRedcInstruction[0]->isOP3 == false && (aluRedcInstruction[0]->opcode == ALU_OP2_INST_DOT4 || aluRedcInstruction[0]->opcode == ALU_OP2_INST_DOT4_IEEE) ) { // todo: Figure out and implement the difference between normal DOT4 and DOT4_IEEE sint32 outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[0]); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); // dot(float4(op0),float4(op1)) src->add("dot(float4("); _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("),float4("); _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("))"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); } else if( aluRedcInstruction[0]->isOP3 == false && (aluRedcInstruction[0]->opcode == ALU_OP2_INST_CUBE) ) { /* * How the CUBE instruction works (guessed mostly, based on DirectX/OpenGL spec): Input: float4, 3d direction vector (can be unnormalized) + w component (which can be ignored, since it only scales the vector but does not affect the direction) First we figure out the major axis (closest axis-aligned vector). There are six possible vectors: +rx 0 -rx 1 +ry 2 -ry 3 +rz 4 -rz 5 The major axis vector is calculated by looking at the largest (absolute) 3d vector component and then setting the other components to 0.0 The value that remains in the axis vector is referred to as 'MajorAxis' by the AMD documentation. The S,T coordinates are taken from the other two components. Example: -0.5,0.2,0.4 -> -rx -> -0.5,0.0,0.0 MajorAxis: -0.5, S: 0.2 T: 0.4 The CUBE reduction instruction requires a specific mapping for the input vector: src0 = Rn.zzxy src1 = Rn.yxzz It's probably related to the way the instruction works internally? If we look at the individual components per ALU unit: z y -> Compare y/z z x -> Compare x/z x z -> Compare x/z y z -> Compare y/z */ sint32 outputType; src->add("redcCUBE("); src->add("float4("); _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("),"); src->add("float4("); _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("),"); src->add("cubeMapSTM,cubeMapFaceId);" _CRLF); // dst.X (S) outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[0]); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("cubeMapSTM.x"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); // dst.Y (T) outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[1]); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[1]); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("cubeMapSTM.y"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); // dst.Z (MajorAxis) outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[2]); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[2]); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add("cubeMapSTM.z"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); src->add(";" _CRLF); // dst.W (FaceId) outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[3]); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[3]); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add("cubeMapFaceId"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); src->add(";" _CRLF); } else cemu_assert_unimplemented(); } static void _emitALUClauseRegisterBackupCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, sint32 startIndex) { sint32 instructionGroupIndex = cfInstruction->instructionsALU[startIndex].instructionGroupIndex; size_t groupSize = 1; while ((startIndex + groupSize) < cfInstruction->instructionsALU.size()) { if (instructionGroupIndex != cfInstruction->instructionsALU[startIndex + groupSize].instructionGroupIndex) break; groupSize++; } shaderContext->aluPVPSState->CreateGPRTemporaries(shaderContext, { cfInstruction->instructionsALU.data() + startIndex, groupSize }); } /* bool _isPVUsedInNextGroup(LatteDecompilerCFInstruction* cfInstruction, sint32 startIndex, sint32 pvUnit) { sint32 currentGroupIndex = cfInstruction->instructionsALU[startIndex].instructionGroupIndex; for (sint32 i = startIndex + 1; i < (sint32)cfInstruction->instructionsALU.size(); i++) { LatteDecompilerALUInstruction& aluInstructionItr = cfInstruction->instructionsALU[i]; if(aluInstructionItr.instructionGroupIndex == currentGroupIndex ) continue; if ((sint32)aluInstructionItr.instructionGroupIndex > currentGroupIndex + 1) return false; // check OP code type if (aluInstructionItr.isOP3) { // op0 if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[0].sel)) { uint32 chan = aluInstructionItr.sourceOperand[0].chan; if (pvUnit == chan) return true; } // op1 if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[1].sel)) { uint32 chan = aluInstructionItr.sourceOperand[1].chan; if (pvUnit == chan) return true; } // op2 if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[2].sel)) { uint32 chan = aluInstructionItr.sourceOperand[2].chan; if (pvUnit == chan) return true; } } else { // op0 if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[0].sel)) { uint32 chan = aluInstructionItr.sourceOperand[0].chan; if (pvUnit == chan) return true; } // op1 if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[1].sel)) { uint32 chan = aluInstructionItr.sourceOperand[1].chan; if (pvUnit == chan) return true; } // todo: Not all operations use both operands } } return false; } */ static void _emitFloat3(LatteDecompilerShaderContext* shaderContext, uint32 dataType, LatteDecompilerALUInstruction* aluInst0, sint32 opIdx0, LatteDecompilerALUInstruction* aluInst1, sint32 opIdx1, LatteDecompilerALUInstruction* aluInst2, sint32 opIdx2) { StringBuf* src = shaderContext->shaderSource; if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) { src->add("float3("); _emitOperandInputCode(shaderContext, aluInst0, opIdx0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluInst1, opIdx1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitOperandInputCode(shaderContext, aluInst2, opIdx2, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); } else if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { src->add("int3("); _emitOperandInputCode(shaderContext, aluInst0, opIdx0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(","); _emitOperandInputCode(shaderContext, aluInst1, opIdx1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(","); _emitOperandInputCode(shaderContext, aluInst2, opIdx2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")"); } else cemu_assert_unimplemented(); } static void _emitGPRVectorAssignment(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction** aluInstructions, sint32 count) { StringBuf* src = shaderContext->shaderSource; // output var name (GPR) src->add(_getRegisterVarName(shaderContext, aluInstructions[0]->destGpr, -1)); src->add("."); for (sint32 f = 0; f < count; f++) { src->add(_getElementStrByIndex(aluInstructions[f]->destElem)); } src->add(" = "); } static void _emitALUClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { ALUClauseTemporariesState pvpsState; shaderContext->aluPVPSState = &pvpsState; StringBuf* src = shaderContext->shaderSource; LatteDecompilerALUInstruction* aluRedcInstruction[4]; size_t groupStartIndex = 0; for(size_t i=0; i<cfInstruction->instructionsALU.size(); i++) { LatteDecompilerALUInstruction& aluInstruction = cfInstruction->instructionsALU[i]; if( aluInstruction.indexInGroup == 0 ) { src->addFmt("// {}" _CRLF, aluInstruction.instructionGroupIndex); // apply PV/PS updates for previous group if (i > 0) { pvpsState.TrackGroupOutputPVPS(shaderContext, cfInstruction->instructionsALU.data() + groupStartIndex, i - groupStartIndex); } groupStartIndex = i; // backup registers which are read after being written _emitALUClauseRegisterBackupCode(shaderContext, cfInstruction, i); } // detect reduction instructions and use a special handler bool isReductionOperation = _isReductionInstruction(&aluInstruction); if( isReductionOperation ) { cemu_assert_debug((i + 4) <= cfInstruction->instructionsALU.size()); aluRedcInstruction[0] = &aluInstruction; aluRedcInstruction[1] = &cfInstruction->instructionsALU[i + 1]; aluRedcInstruction[2] = &cfInstruction->instructionsALU[i + 2]; aluRedcInstruction[3] = &cfInstruction->instructionsALU[i + 3]; if( aluRedcInstruction[0]->isOP3 != aluRedcInstruction[1]->isOP3 || aluRedcInstruction[1]->isOP3 != aluRedcInstruction[2]->isOP3 || aluRedcInstruction[2]->isOP3 != aluRedcInstruction[3]->isOP3 ) debugBreakpoint(); if( aluRedcInstruction[0]->opcode != aluRedcInstruction[1]->opcode || aluRedcInstruction[1]->opcode != aluRedcInstruction[2]->opcode || aluRedcInstruction[2]->opcode != aluRedcInstruction[3]->opcode ) debugBreakpoint(); if( aluRedcInstruction[0]->omod != aluRedcInstruction[1]->omod || aluRedcInstruction[1]->omod != aluRedcInstruction[2]->omod || aluRedcInstruction[2]->omod != aluRedcInstruction[3]->omod ) debugBreakpoint(); if( aluRedcInstruction[0]->destClamp != aluRedcInstruction[1]->destClamp || aluRedcInstruction[1]->destClamp != aluRedcInstruction[2]->destClamp || aluRedcInstruction[2]->destClamp != aluRedcInstruction[3]->destClamp ) debugBreakpoint(); _emitALUReductionInstructionCode(shaderContext, aluRedcInstruction); i += 3; // skip the instructions that are part of the reduction operation } else /* not a reduction operation */ { if( aluInstruction.isOP3 ) { // op3 _emitALUOP3InstructionCode(shaderContext, cfInstruction, &aluInstruction); } else { // op2 if( aluInstruction.opcode == ALU_OP2_INST_NOP ) continue; // skip NOP instruction _emitALUOP2InstructionCode(shaderContext, cfInstruction, &aluInstruction); } } // handle omod sint32 outputDataType = _getALUInstructionOutputDataType(shaderContext, &aluInstruction); if( aluInstruction.omod != ALU_OMOD_NONE ) { if( outputDataType == LATTE_DECOMPILER_DTYPE_FLOAT ) { _emitInstructionOutputVariableName(shaderContext, &aluInstruction); if( aluInstruction.omod == ALU_OMOD_MUL2 ) src->add(" *= 2.0;" _CRLF); else if( aluInstruction.omod == ALU_OMOD_MUL4 ) src->add(" *= 4.0;" _CRLF); else if( aluInstruction.omod == ALU_OMOD_DIV2 ) src->add(" /= 2.0;" _CRLF); } else if( outputDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(" = "); src->add("as_type<int>(as_type<float>("); // TODO: correct? _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(")"); if( aluInstruction.omod == 1 ) src->add(" * 2.0"); else if( aluInstruction.omod == 2 ) src->add(" * 4.0"); else if( aluInstruction.omod == 3 ) src->add(" / 2.0"); src->add(");" _CRLF); } else { cemu_assert_unimplemented(); } } // handle clamp if( aluInstruction.destClamp != 0 ) { if( outputDataType == LATTE_DECOMPILER_DTYPE_FLOAT ) { _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(" = clamp("); _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(", 0.0, 1.0);" _CRLF); } else if( outputDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(" = clampFI32("); _emitInstructionOutputVariableName(shaderContext, &aluInstruction); src->add(");" _CRLF); } else { cemu_assert_unimplemented(); } } // handle result broadcasting for reduction instructions if( isReductionOperation ) { // reduction operations set all four PV components (todo: Needs further research. According to AMD docs, dot4 only sets PV.x? update: Unlike DOT4, CUBE sets all PV elements accordingly to their GPR output?) if( aluRedcInstruction[0]->opcode == ALU_OP2_INST_CUBE ) { // CUBE for (sint32 f = 0; f < 4; f++) { if (aluRedcInstruction[f]->writeMask != 0) continue; _emitInstructionPVPSOutputVariableName(shaderContext, aluRedcInstruction[f]); src->add(" = "); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); src->add(";" _CRLF); } } else { // DOT4, DOT4_IEEE, etc. // reduction operation result is only set for output in redc[0], we also need to update redc[1] to redc[3] for(sint32 f=0; f<4; f++) { if( aluRedcInstruction[f]->writeMask == 0 ) _emitInstructionPVPSOutputVariableName(shaderContext, aluRedcInstruction[f]); else { if (f == 0) continue; _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[f]); } src->add(" = "); _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); src->add(";" _CRLF); } } } } shaderContext->aluPVPSState = nullptr; } /* * Emits code to access one component (xyzw) of the texture coordinate input vector */ static void _emitTEXSampleCoordInputComponent(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction, sint32 componentIndex, sint32 interpretSrcAsType) { cemu_assert(componentIndex >= 0 && componentIndex < 4); cemu_assert_debug(interpretSrcAsType == LATTE_DECOMPILER_DTYPE_SIGNED_INT || interpretSrcAsType == LATTE_DECOMPILER_DTYPE_FLOAT); StringBuf* src = shaderContext->shaderSource; sint32 elementSel = texInstruction->textureFetch.srcSel[componentIndex]; if (elementSel < 4) { _emitRegisterChannelAccessCode(shaderContext, texInstruction->srcGpr, elementSel, interpretSrcAsType); return; } const char* resultElemTable[4] = {"x","y","z","w"}; if(interpretSrcAsType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) { if( elementSel == 4 ) src->add("as_type<int>(0.0)"); else if( elementSel == 5 ) src->add("as_type<int>(1.0)"); } else if(interpretSrcAsType == LATTE_DECOMPILER_DTYPE_FLOAT ) { if( elementSel == 4 ) src->add("0.0"); else if( elementSel == 5 ) src->add("1.0"); } } static const char* _texGprAccessElemTable[8] = {"x","y","z","w","_","_","_","_"}; static char* _getTexGPRAccess(LatteDecompilerShaderContext* shaderContext, sint32 gprIndex, uint32 dataType, sint8 selX, sint8 selY, sint8 selZ, sint8 selW, char* tempBuffer) { // as_type<float>(R{}i.w) *tempBuffer = '\0'; uint8 elemCount = (selX >= 0 ? 1 : 0) + (selY >= 0 ? 1 : 0) + (selZ >= 0 ? 1 : 0) + (selW >= 0 ? 1 : 0); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) ; // no conversion else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) { if (elemCount == 1) strcat(tempBuffer, "as_type<float>("); else strcat(tempBuffer, ("as_type<float" + std::to_string(elemCount) + ">(").c_str()); } else cemu_assert_unimplemented(); strcat(tempBuffer, _getRegisterVarName(shaderContext, gprIndex)); // _texGprAccessElemTable strcat(tempBuffer, "."); if (selX >= 0) strcat(tempBuffer, _texGprAccessElemTable[selX]); if (selY >= 0) strcat(tempBuffer, _texGprAccessElemTable[selY]); if (selZ >= 0) strcat(tempBuffer, _texGprAccessElemTable[selZ]); if (selW >= 0) strcat(tempBuffer, _texGprAccessElemTable[selW]); if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) ; // no conversion else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) strcat(tempBuffer, ")"); else cemu_assert_unimplemented(); } else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) { if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) cemu_assert_unimplemented(); else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) ; // no conversion else cemu_assert_unimplemented(); strcat(tempBuffer, _getRegisterVarName(shaderContext, gprIndex)); // _texGprAccessElemTable strcat(tempBuffer, "."); if (selX >= 0) strcat(tempBuffer, _texGprAccessElemTable[selX]); if (selY >= 0) strcat(tempBuffer, _texGprAccessElemTable[selY]); if (selZ >= 0) strcat(tempBuffer, _texGprAccessElemTable[selZ]); if (selW >= 0) strcat(tempBuffer, _texGprAccessElemTable[selW]); if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) cemu_assert_unimplemented(); else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) ; // no conversion else cemu_assert_unimplemented(); } else cemu_assert_unimplemented(); return tempBuffer; } static void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; if (texInstruction->textureFetch.textureIndex < 0 || texInstruction->textureFetch.textureIndex >= LATTE_NUM_MAX_TEX_UNITS) { // skip out of bounds texture unit access return; } auto texDim = shaderContext->shader->textureUnitDim[texInstruction->textureFetch.textureIndex]; char tempBuffer0[32]; char tempBuffer1[32]; src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); const char* resultElemTable[4] = {"x","y","z","w"}; sint32 numWrittenElements = 0; for(sint32 f = 0; f < 4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[f]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { debugBreakpoint(); } } // texture sampler opcode uint32 texOpcode = texInstruction->opcode; // TODO: is this needed? if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) { // vertex shader forces LOD to zero, but certain sampler types don't support textureLod(...) API if (texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) texOpcode = GPU7_TEX_INST_SAMPLE_C; } // check if offset is used bool hasOffset = false; if( texInstruction->textureFetch.offsetX != 0 || texInstruction->textureFetch.offsetY != 0 || texInstruction->textureFetch.offsetZ != 0 ) hasOffset = true; // emit sample code if (shaderContext->shader->textureIsIntegerFormat[texInstruction->textureFetch.textureIndex]) { // integer samplers if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) // uint to int { if (numWrittenElements == 1) src->add(" = int("); else shaderContext->shaderSource->addFmt(" = int{}(", numWrittenElements); } else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) { if (numWrittenElements == 1) src->add(" = as_type<float>("); else shaderContext->shaderSource->addFmt(" = as_type<float{}>(", numWrittenElements); } } else { // float samplers if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { if (numWrittenElements == 1) src->add(" = as_type<int>("); else shaderContext->shaderSource->addFmt(" = as_type<int{}>(", numWrittenElements); } else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->add(" = ("); } bool isCompare = shaderContext->shader->textureUsesDepthCompare[texInstruction->textureFetch.textureIndex]; bool emulateCompare = (isCompare && !IsValidDepthTextureType(texDim)); bool isGather = (texOpcode == GPU7_TEX_INST_FETCH4); bool unnormalizationHandled = false; bool useTexelCoordinates = false; bool isRead = ((texOpcode == GPU7_TEX_INST_SAMPLE && (texInstruction->textureFetch.unnormalized[0] && texInstruction->textureFetch.unnormalized[1] && texInstruction->textureFetch.unnormalized[2] && texInstruction->textureFetch.unnormalized[3])) || texOpcode == GPU7_TEX_INST_LD); // handle illegal combinations if (texOpcode == GPU7_TEX_INST_FETCH4 && (texDim == Latte::E_DIM::DIM_1D || texDim == Latte::E_DIM::DIM_1D_ARRAY)) { // fetch4 is not allowed on 1D textures // seen in YWW during boss fight of Level 1-4 // todo - investigate what this returns on actual HW if (numWrittenElements == 1) shaderContext->shaderSource->add("0.0"); else shaderContext->shaderSource->addFmt("float{}(0.0)", numWrittenElements); shaderContext->shaderSource->add(");" _CRLF); return; } // Do a framebuffer fetch if possible uint8 renderTargetIndex = shaderContext->shader->textureRenderTargetIndex[texInstruction->textureFetch.textureIndex]; if (static_cast<MetalRenderer*>(g_renderer.get())->SupportsFramebufferFetch() && renderTargetIndex != 255) { // TODO: support comparison samplers // TODO: support swizzling src->addFmt("col{}", renderTargetIndex); } else { // sample_compare returns a float, need to convert to float4 if (isCompare) src->addFmt("float4("); if (emulateCompare) { cemu_assert_debug(!isGather); src->add("sampleCompareEmulate("); } src->addFmt("tex{}", texInstruction->textureFetch.textureIndex); if (!emulateCompare) { src->add("."); if (isRead) { if (hasOffset) cemu_assert_unimplemented(); src->add("read("); unnormalizationHandled = true; useTexelCoordinates = true; } else { if (isGather) src->add("gather"); else src->add("sample"); if (isCompare) src->add("_compare"); src->addFmt("(samplr{}, ", texInstruction->textureFetch.textureIndex); } } else { src->addFmt(", samplr{}, ", texInstruction->textureFetch.textureIndex); } // for textureGather() add shift (todo: depends on rounding mode set in sampler registers?) if (texOpcode == GPU7_TEX_INST_FETCH4) { if (texDim == Latte::E_DIM::DIM_2D) { //src->addFmt2("(vec2(-0.1) / vec2(textureSize(tex{},0).xy)) + ", texInstruction->textureIndex); // vec2(-0.00001) is minimum to break Nvidia // vec2(0.0001) is minimum to fix shadows on Intel, also fixes it on AMD (Windows and Linux) // todo - emulating coordinate rounding mode correctly is tricky // GX2 supports two modes: Truncate or rounding according to DX9 rules // Vulkan uses truncate mode when point sampling (min and mag is both nearest) otherwise it uses rounding // adding a small fixed bias is enough to avoid vendor-specific cases where small inaccuracies cause the number to get rounded down due to truncation src->addFmt("float2(0.0001) + "); } } const sint32 texCoordDataType = (texOpcode == GPU7_TEX_INST_LD) ? LATTE_DECOMPILER_DTYPE_SIGNED_INT : LATTE_DECOMPILER_DTYPE_FLOAT; if(useTexelCoordinates) { // handle integer coordinates for texelFetch if (texDim == Latte::E_DIM::DIM_2D || texDim == Latte::E_DIM::DIM_2D_MSAA) { src->add("uint2("); src->add("float2("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, texCoordDataType); src->addFmt(", "); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, texCoordDataType); src->addFmt(")*supportBuffer.tex{}Scale", texInstruction->textureFetch.textureIndex); // close float2 and scale src->add("), 0"); // close int2 and lod param // todo - lod } else if (texDim == Latte::E_DIM::DIM_1D) { // VC DS games forget to initialize textures and use texel fetch on an uninitialized texture (a dim of 0 maps to 1D) src->add("uint("); src->add("float("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, (texOpcode == GPU7_TEX_INST_LD) ? LATTE_DECOMPILER_DTYPE_SIGNED_INT : LATTE_DECOMPILER_DTYPE_FLOAT); src->addFmt(")*supportBuffer.tex{}Scale.x", texInstruction->textureFetch.textureIndex); src->add("), 0"); // todo - lod } else cemu_assert_debug(false); } else /* useTexelCoordinates == false */ { // float coordinates if ( (texOpcode == GPU7_TEX_INST_SAMPLE_C || texOpcode == GPU7_TEX_INST_SAMPLE_C_L || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) ) { // shadow sampler if (texDim == Latte::E_DIM::DIM_2D_ARRAY) { // 3 coords + compare value src->add("float2("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(", "); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("), uint(rint("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("))"); src->addFmt(", {}", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer0)); } else if (texDim == Latte::E_DIM::DIM_CUBEMAP) { // 2 coords + faceId if (texInstruction->textureFetch.srcSel[0] >= 4 || texInstruction->textureFetch.srcSel[1] >= 4) { debugBreakpoint(); } src->addFmt("redcCUBEReverse({},", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0)); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->addFmt(")"); src->addFmt(", uint(cubeMapArrayIndex{})", texInstruction->textureFetch.textureIndex); // cubemap index } else if (texDim == Latte::E_DIM::DIM_1D) { // 1 coord + 1 unused coord (per spec) + compare value if (texInstruction->textureFetch.srcSel[0] >= 4) { debugBreakpoint(); } src->addFmt("{}, {}", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], -1, -1, -1, tempBuffer0), _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer1)); } else { // 2 coords + compare value (as float3) if (texInstruction->textureFetch.srcSel[0] >= 4 && texInstruction->textureFetch.srcSel[1] >= 4) { debugBreakpoint(); } src->addFmt("float2({}), {}", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0), _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer1)); } } else if(texDim == Latte::E_DIM::DIM_2D_ARRAY) { // 3 coords src->add("float2("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(", "); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("), uint(rint("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_FLOAT); src->add("))"); } else if(texDim == Latte::E_DIM::DIM_3D) { // 3 coords src->add("float3("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(", "); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(", "); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); } else if( texDim == Latte::E_DIM::DIM_CUBEMAP ) { // 2 coords + faceId cemu_assert_debug(texInstruction->textureFetch.srcSel[0] < 4); cemu_assert_debug(texInstruction->textureFetch.srcSel[1] < 4); src->addFmt("redcCUBEReverse({},", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0)); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(")"); src->addFmt(", uint(cubeMapArrayIndex{})", texInstruction->textureFetch.textureIndex); // cubemap index } else if( texDim == Latte::E_DIM::DIM_1D ) { // 1 coord src->add(_getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], -1, -1, -1, tempBuffer0)); } else { // 2 coords src->add("float2("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(","); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); // avoid truncate to effectively round downwards on texel edges if (ActiveSettings::ForceSamplerRoundToPrecision()) src->addFmt("+ float2(1.0)/float2(tex{}.get_width(), tex{}.get_height())/512.0", texInstruction->textureFetch.textureIndex, texInstruction->textureFetch.textureIndex); } // lod or lod bias parameter // 1D textures don't support lod if (texDim != Latte::E_DIM::DIM_1D && texDim != Latte::E_DIM::DIM_1D_ARRAY) { if (texOpcode == GPU7_TEX_INST_SAMPLE_L || texOpcode == GPU7_TEX_INST_SAMPLE_LB || texOpcode == GPU7_TEX_INST_SAMPLE_C_L) { src->add(", "); if (texOpcode == GPU7_TEX_INST_SAMPLE_LB) { src->addFmt("bias({})", _FormatFloatAsConstant((float)texInstruction->textureFetch.lodBias / 16.0f)); } else { // TODO: is this correct? src->add("level("); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 3, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); } } else if (texOpcode == GPU7_TEX_INST_SAMPLE_LZ || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) { src->add(", level(0.0)"); } } } // gradient parameters if (texOpcode == GPU7_TEX_INST_SAMPLE_G) { if (texDim == Latte::E_DIM::DIM_2D || texDim == Latte::E_DIM::DIM_1D) { src->add(", gradient2d(gradH.xy, gradV.xy)"); } else { cemu_assert_unimplemented(); } } // offset if( texOpcode == GPU7_TEX_INST_SAMPLE_L || texOpcode == GPU7_TEX_INST_SAMPLE_LZ || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ || texOpcode == GPU7_TEX_INST_SAMPLE || texOpcode == GPU7_TEX_INST_SAMPLE_C ) { if( hasOffset ) { uint8 offsetComponentCount = 0; if( texDim == Latte::E_DIM::DIM_1D ) offsetComponentCount = 1; else if( texDim == Latte::E_DIM::DIM_2D ) offsetComponentCount = 2; else if( texDim == Latte::E_DIM::DIM_3D ) offsetComponentCount = 3; else if( texDim == Latte::E_DIM::DIM_2D_ARRAY ) offsetComponentCount = 2; else cemu_assert_unimplemented(); if( (texInstruction->textureFetch.offsetX&1) ) cemu_assert_unimplemented(); if( (texInstruction->textureFetch.offsetY&1) ) cemu_assert_unimplemented(); if ((texInstruction->textureFetch.offsetZ & 1)) cemu_assert_unimplemented(); if( offsetComponentCount == 1 ) src->addFmt(",{}", texInstruction->textureFetch.offsetX/2); else if( offsetComponentCount == 2 ) src->addFmt(",int2({},{})", texInstruction->textureFetch.offsetX/2, texInstruction->textureFetch.offsetY/2, texInstruction->textureFetch.offsetZ/2); else if( offsetComponentCount == 3 ) src->addFmt(",int3({},{},{})", texInstruction->textureFetch.offsetX/2, texInstruction->textureFetch.offsetY/2, texInstruction->textureFetch.offsetZ/2); } } // lod bias (TODO: wht?) src->add(")"); } if (isCompare) src->add(")"); if (texOpcode == GPU7_TEX_INST_SAMPLE_C || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) { src->add("."); if (numWrittenElements > 1) { // result is copied into multiple channels for (sint32 f = 0; f < numWrittenElements; f++) { cemu_assert_debug(texInstruction->dstSel[f] == 0); // only x component is defined src->add("x"); } } else { src->add("x"); } } else { src->add("."); for (sint32 f = 0; f < 4; f++) { if (texInstruction->dstSel[f] < 4) { uint8 elemIndex = texInstruction->dstSel[f]; if (isGather) { // 's textureGather() and GPU7's FETCH4 instruction have a different order of elements // xyzw: top-left, top-right, bottom-right, bottom-left // textureGather xyzw // fetch4 yzxw // translate index from fetch4 to textureGather order static uint8 fetchToGather[4] = { 2, // x -> z 0, // y -> x 1, // z -> y 3, // w -> w }; elemIndex = fetchToGather[elemIndex]; } src->add(resultElemTable[elemIndex]); } else if (texInstruction->dstSel[f] == 7) { // masked and not written } else { cemu_assert_unimplemented(); } } } src->add(");"); // debug #ifdef CEMU_DEBUG_ASSERT if(texInstruction->opcode == GPU7_TEX_INST_LD ) src->add(" // TEX_INST_LD"); else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE ) src->add(" // TEX_INST_SAMPLE"); else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_L ) src->add(" // TEX_INST_SAMPLE_L"); else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_LZ ) src->add(" // TEX_INST_SAMPLE_LZ"); else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_C ) src->add(" // TEX_INST_SAMPLE_C"); else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_G ) src->add(" // TEX_INST_SAMPLE_G"); else src->addFmt(" // 0x{:02x}", texInstruction->opcode); if (texInstruction->opcode != texOpcode) src->addFmt(" (applied as 0x{:02x})", texOpcode); src->addFmt(" OffsetXYZ {:02x} {:02x} {:02x}", (uint8)texInstruction->textureFetch.offsetX&0xFF, (uint8)texInstruction->textureFetch.offsetY&0xFF, (uint8)texInstruction->textureFetch.offsetZ&0xFF); #endif src->add("" _CRLF); } static void _emitTEXGetTextureResInfoCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; src->addFmt("R{}", texInstruction->dstGpr); src->add("i"); src->add("."); const char* resultElemTable[4] = {"x","y","z","w"}; sint32 numWrittenElements = 0; for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[f]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { cemu_assert_unimplemented(); } } // todo - mip index parameter? if (static_cast<MetalRenderer*>(g_renderer.get())->SupportsFramebufferFetch() && shaderContext->shader->textureRenderTargetIndex[texInstruction->textureFetch.textureIndex] != 255) { // TODO: use the render target size src->addFmt(" = int4(1920, 1080, 1, 1)."); } else { auto texDim = shaderContext->shader->textureUnitDim[texInstruction->textureFetch.textureIndex]; if (texDim == Latte::E_DIM::DIM_1D) src->addFmt(" = int4(tex{}.get_width(), 1, 1, 1).", texInstruction->textureFetch.textureIndex); else if (texDim == Latte::E_DIM::DIM_1D_ARRAY) src->addFmt(" = int4(tex{}.get_width(), tex{}.get_array_size(), 1, 1).", texInstruction->textureFetch.textureIndex, texInstruction->textureFetch.textureIndex); else if (texDim == Latte::E_DIM::DIM_2D || texDim == Latte::E_DIM::DIM_2D_MSAA) src->addFmt(" = int4(tex{}.get_width(), tex{}.get_height(), 1, 1).", texInstruction->textureFetch.textureIndex, texInstruction->textureFetch.textureIndex); else if (texDim == Latte::E_DIM::DIM_2D_ARRAY) src->addFmt(" = int4(tex{}.get_width(), tex{}.get_height(), tex{}.get_array_size(), 1).", texInstruction->textureFetch.textureIndex, texInstruction->textureFetch.textureIndex, texInstruction->textureFetch.textureIndex); else { cemu_assert_debug(false); src->addFmt(" = int4(tex{}.get_width(), tex{}.get_height(), 1, 1).", texInstruction->textureFetch.textureIndex, texInstruction->textureFetch.textureIndex); } } for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[texInstruction->dstSel[f]]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { debugBreakpoint(); } } src->add(";" _CRLF); } static void _emitTEXGetCompTexLodCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); const char* resultElemTable[4] = {"x","y","z","w"}; sint32 numWrittenElements = 0; for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[f]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { debugBreakpoint(); } } src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); if (static_cast<MetalRenderer*>(g_renderer.get())->SupportsFramebufferFetch() && shaderContext->shader->textureRenderTargetIndex[texInstruction->textureFetch.textureIndex] != 255) { // We assume that textures accessed as framebuffer fetch are always sampled at pixel coordinates, therefore the lod would always be 0.0 src->add("float4(0.0, 0.0, 0.0, 0.0)"); } else { if (shaderContext->shader->textureUnitDim[texInstruction->textureFetch.textureIndex] == Latte::E_DIM::DIM_CUBEMAP) { // 3 coordinates if(shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("float4(textureCalculateLod(tex{}, samplr{}, {}.{}{}{}), 0.0, 0.0)", texInstruction->textureFetch.textureIndex, texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]], resultElemTable[texInstruction->textureFetch.srcSel[2]]); else src->addFmt("float4(textureCalculateLod(tex{}, samplr{}, as_type<float3>({}.{}{}{})), 0.0, 0.0)", texInstruction->textureFetch.textureIndex, texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]], resultElemTable[texInstruction->textureFetch.srcSel[2]]); } else { if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("float4(textureCalculateLod(tex{}, samplr{}, {}.{}{}), 0.0, 0.0)", texInstruction->textureFetch.textureIndex, texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]]); else src->addFmt("float4(textureCalculateLod(tex{}, samplr{}, as_type<float2>({}.{}{})), 0.0, 0.0)", texInstruction->textureFetch.textureIndex, texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]]); debugBreakpoint(); } } _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); src->add("."); for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[texInstruction->dstSel[f]]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { debugBreakpoint(); } } src->add(";" _CRLF); } static void _emitTEXSetCubemapIndexCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; src->addFmt("cubeMapArrayIndex{}", texInstruction->textureFetch.textureIndex); const char* resultElemTable[4] = {"x","y","z","w"}; if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt(" = as_type<float>(R{}i.{});" _CRLF, texInstruction->srcGpr, resultElemTable[texInstruction->textureFetch.srcSel[0]]); else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt(" = R{}f.{};" _CRLF, texInstruction->srcGpr, resultElemTable[texInstruction->textureFetch.srcSel[0]]); else cemu_assert_unimplemented(); } static void _emitTEXGetGradientsHV(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; sint32 componentCount = 0; for (sint32 i = 0; i < 4; i++) { if (texInstruction->dstSel[i] == 7) continue; componentCount++; } src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); const char* resultElemTable[4] = { "x","y","z","w" }; sint32 numWrittenElements = 0; for (sint32 f = 0; f < 4; f++) { if (texInstruction->dstSel[f] < 4) { src->add(resultElemTable[f]); numWrittenElements++; } else if (texInstruction->dstSel[f] == 7) { // masked and not written } else { debugBreakpoint(); } } const char* funcName; if (texInstruction->opcode == GPU7_TEX_INST_GET_GRADIENTS_H) funcName = "dfdx"; else funcName = "dfdy"; src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType, componentCount); src->addFmt("{}(", funcName); _emitRegisterAccessCode(shaderContext, texInstruction->srcGpr, (componentCount >= 1) ? texInstruction->textureFetch.srcSel[0] : -1, (componentCount >= 2) ? texInstruction->textureFetch.srcSel[1] : -1, (componentCount >= 3) ? texInstruction->textureFetch.srcSel[2] : -1, (componentCount >= 4) ? texInstruction->textureFetch.srcSel[3] : -1, LATTE_DECOMPILER_DTYPE_FLOAT); src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); src->add(";" _CRLF); } static void _emitTEXSetGradientsHV(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; if (texInstruction->opcode == GPU7_TEX_INST_SET_GRADIENTS_H) src->add("gradH = "); else src->add("gradV = "); _emitRegisterAccessCode(shaderContext, texInstruction->srcGpr, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], texInstruction->textureFetch.srcSel[2], texInstruction->textureFetch.srcSel[3], LATTE_DECOMPILER_DTYPE_FLOAT); src->add(";" _CRLF); } static void _emitGSReadInputVFetchCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); const char* resultElemTable[4] = {"x","y","z","w"}; sint32 numWrittenElements = 0; for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[f]); numWrittenElements++; } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { cemu_assert_unimplemented(); } } src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType, numWrittenElements); src->add("(objectPayload.vertexOut["); if (texInstruction->textureFetch.srcSel[0] >= 4) cemu_assert_unimplemented(); if (texInstruction->textureFetch.srcSel[1] >= 4) cemu_assert_unimplemented(); src->add("vertexIndex"); src->addFmt("].passParameterSem{}.", texInstruction->textureFetch.offset/16); for(sint32 f=0; f<4; f++) { if( texInstruction->dstSel[f] < 4 ) { src->add(resultElemTable[texInstruction->dstSel[f]]); } else if( texInstruction->dstSel[f] == 7 ) { // masked and not written } else { cemu_assert_unimplemented(); } } src->add(")"); _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); src->add(";" _CRLF); } static sint32 _writeDestMaskXYZW(LatteDecompilerShaderContext* shaderContext, sint8* dstSel) { StringBuf* src = shaderContext->shaderSource; const char* resultElemTable[4] = { "x","y","z","w" }; sint32 numWrittenElements = 0; for (sint32 f = 0; f < 4; f++) { if (dstSel[f] < 4) { src->add(resultElemTable[f]); numWrittenElements++; } else if (dstSel[f] == 7) { // masked and not written } else { cemu_assert_unimplemented(); } } return numWrittenElements; } static void _emitTEXVFetchCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { // handle special case where geometry shader reads input attributes from vertex shader via ringbuffer StringBuf* src = shaderContext->shaderSource; if( texInstruction->textureFetch.textureIndex == 0x9F && shaderContext->shaderType == LatteConst::ShaderType::Geometry ) { _emitGSReadInputVFetchCode(shaderContext, texInstruction); return; } src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); _writeDestMaskXYZW(shaderContext, texInstruction->dstSel); const char* resultElemTable[4] = {"x","y","z","w"}; uint32 numWrittenElements = 0; for (sint32 f=0; f<4; f++) { if (texInstruction->dstSel[f] < 4) numWrittenElements++; } src->add(" = "); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { if (numWrittenElements == 1) src->add("as_type<int>("); else src->addFmt("as_type<int{}>(", numWrittenElements); } else src->add("("); src->addFmt("ubuff{}.d[", texInstruction->textureFetch.textureIndex - 0x80); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt("{}.{}", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]]); else src->addFmt("as_type<int>({}.{})", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]]); src->add("]."); for (sint32 f=0; f<4; f++) { if (texInstruction->dstSel[f] < 4) { src->add(resultElemTable[texInstruction->dstSel[f]]); } else if (texInstruction->dstSel[f] == 7) { // masked and not written } else { debugBreakpoint(); } } src->add(");" _CRLF); } static void _emitTEXReadMemCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) { StringBuf* src = shaderContext->shaderSource; src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); src->add("."); sint32 count = _writeDestMaskXYZW(shaderContext, texInstruction->dstSel); src->add(" = "); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { if (count == 1) src->add("as_type<int>("); else src->addFmt("as_type<int{}>(", count); } else src->add("("); sint32 readCount; if (texInstruction->memRead.format == FMT_32_FLOAT) { readCount = 1; // todo src->add("0.0"); } else if (texInstruction->memRead.format == FMT_32_32_FLOAT) { readCount = 2; // todo src->add("float2(0.0,0.0)"); } else if (texInstruction->memRead.format == FMT_32_32_32_FLOAT) { readCount = 3; // todo src->add("float3(0.0,0.0,0.0)"); } else { cemu_assert_unimplemented(); } if (count < readCount) { if (count == 1) src->add(".x"); else if (count == 2) src->add(".xy"); else if (count == 3) src->add(".xyz"); } src->add(");" _CRLF); } static void _emitTEXClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { cemu_assert_debug(cfInstruction->instructionsALU.empty()); for(auto& texInstruction : cfInstruction->instructionsTEX) { if( texInstruction.opcode == GPU7_TEX_INST_SAMPLE || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_L || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_LB || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_LZ || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_L || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_LZ || texInstruction.opcode == GPU7_TEX_INST_FETCH4 || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_G || texInstruction.opcode == GPU7_TEX_INST_LD ) _emitTEXSampleTextureCode(shaderContext, &texInstruction); else if( texInstruction.opcode == GPU7_TEX_INST_GET_TEXTURE_RESINFO ) _emitTEXGetTextureResInfoCode(shaderContext, &texInstruction); else if( texInstruction.opcode == GPU7_TEX_INST_GET_COMP_TEX_LOD ) _emitTEXGetCompTexLodCode(shaderContext, &texInstruction); else if( texInstruction.opcode == GPU7_TEX_INST_SET_CUBEMAP_INDEX ) _emitTEXSetCubemapIndexCode(shaderContext, &texInstruction); else if (texInstruction.opcode == GPU7_TEX_INST_GET_GRADIENTS_H || texInstruction.opcode == GPU7_TEX_INST_GET_GRADIENTS_V) _emitTEXGetGradientsHV(shaderContext, &texInstruction); else if (texInstruction.opcode == GPU7_TEX_INST_SET_GRADIENTS_H || texInstruction.opcode == GPU7_TEX_INST_SET_GRADIENTS_V) _emitTEXSetGradientsHV(shaderContext, &texInstruction); else if (texInstruction.opcode == GPU7_TEX_INST_VFETCH) _emitTEXVFetchCode(shaderContext, &texInstruction); else if (texInstruction.opcode == GPU7_TEX_INST_MEM) _emitTEXReadMemCode(shaderContext, &texInstruction); else cemu_assert_unimplemented(); } } // generate the code for reading the source input GPR (or constants) for exports static void _emitExportGPRReadCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, sint32 requiredType, uint32 burstIndex) { StringBuf* src = shaderContext->shaderSource; uint32 numOutputs = 4; if( cfInstruction->type == GPU7_CF_INST_MEM_RING_WRITE ) { numOutputs = (cfInstruction->memWriteCompMask&1)?1:0; numOutputs += (cfInstruction->memWriteCompMask&2)?1:0; numOutputs += (cfInstruction->memWriteCompMask&4)?1:0; numOutputs += (cfInstruction->memWriteCompMask&8)?1:0; } if (requiredType == LATTE_DECOMPILER_DTYPE_FLOAT) { if(numOutputs == 1) src->add("float("); else src->addFmt("float{}(", numOutputs); } else if (requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) { if (numOutputs == 1) src->add("int("); else src->addFmt("int{}(", numOutputs); } else cemu_assert_unimplemented(); sint32 actualOutputs = 0; for(sint32 i=0; i<4; i++) { // todo: Use type of register element based on information from type tracker (currently we assume it's always a signed integer) uint32 exportSel = 0; if( cfInstruction->type == GPU7_CF_INST_MEM_RING_WRITE ) { exportSel = i; if( (cfInstruction->memWriteCompMask&(1<<i)) == 0 ) continue; // dont output } else { exportSel = cfInstruction->exportComponentSel[i]; } if( actualOutputs > 0 ) src->add(", "); actualOutputs++; if( exportSel < 4 ) { _emitRegisterAccessCode(shaderContext, cfInstruction->exportSourceGPR+burstIndex, exportSel, -1, -1, -1, requiredType); } else if (exportSel == 4) { // constant zero src->add("0"); } else if (exportSel == 5) { // constant one src->add("1.0"); } else if( exportSel == 7 ) { // element masked (which means 0 is exported?) src->add("0"); } else { cemu_assert_debug(false); src->add("0"); } } if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) src->add(")"); else if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) src->add(")"); else cemu_assert_unimplemented(); } static void _emitExportCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { StringBuf* src = shaderContext->shaderSource; src->add("// export" _CRLF); if(shaderContext->shaderType == LatteConst::ShaderType::Vertex ) { if (!shaderContext->contextRegistersNew->IsRasterizationEnabled()) { src->add("// Rasterization disabled" _CRLF); return; } if( cfInstruction->exportBurstCount != 0 ) debugBreakpoint(); if (cfInstruction->exportType == 1 && cfInstruction->exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION) { // export position // GX2 special state 0 disables rasterizer viewport offset and scaling (probably, exact mechanism is not known). Handle this here bool hasAnyViewportScaleDisabled = !shaderContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_SCALE_ENA() || !shaderContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_SCALE_ENA() || !shaderContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_SCALE_ENA(); if (hasAnyViewportScaleDisabled) { src->add("float4 finalPos = "); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); src->add(";" _CRLF); src->add("finalPos.xy = finalPos.xy * supportBuffer.windowSpaceToClipSpaceTransform - float2(1.0,1.0);" _CRLF); src->add("SET_POSITION(finalPos);"); } else { src->add("SET_POSITION("); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); src->add(");" _CRLF); } } else if (cfInstruction->exportType == 1 && cfInstruction->exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_POINT_SIZE ) { // export gl_PointSize if (shaderContext->analyzer.outputPointSize) { cemu_assert_debug(shaderContext->analyzer.writesPointSize); src->add("out.pointSize = ("); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); src->add(").x"); src->add(";" _CRLF); } } else if( cfInstruction->exportType == 2 && cfInstruction->exportArrayBase < 32 ) { // export parameter sint32 paramIndex = cfInstruction->exportArrayBase; uint32 vsSemanticId = _getVertexShaderOutParamSemanticId(shaderContext->contextRegisters, paramIndex); if (vsSemanticId != 0xFF) { src->addFmt("out.passParameterSem{} = ", vsSemanticId); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); src->add(";" _CRLF); } else { src->add("// skipped export to semanticId 255" _CRLF); } } else cemu_assert_unimplemented(); } else if(shaderContext->shaderType == LatteConst::ShaderType::Pixel ) { if( cfInstruction->exportType == 0 && cfInstruction->exportArrayBase < 8 ) { for(uint32 i=0; i<(cfInstruction->exportBurstCount+1); i++) { sint32 pixelColorOutputIndex = LatteDecompiler_getColorOutputIndexFromExportIndex(shaderContext, cfInstruction->exportArrayBase+i); // if color output is for target 0, then also handle alpha test bool alphaTestEnable = shaderContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_TEST_ENABLE(); auto alphaTestFunc = shaderContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_FUNC(); if( pixelColorOutputIndex == 0 && alphaTestEnable && alphaTestFunc == Latte::E_COMPAREFUNC::NEVER ) { // never pass alpha test src->add("discard_fragment();" _CRLF); } else if( pixelColorOutputIndex == 0 && alphaTestEnable && alphaTestFunc != Latte::E_COMPAREFUNC::ALWAYS) { src->add("if( (("); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, i); src->add(").a "); switch( alphaTestFunc ) { case Latte::E_COMPAREFUNC::LESS: src->add("<"); break; case Latte::E_COMPAREFUNC::EQUAL: src->add("=="); break; case Latte::E_COMPAREFUNC::LEQUAL: src->add("<="); break; case Latte::E_COMPAREFUNC::GREATER: src->add(">"); break; case Latte::E_COMPAREFUNC::NOTEQUAL: src->add("!="); break; case Latte::E_COMPAREFUNC::GEQUAL: src->add(">="); break; } src->add(" supportBuffer.alphaTestRef"); src->add(") == false) discard_fragment();" _CRLF); } // pixel color output auto dataType = GetColorBufferDataType(pixelColorOutputIndex, *shaderContext->contextRegistersNew); if (dataType != MetalDataType::NONE) { src->addFmt("out.passPixelColor{} = as_type<{}>(", pixelColorOutputIndex, GetDataTypeStr(dataType)); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, i); src->add(");" _CRLF); } if( cfInstruction->exportArrayBase+i >= 8 ) cemu_assert_unimplemented(); } } else if( cfInstruction->exportType == 0 && cfInstruction->exportArrayBase == 61 ) { // pixel depth or gl_FragStencilRefARB if( cfInstruction->exportBurstCount > 0 ) cemu_assert_unimplemented(); if (cfInstruction->exportComponentSel[0] == 7) { cemu_assert_unimplemented(); // gl_FragDepth ? } if (cfInstruction->exportComponentSel[1] != 7) { cemu_assert_unimplemented(); // exporting to gl_FragStencilRefARB } if (cfInstruction->exportComponentSel[2] != 7) { cemu_assert_unimplemented(); // ukn } if (cfInstruction->exportComponentSel[3] != 7) { cemu_assert_unimplemented(); // ukn } if (!shaderContext->shader->depthMask) return; src->add("out.passDepth = "); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); src->add(".x"); src->add(";" _CRLF); } else cemu_assert_unimplemented(); } } static void _emitXYZWByMask(StringBuf* src, uint32 mask) { if( (mask&(1<<0)) != 0 ) src->add("x"); if( (mask&(1<<1)) != 0 ) src->add("y"); if( (mask&(1<<2)) != 0 ) src->add("z"); if( (mask&(1<<3)) != 0 ) src->add("w"); } static void _emitCFRingWriteCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { StringBuf* src = shaderContext->shaderSource; // calculate parameter output (based on ring buffer output offset relative to GS unit) uint32 bytesPerVertex = shaderContext->contextRegisters[mmSQ_GS_VERT_ITEMSIZE] * 4; bytesPerVertex = std::max(bytesPerVertex, (uint32)1); // avoid division by zero uint32 parameterOffset = ((cfInstruction->exportArrayBase * 4) % bytesPerVertex); // for geometry shaders with streamout, MEM_RING_WRITE is used to pass the data to the copy shader, which then uses STREAM*_WRITE if (shaderContext->shaderType == LatteConst::ShaderType::Geometry && shaderContext->analyzer.hasStreamoutEnable) { // if streamout is enabled, we generate transform feedback output code instead of the normal gs output for (uint32 burstIndex = 0; burstIndex < (cfInstruction->exportBurstCount + 1); burstIndex++) { parameterOffset = ((cfInstruction->exportArrayBase * 4 + burstIndex*0x10) % bytesPerVertex); // find matching stream write in copy shader LatteGSCopyShaderStreamWrite_t* streamWrite = nullptr; for (auto& it : shaderContext->parsedGSCopyShader->list_streamWrites) { if (it.offset == parameterOffset) { streamWrite = ⁢ break; } } if (streamWrite == nullptr) { cemu_assert_suspicious(); return; } for (sint32 i = 0; i < 4; i++) { if ((cfInstruction->memWriteCompMask&(1 << i)) == 0) continue; uint32 u32Offset = streamWrite->exportArrayBase + i; src->addFmt("sb[sbBase{} + {}]", streamWrite->bufferIndex, u32Offset); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->addFmt("{}.", _getRegisterVarName(shaderContext, cfInstruction->exportSourceGPR+burstIndex)); if (i == 0) src->add("x"); else if (i == 1) src->add("y"); else if (i == 2) src->add("z"); else if (i == 3) src->add("w"); _emitTypeConversionSuffixMSL(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(";" _CRLF); } } return; } if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) { if (!shaderContext->contextRegistersNew->IsRasterizationEnabled()) { src->add("// Rasterization disabled" _CRLF); return; } if (cfInstruction->memWriteElemSize != 3) cemu_assert_unimplemented(); if ((cfInstruction->exportArrayBase & 3) != 0) cemu_assert_unimplemented(); for (sint32 burstIndex = 0; burstIndex < (sint32)(cfInstruction->exportBurstCount + 1); burstIndex++) { src->addFmt("out.passParameterSem{}.", (cfInstruction->exportArrayBase) / 4 + burstIndex); _emitXYZWByMask(src, cfInstruction->memWriteCompMask); src->addFmt(" = "); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_SIGNED_INT, burstIndex); src->add(";" _CRLF); } } else if (shaderContext->shaderType == LatteConst::ShaderType::Geometry) { cemu_assert_debug(cfInstruction->memWriteElemSize == 3); //if (cfInstruction->memWriteElemSize != 3) // debugBreakpoint(); cemu_assert_debug((cfInstruction->exportArrayBase & 3) == 0); for (uint32 burstIndex = 0; burstIndex < (cfInstruction->exportBurstCount + 1); burstIndex++) { uint32 parameterExportType = 0; uint32 parameterExportBase = 0; if (LatteGSCopyShaderParser_getExportTypeByOffset(shaderContext->parsedGSCopyShader, parameterOffset + burstIndex * (cfInstruction->memWriteElemSize+1)*4, ¶meterExportType, ¶meterExportBase) == false) { cemu_assert_debug(false); shaderContext->hasError = true; return; } if (parameterExportType == 1 && parameterExportBase == GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION) { src->add("{" _CRLF); src->addFmt("float4 pos = float4(0.0,0.0,0.0,1.0);" _CRLF); src->addFmt("pos."); _emitXYZWByMask(src, cfInstruction->memWriteCompMask); src->addFmt(" = "); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, burstIndex); src->add(";" _CRLF); src->add("SET_POSITION(pos);" _CRLF); src->add("}" _CRLF); } else if (parameterExportType == 2 && parameterExportBase < 16) { src->addFmt("out.passParameterSem{}.", parameterExportBase); _emitXYZWByMask(src, cfInstruction->memWriteCompMask); src->addFmt(" = "); _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, burstIndex); src->add(";" _CRLF); } else cemu_assert_debug(false); } } else debugBreakpoint(); // todo } static void _emitStreamWriteCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { StringBuf* src = shaderContext->shaderSource; if (shaderContext->analyzer.hasStreamoutEnable == false) { #ifdef CEMU_DEBUG_ASSERT src->add("// omitted streamout write" _CRLF); #endif return; } uint32 streamoutBufferIndex; if (cfInstruction->type == GPU7_CF_INST_MEM_STREAM0_WRITE) streamoutBufferIndex = 0; else if (cfInstruction->type == GPU7_CF_INST_MEM_STREAM1_WRITE) streamoutBufferIndex = 1; else cemu_assert_unimplemented(); if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) { uint32 arraySize = cfInstruction->memWriteArraySize + 1; for (sint32 i = 0; i < (sint32)arraySize; i++) { if ((cfInstruction->memWriteCompMask&(1 << i)) == 0) continue; uint32 u32Offset = cfInstruction->exportArrayBase + i; src->addFmt("sb[sbBase{} + {}]", streamoutBufferIndex, u32Offset); src->add(" = "); _emitTypeConversionPrefixMSL(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(_getRegisterVarName(shaderContext, cfInstruction->exportSourceGPR)); _appendChannelAccess(src, i); _emitTypeConversionSuffixMSL(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); src->add(";" _CRLF); } } else cemu_assert_debug(false); } static void _emitCFCall(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) { StringBuf* src = shaderContext->shaderSource; uint32 subroutineAddr = cfInstruction->addr; LatteDecompilerSubroutineInfo* subroutineInfo = nullptr; // find subroutine for (auto& subroutineItr : shaderContext->list_subroutines) { if (subroutineItr.cfAddr == subroutineAddr) { subroutineInfo = &subroutineItr; break; } } if (subroutineInfo == nullptr) { cemu_assert_debug(false); return; } // inline function if (shaderContext->isSubroutine) { cemu_assert_debug(false); // inlining with cascaded function calls not supported return; } // init CF stack variables src->addFmt("activeMaskStackSub{:04x}[0] = true;" _CRLF, subroutineInfo->cfAddr); src->addFmt("activeMaskStackCSub{:04x}[0] = true;" _CRLF, subroutineInfo->cfAddr); src->addFmt("activeMaskStackCSub{:04x}[1] = true;" _CRLF, subroutineInfo->cfAddr); shaderContext->isSubroutine = true; shaderContext->subroutineInfo = subroutineInfo; for(auto& cfInstruction : subroutineInfo->instructions) LatteDecompiler_emitClauseCodeMSL(shaderContext, &cfInstruction, true); shaderContext->isSubroutine = false; shaderContext->subroutineInfo = nullptr; } void LatteDecompiler_emitClauseCodeMSL(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, bool isSubroutine) { StringBuf* src = shaderContext->shaderSource; if( cfInstruction->type == GPU7_CF_INST_ALU || cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction->type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction->type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction->type == GPU7_CF_INST_ALU_BREAK || cfInstruction->type == GPU7_CF_INST_ALU_ELSE_AFTER ) { // emit ALU code if (shaderContext->analyzer.modifiesPixelActiveState) { if(cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE) src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - 1)); else src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); } if (cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE) { src->addFmt("{} = {};" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth-1)); src->addFmt("{} = {};" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); } _emitALUClauseCode(shaderContext, cfInstruction); if( shaderContext->analyzer.modifiesPixelActiveState ) src->add("}" _CRLF); cemu_assert_debug(!(shaderContext->analyzer.modifiesPixelActiveState == false && cfInstruction->type != GPU7_CF_INST_ALU)); // handle ELSE case of PUSH_BEFORE if( cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE ) { src->add("else {" _CRLF); src->addFmt("{} = false;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); src->addFmt("{} = false;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); src->add("}" _CRLF); } // post clause handler if( cfInstruction->type == GPU7_CF_INST_ALU_POP_AFTER ) { src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - 1), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - 1), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - 1)); } else if( cfInstruction->type == GPU7_CF_INST_ALU_POP2_AFTER ) { src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - 2), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - 2), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - 2)); } else if( cfInstruction->type == GPU7_CF_INST_ALU_ELSE_AFTER ) { // no condition test // pop stack if( cfInstruction->popCount != 0 ) debugBreakpoint(); // else operation src->addFmt("{} = {} == false;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); } } else if( cfInstruction->type == GPU7_CF_INST_TEX ) { // emit TEX code if (shaderContext->analyzer.modifiesPixelActiveState) { src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth+1)); } _emitTEXClauseCode(shaderContext, cfInstruction); if (shaderContext->analyzer.modifiesPixelActiveState) { src->add("}" _CRLF); } } else if( cfInstruction->type == GPU7_CF_INST_EXPORT || cfInstruction->type == GPU7_CF_INST_EXPORT_DONE ) { // emit export code _emitExportCode(shaderContext, cfInstruction); } else if( cfInstruction->type == GPU7_CF_INST_ELSE ) { // todo: Condition test, popCount? src->addFmt("{} = {} == false;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); } else if( cfInstruction->type == GPU7_CF_INST_POP ) { src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - cfInstruction->popCount), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount)); } else if( cfInstruction->type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction->type == GPU7_CF_INST_LOOP_START_NO_AL) { // start of loop // if pixel is disabled, then skip loop if (ActiveSettings::ShaderPreventInfiniteLoopsEnabled()) { // with iteration limit to prevent infinite loops src->addFmt("int loopCounter{} = 0;" _CRLF, (sint32)cfInstruction->cfAddr); src->addFmt("while( {} == true && loopCounter{} < 500 )" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), (sint32)cfInstruction->cfAddr); src->add("{" _CRLF); src->addFmt("loopCounter{}++;" _CRLF, (sint32)cfInstruction->cfAddr); } else { src->addFmt("while( {} == true )" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); src->add("{" _CRLF); } } else if( cfInstruction->type == GPU7_CF_INST_LOOP_END ) { // this might not always work if( cfInstruction->popCount != 0 ) debugBreakpoint(); src->add("}" _CRLF); } else if( cfInstruction->type == GPU7_CF_INST_LOOP_BREAK ) { if( cfInstruction->popCount != 0 ) debugBreakpoint(); if (shaderContext->analyzer.modifiesPixelActiveState) { src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); } // note: active stack level is set to the same level as the loop begin. popCount is ignored src->add("break;" _CRLF); if (shaderContext->analyzer.modifiesPixelActiveState) src->add("}" _CRLF); } else if( cfInstruction->type == GPU7_CF_INST_MEM_STREAM0_WRITE || cfInstruction->type == GPU7_CF_INST_MEM_STREAM1_WRITE ) { _emitStreamWriteCode(shaderContext, cfInstruction); } else if( cfInstruction->type == GPU7_CF_INST_MEM_RING_WRITE ) { _emitCFRingWriteCode(shaderContext, cfInstruction); } else if( cfInstruction->type == GPU7_CF_INST_EMIT_VERTEX ) { if( shaderContext->analyzer.modifiesPixelActiveState ) src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); // write point size if (shaderContext->analyzer.outputPointSize && shaderContext->analyzer.writesPointSize == false) src->add("out.pointSize = supportBuffer.pointSize;" _CRLF); src->add("mesh.set_vertex(vertexIndex, out);" _CRLF); src->add("vertexIndex++;" _CRLF); // increment transform feedback pointer for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if (!shaderContext->output->streamoutBufferWriteMask[i]) continue; cemu_assert_debug((shaderContext->output->streamoutBufferStride[i] & 3) == 0); src->addFmt("sbBase{} += {};" _CRLF, i, shaderContext->output->streamoutBufferStride[i] / 4); } if( shaderContext->analyzer.modifiesPixelActiveState ) src->add("}" _CRLF); } else if (cfInstruction->type == GPU7_CF_INST_CALL) { _emitCFCall(shaderContext, cfInstruction); } else if (cfInstruction->type == GPU7_CF_INST_RETURN) { // todo (handle properly) } else { cemu_assert_debug(false); } } void LatteDecompiler_emitHelperFunctions(LatteDecompilerShaderContext* shaderContext, StringBuf* fCStr_shaderSource) { if( shaderContext->analyzer.hasRedcCUBE ) { fCStr_shaderSource->add("void redcCUBE(float4 src0, float4 src1, thread float3& stm, thread int& faceId)\r\n" "{\r\n" "// stm -> x .. s, y .. t, z .. MajorAxis*2.0\r\n" "float3 inputCoord = normalize(float3(src1.y, src1.x, src0.x));\r\n" "float rx = inputCoord.x;\r\n" "float ry = inputCoord.y;\r\n" "float rz = inputCoord.z;\r\n" "if( abs(rx) > abs(ry) && abs(rx) > abs(rz) )\r\n" "{\r\n" "stm.z = rx*2.0;\r\n" "stm.xy = float2(ry,rz); \r\n" "if( rx >= 0.0 )\r\n" "{\r\n" "faceId = 0;\r\n" "}\r\n" "else\r\n" "{\r\n" "faceId = 1;\r\n" "}\r\n" "}\r\n" "else if( abs(ry) > abs(rx) && abs(ry) > abs(rz) )\r\n" "{\r\n" "stm.z = ry*2.0;\r\n" "stm.xy = float2(rx,rz); \r\n" "if( ry >= 0.0 )\r\n" "{\r\n" "faceId = 2;\r\n" "}\r\n" "else\r\n" "{\r\n" "faceId = 3;\r\n" "}\r\n" "}\r\n" "else //if( abs(rz) > abs(ry) && abs(rz) > abs(rx) )\r\n" "{\r\n" "stm.z = rz*2.0;\r\n" "stm.xy = float2(rx,ry); \r\n" "if( rz >= 0.0 )\r\n" "{\r\n" "faceId = 4;\r\n" "}\r\n" "else\r\n" "{\r\n" "faceId = 5;\r\n" "}\r\n" "}\r\n" "}\r\n"); } if( shaderContext->analyzer.hasCubeMapTexture ) { fCStr_shaderSource->add("float3 redcCUBEReverse(float2 st, int faceId)\r\n" "{\r\n" "st.yx = st.xy;\r\n" "float3 v;\r\n" "float majorAxis = 1.0;\r\n" "if( faceId == 0 )\r\n" "{\r\n" "v.yz = (st-float2(1.5))*(majorAxis*2.0);\r\n" "v.x = 1.0;\r\n" "}\r\n" "else if( faceId == 1 )\r\n" "{\r\n" "v.yz = (st-float2(1.5))*(majorAxis*2.0);\r\n" "v.x = -1.0;\r\n" "}\r\n" "else if( faceId == 2 )\r\n" "{\r\n" "v.xz = (st-float2(1.5))*(majorAxis*2.0);\r\n" "v.y = 1.0;\r\n" "}\r\n" "else if( faceId == 3 )\r\n" "{\r\n" "v.xz = (st-float2(1.5))*(majorAxis*2.0);\r\n" "v.y = -1.0;\r\n" "}\r\n" "else if( faceId == 4 )\r\n" "{\r\n" "v.xy = (st-float2(1.5))*(majorAxis*2.0);\r\n" "v.z = 1.0;\r\n" "}\r\n" "else\r\n" "{\r\n" "v.xy = (st-float2(1.5))*(majorAxis*2.0);\r\n" "v.z = -1.0;\r\n" "}\r\n" "return v;\r\n" "}\r\n"); } // Sample compare emulate // TODO: only add when needed // TODO: lod_options overload // TODO: when the sampler has linear min mag filter, use gather and filter manually // TODO: offset? fCStr_shaderSource->add("" "template<typename TextureT, typename CoordT>\r\n" "float sampleCompareEmulate(TextureT tex, sampler samplr, CoordT coord, float compareValue) {\r\n" "return compareValue < tex.sample(samplr, coord).x ? 1.0 : 0.0;\r\n" "}\r\n" ); // Texture calculate lod // TODO: only add when needed fCStr_shaderSource->add("" "template<typename TextureT, typename CoordT>\r\n" "float2 textureCalculateLod(TextureT tex, sampler samplr, CoordT coord) {\r\n" "float lod = tex.calculate_unclamped_lod(samplr, coord);\r\n" "return float2(floor(lod), fract(lod));\r\n" "}\r\n"); // clamp fCStr_shaderSource->add("" "int clampFI32(int v)\r\n" "{\r\n" "if( v == 0x7FFFFFFF )\r\n" " return as_type<int>(1.0);\r\n" "else if( v == 0xFFFFFFFF )\r\n" " return as_type<int>(0.0);\r\n" "return as_type<int>(clamp(as_type<float>(v), 0.0, 1.0));\r\n" "}\r\n"); // mul non-ieee way (0*NaN/INF => 0.0) if (shaderContext->options->strictMul) { // things we tried: //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ return mix(a*b,0.0,a==0.0||b==0.0); }" STR_LINEBREAK); //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ return mix(vec2(a*b,0.0),vec2(0.0,0.0),(equal(vec2(a),vec2(0.0,0.0))||equal(vec2(b),vec2(0.0,0.0)))).x; }" STR_LINEBREAK); //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ if( a == 0.0 || b == 0.0 ) return 0.0; return a*b; }" STR_LINEBREAK); //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){float r = a*b;r = intBitsToFloat(floatBitsToInt(r)&(((floatBitsToInt(a) != 0) && (floatBitsToInt(b) != 0))?0xFFFFFFFF:0));return r;}" STR_LINEBREAK); works // for "min" it used to be: float mul_nonIEEE(float a, float b){ return min(a*b,min(abs(a)*3.40282347E+38F,abs(b)*3.40282347E+38F)); } if( LatteGPUState.glVendor == GLVENDOR_NVIDIA && !ActiveSettings::DumpShadersEnabled()) fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){return mix(0.0, a*b, (a != 0.0) && (b != 0.0));}" _CRLF); // compiles faster on Nvidia and also results in lower RAM usage (OpenGL) else fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ if( a == 0.0 || b == 0.0 ) return 0.0; return a*b; }" _CRLF); // DXKV-like: fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ return (b==0.0 ? 0.0 : a) * (a==0.0 ? 0.0 : b); }" _CRLF); } } #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp" static void LatteDecompiler_emitAttributeImport(LatteDecompilerShaderContext* shaderContext, LatteParsedFetchShaderAttribute_t& attrib) { auto src = shaderContext->shaderSource; static const char* dsMappingTableFloat[6] = { "int(attrDecoder.x)", "int(attrDecoder.y)", "int(attrDecoder.z)", "int(attrDecoder.w)", /*"floatBitsToInt(0.0)"*/ "0", /*"floatBitsToInt(1.0)"*/ "0x3f800000" }; static const char* dsMappingTableInt[6] = { "int(attrDecoder.x)", "int(attrDecoder.y)", "int(attrDecoder.z)", "int(attrDecoder.w)", "0", "1" }; // get register index based on vtx semantic table uint32 attributeShaderLoc = 0xFFFFFFFF; for (sint32 f = 0; f < 32; f++) { if (shaderContext->contextRegisters[mmSQ_VTX_SEMANTIC_0 + f] == attrib.semanticId) { attributeShaderLoc = f; break; } } if (attributeShaderLoc == 0xFFFFFFFF) return; // attribute is not mapped to VS input uint32 registerIndex = attributeShaderLoc + 1; // R0 is skipped // is register used? if ((shaderContext->analyzer.gprUseMask[registerIndex / 8] & (1 << (registerIndex % 8))) == 0) { src->addFmt("// skipped unused attribute for r{}" _CRLF, registerIndex); return; } LatteDecompiler_emitAttributeDecodeMSL(shaderContext->shader, src, &attrib); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt("{} = int4(", _getRegisterVarName(shaderContext, registerIndex)); else src->addFmt("{} = float4(", _getRegisterVarName(shaderContext, registerIndex)); for (sint32 f = 0; f < 4; f++) { uint8 ds = attrib.ds[f]; if (f > 0) src->add(", "); _emitTypeConversionPrefixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); if (ds >= 6) { cemu_assert_unimplemented(); ds = 4; // read as 0.0 } if (attrib.nfa != 1) { src->add(dsMappingTableFloat[ds]); } else { src->add(dsMappingTableInt[ds]); } _emitTypeConversionSuffixMSL(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); } src->add(");" _CRLF); } void LatteDecompiler_emitMSLShader(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader) { bool isRectVertexShader = UseRectEmulation(*shaderContext->contextRegistersNew); bool usesGeometryShader = UseGeometryShader(*shaderContext->contextRegistersNew, shaderContext->options->usesGeometryShader); bool fetchVertexManually = (usesGeometryShader || (shaderContext->fetchShader && shaderContext->fetchShader->mtlFetchVertexManually)); StringBuf* src = new StringBuf(1024*1024*12); // reserve 12MB for generated source (we resize-to-fit at the end) shaderContext->shaderSource = src; // debug info src->addFmt("// shader {:016x}" _CRLF, shaderContext->shaderBaseHash); #ifdef CEMU_DEBUG_ASSERT src->addFmt("// usesIntegerValues: {}" _CRLF, shaderContext->analyzer.usesIntegerValues ? "true" : "false"); src->addFmt(_CRLF); #endif // include metal standard library src->add("#include <metal_stdlib>" _CRLF); src->add("using namespace metal;" _CRLF); // header part (definitions for inputs and outputs) LatteDecompiler::emitHeader(shaderContext, isRectVertexShader, usesGeometryShader, fetchVertexManually); // helper functions LatteDecompiler_emitHelperFunctions(shaderContext, src); const char* functionType = ""; const char* outputTypeName = ""; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: if (fetchVertexManually) { // TODO: clean this up // fetchVertex will modify vid in case of an object shader and an indexed draw // Vertex buffers std::string vertexBufferDefinitions = "#define VERTEX_BUFFER_DEFINITIONS "; std::string vertexBuffers = "#define VERTEX_BUFFERS "; std::string inputFetchDefinition = "VertexIn fetchVertex("; if (usesGeometryShader) inputFetchDefinition += "thread uint&"; else inputFetchDefinition += "uint"; inputFetchDefinition += " vid, uint iid"; if (usesGeometryShader) inputFetchDefinition += ", device uint* indexBuffer, uchar indexType"; inputFetchDefinition += " VERTEX_BUFFER_DEFINITIONS) {\n"; // Index buffer if (usesGeometryShader) { inputFetchDefinition += "if (indexType == 1) // UShort\n"; inputFetchDefinition += "vid = ((device ushort*)indexBuffer)[vid];\n"; inputFetchDefinition += "else if (indexType == 2) // UInt\n"; inputFetchDefinition += "vid = ((device uint*)indexBuffer)[vid];\n"; } inputFetchDefinition += "VertexIn in;\n"; for (auto& bufferGroup : shaderContext->fetchShader->bufferGroups) { std::optional<LatteConst::VertexFetchType2> fetchType; uint32 bufferIndex = bufferGroup.attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; uint32 bufferStride = (shaderContext->contextRegisters[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; for (sint32 j = 0; j < bufferGroup.attribCount; ++j) { auto& attr = bufferGroup.attrib[j]; uint32 semanticId = shaderContext->output->resourceMappingMTL.attributeMapping[attr.semanticId]; if (semanticId == (uint32)-1) continue; // attribute not used? std::string formatName; uint8 componentCount = 0; switch (GetMtlVertexFormat(attr.format)) { case MTL::VertexFormatUChar: formatName = "uchar"; componentCount = 1; break; case MTL::VertexFormatUChar2: formatName = "uchar2"; componentCount = 2; break; case MTL::VertexFormatUChar3: formatName = "uchar3"; componentCount = 3; break; case MTL::VertexFormatUChar4: formatName = "uchar4"; componentCount = 4; break; case MTL::VertexFormatUShort: formatName = "ushort"; componentCount = 1; break; case MTL::VertexFormatUShort2: formatName = "ushort2"; componentCount = 2; break; case MTL::VertexFormatUShort3: formatName = "ushort3"; componentCount = 3; break; case MTL::VertexFormatUShort4: formatName = "ushort4"; componentCount = 4; break; case MTL::VertexFormatUInt: formatName = "uint"; componentCount = 1; break; case MTL::VertexFormatUInt2: formatName = "uint2"; componentCount = 2; break; case MTL::VertexFormatUInt3: formatName = "uint3"; componentCount = 3; break; case MTL::VertexFormatUInt4: formatName = "uint4"; componentCount = 4; break; } // Get the fetch type std::string fetchTypeStr; if (attr.fetchType == LatteConst::VertexFetchType2::VERTEX_DATA) fetchTypeStr = "vid"; else if (attr.fetchType == LatteConst::VertexFetchType2::INSTANCE_DATA) fetchTypeStr = "iid"; else if (attr.fetchType == LatteConst::VertexFetchType2::NO_INDEX_OFFSET_DATA) fetchTypeStr = "0"; // TODO: correct? // Fetch the attribute inputFetchDefinition += fmt::format("in.ATTRIBUTE_NAME{} = uint4(uint", semanticId); if (componentCount != 1) inputFetchDefinition += fmt::format("{}", componentCount); inputFetchDefinition += fmt::format("(*(device {}*)", formatName); inputFetchDefinition += fmt::format("(vertexBuffer{}", attr.attributeBufferIndex); inputFetchDefinition += fmt::format(" + {} * {} + {}))", fetchTypeStr, bufferStride, attr.offset); for (uint8 i = 0; i < (4 - componentCount); i++) inputFetchDefinition += ", 0"; inputFetchDefinition += ");\n"; if (fetchType.has_value()) cemu_assert_debug(fetchType == attr.fetchType); else fetchType = attr.fetchType; if (attr.fetchType == LatteConst::INSTANCE_DATA) { cemu_assert_debug(attr.aluDivisor == 1); // other divisor not yet supported } } // TODO: fetch type vertexBufferDefinitions += fmt::format(", device uchar* vertexBuffer{} [[buffer({})]]", bufferIndex, GET_MTL_VERTEX_BUFFER_INDEX(bufferIndex)); vertexBuffers += fmt::format(", vertexBuffer{}", bufferIndex); } inputFetchDefinition += "return in;\n"; inputFetchDefinition += "}\n"; src->add(vertexBufferDefinitions.c_str()); src->add("\n"); src->add(vertexBuffers.c_str()); src->add("\n"); src->add(inputFetchDefinition.c_str()); } if (usesGeometryShader) { functionType = "[[object, max_total_threads_per_threadgroup(VERTICES_PER_VERTEX_PRIMITIVE), max_total_threadgroups_per_mesh_grid(1)]]"; outputTypeName = "void"; } else { functionType = "vertex"; if (shaderContext->contextRegistersNew->IsRasterizationEnabled()) outputTypeName = "VertexOut"; else outputTypeName = "void"; } break; case LatteConst::ShaderType::Geometry: functionType = "[[mesh, max_total_threads_per_threadgroup(1)]]"; outputTypeName = "void"; break; case LatteConst::ShaderType::Pixel: functionType = "fragment"; outputTypeName = "FragmentOut"; break; } // start of main src->addFmt("{} {} main0(", functionType, outputTypeName); LatteDecompiler::emitInputs(shaderContext, isRectVertexShader, usesGeometryShader, fetchVertexManually); src->add(") {" _CRLF); if (fetchVertexManually && (shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Geometry)) { if (shader->shaderType == LatteConst::ShaderType::Vertex) { if (usesGeometryShader) { // Calculate the imaginary vertex id LattePrimitiveMode vsOutPrimType = shaderContext->contextRegistersNew->VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); if (PrimitiveRequiresConnection(vsOutPrimType)) src->add("uint vid = tig + tid;" _CRLF); else src->add("uint vid = tig * VERTICES_PER_VERTEX_PRIMITIVE + tid;" _CRLF); src->add("uint iid = vid / supportBuffer.verticesPerInstance;" _CRLF); src->add("vid %= supportBuffer.verticesPerInstance;" _CRLF); // Fetch the input src->add("VertexIn in = fetchVertex(vid, iid, indexBuffer, indexType VERTEX_BUFFERS);" _CRLF); // Output is defined as object payload src->add("object_data VertexOut& out = objectPayload.vertexOut[tid];" _CRLF); } else { // Fetch the input src->add("VertexIn in = fetchVertex(vid, iid VERTEX_BUFFERS);" _CRLF); } } else if (shader->shaderType == LatteConst::ShaderType::Geometry) { src->add("GeometryOut out;" _CRLF); // The index of the current vertex that is being emitted src->add("uint vertexIndex = 0;" _CRLF); } } if (shader->shaderType == LatteConst::ShaderType::Pixel || (shaderContext->contextRegistersNew->IsRasterizationEnabled() && !usesGeometryShader)) { src->addFmt("{} out;" _CRLF, outputTypeName); } // variable definition if (shaderContext->typeTracker.useArrayGPRs == false) { // each register is a separate variable for (sint32 i = 0; i < 128; i++) { if (shaderContext->analyzer.usesRelativeGPRRead || (shaderContext->analyzer.gprUseMask[i / 8] & (1 << (i & 7))) != 0) { if (shaderContext->typeTracker.genIntReg) src->addFmt("int4 R{}i = int4(0);" _CRLF, i); else if (shaderContext->typeTracker.genFloatReg) src->addFmt("float4 R{}f = float4(0.0);" _CRLF, i); } } } else { // registers are represented using a single large array if (shaderContext->typeTracker.genIntReg) src->addFmt("int4 Ri[128];" _CRLF); else if (shaderContext->typeTracker.genFloatReg) src->addFmt("float4 Rf[128];" _CRLF); for (sint32 i = 0; i < 128; i++) { if (shaderContext->typeTracker.genIntReg) src->addFmt("Ri[{}] = int4(0);" _CRLF, i); else if (shaderContext->typeTracker.genFloatReg) src->addFmt("Rf[{}] = float4(0.0);" _CRLF, i); } } if( shader->shaderType == LatteConst::ShaderType::Vertex ) src->addFmt("uint4 attrDecoder;" _CRLF); if (shaderContext->typeTracker.genIntReg) src->addFmt("int backupReg0i, backupReg1i, backupReg2i, backupReg3i, backupReg4i;" _CRLF); if (shaderContext->typeTracker.genFloatReg) src->addFmt("float backupReg0f, backupReg1f, backupReg2f, backupReg3f, backupReg4f;" _CRLF); if (shaderContext->typeTracker.genIntReg) { src->addFmt("int PV0ix = 0, PV0iy = 0, PV0iz = 0, PV0iw = 0, PV1ix = 0, PV1iy = 0, PV1iz = 0, PV1iw = 0;" _CRLF); src->addFmt("int PS0i = 0, PS1i = 0;" _CRLF); src->addFmt("int4 tempi = int4(0);" _CRLF); } if (shaderContext->typeTracker.genFloatReg) { src->addFmt("float PV0fx = 0.0, PV0fy = 0.0, PV0fz = 0.0, PV0fw = 0.0, PV1fx = 0.0, PV1fy = 0.0, PV1fz = 0.0, PV1fw = 0.0;" _CRLF); src->addFmt("float PS0f = 0.0, PS1f = 0.0;" _CRLF); src->addFmt("float4 tempf = float4(0.0);" _CRLF); } if (shaderContext->analyzer.hasGradientLookup) { src->add("float4 gradH;" _CRLF); src->add("float4 gradV;" _CRLF); } src->add("float tempResultf;" _CRLF); src->add("int tempResulti;" _CRLF); src->add("int4 ARi = int4(0);" _CRLF); src->add("bool predResult = true;" _CRLF); if(shaderContext->analyzer.modifiesPixelActiveState ) { src->addFmt("bool activeMaskStack[{}];" _CRLF, shaderContext->analyzer.activeStackMaxDepth+1); src->addFmt("bool activeMaskStackC[{}];" _CRLF, shaderContext->analyzer.activeStackMaxDepth+2); for (sint32 i = 0; i < shaderContext->analyzer.activeStackMaxDepth; i++) { src->addFmt("activeMaskStack[{}] = false;" _CRLF, i); } for (sint32 i = 0; i < shaderContext->analyzer.activeStackMaxDepth+1; i++) { src->addFmt("activeMaskStackC[{}] = false;" _CRLF, i); } src->addFmt("activeMaskStack[0] = true;" _CRLF); src->addFmt("activeMaskStackC[0] = true;" _CRLF); src->addFmt("activeMaskStackC[1] = true;" _CRLF); // generate vars for each subroutine for (auto& subroutineInfo : shaderContext->list_subroutines) { sint32 subroutineMaxStackDepth = 0; src->addFmt("bool activeMaskStackSub{:04x}[{}];" _CRLF, subroutineInfo.cfAddr, subroutineMaxStackDepth + 1); src->addFmt("bool activeMaskStackCSub{:04x}[{}];" _CRLF, subroutineInfo.cfAddr, subroutineMaxStackDepth + 2); } } // helper variables for cube maps (todo: Only emit when used) if (shaderContext->analyzer.hasRedcCUBE) { src->add("float3 cubeMapSTM;" _CRLF); src->add("int cubeMapFaceId;" _CRLF); } for(sint32 i=0; i<LATTE_NUM_MAX_TEX_UNITS; i++) { if(!shaderContext->output->textureUnitMask[i]) continue; if( shader->textureUnitDim[i] != Latte::E_DIM::DIM_CUBEMAP ) continue; src->addFmt("float cubeMapArrayIndex{} = 0.0;" _CRLF, i); } // init base offset for streamout buffer writes if (shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Geometry) { for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if(!shaderContext->output->streamoutBufferWriteMask[i]) continue; cemu_assert_debug((shaderContext->output->streamoutBufferStride[i]&3) == 0); if (shader->shaderType == LatteConst::ShaderType::Vertex) // vertex shader src->addFmt("int sbBase{} = supportBuffer.streamoutBufferBase{}/4 + (vid + supportBuffer.verticesPerInstance * iid)*{};" _CRLF, i, i, shaderContext->output->streamoutBufferStride[i] / 4); else // geometry shader { uint32 gsOutPrimType = shaderContext->contextRegisters[mmVGT_GS_OUT_PRIM_TYPE]; uint32 bytesPerVertex = shaderContext->contextRegisters[mmSQ_GS_VERT_ITEMSIZE] * 4; uint32 maxVerticesInGS = ((shaderContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF) * 4) / bytesPerVertex; cemu_assert_debug(gsOutPrimType == 0); // currently we only properly handle GS output primitive points src->addFmt("int sbBase{} = supportBuffer.streamoutBufferBase{}/4 + (gl_PrimitiveIDIn * {})*{};" _CRLF, i, i, maxVerticesInGS, shaderContext->output->streamoutBufferStride[i] / 4); } } } // code to load inputs from previous stage if( shader->shaderType == LatteConst::ShaderType::Vertex ) { if( (shaderContext->analyzer.gprUseMask[0/8]&(1<<(0%8))) != 0 ) { if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt("{} = int4(vid, 0, 0, iid);" _CRLF, _getRegisterVarName(shaderContext, 0)); else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{} = float4(vid, 0, 0, iid);" _CRLF, _getRegisterVarName(shaderContext, 0)); // TODO: as_type<int4>(float4(vid, 0, 0, iid))? else cemu_assert_unimplemented(); } LatteFetchShader* parsedFetchShader = shaderContext->fetchShader; for(auto& bufferGroup : parsedFetchShader->bufferGroups) { for(sint32 i=0; i<bufferGroup.attribCount; i++) LatteDecompiler_emitAttributeImport(shaderContext, bufferGroup.attrib[i]); } for (auto& bufferGroup : parsedFetchShader->bufferGroupsInvalid) { // these attributes point to non-existent buffers // todo - figure out how the hardware actually handles this, currently we assume the input values are zero for (sint32 i = 0; i < bufferGroup.attribCount; i++) LatteDecompiler_emitAttributeImport(shaderContext, bufferGroup.attrib[i]); } } else if (shader->shaderType == LatteConst::ShaderType::Pixel) { LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); uint32 psControl0 = shaderContext->contextRegisters[mmSPI_PS_IN_CONTROL_0]; uint32 psControl1 = shaderContext->contextRegisters[mmSPI_PS_IN_CONTROL_1]; uint32 spiInterpControl = shaderContext->contextRegisters[mmSPI_INTERP_CONTROL_0]; uint8 spriteEnable = (spiInterpControl >> 1) & 1; cemu_assert_debug(spriteEnable == 0); uint8 frontFace_enabled = (psControl1 >> 8) & 1; uint8 frontFace_chan = (psControl1 >> 9) & 3; uint8 frontFace_allBits = (psControl1 >> 11) & 1; uint8 frontFace_regIndex = (psControl1 >> 12) & 0x1F; // handle param_gen if (psInputTable->paramGen != 0) { cemu_assert_debug((psInputTable->paramGen) == 1); // handle the other bits (the same set of coordinates with different perspective/projection settings?) uint32 paramGenGPRIndex = psInputTable->paramGenGPR; if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{} = pointCoord.xyxy;" _CRLF, _getRegisterVarName(shaderContext, paramGenGPRIndex)); else src->addFmt("{} = as_type<int4>(pointCoord.xyxy);" _CRLF, _getRegisterVarName(shaderContext, paramGenGPRIndex)); } for (sint32 i = 0; i < psInputTable->count; i++) { uint32 psControl0 = shaderContext->contextRegisters[mmSPI_PS_IN_CONTROL_0]; uint32 spi0_paramGen = (psControl0 >> 15) & 0xF; sint32 gprIndex = i;// +spi0_paramGen + paramRegOffset; if ((shaderContext->analyzer.gprUseMask[gprIndex / 8] & (1 << (gprIndex % 8))) == 0 && shaderContext->analyzer.usesRelativeGPRRead == false) continue; uint32 psInputSemanticId = psInputTable->import[i].semanticId; if (psInputSemanticId == LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION) { if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{} = GET_FRAGCOORD();" _CRLF, _getRegisterVarName(shaderContext, gprIndex)); else src->addFmt("{} = as_type<int4>(GET_FRAGCOORD());" _CRLF, _getRegisterVarName(shaderContext, gprIndex)); continue; } if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt("{} = as_type<int4>(in.passParameterSem{});" _CRLF, _getRegisterVarName(shaderContext, gprIndex), psInputSemanticId); else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{} = in.passParameterSem{};" _CRLF, _getRegisterVarName(shaderContext, gprIndex), psInputSemanticId); else cemu_assert_unimplemented(); } // front facing attribute if (frontFace_enabled) { if ((shaderContext->analyzer.gprUseMask[0 / 8] & (1 << (0 % 8))) != 0) { if (frontFace_allBits) cemu_assert_debug(false); if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) src->addFmt("{}.{} = as_type<int>(frontFacing ? 1.0 : 0.0);" _CRLF, _getRegisterVarName(shaderContext, frontFace_regIndex), _getElementStrByIndex(frontFace_chan)); else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) src->addFmt("{}.{} = frontFacing ? 1.0 : 0.0;" _CRLF, _getRegisterVarName(shaderContext, frontFace_regIndex), _getElementStrByIndex(frontFace_chan)); else cemu_assert_debug(false); } } } for(auto& cfInstruction : shaderContext->cfInstructions) LatteDecompiler_emitClauseCodeMSL(shaderContext, &cfInstruction, false); //if(shader->shaderType == LatteConst::ShaderType::Geometry) // src->add("EndPrimitive();" _CRLF); // vertex shader should write renderstate point size at the end if required but not modified by shader if (shaderContext->analyzer.outputPointSize && !shaderContext->analyzer.writesPointSize) { if (shader->shaderType == LatteConst::ShaderType::Vertex && !shaderContext->options->usesGeometryShader && shaderContext->contextRegistersNew->IsRasterizationEnabled()) src->add("out.pointSize = supportBuffer.pointSize;" _CRLF); } if (usesGeometryShader && (shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Geometry)) { if (shader->shaderType == LatteConst::ShaderType::Vertex) { src->add("if (tid == 0) {" _CRLF); src->add("meshGridProperties.set_threadgroups_per_grid(uint3(1, 1, 1));" _CRLF); src->add("}" _CRLF); } else if (shader->shaderType == LatteConst::ShaderType::Geometry) { src->add("mesh.set_primitive_count(GET_PRIMITIVE_COUNT(vertexIndex));" _CRLF); // Set indices if (shaderContext->contextRegisters[mmVGT_GS_OUT_PRIM_TYPE] == 1) // Line strip { src->add("for (uint8_t i = 0; i < GET_PRIMITIVE_COUNT(vertexIndex) * 2; i++) {" _CRLF); src->add("mesh.set_index(i, (i 2 3) + i % 2);" _CRLF); src->add("}" _CRLF); } else if (shaderContext->contextRegisters[mmVGT_GS_OUT_PRIM_TYPE] == 2) // Triangle strip { src->add("for (uint8_t i = 0; i < GET_PRIMITIVE_COUNT(vertexIndex) * 3; i++) {" _CRLF); src->add("mesh.set_index(i, (i / 3) + i % 3);" _CRLF); src->add("}" _CRLF); } else { src->add("for (uint8_t i = 0; i < vertexIndex; i++) {" _CRLF); src->add("mesh.set_index(i, i);" _CRLF); src->add("}" _CRLF); } } } if (shader->shaderType == LatteConst::ShaderType::Pixel || (shaderContext->contextRegistersNew->IsRasterizationEnabled() && !usesGeometryShader)) { // Return src->add("return out;" _CRLF); } // end of shader main src->add("}" _CRLF); src->shrink_to_fit(); shader->strBuf_shaderSource = src; } ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLAttrDecoder.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "util/helpers/StringBuf.h" #define _CRLF "\r\n" static void _readLittleEndianAttributeU32x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = in.attrDataSem{};" _CRLF, attributeInputIndex); } static void _readLittleEndianAttributeU32x3(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = uint4(in.attrDataSem{}.xyz,0);" _CRLF, attributeInputIndex); } static void _readLittleEndianAttributeU32x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = uint4(in.attrDataSem{}.xy,0,0);" _CRLF, attributeInputIndex); } static void _readLittleEndianAttributeU32x1(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = uint4(in.attrDataSem{}.x,0,0,0);" _CRLF, attributeInputIndex); } static void _readLittleEndianAttributeU16x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = uint4(in.attrDataSem{}.xy,0,0);" _CRLF, attributeInputIndex); } static void _readLittleEndianAttributeU16x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = in.attrDataSem{};" _CRLF, attributeInputIndex); } static void _readBigEndianAttributeU32x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder = in.attrDataSem{};" _CRLF, attributeInputIndex); src->add("attrDecoder = (attrDecoder>>24)|((attrDecoder>>8)&0xFF00)|((attrDecoder<<8)&0xFF0000)|((attrDecoder<<24));" _CRLF); } static void _readBigEndianAttributeU32x3(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.xyz = in.attrDataSem{}.xyz;" _CRLF, attributeInputIndex); src->add("attrDecoder.xyz = (attrDecoder.xyz>>24)|((attrDecoder.xyz>>8)&0xFF00)|((attrDecoder.xyz<<8)&0xFF0000)|((attrDecoder.xyz<<24));" _CRLF); src->add("attrDecoder.w = 0;" _CRLF); } static void _readBigEndianAttributeU32x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.xy = in.attrDataSem{}.xy;" _CRLF, attributeInputIndex); src->add("attrDecoder.xy = (attrDecoder.xy>>24)|((attrDecoder.xy>>8)&0xFF00)|((attrDecoder.xy<<8)&0xFF0000)|((attrDecoder.xy<<24));" _CRLF); src->add("attrDecoder.z = 0;" _CRLF); src->add("attrDecoder.w = 0;" _CRLF); } static void _readBigEndianAttributeU32x1(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.x = in.attrDataSem{}.x;" _CRLF, attributeInputIndex); src->add("attrDecoder.x = (attrDecoder.x>>24)|((attrDecoder.x>>8)&0xFF00)|((attrDecoder.x<<8)&0xFF0000)|((attrDecoder.x<<24));" _CRLF); src->add("attrDecoder.y = 0;" _CRLF); src->add("attrDecoder.z = 0;" _CRLF); src->add("attrDecoder.w = 0;" _CRLF); } static void _readBigEndianAttributeU16x1(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.xy = in.attrDataSem{}.xy;" _CRLF, attributeInputIndex); src->add("attrDecoder.x = ((attrDecoder.x>>8)&0xFF)|((attrDecoder.x<<8)&0xFF00);" _CRLF); src->add("attrDecoder.y = 0;" _CRLF); src->add("attrDecoder.z = 0;" _CRLF); src->add("attrDecoder.w = 0;" _CRLF); } static void _readBigEndianAttributeU16x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.xy = in.attrDataSem{}.xy;" _CRLF, attributeInputIndex); src->add("attrDecoder.xy = ((attrDecoder.xy>>8)&0xFF)|((attrDecoder.xy<<8)&0xFF00);" _CRLF); src->add("attrDecoder.z = 0;" _CRLF); src->add("attrDecoder.w = 0;" _CRLF); } static void _readBigEndianAttributeU16x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) { src->addFmt("attrDecoder.xyzw = in.attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); src->add("attrDecoder = ((attrDecoder>>8)&0xFF)|((attrDecoder<<8)&0xFF00);" _CRLF); } void LatteDecompiler_emitAttributeDecodeMSL(LatteDecompilerShader* shaderContext, StringBuf* src, LatteParsedFetchShaderAttribute_t* attrib) { if (attrib->attributeBufferIndex >= Latte::GPU_LIMITS::NUM_VERTEX_BUFFERS) { src->add("attrDecoder = int4(0);" _CRLF); return; } uint32 attributeInputIndex = attrib->semanticId; if( attrib->endianSwap == LatteConst::VertexFetchEndianMode::SWAP_U32 ) { if( attrib->format == FMT_32_32_32_32_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU32x4(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_32_32_32_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU32x3(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_32_32_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU32x2(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_32_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_2_10_10_10 && attrib->nfa == 0 ) { _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); // Bayonetta 2 uses this format to store normals src->add("attrDecoder.xyzw = uint4((attrDecoder.x>>0)&0x3FF,(attrDecoder.x>>10)&0x3FF,(attrDecoder.x>>20)&0x3FF,(attrDecoder.x>>30)&0x3);" _CRLF); if (attrib->isSigned != 0) { src->add("if( (attrDecoder.x&0x200) != 0 ) attrDecoder.x |= 0xFFFFFC00;" _CRLF); src->add("if( (attrDecoder.y&0x200) != 0 ) attrDecoder.y |= 0xFFFFFC00;" _CRLF); src->add("if( (attrDecoder.z&0x200) != 0 ) attrDecoder.z |= 0xFFFFFC00;" _CRLF); src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/511.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/511.0,-1.0));" _CRLF); src->add("attrDecoder.z = as_type<uint>(max(float(int(attrDecoder.z))/511.0,-1.0));" _CRLF); } else { src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/1023.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/1023.0,-1.0));" _CRLF); src->add("attrDecoder.z = as_type<uint>(max(float(int(attrDecoder.z))/1023.0,-1.0));" _CRLF); } src->add("attrDecoder.w = as_type<uint>(float(attrDecoder.w));" _CRLF); // unsure? } else if( attrib->format == FMT_32_32_32_32 && attrib->nfa == 1 && attrib->isSigned == 0 ) { _readBigEndianAttributeU32x4(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_32_32_32 && attrib->nfa == 1 && attrib->isSigned == 0 ) { _readBigEndianAttributeU32x3(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_32_32 && attrib->nfa == 1 && attrib->isSigned == 0 ) { _readBigEndianAttributeU32x2(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_32 && attrib->nfa == 1 && attrib->isSigned == 0) { _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_32 && attrib->nfa == 1 && attrib->isSigned == 1) { // we can just read the signed s32 as a u32 since no sign-extension is necessary _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned == 0 ) { // seen in Minecraft Wii U Edition src->addFmt("attrDecoder.xyzw = as_type<uint4>(float4(in.attrDataSem{}.wzyx)/255.0);" _CRLF, attributeInputIndex); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned != 0 ) { // seen in Minecraft Wii U Edition src->addFmt("attrDecoder.xyzw = in.attrDataSem{}.wzyx;" _CRLF, attributeInputIndex); src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.z&0x80) != 0 ) attrDecoder.z |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.w&0x80) != 0 ) attrDecoder.w |= 0xFFFFFF00;" _CRLF); src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); src->add("attrDecoder.z = as_type<uint>(max(float(int(attrDecoder.z))/127.0,-1.0));" _CRLF); src->add("attrDecoder.w = as_type<uint>(max(float(int(attrDecoder.w))/127.0,-1.0));" _CRLF); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 1 && attrib->isSigned == 0 ) { // seen in Minecraft Wii U Edition src->addFmt("attrDecoder.xyzw = in.attrDataSem{}.wzyx;" _CRLF, attributeInputIndex); } else if (attrib->format == FMT_8_8_8_8 && attrib->nfa == 2 && attrib->isSigned == 0) { // seen in Ben 10 Omniverse src->addFmt("attrDecoder.xyzw = as_type<uint4>(float4(in.attrDataSem{}.wzyx));" _CRLF, attributeInputIndex); } else { cemuLog_log(LogType::Force, "_emitAttributeDecode(): Unsupported fmt {:02x} nfa {} signed {} endian {}\n", attrib->format, attrib->nfa, attrib->isSigned, attrib->endianSwap); cemu_assert_unimplemented(); } } else if( attrib->endianSwap == LatteConst::VertexFetchEndianMode::SWAP_NONE ) { if( attrib->format == FMT_32_32_32_32_FLOAT && attrib->nfa == 2 ) { _readLittleEndianAttributeU32x4(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_32_32_32_FLOAT && attrib->nfa == 2) { _readLittleEndianAttributeU32x3(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_32_32_FLOAT && attrib->nfa == 2) { // seen in Cities of Gold _readLittleEndianAttributeU32x2(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_32 && attrib->nfa == 1 && attrib->isSigned == 0) { // seen in Nano Assault Neo _readLittleEndianAttributeU32x1(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_2_10_10_10 && attrib->nfa == 0 && attrib->isSigned == 0) { // seen in Fast Racing Neo _readLittleEndianAttributeU32x1(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xyzw = uint4((attrDecoder.x>>0)&0x3FF,(attrDecoder.x>>10)&0x3FF,(attrDecoder.x>>20)&0x3FF,(attrDecoder.x>>30)&0x3);" _CRLF); src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/1023.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/1023.0,-1.0));" _CRLF); src->add("attrDecoder.z = as_type<uint>(max(float(int(attrDecoder.z))/1023.0,-1.0));" _CRLF); src->add("attrDecoder.w = as_type<uint>(float(attrDecoder.w));" _CRLF); // todo - is this correct? } else if (attrib->format == FMT_16_16_16_16 && attrib->nfa == 0 && attrib->isSigned != 0) { // seen in CoD ghosts _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.z = as_type<uint>(max(float(int(attrDecoder.z))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.w = as_type<uint>(max(float(int(attrDecoder.w))/32767.0,-1.0));" _CRLF); } else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 2 && attrib->isSigned == 1 ) { // seen in Rabbids Land _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.xyzw = as_type<uint4>(float4(int4(attrDecoder)));" _CRLF); } else if (attrib->format == FMT_16_16_16_16_FLOAT && attrib->nfa == 2) { // seen in Giana Sisters: Twisted Dreams _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); // TODO: uint4? src->add("attrDecoder.xyzw = as_type<uint4>(float4(float2(as_type<half2>(attrDecoder.x|(attrDecoder.y<<16))),float2(as_type<half2>(attrDecoder.z|(attrDecoder.w<<16)))));" _CRLF); } else if (attrib->format == FMT_16_16 && attrib->nfa == 0 && attrib->isSigned != 0) { // seen in Nano Assault Neo _readLittleEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); } else if (attrib->format == FMT_16_16_FLOAT && attrib->nfa == 2) { // seen in Giana Sisters: Twisted Dreams _readLittleEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xy = as_type<uint2>(float2(as_type<half2>(attrDecoder.x|(attrDecoder.y<<16))));" _CRLF); src->add("attrDecoder.zw = uint2(0);" _CRLF); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned == 0 ) { src->addFmt("attrDecoder.xyzw = as_type<uint4>(float4(in.attrDataSem{}.xyzw)/255.0);" _CRLF, attributeInputIndex); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned != 0 ) { src->addFmt("attrDecoder.xyzw = in.attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.z&0x80) != 0 ) attrDecoder.z |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.w&0x80) != 0 ) attrDecoder.w |= 0xFFFFFF00;" _CRLF); src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); src->add("attrDecoder.z = as_type<uint>(max(float(int(attrDecoder.z))/127.0,-1.0));" _CRLF); src->add("attrDecoder.w = as_type<uint>(max(float(int(attrDecoder.w))/127.0,-1.0));" _CRLF); } else if (attrib->format == FMT_8_8_8_8 && attrib->nfa == 1 && attrib->isSigned == 0) { src->addFmt("attrDecoder.xyzw = in.attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); } else if (attrib->format == FMT_8_8_8_8 && attrib->nfa == 1 && attrib->isSigned != 0) { // seen in Sonic Lost World src->addFmt("attrDecoder.xyzw = in.attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.z&0x80) != 0 ) attrDecoder.z |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.w&0x80) != 0 ) attrDecoder.w |= 0xFFFFFF00;" _CRLF); } else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 2 && attrib->isSigned == 0 ) { // seen in One Piece // TODO: uint4? src->addFmt("attrDecoder.xyzw = as_type<uint4>(float4(in.attrDataSem{}.xyzw));" _CRLF, attributeInputIndex); } else if (attrib->format == FMT_8_8 && attrib->nfa == 0 && attrib->isSigned == 0) { if( (attrib->offset&3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL ) { // AMD workaround src->addFmt("attrDecoder.xy = as_type<uint2>(float2(in.attrDataSem{}.zw)/255.0);" _CRLF, attributeInputIndex); src->add("attrDecoder.zw = uint2(0);" _CRLF); } else { src->addFmt("attrDecoder.xy = as_type<uint2>(float2(in.attrDataSem{}.xy)/255.0);" _CRLF, attributeInputIndex); src->add("attrDecoder.zw = uint2(0);" _CRLF); } } else if (attrib->format == FMT_8_8 && attrib->nfa == 2 && attrib->isSigned == 0) { // seen in BotW if ((attrib->offset & 3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL) { // AMD workaround src->addFmt("attrDecoder.xy = as_type<uint2>(float2(in.attrDataSem{}.zw));" _CRLF, attributeInputIndex); src->add("attrDecoder.zw = uint2(0);" _CRLF); } else { src->addFmt("attrDecoder.xy = as_type<uint2>(float2(in.attrDataSem{}.xy));" _CRLF, attributeInputIndex); src->add("attrDecoder.zw = uint2(0);" _CRLF); } } else if (attrib->format == FMT_8_8 && attrib->nfa == 0 && attrib->isSigned != 0) { if ((attrib->offset & 3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL) { // AMD workaround src->addFmt("attrDecoder.xy = in.attrDataSem{}.zw;" _CRLF, attributeInputIndex); src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); src->add("attrDecoder.zw = uint2(0);" _CRLF); } else { src->addFmt("attrDecoder.xy = in.attrDataSem{}.xy;" _CRLF, attributeInputIndex); src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); src->add("attrDecoder.zw = uint2(0);" _CRLF); } } else if (attrib->format == FMT_8_8 && attrib->nfa == 1 && attrib->isSigned == 0) { if ((attrib->offset & 3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL) { // AMD workaround src->addFmt("attrDecoder.xyzw = uint4(in.attrDataSem{}.zw,0,0);" _CRLF, attributeInputIndex); } else { src->addFmt("attrDecoder.xyzw = uint4(in.attrDataSem{}.xy,0,0);" _CRLF, attributeInputIndex); } } else if( attrib->format == FMT_8 && attrib->nfa == 0 && attrib->isSigned == 0 ) { // seen in Pikmin 3 src->addFmt("attrDecoder.x = as_type<uint>(float(in.attrDataSem{}.x)/255.0);" _CRLF, attributeInputIndex); src->add("attrDecoder.yzw = uint3(0);" _CRLF); } else if( attrib->format == FMT_8 && attrib->nfa == 1 && attrib->isSigned == 0 ) { src->addFmt("attrDecoder.xyzw = uint4(in.attrDataSem{}.x,0,0,0);" _CRLF, attributeInputIndex); } else { cemuLog_log(LogType::Force, "_emitAttributeDecode(): Unsupported fmt {:02x} nfa {} signed {} endian {}\n", attrib->format, attrib->nfa, attrib->isSigned, attrib->endianSwap); cemu_assert_debug(false); } } else if( attrib->endianSwap == LatteConst::VertexFetchEndianMode::SWAP_U16 ) { if( attrib->format == FMT_16_16_16_16_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); // TODO: uint4? src->add("attrDecoder.xyzw = as_type<uint4>(float4(float2(as_type<half2>(attrDecoder.x|(attrDecoder.y<<16))),float2(as_type<half2>(attrDecoder.z|(attrDecoder.w<<16)))));" _CRLF); } else if (attrib->format == FMT_16_16_16_16 && attrib->nfa == 0 && attrib->isSigned != 0) { _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.z = as_type<uint>(max(float(int(attrDecoder.z))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.w = as_type<uint>(max(float(int(attrDecoder.w))/32767.0,-1.0));" _CRLF); } else if (attrib->format == FMT_16_16_16_16 && attrib->nfa == 0 && attrib->isSigned == 0) { // seen in BotW _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("attrDecoder.x = as_type<uint>(float(int(attrDecoder.x))/65535.0);" _CRLF); src->add("attrDecoder.y = as_type<uint>(float(int(attrDecoder.y))/65535.0);" _CRLF); src->add("attrDecoder.z = as_type<uint>(float(int(attrDecoder.z))/65535.0);" _CRLF); src->add("attrDecoder.w = as_type<uint>(float(int(attrDecoder.w))/65535.0);" _CRLF); } else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 2 && attrib->isSigned != 0 ) { // seen in Minecraft Wii U Edition _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.x = as_type<uint>(float(int(attrDecoder.x)));" _CRLF); src->add("attrDecoder.y = as_type<uint>(float(int(attrDecoder.y)));" _CRLF); src->add("attrDecoder.z = as_type<uint>(float(int(attrDecoder.z)));" _CRLF); src->add("attrDecoder.w = as_type<uint>(float(int(attrDecoder.w)));" _CRLF); } else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 1 && attrib->isSigned != 0 ) { // seen in Minecraft Wii U Edition _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); } else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 1 && attrib->isSigned == 0 ) { _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_16_16_FLOAT && attrib->nfa == 2 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xy = as_type<uint2>(float2(as_type<half2>(attrDecoder.x|(attrDecoder.y<<16))));" _CRLF); src->add("attrDecoder.zw = uint2(0);" _CRLF); } else if( attrib->format == FMT_16_16 && attrib->nfa == 0 && attrib->isSigned == 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xy = as_type<uint2>(float2(float(attrDecoder.x), float(attrDecoder.y))/65535.0);" _CRLF); src->add("attrDecoder.zw = uint2(0);" _CRLF); } else if( attrib->format == FMT_16_16 && attrib->nfa == 0 && attrib->isSigned != 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.x = as_type<uint>(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.y = as_type<uint>(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.zw = uint2(0);" _CRLF); } else if( attrib->format == FMT_16_16 && attrib->nfa == 1 && attrib->isSigned == 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); } else if( attrib->format == FMT_16_16 && attrib->nfa == 1 && attrib->isSigned != 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.zw = uint2(0);" _CRLF); } else if( attrib->format == FMT_16_16 && attrib->nfa == 2 && attrib->isSigned == 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("attrDecoder.xy = as_type<uint2>(float2(float(attrDecoder.x), float(attrDecoder.y)));" _CRLF); src->add("attrDecoder.zw = uint2(0);" _CRLF); } else if( attrib->format == FMT_16_16 && attrib->nfa == 2 && attrib->isSigned != 0 ) { _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); src->add("attrDecoder.xy = as_type<uint2>(float2(float(int(attrDecoder.x)), float(int(attrDecoder.y))));" _CRLF); src->add("attrDecoder.zw = uint2(0);" _CRLF); } else if (attrib->format == FMT_16 && attrib->nfa == 1 && attrib->isSigned == 0) { _readBigEndianAttributeU16x1(shaderContext, src, attributeInputIndex); } else if (attrib->format == FMT_16 && attrib->nfa == 0 && attrib->isSigned == 0) { // seen in CoD ghosts _readBigEndianAttributeU16x1(shaderContext, src, attributeInputIndex); src->add("attrDecoder.x = as_type<uint>(float(int(attrDecoder.x))/65535.0);" _CRLF); } else { cemuLog_logDebug(LogType::Force, "_emitAttributeDecode(): Unsupported fmt {:02x} nfa {} signed {} endian {}", attrib->format, attrib->nfa, attrib->isSigned, attrib->endianSwap); } } else { cemu_assert_debug(false); } } ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitMSLHeader.hpp ================================================ #pragma once #include "Common/precompiled.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Core/LatteShader.h" namespace LatteDecompiler { static void _emitUniformVariables(LatteDecompilerShaderContext* decompilerContext, bool usesGeometryShader) { auto src = decompilerContext->shaderSource; auto& uniformOffsets = decompilerContext->output->uniformOffsetsVK; src->add("struct SupportBuffer {" _CRLF); uint32 uniformCurrentOffset = 0; auto shader = decompilerContext->shader; auto shaderType = decompilerContext->shader->shaderType; if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) { // uniform registers or buffers are accessed statically with predictable offsets // this allows us to remap the used entries into a more compact array src->addFmt("int4 remapped[{}];" _CRLF, (sint32)shader->list_remappedUniformEntries.size()); uniformOffsets.offset_remapped = uniformCurrentOffset; uniformCurrentOffset += 16 * shader->list_remappedUniformEntries.size(); } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) { uint32 cfileSize = decompilerContext->analyzer.uniformRegisterAccessTracker.DetermineSize(decompilerContext->shaderBaseHash, 256); // full or partial uniform register file has to be present src->addFmt("int4 uniformRegister[{}];" _CRLF, cfileSize); uniformOffsets.offset_uniformRegister = uniformCurrentOffset; uniformOffsets.count_uniformRegister = cfileSize; uniformCurrentOffset += 16 * cfileSize; } // special uniforms bool hasAnyViewportScaleDisabled = !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_SCALE_ENA() || !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_SCALE_ENA() || !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_SCALE_ENA(); if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && hasAnyViewportScaleDisabled) { // aka GX2 special state 0 uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; src->add("float2 windowSpaceToClipSpaceTransform;" _CRLF); uniformOffsets.offset_windowSpaceToClipSpaceTransform = uniformCurrentOffset; uniformCurrentOffset += 8; } bool alphaTestEnable = decompilerContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_TEST_ENABLE(); if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && alphaTestEnable) { uniformCurrentOffset = (uniformCurrentOffset + 3)&~3; src->add("float alphaTestRef;" _CRLF); uniformOffsets.offset_alphaTestRef = uniformCurrentOffset; uniformCurrentOffset += 4; } if (decompilerContext->analyzer.outputPointSize && decompilerContext->analyzer.writesPointSize == false) { if ((decompilerContext->shaderType == LatteConst::ShaderType::Vertex && !decompilerContext->options->usesGeometryShader) || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { uniformCurrentOffset = (uniformCurrentOffset + 3)&~3; src->add("float pointSize;" _CRLF); uniformOffsets.offset_pointSize = uniformCurrentOffset; uniformCurrentOffset += 4; } } // define fragCoordScale which holds the xy scale for render target resolution vs effective resolution if (shader->shaderType == LatteConst::ShaderType::Pixel) { uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; src->add("float2 fragCoordScale;" _CRLF); uniformOffsets.offset_fragCoordScale = uniformCurrentOffset; uniformCurrentOffset += 8; } // provide scale factor for every texture that is accessed via texel coordinates (texelFetch) for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++) { if (decompilerContext->analyzer.texUnitUsesTexelCoordinates.test(t) == false) continue; uniformCurrentOffset = (uniformCurrentOffset + 7) & ~7; src->addFmt("float2 tex{}Scale;" _CRLF, t); uniformOffsets.offset_texScale[t] = uniformCurrentOffset; uniformCurrentOffset += 8; } // define verticesPerInstance + streamoutBufferBaseX if ((shader->shaderType == LatteConst::ShaderType::Vertex && usesGeometryShader) || (decompilerContext->analyzer.useSSBOForStreamout && (shader->shaderType == LatteConst::ShaderType::Vertex && !decompilerContext->options->usesGeometryShader) || (shader->shaderType == LatteConst::ShaderType::Geometry))) { src->add("int verticesPerInstance;" _CRLF); uniformOffsets.offset_verticesPerInstance = uniformCurrentOffset; uniformCurrentOffset += 4; for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if (decompilerContext->output->streamoutBufferWriteMask[i]) { src->addFmt("int streamoutBufferBase{};" _CRLF, i); uniformOffsets.offset_streamoutBufferBase[i] = uniformCurrentOffset; uniformCurrentOffset += 4; } } } src->add("};" _CRLF _CRLF); uniformOffsets.offset_endOfBlock = uniformCurrentOffset; } static void _emitUniformBuffers(LatteDecompilerShaderContext* decompilerContext) { auto shaderSrc = decompilerContext->shaderSource; // uniform buffer definition if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if (!decompilerContext->analyzer.uniformBufferAccessTracker[i].HasAccess()) continue; cemu_assert_debug(decompilerContext->output->resourceMappingMTL.uniformBuffersBindingPoint[i] >= 0); shaderSrc->addFmt("struct UBuff{} {{" _CRLF, i); shaderSrc->addFmt("float4 d[{}];" _CRLF, decompilerContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(decompilerContext->shaderBaseHash, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE)); shaderSrc->add("};" _CRLF _CRLF); } } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) { // already generated in _emitUniformVariables } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) { // already generated in _emitUniformVariables } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_NONE) { // no uniforms used } else { cemu_assert_debug(false); } } static void _emitAttributes(LatteDecompilerShaderContext* decompilerContext, bool fetchVertexManually) { auto src = decompilerContext->shaderSource; std::string attributeNames; if (decompilerContext->shader->shaderType == LatteConst::ShaderType::Vertex) { src->add("struct VertexIn {" _CRLF); // attribute inputs for (uint32 i = 0; i < LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS; i++) { if (decompilerContext->analyzer.inputAttributSemanticMask[i]) { cemu_assert_debug(decompilerContext->output->resourceMappingMTL.attributeMapping[i] >= 0); src->addFmt("uint4 attrDataSem{}", i); if (fetchVertexManually) attributeNames += "#define ATTRIBUTE_NAME" + std::to_string((sint32)decompilerContext->output->resourceMappingMTL.attributeMapping[i]) + " attrDataSem" + std::to_string(i) + "\n"; else src->addFmt(" [[attribute({})]]", (sint32)decompilerContext->output->resourceMappingMTL.attributeMapping[i]); src->add(";" _CRLF); } } src->add("};" _CRLF _CRLF); } src->addFmt("{}", attributeNames); } static void _emitVSOutputs(LatteDecompilerShaderContext* shaderContext, bool isRectVertexShader) { auto* src = shaderContext->shaderSource; src->add("struct VertexOut {" _CRLF); src->add("float4 position [[position]] [[invariant]];" _CRLF); if (shaderContext->analyzer.outputPointSize) src->add("float pointSize [[point_size]];" _CRLF); LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); auto parameterMask = shaderContext->shader->outputParameterMask; bool psInputsWritten[GPU7_PS_MAX_INPUTS] = {false}; for (uint32 i = 0; i < 32; i++) { if ((parameterMask&(1 << i)) == 0) continue; uint32 vsSemanticId = _getVertexShaderOutParamSemanticId(shaderContext->contextRegisters, i); if (vsSemanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) continue; // get import based on semanticId sint32 psInputIndex = -1; for (sint32 f = 0; f < psInputTable->count; f++) { if (psInputTable->import[f].semanticId == vsSemanticId) { psInputIndex = f; break; } } if (psInputIndex == -1) continue; // no ps input psInputsWritten[psInputIndex] = true; src->addFmt("float4 passParameterSem{}", psInputTable->import[psInputIndex].semanticId); if (!isRectVertexShader) { src->addFmt(" [[user(locn{})]]", psInputIndex); if (psInputTable->import[psInputIndex].isFlat) src->add(" [[flat]]"); if (psInputTable->import[psInputIndex].isNoPerspective) src->add(" [[center_no_perspective]]"); } src->addFmt(";" _CRLF); } // TODO: handle this in the fragment shader instead? // Declare all PS inputs that are not written by the VS for (uint32 i = 0; i < psInputTable->count; i++) { if (psInputsWritten[i]) continue; if (psInputTable->import[i].semanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) continue; src->addFmt("float4 unknown{} [[user(locn{})]];" _CRLF, psInputTable->import[i].semanticId, i); } src->add("};" _CRLF _CRLF); if (isRectVertexShader) { src->add("struct ObjectPayload {" _CRLF); src->add("VertexOut vertexOut[VERTICES_PER_VERTEX_PRIMITIVE];" _CRLF); src->add("};" _CRLF _CRLF); } } static void _emitPSInputs(LatteDecompilerShaderContext* shaderContext) { auto* src = shaderContext->shaderSource; src->add("#define GET_FRAGCOORD() float4(in.position.xy * supportBuffer.fragCoordScale.xy, in.position.z, 1.0 / in.position.w)" _CRLF); src->add("struct FragmentIn {" _CRLF); src->add("float4 position [[position]];" _CRLF); LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); for (sint32 i = 0; i < psInputTable->count; i++) { if (psInputTable->import[i].semanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) continue; src->addFmt("float4 passParameterSem{}", psInputTable->import[i].semanticId); src->addFmt(" [[user(locn{})]]", i); if (psInputTable->import[i].isFlat) src->add(" [[flat]]"); if (psInputTable->import[i].isNoPerspective) src->add(" [[center_no_perspective]]"); src->add(";" _CRLF); } src->add("};" _CRLF _CRLF); } static void _emitInputsAndOutputs(LatteDecompilerShaderContext* decompilerContext, bool isRectVertexShader, bool usesGeometryShader, bool fetchVertexManually) { auto src = decompilerContext->shaderSource; if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) { _emitAttributes(decompilerContext, fetchVertexManually); } else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) { _emitPSInputs(decompilerContext); src->add("struct FragmentOut {" _CRLF); // generate pixel outputs for pixel shader for (uint32 i = 0; i < LATTE_NUM_COLOR_TARGET; i++) { if ((decompilerContext->shader->pixelColorOutputMask & (1 << i)) != 0) { auto dataType = GetColorBufferDataType(i, *decompilerContext->contextRegistersNew); if (dataType != MetalDataType::NONE) { src->addFmt("{} passPixelColor{} [[color({})]];" _CRLF, GetDataTypeStr(dataType), i, i); } } } // generate depth output for pixel shader if (decompilerContext->shader->depthMask) src->add("float passDepth [[depth(any)]];" _CRLF); src->add("};" _CRLF _CRLF); } if (!usesGeometryShader || isRectVertexShader) { if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->contextRegistersNew->IsRasterizationEnabled()) _emitVSOutputs(decompilerContext, isRectVertexShader); } else { if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { src->add("struct VertexOut {" _CRLF); uint32 ringParameterCountVS2GS = 0; if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) { ringParameterCountVS2GS = decompilerContext->shader->ringParameterCount; } else { ringParameterCountVS2GS = decompilerContext->shader->ringParameterCountFromPrevStage; } for (uint32 f = 0; f < ringParameterCountVS2GS; f++) src->addFmt("int4 passParameterSem{};" _CRLF, f); src->add("};" _CRLF _CRLF); src->add("struct ObjectPayload {" _CRLF); src->add("VertexOut vertexOut[VERTICES_PER_VERTEX_PRIMITIVE];" _CRLF); src->add("};" _CRLF _CRLF); } if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { // parameters shared between geometry and pixel shader uint32 ringItemSize = decompilerContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF; if ((ringItemSize & 0xF) != 0) debugBreakpoint(); if (((decompilerContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF) & 0xF) != 0) debugBreakpoint(); src->add("struct GeometryOut {" _CRLF); src->add("float4 position [[position]];" _CRLF); for (sint32 p = 0; p < decompilerContext->parsedGSCopyShader->numParam; p++) { if (decompilerContext->parsedGSCopyShader->paramMapping[p].exportType != 2) continue; src->addFmt("float4 passParameterSem{} [[user(locn{})]];" _CRLF, (sint32)decompilerContext->parsedGSCopyShader->paramMapping[p].exportParam, decompilerContext->parsedGSCopyShader->paramMapping[p].exportParam & 0x7F); } src->add("};" _CRLF _CRLF); const uint32 MAX_VERTEX_COUNT = 32; // Define the mesh shader output type src->addFmt("using MeshType = mesh<GeometryOut, void, {}, GET_PRIMITIVE_COUNT({}), topology::MTL_PRIMITIVE_TYPE>;" _CRLF, MAX_VERTEX_COUNT, MAX_VERTEX_COUNT); } } } static void emitHeader(LatteDecompilerShaderContext* decompilerContext, bool isRectVertexShader, bool usesGeometryShader, bool fetchVertexManually) { auto src = decompilerContext->shaderSource; if (usesGeometryShader && (decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry)) { LattePrimitiveMode vsOutPrimType = decompilerContext->contextRegistersNew->VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); src->addFmt("#define VERTICES_PER_VERTEX_PRIMITIVE {}" _CRLF, GetVerticesPerPrimitive(vsOutPrimType)); uint32 gsOutPrimType = decompilerContext->contextRegisters[mmVGT_GS_OUT_PRIM_TYPE]; if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { switch (gsOutPrimType) { case 0: // Point src->add("#define MTL_PRIMITIVE_TYPE point" _CRLF); src->add("#define GET_PRIMITIVE_COUNT(vertexCount) (vertexCount / 1)" _CRLF); break; case 1: // Line strip src->add("#define MTL_PRIMITIVE_TYPE line" _CRLF); src->add("#define GET_PRIMITIVE_COUNT(vertexCount) (vertexCount - 1)" _CRLF); break; case 2: // Triangle strip src->add("#define MTL_PRIMITIVE_TYPE triangle" _CRLF); src->add("#define GET_PRIMITIVE_COUNT(vertexCount) (vertexCount - 2)" _CRLF); break; default: cemuLog_log(LogType::Force, "Unknown geometry out primitive type {}", gsOutPrimType); break; } } } if (decompilerContext->contextRegistersNew->PA_CL_CLIP_CNTL.get_DX_CLIP_SPACE_DEF()) src->add("#define SET_POSITION(_v) out.position = _v" _CRLF); else src->add("#define SET_POSITION(_v) out.position = _v; out.position.z = (out.position.z + out.position.w) / 2.0" _CRLF); const bool dump_shaders_enabled = ActiveSettings::DumpShadersEnabled(); if(dump_shaders_enabled) decompilerContext->shaderSource->add("// start of shader inputs/outputs, predetermined by Cemu. Do not touch" _CRLF); // uniform variables _emitUniformVariables(decompilerContext, usesGeometryShader); // uniform buffers _emitUniformBuffers(decompilerContext); // inputs and outputs _emitInputsAndOutputs(decompilerContext, isRectVertexShader, usesGeometryShader, fetchVertexManually); if (dump_shaders_enabled) decompilerContext->shaderSource->add("// end of shader inputs/outputs" _CRLF); } static void _emitUniformBufferDefinitions(LatteDecompilerShaderContext* decompilerContext) { auto src = decompilerContext->shaderSource; // uniform buffer definition if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if (!decompilerContext->analyzer.uniformBufferAccessTracker[i].HasAccess()) continue; cemu_assert_debug(decompilerContext->output->resourceMappingMTL.uniformBuffersBindingPoint[i] >= 0); src->addFmt(", constant UBuff{}& ubuff{} [[buffer({})]]", i, i, (sint32)decompilerContext->output->resourceMappingMTL.uniformBuffersBindingPoint[i]); } } } static void _emitTextureDefinitions(LatteDecompilerShaderContext* shaderContext) { bool renderTargetIndexUsed[LATTE_NUM_COLOR_TARGET] = {false}; auto src = shaderContext->shaderSource; // texture sampler definition for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) { if (!shaderContext->output->textureUnitMask[i]) continue; uint8 renderTargetIndex = shaderContext->shader->textureRenderTargetIndex[i]; if (static_cast<MetalRenderer*>(g_renderer.get())->SupportsFramebufferFetch() && renderTargetIndex != 255) { if (!renderTargetIndexUsed[renderTargetIndex]) { src->addFmt(", {} col{} [[color({})]]", GetDataTypeStr(GetColorBufferDataType(renderTargetIndex, *shaderContext->contextRegistersNew)), renderTargetIndex, renderTargetIndex); renderTargetIndexUsed[renderTargetIndex] = true; } } else { src->add(", "); // Only certain texture dimensions can be used with comparison samplers if (shaderContext->shader->textureUsesDepthCompare[i] && IsValidDepthTextureType(shaderContext->shader->textureUnitDim[i])) src->add("depth"); else src->add("texture"); if (shaderContext->shader->textureIsIntegerFormat[i]) { // integer samplers if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_1D) src->add("1d<uint>"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D || shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D_MSAA) src->add("2d<uint>"); else cemu_assert_unimplemented(); } else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D || shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D_MSAA) src->add("2d<float>"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_1D) src->add("1d<float>"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D_ARRAY) src->add("2d_array<float>"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_CUBEMAP) src->add("cube_array<float>"); else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_3D) src->add("3d<float>"); else { cemu_assert_unimplemented(); } uint32 binding = shaderContext->output->resourceMappingMTL.textureUnitToBindingPoint[i]; //uint32 textureBinding = shaderContext->output->resourceMappingMTL.textureUnitToBindingPoint[i] % 31; //uint32 samplerBinding = textureBinding % 16; src->addFmt(" tex{} [[texture({})]]", i, binding); src->addFmt(", sampler samplr{} [[sampler({})]]", i, binding); } } } static void emitInputs(LatteDecompilerShaderContext* decompilerContext, bool isRectVertexShader, bool usesGeometryShader, bool fetchVertexManually) { auto src = decompilerContext->shaderSource; switch (decompilerContext->shaderType) { case LatteConst::ShaderType::Vertex: if (usesGeometryShader) { src->add("object_data ObjectPayload& objectPayload [[payload]]"); src->add(", mesh_grid_properties meshGridProperties"); src->add(", uint tig [[threadgroup_position_in_grid]]"); src->add(", uint tid [[thread_index_in_threadgroup]]"); // TODO: only include index buffer if needed src->addFmt(", device uint* indexBuffer [[buffer({})]]", decompilerContext->output->resourceMappingMTL.indexBufferBinding); // TODO: put into the support buffer? src->addFmt(", constant uchar& indexType [[buffer({})]]", decompilerContext->output->resourceMappingMTL.indexTypeBinding); } else { // TODO: only include these if needed? src->add("uint vid [[vertex_id]]"); src->add(", uint iid [[instance_id]]"); } if (fetchVertexManually) src->add(" VERTEX_BUFFER_DEFINITIONS"); else src->add(", VertexIn in [[stage_in]]"); break; case LatteConst::ShaderType::Geometry: src->add("MeshType mesh"); src->add(", const object_data ObjectPayload& objectPayload [[payload]]"); break; case LatteConst::ShaderType::Pixel: src->add("FragmentIn in [[stage_in]]"); // TODO: only include these if needed? src->add(", float2 pointCoord [[point_coord]]"); src->add(", bool frontFacing [[front_facing]]"); break; default: break; } if (decompilerContext->output->resourceMappingMTL.uniformVarsBufferBindingPoint >= 0) src->addFmt(", constant SupportBuffer& supportBuffer [[buffer({})]]", decompilerContext->output->resourceMappingMTL.uniformVarsBufferBindingPoint); // streamout buffer (transform feedback) if ((decompilerContext->shaderType == LatteConst::ShaderType::Vertex && !decompilerContext->options->usesGeometryShader) || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) { if (decompilerContext->analyzer.hasStreamoutEnable && decompilerContext->analyzer.hasStreamoutWrite) src->addFmt(", device int* sb [[buffer({})]]" _CRLF, decompilerContext->output->resourceMappingMTL.tfStorageBindingPoint); } // uniform buffers _emitUniformBufferDefinitions(decompilerContext); // textures _emitTextureDefinitions(decompilerContext); } } ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h ================================================ // GPU7 instruction set is a hybrid between R600 and R700 // CF instructions #define CF_INST_NOP (0x00) #define CF_INST_TEX (0x01) #define CF_INST_LOOP_END (0x05) #define CF_INST_LOOP_START_DX10 (0x06) #define CF_INST_JUMP (0x0A) #define CF_INST_ELSE (0x0D) #define CF_INST_POP (0x0E) #define CF_INST_ELSE_AFTER (0x0F) #define CF_INST_CALL_FS (0x13) #define CF_INST_EXPORT (0x27) #define CF_INST_EXPORT_DONE (0x28) #define CF_INST_ALU (0x08) #define CF_INST_ALU_PUSH_BEFORE (0x09) #define CF_INST_ALU_POP_AFTER (0x0A) #define CF_INST_ALU_POP2_AFTER (0x0B) #define CF_INST_ALU_BREAK (0x0E) // leave loop if pred is modified #define ALU_OMOD_NONE (0) #define ALU_OMOD_MUL2 (1) #define ALU_OMOD_MUL4 (2) #define ALU_OMOD_DIV2 (3) // ALU op2 instructions #define ALU_OP2_INST_ADD (0x000) #define ALU_OP2_INST_MUL (0x001) #define ALU_OP2_INST_MUL_IEEE (0x002) #define ALU_OP2_INST_MAX (0x003) #define ALU_OP2_INST_MIN (0x004) #define ALU_OP2_INST_MAX_DX10 (0x005) #define ALU_OP2_INST_MIN_DX10 (0x006) #define ALU_OP2_INST_SETE (0x008) #define ALU_OP2_INST_SETGT (0x009) #define ALU_OP2_INST_SETGE (0x00A) #define ALU_OP2_INST_SETNE (0x00B) #define ALU_OP2_INST_SETE_DX10 (0x00C) #define ALU_OP2_INST_SETGT_DX10 (0x00D) #define ALU_OP2_INST_SETGE_DX10 (0x00E) #define ALU_OP2_INST_SETNE_DX10 (0x00F) #define ALU_OP2_INST_FLOOR (0x014) #define ALU_OP2_INST_FRACT (0x010) #define ALU_OP2_INST_TRUNC (0x011) #define ALU_OP2_INST_RNDNE (0x013) #define ALU_OP2_INST_MOVA_FLOOR (0x016) // changes address register #define ALU_OP2_INST_MOVA_INT (0x018) // changes address register #define ALU_OP2_INST_MOV (0x019) #define ALU_OP2_INST_NOP (0x01A) #define ALU_OP2_INST_PRED_SETE (0x020) #define ALU_OP2_INST_PRED_SETGT (0x021) #define ALU_OP2_INST_PRED_SETGE (0x022) #define ALU_OP2_INST_PRED_SETNE (0x023) #define ALU_OP2_INST_AND_INT (0x030) // integer instruction #define ALU_OP2_INST_OR_INT (0x031) // integer instruction #define ALU_OP2_INST_XOR_INT (0x032) // integer instruction #define ALU_OP2_INST_NOT_INT (0x033) // integer instruction #define ALU_OP2_INST_ADD_INT (0x034) // integer instruction #define ALU_OP2_INST_SUB_INT (0x035) // integer instruction #define ALU_OP2_INST_MAX_INT (0x036) // integer instruction #define ALU_OP2_INST_MIN_INT (0x037) // integer instruction #define ALU_OP2_INST_MAX_UINT (0x038) // integer instruction #define ALU_OP2_INST_MIN_UINT (0x039) // integer instruction #define ALU_OP2_INST_SETE_INT (0x03A) // integer instruction #define ALU_OP2_INST_SETGT_INT (0x03B) // integer instruction #define ALU_OP2_INST_SETGE_INT (0x03C) // integer instruction #define ALU_OP2_INST_SETNE_INT (0x03D) // integer instruction #define ALU_OP2_INST_SETGT_UINT (0x03E) // integer instruction #define ALU_OP2_INST_SETGE_UINT (0x03F) // integer instruction #define ALU_OP2_INST_PRED_SETE_INT (0x042) // integer instruction #define ALU_OP2_INST_PRED_SETGT_INT (0x043) // integer instruction #define ALU_OP2_INST_PRED_SETGE_INT (0x044) // integer instruction #define ALU_OP2_INST_PRED_SETNE_INT (0x045) // integer instruction #define ALU_OP2_INST_KILLE (0x02C) #define ALU_OP2_INST_KILLGT (0x02D) #define ALU_OP2_INST_KILLGE (0x02E) #define ALU_OP2_INST_KILLE_INT (0x046) #define ALU_OP2_INST_KILLGT_INT (0x047) #define ALU_OP2_INST_KILLNE_INT (0x049) #define ALU_OP2_INST_DOT4 (0x050) #define ALU_OP2_INST_DOT4_IEEE (0x051) #define ALU_OP2_INST_CUBE (0x052) #define ALU_OP2_INST_EXP_IEEE (0x061) #define ALU_OP2_INST_LOG_CLAMPED (0x062) #define ALU_OP2_INST_LOG_IEEE (0x063) #define ALU_OP2_INST_SQRT_IEEE (0x06A) #define ALU_OP2_INST_SIN (0x06E) #define ALU_OP2_INST_COS (0x06F) #define ALU_OP2_INST_RECIP_FF (0x065) #define ALU_OP2_INST_RECIP_IEEE (0x066) #define ALU_OP2_INST_RECIPSQRT_CLAMPED (0x067) #define ALU_OP2_INST_RECIPSQRT_FF (0x068) #define ALU_OP2_INST_RECIPSQRT_IEEE (0x069) #define ALU_OP2_INST_FLT_TO_INT (0x06B) // conversion instruction #define ALU_OP2_INST_INT_TO_FLOAT (0x06C) // conversion instruction #define ALU_OP2_INST_UINT_TO_FLOAT (0x06D) // conversion instruction #define ALU_OP2_INST_ASHR_INT (0x070) // integer instruction #define ALU_OP2_INST_LSHR_INT (0x071) // integer instruction #define ALU_OP2_INST_LSHL_INT (0x072) // integer instruction #define ALU_OP2_INST_MULLO_INT (0x073) // integer instruction #define ALU_OP2_INST_MULLO_UINT (0x075) // integer instruction #define ALU_OP2_INST_FLT_TO_UINT (0x079) // conversion instruction // ALU op3 instructions #define ALU_OP3_INST_MULADD (0x10) #define ALU_OP3_INST_MULADD_M2 (0x11) #define ALU_OP3_INST_MULADD_M4 (0x12) #define ALU_OP3_INST_MULADD_D2 (0x13) #define ALU_OP3_INST_MULADD_IEEE (0x14) #define ALU_OP3_INST_CMOVE (0x18) #define ALU_OP3_INST_CMOVGT (0x19) #define ALU_OP3_INST_CMOVGE (0x1A) #define ALU_OP3_INST_CNDE_INT (0x1C) // integer instruction #define ALU_OP3_INST_CNDGT_INT (0x1D) // integer instruction #define ALU_OP3_INST_CMOVGE_INT (0x1E) // integer instruction // fetch shader #define VTX_INST_SEMANTIC (0x01) #define VTX_INST_MEM (0x02) ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h ================================================ #pragma once struct LatteDecompilerALUInstruction { struct LatteDecompilerCFInstruction* cfInstruction{}; bool isOP3{}; uint32 opcode{}; uint32 instructionGroupIndex{}; uint8 indexMode{}; uint8 omod{}; // destination uint32 destGpr{}; uint8 destRel{}; uint8 destElem{}; uint8 destClamp{}; uint8 writeMask{}; // flags uint8 updateExecuteMask{}; uint8 updatePredicate{}; // source operands struct { uint32 sel{}; uint8 rel{}; uint8 abs{}; uint8 neg{}; uint8 chan{}; }sourceOperand[3]; union { uint32 w[4]; float f[4]; }literalData; // information from analyzer stage uint8 aluUnit{}; // 0-3 -> ALU.x/y/u/w (PV), 4 -> Trans unit (PS) uint8 indexInGroup{}; // index of instruction within instruction group bool isLastInstructionOfGroup{}; }; struct LatteDecompilerTEXInstruction { struct LatteDecompilerCFInstruction* cfInstruction{}; uint32 opcode{}; // texture or vertex fetch // shared sint32 srcGpr; sint32 dstGpr; sint8 dstSel[4]; // texture fetch struct { sint32 textureIndex{}; sint32 samplerIndex{}; uint32 offset{}; sint8 srcSel[4]{}; sint8 offsetX{}; sint8 offsetY{}; sint8 offsetZ{}; bool unnormalized[4]{}; // set if texture coordinates are in [0,dim] range instead of [0,1] sint8 lodBias{}; // divide by 16 to get actual value }textureFetch; // memRead struct { uint32 arrayBase{}; sint8 srcSelX{}; uint32 format{}; uint8 nfa{}; uint8 isSigned{}; }memRead; }; struct LatteDecompilerCFInstruction { uint32 type{}; uint32 cfAddr{}; // for clauses with instructions uint32 addr{}; sint32 count{}; // clause contains either ALU or TEX instructions std::vector<LatteDecompilerALUInstruction> instructionsALU; std::vector<LatteDecompilerTEXInstruction> instructionsTEX; // for clauses that access uniform buffers uint32 cBank0Index{}; uint32 cBank1Index{}; uint32 cBank0AddrBase{}; uint32 cBank1AddrBase{}; // for exports uint32 exportType{}; uint8 exportComponentSel[4]{}; uint32 exportBurstCount{}; // for mem write uint32 memWriteArraySize{}; uint8 memWriteCompMask{}; uint8 memWriteElemSize{}; // 0-3 // for exports and mem write uint32 exportArrayBase{}; uint32 exportSourceGPR{}; // misc uint32 cfCond{}; uint32 popCount{}; // information from analyzer stage bool modifiesPredicate{}; bool modifiesActiveMask{}; uint32 numPredInstructions{}; sint32 activeStackDepth{}; // stack depth during the clause/CF instruction LatteDecompilerCFInstruction() { } ~LatteDecompilerCFInstruction() { cemu_assert_debug(!(instructionsALU.size() != 0 && instructionsTEX.size() != 0)); // make sure we haven't accidentally added the wrong instruction type } LatteDecompilerCFInstruction(const LatteDecompilerCFInstruction& mE) = default; LatteDecompilerCFInstruction(LatteDecompilerCFInstruction&& mE) = default; LatteDecompilerCFInstruction& operator=(LatteDecompilerCFInstruction&& mE) = default; }; struct LatteDecompilerSubroutineInfo { uint32 cfAddr; std::vector<LatteDecompilerCFInstruction> instructions; }; // helper struct to track the highest accessed offset within a buffer struct LatteDecompilerBufferAccessTracker { bool hasStaticIndexAccess{false}; bool hasDynamicIndexAccess{false}; sint32 highestAccessDynamicIndex{0}; sint32 highestAccessStaticIndex{0}; // track access, index is the array index and not a byte offset void TrackAccess(sint32 index, bool isDynamicIndex) { if (isDynamicIndex) { hasDynamicIndexAccess = true; if (index > highestAccessDynamicIndex) highestAccessDynamicIndex = index; } else { hasStaticIndexAccess = true; if (index > highestAccessStaticIndex) highestAccessStaticIndex = index; } } sint32 DetermineSize(uint64 shaderBaseHash, sint32 maximumSize) const { // here we try to predict the accessed byte range so we dont have to upload the whole buffer // if no bound can be determined then return maximumSize // for some known shaders we use hand-tuned values instead of the maximumSize fallback value that those shaders would normally use if(shaderBaseHash == 0x8ff56afdf1a2f837) // XCX text rendering return 24; if(shaderBaseHash == 0x37b9100c1310d3bb) // BotW UI backdrops 1 return 24; if(shaderBaseHash == 0xf7ba548c1fefe24a) // BotW UI backdrops 2 return 30; sint32 highestAccessIndex = -1; if(hasStaticIndexAccess) highestAccessIndex = highestAccessStaticIndex; if(hasDynamicIndexAccess) return maximumSize; // dynamic index exists and no bound can be determined if (highestAccessIndex < 0) return 1; // no access at all? But avoid zero as a size return highestAccessIndex + 1; } bool HasAccess() const { return hasStaticIndexAccess || hasDynamicIndexAccess; } bool HasRelativeAccess() const { return hasDynamicIndexAccess; } }; struct LatteDecompilerShaderContext { LatteDecompilerOutput_t* output; LatteDecompilerShader* shader; LatteConst::ShaderType shaderType; const class LatteDecompilerOptions* options; uint32* contextRegisters; // deprecated struct LatteContextRegister* contextRegistersNew; uint64 shaderBaseHash; StringBuf* shaderSource; std::vector<LatteDecompilerCFInstruction> cfInstructions; // fetch shader (required for vertex shader) LatteFetchShader* fetchShader{}; // geometry copy shader (only present when geometry shader is active) LatteParsedGSCopyShader* parsedGSCopyShader; // state bool hasError; // type tracker struct { // data type tracker uint8 defaultDataType; bool genFloatReg; // if set, generate R*f register variables bool genIntReg; // if set, generate R*i register variables bool useArrayGPRs; // if set, an array is used to represent GPRs instead of individual variables }typeTracker; // analyzer struct { // general bool hasStreamoutEnable{}; // set if streamout is enabled bool hasLoops{}; // loop directives present in shader // vertex shader bool isPointsPrimitive{}; // set if current render primitive is points bool outputPointSize{}; // set if the current shader should output the point size std::bitset<256> inputAttributSemanticMask; // one set bit for every used semanticId - todo: there are only 128 bit available semantic locations? The MSB has special meaning? // uniforms LatteDecompilerBufferAccessTracker uniformRegisterAccessTracker; LatteDecompilerBufferAccessTracker uniformBufferAccessTracker[LATTE_NUM_MAX_UNIFORM_BUFFERS]; // ssbo bool hasSSBORead; // shader has instructions that read from SSBO bool hasSSBOWrite; // shader has instructions that write to SSBO // textures std::bitset<LATTE_NUM_MAX_TEX_UNITS> texUnitUsesTexelCoordinates; bool hasCubeMapTexture; // set to true if a cubemap texture is used bool hasGradientLookup; // set to true if texture lookup with custom gradients is used // misc bool usesRelativeGPRRead; // set if indexed GPR reads are used bool usesRelativeGPRWrite; // set if indexed GPR writes are used uint8 gprUseMask[(LATTE_NUM_GPR + 7) / 8]; // 1 bit per GPR, set if GPR is read/written anywhere in the program (ignores GPR accesses with relative index) bool hasStreamoutWrite; // stream-out CF instructions are used bool hasRedcCUBE; // has cube reduction instruction bool modifiesPixelActiveState; // set if the active mask is changed anywhere in the shader (If false, we can skip active mask checks) bool usesIntegerValues; // set if the shader uses any kind of integer instruction or integer-based GPR/AR access sint32 activeStackMaxDepth; // maximum depth of pixel state stack // analyzer stage (vs) bool writesPointSize{}; // streamout (vs and gs) bool useSSBOForStreamout{}; // geometry shader uint32 numEmitVertex{}; // counts how often emit vertex instruction is found }analyzer; // set while generating code for subroutine bool isSubroutine; LatteDecompilerSubroutineInfo* subroutineInfo; // emitter bool hasUniformVarBlock; sint32 currentBindingPointVK{}; sint32 currentBufferBindingPointMTL{}; sint32 currentTextureBindingPointMTL{}; struct ALUClauseTemporariesState* aluPVPSState{nullptr}; // misc std::vector<LatteDecompilerSubroutineInfo> list_subroutines; }; void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader); void LatteDecompiler_analyzeDataTypes(LatteDecompilerShaderContext* shaderContext); void LatteDecompiler_emitGLSLShader(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader); #if ENABLE_METAL void LatteDecompiler_emitMSLShader(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader); #endif void LatteDecompiler_cleanup(LatteDecompilerShaderContext* shaderContext); // helper functions sint32 LatteDecompiler_getColorOutputIndexFromExportIndex(LatteDecompilerShaderContext* shaderContext, sint32 exportIndex); ================================================ FILE: src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerRegisterDataTypeTracker.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h" void LatteDecompiler_analyzeDataTypes(LatteDecompilerShaderContext* shaderContext) { // determine default type if (shaderContext->analyzer.usesIntegerValues) { shaderContext->typeTracker.defaultDataType = LATTE_DECOMPILER_DTYPE_SIGNED_INT; shaderContext->typeTracker.genIntReg = true; } else { shaderContext->typeTracker.defaultDataType = LATTE_DECOMPILER_DTYPE_FLOAT; shaderContext->typeTracker.genFloatReg = true; } // determine register representation if (shaderContext->analyzer.usesRelativeGPRWrite) { shaderContext->typeTracker.useArrayGPRs = true; } } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/CachedFBOMtl.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/CachedFBOMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureViewMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" CachedFBOMtl::CachedFBOMtl(class MetalRenderer* metalRenderer, uint64 key) : LatteCachedFBO(key) { m_renderPassDescriptor = MTL::RenderPassDescriptor::alloc()->init(); bool hasAttachment = false; for (int i = 0; i < 8; ++i) { const auto& buffer = colorBuffer[i]; auto textureView = (LatteTextureViewMtl*)buffer.texture; if (!textureView) { continue; } auto colorAttachment = m_renderPassDescriptor->colorAttachments()->object(i); colorAttachment->setTexture(textureView->GetRGBAView()); colorAttachment->setLoadAction(MTL::LoadActionLoad); colorAttachment->setStoreAction(MTL::StoreActionStore); hasAttachment = true; } // setup depth attachment if (depthBuffer.texture) { auto textureView = static_cast<LatteTextureViewMtl*>(depthBuffer.texture); auto depthAttachment = m_renderPassDescriptor->depthAttachment(); depthAttachment->setTexture(textureView->GetRGBAView()); depthAttachment->setLoadAction(MTL::LoadActionLoad); depthAttachment->setStoreAction(MTL::StoreActionStore); // setup stencil attachment if (depthBuffer.hasStencil && GetMtlPixelFormatInfo(depthBuffer.texture->format, true).hasStencil) { auto stencilAttachment = m_renderPassDescriptor->stencilAttachment(); stencilAttachment->setTexture(textureView->GetRGBAView()); stencilAttachment->setLoadAction(MTL::LoadActionLoad); stencilAttachment->setStoreAction(MTL::StoreActionStore); } hasAttachment = true; } // HACK: setup a dummy color attachment to prevent Metal from discarding draws for stremout draws in Super Smash Bros. for Wii U (works fine on MoltenVK without this hack though) if (!hasAttachment) { auto colorAttachment = m_renderPassDescriptor->colorAttachments()->object(0); colorAttachment->setTexture(metalRenderer->GetNullTexture2D()); colorAttachment->setLoadAction(MTL::LoadActionDontCare); colorAttachment->setStoreAction(MTL::StoreActionDontCare); } // Visibility buffer m_renderPassDescriptor->setVisibilityResultBuffer(metalRenderer->GetOcclusionQueryResultBuffer()); } CachedFBOMtl::~CachedFBOMtl() { m_renderPassDescriptor->release(); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/CachedFBOMtl.h ================================================ #pragma once #include <Metal/Metal.hpp> #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/Core/LatteCachedFBO.h" class CachedFBOMtl : public LatteCachedFBO { public: CachedFBOMtl(class MetalRenderer* metalRenderer, uint64 key); ~CachedFBOMtl(); MTL::RenderPassDescriptor* GetRenderPassDescriptor() { return m_renderPassDescriptor; } private: MTL::RenderPassDescriptor* m_renderPassDescriptor = nullptr; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/LatteTextureMtl.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureViewMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" LatteTextureMtl::LatteTextureMtl(class MetalRenderer* mtlRenderer, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) : LatteTexture(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth), m_mtlr(mtlRenderer) { NS_STACK_SCOPED MTL::TextureDescriptor* desc = MTL::TextureDescriptor::alloc()->init(); desc->setStorageMode(MTL::StorageModePrivate); //desc->setCpuCacheMode(MTL::CPUCacheModeWriteCombined); sint32 effectiveBaseWidth = width; sint32 effectiveBaseHeight = height; sint32 effectiveBaseDepth = depth; if (overwriteInfo.hasResolutionOverwrite) { effectiveBaseWidth = overwriteInfo.width; effectiveBaseHeight = overwriteInfo.height; effectiveBaseDepth = overwriteInfo.depth; } effectiveBaseWidth = std::max(1, effectiveBaseWidth); effectiveBaseHeight = std::max(1, effectiveBaseHeight); effectiveBaseDepth = std::max(1, effectiveBaseDepth); MTL::TextureType textureType; switch (dim) { case Latte::E_DIM::DIM_1D: textureType = MTL::TextureType1D; effectiveBaseHeight = 1; break; case Latte::E_DIM::DIM_2D: case Latte::E_DIM::DIM_2D_MSAA: textureType = MTL::TextureType2D; break; case Latte::E_DIM::DIM_2D_ARRAY: textureType = MTL::TextureType2DArray; break; case Latte::E_DIM::DIM_3D: textureType = MTL::TextureType3D; break; case Latte::E_DIM::DIM_CUBEMAP: cemu_assert_debug(effectiveBaseDepth % 6 == 0 && "cubemaps must have an array length multiple of 6"); textureType = MTL::TextureTypeCubeArray; break; default: cemu_assert_unimplemented(); textureType = MTL::TextureType2D; break; } desc->setTextureType(textureType); // Clamp mip levels mipLevels = std::min(mipLevels, (uint32)maxPossibleMipLevels); mipLevels = std::max(mipLevels, (uint32)1); desc->setWidth(effectiveBaseWidth); desc->setHeight(effectiveBaseHeight); desc->setMipmapLevelCount(mipLevels); if (textureType == MTL::TextureType3D) { desc->setDepth(effectiveBaseDepth); } else if (textureType == MTL::TextureTypeCubeArray) { desc->setArrayLength(effectiveBaseDepth / 6); } else if (textureType == MTL::TextureType2DArray) { desc->setArrayLength(effectiveBaseDepth); } auto pixelFormat = GetMtlPixelFormat(format, isDepth); desc->setPixelFormat(pixelFormat); MTL::TextureUsage usage = MTL::TextureUsageShaderRead | MTL::TextureUsagePixelFormatView; if (FormatIsRenderable(format)) usage |= MTL::TextureUsageRenderTarget; desc->setUsage(usage); m_texture = mtlRenderer->GetDevice()->newTexture(desc); } LatteTextureMtl::~LatteTextureMtl() { m_texture->release(); } LatteTextureView* LatteTextureMtl::CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) { cemu_assert_debug(mipCount > 0); cemu_assert_debug(sliceCount > 0); cemu_assert_debug((firstMip + mipCount) <= this->mipLevels); cemu_assert_debug((firstSlice + sliceCount) <= this->depth); return new LatteTextureViewMtl(m_mtlr, this, dim, format, firstMip, mipCount, firstSlice, sliceCount); } // TODO: lazy allocation? void LatteTextureMtl::AllocateOnHost() { // The texture is already allocated } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/LatteTextureMtl.h ================================================ #pragma once #include <Metal/Metal.hpp> #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "HW/Latte/ISA/LatteReg.h" #include "util/ChunkedHeap/ChunkedHeap.h" class LatteTextureMtl : public LatteTexture { public: LatteTextureMtl(class MetalRenderer* mtlRenderer, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); ~LatteTextureMtl(); MTL::Texture* GetTexture() const { return m_texture; } void AllocateOnHost() override; protected: LatteTextureView* CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) override; private: class MetalRenderer* m_mtlr; MTL::Texture* m_texture; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" LatteTextureReadbackInfoMtl::~LatteTextureReadbackInfoMtl() { if (m_commandBuffer) m_commandBuffer->release(); } void LatteTextureReadbackInfoMtl::StartTransfer() { cemu_assert(m_textureView); auto* baseTexture = (LatteTextureMtl*)m_textureView->baseTexture; cemu_assert_debug(m_textureView->firstSlice == 0); cemu_assert_debug(m_textureView->firstMip == 0); cemu_assert_debug(m_textureView->baseTexture->dim != Latte::E_DIM::DIM_3D); size_t bytesPerRow = GetMtlTextureBytesPerRow(baseTexture->format, baseTexture->isDepth, baseTexture->width); size_t bytesPerImage = GetMtlTextureBytesPerImage(baseTexture->format, baseTexture->isDepth, baseTexture->height, bytesPerRow); auto blitCommandEncoder = m_mtlr->GetBlitCommandEncoder(); blitCommandEncoder->copyFromTexture(baseTexture->GetTexture(), 0, 0, MTL::Origin{0, 0, 0}, MTL::Size{(uint32)baseTexture->width, (uint32)baseTexture->height, 1}, m_mtlr->GetTextureReadbackBuffer(), m_bufferOffset, bytesPerRow, bytesPerImage); m_commandBuffer = m_mtlr->GetCurrentCommandBuffer()->retain(); // TODO: uncomment? //m_mtlr->RequestSoonCommit(); m_mtlr->CommitCommandBuffer(); } bool LatteTextureReadbackInfoMtl::IsFinished() { // Command buffer wasn't even comitted, let's commit immediately //if (m_mtlr->GetCurrentCommandBuffer() == m_commandBuffer) // m_mtlr->CommitCommandBuffer(); return CommandBufferCompleted(m_commandBuffer); } void LatteTextureReadbackInfoMtl::ForceFinish() { m_commandBuffer->waitUntilCompleted(); } uint8* LatteTextureReadbackInfoMtl::GetData() { return (uint8*)m_mtlr->GetTextureReadbackBuffer()->contents() + m_bufferOffset; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Metal/MetalCommon.h" #include "Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h" class LatteTextureReadbackInfoMtl : public LatteTextureReadbackInfo { public: LatteTextureReadbackInfoMtl(class MetalRenderer* mtlRenderer, LatteTextureView* textureView, uint32 bufferOffset) : LatteTextureReadbackInfo(textureView), m_mtlr{mtlRenderer}, m_bufferOffset{bufferOffset} {} ~LatteTextureReadbackInfoMtl(); void StartTransfer() override; bool IsFinished() override; void ForceFinish() override; uint8* GetData() override; private: class MetalRenderer* m_mtlr; MTL::CommandBuffer* m_commandBuffer = nullptr; uint32 m_bufferOffset = 0; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/LatteTextureViewMtl.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureViewMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" #include "Metal/MTLTexture.hpp" uint32 LatteTextureMtl_AdjustTextureCompSel(Latte::E_GX2SURFFMT format, uint32 compSel) { switch (format) { case Latte::E_GX2SURFFMT::R8_UNORM: // R8 is replicated on all channels (while OpenGL would return 1.0 for BGA instead) case Latte::E_GX2SURFFMT::R8_SNORM: // probably the same as _UNORM, but needs testing if (compSel >= 1 && compSel <= 3) compSel = 0; break; case Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM: // order of components is reversed (RGBA -> ABGR) if (compSel >= 0 && compSel <= 3) compSel = 3 - compSel; break; case Latte::E_GX2SURFFMT::BC4_UNORM: case Latte::E_GX2SURFFMT::BC4_SNORM: if (compSel >= 1 && compSel <= 3) compSel = 0; break; case Latte::E_GX2SURFFMT::BC5_UNORM: case Latte::E_GX2SURFFMT::BC5_SNORM: // RG maps to RG // B maps to ? // A maps to G (guessed) if (compSel == 3) compSel = 1; // read Alpha as Green break; case Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM: // reverse components (Wii U: ABGR, OpenGL: RGBA) // used in Resident Evil Revelations if (compSel >= 0 && compSel <= 3) compSel = 3 - compSel; break; case Latte::E_GX2SURFFMT::X24_G8_UINT: // map everything to alpha? if (compSel >= 0 && compSel <= 3) compSel = 3; break; case Latte::E_GX2SURFFMT::R4_G4_UNORM: // red and green swapped if (compSel == 0) compSel = 1; else if (compSel == 1) compSel = 0; break; default: break; } return compSel; } LatteTextureViewMtl::LatteTextureViewMtl(MetalRenderer* mtlRenderer, LatteTextureMtl* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) : LatteTextureView(texture, firstMip, mipCount, firstSlice, sliceCount, dim, format), m_mtlr(mtlRenderer), m_baseTexture(texture) { m_rgbaView = CreateSwizzledView(RGBA_SWIZZLE); } LatteTextureViewMtl::~LatteTextureViewMtl() { m_rgbaView->release(); for (sint32 i = 0; i < std::size(m_viewCache); i++) { if (m_viewCache[i].key != INVALID_SWIZZLE) m_viewCache[i].texture->release(); } for (auto& [key, texture] : m_fallbackViewCache) { texture->release(); } } MTL::Texture* LatteTextureViewMtl::GetSwizzledView(uint32 gpuSamplerSwizzle) { // Mask out gpuSamplerSwizzle &= 0x0FFF0000; // RGBA swizzle == no swizzle if (gpuSamplerSwizzle == RGBA_SWIZZLE) { return m_rgbaView; } // First, try to find a view in the cache // Fast cache sint32 freeIndex = -1; for (sint32 i = 0; i < std::size(m_viewCache); i++) { const auto& entry = m_viewCache[i]; if (entry.key == gpuSamplerSwizzle) { return entry.texture; } else if (entry.key == INVALID_SWIZZLE && freeIndex == -1) { freeIndex = i; } } // Fallback cache auto& fallbackEntry = m_fallbackViewCache[gpuSamplerSwizzle]; if (fallbackEntry) { return fallbackEntry; } MTL::Texture* texture = CreateSwizzledView(gpuSamplerSwizzle); if (freeIndex != -1) m_viewCache[freeIndex] = {gpuSamplerSwizzle, texture}; else fallbackEntry = texture; return texture; } MTL::Texture* LatteTextureViewMtl::CreateSwizzledView(uint32 gpuSamplerSwizzle) { uint32 compSelR = (gpuSamplerSwizzle >> 16) & 0x7; uint32 compSelG = (gpuSamplerSwizzle >> 19) & 0x7; uint32 compSelB = (gpuSamplerSwizzle >> 22) & 0x7; uint32 compSelA = (gpuSamplerSwizzle >> 25) & 0x7; compSelR = LatteTextureMtl_AdjustTextureCompSel(format, compSelR); compSelG = LatteTextureMtl_AdjustTextureCompSel(format, compSelG); compSelB = LatteTextureMtl_AdjustTextureCompSel(format, compSelB); compSelA = LatteTextureMtl_AdjustTextureCompSel(format, compSelA); MTL::TextureType textureType; switch (dim) { case Latte::E_DIM::DIM_1D: textureType = MTL::TextureType1D; break; case Latte::E_DIM::DIM_2D: case Latte::E_DIM::DIM_2D_MSAA: textureType = MTL::TextureType2D; break; case Latte::E_DIM::DIM_2D_ARRAY: textureType = MTL::TextureType2DArray; break; case Latte::E_DIM::DIM_3D: textureType = MTL::TextureType3D; break; case Latte::E_DIM::DIM_CUBEMAP: cemu_assert_debug(this->numSlice % 6 == 0 && "cubemaps must have an array length multiple of 6"); textureType = MTL::TextureTypeCubeArray; break; default: cemu_assert_unimplemented(); textureType = MTL::TextureType2D; break; } uint32 baseLevel = firstMip; uint32 levelCount = this->numMip; uint32 baseLayer = 0; uint32 layerCount = 1; // TODO: check if base texture is 3D texture as well if (textureType == MTL::TextureType3D) { cemu_assert_debug(firstMip == 0); cemu_assert_debug(this->numSlice == baseTexture->depth); } else { baseLayer = firstSlice; if (textureType == MTL::TextureTypeCubeArray || textureType == MTL::TextureType2DArray) layerCount = this->numSlice; } MTL::TextureSwizzleChannels swizzle; swizzle.red = GetMtlTextureSwizzle(compSelR); swizzle.green = GetMtlTextureSwizzle(compSelG); swizzle.blue = GetMtlTextureSwizzle(compSelB); swizzle.alpha = GetMtlTextureSwizzle(compSelA); // Clamp mip levels levelCount = std::min(levelCount, m_baseTexture->maxPossibleMipLevels - baseLevel); levelCount = std::max(levelCount, (uint32)1); auto pixelFormat = GetMtlPixelFormat(format, m_baseTexture->isDepth); MTL::Texture* texture = m_baseTexture->GetTexture()->newTextureView(pixelFormat, textureType, NS::Range::Make(baseLevel, levelCount), NS::Range::Make(baseLayer, layerCount), swizzle); return texture; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/LatteTextureViewMtl.h ================================================ #pragma once #include <Metal/Metal.hpp> #include <unordered_map> #include "Cafe/HW/Latte/Core/LatteTexture.h" #define RGBA_SWIZZLE 0x06880000 #define INVALID_SWIZZLE 0xFFFFFFFF class LatteTextureViewMtl : public LatteTextureView { public: LatteTextureViewMtl(class MetalRenderer* mtlRenderer, class LatteTextureMtl* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount); ~LatteTextureViewMtl(); MTL::Texture* GetSwizzledView(uint32 gpuSamplerSwizzle); MTL::Texture* GetRGBAView() { return GetSwizzledView(RGBA_SWIZZLE); } private: class MetalRenderer* m_mtlr; class LatteTextureMtl* m_baseTexture; MTL::Texture* m_rgbaView; struct { uint32 key; MTL::Texture* texture; } m_viewCache[4] = {{INVALID_SWIZZLE, nullptr}, {INVALID_SWIZZLE, nullptr}, {INVALID_SWIZZLE, nullptr}, {INVALID_SWIZZLE, nullptr}}; std::unordered_map<uint32, MTL::Texture*> m_fallbackViewCache; MTL::Texture* CreateSwizzledView(uint32 gpuSamplerSwizzle); }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/LatteToMtl.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" #include "Cemu/Logging/CemuLogging.h" #include "HW/Latte/Core/LatteTextureLoader.h" #include "HW/Latte/Renderer/Metal/MetalCommon.h" std::map<Latte::E_GX2SURFFMT, MetalPixelFormatInfo> MTL_COLOR_FORMAT_TABLE = { {Latte::E_GX2SURFFMT::INVALID_FORMAT, {MTL::PixelFormatInvalid, MetalDataType::NONE, 0}}, {Latte::E_GX2SURFFMT::R4_G4_UNORM, {MTL::PixelFormatABGR4Unorm, MetalDataType::FLOAT, 2}}, {Latte::E_GX2SURFFMT::R5_G6_B5_UNORM, {MTL::PixelFormatB5G6R5Unorm, MetalDataType::FLOAT, 2}}, {Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM, {MTL::PixelFormatBGR5A1Unorm, MetalDataType::FLOAT, 2}}, {Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM, {MTL::PixelFormatABGR4Unorm, MetalDataType::FLOAT, 2}}, {Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM, {MTL::PixelFormatA1BGR5Unorm, MetalDataType::FLOAT, 2}}, {Latte::E_GX2SURFFMT::R8_UNORM, {MTL::PixelFormatR8Unorm, MetalDataType::FLOAT, 1}}, {Latte::E_GX2SURFFMT::R8_SNORM, {MTL::PixelFormatR8Snorm, MetalDataType::FLOAT, 1}}, {Latte::E_GX2SURFFMT::R8_UINT, {MTL::PixelFormatR8Uint, MetalDataType::UINT, 1}}, {Latte::E_GX2SURFFMT::R8_SINT, {MTL::PixelFormatR8Sint, MetalDataType::INT, 1}}, {Latte::E_GX2SURFFMT::R8_G8_UNORM, {MTL::PixelFormatRG8Unorm, MetalDataType::FLOAT, 2}}, {Latte::E_GX2SURFFMT::R8_G8_SNORM, {MTL::PixelFormatRG8Snorm, MetalDataType::FLOAT, 2}}, {Latte::E_GX2SURFFMT::R8_G8_UINT, {MTL::PixelFormatRG8Uint, MetalDataType::UINT, 2}}, {Latte::E_GX2SURFFMT::R8_G8_SINT, {MTL::PixelFormatRG8Sint, MetalDataType::INT, 2}}, {Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM, {MTL::PixelFormatRGBA8Unorm, MetalDataType::FLOAT, 4}}, {Latte::E_GX2SURFFMT::R8_G8_B8_A8_SNORM, {MTL::PixelFormatRGBA8Snorm, MetalDataType::FLOAT, 4}}, {Latte::E_GX2SURFFMT::R8_G8_B8_A8_UINT, {MTL::PixelFormatRGBA8Uint, MetalDataType::UINT, 4}}, {Latte::E_GX2SURFFMT::R8_G8_B8_A8_SINT, {MTL::PixelFormatRGBA8Sint, MetalDataType::INT, 4}}, {Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB, {MTL::PixelFormatRGBA8Unorm_sRGB, MetalDataType::FLOAT, 4}}, {Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM, {MTL::PixelFormatRGB10A2Unorm, MetalDataType::FLOAT, 4}}, {Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM, {MTL::PixelFormatRGBA16Snorm, MetalDataType::FLOAT, 8}}, {Latte::E_GX2SURFFMT::R10_G10_B10_A2_UINT, {MTL::PixelFormatRGB10A2Uint, MetalDataType::UINT, 4}}, {Latte::E_GX2SURFFMT::R10_G10_B10_A2_SINT, {MTL::PixelFormatRGBA16Sint, MetalDataType::INT, 8}}, {Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB, {MTL::PixelFormatRGB10A2Unorm, MetalDataType::FLOAT, 4}}, // TODO: sRGB? {Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM, {MTL::PixelFormatBGR10A2Unorm, MetalDataType::FLOAT, 4}}, {Latte::E_GX2SURFFMT::A2_B10_G10_R10_UINT, {MTL::PixelFormatRGB10A2Uint, MetalDataType::UINT, 4}}, {Latte::E_GX2SURFFMT::R16_UNORM, {MTL::PixelFormatR16Unorm, MetalDataType::FLOAT, 2}}, {Latte::E_GX2SURFFMT::R16_SNORM, {MTL::PixelFormatR16Snorm, MetalDataType::FLOAT, 2}}, {Latte::E_GX2SURFFMT::R16_UINT, {MTL::PixelFormatR16Uint, MetalDataType::UINT, 2}}, {Latte::E_GX2SURFFMT::R16_SINT, {MTL::PixelFormatR16Sint, MetalDataType::INT, 2}}, {Latte::E_GX2SURFFMT::R16_FLOAT, {MTL::PixelFormatR16Float, MetalDataType::FLOAT, 2}}, {Latte::E_GX2SURFFMT::R16_G16_UNORM, {MTL::PixelFormatRG16Unorm, MetalDataType::FLOAT, 4}}, {Latte::E_GX2SURFFMT::R16_G16_SNORM, {MTL::PixelFormatRG16Snorm, MetalDataType::FLOAT, 4}}, {Latte::E_GX2SURFFMT::R16_G16_UINT, {MTL::PixelFormatRG16Uint, MetalDataType::UINT, 4}}, {Latte::E_GX2SURFFMT::R16_G16_SINT, {MTL::PixelFormatRG16Sint, MetalDataType::INT, 4}}, {Latte::E_GX2SURFFMT::R16_G16_FLOAT, {MTL::PixelFormatRG16Float, MetalDataType::FLOAT, 4}}, {Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM, {MTL::PixelFormatRGBA16Unorm, MetalDataType::FLOAT, 8}}, {Latte::E_GX2SURFFMT::R16_G16_B16_A16_SNORM, {MTL::PixelFormatRGBA16Snorm, MetalDataType::FLOAT, 8}}, {Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT, {MTL::PixelFormatRGBA16Uint, MetalDataType::UINT, 8}}, {Latte::E_GX2SURFFMT::R16_G16_B16_A16_SINT, {MTL::PixelFormatRGBA16Sint, MetalDataType::INT, 8}}, {Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT, {MTL::PixelFormatRGBA16Float, MetalDataType::FLOAT, 8}}, {Latte::E_GX2SURFFMT::R24_X8_UNORM, {MTL::PixelFormatR32Float, MetalDataType::FLOAT, 4}}, // TODO: correct? {Latte::E_GX2SURFFMT::R24_X8_FLOAT, {MTL::PixelFormatR32Float, MetalDataType::FLOAT, 4}}, // TODO: correct? {Latte::E_GX2SURFFMT::X24_G8_UINT, {MTL::PixelFormatRGBA8Uint, MetalDataType::UINT, 4}}, // TODO: correct? {Latte::E_GX2SURFFMT::R32_X8_FLOAT, {MTL::PixelFormatR32Float, MetalDataType::FLOAT, 4}}, // TODO: correct? {Latte::E_GX2SURFFMT::X32_G8_UINT_X24, {MTL::PixelFormatRGBA16Uint, MetalDataType::UINT, 8}}, // TODO: correct? {Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT, {MTL::PixelFormatRG11B10Float, MetalDataType::FLOAT, 4}}, {Latte::E_GX2SURFFMT::R32_UINT, {MTL::PixelFormatR32Uint, MetalDataType::UINT, 4}}, {Latte::E_GX2SURFFMT::R32_SINT, {MTL::PixelFormatR32Sint, MetalDataType::INT, 4}}, {Latte::E_GX2SURFFMT::R32_FLOAT, {MTL::PixelFormatR32Float, MetalDataType::FLOAT, 4}}, {Latte::E_GX2SURFFMT::R32_G32_UINT, {MTL::PixelFormatRG32Uint, MetalDataType::UINT, 8}}, {Latte::E_GX2SURFFMT::R32_G32_SINT, {MTL::PixelFormatRG32Sint, MetalDataType::INT, 8}}, {Latte::E_GX2SURFFMT::R32_G32_FLOAT, {MTL::PixelFormatRG32Float, MetalDataType::FLOAT, 8}}, {Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT, {MTL::PixelFormatRGBA32Uint, MetalDataType::UINT, 16}}, {Latte::E_GX2SURFFMT::R32_G32_B32_A32_SINT, {MTL::PixelFormatRGBA32Sint, MetalDataType::INT, 16}}, {Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT, {MTL::PixelFormatRGBA32Float, MetalDataType::FLOAT, 16}}, {Latte::E_GX2SURFFMT::BC1_UNORM, {MTL::PixelFormatBC1_RGBA, MetalDataType::FLOAT, 8, {4, 4}}}, // TODO: correct? {Latte::E_GX2SURFFMT::BC1_SRGB, {MTL::PixelFormatBC1_RGBA_sRGB, MetalDataType::FLOAT, 8, {4, 4}}}, // TODO: correct? {Latte::E_GX2SURFFMT::BC2_UNORM, {MTL::PixelFormatBC2_RGBA, MetalDataType::FLOAT, 16, {4, 4}}}, // TODO: correct? {Latte::E_GX2SURFFMT::BC2_SRGB, {MTL::PixelFormatBC2_RGBA_sRGB, MetalDataType::FLOAT, 16, {4, 4}}}, // TODO: correct? {Latte::E_GX2SURFFMT::BC3_UNORM, {MTL::PixelFormatBC3_RGBA, MetalDataType::FLOAT, 16, {4, 4}}}, // TODO: correct? {Latte::E_GX2SURFFMT::BC3_SRGB, {MTL::PixelFormatBC3_RGBA_sRGB, MetalDataType::FLOAT, 16, {4, 4}}}, // TODO: correct? {Latte::E_GX2SURFFMT::BC4_UNORM, {MTL::PixelFormatBC4_RUnorm, MetalDataType::FLOAT, 8, {4, 4}}}, // TODO: correct? {Latte::E_GX2SURFFMT::BC4_SNORM, {MTL::PixelFormatBC4_RSnorm, MetalDataType::FLOAT, 8, {4, 4}}}, // TODO: correct? {Latte::E_GX2SURFFMT::BC5_UNORM, {MTL::PixelFormatBC5_RGUnorm, MetalDataType::FLOAT, 16, {4, 4}}}, // TODO: correct? {Latte::E_GX2SURFFMT::BC5_SNORM, {MTL::PixelFormatBC5_RGSnorm, MetalDataType::FLOAT, 16, {4, 4}}}, // TODO: correct? }; std::map<Latte::E_GX2SURFFMT, MetalPixelFormatInfo> MTL_DEPTH_FORMAT_TABLE = { {Latte::E_GX2SURFFMT::INVALID_FORMAT, {MTL::PixelFormatInvalid, MetalDataType::NONE, 0}}, {Latte::E_GX2SURFFMT::D24_S8_UNORM, {MTL::PixelFormatDepth24Unorm_Stencil8, MetalDataType::NONE, 4, {1, 1}, true}}, {Latte::E_GX2SURFFMT::D24_S8_FLOAT, {MTL::PixelFormatDepth32Float_Stencil8, MetalDataType::NONE, 4, {1, 1}, true}}, {Latte::E_GX2SURFFMT::D32_S8_FLOAT, {MTL::PixelFormatDepth32Float_Stencil8, MetalDataType::NONE, 5, {1, 1}, true}}, {Latte::E_GX2SURFFMT::D16_UNORM, {MTL::PixelFormatDepth16Unorm, MetalDataType::NONE, 2, {1, 1}}}, {Latte::E_GX2SURFFMT::D32_FLOAT, {MTL::PixelFormatDepth32Float, MetalDataType::NONE, 4, {1, 1}}}, }; // TODO: R10_G10_B10_A2_UINT and R10_G10_B10_A2_SINT // TODO: A2_B10_G10_R10_UNORM and A2_B10_G10_R10_UINT void CheckForPixelFormatSupport(const MetalPixelFormatSupport& support) { // Texture decoders // Color MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT].textureDecoder = TextureDecoder_R32_G32_B32_A32_FLOAT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT].textureDecoder = TextureDecoder_R32_G32_B32_A32_UINT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT].textureDecoder = TextureDecoder_R16_G16_B16_A16_FLOAT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT].textureDecoder = TextureDecoder_R16_G16_B16_A16_UINT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM].textureDecoder = TextureDecoder_R16_G16_B16_A16::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R16_G16_B16_A16_SNORM].textureDecoder = TextureDecoder_R16_G16_B16_A16::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM].textureDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R8_G8_B8_A8_SNORM].textureDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB].textureDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R8_G8_B8_A8_UINT].textureDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R8_G8_B8_A8_SINT].textureDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R32_G32_FLOAT].textureDecoder = TextureDecoder_R32_G32_FLOAT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R32_G32_UINT].textureDecoder = TextureDecoder_R32_G32_UINT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R16_G16_UNORM].textureDecoder = TextureDecoder_R16_G16::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R16_G16_FLOAT].textureDecoder = TextureDecoder_R16_G16_FLOAT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R8_G8_UNORM].textureDecoder = TextureDecoder_R8_G8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R8_G8_SNORM].textureDecoder = TextureDecoder_R8_G8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_UNORM].textureDecoder = TextureDecoder_R4_G4_UNORM_To_ABGR4::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R32_FLOAT].textureDecoder = TextureDecoder_R32_FLOAT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R32_UINT].textureDecoder = TextureDecoder_R32_UINT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R16_FLOAT].textureDecoder = TextureDecoder_R16_FLOAT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R16_UNORM].textureDecoder = TextureDecoder_R16_UNORM::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R16_SNORM].textureDecoder = TextureDecoder_R16_SNORM::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R16_UINT].textureDecoder = TextureDecoder_R16_UINT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R8_UNORM].textureDecoder = TextureDecoder_R8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R8_SNORM].textureDecoder = TextureDecoder_R8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R8_UINT].textureDecoder = TextureDecoder_R8_UINT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G6_B5_UNORM].textureDecoder = TextureDecoder_R5_G6_B5_swappedRB::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM].textureDecoder = TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM].textureDecoder = TextureDecoder_A1_B5_G5_R5_UNORM::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT].textureDecoder = TextureDecoder_R11_G11_B10_FLOAT::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM].textureDecoder = TextureDecoder_R4_G4_B4_A4_UNORM::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM].textureDecoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM].textureDecoder = TextureDecoder_R10_G10_B10_A2_SNORM_To_RGBA16::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB].textureDecoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::BC1_SRGB].textureDecoder = TextureDecoder_BC1::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::BC1_UNORM].textureDecoder = TextureDecoder_BC1::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::BC2_UNORM].textureDecoder = TextureDecoder_BC2::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::BC2_SRGB].textureDecoder = TextureDecoder_BC2::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::BC3_UNORM].textureDecoder = TextureDecoder_BC3::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::BC3_SRGB].textureDecoder = TextureDecoder_BC3::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::BC4_UNORM].textureDecoder = TextureDecoder_BC4::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::BC4_SNORM].textureDecoder = TextureDecoder_BC4::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::BC5_UNORM].textureDecoder = TextureDecoder_BC5::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::BC5_SNORM].textureDecoder = TextureDecoder_BC5::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R24_X8_UNORM].textureDecoder = TextureDecoder_R24_X8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::X24_G8_UINT].textureDecoder = TextureDecoder_X24_G8_UINT::getInstance(); if (!support.m_supportsPacked16BitFormats) { // B5G6R5Unorm MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G6_B5_UNORM].pixelFormat = MTL::PixelFormatRGBA8Unorm; MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G6_B5_UNORM].bytesPerBlock = 4; MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G6_B5_UNORM].textureDecoder = TextureDecoder_R5G6B5_UNORM_To_RGBA8::getInstance(); // A1BGR5Unorm MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM].pixelFormat = MTL::PixelFormatRGBA8Unorm; MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM].textureDecoder = TextureDecoder_A1_B5_G5_R5_UNORM_vulkan_To_RGBA8::getInstance(); // ABGR4Unorm MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_UNORM].pixelFormat = MTL::PixelFormatRG8Unorm; MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_UNORM].bytesPerBlock = 2; MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_UNORM].textureDecoder = TextureDecoder_R4G4_UNORM_To_RG8::getInstance(); MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM].pixelFormat = MTL::PixelFormatRGBA8Unorm; MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM].bytesPerBlock = 4; MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM].textureDecoder = TextureDecoder_R4G4B4A4_UNORM_To_RGBA8::getInstance(); // BGR5A1Unorm MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM].pixelFormat = MTL::PixelFormatRGBA8Unorm; MTL_COLOR_FORMAT_TABLE[Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM].textureDecoder = TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB_To_RGBA8::getInstance(); } // Depth MTL_DEPTH_FORMAT_TABLE[Latte::E_GX2SURFFMT::D24_S8_UNORM].textureDecoder = TextureDecoder_D24_S8::getInstance(); MTL_DEPTH_FORMAT_TABLE[Latte::E_GX2SURFFMT::D24_S8_FLOAT].textureDecoder = TextureDecoder_NullData64::getInstance(); // TODO: why? MTL_DEPTH_FORMAT_TABLE[Latte::E_GX2SURFFMT::D32_FLOAT].textureDecoder = TextureDecoder_R32_FLOAT::getInstance(); MTL_DEPTH_FORMAT_TABLE[Latte::E_GX2SURFFMT::D16_UNORM].textureDecoder = TextureDecoder_R16_UNORM::getInstance(); MTL_DEPTH_FORMAT_TABLE[Latte::E_GX2SURFFMT::D32_S8_FLOAT].textureDecoder = TextureDecoder_D32_S8_UINT_X24::getInstance(); if (!support.m_supportsDepth24Unorm_Stencil8) { // Depth24Unorm_Stencil8 MTL_DEPTH_FORMAT_TABLE[Latte::E_GX2SURFFMT::D24_S8_UNORM].pixelFormat = MTL::PixelFormatDepth32Float_Stencil8; // TODO: implement the decoder //MTL_DEPTH_FORMAT_TABLE[Latte::E_GX2SURFFMT::D24_S8_UNORM].textureDecoder = TextureDecoder_D24_S8_To_D32_S8::getInstance(); } } const MetalPixelFormatInfo GetMtlPixelFormatInfo(Latte::E_GX2SURFFMT format, bool isDepth) { if (isDepth) { auto it = MTL_DEPTH_FORMAT_TABLE.find(format); if (it == MTL_DEPTH_FORMAT_TABLE.end()) return {MTL::PixelFormatDepth16Unorm, MetalDataType::NONE, 2}; // Fallback else return it->second; } else { auto it = MTL_COLOR_FORMAT_TABLE.find(format); if (it == MTL_COLOR_FORMAT_TABLE.end()) return {MTL::PixelFormatR8Unorm, MetalDataType::FLOAT, 1}; // Fallback else return it->second; } } MTL::PixelFormat GetMtlPixelFormat(Latte::E_GX2SURFFMT format, bool isDepth) { auto pixelFormat = GetMtlPixelFormatInfo(format, isDepth).pixelFormat; if (pixelFormat == MTL::PixelFormatInvalid) cemuLog_log(LogType::Force, "invalid pixel format 0x{:x}, is depth: {}\n", format, isDepth); return pixelFormat; } inline uint32 CeilDivide(uint32 a, uint32 b) { return (a + b - 1) / b; } size_t GetMtlTextureBytesPerRow(Latte::E_GX2SURFFMT format, bool isDepth, uint32 width) { const auto& formatInfo = GetMtlPixelFormatInfo(format, isDepth); return CeilDivide(width, formatInfo.blockTexelSize.x) * formatInfo.bytesPerBlock; } size_t GetMtlTextureBytesPerImage(Latte::E_GX2SURFFMT format, bool isDepth, uint32 height, size_t bytesPerRow) { const auto& formatInfo = GetMtlPixelFormatInfo(format, isDepth); return CeilDivide(height, formatInfo.blockTexelSize.y) * bytesPerRow; } MTL::PrimitiveType GetMtlPrimitiveType(LattePrimitiveMode primitiveMode) { switch (primitiveMode) { case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS: return MTL::PrimitiveTypePoint; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINES: return MTL::PrimitiveTypeLine; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_STRIP: return MTL::PrimitiveTypeLineStrip; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_LOOP: return MTL::PrimitiveTypeLineStrip; // line loops are emulated as line strips with an extra connecting strip at the end case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_STRIP_ADJACENT: // Tropical Freeze level 3-6 cemuLog_logOnce(LogType::Force, "Metal doesn't support line strip adjacent primitive, using line strip instead"); return MTL::PrimitiveTypeLineStrip; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLES: return MTL::PrimitiveTypeTriangle; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLE_FAN: return MTL::PrimitiveTypeTriangleStrip; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLE_STRIP: return MTL::PrimitiveTypeTriangleStrip; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUADS: return MTL::PrimitiveTypeTriangle; // quads are emulated as 2 triangles case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUAD_STRIP: return MTL::PrimitiveTypeTriangle; // quad strips are emulated as (count-2)/2 triangles case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS: return MTL::PrimitiveTypeTriangle; // rects are emulated as 2 triangles default: cemuLog_log(LogType::Force, "Unsupported primitive mode {}", primitiveMode); cemu_assert_debug(false); return MTL::PrimitiveTypeTriangle; } } MTL::VertexFormat GetMtlVertexFormat(uint8 format) { switch (format) { case FMT_32_32_32_32_FLOAT: return MTL::VertexFormatUInt4; case FMT_32_32_32_FLOAT: return MTL::VertexFormatUInt3; case FMT_32_32_FLOAT: return MTL::VertexFormatUInt2; case FMT_32_FLOAT: return MTL::VertexFormatUInt; case FMT_8_8_8_8: return MTL::VertexFormatUChar4; case FMT_8_8_8: return MTL::VertexFormatUChar3; case FMT_8_8: return MTL::VertexFormatUChar2; case FMT_8: return MTL::VertexFormatUChar; case FMT_32_32_32_32: return MTL::VertexFormatUInt4; case FMT_32_32_32: return MTL::VertexFormatUInt3; case FMT_32_32: return MTL::VertexFormatUInt2; case FMT_32: return MTL::VertexFormatUInt; case FMT_16_16_16_16: return MTL::VertexFormatUShort4; // verified to match OpenGL case FMT_16_16_16: return MTL::VertexFormatUShort3; case FMT_16_16: return MTL::VertexFormatUShort2; case FMT_16: return MTL::VertexFormatUShort; case FMT_16_16_16_16_FLOAT: return MTL::VertexFormatUShort4; // verified to match OpenGL case FMT_16_16_16_FLOAT: return MTL::VertexFormatUShort3; case FMT_16_16_FLOAT: return MTL::VertexFormatUShort2; case FMT_16_FLOAT: return MTL::VertexFormatUShort; case FMT_2_10_10_10: return MTL::VertexFormatUInt; // verified to match OpenGL default: cemuLog_log(LogType::Force, "unsupported vertex format {}", (uint32)format); assert_dbg(); return MTL::VertexFormatInvalid; } } uint32 GetMtlVertexFormatSize(uint8 format) { switch (format) { case FMT_32_32_32_32_FLOAT: return 16; case FMT_32_32_32_FLOAT: return 12; case FMT_32_32_FLOAT: return 8; case FMT_32_FLOAT: return 4; case FMT_8_8_8_8: return 4; case FMT_8_8_8: return 3; case FMT_8_8: return 2; case FMT_8: return 1; case FMT_32_32_32_32: return 16; case FMT_32_32_32: return 12; case FMT_32_32: return 8; case FMT_32: return 4; case FMT_16_16_16_16: return 8; case FMT_16_16_16: return 6; case FMT_16_16: return 4; case FMT_16: return 2; case FMT_16_16_16_16_FLOAT: return 8; case FMT_16_16_16_FLOAT: return 6; case FMT_16_16_FLOAT: return 4; case FMT_16_FLOAT: return 2; case FMT_2_10_10_10: return 4; default: return 0; } } MTL::IndexType GetMtlIndexType(Renderer::INDEX_TYPE indexType) { switch (indexType) { case Renderer::INDEX_TYPE::U16: return MTL::IndexTypeUInt16; case Renderer::INDEX_TYPE::U32: return MTL::IndexTypeUInt32; default: cemu_assert_suspicious(); return MTL::IndexTypeUInt32; } } MTL::BlendOperation GetMtlBlendOp(Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC combineFunc) { switch (combineFunc) { case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::DST_PLUS_SRC: return MTL::BlendOperationAdd; case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::SRC_MINUS_DST: return MTL::BlendOperationSubtract; case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::MIN_DST_SRC: return MTL::BlendOperationMin; case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::MAX_DST_SRC: return MTL::BlendOperationMax; case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::DST_MINUS_SRC: return MTL::BlendOperationReverseSubtract; default: cemu_assert_suspicious(); return MTL::BlendOperationAdd; } } const MTL::BlendFactor MTL_BLEND_FACTORS[] = { /* 0x00 */ MTL::BlendFactorZero, /* 0x01 */ MTL::BlendFactorOne, /* 0x02 */ MTL::BlendFactorSourceColor, /* 0x03 */ MTL::BlendFactorOneMinusSourceColor, /* 0x04 */ MTL::BlendFactorSourceAlpha, /* 0x05 */ MTL::BlendFactorOneMinusSourceAlpha, /* 0x06 */ MTL::BlendFactorDestinationAlpha, /* 0x07 */ MTL::BlendFactorOneMinusDestinationAlpha, /* 0x08 */ MTL::BlendFactorDestinationColor, /* 0x09 */ MTL::BlendFactorOneMinusDestinationColor, /* 0x0A */ MTL::BlendFactorSourceAlphaSaturated, /* 0x0B */ MTL::BlendFactorZero, // TODO /* 0x0C */ MTL::BlendFactorZero, // TODO /* 0x0D */ MTL::BlendFactorBlendColor, /* 0x0E */ MTL::BlendFactorOneMinusBlendColor, /* 0x0F */ MTL::BlendFactorSource1Color, /* 0x10 */ MTL::BlendFactorOneMinusSource1Color, /* 0x11 */ MTL::BlendFactorSource1Alpha, /* 0x12 */ MTL::BlendFactorOneMinusSource1Alpha, /* 0x13 */ MTL::BlendFactorBlendAlpha, /* 0x14 */ MTL::BlendFactorOneMinusBlendAlpha }; MTL::BlendFactor GetMtlBlendFactor(Latte::LATTE_CB_BLENDN_CONTROL::E_BLENDFACTOR factor) { cemu_assert_debug((uint32)factor < std::size(MTL_BLEND_FACTORS)); return MTL_BLEND_FACTORS[(uint32)factor]; } const MTL::CompareFunction MTL_COMPARE_FUNCTIONS[8] = { MTL::CompareFunctionNever, MTL::CompareFunctionLess, MTL::CompareFunctionEqual, MTL::CompareFunctionLessEqual, MTL::CompareFunctionGreater, MTL::CompareFunctionNotEqual, MTL::CompareFunctionGreaterEqual, MTL::CompareFunctionAlways }; MTL::CompareFunction GetMtlCompareFunc(Latte::E_COMPAREFUNC func) { cemu_assert_debug((uint32)func < std::size(MTL_COMPARE_FUNCTIONS)); return MTL_COMPARE_FUNCTIONS[(uint32)func]; } // TODO: clamp to border color? (should be fine though) const MTL::SamplerAddressMode MTL_SAMPLER_ADDRESS_MODES[] = { MTL::SamplerAddressModeRepeat, // WRAP MTL::SamplerAddressModeMirrorRepeat, // MIRROR MTL::SamplerAddressModeClampToEdge, // CLAMP_LAST_TEXEL MTL::SamplerAddressModeMirrorClampToEdge, // MIRROR_ONCE_LAST_TEXEL MTL::SamplerAddressModeClampToEdge, // unsupported HALF_BORDER MTL::SamplerAddressModeClampToBorderColor, // unsupported MIRROR_ONCE_HALF_BORDER MTL::SamplerAddressModeClampToBorderColor, // CLAMP_BORDER MTL::SamplerAddressModeClampToBorderColor // MIRROR_ONCE_BORDER }; MTL::SamplerAddressMode GetMtlSamplerAddressMode(Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_CLAMP clamp) { cemu_assert_debug((uint32)clamp < std::size(MTL_SAMPLER_ADDRESS_MODES)); return MTL_SAMPLER_ADDRESS_MODES[(uint32)clamp]; } const MTL::TextureSwizzle MTL_TEXTURE_SWIZZLES[] = { MTL::TextureSwizzleRed, MTL::TextureSwizzleGreen, MTL::TextureSwizzleBlue, MTL::TextureSwizzleAlpha, MTL::TextureSwizzleZero, MTL::TextureSwizzleOne, MTL::TextureSwizzleZero, MTL::TextureSwizzleZero }; MTL::TextureSwizzle GetMtlTextureSwizzle(uint32 swizzle) { cemu_assert_debug(swizzle < std::size(MTL_TEXTURE_SWIZZLES)); return MTL_TEXTURE_SWIZZLES[swizzle]; } const MTL::StencilOperation MTL_STENCIL_OPERATIONS[8] = { MTL::StencilOperationKeep, MTL::StencilOperationZero, MTL::StencilOperationReplace, MTL::StencilOperationIncrementClamp, MTL::StencilOperationDecrementClamp, MTL::StencilOperationInvert, MTL::StencilOperationIncrementWrap, MTL::StencilOperationDecrementWrap }; MTL::StencilOperation GetMtlStencilOp(Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION action) { cemu_assert_debug((uint32)action < std::size(MTL_STENCIL_OPERATIONS)); return MTL_STENCIL_OPERATIONS[(uint32)action]; } MTL::ColorWriteMask GetMtlColorWriteMask(uint8 mask) { MTL::ColorWriteMask mtlMask = MTL::ColorWriteMaskNone; if (mask & 0x1) mtlMask |= MTL::ColorWriteMaskRed; if (mask & 0x2) mtlMask |= MTL::ColorWriteMaskGreen; if (mask & 0x4) mtlMask |= MTL::ColorWriteMaskBlue; if (mask & 0x8) mtlMask |= MTL::ColorWriteMaskAlpha; return mtlMask; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Metal/MetalCommon.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/Core/LatteConst.h" //#include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Common/precompiled.h" #include "HW/Latte/Core/LatteTextureLoader.h" struct Uvec2 { uint32 x; uint32 y; }; enum class MetalDataType { NONE, INT, UINT, FLOAT, }; struct MetalPixelFormatInfo { MTL::PixelFormat pixelFormat; MetalDataType dataType; size_t bytesPerBlock; Uvec2 blockTexelSize = {1, 1}; bool hasStencil = false; TextureDecoder* textureDecoder = nullptr; }; void CheckForPixelFormatSupport(const MetalPixelFormatSupport& support); const MetalPixelFormatInfo GetMtlPixelFormatInfo(Latte::E_GX2SURFFMT format, bool isDepth); MTL::PixelFormat GetMtlPixelFormat(Latte::E_GX2SURFFMT format, bool isDepth); inline MetalDataType GetColorBufferDataType(const uint32 index, const LatteContextRegister& lcr) { auto format = LatteMRT::GetColorBufferFormat(index, lcr); return GetMtlPixelFormatInfo(format, false).dataType; } inline const char* GetDataTypeStr(MetalDataType dataType) { switch (dataType) { case MetalDataType::INT: return "int4"; case MetalDataType::UINT: return "uint4"; case MetalDataType::FLOAT: return "float4"; default: cemu_assert_suspicious(); return "INVALID"; } } size_t GetMtlTextureBytesPerRow(Latte::E_GX2SURFFMT format, bool isDepth, uint32 width); size_t GetMtlTextureBytesPerImage(Latte::E_GX2SURFFMT format, bool isDepth, uint32 height, size_t bytesPerRow); MTL::PrimitiveType GetMtlPrimitiveType(LattePrimitiveMode primitiveMode); MTL::VertexFormat GetMtlVertexFormat(uint8 format); uint32 GetMtlVertexFormatSize(uint8 format); MTL::IndexType GetMtlIndexType(Renderer::INDEX_TYPE indexType); MTL::BlendOperation GetMtlBlendOp(Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC combineFunc); MTL::BlendFactor GetMtlBlendFactor(Latte::LATTE_CB_BLENDN_CONTROL::E_BLENDFACTOR factor); MTL::CompareFunction GetMtlCompareFunc(Latte::E_COMPAREFUNC func); MTL::SamplerAddressMode GetMtlSamplerAddressMode(Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_CLAMP clamp); MTL::TextureSwizzle GetMtlTextureSwizzle(uint32 swizzle); MTL::StencilOperation GetMtlStencilOp(Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION action); MTL::ColorWriteMask GetMtlColorWriteMask(uint8 mask); ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalAttachmentsInfo.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalAttachmentsInfo.h" #include "Cafe/HW/Latte/Renderer/Metal/CachedFBOMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureViewMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" MetalAttachmentsInfo::MetalAttachmentsInfo(class CachedFBOMtl* fbo) { for (uint8 i = 0; i < LATTE_NUM_COLOR_TARGET; i++) { const auto& colorBuffer = fbo->colorBuffer[i]; auto texture = static_cast<LatteTextureViewMtl*>(colorBuffer.texture); if (!texture) continue; colorFormats[i] = texture->format; } // Depth stencil attachment if (fbo->depthBuffer.texture) { auto texture = static_cast<LatteTextureViewMtl*>(fbo->depthBuffer.texture); depthFormat = texture->format; hasStencil = fbo->depthBuffer.hasStencil; } } MetalAttachmentsInfo::MetalAttachmentsInfo(const LatteContextRegister& lcr, const LatteDecompilerShader* pixelShader) { uint8 cbMask = LatteMRT::GetActiveColorBufferMask(pixelShader, lcr); bool dbMask = LatteMRT::GetActiveDepthBufferMask(lcr); // Color attachments for (int i = 0; i < 8; ++i) { if ((cbMask & (1 << i)) == 0) continue; colorFormats[i] = LatteMRT::GetColorBufferFormat(i, lcr); } // Depth stencil attachment if (dbMask) { Latte::E_GX2SURFFMT format = LatteMRT::GetDepthBufferFormat(lcr); depthFormat = format; hasStencil = GetMtlPixelFormatInfo(format, true).hasStencil; } } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalAttachmentsInfo.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Metal/MetalCommon.h" class MetalAttachmentsInfo { public: MetalAttachmentsInfo() = default; MetalAttachmentsInfo(class CachedFBOMtl* fbo); MetalAttachmentsInfo(const LatteContextRegister& lcr, const class LatteDecompilerShader* pixelShader); Latte::E_GX2SURFFMT colorFormats[LATTE_NUM_COLOR_TARGET] = {Latte::E_GX2SURFFMT::INVALID_FORMAT}; Latte::E_GX2SURFFMT depthFormat = Latte::E_GX2SURFFMT::INVALID_FORMAT; bool hasStencil = false; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalBufferAllocator.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalBufferAllocator.h" MetalBufferChunkedHeap::~MetalBufferChunkedHeap() { for (auto& chunk : m_chunkBuffers) chunk->release(); } uint32 MetalBufferChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) { size_t allocationSize = std::max<size_t>(m_minimumBufferAllocationSize, minimumAllocationSize); MTL::Buffer* buffer = m_mtlr->GetDevice()->newBuffer(allocationSize, m_options); cemu_assert_debug(buffer); cemu_assert_debug(m_chunkBuffers.size() == chunkIndex); m_chunkBuffers.emplace_back(buffer); return allocationSize; } void MetalSynchronizedRingAllocator::addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset) { auto commandBuffer = m_mtlr->GetCurrentCommandBuffer(); if (commandBuffer == buffer.lastSyncpointCommandBuffer) return; buffer.lastSyncpointCommandBuffer = commandBuffer; buffer.queue_syncPoints.emplace(commandBuffer, offset); } void MetalSynchronizedRingAllocator::allocateAdditionalUploadBuffer(uint32 sizeRequiredForAlloc) { // calculate buffer size, should be a multiple of bufferAllocSize that is at least as large as sizeRequiredForAlloc uint32 bufferAllocSize = m_minimumBufferAllocSize; while (bufferAllocSize < sizeRequiredForAlloc) bufferAllocSize += m_minimumBufferAllocSize; AllocatorBuffer_t newBuffer{}; newBuffer.writeIndex = 0; newBuffer.basePtr = nullptr; newBuffer.mtlBuffer = m_mtlr->GetDevice()->newBuffer(bufferAllocSize, m_options); newBuffer.basePtr = (uint8*)newBuffer.mtlBuffer->contents(); newBuffer.size = bufferAllocSize; newBuffer.index = (uint32)m_buffers.size(); m_buffers.push_back(newBuffer); } MetalSynchronizedRingAllocator::AllocatorReservation_t MetalSynchronizedRingAllocator::AllocateBufferMemory(uint32 size, uint32 alignment) { if (alignment < 128) alignment = 128; size = (size + 127) & ~127; for (auto& itr : m_buffers) { // align pointer uint32 alignmentPadding = (alignment - (itr.writeIndex % alignment)) % alignment; uint32 distanceToSyncPoint; if (!itr.queue_syncPoints.empty()) { if (itr.queue_syncPoints.front().offset < itr.writeIndex) distanceToSyncPoint = 0xFFFFFFFF; else distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex; } else distanceToSyncPoint = 0xFFFFFFFF; uint32 spaceNeeded = alignmentPadding + size; if (spaceNeeded > distanceToSyncPoint) continue; // not enough space in current buffer if ((itr.writeIndex + spaceNeeded) > itr.size) { // wrap-around spaceNeeded = size; alignmentPadding = 0; // check if there is enough space in current buffer after wrap-around if (!itr.queue_syncPoints.empty()) { distanceToSyncPoint = itr.queue_syncPoints.front().offset - 0; if (spaceNeeded > distanceToSyncPoint) continue; } else if (spaceNeeded > itr.size) continue; itr.writeIndex = 0; } addUploadBufferSyncPoint(itr, itr.writeIndex); itr.writeIndex += alignmentPadding; uint32 offset = itr.writeIndex; itr.writeIndex += size; itr.cleanupCounter = 0; MetalSynchronizedRingAllocator::AllocatorReservation_t res; res.mtlBuffer = itr.mtlBuffer; res.memPtr = itr.basePtr + offset; res.bufferOffset = offset; res.size = size; res.bufferIndex = itr.index; return res; } // allocate new buffer allocateAdditionalUploadBuffer(size); return AllocateBufferMemory(size, alignment); } void MetalSynchronizedRingAllocator::FlushReservation(AllocatorReservation_t& uploadReservation) { if (RequiresFlush()) { uploadReservation.mtlBuffer->didModifyRange(NS::Range(uploadReservation.bufferOffset, uploadReservation.size)); } } void MetalSynchronizedRingAllocator::CleanupBuffer(MTL::CommandBuffer* latestFinishedCommandBuffer) { for (auto& itr : m_buffers) { while (!itr.queue_syncPoints.empty() && latestFinishedCommandBuffer == itr.queue_syncPoints.front().commandBuffer) { itr.queue_syncPoints.pop(); } if (itr.queue_syncPoints.empty()) itr.cleanupCounter++; } // check if last buffer is available for deletion if (m_buffers.size() >= 2) { auto& lastBuffer = m_buffers.back(); if (lastBuffer.cleanupCounter >= 1000) { // release buffer lastBuffer.mtlBuffer->release(); m_buffers.pop_back(); } } } MTL::Buffer* MetalSynchronizedRingAllocator::GetBufferByIndex(uint32 index) const { return m_buffers[index].mtlBuffer; } void MetalSynchronizedRingAllocator::GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const { numBuffers = (uint32)m_buffers.size(); totalBufferSize = 0; freeBufferSize = 0; for (auto& itr : m_buffers) { totalBufferSize += itr.size; // calculate free space in buffer uint32 distanceToSyncPoint; if (!itr.queue_syncPoints.empty()) { if (itr.queue_syncPoints.front().offset < itr.writeIndex) distanceToSyncPoint = (itr.size - itr.writeIndex) + itr.queue_syncPoints.front().offset; // size with wrap-around else distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex; } else distanceToSyncPoint = itr.size; freeBufferSize += distanceToSyncPoint; } } /* MetalSynchronizedHeapAllocator */ MetalSynchronizedHeapAllocator::AllocatorReservation* MetalSynchronizedHeapAllocator::AllocateBufferMemory(uint32 size, uint32 alignment) { CHAddr addr = m_chunkedHeap.alloc(size, alignment); m_activeAllocations.emplace_back(addr); AllocatorReservation* res = m_poolAllocatorReservation.allocObj(); res->bufferIndex = addr.chunkIndex; res->bufferOffset = addr.offset; res->size = size; res->mtlBuffer = m_chunkedHeap.GetBufferByIndex(addr.chunkIndex); res->memPtr = m_chunkedHeap.GetChunkPtr(addr.chunkIndex) + addr.offset; return res; } void MetalSynchronizedHeapAllocator::FreeReservation(AllocatorReservation* uploadReservation) { // put the allocation on a delayed release queue for the current command buffer MTL::CommandBuffer* currentCommandBuffer = m_mtlr->GetCurrentCommandBuffer(); auto it = std::find_if(m_activeAllocations.begin(), m_activeAllocations.end(), [&uploadReservation](const TrackedAllocation& allocation) { return allocation.allocation.chunkIndex == uploadReservation->bufferIndex && allocation.allocation.offset == uploadReservation->bufferOffset; }); cemu_assert_debug(it != m_activeAllocations.end()); m_releaseQueue[currentCommandBuffer].emplace_back(it->allocation); m_activeAllocations.erase(it); m_poolAllocatorReservation.freeObj(uploadReservation); } void MetalSynchronizedHeapAllocator::FlushReservation(AllocatorReservation* uploadReservation) { if (m_chunkedHeap.RequiresFlush()) { uploadReservation->mtlBuffer->didModifyRange(NS::Range(uploadReservation->bufferOffset, uploadReservation->size)); } } void MetalSynchronizedHeapAllocator::CleanupBuffer(MTL::CommandBuffer* latestFinishedCommandBuffer) { auto it = m_releaseQueue.find(latestFinishedCommandBuffer); if (it == m_releaseQueue.end()) return; // release allocations for (auto& addr : it->second) m_chunkedHeap.free(addr); m_releaseQueue.erase(it); } void MetalSynchronizedHeapAllocator::GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const { m_chunkedHeap.GetStats(numBuffers, totalBufferSize, freeBufferSize); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalBufferAllocator.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Metal/MTLResource.hpp" #include "util/ChunkedHeap/ChunkedHeap.h" #include "util/helpers/MemoryPool.h" #include <utility> inline MTL::ResourceOptions GetResourceOptions(MTL::ResourceOptions options) { if (options & MTL::ResourceStorageModeShared || options & MTL::ResourceStorageModeManaged) options |= MTL::ResourceCPUCacheModeWriteCombined; return options; } class MetalBufferChunkedHeap : private ChunkedHeap<> { public: MetalBufferChunkedHeap(const class MetalRenderer* mtlRenderer, MTL::ResourceOptions options, size_t minimumBufferAllocationSize) : m_mtlr(mtlRenderer), m_options(GetResourceOptions(options)), m_minimumBufferAllocationSize(minimumBufferAllocationSize) { }; ~MetalBufferChunkedHeap(); using ChunkedHeap::alloc; using ChunkedHeap::free; uint8* GetChunkPtr(uint32 index) const { if (index >= m_chunkBuffers.size()) return nullptr; return (uint8*)m_chunkBuffers[index]->contents(); } MTL::Buffer* GetBufferByIndex(uint32 index) const { cemu_assert_debug(index < m_chunkBuffers.size()); return m_chunkBuffers[index]; } bool RequiresFlush() const { return m_options & MTL::ResourceStorageModeManaged; } void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const { numBuffers = m_chunkBuffers.size(); totalBufferSize = m_numHeapBytes; freeBufferSize = m_numHeapBytes - m_numAllocatedBytes; } private: uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override; const class MetalRenderer* m_mtlr; MTL::ResourceOptions m_options; size_t m_minimumBufferAllocationSize; std::vector<MTL::Buffer*> m_chunkBuffers; }; // a circular ring-buffer which tracks and releases memory per command-buffer class MetalSynchronizedRingAllocator { public: MetalSynchronizedRingAllocator(class MetalRenderer* mtlRenderer, MTL::ResourceOptions options, uint32 minimumBufferAllocSize) : m_mtlr(mtlRenderer), m_options(GetResourceOptions(options)), m_minimumBufferAllocSize(minimumBufferAllocSize) {}; MetalSynchronizedRingAllocator(const MetalSynchronizedRingAllocator&) = delete; // disallow copy struct BufferSyncPoint_t { // todo - modularize sync point MTL::CommandBuffer* commandBuffer; uint32 offset; BufferSyncPoint_t(MTL::CommandBuffer* _commandBuffer, uint32 _offset) : commandBuffer(_commandBuffer), offset(_offset) {}; }; struct AllocatorBuffer_t { MTL::Buffer* mtlBuffer; uint8* basePtr; uint32 size; uint32 writeIndex; std::queue<BufferSyncPoint_t> queue_syncPoints; MTL::CommandBuffer* lastSyncpointCommandBuffer{ nullptr }; uint32 index; uint32 cleanupCounter{ 0 }; // increased by one every time CleanupBuffer() is called if there is no sync point. If it reaches 300 then the buffer is released }; struct AllocatorReservation_t { MTL::Buffer* mtlBuffer; uint8* memPtr; uint32 bufferOffset; uint32 size; uint32 bufferIndex; }; AllocatorReservation_t AllocateBufferMemory(uint32 size, uint32 alignment); void FlushReservation(AllocatorReservation_t& uploadReservation); void CleanupBuffer(MTL::CommandBuffer* latestFinishedCommandBuffer); MTL::Buffer* GetBufferByIndex(uint32 index) const; bool RequiresFlush() const { return m_options & MTL::ResourceStorageModeManaged; } void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const; private: void allocateAdditionalUploadBuffer(uint32 sizeRequiredForAlloc); void addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset); const class MetalRenderer* m_mtlr; MTL::ResourceOptions m_options; const uint32 m_minimumBufferAllocSize; std::vector<AllocatorBuffer_t> m_buffers; }; // heap style allocator with released memory being freed after the current command buffer finishes class MetalSynchronizedHeapAllocator { struct TrackedAllocation { TrackedAllocation(CHAddr allocation) : allocation(allocation) {}; CHAddr allocation; }; public: MetalSynchronizedHeapAllocator(class MetalRenderer* mtlRenderer, MTL::ResourceOptions options, size_t minimumBufferAllocSize) : m_mtlr(mtlRenderer), m_chunkedHeap(m_mtlr, options, minimumBufferAllocSize) {} MetalSynchronizedHeapAllocator(const MetalSynchronizedHeapAllocator&) = delete; // disallow copy struct AllocatorReservation { MTL::Buffer* mtlBuffer; uint8* memPtr; uint32 bufferOffset; uint32 size; uint32 bufferIndex; }; AllocatorReservation* AllocateBufferMemory(uint32 size, uint32 alignment); void FreeReservation(AllocatorReservation* uploadReservation); void FlushReservation(AllocatorReservation* uploadReservation); void CleanupBuffer(MTL::CommandBuffer* latestFinishedCommandBuffer); void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const; private: const class MetalRenderer* m_mtlr; MetalBufferChunkedHeap m_chunkedHeap; // allocations std::vector<TrackedAllocation> m_activeAllocations; MemoryPool<AllocatorReservation> m_poolAllocatorReservation{32}; // release queue std::unordered_map<MTL::CommandBuffer*, std::vector<CHAddr>> m_releaseQueue; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalCommon.h ================================================ #pragma once #include <Foundation/Foundation.hpp> #include <Metal/Metal.hpp> #include "Cafe/HW/Latte/Core/LatteConst.h" struct MetalPixelFormatSupport { bool m_supportsR8Unorm_sRGB; bool m_supportsRG8Unorm_sRGB; bool m_supportsPacked16BitFormats; bool m_supportsDepth24Unorm_Stencil8; MetalPixelFormatSupport() = default; MetalPixelFormatSupport(MTL::Device* device) { m_supportsR8Unorm_sRGB = device->supportsFamily(MTL::GPUFamilyApple1); m_supportsRG8Unorm_sRGB = device->supportsFamily(MTL::GPUFamilyApple1); m_supportsPacked16BitFormats = device->supportsFamily(MTL::GPUFamilyApple1); m_supportsDepth24Unorm_Stencil8 = device->depth24Stencil8PixelFormatSupported(); } }; // TODO: don't define a new struct for this struct MetalQueryRange { uint32 begin; uint32 end; }; #define MAX_MTL_BUFFERS 31 // Buffer indices 28-30 are reserved for the helper shaders #define MTL_RESERVED_BUFFERS 3 #define MAX_MTL_VERTEX_BUFFERS (MAX_MTL_BUFFERS - MTL_RESERVED_BUFFERS) #define GET_MTL_VERTEX_BUFFER_INDEX(index) (MAX_MTL_VERTEX_BUFFERS - index - 1) #define MAX_MTL_TEXTURES 31 #define MAX_MTL_SAMPLERS 16 #define GET_HELPER_BUFFER_BINDING(index) (28 + index) #define GET_HELPER_TEXTURE_BINDING(index) (29 + index) #define GET_HELPER_SAMPLER_BINDING(index) (14 + index) constexpr uint32 INVALID_UINT32 = std::numeric_limits<uint32>::max(); constexpr size_t INVALID_OFFSET = std::numeric_limits<size_t>::max(); inline size_t Align(size_t size, size_t alignment) { return (size + alignment - 1) & ~(alignment - 1); } __attribute__((unused)) static inline void StackAutoRelease(void* object) { (*(NS::Object**)object)->release(); } #define NS_STACK_SCOPED __attribute__((cleanup(StackAutoRelease))) __attribute__((unused)) // Cast from const char* to NS::String* inline NS::String* ToNSString(const char* str) { return NS::String::string(str, NS::ASCIIStringEncoding); } // Cast from std::string to NS::String* inline NS::String* ToNSString(const std::string& str) { return ToNSString(str.c_str()); } // Cast from const char* to NS::URL* inline NS::URL* ToNSURL(const char* str) { return NS::URL::fileURLWithPath(ToNSString(str)); } // Cast from std::string to NS::URL* inline NS::URL* ToNSURL(const std::string& str) { return ToNSURL(str.c_str()); } inline NS::String* GetLabel(const std::string& label, const void* identifier) { return ToNSString(label + " (" + std::to_string(reinterpret_cast<uintptr_t>(identifier)) + ")"); } constexpr MTL::RenderStages ALL_MTL_RENDER_STAGES = MTL::RenderStageVertex | MTL::RenderStageObject | MTL::RenderStageMesh | MTL::RenderStageFragment; inline bool IsValidDepthTextureType(Latte::E_DIM dim) { return (dim == Latte::E_DIM::DIM_2D || dim == Latte::E_DIM::DIM_2D_MSAA || dim == Latte::E_DIM::DIM_2D_ARRAY || dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA || dim == Latte::E_DIM::DIM_CUBEMAP); } inline bool CommandBufferCompleted(MTL::CommandBuffer* commandBuffer) { auto status = commandBuffer->status(); return (status == MTL::CommandBufferStatusCompleted || status == MTL::CommandBufferStatusError); } inline bool FormatIsRenderable(Latte::E_GX2SURFFMT format) { return !Latte::IsCompressedFormat(format); } template <typename... T> inline bool executeCommand(fmt::format_string<T...> fmt, T&&... args) { std::string command = fmt::format(fmt, std::forward<T>(args)...); int res = system(command.c_str()); if (res != 0) { cemuLog_log(LogType::Force, "command \"{}\" failed with exit code {}", command, res); return false; } return true; } /* class MemoryMappedFile { public: MemoryMappedFile(const std::string& filePath) { // Open the file m_fd = open(filePath.c_str(), O_RDONLY); if (m_fd == -1) { cemuLog_log(LogType::Force, "failed to open file: {}", filePath); return; } // Get the file size // Use a loop to handle the case where the file size is 0 (more of a safety net) struct stat fileStat; while (true) { if (fstat(m_fd, &fileStat) == -1) { close(m_fd); cemuLog_log(LogType::Force, "failed to get file size: {}", filePath); return; } m_fileSize = fileStat.st_size; if (m_fileSize == 0) { cemuLog_logOnce(LogType::Force, "file size is 0: {}", filePath); std::this_thread::sleep_for(std::chrono::milliseconds(10)); continue; } break; } // Memory map the file m_data = mmap(nullptr, m_fileSize, PROT_READ, MAP_PRIVATE, m_fd, 0); if (m_data == MAP_FAILED) { close(m_fd); cemuLog_log(LogType::Force, "failed to memory map file: {}", filePath); return; } } ~MemoryMappedFile() { if (m_data && m_data != MAP_FAILED) munmap(m_data, m_fileSize); if (m_fd != -1) close(m_fd); } uint8* data() const { return static_cast<uint8*>(m_data); } size_t size() const { return m_fileSize; } private: int m_fd = -1; void* m_data = nullptr; size_t m_fileSize = 0; }; */ inline uint32 GetVerticesPerPrimitive(LattePrimitiveMode primitiveMode) { switch (primitiveMode) { case LattePrimitiveMode::POINTS: return 1; case LattePrimitiveMode::LINES: return 2; case LattePrimitiveMode::LINE_STRIP: // Same as line, but requires connection return 2; case LattePrimitiveMode::TRIANGLES: return 3; case LattePrimitiveMode::RECTS: return 3; default: cemuLog_log(LogType::Force, "Unimplemented primitive type {}", primitiveMode); return 0; } } inline bool PrimitiveRequiresConnection(LattePrimitiveMode primitiveMode) { if (primitiveMode == LattePrimitiveMode::LINE_STRIP) return true; else return false; } inline bool UseRectEmulation(const LatteContextRegister& lcr) { const LattePrimitiveMode primitiveMode = lcr.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); return (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS); } inline bool UseGeometryShader(const LatteContextRegister& lcr, bool hasGeometryShader) { return hasGeometryShader || UseRectEmulation(lcr); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalCppImpl.cpp ================================================ #define NS_PRIVATE_IMPLEMENTATION #define CA_PRIVATE_IMPLEMENTATION #define MTL_PRIVATE_IMPLEMENTATION #include <Foundation/Foundation.hpp> #include <QuartzCore/QuartzCore.hpp> #include <Metal/Metal.hpp> ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalDepthStencilCache.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalDepthStencilCache.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "HW/Latte/ISA/RegDefines.h" #include "HW/Latte/Renderer/Metal/LatteToMtl.h" #include "Metal/MTLDepthStencil.hpp" MetalDepthStencilCache::~MetalDepthStencilCache() { for (auto& pair : m_depthStencilCache) { pair.second->release(); } m_depthStencilCache.clear(); } MTL::DepthStencilState* MetalDepthStencilCache::GetDepthStencilState(const LatteContextRegister& lcr) { uint64 stateHash = CalculateDepthStencilHash(lcr); auto& depthStencilState = m_depthStencilCache[stateHash]; if (depthStencilState) return depthStencilState; // Depth stencil state bool depthEnable = lcr.DB_DEPTH_CONTROL.get_Z_ENABLE(); auto depthFunc = lcr.DB_DEPTH_CONTROL.get_Z_FUNC(); bool depthWriteEnable = lcr.DB_DEPTH_CONTROL.get_Z_WRITE_ENABLE(); NS_STACK_SCOPED MTL::DepthStencilDescriptor* desc = MTL::DepthStencilDescriptor::alloc()->init(); if (depthEnable) { desc->setDepthWriteEnabled(depthWriteEnable); desc->setDepthCompareFunction(GetMtlCompareFunc(depthFunc)); } // Stencil state bool stencilEnable = lcr.DB_DEPTH_CONTROL.get_STENCIL_ENABLE(); if (stencilEnable) { // get stencil control parameters bool backStencilEnable = lcr.DB_DEPTH_CONTROL.get_BACK_STENCIL_ENABLE(); auto frontStencilFunc = lcr.DB_DEPTH_CONTROL.get_STENCIL_FUNC_F(); auto frontStencilZPass = lcr.DB_DEPTH_CONTROL.get_STENCIL_ZPASS_F(); auto frontStencilZFail = lcr.DB_DEPTH_CONTROL.get_STENCIL_ZFAIL_F(); auto frontStencilFail = lcr.DB_DEPTH_CONTROL.get_STENCIL_FAIL_F(); auto backStencilFunc = lcr.DB_DEPTH_CONTROL.get_STENCIL_FUNC_B(); auto backStencilZPass = lcr.DB_DEPTH_CONTROL.get_STENCIL_ZPASS_B(); auto backStencilZFail = lcr.DB_DEPTH_CONTROL.get_STENCIL_ZFAIL_B(); auto backStencilFail = lcr.DB_DEPTH_CONTROL.get_STENCIL_FAIL_B(); // get stencil control parameters uint32 stencilCompareMaskFront = lcr.DB_STENCILREFMASK.get_STENCILMASK_F(); uint32 stencilWriteMaskFront = lcr.DB_STENCILREFMASK.get_STENCILWRITEMASK_F(); uint32 stencilCompareMaskBack = lcr.DB_STENCILREFMASK_BF.get_STENCILMASK_B(); uint32 stencilWriteMaskBack = lcr.DB_STENCILREFMASK_BF.get_STENCILWRITEMASK_B(); NS_STACK_SCOPED MTL::StencilDescriptor* frontStencil = MTL::StencilDescriptor::alloc()->init(); frontStencil->setReadMask(stencilCompareMaskFront); frontStencil->setWriteMask(stencilWriteMaskFront); frontStencil->setStencilCompareFunction(GetMtlCompareFunc(frontStencilFunc)); frontStencil->setDepthFailureOperation(GetMtlStencilOp(frontStencilZFail)); frontStencil->setStencilFailureOperation(GetMtlStencilOp(frontStencilFail)); frontStencil->setDepthStencilPassOperation(GetMtlStencilOp(frontStencilZPass)); desc->setFrontFaceStencil(frontStencil); NS_STACK_SCOPED MTL::StencilDescriptor* backStencil = MTL::StencilDescriptor::alloc()->init(); if (backStencilEnable) { backStencil->setReadMask(stencilCompareMaskBack); backStencil->setWriteMask(stencilWriteMaskBack); backStencil->setStencilCompareFunction(GetMtlCompareFunc(backStencilFunc)); backStencil->setDepthFailureOperation(GetMtlStencilOp(backStencilZFail)); backStencil->setStencilFailureOperation(GetMtlStencilOp(backStencilFail)); backStencil->setDepthStencilPassOperation(GetMtlStencilOp(backStencilZPass)); } else { backStencil->setReadMask(stencilCompareMaskFront); backStencil->setWriteMask(stencilWriteMaskFront); backStencil->setStencilCompareFunction(GetMtlCompareFunc(frontStencilFunc)); backStencil->setDepthFailureOperation(GetMtlStencilOp(frontStencilZFail)); backStencil->setStencilFailureOperation(GetMtlStencilOp(frontStencilFail)); backStencil->setDepthStencilPassOperation(GetMtlStencilOp(frontStencilZPass)); } desc->setBackFaceStencil(backStencil); } depthStencilState = m_mtlr->GetDevice()->newDepthStencilState(desc); return depthStencilState; } uint64 MetalDepthStencilCache::CalculateDepthStencilHash(const LatteContextRegister& lcr) { uint32* ctxRegister = lcr.GetRawView(); // Hash uint64 stateHash = 0; uint32 depthControl = ctxRegister[Latte::REGADDR::DB_DEPTH_CONTROL]; bool stencilTestEnable = depthControl & 1; if (stencilTestEnable) { stateHash += ctxRegister[mmDB_STENCILREFMASK]; stateHash = std::rotl<uint64>(stateHash, 17); if(depthControl & (1<<7)) // back stencil enable { stateHash += ctxRegister[mmDB_STENCILREFMASK_BF]; stateHash = std::rotl<uint64>(stateHash, 13); } } else { // zero out stencil related bits (8-31) depthControl &= 0xFF; } stateHash = std::rotl<uint64>(stateHash, 17); stateHash += depthControl; return stateHash; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalDepthStencilCache.h ================================================ #pragma once #include <Metal/Metal.hpp> #include "HW/Latte/ISA/LatteReg.h" class MetalDepthStencilCache { public: MetalDepthStencilCache(class MetalRenderer* metalRenderer) : m_mtlr{metalRenderer} {} ~MetalDepthStencilCache(); MTL::DepthStencilState* GetDepthStencilState(const LatteContextRegister& lcr); private: class MetalRenderer* m_mtlr; std::map<uint64, MTL::DepthStencilState*> m_depthStencilCache; uint64 CalculateDepthStencilHash(const LatteContextRegister& lcr); }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalLayer.h ================================================ #pragma once void* CreateMetalLayer(void* handle, float& scaleX, float& scaleY); ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalLayer.mm ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalLayer.h" #include "Cafe/HW/Latte/Renderer/MetalView.h" void* CreateMetalLayer(void* handle, float& scaleX, float& scaleY) { NSView* view = (NSView*)handle; MetalView* childView = [[MetalView alloc] initWithFrame:view.bounds]; childView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; childView.wantsLayer = YES; [view addSubview:childView]; const NSRect points = [childView frame]; const NSRect pixels = [childView convertRectToBacking:points]; scaleX = (float)(pixels.size.width / points.size.width); scaleY = (float)(pixels.size.height / points.size.height); return childView.layer; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalLayerHandle.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalLayerHandle.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalLayer.h" #include "gui/interface/WindowSystem.h" MetalLayerHandle::MetalLayerHandle(MTL::Device* device, const Vector2i& size, bool mainWindow) { const auto& windowInfo = (mainWindow ? WindowSystem::GetWindowInfo().window_main : WindowSystem::GetWindowInfo().window_pad); m_layer = (CA::MetalLayer*)CreateMetalLayer(windowInfo.surface, m_layerScaleX, m_layerScaleY); m_layer->setDevice(device); m_layer->setDrawableSize(CGSize{(float)size.x * m_layerScaleX, (float)size.y * m_layerScaleY}); m_layer->setFramebufferOnly(true); } MetalLayerHandle::~MetalLayerHandle() { if (m_layer) m_layer->release(); } void MetalLayerHandle::Resize(const Vector2i& size) { m_layer->setDrawableSize(CGSize{(float)size.x * m_layerScaleX, (float)size.y * m_layerScaleY}); } bool MetalLayerHandle::AcquireDrawable() { if (m_drawable) return true; m_drawable = m_layer->nextDrawable(); if (!m_drawable) { cemuLog_log(LogType::Force, "layer {} failed to acquire next drawable", (void*)this); return false; } return true; } void MetalLayerHandle::PresentDrawable(MTL::CommandBuffer* commandBuffer) { commandBuffer->presentDrawable(m_drawable); m_drawable = nullptr; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalLayerHandle.h ================================================ #pragma once #include <QuartzCore/QuartzCore.hpp> #include "Cafe/HW/Latte/Renderer/Metal/MetalCommon.h" #include "util/math/vector2.h" class MetalLayerHandle { public: MetalLayerHandle() = default; MetalLayerHandle(MTL::Device* device, const Vector2i& size, bool mainWindow); ~MetalLayerHandle(); void Resize(const Vector2i& size); bool AcquireDrawable(); void PresentDrawable(MTL::CommandBuffer* commandBuffer); CA::MetalLayer* GetLayer() const { return m_layer; } CA::MetalDrawable* GetDrawable() const { return m_drawable; } private: CA::MetalLayer* m_layer = nullptr; float m_layerScaleX, m_layerScaleY; CA::MetalDrawable* m_drawable = nullptr; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalMemoryManager.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalCommon.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalMemoryManager.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalVoidVertexPipeline.h" #include "CafeSystem.h" #include "Cemu/Logging/CemuLogging.h" #include "Common/precompiled.h" #include "HW/MMU/MMU.h" #include "config/CemuConfig.h" MetalMemoryManager::~MetalMemoryManager() { if (m_bufferCache) { m_bufferCache->release(); } } void* MetalMemoryManager::AcquireTextureUploadBuffer(size_t size) { if (m_textureUploadBuffer.size() < size) { m_textureUploadBuffer.resize(size); } return m_textureUploadBuffer.data(); } void MetalMemoryManager::ReleaseTextureUploadBuffer(uint8* mem) { cemu_assert_debug(m_textureUploadBuffer.data() == mem); m_textureUploadBuffer.clear(); } void MetalMemoryManager::InitBufferCache(size_t size) { cemu_assert_debug(!m_bufferCache); m_metalBufferCacheMode = g_current_game_profile->GetBufferCacheMode(); if (m_metalBufferCacheMode == MetalBufferCacheMode::Auto) { // TODO: do this for all unified memory systems? if (m_mtlr->IsAppleGPU()) { switch (CafeSystem::GetForegroundTitleId()) { // The Legend of Zelda: Wind Waker HD case 0x0005000010143600: // EUR case 0x0005000010143500: // USA case 0x0005000010143400: // JPN // TODO: use host instead? m_metalBufferCacheMode = MetalBufferCacheMode::DeviceShared; break; default: m_metalBufferCacheMode = MetalBufferCacheMode::DevicePrivate; break; } } else { m_metalBufferCacheMode = MetalBufferCacheMode::DevicePrivate; } } // First, try to import the host memory as a buffer if (m_metalBufferCacheMode == MetalBufferCacheMode::Host) { if (m_mtlr->HasUnifiedMemory()) { m_importedMemBaseAddress = mmuRange_MEM2.getBase(); m_hostAllocationSize = mmuRange_MEM2.getSize(); m_bufferCache = m_mtlr->GetDevice()->newBuffer(memory_getPointerFromVirtualOffset(m_importedMemBaseAddress), m_hostAllocationSize, MTL::ResourceStorageModeShared, nullptr); if (!m_bufferCache) { cemuLog_log(LogType::Force, "Failed to import host memory as a buffer, using device shared mode instead"); m_metalBufferCacheMode = MetalBufferCacheMode::DeviceShared; } } else { cemuLog_log(LogType::Force, "Host buffer cache mode is only available on unified memory systems, using device shared mode instead"); m_metalBufferCacheMode = MetalBufferCacheMode::DeviceShared; } } if (!m_bufferCache) m_bufferCache = m_mtlr->GetDevice()->newBuffer(size, (m_metalBufferCacheMode == MetalBufferCacheMode::DevicePrivate ? MTL::ResourceStorageModePrivate : MTL::ResourceStorageModeShared)); #ifdef CEMU_DEBUG_ASSERT m_bufferCache->setLabel(GetLabel("Buffer cache", m_bufferCache)); #endif } void MetalMemoryManager::UploadToBufferCache(const void* data, size_t offset, size_t size) { cemu_assert_debug(m_metalBufferCacheMode != MetalBufferCacheMode::Host); cemu_assert_debug(m_bufferCache); cemu_assert_debug((offset + size) <= m_bufferCache->length()); if (m_metalBufferCacheMode == MetalBufferCacheMode::DevicePrivate) { auto blitCommandEncoder = m_mtlr->GetBlitCommandEncoder(); auto allocation = m_stagingAllocator.AllocateBufferMemory(size, 1); memcpy(allocation.memPtr, data, size); m_stagingAllocator.FlushReservation(allocation); blitCommandEncoder->copyFromBuffer(allocation.mtlBuffer, allocation.bufferOffset, m_bufferCache, offset, size); //m_mtlr->CopyBufferToBuffer(allocation.mtlBuffer, allocation.bufferOffset, m_bufferCache, offset, size, ALL_MTL_RENDER_STAGES, ALL_MTL_RENDER_STAGES); } else { memcpy((uint8*)m_bufferCache->contents() + offset, data, size); } } void MetalMemoryManager::CopyBufferCache(size_t srcOffset, size_t dstOffset, size_t size) { cemu_assert_debug(m_metalBufferCacheMode != MetalBufferCacheMode::Host); cemu_assert_debug(m_bufferCache); if (m_metalBufferCacheMode == MetalBufferCacheMode::DevicePrivate) m_mtlr->CopyBufferToBuffer(m_bufferCache, srcOffset, m_bufferCache, dstOffset, size, ALL_MTL_RENDER_STAGES, ALL_MTL_RENDER_STAGES); else memcpy((uint8*)m_bufferCache->contents() + dstOffset, (uint8*)m_bufferCache->contents() + srcOffset, size); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalMemoryManager.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Metal/MetalBufferAllocator.h" #include "GameProfile/GameProfile.h" class MetalMemoryManager { public: MetalMemoryManager(class MetalRenderer* metalRenderer) : m_mtlr{metalRenderer}, m_stagingAllocator(m_mtlr, m_mtlr->GetOptimalBufferStorageMode(), 32u * 1024 * 1024), m_indexAllocator(m_mtlr, m_mtlr->GetOptimalBufferStorageMode(), 4u * 1024 * 1024) {} ~MetalMemoryManager(); MetalSynchronizedRingAllocator& GetStagingAllocator() { return m_stagingAllocator; } MetalSynchronizedHeapAllocator& GetIndexAllocator() { return m_indexAllocator; } MTL::Buffer* GetBufferCache() { return m_bufferCache; } void CleanupBuffers(MTL::CommandBuffer* latestFinishedCommandBuffer) { m_stagingAllocator.CleanupBuffer(latestFinishedCommandBuffer); m_indexAllocator.CleanupBuffer(latestFinishedCommandBuffer); } // Texture upload buffer void* AcquireTextureUploadBuffer(size_t size); void ReleaseTextureUploadBuffer(uint8* mem); // Buffer cache void InitBufferCache(size_t size); void UploadToBufferCache(const void* data, size_t offset, size_t size); void CopyBufferCache(size_t srcOffset, size_t dstOffset, size_t size); // Getters bool UseHostMemoryForCache() const { return (m_metalBufferCacheMode == MetalBufferCacheMode::Host); } bool NeedsReducedLatency() const { return (m_metalBufferCacheMode == MetalBufferCacheMode::DeviceShared || m_metalBufferCacheMode == MetalBufferCacheMode::Host); } MPTR GetImportedMemBaseAddress() const { return m_importedMemBaseAddress; } size_t GetHostAllocationSize() const { return m_hostAllocationSize; } private: class MetalRenderer* m_mtlr; std::vector<uint8> m_textureUploadBuffer; MetalSynchronizedRingAllocator m_stagingAllocator; MetalSynchronizedHeapAllocator m_indexAllocator; MTL::Buffer* m_bufferCache = nullptr; MetalBufferCacheMode m_metalBufferCacheMode; MPTR m_importedMemBaseAddress; size_t m_hostAllocationSize = 0; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalOutputShaderCache.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalOutputShaderCache.h" #include "Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h" MetalOutputShaderCache::~MetalOutputShaderCache() { for (uint8 i = 0; i < METAL_OUTPUT_SHADER_CACHE_SIZE; i++) { if (m_cache[i]) m_cache[i]->release(); } } MTL::RenderPipelineState* MetalOutputShaderCache::GetPipeline(RendererOutputShader* shader, uint8 shaderIndex, bool usesSRGB) { uint8 cacheIndex = (usesSRGB ? METAL_SHADER_TYPE_COUNT : 0) + shaderIndex; auto& renderPipelineState = m_cache[cacheIndex]; if (renderPipelineState) return renderPipelineState; // Create a new render pipeline state auto vertexShaderMtl = static_cast<RendererShaderMtl*>(shader->GetVertexShader())->GetFunction(); auto fragmentShaderMtl = static_cast<RendererShaderMtl*>(shader->GetFragmentShader())->GetFunction(); NS_STACK_SCOPED auto renderPipelineDescriptor = MTL::RenderPipelineDescriptor::alloc()->init(); renderPipelineDescriptor->setVertexFunction(vertexShaderMtl); renderPipelineDescriptor->setFragmentFunction(fragmentShaderMtl); renderPipelineDescriptor->colorAttachments()->object(0)->setPixelFormat(usesSRGB ? MTL::PixelFormatBGRA8Unorm_sRGB : MTL::PixelFormatBGRA8Unorm); NS::Error* error = nullptr; renderPipelineState = m_mtlr->GetDevice()->newRenderPipelineState(renderPipelineDescriptor, &error); if (error) { cemuLog_log(LogType::Force, "error creating output render pipeline state: {}", error->localizedDescription()->utf8String()); } return renderPipelineState; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalOutputShaderCache.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" constexpr uint8 METAL_SHADER_TYPE_COUNT = 6; constexpr uint8 METAL_OUTPUT_SHADER_CACHE_SIZE = 2 * METAL_SHADER_TYPE_COUNT; class MetalOutputShaderCache { public: MetalOutputShaderCache(class MetalRenderer* metalRenderer) : m_mtlr{metalRenderer} {} ~MetalOutputShaderCache(); MTL::RenderPipelineState* GetPipeline(RendererOutputShader* shader, uint8 shaderIndex, bool usesSRGB); private: class MetalRenderer* m_mtlr; MTL::RenderPipelineState* m_cache[METAL_OUTPUT_SHADER_CACHE_SIZE] = {nullptr}; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalPerformanceMonitor.h ================================================ #pragma once class MetalPerformanceMonitor { public: // Per frame data uint32 m_commandBuffers = 0; uint32 m_renderPasses = 0; uint32 m_clears = 0; uint32 m_manualVertexFetchDraws = 0; uint32 m_meshDraws = 0; uint32 m_triangleFans = 0; MetalPerformanceMonitor() = default; ~MetalPerformanceMonitor() = default; void ResetPerFrameData() { m_commandBuffers = 0; m_renderPasses = 0; m_clears = 0; m_manualVertexFetchDraws = 0; m_meshDraws = 0; m_triangleFans = 0; } }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalPipelineCache.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalPipelineCache.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalPipelineCompiler.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Common/RegisterSerializer.h" #include "Cafe/HW/Latte/Core/LatteShaderCache.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cemu/FileCache/FileCache.h" #include "Common/precompiled.h" #include "util/helpers/helpers.h" #include "config/ActiveSettings.h" #include <openssl/sha.h> static bool g_compilePipelineThreadInit{false}; static std::mutex g_compilePipelineMutex; static std::condition_variable g_compilePipelineCondVar; static std::queue<MetalPipelineCompiler*> g_compilePipelineRequests; static void compileThreadFunc(sint32 threadIndex) { SetThreadName("compilePl"); // one thread runs at normal priority while the others run at lower priority if (threadIndex != 0) ; // TODO: set thread priority while (true) { std::unique_lock lock(g_compilePipelineMutex); while (g_compilePipelineRequests.empty()) g_compilePipelineCondVar.wait(lock); MetalPipelineCompiler* request = g_compilePipelineRequests.front(); g_compilePipelineRequests.pop(); lock.unlock(); request->Compile(true, false, true); delete request; } } static void initCompileThread() { uint32 numCompileThreads; uint32 cpuCoreCount = GetPhysicalCoreCount(); if (cpuCoreCount <= 2) numCompileThreads = 1; else numCompileThreads = 2 + (cpuCoreCount - 3); // 2 plus one additionally for every extra core above 3 numCompileThreads = std::min(numCompileThreads, 8u); // cap at 8 for (uint32 i = 0; i < numCompileThreads; i++) { std::thread compileThread(compileThreadFunc, i); compileThread.detach(); } } static void queuePipeline(MetalPipelineCompiler* v) { std::unique_lock lock(g_compilePipelineMutex); g_compilePipelineRequests.push(std::move(v)); lock.unlock(); g_compilePipelineCondVar.notify_one(); } // make a guess if a pipeline is not essential // non-essential means that skipping these drawcalls shouldn't lead to permanently corrupted graphics bool IsAsyncPipelineAllowed(const MetalAttachmentsInfo& attachmentsInfo, Vector2i extend, uint32 indexCount) { if (extend.x == 1600 && extend.y == 1600) return false; // Splatoon ink mechanics use 1600x1600 R8 and R8G8 framebuffers, this resolution is rare enough that we can just blacklist it globally if (attachmentsInfo.depthFormat != Latte::E_GX2SURFFMT::INVALID_FORMAT) return true; // aggressive filter but seems to work well so far // small index count (3,4,5,6) is often associated with full-viewport quads (which are considered essential due to often being used to generate persistent textures) if (indexCount <= 6) return false; return true; } MetalPipelineCache* g_mtlPipelineCache = nullptr; MetalPipelineCache& MetalPipelineCache::GetInstance() { return *g_mtlPipelineCache; } MetalPipelineCache::MetalPipelineCache(class MetalRenderer* metalRenderer) : m_mtlr{metalRenderer} { g_mtlPipelineCache = this; } MetalPipelineCache::~MetalPipelineCache() { for (auto& [key, pipelineObj] : m_pipelineCache) { pipelineObj->m_pipeline->release(); delete pipelineObj; } } PipelineObject* MetalPipelineCache::GetRenderPipelineState(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const LatteDecompilerShader* geometryShader, const LatteDecompilerShader* pixelShader, const MetalAttachmentsInfo& lastUsedAttachmentsInfo, const MetalAttachmentsInfo& activeAttachmentsInfo, Vector2i extend, uint32 indexCount, const LatteContextRegister& lcr) { uint64 hash = CalculatePipelineHash(fetchShader, vertexShader, geometryShader, pixelShader, lastUsedAttachmentsInfo, activeAttachmentsInfo, lcr); PipelineObject*& pipelineObj = m_pipelineCache[hash]; if (pipelineObj) return pipelineObj; pipelineObj = new PipelineObject(); MetalPipelineCompiler* compiler = new MetalPipelineCompiler(m_mtlr, *pipelineObj); compiler->InitFromState(fetchShader, vertexShader, geometryShader, pixelShader, lastUsedAttachmentsInfo, activeAttachmentsInfo, lcr); bool allowAsyncCompile = false; if (GetConfig().async_compile) allowAsyncCompile = IsAsyncPipelineAllowed(activeAttachmentsInfo, extend, indexCount); if (allowAsyncCompile) { if (!g_compilePipelineThreadInit) { initCompileThread(); g_compilePipelineThreadInit = true; } queuePipeline(compiler); } else { // Also force compile to ensure that the pipeline is ready cemu_assert_debug(compiler->Compile(true, true, true)); delete compiler; } // Save to cache AddCurrentStateToCache(hash, lastUsedAttachmentsInfo); return pipelineObj; } uint64 MetalPipelineCache::CalculatePipelineHash(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const LatteDecompilerShader* geometryShader, const LatteDecompilerShader* pixelShader, const MetalAttachmentsInfo& lastUsedAttachmentsInfo, const MetalAttachmentsInfo& activeAttachmentsInfo, const LatteContextRegister& lcr) { // Hash uint64 stateHash = 0; for (int i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; ++i) { Latte::E_GX2SURFFMT format = lastUsedAttachmentsInfo.colorFormats[i]; if (format == Latte::E_GX2SURFFMT::INVALID_FORMAT) continue; stateHash += GetMtlPixelFormat(format, false) + i * 31; stateHash = std::rotl<uint64>(stateHash, 7); if (activeAttachmentsInfo.colorFormats[i] == Latte::E_GX2SURFFMT::INVALID_FORMAT) { stateHash += 1; stateHash = std::rotl<uint64>(stateHash, 1); } } if (lastUsedAttachmentsInfo.depthFormat != Latte::E_GX2SURFFMT::INVALID_FORMAT) { stateHash += GetMtlPixelFormat(lastUsedAttachmentsInfo.depthFormat, true); stateHash = std::rotl<uint64>(stateHash, 7); if (activeAttachmentsInfo.depthFormat == Latte::E_GX2SURFFMT::INVALID_FORMAT) { stateHash += 1; stateHash = std::rotl<uint64>(stateHash, 1); } } for (auto& group : fetchShader->bufferGroups) { uint32 bufferStride = group.getCurrentBufferStride(lcr.GetRawView()); stateHash = std::rotl<uint64>(stateHash, 7); stateHash += bufferStride * 3; } stateHash += fetchShader->getVkPipelineHashFragment(); stateHash = std::rotl<uint64>(stateHash, 7); stateHash += lcr.GetRawView()[mmVGT_STRMOUT_EN]; stateHash = std::rotl<uint64>(stateHash, 7); if(lcr.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL()) stateHash += 0x333333; stateHash = (stateHash >> 8) + (stateHash * 0x370531ull) % 0x7F980D3BF9B4639Dull; uint32* ctxRegister = lcr.GetRawView(); if (vertexShader) stateHash += vertexShader->baseHash; stateHash = std::rotl<uint64>(stateHash, 13); if (pixelShader) stateHash += pixelShader->baseHash + pixelShader->auxHash; stateHash = std::rotl<uint64>(stateHash, 13); uint32 polygonCtrl = lcr.PA_SU_SC_MODE_CNTL.getRawValue(); stateHash += polygonCtrl; stateHash = std::rotl<uint64>(stateHash, 7); stateHash += ctxRegister[Latte::REGADDR::PA_CL_CLIP_CNTL]; stateHash = std::rotl<uint64>(stateHash, 7); const auto colorControlReg = ctxRegister[Latte::REGADDR::CB_COLOR_CONTROL]; stateHash += colorControlReg; stateHash += ctxRegister[Latte::REGADDR::CB_TARGET_MASK]; const uint32 blendEnableMask = (colorControlReg >> 8) & 0xFF; if (blendEnableMask) { for (auto i = 0; i < 8; ++i) { if (((blendEnableMask & (1 << i))) == 0) continue; stateHash = std::rotl<uint64>(stateHash, 7); stateHash += ctxRegister[Latte::REGADDR::CB_BLEND0_CONTROL + i]; } } // Mesh pipeline const LattePrimitiveMode primitiveMode = static_cast<LattePrimitiveMode>(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]); bool isPrimitiveRect = (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS); bool usesGeometryShader = (geometryShader != nullptr || isPrimitiveRect); if (usesGeometryShader) { stateHash += lcr.GetRawView()[mmVGT_PRIMITIVE_TYPE]; stateHash = std::rotl<uint64>(stateHash, 7); } return stateHash; } struct { uint32 pipelineLoadIndex; uint32 pipelineMaxFileIndex; std::atomic_uint32_t pipelinesQueued; std::atomic_uint32_t pipelinesLoaded; } g_mtlCacheState; uint32 MetalPipelineCache::BeginLoading(uint64 cacheTitleId) { std::error_code ec; fs::create_directories(ActiveSettings::GetCachePath("shaderCache/transferable"), ec); const auto pathCacheFile = ActiveSettings::GetCachePath("shaderCache/transferable/{:016x}_mtlpipeline.bin", cacheTitleId); // init cache loader state g_mtlCacheState.pipelineLoadIndex = 0; g_mtlCacheState.pipelineMaxFileIndex = 0; g_mtlCacheState.pipelinesLoaded = 0; g_mtlCacheState.pipelinesQueued = 0; // start async compilation threads m_compilationCount.store(0); m_compilationQueue.clear(); // get core count uint32 cpuCoreCount = GetPhysicalCoreCount(); m_numCompilationThreads = std::clamp(cpuCoreCount, 1u, 8u); // TODO: uncomment? //if (VulkanRenderer::GetInstance()->GetDisableMultithreadedCompilation()) // m_numCompilationThreads = 1; for (uint32 i = 0; i < m_numCompilationThreads; i++) { std::thread compileThread(&MetalPipelineCache::CompilerThread, this); compileThread.detach(); } // open cache file or create it cemu_assert_debug(s_cache == nullptr); s_cache = FileCache::Open(pathCacheFile, true, LatteShaderCache_getPipelineCacheExtraVersion(cacheTitleId)); if (!s_cache) { cemuLog_log(LogType::Force, "Failed to open or create Metal pipeline cache file: {}", _pathToUtf8(pathCacheFile)); return 0; } else { s_cache->UseCompression(false); g_mtlCacheState.pipelineMaxFileIndex = s_cache->GetMaximumFileIndex(); } return s_cache->GetFileCount(); } bool MetalPipelineCache::UpdateLoading(uint32& pipelinesLoadedTotal, uint32& pipelinesMissingShaders) { pipelinesLoadedTotal = g_mtlCacheState.pipelinesLoaded; pipelinesMissingShaders = 0; while (g_mtlCacheState.pipelineLoadIndex <= g_mtlCacheState.pipelineMaxFileIndex) { if (m_compilationQueue.size() >= 50) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); return true; // queue up to 50 entries at a time } uint64 fileNameA, fileNameB; std::vector<uint8> fileData; if (s_cache->GetFileByIndex(g_mtlCacheState.pipelineLoadIndex, &fileNameA, &fileNameB, fileData)) { // queue for async compilation g_mtlCacheState.pipelinesQueued++; m_compilationQueue.push(std::move(fileData)); g_mtlCacheState.pipelineLoadIndex++; return true; } g_mtlCacheState.pipelineLoadIndex++; } if (g_mtlCacheState.pipelinesLoaded != g_mtlCacheState.pipelinesQueued) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); return true; // pipelines still compiling } return false; // done } void MetalPipelineCache::EndLoading() { // shut down compilation threads uint32 threadCount = m_numCompilationThreads; m_numCompilationThreads = 0; // signal thread shutdown for (uint32 i = 0; i < threadCount; i++) { m_compilationQueue.push({}); // push empty workload for every thread. Threads then will shutdown after checking for m_numCompilationThreads == 0 } // keep cache file open for writing of new pipelines } void MetalPipelineCache::Close() { if(s_cache) { delete s_cache; s_cache = nullptr; } } struct CachedPipeline { struct ShaderHash { uint64 baseHash; uint64 auxHash; bool isPresent{}; void set(uint64 baseHash, uint64 auxHash) { this->baseHash = baseHash; this->auxHash = auxHash; this->isPresent = true; } }; ShaderHash vsHash; // includes fetch shader ShaderHash gsHash; ShaderHash psHash; MetalAttachmentsInfo lastUsedAttachmentsInfo; Latte::GPUCompactedRegisterState gpuState; }; void MetalPipelineCache::LoadPipelineFromCache(std::span<uint8> fileData) { static FSpinlock s_spinlockSharedInternal; // deserialize file LatteContextRegister* lcr = new LatteContextRegister(); s_spinlockSharedInternal.lock(); CachedPipeline* cachedPipeline = new CachedPipeline(); s_spinlockSharedInternal.unlock(); MemStreamReader streamReader(fileData.data(), fileData.size()); if (!DeserializePipeline(streamReader, *cachedPipeline)) { // failed to deserialize s_spinlockSharedInternal.lock(); delete lcr; delete cachedPipeline; s_spinlockSharedInternal.unlock(); return; } // restored register view from compacted state Latte::LoadGPURegisterState(*lcr, cachedPipeline->gpuState); LatteDecompilerShader* vertexShader = nullptr; LatteDecompilerShader* geometryShader = nullptr; LatteDecompilerShader* pixelShader = nullptr; // find vertex shader if (cachedPipeline->vsHash.isPresent) { vertexShader = LatteSHRC_FindVertexShader(cachedPipeline->vsHash.baseHash, cachedPipeline->vsHash.auxHash); if (!vertexShader) { cemuLog_log(LogType::Force, "Vertex shader not found in cache"); return; } } // find geometry shader if (cachedPipeline->gsHash.isPresent) { geometryShader = LatteSHRC_FindGeometryShader(cachedPipeline->gsHash.baseHash, cachedPipeline->gsHash.auxHash); if (!geometryShader) { cemuLog_log(LogType::Force, "Geometry shader not found in cache"); return; } } // find pixel shader if (cachedPipeline->psHash.isPresent) { pixelShader = LatteSHRC_FindPixelShader(cachedPipeline->psHash.baseHash, cachedPipeline->psHash.auxHash); if (!pixelShader) { cemuLog_log(LogType::Force, "Pixel shader not found in cache"); return; } } if (!pixelShader) { cemu_assert_debug(false); return; } MetalAttachmentsInfo attachmentsInfo(*lcr, pixelShader); PipelineObject* pipelineObject = new PipelineObject(); // compile { MetalPipelineCompiler pp(m_mtlr, *pipelineObject); pp.InitFromState(vertexShader->compatibleFetchShader, vertexShader, geometryShader, pixelShader, cachedPipeline->lastUsedAttachmentsInfo, attachmentsInfo, *lcr); pp.Compile(true, true, false); // destroy pp early } // Cache the pipeline uint64 pipelineStateHash = CalculatePipelineHash(vertexShader->compatibleFetchShader, vertexShader, geometryShader, pixelShader, cachedPipeline->lastUsedAttachmentsInfo, attachmentsInfo, *lcr); m_pipelineCacheLock.lock(); m_pipelineCache[pipelineStateHash] = pipelineObject; m_pipelineCacheLock.unlock(); // clean up s_spinlockSharedInternal.lock(); delete lcr; delete cachedPipeline; s_spinlockSharedInternal.unlock(); } ConcurrentQueue<CachedPipeline*> g_mtlPipelineCachingQueue; void MetalPipelineCache::AddCurrentStateToCache(uint64 pipelineStateHash, const MetalAttachmentsInfo& lastUsedAttachmentsInfo) { if (!m_pipelineCacheStoreThread) { m_pipelineCacheStoreThread = new std::thread(&MetalPipelineCache::WorkerThread, this); m_pipelineCacheStoreThread->detach(); } // fill job structure with cached GPU state // for each cached pipeline we store: // - Active shaders (referenced by hash) // - An almost-complete register state of the GPU (minus some ALU uniform constants which aren't relevant) CachedPipeline* job = new CachedPipeline(); auto vs = LatteSHRC_GetActiveVertexShader(); auto gs = LatteSHRC_GetActiveGeometryShader(); auto ps = LatteSHRC_GetActivePixelShader(); if (vs) job->vsHash.set(vs->baseHash, vs->auxHash); if (gs) job->gsHash.set(gs->baseHash, gs->auxHash); if (ps) job->psHash.set(ps->baseHash, ps->auxHash); job->lastUsedAttachmentsInfo = lastUsedAttachmentsInfo; Latte::StoreGPURegisterState(LatteGPUState.contextNew, job->gpuState); // queue job g_mtlPipelineCachingQueue.push(job); } bool MetalPipelineCache::SerializePipeline(MemStreamWriter& memWriter, CachedPipeline& cachedPipeline) { memWriter.writeBE<uint8>(0x01); // version uint8 presentMask = 0; if (cachedPipeline.vsHash.isPresent) presentMask |= 1; if (cachedPipeline.gsHash.isPresent) presentMask |= 2; if (cachedPipeline.psHash.isPresent) presentMask |= 4; memWriter.writeBE<uint8>(presentMask); if (cachedPipeline.vsHash.isPresent) { memWriter.writeBE<uint64>(cachedPipeline.vsHash.baseHash); memWriter.writeBE<uint64>(cachedPipeline.vsHash.auxHash); } if (cachedPipeline.gsHash.isPresent) { memWriter.writeBE<uint64>(cachedPipeline.gsHash.baseHash); memWriter.writeBE<uint64>(cachedPipeline.gsHash.auxHash); } if (cachedPipeline.psHash.isPresent) { memWriter.writeBE<uint64>(cachedPipeline.psHash.baseHash); memWriter.writeBE<uint64>(cachedPipeline.psHash.auxHash); } for (uint8 i = 0; i < LATTE_NUM_COLOR_TARGET; i++) memWriter.writeBE<uint16>((uint16)cachedPipeline.lastUsedAttachmentsInfo.colorFormats[i]); memWriter.writeBE<uint16>((uint16)cachedPipeline.lastUsedAttachmentsInfo.depthFormat); Latte::SerializeRegisterState(cachedPipeline.gpuState, memWriter); return true; } bool MetalPipelineCache::DeserializePipeline(MemStreamReader& memReader, CachedPipeline& cachedPipeline) { // version if (memReader.readBE<uint8>() != 1) { cemuLog_log(LogType::Force, "Cached Metal pipeline corrupted or has unknown version"); return false; } // shader hashes uint8 presentMask = memReader.readBE<uint8>(); if (presentMask & 1) { uint64 baseHash = memReader.readBE<uint64>(); uint64 auxHash = memReader.readBE<uint64>(); cachedPipeline.vsHash.set(baseHash, auxHash); } if (presentMask & 2) { uint64 baseHash = memReader.readBE<uint64>(); uint64 auxHash = memReader.readBE<uint64>(); cachedPipeline.gsHash.set(baseHash, auxHash); } if (presentMask & 4) { uint64 baseHash = memReader.readBE<uint64>(); uint64 auxHash = memReader.readBE<uint64>(); cachedPipeline.psHash.set(baseHash, auxHash); } for (uint8 i = 0; i < LATTE_NUM_COLOR_TARGET; i++) cachedPipeline.lastUsedAttachmentsInfo.colorFormats[i] = (Latte::E_GX2SURFFMT)memReader.readBE<uint16>(); cachedPipeline.lastUsedAttachmentsInfo.depthFormat = (Latte::E_GX2SURFFMT)memReader.readBE<uint16>(); // deserialize GPU state if (!Latte::DeserializeRegisterState(cachedPipeline.gpuState, memReader)) { return false; } cemu_assert_debug(!memReader.hasError()); return true; } int MetalPipelineCache::CompilerThread() { SetThreadName("plCacheCompiler"); while (m_numCompilationThreads != 0) { std::vector<uint8> pipelineData = m_compilationQueue.pop(); if(pipelineData.empty()) continue; LoadPipelineFromCache(pipelineData); ++g_mtlCacheState.pipelinesLoaded; } return 0; } void MetalPipelineCache::WorkerThread() { SetThreadName("plCacheWriter"); while (true) { CachedPipeline* job; g_mtlPipelineCachingQueue.pop(job); if (!s_cache) { delete job; continue; } // serialize MemStreamWriter memWriter(1024 * 4); SerializePipeline(memWriter, *job); auto blob = memWriter.getResult(); // file name is derived from data hash uint8 hash[SHA256_DIGEST_LENGTH]; SHA256(blob.data(), blob.size(), hash); uint64 nameA = *(uint64be*)(hash + 0); uint64 nameB = *(uint64be*)(hash + 8); s_cache->AddFileAsync({ nameA, nameB }, blob.data(), blob.size()); delete job; } } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalPipelineCache.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Metal/MetalPipelineCompiler.h" #include "util/helpers/ConcurrentQueue.h" #include "util/helpers/fspinlock.h" #include "util/math/vector2.h" class MetalPipelineCache { public: static MetalPipelineCache& GetInstance(); MetalPipelineCache(class MetalRenderer* metalRenderer); ~MetalPipelineCache(); PipelineObject* GetRenderPipelineState(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const LatteDecompilerShader* geometryShader, const LatteDecompilerShader* pixelShader, const class MetalAttachmentsInfo& lastUsedAttachmentsInfo, const class MetalAttachmentsInfo& activeAttachmentsInfo, Vector2i extend, uint32 indexCount, const LatteContextRegister& lcr); // Cache loading uint32 BeginLoading(uint64 cacheTitleId); // returns count of pipelines stored in cache bool UpdateLoading(uint32& pipelinesLoadedTotal, uint32& pipelinesMissingShaders); void EndLoading(); void LoadPipelineFromCache(std::span<uint8> fileData); void Close(); // called on title exit // Debug size_t GetPipelineCacheSize() const { return m_pipelineCache.size(); } private: class MetalRenderer* m_mtlr; std::map<uint64, PipelineObject*> m_pipelineCache; FSpinlock m_pipelineCacheLock; std::thread* m_pipelineCacheStoreThread; class FileCache* s_cache; std::atomic_uint32_t m_numCompilationThreads{ 0 }; ConcurrentQueue<std::vector<uint8>> m_compilationQueue; std::atomic_uint32_t m_compilationCount; static uint64 CalculatePipelineHash(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const LatteDecompilerShader* geometryShader, const LatteDecompilerShader* pixelShader, const class MetalAttachmentsInfo& lastUsedAttachmentsInfo, const class MetalAttachmentsInfo& activeAttachmentsInfo, const LatteContextRegister& lcr); void AddCurrentStateToCache(uint64 pipelineStateHash, const class MetalAttachmentsInfo& lastUsedAttachmentsInfo); // pipeline serialization for file bool SerializePipeline(class MemStreamWriter& memWriter, struct CachedPipeline& cachedPipeline); bool DeserializePipeline(class MemStreamReader& memReader, struct CachedPipeline& cachedPipeline); int CompilerThread(); void WorkerThread(); }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalPipelineCompiler.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalCommon.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalPipelineCompiler.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Renderer/Metal/CachedFBOMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureViewMtl.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include <chrono> extern std::atomic_int g_compiling_pipelines; extern std::atomic_int g_compiling_pipelines_async; extern std::atomic_uint64_t g_compiling_pipelines_syncTimeSum; static void rectsEmulationGS_outputSingleVertex(std::string& gsSrc, const LatteDecompilerShader* vertexShader, LatteShaderPSInputTable& psInputTable, sint32 vIdx, const LatteContextRegister& latteRegister) { auto parameterMask = vertexShader->outputParameterMask; for (uint32 i = 0; i < 32; i++) { if ((parameterMask & (1 << i)) == 0) continue; sint32 vsSemanticId = psInputTable.getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i); if (vsSemanticId < 0) continue; // make sure PS has matching input if (!psInputTable.hasPSImportForSemanticId(vsSemanticId)) continue; gsSrc.append(fmt::format("out.passParameterSem{} = objectPayload.vertexOut[{}].passParameterSem{};\r\n", vsSemanticId, vIdx, vsSemanticId)); } gsSrc.append(fmt::format("out.position = objectPayload.vertexOut[{}].position;\r\n", vIdx)); gsSrc.append(fmt::format("mesh.set_vertex({}, out);\r\n", vIdx)); } static void rectsEmulationGS_outputGeneratedVertex(std::string& gsSrc, const LatteDecompilerShader* vertexShader, LatteShaderPSInputTable& psInputTable, const char* variant, const LatteContextRegister& latteRegister) { auto parameterMask = vertexShader->outputParameterMask; for (uint32 i = 0; i < 32; i++) { if ((parameterMask & (1 << i)) == 0) continue; sint32 vsSemanticId = psInputTable.getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i); if (vsSemanticId < 0) continue; // make sure PS has matching input if (!psInputTable.hasPSImportForSemanticId(vsSemanticId)) continue; gsSrc.append(fmt::format("out.passParameterSem{} = gen4thVertex{}(objectPayload.vertexOut[0].passParameterSem{}, objectPayload.vertexOut[1].passParameterSem{}, objectPayload.vertexOut[2].passParameterSem{});\r\n", vsSemanticId, variant, vsSemanticId, vsSemanticId, vsSemanticId)); } gsSrc.append(fmt::format("out.position = gen4thVertex{}(objectPayload.vertexOut[0].position, objectPayload.vertexOut[1].position, objectPayload.vertexOut[2].position);\r\n", variant)); gsSrc.append(fmt::format("mesh.set_vertex(3, out);\r\n")); } static void rectsEmulationGS_outputVerticesCode(std::string& gsSrc, const LatteDecompilerShader* vertexShader, LatteShaderPSInputTable& psInputTable, sint32 p0, sint32 p1, sint32 p2, sint32 p3, const char* variant, const LatteContextRegister& latteRegister) { sint32 pList[4] = { p0, p1, p2, p3 }; for (sint32 i = 0; i < 4; i++) { if (pList[i] == 3) rectsEmulationGS_outputGeneratedVertex(gsSrc, vertexShader, psInputTable, variant, latteRegister); else rectsEmulationGS_outputSingleVertex(gsSrc, vertexShader, psInputTable, pList[i], latteRegister); } gsSrc.append(fmt::format("mesh.set_index(0, {});\r\n", pList[0])); gsSrc.append(fmt::format("mesh.set_index(1, {});\r\n", pList[1])); gsSrc.append(fmt::format("mesh.set_index(2, {});\r\n", pList[2])); gsSrc.append(fmt::format("mesh.set_index(3, {});\r\n", pList[1])); gsSrc.append(fmt::format("mesh.set_index(4, {});\r\n", pList[2])); gsSrc.append(fmt::format("mesh.set_index(5, {});\r\n", pList[3])); } static RendererShaderMtl* rectsEmulationGS_generate(MetalRenderer* metalRenderer, const LatteDecompilerShader* vertexShader, const LatteContextRegister& latteRegister) { std::string gsSrc; gsSrc.append("#include <metal_stdlib>\r\n"); gsSrc.append("using namespace metal;\r\n"); LatteShaderPSInputTable psInputTable; LatteShader_CreatePSInputTable(&psInputTable, latteRegister.GetRawView()); // inputs & outputs std::string vertexOutDefinition = "struct VertexOut {\r\n"; vertexOutDefinition += "float4 position;\r\n"; std::string geometryOutDefinition = "struct GeometryOut {\r\n"; geometryOutDefinition += "float4 position [[position]];\r\n"; auto parameterMask = vertexShader->outputParameterMask; for (uint32 i = 0; i < 32; i++) { if ((parameterMask & (1 << i)) == 0) continue; sint32 vsSemanticId = psInputTable.getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i); if (vsSemanticId < 0) continue; auto psImport = psInputTable.getPSImportBySemanticId(vsSemanticId); if (psImport == nullptr) continue; // VertexOut vertexOutDefinition += fmt::format("float4 passParameterSem{};\r\n", vsSemanticId); // GeometryOut geometryOutDefinition += fmt::format("float4 passParameterSem{}", vsSemanticId); geometryOutDefinition += fmt::format(" [[user(locn{})]]", psInputTable.getPSImportLocationBySemanticId(vsSemanticId)); if (psImport->isFlat) geometryOutDefinition += " [[flat]]"; if (psImport->isNoPerspective) geometryOutDefinition += " [[center_no_perspective]]"; geometryOutDefinition += ";\r\n"; } vertexOutDefinition += "};\r\n"; geometryOutDefinition += "};\r\n"; gsSrc.append(vertexOutDefinition); gsSrc.append(geometryOutDefinition); gsSrc.append("struct ObjectPayload {\r\n"); gsSrc.append("VertexOut vertexOut[3];\r\n"); gsSrc.append("};\r\n"); // gen function gsSrc.append("float4 gen4thVertexA(float4 a, float4 b, float4 c)\r\n"); gsSrc.append("{\r\n"); gsSrc.append("return b - (c - a);\r\n"); gsSrc.append("}\r\n"); gsSrc.append("float4 gen4thVertexB(float4 a, float4 b, float4 c)\r\n"); gsSrc.append("{\r\n"); gsSrc.append("return c - (b - a);\r\n"); gsSrc.append("}\r\n"); gsSrc.append("float4 gen4thVertexC(float4 a, float4 b, float4 c)\r\n"); gsSrc.append("{\r\n"); gsSrc.append("return c + (b - a);\r\n"); gsSrc.append("}\r\n"); // main gsSrc.append("using MeshType = mesh<GeometryOut, void, 4, 2, topology::triangle>;\r\n"); gsSrc.append("[[mesh, max_total_threads_per_threadgroup(1)]]\r\n"); gsSrc.append("void main0(MeshType mesh, const object_data ObjectPayload& objectPayload [[payload]])\r\n"); gsSrc.append("{\r\n"); gsSrc.append("GeometryOut out;\r\n"); // there are two possible winding orders that need different triangle generation: // 0 1 // 2 3 // and // 0 1 // 3 2 // all others are just symmetries of these cases // we can determine the case by comparing the distance 0<->1 and 0<->2 gsSrc.append("float dist0_1 = length(objectPayload.vertexOut[1].position.xy - objectPayload.vertexOut[0].position.xy);\r\n"); gsSrc.append("float dist0_2 = length(objectPayload.vertexOut[2].position.xy - objectPayload.vertexOut[0].position.xy);\r\n"); gsSrc.append("float dist1_2 = length(objectPayload.vertexOut[2].position.xy - objectPayload.vertexOut[1].position.xy);\r\n"); // emit vertices gsSrc.append("if(dist0_1 > dist0_2 && dist0_1 > dist1_2)\r\n"); gsSrc.append("{\r\n"); // p0 to p1 is diagonal rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 2, 1, 0, 3, "A", latteRegister); gsSrc.append("} else if ( dist0_2 > dist0_1 && dist0_2 > dist1_2 ) {\r\n"); // p0 to p2 is diagonal rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 1, 2, 0, 3, "B", latteRegister); gsSrc.append("} else {\r\n"); // p1 to p2 is diagonal rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 0, 1, 2, 3, "C", latteRegister); gsSrc.append("}\r\n"); gsSrc.append("mesh.set_primitive_count(2);\r\n"); gsSrc.append("}\r\n"); auto mtlShader = new RendererShaderMtl(metalRenderer, RendererShader::ShaderType::kGeometry, 0, 0, false, false, gsSrc); mtlShader->PreponeCompilation(true); return mtlShader; } #define INVALID_TITLE_ID 0xFFFFFFFFFFFFFFFF uint64 s_cacheTitleId = INVALID_TITLE_ID; extern std::atomic_int g_compiled_shaders_total; extern std::atomic_int g_compiled_shaders_async; template<typename T> void SetFragmentState(T* desc, const MetalAttachmentsInfo& lastUsedAttachmentsInfo, const MetalAttachmentsInfo& activeAttachmentsInfo, bool rasterizationEnabled, const LatteContextRegister& lcr) { // TODO: check if the pixel shader is valid as well? if (!rasterizationEnabled/* || !pixelShaderMtl*/) { desc->setRasterizationEnabled(false); return; } // Color attachments const Latte::LATTE_CB_COLOR_CONTROL& colorControlReg = lcr.CB_COLOR_CONTROL; uint32 blendEnableMask = colorControlReg.get_BLEND_MASK(); uint32 renderTargetMask = lcr.CB_TARGET_MASK.get_MASK(); for (uint8 i = 0; i < LATTE_NUM_COLOR_TARGET; i++) { Latte::E_GX2SURFFMT format = lastUsedAttachmentsInfo.colorFormats[i]; if (format == Latte::E_GX2SURFFMT::INVALID_FORMAT) continue; MTL::PixelFormat pixelFormat = GetMtlPixelFormat(format, false); auto colorAttachment = desc->colorAttachments()->object(i); colorAttachment->setPixelFormat(pixelFormat); // Disable writes if not in the active FBO if (activeAttachmentsInfo.colorFormats[i] == Latte::E_GX2SURFFMT::INVALID_FORMAT) { colorAttachment->setWriteMask(MTL::ColorWriteMaskNone); continue; } colorAttachment->setWriteMask(GetMtlColorWriteMask((renderTargetMask >> (i * 4)) & 0xF)); // Blending bool blendEnabled = ((blendEnableMask & (1 << i))) != 0; // Only float data type is blendable if (blendEnabled && GetMtlPixelFormatInfo(format, false).dataType == MetalDataType::FLOAT) { colorAttachment->setBlendingEnabled(true); const auto& blendControlReg = lcr.CB_BLENDN_CONTROL[i]; auto rgbBlendOp = GetMtlBlendOp(blendControlReg.get_COLOR_COMB_FCN()); auto srcRgbBlendFactor = GetMtlBlendFactor(blendControlReg.get_COLOR_SRCBLEND()); auto dstRgbBlendFactor = GetMtlBlendFactor(blendControlReg.get_COLOR_DSTBLEND()); colorAttachment->setRgbBlendOperation(rgbBlendOp); colorAttachment->setSourceRGBBlendFactor(srcRgbBlendFactor); colorAttachment->setDestinationRGBBlendFactor(dstRgbBlendFactor); if (blendControlReg.get_SEPARATE_ALPHA_BLEND()) { colorAttachment->setAlphaBlendOperation(GetMtlBlendOp(blendControlReg.get_ALPHA_COMB_FCN())); colorAttachment->setSourceAlphaBlendFactor(GetMtlBlendFactor(blendControlReg.get_ALPHA_SRCBLEND())); colorAttachment->setDestinationAlphaBlendFactor(GetMtlBlendFactor(blendControlReg.get_ALPHA_DSTBLEND())); } else { colorAttachment->setAlphaBlendOperation(rgbBlendOp); colorAttachment->setSourceAlphaBlendFactor(srcRgbBlendFactor); colorAttachment->setDestinationAlphaBlendFactor(dstRgbBlendFactor); } } } // Depth stencil attachment if (lastUsedAttachmentsInfo.depthFormat != Latte::E_GX2SURFFMT::INVALID_FORMAT) { MTL::PixelFormat pixelFormat = GetMtlPixelFormat(lastUsedAttachmentsInfo.depthFormat, true); desc->setDepthAttachmentPixelFormat(pixelFormat); if (lastUsedAttachmentsInfo.hasStencil) desc->setStencilAttachmentPixelFormat(pixelFormat); } } MetalPipelineCompiler::~MetalPipelineCompiler() { /* for (auto& pair : m_pipelineCache) { pair.second->release(); } m_pipelineCache.clear(); NS::Error* error = nullptr; m_binaryArchive->serializeToURL(m_binaryArchiveURL, &error); if (error) { cemuLog_log(LogType::Force, "error serializing binary archive: {}", error->localizedDescription()->utf8String()); error->release(); } m_binaryArchive->release(); m_binaryArchiveURL->release(); */ if (m_pipelineDescriptor) m_pipelineDescriptor->release(); } void MetalPipelineCompiler::InitFromState(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const LatteDecompilerShader* geometryShader, const LatteDecompilerShader* pixelShader, const MetalAttachmentsInfo& lastUsedAttachmentsInfo, const MetalAttachmentsInfo& activeAttachmentsInfo, const LatteContextRegister& lcr) { m_usesGeometryShader = UseGeometryShader(lcr, geometryShader != nullptr); if (m_usesGeometryShader && !m_mtlr->SupportsMeshShaders()) return; // Rasterization m_rasterizationEnabled = lcr.IsRasterizationEnabled(); // Shaders m_vertexShaderMtl = static_cast<RendererShaderMtl*>(vertexShader->shader); if (geometryShader) m_geometryShaderMtl = static_cast<RendererShaderMtl*>(geometryShader->shader); else if (UseRectEmulation(lcr)) m_geometryShaderMtl = rectsEmulationGS_generate(m_mtlr, vertexShader, lcr); else m_geometryShaderMtl = nullptr; m_pixelShaderMtl = static_cast<RendererShaderMtl*>(pixelShader->shader); if (m_usesGeometryShader) InitFromStateMesh(fetchShader, lastUsedAttachmentsInfo, activeAttachmentsInfo, lcr); else InitFromStateRender(fetchShader, vertexShader, lastUsedAttachmentsInfo, activeAttachmentsInfo, lcr); } bool MetalPipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool showInOverlay) { if (m_usesGeometryShader && !m_mtlr->SupportsMeshShaders()) return false; if (forceCompile) { // if some shader stages are not compiled yet, compile them now if (m_vertexShaderMtl && !m_vertexShaderMtl->IsCompiled()) m_vertexShaderMtl->PreponeCompilation(isRenderThread); if (m_geometryShaderMtl && !m_geometryShaderMtl->IsCompiled()) m_geometryShaderMtl->PreponeCompilation(isRenderThread); if (m_pixelShaderMtl && !m_pixelShaderMtl->IsCompiled()) m_pixelShaderMtl->PreponeCompilation(isRenderThread); } else { // fail early if some shader stages are not compiled if (m_vertexShaderMtl && !m_vertexShaderMtl->IsCompiled()) return false; if (m_geometryShaderMtl && !m_geometryShaderMtl->IsCompiled()) return false; if (m_pixelShaderMtl && !m_pixelShaderMtl->IsCompiled()) return false; } // Compile MTL::RenderPipelineState* pipeline = nullptr; NS::Error* error = nullptr; auto start = std::chrono::high_resolution_clock::now(); if (m_usesGeometryShader) { auto desc = static_cast<MTL::MeshRenderPipelineDescriptor*>(m_pipelineDescriptor); // Shaders desc->setObjectFunction(m_vertexShaderMtl->GetFunction()); desc->setMeshFunction(m_geometryShaderMtl->GetFunction()); if (m_rasterizationEnabled) desc->setFragmentFunction(m_pixelShaderMtl->GetFunction()); #ifdef CEMU_DEBUG_ASSERT desc->setLabel(GetLabel("Mesh render pipeline state", desc)); #endif pipeline = m_mtlr->GetDevice()->newRenderPipelineState(desc, MTL::PipelineOptionNone, nullptr, &error); } else { auto desc = static_cast<MTL::RenderPipelineDescriptor*>(m_pipelineDescriptor); // Shaders desc->setVertexFunction(m_vertexShaderMtl->GetFunction()); if (m_rasterizationEnabled) desc->setFragmentFunction(m_pixelShaderMtl->GetFunction()); #ifdef CEMU_DEBUG_ASSERT desc->setLabel(GetLabel("Render pipeline state", desc)); #endif pipeline = m_mtlr->GetDevice()->newRenderPipelineState(desc, MTL::PipelineOptionNone, nullptr, &error); } auto end = std::chrono::high_resolution_clock::now(); auto creationDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); if (error) { cemuLog_log(LogType::Force, "error creating render pipeline state: {}", error->localizedDescription()->utf8String()); } if (showInOverlay) { if (isRenderThread) g_compiling_pipelines_syncTimeSum += creationDuration; else g_compiling_pipelines_async++; g_compiling_pipelines++; } m_pipelineObj.m_pipeline = pipeline; return true; } void MetalPipelineCompiler::InitFromStateRender(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const MetalAttachmentsInfo& lastUsedAttachmentsInfo, const MetalAttachmentsInfo& activeAttachmentsInfo, const LatteContextRegister& lcr) { // Render pipeline state MTL::RenderPipelineDescriptor* desc = MTL::RenderPipelineDescriptor::alloc()->init(); // Vertex descriptor if (!fetchShader->mtlFetchVertexManually) { NS_STACK_SCOPED MTL::VertexDescriptor* vertexDescriptor = MTL::VertexDescriptor::alloc()->init(); for (auto& bufferGroup : fetchShader->bufferGroups) { std::optional<LatteConst::VertexFetchType2> fetchType; uint32 minBufferStride = 0; for (sint32 j = 0; j < bufferGroup.attribCount; ++j) { auto& attr = bufferGroup.attrib[j]; uint32 semanticId = vertexShader->resourceMapping.attributeMapping[attr.semanticId]; if (semanticId == (uint32)-1) continue; // attribute not used? auto attribute = vertexDescriptor->attributes()->object(semanticId); attribute->setOffset(attr.offset); attribute->setBufferIndex(GET_MTL_VERTEX_BUFFER_INDEX(attr.attributeBufferIndex)); attribute->setFormat(GetMtlVertexFormat(attr.format)); minBufferStride = std::max(minBufferStride, attr.offset + GetMtlVertexFormatSize(attr.format)); if (fetchType.has_value()) cemu_assert_debug(fetchType == attr.fetchType); else fetchType = attr.fetchType; if (attr.fetchType == LatteConst::INSTANCE_DATA) { cemu_assert_debug(attr.aluDivisor == 1); // other divisor not yet supported } } uint32 bufferIndex = bufferGroup.attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; uint32 bufferStride = (lcr.GetRawView()[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; auto layout = vertexDescriptor->layouts()->object(GET_MTL_VERTEX_BUFFER_INDEX(bufferIndex)); if (bufferStride == 0) { // Buffer stride cannot be zero, let's use the minimum stride bufferStride = minBufferStride; // Additionally, constant vertex function must be used layout->setStepFunction(MTL::VertexStepFunctionConstant); layout->setStepRate(0); } else { if (!fetchType.has_value() || fetchType == LatteConst::VertexFetchType2::VERTEX_DATA) layout->setStepFunction(MTL::VertexStepFunctionPerVertex); else if (fetchType == LatteConst::VertexFetchType2::INSTANCE_DATA) layout->setStepFunction(MTL::VertexStepFunctionPerInstance); else { cemuLog_log(LogType::Force, "unimplemented vertex fetch type {}", (uint32)fetchType.value()); cemu_assert(false); } } bufferStride = Align(bufferStride, 4); layout->setStride(bufferStride); } desc->setVertexDescriptor(vertexDescriptor); } SetFragmentState(desc, lastUsedAttachmentsInfo, activeAttachmentsInfo, m_rasterizationEnabled, lcr); m_pipelineDescriptor = desc; } void MetalPipelineCompiler::InitFromStateMesh(const LatteFetchShader* fetchShader, const MetalAttachmentsInfo& lastUsedAttachmentsInfo, const MetalAttachmentsInfo& activeAttachmentsInfo, const LatteContextRegister& lcr) { // Render pipeline state MTL::MeshRenderPipelineDescriptor* desc = MTL::MeshRenderPipelineDescriptor::alloc()->init(); SetFragmentState(desc, lastUsedAttachmentsInfo, activeAttachmentsInfo, m_rasterizationEnabled, lcr); m_pipelineDescriptor = desc; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalPipelineCompiler.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Metal/MetalAttachmentsInfo.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" struct PipelineObject { MTL::RenderPipelineState* m_pipeline = nullptr; }; class MetalPipelineCompiler { public: MetalPipelineCompiler(class MetalRenderer* metalRenderer, PipelineObject& pipelineObj) : m_mtlr{metalRenderer}, m_pipelineObj{pipelineObj} {} ~MetalPipelineCompiler(); void InitFromState(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const LatteDecompilerShader* geometryShader, const LatteDecompilerShader* pixelShader, const class MetalAttachmentsInfo& lastUsedAttachmentsInfo, const class MetalAttachmentsInfo& activeAttachmentsInfo, const LatteContextRegister& lcr); bool Compile(bool forceCompile, bool isRenderThread, bool showInOverlay); private: class MetalRenderer* m_mtlr; PipelineObject& m_pipelineObj; class RendererShaderMtl* m_vertexShaderMtl; class RendererShaderMtl* m_geometryShaderMtl; class RendererShaderMtl* m_pixelShaderMtl; bool m_usesGeometryShader; bool m_rasterizationEnabled; NS::Object* m_pipelineDescriptor = nullptr; void InitFromStateRender(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const class MetalAttachmentsInfo& lastUsedAttachmentsInfo, const class MetalAttachmentsInfo& activeAttachmentsInfo, const LatteContextRegister& lcr); void InitFromStateMesh(const LatteFetchShader* fetchShader, const class MetalAttachmentsInfo& lastUsedAttachmentsInfo, const class MetalAttachmentsInfo& activeAttachmentsInfo, const LatteContextRegister& lcr); }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalQuery.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalQuery.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" bool LatteQueryObjectMtl::getResult(uint64& numSamplesPassed) { if (m_commandBuffer && !CommandBufferCompleted(m_commandBuffer)) return false; uint64* resultPtr = m_mtlr->GetOcclusionQueryResultsPtr(); numSamplesPassed = 0; for (uint32 i = m_range.begin; i != m_range.end; i = (i + 1) % MetalRenderer::OCCLUSION_QUERY_POOL_SIZE) numSamplesPassed += resultPtr[i]; return true; } LatteQueryObjectMtl::~LatteQueryObjectMtl() { if (m_commandBuffer) m_commandBuffer->release(); } void LatteQueryObjectMtl::begin() { m_range.begin = m_mtlr->GetOcclusionQueryIndex(); m_mtlr->BeginOcclusionQuery(); } void LatteQueryObjectMtl::end() { m_range.end = m_mtlr->GetOcclusionQueryIndex(); m_mtlr->EndOcclusionQuery(); m_commandBuffer = m_mtlr->GetAndRetainCurrentCommandBufferIfNotCompleted(); if (m_commandBuffer) m_mtlr->RequestSoonCommit(); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalQuery.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteQueryObject.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalCommon.h" class LatteQueryObjectMtl : public LatteQueryObject { public: LatteQueryObjectMtl(class MetalRenderer* mtlRenderer) : m_mtlr{mtlRenderer} {} ~LatteQueryObjectMtl(); bool getResult(uint64& numSamplesPassed) override; void begin() override; void end() override; void GrowRange() { m_range.end++; } private: class MetalRenderer* m_mtlr; MetalQueryRange m_range = {INVALID_UINT32, INVALID_UINT32}; // TODO: make this a list of command buffers? MTL::CommandBuffer* m_commandBuffer = nullptr; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalMemoryManager.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureViewMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/CachedFBOMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalOutputShaderCache.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalPipelineCache.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalDepthStencilCache.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalSamplerCache.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteTextureReadbackMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalVoidVertexPipeline.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalQuery.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/UtilityShaderSource.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteIndices.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "CafeSystem.h" #include "Cemu/Logging/CemuLogging.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Core/LatteConst.h" #include "config/CemuConfig.h" #define IMGUI_IMPL_METAL_CPP #include "imgui/imgui_extension.h" #include "imgui/imgui_impl_metal.h" #define EVENT_VALUE_WRAP 4096 extern bool hasValidFramebufferAttached; float supportBufferData[512 * 4]; // Defined in the OpenGL renderer void LatteDraw_handleSpecialState8_clearAsDepth(); std::vector<MetalRenderer::DeviceInfo> MetalRenderer::GetDevices() { NS_STACK_SCOPED auto devices = MTL::CopyAllDevices(); std::vector<MetalRenderer::DeviceInfo> result; result.reserve(devices->count()); for (uint32 i = 0; i < devices->count(); i++) { MTL::Device* device = static_cast<MTL::Device*>(devices->object(i)); result.push_back({std::string(device->name()->utf8String()), device->registryID()}); } return result; } MetalRenderer::MetalRenderer() { // Options // Position invariance switch (g_current_game_profile->GetPositionInvariance()) { case PositionInvariance::Auto: switch (CafeSystem::GetForegroundTitleId()) { // Bayonetta case 0x0005000010157F00: // EUR case 0x0005000010157E00: // USA case 0x000500001014DB00: // JPN // Bayonetta 2 case 0x0005000010172700: // EUR case 0x0005000010172600: // USA // Disney Planes case 0x0005000010136900: // EUR case 0x0005000010136A00: // EUR (TODO: check) case 0x0005000010136B00: // EUR (TODO: check) case 0x000500001011C500: // USA (TODO: check) // LEGO STAR WARS: The Force Awakens case 0x00050000101DAA00: // EUR case 0x00050000101DAB00: // USA // Mario Kart 8 case 0x000500001010ED00: // EUR case 0x000500001010EC00: // USA case 0x000500001010EB00: // JPN case 0x0005000010183A00: // JPN (TODO: check) // Minecraft: Story Mode case 0x000500001020A300: // EUR case 0x00050000101E0100: // USA //case 0x000500001020a200: // USA // Ninja Gaiden 3: Razor's Edge case 0x0005000010110B00: // EUR case 0x0005000010139B00: // EUR (TODO: check) case 0x0005000010110A00: // USA case 0x0005000010110900: // JPN // Resident Evil: Revelations case 0x000500001012B400: // EUR case 0x000500001012CF00: // USA // Star Fox Zero case 0x00050000101B0500: // EUR case 0x0005000010201C00: // EUR (TODO: check) case 0x00050000101B0400: // USA case 0x0005000010201B00: // USA (TODO: check) // The Legend of Zelda: Breath of the Wild case 0x00050000101C9500: // EUR case 0x00050000101C9400: // USA case 0x00050000101C9300: // JPN // Wonderful 101 case 0x0005000010135300: // EUR case 0x000500001012DC00: // USA case 0x0005000010116300: // JPN case 0x0005000010185600: // JPN (TODO: check) m_positionInvariance = true; break; default: m_positionInvariance = false; break; } break; case PositionInvariance::False: m_positionInvariance = false; break; case PositionInvariance::True: m_positionInvariance = true; break; } // Pick a device auto& config = GetConfig(); const bool hasDeviceSet = config.mtl_graphic_device_uuid != 0; // If a device is set, try to find it if (hasDeviceSet) { NS_STACK_SCOPED auto devices = MTL::CopyAllDevices(); for (uint32 i = 0; i < devices->count(); i++) { MTL::Device* device = static_cast<MTL::Device*>(devices->object(i)); if (device->registryID() == config.mtl_graphic_device_uuid) { m_device = device; break; } } } if (!m_device) { if (hasDeviceSet) { cemuLog_log(LogType::Force, "The selected GPU ({}) could not be found. Using the system default device.", config.mtl_graphic_device_uuid); config.mtl_graphic_device_uuid = 0; } // Use the system default device m_device = MTL::CreateSystemDefaultDevice(); } // Vendor const char* deviceName = m_device->name()->utf8String(); if (memcmp(deviceName, "Apple", 5) == 0) m_vendor = GfxVendor::Apple; else if (memcmp(deviceName, "AMD", 3) == 0) m_vendor = GfxVendor::AMD; else if (memcmp(deviceName, "Intel", 5) == 0) m_vendor = GfxVendor::Intel; else if (memcmp(deviceName, "NVIDIA", 6) == 0) m_vendor = GfxVendor::Nvidia; else m_vendor = GfxVendor::Generic; // Feature support m_isAppleGPU = m_device->supportsFamily(MTL::GPUFamilyApple1); m_supportsFramebufferFetch = GetConfig().framebuffer_fetch.GetValue() ? m_device->supportsFamily(MTL::GPUFamilyApple2) : false; m_hasUnifiedMemory = m_device->hasUnifiedMemory(); m_supportsMetal3 = m_device->supportsFamily(MTL::GPUFamilyMetal3); m_supportsMeshShaders = (m_supportsMetal3 && (m_vendor != GfxVendor::Intel || GetConfig().force_mesh_shaders.GetValue())); // Intel GPUs have issues with mesh shaders m_recommendedMaxVRAMUsage = m_device->recommendedMaxWorkingSetSize(); m_pixelFormatSupport = MetalPixelFormatSupport(m_device); CheckForPixelFormatSupport(m_pixelFormatSupport); // Command queue m_commandQueue = m_device->newCommandQueue(); // Synchronization resources m_event = m_device->newEvent(); // Resources NS_STACK_SCOPED MTL::SamplerDescriptor* samplerDescriptor = MTL::SamplerDescriptor::alloc()->init(); #ifdef CEMU_DEBUG_ASSERT samplerDescriptor->setLabel(GetLabel("Nearest sampler state", samplerDescriptor)); #endif m_nearestSampler = m_device->newSamplerState(samplerDescriptor); samplerDescriptor->setMinFilter(MTL::SamplerMinMagFilterLinear); samplerDescriptor->setMagFilter(MTL::SamplerMinMagFilterLinear); #ifdef CEMU_DEBUG_ASSERT samplerDescriptor->setLabel(GetLabel("Linear sampler state", samplerDescriptor)); #endif m_linearSampler = m_device->newSamplerState(samplerDescriptor); // Null resources NS_STACK_SCOPED MTL::TextureDescriptor* textureDescriptor = MTL::TextureDescriptor::alloc()->init(); textureDescriptor->setTextureType(MTL::TextureType1D); textureDescriptor->setWidth(1); textureDescriptor->setUsage(MTL::TextureUsageShaderRead); m_nullTexture1D = m_device->newTexture(textureDescriptor); #ifdef CEMU_DEBUG_ASSERT m_nullTexture1D->setLabel(GetLabel("Null texture 1D", m_nullTexture1D)); #endif textureDescriptor->setTextureType(MTL::TextureType2D); textureDescriptor->setHeight(1); textureDescriptor->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageRenderTarget); m_nullTexture2D = m_device->newTexture(textureDescriptor); #ifdef CEMU_DEBUG_ASSERT m_nullTexture2D->setLabel(GetLabel("Null texture 2D", m_nullTexture2D)); #endif m_memoryManager = new MetalMemoryManager(this); m_outputShaderCache = new MetalOutputShaderCache(this); m_pipelineCache = new MetalPipelineCache(this); m_depthStencilCache = new MetalDepthStencilCache(this); m_samplerCache = new MetalSamplerCache(this); // Lower the commit treshold when buffer cache needs reduced latency if (m_memoryManager->NeedsReducedLatency()) m_defaultCommitTreshlod = 64; else m_defaultCommitTreshlod = 196; // Occlusion queries m_occlusionQuery.m_resultBuffer = m_device->newBuffer(OCCLUSION_QUERY_POOL_SIZE * sizeof(uint64), MTL::ResourceStorageModeShared); #ifdef CEMU_DEBUG_ASSERT m_occlusionQuery.m_resultBuffer->setLabel(GetLabel("Occlusion query result buffer", m_occlusionQuery.m_resultBuffer)); #endif m_occlusionQuery.m_resultsPtr = (uint64*)m_occlusionQuery.m_resultBuffer->contents(); // Reset vertex and uniform buffers for (uint32 i = 0; i < MAX_MTL_VERTEX_BUFFERS; i++) m_state.m_vertexBufferOffsets[i] = INVALID_OFFSET; for (uint32 i = 0; i < METAL_SHADER_TYPE_TOTAL; i++) { for (uint32 j = 0; j < MAX_MTL_BUFFERS; j++) m_state.m_uniformBufferOffsets[i][j] = INVALID_OFFSET; } // Utility shader library // Create the library NS::Error* error = nullptr; NS_STACK_SCOPED MTL::Library* utilityLibrary = m_device->newLibrary(ToNSString(utilityShaderSource), nullptr, &error); if (error) { cemuLog_log(LogType::Force, "failed to create utility library (error: {})", error->localizedDescription()->utf8String()); } // Pipelines NS_STACK_SCOPED MTL::Function* vertexFullscreenFunction = utilityLibrary->newFunction(ToNSString("vertexFullscreen")); NS_STACK_SCOPED MTL::Function* fragmentCopyDepthToColorFunction = utilityLibrary->newFunction(ToNSString("fragmentCopyDepthToColor")); m_copyDepthToColorDesc = MTL::RenderPipelineDescriptor::alloc()->init(); m_copyDepthToColorDesc->setVertexFunction(vertexFullscreenFunction); m_copyDepthToColorDesc->setFragmentFunction(fragmentCopyDepthToColorFunction); // Void vertex pipelines if (m_isAppleGPU) m_copyBufferToBufferPipeline = new MetalVoidVertexPipeline(this, utilityLibrary, "vertexCopyBufferToBuffer"); // HACK: for some reason, this variable ends up being initialized to some garbage data, even though its declared as bool m_captureFrame = false; m_occlusionQuery.m_lastCommandBuffer = nullptr; m_captureFrame = false; } MetalRenderer::~MetalRenderer() { if (m_isAppleGPU) delete m_copyBufferToBufferPipeline; //delete m_copyTextureToTexturePipeline; //delete m_restrideBufferPipeline; m_copyDepthToColorDesc->release(); for (const auto [pixelFormat, pipeline] : m_copyDepthToColorPipelines) pipeline->release(); delete m_outputShaderCache; delete m_pipelineCache; delete m_depthStencilCache; delete m_samplerCache; delete m_memoryManager; m_nullTexture1D->release(); m_nullTexture2D->release(); m_nearestSampler->release(); m_linearSampler->release(); if (m_readbackBuffer) m_readbackBuffer->release(); if (m_xfbRingBuffer) m_xfbRingBuffer->release(); m_occlusionQuery.m_resultBuffer->release(); m_event->release(); m_commandQueue->release(); m_device->release(); } void MetalRenderer::InitializeLayer(const Vector2i& size, bool mainWindow) { auto& layer = GetLayer(mainWindow); layer = MetalLayerHandle(m_device, size, mainWindow); layer.GetLayer()->setPixelFormat(MTL::PixelFormatBGRA8Unorm); } void MetalRenderer::ShutdownLayer(bool mainWindow) { GetLayer(mainWindow) = MetalLayerHandle(); } void MetalRenderer::ResizeLayer(const Vector2i& size, bool mainWindow) { GetLayer(mainWindow).Resize(size); } void MetalRenderer::Initialize() { Renderer::Initialize(); RendererShaderMtl::Initialize(); } void MetalRenderer::Shutdown() { // TODO: should shutdown both layers ImGui_ImplMetal_Shutdown(); CommitCommandBuffer(); Renderer::Shutdown(); RendererShaderMtl::Shutdown(); } bool MetalRenderer::IsPadWindowActive() { return (GetLayer(false).GetLayer() != nullptr); } bool MetalRenderer::GetVRAMInfo(int& usageInMB, int& totalInMB) const { // Subtract host memory from total VRAM, since it's shared with the CPU usageInMB = (m_device->currentAllocatedSize() - m_memoryManager->GetHostAllocationSize()) / 1024 / 1024; totalInMB = m_recommendedMaxVRAMUsage / 1024 / 1024; return true; } void MetalRenderer::ClearColorbuffer(bool padView) { if (!AcquireDrawable(!padView)) return; ClearColorTextureInternal(GetLayer(!padView).GetDrawable()->texture(), 0, 0, 0.0f, 0.0f, 0.0f, 0.0f); } void MetalRenderer::DrawEmptyFrame(bool mainWindow) { if (!BeginFrame(mainWindow)) return; SwapBuffers(mainWindow, !mainWindow); } void MetalRenderer::SwapBuffers(bool swapTV, bool swapDRC) { if (swapTV) SwapBuffer(true); if (swapDRC) SwapBuffer(false); // Reset the command buffers (they are released by TemporaryBufferAllocator) CommitCommandBuffer(); // Debug m_performanceMonitor.ResetPerFrameData(); // GPU capture if (m_capturing) { EndCapture(); } else if (m_captureFrame) { StartCapture(); m_captureFrame = false; } } void MetalRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool padView) { if (!m_screenshot_requested && m_screenshot_state == ScreenshotState::None) return; if (m_mainLayer.GetDrawable()) { // we already took a pad view screenshow and want a main window screenshot if (m_screenshot_state == ScreenshotState::Main && padView) return; if (m_screenshot_state == ScreenshotState::Pad && !padView) return; // remember which screenshot is left to take if (m_screenshot_state == ScreenshotState::None) m_screenshot_state = padView ? ScreenshotState::Main : ScreenshotState::Pad; else m_screenshot_state = ScreenshotState::None; } else m_screenshot_state = ScreenshotState::None; auto texMtl = static_cast<LatteTextureMtl*>(texView->baseTexture); int width, height; texMtl->GetEffectiveSize(width, height, 0); uint32 bytesPerRow = GetMtlTextureBytesPerRow(texMtl->format, texMtl->isDepth, width); uint32 size = GetMtlTextureBytesPerImage(texMtl->format, texMtl->isDepth, height, bytesPerRow); auto blitCommandEncoder = GetBlitCommandEncoder(); auto& bufferAllocator = m_memoryManager->GetStagingAllocator(); auto buffer = bufferAllocator.AllocateBufferMemory(size, 1); blitCommandEncoder->copyFromTexture(texMtl->GetTexture(), 0, 0, MTL::Origin(0, 0, 0), MTL::Size(width, height, 1), buffer.mtlBuffer, buffer.bufferOffset, bytesPerRow, 0); bool formatValid = true; std::vector<uint8> rgb_data; rgb_data.reserve(3 * width * height); auto pixelFormat = texMtl->GetTexture()->pixelFormat(); // TODO: implement more formats switch (pixelFormat) { case MTL::PixelFormatRGBA8Unorm: for (auto ptr = buffer.memPtr; ptr < buffer.memPtr + size; ptr += 4) { rgb_data.emplace_back(*ptr); rgb_data.emplace_back(*(ptr + 1)); rgb_data.emplace_back(*(ptr + 2)); } break; case MTL::PixelFormatRGBA8Unorm_sRGB: for (auto ptr = buffer.memPtr; ptr < buffer.memPtr + size; ptr += 4) { rgb_data.emplace_back(SRGBComponentToRGB(*ptr)); rgb_data.emplace_back(SRGBComponentToRGB(*(ptr + 1))); rgb_data.emplace_back(SRGBComponentToRGB(*(ptr + 2))); } break; default: cemuLog_log(LogType::Force, "Unsupported screenshot texture pixel format {}", pixelFormat); formatValid = false; break; } if (formatValid) SaveScreenshot(rgb_data, width, height, !padView); } void MetalRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) { if (!AcquireDrawable(!padView)) return; MTL::Texture* presentTexture = static_cast<LatteTextureViewMtl*>(texView)->GetRGBAView(); // Create render pass auto& layer = GetLayer(!padView); NS_STACK_SCOPED MTL::RenderPassDescriptor* renderPassDescriptor = MTL::RenderPassDescriptor::alloc()->init(); auto colorAttachment = renderPassDescriptor->colorAttachments()->object(0); colorAttachment->setTexture(layer.GetDrawable()->texture()); colorAttachment->setLoadAction(clearBackground ? MTL::LoadActionClear : MTL::LoadActionLoad); colorAttachment->setStoreAction(MTL::StoreActionStore); auto renderCommandEncoder = GetTemporaryRenderCommandEncoder(renderPassDescriptor); // Get a render pipeline // Find out which shader we are using uint8 shaderIndex = 255; if (shader == RendererOutputShader::s_copy_shader) shaderIndex = 0; else if (shader == RendererOutputShader::s_bicubic_shader) shaderIndex = 1; else if (shader == RendererOutputShader::s_hermit_shader) shaderIndex = 2; else if (shader == RendererOutputShader::s_copy_shader_ud) shaderIndex = 3; else if (shader == RendererOutputShader::s_bicubic_shader_ud) shaderIndex = 4; else if (shader == RendererOutputShader::s_hermit_shader_ud) shaderIndex = 5; uint8 shaderType = shaderIndex % 3; // Get the render pipeline state auto renderPipelineState = m_outputShaderCache->GetPipeline(shader, shaderIndex, m_state.m_usesSRGB); // Draw to Metal layer renderCommandEncoder->setRenderPipelineState(renderPipelineState); renderCommandEncoder->setFragmentTexture(presentTexture, 0); renderCommandEncoder->setFragmentSamplerState((useLinearTexFilter ? m_linearSampler : m_nearestSampler), 0); // Set uniforms float outputSize[2] = {(float)imageWidth, (float)imageHeight}; switch (shaderType) { case 2: renderCommandEncoder->setFragmentBytes(outputSize, sizeof(outputSize), 0); break; default: break; } renderCommandEncoder->setViewport(MTL::Viewport{(double)imageX, (double)imageY, (double)imageWidth, (double)imageHeight, 0.0, 1.0}); renderCommandEncoder->setScissorRect(MTL::ScissorRect{(uint32)imageX, (uint32)imageY, (uint32)imageWidth, (uint32)imageHeight}); renderCommandEncoder->drawPrimitives(MTL::PrimitiveTypeTriangle, NS::UInteger(0), NS::UInteger(3)); EndEncoding(); } bool MetalRenderer::BeginFrame(bool mainWindow) { return AcquireDrawable(mainWindow); } void MetalRenderer::Flush(bool waitIdle) { if (m_recordedDrawcalls > 0 || waitIdle) CommitCommandBuffer(); if (waitIdle && m_executingCommandBuffers.size() != 0) m_executingCommandBuffers.back()->waitUntilCompleted(); } void MetalRenderer::NotifyLatteCommandProcessorIdle() { //if (m_commitOnIdle) // CommitCommandBuffer(); } bool MetalRenderer::ImguiBegin(bool mainWindow) { if (!Renderer::ImguiBegin(mainWindow)) return false; if (!AcquireDrawable(mainWindow)) return false; EnsureImGuiBackend(); // Check if the font texture needs to be built ImGuiIO& io = ImGui::GetIO(); if (!io.Fonts->IsBuilt()) ImGui_ImplMetal_CreateFontsTexture(m_device); auto& layer = GetLayer(mainWindow); // Render pass descriptor NS_STACK_SCOPED MTL::RenderPassDescriptor* renderPassDescriptor = MTL::RenderPassDescriptor::alloc()->init(); auto colorAttachment = renderPassDescriptor->colorAttachments()->object(0); colorAttachment->setTexture(layer.GetDrawable()->texture()); colorAttachment->setLoadAction(MTL::LoadActionLoad); colorAttachment->setStoreAction(MTL::StoreActionStore); // New frame ImGui_ImplMetal_NewFrame(renderPassDescriptor); ImGui_UpdateWindowInformation(mainWindow); ImGui::NewFrame(); if (m_encoderType != MetalEncoderType::Render) GetTemporaryRenderCommandEncoder(renderPassDescriptor); return true; } void MetalRenderer::ImguiEnd() { EnsureImGuiBackend(); if (m_encoderType != MetalEncoderType::Render) { cemuLog_logOnce(LogType::Force, "no render command encoder, cannot draw ImGui"); return; } ImGui::Render(); ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), GetCurrentCommandBuffer(), (MTL::RenderCommandEncoder*)m_commandEncoder); //ImGui::EndFrame(); EndEncoding(); } ImTextureID MetalRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) { try { std::vector <uint8> tmp(size.x * size.y * 4); for (size_t i = 0; i < data.size() / 3; ++i) { tmp[(i * 4) + 0] = data[(i * 3) + 0]; tmp[(i * 4) + 1] = data[(i * 3) + 1]; tmp[(i * 4) + 2] = data[(i * 3) + 2]; tmp[(i * 4) + 3] = 0xFF; } NS_STACK_SCOPED MTL::TextureDescriptor* desc = MTL::TextureDescriptor::alloc()->init(); desc->setTextureType(MTL::TextureType2D); desc->setPixelFormat(MTL::PixelFormatRGBA8Unorm); desc->setWidth(size.x); desc->setHeight(size.y); desc->setStorageMode(m_isAppleGPU ? MTL::StorageModeShared : MTL::StorageModeManaged); desc->setUsage(MTL::TextureUsageShaderRead); MTL::Texture* texture = m_device->newTexture(desc); // TODO: do a GPU copy? texture->replaceRegion(MTL::Region(0, 0, size.x, size.y), 0, 0, tmp.data(), size.x * 4, 0); return (ImTextureID)texture; } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't generate imgui texture: {}", ex.what()); return nullptr; } } void MetalRenderer::DeleteTexture(ImTextureID id) { EnsureImGuiBackend(); ((MTL::Texture*)id)->release(); } void MetalRenderer::DeleteFontTextures() { EnsureImGuiBackend(); ImGui_ImplMetal_DestroyFontsTexture(); } void MetalRenderer::AppendOverlayDebugInfo() { ImGui::Text("--- GPU info ---"); ImGui::Text("GPU %s", m_device->name()->utf8String()); ImGui::Text("Is Apple GPU %s", (m_isAppleGPU ? "yes" : "no")); ImGui::Text("Supports framebuffer fetch %s", (m_supportsFramebufferFetch ? "yes" : "no")); ImGui::Text("Has unified memory %s", (m_hasUnifiedMemory ? "yes" : "no")); ImGui::Text("Supports Metal3 %s", (m_supportsMetal3 ? "yes" : "no")); ImGui::Text("--- Metal info ---"); ImGui::Text("Render pipeline states %zu", m_pipelineCache->GetPipelineCacheSize()); ImGui::Text("--- Metal info (per frame) ---"); ImGui::Text("Command buffers %u", m_performanceMonitor.m_commandBuffers); ImGui::Text("Render passes %u", m_performanceMonitor.m_renderPasses); ImGui::Text("Clears %u", m_performanceMonitor.m_clears); ImGui::Text("Manual vertex fetch draws %u (mesh draws: %u)", m_performanceMonitor.m_manualVertexFetchDraws, m_performanceMonitor.m_meshDraws); ImGui::Text("Triangle fans %u", m_performanceMonitor.m_triangleFans); ImGui::Text("--- Cache debug info ---"); uint32 bufferCacheHeapSize = 0; uint32 bufferCacheAllocationSize = 0; uint32 bufferCacheNumAllocations = 0; LatteBufferCache_getStats(bufferCacheHeapSize, bufferCacheAllocationSize, bufferCacheNumAllocations); ImGui::Text("Buffer"); ImGui::SameLine(60.0f); ImGui::Text("%06uKB / %06uKB Allocs: %u", (uint32)(bufferCacheAllocationSize + 1023) / 1024, ((uint32)bufferCacheHeapSize + 1023) / 1024, (uint32)bufferCacheNumAllocations); uint32 numBuffers; size_t totalSize, freeSize; m_memoryManager->GetStagingAllocator().GetStats(numBuffers, totalSize, freeSize); ImGui::Text("Staging"); ImGui::SameLine(60.0f); ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers); m_memoryManager->GetIndexAllocator().GetStats(numBuffers, totalSize, freeSize); ImGui::Text("Index"); ImGui::SameLine(60.0f); ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers); } void MetalRenderer::renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ) { // halfZ is handled in the shader m_state.m_viewport = MTL::Viewport{x, y, width, height, nearZ, farZ}; } void MetalRenderer::renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) { m_state.m_scissor = MTL::ScissorRect{(uint32)scissorX, (uint32)scissorY, (uint32)scissorWidth, (uint32)scissorHeight}; } LatteCachedFBO* MetalRenderer::rendertarget_createCachedFBO(uint64 key) { return new CachedFBOMtl(this, key); } void MetalRenderer::rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) { if (cfbo == (LatteCachedFBO*)m_state.m_activeFBO.m_fbo) m_state.m_activeFBO = {nullptr}; } void MetalRenderer::rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) { m_state.m_activeFBO = {(CachedFBOMtl*)cfbo, MetalAttachmentsInfo((CachedFBOMtl*)cfbo)}; m_state.m_fboChanged = true; } void* MetalRenderer::texture_acquireTextureUploadBuffer(uint32 size) { return m_memoryManager->AcquireTextureUploadBuffer(size); } void MetalRenderer::texture_releaseTextureUploadBuffer(uint8* mem) { m_memoryManager->ReleaseTextureUploadBuffer(mem); } TextureDecoder* MetalRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) { return GetMtlPixelFormatInfo(format, isDepth).textureDecoder; } void MetalRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) { if (hostTexture->isDepth) { texture_clearDepthSlice(hostTexture, sliceIndex, mipIndex, true, hostTexture->hasStencil, 0.0f, 0); } else { texture_clearColorSlice(hostTexture, sliceIndex, mipIndex, 0.0f, 0.0f, 0.0f, 0.0f); } } // TODO: do a cpu copy on Apple Silicon? void MetalRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) { auto textureMtl = (LatteTextureMtl*)hostTexture; uint32 offsetZ = 0; if (textureMtl->Is3DTexture()) { offsetZ = sliceIndex; sliceIndex = 0; } size_t bytesPerRow = GetMtlTextureBytesPerRow(textureMtl->format, textureMtl->isDepth, width); // No need to set bytesPerImage for 3D textures, since we always load just one slice //size_t bytesPerImage = GetMtlTextureBytesPerImage(textureMtl->GetFormat(), textureMtl->isDepth, height, bytesPerRow); //if (m_isAppleGPU) //{ // textureMtl->GetTexture()->replaceRegion(MTL::Region(0, 0, offsetZ, width, height, 1), mipIndex, sliceIndex, pixelData, bytesPerRow, 0); //} //else //{ auto blitCommandEncoder = GetBlitCommandEncoder(); // Allocate a temporary buffer auto& bufferAllocator = m_memoryManager->GetStagingAllocator(); auto allocation = bufferAllocator.AllocateBufferMemory(compressedImageSize, 1); memcpy(allocation.memPtr, pixelData, compressedImageSize); bufferAllocator.FlushReservation(allocation); // TODO: specify blit options when copying to a depth stencil texture? // Copy the data from the temporary buffer to the texture blitCommandEncoder->copyFromBuffer(allocation.mtlBuffer, allocation.bufferOffset, bytesPerRow, 0, MTL::Size(width, height, 1), textureMtl->GetTexture(), sliceIndex, mipIndex, MTL::Origin(0, 0, offsetZ)); //} } void MetalRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) { if (!FormatIsRenderable(hostTexture->format)) { cemuLog_logOnce(LogType::Force, "cannot clear color texture with format {}, because it's not renderable", hostTexture->format); return; } auto mtlTexture = static_cast<LatteTextureMtl*>(hostTexture)->GetTexture(); ClearColorTextureInternal(mtlTexture, sliceIndex, mipIndex, r, g, b, a); } void MetalRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) { clearStencil = (clearStencil && GetMtlPixelFormatInfo(hostTexture->format, true).hasStencil); if (!clearDepth && !clearStencil) { cemuLog_logOnce(LogType::Force, "skipping depth/stencil clear"); return; } auto mtlTexture = static_cast<LatteTextureMtl*>(hostTexture)->GetTexture(); NS_STACK_SCOPED MTL::RenderPassDescriptor* renderPassDescriptor = MTL::RenderPassDescriptor::alloc()->init(); if (clearDepth) { auto depthAttachment = renderPassDescriptor->depthAttachment(); depthAttachment->setTexture(mtlTexture); depthAttachment->setClearDepth(depthValue); depthAttachment->setLoadAction(MTL::LoadActionClear); depthAttachment->setStoreAction(MTL::StoreActionStore); depthAttachment->setSlice(sliceIndex); depthAttachment->setLevel(mipIndex); } if (clearStencil) { auto stencilAttachment = renderPassDescriptor->stencilAttachment(); stencilAttachment->setTexture(mtlTexture); stencilAttachment->setClearStencil(stencilValue); stencilAttachment->setLoadAction(MTL::LoadActionClear); stencilAttachment->setStoreAction(MTL::StoreActionStore); stencilAttachment->setSlice(sliceIndex); stencilAttachment->setLevel(mipIndex); } GetTemporaryRenderCommandEncoder(renderPassDescriptor); EndEncoding(); // Debug m_performanceMonitor.m_clears++; } LatteTexture* MetalRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) { return new LatteTextureMtl(this, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); } void MetalRenderer::texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) { m_state.m_textures[textureUnit] = static_cast<LatteTextureViewMtl*>(textureView); } void MetalRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth_) { // Source size seems to apply to the destination texture as well, therefore we need to adjust it when block size doesn't match Uvec2 srcBlockTexelSize = GetMtlPixelFormatInfo(src->format, src->isDepth).blockTexelSize; Uvec2 dstBlockTexelSize = GetMtlPixelFormatInfo(dst->format, dst->isDepth).blockTexelSize; if (srcBlockTexelSize.x != dstBlockTexelSize.x || srcBlockTexelSize.y != dstBlockTexelSize.y) { uint32 multX = (srcBlockTexelSize.x > dstBlockTexelSize.x ? srcBlockTexelSize.x / dstBlockTexelSize.x : dstBlockTexelSize.x / srcBlockTexelSize.x); effectiveCopyWidth *= multX; uint32 multY = (srcBlockTexelSize.y > dstBlockTexelSize.y ? srcBlockTexelSize.y / dstBlockTexelSize.y : dstBlockTexelSize.y / srcBlockTexelSize.y); effectiveCopyHeight *= multY; } auto blitCommandEncoder = GetBlitCommandEncoder(); auto mtlSrc = static_cast<LatteTextureMtl*>(src)->GetTexture(); auto mtlDst = static_cast<LatteTextureMtl*>(dst)->GetTexture(); uint32 srcBaseLayer = 0; uint32 dstBaseLayer = 0; uint32 srcOffsetZ = 0; uint32 dstOffsetZ = 0; uint32 srcLayerCount = 1; uint32 dstLayerCount = 1; uint32 srcDepth = 1; uint32 dstDepth = 1; if (src->Is3DTexture()) { srcOffsetZ = srcSlice; srcDepth = srcDepth_; } else { srcBaseLayer = srcSlice; srcLayerCount = srcDepth_; } if (dst->Is3DTexture()) { dstOffsetZ = dstSlice; dstDepth = srcDepth_; } else { dstBaseLayer = dstSlice; dstLayerCount = srcDepth_; } // If copying whole textures, we can do a more efficient copy if (effectiveSrcX == 0 && effectiveSrcY == 0 && effectiveDstX == 0 && effectiveDstY == 0 && srcOffsetZ == 0 && dstOffsetZ == 0 && effectiveCopyWidth == src->GetMipWidth(srcMip) && effectiveCopyHeight == src->GetMipHeight(srcMip) && srcDepth == src->GetMipDepth(srcMip) && effectiveCopyWidth == dst->GetMipWidth(dstMip) && effectiveCopyHeight == dst->GetMipHeight(dstMip) && dstDepth == dst->GetMipDepth(dstMip) && srcLayerCount == dstLayerCount) { blitCommandEncoder->copyFromTexture(mtlSrc, srcBaseLayer, srcMip, mtlDst, dstBaseLayer, dstMip, srcLayerCount, 1); } else { if (srcLayerCount == dstLayerCount) { for (uint32 i = 0; i < srcLayerCount; i++) { blitCommandEncoder->copyFromTexture(mtlSrc, srcBaseLayer + i, srcMip, MTL::Origin(effectiveSrcX, effectiveSrcY, srcOffsetZ), MTL::Size(effectiveCopyWidth, effectiveCopyHeight, srcDepth), mtlDst, dstBaseLayer + i, dstMip, MTL::Origin(effectiveDstX, effectiveDstY, dstOffsetZ)); } } else { for (uint32 i = 0; i < std::max(srcLayerCount, dstLayerCount); i++) { if (srcLayerCount == 1) srcOffsetZ++; else srcSlice++; if (dstLayerCount == 1) dstOffsetZ++; else dstSlice++; blitCommandEncoder->copyFromTexture(mtlSrc, srcBaseLayer, srcMip, MTL::Origin(effectiveSrcX, effectiveSrcY, srcOffsetZ), MTL::Size(effectiveCopyWidth, effectiveCopyHeight, 1), mtlDst, dstBaseLayer, dstMip, MTL::Origin(effectiveDstX, effectiveDstY, dstOffsetZ)); } } } } LatteTextureReadbackInfo* MetalRenderer::texture_createReadback(LatteTextureView* textureView) { size_t uploadSize = static_cast<LatteTextureMtl*>(textureView->baseTexture)->GetTexture()->allocatedSize(); if ((m_readbackBufferWriteOffset + uploadSize) > TEXTURE_READBACK_SIZE) { m_readbackBufferWriteOffset = 0; } auto* result = new LatteTextureReadbackInfoMtl(this, textureView, m_readbackBufferWriteOffset); m_readbackBufferWriteOffset += uploadSize; return result; } void MetalRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) { // scale copy size to effective size sint32 effectiveCopyWidth = width; sint32 effectiveCopyHeight = height; LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); //sint32 sourceEffectiveWidth, sourceEffectiveHeight; //sourceTexture->GetEffectiveSize(sourceEffectiveWidth, sourceEffectiveHeight, srcMip); texture_copyImageSubData(sourceTexture, srcMip, 0, 0, srcSlice, destinationTexture, dstMip, 0, 0, dstSlice, effectiveCopyWidth, effectiveCopyHeight, 1); } void MetalRenderer::bufferCache_init(const sint32 bufferSize) { m_memoryManager->InitBufferCache(bufferSize); } void MetalRenderer::bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) { m_memoryManager->UploadToBufferCache(buffer, bufferOffset, size); } void MetalRenderer::bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) { m_memoryManager->CopyBufferCache(srcOffset, dstOffset, size); } void MetalRenderer::bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) { if (m_memoryManager->UseHostMemoryForCache()) dstOffset -= m_memoryManager->GetImportedMemBaseAddress(); CopyBufferToBuffer(GetXfbRingBuffer(), srcOffset, m_memoryManager->GetBufferCache(), dstOffset, size, MTL::RenderStageVertex | MTL::RenderStageMesh, ALL_MTL_RENDER_STAGES); } void MetalRenderer::buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) { cemu_assert_debug(!m_memoryManager->UseHostMemoryForCache()); cemu_assert_debug(bufferIndex < LATTE_MAX_VERTEX_BUFFERS); m_state.m_vertexBufferOffsets[bufferIndex] = offset; } void MetalRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) { cemu_assert_debug(!m_memoryManager->UseHostMemoryForCache()); m_state.m_uniformBufferOffsets[GetMtlGeneralShaderType(shaderType)][bufferIndex] = offset; } RendererShader* MetalRenderer::shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) { return new RendererShaderMtl(this, type, baseHash, auxHash, isGameShader, isGfxPackShader, source); } void MetalRenderer::streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) { m_state.m_streamoutState.buffers[bufferIndex].enabled = true; m_state.m_streamoutState.buffers[bufferIndex].ringBufferOffset = ringBufferOffset; } void MetalRenderer::streamout_begin() { // Do nothing } void MetalRenderer::streamout_rendererFinishDrawcall() { // Do nothing } void MetalRenderer::draw_beginSequence() { m_state.m_skipDrawSequence = false; bool streamoutEnable = LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0; // update shader state LatteSHRC_UpdateActiveShaders(); if (LatteGPUState.activeShaderHasError) { cemuLog_logOnce(LogType::Force, "Skipping drawcalls due to shader error\n"); m_state.m_skipDrawSequence = true; cemu_assert_debug(false); return; } // update render target and texture state LatteGPUState.requiresTextureBarrier = false; while (true) { LatteGPUState.repeatTextureInitialization = false; if (!LatteMRT::UpdateCurrentFBO()) { cemuLog_logOnce(LogType::Force, "Rendertarget invalid\n"); m_state.m_skipDrawSequence = true; return; // no render target } if (!hasValidFramebufferAttached && !streamoutEnable) { cemuLog_logOnce(LogType::Force, "Drawcall with no color buffer or depth buffer attached\n"); m_state.m_skipDrawSequence = true; return; // no render target } LatteTexture_updateTextures(); if (!LatteGPUState.repeatTextureInitialization) break; } // apply render target LatteMRT::ApplyCurrentState(); // viewport and scissor box LatteRenderTarget_updateViewport(); LatteRenderTarget_updateScissorBox(); if (!LatteGPUState.contextNew.IsRasterizationEnabled() && !streamoutEnable) m_state.m_skipDrawSequence = true; } void MetalRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) { if (m_state.m_skipDrawSequence) { LatteGPUState.drawCallCounter++; return; } // fast clear color as depth if (LatteGPUState.contextNew.GetSpecialStateValues()[8] != 0) { LatteDraw_handleSpecialState8_clearAsDepth(); LatteGPUState.drawCallCounter++; return; } else if (LatteGPUState.contextNew.GetSpecialStateValues()[5] != 0) { draw_handleSpecialState5(); LatteGPUState.drawCallCounter++; return; } auto& encoderState = m_state.m_encoderState; // Shaders LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); const auto fetchShader = LatteSHRC_GetActiveFetchShader(); /* bool neverSkipAccurateBarrier = false; // "Accurate barriers" is usually enabled globally but since the CPU cost is substantial we allow users to disable it (debug -> 'Accurate barriers' option) // We always force accurate barriers for known problematic shaders if (pixelShader) { if (pixelShader->baseHash == 0x6f6f6e7b9aae57af && pixelShader->auxHash == 0x00078787f9249249) // BotW lava neverSkipAccurateBarrier = true; if (pixelShader->baseHash == 0x4c0bd596e3aef4a6 && pixelShader->auxHash == 0x003c3c3fc9269249) // BotW foam layer for water on the bottom of waterfalls neverSkipAccurateBarrier = true; } // Check if we need to end the render pass if (!m_state.m_isFirstDrawInRenderPass && (GetConfig().vk_accurate_barriers || neverSkipAccurateBarrier)) { // Fragment shader is most likely to require a render pass flush, so check for it first bool endRenderPass = CheckIfRenderPassNeedsFlush(pixelShader); if (!endRenderPass) endRenderPass = CheckIfRenderPassNeedsFlush(vertexShader); if (!endRenderPass && geometryShader) endRenderPass = CheckIfRenderPassNeedsFlush(geometryShader); if (endRenderPass) { EndEncoding(); // TODO: only log in debug? cemuLog_logOnce(LogType::Force, "Ending render pass due to render target self-dependency\n"); } } */ // Primitive type const LattePrimitiveMode primitiveMode = LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); auto mtlPrimitiveType = GetMtlPrimitiveType(primitiveMode); bool usesGeometryShader = UseGeometryShader(LatteGPUState.contextNew, geometryShader != nullptr); if (usesGeometryShader && !m_supportsMeshShaders) return; bool fetchVertexManually = (usesGeometryShader || fetchShader->mtlFetchVertexManually); // Index buffer Renderer::INDEX_TYPE hostIndexType; uint32 hostIndexCount; uint32 indexMin = 0; uint32 indexMax = 0; Renderer::IndexAllocation indexAllocation; LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation); auto indexAllocationMtl = static_cast<MetalSynchronizedHeapAllocator::AllocatorReservation*>(indexAllocation.rendererInternal); // Buffer cache if (m_memoryManager->UseHostMemoryForCache()) { // direct memory access (Wii U memory space imported as a buffer), update buffer bindings draw_updateVertexBuffersDirectAccess(); if (vertexShader) draw_updateUniformBuffersDirectAccess(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START); if (geometryShader) draw_updateUniformBuffersDirectAccess(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START); if (pixelShader) draw_updateUniformBuffersDirectAccess(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START); } else { // synchronize vertex and uniform cache and update buffer bindings // We need to call this before getting the render command encoder, since it can cause buffer copies LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount); } // Render pass auto renderCommandEncoder = GetRenderCommandEncoder(); // Render pipeline state PipelineObject* pipelineObj = m_pipelineCache->GetRenderPipelineState(fetchShader, vertexShader, geometryShader, pixelShader, m_state.m_lastUsedFBO.m_attachmentsInfo, m_state.m_activeFBO.m_attachmentsInfo, m_state.m_activeFBO.m_fbo->m_size, count, LatteGPUState.contextNew); if (!pipelineObj->m_pipeline) return; if (pipelineObj->m_pipeline != encoderState.m_renderPipelineState) { renderCommandEncoder->setRenderPipelineState(pipelineObj->m_pipeline); encoderState.m_renderPipelineState = pipelineObj->m_pipeline; } // Depth stencil state // Disable depth write when there is no depth attachment auto& depthControl = LatteGPUState.contextNew.DB_DEPTH_CONTROL; bool depthWriteEnable = depthControl.get_Z_WRITE_ENABLE(); if (!m_state.m_activeFBO.m_fbo->depthBuffer.texture) depthControl.set_Z_WRITE_ENABLE(false); MTL::DepthStencilState* depthStencilState = m_depthStencilCache->GetDepthStencilState(LatteGPUState.contextNew); if (depthStencilState != encoderState.m_depthStencilState) { renderCommandEncoder->setDepthStencilState(depthStencilState); encoderState.m_depthStencilState = depthStencilState; } // Restore the original depth write state depthControl.set_Z_WRITE_ENABLE(depthWriteEnable); // Stencil reference bool stencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ENABLE(); if (stencilEnable) { bool backStencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_BACK_STENCIL_ENABLE(); uint32 stencilRefFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILREF_F(); uint32 stencilRefBack; if (backStencilEnable) stencilRefBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILREF_B(); else stencilRefBack = stencilRefFront; if (stencilRefFront != encoderState.m_stencilRefFront || stencilRefBack != encoderState.m_stencilRefBack) { renderCommandEncoder->setStencilReferenceValues(stencilRefFront, stencilRefBack); encoderState.m_stencilRefFront = stencilRefFront; encoderState.m_stencilRefBack = stencilRefBack; } } // Blend color uint32* blendColorConstantU32 = LatteGPUState.contextRegister + Latte::REGADDR::CB_BLEND_RED; if (blendColorConstantU32[0] != encoderState.m_blendColor[0] || blendColorConstantU32[1] != encoderState.m_blendColor[1] || blendColorConstantU32[2] != encoderState.m_blendColor[2] || blendColorConstantU32[3] != encoderState.m_blendColor[3]) { float* blendColorConstant = (float*)LatteGPUState.contextRegister + Latte::REGADDR::CB_BLEND_RED; renderCommandEncoder->setBlendColor(blendColorConstant[0], blendColorConstant[1], blendColorConstant[2], blendColorConstant[3]); encoderState.m_blendColor[0] = blendColorConstantU32[0]; encoderState.m_blendColor[1] = blendColorConstantU32[1]; encoderState.m_blendColor[2] = blendColorConstantU32[2]; encoderState.m_blendColor[3] = blendColorConstantU32[3]; } // polygon control const auto& polygonControlReg = LatteGPUState.contextNew.PA_SU_SC_MODE_CNTL; const auto frontFace = polygonControlReg.get_FRONT_FACE(); uint32 cullFront = polygonControlReg.get_CULL_FRONT(); uint32 cullBack = polygonControlReg.get_CULL_BACK(); uint32 polyOffsetFrontEnable = polygonControlReg.get_OFFSET_FRONT_ENABLED(); if (polyOffsetFrontEnable) { uint32 frontScaleU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.getRawValue(); uint32 frontOffsetU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_OFFSET.getRawValue(); uint32 offsetClampU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.getRawValue(); if (frontOffsetU32 != encoderState.m_depthBias || frontScaleU32 != encoderState.m_depthSlope || offsetClampU32 != encoderState.m_depthClamp) { float frontScale = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.get_SCALE(); float frontOffset = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_OFFSET.get_OFFSET(); float offsetClamp = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.get_CLAMP(); frontScale /= 16.0f; renderCommandEncoder->setDepthBias(frontOffset, frontScale, offsetClamp); encoderState.m_depthBias = frontOffsetU32; encoderState.m_depthSlope = frontScaleU32; encoderState.m_depthClamp = offsetClampU32; } } else { if (0 != encoderState.m_depthBias || 0 != encoderState.m_depthSlope || 0 != encoderState.m_depthClamp) { renderCommandEncoder->setDepthBias(0.0f, 0.0f, 0.0f); encoderState.m_depthBias = 0; encoderState.m_depthSlope = 0; encoderState.m_depthClamp = 0; } } // Depth clip mode cemu_assert_debug(LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_NEAR_DISABLE() == LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_FAR_DISABLE()); // near or far clipping can be disabled individually bool zClipEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_FAR_DISABLE() == false; if (zClipEnable != encoderState.m_depthClipEnable) { renderCommandEncoder->setDepthClipMode(zClipEnable ? MTL::DepthClipModeClip : MTL::DepthClipModeClamp); encoderState.m_depthClipEnable = zClipEnable; } // Visibility result mode if (m_occlusionQuery.m_active) { auto mode = (m_occlusionQuery.m_currentIndex == INVALID_UINT32 ? MTL::VisibilityResultModeDisabled : MTL::VisibilityResultModeCounting); renderCommandEncoder->setVisibilityResultMode(mode, m_occlusionQuery.m_currentIndex * sizeof(uint64)); } // todo - how does culling behave with rects? // right now we just assume that their winding is always CW if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS) { if (frontFace == Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CW) cullFront = cullBack; else cullBack = cullFront; } // Cull mode // Cull front and back is handled by disabling rasterization if (!(cullFront && cullBack)) { MTL::CullMode cullMode; if (cullFront) cullMode = MTL::CullModeFront; else if (cullBack) cullMode = MTL::CullModeBack; else cullMode = MTL::CullModeNone; if (cullMode != encoderState.m_cullMode) { renderCommandEncoder->setCullMode(cullMode); encoderState.m_cullMode = cullMode; } } // Front face MTL::Winding frontFaceWinding; if (frontFace == Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CCW) frontFaceWinding = MTL::WindingCounterClockwise; else frontFaceWinding = MTL::WindingClockwise; if (frontFaceWinding != encoderState.m_frontFaceWinding) { renderCommandEncoder->setFrontFacingWinding(frontFaceWinding); encoderState.m_frontFaceWinding = frontFaceWinding; } // Viewport if (m_state.m_viewport.originX != encoderState.m_viewport.originX || m_state.m_viewport.originY != encoderState.m_viewport.originY || m_state.m_viewport.width != encoderState.m_viewport.width || m_state.m_viewport.height != encoderState.m_viewport.height || m_state.m_viewport.znear != encoderState.m_viewport.znear || m_state.m_viewport.zfar != encoderState.m_viewport.zfar) { renderCommandEncoder->setViewport(m_state.m_viewport); encoderState.m_viewport = m_state.m_viewport; } // Scissor if (m_state.m_scissor.x != encoderState.m_scissor.x || m_state.m_scissor.y != encoderState.m_scissor.y || m_state.m_scissor.width != encoderState.m_scissor.width || m_state.m_scissor.height != encoderState.m_scissor.height) { encoderState.m_scissor = m_state.m_scissor; // TODO: clamp scissor to render target dimensions? //scissor.width = ; //scissor.height = ; renderCommandEncoder->setScissorRect(encoderState.m_scissor); } // Resources // Vertex buffers for (uint8 i = 0; i < MAX_MTL_VERTEX_BUFFERS; i++) { size_t offset = m_state.m_vertexBufferOffsets[i]; if (offset != INVALID_OFFSET) { // Bind SetBuffer(renderCommandEncoder, GetMtlShaderType(vertexShader->shaderType, usesGeometryShader), m_memoryManager->GetBufferCache(), offset, GET_MTL_VERTEX_BUFFER_INDEX(i)); } } // Prepare streamout m_state.m_streamoutState.verticesPerInstance = count; LatteStreamout_PrepareDrawcall(count, instanceCount); // Uniform buffers, textures and samplers BindStageResources(renderCommandEncoder, vertexShader, usesGeometryShader); if (usesGeometryShader && geometryShader) BindStageResources(renderCommandEncoder, geometryShader, usesGeometryShader); BindStageResources(renderCommandEncoder, pixelShader, usesGeometryShader); // Draw if (usesGeometryShader) { if (hostIndexType != INDEX_TYPE::NONE) SetBuffer(renderCommandEncoder, METAL_SHADER_TYPE_OBJECT, indexAllocationMtl->mtlBuffer, indexAllocationMtl->bufferOffset, vertexShader->resourceMapping.indexBufferBinding); uint8 hostIndexTypeU8 = (uint8)hostIndexType; renderCommandEncoder->setObjectBytes(&hostIndexTypeU8, sizeof(hostIndexTypeU8), vertexShader->resourceMapping.indexTypeBinding); encoderState.m_buffers[METAL_SHADER_TYPE_OBJECT][vertexShader->resourceMapping.indexTypeBinding] = {nullptr}; uint32 verticesPerPrimitive = GetVerticesPerPrimitive(primitiveMode); uint32 threadgroupCount = count * instanceCount; if (PrimitiveRequiresConnection(primitiveMode)) threadgroupCount -= verticesPerPrimitive - 1; else threadgroupCount /= verticesPerPrimitive; renderCommandEncoder->drawMeshThreadgroups(MTL::Size(threadgroupCount, 1, 1), MTL::Size(verticesPerPrimitive, 1, 1), MTL::Size(1, 1, 1)); } else { if (hostIndexType != INDEX_TYPE::NONE) { auto mtlIndexType = GetMtlIndexType(hostIndexType); renderCommandEncoder->drawIndexedPrimitives(mtlPrimitiveType, hostIndexCount, mtlIndexType, indexAllocationMtl->mtlBuffer, indexAllocationMtl->bufferOffset, instanceCount, baseVertex, baseInstance); } else { renderCommandEncoder->drawPrimitives(mtlPrimitiveType, baseVertex, count, instanceCount, baseInstance); } } m_state.m_isFirstDrawInRenderPass = false; // Occlusion queries if (m_occlusionQuery.m_active) m_occlusionQuery.m_currentIndex = (m_occlusionQuery.m_currentIndex + 1) % OCCLUSION_QUERY_POOL_SIZE; // Streamout LatteStreamout_FinishDrawcall(m_memoryManager->UseHostMemoryForCache()); // Debug if (fetchVertexManually) m_performanceMonitor.m_manualVertexFetchDraws++; if (usesGeometryShader) m_performanceMonitor.m_meshDraws++; if (primitiveMode == LattePrimitiveMode::TRIANGLE_FAN) m_performanceMonitor.m_triangleFans++; LatteGPUState.drawCallCounter++; } void MetalRenderer::draw_endSequence() { LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); // post-drawcall logic if (pixelShader) LatteRenderTarget_trackUpdates(); bool hasReadback = LatteTextureReadback_Update(); m_recordedDrawcalls++; // The number of draw calls needs to twice as big, since we are interrupting the render pass // TODO: ucomment? if (m_recordedDrawcalls >= m_commitTreshold * 2/* || hasReadback*/) { CommitCommandBuffer(); // TODO: where should this be called? LatteTextureReadback_UpdateFinishedTransfers(false); } } void MetalRenderer::draw_updateVertexBuffersDirectAccess() { LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); if (!parsedFetchShader) return; for (auto& bufferGroup : parsedFetchShader->bufferGroups) { uint32 bufferIndex = bufferGroup.attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0]; if (bufferAddress == MPTR_NULL) [[unlikely]] bufferAddress = m_memoryManager->GetImportedMemBaseAddress(); m_state.m_vertexBufferOffsets[bufferIndex] = bufferAddress - m_memoryManager->GetImportedMemBaseAddress(); } } void MetalRenderer::draw_updateUniformBuffersDirectAccess(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset) { if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { for (const auto& buf : shader->list_quickBufferList) { sint32 i = buf.index; MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0]; uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1; if (physicalAddr == MPTR_NULL) [[unlikely]] { cemu_assert_unimplemented(); continue; } uniformSize = std::min<uint32>(uniformSize, buf.size); cemu_assert_debug(physicalAddr < 0x50000000); uint32 bufferIndex = i; cemu_assert_debug(bufferIndex < 16); m_state.m_uniformBufferOffsets[GetMtlGeneralShaderType(shader->shaderType)][bufferIndex] = physicalAddr - m_memoryManager->GetImportedMemBaseAddress(); } } } void MetalRenderer::draw_handleSpecialState5() { LatteMRT::UpdateCurrentFBO(); LatteRenderTarget_updateViewport(); LatteTextureView* colorBuffer = LatteMRT::GetColorAttachment(0); LatteTextureView* depthBuffer = LatteMRT::GetDepthAttachment(); auto colorTextureMtl = static_cast<LatteTextureViewMtl*>(colorBuffer); auto depthTextureMtl = static_cast<LatteTextureViewMtl*>(depthBuffer); sint32 vpWidth, vpHeight; LatteMRT::GetVirtualViewportDimensions(vpWidth, vpHeight); // Get the pipeline MTL::PixelFormat colorPixelFormat = colorTextureMtl->GetRGBAView()->pixelFormat(); auto& pipeline = m_copyDepthToColorPipelines[colorPixelFormat]; if (!pipeline) { m_copyDepthToColorDesc->colorAttachments()->object(0)->setPixelFormat(colorPixelFormat); NS::Error* error = nullptr; pipeline = m_device->newRenderPipelineState(m_copyDepthToColorDesc, &error); if (error) { cemuLog_log(LogType::Force, "failed to create copy depth to color pipeline (error: {})", error->localizedDescription()->utf8String()); } } // Sadly, we need to end encoding to ensure that the depth data is up-to-date EndEncoding(); // Copy depth to color auto renderCommandEncoder = GetRenderCommandEncoder(); auto& encoderState = m_state.m_encoderState; renderCommandEncoder->setRenderPipelineState(pipeline); // TODO: make a helper function for this encoderState.m_renderPipelineState = pipeline; SetTexture(renderCommandEncoder, METAL_SHADER_TYPE_FRAGMENT, depthTextureMtl->GetRGBAView(), GET_HELPER_TEXTURE_BINDING(0)); // TODO: make a helper function for this renderCommandEncoder->setFragmentBytes(&vpWidth, sizeof(sint32), GET_HELPER_BUFFER_BINDING(0)); encoderState.m_buffers[METAL_SHADER_TYPE_FRAGMENT][GET_HELPER_BUFFER_BINDING(0)] = {nullptr}; renderCommandEncoder->drawPrimitives(MTL::PrimitiveTypeTriangle, NS::UInteger(0), NS::UInteger(3)); } Renderer::IndexAllocation MetalRenderer::indexData_reserveIndexMemory(uint32 size) { auto allocation = m_memoryManager->GetIndexAllocator().AllocateBufferMemory(size, 128); return {allocation->memPtr, allocation}; } void MetalRenderer::indexData_releaseIndexMemory(IndexAllocation& allocation) { m_memoryManager->GetIndexAllocator().FreeReservation(static_cast<MetalSynchronizedHeapAllocator::AllocatorReservation*>(allocation.rendererInternal)); } void MetalRenderer::indexData_uploadIndexMemory(IndexAllocation& allocation) { m_memoryManager->GetIndexAllocator().FlushReservation(static_cast<MetalSynchronizedHeapAllocator::AllocatorReservation*>(allocation.rendererInternal)); } LatteQueryObject* MetalRenderer::occlusionQuery_create() { return new LatteQueryObjectMtl(this); } void MetalRenderer::occlusionQuery_destroy(LatteQueryObject* queryObj) { auto queryObjMtl = static_cast<LatteQueryObjectMtl*>(queryObj); delete queryObjMtl; } void MetalRenderer::occlusionQuery_flush() { if (m_occlusionQuery.m_lastCommandBuffer) m_occlusionQuery.m_lastCommandBuffer->waitUntilCompleted(); } void MetalRenderer::occlusionQuery_updateState() { ProcessFinishedCommandBuffers(); } void MetalRenderer::SetBuffer(MTL::RenderCommandEncoder* renderCommandEncoder, MetalShaderType shaderType, MTL::Buffer* buffer, size_t offset, uint32 index) { auto& boundBuffer = m_state.m_encoderState.m_buffers[shaderType][index]; if (buffer == boundBuffer.m_buffer && offset == boundBuffer.m_offset) return; if (buffer == boundBuffer.m_buffer) { // Update just the offset boundBuffer.m_offset = offset; switch (shaderType) { case METAL_SHADER_TYPE_VERTEX: renderCommandEncoder->setVertexBufferOffset(offset, index); break; case METAL_SHADER_TYPE_OBJECT: renderCommandEncoder->setObjectBufferOffset(offset, index); break; case METAL_SHADER_TYPE_MESH: renderCommandEncoder->setMeshBufferOffset(offset, index); break; case METAL_SHADER_TYPE_FRAGMENT: renderCommandEncoder->setFragmentBufferOffset(offset, index); break; } return; } boundBuffer = {buffer, offset}; switch (shaderType) { case METAL_SHADER_TYPE_VERTEX: renderCommandEncoder->setVertexBuffer(buffer, offset, index); break; case METAL_SHADER_TYPE_OBJECT: renderCommandEncoder->setObjectBuffer(buffer, offset, index); break; case METAL_SHADER_TYPE_MESH: renderCommandEncoder->setMeshBuffer(buffer, offset, index); break; case METAL_SHADER_TYPE_FRAGMENT: renderCommandEncoder->setFragmentBuffer(buffer, offset, index); break; } } void MetalRenderer::SetTexture(MTL::RenderCommandEncoder* renderCommandEncoder, MetalShaderType shaderType, MTL::Texture* texture, uint32 index) { auto& boundTexture = m_state.m_encoderState.m_textures[shaderType][index]; if (texture == boundTexture) return; boundTexture = texture; switch (shaderType) { case METAL_SHADER_TYPE_VERTEX: renderCommandEncoder->setVertexTexture(texture, index); break; case METAL_SHADER_TYPE_OBJECT: renderCommandEncoder->setObjectTexture(texture, index); break; case METAL_SHADER_TYPE_MESH: renderCommandEncoder->setMeshTexture(texture, index); break; case METAL_SHADER_TYPE_FRAGMENT: renderCommandEncoder->setFragmentTexture(texture, index); break; } } void MetalRenderer::SetSamplerState(MTL::RenderCommandEncoder* renderCommandEncoder, MetalShaderType shaderType, MTL::SamplerState* samplerState, uint32 index) { auto& boundSamplerState = m_state.m_encoderState.m_samplers[shaderType][index]; if (samplerState == boundSamplerState) return; boundSamplerState = samplerState; switch (shaderType) { case METAL_SHADER_TYPE_VERTEX: renderCommandEncoder->setVertexSamplerState(samplerState, index); break; case METAL_SHADER_TYPE_OBJECT: renderCommandEncoder->setObjectSamplerState(samplerState, index); break; case METAL_SHADER_TYPE_MESH: renderCommandEncoder->setMeshSamplerState(samplerState, index); break; case METAL_SHADER_TYPE_FRAGMENT: renderCommandEncoder->setFragmentSamplerState(samplerState, index); break; } } MTL::CommandBuffer* MetalRenderer::GetCommandBuffer() { bool needsNewCommandBuffer = (!m_currentCommandBuffer.m_commandBuffer || m_currentCommandBuffer.m_commited); if (needsNewCommandBuffer) { // Debug //m_commandQueue->insertDebugCaptureBoundary(); auto pool = NS::AutoreleasePool::alloc()->init(); MTL::CommandBuffer* mtlCommandBuffer = m_commandQueue->commandBuffer()->retain(); pool->release(); m_currentCommandBuffer = {mtlCommandBuffer}; // Wait for the previous command buffer if (m_eventValue != -1) mtlCommandBuffer->encodeWait(m_event, m_eventValue); m_recordedDrawcalls = 0; m_commitTreshold = m_defaultCommitTreshlod; // Debug m_performanceMonitor.m_commandBuffers++; return mtlCommandBuffer; } else { return m_currentCommandBuffer.m_commandBuffer; } } MTL::RenderCommandEncoder* MetalRenderer::GetTemporaryRenderCommandEncoder(MTL::RenderPassDescriptor* renderPassDescriptor) { EndEncoding(); auto commandBuffer = GetCommandBuffer(); auto pool = NS::AutoreleasePool::alloc()->init(); auto renderCommandEncoder = commandBuffer->renderCommandEncoder(renderPassDescriptor)->retain(); pool->release(); #ifdef CEMU_DEBUG_ASSERT renderCommandEncoder->setLabel(GetLabel("Temporary render command encoder", renderCommandEncoder)); #endif m_commandEncoder = renderCommandEncoder; m_encoderType = MetalEncoderType::Render; // Debug m_performanceMonitor.m_renderPasses++; return renderCommandEncoder; } // Some render passes clear the attachments, forceRecreate is supposed to be used in those cases MTL::RenderCommandEncoder* MetalRenderer::GetRenderCommandEncoder(bool forceRecreate) { bool fboChanged = m_state.m_fboChanged; m_state.m_fboChanged = false; // Check if we need to begin a new render pass if (m_commandEncoder) { if (!forceRecreate) { if (m_encoderType == MetalEncoderType::Render) { bool needsNewRenderPass = false; if (fboChanged) { needsNewRenderPass = (m_state.m_lastUsedFBO.m_fbo == nullptr); if (!needsNewRenderPass) { for (uint8 i = 0; i < 8; i++) { if (m_state.m_activeFBO.m_fbo->colorBuffer[i].texture && m_state.m_activeFBO.m_fbo->colorBuffer[i].texture != m_state.m_lastUsedFBO.m_fbo->colorBuffer[i].texture) { needsNewRenderPass = true; break; } } } if (!needsNewRenderPass) { if (m_state.m_activeFBO.m_fbo->depthBuffer.texture && (m_state.m_activeFBO.m_fbo->depthBuffer.texture != m_state.m_lastUsedFBO.m_fbo->depthBuffer.texture || ( m_state.m_activeFBO.m_fbo->depthBuffer.hasStencil && !m_state.m_lastUsedFBO.m_fbo->depthBuffer.hasStencil))) { needsNewRenderPass = true; } } } if (!needsNewRenderPass) { return (MTL::RenderCommandEncoder*)m_commandEncoder; } } } EndEncoding(); } auto commandBuffer = GetCommandBuffer(); auto pool = NS::AutoreleasePool::alloc()->init(); auto renderCommandEncoder = commandBuffer->renderCommandEncoder(m_state.m_activeFBO.m_fbo->GetRenderPassDescriptor())->retain(); pool->release(); #ifdef CEMU_DEBUG_ASSERT renderCommandEncoder->setLabel(GetLabel("Render command encoder", renderCommandEncoder)); #endif m_commandEncoder = renderCommandEncoder; m_encoderType = MetalEncoderType::Render; // Update state m_state.m_lastUsedFBO = m_state.m_activeFBO; m_state.m_isFirstDrawInRenderPass = true; ResetEncoderState(); // Debug m_performanceMonitor.m_renderPasses++; return renderCommandEncoder; } MTL::ComputeCommandEncoder* MetalRenderer::GetComputeCommandEncoder() { if (m_commandEncoder) { if (m_encoderType == MetalEncoderType::Compute) { return (MTL::ComputeCommandEncoder*)m_commandEncoder; } EndEncoding(); } auto commandBuffer = GetCommandBuffer(); auto pool = NS::AutoreleasePool::alloc()->init(); auto computeCommandEncoder = commandBuffer->computeCommandEncoder()->retain(); pool->release(); m_commandEncoder = computeCommandEncoder; m_encoderType = MetalEncoderType::Compute; ResetEncoderState(); return computeCommandEncoder; } MTL::BlitCommandEncoder* MetalRenderer::GetBlitCommandEncoder() { if (m_commandEncoder) { if (m_encoderType == MetalEncoderType::Blit) { return (MTL::BlitCommandEncoder*)m_commandEncoder; } EndEncoding(); } auto commandBuffer = GetCommandBuffer(); auto pool = NS::AutoreleasePool::alloc()->init(); auto blitCommandEncoder = commandBuffer->blitCommandEncoder()->retain(); pool->release(); m_commandEncoder = blitCommandEncoder; m_encoderType = MetalEncoderType::Blit; ResetEncoderState(); return blitCommandEncoder; } void MetalRenderer::EndEncoding() { if (m_commandEncoder) { m_commandEncoder->endEncoding(); m_commandEncoder->release(); m_commandEncoder = nullptr; m_encoderType = MetalEncoderType::None; // Commit the command buffer if enough draw calls have been recorded if (m_recordedDrawcalls >= m_commitTreshold) CommitCommandBuffer(); } } void MetalRenderer::CommitCommandBuffer() { if (!m_currentCommandBuffer.m_commandBuffer) return; EndEncoding(); ProcessFinishedCommandBuffers(); // Commit the command buffer if (!m_currentCommandBuffer.m_commited) { // Handled differently, since it seems like Metal doesn't always call the completion handler //commandBuffer.m_commandBuffer->addCompletedHandler(^(MTL::CommandBuffer*) { // m_memoryManager->GetTemporaryBufferAllocator().CommandBufferFinished(commandBuffer.m_commandBuffer); //}); // Signal event m_eventValue = (m_eventValue + 1) % EVENT_VALUE_WRAP; auto mtlCommandBuffer = m_currentCommandBuffer.m_commandBuffer; mtlCommandBuffer->encodeSignalEvent(m_event, m_eventValue); mtlCommandBuffer->commit(); m_currentCommandBuffer.m_commited = true; m_executingCommandBuffers.push_back(mtlCommandBuffer); // Debug //m_commandQueue->insertDebugCaptureBoundary(); } } void MetalRenderer::ProcessFinishedCommandBuffers() { // Check for finished command buffers for (auto it = m_executingCommandBuffers.begin(); it != m_executingCommandBuffers.end();) { auto commandBuffer = *it; if (CommandBufferCompleted(commandBuffer)) { m_memoryManager->CleanupBuffers(commandBuffer); commandBuffer->release(); it = m_executingCommandBuffers.erase(it); } else { ++it; } } } bool MetalRenderer::AcquireDrawable(bool mainWindow) { auto& layer = GetLayer(mainWindow); if (!layer.GetLayer()) return false; const bool latteBufferUsesSRGB = mainWindow ? LatteGPUState.tvBufferUsesSRGB : LatteGPUState.drcBufferUsesSRGB; if (latteBufferUsesSRGB != m_state.m_usesSRGB) { layer.GetLayer()->setPixelFormat(latteBufferUsesSRGB ? MTL::PixelFormatBGRA8Unorm_sRGB : MTL::PixelFormatBGRA8Unorm); m_state.m_usesSRGB = latteBufferUsesSRGB; } return layer.AcquireDrawable(); } /* bool MetalRenderer::CheckIfRenderPassNeedsFlush(LatteDecompilerShader* shader) { sint32 textureCount = shader->resourceMapping.getTextureCount(); for (int i = 0; i < textureCount; ++i) { const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i); auto hostTextureUnit = relative_textureUnit; auto textureDim = shader->textureUnitDim[relative_textureUnit]; // Texture is accessed as a framebuffer fetch, therefore there is no need to flush it if (shader->textureRenderTargetIndex[relative_textureUnit] != 255) continue; auto texUnitRegIndex = hostTextureUnit * 7; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: hostTextureUnit += LATTE_CEMU_VS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS; break; case LatteConst::ShaderType::Pixel: hostTextureUnit += LATTE_CEMU_PS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS; break; case LatteConst::ShaderType::Geometry: hostTextureUnit += LATTE_CEMU_GS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS; break; default: UNREACHABLE; } auto textureView = m_state.m_textures[hostTextureUnit]; if (!textureView) continue; LatteTexture* baseTexture = textureView->baseTexture; // If the texture is also used in the current render pass, we need to end the render pass to "flush" the texture for (uint8 i = 0; i < LATTE_NUM_COLOR_TARGET; i++) { auto colorTarget = m_state.m_activeFBO.m_fbo->colorBuffer[i].texture; if (colorTarget && colorTarget->baseTexture == baseTexture) return true; } } return false; } */ void MetalRenderer::BindStageResources(MTL::RenderCommandEncoder* renderCommandEncoder, LatteDecompilerShader* shader, bool usesGeometryShader) { auto mtlShaderType = GetMtlShaderType(shader->shaderType, usesGeometryShader); sint32 textureCount = shader->resourceMapping.getTextureCount(); for (int i = 0; i < textureCount; ++i) { const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i); auto hostTextureUnit = relative_textureUnit; // Don't bind textures that are accessed with a framebuffer fetch if (m_supportsFramebufferFetch && shader->textureRenderTargetIndex[relative_textureUnit] != 255) continue; auto textureDim = shader->textureUnitDim[relative_textureUnit]; auto texUnitRegIndex = hostTextureUnit * 7; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: hostTextureUnit += LATTE_CEMU_VS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS; break; case LatteConst::ShaderType::Pixel: hostTextureUnit += LATTE_CEMU_PS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS; break; case LatteConst::ShaderType::Geometry: hostTextureUnit += LATTE_CEMU_GS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS; break; default: UNREACHABLE; } // TODO: correct? uint32 binding = shader->resourceMapping.getTextureBaseBindingPoint() + i; if (binding >= MAX_MTL_TEXTURES) { cemuLog_logOnce(LogType::Force, "invalid texture binding {}", binding); continue; } auto textureView = m_state.m_textures[hostTextureUnit]; if (!textureView) { if (textureDim == Latte::E_DIM::DIM_1D) SetTexture(renderCommandEncoder, mtlShaderType, m_nullTexture1D, binding); else SetTexture(renderCommandEncoder, mtlShaderType, m_nullTexture2D, binding); SetSamplerState(renderCommandEncoder, mtlShaderType, m_nearestSampler, binding); continue; } if (textureDim == Latte::E_DIM::DIM_1D && (textureView->dim != Latte::E_DIM::DIM_1D)) { SetTexture(renderCommandEncoder, mtlShaderType, m_nullTexture1D, binding); continue; } else if (textureDim == Latte::E_DIM::DIM_2D && (textureView->dim != Latte::E_DIM::DIM_2D && textureView->dim != Latte::E_DIM::DIM_2D_MSAA)) { SetTexture(renderCommandEncoder, mtlShaderType, m_nullTexture2D, binding); continue; } LatteTexture* baseTexture = textureView->baseTexture; uint32 stageSamplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit]; MTL::SamplerState* sampler; if (stageSamplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) { uint32 samplerIndex = stageSamplerIndex + LatteDecompiler_getTextureSamplerBaseIndex(shader->shaderType); _LatteRegisterSetSampler* samplerWords = LatteGPUState.contextNew.SQ_TEX_SAMPLER + samplerIndex; // Overwriting // Lod bias //if (baseTexture->overwriteInfo.hasLodBias) // samplerWords->WORD1.set_LOD_BIAS(baseTexture->overwriteInfo.lodBias); //else if (baseTexture->overwriteInfo.hasRelativeLodBias) // samplerWords->WORD1.set_LOD_BIAS(samplerWords->WORD1.get_LOD_BIAS() + baseTexture->overwriteInfo.relativeLodBias); // Max anisotropy if (baseTexture->overwriteInfo.anisotropicLevel >= 0) samplerWords->WORD0.set_MAX_ANISO_RATIO(baseTexture->overwriteInfo.anisotropicLevel); sampler = m_samplerCache->GetSamplerState(LatteGPUState.contextNew, shader->shaderType, stageSamplerIndex, samplerWords); } else { sampler = m_nearestSampler; } SetSamplerState(renderCommandEncoder, mtlShaderType, sampler, binding); // get texture register word 0 uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4]; auto& boundTexture = m_state.m_encoderState.m_textures[mtlShaderType][binding]; MTL::Texture* mtlTexture = textureView->GetSwizzledView(word4); SetTexture(renderCommandEncoder, mtlShaderType, mtlTexture, binding); } // Support buffer auto GET_UNIFORM_DATA_PTR = [&](size_t index) { return supportBufferData + (index / 4); }; sint32 shaderAluConst; sint32 shaderUniformRegisterOffset; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: shaderAluConst = 0x400; shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START; break; case LatteConst::ShaderType::Pixel: shaderAluConst = 0; shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START; break; case LatteConst::ShaderType::Geometry: shaderAluConst = 0; // geometry shader has no ALU const shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START; break; default: UNREACHABLE; } if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) { if (shader->uniform.list_ufTexRescale.empty() == false) { for (auto& entry : shader->uniform.list_ufTexRescale) { float* xyScale = LatteTexture_getEffectiveTextureScale(shader->shaderType, entry.texUnit); memcpy(entry.currentValue, xyScale, sizeof(float) * 2); memcpy(GET_UNIFORM_DATA_PTR(entry.uniformLocation), xyScale, sizeof(float) * 2); } } if (shader->uniform.loc_alphaTestRef >= 0) { *GET_UNIFORM_DATA_PTR(shader->uniform.loc_alphaTestRef) = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF(); } if (shader->uniform.loc_pointSize >= 0) { const auto& pointSizeReg = LatteGPUState.contextNew.PA_SU_POINT_SIZE; float pointWidth = (float)pointSizeReg.get_WIDTH() / 8.0f; if (pointWidth == 0.0f) pointWidth = 1.0f / 8.0f; // minimum size *GET_UNIFORM_DATA_PTR(shader->uniform.loc_pointSize) = pointWidth; } if (shader->uniform.loc_remapped >= 0) { LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped)); } if (shader->uniform.loc_uniformRegister >= 0) { uint32* uniformRegData = (uint32*)(LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst); memcpy(GET_UNIFORM_DATA_PTR(shader->uniform.loc_uniformRegister), uniformRegData, shader->uniform.count_uniformRegister * 16); } if (shader->uniform.loc_windowSpaceToClipSpaceTransform >= 0) { sint32 viewportWidth; sint32 viewportHeight; LatteRenderTarget_GetCurrentVirtualViewportSize(&viewportWidth, &viewportHeight); // always call after _updateViewport() float* v = GET_UNIFORM_DATA_PTR(shader->uniform.loc_windowSpaceToClipSpaceTransform); v[0] = 2.0f / (float)viewportWidth; v[1] = 2.0f / (float)viewportHeight; } if (shader->uniform.loc_fragCoordScale >= 0) { LatteMRT::GetCurrentFragCoordScale(GET_UNIFORM_DATA_PTR(shader->uniform.loc_fragCoordScale)); } if (shader->uniform.loc_verticesPerInstance >= 0) { *(int*)(supportBufferData + ((size_t)shader->uniform.loc_verticesPerInstance / 4)) = m_state.m_streamoutState.verticesPerInstance; for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++) { if (shader->uniform.loc_streamoutBufferBase[b] >= 0) { *(uint32*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_streamoutBufferBase[b]) = m_state.m_streamoutState.buffers[b].ringBufferOffset; } } } size_t size = shader->uniform.uniformRangeSize; auto& bufferAllocator = m_memoryManager->GetStagingAllocator(); auto allocation = bufferAllocator.AllocateBufferMemory(size, 1); memcpy(allocation.memPtr, supportBufferData, size); bufferAllocator.FlushReservation(allocation); SetBuffer(renderCommandEncoder, mtlShaderType, allocation.mtlBuffer, allocation.bufferOffset, shader->resourceMapping.uniformVarsBufferBindingPoint); } // Uniform buffers for (sint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if (shader->resourceMapping.uniformBuffersBindingPoint[i] >= 0) { uint32 binding = shader->resourceMapping.uniformBuffersBindingPoint[i]; if (binding >= MAX_MTL_BUFFERS) { cemuLog_logOnce(LogType::Force, "invalid buffer binding {}", binding); continue; } size_t offset = m_state.m_uniformBufferOffsets[GetMtlGeneralShaderType(shader->shaderType)][i]; if (offset == INVALID_OFFSET) continue; SetBuffer(renderCommandEncoder, mtlShaderType, m_memoryManager->GetBufferCache(), offset, binding); } } // Storage buffer if (shader->resourceMapping.tfStorageBindingPoint >= 0) { SetBuffer(renderCommandEncoder, mtlShaderType, m_xfbRingBuffer, 0, shader->resourceMapping.tfStorageBindingPoint); } } void MetalRenderer::ClearColorTextureInternal(MTL::Texture* mtlTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) { NS_STACK_SCOPED MTL::RenderPassDescriptor* renderPassDescriptor = MTL::RenderPassDescriptor::alloc()->init(); auto colorAttachment = renderPassDescriptor->colorAttachments()->object(0); colorAttachment->setTexture(mtlTexture); colorAttachment->setClearColor(MTL::ClearColor(r, g, b, a)); colorAttachment->setLoadAction(MTL::LoadActionClear); colorAttachment->setStoreAction(MTL::StoreActionStore); colorAttachment->setSlice(sliceIndex); colorAttachment->setLevel(mipIndex); GetTemporaryRenderCommandEncoder(renderPassDescriptor); EndEncoding(); // Debug m_performanceMonitor.m_clears++; } void MetalRenderer::CopyBufferToBuffer(MTL::Buffer* src, uint32 srcOffset, MTL::Buffer* dst, uint32 dstOffset, uint32 size, MTL::RenderStages after, MTL::RenderStages before) { // TODO: uncomment and fix performance issues // Do the copy in a vertex shader on Apple GPUs /* if (m_isAppleGPU && m_encoderType == MetalEncoderType::Render) { auto renderCommandEncoder = static_cast<MTL::RenderCommandEncoder*>(m_commandEncoder); MTL::Resource* barrierBuffers[] = {src}; renderCommandEncoder->memoryBarrier(barrierBuffers, 1, after, after | MTL::RenderStageVertex); renderCommandEncoder->setRenderPipelineState(m_copyBufferToBufferPipeline->GetRenderPipelineState()); m_state.m_encoderState.m_renderPipelineState = m_copyBufferToBufferPipeline->GetRenderPipelineState(); SetBuffer(renderCommandEncoder, METAL_SHADER_TYPE_VERTEX, src, srcOffset, GET_HELPER_BUFFER_BINDING(0)); SetBuffer(renderCommandEncoder, METAL_SHADER_TYPE_VERTEX, dst, dstOffset, GET_HELPER_BUFFER_BINDING(1)); renderCommandEncoder->drawPrimitives(MTL::PrimitiveTypePoint, NS::UInteger(0), NS::UInteger(size)); barrierBuffers[0] = dst; renderCommandEncoder->memoryBarrier(barrierBuffers, 1, before | MTL::RenderStageVertex, before); } else { */ auto blitCommandEncoder = GetBlitCommandEncoder(); blitCommandEncoder->copyFromBuffer(src, srcOffset, dst, dstOffset, size); //} } void MetalRenderer::SwapBuffer(bool mainWindow) { if (!AcquireDrawable(mainWindow)) return; auto commandBuffer = GetCommandBuffer(); GetLayer(mainWindow).PresentDrawable(commandBuffer); } void MetalRenderer::EnsureImGuiBackend() { if (!ImGui::GetIO().BackendRendererUserData) { ImGui_ImplMetal_Init(m_device); //ImGui_ImplMetal_CreateFontsTexture(m_device); } } void MetalRenderer::StartCapture() { auto captureManager = MTL::CaptureManager::sharedCaptureManager(); auto desc = MTL::CaptureDescriptor::alloc()->init(); desc->setCaptureObject(m_device); // Check if a debugger with support for GPU capture is attached if (captureManager->supportsDestination(MTL::CaptureDestinationDeveloperTools)) { desc->setDestination(MTL::CaptureDestinationDeveloperTools); } else { if (GetConfig().gpu_capture_dir.GetValue().empty()) { cemuLog_log(LogType::Force, "No GPU capture directory specified, cannot do a GPU capture"); return; } // Check if the GPU trace document destination is available if (!captureManager->supportsDestination(MTL::CaptureDestinationGPUTraceDocument)) { cemuLog_log(LogType::Force, "GPU trace document destination is not available, cannot do a GPU capture"); return; } // Get current date and time as a string auto now = std::chrono::system_clock::now(); std::time_t now_time = std::chrono::system_clock::to_time_t(now); std::ostringstream oss; oss << std::put_time(std::localtime(&now_time), "%Y-%m-%d_%H-%M-%S"); std::string now_str = oss.str(); std::string capturePath = fmt::format("{}/cemu_{}.gputrace", GetConfig().gpu_capture_dir.GetValue(), now_str); desc->setDestination(MTL::CaptureDestinationGPUTraceDocument); desc->setOutputURL(ToNSURL(capturePath)); } NS::Error* error = nullptr; captureManager->startCapture(desc, &error); if (error) { cemuLog_log(LogType::Force, "Failed to start GPU capture: {}", error->localizedDescription()->utf8String()); } m_capturing = true; } void MetalRenderer::EndCapture() { auto captureManager = MTL::CaptureManager::sharedCaptureManager(); captureManager->stopCapture(); m_capturing = false; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalLayerHandle.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalPerformanceMonitor.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalOutputShaderCache.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalAttachmentsInfo.h" enum MetalGeneralShaderType { METAL_GENERAL_SHADER_TYPE_VERTEX, METAL_GENERAL_SHADER_TYPE_GEOMETRY, METAL_GENERAL_SHADER_TYPE_FRAGMENT, METAL_GENERAL_SHADER_TYPE_TOTAL }; inline MetalGeneralShaderType GetMtlGeneralShaderType(LatteConst::ShaderType shaderType) { switch (shaderType) { case LatteConst::ShaderType::Vertex: return METAL_GENERAL_SHADER_TYPE_VERTEX; case LatteConst::ShaderType::Geometry: return METAL_GENERAL_SHADER_TYPE_GEOMETRY; case LatteConst::ShaderType::Pixel: return METAL_GENERAL_SHADER_TYPE_FRAGMENT; default: return METAL_GENERAL_SHADER_TYPE_TOTAL; } } enum MetalShaderType { METAL_SHADER_TYPE_VERTEX, METAL_SHADER_TYPE_OBJECT, METAL_SHADER_TYPE_MESH, METAL_SHADER_TYPE_FRAGMENT, METAL_SHADER_TYPE_TOTAL }; inline MetalShaderType GetMtlShaderType(LatteConst::ShaderType shaderType, bool usesGeometryShader) { switch (shaderType) { case LatteConst::ShaderType::Vertex: if (usesGeometryShader) return METAL_SHADER_TYPE_OBJECT; else return METAL_SHADER_TYPE_VERTEX; case LatteConst::ShaderType::Geometry: return METAL_SHADER_TYPE_MESH; case LatteConst::ShaderType::Pixel: return METAL_SHADER_TYPE_FRAGMENT; default: return METAL_SHADER_TYPE_TOTAL; } } struct MetalEncoderState { MTL::RenderPipelineState* m_renderPipelineState = nullptr; MTL::DepthStencilState* m_depthStencilState = nullptr; MTL::CullMode m_cullMode = MTL::CullModeNone; MTL::Winding m_frontFaceWinding = MTL::WindingClockwise; MTL::Viewport m_viewport; MTL::ScissorRect m_scissor; uint32 m_stencilRefFront = 0; uint32 m_stencilRefBack = 0; uint32 m_blendColor[4] = {0}; uint32 m_depthBias = 0; uint32 m_depthSlope = 0; uint32 m_depthClamp = 0; bool m_depthClipEnable = true; struct { MTL::Buffer* m_buffer; size_t m_offset; } m_buffers[METAL_SHADER_TYPE_TOTAL][MAX_MTL_BUFFERS]; MTL::Texture* m_textures[METAL_SHADER_TYPE_TOTAL][MAX_MTL_TEXTURES]; MTL::SamplerState* m_samplers[METAL_SHADER_TYPE_TOTAL][MAX_MTL_SAMPLERS]; }; struct MetalStreamoutState { struct { bool enabled; uint32 ringBufferOffset; } buffers[LATTE_NUM_STREAMOUT_BUFFER]; sint32 verticesPerInstance; }; struct MetalActiveFBOState { class CachedFBOMtl* m_fbo = nullptr; MetalAttachmentsInfo m_attachmentsInfo; }; struct MetalState { MetalEncoderState m_encoderState{}; bool m_usesSRGB = false; bool m_skipDrawSequence = false; bool m_isFirstDrawInRenderPass = true; MetalActiveFBOState m_activeFBO; // If the FBO changes, but it's the same FBO as the last one with some omitted attachments, this FBO doesn't change MetalActiveFBOState m_lastUsedFBO; bool m_fboChanged = false; size_t m_vertexBufferOffsets[MAX_MTL_VERTEX_BUFFERS]; class LatteTextureViewMtl* m_textures[LATTE_NUM_MAX_TEX_UNITS * 3] = {nullptr}; size_t m_uniformBufferOffsets[METAL_GENERAL_SHADER_TYPE_TOTAL][MAX_MTL_BUFFERS]; MTL::Viewport m_viewport; MTL::ScissorRect m_scissor; MetalStreamoutState m_streamoutState; }; struct MetalCommandBuffer { MTL::CommandBuffer* m_commandBuffer = nullptr; bool m_commited = false; }; enum class MetalEncoderType { None, Render, Compute, Blit, }; class MetalRenderer : public Renderer { public: static constexpr uint32 OCCLUSION_QUERY_POOL_SIZE = 1024; static constexpr uint32 TEXTURE_READBACK_SIZE = 32 * 1024 * 1024; // 32 MB struct DeviceInfo { std::string name; uint64 uuid; }; static std::vector<DeviceInfo> GetDevices(); MetalRenderer(); ~MetalRenderer() override; RendererAPI GetType() override { return RendererAPI::Metal; } static MetalRenderer* GetInstance() { return static_cast<MetalRenderer*>(g_renderer.get()); } // Helper functions MTL::Device* GetDevice() const { return m_device; } void InitializeLayer(const Vector2i& size, bool mainWindow); void ShutdownLayer(bool mainWindow); void ResizeLayer(const Vector2i& size, bool mainWindow); void Initialize() override; void Shutdown() override; bool IsPadWindowActive() override; bool GetVRAMInfo(int& usageInMB, int& totalInMB) const override; void ClearColorbuffer(bool padView) override; void DrawEmptyFrame(bool mainWindow) override; void SwapBuffers(bool swapTV, bool swapDRC) override; void HandleScreenshotRequest(LatteTextureView* texView, bool padView) override; void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) override; bool BeginFrame(bool mainWindow) override; // flush control void Flush(bool waitIdle = false) override; // called when explicit flush is required (e.g. by imgui) void NotifyLatteCommandProcessorIdle() override; // called when command processor has no more commands available or when stalled // imgui bool ImguiBegin(bool mainWindow) override; void ImguiEnd() override; ImTextureID GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) override; void DeleteTexture(ImTextureID id) override; void DeleteFontTextures() override; bool UseTFViaSSBO() const override { return true; } void AppendOverlayDebugInfo() override; // rendertarget void renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ = false) override; void renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) override; LatteCachedFBO* rendertarget_createCachedFBO(uint64 key) override; void rendertarget_deleteCachedFBO(LatteCachedFBO* fbo) override; void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) override; // texture functions void* texture_acquireTextureUploadBuffer(uint32 size) override; void texture_releaseTextureUploadBuffer(uint8* mem) override; TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override; void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override; void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) override; void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override; void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) override; LatteTexture* texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) override; void texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) override; void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) override; LatteTextureReadbackInfo* texture_createReadback(LatteTextureView* textureView) override; // surface copy void surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) override; // buffer cache void bufferCache_init(const sint32 bufferSize) override; void bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) override; void bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) override; void bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) override; void buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) override; void buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) override; // shader RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool compileAsync, bool isGfxPackSource) override; // streamout void streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) override; void streamout_begin() override; void streamout_rendererFinishDrawcall() override; // core drawing logic void draw_beginSequence() override; void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override; void draw_endSequence() override; void draw_updateVertexBuffersDirectAccess(); void draw_updateUniformBuffersDirectAccess(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset); void draw_handleSpecialState5(); // index IndexAllocation indexData_reserveIndexMemory(uint32 size) override; void indexData_releaseIndexMemory(IndexAllocation& allocation) override; void indexData_uploadIndexMemory(IndexAllocation& allocation) override; // occlusion queries LatteQueryObject* occlusionQuery_create() override; void occlusionQuery_destroy(LatteQueryObject* queryObj) override; void occlusionQuery_flush() override; void occlusionQuery_updateState() override; // Helpers MetalPerformanceMonitor& GetPerformanceMonitor() { return m_performanceMonitor; } void SetShouldMaximizeConcurrentCompilation(bool shouldMaximizeConcurrentCompilation) { if (m_supportsMetal3) m_device->setShouldMaximizeConcurrentCompilation(shouldMaximizeConcurrentCompilation); } bool IsCommandBufferActive() const { return (m_currentCommandBuffer.m_commandBuffer && !m_currentCommandBuffer.m_commited); } MTL::CommandBuffer* GetCurrentCommandBuffer() const { cemu_assert_debug(m_currentCommandBuffer.m_commandBuffer); return m_currentCommandBuffer.m_commandBuffer; } MTL::CommandBuffer* GetAndRetainCurrentCommandBufferIfNotCompleted() const { // The command buffer has been commited and has finished execution if (m_currentCommandBuffer.m_commited && m_executingCommandBuffers.size() == 0) return nullptr; return GetCurrentCommandBuffer()->retain(); } void RequestSoonCommit() { m_commitTreshold = m_recordedDrawcalls + 8; } MTL::CommandEncoder* GetCommandEncoder() { return m_commandEncoder; } MetalEncoderType GetEncoderType() { return m_encoderType; } void ResetEncoderState() { m_state.m_encoderState = {}; // TODO: set viewport and scissor to render target dimensions if render commands for (uint32 i = 0; i < METAL_SHADER_TYPE_TOTAL; i++) { for (uint32 j = 0; j < MAX_MTL_BUFFERS; j++) m_state.m_encoderState.m_buffers[i][j] = {nullptr}; for (uint32 j = 0; j < MAX_MTL_TEXTURES; j++) m_state.m_encoderState.m_textures[i][j] = nullptr; for (uint32 j = 0; j < MAX_MTL_SAMPLERS; j++) m_state.m_encoderState.m_samplers[i][j] = nullptr; } } MetalEncoderState& GetEncoderState() { return m_state.m_encoderState; } void SetBuffer(MTL::RenderCommandEncoder* renderCommandEncoder, MetalShaderType shaderType, MTL::Buffer* buffer, size_t offset, uint32 index); void SetTexture(MTL::RenderCommandEncoder* renderCommandEncoder, MetalShaderType shaderType, MTL::Texture* texture, uint32 index); void SetSamplerState(MTL::RenderCommandEncoder* renderCommandEncoder, MetalShaderType shaderType, MTL::SamplerState* samplerState, uint32 index); MTL::CommandBuffer* GetCommandBuffer(); MTL::RenderCommandEncoder* GetTemporaryRenderCommandEncoder(MTL::RenderPassDescriptor* renderPassDescriptor); MTL::RenderCommandEncoder* GetRenderCommandEncoder(bool forceRecreate = false); MTL::ComputeCommandEncoder* GetComputeCommandEncoder(); MTL::BlitCommandEncoder* GetBlitCommandEncoder(); void EndEncoding(); void CommitCommandBuffer(); void ProcessFinishedCommandBuffers(); bool AcquireDrawable(bool mainWindow); //bool CheckIfRenderPassNeedsFlush(LatteDecompilerShader* shader); void BindStageResources(MTL::RenderCommandEncoder* renderCommandEncoder, LatteDecompilerShader* shader, bool usesGeometryShader); void ClearColorTextureInternal(MTL::Texture* mtlTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a); void CopyBufferToBuffer(MTL::Buffer* src, uint32 srcOffset, MTL::Buffer* dst, uint32 dstOffset, uint32 size, MTL::RenderStages after, MTL::RenderStages before); // Getters bool GetPositionInvariance() const { return m_positionInvariance; } bool IsAppleGPU() const { return m_isAppleGPU; } bool SupportsFramebufferFetch() const { return m_supportsFramebufferFetch; } bool HasUnifiedMemory() const { return m_hasUnifiedMemory; } bool SupportsMetal3() const { return m_supportsMetal3; } bool SupportsMeshShaders() const { return m_supportsMeshShaders; } //MTL::StorageMode GetOptimalTextureStorageMode() const //{ // return (m_isAppleGPU ? MTL::StorageModeShared : MTL::StorageModePrivate); //} MTL::ResourceOptions GetOptimalBufferStorageMode() const { return (m_hasUnifiedMemory ? MTL::ResourceStorageModeShared : MTL::ResourceStorageModeManaged); } MTL::Texture* GetNullTexture2D() const { return m_nullTexture2D; } MTL::Buffer* GetTextureReadbackBuffer() { if (!m_readbackBuffer) { m_readbackBuffer = m_device->newBuffer(TEXTURE_READBACK_SIZE, MTL::ResourceStorageModeShared); #ifdef CEMU_DEBUG_ASSERT m_readbackBuffer->setLabel(GetLabel("Texture readback buffer", m_readbackBuffer)); #endif } return m_readbackBuffer; } MTL::Buffer* GetXfbRingBuffer() { if (!m_xfbRingBuffer) { // HACK: using just LatteStreamout_GetRingBufferSize will cause page faults m_xfbRingBuffer = m_device->newBuffer(LatteStreamout_GetRingBufferSize() * 4, MTL::ResourceStorageModePrivate); #ifdef CEMU_DEBUG_ASSERT m_xfbRingBuffer->setLabel(GetLabel("Transform feedback buffer", m_xfbRingBuffer)); #endif } return m_xfbRingBuffer; } MTL::Buffer* GetOcclusionQueryResultBuffer() const { return m_occlusionQuery.m_resultBuffer; } uint64* GetOcclusionQueryResultsPtr() { return m_occlusionQuery.m_resultsPtr; } uint32 GetOcclusionQueryIndex() { return m_occlusionQuery.m_currentIndex; } void BeginOcclusionQuery() { m_occlusionQuery.m_active = true; } void EndOcclusionQuery() { m_occlusionQuery.m_active = false; // Release the old command buffer if (m_occlusionQuery.m_lastCommandBuffer) m_occlusionQuery.m_lastCommandBuffer->release(); // Get and retain the current command buffer m_occlusionQuery.m_lastCommandBuffer = GetAndRetainCurrentCommandBufferIfNotCompleted(); } // GPU capture void CaptureFrame() { m_captureFrame = true; } private: MetalLayerHandle m_mainLayer; MetalLayerHandle m_padLayer; MetalPerformanceMonitor m_performanceMonitor; // Options bool m_positionInvariance; // Metal objects MTL::Device* m_device = nullptr; MTL::CommandQueue* m_commandQueue; // Feature support bool m_isAppleGPU; bool m_supportsFramebufferFetch; bool m_hasUnifiedMemory; bool m_supportsMetal3; bool m_supportsMeshShaders; uint32 m_recommendedMaxVRAMUsage; MetalPixelFormatSupport m_pixelFormatSupport; // Managers and caches class MetalMemoryManager* m_memoryManager; class MetalOutputShaderCache* m_outputShaderCache; class MetalPipelineCache* m_pipelineCache; class MetalDepthStencilCache* m_depthStencilCache; class MetalSamplerCache* m_samplerCache; // Pipelines MTL::RenderPipelineDescriptor* m_copyDepthToColorDesc; std::map<MTL::PixelFormat, MTL::RenderPipelineState*> m_copyDepthToColorPipelines; // Void vertex pipelines class MetalVoidVertexPipeline* m_copyBufferToBufferPipeline; // Synchronization resources MTL::Event* m_event; int32_t m_eventValue = -1; // Resources MTL::SamplerState* m_nearestSampler; MTL::SamplerState* m_linearSampler; // Null resources MTL::Texture* m_nullTexture1D; MTL::Texture* m_nullTexture2D; // Texture readback MTL::Buffer* m_readbackBuffer = nullptr; uint32 m_readbackBufferWriteOffset = 0; // Transform feedback MTL::Buffer* m_xfbRingBuffer = nullptr; // Occlusion queries struct { MTL::Buffer* m_resultBuffer; uint64* m_resultsPtr; uint32 m_currentIndex = 0; bool m_active = false; MTL::CommandBuffer* m_lastCommandBuffer = nullptr; } m_occlusionQuery; // Active objects MetalCommandBuffer m_currentCommandBuffer{}; std::vector<MTL::CommandBuffer*> m_executingCommandBuffers; MetalEncoderType m_encoderType = MetalEncoderType::None; MTL::CommandEncoder* m_commandEncoder = nullptr; uint32 m_recordedDrawcalls; uint32 m_defaultCommitTreshlod; uint32 m_commitTreshold; // State MetalState m_state; // GPU capture bool m_captureFrame = false; bool m_capturing = false; // Helpers MetalLayerHandle& GetLayer(bool mainWindow) { return (mainWindow ? m_mainLayer : m_padLayer); } void SwapBuffer(bool mainWindow); void EnsureImGuiBackend(); // GPU capture void StartCapture(); void EndCapture(); }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalSamplerCache.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalSamplerCache.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h" MTL::SamplerBorderColor GetBorderColor(LatteConst::ShaderType shaderType, uint32 stageSamplerIndex, const _LatteRegisterSetSampler* samplerWords, bool logWorkaround = false) { auto borderType = samplerWords->WORD0.get_BORDER_COLOR_TYPE(); MTL::SamplerBorderColor borderColor; if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::TRANSPARENT_BLACK) borderColor = MTL::SamplerBorderColorTransparentBlack; else if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::OPAQUE_BLACK) borderColor = MTL::SamplerBorderColorOpaqueBlack; else if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::OPAQUE_WHITE) borderColor = MTL::SamplerBorderColorOpaqueWhite; else [[unlikely]] { _LatteRegisterSetSamplerBorderColor* borderColorReg; if (shaderType == LatteConst::ShaderType::Vertex) borderColorReg = LatteGPUState.contextNew.TD_VS_SAMPLER_BORDER_COLOR + stageSamplerIndex; else if (shaderType == LatteConst::ShaderType::Pixel) borderColorReg = LatteGPUState.contextNew.TD_PS_SAMPLER_BORDER_COLOR + stageSamplerIndex; else // geometry borderColorReg = LatteGPUState.contextNew.TD_GS_SAMPLER_BORDER_COLOR + stageSamplerIndex; float r = borderColorReg->red.get_channelValue(); float g = borderColorReg->green.get_channelValue(); float b = borderColorReg->blue.get_channelValue(); float a = borderColorReg->alpha.get_channelValue(); // Metal doesn't support custom border color // Let's find the best match bool opaque = (a == 1.0f); bool white = (r == 1.0f); if (opaque) { if (white) borderColor = MTL::SamplerBorderColorOpaqueWhite; else borderColor = MTL::SamplerBorderColorOpaqueBlack; } else { borderColor = MTL::SamplerBorderColorTransparentBlack; } if (logWorkaround) { float newR, newG, newB, newA; switch (borderColor) { case MTL::SamplerBorderColorTransparentBlack: newR = 0.0f; newG = 0.0f; newB = 0.0f; newA = 0.0f; break; case MTL::SamplerBorderColorOpaqueBlack: newR = 0.0f; newG = 0.0f; newB = 0.0f; newA = 1.0f; break; case MTL::SamplerBorderColorOpaqueWhite: newR = 1.0f; newG = 1.0f; newB = 1.0f; newA = 1.0f; break; } if (r != newR || g != newG || b != newB || a != newA) cemuLog_log(LogType::Force, "Custom border color ({}, {}, {}, {}) is not supported on Metal, using ({}, {}, {}, {}) instead", r, g, b, a, newR, newG, newB, newA); } } return borderColor; } MetalSamplerCache::~MetalSamplerCache() { for (auto& pair : m_samplerCache) { pair.second->release(); } m_samplerCache.clear(); } MTL::SamplerState* MetalSamplerCache::GetSamplerState(const LatteContextRegister& lcr, LatteConst::ShaderType shaderType, uint32 stageSamplerIndex, const _LatteRegisterSetSampler* samplerWords) { uint64 stateHash = CalculateSamplerHash(lcr, shaderType, stageSamplerIndex, samplerWords); auto& samplerState = m_samplerCache[stateHash]; if (samplerState) return samplerState; // Sampler state NS_STACK_SCOPED MTL::SamplerDescriptor* samplerDescriptor = MTL::SamplerDescriptor::alloc()->init(); // lod uint32 iMinLOD = samplerWords->WORD1.get_MIN_LOD(); uint32 iMaxLOD = samplerWords->WORD1.get_MAX_LOD(); //sint32 iLodBias = samplerWords->WORD1.get_LOD_BIAS(); auto filterMip = samplerWords->WORD0.get_MIP_FILTER(); if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::NONE) { samplerDescriptor->setMipFilter(MTL::SamplerMipFilterNearest); samplerDescriptor->setLodMinClamp(0.0f); samplerDescriptor->setLodMaxClamp(0.25f); } else if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::POINT) { samplerDescriptor->setMipFilter(MTL::SamplerMipFilterNearest); samplerDescriptor->setLodMinClamp((float)iMinLOD / 64.0f); samplerDescriptor->setLodMaxClamp((float)iMaxLOD / 64.0f); } else if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::LINEAR) { samplerDescriptor->setMipFilter(MTL::SamplerMipFilterLinear); samplerDescriptor->setLodMinClamp((float)iMinLOD / 64.0f); samplerDescriptor->setLodMaxClamp((float)iMaxLOD / 64.0f); } else { // fallback for invalid constants samplerDescriptor->setMipFilter(MTL::SamplerMipFilterLinear); samplerDescriptor->setLodMinClamp((float)iMinLOD / 64.0f); samplerDescriptor->setLodMaxClamp((float)iMaxLOD / 64.0f); } auto filterMin = samplerWords->WORD0.get_XY_MIN_FILTER(); cemu_assert_debug(filterMin != Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::BICUBIC); // todo samplerDescriptor->setMinFilter((filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT || filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT) ? MTL::SamplerMinMagFilterNearest : MTL::SamplerMinMagFilterLinear); auto filterMag = samplerWords->WORD0.get_XY_MAG_FILTER(); samplerDescriptor->setMagFilter((filterMag == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT || filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT) ? MTL::SamplerMinMagFilterNearest : MTL::SamplerMinMagFilterLinear); auto filterZ = samplerWords->WORD0.get_Z_FILTER(); // todo: z-filter for texture array samplers is customizable for GPU7 but OpenGL/Vulkan doesn't expose this functionality? auto clampX = samplerWords->WORD0.get_CLAMP_X(); auto clampY = samplerWords->WORD0.get_CLAMP_Y(); auto clampZ = samplerWords->WORD0.get_CLAMP_Z(); samplerDescriptor->setSAddressMode(GetMtlSamplerAddressMode(clampX)); samplerDescriptor->setTAddressMode(GetMtlSamplerAddressMode(clampY)); samplerDescriptor->setRAddressMode(GetMtlSamplerAddressMode(clampZ)); auto maxAniso = samplerWords->WORD0.get_MAX_ANISO_RATIO(); if (maxAniso > 0) samplerDescriptor->setMaxAnisotropy(1 << maxAniso); // TODO: set lod bias //samplerInfo.mipLodBias = (float)iLodBias / 64.0f; // depth compare //uint8 depthCompareMode = shader->textureUsesDepthCompare[relative_textureUnit] ? 1 : 0; // TODO: is it okay to just cast? samplerDescriptor->setCompareFunction(GetMtlCompareFunc((Latte::E_COMPAREFUNC)samplerWords->WORD0.get_DEPTH_COMPARE_FUNCTION())); // Border color auto borderColor = GetBorderColor(shaderType, stageSamplerIndex, samplerWords, true); samplerDescriptor->setBorderColor(borderColor); samplerState = m_mtlr->GetDevice()->newSamplerState(samplerDescriptor); return samplerState; } uint64 MetalSamplerCache::CalculateSamplerHash(const LatteContextRegister& lcr, LatteConst::ShaderType shaderType, uint32 stageSamplerIndex, const _LatteRegisterSetSampler* samplerWords) { uint64 hash = 0; hash = std::rotl<uint64>(hash, 17); hash += (uint64)samplerWords->WORD0.getRawValue(); hash = std::rotl<uint64>(hash, 17); hash += (uint64)samplerWords->WORD1.getRawValue(); hash = std::rotl<uint64>(hash, 17); hash += (uint64)samplerWords->WORD2.getRawValue(); auto borderColor = GetBorderColor(shaderType, stageSamplerIndex, samplerWords); hash = std::rotl<uint64>(hash, 5); hash += (uint64)borderColor; // TODO: check this return hash; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalSamplerCache.h ================================================ #pragma once #include <Metal/Metal.hpp> #include "HW/Latte/Core/LatteConst.h" #include "HW/Latte/ISA/LatteReg.h" class MetalSamplerCache { public: MetalSamplerCache(class MetalRenderer* metalRenderer) : m_mtlr{metalRenderer} {} ~MetalSamplerCache(); MTL::SamplerState* GetSamplerState(const LatteContextRegister& lcr, LatteConst::ShaderType shaderType, uint32 stageSamplerIndex, const _LatteRegisterSetSampler* samplerWords); private: class MetalRenderer* m_mtlr; std::map<uint64, MTL::SamplerState*> m_samplerCache; uint64 CalculateSamplerHash(const LatteContextRegister& lcr, LatteConst::ShaderType shaderType, uint32 stageSamplerIndex, const _LatteRegisterSetSampler* samplerWords); }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalVoidVertexPipeline.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalVoidVertexPipeline.h" MetalVoidVertexPipeline::MetalVoidVertexPipeline(class MetalRenderer* mtlRenderer, MTL::Library* library, const std::string& vertexFunctionName) { // Render pipeline state NS_STACK_SCOPED MTL::Function* vertexFunction = library->newFunction(ToNSString(vertexFunctionName)); NS_STACK_SCOPED MTL::RenderPipelineDescriptor* renderPipelineDescriptor = MTL::RenderPipelineDescriptor::alloc()->init(); renderPipelineDescriptor->setVertexFunction(vertexFunction); renderPipelineDescriptor->setRasterizationEnabled(false); NS::Error* error = nullptr; m_renderPipelineState = mtlRenderer->GetDevice()->newRenderPipelineState(renderPipelineDescriptor, &error); if (error) { cemuLog_log(LogType::Force, "error creating hybrid render pipeline state: {}", error->localizedDescription()->utf8String()); } } MetalVoidVertexPipeline::~MetalVoidVertexPipeline() { m_renderPipelineState->release(); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/MetalVoidVertexPipeline.h ================================================ #include "Cafe/HW/Latte/Renderer/Metal/MetalCommon.h" #include "HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Metal/MTLLibrary.hpp" #include "Metal/MTLRenderPipeline.hpp" class MetalVoidVertexPipeline { public: MetalVoidVertexPipeline(class MetalRenderer* mtlRenderer, MTL::Library* library, const std::string& vertexFunctionName); ~MetalVoidVertexPipeline(); MTL::RenderPipelineState* GetRenderPipelineState() const { return m_renderPipelineState; } private: MTL::RenderPipelineState* m_renderPipelineState; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalCommon.h" //#include "Cemu/FileCache/FileCache.h" //#include "config/ActiveSettings.h" #include "Cemu/Logging/CemuLogging.h" #include "Common/precompiled.h" #include "GameProfile/GameProfile.h" #include "util/helpers/helpers.h" #define METAL_AIR_CACHE_NAME "Cemu_AIR_cache" #define METAL_AIR_CACHE_PATH "/Volumes/" METAL_AIR_CACHE_NAME #define METAL_AIR_CACHE_SIZE (16 * 1024 * 1024) #define METAL_AIR_CACHE_BLOCK_COUNT (METAL_AIR_CACHE_SIZE / 512) static bool s_isLoadingShadersMtl{false}; //static bool s_hasRAMFilesystem{false}; //class FileCache* s_airCache{nullptr}; extern std::atomic_int g_compiled_shaders_total; extern std::atomic_int g_compiled_shaders_async; class ShaderMtlThreadPool { public: void StartThreads() { if (m_threadsActive.exchange(true)) return; // Create thread pool const uint32 threadCount = 2; for (uint32 i = 0; i < threadCount; ++i) s_threads.emplace_back(&ShaderMtlThreadPool::CompilerThreadFunc, this); // Create AIR cache thread /* s_airCacheThread = new std::thread(&ShaderMtlThreadPool::AIRCacheThreadFunc, this); // Set priority sched_param schedParam; schedParam.sched_priority = 20; if (pthread_setschedparam(s_airCacheThread->native_handle(), SCHED_FIFO, &schedParam) != 0) { cemuLog_log(LogType::Force, "failed to set FIFO thread priority"); } if (pthread_setschedparam(s_airCacheThread->native_handle(), SCHED_RR, &schedParam) != 0) { cemuLog_log(LogType::Force, "failed to set RR thread priority"); } */ } void StopThreads() { if (!m_threadsActive.exchange(false)) return; for (uint32 i = 0; i < s_threads.size(); ++i) s_compilationQueueCount.increment(); for (auto& it : s_threads) it.join(); s_threads.clear(); /* if (s_airCacheThread) { s_airCacheQueueCount.increment(); s_airCacheThread->join(); delete s_airCacheThread; } */ } ~ShaderMtlThreadPool() { StopThreads(); } void CompilerThreadFunc() { SetThreadName("mtlShaderComp"); while (m_threadsActive.load(std::memory_order::relaxed)) { s_compilationQueueCount.decrementWithWait(); s_compilationQueueMutex.lock(); if (s_compilationQueue.empty()) { // queue empty again, shaders compiled synchronously via PreponeCompilation() s_compilationQueueMutex.unlock(); continue; } RendererShaderMtl* job = s_compilationQueue.front(); s_compilationQueue.pop_front(); // set compilation state cemu_assert_debug(job->m_compilationState.getValue() == RendererShaderMtl::COMPILATION_STATE::QUEUED); job->m_compilationState.setValue(RendererShaderMtl::COMPILATION_STATE::COMPILING); s_compilationQueueMutex.unlock(); // compile job->CompileInternal(); if (job->ShouldCountCompilation()) ++g_compiled_shaders_async; // mark as compiled cemu_assert_debug(job->m_compilationState.getValue() == RendererShaderMtl::COMPILATION_STATE::COMPILING); job->m_compilationState.setValue(RendererShaderMtl::COMPILATION_STATE::DONE); } } /* void AIRCacheThreadFunc() { SetThreadName("mtlAIRCache"); while (m_threadsActive.load(std::memory_order::relaxed)) { s_airCacheQueueCount.decrementWithWait(); s_airCacheQueueMutex.lock(); if (s_airCacheQueue.empty()) { s_airCacheQueueMutex.unlock(); continue; } // Create RAM filesystem if (!s_hasRAMFilesystem) { executeCommand("diskutil erasevolume HFS+ {} $(hdiutil attach -nomount ram://{})", METAL_AIR_CACHE_NAME, METAL_AIR_CACHE_BLOCK_COUNT); s_hasRAMFilesystem = true; } RendererShaderMtl* job = s_airCacheQueue.front(); s_airCacheQueue.pop_front(); s_airCacheQueueMutex.unlock(); // compile job->CompileToAIR(); } } */ bool HasThreadsRunning() const { return m_threadsActive; } public: std::vector<std::thread> s_threads; //std::thread* s_airCacheThread{nullptr}; std::deque<RendererShaderMtl*> s_compilationQueue; CounterSemaphore s_compilationQueueCount; std::mutex s_compilationQueueMutex; /* std::deque<RendererShaderMtl*> s_airCacheQueue; CounterSemaphore s_airCacheQueueCount; std::mutex s_airCacheQueueMutex; */ private: std::atomic<bool> m_threadsActive; } shaderMtlThreadPool; // TODO: find out if it would be possible to cache compiled Metal shaders void RendererShaderMtl::ShaderCacheLoading_begin(uint64 cacheTitleId) { s_isLoadingShadersMtl = true; // Open AIR cache /* if (s_airCache) { delete s_airCache; s_airCache = nullptr; } uint32 airCacheMagic = GeneratePrecompiledCacheId(); const std::string cacheFilename = fmt::format("{:016x}_air.bin", cacheTitleId); const fs::path cachePath = ActiveSettings::GetCachePath("shaderCache/precompiled/{}", cacheFilename); s_airCache = FileCache::Open(cachePath, true, airCacheMagic); if (!s_airCache) cemuLog_log(LogType::Force, "Unable to open AIR cache {}", cacheFilename); */ // Maximize shader compilation speed static_cast<MetalRenderer*>(g_renderer.get())->SetShouldMaximizeConcurrentCompilation(true); } void RendererShaderMtl::ShaderCacheLoading_end() { s_isLoadingShadersMtl = false; // Reset shader compilation speed static_cast<MetalRenderer*>(g_renderer.get())->SetShouldMaximizeConcurrentCompilation(false); } void RendererShaderMtl::ShaderCacheLoading_Close() { // Close the AIR cache /* if (s_airCache) { delete s_airCache; s_airCache = nullptr; } // Close RAM filesystem if (s_hasRAMFilesystem) executeCommand("diskutil eject {}", METAL_AIR_CACHE_PATH); */ } void RendererShaderMtl::Initialize() { shaderMtlThreadPool.StartThreads(); } void RendererShaderMtl::Shutdown() { shaderMtlThreadPool.StopThreads(); } RendererShaderMtl::RendererShaderMtl(MetalRenderer* mtlRenderer, ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& mslCode) : RendererShader(type, baseHash, auxHash, isGameShader, isGfxPackShader), m_mtlr{mtlRenderer}, m_mslCode{mslCode} { // start async compilation shaderMtlThreadPool.s_compilationQueueMutex.lock(); m_compilationState.setValue(COMPILATION_STATE::QUEUED); shaderMtlThreadPool.s_compilationQueue.push_back(this); shaderMtlThreadPool.s_compilationQueueCount.increment(); shaderMtlThreadPool.s_compilationQueueMutex.unlock(); cemu_assert_debug(shaderMtlThreadPool.HasThreadsRunning()); // make sure .StartThreads() was called } RendererShaderMtl::~RendererShaderMtl() { if (m_function) m_function->release(); } void RendererShaderMtl::PreponeCompilation(bool isRenderThread) { shaderMtlThreadPool.s_compilationQueueMutex.lock(); bool isStillQueued = m_compilationState.hasState(COMPILATION_STATE::QUEUED); if (isStillQueued) { // remove from queue shaderMtlThreadPool.s_compilationQueue.erase(std::remove(shaderMtlThreadPool.s_compilationQueue.begin(), shaderMtlThreadPool.s_compilationQueue.end(), this), shaderMtlThreadPool.s_compilationQueue.end()); m_compilationState.setValue(COMPILATION_STATE::COMPILING); } shaderMtlThreadPool.s_compilationQueueMutex.unlock(); if (!isStillQueued) { m_compilationState.waitUntilValue(COMPILATION_STATE::DONE); if (ShouldCountCompilation()) --g_compiled_shaders_async; // compilation caused a stall so we don't consider this one async return; } else { // compile synchronously CompileInternal(); m_compilationState.setValue(COMPILATION_STATE::DONE); } } bool RendererShaderMtl::IsCompiled() { return m_compilationState.hasState(COMPILATION_STATE::DONE); }; bool RendererShaderMtl::WaitForCompiled() { m_compilationState.waitUntilValue(COMPILATION_STATE::DONE); return true; } bool RendererShaderMtl::ShouldCountCompilation() const { return !s_isLoadingShadersMtl && m_isGameShader; } MTL::Library* RendererShaderMtl::LibraryFromSource() { // Compile from source NS_STACK_SCOPED MTL::CompileOptions* options = MTL::CompileOptions::alloc()->init(); if (g_current_game_profile->GetShaderFastMath()) options->setFastMathEnabled(true); if (m_mtlr->GetPositionInvariance()) { // TODO: filter out based on GPU state options->setPreserveInvariance(true); } NS::Error* error = nullptr; MTL::Library* library = m_mtlr->GetDevice()->newLibrary(ToNSString(m_mslCode), options, &error); if (error) { cemuLog_log(LogType::Force, "failed to create library from source: {} -> {}", error->localizedDescription()->utf8String(), m_mslCode.c_str()); return nullptr; } return library; } /* MTL::Library* RendererShaderMtl::LibraryFromAIR(std::span<uint8> data) { dispatch_data_t dispatchData = dispatch_data_create(data.data(), data.size(), nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); NS::Error* error = nullptr; MTL::Library* library = m_mtlr->GetDevice()->newLibrary(dispatchData, &error); if (error) { cemuLog_log(LogType::Force, "failed to create library from AIR: {}", error->localizedDescription()->utf8String()); return nullptr; } return library; } */ void RendererShaderMtl::CompileInternal() { MTL::Library* library = nullptr; // First, try to retrieve the compiled shader from the AIR cache /* if (s_isLoadingShadersMtl && (m_isGameShader && !m_isGfxPackShader) && s_airCache) { cemu_assert_debug(m_baseHash != 0); uint64 h1, h2; GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); std::vector<uint8> cacheFileData; if (s_airCache->GetFile({ h1, h2 }, cacheFileData)) { library = LibraryFromAIR(std::span<uint8>(cacheFileData.data(), cacheFileData.size())); FinishCompilation(); } } */ // Not in the cache, compile from source if (!library) { // Compile from source library = LibraryFromSource(); FinishCompilation(); if (!library) return; // Store in the AIR cache /* shaderMtlThreadPool.s_airCacheQueueMutex.lock(); shaderMtlThreadPool.s_airCacheQueue.push_back(this); shaderMtlThreadPool.s_airCacheQueueCount.increment(); shaderMtlThreadPool.s_airCacheQueueMutex.unlock(); */ } m_function = library->newFunction(ToNSString("main0")); library->release(); // Count shader compilation if (ShouldCountCompilation()) g_compiled_shaders_total++; } /* void RendererShaderMtl::CompileToAIR() { uint64 h1, h2; GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); // The shader is not in the cache, compile it std::string baseFilename = fmt::format("{}/{}_{}", METAL_AIR_CACHE_PATH, h1, h2); // Source std::ofstream mslFile; mslFile.open(fmt::format("{}.metal", baseFilename)); mslFile << m_mslCode; mslFile.close(); // Compile if (!executeCommand("xcrun -sdk macosx metal -o {}.ir -c {}.metal -w", baseFilename, baseFilename)) return; if (!executeCommand("xcrun -sdk macosx metallib -o {}.metallib {}.ir", baseFilename, baseFilename)) return; // Clean up executeCommand("rm {}.metal", baseFilename); executeCommand("rm {}.ir", baseFilename); // Load from the newly generated AIR MemoryMappedFile airFile(fmt::format("{}.metallib", baseFilename)); std::span<uint8> airData = std::span<uint8>(airFile.data(), airFile.size()); //library = LibraryFromAIR(std::span<uint8>(airData.data(), airData.size())); // Store in the cache s_airCache->AddFile({ h1, h2 }, airData.data(), airData.size()); // Clean up executeCommand("rm {}.metallib", baseFilename); FinishCompilation(); } */ void RendererShaderMtl::FinishCompilation() { m_mslCode.clear(); m_mslCode.shrink_to_fit(); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/RendererShaderMtl.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/RendererShader.h" #include "HW/Latte/Renderer/Metal/CachedFBOMtl.h" #include "HW/Latte/Renderer/Metal/MetalRenderer.h" #include "util/helpers/ConcurrentQueue.h" #include "util/helpers/Semaphore.h" #include <Metal/Metal.hpp> class RendererShaderMtl : public RendererShader { friend class ShaderMtlThreadPool; enum class COMPILATION_STATE : uint32 { NONE, QUEUED, COMPILING, DONE }; public: static void ShaderCacheLoading_begin(uint64 cacheTitleId); static void ShaderCacheLoading_end(); static void ShaderCacheLoading_Close(); static void Initialize(); static void Shutdown(); RendererShaderMtl(class MetalRenderer* mtlRenderer, ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& mslCode); virtual ~RendererShaderMtl(); MTL::Function* GetFunction() const { return m_function; } void PreponeCompilation(bool isRenderThread) override; bool IsCompiled() override; bool WaitForCompiled() override; private: class MetalRenderer* m_mtlr; MTL::Function* m_function = nullptr; StateSemaphore<COMPILATION_STATE> m_compilationState{ COMPILATION_STATE::NONE }; std::string m_mslCode; bool ShouldCountCompilation() const; MTL::Library* LibraryFromSource(); //MTL::Library* LibraryFromAIR(std::span<uint8> data); void CompileInternal(); //void CompileToAIR(); void FinishCompilation(); }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Metal/UtilityShaderSource.h ================================================ #pragma once #define __STRINGIFY(x) #x #define _STRINGIFY(x) __STRINGIFY(x) constexpr const char* utilityShaderSource = R"(#include <metal_stdlib> using namespace metal; #define GET_BUFFER_BINDING(index) (28 + index) #define GET_TEXTURE_BINDING(index) (29 + index) #define GET_SAMPLER_BINDING(index) (14 + index) constant float2 positions[] = {float2(-1.0, -3.0), float2(-1.0, 1.0), float2(3.0, 1.0)}; struct VertexOut { float4 position [[position]]; float2 texCoord; }; vertex VertexOut vertexFullscreen(ushort vid [[vertex_id]]) { VertexOut out; out.position = float4(positions[vid], 0.0, 1.0); out.texCoord = positions[vid] * 0.5 + 0.5; out.texCoord.y = 1.0 - out.texCoord.y; return out; } //fragment float4 fragmentPresent(VertexOut in [[stage_in]], texture2d<float> tex [[texture(0)]], //sampler samplr [[sampler(0)]]) { // return tex.sample(samplr, in.texCoord); //} vertex void vertexCopyBufferToBuffer(uint vid [[vertex_id]], device uint8_t* src [[buffer(GET_BUFFER_BINDING(0))]], device uint8_t* dst [[buffer(GET_BUFFER_BINDING(1))]]) { dst[vid] = src[vid]; } fragment float4 fragmentCopyDepthToColor(VertexOut in [[stage_in]], texture2d<float, access::read> src [[texture(GET_TEXTURE_BINDING(0))]]) { return float4(src.read(uint2(in.position.xy)).r, 0.0, 0.0, 0.0); } //struct RestrideParams { // uint oldStride; // uint newStride; //}; //vertex void vertexRestrideBuffer(uint vid [[vertex_id]], device uint8_t* src [[buffer//(GET_BUFFER_BINDING(0))]], device uint8_t* dst [[buffer(GET_BUFFER_BINDING(1))]], constant //RestrideParams& params [[buffer(GET_BUFFER_BINDING(2))]]) { // for (uint32_t i = 0; i < params.oldStride; i++) { // dst[vid * params.newStride + i] = src[vid * params.oldStride + i]; // } //} )"; ================================================ FILE: src/Cafe/HW/Latte/Renderer/MetalView.h ================================================ #pragma once #import <Cocoa/Cocoa.h> #import <QuartzCore/CAMetalLayer.h> @interface MetalView : NSView @end ================================================ FILE: src/Cafe/HW/Latte/Renderer/MetalView.mm ================================================ #include "Cafe/HW/Latte/Renderer/MetalView.h" @implementation MetalView -(BOOL) wantsUpdateLayer { return YES; } +(Class) layerClass { return [CAMetalLayer class]; } // copied from https://github.com/KhronosGroup/MoltenVK/blob/master/Demos/Cube/macOS/DemoViewController.m -(CALayer*) makeBackingLayer { CALayer* layer = [self.class.layerClass layer]; CGSize viewScale = [self convertSizeToBacking: CGSizeMake(1.0, 1.0)]; layer.contentsScale = MIN(viewScale.width, viewScale.height); return layer; } -(BOOL) layer: (CALayer *)layer shouldInheritContentsScale: (CGFloat)newScale fromWindow: (NSWindow *)window { if (newScale == layer.contentsScale) { return NO; } layer.contentsScale = newScale; return YES; } @end ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" #include "Cafe/HW/Latte/Core/LatteCachedFBO.h" #include "Common/GLInclude/GLInclude.h" class CachedFBOGL : public LatteCachedFBO { public: CachedFBOGL(uint64 key) : LatteCachedFBO(key) { // generate framebuffer if (glCreateFramebuffers && false) glCreateFramebuffers(1, &glId_fbo); else glGenFramebuffers(1, &glId_fbo); // bind framebuffer ((OpenGLRenderer*)g_renderer.get())->rendertarget_bindFramebufferObject(this); // setup framebuffer for (sint32 i = 0; i < 8; i++) { LatteTextureViewGL* colorTexViewGL = (LatteTextureViewGL*)colorBuffer[i].texture; if (!colorTexViewGL) glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + i, GL_TEXTURE_2D, 0, 0); else if (colorTexViewGL->dim == Latte::E_DIM::DIM_2D) glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + i, GL_TEXTURE_2D, colorTexViewGL->glTexId, 0); else if (colorTexViewGL->dim == Latte::E_DIM::DIM_3D) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT + i, colorTexViewGL->glTexId, 0, 0); else if (colorTexViewGL->dim == Latte::E_DIM::DIM_2D_ARRAY) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT + i, colorTexViewGL->glTexId, 0, 0); else if (colorTexViewGL->dim == Latte::E_DIM::DIM_CUBEMAP) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT + i, colorTexViewGL->glTexId, 0, 0); else { cemu_assert_suspicious(); } } if (depthBuffer.texture) { LatteTextureViewGL* depthTexViewGL = (LatteTextureViewGL*)depthBuffer.texture; if (depthTexViewGL->dim == Latte::E_DIM::DIM_2D) glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexViewGL->glTexId, 0); else if (depthTexViewGL->dim == Latte::E_DIM::DIM_2D_ARRAY) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexViewGL->glTexId, 0, 0); else cemu_assert_suspicious(); if (depthBuffer.hasStencil) { if (depthTexViewGL->dim == Latte::E_DIM::DIM_2D) glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthTexViewGL->glTexId, 0); else if (depthTexViewGL->dim == Latte::E_DIM::DIM_2D_ARRAY) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexViewGL->glTexId, 0, 0); else cemu_assert_suspicious(); } else { glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); } } else { glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); } SetDrawBuffers(); } private: void SetDrawBuffers() { GLenum buffers[8]; uint32 bufferCount = 0; for (uint32 i = 0; i < 8; i++) { if (colorBuffer[i].texture) { buffers[bufferCount] = GL_COLOR_ATTACHMENT0_EXT + i; bufferCount++; } else { // pad with GL_NONE entries to make sure that draw buffer indices match up with color attachment indices buffers[bufferCount] = GL_NONE; bufferCount++; } } // trim trailing GL_NONE while (bufferCount > 0 && buffers[bufferCount - 1] == GL_NONE) bufferCount--; if (bufferCount == 0) glDrawBuffer(GL_NONE); else glDrawBuffers(bufferCount, buffers); } public: GLuint glId_fbo{}; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp ================================================ #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "config/LaunchSettings.h" LatteTextureGL::LatteTextureGL(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) : LatteTexture(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth) { GenerateEmptyTextureFromGX2Dim(dim, this->glId_texture, this->glTexTarget, true); // set format info FormatInfoGL glFormatInfo; GetOpenGLFormatInfo(isDepth, overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)overwriteInfo.format : format, dim, &glFormatInfo); this->glInternalFormat = glFormatInfo.glInternalFormat; this->isAlternativeFormat = glFormatInfo.isUsingAlternativeFormat; // set debug name bool useGLDebugNames = false; #ifdef CEMU_DEBUG_ASSERT useGLDebugNames = true; #endif if (LaunchSettings::NSightModeEnabled()) useGLDebugNames = true; if (useGLDebugNames) { char textureDebugLabel[512]; sprintf(textureDebugLabel, "%08x_f%04x%s_p%04x_%dx%d", physAddress, (uint32)format, this->isDepth ? "_d" : "", pitch, width, height); glObjectLabel(GL_TEXTURE, this->glId_texture, -1, textureDebugLabel); } } LatteTextureGL::~LatteTextureGL() { glDeleteTextures(1, &glId_texture); catchOpenGLError(); } void LatteTextureGL::GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget, bool createForTargetType) { if (dim == Latte::E_DIM::DIM_2D) texTarget = GL_TEXTURE_2D; else if (dim == Latte::E_DIM::DIM_1D) texTarget = GL_TEXTURE_1D; else if (dim == Latte::E_DIM::DIM_3D) texTarget = GL_TEXTURE_3D; else if (dim == Latte::E_DIM::DIM_2D_ARRAY) texTarget = GL_TEXTURE_2D_ARRAY; else if (dim == Latte::E_DIM::DIM_CUBEMAP) texTarget = GL_TEXTURE_CUBE_MAP_ARRAY; else if (dim == Latte::E_DIM::DIM_2D_MSAA) texTarget = GL_TEXTURE_2D; // todo, GL_TEXTURE_2D_MULTISAMPLE ? else { cemu_assert_unimplemented(); } if(createForTargetType) texId = glCreateTextureWrapper(texTarget); // initializes the texture to texTarget (equivalent to calling glGenTextures + glBindTexture) else glGenTextures(1, &texId); } LatteTextureView* LatteTextureGL::CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) { return new LatteTextureViewGL(this, dim, format, firstMip, mipCount, firstSlice, sliceCount); } void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, FormatInfoGL* formatInfoOut) { formatInfoOut->isUsingAlternativeFormat = false; if (isDepth) { if (format == Latte::E_GX2SURFFMT::D24_S8_UNORM) { formatInfoOut->setFormat(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8); return; } else if (format == Latte::E_GX2SURFFMT::D24_S8_FLOAT) { formatInfoOut->setFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV); formatInfoOut->markAsAlternativeFormat(); return; } else if (format == Latte::E_GX2SURFFMT::D32_S8_FLOAT) { formatInfoOut->setFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV); return; } else if (format == Latte::E_GX2SURFFMT::D32_FLOAT) { formatInfoOut->setFormat(GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT); return; } else if (format == Latte::E_GX2SURFFMT::D16_UNORM) { formatInfoOut->setFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT); return; } // unsupported depth format cemuLog_log(LogType::Force, "OpenGL: Unsupported texture depth format 0x{:04x}", (uint32)format); // use placeholder format formatInfoOut->setFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT); formatInfoOut->markAsAlternativeFormat(); return; } bool glIsCompressed = false; bool isUsingAlternativeFormat = false; // set to true if there is no bit-perfect matching OpenGL format sint32 glInternalFormat; sint32 glSuppliedFormat; sint32 glSuppliedFormatType; // get format information if (format == Latte::E_GX2SURFFMT::R4_G4_UNORM) { formatInfoOut->setFormat(GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4); formatInfoOut->markAsAlternativeFormat(); return; } else if (format == Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM) { formatInfoOut->setFormat(GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4); return; } else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) { formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT); return; } else if (format == Latte::E_GX2SURFFMT::R16_G16_FLOAT) { formatInfoOut->setFormat(GL_RG16F, GL_RG, GL_HALF_FLOAT); return; } else if (format == Latte::E_GX2SURFFMT::R16_SNORM) { formatInfoOut->setFormat(GL_R16_SNORM, GL_RED, GL_SHORT); return; } else if (format == Latte::E_GX2SURFFMT::R16_FLOAT) { formatInfoOut->setFormat(GL_R16F, GL_RED, GL_HALF_FLOAT); return; } else if (format == Latte::E_GX2SURFFMT::BC1_UNORM || format == Latte::E_GX2SURFFMT::BC1_SRGB) { if (format == Latte::E_GX2SURFFMT::BC1_SRGB) formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, -1, -1); else formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, -1, -1); return; } else if (format == Latte::E_GX2SURFFMT::BC2_UNORM || format == Latte::E_GX2SURFFMT::BC2_SRGB) { // todo - use OpenGL BC2 format if available formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_FLOAT); formatInfoOut->markAsAlternativeFormat(); return; } else if (format == Latte::E_GX2SURFFMT::BC3_UNORM || format == Latte::E_GX2SURFFMT::BC3_SRGB) { if (format == Latte::E_GX2SURFFMT::BC3_SRGB) formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, -1, -1); else formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, -1, -1); return; } else if (format == Latte::E_GX2SURFFMT::BC4_UNORM || format == Latte::E_GX2SURFFMT::BC4_SNORM) { bool allowCompressed = true; if (dim != Latte::E_DIM::DIM_2D && dim != Latte::E_DIM::DIM_2D_ARRAY) allowCompressed = false; // RGTC1 does not support non-2D textures if (allowCompressed) { if (format == Latte::E_GX2SURFFMT::BC4_UNORM) formatInfoOut->setCompressed(GL_COMPRESSED_RED_RGTC1, -1, -1); else formatInfoOut->setCompressed(GL_COMPRESSED_SIGNED_RED_RGTC1, -1, -1); return; } else { formatInfoOut->setFormat(GL_RG16F, GL_RG, GL_FLOAT); formatInfoOut->markAsAlternativeFormat(); return; } } else if (format == Latte::E_GX2SURFFMT::BC5_UNORM || format == Latte::E_GX2SURFFMT::BC5_SNORM) { if (format == Latte::E_GX2SURFFMT::BC5_SNORM) formatInfoOut->setCompressed(GL_COMPRESSED_SIGNED_RG_RGTC2, -1, -1); else formatInfoOut->setCompressed(GL_COMPRESSED_RG_RGTC2, -1, -1); return; } else if (format == Latte::E_GX2SURFFMT::R32_FLOAT) { formatInfoOut->setFormat(GL_R32F, GL_RED, GL_FLOAT); return; } else if (format == Latte::E_GX2SURFFMT::R32_G32_FLOAT) { formatInfoOut->setFormat(GL_RG32F, GL_RG, GL_FLOAT); return; } else if (format == Latte::E_GX2SURFFMT::R32_G32_UINT) { formatInfoOut->setFormat(GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT); return; } else if (format == Latte::E_GX2SURFFMT::R32_UINT) { formatInfoOut->setFormat(GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT); return; } else if (format == Latte::E_GX2SURFFMT::R16_UINT) { // used by VC DS (New Super Mario Bros) formatInfoOut->setFormat(GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT); return; } else if (format == Latte::E_GX2SURFFMT::R8_UINT) { // used by VC DS (New Super Mario Bros) formatInfoOut->setFormat(GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE); return; } else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) { formatInfoOut->setFormat(GL_RGBA32F, GL_RGBA, GL_FLOAT); return; } else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) { if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) glInternalFormat = GL_SRGB8_ALPHA8; else glInternalFormat = GL_RGBA8; // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_UNSIGNED_BYTE; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SNORM) { glInternalFormat = GL_RGBA8_SNORM; // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_BYTE; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R8_UNORM) { glInternalFormat = GL_R8; // supplied format glSuppliedFormat = GL_RED; glSuppliedFormatType = GL_UNSIGNED_BYTE; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R8_SNORM) { glInternalFormat = GL_R8_SNORM; // supplied format glSuppliedFormat = GL_RED; glSuppliedFormatType = GL_BYTE; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R8_G8_UNORM) { glInternalFormat = GL_RG8; // supplied format glSuppliedFormat = GL_RG; glSuppliedFormatType = GL_UNSIGNED_BYTE; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R8_G8_SNORM) { glInternalFormat = GL_RG8_SNORM; // supplied format glSuppliedFormat = GL_RG; glSuppliedFormatType = GL_BYTE; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R16_UNORM) { glInternalFormat = GL_R16; // supplied format glSuppliedFormat = GL_RED; glSuppliedFormatType = GL_UNSIGNED_SHORT; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) { glInternalFormat = GL_RGBA16; // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_UNSIGNED_SHORT; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_SNORM) { glInternalFormat = GL_RGBA16_SNORM; // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_SHORT; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R16_G16_UNORM) { glInternalFormat = GL_RG16; // supplied format glSuppliedFormat = GL_RG; glSuppliedFormatType = GL_UNSIGNED_SHORT; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R5_G6_B5_UNORM) { glInternalFormat = GL_RGB565; // supplied format glSuppliedFormat = GL_RGB; glSuppliedFormatType = GL_UNSIGNED_SHORT_5_6_5_REV; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM) { glInternalFormat = GL_RGB5_A1; // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_UNSIGNED_SHORT_5_5_5_1; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM) { glInternalFormat = GL_RGB5_A1; // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_UNSIGNED_SHORT_5_5_5_1; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM) { glInternalFormat = GL_RGB10_A2; // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_UNSIGNED_INT_2_10_10_10_REV; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB) // used by Super Mario Maker { glInternalFormat = GL_RGB10_A2; // todo - how to handle SRGB for this format? // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_UNSIGNED_INT_2_10_10_10_REV; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM) { glInternalFormat = GL_RGB10_A2; // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_UNSIGNED_INT_10_10_10_2; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM) { glInternalFormat = GL_RGBA16_SNORM; // OpenGL has no signed version of GL_RGB10_A2 isUsingAlternativeFormat = true; // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_SHORT; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT) { glInternalFormat = GL_R11F_G11F_B10F; // supplied format glSuppliedFormat = GL_RGB; glSuppliedFormatType = GL_UNSIGNED_INT_10F_11F_11F_REV; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT) { glInternalFormat = GL_RGBA32UI; // supplied format glSuppliedFormat = GL_RGBA_INTEGER; glSuppliedFormatType = GL_UNSIGNED_INT; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT) { glInternalFormat = GL_RGBA16UI; // supplied format glSuppliedFormat = GL_RGBA_INTEGER; glSuppliedFormatType = GL_UNSIGNED_SHORT; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UINT) { glInternalFormat = GL_RGBA8UI; // supplied format glSuppliedFormat = GL_RGBA_INTEGER; glSuppliedFormatType = GL_UNSIGNED_BYTE; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R24_X8_UNORM) { // OpenGL has no color version of GL_DEPTH24_STENCIL8, therefore we use a 32-bit floating-point format instead glInternalFormat = GL_R32F; isUsingAlternativeFormat = true; // supplied format glSuppliedFormat = GL_RED; glSuppliedFormatType = GL_FLOAT; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::X24_G8_UINT) { // OpenGL has no X24_G8 format, we use RGBA8UI instead and manually swizzle the channels // this format is used in Resident Evil Revelations when scanning with the Genesis. It's also used in Cars 3: Driven to Win? glInternalFormat = GL_RGBA8UI; isUsingAlternativeFormat = true; // supplied format glSuppliedFormat = GL_RGBA; glSuppliedFormatType = GL_FLOAT; glIsCompressed = false; } else if (format == Latte::E_GX2SURFFMT::R32_X8_FLOAT) { // only available as depth format in OpenGL // used by Cars 3: Driven to Win // find a way to emulate this using a color format glInternalFormat = GL_DEPTH32F_STENCIL8; isUsingAlternativeFormat = false; // supplied format glSuppliedFormat = GL_DEPTH_STENCIL; glSuppliedFormatType = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; glIsCompressed = false; cemu_assert_debug(false); } else { cemuLog_log(LogType::Force, "OpenGL: Unsupported texture format 0x{:04x}", (uint32)format); cemu_assert_unimplemented(); } formatInfoOut->glInternalFormat = glInternalFormat; formatInfoOut->glSuppliedFormat = glSuppliedFormat; formatInfoOut->glSuppliedFormatType = glSuppliedFormatType; formatInfoOut->glIsCompressed = glIsCompressed; formatInfoOut->isUsingAlternativeFormat = isUsingAlternativeFormat; } void LatteTextureGL::AllocateOnHost() { auto hostTexture = this; cemu_assert_debug(hostTexture->isDataDefined == false); sint32 effectiveBaseWidth = hostTexture->width; sint32 effectiveBaseHeight = hostTexture->height; sint32 effectiveBaseDepth = hostTexture->depth; if (hostTexture->overwriteInfo.hasResolutionOverwrite) { effectiveBaseWidth = hostTexture->overwriteInfo.width; effectiveBaseHeight = hostTexture->overwriteInfo.height; effectiveBaseDepth = hostTexture->overwriteInfo.depth; } // calculate mip count sint32 mipLevels = std::min(hostTexture->mipLevels, hostTexture->maxPossibleMipLevels); mipLevels = std::max(mipLevels, 1); // create immutable storage if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) { cemu_assert_debug(effectiveBaseDepth == 1); glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); } else if (hostTexture->dim == Latte::E_DIM::DIM_1D) { cemu_assert_debug(effectiveBaseHeight == 1); cemu_assert_debug(effectiveBaseDepth == 1); glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth); } else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) { glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); } else if (hostTexture->dim == Latte::E_DIM::DIM_3D) { glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); } else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) { glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); } else { cemu_assert_unimplemented(); } } ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Common/GLInclude/GLInclude.h" class LatteTextureGL : public LatteTexture { public: LatteTextureGL(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); ~LatteTextureGL(); void AllocateOnHost() override; static void GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget, bool createForTargetType); protected: LatteTextureView* CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) override; public: struct FormatInfoGL { sint32 glInternalFormat; sint32 glSuppliedFormat; sint32 glSuppliedFormatType; bool glIsCompressed; bool isUsingAlternativeFormat{}; void setFormat(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType) { this->glInternalFormat = glInternalFormat; this->glSuppliedFormat = glSuppliedFormat; this->glSuppliedFormatType = glSuppliedFormatType; this->glIsCompressed = false; } void setCompressed(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType) { setFormat(glInternalFormat, glSuppliedFormat, glSuppliedFormatType); this->glIsCompressed = true; } void markAsAlternativeFormat() { this->isUsingAlternativeFormat = true; } }; static void GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, FormatInfoGL* formatInfoOut); // OpenGL stuff GLuint glId_texture; GLint glTexTarget; // GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_2D_ARRAY etc. GLint glInternalFormat = 0; // internal format of OpenGL texture (0 if not initialized) bool isAlternativeFormat{}; // if set to true, the OpenGL format is not a bit-perfect match for the GX2 format }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp ================================================ #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "config/LaunchSettings.h" LatteTextureViewGL::LatteTextureViewGL(LatteTextureGL* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount, bool registerView, bool forceCreateNewTexId) : LatteTextureView(texture, firstMip, mipCount, firstSlice, sliceCount, dim, format, registerView) { if (dim != texture->dim || format != texture->format || firstSlice != 0 || firstMip != 0 || mipCount != texture->mipLevels || sliceCount != texture->depth || forceCreateNewTexId) { LatteTextureGL::GenerateEmptyTextureFromGX2Dim(dim, glTexId, glTexTarget, false); this->glInternalFormat = 0; InitAliasView(); } else { glTexId = texture->glId_texture; glTexTarget = texture->glTexTarget; glInternalFormat = texture->glInternalFormat; } // mark all sampler properties as undefined samplerState.maxAniso = 0xFF; samplerState.filterMin = 0xFFFFFFFF; samplerState.filterMag = 0xFFFFFFFF; samplerState.maxMipLevels = 0xFF; samplerState.borderType = 0xFF; samplerState.borderColor[0] = 9999.0f; samplerState.borderColor[1] = 9999.0f; samplerState.borderColor[2] = 9999.0f; samplerState.borderColor[3] = 9999.0f; samplerState.clampS = 0xFF; samplerState.clampT = 0xFF; samplerState.clampR = 0xFF; samplerState.minLod = 0xFFFF; samplerState.maxLod = 0xFFFF; samplerState.lodBias = 0x7FFF; samplerState.depthCompareMode = 0xFF; samplerState.depthCompareFunc = 0xFF; swizzleR = 0xFF; swizzleG = 0xFF; swizzleB = 0xFF; swizzleA = 0xFF; } LatteTextureViewGL::~LatteTextureViewGL() { delete m_alternativeView; ((OpenGLRenderer*)g_renderer.get())->texture_notifyDelete(this); glDeleteTextures(1, &glTexId); } void LatteTextureViewGL::InitAliasView() { const auto texture = (LatteTextureGL*)baseTexture; // compute internal format if(texture->overwriteInfo.hasFormatOverwrite) { cemu_assert_debug(format == texture->format); glInternalFormat = texture->glInternalFormat; // for format overwrite no aliasing is allowed and thus we always inherit the internal format of the base texture } else if (baseTexture->isDepth) { // depth is handled differently cemu_assert(format == texture->format); // is depth alias with different format intended? glInternalFormat = texture->glInternalFormat; } else { LatteTextureGL::FormatInfoGL glFormatInfo; LatteTextureGL::GetOpenGLFormatInfo(baseTexture->isDepth, format, dim, &glFormatInfo); glInternalFormat = glFormatInfo.glInternalFormat; } catchOpenGLError(); if (firstMip >= texture->maxPossibleMipLevels) { cemuLog_logDebug(LogType::Force, "InitAliasView(): Out of bounds mip level requested"); glTextureView(glTexId, glTexTarget, texture->glId_texture, glInternalFormat, texture->maxPossibleMipLevels - 1, numMip, firstSlice, this->numSlice); } else glTextureView(glTexId, glTexTarget, texture->glId_texture, glInternalFormat, firstMip, numMip, firstSlice, numSlice); catchOpenGLError(); if (glTextureParameteri) { glTextureParameteri(glTexId, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTextureParameteri(glTexId, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTextureParameteri(glTexId, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTextureParameteri(glTexId, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTextureParameteri(glTexId, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTextureParameteri(glTexId, GL_TEXTURE_COMPARE_MODE, GL_NONE); } else { // todo - fallback for when DSA isn't supported } // set debug name bool useGLDebugNames = false; #ifdef CEMU_DEBUG_ASSERT useGLDebugNames = true; #endif if (LaunchSettings::NSightModeEnabled()) useGLDebugNames = true; if (useGLDebugNames) { char textureDebugLabel[512]; sprintf(textureDebugLabel, "%08x_f%04x_p%04x_viewFMT%04x%s_org%d", baseTexture->physAddress, (uint32)baseTexture->format, baseTexture->pitch, (uint32)this->format, baseTexture->isDepth?"_d":"", texture->glId_texture); glObjectLabel(GL_TEXTURE, glTexId, -1, textureDebugLabel); } } LatteTextureViewGL* LatteTextureViewGL::GetAlternativeView() { if (!m_alternativeView) m_alternativeView = new LatteTextureViewGL((LatteTextureGL*)baseTexture, dim, format, firstMip, numMip, firstSlice, numSlice, false, true); return m_alternativeView; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Common/GLInclude/GLInclude.h" class LatteTextureViewGL : public LatteTextureView { public: LatteTextureViewGL(class LatteTextureGL* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount, bool registerView = true, bool forceCreateNewTexId = false); ~LatteTextureViewGL(); LatteTextureViewGL* GetAlternativeView(); // GL specific states LatteSamplerState samplerState{}; uint8 swizzleR; uint8 swizzleG; uint8 swizzleB; uint8 swizzleA; GLuint glTexId; GLint glTexTarget; sint32 glInternalFormat; LatteTextureViewGL* m_alternativeView{ nullptr }; private: void InitAliasView(); }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLQuery.cpp ================================================ #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Common/GLInclude/GLInclude.h" class LatteQueryObjectGL : public LatteQueryObject { friend class OpenGLRenderer; bool getResult(uint64& numSamplesPassed) override; void begin() override; void end() override; private: GLuint m_queryId{}; GLenum m_glTarget{}; }; LatteQueryObject* OpenGLRenderer::occlusionQuery_create() { if (!list_queryCacheOcclusion.empty()) { LatteQueryObjectGL* queryObject = list_queryCacheOcclusion.front(); list_queryCacheOcclusion.erase(list_queryCacheOcclusion.begin() + 0); queryObject->m_glTarget = GL_SAMPLES_PASSED; queryObject->queryEnded = false; queryObject->queryEventStart = 0; queryObject->queryEventEnd = 0; return queryObject; } // no query object available in cache, create new query LatteQueryObjectGL* queryObject = new LatteQueryObjectGL(); glGenQueries(1, &queryObject->m_queryId); queryObject->m_glTarget = GL_SAMPLES_PASSED; queryObject->queryEnded = false; queryObject->index = 0; queryObject->queryEventStart = 0; queryObject->queryEventEnd = 0; catchOpenGLError(); return queryObject; } void OpenGLRenderer::occlusionQuery_destroy(LatteQueryObject* queryObj) { list_queryCacheOcclusion.emplace_back(static_cast<LatteQueryObjectGL*>(queryObj)); } void OpenGLRenderer::occlusionQuery_flush() { glFlush(); } bool LatteQueryObjectGL::getResult(uint64& numSamplesPassed) { GLint resultAvailable = 0; catchOpenGLError(); glGetQueryObjectiv(this->m_queryId, GL_QUERY_RESULT_AVAILABLE, &resultAvailable); if (resultAvailable == 0) return false; catchOpenGLError(); GLint64 queryResult = 0; glGetQueryObjecti64v(this->m_queryId, GL_QUERY_RESULT, &queryResult); numSamplesPassed = queryResult; return true; } void LatteQueryObjectGL::begin() { catchOpenGLError(); glBeginQueryIndexed(this->m_glTarget, this->index, this->m_queryId); catchOpenGLError(); } void LatteQueryObjectGL::end() { glEndQueryIndexed(this->m_glTarget, this->index); catchOpenGLError(); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp ================================================ #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "WindowSystem.h" #include "Cafe/HW/Latte/Core/LatteRingBuffer.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "imgui/imgui_impl_opengl3.h" #include "imgui/imgui_extension.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/OS/libs/gx2/GX2.h" class DefaultOpenGLCanvasCallbacks : public OpenGLCanvasCallbacks { } g_defaultOpenGLCanvasCallbacks; OpenGLCanvasCallbacks* g_openGLCanvasCallbacks = &g_defaultOpenGLCanvasCallbacks; void SetOpenGLCanvasCallbacks(OpenGLCanvasCallbacks* callbacks) { cemu_assert_debug(g_openGLCanvasCallbacks == &g_defaultOpenGLCanvasCallbacks); g_openGLCanvasCallbacks = callbacks; } void ClearOpenGLCanvasCallbacks() { cemu_assert_debug(g_openGLCanvasCallbacks != &g_defaultOpenGLCanvasCallbacks); g_openGLCanvasCallbacks = &g_defaultOpenGLCanvasCallbacks; } bool GLCanvas_HasPadViewOpen() { return g_openGLCanvasCallbacks->HasPadViewOpen(); } bool GLCanvas_MakeCurrent(bool padView) { return g_openGLCanvasCallbacks->MakeCurrent(padView); } void GLCanvas_SwapBuffers(bool swapTV, bool swapDRC) { g_openGLCanvasCallbacks->SwapBuffers(swapTV, swapDRC); } #define STRINGIFY2(X) #X #define STRINGIFY(X) STRINGIFY2(X) namespace CemuGL { #define GLFUNC(__type, __name) __type __name; #define EGLFUNC(__type, __name) __type __name; #include "Common/GLInclude/glFunctions.h" #undef GLFUNC #undef EGLFUNC } #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" static const int TEXBUFFER_SIZE = 1024 * 1024 * 32; // 32MB struct { // options bool useTextureUploadBuffer; // texture upload GLuint uploadBuffer; sint32 uploadIndex; void* uploadBufferPtr; LatteRingBuffer_t* uploadRingBuffer; // current texture work buffer (subrange of uploadRingBuffer) uint8* texWorkBuffer; sint32 texWorkBufferSize; // texture upload buffer (when not using persistent buffer) std::vector<uint8> texUploadBuffer; // FBO for fast clearing (on Nvidia or if glClearTexSubImage is not supported) GLuint clearFBO; }glRendererState; static const GLenum glDepthFuncTable[] = { GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS }; static const GLenum glAlphaTestFunc[] = { GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS }; OpenGLRenderer::OpenGLRenderer() { glRendererState.useTextureUploadBuffer = false; if (glRendererState.useTextureUploadBuffer) { glCreateBuffers(1, &glRendererState.uploadBuffer); glNamedBufferStorage(glRendererState.uploadBuffer, TEXBUFFER_SIZE, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT); void* buffer = glMapNamedBufferRange(glRendererState.uploadBuffer, 0, TEXBUFFER_SIZE, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); if (buffer == nullptr) { cemuLog_log(LogType::Force, "Failed to allocate GL texture upload buffer. Using traditional API instead"); cemu_assert_debug(false); } glRendererState.uploadBufferPtr = buffer; glRendererState.uploadRingBuffer = LatteRingBuffer_create((uint8*)buffer, TEXBUFFER_SIZE); glRendererState.uploadIndex = 0; } #if BOOST_OS_WINDOWS try { m_dxgi_wrapper = std::make_unique<DXGIWrapper>(); } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "Unable to create dxgi wrapper: {} (VRAM overlay stat won't be available)", ex.what()); } #endif } OpenGLRenderer::~OpenGLRenderer() { if(m_pipeline != 0) glDeleteProgramPipelines(1, &m_pipeline); glDeleteBuffers(1, &m_backbufferBlit_uniformBuffer); } OpenGLRenderer* OpenGLRenderer::GetInstance() { cemu_assert_debug(g_renderer && dynamic_cast<OpenGLRenderer*>(g_renderer.get())); return (OpenGLRenderer*)g_renderer.get(); } bool OpenGLRenderer::ImguiBegin(bool mainWindow) { if (!mainWindow) { GLCanvas_MakeCurrent(true); m_isPadViewContext = true; } if(!Renderer::ImguiBegin(mainWindow)) return false; renderstate_resetColorControl(); renderstate_resetDepthControl(); renderstate_resetStencilMask(); if (glClipControl) glClipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); ImGui_ImplOpenGL3_NewFrame(); ImGui_UpdateWindowInformation(mainWindow); ImGui::NewFrame(); return true; } void OpenGLRenderer::ImguiEnd() { ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); if (m_isPadViewContext) { GLCanvas_MakeCurrent(false); m_isPadViewContext = false; } if (glClipControl) glClipControl(GL_UPPER_LEFT, GL_NEGATIVE_ONE_TO_ONE); } ImTextureID OpenGLRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) { GLuint textureId; glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_2D, textureId); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glActiveTexture(GL_TEXTURE0); glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, size.x, size.y, 0, GL_RGB, GL_UNSIGNED_BYTE, data.data()); return (ImTextureID)(uintptr_t)textureId; } void OpenGLRenderer::DeleteTexture(ImTextureID id) { if (id) { GLuint textureId = (GLuint)(uintptr_t)id; glDeleteTextures(1, &textureId); } } void OpenGLRenderer::DeleteFontTextures() { ImGui_ImplOpenGL3_DestroyFontsTexture(); } typedef void(*GL_IMPORT)(); #if BOOST_OS_WINDOWS GL_IMPORT _GetOpenGLFunction(HMODULE hLib, const char* name) { GL_IMPORT r = (GL_IMPORT)wglGetProcAddress(name); if (r == nullptr) r = (GL_IMPORT)GetProcAddress(hLib, name); return r; } void LoadOpenGLImports() { HMODULE hLib = LoadLibraryA("opengl32.dll"); #define GLFUNC(__type, __name) __name = (__type)_GetOpenGLFunction(hLib, STRINGIFY(__name)); #include "Common/GLInclude/glFunctions.h" #undef GLFUNC } #elif BOOST_OS_LINUX || BOOST_OS_BSD GL_IMPORT _GetOpenGLFunction(void* hLib, PFNGLXGETPROCADDRESSPROC func, const char* name) { GL_IMPORT r = (GL_IMPORT)func((const GLubyte*)name); return r; } #include <dlfcn.h> // #define RTLD_NOW 0x00002 // #define RTLD_GLOBAL 0x00100 void LoadOpenGLImports() { PFNGLXGETPROCADDRESSPROC _glXGetProcAddress = nullptr; void* libGL = dlopen("libGL.so.1", RTLD_NOW | RTLD_GLOBAL); _glXGetProcAddress = (PFNGLXGETPROCADDRESSPROC)dlsym(libGL, "glXGetProcAddressARB"); if(!_glXGetProcAddress) { libGL = dlopen("libGL.so", RTLD_NOW | RTLD_GLOBAL); _glXGetProcAddress = (PFNGLXGETPROCADDRESSPROC)dlsym(libGL, "glXGetProcAddressARB"); } void* libEGL = dlopen("libEGL.so.1", RTLD_NOW | RTLD_GLOBAL); if(!libEGL) { libGL = dlopen("libEGL.so", RTLD_NOW | RTLD_GLOBAL); } #define GLFUNC(__type, __name) __name = (__type)_GetOpenGLFunction(libGL, _glXGetProcAddress, STRINGIFY(__name)); #define EGLFUNC(__type, __name) __name = (__type)dlsym(libEGL, STRINGIFY(__name)); #include "Common/GLInclude/glFunctions.h" #undef GLFUNC #undef EGLFUNC } #if BOOST_OS_LINUX || BOOST_OS_BSD // dummy function for all code that is statically linked with cemu and attempts to use eglSwapInterval // used to suppress wxWidgets calls to eglSwapInterval extern "C" EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval(EGLDisplay dpy, EGLint interval) { return EGL_TRUE; } #endif #elif BOOST_OS_MACOS void LoadOpenGLImports() { cemu_assert_unimplemented(); } #endif void OpenGLRenderer::Initialize() { Renderer::Initialize(); auto lock = cemuLog_acquire(); cemuLog_log(LogType::Force, "------- Init OpenGL graphics backend -------"); GLCanvas_MakeCurrent(false); LoadOpenGLImports(); GetVendorInformation(); #if BOOST_OS_WINDOWS if (wglSwapIntervalEXT) wglSwapIntervalEXT(0); // disable V-Sync per default #endif if (glMaxShaderCompilerThreadsARB) glMaxShaderCompilerThreadsARB(0xFFFFFFFF); cemuLog_log(LogType::Force, "OpenGL extensions:"); cemuLog_log(LogType::Force, "ARB_clip_control: {}", glClipControl ? "available" : "not supported"); cemuLog_log(LogType::Force, "ARB_get_program_binary: {}", (glGetProgramBinary != NULL && glProgramBinary != NULL) ? "available" : "not supported"); cemuLog_log(LogType::Force, "ARB_clear_texture: {}", (glClearTexImage != NULL) ? "available" : "not supported"); cemuLog_log(LogType::Force, "ARB_copy_image: {}", (glCopyImageSubData != NULL) ? "available" : "not supported"); cemuLog_log(LogType::Force, "NV_depth_buffer_float: {}", (glDepthRangedNV != NULL) ? "available" : "not supported"); // enable framebuffer SRGB support glEnable(GL_FRAMEBUFFER_SRGB); if (this->m_vendor != GfxVendor::AMD) { // don't enable this for AMD because GL_PROGRAM_POINT_SIZE breaks shader binaries for some reason // it also seems like AMD ignores GL_POINT_SPRITE and gl_PointCoord is always 0.0/0.0 // point size is always defined via shader glEnable(GL_PROGRAM_POINT_SIZE); // since we are using compatibility profile point sprites are disabled by default (e.g. gl_PointCoord wont work) glEnable(GL_POINT_SPRITE); } // check if clip control is available if (glClipControl) { glClipControl(GL_UPPER_LEFT, GL_NEGATIVE_ONE_TO_ONE); } else { cemuLog_log(LogType::Force, "ARB_CLIP_CONTROL not supported by graphics driver. This will lead to crashes or graphical artifacts."); } // set pixel unpack alignment to 1 byte glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // on NVIDIA rendering to SNORM textures is clamped to [0.0,1.0] range for non-core profiles // we can still disable the clamping using an older function (note that AMD and Intel don't need this, which is technically incorrect according to the compatibility profile spec) if (m_vendor == GfxVendor::Nvidia) glClampColor(GL_CLAMP_FRAGMENT_COLOR, GL_FALSE); glEnable(GL_PRIMITIVE_RESTART); glPrimitiveRestartIndex(0xFFFFFFFF); glGenProgramPipelines(1, &m_pipeline); glBindProgramPipeline(m_pipeline); lock.unlock(); // create framebuffer for fast clearing (avoid glClearTexSubImage on Nvidia) if (glCreateFramebuffers) glCreateFramebuffers(1, &glRendererState.clearFBO); else { glGenFramebuffers(1, &glRendererState.clearFBO); // bind to initialize glBindFramebuffer(GL_FRAMEBUFFER_EXT, glRendererState.clearFBO); glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); } // create uniform buffers for backbufferblit glCreateBuffers(1, &m_backbufferBlit_uniformBuffer); glNamedBufferStorage(m_backbufferBlit_uniformBuffer, sizeof(RendererOutputShader::OutputUniformVariables), nullptr, GL_DYNAMIC_STORAGE_BIT); draw_init(); catchOpenGLError(); glGenBuffers(1, &glStreamoutCacheRingBuffer); glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, glStreamoutCacheRingBuffer); glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, LatteStreamout_GetRingBufferSize(), NULL, GL_DYNAMIC_DRAW); glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0); catchOpenGLError(); // imgui ImGui_ImplOpenGL3_Init("#version 130"); } bool OpenGLRenderer::IsPadWindowActive() { return GLCanvas_HasPadViewOpen(); } void OpenGLRenderer::Flush(bool waitIdle) { glFlush(); if (waitIdle) glFinish(); } void OpenGLRenderer::NotifyLatteCommandProcessorIdle() { glFlush(); } void OpenGLRenderer::GetVendorInformation() { // example vendor strings: // ATI Technologies Inc. // NVIDIA Corporation // Intel char* glVendorString = (char*)glGetString(GL_VENDOR); char* glRendererString = (char*)glGetString(GL_RENDERER); char* glVersionString = (char*)glGetString(GL_VERSION); cemuLog_log(LogType::Force, "GL_VENDOR: {}", glVendorString ? glVendorString : "unknown"); cemuLog_log(LogType::Force, "GL_RENDERER: {}", glRendererString ? glRendererString : "unknown"); cemuLog_log(LogType::Force, "GL_VERSION: {}", glVersionString ? glVersionString : "unknown"); if(glVersionString && boost::icontains(glVersionString, "Mesa")) { m_vendor = GfxVendor::Mesa; return; } if (glVendorString) { if ((toupper(glVendorString[0]) == 'A' && toupper(glVendorString[1]) == 'T' && toupper(glVendorString[2]) == 'I') || (toupper(glVendorString[0]) == 'A' && toupper(glVendorString[1]) == 'M' && toupper(glVendorString[2]) == 'D')) { m_vendor = GfxVendor::AMD; return; } else if (memcmp(glVendorString, "NVIDIA", 6) == 0) { m_vendor = GfxVendor::Nvidia; return; } else if (memcmp(glVendorString, "Intel", 5) == 0) { m_vendor = GfxVendor::Intel; return; } } m_vendor = GfxVendor::Generic; } void _glDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Buffer")) return; if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "performance warning")) return; if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Dithering is enabled")) return; if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Blending is enabled, but is not supported for integer framebuffers")) return; if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "does not have a defined base level")) return; if(LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "has depth comparisons disabled, with a texture object")) return; cemuLog_log(LogType::Force, "GLDEBUG: {}", message); cemu_assert_debug(false); } void OpenGLRenderer::EnableDebugMode() { glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(_glDebugCallback, NULL); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, true); } void OpenGLRenderer::SwapBuffers(bool swapTV, bool swapDRC) { GLCanvas_SwapBuffers(swapTV, swapDRC); if (swapTV) cleanupAfterFrame(); } bool OpenGLRenderer::BeginFrame(bool mainWindow) { if (!mainWindow && !IsPadWindowActive()) return false; GLCanvas_MakeCurrent(!mainWindow); ClearColorbuffer(!mainWindow); return true; } void OpenGLRenderer::DrawEmptyFrame(bool mainWindow) { if (!BeginFrame(mainWindow)) return; SwapBuffers(mainWindow, !mainWindow); } void OpenGLRenderer::ClearColorbuffer(bool padView) { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); } void OpenGLRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool padView) { if(!m_screenshot_requested && m_screenshot_state == ScreenshotState::None) return; if (IsPadWindowActive()) { // we already took a pad view screenshow and want a main window screenshot if (m_screenshot_state == ScreenshotState::Main && padView) return; if (m_screenshot_state == ScreenshotState::Pad && !padView) return; // remember which screenshot is left to take if (m_screenshot_state == ScreenshotState::None) m_screenshot_state = padView ? ScreenshotState::Main : ScreenshotState::Pad; else m_screenshot_state = ScreenshotState::None; } else m_screenshot_state = ScreenshotState::None; int screenshotWidth, screenshotHeight; glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); texture_bindAndActivate(texView, 0); glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &screenshotWidth); glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &screenshotHeight); glPixelStorei(GL_PACK_ALIGNMENT, 1); // set alignment to 1 const sint32 pixelDataSize = screenshotWidth * screenshotHeight * 3; std::vector<uint8> rgb_data(pixelDataSize); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, rgb_data.data()); texture_bindAndActivate(nullptr, 0); const bool srcUsesSRGB = HAS_FLAG(texView->format, Latte::E_GX2SURFFMT::FMT_BIT_SRGB); const bool dstUsesSRGB = (!padView && LatteGPUState.tvBufferUsesSRGB) || (padView && LatteGPUState.drcBufferUsesSRGB); if((srcUsesSRGB && !dstUsesSRGB) || (!srcUsesSRGB && dstUsesSRGB)) { for (sint32 iy = 0; iy < screenshotHeight; ++iy) { for (sint32 ix = 0; ix < screenshotWidth; ++ix) { uint8* pData = rgb_data.data() + (ix + iy * screenshotWidth) * 3; if (srcUsesSRGB && !dstUsesSRGB) { // SRGB -> RGB pData[0] = SRGBComponentToRGB(pData[0]); pData[1] = SRGBComponentToRGB(pData[1]); pData[2] = SRGBComponentToRGB(pData[2]); } else if (!srcUsesSRGB && dstUsesSRGB) { // RGB -> SRGB pData[0] = RGBComponentToSRGB(pData[0]); pData[1] = RGBComponentToSRGB(pData[1]); pData[2] = RGBComponentToSRGB(pData[2]); } } } } SaveScreenshot(rgb_data, screenshotWidth, screenshotHeight, !padView); } void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) { if (padView && !IsPadWindowActive()) return; catchOpenGLError(); GLCanvas_MakeCurrent(padView); renderstate_resetColorControl(); renderstate_resetDepthControl(); attributeStream_reset(); // bind back buffer rendertarget_bindFramebufferObject(nullptr); if (clearBackground) { int windowWidth, windowHeight; if (padView) WindowSystem::GetPadWindowPhysSize(windowWidth, windowHeight); else WindowSystem::GetWindowPhysSize(windowWidth, windowHeight); g_renderer->renderTarget_setViewport(0, 0, windowWidth, windowHeight, 0.0f, 1.0f); g_renderer->ClearColorbuffer(padView); } shader_unbind(RendererShader::ShaderType::kGeometry); shader_bind(shader->GetVertexShader()); shader_bind(shader->GetFragmentShader()); // update and bind uniform buffer auto uniformBuffer = shader->FillUniformBlockBuffer(*texView, {imageWidth, imageHeight}, padView); glNamedBufferSubData(m_backbufferBlit_uniformBuffer, 0, sizeof(uniformBuffer), &uniformBuffer); glBindBufferBase(GL_UNIFORM_BUFFER, 0, m_backbufferBlit_uniformBuffer); // set viewport glViewportIndexedf(0, imageX, imageY, imageWidth, imageHeight); LatteTextureViewGL* texViewGL = (LatteTextureViewGL*)texView; texture_bindAndActivate(texView, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); texViewGL->samplerState.clampS = texViewGL->samplerState.clampT = 0xFF; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST); texViewGL->samplerState.filterMin = 0xFFFFFFFF; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST); texViewGL->samplerState.filterMag = 0xFFFFFFFF; glDisable(GL_FRAMEBUFFER_SRGB); uint16 indexData[6] = { 0,1,2,3,4,5 }; glDrawRangeElements(GL_TRIANGLES, 0, 5, 6, GL_UNSIGNED_SHORT, indexData); glEnable(GL_FRAMEBUFFER_SRGB); // unbind texture texture_bindAndActivate(nullptr, 0); catchOpenGLError(); // restore viewport glViewportIndexedf(0, prevViewportX, prevViewportY, prevViewportWidth, prevViewportHeight); // switch back to TV context if (padView) GLCanvas_MakeCurrent(false); } void OpenGLRenderer::renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ /*= false*/) { if (prevNearZ != nearZ || prevFarZ != farZ || _prevHalfZ != halfZ) { if (*(uint32*)&farZ == 0x3f7fffff) *(uint32*)&farZ = 0x3f800000; if (glDepthRangedNV) glDepthRangedNV(nearZ, farZ); else glDepthRange(nearZ, farZ); prevNearZ = nearZ; prevFarZ = farZ; } bool invertY = false; if (height < 0.0) { invertY = true; y += height; height = -height; } if (glClipControl && (_prevInvertY != invertY || _prevHalfZ != halfZ)) { GLenum clipDepth = halfZ ? GL_ZERO_TO_ONE : GL_NEGATIVE_ONE_TO_ONE; if (invertY) glClipControl(GL_LOWER_LEFT, clipDepth); // OpenGL style else glClipControl(GL_UPPER_LEFT, clipDepth); // DX style (default for GX2) _prevInvertY = invertY; _prevHalfZ = halfZ; } if (prevViewportX == x && prevViewportY == y && prevViewportWidth == width && prevViewportHeight == height) return; // viewport did not change glViewportIndexedf(0, x, y, width, height); prevViewportX = x; prevViewportY = y; prevViewportWidth = width; prevViewportHeight = height; } void OpenGLRenderer::renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) { if (prevScissorEnable != true) { // enable scissor box glEnable(GL_SCISSOR_TEST); prevScissorEnable = true; } glScissor(scissorX, scissorY, scissorWidth, scissorHeight); } LatteCachedFBO* OpenGLRenderer::rendertarget_createCachedFBO(uint64 key) { return new CachedFBOGL(key); } void OpenGLRenderer::rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) { auto cfboGL = (CachedFBOGL*)cfbo; if (prevBoundFBO == cfboGL->glId_fbo) { glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); prevBoundFBO = 0; } glDeleteFramebuffers(1, &cfboGL->glId_fbo); } // set active FBO void OpenGLRenderer::rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) { GLuint fboid; if (cfbo) { const auto cfboGL = (CachedFBOGL*)cfbo; fboid = cfboGL->glId_fbo; } else fboid = 0; if (prevBoundFBO != fboid) { glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboid); prevBoundFBO = fboid; } } void OpenGLRenderer::renderstate_setChannelTargetMask(uint32 renderTargetMask) { if (renderTargetMask != prevTargetColorMask) { for (sint32 i = 0; i < 8; i++) { uint32 targetRGBAMask = ((renderTargetMask >> (i * 4)) & 0xF); if (targetRGBAMask != ((prevTargetColorMask >> (i * 4)) & 0xF)) { // update color mask glColorMaski(i, (targetRGBAMask & 1) ? GL_TRUE : GL_FALSE, (targetRGBAMask & 2) ? GL_TRUE : GL_FALSE, (targetRGBAMask & 4) ? GL_TRUE : GL_FALSE, (targetRGBAMask & 8) ? GL_TRUE : GL_FALSE); } } prevTargetColorMask = renderTargetMask; } } void OpenGLRenderer::renderstate_setAlwaysWriteDepth() { if (prevDepthEnable == 0) { glEnable(GL_DEPTH_TEST); prevDepthEnable = 1; } glDepthFunc(GL_ALWAYS); prevDepthFunc = Latte::LATTE_DB_DEPTH_CONTROL::E_ZFUNC::ALWAYS; } static const GLuint table_glBlendSrcDst[] = { /* 0x00 */ GL_ZERO, /* 0x01 */ GL_ONE, /* 0x02 */ GL_SRC_COLOR, /* 0x03 */ GL_ONE_MINUS_SRC_COLOR, /* 0x04 */ GL_SRC_ALPHA, /* 0x05 */ GL_ONE_MINUS_SRC_ALPHA, /* 0x06 */ GL_DST_ALPHA, /* 0x07 */ GL_ONE_MINUS_DST_ALPHA, /* 0x08 */ GL_DST_COLOR, /* 0x09 */ GL_ONE_MINUS_DST_COLOR, /* 0x0A */ GL_SRC_ALPHA_SATURATE, /* 0x0B */ 0xFFFFFFFF, /* 0x0C */ 0xFFFFFFFF, /* 0x0D */ GL_CONSTANT_COLOR, /* 0x0E */ GL_ONE_MINUS_CONSTANT_COLOR, /* 0x0F */ GL_SRC1_COLOR, /* 0x10 */ GL_ONE_MINUS_SRC1_COLOR, /* 0x11 */ GL_SRC1_ALPHA, /* 0x12 */ GL_ONE_MINUS_SRC1_ALPHA, /* 0x13 */ GL_CONSTANT_ALPHA, /* 0x14 */ GL_ONE_MINUS_CONSTANT_ALPHA }; static GLuint GetGLBlendFactor(Latte::LATTE_CB_BLENDN_CONTROL::E_BLENDFACTOR blendFactor) { uint32 blendFactorU = (uint32)blendFactor; if (blendFactorU >= 0xF && blendFactorU <= 0x12) { debug_printf("Unsupported dual-source blending used\n"); cemu_assert_debug(false); // dual-source blending return GL_ZERO; } if (blendFactorU >= (sizeof(table_glBlendSrcDst) / sizeof(table_glBlendSrcDst[0]))) { debug_printf("GetGLBlendFactor: Constant 0x%x out of range\n", blendFactor); return GL_ZERO; } if (table_glBlendSrcDst[blendFactorU] == -1) { debug_printf("GetGLBlendFactor: Constant 0x%x is invalid\n", blendFactor); cemu_assert_debug(false); return GL_ZERO; } return table_glBlendSrcDst[blendFactorU]; } static const GLuint table_glBlendCombine[] = { GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_MIN, GL_MAX, GL_FUNC_REVERSE_SUBTRACT }; GLuint GetGLBlendCombineFunc(Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC combineFunc) { uint32 combineFuncU = (uint32)combineFunc; if (combineFuncU >= (sizeof(table_glBlendCombine) / sizeof(table_glBlendCombine[0]))) { cemu_assert_suspicious(); return GL_FUNC_ADD; } return table_glBlendCombine[combineFuncU]; } void* OpenGLRenderer::texture_acquireTextureUploadBuffer(uint32 size) { if (glRendererState.useTextureUploadBuffer) { glRendererState.texWorkBuffer = LatteRingBuffer_allocate(glRendererState.uploadRingBuffer, size, 1024); glRendererState.texWorkBufferSize = size; return glRendererState.texWorkBuffer; } // static memory buffer if (glRendererState.texUploadBuffer.size() < size) { glRendererState.texUploadBuffer.resize(size); } return glRendererState.texUploadBuffer.data(); } void OpenGLRenderer::texture_releaseTextureUploadBuffer(uint8* mem) { // do nothing } TextureDecoder* OpenGLRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) { TextureDecoder* texDecoder = nullptr; if (isDepth) { if (format == Latte::E_GX2SURFFMT::R32_FLOAT) { return TextureDecoder_R32_FLOAT::getInstance(); } if (format == Latte::E_GX2SURFFMT::D24_S8_UNORM) { return TextureDecoder_D24_S8::getInstance(); } else if (format == Latte::E_GX2SURFFMT::D24_S8_FLOAT) { return TextureDecoder_NullData64::getInstance(); } else if (format == Latte::E_GX2SURFFMT::D32_S8_FLOAT) { return TextureDecoder_D32_S8_UINT_X24::getInstance(); } else if (format == Latte::E_GX2SURFFMT::R32_FLOAT) { return TextureDecoder_R32_FLOAT::getInstance(); } else if (format == Latte::E_GX2SURFFMT::R16_UNORM) { return TextureDecoder_R16_FLOAT::getInstance(); } return nullptr; } if (format == Latte::E_GX2SURFFMT::R4_G4_UNORM) texDecoder = TextureDecoder_R4_G4_UNORM_To_RGBA4::getInstance(); else if (format == Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM) texDecoder = TextureDecoder_R4_G4_B4_A4_UNORM::getInstance(); else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) texDecoder = TextureDecoder_R16_G16_B16_A16_FLOAT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R16_G16_FLOAT) texDecoder = TextureDecoder_R16_G16_FLOAT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R16_SNORM) texDecoder = TextureDecoder_R16_SNORM::getInstance(); else if (format == Latte::E_GX2SURFFMT::R16_FLOAT) texDecoder = TextureDecoder_R16_FLOAT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R32_FLOAT) texDecoder = TextureDecoder_R32_FLOAT::getInstance(); else if (format == Latte::E_GX2SURFFMT::BC1_UNORM) texDecoder = TextureDecoder_BC1::getInstance(); else if (format == Latte::E_GX2SURFFMT::BC1_SRGB) texDecoder = TextureDecoder_BC1::getInstance(); else if (format == Latte::E_GX2SURFFMT::BC2_UNORM) texDecoder = TextureDecoder_BC2_UNORM_uncompress::getInstance(); else if (format == Latte::E_GX2SURFFMT::BC2_SRGB) texDecoder = TextureDecoder_BC2_SRGB_uncompress::getInstance(); else if (format == Latte::E_GX2SURFFMT::BC3_UNORM) texDecoder = TextureDecoder_BC3::getInstance(); else if (format == Latte::E_GX2SURFFMT::BC3_SRGB) texDecoder = TextureDecoder_BC3::getInstance(); else if (format == Latte::E_GX2SURFFMT::BC4_UNORM) { if (dim != Latte::E_DIM::DIM_2D && dim != Latte::E_DIM::DIM_2D_ARRAY) texDecoder = TextureDecoder_BC4_UNORM_uncompress::getInstance(); else texDecoder = TextureDecoder_BC4::getInstance(); } else if (format == Latte::E_GX2SURFFMT::BC4_SNORM) { if (dim != Latte::E_DIM::DIM_2D && dim != Latte::E_DIM::DIM_2D_ARRAY) texDecoder = TextureDecoder_BC4::getInstance(); else texDecoder = TextureDecoder_BC4_UNORM_uncompress::getInstance(); } else if (format == Latte::E_GX2SURFFMT::BC5_UNORM) texDecoder = TextureDecoder_BC5::getInstance(); else if (format == Latte::E_GX2SURFFMT::BC5_SNORM) texDecoder = TextureDecoder_BC5::getInstance(); else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM) texDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SNORM) texDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) texDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); else if (format == Latte::E_GX2SURFFMT::R8_UNORM) texDecoder = TextureDecoder_R8::getInstance(); else if (format == Latte::E_GX2SURFFMT::R8_SNORM) texDecoder = TextureDecoder_R8::getInstance(); else if (format == Latte::E_GX2SURFFMT::R8_G8_UNORM) texDecoder = TextureDecoder_R8_G8::getInstance(); else if (format == Latte::E_GX2SURFFMT::R8_G8_SNORM) texDecoder = TextureDecoder_R8_G8::getInstance(); else if (format == Latte::E_GX2SURFFMT::R16_UNORM) texDecoder = TextureDecoder_R16_UNORM::getInstance(); else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) texDecoder = TextureDecoder_R16_G16_B16_A16::getInstance(); else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_SNORM) texDecoder = TextureDecoder_R16_G16_B16_A16::getInstance(); else if (format == Latte::E_GX2SURFFMT::R16_G16_UNORM) texDecoder = TextureDecoder_R16_G16::getInstance(); else if (format == Latte::E_GX2SURFFMT::R5_G6_B5_UNORM) texDecoder = TextureDecoder_R5_G6_B5::getInstance(); else if (format == Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM) texDecoder = TextureDecoder_R5_G5_B5_A1_UNORM_swappedOpenGL::getInstance(); else if (format == Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM) texDecoder = TextureDecoder_A1_B5_G5_R5_UNORM::getInstance(); else if (format == Latte::E_GX2SURFFMT::R32_G32_FLOAT) texDecoder = TextureDecoder_R32_G32_FLOAT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R32_G32_UINT) texDecoder = TextureDecoder_R32_G32_UINT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R32_UINT) texDecoder = TextureDecoder_R32_UINT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R16_UINT) texDecoder = TextureDecoder_R16_UINT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R8_UINT) texDecoder = TextureDecoder_R8_UINT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) texDecoder = TextureDecoder_R32_G32_B32_A32_FLOAT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM) texDecoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance(); else if (format == Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM) texDecoder = TextureDecoder_A2_B10_G10_R10_UNORM_To_RGBA16::getInstance(); else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM) texDecoder = TextureDecoder_R10_G10_B10_A2_SNORM_To_RGBA16::getInstance(); else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB) texDecoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance(); else if (format == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT) texDecoder = TextureDecoder_R11_G11_B10_FLOAT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT) texDecoder = TextureDecoder_R32_G32_B32_A32_UINT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT) texDecoder = TextureDecoder_R16_G16_B16_A16_UINT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UINT) texDecoder = TextureDecoder_R8_G8_B8_A8_UINT::getInstance(); else if (format == Latte::E_GX2SURFFMT::R24_X8_UNORM) texDecoder = TextureDecoder_R24_X8::getInstance(); else if (format == Latte::E_GX2SURFFMT::X24_G8_UINT) texDecoder = TextureDecoder_X24_G8_UINT::getInstance(); else if (format == Latte::E_GX2SURFFMT::D32_S8_FLOAT) texDecoder = TextureDecoder_D32_S8_UINT_X24::getInstance(); else cemu_assert_debug(false); cemu_assert_debug(!isDepth); return texDecoder; } // use standard API to upload texture data void OpenGLRenderer_texture_loadSlice_normal(LatteTexture* hostTextureGeneric, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize) { auto hostTexture = (LatteTextureGL*)hostTextureGeneric; sint32 effectiveWidth = width; sint32 effectiveHeight = height; sint32 effectiveDepth = depth; cemu_assert_debug(hostTexture->overwriteInfo.hasResolutionOverwrite == false); // not supported in _loadSlice cemu_assert_debug(hostTexture->overwriteInfo.hasFormatOverwrite == false); // not supported in _loadSlice // get format info LatteTextureGL::FormatInfoGL glFormatInfo; LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)hostTexture->overwriteInfo.format : hostTexture->format, hostTexture->dim, &glFormatInfo); // upload slice catchOpenGLError(); if (mipIndex >= hostTexture->maxPossibleMipLevels) { cemuLog_logDebug(LogType::Force, "2D texture mip level allocated out of range"); return; } if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) { if (glFormatInfo.glIsCompressed) glCompressedTextureSubImage2DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, 0, effectiveWidth, effectiveHeight, glFormatInfo.glInternalFormat, imageSize, pixelData); else glTextureSubImage2DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, 0, effectiveWidth, effectiveHeight, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); } else if (hostTexture->dim == Latte::E_DIM::DIM_1D) { if (glFormatInfo.glIsCompressed) glCompressedTextureSubImage1DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, width, glFormatInfo.glInternalFormat, imageSize, pixelData); else glTextureSubImage1DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, width, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); } else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA || hostTexture->dim == Latte::E_DIM::DIM_3D || hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) { if (glFormatInfo.glIsCompressed) glCompressedTextureSubImage3DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glInternalFormat, imageSize, pixelData); else glTextureSubImage3DWrapper(hostTexture->glTexTarget, hostTexture->glId_texture, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); } catchOpenGLError(); } // use persistent buffers to upload data void OpenGLRenderer_texture_loadSlice_viaBuffers(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize) { // bind buffer glBindBuffer(GL_PIXEL_UNPACK_BUFFER, glRendererState.uploadBuffer); catchOpenGLError(); // upload data to buffer cemu_assert(imageSize <= TEXBUFFER_SIZE); cemu_assert_debug(glRendererState.texWorkBufferSize == imageSize); glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, (GLintptr)(glRendererState.texWorkBuffer - (uint8*)glRendererState.uploadBufferPtr), imageSize); catchOpenGLError(); OpenGLRenderer_texture_loadSlice_normal(hostTexture, width, height, depth, (void*)(glRendererState.texWorkBuffer - (uint8*)glRendererState.uploadBufferPtr), sliceIndex, mipIndex, imageSize); // unbind buffer and sync glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); catchOpenGLError(); } void OpenGLRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize) { if (glRendererState.useTextureUploadBuffer) OpenGLRenderer_texture_loadSlice_viaBuffers(hostTexture, width, height, depth, pixelData, sliceIndex, mipIndex, imageSize); else OpenGLRenderer_texture_loadSlice_normal(hostTexture, width, height, depth, pixelData, sliceIndex, mipIndex, imageSize); } void OpenGLRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) { LatteTextureGL* texGL = (LatteTextureGL*)hostTexture; cemu_assert_debug(!texGL->isDepth); sint32 eWidth, eHeight; hostTexture->GetEffectiveSize(eWidth, eHeight, mipIndex); renderstate_resetColorControl(); renderTarget_setViewport(0, 0, eWidth, eHeight, 0.0f, 1.0f); LatteMRT::BindColorBufferOnly(hostTexture->GetOrCreateView(mipIndex, 1, sliceIndex, 1)); glClearColor(r, g, b, a); glClear(GL_COLOR_BUFFER_BIT); } void OpenGLRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) { LatteTextureGL* texGL = (LatteTextureGL*)hostTexture; cemu_assert_debug(texGL->isDepth); sint32 eWidth, eHeight; hostTexture->GetEffectiveSize(eWidth, eHeight, mipIndex); renderstate_resetColorControl(); renderstate_resetDepthControl(); renderTarget_setViewport(0, 0, eWidth, eHeight, 0.0f, 1.0f); LatteMRT::BindDepthBufferOnly(hostTexture->GetOrCreateView(mipIndex, 1, sliceIndex, 1)); if (!hostTexture->hasStencil) clearStencil = false; if (clearDepth) glClearDepth(clearDepth); if (clearStencil) { renderstate_resetStencilMask(); glClearStencil((GLint)stencilValue); } glClear((clearDepth ? GL_DEPTH_BUFFER_BIT : 0) | (clearStencil ? GL_STENCIL_BUFFER_BIT : 0)); catchOpenGLError(); } void OpenGLRenderer::texture_clearSlice(LatteTexture* hostTextureGeneric, sint32 sliceIndex, sint32 mipIndex) { auto hostTexture = (LatteTextureGL*)hostTextureGeneric; // get OpenGL format info LatteTextureGL::FormatInfoGL formatInfoGL; LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->format, hostTexture->dim, &formatInfoGL); // get effective size of mip sint32 effectiveWidth, effectiveHeight; hostTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, mipIndex); // on Nvidia glClearTexImage and glClearTexSubImage has bad performance (clearing a 4K texture takes up to 50ms) // clearing with glTextureSubImage2D from a CPU RAM buffer is only slightly slower // clearing with glTextureSubImage2D from a OpenGL buffer is 10-20% faster than glClearTexImage // clearing with FBO and glClear is orders of magnitude faster than the other methods // (these are results from 2018, may be different now) if (this->m_vendor == GfxVendor::Nvidia || glClearTexSubImage == nullptr) { if (formatInfoGL.glIsCompressed) { cemuLog_logDebug(LogType::Force, "Unsupported clear for compressed texture"); return; // todo - create integer texture view to clear compressed textures } if (hostTextureGeneric->isDepth) { cemuLog_logDebug(LogType::Force, "Unsupported clear for depth texture"); return; // todo - use depth clear } glBindFramebuffer(GL_FRAMEBUFFER_EXT, glRendererState.clearFBO); // set attachment if (hostTexture->dim == Latte::E_DIM::DIM_2D) glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, hostTexture->glId_texture, mipIndex); else glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, hostTexture->glId_texture, mipIndex, sliceIndex); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glBindFramebuffer(GL_FRAMEBUFFER_EXT, prevBoundFBO); return; } if (glClearTexSubImage == nullptr) return; glClearTexSubImage(hostTexture->glId_texture, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, formatInfoGL.glSuppliedFormat, formatInfoGL.glSuppliedFormatType, NULL); } LatteTexture* OpenGLRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) { return new LatteTextureGL(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); } void OpenGLRenderer::texture_setActiveTextureUnit(sint32 index) { if (activeTextureUnit != index) { glActiveTexture(GL_TEXTURE0 + index); activeTextureUnit = index; } } void OpenGLRenderer::texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) { const auto textureViewGL = (LatteTextureViewGL*)textureView; // don't call glBindTexture if the texture is already bound if (m_latteBoundTextures[textureUnit] == textureViewGL) { texture_setActiveTextureUnit(textureUnit); return; // already bound } // bind m_latteBoundTextures[textureUnit] = textureViewGL; texture_setActiveTextureUnit(textureUnit); if (textureViewGL) { glBindTexture(textureViewGL->glTexTarget, textureViewGL->glTexId); } } void OpenGLRenderer::texture_notifyDelete(LatteTextureView* textureView) { for (uint32 i = 0; i < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3; i++) { if (m_latteBoundTextures[i] == textureView) m_latteBoundTextures[i] = nullptr; } } // set Latte texture, on the OpenGL renderer this behaves like _bindAndActivate() but doesn't call _setActiveTextureUnit() if the texture is already bound void OpenGLRenderer::texture_setLatteTexture(LatteTextureView* textureView1, uint32 textureUnit) { auto textureView = ((LatteTextureViewGL*)textureView1); cemu_assert_debug(textureUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3); if (m_latteBoundTextures[textureUnit] == textureView) return; if (textureView == nullptr) return; // bind if (glBindTextureUnit) { glBindTextureUnit(textureUnit, textureView->glTexId); m_latteBoundTextures[textureUnit] = textureView; activeTextureUnit = -1; } else { texture_setActiveTextureUnit(textureUnit); glBindTexture(textureView->glTexTarget, textureView->glTexId); m_latteBoundTextures[textureUnit] = textureView; } } void OpenGLRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) { auto srcGL = (LatteTextureGL*)src; auto dstGL = (LatteTextureGL*)dst; if ((srcGL->isAlternativeFormat || dstGL->isAlternativeFormat) && (srcGL->glInternalFormat != dstGL->glInternalFormat)) { if (srcGL->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT && dstGL->format == Latte::E_GX2SURFFMT::BC4_UNORM) { cemu_assert_debug(dstGL->dim != Latte::E_DIM::DIM_2D); // special case where BC4 format is replaced with R16F for array/3d-textures (since OpenGL's BC4 compression only supports 2D textures) texture_syncSliceSpecialBC4(srcGL, srcSlice, srcMip, dstGL, dstSlice, dstMip); return; } else { cemuLog_logDebug(LogType::Force, "_syncSlice() called with unhandled alternative format"); return; } } if (srcGL->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT && dstGL->format == Latte::E_GX2SURFFMT::BC3_UNORM) { if ((dstGL->width >> dstMip) < 4 || (dstGL->height >> dstMip) < 4) { texture_syncSliceSpecialIntegerToBC3(srcGL, srcSlice, srcMip, dstGL, dstSlice, dstMip); return; } } catchOpenGLError(); glCopyImageSubData(srcGL->glId_texture, srcGL->glTexTarget, srcMip, effectiveSrcX, effectiveSrcY, srcSlice, dstGL->glId_texture, dstGL->glTexTarget, dstMip, effectiveDstX, effectiveDstY, dstSlice, effectiveCopyWidth, effectiveCopyHeight, srcDepth); catchOpenGLError(); } LatteTextureReadbackInfo* OpenGLRenderer::texture_createReadback(LatteTextureView* textureView) { return new LatteTextureReadbackInfoGL(textureView); } void LatteDraw_resetAttributePointerCache(); void OpenGLRenderer::attributeStream_reset() { // reset attribute state attributeStream_unbindVertexBuffer(); // setup vertices SetArrayElementBuffer(0); LatteDraw_resetAttributePointerCache(); SetAttributeArrayState(0, true, -1); SetAttributeArrayState(1, true, -1); for (uint32 i = 0; i < GPU_GL_MAX_NUM_ATTRIBUTE; i++) SetAttributeArrayState(i, false, -1); } void OpenGLRenderer::bufferCache_init(const sint32 bufferSize) { glGenBuffers(1, &glAttributeCacheAB); glBindBuffer(GL_ARRAY_BUFFER, glAttributeCacheAB); glBufferData(GL_ARRAY_BUFFER, bufferSize, NULL, GL_STREAM_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } void OpenGLRenderer::attributeStream_bindVertexCacheBuffer() { if (_boundArrayBuffer == glAttributeCacheAB) return; _boundArrayBuffer = glAttributeCacheAB; glBindBuffer(GL_ARRAY_BUFFER, glAttributeCacheAB); } void OpenGLRenderer::attributeStream_unbindVertexBuffer() { if (_boundArrayBuffer == 0) return; _boundArrayBuffer = 0; glBindBuffer(GL_ARRAY_BUFFER, 0); } RendererShader* OpenGLRenderer::shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) { return new RendererShaderGL(type, baseHash, auxHash, isGameShader, isGfxPackShader, source); } void OpenGLRenderer::shader_bind(RendererShader* shader) { auto shaderGL = (RendererShaderGL*)shader; GLbitfield shaderBit; const auto program = shaderGL->GetProgram(); switch(shader->GetType()) { case RendererShader::ShaderType::kVertex: if (program == prevVertexShaderProgram) return; shaderBit = GL_VERTEX_SHADER_BIT; prevVertexShaderProgram = program; break; case RendererShader::ShaderType::kFragment: if (program == prevPixelShaderProgram) return; shaderBit = GL_FRAGMENT_SHADER_BIT; prevPixelShaderProgram = program; break; case RendererShader::ShaderType::kGeometry: if (program == prevGeometryShaderProgram) return; shaderBit = GL_GEOMETRY_SHADER_BIT; prevGeometryShaderProgram = program; break; default: UNREACHABLE; } catchOpenGLError(); glUseProgramStages(m_pipeline, shaderBit, program); catchOpenGLError(); } void OpenGLRenderer::shader_unbind(RendererShader::ShaderType shaderType) { switch (shaderType) { case RendererShader::ShaderType::kVertex: glUseProgramStages(m_pipeline, GL_VERTEX_SHADER_BIT, 0); prevVertexShaderProgram = -1; break; case RendererShader::ShaderType::kFragment: glUseProgramStages(m_pipeline, GL_FRAGMENT_SHADER_BIT, 0); prevPixelShaderProgram = -1; break; case RendererShader::ShaderType::kGeometry: glUseProgramStages(m_pipeline, GL_GEOMETRY_SHADER_BIT, 0); prevGeometryShaderProgram = -1; break; default: UNREACHABLE; } } void decodeBC4Block_UNORM(uint8* blockStorage, float* rOutput); void OpenGLRenderer::texture_syncSliceSpecialBC4(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex) { auto srcTextureGL = (LatteTextureGL*)srcTexture; auto dstTextureGL = (LatteTextureGL*)dstTexture; sint32 sourceTexWidth = std::max(srcTexture->width >> srcMipIndex, 1); sint32 sourceTexHeight = std::max(srcTexture->height >> srcMipIndex, 1); sint32 destTexWidth = std::max(dstTexture->width >> dstMipIndex, 1); sint32 destTexHeight = std::max(dstTexture->height >> dstMipIndex, 1); sint32 compressedCopyWidth = std::min(sourceTexWidth, std::max(1, destTexWidth / 4)); sint32 compressedCopyHeight = std::min(sourceTexHeight, std::max(1, destTexHeight / 4)); uint8* texelData = (uint8*)malloc(compressedCopyWidth*compressedCopyHeight * 8); float* pixelRGBA16fData = (float*)malloc(destTexWidth*destTexHeight * sizeof(float) * 2); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); if (glGetTextureSubImage) glGetTextureSubImage(srcTextureGL->glId_texture, 0, 0, 0, srcSliceIndex, compressedCopyWidth, compressedCopyHeight, 1, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, compressedCopyWidth * compressedCopyHeight * 8, texelData); for (sint32 bx = 0; bx < compressedCopyWidth; bx++) { for (sint32 by = 0; by < compressedCopyHeight; by++) { float rBlock[4 * 4]; decodeBC4Block_UNORM(texelData + (bx + by * compressedCopyWidth) * 8, rBlock); for (sint32 sy = 0; sy < std::min(4, destTexHeight - by * 4); sy++) { for (sint32 sx = 0; sx < std::min(4, destTexWidth - bx * 4); sx++) { sint32 pixelIndex = (bx * 4 + sx) + (by * 4 + sy)*destTexWidth; pixelRGBA16fData[pixelIndex * 2] = rBlock[sx + sy * 4]; pixelRGBA16fData[pixelIndex * 2 + 1] = rBlock[sx + sy * 4]; } } } } // upload mip if (glGetTextureSubImage && glTextureSubImage3D) glTextureSubImage3D(dstTextureGL->glId_texture, dstMipIndex, 0, 0, dstSliceIndex, destTexWidth, destTexHeight, 1, GL_RG, GL_FLOAT, pixelRGBA16fData); free(pixelRGBA16fData); free(texelData); catchOpenGLError(); } void OpenGLRenderer::texture_syncSliceSpecialIntegerToBC3(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex) { auto srcTextureGL = (LatteTextureGL*)srcTexture; auto dstTextureGL = (LatteTextureGL*)dstTexture; sint32 sourceTexWidth = std::max(srcTexture->width >> srcMipIndex, 1); sint32 sourceTexHeight = std::max(srcTexture->height >> srcMipIndex, 1); sint32 destTexWidth = std::max(dstTexture->width >> dstMipIndex, 1); sint32 destTexHeight = std::max(dstTexture->height >> dstMipIndex, 1); sint32 compressedCopyWidth = std::min(sourceTexWidth, std::max(1, destTexWidth / 4)); sint32 compressedCopyHeight = std::min(sourceTexHeight, std::max(1, destTexHeight / 4)); uint8* texelData = (uint8*)malloc(compressedCopyWidth*compressedCopyHeight * 16); catchOpenGLError(); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); catchOpenGLError(); if (glGetTextureSubImage) glGetTextureSubImage(srcTextureGL->glId_texture, 0, 0, 0, srcSliceIndex, compressedCopyWidth, compressedCopyHeight, 1, GL_RGBA_INTEGER, GL_UNSIGNED_INT, compressedCopyWidth * compressedCopyHeight * 16, texelData); //float* pixelRGBA16fData = (float*)malloc(destTexWidth*destTexHeight * sizeof(float) * 2); //for (sint32 bx = 0; bx < compressedCopyWidth; bx++) //{ // for (sint32 by = 0; by < compressedCopyHeight; by++) // { // float rBlock[4 * 4]; // decodeBC4Block_UNORM(texelData + (bx + by * compressedCopyWidth) * 8, rBlock); // for (sint32 sy = 0; sy < min(4, destTexHeight - by * 4); sy++) // { // for (sint32 sx = 0; sx < min(4, destTexWidth - bx * 4); sx++) // { // sint32 pixelIndex = (bx * 4 + sx) + (by * 4 + sy)*destTexWidth; // pixelRGBA16fData[pixelIndex * 2] = rBlock[sx + sy * 4]; // pixelRGBA16fData[pixelIndex * 2 + 1] = rBlock[sx + sy * 4]; // } // } // } //} // upload mip catchOpenGLError(); if (glGetTextureSubImage && glCompressedTextureSubImage3D) glCompressedTextureSubImage3D(dstTextureGL->glId_texture, dstMipIndex, 0, 0, dstSliceIndex, destTexWidth, destTexHeight, 1, dstTextureGL->glInternalFormat, compressedCopyWidth * compressedCopyHeight * 16, texelData); free(texelData); catchOpenGLError(); } void OpenGLRenderer::renderstate_updateBlendingAndColorControl() { catchOpenGLError(); const auto& colorControlReg = LatteGPUState.contextNew.CB_COLOR_CONTROL; const auto specialOp = colorControlReg.get_SPECIAL_OP(); const uint32 blendEnableMask = colorControlReg.get_BLEND_MASK(); const auto logicOp = colorControlReg.get_ROP(); cemu_assert_debug(!colorControlReg.get_MULTIWRITE_ENABLE()); // not supported uint32 renderTargetMask = LatteGPUState.contextNew.CB_TARGET_MASK.get_MASK(); if (specialOp == Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::DISABLE) { renderTargetMask = 0; } OpenGLRenderer::renderstate_setChannelTargetMask(renderTargetMask); catchOpenGLError(); // handle depth and stencil control // get depth control parameters bool depthEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_ENABLE(); auto depthFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_FUNC(); bool depthWriteEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_WRITE_ENABLE(); // get stencil control parameters bool stencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ENABLE(); bool backStencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_BACK_STENCIL_ENABLE(); auto frontStencilFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FUNC_F(); auto frontStencilZPass = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZPASS_F(); auto frontStencilZFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZFAIL_F(); auto frontStencilFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FAIL_F(); auto backStencilFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FUNC_B(); auto backStencilZPass = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZPASS_B(); auto backStencilZFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZFAIL_B(); auto backStencilFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FAIL_B(); // get stencil control parameters uint32 stencilCompareMaskFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILMASK_F(); uint32 stencilWriteMaskFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILWRITEMASK_F(); uint32 stencilRefFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILREF_F(); uint32 stencilCompareMaskBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILMASK_B(); uint32 stencilWriteMaskBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILWRITEMASK_B(); uint32 stencilRefBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILREF_B(); const static GLenum stencilActionGX2ToGL[] = { GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP }; if (prevStencilEnable != stencilEnable) { if (stencilEnable) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); prevStencilEnable = stencilEnable; } // update stencil parameters only if stencil is enabled if (stencilEnable) { if (!backStencilEnable) { // if back face stencil is disabled then use front parameters backStencilFunc = frontStencilFunc; backStencilZPass = frontStencilZPass; backStencilZFail = frontStencilZFail; backStencilFail = frontStencilFail; stencilRefBack = stencilRefFront; stencilCompareMaskBack = stencilCompareMaskFront; stencilWriteMaskBack = stencilWriteMaskFront; } // update stencil configuration for front side if (prevFrontStencilFail != frontStencilFail || prevFrontStencilZFail != frontStencilZFail || prevFrontStencilZPass != frontStencilZPass) { glStencilOpSeparate(GL_FRONT, stencilActionGX2ToGL[(size_t)frontStencilFail], stencilActionGX2ToGL[(size_t)frontStencilZFail], stencilActionGX2ToGL[(size_t)frontStencilZPass]); prevFrontStencilFail = frontStencilFail; prevFrontStencilZFail = frontStencilZFail; prevFrontStencilZPass = frontStencilZPass; } if (prevFrontStencilFunc != frontStencilFunc || prevStencilRefFront != stencilRefFront || prevStencilCompareMaskFront != stencilCompareMaskFront) { glStencilFuncSeparate(GL_FRONT, glDepthFuncTable[(size_t)frontStencilFunc], stencilRefFront, stencilCompareMaskFront); prevFrontStencilFunc = frontStencilFunc; prevStencilRefFront = stencilRefFront; prevStencilCompareMaskFront = stencilCompareMaskFront; } if (prevStencilWriteMaskFront != stencilWriteMaskFront) { glStencilMaskSeparate(GL_FRONT, stencilWriteMaskFront); prevStencilWriteMaskFront = stencilWriteMaskFront; } // update stencil configuration for back side if (prevBackStencilFail != backStencilFail || prevBackStencilZFail != backStencilZFail || prevBackStencilZPass != backStencilZPass) { glStencilOpSeparate(GL_BACK, stencilActionGX2ToGL[(size_t)backStencilFail], stencilActionGX2ToGL[(size_t)backStencilZFail], stencilActionGX2ToGL[(size_t)backStencilZPass]); prevBackStencilFail = backStencilFail; prevBackStencilZFail = backStencilZFail; prevBackStencilZPass = backStencilZPass; } if (prevBackStencilFunc != backStencilFunc || prevStencilRefBack != stencilRefBack || prevStencilCompareMaskBack != stencilCompareMaskBack) { glStencilFuncSeparate(GL_BACK, glDepthFuncTable[(size_t)backStencilFunc], stencilRefBack, stencilCompareMaskBack); prevBackStencilFunc = backStencilFunc; prevStencilRefBack = stencilRefBack; prevStencilCompareMaskBack = stencilCompareMaskBack; } if (prevStencilWriteMaskBack != stencilWriteMaskBack) { glStencilMaskSeparate(GL_BACK, stencilWriteMaskBack); prevStencilWriteMaskBack = stencilWriteMaskBack; } } if (depthEnable != prevDepthEnable) { if (depthEnable) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); prevDepthEnable = depthEnable; } if (depthWriteEnable != prevDepthWriteEnable) { if (depthWriteEnable) glDepthMask(GL_TRUE); else glDepthMask(GL_FALSE); prevDepthWriteEnable = depthWriteEnable; } if (depthFunc != prevDepthFunc) { glDepthFunc(glDepthFuncTable[(size_t)depthFunc]); prevDepthFunc = depthFunc; } catchOpenGLError(); uint32 blendChangeMask = blendEnableMask ^ prevBlendMask; if (blendChangeMask) { for (uint32 i = 0; i < 8; i++) { if ((blendChangeMask & (1 << i)) != 0) { // bit changed -> blend mode was toggled if ((blendEnableMask & (1 << i)) != 0) glEnablei(GL_BLEND, i); else glDisablei(GL_BLEND, i); } } prevBlendMask = blendEnableMask; } catchOpenGLError(); uint32* blendColorConstant = LatteGPUState.contextRegister + Latte::REGADDR::CB_BLEND_RED; if (blendColorConstant[0] != prevBlendColorConstant[0] || blendColorConstant[1] != prevBlendColorConstant[1] || blendColorConstant[2] != prevBlendColorConstant[2] || blendColorConstant[3] != prevBlendColorConstant[3]) { glBlendColor(*(float*)(blendColorConstant + 0), *(float*)(blendColorConstant + 1), *(float*)(blendColorConstant + 2), *(float*)(blendColorConstant + 3)); prevBlendColorConstant[0] = blendColorConstant[0]; prevBlendColorConstant[1] = blendColorConstant[1]; prevBlendColorConstant[2] = blendColorConstant[2]; prevBlendColorConstant[3] = blendColorConstant[3]; } for (uint32 i = 0; i < 8; i++) { const auto& blendControlReg = LatteGPUState.contextNew.CB_BLENDN_CONTROL[i]; if (blendControlReg.getRawValue() != prevBlendControlReg[i]) { if (blendControlReg.get_SEPARATE_ALPHA_BLEND()) { glBlendFuncSeparatei(i, GetGLBlendFactor(blendControlReg.get_COLOR_SRCBLEND()), GetGLBlendFactor(blendControlReg.get_COLOR_DSTBLEND()), GetGLBlendFactor(blendControlReg.get_ALPHA_SRCBLEND()), GetGLBlendFactor(blendControlReg.get_ALPHA_DSTBLEND())); glBlendEquationSeparatei(i, GetGLBlendCombineFunc(blendControlReg.get_COLOR_COMB_FCN()), GetGLBlendCombineFunc(blendControlReg.get_ALPHA_COMB_FCN())); } else { auto colorSrc = GetGLBlendFactor(blendControlReg.get_COLOR_SRCBLEND()); auto colorDst = GetGLBlendFactor(blendControlReg.get_COLOR_DSTBLEND()); glBlendFuncSeparatei(i, colorSrc, colorDst, colorSrc, colorDst); auto combineFunc = GetGLBlendCombineFunc(blendControlReg.get_COLOR_COMB_FCN()); glBlendEquationSeparatei(i, combineFunc, combineFunc); } prevBlendControlReg[i] = blendControlReg.getRawValue(); } } // set logic op uint32 logicOpGL = GL_COPY; if (logicOp == Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::COPY) logicOpGL = GL_COPY; else if (logicOp == Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::SET) logicOpGL = GL_SET; else if (logicOp == Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::CLEAR) logicOpGL = GL_CLEAR; else if (logicOp == Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::OR) logicOpGL = GL_OR; else cemu_assert_unimplemented(); if (prevLogicOp != logicOpGL) { if (logicOpGL != GL_COPY) glEnable(GL_COLOR_LOGIC_OP); else glDisable(GL_COLOR_LOGIC_OP); glLogicOp(logicOpGL); prevLogicOp = logicOpGL; } // polygon control const auto& polygonControlReg = LatteGPUState.contextNew.PA_SU_SC_MODE_CNTL; const auto frontFace = polygonControlReg.get_FRONT_FACE(); uint32 cullFront = polygonControlReg.get_CULL_FRONT(); uint32 cullBack = polygonControlReg.get_CULL_BACK(); // todo - polygon modes uint32 lineAndPointOffsetEnabled = polygonControlReg.get_OFFSET_PARA_ENABLED(); uint32 polyOffsetFrontEnabled = polygonControlReg.get_OFFSET_FRONT_ENABLED(); uint32 polyOffsetBackEnabled = polygonControlReg.get_OFFSET_BACK_ENABLED(); uint32 cullEnable = (cullFront || cullBack) ? 1 : 0; if (prevCullEnable != cullEnable) { if (cullEnable) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); prevCullEnable = cullEnable; } if (prevCullFrontFace != frontFace) { if (frontFace == Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CCW) glFrontFace(GL_CCW); else if (frontFace == Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CW) glFrontFace(GL_CW); else cemu_assert_unimplemented(); prevCullFrontFace = frontFace; } if (prevCullFront != cullFront || prevCullBack != cullBack) { if (cullFront && cullBack) glCullFace(GL_FRONT_AND_BACK); else if (cullFront == 0 && cullBack) glCullFace(GL_BACK); else if (cullFront && cullBack == 0) glCullFace(GL_FRONT); else ; // front and back disabled, do nothing here since we force disable culling via glDisable(GL_CULL_FACE) above prevCullFront = cullFront; prevCullBack = cullBack; } if (polyOffsetFrontEnabled != prevPolygonOffsetFrontEnabled) { if (polyOffsetFrontEnabled) glEnable(GL_POLYGON_OFFSET_FILL); else glDisable(GL_POLYGON_OFFSET_FILL); prevPolygonOffsetFrontEnabled = polyOffsetFrontEnabled; } if (polyOffsetFrontEnabled) { // if polygon offset is enabled check if offset/scale register changed if (LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_OFFSET.getRawValue() != prevPolygonFrontOffsetU32 || LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.getRawValue() != prevPolygonFrontScaleU32 || LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.getRawValue() != prevPolygonClampU32) { float frontScale = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.get_SCALE(); float frontOffset = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_OFFSET.get_OFFSET(); float offsetClamp = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.get_CLAMP(); frontScale /= 16.0f; //if( glPolygonOffsetClampEXT ) // glPolygonOffsetClampEXT(frontOffset, frontScale, offsetClamp); //else glPolygonOffset(frontScale, frontOffset); prevPolygonFrontOffsetU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.getRawValue(); prevPolygonFrontScaleU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.getRawValue(); prevPolygonClampU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.getRawValue(); } } // clip control catchOpenGLError(); cemu_assert_debug(LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_NEAR_DISABLE() == LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_FAR_DISABLE()); // near/far clipping disabled individually uint32 zClipEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_NEAR_DISABLE() == false; // todo: Mass Effect 3 uses precompiled display lists which seem to write values to PA_CL_CLIP_CNTL which aren't available via the traditional GX2 API if (prevZClipEnable != zClipEnable) { if (zClipEnable) { // disable depth clamping and enable clipping glDisable(GL_DEPTH_CLAMP); } else { // enable depth clamping and disable clipping glEnable(GL_DEPTH_CLAMP); } prevZClipEnable = zClipEnable; } catchOpenGLError(); // point size const auto& pointSizeReg = LatteGPUState.contextNew.PA_SU_POINT_SIZE; if (pointSizeReg.getRawValue() != prevPointSizeReg) { float pointWidth = (float)pointSizeReg.get_WIDTH() / 8.0f; float pointHeight = (float)pointSizeReg.get_HEIGHT() / 8.0f; if (pointWidth == 0.0f) glPointSize(1.0f / 8.0f); // minimum size else glPointSize(pointWidth); prevPointSizeReg = pointSizeReg.getRawValue(); catchOpenGLError(); } // primitive restart index uint32 primitiveRestartIndex = LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.get_RESTART_INDEX(); if (prevPrimitiveRestartIndex != primitiveRestartIndex) { glPrimitiveRestartIndex(primitiveRestartIndex); prevPrimitiveRestartIndex = primitiveRestartIndex; } } void OpenGLRenderer::renderstate_resetColorControl() { renderstate_setChannelTargetMask(0xF); // disable blending uint32 blendEnableMask = 0; for (uint32 i = 0; i < 8; i++) { if (((blendEnableMask^prevBlendMask)&(1 << i)) != 0) { // bit changed -> blend mode was toggled if ((blendEnableMask&(1 << i)) != 0) glEnablei(GL_BLEND, i); else glDisablei(GL_BLEND, i); } } prevBlendMask = blendEnableMask; // "forget" blend states for (uint32 i = 0; i < 8; i++) { prevBlendControlReg[i] = 0xFFFFFFFF; } // disable alpha test if (prevAlphaTestEnable != 0) { glDisable(GL_ALPHA_TEST); prevAlphaTestEnable = 0; } if (prevLogicOp != GL_COPY) { glDisable(GL_COLOR_LOGIC_OP); glLogicOp(GL_COPY); prevLogicOp = GL_COPY; } // disable culling uint32 cullEnable = 0; if (prevCullEnable != cullEnable) { glDisable(GL_CULL_FACE); prevCullEnable = 0; } // disable polygon offset if (prevPolygonOffsetFrontEnabled != 0) { glDisable(GL_POLYGON_OFFSET_FILL); prevPolygonOffsetFrontEnabled = 0; } // disable scissor box if (prevScissorEnable != false) { glDisable(GL_SCISSOR_TEST); prevScissorEnable = false; } } void OpenGLRenderer::renderstate_resetDepthControl() { if (prevDepthEnable) { glDisable(GL_DEPTH_TEST); prevDepthEnable = false; } if (!prevDepthWriteEnable) { glDepthMask(GL_TRUE); prevDepthWriteEnable = true; } if (prevStencilEnable) { glDisable(GL_STENCIL_TEST); prevStencilEnable = false; } //if (prevZClipEnable == 0) //{ // glDisable(GL_DEPTH_CLAMP); // prevZClipEnable = 1; //} glDisable(GL_DEPTH_CLAMP); prevZClipEnable = 1; if (prevPrimitiveRestartIndex != 0xFFFFFFFF) { glPrimitiveRestartIndex(0xFFFFFFFF); prevPrimitiveRestartIndex = 0xFFFFFFFF; } } void OpenGLRenderer::renderstate_resetStencilMask() { uint32 stencilWriteMaskFront = 0xFFFFFFFF; // enable front mask uint32 stencilWriteMaskBack = 0xFFFFFFFF; // enable back mask if (prevStencilWriteMaskFront != stencilWriteMaskFront) { glStencilMaskSeparate(GL_FRONT, stencilWriteMaskFront); prevStencilWriteMaskFront = stencilWriteMaskFront; } if (prevStencilWriteMaskBack != stencilWriteMaskBack) { glStencilMaskSeparate(GL_BACK, stencilWriteMaskBack); prevStencilWriteMaskBack = stencilWriteMaskBack; } } void OpenGLRenderer::cleanupAfterFrame() { ReleaseBufferCacheEntries(); } void OpenGLRenderer::ReleaseBufferCacheEntries() { for (auto& itr : m_destructionQueues.bufferCacheEntries) itr.free(); m_destructionQueues.bufferCacheEntries.clear(); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h ================================================ #pragma once #include "Common/GLInclude/GLInclude.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" #define GPU_GL_MAX_NUM_ATTRIBUTE (16) // Wii U GPU supports more than 16 but not all desktop GPUs do. Have to keep this at 16 until we find a better solution class OpenGLCanvasCallbacks { public: virtual bool HasPadViewOpen() const { return false; } virtual bool MakeCurrent(bool padView) { return false; } virtual void SwapBuffers(bool swapTV, bool swapDRC) {} virtual ~OpenGLCanvasCallbacks() = default; }; void SetOpenGLCanvasCallbacks(OpenGLCanvasCallbacks* callbacks); void ClearOpenGLCanvasCallbacks(); bool GLCanvas_HasPadViewOpen(); bool GLCanvas_MakeCurrent(bool padView); void GLCanvas_SwapBuffers(bool swapTV, bool swapDRC); class OpenGLRenderer : public Renderer { friend class OpenGLCanvas; public: OpenGLRenderer(); ~OpenGLRenderer(); RendererAPI GetType() override { return RendererAPI::OpenGL; } static OpenGLRenderer* GetInstance(); // imgui bool ImguiBegin(bool mainWindow) override; void ImguiEnd() override; ImTextureID GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) override; void DeleteTexture(ImTextureID id) override; void DeleteFontTextures() override; void Initialize() override; bool IsPadWindowActive() override; void Flush(bool waitIdle = false) override; void NotifyLatteCommandProcessorIdle() override; void EnableDebugMode() override; void SwapBuffers(bool swapTV = true, bool swapDRC = true) override; bool BeginFrame(bool mainWindow) override; void DrawEmptyFrame(bool mainWindow) override; void ClearColorbuffer(bool padView) override; void HandleScreenshotRequest(LatteTextureView* texView, bool padView) override; void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) override; void AppendOverlayDebugInfo() override { // does nothing currently } // rendertarget void renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ = false) override; void renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) override; LatteCachedFBO* rendertarget_createCachedFBO(uint64 key) override; void rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) override; void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) override; // renderstate functions void renderstate_setChannelTargetMask(uint32 renderTargetMask); void renderstate_setAlwaysWriteDepth(); void renderstate_updateBlendingAndColorControl(); void renderstate_resetColorControl(); void renderstate_resetDepthControl(); void renderstate_resetStencilMask(); void renderstate_updateTextureSettingsGL(LatteDecompilerShader* shaderContext, LatteTextureView* _hostTextureView, uint32 hostTextureUnit, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N texUnitWord4, uint32 texUnitIndex, bool isDepthSampler); // texture functions void* texture_acquireTextureUploadBuffer(uint32 size) override; void texture_releaseTextureUploadBuffer(uint8* mem) override; TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override; void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override; void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) override; void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override; void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) override; LatteTexture* texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) override; void texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) override; void texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit); void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) override; void texture_notifyDelete(LatteTextureView* textureView); LatteTextureReadbackInfo* texture_createReadback(LatteTextureView* textureView) override; void surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) override; void attributeStream_reset(); void bufferCache_init(const sint32 bufferSize) override; void bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) override; void bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) override; void buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) override; void buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) override; void _setupVertexAttributes(); void attributeStream_bindVertexCacheBuffer(); void attributeStream_unbindVertexBuffer(); static void SetAttributeArrayState(uint32 index, bool isEnabled, sint32 aluDivisor); static void SetArrayElementBuffer(GLuint arrayElementBuffer); // index (not used by OpenGL renderer yet) IndexAllocation indexData_reserveIndexMemory(uint32 size) override { cemu_assert_unimplemented(); return {}; } void indexData_releaseIndexMemory(IndexAllocation& allocation) override { cemu_assert_unimplemented(); } void indexData_uploadIndexMemory(IndexAllocation& allocation) override { cemu_assert_unimplemented(); } // uniform void uniformData_update(); // shader RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override; void shader_bind(RendererShader* shader); void shader_unbind(RendererShader::ShaderType shaderType); // streamout void streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) override; void streamout_begin() override; void bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) override; void streamout_rendererFinishDrawcall() override; // draw void draw_init(); void draw_beginSequence() override; void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override; void draw_endSequence() override; template<bool TIsMinimal, bool THasProfiling> void draw_genericDrawHandler(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType); // resource garbage collection void cleanupAfterFrame(); void ReleaseBufferCacheEntries(); // occlusion queries LatteQueryObject* occlusionQuery_create() override; void occlusionQuery_destroy(LatteQueryObject* queryObj) override; void occlusionQuery_flush() override; void occlusionQuery_updateState() override {}; private: void GetVendorInformation() override; void texture_setActiveTextureUnit(sint32 index); void texture_syncSliceSpecialBC4(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex); void texture_syncSliceSpecialIntegerToBC3(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex); GLuint m_pipeline = 0; bool m_isPadViewContext{}; // rendertarget viewport float prevViewportX = 0; float prevViewportY = 0; float prevViewportWidth = 0; float prevViewportHeight = 0; float prevNearZ = -999999.0f; float prevFarZ = -9999999.0f; bool _prevInvertY = false; bool _prevHalfZ = false; uint32 prevScissorX = 0; uint32 prevScissorY = 0; uint32 prevScissorWidth = 0; uint32 prevScissorHeight = 0; bool prevScissorEnable = false; bool m_isXfbActive = false; sint32 activeTextureUnit = 0; void* m_latteBoundTextures[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; // backbuffer blit GLuint m_backbufferBlit_uniformBuffer; // attribute stream GLuint glAttributeCacheAB{}; GLuint _boundArrayBuffer{}; // streamout GLuint glStreamoutCacheRingBuffer; // cfbo GLuint prevBoundFBO = 0; GLuint glId_fbo = 0; // renderstate uint32 prevBlendControlReg[8] = { 0 }; uint32 prevBlendMask = 0; uint32 prevLogicOp = 0; uint32 prevBlendColorConstant[4] = { 0 }; uint8 prevAlphaTestEnable = 0; bool prevDepthEnable = 0; bool prevDepthWriteEnable = 0; Latte::LATTE_DB_DEPTH_CONTROL::E_ZFUNC prevDepthFunc = (Latte::LATTE_DB_DEPTH_CONTROL::E_ZFUNC)-1; bool prevStencilEnable = false; Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC prevFrontStencilFunc = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC)-1; Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC prevBackStencilFunc = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC)-1; Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevFrontStencilZPass = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevFrontStencilZFail = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevFrontStencilFail = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevBackStencilZPass = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevBackStencilZFail = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevBackStencilFail = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; uint32 prevStencilRefFront = -1; uint32 prevStencilCompareMaskFront = -1; uint32 prevStencilWriteMaskFront = -1; uint32 prevStencilRefBack = -1; uint32 prevStencilCompareMaskBack = -1; uint32 prevStencilWriteMaskBack = -1; uint32 prevTargetColorMask = 0; // RGBA color mask for each render target (4 bit per target, starting from LSB) uint32 prevCullEnable = 0; uint32 prevCullFront = 0; uint32 prevCullBack = 0; Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE prevCullFrontFace = Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CCW; uint32 prevPolygonFrontScaleU32 = 0; uint32 prevPolygonFrontOffsetU32 = 0; uint32 prevPolygonClampU32 = 0; uint32 prevPolygonOffsetFrontEnabled = 0; uint32 prevPolygonOffsetBackEnabled = 0; uint32 prevZClipEnable = 0; uint32 prevPointSizeReg = 0xFFFFFFFF; uint32 prevPrimitiveRestartIndex = 0xFFFFFFFF; GLuint prevVertexShaderProgram = -1; GLuint prevGeometryShaderProgram = -1; GLuint prevPixelShaderProgram = -1; // occlusion queries std::vector<class LatteQueryObjectGL*> list_queryCacheOcclusion; // cache for unused queries // resource garbage collection struct BufferCacheReleaseQueueEntry { BufferCacheReleaseQueueEntry(VirtualBufferHeap_t* heap, VirtualBufferHeapEntry_t* entry) : m_heap(heap), m_entry(entry) {}; void free() { virtualBufferHeap_free(m_heap, m_entry); } VirtualBufferHeap_t* m_heap{}; VirtualBufferHeapEntry_t* m_entry{}; }; struct { sint32 index; std::vector<BufferCacheReleaseQueueEntry> bufferCacheEntries; }m_destructionQueues; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp ================================================ #include "Common/GLInclude/GLInclude.h" #include "Cafe/HW/Latte/Core/LatteRingBuffer.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteSoftware.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/GameProfile/GameProfile.h" #include "config/ActiveSettings.h" using _INDEX_TYPE = Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE; GLenum sGLActiveDrawMode = 0; extern bool hasValidFramebufferAttached; #define INDEX_CACHE_ENTRIES (8) typedef struct { MPTR prevIndexDataMPTR; sint32 prevIndexType; sint32 prevCount; // index data uint8* indexData; uint8* indexData2; uint32 indexBufferOffset; sint32 indexDataSize; // current size sint32 indexDataLimit; // maximum size // info uint32 maxIndex; uint32 minIndex; }indexDataCacheEntry_t; struct { indexDataCacheEntry_t indexCacheEntry[INDEX_CACHE_ENTRIES]; sint32 nextCacheEntryIndex; // info about currently used index data uint32 maxIndex; uint32 minIndex; uint8* indexData; // buffer GLuint glIndexCacheBuffer; VirtualBufferHeap_t* indexBufferVirtualHeap; uint8* mappedIndexBuffer; LatteRingBuffer_t* indexRingBuffer; uint8* tempIndexStorage; // misc bool initialized; GLuint glActiveElementArrayBuffer; }indexState = { 0 }; struct { uint8* vboOutput; uint32 vboStride; uint8 dataFormat; uint8 nfa; bool isSigned; }activeAttributePointer[LATTE_VS_ATTRIBUTE_LIMIT] = { 0 }; void LatteDraw_resetAttributePointerCache() { for (sint32 i = 0; i < LATTE_VS_ATTRIBUTE_LIMIT; i++) { activeAttributePointer[i].vboOutput = (uint8*)-1; activeAttributePointer[i].vboStride = (uint32)-1; } } void _setAttributeBufferPointerRaw(uint32 attributeShaderLoc, uint8* buffer, uint32 bufferSize, uint32 stride, LatteParsedFetchShaderAttribute_t* attrib, uint8* vboOutput, uint32 vboStride) { uint32 dataFormat = attrib->format; bool isSigned = attrib->isSigned != 0; uint8 nfa = attrib->nfa; // don't call glVertexAttribIPointer if parameters have not changed if (activeAttributePointer[attributeShaderLoc].vboOutput == vboOutput && activeAttributePointer[attributeShaderLoc].vboStride == vboStride && activeAttributePointer[attributeShaderLoc].dataFormat == dataFormat && activeAttributePointer[attributeShaderLoc].nfa == nfa && activeAttributePointer[attributeShaderLoc].isSigned == isSigned) { return; } activeAttributePointer[attributeShaderLoc].vboOutput = vboOutput; activeAttributePointer[attributeShaderLoc].vboStride = vboStride; activeAttributePointer[attributeShaderLoc].dataFormat = dataFormat; activeAttributePointer[attributeShaderLoc].nfa = nfa; activeAttributePointer[attributeShaderLoc].isSigned = isSigned; // setup attribute pointer if (dataFormat == FMT_32_32_32_32_FLOAT || dataFormat == FMT_32_32_32_32) { glVertexAttribIPointer(attributeShaderLoc, 4, GL_UNSIGNED_INT, vboStride, vboOutput); } else if (dataFormat == FMT_32_32_32_FLOAT || dataFormat == FMT_32_32_32) { glVertexAttribIPointer(attributeShaderLoc, 3, GL_UNSIGNED_INT, vboStride, vboOutput); } else if (dataFormat == FMT_32_32_FLOAT || dataFormat == FMT_32_32) { glVertexAttribIPointer(attributeShaderLoc, 2, GL_UNSIGNED_INT, vboStride, vboOutput); } else if (dataFormat == FMT_32_FLOAT || dataFormat == FMT_32) { glVertexAttribIPointer(attributeShaderLoc, 1, GL_UNSIGNED_INT, vboStride, vboOutput); } else if (dataFormat == FMT_8_8_8_8) { glVertexAttribIPointer(attributeShaderLoc, 4, GL_UNSIGNED_BYTE, vboStride, vboOutput); } else if (dataFormat == FMT_8_8) { // workaround for AMD (alignment must be 4 for 2xbyte) if (((uint32)(size_t)vboOutput & 0x3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD) { glVertexAttribIPointer(attributeShaderLoc, 4, GL_UNSIGNED_BYTE, vboStride, vboOutput - 2); } else { glVertexAttribIPointer(attributeShaderLoc, 2, GL_UNSIGNED_BYTE, vboStride, vboOutput); } } else if (dataFormat == FMT_8) { glVertexAttribIPointer(attributeShaderLoc, 1, GL_UNSIGNED_BYTE, vboStride, vboOutput); } else if (dataFormat == FMT_16_16_16_16_FLOAT || dataFormat == FMT_16_16_16_16) { glVertexAttribIPointer(attributeShaderLoc, 4, GL_UNSIGNED_SHORT, vboStride, vboOutput); } else if (dataFormat == FMT_16_16_FLOAT || dataFormat == FMT_16_16) { glVertexAttribIPointer(attributeShaderLoc, 2, GL_UNSIGNED_SHORT, vboStride, vboOutput); } else if (dataFormat == FMT_16_FLOAT || dataFormat == FMT_16) { glVertexAttribIPointer(attributeShaderLoc, 1, GL_UNSIGNED_SHORT, vboStride, vboOutput); } else if (dataFormat == FMT_2_10_10_10) { glVertexAttribIPointer(attributeShaderLoc, 1, GL_UNSIGNED_INT, vboStride, vboOutput); } else { debug_printf("_setAttributeBufferPointerRaw(): Unsupported format %d\n", dataFormat); cemu_assert_unimplemented(); } } bool glAttributeArrayIsEnabled[GPU_GL_MAX_NUM_ATTRIBUTE] = { 0 }; sint32 glAttributeArrayAluDivisor[GPU_GL_MAX_NUM_ATTRIBUTE] = { 0 }; void OpenGLRenderer::SetAttributeArrayState(uint32 index, bool isEnabled, sint32 aluDivisor) { cemu_assert_debug(index < GPU_GL_MAX_NUM_ATTRIBUTE); catchOpenGLError(); if (glAttributeArrayIsEnabled[index] != isEnabled) { if (isEnabled) { // enable glEnableVertexAttribArray(index); glAttributeArrayIsEnabled[index] = true; } else { // disable glDisableVertexAttribArray(index); glAttributeArrayIsEnabled[index] = false; } catchOpenGLError(); } // set divisor state if (glAttributeArrayAluDivisor[index] != aluDivisor) { if (aluDivisor <= 0) glVertexAttribDivisor(index, 0); else glVertexAttribDivisor(index, aluDivisor); glAttributeArrayAluDivisor[index] = aluDivisor; catchOpenGLError(); } } // Sets the currently active element array buffer and binds it void OpenGLRenderer::SetArrayElementBuffer(GLuint arrayElementBuffer) { if (arrayElementBuffer == indexState.glActiveElementArrayBuffer) return; glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, arrayElementBuffer); indexState.glActiveElementArrayBuffer = arrayElementBuffer; } typedef struct { MPTR physAddr; sint32 count; uint32 primitiveRestartIndex; uint32 primitiveMode; }indexDataCacheKey_t; typedef struct _indexDataCacheEntry_t { indexDataCacheKey_t key; _indexDataCacheEntry_t* nextInBucket; // points to next element in same bucket uint32 physSize; uint32 hash; _INDEX_TYPE indexType; //sint32 indexType; uint32 minIndex; uint32 maxIndex; uint32 lastAccessFrameCount; VirtualBufferHeapEntry_t* heapEntry; _indexDataCacheEntry_t* nextInMostRecentUsage; // points to element which was used more recently _indexDataCacheEntry_t* prevInMostRecentUsage; // points to element which was used less recently }indexDataCacheEntry2_t; #define INDEX_DATA_CACHE_BUCKETS (1783) indexDataCacheEntry2_t* indexDataCacheBucket[INDEX_DATA_CACHE_BUCKETS] = { 0 }; indexDataCacheEntry2_t* indexDataCacheFirst = nullptr; // points to least recently used item indexDataCacheEntry2_t* indexDataCacheLast = nullptr; // points to most recently used item sint32 indexDataCacheEntryCount = 0; void _appendToUsageLinkedList(indexDataCacheEntry2_t* entry) { if (indexDataCacheLast == nullptr) { indexDataCacheLast = entry; indexDataCacheFirst = entry; entry->nextInMostRecentUsage = nullptr; entry->prevInMostRecentUsage = nullptr; } else { indexDataCacheLast->nextInMostRecentUsage = entry; entry->prevInMostRecentUsage = indexDataCacheLast; entry->nextInMostRecentUsage = nullptr; indexDataCacheLast = entry; } } void _removeFromUsageLinkedList(indexDataCacheEntry2_t* entry) { if (entry->prevInMostRecentUsage) { entry->prevInMostRecentUsage->nextInMostRecentUsage = entry->nextInMostRecentUsage; } else indexDataCacheFirst = entry->nextInMostRecentUsage; if (entry->nextInMostRecentUsage) { entry->nextInMostRecentUsage->prevInMostRecentUsage = entry->prevInMostRecentUsage; } else indexDataCacheLast = entry->prevInMostRecentUsage; entry->prevInMostRecentUsage = nullptr; entry->nextInMostRecentUsage = nullptr; } void _removeFromBucket(indexDataCacheEntry2_t* entry) { uint32 indexDataBucketIdx = (uint32)((entry->key.physAddr + entry->key.count) ^ (entry->key.physAddr >> 16)) % INDEX_DATA_CACHE_BUCKETS; if (indexDataCacheBucket[indexDataBucketIdx] == entry) { indexDataCacheBucket[indexDataBucketIdx] = entry->nextInBucket; entry->nextInBucket = nullptr; return; } indexDataCacheEntry2_t* cacheEntryItr = indexDataCacheBucket[indexDataBucketIdx]; while (cacheEntryItr) { if (cacheEntryItr->nextInBucket == entry) { cacheEntryItr->nextInBucket = entry->nextInBucket; entry->nextInBucket = nullptr; return; } // next cacheEntryItr = cacheEntryItr->nextInBucket; } } void _decodeAndUploadIndexData(indexDataCacheEntry2_t* cacheEntry) { uint32 count = cacheEntry->key.count; uint32 primitiveRestartIndex = cacheEntry->key.primitiveRestartIndex; if (cacheEntry->indexType == _INDEX_TYPE::U16_BE) { // 16bit indices uint16* indexInputU16 = (uint16*)memory_getPointerFromPhysicalOffset(cacheEntry->key.physAddr); uint16* indexOutputU16 = (uint16*)indexState.tempIndexStorage; cemu_assert_debug(count != 0); uint16 indexMinU16 = 0xFFFF; uint16 indexMaxU16 = 0; if (primitiveRestartIndex < 0x10000) { // with primitive restart index uint16 primitiveRestartIndexU16 = (uint16)primitiveRestartIndex; for (uint32 i = 0; i < count; i++) { uint16 idxU16 = _swapEndianU16(*indexInputU16); indexInputU16++; if (primitiveRestartIndexU16 != idxU16) { indexMinU16 = std::min(indexMinU16, idxU16); indexMaxU16 = std::max(indexMaxU16, idxU16); } *indexOutputU16 = idxU16; indexOutputU16++; } } else { // without primitive restart index for (uint32 i = 0; i < count; i++) { uint16 idxU16 = _swapEndianU16(*indexInputU16); indexInputU16++; indexMinU16 = std::min(indexMinU16, idxU16); indexMaxU16 = std::max(indexMaxU16, idxU16); *indexOutputU16 = idxU16; indexOutputU16++; } } cacheEntry->minIndex = indexMinU16; cacheEntry->maxIndex = indexMaxU16; glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, cacheEntry->heapEntry->startOffset, count * sizeof(uint16), indexState.tempIndexStorage); performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += (count * sizeof(uint16)); } else if(cacheEntry->indexType == _INDEX_TYPE::U32_BE) { // 32bit indices uint32* indexInputU32 = (uint32*)memory_getPointerFromPhysicalOffset(cacheEntry->key.physAddr); uint32* indexOutputU32 = (uint32*)indexState.tempIndexStorage; cemu_assert_debug(count != 0); uint32 indexMinU32 = _swapEndianU32(*indexInputU32); uint32 indexMaxU32 = _swapEndianU32(*indexInputU32); for (uint32 i = 0; i < count; i++) { uint32 idxU32 = _swapEndianU32(*indexInputU32); indexInputU32++; if (idxU32 != primitiveRestartIndex) { indexMinU32 = std::min(indexMinU32, idxU32); indexMaxU32 = std::max(indexMaxU32, idxU32); } *indexOutputU32 = idxU32; indexOutputU32++; } cacheEntry->minIndex = indexMinU32; cacheEntry->maxIndex = indexMaxU32; glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, cacheEntry->heapEntry->startOffset, count * sizeof(uint32), indexState.tempIndexStorage); performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += (count * sizeof(uint32)); } else { cemu_assert_debug(false); } } void LatteDraw_cleanupAfterFrame() { // drop everything from cache that is older than 30 frames uint32 frameCounter = LatteGPUState.frameCounter; while (indexDataCacheFirst) { indexDataCacheEntry2_t* entry = indexDataCacheFirst; if ((frameCounter - entry->lastAccessFrameCount) < 30) break; // remove entry virtualBufferHeap_free(indexState.indexBufferVirtualHeap, entry->heapEntry); _removeFromUsageLinkedList(entry); _removeFromBucket(entry); free(entry); } } void LatteDrawGL_removeLeastRecentlyUsedIndexCacheEntries(sint32 count) { while (indexDataCacheFirst && count > 0) { indexDataCacheEntry2_t* entry = indexDataCacheFirst; // remove entry virtualBufferHeap_free(indexState.indexBufferVirtualHeap, entry->heapEntry); _removeFromUsageLinkedList(entry); _removeFromBucket(entry); free(entry); count--; } } uint32 LatteDrawGL_calculateIndexDataHash(uint8* data, uint32 size) { uint32 h = 0; if (size < 16) { // hash the bytes individually while (size != 0) { h += (uint32)*data; data++; size--; } return h; } // first 16 bytes h += *(uint32*)(data + 0); h += *(uint32*)(data + 4); h += *(uint32*)(data + 8); h += *(uint32*)(data + 12); // last 16 bytes data = data + ((size - 16)&~3); h += *(uint32*)(data + 0); h += *(uint32*)(data + 4); h += *(uint32*)(data + 8); h += *(uint32*)(data + 12); return h; } // index handling with cache // todo - Outdated cache implementation. Update OpenGL renderer to use the generic implementation that is also used by the Vulkan renderer void LatteDrawGL_prepareIndicesWithGPUCache(MPTR indexDataMPTR, _INDEX_TYPE indexType, sint32 count, sint32 primitiveMode) { if (indexType == _INDEX_TYPE::AUTO) { indexState.minIndex = 0; indexState.maxIndex = count - 1; // since no indices are used we don't need to unbind the element array buffer return; // automatic indices } OpenGLRenderer::SetArrayElementBuffer(indexState.glIndexCacheBuffer); uint32 indexDataBucketIdx = (uint32)((indexDataMPTR + count) ^ (indexDataMPTR >> 16)) % INDEX_DATA_CACHE_BUCKETS; // find matching entry uint32 primitiveRestartIndex = LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.get_RESTART_INDEX(); indexDataCacheEntry2_t* cacheEntryItr = indexDataCacheBucket[indexDataBucketIdx]; indexDataCacheKey_t compareKey; compareKey.physAddr = indexDataMPTR; compareKey.count = count; compareKey.primitiveMode = primitiveMode; compareKey.primitiveRestartIndex = primitiveRestartIndex; while (cacheEntryItr) { if (memcmp(&(cacheEntryItr->key), &compareKey, sizeof(compareKey)) != 0) { // next cacheEntryItr = cacheEntryItr->nextInBucket; continue; } // entry found indexState.minIndex = cacheEntryItr->minIndex; indexState.maxIndex = cacheEntryItr->maxIndex; indexState.indexData = (uint8*)(size_t)cacheEntryItr->heapEntry->startOffset; cacheEntryItr->lastAccessFrameCount = LatteGPUState.frameCounter; // check if the data changed uint32 h = LatteDrawGL_calculateIndexDataHash(memory_getPointerFromPhysicalOffset(indexDataMPTR), cacheEntryItr->physSize); if (cacheEntryItr->hash != h) { cemuLog_logDebug(LogType::Force, "IndexData hash changed"); _decodeAndUploadIndexData(cacheEntryItr); cacheEntryItr->hash = h; } // move entry to the front _removeFromUsageLinkedList(cacheEntryItr); _appendToUsageLinkedList(cacheEntryItr); return; } // calculate size of index data in cache sint32 cacheIndexDataSize = 0; if (indexType == _INDEX_TYPE::U16_BE || indexType == _INDEX_TYPE::U16_LE) cacheIndexDataSize = count * sizeof(uint16); else cacheIndexDataSize = count * sizeof(uint32); // no matching entry, create new one VirtualBufferHeapEntry_t* heapEntry = virtualBufferHeap_allocate(indexState.indexBufferVirtualHeap, cacheIndexDataSize); if (heapEntry == nullptr) { while (true) { LatteDrawGL_removeLeastRecentlyUsedIndexCacheEntries(10); heapEntry = virtualBufferHeap_allocate(indexState.indexBufferVirtualHeap, cacheIndexDataSize); if (heapEntry != nullptr) break; if (indexDataCacheFirst == nullptr) { cemuLog_log(LogType::Force, "Unable to allocate entry in index cache"); assert_dbg(); } } } indexDataCacheEntry2_t* cacheEntry = (indexDataCacheEntry2_t*)malloc(sizeof(indexDataCacheEntry2_t)); memset(cacheEntry, 0, sizeof(indexDataCacheEntry2_t)); cacheEntry->key.physAddr = indexDataMPTR; cacheEntry->physSize = (indexType == _INDEX_TYPE::U16_BE || indexType == _INDEX_TYPE::U16_LE) ? (count * sizeof(uint16)) : (count * sizeof(uint32)); cacheEntry->hash = LatteDrawGL_calculateIndexDataHash(memory_getPointerFromPhysicalOffset(indexDataMPTR), cacheEntry->physSize); cacheEntry->key.count = count; cacheEntry->key.primitiveRestartIndex = primitiveRestartIndex; cacheEntry->indexType = indexType; cacheEntry->key.primitiveMode = primitiveMode; cacheEntry->heapEntry = heapEntry; cacheEntry->lastAccessFrameCount = LatteGPUState.frameCounter; // append entry in bucket list cacheEntry->nextInBucket = indexDataCacheBucket[indexDataBucketIdx]; indexDataCacheBucket[indexDataBucketIdx] = cacheEntry; // append as most recently used entry _appendToUsageLinkedList(cacheEntry); // decode and upload the data _decodeAndUploadIndexData(cacheEntry); indexDataCacheEntryCount++; indexState.minIndex = cacheEntry->minIndex; indexState.maxIndex = cacheEntry->maxIndex; indexState.indexData = (uint8*)(size_t)cacheEntry->heapEntry->startOffset; } void LatteDraw_handleSpecialState8_clearAsDepth() { if (LatteGPUState.contextNew.GetSpecialStateValues()[0] == 0) cemuLog_logDebug(LogType::Force, "Special state 8 requires special state 0 but it is not set?"); // get depth buffer information uint32 regDepthBuffer = LatteGPUState.contextRegister[mmDB_HTILE_DATA_BASE]; uint32 regDepthSize = LatteGPUState.contextRegister[mmDB_DEPTH_SIZE]; uint32 regDepthBufferInfo = LatteGPUState.contextRegister[mmDB_DEPTH_INFO]; // get format and tileMode from info reg uint32 depthBufferTileMode = (regDepthBufferInfo >> 15) & 0xF; MPTR depthBufferPhysMem = regDepthBuffer << 8; uint32 depthBufferPitch = (((regDepthSize >> 0) & 0x3FF) + 1); uint32 depthBufferHeight = ((((regDepthSize >> 10) & 0xFFFFF) + 1) / depthBufferPitch); depthBufferPitch <<= 3; depthBufferHeight <<= 3; uint32 depthBufferWidth = depthBufferPitch; sint32 sliceIndex = 0; // todo sint32 mipIndex = 0; // clear all color buffers that match the format of the depth buffer sint32 searchIndex = 0; bool targetFound = false; while (true) { LatteTextureView* view = LatteTC_LookupTextureByData(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, 1, sliceIndex, 1, &searchIndex); if (!view) { // should we clear in RAM instead? break; } sint32 effectiveClearWidth = view->baseTexture->width; sint32 effectiveClearHeight = view->baseTexture->height; LatteTexture_scaleToEffectiveSize(view->baseTexture, &effectiveClearWidth, &effectiveClearHeight, 0); // hacky way to get clear color float* regClearColor = (float*)(LatteGPUState.contextRegister + 0xC000 + 0); // REG_BASE_ALU_CONST uint8 clearColor[4] = { 0 }; clearColor[0] = (uint8)(regClearColor[0] * 255.0f); clearColor[1] = (uint8)(regClearColor[1] * 255.0f); clearColor[2] = (uint8)(regClearColor[2] * 255.0f); clearColor[3] = (uint8)(regClearColor[3] * 255.0f); // todo - use fragment shader software emulation (evoke for one pixel) to determine clear color // todo - dont clear entire slice, use effectiveClearWidth, effectiveClearHeight if (g_renderer->GetType() == RendererAPI::OpenGL) { //cemu_assert_debug(false); // implement g_renderer->texture_clearColorSlice properly for OpenGL renderer if (glClearTexSubImage) glClearTexSubImage(((LatteTextureViewGL*)view)->glTexId, mipIndex, 0, 0, 0, effectiveClearWidth, effectiveClearHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor); } else { if (view->baseTexture->isDepth) g_renderer->texture_clearDepthSlice(view->baseTexture, sliceIndex + view->firstSlice, mipIndex + view->firstMip, true, view->baseTexture->hasStencil, 0.0f, 0); else g_renderer->texture_clearColorSlice(view->baseTexture, sliceIndex + view->firstSlice, mipIndex + view->firstMip, clearColor[0], clearColor[1], clearColor[2], clearColor[3]); } } } void LatteDrawGL_doDraw(_INDEX_TYPE indexType, uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count) { if (indexType == _INDEX_TYPE::U16_BE) { // 16bit index, big endian if (instanceCount > 1 || baseInstance != 0) { glDrawElementsInstancedBaseVertexBaseInstance(sGLActiveDrawMode, count, GL_UNSIGNED_SHORT, indexState.indexData, instanceCount, baseVertex, baseInstance); } else { if (baseVertex != 0) glDrawRangeElementsBaseVertex(sGLActiveDrawMode, indexState.minIndex, indexState.maxIndex, count, GL_UNSIGNED_SHORT, indexState.indexData, baseVertex); else glDrawRangeElements(sGLActiveDrawMode, indexState.minIndex, indexState.maxIndex, count, GL_UNSIGNED_SHORT, indexState.indexData); } } else if (indexType == _INDEX_TYPE::U32_BE) { // 32bit index, big endian if (instanceCount > 1 || baseInstance != 0) { //debug_printf("Render instanced\n"); glDrawElementsInstancedBaseVertexBaseInstance(sGLActiveDrawMode, count, GL_UNSIGNED_INT, indexState.indexData, instanceCount, baseVertex, baseInstance); } else { glDrawRangeElementsBaseVertex(sGLActiveDrawMode, indexState.minIndex, indexState.maxIndex, count, GL_UNSIGNED_INT, indexState.indexData, baseVertex); } } else if (indexType == _INDEX_TYPE::AUTO) { // render without index (automatic index generation) cemu_assert_debug(baseInstance == 0); if (instanceCount > 1) glDrawArraysInstanced(sGLActiveDrawMode, baseVertex, count, instanceCount); else { glDrawArrays(sGLActiveDrawMode, baseVertex, count); } } else { cemu_assert_debug(false); } } uint32 _glVertexBufferOffset[32] = { 0 }; void OpenGLRenderer::buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) { _glVertexBufferOffset[bufferIndex] = offset; } void OpenGLRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) { switch (shaderType) { case LatteConst::ShaderType::Vertex: bufferIndex += 0; break; case LatteConst::ShaderType::Pixel: bufferIndex += 32; break; case LatteConst::ShaderType::Geometry: bufferIndex += 64; break; } if (offset == 0 && size == 0) { // when binding NULL we just bind some arbitrary undefined data so the OpenGL driver is happy since a size of 0 is not allowed (should we bind a buffer filled with zeroes instead?) glBindBufferRange(GL_UNIFORM_BUFFER, bufferIndex, glAttributeCacheAB, 0, 32); return; } glBindBufferRange(GL_UNIFORM_BUFFER, bufferIndex, glAttributeCacheAB, offset, size); } void LatteDraw_resetAttributePointerCache(); void _resetAttributes(LatteParsedFetchShaderBufferGroup_t* attribGroup, bool* attributeArrayUsed) { for (sint32 i = 0; i < attribGroup->attribCount; i++) { LatteParsedFetchShaderAttribute_t* attrib = attribGroup->attrib + i; sint32 attributeShaderLocation = attrib->semanticId; // we now bind to the semanticId instead attributeArrayUsed[attributeShaderLocation] = false; } } void OpenGLRenderer::_setupVertexAttributes() { LatteFetchShader* fetchShader = LatteSHRC_GetActiveFetchShader(); LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); catchOpenGLError(); // bind buffer attributeStream_bindVertexCacheBuffer(); catchOpenGLError(); LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); bool attributeArrayUsed[32] = { 0 }; // used to keep track of enabled vertex attributes for this shader sint32 attributeDataIndex = 0; uint32 vboDataOffset = 0; bool tfBufferIsBound = false; sint32 maxReallocAttemptLimit = 1; for(auto& bufferGroup : parsedFetchShader->bufferGroups) { uint32 bufferIndex = bufferGroup.attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0]; uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1; uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; if (bufferAddress == MPTR_NULL) { _resetAttributes(&bufferGroup, attributeArrayUsed); continue; } vboDataOffset = _glVertexBufferOffset[bufferIndex]; for (sint32 i = 0; i < bufferGroup.attribCount; i++) { LatteParsedFetchShaderAttribute_t* attrib = bufferGroup.attrib + i; sint32 attributeShaderLocation = attrib->semanticId; // we now bind to the semanticId instead attributeShaderLocation = vertexShader->resourceMapping.getAttribHostShaderIndex(attrib->semanticId); if (attributeShaderLocation == -1) continue; // attribute not used if (attributeShaderLocation >= GPU_GL_MAX_NUM_ATTRIBUTE) continue; if (attributeArrayUsed[attributeShaderLocation] == true) { debug_printf("Fetch shader attribute is bound multiple times\n"); } // get buffer uint32 bufferIndex = attrib->attributeBufferIndex; cemu_assert_debug(bufferIndex < 0x10); cemu_assert_debug(attrib->fetchType == LatteConst::VERTEX_DATA || attrib->fetchType == LatteConst::INSTANCE_DATA); // unsupported fetch type SetAttributeArrayState(attributeShaderLocation, true, (bufferStride == 0) ? 99999999 : attrib->aluDivisor); uint8* bufferInput = memory_getPointerFromPhysicalOffset(bufferAddress) + attrib->offset; uint32 bufferSizeInput = bufferSize - attrib->offset; uint8* vboGLPtr; vboGLPtr = (uint8*)(size_t)(vboDataOffset + attrib->offset); _setAttributeBufferPointerRaw(attributeShaderLocation, NULL, 0, bufferStride, attrib, vboGLPtr, bufferStride); attributeArrayUsed[attributeShaderLocation] = true; attributeDataIndex++; catchOpenGLError(); } } for (uint32 i = 0; i < GPU_GL_MAX_NUM_ATTRIBUTE; i++) { if (attributeArrayUsed[i] == false && glAttributeArrayIsEnabled[i] == true) SetAttributeArrayState(i, false, -1); } } void rectsEmulationGS_outputSingleVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 vIdx); void rectsEmulationGS_outputGeneratedVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, const char* variant); void rectsEmulationGS_outputVerticesCode(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 p0, sint32 p1, sint32 p2, sint32 p3, const char* variant, const LatteContextRegister& latteRegister); std::map<uint64, RendererShaderGL*> g_mapGLRectEmulationGS; RendererShaderGL* rectsEmulationGS_generateShaderGL(LatteDecompilerShader* vertexShader) { LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); std::string gsSrc; gsSrc.append("#version 450\r\n"); // layout gsSrc.append("layout(triangles) in;\r\n"); gsSrc.append("layout(triangle_strip) out;\r\n"); gsSrc.append("layout(max_vertices = 4) out;\r\n"); // gl_PerVertex input gsSrc.append("in gl_PerVertex {\r\n"); gsSrc.append("vec4 gl_Position;\r\n"); gsSrc.append("} gl_in[];\r\n"); // gl_PerVertex output gsSrc.append("out gl_PerVertex {\r\n"); gsSrc.append("vec4 gl_Position;\r\n"); gsSrc.append("};\r\n"); // inputs & outputs auto parameterMask = vertexShader->outputParameterMask; for (sint32 f = 0; f < 2; f++) { for (uint32 i = 0; i < 32; i++) { if ((parameterMask & (1 << i)) == 0) continue; sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(LatteGPUState.contextRegister, i); if (vsSemanticId < 0) continue; auto psImport = psInputTable->getPSImportBySemanticId(vsSemanticId); if (psImport == nullptr) continue; gsSrc.append(fmt::format("layout(location = {}) ", psInputTable->getPSImportLocationBySemanticId(vsSemanticId))); if (psImport->isFlat) gsSrc.append("flat "); if (psImport->isNoPerspective) gsSrc.append("noperspective "); if (f == 0) gsSrc.append("in"); else gsSrc.append("out"); if (f == 0) gsSrc.append(fmt::format(" vec4 passParameterSem{}In[];\r\n", vsSemanticId)); else gsSrc.append(fmt::format(" vec4 passParameterSem{}Out;\r\n", vsSemanticId)); } } // gen function gsSrc.append("vec4 gen4thVertexA(vec4 a, vec4 b, vec4 c)\r\n"); gsSrc.append("{\r\n"); gsSrc.append("return b - (c - a);\r\n"); gsSrc.append("}\r\n"); gsSrc.append("vec4 gen4thVertexB(vec4 a, vec4 b, vec4 c)\r\n"); gsSrc.append("{\r\n"); gsSrc.append("return c - (b - a);\r\n"); gsSrc.append("}\r\n"); gsSrc.append("vec4 gen4thVertexC(vec4 a, vec4 b, vec4 c)\r\n"); gsSrc.append("{\r\n"); gsSrc.append("return c + (b - a);\r\n"); gsSrc.append("}\r\n"); // main gsSrc.append("void main()\r\n"); gsSrc.append("{\r\n"); // there are two possible winding orders that need different triangle generation: // 0 1 // 2 3 // and // 0 1 // 3 2 // all others are just symmetries of these cases // we can determine the case by comparing the distance 0<->1 and 0<->2 gsSrc.append("float dist0_1 = length(gl_in[1].gl_Position.xy - gl_in[0].gl_Position.xy);\r\n"); gsSrc.append("float dist0_2 = length(gl_in[2].gl_Position.xy - gl_in[0].gl_Position.xy);\r\n"); gsSrc.append("float dist1_2 = length(gl_in[2].gl_Position.xy - gl_in[1].gl_Position.xy);\r\n"); // emit vertices gsSrc.append("if(dist0_1 > dist0_2 && dist0_1 > dist1_2)\r\n"); gsSrc.append("{\r\n"); // p0 to p1 is diagonal rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 2, 1, 0, 3, "A", LatteGPUState.contextNew); gsSrc.append("} else if ( dist0_2 > dist0_1 && dist0_2 > dist1_2 ) {\r\n"); // p0 to p2 is diagonal rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 1, 2, 0, 3, "B", LatteGPUState.contextNew); gsSrc.append("} else {\r\n"); // p1 to p2 is diagonal rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 0, 1, 2, 3, "C", LatteGPUState.contextNew); gsSrc.append("}\r\n"); gsSrc.append("}\r\n"); auto glShader = new RendererShaderGL(RendererShader::ShaderType::kGeometry, 0, 0, false, false, gsSrc); glShader->PreponeCompilation(true); return glShader; } RendererShaderGL* rectsEmulationGS_getShaderGL(LatteDecompilerShader* vertexShader) { LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); uint64 h = vertexShader->baseHash + psInputTable->key; auto itr = g_mapGLRectEmulationGS.find(h); if (itr != g_mapGLRectEmulationGS.end()) return (*itr).second; auto glShader = rectsEmulationGS_generateShaderGL(vertexShader); g_mapGLRectEmulationGS.emplace(h, glShader); return glShader; } uint32 sPrevTextureReadbackDrawcallUpdate = 0; template<bool TIsMinimal, bool THasProfiling> void OpenGLRenderer::draw_genericDrawHandler(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType) { ReleaseBufferCacheEntries(); catchOpenGLError(); void* indexData = indexDataMPTR != MPTR_NULL ? memory_getPointerFromPhysicalOffset(indexDataMPTR) : NULL; auto primitiveMode = LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); // handle special state 8 (clear as depth) if (LatteGPUState.contextNew.GetSpecialStateValues()[8] != 0) { LatteDraw_handleSpecialState8_clearAsDepth(); LatteGPUState.drawCallCounter++; return; } // update shaders and uniforms if constexpr (!TIsMinimal) { beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr); LatteSHRC_UpdateActiveShaders(); LatteDecompilerShader* vs = (LatteDecompilerShader*)LatteSHRC_GetActiveVertexShader(); LatteDecompilerShader* gs = (LatteDecompilerShader*)LatteSHRC_GetActiveGeometryShader(); LatteDecompilerShader* ps = (LatteDecompilerShader*)LatteSHRC_GetActivePixelShader(); if (vs) shader_bind(vs->shader); else shader_unbind(RendererShader::ShaderType::kVertex); if (ps && LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] == 0) shader_bind(ps->shader); else shader_unbind(RendererShader::ShaderType::kFragment); if (gs) shader_bind(gs->shader); else shader_unbind(RendererShader::ShaderType::kGeometry); endPerfMonProfiling(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr); } if (LatteGPUState.activeShaderHasError) { debug_printf("Skipped drawcall due to shader error\n"); return; } // check for blacklisted shaders uint64 vsShaderHash = 0; if (LatteSHRC_GetActiveVertexShader()) vsShaderHash = LatteSHRC_GetActiveVertexShader()->baseHash; uint64 psShaderHash = 0; if (LatteSHRC_GetActivePixelShader()) psShaderHash = LatteSHRC_GetActivePixelShader()->baseHash; // setup streamout (if enabled) bool rasterizerEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL() == false; if (!LatteGPUState.contextNew.PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA()) rasterizerEnable = true; bool streamoutEnable = LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0; if (streamoutEnable) { if (glBeginTransformFeedback == nullptr) { cemu_assert_debug(false); return; // transform feedback not supported } } // skip draw if output is not used if (rasterizerEnable == false && streamoutEnable == false) { // rasterizer and streamout disabled LatteGPUState.drawCallCounter++; return; } // get primitive if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLES) sGLActiveDrawMode = GL_TRIANGLES; else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLE_STRIP) sGLActiveDrawMode = GL_TRIANGLE_STRIP; else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUADS) sGLActiveDrawMode = GL_QUADS; else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLE_FAN) sGLActiveDrawMode = GL_TRIANGLE_FAN; else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS) sGLActiveDrawMode = GL_TRIANGLES; else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS) sGLActiveDrawMode = GL_POINTS; else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINES) sGLActiveDrawMode = GL_LINES; else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_STRIP) sGLActiveDrawMode = GL_LINE_STRIP; else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_LOOP) sGLActiveDrawMode = GL_LINE_LOOP; else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUAD_STRIP) sGLActiveDrawMode = GL_QUAD_STRIP; else { cemu_assert_debug(false); // unsupported primitive type LatteGPUState.drawCallCounter++; return; } if constexpr (!TIsMinimal) { // update render targets and textures LatteGPUState.requiresTextureBarrier = false; beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageTextures); while (true) { LatteGPUState.repeatTextureInitialization = false; if (streamoutEnable == false) { // only handle rendertargets if streamout is inactive if (LatteMRT::UpdateCurrentFBO() == false) return; // no render target if (hasValidFramebufferAttached == false) return; } LatteTexture_updateTextures(); // caution: Do not call any functions that potentially modify texture bindings after this line if (LatteGPUState.repeatTextureInitialization == false) break; catchOpenGLError(); } endPerfMonProfiling(performanceMonitor.gpuTime_dcStageTextures); beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageMRT); LatteMRT::ApplyCurrentState(); endPerfMonProfiling(performanceMonitor.gpuTime_dcStageMRT); // texture barrier for write-read patterns if (LatteGPUState.requiresTextureBarrier && glTextureBarrier) { glTextureBarrier(); } } catchOpenGLError(); // handle RECT primitive if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS) { RendererShaderGL* rectsEmulationShader = rectsEmulationGS_getShaderGL(LatteSHRC_GetActiveVertexShader()); shader_bind(rectsEmulationShader); } // prepare index cache beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageIndexMgr); LatteDrawGL_prepareIndicesWithGPUCache(indexDataMPTR, indexType, count, (sint32)primitiveMode); endPerfMonProfiling(performanceMonitor.gpuTime_dcStageIndexMgr); // synchronize vertex and uniform buffers LatteBufferCache_Sync(indexState.minIndex + baseVertex, indexState.maxIndex + baseVertex, baseInstance, instanceCount); _setupVertexAttributes(); // update renderstate LatteRenderTarget_updateViewport(); LatteRenderTarget_updateScissorBox(); renderstate_updateBlendingAndColorControl(); catchOpenGLError(); // handle special state 5 (convert depth to color) if (LatteGPUState.contextNew.GetSpecialStateValues()[5] != 0) { debug_printf("GPU7 special state 5 used\n"); LatteTextureView* rt_color = LatteMRT::GetColorAttachment(0); LatteTextureView* rt_depth = LatteMRT::GetDepthAttachment(); if (!rt_depth || !rt_color) { cemuLog_log(LogType::Force, "GPU7 special state 5 used but render target not setup correctly"); return; } surfaceCopy_copySurfaceWithFormatConversion(rt_depth->baseTexture, rt_depth->firstMip, rt_depth->firstSlice, rt_color->baseTexture, rt_color->firstMip, rt_color->firstSlice, rt_depth->baseTexture->width, rt_depth->baseTexture->height); LatteGPUState.drawCallCounter++; return; } beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr); // update uniform values uniformData_update(); endPerfMonProfiling(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr); catchOpenGLError(); // upload special uniforms LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); if (vertexShader) { auto vertexShaderGL = (RendererShaderGL*)vertexShader->shader; if (vertexShader->uniform.loc_windowSpaceToClipSpaceTransform >= 0) { sint32 viewportWidth; sint32 viewportHeight; LatteRenderTarget_GetCurrentVirtualViewportSize(&viewportWidth, &viewportHeight); // always call after _updateViewport() float t[2]; t[0] = 2.0f / (float)viewportWidth; t[1] = 2.0f / (float)viewportHeight; glProgramUniform2fv(vertexShaderGL->GetProgram(), vertexShader->uniform.loc_windowSpaceToClipSpaceTransform, 1, t); } // update uf_texRescaleFactors for (auto& entry : vertexShader->uniform.list_ufTexRescale) { float* xyScale = LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType::Vertex, entry.texUnit); if (memcmp(entry.currentValue, xyScale, sizeof(float) * 2) == 0) continue; // value unchanged memcpy(entry.currentValue, xyScale, sizeof(float) * 2); glProgramUniform2fv(vertexShaderGL->GetProgram(), entry.uniformLocation, 1, xyScale); } // update uf_pointSize if (vertexShader->uniform.loc_pointSize >= 0) { float t[1]; float pointWidth = (float)LatteGPUState.contextNew.PA_SU_POINT_SIZE.get_WIDTH() / 8.0f; if (pointWidth == 0.0f) pointWidth = 1.0f / 8.0f; // minimum size t[0] = pointWidth; glProgramUniform1fv(vertexShaderGL->GetProgram(), vertexShader->uniform.loc_pointSize, 1, t); } } if (geometryShader) { auto geometryShaderGL = (RendererShaderGL*)geometryShader->shader; // update uf_texRescaleFactors for (auto& entry : geometryShader->uniform.list_ufTexRescale) { float* xyScale = LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType::Geometry, entry.texUnit); if (memcmp(entry.currentValue, xyScale, sizeof(float) * 2) == 0) continue; // value unchanged memcpy(entry.currentValue, xyScale, sizeof(float) * 2); glProgramUniform2fv(geometryShaderGL->GetProgram(), entry.uniformLocation, 1, xyScale); } // update uf_pointSize if (geometryShader->uniform.loc_pointSize >= 0) { float t[1]; float pointWidth = (float)LatteGPUState.contextNew.PA_SU_POINT_SIZE.get_WIDTH() / 8.0f; if (pointWidth == 0.0f) pointWidth = 1.0f / 8.0f; // minimum size t[0] = pointWidth; glProgramUniform1fv(geometryShaderGL->GetProgram(), geometryShader->uniform.loc_pointSize, 1, t); } } if (pixelShader) { auto pixelShaderGL = (RendererShaderGL*)pixelShader->shader; if (pixelShader->uniform.loc_alphaTestRef >= 0) { float t[1]; t[0] = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF(); if (pixelShader->uniform.ufCurrentValueAlphaTestRef != t[0]) { glProgramUniform1fv(pixelShaderGL->GetProgram(), pixelShader->uniform.loc_alphaTestRef, 1, t); pixelShader->uniform.ufCurrentValueAlphaTestRef = t[0]; } } // update uf_fragCoordScale if (pixelShader->uniform.loc_fragCoordScale >= 0) { float coordScale[4]; LatteMRT::GetCurrentFragCoordScale(coordScale); if (pixelShader->uniform.ufCurrentValueFragCoordScale[0] != coordScale[0] || pixelShader->uniform.ufCurrentValueFragCoordScale[1] != coordScale[1]) { glProgramUniform2fv(pixelShaderGL->GetProgram(), pixelShader->uniform.loc_fragCoordScale, 1, coordScale); pixelShader->uniform.ufCurrentValueFragCoordScale[0] = coordScale[0]; pixelShader->uniform.ufCurrentValueFragCoordScale[1] = coordScale[1]; } } // update uf_texRescaleFactors for (auto& entry : pixelShader->uniform.list_ufTexRescale) { float* xyScale = LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType::Pixel, entry.texUnit); if (memcmp(entry.currentValue, xyScale, sizeof(float) * 2) == 0) continue; // value unchanged memcpy(entry.currentValue, xyScale, sizeof(float) * 2); glProgramUniform2fv(pixelShaderGL->GetProgram(), entry.uniformLocation, 1, xyScale); } } catchOpenGLError(); // prepare streamout LatteStreamout_PrepareDrawcall(count, instanceCount); if (streamoutEnable && rasterizerEnable) { if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUADS) sGLActiveDrawMode = GL_POINTS; } catchOpenGLError(); // render beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageDrawcallAPI); LatteDrawGL_doDraw(indexType, baseVertex, baseInstance, instanceCount, count); endPerfMonProfiling(performanceMonitor.gpuTime_dcStageDrawcallAPI); // post-drawcall logic if(pixelShader) LatteRenderTarget_trackUpdates(); LatteStreamout_FinishDrawcall(false); catchOpenGLError(); if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS) shader_unbind(RendererShader::ShaderType::kGeometry); LatteGPUState.drawCallCounter++; if (streamoutEnable && rasterizerEnable) { // streamout and rasterizer enabled, repeat drawcall with streamout disabled uint32 strmOutEnOrg = LatteGPUState.contextRegister[mmVGT_STRMOUT_EN]; LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] = 0; draw_genericDrawHandler<false, THasProfiling>(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] = strmOutEnOrg; return; } LatteTextureReadback_Update(); uint32 dcSinceLastReadbackCheck = LatteGPUState.drawCallCounter - sPrevTextureReadbackDrawcallUpdate; if (dcSinceLastReadbackCheck >= 150) { LatteTextureReadback_UpdateFinishedTransfers(false); sPrevTextureReadbackDrawcallUpdate = LatteGPUState.drawCallCounter; } catchOpenGLError(); } void OpenGLRenderer::draw_beginSequence() { // no-op } void OpenGLRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) { bool isMinimal = !isFirst; if (isMinimal) draw_genericDrawHandler<true, false>(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); else draw_genericDrawHandler<false, false>(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); } void OpenGLRenderer::draw_endSequence() { // no-op } #define GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR (18*1024*1024) // 18MB void OpenGLRenderer::draw_init() { if (indexState.initialized) return; indexState.initialized = true; // create index buffer glGenBuffers(1, &indexState.glIndexCacheBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexState.glIndexCacheBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR, NULL, GL_DYNAMIC_DRAW); #if BOOST_OS_WINDOWS indexState.mappedIndexBuffer = (uint8*)_aligned_malloc(GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR, 256); #else indexState.mappedIndexBuffer = (uint8*)aligned_alloc(256, GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR); #endif glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); indexState.indexRingBuffer = LatteRingBuffer_create(indexState.mappedIndexBuffer, GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR); indexState.tempIndexStorage = (uint8*)malloc(1024 * 1024 * 8); // create virtual heap for index buffer indexState.indexBufferVirtualHeap = virtualBufferHeap_create(GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR); } void OpenGLRenderer::bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) { attributeStream_bindVertexCacheBuffer(); glBufferSubData(GL_ARRAY_BUFFER, bufferOffset, size, buffer); } void OpenGLRenderer::bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) { attributeStream_bindVertexCacheBuffer(); glCopyBufferSubData(GL_ARRAY_BUFFER, GL_ARRAY_BUFFER, srcOffset, dstOffset, size); } GLint glClampTable[] = { GL_REPEAT, GL_MIRRORED_REPEAT, GL_CLAMP_TO_EDGE, GL_MIRROR_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_MIRROR_CLAMP_TO_BORDER_EXT, GL_CLAMP_TO_BORDER, GL_MIRROR_CLAMP_TO_BORDER_EXT }; GLint glCompSelTable[8] = { GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_ZERO, GL_ONE, 0, 0 }; GLint glDepthCompareTable[8] = { GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, GL_ALWAYS }; // Remaps component selection if the underlying OpenGL texture format would behave differently than it's GPU7 counterpart uint32 _correctTextureCompSelGL(Latte::E_GX2SURFFMT format, uint32 compSel) { switch (format) { case Latte::E_GX2SURFFMT::R8_UNORM: // R8 is replicated on all channels (while OpenGL would return 1.0 for BGA instead) case Latte::E_GX2SURFFMT::R8_SNORM: // probably the same as _UNORM, but needs testing if (compSel >= 1 && compSel <= 3) compSel = 0; break; case Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM: // order of components is reversed (RGBA -> ABGR) if (compSel >= 0 && compSel <= 3) compSel = 3 - compSel; break; case Latte::E_GX2SURFFMT::BC4_UNORM: case Latte::E_GX2SURFFMT::BC4_SNORM: if (compSel >= 1 && compSel <= 3) compSel = 0; break; case Latte::E_GX2SURFFMT::BC5_UNORM: case Latte::E_GX2SURFFMT::BC5_SNORM: // RG maps to RG // B maps to ? // A maps to G (guessed) if (compSel == 3) compSel = 1; // read Alpha as Green break; case Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM: // reverse components (Wii U: ABGR, OpenGL: RGBA) // used in Resident Evil Revelations if (compSel >= 0 && compSel <= 3) compSel = 3 - compSel; break; case Latte::E_GX2SURFFMT::X24_G8_UINT: // map everything to alpha? if (compSel >= 0 && compSel <= 3) compSel = 3; break; default: break; } return compSel; } #define quickBindTexture() if( textureIsActive == false ) { texture_bindAndActivate(hostTextureView, hostTextureUnit); textureIsActive = true; } uint32 _getGLMinFilter(Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER filterMin, Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER filterMip) { bool isMinPointFilter = (filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT) || (filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT); if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::NONE) { // no mip return isMinPointFilter ? GL_NEAREST : GL_LINEAR; } else if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::POINT) { // nearest neighbor return isMinPointFilter ? GL_NEAREST_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_NEAREST; } // else -> filterMip == LINEAR return isMinPointFilter ? GL_NEAREST_MIPMAP_LINEAR : GL_LINEAR_MIPMAP_LINEAR; } /* * Update channel swizzling and other texture settings for a texture unit * hostTextureView is the texture unit view used on the host side */ void OpenGLRenderer::renderstate_updateTextureSettingsGL(LatteDecompilerShader* shaderContext, LatteTextureView* _hostTextureView, uint32 hostTextureUnit, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N texUnitWord4, uint32 texUnitIndex, bool isDepthSampler) { auto hostTextureView = (LatteTextureViewGL*)_hostTextureView; LatteTexture* baseTexture = hostTextureView->baseTexture; // get texture register word 0 catchOpenGLError(); bool textureIsActive = false; uint32 compSelR = (uint32)texUnitWord4.get_DST_SEL_X(); uint32 compSelG = (uint32)texUnitWord4.get_DST_SEL_Y(); uint32 compSelB = (uint32)texUnitWord4.get_DST_SEL_Z(); uint32 compSelA = (uint32)texUnitWord4.get_DST_SEL_W(); // on OpenGL some channels might be mapped differently compSelR = _correctTextureCompSelGL(hostTextureView->format, compSelR); compSelG = _correctTextureCompSelGL(hostTextureView->format, compSelG); compSelB = _correctTextureCompSelGL(hostTextureView->format, compSelB); compSelA = _correctTextureCompSelGL(hostTextureView->format, compSelA); // update swizzle parameters if (hostTextureView->swizzleR != compSelR) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_SWIZZLE_R, glCompSelTable[compSelR]); hostTextureView->swizzleR = compSelR; } if (hostTextureView->swizzleG != compSelG) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_SWIZZLE_G, glCompSelTable[compSelG]); hostTextureView->swizzleG = compSelG; } if (hostTextureView->swizzleB != compSelB) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_SWIZZLE_B, glCompSelTable[compSelB]); hostTextureView->swizzleB = compSelB; } if (hostTextureView->swizzleA != compSelA) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_SWIZZLE_A, glCompSelTable[compSelA]); hostTextureView->swizzleA = compSelA; } catchOpenGLError(); uint32 stageSamplerIndex = shaderContext->textureUnitSamplerAssignment[texUnitIndex]; if (stageSamplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) { uint32 samplerIndex = stageSamplerIndex; samplerIndex += LatteDecompiler_getTextureSamplerBaseIndex(shaderContext->shaderType); const _LatteRegisterSetSampler* samplerWords = LatteGPUState.contextNew.SQ_TEX_SAMPLER + samplerIndex; auto filterMag = samplerWords->WORD0.get_XY_MAG_FILTER(); auto filterMin = samplerWords->WORD0.get_XY_MAG_FILTER(); //auto filterZ = samplerWords->WORD0.get_Z_FILTER(); auto filterMip = samplerWords->WORD0.get_MIP_FILTER(); // get OpenGL constant for min filter uint32 filterMinGL = _getGLMinFilter(filterMin, filterMip); uint32 filterMagGL = (filterMag == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT || filterMag == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT) ? GL_NEAREST : GL_LINEAR; // todo: z-filter is customizable for GPU7 but OpenGL doesn't offer the same functionality? LatteSamplerState* samplerState = &hostTextureView->samplerState; catchOpenGLError(); uint32 clampX = (uint32)samplerWords->WORD0.get_CLAMP_X(); uint32 clampY = (uint32)samplerWords->WORD0.get_CLAMP_Y(); uint32 clampZ = (uint32)samplerWords->WORD0.get_CLAMP_Z(); if (samplerState->clampS != clampX) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_WRAP_S, glClampTable[clampX]); samplerState->clampS = clampX; } if (samplerState->clampT != clampY) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_WRAP_T, glClampTable[clampY]); samplerState->clampT = clampY; } if (samplerState->clampR != clampZ) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_WRAP_R, glClampTable[clampZ]); samplerState->clampR = clampZ; } catchOpenGLError(); uint32 maxAniso = (uint32)samplerWords->WORD0.get_MAX_ANISO_RATIO(); if (baseTexture->overwriteInfo.anisotropicLevel >= 0) maxAniso = baseTexture->overwriteInfo.anisotropicLevel; if (samplerState->maxAniso != maxAniso) { quickBindTexture(); glTexParameterf(hostTextureView->glTexTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)(1 << maxAniso)); samplerState->maxAniso = maxAniso; catchOpenGLError(); } if (samplerState->filterMin != filterMinGL) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_MIN_FILTER, filterMinGL); samplerState->filterMin = filterMinGL; catchOpenGLError(); } if (samplerState->filterMag != filterMagGL) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_MAG_FILTER, filterMagGL); samplerState->filterMag = filterMagGL; catchOpenGLError(); } if (samplerState->maxMipLevels != hostTextureView->numMip) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_MAX_LEVEL, std::max(hostTextureView->numMip, 1) - 1); samplerState->maxMipLevels = hostTextureView->numMip; catchOpenGLError(); } // lod uint32 iMinLOD = samplerWords->WORD1.get_MIN_LOD(); uint32 iMaxLOD = samplerWords->WORD1.get_MAX_LOD(); sint32 iLodBias = samplerWords->WORD1.get_LOD_BIAS(); // apply relative lod bias from graphic pack if (baseTexture->overwriteInfo.hasRelativeLodBias) { iLodBias += baseTexture->overwriteInfo.relativeLodBias; } // apply absolute lod bias from graphic pack if (baseTexture->overwriteInfo.hasLodBias) { iLodBias = baseTexture->overwriteInfo.lodBias; } if (samplerState->minLod != iMinLOD) { quickBindTexture(); glTexParameterf(hostTextureView->glTexTarget, GL_TEXTURE_MIN_LOD, (float)iMinLOD / 64.0f); samplerState->minLod = iMinLOD; } if (samplerState->maxLod != iMaxLOD) { quickBindTexture(); glTexParameterf(hostTextureView->glTexTarget, GL_TEXTURE_MAX_LOD, (float)iMaxLOD / 64.0f); samplerState->maxLod = iMaxLOD; } if (samplerState->lodBias != iLodBias) { quickBindTexture(); glTexParameterf(hostTextureView->glTexTarget, GL_TEXTURE_LOD_BIAS, (float)iLodBias / 64.0f); samplerState->lodBias = iLodBias; } // depth compare uint32 samplerDepthCompare = (uint32)samplerWords->WORD0.get_DEPTH_COMPARE_FUNCTION(); uint8 depthCompareMode = isDepthSampler ? 1 : 0; if (samplerDepthCompare != samplerState->depthCompareFunc) { quickBindTexture(); glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_COMPARE_FUNC, glDepthCompareTable[samplerDepthCompare]); samplerState->depthCompareFunc = samplerDepthCompare; } if (depthCompareMode != samplerState->depthCompareMode) { quickBindTexture(); if (depthCompareMode != 0) glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); else glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_COMPARE_MODE, GL_NONE); samplerState->depthCompareMode = depthCompareMode; } catchOpenGLError(); // border auto borderType = samplerWords->WORD0.get_BORDER_COLOR_TYPE(); if (samplerState->borderType != (uint8)borderType || borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::REGISTER) { // todo: Should we use integer border color (glTexParameteriv) for integer texture formats? GLfloat borderColor[4]; if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::TRANSPARENT_BLACK) { borderColor[0] = 0.0f; borderColor[1] = 0.0f; borderColor[2] = 0.0f; borderColor[3] = 0.0f; } else if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::OPAQUE_BLACK) { borderColor[0] = 0.0f; borderColor[1] = 0.0f; borderColor[2] = 0.0f; borderColor[3] = 1.0f; } else if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::OPAQUE_WHITE) { borderColor[0] = 1.0f; borderColor[1] = 1.0f; borderColor[2] = 1.0f; borderColor[3] = 1.0f; } else { // border color from register _LatteRegisterSetSamplerBorderColor* borderColorReg; if (shaderContext->shaderType == LatteConst::ShaderType::Vertex || shaderContext->shaderType == LatteConst::ShaderType::Compute) borderColorReg = LatteGPUState.contextNew.TD_VS_SAMPLER_BORDER_COLOR + stageSamplerIndex; else if (shaderContext->shaderType == LatteConst::ShaderType::Pixel) borderColorReg = LatteGPUState.contextNew.TD_PS_SAMPLER_BORDER_COLOR + stageSamplerIndex; else // geometry borderColorReg = LatteGPUState.contextNew.TD_GS_SAMPLER_BORDER_COLOR + stageSamplerIndex; borderColor[0] = borderColorReg->red.get_channelValue(); borderColor[1] = borderColorReg->green.get_channelValue(); borderColor[2] = borderColorReg->blue.get_channelValue(); borderColor[3] = borderColorReg->alpha.get_channelValue(); } if (samplerState->borderColor[0] != borderColor[0] || samplerState->borderColor[1] != borderColor[1] || samplerState->borderColor[2] != borderColor[2] || samplerState->borderColor[3] != borderColor[3]) { quickBindTexture(); glTexParameterfv(hostTextureView->glTexTarget, GL_TEXTURE_BORDER_COLOR, borderColor); samplerState->borderColor[0] = borderColor[0]; samplerState->borderColor[1] = borderColor[1]; samplerState->borderColor[2] = borderColor[2]; samplerState->borderColor[3] = borderColor[3]; } samplerState->borderType = (uint8)borderType; } catchOpenGLError(); } } ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererStreamout.cpp ================================================ #include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/OS/libs/gx2/GX2.h" // todo - remove dependency void OpenGLRenderer::streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) { catchOpenGLError(); glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, bufferIndex, glStreamoutCacheRingBuffer, ringBufferOffset, rangeSize); catchOpenGLError(); } void OpenGLRenderer::streamout_begin() { // get primitive mode GLenum glTransformFeedbackPrimitiveMode; auto primitiveMode = LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS) glTransformFeedbackPrimitiveMode = GL_POINTS; else if(primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLES) glTransformFeedbackPrimitiveMode = GL_POINTS; else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUADS) glTransformFeedbackPrimitiveMode = GL_POINTS; else { debug_printf("Unsupported streamout primitive mode 0x%02x\n", primitiveMode); cemu_assert_debug(false); } cemu_assert_debug(m_isXfbActive == false); glEnable(GL_RASTERIZER_DISCARD_EXT); glBeginTransformFeedback(glTransformFeedbackPrimitiveMode); catchOpenGLError(); m_isXfbActive = true; } void OpenGLRenderer::bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) { if (glCopyNamedBufferSubData) glCopyNamedBufferSubData(glStreamoutCacheRingBuffer, glAttributeCacheAB, srcOffset, dstOffset, size); else cemuLog_log(LogType::Force, "glCopyNamedBufferSubData() not supported"); } void OpenGLRenderer::streamout_rendererFinishDrawcall() { if (m_isXfbActive) { glEndTransformFeedback(); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0); glDisable(GL_RASTERIZER_DISCARD_EXT); m_isXfbActive = false; } } ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp ================================================ #include "RendererShaderGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Core/LatteShader.h" GLint _gl_remappedUniformData[4 * 256]; void OpenGLRenderer::uniformData_update() { // update uniforms LatteDecompilerShader* shaderArray[3]; shaderArray[0] = LatteSHRC_GetActiveVertexShader(); shaderArray[1] = LatteSHRC_GetActivePixelShader(); shaderArray[2] = LatteSHRC_GetActiveGeometryShader();; uint32 shaderBlockUniformRegisterOffset[3]; shaderBlockUniformRegisterOffset[0] = mmSQ_VTX_UNIFORM_BLOCK_START; shaderBlockUniformRegisterOffset[1] = mmSQ_PS_UNIFORM_BLOCK_START; shaderBlockUniformRegisterOffset[2] = mmSQ_GS_UNIFORM_BLOCK_START; uint32 shaderALUConstOffset[3]; shaderALUConstOffset[0] = 0x400; shaderALUConstOffset[1] = 0; shaderALUConstOffset[2] = 0xFFFFFFFF; // GS has no uniform registers for (sint32 s = 0; s < 3; s++) { // update block uniforms LatteDecompilerShader* shader = shaderArray[s]; if (!shader) continue; auto hostShader = (RendererShaderGL*)shader->shader; if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) { auto& list_uniformMapping = shader->list_remappedUniformEntries; cemu_assert_debug(list_uniformMapping.size() <= 256); sint32 remappedArraySize = (sint32)list_uniformMapping.size(); LatteBufferCache_LoadRemappedUniforms(shader, (float*)(_gl_remappedUniformData)); // update values only when the hash changed if (remappedArraySize > 0) { uint64 uniformDataHash[2] = { 0 }; uint64* remappedUniformData64 = (uint64*)_gl_remappedUniformData; for (sint32 f = 0; f < remappedArraySize; f++) { uniformDataHash[0] ^= remappedUniformData64[0]; uniformDataHash[0] = std::rotl<uint64>(uniformDataHash[0], 11); uniformDataHash[1] ^= remappedUniformData64[1]; uniformDataHash[1] = std::rotl<uint64>(uniformDataHash[1], 11); remappedUniformData64 += 2; } if (shader->uniformDataHash64[0] != uniformDataHash[0] || shader->uniformDataHash64[1] != uniformDataHash[1]) { shader->uniformDataHash64[0] = uniformDataHash[0]; shader->uniformDataHash64[1] = uniformDataHash[1]; hostShader->SetUniform4iv(shader->uniform.loc_remapped, _gl_remappedUniformData, remappedArraySize); } } } else if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) { if (shaderALUConstOffset[s] == 0xFFFFFFFF) assert_dbg(); GLint* uniformRegData = (GLint*)(LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderALUConstOffset[s]); hostShader->SetUniform4iv(shader->uniform.loc_uniformRegister, uniformRegData, shader->uniform.count_uniformRegister); } else if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { // handled by _syncGPUUniformBuffers() } else if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_NONE) { // no uniforms used } } } ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp ================================================ #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteDefaultShaders.h" void LatteDraw_resetAttributePointerCache(); void _setDepthCompareMode(LatteTextureViewGL* textureView, uint8 depthCompareMode) { if (depthCompareMode != textureView->samplerState.depthCompareMode) { if (depthCompareMode != 0) glTexParameteri(textureView->glTexTarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); else glTexParameteri(textureView->glTexTarget, GL_TEXTURE_COMPARE_MODE, GL_NONE); textureView->samplerState.depthCompareMode = depthCompareMode; } } void OpenGLRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) { // scale copy size to effective size sint32 effectiveCopyWidth = width; sint32 effectiveCopyHeight = height; LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); sint32 sourceEffectiveWidth, sourceEffectiveHeight; sourceTexture->GetEffectiveSize(sourceEffectiveWidth, sourceEffectiveHeight, srcMip); // reset everything renderstate_resetColorControl(); renderstate_resetDepthControl(); attributeStream_unbindVertexBuffer(); SetArrayElementBuffer(0); LatteDraw_resetAttributePointerCache(); SetAttributeArrayState(0, true, -1); SetAttributeArrayState(1, true, -1); for (uint32 i = 2; i < GPU_GL_MAX_NUM_ATTRIBUTE; i++) SetAttributeArrayState(i, false, -1); catchOpenGLError(); // set viewport g_renderer->renderTarget_setViewport(0, 0, (float)effectiveCopyWidth, (float)effectiveCopyHeight, 0.0f, 1.0f); catchOpenGLError(); // get a view of the copied slice/mip in the source and destination texture LatteTextureView* sourceView = sourceTexture->GetOrCreateView(srcMip, 1, srcSlice, 1); LatteTextureView* destinationView = destinationTexture->GetOrCreateView(dstMip, 1, dstSlice, 1); texture_bindAndActivate(sourceView, 0); catchOpenGLError(); // setup texture attributes _setDepthCompareMode((LatteTextureViewGL*)sourceView, 0); catchOpenGLError(); // bind framebuffer if (destinationTexture->isDepth) LatteMRT::BindDepthBufferOnly(destinationView); else LatteMRT::BindColorBufferOnly(destinationView); catchOpenGLError(); // enable depth writes if the destination is a depth texture if (destinationTexture->isDepth) renderstate_setAlwaysWriteDepth(); // bind format specific copy shader LatteDefaultShader_t* copyShader = LatteDefaultShader_getPixelCopyShader_depthToColor(); if (destinationTexture->isDepth) copyShader = LatteDefaultShader_getPixelCopyShader_colorToDepth(); glUseProgram(copyShader->glProgamId); catchOpenGLError(); // setup uniforms glUniform1i(copyShader->copyShaderUniforms.uniformLoc_textureSrc, 0); catchOpenGLError(); float vertexOffsets[4 * 4]; float srcCopyWidth = (float)width / (float)sourceTexture->width; float srcCopyHeight = (float)height / (float)sourceTexture->height; // q0 vertex vertexOffsets[0] = -1.0f; vertexOffsets[1] = 1.0f; // q0 uv vertexOffsets[2] = 0.0f; vertexOffsets[3] = 0.0f; // q1 vertexOffsets[4] = 1.0f; vertexOffsets[5] = 1.0f; // q1 uv vertexOffsets[6] = srcCopyWidth; vertexOffsets[7] = 0.0f; // q2 vertexOffsets[8] = -1.0f; vertexOffsets[9] = -1.0f; // q2 uv vertexOffsets[10] = 0.0f; vertexOffsets[11] = srcCopyHeight; // q3 vertexOffsets[12] = 1.0f; vertexOffsets[13] = -1.0f; // q3 uv vertexOffsets[14] = srcCopyWidth; vertexOffsets[15] = srcCopyHeight; glUniform4fv(copyShader->copyShaderUniforms.uniformLoc_vertexOffsets, 4, vertexOffsets); catchOpenGLError(); // draw uint16 indexData[6] = { 0,1,3,0,2,3 }; glDrawRangeElements(GL_TRIANGLES, 0, 5, 6, GL_UNSIGNED_SHORT, indexData); catchOpenGLError(); LatteGPUState.repeatTextureInitialization = true; glUseProgram(0); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h" #include "Common/GLInclude/GLInclude.h" class LatteTextureReadbackInfoGL : public LatteTextureReadbackInfo { public: LatteTextureReadbackInfoGL(LatteTextureView* textureView); ~LatteTextureReadbackInfoGL(); void StartTransfer() override; bool IsFinished() override; uint8* GetData() override; void ReleaseData() override; private: GLuint m_texFormatGL; GLuint m_texDataTypeGL; // buffer GLuint texImageBufferGL = 0; // sync GLsync imageCopyFinSync = nullptr; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" #include "Cemu/FileCache/FileCache.h" #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" extern std::atomic_int g_compiled_shaders_total; extern std::atomic_int g_compiled_shaders_async; bool s_isLoadingShaders{false}; bool RendererShaderGL::loadBinary() { if (!s_programBinaryCache) return false; if (m_isGameShader == false || m_isGfxPackShader) return false; // only non-custom if (!glProgramBinary) return false; // OpenGL program binaries not supported cemu_assert_debug(m_baseHash != 0); uint64 h1, h2; GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); std::vector<uint8> cacheFileData; if (!s_programBinaryCache->GetFile({h1, h2 }, cacheFileData)) return false; if (cacheFileData.size() <= sizeof(uint32)) return false; uint32 shaderBinFormat = *(uint32*)(cacheFileData.data()); m_program = glCreateProgram(); glProgramBinary(m_program, shaderBinFormat, cacheFileData.data()+4, cacheFileData.size()-4); int status = -1; glGetProgramiv(m_program, GL_LINK_STATUS, &status); if (status != GL_TRUE) { glDeleteProgram(m_program); m_program = 0; return false; } m_isCompiled = true; return true; } void RendererShaderGL::storeBinary() { if (!s_programBinaryCache) return; if (!glGetProgramBinary) return; if (m_program == 0) return; if (!m_isGameShader || m_isGfxPackShader) return; GLint binaryLength = 0; glGetProgramiv(m_program, GL_PROGRAM_BINARY_LENGTH, &binaryLength); if (binaryLength > 0) { uint64 h1, h2; GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); // build stored shader data (4 byte format + binary data) std::vector<uint8> storedBinary(binaryLength+sizeof(uint32), 0); GLenum binaryFormat = 0; glGetProgramBinary(m_program, binaryLength, NULL, &binaryFormat, storedBinary.data()+sizeof(uint32)); *(uint32*)(storedBinary.data() + 0) = binaryFormat; // store s_programBinaryCache->AddFileAsync({h1, h2 }, storedBinary.data(), storedBinary.size()); } } RendererShaderGL::RendererShaderGL(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslSource) : RendererShader(type, baseHash, auxHash, isGameShader, isGfxPackShader), m_glslSource(glslSource) { GLenum glShaderType; switch (type) { case ShaderType::kVertex: glShaderType = GL_VERTEX_SHADER; break; case ShaderType::kFragment: glShaderType = GL_FRAGMENT_SHADER; break; case ShaderType::kGeometry: glShaderType = GL_GEOMETRY_SHADER; break; default: cemu_assert_debug(false); } if (s_isLoadingShaders) { if (loadBinary()) { m_glslSource.clear(); m_glslSource.shrink_to_fit(); return; } } m_shader_object = glCreateShader(glShaderType); const char *c_str = m_glslSource.c_str(); const GLint size = (GLint)m_glslSource.size(); glShaderSource(m_shader_object, 1, &c_str, &size); glCompileShader(m_shader_object); GLint log_length; glGetShaderiv(m_shader_object, GL_INFO_LOG_LENGTH, &log_length); if (log_length > 0) { char log[2048]{}; GLsizei log_size; glGetShaderInfoLog(m_shader_object, std::min<uint32>(log_length, sizeof(log) - 1), &log_size, log); cemuLog_log(LogType::Force, "Error/Warning in shader:"); cemuLog_log(LogType::Force, log); } // set debug name if (LaunchSettings::NSightModeEnabled()) { auto objNameStr = fmt::format("shader_{:016x}_{:016x}", m_baseHash, m_auxHash); glObjectLabel(GL_SHADER, m_shader_object, objNameStr.size(), objNameStr.c_str()); } m_program = glCreateProgram(); glProgramParameteri(m_program, GL_PROGRAM_SEPARABLE, GL_TRUE); glProgramParameteri(m_program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); glAttachShader(m_program, m_shader_object); m_shader_attached = true; glLinkProgram(m_program); storeBinary(); // count shader compilation if (!s_isLoadingShaders) ++g_compiled_shaders_total; // we can throw away the GLSL code to conserve RAM m_glslSource.clear(); m_glslSource.shrink_to_fit(); } RendererShaderGL::~RendererShaderGL() { if (m_shader_object != 0 && m_shader_attached) glDetachShader(m_program, m_shader_object); if (m_shader_object != 0) glDeleteShader(m_shader_object); if (m_program != 0) glDeleteProgram(m_program); } void RendererShaderGL::PreponeCompilation(bool isRenderThread) { // the logic for initiating compilation is currently in the constructor // here we only guarantee that it is finished before we return if (m_isCompiled) return; WaitForCompiled(); } bool RendererShaderGL::IsCompiled() { cemu_assert_debug(false); return true; } bool RendererShaderGL::WaitForCompiled() { char infoLog[8 * 1024]; if (m_isCompiled) return true; // check if compilation was successful GLint compileStatus = GL_FALSE; glGetShaderiv(m_shader_object, GL_COMPILE_STATUS, &compileStatus); if (compileStatus == 0) { uint32 infoLogLength, tempLength; glGetShaderiv(m_shader_object, GL_INFO_LOG_LENGTH, (GLint *)&infoLogLength); if (infoLogLength != 0) { tempLength = sizeof(infoLog) - 1; glGetShaderInfoLog(m_shader_object, std::min(infoLogLength, tempLength), (GLsizei*)&tempLength, (GLcharARB*)infoLog); infoLog[tempLength] = '\0'; cemuLog_log(LogType::Force, "Compile error in shader. Log:"); cemuLog_log(LogType::Force, infoLog); } if (m_shader_object != 0) glDeleteShader(m_shader_object); m_isCompiled = true; return false; } // get shader binary GLint linkStatus = GL_FALSE; glGetProgramiv(m_program, GL_LINK_STATUS, &linkStatus); if (linkStatus == 0) { uint32 infoLogLength, tempLength; glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, (GLint *)&infoLogLength); if (infoLogLength != 0) { tempLength = sizeof(infoLog) - 1; glGetProgramInfoLog(m_program, std::min(infoLogLength, tempLength), (GLsizei*)&tempLength, (GLcharARB*)infoLog); infoLog[tempLength] = '\0'; cemuLog_log(LogType::Force, "Link error in shader. Log:"); cemuLog_log(LogType::Force, infoLog); } m_isCompiled = true; return false; } /*glDetachShader(m_program, m_shader_object); m_shader_attached = false;*/ m_isCompiled = true; return true; } sint32 RendererShaderGL::GetUniformLocation(const char* name) { return glGetUniformLocation(m_program, name); } void RendererShaderGL::SetUniform1i(sint32 location, sint32 value) { glProgramUniform1i(m_program, location, value); } void RendererShaderGL::SetUniform1f(sint32 location, float value) { glProgramUniform1f(m_program, location, value); } void RendererShaderGL::SetUniform2fv(sint32 location, void* data, sint32 count) { glProgramUniform2fv(m_program, location, count, (const GLfloat*)data); } void RendererShaderGL::SetUniform4iv(sint32 location, void* data, sint32 count) { glProgramUniform4iv(m_program, location, count, (const GLint*)data); } void RendererShaderGL::ShaderCacheLoading_begin(uint64 cacheTitleId) { cemu_assert_debug(!s_programBinaryCache); // should not be set, ShaderCacheLoading_Close() not called? // determine if cache is enabled bool usePrecompiled = false; switch (ActiveSettings::GetPrecompiledShadersOption()) { case PrecompiledShaderOption::Auto: if (g_renderer->GetVendor() == GfxVendor::Nvidia) usePrecompiled = false; else usePrecompiled = true; break; case PrecompiledShaderOption::Enable: usePrecompiled = true; break; case PrecompiledShaderOption::Disable: usePrecompiled = false; break; default: UNREACHABLE; } cemuLog_log(LogType::Force, "Using precompiled shaders: {}", usePrecompiled ? "true" : "false"); if (usePrecompiled) { const uint32 cacheMagic = GeneratePrecompiledCacheId(); const std::string cacheFilename = fmt::format("{:016x}_gl.bin", cacheTitleId); s_programBinaryCache = FileCache::Open(ActiveSettings::GetCachePath("shaderCache/precompiled/{}", cacheFilename), true, cacheMagic); if (s_programBinaryCache == nullptr) cemuLog_log(LogType::Force, "Unable to open OpenGL precompiled cache {}", cacheFilename); } s_isLoadingShaders = true; } void RendererShaderGL::ShaderCacheLoading_end() { s_isLoadingShaders = false; } void RendererShaderGL::ShaderCacheLoading_Close() { if(s_programBinaryCache) { delete s_programBinaryCache; s_programBinaryCache = nullptr; } g_compiled_shaders_total = 0; g_compiled_shaders_async = 0; } FileCache* RendererShaderGL::s_programBinaryCache{}; ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/RendererShader.h" #include "Common/GLInclude/GLInclude.h" class RendererShaderGL : public RendererShader { public: RendererShaderGL(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslSource); virtual ~RendererShaderGL(); void PreponeCompilation(bool isRenderThread) override; bool IsCompiled() override; bool WaitForCompiled() override; GLuint GetProgram() const { cemu_assert_debug(m_isCompiled); return m_program; } GLuint GetShaderObject() const { cemu_assert_debug(m_isCompiled); return m_shader_object; } sint32 GetUniformLocation(const char* name); void SetUniform1i(sint32 location, sint32 value); void SetUniform1f(sint32 location, float value); void SetUniform2fv(sint32 location, void* data, sint32 count); void SetUniform4iv(sint32 location, void* data, sint32 count); static void ShaderCacheLoading_begin(uint64 cacheTitleId); static void ShaderCacheLoading_end(); static void ShaderCacheLoading_Close(); private: GLuint m_program; GLuint m_shader_object; bool loadBinary(); void storeBinary(); std::string m_glslSource; bool m_shader_attached{ false }; bool m_isCompiled{ false }; static class FileCache* s_programBinaryCache; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h" #include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" LatteTextureReadbackInfoGL::LatteTextureReadbackInfoGL(LatteTextureView* textureView) : LatteTextureReadbackInfo(textureView) { LatteTexture* baseTexture = textureView->baseTexture; // handle format if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM) { m_image_size = baseTexture->width*baseTexture->height * 4; m_texFormatGL = GL_RGBA; m_texDataTypeGL = GL_UNSIGNED_BYTE; } else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) { m_image_size = baseTexture->width*baseTexture->height * 4; m_texFormatGL = GL_RGBA; m_texDataTypeGL = GL_UNSIGNED_BYTE; } else if (textureView->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) { m_image_size = baseTexture->width*baseTexture->height * 16; m_texFormatGL = GL_RGBA; m_texDataTypeGL = GL_FLOAT; } else if (textureView->format == Latte::E_GX2SURFFMT::R32_FLOAT) { if (baseTexture->isDepth) { m_image_size = baseTexture->width*baseTexture->height * 4; m_texFormatGL = GL_DEPTH_COMPONENT; m_texDataTypeGL = GL_FLOAT; } else { m_image_size = baseTexture->width*baseTexture->height * 4; m_texFormatGL = GL_RED; m_texDataTypeGL = GL_FLOAT; } } else if (textureView->format == Latte::E_GX2SURFFMT::R16_UNORM) { if (baseTexture->isDepth) { m_image_size = baseTexture->width*baseTexture->height * 2; m_texFormatGL = GL_DEPTH_COMPONENT; m_texDataTypeGL = GL_UNSIGNED_SHORT; cemu_assert_unimplemented(); } else { m_image_size = baseTexture->width*baseTexture->height * 2; m_texFormatGL = GL_RED; m_texDataTypeGL = GL_UNSIGNED_SHORT; } } else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) { m_image_size = baseTexture->width*baseTexture->height * 8; m_texFormatGL = GL_RGBA; m_texDataTypeGL = GL_HALF_FLOAT; } else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_UNORM) { m_image_size = baseTexture->width*baseTexture->height * 2; m_texFormatGL = GL_RG; m_texDataTypeGL = GL_UNSIGNED_BYTE; } else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) { m_image_size = baseTexture->width*baseTexture->height * 8; m_texFormatGL = GL_RGBA; m_texDataTypeGL = GL_UNSIGNED_SHORT; } else { cemuLog_logDebug(LogType::Force, "Unsupported texture readback format {:04x}", (uint32)textureView->format); return; } } LatteTextureReadbackInfoGL::~LatteTextureReadbackInfoGL() { if(imageCopyFinSync != 0) glDeleteSync(imageCopyFinSync); if(texImageBufferGL) glDeleteBuffers(1, &texImageBufferGL); } void LatteTextureReadbackInfoGL::StartTransfer() { cemu_assert(m_textureView); ((OpenGLRenderer*)g_renderer.get())->texture_bindAndActivate(m_textureView, 0); // create unsynchronized buffer glGenBuffers(1, &texImageBufferGL); glBindBuffer(GL_PIXEL_PACK_BUFFER, texImageBufferGL); glBufferData(GL_PIXEL_PACK_BUFFER, m_image_size, NULL, GL_DYNAMIC_READ); // request texture read into buffer glGetTexImage(((LatteTextureViewGL*)m_textureView)->glTexTarget, 0, m_texFormatGL, m_texDataTypeGL, NULL); glFlush(); // create fence sync (so we can check if the image copy operation finished) imageCopyFinSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); m_textureView = nullptr; } bool LatteTextureReadbackInfoGL::IsFinished() { GLenum status = glClientWaitSync(imageCopyFinSync, 0, 0); if (status == GL_TIMEOUT_EXPIRED) return false; else if (status == GL_ALREADY_SIGNALED || status == GL_SIGNALED) return true; else throw std::runtime_error("_updateFinishedTransfers(): Error during readback sync check\n"); } uint8* LatteTextureReadbackInfoGL::GetData() { glBindBuffer(GL_PIXEL_PACK_BUFFER, texImageBufferGL); return (uint8*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); } void LatteTextureReadbackInfoGL::ReleaseData() { glUnmapBuffer(GL_PIXEL_PACK_BUFFER); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Renderer.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "WindowSystem.h" #include "config/CemuConfig.h" #include "Cafe/HW/Latte/Core/LatteOverlay.h" #include <imgui.h> #include "imgui/imgui_extension.h" #include <png.h> #include "config/ActiveSettings.h" std::unique_ptr<Renderer> g_renderer; bool Renderer::GetVRAMInfo(int& usageInMB, int& totalInMB) const { usageInMB = totalInMB = -1; #if BOOST_OS_WINDOWS if (m_dxgi_wrapper) { DXGI_QUERY_VIDEO_MEMORY_INFO info{}; if (m_dxgi_wrapper->QueryVideoMemoryInfo(info)) { totalInMB = (info.Budget / 1000) / 1000; usageInMB = (info.CurrentUsage / 1000) / 1000; return true; } } #endif return false; } void Renderer::Initialize() { // imgui imguiFontAtlas = new ImFontAtlas(); imguiFontAtlas->AddFontDefault(); auto setupContext = [](ImGuiContext* context){ ImGui::SetCurrentContext(context); ImGuiIO& io = ImGui::GetIO(); io.WantSaveIniSettings = false; io.IniFilename = nullptr; }; imguiTVContext = ImGui::CreateContext(imguiFontAtlas); imguiPadContext = ImGui::CreateContext(imguiFontAtlas); setupContext(imguiTVContext); setupContext(imguiPadContext); } void Renderer::Shutdown() { // imgui ImGui::DestroyContext(imguiTVContext); ImGui::DestroyContext(imguiPadContext); ImGui_ClearFonts(); delete imguiFontAtlas; } bool Renderer::ImguiBegin(bool mainWindow) { sint32 w = 0, h = 0; if (mainWindow) WindowSystem::GetWindowPhysSize(w, h); else if (WindowSystem::IsPadWindowOpen()) WindowSystem::GetPadWindowPhysSize(w, h); else return false; if (w == 0 || h == 0) return false; // select the right context ImGui::SetCurrentContext(mainWindow ? imguiTVContext : imguiPadContext); const Vector2f window_size{(float)w, (float)h}; auto& io = ImGui::GetIO(); io.DisplaySize = {window_size.x, window_size.y}; // should be only updated in the renderer and only when needed ImGui_PrecacheFonts(); return true; } uint8 Renderer::SRGBComponentToRGB(uint8 ci) { const float cs = (float)ci / 255.0f; float cl; if (cs <= 0.04045) cl = cs / 12.92f; else cl = std::pow((cs + 0.055f) / 1.055f, 2.4f); cl = std::min(cl, 1.0f); return (uint8)(cl * 255.0f); } uint8 Renderer::RGBComponentToSRGB(uint8 cli) { const float cl = (float)cli / 255.0f; float cs; if (cl < 0.0031308) cs = 12.92f * cl; else cs = 1.055f * std::pow(cl, 0.41666f) - 0.055f; cs = std::max(std::min(cs, 1.0f), 0.0f); return (uint8)(cs * 255.0f); } void Renderer::RequestScreenshot(ScreenshotSaveFunction onSaveScreenshot) { m_screenshot_requested = true; m_on_save_screenshot = onSaveScreenshot; } void Renderer::CancelScreenshotRequest() { m_screenshot_requested = false; m_on_save_screenshot = {}; } void Renderer::SaveScreenshot(const std::vector<uint8>& rgb_data, int width, int height, bool mainWindow) { std::thread( [=, screenshotRequested = std::exchange(m_screenshot_requested, false), onSaveScreenshot = std::exchange(m_on_save_screenshot, {})]() { if (screenshotRequested && onSaveScreenshot) { auto notificationMessage = onSaveScreenshot(rgb_data, width, height, mainWindow); if (notificationMessage.has_value()) LatteOverlay_pushNotification(notificationMessage.value(), 2500); } }) .detach(); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Renderer.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteCachedFBO.h" #include "Cafe/HW/Latte/Renderer/RendererShader.h" #include "Cafe/HW/Latte/Core/LatteTextureLoader.h" #include "Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h" #include "Cafe/HW/Latte/Core/LatteQueryObject.h" #include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" #if BOOST_OS_WINDOWS #include "util/DXGIWrapper/DXGIWrapper.h" #endif // imgui forward declarations struct ImFontAtlas; struct ImGuiContext; enum class GfxVendor { Generic, AMD, Intel, Nvidia, Apple, Mesa, MAX }; enum class RendererAPI { OpenGL, Vulkan, Metal, MAX }; using ImTextureID = void*; class Renderer { public: enum class INDEX_TYPE { NONE, U16, U32 }; virtual ~Renderer() = default; virtual RendererAPI GetType() = 0; virtual void Initialize(); virtual void Shutdown(); virtual bool IsPadWindowActive() = 0; virtual bool GetVRAMInfo(int& usageInMB, int& totalInMB) const; virtual void EnableDebugMode() {} virtual void ClearColorbuffer(bool padView) = 0; virtual void DrawEmptyFrame(bool mainWindow) = 0; virtual void SwapBuffers(bool swapTV, bool swapDRC) = 0; using ScreenshotSaveFunction = std::function<std::optional<std::string>(const std::vector<uint8>&, int, int, bool)>; void RequestScreenshot(ScreenshotSaveFunction onSaveScreenshot); void CancelScreenshotRequest(); virtual void HandleScreenshotRequest(LatteTextureView* texView, bool padView){} virtual void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) = 0; virtual bool BeginFrame(bool mainWindow) = 0; // flush control virtual void Flush(bool waitIdle = false) = 0; // called when explicit flush is required (e.g. by imgui) virtual void NotifyLatteCommandProcessorIdle() = 0; // called when command processor has no more commands available or when stalled // imgui virtual bool ImguiBegin(bool mainWindow); virtual void ImguiEnd() = 0; virtual ImTextureID GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) = 0; virtual void DeleteTexture(ImTextureID id) = 0; virtual void DeleteFontTextures() = 0; GfxVendor GetVendor() const { return m_vendor; } virtual bool UseTFViaSSBO() const { return false; } virtual void AppendOverlayDebugInfo() = 0; // rendertarget virtual void renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ = false) = 0; virtual void renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) = 0; virtual LatteCachedFBO* rendertarget_createCachedFBO(uint64 key) = 0; virtual void rendertarget_deleteCachedFBO(LatteCachedFBO* fbo) = 0; virtual void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) = 0; // texture functions virtual void* texture_acquireTextureUploadBuffer(uint32 size) = 0; virtual void texture_releaseTextureUploadBuffer(uint8* mem) = 0; virtual TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) = 0; virtual void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) = 0; virtual void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) = 0; virtual void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) = 0; virtual void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) = 0; virtual LatteTexture* texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) = 0; virtual void texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) = 0; virtual void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) = 0; virtual LatteTextureReadbackInfo* texture_createReadback(LatteTextureView* textureView) = 0; // surface copy virtual void surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) = 0; // buffer cache virtual void bufferCache_init(const sint32 bufferSize) = 0; virtual void bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) = 0; virtual void bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) = 0; virtual void bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) = 0; virtual void buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) = 0; virtual void buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) = 0; // shader virtual RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool compileAsync, bool isGfxPackSource) = 0; // streamout virtual void streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) = 0; virtual void streamout_begin() = 0; virtual void streamout_rendererFinishDrawcall() = 0; // core drawing logic virtual void draw_beginSequence() = 0; virtual void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) = 0; virtual void draw_endSequence() = 0; // index struct IndexAllocation { void* mem; // pointer to index data inside buffer void* rendererInternal; // for renderer use }; virtual IndexAllocation indexData_reserveIndexMemory(uint32 size) = 0; virtual void indexData_releaseIndexMemory(IndexAllocation& allocation) = 0; virtual void indexData_uploadIndexMemory(IndexAllocation& allocation) = 0; // occlusion queries virtual LatteQueryObject* occlusionQuery_create() = 0; virtual void occlusionQuery_destroy(LatteQueryObject* queryObj) = 0; virtual void occlusionQuery_flush() = 0; virtual void occlusionQuery_updateState() = 0; protected: virtual void GetVendorInformation() { } GfxVendor m_vendor = GfxVendor::Generic; static uint8 SRGBComponentToRGB(uint8 ci); static uint8 RGBComponentToSRGB(uint8 cli); enum class ScreenshotState { None, Main, Pad, }; ScreenshotState m_screenshot_state = ScreenshotState::None; bool m_screenshot_requested = false; ScreenshotSaveFunction m_on_save_screenshot; void SaveScreenshot(const std::vector<uint8>& rgb_data, int width, int height, bool mainWindow); ImFontAtlas* imguiFontAtlas{}; ImGuiContext* imguiTVContext{}; ImGuiContext* imguiPadContext{}; #if BOOST_OS_WINDOWS std::unique_ptr<DXGIWrapper> m_dxgi_wrapper{}; #endif }; extern std::unique_ptr<Renderer> g_renderer; ================================================ FILE: src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp ================================================ #include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "config/ActiveSettings.h" const std::string RendererOutputShader::s_copy_shader_source = R"( void outputShader() { colorOut0 = vec4(texture(textureSrc, passUV).rgb,1.0); } )"; const std::string RendererOutputShader::s_copy_shader_source_mtl = R"(#include <metal_stdlib> using namespace metal; struct VertexOut { float2 uv; }; fragment float4 main0(VertexOut in [[stage_in]], texture2d<float> textureSrc [[texture(0)]], sampler samplr [[sampler(0)]]) { return float4(textureSrc.sample(samplr, in.uv).rgb, 1.0); } )"; const std::string RendererOutputShader::s_bicubic_shader_source = R"( vec4 cubic(float x) { float x2 = x * x; float x3 = x2 * x; vec4 w; w.x = -x3 + 3 * x2 - 3 * x + 1; w.y = 3 * x3 - 6 * x2 + 4; w.z = -3 * x3 + 3 * x2 + 3 * x + 1; w.w = x3; return w / 6.0; } vec4 bcFilter(vec2 uv, vec4 texelSize) { vec2 pixel = uv*texelSize.zw - 0.5; vec2 pixelFrac = fract(pixel); vec2 pixelInt = pixel - pixelFrac; vec4 xcubic = cubic(pixelFrac.x); vec4 ycubic = cubic(pixelFrac.y); vec4 c = vec4(pixelInt.x - 0.5, pixelInt.x + 1.5, pixelInt.y - 0.5, pixelInt.y + 1.5); vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w); vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s; vec4 sample0 = texture(textureSrc, vec2(offset.x, offset.z) * texelSize.xy); vec4 sample1 = texture(textureSrc, vec2(offset.y, offset.z) * texelSize.xy); vec4 sample2 = texture(textureSrc, vec2(offset.x, offset.w) * texelSize.xy); vec4 sample3 = texture(textureSrc, vec2(offset.y, offset.w) * texelSize.xy); float sx = s.x / (s.x + s.y); float sy = s.z / (s.z + s.w); return mix( mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy); } void outputShader(){ vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy); colorOut0 = vec4(bcFilter(passUV, texelSize).rgb,1.0); } )"; const std::string RendererOutputShader::s_bicubic_shader_source_mtl = R"(#include <metal_stdlib> using namespace metal; float4 cubic(float x) { float x2 = x * x; float x3 = x2 * x; float4 w; w.x = -x3 + 3 * x2 - 3 * x + 1; w.y = 3 * x3 - 6 * x2 + 4; w.z = -3 * x3 + 3 * x2 + 3 * x + 1; w.w = x3; return w / 6.0; } float4 bcFilter(texture2d<float> textureSrc, sampler samplr, float2 texcoord, float2 texscale) { float fx = fract(texcoord.x); float fy = fract(texcoord.y); texcoord.x -= fx; texcoord.y -= fy; float4 xcubic = cubic(fx); float4 ycubic = cubic(fy); float4 c = float4(texcoord.x - 0.5, texcoord.x + 1.5, texcoord.y - 0.5, texcoord.y + 1.5); float4 s = float4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w); float4 offset = c + float4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s; float4 sample0 = textureSrc.sample(samplr, float2(offset.x, offset.z) * texscale); float4 sample1 = textureSrc.sample(samplr, float2(offset.y, offset.z) * texscale); float4 sample2 = textureSrc.sample(samplr, float2(offset.x, offset.w) * texscale); float4 sample3 = textureSrc.sample(samplr, float2(offset.y, offset.w) * texscale); float sx = s.x / (s.x + s.y); float sy = s.z / (s.z + s.w); return mix( mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy); } struct VertexOut { float2 uv; }; fragment float4 main0(VertexOut in [[stage_in]], texture2d<float> textureSrc [[texture(0)]], sampler samplr [[sampler(0)]]) { float2 textureSrcResolution = float2(textureSrc.get_width(), textureSrc.get_height()); return float4(bcFilter(textureSrc, samplr, in.uv * textureSrcResolution, float2(1.0, 1.0) / textureSrcResolution).rgb, 1.0); } )"; const std::string RendererOutputShader::s_hermite_shader_source = R"( // https://www.shadertoy.com/view/MllSzX vec3 CubicHermite (vec3 A, vec3 B, vec3 C, vec3 D, float t) { float t2 = t*t; float t3 = t*t*t; vec3 a = -A/2.0 + (3.0*B)/2.0 - (3.0*C)/2.0 + D/2.0; vec3 b = A - (5.0*B)/2.0 + 2.0*C - D / 2.0; vec3 c = -A/2.0 + C/2.0; vec3 d = B; return a*t3 + b*t2 + c*t + d; } vec3 BicubicHermiteTexture(vec2 uv, vec4 texelSize) { vec2 pixel = uv*texelSize.zw + 0.5; vec2 frac = fract(pixel); pixel = floor(pixel) / texelSize.zw - vec2(texelSize.xy/2.0); vec4 doubleSize = texelSize*2.0; vec3 C00 = texture(textureSrc, pixel + vec2(-texelSize.x ,-texelSize.y)).rgb; vec3 C10 = texture(textureSrc, pixel + vec2( 0.0 ,-texelSize.y)).rgb; vec3 C20 = texture(textureSrc, pixel + vec2( texelSize.x ,-texelSize.y)).rgb; vec3 C30 = texture(textureSrc, pixel + vec2( doubleSize.x,-texelSize.y)).rgb; vec3 C01 = texture(textureSrc, pixel + vec2(-texelSize.x , 0.0)).rgb; vec3 C11 = texture(textureSrc, pixel + vec2( 0.0 , 0.0)).rgb; vec3 C21 = texture(textureSrc, pixel + vec2( texelSize.x , 0.0)).rgb; vec3 C31 = texture(textureSrc, pixel + vec2( doubleSize.x, 0.0)).rgb; vec3 C02 = texture(textureSrc, pixel + vec2(-texelSize.x , texelSize.y)).rgb; vec3 C12 = texture(textureSrc, pixel + vec2( 0.0 , texelSize.y)).rgb; vec3 C22 = texture(textureSrc, pixel + vec2( texelSize.x , texelSize.y)).rgb; vec3 C32 = texture(textureSrc, pixel + vec2( doubleSize.x, texelSize.y)).rgb; vec3 C03 = texture(textureSrc, pixel + vec2(-texelSize.x , doubleSize.y)).rgb; vec3 C13 = texture(textureSrc, pixel + vec2( 0.0 , doubleSize.y)).rgb; vec3 C23 = texture(textureSrc, pixel + vec2( texelSize.x , doubleSize.y)).rgb; vec3 C33 = texture(textureSrc, pixel + vec2( doubleSize.x, doubleSize.y)).rgb; vec3 CP0X = CubicHermite(C00, C10, C20, C30, frac.x); vec3 CP1X = CubicHermite(C01, C11, C21, C31, frac.x); vec3 CP2X = CubicHermite(C02, C12, C22, C32, frac.x); vec3 CP3X = CubicHermite(C03, C13, C23, C33, frac.x); return CubicHermite(CP0X, CP1X, CP2X, CP3X, frac.y); } void outputShader(){ vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy); colorOut0 = vec4(BicubicHermiteTexture(passUV, texelSize), 1.0); } )"; const std::string RendererOutputShader::s_hermite_shader_source_mtl = R"(#include <metal_stdlib> using namespace metal; // https://www.shadertoy.com/view/MllSzX float3 CubicHermite(float3 A, float3 B, float3 C, float3 D, float t) { float t2 = t*t; float t3 = t*t*t; float3 a = -A/2.0 + (3.0*B)/2.0 - (3.0*C)/2.0 + D/2.0; float3 b = A - (5.0*B)/2.0 + 2.0*C - D / 2.0; float3 c = -A/2.0 + C/2.0; float3 d = B; return a*t3 + b*t2 + c*t + d; } float3 BicubicHermiteTexture(texture2d<float> textureSrc, sampler samplr, float2 uv, float4 texelSize) { float2 pixel = uv*texelSize.zw + 0.5; float2 frac = fract(pixel); pixel = floor(pixel) / texelSize.zw - float2(texelSize.xy/2.0); float4 doubleSize = texelSize*texelSize; float3 C00 = textureSrc.sample(samplr, pixel + float2(-texelSize.x ,-texelSize.y)).rgb; float3 C10 = textureSrc.sample(samplr, pixel + float2( 0.0 ,-texelSize.y)).rgb; float3 C20 = textureSrc.sample(samplr, pixel + float2( texelSize.x ,-texelSize.y)).rgb; float3 C30 = textureSrc.sample(samplr, pixel + float2( doubleSize.x,-texelSize.y)).rgb; float3 C01 = textureSrc.sample(samplr, pixel + float2(-texelSize.x , 0.0)).rgb; float3 C11 = textureSrc.sample(samplr, pixel + float2( 0.0 , 0.0)).rgb; float3 C21 = textureSrc.sample(samplr, pixel + float2( texelSize.x , 0.0)).rgb; float3 C31 = textureSrc.sample(samplr, pixel + float2( doubleSize.x, 0.0)).rgb; float3 C02 = textureSrc.sample(samplr, pixel + float2(-texelSize.x , texelSize.y)).rgb; float3 C12 = textureSrc.sample(samplr, pixel + float2( 0.0 , texelSize.y)).rgb; float3 C22 = textureSrc.sample(samplr, pixel + float2( texelSize.x , texelSize.y)).rgb; float3 C32 = textureSrc.sample(samplr, pixel + float2( doubleSize.x, texelSize.y)).rgb; float3 C03 = textureSrc.sample(samplr, pixel + float2(-texelSize.x , doubleSize.y)).rgb; float3 C13 = textureSrc.sample(samplr, pixel + float2( 0.0 , doubleSize.y)).rgb; float3 C23 = textureSrc.sample(samplr, pixel + float2( texelSize.x , doubleSize.y)).rgb; float3 C33 = textureSrc.sample(samplr, pixel + float2( doubleSize.x, doubleSize.y)).rgb; float3 CP0X = CubicHermite(C00, C10, C20, C30, frac.x); float3 CP1X = CubicHermite(C01, C11, C21, C31, frac.x); float3 CP2X = CubicHermite(C02, C12, C22, C32, frac.x); float3 CP3X = CubicHermite(C03, C13, C23, C33, frac.x); return CubicHermite(CP0X, CP1X, CP2X, CP3X, frac.y); } struct VertexOut { float4 position [[position]]; float2 uv; }; fragment float4 main0(VertexOut in [[stage_in]], texture2d<float> textureSrc [[texture(0)]], sampler samplr [[sampler(0)]], constant float2& outputResolution [[buffer(0)]]) { float4 texelSize = float4(1.0 / outputResolution.xy, outputResolution.xy); return float4(BicubicHermiteTexture(textureSrc, samplr, in.uv, texelSize), 1.0); } )"; RendererOutputShader::RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source) { std::string finalFragmentSrc; if (g_renderer->GetType() == RendererAPI::Metal) finalFragmentSrc = fragment_source; else finalFragmentSrc = PrependFragmentPreamble(fragment_source); m_vertex_shader.reset(g_renderer->shader_create(RendererShader::ShaderType::kVertex, 0, 0, vertex_source, false, false)); m_fragment_shader.reset(g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, finalFragmentSrc, false, false)); m_vertex_shader->PreponeCompilation(true); m_fragment_shader->PreponeCompilation(true); if (!m_vertex_shader->WaitForCompiled()) throw std::exception(); if(!m_fragment_shader->WaitForCompiled()) throw std::exception(); } RendererOutputShader::OutputUniformVariables RendererOutputShader::FillUniformBlockBuffer(const LatteTextureView& texture_view, const Vector2i& output_res, const bool padView) const { OutputUniformVariables vars; sint32 effectiveWidth, effectiveHeight; texture_view.baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); vars.textureSrcResolution = {(float)effectiveWidth, (float)effectiveHeight}; vars.nativeResolution = {(float)texture_view.baseTexture->width, (float)texture_view.baseTexture->height}; vars.outputResolution = output_res; vars.applySRGBEncoding = padView ? LatteGPUState.drcBufferUsesSRGB : LatteGPUState.tvBufferUsesSRGB; vars.targetGamma = padView ? ActiveSettings::GetDRCGamma() : ActiveSettings::GetTVGamma(); vars.displayGamma = GetConfig().userDisplayGamma; return vars; } RendererOutputShader* RendererOutputShader::s_copy_shader; RendererOutputShader* RendererOutputShader::s_copy_shader_ud; RendererOutputShader* RendererOutputShader::s_bicubic_shader; RendererOutputShader* RendererOutputShader::s_bicubic_shader_ud; RendererOutputShader* RendererOutputShader::s_hermit_shader; RendererOutputShader* RendererOutputShader::s_hermit_shader_ud; std::string RendererOutputShader::GetOpenGlVertexSource(bool render_upside_down) { // vertex shader std::ostringstream vertex_source; vertex_source << R"(#version 420 layout(location = 0) smooth out vec2 passUV; out gl_PerVertex { vec4 gl_Position; }; void main(){ vec2 vPos; vec2 vUV; int vID = gl_VertexID; )"; if (render_upside_down) { vertex_source << R"( if( vID == 0 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,0.0); } else if( vID == 1 ) { vPos = vec2(-1.0,1.0); vUV = vec2(0.0,0.0); } else if( vID == 2 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,1.0); } else if( vID == 3 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,1.0); } else if( vID == 4 ) { vPos = vec2(1.0,-1.0); vUV = vec2(1.0,1.0); } else if( vID == 5 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,0.0); } )"; } else { vertex_source << R"( if( vID == 0 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,1.0); } else if( vID == 1 ) { vPos = vec2(-1.0,1.0); vUV = vec2(0.0,1.0); } else if( vID == 2 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,0.0); } else if( vID == 3 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,0.0); } else if( vID == 4 ) { vPos = vec2(1.0,-1.0); vUV = vec2(1.0,0.0); } else if( vID == 5 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,1.0); } )"; } vertex_source << R"( passUV = vUV; gl_Position = vec4(vPos, 0.0, 1.0); } )"; return vertex_source.str(); } std::string RendererOutputShader::GetVulkanVertexSource(bool render_upside_down) { // vertex shader std::ostringstream vertex_source; vertex_source << R"(#version 450 layout(location = 0) out vec2 passUV; out gl_PerVertex { vec4 gl_Position; }; void main(){ vec2 vPos; vec2 vUV; int vID = gl_VertexIndex; )"; if (render_upside_down) { vertex_source << R"( if( vID == 0 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,0.0); } else if( vID == 1 ) { vPos = vec2(-1.0,1.0); vUV = vec2(0.0,0.0); } else if( vID == 2 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,1.0); } else if( vID == 3 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,1.0); } else if( vID == 4 ) { vPos = vec2(1.0,-1.0); vUV = vec2(1.0,1.0); } else if( vID == 5 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,0.0); } )"; } else { vertex_source << R"( if( vID == 0 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,1.0); } else if( vID == 1 ) { vPos = vec2(-1.0,1.0); vUV = vec2(0.0,1.0); } else if( vID == 2 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,0.0); } else if( vID == 3 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,0.0); } else if( vID == 4 ) { vPos = vec2(1.0,-1.0); vUV = vec2(1.0,0.0); } else if( vID == 5 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,1.0); } )"; } vertex_source << R"( passUV = vUV; gl_Position = vec4(vPos, 0.0, 1.0); } )"; return vertex_source.str(); } std::string RendererOutputShader::GetMetalVertexSource(bool render_upside_down) { // vertex shader std::ostringstream vertex_source; vertex_source << R"(#include <metal_stdlib> using namespace metal; struct VertexOut { float4 position [[position]]; float2 uv; }; vertex VertexOut main0(ushort vid [[vertex_id]]) { VertexOut out; float2 pos; if (vid == 0) pos = float2(-1.0, -3.0); else if (vid == 1) pos = float2(-1.0, 1.0); else if (vid == 2) pos = float2(3.0, 1.0); out.uv = pos * 0.5 + 0.5; out.uv.y = 1.0 - out.uv.y; )"; if (render_upside_down) { vertex_source << R"( pos.y = -pos.y; )"; } vertex_source << R"( out.position = float4(pos, 0.0, 1.0); return out; } )"; return vertex_source.str(); } std::string RendererOutputShader::PrependFragmentPreamble(const std::string& shaderSrc) { return R"(#version 430 layout(location = 0) smooth in vec2 passUV; layout(binding = 0) uniform sampler2D textureSrc; layout(location = 0) out vec4 colorOut0; #ifdef VULKAN layout (binding = 1, std140) #else layout (binding = 0, std140) #endif uniform parameters { uniform vec2 textureSrcResolution; uniform vec2 nativeResolution; uniform vec2 outputResolution; uniform bool applySRGBEncoding; uniform float targetGamma; uniform float displayGamma; }; float sRGBEncode(float linear) { if(linear <= 0.0031308) return 12.92f * linear; else return 1.055f * pow(linear, 1.0f / 2.4f) - 0.055f; } vec3 sRGBEncode(vec3 linear) { return vec3(sRGBEncode(linear.r), sRGBEncode(linear.g), sRGBEncode(linear.b)); } // fwd. declaration void outputShader(); void main() { outputShader(); // sets colorOut0 if(applySRGBEncoding) colorOut0 = vec4(sRGBEncode(colorOut0.rgb), 1.0f); if (displayGamma > 0.0f) colorOut0 = pow(colorOut0, vec4(targetGamma / displayGamma) ); else colorOut0 = vec4( sRGBEncode( pow(colorOut0.rgb, vec3(targetGamma)) ), 1.0f); } )" + shaderSrc; } void RendererOutputShader::InitializeStatic() { if (g_renderer->GetType() == RendererAPI::Metal) { std::string vertex_source = GetMetalVertexSource(false); std::string vertex_source_ud = GetMetalVertexSource(true); s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source_mtl); s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source_mtl); s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source_mtl); s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_shader_source_mtl); s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source_mtl); s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source_mtl); } else { std::string vertex_source, vertex_source_ud; // vertex shader if (g_renderer->GetType() == RendererAPI::OpenGL) { vertex_source = GetOpenGlVertexSource(false); vertex_source_ud = GetOpenGlVertexSource(true); } else if (g_renderer->GetType() == RendererAPI::Vulkan) { vertex_source = GetVulkanVertexSource(false); vertex_source_ud = GetVulkanVertexSource(true); } s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source); s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source); s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source); s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_shader_source); s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source); s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source); } } void RendererOutputShader::ShutdownStatic() { delete s_copy_shader; delete s_copy_shader_ud; delete s_bicubic_shader; delete s_bicubic_shader_ud; delete s_hermit_shader; delete s_hermit_shader_ud; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/RendererOuputShader.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/RendererShader.h" #include "util/math/vector2.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" class RendererOutputShader { public: struct OutputUniformVariables { Vector2f textureSrcResolution; Vector2f nativeResolution; Vector2f outputResolution; uint32 applySRGBEncoding; float targetGamma; float displayGamma; }; enum Shader { kCopy, kBicubic, kHermit, }; RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source); virtual ~RendererOutputShader() = default; OutputUniformVariables FillUniformBlockBuffer(const LatteTextureView& texture_view, const Vector2i& output_res, const bool padView) const; RendererShader* GetVertexShader() const { return m_vertex_shader.get(); } RendererShader* GetFragmentShader() const { return m_fragment_shader.get(); } static void InitializeStatic(); static void ShutdownStatic(); static RendererOutputShader* s_copy_shader; static RendererOutputShader* s_copy_shader_ud; static RendererOutputShader* s_bicubic_shader; static RendererOutputShader* s_bicubic_shader_ud; static RendererOutputShader* s_hermit_shader; static RendererOutputShader* s_hermit_shader_ud; static std::string GetOpenGlVertexSource(bool render_upside_down); static std::string GetVulkanVertexSource(bool render_upside_down); static std::string GetMetalVertexSource(bool render_upside_down); static std::string PrependFragmentPreamble(const std::string& shaderSrc); protected: std::unique_ptr<RendererShader> m_vertex_shader; std::unique_ptr<RendererShader> m_fragment_shader; private: static const std::string s_copy_shader_source; static const std::string s_bicubic_shader_source; static const std::string s_hermite_shader_source; static const std::string s_bicubic_shader_source_vk; static const std::string s_hermite_shader_source_vk; static const std::string s_copy_shader_source_mtl; static const std::string s_bicubic_shader_source_mtl; static const std::string s_hermite_shader_source_mtl; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/RendererShader.cpp ================================================ #include "Cafe/HW/Latte/Renderer/RendererShader.h" #include "Cafe/GameProfile/GameProfile.h" // generate a Cemu version and setting dependent id uint32 RendererShader::GeneratePrecompiledCacheId() { uint32 v = 0; const char* s = EMULATOR_VERSION_SUFFIX; while (*s) { v = std::rotl<uint32>(v, 7); v += (uint32)(*s); s++; } v += (EMULATOR_VERSION_MAJOR * 1000000u); v += (EMULATOR_VERSION_MINOR * 10000u); v += (EMULATOR_VERSION_PATCH * 100u); // settings that can influence shaders v += (uint32)g_current_game_profile->GetAccurateShaderMul() * 133; return v; } void RendererShader::GenerateShaderPrecompiledCacheFilename(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, uint64& h1, uint64& h2) { h1 = baseHash; h2 = auxHash; if (type == RendererShader::ShaderType::kVertex) h2 += 0xA16374cull; else if (type == RendererShader::ShaderType::kFragment) h2 += 0x8752deull; else if (type == RendererShader::ShaderType::kGeometry) h2 += 0x65a035ull; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/RendererShader.h ================================================ #pragma once class RendererShader { public: enum class ShaderType { kVertex, kFragment, kGeometry }; virtual ~RendererShader() = default; ShaderType GetType() const { return m_type; } virtual void PreponeCompilation(bool isRenderThread) = 0; // if shader not yet compiled, compile it synchronously (if possible) or alternatively wait for compilation. After this function IsCompiled() is guaranteed to be true virtual bool IsCompiled() = 0; virtual bool WaitForCompiled() = 0; protected: // if isGameShader is true, then baseHash and auxHash are valid RendererShader(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader) : m_type(type), m_baseHash(baseHash), m_auxHash(auxHash), m_isGameShader(isGameShader), m_isGfxPackShader(isGfxPackShader) {} static uint32 GeneratePrecompiledCacheId(); static void GenerateShaderPrecompiledCacheFilename(ShaderType type, uint64 baseHash, uint64 auxHash, uint64& h1, uint64& h2); protected: ShaderType m_type; uint64 m_baseHash; uint64 m_auxHash; bool m_isGameShader; bool m_isGfxPackShader; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h" void CachedFBOVk::CreateRenderPass() { VKRObjectRenderPass::AttachmentInfo_t attachmentInfo; for (int i = 0; i < 8; ++i) { auto& buffer = colorBuffer[i]; auto textureViewVk = (LatteTextureViewVk*)buffer.texture; if (!textureViewVk) { attachmentInfo.colorAttachment[i].viewObj = nullptr; continue; } // setup color attachment auto viewObj = textureViewVk->GetViewRGBA(); attachmentInfo.colorAttachment[i].viewObj = viewObj; attachmentInfo.colorAttachment[i].format = textureViewVk->GetFormat(); } // setup depth attachment if (depthBuffer.texture) { LatteTextureViewVk* depthTexVk = static_cast<LatteTextureViewVk*>(depthBuffer.texture); auto depthBufferViewObj = depthTexVk->GetViewRGBA(); attachmentInfo.depthAttachment.viewObj = depthBufferViewObj; attachmentInfo.depthAttachment.format = depthTexVk->GetFormat(); attachmentInfo.depthAttachment.hasStencil = depthTexVk->baseTexture->hasStencil; } else { // no depth attachment attachmentInfo.depthAttachment.viewObj = nullptr; } m_vkrObjRenderPass = new VKRObjectRenderPass(attachmentInfo); } CachedFBOVk::~CachedFBOVk() { while (!m_usedByPipelines.empty()) delete m_usedByPipelines[0]; auto vkr = VulkanRenderer::GetInstance(); vkr->ReleaseDestructibleObject(m_vkrObjFramebuffer); m_vkrObjFramebuffer = nullptr; vkr->ReleaseDestructibleObject(m_vkrObjRenderPass); m_vkrObjRenderPass = nullptr; } VKRObjectTextureView* CachedFBOVk::GetColorBufferImageView(uint32 index) { cemu_assert(index < 8); auto& cb = colorBuffer[index]; auto textureViewVk = (LatteTextureViewVk*)cb.texture; if (!textureViewVk) return nullptr; auto viewDim = textureViewVk->dim; if (viewDim == Latte::E_DIM::DIM_3D) viewDim = Latte::E_DIM::DIM_2D; // bind 3D texture slices as 2D images return textureViewVk->GetViewRGBA(); } VKRObjectTextureView* CachedFBOVk::GetDepthStencilBufferImageView(bool& hasStencil) { hasStencil = false; auto textureViewVk = (LatteTextureViewVk*)depthBuffer.texture; if (!textureViewVk) return nullptr; cemu_assert_debug(textureViewVk->numMip == 1); hasStencil = textureViewVk->baseTexture->hasStencil; return textureViewVk->GetViewRGBA(); } void CachedFBOVk::CreateFramebuffer() { std::array<VKRObjectTextureView*, 9> imageViews{}; int imageViewIndex = 0; for (uint32 i = 0; i < 8; ++i) { VKRObjectTextureView* cbView = GetColorBufferImageView(i); if (!cbView) continue; imageViews[imageViewIndex++] = cbView; } bool hasStencil = false; VKRObjectTextureView* depthStencilView = GetDepthStencilBufferImageView(hasStencil); if (depthStencilView) imageViews[imageViewIndex++] = depthStencilView; cemu_assert_debug(imageViewIndex < 9); m_vkrObjFramebuffer = new VKRObjectFramebuffer(m_vkrObjRenderPass, std::span<VKRObjectTextureView*>(imageViews.data(), imageViewIndex), m_size); m_extend = { (uint32)m_size.x, (uint32)m_size.y }; } void CachedFBOVk::InitDynamicRenderingData() { // init struct for KHR_dynamic_rendering for (int i = 0; i < 8; ++i) { m_vkColorAttachments[i].sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; m_vkColorAttachments[i].pNext = nullptr; m_vkColorAttachments[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL; m_vkColorAttachments[i].resolveMode = VK_RESOLVE_MODE_NONE; m_vkColorAttachments[i].resolveImageView = VK_NULL_HANDLE; m_vkColorAttachments[i].resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; m_vkColorAttachments[i].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; m_vkColorAttachments[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE; // ignore clearValue VKRObjectTextureView* cbView = GetColorBufferImageView(i); auto& buffer = colorBuffer[i]; if (!cbView) { m_vkColorAttachments[i].imageView = VK_NULL_HANDLE; continue; } else m_vkColorAttachments[i].imageView = cbView->m_textureImageView; } m_vkRenderingInfo.pColorAttachments = m_vkColorAttachments; m_vkRenderingInfo.colorAttachmentCount = 8; // trim the color attachment list if tail entries are not set while (m_vkRenderingInfo.colorAttachmentCount > 0) { if (m_vkColorAttachments[m_vkRenderingInfo.colorAttachmentCount - 1].imageView) break; m_vkRenderingInfo.colorAttachmentCount--; } // initially set both stencil and depth attachment to an empty default m_vkDepthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; m_vkDepthAttachment.pNext = nullptr; m_vkDepthAttachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL; m_vkDepthAttachment.resolveMode = VK_RESOLVE_MODE_NONE; m_vkDepthAttachment.resolveImageView = VK_NULL_HANDLE; m_vkDepthAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; m_vkDepthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; m_vkDepthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; m_vkStencilAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; m_vkStencilAttachment.pNext = nullptr; m_vkStencilAttachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL; m_vkStencilAttachment.resolveMode = VK_RESOLVE_MODE_NONE; m_vkStencilAttachment.resolveImageView = VK_NULL_HANDLE; m_vkStencilAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; m_vkStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; m_vkStencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; m_vkRenderingInfo.pDepthAttachment = nullptr; m_vkRenderingInfo.pStencilAttachment = nullptr; bool hasStencil = false; VKRObjectTextureView* depthStencilView = GetDepthStencilBufferImageView(hasStencil); // setup depth and stencil attachment if (depthStencilView) { m_vkDepthAttachment.imageView = depthStencilView->m_textureImageView; m_vkDepthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; m_vkDepthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; m_vkRenderingInfo.pDepthAttachment = &m_vkDepthAttachment; if (hasStencil) { m_vkStencilAttachment.imageView = depthStencilView->m_textureImageView; m_vkStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; m_vkStencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; m_vkRenderingInfo.pStencilAttachment = &m_vkStencilAttachment; } } m_vkRenderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR; m_vkRenderingInfo.pNext = nullptr; m_vkRenderingInfo.flags = 0; m_vkRenderingInfo.renderArea.offset = { 0, 0 }; m_vkRenderingInfo.renderArea.extent = m_extend; m_vkRenderingInfo.viewMask = 0; // multiview disabled m_vkRenderingInfo.layerCount = 1; } uint32 s_currentCollisionCheckIndex = 1; bool CachedFBOVk::CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const { s_currentCollisionCheckIndex++; const uint32 curColIndex = s_currentCollisionCheckIndex; for (auto& itr : m_referencedTextures) { LatteTextureVk* vkTex = (LatteTextureVk*)itr; vkTex->m_collisionCheckIndex = curColIndex; } if (vsDS) { for (auto& itr : vsDS->list_fboCandidates) { if (itr->m_collisionCheckIndex == curColIndex) return true; } } if (gsDS) { for (auto& itr : gsDS->list_fboCandidates) { if (itr->m_collisionCheckIndex == curColIndex) return true; } } if (psDS) { for (auto& itr : psDS->list_fboCandidates) { if (itr->m_collisionCheckIndex == curColIndex) return true; } } return false; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteCachedFBO.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h" class CachedFBOVk : public LatteCachedFBO { public: CachedFBOVk(uint64 key, VkDevice device) : LatteCachedFBO(key), m_device(device) { CreateRenderPass(); CreateFramebuffer(); InitDynamicRenderingData(); } ~CachedFBOVk(); static inline FSpinlock s_spinlockDependency; VKRObjectFramebuffer* GetFramebufferObj() const { return m_vkrObjFramebuffer; } VKRObjectRenderPass* GetRenderPassObj() const { return m_vkrObjRenderPass; } // for KHR_dynamic_rendering VkRenderingInfoKHR* GetRenderingInfo() { return &m_vkRenderingInfo; } void TrackDependency(class PipelineInfo* pipelineInfo) { s_spinlockDependency.lock(); m_usedByPipelines.emplace_back(pipelineInfo); s_spinlockDependency.unlock(); } void RemoveDependency(class PipelineInfo* pipelineInfo) { s_spinlockDependency.lock(); vectorRemoveByValue(m_usedByPipelines, pipelineInfo); s_spinlockDependency.unlock(); } [[nodiscard]] const VkExtent2D& GetExtend() const { return m_extend;} // checks if any of the sampled textures are output by the FBO bool CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const; private: void CreateRenderPass(); void CreateFramebuffer(); void InitDynamicRenderingData(); VKRObjectTextureView* GetColorBufferImageView(uint32 index); VKRObjectTextureView* GetDepthStencilBufferImageView(bool& hasStencil); VkDevice m_device; VKRObjectRenderPass* m_vkrObjRenderPass{}; VKRObjectFramebuffer* m_vkrObjFramebuffer{}; VkExtent2D m_extend; // for KHR_dynamic_rendering VkRenderingInfoKHR m_vkRenderingInfo; VkRenderingAttachmentInfoKHR m_vkColorAttachments[8]; VkRenderingAttachmentInfoKHR m_vkDepthAttachment; VkRenderingAttachmentInfoKHR m_vkStencilAttachment; std::vector<class PipelineInfo*> m_usedByPipelines; // PipelineInfo objects which use this renderpass/framebuffer }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/CocoaSurface.h ================================================ #pragma once #if BOOST_OS_MACOS #include <vulkan/vulkan.h> VkSurfaceKHR CreateCocoaSurface(VkInstance instance, void* handle); #endif ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/CocoaSurface.mm ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/CocoaSurface.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/MetalView.h" VkSurfaceKHR CreateCocoaSurface(VkInstance instance, void* handle) { NSView* view = (NSView*)handle; MetalView* childView = [[MetalView alloc] initWithFrame:view.bounds]; childView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; childView.wantsLayer = YES; [view addSubview:childView]; VkMetalSurfaceCreateInfoEXT surface; surface.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; surface.pNext = NULL; surface.flags = 0; surface.pLayer = (CAMetalLayer*)childView.layer; VkSurfaceKHR result; VkResult err; if ((err = vkCreateMetalSurfaceEXT(instance, &surface, nullptr, &result)) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Cannot create a Metal Vulkan surface: {}", (sint32)err); throw std::runtime_error(fmt::format("Cannot create a Metal Vulkan surface: {}", err)); } return result; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" uint32 LatteTextureVk_AdjustTextureCompSel(Latte::E_GX2SURFFMT format, uint32 compSel) { switch (format) { case Latte::E_GX2SURFFMT::R8_UNORM: // R8 is replicated on all channels (while OpenGL would return 1.0 for BGA instead) case Latte::E_GX2SURFFMT::R8_SNORM: // probably the same as _UNORM, but needs testing if (compSel >= 1 && compSel <= 3) compSel = 0; break; case Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM: // order of components is reversed (RGBA -> ABGR) if (compSel >= 0 && compSel <= 3) compSel = 3 - compSel; break; case Latte::E_GX2SURFFMT::BC4_UNORM: case Latte::E_GX2SURFFMT::BC4_SNORM: if (compSel >= 1 && compSel <= 3) compSel = 0; break; case Latte::E_GX2SURFFMT::BC5_UNORM: case Latte::E_GX2SURFFMT::BC5_SNORM: // RG maps to RG // B maps to ? // A maps to G (guessed) if (compSel == 3) compSel = 1; // read Alpha as Green break; case Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM: // reverse components (Wii U: ABGR, OpenGL: RGBA) // used in Resident Evil Revelations if (compSel >= 0 && compSel <= 3) compSel = 3 - compSel; break; case Latte::E_GX2SURFFMT::X24_G8_UINT: // map everything to alpha? if (compSel >= 0 && compSel <= 3) compSel = 3; break; case Latte::E_GX2SURFFMT::R4_G4_UNORM: // red and green swapped if (compSel == 0) compSel = 1; else if (compSel == 1) compSel = 0; break; default: break; } return compSel; } LatteTextureViewVk::LatteTextureViewVk(VkDevice device, LatteTextureVk* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) : LatteTextureView(texture, firstMip, mipCount, firstSlice, sliceCount, dim, format), m_device(device) { if(texture->overwriteInfo.hasFormatOverwrite) { cemu_assert_debug(format == texture->format); // if format overwrite is used, the texture is no longer taking part in aliasing and the format of any view has to match m_format = texture->GetFormat(); } else if (dim != texture->dim || format != texture->format) { VulkanRenderer::FormatInfoVK texFormatInfo; VulkanRenderer::GetInstance()->GetTextureFormatInfoVK(format, texture->isDepth, dim, 0, 0, &texFormatInfo); m_format = texFormatInfo.vkImageFormat; } else m_format = texture->GetFormat(); m_uniqueId = VulkanRenderer::GetInstance()->GenUniqueId(); } LatteTextureViewVk::~LatteTextureViewVk() { while (!list_descriptorSets.empty()) delete list_descriptorSets[0]; if (m_smallCacheView0) VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_smallCacheView0); if (m_smallCacheView1) VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_smallCacheView1); if (m_fallbackCache) { for (auto& itr : *m_fallbackCache) VulkanRenderer::GetInstance()->ReleaseDestructibleObject(itr.second); delete m_fallbackCache; m_fallbackCache = nullptr; } } VKRObjectTextureView* LatteTextureViewVk::CreateView(uint32 gpuSamplerSwizzle) { uint32 compSelR = (gpuSamplerSwizzle >> 16) & 0x7; uint32 compSelG = (gpuSamplerSwizzle >> 19) & 0x7; uint32 compSelB = (gpuSamplerSwizzle >> 22) & 0x7; uint32 compSelA = (gpuSamplerSwizzle >> 25) & 0x7; compSelR = LatteTextureVk_AdjustTextureCompSel(format, compSelR); compSelG = LatteTextureVk_AdjustTextureCompSel(format, compSelG); compSelB = LatteTextureVk_AdjustTextureCompSel(format, compSelB); compSelA = LatteTextureVk_AdjustTextureCompSel(format, compSelA); VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = GetBaseImage()->GetImageObj()->m_image; viewInfo.viewType = GetImageViewTypeFromGX2Dim(dim); viewInfo.format = m_format; viewInfo.subresourceRange.aspectMask = GetBaseImage()->GetImageAspect(); if (viewInfo.subresourceRange.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; // make sure stencil is never set, we only support sampling depth for now viewInfo.subresourceRange.baseMipLevel = firstMip; viewInfo.subresourceRange.levelCount = this->numMip; if (viewInfo.viewType == VK_IMAGE_VIEW_TYPE_3D && baseTexture->Is3DTexture()) { cemu_assert_debug(firstMip == 0); cemu_assert_debug(this->numSlice == baseTexture->depth); viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; } else { viewInfo.subresourceRange.baseArrayLayer = firstSlice; viewInfo.subresourceRange.layerCount = this->numSlice; } static const VkComponentSwizzle swizzle[] = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A, VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_ONE, VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_ZERO }; viewInfo.components.r = swizzle[compSelR]; viewInfo.components.g = swizzle[compSelG]; viewInfo.components.b = swizzle[compSelB]; viewInfo.components.a = swizzle[compSelA]; VkImageView view; if (vkCreateImageView(m_device, &viewInfo, nullptr, &view) != VK_SUCCESS) throw std::runtime_error("failed to create texture image view!"); return new VKRObjectTextureView(GetBaseImage()->GetImageObj(), view); } VKRObjectTextureView* LatteTextureViewVk::GetViewRGBA() { return GetSamplerView(0x06880000); // RGBA swizzle } VKRObjectTextureView* LatteTextureViewVk::GetSamplerView(uint32 gpuSamplerSwizzle) { gpuSamplerSwizzle &= 0x0FFF0000; // look up view in cache // intentionally using unrolled code here instead of a loop for a small performance gain if (m_smallCacheSwizzle0 == gpuSamplerSwizzle) return m_smallCacheView0; if (m_smallCacheSwizzle1 == gpuSamplerSwizzle) return m_smallCacheView1; auto fallbackCache = m_fallbackCache; if (m_fallbackCache) { const auto it = fallbackCache->find(gpuSamplerSwizzle); if (it != fallbackCache->cend()) return it->second; } // not cached, create new view and store in cache auto viewObj = CreateView(gpuSamplerSwizzle); if (m_smallCacheSwizzle0 == CACHE_EMPTY_ENTRY) { m_smallCacheSwizzle0 = gpuSamplerSwizzle; m_smallCacheView0 = viewObj; } else if (m_smallCacheSwizzle1 == CACHE_EMPTY_ENTRY) { m_smallCacheSwizzle1 = gpuSamplerSwizzle; m_smallCacheView1 = viewObj; } else { if (!m_fallbackCache) m_fallbackCache = new std::unordered_map<uint32, VKRObjectTextureView*>(); m_fallbackCache->insert_or_assign(gpuSamplerSwizzle, viewObj); } return viewObj; } VkSampler LatteTextureViewVk::GetDefaultTextureSampler(bool useLinearTexFilter) { VkSampler& sampler = GetViewRGBA()->m_textureDefaultSampler[useLinearTexFilter ? 1 : 0]; if (sampler != VK_NULL_HANDLE) return sampler; VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; // emulate OpenGL minFilters // see note under: https://docs.vulkan.org/spec/latest/chapters/samplers.html#VkSamplerCreateInfo // if maxLod = 0 then magnification is always performed samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; samplerInfo.minLod = 0.0f; samplerInfo.maxLod = 0.25f; if (useLinearTexFilter) { samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; } else { samplerInfo.magFilter = VK_FILTER_NEAREST; samplerInfo.minFilter = VK_FILTER_NEAREST; } samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; if (vkCreateSampler(m_device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create default sampler"); throw std::runtime_error("failed to create texture sampler!"); } return sampler; } VkImageViewType LatteTextureViewVk::GetImageViewTypeFromGX2Dim(Latte::E_DIM dim) { switch (dim) { case Latte::E_DIM::DIM_1D: return VK_IMAGE_VIEW_TYPE_1D; case Latte::E_DIM::DIM_2D: case Latte::E_DIM::DIM_2D_MSAA: return VK_IMAGE_VIEW_TYPE_2D; case Latte::E_DIM::DIM_2D_ARRAY: return VK_IMAGE_VIEW_TYPE_2D_ARRAY; case Latte::E_DIM::DIM_3D: return VK_IMAGE_VIEW_TYPE_3D; case Latte::E_DIM::DIM_CUBEMAP: return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; default: cemu_assert_unimplemented(); } return VK_IMAGE_VIEW_TYPE_2D; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h" class LatteTextureViewVk : public LatteTextureView { public: LatteTextureViewVk(VkDevice device, class LatteTextureVk* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount); ~LatteTextureViewVk(); uint64 GetUniqueId() const { return m_uniqueId; }; VKRObjectTextureView* GetViewRGBA(); VKRObjectTextureView* GetSamplerView(uint32 gpuSamplerSwizzle); VkSampler GetDefaultTextureSampler(bool useLinearTexFilter); VkFormat GetFormat() const { return m_format; } LatteTextureVk* GetBaseImage() const { return (LatteTextureVk*)baseTexture; } void AddDescriptorSetReference(struct VkDescriptorSetInfo* dsInfo) { if (std::find(list_descriptorSets.begin(), list_descriptorSets.end(), dsInfo) == list_descriptorSets.end()) list_descriptorSets.emplace_back(dsInfo); }; void RemoveDescriptorSetReference(struct VkDescriptorSetInfo* dsInfo) { list_descriptorSets.erase(std::remove(list_descriptorSets.begin(), list_descriptorSets.end(), dsInfo), list_descriptorSets.end()); }; private: VkImageViewType GetImageViewTypeFromGX2Dim(Latte::E_DIM dim); VKRObjectTextureView* CreateView(uint32 gpuSamplerSwizzle); // each texture view holds one Vulkan image view per swizzle mask. Image views are only instantiated when requested via GetViewRGBA/GetSamplerView // since a large majority of texture views will only have 1 or 2 instantiated image views, we use a small fixed-size cache // and only allocate the larger map (m_fallbackCache) if necessary inline static const uint32 CACHE_EMPTY_ENTRY = 0xFFFFFFFF; uint32 m_smallCacheSwizzle0 = { CACHE_EMPTY_ENTRY }; uint32 m_smallCacheSwizzle1 = { CACHE_EMPTY_ENTRY }; VKRObjectTextureView* m_smallCacheView0 = {}; VKRObjectTextureView* m_smallCacheView1 = {}; std::unordered_map<uint32, VKRObjectTextureView*>* m_fallbackCache{}; VkDevice m_device; VkFormat m_format; std::vector<struct VkDescriptorSetInfo*> list_descriptorSets; // list of descriptors sets referencing this view uint64 m_uniqueId; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) : LatteTexture(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth), m_vkr(vkRenderer) { vkObjTex = new VKRObjectTexture(); VkImageCreateInfo imageInfo{}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; sint32 effectiveBaseWidth = width; sint32 effectiveBaseHeight = height; sint32 effectiveBaseDepth = depth; if (overwriteInfo.hasResolutionOverwrite) { effectiveBaseWidth = overwriteInfo.width; effectiveBaseHeight = overwriteInfo.height; effectiveBaseDepth = overwriteInfo.depth; } effectiveBaseDepth = std::max(1, effectiveBaseDepth); imageInfo.extent.width = effectiveBaseWidth; imageInfo.extent.height = effectiveBaseHeight; imageInfo.mipLevels = mipLevels; imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; if (dim == Latte::E_DIM::DIM_3D) { imageInfo.extent.depth = effectiveBaseDepth; imageInfo.arrayLayers = 1; imageInfo.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; } else { imageInfo.extent.depth = 1; imageInfo.arrayLayers = effectiveBaseDepth; if (dim != Latte::E_DIM::DIM_1D && (effectiveBaseDepth % 6) == 0 && effectiveBaseWidth == effectiveBaseHeight) imageInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; } VulkanRenderer::FormatInfoVK texFormatInfo; vkRenderer->GetTextureFormatInfoVK(format, isDepth, dim, effectiveBaseWidth, effectiveBaseHeight, &texFormatInfo); cemu_assert_debug(hasStencil == ((texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0)); imageInfo.format = texFormatInfo.vkImageFormat; vkObjTex->m_imageAspect = texFormatInfo.vkImageAspect; if (isDepth == false && texFormatInfo.isCompressed) { imageInfo.flags |= VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT; } if (isDepth == false) imageInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; if (isDepth) { imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; } else { if(Latte::IsCompressedFormat(format) == false && texFormatInfo.vkImageFormat != VK_FORMAT_R4G4_UNORM_PACK8) // Vulkan's R4G4 cant be used as a color attachment imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; } if (dim == Latte::E_DIM::DIM_2D) imageInfo.imageType = VK_IMAGE_TYPE_2D; else if (dim == Latte::E_DIM::DIM_1D) imageInfo.imageType = VK_IMAGE_TYPE_1D; else if (dim == Latte::E_DIM::DIM_3D) imageInfo.imageType = VK_IMAGE_TYPE_3D; else if (dim == Latte::E_DIM::DIM_2D_ARRAY) imageInfo.imageType = VK_IMAGE_TYPE_2D; else if (dim == Latte::E_DIM::DIM_CUBEMAP) imageInfo.imageType = VK_IMAGE_TYPE_2D; else if (dim == Latte::E_DIM::DIM_2D_MSAA) imageInfo.imageType = VK_IMAGE_TYPE_2D; else { cemu_assert_unimplemented(); } if (vkCreateImage(m_vkr->GetLogicalDevice(), &imageInfo, nullptr, &vkObjTex->m_image) != VK_SUCCESS) m_vkr->UnrecoverableError("Failed to create texture image"); if (m_vkr->IsDebugMarkersEnabled()) { VkDebugUtilsObjectNameInfoEXT objName{}; objName.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; objName.objectType = VK_OBJECT_TYPE_IMAGE; objName.pNext = nullptr; objName.objectHandle = (uint64_t)vkObjTex->m_image; auto objNameStr = fmt::format("tex_{:08x}_fmt{:04x}", physAddress, (uint32)format); objName.pObjectName = objNameStr.c_str(); vkSetDebugUtilsObjectNameEXT(m_vkr->GetLogicalDevice(), &objName); } vkObjTex->m_flags = imageInfo.flags; vkObjTex->m_format = imageInfo.format; // init layout array m_layoutsMips = std::max(mipLevels, 1u); // todo - use effective mip count m_layoutsDepth = std::max(depth, 1u); if (Is3DTexture()) m_layouts.resize(m_layoutsMips, VK_IMAGE_LAYOUT_UNDEFINED); // one per mip else m_layouts.resize(m_layoutsMips * m_layoutsDepth, VK_IMAGE_LAYOUT_UNDEFINED); // one per layer per mip } LatteTextureVk::~LatteTextureVk() { cemu_assert_debug(views.empty()); m_vkr->surfaceCopy_notifyTextureRelease(this); VulkanRenderer::GetInstance()->ReleaseDestructibleObject(vkObjTex); vkObjTex = nullptr; } LatteTextureView* LatteTextureVk::CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) { cemu_assert_debug(mipCount > 0); cemu_assert_debug(sliceCount > 0); cemu_assert_debug((firstMip + mipCount) <= this->mipLevels); cemu_assert_debug((firstSlice + sliceCount) <= this->depth); return new LatteTextureViewVk(m_vkr->GetLogicalDevice(), this, dim, format, firstMip, mipCount, firstSlice, sliceCount); } void LatteTextureVk::AllocateOnHost() { auto allocationInfo = VulkanRenderer::GetInstance()->GetMemoryManager()->imageMemoryAllocate(GetImageObj()->m_image); vkObjTex->m_allocation = allocationInfo; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteTexture.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "util/ChunkedHeap/ChunkedHeap.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h" class LatteTextureVk : public LatteTexture { public: LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); ~LatteTextureVk(); void AllocateOnHost() override; VKRObjectTexture* GetImageObj() const { return vkObjTex; }; VkFormat GetFormat() const { return vkObjTex->m_format; } VkImageAspectFlags GetImageAspect() const { return vkObjTex->m_imageAspect; } VkImageLayout GetImageLayout(VkImageSubresource& subresource) { cemu_assert_debug(subresource.mipLevel < m_layoutsMips); cemu_assert_debug(subresource.arrayLayer < m_layoutsDepth); if (Is3DTexture()) return m_layouts[subresource.mipLevel]; return m_layouts[subresource.mipLevel * m_layoutsDepth + subresource.arrayLayer]; } VkImageLayout GetImageLayout(VkImageSubresourceRange& subresource) { cemu_assert_debug(subresource.baseMipLevel < m_layoutsMips); cemu_assert_debug(subresource.baseArrayLayer < m_layoutsDepth); cemu_assert_debug(subresource.levelCount == 1); if (Is3DTexture()) return m_layouts[subresource.baseMipLevel]; cemu_assert_debug(subresource.layerCount > 0); if (subresource.layerCount > 1) { VkImageLayout imgLayout = m_layouts[subresource.baseMipLevel * m_layoutsDepth + subresource.baseArrayLayer]; for (uint32 i = 1; i < subresource.layerCount; i++) { cemu_assert_debug(m_layouts[subresource.baseMipLevel * m_layoutsDepth + subresource.baseArrayLayer + i] == imgLayout); } return imgLayout; } return m_layouts[subresource.baseMipLevel * m_layoutsDepth + subresource.baseArrayLayer]; } void SetImageLayout(VkImageSubresource& subresource, VkImageLayout newLayout) { cemu_assert_debug(subresource.mipLevel < m_layoutsMips); cemu_assert_debug(subresource.arrayLayer < m_layoutsDepth); if (Is3DTexture()) m_layouts[subresource.mipLevel] = newLayout; else m_layouts[subresource.mipLevel * m_layoutsDepth + subresource.arrayLayer] = newLayout; } void SetImageLayout(VkImageSubresourceRange& subresource, VkImageLayout newLayout) { cemu_assert_debug(subresource.baseMipLevel < m_layoutsMips); cemu_assert_debug(subresource.baseArrayLayer < m_layoutsDepth); cemu_assert_debug(subresource.levelCount == 1); if (Is3DTexture()) m_layouts[subresource.baseMipLevel] = newLayout; else { for(uint32 i=0; i<subresource.layerCount; i++) m_layouts[subresource.baseMipLevel * m_layoutsDepth + subresource.baseArrayLayer + i] = newLayout; } } protected: LatteTextureView* CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) override; public: uint64 m_vkFlushIndex{}; // used to track read-write dependencies within the same renderpass uint64 m_vkFlushIndex_read{}; uint64 m_vkFlushIndex_write{}; uint32 m_collisionCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall private: class VulkanRenderer* m_vkr; VKRObjectTexture* vkObjTex{}; std::vector<VkImageLayout> m_layouts; uint32 m_layoutsMips; uint32 m_layoutsDepth; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "config/ActiveSettings.h" #include "config/CemuConfig.h" #include "util/helpers/ConcurrentQueue.h" #include "Cemu/FileCache/FileCache.h" #include <glslang/Public/ShaderLang.h> #include <glslang/SPIRV/GlslangToSpv.h> #include "util/helpers/helpers.h" bool s_isLoadingShadersVk{ false }; class FileCache* s_spirvCache{nullptr}; extern std::atomic_int g_compiled_shaders_total; extern std::atomic_int g_compiled_shaders_async; consteval TBuiltInResource GetDefaultBuiltInResource() { TBuiltInResource defaultResource = {}; defaultResource.maxLights = 32; defaultResource.maxClipPlanes = 6; defaultResource.maxTextureUnits = 32; defaultResource.maxTextureCoords = 32; defaultResource.maxVertexAttribs = 64; defaultResource.maxVertexUniformComponents = 4096; defaultResource.maxVaryingFloats = 64; defaultResource.maxVertexTextureImageUnits = 32; defaultResource.maxCombinedTextureImageUnits = 80; defaultResource.maxTextureImageUnits = 32; defaultResource.maxFragmentUniformComponents = 4096; defaultResource.maxDrawBuffers = 32; defaultResource.maxVertexUniformVectors = 128; defaultResource.maxVaryingVectors = 8; defaultResource.maxFragmentUniformVectors = 16; defaultResource.maxVertexOutputVectors = 16; defaultResource.maxFragmentInputVectors = 15; defaultResource.minProgramTexelOffset = -8; defaultResource.maxProgramTexelOffset = 7; defaultResource.maxClipDistances = 8; defaultResource.maxComputeWorkGroupCountX = 65535; defaultResource.maxComputeWorkGroupCountY = 65535; defaultResource.maxComputeWorkGroupCountZ = 65535; defaultResource.maxComputeWorkGroupSizeX = 1024; defaultResource.maxComputeWorkGroupSizeY = 1024; defaultResource.maxComputeWorkGroupSizeZ = 64; defaultResource.maxComputeUniformComponents = 1024; defaultResource.maxComputeTextureImageUnits = 16; defaultResource.maxComputeImageUniforms = 8; defaultResource.maxComputeAtomicCounters = 8; defaultResource.maxComputeAtomicCounterBuffers = 1; defaultResource.maxVaryingComponents = 60; defaultResource.maxVertexOutputComponents = 64; defaultResource.maxGeometryInputComponents = 64; defaultResource.maxGeometryOutputComponents = 128; defaultResource.maxFragmentInputComponents = 128; defaultResource.maxImageUnits = 8; defaultResource.maxCombinedImageUnitsAndFragmentOutputs = 8; defaultResource.maxCombinedShaderOutputResources = 8; defaultResource.maxImageSamples = 0; defaultResource.maxVertexImageUniforms = 0; defaultResource.maxTessControlImageUniforms = 0; defaultResource.maxTessEvaluationImageUniforms = 0; defaultResource.maxGeometryImageUniforms = 0; defaultResource.maxFragmentImageUniforms = 8; defaultResource.maxCombinedImageUniforms = 8; defaultResource.maxGeometryTextureImageUnits = 16; defaultResource.maxGeometryOutputVertices = 256; defaultResource.maxGeometryTotalOutputComponents = 1024; defaultResource.maxGeometryUniformComponents = 1024; defaultResource.maxGeometryVaryingComponents = 64; defaultResource.maxTessControlInputComponents = 128; defaultResource.maxTessControlOutputComponents = 128; defaultResource.maxTessControlTextureImageUnits = 16; defaultResource.maxTessControlUniformComponents = 1024; defaultResource.maxTessControlTotalOutputComponents = 4096; defaultResource.maxTessEvaluationInputComponents = 128; defaultResource.maxTessEvaluationOutputComponents = 128; defaultResource.maxTessEvaluationTextureImageUnits = 16; defaultResource.maxTessEvaluationUniformComponents = 1024; defaultResource.maxTessPatchComponents = 120; defaultResource.maxPatchVertices = 32; defaultResource.maxTessGenLevel = 64; defaultResource.maxViewports = 16; defaultResource.maxVertexAtomicCounters = 0; defaultResource.maxTessControlAtomicCounters = 0; defaultResource.maxTessEvaluationAtomicCounters = 0; defaultResource.maxGeometryAtomicCounters = 0; defaultResource.maxFragmentAtomicCounters = 8; defaultResource.maxCombinedAtomicCounters = 8; defaultResource.maxAtomicCounterBindings = 1; defaultResource.maxVertexAtomicCounterBuffers = 0; defaultResource.maxTessControlAtomicCounterBuffers = 0; defaultResource.maxTessEvaluationAtomicCounterBuffers = 0; defaultResource.maxGeometryAtomicCounterBuffers = 0; defaultResource.maxFragmentAtomicCounterBuffers = 1; defaultResource.maxCombinedAtomicCounterBuffers = 1; defaultResource.maxAtomicCounterBufferSize = 16384; defaultResource.maxTransformFeedbackBuffers = 4; defaultResource.maxTransformFeedbackInterleavedComponents = 64; defaultResource.maxCullDistances = 8; defaultResource.maxCombinedClipAndCullDistances = 8; defaultResource.maxSamples = 4; defaultResource.maxMeshOutputVerticesNV = 256; defaultResource.maxMeshOutputPrimitivesNV = 512; defaultResource.maxMeshWorkGroupSizeX_NV = 32; defaultResource.maxMeshWorkGroupSizeY_NV = 1; defaultResource.maxMeshWorkGroupSizeZ_NV = 1; defaultResource.maxTaskWorkGroupSizeX_NV = 32; defaultResource.maxTaskWorkGroupSizeY_NV = 1; defaultResource.maxTaskWorkGroupSizeZ_NV = 1; defaultResource.maxMeshViewCountNV = 4; defaultResource.limits = {}; defaultResource.limits.nonInductiveForLoops = true; defaultResource.limits.whileLoops = true; defaultResource.limits.doWhileLoops = true; defaultResource.limits.generalUniformIndexing = true; defaultResource.limits.generalAttributeMatrixVectorIndexing = true; defaultResource.limits.generalVaryingIndexing = true; defaultResource.limits.generalSamplerIndexing = true; defaultResource.limits.generalVariableIndexing = true; defaultResource.limits.generalConstantMatrixVectorIndexing = true; return defaultResource; }; class _ShaderVkThreadPool { public: void StartThreads() { if (m_threadsActive.exchange(true)) return; // create thread pool const uint32 threadCount = 2; for (uint32 i = 0; i < threadCount; ++i) s_threads.emplace_back(&_ShaderVkThreadPool::CompilerThreadFunc, this); } void StopThreads() { if (!m_threadsActive.exchange(false)) return; for (uint32 i = 0; i < s_threads.size(); ++i) s_compilationQueueCount.increment(); for (auto& it : s_threads) it.join(); s_threads.clear(); } ~_ShaderVkThreadPool() { StopThreads(); } void CompilerThreadFunc() { SetThreadName("vkShaderComp"); while (m_threadsActive.load(std::memory_order::relaxed)) { s_compilationQueueCount.decrementWithWait(); s_compilationQueueMutex.lock(); if (s_compilationQueue.empty()) { // queue empty again, shaders compiled synchronously via PreponeCompilation() s_compilationQueueMutex.unlock(); continue; } RendererShaderVk* job = s_compilationQueue.front(); s_compilationQueue.pop_front(); // set compilation state cemu_assert_debug(job->m_compilationState.getValue() == RendererShaderVk::COMPILATION_STATE::QUEUED); job->m_compilationState.setValue(RendererShaderVk::COMPILATION_STATE::COMPILING); s_compilationQueueMutex.unlock(); // compile job->CompileInternal(false); ++g_compiled_shaders_async; // mark as compiled cemu_assert_debug(job->m_compilationState.getValue() == RendererShaderVk::COMPILATION_STATE::COMPILING); job->m_compilationState.setValue(RendererShaderVk::COMPILATION_STATE::DONE); } } bool HasThreadsRunning() const { return m_threadsActive; } public: std::vector<std::thread> s_threads; std::deque<RendererShaderVk*> s_compilationQueue; CounterSemaphore s_compilationQueueCount; std::mutex s_compilationQueueMutex; private: std::atomic<bool> m_threadsActive; }ShaderVkThreadPool; RendererShaderVk::RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslCode) : RendererShader(type, baseHash, auxHash, isGameShader, isGfxPackShader), m_glslCode(glslCode) { // start async compilation ShaderVkThreadPool.s_compilationQueueMutex.lock(); m_compilationState.setValue(COMPILATION_STATE::QUEUED); ShaderVkThreadPool.s_compilationQueue.push_back(this); ShaderVkThreadPool.s_compilationQueueCount.increment(); ShaderVkThreadPool.s_compilationQueueMutex.unlock(); cemu_assert_debug(ShaderVkThreadPool.HasThreadsRunning()); // make sure .StartThreads() was called } RendererShaderVk::~RendererShaderVk() { while (!list_pipelineInfo.empty()) delete list_pipelineInfo[0]; VkDevice vkDev = VulkanRenderer::GetInstance()->GetLogicalDevice(); vkDestroyShaderModule(vkDev, m_shader_module, nullptr); } void RendererShaderVk::Init() { ShaderVkThreadPool.StartThreads(); } void RendererShaderVk::Shutdown() { ShaderVkThreadPool.StopThreads(); } void RendererShaderVk::CreateVkShaderModule(std::span<uint32> spirvBuffer) { VkShaderModuleCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = spirvBuffer.size_bytes(); createInfo.pCode = spirvBuffer.data(); VulkanRenderer* vkr = (VulkanRenderer*)g_renderer.get(); VkDevice m_device = vkr->GetLogicalDevice(); VkResult result = vkCreateShaderModule(m_device, &createInfo, nullptr, &m_shader_module); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Vulkan: Shader error"); throw std::runtime_error(fmt::format("Failed to create shader module: {}", result)); } // set debug name if (vkr->IsDebugMarkersEnabled()) { VkDebugUtilsObjectNameInfoEXT objName{}; objName.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; objName.objectType = VK_OBJECT_TYPE_SHADER_MODULE; objName.pNext = nullptr; objName.objectHandle = (uint64_t)m_shader_module; auto objNameStr = fmt::format("shader_{:016x}_{:016x}", m_baseHash, m_auxHash); objName.pObjectName = objNameStr.c_str(); vkSetDebugUtilsObjectNameEXT(vkr->GetLogicalDevice(), &objName); } } void RendererShaderVk::FinishCompilation() { m_glslCode.clear(); m_glslCode.shrink_to_fit(); } void RendererShaderVk::CompileInternal(bool isRenderThread) { const bool compileWithDebugInfo = ((VulkanRenderer*)g_renderer.get())->IsTracingToolEnabled(); // try to retrieve SPIR-V module from cache if (s_isLoadingShadersVk && (m_isGameShader && !m_isGfxPackShader) && s_spirvCache && !compileWithDebugInfo) { cemu_assert_debug(m_baseHash != 0); uint64 h1, h2; GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); std::vector<uint8> cacheFileData; if (s_spirvCache->GetFile({ h1, h2 }, cacheFileData)) { // generate shader from cached SPIR-V buffer CreateVkShaderModule(std::span<uint32>((uint32*)cacheFileData.data(), cacheFileData.size() / sizeof(uint32))); FinishCompilation(); return; } } EShLanguage state; switch (GetType()) { case ShaderType::kVertex: state = EShLangVertex; break; case ShaderType::kFragment: state = EShLangFragment; break; case ShaderType::kGeometry: state = EShLangGeometry; break; default: cemu_assert_debug(false); } glslang::TShader Shader(state); const char* cstr = m_glslCode.c_str(); Shader.setStrings(&cstr, 1); Shader.setEnvInput(glslang::EShSourceGlsl, state, glslang::EShClientVulkan, 100); Shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetClientVersion::EShTargetVulkan_1_1); Shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetLanguageVersion::EShTargetSpv_1_3); std::string PreprocessedGLSL; glslang::TShader::ForbidIncluder Includer; TBuiltInResource Resources = GetDefaultBuiltInResource(); EShMessages messagesPreprocess = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); if (!Shader.preprocess(&Resources, 450, ENoProfile, false, false, messagesPreprocess, &PreprocessedGLSL, Includer)) { cemuLog_log(LogType::Force, fmt::format("GLSL Preprocessing Failed For {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Shader.getInfoLog())); FinishCompilation(); return; } const char* PreprocessedCStr = PreprocessedGLSL.c_str(); Shader.setStrings(&PreprocessedCStr, 1); EShMessages messagesParseLink = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); if (!Shader.parse(&Resources, 100, false, messagesParseLink)) { cemuLog_log(LogType::Force, fmt::format("GLSL parsing failed for {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Shader.getInfoLog())); cemuLog_logDebug(LogType::Force, "GLSL source:\n{}", m_glslCode); cemu_assert_debug(false); FinishCompilation(); return; } glslang::TProgram Program; Program.addShader(&Shader); if (!Program.link(messagesParseLink)) { cemuLog_log(LogType::Force, fmt::format("GLSL linking failed for {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Program.getInfoLog())); cemu_assert_debug(false); FinishCompilation(); return; } if (!Program.mapIO()) { cemuLog_log(LogType::Force, fmt::format("GLSL linking failed for {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Program.getInfoLog())); FinishCompilation(); return; } // temp storage for SPIR-V after translation std::vector<uint32> spirvBuffer; spv::SpvBuildLogger logger; glslang::SpvOptions spvOptions; spvOptions.disableOptimizer = false; spvOptions.validate = false; spvOptions.optimizeSize = true; if (compileWithDebugInfo) { spvOptions.generateDebugInfo = true; spvOptions.emitNonSemanticShaderDebugInfo = true; spvOptions.emitNonSemanticShaderDebugSource = true; Shader.addSourceText(m_glslCode.c_str(), (uint32)m_glslCode.size()); Shader.setSourceFile(fmt::format("shader_{:016x}_{:016x}.glsl", m_baseHash, m_auxHash).c_str()); } //auto beginTime = benchmarkTimer_start(); GlslangToSpv(*Program.getIntermediate(state), spirvBuffer, &logger, &spvOptions); //double timeDur = benchmarkTimer_stop(beginTime); //forceLogRemoveMe_printf("Shader GLSL-to-SPIRV compilation took %lfms Size %08x", timeDur, spirvBuffer.size()*4); // store in cache, unless it got compiled with debug info or is a modified shader from a gfx pack if (s_spirvCache && m_isGameShader && m_isGfxPackShader == false && !compileWithDebugInfo) { uint64 h1, h2; GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); s_spirvCache->AddFile({ h1, h2 }, (const uint8*)spirvBuffer.data(), spirvBuffer.size() * sizeof(uint32)); } CreateVkShaderModule(spirvBuffer); // count compiled shader if (!s_isLoadingShadersVk) { if( m_isGameShader ) ++g_compiled_shaders_total; } FinishCompilation(); } void RendererShaderVk::PreponeCompilation(bool isRenderThread) { ShaderVkThreadPool.s_compilationQueueMutex.lock(); bool isStillQueued = m_compilationState.hasState(COMPILATION_STATE::QUEUED); if (isStillQueued) { // remove from queue ShaderVkThreadPool.s_compilationQueue.erase(std::remove(ShaderVkThreadPool.s_compilationQueue.begin(), ShaderVkThreadPool.s_compilationQueue.end(), this), ShaderVkThreadPool.s_compilationQueue.end()); m_compilationState.setValue(COMPILATION_STATE::COMPILING); } ShaderVkThreadPool.s_compilationQueueMutex.unlock(); if (!isStillQueued) { m_compilationState.waitUntilValue(COMPILATION_STATE::DONE); --g_compiled_shaders_async; // compilation caused a stall so we don't consider this one async return; } else { // compile synchronously CompileInternal(isRenderThread); m_compilationState.setValue(COMPILATION_STATE::DONE); } } bool RendererShaderVk::IsCompiled() { return m_compilationState.hasState(COMPILATION_STATE::DONE); }; bool RendererShaderVk::WaitForCompiled() { m_compilationState.waitUntilValue(COMPILATION_STATE::DONE); return true; } void RendererShaderVk::ShaderCacheLoading_begin(uint64 cacheTitleId) { if (s_spirvCache) { delete s_spirvCache; s_spirvCache = nullptr; } uint32 spirvCacheMagic = GeneratePrecompiledCacheId(); const std::string cacheFilename = fmt::format("{:016x}_spirv.bin", cacheTitleId); const fs::path cachePath = ActiveSettings::GetCachePath("shaderCache/precompiled/{}", cacheFilename); s_spirvCache = FileCache::Open(cachePath, true, spirvCacheMagic); if (s_spirvCache == nullptr) cemuLog_log(LogType::Force, "Unable to open SPIR-V cache {}", cacheFilename); s_isLoadingShadersVk = true; } void RendererShaderVk::ShaderCacheLoading_end() { // keep g_spirvCache open since we will write to it while the game is running s_isLoadingShadersVk = false; } void RendererShaderVk::ShaderCacheLoading_Close() { delete s_spirvCache; s_spirvCache = nullptr; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/RendererShader.h" #include "util/helpers/ConcurrentQueue.h" #include <vulkan/vulkan_core.h> #include "util/helpers/Semaphore.h" #include "util/helpers/fspinlock.h" class RendererShaderVk : public RendererShader { friend class VulkanRenderer; friend class _ShaderVkThreadPool; enum class COMPILATION_STATE : uint32 { NONE, QUEUED, COMPILING, DONE }; public: static void ShaderCacheLoading_begin(uint64 cacheTitleId); static void ShaderCacheLoading_end(); static void ShaderCacheLoading_Close(); RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslCode); virtual ~RendererShaderVk(); static void Init(); static void Shutdown(); VkShaderModule& GetShaderModule() { return m_shader_module; } static inline FSpinlock s_dependencyLock; void TrackDependency(class PipelineInfo* p) { s_dependencyLock.lock(); list_pipelineInfo.emplace_back(p); s_dependencyLock.unlock(); } void RemoveDependency(class PipelineInfo* p) { s_dependencyLock.lock(); vectorRemoveByValue(list_pipelineInfo, p); s_dependencyLock.unlock(); } void PreponeCompilation(bool isRenderThread) override; bool IsCompiled() override; bool WaitForCompiled() override; private: void CompileInternal(bool isRenderThread); void FinishCompilation(); VkShaderModule m_shader_module = nullptr; StateSemaphore<COMPILATION_STATE> m_compilationState{ COMPILATION_STATE::NONE }; std::string m_glslCode; void CreateVkShaderModule(std::span<uint32> spirvBuffer); // pipeline infos std::vector<class PipelineInfo*> list_pipelineInfo; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp ================================================ #include "SwapchainInfoVk.h" #include "config/CemuConfig.h" #include "WindowSystem.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteTiming.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" SwapchainInfoVk::SwapchainInfoVk(bool mainWindow, Vector2i size) : mainWindow(mainWindow), m_desiredExtent(size) { auto& windowHandleInfo = mainWindow ? WindowSystem::GetWindowInfo().canvas_main : WindowSystem::GetWindowInfo().canvas_pad; auto renderer = VulkanRenderer::GetInstance(); m_instance = renderer->GetVkInstance(); m_logicalDevice = renderer->GetLogicalDevice(); m_physicalDevice = renderer->GetPhysicalDevice(); m_surface = renderer->CreateFramebufferSurface(m_instance, windowHandleInfo); } SwapchainInfoVk::~SwapchainInfoVk() { Cleanup(); if(m_surface != VK_NULL_HANDLE) vkDestroySurfaceKHR(m_instance, m_surface, nullptr); } void SwapchainInfoVk::Create() { const auto details = QuerySwapchainSupport(m_surface, m_physicalDevice); m_surfaceFormat = ChooseSurfaceFormat(details.formats); m_actualExtent = ChooseSwapExtent(details.capabilities); // use at least two swapchain images. fewer than that causes problems on some drivers uint32_t image_count = std::max(2u, details.capabilities.minImageCount); if(details.capabilities.maxImageCount > 0) image_count = std::min(image_count, details.capabilities.maxImageCount); if(image_count < 2) cemuLog_log(LogType::Force, "Vulkan: Swapchain image count less than 2 may cause problems"); VkSwapchainCreateInfoKHR create_info = CreateSwapchainCreateInfo(m_surface, details, m_surfaceFormat, image_count, m_actualExtent); create_info.oldSwapchain = nullptr; create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; VkResult result = vkCreateSwapchainKHR(m_logicalDevice, &create_info, nullptr, &m_swapchain); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to create a swapchain"); result = vkGetSwapchainImagesKHR(m_logicalDevice, m_swapchain, &image_count, nullptr); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to retrieve the count of swapchain images"); m_swapchainImages.resize(image_count); result = vkGetSwapchainImagesKHR(m_logicalDevice, m_swapchain, &image_count, m_swapchainImages.data()); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to retrieve swapchain images"); // create default renderpass VkAttachmentDescription colorAttachment = {}; colorAttachment.format = m_surfaceFormat.format; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference colorAttachmentRef = {}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass); if (result != VK_SUCCESS) UnrecoverableError("Failed to create renderpass for swapchain"); // create swapchain image views m_swapchainImageViews.resize(m_swapchainImages.size()); for (sint32 i = 0; i < m_swapchainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = m_swapchainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; createInfo.format = m_surfaceFormat.format; createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; createInfo.subresourceRange.baseMipLevel = 0; createInfo.subresourceRange.levelCount = 1; createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; result = vkCreateImageView(m_logicalDevice, &createInfo, nullptr, &m_swapchainImageViews[i]); if (result != VK_SUCCESS) UnrecoverableError("Failed to create imageviews for swapchain"); } // create swapchain framebuffers m_swapchainFramebuffers.resize(m_swapchainImages.size()); for (size_t i = 0; i < m_swapchainImages.size(); i++) { VkImageView attachments[1]; attachments[0] = m_swapchainImageViews[i]; // create framebuffer VkFramebufferCreateInfo framebufferInfo = {}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = m_swapchainRenderPass; framebufferInfo.attachmentCount = 1; framebufferInfo.pAttachments = attachments; framebufferInfo.width = m_actualExtent.width; framebufferInfo.height = m_actualExtent.height; framebufferInfo.layers = 1; result = vkCreateFramebuffer(m_logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]); if (result != VK_SUCCESS) UnrecoverableError("Failed to create framebuffer for swapchain"); } m_presentSemaphores.resize(m_swapchainImages.size()); // create present semaphores VkSemaphoreCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; for (auto& semaphore : m_presentSemaphores){ if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) UnrecoverableError("Failed to create semaphore for swapchain present"); } m_acquireSemaphores.resize(m_swapchainImages.size()); // create acquire semaphores info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; for (auto& semaphore : m_acquireSemaphores){ if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) UnrecoverableError("Failed to create semaphore for swapchain acquire"); } VkFenceCreateInfo fenceInfo = {}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; result = vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); if (result != VK_SUCCESS) UnrecoverableError("Failed to create fence for swapchain"); m_acquireIndex = 0; hasDefinedSwapchainImage = false; m_queueDepth = 0; } void SwapchainInfoVk::Cleanup() { m_swapchainImages.clear(); for (auto& sem: m_acquireSemaphores) vkDestroySemaphore(m_logicalDevice, sem, nullptr); m_acquireSemaphores.clear(); for (auto& sem: m_presentSemaphores) vkDestroySemaphore(m_logicalDevice, sem, nullptr); m_presentSemaphores.clear(); if (m_swapchainRenderPass) { vkDestroyRenderPass(m_logicalDevice, m_swapchainRenderPass, nullptr); m_swapchainRenderPass = nullptr; } for (auto& imageView : m_swapchainImageViews) vkDestroyImageView(m_logicalDevice, imageView, nullptr); m_swapchainImageViews.clear(); for (auto& framebuffer : m_swapchainFramebuffers) vkDestroyFramebuffer(m_logicalDevice, framebuffer, nullptr); m_swapchainFramebuffers.clear(); if (m_imageAvailableFence) { WaitAvailableFence(); vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr); m_imageAvailableFence = nullptr; } if (m_swapchain) { vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr); m_swapchain = VK_NULL_HANDLE; } } bool SwapchainInfoVk::IsValid() const { return m_swapchain && !m_acquireSemaphores.empty(); } void SwapchainInfoVk::WaitAvailableFence() { if(m_awaitableFence != VK_NULL_HANDLE) vkWaitForFences(m_logicalDevice, 1, &m_awaitableFence, VK_TRUE, UINT64_MAX); m_awaitableFence = VK_NULL_HANDLE; } void SwapchainInfoVk::ResetAvailableFence() const { vkResetFences(m_logicalDevice, 1, &m_imageAvailableFence); } VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore() { VkSemaphore ret = m_currentSemaphore; m_currentSemaphore = VK_NULL_HANDLE; return ret; } bool SwapchainInfoVk::AcquireImage() { ResetAvailableFence(); VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex]; VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, 1'000'000'000, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) m_shouldRecreate = true; if (result == VK_TIMEOUT) { swapchainImageIndex = -1; return false; } if (result < 0) { swapchainImageIndex = -1; if (result != VK_ERROR_OUT_OF_DATE_KHR) throw std::runtime_error(fmt::format("Failed to acquire next image: {}", result)); return false; } m_currentSemaphore = acquireSemaphore; m_awaitableFence = m_imageAvailableFence; m_acquireIndex = (m_acquireIndex + 1) % m_swapchainImages.size(); return true; } void SwapchainInfoVk::UnrecoverableError(const char* errMsg) { cemuLog_log(LogType::Force, "Unrecoverable error in Vulkan swapchain"); cemuLog_log(LogType::Force, "Msg: {}", errMsg); throw std::runtime_error(errMsg); } SwapchainInfoVk::SwapchainSupportDetails SwapchainInfoVk::QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device) { SwapchainSupportDetails details; VkResult result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); if (result != VK_SUCCESS) { if (result != VK_ERROR_SURFACE_LOST_KHR) cemuLog_log(LogType::Force, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed. Error {}", (sint32)result); throw std::runtime_error(fmt::format("Unable to retrieve physical device surface capabilities: {}", result)); } uint32_t formatCount = 0; result = vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "vkGetPhysicalDeviceSurfaceFormatsKHR failed. Error {}", (sint32)result); throw std::runtime_error(fmt::format("Unable to retrieve the number of formats for a surface on a physical device: {}", result)); } if (formatCount != 0) { details.formats.resize(formatCount); result = vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "vkGetPhysicalDeviceSurfaceFormatsKHR failed. Error {}", (sint32)result); throw std::runtime_error(fmt::format("Unable to retrieve the formats for a surface on a physical device: {}", result)); } } uint32_t presentModeCount = 0; result = vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "vkGetPhysicalDeviceSurfacePresentModesKHR failed. Error {}", (sint32)result); throw std::runtime_error(fmt::format("Unable to retrieve the count of present modes for a surface on a physical device: {}", result)); } if (presentModeCount != 0) { details.presentModes.resize(presentModeCount); result = vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "vkGetPhysicalDeviceSurfacePresentModesKHR failed. Error {}", (sint32)result); throw std::runtime_error(fmt::format("Unable to retrieve the present modes for a surface on a physical device: {}", result)); } } return details; } VkSurfaceFormatKHR SwapchainInfoVk::ChooseSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& formats) const { if (formats.size() == 1 && formats[0].format == VK_FORMAT_UNDEFINED) return{ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; for (const auto& format : formats) { if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) return format; } return formats[0]; } VkExtent2D SwapchainInfoVk::ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) const { if (capabilities.currentExtent.width != std::numeric_limits<uint32>::max()) return capabilities.currentExtent; VkExtent2D actualExtent = { (uint32)m_desiredExtent.x, (uint32)m_desiredExtent.y }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); return actualExtent; } VkPresentModeKHR SwapchainInfoVk::ChoosePresentMode(const std::vector<VkPresentModeKHR>& modes) { m_maxQueued = 0; const auto vsyncState = (VSync)GetConfig().vsync.GetValue(); if (vsyncState == VSync::MAILBOX) { if (std::find(modes.cbegin(), modes.cend(), VK_PRESENT_MODE_MAILBOX_KHR) != modes.cend()) return VK_PRESENT_MODE_MAILBOX_KHR; cemuLog_log(LogType::Force, "Vulkan: Can't find mailbox present mode"); } else if (vsyncState == VSync::Immediate) { if (std::find(modes.cbegin(), modes.cend(), VK_PRESENT_MODE_IMMEDIATE_KHR) != modes.cend()) return VK_PRESENT_MODE_IMMEDIATE_KHR; cemuLog_log(LogType::Force, "Vulkan: Can't find immediate present mode"); } else if (vsyncState == VSync::SYNC_AND_LIMIT) { LatteTiming_EnableHostDrivenVSync(); // use immediate mode if available, other wise fall back to //if (std::find(modes.cbegin(), modes.cend(), VK_PRESENT_MODE_IMMEDIATE_KHR) != modes.cend()) // return VK_PRESENT_MODE_IMMEDIATE_KHR; //else // cemuLog_log(LogType::Force, "Vulkan: Present mode 'immediate' not available. Vsync might not behave as intended"); return VK_PRESENT_MODE_FIFO_KHR; } m_maxQueued = 1; return VK_PRESENT_MODE_FIFO_KHR; } VkSwapchainCreateInfoKHR SwapchainInfoVk::CreateSwapchainCreateInfo(VkSurfaceKHR surface, const SwapchainSupportDetails& swapchainSupport, const VkSurfaceFormatKHR& surfaceFormat, uint32 imageCount, const VkExtent2D& extent) { VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; createInfo.minImageCount = imageCount; createInfo.imageFormat = surfaceFormat.format; createInfo.imageExtent = extent; createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; const VulkanRenderer::QueueFamilyIndices indices = VulkanRenderer::GetInstance()->FindQueueFamilies(surface, m_physicalDevice); m_swapchainQueueFamilyIndices = { (uint32)indices.graphicsFamily, (uint32)indices.presentFamily }; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = m_swapchainQueueFamilyIndices.size(); createInfo.pQueueFamilyIndices = m_swapchainQueueFamilyIndices.data(); } else createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; createInfo.preTransform = swapchainSupport.capabilities.currentTransform; createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; createInfo.presentMode = ChoosePresentMode(swapchainSupport.presentModes); createInfo.clipped = VK_TRUE; cemuLog_logDebug(LogType::Force, "vulkan presentation mode: {}", createInfo.presentMode); return createInfo; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h ================================================ #pragma once #include "util/math/vector2.h" #include <vulkan/vulkan_core.h> struct SwapchainInfoVk { enum class VSync { // values here must match GeneralSettings2::m_vsync Immediate = 0, FIFO = 1, MAILBOX = 2, SYNC_AND_LIMIT = 3, // synchronize emulated vsync events to monitor vsync. But skip events if rate higher than virtual vsync period }; struct SwapchainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; std::vector<VkSurfaceFormatKHR> formats; std::vector<VkPresentModeKHR> presentModes; }; void Cleanup(); void Create(); bool IsValid() const; void WaitAvailableFence(); void ResetAvailableFence() const; bool AcquireImage(); // retrieve semaphore of last acquire for submitting a wait operation // only one wait operation must be submitted per acquire (which submits a single signal operation) // therefore subsequent calls will return a NULL handle VkSemaphore ConsumeAcquireSemaphore(); static void UnrecoverableError(const char* errMsg); static SwapchainSupportDetails QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device); VkPresentModeKHR ChoosePresentMode(const std::vector<VkPresentModeKHR>& modes); VkSurfaceFormatKHR ChooseSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& formats) const; VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) const; VkSwapchainCreateInfoKHR CreateSwapchainCreateInfo(VkSurfaceKHR surface, const SwapchainSupportDetails& swapchainSupport, const VkSurfaceFormatKHR& surfaceFormat, uint32 imageCount, const VkExtent2D& extent); VkExtent2D getExtent() const { return m_actualExtent; } SwapchainInfoVk(bool mainWindow, Vector2i size); SwapchainInfoVk(const SwapchainInfoVk&) = delete; SwapchainInfoVk(SwapchainInfoVk&&) noexcept = default; ~SwapchainInfoVk(); bool mainWindow{}; bool m_shouldRecreate = false; VSync m_vsyncState = VSync::Immediate; bool hasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state VkInstance m_instance{}; VkPhysicalDevice m_physicalDevice{}; VkDevice m_logicalDevice{}; VkSurfaceKHR m_surface{}; VkSurfaceFormatKHR m_surfaceFormat{}; VkSwapchainKHR m_swapchain{}; Vector2i m_desiredExtent{}; VkExtent2D m_actualExtent{}; uint32 swapchainImageIndex = (uint32)-1; uint64 m_presentId = 1; uint64 m_queueDepth = 0; // number of frames with pending presentation requests uint64 m_maxQueued = 0; // the maximum number of frames with presentation requests. // swapchain image ringbuffer (indexed by swapchainImageIndex) std::vector<VkImage> m_swapchainImages; std::vector<VkImageView> m_swapchainImageViews; std::vector<VkFramebuffer> m_swapchainFramebuffers; std::vector<VkSemaphore> m_presentSemaphores; // indexed by swapchainImageIndex VkRenderPass m_swapchainRenderPass = nullptr; private: uint32 m_acquireIndex = 0; std::vector<VkSemaphore> m_acquireSemaphores; // indexed by m_acquireIndex VkFence m_imageAvailableFence{}; VkFence m_awaitableFence = VK_NULL_HANDLE; VkSemaphore m_currentSemaphore = VK_NULL_HANDLE; std::array<uint32, 2> m_swapchainQueueFamilyIndices; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h" #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" LatteTextureReadbackInfoVk::LatteTextureReadbackInfoVk(VkDevice device, LatteTextureView* textureView) : LatteTextureReadbackInfo(textureView), m_device(device) { m_image_size = GetImageSize(textureView); } LatteTextureReadbackInfoVk::~LatteTextureReadbackInfoVk() { } uint32 LatteTextureReadbackInfoVk::GetImageSize(LatteTextureView* textureView) { const auto* baseTexture = (LatteTextureVk*)textureView->baseTexture; // handle format const auto textureFormat = baseTexture->GetFormat(); if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM) { cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_UNORM); return baseTexture->width * baseTexture->height * 4; } else if (textureView->format == Latte::E_GX2SURFFMT::R8_UNORM ) { cemu_assert(textureFormat == VK_FORMAT_R8_UNORM); return baseTexture->width * baseTexture->height * 1; } else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) { cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_SRGB); return baseTexture->width * baseTexture->height * 4; } else if (textureView->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) { cemu_assert(textureFormat == VK_FORMAT_R32G32B32A32_SFLOAT); return baseTexture->width * baseTexture->height * 16; } else if (textureView->format == Latte::E_GX2SURFFMT::R32_FLOAT) { cemu_assert(textureFormat == VK_FORMAT_R32_SFLOAT || textureFormat == VK_FORMAT_D32_SFLOAT); if (baseTexture->isDepth) return baseTexture->width * baseTexture->height * 4; else return baseTexture->width * baseTexture->height * 4; } else if (textureView->format == Latte::E_GX2SURFFMT::R16_UNORM) { cemu_assert(textureFormat == VK_FORMAT_R16_UNORM); if (baseTexture->isDepth) { cemu_assert_debug(false); return baseTexture->width * baseTexture->height * 2; } else { return baseTexture->width * baseTexture->height * 2; } } else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) { cemu_assert(textureFormat == VK_FORMAT_R16G16B16A16_SFLOAT); return baseTexture->width * baseTexture->height * 8; } else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_UNORM) { cemu_assert(textureFormat == VK_FORMAT_R8G8_UNORM); return baseTexture->width * baseTexture->height * 2; } else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) { cemu_assert(textureFormat == VK_FORMAT_R16G16B16A16_UNORM); return baseTexture->width * baseTexture->height * 8; } else if (textureView->format == Latte::E_GX2SURFFMT::D24_S8_UNORM) { cemu_assert(textureFormat == VK_FORMAT_D24_UNORM_S8_UINT); // todo - if driver does not support VK_FORMAT_D24_UNORM_S8_UINT this is represented as VK_FORMAT_D32_SFLOAT_S8_UINT which is 8 bytes return baseTexture->width * baseTexture->height * 4; } else if (textureView->format == Latte::E_GX2SURFFMT::R5_G6_B5_UNORM ) { if(textureFormat == VK_FORMAT_R5G6B5_UNORM_PACK16){ return baseTexture->width * baseTexture->height * 2; } return 0; } else { cemuLog_log(LogType::Force, "Unsupported texture readback format {:04x}", (uint32)textureView->format); cemu_assert_debug(false); return 0; } } void LatteTextureReadbackInfoVk::StartTransfer() { cemu_assert(m_textureView); auto* baseTexture = (LatteTextureVk*)m_textureView->baseTexture; baseTexture->GetImageObj()->flagForCurrentCommandBuffer(); cemu_assert_debug(m_textureView->firstSlice == 0); cemu_assert_debug(m_textureView->firstMip == 0); cemu_assert_debug(m_textureView->baseTexture->dim != Latte::E_DIM::DIM_3D); VkBufferImageCopy region{}; region.bufferOffset = m_buffer_offset; region.bufferRowLength = baseTexture->width; region.bufferImageHeight = baseTexture->height; region.imageSubresource.aspectMask = baseTexture->GetImageAspect(); region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageSubresource.mipLevel = 0; region.imageOffset = {0,0,0}; region.imageExtent = {(uint32)baseTexture->width,(uint32)baseTexture->height,1}; const auto renderer = VulkanRenderer::GetInstance(); renderer->draw_endRenderPass(); renderer->barrier_image<VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE, VulkanRenderer::TRANSFER_READ>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); renderer->barrier_sequentializeTransfer(); vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_GENERAL, m_buffer, 1, ®ion); renderer->barrier_sequentializeTransfer(); renderer->barrier_image<VulkanRenderer::TRANSFER_READ, VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); // make sure transfer is finished before image is modified renderer->barrier_bufferRange<VulkanRenderer::TRANSFER_WRITE, VulkanRenderer::HOST_READ>(m_buffer, m_buffer_offset, m_image_size); // make sure transfer is finished before result is read m_associatedCommandBufferId = renderer->GetCurrentCommandBufferId(); m_textureView = nullptr; // to decrease latency of readbacks make sure that the current command buffer is submitted soon renderer->RequestSubmitSoon(); renderer->RequestSubmitOnIdle(); } bool LatteTextureReadbackInfoVk::IsFinished() { const auto renderer = VulkanRenderer::GetInstance(); return renderer->HasCommandBufferFinished(m_associatedCommandBufferId); } void LatteTextureReadbackInfoVk::ForceFinish() { const auto renderer = VulkanRenderer::GetInstance(); renderer->WaitCommandBufferFinished(m_associatedCommandBufferId); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h ================================================ #pragma once #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "util/math/vector2.h" class VKRMoveableRefCounterRef { public: class VKRMoveableRefCounter* ref; }; class VKRMoveableRefCounter { public: VKRMoveableRefCounter() { selfRef = new VKRMoveableRefCounterRef(); selfRef->ref = this; } virtual ~VKRMoveableRefCounter() { cemu_assert_debug(m_refCount == 0); // remove references #ifdef CEMU_DEBUG_ASSERT for (auto itr : refs) { auto& rev = itr->ref->reverseRefs; rev.erase(std::remove(rev.begin(), rev.end(), this->selfRef), rev.end()); } #endif for (auto itr : refs) { itr->ref->m_refCount--; if (itr->ref->m_refCount == 0) itr->ref->RefCountReachedZero(); } refs.clear(); delete selfRef; selfRef = nullptr; } VKRMoveableRefCounter(const VKRMoveableRefCounter&) = delete; VKRMoveableRefCounter& operator=(const VKRMoveableRefCounter&) = delete; VKRMoveableRefCounter(VKRMoveableRefCounter&& rhs) noexcept { this->refs = std::move(rhs.refs); this->m_refCount.store(rhs.m_refCount); rhs.m_refCount = 0; this->selfRef = rhs.selfRef; rhs.selfRef = nullptr; this->selfRef->ref = this; } VKRMoveableRefCounter& operator=(VKRMoveableRefCounter&& rhs) noexcept { cemu_assert(false); return *this; } void addRef(VKRMoveableRefCounter* refTarget) { this->refs.emplace_back(refTarget->selfRef); refTarget->m_refCount++; #ifdef CEMU_DEBUG_ASSERT // add reverse ref refTarget->reverseRefs.emplace_back(this->selfRef); #endif } // methods to directly increment/decrement ref counter (for situations where no external object is available) void incRef() { m_refCount++; } void decRef() { m_refCount--; if (m_refCount == 0) RefCountReachedZero(); } protected: virtual void RefCountReachedZero() { // does nothing by default } std::atomic_int_least32_t m_refCount{}; private: VKRMoveableRefCounterRef* selfRef; std::vector<VKRMoveableRefCounterRef*> refs; #ifdef CEMU_DEBUG_ASSERT std::vector<VKRMoveableRefCounterRef*> reverseRefs; #endif }; class VKRDestructibleObject : public VKRMoveableRefCounter { public: void flagForCurrentCommandBuffer(); bool canDestroy(); virtual ~VKRDestructibleObject() {}; private: uint64 m_lastCmdBufferId{}; }; /* Vulkan objects */ class VKRObjectTexture : public VKRDestructibleObject { public: VKRObjectTexture(); ~VKRObjectTexture() override; VkImage m_image{ VK_NULL_HANDLE }; VkFormat m_format = VK_FORMAT_UNDEFINED; VkImageCreateFlags m_flags; VkImageAspectFlags m_imageAspect; struct VkImageMemAllocation* m_allocation{}; }; class VKRObjectTextureView : public VKRDestructibleObject { public: VKRObjectTextureView(VKRObjectTexture* tex, VkImageView view); ~VKRObjectTextureView() override; VkImageView m_textureImageView{ VK_NULL_HANDLE }; VkSampler m_textureDefaultSampler[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE }; // relict from LatteTextureViewVk, get rid of it eventually }; class VKRObjectSampler : public VKRDestructibleObject { public: VKRObjectSampler(VkSamplerCreateInfo* samplerInfo); ~VKRObjectSampler() override; static VKRObjectSampler* GetOrCreateSampler(VkSamplerCreateInfo* samplerInfo); static void DestroyCache(); void RefCountReachedZero() override; // sampler objects are destroyed when not referenced anymore VkSampler GetSampler() const { return m_sampler; } private: static std::unordered_map<uint64, VKRObjectSampler*> s_samplerCache; VkSampler m_sampler{ VK_NULL_HANDLE }; uint64 m_hash; }; class VKRObjectRenderPass : public VKRDestructibleObject { public: struct AttachmentEntryColor_t { VKRObjectTextureView* viewObj{}; bool isPresent{}; VkFormat format; // todo - we dont need to track isPresent or viewObj, we can just compare this with VK_FORMAT_UNDEFINED }; struct AttachmentEntryDepth_t { VKRObjectTextureView* viewObj{}; bool isPresent{}; VkFormat format; // todo - we dont need to track isPresent or viewObj, we can just compare this with VK_FORMAT_UNDEFINED bool hasStencil; }; struct AttachmentInfo_t { AttachmentEntryColor_t colorAttachment[Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS]; AttachmentEntryDepth_t depthAttachment; }; VkFormat GetColorFormat(size_t index) { if (index >= Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS) return VK_FORMAT_UNDEFINED; return m_colorAttachmentFormat[index]; } VkFormat GetDepthFormat() { return m_depthAttachmentFormat; } public: VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount = Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS); ~VKRObjectRenderPass() override; VkRenderPass m_renderPass{ VK_NULL_HANDLE }; VkFormat m_colorAttachmentFormat[Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS]; VkFormat m_depthAttachmentFormat; uint64 m_hashForPipeline; // helper var. Holds hash of all the renderpass creation parameters (mainly the formats) that affect the pipeline state }; class VKRObjectFramebuffer : public VKRDestructibleObject { public: VKRObjectFramebuffer(VKRObjectRenderPass* renderPass, std::span<VKRObjectTextureView*> attachments, Vector2i size); ~VKRObjectFramebuffer() override; VkFramebuffer m_frameBuffer{ VK_NULL_HANDLE }; }; class VKRObjectPipeline : public VKRDestructibleObject { public: VKRObjectPipeline(); ~VKRObjectPipeline() override; void SetPipeline(VkPipeline newPipeline); VkPipeline GetPipeline() const { return m_pipeline; } VkDescriptorSetLayout m_vertexDSL = VK_NULL_HANDLE, m_pixelDSL = VK_NULL_HANDLE, m_geometryDSL = VK_NULL_HANDLE; VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; private: VkPipeline m_pipeline = VK_NULL_HANDLE; }; class VKRObjectDescriptorSet : public VKRDestructibleObject { public: VKRObjectDescriptorSet(); ~VKRObjectDescriptorSet() override; VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include <imgui.h> /* VKRSynchronizedMemoryBuffer */ VKRSynchronizedRingAllocator::~VKRSynchronizedRingAllocator() { for(auto& buf : m_buffers) { m_vkrMemMgr->DeleteBuffer(buf.vk_buffer, buf.vk_mem); } } void VKRSynchronizedRingAllocator::addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset) { auto cmdBufferId = m_vkr->GetCurrentCommandBufferId(); if (cmdBufferId == buffer.lastSyncpointCmdBufferId) return; buffer.lastSyncpointCmdBufferId = cmdBufferId; buffer.queue_syncPoints.emplace(cmdBufferId, offset); } void VKRSynchronizedRingAllocator::allocateAdditionalUploadBuffer(uint32 sizeRequiredForAlloc) { // calculate buffer size, should be a multiple of bufferAllocSize that is at least as large as sizeRequiredForAlloc uint32 bufferAllocSize = m_minimumBufferAllocSize; while (bufferAllocSize < sizeRequiredForAlloc) bufferAllocSize += m_minimumBufferAllocSize; AllocatorBuffer_t newBuffer{}; newBuffer.writeIndex = 0; newBuffer.basePtr = nullptr; if (m_bufferType == VKR_BUFFER_TYPE::STAGING) m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, newBuffer.vk_buffer, newBuffer.vk_mem); else if (m_bufferType == VKR_BUFFER_TYPE::INDEX) m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem); else if (m_bufferType == VKR_BUFFER_TYPE::STRIDE) m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem); else cemu_assert_debug(false); void* bufferPtr = nullptr; vkMapMemory(m_vkr->GetLogicalDevice(), newBuffer.vk_mem, 0, VK_WHOLE_SIZE, 0, &bufferPtr); newBuffer.basePtr = (uint8*)bufferPtr; newBuffer.size = bufferAllocSize; newBuffer.index = (uint32)m_buffers.size(); m_buffers.push_back(newBuffer); } VKRSynchronizedRingAllocator::AllocatorReservation_t VKRSynchronizedRingAllocator::AllocateBufferMemory(uint32 size, uint32 alignment) { if (alignment < 128) alignment = 128; size = (size + 127) & ~127; for (auto& itr : m_buffers) { // align pointer uint32 alignmentPadding = (alignment - (itr.writeIndex % alignment)) % alignment; uint32 distanceToSyncPoint; if (!itr.queue_syncPoints.empty()) { if (itr.queue_syncPoints.front().offset < itr.writeIndex) distanceToSyncPoint = 0xFFFFFFFF; else distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex; } else distanceToSyncPoint = 0xFFFFFFFF; uint32 spaceNeeded = alignmentPadding + size; if (spaceNeeded > distanceToSyncPoint) continue; // not enough space in current buffer if ((itr.writeIndex + spaceNeeded) > itr.size) { // wrap-around spaceNeeded = size; alignmentPadding = 0; // check if there is enough space in current buffer after wrap-around if (!itr.queue_syncPoints.empty()) { distanceToSyncPoint = itr.queue_syncPoints.front().offset - 0; if (spaceNeeded > distanceToSyncPoint) continue; } else if (spaceNeeded > itr.size) continue; itr.writeIndex = 0; } addUploadBufferSyncPoint(itr, itr.writeIndex); itr.writeIndex += alignmentPadding; uint32 offset = itr.writeIndex; itr.writeIndex += size; itr.cleanupCounter = 0; VKRSynchronizedRingAllocator::AllocatorReservation_t res; res.vkBuffer = itr.vk_buffer; res.vkMem = itr.vk_mem; res.memPtr = itr.basePtr + offset; res.bufferOffset = offset; res.size = size; res.bufferIndex = itr.index; return res; } // allocate new buffer allocateAdditionalUploadBuffer(size); return AllocateBufferMemory(size, alignment); } void VKRSynchronizedRingAllocator::FlushReservation(AllocatorReservation_t& uploadReservation) { cemu_assert_debug(m_bufferType == VKR_BUFFER_TYPE::STAGING); // only the staging buffer isn't coherent // todo - use nonCoherentAtomSize for flush size (instead of hardcoded constant) VkMappedMemoryRange flushedRange{}; flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; flushedRange.memory = uploadReservation.vkMem; flushedRange.offset = uploadReservation.bufferOffset; flushedRange.size = uploadReservation.size; vkFlushMappedMemoryRanges(m_vkr->GetLogicalDevice(), 1, &flushedRange); } void VKRSynchronizedRingAllocator::CleanupBuffer(uint64 latestFinishedCommandBufferId) { if (latestFinishedCommandBufferId > 1) latestFinishedCommandBufferId -= 1; for (auto& itr : m_buffers) { while (!itr.queue_syncPoints.empty() && latestFinishedCommandBufferId > itr.queue_syncPoints.front().commandBufferId) { itr.queue_syncPoints.pop(); } if (itr.queue_syncPoints.empty()) itr.cleanupCounter++; } // check if last buffer is available for deletion if (m_buffers.size() >= 2) { auto& lastBuffer = m_buffers.back(); if (lastBuffer.cleanupCounter >= 1000) { // release buffer vkUnmapMemory(m_vkr->GetLogicalDevice(), lastBuffer.vk_mem); m_vkrMemMgr->DeleteBuffer(lastBuffer.vk_buffer, lastBuffer.vk_mem); m_buffers.pop_back(); } } } VkBuffer VKRSynchronizedRingAllocator::GetBufferByIndex(uint32 index) const { return m_buffers[index].vk_buffer; } void VKRSynchronizedRingAllocator::GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const { numBuffers = (uint32)m_buffers.size(); totalBufferSize = 0; freeBufferSize = 0; for (auto& itr : m_buffers) { totalBufferSize += itr.size; // calculate free space in buffer uint32 distanceToSyncPoint; if (!itr.queue_syncPoints.empty()) { if (itr.queue_syncPoints.front().offset < itr.writeIndex) distanceToSyncPoint = (itr.size - itr.writeIndex) + itr.queue_syncPoints.front().offset; // size with wrap-around else distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex; } else distanceToSyncPoint = itr.size; freeBufferSize += distanceToSyncPoint; } } /* VKRSynchronizedHeapAllocator */ VKRSynchronizedHeapAllocator::VKRSynchronizedHeapAllocator(class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocSize) : m_vkrMemMgr(vkMemoryManager), m_chunkedHeap(bufferType, minimumBufferAllocSize) {}; VKRSynchronizedHeapAllocator::AllocatorReservation* VKRSynchronizedHeapAllocator::AllocateBufferMemory(uint32 size, uint32 alignment) { CHAddr addr = m_chunkedHeap.alloc(size, alignment); m_activeAllocations.emplace_back(addr); AllocatorReservation* res = m_poolAllocatorReservation.allocObj(); res->bufferIndex = addr.chunkIndex; res->bufferOffset = addr.offset; res->size = size; res->memPtr = m_chunkedHeap.GetChunkPtr(addr.chunkIndex) + addr.offset; m_chunkedHeap.GetChunkVkMemInfo(addr.chunkIndex, res->vkBuffer, res->vkMem); return res; } void VKRSynchronizedHeapAllocator::FreeReservation(AllocatorReservation* uploadReservation) { // put the allocation on a delayed release queue for the current command buffer uint64 currentCommandBufferId = VulkanRenderer::GetInstance()->GetCurrentCommandBufferId(); auto it = std::find_if(m_activeAllocations.begin(), m_activeAllocations.end(), [&uploadReservation](const TrackedAllocation& allocation) { return allocation.allocation.chunkIndex == uploadReservation->bufferIndex && allocation.allocation.offset == uploadReservation->bufferOffset; }); cemu_assert_debug(it != m_activeAllocations.end()); m_releaseQueue[currentCommandBufferId].emplace_back(it->allocation); m_activeAllocations.erase(it); m_poolAllocatorReservation.freeObj(uploadReservation); } void VKRSynchronizedHeapAllocator::FlushReservation(AllocatorReservation* uploadReservation) { if (m_chunkedHeap.RequiresFlush(uploadReservation->bufferIndex)) { VkMappedMemoryRange flushedRange{}; flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; flushedRange.memory = uploadReservation->vkMem; flushedRange.offset = uploadReservation->bufferOffset; flushedRange.size = uploadReservation->size; vkFlushMappedMemoryRanges(VulkanRenderer::GetInstance()->GetLogicalDevice(), 1, &flushedRange); } } void VKRSynchronizedHeapAllocator::CleanupBuffer(uint64 latestFinishedCommandBufferId) { auto it = m_releaseQueue.begin(); while (it != m_releaseQueue.end()) { if (it->first <= latestFinishedCommandBufferId) { // release allocations for(auto& addr : it->second) m_chunkedHeap.free(addr); it = m_releaseQueue.erase(it); continue; } it++; } } void VKRSynchronizedHeapAllocator::GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const { m_chunkedHeap.GetStats(numBuffers, totalBufferSize, freeBufferSize); } /* VkTextureChunkedHeap */ VkTextureChunkedHeap::~VkTextureChunkedHeap() { VkDevice device = VulkanRenderer::GetInstance()->GetLogicalDevice(); for (auto& i : m_list_chunkInfo) { vkFreeMemory(device, i.mem, nullptr); } } uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) { cemu_assert_debug(m_list_chunkInfo.size() == chunkIndex); m_list_chunkInfo.resize(m_list_chunkInfo.size() + 1); // pad minimumAllocationSize to 32KB alignment minimumAllocationSize = (minimumAllocationSize + (32 * 1024 - 1)) & ~(32 * 1024 - 1); uint32 allocationSize = 1024 * 1024 * 128; if (chunkIndex == 0) { // make the first allocation smaller, this decreases wasted memory when there are textures that require specific flags (and thus separate heaps) allocationSize = 1024 * 1024 * 16; } if (allocationSize < minimumAllocationSize) allocationSize = minimumAllocationSize; // get available memory types/heaps std::vector<uint32> deviceLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); std::vector<uint32> hostLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, 0); // remove device local memory types from host local vector auto pred = [&deviceLocalMemoryTypeIndices](const uint32& v) -> bool { return std::find(deviceLocalMemoryTypeIndices.begin(), deviceLocalMemoryTypeIndices.end(), v) != deviceLocalMemoryTypeIndices.end(); }; hostLocalMemoryTypeIndices.erase(std::remove_if(hostLocalMemoryTypeIndices.begin(), hostLocalMemoryTypeIndices.end(), pred), hostLocalMemoryTypeIndices.end()); // allocate chunk memory for (sint32 t = 0; t < 3; t++) { // attempt to allocate from device local memory first for (auto memType : deviceLocalMemoryTypeIndices) { VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = allocationSize; allocInfo.memoryTypeIndex = memType; VkDeviceMemory imageMemory; VkResult r = vkAllocateMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), &allocInfo, nullptr, &imageMemory); if (r != VK_SUCCESS) continue; m_list_chunkInfo[chunkIndex].mem = imageMemory; return allocationSize; } // attempt to allocate from host-local memory for (auto memType : hostLocalMemoryTypeIndices) { VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = allocationSize; allocInfo.memoryTypeIndex = memType; VkDeviceMemory imageMemory; VkResult r = vkAllocateMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), &allocInfo, nullptr, &imageMemory); if (r != VK_SUCCESS) continue; m_list_chunkInfo[chunkIndex].mem = imageMemory; return allocationSize; } // retry with smaller size if possible allocationSize /= 2; if (allocationSize < minimumAllocationSize) break; cemuLog_log(LogType::Force, "Failed to allocate texture memory chunk with size {}MB. Trying again with smaller allocation size", allocationSize / 1024 / 1024); } cemuLog_log(LogType::Force, "Unable to allocate image memory chunk ({} heaps)", deviceLocalMemoryTypeIndices.size()); throw std::runtime_error("failed to allocate image memory!"); return 0; } /* VkBufferChunkedHeap */ VKRBuffer* VKRBuffer::Create(VKR_BUFFER_TYPE bufferType, size_t bufferSize, VkMemoryPropertyFlags properties) { auto* memMgr = VulkanRenderer::GetInstance()->GetMemoryManager(); VkBuffer buffer; VkDeviceMemory bufferMemory; bool allocSuccess; if (bufferType == VKR_BUFFER_TYPE::STAGING) allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, properties, buffer, bufferMemory); else if (bufferType == VKR_BUFFER_TYPE::INDEX) allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, properties, buffer, bufferMemory); else if (bufferType == VKR_BUFFER_TYPE::STRIDE) allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, properties, buffer, bufferMemory); else cemu_assert_debug(false); if (!allocSuccess) return nullptr; VKRBuffer* bufferObj = new VKRBuffer(buffer, bufferMemory); // if host visible, then map buffer void* data = nullptr; if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { vkMapMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), bufferMemory, 0, bufferSize, 0, &data); bufferObj->m_requiresFlush = !HAS_FLAG(properties, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); } bufferObj->m_mappedMemory = (uint8*)data; return bufferObj; } VKRBuffer::~VKRBuffer() { if (m_mappedMemory) vkUnmapMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_bufferMemory); if (m_bufferMemory != VK_NULL_HANDLE) vkFreeMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_bufferMemory, nullptr); if (m_buffer != VK_NULL_HANDLE) vkDestroyBuffer(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_buffer, nullptr); } VkBufferChunkedHeap::~VkBufferChunkedHeap() { for (auto& chunk : m_chunkBuffers) delete chunk; } uint32 VkBufferChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) { size_t allocationSize = std::max<size_t>(m_minimumBufferAllocationSize, minimumAllocationSize); VKRBuffer* buffer = VKRBuffer::Create(m_bufferType, allocationSize, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); if(!buffer) buffer = VKRBuffer::Create(m_bufferType, allocationSize, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); if(!buffer) VulkanRenderer::GetInstance()->UnrecoverableError("Failed to allocate buffer memory for VkBufferChunkedHeap"); cemu_assert_debug(buffer); cemu_assert_debug(m_chunkBuffers.size() == chunkIndex); m_chunkBuffers.emplace_back(buffer); // todo - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT might be worth it? return allocationSize; } bool VKRMemoryManager::FindMemoryType(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const { VkPhysicalDeviceMemoryProperties memProperties; vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties); for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { memoryIndex = i; return true; } } return false; } std::vector<uint32> VKRMemoryManager::FindMemoryTypes(uint32_t typeFilter, VkMemoryPropertyFlags properties) const { std::vector<uint32> memoryTypes; memoryTypes.clear(); VkPhysicalDeviceMemoryProperties memProperties; vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties); for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) memoryTypes.emplace_back(i); } if (memoryTypes.empty()) m_vkr->UnrecoverableError(fmt::format("Failed to find suitable memory type ({0:#08x} {1:#08x})", typeFilter, properties).c_str()); return memoryTypes; } size_t VKRMemoryManager::GetTotalMemoryForBufferType(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, size_t minimumBufferSize) { VkDevice logicalDevice = m_vkr->GetLogicalDevice(); // create temporary buffer object to get memory type VkBuffer temporaryBuffer; VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.usage = usage; bufferInfo.size = minimumBufferSize; // the buffer size can theoretically influence the memory type, is there a better way to handle this? bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; if (vkCreateBuffer(logicalDevice, &bufferInfo, nullptr, &temporaryBuffer) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Vulkan: GetTotalMemoryForBufferType() failed to create temporary buffer"); return 0; } // get memory requirements for buffer VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(logicalDevice, temporaryBuffer, &memRequirements); uint32 typeFilter = memRequirements.memoryTypeBits; // destroy temporary buffer vkDestroyBuffer(logicalDevice, temporaryBuffer, nullptr); // get list of all suitable heaps std::unordered_set<uint32> list_heapIndices; VkPhysicalDeviceMemoryProperties memProperties{}; vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties); for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) list_heapIndices.emplace(memProperties.memoryTypes[i].heapIndex); } // sum up size of heaps size_t total = 0; for (auto heapIndex : list_heapIndices) { if (heapIndex > memProperties.memoryHeapCount) continue; total += memProperties.memoryHeaps[heapIndex].size; } return total; } bool VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const { VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.usage = usage; bufferInfo.size = size; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer)"); return false; } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(m_vkr->GetLogicalDevice(), buffer, &memRequirements); VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; if (!FindMemoryType(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex)) { vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); return false; } if (vkAllocateMemory(m_vkr->GetLogicalDevice(), &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); return false; } if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) { vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBuffer)"); return false; } return true; } bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const { VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.usage = usage; bufferInfo.size = size; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VkExternalMemoryBufferCreateInfo emb{}; emb.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO; emb.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT; bufferInfo.pNext = &emb; if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer)"); return false; } VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(m_vkr->GetLogicalDevice(), buffer, &memRequirements); VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; VkImportMemoryHostPointerInfoEXT importHostMem{}; importHostMem.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT; importHostMem.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT; importHostMem.pHostPointer = hostPointer; // VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT or // VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_MAPPED_FOREIGN_MEMORY_BIT_EXT // whats the difference ? allocInfo.pNext = &importHostMem; if (!FindMemoryType(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex)) { vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); return false; } if (vkAllocateMemory(m_vkr->GetLogicalDevice(), &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); return false; } if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) { vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBufferFromHostMemory)"); return false; } return true; } void VKRMemoryManager::DeleteBuffer(VkBuffer& buffer, VkDeviceMemory& deviceMem) const { if (buffer != VK_NULL_HANDLE) vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); if (deviceMem != VK_NULL_HANDLE) vkFreeMemory(m_vkr->GetLogicalDevice(), deviceMem, nullptr); buffer = VK_NULL_HANDLE; deviceMem = VK_NULL_HANDLE; } VkImageMemAllocation* VKRMemoryManager::imageMemoryAllocate(VkImage image) { VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(m_vkr->GetLogicalDevice(), image, &memRequirements); uint32 typeFilter = memRequirements.memoryTypeBits; // get or create heap for this type filter VkTextureChunkedHeap* texHeap; auto it = map_textureHeap.find(typeFilter); if (it == map_textureHeap.end()) { texHeap = new VkTextureChunkedHeap(this, typeFilter); map_textureHeap.emplace(typeFilter, texHeap); } else texHeap = it->second.get(); // alloc mem from heap uint32 allocationSize = (uint32)memRequirements.size; CHAddr mem = texHeap->allocMem(allocationSize, (uint32)memRequirements.alignment); if (!mem.isValid()) { // allocation failed, try to make space by deleting textures // todo - improve this algorithm std::vector<LatteTexture*> deleteableTextures = LatteTC_GetDeleteableTextures(); // delete up to 20 textures from the deletable textures list, then retry allocation while (!deleteableTextures.empty()) { size_t numDelete = deleteableTextures.size(); if (numDelete > 20) numDelete = 20; for (size_t i = 0; i < numDelete; i++) LatteTexture_Delete(deleteableTextures[i]); deleteableTextures.erase(deleteableTextures.begin(), deleteableTextures.begin() + numDelete); mem = texHeap->allocMem(allocationSize, (uint32)memRequirements.alignment); if (mem.isValid()) break; } if (!mem.isValid()) { m_vkr->UnrecoverableError("Ran out of VRAM for textures"); } } vkBindImageMemory(m_vkr->GetLogicalDevice(), image, texHeap->getChunkMem(mem.chunkIndex), mem.offset); return new VkImageMemAllocation(typeFilter, mem, allocationSize); } void VKRMemoryManager::imageMemoryFree(VkImageMemAllocation* imageMemAllocation) { auto heapItr = map_textureHeap.find(imageMemAllocation->typeFilter); if (heapItr == map_textureHeap.end()) { cemuLog_log(LogType::Force, "Internal texture heap error"); return; } heapItr->second->freeMem(imageMemAllocation->mem); delete imageMemAllocation; } void VKRMemoryManager::appendOverlayHeapDebugInfo() { for (auto& itr : map_textureHeap) { uint32 heapSize; uint32 allocatedBytes; itr.second->getStatistics(heapSize, allocatedBytes); uint32 heapSizeMB = (heapSize / 1024 / 1024); uint32 allocatedBytesMB = (allocatedBytes / 1024 / 1024); ImGui::Text("%s", fmt::format("{0:#08x} Size: {1}MB/{2}MB", itr.first, allocatedBytesMB, heapSizeMB).c_str()); } } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "util/ChunkedHeap/ChunkedHeap.h" #include "util/helpers/MemoryPool.h" enum class VKR_BUFFER_TYPE { STAGING, // staging upload buffer INDEX, // buffer for index data STRIDE, // buffer for stride-adjusted vertex data }; class VKRBuffer { public: static VKRBuffer* Create(VKR_BUFFER_TYPE bufferType, size_t bufferSize, VkMemoryPropertyFlags properties); ~VKRBuffer(); VkBuffer GetVkBuffer() const { return m_buffer; } VkDeviceMemory GetVkBufferMemory() const { return m_bufferMemory; } uint8* GetPtr() const { return m_mappedMemory; } bool RequiresFlush() const { return m_requiresFlush; } private: VKRBuffer(VkBuffer buffer, VkDeviceMemory bufferMem) : m_buffer(buffer), m_bufferMemory(bufferMem) { }; VkBuffer m_buffer; VkDeviceMemory m_bufferMemory; uint8* m_mappedMemory; bool m_requiresFlush{false}; }; struct VkImageMemAllocation { VkImageMemAllocation(uint32 _typeFilter, CHAddr _mem, uint32 _allocationSize) : typeFilter(_typeFilter), mem(_mem), allocationSize(_allocationSize) { }; uint32 typeFilter; CHAddr mem; uint32 allocationSize; uint32 getAllocationSize() { return allocationSize; } }; class VkTextureChunkedHeap : private ChunkedHeap<> { public: VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter) { }; ~VkTextureChunkedHeap(); struct ChunkInfo { VkDeviceMemory mem; }; CHAddr allocMem(uint32 size, uint32 alignment) { if (alignment < 4) alignment = 4; if ((alignment & (alignment - 1)) != 0) { // not a power of two cemuLog_log(LogType::Force, "VkTextureChunkedHeap: Invalid alignment {}", alignment); } return this->alloc(size, alignment); } void freeMem(CHAddr addr) { this->free(addr); } VkDeviceMemory getChunkMem(uint32 index) { if (index >= m_list_chunkInfo.size()) return VK_NULL_HANDLE; return m_list_chunkInfo[index].mem; } void getStatistics(uint32& totalHeapSize, uint32& allocatedBytes) const { totalHeapSize = m_numHeapBytes; allocatedBytes = m_numAllocatedBytes; } private: uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override; uint32 m_typeFilter{ 0xFFFFFFFF }; class VKRMemoryManager* m_vkrMemoryManager; std::vector<ChunkInfo> m_list_chunkInfo; }; class VkBufferChunkedHeap : private ChunkedHeap<> { public: VkBufferChunkedHeap(VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocationSize) : m_bufferType(bufferType), m_minimumBufferAllocationSize(minimumBufferAllocationSize) { }; ~VkBufferChunkedHeap(); using ChunkedHeap::alloc; using ChunkedHeap::free; uint8* GetChunkPtr(uint32 index) const { if (index >= m_chunkBuffers.size()) return nullptr; return m_chunkBuffers[index]->GetPtr(); } void GetChunkVkMemInfo(uint32 index, VkBuffer& buffer, VkDeviceMemory& mem) { if (index >= m_chunkBuffers.size()) { buffer = VK_NULL_HANDLE; mem = VK_NULL_HANDLE; return; } buffer = m_chunkBuffers[index]->GetVkBuffer(); mem = m_chunkBuffers[index]->GetVkBufferMemory(); } void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const { numBuffers = m_chunkBuffers.size(); totalBufferSize = m_numHeapBytes; freeBufferSize = m_numHeapBytes - m_numAllocatedBytes; } bool RequiresFlush(uint32 index) const { if (index >= m_chunkBuffers.size()) return false; return m_chunkBuffers[index]->RequiresFlush(); } private: uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override; VKR_BUFFER_TYPE m_bufferType; std::vector<VKRBuffer*> m_chunkBuffers; size_t m_minimumBufferAllocationSize; }; // a circular ring-buffer which tracks and releases memory per command-buffer class VKRSynchronizedRingAllocator { public: VKRSynchronizedRingAllocator(class VulkanRenderer* vkRenderer, class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, uint32 minimumBufferAllocSize) : m_vkr(vkRenderer), m_vkrMemMgr(vkMemoryManager), m_bufferType(bufferType), m_minimumBufferAllocSize(minimumBufferAllocSize) {}; VKRSynchronizedRingAllocator(const VKRSynchronizedRingAllocator&) = delete; // disallow copy ~VKRSynchronizedRingAllocator(); struct BufferSyncPoint_t { // todo - modularize sync point uint64 commandBufferId; uint32 offset; BufferSyncPoint_t(uint64 _commandBufferId, uint32 _offset) : commandBufferId(_commandBufferId), offset(_offset) {}; }; struct AllocatorBuffer_t { VkBuffer vk_buffer; VkDeviceMemory vk_mem; uint8* basePtr; uint32 size; uint32 writeIndex; std::queue<BufferSyncPoint_t> queue_syncPoints; uint64 lastSyncpointCmdBufferId{ 0xFFFFFFFFFFFFFFFFull }; uint32 index; uint32 cleanupCounter{ 0 }; // increased by one every time CleanupBuffer() is called if there is no sync point. If it reaches 300 then the buffer is released }; struct AllocatorReservation_t { VkBuffer vkBuffer; VkDeviceMemory vkMem; uint8* memPtr; uint32 bufferOffset; uint32 size; uint32 bufferIndex; }; AllocatorReservation_t AllocateBufferMemory(uint32 size, uint32 alignment); void FlushReservation(AllocatorReservation_t& uploadReservation); void CleanupBuffer(uint64 latestFinishedCommandBufferId); VkBuffer GetBufferByIndex(uint32 index) const; void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const; private: void allocateAdditionalUploadBuffer(uint32 sizeRequiredForAlloc); void addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset); const class VulkanRenderer* m_vkr; const class VKRMemoryManager* m_vkrMemMgr; const VKR_BUFFER_TYPE m_bufferType; const uint32 m_minimumBufferAllocSize; std::vector<AllocatorBuffer_t> m_buffers; }; // heap style allocator with released memory being freed after the current command buffer finishes class VKRSynchronizedHeapAllocator { struct TrackedAllocation { TrackedAllocation(CHAddr allocation) : allocation(allocation) {}; CHAddr allocation; }; public: VKRSynchronizedHeapAllocator(class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocSize); VKRSynchronizedHeapAllocator(const VKRSynchronizedHeapAllocator&) = delete; // disallow copy struct AllocatorReservation { VkBuffer vkBuffer; VkDeviceMemory vkMem; uint8* memPtr; uint32 bufferOffset; uint32 size; uint32 bufferIndex; }; AllocatorReservation* AllocateBufferMemory(uint32 size, uint32 alignment); void FreeReservation(AllocatorReservation* uploadReservation); void FlushReservation(AllocatorReservation* uploadReservation); void CleanupBuffer(uint64 latestFinishedCommandBufferId); void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const; private: const class VKRMemoryManager* m_vkrMemMgr; VkBufferChunkedHeap m_chunkedHeap; // allocations std::vector<TrackedAllocation> m_activeAllocations; MemoryPool<AllocatorReservation> m_poolAllocatorReservation{32}; // release queue std::unordered_map<uint64, std::vector<CHAddr>> m_releaseQueue; }; void LatteIndices_invalidateAll(); class VKRMemoryManager { friend class VKRSynchronizedRingAllocator; public: VKRMemoryManager(class VulkanRenderer* renderer) : m_stagingBuffer(renderer, this, VKR_BUFFER_TYPE::STAGING, 32u * 1024 * 1024), m_indexBuffer(this, VKR_BUFFER_TYPE::INDEX, 4u * 1024 * 1024), m_vertexStrideMetalBuffer(renderer, this, VKR_BUFFER_TYPE::STRIDE, 4u * 1024 * 1024) { m_vkr = renderer; } // texture memory management std::unordered_map<uint32, std::unique_ptr<VkTextureChunkedHeap>> map_textureHeap; // one heap per memory type std::vector<uint8> m_textureUploadBuffer; // texture upload buffer void* TextureUploadBufferAcquire(uint32 size) { if (m_textureUploadBuffer.size() < size) m_textureUploadBuffer.resize(size); return m_textureUploadBuffer.data(); } void TextureUploadBufferRelease(uint8* mem) { cemu_assert_debug(m_textureUploadBuffer.data() == mem); m_textureUploadBuffer.clear(); } VKRSynchronizedRingAllocator& getStagingAllocator() { return m_stagingBuffer; }; // allocator for texture/attribute/uniform uploads VKRSynchronizedHeapAllocator& GetIndexAllocator() { return m_indexBuffer; }; // allocator for index data VKRSynchronizedRingAllocator& getMetalStrideWorkaroundAllocator() { return m_vertexStrideMetalBuffer; }; // allocator for stride-adjusted vertex data void cleanupBuffers(uint64 latestFinishedCommandBufferId) { LatteIndices_invalidateAll(); m_stagingBuffer.CleanupBuffer(latestFinishedCommandBufferId); m_indexBuffer.CleanupBuffer(latestFinishedCommandBufferId); m_vertexStrideMetalBuffer.CleanupBuffer(latestFinishedCommandBufferId); } bool FindMemoryType(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const; // searches for exact properties. Can gracefully fail without throwing exception (returns false) std::vector<uint32> FindMemoryTypes(uint32_t typeFilter, VkMemoryPropertyFlags properties) const; // image memory allocation void imageMemoryFree(VkImageMemAllocation* imageMemAllocation); VkImageMemAllocation* imageMemoryAllocate(VkImage image); // buffer management size_t GetTotalMemoryForBufferType(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, size_t minimumBufferSize = 16 * 1024 * 1024); bool CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; // same as CreateBuffer but doesn't throw exception on failure bool CreateBufferFromHostMemory(void* hostPointer, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; void DeleteBuffer(VkBuffer& buffer, VkDeviceMemory& deviceMem) const; // overlay info void appendOverlayHeapDebugInfo(); private: class VulkanRenderer* m_vkr; VKRSynchronizedRingAllocator m_stagingBuffer; VKRSynchronizedHeapAllocator m_indexBuffer; VKRSynchronizedRingAllocator m_vertexStrideMetalBuffer; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "imgui/imgui_impl_vulkan.h" #include "imgui/imgui_extension.h" #include "config/CemuConfig.h" PipelineInfo::PipelineInfo(uint64 minimalStateHash, uint64 pipelineHash, LatteFetchShader* fetchShader, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader) { this->minimalStateHash = minimalStateHash; this->stateHash = pipelineHash; this->fetchShader = fetchShader; this->vertexShader = vertexShader; this->geometryShader = geometryShader; this->pixelShader = pixelShader; this->vertexShaderVk = vertexShader ? (RendererShaderVk*)vertexShader->shader : nullptr; this->geometryShaderVk = geometryShader ? (RendererShaderVk*)geometryShader->shader : nullptr; this->pixelShaderVk = pixelShader ? (RendererShaderVk*)pixelShader->shader : nullptr; // init VKRObjPipeline m_vkrObjPipeline = new VKRObjectPipeline(); // track dependency with shaders if (vertexShaderVk) vertexShaderVk->TrackDependency(this); if (geometryShaderVk) geometryShaderVk->TrackDependency(this); if (pixelShaderVk) pixelShaderVk->TrackDependency(this); // "Accurate barriers" is usually enabled globally but since the CPU cost is substantial we allow users to disable it (debug -> 'Accurate barriers' option) // We always force accurate barriers for known problematic shaders if (pixelShader) { if (pixelShader->baseHash == 0x6f6f6e7b9aae57af && pixelShader->auxHash == 0x00078787f9249249) // BotW lava neverSkipAccurateBarrier = true; if (pixelShader->baseHash == 0x4c0bd596e3aef4a6 && pixelShader->auxHash == 0x003c3c3fc9269249) // BotW foam layer for water on the bottom of waterfalls neverSkipAccurateBarrier = true; if (pixelShader->baseHash == 0x3a7e6b48dae31305 && pixelShader->auxHash == 0x003c3c3fc9269249) // BotW Gerudo Town water neverSkipAccurateBarrier = true; } } PipelineInfo::~PipelineInfo() { if (rectEmulationGS) { delete rectEmulationGS; rectEmulationGS = nullptr; } // delete descriptor sets while (!pixel_ds_cache.empty()) { VkDescriptorSetInfo* dsInfo = pixel_ds_cache.begin()->second; delete dsInfo; } while (!geometry_ds_cache.empty()) { VkDescriptorSetInfo* dsInfo = geometry_ds_cache.begin()->second; delete dsInfo; } while (!vertex_ds_cache.empty()) { VkDescriptorSetInfo* dsInfo = vertex_ds_cache.begin()->second; delete dsInfo; } // disassociate from shaders if (vertexShaderVk) vertexShaderVk->RemoveDependency(this); if (geometryShaderVk) geometryShaderVk->RemoveDependency(this); if (pixelShaderVk) pixelShaderVk->RemoveDependency(this); // queue pipeline for destruction if (m_vkrObjPipeline) { VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkrObjPipeline); m_vkrObjPipeline = nullptr; } // remove from cache VulkanRenderer::GetInstance()->unregisterGraphicsPipeline(this); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.cpp ================================================ #if BOOST_OS_WINDOWS #include <Windows.h> #include "WindowSystem.h" typedef LONG NTSTATUS; typedef UINT32 D3DKMT_HANDLE; typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID; typedef struct _D3DKMT_OPENADAPTERFROMHDC { HDC hDc; D3DKMT_HANDLE hAdapter; LUID AdapterLuid; D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId; }D3DKMT_OPENADAPTERFROMHDC; typedef struct _D3DKMT_WAITFORVERTICALBLANKEVENT { D3DKMT_HANDLE hAdapter; D3DKMT_HANDLE hDevice; D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId; } D3DKMT_WAITFORVERTICALBLANKEVENT; class DeviceVsyncHandler { public: DeviceVsyncHandler(void(*cbVSync)()) : m_vsyncDriverVSyncCb(cbVSync) { m_shutdownThread = false; if (!pfnD3DKMTOpenAdapterFromHdc) { HMODULE hModuleGDI = LoadLibraryA("gdi32.dll"); *(void**)&pfnD3DKMTOpenAdapterFromHdc = GetProcAddress(hModuleGDI, "D3DKMTOpenAdapterFromHdc"); *(void**)&pfnD3DKMTWaitForVerticalBlankEvent = GetProcAddress(hModuleGDI, "D3DKMTWaitForVerticalBlankEvent"); } m_thd = std::thread(&DeviceVsyncHandler::vsyncThread, this); } ~DeviceVsyncHandler() { m_shutdownThread = true; cemu_assert_debug(m_thd.joinable()); m_thd.join(); } void notifyWindowPosChanged() { m_checkMonitorChange = true; } private: bool HasMonitorChanged() { HWND hWnd = (HWND)WindowSystem::GetWindowInfo().canvas_main.surface; if (hWnd == 0) return true; HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); MONITORINFOEXW monitorInfo{}; monitorInfo.cbSize = sizeof(monitorInfo); if (GetMonitorInfoW(hMonitor, &monitorInfo) == 0) return true; if (wcscmp(monitorInfo.szDevice, m_activeMonitorDevice) == 0) return false; return true; } HRESULT GetAdapterHandleFromHwnd(D3DKMT_HANDLE* phAdapter, UINT* pOutput) { HWND hWnd = (HWND)WindowSystem::GetWindowInfo().canvas_main.surface; if (hWnd == 0) return E_FAIL; wcsncpy(m_activeMonitorDevice, L"", 32); // reset remembered monitor device m_checkMonitorChange = false; D3DKMT_OPENADAPTERFROMHDC OpenAdapterData; *phAdapter = 0; *pOutput = 0; HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); MONITORINFOEXW monitorInfo{}; monitorInfo.cbSize = sizeof(monitorInfo); if (GetMonitorInfoW(hMonitor, &monitorInfo) == 0) return E_FAIL; HDC hdc = CreateDCW(NULL, monitorInfo.szDevice, NULL, NULL); if (hdc == NULL) { return E_FAIL; } OpenAdapterData.hDc = hdc; if (pfnD3DKMTOpenAdapterFromHdc(&OpenAdapterData) == 0) { DeleteDC(hdc); *phAdapter = OpenAdapterData.hAdapter; *pOutput = OpenAdapterData.VidPnSourceId; // remember monitor device wcsncpy(m_activeMonitorDevice, monitorInfo.szDevice, 32); return S_OK; } DeleteDC(hdc); return E_FAIL; } void vsyncThread() { D3DKMT_HANDLE hAdapter; UINT hOutput; GetAdapterHandleFromHwnd(&hAdapter, &hOutput); int failCount = 0; while (!m_shutdownThread) { D3DKMT_WAITFORVERTICALBLANKEVENT arg; arg.hDevice = 0; arg.hAdapter = hAdapter; arg.VidPnSourceId = hOutput; NTSTATUS r = pfnD3DKMTWaitForVerticalBlankEvent(&arg); if (r != 0) { //cemuLog_log(LogType::Force, "Wait for VerticalBlank failed"); Sleep(1000 / 60); failCount++; if (failCount >= 10) { while (GetAdapterHandleFromHwnd(&hAdapter, &hOutput) != S_OK) { Sleep(1000 / 60); if (m_shutdownThread) return; } failCount = 0; } } else signalVsync(); if (m_checkMonitorChange) { m_checkMonitorChange = false; if (HasMonitorChanged()) { while (GetAdapterHandleFromHwnd(&hAdapter, &hOutput) != S_OK) { Sleep(1000 / 60); if (m_shutdownThread) return; } } } } } void signalVsync() { if(m_vsyncDriverVSyncCb) m_vsyncDriverVSyncCb(); } void setCallback(void(*cbVSync)()) { m_vsyncDriverVSyncCb = cbVSync; } private: NTSTATUS(__stdcall* pfnD3DKMTOpenAdapterFromHdc)(D3DKMT_OPENADAPTERFROMHDC* Arg1) = nullptr; NTSTATUS(__stdcall* pfnD3DKMTWaitForVerticalBlankEvent)(const D3DKMT_WAITFORVERTICALBLANKEVENT* Arg1) = nullptr; std::thread m_thd; bool m_shutdownThread; bool m_checkMonitorChange{}; WCHAR m_activeMonitorDevice[32]; void (*m_vsyncDriverVSyncCb)() = nullptr; }; DeviceVsyncHandler* s_vsyncDriver = nullptr; std::mutex s_driverAccess; void VsyncDriver_startThread(void(*cbVSync)()) { std::unique_lock<std::mutex> ul(s_driverAccess); if (!s_vsyncDriver) s_vsyncDriver = new DeviceVsyncHandler(cbVSync); } void VsyncDriver_notifyWindowPosChanged() { std::unique_lock<std::mutex> ul(s_driverAccess); if (s_vsyncDriver) s_vsyncDriver->notifyWindowPosChanged(); } #else void VsyncDriver_startThread(void(*cbVSync)()) { cemu_assert_unimplemented(); } void VsyncDriver_notifyWindowPosChanged() { } #endif ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h ================================================ #pragma once void VsyncDriver_startThread(void(*cbVSync)()); void VsyncDriver_notifyWindowPosChanged(); ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #define VKFUNC_DEFINE #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include <numeric> // for std::iota #if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD #include <dlfcn.h> #endif #define VULKAN_API_CPU_BENCHMARK 0 // if 1, Cemu will log the CPU time spent per Vulkan API function bool g_vulkan_available = false; #if VULKAN_API_CPU_BENCHMARK != 0 uint64 s_vulkanBenchmarkLastResultsTime = 0; struct VulkanBenchmarkFuncInfo { std::string funcName; uint64 cycles; uint32 numCalls; }; std::vector<VulkanBenchmarkFuncInfo*> s_vulkanBenchmarkFuncs; template<typename TRet, typename... Args> auto VkWrapperFuncGenTest(TRet (*func)(Args...), const char* name) { static VulkanBenchmarkFuncInfo _FuncInfo; static auto _FuncPtrCopy = func; TRet (*newFunc)(Args...); if constexpr(std::is_void_v<TRet>) { newFunc = +[](Args... args) { uint64 t = __rdtsc(); _mm_mfence(); _FuncPtrCopy(args...); _mm_mfence(); _FuncInfo.cycles += (__rdtsc() - t); _FuncInfo.numCalls++; }; } else newFunc = +[](Args... args) -> TRet { uint64 t = __rdtsc(); _mm_mfence(); TRet r = _FuncPtrCopy(args...); _mm_mfence(); _FuncInfo.cycles += (__rdtsc() - t); _FuncInfo.numCalls++; return r; }; if(func && func != newFunc) _FuncPtrCopy = func; if(_FuncInfo.funcName.empty()) { _FuncInfo = {.funcName = name, .cycles = 0, .numCalls = 0}; s_vulkanBenchmarkFuncs.emplace_back(&_FuncInfo); } return newFunc; }; #endif // called when a TV SwapBuffers is called void VulkanBenchmarkPrintResults() { #if VULKAN_API_CPU_BENCHMARK != 0 // note: This could be done by hooking vk present functions uint64 currentCycle = __rdtsc(); uint64 elapsedCycles = currentCycle - s_vulkanBenchmarkLastResultsTime; s_vulkanBenchmarkLastResultsTime = currentCycle; double elapsedCyclesDbl = (double)elapsedCycles; cemuLog_log(LogType::Force, "--- Vulkan API CPU benchmark ---"); cemuLog_log(LogType::Force, "Elapsed cycles this frame: {:} | Current cycle {:} | NumFunc {:}", elapsedCycles, currentCycle, s_vulkanBenchmarkFuncs.size()); std::vector<sint32> sortedIndices(s_vulkanBenchmarkFuncs.size()); std::iota(sortedIndices.begin(), sortedIndices.end(), 0); std::sort(sortedIndices.begin(), sortedIndices.end(), [](int32_t a, int32_t b) { return s_vulkanBenchmarkFuncs[a]->cycles > s_vulkanBenchmarkFuncs[b]->cycles; }); for (sint32 idx : sortedIndices) { auto& func = s_vulkanBenchmarkFuncs[idx]; if(func->cycles == 0) return; cemuLog_log(LogType::Force, "{}: {} cycles ({:.4}%) {} calls", func->funcName.c_str(), func->cycles, ((double)func->cycles / elapsedCyclesDbl) * 100.0, func->numCalls); func->cycles = 0; func->numCalls = 0; } #endif } #if BOOST_OS_WINDOWS bool InitializeGlobalVulkan() { const auto hmodule = LoadLibraryA("vulkan-1.dll"); if(g_vulkan_available) return true; if (hmodule == nullptr) { cemuLog_log(LogType::Force, "Vulkan loader not available. Outdated graphics driver or Vulkan runtime not installed?"); return false; } #define VKFUNC_INIT #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" if(!vkEnumerateInstanceVersion) { cemuLog_log(LogType::Force, "vkEnumerateInstanceVersion not available. Outdated graphics driver or Vulkan runtime?"); FreeLibrary(hmodule); return false; } g_vulkan_available = true; return true; } bool InitializeInstanceVulkan(VkInstance instance) { const auto hmodule = GetModuleHandleA("vulkan-1.dll"); if (hmodule == nullptr) return false; #define VKFUNC_INSTANCE_INIT #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" return true; } bool InitializeDeviceVulkan(VkDevice device) { const auto hmodule = GetModuleHandleA("vulkan-1.dll"); if (hmodule == nullptr) return false; #define VKFUNC_DEVICE_INIT #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #if VULKAN_API_CPU_BENCHMARK != 0 #define VKFUNC_DEFINE_CUSTOM(__func) __func = VkWrapperFuncGenTest(__func, #__func) #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #endif return true; } #else void* dlopen_vulkan_loader() { #if BOOST_OS_LINUX || BOOST_OS_BSD void* vulkan_so = dlopen("libvulkan.so", RTLD_NOW); if(!vulkan_so) vulkan_so = dlopen("libvulkan.so.1", RTLD_NOW); #elif BOOST_OS_MACOS void* vulkan_so = dlopen("libMoltenVK.dylib", RTLD_NOW); #endif return vulkan_so; } bool InitializeGlobalVulkan() { void* vulkan_so = dlopen_vulkan_loader(); if(g_vulkan_available) return true; if (!vulkan_so) { cemuLog_log(LogType::Force, "Vulkan loader not available."); return false; } #define VKFUNC_INIT #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" if(!vkEnumerateInstanceVersion) { cemuLog_log(LogType::Force, "vkEnumerateInstanceVersion not available. Outdated graphics driver or Vulkan runtime?"); return false; } g_vulkan_available = true; return true; } bool InitializeInstanceVulkan(VkInstance instance) { void* vulkan_so = dlopen_vulkan_loader(); if (!vulkan_so) return false; #define VKFUNC_INSTANCE_INIT #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" return true; } bool InitializeDeviceVulkan(VkDevice device) { void* vulkan_so = dlopen_vulkan_loader(); if (!vulkan_so) return false; #define VKFUNC_DEVICE_INIT #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #if VULKAN_API_CPU_BENCHMARK != 0 #define VKFUNC_DEFINE_CUSTOM(__func) __func = VkWrapperFuncGenTest(__func, #__func) #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #endif return true; } #endif ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h ================================================ #ifndef _VULKAN_API_ #define _VULKAN_API_ #if BOOST_OS_WINDOWS #define VK_USE_PLATFORM_WIN32_KHR // todo - define in CMakeLists.txt #endif #include <vulkan/vulkan.h> bool InitializeGlobalVulkan(); bool InitializeInstanceVulkan(VkInstance instance); bool InitializeDeviceVulkan(VkDevice device); extern bool g_vulkan_available; #endif #ifdef VKFUNC_DEFINE_CUSTOM #define VKFUNC(__FUNC__) VKFUNC_DEFINE_CUSTOM(__FUNC__) #define VKFUNC_INSTANCE(__FUNC__) VKFUNC_DEFINE_CUSTOM(__FUNC__) #define VKFUNC_DEVICE(__FUNC__) VKFUNC_DEFINE_CUSTOM(__FUNC__) #elif defined(VKFUNC_DEFINE) #define VKFUNC(__FUNC__) NOEXPORT PFN_##__FUNC__ __FUNC__ = nullptr #define VKFUNC_INSTANCE(__FUNC__) NOEXPORT PFN_##__FUNC__ __FUNC__ = nullptr #define VKFUNC_DEVICE(__FUNC__) NOEXPORT PFN_##__FUNC__ __FUNC__ = nullptr #else #if defined(VKFUNC_INIT) #if BOOST_OS_WINDOWS #define VKFUNC(__FUNC__) __FUNC__ = (PFN_##__FUNC__)GetProcAddress(hmodule, #__FUNC__) #else #define VKFUNC(__FUNC__) __FUNC__ = (PFN_##__FUNC__)dlsym(vulkan_so, #__FUNC__) #endif #define VKFUNC_INSTANCE(__FUNC__) #define VKFUNC_DEVICE(__FUNC__) #elif defined(VKFUNC_INSTANCE_INIT) #define VKFUNC(__FUNC__) #define VKFUNC_INSTANCE(__FUNC__) __FUNC__ = (PFN_##__FUNC__)vkGetInstanceProcAddr(instance, #__FUNC__) #define VKFUNC_DEVICE(__FUNC__) #elif defined(VKFUNC_DEVICE_INIT) #define VKFUNC(__FUNC__) #define VKFUNC_INSTANCE(__FUNC__) #define VKFUNC_DEVICE(__FUNC__) __FUNC__ = (PFN_##__FUNC__)vkGetDeviceProcAddr(device, #__FUNC__) #else #define VKFUNC(__FUNC__) extern PFN_##__FUNC__ __FUNC__ #define VKFUNC_INSTANCE(__FUNC__) extern PFN_##__FUNC__ __FUNC__ #define VKFUNC_DEVICE(__FUNC__) extern PFN_##__FUNC__ __FUNC__ #endif #endif // global functions VKFUNC(vkGetInstanceProcAddr); VKFUNC(vkCreateInstance); VKFUNC(vkGetDeviceProcAddr); VKFUNC(vkEnumerateInstanceExtensionProperties); VKFUNC(vkEnumerateDeviceExtensionProperties); VKFUNC(vkEnumerateInstanceVersion); // instance functions VKFUNC_INSTANCE(vkDestroyInstance); VKFUNC_INSTANCE(vkEnumeratePhysicalDevices); VKFUNC_INSTANCE(vkCreateDevice); VKFUNC_INSTANCE(vkDestroyDevice); VKFUNC_INSTANCE(vkDeviceWaitIdle); // device debug functions // instance debug functions VKFUNC_INSTANCE(vkCreateDebugReportCallbackEXT); VKFUNC_INSTANCE(vkGetPhysicalDeviceToolPropertiesEXT); VKFUNC_INSTANCE(vkSetDebugUtilsObjectNameEXT); //VKFUNC_INSTANCE(vkCreateDebugReportCallbackEXT); // getters VKFUNC_DEVICE(vkGetDeviceQueue); VKFUNC_INSTANCE(vkGetPhysicalDeviceQueueFamilyProperties); VKFUNC_INSTANCE(vkGetPhysicalDeviceSurfaceSupportKHR); VKFUNC_INSTANCE(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); VKFUNC_INSTANCE(vkGetPhysicalDeviceSurfaceFormatsKHR); VKFUNC_INSTANCE(vkGetPhysicalDeviceSurfacePresentModesKHR); VKFUNC_INSTANCE(vkGetPhysicalDeviceMemoryProperties); VKFUNC_INSTANCE(vkGetPhysicalDeviceProperties); VKFUNC_INSTANCE(vkGetPhysicalDeviceProperties2); VKFUNC_INSTANCE(vkGetPhysicalDeviceFeatures2); VKFUNC_INSTANCE(vkGetPhysicalDeviceFormatProperties); // semaphore VKFUNC_DEVICE(vkCreateSemaphore); VKFUNC_DEVICE(vkDestroySemaphore); // imageview VKFUNC_DEVICE(vkCreateImageView); VKFUNC_DEVICE(vkDestroyImageView); // shader VKFUNC_DEVICE(vkCreateShaderModule); VKFUNC_DEVICE(vkDestroyShaderModule); // framebuffer VKFUNC_DEVICE(vkCreateFramebuffer); VKFUNC_DEVICE(vkDestroyFramebuffer); // renderpass VKFUNC_DEVICE(vkCreateRenderPass); VKFUNC_DEVICE(vkDestroyRenderPass); VKFUNC_DEVICE(vkCmdBeginRenderPass); VKFUNC_DEVICE(vkCmdEndRenderPass); // command buffers VKFUNC_DEVICE(vkCreateCommandPool); VKFUNC_DEVICE(vkDestroyCommandPool); VKFUNC_DEVICE(vkAllocateCommandBuffers); VKFUNC_DEVICE(vkFreeCommandBuffers); VKFUNC_DEVICE(vkBeginCommandBuffer); VKFUNC_DEVICE(vkResetCommandBuffer); VKFUNC_DEVICE(vkEndCommandBuffer); VKFUNC_DEVICE(vkQueueSubmit); // pipeline VKFUNC_DEVICE(vkCreatePipelineCache); VKFUNC_DEVICE(vkMergePipelineCaches); VKFUNC_DEVICE(vkGetPipelineCacheData); VKFUNC_DEVICE(vkDestroyPipelineCache); VKFUNC_DEVICE(vkCreatePipelineLayout); VKFUNC_DEVICE(vkDestroyPipelineLayout); VKFUNC_DEVICE(vkCreateGraphicsPipelines); VKFUNC_DEVICE(vkDestroyPipeline); VKFUNC_DEVICE(vkCmdBindPipeline); // swapchain #if BOOST_OS_LINUX || BOOST_OS_BSD VKFUNC_INSTANCE(vkCreateXlibSurfaceKHR); VKFUNC_INSTANCE(vkCreateXcbSurfaceKHR); #ifdef HAS_WAYLAND VKFUNC_INSTANCE(vkCreateWaylandSurfaceKHR); #endif #endif #if BOOST_OS_WINDOWS VKFUNC_INSTANCE(vkCreateWin32SurfaceKHR); #endif #if BOOST_OS_MACOS VKFUNC_INSTANCE(vkCreateMetalSurfaceEXT); #endif VKFUNC_INSTANCE(vkDestroySurfaceKHR); VKFUNC_DEVICE(vkCreateSwapchainKHR); VKFUNC_DEVICE(vkDestroySwapchainKHR); VKFUNC_DEVICE(vkGetSwapchainImagesKHR); VKFUNC_DEVICE(vkAcquireNextImageKHR); VKFUNC_INSTANCE(vkQueuePresentKHR); // fences VKFUNC_DEVICE(vkCreateFence); VKFUNC_DEVICE(vkWaitForFences); VKFUNC_DEVICE(vkGetFenceStatus); VKFUNC_DEVICE(vkResetFences); VKFUNC_DEVICE(vkDestroyFence); // cmd VKFUNC_DEVICE(vkCmdDraw); VKFUNC_DEVICE(vkCmdCopyBufferToImage); VKFUNC_DEVICE(vkCmdCopyImageToBuffer); VKFUNC_DEVICE(vkCmdClearColorImage); VKFUNC_DEVICE(vkCmdClearAttachments); VKFUNC_DEVICE(vkCmdBindIndexBuffer); VKFUNC_DEVICE(vkCmdBindVertexBuffers); VKFUNC_DEVICE(vkCmdDrawIndexed); VKFUNC_DEVICE(vkCmdSetViewport); VKFUNC_DEVICE(vkCmdSetScissor); VKFUNC_DEVICE(vkCmdBindDescriptorSets); VKFUNC_DEVICE(vkCmdPipelineBarrier); VKFUNC_DEVICE(vkCmdClearDepthStencilImage); VKFUNC_DEVICE(vkCmdCopyBuffer); VKFUNC_DEVICE(vkCmdCopyImage); VKFUNC_DEVICE(vkCmdBlitImage); VKFUNC_DEVICE(vkCmdPushConstants); VKFUNC_DEVICE(vkCmdSetBlendConstants); VKFUNC_DEVICE(vkCmdSetDepthBias); // synchronization2 VKFUNC_DEVICE(vkCmdPipelineBarrier2KHR); // khr_dynamic_rendering VKFUNC_DEVICE(vkCmdBeginRenderingKHR); VKFUNC_DEVICE(vkCmdEndRenderingKHR); // khr_present_wait VKFUNC_DEVICE(vkWaitForPresentKHR); // transform feedback extension VKFUNC_DEVICE(vkCmdBindTransformFeedbackBuffersEXT); VKFUNC_DEVICE(vkCmdBeginTransformFeedbackEXT); VKFUNC_DEVICE(vkCmdEndTransformFeedbackEXT); // query VKFUNC_DEVICE(vkCreateQueryPool); VKFUNC_DEVICE(vkDestroyQueryPool); VKFUNC_DEVICE(vkCmdResetQueryPool); VKFUNC_DEVICE(vkCmdBeginQuery); VKFUNC_DEVICE(vkCmdEndQuery); VKFUNC_DEVICE(vkCmdCopyQueryPoolResults); // event VKFUNC_DEVICE(vkCreateEvent); VKFUNC_DEVICE(vkCmdSetEvent); VKFUNC_DEVICE(vkCmdWaitEvents); VKFUNC_DEVICE(vkGetEventStatus); VKFUNC_DEVICE(vkDestroyEvent); // memory VKFUNC_DEVICE(vkAllocateMemory); VKFUNC_DEVICE(vkFreeMemory); VKFUNC_DEVICE(vkCreateBuffer); VKFUNC_DEVICE(vkDestroyBuffer); VKFUNC_DEVICE(vkBindBufferMemory); VKFUNC_DEVICE(vkMapMemory); VKFUNC_DEVICE(vkUnmapMemory); VKFUNC_DEVICE(vkGetBufferMemoryRequirements); VKFUNC_DEVICE(vkFlushMappedMemoryRanges); VKFUNC_DEVICE(vkInvalidateMappedMemoryRanges); // image VKFUNC_DEVICE(vkCreateImage); VKFUNC_DEVICE(vkDestroyImage); VKFUNC_DEVICE(vkGetImageMemoryRequirements); VKFUNC_DEVICE(vkBindImageMemory); VKFUNC_DEVICE(vkCreateSampler); VKFUNC_DEVICE(vkDestroySampler); VKFUNC_DEVICE(vkCreateDescriptorSetLayout); VKFUNC_DEVICE(vkAllocateDescriptorSets); VKFUNC_DEVICE(vkFreeDescriptorSets); VKFUNC_DEVICE(vkUpdateDescriptorSets); VKFUNC_DEVICE(vkCreateDescriptorPool); VKFUNC_DEVICE(vkDestroyDescriptorPool); VKFUNC_DEVICE(vkDestroyDescriptorSetLayout); #undef VKFUNC_INIT #undef VKFUNC_INSTANCE_INIT #undef VKFUNC_DEVICE_INIT #undef VKFUNC_DEFINE #undef VKFUNC #undef VKFUNC_INSTANCE #undef VKFUNC_DEVICE #undef VKFUNC_DEFINE_CUSTOM ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "config/ActiveSettings.h" #include "util/helpers/helpers.h" #include "util/helpers/Serializer.h" #include "Cafe/HW/Latte/Common/RegisterSerializer.h" /* rects emulation */ void rectsEmulationGS_outputSingleVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 vIdx, const LatteContextRegister& latteRegister) { auto parameterMask = vertexShader->outputParameterMask; for (uint32 i = 0; i < 32; i++) { if ((parameterMask & (1 << i)) == 0) continue; sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i); if (vsSemanticId < 0) continue; // make sure PS has matching input if (!psInputTable->hasPSImportForSemanticId(vsSemanticId)) continue; gsSrc.append(fmt::format("passParameterSem{}Out = passParameterSem{}In[{}];\r\n", vsSemanticId, vsSemanticId, vIdx)); } gsSrc.append(fmt::format("gl_Position = gl_in[{}].gl_Position;\r\n", vIdx)); gsSrc.append("EmitVertex();\r\n"); } void rectsEmulationGS_outputGeneratedVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, const char* variant, const LatteContextRegister& latteRegister) { auto parameterMask = vertexShader->outputParameterMask; for (uint32 i = 0; i < 32; i++) { if ((parameterMask & (1 << i)) == 0) continue; sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i); if (vsSemanticId < 0) continue; // make sure PS has matching input if (!psInputTable->hasPSImportForSemanticId(vsSemanticId)) continue; gsSrc.append(fmt::format("passParameterSem{}Out = gen4thVertex{}(passParameterSem{}In[0], passParameterSem{}In[1], passParameterSem{}In[2]);\r\n", vsSemanticId, variant, vsSemanticId, vsSemanticId, vsSemanticId)); } gsSrc.append(fmt::format("gl_Position = gen4thVertex{}(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_in[2].gl_Position);\r\n", variant)); gsSrc.append("EmitVertex();\r\n"); } void rectsEmulationGS_outputVerticesCode(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 p0, sint32 p1, sint32 p2, sint32 p3, const char* variant, const LatteContextRegister& latteRegister) { sint32 pList[4] = { p0, p1, p2, p3 }; for (sint32 i = 0; i < 4; i++) { if (pList[i] == 3) rectsEmulationGS_outputGeneratedVertex(gsSrc, vertexShader, psInputTable, variant, latteRegister); else rectsEmulationGS_outputSingleVertex(gsSrc, vertexShader, psInputTable, pList[i], latteRegister); } } RendererShaderVk* rectsEmulationGS_generate(LatteDecompilerShader* vertexShader, const LatteContextRegister& latteRegister) { std::string gsSrc; gsSrc.append("#version 450\r\n"); LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); // layout gsSrc.append("layout(triangles) in;\r\n"); gsSrc.append("layout(triangle_strip) out;\r\n"); gsSrc.append("layout(max_vertices = 4) out;\r\n"); // inputs & outputs auto parameterMask = vertexShader->outputParameterMask; for (sint32 f = 0; f < 2; f++) { for (uint32 i = 0; i < 32; i++) { if ((parameterMask & (1 << i)) == 0) continue; sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i); if (vsSemanticId < 0) continue; auto psImport = psInputTable->getPSImportBySemanticId(vsSemanticId); if (psImport == nullptr) continue; gsSrc.append(fmt::format("layout(location = {}) ", psInputTable->getPSImportLocationBySemanticId(vsSemanticId))); if (psImport->isFlat) gsSrc.append("flat "); if (psImport->isNoPerspective) gsSrc.append("noperspective "); if (f == 0) gsSrc.append("in"); else gsSrc.append("out"); if (f == 0) gsSrc.append(fmt::format(" vec4 passParameterSem{}In[];\r\n", vsSemanticId)); else gsSrc.append(fmt::format(" vec4 passParameterSem{}Out;\r\n", vsSemanticId)); } } // gen function gsSrc.append("vec4 gen4thVertexA(vec4 a, vec4 b, vec4 c)\r\n"); gsSrc.append("{\r\n"); gsSrc.append("return b - (c - a);\r\n"); gsSrc.append("}\r\n"); gsSrc.append("vec4 gen4thVertexB(vec4 a, vec4 b, vec4 c)\r\n"); gsSrc.append("{\r\n"); gsSrc.append("return c - (b - a);\r\n"); gsSrc.append("}\r\n"); gsSrc.append("vec4 gen4thVertexC(vec4 a, vec4 b, vec4 c)\r\n"); gsSrc.append("{\r\n"); gsSrc.append("return c + (b - a);\r\n"); gsSrc.append("}\r\n"); // main gsSrc.append("void main()\r\n"); gsSrc.append("{\r\n"); // there are two possible winding orders that need different triangle generation: // 0 1 // 2 3 // and // 0 1 // 3 2 // all others are just symmetries of these cases // we can determine the case by comparing the distance 0<->1 and 0<->2 gsSrc.append("float dist0_1 = length(gl_in[1].gl_Position.xy - gl_in[0].gl_Position.xy);\r\n"); gsSrc.append("float dist0_2 = length(gl_in[2].gl_Position.xy - gl_in[0].gl_Position.xy);\r\n"); gsSrc.append("float dist1_2 = length(gl_in[2].gl_Position.xy - gl_in[1].gl_Position.xy);\r\n"); // emit vertices gsSrc.append("if(dist0_1 > dist0_2 && dist0_1 > dist1_2)\r\n"); gsSrc.append("{\r\n"); // p0 to p1 is diagonal rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 2, 1, 0, 3, "A", latteRegister); gsSrc.append("} else if ( dist0_2 > dist0_1 && dist0_2 > dist1_2 ) {\r\n"); // p0 to p2 is diagonal rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 1, 2, 0, 3, "B", latteRegister); gsSrc.append("} else {\r\n"); // p1 to p2 is diagonal rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 0, 1, 2, 3, "C", latteRegister); gsSrc.append("}\r\n"); gsSrc.append("}\r\n"); auto vkShader = new RendererShaderVk(RendererShader::ShaderType::kGeometry, 0, 0, false, false, gsSrc); vkShader->PreponeCompilation(true); return vkShader; } /* pipeline compiler and cache helper */ extern std::atomic_int g_compiling_pipelines; extern std::atomic_int g_compiling_pipelines_async; extern std::atomic_uint64_t g_compiling_pipelines_syncTimeSum; PipelineCompiler::PipelineCompiler() {}; PipelineCompiler::~PipelineCompiler() { if (m_vkrObjPipeline) m_vkrObjPipeline->decRef(); if (m_renderPassObj) m_renderPassObj->decRef(); }; VkFormat PipelineCompiler::GetVertexFormat(uint8 format) { switch (format) { case FMT_32_32_32_32_FLOAT: return VK_FORMAT_R32G32B32A32_UINT; case FMT_32_32_32_FLOAT: return VK_FORMAT_R32G32B32_UINT; case FMT_32_32_FLOAT: return VK_FORMAT_R32G32_UINT; case FMT_32_FLOAT: return VK_FORMAT_R32_UINT; case FMT_8_8_8_8: return VK_FORMAT_R8G8B8A8_UINT; case FMT_8_8_8: return VK_FORMAT_R8G8B8_UINT; case FMT_8_8: return VK_FORMAT_R8G8_UINT; case FMT_8: return VK_FORMAT_R8_UINT; case FMT_32_32_32_32: return VK_FORMAT_R32G32B32A32_UINT; case FMT_32_32_32: return VK_FORMAT_R32G32B32_UINT; case FMT_32_32: return VK_FORMAT_R32G32_UINT; case FMT_32: return VK_FORMAT_R32_UINT; case FMT_16_16_16_16: return VK_FORMAT_R16G16B16A16_UINT; // verified to match OpenGL case FMT_16_16_16: return VK_FORMAT_R16G16B16_UINT; case FMT_16_16: return VK_FORMAT_R16G16_UINT; case FMT_16: return VK_FORMAT_R16_UINT; case FMT_16_16_16_16_FLOAT: return VK_FORMAT_R16G16B16A16_UINT; // verified to match OpenGL case FMT_16_16_16_FLOAT: return VK_FORMAT_R16G16B16_UINT; case FMT_16_16_FLOAT: return VK_FORMAT_R16G16_UINT; case FMT_16_FLOAT: return VK_FORMAT_R16_UINT; case FMT_2_10_10_10: return VK_FORMAT_R32_UINT; // verified to match OpenGL default: cemuLog_log(LogType::Force, "Unsupported vertex format: {:02x}", format); assert_dbg(); return VK_FORMAT_UNDEFINED; } } static VkBlendOp GetVkBlendOp(Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC combineFunc) { switch (combineFunc) { case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::DST_PLUS_SRC: return VK_BLEND_OP_ADD; case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::SRC_MINUS_DST: return VK_BLEND_OP_SUBTRACT; case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::MIN_DST_SRC: return VK_BLEND_OP_MIN; case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::MAX_DST_SRC: return VK_BLEND_OP_MAX; case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::DST_MINUS_SRC: return VK_BLEND_OP_REVERSE_SUBTRACT; default: cemu_assert_suspicious(); return VK_BLEND_OP_ADD; } } static VkBlendFactor GetVkBlendFactor(Latte::LATTE_CB_BLENDN_CONTROL::E_BLENDFACTOR factor) { const VkBlendFactor factors[] = { /* 0x00 */ VK_BLEND_FACTOR_ZERO, /* 0x01 */ VK_BLEND_FACTOR_ONE, /* 0x02 */ VK_BLEND_FACTOR_SRC_COLOR, /* 0x03 */ VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, /* 0x04 */ VK_BLEND_FACTOR_SRC_ALPHA, /* 0x05 */ VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, /* 0x06 */ VK_BLEND_FACTOR_DST_ALPHA, /* 0x07 */ VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, /* 0x08 */ VK_BLEND_FACTOR_DST_COLOR, /* 0x09 */ VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, /* 0x0A */ VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, /* 0x0B */ VK_BLEND_FACTOR_MAX_ENUM, // todo /* 0x0C */ VK_BLEND_FACTOR_MAX_ENUM, // todo /* 0x0D */ VK_BLEND_FACTOR_CONSTANT_COLOR, /* 0x0E */ VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR, /* 0x0F */ VK_BLEND_FACTOR_SRC1_COLOR, /* 0x10 */ VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR, /* 0x11 */ VK_BLEND_FACTOR_SRC1_ALPHA, /* 0x12 */ VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, /* 0x13 */ VK_BLEND_FACTOR_CONSTANT_ALPHA, /* 0x14 */ VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA }; cemu_assert_debug((uint32)factor < std::size(factors)); return factors[(uint32)factor]; } bool PipelineCompiler::ConsumesBlendConstants(VkBlendFactor blendFactor) { if (blendFactor == VK_BLEND_FACTOR_CONSTANT_COLOR || blendFactor == VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR || blendFactor == VK_BLEND_FACTOR_CONSTANT_ALPHA || blendFactor == VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA) return true; return false; } void PipelineCompiler::CreateDescriptorSetLayout(VulkanRenderer* vkRenderer, LatteDecompilerShader* shader, VkDescriptorSetLayout& layout, PipelineInfo* vkrPipelineInfo) { // create vertex shader descriptor set std::vector<VkDescriptorSetLayoutBinding> descriptorSetLayoutBindings; VkShaderStageFlags stageFlags = 0; uint32 stageIndex = 0; if (shader->shaderType == LatteConst::ShaderType::Vertex) { stageFlags = VK_SHADER_STAGE_VERTEX_BIT; stageIndex = VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX; } else if (shader->shaderType == LatteConst::ShaderType::Pixel) { stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; stageIndex = VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT; } else if (shader->shaderType == LatteConst::ShaderType::Geometry) { stageFlags = VK_SHADER_STAGE_GEOMETRY_BIT; stageIndex = VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY; } // attributes // -> not part of descriptor // textures sint32 textureBindingBase = shader->resourceMapping.getTextureBaseBindingPoint(); if (textureBindingBase >= 0) { sint32 textureCount = shader->resourceMapping.getTextureCount(); for (sint32 i = 0; i < textureCount; i++) { VkDescriptorSetLayoutBinding entry{}; entry.binding = (uint32)textureBindingBase + i; entry.descriptorCount = 1; entry.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; entry.pImmutableSamplers = nullptr; entry.stageFlags = stageFlags; descriptorSetLayoutBindings.emplace_back(entry); } } // uniform buffers if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) { VkDescriptorSetLayoutBinding entry{}; entry.binding = shader->resourceMapping.uniformVarsBufferBindingPoint; entry.descriptorCount = 1; entry.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; entry.pImmutableSamplers = nullptr; entry.stageFlags = stageFlags; descriptorSetLayoutBindings.emplace_back(entry); } for (sint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if (shader->resourceMapping.uniformBuffersBindingPoint[i] >= 0) { VkDescriptorSetLayoutBinding entry{}; entry.binding = shader->resourceMapping.uniformBuffersBindingPoint[i]; entry.descriptorCount = 1; entry.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; entry.pImmutableSamplers = nullptr; entry.stageFlags = stageFlags; descriptorSetLayoutBindings.emplace_back(entry); vkrPipelineInfo->dynamicOffsetInfo.list_uniformBuffers[stageIndex].emplace_back((uint8)i); } } // storage buffer for TF if (shader->resourceMapping.tfStorageBindingPoint >= 0) { VkDescriptorSetLayoutBinding entry{}; entry.binding = shader->resourceMapping.tfStorageBindingPoint; entry.descriptorCount = 1; entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; entry.pImmutableSamplers = nullptr; entry.stageFlags = stageFlags; descriptorSetLayoutBindings.emplace_back(entry); } if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) vkrPipelineInfo->dynamicOffsetInfo.hasUniformVar[stageIndex] = true; if (shader->resourceMapping.hasUniformBuffers()) vkrPipelineInfo->dynamicOffsetInfo.hasUniformBuffers[stageIndex] = true; VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layoutInfo.bindingCount = descriptorSetLayoutBindings.size(); layoutInfo.pBindings = descriptorSetLayoutBindings.data(); if (vkCreateDescriptorSetLayout(vkRenderer->m_logicalDevice, &layoutInfo, nullptr, &layout) != VK_SUCCESS) vkRenderer->UnrecoverableError(fmt::format("Failed to create descriptor set layout for shader {0:#x}", shader->baseHash).c_str()); } bool PipelineCompiler::InitShaderStages(VulkanRenderer* vkRenderer, RendererShaderVk* vkVertexShader, RendererShaderVk* vkPixelShader, RendererShaderVk* vkGeometryShader) { // prepare shader stages cemu_assert_debug(vkVertexShader == nullptr || vkVertexShader->IsCompiled()); cemu_assert_debug(vkPixelShader == nullptr || vkPixelShader->IsCompiled()); cemu_assert_debug(vkGeometryShader == nullptr || vkGeometryShader->IsCompiled()); if ((vkVertexShader && vkVertexShader->GetShaderModule() == VK_NULL_HANDLE) || (vkGeometryShader && vkGeometryShader->GetShaderModule() == VK_NULL_HANDLE) || (vkPixelShader && vkPixelShader->GetShaderModule() == VK_NULL_HANDLE)) { cemuLog_log(LogType::Force, "Vulkan-Info: Pipeline creation failed due to invalid shader(s)"); return false; } if (vkVertexShader) shaderStages.emplace_back(vkRenderer->CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_VERTEX_BIT, vkVertexShader->GetShaderModule(), "main")); if (vkGeometryShader) shaderStages.emplace_back(vkRenderer->CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_GEOMETRY_BIT, vkGeometryShader->GetShaderModule(), "main")); else if (m_rectEmulationGS) shaderStages.emplace_back(vkRenderer->CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_GEOMETRY_BIT, m_rectEmulationGS->GetShaderModule(), "main")); if (vkPixelShader) shaderStages.emplace_back(vkRenderer->CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_FRAGMENT_BIT, vkPixelShader->GetShaderModule(), "main")); return true; } void PipelineCompiler::InitVertexInputState(const LatteContextRegister& latteRegister, LatteDecompilerShader* vertexShader, LatteFetchShader* fetchShader) { vertexInputAttributeDescription.reserve(16); vertexInputBindingDescription.reserve(fetchShader->bufferGroups.size()); for (auto& bufferGroup : fetchShader->bufferGroups) { std::optional<LatteConst::VertexFetchType2> fetchType; for (sint32 j = 0; j < bufferGroup.attribCount; ++j) { auto& attr = bufferGroup.attrib[j]; uint32 semanticId = vertexShader->resourceMapping.attributeMapping[attr.semanticId]; if (semanticId == (uint32)-1) continue; // attribute not used? VkVertexInputAttributeDescription entry{}; entry.location = semanticId; entry.offset = attr.offset; entry.binding = attr.attributeBufferIndex; entry.format = GetVertexFormat(attr.format); vertexInputAttributeDescription.emplace_back(entry); if (fetchType.has_value()) cemu_assert_debug(fetchType == attr.fetchType); else fetchType = attr.fetchType; if (attr.fetchType == LatteConst::INSTANCE_DATA) { cemu_assert_debug(attr.aluDivisor == 1); // other divisor not yet supported // use VK_EXT_vertex_attribute_divisor } } uint32 bufferIndex = bufferGroup.attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; uint32 bufferStride = (latteRegister.GetRawView()[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; VkVertexInputBindingDescription entry{}; #if BOOST_OS_MACOS if (bufferStride % 4 != 0) { bufferStride = bufferStride + (4-(bufferStride % 4)); } #endif entry.stride = bufferStride; if (!fetchType.has_value() || fetchType == LatteConst::VertexFetchType2::VERTEX_DATA) entry.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; else if (fetchType == LatteConst::VertexFetchType2::INSTANCE_DATA) entry.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE; else { cemu_assert(false); } entry.binding = bufferIndex; vertexInputBindingDescription.emplace_back(entry); } vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = vertexInputBindingDescription.size(); vertexInputInfo.pVertexBindingDescriptions = vertexInputBindingDescription.data(); vertexInputInfo.vertexAttributeDescriptionCount = vertexInputAttributeDescription.size(); vertexInputInfo.pVertexAttributeDescriptions = vertexInputAttributeDescription.data(); } void PipelineCompiler::InitInputAssemblyState(const Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE primitiveMode) { inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssembly.primitiveRestartEnable = VK_TRUE; switch (primitiveMode) { case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS: inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; inputAssembly.primitiveRestartEnable = false; break; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINES: inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; inputAssembly.primitiveRestartEnable = false; break; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_STRIP: inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; break; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_LOOP: inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; // line loops are emulated as line strips with an extra connecting strip at the end break; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_STRIP_ADJACENT: // Tropical Freeze level 3-6 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY; break; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLES: inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; inputAssembly.primitiveRestartEnable = false; break; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLE_FAN: inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; break; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLE_STRIP: inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; break; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUADS: inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // quads are emulated as 2 triangles inputAssembly.primitiveRestartEnable = false; break; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUAD_STRIP: inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // quad strips are emulated as (count-2)/2 triangles inputAssembly.primitiveRestartEnable = false; break; case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS: inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // rects are emulated as 2 triangles inputAssembly.primitiveRestartEnable = false; break; default: cemuLog_logDebug(LogType::Force, "Vulkan-Unsupported: Graphics pipeline with primitive mode {} created", primitiveMode); cemu_assert_debug(false); } } void PipelineCompiler::InitViewportState() { viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.scissorCount = 1; } void PipelineCompiler::InitRasterizerState(const LatteContextRegister& latteRegister, VulkanRenderer* vkRenderer, bool isPrimitiveRect, bool& usesDepthBias) { // polygon control const auto& polygonControlReg = latteRegister.PA_SU_SC_MODE_CNTL; const auto frontFace = polygonControlReg.get_FRONT_FACE(); uint32 cullFront = polygonControlReg.get_CULL_FRONT(); uint32 cullBack = polygonControlReg.get_CULL_BACK(); uint32 polyOffsetFrontEnable = polygonControlReg.get_OFFSET_FRONT_ENABLED(); cemu_assert_debug(LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_NEAR_DISABLE() == LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_FAR_DISABLE()); // near or far clipping can be disabled individually bool zClipEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_FAR_DISABLE() == false; // z-clipping rasterizerExt.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_DEPTH_CLIP_STATE_CREATE_INFO_EXT; rasterizerExt.depthClipEnable = zClipEnable; rasterizerExt.flags = 0; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.rasterizerDiscardEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL(); rasterizer.pNext = VulkanRenderer::GetInstance()->m_featureControl.deviceExtensions.depth_clip_enable ? &rasterizerExt : nullptr; // GX2SetSpecialState(0, true) workaround if (!LatteGPUState.contextNew.PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA()) rasterizer.rasterizerDiscardEnable = false; rasterizer.polygonMode = VK_POLYGON_MODE_FILL; if (vkRenderer->m_featureControl.deviceExtensions.nv_fill_rectangle && isPrimitiveRect) rasterizer.polygonMode = VK_POLYGON_MODE_FILL_RECTANGLE_NV; rasterizer.depthClampEnable = VK_TRUE; // depth clamping is always enabled rasterizer.lineWidth = 1.0f; // TODO -> mmPA_SU_LINE_CNTL usesDepthBias = polyOffsetFrontEnable; if (polyOffsetFrontEnable) { rasterizer.depthBiasEnable = VK_TRUE; // initialize to zero, set dynamically via vkCmdSetDepthBias rasterizer.depthBiasConstantFactor = 0.0f; rasterizer.depthBiasSlopeFactor = 0.0f; rasterizer.depthBiasClamp = 0.0f; } else rasterizer.depthBiasEnable = VK_FALSE; // todo - how does culling behave with rects? // right now we just assume that their winding is always CW if (isPrimitiveRect) { if (frontFace == Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CW) cullFront = cullBack; else cullBack = cullFront; } if (cullFront && cullBack) rasterizer.cullMode = VK_CULL_MODE_FRONT_AND_BACK; else if (cullFront) rasterizer.cullMode = VK_CULL_MODE_FRONT_BIT; else if (cullBack) rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; else rasterizer.cullMode = VK_CULL_MODE_NONE; if (frontFace == Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CCW) rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; else rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; // multisampling multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; } bool _IsVkIntegerFormat(VkFormat fmt) { return // 8bit integer formats fmt == VK_FORMAT_R8_UINT || fmt == VK_FORMAT_R8_SINT || fmt == VK_FORMAT_R8G8_UINT || fmt == VK_FORMAT_R8G8_SINT || fmt == VK_FORMAT_R8G8B8_UINT || fmt == VK_FORMAT_R8G8B8_SINT || fmt == VK_FORMAT_R8G8B8A8_UINT || fmt == VK_FORMAT_R8G8B8A8_SINT || fmt == VK_FORMAT_B8G8R8A8_UINT || fmt == VK_FORMAT_B8G8R8A8_SINT || // 16bit integer formats fmt == VK_FORMAT_R16_UINT || fmt == VK_FORMAT_R16_SINT || fmt == VK_FORMAT_R16G16_UINT || fmt == VK_FORMAT_R16G16_SINT || fmt == VK_FORMAT_R16G16B16_UINT || fmt == VK_FORMAT_R16G16B16_SINT || fmt == VK_FORMAT_R16G16B16A16_UINT || fmt == VK_FORMAT_R16G16B16A16_SINT || // 32bit integer formats fmt == VK_FORMAT_R32_UINT || fmt == VK_FORMAT_R32_SINT || fmt == VK_FORMAT_R32G32_UINT || fmt == VK_FORMAT_R32G32_SINT || fmt == VK_FORMAT_R32G32B32_UINT || fmt == VK_FORMAT_R32G32B32_SINT || fmt == VK_FORMAT_R32G32B32A32_UINT || fmt == VK_FORMAT_R32G32B32A32_SINT; } void PipelineCompiler::InitBlendState(const LatteContextRegister& latteRegister, PipelineInfo* pipelineInfo, bool& usesBlendConstants, VKRObjectRenderPass* renderPassObj) { const Latte::LATTE_CB_COLOR_CONTROL& colorControlReg = latteRegister.CB_COLOR_CONTROL; uint32 blendEnableMask = colorControlReg.get_BLEND_MASK(); uint32 renderTargetMask = latteRegister.CB_TARGET_MASK.get_MASK(); usesBlendConstants = false; for (size_t i = 0; i < colorBlendAttachments.size(); i++) { auto& entry = colorBlendAttachments[i]; if (((blendEnableMask & (1 << i))) != 0) entry.blendEnable = VK_TRUE; else entry.blendEnable = VK_FALSE; if (entry.blendEnable != VK_FALSE && _IsVkIntegerFormat(renderPassObj->GetColorFormat(i))) { // force-disable blending for integer formats entry.blendEnable = VK_FALSE; } const auto& blendControlReg = latteRegister.CB_BLENDN_CONTROL[i]; entry.colorWriteMask = (renderTargetMask >> (i * 4)) & 0xF; entry.colorBlendOp = GetVkBlendOp(blendControlReg.get_COLOR_COMB_FCN()); entry.srcColorBlendFactor = GetVkBlendFactor(blendControlReg.get_COLOR_SRCBLEND()); entry.dstColorBlendFactor = GetVkBlendFactor(blendControlReg.get_COLOR_DSTBLEND()); if (blendControlReg.get_SEPARATE_ALPHA_BLEND()) { entry.alphaBlendOp = GetVkBlendOp(blendControlReg.get_ALPHA_COMB_FCN()); entry.srcAlphaBlendFactor = GetVkBlendFactor(blendControlReg.get_ALPHA_SRCBLEND()); entry.dstAlphaBlendFactor = GetVkBlendFactor(blendControlReg.get_ALPHA_DSTBLEND()); } else { entry.alphaBlendOp = entry.colorBlendOp; entry.srcAlphaBlendFactor = entry.srcColorBlendFactor; entry.dstAlphaBlendFactor = entry.dstColorBlendFactor; } usesBlendConstants |= ConsumesBlendConstants(entry.srcColorBlendFactor); usesBlendConstants |= ConsumesBlendConstants(entry.dstColorBlendFactor); usesBlendConstants |= ConsumesBlendConstants(entry.srcAlphaBlendFactor); usesBlendConstants |= ConsumesBlendConstants(entry.dstAlphaBlendFactor); } // setup VkPipelineColorBlendStateCreateInfo colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; const auto logicOp = colorControlReg.get_ROP(); if (logicOp == Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::COPY) { colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; } else { colorBlending.logicOpEnable = VK_TRUE; switch (logicOp) { case Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::SET: colorBlending.logicOp = VK_LOGIC_OP_SET; break; case Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::CLEAR: colorBlending.logicOp = VK_LOGIC_OP_CLEAR; break; case Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::OR: colorBlending.logicOp = VK_LOGIC_OP_OR; break; default: colorBlending.logicOp = VK_LOGIC_OP_COPY; cemu_assert_unimplemented(); } } colorBlending.attachmentCount = colorBlendAttachments.size(); colorBlending.pAttachments = colorBlendAttachments.data(); // we use VK_DYNAMIC_STATE_BLEND_CONSTANTS, the blend constants here don't matter colorBlending.blendConstants[0] = 0; colorBlending.blendConstants[1] = 0; colorBlending.blendConstants[2] = 0; colorBlending.blendConstants[3] = 0; } void PipelineCompiler::InitDescriptorSetLayouts(VulkanRenderer* vkRenderer, PipelineInfo* vkrPipelineInfo, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader) { auto vkObjPipeline = vkrPipelineInfo->m_vkrObjPipeline; if (vertexShader) { cemu_assert_debug(descriptorSetLayoutCount == 0); CreateDescriptorSetLayout(vkRenderer, vertexShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); vkObjPipeline->m_vertexDSL = descriptorSetLayout[descriptorSetLayoutCount]; descriptorSetLayoutCount++; } if (pixelShader) { cemu_assert_debug(descriptorSetLayoutCount == 1); CreateDescriptorSetLayout(vkRenderer, pixelShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); vkObjPipeline->m_pixelDSL = descriptorSetLayout[descriptorSetLayoutCount]; descriptorSetLayoutCount++; } else if (geometryShader) { // if no pixel shader is present, create empty placeholder descriptor set layout (geometry shader set must be at index 2) VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layoutInfo.bindingCount = 0; layoutInfo.pBindings = nullptr; if (vkCreateDescriptorSetLayout(vkRenderer->m_logicalDevice, &layoutInfo, nullptr, &descriptorSetLayout[descriptorSetLayoutCount]) != VK_SUCCESS) vkRenderer->UnrecoverableError(fmt::format("Failed to create placeholder descriptor set layout for shader {0:#x}", geometryShader->baseHash).c_str()); descriptorSetLayoutCount++; } if (geometryShader) { cemu_assert_debug(descriptorSetLayoutCount == 2); CreateDescriptorSetLayout(vkRenderer, geometryShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); vkObjPipeline->m_geometryDSL = descriptorSetLayout[descriptorSetLayoutCount]; descriptorSetLayoutCount++; } } void PipelineCompiler::InitDepthStencilState() { // get depth control parameters bool depthEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_ENABLE(); auto depthFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_FUNC(); bool depthWriteEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_WRITE_ENABLE(); // setup VkPipelineDepthStencilStateCreateInfo depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencilState.depthTestEnable = depthEnable ? VK_TRUE : VK_FALSE; depthStencilState.depthWriteEnable = depthWriteEnable ? VK_TRUE : VK_FALSE; static const VkCompareOp vkDepthCompareTable[8] = { VK_COMPARE_OP_NEVER, VK_COMPARE_OP_LESS, VK_COMPARE_OP_EQUAL, VK_COMPARE_OP_LESS_OR_EQUAL, VK_COMPARE_OP_GREATER, VK_COMPARE_OP_NOT_EQUAL, VK_COMPARE_OP_GREATER_OR_EQUAL, VK_COMPARE_OP_ALWAYS }; depthStencilState.depthCompareOp = vkDepthCompareTable[(size_t)depthFunc]; depthStencilState.depthBoundsTestEnable = false; // todo depthStencilState.minDepthBounds = 0.0f; depthStencilState.maxDepthBounds = 1.0f; // get stencil control parameters bool stencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ENABLE(); bool backStencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_BACK_STENCIL_ENABLE(); auto frontStencilFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FUNC_F(); auto frontStencilZPass = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZPASS_F(); auto frontStencilZFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZFAIL_F(); auto frontStencilFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FAIL_F(); auto backStencilFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FUNC_B(); auto backStencilZPass = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZPASS_B(); auto backStencilZFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZFAIL_B(); auto backStencilFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FAIL_B(); // get stencil control parameters uint32 stencilCompareMaskFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILMASK_F(); uint32 stencilWriteMaskFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILWRITEMASK_F(); uint32 stencilRefFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILREF_F(); uint32 stencilCompareMaskBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILMASK_B(); uint32 stencilWriteMaskBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILWRITEMASK_B(); uint32 stencilRefBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILREF_B(); static const VkStencilOp stencilOpTable[8] = { VK_STENCIL_OP_KEEP, VK_STENCIL_OP_ZERO, VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_INCREMENT_AND_CLAMP, VK_STENCIL_OP_DECREMENT_AND_CLAMP, VK_STENCIL_OP_INVERT, VK_STENCIL_OP_INCREMENT_AND_WRAP, VK_STENCIL_OP_DECREMENT_AND_WRAP }; depthStencilState.stencilTestEnable = stencilEnable ? VK_TRUE : VK_FALSE; depthStencilState.front.reference = stencilRefFront; depthStencilState.front.compareMask = stencilCompareMaskFront; depthStencilState.front.writeMask = stencilWriteMaskFront; depthStencilState.front.compareOp = vkDepthCompareTable[(size_t)frontStencilFunc]; depthStencilState.front.depthFailOp = stencilOpTable[(size_t)frontStencilZFail]; depthStencilState.front.failOp = stencilOpTable[(size_t)frontStencilFail]; depthStencilState.front.passOp = stencilOpTable[(size_t)frontStencilZPass]; if (backStencilEnable) { depthStencilState.back.reference = stencilRefBack; depthStencilState.back.compareMask = stencilCompareMaskBack; depthStencilState.back.writeMask = stencilWriteMaskBack; depthStencilState.back.compareOp = vkDepthCompareTable[(size_t)backStencilFunc]; depthStencilState.back.depthFailOp = stencilOpTable[(size_t)backStencilZFail]; depthStencilState.back.failOp = stencilOpTable[(size_t)backStencilFail]; depthStencilState.back.passOp = stencilOpTable[(size_t)backStencilZPass]; } else { depthStencilState.back.reference = stencilRefFront; depthStencilState.back.compareMask = stencilCompareMaskFront; depthStencilState.back.writeMask = stencilWriteMaskFront; depthStencilState.back.compareOp = vkDepthCompareTable[(size_t)frontStencilFunc]; depthStencilState.back.depthFailOp = stencilOpTable[(size_t)frontStencilZFail]; depthStencilState.back.failOp = stencilOpTable[(size_t)frontStencilFail]; depthStencilState.back.passOp = stencilOpTable[(size_t)frontStencilZPass]; } } void PipelineCompiler::InitDynamicState(PipelineInfo* pipelineInfo, bool usesBlendConstants, bool usesDepthBias) { if (usesBlendConstants) { dynamicStates.emplace_back(VK_DYNAMIC_STATE_BLEND_CONSTANTS); pipelineInfo->usesBlendConstants = true; } if (usesDepthBias) { dynamicStates.emplace_back(VK_DYNAMIC_STATE_DEPTH_BIAS); pipelineInfo->usesDepthBias = true; } dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.dynamicStateCount = dynamicStates.size(); dynamicState.pDynamicStates = dynamicStates.data(); } bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj, bool requireRobustBufferAccess) { VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance(); // ########################################################################################################################################## bool isPrimitiveRect = false; const auto primitiveMode = latteRegister.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); isPrimitiveRect = (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS); m_fetchShader = pipelineInfo->fetchShader; m_vkVertexShader = pipelineInfo->vertexShaderVk; m_vkPixelShader = pipelineInfo->pixelShaderVk; m_vkGeometryShader = pipelineInfo->geometryShaderVk; m_vkrObjPipeline = pipelineInfo->m_vkrObjPipeline; m_renderPassObj = renderPassObj; m_requestRobustBufferAccess = requireRobustBufferAccess; // if required generate RECT emulation geometry shader if (!vkRenderer->m_featureControl.deviceExtensions.nv_fill_rectangle && isPrimitiveRect) { cemu_assert(m_vkGeometryShader == nullptr); // todo - handle cases where the game already provides a GS m_rectEmulationGS = rectsEmulationGS_generate(pipelineInfo->vertexShader, latteRegister); pipelineInfo->rectEmulationGS = m_rectEmulationGS; } // ########################################################################################################################################## pipelineInfo->primitiveMode = primitiveMode; InitVertexInputState(latteRegister, pipelineInfo->vertexShader, pipelineInfo->fetchShader); InitInputAssemblyState(primitiveMode); InitViewportState(); bool usesDepthBias = false; InitRasterizerState(latteRegister, vkRenderer, isPrimitiveRect, usesDepthBias); bool usesBlendConstants = false; InitBlendState(latteRegister, pipelineInfo, usesBlendConstants, renderPassObj); InitDescriptorSetLayouts(vkRenderer, pipelineInfo, pipelineInfo->vertexShader, pipelineInfo->pixelShader, pipelineInfo->geometryShader); // ########################################################################################################################################## VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = descriptorSetLayoutCount; pipelineLayoutInfo.pSetLayouts = descriptorSetLayout; pipelineLayoutInfo.pPushConstantRanges = nullptr; pipelineLayoutInfo.pushConstantRangeCount = 0; VkResult result = vkCreatePipelineLayout(vkRenderer->m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipelineLayout); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create pipeline layout: {}", result); return false; } // ################################################### InitDepthStencilState(); // ########################################################################################################################################## InitDynamicState(pipelineInfo, usesBlendConstants, usesDepthBias); // ########################################################################################################################################## pipelineInfo->m_vkrObjPipeline->m_pipelineLayout = m_pipelineLayout; // increment ref counter for vkrObjPipeline and renderpass object to make sure they dont get released while we are using them m_vkrObjPipeline->incRef(); m_renderPassObj->incRef(); return true; } bool PipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool showInOverlay) { VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance(); if (!vkRenderer->m_featureControl.deviceExtensions.pipeline_creation_cache_control) forceCompile = true; // if VK_EXT_pipeline_creation_cache_control is not supported we always force synchronous compilation if (!forceCompile) { // fail early if some shader stages are not compiled if (m_vkVertexShader && m_vkVertexShader->IsCompiled() == false) return false; if (m_vkPixelShader && m_vkPixelShader->IsCompiled() == false) return false; if (m_vkGeometryShader && m_vkGeometryShader->IsCompiled() == false) return false; } else { // if some shader stages are not compiled yet, compile them now if (m_vkVertexShader && m_vkVertexShader->IsCompiled() == false) m_vkVertexShader->PreponeCompilation(isRenderThread); if (m_vkPixelShader && m_vkPixelShader->IsCompiled() == false) m_vkPixelShader->PreponeCompilation(isRenderThread); if (m_vkGeometryShader && m_vkGeometryShader->IsCompiled() == false) m_vkGeometryShader->PreponeCompilation(isRenderThread); } if (shaderStages.empty()) { if (!InitShaderStages(vkRenderer, m_vkVertexShader, m_vkPixelShader, m_vkGeometryShader)) return true; // invalid shaders, cannot compile } VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = shaderStages.size(); pipelineInfo.pStages = shaderStages.data(); pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pInputAssemblyState = &inputAssembly; pipelineInfo.pViewportState = &viewportState; pipelineInfo.pDynamicState = &dynamicState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; pipelineInfo.layout = m_pipelineLayout; pipelineInfo.renderPass = m_renderPassObj->m_renderPass; pipelineInfo.pDepthStencilState = &depthStencilState; pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = nullptr; pipelineInfo.flags = 0; if (!forceCompile) pipelineInfo.flags |= VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT; void* prevStruct = nullptr; VkPipelineCreationFeedbackCreateInfoEXT creationFeedbackInfo; VkPipelineCreationFeedbackEXT creationFeedback; boost::container::static_vector<VkPipelineCreationFeedbackEXT, 3> creationStageFeedback; if (vkRenderer->m_featureControl.deviceExtensions.pipeline_feedback) { creationFeedback = {}; creationFeedback.flags = VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT_EXT; for (uint32_t i = 0; i < pipelineInfo.stageCount; ++i) creationStageFeedback.push_back({VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT_EXT, 0}); creationFeedbackInfo = {}; creationFeedbackInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATION_FEEDBACK_CREATE_INFO_EXT; creationFeedbackInfo.pPipelineCreationFeedback = &creationFeedback; creationFeedbackInfo.pPipelineStageCreationFeedbacks = creationStageFeedback.data(); creationFeedbackInfo.pipelineStageCreationFeedbackCount = pipelineInfo.stageCount; creationFeedbackInfo.pNext = prevStruct; prevStruct = &creationFeedbackInfo; } VkPipelineRobustnessCreateInfoEXT pipelineRobustnessCreateInfo{}; if (vkRenderer->m_featureControl.deviceExtensions.pipeline_robustness && m_requestRobustBufferAccess) { // per-pipeline handling of robust buffer access, if the extension is not available then we fall back to device feature robustBufferAccess pipelineRobustnessCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_ROBUSTNESS_CREATE_INFO_EXT; pipelineRobustnessCreateInfo.pNext = prevStruct; prevStruct = &pipelineRobustnessCreateInfo; pipelineRobustnessCreateInfo.storageBuffers = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_EXT; pipelineRobustnessCreateInfo.uniformBuffers = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_EXT; pipelineRobustnessCreateInfo.vertexInputs = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT; pipelineRobustnessCreateInfo.images = VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_DEVICE_DEFAULT_EXT; } pipelineInfo.pNext = prevStruct; VkPipeline pipeline = VK_NULL_HANDLE; VkResult result; uint8 retryCount = 0; while (retryCount < 3) { std::shared_lock lock(vkRenderer->m_pipeline_cache_save_mutex); result = vkCreateGraphicsPipelines(vkRenderer->m_logicalDevice, vkRenderer->m_pipeline_cache, 1, &pipelineInfo, nullptr, &pipeline); lock.unlock(); if (result != VK_ERROR_OUT_OF_DEVICE_MEMORY) break; retryCount++; } if (result == VK_ERROR_PIPELINE_COMPILE_REQUIRED_EXT) { return false; } else if (result == VK_SUCCESS) { m_vkrObjPipeline->SetPipeline(pipeline); } else { cemuLog_log(LogType::Force, "Failed to create graphics pipeline. Error {}", (sint32)result); cemu_assert_debug(false); return true; // true indicates that caller should no longer attempt to compile this pipeline again } vkRenderer->m_pipeline_cache_semaphore.notify(); if (vkRenderer->m_featureControl.deviceExtensions.pipeline_feedback) { if (HAS_FLAG(creationFeedback.flags, VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT_EXT)) { bool hasCacheHit = HAS_FLAG(creationFeedback.flags, VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT_EXT); if (!hasCacheHit) { if (showInOverlay) { if (isRenderThread) g_compiling_pipelines_syncTimeSum += creationFeedback.duration; else g_compiling_pipelines_async++; g_compiling_pipelines++; } } } } return true; } void PipelineCompiler::TrackAsCached(uint64 baseHash, uint64 pipelineStateHash) { auto& pipelineCache = VulkanPipelineStableCache::GetInstance(); if (pipelineCache.HasPipelineCached(baseHash, pipelineStateHash)) return; pipelineCache.AddCurrentStateToCache(baseHash, pipelineStateHash); } // calculate whether the pipeline requires robust buffer access // if there is a potential risk for a shader to do out-of-bounds reads or writes we need to enable robust buffer access // this can happen when: // - Streamout is used with too small of a buffer (probably? Could also be some issue with how the streamout array index is calculated -> We can maybe fix this in the future) // - The shader uses dynamic indices for uniform access. This will trigger the uniform mode to be FULL_CBANK bool PipelineCompiler::CalcRobustBufferAccessRequirement(LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader) { bool requiresRobustBufferAcces = false; if (vertexShader) { cemu_assert_debug(vertexShader->shaderType == LatteConst::ShaderType::Vertex); requiresRobustBufferAcces |= vertexShader->hasStreamoutBufferWrite; requiresRobustBufferAcces |= vertexShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK; } if (geometryShader) { cemu_assert_debug(geometryShader->shaderType == LatteConst::ShaderType::Geometry); requiresRobustBufferAcces |= geometryShader->hasStreamoutBufferWrite; requiresRobustBufferAcces |= geometryShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK; } if (pixelShader) { cemu_assert_debug(pixelShader->shaderType == LatteConst::ShaderType::Pixel); requiresRobustBufferAcces |= pixelShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK; } return requiresRobustBufferAcces; } static std::vector<std::thread> s_compileThreads; static std::atomic_bool s_compileThreadsShutdownSignal{}; static ConcurrentQueue<PipelineCompiler*> s_pipelineCompileRequests; static void compilePipeline_thread(sint32 threadIndex) { SetThreadName("compilePl"); #ifdef _WIN32 // to avoid starving the main cpu and render threads the pipeline compile threads run at lower priority // except for one thread which we always run at normal priority to prevent the opposite scenario where all compile threads are starved if(threadIndex != 0) SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); #endif while (!s_compileThreadsShutdownSignal) { PipelineCompiler* request = s_pipelineCompileRequests.pop(); if (!request) continue; request->Compile(true, false, true); delete request; } } void PipelineCompiler::CompileThreadPool_Start() { cemu_assert_debug(s_compileThreads.empty()); s_compileThreadsShutdownSignal = false; uint32 numCompileThreads; uint32 cpuCoreCount = GetPhysicalCoreCount(); if (cpuCoreCount <= 2) numCompileThreads = 1; else numCompileThreads = 2 + (cpuCoreCount - 3); // 2 plus one additionally for every extra core above 3 numCompileThreads = std::min(numCompileThreads, 8u); // cap at 8 for (uint32_t i = 0; i < numCompileThreads; i++) { s_compileThreads.emplace_back(compilePipeline_thread, i); } } void PipelineCompiler::CompileThreadPool_Stop() { s_compileThreadsShutdownSignal = true; { // push one empty workload for each thread // this way we can make sure that each waiting thread is woken up to see the shutdown signal for (auto& thread : s_compileThreads) s_pipelineCompileRequests.push(nullptr); } for (auto& thread : s_compileThreads) thread.join(); while (!s_pipelineCompileRequests.empty()) { PipelineCompiler* pipelineCompiler = s_pipelineCompileRequests.pop(); if (pipelineCompiler) delete pipelineCompiler; } s_compileThreads.clear(); } void PipelineCompiler::CompileThreadPool_QueueCompilation(PipelineCompiler* v) { s_pipelineCompileRequests.push(v); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "VKRBase.h" class PipelineCompiler : public VKRMoveableRefCounter { private: // helper functions VkFormat GetVertexFormat(uint8 format); bool ConsumesBlendConstants(VkBlendFactor blendFactor); void CreateDescriptorSetLayout(VulkanRenderer* vkRenderer, LatteDecompilerShader* shader, VkDescriptorSetLayout& layout, PipelineInfo* vkrPipelineInfo); private: /* shader stages (requires compiled shader) */ RendererShaderVk* m_rectEmulationGS{}; bool InitShaderStages(VulkanRenderer* vkRenderer, RendererShaderVk* vkVertexShader, RendererShaderVk* vkPixelShader, RendererShaderVk* vkGeometryShader); /* vertex input state */ void InitVertexInputState(const LatteContextRegister& latteRegister, LatteDecompilerShader* vertexShader, LatteFetchShader* fetchShader); void InitInputAssemblyState(const Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE primitiveMode); void InitViewportState(); void InitRasterizerState(const LatteContextRegister& latteRegister, VulkanRenderer* vkRenderer, bool isPrimitiveRect, bool& usesDepthBias); void InitBlendState(const LatteContextRegister& latteRegister, PipelineInfo* pipelineInfo, bool& usesBlendConstants, VKRObjectRenderPass* renderPassObj); void InitDescriptorSetLayouts(VulkanRenderer* vkRenderer, PipelineInfo* vkrPipelineInfo, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader); void InitDepthStencilState(); void InitDynamicState(PipelineInfo* pipelineInfo, bool usesBlendConstants, bool usesDepthBias); public: PipelineCompiler(); ~PipelineCompiler(); VKRObjectPipeline* m_vkrObjPipeline{}; LatteFetchShader* m_fetchShader{}; RendererShaderVk* m_vkVertexShader{}; RendererShaderVk* m_vkPixelShader{}; RendererShaderVk* m_vkGeometryShader{}; bool InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj, bool requireRobustBufferAccess); void TrackAsCached(uint64 baseHash, uint64 pipelineStateHash); // stores pipeline to permanent cache if not yet cached. Must be called synchronously from render thread due to dependency on GPU state static bool CalcRobustBufferAccessRequirement(LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader); // API for thread pool static void CompileThreadPool_Start(); static void CompileThreadPool_Stop(); static void CompileThreadPool_QueueCompilation(PipelineCompiler* v); VkPipelineLayout m_pipelineLayout; VKRObjectRenderPass* m_renderPassObj{}; bool m_requestRobustBufferAccess{false}; /* shader stages */ std::vector<VkPipelineShaderStageCreateInfo> shaderStages; /* vertex attribute description */ std::vector<VkVertexInputAttributeDescription> vertexInputAttributeDescription; std::vector<VkVertexInputBindingDescription> vertexInputBindingDescription; VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; /* input assembly state */ VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; /* viewport state */ VkPipelineViewportStateCreateInfo viewportState{}; /* rasterizer state */ VkPipelineRasterizationStateCreateInfo rasterizer{}; VkPipelineRasterizationDepthClipStateCreateInfoEXT rasterizerExt{}; VkPipelineMultisampleStateCreateInfo multisampling{}; /* blend state */ std::array<VkPipelineColorBlendAttachmentState, 8> colorBlendAttachments{}; VkPipelineColorBlendStateCreateInfo colorBlending{}; /* descriptor set layouts */ VkDescriptorSetLayout descriptorSetLayout[3]; sint32 descriptorSetLayoutCount = 0; /* depth stencil state */ VkPipelineDepthStencilStateCreateInfo depthStencilState{}; /* dynamic state */ std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicState = {}; // returns true if the shader was compiled (even if errors occurred) bool Compile(bool forceCompile, bool isRenderThread, bool showInOverlay); }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteCachedFBO.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "config/ActiveSettings.h" #include "util/helpers/Serializer.h" #include "Cafe/HW/Latte/Common/RegisterSerializer.h" #include "Cemu/FileCache/FileCache.h" #include "Cafe/HW/Latte/Core/LatteShaderCache.h" #include "util/helpers/helpers.h" #include <openssl/sha.h> struct { uint32 pipelineLoadIndex; uint32 pipelineMaxFileIndex; std::atomic_uint32_t pipelinesQueued; std::atomic_uint32_t pipelinesLoaded; }g_vkCacheState; VulkanPipelineStableCache g_vkPipelineStableCacheInstance; VulkanPipelineStableCache& VulkanPipelineStableCache::GetInstance() { return g_vkPipelineStableCacheInstance; } uint32 VulkanPipelineStableCache::BeginLoading(uint64 cacheTitleId) { std::error_code ec; fs::create_directories(ActiveSettings::GetCachePath("shaderCache/transferable"), ec); const auto pathCacheFile = ActiveSettings::GetCachePath("shaderCache/transferable/{:016x}_vkpipeline.bin", cacheTitleId); // init cache loader state g_vkCacheState.pipelineLoadIndex = 0; g_vkCacheState.pipelineMaxFileIndex = 0; g_vkCacheState.pipelinesLoaded = 0; g_vkCacheState.pipelinesQueued = 0; // start async compilation threads m_compilationCount.store(0); m_compilationQueue.clear(); // get core count uint32 cpuCoreCount = GetPhysicalCoreCount(); m_numCompilationThreads = std::clamp(cpuCoreCount, 1u, 8u); if (VulkanRenderer::GetInstance()->GetDisableMultithreadedCompilation()) m_numCompilationThreads = 1; for (uint32 i = 0; i < m_numCompilationThreads; i++) { std::thread compileThread(&VulkanPipelineStableCache::CompilerThread, this); compileThread.detach(); } // open cache file or create it cemu_assert_debug(s_cache == nullptr); s_cache = FileCache::Open(pathCacheFile, true, LatteShaderCache_getPipelineCacheExtraVersion(cacheTitleId)); if (!s_cache) { cemuLog_log(LogType::Force, "Failed to open or create Vulkan pipeline cache file: {}", _pathToUtf8(pathCacheFile)); return 0; } else { s_cache->UseCompression(false); g_vkCacheState.pipelineMaxFileIndex = s_cache->GetMaximumFileIndex(); } return s_cache->GetFileCount(); } bool VulkanPipelineStableCache::UpdateLoading(uint32& pipelinesLoadedTotal, uint32& pipelinesMissingShaders) { pipelinesLoadedTotal = g_vkCacheState.pipelinesLoaded; pipelinesMissingShaders = 0; while (g_vkCacheState.pipelineLoadIndex <= g_vkCacheState.pipelineMaxFileIndex) { if (m_compilationQueue.size() >= 50) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); return true; // queue up to 50 entries at a time } uint64 fileNameA, fileNameB; std::vector<uint8> fileData; if (s_cache->GetFileByIndex(g_vkCacheState.pipelineLoadIndex, &fileNameA, &fileNameB, fileData)) { // queue for async compilation g_vkCacheState.pipelinesQueued++; m_compilationQueue.push(std::move(fileData)); g_vkCacheState.pipelineLoadIndex++; return true; } g_vkCacheState.pipelineLoadIndex++; } if (g_vkCacheState.pipelinesLoaded != g_vkCacheState.pipelinesQueued) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); return true; // pipelines still compiling } return false; // done } void VulkanPipelineStableCache::EndLoading() { // shut down compilation threads uint32 threadCount = m_numCompilationThreads; m_numCompilationThreads = 0; // signal thread shutdown for (uint32 i = 0; i < threadCount; i++) { m_compilationQueue.push({}); // push empty workload for every thread. Threads then will shutdown after checking for m_numCompilationThreads == 0 } // keep cache file open for writing of new pipelines } void VulkanPipelineStableCache::Close() { if(s_cache) { delete s_cache; s_cache = nullptr; } } struct CachedPipeline { struct ShaderHash { uint64 baseHash; uint64 auxHash; bool isPresent{}; void set(uint64 baseHash, uint64 auxHash) { this->baseHash = baseHash; this->auxHash = auxHash; this->isPresent = true; } }; ShaderHash vsHash; // includes fetch shader ShaderHash gsHash; ShaderHash psHash; Latte::GPUCompactedRegisterState gpuState; }; VkFormat __getColorBufferVkFormat(const uint32 index, const LatteContextRegister& lcr) { Latte::E_GX2SURFFMT colorBufferFormat = LatteMRT::GetColorBufferFormat(index, lcr); VulkanRenderer::FormatInfoVK texFormatInfo; VulkanRenderer::GetInstance()->GetTextureFormatInfoVK(colorBufferFormat, false, Latte::E_DIM::DIM_2D, 1280, 720, &texFormatInfo); return texFormatInfo.vkImageFormat; } void __getDepthBufferVkFormat(const LatteContextRegister& lcr, VkFormat& dbFormat, bool& hasStencil) { Latte::E_GX2SURFFMT format = LatteMRT::GetDepthBufferFormat(lcr); VulkanRenderer::FormatInfoVK texFormatInfo; VulkanRenderer::GetInstance()->GetTextureFormatInfoVK(format, true, Latte::E_DIM::DIM_2D, 1280, 720, &texFormatInfo); dbFormat = texFormatInfo.vkImageFormat; hasStencil = (texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; } // create placeholder renderpass for cached pipeline VKRObjectRenderPass* __CreateTemporaryRenderPass(const LatteDecompilerShader* pixelShader, const LatteContextRegister& lcr) { VKRObjectRenderPass::AttachmentInfo_t attachmentInfo; uint8 cbMask = LatteMRT::GetActiveColorBufferMask(pixelShader, lcr); bool dbMask = LatteMRT::GetActiveDepthBufferMask(lcr); for (int i = 0; i < 8; ++i) { if ((cbMask & (1 << i)) == 0) { attachmentInfo.colorAttachment[i].viewObj = nullptr; continue; } // setup color attachment attachmentInfo.colorAttachment[i].viewObj = nullptr; attachmentInfo.colorAttachment[i].isPresent = true; attachmentInfo.colorAttachment[i].format = __getColorBufferVkFormat(i, lcr); } // setup depth attachment if (dbMask) { attachmentInfo.depthAttachment.viewObj = nullptr; attachmentInfo.depthAttachment.isPresent = true; VkFormat dbFormat; bool hasStencil; __getDepthBufferVkFormat(lcr, dbFormat, hasStencil); attachmentInfo.depthAttachment.format = dbFormat; attachmentInfo.depthAttachment.hasStencil = hasStencil; } else { // no depth attachment attachmentInfo.depthAttachment.viewObj = nullptr; attachmentInfo.depthAttachment.isPresent = false; } return new VKRObjectRenderPass(attachmentInfo); } void VulkanPipelineStableCache::LoadPipelineFromCache(std::span<uint8> fileData) { static FSpinlock s_spinlockSharedInternal; // deserialize file LatteContextRegister* lcr = new LatteContextRegister(); s_spinlockSharedInternal.lock(); CachedPipeline* cachedPipeline = new CachedPipeline(); s_spinlockSharedInternal.unlock(); MemStreamReader streamReader(fileData.data(), fileData.size()); if (!DeserializePipeline(streamReader, *cachedPipeline)) { // failed to deserialize s_spinlockSharedInternal.lock(); delete lcr; delete cachedPipeline; s_spinlockSharedInternal.unlock(); return; } // restored register view from compacted state Latte::LoadGPURegisterState(*lcr, cachedPipeline->gpuState); LatteDecompilerShader* vertexShader = nullptr; LatteDecompilerShader* geometryShader = nullptr; LatteDecompilerShader* pixelShader = nullptr; // find vertex shader if (cachedPipeline->vsHash.isPresent) { vertexShader = LatteSHRC_FindVertexShader(cachedPipeline->vsHash.baseHash, cachedPipeline->vsHash.auxHash); if (!vertexShader) { cemuLog_logDebug(LogType::Force, "Vertex shader not found in cache"); return; } } // find geometry shader if (cachedPipeline->gsHash.isPresent) { geometryShader = LatteSHRC_FindGeometryShader(cachedPipeline->gsHash.baseHash, cachedPipeline->gsHash.auxHash); if (!geometryShader) { cemuLog_logDebug(LogType::Force, "Geometry shader not found in cache"); return; } } // find pixel shader if (cachedPipeline->psHash.isPresent) { pixelShader = LatteSHRC_FindPixelShader(cachedPipeline->psHash.baseHash, cachedPipeline->psHash.auxHash); if (!pixelShader) { cemuLog_logDebug(LogType::Force, "Pixel shader not found in cache"); return; } } // create temporary renderpass if (!pixelShader) { cemu_assert_debug(false); return; } auto renderPass = __CreateTemporaryRenderPass(pixelShader, *lcr); // create pipeline info m_pipelineIsCachedLock.lock(); PipelineInfo* pipelineInfo = new PipelineInfo(0, 0, vertexShader->compatibleFetchShader, vertexShader, pixelShader, geometryShader); m_pipelineIsCachedLock.unlock(); // compile { PipelineCompiler pipelineCompiler; bool requiresRobustBufferAccess = PipelineCompiler::CalcRobustBufferAccessRequirement(vertexShader, pixelShader, geometryShader); if (!pipelineCompiler.InitFromCurrentGPUState(pipelineInfo, *lcr, renderPass, requiresRobustBufferAccess)) { s_spinlockSharedInternal.lock(); delete lcr; delete cachedPipeline; s_spinlockSharedInternal.unlock(); return; } pipelineCompiler.Compile(true, true, false); } // on success, calculate pipeline hash and flag as present in cache uint64 pipelineBaseHash = vertexShader->baseHash; uint64 pipelineStateHash = VulkanRenderer::draw_calculateGraphicsPipelineHash(vertexShader->compatibleFetchShader, vertexShader, geometryShader, pixelShader, renderPass, *lcr); m_pipelineIsCachedLock.lock(); m_pipelineIsCached.emplace(pipelineBaseHash, pipelineStateHash); m_pipelineIsCachedLock.unlock(); // clean up s_spinlockSharedInternal.lock(); delete pipelineInfo; delete lcr; delete cachedPipeline; VulkanRenderer::GetInstance()->ReleaseDestructibleObject(renderPass); s_spinlockSharedInternal.unlock(); } bool VulkanPipelineStableCache::HasPipelineCached(uint64 baseHash, uint64 pipelineStateHash) { PipelineHash ph(baseHash, pipelineStateHash); return m_pipelineIsCached.find(ph) != m_pipelineIsCached.end(); } ConcurrentQueue<CachedPipeline*> g_pipelineCachingQueue; void VulkanPipelineStableCache::AddCurrentStateToCache(uint64 baseHash, uint64 pipelineStateHash) { m_pipelineIsCached.emplace(baseHash, pipelineStateHash); if (!m_pipelineCacheStoreThread) { m_pipelineCacheStoreThread = new std::thread(&VulkanPipelineStableCache::WorkerThread, this); m_pipelineCacheStoreThread->detach(); } // fill job structure with cached GPU state // for each cached pipeline we store: // - Active shaders (referenced by hash) // - An almost-complete register state of the GPU (minus some ALU uniform constants which aren't relevant) CachedPipeline* job = new CachedPipeline(); auto vs = LatteSHRC_GetActiveVertexShader(); auto gs = LatteSHRC_GetActiveGeometryShader(); auto ps = LatteSHRC_GetActivePixelShader(); if (vs) job->vsHash.set(vs->baseHash, vs->auxHash); if (gs) job->gsHash.set(gs->baseHash, gs->auxHash); if (ps) job->psHash.set(ps->baseHash, ps->auxHash); Latte::StoreGPURegisterState(LatteGPUState.contextNew, job->gpuState); // queue job g_pipelineCachingQueue.push(job); } bool VulkanPipelineStableCache::SerializePipeline(MemStreamWriter& memWriter, CachedPipeline& cachedPipeline) { memWriter.writeBE<uint8>(0x01); // version uint8 presentMask = 0; if (cachedPipeline.vsHash.isPresent) presentMask |= 1; if (cachedPipeline.gsHash.isPresent) presentMask |= 2; if (cachedPipeline.psHash.isPresent) presentMask |= 4; memWriter.writeBE<uint8>(presentMask); if (cachedPipeline.vsHash.isPresent) { memWriter.writeBE<uint64>(cachedPipeline.vsHash.baseHash); memWriter.writeBE<uint64>(cachedPipeline.vsHash.auxHash); } if (cachedPipeline.gsHash.isPresent) { memWriter.writeBE<uint64>(cachedPipeline.gsHash.baseHash); memWriter.writeBE<uint64>(cachedPipeline.gsHash.auxHash); } if (cachedPipeline.psHash.isPresent) { memWriter.writeBE<uint64>(cachedPipeline.psHash.baseHash); memWriter.writeBE<uint64>(cachedPipeline.psHash.auxHash); } Latte::SerializeRegisterState(cachedPipeline.gpuState, memWriter); return true; } bool VulkanPipelineStableCache::DeserializePipeline(MemStreamReader& memReader, CachedPipeline& cachedPipeline) { // version if (memReader.readBE<uint8>() != 1) { cemuLog_log(LogType::Force, "Cached Vulkan pipeline corrupted or has unknown version"); return false; } // shader hashes uint8 presentMask = memReader.readBE<uint8>(); if (presentMask & 1) { uint64 baseHash = memReader.readBE<uint64>(); uint64 auxHash = memReader.readBE<uint64>(); cachedPipeline.vsHash.set(baseHash, auxHash); } if (presentMask & 2) { uint64 baseHash = memReader.readBE<uint64>(); uint64 auxHash = memReader.readBE<uint64>(); cachedPipeline.gsHash.set(baseHash, auxHash); } if (presentMask & 4) { uint64 baseHash = memReader.readBE<uint64>(); uint64 auxHash = memReader.readBE<uint64>(); cachedPipeline.psHash.set(baseHash, auxHash); } // deserialize GPU state if (!Latte::DeserializeRegisterState(cachedPipeline.gpuState, memReader)) { return false; } cemu_assert_debug(!memReader.hasError()); return true; } int VulkanPipelineStableCache::CompilerThread() { SetThreadName("plCacheCompiler"); while (m_numCompilationThreads != 0) { std::vector<uint8> pipelineData = m_compilationQueue.pop(); if(pipelineData.empty()) continue; LoadPipelineFromCache(pipelineData); ++g_vkCacheState.pipelinesLoaded; } return 0; } void VulkanPipelineStableCache::WorkerThread() { SetThreadName("plCacheWriter"); while (true) { CachedPipeline* job; g_pipelineCachingQueue.pop(job); if (!s_cache) { delete job; continue; } // serialize MemStreamWriter memWriter(1024 * 4); SerializePipeline(memWriter, *job); auto blob = memWriter.getResult(); // file name is derived from data hash uint8 hash[SHA256_DIGEST_LENGTH]; SHA256(blob.data(), blob.size(), hash); uint64 nameA = *(uint64be*)(hash + 0); uint64 nameB = *(uint64be*)(hash + 8); s_cache->AddFileAsync({ nameA, nameB }, blob.data(), blob.size()); delete job; } } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h ================================================ #pragma once #include "util/helpers/fspinlock.h" struct VulkanPipelineHash { VulkanPipelineHash() = default; VulkanPipelineHash(uint64 h0, uint64 h1) : h0(h0), h1(h1) {}; uint64 h0; uint64 h1; }; class VulkanPipelineStableCache { struct PipelineHash { PipelineHash(uint64 h0, uint64 h1) : h0(h0), h1(h1) {}; uint64 h0; uint64 h1; bool operator==(const PipelineHash& r) const { return h0 == r.h0 && h1 == r.h1; } struct HashFunc { size_t operator()(const PipelineHash& v) const { static_assert(sizeof(uint64) == sizeof(size_t)); return v.h0 ^ v.h1; } }; }; public: static VulkanPipelineStableCache& GetInstance(); uint32 BeginLoading(uint64 cacheTitleId); // returns count of pipelines stored in cache bool UpdateLoading(uint32& pipelinesLoadedTotal, uint32& pipelinesMissingShaders); void EndLoading(); void LoadPipelineFromCache(std::span<uint8> fileData); void Close(); // called on title exit bool HasPipelineCached(uint64 baseHash, uint64 pipelineStateHash); void AddCurrentStateToCache(uint64 baseHash, uint64 pipelineStateHash); // pipeline serialization for file bool SerializePipeline(class MemStreamWriter& memWriter, struct CachedPipeline& cachedPipeline); bool DeserializePipeline(class MemStreamReader& memReader, struct CachedPipeline& cachedPipeline); private: int CompilerThread(); void WorkerThread(); std::thread* m_pipelineCacheStoreThread; std::unordered_set<PipelineHash, PipelineHash::HashFunc> m_pipelineIsCached; FSpinlock m_pipelineIsCachedLock; class FileCache* s_cache; std::atomic_uint32_t m_numCompilationThreads{ 0 }; ConcurrentQueue<std::vector<uint8>> m_compilationQueue; std::atomic_uint32_t m_compilationCount; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanQuery.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" class LatteQueryObjectVk : public LatteQueryObject { friend class VulkanRenderer; LatteQueryObjectVk(VulkanRenderer* rendererVk) : m_rendererVk(rendererVk) { }; bool getResult(uint64& numSamplesPassed) override; void begin() override; void end() override; void beginFragment(); void endFragment(); void handleFinishedFragments(); uint32 acquireQueryIndex(); void releaseQueryIndex(uint32 queryIndex); private: struct queryFragment { uint32 queryIndex; uint64 m_finishCommandBuffer; bool isFinished; }; VulkanRenderer* m_rendererVk; //sint32 m_queryIndex; std::vector<queryFragment> list_queryFragments; bool m_vkQueryEnded{}; bool m_hasActiveQuery{}; bool m_hasActiveFragment{}; uint64 m_finishCommandBuffer; uint64 m_acccumulatedSum; }; bool LatteQueryObjectVk::getResult(uint64& numSamplesPassed) { if (!m_vkQueryEnded) return false; if (!m_rendererVk->HasCommandBufferFinished(m_finishCommandBuffer)) return false; handleFinishedFragments(); cemu_assert_debug(list_queryFragments.empty()); numSamplesPassed = m_acccumulatedSum; //numSamplesPassed = m_rendererVk->m_occlusionQueries.ptrQueryResults[m_queryIndex]; return true; } void LatteQueryObjectVk::beginFragment() { m_rendererVk->draw_endRenderPass(); handleFinishedFragments(); uint32 newQueryIndex = acquireQueryIndex(); queryFragment qf{}; qf.queryIndex = newQueryIndex; qf.isFinished = false; qf.m_finishCommandBuffer = 0; list_queryFragments.emplace_back(qf); vkCmdResetQueryPool(m_rendererVk->m_state.currentCommandBuffer, m_rendererVk->m_occlusionQueries.queryPool, newQueryIndex, 1); vkCmdBeginQuery(m_rendererVk->m_state.currentCommandBuffer, m_rendererVk->m_occlusionQueries.queryPool, newQueryIndex, VK_QUERY_CONTROL_PRECISE_BIT); // todo - we already synchronize with command buffers, should we also set wait bits? m_hasActiveFragment = true; } void LatteQueryObjectVk::begin() { m_vkQueryEnded = false; m_hasActiveQuery = true; beginFragment(); } void LatteQueryObjectVk::endFragment() { m_rendererVk->draw_endRenderPass(); cemu_assert_debug(m_hasActiveFragment); uint32 queryIndex = list_queryFragments.back().queryIndex; vkCmdEndQuery(m_rendererVk->m_state.currentCommandBuffer, m_rendererVk->m_occlusionQueries.queryPool, queryIndex); vkCmdCopyQueryPoolResults(m_rendererVk->m_state.currentCommandBuffer, m_rendererVk->m_occlusionQueries.queryPool, queryIndex, 1, m_rendererVk->m_occlusionQueries.bufferQueryResults, queryIndex * sizeof(uint64), 8, VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); list_queryFragments.back().m_finishCommandBuffer = m_rendererVk->GetCurrentCommandBufferId(); list_queryFragments.back().isFinished = true; m_hasActiveFragment = false; } void LatteQueryObjectVk::handleFinishedFragments() { // remove finished fragments and add to m_acccumulatedSum while (!list_queryFragments.empty()) { auto& it = list_queryFragments.front(); if (!it.isFinished) break; if (!m_rendererVk->HasCommandBufferFinished(it.m_finishCommandBuffer)) break; m_acccumulatedSum += m_rendererVk->m_occlusionQueries.ptrQueryResults[it.queryIndex]; releaseQueryIndex(it.queryIndex); list_queryFragments.erase(list_queryFragments.begin()); } } uint32 LatteQueryObjectVk::acquireQueryIndex() { if (m_rendererVk->m_occlusionQueries.list_availableQueryIndices.empty()) { cemuLog_log(LogType::Force, "Vulkan-Error: Exhausted query pool"); assert_dbg(); } uint32 queryIndex = m_rendererVk->m_occlusionQueries.list_availableQueryIndices.back(); m_rendererVk->m_occlusionQueries.list_availableQueryIndices.pop_back(); return queryIndex; } void LatteQueryObjectVk::releaseQueryIndex(uint32 queryIndex) { m_rendererVk->m_occlusionQueries.list_availableQueryIndices.emplace_back(queryIndex); } void LatteQueryObjectVk::end() { cemu_assert_debug(!list_queryFragments.empty()); if(m_hasActiveFragment) endFragment(); m_vkQueryEnded = true; m_hasActiveQuery = false; m_finishCommandBuffer = m_rendererVk->GetCurrentCommandBufferId(); m_rendererVk->m_occlusionQueries.m_lastCommandBuffer = m_finishCommandBuffer; m_rendererVk->RequestSubmitSoon(); // make sure the current command buffer gets submitted soon m_rendererVk->RequestSubmitOnIdle(); } LatteQueryObject* VulkanRenderer::occlusionQuery_create() { // create query pool if it doesn't already exist if(m_occlusionQueries.queryPool == VK_NULL_HANDLE) { VkQueryPoolCreateInfo queryPoolCreateInfo{}; queryPoolCreateInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; queryPoolCreateInfo.flags = 0; queryPoolCreateInfo.queryType = VK_QUERY_TYPE_OCCLUSION; queryPoolCreateInfo.queryCount = OCCLUSION_QUERY_POOL_SIZE; queryPoolCreateInfo.pipelineStatistics = 0; auto r = vkCreateQueryPool(m_logicalDevice, &queryPoolCreateInfo, nullptr, &m_occlusionQueries.queryPool); if (r != VK_SUCCESS) { cemuLog_log(LogType::Force, "Vulkan-Error: Failed to create query pool with error {}", (sint32)r); return nullptr; } } LatteQueryObjectVk* queryObjVk = nullptr; if (m_occlusionQueries.list_cachedQueries.empty()) { queryObjVk = new LatteQueryObjectVk(this); } else { queryObjVk = m_occlusionQueries.list_cachedQueries.front(); m_occlusionQueries.list_cachedQueries.erase(m_occlusionQueries.list_cachedQueries.begin()+0); } queryObjVk->queryEnded = false; queryObjVk->queryEventStart = 0; queryObjVk->queryEventEnd = 0; queryObjVk->m_vkQueryEnded = false; queryObjVk->m_acccumulatedSum = 0; cemu_assert_debug(queryObjVk->list_queryFragments.empty()); // query fragment list should always be cleared in _destroy() m_occlusionQueries.list_currentlyActiveQueries.emplace_back(queryObjVk); return queryObjVk; } void VulkanRenderer::occlusionQuery_destroy(LatteQueryObject* queryObj) { LatteQueryObjectVk* queryObjVk = static_cast<LatteQueryObjectVk*>(queryObj); m_occlusionQueries.list_currentlyActiveQueries.erase(std::remove(m_occlusionQueries.list_currentlyActiveQueries.begin(), m_occlusionQueries.list_currentlyActiveQueries.end(), queryObj), m_occlusionQueries.list_currentlyActiveQueries.end()); m_occlusionQueries.list_cachedQueries.emplace_back(queryObjVk); for (auto& it : queryObjVk->list_queryFragments) queryObjVk->releaseQueryIndex(it.queryIndex); queryObjVk->list_queryFragments.clear(); } void VulkanRenderer::occlusionQuery_flush() { WaitCommandBufferFinished(m_occlusionQueries.m_lastCommandBuffer); } void VulkanRenderer::occlusionQuery_updateState() { // check for finished command buffers here since query states are tied to buffers ProcessFinishedCommandBuffers(); } void VulkanRenderer::occlusionQuery_notifyEndCommandBuffer() { for (auto& it : m_occlusionQueries.list_currentlyActiveQueries) if(it->m_hasActiveQuery) it->endFragment(); } void VulkanRenderer::occlusionQuery_notifyBeginCommandBuffer() { for (auto& it : m_occlusionQueries.list_currentlyActiveQueries) if (it->m_hasActiveQuery) it->beginFragment(); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h" #include "Cafe/HW/Latte/Renderer/Vulkan/CocoaSurface.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/CafeSystem.h" #include "util/helpers/helpers.h" #include "util/helpers/StringHelpers.h" #include "config/ActiveSettings.h" #include "config/CemuConfig.h" #include "WindowSystem.h" #include "imgui/imgui_extension.h" #include "imgui/imgui_impl_vulkan.h" #include "Cafe/TitleList/GameInfo.h" #include "Cafe/HW/Latte/Core/LatteTiming.h" // vsync control #include <cstdint> #include <glslang/Public/ShaderLang.h> #ifndef VK_API_VERSION_MAJOR #define VK_API_VERSION_MAJOR(version) (((uint32_t)(version) >> 22) & 0x7FU) #define VK_API_VERSION_MINOR(version) (((uint32_t)(version) >> 12) & 0x3FFU) #endif extern std::atomic_int g_compiling_pipelines; const std::vector<const char*> kOptionalDeviceExtensions = { VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, VK_NV_FILL_RECTANGLE_EXTENSION_NAME, VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME, VK_EXT_FILTER_CUBIC_EXTENSION_NAME, // not supported by any device yet VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME, VK_KHR_PRESENT_WAIT_EXTENSION_NAME, VK_KHR_PRESENT_ID_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME }; const std::vector<const char*> kRequiredDeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME }; // Intel doesnt support VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { #ifdef CEMU_DEBUG_ASSERT if (strstr(pCallbackData->pMessage, "consumes input location")) return VK_FALSE; // false means we dont care if (strstr(pCallbackData->pMessage, "blend")) return VK_FALSE; // // note: Check if previously used location in VK_EXT_debug_report callback is the same as messageIdNumber under the new extension // validation errors which are difficult to fix if (pCallbackData->messageIdNumber == 0x6c3b517c || pCallbackData->messageIdNumber == 0xffffffffa6b17cdf || pCallbackData->messageIdNumber == 0xffffffffc406fcb7) return VK_FALSE; // its illegal to render to and sample from same texture if (pCallbackData->messageIdNumber == 0x6e633069) return VK_FALSE; // framebuffer attachments should have identity swizzle if (pCallbackData->messageIdNumber == 0xffffffffb408bc0b) return VK_FALSE; // too many samplers if (pCallbackData->messageIdNumber == 0x6bbb14) return VK_FALSE; // SPIR-V inconsistency if (strstr(pCallbackData->pMessage, "Number of currently valid sampler objects is not less than the maximum allowed")) return VK_FALSE; #endif cemuLog_log(LogType::Force, (char*)pCallbackData->pMessage); return VK_FALSE; } std::vector<VulkanRenderer::DeviceInfo> VulkanRenderer::GetDevices() { if(!vkEnumerateInstanceVersion) { cemuLog_log(LogType::Force, "Vulkan cant list devices because Vulkan loader failed"); return {}; } uint32 apiVersion = VK_API_VERSION_1_1; if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) { if (VK_API_VERSION_MAJOR(apiVersion) < 1 || VK_API_VERSION_MINOR(apiVersion) < 2) apiVersion = VK_API_VERSION_1_1; } std::vector<DeviceInfo> result; std::vector<const char*> requiredExtensions; requiredExtensions.clear(); requiredExtensions.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME); #if BOOST_OS_WINDOWS requiredExtensions.emplace_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); #elif BOOST_OS_LINUX || BOOST_OS_BSD auto backend = WindowSystem::GetWindowInfo().window_main.backend; if(backend == WindowSystem::WindowHandleInfo::Backend::X11) requiredExtensions.emplace_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); #ifdef HAS_WAYLAND else if (backend == WindowSystem::WindowHandleInfo::Backend::Wayland) requiredExtensions.emplace_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); #endif #elif BOOST_OS_MACOS requiredExtensions.emplace_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); #endif VkApplicationInfo app_info{}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pApplicationName = EMULATOR_NAME; app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH); app_info.pEngineName = EMULATOR_NAME; app_info.engineVersion = app_info.applicationVersion; app_info.apiVersion = apiVersion; VkInstanceCreateInfo create_info{}; create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; create_info.pApplicationInfo = &app_info; create_info.ppEnabledExtensionNames = requiredExtensions.data(); create_info.enabledExtensionCount = requiredExtensions.size(); create_info.ppEnabledLayerNames = nullptr; create_info.enabledLayerCount = 0; VkInstance instance = nullptr; try { VkResult err; if ((err = vkCreateInstance(&create_info, nullptr, &instance)) != VK_SUCCESS) throw std::runtime_error(fmt::format("Unable to create a Vulkan instance: {}", err)); if (!InitializeInstanceVulkan(instance)) throw std::runtime_error("can't initialize instanced vulkan functions"); uint32_t device_count = 0; vkEnumeratePhysicalDevices(instance, &device_count, nullptr); if (device_count == 0) throw std::runtime_error("Failed to find a GPU with Vulkan support."); // create tmp surface to create a logical device auto surface = CreateFramebufferSurface(instance, WindowSystem::GetWindowInfo().window_main); std::vector<VkPhysicalDevice> devices(device_count); vkEnumeratePhysicalDevices(instance, &device_count, devices.data()); for (const auto& device : devices) { if (IsDeviceSuitable(surface, device)) { VkPhysicalDeviceIDProperties physDeviceIDProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES }; VkPhysicalDeviceProperties2 physDeviceProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; physDeviceProps.pNext = &physDeviceIDProps; vkGetPhysicalDeviceProperties2(device, &physDeviceProps); result.emplace_back(physDeviceProps.properties.deviceName, physDeviceIDProps.deviceUUID); } } vkDestroySurfaceKHR(instance, surface, nullptr); } catch (...) { } if (instance) vkDestroyInstance(instance, nullptr); return result; } void VulkanRenderer::DetermineVendor() { VkPhysicalDeviceProperties2 properties{}; VkPhysicalDeviceDriverProperties driverProperties{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES }; properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; if (m_featureControl.deviceExtensions.driver_properties) properties.pNext = &driverProperties; vkGetPhysicalDeviceProperties2(m_physicalDevice, &properties); switch (properties.properties.vendorID) { case 0x10DE: m_vendor = GfxVendor::Nvidia; break; case 0x8086: // iGPU m_vendor = GfxVendor::Intel; break; case 0x1002: m_vendor = GfxVendor::AMD; break; case 0x106B: m_vendor = GfxVendor::Apple; break; } VkDriverId driverId = driverProperties.driverID; if(driverId == VK_DRIVER_ID_MESA_RADV || driverId == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA) m_vendor = GfxVendor::Mesa; cemuLog_log(LogType::Force, "Using GPU: {}", properties.properties.deviceName); if (m_featureControl.deviceExtensions.driver_properties) { cemuLog_log(LogType::Force, "Driver version: {}", driverProperties.driverInfo); if(m_vendor == GfxVendor::Nvidia) { // multithreaded pipelines on nvidia (requires 515 or higher) m_featureControl.disableMultithreadedCompilation = (StringHelpers::ToInt(std::string(driverProperties.driverInfo)) < 515); } } else { cemuLog_log(LogType::Force, "Driver version (as stored in device info): {:08}", properties.properties.driverVersion); if(m_vendor == GfxVendor::Nvidia) { // if the driver does not support the extension, // it is assumed the driver is under version 515 m_featureControl.disableMultithreadedCompilation = true; } } } void VulkanRenderer::GetDeviceFeatures() { /* Get Vulkan features via GetPhysicalDeviceFeatures2 */ void* prevStruct = nullptr; VkPhysicalDeviceCustomBorderColorFeaturesEXT bcf{}; bcf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT; bcf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT; prevStruct = &bcf; VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT pcc{}; pcc.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES_EXT; pcc.pNext = prevStruct; prevStruct = &pcc; VkPhysicalDevicePresentIdFeaturesKHR pidf{}; pidf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR; pidf.pNext = prevStruct; prevStruct = &pidf; VkPhysicalDevicePresentWaitFeaturesKHR pwf{}; pwf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR; pwf.pNext = prevStruct; prevStruct = &pwf; VkPhysicalDevicePipelineRobustnessFeaturesEXT pprf{}; if (m_featureControl.deviceExtensions.pipeline_robustness) { pprf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT; pprf.pNext = prevStruct; prevStruct = &pprf; } VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{}; physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; physicalDeviceFeatures2.pNext = prevStruct; vkGetPhysicalDeviceFeatures2(m_physicalDevice, &physicalDeviceFeatures2); cemuLog_log(LogType::Force, "Vulkan: present_wait extension: {}", (pwf.presentWait && pidf.presentId) ? "supported" : "unsupported"); /* Get Vulkan device properties and limits */ VkPhysicalDeviceFloatControlsPropertiesKHR pfcp{}; prevStruct = nullptr; if (m_featureControl.deviceExtensions.shader_float_controls) { pfcp.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES_KHR; pfcp.pNext = prevStruct; prevStruct = &pfcp; } VkPhysicalDeviceProperties2 prop2{}; prop2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; prop2.pNext = prevStruct; vkGetPhysicalDeviceProperties2(m_physicalDevice, &prop2); /* Determine which subfeatures we can use */ m_featureControl.deviceExtensions.pipeline_creation_cache_control = pcc.pipelineCreationCacheControl; m_featureControl.deviceExtensions.custom_border_color_without_format = m_featureControl.deviceExtensions.custom_border_color && bcf.customBorderColorWithoutFormat; m_featureControl.shaderFloatControls.shaderRoundingModeRTEFloat32 = m_featureControl.deviceExtensions.shader_float_controls && pfcp.shaderRoundingModeRTEFloat32; if(!m_featureControl.shaderFloatControls.shaderRoundingModeRTEFloat32) cemuLog_log(LogType::Force, "Shader round mode control not available on this device or driver. Some rendering issues might occur."); if (!m_featureControl.deviceExtensions.pipeline_creation_cache_control) { cemuLog_log(LogType::Force, "VK_EXT_pipeline_creation_cache_control not supported. Cannot use asynchronous shader and pipeline compilation"); // if async shader compilation is enabled show warning message if (GetConfig().async_compile) LatteOverlay_pushNotification(_tr("Async shader compile is enabled but not supported by the graphics driver\nCemu will use synchronous compilation which can cause additional stutter"), 10000); } if (!m_featureControl.deviceExtensions.custom_border_color_without_format) { if (m_featureControl.deviceExtensions.custom_border_color) { cemuLog_log(LogType::Force, "VK_EXT_custom_border_color is present but only with limited support. Cannot emulate arbitrary border color"); } else { cemuLog_log(LogType::Force, "VK_EXT_custom_border_color not supported. Cannot emulate arbitrary border color"); } } if (!m_featureControl.deviceExtensions.depth_clip_enable) { cemuLog_log(LogType::Force, "VK_EXT_depth_clip_enable not supported"); } if (m_featureControl.deviceExtensions.pipeline_robustness) { if ( pprf.pipelineRobustness != VK_TRUE ) m_featureControl.deviceExtensions.pipeline_robustness = false; } // get limits m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(prop2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4); m_featureControl.limits.nonCoherentAtomSize = std::max(prop2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4); cemuLog_log(LogType::Force, fmt::format("VulkanLimits: UBAlignment {0} nonCoherentAtomSize {1}", prop2.properties.limits.minUniformBufferOffsetAlignment, prop2.properties.limits.nonCoherentAtomSize)); } VulkanRenderer::VulkanRenderer() { glslang::InitializeProcess(); cemuLog_log(LogType::Force, "------- Init Vulkan graphics backend -------"); const bool useValidationLayer = cemuLog_isLoggingEnabled(LogType::VulkanValidation); if (useValidationLayer) cemuLog_log(LogType::Force, "Validation layer is enabled"); VkResult err; // build list of layers m_layerNames.clear(); if (useValidationLayer) m_layerNames.emplace_back("VK_LAYER_KHRONOS_validation"); // check available instance extensions std::vector<const char*> enabledInstanceExtensions = CheckInstanceExtensionSupport(m_featureControl); uint32 apiVersion = VK_API_VERSION_1_1; if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) { if (VK_API_VERSION_MAJOR(apiVersion) < 1 || VK_API_VERSION_MINOR(apiVersion) < 2) apiVersion = VK_API_VERSION_1_1; } cemuLog_log(LogType::Force, fmt::format("Vulkan instance version: {}.{}", VK_API_VERSION_MAJOR(apiVersion), VK_API_VERSION_MINOR(apiVersion))); VkApplicationInfo app_info{}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pApplicationName = EMULATOR_NAME; app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH); app_info.pEngineName = EMULATOR_NAME; app_info.engineVersion = app_info.applicationVersion; app_info.apiVersion = apiVersion; VkInstanceCreateInfo create_info{}; create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; create_info.pApplicationInfo = &app_info; create_info.ppEnabledExtensionNames = enabledInstanceExtensions.data(); create_info.enabledExtensionCount = enabledInstanceExtensions.size(); create_info.ppEnabledLayerNames = m_layerNames.data(); create_info.enabledLayerCount = m_layerNames.size(); err = vkCreateInstance(&create_info, nullptr, &m_instance); if (err == VK_ERROR_LAYER_NOT_PRESENT) { cemuLog_log(LogType::Force, "Failed to enable vulkan validation (VK_LAYER_KHRONOS_validation)"); create_info.enabledLayerCount = 0; err = vkCreateInstance(&create_info, nullptr, &m_instance); } if (err != VK_SUCCESS) throw std::runtime_error(fmt::format("Unable to create a Vulkan instance: {}", err)); if (!InitializeInstanceVulkan(m_instance)) throw std::runtime_error("Unable to load instanced Vulkan functions"); uint32_t device_count = 0; vkEnumeratePhysicalDevices(m_instance, &device_count, nullptr); if (device_count == 0) throw std::runtime_error("Failed to find a GPU with Vulkan support."); // create tmp surface to create a logical device auto surface = CreateFramebufferSurface(m_instance, WindowSystem::GetWindowInfo().window_main); auto& config = GetConfig(); decltype(config.vk_graphic_device_uuid) zero{}; const bool has_device_set = config.vk_graphic_device_uuid != zero; VkPhysicalDevice fallbackDevice = VK_NULL_HANDLE; std::vector<VkPhysicalDevice> devices(device_count); vkEnumeratePhysicalDevices(m_instance, &device_count, devices.data()); for (const auto& device : devices) { if (IsDeviceSuitable(surface, device)) { if (fallbackDevice == VK_NULL_HANDLE) fallbackDevice = device; if (has_device_set) { VkPhysicalDeviceIDProperties physDeviceIDProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES }; VkPhysicalDeviceProperties2 physDeviceProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; physDeviceProps.pNext = &physDeviceIDProps; vkGetPhysicalDeviceProperties2(device, &physDeviceProps); if (memcmp(config.vk_graphic_device_uuid.data(), physDeviceIDProps.deviceUUID, VK_UUID_SIZE) != 0) continue; } m_physicalDevice = device; break; } } if (m_physicalDevice == VK_NULL_HANDLE && fallbackDevice != VK_NULL_HANDLE) { cemuLog_log(LogType::Force, "The selected GPU could not be found or is not suitable. Falling back to first available device instead"); m_physicalDevice = fallbackDevice; config.vk_graphic_device_uuid = {}; // resetting device selection } else if (m_physicalDevice == VK_NULL_HANDLE) { cemuLog_log(LogType::Force, "No physical GPU could be found with the required extensions and swap chain support."); throw std::runtime_error("No physical GPU could be found with the required extensions and swap chain support."); } CheckDeviceExtensionSupport(m_physicalDevice, m_featureControl); // todo - merge this with GetDeviceFeatures and separate from IsDeviceSuitable? DetermineVendor(); GetDeviceFeatures(); // init memory manager memoryManager.reset(new VKRMemoryManager(this)); try { VkPhysicalDeviceIDProperties physDeviceIDProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES }; VkPhysicalDeviceProperties2 physDeviceProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; physDeviceProps.pNext = &physDeviceIDProps; vkGetPhysicalDeviceProperties2(m_physicalDevice, &physDeviceProps); #if BOOST_OS_WINDOWS m_dxgi_wrapper = std::make_unique<DXGIWrapper>(physDeviceIDProps.deviceLUID); #endif } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't create dxgi wrapper: {}", ex.what()); } // create logical device m_indices = FindQueueFamilies(surface, m_physicalDevice); std::set<int> uniqueQueueFamilies = { m_indices.graphicsFamily, m_indices.presentFamily }; std::vector<VkDeviceQueueCreateInfo> queueCreateInfos = CreateQueueCreateInfos(uniqueQueueFamilies); VkPhysicalDeviceFeatures deviceFeatures = {}; deviceFeatures.independentBlend = VK_TRUE; deviceFeatures.samplerAnisotropy = VK_TRUE; deviceFeatures.imageCubeArray = VK_TRUE; //moltenVK supports logicOp via private api deviceFeatures.logicOp = VK_TRUE; #if !BOOST_OS_MACOS deviceFeatures.geometryShader = VK_TRUE; #endif deviceFeatures.occlusionQueryPrecise = VK_TRUE; deviceFeatures.depthClamp = VK_TRUE; deviceFeatures.depthBiasClamp = VK_TRUE; if (m_featureControl.deviceExtensions.pipeline_robustness) { deviceFeatures.robustBufferAccess = VK_FALSE; } else { cemuLog_log(LogType::Force, "VK_EXT_pipeline_robustness not supported. Falling back to robustBufferAccess"); deviceFeatures.robustBufferAccess = VK_TRUE; } if (m_featureControl.mode.useTFEmulationViaSSBO) { deviceFeatures.vertexPipelineStoresAndAtomics = true; } void* deviceExtensionFeatures = nullptr; // enable VK_EXT_pipeline_creation_cache_control VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT cacheControlFeature{}; if (m_featureControl.deviceExtensions.pipeline_creation_cache_control) { cacheControlFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES_EXT; cacheControlFeature.pNext = deviceExtensionFeatures; deviceExtensionFeatures = &cacheControlFeature; cacheControlFeature.pipelineCreationCacheControl = VK_TRUE; } // enable VK_EXT_custom_border_color VkPhysicalDeviceCustomBorderColorFeaturesEXT customBorderColorFeature{}; if (m_featureControl.deviceExtensions.custom_border_color_without_format) { customBorderColorFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT; customBorderColorFeature.pNext = deviceExtensionFeatures; deviceExtensionFeatures = &customBorderColorFeature; customBorderColorFeature.customBorderColors = VK_TRUE; customBorderColorFeature.customBorderColorWithoutFormat = VK_TRUE; } // enable VK_KHR_present_id VkPhysicalDevicePresentIdFeaturesKHR presentIdFeature{}; if(m_featureControl.deviceExtensions.present_wait) { presentIdFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR; presentIdFeature.pNext = deviceExtensionFeatures; deviceExtensionFeatures = &presentIdFeature; presentIdFeature.presentId = VK_TRUE; } // enable VK_KHR_present_wait VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeature{}; if(m_featureControl.deviceExtensions.present_wait) { presentWaitFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR; presentWaitFeature.pNext = deviceExtensionFeatures; deviceExtensionFeatures = &presentWaitFeature; presentWaitFeature.presentWait = VK_TRUE; } // enable VK_EXT_pipeline_robustness VkPhysicalDevicePipelineRobustnessFeaturesEXT pipelineRobustnessFeature{}; if (m_featureControl.deviceExtensions.pipeline_robustness) { pipelineRobustnessFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT; pipelineRobustnessFeature.pNext = deviceExtensionFeatures; deviceExtensionFeatures = &pipelineRobustnessFeature; pipelineRobustnessFeature.pipelineRobustness = VK_TRUE; } std::vector<const char*> used_extensions; VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions); VkResult result = vkCreateDevice(m_physicalDevice, &createInfo, nullptr, &m_logicalDevice); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Vulkan: Unable to create a logical device. Error {}", (sint32)result); throw std::runtime_error(fmt::format("Unable to create a logical device: {}", result)); } InitializeDeviceVulkan(m_logicalDevice); vkGetDeviceQueue(m_logicalDevice, m_indices.graphicsFamily, 0, &m_graphicsQueue); vkGetDeviceQueue(m_logicalDevice, m_indices.graphicsFamily, 0, &m_presentQueue); vkDestroySurfaceKHR(m_instance, surface, nullptr); if (useValidationLayer && m_featureControl.instanceExtensions.debug_utils) { PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT")); VkDebugUtilsMessengerCreateInfoEXT debugCallback{}; debugCallback.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; debugCallback.pNext = nullptr; debugCallback.flags = 0; debugCallback.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; debugCallback.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; debugCallback.pfnUserCallback = &DebugUtilsCallback; vkCreateDebugUtilsMessengerEXT(m_instance, &debugCallback, nullptr, &m_debugCallback); cemuLog_log(LogType::Force, "Debug: Vulkan validation layer enabled, vkCreateDebugUtilsMessengerEXT will be used to log validation errors"); } if (this->IsTracingToolEnabled()) cemuLog_log(LogType::Force, "Debug: Tracing tool detected, will recompile all shaders with debug info enabled. This disables the SPIR-V cache."); if (this->IsDebugMarkersEnabled()) cemuLog_log(LogType::Force, "Debug: Detected tool capable of using debug markers, will use vkDebugMarkerSetObjectNameEXT to identify Vulkan objects"); // set initial viewport and scissor box size m_state.currentViewport.width = 4; m_state.currentViewport.height = 4; m_state.currentScissorRect.extent.width = 4; m_state.currentScissorRect.extent.height = 4; QueryMemoryInfo(); QueryAvailableFormats(); CreateCommandPool(); CreateCommandBuffers(); CreateDescriptorPool(); swapchain_createDescriptorSetLayout(); // extension info // cemuLog_log(LogType::Force, "VK_KHR_dynamic_rendering: {}", m_featureControl.deviceExtensions.dynamic_rendering?"supported":"not supported"); void* bufferPtr; // init ringbuffer for uniform vars m_uniformVarBufferMemoryIsCoherent = false; if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) m_uniformVarBufferMemoryIsCoherent = true; else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) m_uniformVarBufferMemoryIsCoherent = true; // unified memory else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) m_uniformVarBufferMemoryIsCoherent = true; else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) m_uniformVarBufferMemoryIsCoherent = true; else { memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory); } if (!m_uniformVarBufferMemoryIsCoherent) cemuLog_log(LogType::Force, "[Vulkan-Info] Using non-coherent memory for uniform data"); bufferPtr = nullptr; vkMapMemory(m_logicalDevice, m_uniformVarBufferMemory, 0, VK_WHOLE_SIZE, 0, &bufferPtr); m_uniformVarBufferPtr = (uint8*)bufferPtr; // texture readback buffer if (!memoryManager->CreateBuffer(TEXTURE_READBACK_SIZE, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_textureReadbackBuffer, m_textureReadbackBufferMemory)) { memoryManager->CreateBuffer(TEXTURE_READBACK_SIZE, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_textureReadbackBuffer, m_textureReadbackBufferMemory); } bufferPtr = nullptr; vkMapMemory(m_logicalDevice, m_textureReadbackBufferMemory, 0, VK_WHOLE_SIZE, 0, &bufferPtr); m_textureReadbackBufferPtr = (uint8*)bufferPtr; // transform feedback ringbuffer memoryManager->CreateBuffer(LatteStreamout_GetRingBufferSize(), VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | (m_featureControl.mode.useTFEmulationViaSSBO ? VK_BUFFER_USAGE_STORAGE_BUFFER_BIT : 0), 0, m_xfbRingBuffer, m_xfbRingBufferMemory); // occlusion query result buffer if (!memoryManager->CreateBuffer(OCCLUSION_QUERY_POOL_SIZE * sizeof(uint64), VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults)) { memoryManager->CreateBuffer(OCCLUSION_QUERY_POOL_SIZE * sizeof(uint64), VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults); } bufferPtr = nullptr; vkMapMemory(m_logicalDevice, m_occlusionQueries.memoryQueryResults, 0, VK_WHOLE_SIZE, 0, &bufferPtr); m_occlusionQueries.ptrQueryResults = (uint64*)bufferPtr; for (sint32 i = 0; i < OCCLUSION_QUERY_POOL_SIZE; i++) m_occlusionQueries.list_availableQueryIndices.emplace_back(i); // start compilation threads RendererShaderVk::Init(); // shaders PipelineCompiler::CompileThreadPool_Start(); // pipelines } VulkanRenderer::~VulkanRenderer() { SubmitCommandBuffer(); WaitDeviceIdle(); WaitCommandBufferFinished(GetCurrentCommandBufferId()); // shut down pipeline save thread m_destructionRequested = true; m_pipeline_cache_semaphore.notify(); m_pipeline_cache_save_thread.join(); vkDestroyPipelineCache(m_logicalDevice, m_pipeline_cache, nullptr); if(!m_backbufferBlitDescriptorSetCache.empty()) { std::vector<VkDescriptorSet> freeVector; freeVector.reserve(m_backbufferBlitDescriptorSetCache.size()); std::transform(m_backbufferBlitDescriptorSetCache.begin(), m_backbufferBlitDescriptorSetCache.end(), std::back_inserter(freeVector), [](auto& i) { return i.second; }); vkFreeDescriptorSets(m_logicalDevice, m_descriptorPool, freeVector.size(), freeVector.data()); } vkDestroyDescriptorPool(m_logicalDevice, m_descriptorPool, nullptr); for(auto& i : m_backbufferBlitPipelineCache) { vkDestroyPipeline(m_logicalDevice, i.second, nullptr); } m_backbufferBlitPipelineCache = {}; if(m_occlusionQueries.queryPool != VK_NULL_HANDLE) vkDestroyQueryPool(m_logicalDevice, m_occlusionQueries.queryPool, nullptr); vkDestroyDescriptorSetLayout(m_logicalDevice, m_swapchainDescriptorSetLayout, nullptr); // shut down imgui ImGui_ImplVulkan_Shutdown(); // delete null objects DeleteNullObjects(); // delete buffers memoryManager->DeleteBuffer(m_uniformVarBuffer, m_uniformVarBufferMemory); memoryManager->DeleteBuffer(m_textureReadbackBuffer, m_textureReadbackBufferMemory); memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory); memoryManager->DeleteBuffer(m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults); memoryManager->DeleteBuffer(m_bufferCache, m_bufferCacheMemory); m_padSwapchainInfo = nullptr; m_mainSwapchainInfo = nullptr; // clean up resources used for surface copy surfaceCopy_cleanup(); // clean up default shaders delete defaultShaders.copySurface_vs; defaultShaders.copySurface_vs = nullptr; delete defaultShaders.copySurface_psColor2Depth; defaultShaders.copySurface_psColor2Depth = nullptr; delete defaultShaders.copySurface_psDepth2Color; defaultShaders.copySurface_psDepth2Color = nullptr; // destroy misc for (auto& it : m_cmd_buffer_fences) { vkDestroyFence(m_logicalDevice, it, nullptr); it = VK_NULL_HANDLE; } for(auto& sem : m_commandBufferSemaphores) { vkDestroySemaphore(m_logicalDevice, sem, nullptr); sem = VK_NULL_HANDLE; } if (m_pipelineLayout != VK_NULL_HANDLE) vkDestroyPipelineLayout(m_logicalDevice, m_pipelineLayout, nullptr); if (m_commandPool != VK_NULL_HANDLE) vkDestroyCommandPool(m_logicalDevice, m_commandPool, nullptr); VKRObjectSampler::DestroyCache(); // destroy debug callback if (m_debugCallback) { PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(m_instance, "vkDestroyDebugUtilsMessengerEXT")); vkDestroyDebugUtilsMessengerEXT(m_instance, m_debugCallback, nullptr); } while(!m_destructionQueue.empty()) ProcessDestructionQueue(); // destroy memory manager memoryManager.reset(); // destroy instance, devices if (m_instance != VK_NULL_HANDLE) { if (m_logicalDevice != VK_NULL_HANDLE) { vkDestroyDevice(m_logicalDevice, nullptr); } vkDestroyInstance(m_instance, nullptr); } // crashes? //glslang::FinalizeProcess(); } VulkanRenderer* VulkanRenderer::GetInstance() { #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug(g_renderer && dynamic_cast<VulkanRenderer*>(g_renderer.get())); // Use #if here because dynamic_casts dont get optimized away even if the result is not stored as with cemu_assert_debug #endif return (VulkanRenderer*)g_renderer.get(); } void VulkanRenderer::InitializeSurface(const Vector2i& size, bool mainWindow) { if (mainWindow) { m_mainSwapchainInfo = std::make_unique<SwapchainInfoVk>(mainWindow, size); m_mainSwapchainInfo->Create(); } else { m_padSwapchainInfo = std::make_unique<SwapchainInfoVk>(mainWindow, size); // todo: figure out a way to exclusively create swapchain on main LatteThread m_padSwapchainInfo->Create(); } } const std::unique_ptr<SwapchainInfoVk>& VulkanRenderer::GetChainInfoPtr(bool mainWindow) const { return mainWindow ? m_mainSwapchainInfo : m_padSwapchainInfo; } SwapchainInfoVk& VulkanRenderer::GetChainInfo(bool mainWindow) const { return *GetChainInfoPtr(mainWindow); } void VulkanRenderer::StopUsingPadAndWait() { m_destroyPadSwapchainNextAcquire.test_and_set(); m_destroyPadSwapchainNextAcquire.wait(true); } bool VulkanRenderer::IsPadWindowActive() { return IsSwapchainInfoValid(false); } void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool padView) { if (!m_screenshot_requested && m_screenshot_state == ScreenshotState::None) return; if (IsSwapchainInfoValid(false)) { // we already took a pad view screenshow and want a main window screenshot if (m_screenshot_state == ScreenshotState::Main && padView) return; if (m_screenshot_state == ScreenshotState::Pad && !padView) return; // remember which screenshot is left to take if (m_screenshot_state == ScreenshotState::None) m_screenshot_state = padView ? ScreenshotState::Main : ScreenshotState::Pad; else m_screenshot_state = ScreenshotState::None; } else m_screenshot_state = ScreenshotState::None; auto texViewVk = (LatteTextureViewVk*)texView; auto baseImageTex = texViewVk->GetBaseImage(); auto textureVk = baseImageTex->GetImageObj(); textureVk->flagForCurrentCommandBuffer(); auto dumpImage = textureVk->m_image; auto baseImage = dumpImage; int width, height; baseImageTex->GetEffectiveSize(width, height, 0); VkImage image = nullptr; VkDeviceMemory imageMemory = nullptr; if (texViewVk->firstMip != 0) { cemuLog_log(LogType::Force, "Failed to capture screenshot: capturing non-zero mip is not supported"); return; } auto format = baseImageTex->GetFormat(); if (format != VK_FORMAT_R8G8B8A8_UNORM && format != VK_FORMAT_R8G8B8A8_SRGB && format != VK_FORMAT_R8G8B8_UNORM && format != VK_FORMAT_R8G8B8_SNORM) { VkFormatProperties formatProps; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProps); bool supportsBlit = (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0; const bool dstUsesSRGB = (!padView && LatteGPUState.tvBufferUsesSRGB) || (padView && LatteGPUState.drcBufferUsesSRGB); const auto blitFormat = dstUsesSRGB ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, blitFormat, &formatProps); supportsBlit &= (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0; if (!supportsBlit) { cemuLog_log(LogType::Force, "Screenshot failed: Framebuffer is not in RGB8 format and blitting is unsupported"); return; } // convert texture using blitting VkImageCreateInfo imageInfo{}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.format = blitFormat; imageInfo.extent = {(uint32)width, (uint32)height, 1}; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.arrayLayers = 1; imageInfo.mipLevels = 1; imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; if (vkCreateImage(m_logicalDevice, &imageInfo, nullptr, &image) != VK_SUCCESS) return; VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(m_logicalDevice, image, &memRequirements); VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; uint32 memIndex; bool foundMemory = memoryManager->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memIndex); if (!foundMemory) { vkDestroyImage(m_logicalDevice, image, nullptr); cemuLog_log(LogType::Force, "Screenshot request failed due to incompatible vulkan memory types."); return; } allocInfo.memoryTypeIndex = memIndex; if (vkAllocateMemory(m_logicalDevice, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { vkDestroyImage(m_logicalDevice, image, nullptr); cemuLog_log(LogType::Force, "Screenshot request failed due to failed memory allocation."); return; } vkBindImageMemory(m_logicalDevice, image, imageMemory, 0); // prepare dst image for blitting { VkImageSubresourceRange range; range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; range.baseMipLevel = 0; range.levelCount = 1; range.baseArrayLayer = 0; range.layerCount = 1; // TRANSFER_READ is here only to silence validation as srcStageMask = 0 is only supported when using synchronization2 barrier_image<TRANSFER_READ, TRANSFER_WRITE>(image, range, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); } // prepare src image for blitting { VkImageSubresourceLayers range; range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; range.mipLevel = 0; range.baseArrayLayer = texViewVk->firstSlice; range.layerCount = 1; barrier_image<IMAGE_WRITE | TRANSFER_WRITE, SYNC_OP::TRANSFER_READ>(baseImageTex, range, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); } VkOffset3D blitSize{width, height, 1}; VkImageBlit imageBlitRegion{}; imageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imageBlitRegion.srcSubresource.mipLevel = 0; imageBlitRegion.srcSubresource.baseArrayLayer = texViewVk->firstSlice; imageBlitRegion.srcSubresource.layerCount = 1; imageBlitRegion.srcOffsets[1] = blitSize; imageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imageBlitRegion.dstSubresource.mipLevel = 0; imageBlitRegion.dstSubresource.baseArrayLayer = 0; imageBlitRegion.dstSubresource.layerCount = 1; imageBlitRegion.dstOffsets[1] = blitSize; // Issue the blit command vkCmdBlitImage(m_state.currentCommandBuffer, dumpImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageBlitRegion, VK_FILTER_NEAREST); // dest image to general layout { VkImageSubresourceRange range; range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; range.baseMipLevel = 0; range.levelCount = 1; range.baseArrayLayer = 0; range.layerCount = 1; barrier_image<TRANSFER_WRITE, TRANSFER_READ>(image, range, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL); } // transition image back { VkImageSubresourceLayers range; range.aspectMask = baseImageTex->GetImageAspect(); range.mipLevel = 0; range.baseArrayLayer = texViewVk->firstSlice; range.layerCount = 1; barrier_image<TRANSFER_READ, TRANSFER_WRITE | IMAGE_WRITE>(baseImageTex, range, VK_IMAGE_LAYOUT_GENERAL); } format = VK_FORMAT_R8G8B8A8_UNORM; dumpImage = image; } uint32 size; switch (format) { case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_R8G8B8A8_SRGB: size = 4 * width * height; break; case VK_FORMAT_R8G8B8_UNORM: case VK_FORMAT_R8G8B8_SRGB: size = 3 * width * height; break; default: size = 0; } if (size == 0) { cemu_assert_debug(false); return; } VkBufferImageCopy region{}; region.bufferOffset = 0; region.bufferRowLength = width; region.bufferImageHeight = height; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageSubresource.mipLevel = 0; region.imageOffset = { 0,0,0 }; region.imageExtent = { (uint32)width,(uint32)height,1 }; void* bufferPtr = nullptr; VkBuffer buffer = nullptr; VkDeviceMemory bufferMemory = nullptr; memoryManager->CreateBuffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, buffer, bufferMemory); vkMapMemory(m_logicalDevice, bufferMemory, 0, VK_WHOLE_SIZE, 0, &bufferPtr); // if no blit was necessary a barrier still needs to be inserted and slice may not be zero if (dumpImage == baseImage) { region.imageSubresource.baseArrayLayer = texViewVk->firstSlice; barrier_image<IMAGE_WRITE | TRANSFER_WRITE, TRANSFER_READ>(baseImageTex, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); } vkCmdCopyImageToBuffer(m_state.currentCommandBuffer, dumpImage, VK_IMAGE_LAYOUT_GENERAL, buffer, 1, ®ion); SubmitCommandBuffer(); WaitCommandBufferFinished(GetCurrentCommandBufferId()); bool formatValid = true; std::vector<uint8> rgb_data; rgb_data.reserve(3 * width * height); switch (format) { case VK_FORMAT_R8G8B8A8_UNORM: for (auto ptr = (uint8*)bufferPtr; ptr < (uint8*)bufferPtr + size; ptr += 4) { rgb_data.emplace_back(*ptr); rgb_data.emplace_back(*(ptr + 1)); rgb_data.emplace_back(*(ptr + 2)); } break; case VK_FORMAT_R8G8B8A8_SRGB: for (auto ptr = (uint8*)bufferPtr; ptr < (uint8*)bufferPtr + size; ptr += 4) { rgb_data.emplace_back(SRGBComponentToRGB(*ptr)); rgb_data.emplace_back(SRGBComponentToRGB(*(ptr + 1))); rgb_data.emplace_back(SRGBComponentToRGB(*(ptr + 2))); } break; case VK_FORMAT_R8G8B8_UNORM: std::copy((uint8*)bufferPtr, (uint8*)bufferPtr + size, rgb_data.begin()); break; case VK_FORMAT_R8G8B8_SRGB: std::transform((uint8*)bufferPtr, (uint8*)bufferPtr + size, rgb_data.begin(), SRGBComponentToRGB); break; default: formatValid = false; cemu_assert_debug(false); } vkUnmapMemory(m_logicalDevice, bufferMemory); vkFreeMemory(m_logicalDevice, bufferMemory, nullptr); vkDestroyBuffer(m_logicalDevice, buffer, nullptr); if (image) vkDestroyImage(m_logicalDevice, image, nullptr); if (imageMemory) vkFreeMemory(m_logicalDevice, imageMemory, nullptr); if (formatValid) SaveScreenshot(rgb_data, width, height, !padView); } static const float kQueuePriority = 1.0f; std::vector<VkDeviceQueueCreateInfo> VulkanRenderer::CreateQueueCreateInfos(const std::set<sint32>& uniqueQueueFamilies) const { std::vector<VkDeviceQueueCreateInfo> queueCreateInfos; for (int queueFamily : uniqueQueueFamilies) { VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; queueCreateInfo.pQueuePriorities = &kQueuePriority; queueCreateInfos.emplace_back(queueCreateInfo); } return queueCreateInfos; } VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector<VkDeviceQueueCreateInfo>& queueCreateInfos, const VkPhysicalDeviceFeatures& deviceFeatures, const void* deviceExtensionStructs, std::vector<const char*>& used_extensions) const { used_extensions = kRequiredDeviceExtensions; if (m_featureControl.deviceExtensions.tooling_info) used_extensions.emplace_back(VK_EXT_TOOLING_INFO_EXTENSION_NAME); if (m_featureControl.deviceExtensions.depth_range_unrestricted) used_extensions.emplace_back(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); if (m_featureControl.deviceExtensions.nv_fill_rectangle) used_extensions.emplace_back(VK_NV_FILL_RECTANGLE_EXTENSION_NAME); if (m_featureControl.deviceExtensions.pipeline_feedback) used_extensions.emplace_back(VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME); if (m_featureControl.deviceExtensions.cubic_filter) used_extensions.emplace_back(VK_EXT_FILTER_CUBIC_EXTENSION_NAME); if (m_featureControl.deviceExtensions.custom_border_color) used_extensions.emplace_back(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); if (m_featureControl.deviceExtensions.driver_properties) used_extensions.emplace_back(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME); if (m_featureControl.deviceExtensions.external_memory_host) used_extensions.emplace_back(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); if (m_featureControl.deviceExtensions.synchronization2) used_extensions.emplace_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); if (m_featureControl.deviceExtensions.dynamic_rendering) used_extensions.emplace_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); if (m_featureControl.deviceExtensions.shader_float_controls) used_extensions.emplace_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); if (m_featureControl.deviceExtensions.depth_clip_enable) used_extensions.emplace_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME); if (m_featureControl.deviceExtensions.present_wait) { used_extensions.emplace_back(VK_KHR_PRESENT_ID_EXTENSION_NAME); used_extensions.emplace_back(VK_KHR_PRESENT_WAIT_EXTENSION_NAME); } if (m_featureControl.deviceExtensions.pipeline_robustness) used_extensions.emplace_back(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME); VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.pQueueCreateInfos = queueCreateInfos.data(); createInfo.queueCreateInfoCount = (uint32_t)queueCreateInfos.size(); createInfo.pEnabledFeatures = &deviceFeatures; createInfo.enabledExtensionCount = used_extensions.size(); createInfo.ppEnabledExtensionNames = used_extensions.data(); createInfo.pNext = deviceExtensionStructs; if (!m_layerNames.empty()) { createInfo.enabledLayerCount = m_layerNames.size(); createInfo.ppEnabledLayerNames = m_layerNames.data(); } return createInfo; } RendererShader* VulkanRenderer::shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) { return new RendererShaderVk(type, baseHash, auxHash, isGameShader, isGfxPackShader, source); } VulkanRenderer::QueueFamilyIndices VulkanRenderer::FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device) { uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); QueueFamilyIndices indices; for (int i = 0; i < (int)queueFamilies.size(); ++i) { const auto& queueFamily = queueFamilies[i]; if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) indices.graphicsFamily = i; VkBool32 presentSupport = false; const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); if (result != VK_SUCCESS) throw std::runtime_error(fmt::format("Error while attempting to check if a surface supports presentation: {}", result)); if (queueFamily.queueCount > 0 && presentSupport) indices.presentFamily = i; if (indices.IsComplete()) break; } return indices; } bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info) { std::vector<VkExtensionProperties> availableDeviceExtensions; auto isExtensionAvailable = [&availableDeviceExtensions](const char* extensionName) -> bool { return std::find_if(availableDeviceExtensions.begin(), availableDeviceExtensions.end(), [&extensionName](const VkExtensionProperties& prop) -> bool { return strcmp(prop.extensionName, extensionName) == 0; }) != availableDeviceExtensions.cend(); }; uint32_t extensionCount; VkResult result = vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); if (result != VK_SUCCESS) throw std::runtime_error(fmt::format("Cannot retrieve count of properties for a physical device: {}", result)); availableDeviceExtensions.resize(extensionCount); result = vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableDeviceExtensions.data()); if (result != VK_SUCCESS) throw std::runtime_error(fmt::format("Cannot retrieve properties for a physical device: {}", result)); std::set<std::string> requiredExtensions(kRequiredDeviceExtensions.begin(), kRequiredDeviceExtensions.end()); for (const auto& extension : availableDeviceExtensions) { requiredExtensions.erase(extension.extensionName); } info.deviceExtensions.tooling_info = isExtensionAvailable(VK_EXT_TOOLING_INFO_EXTENSION_NAME); info.deviceExtensions.transform_feedback = isExtensionAvailable(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME); info.deviceExtensions.depth_range_unrestricted = isExtensionAvailable(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); info.deviceExtensions.nv_fill_rectangle = isExtensionAvailable(VK_NV_FILL_RECTANGLE_EXTENSION_NAME); info.deviceExtensions.pipeline_feedback = isExtensionAvailable(VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME); info.deviceExtensions.cubic_filter = isExtensionAvailable(VK_EXT_FILTER_CUBIC_EXTENSION_NAME); info.deviceExtensions.custom_border_color = isExtensionAvailable(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); info.deviceExtensions.driver_properties = isExtensionAvailable(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME); info.deviceExtensions.external_memory_host = isExtensionAvailable(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); info.deviceExtensions.synchronization2 = isExtensionAvailable(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); info.deviceExtensions.shader_float_controls = isExtensionAvailable(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); info.deviceExtensions.dynamic_rendering = false; // isExtensionAvailable(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); info.deviceExtensions.depth_clip_enable = isExtensionAvailable(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME); info.deviceExtensions.pipeline_robustness = isExtensionAvailable(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME); // dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022 info.deviceExtensions.present_wait = isExtensionAvailable(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) && isExtensionAvailable(VK_KHR_PRESENT_ID_EXTENSION_NAME); // check for validation layers and frame debuggers info.usingDebugMarkerTool = false; info.usingTracingTool = false; if (info.deviceExtensions.tooling_info && vkGetPhysicalDeviceToolPropertiesEXT) { uint32_t toolCount = 0; if (vkGetPhysicalDeviceToolPropertiesEXT(device, &toolCount, nullptr) == VK_SUCCESS) { std::vector<VkPhysicalDeviceToolPropertiesEXT> toolProperties(toolCount); if (toolCount > 0 && vkGetPhysicalDeviceToolPropertiesEXT(device, &toolCount, toolProperties.data()) == VK_SUCCESS) { for (auto& itr : toolProperties) { if ((itr.purposes & VK_TOOL_PURPOSE_DEBUG_MARKERS_BIT_EXT) != 0 && info.instanceExtensions.debug_utils && vkSetDebugUtilsObjectNameEXT) info.usingDebugMarkerTool = true; if ((itr.purposes & VK_TOOL_PURPOSE_TRACING_BIT) != 0) info.usingTracingTool = true; } } } } return requiredExtensions.empty(); } std::vector<const char*> VulkanRenderer::CheckInstanceExtensionSupport(FeatureControl& info) { std::vector<VkExtensionProperties> availableInstanceExtensions; std::vector<const char*> enabledInstanceExtensions; VkResult err; auto isExtensionAvailable = [&availableInstanceExtensions](const char* extensionName) -> bool { return std::find_if(availableInstanceExtensions.begin(), availableInstanceExtensions.end(), [&extensionName](const VkExtensionProperties& prop) -> bool { return strcmp(prop.extensionName, extensionName) == 0; }) != availableInstanceExtensions.cend(); }; // get list of available instance extensions uint32_t count; if ((err = vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr)) != VK_SUCCESS) throw std::runtime_error(fmt::format("Failed to retrieve the instance extension properties : {}", err)); availableInstanceExtensions.resize(count); if ((err = vkEnumerateInstanceExtensionProperties(nullptr, &count, availableInstanceExtensions.data())) != VK_SUCCESS) throw std::runtime_error(fmt::format("Failed to retrieve the instance extension properties: {}", err)); // build list of required extensions std::vector<const char*> requiredInstanceExtensions; requiredInstanceExtensions.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME); #if BOOST_OS_WINDOWS requiredInstanceExtensions.emplace_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); #elif BOOST_OS_LINUX || BOOST_OS_BSD auto backend = WindowSystem::GetWindowInfo().window_main.backend; if(backend == WindowSystem::WindowHandleInfo::Backend::X11) requiredInstanceExtensions.emplace_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); #if HAS_WAYLAND else if (backend == WindowSystem::WindowHandleInfo::Backend::Wayland) requiredInstanceExtensions.emplace_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); #endif #elif BOOST_OS_MACOS requiredInstanceExtensions.emplace_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); #endif if (cemuLog_isLoggingEnabled(LogType::VulkanValidation)) requiredInstanceExtensions.emplace_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); // make sure all required extensions are supported for (const auto& extension : availableInstanceExtensions) { for (auto it = requiredInstanceExtensions.begin(); it < requiredInstanceExtensions.end(); ++it) { if (strcmp(*it, extension.extensionName) == 0) { enabledInstanceExtensions.emplace_back(*it); requiredInstanceExtensions.erase(it); break; } } } if (!requiredInstanceExtensions.empty()) { cemuLog_log(LogType::Force, "The following required Vulkan instance extensions are not supported:"); std::stringstream ss; for (const auto& extension : requiredInstanceExtensions) cemuLog_log(LogType::Force, "{}", extension); cemuLog_waitForFlush(); throw std::runtime_error(ss.str()); } // check for optional extensions info.instanceExtensions.debug_utils = isExtensionAvailable(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); if (info.instanceExtensions.debug_utils) enabledInstanceExtensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); return enabledInstanceExtensions; } bool VulkanRenderer::IsDeviceSuitable(VkSurfaceKHR surface, const VkPhysicalDevice& device) { if (!FindQueueFamilies(surface, device).IsComplete()) return false; // check API version (using Vulkan 1.0 way of querying properties) VkPhysicalDeviceProperties properties{}; vkGetPhysicalDeviceProperties(device, &properties); uint32 vkVersionMajor = VK_API_VERSION_MAJOR(properties.apiVersion); uint32 vkVersionMinor = VK_API_VERSION_MINOR(properties.apiVersion); if (vkVersionMajor < 1 || (vkVersionMajor == 1 && vkVersionMinor < 1)) return false; // minimum required version is Vulkan 1.1 FeatureControl info; if (!CheckDeviceExtensionSupport(device, info)) return false; const auto swapchainSupport = SwapchainInfoVk::QuerySwapchainSupport(surface, device); return !swapchainSupport.formats.empty() && !swapchainSupport.presentModes.empty(); } #if BOOST_OS_WINDOWS VkSurfaceKHR VulkanRenderer::CreateWinSurface(VkInstance instance, HWND hwindow) { VkWin32SurfaceCreateInfoKHR sci{}; sci.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; sci.hwnd = hwindow; sci.hinstance = GetModuleHandle(nullptr); VkSurfaceKHR result; VkResult err; if ((err = vkCreateWin32SurfaceKHR(instance, &sci, nullptr, &result)) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Cannot create a Win32 Vulkan surface: {}", (sint32)err); throw std::runtime_error(fmt::format("Cannot create a Win32 Vulkan surface: {}", err)); } return result; } #endif #if BOOST_OS_LINUX || BOOST_OS_BSD VkSurfaceKHR VulkanRenderer::CreateXlibSurface(VkInstance instance, Display* dpy, Window window) { VkXlibSurfaceCreateInfoKHR sci{}; sci.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; sci.flags = 0; sci.dpy = dpy; sci.window = window; VkSurfaceKHR result; VkResult err; if ((err = vkCreateXlibSurfaceKHR(instance, &sci, nullptr, &result)) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Cannot create a X11 Vulkan surface: {}", (sint32)err); throw std::runtime_error(fmt::format("Cannot create a X11 Vulkan surface: {}", err)); } return result; } VkSurfaceKHR VulkanRenderer::CreateXcbSurface(VkInstance instance, xcb_connection_t* connection, xcb_window_t window) { VkXcbSurfaceCreateInfoKHR sci{}; sci.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; sci.flags = 0; sci.connection = connection; sci.window = window; VkSurfaceKHR result; VkResult err; if ((err = vkCreateXcbSurfaceKHR(instance, &sci, nullptr, &result)) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Cannot create a XCB Vulkan surface: {}", (sint32)err); throw std::runtime_error(fmt::format("Cannot create a XCB Vulkan surface: {}", err)); } return result; } #ifdef HAS_WAYLAND VkSurfaceKHR VulkanRenderer::CreateWaylandSurface(VkInstance instance, wl_display* display, wl_surface* surface) { VkWaylandSurfaceCreateInfoKHR sci{}; sci.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; sci.flags = 0; sci.display = display; sci.surface = surface; VkSurfaceKHR result; VkResult err; if ((err = vkCreateWaylandSurfaceKHR(instance, &sci, nullptr, &result)) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Cannot create a Wayland Vulkan surface: {}", (sint32)err); throw std::runtime_error(fmt::format("Cannot create a Wayland Vulkan surface: {}", err)); } return result; } #endif // HAS_WAYLAND #endif // BOOST_OS_LINUX VkSurfaceKHR VulkanRenderer::CreateFramebufferSurface(VkInstance instance, WindowSystem::WindowHandleInfo& windowInfo) { #if BOOST_OS_WINDOWS return CreateWinSurface(instance, static_cast<HWND>(windowInfo.surface)); #elif BOOST_OS_LINUX || BOOST_OS_BSD if(windowInfo.backend == WindowSystem::WindowHandleInfo::Backend::X11) return CreateXlibSurface(instance, static_cast<Display*>(windowInfo.display), reinterpret_cast<Window>(windowInfo.surface)); #ifdef HAS_WAYLAND if(windowInfo.backend == WindowSystem::WindowHandleInfo::Backend::Wayland) return CreateWaylandSurface(instance, static_cast<wl_display*>(windowInfo.display), static_cast<wl_surface*>(windowInfo.surface)); #endif return {}; #elif BOOST_OS_MACOS return CreateCocoaSurface(instance, windowInfo.surface); #endif } void VulkanRenderer::CreateCommandPool() { VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = m_indices.graphicsFamily; poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; VkResult result = vkCreateCommandPool(m_logicalDevice, &poolInfo, nullptr, &m_commandPool); if (result != VK_SUCCESS) throw std::runtime_error(fmt::format("Failed to create command pool: {}", result)); } void VulkanRenderer::CreateCommandBuffers() { auto it = m_cmd_buffer_fences.begin(); VkFenceCreateInfo fenceInfo{}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &*it); ++it; fenceInfo.flags = 0; for (; it != m_cmd_buffer_fences.end(); ++it) { vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &*it); } VkCommandBufferAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = m_commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = (uint32_t)m_commandBuffers.size(); const VkResult result = vkAllocateCommandBuffers(m_logicalDevice, &allocInfo, m_commandBuffers.data()); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to allocate command buffers: {}", result); throw std::runtime_error(fmt::format("Failed to allocate command buffers: {}", result)); } for (auto& semItr : m_commandBufferSemaphores) { VkSemaphoreCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semItr) != VK_SUCCESS) UnrecoverableError("Failed to create semaphore for command buffer"); } } bool VulkanRenderer::IsSwapchainInfoValid(bool mainWindow) const { auto& chainInfo = GetChainInfoPtr(mainWindow); return chainInfo && chainInfo->IsValid(); } void VulkanRenderer::CreateNullTexture(NullTexture& nullTex, VkImageType imageType) { // these are used when the game requests NULL ptr textures VkImageCreateInfo imageInfo{}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; if (imageType == VK_IMAGE_TYPE_1D) { imageInfo.extent.width = 4; imageInfo.extent.height = 1; } else if (imageType == VK_IMAGE_TYPE_2D) { imageInfo.extent.width = 4; imageInfo.extent.height = 1; } else { cemu_assert(false); } imageInfo.mipLevels = 1; imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.extent.depth = 1; imageInfo.arrayLayers = 1; imageInfo.imageType = imageType; imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; if (vkCreateImage(m_logicalDevice, &imageInfo, nullptr, &nullTex.image) != VK_SUCCESS) UnrecoverableError("Failed to create nullTex image"); nullTex.allocation = memoryManager->imageMemoryAllocate(nullTex.image); VkClearColorValue clrColor{}; ClearColorImageRaw(nullTex.image, 0, 0, clrColor, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL); // texture view VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = nullTex.image; if (imageType == VK_IMAGE_TYPE_1D) viewInfo.viewType = VK_IMAGE_VIEW_TYPE_1D; else if (imageType == VK_IMAGE_TYPE_2D) viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; else { cemu_assert(false); } viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; viewInfo.subresourceRange.baseMipLevel = 0; viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; if (vkCreateImageView(m_logicalDevice, &viewInfo, nullptr, &nullTex.view) != VK_SUCCESS) UnrecoverableError("Failed to create nullTex image view"); // sampler VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.mipLodBias = 0.0f; samplerInfo.compareOp = VK_COMPARE_OP_NEVER; samplerInfo.minLod = 0.0f; samplerInfo.maxLod = 0.0f; samplerInfo.maxAnisotropy = 1.0; samplerInfo.anisotropyEnable = VK_FALSE; samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; vkCreateSampler(m_logicalDevice, &samplerInfo, nullptr, &nullTex.sampler); } void VulkanRenderer::CreateNullObjects() { CreateNullTexture(nullTexture1D, VK_IMAGE_TYPE_1D); CreateNullTexture(nullTexture2D, VK_IMAGE_TYPE_2D); } void VulkanRenderer::DeleteNullTexture(NullTexture& nullTex) { vkDestroySampler(m_logicalDevice, nullTex.sampler, nullptr); nullTex.sampler = VK_NULL_HANDLE; vkDestroyImageView(m_logicalDevice, nullTex.view, nullptr); nullTex.view = VK_NULL_HANDLE; vkDestroyImage(m_logicalDevice, nullTex.image, nullptr); nullTex.image = VK_NULL_HANDLE; memoryManager->imageMemoryFree(nullTex.allocation); nullTex.allocation = nullptr; } void VulkanRenderer::DeleteNullObjects() { DeleteNullTexture(nullTexture1D); DeleteNullTexture(nullTexture2D); } void VulkanRenderer::ImguiInit() { VkRenderPass prevRenderPass = m_imguiRenderPass; VkAttachmentDescription colorAttachment = {}; colorAttachment.format = m_mainSwapchainInfo->m_surfaceFormat.format; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference colorAttachmentRef = {}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; const auto result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_imguiRenderPass); if (result != VK_SUCCESS) throw VkException(result, "can't create imgui renderpass"); ImGui_ImplVulkan_InitInfo info{}; info.Instance = m_instance; info.PhysicalDevice = m_physicalDevice; info.Device = m_logicalDevice; info.QueueFamily = m_indices.presentFamily; info.Queue = m_presentQueue; info.PipelineCache = m_pipeline_cache; info.DescriptorPool = m_descriptorPool; info.MinImageCount = m_mainSwapchainInfo->m_swapchainImages.size(); info.ImageCount = info.MinImageCount; ImGui_ImplVulkan_Init(&info, m_imguiRenderPass); if (prevRenderPass != VK_NULL_HANDLE) vkDestroyRenderPass(GetLogicalDevice(), prevRenderPass, nullptr); } void VulkanRenderer::Initialize() { Renderer::Initialize(); InitFirstCommandBuffer(); CreatePipelineCache(); ImguiInit(); CreateNullObjects(); } void VulkanRenderer::Shutdown() { SubmitCommandBuffer(); WaitDeviceIdle(); // stop compilation threads RendererShaderVk::Shutdown(); PipelineCompiler::CompileThreadPool_Stop(); DeleteFontTextures(); Renderer::Shutdown(); if (m_imguiRenderPass != VK_NULL_HANDLE) { vkDestroyRenderPass(m_logicalDevice, m_imguiRenderPass, nullptr); m_imguiRenderPass = VK_NULL_HANDLE; } RendererShaderVk::Shutdown(); } void VulkanRenderer::UnrecoverableError(const char* errMsg) const { cemuLog_log(LogType::Force, "Unrecoverable error in Vulkan renderer"); cemuLog_log(LogType::Force, "Msg: {}", errMsg); throw std::runtime_error(errMsg); } struct VulkanRequestedFormat_t { VkFormat fmt; const char* name; bool isDepth; bool mustSupportAttachment; bool mustSupportBlending; }; #define reqColorFormat(__name, __reqAttachment, __reqBlend) {__name, ""#__name, false, __reqAttachment, __reqBlend} #define reqDepthFormat(__name) {__name, ""#__name, true, true, false} VulkanRequestedFormat_t requestedFormatList[] = { reqDepthFormat(VK_FORMAT_D32_SFLOAT_S8_UINT), reqDepthFormat(VK_FORMAT_D24_UNORM_S8_UINT), reqDepthFormat(VK_FORMAT_D32_SFLOAT), reqDepthFormat(VK_FORMAT_D16_UNORM), reqColorFormat(VK_FORMAT_R32G32B32A32_SFLOAT, true, true), reqColorFormat(VK_FORMAT_R32G32B32A32_UINT, true, false), reqColorFormat(VK_FORMAT_R16G16B16A16_SFLOAT, true, true), reqColorFormat(VK_FORMAT_R16G16B16A16_UINT, true, false), reqColorFormat(VK_FORMAT_R16G16B16A16_UNORM, true, true), reqColorFormat(VK_FORMAT_R16G16B16A16_SNORM, true, true), reqColorFormat(VK_FORMAT_R8G8B8A8_UNORM, true, true), reqColorFormat(VK_FORMAT_R8G8B8A8_SNORM, true, true), reqColorFormat(VK_FORMAT_R8G8B8A8_SRGB, true, true), reqColorFormat(VK_FORMAT_R8G8B8A8_UINT, true, false), reqColorFormat(VK_FORMAT_R8G8B8A8_SINT, true, false), reqColorFormat(VK_FORMAT_R4G4B4A4_UNORM_PACK16, true, true), reqColorFormat(VK_FORMAT_R32G32_SFLOAT, true, true), reqColorFormat(VK_FORMAT_R32G32_UINT, true, false), reqColorFormat(VK_FORMAT_R16G16_UNORM, true, true), reqColorFormat(VK_FORMAT_R16G16_SFLOAT, true, true), reqColorFormat(VK_FORMAT_R8G8_UNORM, true, true), reqColorFormat(VK_FORMAT_R8G8_SNORM, true, true), reqColorFormat(VK_FORMAT_R4G4_UNORM_PACK8, true, true), reqColorFormat(VK_FORMAT_R32_SFLOAT, true, true), reqColorFormat(VK_FORMAT_R32_UINT, true, false), reqColorFormat(VK_FORMAT_R16_SFLOAT, true, true), reqColorFormat(VK_FORMAT_R16_UNORM, true, true), reqColorFormat(VK_FORMAT_R16_SNORM, true, true), reqColorFormat(VK_FORMAT_R8_UNORM, true, true), reqColorFormat(VK_FORMAT_R8_SNORM, true, true), reqColorFormat(VK_FORMAT_R5G6B5_UNORM_PACK16, true, true), reqColorFormat(VK_FORMAT_R5G5B5A1_UNORM_PACK16, true, true), reqColorFormat(VK_FORMAT_B10G11R11_UFLOAT_PACK32, true, true), reqColorFormat(VK_FORMAT_R16G16B16A16_SNORM, true, true), reqColorFormat(VK_FORMAT_BC1_RGBA_SRGB_BLOCK, false, false), reqColorFormat(VK_FORMAT_BC1_RGBA_UNORM_BLOCK, false, false), reqColorFormat(VK_FORMAT_BC2_UNORM_BLOCK, false, false), reqColorFormat(VK_FORMAT_BC2_SRGB_BLOCK, false, false), reqColorFormat(VK_FORMAT_BC3_UNORM_BLOCK, false, false), reqColorFormat(VK_FORMAT_BC3_SRGB_BLOCK, false, false), reqColorFormat(VK_FORMAT_BC4_UNORM_BLOCK, false, false), reqColorFormat(VK_FORMAT_BC4_SNORM_BLOCK, false, false), reqColorFormat(VK_FORMAT_BC5_UNORM_BLOCK, false, false), reqColorFormat(VK_FORMAT_BC5_SNORM_BLOCK, false, false), reqColorFormat(VK_FORMAT_A2B10G10R10_UNORM_PACK32, true, true), reqColorFormat(VK_FORMAT_R32_SFLOAT, true, true) }; void VulkanRenderer::QueryMemoryInfo() { VkPhysicalDeviceMemoryProperties memProperties; vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memProperties); cemuLog_log(LogType::Force, "Vulkan device memory info:"); for (uint32 i = 0; i < memProperties.memoryHeapCount; i++) { cemuLog_log(LogType::Force, "Heap {} - Size {}MB Flags 0x{:08x}", i, (sint32)(memProperties.memoryHeaps[i].size / 1024ll / 1024ll), (uint32)memProperties.memoryHeaps[i].flags); } for (uint32 i = 0; i < memProperties.memoryTypeCount; i++) { cemuLog_log(LogType::Force, "Memory {} - HeapIndex {} Flags 0x{:08x}", i, (sint32)memProperties.memoryTypes[i].heapIndex, (uint32)memProperties.memoryTypes[i].propertyFlags); } } void VulkanRenderer::QueryAvailableFormats() { VkFormatProperties fmtProp{}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, VK_FORMAT_D24_UNORM_S8_UINT, &fmtProp); // D24S8 if (fmtProp.optimalTilingFeatures != 0) // todo - more restrictive check { m_supportedFormatInfo.fmt_d24_unorm_s8_uint = true; } // R4G4 fmtProp = {}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, VK_FORMAT_R4G4_UNORM_PACK8, &fmtProp); if (fmtProp.optimalTilingFeatures != 0) { m_supportedFormatInfo.fmt_r4g4_unorm_pack = true; } // R5G6B5 fmtProp = {}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, VK_FORMAT_R5G6B5_UNORM_PACK16, &fmtProp); if (fmtProp.optimalTilingFeatures != 0) { m_supportedFormatInfo.fmt_r5g6b5_unorm_pack = true; } // R4G4B4A4 fmtProp = {}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, VK_FORMAT_R4G4B4A4_UNORM_PACK16, &fmtProp); if (fmtProp.optimalTilingFeatures != 0) { m_supportedFormatInfo.fmt_r4g4b4a4_unorm_pack = true; } // A1R5G5B5 fmtProp = {}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, VK_FORMAT_A1R5G5B5_UNORM_PACK16, &fmtProp); if (fmtProp.optimalTilingFeatures != 0) { m_supportedFormatInfo.fmt_a1r5g5b5_unorm_pack = true; } // print info about unsupported formats to log for (auto& it : requestedFormatList) { fmtProp = {}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, it.fmt, &fmtProp); VkFormatFeatureFlags requestedBits = 0; if (it.mustSupportAttachment) { if (it.isDepth) requestedBits |= VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; else requestedBits |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT; if (!it.isDepth && it.mustSupportBlending) requestedBits |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; } requestedBits |= VK_FORMAT_FEATURE_TRANSFER_DST_BIT; requestedBits |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; if (fmtProp.optimalTilingFeatures == 0) { cemuLog_log(LogType::Force, "{} not supported", it.name); } else if ((fmtProp.optimalTilingFeatures & requestedBits) != requestedBits) { //std::string missingStr; //missingStr.assign(fmt::format("{} missing features:", it.name)); //if (!(fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) && !it.isDepth && it.mustSupportAttachment) // missingStr.append(" COLOR_ATTACHMENT"); //if (!(fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT) && !it.isDepth && it.mustSupportBlending) // missingStr.append(" COLOR_ATTACHMENT_BLEND"); //if (!(fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) && it.isDepth && it.mustSupportAttachment) // missingStr.append(" DEPTH_ATTACHMENT"); //if (!(fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) // missingStr.append(" TRANSFER_DST"); //if (!(fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) // missingStr.append(" SAMPLED_IMAGE"); //cemuLog_log(LogType::Force, "{}", missingStr.c_str()); } } } bool VulkanRenderer::ImguiBegin(bool mainWindow) { if (!Renderer::ImguiBegin(mainWindow)) return false; auto& chainInfo = GetChainInfo(mainWindow); if (!AcquireNextSwapchainImage(mainWindow)) return false; draw_endRenderPass(); m_state.currentPipeline = VK_NULL_HANDLE; ImGui_ImplVulkan_CreateFontsTexture(m_state.currentCommandBuffer); ImGui_ImplVulkan_NewFrame(m_state.currentCommandBuffer, chainInfo.m_swapchainFramebuffers[chainInfo.swapchainImageIndex], chainInfo.getExtent()); ImGui_UpdateWindowInformation(mainWindow); ImGui::NewFrame(); return true; } void VulkanRenderer::ImguiEnd() { ImGui::Render(); ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), m_state.currentCommandBuffer); vkCmdEndRenderPass(m_state.currentCommandBuffer); } ImTextureID VulkanRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) { try { std::vector <uint8> tmp(size.x * size.y * 4); for (size_t i = 0; i < data.size() / 3; ++i) { tmp[(i * 4) + 0] = data[(i * 3) + 0]; tmp[(i * 4) + 1] = data[(i * 3) + 1]; tmp[(i * 4) + 2] = data[(i * 3) + 2]; tmp[(i * 4) + 3] = 0xFF; } return (ImTextureID)ImGui_ImplVulkan_GenerateTexture(m_state.currentCommandBuffer, tmp, size); } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't generate imgui texture: {}", ex.what()); return nullptr; } } void VulkanRenderer::DeleteTexture(ImTextureID id) { WaitDeviceIdle(); ImGui_ImplVulkan_DeleteTexture(id); } void VulkanRenderer::DeleteFontTextures() { WaitDeviceIdle(); ImGui_ImplVulkan_DestroyFontsTexture(); } bool VulkanRenderer::BeginFrame(bool mainWindow) { if (!AcquireNextSwapchainImage(mainWindow)) return false; auto& chainInfo = GetChainInfo(mainWindow); VkClearColorValue clearColor{ 0, 0, 0, 0 }; ClearColorImageRaw(chainInfo.m_swapchainImages[chainInfo.swapchainImageIndex], 0, 0, clearColor, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); // mark current swapchain image as well defined chainInfo.hasDefinedSwapchainImage = true; return true; } void VulkanRenderer::DrawEmptyFrame(bool mainWindow) { if (!BeginFrame(mainWindow)) return; SwapBuffers(mainWindow, !mainWindow); } void VulkanRenderer::InitFirstCommandBuffer() { cemu_assert_debug(m_state.currentCommandBuffer == nullptr); // m_commandBufferIndex always points to the currently used command buffer, so we set it to 0 m_commandBufferIndex = 0; m_commandBufferSyncIndex = 0; m_state.currentCommandBuffer = m_commandBuffers[m_commandBufferIndex]; vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]); VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo); vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &m_state.currentScissorRect); m_state.resetCommandBufferState(); } void VulkanRenderer::ProcessFinishedCommandBuffers() { bool finishedCmdBuffers = false; while (m_commandBufferSyncIndex != m_commandBufferIndex) { VkResult fenceStatus = vkGetFenceStatus(m_logicalDevice, m_cmd_buffer_fences[m_commandBufferSyncIndex]); if (fenceStatus == VK_SUCCESS) { ProcessDestructionQueue(); m_uniformVarBufferReadIndex = m_cmdBufferUniformRingbufIndices[m_commandBufferSyncIndex]; m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size(); memoryManager->cleanupBuffers(m_countCommandBufferFinished); m_countCommandBufferFinished++; finishedCmdBuffers = true; continue; } else if (fenceStatus == VK_NOT_READY) { // not signaled break; } cemuLog_log(LogType::Force, "vkGetFenceStatus returned unexpected error {}", (sint32)fenceStatus); cemu_assert_debug(false); } if (finishedCmdBuffers) { LatteTextureReadback_UpdateFinishedTransfers(false); } } void VulkanRenderer::WaitForNextFinishedCommandBuffer() { cemu_assert_debug(m_commandBufferSyncIndex != m_commandBufferIndex); // wait on least recently submitted command buffer VkResult result = vkWaitForFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferSyncIndex], true, UINT64_MAX); if (result == VK_TIMEOUT) { cemuLog_log(LogType::Force, "vkWaitForFences: Returned VK_TIMEOUT on infinite fence"); } else if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "vkWaitForFences: Returned unhandled error {}", (sint32)result); } // process ProcessFinishedCommandBuffers(); } void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphore waitSemaphore) { draw_endRenderPass(); occlusionQuery_notifyEndCommandBuffer(); vkEndCommandBuffer(m_state.currentCommandBuffer); VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &m_state.currentCommandBuffer; // signal current command buffer semaphore VkSemaphore signalSemArray[2]; if (signalSemaphore != VK_NULL_HANDLE) { submitInfo.signalSemaphoreCount = 2; signalSemArray[0] = m_commandBufferSemaphores[m_commandBufferIndex]; // signal current signalSemArray[1] = signalSemaphore; // signal current submitInfo.pSignalSemaphores = signalSemArray; } else { submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = &m_commandBufferSemaphores[m_commandBufferIndex]; // signal current } // wait for previous command buffer semaphore VkSemaphore prevSem = GetLastSubmittedCmdBufferSemaphore(); const VkPipelineStageFlags semWaitStageMask[2] = { VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT }; VkSemaphore waitSemArray[2]; submitInfo.waitSemaphoreCount = 0; if (m_numSubmittedCmdBuffers > 0) waitSemArray[submitInfo.waitSemaphoreCount++] = prevSem; // wait on semaphore from previous submit if (waitSemaphore != VK_NULL_HANDLE) waitSemArray[submitInfo.waitSemaphoreCount++] = waitSemaphore; submitInfo.pWaitDstStageMask = semWaitStageMask; submitInfo.pWaitSemaphores = waitSemArray; const VkResult result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, m_cmd_buffer_fences[m_commandBufferIndex]); if (result != VK_SUCCESS) UnrecoverableError(fmt::format("failed to submit command buffer. Error {}", result).c_str()); m_numSubmittedCmdBuffers++; // check if any previously submitted command buffers have finished execution ProcessFinishedCommandBuffers(); // acquire next command buffer auto nextCmdBufferIndex = (m_commandBufferIndex + 1) % m_commandBuffers.size(); if (nextCmdBufferIndex == m_commandBufferSyncIndex) { // force wait for the next command buffer cemuLog_logDebug(LogType::Force, "Vulkan: Waiting for available command buffer..."); WaitForNextFinishedCommandBuffer(); } m_cmdBufferUniformRingbufIndices[nextCmdBufferIndex] = m_cmdBufferUniformRingbufIndices[m_commandBufferIndex]; m_commandBufferIndex = nextCmdBufferIndex; m_state.currentCommandBuffer = m_commandBuffers[m_commandBufferIndex]; vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]); vkResetCommandBuffer(m_state.currentCommandBuffer, 0); VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo); // make sure some states are set for this command buffer vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &m_state.currentScissorRect); // DEBUG //debug_genericBarrier(); // reset states which are bound to a command buffer m_state.resetCommandBufferState(); occlusionQuery_notifyBeginCommandBuffer(); m_recordedDrawcalls = 0; m_submitThreshold = 300; m_submitOnIdle = false; } // submit within next 10 drawcalls void VulkanRenderer::RequestSubmitSoon() { m_submitThreshold = std::min(m_submitThreshold, m_recordedDrawcalls + 10); } // command buffer will be submitted when GPU has no more commands to process or when threshold is reached void VulkanRenderer::RequestSubmitOnIdle() { m_submitOnIdle = true; } uint64 VulkanRenderer::GetCurrentCommandBufferId() const { return m_numSubmittedCmdBuffers; } bool VulkanRenderer::HasCommandBufferFinished(uint64 commandBufferId) const { return m_countCommandBufferFinished > commandBufferId; } void VulkanRenderer::WaitCommandBufferFinished(uint64 commandBufferId) { if (commandBufferId == m_numSubmittedCmdBuffers) SubmitCommandBuffer(); while (HasCommandBufferFinished(commandBufferId) == false) WaitForNextFinishedCommandBuffer(); } void VulkanRenderer::PipelineCacheSaveThread(size_t cache_size) { SetThreadName("vkDriverPlCache"); const auto dir = ActiveSettings::GetCachePath("shaderCache/driver/vk"); if (!fs::exists(dir)) { try { fs::create_directories(dir); } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't create vulkan pipeline cache directory \"{}\": {}", _pathToUtf8(dir), ex.what()); return; } } const auto filename = dir / fmt::format(L"{:016x}.bin", CafeSystem::GetForegroundTitleId()); while (true) { if (m_destructionRequested) return; m_pipeline_cache_semaphore.wait(); if (m_destructionRequested) return; for (sint32 i = 0; i < 15 * 4; i++) { if (m_destructionRequested) return; std::this_thread::sleep_for(std::chrono::milliseconds(250)); } // always prioritize the compiler threads over this thread // avoid calling stalling lock() since it will block other threads from entering even when the lock is currently held in shared mode while (!m_pipeline_cache_save_mutex.try_lock()) std::this_thread::sleep_for(std::chrono::milliseconds(250)); size_t size = 0; VkResult res = vkGetPipelineCacheData(m_logicalDevice, m_pipeline_cache, &size, nullptr); if (res == VK_SUCCESS && size > 0 && size != cache_size) { std::vector<uint8_t> cacheData(size); res = vkGetPipelineCacheData(m_logicalDevice, m_pipeline_cache, &size, cacheData.data()); m_pipeline_cache_semaphore.reset(); m_pipeline_cache_save_mutex.unlock(); if (res == VK_SUCCESS) { auto file = std::ofstream(filename, std::ios::out | std::ios::binary); if (file.is_open()) { file.write((char*)cacheData.data(), cacheData.size()); file.close(); cache_size = size; cemuLog_logDebug(LogType::Force, "pipeline cache saved"); } else { cemuLog_log(LogType::Force, "can't write pipeline cache to disk"); } } else { cemuLog_log(LogType::Force, "can't retrieve pipeline cache data: 0x{:x}", res); } } else { m_pipeline_cache_semaphore.reset(); m_pipeline_cache_save_mutex.unlock(); } } } void VulkanRenderer::CreatePipelineCache() { std::vector<uint8_t> cacheData; const auto dir = ActiveSettings::GetCachePath("shaderCache/driver/vk"); if (fs::exists(dir)) { const auto filename = dir / fmt::format("{:016x}.bin", CafeSystem::GetForegroundTitleId()); auto file = std::ifstream(filename, std::ios::in | std::ios::binary | std::ios::ate); if (file.is_open()) { const size_t fileSize = file.tellg(); file.seekg(0, std::ifstream::beg); cacheData.resize(fileSize); file.read((char*)cacheData.data(), cacheData.size()); file.close(); } } VkPipelineCacheCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; createInfo.initialDataSize = cacheData.size(); createInfo.pInitialData = cacheData.data(); VkResult result = vkCreatePipelineCache(m_logicalDevice, &createInfo, nullptr, &m_pipeline_cache); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to open Vulkan pipeline cache: {}", result); // unable to load the existing cache, start with an empty cache instead createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; createInfo.initialDataSize = 0; createInfo.pInitialData = nullptr; result = vkCreatePipelineCache(m_logicalDevice, &createInfo, nullptr, &m_pipeline_cache); if (result != VK_SUCCESS) UnrecoverableError(fmt::format("Failed to create new Vulkan pipeline cache: {}", result).c_str()); } size_t cache_size = 0; vkGetPipelineCacheData(m_logicalDevice, m_pipeline_cache, &cache_size, nullptr); m_pipeline_cache_save_thread = std::thread(&VulkanRenderer::PipelineCacheSaveThread, this, cache_size); } void VulkanRenderer::swapchain_createDescriptorSetLayout() { VkDescriptorSetLayoutBinding bindings[2]{}; VkDescriptorSetLayoutBinding& samplerLayoutBinding = bindings[0]; samplerLayoutBinding.binding = 0; samplerLayoutBinding.descriptorCount = 1; samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; samplerLayoutBinding.pImmutableSamplers = nullptr; samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; VkDescriptorSetLayoutBinding& uniformBufferBinding = bindings[1]; uniformBufferBinding.binding = 1; uniformBufferBinding.descriptorCount = 1; uniformBufferBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; uniformBufferBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layoutInfo.bindingCount = std::size(bindings); layoutInfo.pBindings = bindings; if (vkCreateDescriptorSetLayout(m_logicalDevice, &layoutInfo, nullptr, &m_swapchainDescriptorSetLayout) != VK_SUCCESS) UnrecoverableError("failed to create descriptor set layout for swapchain"); } void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, sint32 width, sint32 height, FormatInfoVK* formatInfoOut) { formatInfoOut->texelCountX = width; formatInfoOut->texelCountY = height; formatInfoOut->isCompressed = false; if (isDepth) { switch (format) { case Latte::E_GX2SURFFMT::D24_S8_UNORM: if (m_supportedFormatInfo.fmt_d24_unorm_s8_uint == false) { formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT_S8_UINT; formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; formatInfoOut->decoder = TextureDecoder_NullData64::getInstance(); } else { formatInfoOut->vkImageFormat = VK_FORMAT_D24_UNORM_S8_UINT; formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; formatInfoOut->decoder = TextureDecoder_D24_S8::getInstance(); } break; case Latte::E_GX2SURFFMT::D24_S8_FLOAT: // alternative format formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT_S8_UINT; formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; formatInfoOut->decoder = TextureDecoder_NullData64::getInstance(); break; case Latte::E_GX2SURFFMT::D32_FLOAT: formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT; formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT; formatInfoOut->decoder = TextureDecoder_R32_FLOAT::getInstance(); break; case Latte::E_GX2SURFFMT::D16_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_D16_UNORM; formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT; formatInfoOut->decoder = TextureDecoder_R16_UNORM::getInstance(); break; case Latte::E_GX2SURFFMT::D32_S8_FLOAT: formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT_S8_UINT; formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; formatInfoOut->decoder = TextureDecoder_D32_S8_UINT_X24::getInstance(); break; default: cemuLog_log(LogType::Force, "Unsupported depth texture format {:04x}", (uint32)format); // default to placeholder format formatInfoOut->vkImageFormat = VK_FORMAT_D16_UNORM; formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT; formatInfoOut->decoder = nullptr; break; } } else { formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_COLOR_BIT; if(format == (Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT | Latte::E_GX2SURFFMT::FMT_BIT_SRGB)) // Seen in Sonic Transformed level Starry Speedway. SRGB should just be ignored for native float formats? format = Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT; switch (format) { // RGBA formats case Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT: formatInfoOut->vkImageFormat = VK_FORMAT_R32G32B32A32_SFLOAT; formatInfoOut->decoder = TextureDecoder_R32_G32_B32_A32_FLOAT::getInstance(); break; case Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT: formatInfoOut->vkImageFormat = VK_FORMAT_R32G32B32A32_UINT; formatInfoOut->decoder = TextureDecoder_R32_G32_B32_A32_UINT::getInstance(); break; case Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT: formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; formatInfoOut->decoder = TextureDecoder_R16_G16_B16_A16_FLOAT::getInstance(); break; case Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT: formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_UINT; formatInfoOut->decoder = TextureDecoder_R16_G16_B16_A16_UINT::getInstance(); break; case Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_UNORM; formatInfoOut->decoder = TextureDecoder_R16_G16_B16_A16::getInstance(); break; case Latte::E_GX2SURFFMT::R16_G16_B16_A16_SNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SNORM; formatInfoOut->decoder = TextureDecoder_R16_G16_B16_A16::getInstance(); break; case Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM; formatInfoOut->decoder = TextureDecoder_R8_G8_B8_A8::getInstance(); break; case Latte::E_GX2SURFFMT::R8_G8_B8_A8_SNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_SNORM; formatInfoOut->decoder = TextureDecoder_R8_G8_B8_A8::getInstance(); break; case Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB: formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_SRGB; formatInfoOut->decoder = TextureDecoder_R8_G8_B8_A8::getInstance(); break; case Latte::E_GX2SURFFMT::R8_G8_B8_A8_UINT: formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UINT; formatInfoOut->decoder = TextureDecoder_R8_G8_B8_A8::getInstance(); break; case Latte::E_GX2SURFFMT::R8_G8_B8_A8_SINT: formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_SINT; formatInfoOut->decoder = TextureDecoder_R8_G8_B8_A8::getInstance(); break; // RG formats case Latte::E_GX2SURFFMT::R32_G32_FLOAT: formatInfoOut->vkImageFormat = VK_FORMAT_R32G32_SFLOAT; formatInfoOut->decoder = TextureDecoder_R32_G32_FLOAT::getInstance(); break; case Latte::E_GX2SURFFMT::R32_G32_UINT: formatInfoOut->vkImageFormat = VK_FORMAT_R32G32_UINT; formatInfoOut->decoder = TextureDecoder_R32_G32_UINT::getInstance(); break; case Latte::E_GX2SURFFMT::R16_G16_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R16G16_UNORM; formatInfoOut->decoder = TextureDecoder_R16_G16::getInstance(); break; case Latte::E_GX2SURFFMT::R16_G16_FLOAT: formatInfoOut->vkImageFormat = VK_FORMAT_R16G16_SFLOAT; formatInfoOut->decoder = TextureDecoder_R16_G16_FLOAT::getInstance(); break; case Latte::E_GX2SURFFMT::R8_G8_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R8G8_UNORM; formatInfoOut->decoder = TextureDecoder_R8_G8::getInstance(); break; case Latte::E_GX2SURFFMT::R8_G8_SNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R8G8_SNORM; formatInfoOut->decoder = TextureDecoder_R8_G8::getInstance(); break; case Latte::E_GX2SURFFMT::R4_G4_UNORM: if (m_supportedFormatInfo.fmt_r4g4_unorm_pack == false) { if (m_supportedFormatInfo.fmt_r4g4b4a4_unorm_pack == false) { formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM; formatInfoOut->decoder = TextureDecoder_R4G4_UNORM_To_RGBA8::getInstance(); } else { formatInfoOut->vkImageFormat = VK_FORMAT_R4G4B4A4_UNORM_PACK16; formatInfoOut->decoder = TextureDecoder_R4_G4_UNORM_To_ABGR4::getInstance(); } } else { formatInfoOut->vkImageFormat = VK_FORMAT_R4G4_UNORM_PACK8; formatInfoOut->decoder = TextureDecoder_R4_G4::getInstance(); } break; // R formats case Latte::E_GX2SURFFMT::R32_FLOAT: formatInfoOut->vkImageFormat = VK_FORMAT_R32_SFLOAT; formatInfoOut->decoder = TextureDecoder_R32_FLOAT::getInstance(); break; case Latte::E_GX2SURFFMT::R32_UINT: formatInfoOut->vkImageFormat = VK_FORMAT_R32_UINT; formatInfoOut->decoder = TextureDecoder_R32_UINT::getInstance(); break; case Latte::E_GX2SURFFMT::R16_FLOAT: formatInfoOut->vkImageFormat = VK_FORMAT_R16_SFLOAT; formatInfoOut->decoder = TextureDecoder_R16_FLOAT::getInstance(); break; case Latte::E_GX2SURFFMT::R16_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R16_UNORM; formatInfoOut->decoder = TextureDecoder_R16_UNORM::getInstance(); break; case Latte::E_GX2SURFFMT::R16_SNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R16_SNORM; formatInfoOut->decoder = TextureDecoder_R16_SNORM::getInstance(); break; case Latte::E_GX2SURFFMT::R16_UINT: formatInfoOut->vkImageFormat = VK_FORMAT_R16_UINT; formatInfoOut->decoder = TextureDecoder_R16_UINT::getInstance(); break; case Latte::E_GX2SURFFMT::R8_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R8_UNORM; formatInfoOut->decoder = TextureDecoder_R8::getInstance(); break; case Latte::E_GX2SURFFMT::R8_SNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R8_SNORM; formatInfoOut->decoder = TextureDecoder_R8::getInstance(); break; case Latte::E_GX2SURFFMT::R8_UINT: formatInfoOut->vkImageFormat = VK_FORMAT_R8_UINT; formatInfoOut->decoder = TextureDecoder_R8_UINT::getInstance(); break; // special formats case Latte::E_GX2SURFFMT::R5_G6_B5_UNORM: if (m_supportedFormatInfo.fmt_r5g6b5_unorm_pack == false) { formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM; formatInfoOut->decoder = TextureDecoder_R5G6B5_UNORM_To_RGBA8::getInstance(); } else { // Vulkan has R in MSB, GPU7 has it in LSB formatInfoOut->vkImageFormat = VK_FORMAT_R5G6B5_UNORM_PACK16; formatInfoOut->decoder = TextureDecoder_R5_G6_B5_swappedRB::getInstance(); } break; case Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM: if (m_supportedFormatInfo.fmt_a1r5g5b5_unorm_pack == false) { formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM; formatInfoOut->decoder = TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB_To_RGBA8::getInstance(); } else { // used in Super Mario 3D World for the hidden Luigi sprites // since order of channels is reversed in Vulkan compared to GX2 the format we need is A1B5G5R5 formatInfoOut->vkImageFormat = VK_FORMAT_A1R5G5B5_UNORM_PACK16; formatInfoOut->decoder = TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB::getInstance(); } break; case Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM: if (m_supportedFormatInfo.fmt_a1r5g5b5_unorm_pack == false) { formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM; formatInfoOut->decoder = TextureDecoder_A1_B5_G5_R5_UNORM_vulkan_To_RGBA8::getInstance(); } else { // used by VC64 (e.g. Ocarina of Time) formatInfoOut->vkImageFormat = VK_FORMAT_A1R5G5B5_UNORM_PACK16; // A 15 R 10..14, G 5..9 B 0..4 formatInfoOut->decoder = TextureDecoder_A1_B5_G5_R5_UNORM_vulkan::getInstance(); } break; case Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT: formatInfoOut->vkImageFormat = VK_FORMAT_B10G11R11_UFLOAT_PACK32; // verify if order of channels is still the same as GX2 formatInfoOut->decoder = TextureDecoder_R11_G11_B10_FLOAT::getInstance(); break; case Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM: if (m_supportedFormatInfo.fmt_r4g4b4a4_unorm_pack == false) { formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM; formatInfoOut->decoder = TextureDecoder_R4G4B4A4_UNORM_To_RGBA8::getInstance(); } else { formatInfoOut->vkImageFormat = VK_FORMAT_R4G4B4A4_UNORM_PACK16; formatInfoOut->decoder = TextureDecoder_R4_G4_B4_A4_UNORM::getInstance(); } break; // special formats - R10G10B10_A2 case Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; // todo - verify formatInfoOut->decoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance(); break; case Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SNORM; // Vulkan has VK_FORMAT_A2R10G10B10_SNORM_PACK32 but it doesnt work? formatInfoOut->decoder = TextureDecoder_R10_G10_B10_A2_SNORM_To_RGBA16::getInstance(); break; case Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB: //formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SNORM; // Vulkan has no uncompressed SRGB format with more than 8 bits per channel //formatInfoOut->decoder = TextureDecoder_R10_G10_B10_A2_SNORM_To_RGBA16::getInstance(); //break; formatInfoOut->vkImageFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; // todo - verify formatInfoOut->decoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance(); break; // compressed formats case Latte::E_GX2SURFFMT::BC1_SRGB: formatInfoOut->vkImageFormat = VK_FORMAT_BC1_RGBA_SRGB_BLOCK; // todo - verify formatInfoOut->decoder = TextureDecoder_BC1::getInstance(); break; case Latte::E_GX2SURFFMT::BC1_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_BC1_RGBA_UNORM_BLOCK; // todo - verify formatInfoOut->decoder = TextureDecoder_BC1::getInstance(); break; case Latte::E_GX2SURFFMT::BC2_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_BC2_UNORM_BLOCK; // todo - verify formatInfoOut->decoder = TextureDecoder_BC2::getInstance(); break; case Latte::E_GX2SURFFMT::BC2_SRGB: formatInfoOut->vkImageFormat = VK_FORMAT_BC2_SRGB_BLOCK; // todo - verify formatInfoOut->decoder = TextureDecoder_BC2::getInstance(); break; case Latte::E_GX2SURFFMT::BC3_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_BC3_UNORM_BLOCK; formatInfoOut->decoder = TextureDecoder_BC3::getInstance(); break; case Latte::E_GX2SURFFMT::BC3_SRGB: formatInfoOut->vkImageFormat = VK_FORMAT_BC3_SRGB_BLOCK; formatInfoOut->decoder = TextureDecoder_BC3::getInstance(); break; case Latte::E_GX2SURFFMT::BC4_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_BC4_UNORM_BLOCK; formatInfoOut->decoder = TextureDecoder_BC4::getInstance(); break; case Latte::E_GX2SURFFMT::BC4_SNORM: formatInfoOut->vkImageFormat = VK_FORMAT_BC4_SNORM_BLOCK; formatInfoOut->decoder = TextureDecoder_BC4::getInstance(); break; case Latte::E_GX2SURFFMT::BC5_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_BC5_UNORM_BLOCK; formatInfoOut->decoder = TextureDecoder_BC5::getInstance(); break; case Latte::E_GX2SURFFMT::BC5_SNORM: formatInfoOut->vkImageFormat = VK_FORMAT_BC5_SNORM_BLOCK; formatInfoOut->decoder = TextureDecoder_BC5::getInstance(); break; case Latte::E_GX2SURFFMT::R24_X8_UNORM: formatInfoOut->vkImageFormat = VK_FORMAT_R32_SFLOAT; formatInfoOut->decoder = TextureDecoder_R24_X8::getInstance(); break; case Latte::E_GX2SURFFMT::X24_G8_UINT: // used by Color Splash and Resident Evil formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UINT; // todo - should we use ABGR format? formatInfoOut->decoder = TextureDecoder_X24_G8_UINT::getInstance(); // todo - verify case Latte::E_GX2SURFFMT::R32_X8_FLOAT: // seen in Disney Infinity 3.0 formatInfoOut->vkImageFormat = VK_FORMAT_R32_SFLOAT; formatInfoOut->decoder = TextureDecoder_NullData64::getInstance(); break; default: cemuLog_log(LogType::Force, "Unsupported color texture format {:04x}", (uint32)format); cemu_assert_debug(false); } } } VkPipelineShaderStageCreateInfo VulkanRenderer::CreatePipelineShaderStageCreateInfo(VkShaderStageFlagBits stage, VkShaderModule& module, const char* entryName) const { VkPipelineShaderStageCreateInfo shaderStageInfo{}; shaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shaderStageInfo.stage = stage; shaderStageInfo.module = module; shaderStageInfo.pName = entryName; return shaderStageInfo; } VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSetLayout descriptorLayout, bool padView, RendererOutputShader* shader) { auto& chainInfo = GetChainInfo(!padView); RendererShaderVk* vertexRendererShader = static_cast<RendererShaderVk*>(shader->GetVertexShader()); RendererShaderVk* fragmentRendererShader = static_cast<RendererShaderVk*>(shader->GetFragmentShader()); uint64 hash = 0; hash += (uint64)vertexRendererShader; hash += (uint64)fragmentRendererShader; hash += ((uint64)padView) << 1; const auto it = m_backbufferBlitPipelineCache.find(hash); if (it != m_backbufferBlitPipelineCache.cend()) return it->second; std::vector<VkPipelineShaderStageCreateInfo> shaderStages; if (vertexRendererShader) shaderStages.emplace_back(CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_VERTEX_BIT, vertexRendererShader->GetShaderModule(), "main")); if (fragmentRendererShader) shaderStages.emplace_back(CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentRendererShader->GetShaderModule(), "main")); VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.vertexAttributeDescriptionCount = 0; VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; inputAssembly.primitiveRestartEnable = VK_FALSE; VkPipelineViewportStateCreateInfo viewportState{}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.scissorCount = 1; VkDynamicState dynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicState = {}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.dynamicStateCount = std::size(dynamicStates); dynamicState.pDynamicStates = dynamicStates; VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; rasterizer.polygonMode = VK_POLYGON_MODE_FILL; rasterizer.lineWidth = 1.0f; rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; VkPipelineColorBlendAttachmentState colorBlendAttachment{}; colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; colorBlendAttachment.blendEnable = VK_FALSE; VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = VK_LOGIC_OP_COPY; colorBlending.attachmentCount = 1; colorBlending.pAttachments = &colorBlendAttachment; colorBlending.blendConstants[0] = 0.0f; colorBlending.blendConstants[1] = 0.0f; colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &descriptorLayout; VkResult result; if (m_pipelineLayout == VK_NULL_HANDLE) { result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipelineLayout); if (result != VK_SUCCESS) throw std::runtime_error(fmt::format("Failed to create pipeline layout: {}", result)); } VkGraphicsPipelineCreateInfo pipelineInfo = {}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = shaderStages.size(); pipelineInfo.pStages = shaderStages.data(); pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pInputAssemblyState = &inputAssembly; pipelineInfo.pViewportState = &viewportState; pipelineInfo.pDynamicState = &dynamicState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; pipelineInfo.layout = m_pipelineLayout; pipelineInfo.renderPass = chainInfo.m_swapchainRenderPass; pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; VkPipeline pipeline = nullptr; std::shared_lock lock(m_pipeline_cache_save_mutex); result = vkCreateGraphicsPipelines(m_logicalDevice, m_pipeline_cache, 1, &pipelineInfo, nullptr, &pipeline); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create graphics pipeline. Error {}", result); throw std::runtime_error(fmt::format("Failed to create graphics pipeline: {}", result)); } m_backbufferBlitPipelineCache[hash] = pipeline; m_pipeline_cache_semaphore.notify(); return pipeline; } bool VulkanRenderer::AcquireNextSwapchainImage(bool mainWindow) { if(!IsSwapchainInfoValid(mainWindow)) return false; if(!mainWindow && m_destroyPadSwapchainNextAcquire.test()) { RecreateSwapchain(mainWindow, true); m_destroyPadSwapchainNextAcquire.clear(); m_destroyPadSwapchainNextAcquire.notify_all(); return false; } auto& chainInfo = GetChainInfo(mainWindow); if (chainInfo.swapchainImageIndex != -1) return true; // image already reserved if (!UpdateSwapchainProperties(mainWindow)) return false; bool result = chainInfo.AcquireImage(); if (!result) return false; SubmitCommandBuffer(VK_NULL_HANDLE, chainInfo.ConsumeAcquireSemaphore()); return true; } void VulkanRenderer::RecreateSwapchain(bool mainWindow, bool skipCreate) { SubmitCommandBuffer(); WaitDeviceIdle(); auto& chainInfo = GetChainInfo(mainWindow); Vector2i size; if (mainWindow) { ImGui_ImplVulkan_Shutdown(); WindowSystem::GetWindowPhysSize(size.x, size.y); } else { WindowSystem::GetPadWindowPhysSize(size.x, size.y); } chainInfo.swapchainImageIndex = -1; chainInfo.Cleanup(); chainInfo.m_desiredExtent = size; if(!skipCreate) { chainInfo.Create(); } if (mainWindow) ImguiInit(); } bool VulkanRenderer::UpdateSwapchainProperties(bool mainWindow) { auto& chainInfo = GetChainInfo(mainWindow); bool stateChanged = chainInfo.m_shouldRecreate; const auto configValue = (VSync)GetConfig().vsync.GetValue(); if(chainInfo.m_vsyncState != configValue) stateChanged = true; int width, height; if (mainWindow) WindowSystem::GetWindowPhysSize(width, height); else WindowSystem::GetPadWindowPhysSize(width, height); auto extent = chainInfo.getExtent(); if (width != extent.width || height != extent.height) stateChanged = true; if(stateChanged) { try { RecreateSwapchain(mainWindow); } catch (std::exception&) { cemu_assert_debug(false); return false; } } chainInfo.m_shouldRecreate = false; chainInfo.m_vsyncState = configValue; return true; } void VulkanRenderer::SwapBuffer(bool mainWindow) { if(!AcquireNextSwapchainImage(mainWindow)) return; auto& chainInfo = GetChainInfo(mainWindow); if (!chainInfo.hasDefinedSwapchainImage) { // set the swapchain image to a defined state VkClearColorValue clearColor{ 0, 0, 0, 0 }; ClearColorImageRaw(chainInfo.m_swapchainImages[chainInfo.swapchainImageIndex], 0, 0, clearColor, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); } const size_t currentFrameCmdBufferID = GetCurrentCommandBufferId(); VkSemaphore presentSemaphore = chainInfo.m_presentSemaphores[chainInfo.swapchainImageIndex]; SubmitCommandBuffer(presentSemaphore); // submit all command and signal semaphore cemu_assert_debug(m_numSubmittedCmdBuffers > 0); // wait for the previous frame to finish rendering WaitCommandBufferFinished(m_commandBufferIDOfPrevFrame); m_commandBufferIDOfPrevFrame = currentFrameCmdBufferID; chainInfo.WaitAvailableFence(); VkPresentIdKHR presentId = {}; VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = &chainInfo.m_swapchain; presentInfo.pImageIndices = &chainInfo.swapchainImageIndex; // wait on command buffer semaphore presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = &presentSemaphore; // if present_wait is available and enabled, add frame markers to present requests // and limit the number of queued present operations if (m_featureControl.deviceExtensions.present_wait && chainInfo.m_maxQueued > 0) { presentId.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR; presentId.swapchainCount = 1; presentId.pPresentIds = &chainInfo.m_presentId; presentInfo.pNext = &presentId; if(chainInfo.m_queueDepth >= chainInfo.m_maxQueued) { uint64 waitFrameId = chainInfo.m_presentId - chainInfo.m_queueDepth; vkWaitForPresentKHR(m_logicalDevice, chainInfo.m_swapchain, waitFrameId, 40'000'000); chainInfo.m_queueDepth--; } } VkResult result = vkQueuePresentKHR(m_presentQueue, &presentInfo); if (result < 0 && result != VK_ERROR_OUT_OF_DATE_KHR) { throw std::runtime_error(fmt::format("Failed to present image: {}", result)); } if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) chainInfo.m_shouldRecreate = true; if(result >= 0) { chainInfo.m_queueDepth++; chainInfo.m_presentId++; } chainInfo.hasDefinedSwapchainImage = false; chainInfo.swapchainImageIndex = -1; } void VulkanRenderer::Flush(bool waitIdle) { if (m_recordedDrawcalls > 0 || m_submitOnIdle) SubmitCommandBuffer(); if (waitIdle) WaitCommandBufferFinished(GetCurrentCommandBufferId()); } void VulkanRenderer::NotifyLatteCommandProcessorIdle() { if (m_submitOnIdle) SubmitCommandBuffer(); } void VulkanBenchmarkPrintResults(); void VulkanRenderer::SwapBuffers(bool swapTV, bool swapDRC) { SubmitCommandBuffer(); if (swapTV && IsSwapchainInfoValid(true)) SwapBuffer(true); if (swapDRC && IsSwapchainInfoValid(false)) SwapBuffer(false); if(swapTV) VulkanBenchmarkPrintResults(); } void VulkanRenderer::ClearColorbuffer(bool padView) { if (!IsSwapchainInfoValid(!padView)) return; auto& chainInfo = GetChainInfo(!padView); if (chainInfo.swapchainImageIndex == -1) return; VkClearColorValue clearColor{ 0, 0, 0, 0 }; ClearColorImageRaw(chainInfo.m_swapchainImages[chainInfo.swapchainImageIndex], 0, 0, clearColor, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL); } void VulkanRenderer::ClearColorImageRaw(VkImage image, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout inputLayout, VkImageLayout outputLayout) { draw_endRenderPass(); VkImageSubresourceRange subresourceRange{}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = mipIndex; subresourceRange.levelCount = 1; subresourceRange.baseArrayLayer = sliceIndex; subresourceRange.layerCount = 1; barrier_image<SYNC_OP::ANY_TRANSFER | SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE, SYNC_OP::ANY_TRANSFER>(image, subresourceRange, inputLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); vkCmdClearColorImage(m_state.currentCommandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1, &subresourceRange); barrier_image<ANY_TRANSFER, SYNC_OP::ANY_TRANSFER | SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE>(image, subresourceRange, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, outputLayout); } void VulkanRenderer::ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout outputLayout) { if(vkTexture->isDepth) { cemu_assert_suspicious(); return; } if (vkTexture->IsCompressedFormat()) { // vkCmdClearColorImage cannot be called on compressed formats // for now we ignore affected clears but still transition the image to the correct layout auto imageObj = vkTexture->GetImageObj(); imageObj->flagForCurrentCommandBuffer(); VkImageSubresourceLayers subresourceRange{}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.mipLevel = mipIndex; subresourceRange.baseArrayLayer = sliceIndex; subresourceRange.layerCount = 1; barrier_image<ANY_TRANSFER | IMAGE_READ, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, subresourceRange, outputLayout); if(color.float32[0] == 0.0f && color.float32[1] == 0.0f && color.float32[2] == 0.0f && color.float32[3] == 0.0f) { static bool dbgMsgPrinted = false; if(!dbgMsgPrinted) { cemuLog_logDebug(LogType::Force, "Unsupported compressed texture clear to zero"); dbgMsgPrinted = true; } } return; } VkImageSubresourceRange subresourceRange; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = mipIndex; subresourceRange.levelCount = 1; subresourceRange.baseArrayLayer = sliceIndex; subresourceRange.layerCount = 1; auto imageObj = vkTexture->GetImageObj(); imageObj->flagForCurrentCommandBuffer(); VkImageLayout inputLayout = vkTexture->GetImageLayout(subresourceRange); ClearColorImageRaw(imageObj->m_image, sliceIndex, mipIndex, color, inputLayout, outputLayout); vkTexture->SetImageLayout(subresourceRange, outputLayout); } void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) { if(!AcquireNextSwapchainImage(!padView)) return; auto& chainInfo = GetChainInfo(!padView); LatteTextureViewVk* texViewVk = (LatteTextureViewVk*)texView; draw_endRenderPass(); // barrier for input texture VkMemoryBarrier memoryBarrier{}; memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT; VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; memoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; memoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); auto pipeline = backbufferBlit_createGraphicsPipeline(m_swapchainDescriptorSetLayout, padView, shader); VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = chainInfo.m_swapchainRenderPass; renderPassInfo.framebuffer = chainInfo.m_swapchainFramebuffers[chainInfo.swapchainImageIndex]; renderPassInfo.renderArea.offset = { 0, 0 }; renderPassInfo.renderArea.extent = chainInfo.getExtent(); renderPassInfo.clearValueCount = 0; VkViewport viewport{}; viewport.x = imageX; viewport.y = imageY; viewport.width = imageWidth; viewport.height = imageHeight; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &viewport); VkRect2D scissor{}; scissor.extent = chainInfo.getExtent(); vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &scissor); auto descriptSet = backbufferBlit_createDescriptorSet(m_swapchainDescriptorSetLayout, texViewVk, useLinearTexFilter); vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); if (clearBackground) { VkClearAttachment clearAttachment{}; clearAttachment.clearValue = {0,0,0,0}; clearAttachment.colorAttachment = 0; clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; VkClearRect clearExtent = {{{0,0},chainInfo.m_actualExtent}, 0, 1}; vkCmdClearAttachments(m_state.currentCommandBuffer, 1, &clearAttachment, 1, &clearExtent); } vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); m_state.currentPipeline = pipeline; auto outputUniforms = shader->FillUniformBlockBuffer(*texView, {imageWidth, imageHeight}, padView); auto outputUniformOffset = uniformData_uploadUniformDataBufferGetOffset({(uint8*)&outputUniforms, sizeof(decltype(outputUniforms))}); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 1, &outputUniformOffset); vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0); vkCmdEndRenderPass(m_state.currentCommandBuffer); // restore viewport vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); // mark current swapchain image as well defined chainInfo.hasDefinedSwapchainImage = true; } void VulkanRenderer::CreateDescriptorPool() { std::array<VkDescriptorPoolSize, 4> poolSizes = {}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; poolSizes[0].descriptorCount = 1024 * 128; poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; poolSizes[1].descriptorCount = 1024 * 1; poolSizes[2].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; poolSizes[2].descriptorCount = 1024 * 128; poolSizes[3].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; poolSizes[3].descriptorCount = 1024 * 4; VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = poolSizes.size(); poolInfo.pPoolSizes = poolSizes.data(); poolInfo.maxSets = 1024 * 256; poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; if (vkCreateDescriptorPool(m_logicalDevice, &poolInfo, nullptr, &m_descriptorPool) != VK_SUCCESS) UnrecoverableError("Failed to create descriptor pool!"); } VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorSetLayout descriptor_set_layout, LatteTextureViewVk* texViewVk, bool useLinearTexFilter) { uint64 hash = 0; hash += (uint64)texViewVk->GetViewRGBA(); hash += (uint64)texViewVk->GetDefaultTextureSampler(useLinearTexFilter); const auto it = m_backbufferBlitDescriptorSetCache.find(hash); if (it != m_backbufferBlitDescriptorSetCache.cend()) return it->second; VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = m_descriptorPool; allocInfo.descriptorSetCount = 1; allocInfo.pSetLayouts = &descriptor_set_layout; VkDescriptorSet result; if (vkAllocateDescriptorSets(m_logicalDevice, &allocInfo, &result) != VK_SUCCESS) UnrecoverableError("Failed to allocate descriptor sets for backbuffer blit"); performanceMonitor.vk.numDescriptorSets.increment(); VkDescriptorImageInfo imageInfo = {}; imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; imageInfo.imageView = texViewVk->GetViewRGBA()->m_textureImageView; imageInfo.sampler = texViewVk->GetDefaultTextureSampler(useLinearTexFilter); VkWriteDescriptorSet descriptorWrites[2]{}; VkWriteDescriptorSet& samplerWrite = descriptorWrites[0]; samplerWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; samplerWrite.dstSet = result; samplerWrite.dstBinding = 0; samplerWrite.dstArrayElement = 0; samplerWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; samplerWrite.descriptorCount = 1; samplerWrite.pImageInfo = &imageInfo; VkWriteDescriptorSet& uniformBufferWrite = descriptorWrites[1]; uniformBufferWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; uniformBufferWrite.dstSet = result; uniformBufferWrite.dstBinding = 1; uniformBufferWrite.descriptorCount = 1; uniformBufferWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; VkDescriptorBufferInfo uniformBufferInfo{}; uniformBufferInfo.buffer = m_uniformVarBuffer; uniformBufferInfo.offset = 0; uniformBufferInfo.range = sizeof(RendererOutputShader::OutputUniformVariables); uniformBufferWrite.pBufferInfo = &uniformBufferInfo; vkUpdateDescriptorSets(m_logicalDevice, std::size(descriptorWrites), descriptorWrites, 0, nullptr); performanceMonitor.vk.numDescriptorSamplerTextures.increment(); m_backbufferBlitDescriptorSetCache[hash] = result; return result; } void VulkanRenderer::renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ) { // the Vulkan renderer handles halfZ in the vertex shader float vpNewX = x; float vpNewY = y + height; float vpNewWidth = width; float vpNewHeight = -height; if (m_state.currentViewport.x == vpNewX && m_state.currentViewport.y == vpNewY && m_state.currentViewport.width == vpNewWidth && m_state.currentViewport.height == vpNewHeight && m_state.currentViewport.minDepth == nearZ && m_state.currentViewport.maxDepth == farZ) return; // viewport did not change m_state.currentViewport.x = vpNewX; m_state.currentViewport.y = vpNewY; m_state.currentViewport.width = vpNewWidth; m_state.currentViewport.height = vpNewHeight; m_state.currentViewport.minDepth = nearZ; m_state.currentViewport.maxDepth = farZ; vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); } void VulkanRenderer::renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) { m_state.currentScissorRect.offset.x = scissorX; m_state.currentScissorRect.offset.y = scissorY; m_state.currentScissorRect.extent.width = scissorWidth; m_state.currentScissorRect.extent.height = scissorHeight; vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &m_state.currentScissorRect); } LatteCachedFBO* VulkanRenderer::rendertarget_createCachedFBO(uint64 key) { return new CachedFBOVk(key, m_logicalDevice); } void VulkanRenderer::rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) { if (cfbo == m_state.activeFBO) m_state.activeFBO = nullptr; } void VulkanRenderer::rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) { m_state.activeFBO = (CachedFBOVk*)cfbo; } void* VulkanRenderer::texture_acquireTextureUploadBuffer(uint32 size) { return memoryManager->TextureUploadBufferAcquire(size); } void VulkanRenderer::texture_releaseTextureUploadBuffer(uint8* mem) { memoryManager->TextureUploadBufferRelease(mem); } TextureDecoder* VulkanRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) { FormatInfoVK texFormatInfo{}; GetTextureFormatInfoVK(format, isDepth, dim, width, height, &texFormatInfo); return texFormatInfo.decoder; } void VulkanRenderer::ReleaseDestructibleObject(VKRDestructibleObject* destructibleObject) { // destroy immediately if possible if (destructibleObject->canDestroy()) { delete destructibleObject; return; } // otherwise put on queue m_spinlockDestructionQueue.lock(); m_destructionQueue.emplace_back(destructibleObject); m_spinlockDestructionQueue.unlock(); } void VulkanRenderer::ProcessDestructionQueue() { m_spinlockDestructionQueue.lock(); for (auto it = m_destructionQueue.begin(); it != m_destructionQueue.end();) { if ((*it)->canDestroy()) { delete (*it); it = m_destructionQueue.erase(it); continue; } ++it; } m_spinlockDestructionQueue.unlock(); } VkDescriptorSetInfo::~VkDescriptorSetInfo() { for (auto& it : list_referencedViews) it->RemoveDescriptorSetReference(this); // unregister switch (shaderType) { case LatteConst::ShaderType::Vertex: { auto r = pipeline_info->vertex_ds_cache.erase(stateHash); cemu_assert_debug(r == 1); break; } case LatteConst::ShaderType::Pixel: { auto r = pipeline_info->pixel_ds_cache.erase(stateHash); cemu_assert_debug(r == 1); break; } case LatteConst::ShaderType::Geometry: { auto r = pipeline_info->geometry_ds_cache.erase(stateHash); cemu_assert_debug(r == 1); break; } default: UNREACHABLE; } // update global stats performanceMonitor.vk.numDescriptorSamplerTextures.decrement(statsNumSamplerTextures); performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers); performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers); auto renderer = VulkanRenderer::GetInstance(); renderer->ReleaseDestructibleObject(m_vkObjDescriptorSet); m_vkObjDescriptorSet = nullptr; } void VulkanRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) { draw_endRenderPass(); auto vkTexture = (LatteTextureVk*)hostTexture; if (vkTexture->isDepth) texture_clearDepthSlice(hostTexture, sliceIndex, mipIndex, true, vkTexture->hasStencil, 0.0f, 0); else { cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D); ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL); } } void VulkanRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) { auto vkTexture = (LatteTextureVk*)hostTexture; if(vkTexture->dim == Latte::E_DIM::DIM_3D) { cemu_assert_unimplemented(); } ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, VK_IMAGE_LAYOUT_GENERAL); } void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) { draw_endRenderPass(); // vkCmdClearDepthStencilImage must not be inside renderpass auto vkTexture = (LatteTextureVk*)hostTexture; VkImageAspectFlags imageAspect = vkTexture->GetImageAspect(); VkImageAspectFlags aspectMask = 0; if (clearDepth && (imageAspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; if (clearStencil && (imageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; auto imageObj = vkTexture->GetImageObj(); imageObj->flagForCurrentCommandBuffer(); VkImageSubresourceLayers subresourceRange{}; subresourceRange.aspectMask = vkTexture->GetImageAspect(); subresourceRange.mipLevel = mipIndex; subresourceRange.baseArrayLayer = sliceIndex; subresourceRange.layerCount = 1; barrier_image<ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE, ANY_TRANSFER>(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL); VkClearDepthStencilValue depthStencilValue{}; depthStencilValue.depth = depthValue; depthStencilValue.stencil = stencilValue; VkImageSubresourceRange range{}; range.baseMipLevel = mipIndex; range.levelCount = 1; range.baseArrayLayer = sliceIndex; range.layerCount = 1; range.aspectMask = aspectMask; vkCmdClearDepthStencilImage(m_state.currentCommandBuffer, imageObj->m_image, VK_IMAGE_LAYOUT_GENERAL, &depthStencilValue, 1, &range); barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL); } void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) { auto vkTexture = (LatteTextureVk*)hostTexture; auto vkImageObj = vkTexture->GetImageObj(); vkImageObj->flagForCurrentCommandBuffer(); draw_endRenderPass(); VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(m_logicalDevice, vkImageObj->m_image, &memRequirements); uint32 uploadSize = compressedImageSize;// memRequirements.size; uint32 uploadAlignment = memRequirements.alignment; VKRSynchronizedRingAllocator& vkMemAllocator = memoryManager->getStagingAllocator(); auto uploadResv = vkMemAllocator.AllocateBufferMemory(uploadSize, uploadAlignment); memcpy(uploadResv.memPtr, pixelData, compressedImageSize); vkMemAllocator.FlushReservation(uploadResv); FormatInfoVK texFormatInfo; GetTextureFormatInfoVK(hostTexture->format, hostTexture->isDepth, hostTexture->dim, 0, 0, &texFormatInfo); bool is3DTexture = hostTexture->Is3DTexture(); VkImageSubresourceLayers barrierSubresourceRange{}; barrierSubresourceRange.aspectMask = texFormatInfo.vkImageAspect; barrierSubresourceRange.mipLevel = mipIndex; barrierSubresourceRange.baseArrayLayer = is3DTexture ? 0 : sliceIndex; barrierSubresourceRange.layerCount = 1; barrier_image<ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE | HOST_WRITE, ANY_TRANSFER>(vkTexture, barrierSubresourceRange, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); VkBufferImageCopy imageRegion[2]{}; sint32 imageRegionCount = 0; if (texFormatInfo.vkImageAspect == VK_IMAGE_ASPECT_COLOR_BIT || texFormatInfo.vkImageAspect == VK_IMAGE_ASPECT_DEPTH_BIT) { imageRegion[0].bufferOffset = uploadResv.bufferOffset; imageRegion[0].imageExtent.width = width; imageRegion[0].imageExtent.height = height; imageRegion[0].imageExtent.depth = 1; imageRegion[0].imageOffset.z = is3DTexture ? sliceIndex : 0; imageRegion[0].imageSubresource.mipLevel = mipIndex; imageRegion[0].imageSubresource.aspectMask = texFormatInfo.vkImageAspect; imageRegion[0].imageSubresource.baseArrayLayer = is3DTexture ? 0 : sliceIndex; imageRegion[0].imageSubresource.layerCount = 1; imageRegionCount = 1; } else if (texFormatInfo.vkImageAspect == VK_IMAGE_ASPECT_DEPTH_BIT) { if (is3DTexture) cemu_assert_debug(false); // depth only copy imageRegion[0].bufferOffset = uploadResv.bufferOffset; imageRegion[0].imageExtent.width = width; imageRegion[0].imageExtent.height = height; imageRegion[0].imageExtent.depth = 1; imageRegion[0].imageSubresource.mipLevel = mipIndex; imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; imageRegion[0].imageSubresource.baseArrayLayer = sliceIndex; imageRegion[0].imageSubresource.layerCount = 1; imageRegionCount = 1; } else if (texFormatInfo.vkImageAspect == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { if (is3DTexture) cemu_assert_debug(false); // depth copy imageRegion[0].bufferOffset = uploadResv.bufferOffset; imageRegion[0].imageExtent.width = width; imageRegion[0].imageExtent.height = height; imageRegion[0].imageExtent.depth = 1; imageRegion[0].imageSubresource.mipLevel = mipIndex; imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; imageRegion[0].imageSubresource.baseArrayLayer = sliceIndex; imageRegion[0].imageSubresource.layerCount = 1; // stencil copy imageRegion[1].bufferOffset = uploadResv.bufferOffset; imageRegion[1].imageExtent.width = width; imageRegion[1].imageExtent.height = height; imageRegion[1].imageExtent.depth = 1; imageRegion[1].imageSubresource.mipLevel = mipIndex; imageRegion[1].imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; imageRegion[1].imageSubresource.baseArrayLayer = sliceIndex; imageRegion[1].imageSubresource.layerCount = 1; imageRegionCount = 2; } else cemu_assert_debug(false); vkCmdCopyBufferToImage(m_state.currentCommandBuffer, uploadResv.vkBuffer, vkImageObj->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion); barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, barrierSubresourceRange, VK_IMAGE_LAYOUT_GENERAL); } LatteTexture* VulkanRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) { return new LatteTextureVk(this, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); } void VulkanRenderer::texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) { m_state.boundTexture[textureUnit] = static_cast<LatteTextureViewVk*>(textureView); } void VulkanRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) { LatteTextureVk* srcVk = static_cast<LatteTextureVk*>(src); LatteTextureVk* dstVk = static_cast<LatteTextureVk*>(dst); draw_endRenderPass(); // vkCmdCopyImage must be called outside of a renderpass VKRObjectTexture* srcVkObj = srcVk->GetImageObj(); VKRObjectTexture* dstVkObj = dstVk->GetImageObj(); srcVkObj->flagForCurrentCommandBuffer(); dstVkObj->flagForCurrentCommandBuffer(); VkImageCopy region{}; region.srcOffset.x = effectiveSrcX; region.srcOffset.y = effectiveSrcY; region.dstOffset.x = effectiveDstX; region.dstOffset.y = effectiveDstY; region.extent.width = effectiveCopyWidth; region.extent.height = effectiveCopyHeight; region.extent.depth = 1; if (src->Is3DTexture()) { region.srcOffset.z = srcSlice; region.extent.depth = srcDepth; region.srcSubresource.baseArrayLayer = 0; region.srcSubresource.layerCount = 1; } else { region.srcOffset.z = 0; region.extent.depth = 1; region.srcSubresource.baseArrayLayer = srcSlice; region.srcSubresource.layerCount = srcDepth; } if (dst->Is3DTexture()) { region.dstOffset.z = dstSlice; region.dstSubresource.baseArrayLayer = 0; region.dstSubresource.layerCount = 1; } else { region.dstOffset.z = 0; region.dstSubresource.baseArrayLayer = dstSlice; region.dstSubresource.layerCount = srcDepth; } region.srcSubresource.mipLevel = srcMip; region.srcSubresource.aspectMask = srcVk->GetImageAspect(); region.dstSubresource.mipLevel = dstMip; region.dstSubresource.aspectMask = dstVk->GetImageAspect(); bool srcIsCompressed = Latte::IsCompressedFormat(srcVk->format); bool dstIsCompressed = Latte::IsCompressedFormat(dstVk->format); if (!srcIsCompressed && dstIsCompressed) { // handle the special case where the destination is compressed and not a multiple of the texel size (4) sint32 mipWidth = std::max(dst->width >> dstMip, 1); sint32 mipHeight = std::max(dst->height >> dstMip, 1); if (mipWidth < 4 || mipHeight < 4) { cemuLog_logDebug(LogType::Force, "vkCmdCopyImage - blocked copy for unsupported uncompressed->compressed copy with dst smaller than 4x4"); return; } } // make sure all write operations to the src image have finished barrier_image<SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER>(srcVk, region.srcSubresource, VK_IMAGE_LAYOUT_GENERAL); // make sure all read and write operations to the dst image have finished barrier_image<SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER>(dstVk, region.dstSubresource, VK_IMAGE_LAYOUT_GENERAL); vkCmdCopyImage(m_state.currentCommandBuffer, srcVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, dstVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); // make sure the transfer is finished before the image is read or written barrier_image<SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstVk, region.dstSubresource, VK_IMAGE_LAYOUT_GENERAL); } LatteTextureReadbackInfo* VulkanRenderer::texture_createReadback(LatteTextureView* textureView) { auto* result = new LatteTextureReadbackInfoVk(m_logicalDevice, textureView); LatteTextureVk* vkTex = (LatteTextureVk*)textureView->baseTexture; VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(m_logicalDevice, vkTex->GetImageObj()->m_image, &memRequirements); const uint32 linearImageSize = result->GetImageSize(); const uint32 uploadSize = (linearImageSize == 0) ? memRequirements.size : linearImageSize; const uint32 uploadAlignment = 256; // todo - use Vk optimalBufferCopyOffsetAlignment m_textureReadbackBufferWriteIndex = (m_textureReadbackBufferWriteIndex + uploadAlignment - 1) & ~(uploadAlignment - 1); if ((m_textureReadbackBufferWriteIndex + uploadSize + 256) > TEXTURE_READBACK_SIZE) { m_textureReadbackBufferWriteIndex = 0; } const uint32 uploadBufferOffset = m_textureReadbackBufferWriteIndex; m_textureReadbackBufferWriteIndex += uploadSize; result->SetBuffer(m_textureReadbackBuffer, m_textureReadbackBufferPtr, uploadBufferOffset); return result; } uint32 s_vkCurrentUniqueId = 0; uint64 VulkanRenderer::GenUniqueId() { s_vkCurrentUniqueId++; return s_vkCurrentUniqueId; } void VulkanRenderer::streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) { VkDeviceSize tfBufferOffset = ringBufferOffset; m_streamoutState.buffer[bufferIndex].enabled = true; m_streamoutState.buffer[bufferIndex].ringBufferOffset = ringBufferOffset; } void VulkanRenderer::streamout_begin() { if (m_featureControl.mode.useTFEmulationViaSSBO) return; if (m_state.hasActiveXfb == false) m_state.hasActiveXfb = true; } void VulkanRenderer::streamout_applyTransformFeedbackState() { if (m_featureControl.mode.useTFEmulationViaSSBO) return; cemu_assert_debug(m_state.hasActiveXfb == false); if (m_state.hasActiveXfb) { // set buffers for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) { if (m_streamoutState.buffer[i].enabled) { VkBuffer tfBuffer = m_xfbRingBuffer; VkDeviceSize tfBufferOffset = m_streamoutState.buffer[i].ringBufferOffset; VkDeviceSize tfBufferSize = VK_WHOLE_SIZE; vkCmdBindTransformFeedbackBuffersEXT(m_state.currentCommandBuffer, i, 1, &tfBuffer, &tfBufferOffset, &tfBufferSize); } } // begin transform feedback vkCmdBeginTransformFeedbackEXT(m_state.currentCommandBuffer, 0, 0, nullptr, nullptr); } } void VulkanRenderer::streamout_rendererFinishDrawcall() { if (m_state.hasActiveXfb) { vkCmdEndTransformFeedbackEXT(m_state.currentCommandBuffer, 0, 0, nullptr, nullptr); m_streamoutState.buffer[0].enabled = false; m_streamoutState.buffer[1].enabled = false; m_streamoutState.buffer[2].enabled = false; m_streamoutState.buffer[3].enabled = false; m_state.hasActiveXfb = false; } } void VulkanRenderer::buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) { cemu_assert_debug(!m_useHostMemoryForCache); if (m_state.currentVertexBinding[bufferIndex].offset == offset) return; cemu_assert_debug(bufferIndex < LATTE_MAX_VERTEX_BUFFERS); m_state.currentVertexBinding[bufferIndex].offset = offset; VkBuffer attrBuffer = m_bufferCache; VkDeviceSize attrOffset = offset; vkCmdBindVertexBuffers(m_state.currentCommandBuffer, bufferIndex, 1, &attrBuffer, &attrOffset); } void VulkanRenderer::buffer_bindVertexStrideWorkaroundBuffer(VkBuffer fixedBuffer, uint32 offset, uint32 bufferIndex, uint32 size) { cemu_assert_debug(bufferIndex < LATTE_MAX_VERTEX_BUFFERS); m_state.currentVertexBinding[bufferIndex].offset = 0xFFFFFFFF; VkBuffer attrBuffer = fixedBuffer; VkDeviceSize attrOffset = offset; vkCmdBindVertexBuffers(m_state.currentCommandBuffer, bufferIndex, 1, &attrBuffer, &attrOffset); } std::pair<VkBuffer, uint32> VulkanRenderer::buffer_genStrideWorkaroundVertexBuffer(MPTR buffer, uint32 size, uint32 oldStride) { cemu_assert_debug(oldStride % 4 != 0); std::span<uint8> old_buffer{memory_getPointerFromPhysicalOffset(buffer), size}; //new stride is the nearest multiple of 4 uint32 newStride = oldStride + (4-(oldStride % 4)); uint32 newSize = size / oldStride * newStride; auto new_buffer_alloc = memoryManager->getMetalStrideWorkaroundAllocator().AllocateBufferMemory(newSize, 128); std::span<uint8> new_buffer{new_buffer_alloc.memPtr, new_buffer_alloc.size}; for(size_t elem = 0; elem < size / oldStride; elem++) { memcpy(&new_buffer[elem * newStride], &old_buffer[elem * oldStride], oldStride); } return {new_buffer_alloc.vkBuffer, new_buffer_alloc.bufferOffset}; } void VulkanRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) { cemu_assert_debug(!m_useHostMemoryForCache); cemu_assert_debug(bufferIndex < 16); switch (shaderType) { case LatteConst::ShaderType::Vertex: dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].uniformBufferOffset[bufferIndex] = offset; break; case LatteConst::ShaderType::Geometry: dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].uniformBufferOffset[bufferIndex] = offset; break; case LatteConst::ShaderType::Pixel: dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].uniformBufferOffset[bufferIndex] = offset; break; default: cemu_assert_debug(false); } } void VulkanRenderer::bufferCache_init(const sint32 bufferSize) { m_importedMemBaseAddress = 0x10000000; size_t hostAllocationSize = 0x40000000ull; // todo - get size of allocation bool configUseHostMemory = false; // todo - replace this with a config option m_useHostMemoryForCache = false; if (m_featureControl.deviceExtensions.external_memory_host && configUseHostMemory) { m_useHostMemoryForCache = memoryManager->CreateBufferFromHostMemory(memory_getPointerFromVirtualOffset(m_importedMemBaseAddress), hostAllocationSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 0, m_importedMem, m_importedMemMemory); if (!m_useHostMemoryForCache) { cemuLog_log(LogType::Force, "Unable to import host memory to Vulkan buffer. Use default cache system instead"); } } if(!m_useHostMemoryForCache) memoryManager->CreateBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 0, m_bufferCache, m_bufferCacheMemory); } void VulkanRenderer::bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) { draw_endRenderPass(); VKRSynchronizedRingAllocator& vkMemAllocator = memoryManager->getStagingAllocator(); auto uploadResv = vkMemAllocator.AllocateBufferMemory(size, 256); memcpy(uploadResv.memPtr, buffer, size); vkMemAllocator.FlushReservation(uploadResv); barrier_bufferRange<ANY_TRANSFER | HOST_WRITE, ANY_TRANSFER, BUFFER_SHADER_READ, TRANSFER_WRITE>( uploadResv.vkBuffer, uploadResv.bufferOffset, uploadResv.size, // make sure any in-flight transfers are completed m_bufferCache, bufferOffset, size); // make sure all reads are completed before we overwrite the data VkBufferCopy region; region.srcOffset = uploadResv.bufferOffset; region.dstOffset = bufferOffset; region.size = size; vkCmdCopyBuffer(m_state.currentCommandBuffer, uploadResv.vkBuffer, m_bufferCache, 1, ®ion); barrier_sequentializeTransfer(); } void VulkanRenderer::bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) { cemu_assert_debug(!m_useHostMemoryForCache); draw_endRenderPass(); barrier_sequentializeTransfer(); bool isOverlapping = (srcOffset + size) > dstOffset && (srcOffset) < (dstOffset + size); cemu_assert_debug(!isOverlapping); VkBufferCopy bufferCopy{}; bufferCopy.srcOffset = srcOffset; bufferCopy.dstOffset = dstOffset; bufferCopy.size = size; vkCmdCopyBuffer(m_state.currentCommandBuffer, m_bufferCache, m_bufferCache, 1, &bufferCopy); barrier_sequentializeTransfer(); } void VulkanRenderer::bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) { draw_endRenderPass(); VkBuffer dstBuffer; if (m_useHostMemoryForCache) { // in host memory mode, dstOffset is physical address instead of cache address dstBuffer = m_importedMem; dstOffset -= m_importedMemBaseAddress; } else dstBuffer = m_bufferCache; barrier_bufferRange<BUFFER_SHADER_WRITE, TRANSFER_READ, ANY_TRANSFER | BUFFER_SHADER_READ, TRANSFER_WRITE>( m_xfbRingBuffer, srcOffset, size, // wait for all writes to finish dstBuffer, dstOffset, size); // wait for all reads to finish barrier_sequentializeTransfer(); VkBufferCopy bufferCopy{}; bufferCopy.srcOffset = srcOffset; bufferCopy.dstOffset = dstOffset; bufferCopy.size = size; vkCmdCopyBuffer(m_state.currentCommandBuffer, m_xfbRingBuffer, dstBuffer, 1, &bufferCopy); barrier_sequentializeTransfer(); } void VulkanRenderer::AppendOverlayDebugInfo() { ImGui::Text("--- Vulkan debug info ---"); ImGui::Text("GfxPipelines %u", performanceMonitor.vk.numGraphicPipelines.get()); ImGui::Text("DescriptorSets %u", performanceMonitor.vk.numDescriptorSets.get()); ImGui::Text("DS ImgSamplers %u", performanceMonitor.vk.numDescriptorSamplerTextures.get()); ImGui::Text("DS DynUniform %u", performanceMonitor.vk.numDescriptorDynUniformBuffers.get()); ImGui::Text("DS StorageBuf %u", performanceMonitor.vk.numDescriptorStorageBuffers.get()); ImGui::Text("Images %u", performanceMonitor.vk.numImages.get()); ImGui::Text("ImageView %u", performanceMonitor.vk.numImageViews.get()); ImGui::Text("ImageSampler %u", performanceMonitor.vk.numSamplers.get()); ImGui::Text("RenderPass %u", performanceMonitor.vk.numRenderPass.get()); ImGui::Text("Framebuffer %u", performanceMonitor.vk.numFramebuffer.get()); m_spinlockDestructionQueue.lock(); ImGui::Text("DestructionQ %u", (unsigned int)m_destructionQueue.size()); m_spinlockDestructionQueue.unlock(); ImGui::Text("BeginRP/f %u", performanceMonitor.vk.numBeginRenderpassPerFrame.get()); ImGui::Text("Barriers/f %u", performanceMonitor.vk.numDrawBarriersPerFrame.get()); ImGui::Text("--- Cache debug info ---"); uint32 bufferCacheHeapSize = 0; uint32 bufferCacheAllocationSize = 0; uint32 bufferCacheNumAllocations = 0; LatteBufferCache_getStats(bufferCacheHeapSize, bufferCacheAllocationSize, bufferCacheNumAllocations); ImGui::Text("Buffer"); ImGui::SameLine(60.0f); ImGui::Text("%06uKB / %06uKB Allocs: %u", (uint32)(bufferCacheAllocationSize + 1023) / 1024, ((uint32)bufferCacheHeapSize + 1023) / 1024, (uint32)bufferCacheNumAllocations); uint32 numBuffers; size_t totalSize, freeSize; memoryManager->getStagingAllocator().GetStats(numBuffers, totalSize, freeSize); ImGui::Text("Staging"); ImGui::SameLine(60.0f); ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers); memoryManager->GetIndexAllocator().GetStats(numBuffers, totalSize, freeSize); ImGui::Text("Index"); ImGui::SameLine(60.0f); ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers); ImGui::Text("--- Tex heaps ---"); memoryManager->appendOverlayHeapDebugInfo(); } void VKRDestructibleObject::flagForCurrentCommandBuffer() { m_lastCmdBufferId = VulkanRenderer::GetInstance()->GetCurrentCommandBufferId(); } bool VKRDestructibleObject::canDestroy() { if (m_refCount > 0) return false; return VulkanRenderer::GetInstance()->HasCommandBufferFinished(m_lastCmdBufferId); } VKRObjectTexture::VKRObjectTexture() { performanceMonitor.vk.numImages.increment(); } VKRObjectTexture::~VKRObjectTexture() { auto vkr = VulkanRenderer::GetInstance(); if (m_allocation) { vkr->GetMemoryManager()->imageMemoryFree(m_allocation); m_allocation = nullptr; } if (m_image) vkDestroyImage(vkr->GetLogicalDevice(), m_image, nullptr); performanceMonitor.vk.numImages.decrement(); } VKRObjectTextureView::VKRObjectTextureView(VKRObjectTexture* tex, VkImageView view) { m_textureImageView = view; this->addRef(tex); performanceMonitor.vk.numImageViews.increment(); } VKRObjectTextureView::~VKRObjectTextureView() { auto logicalDevice = VulkanRenderer::GetInstance()->GetLogicalDevice(); if (m_textureDefaultSampler[0] != VK_NULL_HANDLE) vkDestroySampler(logicalDevice, m_textureDefaultSampler[0], nullptr); if (m_textureDefaultSampler[1] != VK_NULL_HANDLE) vkDestroySampler(logicalDevice, m_textureDefaultSampler[1], nullptr); vkDestroyImageView(logicalDevice, m_textureImageView, nullptr); performanceMonitor.vk.numImageViews.decrement(); } static uint64 CalcHashSamplerCreateInfo(const VkSamplerCreateInfo& info) { uint64 h = 0xcbf29ce484222325ULL; auto fnvHashCombine = [](uint64_t &h, auto val) { using T = decltype(val); static_assert(sizeof(T) <= 8); uint64_t val64 = 0; std::memcpy(&val64, &val, sizeof(val)); h ^= val64; h *= 0x100000001b3ULL; }; cemu_assert_debug(info.sType == VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO); fnvHashCombine(h, info.flags); fnvHashCombine(h, info.magFilter); fnvHashCombine(h, info.minFilter); fnvHashCombine(h, info.mipmapMode); fnvHashCombine(h, info.addressModeU); fnvHashCombine(h, info.addressModeV); fnvHashCombine(h, info.addressModeW); fnvHashCombine(h, info.mipLodBias); fnvHashCombine(h, info.anisotropyEnable); if(info.anisotropyEnable == VK_TRUE) fnvHashCombine(h, info.maxAnisotropy); fnvHashCombine(h, info.compareEnable); if(info.compareEnable == VK_TRUE) fnvHashCombine(h, info.compareOp); fnvHashCombine(h, info.minLod); fnvHashCombine(h, info.maxLod); fnvHashCombine(h, info.borderColor); fnvHashCombine(h, info.unnormalizedCoordinates); // handle custom border color VkBaseOutStructure* ext = (VkBaseOutStructure*)info.pNext; while(ext) { if(ext->sType == VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT) { auto* extInfo = (VkSamplerCustomBorderColorCreateInfoEXT*)ext; fnvHashCombine(h, extInfo->customBorderColor.uint32[0]); fnvHashCombine(h, extInfo->customBorderColor.uint32[1]); fnvHashCombine(h, extInfo->customBorderColor.uint32[2]); fnvHashCombine(h, extInfo->customBorderColor.uint32[3]); } else { cemu_assert_unimplemented(); } ext = ext->pNext; } return h; } std::unordered_map<uint64, VKRObjectSampler*> VKRObjectSampler::s_samplerCache; VKRObjectSampler::VKRObjectSampler(VkSamplerCreateInfo* samplerInfo) { auto* vulkanRenderer = VulkanRenderer::GetInstance(); if (vkCreateSampler(vulkanRenderer->GetLogicalDevice(), samplerInfo, nullptr, &m_sampler) != VK_SUCCESS) vulkanRenderer->UnrecoverableError("Failed to create texture sampler"); performanceMonitor.vk.numSamplers.increment(); m_hash = CalcHashSamplerCreateInfo(*samplerInfo); } VKRObjectSampler::~VKRObjectSampler() { vkDestroySampler(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_sampler, nullptr); performanceMonitor.vk.numSamplers.decrement(); // remove from cache auto it = s_samplerCache.find(m_hash); if(it != s_samplerCache.end()) s_samplerCache.erase(it); } void VKRObjectSampler::RefCountReachedZero() { VulkanRenderer::GetInstance()->ReleaseDestructibleObject(this); } VKRObjectSampler* VKRObjectSampler::GetOrCreateSampler(VkSamplerCreateInfo* samplerInfo) { auto* vulkanRenderer = VulkanRenderer::GetInstance(); uint64 hash = CalcHashSamplerCreateInfo(*samplerInfo); auto it = s_samplerCache.find(hash); if (it != s_samplerCache.end()) { auto* sampler = it->second; return sampler; } auto* sampler = new VKRObjectSampler(samplerInfo); s_samplerCache[hash] = sampler; return sampler; } void VKRObjectSampler::DestroyCache() { // assuming all other objects which depend on vkSampler are destroyed, this cache should also have been emptied already // but just to be sure lets still clear the cache cemu_assert_debug(s_samplerCache.empty()); for(auto& sampler : s_samplerCache) { cemu_assert_debug(sampler.second->m_refCount == 0); delete sampler.second; } s_samplerCache.clear(); } VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount) { // generate helper hash for pipeline state uint64 stateHash = 0; for (int i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; ++i) { if (attachmentInfo.colorAttachment[i].isPresent || attachmentInfo.colorAttachment[i].viewObj) { stateHash += attachmentInfo.colorAttachment[i].format + i * 31; stateHash = std::rotl<uint64>(stateHash, 7); } } if (attachmentInfo.depthAttachment.isPresent || attachmentInfo.depthAttachment.viewObj) { stateHash += attachmentInfo.depthAttachment.format; stateHash = std::rotl<uint64>(stateHash, 7); } m_hashForPipeline = stateHash; // setup Vulkan renderpass std::vector<VkAttachmentDescription> attachments_descriptions; std::array<VkAttachmentReference, Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS> color_attachments_references{}; cemu_assert(colorAttachmentCount <= color_attachments_references.size()); sint32 numColorAttachments = 0; for (int i = 0; i < 8; ++i) { if (attachmentInfo.colorAttachment[i].viewObj == nullptr && attachmentInfo.colorAttachment[i].isPresent == false) { color_attachments_references[i].attachment = VK_ATTACHMENT_UNUSED; m_colorAttachmentFormat[i] = VK_FORMAT_UNDEFINED; continue; } m_colorAttachmentFormat[i] = attachmentInfo.colorAttachment[i].format; color_attachments_references[i].attachment = (uint32)attachments_descriptions.size(); color_attachments_references[i].layout = VK_IMAGE_LAYOUT_GENERAL; VkAttachmentDescription entry{}; entry.format = attachmentInfo.colorAttachment[i].format; entry.samples = VK_SAMPLE_COUNT_1_BIT; entry.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; entry.storeOp = VK_ATTACHMENT_STORE_OP_STORE; entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL; entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL; attachments_descriptions.emplace_back(entry); numColorAttachments = i + 1; } VkAttachmentReference depth_stencil_attachments_references{}; bool hasDepthStencilAttachment = false; if (attachmentInfo.depthAttachment.viewObj == nullptr && attachmentInfo.depthAttachment.isPresent == false) { depth_stencil_attachments_references.attachment = VK_ATTACHMENT_UNUSED; m_depthAttachmentFormat = VK_FORMAT_UNDEFINED; } else { hasDepthStencilAttachment = true; depth_stencil_attachments_references.attachment = (uint32)attachments_descriptions.size(); depth_stencil_attachments_references.layout = VK_IMAGE_LAYOUT_GENERAL; m_depthAttachmentFormat = attachmentInfo.depthAttachment.format; VkAttachmentDescription entry{}; entry.format = attachmentInfo.depthAttachment.format; entry.samples = VK_SAMPLE_COUNT_1_BIT; entry.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; entry.storeOp = VK_ATTACHMENT_STORE_OP_STORE; if (attachmentInfo.depthAttachment.hasStencil) { entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; } else { entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; } entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL; entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL; attachments_descriptions.emplace_back(entry); } // todo - use numColorAttachments instead of .size() or colorAttachmentCount (needs adjusting in many places) VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = colorAttachmentCount; subpass.pColorAttachments = color_attachments_references.data(); subpass.inputAttachmentCount = 0; subpass.pInputAttachments = nullptr; subpass.pDepthStencilAttachment = &depth_stencil_attachments_references; VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = (uint32)attachments_descriptions.size(); renderPassInfo.pAttachments = attachments_descriptions.data(); renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; renderPassInfo.pDependencies = nullptr; renderPassInfo.dependencyCount = 0; // before Cemu 1.25.5 we used zero here, which means implicit synchronization. For 1.25.5 it was changed to 2 (using the subpass dependencies above) // Reverted this again to zero for Cemu 1.25.5b as the performance cost is just too high. Manual synchronization is preferred if (vkCreateRenderPass(VulkanRenderer::GetInstance()->GetLogicalDevice(), &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS) { cemuLog_log(LogType::Force, "Vulkan-Error: Failed to create render pass"); throw std::runtime_error("failed to create render pass!"); } // track references for (int i = 0; i < 8; ++i) { if (attachmentInfo.colorAttachment[i].viewObj) addRef(attachmentInfo.colorAttachment[i].viewObj); } if (attachmentInfo.depthAttachment.viewObj) addRef(attachmentInfo.depthAttachment.viewObj); performanceMonitor.vk.numRenderPass.increment(); } VKRObjectRenderPass::~VKRObjectRenderPass() { if (m_renderPass != VK_NULL_HANDLE) vkDestroyRenderPass(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_renderPass, nullptr); performanceMonitor.vk.numRenderPass.decrement(); } VKRObjectFramebuffer::VKRObjectFramebuffer(VKRObjectRenderPass* renderPass, std::span<VKRObjectTextureView*> attachments, Vector2i size) { // convert VKRObjectTextureView* array to vkImageView array std::array<VkImageView, 16> attachmentViews; cemu_assert(attachments.size() < attachmentViews.size()); for (size_t i = 0; i < attachments.size(); i++) attachmentViews[i] = attachments[i]->m_textureImageView; VkFramebufferCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; createInfo.pAttachments = attachmentViews.data(); createInfo.attachmentCount = attachments.size(); createInfo.renderPass = renderPass->m_renderPass; createInfo.layers = 1; createInfo.width = size.x; createInfo.height = size.y; if (vkCreateFramebuffer(VulkanRenderer::GetInstance()->GetLogicalDevice(), &createInfo, nullptr, &m_frameBuffer) != VK_SUCCESS) throw std::runtime_error("failed to create framebuffer!"); // track refs this->addRef(renderPass); for (auto& itr : attachments) this->addRef(itr); performanceMonitor.vk.numFramebuffer.increment(); } VKRObjectFramebuffer::~VKRObjectFramebuffer() { if (m_frameBuffer != VK_NULL_HANDLE) vkDestroyFramebuffer(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_frameBuffer, nullptr); performanceMonitor.vk.numFramebuffer.decrement(); } VKRObjectPipeline::VKRObjectPipeline() { } void VKRObjectPipeline::SetPipeline(VkPipeline newPipeline) { if (m_pipeline == newPipeline) return; cemu_assert_debug(m_pipeline == VK_NULL_HANDLE); // replacing an already assigned pipeline is not intended if(m_pipeline == VK_NULL_HANDLE && newPipeline != VK_NULL_HANDLE) performanceMonitor.vk.numGraphicPipelines.increment(); else if(m_pipeline != VK_NULL_HANDLE && newPipeline == VK_NULL_HANDLE) performanceMonitor.vk.numGraphicPipelines.decrement(); m_pipeline = newPipeline; } VKRObjectPipeline::~VKRObjectPipeline() { auto vkr = VulkanRenderer::GetInstance(); if (m_pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(vkr->GetLogicalDevice(), m_pipeline, nullptr); performanceMonitor.vk.numGraphicPipelines.decrement(); } if (m_vertexDSL != VK_NULL_HANDLE) vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), m_vertexDSL, nullptr); if (m_pixelDSL != VK_NULL_HANDLE) vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), m_pixelDSL, nullptr); if (m_geometryDSL != VK_NULL_HANDLE) vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), m_geometryDSL, nullptr); if (m_pipelineLayout != VK_NULL_HANDLE) vkDestroyPipelineLayout(vkr->GetLogicalDevice(), m_pipelineLayout, nullptr); } VKRObjectDescriptorSet::VKRObjectDescriptorSet() { performanceMonitor.vk.numDescriptorSets.increment(); } VKRObjectDescriptorSet::~VKRObjectDescriptorSet() { auto vkr = VulkanRenderer::GetInstance(); vkFreeDescriptorSets(vkr->GetLogicalDevice(), vkr->GetDescriptorPool(), 1, &descriptorSet); performanceMonitor.vk.numDescriptorSets.decrement(); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h ================================================ #pragma once #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h" #include "Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h" #include "util/math/vector2.h" #include "util/helpers/Semaphore.h" #include "util/containers/flat_hash_map.hpp" #include "util/containers/robin_hood.h" struct VkSupportedFormatInfo_t { bool fmt_d24_unorm_s8_uint{}; bool fmt_r4g4_unorm_pack{}; bool fmt_r5g6b5_unorm_pack{}; bool fmt_r4g4b4a4_unorm_pack{}; bool fmt_a1r5g5b5_unorm_pack{}; }; struct VkDescriptorSetInfo { VKRObjectDescriptorSet* m_vkObjDescriptorSet{}; ~VkDescriptorSetInfo(); std::vector<LatteTextureViewVk*> list_referencedViews; std::vector<LatteTextureVk*> list_fboCandidates; // prefiltered list of textures which may need a barrier LatteConst::ShaderType shaderType{}; uint64 stateHash{}; class PipelineInfo* pipeline_info{}; // tracking for allocated descriptors uint8 statsNumSamplerTextures{ 0 }; uint8 statsNumDynUniformBuffers{ 0 }; uint8 statsNumStorageBuffers{ 0 }; }; class VkException : public std::runtime_error { public: VkException(VkResult result, const std::string& message) : runtime_error(message), m_result(result) {} VkResult GetResult() const { return m_result; } private: VkResult m_result; }; namespace VulkanRendererConst { static const inline int SHADER_STAGE_INDEX_VERTEX = 0; static const inline int SHADER_STAGE_INDEX_FRAGMENT = 1; static const inline int SHADER_STAGE_INDEX_GEOMETRY = 2; static const inline int SHADER_STAGE_INDEX_COUNT = 3; }; class PipelineInfo { public: PipelineInfo(uint64 minimalStateHash, uint64 pipelineHash, struct LatteFetchShader* fetchShader, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader); PipelineInfo(const PipelineInfo& info) = delete; ~PipelineInfo(); bool operator==(const PipelineInfo& pipeline_info) const { return true; } template<typename T> struct direct_hash { size_t operator()(const uint64& k) const noexcept { return k; } }; // std::unordered_map<uint64, VkDescriptorSetInfo*> 3.16% (total CPU time) // robin_hood::unordered_flat_map<uint64, VkDescriptorSetInfo*> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; ~1.80% // ska::bytell_hash_map<uint64, VkDescriptorSetInfo*, direct_hash<uint64>> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; -> 1.91% ska::flat_hash_map<uint64, VkDescriptorSetInfo*, direct_hash<uint64>> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; // 1.71% VKRObjectPipeline* m_vkrObjPipeline; LatteDecompilerShader* vertexShader = nullptr; LatteDecompilerShader* geometryShader = nullptr; LatteDecompilerShader* pixelShader = nullptr; LatteFetchShader* fetchShader = nullptr; Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE primitiveMode{}; RendererShaderVk* vertexShaderVk = nullptr; RendererShaderVk* geometryShaderVk = nullptr; RendererShaderVk* pixelShaderVk = nullptr; uint64 minimalStateHash; uint64 stateHash; bool usesBlendConstants{ false }; bool usesDepthBias{ false }; struct { bool hasUniformVar[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; bool hasUniformBuffers[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; std::vector<uint8> list_uniformBuffers[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; }dynamicOffsetInfo{}; // primitive rects emulation RendererShaderVk* rectEmulationGS = nullptr; // hack - accurate barrier needed for this pipeline bool neverSkipAccurateBarrier{false}; }; namespace WindowSystem { struct WindowHandleInfo; }; class VulkanRenderer : public Renderer { friend class LatteQueryObjectVk; friend class LatteTextureReadbackInfoVk; friend class PipelineCompiler; using VSync = SwapchainInfoVk::VSync; static const inline int UNIFORMVAR_RINGBUFFER_SIZE = 1024 * 1024 * 16; // 16MB static const inline int TEXTURE_READBACK_SIZE = 32 * 1024 * 1024; // 32 MB static const inline int OCCLUSION_QUERY_POOL_SIZE = 1024; public: // memory management std::unique_ptr<VKRMemoryManager> memoryManager; VKRMemoryManager* GetMemoryManager() const { return memoryManager.get(); }; VkSupportedFormatInfo_t m_supportedFormatInfo; typedef struct { // Vulkan image info VkFormat vkImageFormat; VkImageAspectFlags vkImageAspect; bool isCompressed; // texture decoder info TextureDecoder* decoder; sint32 texelCountX; sint32 texelCountY; }FormatInfoVK; struct DeviceInfo { DeviceInfo(const std::string name, uint8* uuid) : name(name) { std::copy(uuid, uuid + VK_UUID_SIZE, this->uuid.data()); } std::string name; std::array<uint8, VK_UUID_SIZE> uuid; }; static std::vector<DeviceInfo> GetDevices(); VulkanRenderer(); virtual ~VulkanRenderer(); RendererAPI GetType() override { return RendererAPI::Vulkan; } static VulkanRenderer* GetInstance(); void UnrecoverableError(const char* errMsg) const; void GetDeviceFeatures(); void DetermineVendor(); void InitializeSurface(const Vector2i& size, bool mainWindow); const std::unique_ptr<SwapchainInfoVk>& GetChainInfoPtr(bool mainWindow) const; SwapchainInfoVk& GetChainInfo(bool mainWindow) const; void StopUsingPadAndWait(); bool IsPadWindowActive() override; void HandleScreenshotRequest(LatteTextureView* texView, bool padView) override; void QueryMemoryInfo(); void QueryAvailableFormats(); #if BOOST_OS_WINDOWS static VkSurfaceKHR CreateWinSurface(VkInstance instance, HWND hwindow); #endif #if BOOST_OS_LINUX || BOOST_OS_BSD static VkSurfaceKHR CreateXlibSurface(VkInstance instance, Display* dpy, Window window); static VkSurfaceKHR CreateXcbSurface(VkInstance instance, xcb_connection_t* connection, xcb_window_t window); #ifdef HAS_WAYLAND static VkSurfaceKHR CreateWaylandSurface(VkInstance instance, wl_display* display, wl_surface* surface); #endif #endif static VkSurfaceKHR CreateFramebufferSurface(VkInstance instance, struct WindowSystem::WindowHandleInfo& windowInfo); void AppendOverlayDebugInfo() override; void ImguiInit(); VkInstance GetVkInstance() const { return m_instance; } VkDevice GetLogicalDevice() const { return m_logicalDevice; } VkPhysicalDevice GetPhysicalDevice() const { return m_physicalDevice; } VkDescriptorPool GetDescriptorPool() const { return m_descriptorPool; } void WaitDeviceIdle() const { vkDeviceWaitIdle(m_logicalDevice); } void Initialize() override; void Shutdown() override; void SwapBuffers(bool swapTV = true, bool swapDRC = true) override; void Flush(bool waitIdle = false) override; void NotifyLatteCommandProcessorIdle() override; uint64 GenUniqueId(); // return unique id (uses incrementing counter) void DrawEmptyFrame(bool mainWindow) override; void InitFirstCommandBuffer(); void ProcessFinishedCommandBuffers(); void WaitForNextFinishedCommandBuffer(); void SubmitCommandBuffer(VkSemaphore signalSemaphore = VK_NULL_HANDLE, VkSemaphore waitSemaphore = VK_NULL_HANDLE); void RequestSubmitSoon(); void RequestSubmitOnIdle(); // command buffer synchronization uint64 GetCurrentCommandBufferId() const; bool HasCommandBufferFinished(uint64 commandBufferId) const; void WaitCommandBufferFinished(uint64 commandBufferId); // resource destruction queue void ReleaseDestructibleObject(VKRDestructibleObject* destructibleObject); void ProcessDestructionQueue(); FSpinlock m_spinlockDestructionQueue; std::vector<VKRDestructibleObject*> m_destructionQueue; void PipelineCacheSaveThread(size_t cache_size); void ClearColorbuffer(bool padView) override; void ClearColorImageRaw(VkImage image, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout inputLayout, VkImageLayout outputLayout); void ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout outputLayout); void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) override; void CreateDescriptorPool(); VkDescriptorSet backbufferBlit_createDescriptorSet(VkDescriptorSetLayout descriptor_set_layout, LatteTextureViewVk* texViewVk, bool useLinearTexFilter); robin_hood::unordered_flat_map<uint64, robin_hood::unordered_flat_map<uint64, PipelineInfo*> > m_pipeline_info_cache; // using robin_hood::unordered_flat_map is twice as fast (1-2% overall CPU time reduction) void draw_debugPipelineHashState(); PipelineInfo* draw_getCachedPipeline(); // pipeline state hash static uint64 draw_calculateMinimalGraphicsPipelineHash(const LatteFetchShader* fetchShader, const LatteContextRegister& lcr); static uint64 draw_calculateGraphicsPipelineHash(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const LatteDecompilerShader* geometryShader, const LatteDecompilerShader* pixelShader, const VKRObjectRenderPass* renderPassObj, const LatteContextRegister& lcr); // rendertarget void renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ = false) override; void renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) override; LatteCachedFBO* rendertarget_createCachedFBO(uint64 key) override; void rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) override; void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) override; // texture functions void* texture_acquireTextureUploadBuffer(uint32 size) override; void texture_releaseTextureUploadBuffer(uint8* mem) override; TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override; void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override; void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override; void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) override; void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) override; LatteTexture* texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) override; void texture_setLatteTexture(LatteTextureView* textureView, uint32 textureUnit) override; void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) override; LatteTextureReadbackInfo* texture_createReadback(LatteTextureView* textureView) override; // surface copy void surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) override; void surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTexture); private: void surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight); void surfaceCopy_cleanup(); private: uint64 copySurface_getPipelineStateHash(struct VkCopySurfaceState_t& state); struct CopySurfacePipelineInfo* copySurface_getCachedPipeline(struct VkCopySurfaceState_t& state); struct CopySurfacePipelineInfo* copySurface_getOrCreateGraphicsPipeline(struct VkCopySurfaceState_t& state); VKRObjectTextureView* surfaceCopy_createImageView(LatteTextureVk* textureVk, uint32 sliceIndex, uint32 mipIndex); VKRObjectFramebuffer* surfaceCopy_getOrCreateFramebuffer(struct VkCopySurfaceState_t& state, struct CopySurfacePipelineInfo* pipelineInfo); VKRObjectDescriptorSet* surfaceCopy_getOrCreateDescriptorSet(struct VkCopySurfaceState_t& state, struct CopySurfacePipelineInfo* pipelineInfo); VKRObjectRenderPass* copySurface_createRenderpass(struct VkCopySurfaceState_t& state); std::unordered_map<uint64, struct CopySurfacePipelineInfo*> m_copySurfacePipelineCache; public: // renderer interface void bufferCache_init(const sint32 bufferSize) override; void bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) override; void bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) override; void buffer_bindVertexBuffer(uint32 bufferIndex, uint32 buffer, uint32 size) override; void buffer_bindVertexStrideWorkaroundBuffer(VkBuffer fixedBuffer, uint32 offset, uint32 bufferIndex, uint32 size); std::pair<VkBuffer, uint32> buffer_genStrideWorkaroundVertexBuffer(MPTR buffer, uint32 size, uint32 oldStride); void buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) override; RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override; IndexAllocation indexData_reserveIndexMemory(uint32 size) override; void indexData_releaseIndexMemory(IndexAllocation& allocation) override; void indexData_uploadIndexMemory(IndexAllocation& allocation) override; // externally callable void GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, sint32 width, sint32 height, FormatInfoVK* formatInfoOut); void unregisterGraphicsPipeline(PipelineInfo* pipelineInfo); private: struct VkRendererState { VkRendererState() = default; VkRendererState(const VkRendererState&) = delete; VkRendererState(VkRendererState&&) noexcept = delete; // textures LatteTextureViewVk* boundTexture[128]{}; // rendertarget CachedFBOVk* activeFBO{}; // the FBO active for the emulated GPU // command buffer VkCommandBuffer currentCommandBuffer{}; // pipeline VkPipeline currentPipeline{ VK_NULL_HANDLE }; // renderpass CachedFBOVk* activeRenderpassFBO{}; // the FBO of the currently active Vulkan renderpass // drawcall state PipelineInfo* activePipelineInfo{ nullptr }; VkDescriptorSetInfo* activeVertexDS{ nullptr }; VkDescriptorSetInfo* activePixelDS{ nullptr }; VkDescriptorSetInfo* activeGeometryDS{ nullptr }; bool descriptorSetsChanged{ false }; bool hasRenderSelfDependency{ false }; // set if current drawcall samples textures which are also output as a rendertarget // viewport and scissor box VkViewport currentViewport{}; VkRect2D currentScissorRect{}; // vertex bindings struct { uint32 offset; }currentVertexBinding[LATTE_MAX_VERTEX_BUFFERS]{}; // transform feedback bool hasActiveXfb{}; // index buffer Renderer::INDEX_TYPE activeIndexType{}; uint32 activeIndexBufferIndex{}; uint32 activeIndexBufferOffset{}; // polygon offset uint32 prevPolygonFrontOffsetU32{ 0xFFFFFFFF }; uint32 prevPolygonFrontScaleU32{ 0xFFFFFFFF }; uint32 prevPolygonFrontClampU32{ 0xFFFFFFFF }; void resetCommandBufferState() { prevPolygonFrontOffsetU32 = 0xFFFFFFFF; prevPolygonFrontScaleU32 = 0xFFFFFFFF; prevPolygonFrontClampU32 = 0xFFFFFFFF; currentPipeline = VK_NULL_HANDLE; for (auto& itr : currentVertexBinding) { itr.offset = 0xFFFFFFFF; } activeIndexType = Renderer::INDEX_TYPE::NONE; activeIndexBufferIndex = std::numeric_limits<uint32>::max(); activeIndexBufferOffset = std::numeric_limits<uint32>::max(); } // invalidation / flushing uint64 currentFlushIndex{0}; bool requestFlush{ false }; // flush after every draw operation. The renderpass dependencies dont handle dependencies across multiple drawcalls inside a single renderpass // draw sequence bool drawSequenceSkip; // if true, skip draw_execute() }m_state; std::unique_ptr<SwapchainInfoVk> m_mainSwapchainInfo{}, m_padSwapchainInfo{}; std::atomic_flag m_destroyPadSwapchainNextAcquire{}; bool IsSwapchainInfoValid(bool mainWindow) const; VkRenderPass m_imguiRenderPass = VK_NULL_HANDLE; VkDescriptorPool m_descriptorPool; public: struct QueueFamilyIndices { int32_t graphicsFamily = -1; int32_t presentFamily = -1; bool IsComplete() const { return graphicsFamily >= 0 && presentFamily >= 0; } }; static QueueFamilyIndices FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device); private: struct FeatureControl { struct { // if using new optional extensions add to CheckDeviceExtensionSupport and CreateDeviceCreateInfo bool tooling_info = false; // VK_EXT_tooling_info bool transform_feedback = false; bool depth_range_unrestricted = false; bool nv_fill_rectangle = false; // NV_fill_rectangle bool pipeline_feedback = false; bool pipeline_creation_cache_control = false; // VK_EXT_pipeline_creation_cache_control bool custom_border_color = false; // VK_EXT_custom_border_color bool custom_border_color_without_format = false; // VK_EXT_custom_border_color (specifically customBorderColorWithoutFormat) bool cubic_filter = false; // VK_EXT_FILTER_CUBIC_EXTENSION_NAME bool driver_properties = false; // VK_KHR_driver_properties bool external_memory_host = false; // VK_EXT_external_memory_host bool synchronization2 = false; // VK_KHR_synchronization2 bool dynamic_rendering = false; // VK_KHR_dynamic_rendering bool shader_float_controls = false; // VK_KHR_shader_float_controls bool present_wait = false; // VK_KHR_present_wait bool depth_clip_enable = false; // VK_EXT_depth_clip_enable bool pipeline_robustness = false; // VK_EXT_pipeline_robustness }deviceExtensions; struct { bool shaderRoundingModeRTEFloat32{ false }; }shaderFloatControls; // from VK_KHR_shader_float_controls struct { bool debug_utils = false; // VK_EXT_DEBUG_UTILS }instanceExtensions; struct { bool useTFEmulationViaSSBO = true; // emulate transform feedback via shader writes to a storage buffer }mode; struct { uint32 minUniformBufferOffsetAlignment = 256; uint32 nonCoherentAtomSize = 256; }limits; bool usingDebugMarkerTool{ false }; // validation layer or other tool capable of handling debug markers is used bool usingTracingTool{ false }; // frame debugger or other API replaying tool is used bool disableMultithreadedCompilation{ false }; // for old nvidia drivers }m_featureControl{}; static bool CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info); static std::vector<const char*> CheckInstanceExtensionSupport(FeatureControl& info); bool UpdateSwapchainProperties(bool mainWindow); void SwapBuffer(bool mainWindow); VkDescriptorSetLayout m_swapchainDescriptorSetLayout; VkQueue m_graphicsQueue, m_presentQueue; // swapchain std::vector<VkDeviceQueueCreateInfo> CreateQueueCreateInfos(const std::set<int>& uniqueQueueFamilies) const; VkDeviceCreateInfo CreateDeviceCreateInfo(const std::vector<VkDeviceQueueCreateInfo>& queueCreateInfos, const VkPhysicalDeviceFeatures& deviceFeatures, const void* deviceExtensionStructs, std::vector<const char*>& used_extensions) const; static bool IsDeviceSuitable(VkSurfaceKHR surface, const VkPhysicalDevice& device); void CreateCommandPool(); void CreateCommandBuffers(); void swapchain_createDescriptorSetLayout(); // shader bool IsAsyncPipelineAllowed(uint32 numIndices); uint64 GetDescriptorSetStateHash(LatteDecompilerShader* shader); // imgui bool ImguiBegin(bool mainWindow) override; void ImguiEnd() override; ImTextureID GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) override; void DeleteTexture(ImTextureID id) override; void DeleteFontTextures() override; bool BeginFrame(bool mainWindow) override; bool UseTFViaSSBO() const override { return m_featureControl.mode.useTFEmulationViaSSBO; } // drawcall emulation PipelineInfo* draw_createGraphicsPipeline(uint32 indexCount); PipelineInfo* draw_getOrCreateGraphicsPipeline(uint32 indexCount); void draw_updateVkBlendConstants(); void draw_updateDepthBias(bool forceUpdate); void draw_setRenderPass(); void draw_endRenderPass(); void draw_beginSequence() override; void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override; void draw_endSequence() override; void draw_updateVertexBuffersDirectAccess(); void draw_updateUniformBuffersDirectAccess(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType); void draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderStageIndex, uint32* dynamicOffsets, sint32& numDynOffsets, const PipelineInfo* pipeline_info); VkDescriptorSetInfo* draw_getOrCreateDescriptorSet(PipelineInfo* pipeline_info, LatteDecompilerShader* shader); void draw_prepareDescriptorSets(PipelineInfo* pipeline_info, VkDescriptorSetInfo*& vertexDS, VkDescriptorSetInfo*& pixelDS, VkDescriptorSetInfo*& geometryDS); void draw_handleSpecialState5(); // draw synchronization helper void sync_inputTexturesChanged(); void sync_RenderPassLoadTextures(CachedFBOVk* fboVk); void sync_RenderPassStoreTextures(CachedFBOVk* fboVk); // command buffer VkCommandBuffer getCurrentCommandBuffer() const { return m_state.currentCommandBuffer; } // uniform uint32 uniformData_uploadUniformDataBufferGetOffset(std::span<uint8, std::dynamic_extent> data); void uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader); // misc void CreatePipelineCache(); VkPipelineShaderStageCreateInfo CreatePipelineShaderStageCreateInfo(VkShaderStageFlagBits stage, VkShaderModule& module, const char* entryName) const; VkPipeline backbufferBlit_createGraphicsPipeline(VkDescriptorSetLayout descriptorLayout, bool padView, RendererOutputShader* shader); bool AcquireNextSwapchainImage(bool mainWindow); void RecreateSwapchain(bool mainWindow, bool skipCreate = false); // streamout void streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) override; void streamout_begin() override; void streamout_applyTransformFeedbackState(); void bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) override; void streamout_rendererFinishDrawcall() override; // occlusion queries LatteQueryObject* occlusionQuery_create() override; void occlusionQuery_destroy(LatteQueryObject* queryObj) override; void occlusionQuery_flush() override; void occlusionQuery_updateState() override; void occlusionQuery_notifyEndCommandBuffer(); void occlusionQuery_notifyBeginCommandBuffer(); private: std::vector<const char*> m_layerNames; VkInstance m_instance = VK_NULL_HANDLE; VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; VkDevice m_logicalDevice = VK_NULL_HANDLE; VkDebugUtilsMessengerEXT m_debugCallback = nullptr; volatile bool m_destructionRequested = false; QueueFamilyIndices m_indices{}; Semaphore m_pipeline_cache_semaphore; std::shared_mutex m_pipeline_cache_save_mutex; std::thread m_pipeline_cache_save_thread; VkPipelineCache m_pipeline_cache{ nullptr }; std::unordered_map<uint64, VkPipeline> m_backbufferBlitPipelineCache; std::unordered_map<uint64, VkDescriptorSet> m_backbufferBlitDescriptorSetCache; VkPipelineLayout m_pipelineLayout{nullptr}; VkCommandPool m_commandPool{ nullptr }; // buffer to cache uniform vars VkBuffer m_uniformVarBuffer = VK_NULL_HANDLE; VkDeviceMemory m_uniformVarBufferMemory = VK_NULL_HANDLE; bool m_uniformVarBufferMemoryIsCoherent{false}; uint8* m_uniformVarBufferPtr = nullptr; uint32 m_uniformVarBufferWriteIndex = 0; uint32 m_uniformVarBufferReadIndex = 0; // transform feedback ringbuffer VkBuffer m_xfbRingBuffer = VK_NULL_HANDLE; VkDeviceMemory m_xfbRingBufferMemory = VK_NULL_HANDLE; // buffer cache (attributes, uniforms and streamout) VkBuffer m_bufferCache = VK_NULL_HANDLE; VkDeviceMemory m_bufferCacheMemory = VK_NULL_HANDLE; // texture readback VkBuffer m_textureReadbackBuffer = VK_NULL_HANDLE; VkDeviceMemory m_textureReadbackBufferMemory = VK_NULL_HANDLE; uint8* m_textureReadbackBufferPtr = nullptr; uint32 m_textureReadbackBufferWriteIndex = 0; // placeholder objects to simulate NULL buffers and textures struct NullTexture { VkImage image; VkImageView view; VkSampler sampler; VkImageMemAllocation* allocation; }; NullTexture nullTexture1D{}; NullTexture nullTexture2D{}; void CreateNullTexture(NullTexture& nullTex, VkImageType imageType); void CreateNullObjects(); void DeleteNullTexture(NullTexture& nullTex); void DeleteNullObjects(); // if VK_EXT_external_memory_host is supported we can (optionally) import all of the Wii U memory into a Vulkan memory object // this allows us to skip any vertex/uniform caching logic and let the GPU directly read the memory from main RAM // Wii U memory imported into a buffer bool m_useHostMemoryForCache{ false }; VkBuffer m_importedMem = VK_NULL_HANDLE; VkDeviceMemory m_importedMemMemory = VK_NULL_HANDLE; MPTR m_importedMemBaseAddress = 0; // command buffer, garbage collection, synchronization static constexpr uint32 kCommandBufferPoolSize = 128; size_t m_commandBufferIndex = 0; // current buffer being filled size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit) size_t m_commandBufferIDOfPrevFrame = 0; std::array<size_t, kCommandBufferPoolSize> m_cmdBufferUniformRingbufIndices {}; // index in the uniform ringbuffer std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences; std::array<VkCommandBuffer, kCommandBufferPoolSize> m_commandBuffers; std::array<VkSemaphore, kCommandBufferPoolSize> m_commandBufferSemaphores; VkSemaphore GetLastSubmittedCmdBufferSemaphore() { return m_commandBufferSemaphores[(m_commandBufferIndex + m_commandBufferSemaphores.size() - 1) % m_commandBufferSemaphores.size()]; } uint64 m_numSubmittedCmdBuffers{}; uint64 m_countCommandBufferFinished{}; uint32 m_recordedDrawcalls{}; // number of drawcalls recorded into current command buffer uint32 m_submitThreshold{}; // submit current buffer if recordedDrawcalls exceeds this number bool m_submitOnIdle{}; // submit current buffer if Latte command processor goes into idle state (no more commands or waiting for externally signaled condition) // tracking for dynamic offsets struct { uint32 uniformVarBufferOffset[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; struct { uint32 uniformBufferOffset[LATTE_NUM_MAX_UNIFORM_BUFFERS]; }shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; }dynamicOffsetInfo{}; // streamout struct { struct { bool enabled; uint32 ringBufferOffset; }buffer[LATTE_NUM_STREAMOUT_BUFFER]; sint32 verticesPerInstance; }m_streamoutState{}; struct { VkQueryPool queryPool{VK_NULL_HANDLE}; sint32 currentQueryIndex{}; std::vector<class LatteQueryObjectVk*> list_cachedQueries; std::vector<class LatteQueryObjectVk*> list_currentlyActiveQueries; uint64 m_lastCommandBuffer{}; // query result buffer VkBuffer bufferQueryResults; VkDeviceMemory memoryQueryResults; uint64* ptrQueryResults; std::vector<uint16> list_availableQueryIndices; }m_occlusionQueries; // barrier enum SYNC_OP : uint32 { /* name */ /* operations */ HOST_WRITE = 0x01, HOST_READ = 0x02, // BUFFER_INDEX_READ (should be separated?) BUFFER_SHADER_READ = 0x04, // any form of shader read access BUFFER_SHADER_WRITE = 0x08, // any form of shader write access ANY_TRANSFER = 0x10, // previous transfer to/from buffer or image TRANSFER_READ = 0x80, // transfer from image/buffer TRANSFER_WRITE = 0x100, // transfer to image/buffer IMAGE_READ = 0x20, IMAGE_WRITE = 0x40, }; template<uint32 TSyncOp> void barrier_calcStageAndMask(VkPipelineStageFlags& stages, VkAccessFlags& accessFlags) { stages = 0; accessFlags = 0; if constexpr ((TSyncOp & BUFFER_SHADER_READ) != 0) { // in theory: VK_ACCESS_INDEX_READ_BIT should be set here too but indices are currently separated stages |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; accessFlags |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT; } if constexpr ((TSyncOp & BUFFER_SHADER_WRITE) != 0) { stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; accessFlags |= VK_ACCESS_SHADER_WRITE_BIT; } if constexpr ((TSyncOp & ANY_TRANSFER) != 0) { //stages |= VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT; //accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT; stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; } if constexpr ((TSyncOp & TRANSFER_READ) != 0) { stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; accessFlags |= VK_ACCESS_TRANSFER_READ_BIT; //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; } if constexpr ((TSyncOp & TRANSFER_WRITE) != 0) { stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; accessFlags |= VK_ACCESS_TRANSFER_WRITE_BIT; //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; } if constexpr ((TSyncOp & HOST_WRITE) != 0) { stages |= VK_PIPELINE_STAGE_HOST_BIT; accessFlags |= VK_ACCESS_HOST_WRITE_BIT; } if constexpr ((TSyncOp & HOST_READ) != 0) { stages |= VK_PIPELINE_STAGE_HOST_BIT; accessFlags |= VK_ACCESS_HOST_READ_BIT; } if constexpr ((TSyncOp & IMAGE_READ) != 0) { stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; accessFlags |= VK_ACCESS_SHADER_READ_BIT; stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; } if constexpr ((TSyncOp & IMAGE_WRITE) != 0) { stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; } } template<uint32 TSrcSyncOp, uint32 TDstSyncOp> void barrier_bufferRange(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size) { VkBufferMemoryBarrier bufMemBarrier{}; bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; bufMemBarrier.pNext = nullptr; bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; VkPipelineStageFlags srcStages = 0; VkPipelineStageFlags dstStages = 0; bufMemBarrier.srcAccessMask = 0; bufMemBarrier.dstAccessMask = 0; barrier_calcStageAndMask<TSrcSyncOp>(srcStages, bufMemBarrier.srcAccessMask); barrier_calcStageAndMask<TDstSyncOp>(dstStages, bufMemBarrier.dstAccessMask); bufMemBarrier.buffer = buffer; bufMemBarrier.offset = offset; bufMemBarrier.size = size; vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 1, &bufMemBarrier, 0, nullptr); } template<uint32 TSrcSyncOpA, uint32 TDstSyncOpA, uint32 TSrcSyncOpB, uint32 TDstSyncOpB> void barrier_bufferRange(VkBuffer bufferA, VkDeviceSize offsetA, VkDeviceSize sizeA, VkBuffer bufferB, VkDeviceSize offsetB, VkDeviceSize sizeB) { VkPipelineStageFlags srcStagesA = 0; VkPipelineStageFlags dstStagesA = 0; VkPipelineStageFlags srcStagesB = 0; VkPipelineStageFlags dstStagesB = 0; VkBufferMemoryBarrier bufMemBarrier[2]; bufMemBarrier[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; bufMemBarrier[0].pNext = nullptr; bufMemBarrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier[0].srcAccessMask = 0; bufMemBarrier[0].dstAccessMask = 0; barrier_calcStageAndMask<TSrcSyncOpA>(srcStagesA, bufMemBarrier[0].srcAccessMask); barrier_calcStageAndMask<TDstSyncOpA>(dstStagesA, bufMemBarrier[0].dstAccessMask); bufMemBarrier[0].buffer = bufferA; bufMemBarrier[0].offset = offsetA; bufMemBarrier[0].size = sizeA; bufMemBarrier[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; bufMemBarrier[1].pNext = nullptr; bufMemBarrier[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier[1].srcAccessMask = 0; bufMemBarrier[1].dstAccessMask = 0; barrier_calcStageAndMask<TSrcSyncOpB>(srcStagesB, bufMemBarrier[1].srcAccessMask); barrier_calcStageAndMask<TDstSyncOpB>(dstStagesB, bufMemBarrier[1].dstAccessMask); bufMemBarrier[1].buffer = bufferB; bufMemBarrier[1].offset = offsetB; bufMemBarrier[1].size = sizeB; vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStagesA|srcStagesB, dstStagesA|dstStagesB, 0, 0, nullptr, 2, bufMemBarrier, 0, nullptr); } void barrier_sequentializeTransfer() { VkMemoryBarrier memBarrier{}; memBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; memBarrier.pNext = nullptr; VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT; VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; memBarrier.dstAccessMask = 0; memBarrier.srcAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); memBarrier.dstAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 1, &memBarrier, 0, nullptr, 0, nullptr); } void barrier_sequentializeCommand() { VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 0, nullptr, 0, nullptr); } template<uint32 TSrcSyncOp, uint32 TDstSyncOp> void barrier_image(VkImage imageVk, VkImageSubresourceRange& subresourceRange, VkImageLayout oldLayout, VkImageLayout newLayout) { VkPipelineStageFlags srcStages = 0; VkPipelineStageFlags dstStages = 0; VkImageMemoryBarrier imageMemBarrier{}; imageMemBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageMemBarrier.srcAccessMask = 0; imageMemBarrier.dstAccessMask = 0; barrier_calcStageAndMask<TSrcSyncOp>(srcStages, imageMemBarrier.srcAccessMask); barrier_calcStageAndMask<TDstSyncOp>(dstStages, imageMemBarrier.dstAccessMask); imageMemBarrier.image = imageVk; imageMemBarrier.subresourceRange = subresourceRange; imageMemBarrier.oldLayout = oldLayout; imageMemBarrier.newLayout = newLayout; vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, NULL, 0, NULL, 1, &imageMemBarrier); } template<uint32 TSrcSyncOp, uint32 TDstSyncOp> void barrier_image(LatteTextureVk* vkTexture, VkImageSubresourceLayers& subresourceLayers, VkImageLayout newLayout) { VkImage imageVk = vkTexture->GetImageObj()->m_image; VkImageSubresourceRange subresourceRange; subresourceRange.aspectMask = subresourceLayers.aspectMask; subresourceRange.baseArrayLayer = subresourceLayers.baseArrayLayer; subresourceRange.layerCount = subresourceLayers.layerCount; subresourceRange.baseMipLevel = subresourceLayers.mipLevel; subresourceRange.levelCount = 1; barrier_image<TSrcSyncOp, TDstSyncOp>(imageVk, subresourceRange, vkTexture->GetImageLayout(subresourceRange), newLayout); vkTexture->SetImageLayout(subresourceRange, newLayout); } public: bool GetDisableMultithreadedCompilation() const { return m_featureControl.disableMultithreadedCompilation; } bool HasSPRIVRoundingModeRTE32() const { return m_featureControl.shaderFloatControls.shaderRoundingModeRTEFloat32; } bool IsDebugMarkersEnabled() const { return m_featureControl.usingDebugMarkerTool; } bool IsTracingToolEnabled() const { return m_featureControl.usingTracingTool; } private: // debug void debug_genericBarrier(); // shaders struct { RendererShaderVk* copySurface_vs{}; RendererShaderVk* copySurface_psDepth2Color{}; RendererShaderVk* copySurface_psColor2Depth{}; }defaultShaders; }; ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Core/LatteIndices.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "imgui/imgui_impl_vulkan.h" #include "Cafe/GameProfile/GameProfile.h" #include "util/helpers/helpers.h" extern bool hasValidFramebufferAttached; // includes only states that may change during minimal drawcalls uint64 VulkanRenderer::draw_calculateMinimalGraphicsPipelineHash(const LatteFetchShader* fetchShader, const LatteContextRegister& lcr) { uint64 stateHash = 0; // fetch shader for (auto& group : fetchShader->bufferGroups) { uint32 bufferStride = group.getCurrentBufferStride(lcr.GetRawView()); stateHash = std::rotl<uint64>(stateHash, 7); stateHash += bufferStride * 3; } stateHash += fetchShader->getVkPipelineHashFragment(); stateHash = std::rotl<uint64>(stateHash, 7); stateHash += lcr.GetRawView()[mmVGT_PRIMITIVE_TYPE]; stateHash = std::rotl<uint64>(stateHash, 7); stateHash += lcr.GetRawView()[mmVGT_STRMOUT_EN]; stateHash = std::rotl<uint64>(stateHash, 7); if(lcr.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL()) stateHash += 0x333333; return stateHash; } uint64 VulkanRenderer::draw_calculateGraphicsPipelineHash(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const LatteDecompilerShader* geometryShader, const LatteDecompilerShader* pixelShader, const VKRObjectRenderPass* renderPassObj, const LatteContextRegister& lcr) { // note: vertexShader references a fetchShader (vertexShader->fetchShader) but it's not necessarily the one that is currently active // this is because we try to separate dynamic state (mainly attribute offsets) from the actual attribute data layout and mapping (types and slots) // on Vulkan this causes issues because we bake the attribute offsets, which may not match vertexShader->compatibleFetchShader, into the pipeline // To avoid issues always use the active fetch shader. Not the one associated with the vertexShader object // note 2: // there is a secondary issue where we dont store all fetch shaders into the pipeline cache (only a single fetch shader is tied to each stored vertex shader) // but we can probably trust drivers to not require pipeline recompilation if only the offsets differ // An alternative would be to use VK_EXT_vertex_input_dynamic_state but it comes with minor overhead // Regardless, the extension is not well supported as of writing this (July 2021, only 10% of GPUs support it on Windows. Nvidia only) cemu_assert_debug(vertexShader->compatibleFetchShader->key == fetchShader->key); // fetch shaders must be layout compatible, but may have different offsets uint64 stateHash; stateHash = draw_calculateMinimalGraphicsPipelineHash(fetchShader, lcr); stateHash = (stateHash >> 8) + (stateHash * 0x370531ull) % 0x7F980D3BF9B4639Dull; uint32* ctxRegister = lcr.GetRawView(); if (vertexShader) stateHash += vertexShader->baseHash; stateHash = std::rotl<uint64>(stateHash, 13); if (geometryShader) stateHash += geometryShader->baseHash; stateHash = std::rotl<uint64>(stateHash, 13); if (pixelShader) stateHash += pixelShader->baseHash + pixelShader->auxHash; stateHash = std::rotl<uint64>(stateHash, 13); uint32 polygonCtrl = lcr.PA_SU_SC_MODE_CNTL.getRawValue(); stateHash += polygonCtrl; stateHash = std::rotl<uint64>(stateHash, 7); stateHash += ctxRegister[Latte::REGADDR::PA_CL_CLIP_CNTL]; stateHash = std::rotl<uint64>(stateHash, 7); const auto colorControlReg = ctxRegister[Latte::REGADDR::CB_COLOR_CONTROL]; stateHash += colorControlReg; stateHash += ctxRegister[Latte::REGADDR::CB_TARGET_MASK]; const uint32 blendEnableMask = (colorControlReg >> 8) & 0xFF; if (blendEnableMask) { for (auto i = 0; i < 8; ++i) { if (((blendEnableMask & (1 << i))) == 0) continue; stateHash = std::rotl<uint64>(stateHash, 7); stateHash += ctxRegister[Latte::REGADDR::CB_BLEND0_CONTROL + i]; } } stateHash += renderPassObj->m_hashForPipeline; uint32 depthControl = ctxRegister[Latte::REGADDR::DB_DEPTH_CONTROL]; bool stencilTestEnable = depthControl & 1; if (stencilTestEnable) { stateHash += ctxRegister[mmDB_STENCILREFMASK]; stateHash = std::rotl<uint64>(stateHash, 17); if(depthControl & (1<<7)) // back stencil enable { stateHash += ctxRegister[mmDB_STENCILREFMASK_BF]; stateHash = std::rotl<uint64>(stateHash, 13); } } else { // zero out stencil related bits (8-31) depthControl &= 0xFF; } stateHash = std::rotl<uint64>(stateHash, 17); stateHash += depthControl; // polygon offset if (polygonCtrl & (1 << 11)) { // front offset enabled stateHash += 0x1111; } return stateHash; } void VulkanRenderer::draw_debugPipelineHashState() { cemu_assert_debug(false); } PipelineInfo* VulkanRenderer::draw_getCachedPipeline() { // todo - optimize m_pipeline_info_cache away and store directly in vk vertex shader const auto fetchShader = LatteSHRC_GetActiveFetchShader(); const auto vertexShader = LatteSHRC_GetActiveVertexShader(); const auto it = m_pipeline_info_cache.find(vertexShader->baseHash); if (it == m_pipeline_info_cache.cend()) return nullptr; const auto geometryShader = LatteSHRC_GetActiveGeometryShader(); const auto pixelShader = LatteSHRC_GetActivePixelShader(); auto cachedFboVk = (CachedFBOVk*)m_state.activeFBO; const uint64 stateHash = draw_calculateGraphicsPipelineHash(fetchShader, vertexShader, geometryShader, pixelShader, cachedFboVk->GetRenderPassObj(), LatteGPUState.contextNew); const auto innerit = it->second.find(stateHash); if (innerit == it->second.cend()) return nullptr; return innerit->second; } void VulkanRenderer::unregisterGraphicsPipeline(PipelineInfo* pipelineInfo) { bool removedFromCache = false; for (auto& topMapItr : m_pipeline_info_cache) { auto& subMap = topMapItr.second; for (auto it = subMap.cbegin(); it != subMap.cend();) { if (it->second == pipelineInfo) { subMap.erase(it); removedFromCache = true; break; } ++it; } if (removedFromCache) break; } } // make a guess if a pipeline is not essential // non-essential means that skipping these drawcalls shouldn't lead to permanently corrupted graphics bool VulkanRenderer::IsAsyncPipelineAllowed(uint32 numIndices) { // frame debuggers dont handle async well (as of 2020) if (IsTracingToolEnabled()) return false; CachedFBOVk* currentFBO = m_state.activeFBO; auto fboExtend = currentFBO->GetExtend(); if (fboExtend.width == 1600 && fboExtend.height == 1600) return false; // Splatoon ink mechanics use 1600x1600 R8 and R8G8 framebuffers, this resolution is rare enough that we can just blacklist it globally if (currentFBO->hasDepthBuffer()) return true; // aggressive filter but seems to work well so far // small index count (3,4,5,6) is often associated with full-viewport quads (which are considered essential due to often being used to generate persistent textures) if (numIndices <= 6) { return false; } return true; } // create graphics pipeline for current state PipelineInfo* VulkanRenderer::draw_createGraphicsPipeline(uint32 indexCount) { const auto fetchShader = LatteSHRC_GetActiveFetchShader(); const auto vertexShader = LatteSHRC_GetActiveVertexShader(); const auto geometryShader = LatteSHRC_GetActiveGeometryShader(); const auto pixelShader = LatteSHRC_GetActivePixelShader(); auto cachedFboVk = (CachedFBOVk*)m_state.activeFBO; uint64 minimalStateHash = draw_calculateMinimalGraphicsPipelineHash(fetchShader, LatteGPUState.contextNew); uint64 pipelineHash = draw_calculateGraphicsPipelineHash(fetchShader, vertexShader, geometryShader, pixelShader, cachedFboVk->GetRenderPassObj(), LatteGPUState.contextNew); // create PipelineInfo auto vkFBO = (CachedFBOVk*)(VulkanRenderer::GetInstance()->m_state.activeFBO); PipelineInfo* pipelineInfo = new PipelineInfo(minimalStateHash, pipelineHash, fetchShader, vertexShader, pixelShader, geometryShader); // register pipeline uint64 vsBaseHash = vertexShader->baseHash; auto it = m_pipeline_info_cache.emplace(vsBaseHash, robin_hood::unordered_flat_map<uint64, PipelineInfo*>()); auto& cache_map = it.first->second; cache_map.emplace(pipelineHash, pipelineInfo); // init pipeline compiler PipelineCompiler* pipelineCompiler = new PipelineCompiler(); bool requiresRobustBufferAccess = PipelineCompiler::CalcRobustBufferAccessRequirement(vertexShader, pixelShader, geometryShader); pipelineCompiler->InitFromCurrentGPUState(pipelineInfo, LatteGPUState.contextNew, vkFBO->GetRenderPassObj(), requiresRobustBufferAccess); pipelineCompiler->TrackAsCached(vsBaseHash, pipelineHash); // use heuristics based on parameter patterns to determine if the current drawcall is essential (non-skipable) bool allowAsyncCompile = false; if (GetConfig().async_compile) allowAsyncCompile = IsAsyncPipelineAllowed(indexCount); if (allowAsyncCompile) { // even when async is allowed, attempt synchronous creation first (which will immediately fail if the pipeline is not cached) if (pipelineCompiler->Compile(false, true, true) == false) { // shaders or pipeline not cached -> asynchronous compilation PipelineCompiler::CompileThreadPool_QueueCompilation(pipelineCompiler); } else { delete pipelineCompiler; } } else { // synchronous compilation pipelineCompiler->Compile(true, true, true); delete pipelineCompiler; } return pipelineInfo; } PipelineInfo* VulkanRenderer::draw_getOrCreateGraphicsPipeline(uint32 indexCount) { auto cache_object = draw_getCachedPipeline(); if (cache_object != nullptr) { #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug(cache_object->vertexShader == LatteSHRC_GetActiveVertexShader()); cemu_assert_debug(cache_object->geometryShader == LatteSHRC_GetActiveGeometryShader()); cemu_assert_debug(cache_object->pixelShader == LatteSHRC_GetActivePixelShader()); if (cache_object->fetchShader->key != LatteSHRC_GetActiveFetchShader()->key || cache_object->fetchShader->vkPipelineHashFragment != LatteSHRC_GetActiveFetchShader()->vkPipelineHashFragment) { debug_printf("Incompatible fetch shader %p %p\n", cache_object->fetchShader, LatteSHRC_GetActiveFetchShader()); assert_dbg(); } uint64 calcMinimalHash = draw_calculateMinimalGraphicsPipelineHash(LatteSHRC_GetActiveFetchShader(), LatteGPUState.contextNew); auto currentPrimitiveMode = LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); cemu_assert_debug(cache_object->primitiveMode == currentPrimitiveMode); cemu_assert_debug(cache_object->minimalStateHash == calcMinimalHash); #endif return cache_object; } //draw_debugPipelineHashState(); return draw_createGraphicsPipeline(indexCount); } Renderer::IndexAllocation VulkanRenderer::indexData_reserveIndexMemory(uint32 size) { VKRSynchronizedHeapAllocator::AllocatorReservation* resv = memoryManager->GetIndexAllocator().AllocateBufferMemory(size, 32); return { resv->memPtr, resv }; } void VulkanRenderer::indexData_releaseIndexMemory(IndexAllocation& allocation) { memoryManager->GetIndexAllocator().FreeReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal); } void VulkanRenderer::indexData_uploadIndexMemory(IndexAllocation& allocation) { memoryManager->GetIndexAllocator().FlushReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal); } float s_vkUniformData[512 * 4]; uint32 VulkanRenderer::uniformData_uploadUniformDataBufferGetOffset(std::span<uint8> data) { const uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1; const uint32 uniformSize = ((uint32)data.size() + bufferAlignmentM1) & ~bufferAlignmentM1; auto waitWhileCondition = [&](std::function<bool()> condition) { while (condition()) { if (m_commandBufferSyncIndex == m_commandBufferIndex) { if (m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] != m_uniformVarBufferReadIndex) { draw_endRenderPass(); SubmitCommandBuffer(); } else { // submitting work would not change readIndex, so there's no way for conditions based on it to change cemuLog_log(LogType::Force, "draw call overflowed and corrupted uniform ringbuffer. expect visual corruption"); cemu_assert_suspicious(); break; } } WaitForNextFinishedCommandBuffer(); } }; // wrap around if it doesnt fit consecutively if (m_uniformVarBufferWriteIndex + uniformSize > UNIFORMVAR_RINGBUFFER_SIZE) { waitWhileCondition([&]() { return m_uniformVarBufferReadIndex > m_uniformVarBufferWriteIndex || m_uniformVarBufferReadIndex == 0; }); m_uniformVarBufferWriteIndex = 0; } auto ringBufRemaining = [&]() { ssize_t ringBufferUsedBytes = (ssize_t)m_uniformVarBufferWriteIndex - m_uniformVarBufferReadIndex; if (ringBufferUsedBytes < 0) ringBufferUsedBytes += UNIFORMVAR_RINGBUFFER_SIZE; return UNIFORMVAR_RINGBUFFER_SIZE - 1 - ringBufferUsedBytes; }; waitWhileCondition([&]() { return ringBufRemaining() < uniformSize; }); const uint32 uniformOffset = m_uniformVarBufferWriteIndex; memcpy(m_uniformVarBufferPtr + uniformOffset, data.data(), data.size()); m_uniformVarBufferWriteIndex += uniformSize; // flush if not coherent if (!m_uniformVarBufferMemoryIsCoherent) { VkMappedMemoryRange flushedRange{}; flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; flushedRange.memory = m_uniformVarBufferMemory; flushedRange.offset = uniformOffset; flushedRange.size = uniformSize; vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange); } return uniformOffset; } void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader) { auto GET_UNIFORM_DATA_PTR = [](size_t index) { return s_vkUniformData + (index / 4); }; sint32 shaderAluConst; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: shaderAluConst = 0x400; break; case LatteConst::ShaderType::Pixel: shaderAluConst = 0; break; case LatteConst::ShaderType::Geometry: shaderAluConst = 0; // geometry shader has no ALU const break; default: UNREACHABLE; } if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) { if (shader->uniform.list_ufTexRescale.empty() == false) { for (auto& entry : shader->uniform.list_ufTexRescale) { float* xyScale = LatteTexture_getEffectiveTextureScale(shader->shaderType, entry.texUnit); memcpy(entry.currentValue, xyScale, sizeof(float) * 2); memcpy(GET_UNIFORM_DATA_PTR(entry.uniformLocation), xyScale, sizeof(float) * 2); } } if (shader->uniform.loc_alphaTestRef >= 0) { *GET_UNIFORM_DATA_PTR(shader->uniform.loc_alphaTestRef) = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF(); } if (shader->uniform.loc_pointSize >= 0) { const auto& pointSizeReg = LatteGPUState.contextNew.PA_SU_POINT_SIZE; float pointWidth = (float)pointSizeReg.get_WIDTH() / 8.0f; if (pointWidth == 0.0f) pointWidth = 1.0f / 8.0f; // minimum size *GET_UNIFORM_DATA_PTR(shader->uniform.loc_pointSize) = pointWidth; } if (shader->uniform.loc_remapped >= 0) { LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped)); } if (shader->uniform.loc_uniformRegister >= 0) { uint32* uniformRegData = (uint32*)(LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst); memcpy(GET_UNIFORM_DATA_PTR(shader->uniform.loc_uniformRegister), uniformRegData, shader->uniform.count_uniformRegister * 16); } if (shader->uniform.loc_windowSpaceToClipSpaceTransform >= 0) { sint32 viewportWidth; sint32 viewportHeight; LatteRenderTarget_GetCurrentVirtualViewportSize(&viewportWidth, &viewportHeight); // always call after _updateViewport() float* v = GET_UNIFORM_DATA_PTR(shader->uniform.loc_windowSpaceToClipSpaceTransform); v[0] = 2.0f / (float)viewportWidth; v[1] = 2.0f / (float)viewportHeight; } if (shader->uniform.loc_fragCoordScale >= 0) { LatteMRT::GetCurrentFragCoordScale(GET_UNIFORM_DATA_PTR(shader->uniform.loc_fragCoordScale)); } if (shader->uniform.loc_verticesPerInstance >= 0) { *(int*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_verticesPerInstance) = m_streamoutState.verticesPerInstance; for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++) { if (shader->uniform.loc_streamoutBufferBase[b] >= 0) { *(uint32*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_streamoutBufferBase[b]) = m_streamoutState.buffer[b].ringBufferOffset; } } } dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformData_uploadUniformDataBufferGetOffset({(uint8*)s_vkUniformData, shader->uniform.uniformRangeSize}); } } void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderStageIndex, uint32* dynamicOffsets, sint32& numDynOffsets, const PipelineInfo* pipeline_info) { numDynOffsets = 0; if (pipeline_info->dynamicOffsetInfo.hasUniformVar[shaderStageIndex]) { dynamicOffsets[0] = dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex]; numDynOffsets++; } if (pipeline_info->dynamicOffsetInfo.hasUniformBuffers[shaderStageIndex]) { for (auto& itr : pipeline_info->dynamicOffsetInfo.list_uniformBuffers[shaderStageIndex]) { dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].uniformBufferOffset[itr]; numDynOffsets++; } } } uint64 VulkanRenderer::GetDescriptorSetStateHash(LatteDecompilerShader* shader) { uint64 hash = 0; const sint32 textureCount = shader->resourceMapping.getTextureCount(); for (int i = 0; i < textureCount; ++i) { const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i); auto hostTextureUnit = relative_textureUnit; auto textureDim = shader->textureUnitDim[relative_textureUnit]; auto texUnitRegIndex = hostTextureUnit * 7; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: hostTextureUnit += LATTE_CEMU_VS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS; break; case LatteConst::ShaderType::Pixel: hostTextureUnit += LATTE_CEMU_PS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS; break; case LatteConst::ShaderType::Geometry: hostTextureUnit += LATTE_CEMU_GS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS; break; default: UNREACHABLE; } auto texture = m_state.boundTexture[hostTextureUnit]; if (!texture) continue; const uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4]; uint32 samplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit]; if (samplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) { samplerIndex += LatteDecompiler_getTextureSamplerBaseIndex(shader->shaderType); hash += LatteGPUState.contextRegister[Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 + samplerIndex * 3 + 0]; hash = std::rotl<uint64>(hash, 7); hash += LatteGPUState.contextRegister[Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 + samplerIndex * 3 + 1]; hash = std::rotl<uint64>(hash, 7); hash += LatteGPUState.contextRegister[Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 + samplerIndex * 3 + 2]; hash = std::rotl<uint64>(hash, 7); } hash = std::rotl<uint64>(hash, 7); // hash view id + swizzle mask hash += (uint64)texture->GetUniqueId(); hash = std::rotr<uint64>(hash, 21); hash += (uint64)(word4 & 0x0FFF0000); } return hash; } VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* pipeline_info, LatteDecompilerShader* shader) { const uint64 stateHash = GetDescriptorSetStateHash(shader); VkDescriptorSetLayout descriptor_set_layout; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: { const auto it = pipeline_info->vertex_ds_cache.find(stateHash); if (it != pipeline_info->vertex_ds_cache.cend()) return it->second; descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_vertexDSL; break; } case LatteConst::ShaderType::Pixel: { const auto it = pipeline_info->pixel_ds_cache.find(stateHash); if (it != pipeline_info->pixel_ds_cache.cend()) return it->second; descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_pixelDSL; break; } case LatteConst::ShaderType::Geometry: { const auto it = pipeline_info->geometry_ds_cache.find(stateHash); if (it != pipeline_info->geometry_ds_cache.cend()) return it->second; descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_geometryDSL; break; } default: UNREACHABLE; } // create new descriptor set VkDescriptorSetInfo* dsInfo = new VkDescriptorSetInfo(); dsInfo->stateHash = stateHash; dsInfo->shaderType = shader->shaderType; dsInfo->pipeline_info = pipeline_info; dsInfo->m_vkObjDescriptorSet = new VKRObjectDescriptorSet(); auto vkObjDS = dsInfo->m_vkObjDescriptorSet; VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = m_descriptorPool; allocInfo.descriptorSetCount = 1; allocInfo.pSetLayouts = &descriptor_set_layout; VkDescriptorSet result; if (vkAllocateDescriptorSets(m_logicalDevice, &allocInfo, &result) != VK_SUCCESS) { UnrecoverableError(fmt::format("Failed to allocate descriptor sets. Currently allocated: Descriptors={} TextureSamplers={} DynUniformBuffers={} StorageBuffers={}", performanceMonitor.vk.numDescriptorSets.get(), performanceMonitor.vk.numDescriptorSamplerTextures.get(), performanceMonitor.vk.numDescriptorDynUniformBuffers.get(), performanceMonitor.vk.numDescriptorStorageBuffers.get() ).c_str()); } vkObjDS->descriptorSet = result; sint32 textureCount = shader->resourceMapping.getTextureCount(); std::vector<VkWriteDescriptorSet> descriptorWrites; std::vector<VkDescriptorImageInfo> textureArray; for (int i = 0; i < textureCount; ++i) { VkDescriptorImageInfo info{}; const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i); auto hostTextureUnit = relative_textureUnit; auto textureDim = shader->textureUnitDim[relative_textureUnit]; auto texUnitRegIndex = hostTextureUnit * 7; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: hostTextureUnit += LATTE_CEMU_VS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS; break; case LatteConst::ShaderType::Pixel: hostTextureUnit += LATTE_CEMU_PS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS; break; case LatteConst::ShaderType::Geometry: hostTextureUnit += LATTE_CEMU_GS_TEX_UNIT_BASE; texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS; break; default: UNREACHABLE; } auto textureView = m_state.boundTexture[hostTextureUnit]; if (!textureView) { if (textureDim == Latte::E_DIM::DIM_1D) { info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; info.imageView = nullTexture1D.view; info.sampler = nullTexture1D.sampler; textureArray.emplace_back(info); } else { info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; info.imageView = nullTexture2D.view; info.sampler = nullTexture2D.sampler; textureArray.emplace_back(info); } continue; } // safeguard to avoid using mismatching texture dimensions // if we properly support aux hash for vs/gs this should never trigger if (textureDim == Latte::E_DIM::DIM_1D && (textureView->dim != Latte::E_DIM::DIM_1D)) { // should be 1D info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; info.imageView = nullTexture1D.view; info.sampler = nullTexture1D.sampler; textureArray.emplace_back(info); // cemuLog_log(LogType::Force, "Vulkan-Info: Shader 0x{:016x} uses 1D texture but bound texture has mismatching type (dim: 0x{:02x})", shader->baseHash, textureView->gx2Dim); continue; } else if (textureDim == Latte::E_DIM::DIM_2D && (textureView->dim != Latte::E_DIM::DIM_2D && textureView->dim != Latte::E_DIM::DIM_2D_MSAA)) { // should be 2D // is GPU7 fine with 2D access to a 2D_ARRAY texture? info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; info.imageView = nullTexture2D.view; info.sampler = nullTexture2D.sampler; textureArray.emplace_back(info); // cemuLog_log(LogType::Force, "Vulkan-Info: Shader 0x{:016x} uses 2D texture but bound texture has mismatching type (dim: 0x{:02x})", shader->baseHash, textureView->gx2Dim); continue; } info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{}; VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; LatteTexture* baseTexture = textureView->baseTexture; // get texture register word 0 uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4]; auto imageViewObj = textureView->GetSamplerView(word4); info.imageView = imageViewObj->m_textureImageView; vkObjDS->addRef(imageViewObj); // track relation between view and descriptor set vectorAppendUnique(dsInfo->list_referencedViews, textureView); textureView->AddDescriptorSetReference(dsInfo); if (!baseTexture->IsCompressedFormat()) vectorAppendUnique(dsInfo->list_fboCandidates, (LatteTextureVk*)baseTexture); uint32 stageSamplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit]; if (stageSamplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) { uint32 samplerIndex = stageSamplerIndex + LatteDecompiler_getTextureSamplerBaseIndex(shader->shaderType); const _LatteRegisterSetSampler* samplerWords = LatteGPUState.contextNew.SQ_TEX_SAMPLER + samplerIndex; // lod uint32 iMinLOD = samplerWords->WORD1.get_MIN_LOD(); uint32 iMaxLOD = samplerWords->WORD1.get_MAX_LOD(); sint32 iLodBias = samplerWords->WORD1.get_LOD_BIAS(); // apply relative lod bias from graphic pack if (baseTexture->overwriteInfo.hasRelativeLodBias) iLodBias += baseTexture->overwriteInfo.relativeLodBias; // apply absolute lod bias from graphic pack if (baseTexture->overwriteInfo.hasLodBias) iLodBias = baseTexture->overwriteInfo.lodBias; auto filterMip = samplerWords->WORD0.get_MIP_FILTER(); if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::NONE) { samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; samplerInfo.minLod = 0; samplerInfo.maxLod = 0.25f; } else if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::POINT) { samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; samplerInfo.minLod = (float)iMinLOD / 64.0f; samplerInfo.maxLod = (float)iMaxLOD / 64.0f; } else if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::LINEAR) { samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.minLod = (float)iMinLOD / 64.0f; samplerInfo.maxLod = (float)iMaxLOD / 64.0f; } else { // fallback for invalid constants samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.minLod = (float)iMinLOD / 64.0f; samplerInfo.maxLod = (float)iMaxLOD / 64.0f; } auto filterMin = samplerWords->WORD0.get_XY_MIN_FILTER(); cemu_assert_debug(filterMin != Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::BICUBIC); // todo samplerInfo.minFilter = (filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT || filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR; auto filterMag = samplerWords->WORD0.get_XY_MAG_FILTER(); samplerInfo.magFilter = (filterMag == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT || filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR; auto filterZ = samplerWords->WORD0.get_Z_FILTER(); // todo: z-filter for texture array samplers is customizable for GPU7 but OpenGL/Vulkan doesn't expose this functionality? static const VkSamplerAddressMode s_vkClampTable[] = { VK_SAMPLER_ADDRESS_MODE_REPEAT, // WRAP VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, // MIRROR VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // CLAMP_LAST_TEXEL VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE, // MIRROR_ONCE_LAST_TEXEL VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // unsupported HALF_BORDER VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, // unsupported MIRROR_ONCE_HALF_BORDER VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, // CLAMP_BORDER VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER // MIRROR_ONCE_BORDER }; auto clampX = samplerWords->WORD0.get_CLAMP_X(); auto clampY = samplerWords->WORD0.get_CLAMP_Y(); auto clampZ = samplerWords->WORD0.get_CLAMP_Z(); samplerInfo.addressModeU = s_vkClampTable[(size_t)clampX]; samplerInfo.addressModeV = s_vkClampTable[(size_t)clampY]; samplerInfo.addressModeW = s_vkClampTable[(size_t)clampZ]; auto maxAniso = samplerWords->WORD0.get_MAX_ANISO_RATIO(); if (baseTexture->overwriteInfo.anisotropicLevel >= 0) maxAniso = baseTexture->overwriteInfo.anisotropicLevel; if (maxAniso > 0) { samplerInfo.anisotropyEnable = VK_TRUE; samplerInfo.maxAnisotropy = (float)(1 << maxAniso); } else { samplerInfo.anisotropyEnable = VK_FALSE; samplerInfo.maxAnisotropy = 1.0f; } samplerInfo.mipLodBias = (float)iLodBias / 64.0f; // depth compare uint8 depthCompareMode = shader->textureUsesDepthCompare[relative_textureUnit] ? 1 : 0; static const VkCompareOp s_vkCompareOps[] { VK_COMPARE_OP_NEVER, VK_COMPARE_OP_LESS, VK_COMPARE_OP_EQUAL, VK_COMPARE_OP_LESS_OR_EQUAL, VK_COMPARE_OP_GREATER, VK_COMPARE_OP_NOT_EQUAL, VK_COMPARE_OP_GREATER_OR_EQUAL, VK_COMPARE_OP_ALWAYS, }; if (depthCompareMode == 1) { samplerInfo.compareEnable = VK_TRUE; samplerInfo.compareOp = s_vkCompareOps[(size_t)samplerWords->WORD0.get_DEPTH_COMPARE_FUNCTION()]; } else { samplerInfo.compareEnable = VK_FALSE; } // border auto borderType = samplerWords->WORD0.get_BORDER_COLOR_TYPE(); if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::TRANSPARENT_BLACK) samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; else if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::OPAQUE_BLACK) samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; else if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::OPAQUE_WHITE) samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; else { if (this->m_featureControl.deviceExtensions.custom_border_color_without_format) { samplerCustomBorderColor.sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT; samplerCustomBorderColor.format = VK_FORMAT_UNDEFINED; _LatteRegisterSetSamplerBorderColor* borderColorReg; if (shader->shaderType == LatteConst::ShaderType::Vertex) borderColorReg = LatteGPUState.contextNew.TD_VS_SAMPLER_BORDER_COLOR + stageSamplerIndex; else if (shader->shaderType == LatteConst::ShaderType::Pixel) borderColorReg = LatteGPUState.contextNew.TD_PS_SAMPLER_BORDER_COLOR + stageSamplerIndex; else // geometry borderColorReg = LatteGPUState.contextNew.TD_GS_SAMPLER_BORDER_COLOR + stageSamplerIndex; samplerCustomBorderColor.customBorderColor.float32[0] = borderColorReg->red.get_channelValue(); samplerCustomBorderColor.customBorderColor.float32[1] = borderColorReg->green.get_channelValue(); samplerCustomBorderColor.customBorderColor.float32[2] = borderColorReg->blue.get_channelValue(); samplerCustomBorderColor.customBorderColor.float32[3] = borderColorReg->alpha.get_channelValue(); samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_CUSTOM_EXT; samplerInfo.pNext = &samplerCustomBorderColor; } else { // default to transparent black if custom border color is not supported samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; } } } VKRObjectSampler* samplerObj = VKRObjectSampler::GetOrCreateSampler(&samplerInfo); vkObjDS->addRef(samplerObj); info.sampler = samplerObj->GetSampler(); textureArray.emplace_back(info); } if (textureCount > 0) { for (sint32 i = 0; i < textureCount; i++) { VkWriteDescriptorSet write_descriptor{}; write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write_descriptor.dstSet = result; write_descriptor.dstBinding = shader->resourceMapping.getTextureBaseBindingPoint() + i; write_descriptor.dstArrayElement = 0; write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write_descriptor.descriptorCount = 1; write_descriptor.pImageInfo = textureArray.data() + i; descriptorWrites.emplace_back(write_descriptor); performanceMonitor.vk.numDescriptorSamplerTextures.increment(); dsInfo->statsNumSamplerTextures++; } } // descriptor for uniform vars (as buffer) VkDescriptorBufferInfo uniformVarsBufferInfo{}; if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) { uniformVarsBufferInfo.buffer = m_uniformVarBuffer; uniformVarsBufferInfo.offset = 0; // fixed offset is always zero since we only use dynamic offsets uniformVarsBufferInfo.range = shader->uniform.uniformRangeSize; VkWriteDescriptorSet write_descriptor{}; write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write_descriptor.dstSet = result; write_descriptor.dstBinding = shader->resourceMapping.uniformVarsBufferBindingPoint; write_descriptor.dstArrayElement = 0; write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; write_descriptor.descriptorCount = 1; write_descriptor.pBufferInfo = &uniformVarsBufferInfo; descriptorWrites.emplace_back(write_descriptor); performanceMonitor.vk.numDescriptorDynUniformBuffers.increment(); dsInfo->statsNumDynUniformBuffers++; } // descriptor for uniform buffers VkDescriptorBufferInfo uniformBufferInfo{}; uniformBufferInfo.buffer = m_useHostMemoryForCache ? m_importedMem : m_bufferCache; uniformBufferInfo.offset = 0; // fixed offset is always zero since we only use dynamic offsets if (m_vendor == GfxVendor::AMD) { // on AMD we enable robust buffer access and map the remaining range of the buffer uniformBufferInfo.range = VK_WHOLE_SIZE; } else { // on other vendors (which may not allow large range values) we disable robust buffer access and use a fixed size // update: starting with their Vulkan 1.2 drivers Nvidia now also prevents out-of-bounds access. Unlike on AMD, we can't use VK_WHOLE_SIZE due to 64KB size limit of uniforms // as a workaround we set the size to the allowed maximum. A proper solution would be to use SSBOs for large uniforms / uniforms with unknown size? uniformBufferInfo.range = 1024 * 16 * 4; // XCX } for (sint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) { if (shader->resourceMapping.uniformBuffersBindingPoint[i] >= 0) { VkWriteDescriptorSet write_descriptor{}; write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write_descriptor.dstSet = result; write_descriptor.dstBinding = shader->resourceMapping.uniformBuffersBindingPoint[i]; write_descriptor.dstArrayElement = 0; write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; write_descriptor.descriptorCount = 1; write_descriptor.pBufferInfo = &uniformBufferInfo; descriptorWrites.emplace_back(write_descriptor); performanceMonitor.vk.numDescriptorDynUniformBuffers.increment(); dsInfo->statsNumDynUniformBuffers++; } } VkDescriptorBufferInfo tfStorageBufferInfo{}; if (shader->resourceMapping.tfStorageBindingPoint >= 0) { tfStorageBufferInfo.buffer = m_xfbRingBuffer; tfStorageBufferInfo.offset = 0; // offset is calculated in shader tfStorageBufferInfo.range = VK_WHOLE_SIZE; VkWriteDescriptorSet write_descriptor{}; write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write_descriptor.dstSet = result; write_descriptor.dstBinding = shader->resourceMapping.tfStorageBindingPoint; write_descriptor.dstArrayElement = 0; write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; write_descriptor.descriptorCount = 1; write_descriptor.pBufferInfo = &tfStorageBufferInfo; descriptorWrites.emplace_back(write_descriptor); performanceMonitor.vk.numDescriptorStorageBuffers.increment(); dsInfo->statsNumStorageBuffers++; } if (!descriptorWrites.empty()) vkUpdateDescriptorSets(m_logicalDevice, (uint32)descriptorWrites.size(), descriptorWrites.data(), 0, nullptr); switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: { pipeline_info->vertex_ds_cache[stateHash] = dsInfo; break; } case LatteConst::ShaderType::Pixel: { pipeline_info->pixel_ds_cache[stateHash] = dsInfo; break; } case LatteConst::ShaderType::Geometry: { pipeline_info->geometry_ds_cache[stateHash] = dsInfo; break; } default: UNREACHABLE; } return dsInfo; } void VulkanRenderer::sync_inputTexturesChanged() { bool writeFlushRequired = false; if (m_state.activeVertexDS) { for (auto& tex : m_state.activeVertexDS->list_fboCandidates) { tex->m_vkFlushIndex_read = m_state.currentFlushIndex; if (tex->m_vkFlushIndex_write == m_state.currentFlushIndex) writeFlushRequired = true; } } if (m_state.activeGeometryDS) { for (auto& tex : m_state.activeGeometryDS->list_fboCandidates) { tex->m_vkFlushIndex_read = m_state.currentFlushIndex; if (tex->m_vkFlushIndex_write == m_state.currentFlushIndex) writeFlushRequired = true; } } if (m_state.activePixelDS) { for (auto& tex : m_state.activePixelDS->list_fboCandidates) { tex->m_vkFlushIndex_read = m_state.currentFlushIndex; if (tex->m_vkFlushIndex_write == m_state.currentFlushIndex) writeFlushRequired = true; } } // barrier here if (writeFlushRequired) { VkMemoryBarrier memoryBarrier{}; memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; memoryBarrier.srcAccessMask = 0; memoryBarrier.dstAccessMask = 0; VkPipelineStageFlags srcStage = 0; VkPipelineStageFlags dstStage = 0; // src srcStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; memoryBarrier.srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; // dst dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); performanceMonitor.vk.numDrawBarriersPerFrame.increment(); m_state.currentFlushIndex++; } } void VulkanRenderer::sync_RenderPassLoadTextures(CachedFBOVk* fboVk) { bool readFlushRequired = false; // always called after draw_inputTexturesChanged() for (auto& tex : fboVk->GetTextures()) { LatteTextureVk* texVk = (LatteTextureVk*)tex; // write-before-write if (texVk->m_vkFlushIndex_write == m_state.currentFlushIndex) readFlushRequired = true; texVk->m_vkFlushIndex_write = m_state.currentFlushIndex; // todo - also check for write-before-write ? if (texVk->m_vkFlushIndex_read == m_state.currentFlushIndex) readFlushRequired = true; } // barrier here if (readFlushRequired) { VkMemoryBarrier memoryBarrier{}; memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; memoryBarrier.srcAccessMask = 0; memoryBarrier.dstAccessMask = 0; VkPipelineStageFlags srcStage = 0; VkPipelineStageFlags dstStage = 0; // src srcStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; memoryBarrier.srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; // dst dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); performanceMonitor.vk.numDrawBarriersPerFrame.increment(); m_state.currentFlushIndex++; } } void VulkanRenderer::sync_RenderPassStoreTextures(CachedFBOVk* fboVk) { uint32 flushIndex = m_state.currentFlushIndex; for (auto& tex : fboVk->GetTextures()) { LatteTextureVk* texVk = (LatteTextureVk*)tex; texVk->m_vkFlushIndex_write = flushIndex; } } void VulkanRenderer::draw_prepareDescriptorSets(PipelineInfo* pipeline_info, VkDescriptorSetInfo*& vertexDS, VkDescriptorSetInfo*& pixelDS, VkDescriptorSetInfo*& geometryDS) { const auto vertexShader = LatteSHRC_GetActiveVertexShader(); const auto geometryShader = LatteSHRC_GetActiveGeometryShader(); const auto pixelShader = LatteSHRC_GetActivePixelShader(); auto prepareShaderDescriptors = [this, &pipeline_info](LatteDecompilerShader* shader) -> VkDescriptorSetInfo* { if (!shader) return nullptr; auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, shader); descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer(); return descriptorSetInfo; }; vertexDS = prepareShaderDescriptors(vertexShader); pixelDS = prepareShaderDescriptors(pixelShader); geometryDS = prepareShaderDescriptors(geometryShader); } void VulkanRenderer::draw_updateVkBlendConstants() { uint32* blendColorConstant = LatteGPUState.contextRegister + Latte::REGADDR::CB_BLEND_RED; vkCmdSetBlendConstants(m_state.currentCommandBuffer, (const float*)blendColorConstant); } void VulkanRenderer::draw_updateDepthBias(bool forceUpdate) { uint32 frontScaleU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.getRawValue(); uint32 frontOffsetU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_OFFSET.getRawValue(); uint32 offsetClampU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.getRawValue(); if (forceUpdate == false && m_state.prevPolygonFrontScaleU32 == frontScaleU32 && m_state.prevPolygonFrontOffsetU32 == frontOffsetU32 && m_state.prevPolygonFrontClampU32 == offsetClampU32) return; m_state.prevPolygonFrontScaleU32 = frontScaleU32; m_state.prevPolygonFrontOffsetU32 = frontOffsetU32; m_state.prevPolygonFrontClampU32 = offsetClampU32; float frontScale = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.get_SCALE(); float frontOffset = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_OFFSET.get_OFFSET(); float offsetClamp = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.get_CLAMP(); frontScale /= 16.0f; vkCmdSetDepthBias(m_state.currentCommandBuffer, frontOffset, offsetClamp, frontScale); } bool s_syncOnNextDraw = false; void VulkanRenderer::draw_setRenderPass() { CachedFBOVk* fboVk = m_state.activeFBO; // update self-dependency flag if (m_state.descriptorSetsChanged || m_state.activeRenderpassFBO != fboVk) { m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); } auto vkObjRenderPass = fboVk->GetRenderPassObj(); auto vkObjFramebuffer = fboVk->GetFramebufferObj(); bool overridePassReuse = m_state.hasRenderSelfDependency && (GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier); if (!overridePassReuse && m_state.activeRenderpassFBO == fboVk) { if (m_state.descriptorSetsChanged) sync_inputTexturesChanged(); return; } draw_endRenderPass(); if (m_state.descriptorSetsChanged) sync_inputTexturesChanged(); // assume that FBO changed, update self-dependency state m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); sync_RenderPassLoadTextures(fboVk); if (m_featureControl.deviceExtensions.dynamic_rendering) { vkCmdBeginRenderingKHR(m_state.currentCommandBuffer, fboVk->GetRenderingInfo()); } else { VkRenderPass renderPass = vkObjRenderPass->m_renderPass; VkFramebuffer framebuffer = vkObjFramebuffer->m_frameBuffer; VkExtent2D extend = fboVk->GetExtend(); VkRenderPassBeginInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = framebuffer; renderPassInfo.renderArea.offset = { 0, 0 }; renderPassInfo.renderArea.extent = extend; renderPassInfo.clearValueCount = 0; vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); } m_state.activeRenderpassFBO = fboVk; vkObjRenderPass->flagForCurrentCommandBuffer(); vkObjFramebuffer->flagForCurrentCommandBuffer(); performanceMonitor.vk.numBeginRenderpassPerFrame.increment(); } void VulkanRenderer::draw_endRenderPass() { if (!m_state.activeRenderpassFBO) return; if (m_featureControl.deviceExtensions.dynamic_rendering) vkCmdEndRenderingKHR(m_state.currentCommandBuffer); else vkCmdEndRenderPass(m_state.currentCommandBuffer); sync_RenderPassStoreTextures(m_state.activeRenderpassFBO); m_state.activeRenderpassFBO = nullptr; } void LatteDraw_handleSpecialState8_clearAsDepth(); // transfer depth buffer data to color buffer void VulkanRenderer::draw_handleSpecialState5() { LatteMRT::UpdateCurrentFBO(); LatteRenderTarget_updateViewport(); LatteTextureView* colorBuffer = LatteMRT::GetColorAttachment(0); LatteTextureView* depthBuffer = LatteMRT::GetDepthAttachment(); sint32 vpWidth, vpHeight; LatteMRT::GetVirtualViewportDimensions(vpWidth, vpHeight); surfaceCopy_copySurfaceWithFormatConversion( depthBuffer->baseTexture, depthBuffer->firstMip, depthBuffer->firstSlice, colorBuffer->baseTexture, colorBuffer->firstMip, colorBuffer->firstSlice, vpWidth, vpHeight); } void VulkanRenderer::draw_beginSequence() { m_state.drawSequenceSkip = false; bool streamoutEnable = LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0; // update shader state LatteSHRC_UpdateActiveShaders(); if (LatteGPUState.activeShaderHasError) { cemuLog_logDebugOnce(LogType::Force, "Skipping drawcalls due to shader error"); m_state.drawSequenceSkip = true; cemu_assert_debug(false); return; } // update render target and texture state LatteGPUState.requiresTextureBarrier = false; while (true) { LatteGPUState.repeatTextureInitialization = false; if (!LatteMRT::UpdateCurrentFBO()) { debug_printf("Rendertarget invalid\n"); m_state.drawSequenceSkip = true; return; // no render target } if (!hasValidFramebufferAttached && !streamoutEnable) { debug_printf("Drawcall with no color buffer or depth buffer attached\n"); m_state.drawSequenceSkip = true; return; // no render target } LatteTexture_updateTextures(); if (!LatteGPUState.repeatTextureInitialization) break; } // apply render target LatteMRT::ApplyCurrentState(); // viewport and scissor box LatteRenderTarget_updateViewport(); LatteRenderTarget_updateScissorBox(); // check for conditions which would turn the drawcalls into no-ops bool rasterizerEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL() == false; // GX2SetSpecialState(0, true) enables DX_RASTERIZATION_KILL, but still expects depth writes to happen? -> Research which stages are disabled by DX_RASTERIZATION_KILL exactly // for now we use a workaround: if (!LatteGPUState.contextNew.PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA()) rasterizerEnable = true; if (rasterizerEnable == false && streamoutEnable == false) m_state.drawSequenceSkip = true; } void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) { if (m_state.drawSequenceSkip) { LatteGPUState.drawCallCounter++; return; } // fast clear color as depth if (LatteGPUState.contextNew.GetSpecialStateValues()[8] != 0) { LatteDraw_handleSpecialState8_clearAsDepth(); LatteGPUState.drawCallCounter++; return; } else if (LatteGPUState.contextNew.GetSpecialStateValues()[5] != 0) { draw_handleSpecialState5(); LatteGPUState.drawCallCounter++; return; } // prepare streamout m_streamoutState.verticesPerInstance = count; LatteStreamout_PrepareDrawcall(count, instanceCount); // update uniform vars LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); if (vertexShader) uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader); if (pixelShader) uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader); if (geometryShader) uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader); // store where the read pointer should go after command buffer execution m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex; // process index data const LattePrimitiveMode primitiveMode = static_cast<LattePrimitiveMode>(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]); Renderer::INDEX_TYPE hostIndexType; uint32 hostIndexCount; uint32 indexMin = 0; uint32 indexMax = 0; Renderer::IndexAllocation indexAllocation; LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation); VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal; // update index binding bool isPrevIndexData = false; if (hostIndexType != INDEX_TYPE::NONE) { uint32 indexBufferIndex = indexReservation->bufferIndex; uint32 indexBufferOffset = indexReservation->bufferOffset; if (m_state.activeIndexBufferOffset != indexBufferOffset || m_state.activeIndexBufferIndex != indexBufferIndex || m_state.activeIndexType != hostIndexType) { m_state.activeIndexType = hostIndexType; m_state.activeIndexBufferOffset = indexBufferOffset; m_state.activeIndexBufferIndex = indexBufferIndex; VkIndexType vkType; if (hostIndexType == INDEX_TYPE::U16) vkType = VK_INDEX_TYPE_UINT16; else if (hostIndexType == INDEX_TYPE::U32) vkType = VK_INDEX_TYPE_UINT32; else cemu_assert(false); vkCmdBindIndexBuffer(m_state.currentCommandBuffer, indexReservation->vkBuffer, indexBufferOffset, vkType); } else isPrevIndexData = true; } if (m_useHostMemoryForCache) { // direct memory access (Wii U memory space imported as a Vulkan buffer), update buffer bindings draw_updateVertexBuffersDirectAccess(); LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); if (vertexShader) draw_updateUniformBuffersDirectAccess(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex); LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); if (geometryShader) draw_updateUniformBuffersDirectAccess(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry); LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); if (pixelShader) draw_updateUniformBuffersDirectAccess(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel); } else { // synchronize vertex and uniform cache and update buffer bindings LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount); } PipelineInfo* pipeline_info; if (!isFirst) { if (m_state.activePipelineInfo->minimalStateHash != draw_calculateMinimalGraphicsPipelineHash(vertexShader->compatibleFetchShader, LatteGPUState.contextNew)) { // pipeline changed pipeline_info = draw_getOrCreateGraphicsPipeline(count); m_state.activePipelineInfo = pipeline_info; } else { pipeline_info = m_state.activePipelineInfo; #ifdef CEMU_DEBUG_ASSERT auto pipeline_info2 = draw_getOrCreateGraphicsPipeline(count); if (pipeline_info != pipeline_info2) { cemu_assert_debug(false); } #endif } } else { pipeline_info = draw_getOrCreateGraphicsPipeline(count); m_state.activePipelineInfo = pipeline_info; } auto vkObjPipeline = pipeline_info->m_vkrObjPipeline; if (vkObjPipeline->GetPipeline() == VK_NULL_HANDLE) { // invalid/uninitialized pipeline m_state.activeVertexDS = nullptr; return; } VkDescriptorSetInfo *vertexDS = nullptr, *pixelDS = nullptr, *geometryDS = nullptr; if (!isFirst && m_state.activeVertexDS) { vertexDS = m_state.activeVertexDS; pixelDS = m_state.activePixelDS; geometryDS = m_state.activeGeometryDS; m_state.descriptorSetsChanged = false; } else { draw_prepareDescriptorSets(pipeline_info, vertexDS, pixelDS, geometryDS); m_state.activeVertexDS = vertexDS; m_state.activePixelDS = pixelDS; m_state.activeGeometryDS = geometryDS; m_state.descriptorSetsChanged = true; } draw_setRenderPass(); if (m_state.currentPipeline != vkObjPipeline->GetPipeline()) { vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->GetPipeline()); vkObjPipeline->flagForCurrentCommandBuffer(); m_state.currentPipeline = vkObjPipeline->GetPipeline(); // depth bias if (pipeline_info->usesDepthBias) draw_updateDepthBias(true); } else { if (pipeline_info->usesDepthBias) draw_updateDepthBias(false); } // update blend constants if (pipeline_info->usesBlendConstants) draw_updateVkBlendConstants(); // update descriptor sets uint32_t dynamicOffsets[17 * 2]; if (vertexDS && pixelDS) { // update vertex and pixel descriptor set in a single call to vkCmdBindDescriptorSets sint32 numDynOffsetsVS; sint32 numDynOffsetsPS; draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsetsVS, pipeline_info); draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets + numDynOffsetsVS, numDynOffsetsPS, pipeline_info); VkDescriptorSet dsArray[2]; dsArray[0] = vertexDS->m_vkObjDescriptorSet->descriptorSet; dsArray[1] = pixelDS->m_vkObjDescriptorSet->descriptorSet; vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 0, 2, dsArray, numDynOffsetsVS + numDynOffsetsPS, dynamicOffsets); } else if (vertexDS) { sint32 numDynOffsets; draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsets, pipeline_info); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 0, 1, &vertexDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); } else if (pixelDS) { sint32 numDynOffsets; draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets, numDynOffsets, pipeline_info); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 1, 1, &pixelDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); } if (geometryDS) { sint32 numDynOffsets; draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, dynamicOffsets, numDynOffsets, pipeline_info); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); } // draw if (hostIndexType != INDEX_TYPE::NONE) vkCmdDrawIndexed(m_state.currentCommandBuffer, hostIndexCount, instanceCount, 0, baseVertex, baseInstance); else vkCmdDraw(m_state.currentCommandBuffer, count, instanceCount, baseVertex, baseInstance); LatteStreamout_FinishDrawcall(m_useHostMemoryForCache); LatteGPUState.drawCallCounter++; } // used in place of vertex/uniform caching when direct memory access is possible void VulkanRenderer::draw_updateVertexBuffersDirectAccess() { LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); if (!parsedFetchShader) return; for (auto& bufferGroup : parsedFetchShader->bufferGroups) { uint32 bufferIndex = bufferGroup.attributeBufferIndex; uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0]; uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1; uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; if (bufferAddress == MPTR_NULL) [[unlikely]] { bufferAddress = 0x10000000; } if (m_state.currentVertexBinding[bufferIndex].offset == bufferAddress) continue; cemu_assert_debug(bufferAddress < 0x50000000); VkBuffer attrBuffer = m_importedMem; VkDeviceSize attrOffset = bufferAddress - m_importedMemBaseAddress; vkCmdBindVertexBuffers(m_state.currentCommandBuffer, bufferIndex, 1, &attrBuffer, &attrOffset); } } void VulkanRenderer::draw_updateUniformBuffersDirectAccess(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType) { if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) { for(const auto& buf : shader->list_quickBufferList) { sint32 i = buf.index; MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0]; uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1; if (physicalAddr == MPTR_NULL) [[unlikely]] { cemu_assert_unimplemented(); continue; } uniformSize = std::min<uint32>(uniformSize, buf.size); cemu_assert_debug(physicalAddr < 0x50000000); uint32 bufferIndex = i; cemu_assert_debug(bufferIndex < 16); switch (shaderType) { case LatteConst::ShaderType::Vertex: dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; break; case LatteConst::ShaderType::Geometry: dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; break; case LatteConst::ShaderType::Pixel: dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; break; default: UNREACHABLE; } } } } void VulkanRenderer::draw_endSequence() { LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); // post-drawcall logic if (pixelShader) LatteRenderTarget_trackUpdates(); bool hasReadback = LatteTextureReadback_Update(); m_recordedDrawcalls++; if (m_recordedDrawcalls >= m_submitThreshold || hasReadback) { SubmitCommandBuffer(); } } void VulkanRenderer::debug_genericBarrier() { draw_endRenderPass(); VkMemoryBarrier memoryBarrier{}; memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; memoryBarrier.srcAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; memoryBarrier.dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; vkCmdPipelineBarrier(m_state.currentCommandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" struct CopyShaderPushConstantData_t { float vertexOffsets[4 * 2]; sint32 srcTexelOffset[2]; }; struct CopySurfacePipelineInfo { template<typename T> struct TexSliceMipMapping { TexSliceMipMapping(LatteTextureVk* texture) : m_texture(texture) {}; ~TexSliceMipMapping() { //delete vkObjPipeline; //delete vkObjRenderPass; for (auto itr : m_array) { if (itr != nullptr) delete itr; } } T* create(sint32 sliceIndex, sint32 mipIndex) { sint32 idx = m_texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); if (idx >= m_array.size()) m_array.resize(idx + 1); T* v = new T(); m_array[idx] = v; return v; } T* get(sint32 sliceIndex, sint32 mipIndex) const { sint32 idx = m_texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); if (idx >= m_array.size()) return nullptr; return m_array[idx]; } TexSliceMipMapping(const TexSliceMipMapping&) = delete; TexSliceMipMapping& operator=(const TexSliceMipMapping&) = delete; TexSliceMipMapping(TexSliceMipMapping&& rhs) { m_texture = rhs.m_texture; m_array = std::move(rhs.m_array); } TexSliceMipMapping& operator=(TexSliceMipMapping&& rhs) { m_texture = rhs.m_texture; m_array = std::move(rhs.m_array); } LatteTextureVk* m_texture; std::vector<T*> m_array; }; struct FramebufferValue { VKRObjectFramebuffer* vkObjFramebuffer; VKRObjectTextureView* vkObjImageView; }; struct DescriptorValue { VKRObjectDescriptorSet* vkObjDescriptorSet; VKRObjectTextureView* vkObjImageView; //VKRObjectSampler* vkObjSampler; }; CopySurfacePipelineInfo() = default; CopySurfacePipelineInfo(VkDevice device) : m_device(device) {} CopySurfacePipelineInfo(const CopySurfacePipelineInfo& info) = delete; ~CopySurfacePipelineInfo() { auto renderer = VulkanRenderer::GetInstance(); renderer->ReleaseDestructibleObject(vkObjRenderPass); renderer->ReleaseDestructibleObject(vkObjPipeline); for(auto& i : map_framebuffers) { for(auto& fb : i.second.m_array) { renderer->ReleaseDestructibleObject(fb->vkObjFramebuffer); renderer->ReleaseDestructibleObject(fb->vkObjImageView); } } for(auto& i : map_descriptors) { for(auto& descriptor : i.second.m_array) { renderer->ReleaseDestructibleObject(descriptor->vkObjImageView); renderer->ReleaseDestructibleObject(descriptor->vkObjDescriptorSet); } } } VkDevice m_device = nullptr; VKRObjectPipeline* vkObjPipeline{}; VKRObjectRenderPass* vkObjRenderPass{}; // map of framebuffers used with this pipeline std::unordered_map<LatteTextureVk*, TexSliceMipMapping<FramebufferValue>> map_framebuffers; // map of descriptor sets used with this pipeline std::unordered_map<LatteTextureVk*, TexSliceMipMapping<DescriptorValue>> map_descriptors; }; struct VkCopySurfaceState_t { LatteTextureVk* sourceTexture; sint32 srcMip; sint32 srcSlice; LatteTextureVk* destinationTexture; sint32 dstMip; sint32 dstSlice; sint32 width; sint32 height; }; extern std::atomic_int g_compiling_pipelines; uint64 VulkanRenderer::copySurface_getPipelineStateHash(VkCopySurfaceState_t& state) { uint64 h = 0; h += (uintptr_t)state.destinationTexture->GetFormat(); h = std::rotr<uint64>(h, 7); h += state.sourceTexture->isDepth ? 0x1111ull : 0; h = std::rotr<uint64>(h, 7); h += state.destinationTexture->isDepth ? 0x1112ull : 0; h = std::rotr<uint64>(h, 7); return h; } CopySurfacePipelineInfo* VulkanRenderer::copySurface_getCachedPipeline(VkCopySurfaceState_t& state) { const uint64 stateHash = copySurface_getPipelineStateHash(state); const auto it = m_copySurfacePipelineCache.find(stateHash); if (it == m_copySurfacePipelineCache.cend()) return nullptr; return it->second; } RendererShaderVk* _vkGenSurfaceCopyShader_vs() { const char* vsShaderSrc = "#version 450\r\n" "layout(location = 0) out ivec2 passSrcTexelOffset;\r\n" "layout(push_constant) uniform pushConstants {\r\n" "vec2 vertexOffsets[4];\r\n" "ivec2 srcTexelOffset;\r\n" "}uf_pushConstants;\r\n" "\r\n" "void main(){\r\n" //"ivec2 tUV;\r\n" "vec2 tPOS;\r\n" "switch(gl_VertexIndex)" "{\r\n" // AMD driver has issues with indexed push constant access, therefore use this workaround "case 0: tPOS = uf_pushConstants.vertexOffsets[0].xy; break;\r\n" "case 1: tPOS = uf_pushConstants.vertexOffsets[1].xy; break;\r\n" "case 2: tPOS = uf_pushConstants.vertexOffsets[3].xy; break;\r\n" "case 3: tPOS = uf_pushConstants.vertexOffsets[0].xy; break;\r\n" "case 4: tPOS = uf_pushConstants.vertexOffsets[2].xy; break;\r\n" "case 5: tPOS = uf_pushConstants.vertexOffsets[3].xy; break;\r\n" "}" "passSrcTexelOffset = uf_pushConstants.srcTexelOffset;\r\n" "gl_Position = vec4(tPOS, 0, 1.0);\r\n" "}\r\n"; std::string shaderStr(vsShaderSrc); auto vkShader = new RendererShaderVk(RendererShader::ShaderType::kVertex, 0, 0, false, false, shaderStr); vkShader->PreponeCompilation(true); return vkShader; } RendererShaderVk* _vkGenSurfaceCopyShader_ps_colorToDepth() { const char* psShaderSrc = "" "#version 450\r\n" "layout(location = 0) in flat ivec2 passSrcTexelOffset;\r\n" "layout(binding = 0) uniform sampler2D textureSrc;\r\n" "in vec4 gl_FragCoord;\r\n" "\r\n" "void main(){\r\n" "gl_FragDepth = texelFetch(textureSrc, passSrcTexelOffset + ivec2(gl_FragCoord.xy), 0).r;\r\n" "}\r\n"; std::string shaderStr(psShaderSrc); auto vkShader = new RendererShaderVk(RendererShader::ShaderType::kFragment, 0, 0, false, false, shaderStr); vkShader->PreponeCompilation(true); return vkShader; } RendererShaderVk* _vkGenSurfaceCopyShader_ps_depthToColor() { const char* psShaderSrc = "" "#version 450\r\n" "layout(location = 0) in flat ivec2 passSrcTexelOffset;\r\n" "layout(binding = 0) uniform sampler2D textureSrc;\r\n" "layout(location = 0) out vec4 colorOut0;\r\n" "in vec4 gl_FragCoord;\r\n" "\r\n" "void main(){\r\n" "colorOut0.r = texelFetch(textureSrc, passSrcTexelOffset + ivec2(gl_FragCoord.xy), 0).r;\r\n" "}\r\n"; std::string shaderStr(psShaderSrc); auto vkShader = new RendererShaderVk(RendererShader::ShaderType::kFragment, 0, 0, false, false, shaderStr); vkShader->PreponeCompilation(true); return vkShader; } VKRObjectRenderPass* VulkanRenderer::copySurface_createRenderpass(VkCopySurfaceState_t& state) { VKRObjectRenderPass::AttachmentInfo_t attachmentInfo{}; if (state.destinationTexture->isDepth) { attachmentInfo.depthAttachment.viewObj = ((LatteTextureViewVk*)state.destinationTexture->baseView)->GetViewRGBA(); attachmentInfo.depthAttachment.format = state.destinationTexture->GetFormat(); attachmentInfo.depthAttachment.hasStencil = state.destinationTexture->hasStencil; } else { attachmentInfo.colorAttachment[0].viewObj = ((LatteTextureViewVk*)state.destinationTexture->baseView)->GetViewRGBA(); attachmentInfo.colorAttachment[0].format = state.destinationTexture->GetFormat(); } VKRObjectRenderPass* vkObjRenderPass = new VKRObjectRenderPass(attachmentInfo, 1); return vkObjRenderPass; } CopySurfacePipelineInfo* VulkanRenderer::copySurface_getOrCreateGraphicsPipeline(VkCopySurfaceState_t& state) { auto cache_object = copySurface_getCachedPipeline(state); if (cache_object != nullptr) return cache_object; if (defaultShaders.copySurface_vs == nullptr) { // on first call generate shaders defaultShaders.copySurface_vs = _vkGenSurfaceCopyShader_vs(); defaultShaders.copySurface_psColor2Depth = _vkGenSurfaceCopyShader_ps_colorToDepth(); defaultShaders.copySurface_psDepth2Color = _vkGenSurfaceCopyShader_ps_depthToColor(); } RendererShaderVk* vertexShader = defaultShaders.copySurface_vs; RendererShaderVk* pixelShader = nullptr; if (state.sourceTexture->isDepth && !state.destinationTexture->isDepth) pixelShader = defaultShaders.copySurface_psDepth2Color; else if (!state.sourceTexture->isDepth && state.destinationTexture->isDepth) pixelShader = defaultShaders.copySurface_psColor2Depth; else { cemu_assert(false); } std::vector<VkPipelineShaderStageCreateInfo> shaderStages; shaderStages.emplace_back(CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_VERTEX_BIT, vertexShader->GetShaderModule(), "main")); shaderStages.emplace_back(CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_FRAGMENT_BIT, pixelShader->GetShaderModule(), "main")); // ########################################################################################################################################## const uint64 stateHash = copySurface_getPipelineStateHash(state); CopySurfacePipelineInfo* copyPipeline = new CopySurfacePipelineInfo(); m_copySurfacePipelineCache.try_emplace(stateHash, copyPipeline); VKRObjectPipeline* vkObjPipeline = new VKRObjectPipeline(); // ########################################################################################################################################## VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.pVertexBindingDescriptions = nullptr; vertexInputInfo.vertexAttributeDescriptionCount = 0; vertexInputInfo.pVertexAttributeDescriptions = nullptr; // ########################################################################################################################################## VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssembly.primitiveRestartEnable = VK_FALSE; inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // ########################################################################################################################################## VkPipelineViewportStateCreateInfo viewportState{}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.scissorCount = 1; // ########################################################################################################################################## VkPipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; rasterizer.polygonMode = VK_POLYGON_MODE_FILL; rasterizer.lineWidth = 1.0f; rasterizer.cullMode = VK_CULL_MODE_NONE; rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; // ########################################################################################################################################## VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; // ########################################################################################################################################## VkPipelineColorBlendStateCreateInfo colorBlending{}; VkPipelineColorBlendAttachmentState blendAttachment{}; if (!state.destinationTexture->isDepth) { blendAttachment.blendEnable = VK_FALSE; blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.attachmentCount = 1; colorBlending.pAttachments = &blendAttachment; colorBlending.logicOpEnable = VK_FALSE; } // ########################################################################################################################################## std::vector<VkDescriptorSetLayoutBinding> descriptorSetLayoutBindings; VkDescriptorSetLayoutBinding entry{}; entry.binding = 0; entry.descriptorCount = 1; entry.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; entry.pImmutableSamplers = nullptr; entry.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; descriptorSetLayoutBindings.emplace_back(entry); VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layoutInfo.bindingCount = (uint32_t)descriptorSetLayoutBindings.size(); layoutInfo.pBindings = descriptorSetLayoutBindings.data(); if (vkCreateDescriptorSetLayout(m_logicalDevice, &layoutInfo, nullptr, &vkObjPipeline->m_pixelDSL) != VK_SUCCESS) UnrecoverableError(fmt::format("Failed to create descriptor set layout for surface copy shader").c_str()); // ########################################################################################################################################## VkPushConstantRange pushConstantRange{}; pushConstantRange.offset = 0; pushConstantRange.size = sizeof(CopyShaderPushConstantData_t); pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &vkObjPipeline->m_pixelDSL; pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; pipelineLayoutInfo.pushConstantRangeCount = 1; VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &vkObjPipeline->m_pipelineLayout); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create pipeline layout: {}", result); vkObjPipeline->SetPipeline(VK_NULL_HANDLE); return copyPipeline; } // ################################################### bool writeDepth = state.destinationTexture->isDepth; VkPipelineDepthStencilStateCreateInfo depthStencilState{}; depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencilState.depthTestEnable = writeDepth ? VK_TRUE : VK_FALSE; depthStencilState.depthWriteEnable = writeDepth ? VK_TRUE : VK_FALSE; depthStencilState.depthCompareOp = VK_COMPARE_OP_ALWAYS; depthStencilState.depthBoundsTestEnable = false; depthStencilState.minDepthBounds = 0.0f; depthStencilState.maxDepthBounds = 1.0f; depthStencilState.stencilTestEnable = VK_FALSE; // ########################################################################################################################################## std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicState = {}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.dynamicStateCount = (uint32_t)dynamicStates.size(); dynamicState.pDynamicStates = dynamicStates.data(); // ########################################################################################################################################## copyPipeline->vkObjRenderPass = copySurface_createRenderpass(state); vkObjPipeline->addRef(copyPipeline->vkObjRenderPass); // ########################################################### VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = (uint32_t)shaderStages.size(); pipelineInfo.pStages = shaderStages.data(); pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pInputAssemblyState = &inputAssembly; pipelineInfo.pViewportState = &viewportState; pipelineInfo.pDynamicState = &dynamicState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = state.destinationTexture->isDepth?nullptr:&colorBlending; pipelineInfo.layout = vkObjPipeline->m_pipelineLayout; pipelineInfo.renderPass = copyPipeline->vkObjRenderPass->m_renderPass; pipelineInfo.pDepthStencilState = &depthStencilState; pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = nullptr; pipelineInfo.flags = 0; copyPipeline->vkObjPipeline = vkObjPipeline; VkPipeline pipeline = VK_NULL_HANDLE; result = vkCreateGraphicsPipelines(m_logicalDevice, m_pipeline_cache, 1, &pipelineInfo, nullptr, &pipeline); if (result != VK_SUCCESS) { copyPipeline->vkObjPipeline->SetPipeline(nullptr); cemuLog_log(LogType::Force, "Failed to create graphics pipeline for surface copy. Error {} Info:", (sint32)result); cemu_assert_suspicious(); } else copyPipeline->vkObjPipeline->SetPipeline(pipeline); return copyPipeline; } VKRObjectTextureView* VulkanRenderer::surfaceCopy_createImageView(LatteTextureVk* textureVk, uint32 sliceIndex, uint32 mipIndex) { VkImageViewCreateInfo viewCreateInfo = {}; viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewCreateInfo.image = textureVk->GetImageObj()->m_image; viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewCreateInfo.format = textureVk->GetFormat(); viewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; viewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; viewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; viewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; if (textureVk->isDepth) viewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; else viewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; viewCreateInfo.subresourceRange.baseMipLevel = mipIndex; viewCreateInfo.subresourceRange.levelCount = 1; viewCreateInfo.subresourceRange.baseArrayLayer = sliceIndex; viewCreateInfo.subresourceRange.layerCount = 1; VkImageView imageView; if (vkCreateImageView(m_logicalDevice, &viewCreateInfo, nullptr, &imageView) != VK_SUCCESS) UnrecoverableError("Failed to create framebuffer image view for copy surface operation"); return new VKRObjectTextureView(textureVk->GetImageObj(), imageView); } VKRObjectFramebuffer* VulkanRenderer::surfaceCopy_getOrCreateFramebuffer(VkCopySurfaceState_t& state, CopySurfacePipelineInfo* pipelineInfo) { auto itr = pipelineInfo->map_framebuffers.find(state.destinationTexture); if (itr != pipelineInfo->map_framebuffers.end()) { auto p = itr->second.get(state.dstSlice, state.dstMip); if (p != nullptr) return p->vkObjFramebuffer; } // create view VKRObjectTextureView* vkObjTextureView = surfaceCopy_createImageView(state.destinationTexture, state.dstSlice, state.dstMip); // create new framebuffer sint32 effectiveWidth, effectiveHeight; state.destinationTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, state.dstMip); std::array<VKRObjectTextureView*, 1> fbAttachments; fbAttachments[0] = vkObjTextureView; VKRObjectFramebuffer* vkObjFramebuffer = new VKRObjectFramebuffer(pipelineInfo->vkObjRenderPass, fbAttachments, Vector2i(effectiveWidth, effectiveHeight)); // register auto insertResult = pipelineInfo->map_framebuffers.try_emplace(state.destinationTexture, state.destinationTexture); CopySurfacePipelineInfo::FramebufferValue* framebufferVal = insertResult.first->second.create(state.dstSlice, state.dstMip); framebufferVal->vkObjFramebuffer = vkObjFramebuffer; framebufferVal->vkObjImageView = vkObjTextureView; return vkObjFramebuffer; } VKRObjectDescriptorSet* VulkanRenderer::surfaceCopy_getOrCreateDescriptorSet(VkCopySurfaceState_t& state, CopySurfacePipelineInfo* pipelineInfo) { auto itr = pipelineInfo->map_descriptors.find(state.sourceTexture); if (itr != pipelineInfo->map_descriptors.end()) { auto p = itr->second.get(state.srcSlice, state.srcMip); if (p != nullptr) return p->vkObjDescriptorSet; } VKRObjectDescriptorSet* vkObjDescriptorSet = new VKRObjectDescriptorSet(); // allocate new descriptor set VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = m_descriptorPool; allocInfo.descriptorSetCount = 1; allocInfo.pSetLayouts = &pipelineInfo->vkObjPipeline->m_pixelDSL; if (vkAllocateDescriptorSets(m_logicalDevice, &allocInfo, &vkObjDescriptorSet->descriptorSet) != VK_SUCCESS) { UnrecoverableError("failed to allocate descriptor set for surface copy operation"); } // create view VKRObjectTextureView* vkObjImageView = surfaceCopy_createImageView(state.sourceTexture, state.srcSlice, state.srcMip); vkObjDescriptorSet->addRef(vkObjImageView); // create sampler VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; samplerInfo.minLod = 0; samplerInfo.maxLod = 0; samplerInfo.minFilter = VK_FILTER_NEAREST; samplerInfo.magFilter = VK_FILTER_NEAREST; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.anisotropyEnable = VK_FALSE; samplerInfo.maxAnisotropy = 1.0f; samplerInfo.mipLodBias = 0; samplerInfo.compareEnable = VK_FALSE; samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; if (vkCreateSampler(m_logicalDevice, &samplerInfo, nullptr, &vkObjImageView->m_textureDefaultSampler[0]) != VK_SUCCESS) UnrecoverableError("Failed to create texture sampler for surface copy operation"); // create descriptor image info VkDescriptorImageInfo descriptorImageInfo{}; descriptorImageInfo.sampler = vkObjImageView->m_textureDefaultSampler[0]; descriptorImageInfo.imageView = vkObjImageView->m_textureImageView; descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; VkWriteDescriptorSet write_descriptor{}; write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write_descriptor.dstSet = vkObjDescriptorSet->descriptorSet; write_descriptor.dstBinding = 0; write_descriptor.dstArrayElement = 0; write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write_descriptor.descriptorCount = 1; write_descriptor.pImageInfo = &descriptorImageInfo; vkUpdateDescriptorSets(m_logicalDevice, 1, &write_descriptor, 0, nullptr); // register auto insertResult = pipelineInfo->map_descriptors.try_emplace(state.sourceTexture, state.sourceTexture); CopySurfacePipelineInfo::DescriptorValue* descriptorValue = insertResult.first->second.create(state.srcSlice, state.srcMip); descriptorValue->vkObjDescriptorSet = vkObjDescriptorSet; descriptorValue->vkObjImageView = vkObjImageView; return vkObjDescriptorSet; } void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight) { draw_endRenderPass(); //debug_printf("surfaceCopy_viaDrawcall Src %04d %04d Dst %04d %04d CopySize %04d %04d\n", srcTextureVk->width, srcTextureVk->height, dstTextureVk->width, dstTextureVk->height, effectiveCopyWidth, effectiveCopyHeight); VkImageSubresourceLayers srcImageSubresource; srcImageSubresource.aspectMask = srcTextureVk->GetImageAspect(); srcImageSubresource.baseArrayLayer = texSrcSlice; srcImageSubresource.mipLevel = texSrcMip; srcImageSubresource.layerCount = 1; VkImageSubresourceLayers dstImageSubresource; dstImageSubresource.aspectMask = dstTextureVk->GetImageAspect(); dstImageSubresource.baseArrayLayer = texDstSlice; dstImageSubresource.mipLevel = texDstMip; dstImageSubresource.layerCount = 1; VkCopySurfaceState_t copySurfaceState; copySurfaceState.sourceTexture = srcTextureVk; copySurfaceState.srcSlice = texSrcSlice; copySurfaceState.srcMip = texSrcMip; copySurfaceState.destinationTexture = dstTextureVk; copySurfaceState.dstSlice = texDstSlice; copySurfaceState.dstMip = texDstMip; copySurfaceState.width = effectiveCopyWidth; copySurfaceState.height = effectiveCopyHeight; CopySurfacePipelineInfo* copySurfacePipelineInfo = copySurface_getOrCreateGraphicsPipeline(copySurfaceState); // get framebuffer VKRObjectFramebuffer* vkObjFramebuffer = surfaceCopy_getOrCreateFramebuffer(copySurfaceState, copySurfacePipelineInfo); vkObjFramebuffer->flagForCurrentCommandBuffer(); // get descriptor set VKRObjectDescriptorSet* vkObjDescriptorSet = surfaceCopy_getOrCreateDescriptorSet(copySurfaceState, copySurfacePipelineInfo); sint32 dstEffectiveWidth, dstEffectiveHeight; dstTextureVk->GetEffectiveSize(dstEffectiveWidth, dstEffectiveHeight, texDstMip); sint32 srcEffectiveWidth, srcEffectiveHeight; srcTextureVk->GetEffectiveSize(srcEffectiveWidth, srcEffectiveHeight, texSrcMip); CopyShaderPushConstantData_t pushConstantData; float srcCopyWidth = (float)1.0f; float srcCopyHeight = (float)1.0f; // q0 vertex pushConstantData.vertexOffsets[0] = -1.0f; pushConstantData.vertexOffsets[1] = 1.0f; // q1 pushConstantData.vertexOffsets[2] = 1.0f; pushConstantData.vertexOffsets[3] = 1.0f; // q2 pushConstantData.vertexOffsets[4] = -1.0f; pushConstantData.vertexOffsets[5] = -1.0f; // q3 pushConstantData.vertexOffsets[6] = 1.0f; pushConstantData.vertexOffsets[7] = -1.0f; pushConstantData.srcTexelOffset[0] = 0; pushConstantData.srcTexelOffset[1] = 0; vkCmdPushConstants(m_state.currentCommandBuffer, copySurfacePipelineInfo->vkObjPipeline->m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pushConstantData), &pushConstantData); // draw VkRenderPassBeginInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = copySurfacePipelineInfo->vkObjRenderPass->m_renderPass; renderPassInfo.framebuffer = vkObjFramebuffer->m_frameBuffer; renderPassInfo.renderArea.offset = { 0, 0 }; renderPassInfo.renderArea.extent = { (uint32_t)effectiveCopyWidth, (uint32_t)effectiveCopyHeight }; renderPassInfo.clearValueCount = 0; VkViewport viewport{}; viewport.x = 0; viewport.y = (float)effectiveCopyHeight; viewport.width = (float)effectiveCopyWidth; viewport.height = (float)-effectiveCopyHeight; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkRect2D scissor; scissor.offset.x = 0; scissor.offset.y = 0; scissor.extent.width = effectiveCopyWidth; scissor.extent.height = effectiveCopyHeight; vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &viewport); vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &scissor); cemu_assert_debug(srcTextureVk->GetImageObj()->m_image != dstTextureVk->GetImageObj()->m_image); barrier_image<SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ>(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any modifying operations on source image to complete barrier_image<SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_WRITE>(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any operations on destination image to complete vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, copySurfacePipelineInfo->vkObjPipeline->GetPipeline()); copySurfacePipelineInfo->vkObjPipeline->flagForCurrentCommandBuffer(); m_state.currentPipeline = copySurfacePipelineInfo->vkObjPipeline->GetPipeline(); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, copySurfacePipelineInfo->vkObjPipeline->m_pipelineLayout, 0, 1, &vkObjDescriptorSet->descriptorSet, 0, nullptr); vkObjDescriptorSet->flagForCurrentCommandBuffer(); vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0); vkCmdEndRenderPass(m_state.currentCommandBuffer); barrier_image<SYNC_OP::IMAGE_READ, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the source image barrier_image<SYNC_OP::IMAGE_WRITE, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the destination image // restore viewport and scissor box vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &m_state.currentScissorRect); LatteTexture_TrackTextureGPUWrite(dstTextureVk, texDstSlice, texDstMip, LatteTexture_getNextUpdateEventCounter()); } struct vkComponentDesc_t { enum class TYPE : uint8 { NONE, UNORM, SNORM, FLOAT }; uint8 bits; TYPE type; vkComponentDesc_t(uint8 b, TYPE t) : bits(b), type(t) {}; friend bool operator==(const vkComponentDesc_t& lhs, const vkComponentDesc_t& rhs) { return lhs.bits == rhs.bits && lhs.type == rhs.type; } }; bool vkIsDepthFormat(VkFormat imageFormat) { switch (imageFormat) { case VK_FORMAT_D32_SFLOAT_S8_UINT: case VK_FORMAT_D24_UNORM_S8_UINT: case VK_FORMAT_D32_SFLOAT: case VK_FORMAT_D16_UNORM: return true; default: break; } return false; } vkComponentDesc_t vkGetFormatDepthBits(VkFormat imageFormat) { switch (imageFormat) { case VK_FORMAT_D32_SFLOAT_S8_UINT: return vkComponentDesc_t(32, vkComponentDesc_t::TYPE::FLOAT); case VK_FORMAT_D24_UNORM_S8_UINT: return vkComponentDesc_t(24, vkComponentDesc_t::TYPE::UNORM); case VK_FORMAT_D32_SFLOAT: return vkComponentDesc_t(32, vkComponentDesc_t::TYPE::FLOAT); case VK_FORMAT_D16_UNORM: return vkComponentDesc_t(16, vkComponentDesc_t::TYPE::UNORM); default: break; } return vkComponentDesc_t(0, vkComponentDesc_t::TYPE::NONE); } bool vkIsBitCompatibleColorDepthFormat(VkFormat format1, VkFormat format2) { cemu_assert_debug(vkIsDepthFormat(format1) != vkIsDepthFormat(format2)); VkFormat depthFormat, colorFormat; if (vkIsDepthFormat(format1)) { depthFormat = format1; colorFormat = format2; } else { depthFormat = format2; colorFormat = format1; } switch (depthFormat) { case VK_FORMAT_D32_SFLOAT_S8_UINT: return colorFormat == VK_FORMAT_R32_SFLOAT; case VK_FORMAT_D24_UNORM_S8_UINT: return false; // there is no 24-bit color format case VK_FORMAT_D32_SFLOAT: return colorFormat == VK_FORMAT_R32_SFLOAT; case VK_FORMAT_D16_UNORM: return colorFormat == VK_FORMAT_R16_UNORM; default: break; } return false; } void VulkanRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) { // scale copy size to effective size sint32 effectiveCopyWidth = width; sint32 effectiveCopyHeight = height; LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); sint32 sourceEffectiveWidth, sourceEffectiveHeight; sourceTexture->GetEffectiveSize(sourceEffectiveWidth, sourceEffectiveHeight, srcMip); sint32 texSrcMip = srcMip; sint32 texSrcSlice = srcSlice; sint32 texDstMip = dstMip; sint32 texDstSlice = dstSlice; LatteTextureVk* srcTextureVk = (LatteTextureVk*)sourceTexture; LatteTextureVk* dstTextureVk = (LatteTextureVk*)destinationTexture; // check if texture rescale ratios match // todo - if not, we have to use drawcall based copying if (!LatteTexture_doesEffectiveRescaleRatioMatch(srcTextureVk, texSrcMip, dstTextureVk, texDstMip)) { cemuLog_logDebug(LogType::Force, "surfaceCopy_copySurfaceViaDrawcall(): Mismatching dimensions"); return; } // check if bpp size matches if (srcTextureVk->GetBPP() != dstTextureVk->GetBPP()) { cemuLog_logDebug(LogType::Force, "surfaceCopy_copySurfaceViaDrawcall(): Mismatching BPP"); return; } surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); } // called whenever a texture is destroyed // it is guaranteed that the texture is not in use and all associated resources (descriptor sets, framebuffers) can be destroyed safely void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTexture) { for (auto& itr : m_copySurfacePipelineCache) { auto& pipelineInfo = itr.second; auto itrDescriptors = pipelineInfo->map_descriptors.find(hostTexture); if (itrDescriptors != pipelineInfo->map_descriptors.end()) { for (auto p : itrDescriptors->second.m_array) { if (p) { VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjDescriptorSet); p->vkObjDescriptorSet = nullptr; VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjImageView); p->vkObjImageView = nullptr; } } pipelineInfo->map_descriptors.erase(itrDescriptors); } auto itrFramebuffers = pipelineInfo->map_framebuffers.find(hostTexture); if (itrFramebuffers != pipelineInfo->map_framebuffers.end()) { for (auto p : itrFramebuffers->second.m_array) { if (p) { VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjFramebuffer); p->vkObjFramebuffer = nullptr; VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjImageView); p->vkObjImageView = nullptr; } } pipelineInfo->map_framebuffers.erase(itrFramebuffers); } } } void VulkanRenderer::surfaceCopy_cleanup() { for(auto& i : m_copySurfacePipelineCache) { delete i.second; } m_copySurfacePipelineCache = {}; } ================================================ FILE: src/Cafe/HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h" class LatteTextureReadbackInfoVk : public LatteTextureReadbackInfo { public: LatteTextureReadbackInfoVk(VkDevice device, LatteTextureView* textureView); ~LatteTextureReadbackInfoVk(); static uint32 GetImageSize(LatteTextureView* textureView); void StartTransfer() override; bool IsFinished() override; void ForceFinish() override; uint8* GetData() override { return m_buffer_ptr + m_buffer_offset; } uint32 GetImageSize() const { return m_image_size; } void SetBuffer(VkBuffer buffer, uint8* buffer_ptr, const uint32 buffer_offset) { m_buffer = buffer; m_buffer_ptr = buffer_ptr; m_buffer_offset = buffer_offset; } private: VkDevice m_device = nullptr; VkBuffer m_buffer = nullptr; uint8* m_buffer_ptr = nullptr; uint32 m_buffer_offset = 0; uint64 m_associatedCommandBufferId = 0; }; ================================================ FILE: src/Cafe/HW/Latte/ShaderInfo/ShaderDescription.cpp ================================================ #include "Cafe/HW/Latte/ShaderInfo/ShaderInfo.h" #include "Cafe/HW/Latte/ISA/LatteInstructions.h" namespace Latte { bool ShaderDescription::analyzeShaderCode(void* shaderProgram, size_t sizeInBytes, LatteConst::ShaderType shaderType) { assert_dbg(); // parse CF flow // we need to parse: // - Export clauses to gather info about exported attributes and written render targets // - ALU clauses to gather info about accessed uniforms (and the remapped uniform) const LatteCFInstruction* cfCode = (const LatteCFInstruction*)shaderProgram; const size_t cfMaxCount = sizeInBytes / 8; size_t cfIndex = 0; while (cfIndex < cfMaxCount) { const LatteCFInstruction* baseInstr = cfCode + cfIndex; cfIndex++; bool isALU = false; if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_DEFAULT>()) { cemu_assert_debug(cfInstr->getField_WHOLE_QUAD_MODE() == 0); cemu_assert_debug(cfInstr->getField_CALL_COUNT() == 0); // todo cemu_assert_debug(cfInstr->getField_POP_COUNT() == 0); // todo auto cond = cfInstr->getField_COND(); assert_dbg(); } else if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_ALU>()) { assert_dbg(); isALU = true; } else if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_EXPORT_IMPORT>()) { assert_dbg(); } else { cemuLog_log(LogType::Force, "ShaderDescription::analyzeShaderCode(): Missing implementation for CF opcode 0x%02x\n", baseInstr->getField_Opcode()); cemu_assert_debug(false); // todo } if (!isALU && baseInstr->getField_END_OF_PROGRAM()) break; } return true; } }; ================================================ FILE: src/Cafe/HW/Latte/ShaderInfo/ShaderInfo.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteConst.h" #include "util/containers/SmallBitset.h" namespace Latte { class ShaderDescription { public: bool analyzeShaderCode(void* shaderProgram, size_t sizeInBytes, LatteConst::ShaderType shaderType); // public data // pixel shader only SmallBitset<8> renderTargetWriteMask; // true if render target is written }; }; ================================================ FILE: src/Cafe/HW/Latte/ShaderInfo/ShaderInstanceInfo.cpp ================================================ ================================================ FILE: src/Cafe/HW/Latte/Transcompiler/LatteTC.cpp ================================================ #include "Cafe/HW/Latte/Transcompiler/LatteTC.h" #include "Cafe/HW/Latte/ISA/LatteInstructions.h" #include "util/Zir/Core/IR.h" #include "util/Zir/Core/ZpIRBuilder.h" #include "util/Zir/Core/ZpIRDebug.h" class CFBlockNode { public: CFBlockNode(uint32 cfAddress, const LatteCFInstruction& cfInstruction) : cfAddress(cfAddress), cfNext(cfAddress + sizeof(LatteCFInstruction)) { m_cfInstructions.emplace_back(cfInstruction); irBasicBlock = new ZpIR::ZpIRBasicBlock(); }; void addInstruction(const LatteCFInstruction& cfInstruction) { m_cfInstructions.emplace_back(cfInstruction); } // next CF address after this block of CF instructions (assuming no branches) void setNextAddress(uint32 addr) { cfNext = addr; } uint32 cfAddress; // offset of the first cf instruction uint32 cfNext{ 0 }; // offset of the next cf instruction if no branch happens ZpIR::ZpIRBasicBlock* irBasicBlock{}; std::vector<LatteCFInstruction> m_cfInstructions; private: }; // emit IR code for all clauses in a DAG node void LatteTCGenIR::genIRForNode(CFBlockNode& node) { m_irGenContext.reset(); m_irGenContext.irBuilder = new ZpIR::BasicBlockBuilder(node.irBasicBlock); m_irGenContext.isEntryBasicBlock = (&node == m_ctx.mainFunctionDAG.GetEntryNode()); // for vertex shaders add initialization code to main() if (&node == m_ctx.mainFunctionDAG.GetEntryNode() && m_ctx.shaderType == SHADER_TYPE::VERTEX) { for (uint32 channel = 0; channel < 4; channel++) { ZpIR::IRReg irReg = m_irGenContext.irBuilder->createReg(ZpIR::DataType::U32); m_irGenContext.irBuilder->emit_RR(ZpIR::IR::OpCode::MOV, irReg, m_irGenContext.irBuilder->createConstU32(0)); this->m_irGenContext.activeVars.set(0, channel, irReg); } // todo - correctly init R0 based on currently set context register state } for (auto& itr : node.m_cfInstructions) { const auto opcode = itr.getField_Opcode(); if (const auto cfInstr = itr.getParserIfOpcodeMatch<LatteCFInstruction_DEFAULT>()) { if(opcode == LatteCFInstruction::OPCODE::INST_CALL_FS) processCF_CALL_FS(*cfInstr); else assert_dbg(); } else if (const auto cfInstr = itr.getParserIfOpcodeMatch<LatteCFInstruction_ALU>()) { if (opcode == LatteCFInstruction::OPCODE::INST_ALU) processCF_ALU(*cfInstr); else assert_dbg(); } else if (const auto cfInstr = itr.getParserIfOpcodeMatch<LatteCFInstruction_EXPORT_IMPORT>()) { if (opcode == LatteCFInstruction::OPCODE::INST_EXPORT || opcode == LatteCFInstruction::OPCODE::INST_EXPORT_DONE) processCF_EXPORT(*cfInstr); else assert_dbg(); } else { debug_printf("Missing implementation for CF opcode 0x%02x\n", itr.getField_Opcode()); assert_dbg(); // todo } } } // parse CF program and create unlinked DAG void LatteTCGenIR::parseCF_createNodes(NodeDAG& nodeDAG) { const LatteCFInstruction* cfCode = (const LatteCFInstruction*)m_ctx.programData; const size_t cfMaxCount = m_ctx.programSize / 8; // quick prepass to gather a list of jump destinations used by the next pass // todo // linear pass where we turn uninterrupted sequences of CF instructions (no branch to or from) into CFBlockNode // algorithm description: // 1) Create CFBlockNode from first CF instruction. Make it the currently active node // 2) For each remaining (1 .. n) CF instruction of program // 2.1) If CF instruction can be merged into active node (no branch destination, no conditionals or other control flow branches) then add it to the currently active node // 2.2) Otherwise finalize active node, add it to node list. Then create new CFBlockNode node from CF instruction and make it active node // 3) Finalize active node and add to node list cemu_assert_debug(cfMaxCount != 0); // zero not allowed CFBlockNode* activeNode = new CFBlockNode(0, cfCode[0]); // first instruction becomes the initial node size_t cfIndex = 1; //m_nodes.emplace_back(activeNode); while (cfIndex < cfMaxCount) { const LatteCFInstruction* baseInstr = cfCode + cfIndex; cfIndex++; bool canMerge; bool isALU = false; if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_DEFAULT>()) { cemu_assert_debug(cfInstr->getField_WHOLE_QUAD_MODE() == 0); cemu_assert_debug(cfInstr->getField_CALL_COUNT() == 0); // todo cemu_assert_debug(cfInstr->getField_POP_COUNT() == 0); // todo auto cond = cfInstr->getField_COND(); assert_dbg(); //cfInstr->getField_COND() == LatteCFInstruction::CF_COND::CF_COND_ACTIVE; } else if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_ALU>()) { // always merge ALU clauses since they dont have their own condition modes? // todo - except if they are a jump target canMerge = true; isALU = true; } else if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_EXPORT_IMPORT>()) { // no extra conditions, always merge canMerge = true; } else { debug_printf("Missing implementation for CF opcode 0x%02x\n", baseInstr->getField_Opcode()); assert_dbg(); // todo } if (canMerge) { activeNode->addInstruction(*baseInstr); } else { activeNode->setNextAddress((uint32)cfIndex - 1); nodeDAG.m_nodes.emplace_back(activeNode); // start new active node activeNode = new CFBlockNode((uint32)cfIndex - 1, *baseInstr); } if (!isALU && baseInstr->getField_END_OF_PROGRAM()) break; } // finalize last node cemu_assert_debug(!activeNode->m_cfInstructions.empty()); nodeDAG.m_nodes.emplace_back(activeNode); } void LatteTCGenIR::parseCFToDAG() { // parse CF and create preliminary node DAG parseCF_createNodes(m_ctx.mainFunctionDAG); // link up the nodes cemu_assert_debug(m_ctx.mainFunctionDAG.m_nodes.size() == 1); // assign to ir object for (auto& itr : m_ctx.mainFunctionDAG.m_nodes) m_ctx.irObject->m_basicBlocks.emplace_back(itr->irBasicBlock); m_ctx.irObject->m_entryBlocks.emplace_back(m_ctx.mainFunctionDAG.m_nodes[0]->irBasicBlock); } void LatteTCGenIR::emitIR() { cemu_assert_debug(m_ctx.mainFunctionDAG.m_nodes.size() == 1); for (auto& itr : m_ctx.mainFunctionDAG.m_nodes) { genIRForNode(*itr); } } void LatteTCGenIR::cleanup() { // clean up //for (auto itr : m_ctx.list_irNodesCtx) // delete itr; //m_ctx.list_irNodesCtx.clear(); } void LatteTCGenIR::setVertexShaderContext(const LatteFetchShader* parsedFetchShader, const uint32* vtxSemanticTable) { m_vertexShaderCtx.parsedFetchShader = parsedFetchShader; m_vertexShaderCtx.vtxSemanticTable = vtxSemanticTable; } ZpIR::ZpIRFunction* LatteTCGenIR::transcompileLatteToIR(const void* programData, uint32 programSize, SHADER_TYPE shaderType) { //return nullptr; ZpIR::ZpIRFunction* irObject = new ZpIR::ZpIRFunction(); // init context m_ctx = {}; m_ctx.programData = (const uint32*)programData; m_ctx.programSize = programSize; m_ctx.irObject = irObject; m_ctx.shaderType = shaderType; // parse control flow instructions and convert it to list of CFBlockNode // each node is a single IR basic block, consisting of one or multiple CF instructions parseCFToDAG(); // process clauses and emit IR nodes emitIR(); // cleanup cleanup(); return irObject; } ================================================ FILE: src/Cafe/HW/Latte/Transcompiler/LatteTC.h ================================================ #pragma once #include "Cafe/HW/Latte/ISA/LatteInstructions.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Core/LatteConst.h" #include "util/Zir/Core/IR.h" #include <boost/container/static_vector.hpp> namespace ZpIR { class BasicBlockBuilder; } // transforms GPU7/Latte shader binaries into an SSA IR (ZpIR) representation class LatteTCGenIR { //friend class CFNodeInfo; friend struct CFNodeInfo_CALL_FS; private: struct NodeDAG // one per function (shaders usually have main only) { std::vector<class CFBlockNode*> m_nodes; // class CFBlockNode* entrypoint; class CFBlockNode* GetEntryNode() { return m_nodes[0]; } }; public: typedef unsigned short GPRElementIndex; // grpIndex*4+channel enum SHADER_TYPE { VERTEX, GEOMETRY, PIXEL, }; void setVertexShaderContext(const LatteFetchShader* parsedFetchShader, const uint32* vtxSemanticTable); ZpIR::ZpIRFunction* transcompileLatteToIR(const void* programData, uint32 programSize, SHADER_TYPE shaderType); private: ZpIR::IRReg getIRRegFromGPRElement(uint32 gprIndex, uint32 channel, ZpIR::DataType typeHint); ZpIR::IRReg getTypedIRRegFromGPRElement(uint32 gprIndex, uint32 channel, ZpIR::DataType type); ZpIR::IRReg loadALUOperand(LatteALUSrcSel srcSel, uint8 srcChan, bool isNeg, bool isAbs, bool isRel, uint8 indexMode, const uint32* literalData, ZpIR::DataType typeHint, bool convertOnTypeMismatch); void emitALUGroup(const class LatteClauseInstruction_ALU* aluUnit[5], const uint32* literalData); void genIRForNode(class CFBlockNode& node); void parseCFToDAG(); void parseCF_createNodes(NodeDAG& nodeDAG); void emitIR(); void cleanup(); // IR emitter void processCF_CALL_FS(const LatteCFInstruction_DEFAULT& cfInstruction); void processCF_ALU(const LatteCFInstruction_ALU& cfInstruction); void processCF_EXPORT(const LatteCFInstruction_EXPORT_IMPORT& cfInstruction); // helpers void CF_CALL_FS_emitFetchAttribute(LatteParsedFetchShaderAttribute_t& attribute, Latte::GPRType dstGPR); private: // tracks mapping GPR<->IR variable for current basic block struct IREmitterActiveVars { bool get(uint32 gprIndex, uint32 channelIndex, ZpIR::IRReg& reg) { size_t index = gprIndex * 4 + channelIndex; if (!m_present.test(index)) return false; reg = m_irReg[index]; return true; } void set(uint32 gprIndex, uint32 channelIndex, ZpIR::IRReg reg) { size_t index = gprIndex * 4 + channelIndex; m_present.set(index); m_irReg[index] = reg; } // set register after current group struct DelayedAssignment { uint16 index; ZpIR::IRReg reg; bool isSet{false}; }; struct DelayedAssignmentPVPS { ZpIR::IRReg reg; bool isSet{ false }; }; void setAfterGroup(uint8 aluUnit, uint32 gprIndex, uint32 channelIndex, ZpIR::IRReg reg) { cemu_assert_debug(aluUnit < 5); cemu_assert_debug(!m_delayedAssignments[aluUnit].isSet); m_delayedAssignments[aluUnit].reg = reg; m_delayedAssignments[aluUnit].index = gprIndex * 4 + channelIndex; m_delayedAssignments[aluUnit].isSet = true; } void setAfterGroupPVPS(uint8 aluUnit, ZpIR::IRReg reg) { cemu_assert_debug(aluUnit < 5); cemu_assert_debug(!m_delayedAssignmentsPSPV[aluUnit].isSet); m_delayedAssignmentsPSPV[aluUnit].reg = reg; m_delayedAssignmentsPSPV[aluUnit].isSet = true; } void applyDelayedAfterGroup() { // GPRs for (size_t i = 0; i < 5; i++) { auto& assignment = m_delayedAssignments[i]; if(!assignment.isSet) continue; setGPR(assignment.index, assignment.reg); assignment.isSet = false; } // PV/PS for (size_t i = 0; i < 5; i++) { auto& assignment = m_delayedAssignmentsPSPV[i]; if (!assignment.isSet) continue; setPVPS((uint32)i, assignment.reg); assignment.isSet = false; } } void reset() { m_present.reset(); } private: void setGPR(uint32 gprChannelindex, ZpIR::IRReg reg) { m_present.set(gprChannelindex); m_irReg[gprChannelindex] = reg; } void setPVPS(uint32 unitIndex, ZpIR::IRReg reg) { m_presentPVPS.set(unitIndex); m_irRegPVPS[unitIndex] = reg; } ZpIR::IRReg m_irReg[128 * 4]{}; ZpIR::IRReg m_irRegPVPS[5]{}; std::bitset<128 * 4> m_present; std::bitset<5> m_presentPVPS; DelayedAssignment m_delayedAssignments[5]{}; DelayedAssignmentPVPS m_delayedAssignmentsPSPV[5]{}; }; struct { IREmitterActiveVars activeVars; ZpIR::BasicBlockBuilder* irBuilder; bool isEntryBasicBlock{}; void reset() { activeVars.reset(); } }m_irGenContext; struct { const uint32* programData; uint32 programSize; //std::vector<struct CFNodeInfo*> list_irNodesCtx; // first node in flow of main() function //struct CFNodeInfo* mainEntry; SHADER_TYPE shaderType; NodeDAG mainFunctionDAG; // current IR object struct ZpIR::ZpIRFunction* irObject; }m_ctx; // vertex shader info struct { const LatteFetchShader* parsedFetchShader{}; const uint32* vtxSemanticTable{}; }m_vertexShaderCtx{}; }; ================================================ FILE: src/Cafe/HW/Latte/Transcompiler/LatteTCGenIR.cpp ================================================ #include "Cafe/HW/Latte/Transcompiler/LatteTC.h" #include "Cafe/HW/Latte/ISA/LatteInstructions.h" #include "util/Zir/Core/ZpIRBuilder.h" void LatteTCGenIR::CF_CALL_FS_emitFetchAttribute(LatteParsedFetchShaderAttribute_t& attribute, Latte::GPRType dstGPR) { auto irBuilder = m_irGenContext.irBuilder; // extract each channel for (sint32 t = 0; t < 4; t++) { uint32 gprElementIndex = (uint32)dstGPR * 4 + t; LatteConst::VertexFetchDstSel ds = (LatteConst::VertexFetchDstSel)attribute.ds[t]; switch (ds) { case LatteConst::VertexFetchDstSel::X: case LatteConst::VertexFetchDstSel::Y: case LatteConst::VertexFetchDstSel::Z: case LatteConst::VertexFetchDstSel::W: { uint8 channelIndex = (uint8)((uint32)ds - (uint32)LatteConst::VertexFetchDstSel::X); ZpIR::IRReg resultHolder = m_irGenContext.irBuilder->createReg(ZpIR::DataType::U32); ZpIR::LocationSymbolName importSource = ZpIR::ShaderSubset::ShaderImportLocation().SetVertexAttribute(attribute.semanticId, channelIndex); m_irGenContext.irBuilder->emit_IMPORT(importSource, resultHolder); // swap endianness if (attribute.endianSwap == LatteConst::VertexFetchEndianMode::SWAP_U32) { // todo - this may be more complex depending on type ZpIR::IRReg elementResult; irBuilder->emit_RR(ZpIR::IR::OpCode::SWAP_ENDIAN, irBuilder->createReg(elementResult, ZpIR::DataType::U32), resultHolder); resultHolder = elementResult; } bool isSigned = attribute.isSigned; // transform LatteConst::VertexFetchFormat fmt = (LatteConst::VertexFetchFormat)attribute.format; LatteClauseInstruction_VTX::NUM_FORMAT_ALL nfa = (LatteClauseInstruction_VTX::NUM_FORMAT_ALL)attribute.nfa; if (fmt == LatteConst::VertexFetchFormat::VTX_FMT_32_32_32_FLOAT || fmt == LatteConst::VertexFetchFormat::VTX_FMT_32_32_FLOAT) { uint32 numComp; if (fmt == LatteConst::VertexFetchFormat::VTX_FMT_32_32_32_FLOAT) numComp = 3; else if (fmt == LatteConst::VertexFetchFormat::VTX_FMT_32_32_FLOAT) numComp = 2; else { cemu_assert_debug(false); } cemu_assert_debug(attribute.endianSwap == LatteConst::VertexFetchEndianMode::SWAP_U32); cemu_assert_debug(nfa == LatteClauseInstruction_VTX::NUM_FORMAT_ALL::NUM_FORMAT_SCALED); cemu_assert_debug(channelIndex < numComp); ZpIR::IRReg elementResult; irBuilder->emit_RR(ZpIR::IR::OpCode::BITCAST, irBuilder->createReg(elementResult, ZpIR::DataType::F32), resultHolder); resultHolder = elementResult; } else if (fmt == LatteConst::VertexFetchFormat::VTX_FMT_8_8_8_8) { uint32 numComp; switch (fmt) { case LatteConst::VertexFetchFormat::VTX_FMT_8_8_8_8: numComp = 4; break; case LatteConst::VertexFetchFormat::VTX_FMT_8_8_8: numComp = 3; break; case LatteConst::VertexFetchFormat::VTX_FMT_8_8: numComp = 2; break; case LatteConst::VertexFetchFormat::VTX_FMT_8: numComp = 1; break; } cemu_assert_debug(attribute.endianSwap == LatteConst::VertexFetchEndianMode::SWAP_NONE); cemu_assert_debug(channelIndex < numComp); if (nfa == LatteClauseInstruction_VTX::NUM_FORMAT_ALL::NUM_FORMAT_NORM) { // scaled if (isSigned) { assert_dbg(); // we can fake sign extend by subtracting 128? Would be faster than the AND + Conditional OR } else { resultHolder = irBuilder->emit_RR(ZpIR::IR::OpCode::CONVERT_INT_TO_FLOAT, ZpIR::DataType::F32, resultHolder); resultHolder = irBuilder->emit_RRR(ZpIR::IR::OpCode::DIV, ZpIR::DataType::F32, resultHolder, irBuilder->createConstF32(255.0f)); } } else { assert_dbg(); } } else { assert_dbg(); } // todo - we need a sign-extend instruction for this which should take arbitrary bit count this->m_irGenContext.activeVars.set(dstGPR, t, resultHolder); // set GPR.channel to the result break; } case LatteConst::VertexFetchDstSel::CONST_0F: { // todo - this could also be an integer zero. Use attribute format / other channel info to determine if this type is integer/float ZpIR::IRReg resultHolder; irBuilder->emit_RR(ZpIR::IR::OpCode::MOV, irBuilder->createReg(resultHolder, ZpIR::DataType::F32), irBuilder->createConstF32(0.0f)); this->m_irGenContext.activeVars.set(dstGPR, t, resultHolder); break; } case LatteConst::VertexFetchDstSel::CONST_1F: { ZpIR::IRReg resultHolder; irBuilder->emit_RR(ZpIR::IR::OpCode::MOV, irBuilder->createReg(resultHolder, ZpIR::DataType::F32), irBuilder->createConstF32(1.0f)); this->m_irGenContext.activeVars.set(dstGPR, t, resultHolder); break; } default: assert_dbg(); } } } void LatteTCGenIR::processCF_CALL_FS(const LatteCFInstruction_DEFAULT& cfInstruction) { auto fetchShader = m_vertexShaderCtx.parsedFetchShader; auto semanticTable = m_vertexShaderCtx.vtxSemanticTable; // generate IR to decode vertex attributes cemu_assert_debug(fetchShader->bufferGroupsInvalid.size() == 0); // todo for(auto& bufferGroup : fetchShader->bufferGroups) { for (sint32 i = 0; i < bufferGroup.attribCount; i++) { auto& attribute = bufferGroup.attrib[i]; uint32 dstGPR = 0; // get register index based on vtx semantic table uint32 attributeShaderLoc = 0xFFFFFFFF; for (sint32 f = 0; f < 32; f++) { if (semanticTable[f] == attribute.semanticId) { attributeShaderLoc = f; break; } } if (attributeShaderLoc == 0xFFFFFFFF) continue; // attribute is not mapped to VS input dstGPR = attributeShaderLoc + 1; // R0 is skipped // emit IR code for attribute import (decode into GPR) CF_CALL_FS_emitFetchAttribute(attribute, dstGPR); } } } // get IRReg for Latte GPR (single channel) typeHint is used when register has to be imported // if convertOnTypeMismatch is set then we bitcast the register on type mismatch ZpIR::IRReg LatteTCGenIR::getIRRegFromGPRElement(uint32 gprIndex, uint32 channel, ZpIR::DataType typeHint) { // get IR register for <GPR>.<channel> from currently active context ZpIR::IRReg r; if (m_irGenContext.activeVars.get(gprIndex, channel, r)) return r; // if GPR.channel is not known // in the entry basic block we can assume a value of zero because there is nowhere to import from if (m_irGenContext.isEntryBasicBlock) { if (typeHint == ZpIR::DataType::F32) return m_irGenContext.irBuilder->createConstF32(0.0f); else if (typeHint == ZpIR::DataType::U32) return m_irGenContext.irBuilder->createConstU32(0); else if (typeHint == ZpIR::DataType::S32) return m_irGenContext.irBuilder->createConstS32(0); cemu_assert_debug(false); } // otherwise create import and resolve later during register allocation r = m_irGenContext.irBuilder->createReg(typeHint); m_irGenContext.irBuilder->addImport(r, gprIndex*4 + channel); return r; } // similar to getIRRegFromGPRElement() but will bitcast the type if it mismatches ZpIR::IRReg LatteTCGenIR::getTypedIRRegFromGPRElement(uint32 gprIndex, uint32 channel, ZpIR::DataType type) { auto irReg = getIRRegFromGPRElement(gprIndex, channel, type); if (m_irGenContext.irBuilder->getRegType(irReg) == type) return irReg; // type does not match, bitcast into new reg auto newReg = m_irGenContext.irBuilder->createReg(type); m_irGenContext.irBuilder->emit_RR(ZpIR::IR::OpCode::BITCAST, newReg, irReg); // remember converted register since its likely that it is accessed with the same type again // todo - ideally, we would keep track of all the types. But it has to be efficient m_irGenContext.activeVars.set(gprIndex, channel, newReg); return newReg; } // try to determine the type of the constant from the raw u32 value ZpIR::DataType _guessTypeFromConstantValue(uint32 bits) { if (bits == 0x3F800000) // float 1.0 return ZpIR::DataType::F32; return ZpIR::DataType::S32; } // maybe pass a type hint parameter ZpIR::IRReg LatteTCGenIR::loadALUOperand(LatteALUSrcSel srcSel, uint8 srcChan, bool isNeg, bool isAbs, bool isRel, uint8 indexMode, const uint32* literalData, ZpIR::DataType typeHint, bool convertOnTypeMismatch) { if (srcSel.isGPR()) { //LatteTCGenIR::GPRElement gprElement = srcSel.getGPR() * 4 + srcChan; if (isRel) assert_dbg(); ZpIR::IRReg reg; if(convertOnTypeMismatch) reg = getTypedIRRegFromGPRElement(srcSel.getGPR(), srcChan, typeHint); else reg = getIRRegFromGPRElement(srcSel.getGPR(), srcChan, typeHint); // if additional transformations are applied then we create a temporary IRReg here // todo - is caching&recycling the transformed registers worth it? if (isAbs || isNeg) { // create new var and apply transformation assert_dbg(); } return reg; } else if (srcSel.isAnyConst()) { if (srcSel.isConst_0F()) { return m_irGenContext.irBuilder->createConstF32(0.0f); // todo - could also be integer type constant? Try to find a way to predict the type correctly } else assert_dbg(); } else if (srcSel.isLiteral()) { // literal constant // we guess the type return m_irGenContext.irBuilder->createTypedConst(literalData[srcChan], _guessTypeFromConstantValue(literalData[srcChan])); } else if (srcSel.isCFile()) { // constant registers / uniform registers uint32 cfileIndex = srcSel.getCFile(); auto newReg = m_irGenContext.irBuilder->createReg(typeHint); ZpIR::LocationSymbolName importSource = ZpIR::ShaderSubset::ShaderImportLocation().SetUniformRegister(cfileIndex*4 + srcChan); m_irGenContext.irBuilder->emit_IMPORT(importSource, newReg); return newReg; } else assert_dbg(); return 0; } void LatteTCGenIR::emitALUGroup(const LatteClauseInstruction_ALU* aluUnit[5], const uint32* literalData) { //struct //{ // uint32 gprElementIndex; // ZpIR::IRReg irReg; // bool isSet; //}groupOutput[5] = {}; ZpIR::BasicBlockBuilder* irBuilder = m_irGenContext.irBuilder; // used by MOV instruction which can be used with any 32bit type (float, int, uint) auto getMOVSourceType = [&](const LatteClauseInstruction_ALU_OP2* instrOP2) -> ZpIR::DataType { auto sel = instrOP2->getSrc0Sel(); if (sel.isGPR()) { ZpIR::IRReg r; if (!m_irGenContext.activeVars.get(sel.getGPR(), instrOP2->getSrc0Chan(), r)) { // import, do we have an alternative way to guess the type? // for now lets assume float because it will be correct more often than not // getting the type wrong means a temporary register and two bit cast instructions will be spawned return ZpIR::DataType::F32; } return m_irGenContext.irBuilder->getRegType(r); } else if (sel.isLiteral()) { return _guessTypeFromConstantValue(literalData[instrOP2->getSrc0Chan()]); } else assert_dbg(); return ZpIR::DataType::S32; }; auto getOp0Reg = [&](const LatteClauseInstruction_ALU_OP2* instrOP2, ZpIR::DataType type) -> ZpIR::IRReg { // todo - pass type hint, so internally correct type is used if register needs to be created ZpIR::IRReg r = loadALUOperand(instrOP2->getSrc0Sel(), instrOP2->getSrc0Chan(), instrOP2->isSrc0Neg(), false, instrOP2->isSrc0Rel(), instrOP2->getIndexMode(), literalData, type, true); // make sure type matches with 'type' (loadALUOperand should convert) cemu_assert_debug(irBuilder->getRegType(r) == type); return r; }; auto getOp1Reg = [&](const LatteClauseInstruction_ALU_OP2* instrOP2, ZpIR::DataType type) -> ZpIR::IRReg { // todo - pass type hint, so internally correct type is used if register needs to be created ZpIR::IRReg r = loadALUOperand(instrOP2->getSrc1Sel(), instrOP2->getSrc1Chan(), instrOP2->isSrc1Neg(), false, instrOP2->isSrc1Rel(), instrOP2->getIndexMode(), literalData, type, true); // make sure type matches with 'type' (loadALUOperand should convert) cemu_assert_debug(irBuilder->getRegType(r) == type); return r; }; auto getResultReg = [&](uint8 aluUnit, const LatteClauseInstruction_ALU_OP2* instrOP2, ZpIR::DataType type) -> ZpIR::IRReg { // create output register ZpIR::IRReg r = m_irGenContext.irBuilder->createReg(type); cemu_assert_debug(instrOP2->getDestClamp() == 0); // todo cemu_assert_debug(instrOP2->getDestRel() == 0); // todo cemu_assert_debug(instrOP2->getOMod() == 0); // todo if (instrOP2->getWriteMask()) { // output to GPR m_irGenContext.activeVars.setAfterGroup(aluUnit, instrOP2->getDestGpr(), instrOP2->getDestElem(), r); } else { // output only to PV/PS assert_dbg(); } // output to PV/PS // todo // check for disabled destination GPR? // also assign PV/PS // todo //ZpIR::IRReg r; //if (m_irGenContext.activeVars.get(gprIndex, channel, r)) // return r; //// if GPR not present then create import for it //r = m_irGenContext.irBuilder->createReg(typeHint); //m_irGenContext.irBuilder->addImport(r, 0x12345678); return r; }; for (sint32 aluUnitIndex = 0; aluUnitIndex < 5; aluUnitIndex++) { const LatteClauseInstruction_ALU* instr = aluUnit[aluUnitIndex]; if (instr == nullptr) continue; if (instr->isOP3()) { assert_dbg(); } else { auto opcode2 = instr->getOP2Code(); auto instrOP2 = instr->getOP2Instruction(); // prepare operands, load them into IRVars if they aren't already uint8 indexMode = instrOP2->getIndexMode(); switch (opcode2) { case LatteClauseInstruction_ALU::OPCODE_OP2::MUL: case LatteClauseInstruction_ALU::OPCODE_OP2::MUL_IEEE: { // how to implement this with least amount of copy paste and still having very good performance? // maybe use lambdas? Or functions? irBuilder->emit_RRR(ZpIR::IR::OpCode::MUL, getResultReg(aluUnitIndex, instrOP2, ZpIR::DataType::F32), getOp0Reg(instrOP2, ZpIR::DataType::F32), getOp1Reg(instrOP2, ZpIR::DataType::F32)); break; } case LatteClauseInstruction_ALU::OPCODE_OP2::MOV: { // MOV is type-agnostic, but some flags might make it apply float operations ZpIR::DataType guessedType = getMOVSourceType(instrOP2); irBuilder->emit_RR(ZpIR::IR::OpCode::MOV, getResultReg(aluUnitIndex, instrOP2, guessedType), getOp0Reg(instrOP2, guessedType)); break; } case LatteClauseInstruction_ALU::OPCODE_OP2::DOT4: { // reduction opcode // must be mirrored to .xyzw units cemu_assert_debug(aluUnitIndex == 0); cemu_assert_debug(aluUnit[0]->getOP2Code() == aluUnit[1]->getOP2Code()); cemu_assert_debug(aluUnit[1]->getOP2Code() == aluUnit[2]->getOP2Code()); cemu_assert_debug(aluUnit[2]->getOP2Code() == aluUnit[3]->getOP2Code()); auto unit_x = instrOP2; auto unit_y = aluUnit[1]->getOP2Instruction(); auto unit_z = aluUnit[2]->getOP2Instruction(); auto unit_w = aluUnit[3]->getOP2Instruction(); cemu_assert_debug(unit_x->getDestClamp() == false); cemu_assert_debug(unit_x->getOMod() == 0); cemu_assert_debug(unit_x->getDestRel() == false); ZpIR::IRReg productX = irBuilder->emit_RRR(ZpIR::IR::OpCode::MUL, ZpIR::DataType::F32, getOp0Reg(unit_x, ZpIR::DataType::F32), getOp1Reg(unit_x, ZpIR::DataType::F32)); ZpIR::IRReg productY = irBuilder->emit_RRR(ZpIR::IR::OpCode::MUL, ZpIR::DataType::F32, getOp0Reg(unit_y, ZpIR::DataType::F32), getOp1Reg(unit_y, ZpIR::DataType::F32)); ZpIR::IRReg productZ = irBuilder->emit_RRR(ZpIR::IR::OpCode::MUL, ZpIR::DataType::F32, getOp0Reg(unit_z, ZpIR::DataType::F32), getOp1Reg(unit_z, ZpIR::DataType::F32)); ZpIR::IRReg productW = irBuilder->emit_RRR(ZpIR::IR::OpCode::MUL, ZpIR::DataType::F32, getOp0Reg(unit_w, ZpIR::DataType::F32), getOp1Reg(unit_w, ZpIR::DataType::F32)); ZpIR::IRReg sum = irBuilder->emit_RRR(ZpIR::IR::OpCode::ADD, ZpIR::DataType::F32, productX, productY); sum = irBuilder->emit_RRR(ZpIR::IR::OpCode::ADD, ZpIR::DataType::F32, sum, productZ); sum = irBuilder->emit_RRR(ZpIR::IR::OpCode::ADD, ZpIR::DataType::F32, sum, productW); // assign result if (unit_x->getWriteMask()) m_irGenContext.activeVars.setAfterGroup(0, unit_x->getDestGpr(), unit_x->getDestElem(), sum); if (unit_y->getWriteMask()) m_irGenContext.activeVars.setAfterGroup(1, unit_y->getDestGpr(), unit_y->getDestElem(), sum); if (unit_z->getWriteMask()) m_irGenContext.activeVars.setAfterGroup(2, unit_z->getDestGpr(), unit_z->getDestElem(), sum); if (unit_w->getWriteMask()) m_irGenContext.activeVars.setAfterGroup(3, unit_w->getDestGpr(), unit_w->getDestElem(), sum); // also set result in PV.x m_irGenContext.activeVars.setAfterGroupPVPS(0, sum); // todo - do we need to update the other units? aluUnitIndex += 3; continue;; } default: assert_dbg(); } // handle dest clamp if (instrOP2->getDestClamp()) { assert_dbg(); } //uint32 src0Sel = (aluWord0 >> 0) & 0x1FF; // source selection //uint32 src1Sel = (aluWord0 >> 13) & 0x1FF; //uint32 src0Rel = (aluWord0 >> 9) & 0x1; // relative addressing mode //uint32 src1Rel = (aluWord0 >> 22) & 0x1; //uint32 src0Chan = (aluWord0 >> 10) & 0x3; // component selection x/y/z/w //uint32 src1Chan = (aluWord0 >> 23) & 0x3; //uint32 src0Neg = (aluWord0 >> 12) & 0x1; // negate input //uint32 src1Neg = (aluWord0 >> 25) & 0x1; //uint32 indexMode = (aluWord0 >> 26) & 7; //uint32 predSel = (aluWord0 >> 29) & 3; //uint32 src0Abs = (aluWord1 >> 0) & 1; //uint32 src1Abs = (aluWord1 >> 1) & 1; //uint32 updateExecuteMask = (aluWord1 >> 2) & 1; //uint32 updatePredicate = (aluWord1 >> 3) & 1; //uint32 writeMask = (aluWord1 >> 4) & 1; //uint32 omod = (aluWord1 >> 5) & 3; //uint32 destGpr = (aluWord1 >> 21) & 0x7F; //uint32 destRel = (aluWord1 >> 28) & 1; //uint32 destElem = (aluWord1 >> 29) & 3; //uint32 destClamp = (aluWord1 >> 31) & 1; } } // update IR vars with outputs from group m_irGenContext.activeVars.applyDelayedAfterGroup(); } void LatteTCGenIR::processCF_ALU(const LatteCFInstruction_ALU& cfInstruction) { uint32 aluAddr = cfInstruction.getField_ADDR(); uint32 aluCount = cfInstruction.getField_COUNT(); const uint32* clauseCode = m_ctx.programData + aluAddr * 2; uint32 clauseLength = aluCount; const LatteClauseInstruction_ALU* aluCode = (const LatteClauseInstruction_ALU*)clauseCode; const LatteClauseInstruction_ALU* aluUnit[5] = {}; const LatteClauseInstruction_ALU* instr = aluCode; const LatteClauseInstruction_ALU* instrLast = aluCode + clauseLength; // process instructions in groups uint8 literalMask = 0; while (instr < instrLast) { if (instr->isOP3()) { assert_dbg(); } else { LatteClauseInstruction_ALU::OPCODE_OP2 opcode2 = instr->getOP2Code(); const LatteClauseInstruction_ALU_OP2* op = instr->getOP2Instruction(); uint32 unitIndex = 0; if (op->isTranscedentalUnit()) unitIndex = 4; else { unitIndex = op->getDestElem(); if (aluUnit[unitIndex]) // unit already occupied, use transcendental unit instead unitIndex = 4; } cemu_assert_debug(!aluUnit[unitIndex]); // unit already used aluUnit[unitIndex] = op; // check for literal access if (op->getSrc0Sel().isLiteral()) literalMask |= (op->getSrc0Chan() >= 2 ? 2 : 1); if (op->getSrc1Sel().isLiteral()) literalMask |= (op->getSrc1Chan() >= 2 ? 2 : 1); } if (instr->isLastInGroup()) { // emit code for group // extract literal constants const uint32* literalData = nullptr; if (literalMask) { literalData = (const uint32*)(instr + 1); if (literalMask & 2) instr += 2; else instr += 1; if ((instr + 1) > instrLast) assert_dbg(); // out of bounds } // generate code for group emitALUGroup(aluUnit, literalData); // reset group std::fill(aluUnit, aluUnit + 5, nullptr); literalMask = 0; } instr++; } if (aluUnit[0] || aluUnit[1] || aluUnit[2] || aluUnit[3] || aluUnit[4]) assert_dbg(); } void LatteTCGenIR::processCF_EXPORT(const LatteCFInstruction_EXPORT_IMPORT& cfInstruction) { auto exportType = cfInstruction.getField_TYPE(); cemu_assert_debug(cfInstruction.getField_BURST_COUNT() == 1); // todo uint32 arrayBase = cfInstruction.getField_ARRAY_BASE(); cemu_assert_debug(cfInstruction.isEncodingBUF() == false); // todo LatteCFInstruction_EXPORT_IMPORT::COMPSEL sel[4]; sel[0] = cfInstruction.getSwizField_SEL_X(); sel[1] = cfInstruction.getSwizField_SEL_Y(); sel[2] = cfInstruction.getSwizField_SEL_Z(); sel[3] = cfInstruction.getSwizField_SEL_W(); uint32 sourceGPR = cfInstruction.getField_RW_GPR(); ZpIR::DataType typeHint; if (exportType == LatteCFInstruction_EXPORT_IMPORT::EXPORT_TYPE::POSITION) typeHint = ZpIR::DataType::F32; else if (exportType == LatteCFInstruction_EXPORT_IMPORT::EXPORT_TYPE::PARAMETER) { // todo - determine correct type for parameter typeHint = ZpIR::DataType::F32; } else assert_dbg(); // get xyzw registers ZpIR::IRReg regArray[4]; size_t regExportCount = 0; // number of exported registers/channels, number of valid regArray entries for (size_t i = 0; i < 4; i++) { switch (sel[i]) { case LatteCFInstruction_EXPORT_IMPORT::COMPSEL::X: case LatteCFInstruction_EXPORT_IMPORT::COMPSEL::Y: case LatteCFInstruction_EXPORT_IMPORT::COMPSEL::Z: case LatteCFInstruction_EXPORT_IMPORT::COMPSEL::W: { uint32 channelIndex = (uint32)sel[i]; regArray[regExportCount] = getTypedIRRegFromGPRElement(sourceGPR, channelIndex, typeHint); regExportCount++; break; } default: { assert_dbg(); break; } } //ZpIR::IRReg r; //if (m_irGenContext.activeVars.get(gprIndex, channel, r)) // return r; } //ZpIR::LocationSymbolName exportSymbolName; ZpIR::ShaderSubset::ShaderExportLocation loc; if (exportType == LatteCFInstruction_EXPORT_IMPORT::EXPORT_TYPE::POSITION) { loc.SetPosition(); } else if (exportType == LatteCFInstruction_EXPORT_IMPORT::EXPORT_TYPE::PARAMETER) { loc.SetOutputAttribute(arrayBase); //exportSymbolName = 0x20000 + arrayBase; } else { // todo assert_dbg(); } cemu_assert_debug(regExportCount == 4); // todo - encode channel mask (e.g. xyz, xw, w, etc.) into export symbol name m_irGenContext.irBuilder->emit_EXPORT(loc, std::span(regArray, regArray + regExportCount)); } ================================================ FILE: src/Cafe/HW/MMU/MMU.cpp ================================================ #include "Cafe/HW/MMU/MMU.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "Cemu/Logging/CemuLogging.h" #include "WindowSystem.h" #include "util/MemMapper/MemMapper.h" #include "config/ActiveSettings.h" uint8* memory_base = NULL; // base address of the reserved 4GB space uint8* memory_elfCodeArena = NULL; void checkMemAlloc(void* result) { if (result == nullptr) assert_dbg(); } void memory_initPhysicalLayout() { assert_dbg(); // todo - rewrite this using new MemMapper and MMU tables //memory_base = (uint8*)VirtualAlloc(NULL, 0x100000000ULL, MEM_RESERVE, PAGE_READWRITE); //VirtualFree(memory_base, 0, MEM_RELEASE); //// todo - figure out all the ranges and allocate them properly //// allocate memory for the kernel ////checkMemAlloc(VirtualAlloc(memory_base + 0x08000000, 1024*1024*2, MEM_COMMIT, PAGE_READWRITE)); //// allocate memory for bootrom //checkMemAlloc(VirtualAlloc(memory_base + 0x00000000, 1024*16, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE)); //// allocate memory at 0x016FFFFC (is this some sort of register interface or maybe just temporary storage?) //checkMemAlloc(VirtualAlloc(memory_base + 0x016FF000, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); //// temporary storage for bootrom copy //checkMemAlloc(VirtualAlloc(memory_base + 0x016c0000, 0x4000 + 0x4000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); //// 0x016c0000 //// L2 //checkMemAlloc(VirtualAlloc(memory_base + 0xE0000000, 1024 * 16, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); //// kernel memory //// currently it is unknown if this is it's own physical memory region or if this is mapped somehow //// considering the ancast is never copied here and no memory mapping is setup it seems like a hardwired mirror to 0x08000000? ////checkMemAlloc(VirtualAlloc(memory_base + 0xFFE00000, 0x180000, MEM_COMMIT, PAGE_READWRITE)); //HANDLE hKernelMem = CreateFileMappingA( // INVALID_HANDLE_VALUE, // use paging file // NULL, // default security // PAGE_READWRITE, // read/write access // 0, // maximum object size (high-order DWORD) // 1024 * 1024 * 2, // maximum object size (low-order DWORD) // "kernelMem08000000"); // name of mapping object // //checkMemAlloc(MapViewOfFileEx(hKernelMem, FILE_MAP_ALL_ACCESS, 0, 0, 1024 * 1024 * 2, memory_base + 0x08000000)); //checkMemAlloc(MapViewOfFileEx(hKernelMem, FILE_MAP_ALL_ACCESS, 0, 0, 1024 * 1024 * 2, memory_base + 0xFFE00000)); //// IOSU->PPC bootParamBlock //checkMemAlloc(VirtualAlloc(memory_base + 0x01FFF000, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); //// used as dynamic kernel memory? //checkMemAlloc(VirtualAlloc(memory_base + 0x1C000000, 0x01000000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); //// mapped by kernel to FF200000 (loader.elf?) //checkMemAlloc(VirtualAlloc(memory_base + 0x1B800000, 0x00800000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); } std::vector<struct MMURange*> g_mmuRanges; std::vector<MMURange*> memory_getMMURanges() { return g_mmuRanges; } MMURange* memory_getMMURangeByAddress(MPTR address) { for (auto& itr : g_mmuRanges) { if (address >= itr->getBase() && address < itr->getEnd()) return itr; } return nullptr; } MMURange::MMURange(const uint32 baseAddress, const uint32 size, MMU_MEM_AREA_ID areaId, const std::string_view name, MFLAG flags) : baseAddress(baseAddress), size(size), initSize(size), areaId(areaId), name(name), flags(flags) { g_mmuRanges.emplace_back(this); } void MMURange::mapMem() { cemu_assert_debug(!m_isMapped); if (MemMapper::AllocateMemory(memory_base + baseAddress, size, MemMapper::PAGE_PERMISSION::P_RW, true) == nullptr) { std::string errorMsg = _tr("Unable to allocate {} memory", name); WindowSystem::ShowErrorDialog(errorMsg, _tr("Error")); #if BOOST_OS_WINDOWS ExitProcess(-1); #else exit(-1); #endif } m_isMapped = true; } void MMURange::unmapMem() { MemMapper::FreeMemory(memory_base + baseAddress, size, true); m_isMapped = false; } MMURange mmuRange_LOW0 { 0x00010000, 0x000F0000, MMU_MEM_AREA_ID::CODE_LOW0, "CODE_LOW0" }; // code cave (Cemuhook) MMURange mmuRange_TRAMPOLINE_AREA { 0x00E00000, 0x00200000, MMU_MEM_AREA_ID::CODE_TRAMPOLINE, "TRAMPOLINE_AREA" }; // code area for trampolines and imports MMURange mmuRange_CODECAVE { 0x01800000, 0x00400000, MMU_MEM_AREA_ID::CODE_CAVE, "CODECAVE" }; // code cave area (4MiB) MMURange mmuRange_TEXT_AREA { 0x02000000, 0x0C000000, MMU_MEM_AREA_ID::CODE_MAIN, "TEXT_AREA" }; // module text sections go here (0x02000000 to 0x10000000, 224MiB) MMURange mmuRange_CEMU_AREA { 0x0E000000, 0x02000000, MMU_MEM_AREA_ID::CEMU_PRIVATE, "CEMU_AREA", MMURange::MFLAG::FLAG_MAP_EARLY }; // Cemu-only, 32MiB. Should be allocated early for SysAllocator MMURange mmuRange_MEM2 { 0x10000000, 0x40000000, MMU_MEM_AREA_ID::MEM2_DATA, "MEM2" }; // main memory area (1GB) MMURange mmuRange_OVERLAY_AREA { 0xA0000000, 0x1C000000, MMU_MEM_AREA_ID::OVERLAY, "OVERLAY_AREA", MMURange::MFLAG::FLAG_OPTIONAL }; // has to be requested, 448MiB MMURange mmuRange_FGBUCKET { 0xE0000000, 0x04000000, MMU_MEM_AREA_ID::FGBUCKET, "FGBUCKET" }; // foreground bucket (64MiB) MMURange mmuRange_TILINGAPERTURE { 0xE8000000, 0x02000000, MMU_MEM_AREA_ID::TILING_APERATURE, "TILINGAPERTURE" }; // tiling aperture MMURange mmuRange_MEM1 { 0xF4000000, 0x02000000, MMU_MEM_AREA_ID::MEM1, "MEM1" }; // 32MiB MMURange mmuRange_RPLLOADER { 0xF6000000, 0x02000000, MMU_MEM_AREA_ID::RPLLOADER, "RPLLOADER_AREA" }; // shared with RPLLoader MMURange mmuRange_SHARED_AREA { 0xF8000000, 0x02000000, MMU_MEM_AREA_ID::SHAREDDATA, "SHARED_AREA", MMURange::MFLAG::FLAG_MAP_EARLY }; // 32MiB, Cemuhook accesses this memory region at boot MMURange mmuRange_CORE0_LC { 0xFFC00000, 0x00005000, MMU_MEM_AREA_ID::CPU_LC0, "CORE0_LC" }; // locked L2 cache of core 0 MMURange mmuRange_CORE1_LC { 0xFFC40000, 0x00005000, MMU_MEM_AREA_ID::CPU_LC1, "CORE1_LC" }; // locked L2 cache of core 1 MMURange mmuRange_CORE2_LC { 0xFFC80000, 0x00005000, MMU_MEM_AREA_ID::CPU_LC2, "CORE2_LC" }; // locked L2 cache of core 2 MMURange mmuRange_HIGHMEM { 0xFFFFF000, 0x00001000, MMU_MEM_AREA_ID::CPU_PER_CORE, "PER-CORE" }; // per-core memory? Used by coreinit and PPC kernel to store core context specific data (like current thread ptr). We dont use it but Project Zero has a bug where it writes a byte at 0xfffffffe thus this memory range needs to be writable void memory_init() { // reserve a continous range of 4GB if(!memory_base) memory_base = (uint8*)MemMapper::ReserveMemory(nullptr, (size_t)0x100000000, MemMapper::PAGE_PERMISSION::P_RW); if( !memory_base ) { debug_printf("memory_init(): Unable to reserve 4GB of memory\n"); debugBreakpoint(); WindowSystem::ShowErrorDialog(_tr("Unable to reserve 4GB of memory"), _tr("Error")); exit(-1); } for (auto& itr : g_mmuRanges) { if (itr->isMappedEarly()) itr->mapMem(); } } void memory_mapForCurrentTitle() { for (auto& itr : g_mmuRanges) if(!itr->isMapped()) itr->resetConfig(); // expand ranges auto gfxPackMappings = GraphicPack2::GetActiveRAMMappings(); for (auto& mapping : gfxPackMappings) { MMURange* mmuRange = nullptr; for (auto& itr : g_mmuRanges) { if (itr->getBase() == mapping.first) { mmuRange = itr; break; } } if (!mmuRange) { cemuLog_log(LogType::Force, fmt::format("Graphic pack error: Unable to apply modified RAM mapping {:08x}-{:08x}. Start address must match one of the existing MMU ranges:", mapping.first, mapping.second)); for (auto& itr : g_mmuRanges) { if(itr->isMapped()) continue; cemuLog_log(LogType::Force, fmt::format("{:08x}-{:08x} ({:})", itr->getBase(), itr->getEnd(), itr->getName())); } continue; } // make sure the new range isn't overlapping with anything bool isOverlapping = false; for (auto& itr : g_mmuRanges) { if(itr == mmuRange) continue; if (mapping.first < itr->getEnd() && mapping.second > itr->getBase()) { cemuLog_log(LogType::Force, fmt::format("Graphic pack error: Unable to apply modified memory range {:08x}-{:08x} since it is overlapping with {:08x}-{:08x} ({:})", mapping.first, mapping.second, itr->getBase(), itr->getEnd(), itr->getName())); isOverlapping = true; } } if(isOverlapping) continue; mmuRange->setEnd(mapping.second); } for (auto& itr : g_mmuRanges) { if (!itr->isOptional() && !itr->isMappedEarly()) itr->mapMem(); } } void memory_unmapForCurrentTitle() { for (auto& itr : g_mmuRanges) { if (itr->isMapped() && !itr->isMappedEarly()) itr->unmapMem(); } } void memory_logModifiedMemoryRanges() { auto gfxPackMappings = GraphicPack2::GetActiveRAMMappings(); for (auto& mapping : gfxPackMappings) { MMURange* mmuRange = nullptr; for (auto& itr : g_mmuRanges) { if (itr->getBase() == mapping.first) { mmuRange = itr; break; } } if (!mmuRange) continue; sint32 extraMem = (sint32)mapping.second - (sint32)(mmuRange->getBase() + mmuRange->getInitSize()); extraMem = (extraMem + 1023) / 1024; std::string memAmountStr; if (extraMem >= 8 * 1024 * 1024) memAmountStr = fmt::format("{:+}MiB", (extraMem + 1023) / 1024); else memAmountStr = fmt::format("{:+}KiB", extraMem); cemuLog_log(LogType::Force, fmt::format("Graphic pack: Using modified RAM mapping {:08x}-{:08x} ({})", mapping.first, mapping.second, memAmountStr)); } } void memory_enableOverlayArena() { if (mmuRange_OVERLAY_AREA.isMapped()) return; mmuRange_OVERLAY_AREA.mapMem(); } void memory_enableHBLELFCodeArea() { if (memory_elfCodeArena != NULL) return; memory_elfCodeArena = (uint8*)MemMapper::AllocateMemory(memory_base + 0x00800000, 0x00800000, MemMapper::PAGE_PERMISSION::P_RW, true); if (memory_elfCodeArena == NULL) { debug_printf("memory_enableHBLELFCodeArea(): Unable to allocate memory for ELF arena\n"); debugBreakpoint(); } } bool memory_isAddressRangeAccessible(MPTR virtualAddress, uint32 size) { for (auto& itr : g_mmuRanges) { if(!itr->isMapped()) continue; if (virtualAddress >= itr->getBase() && virtualAddress < itr->getEnd()) { uint32 remainingSize = itr->getEnd() - virtualAddress; return size <= remainingSize && itr->isMapped(); } } return false; } uint32 memory_virtualToPhysical(uint32 virtualOffset) { // currently we map virtual to physical space 1:1 return virtualOffset; } uint32 memory_physicalToVirtual(uint32 physicalOffset) { // currently we map virtual to physical space 1:1 return physicalOffset; } uint8* memory_getPointerFromPhysicalOffset(uint32 physicalOffset) { return memory_base + physicalOffset; } uint32 memory_getVirtualOffsetFromPointer(void* ptr) { if( !ptr ) return MPTR_NULL; return (uint32)((uint8*)ptr - (uint8*)memory_base); } uint8* memory_getPointerFromVirtualOffset(uint32 virtualOffset) { return memory_base + virtualOffset; } uint8* memory_getPointerFromVirtualOffsetAllowNull(uint32 virtualOffset) { if( virtualOffset == MPTR_NULL ) return nullptr; return memory_getPointerFromVirtualOffset(virtualOffset); } // write access void memory_writeDouble(uint32 address, double vf) { uint64 v = *(uint64*)&vf; uint32 v1 = v&0xFFFFFFFF; uint32 v2 = v>>32; uint8* ptr = memory_getPointerFromVirtualOffset(address); *(uint32*)(ptr+4) = CPU_swapEndianU32(v1); *(uint32*)(ptr+0) = CPU_swapEndianU32(v2); } void memory_writeFloat(uint32 address, float vf) { uint32 v = *(uint32*)&vf; *(uint32*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU32(v); } void memory_writeU32(uint32 address, uint32 v) { *(uint32*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU32(v); } void memory_writeU64(uint32 address, uint64 v) { *(uint64*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU64(v); } void memory_writeU16(uint32 address, uint16 v) { *(uint16*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU16(v); } void memory_writeU8(uint32 address, uint8 v) { *(uint8*)(memory_getPointerFromVirtualOffset(address)) = v; } // read access double memory_readDouble(uint32 address) { uint32 v[2]; v[1] = *(uint32*)(memory_getPointerFromVirtualOffset(address)); v[0] = *(uint32*)(memory_getPointerFromVirtualOffset(address)+4); v[0] = CPU_swapEndianU32(v[0]); v[1] = CPU_swapEndianU32(v[1]); return *(double*)v; } float memory_readFloat(uint32 address) { uint32 v = *(uint32*)(memory_getPointerFromVirtualOffset(address)); v = CPU_swapEndianU32(v); return *(float*)&v; } uint64 memory_readU64(uint32 address) { uint64 v = *(uint64*)(memory_getPointerFromVirtualOffset(address)); return CPU_swapEndianU64(v); } uint32 memory_readU32(uint32 address) { uint32 v = *(uint32*)(memory_getPointerFromVirtualOffset(address)); return CPU_swapEndianU32(v); } uint16 memory_readU16(uint32 address) { uint16 v = *(uint16*)(memory_getPointerFromVirtualOffset(address)); return CPU_swapEndianU16(v); } uint8 memory_readU8(uint32 address) { return *(uint8*)(memory_getPointerFromVirtualOffset(address)); } extern "C" DLLEXPORT void* memory_getBase() { return memory_base; } void memory_writeDumpFile(uint32 startAddr, uint32 size, const fs::path& path) { fs::path filePath = path; filePath /= fmt::format("{:08x}.bin", startAddr); FileStream* fs = FileStream::createFile2(filePath); if (fs) { fs->writeData(memory_base + startAddr, size); delete fs; } } void memory_createDump() { const uint32 pageSize = MemMapper::GetPageSize(); fs::path path = ActiveSettings::GetUserDataPath("dump/ramDump{:}", (uint32)time(nullptr)); fs::create_directories(path); for (auto& itr : g_mmuRanges) { if(!itr->isMapped()) continue; memory_writeDumpFile(itr->getBase(), itr->getSize(), path); } } namespace MMU { // MMIO access handler // located in address region 0x0C000000 - 0x0E000000 // there seem to be multiple subregions + special meanings for some address bits maybe? // Try to figure this out. We know these regions (in Wii U mode): // 0x0C000000 (the old GC register interface?) // 0x0D000000 (new Wii U stuff?) std::unordered_map<PAddr, MMIOFuncWrite32>* g_mmioHandlerW32{}; std::unordered_map<PAddr, MMIOFuncWrite16>* g_mmioHandlerW16{}; std::unordered_map<PAddr, MMIOFuncRead32>* g_mmioHandlerR32{}; std::unordered_map<PAddr, MMIOFuncRead16>* g_mmioHandlerR16{}; void _initHandlers() { if (g_mmioHandlerW32) return; g_mmioHandlerW32 = new std::unordered_map<PAddr, MMIOFuncWrite32>(); g_mmioHandlerW16 = new std::unordered_map<PAddr, MMIOFuncWrite16>(); g_mmioHandlerR32 = new std::unordered_map<PAddr, MMIOFuncRead32>(); g_mmioHandlerR16 = new std::unordered_map<PAddr, MMIOFuncRead16>(); } PAddr _MakeMMIOAddress(MMIOInterface interfaceLocation, uint32 relativeAddress) { PAddr addr = 0; if (interfaceLocation == MMIOInterface::INTERFACE_0C000000) addr = 0x0C000000; else if (interfaceLocation == MMIOInterface::INTERFACE_0D000000) addr = 0x0D000000; else assert_dbg(); return addr + relativeAddress; } void RegisterMMIO_W32(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncWrite32 ptr) { _initHandlers(); g_mmioHandlerW32->emplace(_MakeMMIOAddress(interfaceLocation, relativeAddress), ptr); } void RegisterMMIO_W16(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncWrite16 ptr) { _initHandlers(); g_mmioHandlerW16->emplace(_MakeMMIOAddress(interfaceLocation, relativeAddress), ptr); } void RegisterMMIO_R32(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncRead32 ptr) { _initHandlers(); PAddr addr = _MakeMMIOAddress(interfaceLocation, relativeAddress); g_mmioHandlerR32->emplace(addr, ptr); } void RegisterMMIO_R16(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncRead16 ptr) { _initHandlers(); g_mmioHandlerR16->emplace(_MakeMMIOAddress(interfaceLocation, relativeAddress), ptr); } void WriteMMIO_32(PAddr address, uint32 value) { cemu_assert_debug((address & 0x3) == 0); auto itr = g_mmioHandlerW32->find(address); if (itr == g_mmioHandlerW32->end()) { //cemuLog_logDebug(LogType::Force, "[MMU] MMIO write u32 0x{:08x} from unhandled address 0x{:08x}", value, address); return; } return itr->second(address, value); } void WriteMMIO_16(PAddr address, uint16 value) { cemu_assert_debug((address & 0x1) == 0); auto itr = g_mmioHandlerW16->find(address); if (itr == g_mmioHandlerW16->end()) { //cemuLog_logDebug(LogType::Force, "[MMU] MMIO write u16 0x{:04x} from unhandled address 0x{:08x}", (uint32)value, address); return; } return itr->second(address, value); } // todo - instead of passing the physical address to Read/WriteMMIO we should pass an interface id and a relative address? This would allow remapping the hardware address (tho we can just unregister + register at different addresses) uint32 ReadMMIO_32(PAddr address) { cemu_assert_debug((address & 0x3) == 0); auto itr = g_mmioHandlerR32->find(address); if(itr == g_mmioHandlerR32->end()) { //cemuLog_logDebug(LogType::Force, "[MMU] MMIO read u32 from unhandled address 0x{:08x}", address); return 0; } return itr->second(address); } uint16 ReadMMIO_16(PAddr address) { cemu_assert_debug((address & 0x1) == 0); auto itr = g_mmioHandlerR16->find(address); if (itr == g_mmioHandlerR16->end()) { //cemuLog_logDebug(LogType::Force, "[MMU] MMIO read u16 from unhandled address 0x{:08x}", address); return 0; } return itr->second(address); } } ================================================ FILE: src/Cafe/HW/MMU/MMU.h ================================================ #pragma once void memory_init(); void memory_mapForCurrentTitle(); void memory_unmapForCurrentTitle(); void memory_logModifiedMemoryRanges(); void memory_enableOverlayArena(); void memory_enableHBLELFCodeArea(); uint32 memory_getVirtualOffsetFromPointer(void* ptr); uint8* memory_getPointerFromVirtualOffset(uint32 virtualOffset); uint8* memory_getPointerFromVirtualOffsetAllowNull(uint32 virtualOffset); uint8* memory_getPointerFromPhysicalOffset(uint32 physicalOffset); uint32 memory_virtualToPhysical(uint32 virtualOffset); uint32 memory_physicalToVirtual(uint32 physicalOffset); extern uint8* memory_base; // points to base of PowerPC address space enum class MMU_MEM_AREA_ID { CODE_LOW0, CODE_TRAMPOLINE, CODE_CAVE, CODE_MAIN, MEM2_DATA, FGBUCKET, TILING_APERATURE, OVERLAY, MAPABLE_SPACE, MEM1, RPLLOADER, SHAREDDATA, CPU_LC0, CPU_LC1, CPU_LC2, CPU_PER_CORE, CEMU_PRIVATE, }; struct MMURange { enum MFLAG { FLAG_OPTIONAL = (1 << 0), // allocate only on explicit request FLAG_MAP_EARLY = (1 << 1), // map at Cemu launch, normally memory is mapped when a game is loaded }; MMURange(const uint32 baseAddress, const uint32 size, MMU_MEM_AREA_ID areaId, const std::string_view name, MFLAG flags = (MFLAG)0); void mapMem(); void unmapMem(); uint8* getPtr() const { cemu_assert_debug(m_isMapped); return memory_base + baseAddress; } uint32 getBase() const { return baseAddress; } // reset to initial parameters void resetConfig() { size = initSize; } void setEnd(uint32 endAddress) { cemu_assert_debug(!m_isMapped); cemu_assert_debug((endAddress & 0xFFF) == 0); size = endAddress - baseAddress; } // returns offset of last byte + 1 (base + size) uint32 getEnd() const { return baseAddress + size; } uint32 getSize() const { return size; } uint32 getInitSize() const { return initSize; } std::string_view getName() const { return name; } bool containsAddress(uint32 addr) const { return addr >= getBase() && addr < getEnd(); } bool isMapped() const { return m_isMapped; }; bool isOptional() const { return (flags & MFLAG::FLAG_OPTIONAL) != 0; }; bool isMappedEarly() const { return (flags & MFLAG::FLAG_MAP_EARLY) != 0; }; const uint32 baseAddress; const uint32 initSize; // initial size const std::string name; const MFLAG flags; const MMU_MEM_AREA_ID areaId; // runtime parameters uint32 size; bool m_isMapped{}; }; extern MMURange mmuRange_LOW0; extern MMURange mmuRange_TRAMPOLINE_AREA; extern MMURange mmuRange_CODECAVE; extern MMURange mmuRange_TEXT_AREA; extern MMURange mmuRange_MEM2; extern MMURange mmuRange_CEMU_AREA; extern MMURange mmuRange_OVERLAY_AREA; extern MMURange mmuRange_FGBUCKET; extern MMURange mmuRange_TILINGAPERTURE; extern MMURange mmuRange_MEM1; extern MMURange mmuRange_RPLLOADER; extern MMURange mmuRange_SHARED_AREA; extern MMURange mmuRange_CORE0_LC; extern MMURange mmuRange_CORE1_LC; extern MMURange mmuRange_CORE2_LC; std::vector<MMURange*> memory_getMMURanges(); MMURange* memory_getMMURangeByAddress(MPTR address); bool memory_isAddressRangeAccessible(MPTR virtualAddress, uint32 size); #define MEMORY_CODELOW0_ADDR (0x00010000) #define MEMORY_CODELOW0_SIZE (0x000F0000) // ~1MB #define MEMORY_CODE_TRAMPOLINE_AREA_ADDR (0x00E00000) // code area for trampolines and imports #define MEMORY_CODE_TRAMPOLINE_AREA_SIZE (0x00200000) // 2MB #define MEMORY_CODECAVEAREA_ADDR (0x01800000) #define MEMORY_CODECAVEAREA_SIZE (0x00400000) // 4MB #define MEMORY_CODEAREA_ADDR (0x02000000) #define MEMORY_CODEAREA_SIZE (0x0E000000) // 224MB #define MEMORY_DATA_AREA_ADDR (0x10000000) #define MEMORY_DATA_AREA_SIZE (0x40000000) #define MEMORY_FGBUCKET_AREA_ADDR (0xE0000000) // actual offset is 0xE0000000 according to PPC kernel #define MEMORY_FGBUCKET_AREA_SIZE (0x04000000) // 64MB split up into multiple subareas, size is verified with value from PPC kernel #define MEMORY_TILINGAPERTURE_AREA_ADDR (0xE8000000) #define MEMORY_TILINGAPERTURE_AREA_SIZE (0x02000000) // 32MB #define MEMORY_OVERLAY_AREA_OFFSET (0xA0000000) #define MEMORY_OVERLAY_AREA_SIZE (448*1024*1024) // 448MB (recycled background app memory) #define MEMORY_MEM1_AREA_ADDR (0xF4000000) #define MEMORY_MEM1_AREA_SIZE (0x02000000) // 32MB #define MEMORY_RPLLOADER_AREA_ADDR (0xF6000000) // workarea for RPLLoader (normally this is kernel workspace) #define MEMORY_RPLLOADER_AREA_SIZE (0x02000000) // 32MB #define MEMORY_SHAREDDATA_AREA_ADDR (0xF8000000) #define MEMORY_SHAREDDATA_AREA_SIZE (0x02000000) // 32MB #if BOOST_OS_WINDOWS #define CPU_swapEndianU64(_v) _byteswap_uint64((uint64)(_v)) #define CPU_swapEndianU32(_v) _byteswap_ulong((uint32)(_v)) #define CPU_swapEndianU16(_v) _byteswap_ushort((uint16)(_v)) #elif BOOST_OS_LINUX #define CPU_swapEndianU64(_v) bswap_64((uint64)(_v)) #define CPU_swapEndianU32(_v) bswap_32((uint32)(_v)) #define CPU_swapEndianU16(_v) bswap_16((uint16)(_v)) #elif BOOST_OS_MACOS #define CPU_swapEndianU64(_v) OSSwapInt64((uint64)(_v)) #define CPU_swapEndianU32(_v) OSSwapInt32((uint32)(_v)) #define CPU_swapEndianU16(_v) OSSwapInt16((uint16)(_v)) #elif BOOST_OS_BSD #ifdef __OpenBSD__ #define CPU_swapEndianU64(_v) swap64((uint64)(_v)) #define CPU_swapEndianU32(_v) swap32((uint32)(_v)) #define CPU_swapEndianU16(_v) swap16((uint16)(_v)) #else // FreeBSD and NetBSD #define CPU_swapEndianU64(_v) bswap64((uint64)(_v)) #define CPU_swapEndianU32(_v) bswap32((uint32)(_v)) #define CPU_swapEndianU16(_v) bswap16((uint16)(_v)) #endif #endif // C-style memory access, deprecated. Use memory_read<> and memory_write<> templates instead void memory_writeDouble(uint32 address, double vf); void memory_writeFloat(uint32 address, float vf); void memory_writeU32(uint32 address, uint32 v); void memory_writeU16(uint32 address, uint16 v); void memory_writeU8(uint32 address, uint8 v); void memory_writeU64(uint32 address, uint64 v); double memory_readDouble(uint32 address); float memory_readFloat(uint32 address); uint64 memory_readU64(uint32 address); uint32 memory_readU32(uint32 address); uint16 memory_readU16(uint32 address); uint8 memory_readU8(uint32 address); void memory_createDump(); template<size_t count> void memory_readBytes(VAddr address, std::array<uint8, count>& buffer) { memcpy(buffer.data(), memory_getPointerFromVirtualOffset(address), count); } template <typename T> inline T memory_read(VAddr address) { return *(betype<T>*)(memory_base + address); } template <typename T> inline void memory_write(VAddr address, T value) { *(betype<T>*)(memory_base + address) = value; } // LLE implementation void memory_initPhysicalLayout(); namespace MMU { using MMIOFuncWrite32 = void (*)(PAddr addr, uint32 value); using MMIOFuncWrite16 = void (*)(PAddr addr, uint16 value); using MMIOFuncRead32 = uint32(*)(PAddr addr); using MMIOFuncRead16 = uint16(*)(PAddr addr); enum class MMIOInterface { INTERFACE_0C000000, INTERFACE_0D000000, //INTERFACE_0D000000, }; void RegisterMMIO_W16(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncWrite16 ptr); void RegisterMMIO_W32(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncWrite32 ptr); void RegisterMMIO_R32(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncRead32 ptr); void RegisterMMIO_R16(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncRead16 ptr); template<typename TRegType, auto TReadFunc, auto TWriteFunc> void RegisterMMIO_32(MMIOInterface interfaceLocation, uint32 relativeAddress) { RegisterMMIO_W32(interfaceLocation, relativeAddress, [](PAddr addr, uint32 value) -> void { TRegType temp; temp.setFromRaw(value); TWriteFunc(addr, temp); }); RegisterMMIO_R32(interfaceLocation, relativeAddress, [](PAddr addr) -> uint32 { return TReadFunc(addr).getRawValue(); }); } void WriteMMIO_32(PAddr address, uint32 value); void WriteMMIO_16(PAddr address, uint16 value); uint32 ReadMMIO_32(PAddr address); uint16 ReadMMIO_16(PAddr address); } #define MMU_IsInPPCMemorySpace(__ptr) ((const uint8*)(__ptr) >= memory_base && (const uint8*)(__ptr) < (memory_base + 0x100000000)) ================================================ FILE: src/Cafe/HW/SI/SI.cpp ================================================ #include "Cafe/HW/MMU/MMU.h" #include "Cafe/HW/Common/HwReg.h" #include "si.h" namespace HW_SI { struct { struct { HWREG::SICOMCSR sicomcsr{}; HWREG::SIPOLL sipoll{}; HWREG::SICOUTBUF outBuf[4]{}; }registerState; struct { uint8 cmd{}; uint8 buf[2]{}; }outputBufferState[4]; struct { bool hasErrorNoResponse{}; }channelStatus[4]; }g_si; // normally we should call this periodically according to the parameters set in SIPOLL // but for now we just call it whenever status registers are read void handlePollUpdate() { for (uint32 i = 0; i < 4; i++) { // note: Order of EN and VBCPY is from MSB to LSB bool isEnabled = ((g_si.registerState.sipoll.get_EN() >> (3 - i))&1) != 0; if (isEnabled) { g_si.channelStatus[i].hasErrorNoResponse = true; } } } void handleQueuedTransfers() { } void flushAllOutputBuffers() { for (uint32 i = 0; i < 4; i++) { g_si.outputBufferState[i].cmd = g_si.registerState.outBuf[i].get_CMD(); g_si.outputBufferState[i].buf[0] = g_si.registerState.outBuf[i].get_OUTPUT0(); g_si.outputBufferState[i].buf[1] = g_si.registerState.outBuf[i].get_OUTPUT1(); } } /* +0x6400/0x640C/0x6418/0x6424 | SI0COUTBUF - SI3COUTBUF */ HWREG::SICOUTBUF SI_COUTBUF_R32(PAddr addr) { uint32 joyChannelIndex = (addr & 0xFF) / 0xC; cemu_assert_debug(false); return HWREG::SICOUTBUF(); } void SI_COUTBUF_W32(PAddr addr, HWREG::SICOUTBUF newValue) { uint32 joyChannelIndex = (addr & 0xFF) / 0xC; g_si.registerState.outBuf[joyChannelIndex] = newValue; } /* +0x6430 | SIPOLL */ HWREG::SIPOLL SI_POLL_R32(PAddr addr) { cemu_assert_debug(false); return g_si.registerState.sipoll; } void SI_POLL_W32(PAddr addr, HWREG::SIPOLL newValue) { g_si.registerState.sipoll = newValue; } /* +0x6434 | SICOMCSR */ HWREG::SICOMCSR SI_COMCSR_R32(PAddr addr) { return g_si.registerState.sicomcsr; } void SI_COMCSR_W32(PAddr addr, HWREG::SICOMCSR newValue) { uint32 unhandledBits = g_si.registerState.sicomcsr.getRawValue() & ~(0x80000000); cemu_assert_debug(unhandledBits == 0); // clear transfer complete interrupt if (newValue.get_TCINT()) { g_si.registerState.sicomcsr.set_TCINT(0); } if (newValue.get_TRANSFER_START()) { cemu_assert_debug(false); handleQueuedTransfers(); } } /* +0x6438 | SISR */ HWREG::SISR SI_SR_R32(PAddr addr) { handlePollUpdate(); HWREG::SISR reg; // no response error if (g_si.channelStatus[0].hasErrorNoResponse) reg.set_NOREP0(1); if (g_si.channelStatus[1].hasErrorNoResponse) reg.set_NOREP1(1); if (g_si.channelStatus[2].hasErrorNoResponse) reg.set_NOREP2(1); if (g_si.channelStatus[3].hasErrorNoResponse) reg.set_NOREP3(1); // todo - other status fields return reg; } void SI_SR_W32(PAddr addr, HWREG::SISR newValue) { if (newValue.get_NOREP0()) g_si.channelStatus[0].hasErrorNoResponse = false; if (newValue.get_NOREP1()) g_si.channelStatus[1].hasErrorNoResponse = false; if (newValue.get_NOREP2()) g_si.channelStatus[2].hasErrorNoResponse = false; if (newValue.get_NOREP3()) g_si.channelStatus[3].hasErrorNoResponse = false; if (newValue.get_WR()) { // copies contents of SICOUTBUF to the internal shadow buffers flushAllOutputBuffers(); } } void Initialize() { MMU::RegisterMMIO_32<HWREG::SICOUTBUF, SI_COUTBUF_R32, SI_COUTBUF_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6400); MMU::RegisterMMIO_32<HWREG::SICOUTBUF, SI_COUTBUF_R32, SI_COUTBUF_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x640C); MMU::RegisterMMIO_32<HWREG::SICOUTBUF, SI_COUTBUF_R32, SI_COUTBUF_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6418); MMU::RegisterMMIO_32<HWREG::SICOUTBUF, SI_COUTBUF_R32, SI_COUTBUF_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6424); MMU::RegisterMMIO_32<HWREG::SIPOLL, SI_POLL_R32, SI_POLL_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6430); MMU::RegisterMMIO_32<HWREG::SICOMCSR, SI_COMCSR_R32, SI_COMCSR_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6434); MMU::RegisterMMIO_32<HWREG::SISR, SI_SR_R32, SI_SR_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6438); } } ================================================ FILE: src/Cafe/HW/SI/si.h ================================================ namespace HW_SI { void Initialize(); }; ================================================ FILE: src/Cafe/HW/VI/VI.cpp ================================================ #include "Cafe/HW/MMU/MMU.h" namespace HW_VI { RunAtCemuBoot _initVI([]() { //MMU::RegisterMMIO_R16(MMU::MMIOInterface::INTERFACE_0C000000, 0x1e0002, VI_UKN1E0002_R16); }); } ================================================ FILE: src/Cafe/IOSU/ODM/iosu_odm.cpp ================================================ #include <util/helpers/helpers.h> #include "iosu_odm.h" #include "config/ActiveSettings.h" #include "Common/FileStream.h" #include "util/helpers/Semaphore.h" #include "../kernel/iosu_kernel.h" namespace iosu { namespace odm { using namespace iosu::kernel; std::string s_devicePath = "/dev/odm"; std::thread s_serviceThread; std::atomic_bool s_requestStop{false}; std::atomic_bool s_isRunning{false}; std::atomic_bool s_threadInitialized{ false }; IOSMsgQueueId s_msgQueueId; SysAllocator<iosu::kernel::IOSMessage, 128> _s_msgBuffer; enum class ODM_CMD_OPERATION_TYPE { CHECK_STATE = 4, UKN_5 = 5, }; enum class ODM_STATE { NONE = 0, INITIAL = 1, AUTHENTICATION = 2, WAIT_FOR_DISC_READY = 3, CAFE_DISC = 4, RVL_DISC = 5, CLEANING_DISC = 6, INVALID_DISC = 8, DIRTY_DISC = 9, NO_DISC = 10, INVALID_DRIVE = 11, FATAL = 12, HARD_FATAL = 13, SHUTDOWN = 14, }; void ODMHandleCommandIoctl(uint32 clientHandle, IPCCommandBody* cmd, ODM_CMD_OPERATION_TYPE operationId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut) { switch(operationId) { case ODM_CMD_OPERATION_TYPE::CHECK_STATE: { *(uint32be*)ptrOut = (uint32)ODM_STATE::NO_DISC; break; } case ODM_CMD_OPERATION_TYPE::UKN_5: { // does this return anything? break; } default: { cemuLog_log(LogType::Force, "ODMHandleCommandIoctl: Unknown operationId %d\n", (uint32)operationId); break; } } IOS_ResourceReply(cmd, IOS_ERROR_OK); } uint32 CreateClientHandle() { return 1; // we dont care about handles for now } void CloseClientHandle(uint32 handle) { } void ODMServiceThread() { SetThreadName("ODMService"); s_msgQueueId = IOS_CreateMessageQueue(_s_msgBuffer.GetPtr(), _s_msgBuffer.GetCount()); cemu_assert(!IOS_ResultIsError((IOS_ERROR)s_msgQueueId)); IOS_ERROR r = IOS_RegisterResourceManager(s_devicePath.c_str(), s_msgQueueId); cemu_assert(!IOS_ResultIsError(r)); s_threadInitialized = true; while (true) { IOSMessage msg; IOS_ERROR r = IOS_ReceiveMessage(s_msgQueueId, &msg, 0); cemu_assert(!IOS_ResultIsError(r)); if (msg == 0) { cemu_assert_debug(s_requestStop); break; } IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); uint32 clientHandle = (uint32)cmd->devHandle; if (cmd->cmdId == IPCCommandId::IOS_OPEN) { IOS_ResourceReply(cmd, (IOS_ERROR)CreateClientHandle()); continue; } else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) { CloseClientHandle((IOSDevHandle)(uint32)cmd->devHandle); IOS_ResourceReply(cmd, IOS_ERROR_OK); continue; } else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) { uint32 requestId = cmd->args[0]; uint32 numIn = cmd->args[1]; uint32 numOut = cmd->args[2]; IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr(); IPCIoctlVector* vecIn = vec + numIn; IPCIoctlVector* vecOut = vec + 0; cemuLog_log(LogType::Force, "{}: Received unsupported Ioctlv cmd", s_devicePath); IOS_ResourceReply(cmd, IOS_ERROR_INVALID); continue; } else if (cmd->cmdId == IPCCommandId::IOS_IOCTL) { ODMHandleCommandIoctl(clientHandle, cmd, (ODM_CMD_OPERATION_TYPE)cmd->args[0].value(), MEMPTR<void>(cmd->args[1]), cmd->args[2], MEMPTR<void>(cmd->args[3]), cmd->args[4]); } else { cemuLog_log(LogType::Force, "{}: Unsupported cmdId", s_devicePath); cemu_assert_unimplemented(); IOS_ResourceReply(cmd, IOS_ERROR_INVALID); } } s_threadInitialized = false; } void Initialize() { if (s_isRunning.exchange(true)) return; s_threadInitialized = false; s_requestStop = false; s_serviceThread = std::thread(&ODMServiceThread); while (!s_threadInitialized) std::this_thread::sleep_for(std::chrono::milliseconds(10)); } void Shutdown() { if (!s_isRunning.exchange(false)) return; s_requestStop = true; IOS_SendMessage(s_msgQueueId, 0, 0); s_serviceThread.join(); } } } ================================================ FILE: src/Cafe/IOSU/ODM/iosu_odm.h ================================================ #pragma once namespace iosu { namespace odm { void Initialize(); void Shutdown(); } } ================================================ FILE: src/Cafe/IOSU/PDM/iosu_pdm.cpp ================================================ #include <util/helpers/helpers.h> #include "iosu_pdm.h" #include "Cafe/CafeSystem.h" #include "config/ActiveSettings.h" #include "Common/FileStream.h" #include "util/helpers/Semaphore.h" #if BOOST_OS_LINUX // using chrono::year_month_date and other features require a relatively recent stdlibc++ // to avoid upping the required version we use the STL reference implementation for now #include "Common/unix/date.h" namespace chrono_d = date; #else namespace chrono_d = std::chrono; #endif namespace iosu { namespace pdm { std::recursive_mutex sPlaystatsLock; std::recursive_mutex sDiaryLock; fs::path GetPDFile(const char* filename) { // todo - support for per-account tracking return ActiveSettings::GetMlcPath(fmt::format("usr/save/system/pdm/80000001/{}", filename)); } void MakeDirectory() { fs::path path = GetPDFile("."); std::error_code ec; fs::create_directories(path, ec); } chrono_d::year_month_day GetDateFromDayIndex(uint16 dayIndex) { chrono_d::sys_days startDateDay(chrono_d::year(2000) / chrono_d::January / chrono_d::day(1)); chrono_d::sys_days lastPlayedDay = startDateDay + chrono_d::days(dayIndex); return chrono_d::year_month_day(lastPlayedDay); } uint16 GetTodaysDayIndex() { chrono_d::sys_days startDateDay(chrono_d::year(2000) / chrono_d::January / chrono_d::day(1)); chrono_d::sys_days currentDateDay = chrono_d::floor<chrono_d::days>(std::chrono::system_clock::now()); return (uint16)(currentDateDay - startDateDay).count(); } struct PlayStatsEntry { uint32be titleIdHigh; uint32be titleIdLow; uint32be totalMinutesPlayed; uint16be numTimesLaunched; uint16be firstLaunchDayIndex; // first time this title was launched uint16be mostRecentDayIndex; // last time this title was played uint16be ukn12; // maybe just padding? }; static_assert(sizeof(PlayStatsEntry) == 0x14); struct { FileStream* fs{}; uint32be numEntries; PlayStatsEntry entry[NUM_PLAY_STATS_ENTRIES]; }PlayStats; void CreatePlaystats() { PlayStats.fs = FileStream::createFile2(GetPDFile("PlayStats.dat")); if (!PlayStats.fs) { cemuLog_log(LogType::Force, "Unable to open or create PlayStats.dat"); return; } uint32be entryCount = 0; PlayStats.fs->writeData(&entryCount, sizeof(uint32be)); PlayStats.fs->writeData(PlayStats.entry, NUM_PLAY_STATS_ENTRIES * sizeof(PlayStatsEntry)); static_assert((NUM_PLAY_STATS_ENTRIES * sizeof(PlayStatsEntry)) == 0x1400); } void OpenPlaystats() { std::unique_lock _l(sPlaystatsLock); PlayStats.numEntries = 0; for (size_t i = 0; i < NUM_PLAY_STATS_ENTRIES; i++) { auto& e = PlayStats.entry[i]; memset(&e, 0, sizeof(PlayStatsEntry)); } cemu_assert_debug(!PlayStats.fs); PlayStats.fs = FileStream::openFile2(GetPDFile("PlayStats.dat"), true); if (!PlayStats.fs) { CreatePlaystats(); return; } if (PlayStats.fs->GetSize() != (NUM_PLAY_STATS_ENTRIES * 20 + 4)) { delete PlayStats.fs; PlayStats.fs = nullptr; cemuLog_log(LogType::Force, "PlayStats.dat malformed. Time tracking wont be used"); // dont delete the existing file in case it could still be salvaged (todo) and instead just dont track play time return; } PlayStats.numEntries = 0; PlayStats.fs->readData(&PlayStats.numEntries, sizeof(uint32be)); if (PlayStats.numEntries > NUM_PLAY_STATS_ENTRIES) PlayStats.numEntries = NUM_PLAY_STATS_ENTRIES; PlayStats.fs->readData(PlayStats.entry, NUM_PLAY_STATS_ENTRIES * 20); } void ClosePlaystats() { std::unique_lock _l(sPlaystatsLock); if (PlayStats.fs) { delete PlayStats.fs; PlayStats.fs = nullptr; } } void UnloadPlaystats() { std::unique_lock _l(sPlaystatsLock); cemu_assert_debug(!PlayStats.fs); // unloading expects that file is closed PlayStats.numEntries = 0; for(auto& it : PlayStats.entry) it = PlayStatsEntry{}; } PlayStatsEntry* PlayStats_GetEntry(uint64 titleId) { std::unique_lock _l(sPlaystatsLock); uint32be titleIdHigh = (uint32)(titleId>>32); uint32be titleIdLow = (uint32)(titleId & 0xFFFFFFFF); size_t numEntries = PlayStats.numEntries; for (size_t i = 0; i < numEntries; i++) { if (PlayStats.entry[i].titleIdHigh == titleIdHigh && PlayStats.entry[i].titleIdLow == titleIdLow) return &PlayStats.entry[i]; } return nullptr; } void PlayStats_WriteEntryNoLock(PlayStatsEntry* entry, bool writeEntryCount = false) { if (!PlayStats.fs) return; size_t entryIndex = entry - PlayStats.entry; cemu_assert(entryIndex < NUM_PLAY_STATS_ENTRIES); PlayStats.fs->SetPosition(4 + entryIndex * sizeof(PlayStatsEntry)); if (PlayStats.fs->writeData(entry, sizeof(PlayStatsEntry)) != sizeof(PlayStatsEntry)) { cemuLog_log(LogType::Force, "Failed to write to PlayStats.dat"); return; } if (writeEntryCount) { uint32be numEntries = PlayStats.numEntries; PlayStats.fs->SetPosition(0); PlayStats.fs->writeData(&numEntries, sizeof(uint32be)); } } void PlayStats_WriteEntry(PlayStatsEntry* entry, bool writeEntryCount = false) { std::unique_lock _l(sPlaystatsLock); PlayStats_WriteEntryNoLock(entry, writeEntryCount); } PlayStatsEntry* PlayStats_CreateEntry(uint64 titleId) { std::unique_lock _l(sPlaystatsLock); bool entryCountChanged = false; PlayStatsEntry* newEntry; if(PlayStats.numEntries < NUM_PLAY_STATS_ENTRIES) { newEntry = PlayStats.entry + PlayStats.numEntries; PlayStats.numEntries += 1; entryCountChanged = true; } else { // list is full - find existing entry with least amount of minutes and replace it newEntry = PlayStats.entry + 0; for (uint32 i = 1; i < NUM_PLAY_STATS_ENTRIES; i++) { if(PlayStats.entry[i].totalMinutesPlayed < newEntry->totalMinutesPlayed) newEntry = PlayStats.entry + i; } } newEntry->titleIdHigh = (uint32)(titleId >> 32); newEntry->titleIdLow = (uint32)(titleId & 0xFFFFFFFF); newEntry->firstLaunchDayIndex = GetTodaysDayIndex(); newEntry->mostRecentDayIndex = newEntry->firstLaunchDayIndex; newEntry->numTimesLaunched = 1; newEntry->totalMinutesPlayed = 0; newEntry->ukn12 = 0; PlayStats_WriteEntryNoLock(newEntry, entryCountChanged); return newEntry; } // sets last played if entry already exists // if it does not exist it creates a new entry with first and last played set to today PlayStatsEntry* PlayStats_BeginNewTracking(uint64 titleId) { std::unique_lock _l(sPlaystatsLock); PlayStatsEntry* entry = PlayStats_GetEntry(titleId); if (entry) { entry->mostRecentDayIndex = GetTodaysDayIndex(); entry->numTimesLaunched += 1; PlayStats_WriteEntry(entry); return entry; } return PlayStats_CreateEntry(titleId); } void PlayStats_CountAdditionalMinutes(PlayStatsEntry* entry, uint32 additionalMinutes) { std::unique_lock _l(sPlaystatsLock); if (additionalMinutes == 0) return; entry->totalMinutesPlayed += additionalMinutes; entry->mostRecentDayIndex = GetTodaysDayIndex(); PlayStats_WriteEntryNoLock(entry); } struct PlayDiaryHeader { // the play diary is a rolling log // initially only writeIndex increases // after the log is filled, writeIndex wraps over and readIndex will increase as well uint32be readIndex; uint32be writeIndex; }; static_assert(sizeof(PlayDiaryEntry) == 0x10); static_assert(sizeof(PlayDiaryHeader) == 0x8); struct { FileStream* fs{}; PlayDiaryHeader header; PlayDiaryEntry entry[NUM_PLAY_DIARY_ENTRIES_MAX]; }PlayDiaryData; void CreatePlayDiary() { MakeDirectory(); cemu_assert_debug(!PlayDiaryData.fs); PlayDiaryData.fs = FileStream::createFile2(GetPDFile("PlayDiary.dat")); if (!PlayDiaryData.fs) { cemuLog_log(LogType::Force, "Failed to read or write PlayDiary.dat, playtime tracking will not be possible"); } // write header PlayDiaryData.header.readIndex = 0; PlayDiaryData.header.writeIndex = 0; if (PlayDiaryData.fs) PlayDiaryData.fs->writeData(&PlayDiaryData.header, sizeof(PlayDiaryHeader)); } void OpenPlayDiary() { std::unique_lock _lock(sDiaryLock); cemu_assert_debug(!PlayDiaryData.fs); PlayDiaryData.fs = FileStream::openFile2(GetPDFile("PlayDiary.dat"), true); if (!PlayDiaryData.fs) { CreatePlayDiary(); return; } // read header if (PlayDiaryData.fs->readData(&PlayDiaryData.header, sizeof(PlayDiaryHeader)) != sizeof(PlayDiaryHeader)) { cemuLog_log(LogType::Force, "Failed to read valid PlayDiary header"); delete PlayDiaryData.fs; PlayDiaryData.fs = nullptr; CreatePlayDiary(); return; } if (PlayDiaryData.header.readIndex > NUM_PLAY_DIARY_ENTRIES_MAX || PlayDiaryData.header.writeIndex > NUM_PLAY_DIARY_ENTRIES_MAX) { cemuLog_log(LogType::Force, "Bad value in play diary header (read={} write={})", (uint32)PlayDiaryData.header.readIndex, (uint32)PlayDiaryData.header.writeIndex); PlayDiaryData.header.readIndex = PlayDiaryData.header.readIndex % NUM_PLAY_DIARY_ENTRIES_MAX; PlayDiaryData.header.writeIndex = PlayDiaryData.header.writeIndex % NUM_PLAY_DIARY_ENTRIES_MAX; } // read entries and set any not-yet-written entries to zero uint32 readBytes = PlayDiaryData.fs->readData(PlayDiaryData.entry, NUM_PLAY_DIARY_ENTRIES_MAX * sizeof(PlayDiaryEntry)); uint32 readEntries = readBytes / sizeof(PlayDiaryEntry); while (readEntries < NUM_PLAY_DIARY_ENTRIES_MAX) { PlayDiaryData.entry[readEntries].titleId = 0; PlayDiaryData.entry[readEntries].ukn08 = 0; PlayDiaryData.entry[readEntries].dayIndex = 0; PlayDiaryData.entry[readEntries].ukn0E = 0; readEntries++; } } void ClosePlayDiary() { std::unique_lock _lock(sDiaryLock); if (PlayDiaryData.fs) { delete PlayDiaryData.fs; PlayDiaryData.fs = nullptr; } } void UnloadDiaryData() { std::unique_lock _lock(sDiaryLock); cemu_assert_debug(!PlayDiaryData.fs); // unloading expects that file is closed PlayDiaryData.header.readIndex = 0; PlayDiaryData.header.writeIndex = 0; for (auto& it : PlayDiaryData.entry) it = PlayDiaryEntry{}; } uint32 GetDiaryEntries(uint8 accountSlot, PlayDiaryEntry* diaryEntries, uint32 maxEntries) { std::unique_lock _lock(sDiaryLock); uint32 numReadEntries = 0; uint32 currentEntryIndex = PlayDiaryData.header.readIndex; while (currentEntryIndex != PlayDiaryData.header.writeIndex && numReadEntries < maxEntries) { *diaryEntries = PlayDiaryData.entry[currentEntryIndex]; numReadEntries++; diaryEntries++; currentEntryIndex = (currentEntryIndex+1) % NUM_PLAY_DIARY_ENTRIES_MAX; } return numReadEntries; } bool GetStatForGamelist(uint64 titleId, GameListStat& stat) { stat.last_played.year = 0; stat.last_played.month = 0; stat.last_played.day = 0; stat.numMinutesPlayed = 0; std::unique_lock _lock(sDiaryLock); // the play stats give us last time played and the total minutes PlayStatsEntry* playStats = PlayStats_GetEntry(titleId); if (playStats) { stat.numMinutesPlayed = playStats->totalMinutesPlayed; chrono_d::year_month_day ymd = GetDateFromDayIndex(playStats->mostRecentDayIndex); stat.last_played.year = (int)ymd.year(); stat.last_played.month = (unsigned int)ymd.month() - 1; stat.last_played.day = (unsigned int)ymd.day(); } _lock.unlock(); // check legacy time tracking for game entries in settings.xml std::unique_lock _lockGC(GetConfig().game_cache_entries_mutex); for (auto& gameEntry : GetConfig().game_cache_entries) { if(gameEntry.title_id != titleId) continue; stat.numMinutesPlayed += (gameEntry.legacy_time_played / 60); if (gameEntry.legacy_last_played != 0) { time_t td = gameEntry.legacy_last_played; tm* date = localtime(&td); uint32 legacyYear = (uint32)date->tm_year + 1900; uint32 legacyMonth = (uint32)date->tm_mon; uint32 legacyDay = (uint32)date->tm_mday; if (stat.last_played.year == 0 || std::tie(legacyYear, legacyMonth, legacyDay) > std::tie(stat.last_played.year, stat.last_played.month, stat.last_played.day)) { stat.last_played.year = legacyYear; stat.last_played.month = legacyMonth; stat.last_played.day = legacyDay; } } } return true; } std::thread sPDMTimeTrackingThread; CounterSemaphore sPDMSem; std::atomic_bool sPDMRequestExitThread{ false }; void TimeTrackingThread(uint64 titleId) { SetThreadName("PlayDiaryThread"); PlayStatsEntry* playStatsEntry = PlayStats_BeginNewTracking(titleId); auto startTime = std::chrono::steady_clock::now(); uint32 prevMinuteCounter = 0; while (true) { sPDMSem.decrementWithWaitAndTimeout(15000); if (sPDMRequestExitThread.load(std::memory_order::relaxed)) break; auto currentTime = std::chrono::steady_clock::now(); uint32 elapsedMinutes = std::chrono::duration_cast<std::chrono::minutes>(currentTime - startTime).count(); if (elapsedMinutes > prevMinuteCounter) { PlayStats_CountAdditionalMinutes(playStatsEntry, (elapsedMinutes - prevMinuteCounter)); // todo - also update PlayDiary (and other files) prevMinuteCounter = elapsedMinutes; } } } class : public ::IOSUModule { void PDMLoadAll() { OpenPlaystats(); OpenPlayDiary(); } void PDMUnloadAll() { UnloadPlaystats(); UnloadDiaryData(); } void PDMCloseAll() { ClosePlaystats(); ClosePlayDiary(); } void SystemLaunch() override { // todo - add support for per-account handling PDMLoadAll(); PDMCloseAll(); // close the files again, user may mess with MLC files or change MLC path while no game is running } void SystemExit() override { PDMCloseAll(); PDMUnloadAll(); } void TitleStart() override { // reload data and keep files open PDMUnloadAll(); PDMLoadAll(); auto titleId = CafeSystem::GetForegroundTitleId(); sPDMRequestExitThread = false; sPDMTimeTrackingThread = std::thread(TimeTrackingThread, titleId); } void TitleStop() override { sPDMRequestExitThread.store(true); sPDMSem.increment(); if(sPDMTimeTrackingThread.joinable()) sPDMTimeTrackingThread.join(); PDMCloseAll(); } }sIOSUModuleNNPDM; IOSUModule* GetModule() { return static_cast<IOSUModule*>(&sIOSUModuleNNPDM); } bool GameListStat::LastPlayDate::operator<(const LastPlayDate& b) const { const auto& a = *this; if(a.year < b.year) return true; if(a.year > b.year) return false; // same year if(a.month < b.month) return true; if(a.month > b.month) return false; // same year and month return a.day < b.day; } bool GameListStat::LastPlayDate::operator==(const LastPlayDate& b) const { const auto& a = *this; return a.year == b.year && a.month == b.month && a.day == b.day; } std::weak_ordering GameListStat::LastPlayDate::operator<=>(const LastPlayDate& b) const = default; }; }; ================================================ FILE: src/Cafe/IOSU/PDM/iosu_pdm.h ================================================ #pragma once #include "Cafe/IOSU/iosu_types_common.h" namespace iosu { namespace pdm { inline constexpr size_t NUM_PLAY_STATS_ENTRIES = 256; inline constexpr size_t NUM_PLAY_DIARY_ENTRIES_MAX = 18250; // 0x474A struct PlayDiaryEntry { uint64be titleId; uint32be ukn08; // probably minutes uint16be dayIndex; uint16be ukn0E; }; uint32 GetDiaryEntries(uint8 accountSlot, PlayDiaryEntry* diaryEntries, uint32 maxEntries); /* Helper for UI game list */ struct GameListStat { struct LastPlayDate { uint32 year; // if 0 -> never played uint32 month; uint32 day; bool operator<(const LastPlayDate& b) const; bool operator==(const LastPlayDate& b) const; std::weak_ordering operator<=>(const LastPlayDate& b) const; }last_played; uint32 numMinutesPlayed; }; bool GetStatForGamelist(uint64 titleId, GameListStat& stat); IOSUModule* GetModule(); }; }; ================================================ FILE: src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp ================================================ #include "iosu_ccr_nfc.h" #include "Cafe/IOSU/kernel/iosu_kernel.h" #include "util/crypto/aes128.h" #include <openssl/evp.h> #include <openssl/hmac.h> namespace iosu { namespace ccr_nfc { IOSMsgQueueId sCCRNFCMsgQueue; SysAllocator<iosu::kernel::IOSMessage, 0x20> sCCRNFCMsgQueueMsgBuffer; std::thread sCCRNFCThread; constexpr uint8 sNfcKey[] = { 0xC1, 0x2B, 0x07, 0x10, 0xD7, 0x2C, 0xEB, 0x5D, 0x43, 0x49, 0xB7, 0x43, 0xE3, 0xCA, 0xD2, 0x24 }; constexpr uint8 sNfcKeyIV[] = { 0x4F, 0xD3, 0x9A, 0x6E, 0x79, 0xFC, 0xEA, 0xAD, 0x99, 0x90, 0x4D, 0xB8, 0xEE, 0x38, 0xE9, 0xDB }; constexpr uint8 sUnfixedInfosMagicBytes[] = { 0x00, 0x00, 0xDB, 0x4B, 0x9E, 0x3F, 0x45, 0x27, 0x8F, 0x39, 0x7E, 0xFF, 0x9B, 0x4F, 0xB9, 0x93 }; constexpr uint8 sLockedSecretMagicBytes[] = { 0xFD, 0xC8, 0xA0, 0x76, 0x94, 0xB8, 0x9E, 0x4C, 0x47, 0xD3, 0x7D, 0xE8, 0xCE, 0x5C, 0x74, 0xC1 }; constexpr uint8 sUnfixedInfosString[] = { 0x75, 0x6E, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x73, 0x00, 0x00, 0x00 }; constexpr uint8 sLockedSecretString[] = { 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x00, 0x00, 0x00 }; constexpr uint8 sLockedSecretHmacKey[] = { 0x7F, 0x75, 0x2D, 0x28, 0x73, 0xA2, 0x00, 0x17, 0xFE, 0xF8, 0x5C, 0x05, 0x75, 0x90, 0x4B, 0x6D }; constexpr uint8 sUnfixedInfosHmacKey[] = { 0x1D, 0x16, 0x4B, 0x37, 0x5B, 0x72, 0xA5, 0x57, 0x28, 0xB9, 0x1D, 0x64, 0xB6, 0xA3, 0xC2, 0x05 }; uint8 sLockedSecretInternalKey[0x10]; uint8 sLockedSecretInternalNonce[0x10]; uint8 sLockedSecretInternalHmacKey[0x10]; uint8 sUnfixedInfosInternalKey[0x10]; uint8 sUnfixedInfosInternalNonce[0x10]; uint8 sUnfixedInfosInternalHmacKey[0x10]; sint32 __CCRNFCValidateCryptData(CCRNFCCryptData* data, uint32 size, bool validateOffsets) { if (!data) { return CCR_NFC_ERROR; } if (size != sizeof(CCRNFCCryptData)) { return CCR_NFC_ERROR; } if (!validateOffsets) { return 0; } // Make sure all offsets are within bounds if (data->version == 0) { if (data->unfixedInfosHmacOffset < 0x1C9 && data->unfixedInfosOffset < 0x1C9 && data->lockedSecretHmacOffset < 0x1C9 && data->lockedSecretOffset < 0x1C9 && data->lockedSecretSize < 0x1C9 && data->unfixedInfosSize < 0x1C9) { return 0; } } else if (data->version == 2) { if (data->unfixedInfosHmacOffset < 0x21D && data->unfixedInfosOffset < 0x21D && data->lockedSecretHmacOffset < 0x21D && data->lockedSecretOffset < 0x21D && data->lockedSecretSize < 0x21D && data->unfixedInfosSize < 0x21D) { return 0; } } return CCR_NFC_ERROR; } sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32 inSize, void* outData, uint32 outSize) { uint8 tmpIv[0x10]; memcpy(tmpIv, ivNonce, sizeof(tmpIv)); memcpy(outData, inData, inSize); AES128CTR_transform((uint8*)outData, outSize, (uint8*)key, tmpIv); return 0; } sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32 nameSize, const uint8* inData, uint32 inSize, uint8* outData, uint32 outSize) { if (nameSize != 0xe || outSize != 0x40) { return CCR_NFC_ERROR; } // Create a buffer containing 2 counter bytes, the key name, and the key data uint8 buffer[0x50]; buffer[0] = 0; buffer[1] = 0; memcpy(buffer + 2, name, nameSize); memcpy(buffer + nameSize + 2, inData, inSize); uint16 counter = 0; while (outSize > 0) { // Set counter bytes and increment counter buffer[0] = (counter >> 8) & 0xFF; buffer[1] = counter & 0xFF; counter++; uint32 dataSize = outSize; if (!HMAC(EVP_sha256(), hmacKey, hmacKeySize, buffer, sizeof(buffer), outData, &dataSize)) { return CCR_NFC_ERROR; } outSize -= 0x20; outData += 0x20; } return 0; } sint32 __CCRNFCGenerateInternalKeys(const CCRNFCCryptData* in, const uint8* keyGenSalt) { uint8 lockedSecretBuffer[0x40] = { 0 }; uint8 unfixedInfosBuffer[0x40] = { 0 }; uint8 outBuffer[0x40] = { 0 }; // Fill the locked secret buffer memcpy(lockedSecretBuffer, sLockedSecretMagicBytes, sizeof(sLockedSecretMagicBytes)); if (in->version == 0) { // For Version 0 this is the 16-byte Format Info memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 0x10); } else if (in->version == 2) { // For Version 2 this is 2 times the 7-byte UID + 1 check byte memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 8); memcpy(lockedSecretBuffer + 0x18, in->data + in->uuidOffset, 8); } else { return CCR_NFC_ERROR; } // Append key generation salt memcpy(lockedSecretBuffer + 0x20, keyGenSalt, 0x20); // Generate the key output sint32 res = __CCRNFCGenerateKey(sLockedSecretHmacKey, sizeof(sLockedSecretHmacKey), sLockedSecretString, 0xe, lockedSecretBuffer, sizeof(lockedSecretBuffer), outBuffer, sizeof(outBuffer)); if (res != 0) { return res; } // Unpack the key buffer memcpy(sLockedSecretInternalKey, outBuffer, 0x10); memcpy(sLockedSecretInternalNonce, outBuffer + 0x10, 0x10); memcpy(sLockedSecretInternalHmacKey, outBuffer + 0x20, 0x10); // Fill the unfixed infos buffer memcpy(unfixedInfosBuffer, in->data + in->seedOffset, 2); memcpy(unfixedInfosBuffer + 2, sUnfixedInfosMagicBytes + 2, 0xe); if (in->version == 0) { // For Version 0 this is the 16-byte Format Info memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 0x10); } else if (in->version == 2) { // For Version 2 this is 2 times the 7-byte UID + 1 check byte memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 8); memcpy(unfixedInfosBuffer + 0x18, in->data + in->uuidOffset, 8); } else { return CCR_NFC_ERROR; } // Append key generation salt memcpy(unfixedInfosBuffer + 0x20, keyGenSalt, 0x20); // Generate the key output res = __CCRNFCGenerateKey(sUnfixedInfosHmacKey, sizeof(sUnfixedInfosHmacKey), sUnfixedInfosString, 0xe, unfixedInfosBuffer, sizeof(unfixedInfosBuffer), outBuffer, sizeof(outBuffer)); if (res != 0) { return res; } // Unpack the key buffer memcpy(sUnfixedInfosInternalKey, outBuffer, 0x10); memcpy(sUnfixedInfosInternalNonce, outBuffer + 0x10, 0x10); memcpy(sUnfixedInfosInternalHmacKey, outBuffer + 0x20, 0x10); return 0; } sint32 __CCRNFCCryptData(const CCRNFCCryptData* in, CCRNFCCryptData* out, bool decrypt) { // Decrypt key generation salt uint8 keyGenSalt[0x20]; sint32 res = CCRNFCAESCTRCrypt(sNfcKey, sNfcKeyIV, in->data + in->keyGenSaltOffset, 0x20, keyGenSalt, sizeof(keyGenSalt)); if (res != 0) { return res; } // Prepare internal keys res = __CCRNFCGenerateInternalKeys(in, keyGenSalt); if (res != 0) { return res; } if (decrypt) { // Only version 0 tags have an encrypted locked secret area if (in->version == 0) { res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize); if (res != 0) { return res; } } // Decrypt unfxied infos res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize); if (res != 0) { return res; } // Verify HMACs uint8 hmacBuffer[0x20]; uint32 hmacLen = sizeof(hmacBuffer); if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) { return CCR_NFC_ERROR; } if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0) { return CCR_NFC_INVALID_LOCKED_SECRET; } if (in->version == 0) { hmacLen = sizeof(hmacBuffer); res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR; } else { hmacLen = sizeof(hmacBuffer); res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR; } if (memcmp(in->data + in->unfixedInfosHmacOffset, hmacBuffer, 0x20) != 0) { return CCR_NFC_INVALID_UNFIXED_INFOS; } } else { uint8 hmacBuffer[0x20]; uint32 hmacLen = sizeof(hmacBuffer); if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) { return CCR_NFC_ERROR; } if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0) { return CCR_NFC_INVALID_LOCKED_SECRET; } // Only version 0 tags have an encrypted locked secret area if (in->version == 0) { uint32 hmacLen = 0x20; if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, out->data + in->unfixedInfosHmacOffset, &hmacLen)) { return CCR_NFC_ERROR; } res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize); if (res != 0) { return res; } } else { uint32 hmacLen = 0x20; if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, out->data + in->unfixedInfosHmacOffset, &hmacLen)) { return CCR_NFC_ERROR; } } res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize); if (res != 0) { return res; } } return res; } void CCRNFCThread() { iosu::kernel::IOSMessage msg; while (true) { IOS_ERROR error = iosu::kernel::IOS_ReceiveMessage(sCCRNFCMsgQueue, &msg, 0); cemu_assert(!IOS_ResultIsError(error)); // Check for system exit if (msg == 0xf00dd00d) { return; } IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); if (cmd->cmdId == IPCCommandId::IOS_OPEN) { iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK); } else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) { iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK); } else if (cmd->cmdId == IPCCommandId::IOS_IOCTL) { sint32 result; uint32 requestId = cmd->args[0]; void* ptrIn = MEMPTR<void>(cmd->args[1]); uint32 sizeIn = cmd->args[2]; void* ptrOut = MEMPTR<void>(cmd->args[3]); uint32 sizeOut = cmd->args[4]; if ((result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrIn), sizeIn, true)) == 0 && (result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrOut), sizeOut, false)) == 0) { // Initialize outData with inData memcpy(ptrOut, ptrIn, sizeIn); switch (requestId) { case 1: // encrypt result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), false); break; case 2: // decrypt result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), true); break; default: cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IOCTL requestId"); cemu_assert_suspicious(); result = IOS_ERROR_INVALID; break; } } iosu::kernel::IOS_ResourceReply(cmd, static_cast<IOS_ERROR>(result)); } else { cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IPC cmdId"); cemu_assert_suspicious(); iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_INVALID); } } } class : public ::IOSUModule { void SystemLaunch() override { sCCRNFCMsgQueue = iosu::kernel::IOS_CreateMessageQueue(sCCRNFCMsgQueueMsgBuffer.GetPtr(), sCCRNFCMsgQueueMsgBuffer.GetCount()); cemu_assert(!IOS_ResultIsError(static_cast<IOS_ERROR>(sCCRNFCMsgQueue))); IOS_ERROR error = iosu::kernel::IOS_RegisterResourceManager("/dev/ccr_nfc", sCCRNFCMsgQueue); cemu_assert(!IOS_ResultIsError(error)); sCCRNFCThread = std::thread(CCRNFCThread); } void SystemExit() override { if (sCCRNFCMsgQueue < 0) { return; } iosu::kernel::IOS_SendMessage(sCCRNFCMsgQueue, 0xf00dd00d, 0); sCCRNFCThread.join(); iosu::kernel::IOS_DestroyMessageQueue(sCCRNFCMsgQueue); sCCRNFCMsgQueue = -1; } } sIOSUModuleCCRNFC; IOSUModule* GetModule() { return &sIOSUModuleCCRNFC; } } } ================================================ FILE: src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h ================================================ #pragma once #include "Cafe/IOSU/iosu_types_common.h" #define CCR_NFC_ERROR (-0x2F001E) #define CCR_NFC_INVALID_LOCKED_SECRET (-0x2F0029) #define CCR_NFC_INVALID_UNFIXED_INFOS (-0x2F002A) namespace iosu { namespace ccr_nfc { struct CCRNFCCryptData { uint32 version; uint32 dataSize; uint32 seedOffset; uint32 keyGenSaltOffset; uint32 uuidOffset; uint32 unfixedInfosOffset; uint32 unfixedInfosSize; uint32 lockedSecretOffset; uint32 lockedSecretSize; uint32 unfixedInfosHmacOffset; uint32 lockedSecretHmacOffset; uint8 data[540]; }; static_assert(sizeof(CCRNFCCryptData) == 0x248); IOSUModule* GetModule(); } } ================================================ FILE: src/Cafe/IOSU/fsa/fsa_types.h ================================================ #pragma once enum class FS_RESULT : sint32 // aka FSStatus { SUCCESS = 0, END_ITERATION = -2, // used by FSGetMountSource / FSGetMountSourceNext to indicate when last element was reached MAX_HANDLES = -3, ALREADY_EXISTS = -5, NOT_FOUND = -6, NOT_FILE = -7, NOT_DIR = -8, PERMISSION_ERROR = -10, FATAL_ERROR = -0x400, ERR_PLACEHOLDER = -9999, // used when exact error code has yet to be determined }; enum class FSA_RESULT : sint32 // aka FSError/FSAStatus { OK = 0, NOT_INIT = -0x30000 - 0x01, END_OF_DIRECTORY = -0x30000 - 0x04, END_OF_FILE = -0x30000 - 0x05, MAX_CLIENTS = -0x30000 - 0x12, MAX_FILES = -0x30000 - 0x13, MAX_DIRS = -0x30000 - 0x14, ALREADY_EXISTS = -0x30000 - 0x16, NOT_FOUND = -0x30000 - 0x17, PERMISSION_ERROR = -0x30000 - 0x1A, INVALID_PARAM = -0x30000 - 0x21, INVALID_PATH = -0x30000 - 0x22, INVALID_BUFFER = -0x30000 - 0x23, INVALID_ALIGNMENT = -0x30000 - 0x24, INVALID_CLIENT_HANDLE = -0x30000 - 0x25, INVALID_FILE_HANDLE = -0x30000 - 0x26, INVALID_DIR_HANDLE = -0x30000 - 0x27, NOT_FILE = -0x30000 - 0x28, NOT_DIR = -0x30000 - 0x29, OUT_OF_RESOURCES = -0x30000 - 0x2C, FATAL_ERROR = -0x30000 - 0x400, }; enum class FSA_CMD_OPERATION_TYPE : uint32 { CHANGEDIR = 0x5, GETCWD = 0x6, MAKEDIR = 0x7, REMOVE = 0x8, RENAME = 0x9, OPENDIR = 0xA, READDIR = 0xB, REWINDDIR = 0xC, CLOSEDIR = 0xD, OPENFILE = 0xE, READ = 0xF, WRITE = 0x10, GETPOS = 0x11, SETPOS = 0x12, ISEOF = 0x13, GETSTATFILE = 0x14, CLOSEFILE = 0x15, FLUSHFILE = 0x17, QUERYINFO = 0x18, APPENDFILE = 0x19, TRUNCATEFILE = 0x1A, FLUSHQUOTA = 0x1E, }; using FSResHandle = sint32; using FSFileHandle2 = FSResHandle; using FSDirHandle2 = FSResHandle; #define FS_INVALID_HANDLE_VALUE -1 #define FSA_FILENAME_SIZE_MAX 128 #define FSA_PATH_SIZE_MAX (512 + FSA_FILENAME_SIZE_MAX) #define FSA_CMD_PATH_MAX_LENGTH FSA_PATH_SIZE_MAX #define FSA_MAX_CLIENTS 32 typedef sint32 FSStatus; // DEPR - replaced by FS_RESULT typedef uint32 FS_ERROR_MASK; // replace with enum bitmask typedef uint32 FSFileSize; typedef uint64 FSLargeSize; typedef uint64 FSTime; enum class FSFlag : uint32 { NONE = 0, IS_DIR = 0x80000000, IS_FILE = 0x01000000, }; DEFINE_ENUM_FLAG_OPERATORS(FSFlag); #pragma pack(1) struct FSStat_t { /* +0x000 */ betype<FSFlag> flag; /* +0x004 */ uint32be permissions; /* +0x008 */ uint32be ownerId; /* +0x00C */ uint32be groupId; /* +0x010 */ betype<FSFileSize> size; /* +0x014 */ betype<FSFileSize> allocatedSize; /* +0x018 */ betype<FSLargeSize> quotaSize; /* +0x020 */ uint32be entryId; /* +0x024 */ betype<FSTime> createdTime; /* +0x02C */ betype<FSTime> modifiedTime; /* +0x034 */ uint8 attributes[0x30]; }; static_assert(sizeof(FSStat_t) == 0x64); struct FSDirEntry_t { /* +0x00 */ FSStat_t stat; /* +0x64 */ char name[FSA_FILENAME_SIZE_MAX]; }; static_assert(sizeof(FSDirEntry_t) == 0xE4); struct FSADeviceInfo_t { uint8 ukn0[0x8]; uint64be deviceSizeInSectors; uint32be deviceSectorSize; uint8 ukn014[0x14]; }; static_assert(sizeof(FSADeviceInfo_t) == 0x28); #pragma pack() // query types for QueryInfo #define FSA_QUERY_TYPE_FREESPACE 0 #define FSA_QUERY_TYPE_DEVICE_INFO 4 #define FSA_QUERY_TYPE_STAT 5 ================================================ FILE: src/Cafe/IOSU/fsa/iosu_fsa.cpp ================================================ #include "fsa_types.h" #include "iosu_fsa.h" #include "Cafe/IOSU/kernel/iosu_kernel.h" #include "Cafe/Filesystem/fsc.h" #include "util/helpers/helpers.h" #include "Cafe/OS/libs/coreinit/coreinit_FS.h" // get rid of this dependency, requires reworking some of the IPC stuff. See locations where we use coreinit::FSCmdBlockBody_t #include "Cafe/HW/Latte/Core/LatteBufferCache.h" // also remove this dependency #include "Cafe/HW/MMU/MMU.h" using namespace iosu::kernel; namespace iosu { namespace fsa { IOSMsgQueueId sFSAIoMsgQueue; SysAllocator<iosu::kernel::IOSMessage, 352> _m_sFSAIoMsgQueueMsgBuffer; std::thread sFSAIoThread; struct FSAClient // IOSU's counterpart to the coreinit FSClient struct { std::string workingDirectory; bool isAllocated{false}; void AllocateAndInitialize() { isAllocated = true; workingDirectory = std::string("/"); } void ReleaseAndCleanup() { isAllocated = false; } }; std::array<FSAClient, 624> sFSAClientArray; IOS_ERROR FSAAllocateClient(sint32& indexOut) { for (size_t i = 0; i < sFSAClientArray.size(); i++) { if (sFSAClientArray[i].isAllocated) continue; sFSAClientArray[i].AllocateAndInitialize(); indexOut = (sint32)i; return IOS_ERROR_OK; } return (IOS_ERROR)0xFFFCFFEE; } FSA_RESULT FSA_convertFSCtoFSAStatus(sint32 fscError) { if (fscError == FSC_STATUS_OK) return FSA_RESULT::OK; else if (fscError == FSC_STATUS_FILE_NOT_FOUND) return FSA_RESULT::NOT_FOUND; else if (fscError == FSC_STATUS_ALREADY_EXISTS) return FSA_RESULT::ALREADY_EXISTS; cemu_assert_unimplemented(); return FSA_RESULT::FATAL_ERROR; } std::string __FSATranslatePath(FSAClient* fsaClient, std::string_view input, bool endWithSlash = false) { std::string tmp; if (input.empty()) { tmp.assign(fsaClient->workingDirectory); if (endWithSlash) tmp.push_back('/'); return tmp; } char c = input.front(); cemu_assert_debug(c != '\\'); // how to handle backward slashes? if (c == '/') { // absolute path tmp.assign("/"); // keep the leading slash } else if (c == '~') { cemu_assert_debug(false); input.remove_prefix(1); tmp.assign("/"); } else { // in all other cases the path is relative to the working directory tmp.assign(fsaClient->workingDirectory); } // parse path size_t idx = 0; while (idx < input.size()) { c = input[idx]; if (c == '/') { idx++; continue; // filter leading and repeated slashes } if (c == '.') { if ((input.size() - idx) >= 3 && input[idx + 1] == '.' && input[idx + 2] == '/') { // "../" while(!tmp.empty()) { if(tmp.back() == '/') { tmp.pop_back(); break; } tmp.pop_back(); } idx += 3; continue; } else if ((input.size() - idx) == 2 && input[idx + 1] == '.') { // ".." at the end while(!tmp.empty()) { if(tmp.back() == '/') { tmp.pop_back(); break; } tmp.pop_back(); } idx += 2; continue; } else if ((input.size() - idx) >= 2 && input[idx + 1] == '/') { // "./" idx += 2; continue; } } if (!tmp.empty() && tmp.back() != '/') tmp.push_back('/'); while (idx < input.size()) { c = input[idx]; if (c == '/') break; tmp.push_back(c); idx++; } } if (endWithSlash) { if (tmp.empty() || tmp.back() != '/') tmp.push_back('/'); } return tmp; } FSCVirtualFile* __FSAOpenNode(FSAClient* client, std::string_view path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus) { std::string translatedPath = __FSATranslatePath(client, path); return fsc_open(translatedPath.c_str(), accessFlags, &fscStatus); } class _FSAHandleTable { struct _FSAHandleResource { bool isAllocated{false}; FSCVirtualFile* fscFile; uint16 handleCheckValue; }; public: FSA_RESULT AllocateHandle(FSResHandle& handleOut, FSCVirtualFile* fscFile) { for (size_t i = 0; i < m_handleTable.size(); i++) { auto& it = m_handleTable.at(i); if (it.isAllocated) continue; uint16 checkValue = (uint16)m_currentCounter; m_currentCounter++; it.handleCheckValue = checkValue; it.fscFile = fscFile; it.isAllocated = true; uint32 handleVal = ((uint32)i << 16) | (uint32)checkValue; handleOut = (FSResHandle)handleVal; return FSA_RESULT::OK; } cemuLog_log(LogType::Force, "FSA: Ran out of file handles"); return FSA_RESULT::FATAL_ERROR; } FSA_RESULT ReleaseHandle(FSResHandle handle) { uint16 index = (uint16)((uint32)handle >> 16); uint16 checkValue = (uint16)(handle & 0xFFFF); if (index >= m_handleTable.size()) return FSA_RESULT::INVALID_FILE_HANDLE; auto& it = m_handleTable.at(index); if (!it.isAllocated) return FSA_RESULT::INVALID_FILE_HANDLE; if (it.handleCheckValue != checkValue) return FSA_RESULT::INVALID_FILE_HANDLE; it.fscFile = nullptr; it.isAllocated = false; return FSA_RESULT::OK; } FSCVirtualFile* GetByHandle(FSResHandle handle) { uint16 index = (uint16)((uint32)handle >> 16); uint16 checkValue = (uint16)(handle & 0xFFFF); if (index >= m_handleTable.size()) return nullptr; auto& it = m_handleTable.at(index); if (!it.isAllocated) return nullptr; if (it.handleCheckValue != checkValue) return nullptr; return it.fscFile; } private: uint32 m_currentCounter = 1; std::array<_FSAHandleResource, 0x3C0> m_handleTable; }; _FSAHandleTable sFileHandleTable; _FSAHandleTable sDirHandleTable; FSA_RESULT __FSAOpenFile(FSAClient* client, const char* path, const char* accessModifierStr, sint32* fileHandle) { *fileHandle = FS_INVALID_HANDLE_VALUE; FSC_ACCESS_FLAG accessModifier = FSC_ACCESS_FLAG::NONE; bool truncateFile = false; // todo: Support for this bool isAppend = false; // todo: proper support for this (all write operations should move cursor to the end of the file?) if (strcmp(accessModifierStr, "r") == 0 || strcmp(accessModifierStr, "rb") == 0) accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION; else if (strcmp(accessModifierStr, "r+") == 0) { // the cursor will be set to the beginning of the file // allows read and write access accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION; // read, write } else if (strcmp(accessModifierStr, "w") == 0) { accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE; // create new file & write truncateFile = true; } else if (strcmp(accessModifierStr, "w+") == 0) { accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE; // create new file & read & write truncateFile = true; } else if (strcmp(accessModifierStr, "wb") == 0) // used in Super Meat Boy { // b flag is allowed has no effect accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE; // create new file & write truncateFile = true; } else if (strcmp(accessModifierStr, "a+") == 0) { accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALLOW_CREATE | FSC_ACCESS_FLAG::IS_APPEND; isAppend = true; } else if (strcmp(accessModifierStr, "a") == 0) { accessModifier = FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALLOW_CREATE | FSC_ACCESS_FLAG::IS_APPEND; isAppend = true; } else cemu_assert_debug(false); accessModifier |= FSC_ACCESS_FLAG::OPEN_DIR | FSC_ACCESS_FLAG::OPEN_FILE; sint32 fscStatus; FSCVirtualFile* fscFile = __FSAOpenNode(client, path, accessModifier, fscStatus); if (!fscFile) return FSA_RESULT::NOT_FOUND; if (fscFile->fscGetType() != FSC_TYPE_FILE) { delete fscFile; return FSA_RESULT::NOT_FILE; } if (isAppend) fsc_setFileSeek(fscFile, fsc_getFileSize(fscFile)); FSResHandle fsFileHandle; FSA_RESULT r = sFileHandleTable.AllocateHandle(fsFileHandle, fscFile); if (r != FSA_RESULT::OK) { cemuLog_log(LogType::Force, "Exceeded maximum number of FSA file handles"); delete fscFile; return FSA_RESULT::MAX_FILES; } *fileHandle = fsFileHandle; cemuLog_log(LogType::CoreinitFile, "Open file {} (access: {} result: ok handle: 0x{})", path, accessModifierStr, (uint32)*fileHandle); return FSA_RESULT::OK; } FSA_RESULT __FSAOpenDirectory(FSAClient* client, std::string_view path, sint32* dirHandle) { *dirHandle = FS_INVALID_HANDLE_VALUE; sint32 fscStatus; FSCVirtualFile* fscFile = __FSAOpenNode(client, path, FSC_ACCESS_FLAG::OPEN_DIR | FSC_ACCESS_FLAG::OPEN_FILE, fscStatus); if (!fscFile) return FSA_RESULT::NOT_FOUND; if (fscFile->fscGetType() != FSC_TYPE_DIRECTORY) { delete fscFile; return FSA_RESULT::NOT_DIR; } FSResHandle fsDirHandle; FSA_RESULT r = sDirHandleTable.AllocateHandle(fsDirHandle, fscFile); if (r != FSA_RESULT::OK) { delete fscFile; return FSA_RESULT::MAX_DIRS; } *dirHandle = fsDirHandle; cemuLog_log(LogType::CoreinitFile, "Open directory {} (result: ok handle: 0x{})", path, (uint32)*dirHandle); return FSA_RESULT::OK; } FSA_RESULT __FSACloseFile(uint32 fileHandle) { uint8 handleType = 0; FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); if (!fscFile) { cemuLog_logDebug(LogType::Force, "__FSACloseFile(): Invalid handle (0x{:08x})", fileHandle); return FSA_RESULT::INVALID_FILE_HANDLE; } // unregister file sFileHandleTable.ReleaseHandle(fileHandle); // todo - use the error code of this fsc_close(fscFile); return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_remove(FSAClient* client, FSAShimBuffer* shimBuffer) { std::string path = __FSATranslatePath(client, (char*)shimBuffer->request.cmdRemove.path); sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; fsc_remove(path.c_str(), &fscStatus); return FSA_convertFSCtoFSAStatus(fscStatus); } FSA_RESULT FSAProcessCmd_makeDir(FSAClient* client, FSAShimBuffer* shimBuffer) { std::string path = __FSATranslatePath(client, (char*)shimBuffer->request.cmdMakeDir.path); sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; fsc_createDir(path.c_str(), &fscStatus); return FSA_convertFSCtoFSAStatus(fscStatus); } FSA_RESULT FSAProcessCmd_rename(FSAClient* client, FSAShimBuffer* shimBuffer) { std::string srcPath = __FSATranslatePath(client, (char*)shimBuffer->request.cmdRename.srcPath); std::string dstPath = __FSATranslatePath(client, (char*)shimBuffer->request.cmdRename.dstPath); sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; fsc_rename(srcPath.c_str(), dstPath.c_str(), &fscStatus); return FSA_convertFSCtoFSAStatus(fscStatus); } bool __FSA_GetStatFromFSCFile(FSCVirtualFile* fscFile, FSStat_t* fsStatOut) { memset(fsStatOut, 0x00, sizeof(FSStat_t)); FSFlag statFlag = FSFlag::NONE; if (fsc_isDirectory(fscFile)) { // note: Only quota (save) directories have the size field set. For other directories it's zero. // Hyrule Warriors relies on the size field being zero for /vol/content/data/. Otherwise it will try to read it like a file and get stuck in an endless loop. // Art Academy reads the size for save directories statFlag |= FSFlag::IS_DIR; fsStatOut->size = 0; } else if (fsc_isFile(fscFile)) { fsStatOut->size = fsc_getFileSize(fscFile); statFlag |= FSFlag::IS_FILE; } else { cemu_assert_suspicious(); } fsStatOut->permissions = 0x777; // format matches unix (fstat) permissions? fsStatOut->flag = statFlag; return true; } FSA_RESULT __FSA_GetFileStat(FSAClient* client, const char* path, FSStat_t* fsStatOut) { sint32 fscStatus; FSCVirtualFile* fscFile = __FSAOpenNode(client, path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::OPEN_DIR, fscStatus); if (!fscFile) return FSA_convertFSCtoFSAStatus(fscStatus); __FSA_GetStatFromFSCFile(fscFile, fsStatOut); delete fscFile; return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_queryInfo(FSAClient* client, FSAShimBuffer* shimBuffer) { char* path = (char*)shimBuffer->request.cmdQueryInfo.query; uint32 queryType = shimBuffer->request.cmdQueryInfo.queryType; // handle query sint32 fscStatus = FSC_STATUS_OK; if (queryType == FSA_QUERY_TYPE_STAT) { FSStat_t* fsStat = &shimBuffer->response.cmdQueryInfo.queryStat.stat; FSA_RESULT fsaStatus = __FSA_GetFileStat(client, path, fsStat); return fsaStatus; } else if (queryType == FSA_QUERY_TYPE_FREESPACE) { sint32 fscStatus; FSCVirtualFile* fscFile = __FSAOpenNode(client, path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::OPEN_DIR, fscStatus); if (!fscFile) return FSA_convertFSCtoFSAStatus(fscStatus); betype<uint64>* fsStatSize = &shimBuffer->response.cmdQueryInfo.queryFreeSpace.freespace; *fsStatSize = 30ull * 1024 * 1024 * 1024; // placeholder value. How is this determined? delete fscFile; return FSA_RESULT::OK; } else if (queryType == FSA_QUERY_TYPE_DEVICE_INFO) { FSADeviceInfo_t* deviceInfo = &shimBuffer->response.cmdQueryInfo.queryDeviceInfo.info; // always report hardcoded values for now. deviceInfo->deviceSectorSize = 512; deviceInfo->deviceSizeInSectors = (32ull * 1024 * 1024 * 1024) / deviceInfo->deviceSectorSize; cemu_assert_suspicious(); return FSA_RESULT::OK; } else cemu_assert_unimplemented(); return FSA_convertFSCtoFSAStatus(fscStatus); } FSA_RESULT FSAProcessCmd_getStatFile(FSAClient* client, FSAShimBuffer* shimBuffer) { FSFileHandle2 fileHandle = shimBuffer->request.cmdGetStatFile.fileHandle; FSStat_t* statOut = &shimBuffer->response.cmdStatFile.statOut; FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); if (!fscFile) return FSA_RESULT::NOT_FOUND; cemu_assert_debug(fsc_isFile(fscFile)); __FSA_GetStatFromFSCFile(fscFile, statOut); return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_read(FSAClient* client, FSAShimBuffer* shimBuffer, MEMPTR<void> destPtr, uint32be transferSize) { uint32 transferElementSize = shimBuffer->request.cmdReadFile.size; uint32 filePos = shimBuffer->request.cmdReadFile.filePos; uint32 fileHandle = shimBuffer->request.cmdReadFile.fileHandle; uint32 flags = shimBuffer->request.cmdReadFile.flag; FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); if (!fscFile) return FSA_RESULT::INVALID_FILE_HANDLE; uint32 bytesToRead = transferSize; // update file position if flag is set if ((flags & FSA_CMD_FLAG_SET_POS) != 0) fsc_setFileSeek(fscFile, filePos); // todo: File permissions uint32 bytesSuccessfullyRead = fsc_readFile(fscFile, destPtr, bytesToRead); if (transferElementSize == 0) return FSA_RESULT::OK; LatteBufferCache_notifyDCFlush(destPtr.GetMPTR(), bytesToRead); return (FSA_RESULT)(bytesSuccessfullyRead / transferElementSize); // return number of elements read } FSA_RESULT FSAProcessCmd_write(FSAClient* client, FSAShimBuffer* shimBuffer, MEMPTR<void> destPtr, uint32be transferSize) { uint32 transferElementSize = shimBuffer->request.cmdWriteFile.size; uint32 filePos = shimBuffer->request.cmdWriteFile.filePos; uint32 fileHandle = shimBuffer->request.cmdWriteFile.fileHandle; uint32 flags = shimBuffer->request.cmdWriteFile.flag; FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); if (!fscFile) return FSA_RESULT::INVALID_FILE_HANDLE; cemu_assert_debug((transferSize % transferElementSize) == 0); uint32 bytesToWrite = transferSize; // check for write permission (should this happen before or after setting file position?) if (!fsc_isWritable(fscFile)) { cemu_assert_debug(false); return FSA_RESULT::PERMISSION_ERROR; } // update file position if flag is set if ((flags & FSA_CMD_FLAG_SET_POS) != 0) fsc_setFileSeek(fscFile, filePos); uint32 bytesSuccessfullyWritten = fsc_writeFile(fscFile, destPtr, bytesToWrite); debug_printf("FSAProcessCmd_write(): Writing 0x%08x bytes (bytes actually written: 0x%08x)\n", bytesToWrite, bytesSuccessfullyWritten); return (FSA_RESULT)(bytesSuccessfullyWritten / transferElementSize); // return number of elements read } FSA_RESULT FSAProcessCmd_setPos(FSAClient* client, FSAShimBuffer* shimBuffer) { uint32 fileHandle = shimBuffer->request.cmdSetPosFile.fileHandle; uint32 filePos = shimBuffer->request.cmdSetPosFile.filePos; FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); if (!fscFile) return FSA_RESULT::INVALID_FILE_HANDLE; fsc_setFileSeek(fscFile, filePos); return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_getPos(FSAClient* client, FSAShimBuffer* shimBuffer) { uint32 fileHandle = shimBuffer->request.cmdGetPosFile.fileHandle; FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); if (!fscFile) return FSA_RESULT::INVALID_FILE_HANDLE; uint32 filePos = fsc_getFileSeek(fscFile); shimBuffer->response.cmdGetPosFile.filePos = filePos; return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_openFile(FSAClient* client, FSAShimBuffer* shimBuffer) { sint32 fileHandle = 0; FSA_RESULT fsaResult = __FSAOpenFile(client, (char*)shimBuffer->request.cmdOpenFile.path, (char*)shimBuffer->request.cmdOpenFile.mode, &fileHandle); shimBuffer->response.cmdOpenFile.fileHandleOutput = fileHandle; return fsaResult; } FSA_RESULT FSAProcessCmd_closeFile(FSAClient* client, FSAShimBuffer* shimBuffer) { return __FSACloseFile(shimBuffer->request.cmdCloseFile.fileHandle); } FSA_RESULT FSAProcessCmd_openDir(FSAClient* client, FSAShimBuffer* shimBuffer) { sint32 dirHandle = 0; FSA_RESULT fsaResult = __FSAOpenDirectory(client, (const char*)shimBuffer->request.cmdOpenFile.path, &dirHandle); shimBuffer->response.cmdOpenDir.dirHandleOutput = dirHandle; return fsaResult; } FSA_RESULT FSAProcessCmd_readDir(FSAClient* client, FSAShimBuffer* shimBuffer) { FSCVirtualFile* fscFile = sDirHandleTable.GetByHandle((sint32)shimBuffer->request.cmdReadDir.dirHandle); if (!fscFile) return FSA_RESULT::INVALID_DIR_HANDLE; FSDirEntry_t* dirEntryOut = &shimBuffer->response.cmdReadDir.dirEntry; FSCDirEntry fscDirEntry; if (fsc_nextDir(fscFile, &fscDirEntry) == false) return FSA_RESULT::END_OF_DIRECTORY; strcpy(dirEntryOut->name, fscDirEntry.path); FSFlag statFlag = FSFlag::NONE; dirEntryOut->stat.size = 0; if (fscDirEntry.isDirectory) { statFlag |= FSFlag::IS_DIR; } else if (fscDirEntry.isFile) { statFlag |= FSFlag::IS_FILE; dirEntryOut->stat.size = fscDirEntry.fileSize; } dirEntryOut->stat.flag = statFlag; dirEntryOut->stat.permissions = 0x777; return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_closeDir(FSAClient* client, FSAShimBuffer* shimBuffer) { FSCVirtualFile* fscFile = sDirHandleTable.GetByHandle((sint32)shimBuffer->request.cmdReadDir.dirHandle); if (!fscFile) { cemuLog_logDebug(LogType::Force, "CloseDir: Invalid handle (0x{:08x})", (sint32)shimBuffer->request.cmdReadDir.dirHandle); return FSA_RESULT::INVALID_DIR_HANDLE; } sDirHandleTable.ReleaseHandle(shimBuffer->request.cmdReadDir.dirHandle); fsc_close(fscFile); return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_flushQuota(FSAClient* client, FSAShimBuffer* shimBuffer) { return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_rewindDir(FSAClient* client, FSAShimBuffer* shimBuffer) { FSCVirtualFile* fscFile = sDirHandleTable.GetByHandle((sint32)shimBuffer->request.cmdRewindDir.dirHandle); if (!fscFile) { cemuLog_logDebug(LogType::Force, "RewindDir: Invalid handle (0x{:08x})", (sint32)shimBuffer->request.cmdRewindDir.dirHandle); return FSA_RESULT::INVALID_DIR_HANDLE; } if (!fscFile->fscRewindDir()) return FSA_RESULT::FATAL_ERROR; return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_flushFile(FSAClient* client, FSAShimBuffer* shimBuffer) { return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_appendFile(FSAClient* client, FSAShimBuffer* shimBuffer) { FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(shimBuffer->request.cmdAppendFile.fileHandle); if (!fscFile) return FSA_RESULT::INVALID_FILE_HANDLE; #ifdef CEMU_DEBUG_ASSERT cemuLog_log(LogType::Force, "FSAProcessCmd_appendFile(): size 0x{:08x} count 0x{:08x} (todo)\n", shimBuffer->request.cmdAppendFile.size, shimBuffer->request.cmdAppendFile.count); #endif return (FSA_RESULT)(shimBuffer->request.cmdAppendFile.count.value()); } FSA_RESULT FSAProcessCmd_truncateFile(FSAClient* client, FSAShimBuffer* shimBuffer) { FSFileHandle2 fileHandle = shimBuffer->request.cmdTruncateFile.fileHandle; FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); if (!fscFile) return FSA_RESULT::INVALID_FILE_HANDLE; fsc_setFileLength(fscFile, fsc_getFileSeek(fscFile)); return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_isEof(FSAClient* client, FSAShimBuffer* shimBuffer) { uint32 fileHandle = shimBuffer->request.cmdIsEof.fileHandle; FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); if (!fscFile) return FSA_RESULT::INVALID_FILE_HANDLE; uint32 filePos = fsc_getFileSeek(fscFile); uint32 fileSize = fsc_getFileSize(fscFile); if (filePos >= fileSize) return FSA_RESULT::END_OF_FILE; return FSA_RESULT::OK; } FSA_RESULT FSAProcessCmd_getCwd(FSAClient* client, FSAShimBuffer* shimBuffer) { char* pathOutput = shimBuffer->response.cmdGetCWD.path; sint32 pathOutputMaxLen = sizeof(shimBuffer->response.cmdGetCWD.path); cemu_assert(pathOutputMaxLen > 0); sint32 fscStatus = FSC_STATUS_OK; strncpy(pathOutput, client->workingDirectory.data(), std::min(client->workingDirectory.size() + 1, (size_t)pathOutputMaxLen)); pathOutput[pathOutputMaxLen - 1] = '\0'; return FSA_convertFSCtoFSAStatus(fscStatus); } FSA_RESULT FSAProcessCmd_changeDir(FSAClient* client, FSAShimBuffer* shimBuffer) { const char* path = (const char*)shimBuffer->request.cmdChangeDir.path; shimBuffer->request.cmdChangeDir.path[sizeof(shimBuffer->request.cmdChangeDir.path) - 1] = '\0'; sint32 fscStatus = FSC_STATUS_OK; client->workingDirectory.assign(__FSATranslatePath(client, path, true)); return FSA_convertFSCtoFSAStatus(fscStatus); } void FSAHandleCommandIoctlv(FSAClient* client, IPCCommandBody* cmd, FSA_CMD_OPERATION_TYPE operationId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec) { FSA_RESULT fsaResult = FSA_RESULT::FATAL_ERROR; switch (operationId) { case FSA_CMD_OPERATION_TYPE::READ: { fsaResult = FSAProcessCmd_read(client, (FSAShimBuffer*)vec[0].basePhys.GetPtr(), vec[1].basePhys, vec[1].size); break; } case FSA_CMD_OPERATION_TYPE::WRITE: { fsaResult = FSAProcessCmd_write(client, (FSAShimBuffer*)vec[0].basePhys.GetPtr(), vec[1].basePhys, vec[1].size); break; } case FSA_CMD_OPERATION_TYPE::CHANGEDIR: case FSA_CMD_OPERATION_TYPE::GETCWD: case FSA_CMD_OPERATION_TYPE::MAKEDIR: case FSA_CMD_OPERATION_TYPE::RENAME: case FSA_CMD_OPERATION_TYPE::OPENDIR: case FSA_CMD_OPERATION_TYPE::READDIR: case FSA_CMD_OPERATION_TYPE::CLOSEDIR: case FSA_CMD_OPERATION_TYPE::OPENFILE: case FSA_CMD_OPERATION_TYPE::REMOVE: case FSA_CMD_OPERATION_TYPE::GETPOS: case FSA_CMD_OPERATION_TYPE::SETPOS: case FSA_CMD_OPERATION_TYPE::ISEOF: case FSA_CMD_OPERATION_TYPE::GETSTATFILE: case FSA_CMD_OPERATION_TYPE::CLOSEFILE: case FSA_CMD_OPERATION_TYPE::QUERYINFO: case FSA_CMD_OPERATION_TYPE::APPENDFILE: case FSA_CMD_OPERATION_TYPE::TRUNCATEFILE: case FSA_CMD_OPERATION_TYPE::FLUSHQUOTA: { // These are IOCTL and no IOCTLV cemu_assert_error(); break; } default: { cemu_assert_unimplemented(); break; } } IOS_ResourceReply(cmd, (IOS_ERROR)fsaResult); } void FSAHandleCommandIoctl(FSAClient* client, IPCCommandBody* cmd, FSA_CMD_OPERATION_TYPE operationId, void* ptrIn, void* ptrOut) { FSAShimBuffer* shimBuffer = (FSAShimBuffer*)ptrIn; FSA_RESULT fsaResult = FSA_RESULT::FATAL_ERROR; switch (operationId) { case FSA_CMD_OPERATION_TYPE::REMOVE: { fsaResult = FSAProcessCmd_remove(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::CHANGEDIR: { fsaResult = FSAProcessCmd_changeDir(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::GETCWD: { fsaResult = FSAProcessCmd_getCwd(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::MAKEDIR: { fsaResult = FSAProcessCmd_makeDir(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::RENAME: { fsaResult = FSAProcessCmd_rename(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::OPENDIR: { fsaResult = FSAProcessCmd_openDir(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::READDIR: { fsaResult = FSAProcessCmd_readDir(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::CLOSEDIR: { fsaResult = FSAProcessCmd_closeDir(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::OPENFILE: { fsaResult = FSAProcessCmd_openFile(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::GETPOS: { fsaResult = FSAProcessCmd_getPos(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::SETPOS: { fsaResult = FSAProcessCmd_setPos(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::ISEOF: { fsaResult = FSAProcessCmd_isEof(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::GETSTATFILE: { fsaResult = FSAProcessCmd_getStatFile(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::CLOSEFILE: { fsaResult = FSAProcessCmd_closeFile(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::QUERYINFO: { fsaResult = FSAProcessCmd_queryInfo(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::APPENDFILE: { fsaResult = FSAProcessCmd_appendFile(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::TRUNCATEFILE: { fsaResult = FSAProcessCmd_truncateFile(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::FLUSHQUOTA: { fsaResult = FSAProcessCmd_flushQuota(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::REWINDDIR: { fsaResult = FSAProcessCmd_rewindDir(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::FLUSHFILE: { fsaResult = FSAProcessCmd_flushFile(client, shimBuffer); break; } case FSA_CMD_OPERATION_TYPE::READ: case FSA_CMD_OPERATION_TYPE::WRITE: { // These commands are IOCTLVs not IOCTL cemu_assert_error(); } } IOS_ResourceReply(cmd, (IOS_ERROR)fsaResult); } void FSAIoThread() { SetThreadName("IOSU-FSA"); IOSMessage msg; while (true) { IOS_ERROR r = IOS_ReceiveMessage(sFSAIoMsgQueue, &msg, 0); cemu_assert(!IOS_ResultIsError(r)); if (msg == 0) return; // shutdown signaled IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); uint32 clientHandle = (uint32)cmd->devHandle; if (cmd->cmdId == IPCCommandId::IOS_OPEN) { sint32 clientIndex = 0; r = FSAAllocateClient(clientIndex); if (r != IOS_ERROR_OK) { IOS_ResourceReply(cmd, r); continue; } IOS_ResourceReply(cmd, (IOS_ERROR)clientIndex); continue; } else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) { cemu_assert(clientHandle < sFSAClientArray.size()); sFSAClientArray[clientHandle].ReleaseAndCleanup(); IOS_ResourceReply(cmd, IOS_ERROR_OK); continue; } else if (cmd->cmdId == IPCCommandId::IOS_IOCTL) { cemu_assert(clientHandle < sFSAClientArray.size()); cemu_assert(sFSAClientArray[clientHandle].isAllocated); FSAHandleCommandIoctl(sFSAClientArray.data() + clientHandle, cmd, (FSA_CMD_OPERATION_TYPE)cmd->args[0].value(), MEMPTR<void>(cmd->args[1]), MEMPTR<void>(cmd->args[3])); } else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) { cemu_assert(clientHandle < sFSAClientArray.size()); cemu_assert(sFSAClientArray[clientHandle].isAllocated); FSA_CMD_OPERATION_TYPE requestId = (FSA_CMD_OPERATION_TYPE)cmd->args[0].value(); uint32 numIn = cmd->args[1]; uint32 numOut = cmd->args[2]; IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{cmd->args[3]}.GetPtr(); FSAHandleCommandIoctlv(sFSAClientArray.data() + clientHandle, cmd, requestId, numIn, numOut, vec); } else { cemuLog_log(LogType::Force, "/dev/fsa: Unsupported IPC cmdId"); cemu_assert_suspicious(); IOS_ResourceReply(cmd, IOS_ERROR_INVALID); } } } void Initialize() { for (auto& it : sFSAClientArray) it.ReleaseAndCleanup(); sFSAIoMsgQueue = (IOSMsgQueueId)IOS_CreateMessageQueue(_m_sFSAIoMsgQueueMsgBuffer.GetPtr(), _m_sFSAIoMsgQueueMsgBuffer.GetCount()); IOS_ERROR r = IOS_RegisterResourceManager("/dev/fsa", sFSAIoMsgQueue); IOS_DeviceAssociateId("/dev/fsa", 11); cemu_assert(!IOS_ResultIsError(r)); sFSAIoThread = std::thread(FSAIoThread); } void Shutdown() { IOS_SendMessage(sFSAIoMsgQueue, 0, 0); sFSAIoThread.join(); } } // namespace fsa } // namespace iosu ================================================ FILE: src/Cafe/IOSU/fsa/iosu_fsa.h ================================================ #pragma once #include <IOSU/iosu_ipc_common.h> #include "fsa_types.h" namespace iosu { namespace fsa { struct FSARequest { uint32be ukn0; union { uint8 ukn04[0x51C]; struct { MEMPTR<void> dest; uint32be size; uint32be count; uint32be filePos; uint32be fileHandle; uint32be flag; } cmdReadFile; struct { MEMPTR<void> dest; uint32be size; uint32be count; uint32be filePos; uint32be fileHandle; uint32be flag; } cmdWriteFile; struct { uint8 path[FSA_CMD_PATH_MAX_LENGTH]; uint8 mode[12]; // +0x284 note: code seems to access this value like it has a size of 0x10 but the actual struct element is only 12 bytes? Maybe a typo (10 instead of 0x10 in the struct def) uint32be createMode; // +0x290 uint32be openFlags; // +0x294 uint32be preallocSize; // +0x298 } cmdOpenFile; struct { uint32be fileHandle; } cmdCloseFile; struct { uint8 path[FSA_CMD_PATH_MAX_LENGTH]; } cmdRemove; struct { uint8 path[FSA_CMD_PATH_MAX_LENGTH]; uint8 ukn0284[12]; // +0x284 } cmdOpenDir; struct { betype<uint32> dirHandle; } cmdReadDir; struct { betype<uint32> dirHandle; } cmdCloseDir; struct { uint8 path[FSA_CMD_PATH_MAX_LENGTH]; uint32be uknParam; } cmdMakeDir; struct { uint8 path[FSA_CMD_PATH_MAX_LENGTH]; } cmdChangeDir; struct { uint8 query[FSA_CMD_PATH_MAX_LENGTH]; uint32be queryType; } cmdQueryInfo; struct { uint8 srcPath[FSA_CMD_PATH_MAX_LENGTH]; uint8 dstPath[FSA_CMD_PATH_MAX_LENGTH]; } cmdRename; struct { uint32be size; uint32be count; uint32be fileHandle; uint32be uknParam; } cmdAppendFile; struct { uint32be fileHandle; } cmdTruncateFile; struct { uint32be fileHandle; } cmdGetStatFile; struct { uint8 path[FSA_CMD_PATH_MAX_LENGTH]; } cmdFlushQuota; struct { uint32be fileHandle; uint32be filePos; } cmdSetPosFile; struct { uint32be fileHandle; } cmdGetPosFile; struct { uint32be fileHandle; } cmdIsEof; struct { uint32be dirHandle; } cmdRewindDir; struct { uint32be fileHandle; } cmdFlushFile; struct { uint8 path[FSA_CMD_PATH_MAX_LENGTH]; uint32be mode1; uint32be mode2; } cmdChangeMode; }; }; static_assert(sizeof(FSARequest) == 0x520); #pragma pack(1) struct FSAResponse { uint32be ukn0; union { uint8 ukn04[0x28F]; struct { uint32be fileHandleOutput; // +0x584 used to return file handle on success } cmdOpenFile; struct { uint32be dirHandleOutput; // +0x584 used to return dir handle on success } cmdOpenDir; struct { uint32be filePos; } cmdGetPosFile; struct { FSStat_t statOut; } cmdStatFile; struct { FSDirEntry_t dirEntry; } cmdReadDir; struct { char path[FSA_CMD_PATH_MAX_LENGTH]; } cmdGetCWD; struct { union { uint8 ukn04[0x64]; struct { uint64be freespace; } queryFreeSpace; struct { FSStat_t stat; } queryStat; struct { FSADeviceInfo_t info; } queryDeviceInfo; }; } cmdQueryInfo; }; }; static_assert(sizeof(FSAResponse) == 0x293); #pragma pack() struct FSAShimBuffer { FSARequest request; uint8 ukn0520[0x60]; FSAResponse response; uint8 ukn0813[0x6D]; IPCIoctlVector ioctlvVec[3]; uint8 ukn08A4[0x5C]; /* +0x0900 */ uint32be operationType; betype<IOSDevHandle> fsaDevHandle; /* +0x0908 */ uint16be ipcReqType; // 0 -> IoctlAsync, 1 -> IoctlvAsync uint8 ioctlvVecIn; uint8 ioctlvVecOut; uint32 ukn090C; uint32 ukn0910; uint32 ukn0914; uint32 ukn0918; uint32 ukn091C; uint32 ukn0920; uint32 ukn0924; uint32 ukn0928; uint32 ukn092C; uint32 ukn0930; uint32 ukn0934; }; static_assert(sizeof(FSAShimBuffer) == 0x938); // exact size of this is not known void Initialize(); void Shutdown(); } // namespace fsa } // namespace iosu ================================================ FILE: src/Cafe/IOSU/iosu_ipc_common.h ================================================ #pragma once using IOSDevHandle = uint32; enum class IPCDriverState : uint32 { CLOSED = 1, INITIALIZED = 2, READY = 3, SUBMITTING = 4 }; enum class IPCCommandId : uint32 { IOS_OPEN = 1, IOS_CLOSE = 2, IOS_IOCTL = 6, IOS_IOCTLV = 7, }; struct IPCIoctlVector { MEMPTR<void> baseVirt; uint32be size; MEMPTR<void> basePhys; }; static_assert(sizeof(IPCIoctlVector) == 0xC); struct IPCCommandBody { /* +0x00 */ betype<IPCCommandId> cmdId; /* +0x04 */ uint32be result; // set by IOSU /* +0x08 */ betype<IOSDevHandle> devHandle; /* +0x0C */ uint32be ukn0C; /* +0x10 */ uint32be ukn10; /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be ukn18; /* +0x1C */ uint32be ukn1C; /* +0x20 */ uint32be ukn20; /* +0x24 */ uint32be args[5]; // anything below may only be present on the PPC side? /* +0x38 */ betype<IPCCommandId> prev_cmdId; /* +0x3C */ betype<IOSDevHandle> prev_devHandle; /* +0x40 */ MEMPTR<void> ppcVirt0; /* +0x44 */ MEMPTR<void> ppcVirt1; /* IOS_Open: args[0] = const char* path args[1] = pathLen + 1 args[2] = flags IOS_Close: Only devHandle is set IOS_Ioctl: args[0] = requestId args[1] = ptrIn args[2] = sizeIn args[3] = ptrOut args[4] = sizeOut IOS_Ioctlv: args[0] = requestId args[1] = numIn args[2] = numOut args[3] = IPCIoctlVector* */ }; ================================================ FILE: src/Cafe/IOSU/iosu_types_common.h ================================================ #pragma once using IOSMsgQueueId = uint32; using IOSTimerId = uint32; static constexpr IOSTimerId IOSInvalidTimerId = 0xFFFFFFFF; // returned for syscalls // maybe also shared with IPC? enum IOS_ERROR : sint32 { IOS_ERROR_OK = 0, IOS_ERROR_INVALID = -4, IOS_ERROR_MAXIMUM_REACHED = -5, IOS_ERROR_NONE_AVAILABLE = -7, // returned by non-blocking IOS_ReceiveMessage on empty message IOS_ERROR_WOULD_BLOCK = -8, IOS_ERROR_INVALID_ARG = -29, }; inline bool IOS_ResultIsError(const IOS_ERROR err) { return (err & 0x80000000) != 0; } class IOSUModule { public: virtual void SystemLaunch() {}; // CafeSystem is initialized virtual void SystemExit() {}; // CafeSystem is shutdown virtual void TitleStart() {}; // foreground title is launched virtual void TitleStop() {}; // foreground title is closed }; ================================================ FILE: src/Cafe/IOSU/kernel/iosu_kernel.cpp ================================================ #include "iosu_kernel.h" #include "util/helpers/fspinlock.h" #include "util/helpers/helpers.h" #include "Cafe/OS/libs/coreinit/coreinit_IPC.h" #include "util/highresolutiontimer/HighResolutionTimer.h" namespace iosu { namespace kernel { std::mutex sInternalMutex; void IOS_DestroyResourceManagerForQueueId(IOSMsgQueueId msgQueueId); void _IPCDestroyAllHandlesForMsgQueue(IOSMsgQueueId msgQueueId); static void _assume_lock() { #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug(!sInternalMutex.try_lock()); #endif } /* message queue */ struct IOSMessageQueue { // placeholder /* +0x00 */ uint32be ukn00; /* +0x04 */ uint32be ukn04; /* +0x08 */ uint32be numQueuedMessages; /* +0x0C */ uint32be readIndex; /* +0x10 */ uint32be msgArraySize; // 0 if queue is not allocated /* +0x14 */ MEMPTR<betype<IOSMessage>> msgArray; /* +0x18 */ IOSMsgQueueId queueHandle; /* +0x1C */ uint32be ukn1C; uint32 GetWriteIndex() { uint32 idx = readIndex + numQueuedMessages; if (idx >= msgArraySize) idx -= msgArraySize; return idx; } /* HLE extension */ std::condition_variable cv_send; std::condition_variable cv_recv; }; std::array<IOSMessageQueue, 750> sMsgQueuePool; IOS_ERROR _IOS_GetMessageQueue(IOSMsgQueueId queueHandle, IOSMessageQueue*& queueOut) { _assume_lock(); uint32 index = (queueHandle & 0xFFF); if (index >= sMsgQueuePool.size()) return IOS_ERROR_INVALID; IOSMessageQueue& q = sMsgQueuePool.at(index); if(q.queueHandle != queueHandle) return IOS_ERROR_INVALID; queueOut = &q; return IOS_ERROR_OK; } IOSMsgQueueId IOS_CreateMessageQueue(IOSMessage* messageArray, uint32 messageCount) { std::unique_lock _l(sInternalMutex); cemu_assert(messageCount != 0); auto it = std::find_if(sMsgQueuePool.begin(), sMsgQueuePool.end(), [](const IOSMessageQueue& q) { return q.msgArraySize == 0; }); if (it == sMsgQueuePool.end()) { cemu_assert_suspicious(); return IOS_ERROR_MAXIMUM_REACHED; } size_t index = std::distance(sMsgQueuePool.begin(), it); IOSMessageQueue& msgQueue = sMsgQueuePool.at(index); // create queue handle static uint32 sQueueHandleCounter = 0; uint32 queueHandle = (uint32)index | ((sQueueHandleCounter<<12)&0x7FFFFFFF); sQueueHandleCounter++; msgQueue.queueHandle = queueHandle; msgQueue.msgArraySize = messageCount; msgQueue.msgArray = (betype<IOSMessage>*)messageArray; msgQueue.numQueuedMessages = 0; msgQueue.readIndex = 0; return queueHandle; } IOS_ERROR IOS_DestroyMessageQueue(IOSMsgQueueId msgQueueId) { std::unique_lock _l(sInternalMutex); IOSMessageQueue* msgQueue = nullptr; IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue); if (r != IOS_ERROR_OK) return r; msgQueue->msgArraySize = 0; msgQueue->queueHandle = 0; IOS_DestroyResourceManagerForQueueId(msgQueueId); return IOS_ERROR_OK; } IOS_ERROR IOS_SendMessage(IOSMsgQueueId msgQueueId, IOSMessage message, uint32 flags) { std::unique_lock _l(sInternalMutex); cemu_assert_debug(flags == 0 || flags == 1); bool dontBlock = (flags & 1) != 0; IOSMessageQueue* msgQueue = nullptr; IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue); if (r != IOS_ERROR_OK) return r; while (true) { if (msgQueue->numQueuedMessages == msgQueue->msgArraySize) { if (dontBlock) return IOS_ERROR_WOULD_BLOCK; } else break; msgQueue->cv_send.wait(_l); // after returning from wait, make sure the queue handle is unchanged if (msgQueue->queueHandle != msgQueueId) return IOS_ERROR_INVALID; } uint32 writeIndex = msgQueue->GetWriteIndex(); msgQueue->msgArray[writeIndex] = message; msgQueue->numQueuedMessages += 1; msgQueue->cv_recv.notify_one(); return IOS_ERROR_OK; } IOS_ERROR IOS_ReceiveMessage(IOSMsgQueueId msgQueueId, IOSMessage* messageOut, uint32 flags) { std::unique_lock _l(sInternalMutex); cemu_assert_debug(flags == 0 || flags == 1); bool dontBlock = (flags & 1) != 0; IOSMessageQueue* msgQueue = nullptr; IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue); if (r != IOS_ERROR_OK) return r; while (true) { if (msgQueue->numQueuedMessages == 0) { if (dontBlock) return IOS_ERROR_NONE_AVAILABLE; } else break; msgQueue->cv_recv.wait(_l); // after returning from wait, make sure the queue handle is unchanged if (msgQueue->queueHandle != msgQueueId) return IOS_ERROR_INVALID; } *messageOut = msgQueue->msgArray[(uint32)msgQueue->readIndex]; msgQueue->readIndex = msgQueue->readIndex + 1; if (msgQueue->readIndex >= msgQueue->msgArraySize) msgQueue->readIndex -= msgQueue->msgArraySize; msgQueue->numQueuedMessages -= 1; msgQueue->cv_send.notify_one(); return IOS_ERROR_OK; } /* timer */ std::mutex sTimerMutex; std::condition_variable sTimerCV; std::atomic_bool sTimerThreadStop; struct IOSTimer { IOSMsgQueueId queueId; uint32 message; HRTick nextFire; HRTick repeat; bool isValid; }; std::vector<IOSTimer> sTimers; std::vector<IOSTimerId> sTimersFreeHandles; auto sTimerSortComparator = [](const IOSTimerId& idA, const IOSTimerId& idB) { // order by nextFire, then by timerId to avoid duplicate keys IOSTimer& timerA = sTimers[idA]; IOSTimer& timerB = sTimers[idB]; if (timerA.nextFire != timerB.nextFire) return timerA.nextFire < timerB.nextFire; return idA < idB; }; std::set<IOSTimerId, decltype(sTimerSortComparator)> sTimerByFireTime; IOSTimer& IOS_GetFreeTimer() { cemu_assert_debug(!sTimerMutex.try_lock()); // lock must be held by current thread if (sTimersFreeHandles.empty()) return sTimers.emplace_back(); IOSTimerId timerId = sTimersFreeHandles.back(); sTimersFreeHandles.pop_back(); return sTimers[timerId]; } void IOS_TimerSetNextFireTime(IOSTimer& timer, HRTick nextFire) { cemu_assert_debug(!sTimerMutex.try_lock()); // lock must be held by current thread IOSTimerId timerId = &timer - sTimers.data(); auto it = sTimerByFireTime.find(timerId); if(it != sTimerByFireTime.end()) sTimerByFireTime.erase(it); timer.nextFire = nextFire; if(nextFire != 0) sTimerByFireTime.insert(timerId); } void IOS_StopTimerInternal(IOSTimerId timerId) { cemu_assert_debug(!sTimerMutex.try_lock()); IOS_TimerSetNextFireTime(sTimers[timerId], 0); } IOS_ERROR IOS_CreateTimer(uint32 startMicroseconds, uint32 repeatMicroseconds, uint32 queueId, uint32 message) { std::unique_lock _l(sTimerMutex); IOSTimer& timer = IOS_GetFreeTimer(); timer.queueId = queueId; timer.message = message; HRTick nextFire = HighResolutionTimer::now().getTick() + HighResolutionTimer::microsecondsToTicks(startMicroseconds); timer.repeat = HighResolutionTimer::microsecondsToTicks(repeatMicroseconds); IOS_TimerSetNextFireTime(timer, nextFire); timer.isValid = true; sTimerCV.notify_one(); return (IOS_ERROR)(&timer - sTimers.data()); } IOS_ERROR IOS_StopTimer(IOSTimerId timerId) { std::unique_lock _l(sTimerMutex); if (timerId >= sTimers.size() || !sTimers[timerId].isValid) return IOS_ERROR_INVALID; IOS_StopTimerInternal(timerId); return IOS_ERROR_OK; } IOS_ERROR IOS_DestroyTimer(IOSTimerId timerId) { std::unique_lock _l(sTimerMutex); if (timerId >= sTimers.size() || !sTimers[timerId].isValid) return IOS_ERROR_INVALID; IOS_StopTimerInternal(timerId); sTimers[timerId].isValid = false; sTimersFreeHandles.push_back(timerId); return IOS_ERROR_OK; } void IOSTimerThread() { SetThreadName("IOS-Timer"); std::unique_lock _l(sTimerMutex); while (!sTimerThreadStop) { if (sTimerByFireTime.empty()) { sTimerCV.wait_for(_l, std::chrono::milliseconds(10000)); continue; } IOSTimerId timerId = *sTimerByFireTime.begin(); IOSTimer& timer = sTimers[timerId]; HRTick now = HighResolutionTimer::now().getTick(); if (now >= timer.nextFire) { if(timer.repeat == 0) IOS_TimerSetNextFireTime(timer, 0); else IOS_TimerSetNextFireTime(timer, timer.nextFire + timer.repeat); IOSMsgQueueId queueId = timer.queueId; uint32 message = timer.message; // fire timer _l.unlock(); IOSMessage msg; IOS_SendMessage(queueId, message, 1); _l.lock(); continue; } else { sTimerCV.wait_for(_l, std::chrono::microseconds(HighResolutionTimer::ticksToMicroseconds(timer.nextFire - now))); } } } /* devices and IPC */ struct IOSResourceManager { bool isSet{false}; std::string path; IOSMsgQueueId msgQueueId; }; std::array<IOSResourceManager, 512> sDeviceResources; IOSResourceManager* _IOS_FindResourceManager(const char* devicePath) { _assume_lock(); std::string_view devicePathSV{ devicePath }; for (auto& it : sDeviceResources) { if (it.isSet && it.path == devicePathSV) return ⁢ } return nullptr; } IOSResourceManager* _IOS_CreateNewResourceManager(const char* devicePath, IOSMsgQueueId msgQueueId) { _assume_lock(); std::string_view devicePathSV{ devicePath }; for (auto& it : sDeviceResources) { if (!it.isSet) { it.isSet = true; it.path = devicePath; it.msgQueueId = msgQueueId; return ⁢ } } return nullptr; } IOS_ERROR IOS_RegisterResourceManager(const char* devicePath, IOSMsgQueueId msgQueueId) { std::unique_lock _lock(sInternalMutex); if (_IOS_FindResourceManager(devicePath)) { cemu_assert_suspicious(); return IOS_ERROR_INVALID; // correct error code? } // verify if queue is valid IOSMessageQueue* msgQueue; IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue); if (r != IOS_ERROR_OK) return r; // create resource manager IOSResourceManager* resourceMgr = _IOS_CreateNewResourceManager(devicePath, msgQueueId); if (!resourceMgr) return IOS_ERROR_MAXIMUM_REACHED; return IOS_ERROR_OK; } void IOS_DestroyResourceManagerForQueueId(IOSMsgQueueId msgQueueId) { _assume_lock(); // destroy all IPC handles associated with this queue _IPCDestroyAllHandlesForMsgQueue(msgQueueId); // destroy device resource manager for (auto& it : sDeviceResources) { if (it.isSet && it.msgQueueId == msgQueueId) { it.isSet = false; it.path.clear(); it.msgQueueId = 0; } } } IOS_ERROR IOS_DeviceAssociateId(const char* devicePath, uint32 id) { // not yet implemented return IOS_ERROR_OK; } /* IPC */ struct IOSDispatchableCommand { // stores a copy of incoming IPC requests with some extra information required for replies IPCCommandBody body; // our dispatchable copy IPCIoctlVector vecCopy[8]; // our copy of the Ioctlv vector array IPCCommandBody* originalBody; // the original command that was sent to us uint32 ppcCoreIndex; IOSDevHandle replyHandle; // handle for outgoing replies bool isAllocated{false}; }; SysAllocator<IOSDispatchableCommand, 96> sIPCDispatchableCommandPool; std::queue<IOSDispatchableCommand*> sIPCFreeDispatchableCommands; FSpinlock sIPCDispatchableCommandPoolLock; void _IPCInitDispatchablePool() { sIPCDispatchableCommandPoolLock.lock(); while (!sIPCFreeDispatchableCommands.empty()) sIPCFreeDispatchableCommands.pop(); for (size_t i = 0; i < sIPCDispatchableCommandPool.GetCount(); i++) sIPCFreeDispatchableCommands.push(sIPCDispatchableCommandPool.GetPtr()+i); sIPCDispatchableCommandPoolLock.unlock(); } IOSDispatchableCommand* _IPCAllocateDispatchableCommand() { sIPCDispatchableCommandPoolLock.lock(); if (sIPCFreeDispatchableCommands.empty()) { cemuLog_log(LogType::Force, "IOS: Exhausted pool of dispatchable commands"); sIPCDispatchableCommandPoolLock.unlock(); return nullptr; } IOSDispatchableCommand* cmd = sIPCFreeDispatchableCommands.front(); sIPCFreeDispatchableCommands.pop(); cemu_assert_debug(!cmd->isAllocated); cmd->isAllocated = true; sIPCDispatchableCommandPoolLock.unlock(); return cmd; } void _IPCReleaseDispatchableCommand(IOSDispatchableCommand* cmd) { sIPCDispatchableCommandPoolLock.lock(); cemu_assert_debug(cmd->isAllocated); cmd->isAllocated = false; sIPCFreeDispatchableCommands.push(cmd); sIPCDispatchableCommandPoolLock.unlock(); } static constexpr size_t MAX_NUM_ACTIVE_DEV_HANDLES = 96; // per process struct IPCActiveDeviceHandle { bool isSet{false}; uint32 handleCheckValue{0}; std::string path; IOSMsgQueueId msgQueueId; // dispatch target handle (retrieved via IOS_OPEN command to dispatch target) bool hasDispatchTargetHandle{false}; IOSDevHandle dispatchTargetHandle; }; IPCActiveDeviceHandle sActiveDeviceHandles[MAX_NUM_ACTIVE_DEV_HANDLES]; IOS_ERROR _IPCCreateResourceHandle(const char* devicePath, IOSDevHandle& handleOut) { std::unique_lock _lock(sInternalMutex); static uint32 sHandleCreationCounter = 1; // find resource manager for device IOSResourceManager* resMgr = _IOS_FindResourceManager(devicePath); if (!resMgr) { cemuLog_log(LogType::Force, "IOSU-Kernel: IOS_Open() could not open {}", devicePath); return IOS_ERROR_INVALID; } IOSMsgQueueId msgQueueId = resMgr->msgQueueId; _lock.unlock(); // create new handle sint32 deviceHandleIndex = -1; for (size_t i = 0; i < MAX_NUM_ACTIVE_DEV_HANDLES; i++) { if (!sActiveDeviceHandles[i].isSet) { deviceHandleIndex = (sint32)i; break; } } cemu_assert_debug(deviceHandleIndex >= 0); if (deviceHandleIndex < 0) return IOS_ERROR_MAXIMUM_REACHED; // calc handle uint32 devHandle = deviceHandleIndex | ((sHandleCreationCounter << 12) & 0x7FFFFFFF); sHandleCreationCounter++; // init handle instance sActiveDeviceHandles[deviceHandleIndex].isSet = true; sActiveDeviceHandles[deviceHandleIndex].handleCheckValue = devHandle; sActiveDeviceHandles[deviceHandleIndex].path = devicePath; sActiveDeviceHandles[deviceHandleIndex].msgQueueId = msgQueueId; sActiveDeviceHandles[deviceHandleIndex].hasDispatchTargetHandle = false; handleOut = devHandle; return IOS_ERROR_OK; } IOS_ERROR _IPCDestroyResourceHandle(IOSDevHandle devHandle) { std::unique_lock _lock(sInternalMutex); uint32 index = devHandle & 0xFFF; cemu_assert(index < MAX_NUM_ACTIVE_DEV_HANDLES); if (!sActiveDeviceHandles[index].isSet) { cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Resource manager destroyed before all IPC commands were processed"); return IOS_ERROR_INVALID; } if (devHandle != sActiveDeviceHandles[index].handleCheckValue) { cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Mismatching handle"); return IOS_ERROR_INVALID; } sActiveDeviceHandles[index].isSet = false; sActiveDeviceHandles[index].handleCheckValue = 0; sActiveDeviceHandles[index].hasDispatchTargetHandle = false; _lock.unlock(); return IOS_ERROR_OK; } void _IPCDestroyAllHandlesForMsgQueue(IOSMsgQueueId msgQueueId) { _assume_lock(); for (auto& it : sActiveDeviceHandles) { if (it.isSet && it.msgQueueId == msgQueueId) { it.isSet = false; it.path.clear(); it.handleCheckValue = 0; it.hasDispatchTargetHandle = false; it.msgQueueId = 0; } } } IOS_ERROR _IPCAssignDispatchTargetHandle(IOSDevHandle devHandle, IOSDevHandle internalHandle) { std::unique_lock _lock(sInternalMutex); uint32 index = devHandle & 0xFFF; cemu_assert(index < MAX_NUM_ACTIVE_DEV_HANDLES); if (!sActiveDeviceHandles[index].isSet) { cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Resource manager destroyed before all IPC commands were processed"); return IOS_ERROR_INVALID; } if (devHandle != sActiveDeviceHandles[index].handleCheckValue) { cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Mismatching handle"); return IOS_ERROR_INVALID; } cemu_assert_debug(!sActiveDeviceHandles[index].hasDispatchTargetHandle); sActiveDeviceHandles[index].hasDispatchTargetHandle = true; sActiveDeviceHandles[index].dispatchTargetHandle = internalHandle; _lock.unlock(); return IOS_ERROR_OK; } IOS_ERROR _IPCDispatchToResourceManager(IOSDevHandle devHandle, IOSDispatchableCommand* dispatchCmd) { std::unique_lock _lock(sInternalMutex); uint32 index = devHandle & 0xFFF; cemu_assert(index < MAX_NUM_ACTIVE_DEV_HANDLES); if (!sActiveDeviceHandles[index].isSet) { cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Resource manager destroyed before all IPC commands were processed"); return IOS_ERROR_INVALID; } if (devHandle != sActiveDeviceHandles[index].handleCheckValue) { cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Mismatching handle"); return IOS_ERROR_INVALID; } IOSMsgQueueId msgQueueId = sActiveDeviceHandles[index].msgQueueId; if (dispatchCmd->body.cmdId == IPCCommandId::IOS_OPEN) { cemu_assert(!sActiveDeviceHandles[index].hasDispatchTargetHandle); dispatchCmd->body.devHandle = 0; } else { cemu_assert(sActiveDeviceHandles[index].hasDispatchTargetHandle); dispatchCmd->body.devHandle = sActiveDeviceHandles[index].dispatchTargetHandle; } _lock.unlock(); MEMPTR<IOSDispatchableCommand> msgVal{ dispatchCmd }; IOS_ERROR r = IOS_SendMessage(msgQueueId, msgVal.GetMPTR(), 1); if(r != IOS_ERROR_OK) cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): SendMessage returned {}", (sint32)r); return r; } std::mutex sMtxReply[3]; void _IPCReplyAndRelease(IOSDispatchableCommand* dispatchCmd, uint32 result) { cemu_assert(dispatchCmd->ppcCoreIndex < 3); std::unique_lock _l(sMtxReply[(uint32)dispatchCmd->ppcCoreIndex]); cemu_assert(dispatchCmd >= sIPCDispatchableCommandPool.GetPtr() && dispatchCmd < sIPCDispatchableCommandPool.GetPtr() + sIPCDispatchableCommandPool.GetCount()); dispatchCmd->originalBody->result = result; // submit to COS IPCCommandBody* responseArray[1]; responseArray[0] = dispatchCmd->originalBody; coreinit::IPCDriver_NotifyResponses(dispatchCmd->ppcCoreIndex, responseArray, 1); _IPCReleaseDispatchableCommand(dispatchCmd); } IOS_ERROR _IPCHandlerIn_IOS_Open(IOSDispatchableCommand* dispatchCmd) { IPCCommandBody& cmd = dispatchCmd->body; const char* name = MEMPTR<const char>(cmd.args[0]).GetPtr(); uint32 nameLenPlusOne = cmd.args[1]; cemu_assert(nameLenPlusOne > 0); uint32 flags = cmd.args[2]; cemu_assert_debug(flags == 0); std::string devicePath{ name, nameLenPlusOne - 1 }; IOSDevHandle handle; IOS_ERROR r = _IPCCreateResourceHandle(devicePath.c_str(), handle); if (r != IOS_ERROR_OK) return r; dispatchCmd->replyHandle = handle; dispatchCmd->body.devHandle = 0; r = _IPCDispatchToResourceManager(handle, dispatchCmd); return r; } IOS_ERROR _IPCHandlerIn_IOS_Close(IOSDispatchableCommand* dispatchCmd) { IPCCommandBody& cmd = dispatchCmd->body; IOS_ERROR r = _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd); return r; } IOS_ERROR _IPCHandlerIn_IOS_Ioctl(IOSDispatchableCommand* dispatchCmd) { IPCCommandBody& cmd = dispatchCmd->body; IOS_ERROR r = _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd); return r; } IOS_ERROR _IPCHandlerIn_IOS_Ioctlv(IOSDispatchableCommand* dispatchCmd) { IPCCommandBody& cmd = dispatchCmd->body; uint32 requestId = dispatchCmd->body.args[0]; uint32 numIn = dispatchCmd->body.args[1]; uint32 numOut = dispatchCmd->body.args[2]; IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>(cmd.args[3]).GetPtr(); // copy the vector array uint32 numVec = numIn + numOut; if (numVec <= 8) { std::copy(vec, vec + numVec, dispatchCmd->vecCopy); dispatchCmd->body.args[3] = MEMPTR<IPCIoctlVector>(vec).GetMPTR(); } else { // reuse the original vector pointer cemuLog_log(LogType::Force, "Info: Ioctlv command with more than 8 vectors"); } return _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd); } // normally COS kernel handles this, but currently we skip the IPC getting proxied through it IOS_ERROR _IPCHandlerIn_TranslateVectorAddresses(IOSDispatchableCommand* dispatchCmd) { uint32 numIn = dispatchCmd->body.args[1]; uint32 numOut = dispatchCmd->body.args[2]; IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>(dispatchCmd->body.args[3]).GetPtr(); for (uint32 i = 0; i < numIn + numOut; i++) { if (vec[i].baseVirt == nullptr && vec[i].size != 0) { cemuLog_logDebug(LogType::Force, "IPC Ioctlv failed because baseVirt is null but size is not 0"); return IOS_ERROR_INVALID; } // todo - check for valid pointer range vec[i].basePhys = vec[i].baseVirt; } return IOS_ERROR_OK; } // called by COS directly void IPCSubmitFromCOS(uint32 ppcCoreIndex, IPCCommandBody* cmd) { // create a copy of the cmd IOSDispatchableCommand* dispatchCmd = _IPCAllocateDispatchableCommand(); dispatchCmd->body = *cmd; dispatchCmd->originalBody = cmd; dispatchCmd->ppcCoreIndex = ppcCoreIndex; dispatchCmd->replyHandle = cmd->devHandle; // forward command to device IOS_ERROR r = IOS_ERROR_INVALID; switch ((IPCCommandId)cmd->cmdId) { case IPCCommandId::IOS_OPEN: dispatchCmd->replyHandle = 0; r = _IPCHandlerIn_IOS_Open(dispatchCmd); break; case IPCCommandId::IOS_CLOSE: r = _IPCHandlerIn_IOS_Close(dispatchCmd); break; case IPCCommandId::IOS_IOCTL: r = _IPCHandlerIn_IOS_Ioctl(dispatchCmd); break; case IPCCommandId::IOS_IOCTLV: r = _IPCHandlerIn_TranslateVectorAddresses(dispatchCmd); if(r < 0) cemuLog_log(LogType::Force, "Ioctlv error"); else r = _IPCHandlerIn_IOS_Ioctlv(dispatchCmd); break; default: cemuLog_log(LogType::Force, "Invalid IPC command {}", (uint32)(IPCCommandId)cmd->cmdId); break; } if (r < 0) { cemuLog_log(LogType::Force, "Error occurred while trying to dispatch IPC"); _IPCReplyAndRelease(dispatchCmd, r); // in non-error case the device handler will send the result asynchronously via IOS_ResourceReply } } IOS_ERROR IOS_ResourceReply(IPCCommandBody* cmd, IOS_ERROR result) { IOSDispatchableCommand* dispatchCmd = (IOSDispatchableCommand*)cmd; cemu_assert(dispatchCmd >= sIPCDispatchableCommandPool.GetPtr() && dispatchCmd < sIPCDispatchableCommandPool.GetPtr() + sIPCDispatchableCommandPool.GetCount()); cemu_assert_debug(dispatchCmd->isAllocated); dispatchCmd->originalBody->result = result; if (dispatchCmd->originalBody->cmdId == IPCCommandId::IOS_OPEN) { IOSDevHandle devHandle = dispatchCmd->replyHandle; if (IOS_ResultIsError(result)) { cemuLog_log(LogType::Force, "IOS_ResourceReply(): Target device triggered an error on IOS_OPEN"); // dispatch target returned error, destroy our device handle again IOS_ERROR r = _IPCDestroyResourceHandle(devHandle); cemu_assert(r == IOS_ERROR_OK); } else { cemu_assert(_IPCAssignDispatchTargetHandle(devHandle, (IOSDevHandle)result) == IOS_ERROR_OK); result = (IOS_ERROR)(uint32)devHandle; } } else if (dispatchCmd->originalBody->cmdId == IPCCommandId::IOS_CLOSE) { if (IOS_ResultIsError(result)) { cemuLog_log(LogType::Force, "IOS_ResourceReply(): Target device triggered an error on IOS_CLOSE"); } // reply, then destroy handle IOSDevHandle devHandle = dispatchCmd->replyHandle; _IPCReplyAndRelease(dispatchCmd, result); IOS_ERROR r = _IPCDestroyResourceHandle(devHandle); cemu_assert_debug(r == IOS_ERROR::IOS_ERROR_OK); return IOS_ERROR_OK; } _IPCReplyAndRelease(dispatchCmd, result); return IOS_ERROR_OK; } class : public ::IOSUModule { void SystemLaunch() override { _IPCInitDispatchablePool(); // start timer thread sTimerThreadStop = false; m_timerThread = std::thread(IOSTimerThread); } void SystemExit() override { // stop timer thread sTimerThreadStop = true; sTimerCV.notify_one(); m_timerThread.join(); // reset resources // todo } std::thread m_timerThread; }sIOSUModuleKernel; IOSUModule* GetModule() { return static_cast<IOSUModule*>(&sIOSUModuleKernel); } } } ================================================ FILE: src/Cafe/IOSU/kernel/iosu_kernel.h ================================================ #pragma once #include "Cafe/IOSU/iosu_ipc_common.h" #include "Cafe/IOSU/iosu_types_common.h" namespace iosu { namespace kernel { using IOSMessage = uint32; IOSMsgQueueId IOS_CreateMessageQueue(IOSMessage* messageArray, uint32 messageCount); IOS_ERROR IOS_DestroyMessageQueue(IOSMsgQueueId msgQueueId); IOS_ERROR IOS_SendMessage(IOSMsgQueueId msgQueueId, IOSMessage message, uint32 flags); IOS_ERROR IOS_ReceiveMessage(IOSMsgQueueId msgQueueId, IOSMessage* messageOut, uint32 flags); IOS_ERROR IOS_CreateTimer(uint32 startMicroseconds, uint32 repeatMicroseconds, uint32 queueId, uint32 message); IOS_ERROR IOS_StopTimer(IOSTimerId timerId); IOS_ERROR IOS_DestroyTimer(IOSTimerId timerId); IOS_ERROR IOS_RegisterResourceManager(const char* devicePath, IOSMsgQueueId msgQueueId); IOS_ERROR IOS_DeviceAssociateId(const char* devicePath, uint32 id); IOS_ERROR IOS_ResourceReply(IPCCommandBody* cmd, IOS_ERROR result); void IPCSubmitFromCOS(uint32 ppcCoreIndex, IPCCommandBody* cmd); IOSUModule* GetModule(); } } ================================================ FILE: src/Cafe/IOSU/legacy/iosu_acp.cpp ================================================ #include "iosu_ioctl.h" #include "iosu_acp.h" #include "Cafe/OS/libs/nn_common.h" #include "util/tinyxml2/tinyxml2.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/nn_save/nn_save.h" #include "util/helpers/helpers.h" #include "Cafe/OS/libs/nn_acp/nn_acp.h" #include "Cafe/OS/libs/coreinit/coreinit_FS.h" #include "Cafe/Filesystem/fsc.h" //#include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/IOSU/iosu_types_common.h" #include "Cafe/IOSU/nn/iosu_nn_service.h" #include "Cafe/IOSU/legacy/iosu_act.h" #include "Cafe/CafeSystem.h" #include "config/ActiveSettings.h" #include <inttypes.h> using ACPDeviceType = iosu::acp::ACPDeviceType; static_assert(sizeof(acpMetaXml_t) == 0x3440); static_assert(offsetof(acpMetaXml_t, title_id) == 0x0000); static_assert(offsetof(acpMetaXml_t, boss_id) == 0x0008); static_assert(offsetof(acpMetaXml_t, os_version) == 0x0010); static_assert(offsetof(acpMetaXml_t, app_size) == 0x0018); static_assert(offsetof(acpMetaXml_t, common_save_size) == 0x0020); static_assert(offsetof(acpMetaXml_t, version) == 0x0048); static_assert(offsetof(acpMetaXml_t, product_code) == 0x004C); static_assert(offsetof(acpMetaXml_t, logo_type) == 0x00B4); static_assert(offsetof(acpMetaXml_t, pc_cero) == 0x0100); static_assert(offsetof(acpMetaXml_t, longname_ja) == 0x038C); static_assert(offsetof(acpMetaXml_t, shortname_ja) == 0x1B8C); static_assert(offsetof(acpMetaXml_t, publisher_ja) == 0x278C); static_assert(sizeof(acpMetaData_t) == 0x1AB00); static_assert(offsetof(acpMetaData_t, bootMovie) == 0); static_assert(offsetof(acpMetaData_t, bootLogoTex) == 0x13B38); namespace iosu { struct { bool isInitialized; }iosuAcp = { 0 }; void _xml_parseU32(tinyxml2::XMLElement* xmlElement, const char* name, uint32be* v) { tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name); *v = 0; if (subElement == nullptr) return; const char* text = subElement->GetText(); uint32 value; if (sscanf(text, "%u", &value) == 0) return; *v = value; } void _xml_parseHex16(tinyxml2::XMLElement* xmlElement, const char* name, uint16be* v) { tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name); *v = 0; if (subElement == nullptr) return; const char* text = subElement->GetText(); uint32 value; if (sscanf(text, "%x", &value) == 0) return; *v = value; } void _xml_parseHex32(tinyxml2::XMLElement* xmlElement, const char* name, uint32be* v) { tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name); *v = 0; if (subElement == nullptr) return; const char* text = subElement->GetText(); uint32 value; if (sscanf(text, "%x", &value) == 0) return; *v = value; } void _xml_parseHex64(tinyxml2::XMLElement* xmlElement, const char* name, uint64* v) { tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name); *v = 0; if (subElement == nullptr) return; const char* text = subElement->GetText(); uint64 value; if (sscanf(text, "%" SCNx64, &value) == 0) return; *v = _swapEndianU64(value); } void _xml_parseString_(tinyxml2::XMLElement* xmlElement, const char* name, char* output, sint32 maxLength) { tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name); output[0] = '\0'; if (subElement == nullptr) return; const char* text = subElement->GetText(); if (text == nullptr) { output[0] = '\0'; return; } strncpy(output, text, maxLength - 1); output[maxLength - 1] = '\0'; } #define _metaXml_parseString(__xmlElement, __name, __output) _xml_parseString_(__xmlElement, __name, __output, sizeof(__output)); void parseSaveMetaXml(uint8* metaXmlData, sint32 metaXmlLength, acpMetaXml_t* metaXml) { memset(metaXml, 0, sizeof(acpMetaXml_t)); tinyxml2::XMLDocument appXml; appXml.Parse((const char*)metaXmlData, metaXmlLength); uint32 titleVersion = 0xFFFFFFFF; tinyxml2::XMLElement* menuElement = appXml.FirstChildElement("menu"); if (menuElement) { _xml_parseHex64(menuElement, "title_id", &metaXml->title_id); _xml_parseHex64(menuElement, "boss_id", &metaXml->boss_id); _xml_parseHex64(menuElement, "os_version", &metaXml->os_version); _xml_parseHex64(menuElement, "app_size", &metaXml->app_size); _xml_parseHex64(menuElement, "common_save_size", &metaXml->common_save_size); _xml_parseHex64(menuElement, "account_save_size", &metaXml->account_save_size); _xml_parseHex64(menuElement, "common_boss_size", &metaXml->common_boss_size); _xml_parseHex64(menuElement, "account_boss_size", &metaXml->account_boss_size); _xml_parseHex64(menuElement, "join_game_mode_mask", &metaXml->join_game_mode_mask); _xml_parseU32(menuElement, "version", &metaXml->version); _metaXml_parseString(menuElement, "product_code", metaXml->product_code); _metaXml_parseString(menuElement, "content_platform", metaXml->content_platform); _metaXml_parseString(menuElement, "company_code", metaXml->company_code); _metaXml_parseString(menuElement, "mastering_date", metaXml->mastering_date); _xml_parseU32(menuElement, "logo_type", &metaXml->logo_type); _xml_parseU32(menuElement, "app_launch_type", &metaXml->app_launch_type); _xml_parseU32(menuElement, "invisible_flag", &metaXml->invisible_flag); _xml_parseU32(menuElement, "no_managed_flag", &metaXml->no_managed_flag); _xml_parseU32(menuElement, "no_event_log", &metaXml->no_event_log); _xml_parseU32(menuElement, "no_icon_database", &metaXml->no_icon_database); _xml_parseU32(menuElement, "launching_flag", &metaXml->launching_flag); _xml_parseU32(menuElement, "install_flag", &metaXml->install_flag); _xml_parseU32(menuElement, "closing_msg", &metaXml->closing_msg); _xml_parseU32(menuElement, "title_version", &metaXml->title_version); _xml_parseHex32(menuElement, "group_id", &metaXml->group_id); _xml_parseU32(menuElement, "save_no_rollback", &metaXml->save_no_rollback); _xml_parseU32(menuElement, "bg_daemon_enable", &metaXml->bg_daemon_enable); _xml_parseHex32(menuElement, "join_game_id", &metaXml->join_game_id); _xml_parseU32(menuElement, "olv_accesskey", &metaXml->olv_accesskey); _xml_parseU32(menuElement, "wood_tin", &metaXml->wood_tin); _xml_parseU32(menuElement, "e_manual", &metaXml->e_manual); _xml_parseU32(menuElement, "e_manual_version", &metaXml->e_manual_version); _xml_parseHex32(menuElement, "region", &metaXml->region); _xml_parseU32(menuElement, "pc_cero", &metaXml->pc_cero); _xml_parseU32(menuElement, "pc_esrb", &metaXml->pc_esrb); _xml_parseU32(menuElement, "pc_bbfc", &metaXml->pc_bbfc); _xml_parseU32(menuElement, "pc_usk", &metaXml->pc_usk); _xml_parseU32(menuElement, "pc_pegi_gen", &metaXml->pc_pegi_gen); _xml_parseU32(menuElement, "pc_pegi_fin", &metaXml->pc_pegi_fin); _xml_parseU32(menuElement, "pc_pegi_prt", &metaXml->pc_pegi_prt); _xml_parseU32(menuElement, "pc_pegi_bbfc", &metaXml->pc_pegi_bbfc); _xml_parseU32(menuElement, "pc_cob", &metaXml->pc_cob); _xml_parseU32(menuElement, "pc_grb", &metaXml->pc_grb); _xml_parseU32(menuElement, "pc_cgsrr", &metaXml->pc_cgsrr); _xml_parseU32(menuElement, "pc_oflc", &metaXml->pc_oflc); _xml_parseU32(menuElement, "pc_reserved0", &metaXml->pc_reserved0); _xml_parseU32(menuElement, "pc_reserved1", &metaXml->pc_reserved1); _xml_parseU32(menuElement, "pc_reserved2", &metaXml->pc_reserved2); _xml_parseU32(menuElement, "pc_reserved3", &metaXml->pc_reserved3); _xml_parseU32(menuElement, "ext_dev_nunchaku", &metaXml->ext_dev_nunchaku); _xml_parseU32(menuElement, "ext_dev_classic", &metaXml->ext_dev_classic); _xml_parseU32(menuElement, "ext_dev_urcc", &metaXml->ext_dev_urcc); _xml_parseU32(menuElement, "ext_dev_board", &metaXml->ext_dev_board); _xml_parseU32(menuElement, "ext_dev_usb_keyboard", &metaXml->ext_dev_usb_keyboard); _xml_parseU32(menuElement, "ext_dev_etc", &metaXml->ext_dev_etc); _metaXml_parseString(menuElement, "ext_dev_etc_name", metaXml->ext_dev_etc_name); _xml_parseU32(menuElement, "eula_version", &metaXml->eula_version); _xml_parseU32(menuElement, "drc_use", &metaXml->drc_use); _xml_parseU32(menuElement, "network_use", &metaXml->network_use); _xml_parseU32(menuElement, "online_account_use", &metaXml->online_account_use); _xml_parseU32(menuElement, "direct_boot", &metaXml->direct_boot); _xml_parseU32(menuElement, "reserved_flag0", &(metaXml->reserved_flag[0])); _xml_parseU32(menuElement, "reserved_flag1", &(metaXml->reserved_flag[1])); _xml_parseU32(menuElement, "reserved_flag2", &(metaXml->reserved_flag[2])); _xml_parseU32(menuElement, "reserved_flag3", &(metaXml->reserved_flag[3])); _xml_parseU32(menuElement, "reserved_flag4", &(metaXml->reserved_flag[4])); _xml_parseU32(menuElement, "reserved_flag5", &(metaXml->reserved_flag[5])); _xml_parseU32(menuElement, "reserved_flag6", &(metaXml->reserved_flag[6])); _xml_parseU32(menuElement, "reserved_flag7", &(metaXml->reserved_flag[7])); _metaXml_parseString(menuElement, "longname_ja", metaXml->longname_ja); _metaXml_parseString(menuElement, "longname_en", metaXml->longname_en); _metaXml_parseString(menuElement, "longname_fr", metaXml->longname_fr); _metaXml_parseString(menuElement, "longname_de", metaXml->longname_de); _metaXml_parseString(menuElement, "longname_it", metaXml->longname_it); _metaXml_parseString(menuElement, "longname_es", metaXml->longname_es); _metaXml_parseString(menuElement, "longname_zhs", metaXml->longname_zhs); _metaXml_parseString(menuElement, "longname_ko", metaXml->longname_ko); _metaXml_parseString(menuElement, "longname_nl", metaXml->longname_nl); _metaXml_parseString(menuElement, "longname_pt", metaXml->longname_pt); _metaXml_parseString(menuElement, "longname_ru", metaXml->longname_ru); _metaXml_parseString(menuElement, "longname_zht", metaXml->longname_zht); _metaXml_parseString(menuElement, "shortname_ja", metaXml->shortname_ja); _metaXml_parseString(menuElement, "shortname_en", metaXml->shortname_en); _metaXml_parseString(menuElement, "shortname_fr", metaXml->shortname_fr); _metaXml_parseString(menuElement, "shortname_de", metaXml->shortname_de); _metaXml_parseString(menuElement, "shortname_it", metaXml->shortname_it); _metaXml_parseString(menuElement, "shortname_es", metaXml->shortname_es); _metaXml_parseString(menuElement, "shortname_zhs", metaXml->shortname_zhs); _metaXml_parseString(menuElement, "shortname_ko", metaXml->shortname_ko); _metaXml_parseString(menuElement, "shortname_nl", metaXml->shortname_nl); _metaXml_parseString(menuElement, "shortname_pt", metaXml->shortname_pt); _metaXml_parseString(menuElement, "shortname_ru", metaXml->shortname_ru); _metaXml_parseString(menuElement, "shortname_zht", metaXml->shortname_zht); _metaXml_parseString(menuElement, "publisher_ja", metaXml->publisher_ja); _metaXml_parseString(menuElement, "publisher_en", metaXml->publisher_en); _metaXml_parseString(menuElement, "publisher_fr", metaXml->publisher_fr); _metaXml_parseString(menuElement, "publisher_de", metaXml->publisher_de); _metaXml_parseString(menuElement, "publisher_it", metaXml->publisher_it); _metaXml_parseString(menuElement, "publisher_es", metaXml->publisher_es); _metaXml_parseString(menuElement, "publisher_zhs", metaXml->publisher_zhs); _metaXml_parseString(menuElement, "publisher_ko", metaXml->publisher_ko); _metaXml_parseString(menuElement, "publisher_nl", metaXml->publisher_nl); _metaXml_parseString(menuElement, "publisher_pt", metaXml->publisher_pt); _metaXml_parseString(menuElement, "publisher_ru", metaXml->publisher_ru); _metaXml_parseString(menuElement, "publisher_zht", metaXml->publisher_zht); for (sint32 i = 0; i < 32; i++) { char tempStr[256]; sprintf(tempStr, "add_on_unique_id%d", i); _xml_parseU32(menuElement, tempStr, &(metaXml->add_on_unique_id[i])); } } } bool _is8DigitHex(const char* str) { if (strlen(str) != 8) return false; for (sint32 f = 0; f < 8; f++) { if (str[f] >= '0' && str[f] <= '9') continue; if (str[f] >= 'a' && str[f] <= 'f') continue; if (str[f] >= 'A' && str[f] <= 'F') continue; return false; } return true; } sint32 ACPGetSaveDataTitleIdList(uint32 storageDeviceGuessed, acpTitleId_t* titleIdList, sint32 maxCount, uint32be* countOut) { sint32 count = 0; const char* devicePath = "/vol/storage_mlc01/"; if (storageDeviceGuessed != 3) cemu_assert_unimplemented(); char searchPath[FSA_CMD_PATH_MAX_LENGTH]; sprintf(searchPath, "%susr/save/", devicePath); sint32 fscStatus = 0; FSCVirtualFile* fscDirIteratorTitleIdHigh = fsc_openDirIterator(searchPath, &fscStatus); FSCDirEntry dirEntryTitleIdHigh; FSCDirEntry dirEntryTitleIdLow; if(fscDirIteratorTitleIdHigh) { while (fsc_nextDir(fscDirIteratorTitleIdHigh, &dirEntryTitleIdHigh)) { // is 8-digit hex? if(_is8DigitHex(dirEntryTitleIdHigh.path) == false) continue; uint32 titleIdHigh; sscanf(dirEntryTitleIdHigh.path, "%x", &titleIdHigh); sprintf(searchPath, "%susr/save/%08x/", devicePath, titleIdHigh); FSCVirtualFile* fscDirIteratorTitleIdLow = fsc_openDirIterator(searchPath, &fscStatus); if (fscDirIteratorTitleIdLow) { while (fsc_nextDir(fscDirIteratorTitleIdLow, &dirEntryTitleIdLow)) { // is 8-digit hex? if (_is8DigitHex(dirEntryTitleIdLow.path) == false) continue; uint32 titleIdLow; sscanf(dirEntryTitleIdLow.path, "%x", &titleIdLow); // check if /meta/meta.xml exists char tempPath[FSA_CMD_PATH_MAX_LENGTH]; sprintf(tempPath, "%susr/save/%08x/%08x/meta/meta.xml", devicePath, titleIdHigh, titleIdLow); if (fsc_doesFileExist(tempPath)) { if (count < maxCount) { titleIdList[count].titleIdHigh = titleIdHigh; titleIdList[count].titleIdLow = titleIdLow; count++; } } else { cemuLog_logDebug(LogType::Force, "ACPGetSaveDataTitleIdList(): Missing meta.xml for save {:08x}-{:08x}", titleIdHigh, titleIdLow); } } fsc_close(fscDirIteratorTitleIdLow); } } fsc_close(fscDirIteratorTitleIdHigh); } *countOut = count; return 0; } sint32 ACPGetTitleSaveMetaXml(uint64 titleId, acpMetaXml_t* acpMetaXml, sint32 uknType) { // uknType is probably the storage device? if (uknType != 3) // mlc01 ? assert_dbg(); char xmlPath[FSA_CMD_PATH_MAX_LENGTH]; sprintf(xmlPath, "%susr/save/%08x/%08x/meta/meta.xml", "/vol/storage_mlc01/", (uint32)(titleId>>32), (uint32)(titleId&0xFFFFFFFF)); uint32 saveMetaXmlSize = 0; uint8* saveMetaXmlData = fsc_extractFile(xmlPath, &saveMetaXmlSize); if (saveMetaXmlData) { parseSaveMetaXml(saveMetaXmlData, saveMetaXmlSize, acpMetaXml); free(saveMetaXmlData); } else { cemuLog_log(LogType::Force, "ACPGetTitleSaveMetaXml(): Meta file \"{}\" does not exist", xmlPath); memset(acpMetaXml, 0, sizeof(acpMetaXml_t)); } return 0; } sint32 ACPGetTitleMetaData(uint64 titleId, acpMetaData_t* acpMetaData) { memset(acpMetaData, 0, sizeof(acpMetaData_t)); char titlePath[1024]; if (((titleId >> 32) & 0x10) != 0) { sprintf(titlePath, "/vol/storage_mlc01/sys/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); } else { sprintf(titlePath, "/vol/storage_mlc01/usr/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); } char filePath[FSA_CMD_PATH_MAX_LENGTH]; sprintf(filePath, "%smeta/bootMovie.h264", titlePath); // bootMovie.h264 uint32 metaBootMovieSize = 0; uint8* metaBootMovieData = fsc_extractFile(filePath, &metaBootMovieSize); if (metaBootMovieData) { memcpy(acpMetaData->bootMovie, metaBootMovieData, std::min<uint32>(metaBootMovieSize, sizeof(acpMetaData->bootMovie))); free(metaBootMovieData); } else cemuLog_log(LogType::Force, "ACPGetTitleMetaData(): Unable to load \"{}\"", filePath); // bootLogoTex.tga sprintf(filePath, "%smeta/bootLogoTex.tga", titlePath); uint32 metaBootLogoSize = 0; uint8* metaBootLogoData = fsc_extractFile(filePath, &metaBootLogoSize); if (metaBootLogoData) { memcpy(acpMetaData->bootLogoTex, metaBootLogoData, std::min<uint32>(metaBootLogoSize, sizeof(acpMetaData->bootLogoTex))); free(metaBootLogoData); } else cemuLog_log(LogType::Force, "ACPGetTitleMetaData(): Unable to load \"{}\"", filePath); return 0; } sint32 ACPGetTitleMetaXml(uint64 titleId, acpMetaXml_t* acpMetaXml) { memset(acpMetaXml, 0, sizeof(acpMetaXml_t)); char titlePath[1024]; if (((titleId >> 32) & 0x10) != 0) { sprintf(titlePath, "/vol/storage_mlc01/sys/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); } else { sprintf(titlePath, "/vol/storage_mlc01/usr/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); } char filePath[FSA_CMD_PATH_MAX_LENGTH]; sprintf(filePath, "%smeta/meta.xml", titlePath); uint32 metaXmlSize = 0; uint8* metaXmlData = fsc_extractFile(filePath, &metaXmlSize); if (metaXmlData) { parseSaveMetaXml(metaXmlData, metaXmlSize, acpMetaXml); free(metaXmlData); } else { cemuLog_log(LogType::Force, "ACPGetTitleMetaXml(): Meta file \"{}\" does not exist", filePath); } return 0; } sint32 ACPGetTitleSaveDirEx(uint64 titleId, uint32 storageDeviceGuessed, acpSaveDirInfo_t* saveDirInfo, sint32 maxCount, uint32be* countOut) { sint32 count = 0; const char* devicePath = "/vol/storage_mlc01/"; if (storageDeviceGuessed != 3) cemu_assert_unimplemented(); char searchPath[FSA_CMD_PATH_MAX_LENGTH]; char tempPath[FSA_CMD_PATH_MAX_LENGTH]; sint32 fscStatus = 0; // add common dir sprintf(searchPath, "%susr/save/%08x/%08x/user/common/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); if (fsc_doesDirectoryExist(searchPath)) { acpSaveDirInfo_t* entry = saveDirInfo + count; if (count < maxCount) { // get dir size sprintf(tempPath, "%susr/save/%08x/%08x/user/common/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); FSCVirtualFile* fscDir = fsc_open(tempPath, FSC_ACCESS_FLAG::OPEN_DIR, &fscStatus); uint64 dirSize = 0; if (fscDir) { dirSize = fsc_getFileSize(fscDir); fsc_close(fscDir); } memset(entry, 0, sizeof(acpSaveDirInfo_t)); entry->ukn00 = (uint32)(titleId>>32); entry->ukn04 = (uint32)(titleId&0xFFFFFFFF); entry->persistentId = 0; // 0 -> common save entry->ukn0C = 0; entry->sizeA = _swapEndianU64(0); // ukn entry->sizeB = _swapEndianU64(dirSize); entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK)); sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); count++; } } // add user directories sprintf(searchPath, "%susr/save/%08x/%08x/user/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); FSCVirtualFile* fscDirIterator = fsc_openDirIterator(searchPath, &fscStatus); if (fscDirIterator == nullptr) { cemuLog_log(LogType::Force, "ACPGetTitleSaveDirEx(): Failed to iterate directories in \"{}\"", searchPath); *countOut = 0; } else { FSCDirEntry dirEntry; while( fsc_nextDir(fscDirIterator, &dirEntry) ) { if(dirEntry.isDirectory == false) continue; // is 8-digit hex name? (persistent id) if(_is8DigitHex(dirEntry.path) == false ) continue; uint32 persistentId = 0; sscanf(dirEntry.path, "%x", &persistentId); acpSaveDirInfo_t* entry = saveDirInfo + count; if (count < maxCount) { memset(entry, 0, sizeof(acpSaveDirInfo_t)); entry->ukn00 = (uint32)(titleId >> 32); // titleId? entry->ukn04 = (uint32)(titleId & 0xFFFFFFFF); // titleId? entry->persistentId = persistentId; // 0 -> common save entry->ukn0C = 0; entry->sizeA = _swapEndianU64(0); entry->sizeB = _swapEndianU64(0); entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK)); sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); count++; } } fsc_close(fscDirIterator); } *countOut = count; return 0; } int iosuAcp_thread() { SetThreadName("iosuAcp_thread"); while (true) { uint32 returnValue = 0; // Ioctl return value ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_ACP_MAIN); if (ioQueueEntry->request == IOSU_ACP_REQUEST_CEMU) { iosuAcpCemuRequest_t* acpCemuRequest = (iosuAcpCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); if (acpCemuRequest->requestCode == IOSU_ACP_GET_SAVE_DATA_TITLE_ID_LIST) { uint32be count = 0; acpCemuRequest->returnCode = ACPGetSaveDataTitleIdList(acpCemuRequest->type, (acpTitleId_t*)acpCemuRequest->ptr.GetPtr(), acpCemuRequest->maxCount, &count); acpCemuRequest->resultU32.u32 = count; } else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_SAVE_META_XML) { acpCemuRequest->returnCode = ACPGetTitleSaveMetaXml(acpCemuRequest->titleId, (acpMetaXml_t*)acpCemuRequest->ptr.GetPtr(), acpCemuRequest->type); } else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_SAVE_DIR) { uint32be count = 0; acpCemuRequest->returnCode = ACPGetTitleSaveDirEx(acpCemuRequest->titleId, acpCemuRequest->type, (acpSaveDirInfo_t*)acpCemuRequest->ptr.GetPtr(), acpCemuRequest->maxCount, &count); acpCemuRequest->resultU32.u32 = count; } else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_META_DATA) { acpCemuRequest->returnCode = ACPGetTitleMetaData(acpCemuRequest->titleId, (acpMetaData_t*)acpCemuRequest->ptr.GetPtr()); } else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_META_XML) { acpCemuRequest->returnCode = ACPGetTitleMetaXml(acpCemuRequest->titleId, (acpMetaXml_t*)acpCemuRequest->ptr.GetPtr()); } else if (acpCemuRequest->requestCode == IOSU_ACP_CREATE_SAVE_DIR_EX) { acpCemuRequest->returnCode = acp::ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId); } else cemu_assert_unimplemented(); } else cemu_assert_unimplemented(); iosuIoctl_completeRequest(ioQueueEntry, returnValue); } return 0; } void iosuAcp_init() { if (iosuAcp.isInitialized) return; std::thread t(iosuAcp_thread); t.detach(); iosuAcp.isInitialized = true; } bool iosuAcp_isInitialized() { return iosuAcp.isInitialized; } /* Above is the legacy implementation. Below is the new style implementation which also matches the official IPC protocol and works with the real nn_acp.rpl */ namespace acp { uint64 _ACPGetTimestamp() { return coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK; } nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) { if (deviceType == ACPDeviceType::UnknownType) { return (nnResult)0xA030FB80; } // create or modify the saveinfo const auto saveinfoPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); auto saveinfoData = FileStream::LoadIntoMemory(saveinfoPath); if (saveinfoData && !saveinfoData->empty()) { namespace xml = tinyxml2; xml::XMLDocument doc; tinyxml2::XMLError xmlError = doc.Parse((const char*)saveinfoData->data(), saveinfoData->size()); if (xmlError == xml::XML_SUCCESS || xmlError == xml::XML_ERROR_EMPTY_DOCUMENT) { xml::XMLNode* child = doc.FirstChild(); // check for declaration -> <?xml version="1.0" encoding="utf-8"?> if (!child || !child->ToDeclaration()) { xml::XMLDeclaration* decl = doc.NewDeclaration(); doc.InsertFirstChild(decl); } xml::XMLElement* info = doc.FirstChildElement("info"); if (!info) { info = doc.NewElement("info"); doc.InsertEndChild(info); } // find node with persistentId char tmp[64]; sprintf(tmp, "%08x", persistentId); bool foundNode = false; for (xml::XMLElement* account = info->FirstChildElement("account"); account; account = account->NextSiblingElement("account")) { if (account->Attribute("persistentId", tmp)) { // found the entry! -> update timestamp xml::XMLElement* timestamp = account->FirstChildElement("timestamp"); sprintf(tmp, "%" PRIx64, _ACPGetTimestamp()); if (timestamp) timestamp->SetText(tmp); else { timestamp = doc.NewElement("timestamp"); account->InsertFirstChild(timestamp); } foundNode = true; break; } } if (!foundNode) { tinyxml2::XMLElement* account = doc.NewElement("account"); { sprintf(tmp, "%08x", persistentId); account->SetAttribute("persistentId", tmp); tinyxml2::XMLElement* timestamp = doc.NewElement("timestamp"); { sprintf(tmp, "%" PRIx64, _ACPGetTimestamp()); timestamp->SetText(tmp); } account->InsertFirstChild(timestamp); } info->InsertFirstChild(account); } // update file tinyxml2::XMLPrinter printer; doc.Print(&printer); FileStream* fs = FileStream::createFile2(saveinfoPath); if (fs) { fs->writeString(printer.CStr()); delete fs; } } } return NN_RESULT_SUCCESS; } void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId) { std::string titlePath = CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()); sint32 fscStatus; FSCVirtualFile* fscFile = fsc_open((titlePath + "/meta/meta.xml").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); if (fscFile) { sint32 fileSize = fsc_getFileSize(fscFile); std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); fsc_readFile(fscFile, fileContent.get(), fileSize); fsc_close(fscFile); const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/meta.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); std::ofstream myFile(outPath, std::ios::out | std::ios::binary); myFile.write((char*)fileContent.get(), fileSize); myFile.close(); } fscFile = fsc_open((titlePath + "/meta/iconTex.tga").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); if (fscFile) { sint32 fileSize = fsc_getFileSize(fscFile); std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); fsc_readFile(fscFile, fileContent.get(), fileSize); fsc_close(fscFile); const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/iconTex.tga", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); std::ofstream myFile(outPath, std::ios::out | std::ios::binary); myFile.write((char*)fileContent.get(), fileSize); myFile.close(); } ACPUpdateSaveTimeStamp(persistentId, titleId, iosu::acp::ACPDeviceType::InternalDeviceType); } sint32 _ACPCreateSaveDir(uint32 persistentId, uint64 titleId, ACPDeviceType type) { uint32 high = GetTitleIdHigh(titleId) & (~0xC); uint32 low = GetTitleIdLow(titleId); sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; char path[256]; sprintf(path, "%susr/boss/", "/vol/storage_mlc01/"); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); fsc_createDir(path, &fscStatus); // copy xml meta files CreateSaveMetaFiles(persistentId, titleId); return 0; } nnResult ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type) { uint64 titleId = CafeSystem::GetForegroundTitleId(); return _ACPCreateSaveDir(persistentId, titleId, type); } sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId) { uint32 persistentId = 0; cemu_assert_debug(accountSlot >= 1 && accountSlot <= 13); // outside valid slot range? bool r = iosu::act::GetPersistentId(accountSlot, &persistentId); cemu_assert_debug(r); return _ACPCreateSaveDir(persistentId, titleId, ACPDeviceType::InternalDeviceType); } nnResult ACPGetOlvAccesskey(uint32be* accessKey) { *accessKey = CafeSystem::GetForegroundTitleOlvAccesskey(); return 0; } class AcpMainService : public iosu::nn::IPCService { public: AcpMainService() : iosu::nn::IPCService("/dev/acp_main") {} nnResult ServiceCall(IPCServiceCall& serviceCall) override { cemuLog_log(LogType::Force, "Unsupported service call to /dev/acp_main"); cemu_assert_unimplemented(); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); } }; AcpMainService gACPMainService; class : public ::IOSUModule { void TitleStart() override { gACPMainService.Start(); // gACPMainService.SetTimerUpdate(1000); // call TimerUpdate() once a second } void TitleStop() override { gACPMainService.Stop(); } }sIOSUModuleNNACP; IOSUModule* GetModule() { return static_cast<IOSUModule*>(&sIOSUModuleNNACP); } } // namespace acp } // namespace iosu ================================================ FILE: src/Cafe/IOSU/legacy/iosu_acp.h ================================================ #pragma once #include "Cafe/IOSU/iosu_types_common.h" #include "Cafe/OS/libs/nn_common.h" // for nnResult typedef struct { /* +0x0000 */ uint64 title_id; // parsed via GetHex64 /* +0x0008 */ uint64 boss_id; // parsed via GetHex64 /* +0x0010 */ uint64 os_version; // parsed via GetHex64 /* +0x0018 */ uint64 app_size; // parsed via GetHex64 /* +0x0020 */ uint64 common_save_size; // parsed via GetHex64 /* +0x0028 */ uint64 account_save_size; // parsed via GetHex64 /* +0x0030 */ uint64 common_boss_size; // parsed via GetHex64 /* +0x0038 */ uint64 account_boss_size; // parsed via GetHex64 /* +0x0040 */ uint64 join_game_mode_mask; // parsed via GetHex64 /* +0x0048 */ uint32be version; /* +0x004C */ char product_code[0x20]; /* +0x006C */ char content_platform[0x20]; /* +0x008C */ char company_code[8]; /* +0x0094 */ char mastering_date[0x20]; /* +0x00B4 */ uint32be logo_type; /* +0x00B8 */ uint32be app_launch_type; /* +0x00BC */ uint32be invisible_flag; /* +0x00C0 */ uint32be no_managed_flag; /* +0x00C4 */ uint32be no_event_log; /* +0x00C8 */ uint32be no_icon_database; /* +0x00CC */ uint32be launching_flag; /* +0x00D0 */ uint32be install_flag; /* +0x00D4 */ uint32be closing_msg; /* +0x00D8 */ uint32be title_version; /* +0x00DC */ uint32be group_id; // Hex32 /* +0x00E0 */ uint32be save_no_rollback; /* +0x00E4 */ uint32be bg_daemon_enable; /* +0x00E8 */ uint32be join_game_id; // Hex32 /* +0x00EC */ uint32be olv_accesskey; /* +0x00F0 */ uint32be wood_tin; /* +0x00F4 */ uint32be e_manual; /* +0x00F8 */ uint32be e_manual_version; /* +0x00FC */ uint32be region; // Hex32 /* +0x0100 */ uint32be pc_cero; /* +0x0104 */ uint32be pc_esrb; /* +0x0108 */ uint32be pc_bbfc; /* +0x010C */ uint32be pc_usk; /* +0x0110 */ uint32be pc_pegi_gen; /* +0x0114 */ uint32be pc_pegi_fin; /* +0x0118 */ uint32be pc_pegi_prt; /* +0x011C */ uint32be pc_pegi_bbfc; /* +0x0120 */ uint32be pc_cob; /* +0x0124 */ uint32be pc_grb; /* +0x0128 */ uint32be pc_cgsrr; /* +0x012C */ uint32be pc_oflc; /* +0x0130 */ uint32be pc_reserved0; /* +0x0134 */ uint32be pc_reserved1; /* +0x0138 */ uint32be pc_reserved2; /* +0x013C */ uint32be pc_reserved3; /* +0x0140 */ uint32be ext_dev_nunchaku; /* +0x0144 */ uint32be ext_dev_classic; /* +0x0148 */ uint32be ext_dev_urcc; /* +0x014C */ uint32be ext_dev_board; /* +0x0150 */ uint32be ext_dev_usb_keyboard; /* +0x0154 */ uint32be ext_dev_etc; /* +0x0158 */ char ext_dev_etc_name[0x200]; /* +0x0358 */ uint32be eula_version; /* +0x035C */ uint32be drc_use; /* +0x0360 */ uint32be network_use; /* +0x0364 */ uint32be online_account_use; /* +0x0368 */ uint32be direct_boot; /* +0x036C */ uint32be reserved_flag[8]; // array of U32, reserved_flag%d /* +0x038C */ char longname_ja[0x200]; /* +0x058C */ char longname_en[0x200]; /* +0x078C */ char longname_fr[0x200]; /* +0x098C */ char longname_de[0x200]; /* +0x0B8C */ char longname_it[0x200]; /* +0x0D8C */ char longname_es[0x200]; /* +0x0F8C */ char longname_zhs[0x200]; /* +0x118C */ char longname_ko[0x200]; /* +0x138C */ char longname_nl[0x200]; /* +0x158C */ char longname_pt[0x200]; /* +0x178C */ char longname_ru[0x200]; /* +0x198C */ char longname_zht[0x200]; /* +0x1B8C */ char shortname_ja[0x100]; /* +0x1C8C */ char shortname_en[0x100]; /* +0x1D8C */ char shortname_fr[0x100]; /* +0x1E8C */ char shortname_de[0x100]; /* +0x1F8C */ char shortname_it[0x100]; /* +0x208C */ char shortname_es[0x100]; /* +0x218C */ char shortname_zhs[0x100]; /* +0x228C */ char shortname_ko[0x100]; /* +0x238C */ char shortname_nl[0x100]; /* +0x248C */ char shortname_pt[0x100]; /* +0x258C */ char shortname_ru[0x100]; /* +0x268C */ char shortname_zht[0x100]; /* +0x278C */ char publisher_ja[0x100]; /* +0x288C */ char publisher_en[0x100]; /* +0x298C */ char publisher_fr[0x100]; /* +0x2A8C */ char publisher_de[0x100]; /* +0x2B8C */ char publisher_it[0x100]; /* +0x2C8C */ char publisher_es[0x100]; /* +0x2D8C */ char publisher_zhs[0x100]; /* +0x2E8C */ char publisher_ko[0x100]; /* +0x2F8C */ char publisher_nl[0x100]; /* +0x308C */ char publisher_pt[0x100]; /* +0x318C */ char publisher_ru[0x100]; /* +0x328C */ char publisher_zht[0x100]; /* +0x338C */ uint32be add_on_unique_id[0x20]; // Hex32, add_on_unique_id%d /* +0x340C */ uint8 padding[0x3440 - 0x340C]; // guessed // struct size is 0x3440 }acpMetaXml_t; typedef struct { /* +0x06BDC | +0x00000 */ uint8 bootMovie[0x13B38]; /* +0x1A714 | +0x13B38 */ uint8 bootLogoTex[0x6FBC]; /* +0x216D0 | +0x1AAF4 */ uint8 ukn1AAF4[4]; /* +0x216D4 | +0x1AAF8 */ uint8 ukn1AAF8[4]; /* +0x216D8 | +0x1AAFC */ uint8 ukn1AAFC[4]; }acpMetaData_t; typedef struct { uint32be titleIdHigh; uint32be titleIdLow; }acpTitleId_t; typedef struct { // for ACPGetTitleSaveDirEx uint32be ukn00; uint32be ukn04; uint32be persistentId; uint32be ukn0C; //uint32be ukn10; // ukn10/ukn14 are part of size //uint32be ukn14; //uint32be ukn18; // ukn18/ukn1C are part of size //uint32be ukn1C; uint64 sizeA; uint64 sizeB; // path starts at 0x20, length unknown? char path[0x40]; // %susr/save/%08x/%08x/meta/ // /vol/storage_mlc01/ /* +0x60 */ uint64 time; /* +0x68 */ uint8 padding[0x80 - 0x68]; // size is 0x80, but actual content size is only 0x60 and padded to 0x80? }acpSaveDirInfo_t; // custom dev/acp_main protocol (Cemu only) #define IOSU_ACP_REQUEST_CEMU (0xEE) typedef struct { uint32 requestCode; // input uint8 accountSlot; //uint32 unique; //uint64 titleId; //uint32 titleVersion; //uint32 serverId; uint64 titleId; sint32 type; MEMPTR<void> ptr; sint32 maxCount; // output uint32 returnCode; // ACP return value union { //struct //{ // uint64 u64; //}resultU64; struct { uint32 u32; }resultU32; //struct //{ // char strBuffer[1024]; //}resultString; //struct //{ // uint8 binBuffer[1024]; //}resultBinary; }; }iosuAcpCemuRequest_t; // ACP request Cemu subcodes #define IOSU_ACP_GET_SAVE_DATA_TITLE_ID_LIST 0x01 #define IOSU_ACP_GET_TITLE_SAVE_META_XML 0x02 #define IOSU_ACP_GET_TITLE_SAVE_DIR 0x03 #define IOSU_ACP_GET_TITLE_META_DATA 0x04 #define IOSU_ACP_GET_TITLE_META_XML 0x05 #define IOSU_ACP_CREATE_SAVE_DIR_EX 0x06 namespace iosu { void iosuAcp_init(); namespace acp { enum ACPDeviceType { UnknownType = 0, InternalDeviceType = 1, USBDeviceType = 3, }; class IOSUModule* GetModule(); void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId); nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType); nnResult ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type); sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId); nnResult ACPGetOlvAccesskey(uint32be* accessKey); } } ================================================ FILE: src/Cafe/IOSU/legacy/iosu_act.cpp ================================================ #include "iosu_act.h" #include "iosu_ioctl.h" #include "Cafe/OS/libs/nn_common.h" #include <algorithm> #include <mutex> #include "openssl/evp.h" /* EVP_Digest */ #include "openssl/sha.h" /* SHA256_DIGEST_LENGTH */ #include "Cafe/Account/Account.h" #include "config/ActiveSettings.h" #include "util/helpers/helpers.h" #include "Cemu/napi/napi.h" #include "Cemu/ncrypto/ncrypto.h" #include "Cafe/IOSU/kernel/iosu_kernel.h" #include "Cafe/IOSU/nn/iosu_nn_service.h" using namespace iosu::kernel; using NexToken = NAPI::ACTNexToken; static_assert(sizeof(NexToken) == 0x25C); struct { bool isInitialized; std::mutex actMutex; }iosuAct = { }; // account manager struct actAccountData_t { bool isValid; // options bool isNetworkAccount; bool hasParseError; // set if any occurs while parsing account.dat // IDs uint8 uuid[16]; uint32 persistentId; uint64 transferableIdBase; uint32 simpleAddressId; uint32 principalId; // NNID char accountId[64]; uint8 accountPasswordCache[32]; // country & language uint32 countryIndex; char country[8]; char timeZoneId[16]; sint64 utcOffset; // Mii FFLData_t miiData; uint16le miiNickname[ACT_NICKNAME_LENGTH]; bool IsNetworkAccount() const { return isNetworkAccount; // todo - IOSU only checks if accountId is not empty? } }; #define IOSU_ACT_ACCOUNT_MAX_COUNT (0xC) actAccountData_t _actAccountData[IOSU_ACT_ACCOUNT_MAX_COUNT] = {}; bool _actAccountDataInitialized = false; void FillAccountData(const Account& account, const bool online_enabled, int index) { cemu_assert_debug(index < IOSU_ACT_ACCOUNT_MAX_COUNT); auto& data = _actAccountData[index]; data.isValid = true; // options data.isNetworkAccount = account.IsValidOnlineAccount(); data.hasParseError = false; // IDs std::copy(account.GetUuid().cbegin(), account.GetUuid().cend(), data.uuid); data.persistentId = account.GetPersistentId(); data.transferableIdBase = account.GetTransferableIdBase(); data.simpleAddressId = account.GetSimpleAddressId(); data.principalId = account.GetPrincipalId(); // NNID std::copy(account.GetAccountId().begin(), account.GetAccountId().end(), data.accountId); std::copy(account.GetAccountPasswordCache().begin(), account.GetAccountPasswordCache().end(), data.accountPasswordCache); // country & language data.countryIndex = account.GetCountry(); strcpy(data.country, NCrypto::GetCountryAsString(data.countryIndex)); std::copy(account.GetTimeZoneId().cbegin(), account.GetTimeZoneId().cend(), data.timeZoneId); data.utcOffset = account.GetUtcOffset() / 1'000'000; // Mii std::copy(account.GetMiiData().begin(), account.GetMiiData().end(), (uint8*)&data.miiData); std::copy(account.GetMiiName().begin(), account.GetMiiName().end(), data.miiNickname); // if online mode is disabled, make all accounts offline if(!online_enabled) { data.isNetworkAccount = false; data.principalId = 0; data.simpleAddressId = 0; memset(data.accountId, 0x00, sizeof(data.accountId)); } } void iosuAct_loadAccounts() { if (_actAccountDataInitialized) return; const bool online_enabled = ActiveSettings::IsOnlineEnabled(); const auto persistent_id = ActiveSettings::GetPersistentId(); // first account is always our selected one int counter = 0; const auto& first_acc = Account::GetAccount(persistent_id); FillAccountData(first_acc, online_enabled, counter); ++counter; // enable multiple accounts for cafe functions (badly tested) //for (const auto& account : Account::GetAccounts()) //{ // if (first_acc.GetPersistentId() != account.GetPersistentId()) // { // FillAccountData(account, online_enabled, counter); // ++counter; // } //} cemuLog_log(LogType::Force, "IOSU_ACT: using account {} in first slot", boost::nowide::narrow(first_acc.GetMiiName())); _actAccountDataInitialized = true; } bool iosuAct_isAccountDataLoaded() { return _actAccountDataInitialized; } uint32 iosuAct_acquirePrincipalIdByAccountId(const char* nnid, uint32* pid) { NAPI::AuthInfo authInfo; NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); NAPI::ACTConvertNnidToPrincipalIdResult result = NAPI::ACT_ACTConvertNnidToPrincipalId(authInfo, nnid); if (result.isValid() && result.isFound) { *pid = result.principalId; } else { *pid = 0; return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, 0); // what error should we return? The friend list app expects nn_act.AcquirePrincipalIdByAccountId to never return an error } return 0; } sint32 iosuAct_getAccountIndexBySlot(uint8 slot) { if (slot == iosu::act::ACT_SLOT_CURRENT) return 0; if (slot == 0xFF) return 0; // ? cemu_assert_debug(slot != 0); cemu_assert_debug(slot <= IOSU_ACT_ACCOUNT_MAX_COUNT); return slot - 1; } uint32 iosuAct_getAccountIdOfCurrentAccount() { cemu_assert_debug(_actAccountData[0].isValid); return _actAccountData[0].persistentId; } // IOSU act API interface static const auto ACTResult_Ok = 0; static const auto ACTResult_InvalidValue = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12F00); // 0xC0712F00 static const auto ACTResult_OutOfRange = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12D80); // 0xC0712D80 static const auto ACTResult_AccountDoesNotExist = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST); // 0xA071F480 static const auto ACTResult_NotANetworkAccount = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, 0x1FE80); // 0xA071FE80 nnResult ServerActErrorCodeToNNResult(NAPI::ACT_ERROR_CODE ec) { switch (ec) { case (NAPI::ACT_ERROR_CODE)1: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2401); case (NAPI::ACT_ERROR_CODE)2: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2402); case (NAPI::ACT_ERROR_CODE)3: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2403); case (NAPI::ACT_ERROR_CODE)4: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2458); case (NAPI::ACT_ERROR_CODE)5: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2642); case (NAPI::ACT_ERROR_CODE)6: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2641); case (NAPI::ACT_ERROR_CODE)7: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2522); case (NAPI::ACT_ERROR_CODE)8: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2534); case (NAPI::ACT_ERROR_CODE)9: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2404); case (NAPI::ACT_ERROR_CODE)10: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2451); case (NAPI::ACT_ERROR_CODE)11: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2511); case (NAPI::ACT_ERROR_CODE)12: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2812); case (NAPI::ACT_ERROR_CODE)100: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2571); case (NAPI::ACT_ERROR_CODE)101: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2572); case (NAPI::ACT_ERROR_CODE)103: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2575); case (NAPI::ACT_ERROR_CODE)104: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2452); case (NAPI::ACT_ERROR_CODE)105: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2592); case (NAPI::ACT_ERROR_CODE)106: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2611); case (NAPI::ACT_ERROR_CODE)107: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2502); case (NAPI::ACT_ERROR_CODE)108: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2802); case (NAPI::ACT_ERROR_CODE)109: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2503); case (NAPI::ACT_ERROR_CODE)110: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2501); case (NAPI::ACT_ERROR_CODE)111: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2632); case (NAPI::ACT_ERROR_CODE)112: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2631); case (NAPI::ACT_ERROR_CODE)113: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2452); case (NAPI::ACT_ERROR_CODE)114: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2593); case (NAPI::ACT_ERROR_CODE)115: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2591); case (NAPI::ACT_ERROR_CODE)116: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2614); case (NAPI::ACT_ERROR_CODE)117: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2651); case (NAPI::ACT_ERROR_CODE)118: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2484); case (NAPI::ACT_ERROR_CODE)119: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2803); case (NAPI::ACT_ERROR_CODE)120: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2813); case (NAPI::ACT_ERROR_CODE)121: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2804); case (NAPI::ACT_ERROR_CODE)122: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2814); case (NAPI::ACT_ERROR_CODE)123: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2882); case (NAPI::ACT_ERROR_CODE)124: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2512); case (NAPI::ACT_ERROR_CODE)125: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2485); case (NAPI::ACT_ERROR_CODE)126: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2805); case (NAPI::ACT_ERROR_CODE)127: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2815); case (NAPI::ACT_ERROR_CODE)128: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2661); case (NAPI::ACT_ERROR_CODE)129: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2615); case (NAPI::ACT_ERROR_CODE)130: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2531); case (NAPI::ACT_ERROR_CODE)131: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2616); case (NAPI::ACT_ERROR_CODE)132: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2822); case (NAPI::ACT_ERROR_CODE)133: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2832); case (NAPI::ACT_ERROR_CODE)134: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2823); case (NAPI::ACT_ERROR_CODE)135: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2833); case (NAPI::ACT_ERROR_CODE)136: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2824); case (NAPI::ACT_ERROR_CODE)137: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2834); case (NAPI::ACT_ERROR_CODE)138: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2825); case (NAPI::ACT_ERROR_CODE)139: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2835); case (NAPI::ACT_ERROR_CODE)142: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2635); case (NAPI::ACT_ERROR_CODE)143: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2634); case (NAPI::ACT_ERROR_CODE)1004: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2503); case (NAPI::ACT_ERROR_CODE)1006: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2471); case (NAPI::ACT_ERROR_CODE)1016: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2532); case (NAPI::ACT_ERROR_CODE)1017: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2483); case (NAPI::ACT_ERROR_CODE)1018: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2533); case (NAPI::ACT_ERROR_CODE)1019: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2481); case (NAPI::ACT_ERROR_CODE)1020: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2473); case NAPI::ACT_ERROR_CODE::ACT_GAME_SERVER_NOT_FOUND: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2482); case (NAPI::ACT_ERROR_CODE)1022: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2472); case (NAPI::ACT_ERROR_CODE)1023: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2612); case (NAPI::ACT_ERROR_CODE)1024: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2535); case (NAPI::ACT_ERROR_CODE)1025: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2536); case (NAPI::ACT_ERROR_CODE)1031: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2537); case (NAPI::ACT_ERROR_CODE)1032: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2636); case (NAPI::ACT_ERROR_CODE)1033: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2662); case (NAPI::ACT_ERROR_CODE)1035: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2671); case (NAPI::ACT_ERROR_CODE)1036: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2679); case (NAPI::ACT_ERROR_CODE)1037: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2672); case (NAPI::ACT_ERROR_CODE)1038: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2674); case (NAPI::ACT_ERROR_CODE)1039: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2680); case (NAPI::ACT_ERROR_CODE)1040: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2675); case (NAPI::ACT_ERROR_CODE)1041: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2673); case (NAPI::ACT_ERROR_CODE)1042: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2676); case (NAPI::ACT_ERROR_CODE)1043: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2681); case (NAPI::ACT_ERROR_CODE)1044: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2678); case (NAPI::ACT_ERROR_CODE)1045: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2677); case (NAPI::ACT_ERROR_CODE)1046: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2596); case (NAPI::ACT_ERROR_CODE)1100: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2541); case (NAPI::ACT_ERROR_CODE)1101: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2542); case (NAPI::ACT_ERROR_CODE)1103: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2594); case (NAPI::ACT_ERROR_CODE)1104: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2576); case (NAPI::ACT_ERROR_CODE)1105: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2613); case (NAPI::ACT_ERROR_CODE)1106: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2633); case (NAPI::ACT_ERROR_CODE)1107: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2577); case (NAPI::ACT_ERROR_CODE)1111: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2538); case (NAPI::ACT_ERROR_CODE)1115: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2597); case (NAPI::ACT_ERROR_CODE)1125: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2585); case (NAPI::ACT_ERROR_CODE)1126: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2586); case (NAPI::ACT_ERROR_CODE)1134: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2587); case (NAPI::ACT_ERROR_CODE)1200: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2884); case (NAPI::ACT_ERROR_CODE)2001: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2931); case (NAPI::ACT_ERROR_CODE)2002: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2881); case (NAPI::ACT_ERROR_CODE)2999: return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2883); default: break; } cemuLog_log(LogType::Force, "Received unknown ACT error code {}", (uint32)ec); return nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR); } namespace iosu { namespace act { uint8 getCurrentAccountSlot() { return 1; } actAccountData_t* GetAccountBySlotNo(uint8 slotNo) { // only call this while holding actMutex uint8 accIndex; if(slotNo == iosu::act::ACT_SLOT_CURRENT) { accIndex = getCurrentAccountSlot() - 1; cemu_assert_debug(accIndex >= 0 && accIndex < IOSU_ACT_ACCOUNT_MAX_COUNT); } else if(slotNo > 0 && slotNo <= IOSU_ACT_ACCOUNT_MAX_COUNT) accIndex = slotNo - 1; else { return nullptr; } if(!_actAccountData[accIndex].isValid) return nullptr; return &_actAccountData[accIndex]; } // has ownership of account data // while any thread has a LockedAccount in non-null state no other thread can access the account data class LockedAccount { public: LockedAccount(uint8 slotNo) { iosuAct.actMutex.lock(); m_account = GetAccountBySlotNo(slotNo); if(!m_account) iosuAct.actMutex.unlock(); } ~LockedAccount() { if(m_account) iosuAct.actMutex.unlock(); } void Release() { if(m_account) iosuAct.actMutex.unlock(); m_account = nullptr; } actAccountData_t* operator->() { return m_account; } actAccountData_t& operator*() { return *m_account; } LockedAccount(const LockedAccount&) = delete; LockedAccount& operator=(const LockedAccount&) = delete; operator bool() const { return m_account != nullptr; } private: actAccountData_t* m_account{nullptr}; }; bool getPrincipalId(uint8 slot, uint32* principalId) { sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); if (_actAccountData[accountIndex].isValid == false) { *principalId = 0; return false; } *principalId = _actAccountData[accountIndex].principalId; return true; } bool getAccountId(uint8 slot, char* accountId) { sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); if (_actAccountData[accountIndex].isValid == false) { *accountId = '\0'; return false; } strcpy(accountId, _actAccountData[accountIndex].accountId); return true; } // returns empty string if invalid std::string getAccountId2(uint8 slot) { sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); if (_actAccountData[accountIndex].isValid == false) return {}; return {_actAccountData[accountIndex].accountId}; } bool getMii(uint8 slot, FFLData_t* fflData) { sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); if (_actAccountData[accountIndex].isValid == false) { return false; } memcpy(fflData, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t)); return true; } // return screenname in little-endian wide characters bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]) { sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); if (_actAccountData[accountIndex].isValid == false) { screenname[0] = '\0'; return false; } for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++) { screenname[i] = (uint16)_actAccountData[accountIndex].miiNickname[i]; } return true; } bool getCountryIndex(uint8 slot, uint32* countryIndex) { sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); if (_actAccountData[accountIndex].isValid == false) { *countryIndex = 0; return false; } *countryIndex = _actAccountData[accountIndex].countryIndex; return true; } bool GetPersistentId(uint8 slot, uint32* persistentId) { sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); if(!_actAccountData[accountIndex].isValid) { *persistentId = 0; return false; } *persistentId = _actAccountData[accountIndex].persistentId; return true; } nnResult AcquireNexToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, uint32 serverId, uint8* tokenOut, uint32 tokenLen) { if (accountSlot != ACT_SLOT_CURRENT) return ACTResult_InvalidValue; LockedAccount account(accountSlot); if (!account) return ACTResult_AccountDoesNotExist; if (!account->IsNetworkAccount()) return ACTResult_NotANetworkAccount; cemu_assert_debug(ActiveSettings::IsOnlineEnabled()); if (tokenLen != sizeof(NexToken)) return ACTResult_OutOfRange; NAPI::AuthInfo authInfo; NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, titleId, titleVersion, serverId); if (nexTokenResult.isValid()) { memcpy(tokenOut, &nexTokenResult.nexToken, sizeof(NexToken)); return ACTResult_Ok; } else if (nexTokenResult.apiError == NAPI_RESULT::SERVICE_ERROR) { nnResult returnCode = ServerActErrorCodeToNNResult(nexTokenResult.serviceError); cemu_assert_debug((returnCode&0x80000000) != 0); return returnCode; } return nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR); } nnResult AcquireIndependentServiceToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, std::string_view clientId, uint8* tokenOut, uint32 tokenLen) { static constexpr size_t IndependentTokenMaxLength = 512+1; // 512 bytes + null terminator if(accountSlot != ACT_SLOT_CURRENT) return ACTResult_InvalidValue; LockedAccount account(accountSlot); if (!account) return ACTResult_AccountDoesNotExist; if (!account->IsNetworkAccount()) return ACTResult_NotANetworkAccount; cemu_assert_debug(ActiveSettings::IsOnlineEnabled()); if (tokenLen < IndependentTokenMaxLength) return ACTResult_OutOfRange; NAPI::AuthInfo authInfo; NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); account.Release(); NAPI::ACTGetIndependentTokenResult tokenResult = NAPI::ACT_GetIndependentToken_WithCache(authInfo, titleId, titleVersion, clientId); uint32 returnCode = 0; if (tokenResult.isValid()) { for (size_t i = 0; i < std::min(tokenResult.token.size(), (size_t)IndependentTokenMaxLength); i++) { tokenOut[i] = tokenResult.token[i]; tokenOut[i + 1] = '\0'; } returnCode = 0; } else { returnCode = 0x80000000; // todo - proper error codes } return returnCode; } class ActService : public iosu::nn::IPCService { public: ActService() : iosu::nn::IPCService("/dev/act") {} nnResult ServiceCall(IPCServiceCall& serviceCall) override { cemuLog_log(LogType::Force, "Unsupported service call to /dev/act"); cemu_assert_unimplemented(); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACT, 0); } }; ActService gActService; void Initialize() { gActService.Start(); } void Stop() { gActService.Stop(); } } } // IOSU act IO typedef struct { /* +0x00 */ uint32be ukn00; /* +0x04 */ uint32be ukn04; /* +0x08 */ uint32be ukn08; /* +0x0C */ uint32be subcommandCode; /* +0x10 */ uint8 ukn10; /* +0x11 */ uint8 ukn11; /* +0x12 */ uint8 ukn12; /* +0x13 */ uint8 accountSlot; /* +0x14 */ uint32be unique; // is this command specific? }cmdActRequest00_t; typedef struct { uint32be returnCode; uint8 transferableIdBase[8]; }cmdActGetTransferableIDResult_t; #define ACT_SUBCMD_GET_TRANSFERABLE_ID 4 #define ACT_SUBCMD_INITIALIZE 0x14 #define _cancelIfAccountDoesNotExist() \ if (_actAccountData[accountIndex].isValid == false) \ { \ /* account does not exist*/ \ ioctlReturnValue = 0; \ actCemuRequest->setACTReturnCode(BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST)); /* 0xA071F480 */ \ actCemuRequest->resultU64.u64 = 0; \ iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); \ continue; \ } int iosuAct_thread() { SetThreadName("iosuAct_thread"); while (true) { uint32 ioctlReturnValue = 0; ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_ACT); if (ioQueueEntry->request == 0) { if (ioQueueEntry->countIn != 1 || ioQueueEntry->countOut != 1) { assert_dbg(); } ioBufferVector_t* vectorsDebug = ioQueueEntry->bufferVectors.GetPtr(); void* outputBuffer = ioQueueEntry->bufferVectors[0].buffer.GetPtr(); cmdActRequest00_t* requestCmd = (cmdActRequest00_t*)ioQueueEntry->bufferVectors[0].unknownBuffer.GetPtr(); if (requestCmd->subcommandCode == ACT_SUBCMD_INITIALIZE) { // do nothing for now (there is no result?) } else if (requestCmd->subcommandCode == ACT_SUBCMD_GET_TRANSFERABLE_ID) { cmdActGetTransferableIDResult_t* cmdResult = (cmdActGetTransferableIDResult_t*)outputBuffer; cmdResult->returnCode = 0; *(uint64*)cmdResult->transferableIdBase = _swapEndianU64(0x1122334455667788); } else assert_dbg(); } else if (ioQueueEntry->request == IOSU_ACT_REQUEST_CEMU) { iosuActCemuRequest_t* actCemuRequest = (iosuActCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); sint32 accountIndex; ioctlReturnValue = 0; if (actCemuRequest->requestCode == IOSU_ARC_ACCOUNT_ID) { accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); _cancelIfAccountDoesNotExist(); strcpy(actCemuRequest->resultString.strBuffer, _actAccountData[accountIndex].accountId); actCemuRequest->setACTReturnCode(0); } else if (actCemuRequest->requestCode == IOSU_ARC_UUID) { accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); if (actCemuRequest->accountSlot == 0xFF) { // common uuid (placeholder algorithm) for (uint32 i = 0; i < 16; i++) actCemuRequest->resultBinary.binBuffer[i] = i * 0x74 + i + ~i + i * 133; } else { _cancelIfAccountDoesNotExist(); memcpy(actCemuRequest->resultBinary.binBuffer, _actAccountData[accountIndex].uuid, 16); } cemu_assert_debug(actCemuRequest->uuidName != -1); // todo if (actCemuRequest->uuidName != -1 && actCemuRequest->uuidName != -2) { // generate name based UUID // format: // first 10 bytes of UUID + 6 bytes of a hash // hash algorithm: // sha256 of // 4 bytes uuidName (big-endian) // 4 bytes 0x3A275E09 (big-endian) // 6 bytes from the end of UUID // bytes 10-15 are used from the hash and replace the last 6 bytes of the UUID EVP_MD_CTX *ctx_sha256 = EVP_MD_CTX_new(); EVP_DigestInit(ctx_sha256, EVP_sha256()); uint32 name = (uint32)actCemuRequest->uuidName; uint8 tempArray[] = { static_cast<uint8>((name >> 24) & 0xFF), static_cast<uint8>((name >> 16) & 0xFF), static_cast<uint8>((name >> 8) & 0xFF), static_cast<uint8>((name >> 0) & 0xFF), 0x3A, 0x27, 0x5E, 0x09, }; EVP_DigestUpdate(ctx_sha256, tempArray, sizeof(tempArray)); EVP_DigestUpdate(ctx_sha256, actCemuRequest->resultBinary.binBuffer+10, 6); uint8 h[SHA256_DIGEST_LENGTH]; EVP_DigestFinal_ex(ctx_sha256, h, NULL); EVP_MD_CTX_free(ctx_sha256); memcpy(actCemuRequest->resultBinary.binBuffer + 0xA, h + 0xA, 6); } else if (actCemuRequest->uuidName == -2) { // return account uuid } else { cemuLog_logDebug(LogType::Force, "Gen UUID unknown mode {}", actCemuRequest->uuidName); } actCemuRequest->setACTReturnCode(0); } else if (actCemuRequest->requestCode == IOSU_ARC_SIMPLEADDRESS) { accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); _cancelIfAccountDoesNotExist(); actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].simpleAddressId; actCemuRequest->setACTReturnCode(0); } else if (actCemuRequest->requestCode == IOSU_ARC_PRINCIPALID) { accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); _cancelIfAccountDoesNotExist(); actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].principalId; actCemuRequest->setACTReturnCode(0); } else if (actCemuRequest->requestCode == IOSU_ARC_TRANSFERABLEID) { accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); _cancelIfAccountDoesNotExist(); actCemuRequest->resultU64.u64 = _actAccountData[accountIndex].transferableIdBase; // todo - transferable also contains a unique id actCemuRequest->setACTReturnCode(0); } else if (actCemuRequest->requestCode == IOSU_ARC_PERSISTENTID) { if(actCemuRequest->accountSlot != 0) { accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); _cancelIfAccountDoesNotExist(); actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].persistentId; actCemuRequest->setACTReturnCode(0); } else { // F1 Race Stars calls IsSlotOccupied and indirectly GetPersistentId on slot 0 which is not valid actCemuRequest->resultU32.u32 = 0; actCemuRequest->setACTReturnCode(0); } } else if (actCemuRequest->requestCode == IOSU_ARC_COUNTRY) { accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); _cancelIfAccountDoesNotExist(); strcpy(actCemuRequest->resultString.strBuffer, _actAccountData[accountIndex].country); actCemuRequest->setACTReturnCode(0); } else if (actCemuRequest->requestCode == IOSU_ARC_TIMEZONEID) { accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); _cancelIfAccountDoesNotExist(); strcpy(actCemuRequest->resultString.strBuffer, _actAccountData[accountIndex].timeZoneId); actCemuRequest->setACTReturnCode(0); } else if (actCemuRequest->requestCode == IOSU_ARC_ISNETWORKACCOUNT) { accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); _cancelIfAccountDoesNotExist(); actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].isNetworkAccount; actCemuRequest->setACTReturnCode(0); } else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIRENEXTOKEN) { nnResult r = iosu::act::AcquireNexToken(actCemuRequest->accountSlot, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->serverId, actCemuRequest->resultBinary.binBuffer, sizeof(NexToken)); actCemuRequest->setACTReturnCode(r); } else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREINDEPENDENTTOKEN) { nnResult r = iosu::act::AcquireIndependentServiceToken(actCemuRequest->accountSlot, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->clientId, actCemuRequest->resultBinary.binBuffer, sizeof(actCemuRequest->resultBinary.binBuffer)); actCemuRequest->setACTReturnCode(r); } else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREPIDBYNNID) { uint32 returnCode = iosuAct_acquirePrincipalIdByAccountId(actCemuRequest->clientId, &actCemuRequest->resultU32.u32); actCemuRequest->setACTReturnCode(returnCode); } else if (actCemuRequest->requestCode == IOSU_ARC_MIIDATA) { accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); _cancelIfAccountDoesNotExist(); memcpy(actCemuRequest->resultBinary.binBuffer, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t)); actCemuRequest->setACTReturnCode(0); } else if (actCemuRequest->requestCode == IOSU_ARC_INIT) { iosuAct_loadAccounts(); actCemuRequest->setACTReturnCode(0); } else assert_dbg(); } else { assert_dbg(); } iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); } return 0; } void iosuAct_init_depr() { if (iosuAct.isInitialized) return; std::thread t(iosuAct_thread); t.detach(); iosuAct.isInitialized = true; } bool iosuAct_isInitialized() { return iosuAct.isInitialized; } uint16 FFLCalculateCRC16(uint8* input, sint32 length) { uint16 crc = 0; for (sint32 c = 0; c < length; c++) { for (sint32 f = 0; f < 8; f++) { if ((crc & 0x8000) != 0) { uint16 t = crc << 1; crc = t ^ 0x1021; } else { crc <<= 1; } } crc ^= (uint16)input[c]; } return crc; } ================================================ FILE: src/Cafe/IOSU/legacy/iosu_act.h ================================================ #pragma once void iosuAct_init_depr(); bool iosuAct_isInitialized(); #define ACT_ACCOUNTID_LENGTH (17) // includes '\0' // Mii #define MII_FFL_STORAGE_SIZE (96) #define MII_FFL_NAME_LENGTH (10) // counted in wchar_t elements (16-bit unicode) #define ACT_NICKNAME_LENGTH (10) // aka Mii nickname #define ACT_NICKNAME_SIZE (11) typedef struct { uint32be high; uint32be low; }FFLDataID_t; typedef struct { /* +0x00 */ uint32 uknFlags; /* +0x04 */ FFLDataID_t miiId; // bytes 8 and 9 are part of the CRC? (miiId is based on account transferable id?) /* +0x0C */ uint8 ukn0C[0xA]; /* +0x16 */ uint8 ukn16[2]; /* +0x18 */ uint16 ukn18; /* +0x1A */ uint16le miiName[MII_FFL_NAME_LENGTH]; /* +0x2E */ uint16 ukn2E; /* +0x30 */ uint8 ukn30[MII_FFL_STORAGE_SIZE-0x30]; }FFLData_t; static_assert(sizeof(FFLData_t) == MII_FFL_STORAGE_SIZE, "FFLData_t size invalid"); static_assert(offsetof(FFLData_t, miiId) == 0x04, "FFLData->miiId offset invalid"); static_assert(offsetof(FFLData_t, miiName) == 0x1A, "FFLData->miiName offset invalid"); static_assert(offsetof(FFLData_t, ukn2E) == 0x2E, "FFLData->ukn2E offset invalid"); static uint16 FFLCalculateCRC16(uint8* input, sint32 length); namespace iosu { namespace act { uint8 getCurrentAccountSlot(); bool getPrincipalId(uint8 slot, uint32* principalId); bool getAccountId(uint8 slot, char* accountId); bool getMii(uint8 slot, FFLData_t* fflData); bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]); bool getCountryIndex(uint8 slot, uint32* countryIndex); bool GetPersistentId(uint8 slot, uint32* persistentId); std::string getAccountId2(uint8 slot); static constexpr uint8 ACT_SLOT_CURRENT = 0xFE; void Initialize(); void Stop(); } } // custom dev/act/ protocol (Cemu only) #define IOSU_ACT_REQUEST_CEMU (0xEE) struct iosuActCemuRequest_t { uint32 requestCode; // input uint8 accountSlot; uint32 unique; sint32 uuidName; uint64 titleId; uint32 titleVersion; uint32 serverId; char clientId[64]; uint32 expiresIn; // output uint32 returnCode; union { struct { uint64 u64; }resultU64; struct { uint32 u32; }resultU32; struct { char strBuffer[1024]; }resultString; struct { uint8 binBuffer[1024]; }resultBinary; }; void setACTReturnCode(uint32 code) { returnCode = code; } }; // Act Request Cemu subcodes #define IOSU_ARC_INIT 0x00 #define IOSU_ARC_ACCOUNT_ID 0x01 #define IOSU_ARC_TRANSFERABLEID 0x02 #define IOSU_ARC_PERSISTENTID 0x03 #define IOSU_ARC_UUID 0x04 #define IOSU_ARC_SIMPLEADDRESS 0x05 #define IOSU_ARC_PRINCIPALID 0x06 #define IOSU_ARC_COUNTRY 0x07 #define IOSU_ARC_ISNETWORKACCOUNT 0x08 #define IOSU_ARC_ACQUIRENEXTOKEN 0x09 #define IOSU_ARC_MIIDATA 0x0A #define IOSU_ARC_ACQUIREINDEPENDENTTOKEN 0x0B #define IOSU_ARC_ACQUIREPIDBYNNID 0x0C #define IOSU_ARC_TIMEZONEID 0x0D uint32 iosuAct_getAccountIdOfCurrentAccount(); bool iosuAct_isAccountDataLoaded(); ================================================ FILE: src/Cafe/IOSU/legacy/iosu_crypto.cpp ================================================ #include "iosu_crypto.h" #include "config/ActiveSettings.h" #include "openssl/bn.h" #include "openssl/obj_mac.h" #include "openssl/ec.h" #include "openssl/x509.h" #include "openssl/ssl.h" #include "openssl/ecdsa.h" #include "util/crypto/aes128.h" #include "Common/FileStream.h" uint8 otpMem[1024]; bool hasOtpMem = false; uint8 seepromMem[512]; bool hasSeepromMem = false; struct { bool hasCertificates; struct { bool isValid; sint32 id; X509* cert; std::vector<uint8> certData; RSA* pkey; std::vector<uint8> pkeyDERData; }certList[256]; sint32 certListCount; }iosuCryptoCertificates = { 0 }; CertECC_t g_wiiuDeviceCert; void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size) { memcpy(output, otpMem + wordIndex * 4, size); } void iosuCrypto_readOtpData(uint32be& output, sint32 wordIndex) { memcpy(&output, otpMem + wordIndex * 4, sizeof(uint32be)); } void iosuCrypto_readSeepromData(void* output, sint32 wordIndex, sint32 size) { memcpy(output, seepromMem + wordIndex * 2, size); } bool iosuCrypto_getDeviceId(uint32* deviceId) { uint32be deviceIdBE; *deviceId = 0; if (!hasOtpMem) return false; iosuCrypto_readOtpData(&deviceIdBE, 0x87, sizeof(uint32)); *deviceId = (uint32)deviceIdBE; return true; } void iosuCrypto_getDeviceSerialString(char* serialString) { char serialStringPart0[32]; // code char serialStringPart1[32]; // serial if (hasSeepromMem == false) { strcpy(serialString, "FEH000000000"); return; } memset(serialStringPart0, 0, sizeof(serialStringPart0)); memset(serialStringPart1, 0, sizeof(serialStringPart1)); iosuCrypto_readSeepromData(serialStringPart0, 0xAC, 8); iosuCrypto_readSeepromData(serialStringPart1, 0xB0, 0x10); sprintf(serialString, "%s%s", serialStringPart0, serialStringPart1); } static const char* base64_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; int iosuCrypto_base64Encode(unsigned char const* bytes_to_encode, unsigned int inputLen, char* output) { int i = 0; int j = 0; unsigned char charArray_3[3]; unsigned char charArray_4[4]; sint32 outputLength = 0; while (inputLen--) { charArray_3[i++] = *(bytes_to_encode++); if (i == 3) { charArray_4[0] = (charArray_3[0] & 0xfc) >> 2; charArray_4[1] = ((charArray_3[0] & 0x03) << 4) + ((charArray_3[1] & 0xf0) >> 4); charArray_4[2] = ((charArray_3[1] & 0x0f) << 2) + ((charArray_3[2] & 0xc0) >> 6); charArray_4[3] = charArray_3[2] & 0x3f; for (i = 0; (i < 4); i++) { output[outputLength] = base64_charset[charArray_4[i]]; outputLength++; } i = 0; } } if (i) { for (j = i; j < 3; j++) charArray_3[j] = '\0'; charArray_4[0] = (charArray_3[0] & 0xfc) >> 2; charArray_4[1] = ((charArray_3[0] & 0x03) << 4) + ((charArray_3[1] & 0xf0) >> 4); charArray_4[2] = ((charArray_3[1] & 0x0f) << 2) + ((charArray_3[2] & 0xc0) >> 6); charArray_4[3] = charArray_3[2] & 0x3f; for (j = 0; j < (i + 1); j++) { output[outputLength] = base64_charset[charArray_4[j]]; outputLength++; } while (i++ < 3) { output[outputLength] = '='; outputLength++; } } return outputLength; } std::string iosuCrypto_base64Encode(unsigned char const* bytes_to_encode, unsigned int inputLen) { int encodedLen = inputLen / 3 * 4 + 16; std::string strB64; strB64.resize(encodedLen); int outputLen = iosuCrypto_base64Encode(bytes_to_encode, inputLen, strB64.data()); cemu_assert_debug(outputLen < strB64.size()); strB64.resize(outputLen); return strB64; } static_assert(sizeof(CertECC_t) == sizeof(CertECC_t)); EC_KEY* ECCPubKey_getPublicKey(ECCPubKey& pubKey) // verified and works { BIGNUM* bn_r = BN_new(); BIGNUM* bn_s = BN_new(); BN_bin2bn(pubKey.keyData + 0, 30, bn_r); BN_bin2bn(pubKey.keyData + 30, 30, bn_s); EC_KEY* ec_pubKey = EC_KEY_new_by_curve_name(NID_sect233r1); int r = EC_KEY_set_public_key_affine_coordinates(ec_pubKey, bn_r, bn_s); BN_free(bn_r); BN_free(bn_s); return ec_pubKey; } ECDSA_SIG* ECCPubKey_getSignature(CertECC_t& cert) { BIGNUM* bn_r = BN_new(); BIGNUM* bn_s = BN_new(); BN_bin2bn(cert.signature + 0, 30, bn_r); BN_bin2bn(cert.signature + 30, 30, bn_s); //EC_KEY* ec_pubKey = EC_KEY_new_by_curve_name(NID_sect233r1); //int r = EC_KEY_set_public_key_affine_coordinates(ec_pubKey, bn_r, bn_s); ECDSA_SIG* ec_sig = ECDSA_SIG_new(); //ECDSA_do_sign_ex #if OPENSSL_VERSION_NUMBER >= 0x10100000L ECDSA_SIG_set0(ec_sig, bn_r, bn_s); #else BN_copy(ec_sig->r, bn_r); BN_copy(ec_sig->s, bn_s); #endif BN_free(bn_r); BN_free(bn_s); return ec_sig; } void ECCPubKey_setSignature(CertECC_t& cert, ECDSA_SIG* sig) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L const BIGNUM* sig_r = nullptr, * sig_s = nullptr; ECDSA_SIG_get0(sig, &sig_r, &sig_s); sint32 lenR = BN_num_bytes(sig_r); sint32 lenS = BN_num_bytes(sig_s); cemu_assert_debug(lenR <= 30); cemu_assert_debug(lenS <= 30); memset(cert.signature, 0, sizeof(cert.signature)); BN_bn2bin(sig_r, cert.signature + (30 - lenR)); BN_bn2bin(sig_s, cert.signature + 60 / 2 + (30 - lenS)); #else sint32 lenR = BN_num_bytes(sig->r); sint32 lenS = BN_num_bytes(sig->s); cemu_assert_debug(lenR <= 30); cemu_assert_debug(lenS <= 30); memset(cert.signature, 0, sizeof(cert.signature)); BN_bn2bin(sig->r, cert.signature + (30 - lenR)); BN_bn2bin(sig->s, cert.signature + 60 / 2 + (30 - lenS)); #endif } ECCPrivKey g_consoleCertPrivKey{}; void iosuCrypto_getDeviceCertPrivateKey(void* privKeyOut, sint32 len) { cemu_assert(len == 30); memcpy(privKeyOut, g_consoleCertPrivKey.keyData, 30); } void iosuCrypto_getDeviceCertificate(void* certOut, sint32 len) { cemu_assert(len == 0x180); memcpy(certOut, &g_wiiuDeviceCert, 0x180); } void iosuCrypto_generateDeviceCertificate() { static_assert(sizeof(g_wiiuDeviceCert) == 0x180); memset(&g_wiiuDeviceCert, 0, sizeof(g_wiiuDeviceCert)); if (!hasOtpMem) return; // cant generate certificate without OPT // set header based on otp security mode g_wiiuDeviceCert.sigType = CertECC_t::SIGTYPE::ECC_SHA256; g_wiiuDeviceCert.ukn0C0[0] = 0x00; g_wiiuDeviceCert.ukn0C0[1] = 0x00; g_wiiuDeviceCert.ukn0C0[2] = 0x00; g_wiiuDeviceCert.ukn0C0[3] = 0x02; iosuCrypto_readOtpData(g_wiiuDeviceCert.signature, 0xA3, 0x3C); uint32be caValue; iosuCrypto_readOtpData(caValue, 0xA1); uint32be msValue; iosuCrypto_readOtpData(msValue, 0xA0); sprintf(g_wiiuDeviceCert.certificateSubject, "Root-CA%08x-MS%08x", (uint32)caValue, (uint32)msValue); uint32be ngNameValue; iosuCrypto_readOtpData(ngNameValue, 0x87); sprintf(g_wiiuDeviceCert.ngName, "NG%08x", (uint32)ngNameValue); iosuCrypto_readOtpData(&g_wiiuDeviceCert.ngKeyId, 0xA2, sizeof(uint32)); uint8 privateKey[0x20]; memset(privateKey, 0, sizeof(privateKey)); iosuCrypto_readOtpData(privateKey, 0x88, 0x1E); memcpy(g_consoleCertPrivKey.keyData, privateKey, 30); auto context = BN_CTX_new(); BN_CTX_start(context); BIGNUM* bn_privKey = BN_CTX_get(context); BN_bin2bn(privateKey, 0x1E, bn_privKey); EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_sect233r1); EC_POINT *pubkey = EC_POINT_new(group); EC_POINT_mul(group, pubkey, bn_privKey, NULL, NULL, NULL); BIGNUM* bn_x = BN_CTX_get(context); BIGNUM* bn_y = BN_CTX_get(context); EC_POINT_get_affine_coordinates(group, pubkey, bn_x, bn_y, NULL); uint8 publicKeyOutput[0x3C]; memset(publicKeyOutput, 0, sizeof(publicKeyOutput)); sint32 lenX = BN_num_bytes(bn_x); sint32 lenY = BN_num_bytes(bn_y); BN_bn2bin(bn_x, publicKeyOutput + (0x1E - lenX)); // todo - verify if the bias is correct BN_bn2bin(bn_y, publicKeyOutput + 0x3C / 2 + (0x1E - lenY)); memcpy(g_wiiuDeviceCert.publicKey, publicKeyOutput, 0x3C); // clean up EC_POINT_free(pubkey); BN_CTX_end(context); // clears all BN variables BN_CTX_free(context); } sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output) { iosuCrypto_base64Encode((uint8*)&g_wiiuDeviceCert, sizeof(g_wiiuDeviceCert), output); sint32 len = sizeof(g_wiiuDeviceCert) / 3 * 4; output[len] = '\0'; return len; } bool iosuCrypto_loadCertificate(uint32 id, std::wstring_view mlcSubpath, std::wstring_view pkeyMlcSubpath) { X509* cert = nullptr; // load cert data const auto certPath = ActiveSettings::GetMlcPath(mlcSubpath); auto certData = FileStream::LoadIntoMemory(certPath); if (!certData) return false; // file missing // get optional aes encrypted private key data std::optional<std::vector<uint8>> pkeyData; if (!pkeyMlcSubpath.empty()) { const auto pkeyPath = ActiveSettings::GetMlcPath(pkeyMlcSubpath); pkeyData = FileStream::LoadIntoMemory(pkeyPath); if (!pkeyData || pkeyData->empty()) { cemuLog_log(LogType::Force, "Unable to load private key file {}", pkeyPath.generic_string()); return false; } else if ((pkeyData->size() % 16) != 0) { cemuLog_log(LogType::Force, "Private key file has invalid length. Possibly corrupted? File: {}", pkeyPath.generic_string()); return false; } } // load certificate unsigned char* tempPtr = (unsigned char*)certData->data(); cert = d2i_X509(nullptr, (const unsigned char**)&tempPtr, certData->size()); if (cert == nullptr) { cemuLog_log(LogType::Force, "IOSU_CRYPTO: Unable to load certificate \"{}\"", boost::nowide::narrow(std::wstring(mlcSubpath))); return false; } // load optional rsa key RSA* pkeyRSA = nullptr; if (pkeyData) { cemu_assert((pkeyData->size() & 15) == 0); uint8 aesKey[16]; uint8 iv[16] = { 0 }; uint8 pkeyDecryptedData[4096]; // decrypt pkey iosuCrypto_readOtpData(aesKey, 0x120 / 4, 16); AES128_CBC_decrypt(pkeyDecryptedData, pkeyData->data(), pkeyData->size(), aesKey, iv); // convert to OpenSSL RSA pkey unsigned char* pkeyTempPtr = pkeyDecryptedData; pkeyRSA = d2i_RSAPrivateKey(nullptr, (const unsigned char **)&pkeyTempPtr, pkeyData->size()); if (pkeyRSA == nullptr) { cemuLog_log(LogType::Force, "IOSU_CRYPTO: Unable to decrypt private key \"{}\"", boost::nowide::narrow(std::wstring(pkeyMlcSubpath))); return false; } // encode private key as DER EVP_PKEY *evpPkey = EVP_PKEY_new(); EVP_PKEY_assign_RSA(evpPkey, pkeyRSA); std::vector<uint8> derPKeyData(1024 * 32); unsigned char* derPkeyTemp = derPKeyData.data(); sint32 derPkeySize = i2d_PrivateKey(evpPkey, &derPkeyTemp); derPKeyData.resize(derPkeySize); derPKeyData.shrink_to_fit(); iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].pkeyDERData = derPKeyData; } // register certificate and optional pkey iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].cert = cert; iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].certData = *certData; iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].pkey = pkeyRSA; iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].id = id; iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].isValid = true; iosuCryptoCertificates.certListCount++; return true; } bool iosuCrypto_addClientCertificate(void* sslctx, sint32 certificateId) { SSL_CTX* ctx = (SSL_CTX*)sslctx; // find entry for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++) { if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId) { if (SSL_CTX_use_certificate(ctx, iosuCryptoCertificates.certList[i].cert) != 1) { cemuLog_log(LogType::Force, "Unable to setup certificate {}", certificateId); return false; } if (SSL_CTX_use_RSAPrivateKey(ctx, iosuCryptoCertificates.certList[i].pkey) != 1) { cemuLog_log(LogType::Force, "Unable to setup certificate {} RSA private key", certificateId); return false; } if (SSL_CTX_check_private_key(ctx) == false) { cemuLog_log(LogType::Force, "Certificate private key could not be validated (verify required files for online mode or disable online mode)"); } return true; } } cemuLog_log(LogType::Force, "Certificate not found (verify required files for online mode or disable online mode)"); return false; } bool iosuCrypto_addCACertificate(void* sslctx, sint32 certificateId) { SSL_CTX* ctx = (SSL_CTX*)sslctx; // find entry for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++) { if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId) { X509_STORE* store = SSL_CTX_get_cert_store((SSL_CTX*)sslctx); X509_STORE_add_cert(store, iosuCryptoCertificates.certList[i].cert); return true; } } return false; } bool iosuCrypto_addCustomCACertificate(void* sslctx, uint8* certData, sint32 certLength) { SSL_CTX* ctx = (SSL_CTX*)sslctx; X509_STORE* store = SSL_CTX_get_cert_store((SSL_CTX*)sslctx); unsigned char* tempPtr = (unsigned char*)certData; X509* cert = d2i_X509(NULL, (const unsigned char**)&tempPtr, certLength); if (cert == nullptr) { cemuLog_log(LogType::Force, "Invalid custom server PKI certificate"); return false; } X509_STORE_add_cert(store, cert); return true; } uint8* iosuCrypto_getCertificateDataById(sint32 certificateId, sint32* certificateSize) { for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++) { if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId) { *certificateSize = iosuCryptoCertificates.certList[i].certData.size(); return iosuCryptoCertificates.certList[i].certData.data(); } } return nullptr; } uint8* iosuCrypto_getCertificatePrivateKeyById(sint32 certificateId, sint32* certificateSize) { for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++) { if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId) { *certificateSize = iosuCryptoCertificates.certList[i].pkeyDERData.size(); return iosuCryptoCertificates.certList[i].pkeyDERData.data(); } } return nullptr; } struct { const int id; const wchar_t name[256]; const wchar_t key[256]; } const g_certificates[] = { // NINTENDO CLIENT CERTS { 1, L"ccerts/WIIU_COMMON_1_CERT.der", L"ccerts/WIIU_COMMON_1_RSA_KEY.aes" }, { 3, L"ccerts/WIIU_ACCOUNT_1_CERT.der", L"ccerts/WIIU_ACCOUNT_1_RSA_KEY.aes" }, { 4, L"ccerts/WIIU_OLIVE_1_CERT.der", L"ccerts/WIIU_OLIVE_1_RSA_KEY.aes" }, { 5, L"ccerts/WIIU_VINO_1_CERT.der", L"ccerts/WIIU_VINO_1_RSA_KEY.aes" }, { 6, L"ccerts/WIIU_WOOD_1_CERT.der", L"ccerts/WIIU_WOOD_1_RSA_KEY.aes" }, { 7, L"ccerts/WIIU_OLIVE_1_CERT.der", L"ccerts/WIIU_OLIVE_1_RSA_KEY.aes" }, { 8, L"ccerts/WIIU_WOOD_1_CERT.der", L"ccerts/WIIU_WOOD_1_RSA_KEY.aes" }, // NINTENDO CA CERTS { 100, L"scerts/CACERT_NINTENDO_CA.der", L"" }, { 101, L"scerts/CACERT_NINTENDO_CA_G2.der", L"" }, { 102, L"scerts/CACERT_NINTENDO_CA_G3.der", L"" }, { 103, L"scerts/CACERT_NINTENDO_CLASS2_CA.der", L"" }, { 104, L"scerts/CACERT_NINTENDO_CLASS2_CA_G2.der", L"" }, { 105, L"scerts/CACERT_NINTENDO_CLASS2_CA_G3.der", L"" }, // COMMERCIAL CA CERTS { 1001, L"scerts/BALTIMORE_CYBERTRUST_ROOT_CA.der", L"" }, { 1002, L"scerts/CYBERTRUST_GLOBAL_ROOT_CA.der", L"" }, { 1003, L"scerts/VERIZON_GLOBAL_ROOT_CA.der", L"" }, { 1004, L"scerts/GLOBALSIGN_ROOT_CA.der", L"" }, { 1005, L"scerts/GLOBALSIGN_ROOT_CA_R2.der", L"" }, { 1006, L"scerts/GLOBALSIGN_ROOT_CA_R3.der", L"" }, { 1007, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G3.der", L"" }, { 1008, L"scerts/VERISIGN_UNIVERSAL_ROOT_CA.der", L"" }, { 1009, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G5.der", L"" }, { 1010, L"scerts/THAWTE_PRIMARY_ROOT_CA_G3.der", L"" }, { 1011, L"scerts/THAWTE_PRIMARY_ROOT_CA.der", L"" }, { 1012, L"scerts/GEOTRUST_GLOBAL_CA.der", L"" }, { 1013, L"scerts/GEOTRUST_GLOBAL_CA2.der", L"" }, { 1014, L"scerts/GEOTRUST_PRIMARY_CA.der", L"" }, { 1015, L"scerts/GEOTRUST_PRIMARY_CA_G3.der", L"" }, { 1016, L"scerts/ADDTRUST_EXT_CA_ROOT.der", L"" }, { 1017, L"scerts/COMODO_CA.der", L"" }, { 1018, L"scerts/UTN_DATACORP_SGC_CA.der", L"" }, { 1019, L"scerts/UTN_USERFIRST_HARDWARE_CA.der" , L"" }, { 1020, L"scerts/DIGICERT_HIGH_ASSURANCE_EV_ROOT_CA.der", L"" }, { 1021, L"scerts/DIGICERT_ASSURED_ID_ROOT_CA.der", L"" }, { 1022, L"scerts/DIGICERT_GLOBAL_ROOT_CA.der", L"" }, { 1023, L"scerts/GTE_CYBERTRUST_GLOBAL_ROOT.der", L"" }, { 1024, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA.der", L"" }, { 1025, L"scerts/THAWTE_PREMIUM_SERVER_CA.der", L"" }, { 1026, L"scerts/EQUIFAX_SECURE_CA.der", L"" }, { 1027, L"scerts/ENTRUST_SECURE_SERVER_CA.der", L"" }, { 1028, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G2.der", L"" }, { 1029, L"scerts/ENTRUST_CA_2048.der", L"" }, { 1030, L"scerts/ENTRUST_ROOT_CA.der", L"" }, { 1031, L"scerts/ENTRUST_ROOT_CA_G2.der", L"" }, { 1032, L"scerts/DIGICERT_ASSURED_ID_ROOT_CA_G2.der", L"" }, { 1033, L"scerts/DIGICERT_GLOBAL_ROOT_CA_G2.der", L"" }, }; void iosuCrypto_loadSSLCertificates() { if (iosuCryptoCertificates.hasCertificates) return; if (!hasOtpMem) return; // cant load certificates without OTP keys // load CA certificate bool hasAllCertificates = true; for( const auto& c : g_certificates ) { std::wstring certDir = L"sys/title/0005001b/10054000/content/"; std::wstring certFilePath = certDir + c.name; std::wstring keyFilePath; if( *c.key ) keyFilePath = certDir + c.key; else keyFilePath.clear(); if (iosuCrypto_loadCertificate(c.id, certFilePath, keyFilePath) == false) { cemuLog_log(LogType::Force, "Unable to load certificate \"{}\"", boost::nowide::narrow(certFilePath)); hasAllCertificates = false; } } iosuCryptoCertificates.hasCertificates = hasAllCertificates; // true } void iosuCrypto_init() { // load OTP dump if (std::ifstream otp_file(ActiveSettings::GetUserDataPath("otp.bin"), std::ifstream::in | std::ios::binary); otp_file.is_open()) { otp_file.seekg(0, std::ifstream::end); const auto length = otp_file.tellg(); otp_file.seekg(0, std::ifstream::beg); // verify if OTP is ok if (length != 1024) // todo - should also check some fixed values to verify integrity of otp dump { cemuLog_log(LogType::Force, "IOSU_CRYPTO: otp.bin has wrong size (must be 1024 bytes)"); hasOtpMem = false; } else { otp_file.read((char*)otpMem, 1024); hasOtpMem = (bool)otp_file; } } else { cemuLog_log(LogType::Force, "IOSU_CRYPTO: No otp.bin found. Online mode cannot be used"); hasOtpMem = false; } if (std::ifstream seeprom_file(ActiveSettings::GetUserDataPath("seeprom.bin"), std::ifstream::in | std::ios::binary); seeprom_file.is_open()) { seeprom_file.seekg(0, std::ifstream::end); const auto length = seeprom_file.tellg(); seeprom_file.seekg(0, std::ifstream::beg); // verify if seeprom is ok if (length != 512) // todo - maybe check some known values to verify integrity of seeprom { cemuLog_log(LogType::Force, "IOSU_CRYPTO: seeprom.bin has wrong size (must be 512 bytes)"); hasSeepromMem = false; } else { seeprom_file.read((char*)seepromMem, 512); hasSeepromMem = (bool)seeprom_file; } } else { cemuLog_log(LogType::Force, "IOSU_CRYPTO: No Seeprom.bin found. Online mode cannot be used"); hasSeepromMem = false; } // generate device certificate iosuCrypto_generateDeviceCertificate(); // load SSL certificates iosuCrypto_loadSSLCertificates(); } bool iosuCrypto_checkRequirementMLCFile(std::string_view mlcSubpath, std::string& additionalErrorInfo_filePath) { const auto path = ActiveSettings::GetMlcPath(mlcSubpath); additionalErrorInfo_filePath = _pathToUtf8(path); sint32 fileDataSize = 0; auto fileData = FileStream::LoadIntoMemory(path); if (!fileData) return false; return true; } sint32 iosuCrypt_checkRequirementsForOnlineMode(std::string& additionalErrorInfo) { std::error_code ec; // check if otp.bin is present const auto otp_file = ActiveSettings::GetUserDataPath("otp.bin"); if(!fs::exists(otp_file, ec)) return IOS_CRYPTO_ONLINE_REQ_OTP_MISSING; if(fs::file_size(otp_file, ec) != 1024) return IOS_CRYPTO_ONLINE_REQ_OTP_CORRUPTED; // check if seeprom.bin is present const auto seeprom_file = ActiveSettings::GetUserDataPath("seeprom.bin"); if (!fs::exists(seeprom_file, ec)) return IOS_CRYPTO_ONLINE_REQ_SEEPROM_MISSING; if (fs::file_size(seeprom_file, ec) != 512) return IOS_CRYPTO_ONLINE_REQ_SEEPROM_CORRUPTED; for (const auto& c : g_certificates) { std::string subPath = fmt::format("sys/title/0005001b/10054000/content/{}", boost::nowide::narrow(c.name)); if (iosuCrypto_checkRequirementMLCFile(subPath, additionalErrorInfo) == false) { cemuLog_log(LogType::Force, "Missing dumped file for online mode: {}", subPath); return IOS_CRYPTO_ONLINE_REQ_MISSING_FILE; } if (*c.key) { std::string subPath = fmt::format("sys/title/0005001b/10054000/content/{}", boost::nowide::narrow(c.key)); if (iosuCrypto_checkRequirementMLCFile(subPath, additionalErrorInfo) == false) { cemuLog_log(LogType::Force, "Missing dumped file for online mode: {}", subPath); return IOS_CRYPTO_ONLINE_REQ_MISSING_FILE; } } } return IOS_CRYPTO_ONLINE_REQ_OK; } std::vector<const wchar_t*> iosuCrypt_getCertificateKeys() { std::vector<const wchar_t*> result; result.reserve(std::size(g_certificates)); for (const auto& c : g_certificates) { if (c.key[0] == '\0') continue; result.emplace_back(c.key); } return result; } std::vector<const wchar_t*> iosuCrypt_getCertificateNames() { std::vector<const wchar_t*> result; result.reserve(std::size(g_certificates)); for (const auto& c : g_certificates) { result.emplace_back(c.name); } return result; } ================================================ FILE: src/Cafe/IOSU/legacy/iosu_crypto.h ================================================ #pragma once void iosuCrypto_init(); bool iosuCrypto_getDeviceId(uint32* deviceId); void iosuCrypto_getDeviceSerialString(char* serialString); // certificate API struct ECCPrivKey { uint8 keyData[30]; }; struct ECCPubKey { uint8 keyData[60]; }; struct ECCSig { uint8 keyData[60]; // check size? }; struct CHash256 { uint8 b[32]; }; struct CertECC_t { enum class SIGTYPE : uint32 { ECC_SHA256 = 0x00010005 }; /* +0x000 */ betype<SIGTYPE> sigType; // 01 00 02 00 /* +0x004 */ uint8 signature[0x3C]; // from OTP 0xA3*4 /* +0x040 */ uint8 ukn040[0x40]; // seems to be just padding /* +0x080 */ char certificateSubject[0x40]; // "Root - CA%08x - MS%08x" /* +0x0C0 */ char ukn0C0[0x4]; // ??? 00 00 00 02 ? /* +0x0C4 */ char ngName[0x40]; // "NG%08X" /* +0x104 */ uint32 ngKeyId; // big endian? (from OTP 0xA2*4) /* +0x108 */ uint8 publicKey[0x3C]; /* +0x144 */ uint8 padding[0x180 - 0x144]; }; static_assert(sizeof(CertECC_t) == 0x180); sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output); bool iosuCrypto_verifyCert(CertECC_t& cert); std::string iosuCrypto_base64Encode(unsigned char const* bytes_to_encode, unsigned int inputLen); // certificate store bool iosuCrypto_addClientCertificate(void* sslctx, sint32 certificateId); bool iosuCrypto_addCACertificate(void* sslctx, sint32 certificateId); bool iosuCrypto_addCustomCACertificate(void* sslctx, uint8* certData, sint32 certLength); uint8* iosuCrypto_getCertificateDataById(sint32 certificateId, sint32* certificateSize); uint8* iosuCrypto_getCertificatePrivateKeyById(sint32 certificateId, sint32* certificateSize); // helper for online functionality enum { IOS_CRYPTO_ONLINE_REQ_OK, IOS_CRYPTO_ONLINE_REQ_OTP_MISSING, IOS_CRYPTO_ONLINE_REQ_OTP_CORRUPTED, IOS_CRYPTO_ONLINE_REQ_SEEPROM_MISSING, IOS_CRYPTO_ONLINE_REQ_SEEPROM_CORRUPTED, IOS_CRYPTO_ONLINE_REQ_MISSING_FILE }; sint32 iosuCrypt_checkRequirementsForOnlineMode(std::string& additionalErrorInfo); void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size); std::vector<const wchar_t*> iosuCrypt_getCertificateKeys(); std::vector<const wchar_t*> iosuCrypt_getCertificateNames(); ================================================ FILE: src/Cafe/IOSU/legacy/iosu_fpd.cpp ================================================ #include "iosu_act.h" #include "iosu_fpd.h" #include "Cemu/nex/nex.h" #include "Cemu/nex/nexFriends.h" #include "util/helpers/helpers.h" #include "config/CemuConfig.h" #include "Cafe/CafeSystem.h" #include "config/ActiveSettings.h" #include "Cemu/napi/napi.h" #include "util/helpers/StringHelpers.h" #include "Cafe/IOSU/iosu_types_common.h" #include "Cafe/IOSU/nn/iosu_nn_service.h" #include "Common/CafeString.h" std::mutex g_friend_notification_mutex; std::vector< std::pair<std::string, int> > g_friend_notifications; namespace iosu { namespace fpd { using NotificationRunningId = uint64; struct NotificationEntry { NotificationEntry(uint64 index, NexFriends::NOTIFICATION_TYPE type, uint32 pid) : timestamp(std::chrono::steady_clock::now()), runningId(index), type(type), pid(pid) {} std::chrono::steady_clock::time_point timestamp; NotificationRunningId runningId; NexFriends::NOTIFICATION_TYPE type; uint32 pid; }; class { public: void TrackNotification(NexFriends::NOTIFICATION_TYPE type, uint32 pid) { std::unique_lock _l(m_mtxNotificationQueue); m_notificationQueue.emplace_back(m_notificationQueueIndex++, type, pid); } void RemoveExpired() { // remove entries older than 10 seconds std::chrono::steady_clock::time_point expireTime = std::chrono::steady_clock::now() - std::chrono::seconds(10); std::erase_if(m_notificationQueue, [expireTime](const auto& notification) { return notification.timestamp < expireTime; }); } std::optional<NotificationEntry> GetNextNotification(NotificationRunningId& previousRunningId) { std::unique_lock _l(m_mtxNotificationQueue); auto it = std::lower_bound(m_notificationQueue.begin(), m_notificationQueue.end(), previousRunningId, [](const auto& notification, const auto& runningId) { return notification.runningId <= runningId; }); size_t itIndex = it - m_notificationQueue.begin(); if(it == m_notificationQueue.end()) return std::nullopt; previousRunningId = it->runningId; return *it; } private: std::vector<NotificationEntry> m_notificationQueue; std::mutex m_mtxNotificationQueue; std::atomic_uint64_t m_notificationQueueIndex{1}; }g_NotificationQueue; struct { bool isThreadStarted; bool isInitialized2; NexFriends* nexFriendSession; std::mutex mtxFriendSession; // session state std::atomic_bool sessionStarted{false}; // current state nexPresenceV2 myPresence; }g_fpd = {}; void OverlayNotificationHandler(NexFriends::NOTIFICATION_TYPE type, uint32 pid) { cemuLog_logDebug(LogType::Force, "Friends::Notification {:02x} pid {:08x}", type, pid); if(!GetConfig().notification.friends) return; std::unique_lock lock(g_friend_notification_mutex); std::string message; if(type == NexFriends::NOTIFICATION_TYPE::NOTIFICATION_TYPE_ONLINE) { g_friend_notifications.emplace_back("Connected to friend service", 5000); if(g_fpd.nexFriendSession && g_fpd.nexFriendSession->getPendingFriendRequestCount() > 0) g_friend_notifications.emplace_back(fmt::format("You have {} pending friend request(s)", g_fpd.nexFriendSession->getPendingFriendRequestCount()), 5000); } else { std::string msg_format; switch(type) { case NexFriends::NOTIFICATION_TYPE_ONLINE: break; case NexFriends::NOTIFICATION_TYPE_FRIEND_LOGIN: msg_format = "{} is now online"; break; case NexFriends::NOTIFICATION_TYPE_FRIEND_LOGOFF: msg_format = "{} is now offline"; break; case NexFriends::NOTIFICATION_TYPE_FRIEND_PRESENCE_CHANGE: break; case NexFriends::NOTIFICATION_TYPE_ADDED_FRIEND: msg_format = "{} has been added to your friend list"; break; case NexFriends::NOTIFICATION_TYPE_REMOVED_FRIEND: msg_format = "{} has been removed from your friend list"; break; case NexFriends::NOTIFICATION_TYPE_ADDED_OUTGOING_REQUEST: break; case NexFriends::NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST: break; case NexFriends::NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST: msg_format = "{} wants to add you to his friend list"; break; case NexFriends::NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST: break; default: ; } if (!msg_format.empty()) { std::string name = fmt::format("{:#x}", pid); if (g_fpd.nexFriendSession) { const std::string tmp = g_fpd.nexFriendSession->getAccountNameByPid(pid); if (!tmp.empty()) name = tmp; } g_friend_notifications.emplace_back(fmt::format(fmt::runtime(msg_format), name), 5000); } } } void NotificationHandler(NexFriends::NOTIFICATION_TYPE type, uint32 pid) { OverlayNotificationHandler(type, pid); g_NotificationQueue.TrackNotification(type, pid); } void convertMultiByteStringToBigEndianWidechar(const char* input, uint16be* output, sint32 maxOutputLength) { std::vector<uint16be> beStr = StringHelpers::FromUtf8(input); if (beStr.size() >= maxOutputLength - 1) beStr.resize(maxOutputLength-1); for (size_t i = 0; i < beStr.size(); i++) output[i] = beStr[i]; output[beStr.size()] = '\0'; } void convertFPDTimestampToDate(uint64 timestamp, FPDDate* fpdDate) { // if the timestamp is zero then still return a valid date if (timestamp == 0) { fpdDate->second = 0; fpdDate->minute = 0; fpdDate->hour = 0; fpdDate->day = 1; fpdDate->month = 1; fpdDate->year = 1970; return; } fpdDate->second = (uint8)((timestamp) & 0x3F); fpdDate->minute = (uint8)((timestamp >> 6) & 0x3F); fpdDate->hour = (uint8)((timestamp >> 12) & 0x1F); fpdDate->day = (uint8)((timestamp >> 17) & 0x1F); fpdDate->month = (uint8)((timestamp >> 22) & 0xF); fpdDate->year = (uint16)((timestamp >> 26)); } uint64 convertDateToFPDTimestamp(FPDDate* fpdDate) { uint64 t = 0; t |= (uint64)fpdDate->second; t |= ((uint64)fpdDate->minute<<6); t |= ((uint64)fpdDate->hour<<12); t |= ((uint64)fpdDate->day<<17); t |= ((uint64)fpdDate->month<<22); t |= ((uint64)(uint16)fpdDate->year<<26); return t; } void NexPresenceToGameMode(const nexPresenceV2* presence, GameMode* gameMode) { memset(gameMode, 0, sizeof(GameMode)); gameMode->joinFlagMask = presence->joinFlagMask; gameMode->matchmakeType = presence->joinAvailability; gameMode->joinGameId = presence->gameId; gameMode->joinGameMode = presence->gameMode; gameMode->hostPid = presence->hostPid; gameMode->groupId = presence->groupId; memcpy(gameMode->appSpecificData, presence->appSpecificData, 0x14); } void GameModeToNexPresence(const GameMode* gameMode, nexPresenceV2* presence) { *presence = {}; presence->joinFlagMask = gameMode->joinFlagMask; presence->joinAvailability = (uint8)(uint32)gameMode->matchmakeType; presence->gameId = gameMode->joinGameId; presence->gameMode = gameMode->joinGameMode; presence->hostPid = gameMode->hostPid; presence->groupId = gameMode->groupId; memcpy(presence->appSpecificData, gameMode->appSpecificData, 0x14); } void NexFriendToFPDFriendData(const nexFriend* frd, FriendData* friendData) { memset(friendData, 0, sizeof(FriendData)); // setup friend data friendData->type = 1; // friend friendData->pid = frd->nnaInfo.principalInfo.principalId; memcpy(friendData->mii, frd->nnaInfo.principalInfo.mii.miiData, FFL_SIZE); strcpy((char*)friendData->nnid, frd->nnaInfo.principalInfo.nnid); // screenname convertMultiByteStringToBigEndianWidechar(frd->nnaInfo.principalInfo.mii.miiNickname, friendData->screenname, sizeof(friendData->screenname) / sizeof(uint16be)); friendData->friendExtraData.isOnline = frd->presence.isOnline != 0 ? 1 : 0; friendData->friendExtraData.gameKey.titleId = frd->presence.gameKey.titleId; friendData->friendExtraData.gameKey.ukn08 = frd->presence.gameKey.ukn; NexPresenceToGameMode(&frd->presence, &friendData->friendExtraData.gameMode); auto fixed_presence_msg = '\0' + frd->presence.msg; // avoid first character of comment from being cut off friendData->friendExtraData.gameModeDescription.assignFromUTF8(fixed_presence_msg); auto fixed_comment = '\0' + frd->comment.commentString; // avoid first character of comment from being cut off friendData->friendExtraData.comment.assignFromUTF8(fixed_comment); // set valid dates friendData->uknDate.year = 2018; friendData->uknDate.day = 1; friendData->uknDate.month = 1; friendData->uknDate.hour = 1; friendData->uknDate.minute = 1; friendData->uknDate.second = 1; friendData->friendExtraData.approvalTime.year = 2018; friendData->friendExtraData.approvalTime.day = 1; friendData->friendExtraData.approvalTime.month = 1; friendData->friendExtraData.approvalTime.hour = 1; friendData->friendExtraData.approvalTime.minute = 1; friendData->friendExtraData.approvalTime.second = 1; convertFPDTimestampToDate(frd->lastOnlineTimestamp, &friendData->friendExtraData.lastOnline); } void NexFriendRequestToFPDFriendData(const nexFriendRequest* frdReq, bool isIncoming, FriendData* friendData) { memset(friendData, 0, sizeof(FriendData)); // setup friend data friendData->type = 0; // friend request friendData->pid = frdReq->principalInfo.principalId; memcpy(friendData->mii, frdReq->principalInfo.mii.miiData, FFL_SIZE); strcpy((char*)friendData->nnid, frdReq->principalInfo.nnid); // screenname convertMultiByteStringToBigEndianWidechar(frdReq->principalInfo.mii.miiNickname, friendData->screenname, sizeof(friendData->screenname) / sizeof(uint16be)); convertMultiByteStringToBigEndianWidechar(frdReq->message.commentStr.c_str(), friendData->requestExtraData.comment, sizeof(friendData->requestExtraData.comment) / sizeof(uint16be)); FPDDate expireDate; convertFPDTimestampToDate(frdReq->message.expireTimestamp, &expireDate); bool isProvisional = frdReq->message.expireTimestamp == 0; //friendData->requestExtraData.ukn0A8 = 0; // no change? //friendData->requestExtraData.ukn0A0 = 0; // if not set -> provisional friend request //friendData->requestExtraData.ukn0A4 = isProvisional ? 0 : 123; // no change? friendData->requestExtraData.messageId = frdReq->message.messageId; ///* +0x0A8 */ uint8 ukn0A8; ///* +0x0A9 */ uint8 ukn0A9; // comment language? (guessed) ///* +0x0AA */ uint16be comment[0x40]; ///* +0x12A */ uint8 ukn12A; // ingame name language? (guessed) ///* +0x12B */ uint8 _padding12B; // set valid dates friendData->uknDate.year = 2018; friendData->uknDate.day = 20; friendData->uknDate.month = 4; friendData->uknDate.hour = 12; friendData->uknDate.minute = 1; friendData->uknDate.second = 1; friendData->requestExtraData.uknData0.year = 2018; friendData->requestExtraData.uknData0.day = 24; friendData->requestExtraData.uknData0.month = 4; friendData->requestExtraData.uknData0.hour = 1; friendData->requestExtraData.uknData0.minute = 1; friendData->requestExtraData.uknData0.second = 1; // this is the date used for 'Expires in' convertFPDTimestampToDate(frdReq->message.expireTimestamp, &friendData->requestExtraData.uknData1); } void NexFriendRequestToFPDFriendRequest(const nexFriendRequest* frdReq, bool isIncoming, FriendRequest* friendRequest) { memset(friendRequest, 0, sizeof(FriendRequest)); friendRequest->pid = frdReq->principalInfo.principalId; strncpy((char*)friendRequest->nnid, frdReq->principalInfo.nnid, sizeof(friendRequest->nnid)); friendRequest->nnid[sizeof(friendRequest->nnid) - 1] = '\0'; memcpy(friendRequest->miiData, frdReq->principalInfo.mii.miiData, sizeof(friendRequest->miiData)); convertMultiByteStringToBigEndianWidechar(frdReq->message.commentStr.c_str(), friendRequest->message, sizeof(friendRequest->message) / sizeof(friendRequest->message[0])); convertMultiByteStringToBigEndianWidechar(frdReq->principalInfo.mii.miiNickname, friendRequest->screenname, sizeof(friendRequest->screenname) / sizeof(friendRequest->screenname[0])); friendRequest->isMarkedAsReceived = 1; friendRequest->ukn98 = _swapEndianU64(frdReq->message.messageId); convertFPDTimestampToDate(0, &friendRequest->uknDate); convertFPDTimestampToDate(0, &friendRequest->uknDate2); convertFPDTimestampToDate(frdReq->message.expireTimestamp, &friendRequest->expireDate); } struct FPProfile { uint8be country; uint8be area; uint16be unused; }; static_assert(sizeof(FPProfile) == 4); struct SelfPresence { uint8be ukn[0x130]; // todo }; static_assert(sizeof(SelfPresence) == 0x130); struct SelfPlayingGame { uint8be ukn0[0x10]; }; static_assert(sizeof(SelfPlayingGame) == 0x10); static const auto FPResult_Ok = 0; static const auto FPResult_InvalidIPCParam = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_FP, 0x680); static const auto FPResult_RequestFailed = BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_FP, 0); // figure out proper error code static const auto FPResult_Aborted = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_FP, 0x3480); class FPDService : public iosu::nn::IPCSimpleService { struct NotificationAsyncRequest { NotificationAsyncRequest(IPCCommandBody* cmd, uint32 maxNumEntries, FPDNotification* notificationsOut, uint32be* countOut) : cmd(cmd), maxNumEntries(maxNumEntries), notificationsOut(notificationsOut), countOut(countOut) { } IPCCommandBody* cmd; uint32 maxNumEntries; FPDNotification* notificationsOut; uint32be* countOut; }; struct FPDClient { bool hasLoggedIn{false}; uint32 notificationMask{0}; NotificationRunningId prevRunningId{0}; std::vector<NotificationAsyncRequest> notificationRequests; }; // storage for async IPC requests std::vector<IPCCommandBody*> m_asyncLoginRequests; std::vector<FPDClient*> m_clients; public: FPDService() : iosu::nn::IPCSimpleService("/dev/fpd") {} std::string GetThreadName() override { return "IOSUModule::FPD"; } void StartService() override { cemu_assert_debug(m_asyncLoginRequests.empty()); } void StopService() override { m_asyncLoginRequests.clear(); for(auto& it : m_clients) delete it; m_clients.clear(); } void* CreateClientObject() override { FPDClient* client = new FPDClient(); m_clients.push_back(client); return client; } void DestroyClientObject(void* clientObject) override { FPDClient* client = (FPDClient*)clientObject; std::erase(m_clients, client); delete client; } void SendQueuedNotifications(FPDClient* client) { if (client->notificationRequests.empty()) return; if (client->notificationRequests.size() > 1) cemuLog_log(LogType::Force, "FPD: More than one simultanous notification query not supported"); NotificationAsyncRequest& request = client->notificationRequests[0]; uint32 numNotifications = 0; while(numNotifications < request.maxNumEntries) { auto notification = g_NotificationQueue.GetNextNotification(client->prevRunningId); if (!notification) break; uint32 flag = 1 << static_cast<uint32>(notification->type); if((client->notificationMask & flag) == 0) continue; request.notificationsOut[numNotifications].type = static_cast<uint32>(notification->type); request.notificationsOut[numNotifications].pid = notification->pid; numNotifications++; } if (numNotifications == 0) return; *request.countOut = numNotifications; ServiceCallAsyncRespond(request.cmd, FPResult_Ok); client->notificationRequests.erase(client->notificationRequests.begin()); } void TimerUpdate() override { // called once a second while service is running std::unique_lock _l(g_fpd.mtxFriendSession); if (!g_fpd.nexFriendSession) return; g_fpd.nexFriendSession->update(); while(!m_asyncLoginRequests.empty()) { if(g_fpd.nexFriendSession->isOnline()) { ServiceCallAsyncRespond(m_asyncLoginRequests.front(), FPResult_Ok); m_asyncLoginRequests.erase(m_asyncLoginRequests.begin()); } else break; } // handle notification responses g_NotificationQueue.RemoveExpired(); for(auto& client : m_clients) SendQueuedNotifications(client); } uint32 ServiceCall(void* clientObject, uint32 requestId, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) override { // for /dev/fpd input and output vectors are swapped std::swap(vecIn, vecOut); std::swap(numVecIn, numVecOut); FPDClient* fpdClient = (FPDClient*)clientObject; switch(static_cast<FPD_REQUEST_ID>(requestId)) { case FPD_REQUEST_ID::SetNotificationMask: return CallHandler_SetNotificationMask(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetNotificationAsync: return CallHandler_GetNotificationAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::SetLedEventMask: cemuLog_logDebug(LogType::Force, "[/dev/fpd] SetLedEventMask is todo"); return FPResult_Ok; case FPD_REQUEST_ID::LoginAsync: return CallHandler_LoginAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::HasLoggedIn: return CallHandler_HasLoggedIn(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::IsOnline: return CallHandler_IsOnline(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetMyPrincipalId: return CallHandler_GetMyPrincipalId(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetMyAccountId: return CallHandler_GetMyAccountId(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetMyScreenName: return CallHandler_GetMyScreenName(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetMyMii: return CallHandler_GetMyMii(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetMyProfile: return CallHandler_GetMyProfile(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetMyPresence: return CallHandler_GetMyPresence(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetMyComment: return CallHandler_GetMyComment(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetMyPreference: return CallHandler_GetMyPreference(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetMyPlayingGame: return CallHandler_GetMyPlayingGame(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetFriendAccountId: return CallHandler_GetFriendAccountId(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetFriendScreenName: return CallHandler_GetFriendScreenName(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetFriendMii: return CallHandler_GetFriendMii(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetFriendPresence: return CallHandler_GetFriendPresence(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetFriendRelationship: return CallHandler_GetFriendRelationship(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetFriendList: return CallHandler_GetFriendList_GetFriendListAll(fpdClient, vecIn, numVecIn, vecOut, numVecOut, false); case FPD_REQUEST_ID::GetFriendListAll: return CallHandler_GetFriendList_GetFriendListAll(fpdClient, vecIn, numVecIn, vecOut, numVecOut, true); case FPD_REQUEST_ID::GetFriendRequestList: return CallHandler_GetFriendRequestList(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetFriendRequestListEx: return CallHandler_GetFriendRequestListEx(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetBlackList: return CallHandler_GetBlackList(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetFriendListEx: return CallHandler_GetFriendListEx(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::UpdateCommentAsync: return CallHandler_UpdateCommentAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::UpdatePreferenceAsync: return CallHandler_UpdatePreferenceAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::AddFriendRequestByPlayRecordAsync: return CallHandler_AddFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::AcceptFriendRequestAsync: return CallHandler_AcceptFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::DeleteFriendRequestAsync: return CallHandler_DeleteFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::CancelFriendRequestAsync: return CallHandler_CancelFriendRequestAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::MarkFriendRequestsAsReceivedAsync: return CallHandler_MarkFriendRequestsAsReceivedAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::RemoveFriendAsync: return CallHandler_RemoveFriendAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::DeleteFriendFlagsAsync: return CallHandler_DeleteFriendFlagsAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetBasicInfoAsync: return CallHandler_GetBasicInfoAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::CheckSettingStatusAsync: return CallHandler_CheckSettingStatusAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::IsPreferenceValid: return CallHandler_IsPreferenceValid(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetRequestBlockSettingAsync: return CallHandler_GetRequestBlockSettingAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::AddFriendAsyncByPid: return CallHandler_AddFriendAsyncByPid(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::UpdateGameModeVariation1: case FPD_REQUEST_ID::UpdateGameModeVariation2: return CallHandler_UpdateGameMode(fpdClient, vecIn, numVecIn, vecOut, numVecOut); default: cemuLog_log(LogType::Force, "Unsupported service call {} to /dev/fpd", requestId); return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_FP, 0); } } #define DeclareInputPtr(__Name, __T, __count, __vecIndex) if(sizeof(__T)*(__count) != vecIn[__vecIndex].size) { cemuLog_log(LogType::Force, "FPD: IPC buffer has incorrect size"); return FPResult_InvalidIPCParam;}; __T* __Name = ((__T*)vecIn[__vecIndex].basePhys.GetPtr()) #define DeclareInput(__Name, __T, __vecIndex) if(sizeof(__T) != vecIn[__vecIndex].size) { cemuLog_log(LogType::Force, "FPD: IPC buffer has incorrect size"); return FPResult_InvalidIPCParam;}; __T __Name = *((__T*)vecIn[__vecIndex].basePhys.GetPtr()) #define DeclareOutputPtr(__Name, __T, __count, __vecIndex) if(sizeof(__T)*(__count) != vecOut[__vecIndex].size) { cemuLog_log(LogType::Force, "FPD: IPC buffer has incorrect size"); return FPResult_InvalidIPCParam;}; __T* __Name = ((__T*)vecOut[__vecIndex].basePhys.GetPtr()) template<typename T> static nnResult WriteValueOutput(IPCIoctlVector* vec, const T& value) { if(vec->size != sizeof(T)) return FPResult_InvalidIPCParam; *(T*)vec->basePhys.GetPtr() = value; return FPResult_Ok; } nnResult CallHandler_SetNotificationMask(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 1 || numVecOut != 0) return FPResult_InvalidIPCParam; DeclareInput(notificationMask, uint32be, 0); fpdClient->notificationMask = notificationMask; return FPResult_Ok; } nnResult CallHandler_GetNotificationAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 2) return FPResult_InvalidIPCParam; if((vecOut[0].size % sizeof(FPDNotification)) != 0 || vecOut[0].size < sizeof(FPDNotification)) { cemuLog_log(LogType::Force, "FPD GetNotificationAsync: Unexpected output size"); return FPResult_InvalidIPCParam; } IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); DeclareOutputPtr(countOut, uint32be, 1, 1); uint32 maxCount = vecOut[0].size / sizeof(FPDNotification); DeclareOutputPtr(notificationList, FPDNotification, maxCount, 0); fpdClient->notificationRequests.emplace_back(cmd, maxCount, notificationList, countOut); SendQueuedNotifications(fpdClient); // if any notifications are queued, send them immediately return FPResult_Ok; } nnResult CallHandler_LoginAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!ActiveSettings::IsOnlineEnabled()) { // not online, fail immediately return FPResult_Ok; // Splatoon expects this to always return success otherwise it will softlock. This should be FPResult_Aborted? } StartFriendSession(); fpdClient->hasLoggedIn = true; IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); m_asyncLoginRequests.emplace_back(cmd); return FPResult_Ok; } nnResult CallHandler_HasLoggedIn(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; return WriteValueOutput<uint32be>(vecOut, fpdClient->hasLoggedIn ? 1 : 0); } nnResult CallHandler_IsOnline(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; bool isOnline = g_fpd.nexFriendSession ? g_fpd.nexFriendSession->isOnline() : false; return WriteValueOutput<uint32be>(vecOut, isOnline?1:0); } nnResult CallHandler_GetMyPrincipalId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; uint8 slot = iosu::act::getCurrentAccountSlot(); uint32 pid = 0; iosu::act::getPrincipalId(slot, &pid); return WriteValueOutput<uint32be>(vecOut, pid); } nnResult CallHandler_GetMyAccountId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; uint8 slot = iosu::act::getCurrentAccountSlot(); std::string accountId = iosu::act::getAccountId2(slot); if(vecOut->size != ACT_ACCOUNTID_LENGTH) { cemuLog_log(LogType::Force, "GetMyAccountId: Unexpected output size"); return FPResult_InvalidIPCParam; } if(accountId.length() > ACT_ACCOUNTID_LENGTH-1) { cemuLog_log(LogType::Force, "GetMyAccountId: AccountID is too long"); return FPResult_InvalidIPCParam; } if(accountId.empty()) { cemuLog_log(LogType::Force, "GetMyAccountId: AccountID is empty"); return FPResult_InvalidIPCParam; // should return 0xC0C00800 ? } char* outputStr = (char*)vecOut->basePhys.GetPtr(); memset(outputStr, 0, ACT_ACCOUNTID_LENGTH); memcpy(outputStr, accountId.data(), accountId.length()); return FPResult_Ok; } nnResult CallHandler_GetMyScreenName(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; uint8 slot = iosu::act::getCurrentAccountSlot(); if(vecOut->size != ACT_NICKNAME_SIZE*sizeof(uint16be)) { cemuLog_log(LogType::Force, "GetMyScreenName: Unexpected output size"); return FPResult_InvalidIPCParam; } uint16 screenname[ACT_NICKNAME_SIZE]{0}; bool r = iosu::act::getScreenname(slot, screenname); if (!r) { cemuLog_log(LogType::Force, "GetMyScreenName: Screenname is empty"); return FPResult_InvalidIPCParam; // should return 0xC0C00800 ? } uint16be* outputStr = (uint16be*)vecOut->basePhys.GetPtr(); for(sint32 i = 0; i < ACT_NICKNAME_SIZE; i++) outputStr[i] = screenname[i]; return FPResult_Ok; } nnResult CallHandler_GetMyMii(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; uint8 slot = iosu::act::getCurrentAccountSlot(); if(vecOut->size != FFL_SIZE) { cemuLog_log(LogType::Force, "GetMyMii: Unexpected output size"); return FPResult_InvalidIPCParam; } bool r = iosu::act::getMii(slot, (FFLData_t*)vecOut->basePhys.GetPtr()); if (!r) { cemuLog_log(LogType::Force, "GetMyMii: Mii is empty"); return FPResult_InvalidIPCParam; // should return 0xC0C00800 ? } return FPResult_Ok; } nnResult CallHandler_GetMyProfile(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; uint8 slot = iosu::act::getCurrentAccountSlot(); FPProfile profile{0}; // todo cemuLog_log(LogType::Force, "GetMyProfile is todo"); return WriteValueOutput<FPProfile>(vecOut, profile); } nnResult CallHandler_GetMyPresence(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; uint8 slot = iosu::act::getCurrentAccountSlot(); SelfPresence selfPresence{0}; cemuLog_log(LogType::Force, "GetMyPresence is todo"); return WriteValueOutput<SelfPresence>(vecOut, selfPresence); } nnResult CallHandler_GetMyComment(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; std::vector<uint16be> myComment; if(g_fpd.nexFriendSession) { if(vecOut->size != MY_COMMENT_LENGTH * sizeof(uint16be)) { cemuLog_log(LogType::Force, "GetMyComment: Unexpected output size"); return FPResult_InvalidIPCParam; } nexComment myNexComment; g_fpd.nexFriendSession->getMyComment(myNexComment); myComment = StringHelpers::FromUtf8(myNexComment.commentString); } myComment.insert(myComment.begin(), '\0'); memcpy(vecOut->basePhys.GetPtr(), myComment.data(), MY_COMMENT_LENGTH * sizeof(uint16be)); return FPResult_Ok; } nnResult CallHandler_GetMyPreference(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; FPDPreference selfPreference{0}; if(g_fpd.nexFriendSession) { nexPrincipalPreference nexPreference; g_fpd.nexFriendSession->getMyPreference(nexPreference); selfPreference.showOnline = nexPreference.showOnline; selfPreference.showGame = nexPreference.showGame; selfPreference.blockFriendRequests = nexPreference.blockFriendRequests; selfPreference.ukn = 0; } else memset(&selfPreference, 0, sizeof(FPDPreference)); return WriteValueOutput<FPDPreference>(vecOut, selfPreference); } nnResult CallHandler_GetMyPlayingGame(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; GameKey selfPlayingGame { CafeSystem::GetForegroundTitleId(), CafeSystem::GetForegroundTitleVersion(), {0,0,0,0,0,0} }; if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) != 0x00050000) { selfPlayingGame.titleId = 0; selfPlayingGame.ukn08 = 0; } return WriteValueOutput<GameKey>(vecOut, selfPlayingGame); } nnResult CallHandler_GetFriendAccountId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 2 || numVecOut != 1) return FPResult_InvalidIPCParam; // todo - online check DeclareInput(count, uint32be, 1); DeclareInputPtr(pidList, uint32be, count, 0); DeclareOutputPtr(accountId, CafeString<ACT_ACCOUNTID_LENGTH>, count, 0); memset(accountId, 0, ACT_ACCOUNTID_LENGTH * count); if (g_fpd.nexFriendSession) { for (uint32 i = 0; i < count; i++) { const uint32 pid = pidList[i]; auto& nnidOutput = accountId[i]; nexFriend frd; nexFriendRequest frdReq; if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) { nnidOutput.assign(frd.nnaInfo.principalInfo.nnid); continue; } bool incoming = false; if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) { nnidOutput.assign(frdReq.principalInfo.nnid); continue; } cemuLog_log(LogType::Force, "GetFriendAccountId: PID {} not found", pid); } } return FPResult_Ok; } nnResult CallHandler_GetFriendScreenName(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { static_assert(sizeof(CafeWideString<ACT_NICKNAME_SIZE>) == 11*2); if(numVecIn != 3 || numVecOut != 2) return FPResult_InvalidIPCParam; DeclareInput(count, uint32be, 1); DeclareInputPtr(pidList, uint32be, count, 0); DeclareInput(replaceNonAscii, uint8be, 2); DeclareOutputPtr(nameList, CafeWideString<ACT_NICKNAME_SIZE>, count, 0); uint8be* languageList = nullptr; if(vecOut[1].size > 0) // languageList is optional { DeclareOutputPtr(_languageList, uint8be, count, 1); languageList = _languageList; } memset(nameList, 0, ACT_NICKNAME_SIZE * sizeof(CafeWideString<ACT_NICKNAME_SIZE>)); if (g_fpd.nexFriendSession) { for (uint32 i = 0; i < count; i++) { const uint32 pid = pidList[i]; CafeWideString<ACT_NICKNAME_SIZE>& screennameOutput = nameList[i]; if (languageList) languageList[i] = 0; // unknown nexFriend frd; nexFriendRequest frdReq; if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) { screennameOutput.assignFromUTF8(frd.nnaInfo.principalInfo.mii.miiNickname); if (languageList) languageList[i] = frd.nnaInfo.principalInfo.regionGuessed; continue; } bool incoming = false; if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) { screennameOutput.assignFromUTF8(frdReq.principalInfo.mii.miiNickname); if (languageList) languageList[i] = frdReq.principalInfo.regionGuessed; continue; } cemuLog_log(LogType::Force, "GetFriendScreenName: PID {} not found", pid); } } return FPResult_Ok; } nnResult CallHandler_GetFriendMii(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 2 || numVecOut != 1) return FPResult_InvalidIPCParam; DeclareInput(count, uint32be, 1); DeclareInputPtr(pidList, uint32be, count, 0); DeclareOutputPtr(miiList, FFLData_t, count, 0); memset(miiList, 0, sizeof(FFLData_t) * count); if (g_fpd.nexFriendSession) { for (uint32 i = 0; i < count; i++) { const uint32 pid = pidList[i]; FFLData_t& miiOutput = miiList[i]; nexFriend frd; nexFriendRequest frdReq; if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) { memcpy(&miiOutput, frd.nnaInfo.principalInfo.mii.miiData, FFL_SIZE); continue; } bool incoming = false; if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) { memcpy(&miiOutput, frdReq.principalInfo.mii.miiData, FFL_SIZE); continue; } } } return FPResult_Ok; } nnResult CallHandler_GetFriendPresence(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 2 || numVecOut != 1) return FPResult_InvalidIPCParam; DeclareInput(count, uint32be, 1); DeclareInputPtr(pidList, uint32be, count, 0); DeclareOutputPtr(presenceList, FriendPresence, count, 0); memset(presenceList, 0, sizeof(FriendPresence) * count); if (g_fpd.nexFriendSession) { for (uint32 i = 0; i < count; i++) { FriendPresence& presenceOutput = presenceList[i]; const uint32 pid = pidList[i]; nexFriend frd; if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) { presenceOutput.isOnline = frd.presence.isOnline ? 1 : 0; presenceOutput.isValid = 1; // todo - region and subregion presenceOutput.gameMode.joinFlagMask = frd.presence.joinFlagMask; presenceOutput.gameMode.matchmakeType = frd.presence.joinAvailability; presenceOutput.gameMode.joinGameId = frd.presence.gameId; presenceOutput.gameMode.joinGameMode = frd.presence.gameMode; presenceOutput.gameMode.hostPid = frd.presence.hostPid; presenceOutput.gameMode.groupId = frd.presence.groupId; memcpy(presenceOutput.gameMode.appSpecificData, frd.presence.appSpecificData, 0x14); } else { cemuLog_log(LogType::Force, "GetFriendPresence: PID {} not found", pid); } } } return FPResult_Ok; } nnResult CallHandler_GetFriendRelationship(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if(numVecIn != 2 || numVecOut != 1) return FPResult_InvalidIPCParam; // todo - check for valid session (same for all GetFriend* functions) DeclareInput(count, uint32be, 1); DeclareInputPtr(pidList, uint32be, count, 0); DeclareOutputPtr(relationshipList, uint8be, count, 0); // correct? for(uint32 i=0; i<count; i++) relationshipList[i] = RELATIONSHIP_INVALID; if (g_fpd.nexFriendSession) { for (uint32 i = 0; i < count; i++) { const uint32 pid = pidList[i]; uint8be& relationshipOutput = relationshipList[i]; nexFriend frd; nexFriendRequest frdReq; bool incoming; if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) { relationshipOutput = RELATIONSHIP_FRIEND; continue; } else if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) { if (incoming) relationshipOutput = RELATIONSHIP_FRIENDREQUEST_IN; else relationshipOutput = RELATIONSHIP_FRIENDREQUEST_OUT; } } } return FPResult_Ok; } nnResult CallHandler_GetFriendList_GetFriendListAll(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut, bool isAll) { std::unique_lock _l(g_fpd.mtxFriendSession); if(numVecIn != 2 || numVecOut != 2) return FPResult_InvalidIPCParam; DeclareInput(startIndex, uint32be, 0); DeclareInput(maxCount, uint32be, 1); if (maxCount * sizeof(FriendPID) != vecOut[0].size || vecOut[0].basePhys.IsNull()) { cemuLog_log(LogType::Force, "GetFriendListAll: pid list buffer size is incorrect"); return FPResult_InvalidIPCParam; } if (!g_fpd.nexFriendSession) return WriteValueOutput<uint32be>(vecOut+1, 0); betype<FriendPID>* pidList = (betype<FriendPID>*)vecOut[0].basePhys.GetPtr(); std::vector<FriendPID> temporaryPidList; temporaryPidList.resize(std::min<size_t>(maxCount, 500)); uint32 pidCount = 0; g_fpd.nexFriendSession->getFriendPIDs(temporaryPidList.data(), &pidCount, startIndex, temporaryPidList.size(), isAll); std::copy(temporaryPidList.begin(), temporaryPidList.begin() + pidCount, pidList); return WriteValueOutput<uint32be>(vecOut+1, pidCount); } nnResult CallHandler_GetFriendRequestList(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if(numVecIn != 2 || numVecOut != 2) return FPResult_InvalidIPCParam; DeclareInput(startIndex, uint32be, 0); DeclareInput(maxCount, uint32be, 1); if(maxCount * sizeof(FriendPID) != vecOut[0].size || vecOut[0].basePhys.IsNull()) { cemuLog_log(LogType::Force, "GetFriendRequestList: pid list buffer size is incorrect"); return FPResult_InvalidIPCParam; } if (!g_fpd.nexFriendSession) return WriteValueOutput<uint32be>(vecOut+1, 0); betype<FriendPID>* pidList = (betype<FriendPID>*)vecOut[0].basePhys.GetPtr(); std::vector<FriendPID> temporaryPidList; temporaryPidList.resize(std::min<size_t>(maxCount, 500)); uint32 pidCount = 0; g_fpd.nexFriendSession->getFriendRequestPIDs(temporaryPidList.data(), &pidCount, startIndex, temporaryPidList.size(), true, false); std::copy(temporaryPidList.begin(), temporaryPidList.begin() + pidCount, pidList); return WriteValueOutput<uint32be>(vecOut+1, pidCount); } nnResult CallHandler_GetFriendRequestListEx(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if(numVecIn != 2 || numVecOut != 1) return FPResult_InvalidIPCParam; DeclareInput(count, uint32be, 1); DeclareInputPtr(pidList, uint32be, count, 0); DeclareOutputPtr(friendRequests, FriendRequest, count, 0); memset(friendRequests, 0, sizeof(FriendRequest) * count); if (!g_fpd.nexFriendSession) return FPResult_Ok; for(uint32 i=0; i<count; i++) { nexFriendRequest frdReq; bool incoming = false; if (!g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pidList[i])) { cemuLog_log(LogType::Force, "GetFriendRequestListEx: Failed to get friend request"); return FPResult_RequestFailed; } NexFriendRequestToFPDFriendRequest(&frdReq, incoming, friendRequests + i); } return FPResult_Ok; } nnResult CallHandler_GetBlackList(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if(numVecIn != 2 || numVecOut != 2) return FPResult_InvalidIPCParam; DeclareInput(startIndex, uint32be, 0); DeclareInput(maxCount, uint32be, 1); if(maxCount * sizeof(FriendPID) != vecOut[0].size) { cemuLog_log(LogType::Force, "GetBlackList: pid list buffer size is incorrect"); return FPResult_InvalidIPCParam; } if (!g_fpd.nexFriendSession) return WriteValueOutput<uint32be>(vecOut+1, 0); betype<FriendPID>* pidList = (betype<FriendPID>*)vecOut[0].basePhys.GetPtr(); // todo! cemuLog_logDebug(LogType::Force, "GetBlackList is todo"); uint32 countOut = 0; return WriteValueOutput<uint32be>(vecOut+1, countOut); } nnResult CallHandler_GetFriendListEx(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if(numVecIn != 2 || numVecOut != 1) return FPResult_InvalidIPCParam; DeclareInput(count, uint32be, 1); DeclareInputPtr(pidList, betype<FriendPID>, count, 0); if(count * sizeof(FriendPID) != vecIn[0].size) { cemuLog_log(LogType::Force, "GetFriendListEx: pid input list buffer size is incorrect"); return FPResult_InvalidIPCParam; } if(count * sizeof(FriendData) != vecOut[0].size) { cemuLog_log(LogType::Force, "GetFriendListEx: Friend output list buffer size is incorrect"); return FPResult_InvalidIPCParam; } FriendData* friendOutput = (FriendData*)vecOut[0].basePhys.GetPtr(); memset(friendOutput, 0, sizeof(FriendData) * count); if (g_fpd.nexFriendSession) { for (uint32 i = 0; i < count; i++) { uint32 pid = pidList[i]; FriendData* friendData = friendOutput + i; nexFriend frd; nexFriendRequest frdReq; if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) { NexFriendToFPDFriendData(&frd, friendData); continue; } bool incoming = false; if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) { NexFriendRequestToFPDFriendData(&frdReq, incoming, friendData); continue; } cemuLog_logDebug(LogType::Force, "GetFriendListEx: Failed to find friend or request with pid {}", pid); memset(friendData, 0, sizeof(FriendData)); } } return FPResult_Ok; } static void NexBasicInfoToBasicInfo(const nexPrincipalBasicInfo& nexBasicInfo, FriendBasicInfo& basicInfo) { memset(&basicInfo, 0, sizeof(FriendBasicInfo)); basicInfo.pid = nexBasicInfo.principalId; strcpy(basicInfo.nnid, nexBasicInfo.nnid); convertMultiByteStringToBigEndianWidechar(nexBasicInfo.mii.miiNickname, basicInfo.screenname, sizeof(basicInfo.screenname) / sizeof(uint16be)); memcpy(basicInfo.miiData, nexBasicInfo.mii.miiData, FFL_SIZE); basicInfo.uknDate90.day = 1; basicInfo.uknDate90.month = 1; basicInfo.uknDate90.hour = 1; basicInfo.uknDate90.minute = 1; basicInfo.uknDate90.second = 1; // unknown values not set: // ukn15 // ukn2E // ukn2F } nnResult CallHandler_GetBasicInfoAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if (numVecIn != 2 || numVecOut != 1) return FPResult_InvalidIPCParam; DeclareInput(count, uint32be, 1); DeclareInputPtr(pidListBE, betype<FriendPID>, count, 0); DeclareOutputPtr(basicInfoList, FriendBasicInfo, count, 0); if (!g_fpd.nexFriendSession) { memset(basicInfoList, 0, sizeof(FriendBasicInfo) * sizeof(count)); return FPResult_Ok; } IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); std::vector<uint32> pidList; std::copy(pidListBE, pidListBE + count, std::back_inserter(pidList)); g_fpd.nexFriendSession->requestPrincipleBaseInfoByPID(pidList.data(), count, [cmd, basicInfoList, count](NexFriends::RpcErrorCode result, std::span<nexPrincipalBasicInfo> basicInfo) -> void { if (result != NexFriends::ERR_NONE) return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); cemu_assert_debug(basicInfo.size() == count); for(uint32 i = 0; i < count; i++) NexBasicInfoToBasicInfo(basicInfo[i], basicInfoList[i]); ServiceCallAsyncRespond(cmd, FPResult_Ok); }); return FPResult_Ok; } nnResult CallHandler_UpdateCommentAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if (numVecIn != 1 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; uint32 messageLength = vecIn[0].size / sizeof(uint16be); DeclareInputPtr(newComment, uint16be, messageLength, 0); if (messageLength == 0 || newComment[messageLength-1] != 0) { cemuLog_log(LogType::Force, "UpdateCommentAsync: Message must contain at least a null-termination character"); return FPResult_InvalidIPCParam; } IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); auto utf8_comment = StringHelpers::ToUtf8(newComment, messageLength); nexComment temporaryComment; temporaryComment.ukn0 = 0; temporaryComment.commentString = utf8_comment; temporaryComment.ukn1 = 0; g_fpd.nexFriendSession->updateCommentAsync(temporaryComment, [cmd](NexFriends::RpcErrorCode result) { if (result != NexFriends::ERR_NONE) return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); ServiceCallAsyncRespond(cmd, FPResult_Ok); }); return FPResult_Ok; } nnResult CallHandler_UpdatePreferenceAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if (numVecIn != 1 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInputPtr(newPreference, FPDPreference, 1, 0); IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); g_fpd.nexFriendSession->updatePreferencesAsync(nexPrincipalPreference(newPreference->showOnline != 0 ? 1 : 0, newPreference->showGame != 0 ? 1 : 0, newPreference->blockFriendRequests != 0 ? 1 : 0), [cmd](NexFriends::RpcErrorCode result){ if (result != NexFriends::ERR_NONE) return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); ServiceCallAsyncRespond(cmd, FPResult_Ok); }); return FPResult_Ok; } nnResult CallHandler_AddFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if (numVecIn != 2 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInputPtr(playRecord, RecentPlayRecordEx, 1, 0); uint32 msgLength = vecIn[1].size/sizeof(uint16be); DeclareInputPtr(msgBE, uint16be, msgLength, 1); if(msgLength == 0 || msgBE[msgLength-1] != 0) { cemuLog_log(LogType::Force, "AddFriendRequestAsync: Message must contain at least a null-termination character and end with one"); return FPResult_InvalidIPCParam; } std::string msg = StringHelpers::ToUtf8({ msgBE, msgLength-1 }); IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); g_fpd.nexFriendSession->addFriendRequest(playRecord->pid, msg.data(), [cmd](NexFriends::RpcErrorCode result){ if (result != NexFriends::ERR_NONE) return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); ServiceCallAsyncRespond(cmd, FPResult_Ok); }); return FPResult_Ok; } nnResult CallHandler_AcceptFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if (numVecIn != 1 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInput(requestId, uint64be, 0); nexFriendRequest frq; bool isIncoming; if (!g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, requestId)) return FPResult_RequestFailed; if(!isIncoming) { cemuLog_log(LogType::Force, "AcceptFriendRequestAsync: Trying to accept outgoing friend request"); return FPResult_RequestFailed; } IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); g_fpd.nexFriendSession->acceptFriendRequest(requestId, [cmd](NexFriends::RpcErrorCode result){ if (result != NexFriends::ERR_NONE) return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); return ServiceCallAsyncRespond(cmd, FPResult_Ok); }); return FPResult_Ok; } nnResult CallHandler_DeleteFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { // reject incoming friend request std::unique_lock _l(g_fpd.mtxFriendSession); if (numVecIn != 1 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInput(requestId, uint64be, 0); nexFriendRequest frq; bool isIncoming; if (!g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, requestId)) return FPResult_RequestFailed; if(!isIncoming) { cemuLog_log(LogType::Force, "CancelFriendRequestAsync: Trying to block outgoing friend request"); return FPResult_RequestFailed; } IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); g_fpd.nexFriendSession->deleteFriendRequest(requestId, [cmd](NexFriends::RpcErrorCode result){ if (result != NexFriends::ERR_NONE) return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); return ServiceCallAsyncRespond(cmd, FPResult_Ok); }); return FPResult_Ok; } nnResult CallHandler_CancelFriendRequestAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { // retract outgoing friend request std::unique_lock _l(g_fpd.mtxFriendSession); if (numVecIn != 1 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInput(requestId, uint64be, 0); nexFriendRequest frq; bool isIncoming; if (!g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, requestId)) return FPResult_RequestFailed; if(isIncoming) { cemuLog_log(LogType::Force, "CancelFriendRequestAsync: Trying to cancel incoming friend request"); return FPResult_RequestFailed; } IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); g_fpd.nexFriendSession->removeFriend(frq.principalInfo.principalId, [cmd](NexFriends::RpcErrorCode result){ if (result != NexFriends::ERR_NONE) return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); return ServiceCallAsyncRespond(cmd, FPResult_Ok); }); return FPResult_Ok; } nnResult CallHandler_MarkFriendRequestsAsReceivedAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if (numVecIn != 2 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInput(count, uint32be, 1); DeclareInputPtr(requestIdsBE, uint64be, count, 0); IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); // endian convert std::vector<uint64> requestIds; std::copy(requestIdsBE, requestIdsBE + count, std::back_inserter(requestIds)); g_fpd.nexFriendSession->markFriendRequestsAsReceived(requestIds.data(), requestIds.size(), [cmd](NexFriends::RpcErrorCode result){ if (result != NexFriends::ERR_NONE) return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); return ServiceCallAsyncRespond(cmd, FPResult_Ok); }); return FPResult_Ok; } nnResult CallHandler_RemoveFriendAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if (numVecIn != 1 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInput(pid, uint32be, 0); IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); g_fpd.nexFriendSession->removeFriend(pid, [cmd](NexFriends::RpcErrorCode result){ if (result != NexFriends::ERR_NONE) return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); return ServiceCallAsyncRespond(cmd, FPResult_Ok); }); return FPResult_Ok; } nnResult CallHandler_DeleteFriendFlagsAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); if (numVecIn != 3 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInput(pid, uint32be, 0); cemuLog_logDebug(LogType::Force, "DeleteFriendFlagsAsync is todo"); return FPResult_Ok; } nnResult CallHandler_CheckSettingStatusAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if (numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; if (vecOut[0].size != sizeof(uint8be)) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); // for now we respond immediately uint8 settingsStatus = 1; // todo - figure out what this status means auto r = WriteValueOutput<uint8be>(vecOut, settingsStatus); ServiceCallAsyncRespond(cmd, r); cemuLog_log(LogType::Force, "CheckSettingStatusAsync is todo"); return FPResult_Ok; } nnResult CallHandler_IsPreferenceValid(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if (numVecIn != 0 || numVecOut != 1) return 0; if (!g_fpd.nexFriendSession) return 0; // we currently automatically put the preferences into a valid state on session creation if they are not set yet return WriteValueOutput<uint32be>(vecOut, 1); // if we return 0, the friend app will show the first time setup screen } nnResult CallHandler_GetRequestBlockSettingAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) // todo { if (numVecIn != 2 || numVecOut != 1) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInput(count, uint32be, 1); DeclareInputPtr(pidList, betype<FriendPID>, count, 0); DeclareOutputPtr(settingList, uint8be, count, 0); cemuLog_log(LogType::Force, "GetRequestBlockSettingAsync is todo"); for (uint32 i = 0; i < count; i++) settingList[i] = 0; // implementation is todo. Used by friend list app when adding a friend // 0 means not blocked. Friend app will continue with GetBasicInformation() // 1 means blocked. Friend app will continue with AddFriendAsync to add the user as a provisional friend return FPResult_Ok; } nnResult CallHandler_AddFriendAsyncByPid(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if (numVecIn != 1 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInput(pid, uint32be, 0); cemuLog_log(LogType::Force, "AddFriendAsyncByPid is todo"); return FPResult_Ok; } nnResult CallHandler_UpdateGameMode(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { if (numVecIn != 2 || numVecOut != 0) return FPResult_InvalidIPCParam; if (!g_fpd.nexFriendSession) return FPResult_RequestFailed; DeclareInputPtr(gameMode, iosu::fpd::GameMode, 1, 0); uint32 messageLength = vecIn[1].size / sizeof(uint16be); if(messageLength == 0 || (vecIn[1].size%sizeof(uint16be)) != 0) { cemuLog_log(LogType::Force, "UpdateGameMode: Message must contain at least a null-termination character"); return FPResult_InvalidIPCParam; } DeclareInputPtr(gameModeMessage, uint16be, messageLength, 1); messageLength--; GameModeToNexPresence(gameMode, &g_fpd.myPresence); g_fpd.nexFriendSession->updateMyPresence(g_fpd.myPresence); // todo - message return FPResult_Ok; } void StartFriendSession() { bool expected = false; if (!g_fpd.sessionStarted.compare_exchange_strong(expected, true)) return; cemu_assert(!g_fpd.nexFriendSession); NAPI::AuthInfo authInfo; NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, 0x0005001010001C00, 0x0000, 0x00003200); if (!nexTokenResult.isValid()) { cemuLog_logDebug(LogType::Force, "IOSU_FPD: Failed to acquire nex token for friend server"); g_fpd.myPresence.isOnline = 0; return; } // get values needed for friend session uint32 myPid; uint8 currentSlot = iosu::act::getCurrentAccountSlot(); iosu::act::getPrincipalId(currentSlot, &myPid); char accountId[256] = { 0 }; iosu::act::getAccountId(currentSlot, accountId); FFLData_t miiData; act::getMii(currentSlot, &miiData); uint16 screenName[ACT_NICKNAME_LENGTH + 1] = { 0 }; act::getScreenname(currentSlot, screenName); uint32 countryCode = 0; act::getCountryIndex(currentSlot, &countryCode); // init presence g_fpd.myPresence.isOnline = 1; if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) == 0x00050000) { g_fpd.myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId(); g_fpd.myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion(); } else { g_fpd.myPresence.gameKey.titleId = 0; // icon will not be ??? or invalid to others g_fpd.myPresence.gameKey.ukn = 0; } // resolve potential domain to IP address struct addrinfo hints = {0}, *addrs; hints.ai_family = AF_INET; const int status = getaddrinfo(nexTokenResult.nexToken.host, NULL, &hints, &addrs); if (status != 0) { cemuLog_log(LogType::Force, "IOSU_FPD: Failed to resolve hostname {}", nexTokenResult.nexToken.host); return; } char addrstr[NI_MAXHOST]; getnameinfo(addrs->ai_addr, addrs->ai_addrlen, addrstr, sizeof addrstr, NULL, 0, NI_NUMERICHOST); cemuLog_log(LogType::Force, "IOSU_FPD: Resolved IP for hostname {}, {}", nexTokenResult.nexToken.host, addrstr); // start session const uint32_t hostIp = ((struct sockaddr_in*)addrs->ai_addr)->sin_addr.s_addr; freeaddrinfo(addrs); g_fpd.mtxFriendSession.lock(); g_fpd.nexFriendSession = new NexFriends(hostIp, nexTokenResult.nexToken.port, "ridfebb9", myPid, nexTokenResult.nexToken.nexPassword, nexTokenResult.nexToken.token, accountId, (uint8*)&miiData, (wchar_t*)screenName, (uint8)countryCode, g_fpd.myPresence); g_fpd.nexFriendSession->setNotificationHandler(NotificationHandler); g_fpd.mtxFriendSession.unlock(); cemuLog_log(LogType::Force, "IOSU_FPD: Created friend server session"); } void StopFriendSession() { std::unique_lock _l(g_fpd.mtxFriendSession); bool expected = true; if (!g_fpd.sessionStarted.compare_exchange_strong(expected, false) ) return; delete g_fpd.nexFriendSession; g_fpd.nexFriendSession = nullptr; } private: }; FPDService gFPDService; class : public ::IOSUModule { void TitleStart() override { gFPDService.Start(); gFPDService.SetTimerUpdate(1000); // call TimerUpdate() once a second } void TitleStop() override { gFPDService.StopFriendSession(); gFPDService.Stop(); } }sIOSUModuleNNFPD; IOSUModule* GetModule() { return static_cast<IOSUModule*>(&sIOSUModuleNNFPD); } } } ================================================ FILE: src/Cafe/IOSU/legacy/iosu_fpd.h ================================================ #pragma once #include "Cafe/IOSU/iosu_types_common.h" #include "Common/CafeString.h" namespace iosu { namespace fpd { struct FPDDate { /* +0x0 */ uint16be year; /* +0x2 */ uint8 month; /* +0x3 */ uint8 day; /* +0x4 */ uint8 hour; /* +0x5 */ uint8 minute; /* +0x6 */ uint8 second; /* +0x7 */ uint8 padding; }; static_assert(sizeof(FPDDate) == 8); struct RecentPlayRecordEx { /* +0x00 */ uint32be pid; /* +0x04 */ uint8 ukn04; /* +0x05 */ uint8 ukn05; /* +0x06 */ uint8 ukn06[0x22]; /* +0x28 */ uint8 ukn28[0x22]; /* +0x4A */ uint8 _uknOrPadding4A[6]; /* +0x50 */ uint32be ukn50; /* +0x54 */ uint32be ukn54; /* +0x58 */ uint16be ukn58; /* +0x5C */ uint8 _padding5C[4]; /* +0x60 */ iosu::fpd::FPDDate date; }; static_assert(sizeof(RecentPlayRecordEx) == 0x68, ""); static_assert(offsetof(RecentPlayRecordEx, ukn06) == 0x06, ""); static_assert(offsetof(RecentPlayRecordEx, ukn50) == 0x50, ""); struct GameKey { /* +0x00 */ uint64be titleId; /* +0x08 */ uint16be ukn08; /* +0x0A */ uint8 _padding0A[6]; }; static_assert(sizeof(GameKey) == 0x10); struct Profile { uint8be region; uint8be regionSubcode; uint8be platform; uint8be ukn3; }; static_assert(sizeof(Profile) == 0x4); struct GameMode { /* +0x00 */ uint32be joinFlagMask; /* +0x04 */ uint32be matchmakeType; /* +0x08 */ uint32be joinGameId; /* +0x0C */ uint32be joinGameMode; /* +0x10 */ uint32be hostPid; /* +0x14 */ uint32be groupId; /* +0x18 */ uint8 appSpecificData[0x14]; }; static_assert(sizeof(GameMode) == 0x2C); struct FriendData { /* +0x000 */ uint8 type; // type (Non-Zero -> Friend, 0 -> Friend request ? ) /* +0x001 */ uint8 _padding1[7]; /* +0x008 */ uint32be pid; /* +0x00C */ char nnid[0x10 + 1]; /* +0x01D */ uint8 ukn01D; /* +0x01E */ uint8 _padding1E[2]; /* +0x020 */ uint16be screenname[10 + 1]; /* +0x036 */ uint8 ukn036; /* +0x037 */ uint8 _padding037; /* +0x038 */ uint8 mii[0x60]; /* +0x098 */ FPDDate uknDate; // sub struct (the part above seems to be shared with friend requests) union { struct { /* +0x0A0 */ Profile profile; // this is returned for nn_fp.GetFriendProfile /* +0x0A4 */ uint32be ukn0A4; /* +0x0A8 */ GameKey gameKey; /* +0x0B8 */ GameMode gameMode; /* +0x0E4 */ CafeWideString<0x82> gameModeDescription; /* +0x1E8 */ Profile profile1E8; // how does it differ from the one at 0xA0? Returned by GetFriendPresence /* +0x1EC */ uint8 isOnline; /* +0x1ED */ uint8 _padding1ED[3]; // some other sub struct? /* +0x1F0 */ CafeWideString<0x12> comment; // pops up every few seconds in friend list /* +0x214 */ uint32be _padding214; /* +0x218 */ FPDDate approvalTime; /* +0x220 */ FPDDate lastOnline; }friendExtraData; struct { /* +0x0A0 */ uint64be messageId; // guessed. If 0, then relationship is FRIENDSHIP_REQUEST_OUT, otherwise FRIENDSHIP_REQUEST_IN /* +0x0A8 */ uint8 ukn0A8; /* +0x0A9 */ uint8 ukn0A9; // comment language? (guessed) /* +0x0AA */ uint16be comment[0x40]; /* +0x12A */ uint8 ukn12A; // ingame name language? (guessed) /* +0x12B */ uint8 _padding12B; /* +0x12C */ uint16be uknMessage[18]; // exact length unknown, could be shorter (ingame screen name?) /* +0x150 */ uint64 gameKeyTitleId; /* +0x158 */ uint16be gameKeyUkn; /* +0x15A */ uint8 _padding[6]; /* +0x160 */ FPDDate uknData0; /* +0x168 */ FPDDate uknData1; }requestExtraData; }; }; static_assert(sizeof(FriendData) == 0x228); static_assert(offsetof(FriendData, friendExtraData.gameKey) == 0x0A8); static_assert(offsetof(FriendData, friendExtraData.gameModeDescription) == 0x0E4); static_assert(offsetof(FriendData, friendExtraData.comment) == 0x1F0); static_assert(offsetof(FriendData, requestExtraData.messageId) == 0x0A0); static_assert(offsetof(FriendData, requestExtraData.comment) == 0x0AA); static_assert(offsetof(FriendData, requestExtraData.uknMessage) == 0x12C); static_assert(offsetof(FriendData, requestExtraData.gameKeyTitleId) == 0x150); static_assert(offsetof(FriendData, requestExtraData.uknData1) == 0x168); struct FriendBasicInfo { /* +0x00 */ uint32be pid; /* +0x04 */ char nnid[0x11]; /* +0x15 */ uint8 ukn15; /* +0x16 */ uint8 uknOrPadding[2]; /* +0x18 */ uint16be screenname[11]; /* +0x2E */ uint8 ukn2E; // bool option /* +0x2F */ uint8 ukn2F; /* +0x30 */ uint8 miiData[0x60]; /* +0x90 */ FPDDate uknDate90; }; static_assert(sizeof(FriendBasicInfo) == 0x98); static_assert(offsetof(FriendBasicInfo, nnid) == 0x04); static_assert(offsetof(FriendBasicInfo, ukn15) == 0x15); static_assert(offsetof(FriendBasicInfo, screenname) == 0x18); static_assert(offsetof(FriendBasicInfo, ukn2E) == 0x2E); static_assert(offsetof(FriendBasicInfo, miiData) == 0x30); struct FriendRequest { /* +0x000 */ uint32be pid; /* +0x004 */ uint8 nnid[17]; // guessed type /* +0x015 */ uint8 _uknOrPadding15; /* +0x016 */ uint8 _uknOrPadding16; /* +0x017 */ uint8 _uknOrPadding17; /* +0x018 */ uint16be screenname[11]; /* +0x02E */ uint8 ukn2E; // bool option /* +0x02F */ uint8 ukn2F; // ukn /* +0x030 */ uint8 miiData[0x60]; /* +0x090 */ FPDDate uknDate; /* +0x098 */ uint64 ukn98; /* +0x0A0 */ uint8 isMarkedAsReceived; /* +0x0A1 */ uint8 uknA1; /* +0x0A2 */ uint16be message[0x40]; /* +0x122 */ uint8 ukn122; /* +0x123 */ uint8 _padding123; /* +0x124 */ uint16be uknString2[18]; /* +0x148 */ uint64 gameKeyTitleId; /* +0x150 */ uint16be gameKeyUkn; /* +0x152 */ uint8 _padding152[6]; /* +0x158 */ FPDDate uknDate2; /* +0x160 */ FPDDate expireDate; }; static_assert(sizeof(FriendRequest) == 0x168); static_assert(offsetof(FriendRequest, uknDate) == 0x090); static_assert(offsetof(FriendRequest, message) == 0x0A2); static_assert(offsetof(FriendRequest, uknString2) == 0x124); static_assert(offsetof(FriendRequest, gameKeyTitleId) == 0x148); struct FriendPresence { GameMode gameMode; /* +0x2C */ Profile profile; /* +0x30 */ uint8 isOnline; /* +0x31 */ uint8 isValid; /* +0x32 */ uint8 padding[2]; // guessed }; static_assert(sizeof(FriendPresence) == 0x34); static_assert(offsetof(FriendPresence, isOnline) == 0x30); struct FPDNotification { betype<uint32> type; betype<uint32> pid; }; static_assert(sizeof(FPDNotification) == 8); struct FPDPreference { uint8be showOnline; // show online status to others uint8be showGame; // show played game to others uint8be blockFriendRequests; // block friend requests uint8be ukn; // probably padding? }; static_assert(sizeof(FPDPreference) == 4); static const int RELATIONSHIP_INVALID = 0; static const int RELATIONSHIP_FRIENDREQUEST_OUT = 1; static const int RELATIONSHIP_FRIENDREQUEST_IN = 2; static const int RELATIONSHIP_FRIEND = 3; static const int GAMEMODE_MAX_MESSAGE_LENGTH = 0x80; // limit includes null-terminator character, so only 0x7F actual characters can be used static const int MY_COMMENT_LENGTH = 0x12; enum class FPD_REQUEST_ID { LoginAsync = 0x2775, HasLoggedIn = 0x2777, IsOnline = 0x2778, GetMyPrincipalId = 0x27D9, GetMyAccountId = 0x27DA, GetMyScreenName = 0x27DB, GetMyMii = 0x27DC, GetMyProfile = 0x27DD, GetMyPreference = 0x27DE, GetMyPresence = 0x27DF, IsPreferenceValid = 0x27E0, GetFriendList = 0x283D, GetFriendListAll = 0x283E, GetFriendAccountId = 0x283F, GetFriendScreenName = 0x2840, GetFriendPresence = 0x2845, GetFriendRelationship = 0x2846, GetFriendMii = 0x2841, GetBlackList = 0x28A1, GetFriendRequestList = 0x2905, UpdateGameModeVariation1 = 0x2969, // there seem to be two different requestIds for the 2-param and 3-param version of UpdateGameMode, UpdateGameModeVariation2 = 0x296A, // but the third parameter is never used and the same handler is used for both AddFriendAsyncByPid = 0x29CD, AddFriendAsyncByXXX = 0x29CE, // probably by name? GetRequestBlockSettingAsync = 0x2B5D, GetMyComment = 0x4EE9, GetMyPlayingGame = 0x4EEA, CheckSettingStatusAsync = 0x7596, GetFriendListEx = 0x75F9, GetFriendRequestListEx = 0x76C1, UpdateCommentAsync = 0x7726, UpdatePreferenceAsync = 0x7727, RemoveFriendAsync = 0x7789, DeleteFriendFlagsAsync = 0x778A, AddFriendRequestByPlayRecordAsync = 0x778B, CancelFriendRequestAsync = 0x778C, AcceptFriendRequestAsync = 0x7851, DeleteFriendRequestAsync = 0x7852, MarkFriendRequestsAsReceivedAsync = 0x7854, GetBasicInfoAsync = 0x7919, SetLedEventMask = 0x9D0B, SetNotificationMask = 0x15ff5, GetNotificationAsync = 0x15FF6, }; using FriendPID = uint32; IOSUModule* GetModule(); } } ================================================ FILE: src/Cafe/IOSU/legacy/iosu_ioctl.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "iosu_ioctl.h" #include "util/helpers/ringbuffer.h" #include "util/helpers/Semaphore.h" // deprecated IOCTL handling code RingBuffer<ioQueueEntry_t*, 256> _ioctlRingbuffer[IOS_DEVICE_COUNT]; CounterSemaphore _ioctlRingbufferSemaphore[IOS_DEVICE_COUNT]; std::mutex ioctlMutex; sint32 iosuIoctl_pushAndWait(uint32 ioctlHandle, ioQueueEntry_t* ioQueueEntry) { if (ioctlHandle != IOS_DEVICE_ACT && ioctlHandle != IOS_DEVICE_ACP_MAIN && ioctlHandle != IOS_DEVICE_MCP && ioctlHandle != IOS_DEVICE_BOSS && ioctlHandle != IOS_DEVICE_NIM && ioctlHandle != IOS_DEVICE_FPD) { cemuLog_logDebug(LogType::Force, "Unsupported IOSU device {}", ioctlHandle); cemu_assert_debug(false); return 0; } __OSLockScheduler(); ioctlMutex.lock(); ioQueueEntry->ppcThread = coreinit::OSGetCurrentThread(); _ioctlRingbuffer[ioctlHandle].Push(ioQueueEntry); ioctlMutex.unlock(); _ioctlRingbufferSemaphore[ioctlHandle].increment(); coreinit::__OSSuspendThreadInternal(coreinit::OSGetCurrentThread()); if (ioQueueEntry->isCompleted == false) assert_dbg(); __OSUnlockScheduler(); return ioQueueEntry->returnValue; } ioQueueEntry_t* iosuIoctl_getNextWithWait(uint32 deviceIndex) { _ioctlRingbufferSemaphore[deviceIndex].decrementWithWait(); if (_ioctlRingbuffer[deviceIndex].HasData() == false) assert_dbg(); return _ioctlRingbuffer[deviceIndex].Pop(); } ioQueueEntry_t* iosuIoctl_getNextWithTimeout(uint32 deviceIndex, sint32 ms) { if (!_ioctlRingbufferSemaphore[deviceIndex].decrementWithWaitAndTimeout(ms)) return nullptr; // timeout or spurious wake up if (_ioctlRingbuffer[deviceIndex].HasData() == false) return nullptr; return _ioctlRingbuffer[deviceIndex].Pop(); } void iosuIoctl_completeRequest(ioQueueEntry_t* ioQueueEntry, uint32 returnValue) { ioQueueEntry->returnValue = returnValue; ioQueueEntry->isCompleted = true; coreinit::OSResumeThread(ioQueueEntry->ppcThread); } void iosuIoctl_init() { for (sint32 i = 0; i < IOS_DEVICE_COUNT; i++) { _ioctlRingbuffer[i].Clear(); } } ================================================ FILE: src/Cafe/IOSU/legacy/iosu_ioctl.h ================================================ #pragma once struct OSThread_t; typedef struct _ioBufferVector_t { // the meaning of these values might be arbitrary? (up to the /dev/* handler to process them) //uint32 ukn00; MEMPTR<uint8> buffer; uint32be bufferSize; uint32 ukn08; //uint32 ukn0C; MEMPTR<uint8> unknownBuffer; }ioBufferVector_t; typedef struct { bool isIoctlv; // false -> ioctl, true -> ioctlv bool isAsync; uint32 request; uint32 countIn; uint32 countOut; MEMPTR<ioBufferVector_t> bufferVectors; // info about submitter OSThread_t* ppcThread; //MPTR ppcThread; // result bool isCompleted; uint32 returnValue; }ioQueueEntry_t; #define IOS_PATH_SOCKET "/dev/socket" #define IOS_PATH_ODM "/dev/odm" #define IOS_PATH_ACT "/dev/act" #define IOS_PATH_FPD "/dev/fpd" #define IOS_PATH_ACP_MAIN "/dev/acp_main" #define IOS_PATH_MCP "/dev/mcp" #define IOS_PATH_BOSS "/dev/boss" #define IOS_PATH_NIM "/dev/nim" #define IOS_PATH_IOSUHAX "/dev/iosuhax" #define IOS_DEVICE_UKN (0x1) #define IOS_DEVICE_ODM (0x2) #define IOS_DEVICE_SOCKET (0x3) #define IOS_DEVICE_ACT (0x4) #define IOS_DEVICE_FPD (0x5) #define IOS_DEVICE_ACP_MAIN (0x6) #define IOS_DEVICE_MCP (0x7) #define IOS_DEVICE_BOSS (0x8) #define IOS_DEVICE_NIM (0x9) #define IOS_DEVICE_COUNT 10 void iosuIoctl_init(); // for use by IOSU ioQueueEntry_t* iosuIoctl_getNextWithWait(uint32 deviceIndex); ioQueueEntry_t* iosuIoctl_getNextWithTimeout(uint32 deviceIndex, sint32 ms); void iosuIoctl_completeRequest(ioQueueEntry_t* ioQueueEntry, uint32 returnValue); // for use by PPC sint32 iosuIoctl_pushAndWait(uint32 ioctlHandle, ioQueueEntry_t* ioQueueEntry); ================================================ FILE: src/Cafe/IOSU/legacy/iosu_mcp.cpp ================================================ #include "iosu_ioctl.h" #include "iosu_mcp.h" #include "Cafe/OS/libs/nn_common.h" #include "util/tinyxml2/tinyxml2.h" #include "config/CemuConfig.h" #include "Cafe/TitleList/GameInfo.h" #include "util/helpers/helpers.h" #include "Cafe/TitleList/TitleList.h" #include "Cafe/CafeSystem.h" #include "Cafe/IOSU/iosu_ipc_common.h" #include "Cafe/IOSU/kernel/iosu_kernel.h" using namespace iosu::kernel; namespace iosu { struct { bool isInitialized; }iosuMcp = { 0 }; std::recursive_mutex sTitleInfoMutex; bool sHasAllTitlesMounted = false; uint32 mcpBuildTitleList(MCPTitleInfo* titleList, uint32 maxTitlesToCopy, std::function<bool(const TitleInfo&)> filterFunc) { CafeTitleList::WaitForMandatoryScan(); std::set<TitleId> titleIdsVisited; auto cafeTitleList = CafeTitleList::AcquireInternalList(); uint32 numTitlesCopied = 0; for (auto& it : cafeTitleList) { if (numTitlesCopied == maxTitlesToCopy) break; uint64 titleId = it->GetAppTitleId(); if (titleIdsVisited.find(titleId) != titleIdsVisited.end()) continue; if (!filterFunc(*it)) continue; titleIdsVisited.emplace(titleId); if (titleList) { auto& titleOut = titleList[numTitlesCopied]; titleOut.titleIdHigh = (uint32)(titleId >> 32); titleOut.titleIdLow = (uint32)(titleId & 0xFFFFFFFF); titleOut.titleVersion = it->GetAppTitleVersion(); titleOut.appGroup = it->GetAppGroup(); titleOut.appType = it->GetAppType(); std::string titlePath = CafeSystem::GetMlcStoragePath(titleId); strcpy(titleOut.appPath, titlePath.c_str()); strcpy((char*)titleOut.deviceName, "mlc"); titleOut.osVersion = 0; // todo titleOut.sdkVersion = it->GetAppSDKVersion(); } numTitlesCopied++; } CafeTitleList::ReleaseInternalList(); if (!sHasAllTitlesMounted) { CafeSystem::MlcStorageMountAllTitles(); sHasAllTitlesMounted = true; } return numTitlesCopied; } sint32 mcpGetTitleList(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount) { std::unique_lock _lock(sTitleInfoMutex); uint32 maxEntryCount = titleListBufferSize / sizeof(MCPTitleInfo); *titleCount = mcpBuildTitleList(titleList, maxEntryCount, [](const TitleInfo& titleInfo) -> bool { return true; }); return 0; } sint32 mcpGetTitleCount() { std::unique_lock _lock(sTitleInfoMutex); return mcpBuildTitleList(nullptr, 0xFFFFFFFF, [](const TitleInfo& titleInfo) -> bool { return true; }); } sint32 mcpGetTitleListByAppType(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount, uint32 appType) { std::unique_lock _lock(sTitleInfoMutex); uint32 maxEntryCount = titleListBufferSize / sizeof(MCPTitleInfo); *titleCount = mcpBuildTitleList(titleList, maxEntryCount, [appType](const TitleInfo& titleInfo) -> bool { return titleInfo.GetAppType() == appType; }); return 0; } sint32 mcpGetTitleListByTitleId(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount, uint64 titleId) { std::unique_lock _lock(sTitleInfoMutex); uint32 maxEntryCount = titleListBufferSize / sizeof(MCPTitleInfo); *titleCount = mcpBuildTitleList(titleList, maxEntryCount, [titleId](const TitleInfo& titleInfo) -> bool { return titleInfo.GetAppTitleId() == titleId; }); return 0; } int iosuMcp_thread() { SetThreadName("iosuMcp_thread"); while (true) { uint32 returnValue = 0; // Ioctl return value ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_MCP); if (ioQueueEntry->request == IOSU_MCP_REQUEST_CEMU) { iosuMcpCemuRequest_t* mcpCemuRequest = (iosuMcpCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_LIST) { uint32be titleCount = mcpCemuRequest->titleListRequest.titleCount; mcpCemuRequest->returnCode = mcpGetTitleList(mcpCemuRequest->titleListRequest.titleList.GetPtr(), mcpCemuRequest->titleListRequest.titleListBufferSize, &titleCount); mcpCemuRequest->titleListRequest.titleCount = titleCount; } else if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE) { uint32be titleCount = mcpCemuRequest->titleListRequest.titleCount; mcpCemuRequest->returnCode = mcpGetTitleListByAppType(mcpCemuRequest->titleListRequest.titleList.GetPtr(), mcpCemuRequest->titleListRequest.titleListBufferSize, &titleCount, mcpCemuRequest->titleListRequest.appType); mcpCemuRequest->titleListRequest.titleCount = titleCount; } else if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_INFO) { uint32be titleCount = mcpCemuRequest->titleListRequest.titleCount; mcpCemuRequest->returnCode = mcpGetTitleListByTitleId(mcpCemuRequest->titleListRequest.titleList.GetPtr(), mcpCemuRequest->titleListRequest.titleListBufferSize, &titleCount, mcpCemuRequest->titleListRequest.titleId); mcpCemuRequest->titleListRequest.titleCount = titleCount; } else if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_COUNT) { uint32be titleCount = mcpGetTitleCount(); mcpCemuRequest->returnCode = titleCount; mcpCemuRequest->titleListRequest.titleCount = titleCount; } else assert_dbg(); } else assert_dbg(); iosuIoctl_completeRequest(ioQueueEntry, returnValue); } return 0; } // deprecated void iosuMcp_init() { if (iosuMcp.isInitialized) return; std::thread t(iosuMcp_thread); t.detach(); iosuMcp.isInitialized = true; } namespace mcp { IOSMsgQueueId sMCPIoMsgQueue; SysAllocator<iosu::kernel::IOSMessage, 352> _m_sMCPIoMsgQueueMsgBuffer; std::thread sMCPIoThread; struct MCPClient { std::string workingDirectory; bool isAllocated{ false }; void AllocateAndInitialize() { isAllocated = true; workingDirectory = std::string("/"); } void ReleaseAndCleanup() { isAllocated = false; } }; std::array<MCPClient, 256> sMCPClientArray; IOS_ERROR MCPAllocateClient(sint32& indexOut) { for (size_t i = 0; i < sMCPClientArray.size(); i++) { if (sMCPClientArray[i].isAllocated) continue; sMCPClientArray[i].AllocateAndInitialize(); indexOut = (sint32)i; return IOS_ERROR_OK; } return IOS_ERROR::IOS_ERROR_MAXIMUM_REACHED; } void MCPIoThread() { SetThreadName("IOSU-MCP"); IOSMessage msg; while (true) { IOS_ERROR r = IOS_ReceiveMessage(sMCPIoMsgQueue, &msg, 0); cemu_assert(!IOS_ResultIsError(r)); if (msg == 0) return; // shutdown signaled IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); uint32 clientHandle = (uint32)cmd->devHandle; if (cmd->cmdId == IPCCommandId::IOS_OPEN) { sint32 clientIndex = 0; r = MCPAllocateClient(clientIndex); if (r != IOS_ERROR_OK) { IOS_ResourceReply(cmd, r); continue; } IOS_ResourceReply(cmd, (IOS_ERROR)clientIndex); continue; } else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) { cemu_assert(clientHandle < sMCPClientArray.size()); sMCPClientArray[clientHandle].ReleaseAndCleanup(); IOS_ResourceReply(cmd, IOS_ERROR_OK); continue; } else if (cmd->cmdId == IPCCommandId::IOS_IOCTL) { cemu_assert(clientHandle < sMCPClientArray.size()); cemu_assert(sMCPClientArray[clientHandle].isAllocated); IOS_ResourceReply(cmd, (IOS_ERROR)0x80000000); continue; } else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) { cemu_assert_unimplemented(); //uint32 requestId = cmd->args[0]; //uint32 numIn = cmd->args[1]; //uint32 numOut = cmd->args[2]; //IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr(); IOS_ResourceReply(cmd, IOS_ERROR_INVALID); continue; } else { cemuLog_log(LogType::Force, "/dev/mcp: Unsupported IPC cmdId"); cemu_assert_suspicious(); IOS_ResourceReply(cmd, IOS_ERROR_INVALID); } } } void Init() { for (auto& it : sMCPClientArray) it.ReleaseAndCleanup(); sMCPIoMsgQueue = (IOSMsgQueueId)IOS_CreateMessageQueue(_m_sMCPIoMsgQueueMsgBuffer.GetPtr(), _m_sMCPIoMsgQueueMsgBuffer.GetCount()); IOS_ERROR r = IOS_RegisterResourceManager("/dev/mcp", sMCPIoMsgQueue); IOS_DeviceAssociateId("/dev/mcp", 11); cemu_assert(!IOS_ResultIsError(r)); sMCPIoThread = std::thread(MCPIoThread); } void Shutdown() { IOS_SendMessage(sMCPIoMsgQueue, 0, 0); sMCPIoThread.join(); } }; } ================================================ FILE: src/Cafe/IOSU/legacy/iosu_mcp.h ================================================ #pragma pack(1) struct MCPTitleInfo { /* +0x00 */ uint32be titleIdHigh; // app.xml /* +0x04 */ uint32be titleIdLow; /* +0x08 */ uint32be appGroup; /* +0x0C */ char appPath[0x38]; // size guessed /* +0x44 */ uint32be appType; // app.xml /* +0x48 */ uint16be titleVersion; // app.xml // everything below is uncertain /* +0x4A */ uint64be osVersion; // app.xml /* +0x52 */ uint32be sdkVersion; // app.xml /* +0x56 */ uint8 deviceName[10]; /* +0x60 */ uint8 uknPadding; // possibly the index of the device? //move this and the stuff below }; static_assert(sizeof(MCPTitleInfo) == 0x61); #pragma pack() // custom dev/mcp protocol (Cemu only) #define IOSU_MCP_REQUEST_CEMU (0xEE) typedef struct { uint32 requestCode; struct { MEMPTR<MCPTitleInfo> titleList; uint32be titleCount; uint32be titleListBufferSize; uint64 titleId; // filters uint32be appType; }titleListRequest; // output uint32 returnCode; // ACP return value union { //struct //{ // uint64 u64; //}resultU64; struct { uint32 u32; }resultU32; //struct //{ // char strBuffer[1024]; //}resultString; //struct //{ // uint8 binBuffer[1024]; //}resultBinary; }; }iosuMcpCemuRequest_t; // MCP request Cemu subcodes #define IOSU_MCP_GET_TITLE_LIST 0x01 #define IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE 0x02 #define IOSU_MCP_GET_TITLE_INFO 0x03 // get title list by titleId #define IOSU_MCP_GET_TITLE_COUNT 0x04 namespace iosu { sint32 mcpGetTitleCount(); sint32 mcpGetTitleList(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount); void iosuMcp_init(); namespace mcp { void Init(); void Shutdown(); }; } ================================================ FILE: src/Cafe/IOSU/legacy/iosu_nim.cpp ================================================ #include "iosu_nim.h" #include "iosu_ioctl.h" #include "iosu_act.h" #include "iosu_mcp.h" #include "util/crypto/aes128.h" #include "curl/curl.h" #include "openssl/bn.h" #include "openssl/x509.h" #include "openssl/ssl.h" #include "util/helpers/helpers.h" #include "Cemu/napi/napi.h" #include "Cemu/ncrypto/ncrypto.h" #include "Cafe/CafeSystem.h" namespace iosu { namespace nim { struct NIMTitleLatestVersion { uint64 titleId; uint32 version; }; #define PACKAGE_TYPE_UPDATE (1) struct NIMPackage { uint64 titleId; uint16 version; uint8 type; }; struct { bool isInitialized; // version list sint32 latestVersion; char fqdn[256]; std::vector<NIMTitleLatestVersion> titlesLatestVersion; // nim packages // note: Seems like scope.rpx expects the number of packages to never change after the initial GetNum call? std::vector<NIMPackage> packages; bool packageListReady; bool backgroundThreadStarted; } g_nim = {}; bool nim_CheckDownloadsDisabled() { // currently for the Wii U menu we disable NIM to speed up boot times uint64 tid = CafeSystem::GetForegroundTitleId(); return tid == 0x0005001010040000 || tid == 0x0005001010040100 || tid == 0x0005001010040200; } bool nim_getLatestVersion() { g_nim.latestVersion = -1; NAPI::AuthInfo authInfo; authInfo.country = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry()); authInfo.region = NCrypto::SEEPROM_GetRegion(); auto versionListVersionResult = NAPI::TAG_GetVersionListVersion(authInfo); if (!versionListVersionResult.isValid) return false; if (versionListVersionResult.fqdnURL.size() >= 256) { cemuLog_log(LogType::Force, "NIM: fqdn URL too long"); return false; } g_nim.latestVersion = (sint32)versionListVersionResult.version; strcpy(g_nim.fqdn, versionListVersionResult.fqdnURL.c_str()); return true; } bool nim_getVersionList() { g_nim.titlesLatestVersion.clear(); NAPI::AuthInfo authInfo; authInfo.country = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry()); authInfo.region = NCrypto::SEEPROM_GetRegion(); auto versionListVersionResult = NAPI::TAG_GetVersionListVersion(authInfo); if (!versionListVersionResult.isValid) return false; auto versionListResult = TAG_GetVersionList(authInfo, versionListVersionResult.fqdnURL, versionListVersionResult.version); if (!versionListResult.isValid) return false; for (auto& itr : versionListResult.titleVersionList) { NIMTitleLatestVersion titleLatestVersion; titleLatestVersion.titleId = itr.first; titleLatestVersion.version = itr.second; g_nim.titlesLatestVersion.push_back(titleLatestVersion); } return true; } NIMTitleLatestVersion* nim_findTitleLatestVersion(uint64 titleId) { for (auto& titleLatestVersion : g_nim.titlesLatestVersion) { if (titleLatestVersion.titleId == titleId) return &titleLatestVersion; } return nullptr; } void nim_buildDownloadList() { if(nim_CheckDownloadsDisabled()) { cemuLog_logDebug(LogType::Force, "nim_buildDownloadList: Downloads are disabled for this title"); g_nim.packages.clear(); return; } sint32 titleCount = mcpGetTitleCount(); MCPTitleInfo* titleList = (MCPTitleInfo*)malloc(titleCount * sizeof(MCPTitleInfo)); memset(titleList, 0, titleCount * sizeof(MCPTitleInfo)); uint32be titleCountBE = titleCount; if (mcpGetTitleList(titleList, titleCount * sizeof(MCPTitleInfo), &titleCountBE) != 0) { cemuLog_log(LogType::Force, "IOSU: nim failed to acquire title list"); free(titleList); return; } titleCount = titleCountBE; // check for game updates for (sint32 i = 0; i < titleCount; i++) { if( titleList[i].titleIdHigh != 0x00050000 ) continue; // find update title in title version list uint64 titleId = (0x0005000EULL << 32) | ((uint64)(uint32)titleList[i].titleIdLow); NIMTitleLatestVersion* latestVersionInfo = nim_findTitleLatestVersion(titleId); if(latestVersionInfo == nullptr) continue; // compare version if(latestVersionInfo->version <= (uint32)titleList[i].titleVersion ) continue; // already on latest version // add to packages NIMPackage nimPackage{}; nimPackage.titleId = titleId; nimPackage.type = PACKAGE_TYPE_UPDATE; g_nim.packages.emplace_back(std::move(nimPackage)); } // check for AOC/titles to download // todo free(titleList); } void nim_getPackagesInfo(uint64* titleIdList, sint32 count, titlePackageInfo_t* packageInfoList) { memset(packageInfoList, 0, sizeof(titlePackageInfo_t)*count); if(nim_CheckDownloadsDisabled()) return; for (sint32 i = 0; i < count; i++) { uint64 titleId = _swapEndianU64(titleIdList[i]); titlePackageInfo_t* packageInfo = packageInfoList + i; packageInfo->titleId = _swapEndianU64(titleIdList[0]); packageInfo->ukn0C = 1; // update // pending packageInfo->ukn48 = 0; // with 0 there is no progress bar, with 3 there is a progress bar packageInfo->ukn40 = (5 << 20) | (0 << 27) | (1<<31) | (0x30000<<0); packageInfo->ukn0F = 1; } } struct idbeIconCacheEntry_t { void setIconData(NAPI::IDBEIconDataV0& newIconData) { iconData = newIconData; hasIconData = true; } void setNoIcon() { hasIconData = false; iconData = {}; } uint64 titleId; uint32 lastRequestTime; bool hasIconData{ false }; NAPI::IDBEIconDataV0 iconData{}; }; std::vector<idbeIconCacheEntry_t> idbeIconCache; void idbe_addIconToCache(uint64 titleId, NAPI::IDBEIconDataV0* iconData) { idbeIconCacheEntry_t newCacheEntry; newCacheEntry.titleId = titleId; newCacheEntry.lastRequestTime = GetTickCount(); if (iconData) newCacheEntry.setIconData(*iconData); else newCacheEntry.setNoIcon(); idbeIconCache.push_back(newCacheEntry); } sint32 nim_getIconDatabaseEntry(uint64 titleId, void* idbeIconOutput) { // if titleId is an update, replace it with gameId instead if ((uint32)(titleId >> 32) == 0x0005000EULL) { titleId &= ~0xF00000000ULL; } for (auto& entry : idbeIconCache) { if (entry.titleId == titleId) { if( entry.hasIconData ) memcpy(idbeIconOutput, &entry.iconData, sizeof(NAPI::IDBEIconDataV0)); else memset(idbeIconOutput, 0, sizeof(NAPI::IDBEIconDataV0)); return 0; } } auto result = NAPI::IDBE_Request(ActiveSettings::GetNetworkService(), titleId); if (!result) { memset(idbeIconOutput, 0, sizeof(NAPI::IDBEIconDataV0)); cemuLog_log(LogType::Force, "NIM: Unable to download IDBE icon"); return 0; } // add new cache entry idbe_addIconToCache(titleId, &*result); // return result memcpy(idbeIconOutput, &*result, sizeof(NAPI::IDBEIconDataV0)); return 0; } void nim_backgroundThread() { while (iosuAct_isAccountDataLoaded() == false) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); } if (nim_getLatestVersion()) { if (nim_getVersionList()) { nim_buildDownloadList(); } } g_nim.packageListReady = true; } void iosuNim_waitUntilPackageListReady() { if (g_nim.backgroundThreadStarted == false) { cemuLog_log(LogType::Force, "IOSU: Starting nim background thread"); std::thread t(nim_backgroundThread); t.detach(); g_nim.backgroundThreadStarted = true; } while (g_nim.packageListReady == false) std::this_thread::sleep_for(std::chrono::milliseconds(200)); } void iosuNim_thread() { SetThreadName("iosuNim_thread"); while (true) { uint32 returnValue = 0; // Ioctl return value ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_NIM); if (ioQueueEntry->request == IOSU_NIM_REQUEST_CEMU) { iosuNimCemuRequest_t* nimCemuRequest = (iosuNimCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); if (nimCemuRequest->requestCode == IOSU_NIM_GET_ICON_DATABASE_ENTRY) { nimCemuRequest->returnCode = nim_getIconDatabaseEntry(nimCemuRequest->titleId, nimCemuRequest->ptr.GetPtr()); } else if (nimCemuRequest->requestCode == IOSU_NIM_GET_PACKAGE_COUNT) { iosuNim_waitUntilPackageListReady(); nimCemuRequest->resultU32.u32 = (uint32)g_nim.packages.size(); nimCemuRequest->returnCode = 0; } else if (nimCemuRequest->requestCode == IOSU_NIM_GET_PACKAGES_TITLEID) { iosuNim_waitUntilPackageListReady(); uint32 maxCount = nimCemuRequest->maxCount; uint64* titleIdList = (uint64*)nimCemuRequest->ptr.GetPtr(); uint32 count = 0; memset(titleIdList, 0, sizeof(uint64) * maxCount); for (auto& package : g_nim.packages) { titleIdList[count] = _swapEndianU64(package.titleId); count++; if (count >= maxCount) break; } nimCemuRequest->returnCode = 0; } else if (nimCemuRequest->requestCode == IOSU_NIM_GET_PACKAGES_INFO) { iosuNim_waitUntilPackageListReady(); uint32 maxCount = nimCemuRequest->maxCount; uint64* titleIdList = (uint64*)nimCemuRequest->ptr.GetPtr(); titlePackageInfo_t* packageInfo = (titlePackageInfo_t*)nimCemuRequest->ptr2.GetPtr(); nim_getPackagesInfo(titleIdList, maxCount, packageInfo); nimCemuRequest->returnCode = 0; } else cemu_assert_unimplemented(); } else cemu_assert_unimplemented(); iosuIoctl_completeRequest(ioQueueEntry, returnValue); } return; } void Initialize() { if (g_nim.isInitialized) return; std::vector<idbeIconCacheEntry_t> idbeIconCache = std::vector<idbeIconCacheEntry_t>(); g_nim.titlesLatestVersion = std::vector<NIMTitleLatestVersion>(); g_nim.packages.clear(); g_nim.packageListReady = false; g_nim.backgroundThreadStarted = false; std::thread t2(iosuNim_thread); t2.detach(); g_nim.isInitialized = true; } } } ================================================ FILE: src/Cafe/IOSU/legacy/iosu_nim.h ================================================ #pragma once namespace iosu { namespace nim { struct titlePackageInfo_t { uint64 titleId; uint32be ukn08; uint8 ukn0C; uint8 ukn0D; uint8 ukn0E; uint8 ukn0F; uint8 ukn10; uint8 ukn11; uint8 ukn12; uint8 ukn13; uint8 ukn14[0xC]; uint32be ukn20; uint32be ukn24; // ukn sint64 value (uknDA) //uint32be ukn28_h; //uint32be ukn2C_l; uint64 ukn28DLProgressRelatedMax_u64be; // ukn sint64 value (uknDB) //uint32be ukn30_h; //uint32be ukn34_l; uint64 ukn30DLProgressRelatedCur_u64be; // ukn sint64 value (uknDC) uint32be ukn38DLProgressRelatedMax; uint32be ukn3CDLProgressRelatedCur; uint32be ukn40; uint32be ukn44; uint8 ukn48; // 0-7 are allowed values uint8 ukn49; uint8 ukn4A; uint8 ukn4B; uint32be ukn4C; }; static_assert(sizeof(titlePackageInfo_t) == 0x50); struct iosuNimCemuRequest_t { uint32 requestCode; // input uint64 titleId; MEMPTR<void> ptr; MEMPTR<void> ptr2; sint32 maxCount; // output uint32 returnCode; // NIM return value union { struct { uint32 u32; }resultU32; }; }; // custom dev/nim protocol (Cemu only) #define IOSU_NIM_REQUEST_CEMU (0xEE) // NIM request Cemu subcodes #define IOSU_NIM_GET_ICON_DATABASE_ENTRY 0x01 #define IOSU_NIM_GET_PACKAGE_COUNT 0x02 #define IOSU_NIM_GET_PACKAGES_INFO 0x03 #define IOSU_NIM_GET_PACKAGES_TITLEID 0x04 void Initialize(); } } ================================================ FILE: src/Cafe/IOSU/nn/boss/boss_common.cpp ================================================ ================================================ FILE: src/Cafe/IOSU/nn/boss/boss_common.h ================================================ #pragma once #include "Common/CafeString.h" namespace nn::boss { typedef uint32 BossResult; struct VTableEntry { uint16be offsetA{0}; uint16be offsetB{0}; MEMPTR<void> ptr; }; static_assert(sizeof(VTableEntry) == 8); struct TitleId { uint64be u64{}; static bool IsValid(TitleId* _thisptr); static TitleId* ctorDefault(TitleId* _thisptr); static TitleId* ctorFromTitleId(TitleId* _thisptr, uint64 titleId); // __ct__Q3_2nn4boss7TitleIDFUL static TitleId* ctorCopy(TitleId* _thisptr, TitleId* titleId); // __ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID static bool operator_ne(TitleId* _thisptr, TitleId* titleId); }; static_assert(sizeof(TitleId) == 8); struct TaskId { CafeString<8> id; TaskId() = default; TaskId(const char* taskId) { id.assign(taskId); } static TaskId* ctorDefault(TaskId* _thisptr); static TaskId* ctorFromString(TaskId* _thisptr, const char* taskId); //auto operator<=>(const TaskId& other) const = default; std::strong_ordering operator<=>(const TaskId& other) const noexcept { return id <=> other.id; // Delegate to CafeString's operator<=> } }; static_assert(sizeof(TaskId) == 8); struct Title { uint32be accountId{}; // 0x00 TitleId titleId{}; // 0x8 MEMPTR<void> vTablePtr{}; // 0x10 struct VTable { VTableEntry rtti; VTableEntry dtor; }; static inline SysAllocator<VTable> s_titleVTable; static Title* ctor(Title* _this); static void dtor(Title* _this, uint32 options); static void InitVTable(); }; static_assert(sizeof(Title) == 0x18); struct DirectoryName { CafeString<8> name2; static DirectoryName* ctor(DirectoryName* _thisptr); static const char* operator_const_char(DirectoryName* _thisptr); }; static_assert(sizeof(DirectoryName) == 8); struct BossAccount // the actual class name is "Account" and while the boss namespace helps us separate this from Account(.h) we use an alternative name to avoid confusion { struct VTable { VTableEntry rtti; VTableEntry dtor; }; static inline SysAllocator<VTable> s_VTable; uint32be accountId; MEMPTR<void> vTablePtr; static BossAccount* ctor(BossAccount* _this, uint32 accountId); static void dtor(BossAccount* _this, uint32 options); static void InitVTable(); }; static_assert(sizeof(BossAccount) == 8); enum class TaskWaitState : uint32 // for Task::Wait() { Done = 1, }; enum class TaskTurnState : uint32 { Ukn = 0, Stopped = 1, Ready = 4, Running = 6, Done = 7, // how does this differ from DoneSuccess? DoneSuccess = 16, DoneError = 17 }; enum class TaskState : uint8 { Initial = 0, Stopped = 1, Ready = 4, // waiting for turn to run Running = 6, Done = 7, }; enum class TaskType : uint16 { NbdlTaskSetting = 2, RawDlTaskSetting_1 = 1, RawDlTaskSetting_3 = 3, RawDlTaskSetting_9 = 9, RawUlTaskSetting = 4, PlayLogUploadTaskSetting = 5, PlayReportSetting = 6, DataStoreDownloadSetting = 8, NbdlDataListTaskSetting = 10 }; struct TaskSettingCore // the setting struct as used by IOSU { struct LifeTime { uint32be high; uint32be low; }; uint32be persistentId; uint32be ukn04; uint32be ukn08; uint32be ukn0C; uint32be ukn10; uint32be ukn14; uint32be ukn18; uint32be ukn1C; TaskId taskId; // +0x20 betype<TaskType> taskType; // +0x28 uint8be priority; // +0x2A uint8be mode; // +0x2B uint8be permission; // +0x2C uint16be intervalA; // +0x2E uint32be intervalB; // +0x30 uint32be unk34; // +0x34 - could be padding LifeTime lifeTime; // +0x38 - this is a 64bit value, but the whole struct has a size of 0x1004 and doesnt preserve the alignment in an array. Its probably handled as two 32bit values? uint8be httpProtocol; // +0x40 uint8be internalClientCert; // +0x41 uint16be httpOption; // +0x42 uint8 ukn44[0x48 - 0x44]; // padding? CafeString<256> url; // +0x48 CafeString<64> lastModifiedTime; // +0x148 uint8be internalCaCert[3]; // +0x188 uint8 ukn18B; // +0x18B - padding? uint32be httpTimeout; // +0x18C CafeString<128> caCert[3]; // +0x190 - 3 entries, each 0x80 bytes CafeString<128> clientCertName; // +0x310 CafeString<128> clientCertKey; // +0x390 struct HttpHeader { CafeString<32> name; // +0x00 CafeString<64> value; // +0x20 }; HttpHeader httpHeaders[3]; // +0x410 CafeString<96> httpQueryString; // +0x530 CafeString<512> serviceToken; // +0x590 uint8 ukn790[0x7C0 - 0x790]; // after 0x7C0 the task-specific fields seem to start? union { struct { uint32be optionValue; // +0x7C0 CafeString<32> largeHttpHeaderKey; // +0x7C4 CafeString<512> largeHttpHeaderValue; // +0x7E4 }rawUl; // RawUlTaskSetting struct { uint32be optionValue; // +0x7C0 CafeString<32> largeHttpHeaderKey; // +0x7C4 CafeString<512> largeHttpHeaderValue; // +0x7E4 // inherits the above values from RawUlTaskSetting // the play report fields are too large to fit into the available space, so instead they are passed via their own system call that attaches it to the task. See PlayReportSetting::RegisterPreprocess }playReportSetting; struct { uint8be newArrival; // +0x7C0 uint8be led; // +0x7C1 uint8be ukn7C2[6]; // +0x7C2 - padding? CafeString<8> bossDirectory; // +0x7C8 CafeString<32> fileName; // +0x7D0 Not 100% sure this is fileName }rawDl; // RawDlTaskSetting struct { CafeString<32> bossCode; // +0x7C0 CafeString<8> bossDirectory; // +0x7E0 uint32be ukn7E8; uint32be ukn7EC; uint32be directorySizeLimitHigh; // +0x7F0 uint32be directorySizeLimitLow; // +0x7F4 CafeString<32> fileName; // +0x7F8 // more fields here... }nbdl; struct { uint8 finalPadding[0xC00 - 0x7C0]; }paddedBlock; }; }; static_assert(sizeof(TaskSettingCore) == 0xC00); struct TaskSetting : public TaskSettingCore { uint8 paddingC00[0x1000 - 0xC00]; MEMPTR<void> vTablePtr; // +0x1000 struct VTableTaskSetting { VTableEntry rtti; VTableEntry dtor; VTableEntry RegisterPreprocess; // todo - double check the offset VTableEntry unk1; }; static inline SysAllocator<VTableTaskSetting> s_VTable; static TaskSetting* ctor(TaskSetting* _thisptr); static void dtor(TaskSetting* _this, uint32 options); static bool IsPrivileged(TaskSetting* _thisptr); static void InitializeSetting(TaskSetting* _thisptr); static void InitVTable(); }; static_assert(offsetof(TaskSetting, priority) == 0x2A); static_assert(offsetof(TaskSetting, mode) == 0x2B); static_assert(offsetof(TaskSetting, permission) == 0x2C); static_assert(offsetof(TaskSetting, intervalA) == 0x2E); static_assert(offsetof(TaskSetting, intervalB) == 0x30); static_assert(offsetof(TaskSetting, lifeTime) == 0x38); static_assert(offsetof(TaskSetting, httpProtocol) == 0x40); static_assert(offsetof(TaskSetting, internalClientCert) == 0x41); static_assert(offsetof(TaskSetting, httpOption) == 0x42); static_assert(offsetof(TaskSetting, url) == 0x48); static_assert(offsetof(TaskSetting, lastModifiedTime) == 0x148); static_assert(offsetof(TaskSetting, internalCaCert) == 0x188); static_assert(offsetof(TaskSetting, httpTimeout) == 0x18C); static_assert(offsetof(TaskSetting, caCert) == 0x190); static_assert(offsetof(TaskSetting, clientCertName) == 0x310); static_assert(offsetof(TaskSetting, clientCertKey) == 0x390); static_assert(offsetof(TaskSetting, httpHeaders) == 0x410); static_assert(offsetof(TaskSetting, httpQueryString) == 0x530); static_assert(offsetof(TaskSetting, serviceToken) == 0x590); // rawUl static_assert(offsetof(TaskSetting, rawUl.optionValue) == 0x7C0); static_assert(offsetof(TaskSetting, rawUl.largeHttpHeaderKey) == 0x7C4); static_assert(offsetof(TaskSetting, rawUl.largeHttpHeaderValue) == 0x7E4); // rawDl static_assert(offsetof(TaskSetting, rawDl.newArrival) == 0x7C0); static_assert(offsetof(TaskSetting, rawDl.bossDirectory) == 0x7C8); static_assert(offsetof(TaskSetting, rawDl.fileName) == 0x7D0); // nbdl static_assert(offsetof(TaskSetting, nbdl.bossCode) == 0x7C0); static_assert(offsetof(TaskSetting, nbdl.bossDirectory) == 0x7E0); static_assert(offsetof(TaskSetting, vTablePtr) == 0x1000); static_assert(sizeof(TaskSetting) == 0x1004); /* NetTaskSetting */ struct NetTaskSetting : TaskSetting { struct VTableNetTaskSetting : public VTableTaskSetting { }; static inline SysAllocator<VTableNetTaskSetting> s_VTable; static NetTaskSetting* ctor(NetTaskSetting* _thisptr); static BossResult AddCaCert(NetTaskSetting* _thisptr, const char* name); static BossResult SetServiceToken(NetTaskSetting* _thisptr, const uint8* serviceToken); static BossResult AddInternalCaCert(NetTaskSetting* _thisptr, char certId); static void SetInternalClientCert(NetTaskSetting* _thisptr, char certId); static void InitVTable(); }; static_assert(sizeof(NetTaskSetting) == 0x1004); /* NbdlTaskSetting */ struct NbdlTaskSetting : NetTaskSetting { struct VTableNbdlTaskSetting : public VTableNetTaskSetting { VTableEntry rttiNetTaskSetting; // unknown }; static_assert(sizeof(VTableNbdlTaskSetting) == 8*5); static inline SysAllocator<VTableNbdlTaskSetting> s_VTable; static NbdlTaskSetting* ctor(NbdlTaskSetting* _thisptr); static BossResult Initialize(NbdlTaskSetting* _thisptr, const char* bossCode, uint64 directorySizeLimit, const char* bossDirectory); static BossResult SetFileName(NbdlTaskSetting* _thisptr, const char* fileName); static void InitVTable(); }; static_assert(sizeof(NbdlTaskSetting) == 0x1004); /* RawUlTaskSetting */ struct RawUlTaskSetting : NetTaskSetting { uint32be ukRaw1; // 0x1004 uint32be ukRaw2; // 0x1008 uint32be ukRaw3; // 0x100C uint8 rawSpace[0x200]; // 0x1010 struct VTableRawUlTaskSetting : public VTableNetTaskSetting { VTableEntry rttiNetTaskSetting; // unknown }; static_assert(sizeof(VTableRawUlTaskSetting) == 8*5); static inline SysAllocator<VTableRawUlTaskSetting> s_VTable; static RawUlTaskSetting* ctor(RawUlTaskSetting* _thisptr); static void dtor(RawUlTaskSetting* _this, uint32 options); static void InitVTable(); }; static_assert(sizeof(RawUlTaskSetting) == 0x1210); /* RawDlTaskSetting */ struct RawDlTaskSetting : NetTaskSetting { struct VTableRawDlTaskSetting : public VTableNetTaskSetting { VTableEntry rttiNetTaskSetting; // unknown }; static_assert(sizeof(VTableRawDlTaskSetting) == 8*5); static inline SysAllocator<VTableRawDlTaskSetting> s_VTable; static RawDlTaskSetting* ctor(RawDlTaskSetting* _thisptr); static BossResult Initialize(RawDlTaskSetting* _thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* bossDirectory); static void InitVTable(); }; static_assert(sizeof(RawDlTaskSetting) == 0x1004); /* PlayReportSetting */ struct PlayReportSetting : RawUlTaskSetting { MEMPTR<uint8> ukn1210_ptr; // 0x1210 uint32be ukn1214_size; // 0x1214 uint32be ukPlay3; // 0x1218 uint32be ukPlay4; // 0x121C struct VTablePlayReportSetting : public VTableRawUlTaskSetting {}; static_assert(sizeof(VTablePlayReportSetting) == 8*5); static inline SysAllocator<VTablePlayReportSetting> s_VTable; static PlayReportSetting* ctor(PlayReportSetting* _this); static void dtor(PlayReportSetting* _this, uint32 options); static void Initialize(PlayReportSetting* _this, uint8* ptr, uint32 size); static bool Set(PlayReportSetting* _this, const char* keyname, uint32 value); static void InitVTable(); }; static_assert(sizeof(PlayReportSetting) == 0x1220); /* Storage */ enum class StorageKind : uint32 { StorageNbdl = 0, StorageRawDl = 1, }; struct DataName { CafeString<32> name; static DataName* ctor(DataName* _this); // __ct__Q3_2nn4boss8DataNameFv static const char* operator_const_char(DataName* _this); // __opPCc__Q3_2nn4boss8DataNameCFv const char* c_str() const { return name.c_str(); } }; static_assert(sizeof(DataName) == 32); /* IPC commands for /dev/boss */ enum class BossCommandId : uint32 { // task operations TaskIsRegistered = 0x69, TaskRegisterA = 0x6A, TaskRegisterForImmediateRunA = 0x6B, TaskUnregister = 0x6D, TaskRun = 0x78, TaskStartScheduling = 0x77, TaskStopScheduling = 0x79, TaskWaitA = 0x7A, TaskGetHttpStatusCodeA = 0x7C, TaskGetTurnState = 0x7E, TaskGetContentLength = 0x82, TaskGetProcessedLength = 0x83, // storage operations StorageExist = 0x87, StorageGetDataList = 0xB0, // NsData operations NsDataExist = 0x90, NsDataRead = 0x93, NsDataGetSize = 0x96, NsDataDeleteFile = 0xA7, NsDataDeleteFileWithHistory = 0xA8, NsFinalize = 0xB3, // Title operations TitleSetNewArrivalFlag = 0x9E, // most (if not all?) opcodes seem to have a secondary form with an additional titleId parameter in the high range around 0x150 and serviceId 1 // unknown commands UknA7 = 0xA5, DeleteDataRelated = 0xA6, }; } ================================================ FILE: src/Cafe/IOSU/nn/boss/boss_service.cpp ================================================ #include "Cafe/OS/libs/nn_common.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "util/helpers/helpers.h" #include "Cafe/Filesystem/fsc.h" #include "Cafe/IOSU/iosu_types_common.h" #include "Cafe/IOSU/nn/iosu_nn_service.h" #include "Cafe/IOSU/legacy/iosu_act.h" #include "Cafe/CafeSystem.h" #include "config/ActiveSettings.h" #include "boss_service.h" #include "boss_common.h" #include <pugixml.hpp> #include <curl/curl.h> #include <openssl/x509.h> #include <openssl/evp.h> #include <openssl/hmac.h> #include "Cemu/ncrypto/ncrypto.h" #include "Cemu/napi/napi_helper.h" #include "IOSU/legacy/iosu_crypto.h" #include "util/crypto/aes128.h" namespace iosu::boss { using namespace ::nn::boss; static constexpr nnResult RESULT_SUCCESS = 0x200080; static constexpr nnResult RESULT_C0203880 = 0xC0203880; static constexpr nnResult RESULT_NOTEXIST = 0xA021F900; static constexpr nnResult RESULT_STORAGE_NOTEXIST = 0xA025AA00; static constexpr nnResult RESULT_FADENTRY_NOTEXIST = 0xA021FB00; template <typename ... TArgs> void AppendHeaderParam(CurlRequestHelper& request, const char* fieldName, const char* format, TArgs&& ... args) { request.addHeaderField(fieldName, fmt::format(fmt::runtime(format), std::forward<TArgs>(args)...).c_str()); } class StorageDatabase // FAD { static constexpr uint32 FAD_ENTRY_MAX_COUNT = 512; // max entries in a single FAD file public: struct BossStorageFadEntry { CafeString<32> fileName; // 0x00 uint32be fileId; // 0x20 uint32 ukn24; // 0x24 uint32 flags; // 0x28 uint32 memo_2C; // 0x2C uint64be entryCreationTimestamp; // flags: // 0x80000000 ReadFlag // 0x40000000 DeleteProtectionFlag // 0x20000000 New arrival flag? }; static_assert(sizeof(BossStorageFadEntry) == 0x38); struct BossStorageFadFile { uint8 _00[0x08]; BossStorageFadEntry entries[FAD_ENTRY_MAX_COUNT]; }; static_assert(sizeof(BossStorageFadFile) == 28680); static bool CheckIfStorageExists(const DirectoryName& bossDirectory, uint64 titleId, uint32 persistentId) { fs::path fadPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, bossDirectory.name2.c_str()); std::error_code ec; return fs::exists(fadPath, ec); } nnResult Load(TaskId& taskId, uint64 titleId, uint32 persistentId) { // todo - if same FAD db already loaded then skip this m_hasValidFadData = false; memset(&m_fadData, 0, sizeof(m_fadData)); fs::path fadPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, taskId.id.c_str()); std::error_code ec; if (!fs::exists(fadPath, ec)) fs::create_directories(fadPath, ec); fadPath /= "fad.db"; m_fadFilePath = fadPath; FileStream* fs = FileStream::openFile2(fadPath); if (!fs) return RESULT_STORAGE_NOTEXIST; fs->readData(&m_fadData, sizeof(m_fadData)); delete fs; m_hasValidFadData = true; // the file may not exist yet, so consider it valid even if we cant open it return RESULT_SUCCESS; } nnResult CreateNewStorage(TaskId& taskId, uint64 titleId, uint32 persistentId) { memset(&m_fadData, 0, sizeof(m_fadData)); fs::path fadPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, taskId.id.c_str()); std::error_code ec; if (!fs::exists(fadPath, ec)) fs::create_directories(fadPath, ec); m_fadFilePath = fadPath / "fad.db"; FileStream* fs = FileStream::createFile2(m_fadFilePath); if (fs) { fs->writeData(&m_fadData, sizeof(m_fadData)); delete fs; m_hasValidFadData = true; } else { cemuLog_log(LogType::Force, "Failed to create FAD data file at {}", _pathToUtf8(m_fadFilePath)); return NN_RESULT_PLACEHOLDER_ERROR; } return RESULT_SUCCESS; } void Store() { if (!m_hasValidFadData) return; FileStream* fs = FileStream::createFile2(m_fadFilePath); if (fs) { fs->writeData(&m_fadData, sizeof(m_fadData)); delete fs; } else { cemuLog_log(LogType::Force, "Failed to store FAD data to {}", m_fadFilePath.string()); } } void Clear() { m_hasValidFadData = false; memset(&m_fadData, 0, sizeof(m_fadData)); } void DeleteEntryByFileName(CafeString<32> fileName) { cemu_assert_debug(m_hasValidFadData); for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) { if (m_fadData.entries[i].fileName == fileName) { // shift remaining entries memmove(m_fadData.entries + i, m_fadData.entries + i + 1, (FAD_ENTRY_MAX_COUNT - i - 1) * sizeof(BossStorageFadEntry)); // reset last entry memset(m_fadData.entries + FAD_ENTRY_MAX_COUNT - 1, 0, sizeof(BossStorageFadEntry)); return; } } } nnResult AddEntry(CafeString<32> fileName, uint32 fileId) { if (!fileId) { cemuLog_log(LogType::Force, "Cannot add BOSS file with fileId 0"); return false; } if (fileName.empty()) return false; DeleteEntryByFileName(fileName); cemu_assert_debug(m_hasValidFadData); for (sint32 i=0; i<FAD_ENTRY_MAX_COUNT; i++) { if (m_fadData.entries[i].fileId == 0) { m_fadData.entries[i].fileName = fileName; m_fadData.entries[i].fileId = fileId; m_fadData.entries[i].memo_2C = 0; m_fadData.entries[i].entryCreationTimestamp = 0; m_fadData.entries[i].ukn24 = 0; m_fadData.entries[i].flags = 0; return 0x200080; } } cemuLog_log(LogType::Force, "FAD file {} is full", _pathToUtf8(m_fadFilePath)); return 0xA021FB00; } void GetDataList(std::vector<DataName>& dataList) { cemu_assert_debug(m_hasValidFadData); dataList.clear(); for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) { if (m_fadData.entries[i].fileId == 0) break; // end of list DataName dataName; dataName.name = m_fadData.entries[i].fileName; dataList.push_back(dataName); } } bool GetEntryByFilename(const DataName& fileName, BossStorageFadEntry& outEntry) { if (fileName.name.empty()) return false; cemu_assert_debug(m_hasValidFadData); for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) { if (m_fadData.entries[i].fileName == fileName.name) { outEntry = m_fadData.entries[i]; return true; } if (m_fadData.entries[i].fileId == 0) break; // end of list } return false; } private: BossStorageFadFile m_fadData; bool m_hasValidFadData = false; fs::path m_fadFilePath; }; StorageDatabase m_fadDb; class NsDataAccessor { public: nnResult Open(const DirectoryName& bossDirectory, uint64 titleId, uint32 persistentId, DataName& fileName) { m_isValid = false; m_nsDataPath.clear(); TaskId taskId(bossDirectory.name2.c_str()); nnResult r = m_fadDb.Load(taskId, titleId, persistentId); if (NN_RESULT_IS_FAILURE(r)) return r; if (!m_fadDb.GetEntryByFilename(fileName, m_fadEntry)) return RESULT_FADENTRY_NOTEXIST; m_nsDataPath = BuildNsDataPath(bossDirectory, titleId, persistentId, m_fadEntry.fileId); m_isValid = true; m_bossDirectory = bossDirectory; m_titleId = titleId; m_persistentId = persistentId; m_fileName = fileName; return RESULT_SUCCESS; } void Close() { m_isValid = false; } nnResult Delete() { cemu_assert_debug(m_isValid); // only call this after successful Open() std::error_code ec; if (!fs::remove(m_nsDataPath, ec) ) cemuLog_log(LogType::Force, "Failed to delete BOSS file {}", _pathToUtf8(m_nsDataPath)); // remove from FAD TaskId taskId(m_bossDirectory.name2.c_str()); nnResult r = m_fadDb.Load(taskId, m_titleId, m_persistentId); if (NN_RESULT_IS_SUCCESS(r)) m_fadDb.DeleteEntryByFileName(m_fileName.name); m_isValid = false; return RESULT_SUCCESS; } bool Exists() const { if (!m_isValid) return false; // check if the file actually exists on the host filesystem std::error_code ec; if (m_nsDataPath.empty()) return false; bool r = fs::exists(m_nsDataPath, ec); if (!r) cemuLog_log(LogType::Force, "BOSS: File exists in FAD cache but not on disk {}. To fix this reset the SpotPass cache. In the menu under debug select \"Clear SpotPass cache\"", _pathToUtf8(m_nsDataPath)); return r; } nnResult GetSize(uint32& fileSize) const { cemu_assert_debug(m_isValid); // only call this after successful Open() if (!m_isValid) return NN_RESULT_PLACEHOLDER_ERROR; std::unique_ptr<FileStream> fs(FileStream::openFile2(m_nsDataPath)); if (!fs) return NN_RESULT_PLACEHOLDER_ERROR; fileSize = (uint32)fs->GetSize(); return RESULT_SUCCESS; } bool Read(void* buffer, uint32 size, uint32 offset, uint32& bytesRead) { if (!m_isValid) return false; std::unique_ptr<FileStream> fs(FileStream::openFile2(m_nsDataPath)); if (!fs) return false; fs->SetPosition(offset); bytesRead = (uint32)fs->readData(buffer, size); return true; } private: fs::path BuildNsDataPath(const DirectoryName& bossDirectory, uint64 titleId, uint32 persistentId, uint32 dataId) { return ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}/{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF), bossDirectory.name2.c_str(), dataId); } bool m_isValid{false}; StorageDatabase::BossStorageFadEntry m_fadEntry; fs::path m_nsDataPath; // if m_isValid is true, these hold the current file information: DirectoryName m_bossDirectory; uint64 m_titleId{}; uint32 m_persistentId{}; DataName m_fileName; }; NsDataAccessor m_nsDataAccessor; class RegisteredTask { public: RegisteredTask(const TaskId& taskId, const TaskSettingCore& taskSettings) : m_taskId(taskId), m_taskSettings(taskSettings) { m_persistentId = iosuAct_getAccountIdOfCurrentAccount(); } nnResult Run() { std::unique_lock _l(m_mutex); cemu_assert_debug(m_taskState == TaskState::Initial || m_taskState == TaskState::Done); m_taskTurnState = TaskTurnState::Ready; m_taskState = TaskState::Ready; return RESULT_SUCCESS; } TaskState GetState() { std::unique_lock _l(m_mutex); return m_taskState; } TaskTurnState GetTurnState() { std::unique_lock _l(m_mutex); return m_taskTurnState; } sint32 GetHttpStatusCode() const { return m_httpStatusCode; } uint32 GetContentLength() const { return m_contentLength; } uint32 GetProcessedLength() const { return m_contentLength; // todo - unlike content length, this value is getting updated as the download happens. But for now we just return the content length } nnResult TaskDoRequest(CURL* curl) { std::unique_lock _l(m_mutex); if (m_taskState != TaskState::Ready) { cemuLog_log(LogType::Force, "Task {} is not ready to run, current state: {}", m_taskId.id.c_str(), static_cast<uint32>(m_taskState)); return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); } m_taskState = TaskState::Running; m_taskTurnState = TaskTurnState::Running; m_contentLength = 0; _l.unlock(); if (!ActiveSettings::IsOnlineEnabled()) { m_taskState = TaskState::Done; m_taskTurnState = TaskTurnState::DoneError; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); } uint64 titleId = CafeSystem::GetForegroundTitleId(); // construct request URL std::string requestUrl; if (!m_taskSettings.url.empty()) requestUrl.assign(m_taskSettings.url.c_str()); // custom url else { switch (ActiveSettings::GetNetworkService()) { case NetworkService::Pretendo: requestUrl = PretendoURLs::BOSSURL; break; case NetworkService::Custom: requestUrl = GetNetworkConfig().urls.BOSS.GetValue(); break; case NetworkService::Nintendo: default: requestUrl = NintendoURLs::BOSSURL; break; } } char languageCode[8]; switch (GetConfig().console_language) { case CafeConsoleLanguage::JA: strcpy(languageCode, "ja"); break; case CafeConsoleLanguage::EN: strcpy(languageCode, "en"); break; case CafeConsoleLanguage::FR: strcpy(languageCode, "fr"); break; case CafeConsoleLanguage::DE: strcpy(languageCode, "de"); break; case CafeConsoleLanguage::IT: strcpy(languageCode, "it"); break; case CafeConsoleLanguage::ES: strcpy(languageCode, "es"); break; case CafeConsoleLanguage::ZH: strcpy(languageCode, "zh"); break; case CafeConsoleLanguage::KO: strcpy(languageCode, "ko"); break; case CafeConsoleLanguage::NL: strcpy(languageCode, "nl"); break; case CafeConsoleLanguage::PT: strcpy(languageCode, "pt"); break; case CafeConsoleLanguage::RU: strcpy(languageCode, "ru"); break; case CafeConsoleLanguage::TW: strcpy(languageCode, "tw"); // usually zh-tw? break; default: strcpy(languageCode, "en"); break; } const char* countryCode = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry()); if (m_taskSettings.taskType == TaskType::NbdlDataListTaskSetting) { // use bossCode (and fileName?) as part of the URL requestUrl.append(fmt::format(fmt::runtime("/{}/{}/{}?c={}&l={}"), "1", m_taskSettings.nbdl.bossCode.c_str(), m_taskId.id.c_str(), countryCode, languageCode)); cemu_assert_unimplemented(); cemuLog_logDebug(LogType::Force, "IOSU-BOSS: Unsupported task type: {}", static_cast<uint32>(m_taskSettings.taskType.value())); _l.lock(); m_taskState = TaskState::Done; m_taskTurnState = TaskTurnState::DoneError; m_contentLength = 0; m_httpStatusCode = 0; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); } else if (m_taskSettings.taskType == TaskType::NbdlTaskSetting) { // todo - language and country code come from the task settings cemu_assert_debug(m_taskSettings.nbdl.fileName.empty()); // todo - changes the url const char* apiVersion = "1"; requestUrl.append(fmt::format(fmt::runtime("/{}/{}/{}?c={}&l={}"), apiVersion, m_taskSettings.nbdl.bossCode.c_str(), m_taskId.id.c_str(), countryCode, languageCode)); } else if (m_taskSettings.taskType == TaskType::PlayReportSetting) { cemuLog_logDebug(LogType::Force, "IOSU-BOSS: PlayReport tasks are not implemented yet"); _l.lock(); m_taskState = TaskState::Done; m_taskTurnState = TaskTurnState::DoneError; m_contentLength = 0; m_httpStatusCode = 200; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); } else { cemuLog_logDebug(LogType::Force, "IOSU-BOSS: Unknown task type: {}", static_cast<uint32>(m_taskSettings.taskType.value())); _l.lock(); m_taskState = TaskState::Done; m_taskTurnState = TaskTurnState::DoneError; m_contentLength = 0; m_httpStatusCode = 0; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); } CurlRequestHelper request; request.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::CUSTOM); // add certs SetupSSL(request); // parameters AppendHeaderParam(request, "X-BOSS-Digest:", ""); // todo - implement this AppendHeaderParam(request, "X-Boss-UniqueId", "{:05x}", ((titleId >> 8) & 0xFFFFF)); AppendHeaderParam(request, "X-BOSS-TitleId", "{:016x}", titleId); if (!m_taskSettings.serviceToken.empty()) AppendHeaderParam(request, "X-Nintendo-ServiceToken", "{}", m_taskSettings.serviceToken.c_str()); if (!request.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed BOSS request")); _l.lock(); m_taskState = TaskState::Done; m_taskTurnState = TaskTurnState::DoneError; return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); } sint32 httpStatusCode = request.GetHTTPStatusCode(); m_httpStatusCode = httpStatusCode; m_contentLength = request.getReceivedData().size(); // note - for Nbdl tasks which length is returned? { if (httpStatusCode != 200) { cemuLog_logDebug(LogType::Force, "BOSS task_run: Received unexpected HTTP response code {}", httpStatusCode); cemuLog_logDebug(LogType::Force, "URL: {}", requestUrl); } if (httpStatusCode == 404) { _l.lock(); m_taskState = TaskState::Done; m_taskTurnState = TaskTurnState::DoneError; cemuLog_logDebug(LogType::Force, "BOSS task_run: Received 404 Not Found for URL: {}", requestUrl); return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); } } // for Nbdl tasks get the list of files to download if (m_taskSettings.taskType == TaskType::NbdlTaskSetting) { std::vector<NbdlQueuedFile> queuedFiles = ParseNbdlTasksheet(request.getReceivedData()); cemuLog_log(LogType::Force, "SpotPass - NbdlTask has {} files to download:", queuedFiles.size()); for (const auto& file : queuedFiles) { DownloadNbdlFile(file); } } if (m_taskSettings.taskType == TaskType::RawDlTaskSetting_1 || m_taskSettings.taskType == TaskType::RawDlTaskSetting_3 || m_taskSettings.taskType == TaskType::RawDlTaskSetting_9) { cemu_assert_unimplemented(); } { _l.lock(); m_taskState = TaskState::Done; m_taskTurnState = TaskTurnState::DoneSuccess; } return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); } std::recursive_mutex& GetMutex() { return m_mutex; } private: struct NbdlQueuedFile { NbdlQueuedFile(std::string_view url, std::string_view fileName, uint32 dataId, uint32 fileType, uint32 size) : url(url), fileName(fileName), dataId(dataId), fileType(fileType), size(size) {} std::string url; std::string fileName; uint32 dataId; uint32 fileType; // 0 = AppData, 1 = Message, 2 = Unknown uint32 size; }; std::vector<NbdlQueuedFile> ParseNbdlTasksheet(std::span<uint8> xmlContent) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_buffer(xmlContent.data(), xmlContent.size()); if (!result) return {}; std::vector<NbdlQueuedFile> fileList; for (pugi::xml_node sheet = doc.child("TaskSheet"); sheet; sheet = sheet.next_sibling("TaskSheet")) { pugi::xml_node serviceStatus = sheet.child("ServiceStatus"); if (strcmp(serviceStatus.child_value(), "close") == 0) { // service is closed, skip all files cemuLog_log(LogType::Force, "BossNbdl: Service is closed, skipping all files in tasksheet"); continue; } else if (strcmp(serviceStatus.child_value(), "open") != 0) { cemuLog_log(LogType::Force, "BossNbdl: Unknown ServiceStatus value: {}", serviceStatus.child_value()); } // read and verify titleId pugi::xml_node nodeTitleId = sheet.child("TitleId"); if (nodeTitleId) { uint64 titleId; TitleIdParser::ParseFromStr(nodeTitleId.child_value(), titleId); if (titleId != CafeSystem::GetForegroundTitleId()) cemuLog_log(LogType::Force, "BossNbdl: TitleId in tasksheet ({:016x}) does not match foreground titleId ({:016x})", titleId, CafeSystem::GetForegroundTitleId()); } else { cemuLog_log(LogType::Force, "BossNbdl: Missing TitleId field in tasksheet"); } // read and verify taskId pugi::xml_node nodeTaskId = sheet.child("TaskId"); if (nodeTaskId) { if (strcmp(nodeTaskId.child_value(), m_taskId.id.c_str()) != 0) cemuLog_log(LogType::Force, "BossNbdl: TaskId in tasksheet ({}) does not match current taskId ({})", nodeTaskId.child_value(), m_taskId.id.c_str()); } else { cemuLog_log(LogType::Force, "BossNbdl: Missing TaskId field in tasksheet"); } // read files list pugi::xml_node files = sheet.child("Files"); if (!files) continue; for (pugi::xml_node file = files.child("File"); file; file = file.next_sibling("File")) { pugi::xml_node fileName = file.child("Filename"); if (!fileName) continue; pugi::xml_node dataId = file.child("DataId"); if (!dataId) continue; pugi::xml_node url = file.child("Url"); if (!url) continue; pugi::xml_node size = file.child("Size"); if (!size) continue; pugi::xml_node type = file.child("Type"); if (!type) continue; uint32 fileType; if (strcmp(type.child_value(), "AppData") == 0) fileType = 0; else if (strcmp(type.child_value(), "Message") == 0) fileType = 1; else { cemuLog_log(LogType::Force, "BossNbdl: Unknown file Type value: {}", type.child_value()); fileType = 0; } if (!file.child("AutoDelete").empty()) cemuLog_log(LogType::Force, "BossNbdl: Field AutoDelete is set but not supported. Value: {}", file.child_value("AutoDelete")); // fields and categories which we dont handle yet: // Attributes, Attribute1, Attribute2, Attribute3 // Notify, Conditions, SecInterval, LED, Played fileList.emplace_back( url.child_value(), fileName.child_value(), dataId.text().as_int(), fileType, size.text().as_int() ); } } return fileList; } void SetupSSL(CurlRequestHelper& request) { request.ClearCaCertIds(); request.ClearClientCertIds(); for (sint32 i=0; i<3; i++) { if (m_taskSettings.internalCaCert[i] != 0) request.AddCaCertId(m_taskSettings.internalCaCert[i]); } request.AddCaCertId(105); if (m_taskSettings.internalClientCert != 0) request.AddCaCertId(m_taskSettings.internalClientCert); else request.AddCaCertId(3); // whats the default for each task type? cemu_assert_debug(m_taskSettings.caCert[0].empty()); cemu_assert_debug(m_taskSettings.caCert[1].empty()); cemu_assert_debug(m_taskSettings.caCert[2].empty()); cemu_assert_debug(m_taskSettings.clientCertName.empty()); } void DownloadNbdlFile(const NbdlQueuedFile& nbdlFile) { uint64 titleId = CafeSystem::GetForegroundTitleId(); // todo - use titleId from task settings? fs::path dataFilePath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}/{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF), m_taskId.id.c_str(), nbdlFile.dataId); // if the file already exists locally, skip download // but still add the entry to the FAD db std::error_code ec; if (fs::exists(dataFilePath)) { cemuLog_log(LogType::Force, "\t- {} (DataId:{:08x} {}KB) (Skipping, already downloaded)", nbdlFile.fileName, nbdlFile.dataId, (nbdlFile.size+1023)/1024); TrackDownloadedNbdlFile(nbdlFile); return; } cemuLog_log(LogType::Force, "\t- {} (DataId:{:08x} {}KB)", nbdlFile.fileName, nbdlFile.dataId, (nbdlFile.size+1023)/1024); CurlRequestHelper request; request.initate(ActiveSettings::GetNetworkService(), nbdlFile.url, CurlRequestHelper::SERVER_SSL_CONTEXT::CUSTOM); SetupSSL(request); bool r = request.submitRequest(); if (!r) { cemuLog_log(LogType::Force, "Failed to download SpotPass nbdl file: {}", nbdlFile.fileName); return; } // decrypt and store file std::vector<uint8> receivedData = request.getReceivedData(); struct BossNbdlHeader { /* +0x00 */ uint32be magic; /* +0x04 */ uint32be version; // guessed /* +0x08 */ uint16be ukn08; // must always be 1 /* +0x0A */ uint16be ukn0A; // must always be 2 /* +0x0C */ uint8 nonce[0xC]; /* +0x18 */ uint32 padding18; // unused /* +0x1C */ uint32 padding1C; // unused /* +0x20 */ struct { uint8 uknHashData[0x20]; } encryptedHeader; }; if (receivedData.size() < sizeof(BossNbdlHeader)) { cemuLog_log(LogType::Force, "Received SpotPass nbdl file is too small: {} bytes", receivedData.size()); return; } BossNbdlHeader nbdlHeader; memcpy(&nbdlHeader, receivedData.data(), sizeof(BossNbdlHeader)); if (nbdlHeader.magic != 'boss' || nbdlHeader.version != 0x20001 || nbdlHeader.ukn08 != 1 || nbdlHeader.ukn0A != 2) { cemuLog_log(LogType::Force, "Received SpotPass nbdl file has incorrect header"); return; } // file must be padded to 16 byte alignment for AES decryption (padding is cut off after decryption) size_t originalFileSize = receivedData.size(); const uint32 paddedFileSize = (originalFileSize + 0xF) & (~0xF); receivedData.resize(paddedFileSize); // extra validation if (originalFileSize != nbdlFile.size) { cemuLog_log(LogType::Force, "SpotPass nbdl file size mismatch, expected {} bytes but got {} bytes", nbdlFile.size, originalFileSize); return; } if (nbdlFile.fileName.find("..") != std::string::npos) { cemuLog_log(LogType::Force, "SpotPass nbdl filename contains suspicious characters"); return; } // prepare nonce for AES128-CTR uint8 aesNonce[0x10]; memset(aesNonce, 0, sizeof(aesNonce)); memcpy(aesNonce, nbdlHeader.nonce, 0xC); aesNonce[0xF] = 1; // decrypt uint8 bossAesKey[16] = { 0x39,0x70,0x57,0x35,0x58,0x70,0x34,0x58,0x37,0x41,0x7a,0x30,0x71,0x5a,0x70,0x74 }; memset(aesNonce, 0, sizeof(aesNonce)); memcpy(aesNonce, nbdlHeader.nonce, 0xC); aesNonce[0xF] = 1; AES128CTR_transform(receivedData.data() + offsetof(BossNbdlHeader, encryptedHeader), receivedData.size() - sizeof(BossNbdlHeader::encryptedHeader), bossAesKey, aesNonce); // get HMAC from header uint8 fileHMAC[32]; memcpy(fileHMAC, &((BossNbdlHeader*)receivedData.data())->encryptedHeader.uknHashData, 32); // write decrypted data to filesystem using a temporary path fs::path tmpDataPath = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}/.{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF), m_taskId.id.c_str(), nbdlFile.dataId); if (!fs::exists(tmpDataPath.parent_path())) fs::create_directories(tmpDataPath.parent_path(), ec); FileStream* fs = FileStream::createFile2(tmpDataPath); if (fs) { fs->writeData(receivedData.data() + sizeof(BossNbdlHeader), originalFileSize - sizeof(BossNbdlHeader)); delete fs; } else { cemuLog_log(LogType::Force, "Failed to create temporary SpotPass nbdl file: {}", _pathToUtf8(tmpDataPath)); return; } // to make sure that the file actually stored correctly to disk we read it back and verify the hash auto readbackFileData = FileStream::LoadIntoMemory(tmpDataPath); uint8 hmacHash[32]; unsigned int hmacHashLen = 32; HMAC(EVP_sha256(), "uyr5I8pu4ycq2pOT3D53Bp0n7jK8eyjLO5U20ocUNdN5muwUcC4By881UXECeM08", 64, readbackFileData->data(), originalFileSize - sizeof(BossNbdlHeader), hmacHash, &hmacHashLen); if (memcmp(hmacHash, fileHMAC, 32) != 0) { // failed to verify hash cemuLog_log(LogType::Force, "Failed to verify hash for SpotPass nbdl file: {}", _pathToUtf8(tmpDataPath)); fs::remove(tmpDataPath, ec); return; } // rename temporary file to final path fs::rename(tmpDataPath, dataFilePath, ec); // update FAD entry TrackDownloadedNbdlFile(nbdlFile); } void TrackDownloadedNbdlFile(const NbdlQueuedFile& nbdlFile) { uint64 titleId = CafeSystem::GetForegroundTitleId(); nnResult storageResult = m_fadDb.Load(m_taskId, titleId, m_persistentId); if (storageResult == RESULT_STORAGE_NOTEXIST) storageResult = m_fadDb.CreateNewStorage(m_taskId, titleId, m_persistentId); if (NN_RESULT_IS_FAILURE(storageResult)) { cemuLog_log(LogType::Force, "Failed to create FAD database for task {}: {:08x}", m_taskId.id.c_str(), storageResult); return; } CafeString<32> filenameStr; filenameStr.assign(nbdlFile.fileName); m_fadDb.AddEntry(filenameStr, nbdlFile.dataId); m_fadDb.Store(); // todo - DIDX and ref database } TaskId m_taskId; uint32 m_persistentId; TaskSettingCore m_taskSettings; std::recursive_mutex m_mutex; TaskState m_taskState{ TaskState::Initial }; TaskTurnState m_taskTurnState{ TaskTurnState::Ukn }; sint32 m_httpStatusCode{ 0 }; uint32 m_contentLength{ 0 }; // content length of the last request }; class BossDaemon { public: void Start() { if (m_threadRunning.exchange(true)) return; m_bossDaemonThread = std::thread(&BossDaemon::BossDaemonThread, this); } void Stop() { if (!m_threadRunning.exchange(false)) return; m_bossDaemonThread.join(); } void RegisterTask(const TaskSettingCore& taskSetting) { std::unique_lock _l(m_taskMtx); if (m_registeredTasks.find(taskSetting.taskId) != m_registeredTasks.end()) { // task already registered. Return error? cemuLog_logDebug(LogType::Force, "IOSU-Boss: Task {} is already registered", taskSetting.taskId.id.c_str()); return; } m_registeredTasks.try_emplace(taskSetting.taskId, std::make_shared<RegisteredTask>(taskSetting.taskId, taskSetting)); } void UnregisterTask(uint32 persistentId, const TaskId& taskId) { std::unique_lock _l(m_taskMtx); auto it = m_registeredTasks.find(taskId); if (it != m_registeredTasks.end()) { m_registeredTasks.erase(it); } else { // todo - return error } } bool TaskIsRegistered(uint32 persistentId, const TaskId& taskId) { std::unique_lock _l(m_taskMtx); return m_registeredTasks.find(taskId) != m_registeredTasks.end(); } void TaskRun(uint32 persistentId, const TaskId& taskId) { // todo - there is an extra parameter for controlling background/foreground running? std::shared_ptr<RegisteredTask> registeredTask = GetRegisteredTask2(persistentId, taskId); if (registeredTask) { cemuLog_log(LogType::Force, "IOSU-Boss: Running task {}", taskId.id.c_str()); registeredTask->Run(); } else { cemuLog_log(LogType::Force, "IOSU-Boss: Trying to run task {} which has not been registered", taskId.id.c_str()); // todo - return error -> 0xA021F900 } } void StartScheduleTask(uint32 persistentId, const TaskId& taskId, bool runImmediately) { if (runImmediately) { // proper implementation is still todo // handling automatic scheduling and task state transitions is quite complex // for now we only run the task once std::shared_ptr<RegisteredTask> registeredTask = GetRegisteredTask2(persistentId, taskId); if(!registeredTask) return; TaskState state = registeredTask->GetState(); if (state == TaskState::Stopped || state == TaskState::Initial) { registeredTask->Run(); } else { cemuLog_logDebug(LogType::Force, "StartScheduleTask(): No support for scheduling tasks that aren't in stopped state"); } } else { cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unsupported StartScheduling called for task {}", taskId.id.c_str()); } } void StopScheduleTask(uint32 persistentId, const TaskId& taskId) { // todo // task scheduling is not yet implemented so this does nothing for now } std::shared_ptr<RegisteredTask> GetRegisteredTask2(const uint32 persistentId, const TaskId& taskId) { std::unique_lock _l(m_taskMtx); auto it = m_registeredTasks.find(taskId); // todo - check persistentId as well (make it part of the key?) if (it != m_registeredTasks.end()) return it->second; return nullptr; } private: void BossDaemonThread() { CURL* curl = curl_easy_init(); while ( m_threadRunning ) { // check for tasks to run { std::shared_ptr<RegisteredTask> task = GetNextRunableTask(); if (task) { task->TaskDoRequest(curl); cemu_assert_debug(task->GetState() != TaskState::Ready); } } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } curl_easy_cleanup(curl); } std::shared_ptr<RegisteredTask> GetNextRunableTask() { std::unique_lock _l(m_taskMtx); for (auto& task : m_registeredTasks) { if (task.second->GetState() == TaskState::Ready) return task.second; } return nullptr; } std::thread m_bossDaemonThread; std::atomic_bool m_threadRunning{ false }; // task list std::mutex m_taskMtx; std::map<TaskId, std::shared_ptr<RegisteredTask>> m_registeredTasks; }s_bossDaemon; class BossMainService : public iosu::nn::IPCService { public: BossMainService() : iosu::nn::IPCService("/dev/boss") {} nnResult ServiceCall(IPCServiceCall& serviceCall) override { if (serviceCall.GetServiceId() == 0) { switch (static_cast<BossCommandId>(serviceCall.GetCommandId())) { case BossCommandId::TaskIsRegistered: return HandleTaskIsRegistered(serviceCall); case BossCommandId::TaskRegisterA: return HandleTaskRegister(serviceCall, false); case BossCommandId::TaskRegisterForImmediateRunA: return HandleTaskRegister(serviceCall, true); case BossCommandId::TaskUnregister: return HandleTaskUnregister(serviceCall); case BossCommandId::TaskRun: return HandleTaskRun(serviceCall); case BossCommandId::TaskStartScheduling: return HandleTaskStartScheduling(serviceCall); case BossCommandId::TaskStopScheduling: return HandleTaskStopScheduling(serviceCall); case BossCommandId::TaskWaitA: return HandleTaskWait(serviceCall); case BossCommandId::TaskGetTurnState: return HandleTaskGetTurnState(serviceCall); case BossCommandId::TaskGetHttpStatusCodeA: return HandleTaskGetHttpStatusCodeA(serviceCall); case BossCommandId::TaskGetContentLength: return HandleTaskGetContentLength(serviceCall); case BossCommandId::TaskGetProcessedLength: return HandleTaskGetProcessedLength(serviceCall); case BossCommandId::UknA7: return HandleUknA7(serviceCall); case BossCommandId::DeleteDataRelated: return HandleDeleteDataRelated(serviceCall); case BossCommandId::StorageExist: return HandleStorageExist(serviceCall); case BossCommandId::StorageGetDataList: return HandleStorageGetDataList(serviceCall); case BossCommandId::NsDataExist: return HandleNsDataExist(serviceCall); case BossCommandId::NsDataRead: return HandleNsDataRead(serviceCall); case BossCommandId::NsDataGetSize: return HandleNsDataGetSize(serviceCall); case BossCommandId::NsDataDeleteFile: return HandleNsDataDeleteFile(serviceCall, false); case BossCommandId::NsDataDeleteFileWithHistory: return HandleNsDataDeleteFile(serviceCall, true); case BossCommandId::NsFinalize: return HandleNsFinalize(serviceCall); case BossCommandId::TitleSetNewArrivalFlag: return HandleTitleSetNewArrivalFlag(serviceCall); default: cemuLog_log(LogType::Force, "Unsupported service call to /dev/boss"); cemu_assert_unimplemented(); break; } } else { cemu_assert_suspicious(); } return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); } // helper function to get proper persistentId uint32 BossGetPersistentId(uint32 persistentId) { if (persistentId != 0) return persistentId; uint32 currentPersistentId; bool r = iosu::act::GetPersistentId(iosu::act::ACT_SLOT_CURRENT, ¤tPersistentId);; if (!r) return 0; return currentPersistentId; } nnResult HandleTaskIsRegistered(IPCServiceCall& serviceCall) { uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); bool isRegistered = s_bossDaemon.TaskIsRegistered(persistentId, taskId); // write response serviceCall.WriteResponse<uint8be>(isRegistered ? 1 : 0); return RESULT_SUCCESS; } nnResult HandleTaskRegister(IPCServiceCall& serviceCall, bool forImmediateRun) { uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); IPCServiceCall::UnalignedBuffer taskSettingsBuffer = serviceCall.ReadUnalignedInputBufferInfo(); TaskSettingCore taskSetting = taskSettingsBuffer.ReadType<TaskSettingCore>(); // bugged.. (but on the submission side) if (taskSetting.persistentId == 0) taskSetting.persistentId = persistentId; taskSetting.taskId = taskId; s_bossDaemon.RegisterTask(taskSetting); return RESULT_SUCCESS; } nnResult HandleTaskUnregister(IPCServiceCall& serviceCall) { uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); s_bossDaemon.UnregisterTask(persistentId, taskId); return RESULT_SUCCESS; } nnResult HandleTaskRun(IPCServiceCall& serviceCall) { uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); bool isForegroundRun = serviceCall.ReadParameter<uint8be>() != 0; // todo - handle this s_bossDaemon.TaskRun(persistentId, taskId); return RESULT_SUCCESS; } nnResult HandleTaskStartScheduling(IPCServiceCall& serviceCall) { uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); uint8 startImmediately = serviceCall.ReadParameter<uint8be>(); s_bossDaemon.StartScheduleTask(persistentId, taskId, startImmediately != 0); return RESULT_SUCCESS; } nnResult HandleTaskStopScheduling(IPCServiceCall& serviceCall) { uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); s_bossDaemon.StopScheduleTask(persistentId, taskId); return RESULT_SUCCESS; } nnResult HandleTaskWait(IPCServiceCall& serviceCall) { static_assert(sizeof(betype<TaskWaitState>) == 4); uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); TaskWaitState waitState = serviceCall.ReadParameter<betype<TaskWaitState>>(); uint32 timeout = serviceCall.ReadParameter<uint32be>(); std::chrono::steady_clock::time_point endTime; if (timeout != 0) endTime = std::chrono::steady_clock::now() + std::chrono::seconds(timeout); std::shared_ptr<RegisteredTask> registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); cemu_assert_debug(registeredTask != nullptr); if (!registeredTask) return RESULT_NOTEXIST; while (true) { // todo - make async std::this_thread::sleep_for(std::chrono::milliseconds(100)); TaskState currentState = registeredTask->GetState(); // does the state match the wait state? if (waitState == TaskWaitState::Done) { if (currentState == TaskState::Done) { serviceCall.WriteResponse<uint8be>(1); // 0 -> timeout, 1 -> no timeout return RESULT_SUCCESS; } } else { cemuLog_log(LogType::Force, "IOSU-Boss: Unknown task wait state {}", static_cast<uint32>(waitState)); serviceCall.WriteResponse<uint8be>(0); return RESULT_SUCCESS; } // check for timeout if (timeout != 0 && std::chrono::steady_clock::now() >= endTime) { serviceCall.WriteResponse<uint8be>(0); return RESULT_SUCCESS; // which error to return here? } } } nnResult HandleTaskGetTurnState(IPCServiceCall& serviceCall) { static_assert(sizeof(betype<TaskTurnState>) == 4); uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); std::shared_ptr<RegisteredTask> registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); TaskTurnState turnState = registeredTask ? registeredTask->GetTurnState() : TaskTurnState::Ukn; serviceCall.WriteResponse<uint32be>(1); // execution counter - todo serviceCall.WriteResponse<betype<TaskTurnState>>(turnState); if (!registeredTask) return RESULT_NOTEXIST; return RESULT_SUCCESS; } nnResult HandleTaskGetHttpStatusCodeA(IPCServiceCall& serviceCall) { uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); std::shared_ptr<RegisteredTask> registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); sint32 httpStatusCode = registeredTask ? registeredTask->GetHttpStatusCode() : 0; serviceCall.WriteResponse<sint32be>(1); // execution counter - todo serviceCall.WriteResponse<sint32be>(httpStatusCode); if (!registeredTask) return RESULT_NOTEXIST; return RESULT_SUCCESS; } nnResult HandleTaskGetContentLength(IPCServiceCall& serviceCall) { uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); std::shared_ptr<RegisteredTask> registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); uint32 contentLength = registeredTask ? registeredTask->GetContentLength() : 0; serviceCall.WriteResponse<uint32be>(1); // execution counter - todo serviceCall.WriteResponse<uint64be>(contentLength); if (!registeredTask) return RESULT_NOTEXIST; return RESULT_SUCCESS; } nnResult HandleTaskGetProcessedLength(IPCServiceCall& serviceCall) { uint32 persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; TaskId taskId = serviceCall.ReadParameter<TaskId>(); std::shared_ptr<RegisteredTask> registeredTask = s_bossDaemon.GetRegisteredTask2(persistentId, taskId); uint32 processedLength = registeredTask ? registeredTask->GetProcessedLength() : 0; serviceCall.WriteResponse<uint32be>(1); // execution counter - todo serviceCall.WriteResponse<uint64be>(processedLength); // this the actual length? if (!registeredTask) return RESULT_NOTEXIST; return RESULT_SUCCESS; } /* Storage */ nnResult HandleStorageExist(IPCServiceCall& serviceCall) { static_assert(sizeof(DirectoryName) == 8); static_assert(sizeof(betype<StorageKind>) == 4); auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>(); StorageKind storageKind = serviceCall.ReadParameter<betype<StorageKind>>(); if (!persistentId) return RESULT_C0203880; uint64 titleId = CafeSystem::GetForegroundTitleId(); // todo - investigate how IOSU handles this bool storageExists = true; if (storageKind == StorageKind::StorageNbdl) { // but for now we only check if the fad.db file exists for nbdl storageExists = m_fadDb.CheckIfStorageExists(directoryName, titleId, persistentId); } else { cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled storage kind {}", static_cast<uint32>(storageKind)); cemu_assert_unimplemented(); } serviceCall.WriteResponse<uint8be>(storageExists ? 1 : 0); return RESULT_SUCCESS; } nnResult HandleStorageGetDataList(IPCServiceCall& serviceCall) { auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>(); uint32 ukn = serviceCall.ReadParameter<uint32be>(); cemu_assert_debug(ukn == 0); IPCServiceCall::UnalignedBuffer dataListOutputBuffer = serviceCall.ReadUnalignedOutputBufferInfo(); uint32 startOffset = serviceCall.ReadParameter<uint32be>(); // guessed cemu_assert_debug(startOffset == 0); // todo - implement offset size_t dataListSize = dataListOutputBuffer.GetSize() / sizeof(DataName); std::vector<DataName> dataListTmp; uint64 titleId = CafeSystem::GetForegroundTitleId(); // open FAD database TaskId taskId(directoryName.name2.c_str()); nnResult r = m_fadDb.Load(taskId, titleId, persistentId); if (NN_RESULT_IS_FAILURE(r)) return r; m_fadDb.GetDataList(dataListTmp); if (dataListTmp.size() > dataListSize) dataListTmp.resize(dataListSize); // write to output dataListOutputBuffer.WriteData(dataListTmp.data(), dataListTmp.size() * sizeof(DataName)); serviceCall.WriteResponse<uint32be>((uint32)dataListTmp.size()); serviceCall.WriteResponse<uint8be>(1); // extra byte to indicate success or failure return RESULT_SUCCESS; } /* NsData */ nnResult HandleNsDataExist(IPCServiceCall& serviceCall) { auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>(); uint32 ukn = serviceCall.ReadParameter<uint32be>(); // storage kind? DataName fileName = serviceCall.ReadParameter<DataName>(); uint64 titleId = CafeSystem::GetForegroundTitleId(); nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); if (NN_RESULT_IS_FAILURE(r)) return r; bool nsDataExists = m_nsDataAccessor.Exists(); serviceCall.WriteResponse<uint8be>(nsDataExists); return RESULT_SUCCESS; } nnResult HandleNsDataRead(IPCServiceCall& serviceCall) { auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>(); uint32 ukn = serviceCall.ReadParameter<uint32be>(); // storage kind? DataName fileName = serviceCall.ReadParameter<DataName>(); IPCServiceCall::UnalignedBuffer dataOutputBuffer = serviceCall.ReadUnalignedOutputBufferInfo(); uint64 readOffset = serviceCall.ReadParameter<uint64be>(); uint64 titleId = CafeSystem::GetForegroundTitleId(); nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); if (NN_RESULT_IS_FAILURE(r)) { serviceCall.WriteResponse<uint64be>(0); // should we still write the bytes read in case of failure? return r; } uint32 bytesRead = 0; std::vector<uint8> tmpBuffer; tmpBuffer.resize(dataOutputBuffer.GetSize()); bool rRead = m_nsDataAccessor.Read(tmpBuffer.data(), tmpBuffer.size(), readOffset, bytesRead); dataOutputBuffer.WriteData(tmpBuffer.data(), tmpBuffer.size()); serviceCall.WriteResponse<uint64be>(bytesRead); // todo - handle rRead return RESULT_SUCCESS; } nnResult HandleNsDataGetSize(IPCServiceCall& serviceCall) { auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>(); uint32 ukn = serviceCall.ReadParameter<uint32be>(); // storage kind? DataName fileName = serviceCall.ReadParameter<DataName>(); uint64 titleId = CafeSystem::GetForegroundTitleId(); nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); if (NN_RESULT_IS_FAILURE(r)) return r; uint32 fileSize = 0; r = m_nsDataAccessor.GetSize(fileSize); serviceCall.WriteResponse<uint64be>(fileSize); return r; } nnResult HandleNsDataDeleteFile(IPCServiceCall& serviceCall, bool withHistory) { auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>(); uint32 ukn = serviceCall.ReadParameter<uint32be>(); // storage kind? DataName fileName = serviceCall.ReadParameter<DataName>(); uint64 titleId = CafeSystem::GetForegroundTitleId(); nnResult r = m_nsDataAccessor.Open(directoryName, titleId, persistentId, fileName); if (NN_RESULT_IS_FAILURE(r)) return r; // todo - handle withHistory return m_nsDataAccessor.Delete(); } nnResult HandleNsFinalize(IPCServiceCall& serviceCall) { auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; DirectoryName directoryName = serviceCall.ReadParameter<DirectoryName>(); uint32 ukn = serviceCall.ReadParameter<uint32be>(); // storage kind? DataName fileName = serviceCall.ReadParameter<DataName>(); m_nsDataAccessor.Close(); return RESULT_SUCCESS; } /* Title */ nnResult HandleTitleSetNewArrivalFlag(IPCServiceCall& serviceCall) { auto persistentId = BossGetPersistentId(serviceCall.ReadParameter<uint32be>()); if (!persistentId) return RESULT_C0203880; uint8 flagValue = serviceCall.ReadParameter<uint8be>(); cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled command TitleSetNewArrivalFlag"); return RESULT_SUCCESS; } /* stubbed opcodes */ nnResult HandleUknA7(IPCServiceCall& serviceCall) { cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled command A7"); // no response data return RESULT_SUCCESS; } nnResult HandleDeleteDataRelated(IPCServiceCall& serviceCall) { cemuLog_logDebug(LogType::Force, "IOSU-Boss: Unhandled delete command"); // no response data return RESULT_SUCCESS; } }; BossMainService s_bossService; class : public ::IOSUModule { void TitleStart() override { s_bossService.Start(); s_bossDaemon.Start(); } void TitleStop() override { s_bossService.Stop(); m_fadDb.Clear(); m_nsDataAccessor.Close(); } }sIOSUModuleNNBOSS; IOSUModule* GetModule() { return static_cast<IOSUModule*>(&sIOSUModuleNNBOSS); } } ================================================ FILE: src/Cafe/IOSU/nn/boss/boss_service.h ================================================ #pragma once #include "Cafe/IOSU/iosu_types_common.h" namespace iosu::boss { IOSUModule* GetModule(); } ================================================ FILE: src/Cafe/IOSU/nn/iosu_nn_service.cpp ================================================ #include "iosu_nn_service.h" #include "../kernel/iosu_kernel.h" #include "util/helpers/helpers.h" using namespace iosu::kernel; namespace iosu { namespace nn { /* IPCSimpleService */ void IPCSimpleService::Start() { if (m_isRunning.exchange(true)) return; m_threadInitialized = false; m_requestStop = false; m_serviceThread = std::thread(&IPCSimpleService::ServiceThread, this); while (!m_threadInitialized) std::this_thread::sleep_for(std::chrono::milliseconds(10)); StartService(); } void IPCSimpleService::Stop() { if (!m_isRunning.exchange(false)) return; m_requestStop = true; StopService(); if(m_timerId != IOSInvalidTimerId) IOS_DestroyTimer(m_timerId); m_timerId = IOSInvalidTimerId; IOS_SendMessage(m_msgQueueId, 0, 0); // wake up thread m_serviceThread.join(); } void IPCSimpleService::ServiceThread() { if(!GetThreadName().empty()) SetThreadName(GetThreadName().c_str()); m_msgQueueId = IOS_CreateMessageQueue(_m_msgBuffer.GetPtr(), _m_msgBuffer.GetCount()); cemu_assert(!IOS_ResultIsError((IOS_ERROR)m_msgQueueId)); IOS_ERROR r = IOS_RegisterResourceManager(m_devicePath.c_str(), m_msgQueueId); cemu_assert(!IOS_ResultIsError(r)); m_threadInitialized = true; while (true) { IOSMessage msg; r = IOS_ReceiveMessage(m_msgQueueId, &msg, 0); cemu_assert(!IOS_ResultIsError(r)); if (msg == 0) { cemu_assert_debug(m_requestStop); break; } else if(msg == 1) { TimerUpdate(); continue; } IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); if (cmd->cmdId == IPCCommandId::IOS_OPEN) { void* clientObject = CreateClientObject(); if(clientObject == nullptr) { cemuLog_log(LogType::Force, "IPCSimpleService[{}]: Maximum handle count reached or handle rejected", m_devicePath); IOS_ResourceReply(cmd, IOS_ERROR_MAXIMUM_REACHED); continue; } IOSDevHandle newHandle = GetFreeHandle(); m_clientObjects[newHandle] = clientObject; IOS_ResourceReply(cmd, (IOS_ERROR)newHandle); continue; } else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) { void* clientObject = GetClientObjectByHandle(cmd->devHandle); if (clientObject) DestroyClientObject(clientObject); IOS_ResourceReply(cmd, IOS_ERROR_OK); continue; } else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) { void* clientObject = GetClientObjectByHandle(cmd->devHandle); if (!clientObject) { cemuLog_log(LogType::Force, "IPCSimpleService[{}]: Invalid IPC handle", m_devicePath); IOS_ResourceReply(cmd, IOS_ERROR_INVALID); continue; } uint32 requestId = cmd->args[0]; uint32 numIn = cmd->args[1]; uint32 numOut = cmd->args[2]; IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr(); IPCIoctlVector* vecIn = vec + 0; // the ordering of vecIn/vecOut differs from IPCService IPCIoctlVector* vecOut = vec + numIn; m_delayResponse = false; m_activeCmd = cmd; uint32 result = ServiceCall(clientObject, requestId, vecIn, numIn, vecOut, numOut); if (!m_delayResponse) IOS_ResourceReply(cmd, (IOS_ERROR)result); m_activeCmd = nullptr; continue; } else { cemuLog_log(LogType::Force, "IPCSimpleService[{}]: Unsupported IPC cmdId {}", m_devicePath, (uint32)cmd->cmdId.value()); cemu_assert_unimplemented(); IOS_ResourceReply(cmd, IOS_ERROR_INVALID); } } IOS_DestroyMessageQueue(m_msgQueueId); m_threadInitialized = false; } void IPCSimpleService::SetTimerUpdate(uint32 milliseconds) { if(m_timerId != IOSInvalidTimerId) IOS_DestroyTimer(m_timerId); m_timerId = IOS_CreateTimer(milliseconds * 1000, milliseconds * 1000, m_msgQueueId, 1); } IPCCommandBody* IPCSimpleService::ServiceCallDelayCurrentResponse() { cemu_assert_debug(m_activeCmd); m_delayResponse = true; return m_activeCmd; } void IPCSimpleService::ServiceCallAsyncRespond(IPCCommandBody* response, uint32 r) { IOS_ResourceReply(response, (IOS_ERROR)r); } /* IPCService */ void IPCService::Start() { if (m_isRunning.exchange(true)) return; m_threadInitialized = false; m_requestStop = false; m_serviceThread = std::thread(&IPCService::ServiceThread, this); while (!m_threadInitialized) std::this_thread::sleep_for(std::chrono::milliseconds(10)); } void IPCService::Stop() { if (!m_isRunning.exchange(false)) return; m_requestStop = true; IOS_SendMessage(m_msgQueueId, 0, 0); // wake up thread m_serviceThread.join(); } void IPCService::ServiceThread() { std::string serviceName = m_devicePath.substr(m_devicePath.find_last_of('/') == std::string::npos ? 0 : m_devicePath.find_last_of('/') + 1); serviceName.insert(0, "NNsvc_"); SetThreadName(serviceName.c_str()); m_msgQueueId = IOS_CreateMessageQueue(_m_msgBuffer.GetPtr(), _m_msgBuffer.GetCount()); cemu_assert(!IOS_ResultIsError((IOS_ERROR)m_msgQueueId)); IOS_ERROR r = IOS_RegisterResourceManager(m_devicePath.c_str(), m_msgQueueId); cemu_assert(!IOS_ResultIsError(r)); m_threadInitialized = true; while (true) { IOSMessage msg; IOS_ERROR r = IOS_ReceiveMessage(m_msgQueueId, &msg, 0); cemu_assert(!IOS_ResultIsError(r)); if (msg == 0) { cemu_assert_debug(m_requestStop); break; } IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); if (cmd->cmdId == IPCCommandId::IOS_OPEN) { IOS_ResourceReply(cmd, (IOS_ERROR)CreateClientHandle()); continue; } else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) { CloseClientHandle((IOSDevHandle)(uint32)cmd->devHandle); IOS_ResourceReply(cmd, IOS_ERROR_OK); continue; } else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) { uint32 requestId = cmd->args[0]; uint32 numOut = cmd->args[1]; uint32 numIn = cmd->args[2]; IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr(); IPCIoctlVector* vecOut = vec + 0; // out buffers come first IPCIoctlVector* vecIn = vec + numOut; cemu_assert(numIn > 0 && numOut > 0); cemu_assert(vecIn->size >= 80 && !vecIn->basePhys.IsNull()); IPCServiceRequestHeader* serviceRequest = MEMPTR<IPCServiceRequestHeader>(vecIn->basePhys).GetPtr(); IPCServiceResponseHeader* serviceResponse = MEMPTR<IPCServiceResponseHeader>(vecOut->basePhys).GetPtr(); IOSDevHandle clientHandle = 0; // todo IPCServiceCall serviceCall(clientHandle, serviceRequest->serviceId, serviceRequest->commandId); #if 0 // log all buffers cemuLog_log(LogType::Force, "IPC ServiceCall. In: {}, Out: {}, ServiceId: {}, CommandId: {} (0x{:x})", numIn, numOut, serviceRequest->serviceId, serviceRequest->commandId, serviceRequest->commandId); for (size_t i = 0; i <numOut+numIn; i++) { cemuLog_log(LogType::Force, ""); cemuLog_log(LogType::Force, "Buffer {} - BasePhys: {}, Size: {}", i, vec[i].basePhys, vec[i].size); cemuLog_logHexDump(LogType::Force, MEMPTR<uint8>(vec[i].basePhys).GetPtr(), vec[i].size, 16); } #endif // parameter and response data is appended directly after the headers, so we add the streams without the headers serviceCall.AddInputStream(MEMPTR<uint8>(vecIn[0].basePhys).GetPtr() + sizeof(IPCServiceRequestHeader), vecIn[0].size - sizeof(IPCServiceRequestHeader)); serviceCall.AddOutputStream(MEMPTR<uint8>(vecOut[0].basePhys).GetPtr() + sizeof(IPCServiceResponseHeader), vecOut[0].size - sizeof(IPCServiceResponseHeader)); // add remaining input/output buffers for (size_t i = 1; i < numIn; i++) serviceCall.AddInputStream(MEMPTR<uint8>(vecIn[i].basePhys).GetPtr(), vecIn[i].size); for (size_t i = 1; i < numOut; i++) serviceCall.AddOutputStream(MEMPTR<uint8>(vecOut[i].basePhys).GetPtr(), vecOut[i].size); serviceResponse->nnResultCode = (uint32)ServiceCall(serviceCall); IOS_ResourceReply(cmd, IOS_ERROR_OK); continue; } else { cemuLog_log(LogType::Force, "{}: Unsupported cmdId", m_devicePath); cemu_assert_unimplemented(); IOS_ResourceReply(cmd, IOS_ERROR_INVALID); } } IOS_DestroyMessageQueue(m_msgQueueId); m_threadInitialized = false; } }; }; ================================================ FILE: src/Cafe/IOSU/nn/iosu_nn_service.h ================================================ #pragma once #include "Cafe/IOSU/kernel/iosu_kernel.h" #include "Cafe/IOSU/iosu_ipc_common.h" #include "Cafe/IOSU/iosu_types_common.h" #include "Cafe/OS/libs/nn_common.h" namespace iosu { namespace nn { // a simple service interface which wraps handle management and Ioctlv/IoctlvAsync (used by /dev/fpd and others are still to be determined) class IPCSimpleService { public: IPCSimpleService(std::string_view devicePath) : m_devicePath(devicePath) {}; virtual ~IPCSimpleService() {}; virtual void StartService() {}; virtual void StopService() {}; virtual std::string GetThreadName() = 0; virtual void* CreateClientObject() = 0; virtual void DestroyClientObject(void* clientObject) = 0; virtual uint32 ServiceCall(void* clientObject, uint32 requestId, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) = 0; virtual void TimerUpdate() {}; IPCCommandBody* ServiceCallDelayCurrentResponse(); static void ServiceCallAsyncRespond(IPCCommandBody* response, uint32 r); void Start(); void Stop(); void SetTimerUpdate(uint32 milliseconds); private: void ServiceThread(); IOSDevHandle GetFreeHandle() { while(m_clientObjects.find(m_nextHandle) != m_clientObjects.end() || m_nextHandle == 0) { m_nextHandle++; m_nextHandle &= 0x7FFFFFFF; } IOSDevHandle newHandle = m_nextHandle; m_nextHandle++; m_nextHandle &= 0x7FFFFFFF; return newHandle; } void* GetClientObjectByHandle(IOSDevHandle handle) const { auto it = m_clientObjects.find(handle); if(it == m_clientObjects.end()) return nullptr; return it->second; } std::string m_devicePath; std::thread m_serviceThread; std::atomic_bool m_requestStop{false}; std::atomic_bool m_isRunning{false}; std::atomic_bool m_threadInitialized{ false }; std::unordered_map<IOSDevHandle, void*> m_clientObjects; IOSDevHandle m_nextHandle{1}; IOSTimerId m_timerId{IOSInvalidTimerId}; IPCCommandBody* m_activeCmd{nullptr}; bool m_delayResponse{false}; IOSMsgQueueId m_msgQueueId; SysAllocator<iosu::kernel::IOSMessage, 128> _m_msgBuffer; }; // a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, /dev/acp_main, /dev/boss and most nn services class IPCService { struct IPCServiceRequestHeader { uint32be ukn00; uint32be serviceId; uint32be ukn08; uint32be commandId; }; static_assert(sizeof(IPCServiceRequestHeader) == 0x10); struct IPCServiceResponseHeader { uint32be nnResultCode; }; public: class IPCParameterStream // input stream for parameters { public: IPCParameterStream() = default; IPCParameterStream(void* data, uint32 size) : m_data((uint8_t*)data), m_size(size) {} template<typename T> T ReadParameter(bool& hasError) { hasError = false; if (m_readIndex + sizeof(T) > m_size) { hasError = true; return T{}; } T value; memcpy(&value, &m_data[m_readIndex], sizeof(T)); m_readIndex += sizeof(T); return value; } uint8* GetData() { return m_data; } uint32 GetSize() const { return m_size; } private: uint8* m_data{nullptr}; uint32 m_size{0}; uint32 m_readIndex{0}; }; class IPCResponseStream // output stream for response data { public: IPCResponseStream() = default; IPCResponseStream(void* data, uint32 size) : m_data((uint8*)data), m_size(size) {} template<typename T> void Write(const T& value) { if (m_writtenSize + sizeof(T) > m_size) { m_hasError = true; return; } memcpy(&m_data[m_writtenSize], &value, sizeof(T)); m_writtenSize += sizeof(T); } uint8* GetData() { return m_data; } uint32 GetSize() const { return m_size; } private: uint8* m_data{nullptr}; uint32 m_writtenSize{0}; uint32 m_size{0}; bool m_hasError{false}; }; class IPCServiceCall { struct LargeBufferHeader { uint32be alignedSize; // size of the aligned (middle) part of the buffer uint8be ukn4; uint8be ukn5; uint8be headSize; uint8be tailSize; }; static_assert(sizeof(LargeBufferHeader) == 8); public: struct UnalignedBuffer { UnalignedBuffer(bool isOutput, std::span<uint8> head, std::span<uint8> middle, std::span<uint8> tail) : m_isOutput(isOutput) { headPtr = head.data(); headSize = (uint32)head.size(); middlePtr = middle.data(); middleSize = (uint32)middle.size(); tailPtr = tail.data(); tailSize = (uint32)tail.size(); }; template<typename T> T ReadType() { cemu_assert(!m_isOutput); T v; memcpy((uint8_t*)&v, headPtr, headSize); memcpy((uint8_t*)&v + headSize, middlePtr, middleSize); memcpy((uint8_t*)&v + headSize + middleSize, tailPtr, tailSize); return v; } void WriteData(void* data, size_t size) { if (size == 0) return; cemu_assert(m_isOutput); cemu_assert((headSize + middleSize + tailSize) >= size); size_t bytesToCopy = std::min<size_t>(size, headSize); memcpy(headPtr, data, bytesToCopy); size -= bytesToCopy; if (size > 0) { bytesToCopy = std::min<size_t>(size, middleSize); memcpy(middlePtr, (uint8_t*)data + headSize, bytesToCopy); size -= bytesToCopy; } if (size > 0) { bytesToCopy = std::min<size_t>(size, tailSize); memcpy(tailPtr, (uint8_t*)data + headSize + middleSize, bytesToCopy); } } size_t GetSize() const { return headSize + middleSize + tailSize; } private: void* headPtr; uint32 headSize; void* middlePtr; // aligned uint32 middleSize; void* tailPtr; uint32 tailSize; bool m_isOutput; }; IPCServiceCall(IOSDevHandle clientHandle, uint32 serviceId, uint32 commandId) : m_clientHandle(clientHandle), m_serviceId(serviceId), m_commandId(commandId) { } void AddInputStream(void* data, uint32 size) { cemu_assert(m_paramStreamArraySize < 4); m_paramStreamArray[m_paramStreamArraySize++] = IPCParameterStream(data, size); } void AddOutputStream(void* data, uint32 size) { cemu_assert(m_responseStreamArraySize < 4); m_responseStreamArray[m_responseStreamArraySize++] = IPCResponseStream(data, size); } uint32 GetServiceId() const { return m_serviceId; } uint32 GetCommandId() const { return m_commandId; } template<typename T> T ReadParameter() { // read only from stream 0 for now return m_paramStreamArray[0].ReadParameter<T>(m_hasError); } UnalignedBuffer ReadUnalignedInputBufferInfo() { // how large/unaligned buffers work: // Instead of appending the data into the parameter stream, there are two separate buffers created: // 1. A ioctl vector that points directly to the aligned part of the original buffer. Both the pointer and the size are aligned // 2. A ioctl vector with an allocated up-to-128byte buffer that holds any unaligned head or tail data (e.g. anything that isn't occupying the full 64 byte alignment) // The buffer layout is then also serialized into the parameter stream LargeBufferHeader header = ReadParameter<LargeBufferHeader>(); // get aligned buffer part void* alignedBuffer = m_paramStreamArray[m_inputBufferIndex+0].GetData(); cemu_assert(m_paramStreamArray[m_inputBufferIndex+0].GetSize() == header.alignedSize); // get head and tail buffer parts uint8* unalignedDataBuffer = m_paramStreamArray[m_inputBufferIndex+1].GetData(); cemu_assert((header.headSize + header.tailSize) <= m_paramStreamArray[m_inputBufferIndex+1].GetSize()); UnalignedBuffer largeBuffer(false, {(uint8*)unalignedDataBuffer, header.headSize}, {(uint8*)alignedBuffer, header.alignedSize}, {(uint8*)unalignedDataBuffer + header.headSize, header.tailSize}); m_inputBufferIndex += 2; // if there is no unaligned data then are both buffers still present? return largeBuffer; } UnalignedBuffer ReadUnalignedOutputBufferInfo() { LargeBufferHeader header = ReadParameter<LargeBufferHeader>(); // get aligned buffer part void* alignedBuffer = m_responseStreamArray[m_outputBufferIndex+0].GetData(); cemu_assert(m_responseStreamArray[m_outputBufferIndex+0].GetSize() == header.alignedSize); // get head and tail buffer parts uint8* unalignedDataBuffer = m_responseStreamArray[m_outputBufferIndex+1].GetData(); cemu_assert((header.headSize + header.tailSize) <= m_responseStreamArray[m_outputBufferIndex+1].GetSize()); UnalignedBuffer largeBuffer(true, {(uint8*)unalignedDataBuffer, header.headSize}, {(uint8*)alignedBuffer, header.alignedSize}, {(uint8*)unalignedDataBuffer + header.headSize, header.tailSize}); m_outputBufferIndex += 2; // if there is no unaligned data then are both buffers still present? return largeBuffer; } template<typename T> void WriteResponse(const T& value) { m_responseStreamArray[0].Write<T>(value); } private: IOSDevHandle m_clientHandle; uint32 m_serviceId; uint32 m_commandId; IPCParameterStream m_paramStreamArray[4]{}; size_t m_paramStreamArraySize{0}; IPCResponseStream m_responseStreamArray[4]{}; size_t m_responseStreamArraySize{0}; bool m_hasError{false}; sint8 m_inputBufferIndex{1}; sint8 m_outputBufferIndex{1}; }; IPCService(std::string_view devicePath) : m_devicePath(devicePath) {}; virtual ~IPCService() {}; virtual IOSDevHandle CreateClientHandle() { return (IOSDevHandle)0; } virtual void CloseClientHandle(IOSDevHandle handle) { } virtual nnResult ServiceCall(IPCServiceCall& serviceCall) { cemu_assert_unimplemented(); return 0; } void Start(); void Stop(); private: void ServiceThread(); std::string m_devicePath; std::thread m_serviceThread; std::atomic_bool m_requestStop{false}; std::atomic_bool m_isRunning{false}; std::atomic_bool m_threadInitialized{ false }; IOSMsgQueueId m_msgQueueId; SysAllocator<iosu::kernel::IOSMessage, 128> _m_msgBuffer; }; }; }; ================================================ FILE: src/Cafe/OS/RPL/COSModule.cpp ================================================ #include "COSModule.h" #include "Cafe/OS/libs/coreinit/coreinit.h" #include "Cafe/OS/libs/zlib125/zlib125.h" #include "OS/libs/gx2/GX2.h" #include "OS/libs/dmae/dmae.h" #include "OS/libs/padscore/padscore.h" #include "OS/libs/vpad/vpad.h" #include "OS/libs/snd_core/ax.h" #include "OS/libs/snd_user/snd_user.h" #include "OS/libs/mic/mic.h" #include "OS/libs/erreula/erreula.h" #include "OS/libs/nlibnss/nlibnss.h" #include "OS/libs/nn_acp/nn_acp.h" #include "OS/libs/nn_act/nn_act.h" #include "OS/libs/nn_acp/nn_acp.h" #include "OS/libs/nn_ac/nn_ac.h" #include "OS/libs/nn_boss/nn_boss.h" #include "OS/libs/nn_ec/nn_ec.h" #include "OS/libs/nn_boss/nn_boss.h" #include "OS/libs/nn_nfp/nn_nfp.h" #include "OS/libs/nn_uds/nn_uds.h" #include "OS/libs/nn_nim/nn_nim.h" #include "OS/libs/nn_ndm/nn_ndm.h" #include "OS/libs/nn_spm/nn_spm.h" #include "OS/libs/nn_save/nn_save.h" #include "OS/libs/nsysnet/nsysnet.h" #include "OS/libs/nn_fp/nn_fp.h" #include "OS/libs/nn_idbe/nn_idbe.h" #include "OS/libs/nn_olv/nn_olv.h" #include "OS/libs/nn_idbe/nn_idbe.h" #include "OS/libs/nlibnss/nlibnss.h" #include "OS/libs/nlibcurl/nlibcurl.h" #include "OS/libs/sysapp/sysapp.h" #include "OS/libs/nsyshid/nsyshid.h" #include "OS/libs/nsyskbd/nsyskbd.h" #include "OS/libs/swkbd/swkbd.h" #include "OS/libs/camera/camera.h" #include "OS/libs/proc_ui/proc_ui.h" #include "OS/libs/avm/avm.h" #include "OS/libs/drmapp/drmapp.h" #include "OS/libs/TCL/TCL.h" #include "OS/libs/nn_cmpt/nn_cmpt.h" #include "OS/libs/nn_ccr/nn_ccr.h" #include "OS/libs/nn_temp/nn_temp.h" #include "OS/libs/nn_aoc/nn_aoc.h" #include "OS/libs/nn_pdm/nn_pdm.h" #include "OS/libs/h264_avc/h264dec.h" #include "OS/libs/ntag/ntag.h" #include "OS/libs/nfc/nfc.h" std::span<COSModule*> GetCOSModules() { static std::vector<COSModule*> s_cosModules = { coreinit::GetModule(), zlib::GetModule(), GX2::GetModule(), dmae::GetModule(), padscore::GetModule(), vpad::GetModule(), snd_core::GetModuleSndCore1(), snd_core::GetModuleSndCore2(), snd_user::GetModuleSndUser1(), snd_user::GetModuleSndUser2(), mic::GetModule(), nn::erreula::GetModule(), nn::act::GetModule(), nn::acp::GetModule(), nn::ac::GetModule(), nn::ec::GetModule(), nn::boss::GetModule(), nn::nfp::GetModule(), nn::uds::GetModule(), nn::nim::GetModule(), nn::ndm::GetModule(), nn::spm::GetModule(), nn::save::GetModule(), nsysnet::GetModule(), nn::fp::GetModule(), nn::olv::GetModule(), nn::idbe::GetModule(), nlibnss::GetModule(), nlibcurl::GetModule(), sysapp::GetModule(), nsyshid::GetModule(), nsyskbd::GetModule(), swkbd::GetModule(), camera::GetModule(), proc_ui::GetModule(), avm::GetModule(), drmapp::GetModule(), TCL::GetModule(), nn::cmpt::GetModule(), nn::ccr::GetModule(), nn::temp::GetModule(), nn::aoc::GetModule(), nn::pdm::GetModule(), H264::GetModule(), ntag::GetModule(), nfc::GetModule(), }; return s_cosModules; } ================================================ FILE: src/Cafe/OS/RPL/COSModule.h ================================================ #pragma once namespace coreinit { enum class RplEntryReason; }; // base class for HLE RPL implementations class COSModule { public: virtual std::string_view GetName() = 0; virtual std::vector<std::string_view> GetDependencies() { return {}; }; virtual void RPLMapped() {}; // RPL mapped into process virtual void RPLUnmapped() {}; // RPL unmapped virtual void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) {}; // note: to simplify cleanup, both RPLUnmapped() and rpl_entry(unload) are always called even if the process is shutdown via "Close game" }; std::span<COSModule*> GetCOSModules(); ================================================ FILE: src/Cafe/OS/RPL/elf.cpp ================================================ #include <zlib.h> #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "util/VirtualHeap/VirtualHeap.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" typedef struct { /* +0x00 */ uint32be magic; /* +0x04 */ uint8 eiClass; /* +0x05 */ uint8 eiData; /* +0x06 */ uint8 eiVersion; /* +0x07 */ uint8 eiOSABI; /* +0x08 */ uint8 eiOSABIVersion; /* +0x09 */ uint8 eiPadding[7]; /* +0x10 */ uint16be eType; /* +0x12 */ uint16be eMachine; /* +0x14 */ uint32be eVersion; /* +0x18 */ uint32be entrypoint; /* +0x1C */ uint32be phOffset; /* +0x20 */ uint32be shOffset; /* +0x24 */ uint32be eFlags; /* +0x28 */ uint16be eHeaderSize; /* +0x2A */ uint16be ePHEntrySize; /* +0x2C */ uint16be ePHNum; /* +0x2E */ uint16be eSHEntrySize; /* +0x30 */ uint16be eSHNum; /* +0x32 */ uint16be eShStrIndex; }elfHeader_t; static_assert(sizeof(elfHeader_t) == 0x34, ""); typedef struct { /* +0x00 */ uint32be nameOffset; /* +0x04 */ uint32be shType; /* +0x08 */ uint32be shFlags; /* +0x0C */ uint32be shAddr; /* +0x10 */ uint32be shOffset; /* +0x14 */ uint32be shSize; /* +0x18 */ uint32be shLink; /* +0x1C */ uint32be shInfo; /* +0x20 */ uint32be shAddrAlign; /* +0x24 */ uint32be shEntSize; }elfSectionEntry_t; static_assert(sizeof(elfSectionEntry_t) == 0x28, ""); #define PF_X (1 << 0) /* Segment is executable */ #define PF_W (1 << 1) /* Segment is writable */ #define PF_R (1 << 2) /* Segment is readable */ // Map elf into memory uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name) { elfHeader_t* header = (elfHeader_t*)elfData; uint32 sectionCount = header->eSHNum; uint32 sectionTableOffset = header->shOffset; uint32 sectionTableEntrySize = header->eSHEntrySize; elfSectionEntry_t* sectionTable = (elfSectionEntry_t*)(elfData + (uint32)header->shOffset); memory_enableHBLELFCodeArea(); for (uint32 i = 0; i < sectionCount; i++) { debug_printf("%02d Addr %08x Size %08x Offs %08x Flags %08x Type %08x EntSize %08x\n", i, (uint32)sectionTable[i].shAddr, (uint32)sectionTable[i].shSize, (uint32)sectionTable[i].shOffset, (uint32)sectionTable[i].shFlags, (uint32)sectionTable[i].shType, (uint32)sectionTable[i].shEntSize); uint32 shAddr = (uint32)sectionTable[i].shAddr; uint32 shSize = (uint32)sectionTable[i].shSize; uint32 shOffset = (uint32)sectionTable[i].shOffset; uint32 shType = (uint32)sectionTable[i].shType; uint32 shFlags = (uint32)sectionTable[i].shFlags; if (shOffset > (uint32)size) { cemuLog_log(LogType::Force, "ELF section {} out of bounds", i); continue; } uint32 copySize = std::min(shSize, size - shOffset); // 0x00802000 if (shAddr != 0) { if (shType == SHT_NOBITS) { memset(memory_getPointerFromVirtualOffset(shAddr), 0, shSize); } else { memcpy(memory_getPointerFromVirtualOffset(shAddr), elfData + shOffset, copySize); } // SHT_NOBITS } if((shFlags & PF_X) > 0) PPCRecompiler_allocateRange(shAddr, shSize); } return header->entrypoint; } // From Homebrew Launcher: //#define MEM_BASE (0x00800000) //#define ELF_DATA_ADDR (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x00)) //#define ELF_DATA_SIZE (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x04)) //#define HBL_CHANNEL (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x08)) //#define RPX_MAX_SIZE (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x0C)) //#define RPX_MAX_CODE_SIZE (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x10)) //#define MAIN_ENTRY_ADDR (*(volatile unsigned int*)(MEM_BASE + 0x1400 + 0x00)) //#define OS_FIRMWARE (*(volatile unsigned int*)(MEM_BASE + 0x1400 + 0x04)) // //#define OS_SPECIFICS ((OsSpecifics*)(MEM_BASE + 0x1500)) // //#define MEM_AREA_TABLE ((s_mem_area*)(MEM_BASE + 0x1600)) //typedef struct _OsSpecifics //{ // unsigned int addr_OSDynLoad_Acquire; // unsigned int addr_OSDynLoad_FindExport; // unsigned int addr_OSTitle_main_entry; // // unsigned int addr_KernSyscallTbl1; // unsigned int addr_KernSyscallTbl2; // unsigned int addr_KernSyscallTbl3; // unsigned int addr_KernSyscallTbl4; // unsigned int addr_KernSyscallTbl5; // // int(*LiWaitIopComplete)(int, int *); // int(*LiWaitIopCompleteWithInterrupts)(int, int *); // unsigned int addr_LiWaitOneChunk; // unsigned int addr_PrepareTitle_hook; // unsigned int addr_sgIsLoadingBuffer; // unsigned int addr_gDynloadInitialized; // unsigned int orig_LiWaitOneChunkInstr; //} OsSpecifics; ================================================ FILE: src/Cafe/OS/RPL/rpl.cpp ================================================ #include <zlib.h> #include "Cafe/OS/common/OSCommon.h" #include "Cafe/Filesystem/fsc.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "util/VirtualHeap/VirtualHeap.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "util/ChunkedHeap/ChunkedHeap.h" #include "util/crypto/crc32.h" #include "config/ActiveSettings.h" #include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" #include "COSModule.h" class PPCCodeHeap : public VHeap { public: PPCCodeHeap(void* heapBase, uint32 heapSize) : VHeap(heapBase, heapSize) { }; void* alloc(uint32 size, uint32 alignment = 4) override { return VHeap::alloc(size, alignment); } void free(void* addr) override { uint32 allocSize = getAllocationSizeFromAddr(addr); MPTR ppcAddr = memory_getVirtualOffsetFromPointer(addr); PPCRecompiler_invalidateRange(ppcAddr, ppcAddr + allocSize); VHeap::free(addr); } }; VHeap rplLoaderHeap_workarea(nullptr, MEMORY_RPLLOADER_AREA_SIZE); PPCCodeHeap rplLoaderHeap_lowerAreaCodeMem2(nullptr, MEMORY_CODE_TRAMPOLINE_AREA_SIZE); PPCCodeHeap rplLoaderHeap_codeArea2(nullptr, MEMORY_CODEAREA_SIZE); ChunkedFlatAllocator<64 * 1024> g_heapTrampolineArea; std::vector<RPLDependency*> rplDependencyList; RPLModule* rplModuleList[256]; sint32 rplModuleCount = 0; bool rplLoader_applicationHasMemoryControl = false; uint32 rplLoader_maxCodeAddress = 0; // highest used code address uint32 rplLoader_currentTLSModuleIndex = 1; // value 0 is reserved uint32 rplLoader_currentHandleCounter = 0x00001000; sint16 rplLoader_currentTlsModuleIndex = 0x0001; RPLModule* rplLoader_mainModule = nullptr; uint32 rplLoader_sdataAddr = MPTR_NULL; // r13 uint32 rplLoader_sdata2Addr = MPTR_NULL; // r2 uint32 rplLoader_currentDataAllocatorAddr = 0x10000000; std::map<void(*)(PPCInterpreter_t* hCPU), uint32> g_map_callableExports; struct RPLMappingRegion { MPTR baseAddress; uint32 endAddress; uint32 calcEndAddress; // used to verify endAddress }; struct RPLRegionMappingTable { RPLMappingRegion region[4]; }; #define RPL_MAPPING_REGION_DATA 0 #define RPL_MAPPING_REGION_LOADERINFO 1 #define RPL_MAPPING_REGION_TEXT 2 #define RPL_MAPPING_REGION_TEMP 3 void RPLLoader_UnloadModule(RPLDependency* rplDependency, bool skipPPCCalls); void RPLLoader_RemoveDependency(std::string_view name); uint8* RPLLoader_AllocateTrampolineCodeSpace(RPLModule* rplLoaderContext, sint32 size) { if (rplLoaderContext) { // allocation owned by rpl return (uint8*)rplLoaderContext->heapTrampolineArea.alloc(size, 4); } // allocation owned by global context auto result = (uint8*)g_heapTrampolineArea.alloc(size, 4); rplLoader_maxCodeAddress = std::max(rplLoader_maxCodeAddress, memory_getVirtualOffsetFromPointer(g_heapTrampolineArea.getCurrentBlockPtr()) + g_heapTrampolineArea.getCurrentBlockOffset()); return result; } uint8* RPLLoader_AllocateTrampolineCodeSpace(sint32 size) { return RPLLoader_AllocateTrampolineCodeSpace(nullptr, size); } MPTR RPLLoader_AllocateCodeSpace(uint32 size, uint32 alignment) { cemu_assert_debug((alignment & (alignment - 1)) == 0); // alignment must be a power of 2 MPTR codeAddr = memory_getVirtualOffsetFromPointer(rplLoaderHeap_codeArea2.alloc(size, alignment)); rplLoader_maxCodeAddress = std::max(rplLoader_maxCodeAddress, codeAddr + size); PPCRecompiler_allocateRange(codeAddr, size); return codeAddr; } uint32 RPLLoader_AllocateDataSpace(RPLModule* rpl, uint32 size, uint32 alignment) { if (rplLoader_applicationHasMemoryControl) { StackAllocator<uint32be> memPtr; *(memPtr.GetPointer()) = 0; PPCCoreCallback(rpl->funcAlloc.value(), size, alignment, memPtr.GetPointer()); return (uint32)*(memPtr.GetPointer()); } rplLoader_currentDataAllocatorAddr = (rplLoader_currentDataAllocatorAddr + alignment - 1) & ~(alignment - 1); uint32 mem = rplLoader_currentDataAllocatorAddr; rplLoader_currentDataAllocatorAddr += size; return mem; } void RPLLoader_FreeData(RPLModule* rpl, void* ptr) { PPCCoreCallback(rpl->funcFree.value(), ptr); } uint32 RPLLoader_GetDataAllocatorAddr() { return (rplLoader_currentDataAllocatorAddr + 0xFFF) & (~0xFFF); } uint32 RPLLoader_GetMaxCodeOffset() { return rplLoader_maxCodeAddress; } #define PPCASM_OPC_R_TEMPL_SIMM(_rD, _rA, _IMM) (((_rD)<<21)|((_rA)<<16)|((_IMM)&0xFFFF)) // generates 32-bit jump. Modifies R11 and CTR MPTR _generateTrampolineFarJump(RPLModule* rplLoaderContext, MPTR destAddr) { auto itr = rplLoaderContext->trampolineMap.find(destAddr); if (itr != rplLoaderContext->trampolineMap.end()) return itr->second; MPTR trampolineAddr = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(rplLoaderContext, 4*4)); uint32 destAddrU32 = (uint32)destAddr; uint32 ppcOpcode = 0; // ADDI R11, R0, ... ppcOpcode = PPCASM_OPC_R_TEMPL_SIMM(11, 0, destAddrU32 & 0xFFFF); ppcOpcode |= (14 << 26); memory_writeU32(trampolineAddr + 0x0, ppcOpcode); // ADDIS R11, R11, ...<<16 ppcOpcode = PPCASM_OPC_R_TEMPL_SIMM(11, 11, ((destAddrU32 >> 16) + ((destAddrU32 >> 15) & 1)) & 0xFFFF); ppcOpcode |= (15 << 26); memory_writeU32(trampolineAddr + 0x4, ppcOpcode); // MTCTR r11 memory_writeU32(trampolineAddr + 0x8, 0x7D6903A6); // BCTR memory_writeU32(trampolineAddr + 0xC, 0x4E800420); // if the destination is a known symbol, create a proxy (duplicate) symbol at the jump rplSymbolStorage_createJumpProxySymbol(trampolineAddr, destAddr); rplLoaderContext->trampolineMap.emplace(destAddr, trampolineAddr); return trampolineAddr; } void* RPLLoader_AllocWorkarea(uint32 size, uint32 alignment, uint32* allocSize) { size = (size + 31)&~31; *allocSize = size; void* allocAddr = rplLoaderHeap_workarea.alloc(size, alignment); cemu_assert(allocAddr != nullptr); memset(allocAddr, 0, size); return allocAddr; } void RPLLoader_FreeWorkarea(void* allocAddr) { rplLoaderHeap_workarea.free(allocAddr); } bool RPLLoader_CheckBounds(RPLModule* rplLoaderContext, uint32 offset, uint32 size) { if ((offset + size) > rplLoaderContext->RPLRawData.size_bytes()) return false; return true; } bool RPLLoader_ProcessHeaders(std::string_view moduleName, uint8* rplData, uint32 rplSize, RPLModule** rplLoaderContextOut) { rplHeaderNew_t* rplHeader = (rplHeaderNew_t*)rplData; *rplLoaderContextOut = nullptr; if (rplHeader->version04 != 0x01) return false; if (rplHeader->ukn05 != 0x02) return false; if (rplHeader->magic2_0 != 0xCA) return false; if (rplHeader->magic2_1 != 0xFE) return false; if (rplHeader->ukn06 > 1) return false; if (rplHeader->ukn12 != 0x14) return false; if (rplHeader->ukn14 != 0x01) return false; if (rplHeader->sectionTableEntryCount < 2) return false; // RPL must end with two sections: CRCS + FILEINFO // setup RPL info struct RPLModule* rplLoaderContext = new RPLModule(); rplLoaderContext->RPLRawData = std::span<uint8>(rplData, rplSize); rplLoaderContext->heapTrampolineArea.setBaseAllocator(&rplLoaderHeap_lowerAreaCodeMem2); // load section table if ((uint32)rplHeader->sectionTableEntrySize != sizeof(rplSectionEntryNew_t)) assert_dbg(); sint32 sectionCount = (sint32)rplHeader->sectionTableEntryCount; sint32 sectionTableSize = (sint32)rplHeader->sectionTableEntrySize * sectionCount; rplLoaderContext->sectionTablePtr = (rplSectionEntryNew_t*)malloc(sectionTableSize); memcpy(rplLoaderContext->sectionTablePtr, rplData + (uint32)(rplHeader->sectionTableOffset), sectionTableSize); // copy rpl header memcpy(&rplLoaderContext->rplHeader, rplHeader, sizeof(rplHeaderNew_t)); // verify that section n-1 is FILEINFO rplSectionEntryNew_t* fileinfoSection = rplLoaderContext->sectionTablePtr + ((uint32)rplLoaderContext->rplHeader.sectionTableEntryCount - 1); if (fileinfoSection->fileOffset == 0 || (uint32)fileinfoSection->fileOffset >= rplSize || (uint32)fileinfoSection->type != SHT_RPL_FILEINFO) { cemuLog_logDebug(LogType::Force, "RPLLoader: Last section not FILEINFO"); } // verify that section n-2 is CRCs rplSectionEntryNew_t* crcSection = rplLoaderContext->sectionTablePtr + ((uint32)rplLoaderContext->rplHeader.sectionTableEntryCount - 2); if (crcSection->fileOffset == 0 || (uint32)crcSection->fileOffset >= rplSize || (uint32)crcSection->type != SHT_RPL_CRCS) { cemuLog_logDebug(LogType::Force, "RPLLoader: The section before FILEINFO must be CRCs"); } // load FILEINFO section if (fileinfoSection->sectionSize < sizeof(RPLFileInfoData)) { cemuLog_log(LogType::Force, "RPLLoader: FILEINFO section size is below expected size"); delete rplLoaderContext; return false; } // read RPL mapping info uint8* fileInfoRawPtr = (uint8*)(rplData + fileinfoSection->fileOffset); if (((uint64)fileinfoSection->fileOffset+fileinfoSection->sectionSize) > (uint64)rplSize) { cemuLog_log(LogType::Force, "RPLLoader: FILEINFO section outside of RPL file bounds"); return false; } rplLoaderContext->sectionData_fileInfo.resize(fileinfoSection->sectionSize); memcpy(rplLoaderContext->sectionData_fileInfo.data(), fileInfoRawPtr, rplLoaderContext->sectionData_fileInfo.size()); RPLFileInfoData* fileInfoPtr = (RPLFileInfoData*)rplLoaderContext->sectionData_fileInfo.data(); if (fileInfoPtr->fileInfoMagic != 0xCAFE0402) { cemuLog_log(LogType::Force, "RPLLoader: Invalid FILEINFO magic"); return false; } // process FILEINFO rplLoaderContext->fileInfo.textRegionSize = fileInfoPtr->textRegionSize; rplLoaderContext->fileInfo.dataRegionSize = fileInfoPtr->dataRegionSize; rplLoaderContext->fileInfo.baseAlign = fileInfoPtr->baseAlign; rplLoaderContext->fileInfo.ukn14 = fileInfoPtr->ukn14; rplLoaderContext->fileInfo.trampolineAdjustment = fileInfoPtr->trampolineAdjustment; rplLoaderContext->fileInfo.ukn4C = fileInfoPtr->ukn4C; rplLoaderContext->fileInfo.tlsModuleIndex = fileInfoPtr->tlsModuleIndex; rplLoaderContext->fileInfo.sdataBase1 = fileInfoPtr->sdataBase1; rplLoaderContext->fileInfo.sdataBase2 = fileInfoPtr->sdataBase2; rplLoaderContext->fileInfo.flags = fileInfoPtr->flags; // init section address table rplLoaderContext->sectionAddressTable2.resize(sectionCount); // init modulename rplLoaderContext->moduleName2.assign(moduleName); // convert modulename to lower-case for(auto& c : rplLoaderContext->moduleName2) c = _ansiToLower(c); // load CRC section uint32 crcTableExpectedSize = sectionCount * sizeof(uint32be); if (!RPLLoader_CheckBounds(rplLoaderContext, crcSection->fileOffset, crcTableExpectedSize)) { cemuLog_log(LogType::Force, "RPLLoader: CRC section outside of RPL file bounds"); crcSection->sectionSize = 0; } else if (crcSection->sectionSize < crcTableExpectedSize) { cemuLog_log(LogType::Force, "RPLLoader: CRC section size (0x{:x}) less than required (0x{:x})", (uint32)crcSection->sectionSize, crcTableExpectedSize); } else if (crcSection->sectionSize != crcTableExpectedSize) { cemuLog_log(LogType::Force, "RPLLoader: CRC section size (0x{:x}) does not match expected size (0x{:x})", (uint32)crcSection->sectionSize, crcTableExpectedSize); } uint32 crcActualSectionCount = crcSection->sectionSize / sizeof(uint32); // how many CRCs are actually stored rplLoaderContext->crcTable.resize(sectionCount); if (crcActualSectionCount > 0) { uint32be* crcTableData = (uint32be*)(rplData + crcSection->fileOffset); for (uint32 i = 0; i < crcActualSectionCount; i++) rplLoaderContext->crcTable[i] = crcTableData[i]; } // verify CRC of FILEINFO section uint32 crcCalcFileinfo = crc32_calc(0, rplLoaderContext->sectionData_fileInfo.data(), rplLoaderContext->sectionData_fileInfo.size()); uint32 crcFileinfo = rplLoaderContext->GetSectionCRC(sectionCount - 1); if (crcCalcFileinfo != crcFileinfo) { cemuLog_log(LogType::Force, "RPLLoader: FILEINFO section has CRC mismatch - Calculated: {:08x} Actual: {:08x}", crcCalcFileinfo, crcFileinfo); } rplLoaderContext->sectionAddressTable2[sectionCount - 1].ptr = rplLoaderContext->sectionData_fileInfo.data(); rplLoaderContext->sectionAddressTable2[sectionCount - 2].ptr = nullptr;// rplLoaderContext->crcTablePtr; // set output *rplLoaderContextOut = rplLoaderContext; return true; } class RPLUncompressedSection { public: std::vector<uint8> sectionData; }; rplSectionEntryNew_t* RPLLoader_GetSection(RPLModule* rplLoaderContext, sint32 sectionIndex) { sint32 sectionCount = rplLoaderContext->rplHeader.sectionTableEntryCount; if (sectionIndex < 0 || sectionIndex >= sectionCount) { cemuLog_log(LogType::Force, "RPLLoader: Section index out of bounds"); rplLoaderContext->hasError = true; return nullptr; } rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + sectionIndex; return section; } RPLUncompressedSection* RPLLoader_LoadUncompressedSection(RPLModule* rplLoaderContext, sint32 sectionIndex) { const rplSectionEntryNew_t* section = RPLLoader_GetSection(rplLoaderContext, sectionIndex); if (section == nullptr) return nullptr; RPLUncompressedSection* uSection = new RPLUncompressedSection(); if ((uint32)section->type == 0x8) { uSection->sectionData.resize(section->sectionSize); std::fill(uSection->sectionData.begin(), uSection->sectionData.end(), 0); return uSection; } // check if raw size does not exceed bounds of rpl if (!RPLLoader_CheckBounds(rplLoaderContext, section->fileOffset, section->sectionSize)) { // BSS cemuLog_log(LogType::Force, "RPLLoader: Raw data for section {} exceeds bounds of RPL file", sectionIndex); rplLoaderContext->hasError = true; delete uSection; return nullptr; } uint32 sectionFlags = section->flags; if ((sectionFlags & SHF_RPL_COMPRESSED) != 0) { // decompress if (!RPLLoader_CheckBounds(rplLoaderContext, section->fileOffset, sizeof(uint32be)) ) { cemuLog_log(LogType::Force, "RPLLoader: Uncompressed data of section {} is too large", sectionIndex); rplLoaderContext->hasError = true; delete uSection; return nullptr; } uint32 uncompressedSize = *(uint32be*)(rplLoaderContext->RPLRawData.data() + (uint32)section->fileOffset); if (uncompressedSize >= 1*1024*1024*1024) // sections bigger than 1GB not allowed { cemuLog_log(LogType::Force, "RPLLoader: Uncompressed data of section {} is too large", sectionIndex); rplLoaderContext->hasError = true; delete uSection; return nullptr; } int ret; z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; ret = inflateInit(&strm); if (ret == Z_OK) { strm.avail_in = (uint32)section->sectionSize - 4; strm.next_in = rplLoaderContext->RPLRawData.data() + (uint32)section->fileOffset + 4; strm.avail_out = uncompressedSize; uSection->sectionData.resize(uncompressedSize); strm.next_out = uSection->sectionData.data(); ret = inflate(&strm, Z_FULL_FLUSH); inflateEnd(&strm); if ((ret != Z_OK && ret != Z_STREAM_END) || strm.avail_in != 0 || strm.avail_out != 0) { cemuLog_log(LogType::Force, "RPLLoader: Error while inflating data for section {}", sectionIndex); rplLoaderContext->hasError = true; delete uSection; return nullptr; } } } else { // no decompression uSection->sectionData.resize(section->sectionSize); const uint8* sectionDataBegin = rplLoaderContext->RPLRawData.data() + (uint32)section->fileOffset; std::copy(sectionDataBegin, sectionDataBegin + section->sectionSize, uSection->sectionData.data()); } return uSection; } bool RPLLoader_LoadSingleSection(RPLModule* rplLoaderContext, sint32 sectionIndex, RPLMappingRegion* regionMappingInfo, MPTR mappedAddress) { rplSectionEntryNew_t* section = RPLLoader_GetSection(rplLoaderContext, sectionIndex); if (section == nullptr) return false; uint32 mappingOffset = (uint32)section->virtualAddress - (uint32)regionMappingInfo->baseAddress; if (mappingOffset >= 0x10000000) cemuLog_logDebug(LogType::Force, "Suspicious section mapping offset: 0x{:08x}", mappingOffset); uint32 sectionAddress = mappedAddress + mappingOffset; rplLoaderContext->sectionAddressTable2[sectionIndex].ptr = memory_getPointerFromVirtualOffset(sectionAddress); cemu_assert(rplLoaderContext->debugSectionLoadMask[sectionIndex] == false); rplLoaderContext->debugSectionLoadMask[sectionIndex] = true; // extract section RPLUncompressedSection* uncompressedSection = RPLLoader_LoadUncompressedSection(rplLoaderContext, sectionIndex); if (uncompressedSection == nullptr) { rplLoaderContext->hasError = true; return false; } // copy to mapped address if(section->virtualAddress < regionMappingInfo->baseAddress || (section->virtualAddress + uncompressedSection->sectionData.size()) > regionMappingInfo->endAddress) cemuLog_log(LogType::Force, "RPLLoader: Section {} (0x{:08x} to 0x{:08x}) is not fully contained in it's bounding region (0x{:08x} to 0x{:08x})", sectionIndex, section->virtualAddress, section->virtualAddress + uncompressedSection->sectionData.size(), regionMappingInfo->baseAddress, regionMappingInfo->endAddress); uint8* sectionAddressPtr = memory_getPointerFromVirtualOffset(sectionAddress); std::copy(uncompressedSection->sectionData.begin(), uncompressedSection->sectionData.end(), sectionAddressPtr); // update size in section (todo - use separate field) if (uncompressedSection->sectionData.size() < section->sectionSize) cemuLog_log(LogType::Force, "RPLLoader: Section {} uncompresses to {} bytes but sectionSize is {}", sectionIndex, uncompressedSection->sectionData.size(), (uint32)section->sectionSize); section->sectionSize = uncompressedSection->sectionData.size(); delete uncompressedSection; return true; } bool RPLLoader_LoadSections(sint32 aProcId, RPLModule* rplLoaderContext) { RPLRegionMappingTable regionMappingTable; memset(®ionMappingTable, 0, sizeof(RPLRegionMappingTable)); regionMappingTable.region[0].baseAddress = 0xFFFFFFFF; regionMappingTable.region[1].baseAddress = 0xFFFFFFFF; regionMappingTable.region[2].baseAddress = 0xFFFFFFFF; regionMappingTable.region[3].baseAddress = 0xFFFFFFFF; for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; uint32 sectionVirtualAddr = section->virtualAddress; uint32 sectionFileOffset = section->fileOffset; uint32 sectionSize = section->sectionSize; if(sectionSize == 0) continue; if (sectionType == SHT_RPL_CRCS) continue; if (sectionType == SHT_RPL_FILEINFO) continue; //if (sectionType == SHT_RPL_IMPORTS) -> The official loader seems to skip these, leading to incorrect boundary calculations // continue; if ((sectionFlags & 2) == 0) { uint32 endFileOffset = sectionFileOffset + sectionSize; regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress, sectionFileOffset); regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress = std::max(regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress, endFileOffset); continue; } if ((sectionFlags & 4) != 0 && sectionType != SHT_RPL_EXPORTS && sectionType != SHT_RPL_IMPORTS) { regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress, sectionVirtualAddr); continue; } if ((sectionFlags & 1) != 0) { regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress, sectionVirtualAddr); continue; } else { regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress, sectionVirtualAddr); continue; } } for (sint32 i = 0; i < 4; i++) { if (regionMappingTable.region[i].baseAddress == 0xFFFFFFFF) regionMappingTable.region[i].baseAddress = 0; } regionMappingTable.region[RPL_MAPPING_REGION_TEXT].endAddress = (regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress + rplLoaderContext->fileInfo.textRegionSize) - rplLoaderContext->fileInfo.trampolineAdjustment; regionMappingTable.region[RPL_MAPPING_REGION_DATA].endAddress = regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress + rplLoaderContext->fileInfo.dataRegionSize; regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].endAddress = (regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress + rplLoaderContext->fileInfo.ukn14) - rplLoaderContext->fileInfo.ukn4C; // calculate region size uint32 regionDataSize = regionMappingTable.region[RPL_MAPPING_REGION_DATA].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress; uint32 regionLoaderinfoSize = regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress; uint32 regionTextSize = regionMappingTable.region[RPL_MAPPING_REGION_TEXT].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress; rplLoaderContext->regionMappingBase_data = RPLLoader_AllocateDataSpace(rplLoaderContext, regionDataSize, 0x1000); rplLoaderContext->regionMappingBase_loaderInfo = RPLLoader_AllocateDataSpace(rplLoaderContext, regionLoaderinfoSize, 0x1000); rplLoaderContext->regionMappingBase_text = rplLoaderHeap_codeArea2.alloc(regionTextSize + 0x1000, 0x1000); rplLoader_maxCodeAddress = std::max(rplLoader_maxCodeAddress, rplLoaderContext->regionMappingBase_text.GetMPTR() + regionTextSize + 0x1000); PPCRecompiler_allocateRange(rplLoaderContext->regionMappingBase_text.GetMPTR(), regionTextSize + 0x1000); // workaround for DKC Tropical Freeze if (boost::iequals(rplLoaderContext->moduleName2, "rs10_production")) { // allocate additional 12MB of unused data to get below a size of 0x3E200000 for the main ExpHeap // otherwise the game will assume it's running on a Devkit unit with 2GB of RAM and subtract 1GB from available space RPLLoader_AllocateDataSpace(rplLoaderContext, 12*1024*1024, 0x1000); } // set region sizes rplLoaderContext->regionSize_data = regionDataSize; rplLoaderContext->regionSize_loaderInfo = regionLoaderinfoSize; rplLoaderContext->regionSize_text = regionTextSize; // load data sections for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; if (section->sectionSize == 0) continue; if( rplLoaderContext->sectionAddressTable2[i].ptr != nullptr ) continue; if ((sectionFlags & 2) == 0) continue; if ((sectionFlags & 1) == 0) continue; RPLLoader_LoadSingleSection(rplLoaderContext, i, regionMappingTable.region + RPL_MAPPING_REGION_DATA, rplLoaderContext->regionMappingBase_data); } // load loaderinfo sections for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; if (section->sectionSize == 0) continue; if (rplLoaderContext->sectionAddressTable2[i].ptr != nullptr) continue; if ((sectionFlags & 2) == 0) continue; if(sectionType != SHT_RPL_EXPORTS && sectionType != SHT_RPL_IMPORTS && (sectionFlags&5) != 0 ) continue; bool readRaw = false; RPLLoader_LoadSingleSection(rplLoaderContext, i, regionMappingTable.region + RPL_MAPPING_REGION_LOADERINFO, rplLoaderContext->regionMappingBase_loaderInfo); if (sectionType == SHT_RPL_EXPORTS) { uint8* sectionAddress = (uint8*)rplLoaderContext->sectionAddressTable2[i].ptr; if ((sectionFlags & 4) != 0) { rplLoaderContext->exportFCount = *(uint32be*)(sectionAddress + 0); rplLoaderContext->exportFDataPtr = (rplExportTableEntry_t*)(sectionAddress + 8); } else { rplLoaderContext->exportDCount = *(uint32be*)(sectionAddress + 0); rplLoaderContext->exportDDataPtr = (rplExportTableEntry_t*)(sectionAddress + 8); } } } // load text sections uint32 textSectionMappedBase = rplLoaderContext->regionMappingBase_text.GetMPTR() + (uint32)rplLoaderContext->fileInfo.trampolineAdjustment; // leave some space for trampolines before the code section begins for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; if( section->sectionSize == 0 ) continue; if (rplLoaderContext->sectionAddressTable2[i].ptr != nullptr) continue; if ((sectionFlags & 2) == 0) continue; if ((sectionFlags & 4) == 0) continue; if( sectionType == SHT_RPL_EXPORTS) continue; if (section->type == 0x8) { cemuLog_log(LogType::Force, "RPLLoader: Unsupported text section type 0x8"); cemu_assert_debug(false); } RPLLoader_LoadSingleSection(rplLoaderContext, i, regionMappingTable.region + RPL_MAPPING_REGION_TEXT, textSectionMappedBase); } // load temp region sections uint32 tempRegionSize = regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress; uint8* tempRegionPtr; uint32 tempRegionAllocSize = 0; tempRegionPtr = (uint8*)RPLLoader_AllocWorkarea(tempRegionSize, 0x20, &tempRegionAllocSize); rplLoaderContext->tempRegionPtr = tempRegionPtr; rplLoaderContext->tempRegionAllocSize = tempRegionAllocSize; memcpy(tempRegionPtr, rplLoaderContext->RPLRawData.data()+regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress, tempRegionSize); // load temp region sections for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; if (section->sectionSize == 0) continue; if (rplLoaderContext->sectionAddressTable2[i].ptr != nullptr) continue; if (sectionType == SHT_RPL_FILEINFO || sectionType == SHT_RPL_CRCS) continue; // calculate offset within temp section uint32 sectionFileOffset = section->fileOffset; uint32 sectionSize = section->sectionSize; cemu_assert_debug(sectionFileOffset >= regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress); cemu_assert_debug((sectionFileOffset + sectionSize) <= regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress); rplLoaderContext->sectionAddressTable2[i].ptr = (tempRegionPtr + (sectionFileOffset - regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress)); uint32 sectionEndAddress = sectionFileOffset + sectionSize; regionMappingTable.region[RPL_MAPPING_REGION_TEMP].calcEndAddress = std::max(regionMappingTable.region[RPL_MAPPING_REGION_TEMP].calcEndAddress, sectionEndAddress); } // todo: Verify calcEndAddress<=endAddress for each region // dump loaded sections /* for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; uint32 sectionFlags = section->flags; if (section->sectionSize == 0) continue; if (rplLoaderContext->sectionAddressTable2[i].ptr == nullptr) continue; FileStream* fs = FileStream::createFile2(fmt::format("dump/rpl_sections/{}_{:08x}_type{:08x}.bin", i, (uint32)section->virtualAddress, (uint32)sectionType)); fs->writeData(rplLoaderContext->sectionAddressTable2[i].ptr, section->sectionSize); delete fs; } */ return true; } struct RPLFileSymtabEntry { /* +0x0 */ uint32be ukn00; /* +0x4 */ uint32be symbolAddress; /* +0x8 */ uint32be ukn08; /* +0xC */ uint8 info; /* +0xD */ uint8 ukn0D; /* +0xE */ uint16be sectionIndex; }; struct RPLSharedImportTracking { RPLModule* rplLoaderContext; // rpl loader context of module with exports rplSectionEntryNew_t* exportSection; // export section char modulename[RPL_MODULE_NAME_LENGTH]; }; static_assert(sizeof(RPLFileSymtabEntry) == 0x10, "rplSymtabEntry_t has invalid size"); typedef struct { uint64 hash1; uint64 hash2; uint32 address; }mappedFunctionImport_t; std::vector<mappedFunctionImport_t> list_mappedFunctionImports = std::vector<mappedFunctionImport_t>(); void _calculateMappedImportNameHash(const char* rplName, const char* funcName, uint64* h1Out, uint64* h2Out) { uint64 h1 = 0; uint64 h2 = 0; // rplName while (*rplName) { uint64 v = (uint64)*rplName; h1 += v; h2 ^= v; h1 = std::rotl<uint64>(h1, 3); h2 = std::rotl<uint64>(h2, 7); rplName++; } // funcName while (*funcName) { uint64 v = (uint64)*funcName; h1 += v; h2 ^= v; h1 = std::rotl<uint64>(h1, 3); h2 = std::rotl<uint64>(h2, 7); funcName++; } *h1Out = h1; *h2Out = h2; } uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(PPCInterpreter_t* hCPU)) { auto it = g_map_callableExports.find(ppcCallableExport); if (it != g_map_callableExports.end()) return it->second; // get HLE function index sint32 functionIndex = PPCInterpreter_registerHLECall(ppcCallableExport, fmt::format("PPCCallback{:x}", (uintptr_t)ppcCallableExport)); MPTR codeAddr = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(4)); uint32 opcode = (1 << 26) | functionIndex; memory_write<uint32>(codeAddr, opcode); g_map_callableExports[ppcCallableExport] = codeAddr; return codeAddr; } uint32 rpl_mapHLEImport(RPLModule* rplLoaderContext, const char* rplName, const char* funcName, bool functionMustExist) { // calculate import name hash uint64 mappedImportHash1; uint64 mappedImportHash2; _calculateMappedImportNameHash(rplName, funcName, &mappedImportHash1, &mappedImportHash2); // find already mapped name for (auto& importItr : list_mappedFunctionImports) { if (importItr.hash1 == mappedImportHash1 && importItr.hash2 == mappedImportHash2) { return importItr.address; } } // copy lib file name and cut off .rpl from libName if present char libName[512]; strcpy_s(libName, rplName); for (sint32 i = 0; i < sizeof(libName); i++) { if (libName[i] == '\0') break; if (libName[i] == '.') { libName[i] = '\0'; break; } } // find import in list of known exports sint32 functionIndex = osLib_getFunctionIndex(libName, funcName); if (functionIndex >= 0) { MPTR codeAddr = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(4)); uint32 opcode = (1 << 26) | functionIndex; memory_write<uint32>(codeAddr, opcode); // register mapped import mappedFunctionImport_t newImport; newImport.hash1 = mappedImportHash1; newImport.hash2 = mappedImportHash2; newImport.address = codeAddr; list_mappedFunctionImports.push_back(newImport); // remember in symbol storage for debugger rplSymbolStorage_store(libName, funcName, codeAddr); return codeAddr; } else { if (functionMustExist == false) return MPTR_NULL; } // create code for unsupported import uint32 codeStart = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(256)); uint32 currentAddress = codeStart; uint32 opcode = (1 << 26) | (0xFFD0); // opcode for HLE: Unsupported import memory_write<uint32>(codeStart + 0, opcode); memory_write<uint32>(codeStart + 4, 0x4E800020); currentAddress += 8; // write name of lib/function sint32 libNameLength = std::min(128, (sint32)strlen(libName)); sint32 funcNameLength = std::min(128, (sint32)strlen(funcName)); memcpy(memory_getPointerFromVirtualOffset(currentAddress), libName, libNameLength); currentAddress += libNameLength; memory_writeU8(currentAddress, '.'); currentAddress++; memcpy(memory_getPointerFromVirtualOffset(currentAddress), funcName, funcNameLength); currentAddress += funcNameLength; memory_writeU8(currentAddress, '\0'); currentAddress++; // align address to 4 byte boundary currentAddress = (currentAddress + 3)&~3; // register mapped import mappedFunctionImport_t newImport; newImport.hash1 = mappedImportHash1; newImport.hash2 = mappedImportHash2; newImport.address = codeStart; list_mappedFunctionImports.push_back(newImport); // remember in symbol storage for debugger rplSymbolStorage_store(libName, funcName, codeStart); // return address of code start return codeStart; } MPTR RPLLoader_FindRPLExport(RPLModule* rplLoaderContext, const char* symbolName, bool isData) { if (isData) { cemu_assert_debug(false); // todo - look in DDataPtr } if (rplLoaderContext->exportFDataPtr) { char* exportNameData = (char*)((uint8*)rplLoaderContext->exportFDataPtr - 8); for (uint32 f = 0; f < rplLoaderContext->exportFCount; f++) { char* name = exportNameData + (uint32)rplLoaderContext->exportFDataPtr[f].nameOffset; if (strcmp(name, symbolName) == 0) { return (uint32)rplLoaderContext->exportFDataPtr[f].virtualOffset; } } } return MPTR_NULL; } MPTR _findHLEExport(RPLModule* rplLoaderContext, RPLSharedImportTracking* sharedImportTrackingEntry, char* libname, char* symbolName, bool isData) { if (isData) { // data export MPTR weakExportAddr = osLib_getPointer(libname, symbolName); if (weakExportAddr != 0xFFFFFFFF) return weakExportAddr; cemuLog_logDebug(LogType::Force, "Unsupported data export ({}): {}.{}", rplLoaderContext->moduleName2, libname, symbolName); return MPTR_NULL; } else { // try to find HLE/placeholder export MPTR mappedFunctionAddr = rpl_mapHLEImport(rplLoaderContext, libname, symbolName, true); if (mappedFunctionAddr == MPTR_NULL) cemu_assert_debug(false); return mappedFunctionAddr; } } uint32 RPLLoader_FindModuleExport(RPLModule* rplLoaderContext, bool isData, const char* exportName) { if (isData == false) { // find function export char* exportNameData = (char*)((uint8*)rplLoaderContext->exportFDataPtr - 8); for (uint32 f = 0; f < rplLoaderContext->exportFCount; f++) { char* name = exportNameData + (uint32)rplLoaderContext->exportFDataPtr[f].nameOffset; if (strcmp(name, exportName) == 0) { uint32 exportAddress = rplLoaderContext->exportFDataPtr[f].virtualOffset; return exportAddress; } } } else { // find data export char* exportNameData = (char*)((uint8*)rplLoaderContext->exportDDataPtr - 8); for (uint32 f = 0; f < rplLoaderContext->exportDCount; f++) { char* name = exportNameData + (uint32)rplLoaderContext->exportDDataPtr[f].nameOffset; if (strcmp(name, exportName) == 0) { uint32 exportAddress = rplLoaderContext->exportDDataPtr[f].virtualOffset; return exportAddress; } } } return 0; } bool RPLLoader_FixImportSymbols(RPLModule* rplLoaderContext, sint32 symtabSectionIndex, rplSectionEntryNew_t* symTabSection, std::span<RPLSharedImportTracking> sharedImportTracking, uint32 linkMode) { uint32 sectionSize = symTabSection->sectionSize; uint32 symbolEntrySize = symTabSection->ukn24; if (symbolEntrySize == 0) symbolEntrySize = 0x10; cemu_assert(symbolEntrySize == 0x10); cemu_assert((sectionSize % symbolEntrySize) == 0); uint32 symbolCount = sectionSize / symbolEntrySize; cemu_assert(symbolCount >= 2); uint16 sectionCount = rplLoaderContext->rplHeader.sectionTableEntryCount; uint8* symtabData = (uint8*)rplLoaderContext->sectionAddressTable2[symtabSectionIndex].ptr; uint32 strtabSectionIndex = symTabSection->symtabSectionIndex; uint8* strtabData = (uint8*)rplLoaderContext->sectionAddressTable2[strtabSectionIndex].ptr; uint32 strtabSize = rplLoaderContext->sectionTablePtr[strtabSectionIndex].sectionSize; for (uint32 i = 0; i < symbolCount; i++) { RPLFileSymtabEntry* sym = (RPLFileSymtabEntry*)(symtabData + i*symbolEntrySize); uint16 symSectionIndex = sym->sectionIndex; if (symSectionIndex == 0 || symSectionIndex >= sectionCount) continue; void* symbolSectionAddress = rplLoaderContext->sectionAddressTable2[symSectionIndex].ptr; if (symbolSectionAddress == nullptr) { sym->symbolAddress = 0xCD000000 | i; continue; } rplSectionEntryNew_t* symbolSection = rplLoaderContext->sectionTablePtr + symSectionIndex; uint32 symbolOffset = sym->symbolAddress - symbolSection->virtualAddress; if (symSectionIndex >= sharedImportTracking.size()) { cemuLog_log(LogType::Force, "RPL-Loader: Symbol {} references invalid section", i); } else if (sharedImportTracking[symSectionIndex].rplLoaderContext != nullptr) { if (linkMode == 0) { continue; // ? } if (symbolOffset < 8) { cemu_assert(symbolSectionAddress >= memory_base && symbolSectionAddress <= (memory_base + 0x100000000ULL)); uint32 symbolSectionMPTR = memory_getVirtualOffsetFromPointer(symbolSectionAddress); uint32 symbolRelativeAddress = (uint32)sym->symbolAddress - (uint32)symbolSection->virtualAddress; sym->symbolAddress = (symbolSectionMPTR + symbolRelativeAddress); continue; // ? } if (sharedImportTracking[symSectionIndex].rplLoaderContext == HLE_MODULE_PTR) { // get address uint32 nameOffset = sym->ukn00; char* symbolName = (char*)strtabData + nameOffset; if (nameOffset >= strtabSize) { cemuLog_log(LogType::Force, "RPLLoader: Symbol {} in section {} has out-of-bounds name offset", i, symSectionIndex); continue; } uint32 exportAddress; if (nameOffset == 0) { cemu_assert_debug(symbolName[0] == '\0'); exportAddress = 0; } else { bool isDataExport = (rplLoaderContext->sectionTablePtr[symSectionIndex].flags & 0x4) == 0; exportAddress = _findHLEExport(rplLoaderContext, sharedImportTracking.data() + symSectionIndex, sharedImportTracking[symSectionIndex].modulename, symbolName, isDataExport); } sym->symbolAddress = exportAddress; } else { RPLModule* ctxExportModule = sharedImportTracking[symSectionIndex].rplLoaderContext; uint32 nameOffset = sym->ukn00; char* symbolName = (char*)strtabData + nameOffset; bool foundExport = false; if ((rplLoaderContext->sectionTablePtr[symSectionIndex].flags & 0x4) != 0) { // find function export char* exportNameData = (char*)((uint8*)ctxExportModule->exportFDataPtr - 8); for (uint32 f = 0; f < ctxExportModule->exportFCount; f++) { char* name = exportNameData + (uint32)ctxExportModule->exportFDataPtr[f].nameOffset; if (strcmp(name, symbolName) == 0) { uint32 exportAddress = ctxExportModule->exportFDataPtr[f].virtualOffset; sym->symbolAddress = exportAddress; foundExport = true; break; } } } else { // find data export char* exportNameData = (char*)((uint8*)ctxExportModule->exportDDataPtr - 8); for (uint32 f = 0; f < ctxExportModule->exportDCount; f++) { char* name = exportNameData + (uint32)ctxExportModule->exportDDataPtr[f].nameOffset; if (strcmp(name, symbolName) == 0) { uint32 exportAddress = ctxExportModule->exportDDataPtr[f].virtualOffset; sym->symbolAddress = exportAddress; foundExport = true; break; } } } if (foundExport == false) { #ifdef CEMU_DEBUG_ASSERT if (nameOffset > 0) { cemuLog_logDebug(LogType::Force, "export not found - force lookup in function exports"); // workaround - force look up export in function exports char* exportNameData = (char*)((uint8*)ctxExportModule->exportFDataPtr - 8); for (uint32 f = 0; f < ctxExportModule->exportFCount; f++) { char* name = exportNameData + (uint32)ctxExportModule->exportFDataPtr[f].nameOffset; if (strcmp(name, symbolName) == 0) { uint32 exportAddress = ctxExportModule->exportFDataPtr[f].virtualOffset; sym->symbolAddress = exportAddress; foundExport = true; break; } } } #endif continue; } } } else { uint32 symbolType = sym->info & 0xF; if (symbolType == 6) continue; if (((uint32)symbolSection->type != SHT_RPL_IMPORTS && linkMode != 2) || ((uint32)symbolSection->type == SHT_RPL_IMPORTS && linkMode != 1 && linkMode != 2) ) { // update virtual address to match actual mapped address cemu_assert(symbolSectionAddress >= memory_base && symbolSectionAddress <= (memory_base + 0x100000000ULL)); uint32 symbolSectionMPTR = memory_getVirtualOffsetFromPointer(symbolSectionAddress); uint32 symbolRelativeAddress = (uint32)sym->symbolAddress - (uint32)symbolSection->virtualAddress; sym->symbolAddress = (symbolSectionMPTR + symbolRelativeAddress); } } } return true; } bool RPLLoader_ApplySingleReloc(RPLModule* rplLoaderContext, uint32 uknR3, uint8* relocTargetSectionAddress, uint32 relocType, bool isSymbolBinding2, uint32 relocOffset, uint32 relocAddend, uint32 symbolAddress, sint16 tlsModuleIndex) { MPTR relocTargetSectionMPTR = memory_getVirtualOffsetFromPointer(relocTargetSectionAddress); MPTR relocAddrMPTR = relocTargetSectionMPTR + relocOffset; uint8* relocAddr = memory_getPointerFromVirtualOffset(relocAddrMPTR); if (relocType == RPL_RELOC_HA16) { uint32 relocDestAddr = symbolAddress + relocAddend; uint32 p = (relocDestAddr >> 16); p += (relocDestAddr >> 15) & 1; *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == RPL_RELOC_LO16) { uint32 relocDestAddr = symbolAddress + relocAddend; uint32 p = relocDestAddr; *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == RPL_RELOC_HI16) { uint32 relocDestAddr = symbolAddress + relocAddend; uint32 p = relocDestAddr>>16; *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == RPL_RELOC_REL24) { // todo - effect of isSymbolBinding2? uint32 opc = *(uint32be*)relocAddr; uint32 relocDestAddr = symbolAddress + relocAddend; uint32 jumpDistance = relocDestAddr - memory_getVirtualOffsetFromPointer(relocAddr); if ((jumpDistance>>25) != 0 && (jumpDistance >> 25) != 0x7F) { // can't reach with 24bit jump, use trampoline + absolute branch MPTR trampolineAddr = _generateTrampolineFarJump(rplLoaderContext, relocDestAddr); // make absolute branch cemu_assert_debug((opc >> 26) == 18); // should be B/BL instruction opc &= ~0x03fffffc; opc |= (trampolineAddr & 0x3FFFFFC); opc |= (1 << 1); // absolute jump *(uint32be*)relocAddr = opc; } else { // within range, update jump opcode if ((jumpDistance & 3) != 0) cemuLog_log(LogType::Force, "RPL-Loader: Encountered unaligned RPL_RELOC_REL24"); opc &= ~0x03fffffc; opc |= (jumpDistance &0x03fffffc); *(uint32be*)relocAddr = opc; } } else if (relocType == RPL_RELOC_REL14) { // seen in Your Shape: Fitness Evolved // todo - effect of isSymbolBinding2? uint32 opc = *(uint32be*)relocAddr; uint32 relocDestAddr = symbolAddress + relocAddend; uint32 jumpDistance = relocDestAddr - memory_getVirtualOffsetFromPointer(relocAddr); if ((jumpDistance & ~0x7fff) != 0xFFFF8000 && (jumpDistance & ~0x7fff) != 0x00000000) { cemu_assert_debug(false); } else { // within range, update jump opcode if ((jumpDistance & 3) != 0) cemuLog_log(LogType::Force, "RPL-Loader: Encountered unaligned RPL_RELOC_REL14"); opc &= ~0xfffc; opc |= (jumpDistance & 0xfffc); *(uint32be*)relocAddr = opc; } } else if (relocType == RPL_RELOC_ADDR32) { uint32 relocDestAddr = symbolAddress + relocAddend; uint32 p = relocDestAddr; *(uint32be*)(relocAddr) = (uint32)p; } else if (relocType == R_PPC_DTPMOD32) { // patch tls_index.moduleIndex *(uint32be*)(relocAddr) = (uint32)(sint32)tlsModuleIndex; } else if (relocType == R_PPC_DTPREL32) { // patch tls_index.size *(uint32be*)(relocAddr) = (uint32)(sint32)(symbolAddress + relocAddend); } else if (relocType == R_PPC_REL16_HA) { // used by WUT uint32 relAddr = (symbolAddress + relocAddend) - relocAddrMPTR; uint32 p = (relAddr >> 16); p += (relAddr >> 15) & 1; *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == R_PPC_REL16_HI) { // used by WUT uint32 relAddr = (symbolAddress + relocAddend) - relocAddrMPTR; uint32 p = (relAddr >> 16); *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == R_PPC_REL16_LO) { // used by WUT uint32 relAddr = (symbolAddress + relocAddend) - relocAddrMPTR; uint32 p = (relAddr & 0xFFFF); *(uint16be*)(relocAddr) = (uint16)p; } else if (relocType == 0x6D) // SDATA reloc { uint32 opc = *(uint32be*)relocAddr; uint32 registerIndex = (opc >> 16) & 0x1F; uint32 destination = (symbolAddress + relocAddend);; if (registerIndex == 2) { uint32 offset = destination - rplLoader_sdata2Addr; uint32 newOpc = (opc & 0xffe00000) | (offset & 0xffff) | (registerIndex << 16); *(uint32be*)relocAddr = newOpc; } else if (registerIndex == 13) { uint32 offset = destination - rplLoader_sdataAddr; uint32 newOpc = (opc & 0xffe00000) | (offset & 0xffff) | (registerIndex << 16); *(uint32be*)relocAddr = newOpc; } else { cemuLog_log(LogType::Force, "RPLLoader: sdata reloc uses register other than r2/r13"); cemu_assert(false); } } else if (relocType == 0xFB) { // relative offset - high uint32 relocDestAddr = symbolAddress + relocAddend; uint32 relativeOffset = relocDestAddr - relocAddrMPTR; uint16 prevValue = *(uint16be*)relocAddr; uint32 newImm = ((relativeOffset >> 16) + ((relativeOffset >> 15) & 0x1)); newImm &= 0xFFFF; *(uint16be*)relocAddr = newImm; if (symbolAddress != 0) { cemu_assert_debug((uint32)prevValue == newImm); } } else if (relocType == 0xFD) { // relative offset - low uint32 relocDestAddr = symbolAddress + relocAddend; uint32 relativeOffset = relocDestAddr - relocAddrMPTR; uint16 prevValue = *(uint16be*)relocAddr; uint32 newImm = relativeOffset; newImm &= 0xFFFF; *(uint16be*)relocAddr = newImm; if (symbolAddress != 0) { cemu_assert_debug((uint32)prevValue == newImm); } } else { cemuLog_log(LogType::Force, "RPLLoader: Unsupported reloc type 0x{:02x}", relocType); cemu_assert_debug(false); // unknown reloc type } return true; } bool RPLLoader_ApplyRelocs(RPLModule* rplLoaderContext, sint32 relaSectionIndex, rplSectionEntryNew_t* section, uint32 linkMode) { uint32 relocTargetSectionIndex = section->relocTargetSectionIndex; if (relocTargetSectionIndex >= (uint32)rplLoaderContext->rplHeader.sectionTableEntryCount) assert_dbg(); uint32 symtabSectionIndex = section->symtabSectionIndex; uint8* relocTargetSectionAddress = (uint8*)(rplLoaderContext->sectionAddressTable2[relocTargetSectionIndex].ptr); cemu_assert(relocTargetSectionAddress); // get symtab info rplSectionEntryNew_t* symtabSection = rplLoaderContext->sectionTablePtr + symtabSectionIndex; uint32 symtabSectionSize = symtabSection->sectionSize; uint32 symbolEntrySize = symtabSection->ukn24; if (symbolEntrySize == 0) symbolEntrySize = 0x10; cemu_assert(symbolEntrySize == 0x10); cemu_assert((symtabSectionSize % symbolEntrySize) == 0); uint32 symbolCount = symtabSectionSize / symbolEntrySize; cemu_assert(symbolCount >= 2); uint8* symtabData = (uint8*)rplLoaderContext->sectionAddressTable2[symtabSectionIndex].ptr; // decompress reloc section if needed uint8* relocData; uint32 relocSize; if ((uint32)(section->flags) & SHF_RPL_COMPRESSED) { uint8* relocRawData = (uint8*)rplLoaderContext->sectionAddressTable2[relaSectionIndex].ptr; uint32 relocUncompressedSize = *(uint32be*)relocRawData; relocData = (uint8*)malloc(relocUncompressedSize); relocSize = relocUncompressedSize; // decompress int ret; z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; ret = inflateInit(&strm); if (ret == Z_OK) { strm.avail_in = (uint32)section->sectionSize - 4; strm.next_in = relocRawData + 4; strm.avail_out = relocUncompressedSize; strm.next_out = relocData; ret = inflate(&strm, Z_FULL_FLUSH); cemu_assert_debug(ret == Z_OK || ret == Z_STREAM_END); cemu_assert_debug(strm.avail_in == 0 && strm.avail_out == 0); inflateEnd(&strm); } } else { relocData = (uint8*)rplLoaderContext->sectionAddressTable2[relaSectionIndex].ptr; relocSize = section->sectionSize; } // check CRC uint32 calcCRC = crc32_calc(0, relocData, relocSize); uint32 crc = rplLoaderContext->GetSectionCRC(relaSectionIndex); if (calcCRC != crc) { cemuLog_log(LogType::Force, "RPLLoader {} - Relocation section {} has CRC mismatch - Calc: {:08x} Actual: {:08x}", rplLoaderContext->moduleName2.c_str(), relaSectionIndex, calcCRC, crc); } // process relocations sint32 relocCount = relocSize / sizeof(rplRelocNew_t); rplRelocNew_t* reloc = (rplRelocNew_t*)relocData; for (sint32 i = 0; i < relocCount; i++) { uint32 relocType = (uint32)reloc->symbolIndexAndType & 0xFF; uint32 relocSymbolIndex = (uint32)reloc->symbolIndexAndType >> 8; if (relocType == 0) { // next reloc++; continue; } if (relocSymbolIndex >= symbolCount) { cemuLog_logDebug(LogType::Force, "reloc with symbol index out of range 0x{:04x}", (uint32)relocSymbolIndex); reloc++; continue; } // get symbol RPLFileSymtabEntry* sym = (RPLFileSymtabEntry*)(symtabData + symbolEntrySize*relocSymbolIndex); if ((uint32)sym->sectionIndex >= (uint32)rplLoaderContext->rplHeader.sectionTableEntryCount) { cemuLog_logDebug(LogType::Force, "reloc with sectionIndex out of range 0x{:04x}", (uint32)sym->sectionIndex); reloc++; continue; } // exclude symbols that arent ready yet if (linkMode == 0) { if ((uint32)rplLoaderContext->sectionTablePtr[(uint32)sym->sectionIndex].type == SHT_RPL_IMPORTS) { reloc++; continue; } } uint32 symbolAddress = sym->symbolAddress; uint8 symbolType = (sym->info >> 0) & 0xF; uint8 symbolBinding = (sym->info >> 4) & 0xF; if ((symbolAddress&0xFF000000) == 0xCD000000) { cemu_assert_unimplemented(); // next reloc++; continue; } sint16 tlsModuleIndex = -1; if (relocType == R_PPC_DTPMOD32 || relocType == R_PPC_DTPREL32) { // TLS-relocation if (symbolType != 6) assert_dbg(); // not a TLS symbol if (rplLoaderContext->fileInfo.tlsModuleIndex == -1) { cemuLog_log(LogType::Force, "RPLLoader: TLS relocs applied to non-TLS module"); cemu_assert_debug(false); // module not a TLS-module } tlsModuleIndex = rplLoaderContext->fileInfo.tlsModuleIndex; } uint32 relocOffset = (uint32)reloc->relocOffset - (uint32)rplLoaderContext->sectionTablePtr[relocTargetSectionIndex].virtualAddress; RPLLoader_ApplySingleReloc(rplLoaderContext, 0, relocTargetSectionAddress, relocType, symbolBinding == 2, relocOffset, reloc->relocAddend, symbolAddress, tlsModuleIndex); // next reloc reloc++; } if ((uint32)(section->flags) & SHF_RPL_COMPRESSED) free(relocData); return true; } bool RPLLoader_HandleRelocs(RPLModule* rplLoaderContext, std::span<RPLSharedImportTracking> sharedImportTracking, uint32 linkMode) { // resolve relocs for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; if( sectionType != SHT_SYMTAB ) continue; RPLLoader_FixImportSymbols(rplLoaderContext, i, section, sharedImportTracking, linkMode); } // apply relocs again after we have fixed the import section for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; if (sectionType != SHT_RELA) continue; RPLLoader_ApplyRelocs(rplLoaderContext, i, section, linkMode); } return true; } void _RPLLoader_ExtractModuleNameFromPath(char* output, std::string_view input) { // scan to last '/' cemu_assert(!input.empty()); size_t startIndex = input.size() - 1; while (startIndex > 0) { if (input[startIndex] == '/') { startIndex++; break; } startIndex--; } // cut off after '.' size_t endIndex = startIndex; while (endIndex < input.size()) { if (input[endIndex] == '.') break; endIndex++; } size_t nameLen = endIndex - startIndex; cemu_assert(nameLen != 0); nameLen = std::min<size_t>(nameLen, RPL_MODULE_NAME_LENGTH-1); memcpy(output, input.data() + startIndex, nameLen); output[nameLen] = '\0'; // convert to lower case std::for_each(output, output + nameLen, [](char& c) {c = _ansiToLower(c);}); } void RPLLoader_InitState() { cemu_assert_debug(!rplLoaderHeap_lowerAreaCodeMem2.hasAllocations()); cemu_assert_debug(!rplLoaderHeap_codeArea2.hasAllocations()); cemu_assert_debug(!rplLoaderHeap_workarea.hasAllocations()); rplLoaderHeap_lowerAreaCodeMem2.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_CODE_TRAMPOLINE_AREA_ADDR)); rplLoaderHeap_codeArea2.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_CODEAREA_ADDR)); rplLoaderHeap_workarea.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_RPLLOADER_AREA_ADDR)); g_heapTrampolineArea.setBaseAllocator(&rplLoaderHeap_lowerAreaCodeMem2); RPLLoader_UnloadAll(); } void RPLLoader_BeginCemuhookCRC(RPLModule* rpl) { // calculate some values required for CRC sint32 sectionSymTableIndex = -1; sint32 sectionStrTableIndex = -1; for (sint32 i = 0; i < rpl->rplHeader.sectionTableEntryCount; i++) { if (rpl->sectionTablePtr[i].type == SHT_SYMTAB) sectionSymTableIndex = i; if (rpl->sectionTablePtr[i].type == SHT_STRTAB && i != rpl->rplHeader.nameSectionIndex && sectionStrTableIndex == -1) sectionStrTableIndex = i; } // init patches CRC rpl->patchCRC = 0; static const uint8 rplMagic[4] = { 0x7F, 'R', 'P', 'X' }; rpl->patchCRC = crc32_calc(rpl->patchCRC, rplMagic, sizeof(rplMagic)); sint32 sectionCount = rpl->rplHeader.sectionTableEntryCount; rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionCount, sizeof(sectionCount)); rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionSymTableIndex, sizeof(sectionSymTableIndex)); rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionStrTableIndex, sizeof(sectionStrTableIndex)); sint32 sectionSectNameTableIndex = rpl->rplHeader.nameSectionIndex; rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionSectNameTableIndex, sizeof(sectionSectNameTableIndex)); // sections for (sint32 i = 0; i < rpl->rplHeader.sectionTableEntryCount; i++) { auto sect = rpl->sectionTablePtr + i; uint32 nameOffset = sect->nameOffset; uint32 shType = sect->type; uint32 flags = sect->flags; uint32 virtualAddress = sect->virtualAddress; uint32 alignment = sect->alignment; uint32 sectionFileOffset = sect->fileOffset; uint32 sectionCompressedSize = sect->sectionSize; uint32 rawSize = 0; bool memoryAllocated = false; void* rawData = nullptr; if (shType == SHT_NOBITS) { rawData = NULL; rawSize = sectionCompressedSize; } else if ((flags&SHF_RPL_COMPRESSED) != 0) { uint32 decompressedSize = _swapEndianU32(*(uint32*)(rpl->RPLRawData.data() + sectionFileOffset)); rawSize = decompressedSize; if (rawSize >= 1024*1024*1024) { cemuLog_logDebug(LogType::Force, "RPLLoader-CRC: Cannot load section {} which is too large ({} bytes)", i, decompressedSize); cemu_assert_debug(false); continue; } rawData = (uint8*)malloc(decompressedSize); if (rawData == nullptr) { cemuLog_logDebug(LogType::Force, "RPLLoader-CRC: Failed to allocate memory for uncompressed section {} ({} bytes)", i, decompressedSize); cemu_assert_debug(false); continue; } memoryAllocated = true; // decompress z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; inflateInit(&strm); strm.avail_in = sectionCompressedSize - 4; strm.next_in = rpl->RPLRawData.data() + (uint32)sectionFileOffset + 4; strm.avail_out = decompressedSize; strm.next_out = (Bytef*)rawData; auto ret = inflate(&strm, Z_FULL_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END || strm.avail_in != 0 || strm.avail_out != 0) { cemuLog_logDebug(LogType::Force, "RPLLoader-CRC: Unable to decompress section {}", i); cemuLog_logDebug(LogType::Force, "zRet {} availIn {} availOut {}", ret, (sint32)strm.avail_in, (sint32)strm.avail_out); cemu_assert_debug(false); free(rawData); inflateEnd(&strm); continue; } inflateEnd(&strm); } else { rawSize = sectionCompressedSize; rawData = rpl->RPLRawData.data() + sectionFileOffset; } rpl->patchCRC = crc32_calc(rpl->patchCRC, &nameOffset, sizeof(nameOffset)); rpl->patchCRC = crc32_calc(rpl->patchCRC, &shType, sizeof(shType)); rpl->patchCRC = crc32_calc(rpl->patchCRC, &flags, sizeof(flags)); rpl->patchCRC = crc32_calc(rpl->patchCRC, &virtualAddress, sizeof(virtualAddress)); rpl->patchCRC = crc32_calc(rpl->patchCRC, &rawSize, sizeof(rawSize)); rpl->patchCRC = crc32_calc(rpl->patchCRC, &alignment, sizeof(alignment)); if (rawData != nullptr && rawSize > 0) { rpl->patchCRC = crc32_calc(rpl->patchCRC, rawData, rawSize); } if (memoryAllocated && rawData) free(rawData); } } void RPLLoader_incrementModuleDependencyRefs(RPLModule* rpl) { for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) { if (rpl->sectionTablePtr[i].type != (uint32be)SHT_RPL_IMPORTS) continue; char* libName = (char*)((uint8*)rpl->sectionAddressTable2[i].ptr + 8); RPLLoader_AddDependency(libName); } } void RPLLoader_decrementModuleDependencyRefs(RPLModule* rpl) { for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) { if (rpl->sectionTablePtr[i].type != (uint32be)SHT_RPL_IMPORTS) continue; char* libName = (char*)((uint8*)rpl->sectionAddressTable2[i].ptr + 8); RPLLoader_RemoveDependency(libName); } } void RPLLoader_UpdateEntrypoint(RPLModule* rpl) { uint32 virtualEntrypoint = rpl->rplHeader.entrypoint; uint32 entrypoint = 0xFFFFFFFF; for (sint32 i = 0; i < (sint32)rpl->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rpl->sectionTablePtr + i; uint32 sectionStartAddr = (uint32)section->virtualAddress; uint32 sectionEndAddr = (uint32)section->virtualAddress + (uint32)section->sectionSize; if (virtualEntrypoint >= sectionStartAddr && virtualEntrypoint < sectionEndAddr) { cemu_assert_debug(entrypoint == 0xFFFFFFFF); entrypoint = (virtualEntrypoint - sectionStartAddr + memory_getVirtualOffsetFromPointer(rpl->sectionAddressTable2[i].ptr)); } } cemu_assert(entrypoint != 0xFFFFFFFF); rpl->entrypoint = entrypoint; } void RPLLoader_InitModuleAllocator(RPLModule* rpl) { if (!rplLoader_applicationHasMemoryControl) { rpl->funcAlloc = 0; rpl->funcFree = 0; return; } coreinit::OSDynLoad_GetAllocator(&rpl->funcAlloc, &rpl->funcFree); } // map rpl into memory, but do not resolve relocs and imports yet RPLModule* RPLLoader_LoadFromMemory(uint8* rplData, sint32 size, std::string_view name) { char moduleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(moduleName, name); RPLModule* rpl = nullptr; if (RPLLoader_ProcessHeaders({ moduleName }, rplData, size, &rpl) == false) { delete rpl; return nullptr; } RPLLoader_InitModuleAllocator(rpl); RPLLoader_BeginCemuhookCRC(rpl); if (RPLLoader_LoadSections(0, rpl) == false) { delete rpl; return nullptr; } cemuLog_logDebug(LogType::Force, "Load {} Code-Offset: -0x{:x}", name, rpl->regionMappingBase_text.GetMPTR() - 0x02000000); // sdata (r2/r13) uint32 sdataBaseAddress = rpl->fileInfo.sdataBase1; // base + 0x8000 uint32 sdataBaseAddress2 = rpl->fileInfo.sdataBase2; // base + 0x8000 for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) { if(rpl->sectionTablePtr[i].sectionSize == 0) continue; uint32 sectionFlags = rpl->sectionTablePtr[i].flags; uint32 sectionVirtualAddress = rpl->sectionTablePtr[i].virtualAddress; uint32 sectionSize = rpl->sectionTablePtr[i].sectionSize; if( (sectionFlags&4) != 0 ) continue; if(sdataBaseAddress == 0x00008000 && sdataBaseAddress2 == 0x00008000) continue; // sdata not used (this workaround is needed for Wii U Party to boot) // sdata 1 if ((sdataBaseAddress - 0x8000) >= (sectionVirtualAddress) && (sdataBaseAddress - 0x8000) <= (sectionVirtualAddress + sectionSize)) { uint32 rplLoader_sdataAddrNew = memory_getVirtualOffsetFromPointer(rpl->sectionAddressTable2[i].ptr) + (sdataBaseAddress - sectionVirtualAddress); rplLoader_sdataAddr = rplLoader_sdataAddrNew; } // sdata 2 if ((sdataBaseAddress2 - 0x8000) >= (sectionVirtualAddress) && (sdataBaseAddress2 - 0x8000) <= (sectionVirtualAddress + sectionSize)) { rplLoader_sdata2Addr = memory_getVirtualOffsetFromPointer(rpl->sectionAddressTable2[i].ptr) + (sdataBaseAddress2 - sectionVirtualAddress); } } // cancel if error if (rpl->hasError) { cemuLog_log(LogType::Force, "RPLLoader: Unable to load RPL due to errors"); delete rpl; return nullptr; } // find TLS section uint32 tlsStartAddress = 0xFFFFFFFF; uint32 tlsEndAddress = 0; for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) { if ( ((uint32)rpl->sectionTablePtr[i].flags & SHF_TLS) == 0 ) continue; uint32 sectionVirtualAddress = rpl->sectionTablePtr[i].virtualAddress; uint32 sectionSize = rpl->sectionTablePtr[i].sectionSize; tlsStartAddress = std::min(tlsStartAddress, sectionVirtualAddress); tlsEndAddress = std::max(tlsEndAddress, sectionVirtualAddress+sectionSize); } if (tlsStartAddress == 0xFFFFFFFF) { tlsStartAddress = 0; tlsEndAddress = 0; } rpl->tlsStartAddress = tlsStartAddress; rpl->tlsEndAddress = tlsEndAddress; // add to module list cemu_assert(rplModuleCount < 256); rplModuleList[rplModuleCount] = rpl; rplModuleCount++; // track dependencies RPLLoader_incrementModuleDependencyRefs(rpl); // update entrypoint RPLLoader_UpdateEntrypoint(rpl); return rpl; } void RPLLoader_FlushMemory(RPLModule* rpl) { // invalidate recompiler cache PPCRecompiler_invalidateRange(rpl->regionMappingBase_text.GetMPTR(), rpl->regionMappingBase_text.GetMPTR() + rpl->regionSize_text); rpl->heapTrampolineArea.forEachBlock([](void* mem, uint32 size) { MEMPTR<void> memVirtual = mem; PPCRecompiler_invalidateRange(memVirtual.GetMPTR(), memVirtual.GetMPTR() + size); }); } // resolve relocs and imports of all modules. Or resolve exports void RPLLoader_LinkSingleModule(RPLModule* rplLoaderContext, bool resolveOnlyExports) { // setup shared import tracking std::vector<RPLSharedImportTracking> sharedImportTracking; sharedImportTracking.resize(rplLoaderContext->rplHeader.sectionTableEntryCount - 2); memset(sharedImportTracking.data(), 0, sizeof(RPLSharedImportTracking) * sharedImportTracking.size()); for (uint32 i = 0; i < (uint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { if( rplLoaderContext->sectionTablePtr[i].type != (uint32be)SHT_RPL_IMPORTS ) continue; char* libName = (char*)((uint8*)rplLoaderContext->sectionAddressTable2[i].ptr + 8); // make module name char _importModuleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(_importModuleName, libName); // find in loaded module list std::string importModuleName{_importModuleName}; bool foundModule = false; for (sint32 f = 0; f < rplModuleCount; f++) { if (boost::iequals(rplModuleList[f]->moduleName2, importModuleName)) { sharedImportTracking[i].rplLoaderContext = rplModuleList[f]; memset(sharedImportTracking[i].modulename, 0, sizeof(sharedImportTracking[i].modulename)); strcpy_s(sharedImportTracking[i].modulename, importModuleName.c_str()); foundModule = true; break; } } if( foundModule ) continue; // if not found, we assume it's a HLE lib sharedImportTracking[i].rplLoaderContext = HLE_MODULE_PTR; sharedImportTracking[i].exportSection = nullptr; strcpy_s(sharedImportTracking[i].modulename, libName); } if (resolveOnlyExports) RPLLoader_HandleRelocs(rplLoaderContext, sharedImportTracking, 2); else RPLLoader_HandleRelocs(rplLoaderContext, sharedImportTracking, 0); RPLLoader_FlushMemory(rplLoaderContext); } void RPLLoader_LoadSectionDebugSymbols(RPLModule* rplLoaderContext, rplSectionEntryNew_t* section, int symtabSectionIndex) { uint32 sectionSize = section->sectionSize; uint32 symbolEntrySize = section->ukn24; if (symbolEntrySize == 0) symbolEntrySize = 0x10; cemu_assert(symbolEntrySize == 0x10); cemu_assert((sectionSize % symbolEntrySize) == 0); uint32 symbolCount = sectionSize / symbolEntrySize; cemu_assert(symbolCount >= 2); uint16 sectionCount = rplLoaderContext->rplHeader.sectionTableEntryCount; uint8* symtabData = (uint8*)rplLoaderContext->sectionAddressTable2[symtabSectionIndex].ptr; uint32 strtabSectionIndex = section->symtabSectionIndex; uint8* strtabData = (uint8*)rplLoaderContext->sectionAddressTable2[strtabSectionIndex].ptr; for (uint32 i = 0; i < symbolCount; i++) { RPLFileSymtabEntry* sym = (RPLFileSymtabEntry*)(symtabData + i * symbolEntrySize); uint16 symSectionIndex = sym->sectionIndex; if (symSectionIndex == 0 || symSectionIndex >= sectionCount) continue; void* symbolSectionAddress = rplLoaderContext->sectionAddressTable2[symSectionIndex].ptr; if (symbolSectionAddress == nullptr) continue; rplSectionEntryNew_t* symbolSection = rplLoaderContext->sectionTablePtr + symSectionIndex; if(symbolSection->type == SHT_RPL_EXPORTS || symbolSection->type == SHT_RPL_IMPORTS) continue; // exports and imports are handled separately uint32 symbolOffset = sym->symbolAddress - symbolSection->virtualAddress; uint32 nameOffset = sym->ukn00; if (nameOffset > 0) { char* symbolName = (char*)strtabData + nameOffset; if (sym->info == 0x12) { rplSymbolStorage_store(rplLoaderContext->moduleName2.c_str(), symbolName, sym->symbolAddress); } } } } void RPLLoader_LoadDebugSymbols(RPLModule* rplLoaderContext) { for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) { rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; uint32 sectionType = section->type; if (sectionType != SHT_SYMTAB) continue; RPLLoader_LoadSectionDebugSymbols(rplLoaderContext, section, i); } } void RPLLoader_UnloadModule(RPLDependency* rplDependency, bool skipPPCCalls) { /* A note: Mario Party 10's mg0408.rpl (minigame Spike Ball Scramble) has a bug where it keeps running code (function 0x02086BCC for example) after RPL unload It seems to rely on the RPL loader not zeroing released memory */ if (rplDependency->rplHLEModule) { cemu_assert_debug(!rplDependency->rplLoaderContext); // HLE module unload logic is handled by parent functions for now return; } RPLModule* rpl = rplDependency->rplLoaderContext; // decrease reference counters of all dependencies RPLLoader_decrementModuleDependencyRefs(rpl); // save module config for this module in the debugger g_debuggerDispatcher.NotifyModuleUnloaded(rpl); // call rpl_entry with reason unload if (!skipPPCCalls) { cemu_assert_debug(PPCInterpreter_getCurrentInstance()); // must be running on a CPU emulation thread if (rpl->entrypoint) { PPCCoreCallback(rpl->entrypoint, rplDependency->coreinitHandle, 2); // 2 -> unload } } // release memory rplLoaderHeap_codeArea2.free(rpl->regionMappingBase_text.GetPtr()); rpl->regionMappingBase_text = nullptr; // for some reason freeing the data allocations causes a crash in MP10 on boot if (!skipPPCCalls) { RPLLoader_FreeData(rpl, MEMPTR<void>(rpl->regionMappingBase_data).GetPtr()); rpl->regionMappingBase_data = 0; RPLLoader_FreeData(rpl, MEMPTR<void>(rpl->regionMappingBase_loaderInfo).GetPtr()); rpl->regionMappingBase_loaderInfo = 0; } rpl->heapTrampolineArea.releaseAll(); // todo - remove from rplSymbolStorage_store if (rpl->sectionTablePtr) { free(rpl->sectionTablePtr); rpl->sectionTablePtr = nullptr; } // unload temp region if (rpl->tempRegionPtr) { RPLLoader_FreeWorkarea(rpl->tempRegionPtr); rpl->tempRegionPtr = nullptr; } // remove from rpl module list for (sint32 i = 0; i < rplModuleCount; i++) { if (rplModuleList[i] == rpl) { rplModuleList[i] = rplModuleList[rplModuleCount-1]; rplModuleCount--; break; } } delete rpl; } void RPLLoader_FixModuleTLSIndex(RPLModule* rplLoaderContext) { sint16 tlsModuleIndex = -1; for (auto& dep : rplDependencyList) { if (boost::iequals(rplLoaderContext->moduleName2, dep->modulename)) { tlsModuleIndex = dep->tlsModuleIndex; break; } } cemu_assert(tlsModuleIndex != -1); rplLoaderContext->fileInfo.tlsModuleIndex = tlsModuleIndex; } void RPLLoader_Link() { // calculate TLS index for (sint32 i = 0; i < rplModuleCount; i++) { if (rplModuleList[i]->isLinked) continue; RPLLoader_FixModuleTLSIndex(rplModuleList[i]); } // resolve relocs for (sint32 i = 0; i < rplModuleCount; i++) { if(rplModuleList[i]->isLinked) continue; RPLLoader_LinkSingleModule(rplModuleList[i], false); } // resolve imports and load debug symbols for (sint32 i = 0; i < rplModuleCount; i++) { if (rplModuleList[i]->isLinked) continue; RPLLoader_LinkSingleModule(rplModuleList[i], true); RPLLoader_LoadDebugSymbols(rplModuleList[i]); rplModuleList[i]->isLinked = true; // mark as linked GraphicPack2::NotifyModuleLoaded(rplModuleList[i]); g_debuggerDispatcher.NotifyModuleLoaded(rplModuleList[i]); } } uint32 RPLLoader_GetModuleEntrypoint(RPLModule* rplLoaderContext) { return rplLoaderContext->entrypoint; } // takes a module name without extension, returns true if the RPL module is a known Cafe OS module bool RPLLoader_IsKnownCafeOSModule(std::string_view name) { static std::unordered_set<std::string> s_systemModules556 = { "avm","camera","coreinit","dc","dmae","drmapp","erreula", "gx2","h264","lzma920","mic","nfc","nio_prof","nlibcurl", "nlibnss","nlibnss2","nn_ac","nn_acp","nn_act","nn_aoc","nn_boss", "nn_ccr","nn_cmpt","nn_dlp","nn_ec","nn_fp","nn_hai","nn_hpad", "nn_idbe","nn_ndm","nn_nets2","nn_nfp","nn_nim","nn_olv","nn_pdm", "nn_save","nn_sl","nn_spm","nn_temp","nn_uds","nn_vctl","nsysccr", "nsyshid","nsyskbd","nsysnet","nsysuhs","nsysuvd","ntag","padscore", "proc_ui","sndcore2","snduser2","snd_core","snd_user","swkbd","sysapp", "tcl","tve","uac","uac_rpl","usb_mic","uvc","uvd","vpad","vpadbase", "zlib125"}; std::string nameLower{name}; std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), _ansiToLower); return s_systemModules556.contains(nameLower); } COSModule* RPLLoader_GetHLECafeOSModule(std::string_view moduleName) { std::span<COSModule*> cosModules = GetCOSModules(); for (auto& module : cosModules) { if (boost::iequals(module->GetName(), moduleName)) return module; } return nullptr; } // increment reference counter for module void RPLLoader_AddDependency(std::string_view name) { cemu_assert(!name.empty()); // get module name from path char moduleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(moduleName, name); // check if dependency already exists for (auto& dep : rplDependencyList) { if (strcmp(moduleName, dep->modulename) == 0) { dep->referenceCount++; return; } } // add new entry RPLDependency* newDependency = new RPLDependency(); strcpy(newDependency->modulename, moduleName); newDependency->referenceCount = 1; newDependency->coreinitHandle = rplLoader_currentHandleCounter; newDependency->tlsModuleIndex = rplLoader_currentTlsModuleIndex; newDependency->isCafeOSModule = RPLLoader_IsKnownCafeOSModule(moduleName); rplLoader_currentTlsModuleIndex++; // todo - delay handle and tls allocation until the module is actually loaded. It may not exist rplLoader_currentHandleCounter++; if (rplLoader_currentTlsModuleIndex == 0x7FFF) cemuLog_log(LogType::Force, "RPLLoader: Exhausted TLS module indices pool"); // convert name to path/filename if it isn't already one if (name.find_first_of('.') != std::string_view::npos) { newDependency->filepath = name; } else { newDependency->filepath = name; newDependency->filepath.append(".rpl"); } if (newDependency->filepath.size() >= RPL_MODULE_PATH_LENGTH) cemuLog_log(LogType::Force, "RPLLoader_AddDependency(): RPL path too long \"{}\"", newDependency->filepath); // if no CafeLibs RPL is present then try to load as a HLE module // we dont check for isCafeOSModule == true here because the user might want to replace application RPLs in some cases const auto cafeLibsFilePath = ActiveSettings::GetUserDataPath("cafeLibs/{}", newDependency->filepath); std::error_code ec; if (!fs::exists(cafeLibsFilePath, ec)) newDependency->rplHLEModule = RPLLoader_GetHLECafeOSModule(moduleName); rplDependencyList.push_back(newDependency); } // decrement reference counter for dependency by module path void RPLLoader_RemoveDependency(std::string_view name) { cemu_assert(!name.empty()); // get module name from path char moduleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(moduleName, name); // find dependency and decrement ref count for (auto& dep : rplDependencyList) { if (strcmp(moduleName, dep->modulename) == 0) { dep->referenceCount--; return; } } } bool RPLLoader_HasDependency(std::string_view name) { char moduleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(moduleName, name); for (const auto& dep : rplDependencyList) { if (strcmp(moduleName, dep->modulename) == 0) return true; } return false; } // decrement reference counter for dependency by module handle void RPLLoader_RemoveDependency(uint32 handle) { for (auto& dep : rplDependencyList) { if (dep->coreinitHandle == handle) { cemu_assert_debug(dep->referenceCount != 0); if(dep->referenceCount > 0) dep->referenceCount--; return; } } } RPLDependency* RPLLoader_GetDependencyByRPLModule(RPLModule* rpl) { cemu_assert_debug(rpl); for (auto& dep : rplDependencyList) { if (dep->rplLoaderContext == rpl) return dep; } cemu_assert_suspicious(); // should never happen. Modules get loaded via dependency tracking so a dependency entry needs to exist return nullptr; } uint32 RPLLoader_GetHandleByModuleName(const char* name) { // get module name from path char moduleName[RPL_MODULE_NAME_LENGTH]; _RPLLoader_ExtractModuleNameFromPath(moduleName, name); // search for existing dependency for (auto& dep : rplDependencyList) { if (strcmp(moduleName, dep->modulename) == 0) { cemu_assert_debug(dep->loadAttempted); if (!dep->isCafeOSModule && !dep->rplLoaderContext) return RPL_INVALID_HANDLE; // module not found return dep->coreinitHandle; } } return RPL_INVALID_HANDLE; } uint32 RPLLoader_GetMaxTLSModuleIndex() { return rplLoader_currentTlsModuleIndex - 1; } bool RPLLoader_GetTLSDataByTLSIndex(sint16 tlsModuleIndex, uint8** tlsData, sint32* tlsSize) { RPLModule* rplLoaderContext = nullptr; for (auto& dep : rplDependencyList) { if (dep->tlsModuleIndex == tlsModuleIndex) { rplLoaderContext = dep->rplLoaderContext; break; } } if (rplLoaderContext == nullptr) return false; cemu_assert(rplLoaderContext->tlsStartAddress != 0); uint32 tlsDataSize = rplLoaderContext->tlsEndAddress - rplLoaderContext->tlsStartAddress; cemu_assert_debug(tlsDataSize < 0x10000); // check for suspiciously large TLS area *tlsData = (uint8*)memory_getPointerFromVirtualOffset(rplLoaderContext->tlsStartAddress); *tlsSize = tlsDataSize; return true; } bool RPLLoader_LoadFromVirtualPath(RPLDependency* dependency, std::string_view filePath) { uint32 rplSize = 0; uint8* rplData = fsc_extractFile(std::string(filePath).c_str(), &rplSize); if (rplData) { cemuLog_logDebug(LogType::Force, "Loading: {}", filePath); dependency->rplLoaderContext = RPLLoader_LoadFromMemory(rplData, rplSize, filePath); free(rplData); return true; } return false; } std::span<COSModule*> GetCOSModules(); void RPLLoader_LoadDependency(RPLDependency* dependency) { // if its a HLE module then notify that it has been mapped if (dependency->rplHLEModule) { dependency->rplHLEModule->RPLMapped(); // load chained dependencies // this is necessary for something like GX2.rpl which uses TCL.rpl functions auto depList = dependency->rplHLEModule->GetDependencies(); for (const auto& dep : depList) RPLLoader_AddDependency(dep); return; } // check if module is already loaded for (sint32 i = 0; i < rplModuleCount; i++) { if(!boost::iequals(rplModuleList[i]->moduleName2, dependency->modulename)) continue; dependency->rplLoaderContext = rplModuleList[i]; return; } //char filePath[RPL_MODULE_PATH_LENGTH]; std::string rplPath; // check if path is absolute if (!dependency->filepath.empty() && dependency->filepath.front() == '/') { rplPath = dependency->filepath; RPLLoader_LoadFromVirtualPath(dependency, rplPath); return; } // attempt to load rpl from code directory of current title rplPath = "/internal/current_title/code/"; rplPath.append(dependency->filepath); // except if it is blacklisted bool isBlacklisted = false; if (boost::iequals(dependency->filepath, "erreula.rpl")) { if (fsc_doesFileExist(rplPath.c_str())) isBlacklisted = true; } if (isBlacklisted) cemuLog_log(LogType::Force, fmt::format("Game tried to load \"{}\" but it is blacklisted (using Cemu's implementation instead)", rplPath)); else if (RPLLoader_LoadFromVirtualPath(dependency, rplPath)) return; // attempt to load rpl from Cemu's /cafeLibs/ directory if (ActiveSettings::LoadSharedLibrariesEnabled()) { const auto cafeLibsFilePath = ActiveSettings::GetUserDataPath("cafeLibs/{}", dependency->filepath); auto fileData = FileStream::LoadIntoMemory(cafeLibsFilePath); if (fileData) { cemuLog_log(LogType::Force, "Loading RPL: /cafeLibs/{}", dependency->filepath); dependency->rplLoaderContext = RPLLoader_LoadFromMemory(fileData->data(), fileData->size(), dependency->filepath); return; } } } // loads and unloads modules based on the current dependency list void RPLLoader_UpdateDependencies() { bool repeat = true; while (repeat) { repeat = false; for(auto idx = 0; idx<rplDependencyList.size(); ) { auto dependency = rplDependencyList[idx]; // debug_printf("DEP 0x%02x %s\n", dependency->referenceCount, dependency->modulename); if(dependency->referenceCount == 0) { // unload RPLs // todo - should we let HLE modules know if they are being unloaded? if (dependency->rplLoaderContext) { RPLLoader_UnloadModule(dependency, false); dependency->rplLoaderContext = nullptr; } else if (dependency->rplHLEModule) { dependency->rplHLEModule->rpl_entry(dependency->coreinitHandle, coreinit::RplEntryReason::Unloaded); dependency->rplHLEModule->RPLUnmapped(); // untrack chained dependencies auto depList = dependency->rplHLEModule->GetDependencies(); for (const auto& dep : depList) RPLLoader_RemoveDependency(dep); } // remove from dependency list rplDependencyList.erase(rplDependencyList.begin()+idx); idx--; repeat = true; // unload can effect reference count of other dependencies break; } else if (!dependency->loadAttempted) { // load dependency->loadAttempted = true; RPLLoader_LoadDependency(dependency); repeat = true; idx++; break; } idx++; } } RPLLoader_Link(); } void RPLLoader_LoadCoreinit() { RPLLoader_AddDependency("coreinit"); for (auto& dep : rplDependencyList) { if (strcmp(dep->modulename, "coreinit") == 0) { dep->loadAttempted = true; RPLLoader_LoadDependency(dep); return; } } cemu_assert_suspicious(); } void RPLLoader_SetMainModule(RPLModule* rplLoaderContext) { rplLoaderContext->entrypointCalled = true; rplLoader_mainModule = rplLoaderContext; } uint32 RPLLoader_GetMainModuleHandle() { for (auto& dep : rplDependencyList) { if (dep->rplLoaderContext == rplLoader_mainModule) { return dep->coreinitHandle; } } cemu_assert(false); return 0; } RPLModule* RPLLoader_FindModuleByCodeAddr(uint32 addr) { for (sint32 i = 0; i < rplModuleCount; i++) { uint32 startAddr = rplModuleList[i]->regionMappingBase_text.GetMPTR(); uint32 endAddr = rplModuleList[i]->regionMappingBase_text.GetMPTR() + rplModuleList[i]->regionSize_text; if (addr >= startAddr && addr < endAddr) return rplModuleList[i]; } return nullptr; } RPLModule* RPLLoader_FindModuleByDataAddr(uint32 addr) { for (sint32 i = 0; i < rplModuleCount; i++) { // data uint32 startAddr = rplModuleList[i]->regionMappingBase_data; uint32 endAddr = rplModuleList[i]->regionMappingBase_data + rplModuleList[i]->regionSize_data; if (addr >= startAddr && addr < endAddr) return rplModuleList[i]; // loaderinfo startAddr = rplModuleList[i]->regionMappingBase_loaderInfo; endAddr = rplModuleList[i]->regionMappingBase_loaderInfo + rplModuleList[i]->regionSize_loaderInfo; if (addr >= startAddr && addr < endAddr) return rplModuleList[i]; } return nullptr; } RPLModule* RPLLoader_FindModuleByName(std::string module) { for (sint32 i = 0; i < rplModuleCount; i++) { if (rplModuleList[i]->moduleName2 == module) return rplModuleList[i]; } return nullptr; } void RPLLoader_CallEntrypoints() { // for HLE modules we need to check the dependency list for (auto& dependency : rplDependencyList) { if (!dependency->rplHLEModule) continue; if (dependency->hleEntrypointCalled) continue; dependency->rplHLEModule->rpl_entry(dependency->coreinitHandle, coreinit::RplEntryReason::Loaded); dependency->hleEntrypointCalled = true; } // iterate loaded RPL modules for (sint32 i = 0; i < rplModuleCount; i++) { if (rplModuleList[i]->entrypointCalled) continue; uint32 moduleHandle = RPLLoader_GetHandleByModuleName(rplModuleList[i]->moduleName2.c_str()); MPTR entryPoint = RPLLoader_GetModuleEntrypoint(rplModuleList[i]); PPCCoreCallback(entryPoint, moduleHandle, 1); // 1 -> load, 2 -> unload rplModuleList[i]->entrypointCalled = true; } } // calls the entrypoint of coreinit and marks it as called so that RPLLoader_CallEntrypoints() wont call it again later void RPLLoader_CallCoreinitEntrypoint() { // for HLE modules we need to check the dependency list for (auto& dependency : rplDependencyList) { if (strcmp(dependency->modulename, "coreinit") != 0) continue; if (!dependency->rplHLEModule) continue; if (dependency->hleEntrypointCalled) continue; dependency->rplHLEModule->rpl_entry(dependency->coreinitHandle, coreinit::RplEntryReason::Loaded); dependency->hleEntrypointCalled = true; return; } cemu_assert_unimplemented(); // coreinit.rpl present in cafelibs? We currently do not support native coreinit and no thread context exists yet to do a PPC call } void RPLLoader_NotifyControlPassedToApplication() { rplLoader_applicationHasMemoryControl = true; } uint32 RPLLoader_FindModuleOrHLEExport(uint32 moduleHandle, bool isData, const char* exportName) { // find dependency from handle RPLModule* rplLoaderContext = nullptr; RPLDependency* dependency = nullptr; for (auto& dep : rplDependencyList) { if (dep->coreinitHandle == moduleHandle) { rplLoaderContext = dep->rplLoaderContext; dependency = dep; break; } } uint32 exportResult = 0; if (rplLoaderContext) { exportResult = RPLLoader_FindModuleExport(rplLoaderContext, isData, exportName); } else { // attempt to find HLE export if (isData) { MPTR weakExportAddr = osLib_getPointer(dependency->modulename, exportName); cemu_assert_debug(weakExportAddr != 0xFFFFFFFF); exportResult = weakExportAddr; } else { exportResult = rpl_mapHLEImport(rplLoaderContext, dependency->modulename, exportName, true); } } return exportResult; } uint32 RPLLoader_GetSDA1Base() { cemu_assert_debug(rplModuleCount > 0); // this should not be called before the main executable was loaded return rplLoader_sdataAddr; } uint32 RPLLoader_GetSDA2Base() { cemu_assert_debug(rplModuleCount > 0); return rplLoader_sdata2Addr; } RPLModule** RPLLoader_GetModuleList() { return rplModuleList; } sint32 RPLLoader_GetModuleCount() { return rplModuleCount; } template<typename TAddr, typename TSize> class SimpleHeap { struct allocEntry_t { TAddr start; TAddr end; allocEntry_t(TAddr start, TAddr end) : start(start), end(end) {}; }; public: SimpleHeap(TAddr baseAddress, TSize size) : m_base(baseAddress), m_size(size) { } TAddr alloc(TSize size, TSize alignment) { cemu_assert_debug(alignment != 0); TAddr allocBase = m_base; allocBase = (allocBase + alignment - 1)&~(alignment-1); while (true) { bool hasCollision = false; for (auto& itr : list_allocatedEntries) { if (allocBase < itr.end && (allocBase + size) >= itr.start) { allocBase = itr.end; allocBase = (allocBase + alignment - 1)&~(alignment - 1); hasCollision = true; break; } } if(hasCollision == false) break; } if ((allocBase + size) > (m_base + m_size)) return 0; list_allocatedEntries.emplace_back(allocBase, allocBase + size); return allocBase; } void free(TAddr addr) { for (sint32 i = 0; i < list_allocatedEntries.size(); i++) { if (list_allocatedEntries[i].start == addr) { list_allocatedEntries.erase(list_allocatedEntries.begin() + i); return; } } cemu_assert(false); return; } private: TAddr m_base; TSize m_size; std::vector<allocEntry_t> list_allocatedEntries; }; SimpleHeap<uint32, uint32> heapCodeCaveArea(MEMORY_CODECAVEAREA_ADDR, MEMORY_CODECAVEAREA_SIZE); MEMPTR<void> RPLLoader_AllocateCodeCaveMem(uint32 alignment, uint32 size) { uint32 addr = heapCodeCaveArea.alloc(size, 256); return MEMPTR<void>{addr}; } void RPLLoader_ReleaseCodeCaveMem(MEMPTR<void> addr) { heapCodeCaveArea.free(addr.GetMPTR()); } void RPLLoader_UnloadAll() { // unload all RPL modules while (rplModuleCount > 0) { RPLDependency* dep = RPLLoader_GetDependencyByRPLModule(rplModuleList[0]); RPLLoader_UnloadModule(dep, true); } // notify every remaining HLE module its unloaded and unmapped // and do it in reverse order so that coreinit comes last RPLLoader_RemoveDependency("coreinit"); // undo manual ref count from RPLLoader_LoadCoreinit() for (sint32 i = (sint32)rplDependencyList.size()-1; i>=0; i--) { RPLDependency* dependency = rplDependencyList[i]; cemu_assert_debug(dependency->referenceCount >= 0); // sanity check for ref count if (!dependency->rplHLEModule) continue; if (dependency->referenceCount <= 0) continue; cemu_assert_debug(dependency->hleEntrypointCalled); // entrypoint should have been called dependency->rplHLEModule->rpl_entry(dependency->coreinitHandle, coreinit::RplEntryReason::Unloaded); dependency->rplHLEModule->RPLUnmapped(); } rplDependencyList.clear(); // unload all remaining symbols rplSymbolStorage_unloadAll(); // free all code imports g_heapTrampolineArea.releaseAll(); list_mappedFunctionImports.clear(); g_map_callableExports.clear(); rplLoader_applicationHasMemoryControl = false; rplLoader_maxCodeAddress = 0; rplLoader_currentDataAllocatorAddr = 0x10000000; rplLoader_currentTLSModuleIndex = 1; rplLoader_sdataAddr = MPTR_NULL; rplLoader_sdata2Addr = MPTR_NULL; rplLoader_mainModule = nullptr; } ================================================ FILE: src/Cafe/OS/RPL/rpl.h ================================================ #pragma once struct RPLModule; #define RPL_INVALID_HANDLE 0xFFFFFFFF void RPLLoader_InitState(); void RPLLoader_UnloadAll(); uint8* RPLLoader_AllocateTrampolineCodeSpace(sint32 size); MPTR RPLLoader_AllocateCodeSpace(uint32 size, uint32 alignment); uint32 RPLLoader_GetMaxCodeOffset(); uint32 RPLLoader_GetDataAllocatorAddr(); RPLModule* RPLLoader_LoadFromMemory(uint8* rplData, sint32 size, std::string_view name); uint32 rpl_mapHLEImport(RPLModule* rplLoaderContext, const char* rplName, const char* funcName, bool functionMustExist); void RPLLoader_Link(); MPTR RPLLoader_FindRPLExport(RPLModule* rplLoaderContext, const char* symbolName, bool isData); uint32 RPLLoader_GetModuleEntrypoint(RPLModule* rplLoaderContext); void RPLLoader_SetMainModule(RPLModule* rplLoaderContext); uint32 RPLLoader_GetMainModuleHandle(); void RPLLoader_CallEntrypoints(); void RPLLoader_CallCoreinitEntrypoint(); void RPLLoader_NotifyControlPassedToApplication(); void RPLLoader_AddDependency(std::string_view name); void RPLLoader_RemoveDependency(uint32 handle); bool RPLLoader_HasDependency(std::string_view name); void RPLLoader_UpdateDependencies(); void RPLLoader_LoadCoreinit(); uint32 RPLLoader_GetHandleByModuleName(const char* name); uint32 RPLLoader_GetMaxTLSModuleIndex(); bool RPLLoader_GetTLSDataByTLSIndex(sint16 tlsModuleIndex, uint8** tlsData, sint32* tlsSize); uint32 RPLLoader_FindModuleOrHLEExport(uint32 moduleHandle, bool isData, const char* exportName); uint32 RPLLoader_GetSDA1Base(); uint32 RPLLoader_GetSDA2Base(); sint32 RPLLoader_GetModuleCount(); RPLModule** RPLLoader_GetModuleList(); MEMPTR<void> RPLLoader_AllocateCodeCaveMem(uint32 alignment, uint32 size); void RPLLoader_ReleaseCodeCaveMem(MEMPTR<void> addr); // exports uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(struct PPCInterpreter_t* hCPU)); // elf loader uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name); ================================================ FILE: src/Cafe/OS/RPL/rpl_debug_symbols.cpp ================================================ #include "Cafe/OS/RPL/rpl_debug_symbols.h" std::map<MPTR, rplDebugSymbolBase*> map_DebugSymbols; void rplDebugSymbol_createComment(MPTR address, const wchar_t* comment) { auto new_comment = new rplDebugSymbolComment(); new_comment->type = RplDebugSymbolComment; new_comment->comment = comment; map_DebugSymbols[address] = new_comment; } rplDebugSymbolBase* rplDebugSymbol_getForAddress(MPTR address) { return map_DebugSymbols[address]; } const std::map<MPTR, rplDebugSymbolBase*>& rplDebugSymbol_getSymbols() { return map_DebugSymbols; } ================================================ FILE: src/Cafe/OS/RPL/rpl_debug_symbols.h ================================================ #pragma once #include <map> enum RplDebugSymbolType : uint8 { RplDebugSymbolComment = 0, }; struct rplDebugSymbolBase { RplDebugSymbolType type; rplDebugSymbolBase* next; }; struct rplDebugSymbolComment : rplDebugSymbolBase { std::wstring comment; }; void rplDebugSymbol_createComment(MPTR address, const wchar_t* comment); rplDebugSymbolBase* rplDebugSymbol_getForAddress(MPTR address); const std::map<MPTR, rplDebugSymbolBase*>& rplDebugSymbol_getSymbols(); ================================================ FILE: src/Cafe/OS/RPL/rpl_structs.h ================================================ #pragma once #include "util/ChunkedHeap/ChunkedHeap.h" #define RPL_MODULE_NAME_LENGTH 64 #define RPL_MODULE_PATH_LENGTH 256 // types #define SHT_RPL_EXPORTS (0x80000001) #define SHT_RPL_IMPORTS (0x80000002) #define SHT_RPL_CRCS (0x80000003) #define SHT_RPL_FILEINFO (0x80000004) #define SHT_PROGBITS (0x00000001) #define SHT_SYMTAB (0x00000002) #define SHT_STRTAB (0x00000003) #define SHT_RELA (0x00000004) #define SHT_HASH (0x00000005) #define SHT_DYNAMIC (0x00000006) #define SHT_NOTE (0x00000007) #define SHT_NOBITS (0x00000008) // this section contains no data #define SHT_REL (0x00000009) #define SHT_SHLIB (0x0000000A) #define SHT_DYNSYM (0x0000000B) // flags #define SHF_EXECUTE 0x00000004 #define SHF_RPL_COMPRESSED 0x08000000 #define SHF_TLS 0x04000000 // relocs #define RPL_RELOC_ADDR32 1 #define RPL_RELOC_LO16 4 #define RPL_RELOC_HI16 5 #define RPL_RELOC_HA16 6 #define RPL_RELOC_REL24 10 #define RPL_RELOC_REL14 11 #define R_PPC_DTPMOD32 68 #define R_PPC_DTPREL32 78 #define R_PPC_REL16_HA 251 #define R_PPC_REL16_HI 252 #define R_PPC_REL16_LO 253 #define HLE_MODULE_PTR ((RPLModule*)-1) typedef struct { /* +0x00 */ uint32be relocOffset; /* +0x04 */ uint32be symbolIndexAndType; /* +0x08 */ uint32be relocAddend; }rplRelocNew_t; typedef struct { /* +0x00 */ uint32be nameOffset; /* +0x04 */ uint32be type; /* +0x08 */ uint32be flags; /* +0x0C */ uint32be virtualAddress; /* +0x10 */ uint32be fileOffset; /* +0x14 */ uint32be sectionSize; /* +0x18 */ uint32be symtabSectionIndex; /* +0x1C */ uint32be relocTargetSectionIndex; /* +0x20 */ uint32be alignment; /* +0x24 */ uint32be ukn24; // for symtab: Size of each symbol entry }rplSectionEntryNew_t; typedef struct { /* +0x00 */ uint32be magic1; /* +0x04 */ uint8 version04; // probably version? /* +0x05 */ uint8 ukn05; // probably version? /* +0x06 */ uint8 ukn06; // probably version? /* +0x07 */ uint8 magic2_0; // part of second magic /* +0x08 */ uint8 magic2_1; // part of second magic /* +0x09 */ uint8 ukn09; /* +0x0A */ uint8 ukn0A; /* +0x0B */ uint8 ukn0B; /* +0x0C */ uint32be dataRegionSize; /* +0x10 */ uint8 ukn10; /* +0x11 */ uint8 ukn11; /* +0x12 */ uint16be ukn12; /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be entrypoint; /* +0x1C */ uint32be ukn1C; /* +0x20 */ uint32be sectionTableOffset; /* +0x24 */ uint32be ukn24; /* +0x28 */ uint16be ukn28; /* +0x2A */ uint16be programHeaderTableEntrySize; /* +0x2C */ uint16be programHeaderTableEntryCount; /* +0x2E */ uint16be sectionTableEntrySize; /* +0x30 */ uint16be sectionTableEntryCount; /* +0x32 */ uint16be nameSectionIndex; }rplHeaderNew_t; static_assert(offsetof(rplHeaderNew_t, dataRegionSize) == 0xC); static_assert(offsetof(rplHeaderNew_t, programHeaderTableEntrySize) == 0x2A); typedef struct { /* +0x00 */ uint32be fileInfoMagic; // always 0xCAFE0402 /* +0x04 */ uint32be textRegionSize; // text region size /* +0x08 */ uint32be ukn08; // base align text /* +0x0C */ uint32be dataRegionSize; // size of data sections /* +0x10 */ uint32be baseAlign; // base align data? /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be ukn18; /* +0x1C */ uint32be ukn1C; /* +0x20 */ uint32be trampolineAdjustment; /* +0x24 */ uint32be sdataBase1; /* +0x28 */ uint32be sdataBase2; /* +0x2C */ uint32be ukn2C; /* +0x30 */ uint32be ukn30; /* +0x34 */ uint32be flags; /* +0x38 */ uint32be ukn38; /* +0x3C */ uint32be ukn3C; /* +0x40 */ uint32be minimumToolkitVersion; /* +0x44 */ uint32be ukn44; /* +0x48 */ uint32be ukn48; /* +0x4C */ uint32be ukn4C; /* +0x50 */ uint32be ukn50; /* +0x54 */ uint32be ukn54; /* +0x58 */ sint16be tlsModuleIndex; }RPLFileInfoData; static_assert(offsetof(RPLFileInfoData, tlsModuleIndex) == 0x58); typedef struct { //uint32 address; void* ptr; }rplSectionAddressEntry_t; typedef struct { uint32be virtualOffset; uint32be nameOffset; }rplExportTableEntry_t; struct RPLModule { uint32 ukn00; // pointer to shared memory region? (0xEFE01000) uint32 ukn04; // related to text region size? uint32 padding14; uint32 padding18; rplHeaderNew_t rplHeader; rplSectionEntryNew_t* sectionTablePtr; // copy of section table uint32 entrypoint; MPTR textRegionTemp; // temporary memory for text section? MEMPTR<void> regionMappingBase_text; // base destination address for text region MPTR regionMappingBase_data; // base destination address for data region MPTR regionMappingBase_loaderInfo; // base destination address for loaderInfo region uint8* tempRegionPtr; uint32 tempRegionAllocSize; uint32 exportDCount; rplExportTableEntry_t* exportDDataPtr; uint32 exportFCount; rplExportTableEntry_t* exportFDataPtr; std::string moduleName2; std::vector<rplSectionAddressEntry_t> sectionAddressTable2; uint32 tlsStartAddress; uint32 tlsEndAddress; uint32 regionSize_text; uint32 regionSize_data; uint32 regionSize_loaderInfo; uint32 patchCRC; // Cemuhook style module crc for patches.txt // trampoline management ChunkedFlatAllocator<16 * 1024> heapTrampolineArea; std::unordered_map<MPTR, MPTR> trampolineMap; // section data std::vector<uint8> sectionData_fileInfo; std::vector<uint8> sectionData_crc; // parsed FILEINFO struct { uint32 textRegionSize; // size of region containing all text sections //uint32 ukn08; // base align text? uint32 dataRegionSize; // size of region containing all data sections uint32 baseAlign; uint32 ukn14; uint32 trampolineAdjustment; uint32 ukn4C; sint16 tlsModuleIndex; uint32 sdataBase1; uint32 sdataBase2; uint32 flags; }fileInfo; // parsed CRC std::vector<uint32> crcTable; uint32 GetSectionCRC(size_t sectionIndex) const { if (sectionIndex >= crcTable.size()) return 0; return crcTable[sectionIndex]; } bool IsRPX() const { return fileInfo.flags & 2; } // state bool isLinked; // set to true if _linkModule was called on this module bool entrypointCalled; // set if entrypoint was called // allocator betype<MPTR> funcAlloc; betype<MPTR> funcFree; // replaces rplData ptr std::span<uint8> RPLRawData; bool debugSectionLoadMask[128] = { false }; bool hasError{ false }; }; struct RPLDependency { char modulename[RPL_MODULE_NAME_LENGTH]; std::string filepath; bool loadAttempted; bool hleEntrypointCalled{false}; bool isCafeOSModule; // name is a known Cafe OS system RPL RPLModule* rplLoaderContext{}; // context of loaded module, can be nullptr for HLE COS modules class COSModule* rplHLEModule{}; // set if this is a HLE module sint32 referenceCount; uint32 coreinitHandle; // fake handle for coreinit sint16 tlsModuleIndex; // tls module index assigned to this dependency }; RPLModule* RPLLoader_FindModuleByCodeAddr(uint32 addr); RPLModule* RPLLoader_FindModuleByDataAddr(uint32 addr); RPLModule* RPLLoader_FindModuleByName(std::string module); ================================================ FILE: src/Cafe/OS/RPL/rpl_symbol_storage.cpp ================================================ #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" struct rplSymbolLib_t { char* libName; rplSymbolLib_t* next; }; struct { rplSymbolLib_t* libs; std::mutex m_symbolStorageMutex; std::unordered_map<uint32, RPLStoredSymbol*> map_symbolByAddress; // allocator for strings char* strAllocatorBlock; sint32 strAllocatorOffset; std::vector<void*> list_strAllocatedBlocks; }rplSymbolStorage = { 0 }; #define STR_ALLOC_BLOCK_SIZE (128*1024) // allocate 128KB blocks at once char* rplSymbolStorage_allocDupString(const char* str) { sint32 len = (sint32)strlen(str); if (rplSymbolStorage.strAllocatorBlock == nullptr || (rplSymbolStorage.strAllocatorOffset + len + 1) >= STR_ALLOC_BLOCK_SIZE) { // allocate new block rplSymbolStorage.strAllocatorBlock = (char*)malloc(STR_ALLOC_BLOCK_SIZE); rplSymbolStorage.strAllocatorOffset = 0; rplSymbolStorage.list_strAllocatedBlocks.emplace_back(rplSymbolStorage.strAllocatorBlock); } cemu_assert_debug((rplSymbolStorage.strAllocatorOffset + len + 1) <= STR_ALLOC_BLOCK_SIZE); char* allocatedStr = rplSymbolStorage.strAllocatorBlock + rplSymbolStorage.strAllocatorOffset; rplSymbolStorage.strAllocatorOffset += len + 1; strcpy(allocatedStr, str); return allocatedStr; } char* rplSymbolStorage_storeLibname(const char* libName) { if (rplSymbolStorage.libs == NULL) { rplSymbolLib_t* libEntry = new rplSymbolLib_t(); libEntry->libName = rplSymbolStorage_allocDupString(libName); libEntry->next = NULL; rplSymbolStorage.libs = libEntry; return libEntry->libName; } rplSymbolLib_t* libItr = rplSymbolStorage.libs; while (libItr) { if (boost::iequals(libItr->libName, libName)) return libItr->libName; // next libItr = libItr->next; } // create new entry rplSymbolLib_t* libEntry = new rplSymbolLib_t(); libEntry->libName = rplSymbolStorage_allocDupString(libName); libEntry->next = rplSymbolStorage.libs; rplSymbolStorage.libs = libEntry; return libEntry->libName; } RPLStoredSymbol* rplSymbolStorage_store(const char* libName, const char* symbolName, MPTR address) { std::unique_lock<std::mutex> lck(rplSymbolStorage.m_symbolStorageMutex); char* libNameStorage = rplSymbolStorage_storeLibname(libName); char* symbolNameStorage = rplSymbolStorage_allocDupString(symbolName); RPLStoredSymbol* storedSymbol = new RPLStoredSymbol(); storedSymbol->address = address; storedSymbol->libName = libNameStorage; storedSymbol->symbolName = symbolNameStorage; storedSymbol->flags = 0; rplSymbolStorage.map_symbolByAddress[address] = storedSymbol; return storedSymbol; } RPLStoredSymbol* rplSymbolStorage_getByAddress(MPTR address) { std::unique_lock<std::mutex> lck(rplSymbolStorage.m_symbolStorageMutex); return rplSymbolStorage.map_symbolByAddress[address]; } RPLStoredSymbol* rplSymbolStorage_getByClosestAddress(MPTR address) { // highly inefficient but doesn't matter for now std::unique_lock<std::mutex> lck(rplSymbolStorage.m_symbolStorageMutex); for(uint32 i=0; i<4096; i++) { RPLStoredSymbol* symbol = rplSymbolStorage.map_symbolByAddress[address]; if(symbol) return symbol; address -= 4; } return nullptr; } void rplSymbolStorage_remove(RPLStoredSymbol* storedSymbol) { std::unique_lock<std::mutex> lck(rplSymbolStorage.m_symbolStorageMutex); if (rplSymbolStorage.map_symbolByAddress[storedSymbol->address] == storedSymbol) rplSymbolStorage.map_symbolByAddress[storedSymbol->address] = nullptr; delete storedSymbol; } void rplSymbolStorage_removeRange(MPTR address, sint32 length) { while (length > 0) { RPLStoredSymbol* symbol = rplSymbolStorage_getByAddress(address); if (symbol) rplSymbolStorage_remove(symbol); address += 4; length -= 4; } } void rplSymbolStorage_createJumpProxySymbol(MPTR jumpAddress, MPTR destAddress) { RPLStoredSymbol* destSymbol = rplSymbolStorage_getByAddress(destAddress); if (destSymbol) rplSymbolStorage_store((char*)destSymbol->libName, (char*)destSymbol->symbolName, jumpAddress); } std::unordered_map<uint32, RPLStoredSymbol*>& rplSymbolStorage_lockSymbolMap() { rplSymbolStorage.m_symbolStorageMutex.lock(); return rplSymbolStorage.map_symbolByAddress; } void rplSymbolStorage_unlockSymbolMap() { rplSymbolStorage.m_symbolStorageMutex.unlock(); } void rplSymbolStorage_init() { cemu_assert_debug(rplSymbolStorage.map_symbolByAddress.empty()); cemu_assert_debug(rplSymbolStorage.strAllocatorBlock == nullptr); } void rplSymbolStorage_unloadAll() { // free symbols for (auto& it : rplSymbolStorage.map_symbolByAddress) delete it.second; rplSymbolStorage.map_symbolByAddress.clear(); // free libs rplSymbolLib_t* lib = rplSymbolStorage.libs; while (lib) { rplSymbolLib_t* next = lib->next; delete lib; lib = next; } rplSymbolStorage.libs = nullptr; // free strings for (auto it : rplSymbolStorage.list_strAllocatedBlocks) free(it); rplSymbolStorage.list_strAllocatedBlocks.clear(); rplSymbolStorage.strAllocatorBlock = nullptr; rplSymbolStorage.strAllocatorOffset = 0; } ================================================ FILE: src/Cafe/OS/RPL/rpl_symbol_storage.h ================================================ struct RPLStoredSymbol { MPTR address; void* libName; void* symbolName; uint32 flags; }; void rplSymbolStorage_init(); void rplSymbolStorage_unloadAll(); RPLStoredSymbol* rplSymbolStorage_store(const char* libName, const char* symbolName, MPTR address); void rplSymbolStorage_remove(RPLStoredSymbol* storedSymbol); void rplSymbolStorage_removeRange(MPTR address, sint32 length); RPLStoredSymbol* rplSymbolStorage_getByAddress(MPTR address); RPLStoredSymbol* rplSymbolStorage_getByClosestAddress(MPTR address); void rplSymbolStorage_createJumpProxySymbol(MPTR jumpAddress, MPTR destAddress); std::unordered_map<uint32, RPLStoredSymbol*>& rplSymbolStorage_lockSymbolMap(); void rplSymbolStorage_unlockSymbolMap(); ================================================ FILE: src/Cafe/OS/common/OSCommon.cpp ================================================ #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/OS/libs/proc_ui/proc_ui.h" #include "Cafe/OS/libs/nsysnet/nsysnet.h" #include "Cafe/OS/libs/nlibnss/nlibnss.h" #include "Cafe/OS/libs/nlibcurl/nlibcurl.h" #include "Cafe/OS/libs/nn_nfp/nn_nfp.h" #include "Cafe/OS/libs/nn_act/nn_act.h" #include "Cafe/OS/libs/nn_acp/nn_acp.h" #include "Cafe/OS/libs/nn_ac/nn_ac.h" #include "Cafe/OS/libs/nn_uds/nn_uds.h" #include "Cafe/OS/libs/nn_nim/nn_nim.h" #include "Cafe/OS/libs/nn_ndm/nn_ndm.h" #include "Cafe/OS/libs/nn_spm/nn_spm.h" #include "Cafe/OS/libs/nn_ec/nn_ec.h" #include "Cafe/OS/libs/nn_boss/nn_boss.h" #include "Cafe/OS/libs/nn_sl/nn_sl.h" #include "Cafe/OS/libs/nn_fp/nn_fp.h" #include "Cafe/OS/libs/nn_olv/nn_olv.h" #include "Cafe/OS/libs/nn_idbe/nn_idbe.h" #include "Cafe/OS/libs/nn_save/nn_save.h" #include "Cafe/OS/libs/erreula/erreula.h" #include "Cafe/OS/libs/sysapp/sysapp.h" #include "Cafe/OS/libs/dmae/dmae.h" #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/vpad/vpad.h" #include "Cafe/OS/libs/nsyskbd/nsyskbd.h" #include "Cafe/OS/libs/nsyshid/nsyshid.h" #include "Cafe/OS/libs/snd_user/snd_user.h" #include "Cafe/OS/libs/zlib125/zlib125.h" #include "Cafe/OS/libs/padscore/padscore.h" #include "Cafe/OS/libs/camera/camera.h" #include "../libs/swkbd/swkbd.h" struct osFunctionEntry_t { uint32 libHashA; uint32 libHashB; uint32 funcHashA; uint32 funcHashB; std::string name; HLEIDX hleFunc; osFunctionEntry_t(uint32 libHashA, uint32 libHashB, uint32 funcHashA, uint32 funcHashB, std::string_view name, HLEIDX hleFunc) : libHashA(libHashA), libHashB(libHashB), funcHashA(funcHashA), funcHashB(funcHashB), name(name), hleFunc(hleFunc) {}; }; typedef struct { uint32 libHashA; uint32 libHashB; uint32 funcHashA; uint32 funcHashB; uint32 vPtr; }osPointerEntry_t; std::vector<osFunctionEntry_t>* s_osFunctionTable; std::vector<osPointerEntry_t> osDataTable; void osLib_generateHashFromName(const char* name, uint32* hashA, uint32* hashB) { uint32 h1 = 0x688BA2BA; uint32 h2 = 0xF64A71D5; while( *name ) { uint32 c = (uint32)*name; h1 += c; h1 = (h1<<3)|((h1>>29)); h2 ^= c; h2 = (h2<<7)|((h2>>25)); h1 += h2; h2 += c; h2 = (h2<<3)|((h2>>29)); name++; } *hashA = h1; *hashB = h2; } void osLib_addFunctionInternal(const char* libraryName, const char* functionName, void(*osFunction)(PPCInterpreter_t* hCPU)) { if (!s_osFunctionTable) s_osFunctionTable = new std::vector<osFunctionEntry_t>(); // replace with static allocation + constinit once we have C++20 available // calculate hash uint32 libHashA, libHashB; uint32 funcHashA, funcHashB; osLib_generateHashFromName(libraryName, &libHashA, &libHashB); osLib_generateHashFromName(functionName, &funcHashA, &funcHashB); std::string hleName = fmt::format("{}.{}", libraryName, functionName); // if entry already exists, update it for (auto& it : *s_osFunctionTable) { if (it.libHashA == libHashA && it.libHashB == libHashB && it.funcHashA == funcHashA && it.funcHashB == funcHashB) { it.hleFunc = PPCInterpreter_registerHLECall(osFunction, hleName); return; } } s_osFunctionTable->emplace_back(libHashA, libHashB, funcHashA, funcHashB, hleName, PPCInterpreter_registerHLECall(osFunction, hleName)); } extern "C" DLLEXPORT void osLib_registerHLEFunction(const char* libraryName, const char* functionName, void(*osFunction)(PPCInterpreter_t * hCPU)) { osLib_addFunctionInternal(libraryName, functionName, osFunction); } sint32 osLib_getFunctionIndex(const char* libraryName, const char* functionName) { uint32 libHashA, libHashB; uint32 funcHashA, funcHashB; osLib_generateHashFromName(libraryName, &libHashA, &libHashB); osLib_generateHashFromName(functionName, &funcHashA, &funcHashB); for (auto& it : *s_osFunctionTable) { if (it.libHashA == libHashA && it.libHashB == libHashB && it.funcHashA == funcHashA && it.funcHashB == funcHashB) { return it.hleFunc; } } return -1; } void osLib_addVirtualPointer(const char* libraryName, const char* functionName, uint32 vPtr) { // calculate hash uint32 libHashA, libHashB; uint32 funcHashA, funcHashB; osLib_generateHashFromName(libraryName, &libHashA, &libHashB); osLib_generateHashFromName(functionName, &funcHashA, &funcHashB); // if entry already exists, update it for (auto& it : osDataTable) { if (it.libHashA == libHashA && it.libHashB == libHashB && it.funcHashA == funcHashA && it.funcHashB == funcHashB) { it.vPtr = vPtr; return; } } // add entry auto writeIndex = osDataTable.size(); osDataTable.resize(osDataTable.size() + 1); osDataTable[writeIndex].libHashA = libHashA; osDataTable[writeIndex].libHashB = libHashB; osDataTable[writeIndex].funcHashA = funcHashA; osDataTable[writeIndex].funcHashB = funcHashB; osDataTable[writeIndex].vPtr = vPtr; } uint32 osLib_getPointer(const char* libraryName, const char* functionName) { uint32 libHashA, libHashB; uint32 funcHashA, funcHashB; osLib_generateHashFromName(libraryName, &libHashA, &libHashB); osLib_generateHashFromName(functionName, &funcHashA, &funcHashB); for (auto& it : osDataTable) { if (it.libHashA == libHashA && it.libHashB == libHashB && it.funcHashA == funcHashA && it.funcHashB == funcHashB) { return it.vPtr; } } return 0xFFFFFFFF; } void osLib_returnFromFunction(PPCInterpreter_t* hCPU, uint32 returnValue) { hCPU->gpr[3] = returnValue; hCPU->instructionPointer = hCPU->spr.LR; } void osLib_returnFromFunction64(PPCInterpreter_t* hCPU, uint64 returnValue64) { hCPU->gpr[3] = (returnValue64>>32)&0xFFFFFFFF; hCPU->gpr[4] = (returnValue64>>0)&0xFFFFFFFF; hCPU->instructionPointer = hCPU->spr.LR; } ================================================ FILE: src/Cafe/OS/common/OSCommon.h ================================================ #pragma once struct PPCInterpreter_t; #define OSLIB_FUNCTIONTABLE_TYPE_FUNCTION (1) #define OSLIB_FUNCTIONTABLE_TYPE_POINTER (2) void osLib_generateHashFromName(const char* name, uint32* hashA, uint32* hashB); sint32 osLib_getFunctionIndex(const char* libraryName, const char* functionName); uint32 osLib_getPointer(const char* libraryName, const char* functionName); void osLib_addFunctionInternal(const char* libraryName, const char* functionName, void(*osFunction)(PPCInterpreter_t* hCPU)); #define osLib_addFunction(__p1, __p2, __p3) osLib_addFunctionInternal((const char*)__p1, __p2, __p3) void osLib_addVirtualPointer(const char* libraryName, const char* functionName, uint32 vPtr); void osLib_returnFromFunction(PPCInterpreter_t* hCPU, uint32 returnValue); void osLib_returnFromFunction64(PPCInterpreter_t* hCPU, uint64 returnValue64); // libs #include "Cafe/OS/libs/coreinit/coreinit.h" // from coreinit but more convenient to have this in the common header namespace coreinit { enum class RplEntryReason { Loaded = 1, Unloaded = 2, }; } // utility functions #include "Cafe/OS/common/OSUtil.h" // va_list struct ppc_va_list { uint8be gprIndex; uint8be fprIndex; uint8be _padding2[2]; MEMPTR<uint8be> overflow_arg_area; MEMPTR<uint8be> reg_save_area; }; static_assert(sizeof(ppc_va_list) == 0xC); struct ppc_va_list_reg_storage { uint32be gpr_save_area[8]; // 32 bytes, r3 to r10 float64be fpr_save_area[8]; // 64 bytes, f1 to f8 ppc_va_list vargs; uint32be padding; }; static_assert(sizeof(ppc_va_list_reg_storage) == 0x70); // Equivalent of va_start for PPC HLE functions. Must be called before any StackAllocator<> definitions #define ppc_define_va_list(__gprIndex, __fprIndex) \ MPTR vaOriginalR1 = PPCInterpreter_getCurrentInstance()->gpr[1]; \ StackAllocator<ppc_va_list_reg_storage> va_list_storage; \ for(int i=3; i<=10; i++) va_list_storage->gpr_save_area[i-3] = PPCInterpreter_getCurrentInstance()->gpr[i]; \ for(int i=1; i<=8; i++) va_list_storage->fpr_save_area[i-1] = PPCInterpreter_getCurrentInstance()->fpr[i].fp0; \ va_list_storage->vargs.gprIndex = __gprIndex; \ va_list_storage->vargs.fprIndex = __fprIndex; \ va_list_storage->vargs.reg_save_area = (uint8be*)&va_list_storage; \ va_list_storage->vargs.overflow_arg_area = {vaOriginalR1 + 8}; \ ppc_va_list& vargs = va_list_storage->vargs; enum class ppc_va_type { INT32 = 1, INT64 = 2, FLOAT_OR_DOUBLE = 3, }; static void* _ppc_va_arg(ppc_va_list* vargs, ppc_va_type argType) { void* r; switch ( argType ) { default: cemu_assert_suspicious(); case ppc_va_type::INT32: if ( vargs[0].gprIndex < 8u ) { r = &vargs->reg_save_area[4 * vargs->gprIndex]; vargs->gprIndex++; return r; } r = vargs->overflow_arg_area; vargs->overflow_arg_area += 4; return r; case ppc_va_type::INT64: if ( (vargs->gprIndex & 1) != 0 ) vargs->gprIndex++; if ( vargs->gprIndex < 8 ) { r = &vargs->reg_save_area[4 * vargs->gprIndex]; vargs->gprIndex += 2; return r; } vargs->overflow_arg_area = {(vargs->overflow_arg_area.GetMPTR()+7) & 0xFFFFFFF8}; r = vargs->overflow_arg_area; vargs->overflow_arg_area += 8; return r; case ppc_va_type::FLOAT_OR_DOUBLE: if ( vargs->fprIndex < 8 ) { r = &vargs->reg_save_area[0x20 + 8 * vargs->fprIndex]; vargs->fprIndex++; return r; } vargs->overflow_arg_area = {(vargs->overflow_arg_area.GetMPTR()+7) & 0xFFFFFFF8}; r = vargs->overflow_arg_area; vargs->overflow_arg_area += 8; return r; } } ================================================ FILE: src/Cafe/OS/common/OSUtil.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/MMU/MMU.h" #include <fmt/ostream.h> #include <fmt/compile.h> #include <fmt/ranges.h> class cafeExportParamWrapper { public: template <typename T> static void getParamWrapper(PPCInterpreter_t* hCPU, int& gprIndex, int& fprIndex, T& v) { if constexpr (std::is_pointer_v<T>) { uint32be addr; if (gprIndex >= 8) addr = memory_readU32(hCPU->gpr[1] + 8 + (gprIndex - 8) * 4); else addr = hCPU->gpr[3 + gprIndex]; using TPtr = std::remove_pointer_t<T>; v = MEMPTR<TPtr>(addr).GetPtr(); gprIndex++; } else if constexpr (std::is_base_of_v<MEMPTRBase, T>) { uint32be addr; if (gprIndex >= 8) addr = memory_readU32(hCPU->gpr[1] + 8 + (gprIndex - 8) * 4); else addr = hCPU->gpr[3 + gprIndex]; v = addr.value(); gprIndex++; } else if constexpr (std::is_enum_v<T>) { using TEnum = std::underlying_type_t<T>; getParamWrapper<TEnum>(hCPU, gprIndex, fprIndex, (TEnum&)v); } else if constexpr (std::is_integral_v<T>) { if constexpr (sizeof(T) == sizeof(uint64)) { gprIndex = (gprIndex + 1)&~1; if (gprIndex >= 8) v = (T)memory_readU64(hCPU->gpr[1] + 8 + (gprIndex - 8) * 4); else v = (T)(((uint64)hCPU->gpr[3 + gprIndex]) << 32) | ((uint64)hCPU->gpr[3 + gprIndex + 1]); gprIndex += 2; } else { if (gprIndex >= 8) v = (T)memory_readU32(hCPU->gpr[1] + 8 + (gprIndex - 8) * 4); else v = (T)hCPU->gpr[3 + gprIndex]; gprIndex++; } } else if constexpr (std::is_floating_point_v<T>) { v = (T)hCPU->fpr[1 + fprIndex].fpr; fprIndex++; } else { assert_dbg(); } } template<typename T> static void setReturnResult(PPCInterpreter_t* hCPU, T r) { if constexpr (std::is_pointer_v<T>) { hCPU->gpr[3] = MEMPTR(r).GetMPTR(); } else if constexpr (std::is_reference_v<T>) { hCPU->gpr[3] = MEMPTR(&r).GetMPTR(); } else if constexpr (std::is_enum_v<T>) { using TEnum = std::underlying_type_t<T>; setReturnResult<TEnum>(hCPU, (TEnum)r); } else if constexpr (std::is_integral_v<T>) { if constexpr(sizeof(T) == 8) { const auto t = static_cast<uint64>(r); hCPU->gpr[3] = (uint32)(t >> 32); // high hCPU->gpr[4] = (uint32)(t); // low } else { hCPU->gpr[3] = (uint32)r; } } else { cemu_assert_unimplemented(); //static_assert(false); } } template<typename T> static auto getFormatResult(T r) { if constexpr (std::is_pointer_v<T>) return MEMPTR(r).GetMPTR(); else if constexpr (std::is_enum_v<T>) return static_cast<std::underlying_type_t<T>>(r); else if constexpr(!std::is_fundamental_v<T>) return MEMPTR(&r).GetMPTR(); else return r; } }; template<typename T> T cafeExportGetParamWrapper(PPCInterpreter_t* hCPU, int& gprIndex, int& fprIndex) { T v; cafeExportParamWrapper::getParamWrapper(hCPU, gprIndex, fprIndex, v); return v; } template <typename R, typename ... Args> static std::tuple<Args...> cafeExportBuildArgTuple(PPCInterpreter_t* hCPU, R(fn)(Args...)) { int gprIndex = 0; int fprIndex = 0; return std::tuple<Args...>{ cafeExportGetParamWrapper<Args>(hCPU, gprIndex, fprIndex)... }; } template<typename T> T cafeExportGetFormatParamWrapper(PPCInterpreter_t* hCPU, int& gprIndex, int& fprIndex) { T v; cafeExportParamWrapper::getParamWrapper(hCPU, gprIndex, fprIndex, v); // if T is char* or const char*, return "null" instead of nullptr since newer fmtlib would throw otherwise if constexpr (std::is_same_v<T, char*> || std::is_same_v<T, const char*>) return v ? v : (T)"null"; return v; } template<typename T> using _CAFE_FORMAT_ARG = std::conditional_t<std::is_pointer_v<T>, std::conditional_t<std::is_same_v<T, char*> || std::is_same_v<T, const char*>, T, MEMPTR<T>>, T>; template <typename R, typename... Args> static auto cafeExportBuildFormatTuple(PPCInterpreter_t* hCPU, R(fn)(Args...)) { int gprIndex = 0; int fprIndex = 0; return std::tuple<_CAFE_FORMAT_ARG<Args>...>{ cafeExportGetFormatParamWrapper<_CAFE_FORMAT_ARG<Args>>(hCPU, gprIndex, fprIndex)... }; } template<auto fn, typename TNames, LogType TLogType> void cafeExportCallWrapper(PPCInterpreter_t* hCPU) { auto tup = cafeExportBuildArgTuple(hCPU, fn); bool shouldLog = false; if (cemuLog_isLoggingEnabled(TLogType)) { const auto format_tup = cafeExportBuildFormatTuple(hCPU, fn); if(cemuLog_advancedPPCLoggingEnabled()) { MPTR threadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); if constexpr (std::tuple_size_v<decltype(format_tup)> > 0) shouldLog = cemuLog_log(TLogType, "{}.{}{} # LR: {:#x} | Thread: {:#x}", TNames::GetLib(), TNames::GetFunc(), format_tup, hCPU->spr.LR, threadMPTR); else shouldLog = cemuLog_log(TLogType, "{}.{}() # LR: {:#x} | Thread: {:#x}", TNames::GetLib(), TNames::GetFunc(), hCPU->spr.LR, threadMPTR); } else { if constexpr (std::tuple_size_v<decltype(format_tup)> > 0) { shouldLog = cemuLog_log(TLogType, "{}.{}{}", TNames::GetLib(), TNames::GetFunc(), format_tup); } else shouldLog = cemuLog_log(TLogType, "{}.{}()", TNames::GetLib(), TNames::GetFunc()); } } if constexpr (!std::is_void_v<decltype(std::apply(fn, tup))>) { // has non-void return type decltype(auto) result = std::apply(fn, tup); cafeExportParamWrapper::setReturnResult<decltype(std::apply(fn, tup))>(hCPU, result); if(shouldLog) cemuLog_log(TLogType, "\t\t{}.{} -> {}", TNames::GetLib(), TNames::GetFunc(), cafeExportParamWrapper::getFormatResult(result)); } else { // return type is void std::apply(fn, tup); } // return from func hCPU->instructionPointer = hCPU->spr.LR; } void osLib_addFunctionInternal(const char* libraryName, const char* functionName, void(*osFunction)(PPCInterpreter_t* hCPU)); template<auto fn, typename TNames, LogType TLogType> void cafeExportMakeWrapper(const char* libname, const char* funcname) { osLib_addFunctionInternal(libname, funcname, &cafeExportCallWrapper<fn, TNames, TLogType>); } #define cafeExportRegister(__libname, __func, __logtype) \ { \ struct StringWrapper { \ static const char* GetLib() { return __libname; }; \ static const char* GetFunc() { return #__func; }; \ }; \ cafeExportMakeWrapper<__func, StringWrapper, __logtype>(__libname, # __func);\ } #define cafeExportRegisterFunc(__func, __libname, __funcname, __logtype) \ {\ struct StringWrapper { \ static const char* GetLib() { return __libname; }; \ static const char* GetFunc() { return __funcname; }; \ }; \ cafeExportMakeWrapper<__func, StringWrapper, __logtype>(__libname, __funcname);\ } template<auto fn> MPTR makeCallableExport() { return PPCInterpreter_makeCallableExportDepr(&cafeExportCallWrapper<fn, "CALLABLE_EXPORT">); } void osLib_addVirtualPointer(const char* libraryName, const char* functionName, uint32 vPtr); ================================================ FILE: src/Cafe/OS/common/PPCConcurrentQueue.h ================================================ #pragma once #include <mutex> #include <condition_variable> #include <queue> #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" template <typename T> class PPCConcurrentQueue { public: PPCConcurrentQueue() = default; PPCConcurrentQueue(const PPCConcurrentQueue&) = delete; PPCConcurrentQueue& operator=(const PPCConcurrentQueue&) = delete; void push(const T& item, OSThread_t* thread) { //if(thread == nullptr) // thread = coreinitThread_getCurrentThread(ppcInterpreterCurrentInstance); //OSThread_t* currentThread = coreinit::OSGetCurrentThread(); //cemu_assert_debug(thread == nullptr || currentThread == thread); // cemuLog_logDebug(LogType::Force, "push suspend count: {}", _swapEndianU32(thread->suspend) - m_suspendCount); //__OSLockScheduler(); __OSLockScheduler(); m_queue.push(item); coreinit::__OSResumeThreadInternal(thread, 1); __OSUnlockScheduler(); //__OSUnlockScheduler(); //m_prevSuspendCount = _swapEndianU32(thread->suspend) - m_suspendCount; //coreinit_resumeThread(thread, _swapEndianU32(thread->suspend)); } T pop(OSThread_t* thread = nullptr) { //if (thread == nullptr) // thread = coreinitThread_getCurrentThread(ppcInterpreterCurrentInstance); OSThread_t* currentThread = coreinit::OSGetCurrentThread(); cemu_assert_debug(thread == nullptr || currentThread == thread); //thread = coreinitThread_getCurrentThread(ppcInterpreterCurrentInstance); // cemuLog_logDebug(LogType::Force, "pop suspend count: {}", _swapEndianU32(thread->suspend) + m_suspendCount); __OSLockScheduler(); if (m_queue.empty()) coreinit::__OSSuspendThreadInternal(thread); auto val = m_queue.front(); m_queue.pop(); __OSUnlockScheduler(); //coreinit_suspendThread(thread, m_suspendCount + m_prevSuspendCount); //m_prevSuspendCount = 0; //PPCCore_switchToScheduler(); return val; } private: //const int m_suspendCount = 8000; std::queue<T> m_queue; //std::atomic<uint32> m_prevSuspendCount; }; ================================================ FILE: src/Cafe/OS/libs/TCL/TCL.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/TCL/TCL.h" #include "HW/Latte/Core/LattePM4.h" namespace TCL { SysAllocator<coreinit::OSEvent> s_updateRetirementEvent; uint64 s_currentRetireMarker = 0; struct TCLStatePPC // mapped into PPC space { uint64be gpuRetireMarker; // written by GPU }; SysAllocator<TCLStatePPC> s_tclStatePPC; // called from GPU for timestamp EOP event void TCLGPUNotifyNewRetirementTimestamp() { // gpuRetireMarker is updated via event eop command __OSLockScheduler(); coreinit::OSSignalEventAllInternal(s_updateRetirementEvent.GetPtr()); __OSUnlockScheduler(); } int TCLTimestamp(TCLTimestampId id, uint64be* timestampOut) { if (id == TCLTimestampId::TIMESTAMP_LAST_BUFFER_RETIRED) { MEMPTR<uint32> b; // this is the timestamp of the last buffer that was retired by the GPU stdx::atomic_ref<uint64be> retireTimestamp(s_tclStatePPC->gpuRetireMarker); *timestampOut = retireTimestamp.load(); return 0; } else { cemuLog_log(LogType::Force, "TCLTimestamp(): Unsupported timestamp ID {}", (uint32)id); *timestampOut = 0; return 0; } } int TCLWaitTimestamp(TCLTimestampId id, uint64 waitTs, uint64 timeout) { if (id == TCLTimestampId::TIMESTAMP_LAST_BUFFER_RETIRED) { while ( true ) { stdx::atomic_ref<uint64be> retireTimestamp(s_tclStatePPC->gpuRetireMarker); uint64 currentTimestamp = retireTimestamp.load(); if (currentTimestamp >= waitTs) return 0; coreinit::OSWaitEvent(s_updateRetirementEvent.GetPtr()); } } else { cemuLog_log(LogType::Force, "TCLWaitTimestamp(): Unsupported timestamp ID {}", (uint32)id); } return 0; } static constexpr uint32 TCL_RING_BUFFER_SIZE = 4096; // in U32s std::atomic<uint32> tclRingBufferA[TCL_RING_BUFFER_SIZE]; std::atomic<uint32> tclRingBufferA_readIndex{0}; std::atomic<uint32> tclRingBufferA_writeIndex{0}; // GPU code calls this to grab the next command word bool TCLGPUReadRBWord(uint32& cmdWord) { uint32 readIndex = tclRingBufferA_readIndex.load(std::memory_order::relaxed); uint32 writeIndex = tclRingBufferA_writeIndex.load(std::memory_order::acquire); if (readIndex == writeIndex) return false; cmdWord = tclRingBufferA[readIndex].load(std::memory_order::relaxed); tclRingBufferA_readIndex.store((readIndex + 1) % TCL_RING_BUFFER_SIZE, std::memory_order::release); return true; } void TCLWaitForRBSpace(uint32be numU32s) { uint32 writeIndex = tclRingBufferA_writeIndex.load(std::memory_order::relaxed); while (true) { uint32 readIndex = tclRingBufferA_readIndex.load(std::memory_order::acquire); uint32 distance = (readIndex + TCL_RING_BUFFER_SIZE - writeIndex) & (TCL_RING_BUFFER_SIZE - 1); if (writeIndex == readIndex) // buffer completely empty distance = TCL_RING_BUFFER_SIZE; if (distance >= numU32s + 1) // assume distance minus one, because we are never allowed to completely wrap around break; _mm_pause(); } } // this function assumes that TCLWaitForRBSpace was called and that there is enough space void TCLWriteCmd(uint32be* cmd, uint32 cmdLen) { uint32 writeIndex = tclRingBufferA_writeIndex.load(std::memory_order::relaxed); while (cmdLen > 0) { tclRingBufferA[writeIndex].store(*cmd, std::memory_order::relaxed); writeIndex++; writeIndex &= (TCL_RING_BUFFER_SIZE - 1); cmd++; cmdLen--; } tclRingBufferA_writeIndex.store(writeIndex, std::memory_order::release); } #define EVENT_TYPE_TS 5 void TCLSubmitRetireMarker(bool triggerEventInterrupt) { s_currentRetireMarker++; uint32be cmd[6]; cmd[0] = pm4HeaderType3(IT_EVENT_WRITE_EOP, 5); cmd[1] = (4 | (EVENT_TYPE_TS << 8)); // event type (bits 8-15) and event index (bits 0-7). cmd[2] = MEMPTR<void>(&s_tclStatePPC->gpuRetireMarker).GetMPTR(); // address lower 32bits + data sel bits cmd[3] = 0x40000000; // select 64bit write, lower 16 bits are the upper bits of the address if (triggerEventInterrupt) cmd[3] |= 0x2000000; // trigger interrupt after value has been written cmd[4] = (uint32)s_currentRetireMarker; // data lower 32 bits cmd[5] = (uint32)(s_currentRetireMarker>>32); // data higher 32 bits TCLWriteCmd(cmd, 6); } int TCLSubmitToRing(uint32be* cmd, uint32 cmdLen, betype<TCLSubmissionFlag>* controlFlags, uint64be* timestampValueOut) { TCLSubmissionFlag flags = *controlFlags; cemu_assert_debug(timestampValueOut); // handle case where this is null // make sure there is enough space to submit all commands at one uint32 totalCommandLength = cmdLen; totalCommandLength += 6; // space needed for TCLSubmitRetireMarker TCLWaitForRBSpace(totalCommandLength); // submit command buffer TCLWriteCmd(cmd, cmdLen); // create new marker timestamp and tell GPU to write it to our variable after its done processing the command if ((HAS_FLAG(flags, TCLSubmissionFlag::USE_RETIRED_MARKER))) { TCLSubmitRetireMarker(!HAS_FLAG(flags, TCLSubmissionFlag::NO_MARKER_INTERRUPT)); *timestampValueOut = s_currentRetireMarker; // incremented before each submit } else { cemu_assert_unimplemented(); } return 0; } class : public COSModule { public: std::string_view GetName() override { return "tcl"; } void RPLMapped() override { cafeExportRegister("TCL", TCLSubmitToRing, LogType::Placeholder); cafeExportRegister("TCL", TCLTimestamp, LogType::Placeholder); cafeExportRegister("TCL", TCLWaitTimestamp, LogType::Placeholder); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { s_currentRetireMarker = 0; s_tclStatePPC->gpuRetireMarker = 0; coreinit::OSInitEvent(s_updateRetirementEvent.GetPtr(), coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); } else if (reason == coreinit::RplEntryReason::Unloaded) { s_currentRetireMarker = 0; s_tclStatePPC->gpuRetireMarker = 0; } } }s_COStclModule; COSModule* GetModule() { return &s_COStclModule; } } ================================================ FILE: src/Cafe/OS/libs/TCL/TCL.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace TCL { enum class TCLTimestampId { TIMESTAMP_LAST_BUFFER_RETIRED = 1, }; enum class TCLSubmissionFlag : uint32 { SURFACE_SYNC = 0x400000, // submit surface sync packet before cmd NO_MARKER_INTERRUPT = 0x200000, USE_RETIRED_MARKER = 0x20000000, // Controls whether the timer is updated before or after (retired) the cmd. Also controls which timestamp is returned for the submission. Before and after using separate counters }; int TCLTimestamp(TCLTimestampId id, uint64be* timestampOut); int TCLWaitTimestamp(TCLTimestampId id, uint64 waitTs, uint64 timeout); int TCLSubmitToRing(uint32be* cmd, uint32 cmdLen, betype<TCLSubmissionFlag>* controlFlags, uint64be* timestampValueOut); // called from Latte code bool TCLGPUReadRBWord(uint32& cmdWord); void TCLGPUNotifyNewRetirementTimestamp(); COSModule* GetModule(); } ENABLE_BITMASK_OPERATORS(TCL::TCLSubmissionFlag); ================================================ FILE: src/Cafe/OS/libs/avm/avm.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "avm.h" namespace avm { bool AVMIsHDCPAvailable() { return true; } bool AVMIsHDCPOn() { return true; } bool AVMGetAnalogContentsProtectionEnable(uint32be* isEnable) { *isEnable = 1; return false; } bool AVMIsAnalogContentsProtectionOn() { return true; } bool AVMSetAnalogContentsProtectionEnable(sint32 newState) { return true; // returns 1 (true) if new state was applied successfully? } class : public COSModule { public: std::string_view GetName() override { return "avm"; } void RPLMapped() override { cafeExportRegister("avm", AVMIsHDCPAvailable, LogType::Placeholder); cafeExportRegister("avm", AVMIsHDCPOn, LogType::Placeholder); cafeExportRegister("avm", AVMGetAnalogContentsProtectionEnable, LogType::Placeholder); cafeExportRegister("avm", AVMIsAnalogContentsProtectionOn, LogType::Placeholder); cafeExportRegister("avm", AVMSetAnalogContentsProtectionEnable, LogType::Placeholder); }; }s_COSavmModule; COSModule* GetModule() { return &s_COSavmModule; } } ================================================ FILE: src/Cafe/OS/libs/avm/avm.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace avm { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/camera/camera.cpp ================================================ #include "Common/precompiled.h" #include "Cafe/OS/common/OSCommon.h" #include "camera.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/HW/Espresso/PPCCallback.h" namespace camera { struct CAMInitInfo_t { /* +0x00 */ uint32be ukn00; /* +0x04 */ uint32be width; /* +0x08 */ uint32be height; /* +0x0C */ uint32be workMemorySize; /* +0x10 */ MEMPTR<void> workMemory; /* +0x14 */ uint32be handlerFuncPtr; /* +0x18 */ uint32be ukn18; /* +0x1C */ uint32be fps; /* +0x20 */ uint32be ukn20; }; struct CAMTargetSurface { /* +0x00 */ uint32be surfaceSize; /* +0x04 */ MEMPTR<void> surfacePtr; /* +0x08 */ uint32be ukn08; /* +0x0C */ uint32be ukn0C; /* +0x10 */ uint32be ukn10; /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be ukn18; /* +0x1C */ uint32be ukn1C; }; struct CAMCallbackParam { // type 0 - frame decoded | field1 - imagePtr, field2 - imageSize, field3 - ukn (0) // type 1 - ??? /* +0x0 */ uint32be type; // 0 -> Frame decoded /* +0x4 */ uint32be field1; /* +0x8 */ uint32be field2; /* +0xC */ uint32be field3; }; #define CAM_ERROR_SUCCESS 0 #define CAM_ERROR_INVALID_HANDLE -8 std::vector<struct CameraInstance*> g_table_cameraHandles; std::vector<struct CameraInstance*> g_activeCameraInstances; std::recursive_mutex g_mutex_camera; std::atomic_int g_cameraCounter{ 0 }; SysAllocator<coreinit::OSAlarm_t, 1> g_alarm_camera; SysAllocator<CAMCallbackParam, 1> g_cameraHandlerParam; CameraInstance* GetCameraInstanceByHandle(sint32 camHandle) { std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); if (camHandle <= 0) return nullptr; camHandle -= 1; if (camHandle >= g_table_cameraHandles.size()) return nullptr; return g_table_cameraHandles[camHandle]; } struct CameraInstance { CameraInstance(uint32 frameWidth, uint32 frameHeight, MPTR handlerFunc) : width(frameWidth), height(frameHeight), handlerFunc(handlerFunc) { AcquireHandle(); }; ~CameraInstance() { if (isOpen) { CloseCam(); } ReleaseHandle(); }; sint32 handle{ 0 }; uint32 width; uint32 height; bool isOpen{false}; std::queue<CAMTargetSurface> queue_targetSurfaces; MPTR handlerFunc; bool OpenCam() { if (isOpen) return false; isOpen = true; g_activeCameraInstances.push_back(this); return true; } bool CloseCam() { if (!isOpen) return false; isOpen = false; vectorRemoveByValue(g_activeCameraInstances, this); return true; } void QueueTargetSurface(CAMTargetSurface* targetSurface) { std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); cemu_assert_debug(queue_targetSurfaces.size() < 100); // check for sane queue length queue_targetSurfaces.push(*targetSurface); } private: void AcquireHandle() { std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); for (uint32 i = 0; i < g_table_cameraHandles.size(); i++) { if (g_table_cameraHandles[i] == nullptr) { g_table_cameraHandles[i] = this; this->handle = i + 1; return; } } this->handle = (sint32)(g_table_cameraHandles.size() + 1); g_table_cameraHandles.push_back(this); } void ReleaseHandle() { for (uint32 i = 0; i < g_table_cameraHandles.size(); i++) { if (g_table_cameraHandles[i] == this) { g_table_cameraHandles[i] = nullptr; return; } } cemu_assert_debug(false); } }; sint32 CAMGetMemReq(void* ukn) { return 1 * 1024; // always return 1KB } sint32 CAMCheckMemSegmentation(void* base, uint32 size) { return CAM_ERROR_SUCCESS; // always return success } void ppcCAMUpdate60(PPCInterpreter_t* hCPU) { // update all open camera instances size_t numCamInstances = g_activeCameraInstances.size(); //for (auto& itr : g_activeCameraInstances) for(size_t i=0; i<numCamInstances; i++) { std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); if (i >= g_activeCameraInstances.size()) break; CameraInstance* camInstance = g_activeCameraInstances[i]; // todo - handle 30 / 60 FPS if (camInstance->queue_targetSurfaces.empty()) continue; auto& targetSurface = camInstance->queue_targetSurfaces.front(); g_cameraHandlerParam->type = 0; g_cameraHandlerParam->field1 = targetSurface.surfacePtr.GetMPTR(); g_cameraHandlerParam->field2 = targetSurface.surfaceSize; g_cameraHandlerParam->field3 = 0; cemu_assert_debug(camInstance->handlerFunc != MPTR_NULL); camInstance->queue_targetSurfaces.pop(); _lock.unlock(); PPCCoreCallback(camInstance->handlerFunc, g_cameraHandlerParam.GetPtr()); } osLib_returnFromFunction(hCPU, 0); } sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* camInitInfo, uint32be* error) { CameraInstance* camInstance = new CameraInstance(camInitInfo->width, camInitInfo->height, camInitInfo->handlerFuncPtr); *error = 0; // Hunter's Trophy 2 will fail to boot if we don't set this std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); if (g_cameraCounter == 0) { coreinit::OSCreateAlarm(g_alarm_camera.GetPtr()); coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::OSGetTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60)); } g_cameraCounter++; return camInstance->handle; } sint32 CAMExit(sint32 camHandle) { CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); if (!camInstance) return CAM_ERROR_INVALID_HANDLE; CAMClose(camHandle); delete camInstance; std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); g_cameraCounter--; if (g_cameraCounter == 0) coreinit::OSCancelAlarm(g_alarm_camera.GetPtr()); return CAM_ERROR_SUCCESS; } sint32 CAMOpen(sint32 camHandle) { CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); if (!camInstance) return CAM_ERROR_INVALID_HANDLE; camInstance->OpenCam(); return CAM_ERROR_SUCCESS; } sint32 CAMClose(sint32 camHandle) { CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); if (!camInstance) return CAM_ERROR_INVALID_HANDLE; camInstance->CloseCam(); return CAM_ERROR_SUCCESS; } sint32 CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface) { CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); if (!camInstance) return CAM_ERROR_INVALID_HANDLE; camInstance->QueueTargetSurface(targetSurface); return CAM_ERROR_SUCCESS; } void reset() { g_cameraCounter = 0; } class : public COSModule { public: std::string_view GetName() override { return "camera"; } void RPLMapped() override { cafeExportRegister("camera", CAMGetMemReq, LogType::Placeholder); cafeExportRegister("camera", CAMCheckMemSegmentation, LogType::Placeholder); cafeExportRegister("camera", CAMInit, LogType::Placeholder); cafeExportRegister("camera", CAMExit, LogType::Placeholder); cafeExportRegister("camera", CAMOpen, LogType::Placeholder); cafeExportRegister("camera", CAMClose, LogType::Placeholder); cafeExportRegister("camera", CAMSubmitTargetSurface, LogType::Placeholder); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { reset(); } else if (reason == coreinit::RplEntryReason::Unloaded) { // todo } } }s_COScameraModule; COSModule* GetModule() { return &s_COScameraModule; } } ================================================ FILE: src/Cafe/OS/libs/camera/camera.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace camera { sint32 CAMOpen(sint32 camHandle); sint32 CAMClose(sint32 camHandle); COSModule* GetModule(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Common/SysAllocator.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/OS/libs/coreinit/coreinit_Misc.h" // includes for Initialize coreinit submodules #include "Cafe/OS/libs/coreinit/coreinit_BSP.h" #include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h" #include "Cafe/OS/libs/coreinit/coreinit_Atomic.h" #include "Cafe/OS/libs/coreinit/coreinit_OverlayArena.h" #include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" #include "Cafe/OS/libs/coreinit/coreinit_GHS.h" #include "Cafe/OS/libs/coreinit/coreinit_HWInterface.h" #include "Cafe/OS/libs/coreinit/coreinit_Memory.h" #include "Cafe/OS/libs/coreinit/coreinit_IM.h" #include "Cafe/OS/libs/coreinit/coreinit_LockedCache.h" #include "Cafe/OS/libs/coreinit/coreinit_MemoryMapping.h" #include "Cafe/OS/libs/coreinit/coreinit_IPC.h" #include "Cafe/OS/libs/coreinit/coreinit_IPCBuf.h" #include "Cafe/OS/libs/coreinit/coreinit_Coroutine.h" #include "Cafe/OS/libs/coreinit/coreinit_OSScreen.h" #include "Cafe/OS/libs/coreinit/coreinit_FG.h" #include "Cafe/OS/libs/coreinit/coreinit_SystemInfo.h" #include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_MCP.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_MPQueue.h" #include "Cafe/OS/libs/coreinit/coreinit_FS.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" CoreinitSharedData* gCoreinitData = nullptr; sint32 ScoreStackTrace(OSThread_t* thread, MPTR sp) { uint32 stackMinAddr = thread->stackEnd.GetMPTR(); uint32 stackMaxAddr = thread->stackBase.GetMPTR(); sint32 score = 0; uint32 currentStackPtr = sp; for (sint32 i = 0; i < 50; i++) { uint32 nextStackPtr = memory_readU32(currentStackPtr); if (nextStackPtr < currentStackPtr) break; if (nextStackPtr < stackMinAddr || nextStackPtr > stackMaxAddr) break; if ((nextStackPtr & 3) != 0) break; score += 10; uint32 returnAddress = 0; returnAddress = memory_readU32(nextStackPtr + 4); //cemuLog_log(LogType::Force, fmt::format("SP {0:08x} ReturnAddress {1:08x}", nextStackPtr, returnAddress)); if (returnAddress > 0 && returnAddress < 0x10000000 && (returnAddress&3) == 0) score += 5; // within code region else score -= 5; currentStackPtr = nextStackPtr; } return score; } void DebugLogStackTrace(OSThread_t* thread, MPTR sp, bool printSymbols) { // sp might not point to a valid stackframe // scan stack and evaluate which sp is most likely the beginning of the stackframe // scan 0x400 bytes sint32 highestScore = -1; uint32 highestScoreSP = sp; for (sint32 i = 0; i < 0x100; i++) { uint32 sampleSP = sp + i * 4; sint32 score = ScoreStackTrace(thread, sampleSP); if (score > highestScore) { highestScore = score; highestScoreSP = sampleSP; } } if (highestScoreSP != sp) cemuLog_log(LogType::Force, fmt::format("Trace starting at SP {0:08x} r1 = {1:08x}", highestScoreSP, sp)); else cemuLog_log(LogType::Force, fmt::format("Trace starting at SP/r1 {0:08x}", highestScoreSP)); // print stack trace uint32 currentStackPtr = highestScoreSP; uint32 stackMinAddr = thread->stackEnd.GetMPTR(); uint32 stackMaxAddr = thread->stackBase.GetMPTR(); for (sint32 i = 0; i < 20; i++) { uint32 nextStackPtr = memory_readU32(currentStackPtr); if (nextStackPtr < currentStackPtr) break; if (nextStackPtr < stackMinAddr || nextStackPtr > stackMaxAddr) break; uint32 returnAddress = 0; returnAddress = memory_readU32(nextStackPtr + 4); RPLStoredSymbol* symbol = nullptr; if(printSymbols) symbol = rplSymbolStorage_getByClosestAddress(returnAddress); if(symbol) cemuLog_log(LogType::Force, fmt::format("SP {:08x} ReturnAddr {:08x} ({}.{}+0x{:x})", nextStackPtr, returnAddress, (const char*)symbol->libName, (const char*)symbol->symbolName, returnAddress - symbol->address)); else cemuLog_log(LogType::Force, fmt::format("SP {:08x} ReturnAddr {:08x}", nextStackPtr, returnAddress)); currentStackPtr = nextStackPtr; } } typedef struct { /* +0x00 */ uint32be name; /* +0x04 */ uint32be fileType; // 2 = font /* +0x08 */ uint32be kernelFilenamePtr; /* +0x0C */ MEMPTR<void> data; /* +0x10 */ uint32be size; /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be ukn18; }coreinitShareddataEntry_t; static_assert(sizeof(coreinitShareddataEntry_t) == 0x1C, ""); uint8* extractCafeDefaultFont(sint32* size); MPTR placeholderFont = MPTR_NULL; sint32 placeholderFontSize = 0; void coreinitExport_OSGetSharedData(PPCInterpreter_t* hCPU) { // parameters: // r3 sharedAreaId // r4 flags // r5 areaPtrPtr // r6 areaSizePtr // on real Wii U hw/sw there is a list of shared area entries starting at offset +0xF8000000 // properly formated (each entry is 0x1C bytes) it looks like this: // FF CA FE 01 00 00 00 02 FF E8 47 AC F8 00 00 70 00 C8 0D 4C 00 00 00 00 FF FF FF FC // FF CA FE 02 00 00 00 02 FF E8 47 B7 F8 C8 0D C0 00 22 7E B4 00 00 00 00 00 00 11 D5 // FF CA FE 03 00 00 00 02 FF E8 47 A0 F8 EA 8C 80 00 25 44 E0 00 00 00 00 FF A0 00 00 // FF CA FE 04 00 00 00 02 FF E8 47 C2 F9 0F D1 60 00 7D 93 5C 00 00 00 00 FF FF FF FC uint32 sharedAreaId = hCPU->gpr[3]; coreinitShareddataEntry_t* shareddataTable = (coreinitShareddataEntry_t*)memory_getPointerFromVirtualOffset(MEMORY_SHAREDDATA_AREA_ADDR); uint32 name = 0xFFCAFE01 + sharedAreaId; for (sint32 i = 0; i < 4; i++) { if ((uint32)shareddataTable[i].name == name) { memory_writeU32(hCPU->gpr[5], shareddataTable[i].data.GetMPTR()); memory_writeU32(hCPU->gpr[6], (uint32)shareddataTable[i].size); osLib_returnFromFunction(hCPU, 1); return; } } // some games require a valid result or they will crash, return a pointer to our placeholder font cemuLog_log(LogType::Force, "OSGetSharedData() called by game but no shareddata fonts loaded. Use placeholder font"); if (placeholderFont == MPTR_NULL) { // load and then return placeholder font uint8* placeholderFontPtr = extractCafeDefaultFont(&placeholderFontSize); placeholderFont = coreinit_allocFromSysArea(placeholderFontSize, 256); if (placeholderFont == MPTR_NULL) cemuLog_log(LogType::Force, "Failed to alloc placeholder font sys memory"); memcpy(memory_getPointerFromVirtualOffset(placeholderFont), placeholderFontPtr, placeholderFontSize); free(placeholderFontPtr); } // return placeholder font memory_writeU32(hCPU->gpr[5], placeholderFont); memory_writeU32(hCPU->gpr[6], placeholderFontSize); osLib_returnFromFunction(hCPU, 1); } namespace coreinit { sint32 OSGetCoreId() { return PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); } uint32 OSGetCoreCount() { return Espresso::CORE_COUNT; } uint32 OSIsDebuggerInitialized() { return 0; } uint32 OSIsDebuggerPresent() { return 0; } uint32 OSGetConsoleType() { return 0x03000050; } uint32 OSGetMainCoreId() { return 1; } bool OSIsMainCore() { return OSGetCoreId() == OSGetMainCoreId(); } uint32 OSGetStackPointer() { return PPCInterpreter_getCurrentInstance()->gpr[1]; } void coreinitExport_ENVGetEnvironmentVariable(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "ENVGetEnvironmentVariable(\"{}\",0x08x,0x{:x})", (char*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]), hCPU->gpr[4], hCPU->gpr[5]); char* envKeyStr = (char*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); char* outputString = (char*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); sint32 outputStringMaxLen = (sint32)hCPU->gpr[5]; // also return the string "" just in case if (outputStringMaxLen > 0) { outputString[0] = '\0'; } osLib_returnFromFunction(hCPU, 1); } void coreinit_exit(uint32 r) { cemuLog_log(LogType::Force, "The title terminated the process by calling coreinit.exit({})", (sint32)r); DebugLogStackTrace(coreinit::OSGetCurrentThread(), coreinit::OSGetStackPointer()); cemu_assert_debug(false); // never return while (true) std::this_thread::sleep_for(std::chrono::milliseconds(100)); } bool OSIsOffBoot() { return true; } uint32 OSGetBootPMFlags() { cemuLog_logDebug(LogType::Force, "OSGetBootPMFlags() - placeholder"); return 0; } uint32 OSGetSystemMode() { cemuLog_logDebug(LogType::Force, "OSGetSystemMode() - placeholder"); // if this returns 2, barista softlocks shortly after boot return 0; } void OSPanic(const char* file, sint32 lineNumber, const char* msg) { cemuLog_log(LogType::Force, "OSPanic!"); cemuLog_log(LogType::Force, "File: {}:{}", file, lineNumber); cemuLog_log(LogType::Force, "Msg: {}", msg); DebugLogStackTrace(coreinit::OSGetCurrentThread(), coreinit::OSGetStackPointer()); #ifdef CEMU_DEBUG_ASSERT while (true) std::this_thread::sleep_for(std::chrono::milliseconds(100)); #endif } void InitializeCore() { cafeExportRegister("coreinit", OSGetCoreId, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetCoreCount, LogType::CoreinitThread); cafeExportRegister("coreinit", OSIsDebuggerInitialized, LogType::CoreinitThread); cafeExportRegister("coreinit", OSIsDebuggerPresent, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetConsoleType, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetMainCoreId, LogType::CoreinitThread); cafeExportRegister("coreinit", OSIsMainCore, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetStackPointer, LogType::CoreinitThread); osLib_addFunction("coreinit", "ENVGetEnvironmentVariable", coreinitExport_ENVGetEnvironmentVariable); cafeExportRegisterFunc(coreinit_exit, "coreinit", "exit", LogType::CoreinitThread); cafeExportRegister("coreinit", OSIsOffBoot, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetBootPMFlags, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetSystemMode, LogType::CoreinitThread); cafeExportRegister("coreinit", OSPanic, LogType::Placeholder); } class : public COSModule { public: std::string_view GetName() override { return "coreinit"; } void RPLMapped() override { coreinit::InitializeCore(); coreinit::InitializeSchedulerLock(); coreinit::InitializeSysHeap(); // allocate coreinit global data gCoreinitData = (CoreinitSharedData*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sizeof(CoreinitSharedData), 32)); memset(gCoreinitData, 0x00, sizeof(CoreinitSharedData)); // coreinit weak links osLib_addVirtualPointer("coreinit", "MEMAllocFromDefaultHeap", memory_getVirtualOffsetFromPointer(&gCoreinitData->MEMAllocFromDefaultHeap)); osLib_addVirtualPointer("coreinit", "MEMAllocFromDefaultHeapEx", memory_getVirtualOffsetFromPointer(&gCoreinitData->MEMAllocFromDefaultHeapEx)); osLib_addVirtualPointer("coreinit", "MEMFreeToDefaultHeap", memory_getVirtualOffsetFromPointer(&gCoreinitData->MEMFreeToDefaultHeap)); osLib_addVirtualPointer("coreinit", "__atexit_cleanup", memory_getVirtualOffsetFromPointer(&gCoreinitData->__atexit_cleanup)); osLib_addVirtualPointer("coreinit", "__stdio_cleanup", memory_getVirtualOffsetFromPointer(&gCoreinitData->__stdio_cleanup)); osLib_addVirtualPointer("coreinit", "__cpp_exception_cleanup_ptr", memory_getVirtualOffsetFromPointer(&gCoreinitData->__cpp_exception_cleanup_ptr)); osLib_addVirtualPointer("coreinit", "__cpp_exception_init_ptr", memory_getVirtualOffsetFromPointer(&gCoreinitData->__cpp_exception_init_ptr)); // init GHS and threads coreinit::PrepareGHSRuntime(); coreinit::MapThreadExports(); // reset threads activeThreadCount = 0; // init submodules coreinit::InitializeMEM(); coreinit::InitializeMEMFrmHeap(); coreinit::InitializeMEMUnitHeap(); coreinit::InitializeMEMBlockHeap(); coreinit::InitializeFG(); coreinit::InitializeBSP(); coreinit::InitializeMCP(); coreinit::InitializeOverlayArena(); coreinit::InitializeDynLoad(); coreinit::InitializeGHS(); coreinit::InitializeHWInterface(); coreinit::InitializeAtomic(); coreinit::InitializeMemory(); coreinit::InitializeIM(); coreinit::InitializeLC(); coreinit::InitializeMP(); coreinit::InitializeTimeAndCalendar(); coreinit::MapAlarmExports(); coreinit::InitializeFS(); coreinit::InitializeSystemInfo(); coreinit::InitializeConcurrency(); coreinit::InitializeSpinlock(); coreinit::InitializeMessageQueue(); coreinit::MapIPCExports(); coreinit::InitializeIPCBuf(); coreinit::InitializeMemoryMapping(); coreinit::InitializeCodeGen(); coreinit::InitializeCoroutine(); coreinit::InitializeOSScreen(); // legacy mem stuff coreinit::expheap_load(); // misc exports coreinit::miscInit(); osLib_addFunction("coreinit", "OSGetSharedData", coreinitExport_OSGetSharedData); osLib_addFunction("coreinit", "UCReadSysConfig", coreinitExport_UCReadSysConfig); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { coreinit::InitializeThread(); coreinit::InitializeAlarm(); coreinit::InitializeIPC(); InitializeAsyncCallback(); // remaining coreinit initialization happens in coreinit_start and requires a valid PPC context OSThread_t* initialThread = coreinit::OSGetDefaultThread(1); coreinit::OSSetThreadPriority(initialThread, 16); coreinit::OSRunThread(initialThread, PPCInterpreter_makeCallableExportDepr(coreinit_start), 0, nullptr); } else if (reason == coreinit::RplEntryReason::Unloaded) { // todo } } }s_COSCoreinitModule; COSModule* GetModule() { return &s_COSCoreinitModule; } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit.h ================================================ #pragma once #include "Cafe/HW/Espresso/Const.h" #include "Cafe/OS/RPL/COSModule.h" #define PPC_CORE_COUNT (Espresso::CORE_COUNT) #include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" // async callback helper void InitializeAsyncCallback(); void coreinitAsyncCallback_add(MPTR functionMPTR, uint32 numParameters, uint32 r3 = 0, uint32 r4 = 0, uint32 r5 = 0, uint32 r6 = 0, uint32 r7 = 0, uint32 r8 = 0, uint32 r9 = 0, uint32 r10 = 0); void coreinitAsyncCallback_addWithLock(MPTR functionMPTR, uint32 numParameters, uint32 r3 = 0, uint32 r4 = 0, uint32 r5 = 0, uint32 r6 = 0, uint32 r7 = 0, uint32 r8 = 0, uint32 r9 = 0, uint32 r10 = 0); // coreinit shared memory struct CoreinitSharedData { MEMPTR<void> MEMAllocFromDefaultHeap; MEMPTR<void> MEMAllocFromDefaultHeapEx; MEMPTR<void> MEMFreeToDefaultHeap; MPTR __atexit_cleanup; MPTR __cpp_exception_init_ptr; MPTR __cpp_exception_cleanup_ptr; MPTR __stdio_cleanup; }; extern CoreinitSharedData* gCoreinitData; // coreinit init void coreinit_start(PPCInterpreter_t* hCPU); MPTR OSAllocFromSystem(uint32 size, uint32 alignment); void OSFreeToSystem(MPTR mem); // above is all the legacy stuff. New code uses namespaces namespace coreinit { sint32 OSGetCoreId(); uint32 OSGetCoreCount(); uint32 OSGetStackPointer(); COSModule* GetModule(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cafe/OS/RPL/rpl.h" // #define ALARM_LOGGING namespace coreinit { SysAllocator<OSEvent> g_alarmEvent; SysAllocator<OSThread_t> g_alarmThread; SysAllocator<uint8, 1024 * 128> _g_alarmThreadStack; SysAllocator<char, 32> _g_alarmThreadName; class OSHostAlarm { public: OSHostAlarm(uint64 nextFire, uint64 period, void(*callbackFunc)(uint64 currentTick, void* context), void* context) : m_nextFire(nextFire), m_period(period), m_callbackFunc(callbackFunc), m_context(context) { cemu_assert_debug(__OSHasSchedulerLock()); // must hold lock auto r = g_activeAlarmList.emplace(this); cemu_assert_debug(r.second); // check if insertion was successful m_isActive = true; updateEarliestAlarmAtomic(); } ~OSHostAlarm() { cemu_assert_debug(__OSHasSchedulerLock()); // must hold lock if (m_isActive) { g_activeAlarmList.erase(g_activeAlarmList.find(this)); updateEarliestAlarmAtomic(); } } uint64 getFireTick() const { return m_nextFire; } void triggerAlarm(uint64 currentTick) { m_callbackFunc(currentTick, m_context); } static void updateEarliestAlarmAtomic() { cemu_assert_debug(__OSHasSchedulerLock()); if (!g_activeAlarmList.empty()) { auto firstAlarm = g_activeAlarmList.begin(); g_soonestAlarm = (*firstAlarm)->m_nextFire; } else { g_soonestAlarm = std::numeric_limits<uint64>::max(); } } static void updateAlarms(uint64 currentTick) { cemu_assert_debug(__OSHasSchedulerLock()); if (g_activeAlarmList.empty()) return; // debug begin #ifdef CEMU_DEBUG_ASSERT uint64 prevTick = 0; auto itr = g_activeAlarmList.begin(); while (itr != g_activeAlarmList.end()) { uint64 t = (*itr)->m_nextFire; if (t < prevTick) cemu_assert_suspicious(); prevTick = t; ++itr; } #endif // debug end while (true) { auto firstAlarm = g_activeAlarmList.begin(); if (currentTick >= (*firstAlarm)->m_nextFire) { OSHostAlarm* alarm = *firstAlarm; g_activeAlarmList.erase(firstAlarm); alarm->triggerAlarm(currentTick); // if periodic alarm then requeue if (alarm->m_period > 0) { alarm->m_nextFire += alarm->m_period; g_activeAlarmList.emplace(alarm); } else alarm->m_isActive = false; updateEarliestAlarmAtomic(); } else break; } } uint64 getNextFire() const { return m_nextFire; } static bool quickCheckForAlarm(uint64 currentTick) { // fast way to check if any alarm was triggered without requiring scheduler lock return currentTick >= g_soonestAlarm; } static void Reset() { g_activeAlarmList.clear(); g_soonestAlarm = 0; } public: struct ComparatorFireTime { bool operator ()(OSHostAlarm* const & p1, OSHostAlarm* const & p2) const { auto p1Fire = p1->getNextFire(); auto p2Fire = p2->getNextFire(); if (p1Fire == p2Fire) return (uintptr_t)p1 < (uintptr_t)p2; // if time is equal, differ by pointer (to allow storing multiple alarms with same firing time) return p1Fire < p2Fire; } }; private: uint64 m_nextFire; uint64 m_period; // if zero then repeat is disabled bool m_isActive{ false }; void (*m_callbackFunc)(uint64 currentTick, void* context); void* m_context; static std::set<class OSHostAlarm*, ComparatorFireTime> g_activeAlarmList; static std::atomic_uint64_t g_soonestAlarm; }; std::set<class OSHostAlarm*, OSHostAlarm::ComparatorFireTime> OSHostAlarm::g_activeAlarmList; std::atomic_uint64_t OSHostAlarm::g_soonestAlarm{}; OSHostAlarm* OSHostAlarmCreate(uint64 nextFire, uint64 period, void(*callbackFunc)(uint64 currentTick, void* context), void* context) { OSHostAlarm* hostAlarm = new OSHostAlarm(nextFire, period, callbackFunc, context); return hostAlarm; } void OSHostAlarmDestroy(OSHostAlarm* hostAlarm) { delete hostAlarm; } void alarm_update() { cemu_assert_debug(!__OSHasSchedulerLock()); uint64 currentTick = coreinit::OSGetTime(); if (!OSHostAlarm::quickCheckForAlarm(currentTick)) return; __OSLockScheduler(); OSHostAlarm::updateAlarms(currentTick); __OSUnlockScheduler(); } /* alarm API */ void OSCreateAlarm(OSAlarm_t* alarm) { memset(alarm, 0, sizeof(OSAlarm_t)); alarm->setMagic(); } void OSCreateAlarmEx(OSAlarm_t* alarm, const char* alarmName) { memset(alarm, 0, sizeof(OSAlarm_t)); alarm->setMagic(); alarm->name = alarmName; } std::unordered_map<OSAlarm_t*, OSHostAlarm*> g_activeAlarms; bool OSCancelAlarm(OSAlarm_t* alarm) { __OSLockScheduler(); bool alarmWasActive = false; auto itr = g_activeAlarms.find(alarm); if (itr != g_activeAlarms.end()) { OSHostAlarmDestroy(itr->second); g_activeAlarms.erase(itr); alarmWasActive = true; } __OSUnlockScheduler(); return alarmWasActive; } void __OSHostAlarmTriggered(uint64 currentTick, void* context) { #ifdef ALARM_LOGGING cemuLog_log(LogType::Force, "[Alarm] Alarm ready and alarm thread signalled. Current tick: {}", currentTick); #endif OSSignalEventInternal(g_alarmEvent.GetPtr()); } void __OSInitiateAlarm(OSAlarm_t* alarm, uint64 startTime, uint64 period, MPTR handlerFunc, bool isPeriodic) { cemu_assert_debug(MMU_IsInPPCMemorySpace(alarm)); cemu_assert_debug(__OSHasSchedulerLock()); uint64 nextTime = startTime; #ifdef ALARM_LOGGING double periodInMS = (double)period * 1000.0 / (double)EspressoTime::GetTimerClock(); cemuLog_log(LogType::Force, "[Alarm] Start alarm 0x{:08x}. Func 0x{:08x}. Period: {}ms", MEMPTR(alarm).GetMPTR(), handlerFunc, periodInMS); #endif if (isPeriodic) { cemu_assert_debug(period != 0); if (period == 0) return; uint64 currentTime = OSGetTime(); uint64 ticksSinceStart = currentTime - startTime; uint64 numPeriods = ticksSinceStart / period; nextTime = startTime + (numPeriods + 1ull) * period; alarm->startTime = _swapEndianU64(startTime); alarm->nextTime = _swapEndianU64(nextTime); alarm->period = _swapEndianU64(period); alarm->handler = _swapEndianU32(handlerFunc); } else { alarm->nextTime = _swapEndianU64(startTime); alarm->period = 0; alarm->handler = _swapEndianU32(handlerFunc); } auto existingAlarmItr = g_activeAlarms.find(alarm); if (existingAlarmItr != g_activeAlarms.end()) { // delete existing alarm cemuLog_logDebug(LogType::Force, "__OSInitiateAlarm() called on alarm which was already active"); OSHostAlarmDestroy(existingAlarmItr->second); g_activeAlarms.erase(existingAlarmItr); } g_activeAlarms[alarm] = OSHostAlarmCreate(nextTime, period, __OSHostAlarmTriggered, nullptr); } void OSSetAlarm(OSAlarm_t* alarm, uint64 delayInTicks, MPTR handlerFunc) { __OSLockScheduler(); __OSInitiateAlarm(alarm, OSGetTime() + delayInTicks, 0, handlerFunc, false); __OSUnlockScheduler(); } void OSSetPeriodicAlarm(OSAlarm_t* alarm, uint64 nextFire, uint64 period, MPTR handlerFunc) { __OSLockScheduler(); __OSInitiateAlarm(alarm, nextFire, period, handlerFunc, true); __OSUnlockScheduler(); } void OSSetAlarmUserData(OSAlarm_t* alarm, uint32 userData) { alarm->userData = userData; } uint32 OSGetAlarmUserData(OSAlarm_t* alarm) { return alarm->userData; } void OSAlarm_Shutdown() { __OSLockScheduler(); if(g_activeAlarms.empty()) { __OSUnlockScheduler(); return; } for(auto& itr : g_activeAlarms) { OSHostAlarmDestroy(itr.second); } g_activeAlarms.clear(); OSHostAlarm::Reset(); __OSUnlockScheduler(); } void _OSAlarmThread(PPCInterpreter_t* hCPU) { while( true ) { OSWaitEvent(g_alarmEvent.GetPtr()); uint64 currentTick = OSGetTime(); while (true) { // get alarm to fire OSAlarm_t* alarm = nullptr; __OSLockScheduler(); auto itr = g_activeAlarms.begin(); while(itr != g_activeAlarms.end()) { if (currentTick >= _swapEndianU64(itr->first->nextTime)) { alarm = itr->first; if (alarm->period == 0) { // end alarm g_activeAlarms.erase(itr); break; } else { alarm->nextTime = _swapEndianU64(_swapEndianU64(alarm->nextTime) + _swapEndianU64(alarm->period)); } break; } ++itr; } __OSUnlockScheduler(); if (!alarm) break; // do callback for alarm #ifdef ALARM_LOGGING double periodInMS = (double)_swapEndianU64(alarm->period) * 1000.0 / (double)EspressoTime::GetTimerClock(); cemuLog_log(LogType::Force, "[Alarm] Callback 0x{:08x} for alarm 0x{:08x}. Current tick: {}. Period: {}ms", _swapEndianU32(alarm->handler), MEMPTR(alarm).GetMPTR(), currentTick, periodInMS); #endif PPCCoreCallback(_swapEndianU32(alarm->handler), alarm, &(g_alarmThread.GetPtr()->context)); } } } void MapAlarmExports() { cafeExportRegister("coreinit", OSCreateAlarm, LogType::CoreinitAlarm); cafeExportRegister("coreinit", OSCreateAlarmEx, LogType::CoreinitAlarm); cafeExportRegister("coreinit", OSCancelAlarm, LogType::CoreinitAlarm); cafeExportRegister("coreinit", OSSetAlarm, LogType::CoreinitAlarm); cafeExportRegister("coreinit", OSSetPeriodicAlarm, LogType::CoreinitAlarm); cafeExportRegister("coreinit", OSSetAlarmUserData, LogType::CoreinitAlarm); cafeExportRegister("coreinit", OSGetAlarmUserData, LogType::CoreinitAlarm); } void InitializeAlarm() { // init event OSInitEvent(g_alarmEvent.GetPtr(), OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO); // create alarm callback handler thread coreinit::OSCreateThreadType(g_alarmThread.GetPtr(), RPLLoader_MakePPCCallable(_OSAlarmThread), 0, nullptr, _g_alarmThreadStack.GetPtr() + _g_alarmThreadStack.GetByteSize(), (sint32)_g_alarmThreadStack.GetByteSize(), 0, 0x7, OSThread_t::THREAD_TYPE::TYPE_IO); OSResumeThread(g_alarmThread.GetPtr()); strcpy(_g_alarmThreadName.GetPtr(), "Alarm Thread"); coreinit::OSSetThreadName(g_alarmThread.GetPtr(), _g_alarmThreadName.GetPtr()); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Alarm.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" namespace coreinit { class OSHostAlarm; OSHostAlarm* OSHostAlarmCreate(uint64 nextFire, uint64 period, void(*callbackFunc)(uint64 currentTick, void* context), void* context); void OSHostAlarmDestroy(OSHostAlarm* hostAlarm); struct OSAlarm_t { /* +0x00 */ betype<uint32> magic; /* +0x04 */ MEMPTR<const char> name; /* +0x08 */ uint32 ukn08; /* +0x0C */ MPTR handler; /* +0x10 */ uint32 ukn10; /* +0x14 */ uint32 padding14; /* +0x18 */ uint64 nextTime; // next fire time /* +0x20 */ MPTR prev; // pointer to OSAlarm /* +0x24 */ MPTR next; // pointer to OSAlarm /* +0x28 */ uint64 period; // period (zero for non-periodic timer) /* +0x30 */ uint64 startTime; // period start /* +0x38 */ uint32be userData; /* +0x3C */ uint32 ukn3C; /* +0x40 */ OSThreadQueue uknThreadQueue; /* +0x50 */ MPTR alarmQueue; /* +0x54 */ MPTR ukn54; void setMagic() { magic = (uint32)'aLrM'; } bool checkMagic() { return magic == (uint32)'aLrM'; } }; static_assert(sizeof(OSAlarm_t) == 0x58); void OSCreateAlarm(OSAlarm_t* alarm); bool OSCancelAlarm(OSAlarm_t* alarm); void OSSetAlarm(OSAlarm_t* alarm, uint64 time, MPTR handlerFunc); void OSSetAlarmUserData(OSAlarm_t* alarm, uint32 userData); void OSSetPeriodicAlarm(OSAlarm_t* OSAlarm, uint64 startTick, uint64 periodTick, MPTR OSAlarmHandler); void OSAlarm_Shutdown(); void alarm_update(); void MapAlarmExports(); void InitializeAlarm(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Atomic.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include <atomic> #include "coreinit_Atomic.h" namespace coreinit { /* 32bit atomic operations */ uint32 OSSwapAtomic(std::atomic<uint32be>* mem, uint32 newValue) { uint32be _newValue = newValue; uint32be previousValue = mem->exchange(_newValue); return previousValue; } bool OSCompareAndSwapAtomic(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue) { // seen in GTA3 homebrew port uint32be _compareValue = compareValue; uint32be _swapValue = swapValue; return mem->compare_exchange_strong(_compareValue, _swapValue); } bool OSCompareAndSwapAtomicEx(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue, uint32be* previousValue) { // seen in GTA3 homebrew port uint32be _compareValue = compareValue; uint32be _swapValue = swapValue; bool r = mem->compare_exchange_strong(_compareValue, _swapValue); *previousValue = _compareValue; return r; } uint32 OSAddAtomic(std::atomic<uint32be>* mem, uint32 adder) { // used by SDL Wii U port uint32be knownValue; while (true) { knownValue = mem->load(); uint32be newValue = knownValue + adder; if (mem->compare_exchange_strong(knownValue, newValue)) break; } return knownValue; } /* 64bit atomic operations */ uint64 OSSwapAtomic64(std::atomic<uint64be>* mem, uint64 newValue) { uint64be _newValue = newValue; uint64be previousValue = mem->exchange(_newValue); return previousValue; } uint64 OSSetAtomic64(std::atomic<uint64be>* mem, uint64 newValue) { return OSSwapAtomic64(mem, newValue); } uint64 OSGetAtomic64(std::atomic<uint64be>* mem) { return mem->load(); } uint64 OSAddAtomic64(std::atomic<uint64be>* mem, uint64 adder) { uint64be knownValue; while (true) { knownValue = mem->load(); uint64be newValue = knownValue + adder; if (mem->compare_exchange_strong(knownValue, newValue)) break; } return knownValue; } uint64 OSAndAtomic64(std::atomic<uint64be>* mem, uint64 val) { uint64be knownValue; while (true) { knownValue = mem->load(); uint64be newValue = knownValue & val; if (mem->compare_exchange_strong(knownValue, newValue)) break; } return knownValue; } uint64 OSOrAtomic64(std::atomic<uint64be>* mem, uint64 val) { uint64be knownValue; while (true) { knownValue = mem->load(); uint64be newValue = knownValue | val; if (mem->compare_exchange_strong(knownValue, newValue)) break; } return knownValue; } bool OSCompareAndSwapAtomic64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue) { uint64be _compareValue = compareValue; uint64be _swapValue = swapValue; return mem->compare_exchange_strong(_compareValue, _swapValue); } bool OSCompareAndSwapAtomicEx64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue, uint64be* previousValue) { uint64be _compareValue = compareValue; uint64be _swapValue = swapValue; bool r = mem->compare_exchange_strong(_compareValue, _swapValue); *previousValue = _compareValue; return r; } void InitializeAtomic() { // 32bit atomic operations cafeExportRegister("coreinit", OSSwapAtomic, LogType::Placeholder); cafeExportRegister("coreinit", OSCompareAndSwapAtomic, LogType::Placeholder); cafeExportRegister("coreinit", OSCompareAndSwapAtomicEx, LogType::Placeholder); cafeExportRegister("coreinit", OSAddAtomic, LogType::Placeholder); // 64bit atomic operations cafeExportRegister("coreinit", OSSetAtomic64, LogType::Placeholder); cafeExportRegister("coreinit", OSGetAtomic64, LogType::Placeholder); cafeExportRegister("coreinit", OSSwapAtomic64, LogType::Placeholder); cafeExportRegister("coreinit", OSAddAtomic64, LogType::Placeholder); cafeExportRegister("coreinit", OSAndAtomic64, LogType::Placeholder); cafeExportRegister("coreinit", OSOrAtomic64, LogType::Placeholder); cafeExportRegister("coreinit", OSCompareAndSwapAtomic64, LogType::Placeholder); cafeExportRegister("coreinit", OSCompareAndSwapAtomicEx64, LogType::Placeholder); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Atomic.h ================================================ #pragma once #include <atomic> namespace coreinit { uint32 OSSwapAtomic(std::atomic<uint32be>* mem, uint32 newValue); bool OSCompareAndSwapAtomic(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue); bool OSCompareAndSwapAtomicEx(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue, uint32be* previousValue); uint32 OSAddAtomic(std::atomic<uint32be>* mem, uint32 adder); uint64 OSSwapAtomic64(std::atomic<uint64be>* mem, uint64 newValue); uint64 OSSetAtomic64(std::atomic<uint64be>* mem, uint64 newValue); uint64 OSGetAtomic64(std::atomic<uint64be>* mem); uint64 OSAddAtomic64(std::atomic<uint64be>* mem, uint64 adder); uint64 OSAndAtomic64(std::atomic<uint64be>* mem, uint64 val); uint64 OSOrAtomic64(std::atomic<uint64be>* mem, uint64 val); bool OSCompareAndSwapAtomic64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue); bool OSCompareAndSwapAtomicEx64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue, uint64be* previousValue); void InitializeAtomic(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_BSP.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "coreinit_BSP.h" namespace coreinit { bool bspGetHardwareVersion(uint32be* version) { uint8 highVersion = 0x11; // anything below 0x11 will be considered as Hollywood // todo: Check version returned on console uint32 tempVers = highVersion << 24; *version = tempVers; return true; } void InitializeBSP() { cafeExportRegister("coreinit", bspGetHardwareVersion, LogType::Placeholder); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_BSP.h ================================================ namespace coreinit { void InitializeBSP(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Callbacks.cpp ================================================ #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "util/helpers/fspinlock.h" struct CoreinitAsyncCallback { CoreinitAsyncCallback(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) : m_functionMPTR(functionMPTR), m_numParameters(numParameters), m_gprParam{ r3, r4, r5, r6, r7, r8, r9, r10 } {}; static void queue(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) { s_asyncCallbackSpinlock.lock(); s_asyncCallbackQueue.emplace_back(allocateAndInitFromPool(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10)); s_asyncCallbackSpinlock.unlock(); } static void callNextFromQueue() { s_asyncCallbackSpinlock.lock(); if (s_asyncCallbackQueue.empty()) { cemuLog_log(LogType::Force, "AsyncCallbackQueue is empty. Unexpected behavior"); s_asyncCallbackSpinlock.unlock(); return; } CoreinitAsyncCallback* cb = s_asyncCallbackQueue[0]; s_asyncCallbackQueue.erase(s_asyncCallbackQueue.begin()); s_asyncCallbackSpinlock.unlock(); cb->doCall(); s_asyncCallbackSpinlock.lock(); releaseToPool(cb); s_asyncCallbackSpinlock.unlock(); } private: void doCall() { PPCCoreCallback(m_functionMPTR, m_gprParam[0], m_gprParam[1], m_gprParam[2], m_gprParam[3], m_gprParam[4], m_gprParam[5], m_gprParam[6], m_gprParam[7]); } static CoreinitAsyncCallback* allocateAndInitFromPool(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) { cemu_assert_debug(s_asyncCallbackSpinlock.is_locked()); if (s_asyncCallbackPool.empty()) { CoreinitAsyncCallback* cb = new CoreinitAsyncCallback(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10); return cb; } CoreinitAsyncCallback* cb = s_asyncCallbackPool[0]; s_asyncCallbackPool.erase(s_asyncCallbackPool.begin()); *cb = CoreinitAsyncCallback(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10); return cb; } static void releaseToPool(CoreinitAsyncCallback* cb) { cemu_assert_debug(s_asyncCallbackSpinlock.is_locked()); s_asyncCallbackPool.emplace_back(cb); } static std::vector<struct CoreinitAsyncCallback*> s_asyncCallbackPool; static std::vector<struct CoreinitAsyncCallback*> s_asyncCallbackQueue; static FSpinlock s_asyncCallbackSpinlock; sint32 m_numParameters; uint32 m_gprParam[9]; MPTR m_functionMPTR; }; std::vector<struct CoreinitAsyncCallback*> CoreinitAsyncCallback::s_asyncCallbackPool; std::vector<struct CoreinitAsyncCallback*> CoreinitAsyncCallback::s_asyncCallbackQueue; FSpinlock CoreinitAsyncCallback::s_asyncCallbackSpinlock; SysAllocator<OSThread_t> g_coreinitCallbackThread; SysAllocator<uint8, 1024*64> _g_coreinitCallbackThreadStack; SysAllocator<coreinit::OSSemaphore> g_asyncCallbackAsync; SysAllocator<char, 32> _g_coreinitCBThreadName; void _coreinitCallbackThread(PPCInterpreter_t* hCPU) { while (coreinit::OSWaitSemaphore(g_asyncCallbackAsync.GetPtr())) { CoreinitAsyncCallback::callNextFromQueue(); } } void coreinitAsyncCallback_addWithLock(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) { cemu_assert_debug(numParameters <= 8); if (functionMPTR >= 0x10000000) { cemuLog_log(LogType::Force, fmt::format("Suspicious callback address {0:08x} params: {1:08x} {2:08x} {3:08x} {4:08x}", functionMPTR, r3, r4, r5, r6)); cemuLog_waitForFlush(); } CoreinitAsyncCallback::queue(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10); __OSLockScheduler(); coreinit::OSSignalSemaphoreInternal(g_asyncCallbackAsync.GetPtr(), false); __OSUnlockScheduler(); } void coreinitAsyncCallback_add(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) { cemu_assert_debug(__OSHasSchedulerLock() == false); // do not call when holding scheduler lock coreinitAsyncCallback_addWithLock(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10); } void InitializeAsyncCallback() { coreinit::OSInitSemaphore(g_asyncCallbackAsync.GetPtr(), 0); coreinit::OSCreateThreadType(g_coreinitCallbackThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(_coreinitCallbackThread), 0, nullptr, _g_coreinitCallbackThreadStack.GetPtr() + _g_coreinitCallbackThreadStack.GetByteSize(), (sint32)_g_coreinitCallbackThreadStack.GetByteSize(), 0, 7, OSThread_t::THREAD_TYPE::TYPE_IO); coreinit::OSResumeThread(g_coreinitCallbackThread.GetPtr()); strcpy(_g_coreinitCBThreadName.GetPtr(), "Callback Thread"); coreinit::OSSetThreadName(g_coreinitCallbackThread.GetPtr(), _g_coreinitCBThreadName.GetPtr()); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_CodeGen.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Common/ExceptionHandler/ExceptionHandler.h" namespace coreinit { struct { bool rangeIsAllocated; MPTR rangeStart; uint32 rangeSize; uint8* cacheStateCopy; // holds a copy of the entire range, simulates icache state (updated via ICBI) }coreinitCodeGen = {0}; void codeGenArea_memoryWriteCallback(void* pageStart, size_t size) { uint32 ea = memory_getVirtualOffsetFromPointer(pageStart); uint32 eaEnd = ea + (uint32)size; while (ea <= eaEnd) { codeGenHandleICBI(ea); ea += 0x20; } } void OSGetCodegenVirtAddrRange(betype<uint32>* rangeStart, betype<uint32>* rangeSize) { uint32 codegenSize = 0x01000000; // todo: Read from cos.xml //debug_printf("OSGetCodegenVirtAddrRange(0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); // on first call, allocate range if( coreinitCodeGen.rangeIsAllocated == false ) { coreinitCodeGen.rangeStart = RPLLoader_AllocateCodeSpace(codegenSize, 0x1000); coreinitCodeGen.rangeSize = codegenSize; coreinitCodeGen.cacheStateCopy = new uint8[codegenSize]; memset(coreinitCodeGen.cacheStateCopy, 0, codegenSize); coreinitCodeGen.rangeIsAllocated = true; } *rangeStart = coreinitCodeGen.rangeStart; *rangeSize = coreinitCodeGen.rangeSize; } void OSGetCodegenVirtAddrRangeInternal(uint32& rangeStart, uint32& rangeSize) { if (coreinitCodeGen.rangeIsAllocated == 0) { rangeStart = 0; rangeSize = 0; return; } rangeStart = coreinitCodeGen.rangeStart; rangeSize = coreinitCodeGen.rangeSize; } void ICInvalidateRange(uint32 startAddress, uint32 size) { uint32 ea = startAddress & ~0x1F; uint32 eaEnd = (startAddress + size + 0x1F)&~0x1F; while (ea <= eaEnd) { codeGenHandleICBI(ea); ea += 0x20; } } void coreinitExport_OSCodegenCopy(PPCInterpreter_t* hCPU) { if( coreinitCodeGen.rangeIsAllocated == false ) assert_dbg(); uint32 codeAddrDest = hCPU->gpr[3]; uint32 memAddrSrc = hCPU->gpr[4]; uint32 size = hCPU->gpr[5]; if( codeAddrDest < coreinitCodeGen.rangeStart || codeAddrDest >= (coreinitCodeGen.rangeStart+coreinitCodeGen.rangeSize) ) assert_dbg(); if( (codeAddrDest+size) < coreinitCodeGen.rangeStart || (codeAddrDest+size) > (coreinitCodeGen.rangeStart+coreinitCodeGen.rangeSize) ) assert_dbg(); memcpy(memory_getPointerFromVirtualOffset(codeAddrDest), memory_getPointerFromVirtualOffset(memAddrSrc), size); // invalidate recompiler range uint32 ea = codeAddrDest & ~0x1F; uint32 eaEnd = (codeAddrDest + size + 0x1F)&~0x1F; while (ea <= eaEnd) { codeGenHandleICBI(ea); ea += 0x20; } osLib_returnFromFunction(hCPU, 0); } void codeGenHandleICBI(uint32 ea) { cemu_assert_debug((ea & 0x1F) == 0); if (coreinitCodeGen.rangeIsAllocated == false) return; cemu_assert_debug((coreinitCodeGen.rangeStart & 0x1F) == 0); cemu_assert_debug((coreinitCodeGen.rangeSize & 0x1F) == 0); if (ea >= coreinitCodeGen.rangeStart && ea < (coreinitCodeGen.rangeStart + coreinitCodeGen.rangeSize)) { uint8* cacheCopy = coreinitCodeGen.cacheStateCopy + (ea - coreinitCodeGen.rangeStart); uint8* currentState = memory_getPointerFromVirtualOffset(ea); if (memcmp(currentState, cacheCopy, 32) != 0) { // instructions changed // flush cache PPCRecompiler_invalidateRange(ea, ea+0x20); // update icache copy memcpy(cacheCopy, currentState, 32); } } } bool _avoidCodeGenJIT = false; // currently we dont handle code invalidation well for direct write access // therefore if we detect attempts to write we will disable JITing the area bool codeGenShouldAvoid() { return _avoidCodeGenJIT; } bool OSSwitchSecCodeGenMode(bool isRXOnly) { if (!_avoidCodeGenJIT) { cemuLog_log(LogType::Force, "Disable JIT on dynamic code area"); } _avoidCodeGenJIT = true; // this function getting called is usually a sign that // does this have a return value? return true; } void InitializeCodeGen() { cafeExportRegister("coreinit", OSGetCodegenVirtAddrRange, LogType::Placeholder); cafeExportRegister("coreinit", ICInvalidateRange, LogType::Placeholder); osLib_addFunction("coreinit", "OSCodegenCopy", coreinitExport_OSCodegenCopy); cafeExportRegister("coreinit", OSSwitchSecCodeGenMode, LogType::Placeholder); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_CodeGen.h ================================================ #pragma once namespace coreinit { void OSGetCodegenVirtAddrRangeInternal(uint32& rangeStart, uint32& rangeSize); void codeGenHandleICBI(uint32 ea); bool codeGenShouldAvoid(); void InitializeCodeGen(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Coroutine.h" #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/MMU/MMU.h" #include "Cafe/OS/RPL/rpl.h" namespace coreinit { static_assert(sizeof(OSCoroutine) == 0x180); static uint32 s_PPCAddrOSSwitchCoroutineAfterOSLoadCoroutine = 0; void coreinitExport_OSInitCoroutine(PPCInterpreter_t* hCPU) { OSCoroutine* coroutine = (OSCoroutine*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); coroutine->lr = _swapEndianU32(hCPU->gpr[4]); coroutine->r1 = _swapEndianU32(hCPU->gpr[5]); osLib_returnFromFunction(hCPU, 0); } void coreinitCoroutine_OSSaveCoroutine(OSCoroutine* coroutine, PPCInterpreter_t* hCPU) { coroutine->lr = _swapEndianU32(hCPU->spr.LR); coroutine->cr = _swapEndianU32(ppc_getCR(hCPU)); coroutine->gqr1 = _swapEndianU32(hCPU->spr.UGQR[1]); coroutine->r1 = _swapEndianU32(hCPU->gpr[1]); coroutine->r2 = _swapEndianU32(hCPU->gpr[2]); coroutine->r13 = _swapEndianU32(hCPU->gpr[13]); for (sint32 i = 14; i < 32; i++) coroutine->gpr[i - 14] = _swapEndianU32(hCPU->gpr[i]); for (sint32 i = 14; i < 32; i++) { coroutine->fpr[i - 14] = _swapEndianU64(hCPU->fpr[i].fp0int); } for (sint32 i = 14; i < 32; i++) { coroutine->psr[i - 14] = _swapEndianU64(hCPU->fpr[i].fp1int); } } void coreinitCoroutine_OSLoadCoroutine(OSCoroutine* coroutine, PPCInterpreter_t* hCPU) { hCPU->spr.LR = _swapEndianU32(coroutine->lr); ppc_setCR(hCPU, _swapEndianU32(coroutine->cr)); hCPU->spr.UGQR[1] = _swapEndianU32(coroutine->gqr1); hCPU->gpr[1] = _swapEndianU32(coroutine->r1); hCPU->gpr[2] = _swapEndianU32(coroutine->r2); hCPU->gpr[13] = _swapEndianU32(coroutine->r13); for (sint32 i = 14; i < 32; i++) hCPU->gpr[i] = _swapEndianU32(coroutine->gpr[i - 14]); for (sint32 i = 14; i < 32; i++) { hCPU->fpr[i].fp0int = _swapEndianU64(coroutine->fpr[i - 14]); } for (sint32 i = 14; i < 32; i++) { hCPU->fpr[i].fp1int = _swapEndianU64(coroutine->psr[i - 14]); } } void coreinitExport_OSSwitchCoroutine(PPCInterpreter_t* hCPU) { // OSSwitchCoroutine is a wrapper for OSSaveCoroutine + OSLoadCoroutine but it has side effects that we need to care about: // r31 is saved and restored via the stack in OSSwitchCoroutine // r4 is stored in the r31 field of coroutineCurrent. Injustice: Gods Among Us reads the r31 field and expects it to match coroutineCurrent (0x027183D4 @ EU v16) OSCoroutine* coroutineCurrent = MEMPTR<OSCoroutine>(hCPU->gpr[3]); OSCoroutine* coroutineNext = MEMPTR<OSCoroutine>(hCPU->gpr[4]); hCPU->gpr[1] -= 0x10; memory_writeU32(hCPU->gpr[1]+0xC, hCPU->gpr[31]); memory_writeU32(hCPU->gpr[1]+0x14, hCPU->spr.LR); hCPU->spr.LR = s_PPCAddrOSSwitchCoroutineAfterOSLoadCoroutine; hCPU->gpr[31] = hCPU->gpr[4]; coreinitCoroutine_OSSaveCoroutine(coroutineCurrent, hCPU); hCPU->gpr[3] = hCPU->gpr[31]; hCPU->gpr[4] = 1; coreinitCoroutine_OSLoadCoroutine(coroutineNext, hCPU); hCPU->instructionPointer = hCPU->spr.LR; } void coreinitExport_OSSwitchCoroutineAfterOSLoadCoroutine(PPCInterpreter_t* hCPU) { // resuming after OSSaveCoroutine hCPU->gpr[31] = memory_readU32(hCPU->gpr[1]+0xC); hCPU->spr.LR = memory_readU32(hCPU->gpr[1]+0x14); hCPU->gpr[1] += 0x10; hCPU->instructionPointer = hCPU->spr.LR; } void coreinitExport_OSSwitchFiberEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(param0, 0); ppcDefineParamU32(param1, 1); ppcDefineParamU32(param2, 2); ppcDefineParamU32(param3, 3); ppcDefineParamMPTR(newInstructionPointer, 4); ppcDefineParamMPTR(newStackPointer, 5); MPTR prevStackpointer = hCPU->gpr[1]; hCPU->gpr[1] = newStackPointer; hCPU->gpr[3] = param0; hCPU->gpr[4] = param1; hCPU->gpr[5] = param2; hCPU->gpr[6] = param3; PPCCore_executeCallbackInternal(newInstructionPointer); uint32 returnValue = hCPU->gpr[3]; hCPU->gpr[1] = prevStackpointer; osLib_returnFromFunction(hCPU, returnValue); } void InitializeCoroutine() { osLib_addFunction("coreinit", "OSInitCoroutine", coreinitExport_OSInitCoroutine); osLib_addFunction("coreinit", "OSSwitchCoroutine", coreinitExport_OSSwitchCoroutine); osLib_addFunction("coreinit", "OSSwitchFiberEx", coreinitExport_OSSwitchFiberEx); s_PPCAddrOSSwitchCoroutineAfterOSLoadCoroutine = RPLLoader_MakePPCCallable(coreinitExport_OSSwitchCoroutineAfterOSLoadCoroutine); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Coroutine.h ================================================ #pragma once namespace coreinit { struct OSCoroutine { /* +0x00 */ uint32 lr; /* +0x04 */ uint32 cr; /* +0x08 */ uint32 gqr1; /* +0x0C */ uint32 r1; // stack pointer /* +0x10 */ uint32 r2; /* +0x14 */ uint32 r13; /* +0x18 */ uint32 gpr[18]; uint64 fpr[18]; uint64 psr[18]; }; static_assert(sizeof(OSCoroutine) == 0x180); void InitializeCoroutine(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" namespace coreinit { MPTR _osDynLoadFuncAlloc = MPTR_NULL; MPTR _osDynLoadFuncFree = MPTR_NULL; MPTR _osDynLoadTLSFuncAlloc = MPTR_NULL; MPTR _osDynLoadTLSFuncFree = MPTR_NULL; uint32 OSDynLoad_SetAllocator(MPTR allocFunc, MPTR freeFunc) { _osDynLoadFuncAlloc = allocFunc; _osDynLoadFuncFree = freeFunc; return 0; } void OSDynLoad_SetTLSAllocator(MPTR allocFunc, MPTR freeFunc) { _osDynLoadTLSFuncAlloc = allocFunc; _osDynLoadTLSFuncFree = freeFunc; } uint32 OSDynLoad_GetAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree) { *funcAlloc = _osDynLoadFuncAlloc; *funcFree = _osDynLoadFuncFree; return 0; } void OSDynLoad_GetTLSAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree) { *funcAlloc = _osDynLoadTLSFuncAlloc; *funcFree = _osDynLoadTLSFuncFree; } void* OSDynLoad_AllocatorAlloc(sint32 size, sint32 alignment) { if (_osDynLoadFuncAlloc == MPTR_NULL) return MPTR_NULL; StackAllocator<MEMPTR<void>> _ptrStorage; int r = PPCCoreCallback(_osDynLoadFuncAlloc, size, alignment, _ptrStorage.GetMPTR()); if (r != 0) { cemu_assert_debug(false); return MPTR_NULL; } return _ptrStorage->GetPtr(); } void OSDynLoad_AllocatorFree(void* mem) { if (_osDynLoadFuncFree == MPTR_NULL) return; MEMPTR<void> _mem = mem; PPCCoreCallback(_osDynLoadFuncFree, _mem); } uint32 OSDynLoad_Acquire(const char* libName, uint32be* moduleHandleOut) { // truncate path sint32 fileNameStartIndex = 0; sint32 tempLen = (sint32)strlen(libName); for (sint32 i = tempLen - 1; i >= 0; i--) { if (libName[i] == '/') { fileNameStartIndex = i + 1; break; } } // truncate file extension char tempLibName[512]; strcpy(tempLibName, libName + fileNameStartIndex); tempLen = (sint32)strlen(tempLibName); for (sint32 i = tempLen - 1; i >= 0; i--) { if (tempLibName[i] == '.') { tempLibName[i] = '\0'; break; } } // search for loaded modules with matching name uint32 rplHandle = RPLLoader_GetHandleByModuleName(libName); if (rplHandle == RPL_INVALID_HANDLE && !RPLLoader_HasDependency(libName)) { RPLLoader_AddDependency(libName); RPLLoader_UpdateDependencies(); RPLLoader_Link(); RPLLoader_CallEntrypoints(); rplHandle = RPLLoader_GetHandleByModuleName(libName); } if (rplHandle == RPL_INVALID_HANDLE) *moduleHandleOut = 0; else *moduleHandleOut = rplHandle; if (rplHandle == RPL_INVALID_HANDLE) { cemuLog_logDebug(LogType::Force, "OSDynLoad_Acquire() failed to load module '{}'", libName); return 0xFFFCFFE9; // module not found } return 0; } void OSDynLoad_Release(uint32 moduleHandle) { if (moduleHandle == RPL_INVALID_HANDLE) return; RPLLoader_RemoveDependency(moduleHandle); RPLLoader_UpdateDependencies(); } uint32 OSDynLoad_FindExport(uint32 moduleHandle, uint32 isData, const char* exportName, betype<MPTR>* addrOut) { if (moduleHandle == 0xFFFFFFFF) { // main module // Assassins Creed 4 has this handle hardcoded moduleHandle = RPLLoader_GetMainModuleHandle(); } MPTR exportResult = RPLLoader_FindModuleOrHLEExport(moduleHandle, isData, exportName); *addrOut = exportResult; if (exportResult == MPTR_NULL) return 0xFFFFFFFF; return 0; } void InitializeDynLoad() { cafeExportRegister("coreinit", OSDynLoad_SetAllocator, LogType::Placeholder); cafeExportRegister("coreinit", OSDynLoad_SetTLSAllocator, LogType::Placeholder); cafeExportRegister("coreinit", OSDynLoad_GetAllocator, LogType::Placeholder); cafeExportRegister("coreinit", OSDynLoad_GetTLSAllocator, LogType::Placeholder); cafeExportRegister("coreinit", OSDynLoad_Acquire, LogType::Placeholder); cafeExportRegister("coreinit", OSDynLoad_Release, LogType::Placeholder); cafeExportRegister("coreinit", OSDynLoad_FindExport, LogType::Placeholder); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h ================================================ #pragma once namespace coreinit { uint32 OSDynLoad_SetAllocator(MPTR allocFunc, MPTR freeFunc); void OSDynLoad_SetTLSAllocator(MPTR allocFunc, MPTR freeFunc); uint32 OSDynLoad_GetAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree); void OSDynLoad_GetTLSAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree); void* OSDynLoad_AllocatorAlloc(sint32 size, sint32 alignment); void OSDynLoad_AllocatorFree(void* mem); uint32 OSDynLoad_Acquire(const char* libName, uint32be* moduleHandleOut); void OSDynLoad_Release(uint32 moduleHandle); uint32 OSDynLoad_FindExport(uint32 moduleHandle, uint32 isData, const char* exportName, betype<MPTR>* addrOut); void InitializeDynLoad(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_FG.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include <memory> #define FG_BUCKET_AREA_FREE 0 // free area available game #define FG_BUCKET_AREA_AUDIO_TRANSITION 1 // transition audio buffer #define FG_BUCKET_AREA2 2 // frame storage? TV? #define FG_BUCKET_AREA3 3 // frame storage? DRC? #define FG_BUCKET_AREA4 4 // frame storage? TV? #define FG_BUCKET_AREA5 5 // frame storage? DRC? #define FG_BUCKET_AREA_SAVE 6 #define FG_BUCKET_AREA_COPY 7 // for OS copy data (clipboard and title switch parameters) #define FG_BUCKET_AREA_FREE_SIZE 0x2800000 #define FG_BUCKET_AREA_SAVE_SIZE 0x1000 #define FG_BUCKET_AREA_COPY_SIZE 0x400000 #define FG_BUCKET_AREA_COUNT 8 namespace coreinit { MEMPTR<void> fgAddr = nullptr; // NULL if not in foreground MEMPTR<uint8> fgSaveAreaAddr = nullptr; struct { uint32 id; uint32 startOffset; uint32 size; }fgAreaEntries[FG_BUCKET_AREA_COUNT] = { { 0, 0, 0x2800000 }, { 7, 0x2800000, 0x400000 }, { 1, 0x2C00000, 0x900000 }, { 2, 0x3500000, 0x3C0000 }, { 3, 0x38C0000, 0x1C0000 }, { 4, 0x3A80000, 0x3C0000 }, { 5, 0x3E40000, 0x1BF000 }, { 6, 0x3FFF000, 0x1000 } }; MEMPTR<uint8> GetFGMemByArea(uint32 areaId) { if (fgAddr == nullptr) return nullptr; for (sint32 i = 0; i < FG_BUCKET_AREA_COUNT; i++) { if (fgAreaEntries[i].id == areaId) return MEMPTR<uint8>(fgAddr.GetPtr<uint8>() + fgAreaEntries[i].startOffset); } return nullptr; } bool OSGetForegroundBucket(MEMPTR<void>* offset, uint32be* size) { // return full size of foreground bucket area if (offset) *offset = { (MPTR)MEMORY_FGBUCKET_AREA_ADDR }; if (size) *size = MEMORY_FGBUCKET_AREA_SIZE; // return true if in foreground return true; } bool OSGetForegroundBucketFreeArea(MEMPTR<void>* offset, uint32be* size) { uint8* freeAreaAddr = GetFGMemByArea(FG_BUCKET_AREA_FREE).GetPtr(); *offset = freeAreaAddr; *size = FG_BUCKET_AREA_FREE_SIZE; // return true if in foreground return (fgAddr != nullptr); } void coreinitExport_OSGetForegroundBucket(PPCInterpreter_t* hCPU) { //debug_printf("OSGetForegroundBucket(0x%x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]); // returns the whole FG bucket area (if the current process is in the foreground) ppcDefineParamMPTR(areaOutput, 0); ppcDefineParamMPTR(areaSize, 1); bool r = OSGetForegroundBucket((MEMPTR<void>*)memory_getPointerFromVirtualOffsetAllowNull(areaOutput), (uint32be*)memory_getPointerFromVirtualOffsetAllowNull(areaSize)); osLib_returnFromFunction(hCPU, r ? 1 : 0); } void InitForegroundBucket() { uint32be fgSize; OSGetForegroundBucket(&fgAddr, &fgSize); uint8* freeAreaPtr = GetFGMemByArea(FG_BUCKET_AREA_FREE).GetPtr(); memset(freeAreaPtr, 0, FG_BUCKET_AREA_FREE_SIZE); uint8* saveAreaPtr = GetFGMemByArea(FG_BUCKET_AREA_SAVE).GetPtr(); fgSaveAreaAddr = saveAreaPtr; if (*(uint32be*)saveAreaPtr != (uint32be)'Save') { // clear save area memset(saveAreaPtr, 0, FG_BUCKET_AREA_SAVE_SIZE); // clear copy area memset(GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(), 0, FG_BUCKET_AREA_COPY_SIZE); // init save area *(uint32be*)(saveAreaPtr + 0x00) = 'Save'; *(uint32be*)(saveAreaPtr + 0x08) |= 0x300; } } void __OSClearCopyData() { uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(); if (fgCopyArea) { *(uint32be*)(fgCopyArea + 0x00) = 0; } } uint32 __OSGetCopyDataSize() { uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(); if (fgCopyArea) { return *(uint32be*)(fgCopyArea + 0x00); } return 0; } uint8* __OSGetCopyDataPtr() { uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(); if (fgCopyArea) return fgCopyArea + 4; return nullptr; } bool __OSAppendCopyData(uint8* data, sint32 length) { uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(); if (fgCopyArea == nullptr) return false; uint32 currentOffset = *(uint32be*)(fgCopyArea + 0x00); if ((currentOffset + length) > FG_BUCKET_AREA_COPY_SIZE - 4) { return false; } memcpy(fgCopyArea + currentOffset + 4, data, length); *(uint32be*)(fgCopyArea + 0x00) += length; return true; } bool __OSResizeCopyData(sint32 length) { uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(); if (fgCopyArea == nullptr) return false; sint32 currentOffset = (sint32) * (uint32be*)(fgCopyArea + 0x00); if (length < currentOffset) { // can only make copy data smaller *(uint32be*)(fgCopyArea + 0x00) = length; return true; } return false; } bool OSCopyFromClipboard(void* data, uint32be* size) { uint32 cSize = *size; if (cSize == 0 && data == nullptr) return false; if (OSGetForegroundBucket(nullptr, nullptr) == false) return false; // todo *size = 0; return true; } void coreinitExport_OSCopyFromClipboard(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "OSCopyFromClipboard(0x{:x},0x{:x})", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamMEMPTR(buffer, void, 0); ppcDefineParamMEMPTR(size, uint32be, 1); bool r = OSCopyFromClipboard(buffer.GetPtr(), size.GetPtr()); osLib_returnFromFunction(hCPU, r ? 1 : 0); } void InitializeFG() { osLib_addFunction("coreinit", "OSGetForegroundBucket", coreinitExport_OSGetForegroundBucket); cafeExportRegister("coreinit", OSGetForegroundBucket, LogType::CoreinitMem); cafeExportRegister("coreinit", OSGetForegroundBucketFreeArea, LogType::CoreinitMem); osLib_addFunction("coreinit", "OSCopyFromClipboard", coreinitExport_OSCopyFromClipboard); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_FG.h ================================================ #pragma once namespace coreinit { void __OSClearCopyData(); uint32 __OSGetCopyDataSize(); uint8* __OSGetCopyDataPtr(); bool __OSAppendCopyData(uint8* data, sint32 length); bool __OSResizeCopyData(sint32 length); bool OSGetForegroundBucket(MEMPTR<void>* offset, uint32be* size); bool OSGetForegroundBucketFreeArea(MEMPTR<void>* offset, uint32be* size); void InitForegroundBucket(); void InitializeFG(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_FS.cpp ================================================ #include <OS/RPL/rpl.h> #include "config/ActiveSettings.h" #include "Cafe/OS/libs/coreinit/coreinit_SystemInfo.h" #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_FS.h" #include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" #include "util/helpers/Semaphore.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/IOSU/iosu_ipc_common.h" #include "coreinit_IPC.h" #include "Cafe/Filesystem/fsc.h" #include "coreinit_IPCBuf.h" #include "Cafe/CafeSystem.h" #include "Cafe/TitleList/TitleInfo.h" #define FS_CB_PLACEHOLDER_FINISHCMD (MPTR)(0xF122330E) // return false if src+'\0' does not fit into dst template<std::size_t Size> bool strcpy_whole(char (&dst)[Size], const char* src) { size_t inputLength = strlen(src); if ((inputLength + 1) > Size) { dst[0] = '\0'; return false; } strcpy(dst, src); return true; } bool strcpy_whole(char* dst, size_t dstLength, const char* src) { size_t inputLength = strlen(src); if ((inputLength + 1) > dstLength) { dst[0] = '\0'; return false; } strcpy(dst, src); return true; } namespace coreinit { SysAllocator<OSMutex> s_fsGlobalMutex; inline void FSLockMutex() { OSLockMutex(&s_fsGlobalMutex); } inline void FSUnlockMutex() { OSUnlockMutex(&s_fsGlobalMutex); } void _debugVerifyCommand(const char* stage, FSCmdBlockBody* fsCmdBlockBody); bool sFSInitialized = true; // this should be false but it seems like some games rely on FSInit being called before main()? Twilight Princess for example reads files before it calls FSInit bool sFSShutdown = false; enum class MOUNT_TYPE : uint32 { SD = 0, // 1 = usb? }; struct FS_MOUNT_SOURCE { uint32be sourceType; // ukn values char path[128]; // todo - determine correct length }; FS_RESULT FSGetMountSourceNext(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, MOUNT_TYPE mountSourceType, FS_MOUNT_SOURCE* mountSourceInfo, FS_ERROR_MASK errMask) { if (mountSourceType == MOUNT_TYPE::SD) { // This function is supposed to be called after an initial FSGetMountSource call => always returns FS_RESULT::END_ITERATION because we only have one SD Card // It *might* causes issues if this function is called for getting the first MountSource (instead of "FSGetMountSource") cemu_assert_suspicious(); return FS_RESULT::END_ITERATION; } else { cemu_assert_unimplemented(); } return FS_RESULT::END_ITERATION; } FS_RESULT FSGetMountSource(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, MOUNT_TYPE mountSourceType, FS_MOUNT_SOURCE* mountSourceInfo, FS_ERROR_MASK errMask) { // This implementation is simplified A LOT compared to what the Wii U is actually doing. On Cemu we expect to only have one mountable source (SD Card) anyway, // so we can just hard code it. Other mount types are not (yet) supported. if (mountSourceType == MOUNT_TYPE::SD) { // check for SD card permissions (from cos.xml) // One Piece relies on failing here, otherwise it will call FSGetMountSource in an infinite loop CosCapabilityBitsFS perms = static_cast<CosCapabilityBitsFS>(CafeSystem::GetForegroundTitleCosCapabilities(CosCapabilityGroup::FS)); if(!HAS_FLAG(perms, CosCapabilityBitsFS::SDCARD_MOUNT)) { cemuLog_logOnce(LogType::Force, "Title is trying to access SD card mount info without having SD card permissions. This may not be a bug"); return FS_RESULT::END_ITERATION; } mountSourceInfo->sourceType = 0; strcpy(mountSourceInfo->path, "/sd"); return FS_RESULT::SUCCESS; } else { cemu_assert_unimplemented(); } return FS_RESULT::END_ITERATION; } bool _sdCard01Mounted = false; bool _mlc01Mounted = false; void mountSDCard() { if (_sdCard01Mounted) return; std::error_code ec; const auto path = ActiveSettings::GetUserDataPath("sdcard/"); fs::create_directories(path, ec); FSCDeviceHostFS_Mount("/vol/external01", _pathToUtf8(path), FSC_PRIORITY_BASE); _sdCard01Mounted = true; } FS_RESULT FSMount(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FS_MOUNT_SOURCE* mountSourceInfo, char* mountPathOut, sint32 mountPathMaxLength, FS_ERROR_MASK errMask) { if (mountSourceInfo->sourceType != 0) { return FS_RESULT::ERR_PLACEHOLDER; } if (!strcmp(mountSourceInfo->path, "/sd")) { const char* sdMountPath = "/vol/external01"; // note: mount path should not end with a slash if (!strcpy_whole(mountPathOut, mountPathMaxLength, sdMountPath)) return FS_RESULT::ERR_PLACEHOLDER; // string does not fit mountSDCard(); } return FS_RESULT::SUCCESS; } FS_RESULT FSBindMount(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* mountPathSrc, char* mountPathOut, FS_ERROR_MASK errMask) { if (strcmp(mountPathSrc, "/dev/sdcard01") == 0) { if (_sdCard01Mounted) return FS_RESULT::ERR_PLACEHOLDER; std::error_code ec; const auto path = ActiveSettings::GetUserDataPath("sdcard/"); fs::create_directories(path, ec); if (!FSCDeviceHostFS_Mount(mountPathOut, _pathToUtf8(path), FSC_PRIORITY_BASE)) return FS_RESULT::ERR_PLACEHOLDER; _sdCard01Mounted = true; } else if (strcmp(mountPathSrc, "/dev/mlc01") == 0) { if (_mlc01Mounted) return FS_RESULT::ERR_PLACEHOLDER; if (!FSCDeviceHostFS_Mount(mountPathOut, _pathToUtf8(ActiveSettings::GetMlcPath()), FSC_PRIORITY_BASE)) return FS_RESULT::ERR_PLACEHOLDER; _mlc01Mounted = true; } else { return FS_RESULT::ERR_PLACEHOLDER; } return FS_RESULT::SUCCESS; } // return the aligned FSClientBody struct inside a FSClient struct FSClientBody_t* __FSGetClientBody(FSClient_t* fsClient) { // align pointer to 64 bytes if (fsClient == nullptr) return nullptr; FSClientBody_t* fsClientBody = (FSClientBody_t*)(((uintptr_t)fsClient + 0x3F) & ~0x3F); fsClientBody->selfClient = fsClient; return fsClientBody; } // return the aligned FSClientBody struct inside a FSClient struct FSCmdBlockBody* __FSGetCmdBlockBody(FSCmdBlock_t* fsCmdBlock) { // align pointer to 64 bytes if (fsCmdBlock == nullptr) return nullptr; FSCmdBlockBody* fsCmdBlockBody = (FSCmdBlockBody*)(((uintptr_t)fsCmdBlock + 0x3F) & ~0x3F); fsCmdBlockBody->selfCmdBlock = fsCmdBlock; return fsCmdBlockBody; } void __FSErrorAndBlock(std::string_view msg) { cemuLog_log(LogType::Force, "Critical error in FS: {}", msg); while (true) std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } /* FS client management */ FSClientBody_t* g_fsRegisteredClientBodies = nullptr; sint32 FSGetClientNum() { sint32 clientNum = 0; FSClientBody_t* fsBodyItr = g_fsRegisteredClientBodies; while (fsBodyItr) { clientNum++; fsBodyItr = fsBodyItr->fsClientBodyNext.GetPtr(); } return clientNum; } bool __FSIsClientRegistered(FSClientBody_t* fsClientBody) { FSLockMutex(); FSClientBody_t* fsClientBodyFirst = g_fsRegisteredClientBodies; FSClientBody_t* fsClientBodyItr = fsClientBodyFirst; if (fsClientBodyItr == NULL) { FSUnlockMutex(); return false; } while (true) { if (fsClientBody == fsClientBodyItr) { FSUnlockMutex(); return true; } // next fsClientBodyItr = fsClientBodyItr->fsClientBodyNext.GetPtr(); if (fsClientBodyItr == MPTR_NULL) break; if (fsClientBodyItr == fsClientBodyFirst) break; } FSUnlockMutex(); return false; } void __FSInitCmdQueue(FSCmdQueue* fsCmdQueueBE, MPTR dequeueHandlerFuncMPTR, uint32 numMaxCommandsInFlight) { cemu_assert(numMaxCommandsInFlight > 0); fsCmdQueueBE->dequeueHandlerFuncMPTR = _swapEndianU32(dequeueHandlerFuncMPTR); fsCmdQueueBE->numCommandsInFlight = 0; fsCmdQueueBE->numMaxCommandsInFlight = numMaxCommandsInFlight; coreinit::OSFastMutex_Init(&fsCmdQueueBE->fastMutex, nullptr); fsCmdQueueBE->first = nullptr; fsCmdQueueBE->last = nullptr; } void FSInit() { sFSInitialized = true; } void FSShutdown() { cemuLog_log(LogType::Force, "FSShutdown called"); } FS_RESULT FSAddClientEx(FSClient_t* fsClient, uint32 uknR4, uint32 errHandling) { if (!sFSInitialized || sFSShutdown || !fsClient) { __FSErrorAndBlock("Called FSAddClient(Ex) with invalid parameters or while FS is not initialized"); return FS_RESULT::FATAL_ERROR; } FSLockMutex(); if (uknR4 != 0) { uint32 uknValue = memory_readU32(uknR4 + 0x00); if (uknValue == 0) { FSUnlockMutex(); __FSErrorAndBlock("FSAddClientEx - unknown error"); return FS_RESULT::FATAL_ERROR; } } if (__FSIsClientRegistered(__FSGetClientBody(fsClient))) { FSUnlockMutex(); __FSErrorAndBlock("Called FSAddClient(Ex) on client that was already added"); return FS_RESULT::FATAL_ERROR; } FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); __FSInitCmdQueue(&fsClientBody->fsCmdQueue, MPTR_NULL, 1); IOSDevHandle devHandle = IOS_Open("/dev/fsa", 0); if (IOS_ResultIsError((IOS_ERROR)devHandle)) { cemuLog_log(LogType::Force, "FSAddClientEx(): Exhausted device handles"); cemu_assert(IOS_ResultIsError((IOS_ERROR)devHandle)); FSUnlockMutex(); return FS_RESULT::FATAL_ERROR; } fsClientBody->iosuFSAHandle = devHandle; // add to list of registered clients if (g_fsRegisteredClientBodies != MPTR_NULL) { fsClientBody->fsClientBodyNext = g_fsRegisteredClientBodies; g_fsRegisteredClientBodies = fsClientBody; } else { g_fsRegisteredClientBodies = fsClientBody; fsClientBody->fsClientBodyNext = nullptr; } FSUnlockMutex(); return FS_RESULT::SUCCESS; } FS_RESULT FSAddClient(FSClient_t* fsClient, uint32 errHandling) { return FSAddClientEx(fsClient, 0, errHandling); } FS_RESULT FSDelClient(FSClient_t* fsClient, uint32 errHandling) { if (sFSInitialized == false || sFSShutdown == true || !fsClient) { __FSErrorAndBlock("Called FSDelClient with invalid parameters or while FS is not initialized"); return FS_RESULT::FATAL_ERROR; } FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); FSLockMutex(); IOS_Close(fsClientBody->iosuFSAHandle); // todo: wait till in-flight transactions are done // remove from list if (g_fsRegisteredClientBodies == fsClientBody) { // first entry in list g_fsRegisteredClientBodies = fsClientBody->fsClientBodyNext.GetPtr(); } else { // scan for entry in list FSClientBody_t* fsBodyItr = g_fsRegisteredClientBodies; FSClientBody_t* fsBodyPrev = g_fsRegisteredClientBodies; bool entryFound = false; while (fsBodyItr) { if (fsBodyItr == fsClientBody) { fsBodyPrev->fsClientBodyNext = fsClientBody->fsClientBodyNext; entryFound = true; break; } // next fsBodyPrev = fsBodyItr; fsBodyItr = fsBodyItr->fsClientBodyNext.GetPtr(); } if (entryFound == false) { #ifdef CEMU_DEBUG_ASSERT assert_dbg(); #endif } } FSUnlockMutex(); return FS_RESULT::SUCCESS; } /* FS command queue */ Semaphore g_semaphoreQueuedCmds; void __FSQueueCmdByPriority(FSCmdQueue* fsCmdQueueBE, FSCmdBlockBody* fsCmdBlockBody, bool stopAtEqualPriority) { if (!fsCmdQueueBE->first) { // queue is currently empty cemu_assert(!fsCmdQueueBE->last); fsCmdQueueBE->first = fsCmdBlockBody; fsCmdQueueBE->last = fsCmdBlockBody; fsCmdBlockBody->next = nullptr; fsCmdBlockBody->previous = nullptr; return; } // iterate from last to first element as long as iterated priority is lower FSCmdBlockBody* fsCmdBlockBodyItrPrev = nullptr; FSCmdBlockBody* fsCmdBlockBodyItr = fsCmdQueueBE->last; while (true) { if (!fsCmdBlockBodyItr) { // insert at the head of the list fsCmdQueueBE->first = fsCmdBlockBody; fsCmdBlockBody->next = fsCmdBlockBodyItrPrev; fsCmdBlockBody->previous = nullptr; fsCmdBlockBodyItrPrev->previous = fsCmdBlockBody; return; } // compare priority if ((stopAtEqualPriority && fsCmdBlockBodyItr->priority >= fsCmdBlockBody->priority) || (stopAtEqualPriority == false && fsCmdBlockBodyItr->priority > fsCmdBlockBody->priority)) { // insert cmd here if (fsCmdBlockBodyItrPrev) { fsCmdBlockBody->next = fsCmdBlockBodyItrPrev; } else { fsCmdBlockBody->next = nullptr; fsCmdQueueBE->last = fsCmdBlockBody; } fsCmdBlockBody->previous = fsCmdBlockBodyItr; if (fsCmdBlockBodyItrPrev) fsCmdBlockBodyItrPrev->previous = fsCmdBlockBody; fsCmdBlockBodyItr->next = fsCmdBlockBody; return; } // next fsCmdBlockBodyItrPrev = fsCmdBlockBodyItr; fsCmdBlockBodyItr = fsCmdBlockBodyItr->previous; } } FSCmdBlockBody* __FSTakeCommandFromQueue(FSCmdQueue* cmdQueue) { if (!cmdQueue->first) return nullptr; // dequeue cmd FSCmdBlockBody* dequeuedCmd = cmdQueue->first; if (cmdQueue->first == cmdQueue->last) cmdQueue->last = nullptr; cmdQueue->first = dequeuedCmd->next; dequeuedCmd->next = nullptr; if (dequeuedCmd->next) { FSCmdBlockBody* fsCmdBodyNext = dequeuedCmd->next; fsCmdBodyNext->previous = nullptr; } return dequeuedCmd; } void __FSAIoctlResponseCallback(PPCInterpreter_t* hCPU); void __FSAIPCSubmitCommandAsync(iosu::fsa::FSAShimBuffer* shimBuffer, const MEMPTR<void>& callback, void* context) { if (shimBuffer->ipcReqType == 0) { IOS_ERROR r = IOS_IoctlAsync(shimBuffer->fsaDevHandle, shimBuffer->operationType, &shimBuffer->request, sizeof(shimBuffer->request), &shimBuffer->response, sizeof(shimBuffer->response), callback, context); cemu_assert(!IOS_ResultIsError(r)); } else if (shimBuffer->ipcReqType == 1) { IOS_ERROR r = IOS_IoctlvAsync(shimBuffer->fsaDevHandle, shimBuffer->operationType, shimBuffer->ioctlvVecIn, shimBuffer->ioctlvVecOut, shimBuffer->ioctlvVec, callback, context); cemu_assert(!IOS_ResultIsError(r)); } else { cemu_assert_error(); } } FSA_RESULT __FSADecodeIOSErrorToFSA(IOS_ERROR result) { return (FSA_RESULT)result; } FSA_RESULT __FSAIPCSubmitCommand(iosu::fsa::FSAShimBuffer* shimBuffer) { if (shimBuffer->ipcReqType == 0) { IOS_ERROR result = IOS_Ioctl(shimBuffer->fsaDevHandle, shimBuffer->operationType, &shimBuffer->request, sizeof(shimBuffer->request), &shimBuffer->response, sizeof(shimBuffer->response)); return __FSADecodeIOSErrorToFSA(result); } else if (shimBuffer->ipcReqType == 1) { IOS_ERROR result = IOS_Ioctlv(shimBuffer->fsaDevHandle, shimBuffer->operationType, shimBuffer->ioctlvVecIn, shimBuffer->ioctlvVecOut, shimBuffer->ioctlvVec); return __FSADecodeIOSErrorToFSA(result); } return FSA_RESULT::FATAL_ERROR; } void __FSUpdateQueue(FSCmdQueue* cmdQueue) { FSLockMutex(); if (cmdQueue->numCommandsInFlight < cmdQueue->numMaxCommandsInFlight) { FSCmdBlockBody* dequeuedCommand = __FSTakeCommandFromQueue(cmdQueue); if (dequeuedCommand) { cmdQueue->numCommandsInFlight += 1; if (cmdQueue->numCommandsInFlight >= cmdQueue->numMaxCommandsInFlight) cmdQueue->queueFlags = cmdQueue->queueFlags | FSCmdQueue::QUEUE_FLAG::IS_FULL; cemu_assert_debug(cmdQueue->dequeueHandlerFuncMPTR == 0); // not supported. We HLE call the handler here __FSAIPCSubmitCommandAsync(&dequeuedCommand->fsaShimBuffer, MEMPTR<void>(PPCInterpreter_makeCallableExportDepr(__FSAIoctlResponseCallback)), &dequeuedCommand->fsaShimBuffer); } } FSUnlockMutex(); } void __FSQueueDefaultFinishFunc(FSCmdBlockBody* fsCmdBlockBody, FS_RESULT result) { switch ((FSA_CMD_OPERATION_TYPE)fsCmdBlockBody->fsaShimBuffer.operationType.value()) { case FSA_CMD_OPERATION_TYPE::OPENFILE: { *fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = fsCmdBlockBody->fsaShimBuffer.response.cmdOpenFile.fileHandleOutput; break; } case FSA_CMD_OPERATION_TYPE::GETCWD: { auto transferSize = fsCmdBlockBody->returnValues.cmdGetCwd.transferSize; if (transferSize < 0 && transferSize > sizeof(fsCmdBlockBody->fsaShimBuffer.response.cmdGetCWD.path)) { cemu_assert_error(); } memcpy(fsCmdBlockBody->returnValues.cmdGetCwd.pathPtr, fsCmdBlockBody->fsaShimBuffer.response.cmdGetCWD.path, transferSize); break; } case FSA_CMD_OPERATION_TYPE::OPENDIR: { *fsCmdBlockBody->returnValues.cmdOpenDir.handlePtr = fsCmdBlockBody->fsaShimBuffer.response.cmdOpenDir.dirHandleOutput; break; } case FSA_CMD_OPERATION_TYPE::READDIR: { *fsCmdBlockBody->returnValues.cmdReadDir.dirEntryPtr = fsCmdBlockBody->fsaShimBuffer.response.cmdReadDir.dirEntry; break; } case FSA_CMD_OPERATION_TYPE::GETPOS: { *fsCmdBlockBody->returnValues.cmdGetPosFile.filePosPtr = fsCmdBlockBody->fsaShimBuffer.response.cmdGetPosFile.filePos; break; } case FSA_CMD_OPERATION_TYPE::GETSTATFILE: { *((FSStat_t*)fsCmdBlockBody->returnValues.cmdStatFile.resultPtr.GetPtr()) = fsCmdBlockBody->fsaShimBuffer.response.cmdStatFile.statOut; break; } case FSA_CMD_OPERATION_TYPE::QUERYINFO: { if (fsCmdBlockBody->fsaShimBuffer.request.cmdQueryInfo.queryType == FSA_QUERY_TYPE_FREESPACE) { *((uint64be*)fsCmdBlockBody->returnValues.cmdQueryInfo.queryResultPtr.GetPtr()) = fsCmdBlockBody->fsaShimBuffer.response.cmdQueryInfo.queryFreeSpace.freespace; } else if (fsCmdBlockBody->fsaShimBuffer.request.cmdQueryInfo.queryType == FSA_QUERY_TYPE_STAT) { *((FSStat_t*)fsCmdBlockBody->returnValues.cmdQueryInfo.queryResultPtr.GetPtr()) = fsCmdBlockBody->fsaShimBuffer.response.cmdQueryInfo.queryStat.stat; } else { cemu_assert_unimplemented(); } break; } case FSA_CMD_OPERATION_TYPE::CHANGEDIR: case FSA_CMD_OPERATION_TYPE::MAKEDIR: case FSA_CMD_OPERATION_TYPE::REMOVE: case FSA_CMD_OPERATION_TYPE::RENAME: case FSA_CMD_OPERATION_TYPE::CLOSEDIR: case FSA_CMD_OPERATION_TYPE::READ: case FSA_CMD_OPERATION_TYPE::WRITE: case FSA_CMD_OPERATION_TYPE::SETPOS: case FSA_CMD_OPERATION_TYPE::ISEOF: case FSA_CMD_OPERATION_TYPE::CLOSEFILE: case FSA_CMD_OPERATION_TYPE::APPENDFILE: case FSA_CMD_OPERATION_TYPE::TRUNCATEFILE: case FSA_CMD_OPERATION_TYPE::FLUSHQUOTA: { break; } default: { cemu_assert_unimplemented(); } } } void export___FSQueueDefaultFinishFunc(PPCInterpreter_t* hCPU) { ppcDefineParamPtr(cmd, FSCmdBlockBody, 0); FS_RESULT result = (FS_RESULT)PPCInterpreter_getCallParamU32(hCPU, 1); __FSQueueDefaultFinishFunc(cmd, static_cast<FS_RESULT>(result)); osLib_returnFromFunction(hCPU, 0); } void __FSQueueCmd(FSCmdQueue* cmdQueue, FSCmdBlockBody* fsCmdBlockBody, MPTR finishCmdFunc) { fsCmdBlockBody->cmdFinishFuncMPTR = finishCmdFunc; FSLockMutex(); fsCmdBlockBody->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A22); __FSQueueCmdByPriority(cmdQueue, fsCmdBlockBody, true); FSUnlockMutex(); __FSUpdateQueue(cmdQueue); } FSA_RESULT _FSIosErrorToFSAStatus(IOS_ERROR iosError) { return (FSA_RESULT)iosError; } FS_RESULT _FSAStatusToFSStatus(FSA_RESULT err) { if ((int)err > 0) { return (FS_RESULT)err; } switch (err) { case FSA_RESULT::OK: { return FS_RESULT::SUCCESS; } case FSA_RESULT::END_OF_DIRECTORY: case FSA_RESULT::END_OF_FILE: { return FS_RESULT::END_ITERATION; } case FSA_RESULT::ALREADY_EXISTS: { return FS_RESULT::ALREADY_EXISTS; } case FSA_RESULT::NOT_FOUND: { return FS_RESULT::NOT_FOUND; } case FSA_RESULT::PERMISSION_ERROR: { return FS_RESULT::PERMISSION_ERROR; } case FSA_RESULT::NOT_FILE: { return FS_RESULT::NOT_FILE; } case FSA_RESULT::NOT_DIR: { return FS_RESULT::NOT_DIR; } case FSA_RESULT::MAX_FILES: case FSA_RESULT::MAX_DIRS: { return FS_RESULT::MAX_HANDLES; } case FSA_RESULT::INVALID_CLIENT_HANDLE: case FSA_RESULT::INVALID_FILE_HANDLE: case FSA_RESULT::INVALID_DIR_HANDLE: case FSA_RESULT::INVALID_PARAM: case FSA_RESULT::INVALID_PATH: case FSA_RESULT::INVALID_BUFFER: case FSA_RESULT::INVALID_ALIGNMENT: case FSA_RESULT::NOT_INIT: case FSA_RESULT::MAX_CLIENTS: case FSA_RESULT::OUT_OF_RESOURCES: case FSA_RESULT::FATAL_ERROR: { return FS_RESULT::FATAL_ERROR; } } cemu_assert_unimplemented(); return FS_RESULT::FATAL_ERROR; } void __FSCmdSubmitResult(FSCmdBlockBody* fsCmdBlockBody, FS_RESULT result) { _debugVerifyCommand("FSCmdSubmitResult", fsCmdBlockBody); FSClientBody_t* fsClientBody = fsCmdBlockBody->fsClientBody.GetPtr(); OSFastMutex_Lock(&fsClientBody->fsCmdQueue.fastMutex); fsCmdBlockBody->cancelState &= ~(1 << 0); // clear cancel bit if (fsClientBody->currentCmdBlockBody.GetPtr() == fsCmdBlockBody) fsClientBody->currentCmdBlockBody = nullptr; fsCmdBlockBody->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A24); OSFastMutex_Unlock(&fsClientBody->fsCmdQueue.fastMutex); // send result via msg queue or callback cemu_assert_debug(!fsCmdBlockBody->asyncResult.fsAsyncParamsNew.ioMsgQueue != !fsCmdBlockBody->asyncResult.fsAsyncParamsNew.userCallback); // either must be set fsCmdBlockBody->ukn09EA = 0; fsCmdBlockBody->ukn9F4_lastErrorRelated = 0; if (fsCmdBlockBody->asyncResult.fsAsyncParamsNew.ioMsgQueue) { // msg queue _debugVerifyCommand("SubmitResultQueue", fsCmdBlockBody); coreinit::OSMessageQueue* ioMsgQueue = fsCmdBlockBody->asyncResult.fsAsyncParamsNew.ioMsgQueue.GetPtr(); fsCmdBlockBody->asyncResult.fsCmdBlock = fsCmdBlockBody->selfCmdBlock; fsCmdBlockBody->asyncResult.fsStatusNew = (uint32)result; while (OSSendMessage(ioMsgQueue, &fsCmdBlockBody->asyncResult.msgUnion.osMsg, 0) == 0) { cemuLog_log(LogType::Force, "FS driver: Failed to add message to result queue. Retrying..."); if (PPCInterpreter_getCurrentInstance()) PPCCore_switchToScheduler(); else std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } else { // callback _debugVerifyCommand("SubmitResultCallback", fsCmdBlockBody); FSClient_t* fsClient = fsCmdBlockBody->asyncResult.fsClient.GetPtr(); FSCmdBlock_t* fsCmdBlock = fsCmdBlockBody->asyncResult.fsCmdBlock.GetPtr(); PPCCoreCallback(fsCmdBlockBody->asyncResult.fsAsyncParamsNew.userCallback, fsClient, fsCmdBlock, (sint32)result, fsCmdBlockBody->asyncResult.fsAsyncParamsNew.userContext); } } void __FSAIoctlResponseCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(iosResult, 0); ppcDefineParamPtr(cmd, FSCmdBlockBody, 1); FSA_RESULT fsaStatus = _FSIosErrorToFSAStatus((IOS_ERROR)iosResult); cmd->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A26); cmd->lastFSAStatus = fsaStatus; // translate error code to FSStatus FS_RESULT fsStatus = _FSAStatusToFSStatus(fsaStatus); // On actual hardware this delegates the processing to the AppIO threads, but for now we just run it directly from the IPC thread FSClientBody_t* client = cmd->fsClientBody; FSCmdQueue& cmdQueue = client->fsCmdQueue; FSLockMutex(); cmdQueue.numCommandsInFlight -= 1; cmdQueue.queueFlags = cmdQueue.queueFlags & ~FSCmdQueue::QUEUE_FLAG::IS_FULL; FSUnlockMutex(); if (cmd->cmdFinishFuncMPTR) { if (cmd->cmdFinishFuncMPTR != FS_CB_PLACEHOLDER_FINISHCMD) PPCCoreCallback(MEMPTR<void>(cmd->cmdFinishFuncMPTR), cmd, fsStatus); } __FSCmdSubmitResult(cmd, fsStatus); // dont read from cmd after this point, since the game could already have modified it __FSUpdateQueue(&client->fsCmdQueue); osLib_returnFromFunction(hCPU, 0); } /* FS commands */ void FSInitCmdBlock(FSCmdBlock_t* fsCmdBlock) { memset(fsCmdBlock, 0x00, sizeof(FSCmdBlock_t)); FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); fsCmdBlockBody->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A21); fsCmdBlockBody->priority = 0x10; } void __FSAsyncToSyncInit(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSAsyncParams* asyncParams) { if (fsClient == nullptr || fsCmdBlock == nullptr || asyncParams == nullptr) assert_dbg(); FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); coreinit::OSInitMessageQueue(&fsCmdBlockBody->syncTaskMsgQueue, fsCmdBlockBody->_syncTaskMsg, 1); asyncParams->userCallback = nullptr; asyncParams->userContext = nullptr; asyncParams->ioMsgQueue = &fsCmdBlockBody->syncTaskMsgQueue; } void __FSPrepareCmdAsyncResult(FSClientBody_t* fsClientBody, FSCmdBlockBody* fsCmdBlockBody, FSAsyncResult* fsCmdBlockAsyncResult, FSAsyncParams* fsAsyncParams) { memcpy(&fsCmdBlockAsyncResult->fsAsyncParamsNew, fsAsyncParams, sizeof(FSAsyncParams)); fsCmdBlockAsyncResult->fsClient = fsClientBody->selfClient; fsCmdBlockAsyncResult->fsCmdBlock = fsCmdBlockBody->selfCmdBlock; fsCmdBlockAsyncResult->msgUnion.fsMsg.fsAsyncResult = fsCmdBlockAsyncResult; fsCmdBlockAsyncResult->msgUnion.fsMsg.commandType = _swapEndianU32(8); } sint32 __FSPrepareCmd(FSClientBody_t* fsClientBody, FSCmdBlockBody* fsCmdBlockBody, uint32 errHandling, FSAsyncParams* fsAsyncParams) { if (sFSInitialized == false || sFSShutdown == true) return -0x400; if (!fsClientBody || !fsCmdBlockBody) { cemu_assert(false); return -0x400; } if (_swapEndianU32(fsCmdBlockBody->statusCode) != FSA_CMD_STATUS_CODE_D900A21 && _swapEndianU32(fsCmdBlockBody->statusCode) != FSA_CMD_STATUS_CODE_D900A24) { cemu_assert(false); return -0x400; } if ((fsAsyncParams->userCallback == nullptr && fsAsyncParams->ioMsgQueue == nullptr) || (fsAsyncParams->userCallback != nullptr && fsAsyncParams->ioMsgQueue != nullptr)) { // userCallback and ioMsgQueue both set or none set cemu_assert(false); return -0x400; } fsCmdBlockBody->fsClientBody = fsClientBody; fsCmdBlockBody->errHandling = _swapEndianU32(errHandling); fsCmdBlockBody->uknStatusGuessed09E9 = 0; fsCmdBlockBody->cancelState &= ~(1 << 0); // clear cancel bit fsCmdBlockBody->fsaShimBuffer.fsaDevHandle = fsClientBody->iosuFSAHandle; __FSPrepareCmdAsyncResult(fsClientBody, fsCmdBlockBody, &fsCmdBlockBody->asyncResult, fsAsyncParams); return 0; } #define _FSCmdIntro() \ FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); \ FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); \ sint32 fsError = __FSPrepareCmd(fsClientBody, fsCmdBlockBody, errorMask, fsAsyncParams); \ if (fsError != 0) \ return fsError; void _debugVerifyCommand(const char* stage, FSCmdBlockBody* fsCmdBlockBody) { if (fsCmdBlockBody->asyncResult.msgUnion.fsMsg.commandType != _swapEndianU32(8)) { cemuLog_log(LogType::Force, "Corrupted FS command detected in stage {}", stage); cemuLog_log(LogType::Force, "Printing CMD block: "); for (uint32 i = 0; i < (sizeof(FSCmdBlockBody) + 31) / 32; i++) { uint8* p = ((uint8*)fsCmdBlockBody) + i * 32; cemuLog_log(LogType::Force, "{:04x}: {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} | {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x}", i * 32, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15], p[16], p[17], p[18], p[19], p[20], p[21], p[22], p[23], p[24], p[25], p[26], p[27], p[28], p[29], p[30], p[31]); } } } FSAsyncResult* FSGetAsyncResult(OSMessage* msg) { return (FSAsyncResult*)memory_getPointerFromVirtualOffset(msg->message); } sint32 __FSProcessAsyncResult(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, sint32 fsStatus, uint32 errHandling) { // a positive result (or zero) means success. Most operations return zero in case of success. Read and write operations return the number of transferred units if (fsStatus >= 0) { FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); OSMessage msg; OSReceiveMessage(&fsCmdBlockBody->syncTaskMsgQueue, &msg, OS_MESSAGE_BLOCK); _debugVerifyCommand("handleAsyncResult", fsCmdBlockBody); FSAsyncResult* asyncResult = FSGetAsyncResult(&msg); return asyncResult->fsStatusNew; } else { // todo - error handling cemuLog_log(LogType::Force, "FS handleAsyncResult(): unexpected error {:08x}", errHandling); cemu_assert_debug(false); return 0; } } FSA_RESULT __FSPrepareCmd_OpenFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, char* path, char* mode, uint32 createMode, uint32 openFlags, uint32 preallocSize) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; if (path == NULL) return FSA_RESULT::INVALID_PATH; if (mode == NULL) return FSA_RESULT::INVALID_PARAM; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::OPENFILE; // path size_t pathLen = strlen((char*)path); if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) { cemu_assert_debug(false); pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; } for (sint32 i = 0; i < pathLen; i++) fsaShimBuffer->request.cmdOpenFile.path[i] = path[i]; for (size_t i = pathLen; i < FSA_CMD_PATH_MAX_LENGTH; i++) fsaShimBuffer->request.cmdOpenFile.path[i] = '\0'; // mode size_t modeLen = strlen((char*)mode); if (modeLen >= 12) { cemu_assert_debug(false); modeLen = 12 - 1; } for (sint32 i = 0; i < modeLen; i++) fsaShimBuffer->request.cmdOpenFile.mode[i] = mode[i]; for (size_t i = modeLen; i < 12; i++) fsaShimBuffer->request.cmdOpenFile.mode[i] = '\0'; // createMode fsaShimBuffer->request.cmdOpenFile.createMode = createMode; // openFlags fsaShimBuffer->request.cmdOpenFile.openFlags = openFlags; // preallocSize fsaShimBuffer->request.cmdOpenFile.preallocSize = preallocSize; fsaShimBuffer->response.cmdOpenFile.fileHandleOutput = 0xFFFFFFFF; return FSA_RESULT::OK; } sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (outFileHandle == nullptr || path == nullptr || mode == nullptr) return -0x400; fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = outFileHandle; fsError = (FSStatus)__FSPrepareCmd_OpenFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path, mode, 0x660, 0, 0); if (fsError != (FSStatus)FS_RESULT::SUCCESS) return fsError; __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errHandling) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSOpenFileAsync(fsClient, fsCmdBlock, path, mode, outFileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } sint32 FSOpenFileExAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandlePtr outFileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { if (openFlag != 0) { cemuLog_log(LogType::Force, "FSOpenFileEx called with unsupported flags!"); cemu_assert_debug(false); } _FSCmdIntro(); if (outFileHandle == nullptr || path == nullptr || mode == nullptr) return -0x400; fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = outFileHandle; FSA_RESULT prepareResult = __FSPrepareCmd_OpenFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path, mode, createMode, openFlag, preallocSize); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSOpenFileEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandlePtr outFileHandle, uint32 errHandling) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSOpenFileExAsync(fsClient, fsCmdBlock, path, mode, createMode, openFlag, preallocSize, outFileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } FSA_RESULT __FSPrepareCmd_CloseFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint32 fileHandle) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::CLOSEFILE; fsaShimBuffer->request.cmdCloseFile.fileHandle = fileHandle; return FSA_RESULT::OK; } sint32 FSCloseFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_CloseFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSCloseFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errHandling) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSCloseFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } FSA_RESULT __FSPrepareCmd_FlushFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint32 fileHandle) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::FLUSHFILE; fsaShimBuffer->request.cmdFlushFile.fileHandle = fileHandle; return FSA_RESULT::OK; } sint32 FSFlushFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_FlushFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSFlushFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errHandling) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSFlushFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } FSA_RESULT __FSPrepareCmd_ReadFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, void* dest, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag) { if (fsaShimBuffer == NULL || dest == NULL) return FSA_RESULT::INVALID_BUFFER; MPTR destMPTR = memory_getVirtualOffsetFromPointer(dest); if ((destMPTR & 0x3F) != 0) return FSA_RESULT::INVALID_ALIGNMENT; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 1; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::READ; fsaShimBuffer->ioctlvVecIn = 1; fsaShimBuffer->ioctlvVecOut = 2; fsaShimBuffer->ioctlvVec[0].baseVirt = fsaShimBuffer; fsaShimBuffer->ioctlvVec[0].size = sizeof(iosu::fsa::FSARequest); fsaShimBuffer->ioctlvVec[1].baseVirt = destMPTR; fsaShimBuffer->ioctlvVec[1].size = size * count; fsaShimBuffer->ioctlvVec[2].baseVirt = &fsaShimBuffer->response; fsaShimBuffer->ioctlvVec[2].size = sizeof(iosu::fsa::FSAResponse); fsaShimBuffer->request.cmdReadFile.dest = dest; fsaShimBuffer->request.cmdReadFile.size = size; fsaShimBuffer->request.cmdReadFile.count = count; fsaShimBuffer->request.cmdReadFile.filePos = filePos; fsaShimBuffer->request.cmdReadFile.fileHandle = fileHandle; fsaShimBuffer->request.cmdReadFile.flag = flag; return FSA_RESULT::OK; } SysAllocator<uint8, 128, 64> _tempFSSpace; sint32 __FSReadFileAsyncEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool usePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (size == 0 || count == 0 || dest == NULL) dest = _tempFSSpace.GetPtr(); sint64 transferSizeS64 = (sint64)size * (sint64)count; if (transferSizeS64 < 0 || (transferSizeS64 >= 2LL * 1024LL * 1024LL * 1024LL)) { // size below 0 or over 2GB cemu_assert(false); return -0x400; } // coreinit.rpl splits up each read into smaller chunks (probably to support canceling big writes). This is handled by a specific // callback for the __FSQueueCmd functions. Whenever a chunk is read, it's getting re-queued until the reading has been completed. // For this it writes values into the fsCmdBlockBody->returnValues struct. At the moment we go the lazy route of just reading everything // at once, so we can skip the initialization of these values. if (usePos) flag |= FSA_CMD_FLAG_SET_POS; else flag &= ~FSA_CMD_FLAG_SET_POS; FSA_RESULT prepareResult = __FSPrepareCmd_ReadFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dest, size, count, filePos, fileHandle, flag); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { cemu_assert_debug(flag == 0); // todo return __FSReadFileAsyncEx(fsClient, fsCmdBlock, dst, size, count, false, 0, fileHandle, flag, errorMask, fsAsyncParams); } sint32 FSReadFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSReadFileAsync(fsClient, fsCmdBlock, dst, size, count, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { cemu_assert_debug(flag == 0); // todo sint32 fsStatus = __FSReadFileAsyncEx(fsClient, fsCmdBlock, dst, size, count, true, filePos, fileHandle, flag, errorMask, fsAsyncParams); return fsStatus; } sint32 FSReadFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSReadFileWithPosAsync(fsClient, fsCmdBlock, dst, size, count, filePos, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_WriteFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, void* dest, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag) { if (fsaShimBuffer == NULL || dest == NULL) return FSA_RESULT::INVALID_BUFFER; MPTR destMPTR = memory_getVirtualOffsetFromPointer(dest); if ((destMPTR & 0x3F) != 0) return FSA_RESULT::INVALID_ALIGNMENT; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 1; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::WRITE; fsaShimBuffer->ioctlvVecIn = 2; fsaShimBuffer->ioctlvVecOut = 1; fsaShimBuffer->ioctlvVec[0].baseVirt = fsaShimBuffer; fsaShimBuffer->ioctlvVec[0].size = sizeof(iosu::fsa::FSARequest); fsaShimBuffer->ioctlvVec[1].baseVirt = destMPTR; fsaShimBuffer->ioctlvVec[1].size = size * count; fsaShimBuffer->ioctlvVec[2].baseVirt = &fsaShimBuffer->response; fsaShimBuffer->ioctlvVec[2].size = sizeof(iosu::fsa::FSAResponse); fsaShimBuffer->request.cmdWriteFile.dest = dest; fsaShimBuffer->request.cmdWriteFile.size = size; fsaShimBuffer->request.cmdWriteFile.count = count; fsaShimBuffer->request.cmdWriteFile.filePos = filePos; fsaShimBuffer->request.cmdWriteFile.fileHandle = fileHandle; fsaShimBuffer->request.cmdWriteFile.flag = flag; return FSA_RESULT::OK; } sint32 __FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool useFilePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (size == 0 || count == 0 || dest == nullptr) dest = _tempFSSpace.GetPtr(); sint64 transferSizeS64 = (sint64)size * (sint64)count; if (transferSizeS64 < 0 || (transferSizeS64 >= 2LL * 1024LL * 1024LL * 1024LL)) { // size below 0 or over 2GB cemu_assert(false); return -0x400; } // coreinit.rpl splits up each write into smaller chunks (probably to support canceling big writes). This is handled by a specific // callback for the __FSQueueCmd functions. Whenever a chunk is written, it's getting re-queued until the writing has been completed. // For this it writes values into the fsCmdBlockBody->returnValues struct. At the moment we go the lazy route of just writing everything // at once, so we can skip the initialization of these values. if (useFilePos) flag |= FSA_CMD_FLAG_SET_POS; else flag &= ~FSA_CMD_FLAG_SET_POS; FSA_RESULT prepareResult = __FSPrepareCmd_WriteFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dest, size, count, filePos, fileHandle, flag); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { return __FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, false, 0, fileHandle, flag, errorMask, fsAsyncParams); } sint32 FSWriteFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSWriteFileAsync(fsClient, fsCmdBlock, src, size, count, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { return __FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, true, filePos, fileHandle, flag, errorMask, fsAsyncParams); } sint32 FSWriteFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, filePos, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_SetPosFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint32 fileHandle, uint32 filePos) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->request.cmdSetPosFile.fileHandle = fileHandle; fsaShimBuffer->request.cmdSetPosFile.filePos = filePos; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::SETPOS; return FSA_RESULT::OK; } sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_SetPosFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle, filePos); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSSetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask) { // used by games: Mario Kart 8 StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSSetPosFileAsync(fsClient, fsCmdBlock, fileHandle, filePos, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_GetPosFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint32 fileHandle) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->request.cmdGetPosFile.fileHandle = fileHandle; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::GETPOS; return FSA_RESULT::OK; } sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // games using this: Darksiders Warmastered Edition _FSCmdIntro(); fsCmdBlockBody->returnValues.cmdGetPosFile.filePosPtr = returnedFilePos; FSA_RESULT prepareResult = __FSPrepareCmd_GetPosFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSGetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetPosFileAsync(fsClient, fsCmdBlock, fileHandle, returnedFilePos, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_OpenDir(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, char* path) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; if (path == nullptr) return FSA_RESULT::INVALID_PATH; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::OPENDIR; // path sint32 pathLen = (sint32)strlen((char*)path); if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) { cemu_assert_debug(false); pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; } for (sint32 i = 0; i < pathLen; i++) fsaShimBuffer->request.cmdOpenDir.path[i] = path[i]; for (sint32 i = pathLen; i < FSA_CMD_PATH_MAX_LENGTH; i++) fsaShimBuffer->request.cmdOpenDir.path[i] = '\0'; fsaShimBuffer->response.cmdOpenDir.dirHandleOutput = -1; return FSA_RESULT::OK; } sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); cemu_assert(dirHandleOut && path); fsCmdBlockBody->returnValues.cmdOpenDir.handlePtr = dirHandleOut.GetMPTR(); FSA_RESULT prepareResult = __FSPrepareCmd_OpenDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSOpenDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSOpenDirAsync(fsClient, fsCmdBlock, path, dirHandleOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_ReadDir(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, FSDirHandle2 dirHandle) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::READDIR; fsaShimBuffer->request.cmdReadDir.dirHandle = dirHandle; return FSA_RESULT::OK; } sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_ReadDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); fsCmdBlockBody->returnValues.cmdReadDir.dirEntryPtr = dirEntryOut; __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSReadDirAsync(fsClient, fsCmdBlock, dirHandle, dirEntryOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_CloseDir(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, FSDirHandle2 dirHandle) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::CLOSEDIR; fsaShimBuffer->request.cmdCloseDir.dirHandle = dirHandle; return FSA_RESULT::OK; } sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_CloseDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSCloseDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSCloseDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_RewindDir(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, FSDirHandle2 dirHandle) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::REWINDDIR; fsaShimBuffer->request.cmdRewindDir.dirHandle = dirHandle; return FSA_RESULT::OK; } sint32 FSRewindDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_RewindDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSRewindDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSRewindDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_AppendFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint32 size, uint32 count, uint32 fileHandle, uint32 uknParam) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::APPENDFILE; fsaShimBuffer->request.cmdAppendFile.fileHandle = fileHandle; fsaShimBuffer->request.cmdAppendFile.count = count; fsaShimBuffer->request.cmdAppendFile.size = size; fsaShimBuffer->request.cmdAppendFile.uknParam = uknParam; return FSA_RESULT::OK; } sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_AppendFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, size, count, fileHandle, 0); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask) { StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSAppendFileAsync(fsClient, fsCmdBlock, size, count, fileHandle, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_TruncateFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, FSFileHandle2 fileHandle) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::TRUNCATEFILE; fsaShimBuffer->request.cmdTruncateFile.fileHandle = fileHandle; return FSA_RESULT::OK; } sint32 FSTruncateFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_TruncateFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSTruncateFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask) { StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSTruncateFileAsync(fsClient, fsCmdBlock, fileHandle, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_Rename(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, char* srcPath, char* dstPath) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; if (srcPath == NULL || dstPath == NULL) return FSA_RESULT::INVALID_PATH; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::RENAME; // source path size_t stringLen = strlen((char*)srcPath); if (stringLen >= FSA_CMD_PATH_MAX_LENGTH) { cemu_assert_debug(false); stringLen = FSA_CMD_PATH_MAX_LENGTH - 1; } for (sint32 i = 0; i < stringLen; i++) { fsaShimBuffer->request.cmdRename.srcPath[i] = srcPath[i]; } fsaShimBuffer->request.cmdRename.srcPath[stringLen] = '\0'; // destination path stringLen = strlen((char*)dstPath); if (stringLen >= FSA_CMD_PATH_MAX_LENGTH) { cemu_assert_debug(false); stringLen = FSA_CMD_PATH_MAX_LENGTH - 1; } for (sint32 i = 0; i < stringLen; i++) { fsaShimBuffer->request.cmdRename.dstPath[i] = dstPath[i]; } fsaShimBuffer->request.cmdRename.dstPath[stringLen] = '\0'; return FSA_RESULT::OK; } sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: XCX (via SAVERenameAsync) _FSCmdIntro(); if (srcPath == NULL || dstPath == NULL) { cemu_assert_debug(false); // path must not be NULL return -0x400; } FSA_RESULT prepareResult = __FSPrepareCmd_Rename(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, srcPath, dstPath); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSRename(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask) { StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSRenameAsync(fsClient, fsCmdBlock, srcPath, dstPath, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_Remove(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, uint8* path) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; if (path == NULL) return FSA_RESULT::INVALID_PATH; fsaShimBuffer->fsaDevHandle = devHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::REMOVE; size_t pathLen = strlen((char*)path); if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) { cemu_assert_debug(false); pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; } for (sint32 i = 0; i < pathLen; i++) { fsaShimBuffer->request.cmdRemove.path[i] = path[i]; } fsaShimBuffer->request.cmdRemove.path[pathLen] = '\0'; return FSA_RESULT::OK; } sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: XCX (via SAVERemoveAsync) _FSCmdIntro(); if (filePath == NULL) { cemu_assert_debug(false); // path must not be NULL return -0x400; } FSA_RESULT prepareResult = __FSPrepareCmd_Remove(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, filePath); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask) { StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSRemoveAsync(fsClient, fsCmdBlock, filePath, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_MakeDir(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, const char* path, uint32 uknVal660) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; if (path == NULL) return FSA_RESULT::INVALID_PATH; fsaShimBuffer->fsaDevHandle = devHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::MAKEDIR; size_t pathLen = strlen((char*)path); if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) { cemu_assert_debug(false); pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; } for (sint32 i = 0; i < pathLen; i++) { fsaShimBuffer->request.cmdMakeDir.path[i] = path[i]; } fsaShimBuffer->request.cmdMakeDir.path[pathLen] = '\0'; fsaShimBuffer->request.cmdMakeDir.uknParam = uknVal660; return FSA_RESULT::OK; } sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: XCX (via SAVEMakeDirAsync) _FSCmdIntro(); if (dirPath == NULL) { cemu_assert_debug(false); // path must not be NULL return -0x400; } FSA_RESULT prepareResult = __FSPrepareCmd_MakeDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirPath, 0x660); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, uint32 errorMask) { StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSMakeDirAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_ChangeDir(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, uint8* path) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; if (path == NULL) return FSA_RESULT::INVALID_PATH; fsaShimBuffer->fsaDevHandle = devHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::CHANGEDIR; size_t pathLen = strlen((char*)path); if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) { cemu_assert_debug(false); pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; } for (sint32 i = 0; i < pathLen; i++) fsaShimBuffer->request.cmdChangeDir.path[i] = path[i]; fsaShimBuffer->request.cmdChangeDir.path[pathLen] = '\0'; return FSA_RESULT::OK; } sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (path == NULL) { cemu_assert_debug(false); // path must not be NULL return -0x400; } FSA_RESULT prepareResult = __FSPrepareCmd_ChangeDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, (uint8*)path); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSChangeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask) { StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSChangeDirAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_GetCwd(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = devHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::GETCWD; return FSA_RESULT::OK; } sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: Super Mario Maker _FSCmdIntro(); fsCmdBlockBody->returnValues.cmdGetCwd.pathPtr = dirPathOut; fsCmdBlockBody->returnValues.cmdGetCwd.transferSize = dirPathMaxLen; FSA_RESULT prepareResult = __FSPrepareCmd_GetCwd(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSGetCwd(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask) { StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetCwdAsync(fsClient, fsCmdBlock, dirPathOut, dirPathMaxLen, errorMask, &asyncParams); auto r = __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); return r; } FSA_RESULT __FSPrepareCmd_FlushQuota(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, char* path) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; if (path == NULL) return FSA_RESULT::INVALID_PATH; fsaShimBuffer->fsaDevHandle = devHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::FLUSHQUOTA; size_t pathLen = strlen((char*)path); if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) { cemu_assert_debug(false); pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; } for (sint32 i = 0; i < pathLen; i++) fsaShimBuffer->request.cmdFlushQuota.path[i] = path[i]; fsaShimBuffer->request.cmdFlushQuota.path[pathLen] = '\0'; return FSA_RESULT::OK; } sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_FlushQuota(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSFlushQuota(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask) { StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSFlushQuotaAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_QueryInfo(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, uint8* queryString, uint32 queryType) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; if (queryString == NULL) return FSA_RESULT::INVALID_PATH; if (queryType > 8) return FSA_RESULT::INVALID_PARAM; fsaShimBuffer->fsaDevHandle = devHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::QUERYINFO; size_t stringLen = strlen((char*)queryString); if (stringLen >= FSA_CMD_PATH_MAX_LENGTH) { cemu_assert_debug(false); stringLen = FSA_CMD_PATH_MAX_LENGTH - 1; } for (sint32 i = 0; i < stringLen; i++) { fsaShimBuffer->request.cmdQueryInfo.query[i] = queryString[i]; } fsaShimBuffer->request.cmdQueryInfo.query[stringLen] = '\0'; fsaShimBuffer->request.cmdQueryInfo.queryType = queryType; return FSA_RESULT::OK; } sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); cemu_assert(queryString && queryResult); // query string and result must not be null fsCmdBlockBody->returnValues.cmdQueryInfo.queryResultPtr = queryResult; FSA_RESULT prepareResult = __FSPrepareCmd_QueryInfo(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, queryString, queryType); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSGetStatAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { sint32 fsStatus = __FSQueryInfoAsync(fsClient, fsCmdBlock, (uint8*)path, FSA_QUERY_TYPE_STAT, statOut, errorMask, fsAsyncParams); return fsStatus; } sint32 FSGetStat(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetStatAsync(fsClient, fsCmdBlock, path, statOut, errorMask, &asyncParams); sint32 ret = __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); return ret; } FSA_RESULT __FSPrepareCmd_GetStatFile(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, FSFileHandle2 fileHandle) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = devHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::GETSTATFILE; fsaShimBuffer->request.cmdGetStatFile.fileHandle = fileHandle; return FSA_RESULT::OK; } sint32 FSGetStatFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); cemu_assert(statOut); // statOut must not be null fsCmdBlockBody->returnValues.cmdStatFile.resultPtr = statOut; FSA_RESULT prepareResult = __FSPrepareCmd_GetStatFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSGetStatFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetStatFileAsync(fsClient, fsCmdBlock, fileHandle, statOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by: Wii U system settings app, Art Academy, Unity (e.g. Snoopy's Grand Adventure), Super Smash Bros sint32 fsStatus = __FSQueryInfoAsync(fsClient, fsCmdBlock, (uint8*)path, FSA_QUERY_TYPE_FREESPACE, returnedFreeSize, errorMask, fsAsyncParams); return fsStatus; } sint32 FSGetFreeSpaceSize(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetFreeSpaceSizeAsync(fsClient, fsCmdBlock, path, returnedFreeSize, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } FSA_RESULT __FSPrepareCmd_IsEof(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle devHandle, uint32 fileHandle) { if (fsaShimBuffer == NULL) return FSA_RESULT::INVALID_BUFFER; fsaShimBuffer->fsaDevHandle = devHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::ISEOF; fsaShimBuffer->request.cmdIsEof.fileHandle = fileHandle; return FSA_RESULT::OK; } sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by Paper Monsters Recut _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_IsEof(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); if (prepareResult != FSA_RESULT::OK) return (FSStatus)_FSAStatusToFSStatus(prepareResult); __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, RPLLoader_MakePPCCallable(export___FSQueueDefaultFinishFunc)); return (FSStatus)FS_RESULT::SUCCESS; } sint32 FSIsEof(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask) { StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSIsEofAsync(fsClient, fsCmdBlock, fileHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } void FSSetUserData(FSCmdBlock_t* fsCmdBlock, void* userData) { FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); if (fsCmdBlockBody) fsCmdBlockBody->userData = userData; } void* FSGetUserData(FSCmdBlock_t* fsCmdBlock) { FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); void* userData = nullptr; if (fsCmdBlockBody) userData = fsCmdBlockBody->userData.GetPtr(); return userData; } FS_VOLSTATE FSGetVolumeState(FSClient_t* fsClient) { // todo return FS_VOLSTATE_READY; } sint32 FSGetErrorCodeForViewer(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock) { // todo return 0; // no error } FSCmdBlock_t* FSGetCurrentCmdBlock(FSClient_t* fsClient) { FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); if (!fsClientBody) return nullptr; FSCmdBlockBody* cmdBlockBody = fsClientBody->currentCmdBlockBody; if (!cmdBlockBody) return nullptr; return cmdBlockBody->selfCmdBlock; } sint32 FSGetLastErrorCodeForViewer(FSClient_t* fsClient) { FSCmdBlock_t* cmdBlock = FSGetCurrentCmdBlock(fsClient); if (cmdBlock) { cemu_assert_unimplemented(); } return 0; // no error } std::vector<FSAClientHandle> s_fsa_activeClients; std::mutex s_fsa_activeClientsMutex; FSAClientHandle FSAAddClientEx(void* data) { if (data != NULL) { // TODO cemu_assert_unimplemented(); } IOSDevHandle handle = IOS_Open("/dev/fsa", 0); if (handle < IOS_ERROR::IOS_ERROR_OK) { return (FSAClientHandle)FSA_RESULT::PERMISSION_ERROR; } s_fsa_activeClientsMutex.lock(); s_fsa_activeClients.push_back((FSAClientHandle)handle); s_fsa_activeClientsMutex.unlock(); return (FSAClientHandle)handle; } FSAClientHandle FSAAddClient(void* data) { return FSAAddClientEx(data); } FSA_RESULT FSADelClient(FSAClientHandle clientHandle) { if (clientHandle == 0) { return FSA_RESULT::INVALID_CLIENT_HANDLE; } s_fsa_activeClientsMutex.lock(); auto it = std::find(s_fsa_activeClients.begin(), s_fsa_activeClients.end(), clientHandle); if (it != s_fsa_activeClients.end()) { IOS_Close(clientHandle); s_fsa_activeClients.erase(it); } s_fsa_activeClientsMutex.unlock(); return FSA_RESULT::OK; } SysAllocator<coreinit::IPCBufPool_t*> s_fsaIpcPool; SysAllocator<uint8, 0x37500> s_fsaIpcPoolBuffer; SysAllocator<uint32be> s_fsaIpcPoolBufferNumItems; std::mutex sFSAIPCBufferLock; bool s_fsaInitDone = false; void FSAInit() { if (!s_fsaInitDone) { s_fsaIpcPool = IPCBufPoolCreate(s_fsaIpcPoolBuffer.GetPtr(), s_fsaIpcPoolBuffer.GetByteSize(), sizeof(iosu::fsa::FSAShimBuffer), &s_fsaIpcPoolBufferNumItems, 0); s_fsaInitDone = true; } } bool FSAShimCheckClientHandle(FSAClientHandle clientHandle) { std::scoped_lock lock(s_fsa_activeClientsMutex); if (std::find(s_fsa_activeClients.begin(), s_fsa_activeClients.end(), clientHandle) != s_fsa_activeClients.end()) { return true; } return false; } FSA_RESULT FSAShimAllocateBuffer(MEMPTR<MEMPTR<iosu::fsa::FSAShimBuffer>> outBuffer) { if (!s_fsaInitDone) return FSA_RESULT::NOT_INIT; sFSAIPCBufferLock.lock(); auto ptr = IPCBufPoolAllocate(s_fsaIpcPool, sizeof(iosu::fsa::FSAShimBuffer)); sFSAIPCBufferLock.unlock(); if (!ptr) return FSA_RESULT::OUT_OF_RESOURCES; std::memset(ptr, 0, sizeof(iosu::fsa::FSAShimBuffer)); outBuffer[0] = reinterpret_cast<iosu::fsa::FSAShimBuffer*>(ptr); return FSA_RESULT::OK; } FSA_RESULT FSAShimFreeBuffer(iosu::fsa::FSAShimBuffer* buffer) { sFSAIPCBufferLock.lock(); IPCBufPoolFree(s_fsaIpcPool, (uint8_t*)buffer); sFSAIPCBufferLock.unlock(); return FSA_RESULT::OK; } FSA_RESULT FSACloseFile(FSAClientHandle client, uint32 fileHandle) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_CloseFile(shimBuffer->GetPtr(), client, fileHandle); if (result == FSA_RESULT::OK) result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAFlushFile(FSAClientHandle client, uint32_t fileHandle) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_FlushFile(shimBuffer->GetPtr(), client, fileHandle); if (result == FSA_RESULT::OK) result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAMakeDir(FSAClientHandle client, const char* path, uint32 uknVal660) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_MakeDir(shimBuffer->GetPtr(), client, path, uknVal660); if (result == FSA_RESULT::OK) result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSARename(FSAClientHandle client, char* oldPath, char* newPath) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_Rename(shimBuffer->GetPtr(), client, oldPath, newPath); if (result == FSA_RESULT::OK) result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAChangeDir(FSAClientHandle client, char* path) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_ChangeDir(shimBuffer->GetPtr(), client, (uint8_t*)path); if (result == FSA_RESULT::OK) result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAReadDir(FSAClientHandle client, FSDirHandle2 dirHandle, MEMPTR<FSDirEntry_t> directoryEntry) { if (directoryEntry.IsNull()) return FSA_RESULT::INVALID_BUFFER; if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_ReadDir(shimBuffer->GetPtr(), client, dirHandle); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); if (result == FSA_RESULT::OK) { *directoryEntry = shimBuffer->GetPtr()->response.cmdReadDir.dirEntry; } } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAOpenDir(FSAClientHandle client, char* path, MEMPTR<uint32be> dirHandle) { if (dirHandle.IsNull()) return FSA_RESULT::INVALID_BUFFER; if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_OpenDir(shimBuffer->GetPtr(), client, path); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); if (result == FSA_RESULT::OK) { *dirHandle = shimBuffer->GetPtr()->response.cmdOpenDir.dirHandleOutput; } } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSACloseDir(FSAClientHandle client, FSDirHandle2 dirHandle) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_CloseDir(shimBuffer->GetPtr(), client, dirHandle); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSARewindDir(FSAClientHandle client, FSDirHandle2 dirHandle) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_RewindDir(shimBuffer->GetPtr(), client, dirHandle); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAOpenFileEx(FSAClientHandle client, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32_t preallocSize, MEMPTR<uint32be> outFileHandle) { if (outFileHandle.IsNull()) return FSA_RESULT::INVALID_BUFFER; if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_OpenFile(shimBuffer->GetPtr(), client, path, mode, createMode, openFlag, preallocSize); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); if (result == FSA_RESULT::OK) { *outFileHandle = shimBuffer->GetPtr()->response.cmdOpenFile.fileHandleOutput; } } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAGetStatFile(FSAClientHandle client, FSFileHandle2 fileHandle, MEMPTR<FSStat_t> outStat) { if (outStat.IsNull()) return FSA_RESULT::INVALID_BUFFER; if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_GetStatFile(shimBuffer->GetPtr(), client, fileHandle); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); if (result == FSA_RESULT::OK) { *outStat = shimBuffer->GetPtr()->response.cmdStatFile.statOut; } } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSASetPosFile(FSAClientHandle client, FSFileHandle2 fileHandle, uint32_t pos) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_SetPosFile(shimBuffer->GetPtr(), client, fileHandle, pos); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSATruncateFile(FSAClientHandle client, FSFileHandle2 fileHandle) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_TruncateFile(shimBuffer->GetPtr(), client, fileHandle); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSARemove(FSAClientHandle client, char* path) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_Remove(shimBuffer->GetPtr(), client, (uint8_t*)path); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT __FSPrepareCmd_ChangeMode(iosu::fsa::FSAShimBuffer* fsaShimBuffer, IOSDevHandle fsaHandle, uint8_t* path, uint32 permission, uint32 permissionMask) { if (fsaShimBuffer == nullptr) return FSA_RESULT::INVALID_BUFFER; if (path == nullptr) return FSA_RESULT::INVALID_PATH; fsaShimBuffer->fsaDevHandle = fsaHandle; fsaShimBuffer->ipcReqType = 0; fsaShimBuffer->operationType = (uint32)FSA_CMD_OPERATION_TYPE::REWINDDIR; size_t pathLen = strlen((char*)path); for (sint32 i = 0; i < pathLen; i++) fsaShimBuffer->request.cmdChangeMode.path[i] = path[i]; for (size_t i = pathLen; i < FSA_CMD_PATH_MAX_LENGTH; i++) fsaShimBuffer->request.cmdChangeMode.path[i] = '\0'; fsaShimBuffer->request.cmdChangeMode.mode1 = permission; fsaShimBuffer->request.cmdChangeMode.mode2 = permissionMask; return FSA_RESULT::OK; } FSA_RESULT FSAChangeMode(FSAClientHandle client, const char* path, uint32 permission) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_ChangeMode(shimBuffer->GetPtr(), client, (uint8_t*)path, permission, 0x666); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAReadFile(FSAClientHandle client, void* buffer, uint32_t size, uint32_t count, FSFileHandle2 handle, uint32_t flags) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_ReadFile(shimBuffer->GetPtr(), client, buffer, size, count, 0, handle, flags & ~0x2); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAWriteFile(FSAClientHandle client, void* buffer, uint32_t size, uint32_t count, FSFileHandle2 handle, uint32_t flags) { if (!FSAShimCheckClientHandle(client)) return FSA_RESULT::INVALID_CLIENT_HANDLE; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_WriteFile(shimBuffer->GetPtr(), client, buffer, size, count, 0, handle, flags & ~0x2); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAGetInfoByQuery(FSAClientHandle client, char* path, uint32_t queryType, MEMPTR<void> outData) { if (outData.IsNull()) return FSA_RESULT::INVALID_BUFFER; StackAllocator<MEMPTR<iosu::fsa::FSAShimBuffer>, 1> shimBuffer; FSA_RESULT result = FSAShimAllocateBuffer(shimBuffer.GetPointer()); if (result != FSA_RESULT::OK) return result; result = __FSPrepareCmd_QueryInfo(shimBuffer->GetPtr(), client, (uint8_t*)path, queryType); if (result == FSA_RESULT::OK) { result = __FSAIPCSubmitCommand(shimBuffer->GetPtr()); if (result == FSA_RESULT::OK) { if (queryType == FSA_QUERY_TYPE_FREESPACE) { *MEMPTR<uint64be>(outData.GetMPTR()) = shimBuffer->GetPtr()->response.cmdQueryInfo.queryFreeSpace.freespace; } else if (queryType == FSA_QUERY_TYPE_DEVICE_INFO) { *MEMPTR<FSADeviceInfo_t>(outData.GetMPTR()) = shimBuffer->GetPtr()->response.cmdQueryInfo.queryDeviceInfo.info; } else if (queryType == FSA_QUERY_TYPE_STAT) { *MEMPTR<FSStat_t>(outData.GetMPTR()) = shimBuffer->GetPtr()->response.cmdQueryInfo.queryStat.stat; } else { // TODO: implement other query types cemu_assert_unimplemented(); result = FSA_RESULT::FATAL_ERROR; } } } FSAShimFreeBuffer(shimBuffer->GetPtr()); return result; } FSA_RESULT FSAGetStat(FSAClientHandle client, char* path, FSStat_t* outStat) { return FSAGetInfoByQuery(client, path, FSA_QUERY_TYPE_STAT, outStat); } FSA_RESULT FSAGetFreeSpaceSize(FSAClientHandle client, char* path, uint64* outSize) { return FSAGetInfoByQuery(client, path, FSA_QUERY_TYPE_DEVICE_INFO, outSize); } FSA_RESULT FSAGetDeviceInfo(FSAClientHandle client, char* path, void* outSize) { return FSAGetInfoByQuery(client, path, FSA_QUERY_TYPE_FREESPACE, outSize); } SysAllocator s_fsaStr_OK("FSA_STATUS_OK"); SysAllocator s_fsaStr_NOT_INIT("FSA_STATUS_NOT_INIT"); SysAllocator s_fsaStr_END_OF_DIRECTORY("FSA_STATUS_END_OF_DIRECTORY"); SysAllocator s_fsaStr_END_OF_FILE("FSA_STATUS_END_OF_FILE"); SysAllocator s_fsaStr_MAX_CLIENTS("FSA_STATUS_MAX_CLIENTS"); SysAllocator s_fsaStr_MAX_FILES("FSA_STATUS_MAX_FILES"); SysAllocator s_fsaStr_MAX_DIRS("FSA_STATUS_MAX_DIRS"); SysAllocator s_fsaStr_ALREADY_EXISTS("FSA_STATUS_ALREADY_EXISTS"); SysAllocator s_fsaStr_NOT_FOUND("FSA_STATUS_NOT_FOUND"); SysAllocator s_fsaStr_PERMISSION_ERROR("FSA_STATUS_PERMISSION_ERROR"); SysAllocator s_fsaStr_INVALID_PARAM("FSA_STATUS_INVALID_PARAM"); SysAllocator s_fsaStr_INVALID_PATH("FSA_STATUS_INVALID_PATH"); SysAllocator s_fsaStr_INVALID_BUFFER("FSA_STATUS_INVALID_BUFFER"); SysAllocator s_fsaStr_INVALID_ALIGNMENT("FSA_STATUS_INVALID_ALIGNMENT"); SysAllocator s_fsaStr_INVALID_CLIENT_HANDLE("FSA_STATUS_INVALID_CLIENT_HANDLE"); SysAllocator s_fsaStr_INVALID_FILE_HANDLE("FSA_STATUS_INVALID_FILE_HANDLE"); SysAllocator s_fsaStr_INVALID_DIR_HANDLE("FSA_STATUS_INVALID_DIR_HANDLE"); SysAllocator s_fsaStr_NOT_FILE("FSA_STATUS_NOT_FILE"); SysAllocator s_fsaStr_NOT_DIR("FSA_STATUS_NOT_DIR"); SysAllocator s_fsaStr_OUT_OF_RESOURCES("FSA_STATUS_OUT_OF_RESOURCES"); SysAllocator s_fsaStr_UNKNOWN("FSA_STATUS_???"); const char* FSAGetStatusStr(FSA_RESULT status) { switch (status) { case FSA_RESULT::OK: { return s_fsaStr_OK.GetPtr(); } case FSA_RESULT::NOT_INIT: { return s_fsaStr_NOT_INIT.GetPtr(); } case FSA_RESULT::END_OF_DIRECTORY: { return s_fsaStr_END_OF_DIRECTORY.GetPtr(); } case FSA_RESULT::END_OF_FILE: { return s_fsaStr_END_OF_FILE.GetPtr(); } case FSA_RESULT::MAX_CLIENTS: { return s_fsaStr_MAX_CLIENTS.GetPtr(); } case FSA_RESULT::MAX_FILES: { return s_fsaStr_MAX_FILES.GetPtr(); } case FSA_RESULT::MAX_DIRS: { return s_fsaStr_MAX_DIRS.GetPtr(); } case FSA_RESULT::ALREADY_EXISTS: { return s_fsaStr_ALREADY_EXISTS.GetPtr(); } case FSA_RESULT::NOT_FOUND: { return s_fsaStr_NOT_FOUND.GetPtr(); } case FSA_RESULT::PERMISSION_ERROR: { return s_fsaStr_PERMISSION_ERROR.GetPtr(); } case FSA_RESULT::INVALID_PARAM: { return s_fsaStr_INVALID_PARAM.GetPtr(); } case FSA_RESULT::INVALID_PATH: { return s_fsaStr_INVALID_PATH.GetPtr(); } case FSA_RESULT::INVALID_BUFFER: { return s_fsaStr_INVALID_BUFFER.GetPtr(); } case FSA_RESULT::INVALID_ALIGNMENT: { return s_fsaStr_INVALID_ALIGNMENT.GetPtr(); } case FSA_RESULT::INVALID_CLIENT_HANDLE: { return s_fsaStr_INVALID_CLIENT_HANDLE.GetPtr(); } case FSA_RESULT::INVALID_FILE_HANDLE: { return s_fsaStr_INVALID_FILE_HANDLE.GetPtr(); } case FSA_RESULT::INVALID_DIR_HANDLE: { return s_fsaStr_INVALID_DIR_HANDLE.GetPtr(); } case FSA_RESULT::NOT_FILE: { return s_fsaStr_NOT_FILE.GetPtr(); } case FSA_RESULT::NOT_DIR: { return s_fsaStr_NOT_DIR.GetPtr(); } case FSA_RESULT::OUT_OF_RESOURCES: { return s_fsaStr_OUT_OF_RESOURCES.GetPtr(); } case FSA_RESULT::FATAL_ERROR: { return s_fsaStr_UNKNOWN.GetPtr(); } } cemu_assert_unimplemented(); return s_fsaStr_UNKNOWN.GetPtr(); } FSA_RESULT FSAMount(FSAClientHandle client, const char* source, const char* target, uint32 flags, void* arg_buf, uint32_t arg_len) { if ("/dev/sdcard01" == std::string_view(source) && "/vol/external01" == std::string_view(target) && flags == 0 && arg_buf == nullptr && arg_len == 0) { mountSDCard(); return FSA_RESULT::OK; } else { cemu_assert_unimplemented(); } return FSA_RESULT::FATAL_ERROR; } FSA_RESULT FSAUnmount(FSAClientHandle client, const char* mountedTarget, uint32 flags) { return FSA_RESULT::OK; } void InitializeFS() { OSInitMutex(&s_fsGlobalMutex); cafeExportRegister("coreinit", FSInit, LogType::CoreinitFile); cafeExportRegister("coreinit", FSShutdown, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetMountSource, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetMountSourceNext, LogType::CoreinitFile); cafeExportRegister("coreinit", FSMount, LogType::CoreinitFile); cafeExportRegister("coreinit", FSBindMount, LogType::CoreinitFile); // client management cafeExportRegister("coreinit", FSAddClientEx, LogType::CoreinitFile); cafeExportRegister("coreinit", FSAddClient, LogType::CoreinitFile); cafeExportRegister("coreinit", FSDelClient, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetClientNum, LogType::CoreinitFile); // cmd cafeExportRegister("coreinit", FSInitCmdBlock, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetAsyncResult, LogType::CoreinitFile); // file operations cafeExportRegister("coreinit", FSOpenFileAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSOpenFile, LogType::CoreinitFile); cafeExportRegister("coreinit", FSOpenFileExAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSOpenFileEx, LogType::CoreinitFile); cafeExportRegister("coreinit", FSCloseFileAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSCloseFile, LogType::CoreinitFile); cafeExportRegister("coreinit", FSReadFileAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSReadFile, LogType::CoreinitFile); cafeExportRegister("coreinit", FSReadFileWithPosAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSReadFileWithPos, LogType::CoreinitFile); cafeExportRegister("coreinit", FSWriteFileAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSWriteFile, LogType::CoreinitFile); cafeExportRegister("coreinit", FSWriteFileWithPosAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSWriteFileWithPos, LogType::CoreinitFile); cafeExportRegister("coreinit", FSSetPosFileAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSSetPosFile, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetPosFileAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetPosFile, LogType::CoreinitFile); cafeExportRegister("coreinit", FSAppendFileAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSAppendFile, LogType::CoreinitFile); cafeExportRegister("coreinit", FSTruncateFileAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSTruncateFile, LogType::CoreinitFile); cafeExportRegister("coreinit", FSRenameAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSRename, LogType::CoreinitFile); cafeExportRegister("coreinit", FSRemoveAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSRemove, LogType::CoreinitFile); cafeExportRegister("coreinit", FSMakeDirAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSMakeDir, LogType::CoreinitFile); cafeExportRegister("coreinit", FSChangeDirAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSChangeDir, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetCwdAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetCwd, LogType::CoreinitFile); cafeExportRegister("coreinit", FSIsEofAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSIsEof, LogType::CoreinitFile); // directory operations cafeExportRegister("coreinit", FSOpenDirAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSOpenDir, LogType::CoreinitFile); cafeExportRegister("coreinit", FSReadDirAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSReadDir, LogType::CoreinitFile); cafeExportRegister("coreinit", FSCloseDirAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSCloseDir, LogType::CoreinitFile); // stat cafeExportRegister("coreinit", FSGetFreeSpaceSizeAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetFreeSpaceSize, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetStatAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetStat, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetStatFileAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetStatFile, LogType::CoreinitFile); // misc cafeExportRegister("coreinit", FSFlushQuotaAsync, LogType::CoreinitFile); cafeExportRegister("coreinit", FSFlushQuota, LogType::CoreinitFile); cafeExportRegister("coreinit", FSSetUserData, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetUserData, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetCurrentCmdBlock, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetVolumeState, LogType::CoreinitFile); cafeExportRegister("coreinit", FSGetErrorCodeForViewer, LogType::Placeholder); cafeExportRegister("coreinit", FSGetLastErrorCodeForViewer, LogType::Placeholder); cafeExportRegister("coreinit", FSAMakeDir, LogType::Placeholder); cafeExportRegister("coreinit", FSAInit, LogType::Placeholder); cafeExportRegister("coreinit", FSAAddClient, LogType::Placeholder); cafeExportRegister("coreinit", FSADelClient, LogType::Placeholder); cafeExportRegister("coreinit", FSARewindDir, LogType::Placeholder); cafeExportRegister("coreinit", FSAGetDeviceInfo, LogType::Placeholder); cafeExportRegister("coreinit", FSARename, LogType::Placeholder); cafeExportRegister("coreinit", FSAChangeDir, LogType::Placeholder); cafeExportRegister("coreinit", FSAMount, LogType::Placeholder); cafeExportRegister("coreinit", FSAUnmount, LogType::Placeholder); cafeExportRegister("coreinit", FSAChangeMode, LogType::Placeholder); cafeExportRegister("coreinit", FSAReadDir, LogType::Placeholder); cafeExportRegister("coreinit", FSAOpenDir, LogType::Placeholder); cafeExportRegister("coreinit", FSACloseDir, LogType::Placeholder); cafeExportRegister("coreinit", FSACloseFile, LogType::Placeholder); cafeExportRegister("coreinit", FSAFlushFile, LogType::Placeholder); cafeExportRegister("coreinit", FSAOpenFileEx, LogType::Placeholder); cafeExportRegister("coreinit", FSAGetStatFile, LogType::Placeholder); cafeExportRegister("coreinit", FSAGetFreeSpaceSize, LogType::Placeholder); cafeExportRegister("coreinit", FSASetPosFile, LogType::Placeholder); cafeExportRegister("coreinit", FSATruncateFile, LogType::Placeholder); cafeExportRegister("coreinit", FSARemove, LogType::Placeholder); cafeExportRegister("coreinit", FSAReadFile, LogType::Placeholder); cafeExportRegister("coreinit", FSAWriteFile, LogType::Placeholder); cafeExportRegister("coreinit", FSAGetStat, LogType::Placeholder); cafeExportRegister("coreinit", FSAGetStatusStr, LogType::Placeholder); g_fsRegisteredClientBodies = nullptr; } } // namespace coreinit ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_FS.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/IOSU/iosu_ipc_common.h" #include "Cafe/IOSU/fsa/fsa_types.h" #include "Cafe/IOSU/fsa/iosu_fsa.h" #include "coreinit_MessageQueue.h" typedef MEMPTR<betype<FSFileHandle2>> FSFileHandlePtr; typedef MEMPTR<betype<FSDirHandle2>> FSDirHandlePtr; typedef uint32 FSAClientHandle; struct FSAsyncParams { MEMPTR<void> userCallback; MEMPTR<void> userContext; MEMPTR<coreinit::OSMessageQueue> ioMsgQueue; }; static_assert(sizeof(FSAsyncParams) == 0xC); namespace coreinit { struct FSCmdBlockBody; struct FSCmdQueue { enum class QUEUE_FLAG : uint32 { IS_FULL = (1 << 0), // waiting for Ioctl(v) result CANCEL_ALL = (1 << 4), }; /* +0x00 */ MEMPTR<FSCmdBlockBody> first; /* +0x04 */ MEMPTR<FSCmdBlockBody> last; /* +0x08 */ OSFastMutex fastMutex; /* +0x34 */ MPTR dequeueHandlerFuncMPTR; /* +0x38 */ uint32be numCommandsInFlight; /* +0x3C */ uint32 numMaxCommandsInFlight; /* +0x40 */ betype<QUEUE_FLAG> queueFlags; }; DEFINE_ENUM_FLAG_OPERATORS(FSCmdQueue::QUEUE_FLAG); static_assert(sizeof(FSCmdQueue) == 0x44); #define FS_CLIENT_BUFFER_SIZE (5888) #define FS_CMD_BLOCK_SIZE (2688) struct FSClient_t { uint8 buffer[FS_CLIENT_BUFFER_SIZE]; }; struct FSCmdBlock_t { union { uint8 buffer[FS_CMD_BLOCK_SIZE]; struct { uint32 mount_it; } data; }; }; static_assert(sizeof(FSCmdBlock_t) == FS_CMD_BLOCK_SIZE); struct FSClientBody_t { uint8 ukn0000[0x100]; uint8 ukn0100[0x100]; uint8 ukn0200[0x100]; uint8 ukn0300[0x100]; uint8 ukn0400[0x100]; uint8 ukn0500[0x100]; uint8 ukn0600[0x100]; uint8 ukn0700[0x100]; uint8 ukn0800[0x100]; uint8 ukn0900[0x100]; uint8 ukn0A00[0x100]; uint8 ukn0B00[0x100]; uint8 ukn0C00[0x100]; uint8 ukn0D00[0x100]; uint8 ukn0E00[0x100]; uint8 ukn0F00[0x100]; uint8 ukn1000[0x100]; uint8 ukn1100[0x100]; uint8 ukn1200[0x100]; uint8 ukn1300[0x100]; uint8 ukn1400[0x10]; uint8 ukn1410[0x10]; uint8 ukn1420[0x10]; uint8 ukn1430[0x10]; uint32 ukn1440; betype<IOSDevHandle> iosuFSAHandle; uint32 ukn1448; uint32 ukn144C; uint8 ukn1450[0x10]; uint8 ukn1460[0x10]; uint8 ukn1470[0x10]; FSCmdQueue fsCmdQueue; /* +0x14C4 */ MEMPTR<struct FSCmdBlockBody> currentCmdBlockBody; // set to currently active cmd uint32 ukn14C8; uint32 ukn14CC; uint8 ukn14D0[0x10]; uint8 ukn14E0[0x10]; uint8 ukn14F0[0x10]; uint8 ukn1500[0x100]; uint32 ukn1600; uint32 ukn1604; uint32 ukn1608; uint32 ukn160C; uint32 ukn1610; MEMPTR<FSClientBody_t> fsClientBodyNext; // next FSClientBody_t* in list of registered clients (list is circular, the last element points to the first element) uint32 ukn1618; /* +0x161C */ MEMPTR<FSClient_t> selfClient; // pointer to FSClient struct which holds this FSClientBody uint32 ukn1620; }; struct FSAsyncResult { /* +0x00 */ FSAsyncParams fsAsyncParamsNew; // fs message storage struct FSMessage { /* +0x0C / 0x0978 */ MEMPTR<FSAsyncResult> fsAsyncResult; /* +0x10 */ MPTR fsClientMPTR2; // 0x097C /* +0x14 */ MPTR fsCmdBlockMPTR; // 0x0980 /* +0x18 */ MPTR commandType; // 0x0984 }; union { OSMessage osMsg; FSMessage fsMsg; } msgUnion; /* +0x1C */ MEMPTR<FSClient_t> fsClient; // 0x0988 /* +0x20 */ MEMPTR<FSCmdBlock_t> fsCmdBlock; // 0x98C /* +0x24 */ uint32be fsStatusNew; // 0x990 }; static_assert(sizeof(FSAsyncResult) == 0x28); struct FSCmdBlockReturnValues_t { union { uint8 ukn0[0x14]; struct { MEMPTR<betype<FSResHandle>> handlePtr; } cmdOpenFile; struct { MEMPTR<uint32be> filePosPtr; } cmdGetPosFile; struct { uint32be transferSize; uint32be uknVal094C; uint32be transferElemSize; uint32be uknVal0954; } cmdReadFile; struct { uint32be transferSize; uint32be uknVal094C; uint32be transferElemSize; uint32be uknVal0954; } cmdWriteFile; struct { MEMPTR<uint32be> handlePtr; } cmdOpenDir; struct { MEMPTR<FSDirEntry_t> dirEntryPtr; } cmdReadDir; struct { MEMPTR<char> pathPtr; uint32be transferSize; } cmdGetCwd; struct { MEMPTR<void> queryResultPtr; } cmdQueryInfo; struct { MEMPTR<void> resultPtr; } cmdStatFile; }; }; static_assert(sizeof(FSCmdBlockReturnValues_t) == 0x14); struct FSCmdBlockBody { iosu::fsa::FSAShimBuffer fsaShimBuffer; /* +0x0938 */ MEMPTR<FSClientBody_t> fsClientBody; /* +0x093C */ uint32 statusCode; // not a status code but rather the state? Uses weird values for some reason /* +0x0940 */ uint32be cancelState; // bitmask. Bit 0 -> If set command has been canceled FSCmdBlockReturnValues_t returnValues; // link for cmd queue MEMPTR<FSCmdBlockBody> next; MEMPTR<FSCmdBlockBody> previous; /* +0x960 */ betype<FSA_RESULT> lastFSAStatus; uint32 ukn0964; /* +0x0968 */ uint8 errHandling; // return error flag mask /* +0x096C */ FSAsyncResult asyncResult; /* +0x0994 */ MEMPTR<void> userData; /* +0x0998 */ OSMessageQueue syncTaskMsgQueue; // this message queue is used when mapping asynchronous tasks to synchronous API /* +0x09D4 */ OSMessage _syncTaskMsg[1]; /* +0x09E4 */ MPTR cmdFinishFuncMPTR; /* +0x09E8 */ uint8 priority; uint8 uknStatusGuessed09E9; uint8 ukn09EA; uint8 ukn09EB; uint32 ukn09EC; uint32 ukn9F0; uint32be ukn9F4_lastErrorRelated; /* +0x9F8 */ MEMPTR<FSCmdBlock_t> selfCmdBlock; uint32 ukn9FC; }; static_assert(sizeof(FSCmdBlock_t) == 0xA80); #define FSA_CMD_FLAG_SET_POS (1 << 0) #define FSA_CMD_STATUS_CODE_D900A21 0xD900A21 // cmd block is initialized #define FSA_CMD_STATUS_CODE_D900A22 0xD900A22 // cmd block is queued #define FSA_CMD_STATUS_CODE_D900A24 0xD900A24 // cmd block was processed and is available again #define FSA_CMD_STATUS_CODE_D900A26 0xD900A26 // cmd block result is being processed enum FS_VOLSTATE : sint32 { FS_VOLSTATE_READY = 1, }; // internal interface sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errHandling, FSAsyncParams* fsAsyncParams); // coreinit exports FS_RESULT FSAddClientEx(FSClient_t* fsClient, uint32 uknR4, uint32 errHandling); FS_RESULT FSAddClient(FSClient_t* fsClient, uint32 errHandling); FS_RESULT FSDelClient(FSClient_t* fsClient, uint32 errHandling); void FSInitCmdBlock(FSCmdBlock_t* fsCmdBlock); sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errHandling, FSAsyncParams* asyncParams); sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errHandling); sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSReadFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask); sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSReadFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask); sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSWriteFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask); sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSWriteFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask); sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSSetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask); sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSGetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask); sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask); sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSIsEof(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask); sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSRename(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask); sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask); sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, uint32 errorMask); sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSChangeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask); sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSGetCwd(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask); sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSGetFreeSpaceSize(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask); sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSOpenDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask); sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSCloseDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask); sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSFlushQuota(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask); FS_VOLSTATE FSGetVolumeState(FSClient_t* fsClient); void InitializeFS(); }; // namespace coreinit ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_GHS.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/RPL/rpl.h" namespace coreinit { struct iobbuf { uint32be ukn00; // lock index? uint32be ukn04; // ? uint32be ukn08; // ? uint32be flags; // permissions and channel }; #define GHS_FOPEN_MAX 100 struct GHSAccessibleData { iobbuf _iob[GHS_FOPEN_MAX]; MPTR _iob_lock[GHS_FOPEN_MAX]; uint16be __gh_FOPEN_MAX; MEMPTR<void> ghs_environ; uint32 ghs_Errno; // exposed as 'errno' data export }; SysAllocator<GHSAccessibleData> g_ghs_data; struct ghs_flock { uint32be mutexIndex; }; void __ghs_flock_create(ghs_flock* flock); ghs_flock* __ghs_flock_ptr(iobbuf* iob); std::recursive_mutex g_ghsLock; std::recursive_mutex g_ghsLockFlock; SysAllocator<coreinit::OSMutex, GHS_FOPEN_MAX> _flockMutexArray; bool _flockMutexMask[GHS_FOPEN_MAX]; // if set, mutex in _flockMutexArray is reserved #define IOB_FLAG_IN (0x1) #define IOB_FLAG_OUT (0x2) #define IOB_FLAG_CHANNEL(__x) ((__x)<<18) void PrepareGHSRuntime() { g_ghs_data->ghs_environ = nullptr; g_ghs_data->__gh_FOPEN_MAX = GHS_FOPEN_MAX; g_ghs_data->ghs_Errno = 0; for (sint32 i = 0; i < GHS_FOPEN_MAX; i++) _flockMutexMask[i] = false; // init stdin/stdout/stderr g_ghs_data->_iob[0].flags = IOB_FLAG_IN; g_ghs_data->_iob[1].flags = IOB_FLAG_OUT; g_ghs_data->_iob[1].flags = IOB_FLAG_OUT; g_ghs_data->_iob[0].flags |= IOB_FLAG_CHANNEL(0); g_ghs_data->_iob[1].flags |= IOB_FLAG_CHANNEL(1); g_ghs_data->_iob[2].flags |= IOB_FLAG_CHANNEL(2); __ghs_flock_create(__ghs_flock_ptr(g_ghs_data->_iob + 0)); __ghs_flock_create(__ghs_flock_ptr(g_ghs_data->_iob + 1)); __ghs_flock_create(__ghs_flock_ptr(g_ghs_data->_iob + 2)); osLib_addVirtualPointer("coreinit", "__gh_FOPEN_MAX", memory_getVirtualOffsetFromPointer(&g_ghs_data->__gh_FOPEN_MAX)); osLib_addVirtualPointer("coreinit", "_iob", memory_getVirtualOffsetFromPointer(g_ghs_data->_iob)); osLib_addVirtualPointer("coreinit", "environ", memory_getVirtualOffsetFromPointer(&g_ghs_data->ghs_environ)); osLib_addVirtualPointer("coreinit", "errno", memory_getVirtualOffsetFromPointer(&g_ghs_data->ghs_Errno)); } void __ghs_flock_create(ghs_flock* flock) { g_ghsLockFlock.lock(); // find available mutex sint32 mutexIndex = -1; for (sint32 i = 0; i < GHS_FOPEN_MAX; i++) { if (!_flockMutexMask[i]) { mutexIndex = i; break; } } if (mutexIndex == -1) { cemuLog_log(LogType::Force, "__ghs_flock_create(): No flock available"); cemu_assert(false); // no available mutex } // mark mutex as reserved _flockMutexMask[mutexIndex] = true; // init mutex coreinit::OSInitMutexEx(_flockMutexArray.GetPtr() + mutexIndex, NULL); // update flock to point to the reserved mutex flock->mutexIndex = mutexIndex; g_ghsLockFlock.unlock(); } void __ghs_flock_destroy(uint32 index) { g_ghsLockFlock.lock(); cemu_assert_debug(index > 2); // stdin/stdout/stderr should never be released? cemu_assert(index < GHS_FOPEN_MAX); cemu_assert_debug(_flockMutexMask[index]); _flockMutexMask[index] = false; g_ghsLockFlock.unlock(); } ghs_flock* __ghs_flock_ptr(iobbuf* iob) { size_t streamIndex = iob - g_ghs_data->_iob; return (ghs_flock*)&(g_ghs_data->_iob_lock[streamIndex]); } void __ghs_flock_file(uint32 index) { cemu_assert(index < GHS_FOPEN_MAX); OSLockMutex(_flockMutexArray.GetPtr() + index); } void __ghs_funlock_file(uint32 index) { cemu_assert(index < GHS_FOPEN_MAX); OSUnlockMutex(_flockMutexArray.GetPtr() + index); } void __ghsLock() { while (!g_ghsLock.try_lock()) { PPCCore_switchToScheduler(); } } void __ghsUnlock() { g_ghsLock.unlock(); } void* __get_eh_init_block() { return nullptr; } void* __get_eh_globals() { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); return currentThread->crt.eh_globals.GetPtr(); } void* __get_eh_mem_manage() { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); return ¤tThread->crt.eh_mem_manage; } sint32be* __gh_errno_ptr() { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); return ¤tThread->context.ghs_errno; } void __gh_set_errno(sint32 errNo) { *__gh_errno_ptr() = errNo; } sint32 __gh_get_errno() { return *__gh_errno_ptr(); } void* __get_eh_store_globals() { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); return ¤tThread->crt.eh_store_globals; } void* __get_eh_store_globals_tdeh() { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); return ¤tThread->crt.eh_store_globals_tdeh; } struct ghs_mtx_t { MEMPTR<coreinit::OSMutex> mutexPtr; }; void __ghs_mtx_init(ghs_mtx_t* mtx) { mtx->mutexPtr = (coreinit::OSMutex*)coreinit::_weak_MEMAllocFromDefaultHeapEx(ppcsizeof<coreinit::OSMutex>(), 8); coreinit::OSInitMutex(mtx->mutexPtr.GetPtr()); } void __ghs_mtx_dst(ghs_mtx_t* mtx) { coreinit::_weak_MEMFreeToDefaultHeap(mtx->mutexPtr.GetPtr()); mtx->mutexPtr = nullptr; } void __ghs_mtx_lock(ghs_mtx_t* mtx) { coreinit::OSLockMutex(mtx->mutexPtr.GetPtr()); } void __ghs_mtx_unlock(ghs_mtx_t* mtx) { coreinit::OSUnlockMutex(mtx->mutexPtr.GetPtr()); } struct OSTLSBlock { MPTR addr; uint32 ukn04; }; static_assert(sizeof(OSTLSBlock) == 8); struct TLS_Index { uint16 ukn00; uint16 tlsModuleIndex; MPTR ukn04; }; void* __tls_get_addr(TLS_Index* tlsIndex) { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); if (_swapEndianU16(tlsIndex->tlsModuleIndex) == 0) assert_dbg(); // check if we need to allocate additional TLS blocks for this thread if (_swapEndianU16(tlsIndex->tlsModuleIndex) >= _swapEndianU32(currentThread->numAllocatedTLSBlocks)) { uint32 allocSize = (RPLLoader_GetMaxTLSModuleIndex() + 1) * sizeof(OSTLSBlock); // __OSDynLoad_gTLSHeader.ukn00 * 8; MPTR allocMem = coreinit_allocFromSysArea(allocSize, 4); memset(memory_getPointerFromVirtualOffset(allocMem), 0, allocSize); if (_swapEndianU32(currentThread->numAllocatedTLSBlocks) != 0) { // keep previously allocated blocks memcpy(memory_getPointerFromVirtualOffset(allocMem), memory_getPointerFromVirtualOffset(_swapEndianU32(currentThread->tlsBlocksMPTR)), _swapEndianU32(currentThread->numAllocatedTLSBlocks) * 8); } currentThread->tlsBlocksMPTR = _swapEndianU32(allocMem); currentThread->numAllocatedTLSBlocks = _swapEndianU16(RPLLoader_GetMaxTLSModuleIndex() + 1); } // look up TLS address based on moduleIndex OSTLSBlock* tlsBlock = (OSTLSBlock*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(currentThread->tlsBlocksMPTR) + sizeof(OSTLSBlock) * (uint32)_swapEndianU16(tlsIndex->tlsModuleIndex)); if (tlsBlock->addr != _swapEndianU32(MPTR_NULL)) { //osLib_returnFromFunction(hCPU, _swapEndianU32(tlsBlock->addr)+_swapEndianU32(tlsIndex->ukn04)); return memory_getPointerFromVirtualOffset(_swapEndianU32(tlsBlock->addr) + _swapEndianU32(tlsIndex->ukn04)); } // alloc data for TLS block uint8* tlsSectionData = nullptr; sint32 tlsSize = 0; bool r = RPLLoader_GetTLSDataByTLSIndex((sint16)_swapEndianU16(tlsIndex->tlsModuleIndex), &tlsSectionData, &tlsSize); cemu_assert(r); cemu_assert(tlsSize != 0); MPTR tlsData = coreinit_allocFromSysArea(tlsSize, 32); memcpy(memory_getPointerFromVirtualOffset(tlsData), tlsSectionData, tlsSize); tlsBlock->addr = _swapEndianU32(tlsData); return memory_getPointerFromVirtualOffset(_swapEndianU32(tlsBlock->addr) + _swapEndianU32(tlsIndex->ukn04)); } void InitializeGHS() { cafeExportRegister("coreinit", __ghs_flock_create, LogType::Placeholder); cafeExportRegister("coreinit", __ghs_flock_destroy, LogType::Placeholder); cafeExportRegister("coreinit", __ghs_flock_ptr, LogType::Placeholder); cafeExportRegister("coreinit", __ghs_flock_file, LogType::Placeholder); cafeExportRegister("coreinit", __ghs_funlock_file, LogType::Placeholder); cafeExportRegister("coreinit", __ghsLock, LogType::Placeholder); cafeExportRegister("coreinit", __ghsUnlock, LogType::Placeholder); cafeExportRegister("coreinit", __get_eh_init_block, LogType::Placeholder); cafeExportRegister("coreinit", __get_eh_globals, LogType::Placeholder); cafeExportRegister("coreinit", __get_eh_mem_manage, LogType::Placeholder); cafeExportRegister("coreinit", __gh_errno_ptr, LogType::Placeholder); cafeExportRegister("coreinit", __gh_set_errno, LogType::Placeholder); cafeExportRegister("coreinit", __gh_get_errno, LogType::Placeholder); cafeExportRegister("coreinit", __get_eh_store_globals, LogType::Placeholder); cafeExportRegister("coreinit", __get_eh_store_globals_tdeh, LogType::Placeholder); cafeExportRegister("coreinit", __ghs_mtx_init, LogType::Placeholder); cafeExportRegister("coreinit", __ghs_mtx_dst, LogType::Placeholder); cafeExportRegister("coreinit", __ghs_mtx_lock, LogType::Placeholder); cafeExportRegister("coreinit", __ghs_mtx_unlock, LogType::Placeholder); cafeExportRegister("coreinit", __tls_get_addr, LogType::Placeholder); } }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_GHS.h ================================================ #pragma once namespace coreinit { void PrepareGHSRuntime(); sint32be* __gh_errno_ptr(); void __gh_set_errno(sint32 errNo); sint32 __gh_get_errno(); void InitializeGHS(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_HWInterface.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "coreinit_HWInterface.h" namespace coreinit { enum class RegisterInterfaceId : uint32 // for __OSRead/__OSWrite API (register access in userspace) { INTERFACE_VI_UKN = 0, // 0x0C1E0000 INTERFACE_VI2_UKN = 3, // might also be some other interface? }; enum class SysRegisterInterfaceId : uint32 // for __OSRead/__OSWriteRegister (register access via kernel systemcall) { INTERFACE_UKN = 0, INTERFACE_3_ACR_VI = 3, // 0x0D00021C INTERFACE_6_SI = 6, // 0x0D006400 INTERFACE_7_AI_PROBABLY = 7, // 0x0D046C00 // AI or some secondary AI interface? }; PAddr _GetRegisterPhysicalAddress(RegisterInterfaceId interfaceId, uint32 offset) { PAddr base = 0; switch (interfaceId) { case RegisterInterfaceId::INTERFACE_VI_UKN: base = 0x0C1E0000; break; default: cemu_assert_debug(false); // todo return 0; } return base + offset; } PAddr _GetSysRegisterPhysicalAddress(SysRegisterInterfaceId interfaceId, uint32 offset) { PAddr base = 0; switch (interfaceId) { case SysRegisterInterfaceId::INTERFACE_3_ACR_VI: base = 0x0D00021C; break; case SysRegisterInterfaceId::INTERFACE_6_SI: base = 0x0D006400; break; default: cemu_assert_debug(false); // todo return 0; } return base + offset; } /* Userspace register interface */ uint32 OSReadRegister32(RegisterInterfaceId interfaceId, uint32 offset) { PAddr regAddr = _GetRegisterPhysicalAddress(interfaceId, offset); cemu_assert_debug(regAddr); return MMU::ReadMMIO_32(regAddr); } uint16 OSReadRegister16(RegisterInterfaceId interfaceId, uint32 offset) { PAddr regAddr = _GetRegisterPhysicalAddress(interfaceId, offset); cemu_assert_debug(regAddr); return MMU::ReadMMIO_16(regAddr); } void OSWriteRegister16(uint16 newValue, RegisterInterfaceId interfaceId, uint32 offset) { static bool s_dbg = false; if (!s_dbg) { cemu_assert_debug(false); s_dbg = true; } } void OSWriteRegister32(uint16 newValue, RegisterInterfaceId interfaceId, uint32 offset) { static bool s_dbg = false; if (!s_dbg) { cemu_assert_debug(false); s_dbg = true; } } void OSModifyRegister16(RegisterInterfaceId interfaceId, uint32 uknR4, uint32 uknR5, uint32 uknR6) { static bool s_dbg = false; if (!s_dbg) { cemu_assert_debug(false); s_dbg = true; } } /* Kernel register interface */ uint32 __OSReadRegister32Ex(SysRegisterInterfaceId interfaceId, uint32 registerId) { uint32 offset = registerId * 4; cemu_assert_debug(offset < 0x40); PAddr regAddr = _GetSysRegisterPhysicalAddress(interfaceId, offset); cemu_assert_debug(regAddr); return MMU::ReadMMIO_32(regAddr); } void __OSWriteRegister32Ex(SysRegisterInterfaceId interfaceId, uint32 registerId, uint32 newValue) { uint32 offset = registerId * 4; cemu_assert_debug(offset < 0x40); PAddr regAddr = _GetSysRegisterPhysicalAddress(interfaceId, offset); cemu_assert_debug(regAddr); MMU::WriteMMIO_32(regAddr, newValue); } void InitializeHWInterface() { cafeExportRegister("coreinit", OSReadRegister32, LogType::Placeholder); cafeExportRegister("coreinit", OSReadRegister16, LogType::Placeholder); cafeExportRegister("coreinit", OSWriteRegister16, LogType::Placeholder); cafeExportRegister("coreinit", OSWriteRegister32, LogType::Placeholder); cafeExportRegister("coreinit", OSModifyRegister16, LogType::Placeholder); cafeExportRegister("coreinit", __OSReadRegister32Ex, LogType::Placeholder); cafeExportRegister("coreinit", __OSWriteRegister32Ex, LogType::Placeholder); }; }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_HWInterface.h ================================================ namespace coreinit { void InitializeHWInterface(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_IM.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" // APD = Automatic Power Down namespace coreinit { #define IM_ERROR_NONE 0 void coreinitExport_IMIsAPDEnabledBySysSettings(PPCInterpreter_t* hCPU) { debug_printf("IMIsAPDEnabledBySysSettings(0x%08x)\n", hCPU->gpr[3]); ppcDefineParamTypePtr(isAPDEnabled, uint32be, 0); *isAPDEnabled = 0; osLib_returnFromFunction(hCPU, IM_ERROR_NONE); } void coreinitExport_IMGetTimeBeforeAPD(PPCInterpreter_t* hCPU) { // parameters: // r3 uint32* returns the remaining number of seconds until auto-shutdown memory_writeU32(hCPU->gpr[3], 60 * 30); // 30 minutes osLib_returnFromFunction(hCPU, IM_ERROR_NONE); } void coreinitExport_IMGetTimeBeforeDimming(PPCInterpreter_t* hCPU) { // parameters: // r3 uint32* returns the remaining number of seconds until dimming memory_writeU32(hCPU->gpr[3], 60 * 30); // 30 minutes osLib_returnFromFunction(hCPU, IM_ERROR_NONE); } bool imDimIsEnabled = true; void coreinitExport_IMEnableDim(PPCInterpreter_t* hCPU) { imDimIsEnabled = true; osLib_returnFromFunction(hCPU, IM_ERROR_NONE); } void coreinitExport_IMIsDimEnabled(PPCInterpreter_t* hCPU) { // parameters: // r3 uint32* returns the remaining number of seconds until auto-shutdown memory_writeU32(hCPU->gpr[3], imDimIsEnabled ? 1 : 0); // enabled osLib_returnFromFunction(hCPU, IM_ERROR_NONE); } void coreinitExport_IMGetAPDPeriod(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "IMGetAPDPeriod(0x{:08x})", hCPU->gpr[3]); // parameters: // r3 uint32* returns the number of seconds until auto-shutdown occurs memory_writeU32(hCPU->gpr[3], 600); osLib_returnFromFunction(hCPU, IM_ERROR_NONE); } void coreinitExport_IM_GetParameter(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "IM_GetParameter()"); ppcDefineParamS32(imHandle, 0); // handle from IM_Open() ppcDefineParamS32(uknR4, 1); ppcDefineParamS32(parameterId, 2); ppcDefineParamStructPtr(output, void, 3); ppcDefineParamS32(uknR7, 4); ppcDefineParamS32(uknR8, 5); if (parameterId == 0) { // inactive seconds *(uint32be*)output = 600; } else { cemu_assert_unimplemented(); } osLib_returnFromFunction(hCPU, IM_ERROR_NONE); } void coreinitExport_IM_GetRuntimeParameter(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "IM_GetRuntimeParameter()"); ppcDefineParamS32(parameterId, 0); ppcDefineParamStructPtr(output, void, 1); if (parameterId == 8) { // indicates if last session was ended due to auto-power-down *(uint32be*)output = 0; } else { cemu_assert_unimplemented(); } osLib_returnFromFunction(hCPU, IM_ERROR_NONE); } void coreinitExport_IM_GetHomeButtonParams(PPCInterpreter_t* hCPU) { debug_printf("IM_GetHomeButtonParams(...)\n"); ppcDefineParamS32(imObj, 0); ppcDefineParamMPTR(ipcBuf, 1); ppcDefineParamMPTR(paramOut, 2); ppcDefineParamS32(uknR6, 3); ppcDefineParamS32(uknR7, 4); // todo // note: No idea what these values mean. But they were chosen so that the Browser (surf.rpx) does not OSPanic() memory_writeU32(paramOut + 0x0, 0); memory_writeU32(paramOut + 0x4, 0); // for scope.rpx (Download Manager) //memory_writeU32(paramOut + 0x0, 1); //memory_writeU32(paramOut + 0x4, 2); // some sort of index (starting at 1?) osLib_returnFromFunction(hCPU, IM_ERROR_NONE); } void InitializeIM() { osLib_addFunction("coreinit", "IMIsAPDEnabledBySysSettings", coreinitExport_IMIsAPDEnabledBySysSettings); osLib_addFunction("coreinit", "IMGetTimeBeforeAPD", coreinitExport_IMGetTimeBeforeAPD); osLib_addFunction("coreinit", "IMGetTimeBeforeDimming", coreinitExport_IMGetTimeBeforeDimming); osLib_addFunction("coreinit", "IMEnableDim", coreinitExport_IMEnableDim); osLib_addFunction("coreinit", "IMIsDimEnabled", coreinitExport_IMIsDimEnabled); osLib_addFunction("coreinit", "IMGetAPDPeriod", coreinitExport_IMGetAPDPeriod); osLib_addFunction("coreinit", "IM_GetHomeButtonParams", coreinitExport_IM_GetHomeButtonParams); osLib_addFunction("coreinit", "IM_GetParameter", coreinitExport_IM_GetParameter); osLib_addFunction("coreinit", "IM_GetRuntimeParameter", coreinitExport_IM_GetRuntimeParameter); } }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_IM.h ================================================ #pragma once namespace coreinit { void InitializeIM(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_IOS.cpp ================================================ #include "Cafe/OS/libs/coreinit/coreinit_IOS.h" #include "Cafe/IOSU/legacy/iosu_ioctl.h" // superseded by coreinit_IPC.cpp/h sint32 __depr__IOS_Open(char* path, uint32 mode) { sint32 iosDevice = 0; if (path == nullptr) { iosDevice = 0; } else { if (strcmp(path, IOS_PATH_ODM) == 0) iosDevice = IOS_DEVICE_ODM; else if (strcmp(path, IOS_PATH_SOCKET) == 0) iosDevice = IOS_DEVICE_SOCKET; else if (strcmp(path, IOS_PATH_ACT) == 0) iosDevice = IOS_DEVICE_ACT; else if (strcmp(path, IOS_PATH_FPD) == 0) iosDevice = IOS_DEVICE_FPD; else if (strcmp(path, IOS_PATH_ACP_MAIN) == 0) iosDevice = IOS_DEVICE_ACP_MAIN; else if (strcmp(path, IOS_PATH_MCP) == 0) iosDevice = IOS_DEVICE_MCP; else if (strcmp(path, IOS_PATH_BOSS) == 0) iosDevice = IOS_DEVICE_BOSS; else if (strcmp(path, IOS_PATH_NIM) == 0) iosDevice = IOS_DEVICE_NIM; else if (strcmp(path, IOS_PATH_IOSUHAX) == 0) return -1; else iosDevice = IOS_DEVICE_UKN; } return iosDevice; } sint32 __depr__IOS_Ioctl(uint32 fd, uint32 request, void* inBuffer, uint32 inSize, void* outBuffer, uint32 outSize) { switch (fd) { case IOS_DEVICE_ODM: { // Home Menu uses ioctl cmd 5 on startup and then repeats cmd 4 every frame if (request == 4) { // check drive state debug_printf("checkDriveState()\n"); *(uint32be*)outBuffer = 0xA; } else { debug_printf("odm unsupported ioctl %d\n", request); } break; } default: { // todo cemuLog_logDebug(LogType::Force, "Unsupported Ioctl command"); } } return 0; } sint32 __depr__IOS_Ioctlv(uint32 fd, uint32 request, uint32 countIn, uint32 countOut, ioBufferVector_t* ioBufferVectors) { StackAllocator<ioQueueEntry_t> _queueEntryBuf; ioQueueEntry_t* queueEntry = _queueEntryBuf.GetPointer(); queueEntry->isIoctlv = true; queueEntry->isAsync = false; queueEntry->request = request; queueEntry->countIn = countIn; queueEntry->countOut = countOut; queueEntry->bufferVectors = ioBufferVectors; queueEntry->ppcThread = nullptr; queueEntry->returnValue = 0; queueEntry->isCompleted = false; sint32 r = iosuIoctl_pushAndWait(fd, queueEntry); return r; } sint32 __depr__IOS_Close(uint32 fd) { return 0; } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_IOS.h ================================================ // IOS typedef struct _ioBufferVector_t ioBufferVector_t; sint32 __depr__IOS_Open(char* path, uint32 mode); sint32 __depr__IOS_Ioctl(uint32 fd, uint32 request, void* inBuffer, uint32 inSize, void* outBuffer, uint32 outSize); sint32 __depr__IOS_Ioctlv(uint32 fd, uint32 request, uint32 countIn, uint32 countOut, ioBufferVector_t* ioBufferVectors); sint32 __depr__IOS_Close(uint32 fd); // superseded by coreinit_IPC.h ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/IOSU/kernel/iosu_kernel.h" #include "Cafe/HW/Espresso/Const.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "coreinit_MessageQueue.h" #include "coreinit_IPC.h" namespace coreinit { static constexpr inline size_t IPC_NUM_RESOURCE_BUFFERS = 0x30; struct IPCResourceBuffer { IPCCommandBody commandBody; uint8 bufferData[0x80 - 0x48]; }; static_assert(sizeof(IPCCommandBody) == 0x48); static_assert(sizeof(IPCResourceBuffer) == 0x80); struct IPCResourceBufferDescriptor { /* +0x00 */ uint32be IsAllocated; /* +0x04 */ MEMPTR<OSMessageQueue> asyncMsgQueue; // optional, if set a message will be sent to this queue... /* +0x08 */ MEMPTR<void> asyncResultFunc; // ...otherwise this is checked and delegated to the IPC threads. If false, only eventSynchronousIPC will be signaled. If true, a message will be sent to the per-core ipc queue /* +0x0C */ MEMPTR<void> asyncResultUserParam; /* +0x10 */ uint32 ukn10; /* +0x14 */ MEMPTR<IPCResourceBuffer> resourcePtr; /* +0x18 */ OSEvent eventSynchronousIPC; }; static_assert(sizeof(IPCResourceBufferDescriptor) == 0x3C); struct IPCBufferFIFO { sint32be writeIndex; sint32be readIndex; sint32be numQueuedEntries; sint32be mostQueuedEntries; MEMPTR<IPCResourceBufferDescriptor> ringbufferArray[IPC_NUM_RESOURCE_BUFFERS]; void Init() { writeIndex = 0; readIndex = -1; numQueuedEntries = 0; mostQueuedEntries = 0; for (size_t i = 0; i < IPC_NUM_RESOURCE_BUFFERS; i++) ringbufferArray[i] = nullptr; } void Push(IPCResourceBufferDescriptor* descriptor) { cemu_assert(readIndex != writeIndex); // if equal, fifo is full (should not happen as there not more buffers than ringbuffer entries) ringbufferArray[writeIndex] = descriptor; if (readIndex < 0) readIndex = writeIndex; writeIndex = (writeIndex + 1) % IPC_NUM_RESOURCE_BUFFERS; ++numQueuedEntries; if (numQueuedEntries > mostQueuedEntries) mostQueuedEntries = numQueuedEntries; } IPCResourceBufferDescriptor* Pop() { if (numQueuedEntries == 0) return nullptr; IPCResourceBufferDescriptor* r = ringbufferArray[readIndex].GetPtr(); --numQueuedEntries; if (numQueuedEntries == 0) readIndex = -1; else readIndex = (readIndex + 1) % IPC_NUM_RESOURCE_BUFFERS; return r; } }; struct IPCDriverCOSKernelCommunicationArea { uint32be numAvailableResponses; MEMPTR<IPCCommandBody> responseArray[11]; }; static_assert(sizeof(IPCDriverCOSKernelCommunicationArea) == 0x30); struct alignas(64) IPCDriver { betype<IPCDriverState> state; uint32 ukn04; uint32 coreIndex; uint32 writeIndexCmd020; MEMPTR<IPCResourceBuffer> resourceBuffers; /* 0x16C */ IPCBufferFIFO fifoFreeBuffers; /* 0x23C */ IPCBufferFIFO fifoBuffersInFlight; /* 0x334 */ uint32 resourceBuffersInitialized; /* 0x338 */ IPCDriverCOSKernelCommunicationArea kernelSharedArea; // this is passed to system call 0x1E00 (IPCOpen) /* 0x3FC */ IPCResourceBufferDescriptor resBufferDescriptor[IPC_NUM_RESOURCE_BUFFERS]; }; //static_assert(sizeof(IPCDriverInstance) == 0x1740); SysAllocator<IPCResourceBuffer, IPC_NUM_RESOURCE_BUFFERS * Espresso::CORE_COUNT, 0x40> s_ipcResourceBuffers; SysAllocator<IPCDriver, Espresso::CORE_COUNT, 0x40> s_ipcDriver; IPCDriver& IPCDriver_GetByCore(uint32 coreIndex) { cemu_assert_debug(coreIndex >= 0 && coreIndex < (uint32)Espresso::CORE_COUNT); return s_ipcDriver[coreIndex]; } void IPCDriver_InitForCore(uint32 coreIndex) { IPCDriver& ipcDriver = IPCDriver_GetByCore(coreIndex); ipcDriver.coreIndex = coreIndex; ipcDriver.state = IPCDriverState::INITIALIZED; ipcDriver.resourceBuffers = s_ipcResourceBuffers.GetPtr() + IPC_NUM_RESOURCE_BUFFERS * coreIndex; ipcDriver.resourceBuffersInitialized = 0; // setup resource descriptors for (size_t i = 0; i < IPC_NUM_RESOURCE_BUFFERS; i++) { ipcDriver.resBufferDescriptor[i].resourcePtr = ipcDriver.resourceBuffers.GetPtr() + i; ipcDriver.resBufferDescriptor[i].asyncResultFunc = nullptr; ipcDriver.resBufferDescriptor[i].asyncResultUserParam = nullptr; } ipcDriver.resourceBuffersInitialized = 1; // setup resource buffer FIFOs ipcDriver.fifoFreeBuffers.Init(); ipcDriver.fifoBuffersInFlight.Init(); for (size_t i = 0; i < IPC_NUM_RESOURCE_BUFFERS; i++) ipcDriver.fifoFreeBuffers.Push(ipcDriver.resBufferDescriptor + i); } IPCResourceBufferDescriptor* IPCDriver_AllocateResource(IPCDriver* ipcDriver, IOSDevHandle devHandle, IPCCommandId cmdId, OSMessageQueue* asyncMessageQueue, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam) { cemu_assert_debug(ipcDriver->coreIndex == OSGetCoreId()); IPCResourceBufferDescriptor* descriptor = nullptr; while (true) { descriptor = ipcDriver->fifoFreeBuffers.Pop(); if (!descriptor) { cemuLog_log(LogType::Force, "IPCDriver: Exceeded free resources"); OSYieldThread(); cemu_assert_unimplemented(); // we should wait for an event instead of busylooping } else break; } cemu_assert_debug(descriptor >= ipcDriver->resBufferDescriptor && descriptor < ipcDriver->resBufferDescriptor + IPC_NUM_RESOURCE_BUFFERS); cemu_assert_debug(descriptor->resourcePtr.GetPtr() >= ipcDriver->resourceBuffers.GetPtr() && descriptor->resourcePtr.GetPtr() < (ipcDriver->resourceBuffers.GetPtr() + IPC_NUM_RESOURCE_BUFFERS)); IPCResourceBuffer* res = descriptor->resourcePtr; IPCCommandBody& cmdBody = res->commandBody; descriptor->IsAllocated = 1; descriptor->asyncMsgQueue = asyncMessageQueue; descriptor->asyncResultFunc = asyncResultFunc; descriptor->asyncResultUserParam = asyncResultUserParam; cmdBody.cmdId = cmdId; cmdBody.ukn0C = 0; cmdBody.ukn14 = 0; cmdBody.result = 0; cmdBody.devHandle = devHandle; return descriptor; } void IPCDriver_ReleaseResource(IPCDriver* ipcDriver, IPCResourceBufferDescriptor* requestDescriptor) { requestDescriptor->IsAllocated = 0; ipcDriver->fifoFreeBuffers.Push(requestDescriptor); } /* IPC threads */ SysAllocator<OSThread_t, Espresso::CORE_COUNT> gIPCThread; SysAllocator<uint8, 0x4000 * Espresso::CORE_COUNT> _gIPCThreadStack; SysAllocator<uint8, 0x18 * Espresso::CORE_COUNT> _gIPCThreadNameStorage; SysAllocator<OSMessageQueue, Espresso::CORE_COUNT> gIPCThreadMsgQueue; SysAllocator<OSMessage, Espresso::CORE_COUNT * IPC_NUM_RESOURCE_BUFFERS> _gIPCThreadSemaphoreStorage; // handler thread for asynchronous callbacks for IPC responses void __IPCDriverThreadFunc(PPCInterpreter_t* hCPU) { uint32 coreIndex = OSGetCoreId(); while (true) { OSMessage msg; OSReceiveMessage(gIPCThreadMsgQueue.GetPtr() + coreIndex, &msg, OS_MESSAGE_BLOCK); cemu_assert(msg.data2 == 1); // type must be callback MEMPTR<void> cbFunc{ msg.message }; cemu_assert(cbFunc != nullptr); PPCCoreCallback(cbFunc.GetPtr(), (uint32)msg.data0, (uint32)msg.data1); } osLib_returnFromFunction(hCPU, 0); } void IPCDriver_InitIPCThread(uint32 coreIndex) { // create a thread with 0x4000 stack space // and a message queue large enough to hold the maximum number of commands (IPC_NUM_RESOURCE_BUFFERS) OSInitMessageQueue(gIPCThreadMsgQueue.GetPtr() + coreIndex, _gIPCThreadSemaphoreStorage.GetPtr() + coreIndex * IPC_NUM_RESOURCE_BUFFERS, IPC_NUM_RESOURCE_BUFFERS); OSThread_t* ipcThread = gIPCThread.GetPtr() + coreIndex; __OSCreateThreadType(ipcThread, PPCInterpreter_makeCallableExportDepr(__IPCDriverThreadFunc), 0, nullptr, _gIPCThreadStack.GetPtr() + 0x4000 * coreIndex + 0x4000, 0x4000, 15, (1 << coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER); sprintf((char*)_gIPCThreadNameStorage.GetPtr()+coreIndex*0x18, "{SYS IPC Core %d}", coreIndex); OSSetThreadName(ipcThread, (char*)_gIPCThreadNameStorage.GetPtr() + coreIndex * 0x18); OSResumeThread(ipcThread); } /* coreinit IOS_* API */ void _IPCDriver_SubmitCmdAllQueued(IPCDriver& ipcDriver) { // on COS, submitted commands first go to the COS kernel via syscall 0x2000, where they are processed, copied and queued again // we skip all of this and just pass our IPC commands directly to the IOSU kernel handler HLE function // important: IOSU needs to know which PPC core sent the command, so that it can also notify the same core about the result ipcDriver.state = IPCDriverState::SUBMITTING; while (true) { IPCResourceBufferDescriptor* res = ipcDriver.fifoBuffersInFlight.Pop(); if (!res) break; // resolve pointers switch (res->resourcePtr->commandBody.cmdId) { case IPCCommandId::IOS_OPEN: res->resourcePtr->commandBody.args[0] = res->resourcePtr->commandBody.ppcVirt0.GetMPTR(); break; case IPCCommandId::IOS_CLOSE: break; case IPCCommandId::IOS_IOCTL: res->resourcePtr->commandBody.args[1] = res->resourcePtr->commandBody.ppcVirt0.GetMPTR(); res->resourcePtr->commandBody.args[3] = res->resourcePtr->commandBody.ppcVirt1.GetMPTR(); break; case IPCCommandId::IOS_IOCTLV: res->resourcePtr->commandBody.args[3] = res->resourcePtr->commandBody.ppcVirt0.GetMPTR(); break; default: cemu_assert_unimplemented(); break; } iosu::kernel::IPCSubmitFromCOS(ipcDriver.coreIndex, &res->resourcePtr->commandBody); } ipcDriver.state = IPCDriverState::READY; } void _IPCDriver_SubmitCmd(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor) { if (requestDescriptor->asyncResultFunc == nullptr) OSInitEvent(&requestDescriptor->eventSynchronousIPC, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO); ipcDriver.fifoBuffersInFlight.Push(requestDescriptor); cemu_assert_debug(ipcDriver.state == IPCDriverState::READY || ipcDriver.state == IPCDriverState::INITIALIZED); _IPCDriver_SubmitCmdAllQueued(ipcDriver); } uint32 _IPCDriver_WaitForResultAndRelease(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor) { OSWaitEvent(&requestDescriptor->eventSynchronousIPC); uint32 r = requestDescriptor->resourcePtr->commandBody.result; IPCDriver_ReleaseResource(&ipcDriver, requestDescriptor); return r; } void IPCDriver_HandleResponse(IPCDriver& ipcDriver, IPCCommandBody* res, uint32 ppcCoreIndex) { size_t index = (IPCResourceBuffer*)res - ipcDriver.resourceBuffers.GetPtr(); cemu_assert(index < IPC_NUM_RESOURCE_BUFFERS); IPCResourceBufferDescriptor* descriptor = ipcDriver.resBufferDescriptor + index; cemu_assert(descriptor->IsAllocated != 0); if (descriptor->asyncMsgQueue != nullptr) { OSMessage msg; msg.message = 0; msg.data0 = res->result; msg.data1 = descriptor->asyncResultUserParam.GetMPTR(); msg.data2 = 0; sint32 r = OSSendMessage(descriptor->asyncMsgQueue.GetPtr(), &msg, 0); cemu_assert(r != 0); IPCDriver_ReleaseResource(&ipcDriver, descriptor); return; } if (descriptor->asyncResultFunc != nullptr) { OSMessage msg; msg.message = descriptor->asyncResultFunc.GetMPTR(); msg.data0 = res->result; msg.data1 = descriptor->asyncResultUserParam.GetMPTR(); msg.data2 = 1; sint32 r = OSSendMessage(gIPCThreadMsgQueue.GetPtr() + ppcCoreIndex, &msg, 0); cemu_assert(r != 0); IPCDriver_ReleaseResource(&ipcDriver, descriptor); return; } // signal event for synchronous IPC OSSignalEvent(&descriptor->eventSynchronousIPC); } // handles responses queued in shared region void IPCDriver_KernelCallback(IPCDriver& ipcDriver) { cemu_assert_debug(ipcDriver.kernelSharedArea.numAvailableResponses != 0); for (uint32 i = 0; i < ipcDriver.kernelSharedArea.numAvailableResponses; i++) IPCDriver_HandleResponse(ipcDriver, ipcDriver.kernelSharedArea.responseArray[i], ipcDriver.coreIndex); ipcDriver.kernelSharedArea.numAvailableResponses = 0; } // called by our HLE'd IOSU directly void IPCDriver_NotifyResponses(uint32 ppcCoreIndex, IPCCommandBody** responseArray, uint32 numResponses) { cemu_assert(numResponses < 11); IPCDriver& ipcDriver = IPCDriver_GetByCore(ppcCoreIndex); ipcDriver.kernelSharedArea.numAvailableResponses = numResponses; for (uint32 i = 0; i < numResponses; i++) ipcDriver.kernelSharedArea.responseArray[i] = responseArray[i]; IPCDriver_KernelCallback(ipcDriver); } void _IPCDriver_SetupCmd_IOSOpen(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor, const char* devicePath, uint32 flags) { // store the path in the buffer after the command body IPCResourceBuffer* resBuffer = requestDescriptor->resourcePtr; IPCCommandBody& cmdBody = resBuffer->commandBody; uint8* buffer = resBuffer->bufferData; size_t pathLen = strlen(devicePath); if (pathLen > 31) { cemuLog_log(LogType::Force, "IOS_Open(): Device path must not exceed 31 characters"); cemu_assert_error(); } memcpy(buffer, devicePath, pathLen + 1); cmdBody.ppcVirt0 = MEMPTR<void>(buffer).GetMPTR(); cmdBody.args[0] = 0; cmdBody.args[1] = (uint32)(pathLen + 1); cmdBody.args[2] = flags; } IOS_ERROR _IPCDriver_SetupCmd_IOSIoctl(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut) { IPCCommandBody& cmdBody = requestDescriptor->resourcePtr->commandBody; cmdBody.args[0] = requestId; cmdBody.args[1] = MPTR_NULL; // set to ppcVirt0 later cmdBody.args[2] = sizeIn; cmdBody.args[3] = MPTR_NULL; // set to ppcVirt1 later cmdBody.args[4] = sizeOut; cmdBody.ppcVirt0 = MEMPTR<void>(ptrIn).GetMPTR(); cmdBody.ppcVirt1 = MEMPTR<void>(ptrOut).GetMPTR(); return IOS_ERROR_OK; } IOS_ERROR _IPCDriver_SetupCmd_IOSIoctlv(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec) { IPCCommandBody& cmdBody = requestDescriptor->resourcePtr->commandBody; // set args cmdBody.ppcVirt0 = MEMPTR<void>(vec).GetMPTR(); cmdBody.args[0] = requestId; cmdBody.args[1] = numIn; cmdBody.args[2] = numOut; cmdBody.args[3] = 0; // set to ppcVirt0 later return IOS_ERROR_OK; } IOSDevHandle IOS_Open(const char* devicePath, uint32 flags) { IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, 0, IPCCommandId::IOS_OPEN, nullptr, nullptr, nullptr); _IPCDriver_SetupCmd_IOSOpen(ipcDriver, ipcDescriptor, devicePath, flags); _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); uint32 r = _IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor); return r; } IOS_ERROR IOS_Close(IOSDevHandle devHandle) { IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_CLOSE, nullptr, nullptr, nullptr); _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); IOS_ERROR r = (IOS_ERROR)_IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor); return r; } IOS_ERROR IOS_Ioctl(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut) { IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTL, nullptr, nullptr, nullptr); IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctl(ipcDriver, ipcDescriptor, requestId, ptrIn, sizeIn, ptrOut, sizeOut); if (r != IOS_ERROR_OK) { cemuLog_log(LogType::Force, "IOS_Ioctl failed due to bad parameters"); IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor); return r; } _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); r = (IOS_ERROR)_IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor); return r; } IOS_ERROR IOS_IoctlAsync(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam) { IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTL, nullptr, asyncResultFunc, asyncResultUserParam); IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctl(ipcDriver, ipcDescriptor, requestId, ptrIn, sizeIn, ptrOut, sizeOut); if (r != IOS_ERROR_OK) { cemuLog_log(LogType::Force, "IOS_Ioctl failed due to bad parameters"); IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor); return r; } _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); return r; } IOS_ERROR IOS_Ioctlv(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec) { IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTLV, nullptr, nullptr, nullptr); IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctlv(ipcDriver, ipcDescriptor, requestId, numIn, numOut, vec); if (r != IOS_ERROR_OK) { cemuLog_log(LogType::Force, "IOS_Ioctlv failed due to bad parameters"); IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor); return r; } _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); r = (IOS_ERROR)_IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor); return r; } IOS_ERROR IOS_IoctlvAsync(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam) { IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTLV, nullptr, asyncResultFunc, asyncResultUserParam); IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctlv(ipcDriver, ipcDescriptor, requestId, numIn, numOut, vec); if (r != IOS_ERROR_OK) { cemuLog_log(LogType::Force, "IOS_Ioctlv failed due to bad parameters"); IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor); return r; } _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); return r; } void MapIPCExports() { cafeExportRegister("coreinit", IOS_Open, LogType::PPC_IPC); cafeExportRegister("coreinit", IOS_Close, LogType::PPC_IPC); cafeExportRegister("coreinit", IOS_Ioctl, LogType::PPC_IPC); cafeExportRegister("coreinit", IOS_IoctlAsync, LogType::PPC_IPC); cafeExportRegister("coreinit", IOS_Ioctlv, LogType::PPC_IPC); cafeExportRegister("coreinit", IOS_IoctlvAsync, LogType::PPC_IPC); } void InitializeIPC() { for (uint32 i = 0; i < Espresso::CORE_COUNT; i++) { IPCDriver_InitForCore(i); IPCDriver_InitIPCThread(i); } } }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_IPC.h ================================================ #include "Cafe/IOSU/iosu_ipc_common.h" #include "Cafe/IOSU/iosu_types_common.h" namespace coreinit { void IPCDriver_NotifyResponses(uint32 ppcCoreIndex, IPCCommandBody** responseArray, uint32 numResponses); IOSDevHandle IOS_Open(const char* devicePath, uint32 flags); IOS_ERROR IOS_Close(IOSDevHandle devHandle); IOS_ERROR IOS_Ioctl(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut); IOS_ERROR IOS_IoctlAsync(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam); IOS_ERROR IOS_Ioctlv(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec); IOS_ERROR IOS_IoctlvAsync(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam); void MapIPCExports(); void InitializeIPC(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "coreinit_IPCBuf.h" namespace coreinit { void FIFOInit(IPCFifo_t* fifo, uint32 entryCount, void* entryArray) { fifo->entryCount = entryCount; fifo->entryArray = (FIFOEntry_t*)entryArray; fifo->writeIndex = 0; fifo->readIndex = -1; fifo->availableEntries = 0; } sint32 FIFOPush(IPCFifo_t* fifo, void* entry) { if (fifo->readIndex == fifo->writeIndex) { assert_dbg(); return -8; } fifo->entryArray[(uint32)fifo->writeIndex].p = (uint8*)entry; if ((sint32)fifo->readIndex < 0) { // set readIndex to valid value when fifo was empty fifo->readIndex = fifo->writeIndex; } fifo->availableEntries = (uint32)fifo->availableEntries + 1; fifo->writeIndex = ((uint32)fifo->writeIndex + 1) % (uint32)fifo->entryCount; return 0; } sint32 FIFOPop(IPCFifo_t* fifo, uint8** entry) { *entry = nullptr; if ((sint32)fifo->readIndex < 0) return -7; fifo->availableEntries = (uint32)fifo->availableEntries - 1; *entry = fifo->entryArray[(uint32)fifo->readIndex].p.GetPtr(); fifo->readIndex = ((uint32)fifo->readIndex + 1) % (uint32)fifo->entryCount; if (fifo->availableEntries == (uint32be)0) { fifo->readIndex = -1; } return 0; } void* getUpwardsAlignedAddr(void* addr, uint32 alignment) { uint64 v = ((uint64)addr + alignment - 1) & ~(uint64)(alignment - 1); return (uint8*)v; } void* getDownwardsAlignedAddr(void* addr, uint32 alignment) { uint64 v = ((uint64)addr) & ~(uint64)(alignment - 1); return (uint8*)v; } IPCBufPool_t* IPCBufPoolCreate(uint8* bufferArea, uint32 bufferSize, uint32 entrySize, uint32be* entryCountOutput, uint32 uknR7) { memset(bufferArea, 0, bufferSize); IPCBufPool_t* ipcBufPool = (IPCBufPool_t*)getUpwardsAlignedAddr(bufferArea, 4); uint8* alignedEnd = (uint8*)getDownwardsAlignedAddr(bufferArea + bufferSize, 4); uint8* dataStart = (uint8*)getUpwardsAlignedAddr(ipcBufPool + 1, 0x4); *entryCountOutput = 0; // todo: Validate parameters OSInitMutexEx(&ipcBufPool->mutex, NULL); ipcBufPool->fullBufferPtr = bufferArea; ipcBufPool->fullBufferSize = bufferSize; ipcBufPool->uknFromParamR7 = uknR7; uint32 paddedEntrySize = (entrySize + 0x3F) & ~0x3F; ipcBufPool->ukn10 = 0; ipcBufPool->magic = 0xBADF00D; ipcBufPool->entrySize1 = paddedEntrySize; ipcBufPool->entrySize2 = paddedEntrySize; uint32 remainingSize = (uint32)((bufferArea + bufferSize) - dataStart); uint32 entryCount = remainingSize / paddedEntrySize; if (entryCount <= 1) assert_dbg(); ipcBufPool->entryCountMul4 = entryCount * 4; if ((uint32)ipcBufPool->entryCountMul4 >= paddedEntrySize) { // special handling required (need to adjust entry count?) assert_dbg(); } else { entryCount--; // remove one entry to make room for entry pointer array ipcBufPool->entryCount = entryCount; *entryCountOutput = entryCount; ipcBufPool->entryStartPtr = (dataStart + (uint32)ipcBufPool->entryCountMul4); FIFOInit(&ipcBufPool->fifo, (uint32)ipcBufPool->entryCount, dataStart); } // add all entries to the fifo for (sint32 i = 0; i < (sint32)ipcBufPool->entryCount; i++) { uint8* entry = ipcBufPool->entryStartPtr.GetPtr() + i * (uint32)ipcBufPool->entrySize2; if (FIFOPush(&ipcBufPool->fifo, entry) != 0) return nullptr; } return ipcBufPool; } uint8* IPCBufPoolAllocate(IPCBufPool_t* ipcBufPool, uint32 size) { uint8* entry = nullptr; OSLockMutex(&ipcBufPool->mutex); if (ipcBufPool->magic == (uint32be)0xBADF00D && size <= (uint32)ipcBufPool->entrySize1) { FIFOPop(&ipcBufPool->fifo, &entry); } else { assert_dbg(); } OSUnlockMutex(&ipcBufPool->mutex); return entry; } sint32 IPCBufPoolFree(IPCBufPool_t* ipcBufPool, uint8* entry) { OSLockMutex(&ipcBufPool->mutex); sint32 res = 0; if (ipcBufPool->magic == (uint32be)0xBADF00D) { // check if entry is actually part of the pool uint32 offset = (uint32)(entry - (uint8*)ipcBufPool->entryStartPtr.GetPtr()); if ((offset % (uint32)ipcBufPool->entrySize2) != 0) assert_dbg(); uint32 index = offset / (uint32)ipcBufPool->entrySize2; if ((index >= (uint32)ipcBufPool->entryCount)) assert_dbg(); FIFOPush(&ipcBufPool->fifo, entry); } else { assert_dbg(); res = -4; } OSUnlockMutex(&ipcBufPool->mutex); return res; } void coreinitExport_IPCBufPoolCreate(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(bufferArea, uint8, 0); ppcDefineParamU32(bufferSize, 1); ppcDefineParamU32(entrySize, 2); ppcDefineParamU32BEPtr(entryCountOutput, 3); ppcDefineParamU32(uknR7, 4); IPCBufPool_t* ipcBufPool = IPCBufPoolCreate(bufferArea, bufferSize, entrySize, entryCountOutput, uknR7); osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(ipcBufPool)); return; // example dump of IPC buffer (at 0x1011FF40): // 0000 0B AD F0 0D 10 11 FF 40 00 00 53 01 00 00 00 01 00 00 00 00 00 00 02 C0 00 00 02 C0 00 00 00 1D // 0020 10 12 00 40 00 00 00 78 00 00 00 00 00 00 00 00 00 00 00 1D 00 00 00 1D 10 11 FF A8 6D 55 74 58 // 0040 10 00 87 2C 00 00 00 00 00 00 00 00 00 00 00 00 10 11 FF 7C 00 00 00 00 00 00 00 00 00 00 00 00 // 0060 00 00 00 00 00 00 00 00 10 12 00 40 10 12 03 00 10 12 05 C0 10 12 08 80 10 12 0B 40 10 12 0E 00 // 0080 10 12 10 C0 10 12 13 80 10 12 16 40 10 12 19 00 10 12 1B C0 10 12 1E 80 10 12 21 40 10 12 24 00 // 00A0 10 12 26 C0 10 12 29 80 10 12 2C 40 10 12 2F 00 10 12 31 C0 10 12 34 80 10 12 37 40 10 12 3A 00 // 00C0 10 12 3C C0 10 12 3F 80 10 12 42 40 10 12 45 00 10 12 47 C0 10 12 4A 80 10 12 4D 40 00 00 00 00 // 00E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 } void coreinitExport_IPCBufPoolAllocate(PPCInterpreter_t* hCPU) { debug_printf("IPCBufPoolAllocate(0x%08x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamStructPtr(ipcBufPool, IPCBufPool_t, 0); ppcDefineParamU32(size, 1); uint8* entry = IPCBufPoolAllocate(ipcBufPool, size); osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(entry)); } void coreinitExport_IPCBufPoolFree(PPCInterpreter_t* hCPU) { debug_printf("IPCBufPoolFree(0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamStructPtr(ipcBufPool, IPCBufPool_t, 0); ppcDefineParamTypePtr(entry, uint8, 1); sint32 res = IPCBufPoolFree(ipcBufPool, entry); osLib_returnFromFunction(hCPU, res); } void InitializeIPCBuf() { osLib_addFunction("coreinit", "IPCBufPoolCreate", coreinitExport_IPCBufPoolCreate); osLib_addFunction("coreinit", "IPCBufPoolAllocate", coreinitExport_IPCBufPoolAllocate); osLib_addFunction("coreinit", "IPCBufPoolFree", coreinitExport_IPCBufPoolFree); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.h ================================================ #pragma once namespace coreinit { struct FIFOEntry_t { MEMPTR<uint8> p; }; struct IPCFifo_t { uint32be writeIndex; uint32be readIndex; uint32be availableEntries; // number of available entries uint32be entryCount; MEMPTR<FIFOEntry_t> entryArray; }; struct IPCBufPool_t { /* +0x00 */ uint32be magic; /* +0x04 */ MEMPTR<void> fullBufferPtr; /* +0x08 */ uint32be fullBufferSize; /* +0x0C */ uint32be uknFromParamR7; // boolean? /* +0x10 */ uint32be ukn10; // set to zero on init /* +0x14 */ uint32be entrySize1; /* +0x18 */ uint32be entrySize2; // set to same value as entrySize1 /* +0x1C */ uint32be entryCount; // actual number of used entries /* +0x20 */ MEMPTR<uint8> entryStartPtr; /* +0x24 */ uint32be entryCountMul4; /* +0x28 */ IPCFifo_t fifo; /* +0x3C */ coreinit::OSMutex mutex; /* +0x68 */ uint32 ukn68; // full size is 0x6C }; static_assert(sizeof(IPCBufPool_t) == 0x6C); uint8* IPCBufPoolAllocate(IPCBufPool_t* ipcBufPool, uint32 size); IPCBufPool_t* IPCBufPoolCreate(uint8* bufferArea, uint32 bufferSize, uint32 entrySize, uint32be* entryCountOutput, uint32 uknR7); sint32 IPCBufPoolFree(IPCBufPool_t* ipcBufPool, uint8* entry); void InitializeIPCBuf(); } // namespace coreinit ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Init.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/coreinit/coreinit.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/padscore/padscore.h" #include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/vpad/vpad.h" #include "Cafe/OS/libs/coreinit/coreinit_GHS.h" #include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "Cafe/OS/libs/coreinit/coreinit_FG.h" #include "Cafe/CafeSystem.h" #include "Cafe/GraphicPack/GraphicPack2.h" extern MPTR _entryPoint; extern RPLModule* applicationRPX; typedef struct { MPTR argv[32]; uint32be argc; char argStorage[0x1000]; }coreinitInit_t; coreinitInit_t* _coreinitInfo = nullptr; MPTR OSAllocFromSystem(uint32 size, uint32 alignment) { return coreinit_allocFromSysArea(size, alignment); } void OSFreeToSystem(MPTR mem) { coreinit_freeToSysArea(mem); } extern std::string _pathToExecutable; sint32 argStorageIndex; void _AddArg(const char* arg, sint32 len) { uint32 argc = _coreinitInfo->argc; char* argStorageStr = _coreinitInfo->argStorage + argStorageIndex; memcpy(argStorageStr, arg, len); argStorageStr[len] = '\0'; argStorageIndex += (sint32)strlen(arg) + 1; _coreinitInfo->argv[argc] = _swapEndianU32(memory_getVirtualOffsetFromPointer(argStorageStr)); _coreinitInfo->argc = argc+1; } sint32 _GetArgLength(const char* arg) { sint32 c = 0; while (*arg) { if (*arg == ' ') break; // end at whitespace cemu_assert_debug(*arg != '\"' && *arg != '\''); // todo arg++; c++; } return c; } static std::string GetLaunchArgs() { std::string argStr = CafeSystem::GetForegroundTitleArgStr(); if(std::vector<std::string> overrideArgs; CafeSystem::GetOverrideArgStr(overrideArgs)) { // args are overriden by launch directive (OSLaunchTitleByPath) // keep the rpx path but use the arguments from the override if (size_t pos = argStr.find(' '); pos != std::string::npos) argStr.resize(pos); for(size_t i=0; i<overrideArgs.size(); i++) { argStr += " "; argStr += overrideArgs[i]; } } return argStr; } void CafeInit() { // extract executable filename sint32 rpxPathStart = (sint32)_pathToExecutable.size() - 1; if (rpxPathStart > 0) { while (rpxPathStart > 0 && _pathToExecutable[rpxPathStart-1] != '/') rpxPathStart--; } else { rpxPathStart = 0; } std::string_view rpxFileName(_pathToExecutable.data() + rpxPathStart, _pathToExecutable.size() - rpxPathStart); argStorageIndex = 0; _coreinitInfo->argc = 0; _AddArg(rpxFileName.data(), rpxFileName.size()); strcpy((char*)_coreinitInfo->argStorage, std::string(rpxFileName).c_str()); std::string _argStr = GetLaunchArgs(); CafeSystem::UnsetOverrideArgs(); // make sure next launch doesn't accidentally use the same arguments const char* argString = _argStr.c_str(); // attach parameters from arg string if (argString && argString[0] != '\0') { const char* t = strstr(argString, ".rpx"); if (t) { t += 4; while (*t) { // skip all whitespace while (*t) { if (*t == ' ') { t++; } else break; } // get length of arg sint32 argLength = _GetArgLength(t); if (argLength > 0) { // add arg _AddArg(t, argLength); // next t += argLength; } } } else { cemuLog_logDebug(LogType::Force, "Unable to find end of rpx file name in arg string"); } } // setup UGQR PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->spr.UGQR[0 + 2] = 0x00040004; hCPU->spr.UGQR[0 + 3] = 0x00050005; hCPU->spr.UGQR[0 + 4] = 0x00060006; hCPU->spr.UGQR[0 + 5] = 0x00070007; coreinit::InitForegroundBucket(); coreinit::InitSysHeap(); } struct PreinitUserHeapStruct { MEMPTR<coreinit::MEMHeapBase> heapTempMEM1; MEMPTR<coreinit::MEMHeapBase> heapTempFG; MEMPTR<coreinit::MEMHeapBase> heapTempMEM2; }; SysAllocator<PreinitUserHeapStruct> g_preinitUserParam; void InitCafeHeaps() { // init default heaps g_preinitUserParam->heapTempMEM1 = nullptr; g_preinitUserParam->heapTempFG = nullptr; g_preinitUserParam->heapTempMEM2 = nullptr; coreinit::InitDefaultHeaps(g_preinitUserParam->heapTempMEM1, g_preinitUserParam->heapTempFG, g_preinitUserParam->heapTempMEM2); // if __preinit_user export exists in main executable, run it and pass our heaps MPTR exportAddr = applicationRPX ? RPLLoader_FindRPLExport(applicationRPX, "__preinit_user", false) : MPTR_NULL; if (exportAddr != MPTR_NULL) { PPCCoreCallback(exportAddr, &g_preinitUserParam->heapTempMEM1, &g_preinitUserParam->heapTempFG, &g_preinitUserParam->heapTempMEM2); } // setup heaps if (g_preinitUserParam->heapTempMEM1 != nullptr) coreinit::MEMSetBaseHeapHandle(0, g_preinitUserParam->heapTempMEM1); if (g_preinitUserParam->heapTempFG != nullptr) coreinit::MEMSetBaseHeapHandle(8, g_preinitUserParam->heapTempFG); if (g_preinitUserParam->heapTempMEM2 != nullptr) coreinit::MEMSetBaseHeapHandle(1, g_preinitUserParam->heapTempMEM2); } MPTR CoreInitEntry(sint32 argc, MPTR argv) { const char* rpxPath = (char*)memory_getPointerFromVirtualOffset(memory_readU32(argv + 0)); InitCafeHeaps(); // do a dummy allocation via the OSDynLoad allocator // Watch Dogs relies on this to correctly set up its malloc() allocator // must be larger than 0x190 to trigger creation of a new memory segment. But also must not be close to page alignment (0x1000) or else the bug will trigger void* dummyAlloc = coreinit::OSDynLoad_AllocatorAlloc(0x500, 0x4); if (dummyAlloc) coreinit::OSDynLoad_AllocatorFree(dummyAlloc); return _entryPoint; } sint32 _coreinitTitleEntryPoint; void coreinit_start(PPCInterpreter_t* hCPU) { _coreinitInfo = (coreinitInit_t*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sizeof(coreinitInit_t), 32)); memset(_coreinitInfo, 0, sizeof(coreinitInit_t)); CafeInit(); _coreinitTitleEntryPoint = CoreInitEntry(_coreinitInfo->argc, memory_getVirtualOffsetFromPointer(_coreinitInfo->argv)); RPLLoader_CallEntrypoints(); // init vpadbase (todo - simulate entrypoints for HLE modules) padscore::start(); vpad::start(); // call entry-type callbacks in graphic packs for (const auto gp : GraphicPack2::GetActiveGraphicPacks()) { for (const auto [callback, type] : gp->GetCallbacks()) { if (type == GPCallbackType::Entry) { PPCCoreCallback(callback); } } } // continue at main executable entrypoint hCPU->gpr[4] = memory_getVirtualOffsetFromPointer(_coreinitInfo->argv); hCPU->gpr[3] = _coreinitInfo->argc; hCPU->instructionPointer = _coreinitTitleEntryPoint; } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_LockedCache.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" /* Locked cache is mapped to the following memory regions: offset size core0: 0xFFC00000 0x4000 core1: 0xFFC40000 0x4000 core2: 0xFFC80000 0x4000 */ #define LC_LOCKED_CACHE_GRANULARITY (0x200) // 512B #define LC_LOCKED_CACHE_SIZE (0x4000) // 16KB #define LC_MASK_FREE (0) #define LC_MASK_RESERVED (1) #define LC_MASK_RESERVED_END (2) // indicates end of reserved block namespace coreinit { uint8 lcCacheMask[PPC_CORE_COUNT][(LC_LOCKED_CACHE_SIZE + LC_LOCKED_CACHE_GRANULARITY - 1) / LC_LOCKED_CACHE_GRANULARITY] = { 0 }; uint32 lcAllocatedBlocks[PPC_CORE_COUNT] = { 0 }; MPTR lcAddr[] = { 0xFFC00000, // core 0 0xFFC40000, // core 1 0xFFC80000 // core 2 }; void coreinitExport_LCGetMaxSize(PPCInterpreter_t* hCPU) { osLib_returnFromFunction(hCPU, LC_LOCKED_CACHE_SIZE); } void coreinitExport_LCAlloc(PPCInterpreter_t* hCPU) { //debug_printf("LCAlloc(0x%04x) Thread %08x Core %d", hCPU->gpr[3], coreinitThread_getCurrentThread(hCPU), PPCInterpreter_getCoreIndex(hCPU)); uint32 size = hCPU->gpr[3]; if (size == 0 || size > LC_LOCKED_CACHE_SIZE) { debug_printf("LCAlloc: Invalid alloc size %d\n", size); osLib_returnFromFunction(hCPU, MPTR_NULL); return; } if ((size % LC_LOCKED_CACHE_GRANULARITY) != 0) { debug_printf("LCAlloc: Unaligned alloc size 0x%04x\n", size); size += (LC_LOCKED_CACHE_GRANULARITY - 1); size &= ~(LC_LOCKED_CACHE_GRANULARITY - 1); } uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); uint8* cacheMask = lcCacheMask[coreIndex]; uint32 entryMaskLength = size / LC_LOCKED_CACHE_GRANULARITY; for (uint32 i = 0; i <= (LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY) - entryMaskLength; i++) { // check if range starting at i is free bool rangeIsFree = true; for (uint32 f = 0; f < entryMaskLength; f++) { if (cacheMask[i + f] != LC_MASK_FREE) { rangeIsFree = false; break; } } if (rangeIsFree) { // found space for allocation MPTR allocAddr = lcAddr[coreIndex] + i * LC_LOCKED_CACHE_GRANULARITY; // mark range as allocated for (uint32 f = 0; f < entryMaskLength - 1; f++) { cacheMask[i + f] = LC_MASK_RESERVED; } cacheMask[i + entryMaskLength - 1] = LC_MASK_RESERVED_END; // update allocation counter lcAllocatedBlocks[coreIndex] += entryMaskLength; // return allocAddr //debug_printf("LCAlloc result %08x Thread %08x Core %d", (uint32)allocAddr, coreinitThread_getCurrentThread(hCPU), PPCInterpreter_getCoreIndex(hCPU)); osLib_returnFromFunction(hCPU, allocAddr); return; } } // not enough space left //debug_printf("LCAlloc failed Thread %08x Core %d", coreinitThread_getCurrentThread(hCPU), PPCInterpreter_getCoreIndex(hCPU)); osLib_returnFromFunction(hCPU, MPTR_NULL); } void coreinitExport_LCDealloc(PPCInterpreter_t* hCPU) { uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); uint8* cacheMask = lcCacheMask[coreIndex]; //printf("LCDealloc(0x%08x)\n", hCPU->gpr[3]); MPTR deallocAddr = hCPU->gpr[3]; if (deallocAddr < lcAddr[coreIndex] || deallocAddr >= (lcAddr[coreIndex] + LC_LOCKED_CACHE_SIZE)) { // out of bounds #ifdef CEMU_DEBUG_ASSERT cemuLog_log(LogType::Force, "LCDealloc(): Out of bounds"); #endif osLib_returnFromFunction(hCPU, 0); return; } uint32 relativeOffset = (uint32)deallocAddr - lcAddr[coreIndex]; if ((relativeOffset % LC_LOCKED_CACHE_GRANULARITY) != 0) { debug_printf("LCDealloc: Offset alignment is invalid (0x%08x / 0x%08x)\n", deallocAddr, relativeOffset); osLib_returnFromFunction(hCPU, 0); return; } uint32 startIndex = relativeOffset / LC_LOCKED_CACHE_GRANULARITY; // check this is really the beginning of an allocated block if (startIndex > 0 && (cacheMask[startIndex - 1] != LC_MASK_RESERVED_END && cacheMask[startIndex - 1] != LC_MASK_FREE)) { debug_printf("LCDealloc: Offset is invalid (0x%08x / 0x%08x)\n", deallocAddr, relativeOffset); osLib_returnFromFunction(hCPU, 0); return; } // free range by reseting mask in allocated region for (uint32 i = startIndex; i < (LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY); i++) { if (cacheMask[i] == LC_MASK_RESERVED_END) { // end of allocated block reached cacheMask[i] = LC_MASK_FREE; // update allocation counter lcAllocatedBlocks[coreIndex]--; break; } else if (cacheMask[i] == LC_MASK_FREE) { debug_printf("LCDealloc: Allocation mask error detected\n"); break; } cacheMask[i] = LC_MASK_FREE; // update allocation counter lcAllocatedBlocks[coreIndex]--; } osLib_returnFromFunction(hCPU, 0); } void coreinitExport_LCGetUnallocated(PPCInterpreter_t* hCPU) { uint32 unallocatedBytes = 0; uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); unallocatedBytes = LC_LOCKED_CACHE_SIZE - (lcAllocatedBlocks[coreIndex] * LC_LOCKED_CACHE_GRANULARITY); //debug_printf("LCGetUnallocated() Result: 0x%x\n", unallocatedBytes); osLib_returnFromFunction(hCPU, unallocatedBytes); } void coreinitExport_LCGetAllocatableSize(PPCInterpreter_t* hCPU) { debug_printf("LCGetAllocatableSize()\n"); // no parameters, returns largest allocatable block size // get core LC parameters uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); uint8* cacheMask = lcCacheMask[coreIndex]; // scan for largest range of available blocks uint32 largestFreeRange = 0; uint32 currentRangeSize = 0; for (uint32 i = 0; i < LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY; i++) { if (cacheMask[i] == LC_MASK_FREE) { currentRangeSize++; } else { largestFreeRange = std::max(largestFreeRange, currentRangeSize); currentRangeSize = 0; } } largestFreeRange = std::max(largestFreeRange, currentRangeSize); osLib_returnFromFunction(hCPU, largestFreeRange * LC_LOCKED_CACHE_GRANULARITY); } uint32 LCIsEnabled[PPC_CORE_COUNT] = { 0 }; void coreinitExport_LCEnableDMA(PPCInterpreter_t* hCPU) { //debug_printf("LCEnableDMA()\n"); LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)]++; osLib_returnFromFunction(hCPU, 1); } sint32 _lcDisableErrorCounter = 0; void coreinitExport_LCDisableDMA(PPCInterpreter_t* hCPU) { //debug_printf("LCDisableDMA()\n"); #ifndef PUBLIC_RELASE if (LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)] == 0) assert_dbg(); #endif LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)]--; #ifdef CEMU_DEBUG_ASSERT if (LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)] == 0) { uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); uint8* cacheMask = lcCacheMask[coreIndex]; for (uint32 i = 0; i <= (LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY); i++) { if (cacheMask[i] != LC_MASK_FREE) { if (_lcDisableErrorCounter < 15) { cemuLog_log(LogType::Force, "LC disabled but there is still memory allocated"); _lcDisableErrorCounter++; } } } } #endif osLib_returnFromFunction(hCPU, 1); } void coreinitExport_LCIsDMAEnabled(PPCInterpreter_t* hCPU) { osLib_returnFromFunction(hCPU, LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)] ? 1 : 0); } void coreinitExport_LCHardwareIsAvailable(PPCInterpreter_t* hCPU) { // on the real HW, LC is shared between processes and not all processes can access it. In the emulator we don't care and always return true. osLib_returnFromFunction(hCPU, 1); } void coreinitExport_LCLoadDMABlocks(PPCInterpreter_t* hCPU) { //printf("LCLoadDMABlocks(0x%08x, 0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); uint32 numBlocks = hCPU->gpr[5]; if (numBlocks == 0) numBlocks = 128; uint32 transferSize = numBlocks * 32; uint8* destPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint8* srcPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[4]); // copy right away, we don't emulate the DMAQueue currently memcpy(destPtr, srcPtr, transferSize); osLib_returnFromFunction(hCPU, 0); } void coreinitExport_LCStoreDMABlocks(PPCInterpreter_t* hCPU) { //printf("LCStoreDMABlocks(0x%08x, 0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); uint32 numBlocks = hCPU->gpr[5]; if (numBlocks == 0) numBlocks = 128; //uint32 transferSize = numBlocks*32; uint8* destPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint8* srcPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[4]); // copy right away, we don't emulate the DMAQueue currently memcpy_qwords(destPtr, srcPtr, numBlocks * (32 / sizeof(uint64))); LatteBufferCache_notifyDCFlush(hCPU->gpr[3], numBlocks * 32); osLib_returnFromFunction(hCPU, 0); } void coreinitExport_LCWaitDMAQueue(PPCInterpreter_t* hCPU) { //printf("LCWaitDMAQueue(%d)\n", hCPU->gpr[3]); uint32 len = hCPU->gpr[3]; // todo: Implement (be aware DMA queue is per core) osLib_returnFromFunction(hCPU, 0); } void InitializeLC() { for (sint32 f = 0; f < PPC_CORE_COUNT; f++) { memset(lcCacheMask[f], LC_MASK_FREE, (LC_LOCKED_CACHE_SIZE + LC_LOCKED_CACHE_GRANULARITY - 1) / LC_LOCKED_CACHE_GRANULARITY); } osLib_addFunction("coreinit", "LCGetMaxSize", coreinitExport_LCGetMaxSize); osLib_addFunction("coreinit", "LCAlloc", coreinitExport_LCAlloc); osLib_addFunction("coreinit", "LCDealloc", coreinitExport_LCDealloc); osLib_addFunction("coreinit", "LCGetUnallocated", coreinitExport_LCGetUnallocated); osLib_addFunction("coreinit", "LCGetAllocatableSize", coreinitExport_LCGetAllocatableSize); osLib_addFunction("coreinit", "LCEnableDMA", coreinitExport_LCEnableDMA); osLib_addFunction("coreinit", "LCDisableDMA", coreinitExport_LCDisableDMA); osLib_addFunction("coreinit", "LCIsDMAEnabled", coreinitExport_LCIsDMAEnabled); osLib_addFunction("coreinit", "LCHardwareIsAvailable", coreinitExport_LCHardwareIsAvailable); osLib_addFunction("coreinit", "LCLoadDMABlocks", coreinitExport_LCLoadDMABlocks); osLib_addFunction("coreinit", "LCStoreDMABlocks", coreinitExport_LCStoreDMABlocks); osLib_addFunction("coreinit", "LCWaitDMAQueue", coreinitExport_LCWaitDMAQueue); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_LockedCache.h ================================================ #pragma once namespace coreinit { void InitializeLC(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/IOSU/legacy/iosu_ioctl.h" #include "Cafe/IOSU/legacy/iosu_mcp.h" #include "Cafe/OS/libs/coreinit/coreinit_IOS.h" #include "Cafe/OS/libs/coreinit/coreinit_MCP.h" #include "Cafe/OS/libs/nn_common.h" #include "Cafe/OS/libs/nn_act/nn_act.h" #include "config/CemuConfig.h" #include "Cafe/CafeSystem.h" #define mcpPrepareRequest() \ StackAllocator<iosuMcpCemuRequest_t> _buf_mcpRequest; \ StackAllocator<ioBufferVector_t> _buf_bufferVector; \ iosuMcpCemuRequest_t* mcpRequest = _buf_mcpRequest.GetPointer(); \ ioBufferVector_t* mcpBufferVector = _buf_bufferVector.GetPointer(); \ memset(mcpRequest, 0, sizeof(iosuMcpCemuRequest_t)); \ memset(mcpBufferVector, 0, sizeof(ioBufferVector_t)); \ mcpBufferVector->buffer = (uint8*)mcpRequest; #define MCP_FAKE_HANDLE (0x00000010) typedef struct { uint32be n0; uint32be n1; uint32be n2; }mcpSystemVersion_t; static_assert(sizeof(MCPTitleInfo) == 0x61, "title entry size is invalid"); static_assert(offsetof(MCPTitleInfo, appPath) == 0xC, "title entry appPath has invalid offset"); static_assert(offsetof(MCPTitleInfo, titleVersion) == 0x48, "title entry titleVersion has invalid offset"); static_assert(offsetof(MCPTitleInfo, osVersion) == 0x4A, "title entry osVersion has invalid offset"); MCPHANDLE MCP_Open() { // placeholder return MCP_FAKE_HANDLE; } void MCP_Close(MCPHANDLE mcpHandle) { // placeholder } void coreinitExport_MCP_Open(PPCInterpreter_t* hCPU) { // placeholder osLib_returnFromFunction(hCPU, MCP_Open()); } void coreinitExport_MCP_Close(PPCInterpreter_t* hCPU) { // placeholder osLib_returnFromFunction(hCPU, 0); } sint32 MCP_GetSysProdSettings(MCPHANDLE mcpHandle, SysProdSettings* sysProdSettings) { memset(sysProdSettings, 0x00, sizeof(SysProdSettings)); // todo: Other fields are currently unknown sysProdSettings->gameRegion = (uint8)CafeSystem::GetForegroundTitleRegion(); sysProdSettings->platformRegion = (uint8)CafeSystem::GetPlatformRegion(); // contains Wii U serial parts at +0x1A and +0x22? return 0; } void coreinitExport_MCP_GetSysProdSettings(PPCInterpreter_t* hCPU) { sint32 result = MCP_GetSysProdSettings(hCPU->gpr[3], (SysProdSettings*)memory_getPointerFromVirtualOffset(hCPU->gpr[4])); osLib_returnFromFunction(hCPU, result); } void coreinitExport_MCP_TitleListByAppType(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "MCP_TitleListByAppType(0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); ppcDefineParamU32(mcpHandle, 0); ppcDefineParamU32(appType, 1); ppcDefineParamU32BEPtr(countOutput, 2); ppcDefineParamStructPtr(titleList, MCPTitleInfo, 3); ppcDefineParamU32(titleListSize, 4); sint32 maxCount = titleListSize / sizeof(MCPTitleInfo); mcpPrepareRequest(); mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE; mcpRequest->titleListRequest.titleCount = maxCount; mcpRequest->titleListRequest.titleList = titleList; mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo) * 1; mcpRequest->titleListRequest.appType = appType; __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); *countOutput = mcpRequest->titleListRequest.titleCount; osLib_returnFromFunction(hCPU, 0); } void coreinitExport_MCP_TitleList(PPCInterpreter_t* hCPU) { ppcDefineParamU32(mcpHandle, 0); ppcDefineParamU32BEPtr(countOutput, 1); ppcDefineParamStructPtr(titleList, MCPTitleInfo, 2); ppcDefineParamU32(titleListBufferSize, 3); // todo -> Other parameters? mcpPrepareRequest(); mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST; mcpRequest->titleListRequest.titleCount = *countOutput; mcpRequest->titleListRequest.titleList = titleList; mcpRequest->titleListRequest.titleListBufferSize = titleListBufferSize; __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); *countOutput = mcpRequest->titleListRequest.titleCount; cemuLog_logDebug(LogType::Force, "MCP_TitleList(...) returned {} titles", (uint32)mcpRequest->titleListRequest.titleCount); osLib_returnFromFunction(hCPU, mcpRequest->returnCode); } void coreinitExport_MCP_TitleCount(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "MCP_TitleCount(): Untested"); ppcDefineParamU32(mcpHandle, 0); mcpPrepareRequest(); mcpRequest->requestCode = IOSU_MCP_GET_TITLE_COUNT; mcpRequest->titleListRequest.titleCount = 0; __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); osLib_returnFromFunction(hCPU, mcpRequest->titleListRequest.titleCount); } void coreinitExport_MCP_GetTitleInfo(PPCInterpreter_t* hCPU) { ppcDefineParamU32(mcpHandle, 0); ppcDefineParamU64(titleId, 2); ppcDefineParamStructPtr(titleList, MCPTitleInfo, 4); cemuLog_logDebug(LogType::Force, "MCP_GetTitleInfo() called"); mcpPrepareRequest(); mcpRequest->requestCode = IOSU_MCP_GET_TITLE_INFO; mcpRequest->titleListRequest.titleCount = 1; mcpRequest->titleListRequest.titleList = titleList; mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo); mcpRequest->titleListRequest.titleId = titleId; __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); if (mcpRequest->titleListRequest.titleCount == 0) { cemuLog_log(LogType::Force, "MCP_GetTitleInfo() failed to get title info"); } osLib_returnFromFunction(hCPU, mcpRequest->returnCode); } void coreinitExport_MCP_GetTitleInfoByTitleAndDevice(PPCInterpreter_t* hCPU) { ppcDefineParamU32(mcpHandle, 0); ppcDefineParamU64(titleId, 2); ppcDefineParamStr(device, 4); // e.g. "odd" ppcDefineParamStructPtr(titleList, MCPTitleInfo, 5); cemuLog_logDebug(LogType::Force, "MCP_GetTitleInfoByTitleAndDevice() called (todo - device type support)"); mcpPrepareRequest(); mcpRequest->requestCode = IOSU_MCP_GET_TITLE_INFO; mcpRequest->titleListRequest.titleCount = 1; mcpRequest->titleListRequest.titleList = titleList; mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo); mcpRequest->titleListRequest.titleId = titleId; __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); if (mcpRequest->titleListRequest.titleCount == 0) { cemuLog_logDebug(LogType::Force, "MCP_GetTitleInfoByTitleAndDevice() no title found"); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_MCP, 0)); // E-Shop/nn_vctl.rpl expects error to be returned when no title is found return; } osLib_returnFromFunction(hCPU, mcpRequest->returnCode); } namespace coreinit { void export_MCP_GetSystemVersion(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "MCP_GetSystemVersion({},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamU32(mcpHandle, 0); ppcDefineParamStructPtr(systemVersion, mcpSystemVersion_t, 1); systemVersion->n0 = 0x5; systemVersion->n1 = 0x5; systemVersion->n2 = 0x5; // todo: Load this from \sys\title\00050010\10041200\content\version.bin osLib_returnFromFunction(hCPU, 0); } void export_MCP_Get4SecondOffStatus(PPCInterpreter_t* hCPU) { // r3 = mcpHandle cemuLog_logDebug(LogType::Force, "MCP_Get4SecondOffStatus(...) placeholder"); // if this returns 1 then Barista will display the warning about cold-shutdown ('Holding the POWER button for at least four seconds...') osLib_returnFromFunction(hCPU, 0); } void export_MCP_TitleListByDevice(PPCInterpreter_t* hCPU) { ppcDefineParamU32(mcpHandle, 0); ppcDefineParamStr(deviceName, 1); ppcDefineParamU32BEPtr(titleCount, 2); ppcDefineParamStructPtr(titleList, MCPTitleInfo, 3); // type guessed // purpose of r7 parameter is unknown cemu_assert_unimplemented(); titleList[0].titleIdHigh = 0x00050000; titleList[0].titleIdLow = 0x11223344; strcpy(titleList[0].appPath, "titlePathTest"); *titleCount = 1; cemuLog_logDebug(LogType::Force, "MCP_TitleListByDevice() - placeholder"); osLib_returnFromFunction(hCPU, 0); } #pragma pack(1) struct MCPDevice_t { /* +0x000 */ char storageName[0x8]; // the name in the storage path (mlc, slc, usb?) // volumeId at +8 /* +0x008 */ char volumeId[16]; // /* +0x018 */ char ukn[0x90 - 0x18]; /* +0x090 */ char storagePath[0x280 - 1]; // /vol/storage_%s%02x /* +0x30F */ uint32be flags; // men.rpx checks for 0x2 and 0x8 uint8 ukn313[4]; uint8 ukn317[4]; }; #pragma pack() static_assert(sizeof(MCPDevice_t) == 0x31B); static_assert(sizeof(MCPDevice_t) == 0x31B); static_assert(offsetof(MCPDevice_t, storagePath) == 0x90); static_assert(offsetof(MCPDevice_t, flags) == 0x30F); static_assert(offsetof(MCPDevice_t, ukn313) == 0x313); static_assert(offsetof(MCPDevice_t, ukn317) == 0x317); void MCP_DeviceListEx(uint32 mcpHandle, uint32be* deviceCount, MCPDevice_t* deviceList, uint32 deviceListSize, bool returnFullList) { sint32 maxDeviceCount = deviceListSize / sizeof(MCPDevice_t); cemu_assert(maxDeviceCount >= 2); memset(deviceList, 0, deviceListSize); sint32 index = 0; uint32 flags = 2 | 8; // flag 2 is necessary for Wii U menu and Friend List to load // if we dont set flag 0x8 then Wii U menu will show a disk loading icon and screen // slc strcpy(deviceList[index].storageName, "slc"); strcpy(deviceList[index].volumeId, "VOLID_SLC"); deviceList[index].flags = flags; strcpy(deviceList[index].storagePath, "/vol/system_slc"); // unsure index++; // mlc strcpy(deviceList[index].storageName, "mlc"); strcpy(deviceList[index].volumeId, "VOLID_MLC"); deviceList[index].flags = flags; sprintf(deviceList[index].storagePath, "/vol/storage_mlc01"); index++; // we currently dont emulate USB storage *deviceCount = index; } void export_MCP_DeviceList(PPCInterpreter_t* hCPU) { ppcDefineParamU32(mcpHandle, 0); ppcDefineParamU32BEPtr(deviceCount, 1); ppcDefineParamStructPtr(deviceList, MCPDevice_t, 2); ppcDefineParamU32(deviceListSize, 3); cemuLog_logDebug(LogType::Force, "MCP_DeviceList()"); sint32 maxDeviceCount = deviceListSize / sizeof(MCPDevice_t); if (maxDeviceCount < 2) assert_dbg(); // if this doesnt return both MLC and SLC friendlist (frd.rpx) will softlock during boot memset(deviceList, 0, sizeof(MCPDevice_t) * 1); // 0 strcpy(deviceList[0].storageName, "mlc"); deviceList[0].flags = (0x01); // bitmask? sprintf(deviceList[0].storagePath, "/vol/storage_%s%02x", deviceList[0].storageName, (sint32)deviceList[0].flags); // 1 strcpy(deviceList[1].storageName, "slc"); deviceList[1].flags = (0x01); // bitmask? sprintf(deviceList[1].storagePath, "/vol/storage_%s%02x", deviceList[1].storageName, (sint32)deviceList[1].flags); // 2 //strcpy(deviceList[2].storageName, "usb"); //deviceList[2].storageSubindex = 1; //sprintf(deviceList[2].storagePath, "/vol/storage_%s%02x", deviceList[2].storageName, (sint32)deviceList[2].storageSubindex); *deviceCount = 2; osLib_returnFromFunction(hCPU, 0); } void export_MCP_FullDeviceList(PPCInterpreter_t* hCPU) { ppcDefineParamU32(mcpHandle, 0); ppcDefineParamU32BEPtr(deviceCount, 1); ppcDefineParamStructPtr(deviceList, MCPDevice_t, 2); ppcDefineParamU32(deviceListSize, 3); cemuLog_logDebug(LogType::Force, "MCP_FullDeviceList()"); MCP_DeviceListEx(mcpHandle, deviceCount, deviceList, deviceListSize, true); osLib_returnFromFunction(hCPU, 0); } void export_MCP_UpdateCheckContext(PPCInterpreter_t* hCPU) { ppcDefineParamU32(mcpHandle, 0); ppcDefineParamU32BEPtr(unknownParam, 1); cemuLog_logDebug(LogType::Force, "MCP_UpdateCheckContext() - placeholder (might be wrong)"); *unknownParam = 1; osLib_returnFromFunction(hCPU, 0); } void export_MCP_TitleListUpdateGetNext(PPCInterpreter_t* hCPU) { ppcDefineParamU32(mcpHandle, 0); ppcDefineParamMPTR(callbackMPTR, 1); cemuLog_logDebug(LogType::Force, "MCP_TitleListUpdateGetNext() - placeholder/unimplemented"); // this callback is to let the app know when the title list changed? //PPCCoreCallback(callbackMPTR); // -> If we trigger the callback then the menu will repeat with a call to MCP_GetTitleList(), MCP_DeviceList() and MCP_TitleListUpdateGetNext osLib_returnFromFunction(hCPU, 0); } void export_MCP_GetOverlayAppInfo(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "MCP_GetOverlayAppInfo(...) placeholder"); ppcDefineParamU32(mcpHandle, 0); ppcDefineParamU32(appType, 1); ppcDefineParamU32(uknR5, 2); ppcDefineParamStructPtr(titleList, MCPTitleInfo, 3); // hacky mcpPrepareRequest(); mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE; mcpRequest->titleListRequest.titleCount = 1; mcpRequest->titleListRequest.titleList = titleList; mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo)*1; mcpRequest->titleListRequest.appType = appType; __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); if (mcpRequest->titleListRequest.titleCount != 1) assert_dbg(); osLib_returnFromFunction(hCPU, 0); } uint32 MCP_UpdateClearContextAsync(uint32 mcpHandle, betype<MPTR>* callbackPtr) { cemuLog_logDebug(LogType::Force, "MCP_UpdateClearContextAsync() - stubbed"); uint32 clearContextResult = 0; PPCCoreCallback(*callbackPtr, clearContextResult); return 0; } uint32 MCP_InstallUtilGetTitleEnability(uint32 mcpHandle, uint32be* enabilityOutput, MCPTitleInfo* title) { *enabilityOutput = 1; return 0; } uint32 MCP_GetEcoSettings(uint32 mcpHandle, uint32be* flagCaffeineEnable, uint32be* uknFlag2, uint32be* uknFlag3) { *flagCaffeineEnable = 1; // returning 1 here will stop the Wii U Menu from showing the Quick Start setup dialogue *uknFlag2 = 0; *uknFlag3 = 0; return 0; } uint32 MCP_RightCheckLaunchable(uint32 mcpHandle, uint64 titleId, uint32be* launchableOut) { *launchableOut = 1; return 0; } uint32 MCP_GetTitleId(uint32 mcpHandle, uint64be* outTitleId) { *outTitleId = CafeSystem::GetForegroundTitleId(); return 0; } void InitializeMCP() { osLib_addFunction("coreinit", "MCP_Open", coreinitExport_MCP_Open); osLib_addFunction("coreinit", "MCP_Close", coreinitExport_MCP_Close); osLib_addFunction("coreinit", "MCP_GetSysProdSettings", coreinitExport_MCP_GetSysProdSettings); osLib_addFunction("coreinit", "MCP_TitleListByAppType", coreinitExport_MCP_TitleListByAppType); osLib_addFunction("coreinit", "MCP_TitleList", coreinitExport_MCP_TitleList); osLib_addFunction("coreinit", "MCP_TitleCount", coreinitExport_MCP_TitleCount); osLib_addFunction("coreinit", "MCP_GetTitleInfo", coreinitExport_MCP_GetTitleInfo); osLib_addFunction("coreinit", "MCP_GetTitleInfoByTitleAndDevice", coreinitExport_MCP_GetTitleInfoByTitleAndDevice); osLib_addFunction("coreinit", "MCP_TitleListByDevice", export_MCP_TitleListByDevice); osLib_addFunction("coreinit", "MCP_GetSystemVersion", export_MCP_GetSystemVersion); osLib_addFunction("coreinit", "MCP_Get4SecondOffStatus", export_MCP_Get4SecondOffStatus); osLib_addFunction("coreinit", "MCP_DeviceList", export_MCP_DeviceList); osLib_addFunction("coreinit", "MCP_FullDeviceList", export_MCP_FullDeviceList); osLib_addFunction("coreinit", "MCP_UpdateCheckContext", export_MCP_UpdateCheckContext); osLib_addFunction("coreinit", "MCP_TitleListUpdateGetNext", export_MCP_TitleListUpdateGetNext); osLib_addFunction("coreinit", "MCP_GetOverlayAppInfo", export_MCP_GetOverlayAppInfo); cafeExportRegister("coreinit", MCP_UpdateClearContextAsync, LogType::Placeholder); cafeExportRegister("coreinit", MCP_InstallUtilGetTitleEnability, LogType::Placeholder); cafeExportRegister("coreinit", MCP_RightCheckLaunchable, LogType::Placeholder); cafeExportRegister("coreinit", MCP_GetEcoSettings, LogType::Placeholder); cafeExportRegister("coreinit", MCP_GetTitleId, LogType::Placeholder); } } typedef struct { char settingName[0x40]; uint32 ukn1; uint32 ukn2; uint32 ukn3; uint32 ukn4_size; // size guessed MPTR resultPtr; // pointer to output value }UCParamStruct_t; static_assert(sizeof(UCParamStruct_t) == 0x54); // unsure #if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD #define _strcmpi strcasecmp #endif void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU) { // parameters: // r3 = UC handle? // r4 = Number of values to read (count of UCParamStruct_t entries) // r5 = UCParamStruct_t* UCParamStruct_t* ucParamBase = (UCParamStruct_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]); uint32 ucParamCount = hCPU->gpr[4]; for(uint32 i=0; i<ucParamCount; i++) { UCParamStruct_t* ucParam = ucParamBase+i; if(_strcmpi(ucParam->settingName, "cafe.cntry_reg") == 0) { // country code // get country from simple address uint32be simpleAddress = 0; nn::act::GetSimpleAddressIdEx(&simpleAddress, nn::act::ACT_SLOT_CURRENT); uint32 countryCode = nn::act::getCountryCodeFromSimpleAddress(simpleAddress); if( ucParam->resultPtr != _swapEndianU32(MPTR_NULL) ) memory_writeU32(_swapEndianU32(ucParam->resultPtr), countryCode); } else if (_strcmpi(ucParam->settingName, "cafe.language") == 0) { // language // 0 -> Japanese // 1 -> English // ... uint32 languageId = (uint32)GetConfig().console_language.GetValue(); if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU32(_swapEndianU32(ucParam->resultPtr), languageId); } else if (_strcmpi(ucParam->settingName, "cafe.initial_launch") == 0) { // initial launch // possible ids: // 0xFF Set by SCIFormatSystem (default?) // 0x01 Inited but no user created yet (setting this will make the home menu go into user creation dialog) // 0x02 Inited, with user // other values are unknown memory_writeU8(_swapEndianU32(ucParam->resultPtr), 2); } else if (_strcmpi(ucParam->settingName, "cafe.eula_version") == 0) { // todo - investigate values memory_writeU32(_swapEndianU32(ucParam->resultPtr), 0); } else if (_strcmpi(ucParam->settingName, "cafe.eula_agree") == 0) { // todo - investigate values memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); } else if (_strcmpi(ucParam->settingName, "cafe.version") == 0) { // todo - investigate values memory_writeU16(_swapEndianU32(ucParam->resultPtr), 0); } else if (_strcmpi(ucParam->settingName, "cafe.eco") == 0) { // todo - investigate values memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); } else if (_strcmpi(ucParam->settingName, "cafe.fast_boot") == 0) { // todo - investigate values memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); } else if (_strcmpi(ucParam->settingName, "parent.enable") == 0) { // parental feature enabled if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU32(_swapEndianU32(ucParam->resultPtr), 0); } else if (_strcmpi(ucParam->settingName, "nn.act.account_repaired") == 0) { if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); } else if (_strcmpi(ucParam->settingName, "p_acct1.net_communication_on_game") == 0) { // get parental online control for online features // note: This option is account-bound, the p_acct1 prefix indicates that the account in slot 1 is used // a non-zero value means network access is restricted through parental access. 0 means allowed // account in slot 1 if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed } else if (_strcmpi(ucParam->settingName, "p_acct1.int_movie") == 0) { // get parental online control for movies? // used by Pikmin HD movies if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // 0 -> allowed } else if (_strcmpi(ucParam->settingName, "p_acct1.network_launcher") == 0) { // miiverse restrictions if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only, 2 -> no access) } else if (_strcmpi(ucParam->settingName, "s_acct01.uuid") == 0) { if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL) && ucParam->ukn4_size >= 37) { StackAllocator<uint8, 16> _uuid; uint8* uuid = _uuid.GetPointer(); nn::act::GetUuidEx(uuid, 1, 0); char tempStr[64]; sprintf(tempStr, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]); strcpy((char*)memory_getPointerFromVirtualOffset(_swapEndianU32(ucParam->resultPtr)), tempStr); } } else if (_strcmpi(ucParam->settingName, "s_acct01.nn.ec.eshop_initialized") == 0) { // E-Shop welcome screen if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 1); // data type is guessed } else if (_strcmpi(ucParam->settingName, "p_acct1.int_browser") == 0) { if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); } /* caffeine settings (Quick Start) */ else if (_strcmpi(ucParam->settingName, "caffeine.enable") == 0) { if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 1); } else if (_strcmpi(ucParam->settingName, "caffeine.ad_enable") == 0) { if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); } else if (_strcmpi(ucParam->settingName, "caffeine.push_enable") == 0) { if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); } else if (_strcmpi(ucParam->settingName, "caffeine.drcled_enable") == 0) { if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); } else { cemuLog_logDebug(LogType::Force, "Unsupported SCI value: {} Size {:08x}", ucParam->settingName, ucParam->ukn4_size); } } osLib_returnFromFunction(hCPU, 0); // 0 -> success } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MCP.h ================================================ #pragma once struct SysProdSettings { uint8 ukn00; // 0x00 uint8 ukn01; // 0x01 uint8 ukn02; // 0x02 uint8 platformRegion; // 0x03 uint8 ukn04; uint8 ukn05; uint8 ukn06; uint8 ukn07; uint8 prevBlock; uint8 ukn09; uint8 ukn0A; uint8 gameRegion; // game region? uint8 nextBlock; uint8 ukn0D; uint8 ukn0E; uint8 ukn0F; uint8 ukn10; uint8 ukn11; uint8 ukn12; uint8 ukn13; uint8 ukn14[4]; uint8 ukn18[4]; uint8 ukn1C[4]; uint8 ukn20[4]; uint8 ukn24[4]; uint8 ukn28[4]; uint8 ukn2C[4]; uint8 ukn30[4]; uint8 ukn34[4]; uint8 ukn38[4]; uint8 ukn3C[4]; uint8 ukn40[4]; uint8 ukn44; uint8 ukn45; }; static_assert(sizeof(SysProdSettings) == 0x46); typedef uint32 MCPHANDLE; MCPHANDLE MCP_Open(); void MCP_Close(MCPHANDLE mcpHandle); sint32 MCP_GetSysProdSettings(MCPHANDLE mcpHandle, SysProdSettings* sysProdSettings); void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU); namespace coreinit { void InitializeMCP(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_FG.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_Memory.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h" #include "coreinit_DynLoad.h" // the system area is a block of memory that exists only in the emulator. It is used to simplify dynamic memory allocation for system data // this partially overlaps with the system heap from coreinit_SysHeap.cpp -> Use SysHeap for everything MPTR sysAreaAllocatorOffset = 0; MPTR coreinit_allocFromSysArea(uint32 size, uint32 alignment) { cemu_assert_debug(mmuRange_CEMU_AREA.isMapped()); // deprecated, use OSAllocFromSystem instead (for everything but SysAllocator which probably should use its own allocator?) static std::mutex s_allocator_mutex; s_allocator_mutex.lock(); sysAreaAllocatorOffset = (sysAreaAllocatorOffset+alignment-1); sysAreaAllocatorOffset -= (sysAreaAllocatorOffset%alignment); uint32 newMemOffset = mmuRange_CEMU_AREA.getBase() + sysAreaAllocatorOffset; sysAreaAllocatorOffset += (size+3)&~3; if( sysAreaAllocatorOffset >= mmuRange_CEMU_AREA.getSize() ) { cemuLog_log(LogType::Force, "Ran out of system memory"); cemu_assert(false); // out of bounds } s_allocator_mutex.unlock(); return newMemOffset; } void coreinit_freeToSysArea(MPTR mem) { // todo } namespace coreinit { #define MEM_MAX_HEAP_TABLE (0x20) sint32 g_heapTableCount = 0; MEMHeapBase* g_heapTable[MEM_MAX_HEAP_TABLE] = {}; bool g_slockInitialized = false; bool g_listsInitialized = false; MEMList g_list1; MEMList g_list2; MEMList g_list3; std::array<uint32, 3> gHeapFillValues{ 0xC3C3C3C3, 0xF3F3F3F3, 0xD3D3D3D3 }; SysAllocator<OSSpinLock> gHeapGlobalLock; MEMHeapBase* gDefaultHeap; bool MEMHeapTable_Add(MEMHeapBase* heap) { if (g_heapTableCount >= MEM_MAX_HEAP_TABLE) return false; g_heapTable[g_heapTableCount] = heap; g_heapTableCount++; return true; } bool MEMHeapTable_Remove(MEMHeapBase* heap) { if (g_heapTableCount == 0) return false; if (g_heapTableCount > MEM_MAX_HEAP_TABLE) return false; for (sint32 i = 0; i < g_heapTableCount; ++i) { if (g_heapTable[i] == heap) { g_heapTable[i] = nullptr; g_heapTableCount--; if (g_heapTableCount == 0) return true; if (i >= g_heapTableCount) return true; cemu_assert_debug(i < MEM_MAX_HEAP_TABLE); for (; i < g_heapTableCount; ++i) { g_heapTable[i] = g_heapTable[i + 1]; } cemu_assert_debug(i < MEM_MAX_HEAP_TABLE); g_heapTable[i] = nullptr; return true; } } return false; } MEMHeapBase* _MEMList_FindContainingHeap(MEMList* list, MEMHeapBase* heap) { for (MEMHeapBase* obj = (MEMHeapBase*)MEMGetFirstListObject(list); obj; obj = (MEMHeapBase*)MEMGetNextListObject(list, obj)) { if (obj->heapStart.GetPtr() <= heap && heap < obj->heapEnd.GetPtr()) { const MEMHeapHandle containHeap = _MEMList_FindContainingHeap(&obj->childList, heap); return containHeap ? containHeap : obj; } } return nullptr; } bool MEMList_ContainsHeap(MEMList* list, MEMHeapBase* heap) { for (MEMHeapBase* obj = (MEMHeapBase*)MEMGetFirstListObject(list); obj; obj = (MEMHeapBase*)MEMGetNextListObject(list, obj)) { if (obj == heap) return true; } return false; } MEMList* MEMList_FindContainingHeap(MEMHeapBase* head) { MEMPTR<void> memBound; uint32be memBoundSize; OSGetMemBound(1, &memBound, &memBoundSize); MEMPTR<void> bucket; uint32be bucketSize; coreinit::OSGetForegroundBucket(&bucket, &bucketSize); if ((uintptr_t)memBound.GetPtr() > (uintptr_t)head || (uintptr_t)head >= (uintptr_t)memBound.GetPtr() + (uint32)memBoundSize) { if ((uintptr_t)bucket.GetPtr() > (uintptr_t)head || (uintptr_t)head >= (uintptr_t)bucket.GetPtr() + (uint32)bucketSize) { MEMHeapBase* containHeap = _MEMList_FindContainingHeap(&g_list1, head); if (containHeap) return &containHeap->childList; return &g_list1; } MEMHeapBase* containHeap = _MEMList_FindContainingHeap(&g_list3, head); if (containHeap) return &containHeap->childList; return &g_list3; } MEMHeapBase* containHeap = _MEMList_FindContainingHeap(&g_list2, head); if (containHeap) return &containHeap->childList; return &g_list2; } void MEMInitHeapBase(MEMHeapBase* heap, MEMHeapMagic magic, void* heapStart, void* heapEnd, uint32 createFlags) { memset(heap, 0, sizeof(MEMHeapBase)); heap->magic = magic; heap->heapStart = heapStart; heap->heapEnd = heapEnd; heap->flags = (uint8)createFlags; MEMInitList(&heap->childList, 4); if (!g_slockInitialized) { OSInitSpinLock(&gHeapGlobalLock); g_slockInitialized = true; } if (!g_listsInitialized) { MEMInitList(&g_list1, offsetof(MEMHeapBase, link)); MEMInitList(&g_list2, offsetof(MEMHeapBase, link)); MEMInitList(&g_list3, offsetof(MEMHeapBase, link)); g_listsInitialized = true; } OSInitSpinLock(&heap->spinlock); OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock); MEMList* list = MEMList_FindContainingHeap(heap); MEMAppendListObject(list, heap); OSUninterruptibleSpinLock_Release(&gHeapGlobalLock); } void MEMBaseDestroyHeap(MEMHeapBase* heap) { OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock); if (HAS_FLAG(heap->flags, MEM_HEAP_OPTION_THREADSAFE)) OSUninterruptibleSpinLock_Acquire(&heap->spinlock); MEMList* containHeap = MEMList_FindContainingHeap(heap); cemu_assert_debug(MEMList_ContainsHeap(containHeap, heap)); MEMRemoveListObject(containHeap, heap); if (HAS_FLAG(heap->flags, MEM_HEAP_OPTION_THREADSAFE)) OSUninterruptibleSpinLock_Release(&heap->spinlock); OSUninterruptibleSpinLock_Release(&gHeapGlobalLock); } std::array<MEMHeapBase*, 9> sHeapBaseHandle{}; MEMHeapBase* MEMGetBaseHeapHandle(uint32 index) { if (index >= sHeapBaseHandle.size()) return nullptr; return sHeapBaseHandle[index]; } MEMHeapBase* MEMSetBaseHeapHandle(uint32 index, MEMHeapBase* heapBase) { if (index >= sHeapBaseHandle.size()) return nullptr; if (sHeapBaseHandle[index] != nullptr) { cemuLog_log(LogType::Force, "MEMSetBaseHeapHandle(): Trying to assign heap to non-empty slot"); return sHeapBaseHandle[index]; } sHeapBaseHandle[index] = heapBase; return nullptr; } uint32 MEMSetFillValForHeap(HEAP_FILL_TYPE type, uint32 value) { uint32 idx = (uint32)type; cemu_assert(idx < gHeapFillValues.size()); OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock); const uint32 oldValue = gHeapFillValues[idx]; gHeapFillValues[idx] = value; OSUninterruptibleSpinLock_Release(&gHeapGlobalLock); return oldValue; } uint32 MEMGetFillValForHeap(HEAP_FILL_TYPE type) { uint32 idx = (uint32)type; cemu_assert(idx < gHeapFillValues.size()); OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock); const uint32 value = gHeapFillValues[idx]; OSUninterruptibleSpinLock_Release(&gHeapGlobalLock); return value; } MEMHeapBase* MEMFindContainHeap(const void* memBlock) { MEMPTR<void> memBound; uint32be memBoundSize; OSGetMemBound(1, &memBound, &memBoundSize); MEMPTR<void> bucket; uint32be bucketSize; coreinit::OSGetForegroundBucket(&bucket, &bucketSize); OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock); MEMHeapBase* result; if ((uintptr_t)memBound.GetPtr() > (uintptr_t)memBlock || (uintptr_t)memBlock >= (uintptr_t)memBound.GetPtr() + (uint32)memBoundSize) { if ((uintptr_t)bucket.GetPtr() > (uintptr_t)memBlock || (uintptr_t)memBlock >= (uintptr_t)bucket.GetPtr() + (uint32)bucketSize) { result = _MEMList_FindContainingHeap(&g_list1, (MEMHeapBase*)memBlock); } else { if (coreinit::OSGetForegroundBucket(nullptr, nullptr) == 0) { cemu_assert_unimplemented(); // foreground required result = nullptr; } else { result = _MEMList_FindContainingHeap(&g_list3, (MEMHeapBase*)memBlock); } } } else { if (coreinit::OSGetForegroundBucket(nullptr, nullptr) == 0) { cemu_assert_unimplemented(); // foreground required result = nullptr; } else { result = _MEMList_FindContainingHeap(&g_list2, (MEMHeapBase*)memBlock); } } OSUninterruptibleSpinLock_Release(&gHeapGlobalLock); return result; } void* MEMCreateUserHeapHandle(void* heapAddress, uint32 heapSize) { MEMInitHeapBase((MEMHeapBase*)heapAddress, MEMHeapMagic::USER_HEAP, (uint8*)heapAddress + 0x40, (uint8*)heapAddress + heapSize, 0); return heapAddress; } /* MEM Heap list */ #define GET_MEM_LINK(__obj__,__offset__) ((MEMLink*)((uintptr_t)__obj__ + (uint16)__offset__)) void* MEMGetFirstListObject(MEMList* list) { return MEMGetNextListObject(list, nullptr); } void MEMList_SetSingleObject(MEMList* list, void* object) { cemu_assert_debug(list != nullptr); cemu_assert_debug(object != nullptr); MEMLink* link = GET_MEM_LINK(object, list->offset); link->prev = nullptr; link->next = nullptr; list->numObjects = list->numObjects + 1; list->head = object; list->tail = object; } void MEMInitList(MEMList* list, uint32 offset) { cemu_assert_debug(list != nullptr); cemu_assert(offset <= 0xFFFF); memset(list, 0x00, sizeof(MEMLink)); list->offset = (uint16)offset; } void MEMAppendListObject(MEMList* list, void* object) { cemu_assert_debug(list != nullptr); cemu_assert_debug(object != nullptr); if (list->head) { list->numObjects = list->numObjects + 1; MEMLink* link = GET_MEM_LINK(object, list->offset); link->prev = list->tail; link->next = nullptr; MEMLink* tailLink = GET_MEM_LINK(list->tail.GetPtr(), list->offset); tailLink->next = object; list->tail = object; } else MEMList_SetSingleObject(list, object); } void MEMPrependListObject(MEMList* list, void* object) { cemu_assert_debug(list != nullptr); cemu_assert_debug(object != nullptr); MEMPTR<void> headObject = list->head; if (headObject) { list->numObjects = list->numObjects + 1; MEMLink* link = GET_MEM_LINK(object, list->offset); link->prev = nullptr; link->next = headObject; MEMLink* headLink = GET_MEM_LINK(headObject.GetPtr(), list->offset); headLink->prev = object; list->head = object; } else MEMList_SetSingleObject(list, object); } void MEMRemoveListObject(MEMList* list, void* object) { cemu_assert_debug(list != nullptr); cemu_assert_debug(object != nullptr); MEMLink* link = GET_MEM_LINK(object, list->offset); MEMPTR<void> prevObject = link->prev; if (prevObject) { MEMLink* prevLink = GET_MEM_LINK(prevObject.GetPtr(), list->offset); prevLink->next = link->next; } else list->head = link->next; MEMPTR<void> nextObject = link->next; if (nextObject) { MEMLink* nextLink = GET_MEM_LINK(nextObject.GetPtr(), list->offset); nextLink->prev = prevObject; } else list->tail = prevObject; link->prev = nullptr; link->next = nullptr; list->numObjects = list->numObjects - 1; } void* MEMGetNextListObject(MEMList* list, void* object) { cemu_assert_debug(list != nullptr); if (!object) return list->head.GetPtr(); MEMLink* result = GET_MEM_LINK(object, list->offset); return result->next.GetPtr(); } void* MEMGetPrevListObject(MEMList* list, void* object) { cemu_assert_debug(list != nullptr); if (!object) return list->tail.GetPtr(); MEMLink* result = GET_MEM_LINK(object, list->offset); return result->prev.GetPtr(); } void* MEMGetNthListObject(MEMList* list, uint32 index) { cemu_assert_debug(list != nullptr); void* result = MEMGetNextListObject(list, nullptr); for (uint32 i = 0; i != index; ++i) { result = MEMGetNextListObject(list, result); } return result; } /* Default allocators */ MEMHeapBase* MEMDefaultHeap_Init(void* mem, uint32 size) { MEMHeapBase* heap = MEMCreateExpHeapEx(mem, size, 4); gDefaultHeap = heap; cemu_assert_debug(heap); return heap; } void* default_MEMAllocFromDefaultHeap(uint32 size) { void* mem = MEMAllocFromExpHeapEx(gDefaultHeap, size, 0x40); cemuLog_logDebug(LogType::CoreinitMem, "MEMAllocFromDefaultHeap(0x{:08x}) Result: 0x{:08x}", size, memory_getVirtualOffsetFromPointer(mem)); return mem; } void export_default_MEMAllocFromDefaultHeap(PPCInterpreter_t* hCPU) { ppcDefineParamU32(size, 0); MEMPTR<void> mem = default_MEMAllocFromDefaultHeap(size); osLib_returnFromFunction(hCPU, mem.GetMPTR()); } void* default_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment) { void* mem = MEMAllocFromExpHeapEx(gDefaultHeap, size, alignment); cemuLog_logDebug(LogType::CoreinitMem, "MEMAllocFromDefaultHeap(0x{:08x},{}) Result: 0x{:08x}", size, alignment, memory_getVirtualOffsetFromPointer(mem)); return mem; } void export_default_MEMAllocFromDefaultHeapEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(size, 0); ppcDefineParamS32(alignment, 1); MEMPTR<void> mem = default_MEMAllocFromDefaultHeapEx(size, alignment); osLib_returnFromFunction(hCPU, mem.GetMPTR()); } void default_MEMFreeToDefaultHeap(void* mem) { MEMFreeToExpHeap(gDefaultHeap, mem); } void export_default_MEMFreeToDefaultHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(mem, void, 0); default_MEMFreeToDefaultHeap(mem.GetPtr()); osLib_returnFromFunction(hCPU, 0); } void default_DynLoadAlloc(PPCInterpreter_t* hCPU) { ppcDefineParamS32(size, 0); ppcDefineParamS32(alignment, 1); ppcDefineParamMEMPTR(memResultPtr, uint32be, 2); MPTR mem = PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeapEx.GetMPTR(), size, alignment); *memResultPtr = mem; osLib_returnFromFunction(hCPU, 0); } void default_DynLoadFree(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(mem, void, 0); PPCCoreCallback(gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(), mem); osLib_returnFromFunction(hCPU, 0); } void* _weak_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment) { MEMPTR<void> r{ PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeapEx.GetMPTR(), size, alignment) }; return r.GetPtr(); } void* _weak_MEMAllocFromDefaultHeap(uint32 size) { MEMPTR<void> r{ PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(), size) }; return r.GetPtr(); } void _weak_MEMFreeToDefaultHeap(void* ptr) { PPCCoreCallback(gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(), ptr); } void coreinitExport_MEMAllocFromAllocator(PPCInterpreter_t* hCPU) { MEMAllocator* memAllocator = (MEMAllocator*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); // redirect execution to allocator alloc callback hCPU->instructionPointer = memAllocator->func->funcAlloc.GetMPTR(); } void coreinitExport_MEMFreeToAllocator(PPCInterpreter_t* hCPU) { MEMAllocator* memAllocator = (MEMAllocator*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); // redirect execution to allocator free callback hCPU->instructionPointer = memAllocator->func->funcFree.GetMPTR(); } void _DefaultHeapAllocator_Alloc(PPCInterpreter_t* hCPU) { hCPU->gpr[3] = hCPU->gpr[4]; hCPU->instructionPointer = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); } void _DefaultHeapAllocator_Free(PPCInterpreter_t* hCPU) { hCPU->gpr[3] = hCPU->gpr[4]; hCPU->instructionPointer = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); } SysAllocator<MEMAllocatorFunc> gDefaultHeapAllocator; void coreinitExport_MEMInitAllocatorForDefaultHeap(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(memAllocator, MEMAllocator, 0); gDefaultHeapAllocator->funcAlloc = PPCInterpreter_makeCallableExportDepr(_DefaultHeapAllocator_Alloc); gDefaultHeapAllocator->funcFree = PPCInterpreter_makeCallableExportDepr(_DefaultHeapAllocator_Free); memAllocator->func = gDefaultHeapAllocator.GetPtr(); memAllocator->heap = MEMPTR<void>(MEMGetBaseHeapHandle(1)); memAllocator->param1 = 0; memAllocator->param2 = 0; osLib_returnFromFunction(hCPU, 0); } void InitDefaultHeaps(MEMPTR<MEMHeapBase>& mem1Heap, MEMPTR<MEMHeapBase>& memFGHeap, MEMPTR<MEMHeapBase>& mem2Heap) { mem1Heap = nullptr; memFGHeap = nullptr; mem2Heap = nullptr; gCoreinitData->MEMAllocFromDefaultHeap = RPLLoader_MakePPCCallable(export_default_MEMAllocFromDefaultHeap); gCoreinitData->MEMAllocFromDefaultHeapEx = RPLLoader_MakePPCCallable(export_default_MEMAllocFromDefaultHeapEx); gCoreinitData->MEMFreeToDefaultHeap = RPLLoader_MakePPCCallable(export_default_MEMFreeToDefaultHeap); if (OSGetForegroundBucket(nullptr, nullptr)) { MEMPTR<void> memBound; uint32be memBoundSize; OSGetMemBound(1, &memBound, &memBoundSize); mem1Heap = MEMCreateFrmHeapEx(memBound.GetPtr(), (uint32)memBoundSize, 0); OSGetForegroundBucketFreeArea(&memBound, &memBoundSize); memFGHeap = MEMCreateFrmHeapEx(memBound.GetPtr(), (uint32)memBoundSize, 0); } MEMPTR<void> memBound; uint32be memBoundSize; OSGetMemBound(2, &memBound, &memBoundSize); mem2Heap = MEMDefaultHeap_Init(memBound.GetPtr(), (uint32)memBoundSize); // set DynLoad allocators OSDynLoad_SetAllocator(RPLLoader_MakePPCCallable(default_DynLoadAlloc), RPLLoader_MakePPCCallable(default_DynLoadFree)); OSDynLoad_SetTLSAllocator(RPLLoader_MakePPCCallable(default_DynLoadAlloc), RPLLoader_MakePPCCallable(default_DynLoadFree)); } void CoreInitDefaultHeap(/* ukn */) { cemu_assert_unimplemented(); } void MEMResetToDefaultState() { for (auto& it : sHeapBaseHandle) it = nullptr; g_heapTableCount = 0; g_slockInitialized = false; g_listsInitialized = false; gDefaultHeap = nullptr; memset(&g_list1, 0, sizeof(g_list1)); memset(&g_list2, 0, sizeof(g_list2)); memset(&g_list3, 0, sizeof(g_list3)); } void InitializeMEM() { MEMResetToDefaultState(); cafeExportRegister("coreinit", CoreInitDefaultHeap, LogType::CoreinitMem); osLib_addFunction("coreinit", "MEMInitAllocatorForDefaultHeap", coreinitExport_MEMInitAllocatorForDefaultHeap); osLib_addFunction("coreinit", "MEMAllocFromAllocator", coreinitExport_MEMAllocFromAllocator); osLib_addFunction("coreinit", "MEMFreeToAllocator", coreinitExport_MEMFreeToAllocator); cafeExportRegister("coreinit", MEMGetBaseHeapHandle, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMSetBaseHeapHandle, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMFindContainHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMGetFillValForHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMSetFillValForHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMCreateUserHeapHandle, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMInitList, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMPrependListObject, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMAppendListObject, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMRemoveListObject, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMGetNextListObject, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMGetNthListObject, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMGetPrevListObject, LogType::CoreinitMem); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MEM.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" struct MEMLink_t { MPTR prevObject; MPTR nextObject; }; static_assert(sizeof(MEMLink_t) == 8); struct MEMList_t { MPTR headObject; MPTR tailObject; uint16 numObjects; uint16 offset; }; static_assert(sizeof(MEMList_t) == 0xC); struct MEMAllocatorFunc { MEMPTR<void> funcAlloc; MEMPTR<void> funcFree; }; static_assert(sizeof(MEMAllocatorFunc) == 8); struct MEMAllocator { /* +0x000 */ MEMPTR<MEMAllocatorFunc> func; /* +0x004 */ MEMPTR<void> heap; /* +0x00C */ uint32be param1; /* +0x010 */ uint32be param2; }; static_assert(sizeof(MEMAllocator) == 0x10); MPTR coreinit_allocFromSysArea(uint32 size, uint32 alignment); void coreinit_freeToSysArea(MPTR mem); // mem exports void coreinitExport_MEMInitAllocatorForDefaultHeap(PPCInterpreter_t* hCPU); void coreinitExport_MEMGetBaseHeapHandle(PPCInterpreter_t* hCPU); /* legacy stuff above */ namespace coreinit { #define MEM_HEAP_INVALID_HANDLE (nullptr) #define MEM_HEAP_DEFAULT_ALIGNMENT 4 #define MIN_ALIGNMENT 4 #define MIN_ALIGNMENT_MINUS_ONE (MIN_ALIGNMENT-1) #define MEM_HEAP_OPTION_NONE (0) #define MEM_HEAP_OPTION_CLEAR (1 << 0) #define MEM_HEAP_OPTION_FILL (1 << 1) #define MEM_HEAP_OPTION_THREADSAFE (1 << 2) enum class MEMHeapMagic : uint32 { UNIT_HEAP = 'UNTH', BLOCK_HEAP = 'BLKH', FRAME_HEAP = 'FRMH', EXP_HEAP = 'EXPH', USER_HEAP = 'USRH', }; struct MEMLink { MEMPTR<void> prev; MEMPTR<void> next; }; static_assert(sizeof(MEMLink) == 0x8); struct MEMList { /* 0x00 */ MEMPTR<void> head; /* 0x04 */ MEMPTR<void> tail; /* 0x08 */ uint16be numObjects; /* 0x0A */ uint16be offset; }; static_assert(sizeof(MEMList) == 0xC); void MEMInitList(MEMList* list, uint32 offset); void MEMAppendListObject(MEMList* list, void* object); void MEMRemoveListObject(MEMList* list, void* object); void* MEMGetFirstListObject(MEMList* list); void* MEMGetNextListObject(MEMList* list, void* object); struct MEMHeapBase { /* +0x00 */ betype<MEMHeapMagic> magic; /* +0x04 */ MEMLink link; /* +0x0C */ MEMList childList; /* +0x18 */ MEMPTR<void> heapStart; /* +0x1C */ MEMPTR<void> heapEnd; // heap end + 1 /* +0x20 */ OSSpinLock spinlock; /* +0x30 */ uint8 _ukn[3]; /* +0x33 */ uint8 flags; void AcquireLock() { if (flags & MEM_HEAP_OPTION_THREADSAFE) OSUninterruptibleSpinLock_Acquire(&spinlock); } void ReleaseLock() { if (flags & MEM_HEAP_OPTION_THREADSAFE) OSUninterruptibleSpinLock_Release(&spinlock); } // if set, memset allocations to zero bool HasOptionClear() const { return (flags & MEM_HEAP_OPTION_CLEAR) != 0; } // if set, memset allocations/releases to specific fill values bool HasOptionFill() const { return (flags & MEM_HEAP_OPTION_FILL) != 0; } }; static_assert(offsetof(MEMHeapBase, childList) == 0xC); static_assert(offsetof(MEMHeapBase, spinlock) == 0x20); static_assert(offsetof(MEMHeapBase, flags) == 0x33); static_assert(sizeof(MEMHeapBase) == 0x34); // heap base is actually 0x40 but bytes 0x34 to 0x40 are padding? typedef MEMHeapBase* MEMHeapHandle; /* Heap base */ void MEMInitHeapBase(MEMHeapBase* heap, MEMHeapMagic magic, void* heapStart, void* heapEnd, uint32 createFlags); void MEMBaseDestroyHeap(MEMHeapBase* heap); MEMHeapBase* MEMGetBaseHeapHandle(uint32 index); MEMHeapBase* MEMSetBaseHeapHandle(uint32 index, MEMHeapBase* heapBase); /* Heap list */ bool MEMHeapTable_Add(MEMHeapBase* heap); bool MEMHeapTable_Remove(MEMHeapBase* heap); MEMHeapBase* _MEMList_FindContainingHeap(MEMList* list, MEMHeapBase* heap); bool MEMList_ContainsHeap(MEMList* list, MEMHeapBase* heap); MEMList* MEMList_FindContainingHeap(MEMHeapBase* head); /* Heap settings */ enum class HEAP_FILL_TYPE : uint32 { ON_HEAP_CREATE = 0, ON_ALLOC = 1, ON_FREE = 2, }; uint32 MEMGetFillValForHeap(HEAP_FILL_TYPE type); uint32 MEMSetFillValForHeap(HEAP_FILL_TYPE type, uint32 value); MEMHeapHandle MEMFindContainHeap(const void* memBlock); /* Heap default allocators */ void InitDefaultHeaps(MEMPTR<MEMHeapBase>& mem1Heap, MEMPTR<MEMHeapBase>& memFGHeap, MEMPTR<MEMHeapBase>& mem2Heap); void* default_MEMAllocFromDefaultHeap(uint32 size); void* default_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment); void default_MEMFreeToDefaultHeap(void* mem); void* _weak_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment); void* _weak_MEMAllocFromDefaultHeap(uint32 size); void _weak_MEMFreeToDefaultHeap(void* ptr); /* Unit heap */ void InitializeMEMUnitHeap(); void InitializeMEM(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h" namespace coreinit { MEMHeapHandle MEMInitBlockHeap(MEMBlockHeap2_t* memStart, void* startAddr, void* endAddr, void* initTrackMem, uint32 initTrackMemSize, uint32 createFlags) { if (memStart == nullptr || startAddr == nullptr || endAddr == nullptr || (uintptr_t)startAddr >= (uintptr_t)endAddr) return nullptr; if (initTrackMemSize == 0) initTrackMem = nullptr; else if (initTrackMem == nullptr) initTrackMemSize = 0; MEMInitHeapBase(memStart, MEMHeapMagic::BLOCK_HEAP, startAddr, endAddr, createFlags); memStart->track.addrStart = startAddr; memStart->track.addrEnd = (void*)((uintptr_t)endAddr - 1); memStart->track.isFree = 1; memStart->track.previousBlock = nullptr; memStart->track.nextBlock = nullptr; memStart->headBlock = &memStart->track; memStart->tailBlock = &memStart->track; memStart->nextFreeBlock = nullptr; memStart->freeBlocksLeft = 0; uint8 flags = memStart->flags; if(HAS_FLAG(flags, MEM_HEAP_OPTION_FILL)) { cemu_assert_unimplemented(); } if(initTrackMemSize != 0) { sint32 result = MEMAddBlockHeapTracking(MEMPTR<void>(memStart).GetMPTR(), MEMPTR<void>(initTrackMem).GetMPTR(), initTrackMemSize); if(result != 0) { MEMBaseDestroyHeap(memStart); return nullptr; } } MEMHeapTable_Add(memStart); return memStart; } void* MEMDestroyBlockHeap(MEMHeapHandle hHeap) { if (!hHeap) return nullptr; cemu_assert_debug(hHeap->magic == MEMHeapMagic::BLOCK_HEAP); MEMBaseDestroyHeap(hHeap); MEMHeapTable_Remove(hHeap); memset(hHeap, 0x00, sizeof(MEMBlockHeap2_t)); return hHeap; } sint32 MEMGetAllocatableSizeForBlockHeapEx(MEMBlockHeap2_t* blockHeap, sint32 alignment) { if (!blockHeap || blockHeap->magic != MEMHeapMagic::BLOCK_HEAP) { cemuLog_log(LogType::Force, "MEMGetAllocatableSizeForBlockHeapEx(): Not a valid block heap"); return 0; } if (alignment < 0) alignment = -alignment; else if (alignment == 0) alignment = 4; if (HAS_FLAG(blockHeap->flags, MEM_HEAP_OPTION_THREADSAFE)) OSUninterruptibleSpinLock_Acquire(&blockHeap->spinlock); MEMBlockHeapTrack2_t* track = (MEMBlockHeapTrack2_t*)blockHeap->headBlock.GetPtr(); uint32 allocatableSize = 0; while (track) { if (track->isFree != 0) { uint32 addrStart = track->addrStart.GetMPTR(); uint32 addrEnd = track->addrEnd.GetMPTR(); uint32 alignmentMinusOne = alignment - 1; uint32 alignedAddrStart = ((addrStart + alignmentMinusOne) / alignment) * alignment; if (alignedAddrStart <= addrEnd) { uint32 size = (addrEnd + 1) - alignedAddrStart; allocatableSize = std::max(allocatableSize, size); } } // next track = (MEMBlockHeapTrack2_t*)track->nextBlock.GetPtr(); } if (HAS_FLAG(blockHeap->flags, MEM_HEAP_OPTION_THREADSAFE)) OSUninterruptibleSpinLock_Release(&blockHeap->spinlock); return allocatableSize; } void MEMDumpBlockHeap(MPTR heap); typedef struct { /* +0x00 */ uint32 addrStart; /* +0x04 */ uint32 addrEnd; /* +0x08 */ uint32 isFree; // if 0 -> block is used /* +0x0C */ MPTR previousBlock; /* +0x10 */ MPTR nextBlock; }MEMBlockHeapTrackDEPR; typedef struct { /* +0x00 */ uint32 ukn00; /* +0x04 */ uint32 ukn04; /* +0x08 */ uint32 trackArray; /* +0x0C */ uint32 trackCount; }MEMBlockHeapGroupDEPR; typedef struct { /* +0x000 */ betype<MEMHeapMagic> magic; /* +0x004 */ MEMLink_t link; /* +0x00C */ MEMList_t childList; /* +0x018 */ MPTR heapStart; /* +0x01C */ MPTR heapEnd; /* +0x020 */ coreinit::OSSpinLock spinlock; /* +0x030 */ union { uint32 val; uint32 flags; } attribute; // start of block heap header /* +0x034 */ uint8 ukn034[0x50 - 0x34]; // ? /* +0x050 */ MEMBlockHeapTrackDEPR nullBlockTrack; /* +0x064 */ MPTR headBlock; /* +0x068 */ MPTR tailBlock; /* +0x06C */ MPTR nextFreeBlock; /* +0x070 */ uint32 freeBlocksLeft; }MEMBlockHeapDEPR; void _blockHeapDebugVerifyIfBlockIsLinked(MEMBlockHeapDEPR* blockHeapHead, MEMBlockHeapTrackDEPR* track) { //MEMBlockHeapTrack_t* trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->firstBlock)); //MEMBlockHeapTrack_t* prevHistory[4]; //while( trackItr ) //{ // if( trackItr == track ) // { // debug_printf("PrevBlock3 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[0])); // debug_printf("PrevBlock2 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[1])); // debug_printf("PrevBlock1 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[2])); // debug_printf("PrevBlock0 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[3])); // assert_dbg(); // } // prevHistory[3] = prevHistory[2]; // prevHistory[2] = prevHistory[1]; // prevHistory[1] = prevHistory[0]; // prevHistory[0] = trackItr; // // next // trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(trackItr->nextBlock)); //} } void _blockHeapDebugVerifyLinkOrder(MEMBlockHeapDEPR* blockHeapHead) { //MEMBlockHeapTrack_t* trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->firstBlock)); //MEMBlockHeapTrack_t* prev = NULL; //while( trackItr ) //{ // MPTR prevMPTR = memory_getVirtualOffsetFromPointer(prev); // if( _swapEndianU32(trackItr->previousBlock) != prevMPTR ) // assert_dbg(); // // next // prev = trackItr; // trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(trackItr->nextBlock)); //} } sint32 MEMAddBlockHeapTracking(MPTR heap, MPTR trackMem, uint32 trackMemSize) { MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap); if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP) { return 0; } if (trackMem == MPTR_NULL || trackMemSize <= (sizeof(MEMBlockHeapGroupDEPR) + sizeof(MEMBlockHeapTrackDEPR))) { return -4; } uint32 trackCount = (trackMemSize - sizeof(MEMBlockHeapGroupDEPR)) / sizeof(MEMBlockHeapTrackDEPR); MEMBlockHeapGroupDEPR* group = (MEMBlockHeapGroupDEPR*)memory_getPointerFromVirtualOffset(trackMem); group->ukn00 = _swapEndianU32(0); group->ukn04 = _swapEndianU32(0); group->trackArray = _swapEndianU32(trackMem + sizeof(MEMBlockHeapGroupDEPR)); group->trackCount = _swapEndianU32(trackCount); MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)(group + 1); track[0].previousBlock = _swapEndianU32(0); if (trackCount > 1) { for (uint32 i = 0; i < trackCount - 1; i++) { track[i].nextBlock = _swapEndianU32(memory_getVirtualOffsetFromPointer(track + i + 1)); track[i + 1].previousBlock = _swapEndianU32(0); } } // todo: Use spinlock from heap (and only use it if threadsafe heap flag is set) __OSLockScheduler(); track[trackCount - 1].nextBlock = blockHeapHead->nextFreeBlock; blockHeapHead->nextFreeBlock = _swapEndianU32(memory_getVirtualOffsetFromPointer(track)); blockHeapHead->freeBlocksLeft = _swapEndianU32(_swapEndianU32(blockHeapHead->freeBlocksLeft) + trackCount); __OSUnlockScheduler(); return 0; } uint32 MEMGetTrackingLeftInBlockHeap(MPTR heap) { MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap); if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP) { return 0; } return _swapEndianU32(blockHeapHead->freeBlocksLeft); } MPTR _MEMBlockHeap_GetFreeBlockTrack(MEMBlockHeapDEPR* blockHeapHead) { MPTR trackMPTR = _swapEndianU32(blockHeapHead->nextFreeBlock); MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(trackMPTR); MPTR nextFreeBlockMPTR = _swapEndianU32(track->nextBlock); // for unreserved tracks, nextBlock holds the next unreserved block track->nextBlock = _swapEndianU32(MPTR_NULL); blockHeapHead->nextFreeBlock = _swapEndianU32(nextFreeBlockMPTR); if (_swapEndianU32(blockHeapHead->freeBlocksLeft) == 0) { cemuLog_log(LogType::Force, "BlockHeap: No free blocks left"); assert_dbg(); } blockHeapHead->freeBlocksLeft = _swapEndianU32(_swapEndianU32(blockHeapHead->freeBlocksLeft) - 1); return trackMPTR; } /* * Release MEMBlockHeapTrack_t struct for block heap */ void _MEMBlockHeap_ReleaseBlockTrack(MEMBlockHeapDEPR* blockHeapHead, MPTR trackMPTR) { MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(trackMPTR); track->nextBlock = blockHeapHead->nextFreeBlock; blockHeapHead->nextFreeBlock = _swapEndianU32(trackMPTR); blockHeapHead->freeBlocksLeft = _swapEndianU32(_swapEndianU32(blockHeapHead->freeBlocksLeft) + 1); } sint32 _MEMBlockHeap_AllocAtBlock(MEMBlockHeapDEPR* blockHeapHead, MEMBlockHeapTrackDEPR* track, MPTR allocationAddress, uint32 size) { MPTR trackMPTR = memory_getVirtualOffsetFromPointer(track); uint32 trackEndAddr = _swapEndianU32(track->addrEnd); uint32 prefixBlockSize = allocationAddress - _swapEndianU32(track->addrStart); uint32 suffixBlockSize = (_swapEndianU32(track->addrEnd) + 1) - (allocationAddress + size); if (prefixBlockSize > 0 && suffixBlockSize > 0) { // requires two free blocks if (_swapEndianU32(blockHeapHead->freeBlocksLeft) < 2) return -1; } else if (prefixBlockSize > 0 || suffixBlockSize > 0) { // requires one free block if (_swapEndianU32(blockHeapHead->freeBlocksLeft) < 1) return -2; } MPTR currentPreviousTrack = _swapEndianU32(track->previousBlock); // remove used block from chain of free blocks (debug) if (track->isFree != _swapEndianU32(0)) { // check if block is in list of free blocks (it shouldnt be) MEMBlockHeapTrackDEPR* trackItr = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->nextFreeBlock)); while (trackItr) { if (trackItr == track) assert_dbg(); // next trackItr = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(trackItr->nextBlock)); } } _blockHeapDebugVerifyLinkOrder(blockHeapHead); // create prefix block if (prefixBlockSize > 0) { uint32 blockRangeStart = _swapEndianU32(track->addrStart); uint32 blockRangeEnd = allocationAddress - 1; MPTR prefixTrackMPTR = _MEMBlockHeap_GetFreeBlockTrack(blockHeapHead); MEMBlockHeapTrackDEPR* prefixTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(prefixTrackMPTR); prefixTrack->isFree = _swapEndianU32(1); prefixTrack->addrStart = _swapEndianU32(blockRangeStart); prefixTrack->addrEnd = _swapEndianU32(blockRangeEnd); // register new firstBlock if (blockHeapHead->headBlock == _swapEndianU32(trackMPTR)) blockHeapHead->headBlock = _swapEndianU32(prefixTrackMPTR); // update link in original previous block if (_swapEndianU32(track->previousBlock) != MPTR_NULL) { MEMBlockHeapTrackDEPR* tempTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(track->previousBlock)); tempTrack->nextBlock = _swapEndianU32(prefixTrackMPTR); } // update previous/next track link prefixTrack->nextBlock = _swapEndianU32(trackMPTR); prefixTrack->previousBlock = _swapEndianU32(currentPreviousTrack); // set prefix track as current previous track currentPreviousTrack = prefixTrackMPTR; } // update used block track->isFree = _swapEndianU32(0); track->addrStart = _swapEndianU32(allocationAddress); track->addrEnd = _swapEndianU32(allocationAddress + size - 1); track->previousBlock = _swapEndianU32(currentPreviousTrack); currentPreviousTrack = trackMPTR; // create suffix block if (suffixBlockSize > 0) { uint32 blockRangeStart = allocationAddress + size; uint32 blockRangeEnd = trackEndAddr; // get suffix track MPTR suffixTrackMPTR = _MEMBlockHeap_GetFreeBlockTrack(blockHeapHead); MEMBlockHeapTrackDEPR* suffixTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(suffixTrackMPTR); suffixTrack->isFree = _swapEndianU32(1); suffixTrack->addrStart = _swapEndianU32(blockRangeStart); suffixTrack->addrEnd = _swapEndianU32(blockRangeEnd); // update previous/next track link suffixTrack->previousBlock = _swapEndianU32(currentPreviousTrack); suffixTrack->nextBlock = track->nextBlock; // update last block of heap if (_swapEndianU32(blockHeapHead->tailBlock) == trackMPTR) blockHeapHead->tailBlock = _swapEndianU32(suffixTrackMPTR); // update link in original next block if (_swapEndianU32(track->nextBlock) != MPTR_NULL) { MEMBlockHeapTrackDEPR* tempTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(track->nextBlock)); tempTrack->previousBlock = _swapEndianU32(suffixTrackMPTR); } // update next block track->nextBlock = _swapEndianU32(suffixTrackMPTR); } _blockHeapDebugVerifyLinkOrder(blockHeapHead); // todo: Get fill value via MEMGetFillValForHeap memset(memory_getPointerFromVirtualOffset(allocationAddress), 0x00, size); return 0; } MEMBlockHeapTrackDEPR* _MEMBlockHeap_FindBlockContaining(MEMBlockHeapDEPR* blockHeapHead, MPTR memAddr) { MPTR heapStart = _swapEndianU32(blockHeapHead->heapStart); MPTR heapEnd = _swapEndianU32(blockHeapHead->heapEnd); if (memAddr < heapStart) return NULL; if (memAddr > heapEnd) return NULL; uint32 distanceToStart = memAddr - heapStart; uint32 distanceToEnd = heapEnd - memAddr; // todo: If distanceToStart < distanceToEnd -> Iterate starting from firstBlock, else iterate starting from lastBlock (and continue via track->previousBlock) MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->headBlock)); if (track == NULL) return NULL; while (track) { if (_swapEndianU32(track->addrStart) == memAddr) return track; // next // todo: Should this be ->previousBlock ? track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->nextBlock)); } return NULL; } MPTR MEMAllocFromBlockHeapEx(MPTR heap, uint32 size, sint32 alignment) { MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap); if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP) { return MPTR_NULL; } // find free block which can hold the data (including alignment) __OSLockScheduler(); // todo: replace with spinlock from heap if (alignment == 0) alignment = 4; bool allocateAtEndOfBlock = false; if (alignment < 0) { allocateAtEndOfBlock = true; alignment = -alignment; } MEMBlockHeapTrackDEPR* track; if (allocateAtEndOfBlock) track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->tailBlock)); else track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->headBlock)); cemu_assert_debug(std::popcount<unsigned int>(alignment) == 1); // not a supported alignment value while (track) { if (track->isFree != _swapEndianU32(0)) { uint32 blockRangeStart = _swapEndianU32(track->addrStart); uint32 blockRangeEnd = _swapEndianU32(track->addrEnd) + 1; if (allocateAtEndOfBlock == false) { // calculate remaining size with proper alignment uint32 alignedBlockRangeStart = (blockRangeStart + alignment - 1) & ~(alignment - 1); if (alignedBlockRangeStart < blockRangeEnd) { uint32 allocRange = blockRangeEnd - alignedBlockRangeStart; if (allocRange >= size) { sint32 allocError = _MEMBlockHeap_AllocAtBlock(blockHeapHead, track, alignedBlockRangeStart, size); _blockHeapDebugVerifyLinkOrder(blockHeapHead); __OSUnlockScheduler(); if (allocError == 0) return alignedBlockRangeStart; return MPTR_NULL; } } } else { uint32 alignedBlockRangeStart = (blockRangeEnd + 1 - size) & ~(alignment - 1); if (alignedBlockRangeStart >= blockRangeStart) { sint32 allocError = _MEMBlockHeap_AllocAtBlock(blockHeapHead, track, alignedBlockRangeStart, size); _blockHeapDebugVerifyLinkOrder(blockHeapHead); __OSUnlockScheduler(); if (allocError == 0) return alignedBlockRangeStart; return MPTR_NULL; } } } // next if (allocateAtEndOfBlock) track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->previousBlock)); else track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->nextBlock)); if (track == nullptr) break; } __OSUnlockScheduler(); return MPTR_NULL; } void MEMFreeToBlockHeap(MPTR heap, MPTR memAddr) { MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap); if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP) { return; } __OSLockScheduler(); // todo: replace with spinlock from heap (if heap threadsafe flag is set) _blockHeapDebugVerifyLinkOrder(blockHeapHead); MEMBlockHeapTrackDEPR* block = _MEMBlockHeap_FindBlockContaining(blockHeapHead, memAddr); if (block != NULL) { MPTR blockMPTR = memory_getVirtualOffsetFromPointer(block); #ifdef CEMU_DEBUG_ASSERT if (block->isFree != 0) assert_dbg(); _blockHeapDebugVerifyLinkOrder(blockHeapHead); #endif // mark current block as free block->isFree = _swapEndianU32(1); // attempt to merge with previous block if (_swapEndianU32(block->previousBlock) != MPTR_NULL) { MPTR previousBlockMPTR = _swapEndianU32(block->previousBlock); MEMBlockHeapTrackDEPR* previousBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(previousBlockMPTR); if (_swapEndianU32(previousBlock->isFree) != 0) { // merge previousBlock->addrEnd = block->addrEnd; previousBlock->nextBlock = block->nextBlock; if (_swapEndianU32(block->nextBlock) != MPTR_NULL) { MEMBlockHeapTrackDEPR* tempNextBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(block->nextBlock)); tempNextBlock->previousBlock = _swapEndianU32(previousBlockMPTR); } // release removed block _MEMBlockHeap_ReleaseBlockTrack(blockHeapHead, blockMPTR); // debug _blockHeapDebugVerifyIfBlockIsLinked(blockHeapHead, (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(blockMPTR)); // merged block becomes the new current block blockMPTR = previousBlockMPTR; block = previousBlock; } } // attempt to merge with next block if (_swapEndianU32(block->nextBlock) != MPTR_NULL) { MPTR nextBlockMPTR = _swapEndianU32(block->nextBlock); MEMBlockHeapTrackDEPR* nextBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(nextBlockMPTR); if (_swapEndianU32(nextBlock->isFree) != 0) { // merge block->addrEnd = nextBlock->addrEnd; block->nextBlock = nextBlock->nextBlock; if (_swapEndianU32(nextBlock->nextBlock) != MPTR_NULL) { MEMBlockHeapTrackDEPR* tempNextBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(nextBlock->nextBlock)); tempNextBlock->previousBlock = _swapEndianU32(blockMPTR); } //// merged block becomes the new current block //blockMPTR = previousBlockMPTR; //block = previousBlock; // update last block if (_swapEndianU32(blockHeapHead->tailBlock) == nextBlockMPTR) { blockHeapHead->tailBlock = _swapEndianU32(blockMPTR); } // release removed block _MEMBlockHeap_ReleaseBlockTrack(blockHeapHead, nextBlockMPTR); // debug _blockHeapDebugVerifyIfBlockIsLinked(blockHeapHead, (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(nextBlockMPTR)); } } } _blockHeapDebugVerifyLinkOrder(blockHeapHead); __OSUnlockScheduler(); } uint32 MEMGetTotalFreeSizeForBlockHeap(MEMBlockHeapDEPR* blockHeap) { if (!blockHeap || blockHeap->magic != MEMHeapMagic::BLOCK_HEAP) { cemu_assert_suspicious(); return 0; } __OSLockScheduler(); // todo: replace with spinlock from heap (if heap threadsafe flag is set) uint32 totalSize = 0; // sum up all free blocks MPTR blockMPTR = _swapEndianU32(blockHeap->headBlock); while (blockMPTR) { MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(blockMPTR); if (track->isFree != _swapEndianU32(0)) { // get block size uint32 blockSize = _swapEndianU32(track->addrEnd) - _swapEndianU32(track->addrStart) + 1; // add to totalSize totalSize += blockSize; } // next blockMPTR = _swapEndianU32(track->nextBlock); } __OSUnlockScheduler(); // todo: replace with spinlock from heap (if heap threadsafe flag is set) return totalSize; } void MEMDumpBlockHeap(MPTR heap) { MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap); if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP) { cemu_assert_suspicious(); return; } // iterate reserved/sized blocks debug_printf("### MEMDumpBlockHeap ###\n"); __OSLockScheduler(); // todo: replace with spinlock from heap MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->headBlock)); while (track) { uint32 blockRangeStart = _swapEndianU32(track->addrStart); uint32 blockRangeEnd = _swapEndianU32(track->addrEnd) + 1; debug_printf(" %08x %08x - %08x prev/next %08x %08x isFree: %d\n", memory_getVirtualOffsetFromPointer(track), blockRangeStart, blockRangeEnd, _swapEndianU32(track->previousBlock), _swapEndianU32(track->nextBlock), _swapEndianU32(track->isFree)); // next track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->nextBlock)); } debug_printf("\n"); __OSUnlockScheduler(); } void InitializeMEMBlockHeap() { cafeExportRegister("coreinit", MEMInitBlockHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMDestroyBlockHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMGetAllocatableSizeForBlockHeapEx, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMAddBlockHeapTracking, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMGetTrackingLeftInBlockHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMAllocFromBlockHeapEx, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMFreeToBlockHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMGetTotalFreeSizeForBlockHeap, LogType::CoreinitMem); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" namespace coreinit { struct MEMBlockHeapTrack2_t { /* +0x00 */ MEMPTR<void> addrStart; /* +0x04 */ MEMPTR<void> addrEnd; /* +0x08 */ uint32be isFree; // if 0 -> block is used /* +0x0C */ MEMPTR<void> previousBlock; /* +0x10 */ MEMPTR<void> nextBlock; }; static_assert(sizeof(MEMBlockHeapTrack2_t) == 0x14); struct MEMBlockHeap2_t : MEMHeapBase { /* +0x34 */ uint8 ukn[0x50 - 0x34]; /* +0x50 */ MEMBlockHeapTrack2_t track; /* +0x64 */ MEMPTR<void> headBlock; /* +0x68 */ MEMPTR<void> tailBlock; /* +0x6C */ MEMPTR<void> nextFreeBlock; /* +0x70 */ uint32be freeBlocksLeft; /* +0x74 */ uint8 padding[0x80 - 0x74]; }; static_assert(sizeof(MEMBlockHeap2_t) == 0x80); MEMHeapHandle MEMInitBlockHeap(MEMBlockHeap2_t* memStart, void* startAddr, void* endAddr, void* initTrackMem, uint32 initTrackMemSize, uint32 createFlags); void* MEMDestroyBlockHeap(MEMHeapHandle hHeap); sint32 MEMAddBlockHeapTracking(MPTR heap, MPTR trackMem, uint32 trackMemSize); void InitializeMEMBlockHeap(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" #define EXP_HEAP_GET_FROM_FREE_BLOCKCHAIN(__blockchain__) (MEMExpHeapHead2*)((uintptr_t)__blockchain__ - offsetof(MEMExpHeapHead2, expHeapHead) - offsetof(MEMExpHeapHead40_t, chainFreeBlocks)) namespace coreinit { #define MBLOCK_TYPE_FREE ('FR') #define MBLOCK_TYPE_USED ('UD') struct MBlock2_t { uint32be fields; // 0x00 uint32be dataSize; // 0x04 | size of raw data (excluding size of this struct) MEMPTR<MBlock2_t> prevBlock; // 0x08 MEMPTR<MBlock2_t> nextBlock; // 0x0C uint16be typeCode; // 0x10 uint16be padding; // 0x12 }; static_assert(sizeof(MBlock2_t) == 0x14); struct ExpMemBlockRegion { uintptr_t start; uintptr_t end; }; #define MBLOCK_GET_HEADER(__mblock__) (MBlock2_t*)((uintptr_t)__mblock__ - sizeof(MBlock2_t)) #define MBLOCK_GET_MEMORY(__mblock__) ((uintptr_t)__mblock__ + sizeof(MBlock2_t)) #define MBLOCK_GET_END(__mblock__) ((uintptr_t)__mblock__ + sizeof(MBlock2_t) + (uint32)__mblock__->dataSize) #pragma region internal MBlock2_t* _MEMExpHeap_InitMBlock(ExpMemBlockRegion* region, uint16 typeCode) { MBlock2_t* mBlock = (MBlock2_t*)region->start; memset(mBlock, 0x00, sizeof(MBlock2_t)); mBlock->typeCode = typeCode; mBlock->dataSize = (uint32)(region->end - (region->start + sizeof(MBlock2_t))); return mBlock; } bool _MEMExpHeap_CheckMBlock(MBlock2_t* mBlock, MEMHeapHandle heap, uint16 typeCode, const char* debugText, int options) { #ifndef MBLOCK_DEBUG_ASSERT return true; #endif const uintptr_t mBlockAddr = (uintptr_t)mBlock; const uintptr_t mBlockRawAddr = MBLOCK_GET_MEMORY(mBlock); uintptr_t heapStart = 0, heapEnd = 0; if (heap) { heapStart = (uintptr_t)heap->heapStart.GetPtr(); heapEnd = (uintptr_t)heap->heapEnd.GetPtr(); if (heap == MEM_HEAP_INVALID_HANDLE) { if (mBlockAddr < heapStart || mBlockRawAddr > heapEnd) { return false; } } } if (typeCode == (uint16)mBlock->typeCode) { if (heap == MEM_HEAP_INVALID_HANDLE) return true; const uint32 dataSize = mBlock->dataSize; if (mBlockRawAddr + dataSize <= heapEnd) return true; return false; } return false; } bool _MEMExpHeap_IsValidUsedMBlock(const void* memBlock, MEMHeapHandle heap) { #ifndef MBLOCK_DEBUG_ASSERT return true; #endif if (!memBlock) return false; heap->AcquireLock(); bool valid = false; MBlock2_t* mBlock = MBLOCK_GET_HEADER(memBlock); if (heap) { MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; MEMPTR<MBlock2_t> usedBlock = expHeap->expHeapHead.chainUsedBlocks.headMBlock; while (usedBlock) { if (mBlock == usedBlock.GetPtr()) { valid = _MEMExpHeap_CheckMBlock(mBlock, heap, MBLOCK_TYPE_USED, "used", 0); break; } usedBlock = usedBlock->nextBlock; } } else { valid = _MEMExpHeap_CheckMBlock(mBlock, heap, MBLOCK_TYPE_USED, "used", 0); } heap->ReleaseLock(); return valid; } void _MEMExpHeap_GetRegionOfMBlock(ExpMemBlockRegion* region, MBlock2_t* memBlock) { uint32 alignment = ((memBlock->fields) >> 8) & 0x7FFFFF; uintptr_t memBlockAddr = (uintptr_t)memBlock; region->start = memBlockAddr - alignment; region->end = memBlockAddr + sizeof(MBlock2_t) + (uint32)memBlock->dataSize; } void* _MEMExpHeap_RemoveMBlock(MBlockChain2_t* blockChain, MBlock2_t* block) { MEMPTR<MBlock2_t> prevBlock = block->prevBlock; MEMPTR<MBlock2_t> nextBlock = block->nextBlock; if (prevBlock) prevBlock->nextBlock = nextBlock; else blockChain->headMBlock = nextBlock; if (nextBlock) nextBlock->prevBlock = prevBlock; else blockChain->tailMBlock = prevBlock; return prevBlock.GetPtr(); } MBlock2_t* _MEMExpHeap_InsertMBlock(MBlockChain2_t* blockChain, MBlock2_t* newBlock, MBlock2_t* prevBlock) { newBlock->prevBlock = prevBlock; MEMPTR<MBlock2_t> nextBlock; if (prevBlock) { nextBlock = prevBlock->nextBlock; prevBlock->nextBlock = newBlock; } else { nextBlock = blockChain->headMBlock; blockChain->headMBlock = newBlock; } newBlock->nextBlock = nextBlock; if (nextBlock) nextBlock->prevBlock = newBlock; else blockChain->tailMBlock = newBlock; return newBlock; } bool _MEMExpHeap_RecycleRegion(MBlockChain2_t* blockChain, ExpMemBlockRegion* region) { ExpMemBlockRegion newRegion; newRegion.start = region->start; newRegion.end = region->end; MEMPTR<MBlock2_t> prev; MEMPTR<MBlock2_t> find = blockChain->headMBlock; while (find) { MBlock2_t* findMBlock = find.GetPtr(); const uintptr_t blockAddr = (uintptr_t)findMBlock; if (blockAddr >= region->start) { if (blockAddr == region->end) { newRegion.end = MBLOCK_GET_END(findMBlock); _MEMExpHeap_RemoveMBlock(blockChain, findMBlock); MEMExpHeapHead2* heap = EXP_HEAP_GET_FROM_FREE_BLOCKCHAIN(blockChain); uint8 options = heap->flags; if (HAS_FLAG(options, MEM_HEAP_OPTION_FILL)) { const uint32 fillVal = MEMGetFillValForHeap(HEAP_FILL_TYPE::ON_FREE); memset(findMBlock, fillVal, sizeof(MBlock2_t)); } } break; } prev = find; find = findMBlock->nextBlock; } if (prev) { MBlock2_t* prevMBlock = prev.GetPtr(); if (MBLOCK_GET_END(prevMBlock) == region->start) { newRegion.start = (uintptr_t)prevMBlock; prev = (MBlock2_t*)_MEMExpHeap_RemoveMBlock(blockChain, prevMBlock); } } if ((newRegion.end - newRegion.start) < sizeof(MBlock2_t)) return false; MEMExpHeapHead2* heap = EXP_HEAP_GET_FROM_FREE_BLOCKCHAIN(blockChain); uint8 options = heap->flags; if (HAS_FLAG(options, MEM_HEAP_OPTION_FILL)) { const uint32 fillVal = MEMGetFillValForHeap(HEAP_FILL_TYPE::ON_FREE); memset((void*)region->start, fillVal, region->end - region->start); } MBlock2_t* newBlock = _MEMExpHeap_InitMBlock(&newRegion, MBLOCK_TYPE_FREE); _MEMExpHeap_InsertMBlock(blockChain, newBlock, prev.GetPtr()); return true; } void* _MEMExpHeap_AllocUsedBlockFromFreeBlock(MBlockChain2_t* blockChain, MBlock2_t* freeBlock, uintptr_t blockMemStart, uint32 size, MEMExpHeapAllocDirection direction) { MEMExpHeapHead2* heap = EXP_HEAP_GET_FROM_FREE_BLOCKCHAIN(blockChain); ExpMemBlockRegion freeRegion; _MEMExpHeap_GetRegionOfMBlock(&freeRegion, freeBlock); ExpMemBlockRegion newRegion = {blockMemStart + size, freeRegion.end}; freeRegion.end = blockMemStart - sizeof(MBlock2_t); MBlock2_t* prevBlock = (MBlock2_t*)_MEMExpHeap_RemoveMBlock(blockChain, freeBlock); if ((freeRegion.end - freeRegion.start) >= 0x18 && (direction != MEMExpHeapAllocDirection::HEAD || HAS_FLAG(heap->expHeapHead.fields, MEM_EXPHEAP_USE_ALIGN_MARGIN))) { MBlock2_t* newBlock = _MEMExpHeap_InitMBlock(&freeRegion, MBLOCK_TYPE_FREE); prevBlock = _MEMExpHeap_InsertMBlock(blockChain, newBlock, prevBlock); } else freeRegion.end = freeRegion.start; if ((newRegion.end - newRegion.start) >= 0x18 && (direction != MEMExpHeapAllocDirection::TAIL || HAS_FLAG(heap->expHeapHead.fields, MEM_EXPHEAP_USE_ALIGN_MARGIN))) { MBlock2_t* newBlock = _MEMExpHeap_InitMBlock(&newRegion, MBLOCK_TYPE_FREE); prevBlock = _MEMExpHeap_InsertMBlock(blockChain, newBlock, prevBlock); } else newRegion.start = newRegion.end; uint8 options = heap->flags; if (HAS_FLAG(options, MEM_HEAP_OPTION_CLEAR)) { memset((void*)freeRegion.end, 0x00, newRegion.start - freeRegion.end); } else if (HAS_FLAG(options, MEM_HEAP_OPTION_FILL)) { const uint32 fillValue = MEMGetFillValForHeap(HEAP_FILL_TYPE::ON_ALLOC); memset((void*)freeRegion.end, fillValue, newRegion.start - freeRegion.end); } ExpMemBlockRegion allocRegion = {blockMemStart - sizeof(MBlock2_t), newRegion.start}; MBlock2_t* newBlock = _MEMExpHeap_InitMBlock(&allocRegion, MBLOCK_TYPE_USED); uint32 fields = (uint32)newBlock->fields; if(direction == MEMExpHeapAllocDirection::TAIL) fields |= 1 << 31; else fields &= ~(1 << 31); uint32 alignmentPadding = (uint32)((uintptr_t)newBlock - (uintptr_t)freeRegion.end); fields |= (alignmentPadding & 0x7FFFFF) << 8; fields |= ((uint32)heap->expHeapHead.groupID & 0xFF); newBlock->fields = fields; MBlock2_t* tailBlock = heap->expHeapHead.chainUsedBlocks.tailMBlock.GetPtr(); _MEMExpHeap_InsertMBlock(&heap->expHeapHead.chainUsedBlocks, newBlock, tailBlock); return (void*)blockMemStart; } void* _MEMExpHeap_AllocFromTail(MEMHeapHandle heap, uint32 size, int alignment) { MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; const bool searchForFirstEntry = (expHeap->expHeapHead.fields&1) == MEM_EXPHEAP_ALLOC_MODE_FIRST; const int alignmentMinusOne = alignment - 1; MBlock2_t* freeBlock = nullptr; uintptr_t blockMemStart = 0; uint32 foundSize = -1; for (MBlock2_t* findBlock = expHeap->expHeapHead.chainFreeBlocks.tailMBlock.GetPtr(); findBlock != nullptr; findBlock = findBlock->prevBlock.GetPtr()) { const uintptr_t blockMemory = MBLOCK_GET_MEMORY(findBlock); const uint32 dataSize = (uint32)findBlock->dataSize; const uintptr_t alignedEndBlockMemory = (blockMemory + dataSize - size) & ~alignmentMinusOne; if (alignedEndBlockMemory < blockMemory) continue; if (foundSize <= dataSize) continue; freeBlock = findBlock; blockMemStart = alignedEndBlockMemory; foundSize = dataSize; if (searchForFirstEntry) break; if (foundSize == size) break; } void* mem = nullptr; if (freeBlock) mem = _MEMExpHeap_AllocUsedBlockFromFreeBlock(&expHeap->expHeapHead.chainFreeBlocks, freeBlock, blockMemStart, size, coreinit::MEMExpHeapAllocDirection::TAIL); return mem; } void* _MEMExpHeap_AllocFromHead(MEMHeapHandle heap, uint32 size, int alignment) { MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; const bool searchForFirstEntry = (expHeap->expHeapHead.fields&1) == MEM_EXPHEAP_ALLOC_MODE_FIRST; const int alignmentMinusOne = alignment - 1; MBlock2_t* freeBlock = nullptr; uintptr_t blockMemStart = 0; uint32 foundSize = -1; for (MBlock2_t* findBlock = expHeap->expHeapHead.chainFreeBlocks.headMBlock.GetPtr(); findBlock != nullptr; findBlock = findBlock->nextBlock.GetPtr()) { const uintptr_t blockMemory = MBLOCK_GET_MEMORY(findBlock); const uintptr_t alignedBlockMemory = (blockMemory + alignmentMinusOne) & ~alignmentMinusOne; const uint32 dataSize = (uint32)findBlock->dataSize; if (dataSize < alignedBlockMemory - blockMemory + size) continue; if (foundSize <= dataSize) continue; freeBlock = findBlock; blockMemStart = alignedBlockMemory; foundSize = dataSize; if (searchForFirstEntry) break; if (foundSize == size) break; } void* mem = nullptr; if (freeBlock) mem = _MEMExpHeap_AllocUsedBlockFromFreeBlock(&expHeap->expHeapHead.chainFreeBlocks, freeBlock, blockMemStart, size, coreinit::MEMExpHeapAllocDirection::HEAD); return mem; } MEMHeapHandle _MEMExpHeap_InitHeap(void* startAddress, void* endAddress, uint32 createFlags) { MEMExpHeapHead2* header = (MEMExpHeapHead2*)startAddress; uintptr_t heapStart = (uintptr_t)startAddress + sizeof(MEMExpHeapHead2); MEMInitHeapBase(header, coreinit::MEMHeapMagic::EXP_HEAP, (void*)heapStart, endAddress, createFlags); ExpMemBlockRegion region; region.start = (uintptr_t)header->heapStart.GetPtr(); region.end = (uintptr_t)header->heapEnd.GetPtr(); MBlock2_t* mBlock = _MEMExpHeap_InitMBlock(®ion, MBLOCK_TYPE_FREE); header->expHeapHead.chainFreeBlocks.headMBlock = mBlock; header->expHeapHead.chainFreeBlocks.tailMBlock = mBlock; header->expHeapHead.chainUsedBlocks.headMBlock = nullptr; header->expHeapHead.chainUsedBlocks.tailMBlock = nullptr; header->expHeapHead.groupID = 0; header->expHeapHead.fields = 0; return (MEMHeapHandle)header; } MEMHeapHandle MEMCreateExpHeap(void* startAddress, uint32 size) { return MEMCreateExpHeapEx(startAddress, size, MEM_HEAP_OPTION_NONE); } void* MEMAllocFromExpHeap(MEMHeapHandle heap, uint32 size) { return MEMAllocFromExpHeapEx(heap, size, MEM_HEAP_DEFAULT_ALIGNMENT); } uint32 MEMGetAllocatableSizeForExpHeap(MEMHeapHandle heap) { return MEMGetAllocatableSizeForExpHeapEx(heap, MEM_HEAP_DEFAULT_ALIGNMENT); } void IsValidExpHeapHandle_(MEMHeapHandle heap) { cemu_assert_debug(heap != MEM_HEAP_INVALID_HANDLE); cemu_assert_debug(heap->magic == coreinit::MEMHeapMagic::EXP_HEAP); } #pragma endregion #pragma region exported MEMHeapHandle MEMCreateExpHeapEx(void* startAddress, uint32 size, uint32 createFlags) { if (startAddress == nullptr) return MEM_HEAP_INVALID_HANDLE; const uintptr_t alignedStart = ((uintptr_t)startAddress + MIN_ALIGNMENT_MINUS_ONE) & (~MIN_ALIGNMENT_MINUS_ONE); const uintptr_t alignedEnd = ((uintptr_t)startAddress + size) & (~MIN_ALIGNMENT_MINUS_ONE); if (alignedStart > alignedEnd) return MEM_HEAP_INVALID_HANDLE; if (alignedEnd - alignedStart < 0x6C) return MEM_HEAP_INVALID_HANDLE; MEMHeapHandle result = _MEMExpHeap_InitHeap((void*)alignedStart, (void*)alignedEnd, createFlags); if (result) MEMHeapTable_Add(result); return result; } void* MEMDestroyExpHeap(MEMHeapHandle heap) { IsValidExpHeapHandle_(heap); MEMBaseDestroyHeap(heap); MEMHeapTable_Remove(heap); return heap; } void* MEMAllocFromExpHeapEx(MEMHeapHandle heap, uint32 size, sint32 alignment) { IsValidExpHeapHandle_(heap); if (alignment == 0) { // this is a guaranteed crash on the actual console cemuLog_log(LogType::Force, "MEMAllocFromExpHeapEx(): Alignment 0 not allowed"); alignment = 4; return nullptr; } if (size == 0) size = 1; size = (size + MIN_ALIGNMENT_MINUS_ONE) & ~MIN_ALIGNMENT_MINUS_ONE; heap->AcquireLock(); void* mem; if (alignment < 0) { alignment = -alignment; mem = _MEMExpHeap_AllocFromTail(heap, size, alignment); } else { mem = _MEMExpHeap_AllocFromHead(heap, size, alignment); } heap->ReleaseLock(); return mem; } void MEMFreeToExpHeap(MEMHeapHandle heap, void* mem) { IsValidExpHeapHandle_(heap); if (mem) { heap->AcquireLock(); cemu_assert_debug(_MEMExpHeap_IsValidUsedMBlock(mem, heap) == true); cemu_assert_debug((uintptr_t)heap->heapStart.GetPtr() <= (uintptr_t)mem && (uintptr_t)mem < (uintptr_t)heap->heapEnd.GetPtr()); MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; ExpMemBlockRegion region; MBlock2_t* mBlock = MBLOCK_GET_HEADER(mem); _MEMExpHeap_GetRegionOfMBlock(®ion, mBlock); _MEMExpHeap_RemoveMBlock(&expHeap->expHeapHead.chainUsedBlocks, mBlock); _MEMExpHeap_RecycleRegion(&expHeap->expHeapHead.chainFreeBlocks, ®ion); heap->ReleaseLock(); } } uint16 MEMSetAllocModeForExpHeap(MEMHeapHandle heap, uint16 mode) { IsValidExpHeapHandle_(heap); heap->AcquireLock(); MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; const uint16 oldMode = expHeap->expHeapHead.fields & 0x1; expHeap->expHeapHead.fields |= (mode & 0x1); heap->ReleaseLock(); return oldMode; } uint16 MEMGetAllocModeForExpHeap(MEMHeapHandle heap) { IsValidExpHeapHandle_(heap); heap->AcquireLock(); MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; const uint16 mode = expHeap->expHeapHead.fields & 0x1; heap->ReleaseLock(); return mode; } uint32 MEMAdjustExpHeap(MEMHeapHandle heap) { uint32 newSize = 0; IsValidExpHeapHandle_(heap); heap->AcquireLock(); MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; MEMPTR<MBlock2_t> tailBlock = expHeap->expHeapHead.chainFreeBlocks.tailMBlock; if (tailBlock) { MBlock2_t* tail = tailBlock.GetPtr(); uintptr_t blockMemEnd = MBLOCK_GET_END(tail); uintptr_t heapEnd = (uintptr_t)heap->heapEnd.GetPtr(); if (blockMemEnd == heapEnd) { _MEMExpHeap_RemoveMBlock(&expHeap->expHeapHead.chainFreeBlocks, tail); uint32 removedBlockSize = sizeof(MBlock2_t) + (uint32)tail->dataSize; uintptr_t newHeapEnd = heapEnd - removedBlockSize; heap->heapEnd = (void*)newHeapEnd; newSize = (uint32)(newHeapEnd - (uintptr_t)heap); } } heap->ReleaseLock(); return newSize; } uint32 MEMResizeForMBlockExpHeap(MEMHeapHandle heap, void* memBlock, uint32 size) { uint32 newSize = 0; IsValidExpHeapHandle_(heap); cemu_assert_debug(memBlock != nullptr); heap->AcquireLock(); MBlock2_t* mBlock = MBLOCK_GET_HEADER(memBlock); const uint32 dataSize = mBlock->dataSize; if (dataSize != size) { cemu_assert_debug(_MEMExpHeap_IsValidUsedMBlock(memBlock, heap)); MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; if (size <= dataSize) { ExpMemBlockRegion region; region.start = (uintptr_t)memBlock + size; region.end = MBLOCK_GET_END(mBlock); mBlock->dataSize = size; if (!_MEMExpHeap_RecycleRegion(&expHeap->expHeapHead.chainFreeBlocks, ®ion)) mBlock->dataSize = dataSize; newSize = (uint32)mBlock->dataSize; } else { MEMPTR<MBlock2_t> free = expHeap->expHeapHead.chainFreeBlocks.headMBlock; const uintptr_t blockEndAddr = MBLOCK_GET_END(mBlock); while (free) { MBlock2_t* freeBlock = free.GetPtr(); if ((uintptr_t)freeBlock == blockEndAddr) { const uint32 freeDataSize = freeBlock->dataSize; if (size > (dataSize + freeDataSize + sizeof(MBlock2_t))) { newSize = 0; break; } ExpMemBlockRegion region; _MEMExpHeap_GetRegionOfMBlock(®ion, freeBlock); MBlock2_t* prevBlock = (MBlock2_t*)_MEMExpHeap_RemoveMBlock(&expHeap->expHeapHead.chainFreeBlocks, freeBlock); uintptr_t oldStart = region.start; region.start = (uintptr_t)memBlock + size; if (region.end - region.start < sizeof(MBlock2_t)) region.start = region.end; mBlock->dataSize = (uint32)(region.start - (uintptr_t)memBlock); if (region.end - region.start >= sizeof(MBlock2_t)) { MBlock2_t* newBlock = _MEMExpHeap_InitMBlock(®ion, MBLOCK_TYPE_FREE); _MEMExpHeap_InsertMBlock(&expHeap->expHeapHead.chainFreeBlocks, newBlock, prevBlock); } if (HAS_FLAG(heap->flags, MEM_HEAP_OPTION_CLEAR)) memset((void*)oldStart, 0x00, region.start - oldStart); else if (HAS_FLAG(heap->flags, MEM_HEAP_OPTION_FILL)) { const uint32 fillValue = MEMGetFillValForHeap(HEAP_FILL_TYPE::ON_ALLOC); memset((void*)oldStart, fillValue, region.start - oldStart); } newSize = (uint32)mBlock->dataSize; break; } free = freeBlock->nextBlock; } if (!free) newSize = 0; } } heap->ReleaseLock(); return newSize; } uint32 MEMGetTotalFreeSizeForExpHeap(MEMHeapHandle heap) { uint32 freeSize = 0; IsValidExpHeapHandle_(heap); heap->AcquireLock(); MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; MEMPTR<MBlock2_t> block = expHeap->expHeapHead.chainFreeBlocks.headMBlock; while (block) { freeSize += (uint32)block->dataSize; block = block->nextBlock; } heap->ReleaseLock(); return freeSize; } uint32 MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle heap, sint32 alignment) { uint32 result = 0; IsValidExpHeapHandle_(heap); cemu_assert_debug((alignment & 3) == 0); alignment = abs(alignment); heap->AcquireLock(); MEMExpHeapHead2* exp_heap = (MEMExpHeapHead2*)heap; const sint32 alignment_minus_one = alignment - 1; uint32 smallest_alignment_space = -1; for(auto free_block = exp_heap->expHeapHead.chainFreeBlocks.headMBlock; free_block; free_block = free_block->nextBlock) { MBlock2_t* block = free_block.GetPtr(); const uintptr_t block_memory = MBLOCK_GET_MEMORY(block); const uintptr_t block_end = MBLOCK_GET_END(block); const uintptr_t aligned_memory = (block_memory + alignment_minus_one) & ~alignment_minus_one; if (aligned_memory >= block_end) continue; const uint32 size = (uint32)(block_end - aligned_memory); const uint32 alignment_space = (uint32)(aligned_memory - block_memory); if (size < result) continue; if (size == result) { if (smallest_alignment_space <= alignment_space) continue; } smallest_alignment_space = alignment_space; result = size; } heap->ReleaseLock(); return result; } uint16 MEMSetGroupIDForExpHeap(MEMHeapHandle heap, uint16 groupId) { IsValidExpHeapHandle_(heap); heap->AcquireLock(); MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; const uint16 oldGroupId = expHeap->expHeapHead.groupID; expHeap->expHeapHead.groupID = groupId; heap->ReleaseLock(); return oldGroupId; } uint16 MEMGetGroupIDForExpHeap(MEMHeapHandle heap) { IsValidExpHeapHandle_(heap); heap->AcquireLock(); MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; const uint16 oldGroupId = expHeap->expHeapHead.groupID; heap->ReleaseLock(); return oldGroupId; } void MEMVisitAllocatedForExpHeap(MEMHeapHandle heap, const MEMPTR<void>& visitor, uint32 userParam) { IsValidExpHeapHandle_(heap); cemu_assert_debug(visitor != nullptr); heap->AcquireLock(); MEMPTR<MEMHeapBase> heapMPTR = heap; MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; MEMPTR<MBlock2_t> find = expHeap->expHeapHead.chainUsedBlocks.headMBlock; while (find) { MBlock2_t* mBlock = find.GetPtr(); MEMPTR<void> memMPTR = (void*)MBLOCK_GET_MEMORY(mBlock); PPCCoreCallback(visitor.GetMPTR(), memMPTR.GetMPTR(), heapMPTR.GetMPTR(), userParam); find = mBlock->nextBlock; } heap->ReleaseLock(); } uint32 MEMGetSizeForMBlockExpHeap(const void* memBlock) { cemu_assert_debug(_MEMExpHeap_IsValidUsedMBlock(memBlock, nullptr)); MBlock2_t* mBlock = MBLOCK_GET_HEADER(memBlock); return mBlock->dataSize; } uint16 MEMGetGroupIDForMBlockExpHeap(const void* memBlock) { cemu_assert_debug(_MEMExpHeap_IsValidUsedMBlock(memBlock, nullptr)); MBlock2_t* mBlock = MBLOCK_GET_HEADER(memBlock); return (uint32)mBlock->fields & 0xFF; } uint16 MEMGetAllocDirForMBlockExpHeap(const void* memBlock) { cemu_assert_debug(_MEMExpHeap_IsValidUsedMBlock(memBlock, nullptr)); MBlock2_t* mBlock = MBLOCK_GET_HEADER(memBlock); const uint32 fields = mBlock->fields; return (fields >> 31) & 1; } bool MEMCheckExpHeap(MEMHeapHandle heap, uint32 options) { if (heap == MEM_HEAP_INVALID_HANDLE || heap->magic != coreinit::MEMHeapMagic::EXP_HEAP) return false; heap->AcquireLock(); // todo heap->ReleaseLock(); return true; } bool MEMCheckForMBlockExpHeap(const void* memBlock, MEMHeapHandle heap, uint32 options) { return true; } void _DefaultAllocatorForExpHeap_Alloc(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(allocator, MEMAllocator, 0); ppcDefineParamU32(size, 1); MEMPTR<void> result = MEMAllocFromExpHeapEx((MEMHeapHandle)allocator->heap.GetPtr(), size, (uint32)allocator->param1); osLib_returnFromFunction(hCPU, result.GetMPTR()); } void _DefaultAllocatorForExpHeap_Free(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(allocator, MEMAllocator, 0); ppcDefineParamMEMPTR(mem, void, 1); MEMFreeToExpHeap((MEMHeapHandle)allocator->heap.GetPtr(), mem); osLib_returnFromFunction(hCPU, 0); } SysAllocator<MEMAllocatorFunc> gExpHeapDefaultAllocator; void MEMInitAllocatorForExpHeap(MEMAllocator* allocator, MEMHeapHandle heap, sint32 alignment) { allocator->func = gExpHeapDefaultAllocator.GetPtr(); gExpHeapDefaultAllocator->funcAlloc = PPCInterpreter_makeCallableExportDepr(_DefaultAllocatorForExpHeap_Alloc); gExpHeapDefaultAllocator->funcFree = PPCInterpreter_makeCallableExportDepr(_DefaultAllocatorForExpHeap_Free); allocator->heap = heap; allocator->param1 = alignment; allocator->param2 = 0; } #pragma endregion #pragma region wrapper void expheap_test(); void export_MEMCreateExpHeapEx(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(startAddress, void, 0); ppcDefineParamU32(size, 1); ppcDefineParamU16(options, 2); MEMPTR<MEMHeapBase> heap = MEMCreateExpHeapEx(startAddress.GetPtr(), size, options); cemuLog_logDebug(LogType::CoreinitMem, "MEMCreateExpHeapEx(0x{:08x}, 0x{:x}, 0x{:x}) Result: 0x{:08x}", startAddress.GetMPTR(), size, options, heap.GetMPTR()); osLib_returnFromFunction(hCPU, heap.GetMPTR()); } void export_MEMDestroyExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); cemuLog_logDebug(LogType::CoreinitMem, "MEMDestroyExpHeap(0x{:08x})", heap.GetMPTR()); MEMPTR<MEMHeapBase> oldHeap = (MEMHeapBase*)MEMDestroyExpHeap(heap.GetPtr()); osLib_returnFromFunction(hCPU, oldHeap.GetMPTR()); } void export_MEMAllocFromExpHeapEx(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); ppcDefineParamU32(size, 1); ppcDefineParamS32(alignment, 2); MEMPTR<void> mem = MEMAllocFromExpHeapEx(heap.GetPtr(), size, alignment); cemuLog_logDebug(LogType::CoreinitMem, "MEMAllocFromExpHeapEx(0x{:08x}, 0x{:x}, {}) Result: 0x{:08x}", heap.GetMPTR(), size, alignment, mem.GetMPTR()); osLib_returnFromFunction(hCPU, mem.GetMPTR()); } void export_MEMFreeToExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); ppcDefineParamMEMPTR(mem, void, 1); cemuLog_logDebug(LogType::CoreinitMem, "MEMFreeToExpHeap(0x{:08x}, 0x{:08x})", heap.GetMPTR(), mem.GetMPTR()); MEMFreeToExpHeap(heap.GetPtr(), mem.GetPtr()); osLib_returnFromFunction(hCPU, 0); } void export_MEMSetAllocModeForExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); ppcDefineParamU16(mode, 1); cemuLog_logDebug(LogType::CoreinitMem, "MEMSetAllocModeForExpHeap(0x{:08x}, {})", heap.GetMPTR(), mode); uint16 oldMode = MEMSetAllocModeForExpHeap(heap.GetPtr(), mode); osLib_returnFromFunction(hCPU, oldMode); } void export_MEMGetAllocModeForExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); cemuLog_logDebug(LogType::CoreinitMem, "MEMGetAllocModeForExpHeap(0x{:08x})", heap.GetMPTR()); uint16 oldMode = MEMGetAllocModeForExpHeap(heap.GetPtr()); osLib_returnFromFunction(hCPU, oldMode); } void export_MEMAdjustExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); cemuLog_logDebug(LogType::CoreinitMem, "MEMAdjustExpHeap(0x{:08x})", heap.GetMPTR()); uint32 newSize = MEMAdjustExpHeap(heap.GetPtr()); osLib_returnFromFunction(hCPU, newSize); } void export_MEMResizeForMBlockExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); ppcDefineParamMEMPTR(mem, void, 1); ppcDefineParamU32(size, 2); uint32 newSize = MEMResizeForMBlockExpHeap(heap.GetPtr(), mem.GetPtr(), size); cemuLog_logDebug(LogType::CoreinitMem, "MEMResizeForMBlockExpHeap(0x{:08x}, 0x{:08x}, 0x{:x}) Result: 0x{:x}", heap.GetMPTR(), mem.GetMPTR(), size, newSize); osLib_returnFromFunction(hCPU, newSize); } void export_MEMGetTotalFreeSizeForExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); uint32 size = MEMGetTotalFreeSizeForExpHeap(heap.GetPtr()); cemuLog_logDebug(LogType::CoreinitMem, "MEMGetTotalFreeSizeForExpHeap(0x{:08x}) Result: 0x{:x}", heap.GetMPTR(), size); osLib_returnFromFunction(hCPU, size); } void export_MEMGetAllocatableSizeForExpHeapEx(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); ppcDefineParamS32(alignment, 1); uint32 size = MEMGetAllocatableSizeForExpHeapEx(heap.GetPtr(), alignment); cemuLog_logDebug(LogType::CoreinitMem, "MEMGetAllocatableSizeForExpHeapEx(0x{:08x}, 0x{:x}) Result: 0x{:x}", heap.GetMPTR(), alignment, size); osLib_returnFromFunction(hCPU, size); } void export_MEMSetGroupIDForExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); ppcDefineParamU16(groupId, 1); cemuLog_logDebug(LogType::CoreinitMem, "MEMSetGroupIDForExpHeap(0x{:08x}, {})", heap.GetMPTR(), groupId); #ifdef CEMU_DEBUG_ASSERT assert_dbg(); // someone test this and the entire groupId feature #endif uint16 oldGroupId = MEMSetGroupIDForExpHeap(heap.GetPtr(), groupId); osLib_returnFromFunction(hCPU, oldGroupId); } void export_MEMGetGroupIDForExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); cemuLog_logDebug(LogType::Force, "MEMGetGroupIDForExpHeap(0x{:08x})", heap.GetMPTR()); uint16 oldGroupId = MEMGetGroupIDForExpHeap(heap.GetPtr()); osLib_returnFromFunction(hCPU, oldGroupId); } void export_MEMVisitAllocatedForExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); ppcDefineParamMEMPTR(visitor, void, 1); ppcDefineParamU32(userParam, 2); cemuLog_logDebug(LogType::CoreinitMem, "MEMVisitAllocatedForExpHeap(0x{:08x}, 0x{:08x}, 0x{:x})", heap.GetMPTR(), visitor.GetMPTR(), userParam); MEMVisitAllocatedForExpHeap(heap.GetPtr(), visitor, userParam); osLib_returnFromFunction(hCPU, 0); } void export_MEMGetSizeForMBlockExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(memBlock, void, 0); uint32 size = MEMGetSizeForMBlockExpHeap(memBlock.GetPtr()); cemuLog_logDebug(LogType::CoreinitMem, "MEMGetSizeForMBlockExpHeap(0x{:08x}) Result: 0x{:x}", memBlock.GetMPTR(), size); osLib_returnFromFunction(hCPU, size); } void export_MEMGetGroupIDForMBlockExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(memBlock, void, 0); cemuLog_logDebug(LogType::CoreinitMem, "MEMGetGroupIDForMBlockExpHeap(0x{:08x})", memBlock.GetMPTR()); uint16 groupId = MEMGetGroupIDForMBlockExpHeap(memBlock.GetPtr()); osLib_returnFromFunction(hCPU, groupId); } void export_MEMGetAllocDirForMBlockExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(memBlock, void, 0); cemuLog_logDebug(LogType::CoreinitMem, "MEMGetAllocDirForMBlockExpHeap(0x{:08x})", memBlock.GetMPTR()); uint16 allocDir = MEMGetAllocDirForMBlockExpHeap(memBlock.GetPtr()); osLib_returnFromFunction(hCPU, allocDir); } void export_MEMCheckExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); ppcDefineParamU32(options, 1); cemuLog_logDebug(LogType::CoreinitMem, "MEMCheckExpHeap(0x{:08x}, 0x{:x})", heap.GetMPTR(), options); bool result = MEMCheckExpHeap(heap.GetPtr(), options); osLib_returnFromFunction(hCPU, result ? 1 : 0); } void export_MEMCheckForMBlockExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(memBlock, void, 0); ppcDefineParamMEMPTR(heap, MEMHeapBase, 1); ppcDefineParamU32(options, 2); bool result = MEMCheckForMBlockExpHeap(memBlock.GetPtr(), heap.GetPtr(), options); cemuLog_logDebug(LogType::CoreinitMem, "MEMCheckForMBlockExpHeap(0x{:08x}, 0x{:08x}, 0x{:x}) Result: {}", memBlock.GetMPTR(), heap.GetMPTR(), options, result); osLib_returnFromFunction(hCPU, result ? 1 : 0); } void export_MEMInitAllocatorForExpHeap(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(allocator, MEMAllocator, 0); ppcDefineParamMEMPTR(heap, MEMHeapBase, 1); ppcDefineParamS32(alignment, 2); cemuLog_logDebug(LogType::CoreinitMem, "MEMInitAllocatorForExpHeap(0x{:08x}, 0x{:08x}, {})", allocator.GetMPTR(), heap.GetMPTR(), alignment); MEMInitAllocatorForExpHeap(allocator.GetPtr(), heap.GetPtr(), alignment); osLib_returnFromFunction(hCPU, 0); } void expheap_test() { srand(1000); MEMHeapHandle heapHandle = MEMCreateExpHeapEx(memory_getPointerFromVirtualOffset(0x11000000), 0x10000000, 0); MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heapHandle; sint32 idx = 0; for (MBlock2_t* findBlock = expHeap->expHeapHead.chainFreeBlocks.headMBlock.GetPtr(); findBlock != nullptr; findBlock = findBlock->nextBlock.GetPtr()) { const uintptr_t blockMemory = MBLOCK_GET_MEMORY(findBlock); const uint32 dataSize = (uint32)findBlock->dataSize; debug_printf(">> freeBlock %04d addr %08x - %08x\n", idx, memory_getVirtualOffsetFromPointer((void*)blockMemory), memory_getVirtualOffsetFromPointer((void*)blockMemory) + dataSize); idx++; } void* allocTable[1024]; sint32 allocCount = 0; printf("Run ExpHeap test...\n"); for (sint32 t = 0; t < 2; t++) { // allocate a bunch of entries sint32 r = rand() % 100; //for (sint32 i = 0; i < r; i++) { if( allocCount >= 1024 ) continue; uint32 size = (rand() % 1000) * 12; void* mem = MEMAllocFromExpHeapEx(heapHandle, size * 12, -0x2000); if (mem) { allocTable[allocCount] = mem; allocCount++; } } } // free everything that remains for (sint32 i = 0; i < allocCount; i++) { MEMFreeToExpHeap(heapHandle, allocTable[i]); } allocCount = 0; // debug print free blocks (only one should remain) expHeap = (MEMExpHeapHead2*)heapHandle; idx = 0; for (MBlock2_t* findBlock = expHeap->expHeapHead.chainFreeBlocks.headMBlock.GetPtr(); findBlock != nullptr; findBlock = findBlock->nextBlock.GetPtr()) { const uintptr_t blockMemory = MBLOCK_GET_MEMORY(findBlock); const uint32 dataSize = (uint32)findBlock->dataSize; debug_printf("freeBlock %04d addr %08x - %08x\n", idx, memory_getVirtualOffsetFromPointer((void*)blockMemory), memory_getVirtualOffsetFromPointer((void*)blockMemory) + dataSize); idx++; } assert_dbg(); } void expheap_load() { osLib_addFunction("coreinit", "MEMCreateExpHeapEx", export_MEMCreateExpHeapEx); osLib_addFunction("coreinit", "MEMDestroyExpHeap", export_MEMDestroyExpHeap); osLib_addFunction("coreinit", "MEMAllocFromExpHeapEx", export_MEMAllocFromExpHeapEx); osLib_addFunction("coreinit", "MEMFreeToExpHeap", export_MEMFreeToExpHeap); osLib_addFunction("coreinit", "MEMSetAllocModeForExpHeap", export_MEMSetAllocModeForExpHeap); osLib_addFunction("coreinit", "MEMGetAllocModeForExpHeap", export_MEMGetAllocModeForExpHeap); osLib_addFunction("coreinit", "MEMAdjustExpHeap", export_MEMAdjustExpHeap); osLib_addFunction("coreinit", "MEMResizeForMBlockExpHeap", export_MEMResizeForMBlockExpHeap); osLib_addFunction("coreinit", "MEMGetTotalFreeSizeForExpHeap", export_MEMGetTotalFreeSizeForExpHeap); osLib_addFunction("coreinit", "MEMGetAllocatableSizeForExpHeapEx", export_MEMGetAllocatableSizeForExpHeapEx); osLib_addFunction("coreinit", "MEMSetGroupIDForExpHeap", export_MEMSetGroupIDForExpHeap); osLib_addFunction("coreinit", "MEMGetGroupIDForExpHeap", export_MEMGetGroupIDForExpHeap); osLib_addFunction("coreinit", "MEMVisitAllocatedForExpHeap", export_MEMVisitAllocatedForExpHeap); osLib_addFunction("coreinit", "MEMGetSizeForMBlockExpHeap", export_MEMGetSizeForMBlockExpHeap); osLib_addFunction("coreinit", "MEMGetGroupIDForMBlockExpHeap", export_MEMGetGroupIDForMBlockExpHeap); osLib_addFunction("coreinit", "MEMGetAllocDirForMBlockExpHeap", export_MEMGetAllocDirForMBlockExpHeap); osLib_addFunction("coreinit", "MEMCheckExpHeap", export_MEMCheckExpHeap); osLib_addFunction("coreinit", "MEMCheckForMBlockExpHeap", export_MEMCheckForMBlockExpHeap); osLib_addFunction("coreinit", "MEMInitAllocatorForExpHeap", export_MEMInitAllocatorForExpHeap); } #pragma endregion } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" namespace coreinit { void expheap_load(); #define MEM_EXPHEAP_ALLOC_MODE_FIRST (0) #define MEM_EXPHEAP_ALLOC_MODE_NEAR (1) #define MEM_EXPHEAP_USE_ALIGN_MARGIN (2) enum class MEMExpHeapAllocDirection : uint32 { HEAD = 0, TAIL = 1 }; struct MBlockChain2_t { MEMPTR<struct MBlock2_t> headMBlock; // 0x00 MEMPTR<struct MBlock2_t> tailMBlock; // 0x04 }; static_assert(sizeof(MBlockChain2_t) == 8); struct MEMExpHeapHead40_t { /* +0x00 */ MBlockChain2_t chainFreeBlocks; // 0x00 /* +0x08 */ MBlockChain2_t chainUsedBlocks; // 0x08 /* +0x10 */ uint16 groupID; /* +0x12 */ uint16 fields; // Bit 0 -> Alloc mode, Bit 1 -> Allocate within alignment (create free blocks inside alignment padding) }; static_assert(sizeof(MEMExpHeapHead40_t) == 0x14); struct MEMExpHeapHead2 : MEMHeapBase { // Base /* +0x34 */ uint32be ukn34; /* +0x38 */ uint32be ukn38; /* +0x3C */ uint32be ukn3C; /* +0x40 */ MEMExpHeapHead40_t expHeapHead; }; static_assert(sizeof(MEMExpHeapHead2) == 0x54); MEMHeapHandle MEMCreateExpHeapEx(void* startAddress, uint32 size, uint32 createFlags); void* MEMAllocFromExpHeapEx(MEMHeapHandle heap, uint32 size, sint32 alignment); void MEMFreeToExpHeap(MEMHeapHandle heap, void* mem); uint32 MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle heap, sint32 alignment); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h" namespace coreinit { bool __FrmHeapDebug_IsValid(MEMFrmHeap* frmHeap, const char* funcName) { if (!frmHeap) { cemuLog_log(LogType::APIErrors, "{}: Heap is nullptr", funcName); return false; } if (frmHeap->magic != MEMHeapMagic::FRAME_HEAP) { cemuLog_log(LogType::APIErrors, "{}: Heap has bad magic. Not initialized?", funcName); return false; } return true; } MEMFrmHeap* MEMCreateFrmHeapEx(void* memStart, uint32 size, uint32 createFlags) { cemu_assert_debug(memStart); uintptr_t startAddr = (uintptr_t)memStart; uintptr_t endAddr = startAddr + size; // align and pad address startAddr = (startAddr + 3) & ~3; endAddr &= ~3; if (startAddr == 0) return nullptr; if (startAddr > endAddr || (endAddr - startAddr) < sizeof(MEMFrmHeap)) return nullptr; MEMFrmHeap* frmHeap = (MEMFrmHeap*)startAddr; MEMInitHeapBase(frmHeap, MEMHeapMagic::FRAME_HEAP, (void*)((uintptr_t)startAddr + sizeof(MEMFrmHeap)), (void*)endAddr, createFlags); frmHeap->allocationHead = frmHeap->heapStart; frmHeap->allocationTail = frmHeap->heapEnd; frmHeap->recordedStates = nullptr; MEMHeapTable_Add(frmHeap); return frmHeap; } void* MEMDestroyFrmHeap(MEMFrmHeap* frmHeap) { if (!__FrmHeapDebug_IsValid(frmHeap, "MEMDestroyFrmHeap")) return nullptr; MEMBaseDestroyHeap(frmHeap); MEMHeapTable_Remove(frmHeap); return frmHeap; } uint32 MEMGetAllocatableSizeForFrmHeapEx(MEMFrmHeap* frmHeap, sint32 alignment) { if (!__FrmHeapDebug_IsValid(frmHeap, "MEMGetAllocatableSizeForFrmHeapEx")) return 0; frmHeap->AcquireLock(); uint32 allocatableSize = 0; bool negativeAlignment = alignment < 0; if (negativeAlignment) alignment = -alignment; uint32 bits = (uint32)alignment; if (bits == 0 || (bits & (bits - 1)) != 0) { cemuLog_log(LogType::APIErrors, "MEMGetAllocatableSizeForFrmHeapEx(): Invalid alignment"); return 0; } if (negativeAlignment) { cemu_assert_unimplemented(); } else { uint32 headAllocator = frmHeap->allocationHead.GetMPTR(); uint32 tailAllocator = frmHeap->allocationTail.GetMPTR(); uint32 allocStart = (headAllocator + alignment - 1) & ~(alignment - 1); if (allocStart <= tailAllocator) { allocatableSize = tailAllocator - allocStart; } } frmHeap->ReleaseLock(); return allocatableSize; } void* MEMiGetFreeStartForFrmHeap(MEMFrmHeap* heap) { if (!__FrmHeapDebug_IsValid(heap, "MEMiGetFreeStartForFrmHeap")) return nullptr; return heap->allocationHead; } void* MEMiGetFreeEndForFrmHeap(MEMFrmHeap* heap) { if (!__FrmHeapDebug_IsValid(heap, "MEMiGetFreeEndForFrmHeap")) return nullptr; return heap->allocationTail; } void* __FrmHeap_AllocFromHead(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment) { uint32 head = frmHeap->allocationHead.GetMPTR(); uint32 tail = frmHeap->allocationTail.GetMPTR(); uint32 allocStart = (head + alignment - 1) & ~(alignment - 1); if ((allocStart + size) <= tail) { auto prevHead = frmHeap->allocationHead; frmHeap->allocationHead = allocStart + size; if (frmHeap->HasOptionClear()) memset(prevHead.GetPtr(), 0, frmHeap->allocationHead.GetMPTR() - prevHead.GetMPTR()); return MEMPTR<void>(allocStart).GetPtr(); } return nullptr; } void* __FrmHeap_AllocFromTail(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment) { uint32 head = frmHeap->allocationHead.GetMPTR(); uint32 tail = frmHeap->allocationTail.GetMPTR(); uint32 allocStart = (tail - size) & ~(alignment - 1); if (allocStart >= head) { auto prevTail = frmHeap->allocationTail; frmHeap->allocationTail = allocStart; if (frmHeap->HasOptionClear()) memset(frmHeap->allocationTail.GetPtr(), 0, prevTail.GetMPTR() - frmHeap->allocationTail.GetMPTR()); return MEMPTR<void>(allocStart).GetPtr(); } return nullptr; } void* MEMAllocFromFrmHeapEx(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment) { if (!__FrmHeapDebug_IsValid(frmHeap, "MEMAllocFromFrmHeapEx")) return nullptr; if (size == 0) size = 4; size = (size + 3) & ~3; // pad to 4 byte alignment frmHeap->AcquireLock(); void* mem; if (alignment >= 0) mem = __FrmHeap_AllocFromHead(frmHeap, size, alignment); else mem = __FrmHeap_AllocFromTail(frmHeap, size, -alignment); frmHeap->ReleaseLock(); return mem; } void __FrmHeap_FreeFromHead(MEMFrmHeap* frmHeap) { cemu_assert_debug(frmHeap->recordedStates.IsNull()); frmHeap->recordedStates = nullptr; frmHeap->allocationHead = frmHeap->heapStart; } void __FrmHeap_FreeFromTail(MEMFrmHeap* frmHeap) { cemu_assert_debug(frmHeap->recordedStates.IsNull()); frmHeap->recordedStates = nullptr; void* heapEnd = frmHeap->heapEnd.GetPtr(); frmHeap->allocationTail = heapEnd; } void MEMFreeToFrmHeap(MEMFrmHeap* frmHeap, FrmHeapMode mode) { if (!__FrmHeapDebug_IsValid(frmHeap, "MEMFreeToFrmHeap")) return; frmHeap->AcquireLock(); if ((mode & FrmHeapMode::Head) != 0) __FrmHeap_FreeFromHead(frmHeap); if ((mode & FrmHeapMode::Tail) != 0) __FrmHeap_FreeFromTail(frmHeap); frmHeap->ReleaseLock(); } bool MEMRecordStateForFrmHeap(MEMFrmHeap* frmHeap, uint32 id) { if (!__FrmHeapDebug_IsValid(frmHeap, "MEMRecordStateForFrmHeap")) return false; frmHeap->AcquireLock(); auto allocationHead = frmHeap->allocationHead; auto allocationTail = frmHeap->allocationTail; MEMFrmHeapRecordedState* rState = (MEMFrmHeapRecordedState*)__FrmHeap_AllocFromHead(frmHeap, sizeof(MEMFrmHeapRecordedState), 4); // modifies memHeap->allocationHead cemu_assert_debug(rState); if (!rState) { frmHeap->ReleaseLock(); return false; } rState->id = id; rState->allocationHead = allocationHead; rState->allocationTail = allocationTail; rState->prevRecordedState = frmHeap->recordedStates; frmHeap->recordedStates = rState; frmHeap->ReleaseLock(); return true; } bool MEMFreeByStateToFrmHeap(MEMFrmHeap* frmHeap, uint32 id) { if (!__FrmHeapDebug_IsValid(frmHeap, "MEMFreeByStateToFrmHeap")) return false; frmHeap->AcquireLock(); // find matching state MEMFrmHeapRecordedState* rState = frmHeap->recordedStates.GetPtr(); while (rState) { if (id == 0) break; if (rState->id == id) break; rState = rState->prevRecordedState.GetPtr(); } if (!rState) { frmHeap->ReleaseLock(); return false; } // apply state frmHeap->allocationHead = rState->allocationHead; frmHeap->allocationTail = rState->allocationTail; frmHeap->recordedStates = rState->prevRecordedState; frmHeap->ReleaseLock(); return true; } void InitializeMEMFrmHeap() { cafeExportRegister("coreinit", MEMCreateFrmHeapEx, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMDestroyFrmHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMGetAllocatableSizeForFrmHeapEx, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMiGetFreeStartForFrmHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMiGetFreeEndForFrmHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMAllocFromFrmHeapEx, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMFreeToFrmHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMFreeToFrmHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMRecordStateForFrmHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMFreeByStateToFrmHeap, LogType::CoreinitMem); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" namespace coreinit { struct MEMFrmHeapRecordedState { uint32be id; MEMPTR<void> allocationHead; MEMPTR<void> allocationTail; MEMPTR<MEMFrmHeapRecordedState> prevRecordedState; }; static_assert(sizeof(MEMFrmHeapRecordedState) == 0x10); struct MEMFrmHeap : MEMHeapBase { /* +0x34 */ uint32be ukn34; /* +0x38 */ uint32be ukn38; /* +0x3C */ uint32be ukn3C; /* +0x40 */ MEMPTR<void> allocationHead; /* +0x44 */ MEMPTR<void> allocationTail; /* +0x48 */ MEMPTR<MEMFrmHeapRecordedState> recordedStates; }; static_assert(sizeof(MEMFrmHeap) == 0x4C); enum class FrmHeapMode : uint32 { Head = (1 << 0), Tail = (1 << 1), All = (Head | Tail), }; MEMFrmHeap* MEMCreateFrmHeapEx(void* memStart, uint32 size, uint32 createFlags); void* MEMDestroyFrmHeap(MEMFrmHeap* frmHeap); void* MEMAllocFromFrmHeapEx(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment); void MEMFreeToFrmHeap(MEMFrmHeap* frmHeap, FrmHeapMode mode); void InitializeMEMFrmHeap(); } ENABLE_BITMASK_OPERATORS(coreinit::FrmHeapMode); ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" namespace coreinit { void _MEMUnitHeap_IsValidHeap(MEMHeapHandle heap) { cemu_assert(heap != MEM_HEAP_INVALID_HANDLE); cemu_assert(heap->magic == MEMHeapMagic::UNIT_HEAP); } MEMHeapBase* MEMCreateUnitHeapEx(void* memStart, uint32 heapSize, uint32 blockSize, uint32 alignment, uint32 createFlags) { cemu_assert_debug(memStart != nullptr); cemu_assert_debug(alignment % MIN_ALIGNMENT == 0); cemu_assert_debug(MIN_ALIGNMENT <= alignment && alignment <= 32); uintptr_t startAddr = (uintptr_t)memStart; uintptr_t endAddr = startAddr + heapSize; startAddr = (startAddr + MIN_ALIGNMENT_MINUS_ONE) & (~MIN_ALIGNMENT_MINUS_ONE); endAddr &= (~MIN_ALIGNMENT_MINUS_ONE); if (startAddr > endAddr) return nullptr; const uint32 alignmentMinusOne = alignment - 1; MEMUnitHeap* unitHeap = (MEMUnitHeap*)startAddr; uintptr_t alignedStart = startAddr + sizeof(MEMUnitHeap) + alignmentMinusOne & ~((uintptr_t)alignmentMinusOne); if (alignedStart > endAddr) return nullptr; blockSize = blockSize + alignmentMinusOne & ~alignmentMinusOne; uint32 totalBlockSize = (uint32)(endAddr - alignedStart); uint32 numBlocks = totalBlockSize / blockSize; if (numBlocks == 0) return nullptr; MEMInitHeapBase(unitHeap, MEMHeapMagic::UNIT_HEAP, (void*)alignedStart, (void*)(alignedStart + (numBlocks * blockSize)), createFlags); unitHeap->firstFreeBlock = (MEMUnitHeapBlock*)alignedStart; unitHeap->blockSize = blockSize; MEMUnitHeapBlock* currentBlock = (MEMUnitHeapBlock*)alignedStart; for (uint32 i = 0; i < numBlocks - 1; ++i) { MEMUnitHeapBlock* nextBlock = (MEMUnitHeapBlock*)((uintptr_t)currentBlock + blockSize); currentBlock->nextBlock = nextBlock; currentBlock = nextBlock; } currentBlock->nextBlock = nullptr; if ((MEMHeapHandle*)startAddr != nullptr) { MEMHeapTable_Add((MEMHeapHandle)startAddr); } return (MEMHeapBase*)startAddr; } void* MEMDestroyUnitHeap(MEMHeapHandle heap) { _MEMUnitHeap_IsValidHeap(heap); MEMBaseDestroyHeap(heap); MEMHeapTable_Remove(heap); return heap; } uint32 MEMCalcHeapSizeForUnitHeap(uint32 blockSize, uint32 blockCount, uint32 alignment) { uint32 alignedBlockSize = (blockSize + (alignment - 1)) & ~(alignment - 1); uint32 blockTotalSize = blockCount * alignedBlockSize; uint32 heapSize = blockTotalSize + (alignment - 4) + sizeof(MEMUnitHeap); return heapSize; } uint32 MEMCountFreeBlockForUnitHeap(coreinit::MEMUnitHeap* heap) { _MEMUnitHeap_IsValidHeap(heap); heap->AcquireLock(); MEMUnitHeapBlock* currentBlock = heap->firstFreeBlock; uint32 blockCount = 0; while (currentBlock) { blockCount++; currentBlock = currentBlock->nextBlock; } heap->ReleaseLock(); return blockCount; } void* MEMAllocFromUnitHeap(MEMUnitHeap* heap) { _MEMUnitHeap_IsValidHeap(heap); heap->AcquireLock(); MEMUnitHeapBlock* currentBlock = heap->firstFreeBlock; if (!currentBlock) { heap->ReleaseLock(); return nullptr; } // remove from list of free blocks heap->firstFreeBlock = currentBlock->nextBlock; // fill block if (heap->HasOptionClear()) { memset(currentBlock, 0, heap->blockSize); } else if (heap->HasOptionFill()) { memset(currentBlock, coreinit::MEMGetFillValForHeap(coreinit::HEAP_FILL_TYPE::ON_ALLOC), heap->blockSize); } heap->ReleaseLock(); return currentBlock; } void MEMFreeToUnitHeap(MEMUnitHeap* heap, void* mem) { _MEMUnitHeap_IsValidHeap(heap); if (!mem) return; heap->AcquireLock(); cemu_assert_debug(mem >= heap->heapStart.GetPtr() && mem < heap->heapEnd.GetPtr()); // add to list of free blocks MEMUnitHeapBlock* releasedBlock = (MEMUnitHeapBlock*)mem; releasedBlock->nextBlock = heap->firstFreeBlock; heap->firstFreeBlock = releasedBlock; if (heap->HasOptionFill()) memset(releasedBlock, coreinit::MEMGetFillValForHeap(coreinit::HEAP_FILL_TYPE::ON_FREE), heap->blockSize); heap->ReleaseLock(); } void InitializeMEMUnitHeap() { cafeExportRegister("coreinit", MEMCreateUnitHeapEx, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMDestroyUnitHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMCalcHeapSizeForUnitHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMCountFreeBlockForUnitHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMAllocFromUnitHeap, LogType::CoreinitMem); cafeExportRegister("coreinit", MEMFreeToUnitHeap, LogType::CoreinitMem); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" namespace coreinit { struct MEMUnitHeapBlock { MEMPTR<MEMUnitHeapBlock> nextBlock; }; static_assert(sizeof(MEMUnitHeapBlock) == 4); struct MEMUnitHeap : public MEMHeapBase { /* +0x34 */ uint32 padding034; /* +0x38 */ uint32 padding038; /* +0x3C */ uint32 padding03C; /* +0x40 */ MEMPTR<MEMUnitHeapBlock> firstFreeBlock; /* +0x44 */ uint32be blockSize; }; static_assert(sizeof(MEMUnitHeap) == 0x48); MEMHeapBase* MEMCreateUnitHeapEx(void* memStart, uint32 heapSize, uint32 memBlockSize, uint32 alignment, uint32 createFlags); void* MEMDestroyUnitHeap(MEMHeapHandle hHeap); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MPQueue.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_MPQueue.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "util/helpers/fspinlock.h" // titles that utilize MP task queue: Yoshi's Woolly World, Fast Racing Neo, Tokyo Mirage Sessions, Mii Maker #define AcquireMPQLock() s_workaroundSpinlock.lock() #define ReleaseMPQLock() s_workaroundSpinlock.unlock() namespace coreinit { FSpinlock s_workaroundSpinlock; // workaround for a race condition /* Race condition pseudo-code: WorkerThreads: while( task = MPDequeTask() ) MPRunTask(task); MainThread: QueueTasks(); // wait and reset MPWaitForTaskQWithTimeout(DONE) MPTermTaskQ() MPInitTaskQ() The race condition then happens when a worker thread calls MPDequeTask()/MPRunTask while MPInitTaskQ() is being executed on the main thread. Since MPInitTaskQ() (re)initializes the internal spinlock it's not thread-safe, leading to a corruption of the spinlock state for other threads We work around this by using a global spinlock instead of the taskQ specific one */ void MPInitTask(MPTask* task, void* func, void* data, uint32 size) { s_workaroundSpinlock.lock(); task->thisptr = task; task->coreIndex = PPC_CORE_COUNT; task->taskFunc.func = func; task->taskFunc.data = data; task->taskFunc.size = size; task->taskState = MP_TASK_STATE_INIT; task->userdata = nullptr; task->runtime = 0; s_workaroundSpinlock.unlock(); } bool MPTermTask(MPTask* task) { return true; } bool MPRunTask(MPTask* task) { if (task->taskState != MP_TASK_STATE_READY) return false; auto* taskQ = task->taskQ.GetPtr(); if(taskQ->state != MP_TASKQ_STATE_STOPPING && taskQ->state != MP_TASKQ_STATE_STOP) { AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskQ->spinlock); if (taskQ->state == MP_TASKQ_STATE_STOPPING || taskQ->state == MP_TASKQ_STATE_STOP) { ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskQ->spinlock); return false; } taskQ->taskReadyCount = taskQ->taskReadyCount - 1; taskQ->taskRunCount = taskQ->taskRunCount + 1; ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskQ->spinlock); const auto startTime = OSGetSystemTime(); task->coreIndex = OSGetCoreId(); task->taskState = MP_TASK_STATE_RUN; task->taskFunc.result = PPCCoreCallback(task->taskFunc.func, task->taskFunc.data, task->taskFunc.size); task->taskState = MP_TASK_STATE_DONE; const auto endTime = OSGetSystemTime(); task->runtime = endTime - startTime; cemu_assert_debug(taskQ->state != MP_TASKQ_STATE_DONE); AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskQ->spinlock); taskQ->taskRunCount = taskQ->taskRunCount - 1; taskQ->taskDoneCount[1] = taskQ->taskDoneCount[1] + 1; if (taskQ->state == MP_TASKQ_STATE_STOPPING && taskQ->taskRunCount == 0) taskQ->state = MP_TASKQ_STATE_STOP; if (taskQ->taskCount == taskQ->taskDoneCount[1]) taskQ->state = MP_TASKQ_STATE_DONE; ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskQ->spinlock); return true; } return false; } bool MPGetTaskInfo(MPTask* task, MPTaskInfo* info) { info->state = task->taskState; info->coreIndex = task->coreIndex; info->runtime = task->runtime; // not setting function result? return true; } void* MPGetTaskUserData(MPTask* task) { return task->userdata.GetPtr(); } void MPSetTaskUserData(MPTask* task, void* userdata) { task->userdata = userdata; } void MPInitTaskQ(MPTaskQ* taskQ, MPTask** tasks, uint32 taskCount) { AcquireMPQLock(); taskQ->thisptr = taskQ; OSInitSpinLock(&taskQ->spinlock); taskQ->state = MP_TASKQ_STATE_INIT; taskQ->taskReadyCount = 0; taskQ->taskCount = 0; taskQ->taskRunCount = 0; for(uint32 i = 0; i < OSGetCoreCount(); ++i) { taskQ->taskDoneCount[i] = 0; taskQ->nextIndex[i] = 0; taskQ->endIndex[i] = 0; } taskQ->taskQueue = (MEMPTR<MPTask>*)tasks; taskQ->taskQueueSize = taskCount; ReleaseMPQLock(); } bool MPEnqueTask(MPTaskQ* taskq, MPTask* task) { bool result = false; if(task->taskState == MP_TASK_STATE_INIT) { AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); const uint32 taskQState = taskq->state; if((uint32)taskq->endIndex[1] < taskq->taskQueueSize && (taskQState == MP_TASKQ_STATE_INIT || taskQState == MP_TASKQ_STATE_RUN || taskQState == MP_TASKQ_STATE_STOPPING || taskQState == MP_TASKQ_STATE_STOP || taskQState == MP_TASKQ_STATE_DONE)) { task->taskQ = taskq; task->taskState = MP_TASK_STATE_READY; taskq->thisptr = taskq; const uint32 endIndex = taskq->endIndex[1]; taskq->endIndex[1] = endIndex + 1; taskq->taskCount = taskq->taskCount + 1; taskq->taskReadyCount = taskq->taskReadyCount + 1; taskq->taskQueue[endIndex] = task; if (taskQState == MP_TASKQ_STATE_DONE) taskq->state = MP_TASKQ_STATE_RUN; result = true; } ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); } return result; } bool MPTermTaskQ(MPTaskQ* taskq) { // workaround code for TMS AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); //if ((uint32)taskq->taskReadyCount > 0 && taskq->state == MP_TASKQ_STATE_RUN) if (taskq->state == MP_TASKQ_STATE_RUN) { taskq->state = MP_TASKQ_STATE_STOP; } while (taskq->taskRunCount != 0) { // wait for tasks to finish ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); OSYieldThread(); AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); } ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); return true; } bool MPGetTaskQInfo(MPTaskQ* taskq, MPTaskQInfo* info) { AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); info->state = taskq->state; info->taskCount = taskq->taskCount; info->taskReadyCount = taskq->taskReadyCount; info->taskRunCount = taskq->taskRunCount; info->taskDoneCount = taskq->taskDoneCount[1]; ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); return true; } bool MPStartTaskQ(MPTaskQ* taskq) { bool result = false; AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); if (taskq->state == MP_TASKQ_STATE_INIT || taskq->state == MP_TASKQ_STATE_STOP) { taskq->state = MP_TASKQ_STATE_RUN; result = true; } ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); return result; } bool MPRunTasksFromTaskQ(MPTaskQ* taskq, int granularity) { uint32 result = 0; while (true) { if (taskq->state != MP_TASKQ_STATE_RUN) return (taskq->state & MP_TASKQ_STATE_DONE) != 0; AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); const auto nextIndex = taskq->nextIndex[1]; const auto endIndex = taskq->endIndex[1]; if (nextIndex == endIndex) break; auto newNextIndex = nextIndex + granularity; if (endIndex < nextIndex + granularity) newNextIndex = endIndex; const auto workCount = (newNextIndex - nextIndex); taskq->nextIndex[1] = newNextIndex; taskq->taskReadyCount = taskq->taskReadyCount - workCount; taskq->taskRunCount = taskq->taskRunCount + workCount; ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); // since we are having a granularity parameter, we might want to give the scheduler the chance for other stuff when having multiple tasks if(result != 0) PPCCore_switchToScheduler(); for (int i = nextIndex; i < newNextIndex; ++i) { const auto startTime = OSGetSystemTime(); const auto& task = taskq->taskQueue[i]; result = task->thisptr.GetMPTR(); task->taskState = MP_TASK_STATE_RUN; task->coreIndex = OSGetCoreId(); task->taskFunc.result = PPCCoreCallback(task->taskFunc.func, task->taskFunc.data, task->taskFunc.size); task->taskState = MP_TASK_STATE_DONE; const auto endTime = OSGetSystemTime(); task->runtime = endTime - startTime; } AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); const auto runRemaining = taskq->taskRunCount - workCount; taskq->taskRunCount = runRemaining; const auto doneCount = taskq->taskDoneCount[1] + workCount; taskq->taskDoneCount[1] = doneCount; if (taskq->state == 4 && runRemaining == 0) taskq->state = MP_TASKQ_STATE_STOP; if (taskq->taskCount == doneCount) taskq->state = MP_TASKQ_STATE_DONE; ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); } ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); return result != 0; } bool MPStopTaskQ(MPTaskQ* taskq) { bool result = false; AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); if (taskq->state == MP_TASKQ_STATE_RUN) { taskq->state = MP_TASKQ_STATE_STOPPING; if (taskq->taskRunCount == 0) taskq->state = MP_TASKQ_STATE_STOP; result = true; } ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); return result; } bool MPWaitTaskQ(MPTaskQ* taskQ, uint32 waitState) { bool waitRun = (waitState & MP_TASKQ_STATE_RUN) != 0; bool waitStop = (waitState & MP_TASKQ_STATE_STOP) != 0; bool waitDone = (waitState & MP_TASKQ_STATE_DONE) != 0; size_t loopCounter = 0; while (waitStop || waitDone || waitRun) { const uint32 state = taskQ->state; if (waitRun && HAS_FLAG(state, MP_TASKQ_STATE_RUN)) { waitRun = false; waitDone = false; continue; } if (waitStop && HAS_FLAG(state, MP_TASKQ_STATE_STOP)) { waitStop = false; waitDone = false; continue; } if (waitDone && HAS_FLAG(state, MP_TASKQ_STATE_DONE)) { waitDone = false; waitRun = false; waitStop = false; continue; } if (loopCounter > 0) coreinit::OSSleepTicks(EspressoTime::ConvertNsToTimerTicks(50000)); // sleep thread for 0.05ms to give other threads a chance to run (avoids softlocks in YWW) else PPCCore_switchToScheduler(); loopCounter++; } return true; } bool MPWaitTaskQWithTimeout(MPTaskQ* taskQ, uint32 waitState, sint64 timeout) { bool waitRun = (waitState & MP_TASKQ_STATE_RUN) != 0; bool waitStop = (waitState & MP_TASKQ_STATE_STOP) != 0; bool waitDone = (waitState & MP_TASKQ_STATE_DONE) != 0; const auto startTime = OSGetSystemTime(); const auto timerTicks = EspressoTime::ConvertNsToTimerTicks(timeout); const auto endTime = startTime + timerTicks; while (waitStop || waitDone || waitRun) { const uint32 state = taskQ->state; if (waitRun && HAS_FLAG(state, MP_TASKQ_STATE_RUN)) { waitRun = false; waitDone = false; continue; } if (waitStop && HAS_FLAG(state, MP_TASKQ_STATE_STOP)) { waitStop = false; waitDone = false; continue; } if (waitDone && HAS_FLAG(state, MP_TASKQ_STATE_DONE)) { waitDone = false; waitRun = false; waitStop = false; continue; } if (OSGetSystemTime() >= endTime) { if (waitState == MP_TASKQ_STATE_DONE) cemuLog_log(LogType::Force, "MPWaitTaskQWithTimeout(): Timeout occurred while waiting for done-only state"); return false; } PPCCore_switchToScheduler(); } return true; } MPTask* MPDequeTask(MPTaskQ* taskq) { MPTask* result = nullptr; if (taskq->state == MP_TASKQ_STATE_RUN) { AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); if (taskq->state == MP_TASKQ_STATE_RUN && taskq->nextIndex[1] != taskq->endIndex[1]) { result = taskq->taskQueue[taskq->nextIndex[1]].GetPtr(); taskq->nextIndex[1] = taskq->nextIndex[1] + 1; } ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); } return result; } uint32 MPDequeTasks(MPTaskQ* taskq, MPTask** tasks, sint32 maxTasks) { uint32 dequeCount = 0; if (taskq->state == MP_TASKQ_STATE_RUN) { AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); if (taskq->state == MP_TASKQ_STATE_RUN) { auto nextIndex = (sint32)taskq->nextIndex[1]; auto newEndIndex = nextIndex + maxTasks; if (taskq->endIndex[1] < nextIndex + maxTasks) newEndIndex = taskq->endIndex[1]; dequeCount = newEndIndex - nextIndex; taskq->nextIndex[1] = newEndIndex; for(int i = 0; nextIndex < newEndIndex; ++nextIndex, ++i) { tasks[i] = taskq->taskQueue[nextIndex].GetPtr(); } auto idx = 0; while (nextIndex < newEndIndex) { tasks[idx] = taskq->taskQueue[nextIndex].GetPtr(); nextIndex = nextIndex + 1; idx = idx + 1; } } ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); } return dequeCount; } bool MPResetTaskQ(MPTaskQ* taskq) { debug_printf("MPResetTaskQ called\n"); bool result = false; AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); if (taskq->state == MP_TASKQ_STATE_DONE || taskq->state == MP_TASKQ_STATE_STOP) { taskq->state = MP_TASKQ_STATE_INIT; taskq->taskRunCount = 0; taskq->taskCount = taskq->endIndex[1]; taskq->taskReadyCount = taskq->endIndex[1]; for(uint32 i = 0; i < OSGetCoreCount(); ++i) { taskq->taskDoneCount[i] = 0; taskq->nextIndex[i] = 0; } for(uint32 i = 0; i < taskq->taskCount; ++i) { const auto& task = taskq->taskQueue[i]; task->taskFunc.result = 0; task->coreIndex = PPC_CORE_COUNT; task->runtime = 0; task->taskState = MP_TASK_STATE_READY; } result = true; } ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); return result; } void InitializeMP() { // task cafeExportRegister("coreinit", MPInitTask, LogType::CoreinitMP); cafeExportRegister("coreinit", MPTermTask, LogType::CoreinitMP); cafeExportRegister("coreinit", MPRunTask, LogType::CoreinitMP); cafeExportRegister("coreinit", MPGetTaskInfo, LogType::CoreinitMP); cafeExportRegister("coreinit", MPGetTaskUserData, LogType::CoreinitMP); cafeExportRegister("coreinit", MPSetTaskUserData, LogType::CoreinitMP); // taskq cafeExportRegister("coreinit", MPInitTaskQ, LogType::CoreinitMP); cafeExportRegister("coreinit", MPResetTaskQ, LogType::CoreinitMP); cafeExportRegister("coreinit", MPEnqueTask, LogType::CoreinitMP); cafeExportRegister("coreinit", MPDequeTask, LogType::CoreinitMP); cafeExportRegister("coreinit", MPDequeTasks, LogType::CoreinitMP); cafeExportRegister("coreinit", MPRunTasksFromTaskQ, LogType::CoreinitMP); cafeExportRegister("coreinit", MPStartTaskQ, LogType::CoreinitMP); cafeExportRegister("coreinit", MPWaitTaskQ, LogType::CoreinitMP); cafeExportRegister("coreinit", MPWaitTaskQWithTimeout, LogType::CoreinitMP); cafeExportRegister("coreinit", MPStopTaskQ, LogType::CoreinitMP); cafeExportRegister("coreinit", MPTermTaskQ, LogType::CoreinitMP); cafeExportRegister("coreinit", MPGetTaskQInfo, LogType::CoreinitMP); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MPQueue.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit.h" #include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" namespace coreinit { enum MPTaskState { MP_TASK_STATE_INIT = (1 << 0), MP_TASK_STATE_READY = (1 << 1), MP_TASK_STATE_RUN = (1 << 2), MP_TASK_STATE_DONE = (1 << 3) }; enum MPTaskQState { MP_TASKQ_STATE_INIT = (1 << 0), MP_TASKQ_STATE_RUN = (1 << 1), MP_TASKQ_STATE_STOPPING = (1 << 2), MP_TASKQ_STATE_STOP = (1 << 3), MP_TASKQ_STATE_DONE = (1 << 4) }; struct MPTaskFunction { /* +0x00 */ MEMPTR<void> func; /* +0x04 */ MEMPTR<void> data; /* +0x08 */ uint32be size; /* +0x0C */ uint32be result; }; static_assert(sizeof(MPTaskFunction) == 0x10); #pragma pack(1) struct MPTask { /* +0x00 */ MEMPTR<void> thisptr; /* +0x04 */ MEMPTR<struct MPTaskQ> taskQ; /* +0x08 */ uint32be taskState; /* +0x0C */ MPTaskFunction taskFunc; /* +0x1C */ uint32be coreIndex; /* +0x20 */ sint64be runtime; /* +0x28 */ MEMPTR<void> userdata; }; static_assert(sizeof(MPTask) == 0x2C); #pragma pack() struct MPTaskQ { /* +0x00 */ MEMPTR<void> thisptr; /* +0x04 */ uint32be state; /* +0x08 */ uint32be taskCount; /* +0x0C */ uint32be taskReadyCount; /* +0x10 */ uint32be taskRunCount; /* +0x14 */ uint32be taskDoneCount[PPC_CORE_COUNT]; /* +0x20 */ sint32be nextIndex[PPC_CORE_COUNT]; /* +0x2C */ sint32be endIndex[PPC_CORE_COUNT]; /* +0x38 */ MEMPTR<MEMPTR<MPTask>> taskQueue; /* +0x3C */ uint32be taskQueueSize; /* +0x40 */ OSSpinLock spinlock; }; static_assert(sizeof(MPTaskQ) == 0x50); struct MPTaskQInfo { /* +0x00 */ uint32be state; /* +0x04 */ uint32be taskCount; /* +0x08 */ uint32be taskReadyCount; /* +0x0C */ uint32be taskRunCount; /* +0x10 */ uint32be taskDoneCount; }; static_assert(sizeof(MPTaskQInfo) == 0x14); struct MPTaskInfo { /* +0x00 */ uint32be state; /* +0x04 */ uint32be funcResult; /* +0x08 */ uint32be coreIndex; /* +0x0C */ sint64be runtime; }; static_assert(sizeof(MPTaskQInfo) == 0x14); void MPInitTask(MPTask* task, void* func, void* data, uint32 size); bool MPTermTask(MPTask* task); bool MPRunTask(MPTask* task); bool MPGetTaskInfo(MPTask* task, MPTaskInfo* info); void* MPGetTaskUserData(MPTask* task); void MPSetTaskUserData(MPTask* task, void* userdata); void MPInitTaskQ(MPTaskQ* taskq, MPTask** tasks, uint32 taskCount); bool MPEnqueTask(MPTaskQ* taskq, MPTask* task); bool MPTermTaskQ(MPTaskQ* taskq); bool MPGetTaskQInfo(MPTaskQ* taskq, MPTaskQInfo* info); bool MPStartTaskQ(MPTaskQ* taskq); bool MPRunTasksFromTaskQ(MPTaskQ* taskq, int granularity); bool MPStopTaskQ(MPTaskQ* taskq); bool MPWaitTaskQ(MPTaskQ* taskq, uint32 waitState); bool MPWaitTaskQWithTimeout(MPTaskQ* taskq, uint32 waitState, sint64 timeout); MPTask* MPDequeTask(MPTaskQ* taskq); uint32 MPDequeTasks(MPTaskQ* taskq, MPTask** tasks, sint32 maxTasks); bool MPResetTaskQ(MPTaskQ* taskq); void InitializeMP(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "coreinit_Memory.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "Cafe/CafeSystem.h" namespace coreinit { void DCInvalidateRange(MPTR addr, uint32 size) { MPTR addrEnd = (addr + size + 0x1F) & ~0x1F; addr &= ~0x1F; //LatteBufferCache_notifyDCFlush(addr, addrEnd - addr); } void DCFlushRange(MPTR addr, uint32 size) { MPTR addrEnd = (addr + size + 0x1F) & ~0x1F; addr &= ~0x1F; LatteBufferCache_notifyDCFlush(addr, addrEnd - addr); } void DCFlushRangeNoSync(MPTR addr, uint32 size) { MPTR addrEnd = (addr + size + 0x1F) & ~0x1F; addr &= ~0x1F; LatteBufferCache_notifyDCFlush(addr, addrEnd - addr); } void DCStoreRange(MPTR addr, uint32 size) { MPTR addrEnd = (addr + size + 0x1F) & ~0x1F; addr &= ~0x1F; //LatteBufferCache_notifyDCFlush(addr, addrEnd - addr); } void DCStoreRangeNoSync(MPTR addr, uint32 size) { MPTR addrEnd = (addr + size + 0x1F) & ~0x1F; addr &= ~0x1F; LatteBufferCache_notifyDCFlush(addr, addrEnd - addr); } void DCZeroRange(MPTR addr, uint32 size) { MPTR alignedAddr = addr & ~31; uint32 cachlineOffset = addr & 31; uint32 blocks = (cachlineOffset + size + 31) / 32; if (blocks > 0) { memset(memory_getPointerFromVirtualOffset(alignedAddr), 0x00, blocks * 32); LatteBufferCache_notifyDCFlush(alignedAddr, blocks * 32); } } bool OSIsAddressRangeDCValid(uint32 startOffset, uint32 range) { uint32 endOffset = startOffset + range - 1; uint32 boundaryLow = 0xE8000000; uint32 boundaryHigh = 0xEC000000; if (startOffset < boundaryLow || startOffset >= boundaryHigh) return false; if (endOffset < boundaryLow || endOffset >= boundaryHigh) return false; return true; } void* coreinit_memset(void* dst, uint32 value, uint32 size) { memset(dst, value, size); return dst; } void* coreinit_memcpy(MEMPTR<void> dst, MEMPTR<void> src, uint32 size) { if (dst.GetMPTR() == 0xFFFFFFFF) { // games this was seen in: The Swapper // this may be a bug in the game. The last few bytes of the address space are writable, but wrap-around behavior of COS memcpy is unknown cemu_assert_debug(false); } if (size > 0) { memcpy(dst.GetPtr(), src.GetPtr(), size); // always flushes the cache! LatteBufferCache_notifyDCFlush(dst.GetMPTR(), size); } return dst.GetPtr(); } void* coreinit_memmove(MEMPTR<void> dst, void* src, uint32 size) { if (size > 0) { memmove(dst.GetPtr(), src, size); // always flushes the cache! LatteBufferCache_notifyDCFlush(dst.GetMPTR(), size); } return dst.GetPtr(); } void* OSBlockMove(MEMPTR<void> dst, MEMPTR<void> src, uint32 size, bool flushDC) { if (size > 0) { memmove(dst.GetPtr(), src.GetPtr(), size); if (flushDC) LatteBufferCache_notifyDCFlush(dst.GetMPTR(), size); } return dst.GetPtr(); } void* OSBlockSet(MEMPTR<void> dst, uint32 value, uint32 size) { memset(dst.GetPtr(), value&0xFF, size); return dst.GetPtr(); } MPTR OSEffectiveToPhysical(MPTR effectiveAddr) { MPTR physicalAddr = memory_virtualToPhysical(effectiveAddr); return physicalAddr; } void OSMemoryBarrier() { // no-op } void OSGetMemBound(sint32 memType, MEMPTR<void>* offsetOutput, uint32be* sizeOutput) { MPTR memAddr = MPTR_NULL; uint32 memSize = 0; /* Data taken from browser dump: type start size MEM1 0xF4000000 0x02000000 MEM2 0x106DE000 0x170C2000 */ if (memType == 1) { // MEM1 memAddr = mmuRange_MEM1.getBase(); memSize = mmuRange_MEM1.getSize(); } else if (memType == 2) { // MEM2 uint32 currentRPLAllocatorOffset = RPLLoader_GetDataAllocatorAddr(); // due to differences in our library implementations we currently allocate less memory for the OS/RPLs than on the actual hardware, // as a result more memory is available to games // however, some games crash due to internal overflows if there is too much memory available // here we artificially reduce the available memory for the affected games uint64 titleId = CafeSystem::GetForegroundTitleId(); if ( titleId == 0x0005000010132400ULL || // Lego Marvel Super Heroes (EU) titleId == 0x0005000010132B00ULL || // Lego Marvel Super Heroes (US) titleId == 0x0005000010194200ull || // Lego Dimensions (US) titleId == 0x0005000010195D00ull || // Lego Dimensions (EU) titleId == 0x00050000101A6200ull || // Lego Jurassic World (US) titleId == 0x00050000101A5C00 || // Lego Jurassic World (EU) titleId == 0x000500001014DE00 || // The Lego Movie Videogame (US) titleId == 0x000500001014E000 || // The Lego Movie Videogame (EU) titleId == 0x0005000010168D00 || // Lego The Hobbit (EU) titleId == 0x000500001016A700 || // Lego The Hobbit (JP) // The Hobbit US title id? titleId == 0x00050000101DAB00 || // Lego Star Wars: The Force Awakens (US) titleId == 0x00050000101DAA00 || // Lego Star Wars: The Force Awakens (EU) // LEGO Batman 3: BEYOND GOTHAM titleId == 0x000500001016A400 || // EU titleId == 0x000500001016AD00 || // US // Lego Marvel Avengers titleId == 0x00050000101BE900 || // EU titleId == 0x00050000101BEF00 || // US // LEGO BATMAN 2: DC Super Heroes titleId == 0x0005000010135500 || // EU titleId == 0x0005000010135E00 // US ) { cemuLog_logDebug(LogType::Force, "Hack: Reduce available memory to simulate loaded RPLs"); currentRPLAllocatorOffset += (48 * 1024 * 1024); // 48MB } memAddr = currentRPLAllocatorOffset; memSize = mmuRange_MEM2.getEnd() - currentRPLAllocatorOffset; } else { cemu_assert_debug(false); } if (offsetOutput) *offsetOutput = memAddr; if (sizeOutput) *sizeOutput = memSize; } void InitializeMemory() { cafeExportRegister("coreinit", DCInvalidateRange, LogType::Placeholder); cafeExportRegister("coreinit", DCFlushRange, LogType::Placeholder); cafeExportRegister("coreinit", DCFlushRangeNoSync, LogType::Placeholder); cafeExportRegister("coreinit", DCStoreRange, LogType::Placeholder); cafeExportRegister("coreinit", DCStoreRangeNoSync, LogType::Placeholder); cafeExportRegister("coreinit", DCZeroRange, LogType::Placeholder); cafeExportRegister("coreinit", OSIsAddressRangeDCValid, LogType::Placeholder); cafeExportRegisterFunc(coreinit_memcpy, "coreinit", "memcpy", LogType::Placeholder); cafeExportRegisterFunc(coreinit_memset, "coreinit", "memset", LogType::Placeholder); cafeExportRegisterFunc(coreinit_memmove, "coreinit", "memmove", LogType::Placeholder); cafeExportRegister("coreinit", OSBlockMove, LogType::Placeholder); cafeExportRegister("coreinit", OSBlockSet, LogType::Placeholder); cafeExportRegister("coreinit", OSEffectiveToPhysical, LogType::Placeholder); cafeExportRegister("coreinit", OSMemoryBarrier, LogType::Placeholder); cafeExportRegister("coreinit", OSGetMemBound, LogType::Placeholder); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Memory.h ================================================ #pragma once namespace coreinit { void InitializeMemory(); void OSGetMemBound(sint32 memType, MEMPTR<void>* offsetOutput, uint32be* sizeOutput); void* OSBlockMove(MEMPTR<void> dst, MEMPTR<void> src, uint32 size, bool flushDC); void* OSBlockSet(MEMPTR<void> dst, uint32 value, uint32 size); void OSMemoryBarrier(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MemoryMapping.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "util/MemMapper/MemMapper.h" #define OS_MAP_READ_ONLY (1) #define OS_MAP_READ_WRITE (2) #define VIRT_RANGE_ADDR (0xA0000000) // todo: Process specific. For the main ram pid this overlaps with the overlay arena? #define VIRT_RANGE_SIZE (0x40000000) #define PHYS_RANGE_ADDR (0x10000000) // todo: Process specific #define PHYS_RANGE_SIZE (0x40000000) namespace coreinit { std::mutex s_memMappingMtx; struct OSVirtMemoryEntry { OSVirtMemoryEntry(MPTR virtualAddress, uint32 size, uint32 alignment) : virtualAddress(virtualAddress), size(size), alignment(alignment) {}; MPTR virtualAddress; uint32 size; uint32 alignment; }; std::vector<OSVirtMemoryEntry> s_allocatedVirtMemory; MPTR _OSAllocVirtAddr(uint32 size, uint32 alignment) { std::lock_guard _l(s_memMappingMtx); uint32 currentAddress = VIRT_RANGE_ADDR; uint32 endAddress = VIRT_RANGE_ADDR + VIRT_RANGE_SIZE; uint32 pageSize = (uint32)MemMapper::GetPageSize(); pageSize = std::max<uint32>(pageSize, Espresso::MEM_PAGE_SIZE); while (true) { // calculated aligned start and end address for current region currentAddress = (currentAddress + alignment - 1) & ~(alignment - 1); currentAddress = (currentAddress + pageSize - 1) & ~(pageSize - 1); uint32 currentEndAddress = currentAddress + size; currentEndAddress = (currentEndAddress + pageSize - 1) & ~(pageSize - 1); // check if out of available space if (currentEndAddress > endAddress) { cemuLog_log(LogType::APIErrors, "_OSAllocVirtAddr(): Unable to allocate memory\n"); return MPTR_NULL; } // check for overlapping regions bool emptySpaceFound = true; for(auto& virtMemIt : s_allocatedVirtMemory) { // check for range collision if (currentAddress < (virtMemIt.virtualAddress + virtMemIt.size) && currentEndAddress > virtMemIt.virtualAddress) { // regions overlap // adjust current address and keep looking currentAddress = virtMemIt.virtualAddress + virtMemIt.size; emptySpaceFound = false; break; } } if (emptySpaceFound) { // add entry s_allocatedVirtMemory.emplace_back(currentAddress, currentEndAddress - currentAddress, alignment); return currentAddress; } } return MPTR_NULL; } bool _OSFreeVirtAddr(MPTR virtAddr) { std::lock_guard _l(s_memMappingMtx); auto it = s_allocatedVirtMemory.begin(); while (it != s_allocatedVirtMemory.end()) { if (it->virtualAddress == virtAddr) { s_allocatedVirtMemory.erase(it); return true; } ++it; } return false; } void OSGetAvailPhysAddrRange(uint32be* physRangeStart, uint32be* physRangeSize) { *physRangeStart = PHYS_RANGE_ADDR; *physRangeSize = PHYS_RANGE_SIZE; } void* OSAllocVirtAddr(MEMPTR<void> address, uint32 size, uint32 align) { if (align == 0) align = 1; if (align != 0 && align != 1) assert_dbg(); cemu_assert_debug(address == nullptr); // todo - support for allocation with fixed address address = _OSAllocVirtAddr(size, align); debug_printf("OSAllocVirtAddr(): Allocated virtual memory at 0x%08x\n", address.GetMPTR()); return MEMPTR<void>(address); } uint32 OSFreeVirtAddr(MEMPTR<void> address) { bool r = _OSFreeVirtAddr(address.GetMPTR()); if(!r) cemuLog_log(LogType::APIErrors, "OSFreeVirtAddr: Could not find allocation with address 0x{:08x}\n", address.GetMPTR()); return r ? 1 : 0; } uint32 OSMapMemory(MPTR virtualAddress, MPTR physicalAddress, uint32 size, uint32 mode) { if (virtualAddress < VIRT_RANGE_ADDR || virtualAddress >= (VIRT_RANGE_ADDR + VIRT_RANGE_SIZE)) { cemuLog_log(LogType::APIErrors, "OSMapMemory: Virtual address out of bounds\n"); return 0; } uint8* virtualPtr = memory_getPointerFromVirtualOffset(virtualAddress); MemMapper::PAGE_PERMISSION pageProtect = MemMapper::PAGE_PERMISSION::P_NONE; if (mode == OS_MAP_READ_ONLY) pageProtect = MemMapper::PAGE_PERMISSION::P_READ; else if (mode == OS_MAP_READ_WRITE) pageProtect = MemMapper::PAGE_PERMISSION::P_RW; else cemu_assert_unimplemented(); void* allocationResult = MemMapper::AllocateMemory(virtualPtr, size, pageProtect, true); if (!allocationResult) { cemuLog_log(LogType::Force, "OSMapMemory failed"); return 0; } return 1; } uint32 OSUnmapMemory(MPTR virtualAddress, uint32 size) { if (virtualAddress < VIRT_RANGE_ADDR || virtualAddress >= (VIRT_RANGE_ADDR + VIRT_RANGE_SIZE)) cemu_assert_suspicious(); cemu_assert((size % MemMapper::GetPageSize()) == 0); uint8* virtualPtr = memory_getPointerFromVirtualOffset(virtualAddress); MemMapper::FreeMemory(virtualPtr, size, true); return 1; } void InitializeMemoryMapping() { s_allocatedVirtMemory.clear(); cafeExportRegister("coreinit", OSGetAvailPhysAddrRange, LogType::CoreinitMemoryMapping); cafeExportRegister("coreinit", OSAllocVirtAddr, LogType::CoreinitMemoryMapping); cafeExportRegister("coreinit", OSFreeVirtAddr, LogType::CoreinitMemoryMapping); cafeExportRegister("coreinit", OSMapMemory, LogType::CoreinitMemoryMapping); cafeExportRegister("coreinit", OSUnmapMemory, LogType::CoreinitMemoryMapping); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MemoryMapping.h ================================================ namespace coreinit { void InitializeMemoryMapping(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" namespace coreinit { void UpdateSystemMessageQueue(); void HandleReceivedSystemMessage(OSMessage* msg); SysAllocator<OSMessageQueue> g_systemMessageQueue; SysAllocator<OSMessage, 16> _systemMessageQueueArray; void OSInitMessageQueueEx(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount, void* userData) { msgQueue->magic = 'mSgQ'; msgQueue->userData = userData; msgQueue->msgArray = msgArray; msgQueue->msgCount = msgCount; msgQueue->firstIndex = 0; msgQueue->usedCount = 0; msgQueue->ukn08 = 0; OSInitThreadQueueEx(&msgQueue->threadQueueReceive, msgQueue); OSInitThreadQueueEx(&msgQueue->threadQueueSend, msgQueue); } void OSInitMessageQueue(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount) { OSInitMessageQueueEx(msgQueue, msgArray, msgCount, nullptr); } bool OSReceiveMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags) { bool isSystemMessageQueue = (msgQueue == g_systemMessageQueue); if(isSystemMessageQueue) UpdateSystemMessageQueue(); __OSLockScheduler(msgQueue); while (msgQueue->usedCount == (uint32be)0) { if ((flags & OS_MESSAGE_BLOCK)) { msgQueue->threadQueueReceive.queueAndWait(OSGetCurrentThread()); } else { __OSUnlockScheduler(msgQueue); return false; } } // copy message sint32 messageIndex = msgQueue->firstIndex; OSMessage* readMsg = &(msgQueue->msgArray[messageIndex]); memcpy(msg, readMsg, sizeof(OSMessage)); msgQueue->firstIndex = ((uint32)msgQueue->firstIndex + 1) % (uint32)(msgQueue->msgCount); msgQueue->usedCount = (uint32)msgQueue->usedCount - 1; // wake up any thread waiting to add a message if (!msgQueue->threadQueueSend.isEmpty()) msgQueue->threadQueueSend.wakeupSingleThreadWaitQueue(true); __OSUnlockScheduler(msgQueue); if(isSystemMessageQueue) HandleReceivedSystemMessage(msg); return true; } bool OSPeekMessage(OSMessageQueue* msgQueue, OSMessage* msg) { __OSLockScheduler(msgQueue); if ((msgQueue->usedCount == (uint32be)0)) { __OSUnlockScheduler(msgQueue); return false; } // copy message sint32 messageIndex = msgQueue->firstIndex; if (msg) { OSMessage* readMsg = &(msgQueue->msgArray[messageIndex]); memcpy(msg, readMsg, sizeof(OSMessage)); } __OSUnlockScheduler(msgQueue); return true; } sint32 OSSendMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags) { __OSLockScheduler(); while (msgQueue->usedCount >= msgQueue->msgCount) { if ((flags & OS_MESSAGE_BLOCK)) { msgQueue->threadQueueSend.queueAndWait(OSGetCurrentThread()); } else { __OSUnlockScheduler(); return 0; } } // add message if ((flags & OS_MESSAGE_HIGH_PRIORITY)) { // decrease firstIndex sint32 newFirstIndex = (sint32)((sint32)msgQueue->firstIndex + (sint32)msgQueue->msgCount - 1) % (sint32)msgQueue->msgCount; msgQueue->firstIndex = newFirstIndex; // insert message at new first index msgQueue->usedCount = (uint32)msgQueue->usedCount + 1; OSMessage* newMsg = &(msgQueue->msgArray[newFirstIndex]); memcpy(newMsg, msg, sizeof(OSMessage)); } else { sint32 messageIndex = (uint32)(msgQueue->firstIndex + msgQueue->usedCount) % (uint32)msgQueue->msgCount; msgQueue->usedCount = (uint32)msgQueue->usedCount + 1; OSMessage* newMsg = &(msgQueue->msgArray[messageIndex]); memcpy(newMsg, msg, sizeof(OSMessage)); } // wake up any thread waiting to read a message if (!msgQueue->threadQueueReceive.isEmpty()) msgQueue->threadQueueReceive.wakeupSingleThreadWaitQueue(true); __OSUnlockScheduler(); return 1; } OSMessageQueue* OSGetSystemMessageQueue() { return g_systemMessageQueue.GetPtr(); } void InitializeMessageQueue() { OSInitMessageQueue(g_systemMessageQueue.GetPtr(), _systemMessageQueueArray.GetPtr(), _systemMessageQueueArray.GetCount()); cafeExportRegister("coreinit", OSInitMessageQueueEx, LogType::CoreinitThread); cafeExportRegister("coreinit", OSInitMessageQueue, LogType::CoreinitThread); cafeExportRegister("coreinit", OSReceiveMessage, LogType::CoreinitThread); cafeExportRegister("coreinit", OSPeekMessage, LogType::CoreinitThread); cafeExportRegister("coreinit", OSSendMessage, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetSystemMessageQueue, LogType::CoreinitThread); } }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" namespace coreinit { enum class SysMessageId : uint32 { MsgAcquireForeground = 0xFACEF000, MsgReleaseForeground = 0xFACEBACC, MsgExit = 0xD1E0D1E0, HomeButtonDenied = 0xCCC0FFEE, NetIoStartOrStop = 0xAAC0FFEE, }; struct OSMessage { uint32be message; uint32be data0; uint32be data1; uint32be data2; }; struct OSMessageQueue { /* +0x00 */ uint32be magic; /* +0x04 */ MEMPTR<void> userData; /* +0x08 */ uint32be ukn08; /* +0x0C */ OSThreadQueue threadQueueSend; /* +0x1C */ OSThreadQueue threadQueueReceive; /* +0x2C */ MEMPTR<OSMessage> msgArray; /* +0x30 */ uint32be msgCount; /* +0x34 */ uint32be firstIndex; /* +0x38 */ uint32be usedCount; }; static_assert(sizeof(OSMessageQueue) == 0x3C); // flags #define OS_MESSAGE_BLOCK 1 // blocking send/receive #define OS_MESSAGE_HIGH_PRIORITY 2 // put message in front of all queued messages void OSInitMessageQueueEx(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount, void* userData); void OSInitMessageQueue(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount); bool OSReceiveMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags); bool OSPeekMessage(OSMessageQueue* msgQueue, OSMessage* msg); sint32 OSSendMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags); OSMessageQueue* OSGetSystemMessageQueue(); void InitializeMessageQueue(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Misc.h" #include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" #include "Cafe/CafeSystem.h" #include "Cafe/Filesystem/fsc.h" #include <pugixml.hpp> namespace coreinit { sint32 ppc_vprintf(const char* formatStr, char* strOut, sint32 maxLength, ppc_va_list* vargs) { char tempStr[4096]; sint32 writeIndex = 0; while (*formatStr) { char c = *formatStr; if (c == '%') { const char* formatStart = formatStr; formatStr++; if (*formatStr == '%') { // percent sign if (writeIndex >= maxLength) break; strOut[writeIndex] = '%'; writeIndex++; formatStr++; continue; } // flags bool flag_leftJustify = false; bool flag_zeroPadding = false; if (*formatStr == '-') { flag_leftJustify = true; formatStr++; } if (*formatStr == '+') { // todo formatStr++; } if (*formatStr == ' ') { // todo formatStr++; } if (*formatStr == '#') { // todo formatStr++; } if (*formatStr == '0') { flag_zeroPadding = true; formatStr++; } // width if (*formatStr == '*') { cemu_assert_debug(false); formatStr++; } bool widthIsSpecified = false; sint32 width = 0; while (*formatStr >= '0' && *formatStr <= '9') { width *= 10; width += (*formatStr - '0'); formatStr++; widthIsSpecified = true; } // precision if (*formatStr == '.') { formatStr++; if (*formatStr == '*') { cemu_assert_debug(false); } while (*formatStr >= '0' && *formatStr <= '9') { formatStr++; } } // length + specifier char tempFormat[64]; if (*formatStr == 'X' || *formatStr == 'x' || *formatStr == 'u' || *formatStr == 'd' || *formatStr == 'p' || *formatStr == 'i' || (formatStr[0] == 'l' && formatStr[1] == 'd')) { // number formatStr++; strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); if ((formatStr - formatStart) < sizeof(tempFormat)) tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; sint32 tempLen = sprintf(tempStr, tempFormat, (uint32)*(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) break; strOut[writeIndex] = tempStr[i]; writeIndex++; } } else if (*formatStr == 's') { // string formatStr++; strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); if ((formatStr - formatStart) < sizeof(tempFormat)) tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; MPTR strOffset = *(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32); sint32 tempLen = 0; if (strOffset == MPTR_NULL) tempLen = sprintf(tempStr, "NULL"); else tempLen = sprintf(tempStr, tempFormat, memory_getPointerFromVirtualOffset(strOffset)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) break; strOut[writeIndex] = tempStr[i]; writeIndex++; } strOut[std::min(maxLength - 1, writeIndex)] = '\0'; } else if (*formatStr == 'c') { // character formatStr++; strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); if ((formatStr - formatStart) < sizeof(tempFormat)) tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; sint32 tempLen = sprintf(tempStr, tempFormat, (uint32)*(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) break; strOut[writeIndex] = tempStr[i]; writeIndex++; } } else if (*formatStr == 'f' || *formatStr == 'g' || *formatStr == 'G') { formatStr++; strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); if ((formatStr - formatStart) < sizeof(tempFormat)) tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; sint32 tempLen = sprintf(tempStr, tempFormat, (double)*(betype<double>*)_ppc_va_arg(vargs, ppc_va_type::FLOAT_OR_DOUBLE)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) break; strOut[writeIndex] = tempStr[i]; writeIndex++; } } else if (formatStr[0] == 'l' && formatStr[1] == 'f') { // double formatStr += 2; strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); if ((formatStr - formatStart) < sizeof(tempFormat)) tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; sint32 tempLen = sprintf(tempStr, tempFormat, (double)*(betype<double>*)_ppc_va_arg(vargs, ppc_va_type::FLOAT_OR_DOUBLE)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) break; strOut[writeIndex] = tempStr[i]; writeIndex++; } } else if ((formatStr[0] == 'l' && formatStr[1] == 'l' && (formatStr[2] == 'x' || formatStr[2] == 'X'))) { formatStr += 3; // 64bit int strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); if ((formatStr - formatStart) < sizeof(tempFormat)) tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; sint32 tempLen = sprintf(tempStr, tempFormat, (uint64)*(uint64be*)_ppc_va_arg(vargs, ppc_va_type::INT64)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) break; strOut[writeIndex] = tempStr[i]; writeIndex++; } } else if ((formatStr[0] == 'l' && formatStr[1] == 'l' && formatStr[2] == 'd')) { formatStr += 3; // signed integer (64bit) strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); if ((formatStr - formatStart) < sizeof(tempFormat)) tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; sint32 tempLen = sprintf(tempStr, tempFormat, (sint64)*(sint64be*)_ppc_va_arg(vargs, ppc_va_type::INT64)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) break; strOut[writeIndex] = tempStr[i]; writeIndex++; } } else { // unsupported / unknown specifier cemu_assert_debug(false); break; } } else { if (writeIndex >= maxLength) break; strOut[writeIndex] = c; writeIndex++; formatStr++; } } strOut[std::min(writeIndex, maxLength - 1)] = '\0'; return std::min(writeIndex, maxLength - 1); } /* coreinit logging and string format */ sint32 __os_snprintf(char* outputStr, sint32 maxLength, const char* formatStr) { ppc_define_va_list(3, 0); sint32 r = ppc_vprintf(formatStr, outputStr, maxLength, &vargs); return r; } enum class CafeLogType { OSCONSOLE = 0, }; struct CafeLogBuffer { std::array<char, 270> lineBuffer; size_t lineLength{}; }; CafeLogBuffer g_logBuffer_OSReport; CafeLogBuffer& getLogBuffer(CafeLogType cafeLogType) { if (cafeLogType == CafeLogType::OSCONSOLE) return g_logBuffer_OSReport; // default to OSReport return g_logBuffer_OSReport; } std::string_view getLogBufferName(CafeLogType cafeLogType) { if (cafeLogType == CafeLogType::OSCONSOLE) return "OSConsole"; return "Unknown"; } std::mutex sCafeConsoleMutex; void WriteCafeConsole(CafeLogType cafeLogType, const char* msg, sint32 len) { std::unique_lock _l(sCafeConsoleMutex); CafeLogBuffer& logBuffer = getLogBuffer(cafeLogType); // once a line is full or \n is written it will be posted to log auto flushLine = [](CafeLogBuffer& cafeLogBuffer, std::string_view cafeLogName) -> void { cemuLog_log(LogType::CoreinitLogging, "[{0}] {1}", cafeLogName, std::basic_string_view(cafeLogBuffer.lineBuffer.data(), cafeLogBuffer.lineLength)); cafeLogBuffer.lineLength = 0; }; while (len) { char c = *msg; msg++; len--; if (c == '\r') continue; if (c == '\n') { // flush line immediately flushLine(logBuffer, getLogBufferName(cafeLogType)); continue; } logBuffer.lineBuffer[logBuffer.lineLength] = c; logBuffer.lineLength++; if (logBuffer.lineLength >= logBuffer.lineBuffer.size()) flushLine(logBuffer, getLogBufferName(cafeLogType)); } } void COSVReport(COSReportModule module, COSReportLevel level, const char* format, ppc_va_list* vargs) { char tmpBuffer[1024]; sint32 len = ppc_vprintf(format, tmpBuffer, sizeof(tmpBuffer), vargs); WriteCafeConsole(CafeLogType::OSCONSOLE, tmpBuffer, len); } void OSReport(const char* format) { ppc_define_va_list(1, 0); COSVReport(COSReportModule::coreinit, COSReportLevel::Info, format, &vargs); } void OSVReport(const char* format, ppc_va_list* vargs) { COSVReport(COSReportModule::coreinit, COSReportLevel::Info, format, vargs); } void COSWarn(int moduleId, const char* format) { ppc_define_va_list(2, 0); char tmpBuffer[1024]; int prefixLen = sprintf(tmpBuffer, "[COSWarn-%d] ", moduleId); sint32 len = ppc_vprintf(format, tmpBuffer + prefixLen, sizeof(tmpBuffer) - prefixLen, &vargs); WriteCafeConsole(CafeLogType::OSCONSOLE, tmpBuffer, len + prefixLen); } void OSLogPrintf(int ukn1, int ukn2, int ukn3, const char* format) { ppc_define_va_list(4, 0); char tmpBuffer[1024]; int prefixLen = sprintf(tmpBuffer, "[OSLogPrintf-%d-%d-%d] ", ukn1, ukn2, ukn3); sint32 len = ppc_vprintf(format, tmpBuffer + prefixLen, sizeof(tmpBuffer) - prefixLen, &vargs); WriteCafeConsole(CafeLogType::OSCONSOLE, tmpBuffer, len + prefixLen); } void OSConsoleWrite(const char* strPtr, sint32 length) { if (length < 0) return; WriteCafeConsole(CafeLogType::OSCONSOLE, strPtr, length); } /* home button menu */ bool g_homeButtonMenuEnabled = false; bool OSIsHomeButtonMenuEnabled() { return g_homeButtonMenuEnabled; } bool OSEnableHomeButtonMenu(bool enable) { g_homeButtonMenuEnabled = enable; return true; } uint32 OSGetPFID() { return 15; // hardcoded as game } uint32 OSGetUPID() { return OSGetPFID(); } uint64 s_currentTitleId; uint64 OSGetTitleID() { return s_currentTitleId; } uint32 s_sdkVersion; uint32 __OSGetProcessSDKVersion() { return s_sdkVersion; } // move this to CafeSystem.cpp? void OSLauncherThread(uint64 titleId) { CafeSystem::ShutdownTitle(); CafeSystem::PrepareForegroundTitle(titleId); CafeSystem::RequestRecreateCanvas(); CafeSystem::LaunchForegroundTitle(); } uint32 __LaunchByTitleId(uint64 titleId, uint32 argc, MEMPTR<char>* argv) { // prepare argument buffer #if 0 char argumentBuffer[4096]; uint32 argumentBufferLength = 0; char* argWriter = argumentBuffer; for(uint32 i=0; i<argc; i++) { const char* arg = argv[i]; uint32 argLength = strlen(arg); if((argumentBufferLength + argLength + 1) >= sizeof(argumentBuffer)) { // argument buffer full cemuLog_logDebug(LogType::Force, "LaunchByTitleId: argument buffer full"); return 0x80000000; } memcpy(argWriter, arg, argLength); argWriter[argLength] = '\0'; argWriter += argLength + 1; argumentBufferLength += argLength + 1; } #endif // normally the above buffer is passed to the PPC kernel via syscall 0x2B and then // the kernel forwards it to IOSU MCP when requesting a title launch // but for now we HLE most of the launching code and can just set the argument array directly std::vector<std::string> argArray; for(uint32 i=0; i<argc; i++) argArray.emplace_back(argv[i]); CafeSystem::SetOverrideArgs(argArray); // spawn launcher thread (this current thread will be destroyed during relaunch) std::thread launcherThread(OSLauncherThread, titleId); launcherThread.detach(); // suspend this thread coreinit::OSSuspendThread(coreinit::OSGetCurrentThread()); return 0; } uint32 OSLaunchTitleByPathl(const char* path, uint32 pathLength, uint32 argc) { char appXmlPath[1024]; cemu_assert_debug(argc == 0); // custom args not supported yet if(pathLength >= (sizeof(appXmlPath) - 32)) { // path too long cemuLog_logDebug(LogType::Force, "OSLaunchTitleByPathl: path too long"); return 0x80000000; } // read app.xml to get the titleId memcpy(appXmlPath, path, pathLength); appXmlPath[pathLength] = '\0'; strcat(appXmlPath, "/code/app.xml"); sint32 status; auto fscfile = fsc_open(appXmlPath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &status); if (!fscfile) { cemuLog_logDebug(LogType::Force, "OSLaunchTitleByPathl: failed to open target app.xml"); return 0x80000000; } uint32 size = fsc_getFileSize(fscfile); std::vector<uint8> tmpData(size); fsc_readFile(fscfile, tmpData.data(), size); fsc_close(fscfile); // parse app.xml to get the titleId pugi::xml_document app_doc; if (!app_doc.load_buffer_inplace(tmpData.data(), tmpData.size())) return false; uint64 titleId = std::stoull(app_doc.child("app").child("title_id").child_value(), nullptr, 16); if(titleId == 0) { cemuLog_logDebug(LogType::Force, "OSLaunchTitleByPathl: failed to parse titleId from app.xml"); return 0x80000000; } __LaunchByTitleId(titleId, 0, nullptr); return 0; } uint32 OSRestartGame(uint32 argc, MEMPTR<char>* argv) { __LaunchByTitleId(CafeSystem::GetForegroundTitleId(), argc, argv); return 0; } void OSReleaseForeground() { cemuLog_logDebug(LogType::Force, "OSReleaseForeground not implemented"); } bool s_transitionToBackground = false; bool s_transitionToForeground = false; void StartBackgroundForegroundTransition() { s_transitionToBackground = true; s_transitionToForeground = true; } // called at the beginning of OSReceiveMessage if the queue is the system message queue void UpdateSystemMessageQueue() { if(!OSIsInterruptEnabled()) return; cemu_assert_debug(!__OSHasSchedulerLock()); // normally syscall 0x2E is used to get the next message // for now we just have some preliminary logic here to allow a fake transition to background & foreground if(s_transitionToBackground) { // add transition to background message OSMessage msg{}; msg.data0 = stdx::to_underlying(SysMessageId::MsgReleaseForeground); msg.data1 = 0; // 1 -> System is shutting down 0 -> Begin transitioning to background OSMessageQueue* systemMessageQueue = coreinit::OSGetSystemMessageQueue(); if(OSSendMessage(systemMessageQueue, &msg, 0)) s_transitionToBackground = false; return; } if(s_transitionToForeground) { // add transition to foreground message OSMessage msg{}; msg.data0 = stdx::to_underlying(SysMessageId::MsgAcquireForeground); msg.data1 = 1; // ? msg.data2 = 1; // ? OSMessageQueue* systemMessageQueue = coreinit::OSGetSystemMessageQueue(); if(OSSendMessage(systemMessageQueue, &msg, 0)) s_transitionToForeground = false; return; } } // called when OSReceiveMessage returns a message from the system message queue void HandleReceivedSystemMessage(OSMessage* msg) { cemu_assert_debug(!__OSHasSchedulerLock()); cemuLog_log(LogType::Force, "Receiving message: {:08x}", (uint32)msg->data0); } uint32 OSDriver_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3) { cemuLog_logDebug(LogType::Force, "OSDriver_Register stubbed"); return 0; } uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId) { cemuLog_logDebug(LogType::Force, "OSDriver_Deregister stubbed"); return 0; } void miscInit() { s_currentTitleId = CafeSystem::GetForegroundTitleId(); s_sdkVersion = CafeSystem::GetForegroundTitleSDKVersion(); s_transitionToBackground = false; s_transitionToForeground = false; cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder); cafeExportRegister("coreinit", COSVReport, LogType::Placeholder); cafeExportRegister("coreinit", COSWarn, LogType::Placeholder); cafeExportRegister("coreinit", OSReport, LogType::Placeholder); cafeExportRegister("coreinit", OSVReport, LogType::Placeholder); cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder); cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder); cafeExportRegister("coreinit", OSGetPFID, LogType::Placeholder); cafeExportRegister("coreinit", OSGetUPID, LogType::Placeholder); cafeExportRegister("coreinit", OSGetTitleID, LogType::Placeholder); cafeExportRegister("coreinit", __OSGetProcessSDKVersion, LogType::Placeholder); g_homeButtonMenuEnabled = true; // enabled by default // Disney Infinity 2.0 actually relies on home button menu being enabled by default. If it's false it will crash due to calling erreula->IsAppearHomeNixSign() before initializing erreula cafeExportRegister("coreinit", OSIsHomeButtonMenuEnabled, LogType::CoreinitThread); cafeExportRegister("coreinit", OSEnableHomeButtonMenu, LogType::CoreinitThread); cafeExportRegister("coreinit", OSLaunchTitleByPathl, LogType::Placeholder); cafeExportRegister("coreinit", OSRestartGame, LogType::Placeholder); cafeExportRegister("coreinit", OSReleaseForeground, LogType::Placeholder); cafeExportRegister("coreinit", OSDriver_Register, LogType::Placeholder); cafeExportRegister("coreinit", OSDriver_Deregister, LogType::Placeholder); } }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Misc.h ================================================ #pragma once namespace coreinit { uint32 OSGetUPID(); uint32 OSGetPFID(); uint64 OSGetTitleID(); uint32 __OSGetProcessSDKVersion(); uint32 OSLaunchTitleByPathl(const char* path, uint32 pathLength, uint32 argc); uint32 OSRestartGame(uint32 argc, MEMPTR<char>* argv); void OSReleaseForeground(); void StartBackgroundForegroundTransition(); struct OSDriverInterface { MEMPTR<void> getDriverName; MEMPTR<void> init; MEMPTR<void> onAcquireForeground; MEMPTR<void> onReleaseForeground; MEMPTR<void> done; }; static_assert(sizeof(OSDriverInterface) == 0x14); uint32 OSDriver_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3); uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId); enum class COSReportModule { coreinit = 0, }; enum class COSReportLevel { Error = 0, Warn = 1, Info = 2 }; sint32 ppc_vprintf(const char* formatStr, char* strOut, sint32 maxLength, ppc_va_list* vargs); void miscInit(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_OSScreen.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/OS/libs/coreinit/coreinit_OSScreen_font.h" #include "Cafe/OS/libs/coreinit/coreinit_OSScreen.h" #define OSSCREEN_TV (0) #define OSSCREEN_DRC (1) namespace coreinit { struct { sint32 x; sint32 y; sint32 pitch; }screenSizes[2] = { { 1280, 720, 1280}, // TV { 896, 480, 896 } // DRC (values might be incorrect) }; void* currentScreenBasePtr[2] = { 0 }; void _OSScreen_Clear(uint32 screenIndex, uint32 color) { if (!currentScreenBasePtr[screenIndex]) return; uint32* output = (uint32*)currentScreenBasePtr[screenIndex]; sint32 sizeInPixels = screenSizes[screenIndex].pitch * screenSizes[screenIndex].y; color = _swapEndianU32(color); for (sint32 i = 0; i < sizeInPixels; i++) { *output = color; output++; } } void coreinitExport_OSScreenInit(PPCInterpreter_t* hCPU) { // todo - init VI registers? osLib_returnFromFunction(hCPU, 0); } void coreinitExport_OSScreenGetBufferSizeEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(screenIndex, 0); cemu_assert(screenIndex < 2); uint32 bufferSize = screenSizes[screenIndex].pitch * screenSizes[screenIndex].y * 4 * 2; osLib_returnFromFunction(hCPU, bufferSize); } void _updateCurrentDrawScreen(sint32 screenIndex) { uint32 screenDataSize = screenSizes[screenIndex].pitch * screenSizes[screenIndex].y * 4; if ((LatteGPUState.osScreen.screen[screenIndex].flipRequestCount & 1) != 0) currentScreenBasePtr[screenIndex] = memory_getPointerFromPhysicalOffset(LatteGPUState.osScreen.screen[screenIndex].physPtr + screenDataSize); else currentScreenBasePtr[screenIndex] = memory_getPointerFromPhysicalOffset(LatteGPUState.osScreen.screen[screenIndex].physPtr); } void coreinitExport_OSScreenSetBufferEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(screenIndex, 0); ppcDefineParamU32(buffer, 1); cemu_assert(screenIndex < 2); LatteGPUState.osScreen.screen[screenIndex].physPtr = buffer; _updateCurrentDrawScreen(screenIndex); osLib_returnFromFunction(hCPU, 0); } void coreinitExport_OSScreenEnableEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(screenIndex, 0); ppcDefineParamU32(isEnabled, 1); cemu_assert(screenIndex < 2); LatteGPUState.osScreen.screen[screenIndex].isEnabled = isEnabled != 0; osLib_returnFromFunction(hCPU, 0); } void coreinitExport_OSScreenClearBufferEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(screenIndex, 0); ppcDefineParamU32(color, 1); cemu_assert(screenIndex < 2); _OSScreen_Clear(screenIndex, color); osLib_returnFromFunction(hCPU, 0); } void coreinitExport_OSScreenFlipBuffersEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(screenIndex, 0); cemu_assert(screenIndex < 2); LatteGPUState.osScreen.screen[screenIndex].flipRequestCount++; _updateCurrentDrawScreen(screenIndex); osLib_returnFromFunction(hCPU, 0); } void coreinitExport_OSScreenPutPixelEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(screenIndex, 0); ppcDefineParamS32(x, 1); ppcDefineParamS32(y, 2); ppcDefineParamU32(color, 3); if (screenIndex >= 2) { osLib_returnFromFunction(hCPU, 0); return; } if (x >= 0 && x < screenSizes[screenIndex].x && y >= 0 && y < screenSizes[screenIndex].y) { *(uint32*)((uint8*)currentScreenBasePtr[screenIndex] + (x + y * screenSizes[screenIndex].pitch) * 4) = _swapEndianS32(color); } osLib_returnFromFunction(hCPU, 0); } const char* osScreenCharset = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; sint32 _getOSScreenFontCharIndex(char c) { const char* charset = osScreenCharset; while (*charset) { if (*charset == c) { return (sint32)(charset - osScreenCharset); } charset++; } return -1; } void coreinitExport_OSScreenPutFontEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(screenIndex, 0); ppcDefineParamS32(x, 1); ppcDefineParamS32(y, 2); ppcDefineParamStr(str, 3); // characters are: // 16 x 32 (including the margin) // with a margin of 4 x 8 if (y < 0) { debug_printf("OSScreenPutFontEx: y has invalid value\n"); osLib_returnFromFunction(hCPU, 0); return; } sint32 px = x * 16; sint32 py = y * 24; while (*str) { sint32 charIndex = _getOSScreenFontCharIndex(*str); if (charIndex >= 0) { const uint8* charBitmap = osscreenBitmapFont + charIndex * 50; for (sint32 fy = 0; fy < 25; fy++) { for (sint32 fx = 0; fx < 14; fx++) { if (((charBitmap[(fx / 8) + (fy) * 2] >> (7 - (fx & 7))) & 1) == 0) continue; *(uint32*)((uint8*)currentScreenBasePtr[screenIndex] + ((px + fx) + (py + fy) * screenSizes[screenIndex].pitch) * 4) = 0xFFFFFFFF; } } } px += 16; str++; } osLib_returnFromFunction(hCPU, 0); } void InitializeOSScreen() { osLib_addFunction("coreinit", "OSScreenInit", coreinitExport_OSScreenInit); osLib_addFunction("coreinit", "OSScreenGetBufferSizeEx", coreinitExport_OSScreenGetBufferSizeEx); osLib_addFunction("coreinit", "OSScreenSetBufferEx", coreinitExport_OSScreenSetBufferEx); osLib_addFunction("coreinit", "OSScreenEnableEx", coreinitExport_OSScreenEnableEx); osLib_addFunction("coreinit", "OSScreenClearBufferEx", coreinitExport_OSScreenClearBufferEx); osLib_addFunction("coreinit", "OSScreenFlipBuffersEx", coreinitExport_OSScreenFlipBuffersEx); osLib_addFunction("coreinit", "OSScreenPutPixelEx", coreinitExport_OSScreenPutPixelEx); osLib_addFunction("coreinit", "OSScreenPutFontEx", coreinitExport_OSScreenPutFontEx); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_OSScreen.h ================================================ #pragma once namespace coreinit { void InitializeOSScreen(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_OSScreen_font.h ================================================ // font 'More Perfect DOS VGA' 20pts const uint8 osscreenBitmapFont[] = { // @0 '!' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @50 '"' (14 pixels wide) 0x00, 0x00, // 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x18, 0xC0, // ## ## 0x18, 0xC0, // ## ## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @100 '#' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0xFF, 0xF0, // ############ 0xFF, 0xF0, // ############ 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0xFF, 0xF0, // ############ 0xFF, 0xF0, // ############ 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @150 '$' (14 pixels wide) 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0xE0, 0xE0, // ### ### 0xE0, 0x60, // ### ## 0xE0, 0x60, // ### ## 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0x3F, 0x80, // ####### 0x38, 0xA0, // ### # # 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0xC0, 0xE0, // ## ### 0xC0, 0xE0, // ## ### 0xE0, 0xE0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // // @200 '%' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x20, // ### # 0xF0, 0xF0, // #### #### 0xF0, 0xF0, // #### #### 0x01, 0xE0, // #### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x1E, 0x00, // #### 0x38, 0x00, // ### 0x38, 0x00, // ### 0xF0, 0xE0, // #### ### 0xC0, 0xF0, // ## #### 0xC0, 0xF0, // ## #### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @250 '&' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x1F, 0x00, // ##### 0x1F, 0x00, // ##### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x1F, 0x00, // ##### 0x1F, 0x00, // ##### 0x3E, 0xE0, // ##### ### 0xB7, 0xE0, // # ## ###### 0xE7, 0xC0, // ### ##### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0x3E, 0xE0, // ##### ### 0x3E, 0xE0, // ##### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @300 ''' (14 pixels wide) 0x00, 0x00, // 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @350 '(' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x07, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x07, 0x00, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @400 ')' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x07, 0x00, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x07, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @450 '*' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x1F, 0xC0, // ####### 0xFF, 0xF8, // ############# 0xFF, 0xF8, // ############# 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @500 '+' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x3F, 0xE0, // ######### 0x3F, 0xE0, // ######### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @550 ',' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @600 '-' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xF0, // ############ 0xFF, 0xF0, // ############ 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @650 '.' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @700 '/' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x20, // # 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x01, 0xC0, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x0E, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0xF0, 0x00, // #### 0xC0, 0x00, // ## 0xC0, 0x00, // ## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @750 '0' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x38, 0xE0, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0x38, 0xE0, // ### ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @800 '1' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x03, 0x80, // ### 0x03, 0x80, // ### 0x0F, 0x80, // ##### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x03, 0x80, // ### 0x03, 0x80, // ### 0x03, 0x80, // ### 0x03, 0x80, // ### 0x03, 0x80, // ### 0x03, 0x80, // ### 0x03, 0x80, // ### 0x03, 0x80, // ### 0x03, 0x80, // ### 0x03, 0x80, // ### 0x3F, 0xF0, // ########## 0x3F, 0xF0, // ########## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @850 '2' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x00, 0xE0, // ### 0x03, 0x80, // ### 0x03, 0x80, // ### 0x07, 0x00, // ### 0x1C, 0x00, // ### 0x1C, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0xE0, // ### ### 0xFF, 0xE0, // ########### 0xFF, 0xE0, // ########### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @900 '3' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x1F, 0xC0, // ####### 0x1C, 0x50, // ### # # 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @950 '4' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x07, 0xC0, // ##### 0x07, 0xC0, // ##### 0x1F, 0xC0, // ####### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xFF, 0xF0, // ############ 0xFF, 0xF0, // ############ 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x07, 0xF0, // ####### 0x07, 0xF0, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1000 '5' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xE0, // ########### 0xFF, 0xE0, // ########### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xFF, 0x80, // ######### 0xF0, 0xA0, // #### # # 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1050 '6' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x1F, 0x80, // ###### 0x1F, 0x80, // ###### 0x38, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xFF, 0xC0, // ########## 0xFF, 0xC0, // ########## 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1100 '7' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xE0, // ########### 0xFF, 0xE0, // ########### 0xE0, 0xE0, // ### ### 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x03, 0x80, // ### 0x07, 0x80, // #### 0x07, 0x00, // ### 0x1C, 0x00, // ### 0x1C, 0x00, // ### 0x1C, 0x00, // ### 0x1C, 0x00, // ### 0x1C, 0x00, // ### 0x1C, 0x00, // ### 0x1C, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1150 '8' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x3F, 0x80, // ####### 0xA7, 0xC0, // # # ##### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1200 '9' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x3F, 0xF0, // ########## 0x3F, 0xF0, // ########## 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1250 ':' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1300 ';' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1350 '<' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x01, 0xC0, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x0E, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x1C, 0x00, // ### 0x0E, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x01, 0xC0, // ### 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1400 '=' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0xF0, // ########## 0x3F, 0xF0, // ########## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0xF0, // ########## 0x3F, 0xF0, // ########## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1450 '>' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x38, 0x00, // ### 0x38, 0x00, // ### 0x0E, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x01, 0xC0, // ### 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x00, 0xE0, // ### 0x01, 0xC0, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x0E, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1500 '?' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1550 '@' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE7, 0xF0, // ### ####### 0xE7, 0xF0, // ### ####### 0xE7, 0xF0, // ### ####### 0xE7, 0xF0, // ### ####### 0xE7, 0xF0, // ### ####### 0xE7, 0xC0, // ### ##### 0xE7, 0xC0, // ### ##### 0xE0, 0x00, // ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1600 'A' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x06, 0x00, // ## 0x06, 0x00, // ## 0x1F, 0x80, // ###### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xFF, 0xF0, // ############ 0xFF, 0xF0, // ############ 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1650 'B' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xC0, // ########## 0xFF, 0xC0, // ########## 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0xFF, 0xC0, // ########## 0xFF, 0xC0, // ########## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1700 'C' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x38, 0x70, // ### ### 0xE0, 0x30, // ### ## 0xE0, 0x30, // ### ## 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x30, // ### ## 0xE0, 0x30, // ### ## 0x38, 0x70, // ### ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1750 'D' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0x80, // ######### 0xFF, 0x80, // ######### 0x39, 0xC0, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x39, 0xC0, // ### ### 0xFF, 0x80, // ######### 0xFF, 0x80, // ######### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1800 'E' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xF0, // ############ 0xFF, 0xF0, // ############ 0x38, 0x70, // ### ### 0x38, 0x30, // ### ## 0x38, 0x30, // ### ## 0x39, 0x80, // ### ## 0x39, 0x80, // ### ## 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x39, 0x80, // ### ## 0x39, 0x80, // ### ## 0x38, 0x00, // ### 0x38, 0x30, // ### ## 0x38, 0x30, // ### ## 0x38, 0x70, // ### ### 0xFF, 0xF0, // ############ 0xFF, 0xF0, // ############ 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1850 'F' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xF0, // ############ 0xFF, 0xF0, // ############ 0x38, 0x70, // ### ### 0x38, 0x30, // ### ## 0x38, 0x30, // ### ## 0x39, 0x80, // ### ## 0x39, 0x80, // ### ## 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x39, 0x80, // ### ## 0x39, 0x80, // ### ## 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0xFE, 0x00, // ####### 0xFE, 0x00, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1900 'G' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x38, 0xF0, // ### #### 0xE0, 0x30, // ### ## 0xE0, 0x20, // ### # 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE7, 0xF0, // ### ####### 0xE7, 0xF0, // ### ####### 0xE0, 0xF0, // ### #### 0xE0, 0xF0, // ### #### 0xE0, 0xF0, // ### #### 0xE0, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x1F, 0x20, // ##### # 0x1F, 0x20, // ##### # 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @1950 'H' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xFF, 0xE0, // ########### 0xFF, 0xE0, // ########### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2000 'I' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2050 'J' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0xF0, // ####### 0x07, 0xF0, // ####### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0x3F, 0x00, // ###### 0x3F, 0x00, // ###### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2100 'K' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xF8, 0x70, // ##### ### 0xF8, 0x70, // ##### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0xF8, 0x70, // ##### ### 0xF8, 0x70, // ##### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2150 'L' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFE, 0x00, // ####### 0xFE, 0x00, // ####### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x30, // ### ## 0x38, 0x30, // ### ## 0x38, 0x70, // ### ### 0xFF, 0xF0, // ############ 0xFF, 0xF0, // ############ 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2200 'M' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xF8, 0xF8, // ##### ##### 0xF8, 0xF8, // ##### ##### 0xFF, 0xF8, // ############# 0xFF, 0xF8, // ############# 0xFF, 0xF8, // ############# 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2250 'N' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xF8, 0x70, // ##### ### 0xFE, 0x70, // ####### ### 0xFE, 0x70, // ####### ### 0xFF, 0xF0, // ############ 0xFF, 0xF0, // ############ 0xE7, 0xF0, // ### ####### 0xE7, 0xF0, // ### ####### 0xE1, 0xF0, // ### ##### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2300 'O' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2350 'P' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xC0, // ########## 0xFF, 0xC0, // ########## 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0xFE, 0x00, // ####### 0xFE, 0x00, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2400 'Q' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE6, 0x70, // ### ## ### 0xE7, 0xF0, // ### ####### 0xE7, 0xF0, // ### ####### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x01, 0xC0, // ### 0x01, 0xF0, // ##### 0x01, 0xF0, // ##### 0x00, 0x00, // 0x00, 0x00, // // @2450 'R' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xC0, // ########## 0xFF, 0xC0, // ########## 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x39, 0xC0, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0xF8, 0x70, // ##### ### 0xF8, 0x70, // ##### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2500 'S' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x1F, 0x80, // ###### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2550 'T' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xFC, // ############## 0xFF, 0xFC, // ############## 0xE7, 0x3C, // ### ### #### 0xE7, 0x0C, // ### ### ## 0xC7, 0x0C, // ## ### ## 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2600 'U' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2650 'V' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x1F, 0xC0, // ####### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2700 'W' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xFF, 0xF8, // ############# 0xFF, 0xF8, // ############# 0xF8, 0xF8, // ##### ##### 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2750 'X' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x3F, 0xF0, // ########## 0x3F, 0xF0, // ########## 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x3F, 0xF0, // ########## 0x3F, 0xF0, // ########## 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2800 'Y' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x3C, // ### #### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0x38, 0xF0, // ### #### 0x38, 0xF0, // ### #### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2850 'Z' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xFC, // ############## 0xFF, 0xF8, // ############# 0xE0, 0x38, // ### ### 0xE0, 0xF0, // ### #### 0xC0, 0xF0, // ## #### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x1E, 0x00, // #### 0x38, 0x00, // ### 0x38, 0x00, // ### 0xE0, 0x0C, // ### ## 0xE0, 0x0C, // ### ## 0xE0, 0x3C, // ### #### 0xFF, 0xFC, // ############## 0xFF, 0xFC, // ############## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2900 '[' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x0F, 0xE0, // ####### 0x0F, 0xE0, // ####### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0F, 0xE0, // ####### 0x0F, 0xE0, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @2950 '\' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xC0, 0x00, // ## 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xF8, 0x00, // ##### 0xF8, 0x00, // ##### 0x3E, 0x00, // ##### 0x3F, 0x80, // ####### 0x1F, 0x80, // ###### 0x07, 0xC0, // ##### 0x07, 0xC0, // ##### 0x01, 0xF0, // ##### 0x01, 0xF0, // ##### 0x00, 0x70, // ### 0x00, 0x30, // ## 0x00, 0x30, // ## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3000 ']' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3050 '^' (14 pixels wide) 0x06, 0x00, // ## 0x06, 0x00, // ## 0x1F, 0x80, // ###### 0x39, 0xC0, // ### ### 0xA9, 0x60, // # # # # ## 0xE0, 0x70, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3100 '_' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xFC, // ############## 0xFF, 0xFC, // ############## 0x00, 0x00, // // @3150 '`' (14 pixels wide) 0x00, 0x00, // 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x07, 0x00, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3200 'a' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x01, 0xC0, // ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0x3E, 0x70, // ##### ### 0x3E, 0x70, // ##### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3250 'b' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xF8, 0x00, // ##### 0xF8, 0x00, // ##### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x39, 0xC0, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3300 'c' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3350 'd' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0xC0, // ##### 0x07, 0xC0, // ##### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x39, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0x3E, 0x70, // ##### ### 0x3E, 0x70, // ##### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3400 'e' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0xE0, 0xE0, // ### ### 0xFF, 0xE0, // ########### 0xFF, 0xE0, // ########### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0x00, // ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3450 'f' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0xC0, // ##### 0x07, 0xC0, // ##### 0x0E, 0x70, // ### ### 0x0E, 0x30, // ### ## 0x0E, 0x30, // ### ## 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3500 'g' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3E, 0x70, // ##### ### 0x3E, 0x70, // ##### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x01, 0xC0, // ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### // @3550 'h' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xF8, 0x00, // ##### 0xF8, 0x00, // ##### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x3E, 0x70, // ##### ### 0x3E, 0x70, // ##### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0xF8, 0x70, // ##### ### 0xF8, 0x70, // ##### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3600 'i' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x1F, 0x00, // ##### 0x1F, 0x00, // ##### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3650 'j' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x00, // 0x00, 0x00, // 0x01, 0xF0, // ##### 0x01, 0xF0, // ##### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x00, 0x70, // ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x0F, 0xC0, // ###### 0x0F, 0xC0, // ###### // @3700 'k' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xF8, 0x00, // ##### 0xF8, 0x00, // ##### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x39, 0xC0, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x39, 0xC0, // ### ### 0x39, 0xC0, // ### ### 0x38, 0x70, // ### ### 0xF8, 0x70, // ##### ### 0xF8, 0x70, // ##### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3750 'l' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x1F, 0x00, // ##### 0x1F, 0x00, // ##### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3800 'm' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xF8, 0xE0, // ##### ### 0xF8, 0xE0, // ##### ### 0xFF, 0xF8, // ############# 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3850 'n' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE7, 0xC0, // ### ##### 0xE7, 0xC0, // ### ##### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3900 'o' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0xE0, 0xE0, // ### ### 0x3F, 0x80, // ####### 0x3F, 0x80, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @3950 'p' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE7, 0xC0, // ### ##### 0xE7, 0xC0, // ### ##### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0xFE, 0x00, // ####### 0xFE, 0x00, // ####### // @4000 'q' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3E, 0x70, // ##### ### 0x3E, 0x70, // ##### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x01, 0xC0, // ### 0x07, 0xF0, // ####### 0x07, 0xF0, // ####### // @4050 'r' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE7, 0xC0, // ### ##### 0xE7, 0xC0, // ### ##### 0x3E, 0x70, // ##### ### 0x38, 0x70, // ### ### 0x38, 0x70, // ### ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0xFE, 0x00, // ####### 0xFE, 0x00, // ####### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4100 's' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x38, 0x00, // ### 0x1F, 0x80, // ###### 0x1F, 0x80, // ###### 0x01, 0xC0, // ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x3F, 0xC0, // ######## 0x3F, 0xC0, // ######## 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4150 't' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x06, 0x00, // ## 0x06, 0x00, // ## 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x7F, 0xC0, // ######### 0x7F, 0xC0, // ######### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x00, // ### 0x0E, 0x70, // ### ### 0x0E, 0x70, // ### ### 0x07, 0xC0, // ##### 0x07, 0xC0, // ##### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4200 'u' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0xE1, 0xC0, // ### ### 0x3E, 0x70, // ##### ### 0x3E, 0x70, // ##### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4250 'v' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x3C, // ### #### 0xE0, 0x3C, // ### #### 0xE0, 0x3C, // ### #### 0xE0, 0x3C, // ### #### 0xE0, 0x3C, // ### #### 0xE0, 0x3C, // ### #### 0xE0, 0x3C, // ### #### 0x38, 0xF0, // ### #### 0x38, 0xF0, // ### #### 0x1F, 0xC0, // ####### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4300 'w' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xE7, 0x38, // ### ### ### 0xFF, 0xF8, // ############# 0x38, 0xE0, // ### ### 0x38, 0xE0, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4350 'x' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0x38, 0x70, // ### ### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x1F, 0xC0, // ####### 0x38, 0x70, // ### ### 0xE0, 0x38, // ### ### 0xE0, 0x38, // ### ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4400 'y' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0xE0, 0x70, // ### ### 0x3F, 0xF0, // ########## 0x3F, 0xF0, // ########## 0x00, 0x70, // ### 0x00, 0x70, // ### 0x01, 0xC0, // ### 0xFF, 0x80, // ######### 0xFF, 0x80, // ######### // @4450 'z' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0xFF, 0xE0, // ########### 0xFF, 0xE0, // ########### 0xE3, 0x80, // ### ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x1C, 0x00, // ### 0x1C, 0x00, // ### 0x38, 0x00, // ### 0x38, 0x00, // ### 0xE0, 0xE0, // ### ### 0xFF, 0xE0, // ########### 0xFF, 0xE0, // ########### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4500 '{' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x01, 0xF0, // ##### 0x01, 0xF0, // ##### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x3E, 0x00, // ##### 0x3E, 0x00, // ##### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x01, 0xF0, // ##### 0x01, 0xF0, // ##### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4550 '|' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4600 '}' (14 pixels wide) 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x3E, 0x00, // ##### 0x3E, 0x00, // ##### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x01, 0xE0, // #### 0x01, 0xE0, // #### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x07, 0x00, // ### 0x3E, 0x00, // ##### 0x3E, 0x00, // ##### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // // @4650 '~' (14 pixels wide) 0x00, 0x00, // 0x3C, 0xE0, // #### ### 0x3C, 0xE0, // #### ### 0xE7, 0x80, // ### #### 0xE7, 0x80, // ### #### 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // 0x00, 0x00, // }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_OverlayArena.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/common/OSCommon.h" #include "coreinit_OverlayArena.h" namespace coreinit { struct { bool isEnabled; }g_coreinitOverlayArena = { 0 }; uint32 OSIsEnabledOverlayArena() { return g_coreinitOverlayArena.isEnabled ? 1 : 0; } void OSEnableOverlayArena(uint32 uknParam, uint32be* areaOffset, uint32be* areaSize) { if (g_coreinitOverlayArena.isEnabled == false) { memory_enableOverlayArena(); g_coreinitOverlayArena.isEnabled = true; } *areaOffset = MEMORY_OVERLAY_AREA_OFFSET; *areaSize = MEMORY_OVERLAY_AREA_SIZE; } void InitializeOverlayArena() { cafeExportRegister("coreinit", OSIsEnabledOverlayArena, LogType::Placeholder); cafeExportRegister("coreinit", OSEnableOverlayArena, LogType::Placeholder); g_coreinitOverlayArena.isEnabled = false; } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_OverlayArena.h ================================================ namespace coreinit { void InitializeOverlayArena(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Scheduler.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "coreinit_Scheduler.h" thread_local sint32 s_schedulerLockCount = 0; #if BOOST_OS_WINDOWS #include <synchapi.h> CRITICAL_SECTION s_csSchedulerLock; #else #include <pthread.h> pthread_mutex_t s_ptmSchedulerLock; #endif void __OSLockScheduler(void* obj) { #if BOOST_OS_WINDOWS EnterCriticalSection(&s_csSchedulerLock); #else pthread_mutex_lock(&s_ptmSchedulerLock); #endif s_schedulerLockCount++; cemu_assert_debug(s_schedulerLockCount <= 1); // >= 2 should not happen. Scheduler lock does not allow recursion } bool __OSHasSchedulerLock() { return s_schedulerLockCount > 0; } bool __OSTryLockScheduler(void* obj) { bool r; #if BOOST_OS_WINDOWS r = TryEnterCriticalSection(&s_csSchedulerLock); #else r = pthread_mutex_trylock(&s_ptmSchedulerLock) == 0; #endif if (r) { s_schedulerLockCount++; return true; } return false; } void __OSUnlockScheduler(void* obj) { s_schedulerLockCount--; cemu_assert_debug(s_schedulerLockCount >= 0); #if BOOST_OS_WINDOWS LeaveCriticalSection(&s_csSchedulerLock); #else pthread_mutex_unlock(&s_ptmSchedulerLock); #endif } namespace coreinit { uint32 OSIsInterruptEnabled() { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if (hCPU == nullptr) return 0; return hCPU->coreInterruptMask; } // disables interrupts and scheduling uint32 OSDisableInterrupts() { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if (hCPU == nullptr) return 0; uint32 prevInterruptMask = hCPU->coreInterruptMask; if (hCPU->coreInterruptMask != 0) { // we have no efficient method to turn off scheduling completely, so instead we just increase the remaining cycles if (hCPU->remainingCycles >= 0x40000000) cemuLog_log(LogType::Force, "OSDisableInterrupts(): Warning - Interrupts already disabled but the mask was still set? remCycles {:08x} LR {:08x}", hCPU->remainingCycles, hCPU->spr.LR); hCPU->remainingCycles += 0x40000000; } hCPU->coreInterruptMask = 0; return prevInterruptMask; } uint32 OSRestoreInterrupts(uint32 interruptMask) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if (hCPU == nullptr) return 0; uint32 prevInterruptMask = hCPU->coreInterruptMask; if (hCPU->coreInterruptMask == 0 && interruptMask != 0) { hCPU->remainingCycles -= 0x40000000; } hCPU->coreInterruptMask = interruptMask; return prevInterruptMask; } uint32 OSEnableInterrupts() { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); uint32 prevInterruptMask = hCPU->coreInterruptMask; OSRestoreInterrupts(1); return prevInterruptMask; } void InitializeSchedulerLock() { #if BOOST_OS_WINDOWS InitializeCriticalSection(&s_csSchedulerLock); #else pthread_mutexattr_t ma; pthread_mutexattr_init(&ma); pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&s_ptmSchedulerLock, &ma); #endif cafeExportRegister("coreinit", __OSLockScheduler, LogType::Placeholder); cafeExportRegister("coreinit", __OSUnlockScheduler, LogType::Placeholder); cafeExportRegister("coreinit", OSDisableInterrupts, LogType::CoreinitThread); cafeExportRegister("coreinit", OSEnableInterrupts, LogType::CoreinitThread); cafeExportRegister("coreinit", OSRestoreInterrupts, LogType::CoreinitThread); } }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Scheduler.h ================================================ #pragma once void __OSLockScheduler(void* obj = nullptr); bool __OSHasSchedulerLock(); bool __OSTryLockScheduler(void* obj = nullptr); void __OSUnlockScheduler(void* obj = nullptr); namespace coreinit { uint32 OSIsInterruptEnabled(); uint32 OSDisableInterrupts(); uint32 OSRestoreInterrupts(uint32 interruptMask); uint32 OSEnableInterrupts(); void InitializeSchedulerLock(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit.h" namespace coreinit { void __OSBoostThread(OSThread_t* thread) { __OSLockScheduler(); thread->stateFlags |= 0x20000; thread->context.boostCount += 1; __OSUpdateThreadEffectivePriority(thread); // sets thread->effectivePriority to zero since boostCount != 0 __OSUnlockScheduler(); } void __OSDeboostThread(OSThread_t* thread) { __OSLockScheduler(); cemu_assert_debug(thread->context.boostCount != 0); thread->context.boostCount -= 1; if (thread->context.boostCount == 0) { thread->stateFlags &= ~0x20000; __OSUpdateThreadEffectivePriority(thread); // todo - reschedule if lower priority than other threads on current core? } __OSUnlockScheduler(); } void OSInitSpinLock(OSSpinLock* spinlock) { spinlock->userData = spinlock; spinlock->ownerThread = nullptr; spinlock->count = 0; spinlock->interruptMask = 1; } bool OSAcquireSpinLock(OSSpinLock* spinlock) { OSThread_t* currentThread = OSGetCurrentThread(); if (spinlock->ownerThread == currentThread) { spinlock->count += 1; return true; } else { // loop until lock acquired while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) { OSYieldThread(); } } __OSBoostThread(currentThread); return true; } bool OSTryAcquireSpinLock(OSSpinLock* spinlock) { OSThread_t* currentThread = OSGetCurrentThread(); if (spinlock->ownerThread == currentThread) { spinlock->count += 1; return true; } // try acquire once if (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) return false; __OSBoostThread(currentThread); return true; } bool OSTryAcquireSpinLockWithTimeout(OSSpinLock* spinlock, uint64 timeout) { // used by CoD: Ghosts cemu_assert_debug((timeout >> 63) == 0); // negative? OSThread_t* currentThread = OSGetCurrentThread(); if (spinlock->ownerThread == currentThread) { spinlock->count += 1; return true; } else { // loop until lock acquired or timeout occurred uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout); while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) { OSYieldThread(); if (OSGetSystemTime() >= timeoutValue) { return false; } } } __OSBoostThread(currentThread); return true; } bool OSReleaseSpinLock(OSSpinLock* spinlock) { OSThread_t* currentThread = OSGetCurrentThread(); cemu_assert_debug(spinlock->ownerThread == currentThread); if (spinlock->count != 0) { spinlock->count -= 1; return true; } // release spinlock while (!spinlock->ownerThread.atomic_compare_exchange(currentThread, nullptr)); __OSDeboostThread(currentThread); return true; } bool OSUninterruptibleSpinLock_Acquire(OSSpinLock* spinlock) { // frequently used by VC DS OSThread_t* currentThread = OSGetCurrentThread(); cemu_assert_debug(currentThread != nullptr); if (spinlock->ownerThread == currentThread) { spinlock->count += 1; return true; } else { // loop until lock acquired if (coreinit::__CemuIsMulticoreMode()) { while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) { _mm_pause(); } } else { // we are in single-core mode and the lock will never be released unless we let other threads resume work // to avoid an infinite loop we have no choice but to yield the thread even it is in an uninterruptible state if( !OSIsInterruptEnabled() ) cemuLog_logOnce(LogType::APIErrors, "OSUninterruptibleSpinLock_Acquire(): Lock is occupied which requires a wait but current thread is already in an uninterruptible state (Avoid cascaded OSDisableInterrupts and/or OSUninterruptibleSpinLock)"); while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) { OSYieldThread(); } } } __OSBoostThread(currentThread); spinlock->interruptMask = OSDisableInterrupts(); cemu_assert_debug(spinlock->ownerThread == currentThread); return true; } bool OSUninterruptibleSpinLock_TryAcquire(OSSpinLock* spinlock) { OSThread_t* currentThread = OSGetCurrentThread(); if (spinlock->ownerThread == currentThread) { spinlock->count += 1; return true; } // try acquire once if (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) return false; __OSBoostThread(currentThread); spinlock->interruptMask = OSDisableInterrupts(); return true; } bool OSUninterruptibleSpinLock_TryAcquireWithTimeout(OSSpinLock* spinlock, uint64 timeout) { cemu_assert_debug((timeout >> 63) == 0); // negative? OSThread_t* currentThread = OSGetCurrentThread(); if (spinlock->ownerThread == currentThread) { spinlock->count += 1; return true; } else { // loop until lock acquired or timeout occurred uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout); while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) { OSYieldThread(); if (OSGetSystemTime() >= timeoutValue) { return false; } } } __OSBoostThread(currentThread); spinlock->interruptMask = OSDisableInterrupts(); return true; } bool OSUninterruptibleSpinLock_Release(OSSpinLock* spinlock) { OSThread_t* currentThread = OSGetCurrentThread(); cemu_assert_debug(spinlock->ownerThread == currentThread); if (spinlock->count != 0) { spinlock->count -= 1; return true; } // release spinlock OSRestoreInterrupts(spinlock->interruptMask); spinlock->interruptMask = 1; while (!spinlock->ownerThread.atomic_compare_exchange(currentThread, nullptr)); __OSDeboostThread(currentThread); return true; } void InitializeSpinlock() { cafeExportRegister("coreinit", OSInitSpinLock, LogType::Placeholder); cafeExportRegister("coreinit", OSAcquireSpinLock, LogType::Placeholder); cafeExportRegister("coreinit", OSTryAcquireSpinLock, LogType::Placeholder); cafeExportRegister("coreinit", OSTryAcquireSpinLockWithTimeout, LogType::Placeholder); cafeExportRegister("coreinit", OSReleaseSpinLock, LogType::Placeholder); cafeExportRegister("coreinit", OSUninterruptibleSpinLock_Acquire, LogType::Placeholder); cafeExportRegister("coreinit", OSUninterruptibleSpinLock_TryAcquire, LogType::Placeholder); cafeExportRegister("coreinit", OSUninterruptibleSpinLock_TryAcquireWithTimeout, LogType::Placeholder); cafeExportRegister("coreinit", OSUninterruptibleSpinLock_Release, LogType::Placeholder); } #pragma endregion } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Spinlock.h ================================================ #pragma once namespace coreinit { struct OSSpinLock { /* +0x00 */ MEMPTR<struct OSThread_t> ownerThread; /* +0x04 */ MEMPTR<void> userData; /* +0x08 */ uint32be count; /* +0x0C */ uint32be interruptMask; }; static_assert(sizeof(OSSpinLock) == 0x10); void InitializeSpinlock(); void OSInitSpinLock(OSSpinLock* spinlock); bool OSAcquireSpinLock(OSSpinLock* spinlock); bool OSTryAcquireSpinLock(OSSpinLock* spinlock); bool OSTryAcquireSpinLockWithTimeout(OSSpinLock* spinlock, uint64 timeout); bool OSReleaseSpinLock(OSSpinLock* spinlock); bool OSUninterruptibleSpinLock_Acquire(OSSpinLock* spinlock); bool OSUninterruptibleSpinLock_TryAcquire(OSSpinLock* spinlock); bool OSUninterruptibleSpinLock_TryAcquireWithTimeout(OSSpinLock* spinlock, uint64 timeout); bool OSUninterruptibleSpinLock_Release(OSSpinLock* spinlock); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "util/helpers/fspinlock.h" namespace coreinit { /************* OSEvent ************/ void OSInitEvent(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode) { event->magic = 'eVnT'; cemu_assert_debug(event->magic == 0x65566e54); event->userData = nullptr; event->ukn08 = 0; event->state = initialState; event->mode = mode; OSInitThreadQueueEx(&event->threadQueue, event); } void OSInitEventEx(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode, void* userData) { OSInitEvent(event, initialState, mode); event->userData = userData; } void OSResetEvent(OSEvent* event) { __OSLockScheduler(); if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED) event->state = OSEvent::EVENT_STATE::STATE_NOT_SIGNALED; __OSUnlockScheduler(); } void OSWaitEventInternal(OSEvent* event) { if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED) { if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO) event->state = OSEvent::EVENT_STATE::STATE_NOT_SIGNALED; } else { // enter wait queue event->threadQueue.queueAndWait(OSGetCurrentThread()); } } void OSWaitEvent(OSEvent* event) { __OSLockScheduler(); OSWaitEventInternal(event); __OSUnlockScheduler(); } struct WaitEventWithTimeoutData { OSThread_t* thread; OSThreadQueue* threadQueue; std::atomic_bool hasTimeout; }; void _OSWaitEventWithTimeoutHandler(uint64 currentTick, void* context) { cemu_assert_debug(__OSHasSchedulerLock()); WaitEventWithTimeoutData* data = (WaitEventWithTimeoutData*)context; if (data->thread->state == OSThread_t::THREAD_STATE::STATE_WAITING) { data->hasTimeout = true; data->threadQueue->cancelWait(data->thread); } } bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout) { __OSLockScheduler(); if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED) { if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO) event->state = OSEvent::EVENT_STATE::STATE_NOT_SIGNALED; } else { if (timeout == 0) { // fail immediately __OSUnlockScheduler(); return false; } // wait and set timeout // workaround for a bad implementation in some Unity games (like Qube Directors Cut, see FEventWiiU::Wait) // where the the return value of OSWaitEventWithTimeout is ignored and instead the game measures the elapsed time to determine if a timeout occurred if (timeout < 0x00FFFFFFFFFFFFFFULL) timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout) WaitEventWithTimeoutData data; data.thread = OSGetCurrentThread(); data.threadQueue = &event->threadQueue; data.hasTimeout = false; auto hostAlarm = coreinit::OSHostAlarmCreate(OSGetTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data); event->threadQueue.queueAndWait(OSGetCurrentThread()); coreinit::OSHostAlarmDestroy(hostAlarm); if (data.hasTimeout) { __OSUnlockScheduler(); return false; } } __OSUnlockScheduler(); return true; } void OSSignalEventInternal(OSEvent* event) { cemu_assert_debug(__OSHasSchedulerLock()); if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED) { return; } if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO) { // in auto mode wake up one thread or if there is none then set signaled if (event->threadQueue.isEmpty()) event->state = OSEvent::EVENT_STATE::STATE_SIGNALED; else event->threadQueue.wakeupSingleThreadWaitQueue(true); } else { // in manual mode wake up all threads and set to signaled event->state = OSEvent::EVENT_STATE::STATE_SIGNALED; event->threadQueue.wakeupEntireWaitQueue(true); } } void OSSignalEvent(OSEvent* event) { __OSLockScheduler(); OSSignalEventInternal(event); __OSUnlockScheduler(); } void OSSignalEventAllInternal(OSEvent* event) { if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED) { return; } if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO) { // in auto mode wake up one thread or if there is none then set signaled if (event->threadQueue.isEmpty()) event->state = OSEvent::EVENT_STATE::STATE_SIGNALED; else event->threadQueue.wakeupEntireWaitQueue(true); } else { // in manual mode wake up all threads and set to signaled event->state = OSEvent::EVENT_STATE::STATE_SIGNALED; event->threadQueue.wakeupEntireWaitQueue(true); } } void OSSignalEventAll(OSEvent* event) { __OSLockScheduler(); OSSignalEventAllInternal(event); __OSUnlockScheduler(); } /************* OSRendezvous ************/ SysAllocator<OSEvent> g_rendezvousEvent; void OSInitRendezvous(OSRendezvous* rendezvous) { __OSLockScheduler(); rendezvous->userData = rendezvous; for (sint32 i = 0; i < PPC_CORE_COUNT; i++) rendezvous->coreHit[i] = 0; __OSUnlockScheduler(); } bool OSWaitRendezvous(OSRendezvous* rendezvous, uint32 coreMask) { __OSLockScheduler(); rendezvous->coreHit[OSGetCoreId()] = 1; OSSignalEventAllInternal(g_rendezvousEvent.GetPtr()); while (true) { bool metAll = true; for(sint32 i=0; i<PPC_CORE_COUNT; i++) { if( (coreMask & (1<<i)) == 0 ) continue; // core not required by core mask if (rendezvous->coreHit[i] == 0) { metAll = false; break; } } if (metAll) break; OSWaitEventInternal(g_rendezvousEvent.GetPtr()); } __OSUnlockScheduler(); return true; } /************* OSMutex ************/ void OSInitMutexEx(OSMutex* mutex, void* userData) { mutex->magic = 'mUtX'; mutex->userData = userData; mutex->ukn08 = 0; mutex->owner = nullptr; mutex->lockCount = 0; OSInitThreadQueueEx(&mutex->threadQueue, mutex); } void OSInitMutex(OSMutex* mutex) { OSInitMutexEx(mutex, nullptr); } void OSLockMutexInternal(OSMutex* mutex) { OSThread_t* currentThread = OSGetCurrentThread(); int_fast32_t failedAttempts = 0; while (true) { if (mutex->owner == nullptr) { // acquire lock mutex->owner = currentThread; cemu_assert_debug(mutex->lockCount == 0); mutex->lockCount = 1; // cemu_assert_debug(mutex->next == nullptr && mutex->prev == nullptr); -> not zero initialized currentThread->mutexQueue.addMutex(mutex); break; } else if (mutex->owner == currentThread) { mutex->lockCount = mutex->lockCount + 1; break; } else { if (failedAttempts >= 0x800) cemuLog_log(LogType::Force, "Detected long-term contested OSLockMutex"); currentThread->waitingForMutex = mutex; mutex->threadQueue.queueAndWait(currentThread); currentThread->waitingForMutex = nullptr; failedAttempts++; } } } void OSLockMutex(OSMutex* mutex) { __OSLockScheduler(); OSTestThreadCancelInternal(); OSLockMutexInternal(mutex); __OSUnlockScheduler(); } bool OSTryLockMutex(OSMutex* mutex) { OSThread_t* currentThread = OSGetCurrentThread(); __OSLockScheduler(); OSTestThreadCancelInternal(); if (mutex->owner == nullptr) { // acquire lock mutex->owner = currentThread; cemu_assert_debug(mutex->lockCount == 0); mutex->lockCount = 1; // cemu_assert_debug(mutex->next == nullptr && mutex->prev == nullptr); -> not zero initialized currentThread->mutexQueue.addMutex(mutex); // currentThread->cancelState = currentThread->cancelState | 0x10000; } else if (mutex->owner == currentThread) { mutex->lockCount = mutex->lockCount + 1; } else { __OSUnlockScheduler(); return false; } __OSUnlockScheduler(); return true; } void OSUnlockMutexInternal(OSMutex* mutex) { OSThread_t* currentThread = OSGetCurrentThread(); cemu_assert_debug(mutex->owner == currentThread); cemu_assert_debug(mutex->lockCount > 0); mutex->lockCount = mutex->lockCount - 1; if (mutex->lockCount == 0) { currentThread->mutexQueue.removeMutex(mutex); mutex->owner = nullptr; if (!mutex->threadQueue.isEmpty()) mutex->threadQueue.wakeupSingleThreadWaitQueue(true, true); } // currentThread->cancelState = currentThread->cancelState & ~0x10000; } void OSUnlockMutex(OSMutex* mutex) { __OSLockScheduler(); OSUnlockMutexInternal(mutex); __OSUnlockScheduler(); } /************* OSCond ************/ void OSInitCond(OSCond* cond) { cond->magic = 0x634e6456; cond->userData = nullptr; cond->ukn08 = 0; OSInitThreadQueueEx(&cond->threadQueue, cond); } void OSInitCondEx(OSCond* cond, void* userData) { OSInitCond(cond); cond->userData = userData; } void OSSignalCond(OSCond* cond) { OSWakeupThread(&cond->threadQueue); } void OSWaitCond(OSCond* cond, OSMutex* mutex) { // seen in Bayonetta 2 // releases the mutex while waiting for the condition to be signaled __OSLockScheduler(); OSThread_t* currentThread = OSGetCurrentThread(); cemu_assert_debug(mutex->owner == currentThread); sint32 prevLockCount = mutex->lockCount; // unlock mutex mutex->lockCount = 0; currentThread->mutexQueue.removeMutex(mutex); mutex->owner = nullptr; if (!mutex->threadQueue.isEmpty()) mutex->threadQueue.wakeupEntireWaitQueue(false); // wait on condition cond->threadQueue.queueAndWait(currentThread); // reacquire mutex OSLockMutexInternal(mutex); mutex->lockCount = prevLockCount; __OSUnlockScheduler(); } /************* OSSemaphore ************/ void OSInitSemaphoreEx(OSSemaphore* semaphore, sint32 initialCount, void* userData) { __OSLockScheduler(); semaphore->magic = 0x73506852; semaphore->userData = userData; semaphore->ukn08 = 0; semaphore->count = initialCount; OSInitThreadQueueEx(&semaphore->threadQueue, semaphore); __OSUnlockScheduler(); } void OSInitSemaphore(OSSemaphore* semaphore, sint32 initialCount) { OSInitSemaphoreEx(semaphore, initialCount, nullptr); } sint32 OSWaitSemaphoreInternal(OSSemaphore* semaphore) { cemu_assert_debug(__OSHasSchedulerLock()); while (true) { sint32 prevCount = semaphore->count; if (prevCount > 0) { semaphore->count = prevCount - 1; return prevCount; } semaphore->threadQueue.queueAndWait(OSGetCurrentThread()); } } sint32 OSWaitSemaphore(OSSemaphore* semaphore) { __OSLockScheduler(); sint32 r = OSWaitSemaphoreInternal(semaphore); __OSUnlockScheduler(); return r; } sint32 OSTryWaitSemaphore(OSSemaphore* semaphore) { __OSLockScheduler(); sint32 prevCount = semaphore->count; if (prevCount > 0) { semaphore->count = prevCount - 1; } __OSUnlockScheduler(); return prevCount; } sint32 OSSignalSemaphoreInternal(OSSemaphore* semaphore, bool reschedule) { cemu_assert_debug(__OSHasSchedulerLock()); sint32 prevCount = semaphore->count; semaphore->count = prevCount + 1; semaphore->threadQueue.wakeupEntireWaitQueue(reschedule); return prevCount; } sint32 OSSignalSemaphore(OSSemaphore* semaphore) { __OSLockScheduler(); sint32 r = OSSignalSemaphoreInternal(semaphore, true); __OSUnlockScheduler(); return r; } sint32 OSGetSemaphoreCount(OSSemaphore* semaphore) { // seen in Assassin's Creed 4 __OSLockScheduler(); sint32 currentCount = semaphore->count; __OSUnlockScheduler(); return currentCount; } /************* OSFastMutex ************/ void OSFastMutex_Init(OSFastMutex* fastMutex, void* userData) { fastMutex->magic = 0x664d7458; fastMutex->userData = userData; fastMutex->owner = nullptr; fastMutex->lockCount = 0; fastMutex->contendedState = 0; fastMutex->threadQueueSmall.head = nullptr; fastMutex->threadQueueSmall.tail = nullptr; fastMutex->ownedLink.next = nullptr; fastMutex->ownedLink.prev = nullptr; fastMutex->contendedLink.next = nullptr; fastMutex->contendedLink.prev = nullptr; } FSpinlock g_fastMutexSpinlock; void _OSFastMutex_AcquireContention(OSFastMutex* fastMutex) { g_fastMutexSpinlock.lock(); } void _OSFastMutex_ReleaseContention(OSFastMutex* fastMutex) { g_fastMutexSpinlock.unlock(); } void OSFastMutex_LockInternal(OSFastMutex* fastMutex) { cemu_assert_debug(!__OSHasSchedulerLock()); OSThread_t* currentThread = OSGetCurrentThread(); _OSFastMutex_AcquireContention(fastMutex); while (true) { if (fastMutex->owner.atomic_compare_exchange(nullptr, currentThread))//(fastMutex->owner == nullptr) { // acquire lock cemu_assert_debug(fastMutex->owner == currentThread); cemu_assert_debug(fastMutex->lockCount == 0); fastMutex->lockCount = 1; // todo - add to thread owned fast mutex queue break; } else if (fastMutex->owner == currentThread) { fastMutex->lockCount = fastMutex->lockCount + 1; break; } else { currentThread->waitingForFastMutex = fastMutex; __OSLockScheduler(); fastMutex->threadQueueSmall.queueOnly(currentThread); _OSFastMutex_ReleaseContention(fastMutex); PPCCore_switchToSchedulerWithLock(); currentThread->waitingForFastMutex = nullptr; __OSUnlockScheduler(); _OSFastMutex_AcquireContention(fastMutex); continue; } } _OSFastMutex_ReleaseContention(fastMutex); } void OSFastMutex_Lock(OSFastMutex* fastMutex) { OSFastMutex_LockInternal(fastMutex); } bool OSFastMutex_TryLock(OSFastMutex* fastMutex) { OSThread_t* currentThread = OSGetCurrentThread(); _OSFastMutex_AcquireContention(fastMutex); if (fastMutex->owner.atomic_compare_exchange(nullptr, currentThread)) { // acquire lock cemu_assert_debug(fastMutex->owner == currentThread); cemu_assert_debug(fastMutex->lockCount == 0); fastMutex->lockCount = 1; // todo - add to thread owned fast mutex queue } else if (fastMutex->owner == currentThread) { fastMutex->lockCount = fastMutex->lockCount + 1; } else { _OSFastMutex_ReleaseContention(fastMutex); return false; } _OSFastMutex_ReleaseContention(fastMutex); return true; } void OSFastMutex_UnlockInternal(OSFastMutex* fastMutex) { cemu_assert_debug(!__OSHasSchedulerLock()); OSThread_t* currentThread = OSGetCurrentThread(); _OSFastMutex_AcquireContention(fastMutex); if (fastMutex->owner != currentThread) { // seen in Paper Mario Color Splash //cemuLog_log(LogType::Force, "OSFastMutex_Unlock() called on mutex which is not owned by current thread"); _OSFastMutex_ReleaseContention(fastMutex); return; } cemu_assert_debug(fastMutex->lockCount > 0); fastMutex->lockCount = fastMutex->lockCount - 1; if (fastMutex->lockCount == 0) { // set owner to null if (!fastMutex->owner.atomic_compare_exchange(currentThread, nullptr)) { cemu_assert_debug(false); // should never happen } if (!fastMutex->threadQueueSmall.isEmpty()) { __OSLockScheduler(); fastMutex->threadQueueSmall.wakeupSingleThreadWaitQueue(false); __OSUnlockScheduler(); } } _OSFastMutex_ReleaseContention(fastMutex); } void OSFastMutex_Unlock(OSFastMutex* fastMutex) { //__OSLockScheduler(); OSFastMutex_UnlockInternal(fastMutex); //__OSUnlockScheduler(); } /************* OSFastCond ************/ void OSFastCond_Init(OSFastCond* fastCond, void* userData) { fastCond->magic = 0x664e6456; fastCond->userData = userData; fastCond->ukn08 = 0; OSInitThreadQueueEx(&fastCond->threadQueue, fastCond); } void OSFastCond_Wait(OSFastCond* fastCond, OSFastMutex* fastMutex) { // releases the mutex while waiting for the condition to be signaled __OSLockScheduler(); cemu_assert_debug(fastMutex->owner == OSGetCurrentThread()); sint32 prevLockCount = fastMutex->lockCount; // unlock mutex fastMutex->lockCount = 0; fastMutex->owner = nullptr; if (!fastMutex->threadQueueSmall.isEmpty()) fastMutex->threadQueueSmall.wakeupEntireWaitQueue(false); // wait on condition fastCond->threadQueue.queueAndWait(OSGetCurrentThread()); // reacquire mutex __OSUnlockScheduler(); OSFastMutex_LockInternal(fastMutex); fastMutex->lockCount = prevLockCount; } void OSFastCond_Signal(OSFastCond* fastCond) { OSWakeupThread(&fastCond->threadQueue); } /************* init ************/ void InitializeConcurrency() { OSInitEvent(g_rendezvousEvent.GetPtr(), OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO); // OSEvent cafeExportRegister("coreinit", OSInitEvent, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSInitEventEx, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSResetEvent, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSWaitEvent, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSWaitEventWithTimeout, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSSignalEvent, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSSignalEventAll, LogType::CoreinitThreadSync); // OSRendezvous cafeExportRegister("coreinit", OSInitRendezvous, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSWaitRendezvous, LogType::CoreinitThreadSync); // OSMutex cafeExportRegister("coreinit", OSInitMutex, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSInitMutexEx, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSLockMutex, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSTryLockMutex, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSUnlockMutex, LogType::CoreinitThreadSync); // OSCond cafeExportRegister("coreinit", OSInitCond, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSInitCondEx, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSSignalCond, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSWaitCond, LogType::CoreinitThreadSync); // OSSemaphore cafeExportRegister("coreinit", OSInitSemaphore, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSInitSemaphoreEx, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSWaitSemaphore, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSTryWaitSemaphore, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSSignalSemaphore, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSGetSemaphoreCount, LogType::CoreinitThreadSync); // OSFastMutex cafeExportRegister("coreinit", OSFastMutex_Init, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSFastMutex_Lock, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSFastMutex_TryLock, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSFastMutex_Unlock, LogType::CoreinitThreadSync); // OSFastCond cafeExportRegister("coreinit", OSFastCond_Init, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSFastCond_Wait, LogType::CoreinitThreadSync); cafeExportRegister("coreinit", OSFastCond_Signal, LogType::CoreinitThreadSync); } }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" namespace coreinit { coreinit::MEMHeapHandle _sysHeapHandle = MPTR_NULL; sint32 _sysHeapAllocCounter = 0; sint32 _sysHeapFreeCounter = 0; void* OSAllocFromSystem(uint32 size, uint32 alignment) { _sysHeapAllocCounter++; return coreinit::MEMAllocFromExpHeapEx(_sysHeapHandle, size, alignment); } void OSFreeToSystem(void* ptr) { _sysHeapFreeCounter++; coreinit::MEMFreeToExpHeap(_sysHeapHandle, ptr); } void InitSysHeap() { uint32 sysHeapSize = 8 * 1024 * 1024; // actual size is unknown MEMPTR<void> heapBaseAddress = memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sysHeapSize, 0x1000)); _sysHeapHandle = coreinit::MEMCreateExpHeapEx(heapBaseAddress.GetPtr(), sysHeapSize, MEM_HEAP_OPTION_THREADSAFE); _sysHeapAllocCounter = 0; _sysHeapFreeCounter = 0; } void InitializeSysHeap() { cafeExportRegister("h264", OSAllocFromSystem, LogType::CoreinitMem); cafeExportRegister("h264", OSFreeToSystem, LogType::CoreinitMem); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h ================================================ #pragma once namespace coreinit { void InitSysHeap(); void* OSAllocFromSystem(uint32 size, uint32 alignment); void OSFreeToSystem(void* ptr); void InitializeSysHeap(); } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_SystemInfo.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_SystemInfo.h" namespace coreinit { SysAllocator<OSSystemInfo> g_system_info; const OSSystemInfo& OSGetSystemInfo() { return *g_system_info.GetPtr(); } void InitializeSystemInfo() { cemu_assert(ppcCyclesSince2000 != 0); g_system_info->busClock = ESPRESSO_BUS_CLOCK; g_system_info->coreClock = ESPRESSO_CORE_CLOCK; g_system_info->ticksSince2000 = ESPRESSO_CORE_CLOCK_TO_TIMER_CLOCK(ppcCyclesSince2000); g_system_info->l2cacheSize[0] = 512*1024; // 512KB // 512KB g_system_info->l2cacheSize[1] = 2*1024*1924; // 2MB g_system_info->l2cacheSize[2] = 512*1024; // 512KB g_system_info->coreClockToBusClockRatio = 5; cafeExportRegister("coreinit", OSGetSystemInfo, LogType::Placeholder); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_SystemInfo.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit.h" namespace coreinit { struct OSSystemInfo { uint32be busClock; uint32be coreClock; uint64be ticksSince2000; uint32be l2cacheSize[PPC_CORE_COUNT]; uint32be coreClockToBusClockRatio; }; static_assert(sizeof(OSSystemInfo) == 0x20); const OSSystemInfo& OSGetSystemInfo(); void InitializeSystemInfo(); }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/HW/Espresso/Debugger/GDBStub.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "util/helpers/Semaphore.h" #include "util/helpers/ConcurrentQueue.h" #include "util/Fiber/Fiber.h" #include "util/helpers/helpers.h" #ifdef __arm64__ #if defined(__clang__) #include <arm_acle.h> #elif defined(_MSC_VER) #include <intrin.h> #endif #endif namespace { void enableFlushDenormalsToZero() { #if defined(ARCH_X86_64) _mm_setcsr(_mm_getcsr() | 0x8000); #elif defined(__arm64__) #if defined(__clang__) __arm_wsr64("fpcr", __arm_rsr64("fpcr") | (1 << 24)); #elif defined(__GNUC__) __builtin_aarch64_set_fpcr(__builtin_aarch64_get_fpcr() | (1 << 24)); #elif defined(_MSC_VER) _WriteStatusReg(ARM64_FPCR, _ReadStatusReg(ARM64_FPCR) | (1 << 24)); #endif #endif } } SlimRWLock srwlock_activeThreadList; // public list of active threads MPTR activeThread[256]; sint32 activeThreadCount = 0; void nnNfp_update(); namespace coreinit { #ifdef __arm64__ void __OSFiberThreadEntry(uint32, uint32); #else void __OSFiberThreadEntry(void* thread); #endif void __OSAddReadyThreadToRunQueue(OSThread_t* thread); void __OSRemoveThreadFromRunQueues(OSThread_t* thread); }; namespace coreinit { // scheduler state std::atomic<bool> sSchedulerActive; std::vector<std::thread> sSchedulerThreads; std::mutex sSchedulerStateMtx; SysAllocator<OSThreadQueue> g_activeThreadQueue; // list of all threads (can include non-detached inactive threads) SysAllocator<OSThreadQueue, 3> g_coreRunQueue; CounterSemaphore g_coreRunQueueThreadCount[3]; bool g_isMulticoreMode; thread_local uint32 t_assignedCoreIndex; thread_local Fiber* t_schedulerFiber; struct OSHostThread { OSHostThread(OSThread_t* thread) : m_thread(thread), m_fiber((void(*)(void*))__OSFiberThreadEntry, this, this) { } ~OSHostThread() = default; OSThread_t* m_thread; Fiber m_fiber; // padding (used as stack memory in recompiler) uint8 padding[1024 * 128]; PPCInterpreter_t ppcInstance; uint32 selectedCore; }; std::unordered_map<OSThread_t*, OSHostThread*> s_threadToFiber; bool __CemuIsMulticoreMode() { return g_isMulticoreMode; } // create host thread (fiber) that will be used to run the PPC instance // note that host threads are fibers and not actual threads void __OSCreateHostThread(OSThread_t* thread) { cemu_assert_debug(__OSHasSchedulerLock()); cemu_assert_debug(s_threadToFiber.find(thread) == s_threadToFiber.end()); OSHostThread* hostThread = new OSHostThread(thread); s_threadToFiber.emplace(thread, hostThread); } // delete host thread void __OSDeleteHostThread(OSThread_t* thread) { static OSHostThread* _deleteQueue = nullptr; cemu_assert_debug(__OSHasSchedulerLock()); if (_deleteQueue) { delete _deleteQueue; _deleteQueue = nullptr; } // delete with a delay (using queue of length 1) // since the fiber might still be in use right now we have to delay the deletion auto hostThread = s_threadToFiber[thread]; s_threadToFiber.erase(thread); _deleteQueue = hostThread; } // add thread to active queue void __OSActivateThread(OSThread_t* thread) { cemu_assert_debug(__OSHasSchedulerLock()); g_activeThreadQueue->addThread(thread, &thread->activeThreadChain); // todo - check if thread already in queue MPTR threadMPTR = memory_getVirtualOffsetFromPointer(thread); srwlock_activeThreadList.LockWrite(); bool alreadyActive = false; for (sint32 i = 0; i < activeThreadCount; i++) { if (activeThread[i] == threadMPTR) { cemu_assert_debug(false); // should not happen alreadyActive = true; } } if (alreadyActive == false) { activeThread[activeThreadCount] = threadMPTR; activeThreadCount++; } __OSCreateHostThread(thread); srwlock_activeThreadList.UnlockWrite(); } // remove thread from active queue. Reset id and state void __OSDeactivateThread(OSThread_t* thread) { cemu_assert_debug(__OSHasSchedulerLock()); // remove thread from active thread list MPTR t = memory_getVirtualOffsetFromPointer(thread); srwlock_activeThreadList.LockWrite(); bool noHit = true; for (sint32 i = 0; i < activeThreadCount; i++) { if (activeThread[i] == t) { activeThread[i] = activeThread[activeThreadCount - 1]; activeThreadCount--; noHit = false; break; } } cemu_assert_debug(noHit == false); srwlock_activeThreadList.UnlockWrite(); g_activeThreadQueue->removeThread(thread, &thread->activeThreadChain); // todo - check if thread in queue cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_NONE); thread->id = 0x8000; __OSDeleteHostThread(thread); } bool __OSIsThreadActive(OSThread_t* thread) { cemu_assert_debug(__OSHasSchedulerLock()); MPTR threadMPTR = memory_getVirtualOffsetFromPointer(thread); srwlock_activeThreadList.LockWrite(); bool isRunable = false; for (sint32 i = 0; i < activeThreadCount; i++) { if (activeThread[i] == threadMPTR) { srwlock_activeThreadList.UnlockWrite(); return true; } } srwlock_activeThreadList.UnlockWrite(); return false; } // thread OSThread_t* __currentCoreThread[3] = {}; void OSSetCurrentThread(uint32 coreIndex, OSThread_t* thread) { if (coreIndex < 3) __currentCoreThread[coreIndex] = thread; } OSThread_t* OSGetCurrentThread() { PPCInterpreter_t* currentInstance = PPCInterpreter_getCurrentInstance(); if (currentInstance == nullptr) return nullptr; return __currentCoreThread[currentInstance->spr.UPIR]; } void threadEntry(PPCInterpreter_t* hCPU) { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); uint32 r3 = hCPU->gpr[3]; uint32 r4 = hCPU->gpr[4]; uint32 lr = hCPU->spr.LR; // cpp exception init callback //const uint32 im = OSDisableInterrupts(); -> on an actual Wii U interrupts are disabled for this callback, but there are games that yield the thread in the callback (see Angry Birds Star Wars) if (gCoreinitData->__cpp_exception_init_ptr != MPTR_NULL) { PPCCoreCallback(_swapEndianU32(gCoreinitData->__cpp_exception_init_ptr), ¤tThread->crt.eh_globals); } //OSRestoreInterrupts(im); // forward to thread entrypoint hCPU->spr.LR = lr; hCPU->gpr[3] = r3; hCPU->gpr[4] = r4; hCPU->instructionPointer = currentThread->entrypoint.GetMPTR(); } void coreinitExport_OSExitThreadDepr(PPCInterpreter_t* hCPU); void __OSInitContext(OSContext_t* ctx, MEMPTR<void> initialIP, MEMPTR<void> initialStackPointer) { ctx->SetContextMagic(); ctx->gpr[0] = 0; // r0 is left uninitialized on console? for(auto& it : ctx->gpr) it = 0; ctx->gpr[1] = _swapEndianU32(initialStackPointer.GetMPTR()); ctx->gpr[2] = _swapEndianU32(RPLLoader_GetSDA2Base()); ctx->gpr[13] = _swapEndianU32(RPLLoader_GetSDA1Base()); ctx->srr0 = initialIP.GetMPTR(); ctx->cr = 0; ctx->ukn0A8 = 0; ctx->ukn0AC = 0; ctx->gqr[0] = 0; ctx->gqr[1] = 0; ctx->gqr[2] = 0; ctx->gqr[3] = 0; ctx->gqr[4] = 0; ctx->gqr[5] = 0; ctx->gqr[6] = 0; ctx->gqr[7] = 0; ctx->dsi_dar = 0; ctx->srr1 = 0x9032; ctx->xer = 0; ctx->dsi_dsisr = 0; ctx->upir = 0; ctx->boostCount = 0; ctx->state = 0; for(auto& it : ctx->coretime) it = 0; ctx->starttime = 0; ctx->ghs_errno = 0; ctx->upmc1 = 0; ctx->upmc2 = 0; ctx->upmc3 = 0; ctx->upmc4 = 0; ctx->ummcr0 = 0; ctx->ummcr1 = 0; } void __OSThreadInit(OSThread_t* thread, MEMPTR<void> entrypoint, uint32 argInt, MEMPTR<void> argPtr, MEMPTR<void> stackTop, uint32 stackSize, sint32 priority, uint32 upirCoreIndex, OSThread_t::THREAD_TYPE threadType) { thread->effectivePriority = priority; thread->type = threadType; thread->basePriority = priority; thread->SetThreadMagic(); thread->id = 0x8000; thread->waitAlarm = nullptr; thread->entrypoint = entrypoint; thread->quantumTicks = 0; if(entrypoint) { thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->suspendCounter = 1; } else { thread->state = OSThread_t::THREAD_STATE::STATE_NONE; thread->suspendCounter = 0; } thread->exitValue = (uint32)-1; thread->requestFlags = OSThread_t::REQUEST_FLAG_BIT::REQUEST_FLAG_NONE; thread->pendingSuspend = 0; thread->suspendResult = 0xFFFFFFFF; thread->coretimeSumQuantumStart = 0; thread->deallocatorFunc = nullptr; thread->cleanupCallback = nullptr; thread->waitingForFastMutex = nullptr; thread->stateFlags = 0; thread->waitingForMutex = nullptr; memset(&thread->crt, 0, sizeof(thread->crt)); static_assert(sizeof(thread->crt) == 0x1D8); thread->tlsBlocksMPTR = 0; thread->numAllocatedTLSBlocks = 0; thread->tlsStatus = 0; OSInitThreadQueueEx(&thread->joinQueue, thread); OSInitThreadQueueEx(&thread->suspendQueue, thread); thread->mutexQueue.ukn08 = thread; thread->mutexQueue.ukn0C = 0; thread->mutexQueue.tail = nullptr; thread->mutexQueue.head = nullptr; thread->ownedFastMutex.next = nullptr; thread->ownedFastMutex.prev = nullptr; thread->contendedFastMutex.next = nullptr; thread->contendedFastMutex.prev = nullptr; MEMPTR<void> alignedStackTop{MEMPTR<void>(stackTop).GetMPTR() & 0xFFFFFFF8}; MEMPTR<uint32be> alignedStackTop32{alignedStackTop}; alignedStackTop32[-1] = 0; alignedStackTop32[-2] = 0; __OSInitContext(&thread->context, MEMPTR<void>(PPCInterpreter_makeCallableExportDepr(threadEntry)), (void*)(alignedStackTop32.GetPtr() - 2)); thread->stackBase = stackTop; // without alignment thread->stackEnd = ((uint8*)stackTop.GetPtr() - stackSize); thread->context.upir = upirCoreIndex; thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr)); thread->context.gpr[3] = _swapEndianU32(argInt); thread->context.gpr[4] = _swapEndianU32(argPtr.GetMPTR()); *(uint32be*)((uint8*)stackTop.GetPtr() - stackSize) = 0xDEADBABE; thread->alarmRelatedUkn = 0; for(auto& it : thread->specificArray) it = nullptr; thread->context.fpscr.fpscr = 4; for(sint32 i=0; i<32; i++) { thread->context.fp_ps0[i] = 0.0; thread->context.fp_ps1[i] = 0.0; } thread->context.gqr[2] = 0x40004; thread->context.gqr[3] = 0x50005; thread->context.gqr[4] = 0x60006; thread->context.gqr[5] = 0x70007; for(sint32 i=0; i<Espresso::CORE_COUNT; i++) thread->context.coretime[i] = 0; // currentRunQueue and waitQueueLink is not initialized by COS and instead overwritten without validation // since we already have integrity checks in other functions, lets initialize it here for(sint32 i=0; i<Espresso::CORE_COUNT; i++) thread->currentRunQueue[i] = nullptr; thread->waitQueueLink.prev = nullptr; thread->waitQueueLink.next = nullptr; thread->wakeTimeRelatedUkn2 = 0; thread->wakeUpCount = 0; thread->wakeUpTime = 0; thread->wakeTimeRelatedUkn1 = 0x7FFFFFFFFFFFFFFF; thread->quantumTicks = 0; thread->coretimeSumQuantumStart = 0; thread->totalCycles = 0; for(auto& it : thread->padding68C) it = 0; } void SetThreadAffinityToCore(OSThread_t* thread, uint32 coreIndex) { cemu_assert_debug(coreIndex < 3); thread->attr &= ~(OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE0 | OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE1 | OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE2 | OSThread_t::ATTR_BIT::ATTR_UKN_010); thread->context.affinity &= 0xFFFFFFF8; if (coreIndex == 0) { thread->attr |= OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE0; thread->context.affinity |= (1<<0); } else if (coreIndex == 1) { thread->attr |= OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE1; thread->context.affinity |= (1<<1); } else // if (coreIndex == 2) { thread->attr |= OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE2; thread->context.affinity |= (1<<2); } } void __OSCreateThreadOnActiveThreadWorkaround(OSThread_t* thread) { __OSLockScheduler(); bool isThreadStillActive = __OSIsThreadActive(thread); if (isThreadStillActive) { // workaround for games that restart threads before they correctly entered stopped/moribund state // seen in Fast Racing Neo at boot (0x020617BC OSCreateThread) cemuLog_log(LogType::Force, "Game attempting to re-initialize existing thread"); while ((thread->state == OSThread_t::THREAD_STATE::STATE_READY || thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) && thread->suspendCounter == 0) { // wait for thread to finish __OSUnlockScheduler(); OSSleepTicks(ESPRESSO_TIMER_CLOCK / 2000); // sleep 0.5ms __OSLockScheduler(); } if (__OSIsThreadActive(thread) && thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND) { cemuLog_log(LogType::Force, "Calling OSCreateThread() on thread which is still active (Thread exited without detached flag). Forcing OSDetachThread()..."); __OSUnlockScheduler(); OSDetachThread(thread); __OSLockScheduler(); } } cemu_assert_debug(__OSIsThreadActive(thread) == false); __OSUnlockScheduler(); } bool __OSCreateThreadInternal2(OSThread_t* thread, MEMPTR<void> entrypoint, uint32 argInt, MEMPTR<void> argPtr, MEMPTR<void> stackBase, uint32 stackSize, sint32 priority, uint32 attrBits, OSThread_t::THREAD_TYPE threadType) { __OSCreateThreadOnActiveThreadWorkaround(thread); OSThread_t* currentThread = OSGetCurrentThread(); if (priority < 0 || priority >= 32) { cemuLog_log(LogType::APIErrors, "OSCreateThreadInternal: Thread priority must be in range 0-31"); return false; } if (threadType == OSThread_t::THREAD_TYPE::TYPE_IO) { priority = priority + 0x20; } else if (threadType == OSThread_t::THREAD_TYPE::TYPE_APP) { priority = priority + 0x40; } if(attrBits >= 0x20 || stackBase == nullptr || stackSize == 0) { cemuLog_logDebug(LogType::APIErrors, "OSCreateThreadInternal: Invalid attributes, stack base or size"); return false; } uint32 im = OSDisableInterrupts(); __OSLockScheduler(thread); uint32 coreIndex = PPCInterpreter_getCurrentInstance() ? OSGetCoreId() : 1; __OSThreadInit(thread, entrypoint, argInt, argPtr, stackBase, stackSize, priority, coreIndex, threadType); thread->threadName = nullptr; thread->context.affinity = attrBits & 7; thread->attr = attrBits; if ((attrBits & 7) == 0) // if no explicit affinity is given, use the current core SetThreadAffinityToCore(thread, OSGetCoreId()); if(currentThread) { for(sint32 i=0; i<Espresso::CORE_COUNT; i++) { thread->dsiCallback[i] = currentThread->dsiCallback[i]; thread->isiCallback[i] = currentThread->isiCallback[i]; thread->programCallback[i] = currentThread->programCallback[i]; thread->perfMonCallback[i] = currentThread->perfMonCallback[i]; thread->alignmentExceptionCallback[i] = currentThread->alignmentExceptionCallback[i]; } thread->context.srr1 = thread->context.srr1 | (currentThread->context.srr1 & 0x900); thread->context.fpscr.fpscr = thread->context.fpscr.fpscr | (currentThread->context.fpscr.fpscr & 0xF8); } else { for(sint32 i=0; i<Espresso::CORE_COUNT; i++) { thread->dsiCallback[i] = 0; thread->isiCallback[i] = 0; thread->programCallback[i] = 0; thread->perfMonCallback[i] = 0; thread->alignmentExceptionCallback[i] = nullptr; } } if (entrypoint) { thread->id = 0x8000; __OSActivateThread(thread); // also handles adding the thread to g_activeThreadQueue } __OSUnlockScheduler(thread); OSRestoreInterrupts(im); // recompile entry point function if (entrypoint) PPCRecompiler_recompileIfUnvisited(entrypoint.GetMPTR()); return true; } bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) { if(threadType != OSThread_t::THREAD_TYPE::TYPE_APP && threadType != OSThread_t::THREAD_TYPE::TYPE_IO) { cemuLog_logDebug(LogType::APIErrors, "OSCreateThreadType: Invalid thread type"); cemu_assert_suspicious(); return false; } return __OSCreateThreadInternal2(thread, MEMPTR<void>(entryPoint), numParam, ptrParam, stackTop, stackSize, priority, attr, threadType); } bool OSCreateThread(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr) { return __OSCreateThreadInternal2(thread, MEMPTR<void>(entryPoint), numParam, ptrParam, stackTop, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP); } // similar to OSCreateThreadType, but can be used to create any type of thread bool __OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) { return __OSCreateThreadInternal2(thread, MEMPTR<void>(entryPoint), numParam, ptrParam, stackTop, stackSize, priority, attr, threadType); } bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam) { __OSLockScheduler(); cemu_assert_debug(PPCInterpreter_getCurrentInstance() == nullptr || OSGetCurrentThread() != thread); // called on self, what should this function do? if (thread->state != OSThread_t::THREAD_STATE::STATE_NONE && thread->state != OSThread_t::THREAD_STATE::STATE_MORIBUND) { // unsure about this case cemuLog_logDebug(LogType::Force, "OSRunThread called on thread which cannot be ran"); __OSUnlockScheduler(); return false; } if (thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND) { thread->state = OSThread_t::THREAD_STATE::STATE_NONE; coreinit::__OSDeactivateThread(thread); coreinit::__OSRemoveThreadFromRunQueues(thread); } // set thread state // todo - this should fully reinitialize the thread? thread->entrypoint = funcAddress; thread->context.srr0 = PPCInterpreter_makeCallableExportDepr(threadEntry); thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr)); thread->context.gpr[3] = _swapEndianU32(numParam); thread->context.gpr[4] = _swapEndianU32(memory_getVirtualOffsetFromPointer(ptrParam)); thread->suspendCounter = 0; // verify MPTR threadMPTR = memory_getVirtualOffsetFromPointer(thread); coreinit::__OSActivateThread(thread); thread->state = OSThread_t::THREAD_STATE::STATE_READY; __OSAddReadyThreadToRunQueue(thread); __OSUnlockScheduler(); return true; } void OSExitThread(sint32 exitValue) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->gpr[3] = exitValue; OSThread_t* currentThread = coreinit::OSGetCurrentThread(); // thread cleanup callback if (currentThread->cleanupCallback) { currentThread->stateFlags = _swapEndianU32(_swapEndianU32(currentThread->stateFlags) | 0x00000001); PPCCoreCallback(currentThread->cleanupCallback.GetMPTR(), currentThread, currentThread->stackEnd); } // cpp exception cleanup if (gCoreinitData->__cpp_exception_cleanup_ptr != 0 && currentThread->crt.eh_globals != nullptr) { PPCCoreCallback(_swapEndianU32(gCoreinitData->__cpp_exception_cleanup_ptr), ¤tThread->crt.eh_globals); currentThread->crt.eh_globals = nullptr; } // set exit code currentThread->exitValue = exitValue; __OSLockScheduler(); // release held synchronization primitives if (!currentThread->mutexQueue.isEmpty()) { cemuLog_log(LogType::Force, "OSExitThread: Thread is holding mutexes"); while (true) { OSMutex* mutex = currentThread->mutexQueue.getFirst(); if (!mutex) break; if (mutex->owner != currentThread) { cemuLog_log(LogType::Force, "OSExitThread: Thread is holding mutex which it doesn't own"); currentThread->mutexQueue.removeMutex(mutex); continue; } coreinit::OSUnlockMutexInternal(mutex); } } // todo - release all fast mutexes // handle join queue if (!currentThread->joinQueue.isEmpty()) currentThread->joinQueue.wakeupEntireWaitQueue(false); if ((currentThread->attr & 8) != 0) { // deactivate thread since it is detached currentThread->state = OSThread_t::THREAD_STATE::STATE_NONE; coreinit::__OSDeactivateThread(currentThread); // queue call to thread deallocator if set if (!currentThread->deallocatorFunc.IsNull()) __OSQueueThreadDeallocation(currentThread); } else { // non-detached threads remain active currentThread->state = OSThread_t::THREAD_STATE::STATE_MORIBUND; } PPCCore_switchToSchedulerWithLock(); } void OSSetThreadSpecific(uint32 index, void* value) { OSThread_t* currentThread = OSGetCurrentThread(); if (index >= (uint32)currentThread->specificArray.size()) return; currentThread->specificArray[index] = value; } void* OSGetThreadSpecific(uint32 index) { OSThread_t* currentThread = OSGetCurrentThread(); if (index >= (uint32)currentThread->specificArray.size()) return nullptr; return currentThread->specificArray[index].GetPtr(); } void OSSetThreadName(OSThread_t* thread, const char* name) { thread->threadName = name; } const char* OSGetThreadName(OSThread_t* thread) { return thread->threadName.GetPtr(); } void coreinitExport_OSExitThreadDepr(PPCInterpreter_t* hCPU) { ppcDefineParamU32(exitCode, 0); OSExitThread(exitCode); } void OSYieldThread() { PPCCore_switchToScheduler(); } void _OSSleepTicks_alarmHandler(uint64 currentTick, void* context) { cemu_assert_debug(__OSHasSchedulerLock()); OSThreadQueue* threadQueue = (OSThreadQueue*)context; threadQueue->wakeupEntireWaitQueue(false); } void OSSleepTicks(uint64 ticks) { cemu_assert_debug(__OSHasSchedulerLock() == false); StackAllocator<OSThreadQueue> _threadQueue; OSInitThreadQueue(_threadQueue.GetPointer()); __OSLockScheduler(); OSHostAlarm* hostAlarm = OSHostAlarmCreate(OSGetTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer()); _threadQueue.GetPointer()->queueAndWait(OSGetCurrentThread()); OSHostAlarmDestroy(hostAlarm); __OSUnlockScheduler(); } void OSDetachThread(OSThread_t* thread) { __OSLockScheduler(); thread->attr |= OSThread_t::ATTR_BIT::ATTR_DETACHED; if (thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND) { // exit thread // ? // todo -> call deallocator thread->state = OSThread_t::THREAD_STATE::STATE_NONE; thread->id = 0x8000; coreinit::__OSDeactivateThread(thread); if (!thread->joinQueue.isEmpty()) { // handle join queue thread->joinQueue.wakeupEntireWaitQueue(true); } } __OSUnlockScheduler(); } bool OSJoinThread(OSThread_t* thread, uint32be* exitValue) { __OSLockScheduler(); if ((thread->attr & OSThread_t::ATTR_DETACHED) == 0 && thread->state != OSThread_t::THREAD_STATE::STATE_MORIBUND) { cemu_assert_debug(thread->joinQueue.isEmpty()); // thread still running, wait in join queue thread->joinQueue.queueAndWait(OSGetCurrentThread()); } else if (thread->state != OSThread_t::THREAD_STATE::STATE_MORIBUND) { // cannot join detached and active threads cemuLog_logDebug(LogType::Force, "Cannot join detached active thread"); __OSUnlockScheduler(); return false; } // thread already ended and is still attached, get return value cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND); cemu_assert_debug((thread->attr & OSThread_t::ATTR_DETACHED) == 0); if (exitValue) *exitValue = thread->exitValue; // end thread thread->state = OSThread_t::THREAD_STATE::STATE_NONE; __OSDeactivateThread(thread); coreinit::__OSRemoveThreadFromRunQueues(thread); thread->id = 0x8000; if (!thread->deallocatorFunc.IsNull()) { __OSQueueThreadDeallocation(thread); PPCCore_switchToSchedulerWithLock(); // make sure the deallocation function runs before we return } __OSUnlockScheduler(); return true; } // adds the thread to each core's run queue if in runable state void __OSAddReadyThreadToRunQueue(OSThread_t* thread) { cemu_assert_debug(MMU_IsInPPCMemorySpace(thread)); cemu_assert_debug(thread->IsValidMagic()); cemu_assert_debug(__OSHasSchedulerLock()); if (thread->state != OSThread_t::THREAD_STATE::STATE_READY) return; if (thread->suspendCounter != 0) return; for (sint32 i = 0; i < PPC_CORE_COUNT; i++) { if (thread->currentRunQueue[i] != nullptr) continue; // already on the queue // check affinity if(!thread->context.hasCoreAffinitySet(i)) continue; g_coreRunQueue.GetPtr()[i].addThread(thread, thread->linkRun + i); thread->currentRunQueue[i] = (g_coreRunQueue.GetPtr() + i); g_coreRunQueueThreadCount[i].increment(); } } void __OSRemoveThreadFromRunQueues(OSThread_t* thread) { cemu_assert_debug(__OSHasSchedulerLock()); for (sint32 i = 0; i < PPC_CORE_COUNT; i++) { if(thread->currentRunQueue[i] == nullptr) continue; g_coreRunQueue.GetPtr()[i].removeThread(thread, thread->linkRun + i); thread->currentRunQueue[i] = nullptr; g_coreRunQueueThreadCount[i].decrement(); } } // returns true if thread runs on same core and has higher priority bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread, bool sharedPriorityAndAffinityWorkaround) { uint32 coreIndex = OSGetCoreId(); if (!newThread->context.hasCoreAffinitySet(coreIndex)) return false; // special case: if current and new thread are running only on the same core then reschedule even if priority is equal // this resolves a deadlock in Just Dance 2019 where one thread would always reacquire the same mutex within it's timeslice, blocking another thread on the same core from acquiring it if (sharedPriorityAndAffinityWorkaround && (1<<coreIndex) == newThread->context.affinity && currentThread->context.affinity == newThread->context.affinity && currentThread->effectivePriority == newThread->effectivePriority) return true; // otherwise reschedule if new thread has higher priority return newThread->effectivePriority < currentThread->effectivePriority; } sint32 __OSResumeThreadInternal(OSThread_t* thread, sint32 resumeCount) { cemu_assert_debug(__OSHasSchedulerLock()); sint32 previousSuspendCount = thread->suspendCounter; cemu_assert_debug(previousSuspendCount >= 0); if (previousSuspendCount == 0) { cemuLog_log(LogType::APIErrors, "OSResumeThread: Resuming thread 0x{:08x} which isn't suspended", MEMPTR<OSThread_t>(thread).GetMPTR()); return 0; } thread->suspendCounter = previousSuspendCount - resumeCount; if (thread->suspendCounter < 0) thread->suspendCounter = 0; if (thread->suspendCounter == 0) { __OSAddReadyThreadToRunQueue(thread); // set awakened time if thread is ready // todo - only set this once? thread->wakeUpTime = PPCInterpreter_getMainCoreCycleCounter(); // reschedule if thread has higher priority if (PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, false)) PPCCore_switchToSchedulerWithLock(); } return previousSuspendCount; } sint32 OSResumeThread(OSThread_t* thread) { __OSLockScheduler(); sint32 previousSuspendCount = __OSResumeThreadInternal(thread, 1); __OSUnlockScheduler(); return previousSuspendCount; } void OSContinueThread(OSThread_t* thread) { __OSLockScheduler(); __OSResumeThreadInternal(thread, thread->suspendCounter); __OSUnlockScheduler(); } void __OSSuspendThreadInternal(OSThread_t* thread) { cemu_assert_debug(__OSHasSchedulerLock()); cemu_assert_debug(thread->state != OSThread_t::THREAD_STATE::STATE_NONE && thread->state != OSThread_t::THREAD_STATE::STATE_MORIBUND); // how to handle these? sint32 previousSuspendCount = thread->suspendCounter; if (OSGetCurrentThread() == thread) { thread->suspendCounter = thread->suspendCounter + 1; PPCCore_switchToSchedulerWithLock(); } else { thread->suspendCounter = thread->suspendCounter + 1; if (previousSuspendCount == 0) { __OSRemoveThreadFromRunQueues(thread); // todo - if thread is still running find a way to cancel it's timeslice immediately } } } void OSSuspendThread(OSThread_t* thread) { __OSLockScheduler(); __OSSuspendThreadInternal(thread); __OSUnlockScheduler(); } void __OSSuspendThreadNolock(OSThread_t* thread) { __OSSuspendThreadInternal(thread); } void OSSleepThread(OSThreadQueue* threadQueue) { __OSLockScheduler(); threadQueue->queueAndWait(OSGetCurrentThread()); __OSUnlockScheduler(); } void OSWakeupThread(OSThreadQueue* threadQueue) { __OSLockScheduler(); threadQueue->wakeupEntireWaitQueue(true); __OSUnlockScheduler(); } bool OSSetThreadAffinity(OSThread_t* thread, uint32 affinityMask) { cemu_assert_debug((affinityMask & ~7) == 0); __OSLockScheduler(); uint32 prevAffinityMask = thread->context.getAffinity(); if (thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) { thread->attr = (thread->attr & ~7) | (affinityMask & 7); thread->context.setAffinity(affinityMask); // should this reschedule the thread? } else if (prevAffinityMask != affinityMask) { if(thread->state != OSThread_t::THREAD_STATE::STATE_NONE) { __OSRemoveThreadFromRunQueues(thread); thread->attr = (thread->attr & ~7) | (affinityMask & 7); thread->context.setAffinity(affinityMask); __OSAddReadyThreadToRunQueue(thread); } else { thread->attr = (thread->attr & ~7) | (affinityMask & 7); thread->context.setAffinity(affinityMask); } } __OSUnlockScheduler(); return true; } uint32 OSGetThreadAffinity(OSThread_t* thread) { auto affinityMask = thread->context.getAffinity(); cemu_assert_debug((affinityMask & ~7) == 0); return affinityMask; } void* OSSetThreadDeallocator(OSThread_t* thread, void* newDeallocatorFunc) { __OSLockScheduler(); void* previousFunc = thread->deallocatorFunc.GetPtr(); thread->deallocatorFunc = newDeallocatorFunc; __OSUnlockScheduler(); return previousFunc; } void* OSSetThreadCleanupCallback(OSThread_t* thread, void* cleanupCallback) { __OSLockScheduler(); void* previousFunc = thread->cleanupCallback.GetPtr(); thread->cleanupCallback = cleanupCallback; __OSUnlockScheduler(); return previousFunc; } void __OSSetThreadBasePriority(OSThread_t* thread, sint32 newPriority) { cemu_assert_debug(newPriority >= 0 && newPriority < 32); newPriority += OSThread_t::GetTypePriorityBase(thread->type); thread->basePriority = newPriority; } void __OSUpdateThreadEffectivePriority(OSThread_t* thread) { if (thread->context.boostCount != 0) { // temporarily boosted threads have their priority set to 0 (maximum) thread->effectivePriority = 0; return; } thread->effectivePriority = thread->basePriority; } bool OSSetThreadPriority(OSThread_t* thread, sint32 newPriority) { if (newPriority < 0 || newPriority >= 0x20) { cemu_assert_debug(false); return false; // invalid priority value } __OSLockScheduler(); __OSSetThreadBasePriority(thread, newPriority); __OSUpdateThreadEffectivePriority(thread); // reschedule if needed OSThread_t* currentThread = OSGetCurrentThread(); if (currentThread && currentThread != thread) { if (__OSCoreShouldSwitchToThread(currentThread, thread, false)) PPCCore_switchToSchedulerWithLock(); } __OSUnlockScheduler(); return true; } sint32 OSGetThreadPriority(OSThread_t* thread) { sint32 threadPriority = thread->basePriority; OSThread_t::THREAD_TYPE threadType = thread->type; threadPriority -= OSThread_t::GetTypePriorityBase(threadType); cemu_assert_debug(threadPriority >= 0 && threadPriority < 32); return threadPriority; } bool OSIsThreadTerminated(OSThread_t* thread) { __OSLockScheduler(); bool isTerminated = false; if (thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND || thread->state == OSThread_t::THREAD_STATE::STATE_NONE) isTerminated = true; __OSUnlockScheduler(); return isTerminated; } bool OSIsThreadSuspended(OSThread_t* thread) { __OSLockScheduler(); sint32 suspendCounter = thread->suspendCounter; __OSUnlockScheduler(); return suspendCounter > 0; } bool OSIsThreadRunningNoLock(OSThread_t* thread) { cemu_assert_debug(__OSHasSchedulerLock()); return thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING; } bool OSIsThreadRunning(OSThread_t* thread) { bool isRunning = false; __OSLockScheduler(); isRunning = OSIsThreadRunningNoLock(thread); __OSUnlockScheduler(); return isRunning; } void OSCancelThread(OSThread_t* thread) { __OSLockScheduler(); cemu_assert_debug(thread->requestFlags == 0 || thread->requestFlags == OSThread_t::REQUEST_FLAG_CANCEL); // todo - how to handle cases where other flags are already set? thread->requestFlags = OSThread_t::REQUEST_FLAG_CANCEL; __OSUnlockScheduler(); // if the canceled thread is self, then exit immediately if (thread == OSGetCurrentThread()) OSExitThread(-1); } void OSTestThreadCancelInternal() { // also handles suspend request ? cemu_assert_debug(__OSHasSchedulerLock()); OSThread_t* thread = OSGetCurrentThread(); if (thread->requestFlags == OSThread_t::REQUEST_FLAG_CANCEL) { __OSUnlockScheduler(); OSExitThread(-1); } } void OSTestThreadCancel() { __OSLockScheduler(); OSTestThreadCancelInternal(); __OSUnlockScheduler(); } void __OSSwitchToThreadFiber(OSThread_t* thread, uint32 coreIndex) { cemu_assert_debug(__OSHasSchedulerLock()); cemu_assert_debug(s_threadToFiber.find(thread) != s_threadToFiber.end()); OSHostThread* hostThread = s_threadToFiber.find(thread)->second; hostThread->selectedCore = coreIndex; Fiber::Switch(hostThread->m_fiber); } void __OSThreadLoadContext(PPCInterpreter_t* hCPU, OSThread_t* thread) { // load GPR for (uint32 i = 0; i < 32; i++) hCPU->gpr[i] = _swapEndianU32(thread->context.gpr[i]); // load SPR cr, lr, ctr, xer ppc_setCR(hCPU, thread->context.cr); hCPU->spr.LR = _swapEndianU32(thread->context.lr); hCPU->spr.CTR = thread->context.ctr; PPCInterpreter_setXER(hCPU, thread->context.xer); hCPU->fpscr = thread->context.fpscr.fpscr; // store floating point and Gekko registers for (uint32 i = 0; i < 32; i++) { hCPU->fpr[i].fp0int = thread->context.fp_ps0[i]; hCPU->fpr[i].fp1int = thread->context.fp_ps1[i]; } for (uint32 i = 0; i < 8; i++) { hCPU->spr.UGQR[0 + i] = thread->context.gqr[i]; } hCPU->instructionPointer = thread->context.srr0; } void __OSThreadStoreContext(PPCInterpreter_t* hCPU, OSThread_t* thread) { // store GPR for (uint32 i = 0; i < 32; i++) thread->context.gpr[i] = _swapEndianU32(hCPU->gpr[i]); // store SPRs thread->context.cr = ppc_getCR(hCPU); thread->context.lr = _swapEndianU32(hCPU->spr.LR); thread->context.ctr = hCPU->spr.CTR; thread->context.xer = PPCInterpreter_getXER(hCPU); thread->context.fpscr.fpscr = hCPU->fpscr; thread->context.fpscr.padding = 0; // store floating point and Gekko registers for (uint32 i = 0; i < 32; i++) { thread->context.fp_ps0[i] = hCPU->fpr[i].fp0int; thread->context.fp_ps1[i] = hCPU->fpr[i].fp1int; } for (uint32 i = 0; i < 8; i++) { thread->context.gqr[i] = hCPU->spr.UGQR[0 + i]; } thread->context.srr0 = hCPU->instructionPointer; } void __OSStoreThread(OSThread_t* thread, PPCInterpreter_t* hCPU) { if (thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) { thread->state = OSThread_t::THREAD_STATE::STATE_READY; __OSAddReadyThreadToRunQueue(thread); } else if (thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND || thread->state == OSThread_t::THREAD_STATE::STATE_NONE || thread->state == OSThread_t::THREAD_STATE::STATE_WAITING) { // thread exited or suspending/waiting } else { cemu_assert_debug(false); } thread->requestFlags = (OSThread_t::REQUEST_FLAG_BIT)(thread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL); // remove all flags except cancel flag // update total cycles sint64 executedCycles = (sint64)thread->quantumTicks - (sint64)hCPU->remainingCycles; executedCycles = std::max<sint64>(executedCycles, 0); if (executedCycles < (sint64)hCPU->skippedCycles) executedCycles = 0; else executedCycles -= hCPU->skippedCycles; thread->totalCycles += (uint64)executedCycles; // store context and set current thread to null __OSThreadStoreContext(hCPU, thread); OSSetCurrentThread(OSGetCoreId(), nullptr); PPCInterpreter_setCurrentInstance(nullptr); } void __OSLoadThread(OSThread_t* thread, PPCInterpreter_t* hCPU, uint32 coreIndex) { hCPU->LSQE = 1; hCPU->PSE = 1; hCPU->reservedMemAddr = MPTR_NULL; hCPU->reservedMemValue = 0; hCPU->spr.UPIR = coreIndex; hCPU->coreInterruptMask = 1; PPCInterpreter_setCurrentInstance(hCPU); OSSetCurrentThread(OSGetCoreId(), thread); __OSThreadLoadContext(hCPU, thread); thread->context.upir = coreIndex; thread->quantumTicks = ppcThreadQuantum; // statistics thread->wakeUpTime = PPCInterpreter_getMainCoreCycleCounter(); thread->wakeUpCount = thread->wakeUpCount + 1; } uint32 s_lehmer_lcg[PPC_CORE_COUNT] = { 0 }; void __OSThreadStartTimeslice(OSThread_t* thread, PPCInterpreter_t* hCPU) { uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); // run one timeslice hCPU->remainingCycles = ppcThreadQuantum; hCPU->skippedCycles = 0; // we add a slight randomized variance to the thread quantum to avoid getting stuck in repeated code sequences where one or multiple threads always unload inside a lock // this was seen in Mario Party 10 during early boot where several OSLockMutex operations would align in such a way that one thread would never successfully acquire the lock if (s_lehmer_lcg[coreIndex] == 0) s_lehmer_lcg[coreIndex] = 12345; hCPU->remainingCycles += (s_lehmer_lcg[coreIndex] & 0x7F); s_lehmer_lcg[coreIndex] = (uint32)((uint64)s_lehmer_lcg[coreIndex] * 279470273ull % 0xfffffffbull); } OSThread_t* __OSGetNextRunableThread(uint32 coreIndex) { cemu_assert_debug(__OSHasSchedulerLock()); // pick thread, then remove from run queue OSThreadQueue* runQueue = g_coreRunQueue.GetPtr() + coreIndex; OSThread_t* threadItr = runQueue->head.GetPtr(); OSThread_t* selectedThread = nullptr; if (!threadItr) return nullptr; selectedThread = threadItr; while (threadItr) { if (threadItr->effectivePriority < selectedThread->effectivePriority) selectedThread = threadItr; threadItr = threadItr->linkRun[coreIndex].next.GetPtr(); } cemu_assert_debug(selectedThread->state == OSThread_t::THREAD_STATE::STATE_READY); __OSRemoveThreadFromRunQueues(selectedThread); selectedThread->state = OSThread_t::THREAD_STATE::STATE_RUNNING; return selectedThread; } void __OSDeleteAllActivePPCThreads() { __OSLockScheduler(); while(activeThreadCount > 0) { MEMPTR<OSThread_t> t{activeThread[0]}; t->state = OSThread_t::THREAD_STATE::STATE_NONE; __OSDeactivateThread(t.GetPtr()); } __OSUnlockScheduler(); } void __OSCheckSystemEvents() { // AX update snd_core::AXOut_update(); // alarm update coreinit::alarm_update(); // nfp update nnNfp_update(); } Fiber* g_idleLoopFiber[3]{}; // idle fiber per core if no thread is runnable // this is necessary since we can't block in __OSThreadSwitchToNext() (__OSStoreThread + thread switch must happen inside same scheduler lock) void __OSThreadCoreIdle(void* unusedParam) { bool isMainCore = g_isMulticoreMode == false || t_assignedCoreIndex == 1; sint32 coreIndex = t_assignedCoreIndex; __OSUnlockScheduler(); while (true) { if (!g_coreRunQueueThreadCount[coreIndex].isZero()) // avoid hammering the lock on the main core if there is no runable thread { __OSLockScheduler(); OSThread_t* nextThread = __OSGetNextRunableThread(coreIndex); if (nextThread) { cemu_assert_debug(nextThread->state == OSThread_t::THREAD_STATE::STATE_RUNNING); __OSSwitchToThreadFiber(nextThread, coreIndex); } __OSUnlockScheduler(); } if (isMainCore) { __OSCheckSystemEvents(); if(g_isMulticoreMode == false) coreIndex = (coreIndex + 1) % 3; } else { // wait for semaphore (only in multicore mode) g_coreRunQueueThreadCount[t_assignedCoreIndex].waitUntilNonZero(); if (!sSchedulerActive.load(std::memory_order::relaxed)) Fiber::Switch(*t_schedulerFiber); // switch back to original thread to exit } } } void __OSThreadSwitchToNext() { cemu_assert_debug(__OSHasSchedulerLock()); OSHostThread* hostThread = (OSHostThread*)Fiber::GetFiberPrivateData(); cemu_assert_debug(hostThread); //if (ppcInterpreterCurrentInstance) // debug_printf("Core %d store thread %08x (t = %d)\n", hostThread->ppcInstance.sprNew.UPIR, memory_getVirtualOffsetFromPointer(hostThread->thread), t_assignedCoreIndex); // store context of current thread __OSStoreThread(OSGetCurrentThread(), &hostThread->ppcInstance); cemu_assert_debug(PPCInterpreter_getCurrentInstance() == nullptr); if (!sSchedulerActive.load(std::memory_order::relaxed)) { __OSUnlockScheduler(); Fiber::Switch(*t_schedulerFiber); // switch back to original thread entry for it to exit } // choose core static uint32 _coreCounter = 0; sint32 coreIndex; if (g_isMulticoreMode) coreIndex = t_assignedCoreIndex; else { coreIndex = _coreCounter; _coreCounter = (_coreCounter + 1) % 3; } // if main thread then dont forget to do update checks bool isMainThread = g_isMulticoreMode == false || t_assignedCoreIndex == 1; // find next thread to run // for main thread we force switching to the idle loop since it calls __OSCheckSystemEvents() if(isMainThread) Fiber::Switch(*g_idleLoopFiber[t_assignedCoreIndex]); else if (OSThread_t* nextThread = __OSGetNextRunableThread(coreIndex)) { cemu_assert_debug(nextThread->state == OSThread_t::THREAD_STATE::STATE_RUNNING); __OSSwitchToThreadFiber(nextThread, coreIndex); } else { Fiber::Switch(*g_idleLoopFiber[t_assignedCoreIndex]); } cemu_assert_debug(__OSHasSchedulerLock()); cemu_assert_debug(g_isMulticoreMode == false || hostThread->selectedCore == t_assignedCoreIndex); // received next time slice, load self again __OSLoadThread(hostThread->m_thread, &hostThread->ppcInstance, hostThread->selectedCore); __OSThreadStartTimeslice(hostThread->m_thread, &hostThread->ppcInstance); } #ifdef __arm64__ void __OSFiberThreadEntry(uint32 _high, uint32 _low) { uint64 _thread = (uint64) _high << 32 | _low; #else void __OSFiberThreadEntry(void* _thread) { #endif OSHostThread* hostThread = (OSHostThread*)_thread; enableFlushDenormalsToZero(); PPCInterpreter_t* hCPU = &hostThread->ppcInstance; __OSLoadThread(hostThread->m_thread, hCPU, hostThread->selectedCore); __OSThreadStartTimeslice(hostThread->m_thread, &hostThread->ppcInstance); __OSUnlockScheduler(); // lock is always held when switching to a fiber, so we need to unlock it here while (true) { if (hCPU->remainingCycles > 0) { // try to enter recompiler immediately PPCRecompiler_attemptEnterWithoutRecompile(hCPU, hCPU->instructionPointer); // keep executing as long as there are cycles left while ((--hCPU->remainingCycles) >= 0) PPCInterpreterSlim_executeInstruction(hCPU); } // reset reservation hCPU->reservedMemAddr = 0; hCPU->reservedMemValue = 0; // reschedule __OSLockScheduler(); __OSThreadSwitchToNext(); __OSUnlockScheduler(); } } #if BOOST_OS_LINUX #include <unistd.h> #include <sys/prctl.h> std::vector<pid_t> g_schedulerThreadIds; std::mutex g_schedulerThreadIdsLock; std::vector<pid_t>& OSGetSchedulerThreadIds() { std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); return g_schedulerThreadIds; } #endif void OSSchedulerCoreEmulationThread(void* _assignedCoreIndex) { SetThreadName(fmt::format("OSSched[core={}]", (uintptr_t)_assignedCoreIndex).c_str()); t_assignedCoreIndex = (sint32)(uintptr_t)_assignedCoreIndex; enableFlushDenormalsToZero(); #if BOOST_OS_LINUX if (g_gdbstub) { // need to allow the GDBStub to attach to our thread prctl(PR_SET_DUMPABLE, (unsigned long)1); prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY); } pid_t tid = gettid(); { std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); g_schedulerThreadIds.emplace_back(tid); } #endif t_schedulerFiber = Fiber::PrepareCurrentThread(); // create scheduler idle fiber and switch to it g_idleLoopFiber[t_assignedCoreIndex] = new Fiber(__OSThreadCoreIdle, nullptr, nullptr); cemu_assert_debug(PPCInterpreter_getCurrentInstance() == nullptr); __OSLockScheduler(); Fiber::Switch(*g_idleLoopFiber[t_assignedCoreIndex]); // returned from scheduler loop, exit thread cemu_assert_debug(!__OSHasSchedulerLock()); } std::vector<std::thread::native_handle_type> g_schedulerThreadHandles; std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads() { return g_schedulerThreadHandles; } // starts PPC core emulation void OSSchedulerBegin(sint32 numCPUEmulationThreads) { std::unique_lock _lock(sSchedulerStateMtx); if (sSchedulerActive.exchange(true)) return; cemu_assert_debug(numCPUEmulationThreads == 1 || numCPUEmulationThreads == 3); g_isMulticoreMode = numCPUEmulationThreads > 1; if (numCPUEmulationThreads == 1) sSchedulerThreads.emplace_back(OSSchedulerCoreEmulationThread, (void*)0); else if (numCPUEmulationThreads == 3) { for (size_t i = 0; i < 3; i++) sSchedulerThreads.emplace_back(OSSchedulerCoreEmulationThread, (void*)i); } else cemu_assert_debug(false); for (auto& it : sSchedulerThreads) g_schedulerThreadHandles.emplace_back(it.native_handle()); } // shuts down all scheduler host threads and deletes all fibers and ppc threads void OSSchedulerEnd() { std::unique_lock _lock(sSchedulerStateMtx); sSchedulerActive.store(false); for (size_t i = 0; i < Espresso::CORE_COUNT; i++) g_coreRunQueueThreadCount[i].increment(); // make sure to wake up cores if they are paused and waiting for runnable threads // wait for threads to stop execution for (auto& threadItr : sSchedulerThreads) threadItr.join(); sSchedulerThreads.clear(); g_schedulerThreadHandles.clear(); #if BOOST_OS_LINUX { std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); g_schedulerThreadIds.clear(); } #endif // clean up all fibers for (auto& it : g_idleLoopFiber) { delete it; it = nullptr; } for (auto& it : s_threadToFiber) { OSHostThread* hostThread = it.second; delete hostThread; } s_threadToFiber.clear(); } SysAllocator<OSThread_t, PPC_CORE_COUNT> s_defaultThreads; SysAllocator<uint8, PPC_CORE_COUNT * 1024 * 1024> s_stack; OSThread_t* OSGetDefaultThread(sint32 coreIndex) { if (coreIndex < 0 || coreIndex >= PPC_CORE_COUNT) return nullptr; return s_defaultThreads.GetPtr() + coreIndex; } void* OSGetDefaultThreadStack(sint32 coreIndex, uint32& size) { if (coreIndex < 0 || coreIndex >= PPC_CORE_COUNT) return nullptr; size = 1024 * 1024; return s_stack.GetPtr() + coreIndex * size; } void OSInitThreadQueue(OSThreadQueue* threadQueue) { threadQueue->head = nullptr; threadQueue->tail = nullptr; threadQueue->userData = nullptr; threadQueue->ukn0C = 0; } void OSInitThreadQueueEx(OSThreadQueue* threadQueue, void* userData) { threadQueue->head = nullptr; threadQueue->tail = nullptr; threadQueue->userData = userData; threadQueue->ukn0C = 0; } /* Thread terminator threads (for calling thread deallocators) */ struct TerminatorThread { struct DeallocatorQueueEntry { DeallocatorQueueEntry() = default; DeallocatorQueueEntry(OSThread_t* thread, MEMPTR<void> stack, MEMPTR<void> deallocatorFunc) : thread(thread), stack(stack), deallocatorFunc(deallocatorFunc) {}; OSThread_t* thread{}; MEMPTR<void> stack; MEMPTR<void> deallocatorFunc; }; SysAllocator<OSThread_t> terminatorThread; SysAllocator<uint8, 16 * 1024> threadStack; SysAllocator<char, 64> threadName; SysAllocator<OSSemaphore> semaphoreQueuedDeallocators; ConcurrentQueue<DeallocatorQueueEntry> queueDeallocators; }; TerminatorThread s_terminatorThreads[PPC_CORE_COUNT]; void __OSTerminatorThreadFunc(PPCInterpreter_t* hCPU) { uint32 coreIndex = OSGetCoreId(); while (OSWaitSemaphore(s_terminatorThreads[coreIndex].semaphoreQueuedDeallocators.GetPtr())) { TerminatorThread::DeallocatorQueueEntry queueEntry; s_terminatorThreads[coreIndex].queueDeallocators.pop(queueEntry); PPCCoreCallback(queueEntry.deallocatorFunc, queueEntry.thread, queueEntry.stack); } osLib_returnFromFunction(hCPU, 0); } // queue thread deallocation to run after current thread finishes // the termination threads run at a higher priority on the same core void __OSQueueThreadDeallocation(OSThread_t* thread) { uint32 coreIndex = OSGetCoreId(); TerminatorThread::DeallocatorQueueEntry queueEntry(thread, thread->stackEnd, thread->deallocatorFunc); s_terminatorThreads[coreIndex].queueDeallocators.push(queueEntry); OSSignalSemaphoreInternal(s_terminatorThreads[coreIndex].semaphoreQueuedDeallocators.GetPtr(), false); // do not reschedule here! Current thread must not be interrupted otherwise deallocator will run too early } void __OSInitTerminatorThreads() { for (sint32 i = 0; i < PPC_CORE_COUNT; i++) { OSInitSemaphore(s_terminatorThreads[i].semaphoreQueuedDeallocators.GetPtr(), 0); sprintf(s_terminatorThreads[i].threadName.GetPtr(), "{SYS Thread Terminator Core %d}", i); OSCreateThreadType(s_terminatorThreads[i].terminatorThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(__OSTerminatorThreadFunc), 0, nullptr, (uint8*)s_terminatorThreads[i].threadStack.GetPtr() + s_terminatorThreads[i].threadStack.GetByteSize(), (sint32)s_terminatorThreads[i].threadStack.GetByteSize(), 0, 1 << i, OSThread_t::THREAD_TYPE::TYPE_IO); OSSetThreadName(s_terminatorThreads[i].terminatorThread.GetPtr(), s_terminatorThreads[i].threadName.GetPtr()); OSResumeThread(s_terminatorThreads[i].terminatorThread.GetPtr()); } } SysAllocator<char, 64> _defaultThreadName[PPC_CORE_COUNT]; void __OSInitDefaultThreads() { for (sint32 i = 0; i < PPC_CORE_COUNT; i++) { sprintf(_defaultThreadName[i].GetPtr(), "Default Core %d", i); OSThread_t* coreDefaultThread = coreinit::OSGetDefaultThread(i); uint32 stackSize; void* stackBase = coreinit::OSGetDefaultThreadStack(i, stackSize); coreinit::OSCreateThreadType(coreDefaultThread, MPTR_NULL, 0, nullptr, (uint8*)stackBase + stackSize, stackSize, 16, 1 << i, OSThread_t::THREAD_TYPE::TYPE_APP); coreinit::OSSetThreadName(coreDefaultThread, _defaultThreadName[i].GetPtr()); } } void MapThreadExports() { cafeExportRegister("coreinit", OSCreateThreadType, LogType::CoreinitThread); cafeExportRegister("coreinit", OSCreateThread, LogType::CoreinitThread); cafeExportRegister("coreinit", __OSCreateThreadType, LogType::CoreinitThread); cafeExportRegister("coreinit", OSExitThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetCurrentThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSSetThreadSpecific, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetThreadSpecific, LogType::CoreinitThread); cafeExportRegister("coreinit", OSSetThreadName, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetThreadName, LogType::CoreinitThread); cafeExportRegister("coreinit", OSRunThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSDetachThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSJoinThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSResumeThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSContinueThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSSuspendThread, LogType::CoreinitThread); cafeExportRegister("coreinit", __OSSuspendThreadNolock, LogType::CoreinitThread); cafeExportRegister("coreinit", OSSleepThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSWakeupThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSYieldThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSSleepTicks, LogType::CoreinitThread); cafeExportRegister("coreinit", OSSetThreadDeallocator, LogType::CoreinitThread); cafeExportRegister("coreinit", OSSetThreadCleanupCallback, LogType::CoreinitThread); cafeExportRegister("coreinit", OSSetThreadPriority, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetThreadPriority, LogType::CoreinitThread); cafeExportRegister("coreinit", OSSetThreadAffinity, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetThreadAffinity, LogType::CoreinitThread); cafeExportRegister("coreinit", OSIsThreadTerminated, LogType::CoreinitThread); cafeExportRegister("coreinit", OSIsThreadSuspended, LogType::CoreinitThread); cafeExportRegister("coreinit", OSCancelThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSTestThreadCancel, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetDefaultThread, LogType::CoreinitThread); // OSThreadQueue cafeExportRegister("coreinit", OSInitThreadQueue, LogType::CoreinitThread); cafeExportRegister("coreinit", OSInitThreadQueueEx, LogType::CoreinitThread); } void InitializeThread() { OSInitThreadQueue(g_activeThreadQueue.GetPtr()); for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) OSInitThreadQueue(g_coreRunQueue.GetPtr() + i); for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) __currentCoreThread[i] = nullptr; __OSInitDefaultThreads(); __OSInitTerminatorThreads(); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Thread.h ================================================ #pragma once #include "Cafe/HW/Espresso/Const.h" #include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h" struct OSThread_t; struct OSContextRegFPSCR_t { // FPSCR is a 32bit register but it's stored as a 64bit double /* +0x00 */ uint32be padding; /* +0x04 */ uint32be fpscr; }; struct OSContext_t { static constexpr uint32 OS_CONTEXT_MAGIC_0 = 0x4f53436f; // "OSCo" static constexpr uint32 OS_CONTEXT_MAGIC_1 = 0x6e747874; // "ntxt" /* +0x000 */ betype<uint32> magic0; /* +0x004 */ betype<uint32> magic1; /* +0x008 */ uint32 gpr[32]; /* +0x088 */ uint32 cr; /* +0x08C */ uint32 lr; /* +0x090 */ uint32 ctr; /* +0x094 */ uint32 xer; /* +0x098 */ uint32 srr0; /* +0x09C */ uint32 srr1; /* +0x0A0 */ uint32 dsi_dsisr; /* +0x0A4 */ uint32 dsi_dar; /* +0x0A8 */ uint32 ukn0A8; /* +0x0AC */ uint32 ukn0AC; /* +0x0B0 */ OSContextRegFPSCR_t fpscr; /* +0x0B8 */ uint64be fp_ps0[32]; /* +0x1B8 */ uint16be boostCount; /* +0x1BA */ uint16 state; // OS_CONTEXT_STATE_* /* +0x1BC */ uint32 gqr[8]; // GQR/UGQR /* +0x1DC */ uint32be upir; // set to current core index /* +0x1E0 */ uint64be fp_ps1[32]; /* +0x2E0 */ uint64be coretime[3]; /* +0x2F8 */ uint64be starttime; /* +0x300 */ sint32be ghs_errno; // returned by __gh_errno_ptr() (used by socketlasterr) /* +0x304 */ uint32be affinity; /* +0x308 */ uint32be upmc1; /* +0x30C */ uint32be upmc2; /* +0x310 */ uint32be upmc3; /* +0x314 */ uint32be upmc4; /* +0x318 */ uint32be ummcr0; /* +0x31C */ uint32be ummcr1; bool checkMagic() { return magic0 == (uint32)OS_CONTEXT_MAGIC_0 && magic1 == (uint32)OS_CONTEXT_MAGIC_1; } void SetContextMagic() { magic0 = OS_CONTEXT_MAGIC_0; magic1 = OS_CONTEXT_MAGIC_1; } bool hasCoreAffinitySet(uint32 coreIndex) const { return (((uint32)affinity >> coreIndex) & 1) != 0; } void setAffinity(uint32 mask) { affinity = mask & 7; } uint32 getAffinity() const { return affinity; } }; static_assert(sizeof(OSContext_t) == 0x320); typedef struct { /* +0x000 | +0x3A0 */ uint32 ukn000[0x68 / 4]; /* +0x068 | +0x408 */ MEMPTR<void> eh_globals; /* +0x06C | +0x40C */ uint32 eh_mem_manage[9]; // struct /* +0x090 | +0x430 */ MPTR eh_store_globals[6]; /* +0x0A8 | +0x448 */ MPTR eh_store_globals_tdeh[76]; }crt_t; // size: 0x1D8 static_assert(sizeof(crt_t) == 0x1D8, ""); #pragma pack(1) namespace coreinit { /********* OSThreadQueue *********/ struct OSThreadLink { MEMPTR<struct OSThread_t> next; MEMPTR<struct OSThread_t> prev; }; static_assert(sizeof(OSThreadLink) == 8); struct OSThreadQueueInternal { MEMPTR<OSThread_t> head; MEMPTR<OSThread_t> tail; bool isEmpty() const { cemu_assert_debug((!head.IsNull() == !tail.IsNull()) || (head.IsNull() == tail.IsNull())); return head.IsNull(); } void addThread(OSThread_t* thread, OSThreadLink* threadLink); void addThreadByPriority(OSThread_t* thread, OSThreadLink* threadLink); void removeThread(OSThread_t* thread, OSThreadLink* threadLink); // puts the thread on the waiting queue and changes state to WAITING // relinquishes timeslice // always uses thread->waitQueueLink void queueAndWait(OSThread_t* thread); void queueOnly(OSThread_t* thread); // counterparts for queueAndWait void cancelWait(OSThread_t* thread); void wakeupEntireWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround = false); void wakeupSingleThreadWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround = false); private: OSThread_t* takeFirstFromQueue(size_t linkOffset) { cemu_assert_debug(__OSHasSchedulerLock()); if (head == nullptr) return nullptr; OSThread_t* thread = head.GetPtr(); OSThreadLink* link = _getThreadLink(thread, linkOffset); removeThread(thread, link); return thread; } static size_t getLinkOffset(OSThread_t* thread, OSThreadLink* threadLink) { cemu_assert_debug((void*)threadLink >= (void*)thread && (void*)threadLink < (void*)((uint8*)thread + 0x680)); return (uint8*)threadLink - (uint8*)thread; } static OSThreadLink* _getThreadLink(OSThread_t* thread, size_t linkOffset) { return (OSThreadLink*)((uint8*)thread + linkOffset); } void _debugCheckChain(OSThread_t* thread, OSThreadLink* threadLink) { #ifdef CEMU_DEBUG_ASSERT cemu_assert_debug(tail.IsNull() == head.IsNull()); size_t linkOffset = getLinkOffset(thread, threadLink); // expects thread to be in the chain OSThread_t* threadItr = head.GetPtr(); while (threadItr) { if (threadItr == thread) return; threadItr = _getThreadLink(threadItr, linkOffset)->next.GetPtr(); } cemu_assert_debug(false); // thread not in list! #endif } }; static_assert(sizeof(OSThreadQueueInternal) == 0x8); struct OSThreadQueueSmall : public OSThreadQueueInternal { // no extra members }; static_assert(sizeof(OSThreadQueueSmall) == 8); static_assert(offsetof(OSThreadQueueSmall, head) == 0x0); static_assert(offsetof(OSThreadQueueSmall, tail) == 0x4); struct OSThreadQueue : public OSThreadQueueInternal { MEMPTR<void> userData; uint32be ukn0C; }; static_assert(sizeof(OSThreadQueue) == 0x10); static_assert(offsetof(OSThreadQueue, head) == 0x0); static_assert(offsetof(OSThreadQueue, tail) == 0x4); static_assert(offsetof(OSThreadQueue, userData) == 0x8); static_assert(offsetof(OSThreadQueue, ukn0C) == 0xC); /********* OSMutex *********/ struct OSMutex { /* +0x00 */ uint32 magic; /* +0x04 */ MEMPTR<void> userData; /* +0x08 */ uint32be ukn08; /* +0x0C */ OSThreadQueue threadQueue; /* +0x1C */ MEMPTR<OSThread_t> owner; /* +0x20 */ sint32be lockCount; /* +0x24 */ MEMPTR<OSMutex> next; /* +0x28 */ MEMPTR<OSMutex> prev; }; // size: 0x2C static_assert(sizeof(OSMutex) == 0x2C); struct OSMutexQueue { MEMPTR<OSMutex> head; MEMPTR<OSMutex> tail; MEMPTR<void> ukn08; uint32be ukn0C; bool isEmpty() const { cemu_assert_debug((!head.IsNull() == !tail.IsNull()) || (head.IsNull() == tail.IsNull())); return head.IsNull(); } void addMutex(OSMutex* mutex) { cemu_assert_debug(__OSHasSchedulerLock()); // insert at end if (tail.IsNull()) { mutex->next = nullptr; mutex->prev = nullptr; head = mutex; tail = mutex; } else { tail->next = mutex; mutex->prev = tail; mutex->next = nullptr; tail = mutex; } } void removeMutex(OSMutex* mutex) { cemu_assert_debug(__OSHasSchedulerLock()); cemu_assert_debug(!head.IsNull() && !tail.IsNull()); if (mutex->prev) mutex->prev->next = mutex->next; else head = mutex->next; if (mutex->next) mutex->next->prev = mutex->prev; else tail = mutex->prev; mutex->next = nullptr; mutex->prev = nullptr; } OSMutex* getFirst() { return head.GetPtr(); } }; static_assert(sizeof(OSMutexQueue) == 0x10); /********* OSFastMutex *********/ struct OSFastMutexLink { /* +0x00 */ MEMPTR<struct OSMutex> next; /* +0x04 */ MEMPTR<struct OSMutex> prev; }; struct OSFastMutex { /* +0x00 */ uint32be magic; /* +0x04 */ MEMPTR<void> userData; /* +0x08 */ uint32be contendedState; // tracks current contention state /* +0x0C */ OSThreadQueueSmall threadQueueSmall; /* +0x14 */ OSFastMutexLink ownedLink; // part of thread->fastMutexOwnedQueue /* +0x1C */ MEMPTR<OSThread_t> owner; /* +0x20 */ uint32be lockCount; /* +0x24 */ OSFastMutexLink contendedLink; }; static_assert(sizeof(OSFastMutex) == 0x2C); /********* OSEvent *********/ struct OSEvent { enum class EVENT_MODE : uint32 { MODE_MANUAL = 0, MODE_AUTO = 1, }; enum class EVENT_STATE : uint32 { STATE_NOT_SIGNALED = 0, STATE_SIGNALED = 1 }; /* +0x00 */ uint32be magic; // 'eVnT' /* +0x04 */ MEMPTR<void> userData; /* +0x08 */ uint32be ukn08; /* +0x0C */ betype<EVENT_STATE> state; // 0 -> not signaled, 1 -> signaled /* +0x10 */ OSThreadQueue threadQueue; /* +0x20 */ betype<EVENT_MODE> mode; }; static_assert(sizeof(OSEvent) == 0x24); /********* OSRendezvous *********/ struct OSRendezvous { /* +0x00 */ uint32be coreHit[3]; /* +0x0C */ MEMPTR<void> userData; }; static_assert(sizeof(OSRendezvous) == 0x10); /********* OSCond *********/ struct OSCond { uint32be magic; MEMPTR<void> userData; uint32be ukn08; OSThreadQueue threadQueue; }; static_assert(sizeof(OSCond) == 0x1C); /********* OSSemaphore *********/ struct OSSemaphore { uint32be magic; MEMPTR<void> userData; uint32be ukn08; sint32be count; OSThreadQueue threadQueue; }; static_assert(sizeof(OSSemaphore) == 0x20); /********* OSFastCond *********/ struct OSFastCond { uint32be magic; MEMPTR<void> userData; uint32be ukn08; OSThreadQueue threadQueue; }; static_assert(sizeof(OSFastCond) == 0x1C); }; struct OSThread_t { static constexpr uint32 MAGIC_THREAD = 0x74487244; // "tHrD" enum class THREAD_TYPE : uint32 { TYPE_DRIVER = 0, TYPE_IO = 1, TYPE_APP = 2 }; enum class THREAD_STATE : uint8 { STATE_NONE = 0, STATE_READY = 1, STATE_RUNNING = 2, STATE_WAITING = 4, STATE_MORIBUND = 8, }; enum ATTR_BIT : uint32 { ATTR_AFFINITY_CORE0 = 0x1, ATTR_AFFINITY_CORE1 = 0x2, ATTR_AFFINITY_CORE2 = 0x4, ATTR_DETACHED = 0x8, ATTR_UKN_010 = 0x10, }; enum REQUEST_FLAG_BIT : uint32 { REQUEST_FLAG_NONE = 0, REQUEST_FLAG_SUSPEND = 1, REQUEST_FLAG_CANCEL = 2, }; static sint32 GetTypePriorityBase(THREAD_TYPE threadType) { if (threadType == OSThread_t::THREAD_TYPE::TYPE_DRIVER) return 0; else if (threadType == OSThread_t::THREAD_TYPE::TYPE_IO) return 32; else if (threadType == OSThread_t::THREAD_TYPE::TYPE_APP) return 64; return 0; } void SetThreadMagic() { magic = MAGIC_THREAD; } bool IsValidMagic() const { return magic == MAGIC_THREAD && context.magic0 == OSContext_t::OS_CONTEXT_MAGIC_0 && context.magic1 == OSContext_t::OS_CONTEXT_MAGIC_1; } /* +0x000 */ OSContext_t context; /* +0x320 */ uint32be magic; // "tHrD" (0x74487244) /* +0x324 */ betype<THREAD_STATE> state; /* +0x325 */ uint8 attr; /* +0x326 */ uint16be id; // Warriors Orochi 3 uses this to identify threads /* +0x328 */ betype<sint32> suspendCounter; /* +0x32C */ sint32be effectivePriority; // effective priority (lower is higher) /* +0x330 */ sint32be basePriority; // base priority (lower is higher) /* +0x334 */ uint32be exitValue; // set by OSExitThread() or by returning from the thread entrypoint /* +0x338 */ MEMPTR<coreinit::OSThreadQueue> currentRunQueue[Espresso::CORE_COUNT]; // points to the OSThreadQueue on which this thread is (null if not in queue) /* +0x344 */ coreinit::OSThreadLink linkRun[Espresso::CORE_COUNT]; // general wait queue / thread dependency queue /* +0x35C */ MEMPTR<coreinit::OSThreadQueueInternal> currentWaitQueue; // shared between OSThreadQueue and OSThreadQueueSmall /* +0x360 */ coreinit::OSThreadLink waitQueueLink; /* +0x368 */ coreinit::OSThreadQueue joinQueue; /* +0x378 */ MEMPTR<coreinit::OSMutex> waitingForMutex; // set when thread is waiting for OSMutex to unlock /* +0x37C */ coreinit::OSMutexQueue mutexQueue; /* +0x38C */ coreinit::OSThreadLink activeThreadChain; // queue of active threads (g_activeThreadQueue) /* +0x394 */ MEMPTR<void> stackBase; // upper limit of stack /* +0x398 */ MEMPTR<void> stackEnd; // lower limit of stack /* +0x39C */ MEMPTR<void> entrypoint; /* +0x3A0 */ crt_t crt; /* +0x578 */ sint32 alarmRelatedUkn; /* +0x57C */ std::array<MEMPTR<void>, 16> specificArray; /* +0x5BC */ betype<THREAD_TYPE> type; /* +0x5C0 */ MEMPTR<const char> threadName; /* +0x5C4 */ MEMPTR<void> waitAlarm; // used only by OSWaitEventWithTimeout/OSSignalEvent ? /* +0x5C8 */ uint32 userStackPointer; /* +0x5CC */ MEMPTR<void> cleanupCallback; /* +0x5D0 */ MEMPTR<void> deallocatorFunc; /* +0x5D4 */ uint32 stateFlags; // 0x5D4 | various flags? Controls if canceling/suspension is allowed (at cancel points) or not? If 1 -> Cancel/Suspension not allowed, if 0 -> Cancel/Suspension allowed /* +0x5D8 */ betype<REQUEST_FLAG_BIT> requestFlags; /* +0x5DC */ sint32 pendingSuspend; /* +0x5E0 */ sint32 suspendResult; /* +0x5E4 */ coreinit::OSThreadQueue suspendQueue; /* +0x5F4 */ uint32 _padding5F4; /* +0x5F8 */ uint64be quantumTicks; /* +0x600 */ uint64 coretimeSumQuantumStart; /* +0x608 */ uint64be wakeUpCount; // number of times the thread entered running state /* +0x610 */ uint64be totalCycles; // sum of cycles this thread was active since it was created /* +0x618 */ uint64be wakeUpTime; // time when thread first became active (Should only be set once(?) but we currently set this on every timeslice since thats more useful for debugging) /* +0x620 */ uint64 wakeTimeRelatedUkn1; /* +0x628 */ uint64 wakeTimeRelatedUkn2; // set via OSSetExceptionCallback /* +0x630 */ MPTR dsiCallback[Espresso::CORE_COUNT]; /* +0x63C */ MPTR isiCallback[Espresso::CORE_COUNT]; /* +0x648 */ MPTR programCallback[Espresso::CORE_COUNT]; /* +0x654 */ MPTR perfMonCallback[Espresso::CORE_COUNT]; /* +0x660 */ uint32 ukn660; // todo - some of the members towards the end of the struct were only added in later COS versions. Figure out the mapping between version and members // TLS /* +0x664 */ uint16 numAllocatedTLSBlocks; /* +0x666 */ sint16 tlsStatus; /* +0x668 */ MPTR tlsBlocksMPTR; /* +0x66C */ MEMPTR<coreinit::OSFastMutex> waitingForFastMutex; /* +0x670 */ coreinit::OSFastMutexLink contendedFastMutex; /* +0x678 */ coreinit::OSFastMutexLink ownedFastMutex; /* +0x680 */ MEMPTR<void> alignmentExceptionCallback[Espresso::CORE_COUNT]; /* +0x68C */ uint32 padding68C[20 / 4]; }; static_assert(sizeof(OSThread_t) == 0x6A0); namespace coreinit { void MapThreadExports(); void InitializeThread(); void InitializeConcurrency(); bool __CemuIsMulticoreMode(); OSThread_t* OSGetDefaultThread(sint32 coreIndex); void* OSGetDefaultThreadStack(sint32 coreIndex, uint32& size); bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType); bool __OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType); void OSCreateThreadInternal(OSThread_t* thread, uint32 entryPoint, MPTR stackLowerBaseAddr, uint32 stackSize, uint8 affinityMask, OSThread_t::THREAD_TYPE threadType); bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam); void OSExitThread(sint32 exitValue); void OSDetachThread(OSThread_t* thread); OSThread_t* OSGetCurrentThread(); void OSSetCurrentThread(uint32 coreIndex, OSThread_t* thread); void __OSSetThreadBasePriority(OSThread_t* thread, sint32 newPriority); void __OSUpdateThreadEffectivePriority(OSThread_t* thread); bool OSSetThreadPriority(OSThread_t* thread, sint32 newPriority); uint32 OSGetThreadAffinity(OSThread_t* thread); void OSSetThreadName(OSThread_t* thread, const char* name); const char* OSGetThreadName(OSThread_t* thread); sint32 __OSResumeThreadInternal(OSThread_t* thread, sint32 resumeCount); sint32 OSResumeThread(OSThread_t* thread); void OSContinueThread(OSThread_t* thread); void __OSSuspendThreadInternal(OSThread_t* thread); void __OSSuspendThreadNolock(OSThread_t* thread); void OSSuspendThread(OSThread_t* thread); void OSSleepThread(OSThreadQueue* threadQueue); void OSWakeupThread(OSThreadQueue* threadQueue); bool OSJoinThread(OSThread_t* thread, uint32be* exitValue); void OSTestThreadCancelInternal(); void OSYieldThread(); void OSSleepTicks(uint64 ticks); bool OSIsThreadTerminated(OSThread_t* thread); bool OSIsThreadSuspended(OSThread_t* thread); bool OSIsThreadRunningNoLock(OSThread_t* thread); bool OSIsThreadRunning(OSThread_t* thread); // OSThreadQueue void OSInitThreadQueue(OSThreadQueue* threadQueue); void OSInitThreadQueueEx(OSThreadQueue* threadQueue, void* userData); // OSEvent void OSInitEvent(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode); void OSInitEventEx(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode, void* userData); void OSResetEvent(OSEvent* event); void OSWaitEventInternal(OSEvent* event); // assumes lock is already held void OSWaitEvent(OSEvent* event); bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout); void OSSignalEventInternal(OSEvent* event); // assumes lock is already held void OSSignalEvent(OSEvent* event); void OSSignalEventAllInternal(OSEvent* event); // assumes lock is already held void OSSignalEventAll(OSEvent* event); // OSRendezvous void OSInitRendezvous(OSRendezvous* rendezvous); bool OSWaitRendezvous(OSRendezvous* rendezvous, uint32 coreMask); // OSMutex void OSInitMutex(OSMutex* mutex); void OSInitMutexEx(OSMutex* mutex, void* userData); void OSLockMutex(OSMutex* mutex); bool OSTryLockMutex(OSMutex* mutex); void OSUnlockMutex(OSMutex* mutex); void OSUnlockMutexInternal(OSMutex* mutex); // OSCond void OSInitCond(OSCond* cond); void OSInitCondEx(OSCond* cond, void* userData); void OSSignalCond(OSCond* cond); void OSWaitCond(OSCond* cond, OSMutex* mutex); // OSSemaphore void OSInitSemaphore(OSSemaphore* semaphore, sint32 initialCount); void OSInitSemaphoreEx(OSSemaphore* semaphore, sint32 initialCount, void* userData); sint32 OSWaitSemaphoreInternal(OSSemaphore* semaphore); sint32 OSWaitSemaphore(OSSemaphore* semaphore); sint32 OSTryWaitSemaphore(OSSemaphore* semaphore); sint32 OSSignalSemaphore(OSSemaphore* semaphore); sint32 OSSignalSemaphoreInternal(OSSemaphore* semaphore, bool reschedule); sint32 OSGetSemaphoreCount(OSSemaphore* semaphore); // OSFastMutex void OSFastMutex_Init(OSFastMutex* fastMutex, void* userData); void OSFastMutex_Lock(OSFastMutex* fastMutex); bool OSFastMutex_TryLock(OSFastMutex* fastMutex); void OSFastMutex_Unlock(OSFastMutex* fastMutex); // OSFastCond void OSFastCond_Init(OSFastCond* fastCond, void* userData); void OSFastCond_Wait(OSFastCond* fastCond, OSFastMutex* fastMutex); void OSFastCond_Signal(OSFastCond* fastCond); // scheduler void OSSchedulerBegin(sint32 numCPUEmulationThreads); void OSSchedulerEnd(); // internal void __OSAddReadyThreadToRunQueue(OSThread_t* thread); bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread, bool sharedPriorityAndAffinityWorkaround); void __OSQueueThreadDeallocation(OSThread_t* thread); bool __OSIsThreadActive(OSThread_t* thread); void __OSDeleteAllActivePPCThreads(); } #pragma pack() // deprecated / clean up required extern MPTR activeThread[256]; extern sint32 activeThreadCount; extern SlimRWLock srwlock_activeThreadList; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" namespace coreinit { // puts the thread on the waiting queue and changes state to WAITING // relinquishes timeslice // always uses thread->waitQueueLink void OSThreadQueueInternal::queueAndWait(OSThread_t* thread) { cemu_assert_debug(__OSHasSchedulerLock()); cemu_assert_debug(thread->waitQueueLink.next == nullptr && thread->waitQueueLink.prev == nullptr); thread->currentWaitQueue = this; this->addThreadByPriority(thread, &thread->waitQueueLink); cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING); thread->state = OSThread_t::THREAD_STATE::STATE_WAITING; PPCCore_switchToSchedulerWithLock(); cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING); } void OSThreadQueueInternal::queueOnly(OSThread_t* thread) { cemu_assert_debug(__OSHasSchedulerLock()); cemu_assert_debug(thread->waitQueueLink.next == nullptr && thread->waitQueueLink.prev == nullptr); thread->currentWaitQueue = this; this->addThreadByPriority(thread, &thread->waitQueueLink); cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING); thread->state = OSThread_t::THREAD_STATE::STATE_WAITING; } // remove thread from wait queue and wake it up void OSThreadQueueInternal::cancelWait(OSThread_t* thread) { cemu_assert_debug(__OSHasSchedulerLock()); this->removeThread(thread, &thread->waitQueueLink); thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); // todo - if waking up a thread on the same core with higher priority, reschedule } void OSThreadQueueInternal::addThread(OSThread_t* thread, OSThreadLink* threadLink) { cemu_assert_debug(__OSHasSchedulerLock()); size_t linkOffset = getLinkOffset(thread, threadLink); // insert after tail if (tail.IsNull()) { threadLink->next = nullptr; threadLink->prev = nullptr; head = thread; tail = thread; } else { threadLink->next = nullptr; threadLink->prev = tail; _getThreadLink(tail.GetPtr(), linkOffset)->next = thread; tail = thread; } _debugCheckChain(thread, threadLink); } void OSThreadQueueInternal::addThreadByPriority(OSThread_t* thread, OSThreadLink* threadLink) { cemu_assert_debug(tail.IsNull() == head.IsNull()); // either must be set or none at all cemu_assert_debug(__OSHasSchedulerLock()); size_t linkOffset = getLinkOffset(thread, threadLink); if (tail.IsNull()) { threadLink->next = nullptr; threadLink->prev = nullptr; head = thread; tail = thread; } else { // insert towards tail based on priority OSThread_t* threadItr = tail.GetPtr(); while (threadItr && threadItr->effectivePriority > thread->effectivePriority) threadItr = _getThreadLink(threadItr, linkOffset)->prev.GetPtr(); if (threadItr == nullptr) { // insert in front threadLink->next = head; threadLink->prev = nullptr; _getThreadLink(head.GetPtr(), linkOffset)->prev = thread; head = thread; } else { threadLink->prev = threadItr; threadLink->next = _getThreadLink(threadItr, linkOffset)->next; if (_getThreadLink(threadItr, linkOffset)->next) { OSThread_t* threadAfterItr = _getThreadLink(threadItr, linkOffset)->next.GetPtr(); _getThreadLink(threadAfterItr, linkOffset)->prev = thread; _getThreadLink(threadItr, linkOffset)->next = thread; } else { tail = thread; _getThreadLink(threadItr, linkOffset)->next = thread; } } } _debugCheckChain(thread, threadLink); } void OSThreadQueueInternal::removeThread(OSThread_t* thread, OSThreadLink* threadLink) { cemu_assert_debug(__OSHasSchedulerLock()); size_t linkOffset = getLinkOffset(thread, threadLink); _debugCheckChain(thread, threadLink); if (threadLink->prev) _getThreadLink(threadLink->prev.GetPtr(), linkOffset)->next = threadLink->next; else head = threadLink->next; if (threadLink->next) _getThreadLink(threadLink->next.GetPtr(), linkOffset)->prev = threadLink->prev; else tail = threadLink->prev; threadLink->next = nullptr; threadLink->prev = nullptr; } // counterpart for queueAndWait // if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core) // sharedPriorityAndAffinityWorkaround is currently a hack/placeholder for some special cases. A proper fix likely involves handling all the nuances of thread effective priority void OSThreadQueueInternal::wakeupEntireWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround) { cemu_assert_debug(__OSHasSchedulerLock()); bool shouldReschedule = false; while (OSThread_t* thread = takeFirstFromQueue(offsetof(OSThread_t, waitQueueLink))) { cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_WAITING); //cemu_assert_debug(thread->suspendCounter == 0); thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, sharedPriorityAndAffinityWorkaround)) shouldReschedule = true; } if (shouldReschedule) PPCCore_switchToSchedulerWithLock(); } // counterpart for queueAndWait // if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core) void OSThreadQueueInternal::wakeupSingleThreadWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround) { cemu_assert_debug(__OSHasSchedulerLock()); OSThread_t* thread = takeFirstFromQueue(offsetof(OSThread_t, waitQueueLink)); cemu_assert_debug(thread); bool shouldReschedule = false; if (thread) { thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, sharedPriorityAndAffinityWorkaround)) shouldReschedule = true; } if (shouldReschedule) PPCCore_switchToSchedulerWithLock(); } } ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Time.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" namespace coreinit { uint64 coreinit_GetMFTB() { // bus clock is 1/5th of core clock // timer clock is 1/4th of bus clock return PPCInterpreter_getMainCoreCycleCounter() / 20ULL; } uint64 OSGetSystemTime() { return coreinit_GetMFTB(); } uint64 OSGetTime() { return OSGetSystemTime() + ppcCyclesSince2000TimerClock; } uint32 OSGetSystemTick() { return static_cast<uint32>(coreinit_GetMFTB()); } uint32 OSGetTick() { uint64 osTime = OSGetTime(); return static_cast<uint32>(osTime); } uint32 getLeapDaysUntilYear(uint32 year) { if (year == 0) return 0; return (year + 3) / 4 - (year - 1) / 100 + (year - 1) / 400; } bool IsLeapYear(uint32 year) { if (((year & 3) == 0) && (year % 100) != 0) return true; if ((year % 400) == 0) return true; return false; } uint32 dayToMonth[12] = { 0,31,59,90,120,151,181,212,243,273,304,334 }; uint32 dayToMonthLeapYear[12] = { 0,31,60,91,121,152,182,213,244,274,305,335 }; uint32 getDayInYearByYearAndMonth(uint32 year, uint32 month) { // Project Zero Maiden of Black Water (JPN) gives us an invalid calendar object month %= 12; // or return 0 if too big? if (IsLeapYear(year)) return dayToMonthLeapYear[month]; return dayToMonth[month]; } inline const uint64 DAY_BIAS_2000 = 0xB2575; uint64 OSCalendarTimeToTicks(OSCalendarTime_t *calendar) { uint32 year = calendar->year; uint32 leapDays = getLeapDaysUntilYear(year); uint32 startDayOfCurrentMonth = getDayInYearByYearAndMonth(year, calendar->month); uint64 dayInYear = (startDayOfCurrentMonth + calendar->dayOfMonth) - 1; uint64 dayCount = dayInYear + year * 365 + leapDays - DAY_BIAS_2000; // convert date to seconds uint64 tSeconds = 0; tSeconds += (uint64)dayCount * 24 * 60 * 60; tSeconds += (uint64)calendar->hour * 60 * 60; tSeconds += (uint64)calendar->minute * 60; tSeconds += (uint64)calendar->second; uint64 tSubSecondTicks = 0; tSubSecondTicks += (uint64)calendar->millisecond * ESPRESSO_TIMER_CLOCK / 1000; tSubSecondTicks += (uint64)calendar->microsecond * ESPRESSO_TIMER_CLOCK / 1000000; return tSeconds * ESPRESSO_TIMER_CLOCK + tSubSecondTicks; } void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct) { uint64 tSubSecondTicks = ticks % ESPRESSO_TIMER_CLOCK; uint64 tSeconds = ticks / ESPRESSO_TIMER_CLOCK; uint64 microsecond = tSubSecondTicks * 1000000ull / ESPRESSO_TIMER_CLOCK; microsecond %= 1000ull; calenderStruct->microsecond = (uint32)microsecond; uint64 millisecond = tSubSecondTicks * 1000ull / ESPRESSO_TIMER_CLOCK; millisecond %= 1000ull; calenderStruct->millisecond = (uint32)millisecond; uint64 dayOfWeek = (tSeconds/(24ull * 60 * 60) + 6ull) % 7ull; uint64 secondOfDay = (tSeconds % (24ull * 60 * 60)); calenderStruct->dayOfWeek = (sint32)dayOfWeek; uint64 daysSince0AD = tSeconds / (24ull * 60 * 60) + DAY_BIAS_2000; uint32 year = (uint32)(daysSince0AD / 365ull); uint64 yearStartDay = year * 365 + getLeapDaysUntilYear(year); while (yearStartDay > daysSince0AD) { year--; if (IsLeapYear(year)) yearStartDay -= 366; else yearStartDay -= 365; } calenderStruct->year = year; // calculate time of day uint32 tempSecond = (uint32)secondOfDay; calenderStruct->second = tempSecond % 60; tempSecond /= 60; calenderStruct->minute = tempSecond % 60; tempSecond /= 60; calenderStruct->hour = tempSecond % 24; tempSecond /= 24; // calculate month and day uint32 dayInYear = (uint32)(daysSince0AD - yearStartDay); bool isLeapYear = IsLeapYear(year); uint32 month = 0; // 0-11 uint32 dayInMonth = 0; if (isLeapYear && dayInYear < (31+29)) { if (dayInYear < 31) { // January month = 0; dayInMonth = dayInYear; } else { // February month = 1; dayInMonth = dayInYear-31; } } else { if (isLeapYear) dayInYear--; dayInMonth = dayInYear; if (dayInYear >= 334) { // December month = 11; dayInMonth -= 334; } else if (dayInYear >= 304) { // November month = 10; dayInMonth -= 304; } else if (dayInYear >= 273) { // October month = 9; dayInMonth -= 273; } else if (dayInYear >= 243) { // September month = 8; dayInMonth -= 243; } else if (dayInYear >= 212) { // August month = 7; dayInMonth -= 212; } else if (dayInYear >= 181) { // July month = 6; dayInMonth -= 181; } else if (dayInYear >= 151) { // June month = 5; dayInMonth -= 151; } else if (dayInYear >= 120) { // May month = 4; dayInMonth -= 120; } else if (dayInYear >= 90) { // April month = 3; dayInMonth -= 90; } else if (dayInYear >= 59) { // March month = 2; dayInMonth -= 59; } else if (dayInYear >= 31) { // February month = 1; dayInMonth -= 31; } else { // January month = 0; dayInMonth -= 0; } } calenderStruct->dayOfYear = dayInYear; calenderStruct->month = month; calenderStruct->dayOfMonth = dayInMonth + 1; } uint32 getDaysInMonth(uint32 year, uint32 month) { switch (month) { case 0: // January return 31; case 1: // February return IsLeapYear(year) ? 29 : 28; case 2: // March return 31; case 3: // April return 30; case 4: // May return 31; case 5: // June return 30; case 6: // July return 31; case 7: // August return 31; case 8: // September return 30; case 9: // October return 31; case 10: // November return 30; case 11: // December return 31; default: cemu_assert(false); } return 31; } void verifyDateMatch(OSCalendarTime_t* ct1, OSCalendarTime_t* ct2) { sint64 m1 = ct1->millisecond * 1000 + ct1->microsecond; sint64 m2 = ct2->millisecond * 1000 + ct2->microsecond; sint64 microDif = std::abs(m1 - m2); if (ct1->year != ct2->year || ct1->month != ct2->month || ct1->dayOfMonth != ct2->dayOfMonth || ct1->hour != ct2->hour || ct1->minute != ct2->minute || ct1->second != ct2->second || microDif > 1ull) { debug_printf("Mismatch\n"); assert_dbg(); } } void timeTest() { sint32 iterCount = 0; OSCalendarTime_t ct{}; ct.year = 2000; ct.month = 1; ct.dayOfMonth = 1; ct.hour = 1; ct.minute = 1; ct.second = 1; ct.millisecond = 123; ct.microsecond = 321; while (true) { iterCount++; uint64 ticks = OSCalendarTimeToTicks(&ct); // make sure converting it back results in the same date OSCalendarTime_t ctTemp; OSTicksToCalendarTime(ticks, &ctTemp); verifyDateMatch(&ct, &ctTemp); // add a day ticks += 24ull * 60 * 60 * ESPRESSO_TIMER_CLOCK; OSCalendarTime_t ctOutput; OSTicksToCalendarTime(ticks, &ctOutput); OSCalendarTime_t ctExpected; ctExpected = ct; // add a day manually sint32 daysInMonth = getDaysInMonth(ctExpected.year, ctExpected.month); ctExpected.dayOfMonth = ctExpected.dayOfMonth + 1; if (ctExpected.dayOfMonth >= daysInMonth+1) { ctExpected.dayOfMonth = 1; ctExpected.month = ctExpected.month + 1; if (ctExpected.month > 11) { ctExpected.month = 0; ctExpected.year = ctExpected.year + 1; } } verifyDateMatch(&ctExpected, &ctOutput); ct = ctOutput; } } void InitializeTimeAndCalendar() { cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder); cafeExportRegister("coreinit", OSGetSystemTime, LogType::Placeholder); cafeExportRegister("coreinit", OSGetTick, LogType::Placeholder); cafeExportRegister("coreinit", OSGetSystemTick, LogType::Placeholder); cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder); cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder); //timeTest(); } }; ================================================ FILE: src/Cafe/OS/libs/coreinit/coreinit_Time.h ================================================ #pragma once #include "Cafe/HW/Espresso/Const.h" namespace coreinit { typedef struct { /* +0x00 */ sint32be second; // 0-59 /* +0x04 */ sint32be minute; // 0-59 /* +0x08 */ sint32be hour; // 0-23 /* +0x0C */ sint32be dayOfMonth; // 1-31 /* +0x10 */ sint32be month; // 0-11 /* +0x14 */ sint32be year; // 2000-... /* +0x18 */ sint32be dayOfWeek; // 0-6 /* +0x1C */ sint32be dayOfYear; // 0-365 /* +0x20 */ sint32be millisecond; // 0-999 /* +0x24 */ sint32be microsecond; // 0-999 }OSCalendarTime_t; static_assert(sizeof(OSCalendarTime_t) == 0x28); namespace EspressoTime { typedef sint64 TimerTicks; constexpr sint64 GetCoreClock() { return Espresso::CORE_CLOCK; } constexpr sint64 GetBusClock() { return Espresso::BUS_CLOCK; } constexpr sint64 GetTimerClock() { return Espresso::TIMER_CLOCK; } inline TimerTicks ConvertNsToTimerTicks(uint64 ns) { return static_cast<TimerTicks>((static_cast<uint64>(GetTimerClock()) / 31250ULL) * (ns) / 32000ULL); } inline TimerTicks ConvertMsToTimerTicks(uint64 ms) { return static_cast<TimerTicks>(ms * static_cast<uint64>(GetTimerClock()) / 1000ULL); } }; void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct); uint64 OSGetSystemTime(); uint64 OSGetTime(); uint32 OSGetSystemTick(); uint32 OSGetTick(); void InitializeTimeAndCalendar(); }; ================================================ FILE: src/Cafe/OS/libs/dmae/dmae.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #define DMAE_ENDIAN_NONE 0 #define DMAE_ENDIAN_16 1 #define DMAE_ENDIAN_32 2 #define DMAE_ENDIAN_64 3 uint64 dmaeRetiredTimestamp = 0; uint64 dmae_getTimestamp() { return coreinit::OSGetSystemTime(); } void dmae_setRetiredTimestamp(uint64 timestamp) { dmaeRetiredTimestamp = timestamp; } void dmaeExport_DMAECopyMem(PPCInterpreter_t* hCPU) { if( hCPU->gpr[6] == DMAE_ENDIAN_NONE ) { // don't change endianness memcpy(memory_getPointerFromVirtualOffset(hCPU->gpr[3]), memory_getPointerFromVirtualOffset(hCPU->gpr[4]), hCPU->gpr[5]*4); } else if( hCPU->gpr[6] == DMAE_ENDIAN_32 ) { // swap per uint32 uint32* srcBuffer = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); uint32* dstBuffer = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); for(uint32 i=0; i<hCPU->gpr[5]; i++) { dstBuffer[i] = _swapEndianU32(srcBuffer[i]); } } else if( hCPU->gpr[6] == DMAE_ENDIAN_16 ) { // swap per uint16 uint16* srcBuffer = (uint16*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); uint16* dstBuffer = (uint16*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); for(uint32 i=0; i<hCPU->gpr[5]*2; i++) { dstBuffer[i] = _swapEndianU16(srcBuffer[i]); } } else { cemuLog_logDebug(LogType::Force, "DMAECopyMem(): Unsupported endian swap\n"); } uint64 dmaeTimestamp = dmae_getTimestamp(); dmae_setRetiredTimestamp(dmaeTimestamp); if(hCPU->gpr[5] > 0) LatteBufferCache_notifyDCFlush(hCPU->gpr[3], hCPU->gpr[5]*4); osLib_returnFromFunction64(hCPU, dmaeTimestamp); } void dmaeExport_DMAEFillMem(PPCInterpreter_t* hCPU) { uint32* dstBuffer = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint32 value = hCPU->gpr[4]; uint32 numU32s = hCPU->gpr[5]; value = _swapEndianU32(value); for(uint32 i=0; i<numU32s; i++) { *dstBuffer = value; dstBuffer++; } uint64 dmaeTimestamp = dmae_getTimestamp(); dmae_setRetiredTimestamp(dmaeTimestamp); osLib_returnFromFunction64(hCPU, dmaeTimestamp); } void dmaeExport_DMAEWaitDone(PPCInterpreter_t* hCPU) { //debug_printf("DMAEWaitDone(...)\n"); // parameter: // r3/r4 uint64 dmaeTimestamp osLib_returnFromFunction(hCPU, 1); } void dmaeExport_DMAESemaphore(PPCInterpreter_t* hCPU) { // parameter: // r3 MPTR addr // r4 uint32 actionType uint32 actionType = hCPU->gpr[4]; std::atomic<uint64le>* semaphore = _rawPtrToAtomic((uint64le*)memory_getPointerFromVirtualOffset(hCPU->gpr[3])); if( actionType == 1 ) { // Signal Semaphore semaphore->fetch_add(1); } else if (actionType == 0) // wait { cemuLog_logDebug(LogType::Force, "DMAESemaphore: Unsupported wait operation"); semaphore->fetch_sub(1); } else { cemuLog_logDebug(LogType::Force, "DMAESemaphore unknown action type {}", actionType); cemu_assert_debug(false); } uint64 dmaeTimestamp = dmae_getTimestamp(); dmae_setRetiredTimestamp(dmaeTimestamp); osLib_returnFromFunction64(hCPU, dmaeTimestamp); } void dmaeExport_DMAEGetRetiredTimeStamp(PPCInterpreter_t* hCPU) { debug_printf("DMAEGetRetiredTimeStamp()\n"); osLib_returnFromFunction64(hCPU, dmaeRetiredTimestamp); } namespace dmae { class : public COSModule { public: std::string_view GetName() override { return "dmae"; } void RPLMapped() override { osLib_addFunction("dmae", "DMAECopyMem", dmaeExport_DMAECopyMem); osLib_addFunction("dmae", "DMAEFillMem", dmaeExport_DMAEFillMem); osLib_addFunction("dmae", "DMAEWaitDone", dmaeExport_DMAEWaitDone); osLib_addFunction("dmae", "DMAESemaphore", dmaeExport_DMAESemaphore); osLib_addFunction("dmae", "DMAEGetRetiredTimeStamp", dmaeExport_DMAEGetRetiredTimeStamp); } }s_COSDMAEModule; COSModule* GetModule() { return &s_COSDMAEModule; } } ================================================ FILE: src/Cafe/OS/libs/dmae/dmae.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace dmae { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/drmapp/drmapp.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "drmapp.h" namespace drmapp { uint32 NupChkIsFinished(uint32 ukn) { cemuLog_logDebug(LogType::Force, "drmapp.NupChkIsFinished() - placeholder"); return 1; } uint32 PatchChkIsFinished() { cemuLog_logDebug(LogType::Force, "drmapp.PatchChkIsFinished() - placeholder"); return 1; } uint32 AocChkIsFinished() { cemuLog_logDebug(LogType::Force, "drmapp.AocChkIsFinished() - placeholder"); return 1; } uint32 TicketChkIsFinished() { cemuLog_logDebug(LogType::Force, "drmapp.TicketChkIsFinished__3RplFv() - placeholder"); return 1; } class : public COSModule { public: std::string_view GetName() override { return "drmapp"; } void RPLMapped() override { cafeExportRegisterFunc(NupChkIsFinished, "drmapp", "NupChkIsFinished__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(PatchChkIsFinished, "drmapp", "PatchChkIsFinished__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(AocChkIsFinished, "drmapp", "AocChkIsFinished__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(TicketChkIsFinished, "drmapp", "TicketChkIsFinished__3RplFv", LogType::Placeholder); } }s_COSdrmappModule; COSModule* GetModule() { return &s_COSdrmappModule; } } // namespace drmapp ================================================ FILE: src/Cafe/OS/libs/drmapp/drmapp.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace drmapp { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/erreula/erreula.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "erreula.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "util/helpers/helpers.h" #include <imgui.h> #include "imgui/imgui_extension.h" #include "Cafe/OS/libs/coreinit/coreinit_FS.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/vpad/vpad.h" #include "OS/libs/coreinit/coreinit_DynLoad.h" namespace nn { namespace erreula { enum class ErrorDialogType : uint32 { Code = 0, Text = 1, TextOneButton = 2, TextTwoButton = 3 }; static const sint32 FADE_TIME = 80; enum class ErrEulaState : uint32 { Hidden = 0, Appearing = 1, Visible = 2, Disappearing = 3 }; enum class ResultType : uint32 { None = 0, Finish = 1, Next = 2, Jump = 3, Password = 4 }; struct AppearError { AppearError() = default; AppearError(const AppearError& o) { errorType = o.errorType; screenType = o.screenType; controllerType = o.controllerType; holdType = o.holdType; errorCode = o.errorCode; framerate = o.framerate; text = o.text; button1Text = o.button1Text; button2Text = o.button2Text; title = o.title; drawCursor = o.drawCursor; } betype<ErrorDialogType> errorType; uint32be screenType; uint32be controllerType; uint32be holdType; uint32be errorCode; uint32be framerate; MEMPTR<uint16be> text; MEMPTR<uint16be> button1Text; MEMPTR<uint16be> button2Text; MEMPTR<uint16be> title; uint8 padding[3]; bool drawCursor{}; }; using AppearArg = AppearError; static_assert(sizeof(AppearError) == 0x2C); // maybe larger struct HomeNixSignArg_t { uint32be framerate; }; static_assert(sizeof(HomeNixSignArg_t) == 0x4); // maybe larger struct ControllerInfo_t { MEMPTR<VPADStatus_t> vpadStatus; MEMPTR<KPADStatus_t> kpadStatus[4]; // or 7 now like KPAD_MAX_CONTROLLERS? }; static_assert(sizeof(ControllerInfo_t) == 0x14); // maybe larger class ErrEulaInstance { public: enum class BUTTON_SELECTION : uint32 { NONE = 0xFFFFFFFF, LEFT = 0, RIGHT = 1, }; void Init() { m_buttonSelection = BUTTON_SELECTION::NONE; m_resultCode = -1; m_resultCodeForLeftButton = 0; m_resultCodeForRightButton = 0; SetState(ErrEulaState::Hidden); } void DoAppearError(AppearArg* arg) { m_buttonSelection = BUTTON_SELECTION::NONE; m_resultCode = -1; m_resultCodeForLeftButton = -1; m_resultCodeForRightButton = -1; // for standard dialog its 0 and 1? m_resultCodeForLeftButton = 0; m_resultCodeForRightButton = 1; SetState(ErrEulaState::Appearing); } void DoDisappearError() { if(m_state != ErrEulaState::Visible) return; SetState(ErrEulaState::Disappearing); } void DoCalc() { // appearing and disappearing state will automatically advance after some time if (m_state == ErrEulaState::Appearing || m_state == ErrEulaState::Disappearing) { uint32 elapsedTick = coreinit::OSGetTime() - m_lastStateChange; if (elapsedTick > coreinit::EspressoTime::ConvertMsToTimerTicks(FADE_TIME)) { SetState(m_state == ErrEulaState::Appearing ? ErrEulaState::Visible : ErrEulaState::Hidden); } } } bool IsDecideSelectButtonError() const { return m_buttonSelection != BUTTON_SELECTION::NONE; } bool IsDecideSelectLeftButtonError() const { return m_buttonSelection != BUTTON_SELECTION::LEFT; } bool IsDecideSelectRightButtonError() const { return m_buttonSelection != BUTTON_SELECTION::RIGHT; } void SetButtonSelection(BUTTON_SELECTION selection) { cemu_assert_debug(m_buttonSelection == BUTTON_SELECTION::NONE); m_buttonSelection = selection; cemu_assert_debug(selection == BUTTON_SELECTION::LEFT || selection == BUTTON_SELECTION::RIGHT); m_resultCode = selection == BUTTON_SELECTION::LEFT ? m_resultCodeForLeftButton : m_resultCodeForRightButton; } ErrEulaState GetState() const { return m_state; } sint32 GetResultCode() const { return m_resultCode; } ResultType GetResultType() const { if(m_resultCode == -1) return ResultType::None; if(m_resultCode < 10) return ResultType::Finish; if(m_resultCode >= 9999) return ResultType::Next; if(m_resultCode == 40) return ResultType::Password; return ResultType::Jump; } float GetFadeTransparency() const { if(m_state == ErrEulaState::Appearing || m_state == ErrEulaState::Disappearing) { uint32 elapsedTick = coreinit::OSGetTime() - m_lastStateChange; if(m_state == ErrEulaState::Appearing) return std::min<float>(1.0f, (float)elapsedTick / (float)coreinit::EspressoTime::ConvertMsToTimerTicks(FADE_TIME)); else return std::max<float>(0.0f, 1.0f - (float)elapsedTick / (float)coreinit::EspressoTime::ConvertMsToTimerTicks(FADE_TIME)); } return 1.0f; } private: void SetState(ErrEulaState state) { m_state = state; m_lastStateChange = coreinit::OSGetTime(); } ErrEulaState m_state; uint32 m_lastStateChange; /* +0x30 */ betype<sint32> m_resultCode; /* +0x239C */ betype<BUTTON_SELECTION> m_buttonSelection; /* +0x23A0 */ betype<sint32> m_resultCodeForLeftButton; /* +0x23A4 */ betype<sint32> m_resultCodeForRightButton; }; struct ErrEula_t { SysAllocator<coreinit::OSMutex> mutex; uint32 regionType{0}; uint32 langType{0}; MEMPTR<coreinit::FSClient_t> fsClient; std::unique_ptr<ErrEulaInstance> errEulaInstance; AppearError currentDialog; bool homeNixSignVisible{false}; } g_errEula = {}; std::wstring GetText(uint16be* text) { std::wstringstream result; while(*text != 0) { auto c = (uint16)*text; result << static_cast<wchar_t>(c); text++; } return result.str(); } void ErrEulaCreate(void* workmem, uint32 regionType, uint32 langType, coreinit::FSClient_t* fsClient) { coreinit::OSLockMutex(&g_errEula.mutex); g_errEula.regionType = regionType; g_errEula.langType = langType; g_errEula.fsClient = fsClient; cemu_assert_debug(!g_errEula.errEulaInstance); g_errEula.errEulaInstance = std::make_unique<ErrEulaInstance>(); g_errEula.errEulaInstance->Init(); coreinit::OSUnlockMutex(&g_errEula.mutex); } void ErrEulaDestroy() { g_errEula.errEulaInstance.reset(); } // check if any dialog button was selected bool IsDecideSelectButtonError() { if(!g_errEula.errEulaInstance) return false; return g_errEula.errEulaInstance->IsDecideSelectButtonError(); } // check if left dialog button was selected bool IsDecideSelectLeftButtonError() { if(!g_errEula.errEulaInstance) return false; return g_errEula.errEulaInstance->IsDecideSelectLeftButtonError(); } // check if right dialog button was selected bool IsDecideSelectRightButtonError() { if(!g_errEula.errEulaInstance) return false; return g_errEula.errEulaInstance->IsDecideSelectRightButtonError(); } sint32 GetResultCode() { if(!g_errEula.errEulaInstance) return -1; return g_errEula.errEulaInstance->GetResultCode(); } ResultType GetResultType() { if(!g_errEula.errEulaInstance) return ResultType::None; return g_errEula.errEulaInstance->GetResultType(); } void export_AppearHomeNixSign(PPCInterpreter_t* hCPU) { g_errEula.homeNixSignVisible = TRUE; osLib_returnFromFunction(hCPU, 0); } void ErrEulaAppearError(AppearArg* arg) { g_errEula.currentDialog = *arg; if(g_errEula.errEulaInstance) g_errEula.errEulaInstance->DoAppearError(arg); } void ErrEulaDisappearError() { if(g_errEula.errEulaInstance) g_errEula.errEulaInstance->DoDisappearError(); } ErrEulaState ErrEulaGetStateErrorViewer() { if(!g_errEula.errEulaInstance) return ErrEulaState::Hidden; return g_errEula.errEulaInstance->GetState(); } void export_ChangeLang(PPCInterpreter_t* hCPU) { ppcDefineParamU32(langType, 0); g_errEula.langType = langType; osLib_returnFromFunction(hCPU, 0); } void export_IsAppearHomeNixSign(PPCInterpreter_t* hCPU) { osLib_returnFromFunction(hCPU, g_errEula.homeNixSignVisible); } void export_DisappearHomeNixSign(PPCInterpreter_t* hCPU) { g_errEula.homeNixSignVisible = FALSE; osLib_returnFromFunction(hCPU, 0); } void ErrEulaCalc(ControllerInfo_t* controllerInfo) { if(g_errEula.errEulaInstance) g_errEula.errEulaInstance->DoCalc(); } void render(bool mainWindow) { if(!g_errEula.errEulaInstance) return; if(g_errEula.errEulaInstance->GetState() != ErrEulaState::Visible && g_errEula.errEulaInstance->GetState() != ErrEulaState::Appearing && g_errEula.errEulaInstance->GetState() != ErrEulaState::Disappearing) return; const AppearError& appearArg = g_errEula.currentDialog; std::string text; const uint32 errorCode = (uint32)appearArg.errorCode; if (errorCode != 0) { const uint32 errorCodeHigh = errorCode / 10000; const uint32 errorCodeLow = errorCode % 10000; text = fmt::format("Error-Code: {:03}-{:04}\n", errorCodeHigh, errorCodeLow); } auto font = ImGui_GetFont(32.0f); if (!font) return; const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings; auto& io = ImGui::GetIO(); ImVec2 position = { io.DisplaySize.x / 2.0f, io.DisplaySize.y / 2.0f }; ImVec2 pivot = { 0.5f, 0.5f }; ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(0.9f); ImGui::PushFont(font); std::string title; if (appearArg.title) title = boost::nowide::narrow(GetText(appearArg.title.GetPtr())); if (title.empty()) // ImGui doesn't allow empty titles, so set one if appearArg.title is not set or empty title = "ErrEula"; float fadeTransparency = 1.0f; if (g_errEula.errEulaInstance->GetState() == ErrEulaState::Appearing || g_errEula.errEulaInstance->GetState() == ErrEulaState::Disappearing) { fadeTransparency = g_errEula.errEulaInstance->GetFadeTransparency(); } float originalAlpha = ImGui::GetStyle().Alpha; ImGui::GetStyle().Alpha = fadeTransparency; ImGui::SetNextWindowBgAlpha(0.9f * fadeTransparency); if (ImGui::Begin(title.c_str(), nullptr, kPopupFlags)) { const float startx = ImGui::GetWindowSize().x / 2.0f; bool hasLeftButtonPressed = false, hasRightButtonPressed = false; switch (appearArg.errorType) { default: { // TODO layout based on error code ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); ImGui::Spacing(); ImGui::SetCursorPosX(startx - 50); hasLeftButtonPressed = ImGui::Button("OK", {100, 0}); break; } case ErrorDialogType::Text: { std::string txtTmp = "Unknown Error"; if (appearArg.text) txtTmp = boost::nowide::narrow(GetText(appearArg.text.GetPtr())); text += txtTmp; ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); ImGui::Spacing(); ImGui::SetCursorPosX(startx - 50); hasLeftButtonPressed = ImGui::Button("OK", { 100, 0 }); break; } case ErrorDialogType::TextOneButton: { std::string txtTmp = "Unknown Error"; if (appearArg.text) txtTmp = boost::nowide::narrow(GetText(appearArg.text.GetPtr())); text += txtTmp; ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); ImGui::Spacing(); std::string button1 = "Yes"; if (appearArg.button1Text) button1 = boost::nowide::narrow(GetText(appearArg.button1Text.GetPtr())); float width = std::max(100.0f, ImGui::CalcTextSize(button1.c_str()).x + 10.0f); ImGui::SetCursorPosX(startx - (width / 2.0f)); hasLeftButtonPressed = ImGui::Button(button1.c_str(), { width, 0 }); break; } case ErrorDialogType::TextTwoButton: { std::string txtTmp = "Unknown Error"; if (appearArg.text) txtTmp = boost::nowide::narrow(GetText(appearArg.text.GetPtr())); text += txtTmp; ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); ImGui::Spacing(); std::string button1 = "Yes"; if (appearArg.button1Text) button1 = boost::nowide::narrow(GetText(appearArg.button1Text.GetPtr())); std::string button2 = "No"; if (appearArg.button2Text) button2 = boost::nowide::narrow(GetText(appearArg.button2Text.GetPtr())); float width1 = std::max(100.0f, ImGui::CalcTextSize(button1.c_str()).x + 10.0f); float width2 = std::max(100.0f, ImGui::CalcTextSize(button2.c_str()).x + 10.0f); ImGui::SetCursorPosX(startx - (width1 / 2.0f) - (width2 / 2.0f) - 10); hasLeftButtonPressed = ImGui::Button(button1.c_str(), { width1, 0 }); ImGui::SameLine(); hasRightButtonPressed = ImGui::Button(button2.c_str(), { width2, 0 }); break; } } if (!g_errEula.errEulaInstance->IsDecideSelectButtonError()) { if (hasLeftButtonPressed) g_errEula.errEulaInstance->SetButtonSelection(ErrEulaInstance::BUTTON_SELECTION::LEFT); if (hasRightButtonPressed) g_errEula.errEulaInstance->SetButtonSelection(ErrEulaInstance::BUTTON_SELECTION::RIGHT); } } ImGui::End(); ImGui::PopFont(); ImGui::GetStyle().Alpha = originalAlpha; } class : public COSModule { public: std::string_view GetName() override { return "erreula"; } void RPLMapped() override { cafeExportRegisterFunc(ErrEulaCreate, "erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10RegionTypeQ3_2nn7erreula8LangTypeP8FSClient", LogType::Placeholder); cafeExportRegisterFunc(ErrEulaDestroy, "erreula", "ErrEulaDestroy__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(IsDecideSelectButtonError, "erreula", "ErrEulaIsDecideSelectButtonError__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(IsDecideSelectLeftButtonError, "erreula", "ErrEulaIsDecideSelectLeftButtonError__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(IsDecideSelectRightButtonError, "erreula", "ErrEulaIsDecideSelectRightButtonError__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(GetResultCode, "erreula", "ErrEulaGetResultCode__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(GetResultType, "erreula", "ErrEulaGetResultType__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(ErrEulaAppearError, "erreula", "ErrEulaAppearError__3RplFRCQ3_2nn7erreula9AppearArg", LogType::Placeholder); cafeExportRegisterFunc(ErrEulaDisappearError, "erreula", "ErrEulaDisappearError__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(ErrEulaGetStateErrorViewer, "erreula", "ErrEulaGetStateErrorViewer__3RplFv", LogType::Placeholder); cafeExportRegisterFunc(ErrEulaCalc, "erreula", "ErrEulaCalc__3RplFRCQ3_2nn7erreula14ControllerInfo", LogType::Placeholder); osLib_addFunction("erreula", "ErrEulaAppearHomeNixSign__3RplFRCQ3_2nn7erreula14HomeNixSignArg", export_AppearHomeNixSign); osLib_addFunction("erreula", "ErrEulaChangeLang__3RplFQ3_2nn7erreula8LangType", export_ChangeLang); osLib_addFunction("erreula", "ErrEulaIsAppearHomeNixSign__3RplFv", export_IsAppearHomeNixSign); osLib_addFunction("erreula", "ErrEulaDisappearHomeNixSign__3RplFv", export_DisappearHomeNixSign); } void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { g_errEula.errEulaInstance.reset(); OSInitMutexEx(&g_errEula.mutex, nullptr); } else if (reason == coreinit::RplEntryReason::Unloaded) { g_errEula.errEulaInstance.reset(); // todo - refactor and clean up these variables to be part of errEulaInstance g_errEula.regionType = 0; g_errEula.langType = 0; g_errEula.fsClient = nullptr; g_errEula.currentDialog = {}; g_errEula.homeNixSignVisible = {}; g_errEula.currentDialog = {}; } } }s_COSErreulaModule; COSModule* GetModule() { return &s_COSErreulaModule; } } } ================================================ FILE: src/Cafe/OS/libs/erreula/erreula.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace nn { namespace erreula { void render(bool mainWindow); COSModule* GetModule(); } } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "GX2.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/CafeSystem.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "GX2_Command.h" #include "GX2_State.h" #include "GX2_Memory.h" #include "GX2_Event.h" #include "GX2_Shader.h" #include "GX2_Blit.h" #include "GX2_Draw.h" #include "GX2_Query.h" #include "GX2_Misc.h" #include "GX2_Surface.h" #include "GX2_Surface_Copy.h" #include "GX2_Texture.h" #include <cinttypes> #define GX2_TV_RENDER_NONE 0 #define GX2_TV_RENDER_480 1 #define GX2_TV_RENDER_480_WIDE 2 #define GX2_TV_RENDER_720 3 #define GX2_TV_RENDER_720I 4 #define GX2_TV_RENDER_1080 5 #define GX2_TV_RENDER_COUNT 6 struct { sint32 width; sint32 height; }tvScanBufferResolutions[GX2_TV_RENDER_COUNT] = { 0,0, 640,480, 854,480, 1280,720, 1280,720, 1920,1080 }; uint64 lastSwapTime = 0; void gx2Export_GX2SwapScanBuffers(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SwapScanBuffers()"); bool isPokken = false; uint64 titleId = CafeSystem::GetForegroundTitleId(); if (titleId == 0x00050000101DF500ull || titleId == 0x00050000101C5800ull || titleId == 0x00050000101DF400ull) isPokken = true; if (isPokken) GX2::GX2DrawDone(); GX2::GX2ReserveCmdSpace(5+2); uint64 tick64 = PPCInterpreter_getMainCoreCycleCounter() / 20ULL; lastSwapTime = tick64; // count flip request // is this updated via a PM4 MEM_WRITE operation? // Orochi Warriors seems to call GX2SwapScanBuffers on arbitrary threads/cores. The PM4 commands should go through to the GPU as long as there is no active display list and no other core is submitting commands simultaneously // right now, we work around this by avoiding the infinite loop below (request counter incremented, but PM4 not sent) uint32 coreIndex = coreinit::OSGetCoreId(); if (GX2::sGX2MainCoreIndex == coreIndex) LatteGPUState.sharedArea->flipRequestCountBE = _swapEndianU32(_swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE) + 1); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_REQUEST_SWAP_BUFFERS, 1)); gx2WriteGather_submitU32AsBE(0); // reserved // swap frames gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_TRIGGER_SCANBUFFER_SWAP, 1)); gx2WriteGather_submitU32AsBE(0); // reserved // wait for flip if the CPU is too far ahead // doing it after swap request is how the actual console does it, but that still causes issues in Pokken while ((sint32)(_swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE) - _swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE)) > 5) { GX2::GX2WaitForFlip(); } osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2CopyColorBufferToScanBuffer(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2CopyColorBufferToScanBuffer(0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4]); GX2::GX2ReserveCmdSpace(10); // todo: proper implementation GX2ColorBuffer* colorBuffer = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER, 9)); gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr)); gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.width); gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.height); gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.pitch); gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value()); gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.swizzle); gx2WriteGather_submitU32AsBE((uint32)colorBuffer->viewFirstSlice); gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value()); gx2WriteGather_submitU32AsBE(hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2WaitForFreeScanBuffer(PPCInterpreter_t* hCPU) { // todo: proper implementation debug_printf("GX2WaitForFreeScanBuffer(): Unimplemented\n"); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2GetCurrentScanBuffer(PPCInterpreter_t* hCPU) { // todo: proper implementation uint32 scanTarget = hCPU->gpr[3]; GX2ColorBuffer* colorBufferBE = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); memset(colorBufferBE, 0x00, sizeof(GX2ColorBuffer)); colorBufferBE->surface.width = 100; colorBufferBE->surface.height = 100; // note: For now we abuse the tiling aperture memory area as framebuffer pointers if( scanTarget == GX2_SCAN_TARGET_TV ) { colorBufferBE->surface.imagePtr = MEMORY_TILINGAPERTURE_AREA_ADDR+0x200000; } else if( scanTarget == GX2_SCAN_TARGET_DRC_FIRST ) { colorBufferBE->surface.imagePtr = MEMORY_TILINGAPERTURE_AREA_ADDR+0x40000; } osLib_returnFromFunction(hCPU, 0); } void coreinitExport_GX2GetSystemTVScanMode(PPCInterpreter_t* hCPU) { // 1080p = 7 osLib_returnFromFunction(hCPU, 7); } void coreinitExport_GX2GetSystemTVAspectRatio(PPCInterpreter_t* hCPU) { osLib_returnFromFunction(hCPU, 1); // 16:9 } void gx2Export_GX2TempGetGPUVersion(PPCInterpreter_t* hCPU) { osLib_returnFromFunction(hCPU, 2); } void _GX2InitScanBuffer(GX2ColorBuffer* colorBuffer, sint32 width, sint32 height, Latte::E_GX2SURFFMT format) { colorBuffer->surface.resFlag = GX2_RESFLAG_USAGE_TEXTURE | GX2_RESFLAG_USAGE_COLOR_BUFFER; colorBuffer->surface.width = width; colorBuffer->surface.height = height; colorBuffer->viewFirstSlice = 0; colorBuffer->viewNumSlices = 1; colorBuffer->viewMip = 0; colorBuffer->surface.numLevels = 1; colorBuffer->surface.dim = Latte::E_DIM::DIM_2D; colorBuffer->surface.swizzle = 0; colorBuffer->surface.depth = 1; colorBuffer->surface.tileMode = Latte::E_GX2TILEMODE::TM_LINEAR_GENERAL; colorBuffer->surface.format = format; colorBuffer->surface.mipPtr = MPTR_NULL; colorBuffer->surface.aa = 0; GX2::GX2CalcSurfaceSizeAndAlignment(&colorBuffer->surface); colorBuffer->surface.resFlag = GX2_RESFLAG_USAGE_TEXTURE | GX2_RESFLAG_USAGE_COLOR_BUFFER | GX2_RESFLAG_USAGE_SCAN_BUFFER; } void gx2Export_GX2CalcTVSize(PPCInterpreter_t* hCPU) { uint32 tvRenderMode = hCPU->gpr[3]; Latte::E_GX2SURFFMT format = (Latte::E_GX2SURFFMT)hCPU->gpr[4]; uint32 bufferingMode = hCPU->gpr[5]; uint32 outputSizeMPTR = hCPU->gpr[6]; uint32 outputScaleNeededMPTR = hCPU->gpr[7]; cemu_assert(tvRenderMode < GX2_TV_RENDER_COUNT); uint32 width = tvScanBufferResolutions[tvRenderMode].width; uint32 height = tvScanBufferResolutions[tvRenderMode].height; GX2ColorBuffer colorBuffer; memset(&colorBuffer, 0, sizeof(GX2ColorBuffer)); _GX2InitScanBuffer(&colorBuffer, width, height, format); uint32 imageSize = colorBuffer.surface.imageSize; uint32 alignment = colorBuffer.surface.alignment; uint32 alignmentPaddingSize = (alignment - (imageSize%alignment)) % alignment; uint32 uknMult = 1; // probably for interlaced? if (tvRenderMode == GX2_TV_RENDER_720I) uknMult = 2; uint32 adjustedBufferingMode = bufferingMode; if (tvRenderMode < GX2_TV_RENDER_720) adjustedBufferingMode = 4; uint32 bufferedImageSize = (imageSize + alignmentPaddingSize) * adjustedBufferingMode; bufferedImageSize = bufferedImageSize * uknMult - alignmentPaddingSize; memory_writeU32(outputSizeMPTR, bufferedImageSize); memory_writeU32(outputScaleNeededMPTR, 0); // todo osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2CalcDRCSize(PPCInterpreter_t* hCPU) { ppcDefineParamS32(drcMode, 0); ppcDefineParamU32(format, 1); ppcDefineParamU32(bufferingMode, 2); ppcDefineParamMPTR(sizeMPTR, 3); ppcDefineParamMPTR(scaleNeededMPTR, 4); uint32 width = 0; uint32 height = 0; if (drcMode > 0) { width = 854; height = 480; } GX2ColorBuffer colorBuffer = {}; memset(&colorBuffer, 0, sizeof(colorBuffer)); _GX2InitScanBuffer(&colorBuffer, width, height, (Latte::E_GX2SURFFMT)format); uint32 imageSize = colorBuffer.surface.imageSize; uint32 alignment = colorBuffer.surface.alignment; uint32 alignmentPaddingSize = (alignment - (imageSize%alignment)) % alignment; uint32 adjustedBufferingMode = bufferingMode; uint32 bufferedImageSize = (imageSize + alignmentPaddingSize) * adjustedBufferingMode; bufferedImageSize = bufferedImageSize - alignmentPaddingSize; memory_writeU32(sizeMPTR, bufferedImageSize); memory_writeU32(scaleNeededMPTR, 0); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetDRCScale(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetDRCScale({},{})", hCPU->gpr[3], hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetDRCConnectCallback(PPCInterpreter_t* hCPU) { ppcDefineParamS32(channel, 0); ppcDefineParamMEMPTR(callback, void, 1); cemuLog_log(LogType::GX2, "GX2SetDRCConnectCallback({}, 0x{:08x})", channel, callback.GetMPTR()); if(callback.GetPtr()) PPCCoreCallback(callback, channel, TRUE); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetSemaphore(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetSemaphore(0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamMPTR(semaphoreMPTR, 0); ppcDefineParamS32(mode, 1); uint32 SEM_SEL; if (mode == 0) { // wait SEM_SEL = 7; } else if (mode == 1) { // signal SEM_SEL = 6; } else { cemu_assert_debug(false); osLib_returnFromFunction(hCPU, 0); return; } uint32 semaphoreControl = (SEM_SEL << 29); semaphoreControl |= 0x1000; // WAIT_ON_SIGNAL gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_SEMAPHORE, 2)); gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(semaphoreMPTR)); // semaphore physical address gx2WriteGather_submitU32AsBE(semaphoreControl); // control osLib_returnFromFunction(hCPU, 0); } namespace GX2 { class : public COSModule { public: std::vector<std::string_view> GetDependencies() override { return {"avm", "coreinit", "tcl"}; } std::string_view GetName() override { return "gx2"; } void RPLMapped() override { osLib_addFunction("gx2", "GX2GetContextStateDisplayList", gx2Export_GX2GetContextStateDisplayList); // swap, vsync & timing osLib_addFunction("gx2", "GX2SwapScanBuffers", gx2Export_GX2SwapScanBuffers); osLib_addFunction("gx2", "GX2GetSwapStatus", gx2Export_GX2GetSwapStatus); osLib_addFunction("gx2", "GX2CopyColorBufferToScanBuffer", gx2Export_GX2CopyColorBufferToScanBuffer); osLib_addFunction("gx2", "GX2WaitForFreeScanBuffer", gx2Export_GX2WaitForFreeScanBuffer); osLib_addFunction("gx2", "GX2GetCurrentScanBuffer", gx2Export_GX2GetCurrentScanBuffer); // shader stuff osLib_addFunction("gx2", "GX2SetPixelShader", gx2Export_GX2SetPixelShader); osLib_addFunction("gx2", "GX2SetGeometryShader", gx2Export_GX2SetGeometryShader); osLib_addFunction("gx2", "GX2SetComputeShader", gx2Export_GX2SetComputeShader); osLib_addFunction("gx2", "GX2SetVertexUniformBlock", gx2Export_GX2SetVertexUniformBlock); osLib_addFunction("gx2", "GX2RSetVertexUniformBlock", gx2Export_GX2RSetVertexUniformBlock); osLib_addFunction("gx2", "GX2SetPixelUniformBlock", gx2Export_GX2SetPixelUniformBlock); osLib_addFunction("gx2", "GX2SetGeometryUniformBlock", gx2Export_GX2SetGeometryUniformBlock); osLib_addFunction("gx2", "GX2SetShaderModeEx", gx2Export_GX2SetShaderModeEx); osLib_addFunction("gx2", "GX2CalcGeometryShaderInputRingBufferSize", gx2Export_GX2CalcGeometryShaderInputRingBufferSize); osLib_addFunction("gx2", "GX2CalcGeometryShaderOutputRingBufferSize", gx2Export_GX2CalcGeometryShaderOutputRingBufferSize); // color/depth buffers osLib_addFunction("gx2", "GX2InitColorBufferRegs", gx2Export_GX2InitColorBufferRegs); osLib_addFunction("gx2", "GX2InitDepthBufferRegs", gx2Export_GX2InitDepthBufferRegs); osLib_addFunction("gx2", "GX2SetColorBuffer", gx2Export_GX2SetColorBuffer); osLib_addFunction("gx2", "GX2SetDepthBuffer", gx2Export_GX2SetDepthBuffer); osLib_addFunction("gx2", "GX2SetDRCBuffer", gx2Export_GX2SetDRCBuffer); osLib_addFunction("gx2", "GX2MarkScanBufferCopied", gx2Export_GX2MarkScanBufferCopied); // misc osLib_addFunction("gx2", "GX2TempGetGPUVersion", gx2Export_GX2TempGetGPUVersion); osLib_addFunction("gx2", "GX2CalcTVSize", gx2Export_GX2CalcTVSize); osLib_addFunction("gx2", "GX2CalcDRCSize", gx2Export_GX2CalcDRCSize); osLib_addFunction("gx2", "GX2SetDRCScale", gx2Export_GX2SetDRCScale); osLib_addFunction("gx2", "GX2SetDRCConnectCallback", gx2Export_GX2SetDRCConnectCallback); osLib_addFunction("gx2", "GX2GetSystemTVScanMode", coreinitExport_GX2GetSystemTVScanMode); osLib_addFunction("gx2", "GX2GetSystemTVAspectRatio", coreinitExport_GX2GetSystemTVAspectRatio); osLib_addFunction("gx2", "GX2SetSwapInterval", gx2Export_GX2SetSwapInterval); osLib_addFunction("gx2", "GX2GetSwapInterval", gx2Export_GX2GetSwapInterval); osLib_addFunction("gx2", "GX2GetGPUTimeout", gx2Export_GX2GetGPUTimeout); osLib_addFunction("gx2", "GX2SampleTopGPUCycle", gx2Export_GX2SampleTopGPUCycle); osLib_addFunction("gx2", "GX2SampleBottomGPUCycle", gx2Export_GX2SampleBottomGPUCycle); osLib_addFunction("gx2", "GX2AllocateTilingApertureEx", gx2Export_GX2AllocateTilingApertureEx); osLib_addFunction("gx2", "GX2FreeTilingAperture", gx2Export_GX2FreeTilingAperture); // context state osLib_addFunction("gx2", "GX2SetDefaultState", gx2Export_GX2SetDefaultState); osLib_addFunction("gx2", "GX2SetupContextStateEx", gx2Export_GX2SetupContextStateEx); osLib_addFunction("gx2", "GX2SetContextState", gx2Export_GX2SetContextState); // semaphore osLib_addFunction("gx2", "GX2SetSemaphore", gx2Export_GX2SetSemaphore); GX2::GX2MemInit(); GX2::GX2ResourceInit(); GX2::GX2CommandInit(); GX2::GX2SurfaceInit(); GX2::GX2SurfaceCopyInit(); GX2::GX2TextureInit(); GX2::GX2StateInit(); GX2::GX2ShaderInit(); GX2::GX2EventInit(); GX2::GX2BlitInit(); GX2::GX2DrawInit(); GX2::GX2StreamoutInit(); GX2::GX2QueryInit(); GX2::GX2MiscInit(); }; }s_COSGX2Module; COSModule* GetModule() { return &s_COSGX2Module; } } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2.h ================================================ #pragma once #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/OS/RPL/COSModule.h" // base defines for GX2 #define GX2_TRUE 1 #define GX2_FALSE 0 #define GX2_ENABLE 1 #define GX2_DISABLE 0 #include "GX2_Surface.h" // general namespace GX2 { COSModule* GetModule(); } // shader void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU); void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU); void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU); void gx2Export_GX2SetVertexUniformBlock(PPCInterpreter_t* hCPU); void gx2Export_GX2RSetVertexUniformBlock(PPCInterpreter_t* hCPU); void gx2Export_GX2SetPixelUniformBlock(PPCInterpreter_t* hCPU); void gx2Export_GX2SetGeometryUniformBlock(PPCInterpreter_t* hCPU); void gx2Export_GX2SetShaderModeEx(PPCInterpreter_t* hCPU); void gx2Export_GX2CalcGeometryShaderInputRingBufferSize(PPCInterpreter_t* hCPU); void gx2Export_GX2CalcGeometryShaderOutputRingBufferSize(PPCInterpreter_t* hCPU); // write gather / command queue #define GX2_COMMAND_RING_BUFFER_SIZE (64*1024*1024) // 64MB void gx2Export_GX2GetContextStateDisplayList(PPCInterpreter_t* hCPU); #include "GX2_Command.h" // misc void gx2Export_GX2AllocateTilingApertureEx(PPCInterpreter_t* hCPU); void gx2Export_GX2FreeTilingAperture(PPCInterpreter_t* hCPU); void gx2Export_GX2SetSwapInterval(PPCInterpreter_t* hCPU); void gx2Export_GX2GetSwapInterval(PPCInterpreter_t* hCPU); void gx2Export_GX2GetSwapStatus(PPCInterpreter_t* hCPU); void gx2Export_GX2GetGPUTimeout(PPCInterpreter_t* hCPU); void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU); void gx2Export_GX2SampleBottomGPUCycle(PPCInterpreter_t* hCPU); // color/depth buffers #define GX2_SCAN_TARGET_TV 1 #define GX2_SCAN_TARGET_TV_RIGH 2 #define GX2_SCAN_TARGET_DRC_FIRST 4 #define GX2_SCAN_TARGET_DRC_SECOND 8 void gx2Export_GX2InitColorBufferRegs(PPCInterpreter_t* hCPU); void gx2Export_GX2InitDepthBufferRegs(PPCInterpreter_t* hCPU); void gx2Export_GX2SetColorBuffer(PPCInterpreter_t* hCPU); void gx2Export_GX2SetDepthBuffer(PPCInterpreter_t* hCPU); void gx2Export_GX2SetDRCBuffer(PPCInterpreter_t* hCPU); void gx2Export_GX2MarkScanBufferCopied(PPCInterpreter_t* hCPU); // special state #define GX2_SPECIAL_STATE_COUNT 9 // context state void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU); void gx2Export_GX2SetupContextStateEx(PPCInterpreter_t* hCPU); void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU); ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_AddrTest.cpp ================================================ #include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" #include "util/highresolutiontimer/HighResolutionTimer.h" namespace GX2 { struct AddrCreate_INPUT { /* +0x00 */ uint32be structSize; /* +0x04 */ uint32be ukn04_maybeGen; /* +0x08 */ uint32be ukn08; /* +0x0C */ uint32be revision; /* +0x10 */ uint32be func_Alloc; /* +0x14 */ uint32be func_Free; /* +0x18 */ uint32be func_Debug; /* +0x1C */ uint32be ukn1C; /* +0x20 */ uint32be reg263C; /* +0x24 */ uint32be ukn24; /* +0x28 */ uint32be ukn28; /* +0x2C */ uint32be ukn2C; /* +0x30 */ uint32be ukn30; /* +0x34 */ uint32be ukn34; /* +0x38 */ uint32be ukn38; /* +0x3C */ uint32be ukn3C; /* +0x40 */ uint32be ukn40; }; struct AddrCreate_OUTPUT { uint32be structSize; MEMPTR<void> addrLibPtr; }; static_assert(sizeof(AddrCreate_INPUT) == 0x44); static_assert(sizeof(AddrCreate_OUTPUT) == 8); struct ADDRAllocParam { uint32be ukn00; // alignment? uint32be ukn04; uint32be size; }; struct ADDRComputeSurfaceInfo_INPUT { uint32be structSize; betype<Latte::E_HWTILEMODE> tileMode; betype<Latte::E_HWSURFFMT> format; uint32be bpp; uint32be numSamples; uint32be width; uint32be height; uint32be numSlices; uint32be slice; uint32be mipLevel; uint32be _flags; uint32be numFrags; MEMPTR<void> tileInfo; uint32be tileType; uint32be tileIndex; enum FLAG_BITS { FLAG_BIT_CUBE = (1 << 27), FLAG_BIT_VOLUME = (1 << 26), FLAG_BIT_OPT4SPACE = (1 << 19), }; void SetFlagCube(bool f) { if (f) _flags |= FLAG_BIT_CUBE; else _flags &= ~FLAG_BIT_CUBE; } void SetFlagVolume(bool f) { if (f) _flags |= FLAG_BIT_VOLUME; else _flags &= ~FLAG_BIT_VOLUME; } void SetFlagOpt4Space(bool f) { if (f) _flags |= FLAG_BIT_OPT4SPACE; else _flags &= ~FLAG_BIT_OPT4SPACE; } }; static_assert(sizeof(ADDRComputeSurfaceInfo_INPUT) == 0x3C); struct ADDRComputeSurfaceInfo_OUTPUT { /* 0x00 */ uint32be structSize; /* 0x04 */ uint32be pitch; /* 0x08 */ uint32be height; /* 0x0C */ uint32be depth; /* 0x10 */ uint64be surfSize; /* 0x18 */ uint32be tileMode; /* 0x1C */ uint32be baseAlign; /* 0x20 */ uint32be pitchAlign; /* 0x24 */ uint32be heightAlign; /* 0x28 */ uint32be depthAlign; /* 0x2C */ uint32be bpp; /* 0x30 */ uint32be pixelPitch; /* 0x34 */ uint32be pixelHeight; /* 0x38 */ uint32be pixelBits; /* 0x3C */ uint32be sliceSize; /* 0x40 */ uint32be pitchTileMax; /* 0x44 */ uint32be heightTileMax; /* 0x48 */ uint32be sliceTileMax; /* 0x4C */ MEMPTR<void> tileInfo; /* 0x50 */ uint32be tileType; /* 0x54 */ uint32be tileIndex; /* 0x58 */ MEMPTR<void> stereoInfo; /* 0x5C */ uint32be _padding; }; static_assert(sizeof(ADDRComputeSurfaceInfo_OUTPUT) == 0x60); static void _cb_alloc(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(param, ADDRAllocParam, 0); uint32 r = coreinit_allocFromSysArea(param->size, 0x10); osLib_returnFromFunction(hCPU, r); } static void _cb_free(PPCInterpreter_t* hCPU) { cemu_assert_unimplemented(); } static void _cb_debug(PPCInterpreter_t* hCPU) { cemu_assert_unimplemented(); } static void* sAddrLib{}; static uint32be tclFunc_AddrCreate = 0; static uint32be tclFunc_AddrComputeSurfaceInfo = 0; void _TestAddrLib_Init() { // load tcl_addr_test.rpl (from /cafelibs/) uint32be tclHandle; uint32 r = coreinit::OSDynLoad_Acquire("tcl_addr_test.rpl", &tclHandle); cemu_assert_debug(r == 0); // get imports r = coreinit::OSDynLoad_FindExport(tclHandle, 0, "AddrCreate", &tclFunc_AddrCreate); cemu_assert_debug(r == 0); r = coreinit::OSDynLoad_FindExport(tclHandle, 0, "AddrComputeSurfaceInfo", &tclFunc_AddrComputeSurfaceInfo); cemu_assert_debug(r == 0); // call AddrCreate StackAllocator<AddrCreate_INPUT> addrCreateIn; memset(addrCreateIn.GetPointer(), 0, sizeof(addrCreateIn)); addrCreateIn->structSize = sizeof(addrCreateIn); addrCreateIn->ukn04_maybeGen = 6; // R600? addrCreateIn->ukn08 = 0x51; addrCreateIn->revision = 71; addrCreateIn->reg263C = 0x44902; addrCreateIn->ukn24 = 0; // ukn addrCreateIn->func_Alloc = PPCInterpreter_makeCallableExportDepr(_cb_alloc); addrCreateIn->func_Free = PPCInterpreter_makeCallableExportDepr(_cb_free); addrCreateIn->func_Debug = PPCInterpreter_makeCallableExportDepr(_cb_debug); StackAllocator<AddrCreate_OUTPUT> addrCreateOut; memset(addrCreateOut.GetPointer(), 0, sizeof(addrCreateOut)); addrCreateOut->structSize = sizeof(addrCreateOut); r = PPCCoreCallback((uint32)tclFunc_AddrCreate, addrCreateIn.GetPointer(), addrCreateOut.GetPointer()); sAddrLib = addrCreateOut->addrLibPtr; cemu_assert_debug(r == 0 && sAddrLib != nullptr); } void _TestAddrLib_CalculateSurfaceInfo(Latte::E_GX2SURFFMT surfaceFormat, uint32 surfaceWidth, uint32 surfaceHeight, uint32 surfaceDepth, Latte::E_DIM surfaceDim, Latte::E_GX2TILEMODE surfaceTileMode, uint32 surfaceAA, uint32 level, ADDRComputeSurfaceInfo_OUTPUT* paramOut) { StackAllocator<ADDRComputeSurfaceInfo_INPUT> _paramIn; ADDRComputeSurfaceInfo_INPUT& paramIn = *_paramIn.GetPointer(); memset(¶mIn, 0, sizeof(ADDRComputeSurfaceInfo_INPUT)); memset(paramOut, 0, sizeof(ADDRComputeSurfaceInfo_OUTPUT)); Latte::E_HWSURFFMT hwFormat = GetHWFormat(surfaceFormat); if (surfaceTileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL) { uint32 numSamples = 1 << surfaceAA; uint32 blockSize = IsCompressedFormat(surfaceFormat) ? 4 : 1; uint32 width = ((surfaceWidth >> level) + blockSize - 1) & ~(blockSize - 1); paramOut->bpp = GetFormatBits(hwFormat); paramOut->structSize = sizeof(ADDRComputeSurfaceInfo_OUTPUT); paramOut->pitch = width / blockSize; paramOut->pixelBits = paramOut->bpp; paramOut->baseAlign = 1; paramOut->pitchAlign = 1; paramOut->heightAlign = 1; paramOut->depthAlign = 1; switch (surfaceDim) { case Latte::E_DIM::DIM_1D: paramOut->height = 1; paramOut->depth = 1; break; case Latte::E_DIM::DIM_2D: paramOut->height = std::max<uint32>(surfaceHeight >> level, 1); paramOut->depth = 1; break; case Latte::E_DIM::DIM_3D: paramOut->height = surfaceHeight >> level; paramOut->height = std::max<uint32>(paramOut->height, 1); paramOut->depth = std::max<uint32>(surfaceDepth >> level, 1); break; case Latte::E_DIM::DIM_CUBEMAP: paramOut->height = std::max<uint32>(surfaceHeight >> level, 1); paramOut->depth = std::max<uint32>(surfaceDepth, 6); break; case Latte::E_DIM::DIM_1D_ARRAY: paramOut->height = 1; paramOut->depth = surfaceDepth; break; case Latte::E_DIM::DIM_2D_ARRAY: paramOut->height = std::max<uint32>(surfaceHeight >> level, 1); paramOut->depth = surfaceDepth; break; default: break; } paramOut->height = ((paramOut->height + blockSize - 1) & ~(blockSize - 1)) / (uint64)blockSize; paramOut->pixelPitch = ((surfaceWidth >> level) + blockSize - 1) & ~(blockSize - 1); paramOut->pixelPitch = std::max<uint32>(paramOut->pixelPitch, blockSize); paramOut->pixelHeight = ((surfaceHeight >> level) + blockSize - 1) & ~(blockSize - 1); paramOut->pixelHeight = std::max<uint32>(paramOut->pixelHeight, blockSize);; paramOut->pitch = std::max<uint32>(paramOut->pitch, 1); paramOut->height = std::max<uint32>(paramOut->height, 1); paramOut->surfSize = paramOut->bpp * numSamples * paramOut->depth * paramOut->height * paramOut->pitch >> 3; if (surfaceDim == Latte::E_DIM::DIM_3D) paramOut->sliceSize = (uint32)(paramOut->surfSize); else { if (paramOut->surfSize == 0 && paramOut->depth == 0) paramOut->sliceSize = 0; // edge case for (1D)_ARRAY textures with 0/0/0 res else paramOut->sliceSize = ((uint32)paramOut->surfSize.value() / paramOut->depth); } paramOut->pitchTileMax = (paramOut->pitch >> 3) - 1; paramOut->heightTileMax = (paramOut->height >> 3) - 1; paramOut->sliceTileMax = (paramOut->height * paramOut->pitch >> 6) - 1; } else { paramIn.structSize = sizeof(paramIn); paramIn.tileMode = Latte::MakeHWTileMode(surfaceTileMode); paramIn.format = hwFormat; paramIn.bpp = GetFormatBits(hwFormat); paramIn.numSamples = 1 << surfaceAA; paramIn.numFrags = paramIn.numSamples; paramIn.width = std::max<uint32>(surfaceWidth >> level, 1); switch (surfaceDim) { case Latte::E_DIM::DIM_1D: paramIn.height = 1; paramIn.numSlices = 1; break; case Latte::E_DIM::DIM_2D: paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); paramIn.numSlices = 1; break; case Latte::E_DIM::DIM_3D: paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); paramIn.numSlices = std::max<uint32>(surfaceDepth >> level, 1); break; case Latte::E_DIM::DIM_CUBEMAP: paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); paramIn.numSlices = std::max<uint32>(surfaceDepth, 6); paramIn.SetFlagCube(true); break; case Latte::E_DIM::DIM_1D_ARRAY: paramIn.height = 1; paramIn.numSlices = surfaceDepth; break; case Latte::E_DIM::DIM_2D_ARRAY: paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); paramIn.numSlices = surfaceDepth; break; case Latte::E_DIM::DIM_2D_MSAA: paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); paramIn.numSlices = 1; break; case Latte::E_DIM::DIM_2D_ARRAY_MSAA: paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); paramIn.numSlices = surfaceDepth; break; default: break; } paramIn.slice = 0; paramIn.mipLevel = level; if (surfaceDim == Latte::E_DIM::DIM_3D) paramIn.SetFlagVolume(true); paramIn.SetFlagOpt4Space(level == 0); paramOut->structSize = sizeof(ADDRComputeSurfaceInfo_OUTPUT); PPCCoreCallback((uint32)tclFunc_AddrComputeSurfaceInfo, sAddrLib, _paramIn.GetPointer(), paramOut); } } void _TestAddrLib_Compare(uint32 surfaceWidth, uint32 surfaceHeight, uint32 surfaceDepth, Latte::E_DIM surfaceDim, Latte::E_GX2SURFFMT surfaceFormat, Latte::E_GX2TILEMODE surfaceTileMode, uint32 surfaceAA, uint32 level) { // get result from tcl.rpl StackAllocator<ADDRComputeSurfaceInfo_OUTPUT> _paramOut; ADDRComputeSurfaceInfo_OUTPUT& tclSurfInfo = *_paramOut.GetPointer(); _TestAddrLib_CalculateSurfaceInfo(surfaceFormat, surfaceWidth, surfaceHeight, surfaceDepth, surfaceDim, surfaceTileMode, surfaceAA, level, _paramOut.GetPointer()); // get result from our implementation LatteAddrLib::AddrSurfaceInfo_OUT ourSurfInfo; LatteAddrLib::GX2CalculateSurfaceInfo(surfaceFormat, surfaceWidth, surfaceHeight, surfaceDepth, surfaceDim, surfaceTileMode, surfaceAA, level, &ourSurfInfo); // compare cemu_assert(tclSurfInfo.pitchAlign == ourSurfInfo.pitchAlign); cemu_assert((Latte::E_HWTILEMODE)tclSurfInfo.tileMode.value() == ourSurfInfo.hwTileMode); cemu_assert(tclSurfInfo.baseAlign == ourSurfInfo.baseAlign); cemu_assert(tclSurfInfo.surfSize == ourSurfInfo.surfSize); cemu_assert(tclSurfInfo.depthAlign == ourSurfInfo.depthAlign); cemu_assert(tclSurfInfo.pitch == ourSurfInfo.pitch); cemu_assert(tclSurfInfo.sliceSize == ourSurfInfo.sliceSize); } void _TestAddrLib_Run() { uint32 surfaceAA = 0; std::vector<Latte::E_DIM> dimList = { Latte::E_DIM::DIM_1D, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_3D, Latte::E_DIM::DIM_CUBEMAP, Latte::E_DIM::DIM_1D_ARRAY, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D_MSAA, Latte::E_DIM::DIM_2D_ARRAY_MSAA }; std::vector<Latte::E_GX2TILEMODE> tilemodeList = { // linear Latte::E_GX2TILEMODE::TM_LINEAR_GENERAL, Latte::E_GX2TILEMODE::TM_LINEAR_ALIGNED, // micro tiled Latte::E_GX2TILEMODE::TM_1D_TILED_THIN1, Latte::E_GX2TILEMODE::TM_1D_TILED_THICK, // macro tiled Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1, Latte::E_GX2TILEMODE::TM_2D_TILED_THIN4, Latte::E_GX2TILEMODE::TM_2D_TILED_THIN2, Latte::E_GX2TILEMODE::TM_2D_TILED_THICK, Latte::E_GX2TILEMODE::TM_2B_TILED_THIN1, Latte::E_GX2TILEMODE::TM_2B_TILED_THIN2, Latte::E_GX2TILEMODE::TM_2B_TILED_THIN4, Latte::E_GX2TILEMODE::TM_2B_TILED_THICK, Latte::E_GX2TILEMODE::TM_3D_TILED_THIN1, Latte::E_GX2TILEMODE::TM_3D_TILED_THICK, Latte::E_GX2TILEMODE::TM_3B_TILED_THIN1, Latte::E_GX2TILEMODE::TM_3B_TILED_THICK, // special Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL, Latte::E_GX2TILEMODE::TM_32_SPECIAL, // note: Specific to GX2CalcSurfaceSizeAndAlignment, for AddrLib this should just be interpreted as (tm&0xF) }; std::vector<Latte::E_GX2SURFFMT> formatList = { Latte::E_GX2SURFFMT::HWFMT_8, Latte::E_GX2SURFFMT::HWFMT_8_8, Latte::E_GX2SURFFMT::HWFMT_8_8_8_8, // 8, 16, 32 Latte::E_GX2SURFFMT::R32_UINT, Latte::E_GX2SURFFMT::R32_G32_UINT, Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT, // 32, 64, 128 Latte::E_GX2SURFFMT::HWFMT_BC1, Latte::E_GX2SURFFMT::HWFMT_BC2, Latte::E_GX2SURFFMT::HWFMT_BC3, Latte::E_GX2SURFFMT::HWFMT_BC4, Latte::E_GX2SURFFMT::HWFMT_BC5 }; std::vector<uint32> resXYList = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 31, 32, 33, 50, 63, 64, 65, 127, 128, 129, 200, 253, 254, 255, 256, 257, 511, 512, 513, 1023, 1024, 1025, 2047, 2048, 2049, 4095, 4096, 4097 }; debug_printf("Running AddrLib test...\n"); BenchmarkTimer timer; timer.Start(); size_t index = 0; for (auto dim : dimList) { debug_printf("%d/%d\n", (int)index, (int)dimList.size()); index++; for (auto tileMode : tilemodeList) { for (auto format : formatList) { for (uint32 level = 0; level < 16; level++) { for (auto depth : resXYList) { for (auto height : resXYList) { for (auto width : resXYList) { _TestAddrLib_Compare(width, height, depth, dim, format, tileMode, surfaceAA, level); } } } } } } } timer.Stop(); debug_printf("Test complete (in %d seconds)\n", (int)(timer.GetElapsedMilliseconds() * 0.001)); assert_dbg(); } void _test_AddrLib() { return; _TestAddrLib_Init(); _TestAddrLib_Run(); } } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Blit.cpp ================================================ #include "Common/precompiled.h" #include "GX2_Blit.h" #include "GX2_Command.h" #include "GX2_Surface.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/OS/common/OSCommon.h" #include "GX2_Resource.h" namespace GX2 { // sets the depth/stencil clear registers and updates clear values in DepthBuffer struct void GX2SetClearDepthStencil(GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue) { GX2ReserveCmdSpace(4); depthBuffer->clearDepth = depthClearValue; depthBuffer->clearStencil = stencilClearValue; Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg; stencilClearReg.set_clearValue(stencilClearValue); Latte::LATTE_DB_DEPTH_CLEAR depthClearReg; depthClearReg.set_clearValue(depthClearValue); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 2), Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000, stencilClearReg, depthClearReg); } // similar to GX2SetClearDepthStencil but only sets depth void GX2SetClearDepth(GX2DepthBuffer* depthBuffer, float depthClearValue) { GX2ReserveCmdSpace(3); depthBuffer->clearDepth = depthClearValue; Latte::LATTE_DB_DEPTH_CLEAR depthClearReg; depthClearReg.set_clearValue(depthClearValue); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::DB_DEPTH_CLEAR - 0xA000, depthClearReg); } // similar to GX2SetClearDepthStencil but only sets stencil void GX2SetClearStencil(GX2DepthBuffer* depthBuffer, uint8 stencilClearValue) { GX2ReserveCmdSpace(3); depthBuffer->clearStencil = stencilClearValue; Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg; stencilClearReg.set_clearValue(stencilClearValue); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000, stencilClearReg); } // update DB_STENCIL_CLEAR and DB_STENCIL_CLEAR based on clear flags void _updateDepthStencilClearRegs(float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags) { if ((clearFlags & GX2ClearFlags::SET_DEPTH_REG) != 0 && (clearFlags & GX2ClearFlags::SET_STENCIL_REG) != 0) { GX2ReserveCmdSpace(4); Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg; stencilClearReg.set_clearValue(stencilClearValue); Latte::LATTE_DB_DEPTH_CLEAR depthClearReg; depthClearReg.set_clearValue(depthClearValue); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 2), Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000, stencilClearReg, depthClearReg); } else if ((clearFlags & GX2ClearFlags::SET_DEPTH_REG) != 0) { GX2ReserveCmdSpace(3); Latte::LATTE_DB_DEPTH_CLEAR depthClearReg; depthClearReg.set_clearValue(depthClearValue); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::DB_DEPTH_CLEAR - 0xA000, depthClearReg); } else if ((clearFlags & GX2ClearFlags::SET_STENCIL_REG) != 0) { GX2ReserveCmdSpace(3); Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg; stencilClearReg.set_clearValue(stencilClearValue); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000, stencilClearReg); } } void SubmitHLEClear(GX2ColorBuffer* colorBuffer, float colorRGBA[4], GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue, bool clearColor, bool clearDepth, bool clearStencil) { GX2ReserveCmdSpace(50); uint32 hleClearFlags = 0; if (clearColor) hleClearFlags |= 1; if (clearDepth) hleClearFlags |= 2; if (clearStencil) hleClearFlags |= 4; // color buffer MPTR colorPhysAddr = MPTR_NULL; uint32 colorFormat = 0; uint32 colorTileMode = 0; uint32 colorWidth = 0; uint32 colorHeight = 0; uint32 colorPitch = 0; uint32 colorFirstSlice = 0; uint32 colorNumSlices = 0; if (colorBuffer != nullptr) { colorPhysAddr = memory_virtualToPhysical(colorBuffer->surface.imagePtr); colorFormat = (uint32)colorBuffer->surface.format.value(); colorTileMode = (uint32)colorBuffer->surface.tileMode.value(); colorWidth = colorBuffer->surface.width; colorHeight = colorBuffer->surface.height; colorPitch = colorBuffer->surface.pitch; colorFirstSlice = colorBuffer->viewFirstSlice; colorNumSlices = colorBuffer->viewNumSlices; } // depth buffer MPTR depthPhysAddr = MPTR_NULL; uint32 depthFormat = 0; uint32 depthTileMode = 0; uint32 depthWidth = 0; uint32 depthHeight = 0; uint32 depthPitch = 0; uint32 depthFirstSlice = 0; uint32 depthNumSlices = 0; if (depthBuffer != nullptr) { depthPhysAddr = memory_virtualToPhysical(depthBuffer->surface.imagePtr); depthFormat = (uint32)depthBuffer->surface.format.value(); depthTileMode = (uint32)depthBuffer->surface.tileMode.value(); depthWidth = depthBuffer->surface.width; depthHeight = depthBuffer->surface.height; depthPitch = depthBuffer->surface.pitch; depthFirstSlice = depthBuffer->viewFirstSlice; depthNumSlices = depthBuffer->viewNumSlices; } gx2WriteGather_submit(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23), hleClearFlags, colorPhysAddr, colorFormat, colorTileMode, colorWidth, colorHeight, colorPitch, colorFirstSlice, colorNumSlices, depthPhysAddr, depthFormat, depthTileMode, depthWidth, depthHeight, depthPitch, depthFirstSlice, depthNumSlices, (uint32)(colorRGBA[0] * 255.0f), (uint32)(colorRGBA[1] * 255.0f), (uint32)(colorRGBA[2] * 255.0f), (uint32)(colorRGBA[3] * 255.0f), *(uint32*)&depthClearValue, stencilClearValue&0xFF); } void GX2ClearColor(GX2ColorBuffer* colorBuffer, float r, float g, float b, float a) { if ((colorBuffer->surface.resFlag & GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0) { float colorRGBA[4] = { r, g, b, a }; SubmitHLEClear(colorBuffer, colorRGBA, nullptr, 0.0f, 0, true, false, false); } else { debug_printf("GX2ClearColor() - unsupported surface flags\n"); } } void GX2ClearBuffersEx(GX2ColorBuffer* colorBuffer, GX2DepthBuffer* depthBuffer, float r, float g, float b, float a, float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags) { _updateDepthStencilClearRegs(depthClearValue, stencilClearValue, clearFlags); uint32 hleClearFlags = 0; if ((clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0) hleClearFlags |= 2; if ((clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0) hleClearFlags |= 4; hleClearFlags |= 1; float colorRGBA[4] = { r, g, b, a }; SubmitHLEClear(colorBuffer, colorRGBA, depthBuffer, depthClearValue, stencilClearValue, true, (clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0, (clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0); } // always uses passed depthClearValue/stencilClearValue for clearing, even if clear flags dont specify value updates void GX2ClearDepthStencilEx(GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags) { if (!depthBuffer && (depthBuffer->surface.width == 0 || depthBuffer->surface.height == 0)) { // Super Smash Bros tries to clear an uninitialized depth surface? debug_printf("GX2ClearDepthStencilEx(): Attempting to clear invalid depthbuffer\n"); return; } _updateDepthStencilClearRegs(depthClearValue, stencilClearValue, clearFlags); float colorRGBA[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; SubmitHLEClear(nullptr, colorRGBA, depthBuffer, depthClearValue, stencilClearValue, false, (clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0, (clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0); } void GX2BlitInit() { cafeExportRegister("gx2", GX2SetClearDepthStencil, LogType::GX2); cafeExportRegister("gx2", GX2SetClearDepth, LogType::GX2); cafeExportRegister("gx2", GX2SetClearStencil, LogType::GX2); cafeExportRegister("gx2", GX2ClearColor, LogType::GX2); cafeExportRegister("gx2", GX2ClearBuffersEx, LogType::GX2); cafeExportRegister("gx2", GX2ClearDepthStencilEx, LogType::GX2); } } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Blit.h ================================================ #pragma once namespace GX2 { enum class GX2ClearFlags : uint32 { CLEAR_DEPTH = 0x01, // clear depth to given clear value CLEAR_STENCIL = 0x02, // clear stencil to given stencil clear value SET_DEPTH_REG = 0x04, // SET_STENCIL_REG = 0x08, }; void GX2BlitInit(); } ENABLE_BITMASK_OPERATORS(GX2::GX2ClearFlags); ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Command.cpp ================================================ #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/OS/libs/coreinit/coreinit.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/TCL/TCL.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "GX2.h" #include "GX2_Command.h" #include "GX2_Shader.h" #include "GX2_Misc.h" #include "OS/libs/coreinit/coreinit_MEM.h" namespace GX2 { GX2PerCoreCBState s_perCoreCBState[Espresso::CORE_COUNT]; } void gx2WriteGather_submitU32AsBE(uint32 v) { uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); if (GX2::s_perCoreCBState[coreIndex].currentWritePtr == nullptr) return; *(uint32*)(GX2::s_perCoreCBState[coreIndex].currentWritePtr) = _swapEndianU32(v); GX2::s_perCoreCBState[coreIndex].currentWritePtr++; cemu_assert_debug(GX2::s_perCoreCBState[coreIndex].currentWritePtr <= (GX2::s_perCoreCBState[coreIndex].bufferPtr + GX2::s_perCoreCBState[coreIndex].bufferSizeInU32s)); } void gx2WriteGather_submitU32AsLE(uint32 v) { uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); if (GX2::s_perCoreCBState[coreIndex].currentWritePtr == nullptr) return; *(uint32*)(GX2::s_perCoreCBState[coreIndex].currentWritePtr) = v; GX2::s_perCoreCBState[coreIndex].currentWritePtr++; cemu_assert_debug(GX2::s_perCoreCBState[coreIndex].currentWritePtr <= (GX2::s_perCoreCBState[coreIndex].bufferPtr + GX2::s_perCoreCBState[coreIndex].bufferSizeInU32s)); } void gx2WriteGather_submitU32AsLEArray(uint32* v, uint32 numValues) { uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); if (GX2::s_perCoreCBState[coreIndex].currentWritePtr == nullptr) return; memcpy_dwords(GX2::s_perCoreCBState[coreIndex].currentWritePtr, v, numValues); GX2::s_perCoreCBState[coreIndex].currentWritePtr += numValues; cemu_assert_debug(GX2::s_perCoreCBState[coreIndex].currentWritePtr <= (GX2::s_perCoreCBState[coreIndex].bufferPtr + GX2::s_perCoreCBState[coreIndex].bufferSizeInU32s)); } namespace GX2 { struct GX2CommandState // mapped to PPC space since the GPU writes here { // command pool MEMPTR<uint32be> commandPoolBase; uint32 commandPoolSizeInU32s; MEMPTR<uint32be> gpuCommandReadPtr; // timestamp uint64be lastSubmissionTime; }; SysAllocator<GX2CommandState> s_commandState; GX2PerCoreCBState s_mainCoreLastCommandState; bool s_cbBufferIsInternallyAllocated; void GX2Command_StartNewCommandBuffer(uint32 numU32s); // called from GX2Init. Allocates a 4MB memory chunk from which command buffers are suballocated from void GX2Init_commandBufferPool(void* bufferBase, uint32 bufferSize) { cemu_assert_debug(!s_commandState->commandPoolBase); // should not be allocated already // setup command buffer pool. If not provided allocate a 4MB or custom size buffer uint32 poolSize = bufferSize ? bufferSize : 0x400000; // 4MB (can be overwritten by custom GX2Init parameters?) if (bufferBase) { s_commandState->commandPoolBase = (uint32be*)bufferBase; s_cbBufferIsInternallyAllocated = false; } else { s_commandState->commandPoolBase = (uint32be*)coreinit::_weak_MEMAllocFromDefaultHeapEx(poolSize, 0x100); s_cbBufferIsInternallyAllocated = true; } if (!s_commandState->commandPoolBase) { cemuLog_log(LogType::Force, "GX2: Failed to allocate command buffer pool"); } s_commandState->commandPoolSizeInU32s = poolSize / sizeof(uint32be); s_commandState->gpuCommandReadPtr = s_commandState->commandPoolBase; // init per-core command buffer state for (uint32 i = 0; i < Espresso::CORE_COUNT; i++) { s_perCoreCBState[i].bufferPtr = nullptr; s_perCoreCBState[i].bufferSizeInU32s = 0; s_perCoreCBState[i].currentWritePtr = nullptr; } // start first command buffer for main core GX2Command_StartNewCommandBuffer(0x100); } void GX2Shutdown_commandBufferPool() { if (!s_commandState->commandPoolBase) return; if (s_cbBufferIsInternallyAllocated) coreinit::_weak_MEMFreeToDefaultHeap(s_commandState->commandPoolBase.GetPtr()); s_cbBufferIsInternallyAllocated = false; s_commandState->commandPoolBase = nullptr; s_commandState->commandPoolSizeInU32s = 0; s_commandState->gpuCommandReadPtr = nullptr; } // current position of where the GPU is reading from. Updated via a memory write command submitted to the GPU uint32 GX2Command_GetPoolGPUReadIndex() { stdx::atomic_ref<MEMPTR<uint32be>> _readPtr(s_commandState->gpuCommandReadPtr); MEMPTR<uint32be> currentReadPtr = _readPtr.load(); cemu_assert_debug(currentReadPtr); return (uint32)(currentReadPtr.GetPtr() - s_commandState->commandPoolBase.GetPtr()); } void GX2Command_WaitForNextBufferRetired() { uint64 retiredTimeStamp = GX2GetRetiredTimeStamp(); retiredTimeStamp += 1; // but cant be higher than the submission timestamp stdx::atomic_ref<uint64be> _lastSubmissionTime(s_commandState->lastSubmissionTime); uint64 submissionTimeStamp = _lastSubmissionTime.load(); if (retiredTimeStamp > submissionTimeStamp) retiredTimeStamp = submissionTimeStamp; GX2WaitTimeStamp(retiredTimeStamp); } void GX2Command_SetupCoreCommandBuffer(uint32be* buffer, uint32 sizeInU32s, bool isDisplayList) { uint32 coreIndex = coreinit::OSGetCoreId(); auto& coreCBState = s_perCoreCBState[coreIndex]; coreCBState.bufferPtr = buffer; coreCBState.bufferSizeInU32s = sizeInU32s; coreCBState.currentWritePtr = buffer; coreCBState.isDisplayList = isDisplayList; } void GX2Command_StartNewCommandBuffer(uint32 numU32s) { // On submission command buffers are padded to 32 byte alignment // but nowhere is it guaranteed that internal command buffers have their size aligned to 32 byte (even on console, but testing is required) // Thus the padding can write out of bounds but this seems to trigger only very rarely in partice. As a workaround we always pad the command buffer size to 32 bytes here numU32s = (numU32s + 7) & ~0x7; uint32 coreIndex = coreinit::OSGetCoreId(); auto& coreCBState = s_perCoreCBState[coreIndex]; numU32s = std::max<uint32>(numU32s, 0x100); // grab space from command buffer pool and if necessary wait for it uint32be* bufferPtr = nullptr; uint32 bufferSizeInU32s = 0; uint32 readIndex; while (true) { // try to grab buffer data from first available spot: // 1. At the current write location up to the end of the buffer (avoiding an overlap with the read location) // 2. From the start of the buffer up to the read location readIndex = GX2Command_GetPoolGPUReadIndex(); uint32be* nextWritePos = coreCBState.bufferPtr ? coreCBState.bufferPtr + coreCBState.bufferSizeInU32s : s_commandState->commandPoolBase.GetPtr(); uint32 writeIndex = nextWritePos - s_commandState->commandPoolBase; uint32 poolSizeInU32s = s_commandState->commandPoolSizeInU32s; // readIndex == writeIndex can mean either buffer full or buffer empty // we could use GX2GetRetiredTimeStamp() == GX2GetLastSubmittedTimeStamp() to determine if the buffer is truly empty // but this can have false negatives since the last submission timestamp is updated independently of the read index // so instead we just avoid ever filling the buffer completely cemu_assert_debug(readIndex < poolSizeInU32s); cemu_assert_debug(writeIndex < poolSizeInU32s); if (writeIndex < readIndex) { // writeIndex has wrapped around uint32 wordsAvailable = readIndex - writeIndex; if (wordsAvailable > 0) wordsAvailable--; // avoid writeIndex becoming equal to readIndex if (wordsAvailable >= numU32s) { bufferPtr = s_commandState->commandPoolBase + writeIndex; bufferSizeInU32s = wordsAvailable; break; } } else { uint32 wordsAvailable = poolSizeInU32s - writeIndex; if (wordsAvailable > 0) wordsAvailable--; // avoid writeIndex becoming equal to readIndex if (wordsAvailable >= numU32s) { bufferPtr = nextWritePos; bufferSizeInU32s = wordsAvailable; break; } // not enough space at end of buffer, try to grab from the beginning of the buffer wordsAvailable = readIndex; if (wordsAvailable > 0) wordsAvailable--; // avoid writeIndex becoming equal to readIndex if (wordsAvailable >= numU32s) { bufferPtr = s_commandState->commandPoolBase; bufferSizeInU32s = wordsAvailable; break; } } GX2Command_WaitForNextBufferRetired(); } cemu_assert_debug(bufferPtr); bufferSizeInU32s = std::min<uint32>(numU32s, 0x20000); // size cap #ifdef CEMU_DEBUG_ASSERT uint32 newWriteIndex = ((bufferPtr - s_commandState->commandPoolBase) + bufferSizeInU32s) % s_commandState->commandPoolSizeInU32s; cemu_assert_debug(newWriteIndex != readIndex); #endif // setup buffer and make it the current write gather target cemu_assert_debug(bufferPtr >= s_commandState->commandPoolBase && (bufferPtr + bufferSizeInU32s) <= s_commandState->commandPoolBase + s_commandState->commandPoolSizeInU32s); GX2Command_SetupCoreCommandBuffer(bufferPtr, bufferSizeInU32s, false); } void GX2Command_SubmitCommandBuffer(uint32be* buffer, uint32 sizeInU32s, MEMPTR<uint32be>* completionGPUReadPointer, bool triggerMarkerInterrupt) { uint32be cmd[10]; uint32 cmdLen = 4; cmd[0] = pm4HeaderType3(IT_INDIRECT_BUFFER_PRIV, 3); cmd[1] = memory_virtualToPhysical(MEMPTR<void>(buffer).GetMPTR()); cmd[2] = 0x00000000; // address high bits cmd[3] = sizeInU32s; if (completionGPUReadPointer) { // append command to update completionGPUReadPointer after the GPU is done with the command buffer cmd[4] = pm4HeaderType3(IT_MEM_WRITE, 4); cmd[5] = memory_virtualToPhysical(MEMPTR<void>(completionGPUReadPointer).GetMPTR()) | 2; cmd[6] = 0x40000; cmd[7] = MEMPTR<void>(buffer + sizeInU32s).GetMPTR(); // value to write cmd[8] = 0x00000000; cmdLen = 9; } betype<TCL::TCLSubmissionFlag> submissionFlags{}; if (!triggerMarkerInterrupt) submissionFlags |= TCL::TCLSubmissionFlag::NO_MARKER_INTERRUPT; submissionFlags |= TCL::TCLSubmissionFlag::USE_RETIRED_MARKER; TCL::TCLSubmitToRing(cmd, cmdLen, &submissionFlags, &s_commandState->lastSubmissionTime); } void GX2Command_PadCurrentBuffer() { uint32 coreIndex = coreinit::OSGetCoreId(); auto& coreCBState = s_perCoreCBState[coreIndex]; if (!coreCBState.currentWritePtr) return; uint32 writeDistance = (uint32)(coreCBState.currentWritePtr - coreCBState.bufferPtr); if ((writeDistance&7) != 0) { uint32 distanceToPad = 0x8 - (writeDistance & 0x7); while (distanceToPad) { *coreCBState.currentWritePtr = pm4HeaderType2Filler(); coreCBState.currentWritePtr++; distanceToPad--; } } } void GX2Command_Flush(uint32 numU32sForNextBuffer, bool triggerMarkerInterrupt) { uint32 coreIndex = coreinit::OSGetCoreId(); auto& coreCBState = s_perCoreCBState[coreIndex]; if (coreCBState.isDisplayList) { // display list cemu_assert_debug((uint32)(coreCBState.currentWritePtr - coreCBState.bufferPtr) < coreCBState.bufferSizeInU32s); cemuLog_logDebugOnce(LogType::Force, "GX2 flush called on display list"); } else { // command buffer if (coreCBState.currentWritePtr != coreCBState.bufferPtr) { // pad the command buffer to 32 byte alignment GX2Command_PadCurrentBuffer(); // submit it to the GPU uint32 bufferLength = (uint32)(coreCBState.currentWritePtr - coreCBState.bufferPtr); cemu_assert_debug(bufferLength <= coreCBState.bufferSizeInU32s); GX2Command_SubmitCommandBuffer(coreCBState.bufferPtr, bufferLength, &s_commandState->gpuCommandReadPtr, triggerMarkerInterrupt); GX2Command_StartNewCommandBuffer(numU32sForNextBuffer); } else { // current buffer is empty so we dont need to queue it if (numU32sForNextBuffer > s_commandState->commandPoolSizeInU32s) GX2Command_StartNewCommandBuffer(numU32sForNextBuffer); } } } void GX2Flush() { GX2Command_Flush(256, true); } uint64 GX2GetLastSubmittedTimeStamp() { stdx::atomic_ref<uint64be> _lastSubmissionTime(s_commandState->lastSubmissionTime); return _lastSubmissionTime.load(); } uint64 GX2GetRetiredTimeStamp() { uint64be ts = 0; TCL::TCLTimestamp(TCL::TCLTimestampId::TIMESTAMP_LAST_BUFFER_RETIRED, &ts); return ts; } bool GX2WaitTimeStamp(uint64 tsWait) { // handle GPU timeout here? But for now we timeout after 60 seconds TCL::TCLWaitTimestamp(TCL::TCLTimestampId::TIMESTAMP_LAST_BUFFER_RETIRED, tsWait, Espresso::TIMER_CLOCK * 60); return true; } /* * Guarantees that the requested amount of space is available on the current command buffer * If the space is not available, the current command buffer is pushed to the GPU and a new one is allocated */ void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32) { uint32 coreIndex = coreinit::OSGetCoreId(); auto& coreCBState = s_perCoreCBState[coreIndex]; if (coreCBState.currentWritePtr == nullptr) return; uint32 writeDistance = (uint32)(coreCBState.currentWritePtr - coreCBState.bufferPtr); if (writeDistance + reservedFreeSpaceInU32 > coreCBState.bufferSizeInU32s) { GX2Command_Flush(reservedFreeSpaceInU32, true); } } void GX2WriteGather_beginDisplayList(PPCInterpreter_t* hCPU, MPTR buffer, uint32 maxSize) { uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); if (coreIndex == sGX2MainCoreIndex) { GX2Command_PadCurrentBuffer(); cemu_assert_debug(!s_perCoreCBState[coreIndex].isDisplayList); s_mainCoreLastCommandState = s_perCoreCBState[coreIndex]; } GX2Command_SetupCoreCommandBuffer(MEMPTR<uint32be>(buffer), maxSize/4, true); } uint32 GX2WriteGather_getDisplayListWriteDistance(sint32 coreIndex) { auto& coreCBState = s_perCoreCBState[coreIndex]; cemu_assert_debug(coreCBState.isDisplayList); if (coreCBState.currentWritePtr == nullptr) return 0; return (uint32)(coreCBState.currentWritePtr - coreCBState.bufferPtr) * 4; } uint32 GX2WriteGather_endDisplayList(PPCInterpreter_t* hCPU, MPTR buffer) { uint32 coreIndex = coreinit::OSGetCoreId(); auto& coreCBState = s_perCoreCBState[coreIndex]; GX2Command_PadCurrentBuffer(); uint32 finalWriteIndex = coreCBState.currentWritePtr - coreCBState.bufferPtr; cemu_assert_debug(finalWriteIndex <= coreCBState.bufferSizeInU32s); // if we are on the main GX2 core then restore the GPU command buffer if (coreIndex == sGX2MainCoreIndex) { coreCBState = s_mainCoreLastCommandState; } else { coreCBState.bufferPtr = nullptr; coreCBState.currentWritePtr = nullptr; coreCBState.bufferSizeInU32s = 0; coreCBState.isDisplayList = false; } return finalWriteIndex * 4; } bool GX2GetCurrentDisplayList(MEMPTR<uint32be>* displayListAddr, uint32be* displayListSize) { uint32 coreIndex = coreinit::OSGetCoreId(); auto& coreCBState = s_perCoreCBState[coreIndex]; if (!coreCBState.isDisplayList) return false; if (displayListAddr) *displayListAddr = coreCBState.bufferPtr; if (displayListSize) *displayListSize = coreCBState.bufferSizeInU32s * sizeof(uint32be); return true; } // returns true if we are writing to a display list bool GX2GetDisplayListWriteStatus() { uint32 coreIndex = coreinit::OSGetCoreId(); return s_perCoreCBState[coreIndex].isDisplayList; } void GX2BeginDisplayList(MEMPTR<void> displayListAddr, uint32 size) { GX2WriteGather_beginDisplayList(PPCInterpreter_getCurrentInstance(), displayListAddr.GetMPTR(), size); } void GX2BeginDisplayListEx(MEMPTR<void> displayListAddr, uint32 size, bool profiling) { GX2WriteGather_beginDisplayList(PPCInterpreter_getCurrentInstance(), displayListAddr.GetMPTR(), size); } uint32 GX2EndDisplayList(MEMPTR<void> displayListAddr) { cemu_assert_debug(displayListAddr != nullptr); uint32 displayListSize = GX2WriteGather_endDisplayList(PPCInterpreter_getCurrentInstance(), displayListAddr.GetMPTR()); return displayListSize; } void GX2CallDisplayList(MPTR addr, uint32 size) { cemu_assert_debug((size&3) == 0); // write PM4 command GX2ReserveCmdSpace(4); gx2WriteGather_submit(pm4HeaderType3(IT_INDIRECT_BUFFER_PRIV, 3), memory_virtualToPhysical(addr), 0, // high address bits size / 4); } void GX2DirectCallDisplayList(void* addr, uint32 size) { // this API submits to TCL directly and bypasses write-gatherer // its basically a way to manually submit a command buffer to the GPU uint32 coreIndex = coreinit::OSGetCoreId(); if (coreIndex != sGX2MainCoreIndex) { cemuLog_logDebugOnce(LogType::Force, "GX2DirectCallDisplayList() called on non-main GX2 core"); } if (!s_perCoreCBState[coreIndex].isDisplayList) { // make sure any preceeding commands are submitted first GX2Command_Flush(0x100, false); } GX2Command_SubmitCommandBuffer(static_cast<uint32be*>(addr), size / 4, nullptr, false); } void GX2CopyDisplayList(MEMPTR<uint32be*> addr, uint32 size) { // copy display list to write gather uint32* displayListDWords = (uint32*)addr.GetPtr(); uint32 dwordCount = size / 4; if (dwordCount > 0) { GX2ReserveCmdSpace(dwordCount); gx2WriteGather_submitU32AsLEArray(displayListDWords, dwordCount); } } enum class GX2_PATCH_TYPE : uint32 { FETCH_SHADER = 1, VERTEX_SHADER = 2, GEOMETRY_COPY_SHADER = 3, GEOMETRY_SHADER = 4, PIXEL_SHADER = 5, COMPUTE_SHADER = 6 }; void GX2PatchDisplayList(uint32be* displayData, GX2_PATCH_TYPE patchType, uint32 patchOffset, void* obj) { cemu_assert_debug((patchOffset & 3) == 0); if (patchType == GX2_PATCH_TYPE::VERTEX_SHADER) { GX2VertexShader* vertexShader = (GX2VertexShader*)obj; displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(vertexShader->GetProgramAddr()) >> 8; } else if (patchType == GX2_PATCH_TYPE::PIXEL_SHADER) { GX2PixelShader_t* pixelShader = (GX2PixelShader_t*)obj; displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(pixelShader->GetProgramAddr()) >> 8; } else if (patchType == GX2_PATCH_TYPE::FETCH_SHADER) { GX2FetchShader* fetchShader = (GX2FetchShader*)obj; displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(fetchShader->GetProgramAddr()) >> 8; } else if (patchType == GX2_PATCH_TYPE::GEOMETRY_COPY_SHADER) { GX2GeometryShader_t* geometryShader = (GX2GeometryShader_t*)obj; displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(geometryShader->GetCopyProgramAddr()) >> 8; } else if (patchType == GX2_PATCH_TYPE::GEOMETRY_SHADER) { GX2GeometryShader_t* geometryShader = (GX2GeometryShader_t*)obj; displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(geometryShader->GetGeometryProgramAddr()) >> 8; } else { cemuLog_log(LogType::Force, "GX2PatchDisplayList(): unsupported patchType {}", (uint32)patchType); cemu_assert_debug(false); } } void GX2CommandInit() { cafeExportRegister("gx2", GX2Flush, LogType::GX2); cafeExportRegister("gx2", GX2GetLastSubmittedTimeStamp, LogType::GX2); cafeExportRegister("gx2", GX2GetRetiredTimeStamp, LogType::GX2); cafeExportRegister("gx2", GX2WaitTimeStamp, LogType::GX2); cafeExportRegister("gx2", GX2BeginDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2BeginDisplayListEx, LogType::GX2); cafeExportRegister("gx2", GX2EndDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2GetCurrentDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2GetDisplayListWriteStatus, LogType::GX2); cafeExportRegister("gx2", GX2CallDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2DirectCallDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2CopyDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2PatchDisplayList, LogType::GX2); } void GX2CommandResetToDefaultState() { s_commandState->commandPoolBase = nullptr; s_commandState->commandPoolSizeInU32s = 0; s_commandState->gpuCommandReadPtr = nullptr; s_cbBufferIsInternallyAllocated = false; } } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Command.h ================================================ #pragma once #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Espresso/Const.h" namespace GX2 { struct GX2PerCoreCBState { uint32be* bufferPtr; uint32 bufferSizeInU32s; uint32be* currentWritePtr; bool isDisplayList; }; extern GX2PerCoreCBState s_perCoreCBState[Espresso::CORE_COUNT]; }; void gx2WriteGather_submitU32AsBE(uint32 v); void gx2WriteGather_submitU32AsLE(uint32 v); void gx2WriteGather_submitU32AsLEArray(uint32* v, uint32 numValues); uint32 PPCInterpreter_getCurrentCoreIndex(); // gx2WriteGather_submit functions template <typename ...Targs> inline void gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr) { GX2::s_perCoreCBState[coreIndex].currentWritePtr = writePtr; cemu_assert_debug(GX2::s_perCoreCBState[coreIndex].currentWritePtr <= (GX2::s_perCoreCBState[coreIndex].bufferPtr + GX2::s_perCoreCBState[coreIndex].bufferSizeInU32s)); } template <typename T, typename ...Targs> inline void gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const betype<T>& arg, Targs... args) { static_assert(sizeof(betype<T>) == sizeof(uint32be)); *(betype<T>*)writePtr = arg; writePtr++; gx2WriteGather_submit_(coreIndex, writePtr, args...); } template <typename T, typename ...Targs> requires std::is_floating_point_v<T> inline void gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const T& arg, Targs... args) { static_assert(sizeof(T) == sizeof(uint32)); *writePtr = *(uint32*)&arg; writePtr++; gx2WriteGather_submit_(coreIndex, writePtr, args...); } template <typename T, typename ...Targs> requires std::is_base_of_v<Latte::LATTEREG, T> inline void gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const T& arg, Targs... args) { static_assert(sizeof(Latte::LATTEREG) == sizeof(uint32be)); *writePtr = arg.getRawValue(); writePtr++; gx2WriteGather_submit_(coreIndex, writePtr, args...); } template <typename T, typename ...Targs> requires (!std::is_base_of_v<Latte::LATTEREG, T>) && (!std::is_floating_point_v<T>) inline void gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const T& arg, Targs... args) { *writePtr = arg; writePtr++; gx2WriteGather_submit_(coreIndex, writePtr, args...); } template <typename ...Targs> inline void gx2WriteGather_submit(Targs... args) { uint32 coreIndex = PPCInterpreter_getCurrentCoreIndex(); if (GX2::s_perCoreCBState[coreIndex].currentWritePtr == nullptr) { cemu_assert_suspicious(); // writing to command buffer without valid write pointer? return; } uint32be* writePtr = GX2::s_perCoreCBState[coreIndex].currentWritePtr; gx2WriteGather_submit_(coreIndex, writePtr, std::forward<Targs>(args)...); } namespace GX2 { void GX2Command_Flush(uint32 numU32sForNextBuffer, bool triggerMarkerInterrupt = true); void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32); uint64 GX2GetLastSubmittedTimeStamp(); uint64 GX2GetRetiredTimeStamp(); bool GX2WaitTimeStamp(uint64 tsWait); void GX2BeginDisplayList(MEMPTR<void> displayListAddr, uint32 size); void GX2BeginDisplayListEx(MEMPTR<void> displayListAddr, uint32 size, bool profiling); uint32 GX2EndDisplayList(MEMPTR<void> displayListAddr); void GX2CallDisplayList(MPTR addr, uint32 size); void GX2DirectCallDisplayList(void* addr, uint32 size); bool GX2GetDisplayListWriteStatus(); void GX2CommandInit(); void GX2Init_commandBufferPool(void* bufferBase, uint32 bufferSize); void GX2Shutdown_commandBufferPool(); void GX2CommandResetToDefaultState(); } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_ContextState.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "GX2.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "GX2_Command.h" #include "GX2_State.h" #include "Cafe/CafeSystem.h" #define GPU7_REG_AREA_SIZE_CONFIG_REG 0xB00 #define GPU7_REG_AREA_SIZE_CONTEXT_REG 0x400 #define GPU7_REG_AREA_SIZE_ALU_CONST 0x800 #define GPU7_REG_AREA_SIZE_LOOP_CONST 0x60 #define GPU7_REG_AREA_SIZE_RESOURCE 0xD9E #define GPU7_REG_AREA_SIZE_SAMPLER 0xA2 // (guessed) #define _GX2_CALC_SHADOWMEM_NUM_U32(__v) (((((__v)*4)+0xFF)&~0xFF)/4) MPTR gx2CurrentContextStateMPTR = MPTR_NULL; typedef struct { uint32 regOffset; uint32 regCount; }GX2RegLoadPktEntry_t; GX2RegLoadPktEntry_t aluConst_loadPktEntries[1] = // base: 0xC000 { {0, 0x800}, }; GX2RegLoadPktEntry_t loopConst_loadPktEntries[1] = // base: 0xF880 { {0, 0x60}, }; GX2RegLoadPktEntry_t samplerReg_loadPktEntries[3] = // base: 0xF000 { {0, 0x36}, {0x36, 0x36}, {0x6C, 0x36}, }; GX2RegLoadPktEntry_t configReg_loadPktEntries[0xF] = // base: 0x2000 { {0x300, 0x6}, {0x900, 0x48}, {0x980, 0x48}, {0xA00, 0x48}, {0x310, 0xC}, {0x542, 0x1}, {0x235, 0x1}, {0x232, 0x2}, {0x23A, 0x1}, {0x256, 0x1}, {0x60C, 0x1}, {0x5C5, 0x1}, {0x2C8, 0x1}, {0x363, 0x1}, {0x404, 0x2} }; GX2RegLoadPktEntry_t contextReg_loadPktEntries[0x2D] = // base: 0xA000 { {0x0, 0x2}, {0x3, 0x3}, {0xA, 0x4}, {0x10, 0x38}, {0x50, 0x34}, {0x8E, 0x4}, {0x94, 0x40}, {0x100, 0x9}, {0x10C, 0x3}, {0x10F, 0x60}, {0x185, 0xA}, {0x191, 0x27}, {0x1E0, 0x9}, {0x200, 0x1}, {0x202, 0x7}, {0xE0, 0x20}, {0x210, 0x29}, {0x250, 0x34}, {0x290, 0x1}, {0x292, 0x2}, {0x2A1, 0x1}, {0x2A5, 0x1}, {0x2A8, 0x2}, {0x2AC, 0x3}, {0x2CA, 0x1}, {0x2CC, 0x1}, {0x2CE, 0x1}, {0x300, 0x9}, {0x30C, 0x1}, {0x312, 0x1}, {0x316, 0x2}, {0x343, 0x2}, {0x349, 0x3}, {0x34C, 0x2}, {0x351, 0x1}, {0x37E, 0x6}, {0x2B4, 0x3}, {0x2B8, 0x3}, {0x2BC, 0x3}, {0x2C0, 0x3}, {0x2C8, 0x1}, {0x29B, 0x1}, {0x8C, 0x1}, {0xD5, 0x1}, {0x284, 0xC} }; GX2RegLoadPktEntry_t resourceReg_loadPktEntries[9] = // base: 0xE000 { {0, 0x70}, // ps tex {0x380, 0x70}, {0x460, 0x70}, // vs tex {0x7E0, 0x70}, {0x8B9, 0x7}, {0x8C0, 0x70}, {0x930, 0x70}, // gs tex {0xCB0, 0x70}, {0xD89, 0x7} }; typedef struct { // Hardware view of context state (register areas) uint32 areaConfigReg[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_CONFIG_REG)]; uint32 areaContextReg[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_CONTEXT_REG)]; uint32 areaALUConst[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_ALU_CONST)]; uint32 areaLoopConst[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_LOOP_CONST)]; uint32 areaResource[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_RESOURCE)]; uint32 areaSampler[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_SAMPLER)]; }GX2HwContextState_t; typedef struct { GX2HwContextState_t hwContext; uint32 enableProfling; /* + 0x9804 */ uint32be loadDL_size; uint8 ukn9808[0x3FC-4]; uint8 ukn9C00[0x200]; /* +0x9E00 */ uint8 loadDL_buffer[0x300]; // this displaylist caches the IT_LOAD_* commands for swapping out context }GX2ContextState_t; static_assert(offsetof(GX2ContextState_t, loadDL_size) == 0x9804); static_assert(sizeof(GX2ContextState_t) == 0xA100); uint32 _GX2Context_CalcShadowMemSize(uint32 regCount) { return (regCount*4+0xFF)&~0xFF; } uint32 _GX2Context_CalcStateSize() { uint32 contextStateSize = 0; contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_CONFIG_REG); contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_CONTEXT_REG); contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_ALU_CONST); contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_LOOP_CONST); contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_RESOURCE); contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_SAMPLER); return contextStateSize; } void _GX2Context_CreateLoadDL() { GX2::GX2ReserveCmdSpace(3); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_CONTEXT_CONTROL, 2)); gx2WriteGather_submitU32AsBE(0x80000077); gx2WriteGather_submitU32AsBE(0x80000077); } void _GX2Context_WriteCmdDisableStateShadowing() { GX2::GX2ReserveCmdSpace(3); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_CONTEXT_CONTROL, 2)); gx2WriteGather_submitU32AsBE(0x80000000); gx2WriteGather_submitU32AsBE(0x80000000); } void _GX2Context_cmdLoad(void* gx2ukn, uint32 pm4Header, MPTR physAddrRegArea, uint32 waitForIdle, uint32 numRegOffsetEntries, GX2RegLoadPktEntry_t* regOffsetEntries) { GX2::GX2ReserveCmdSpace(3 + numRegOffsetEntries*2); gx2WriteGather_submitU32AsBE(pm4Header); gx2WriteGather_submitU32AsBE(physAddrRegArea); gx2WriteGather_submitU32AsBE(waitForIdle); for(uint32 i=0; i<numRegOffsetEntries; i++) { gx2WriteGather_submitU32AsBE(regOffsetEntries[i].regOffset); // regOffset gx2WriteGather_submitU32AsBE(regOffsetEntries[i].regCount); // regCount } } #define __cmdStateLoad(__gx2State, __pm4Command, __regArea, __waitForIdle, __regLoadPktEntries) _GX2Context_cmdLoad(NULL, pm4HeaderType3(__pm4Command, 2+sizeof(__regLoadPktEntries)/sizeof(__regLoadPktEntries[0])*2), memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(__regArea)), __waitForIdle, sizeof(__regLoadPktEntries)/sizeof(__regLoadPktEntries[0]), __regLoadPktEntries) void _GX2Context_WriteCmdRestoreState(GX2ContextState_t* gx2ContextState, uint32 ukn) { MPTR physAddrContextState = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(gx2ContextState)); _GX2Context_CreateLoadDL(); __cmdStateLoad(NULL, IT_LOAD_CONFIG_REG, gx2ContextState->hwContext.areaConfigReg, 0x80000000, configReg_loadPktEntries); __cmdStateLoad(NULL, IT_LOAD_CONTEXT_REG, gx2ContextState->hwContext.areaContextReg, 0, contextReg_loadPktEntries); __cmdStateLoad(NULL, IT_LOAD_ALU_CONST, gx2ContextState->hwContext.areaALUConst, 0, aluConst_loadPktEntries); __cmdStateLoad(NULL, IT_LOAD_LOOP_CONST, gx2ContextState->hwContext.areaLoopConst, 0, loopConst_loadPktEntries); __cmdStateLoad(NULL, IT_LOAD_RESOURCE, gx2ContextState->hwContext.areaResource, 0, resourceReg_loadPktEntries); __cmdStateLoad(NULL, IT_LOAD_SAMPLER, gx2ContextState->hwContext.areaSampler, 0, samplerReg_loadPktEntries); } void GX2SetDefaultState() { GX2::GX2ReserveCmdSpace(0x100); Latte::LATTE_PA_CL_VTE_CNTL reg{}; reg.set_VPORT_X_OFFSET_ENA(true).set_VPORT_X_SCALE_ENA(true); reg.set_VPORT_Y_OFFSET_ENA(true).set_VPORT_Y_SCALE_ENA(true); reg.set_VPORT_Z_OFFSET_ENA(true).set_VPORT_Z_SCALE_ENA(true); reg.set_VTX_W0_FMT(true); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::PA_CL_VTE_CNTL - 0xA000, reg); uint32 stencilTestEnable = GX2_FALSE; uint32 backStencilEnable = GX2_FALSE; uint32 frontStencilFunc = 0; uint32 frontStencilZPass = 0; uint32 frontStencilZFail = 0; uint32 frontStencilFail = 0; uint32 backStencilFunc = 0; uint32 backStencilZPass = 0; uint32 backStencilZFail = 0; uint32 backStencilFail = 0; uint32 depthControlReg = 0; // depth stuff depthControlReg |= (1<<1); depthControlReg |= (1<<2); depthControlReg |= ((1&7)<<4); // stencil stuff depthControlReg |= ((stencilTestEnable&1)<<0); depthControlReg |= ((backStencilEnable&1)<<7); depthControlReg |= ((frontStencilFunc&7)<<8); depthControlReg |= ((frontStencilZPass&7)<<14); depthControlReg |= ((frontStencilZFail&7)<<17); depthControlReg |= ((frontStencilFail&7)<<11); depthControlReg |= ((backStencilFunc&7)<<20); depthControlReg |= ((backStencilZPass&7)<<26); depthControlReg |= ((backStencilZFail&3)<<29); depthControlReg |= ((backStencilFail&7)<<23); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1)); gx2WriteGather_submitU32AsBE(Latte::REGADDR::DB_DEPTH_CONTROL-0xA000); gx2WriteGather_submitU32AsBE(depthControlReg); GX2::GX2SetAlphaTest(GX2_DISABLE, GX2::GX2_ALPHAFUNC::LESS, 0.0f); GX2::GX2SetPolygonControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CCW, GX2_DISABLE, GX2_DISABLE, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE::UKN0, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::TRIANGLES, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::TRIANGLES, GX2_DISABLE, GX2_DISABLE, GX2_DISABLE); GX2::GX2SetPolygonOffset(0.0f, 0.0f, 0.0f, 0.0f, 0.0f); GX2::GX2SetPrimitiveRestartIndex(0xffffffff); GX2::GX2SetTargetChannelMasks(0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF); GX2::GX2SetBlendConstantColor(0.0f, 0.0f, 0.0f, 0.0f); GX2::GX2SetPointSize(1.0f, 1.0f); GX2::GX2SetPointLimits(1.0f, 1.0f); GX2::GX2SetColorControl(GX2::GX2_LOGICOP::COPY, GX2_DISABLE, GX2_DISABLE, GX2_ENABLE); GX2::GX2SetRasterizerClipControlEx(true, true, false); // Set clear depth to 1.0 (workaround for Darksiders 2. Investigate whether actual GX2 driver also sets this) float depth = 1.0; gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1)); gx2WriteGather_submitU32AsBE(mmDB_DEPTH_CLEAR - 0xA000); gx2WriteGather_submitU32AsBE(*(uint32*)&depth); // depth (as float) // reset HLE special states for (sint32 i = 0; i <= GX2_SPECIAL_STATE_COUNT; i++) { gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SPECIAL_STATE, 2)); gx2WriteGather_submitU32AsBE(i); // state id gx2WriteGather_submitU32AsBE(0); // disable } } void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetDefaultState()"); GX2SetDefaultState(); osLib_returnFromFunction(hCPU, 0); } void _GX2ContextCreateRestoreStateDL(GX2ContextState_t* gx2ContextState) { // begin display list cemu_assert_debug(!GX2::GX2GetDisplayListWriteStatus()); // must not already be writing to a display list GX2::GX2BeginDisplayList((void*)gx2ContextState->loadDL_buffer, sizeof(gx2ContextState->loadDL_buffer)); _GX2Context_WriteCmdRestoreState(gx2ContextState, 0); uint32 displayListSize = GX2::GX2EndDisplayList((void*)gx2ContextState->loadDL_buffer); gx2ContextState->loadDL_size = displayListSize; } void gx2Export_GX2SetupContextStateEx(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetupContextStateEx(0x{:08x})", hCPU->gpr[3]); cemu_assert_debug(hCPU->gpr[4] == 0 || hCPU->gpr[4] == 1); GX2ContextState_t* gx2ContextState = (GX2ContextState_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint32 hwContextSize = _GX2Context_CalcStateSize(); if( hwContextSize != sizeof(GX2HwContextState_t) ) assert_dbg(); if( sizeof(GX2HwContextState_t) != 0x9800 ) assert_dbg(); // GX2 HW context size mismatch if( sizeof(GX2ContextState_t) != 0xA100 ) assert_dbg(); // GX2 context size mismatch memset(gx2ContextState, 0x00, sizeof(GX2ContextState_t)); gx2ContextState->enableProfling = _swapEndianU32(hCPU->gpr[4]&1); _GX2Context_WriteCmdRestoreState(gx2ContextState, 1); gx2CurrentContextStateMPTR = hCPU->gpr[3]; _GX2Context_CreateLoadDL(); GX2SetDefaultState(); _GX2ContextCreateRestoreStateDL(gx2ContextState); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetContextState(0x{:08x})", hCPU->gpr[3]); // parameters: if( hCPU->gpr[3] == MPTR_NULL ) { // disable state shadowing _GX2Context_WriteCmdDisableStateShadowing(); osLib_returnFromFunction(hCPU, 0); return; } // check if context state changed bool boiWorkaround = CafeSystem::GetRPXHashBase() == 0x6BCD618E; // workaround for a bug in Binding of Isaac to avoid flicker if( boiWorkaround ) { if( hCPU->gpr[3] != gx2CurrentContextStateMPTR ) // dont reload same state { GX2ContextState_t* gx2ContextState = (GX2ContextState_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); _GX2Context_WriteCmdRestoreState(gx2ContextState, 0); _GX2Context_CreateLoadDL(); // set new context state gx2CurrentContextStateMPTR = hCPU->gpr[3]; } else { // even if it's the same context, make sure state shadowing is enabled. _GX2Context_CreateLoadDL(); } } else { GX2ContextState_t* gx2ContextState = (GX2ContextState_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); if (gx2ContextState->loadDL_size == 0) { _GX2Context_CreateLoadDL(); _GX2Context_WriteCmdRestoreState(gx2ContextState, 0); } else { _GX2Context_CreateLoadDL(); GX2::GX2CallDisplayList(memory_getVirtualOffsetFromPointer(gx2ContextState->loadDL_buffer), gx2ContextState->loadDL_size); } // set new context state gx2CurrentContextStateMPTR = hCPU->gpr[3]; } // todo: Save/restore GX2 special state as well -> handle this by correctly emulating the state osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2GetContextStateDisplayList(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2GetContextStateDisplayList(0x{:08x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); ppcDefineParamStructPtr(gx2ContextState, GX2ContextState_t, 0); ppcDefineParamU32BEPtr(displayListPtrOut, 1); ppcDefineParamU32BEPtr(displayListSizeOut, 2); *displayListPtrOut = memory_getVirtualOffsetFromPointer(gx2ContextState->loadDL_buffer); *displayListSizeOut = gx2ContextState->loadDL_size; osLib_returnFromFunction(hCPU, 0); } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Draw.cpp ================================================ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "GX2.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/OS/common/OSCommon.h" #include "GX2_Command.h" #include "GX2_Draw.h" namespace GX2 { void GX2SetAttribBuffer(uint32 bufferIndex, uint32 sizeInBytes, uint32 stride, void* data) { GX2ReserveCmdSpace(9); MPTR physicalAddress = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(data)); // write PM4 command gx2WriteGather_submit( pm4HeaderType3(IT_SET_RESOURCE, 8), 0x8C0 + bufferIndex * 7, physicalAddress, sizeInBytes - 1, // size (stride & 0xFFFF) << 11, // stride 0, // ukn 0, // ukn 0, // ukn 0xC0000000); // ukn } void GX2DrawIndexedEx(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances) { GX2ReserveCmdSpace(3 + 3 + 2 + 2 + 6); gx2WriteGather_submit( // IT_SET_CTL_CONST pm4HeaderType3(IT_SET_CTL_CONST, 2), 0, baseVertex, // IT_SET_CONFIG_REG pm4HeaderType3(IT_SET_CONFIG_REG, 2), Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000, (uint32)primitiveMode, // IT_INDEX_TYPE pm4HeaderType3(IT_INDEX_TYPE, 1), (uint32)indexType, // IT_NUM_INSTANCES pm4HeaderType3(IT_NUM_INSTANCES, 1), numInstances, // IT_DRAW_INDEX_2 pm4HeaderType3(IT_DRAW_INDEX_2, 5) | 0x00000001, -1, memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(indexData)), 0, count, 0); } void GX2DrawIndexedEx2(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances, uint32 baseInstance) { GX2ReserveCmdSpace(3 + 3 + 2 + 2 + 6); gx2WriteGather_submit( // IT_SET_CTL_CONST pm4HeaderType3(IT_SET_CTL_CONST, 2), 0, baseVertex, // set base instance pm4HeaderType3(IT_SET_CTL_CONST, 2), 1, baseInstance, // IT_SET_CONFIG_REG pm4HeaderType3(IT_SET_CONFIG_REG, 2), Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000, (uint32)primitiveMode, // IT_INDEX_TYPE pm4HeaderType3(IT_INDEX_TYPE, 1), (uint32)indexType, // IT_NUM_INSTANCES pm4HeaderType3(IT_NUM_INSTANCES, 1), numInstances, // IT_DRAW_INDEX_2 pm4HeaderType3(IT_DRAW_INDEX_2, 5) | 0x00000001, -1, memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(indexData)), 0, count, 0, // reset base instance pm4HeaderType3(IT_SET_CTL_CONST, 2), 1, 0 // baseInstance ); } void GX2DrawEx(GX2PrimitiveMode2 primitiveMode, uint32 count, uint32 baseVertex, uint32 numInstances) { GX2ReserveCmdSpace(3 + 3 + 2 + 2 + 6); gx2WriteGather_submit( // IT_SET_CTL_CONST pm4HeaderType3(IT_SET_CTL_CONST, 2), 0, baseVertex, // IT_SET_CONFIG_REG pm4HeaderType3(IT_SET_CONFIG_REG, 2), Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000, (uint32)primitiveMode, // IT_INDEX_TYPE pm4HeaderType3(IT_INDEX_TYPE, 1), (uint32)GX2IndexType::U32_BE, // IT_NUM_INSTANCES pm4HeaderType3(IT_NUM_INSTANCES, 1), numInstances, // IT_DRAW_INDEX_2 pm4HeaderType3(IT_DRAW_INDEX_AUTO, 2) | 0x00000001, count, 0 // DRAW_INITIATOR ); } void GX2DrawIndexedImmediateEx(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances) { uint32* indexDataU32 = (uint32*)indexData; uint32 numIndexU32s; bool use32BitIndices = false; if (indexType == GX2IndexType::U16_BE || indexType == GX2IndexType::U16_LE) { // 16bit indices numIndexU32s = (count + 1) / 2; } else if (indexType == GX2IndexType::U32_BE || indexType == GX2IndexType::U32_LE) { // 32bit indices numIndexU32s = count; use32BitIndices = true; } else { cemu_assert_unimplemented(); } GX2ReserveCmdSpace(3 + 3 + 3 + 2 + 2 + 6 + 3 + numIndexU32s); if (numIndexU32s > 0x4000 - 2) { cemuLog_log(LogType::Force, "GX2DrawIndexedImmediateEx(): Draw exceeds maximum PM4 command size. Keep index size below 16KiB minus 8 byte"); return; } // set base vertex gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CTL_CONST, 2)); gx2WriteGather_submitU32AsBE(0); gx2WriteGather_submitU32AsBE(baseVertex); // set primitive mode gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONFIG_REG, 2)); gx2WriteGather_submitU32AsBE(Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000); gx2WriteGather_submitU32AsBE((uint32)primitiveMode); // set index type gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_INDEX_TYPE, 1)); gx2WriteGather_submitU32AsBE((uint32)indexType); // set number of instances gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_NUM_INSTANCES, 1)); gx2WriteGather_submitU32AsBE((uint32)numInstances); // request indexed draw with indices embedded into command buffer gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_DRAW_INDEX_IMMD, 2 + numIndexU32s) | 0x00000001); gx2WriteGather_submitU32AsBE(count); gx2WriteGather_submitU32AsBE(0); // ukn if (use32BitIndices) { for (uint32 i = 0; i < numIndexU32s; i++) { gx2WriteGather_submitU32AsLE(indexDataU32[i]); } } else { for (uint32 i = 0; i < numIndexU32s; i++) { uint32 indexPair = indexDataU32[i]; // swap index pair indexPair = (indexPair >> 16) | (indexPair << 16); gx2WriteGather_submitU32AsLE(indexPair); } } } struct GX2DispatchComputeParam { /* +0x00 */ uint32be worksizeX; /* +0x04 */ uint32be worksizeY; /* +0x08 */ uint32be worksizeZ; }; void GX2DispatchCompute(GX2DispatchComputeParam* dispatchParam) { GX2ReserveCmdSpace(9 + 10); gx2WriteGather_submit(pm4HeaderType3(IT_SET_RESOURCE, 8), (mmSQ_CS_DISPATCH_PARAMS - mmSQ_TEX_RESOURCE_WORD0), memory_virtualToPhysical(MEMPTR<GX2DispatchComputeParam>(dispatchParam).GetMPTR()), 0xF, 0x862000, 1, 0xABCD1234, 0xABCD1234, 0xC0000000); // IT_EVENT_WRITE with RST_VTX_CNT? // set primitive mode gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONFIG_REG, 2)); gx2WriteGather_submitU32AsBE(Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000); gx2WriteGather_submitU32AsBE(1); // mode // set number of instances gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_NUM_INSTANCES, 1)); gx2WriteGather_submitU32AsBE(1); // numInstances uint32 workCount = (uint32)dispatchParam->worksizeX * (uint32)dispatchParam->worksizeY * (uint32)dispatchParam->worksizeZ; gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_DRAW_INDEX_AUTO, 2) | 0x00000001); gx2WriteGather_submitU32AsBE(workCount); gx2WriteGather_submitU32AsBE(0); // DRAW_INITIATOR (has source select for index generator + other unknown info) } void GX2DrawInit() { cafeExportRegister("gx2", GX2SetAttribBuffer, LogType::GX2); cafeExportRegister("gx2", GX2DrawIndexedEx, LogType::GX2); cafeExportRegister("gx2", GX2DrawIndexedEx2, LogType::GX2); cafeExportRegister("gx2", GX2DrawEx, LogType::GX2); cafeExportRegister("gx2", GX2DrawIndexedImmediateEx, LogType::GX2); cafeExportRegister("gx2", GX2DispatchCompute, LogType::GX2); } } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Draw.h ================================================ #pragma once #include "Cafe/HW/Latte/ISA/LatteReg.h" namespace GX2 { using GX2IndexType = Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE; using GX2PrimitiveMode2 = Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE; void GX2SetAttribBuffer(uint32 bufferIndex, uint32 sizeInBytes, uint32 stride, void* data); void GX2DrawIndexedEx(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances); void GX2DrawInit(); } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Event.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "GX2_Command.h" #include "GX2_Event.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/HW/MMU/MMU.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "GX2.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "config/ActiveSettings.h" #include "util/helpers/ConcurrentQueue.h" namespace GX2 { SysAllocator<coreinit::OSThreadQueue> g_vsyncThreadQueue; SysAllocator<coreinit::OSThreadQueue> g_flipThreadQueue; void GX2SetGPUFence(uint32be* fencePtr, uint32 mask, uint32 compareOp, uint32 compareValue) { GX2ReserveCmdSpace(7); uint8 compareOpTable[] = { 0x7,0x1,0x3,0x2,0x6,0x4,0x5,0x0 }; gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_WAIT_REG_MEM, 6)); gx2WriteGather_submitU32AsBE((uint32)(compareOpTable[compareOp & 7]) | 0x10); // compare operand + memory select (0x10) gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(fencePtr)) | 2); // physical address + type size flag(?) gx2WriteGather_submitU32AsBE(0); // ukn, always set to 0 gx2WriteGather_submitU32AsBE(compareValue); // fence value gx2WriteGather_submitU32AsBE(mask); // fence mask gx2WriteGather_submitU32AsBE(0xA); // unknown purpose } enum class GX2PipeEventType : uint32 { TOP = 0, BOTTOM = 1, BOTTOM_AFTER_FLUSH = 2 }; void GX2SubmitUserTimeStamp(uint64* timestampOut, uint64 value, GX2PipeEventType eventType, uint32 triggerInterrupt) { GX2ReserveCmdSpace(7); MPTR physTimestampAddr = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(timestampOut)); uint32 valHigh = (uint32)(value >> 32); uint32 valLow = (uint32)(value & 0xffffffff); if (eventType == GX2PipeEventType::TOP) { // write when on top of pipe gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4)); gx2WriteGather_submitU32AsBE(physTimestampAddr | 0x2); gx2WriteGather_submitU32AsBE(0); // 0x40000 -> 32bit write, 0x0 -> 64bit write? gx2WriteGather_submitU32AsBE(valLow); // low gx2WriteGather_submitU32AsBE(valHigh); // high if (triggerInterrupt != 0) { // top callback gx2WriteGather_submitU32AsBE(0x0000304A); gx2WriteGather_submitU32AsBE(0x40000000); } } else if (eventType == GX2PipeEventType::BOTTOM_AFTER_FLUSH) { // write when on bottom of pipe gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4)); gx2WriteGather_submitU32AsBE(physTimestampAddr | 0x2); gx2WriteGather_submitU32AsBE(0); // 0x40000 -> 32bit write, 0x0 -> 64bit write? gx2WriteGather_submitU32AsBE(valLow); // low gx2WriteGather_submitU32AsBE(valHigh); // high // trigger CB if (triggerInterrupt != 0) { // bottom callback // todo -> Fix this gx2WriteGather_submitU32AsBE(0x0000304B); // hax -> This event is handled differently and uses a different packet? gx2WriteGather_submitU32AsBE(0x40000000); // trigger bottom of pipe callback // used by Mario & Sonic Rio gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_BOTTOM_OF_PIPE_CB, 3)); gx2WriteGather_submitU32AsBE(physTimestampAddr); gx2WriteGather_submitU32AsBE(valLow); // low gx2WriteGather_submitU32AsBE(valHigh); // high } } else if (eventType == GX2PipeEventType::BOTTOM) { // fix this // write timestamp when on bottom of pipe if (triggerInterrupt != 0) { // write value and trigger CB // todo: Use correct packet gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_BOTTOM_OF_PIPE_CB, 3)); gx2WriteGather_submitU32AsBE(physTimestampAddr); gx2WriteGather_submitU32AsBE(valLow); // low gx2WriteGather_submitU32AsBE(valHigh); // high } else { // write value but don't trigger CB gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4)); gx2WriteGather_submitU32AsBE(physTimestampAddr | 0x2); gx2WriteGather_submitU32AsBE(0); // 0x40000 -> 32bit write, 0x0 -> 64bit write? gx2WriteGather_submitU32AsBE(valLow); // low gx2WriteGather_submitU32AsBE(valHigh); // high } } else { cemu_assert_debug(false); } } struct GX2EventFunc { MEMPTR<void> callbackFuncPtr; MEMPTR<void> userData; }s_eventCallback[GX2CallbackEventTypeCount]{}; void GX2SetEventCallback(GX2CallbackEventType eventType, void* callbackFuncPtr, void* userData) { if ((size_t)eventType >= GX2CallbackEventTypeCount) { cemuLog_log(LogType::Force, "GX2SetEventCallback(): Unknown eventType"); return; } s_eventCallback[(size_t)eventType].callbackFuncPtr = callbackFuncPtr; s_eventCallback[(size_t)eventType].userData = userData; } void GX2GetEventCallback(GX2CallbackEventType eventType, MEMPTR<void>* callbackFuncPtrOut, MEMPTR<void>* userDataOut) { if ((size_t)eventType >= GX2CallbackEventTypeCount) { cemuLog_log(LogType::Force, "GX2GetEventCallback(): Unknown eventType"); return; } if (callbackFuncPtrOut) *callbackFuncPtrOut = s_eventCallback[(size_t)eventType].callbackFuncPtr; if (userDataOut) *userDataOut = s_eventCallback[(size_t)eventType].userData; } // event callback thread bool s_callbackThreadLaunched{}; SysAllocator<OSThread_t> s_eventCallbackThread; SysAllocator<uint8, 0x2000> s_eventCallbackThreadStack; SysAllocator<char, 64> s_eventCallbackThreadName; // event callback queue struct GX2EventQueueEntry { GX2EventQueueEntry() {}; GX2EventQueueEntry(GX2CallbackEventType eventType) : eventType(eventType) {}; GX2CallbackEventType eventType{(GX2CallbackEventType)-1}; }; SysAllocator<coreinit::OSSemaphore> s_eventCbQueueSemaphore; ConcurrentQueue<GX2EventQueueEntry> s_eventCbQueue; void __GX2NotifyEvent(GX2CallbackEventType eventType) { if ((size_t)eventType >= GX2CallbackEventTypeCount) { cemu_assert_debug(false); return; } if (s_eventCallback[(size_t)eventType].callbackFuncPtr) { s_eventCbQueue.push(eventType); coreinit::OSSignalSemaphore(s_eventCbQueueSemaphore); } // wake up threads that are waiting for VSYNC or FLIP event if (eventType == GX2CallbackEventType::VSYNC) { __OSLockScheduler(); g_vsyncThreadQueue->wakeupEntireWaitQueue(false); __OSUnlockScheduler(); } else if (eventType == GX2CallbackEventType::FLIP) { __OSLockScheduler(); g_flipThreadQueue->wakeupEntireWaitQueue(false); __OSUnlockScheduler(); } } void __GX2CallbackThread(PPCInterpreter_t* hCPU) { while (coreinit::OSWaitSemaphore(s_eventCbQueueSemaphore)) { GX2EventQueueEntry entry; if (!s_eventCbQueue.peek2(entry)) continue; if(!s_eventCallback[(size_t)entry.eventType].callbackFuncPtr) continue; PPCCoreCallback(s_eventCallback[(size_t)entry.eventType].callbackFuncPtr, (sint32)entry.eventType, s_eventCallback[(size_t)entry.eventType].userData); } osLib_returnFromFunction(hCPU, 0); } void GX2WaitForVsync() { __OSLockScheduler(); g_vsyncThreadQueue.GetPtr()->queueAndWait(coreinit::OSGetCurrentThread()); __OSUnlockScheduler(); } void GX2WaitForFlip() { if ((sint32)(_swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE) == _swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE))) return; // dont wait if no flip is requested __OSLockScheduler(); g_flipThreadQueue.GetPtr()->queueAndWait(coreinit::OSGetCurrentThread()); __OSUnlockScheduler(); } void GX2DrawDone() { // optional force full sync (texture readback and occlusion queries) bool forceFullSync = false; if (g_renderer && g_renderer->GetType() == RendererAPI::Vulkan) forceFullSync = true; if (forceFullSync || ActiveSettings::WaitForGX2DrawDoneEnabled()) { GX2ReserveCmdSpace(2); // write PM4 command gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SYNC_ASYNC_OPERATIONS, 1)); gx2WriteGather_submitU32AsBE(0x00000000); // unused } // flush pipeline GX2Command_Flush(0x100, true); uint64 ts = GX2GetLastSubmittedTimeStamp(); GX2WaitTimeStamp(ts); } void GX2Init_event() { // clear queue // launch event callback thread if (s_callbackThreadLaunched) return; s_callbackThreadLaunched = true; strcpy(s_eventCallbackThreadName.GetPtr(), "GX2 event callback"); coreinit::OSCreateThreadType(s_eventCallbackThread, PPCInterpreter_makeCallableExportDepr(__GX2CallbackThread), 0, nullptr, (uint8*)s_eventCallbackThreadStack.GetPtr() + s_eventCallbackThreadStack.GetByteSize(), (sint32)s_eventCallbackThreadStack.GetByteSize(), 16, OSThread_t::ATTR_DETACHED, OSThread_t::THREAD_TYPE::TYPE_IO); coreinit::OSSetThreadName(s_eventCallbackThread, s_eventCallbackThreadName); coreinit::OSResumeThread(s_eventCallbackThread); } void GX2EventInit() { cafeExportRegister("gx2", GX2SetGPUFence, LogType::GX2); cafeExportRegister("gx2", GX2SubmitUserTimeStamp, LogType::GX2); cafeExportRegister("gx2", GX2SetEventCallback, LogType::GX2); cafeExportRegister("gx2", GX2GetEventCallback, LogType::GX2); cafeExportRegister("gx2", GX2WaitForVsync, LogType::GX2); cafeExportRegister("gx2", GX2WaitForFlip, LogType::GX2); cafeExportRegister("gx2", GX2DrawDone, LogType::GX2); coreinit::OSInitThreadQueue(g_vsyncThreadQueue.GetPtr()); coreinit::OSInitThreadQueue(g_flipThreadQueue.GetPtr()); coreinit::OSInitSemaphore(s_eventCbQueueSemaphore, 0); } void GX2EventResetToDefaultState() { s_callbackThreadLaunched = false; for(auto& it : s_eventCallback) { it.callbackFuncPtr = nullptr; it.userData = nullptr; } } } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Event.h ================================================ #pragma once namespace GX2 { void GX2Init_event(); void GX2EventResetToDefaultState(); void GX2EventInit(); void GX2WaitForVsync(); void GX2WaitForFlip(); void GX2DrawDone(); enum class GX2CallbackEventType { TIMESTAMP_TOP = 0, TIMESTAMP_BOTTOM = 1, VSYNC = 2, FLIP = 3, // 4 is buffer overrun? }; inline constexpr size_t GX2CallbackEventTypeCount = 5; // notification callbacks for GPU void __GX2NotifyNewRetirementTimestamp(uint64 tsRetire); void __GX2NotifyEvent(GX2CallbackEventType eventType); } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Memory.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "GX2.h" #include "GX2_Resource.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" // default GX2 allocator (not the same as the GX2R allocator, but GX2R uses this allocator by default) MPTR gx2Mem_defaultAlloc = MPTR_NULL; MPTR gx2Mem_defaultFree = MPTR_NULL; void gx2Memory_GX2SetDefaultAllocator(MPTR defaultAllocFunc, MPTR defaulFreeFunc) { gx2Mem_defaultAlloc = defaultAllocFunc; gx2Mem_defaultFree = defaulFreeFunc; } void _GX2DefaultAlloc_Alloc(PPCInterpreter_t* hCPU) { // parameters: // r3 uint32 userParam // r4 uint32 size // r5 sint32 alignment hCPU->gpr[3] = hCPU->gpr[4]; hCPU->gpr[4] = hCPU->gpr[5]; hCPU->instructionPointer = gCoreinitData->MEMAllocFromDefaultHeapEx.GetMPTR(); } void _GX2DefaultAlloc_Free(PPCInterpreter_t* hCPU) { hCPU->gpr[3] = hCPU->gpr[4]; hCPU->instructionPointer = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); } void gx2Export_GX2SetDefaultAllocator(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetDefaultAllocator(0x{:08x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]); gx2Mem_defaultAlloc = hCPU->gpr[3]; gx2Mem_defaultFree = hCPU->gpr[4]; osLib_returnFromFunction(hCPU, 0); } void _GX2DefaultAllocR_Alloc(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2DefaultAllocate(0x{:08x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); // parameters: // r3 uint32 userParam // r4 uint32 size // r5 sint32 alignment hCPU->instructionPointer = gx2Mem_defaultAlloc; } void _GX2DefaultAllocR_Free(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2DefaultFree(0x{:08x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]); // parameters: // r3 uint32 userParam // r4 void* mem hCPU->instructionPointer = gx2Mem_defaultFree; } namespace GX2 { void GX2MEMAllocatorsInit() { // set default allocators (can be overwritten by GX2SetDefaultAllocator) gx2Mem_defaultAlloc = PPCInterpreter_makeCallableExportDepr(_GX2DefaultAlloc_Alloc); gx2Mem_defaultFree = PPCInterpreter_makeCallableExportDepr(_GX2DefaultAlloc_Free); // set resource default allocator GX2::GX2RSetAllocator(PPCInterpreter_makeCallableExportDepr(_GX2DefaultAllocR_Alloc), PPCInterpreter_makeCallableExportDepr(_GX2DefaultAllocR_Free)); } void GX2MemInit() { osLib_addFunction("gx2", "GX2SetDefaultAllocator", gx2Export_GX2SetDefaultAllocator); } }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Memory.h ================================================ #pragma once namespace GX2 { void GX2MEMAllocatorsInit(); void GX2MemInit(); }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Misc.cpp ================================================ #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/OS/common/OSCommon.h" #include "GX2.h" #include "config/CemuConfig.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "config/ActiveSettings.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "GX2_Command.h" #include "GX2_Event.h" #include "GX2_Misc.h" #include "GX2_Memory.h" #include "GX2_Texture.h" void gx2Export_GX2SetSwapInterval(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetSwapInterval({})", hCPU->gpr[3]); if( hCPU->gpr[3] >= 20 ) { cemuLog_log(LogType::Force, "GX2SetSwapInterval() called with out of range value ({})", hCPU->gpr[3]); } else LatteGPUState.sharedArea->swapInterval = hCPU->gpr[3]; osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2GetSwapInterval(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2GetSwapInterval()"); osLib_returnFromFunction(hCPU, LatteGPUState.sharedArea->swapInterval); } extern uint64 lastSwapTime; void gx2Export_GX2GetSwapStatus(PPCInterpreter_t* hCPU) { memory_writeU32(hCPU->gpr[3], _swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE)); memory_writeU32(hCPU->gpr[4], _swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE)); memory_writeU64(hCPU->gpr[5], lastSwapTime); memory_writeU64(hCPU->gpr[6], lastSwapTime); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2GetGPUTimeout(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2GetGPUTimeout()"); osLib_returnFromFunction(hCPU, 0x3E8); } #define GX2_INVALID_COUNTER_VALUE_U64 0xFFFFFFFFFFFFFFFFULL void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SampleTopGPUCycle(0x{:08x})", hCPU->gpr[3]); memory_writeU64(hCPU->gpr[3], coreinit::OSGetSystemTime()); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SampleBottomGPUCycle(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SampleBottomGPUCycle(0x{:08x})", hCPU->gpr[3]); memory_writeU64(hCPU->gpr[3], GX2_INVALID_COUNTER_VALUE_U64); osLib_returnFromFunction(hCPU, 0); return; // seems like implementing this correctly causes more harm than good as games will try to dynamically scale their resolution, which our texture cache and graphic packs cant handle well. If we just never return a valid timestamp, it seems like games stop dynamically scaling resolution // Whats a good solution here? Should we implement it correctly and instead rely on graphic pack patches to patch out the dynamic scaling? // some known affected games: Wind Waker HD, Super Mario 3D World gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SAMPLE_TIMER, 1)); gx2WriteGather_submitU32AsBE(hCPU->gpr[3]); osLib_returnFromFunction(hCPU, 0); } namespace GX2 { SysAllocator<uint8, 640 * 480 * 4, 0x1000> _lastFrame; uint32 sGX2MainCoreIndex = 0; void _test_AddrLib(); using GX2InitArg = uint32; enum class GX2InitArgId : GX2InitArg { EndOfArgs = 0, CommandPoolBase = 1, CommandPoolSize = 2, UknArg7 = 7, UknArg8 = 8, UknArg9 = 9, UknArg11 = 11, }; void GX2Init(betype<GX2InitArg>* initArgStream) { if (LatteGPUState.gx2InitCalled) { cemuLog_logDebug(LogType::Force, "GX2Init() called while already initialized"); return; } // parse init params from the stream MEMPTR<void> commandPoolBase = nullptr; uint32 commandPoolSize = 0; if (initArgStream) { while (true) { GX2InitArgId paramId = static_cast<GX2InitArgId>((GX2InitArg)*initArgStream); initArgStream++; if (paramId == GX2InitArgId::EndOfArgs) { break; } else if (paramId == GX2InitArgId::CommandPoolBase) { commandPoolBase = MEMPTR<void>(*initArgStream); initArgStream++; } else if (paramId == GX2InitArgId::CommandPoolSize) { commandPoolSize = *initArgStream; initArgStream++; } else if (paramId == GX2InitArgId::UknArg7 || paramId == GX2InitArgId::UknArg8 || paramId == GX2InitArgId::UknArg9 || paramId == GX2InitArgId::UknArg11) { initArgStream++; } else { cemuLog_log(LogType::Force, "GX2Init: Unsupported init arg {}", (uint32)paramId); } } } // init main core uint32 coreIndex = coreinit::OSGetCoreId(); cemuLog_log(LogType::GX2, "GX2Init() on core {} by thread 0x{:08x}", coreIndex, MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR()); sGX2MainCoreIndex = coreIndex; // init submodules GX2::GX2Init_event(); GX2::GX2Init_commandBufferPool(commandPoolBase, commandPoolSize); // init shared area if (LatteGPUState.sharedAreaAddr == MPTR_NULL) { LatteGPUState.sharedAreaAddr = coreinit_allocFromSysArea(sizeof(gx2GPUSharedArea_t), 0x20); LatteGPUState.sharedArea = (gx2GPUSharedArea_t*)memory_getPointerFromVirtualOffset(LatteGPUState.sharedAreaAddr); } // init shared variables LatteGPUState.sharedArea->flipRequestCountBE = _swapEndianU32(0); LatteGPUState.sharedArea->flipExecuteCountBE = _swapEndianU32(0); LatteGPUState.sharedArea->swapInterval = 1; // init memory handling GX2::GX2MEMAllocatorsInit(); // let GPU know that GX2 is initialized LatteGPUState.gx2InitCalled++; // run tests _test_AddrLib(); } void GX2Shutdown() { if (!LatteGPUState.gx2InitCalled) { cemuLog_logDebug(LogType::Force, "GX2Shutdown() called while not initialized"); return; } LatteGPUState.gx2InitCalled--; if (LatteGPUState.gx2InitCalled != 0) return; GX2DrawDone(); GX2Shutdown_commandBufferPool(); cemuLog_log(LogType::Force, "GX2 shutdown"); } void _GX2DriverReset() { LatteGPUState.gx2InitCalled = 0; sGX2MainCoreIndex = 0; GX2CommandResetToDefaultState(); GX2EventResetToDefaultState(); } sint32 GX2GetMainCoreId(PPCInterpreter_t* hCPU) { if (LatteGPUState.gx2InitCalled == 0) return -1; return sGX2MainCoreIndex; } void GX2ResetGPU(uint32 ukn) { cemuLog_log(LogType::Force, "GX2ResetGPU()"); // always log this GX2::GX2DrawDone(); } void GX2SetTVBuffer(void* imageBuffePtr, uint32 imageBufferSize, E_TVRES tvResolutionMode, uint32 _surfaceFormat, E_TVBUFFERMODE bufferMode) { Latte::E_GX2SURFFMT surfaceFormat = (Latte::E_GX2SURFFMT)_surfaceFormat; LatteGPUState.tvBufferUsesSRGB = HAS_FLAG(surfaceFormat, Latte::E_GX2SURFFMT::FMT_BIT_SRGB); // todo - actually allocate a scanbuffer } void GX2SetTVGamma(float gamma) { LatteGPUState.tvGamma = (1.0f - gamma); } void GX2SetDRCGamma(float gamma) { LatteGPUState.drcGamma = (1.0f - gamma); } bool GX2GetLastFrame(uint32 deviceId, GX2Texture* textureOut) { // return a 480p image textureOut->viewFirstMip = 0; textureOut->viewFirstSlice = 0; textureOut->viewNumMips = 1; textureOut->viewNumSlices = 1; textureOut->compSel = 0x00010203; textureOut->surface.width = 640; textureOut->surface.height = 480; textureOut->surface.depth = 1; textureOut->surface.dim = Latte::E_DIM::DIM_2D; textureOut->surface.format = Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM; textureOut->surface.tileMode = Latte::E_GX2TILEMODE::TM_LINEAR_ALIGNED; textureOut->surface.pitch = 0; textureOut->surface.resFlag = 0; textureOut->surface.aa = 0; GX2CalcSurfaceSizeAndAlignment(&textureOut->surface); textureOut->surface.imagePtr = _lastFrame.GetMPTR(); GX2InitTextureRegs(textureOut); return true; } bool GX2GetLastFrameGammaA(uint32 deviceId, float32be* gamma) { *gamma = 1.0f; return true; } bool GX2GetLastFrameGammaB(uint32 deviceId, float32be* gamma) { *gamma = 1.0f; return true; } uint64 GX2GPUTimeToCPUTime(uint64 gpuTime) { return 0; // hack, see note in GX2SampleBottomGPUCycle } uint32 GX2GetSystemDRCMode() { return 1; } uint32 GX2IsVideoOutReady() { return 1; } void GX2Invalidate(uint32 invalidationFlags, MPTR invalidationAddr, uint32 invalidationSize) { uint32 surfaceSyncFlags = 0; if (invalidationFlags & 0x04) { // uniform block surfaceSyncFlags |= 0x8800000; } if (invalidationFlags & 0x01) { // attribute data surfaceSyncFlags |= 0x800000; } if (invalidationFlags & 0x40) { // CPU cache LatteBufferCache_notifyDCFlush(invalidationAddr, invalidationSize); } if (surfaceSyncFlags != 0) { GX2ReserveCmdSpace(5); // write PM4 command gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SURFACE_SYNC, 4)); // IT_SURFACE_SYNC + 4 data dwords gx2WriteGather_submitU32AsBE(surfaceSyncFlags); gx2WriteGather_submitU32AsBE((invalidationSize + 0xFF) >> 8); // size gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(invalidationAddr) >> 8); // base address (divided by 0x100) gx2WriteGather_submitU32AsBE(0x00000004); // poll interval } } void GX2MiscInit() { cafeExportRegister("gx2", GX2Init, LogType::GX2); cafeExportRegister("gx2", GX2Shutdown, LogType::GX2); cafeExportRegister("gx2", GX2GetMainCoreId, LogType::GX2); cafeExportRegister("gx2", GX2ResetGPU, LogType::GX2); cafeExportRegister("gx2", GX2SetTVBuffer, LogType::GX2); cafeExportRegister("gx2", GX2SetTVGamma, LogType::GX2); cafeExportRegister("gx2", GX2SetDRCGamma, LogType::GX2); cafeExportRegister("gx2", GX2GetLastFrame, LogType::GX2); cafeExportRegister("gx2", GX2GetLastFrameGammaA, LogType::GX2); cafeExportRegister("gx2", GX2GetLastFrameGammaB, LogType::GX2); cafeExportRegister("gx2", GX2GPUTimeToCPUTime, LogType::GX2); cafeExportRegister("gx2", GX2GetSystemDRCMode, LogType::GX2); cafeExportRegister("gx2", GX2IsVideoOutReady, LogType::GX2); cafeExportRegister("gx2", GX2Invalidate, LogType::GX2); sGX2MainCoreIndex = 0; } }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Misc.h ================================================ #pragma once namespace GX2 { extern uint32 sGX2MainCoreIndex; enum class E_TVRES { TODO, }; enum class E_TVBUFFERMODE { DOUBLE_BUFFER = 2, }; void _GX2DriverReset(); void GX2SetTVBuffer(void* imageBuffePtr, uint32 imageBufferSize, E_TVRES tvResolutionMode, uint32 surfaceFormat, E_TVBUFFERMODE bufferMode); void GX2SetTVGamma(float gamma); void GX2Invalidate(uint32 invalidationFlags, MPTR invalidationAddr, uint32 invalidationSize); void GX2MiscInit(); }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Query.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "GX2.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/CafeSystem.h" #include "GX2_Query.h" #define LATTE_GC_NUM_RB 2 #define _QUERY_REG_COUNT 8 // each reg/result is 64bits, little endian namespace GX2 { struct GX2Query { // 4*2 sets of uint64 results uint32 reg[_QUERY_REG_COUNT * 2]; }; static_assert(sizeof(GX2Query) == 0x40); void _BeginOcclusionQuery(GX2Query* queryInfo, bool isGPUQuery) { if (isGPUQuery) { uint64 titleId = CafeSystem::GetForegroundTitleId(); if (titleId == 0x00050000101c4c00ULL || titleId == 0x00050000101c4d00 || titleId == 0x0005000010116100) // XCX EU, US, JPN { // in XCX queries are used to determine if certain objects are visible // if we are not setting the result fast enough and the query still holds a value of 0 (which is the default for GPU queries) // then XCX will not render affected objects, causing flicker // note: This is a very old workaround. It may no longer be necessary since the introduction of full sync. Investigate *(uint64*)(queryInfo->reg + 2) = 0x100000; } else { GX2ReserveCmdSpace(5 * _QUERY_REG_COUNT); MPTR queryInfoPhys = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(queryInfo)); for (sint32 i = 0; i < _QUERY_REG_COUNT; i++) { gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4)); gx2WriteGather_submitU32AsBE((queryInfoPhys + i * 8) | 0x2); gx2WriteGather_submitU32AsBE(0x20000); // 0x20000 -> ? uint32 v = 0; if (i >= LATTE_GC_NUM_RB * 2) v |= 0x80000000; gx2WriteGather_submitU32AsBE(0); gx2WriteGather_submitU32AsBE(v); } } } else { memset(queryInfo, 0, 0x10); // size maybe GPU7_GC_NUM_RB*2*4 ? queryInfo->reg[LATTE_GC_NUM_RB * 4 + 0] = 0; queryInfo->reg[LATTE_GC_NUM_RB * 4 + 1] = _swapEndianU32('OCPU'); } // todo: Set mmDB_RENDER_CONTROL } void GX2QueryBegin(uint32 queryType, GX2Query* query) { if (queryType == GX2_QUERY_TYPE_OCCLUSION_CPU) { _BeginOcclusionQuery(query, false); } else if (queryType == GX2_QUERY_TYPE_OCCLUSION_GPU) { _BeginOcclusionQuery(query, true); } else { debug_printf("GX2QueryBegin(): Unsupported type %d\n", queryType); debugBreakpoint(); return; } // HLE packet GX2ReserveCmdSpace(2); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_BEGIN_OCCLUSION_QUERY, 1)); gx2WriteGather_submitU32AsBE(MEMPTR<GX2Query>(query).GetMPTR()); } void GX2QueryEnd(uint32 queryType, GX2Query* query) { GX2ReserveCmdSpace(2); if (queryType == GX2_QUERY_TYPE_OCCLUSION_CPU || queryType == GX2_QUERY_TYPE_OCCLUSION_GPU) { // HLE packet gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_END_OCCLUSION_QUERY, 1)); gx2WriteGather_submitU32AsBE(MEMPTR<GX2Query>(query).GetMPTR()); } else { debug_printf("GX2QueryBegin(): Unsupported %d\n", queryType); debugBreakpoint(); return; } } uint32 GX2QueryGetOcclusionResult(GX2Query* query, uint64be* resultOut) { if (query->reg[LATTE_GC_NUM_RB * 4 + 1] == _swapEndianU32('OCPU') && query->reg[LATTE_GC_NUM_RB * 4 + 0] == 0) { // CPU query result not ready return GX2_FALSE; } uint64 startValue = *(uint64*)(query->reg + 0); uint64 endValue = *(uint64*)(query->reg + 2); if ((startValue & 0x8000000000000000ULL) || (endValue & 0x8000000000000000ULL)) { return GX2_FALSE; } *resultOut = endValue - startValue; return GX2_TRUE; } void GX2QueryBeginConditionalRender(uint32 queryType, GX2Query* query, uint32 dontWaitBool, uint32 pixelsMustPassBool) { GX2ReserveCmdSpace(3); uint32 flags = 0; if (pixelsMustPassBool) flags |= (1<<31); if (queryType == GX2_QUERY_TYPE_OCCLUSION_GPU) flags |= (1 << 13); else flags |= (2 << 13); flags |= ((dontWaitBool != 0) << 19); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_PREDICATION, 2)); gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(MEMPTR<GX2Query>(query).GetMPTR())); gx2WriteGather_submitU32AsBE(flags); } void GX2QueryEndConditionalRender() { GX2ReserveCmdSpace(3); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_PREDICATION, 2)); gx2WriteGather_submitU32AsBE(MPTR_NULL); gx2WriteGather_submitU32AsBE(0); // unknown / todo } void GX2QueryInit() { cafeExportRegister("gx2", GX2QueryBegin, LogType::GX2); cafeExportRegister("gx2", GX2QueryEnd, LogType::GX2); cafeExportRegister("gx2", GX2QueryGetOcclusionResult, LogType::GX2); cafeExportRegister("gx2", GX2QueryBeginConditionalRender, LogType::GX2); cafeExportRegister("gx2", GX2QueryEndConditionalRender, LogType::GX2); } }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Query.h ================================================ #pragma once #define GX2_QUERY_TYPE_OCCLUSION_CPU 0 #define GX2_QUERY_TYPE_OCCLUSION_GPU 2 // 1 and 3 are streamout related? namespace GX2 { void GX2QueryInit(); }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_RenderTarget.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "GX2.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "GX2_Command.h" void gx2Export_GX2InitColorBufferRegs(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2InitColorBufferRegs(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(colorBuffer, GX2ColorBuffer, 0); LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; LatteAddrLib::GX2CalculateSurfaceInfo(colorBuffer->surface.format, colorBuffer->surface.width, colorBuffer->surface.height, colorBuffer->surface.depth, colorBuffer->surface.dim, colorBuffer->surface.tileMode, colorBuffer->surface.aa, colorBuffer->viewMip, &surfaceInfo); uint32 pitchHeight = (surfaceInfo.height * surfaceInfo.pitch) >> 6; #ifdef CEMU_DEBUG_ASSERT if (colorBuffer->viewNumSlices != 1) cemuLog_logDebug(LogType::Force, "GX2InitColorBufferRegs(): With unsupported slice count {}", (uint32)colorBuffer->viewNumSlices); if (surfaceInfo.pitch < 7) cemuLog_logDebug(LogType::Force, "GX2InitColorBufferRegs(): Pitch too small (pitch = {})", surfaceInfo.pitch); if ((surfaceInfo.pitch & 7) != 0) cemuLog_logDebug(LogType::Force, "GX2InitColorBufferRegs(): Pitch has invalid alignment (pitch = {})", surfaceInfo.pitch); if (pitchHeight == 0) cemuLog_logDebug(LogType::Force, "GX2InitColorBufferRegs(): Invalid value (pitchHeight = {})", pitchHeight); #endif uint32 cSize = ((surfaceInfo.pitch >> 3) - 1) & 0x3FF; cSize |= (((pitchHeight - 1) & 0xFFFFF) << 10); colorBuffer->reg_size = cSize; colorBuffer->reg_mask = 0; // reg color_info Latte::E_GX2SURFFMT format = colorBuffer->surface.format; Latte::E_HWSURFFMT hwFormat = Latte::GetHWFormat(format); uint32 formatHighBits = (uint32)format & 0xF00; uint32 regInfo = 0; regInfo = (uint32)GX2::GetSurfaceFormatSwapMode(colorBuffer->surface.format); regInfo |= ((uint32)hwFormat<<2); cemu_assert_debug(LatteAddrLib::IsValidHWTileMode(surfaceInfo.hwTileMode)); regInfo |= ((uint32)surfaceInfo.hwTileMode << 8); bool clampBlend = false; if (formatHighBits == 0x000) { regInfo |= (0 << 12); clampBlend = true; } else if (formatHighBits == 0x100) // integer { regInfo |= (4 << 12); } else if (formatHighBits == 0x200) // signed { regInfo |= (1 << 12); clampBlend = true; } else if (formatHighBits == 0x300) // integer + signed { regInfo |= (5 << 12); } else if (formatHighBits == 0x400) // srgb { clampBlend = true; regInfo |= (6 << 12); } else if (formatHighBits == 0x800) // float { regInfo |= (7 << 12); } else cemu_assert_debug(false); if (hwFormat == Latte::E_HWSURFFMT::HWFMT_5_5_5_1 || hwFormat == Latte::E_HWSURFFMT::HWFMT_10_10_10_2 ) regInfo |= (2 << 16); else regInfo &= ~(3 << 16); // COMP_SWAP_mask if(colorBuffer->surface.aa != 0) regInfo |= (2 << 18); // TILE_MODE bool isIntegerFormat = (uint32)(format & Latte::E_GX2SURFFMT::FMT_BIT_INT) != 0; if (isIntegerFormat == false) regInfo |= (GX2::GetSurfaceColorBufferExportFormat(colorBuffer->surface.format) << 27); // 0 -> full, 1 -> normalized if (isIntegerFormat || format ==Latte::E_GX2SURFFMT::R24_X8_UNORM || format ==Latte::E_GX2SURFFMT::R24_X8_FLOAT || format ==Latte::E_GX2SURFFMT::R32_X8_FLOAT) { // set the blend bypass bit for formats which dont support blending regInfo |= (1<<22); clampBlend = false; } if (clampBlend) regInfo |= (1<<20); // BLEND_CLAMP_bit if ((uint32)(format & Latte::E_GX2SURFFMT::FMT_BIT_FLOAT) != 0) regInfo |= (1<<25); // ROUND_MODE_bit colorBuffer->reg_info = regInfo; // reg color_view uint32 regView = 0; if (colorBuffer->surface.tileMode != Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL) { regView |= ((uint32)colorBuffer->viewFirstSlice & 0x7FF); regView |= ((((uint32)colorBuffer->viewNumSlices + (uint32)colorBuffer->viewFirstSlice - 1) & 0x7FF) << 13); } colorBuffer->reg_view = regView; colorBuffer->reg_mask = 0; // todo - aa stuff osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2InitDepthBufferRegs(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2InitDepthBufferRegs(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(depthBuffer, GX2DepthBuffer, 0); LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; LatteAddrLib::GX2CalculateSurfaceInfo(depthBuffer->surface.format, depthBuffer->surface.width, depthBuffer->surface.height, depthBuffer->surface.depth, depthBuffer->surface.dim, depthBuffer->surface.tileMode, depthBuffer->surface.aa, depthBuffer->viewMip, &surfaceInfo); cemu_assert_debug(depthBuffer->viewNumSlices != 0); uint32 cSize = ((surfaceInfo.pitch >> 3) - 1) & 0x3FF; cSize |= ((((surfaceInfo.height * surfaceInfo.pitch >> 6) - 1) & 0xFFFFF) << 10); depthBuffer->reg_size = cSize; // todo - other regs osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetColorBuffer(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetColorBuffer(0x{:08x}, {})", hCPU->gpr[3], hCPU->gpr[4]); GX2::GX2ReserveCmdSpace(20); GX2ColorBuffer* colorBufferBE = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); #ifdef CEMU_DEBUG_ASSERT cemuLog_log(LogType::GX2, "ColorBuffer tileMode {:01x} PhysAddr {:08x} fmt {:04x} res {}x{} Mip {} Slice {}", (uint32)colorBufferBE->surface.tileMode.value(), (uint32)colorBufferBE->surface.imagePtr, (uint32)colorBufferBE->surface.format.value(), (uint32)colorBufferBE->surface.width, (uint32)colorBufferBE->surface.height, (uint32)colorBufferBE->viewMip, (uint32)colorBufferBE->viewFirstSlice); #endif uint32 targetIndex = hCPU->gpr[4]; uint32 viewMip = colorBufferBE->viewMip; uint32 colorBufferBase = memory_virtualToPhysical(colorBufferBE->surface.imagePtr); if( viewMip != 0 ) { uint32 baseImagePtr = colorBufferBE->surface.mipPtr; if( viewMip == 1 ) colorBufferBase = memory_virtualToPhysical(baseImagePtr); else colorBufferBase = memory_virtualToPhysical(baseImagePtr+colorBufferBE->surface.mipOffset[viewMip-1]); } Latte::E_GX2TILEMODE tileMode = colorBufferBE->surface.tileMode; uint32 viewMipIndex = colorBufferBE->viewMip; uint32 swizzle = colorBufferBE->surface.swizzle; if (Latte::TM_IsMacroTiled(tileMode) && viewMipIndex < ((swizzle >> 16) & 0xFF)) { // remove swizzle for small mips colorBufferBase ^= (swizzle & 0xFFFF); } // set color buffer pointer for render target gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmCB_COLOR0_BASE - 0xA000 + hCPU->gpr[4]); gx2WriteGather_submitU32AsBE(colorBufferBase); // set color buffer size gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmCB_COLOR0_SIZE - 0xA000 + hCPU->gpr[4]); gx2WriteGather_submitU32AsBE((uint32)colorBufferBE->reg_size); cemu_assert_debug(tileMode != Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL); // set mmCB_COLOR*_VIEW gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 2), mmCB_COLOR0_VIEW - 0xA000 + hCPU->gpr[4], colorBufferBE->reg_view); // todo: mmCB_COLOR0_TILE and mmCB_COLOR0_FRAG // set mmCB_COLOR*_INFO gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 2), mmCB_COLOR0_INFO - 0xA000 + hCPU->gpr[4], colorBufferBE->reg_info); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetDepthBuffer(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetDepthBuffer(0x{:08x})", hCPU->gpr[3]); GX2::GX2ReserveCmdSpace(20); GX2DepthBuffer* depthBufferBE = (GX2DepthBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); cemuLog_log(LogType::GX2, "DepthBuffer tileMode {:01x} PhysAddr {:08x} fmt {:04x} res {}x{}", (uint32)depthBufferBE->surface.tileMode.value(), (uint32)depthBufferBE->surface.imagePtr, (uint32)depthBufferBE->surface.format.value(), (uint32)depthBufferBE->surface.width, (uint32)depthBufferBE->surface.height); uint32 viewMip = depthBufferBE->viewMip; // todo: current code for the PM4 packets is a hack, replace with proper implementation uint32 regHTileDataBase = memory_virtualToPhysical(depthBufferBE->surface.imagePtr)>>8; if( viewMip > 0 ) { cemuLog_logDebug(LogType::Force, "GX2SetDepthBuffer: Unsupported non-zero mip ({}) Pointer: {:08x} Base: {:08x}", viewMip, regHTileDataBase, 0); } // setup depthbuffer info register uint32 regDepthBufferInfo = 0; uint32 depthBufferTileMode = (uint32)depthBufferBE->surface.tileMode.value(); Latte::E_GX2SURFFMT depthBufferFormat = depthBufferBE->surface.format; regDepthBufferInfo |= ((depthBufferTileMode&0xF)<<15); if (depthBufferFormat == Latte::E_GX2SURFFMT::D16_UNORM) regDepthBufferInfo |= (1 << 0); else if (depthBufferFormat == Latte::E_GX2SURFFMT::D24_S8_UNORM) regDepthBufferInfo |= (3 << 0); else if (depthBufferFormat == Latte::E_GX2SURFFMT::D32_FLOAT) regDepthBufferInfo |= (6 << 0); else if (depthBufferFormat == Latte::E_GX2SURFFMT::D32_S8_FLOAT) regDepthBufferInfo |= (7 << 0); else if (depthBufferFormat == Latte::E_GX2SURFFMT::D24_S8_FLOAT) regDepthBufferInfo |= (5 << 0); else { debug_printf("Unsupported depth buffer format 0x%04x\n", depthBufferFormat); } // set color buffer pointer for render target gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1)); gx2WriteGather_submitU32AsBE(mmDB_DEPTH_SIZE - 0xA000); gx2WriteGather_submitU32AsBE((uint32)depthBufferBE->reg_size); // hack // set color buffer size gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+3)); gx2WriteGather_submitU32AsBE(mmDB_DEPTH_BASE - 0xA000); gx2WriteGather_submitU32AsBE(0); // DB_DEPTH_BASE gx2WriteGather_submitU32AsBE(regDepthBufferInfo); // DB_DEPTH_INFO gx2WriteGather_submitU32AsBE(regHTileDataBase); // DB_HTILE_DATA_BASE // set DB_DEPTH_VIEW uint32 db_view = 0; db_view |= ((uint32)depthBufferBE->viewFirstSlice&0x7FF); db_view |= ((((uint32)depthBufferBE->viewNumSlices+(uint32)depthBufferBE->viewFirstSlice-1)&0x7FF)<<13); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmDB_DEPTH_VIEW - 0xA000); gx2WriteGather_submitU32AsBE(db_view); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetDRCBuffer(PPCInterpreter_t* hCPU) { Latte::E_GX2SURFFMT format = (Latte::E_GX2SURFFMT)hCPU->gpr[6]; LatteGPUState.drcBufferUsesSRGB = HAS_FLAG(format, Latte::E_GX2SURFFMT::FMT_BIT_SRGB); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2MarkScanBufferCopied(PPCInterpreter_t* hCPU) { uint32 scanTarget = hCPU->gpr[3]; if( scanTarget == GX2_SCAN_TARGET_TV ) { GX2::GX2ReserveCmdSpace(10); uint32 physAddr = (MEMORY_TILINGAPERTURE_AREA_ADDR+0x200000); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER, 9)); gx2WriteGather_submitU32AsBE(physAddr); gx2WriteGather_submitU32AsBE(1920); gx2WriteGather_submitU32AsBE(1080); gx2WriteGather_submitU32AsBE(1920); // pitch gx2WriteGather_submitU32AsBE(4); // tileMode gx2WriteGather_submitU32AsBE(0); // swizzle gx2WriteGather_submitU32AsBE(0); gx2WriteGather_submitU32AsBE((uint32)Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM); gx2WriteGather_submitU32AsBE(scanTarget); } osLib_returnFromFunction(hCPU, 0); } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Resource.cpp ================================================ #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/OS/common/OSCommon.h" #include "GX2.h" #include "GX2_Command.h" #include "GX2_Resource.h" #include "GX2_Streamout.h" #include "GX2_Draw.h" namespace GX2 { MPTR GX2RAllocateFunc = MPTR_NULL; MPTR GX2RFreeFunc = MPTR_NULL; void GX2RSetAllocator(MPTR funcAllocMPTR, MPTR funcFreeMPR) { GX2RAllocateFunc = funcAllocMPTR; GX2RFreeFunc = funcFreeMPR; } uint32 GX2RGetBufferAllocationSize(GX2RBuffer* buffer) { return (buffer->GetSize() + 0x3F) & ~0x3F; // pad to 64 byte alignment } uint32 GX2RGetBufferAlignment(uint32 resFlags) { if ((resFlags & GX2R_RESFLAG_USAGE_STREAM_OUTPUT) != 0) return 0x100; if ((resFlags & GX2R_RESFLAG_USAGE_UNIFORM_BLOCK) != 0) return 0x100; if ((resFlags & GX2R_RESFLAG_USAGE_SHADER_PROGRAM) != 0) return 0x100; if ((resFlags & GX2R_RESFLAG_USAGE_GS_RINGBUFFER) != 0) return 0x100; if ((resFlags & GX2R_RESFLAG_USAGE_VERTEX_BUFFER) != 0) return 0x40; if ((resFlags & GX2R_RESFLAG_USAGE_INDEX_BUFFER) != 0) return 0x40; if ((resFlags & GX2R_RESFLAG_USAGE_DISPLAY_LIST) != 0) return 0x40; return 0x100; } bool GX2RCreateBuffer(GX2RBuffer* buffer) { uint32 bufferAlignment = GX2RGetBufferAlignment(buffer->resFlags); uint32 bufferSize = GX2RGetBufferAllocationSize(buffer); MPTR allocResult = PPCCoreCallback(GX2RAllocateFunc, (uint32)buffer->resFlags, bufferSize, bufferAlignment); buffer->ptr = allocResult; buffer->resFlags &= ~GX2R_RESFLAG_LOCKED; buffer->resFlags |= GX2R_RESFLAG_ALLOCATED_BY_GX2R; // todo: invalidation return allocResult != MPTR_NULL; } bool GX2RCreateBufferUserMemory(GX2RBuffer* buffer, void* ptr, uint32 unusedSizeParameter) { buffer->ptr = ptr; buffer->resFlags &= ~GX2R_RESFLAG_LOCKED; buffer->resFlags &= ~GX2R_RESFLAG_ALLOCATED_BY_GX2R; // todo: invalidation return true; } void GX2RDestroyBufferEx(GX2RBuffer* buffer, uint32 resFlags) { if ((buffer->resFlags & GX2R_RESFLAG_ALLOCATED_BY_GX2R) == 0) { // this buffer is user-allocated buffer->ptr = nullptr; return; } PPCCoreCallback(GX2RFreeFunc, (uint32)buffer->resFlags, buffer->GetPtr()); buffer->ptr = nullptr; } bool GX2RBufferExists(GX2RBuffer* buffer) { if (!buffer) return false; if (!buffer->GetPtr()) return false; return true; } void GX2RSetBufferName(GX2RBuffer* buffer, const char* name) { // no-op in production builds } void* GX2RLockBufferEx(GX2RBuffer* buffer, uint32 resFlags) { return buffer->GetPtr(); } void GX2RUnlockBufferEx(GX2RBuffer* buffer, uint32 resFlags) { // todo - account for flags, not all buffer types need flushing LatteBufferCache_notifyDCFlush(buffer->GetVirtualAddr(), buffer->GetSize()); } void GX2RInvalidateBuffer(GX2RBuffer* buffer, uint32 resFlags) { // todo - account for flags, not all buffer types need flushing LatteBufferCache_notifyDCFlush(buffer->GetVirtualAddr(), buffer->GetSize()); } void GX2RSetAttributeBuffer(GX2RBuffer* buffer, uint32 bufferIndex, uint32 stride, uint32 offset) { uint32 bufferSize = buffer->GetSize(); if (offset > bufferSize) cemuLog_log(LogType::Force, "GX2RSetAttributeBuffer(): Offset exceeds buffer size"); GX2SetAttribBuffer(bufferIndex, bufferSize - offset, stride, ((uint8be*)buffer->GetPtr()) + offset); } void GX2RSetStreamOutBuffer(uint32 bufferIndex, GX2StreamOutBuffer* soBuffer) { // seen in CoD: Ghosts and CoD: Black Ops 2 GX2SetStreamOutBuffer(bufferIndex, soBuffer); } bool GX2RCreateSurface(GX2Surface* surface, uint32 resFlags) { // seen in Transformers Prime surface->resFlag = resFlags; GX2CalcSurfaceSizeAndAlignment(surface); surface->resFlag &= ~GX2R_RESFLAG_LOCKED; surface->resFlag |= GX2R_RESFLAG_ALLOCATED_BY_GX2R; MPTR allocResult = PPCCoreCallback(GX2RAllocateFunc, (uint32)surface->resFlag, (uint32)surface->imageSize + (uint32)surface->mipSize, (uint32)surface->alignment); surface->imagePtr = allocResult; if (surface->imagePtr != MPTR_NULL && surface->mipSize > 0) { surface->mipPtr = (uint32)surface->imagePtr + surface->imageSize; } else { surface->mipPtr = MPTR_NULL; } // todo: Cache invalidation based on resourceFlags? return allocResult != MPTR_NULL; } bool GX2RCreateSurfaceUserMemory(GX2Surface* surface, void* imagePtr, void* mipPtr, uint32 resFlags) { surface->resFlag = resFlags; surface->resFlag &= ~(GX2R_RESFLAG_LOCKED | GX2R_RESFLAG_ALLOCATED_BY_GX2R); GX2CalcSurfaceSizeAndAlignment(surface); surface->imagePtr = memory_getVirtualOffsetFromPointer(imagePtr); surface->mipPtr = memory_getVirtualOffsetFromPointer(mipPtr); if (surface->resFlag & 0x14000) { // memory invalidate } return true; } void GX2RDestroySurfaceEx(GX2Surface* surface, uint32 resFlags) { if ((surface->resFlag & GX2R_RESFLAG_ALLOCATED_BY_GX2R) == 0) { // this surface is user-allocated surface->imagePtr = MPTR_NULL; return; } resFlags &= (GX2R_RESFLAG_UKN_BIT_19 | GX2R_RESFLAG_UKN_BIT_20 | GX2R_RESFLAG_UKN_BIT_21 | GX2R_RESFLAG_UKN_BIT_22 | GX2R_RESFLAG_UKN_BIT_23); PPCCoreCallback(GX2RFreeFunc, (uint32)surface->resFlag | resFlags, (uint32)surface->imagePtr); surface->imagePtr = MPTR_NULL; } bool GX2RSurfaceExists(GX2Surface* surface) { if (!surface) return false; if (surface->imagePtr == MPTR_NULL) return false; if ((surface->resFlag & (GX2R_RESFLAG_USAGE_CPU_READ | GX2R_RESFLAG_USAGE_CPU_WRITE | GX2R_RESFLAG_USAGE_GPU_READ | GX2R_RESFLAG_USAGE_GPU_WRITE)) == 0) return false; return true; } void* GX2RLockSurfaceEx(GX2Surface* surface, uint32 mipLevel, uint32 resFlags) { // todo: handle invalidation surface->resFlag |= GX2R_RESFLAG_LOCKED; return memory_getPointerFromVirtualOffset(surface->imagePtr); } void GX2RUnlockSurfaceEx(GX2Surface* surface, uint32 mipLevel, uint32 resFlags) { // todo: handle invalidation surface->resFlag &= ~GX2R_RESFLAG_LOCKED; } void GX2RBeginDisplayListEx(GX2RBuffer* buffer, bool ukn, uint32 resFlags) { // todo: handle invalidation GX2::GX2BeginDisplayList(buffer->GetPtr(), buffer->GetSize()); } uint32 GX2REndDisplayList(GX2RBuffer* buffer) { return GX2::GX2EndDisplayList(buffer->GetPtr()); } void GX2RCallDisplayList(GX2RBuffer* buffer, uint32 size) { GX2::GX2CallDisplayList(buffer->GetVirtualAddr(), size); } void GX2RDirectCallDisplayList(GX2RBuffer* buffer, uint32 size) { GX2::GX2DirectCallDisplayList(buffer->GetPtr(), size); } void GX2RDrawIndexed(GX2PrimitiveMode2 primitiveMode, GX2RBuffer* indexBuffer, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, uint32 count, uint32 baseIndex, uint32 baseVertex, uint32 numInstances) { GX2DrawIndexedEx(primitiveMode, count, indexType, (uint8be*)indexBuffer->GetPtr() + (baseIndex * (uint32)indexBuffer->elementSize), baseVertex, numInstances); } void GX2ResourceInit() { cafeExportRegister("gx2", GX2RSetAllocator, LogType::GX2); cafeExportRegister("gx2", GX2RGetBufferAllocationSize, LogType::GX2); cafeExportRegister("gx2", GX2RGetBufferAlignment, LogType::GX2); cafeExportRegister("gx2", GX2RCreateBuffer, LogType::GX2); cafeExportRegister("gx2", GX2RCreateBufferUserMemory, LogType::GX2); cafeExportRegister("gx2", GX2RDestroyBufferEx, LogType::GX2); cafeExportRegister("gx2", GX2RBufferExists, LogType::GX2); cafeExportRegister("gx2", GX2RSetBufferName, LogType::GX2); cafeExportRegister("gx2", GX2RLockBufferEx, LogType::GX2); cafeExportRegister("gx2", GX2RUnlockBufferEx, LogType::GX2); cafeExportRegister("gx2", GX2RInvalidateBuffer, LogType::GX2); cafeExportRegister("gx2", GX2RSetAttributeBuffer, LogType::GX2); cafeExportRegister("gx2", GX2RSetStreamOutBuffer, LogType::GX2); cafeExportRegister("gx2", GX2RCreateSurface, LogType::GX2); cafeExportRegister("gx2", GX2RCreateSurfaceUserMemory, LogType::GX2); cafeExportRegister("gx2", GX2RDestroySurfaceEx, LogType::GX2); cafeExportRegister("gx2", GX2RSurfaceExists, LogType::GX2); cafeExportRegister("gx2", GX2RLockSurfaceEx, LogType::GX2); cafeExportRegister("gx2", GX2RUnlockSurfaceEx, LogType::GX2); cafeExportRegister("gx2", GX2RBeginDisplayListEx, LogType::GX2); cafeExportRegister("gx2", GX2REndDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2RCallDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2RDirectCallDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2RDrawIndexed, LogType::GX2); GX2RAllocateFunc = MPTR_NULL; GX2RFreeFunc = MPTR_NULL; } }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Resource.h ================================================ #pragma once // basic resource flags #define GX2_RESFLAG_USAGE_TEXTURE (1<<0) #define GX2_RESFLAG_USAGE_COLOR_BUFFER (1<<1) #define GX2_RESFLAG_USAGE_DEPTH_BUFFER (1<<2) #define GX2_RESFLAG_USAGE_SCAN_BUFFER (1<<3) // extended resource flags used by GX2R API #define GX2R_RESFLAG_USAGE_VERTEX_BUFFER (1<<4) #define GX2R_RESFLAG_USAGE_INDEX_BUFFER (1<<5) #define GX2R_RESFLAG_USAGE_UNIFORM_BLOCK (1<<6) #define GX2R_RESFLAG_USAGE_SHADER_PROGRAM (1<<7) #define GX2R_RESFLAG_USAGE_STREAM_OUTPUT (1<<8) #define GX2R_RESFLAG_USAGE_DISPLAY_LIST (1<<9) #define GX2R_RESFLAG_USAGE_GS_RINGBUFFER (1<<10) #define GX2R_RESFLAG_USAGE_CPU_READ (1<<11) #define GX2R_RESFLAG_USAGE_CPU_WRITE (1<<12) #define GX2R_RESFLAG_USAGE_GPU_READ (1<<13) #define GX2R_RESFLAG_USAGE_GPU_WRITE (1<<14) #define GX2R_RESFLAG_USE_MEM1 (1<<17) #define GX2R_RESFLAG_UKN_BIT_19 (1<<19) #define GX2R_RESFLAG_UKN_BIT_20 (1<<20) #define GX2R_RESFLAG_UKN_BIT_21 (1<<21) #define GX2R_RESFLAG_UKN_BIT_22 (1<<22) #define GX2R_RESFLAG_UKN_BIT_23 (1<<23) #define GX2R_RESFLAG_ALLOCATED_BY_GX2R (1<<29) #define GX2R_RESFLAG_LOCKED (1<<30) struct GX2RBuffer { /* +0x00 */ uint32be resFlags; /* +0x04 */ uint32be elementSize; /* +0x08 */ uint32be elementCount; /* +0x0C */ MEMPTR<void> ptr; uint32 GetSize() const { return (uint32)elementSize * (uint32)elementCount; } MPTR GetVirtualAddr() const { return ptr.GetMPTR(); } void* GetPtr() const { return ptr.GetPtr(); } }; static_assert(sizeof(GX2RBuffer) == 0x10); namespace GX2 { void GX2ResourceInit(); void GX2RSetAllocator(MPTR funcAllocMPTR, MPTR funcFreeMPR); }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Shader.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "GX2.h" #include "GX2_Shader.h" #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/ISA/LatteInstructions.h" uint32 memory_getVirtualOffsetFromPointer(void* ptr); // remove once we updated everything to MEMPTR namespace GX2 { using namespace Latte; LatteConst::VertexFetchEndianMode _getVtxFormatEndianSwapDefault(uint32 vertexFormat) { switch (vertexFormat) { case 0: case 1: case 4: case 10: return LatteConst::VertexFetchEndianMode::SWAP_NONE; // 0 case 2: case 3: case 7: case 8: case 14: case 15: return LatteConst::VertexFetchEndianMode::SWAP_U16; // 1 case 5: case 6: case 9: case 11: case 12: case 13: case 16: case 17: case 18: case 19: return LatteConst::VertexFetchEndianMode::SWAP_U32; // 2 default: break; } cemu_assert_suspicious(); return LatteConst::VertexFetchEndianMode::SWAP_NONE; } uint32 rawFormatToFetchFormat[] = { 1, 2, 5, 6, 7, 0xD, 0xE, 0xF, 0x10, 0x16, 0x1A, 0x19, 0x1D, 0x1E, 0x1F, 0x20, 0x2F, 0x30, 0x22, 0x23, }; struct GX2AttribDescription { /* +0x00 */ uint32 location; /* +0x04 */ uint32 buffer; /* +0x08 */ uint32be offset; /* +0x0C */ uint32 format; /* +0x10 */ uint32 indexType; /* +0x14 */ uint32 aluDivisor; /* +0x18 */ uint32 destSel; /* +0x1C */ betype<LatteConst::VertexFetchEndianMode> endianSwap; }; static_assert(sizeof(GX2AttribDescription) == 0x20); static_assert(sizeof(betype<LatteConst::VertexFetchEndianMode>) == 0x4); // calculate size of CF program subpart, includes alignment padding for clause instructions size_t _calcFetchShaderCFCodeSize(uint32 attributeCount, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { cemu_assert_debug(fetchShaderType == GX2FetchShader::FetchShaderType::NO_TESSELATION); cemu_assert_debug(tessellationMode == 0); uint32 numCFInstructions = ((attributeCount + 15) / 16) + 1; // one VTX clause can have up to 16 instructions + final CF instruction is RETURN size_t cfSize = numCFInstructions * 8; cfSize = (cfSize + 0xF) & ~0xF; // pad to 16 byte alignment return cfSize; } size_t _calcFetchShaderClauseCodeSize(uint32 attributeCount, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { cemu_assert_debug(fetchShaderType == GX2FetchShader::FetchShaderType::NO_TESSELATION); cemu_assert_debug(tessellationMode == 0); uint32 numClauseInstructions = attributeCount; size_t clauseSize = numClauseInstructions * 16; return clauseSize; } void _writeFetchShaderCFCode(void* programBufferOut, uint32 attributeCount, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { LatteCFInstruction* cfInstructionWriter = (LatteCFInstruction*)programBufferOut; uint32 attributeIndex = 0; uint32 cfSize = (uint32)_calcFetchShaderCFCodeSize(attributeCount, fetchShaderType, tessellationMode); while (attributeIndex < attributeCount) { LatteCFInstruction_DEFAULT defaultInstr; defaultInstr.setField_Opcode(LatteCFInstruction::INST_VTX_TC); defaultInstr.setField_COUNT(std::min(attributeCount - attributeIndex, 16u)); defaultInstr.setField_ADDR(cfSize + attributeIndex*16); memcpy(cfInstructionWriter, &defaultInstr, sizeof(LatteCFInstruction)); attributeIndex += 16; cfInstructionWriter++; } // write RETURN instruction LatteCFInstruction_DEFAULT returnInstr; returnInstr.setField_Opcode(LatteCFInstruction::INST_RETURN); returnInstr.setField_BARRIER(true); memcpy(cfInstructionWriter, &returnInstr, sizeof(LatteCFInstruction)); } void _writeFetchShaderVTXCode(GX2FetchShader* fetchShader, void* programOut, uint32 attributeCount, GX2AttribDescription* attributeDescription, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { uint8* writePtr = (uint8*)programOut; // one instruction per attribute (hardcoded into _writeFetchShaderCFCode) for (uint32 i = 0; i < attributeCount; i++) { uint32 attrFormat = _swapEndianU32(attributeDescription[i].format); uint32 attrDestSel = _swapEndianU32(attributeDescription[i].destSel); uint32 attrLocation = _swapEndianU32(attributeDescription[i].location); uint32 attrBufferId = _swapEndianU32(attributeDescription[i].buffer); uint32 attrIndexType = _swapEndianU32(attributeDescription[i].indexType); uint32 attrAluDivisor = _swapEndianU32(attributeDescription[i].aluDivisor); cemu_assert_debug(attrIndexType <= 1); LatteConst::VertexFetchEndianMode endianSwap = attributeDescription[i].endianSwap; if (endianSwap == LatteConst::VertexFetchEndianMode::SWAP_DEFAULT) // use per-format default endianSwap = _getVtxFormatEndianSwapDefault(attrFormat & 0x3F); uint32 srcSelX = 0; // this field is used to store the divisor index/mode (0 -> per-vertex index, 1 -> alu divisor 0, 2 -> alu divisor 1, 3 -> per-instance index) if (attrIndexType == 0) { srcSelX = 0; // increase index per vertex } else if (attrIndexType == 1) { // instance based index if (attrAluDivisor == 1) { // special encoding if alu divisor is 1 srcSelX = 3; } else { cemu_assert_debug(attrAluDivisor != 0); // divisor should not be zero if instance based index is selected? // store alu divisor in divisor table (up to two entries) uint32 numDivisors = _swapEndianU32(fetchShader->divisorCount); bool divisorFound = false; for (uint32 i = 0; i < numDivisors; i++) { if (fetchShader->divisors[i] == attrAluDivisor) { srcSelX = i != 0 ? 2 : 1; divisorFound = true; break; } } if (divisorFound == false) { // add new divisor if (numDivisors >= 2) { cemu_assert_debug(false); // not enough space for additional divisor } else { srcSelX = numDivisors != 0 ? 2 : 1; fetchShader->divisors[numDivisors] = attrAluDivisor; numDivisors++; fetchShader->divisorCount = _swapEndianU32(numDivisors); } } } } else { cemu_assert_debug(false); } // convert attribute format to fetch format uint32 fetchFormat = rawFormatToFetchFormat[attrFormat & 0x3F] & 0x3F; uint32 nfa = 0; if ((attrFormat & 0x800) != 0) nfa = 2; else if ((attrFormat & 0x100) != 0) nfa = 1; else nfa = 0; LatteClauseInstruction_VTX vtxInstruction; vtxInstruction.setField_VTX_INST(LatteClauseInstruction_VTX::VTX_INST::_VTX_INST_SEMANTIC); vtxInstruction.setFieldSEM_SEMANTIC_ID(attrLocation&0xFF); vtxInstruction.setField_BUFFER_ID(attrBufferId + 0xA0); vtxInstruction.setField_FETCH_TYPE((LatteConst::VertexFetchType2)attrIndexType); vtxInstruction.setField_SRC_SEL_X((LatteClauseInstruction_VTX::SRC_SEL)srcSelX); vtxInstruction.setField_DATA_FORMAT((LatteConst::VertexFetchFormat)fetchFormat); vtxInstruction.setField_NUM_FORMAT_ALL((LatteClauseInstruction_VTX::NUM_FORMAT_ALL)nfa); vtxInstruction.setField_OFFSET(attributeDescription[i].offset); if ((attrFormat & 0x200) != 0) vtxInstruction.setField_FORMAT_COMP_ALL(LatteClauseInstruction_VTX::FORMAT_COMP::COMP_SIGNED); vtxInstruction.setField_ENDIAN_SWAP((LatteConst::VertexFetchEndianMode)endianSwap); vtxInstruction.setField_DST_SEL(0, (LatteClauseInstruction_VTX::DST_SEL)((attrDestSel >> 24) & 0x7)); vtxInstruction.setField_DST_SEL(1, (LatteClauseInstruction_VTX::DST_SEL)((attrDestSel >> 16) & 0x7)); vtxInstruction.setField_DST_SEL(2, (LatteClauseInstruction_VTX::DST_SEL)((attrDestSel >> 8) & 0x7)); vtxInstruction.setField_DST_SEL(3, (LatteClauseInstruction_VTX::DST_SEL)((attrDestSel >> 0) & 0x7)); memcpy(writePtr, &vtxInstruction, 16); writePtr += 16; } } uint32 GX2CalcFetchShaderSizeEx(uint32 attributeCount, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { cemu_assert_debug(fetchShaderType == GX2FetchShader::FetchShaderType::NO_TESSELATION); // other types are todo cemu_assert_debug(tessellationMode == 0); // other modes are todo uint32 finalSize = (uint32)_calcFetchShaderCFCodeSize(attributeCount, fetchShaderType, tessellationMode) + (uint32)_calcFetchShaderClauseCodeSize(attributeCount, fetchShaderType, tessellationMode); return finalSize; } void GX2InitFetchShaderEx(GX2FetchShader* fetchShader, void* programBufferOut, uint32 attributeCount, GX2AttribDescription* attributeDescription, GX2FetchShader::FetchShaderType fetchShaderType, uint32 tessellationMode) { cemu_assert_debug(fetchShaderType == GX2FetchShader::FetchShaderType::NO_TESSELATION); cemu_assert_debug(tessellationMode == 0); /* Fetch shader program: [CF_PROGRAM] [Last CF instruction: 0x0 0x8A000000 (INST_RETURN)] [PAD_TO_16_ALIGNMENT] [CLAUSES] */ memset(fetchShader, 0x00, sizeof(GX2FetchShader)); fetchShader->attribCount = _swapEndianU32(attributeCount); fetchShader->shaderPtr = (MPTR)_swapEndianU32(memory_getVirtualOffsetFromPointer(programBufferOut)); uint8* shaderStart = (uint8*)programBufferOut; uint8* shaderOutput = shaderStart; _writeFetchShaderCFCode(shaderOutput, attributeCount, fetchShaderType, tessellationMode); shaderOutput += _calcFetchShaderCFCodeSize(attributeCount, fetchShaderType, tessellationMode); _writeFetchShaderVTXCode(fetchShader, shaderOutput, attributeCount, attributeDescription, fetchShaderType, tessellationMode); shaderOutput += _calcFetchShaderClauseCodeSize(attributeCount, fetchShaderType, tessellationMode); uint32 shaderSize = (uint32)(shaderOutput - shaderStart); cemu_assert_debug(shaderSize == GX2CalcFetchShaderSizeEx(attributeCount, GX2FetchShader::FetchShaderType::NO_TESSELATION, tessellationMode)); fetchShader->shaderSize = _swapEndianU32((uint32)(shaderOutput - shaderStart)); fetchShader->reg_SQ_PGM_RESOURCES_FS = Latte::LATTE_SQ_PGM_RESOURCES_FS().set_NUM_GPRS(2); // todo - affected by tesselation params? } uint32 GX2GetVertexShaderGPRs(GX2VertexShader* vertexShader) { return vertexShader->regs.SQ_PGM_RESOURCES_VS.value().get_NUM_GPRS(); } uint32 GX2GetVertexShaderStackEntries(GX2VertexShader* vertexShader) { return vertexShader->regs.SQ_PGM_RESOURCES_VS.value().get_NUM_STACK_ENTRIES(); } uint32 GX2GetPixelShaderGPRs(GX2PixelShader_t* pixelShader) { return _swapEndianU32(pixelShader->regs[0])&0xFF; } uint32 GX2GetPixelShaderStackEntries(GX2PixelShader_t* pixelShader) { return (_swapEndianU32(pixelShader->regs[0]>>8))&0xFF; } void GX2SetFetchShader(GX2FetchShader* fetchShaderPtr) { GX2ReserveCmdSpace(11); cemu_assert_debug((_swapEndianU32(fetchShaderPtr->shaderPtr) & 0xFF) == 0); gx2WriteGather_submit( // setup fetch shader pm4HeaderType3(IT_SET_CONTEXT_REG, 1+5), Latte::REGADDR::SQ_PGM_START_FS-0xA000, _swapEndianU32(fetchShaderPtr->shaderPtr)>>8, _swapEndianU32(fetchShaderPtr->shaderSize)>>3, 0x10000, // ukn (ring buffer size?) 0x10000, // ukn (ring buffer size?) fetchShaderPtr->reg_SQ_PGM_RESOURCES_FS, // write instance step pm4HeaderType3(IT_SET_CONTEXT_REG, 1+2), Latte::REGADDR::VGT_INSTANCE_STEP_RATE_0-0xA000, fetchShaderPtr->divisors[0], fetchShaderPtr->divisors[1]); } void GX2SetVertexShader(GX2VertexShader* vertexShader) { uint32 numOutputIds = vertexShader->regs.vsOutIdTableSize; numOutputIds = std::min<uint32>(numOutputIds, 0xA); uint32 vsSemanticTableSize = vertexShader->regs.semanticTableSize; uint32 reserveSize = 31; if (vertexShader->shaderMode == GX2_SHADER_MODE::GEOMETRY_SHADER) { reserveSize += 7; } else { reserveSize += 18; reserveSize += numOutputIds; if (vertexShader->usesStreamOut != 0) reserveSize += 2+12; } if (vsSemanticTableSize > 0) { reserveSize += 5 + vsSemanticTableSize; } GX2ReserveCmdSpace(reserveSize); MPTR shaderProgramAddr; uint32 shaderProgramSize; if (vertexShader->shaderPtr) { // without R API shaderProgramAddr = vertexShader->shaderPtr.GetMPTR(); shaderProgramSize = vertexShader->shaderSize; } else { shaderProgramAddr = vertexShader->rBuffer.GetVirtualAddr(); shaderProgramSize = vertexShader->rBuffer.GetSize(); } cemu_assert_debug(shaderProgramAddr != 0); cemu_assert_debug(shaderProgramSize != 0); if (vertexShader->shaderMode == GX2_SHADER_MODE::GEOMETRY_SHADER) { // in geometry shader mode the vertex shader is written to _ES register and almost all vs control registers are set by GX2SetGeometryShader gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 6), Latte::REGADDR::SQ_PGM_START_ES-0xA000, memory_virtualToPhysical(shaderProgramAddr)>>8, shaderProgramSize>>3, 0x100000, 0x100000, vertexShader->regs.SQ_PGM_RESOURCES_VS); // SQ_PGM_RESOURCES_VS/SQ_PGM_RESOURCES_ES } else { gx2WriteGather_submit( /* vertex shader program */ pm4HeaderType3(IT_SET_CONTEXT_REG, 6), Latte::REGADDR::SQ_PGM_START_VS-0xA000, memory_virtualToPhysical(shaderProgramAddr)>>8, // physical address shaderProgramSize>>3, 0x100000, 0x100000, vertexShader->regs.SQ_PGM_RESOURCES_VS, // SQ_PGM_RESOURCES_VS/ES /* primitive id enable */ pm4HeaderType3(IT_SET_CONTEXT_REG, 2), Latte::REGADDR::VGT_PRIMITIVEID_EN-0xA000, vertexShader->regs.VGT_PRIMITIVEID_EN, /* output config */ pm4HeaderType3(IT_SET_CONTEXT_REG, 2), Latte::REGADDR::SPI_VS_OUT_CONFIG-0xA000, vertexShader->regs.SPI_VS_OUT_CONFIG, /* PA_CL_VS_OUT_CNTL */ pm4HeaderType3(IT_SET_CONTEXT_REG, 2), Latte::REGADDR::PA_CL_VS_OUT_CNTL-0xA000, vertexShader->regs.PA_CL_VS_OUT_CNTL ); cemu_assert_debug(vertexShader->regs.SPI_VS_OUT_CONFIG.value().get_VS_PER_COMPONENT() == false); // not handled on the GPU side gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numOutputIds)); gx2WriteGather_submitU32AsBE(Latte::REGADDR::SPI_VS_OUT_ID_0-0xA000); for(uint32 i=0; i<numOutputIds; i++) gx2WriteGather_submitU32AsBE(vertexShader->regs.LATTE_SPI_VS_OUT_ID_N[i].value().getRawValue()); // todo: SQ_PGM_CF_OFFSET_VS // todo: VGT_STRMOUT_BUFFER_EN // stream out if (vertexShader->usesStreamOut != 0) { // stride 0 gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 2), Latte::REGADDR::VGT_STRMOUT_VTX_STRIDE_0-0xA000, vertexShader->streamOutVertexStride[0]>>2, // stride 1 pm4HeaderType3(IT_SET_CONTEXT_REG, 2), Latte::REGADDR::VGT_STRMOUT_VTX_STRIDE_1-0xA000, vertexShader->streamOutVertexStride[1]>>2, // stride 2 pm4HeaderType3(IT_SET_CONTEXT_REG, 2), Latte::REGADDR::VGT_STRMOUT_VTX_STRIDE_2-0xA000, vertexShader->streamOutVertexStride[2]>>2, // stride 3 pm4HeaderType3(IT_SET_CONTEXT_REG, 2), Latte::REGADDR::VGT_STRMOUT_VTX_STRIDE_3-0xA000, vertexShader->streamOutVertexStride[3]>>2); } } // update semantic table if (vsSemanticTableSize > 0) { gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1), Latte::REGADDR::SQ_VTX_SEMANTIC_CLEAR-0xA000, 0xFFFFFFFF); if (vsSemanticTableSize == 0) { gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1), Latte::REGADDR::SQ_VTX_SEMANTIC_0-0xA000, 0xFFFFFFFF); } else { uint32* vsSemanticTable = (uint32*)vertexShader->regs.SQ_VTX_SEMANTIC_N; vsSemanticTableSize = std::min<uint32>(vsSemanticTableSize, 32); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+vsSemanticTableSize)); gx2WriteGather_submitU32AsBE(Latte::REGADDR::SQ_VTX_SEMANTIC_0-0xA000); gx2WriteGather_submitU32AsLEArray(vsSemanticTable, vsSemanticTableSize); } } } void _GX2SubmitUniformReg(uint32 offsetRegBase, uint32 aluRegisterOffset, uint32be* dataWords, uint32 sizeInU32s) { if(aluRegisterOffset&0x8000) { cemuLog_logDebugOnce(LogType::Force, "_GX2SubmitUniformReg(): Unhandled loop const special case or invalid offset"); return; } if((aluRegisterOffset+sizeInU32s) > 0x400) { cemuLog_logOnce(LogType::APIErrors, "GX2SetVertexUniformReg values are out of range (offset {} + size {} must be equal or smaller than 1024)", aluRegisterOffset, sizeInU32s); } if( (sizeInU32s&3) != 0) { cemuLog_logOnce(LogType::APIErrors, "GX2Set*UniformReg must be called with a size that is a multiple of 4 (size: {:})", sizeInU32s); sizeInU32s &= ~3; } GX2ReserveCmdSpace(2 + sizeInU32s); gx2WriteGather_submit(pm4HeaderType3(IT_SET_ALU_CONST, 1 + sizeInU32s), offsetRegBase + aluRegisterOffset); gx2WriteGather_submitU32AsLEArray((uint32*)dataWords, sizeInU32s); } void GX2SetVertexUniformReg(uint32 offset, uint32 sizeInU32s, uint32be* values) { _GX2SubmitUniformReg(0x400, offset, values, sizeInU32s); } void GX2SetPixelUniformReg(uint32 offset, uint32 sizeInU32s, uint32be* values) { _GX2SubmitUniformReg(0, offset, values, sizeInU32s); } void GX2ShaderInit() { cafeExportRegister("gx2", GX2CalcFetchShaderSizeEx, LogType::GX2); cafeExportRegister("gx2", GX2InitFetchShaderEx, LogType::GX2); cafeExportRegister("gx2", GX2GetVertexShaderGPRs, LogType::GX2); cafeExportRegister("gx2", GX2GetVertexShaderStackEntries, LogType::GX2); cafeExportRegister("gx2", GX2GetPixelShaderGPRs, LogType::GX2); cafeExportRegister("gx2", GX2GetPixelShaderStackEntries, LogType::GX2); cafeExportRegister("gx2", GX2SetFetchShader, LogType::GX2); cafeExportRegister("gx2", GX2SetVertexShader, LogType::GX2); cafeExportRegister("gx2", GX2SetVertexUniformReg, LogType::GX2); cafeExportRegister("gx2", GX2SetPixelUniformReg, LogType::GX2); } } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Shader.h ================================================ #pragma once #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "GX2_Streamout.h" struct GX2FetchShader { enum class FetchShaderType : uint32 { NO_TESSELATION = 0, }; /* +0x00 */ betype<FetchShaderType> fetchShaderType; /* +0x04 */ betype<Latte::LATTE_SQ_PGM_RESOURCES_FS> reg_SQ_PGM_RESOURCES_FS; /* +0x08 */ uint32 shaderSize; /* +0x0C */ MPTR shaderPtr; /* +0x10 */ uint32 attribCount; /* +0x14 */ uint32 divisorCount; /* +0x18 */ uint32be divisors[2]; MPTR GetProgramAddr() const { return _swapEndianU32(shaderPtr); } }; static_assert(sizeof(GX2FetchShader) == 0x20); static_assert(sizeof(betype<GX2FetchShader::FetchShaderType>) == 4); namespace GX2 { void GX2ShaderInit(); } // code below still needs to be modernized (use betype, enum classes, move to namespace) // deprecated, use GX2_SHADER_MODE enum class instead #define GX2_SHADER_MODE_UNIFORM_REGISTER 0 #define GX2_SHADER_MODE_UNIFORM_BLOCK 1 #define GX2_SHADER_MODE_GEOMETRY_SHADER 2 #define GX2_SHADER_MODE_COMPUTE_SHADER 3 enum class GX2_SHADER_MODE : uint32 { UNIFORM_REGISTER = 0, UNIFORM_BLOCK = 1, GEOMETRY_SHADER = 2, COMPUTE_SHADER = 3, }; struct GX2VertexShader { /* +0x000 */ struct { /* +0x00 */ betype<Latte::LATTE_SQ_PGM_RESOURCES_VS> SQ_PGM_RESOURCES_VS; // compatible with SQ_PGM_RESOURCES_ES /* +0x04 */ betype<Latte::LATTE_VGT_PRIMITIVEID_EN> VGT_PRIMITIVEID_EN; /* +0x08 */ betype<Latte::LATTE_SPI_VS_OUT_CONFIG> SPI_VS_OUT_CONFIG; /* +0x0C */ uint32be vsOutIdTableSize; /* +0x10 */ betype<Latte::LATTE_SPI_VS_OUT_ID_N> LATTE_SPI_VS_OUT_ID_N[10]; /* +0x38 */ betype<Latte::LATTE_PA_CL_VS_OUT_CNTL> PA_CL_VS_OUT_CNTL; /* +0x3C */ uint32be uknReg15; // ? /* +0x40 */ uint32be semanticTableSize; /* +0x44 */ betype<Latte::LATTE_SQ_VTX_SEMANTIC_X> SQ_VTX_SEMANTIC_N[32]; /* +0xC4 */ uint32be uknReg49; // ? /* +0xC8 */ uint32be uknReg50; // vgt_vertex_reuse_block_cntl /* +0xCC */ uint32be uknReg51; // vgt_hos_reuse_depth }regs; /* +0x0D0 */ uint32be shaderSize; /* +0x0D4 */ MEMPTR<void> shaderPtr; /* +0x0D8 */ betype<GX2_SHADER_MODE> shaderMode; /* +0x0DC */ uint32 uniformBlockCount; /* +0x0E0 */ MPTR uniformBlockInfo; /* +0x0E4 */ uint32 uniformVarCount; /* +0x0E8 */ MPTR uniformVarInfo; /* +0x0EC */ uint32 uknEC; /* +0x0F0 */ MPTR uknF0; /* +0x0F4 */ uint32 uknF4; /* +0x0F8 */ MPTR uknF8; // each entry has 8 byte? /* +0x0FC */ uint32 samplerCount; /* +0x100 */ MPTR samplerInfo; /* +0x104 */ uint32 attribCount; /* +0x108 */ MPTR attribInfo; /* +0x10C */ uint32be ringItemsize; // for GS /* +0x110 */ uint32be usesStreamOut; /* +0x114 */ uint32be streamOutVertexStride[GX2_MAX_STREAMOUT_BUFFERS]; /* +0x124 */ GX2RBuffer rBuffer; MPTR GetProgramAddr() const { if (this->shaderPtr) return this->shaderPtr.GetMPTR(); return this->rBuffer.GetVirtualAddr(); } }; static_assert(sizeof(GX2VertexShader) == 0x134); typedef struct _GX2PixelShader { uint32 regs[41]; // regs: // 0 ? Used by GPR count API? // 1 ? // 2 mmSPI_PS_IN_CONTROL_0 // 3 mmSPI_PS_IN_CONTROL_1 // 4 numInputs // 5 mmSPI_PS_INPUT_CNTL_0 // ... // 36 mmSPI_PS_INPUT_CNTL_31 // 37 mmCB_SHADER_MASK // 38 mmCB_SHADER_CONTROL // 39 mmDB_SHADER_CONTROL // 40 mmSPI_INPUT_Z /* +0xA4 */ uint32 shaderSize; /* +0xA8 */ MPTR shaderPtr; /* +0xAC */ uint32 shaderMode; /* +0xB0 */ uint32 uniformBlockCount; /* +0xB4 */ MPTR uniformBlockInfo; /* +0xB8 */ uint32 uniformVarCount; /* +0xBC */ MPTR uniformVarInfo; /* +0xC0 */ uint32 uknC0; /* +0xC4 */ MPTR uknC4; /* +0xC8 */ uint32 uknC8; /* +0xCC */ MPTR uknCC; /* +0xD0 */ uint32 samplerCount; /* +0xD4 */ MPTR samplerInfo; /* +0xD8 */ GX2RBuffer rBuffer; MPTR GetProgramAddr() const { if (_swapEndianU32(shaderPtr) != MPTR_NULL) return _swapEndianU32(shaderPtr); return rBuffer.GetVirtualAddr(); } }GX2PixelShader_t; static_assert(sizeof(GX2PixelShader_t) == 0xE8); struct GX2GeometryShader_t { union { /* +0x00 */ uint32 regs[19]; struct { uint32be reg0; uint32be reg1; uint32be VGT_GS_MODE; uint32be reg3; uint32be reg4; uint32be reg5; uint32be reg6; uint32be reg7; // todo }reg; }; /* +0x4C */ uint32 shaderSize; /* +0x50 */ MPTR shaderPtr; /* +0x54 */ uint32 copyShaderSize; /* +0x58 */ MPTR copyShaderPtr; /* +0x5C */ uint32 shaderMode; /* +0x60 */ uint32 uniformBlockCount; /* +0x64 */ MPTR uniformBlockInfo; /* +0x68 */ uint32 uniformVarCount; /* +0x6C */ MPTR uniformVarInfo; /* +0x70 */ uint32 ukn70; /* +0x74 */ MPTR ukn74; /* +0x78 */ uint32 ukn78; /* +0x7C */ MPTR ukn7C; /* +0x80 */ uint32 samplerCount; /* +0x84 */ MPTR samplerInfo; /* +0x88 */ uint32 ringItemsize; /* +0x8C */ uint32 useStreamout; /* +0x90 */ uint32 streamoutStride[GX2_MAX_STREAMOUT_BUFFERS]; /* +0xA0 */ GX2RBuffer rBuffer; /* +0xB0 */ GX2RBuffer rBufferCopyProgram; MPTR GetGeometryProgramAddr() const { if (_swapEndianU32(shaderPtr) != MPTR_NULL) return _swapEndianU32(shaderPtr); return rBuffer.GetVirtualAddr(); } MPTR GetCopyProgramAddr() const { if (_swapEndianU32(copyShaderPtr) != MPTR_NULL) return _swapEndianU32(copyShaderPtr); return rBufferCopyProgram.GetVirtualAddr(); } }; static_assert(sizeof(GX2GeometryShader_t) == 0xC0); ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_State.cpp ================================================ #include "Common/precompiled.h" #include "GX2_State.h" #include "GX2_Command.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/OS/common/OSCommon.h" namespace GX2 { using namespace Latte; void GX2InitAlphaTestReg(GX2AlphaTestReg* reg, uint32 alphaTestEnable, GX2_ALPHAFUNC alphaFunc, float alphaRef) { Latte::LATTE_SX_ALPHA_TEST_CONTROL tmpRegCtrl; tmpRegCtrl.set_ALPHA_FUNC(alphaFunc); tmpRegCtrl.set_ALPHA_TEST_ENABLE(alphaTestEnable != 0); reg->regAlphaTestControl = tmpRegCtrl; Latte::LATTE_SX_ALPHA_REF tmpRegRef; tmpRegRef.set_ALPHA_TEST_REF(alphaRef); reg->regAlphaTestRef = tmpRegRef; } void GX2SetAlphaTestReg(GX2AlphaTestReg* reg) { GX2ReserveCmdSpace(3 + 3); gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::SX_ALPHA_TEST_CONTROL - 0xA000, reg->regAlphaTestControl, pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::SX_ALPHA_REF - 0xA000, reg->regAlphaTestRef); } void GX2SetAlphaTest(uint32 alphaTestEnable, GX2_ALPHAFUNC alphaFunc, float alphaRef) { GX2AlphaTestReg tmpReg; GX2InitAlphaTestReg(&tmpReg, alphaTestEnable, alphaFunc, alphaRef); GX2SetAlphaTestReg(&tmpReg); } void GX2InitColorControlReg(GX2ColorControlReg* reg, GX2_LOGICOP logicOp, uint32 blendMask, uint32 multiwriteEnable, uint32 colorBufferEnable) { Latte::LATTE_CB_COLOR_CONTROL colorControlReg2; colorControlReg2.set_MULTIWRITE_ENABLE(multiwriteEnable != 0); if (colorBufferEnable == 0) colorControlReg2.set_SPECIAL_OP(Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::DISABLE); else colorControlReg2.set_SPECIAL_OP(Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::NORMAL); colorControlReg2.set_BLEND_MASK(blendMask); colorControlReg2.set_ROP(logicOp); reg->reg = colorControlReg2; } void GX2SetColorControlReg(GX2ColorControlReg* reg) { GX2ReserveCmdSpace(3); gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::CB_COLOR_CONTROL - 0xA000, reg->reg); } void GX2SetColorControl(GX2_LOGICOP logicOp, uint32 blendMask, uint32 multiwriteEnable, uint32 colorBufferEnable) { GX2ColorControlReg colorControlReg; GX2InitColorControlReg(&colorControlReg, logicOp, blendMask, multiwriteEnable, colorBufferEnable); GX2SetColorControlReg(&colorControlReg); } void GX2InitPolygonControlReg(GX2PolygonControlReg* reg, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE usePolygonMode, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeFront, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeBack, uint32 polygonOffsetFrontEnable, uint32 polygonOffsetBackEnable, uint32 paraOffsetEnable) { Latte::LATTE_PA_SU_SC_MODE_CNTL v; v.set_FRONT_FACE(frontFace); v.set_CULL_FRONT((cullFront & 1) != 0); v.set_CULL_BACK((cullBack & 1) != 0); v.set_POLYGON_MODE(usePolygonMode); v.set_FRONT_POLY_MODE(polyModeFront); v.set_BACK_POLY_MODE(polyModeBack); v.set_OFFSET_PARA_ENABLED((paraOffsetEnable & 1) != 0); v.set_OFFSET_FRONT_ENABLED((polygonOffsetFrontEnable & 1) != 0); v.set_OFFSET_BACK_ENABLED((polygonOffsetBackEnable & 1) != 0); reg->reg = v; } void GX2SetPolygonControlReg(GX2PolygonControlReg* reg) { GX2ReserveCmdSpace(3); gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::PA_SU_SC_MODE_CNTL - 0xA000, reg->reg); } void GX2SetPolygonControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE usePolygonMode, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeFront, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeBack, uint32 polygonOffsetFrontEnable, uint32 polygonOffsetBackEnable, uint32 paraOffsetEnable) { GX2PolygonControlReg reg{}; GX2InitPolygonControlReg(®, frontFace, cullFront, cullBack, usePolygonMode, polyModeFront, polyModeBack, polygonOffsetFrontEnable, polygonOffsetBackEnable, paraOffsetEnable); GX2SetPolygonControlReg(®); } void GX2SetCullOnlyControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack) { GX2PolygonControlReg reg{}; GX2InitPolygonControlReg(®, frontFace, cullFront, cullBack, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE::UKN0, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::POINTS, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::POINTS, 0, 0, 0); GX2SetPolygonControlReg(®); } void GX2InitPolygonOffsetReg(GX2PolygonOffsetReg* reg, float frontOffset, float frontScale, float backOffset, float backScale, float clampOffset) { frontScale *= 16.0; backScale *= 16.0; reg->regFrontScale = Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_SCALE().set_SCALE(frontScale); reg->regFrontOffset = Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_OFFSET().set_OFFSET(frontOffset); reg->regBackScale = Latte::LATTE_PA_SU_POLY_OFFSET_BACK_SCALE().set_SCALE(backScale); reg->regBackOffset = Latte::LATTE_PA_SU_POLY_OFFSET_BACK_OFFSET().set_OFFSET(backOffset); reg->regClamp = Latte::LATTE_PA_SU_POLY_OFFSET_CLAMP().set_CLAMP(clampOffset); } void GX2SetPolygonOffsetReg(GX2PolygonOffsetReg* reg) { GX2ReserveCmdSpace(6 + 3); gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 4), Latte::REGADDR::PA_SU_POLY_OFFSET_FRONT_SCALE - 0xA000, reg->regFrontScale, reg->regFrontOffset, reg->regBackScale, reg->regBackOffset, pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::PA_SU_POLY_OFFSET_CLAMP - 0xA000, reg->regClamp); } void GX2SetPolygonOffset(float frontOffset, float frontScale, float backOffset, float backScale, float clampOffset) { GX2PolygonOffsetReg tmpReg; GX2InitPolygonOffsetReg(&tmpReg, frontOffset, frontScale, backOffset, backScale, clampOffset); GX2SetPolygonOffsetReg(&tmpReg); } void GX2SetRasterizerClipControlEx(bool enableRasterizer, bool enableZClip, bool enableHalfZ) { GX2ReserveCmdSpace(3); //if (enableHalfZ) //{ // // Smash has a bug where it enables half space clipping during streamout drawcalls and shadowing and then doesn't turn it off until the next GX2SetRasterizerClipControl call // // this leads to some stuff being rendered at the wrong z-plane (e.g. shields behind characters) if the game's default depth range -1 to 1 isn't supported (on OpenGL only Nvidia's glDepthRangedNV allows unclamped values) // uint64 titleId = gameMeta_getTitleId(); // if (titleId == 0x0005000010144F00ULL || // titleId == 0x0005000010145000ULL || // titleId == 0x0005000010110E00ULL) // { // // force disable half space clipping // if (g_renderer && g_renderer->GetType() == RendererAPI::OpenGL && LatteGPUState.glVendor != GLVENDOR_NVIDIA) // enableHalfZ = false; // } //} Latte::LATTE_PA_CL_CLIP_CNTL reg{}; reg.set_ZCLIP_NEAR_DISABLE(!enableZClip).set_ZCLIP_FAR_DISABLE(!enableZClip); reg.set_DX_RASTERIZATION_KILL(!enableRasterizer); reg.set_DX_CLIP_SPACE_DEF(enableHalfZ); reg.set_DX_LINEAR_ATTR_CLIP_ENA(true); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::PA_CL_CLIP_CNTL - 0xA000, reg); } void GX2SetRasterizerClipControl(bool enableRasterizer, bool enableZClip) { GX2SetRasterizerClipControlEx(enableRasterizer, enableZClip, false); } void GX2SetRasterizerClipControlHalfZ(bool enableRasterizer, bool enableZClip, bool enableHalfZ) { GX2SetRasterizerClipControlEx(enableRasterizer, enableZClip, enableHalfZ); } void GX2InitViewportReg(GX2ViewportReg* viewportReg, float x, float y, float width, float height, float nearZ, float farZ) { // todo: set clipping registers and zMin/zMax registers viewportReg->xScale = Latte::LATTE_PA_CL_VPORT_XSCALE().set_SCALE(width * 0.5f); viewportReg->xOffset = Latte::LATTE_PA_CL_VPORT_XOFFSET().set_OFFSET(x + (width * 0.5f)); viewportReg->yScale = Latte::LATTE_PA_CL_VPORT_YSCALE().set_SCALE(height * -0.5f); viewportReg->yOffset = Latte::LATTE_PA_CL_VPORT_YOFFSET().set_OFFSET(y + (height * 0.5f)); viewportReg->zScale = Latte::LATTE_PA_CL_VPORT_ZSCALE().set_SCALE((farZ - nearZ) * 0.5f); viewportReg->zOffset = Latte::LATTE_PA_CL_VPORT_ZOFFSET().set_OFFSET((nearZ + farZ) * 0.5f); } void GX2SetViewportReg(GX2ViewportReg* viewportReg) { GX2ReserveCmdSpace(2 + 6); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 6), Latte::REGADDR::PA_CL_VPORT_XSCALE - 0xA000, viewportReg->xScale, viewportReg->xOffset, viewportReg->yScale, viewportReg->yOffset, viewportReg->zScale, viewportReg->zOffset); } void GX2SetViewport(float x, float y, float width, float height, float nearZ, float farZ) { GX2ViewportReg viewportReg; GX2InitViewportReg(&viewportReg, x, y, width, height, nearZ, farZ); GX2SetViewportReg(&viewportReg); } void GX2InitScissorReg(GX2ScissorReg* scissorReg, uint32 x, uint32 y, uint32 width, uint32 height) { uint32 tlx = x; uint32 tly = y; uint32 brx = x + width; uint32 bry = y + height; tlx = std::min(tlx, 8192u); tly = std::min(tly, 8192u); brx = std::min(brx, 8192u); bry = std::min(bry, 8192u); scissorReg->scissorTL = Latte::LATTE_PA_SC_GENERIC_SCISSOR_TL().set_TL_X(tlx).set_TL_Y(tly).set_WINDOW_OFFSET_DISABLE(true); scissorReg->scissorBR = Latte::LATTE_PA_SC_GENERIC_SCISSOR_BR().set_BR_X(brx).set_BR_Y(bry); } void GX2SetScissorReg(GX2ScissorReg* scissorReg) { GX2ReserveCmdSpace(4); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 2), Latte::REGADDR::PA_SC_GENERIC_SCISSOR_TL - 0xA000, scissorReg->scissorTL, scissorReg->scissorBR); } void GX2GetScissorReg(GX2ScissorReg* scissorReg, uint32be* x, uint32be* y, uint32be* width, uint32be* height) { *x = scissorReg->scissorTL.value().get_TL_X(); *y = scissorReg->scissorTL.value().get_TL_Y(); *width = scissorReg->scissorBR.value().get_BR_X() - scissorReg->scissorTL.value().get_TL_X(); *height = scissorReg->scissorBR.value().get_BR_Y() - scissorReg->scissorTL.value().get_TL_Y(); } void GX2SetScissor(uint32 x, uint32 y, uint32 width, uint32 height) { GX2ScissorReg scissorReg; GX2InitScissorReg(&scissorReg, x, y, width, height); GX2SetScissorReg(&scissorReg); } void GX2SetDepthOnlyControl(bool depthTestEnable, bool depthWriteEnable, LATTE_DB_DEPTH_CONTROL::E_ZFUNC depthFunction) { // disables any currently set stencil test GX2ReserveCmdSpace(3); Latte::LATTE_DB_DEPTH_CONTROL reg{}; reg.set_Z_ENABLE(depthTestEnable); reg.set_Z_WRITE_ENABLE(depthWriteEnable); reg.set_Z_FUNC(depthFunction); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::DB_DEPTH_CONTROL - 0xA000, reg); } void GX2SetDepthStencilControl( bool depthTestEnable, bool depthWriteEnable, LATTE_DB_DEPTH_CONTROL::E_ZFUNC depthFunction, bool stencilTestEnable, bool backStencilTestEnable, LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC frontStencilFunction, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilFail, LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC backStencilFunction, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilFail ) { GX2ReserveCmdSpace(3); Latte::LATTE_DB_DEPTH_CONTROL reg{}; reg.set_Z_ENABLE(depthTestEnable).set_Z_WRITE_ENABLE(depthWriteEnable).set_Z_FUNC(depthFunction); reg.set_STENCIL_ENABLE(stencilTestEnable).set_BACK_STENCIL_ENABLE(backStencilTestEnable); reg.set_STENCIL_FUNC_F(frontStencilFunction).set_STENCIL_FUNC_B(backStencilFunction); reg.set_STENCIL_ZPASS_F(frontStencilZPass).set_STENCIL_ZFAIL_F(frontStencilZFail).set_STENCIL_FAIL_F(frontStencilFail); reg.set_STENCIL_ZPASS_B(backStencilZPass).set_STENCIL_ZFAIL_B(backStencilZFail).set_STENCIL_FAIL_B(backStencilFail); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::DB_DEPTH_CONTROL - 0xA000, reg); } void GX2InitDepthStencilControlReg( GX2DepthStencilControlReg* depthStencilControlReg, bool depthTestEnable, bool depthWriteEnable, LATTE_DB_DEPTH_CONTROL::E_ZFUNC depthFunction, bool stencilTestEnable, bool backStencilTestEnable, LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC frontStencilFunction, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilFail, LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC backStencilFunction, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilFail) { Latte::LATTE_DB_DEPTH_CONTROL reg{}; reg.set_Z_ENABLE(depthTestEnable).set_Z_WRITE_ENABLE(depthWriteEnable).set_Z_FUNC(depthFunction); reg.set_STENCIL_ENABLE(stencilTestEnable).set_BACK_STENCIL_ENABLE(backStencilTestEnable); reg.set_STENCIL_FUNC_F(frontStencilFunction).set_STENCIL_FUNC_B(backStencilFunction); reg.set_STENCIL_ZPASS_F(frontStencilZPass).set_STENCIL_ZFAIL_F(frontStencilZFail).set_STENCIL_FAIL_F(frontStencilFail); reg.set_STENCIL_ZPASS_B(backStencilZPass).set_STENCIL_ZFAIL_B(backStencilZFail).set_STENCIL_FAIL_B(backStencilFail); depthStencilControlReg->reg = reg; } void GX2SetDepthStencilControlReg(GX2DepthStencilControlReg* depthStencilControlReg) { GX2ReserveCmdSpace(3); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::DB_DEPTH_CONTROL - 0xA000, depthStencilControlReg->reg); } void GX2GetDepthStencilControlReg( GX2DepthStencilControlReg* depthStencilControlReg, uint32be* depthTestEnable, uint32be* depthWriteEnable, uint32be* depthFunction, uint32be* stencilTestEnable, uint32be* backStencilTestEnable, uint32be* frontStencilFunction, uint32be* frontStencilZPass, uint32be* frontStencilZFail, uint32be* frontStencilFail, uint32be* backStencilFunction, uint32be* backStencilZPass, uint32be* backStencilZFail, uint32be* backStencilFail) { // used by Hyrule Warriors *depthTestEnable = depthStencilControlReg->reg.value().get_Z_ENABLE(); *depthWriteEnable = depthStencilControlReg->reg.value().get_Z_WRITE_ENABLE(); *depthFunction = (uint32)depthStencilControlReg->reg.value().get_Z_FUNC(); *stencilTestEnable = depthStencilControlReg->reg.value().get_STENCIL_ENABLE(); *backStencilTestEnable = depthStencilControlReg->reg.value().get_BACK_STENCIL_ENABLE(); *frontStencilFunction = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FUNC_F(); *backStencilFunction = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FUNC_B(); *frontStencilZPass = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZPASS_F(); *frontStencilZFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZFAIL_F(); *frontStencilFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FAIL_F(); *backStencilZPass = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZPASS_B(); *backStencilZFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZFAIL_B(); *backStencilFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FAIL_B(); } void GX2InitStencilMaskReg(GX2StencilMaskReg* stencilMaskReg, uint8 compareMaskFront, uint8 writeMaskFront, uint8 refFront, uint8 compareMaskBack, uint8 writeMaskBack, uint8 refBack) { stencilMaskReg->stencilRefMaskFrontReg = LATTE_DB_STENCILREFMASK().set_STENCILREF_F(refFront).set_STENCILMASK_F(compareMaskFront).set_STENCILWRITEMASK_F(writeMaskFront); stencilMaskReg->stencilRefMaskBackReg = LATTE_DB_STENCILREFMASK_BF().set_STENCILREF_B(refBack).set_STENCILMASK_B(compareMaskBack).set_STENCILWRITEMASK_B(writeMaskBack); } void GX2SetStencilMask(uint8 compareMaskFront, uint8 writeMaskFront, uint8 refFront, uint8 compareMaskBack, uint8 writeMaskBack, uint8 refBack) { GX2ReserveCmdSpace(3 + 3); LATTE_DB_STENCILREFMASK frontReg; frontReg.set_STENCILREF_F(refFront).set_STENCILMASK_F(compareMaskFront).set_STENCILWRITEMASK_F(writeMaskFront); LATTE_DB_STENCILREFMASK_BF backReg; backReg.set_STENCILREF_B(refBack).set_STENCILMASK_B(compareMaskBack).set_STENCILWRITEMASK_B(writeMaskBack); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), REGADDR::DB_STENCILREFMASK - 0xA000, frontReg, pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), REGADDR::DB_STENCILREFMASK_BF - 0xA000, backReg); } void GX2SetStencilMaskReg(GX2StencilMaskReg* stencilMaskReg) { GX2ReserveCmdSpace(3 + 3); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), REGADDR::DB_STENCILREFMASK - 0xA000, stencilMaskReg->stencilRefMaskFrontReg, pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), REGADDR::DB_STENCILREFMASK_BF - 0xA000, stencilMaskReg->stencilRefMaskBackReg); } void GX2SetPrimitiveRestartIndex(uint32 restartIndex) { GX2ReserveCmdSpace(3); Latte::LATTE_VGT_MULTI_PRIM_IB_RESET_INDX reg{}; reg.set_RESTART_INDEX(restartIndex); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::VGT_MULTI_PRIM_IB_RESET_INDX - 0xA000, reg); } void GX2InitTargetChannelMasksReg(GX2TargetChannelMaskReg* reg, GX2_CHANNELMASK t0, GX2_CHANNELMASK t1, GX2_CHANNELMASK t2, GX2_CHANNELMASK t3, GX2_CHANNELMASK t4, GX2_CHANNELMASK t5, GX2_CHANNELMASK t6, GX2_CHANNELMASK t7) { uint32 targetMask = 0; targetMask |= ((t0 & 0xF) << 0); targetMask |= ((t1 & 0xF) << 4); targetMask |= ((t2 & 0xF) << 8); targetMask |= ((t3 & 0xF) << 12); targetMask |= ((t4 & 0xF) << 16); targetMask |= ((t5 & 0xF) << 20); targetMask |= ((t6 & 0xF) << 24); targetMask |= ((t7 & 0xF) << 28); Latte::LATTE_CB_TARGET_MASK r; r.set_MASK(targetMask); reg->reg = r; } void GX2SetTargetChannelMasksReg(GX2TargetChannelMaskReg* reg) { GX2ReserveCmdSpace(3); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::CB_TARGET_MASK - 0xA000, reg->reg); } void GX2SetTargetChannelMasks(GX2_CHANNELMASK t0, GX2_CHANNELMASK t1, GX2_CHANNELMASK t2, GX2_CHANNELMASK t3, GX2_CHANNELMASK t4, GX2_CHANNELMASK t5, GX2_CHANNELMASK t6, GX2_CHANNELMASK t7) { GX2TargetChannelMaskReg tmpReg; GX2InitTargetChannelMasksReg(&tmpReg, t0, t1, t2, t3, t4, t5, t6, t7); GX2SetTargetChannelMasksReg(&tmpReg); } static_assert(sizeof(GX2_CHANNELMASK) == 4); void GX2GetTargetChannelMasksReg(GX2TargetChannelMaskReg* reg, betype<GX2_CHANNELMASK>* t0, betype<GX2_CHANNELMASK>* t1, betype<GX2_CHANNELMASK>* t2, betype<GX2_CHANNELMASK>* t3, betype<GX2_CHANNELMASK>* t4, betype<GX2_CHANNELMASK>* t5, betype<GX2_CHANNELMASK>* t6, betype<GX2_CHANNELMASK>* t7) { uint32 maskValue = reg->reg.value().get_MASK(); *t0 = (maskValue >> 0) & 0xF; *t1 = (maskValue >> 4) & 0xF; *t2 = (maskValue >> 8) & 0xF; *t3 = (maskValue >> 12) & 0xF; *t4 = (maskValue >> 16) & 0xF; *t5 = (maskValue >> 20) & 0xF; *t6 = (maskValue >> 24) & 0xF; *t7 = (maskValue >> 28) & 0xF; } void GX2InitBlendControlReg(GX2BlendControlReg* reg, uint32 renderTargetIndex, GX2_BLENDFACTOR colorSrcFactor, GX2_BLENDFACTOR colorDstFactor, GX2_BLENDFUNC colorCombineFunc, uint32 separateAlphaBlend, GX2_BLENDFACTOR alphaSrcFactor, GX2_BLENDFACTOR alphaDstFactor, GX2_BLENDFUNC alphaCombineFunc) { Latte::LATTE_CB_BLENDN_CONTROL tmpReg; tmpReg.set_COLOR_SRCBLEND(colorSrcFactor); tmpReg.set_COLOR_DSTBLEND(colorDstFactor); tmpReg.set_COLOR_COMB_FCN(colorCombineFunc); tmpReg.set_ALPHA_SRCBLEND(alphaSrcFactor); tmpReg.set_ALPHA_DSTBLEND(alphaDstFactor); tmpReg.set_ALPHA_COMB_FCN(alphaCombineFunc); tmpReg.set_SEPARATE_ALPHA_BLEND(separateAlphaBlend != 0); reg->index = renderTargetIndex; reg->reg = tmpReg; } void GX2SetBlendControlReg(GX2BlendControlReg* reg) { GX2ReserveCmdSpace(3); gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), (Latte::REGADDR::CB_BLEND0_CONTROL + (uint32)reg->index) - 0xA000, reg->reg ); } void GX2SetBlendControl(uint32 renderTargetIndex, GX2_BLENDFACTOR colorSrcFactor, GX2_BLENDFACTOR colorDstFactor, GX2_BLENDFUNC colorCombineFunc, uint32 separateAlphaBlend, GX2_BLENDFACTOR alphaSrcFactor, GX2_BLENDFACTOR alphaDstFactor, GX2_BLENDFUNC alphaCombineFunc) { GX2BlendControlReg tmpReg; GX2InitBlendControlReg(&tmpReg, renderTargetIndex, colorSrcFactor, colorDstFactor, colorCombineFunc, separateAlphaBlend, alphaSrcFactor, alphaDstFactor, alphaCombineFunc); GX2SetBlendControlReg(&tmpReg); } void GX2InitBlendConstantColorReg(GX2BlendConstantColorReg* reg, float red, float green, float blue, float alpha) { reg->regRed = Latte::LATTE_CB_BLEND_RED().set_RED(red); reg->regGreen = Latte::LATTE_CB_BLEND_GREEN().set_GREEN(green); reg->regBlue = Latte::LATTE_CB_BLEND_BLUE().set_BLUE(blue); reg->regAlpha = Latte::LATTE_CB_BLEND_ALPHA().set_ALPHA(alpha); } void GX2SetBlendConstantColorReg(GX2BlendConstantColorReg* reg) { GX2ReserveCmdSpace(6); gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 4), Latte::REGADDR::CB_BLEND_RED - 0xA000, reg->regRed, reg->regGreen, reg->regBlue, reg->regAlpha ); } void GX2SetBlendConstantColor(float red, float green, float blue, float alpha) { GX2BlendConstantColorReg tmpReg; GX2InitBlendConstantColorReg(&tmpReg, red, green, blue, alpha); GX2SetBlendConstantColorReg(&tmpReg); } void GX2InitHiStencilInfoRegs(GX2HiStencilInfoReg* hiStencilInfo) { // seen in Color Splash // but the game never calls GX2SetHiStencilInfo thus this has no effect } void GX2InitPointSizeReg(GX2PointSizeReg* reg, float width, float height) { if (width < 0.0f || height < 0.0f) { cemu_assert_suspicious(); } uint32 widthI = (uint32)(width * 8.0f); uint32 heightI = (uint32)(height * 8.0f); widthI = std::min<uint32>(widthI, 0xFFFF); heightI = std::min<uint32>(heightI, 0xFFFF); reg->reg = Latte::LATTE_PA_SU_POINT_SIZE().set_WIDTH(widthI).set_HEIGHT(heightI); } void GX2SetPointSizeReg(GX2PointSizeReg* reg) { GX2ReserveCmdSpace(3); gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::PA_SU_POINT_SIZE - 0xA000, reg->reg ); } void GX2SetPointSize(float width, float height) { GX2PointSizeReg tmpReg; GX2InitPointSizeReg(&tmpReg, width, height); GX2SetPointSizeReg(&tmpReg); } void GX2InitPointLimitsReg(GX2PointLimitsReg* reg, float minSize, float maxSize) { if (minSize < 0.0f || maxSize < 0.0f) { cemu_assert_suspicious(); } uint32 minSizeI = (uint32)(minSize * 8.0f); uint32 maxSizeI = (uint32)(maxSize * 8.0f); minSizeI = std::min<uint32>(minSizeI, 0xFFFF); maxSizeI = std::min<uint32>(maxSizeI, 0xFFFF); reg->reg = Latte::LATTE_PA_SU_POINT_MINMAX().set_MIN_SIZE(minSizeI).set_MAX_SIZE(maxSizeI); } void GX2SetPointLimitsReg(GX2PointLimitsReg* reg) { GX2ReserveCmdSpace(3); gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::PA_SU_POINT_MINMAX - 0xA000, reg->reg ); } void GX2SetPointLimits(float minSize, float maxSize) { GX2PointLimitsReg tmpReg; GX2InitPointLimitsReg(&tmpReg, minSize, maxSize); GX2SetPointLimitsReg(&tmpReg); } enum class GX2_SPECIAL_STATE : uint32 { FAST_CLEAR = 0, FAST_CLEAR_HIZ = 1, }; void _setSpecialState0(bool isEnabled) { GX2ReserveCmdSpace(6); if (isEnabled) { // set PA_CL_VTE_CNTL to 0x300 Latte::LATTE_PA_CL_VTE_CNTL regVTE{}; regVTE.set_VTX_XY_FMT(true); regVTE.set_VTX_Z_FMT(true); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::PA_CL_VTE_CNTL - 0xA000, regVTE); // set PA_CL_CLIP_CNTL to 0x490000 Latte::LATTE_PA_CL_CLIP_CNTL regClip{}; regClip.set_CLIP_DISABLE(true); // 0x10000 regClip.set_DX_CLIP_SPACE_DEF(true); // 0x80000 regClip.set_DX_RASTERIZATION_KILL(true); // 0x400000 gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::PA_CL_CLIP_CNTL - 0xA000, regClip); } else { // set PA_CL_VTE_CNTL to 0x43F Latte::LATTE_PA_CL_VTE_CNTL reg{}; reg.set_VPORT_X_OFFSET_ENA(true).set_VPORT_X_SCALE_ENA(true); reg.set_VPORT_Y_OFFSET_ENA(true).set_VPORT_Y_SCALE_ENA(true); reg.set_VPORT_Z_OFFSET_ENA(true).set_VPORT_Z_SCALE_ENA(true); reg.set_VTX_W0_FMT(true); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), Latte::REGADDR::PA_CL_VTE_CNTL - 0xA000, reg); // reset PA_CL_CLIP_CNTL GX2SetRasterizerClipControl(true, true); } } void GX2SetSpecialState(GX2_SPECIAL_STATE stateId, uint32 isEnabled) { if (stateId == GX2_SPECIAL_STATE::FAST_CLEAR) { _setSpecialState0(isEnabled != 0); } else if (stateId == GX2_SPECIAL_STATE::FAST_CLEAR_HIZ) { // todo // enables additional flags for special state 0 } else { // legacy style gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SPECIAL_STATE, 2)); gx2WriteGather_submitU32AsBE((uint32)stateId); // state id gx2WriteGather_submitU32AsBE(isEnabled); // enable/disable bool } } void GX2StateInit() { cafeExportRegister("gx2", GX2InitAlphaTestReg, LogType::GX2); cafeExportRegister("gx2", GX2SetAlphaTestReg, LogType::GX2); cafeExportRegister("gx2", GX2SetAlphaTest, LogType::GX2); cafeExportRegister("gx2", GX2InitColorControlReg, LogType::GX2); cafeExportRegister("gx2", GX2SetColorControl, LogType::GX2); cafeExportRegister("gx2", GX2SetColorControlReg, LogType::GX2); cafeExportRegister("gx2", GX2InitPolygonControlReg, LogType::GX2); cafeExportRegister("gx2", GX2SetPolygonControlReg, LogType::GX2); cafeExportRegister("gx2", GX2SetPolygonControl, LogType::GX2); cafeExportRegister("gx2", GX2SetCullOnlyControl, LogType::GX2); cafeExportRegister("gx2", GX2InitPolygonOffsetReg, LogType::GX2); cafeExportRegister("gx2", GX2SetPolygonOffsetReg, LogType::GX2); cafeExportRegister("gx2", GX2SetPolygonOffset, LogType::GX2); cafeExportRegister("gx2", GX2SetRasterizerClipControl, LogType::GX2); cafeExportRegister("gx2", GX2SetRasterizerClipControlHalfZ, LogType::GX2); cafeExportRegister("gx2", GX2SetRasterizerClipControlEx, LogType::GX2); cafeExportRegister("gx2", GX2InitViewportReg, LogType::GX2); cafeExportRegister("gx2", GX2SetViewportReg, LogType::GX2); cafeExportRegister("gx2", GX2SetViewport, LogType::GX2); cafeExportRegister("gx2", GX2InitScissorReg, LogType::GX2); cafeExportRegister("gx2", GX2SetScissorReg, LogType::GX2); cafeExportRegister("gx2", GX2GetScissorReg, LogType::GX2); cafeExportRegister("gx2", GX2SetScissor, LogType::GX2); cafeExportRegister("gx2", GX2InitDepthStencilControlReg, LogType::GX2); cafeExportRegister("gx2", GX2SetDepthStencilControlReg, LogType::GX2); cafeExportRegister("gx2", GX2GetDepthStencilControlReg, LogType::GX2); cafeExportRegister("gx2", GX2SetDepthOnlyControl, LogType::GX2); cafeExportRegister("gx2", GX2SetDepthStencilControl, LogType::GX2); cafeExportRegister("gx2", GX2InitStencilMaskReg, LogType::GX2); cafeExportRegister("gx2", GX2SetStencilMask, LogType::GX2); cafeExportRegister("gx2", GX2SetStencilMaskReg, LogType::GX2); cafeExportRegister("gx2", GX2SetPrimitiveRestartIndex, LogType::GX2); cafeExportRegister("gx2", GX2InitTargetChannelMasksReg, LogType::GX2); cafeExportRegister("gx2", GX2SetTargetChannelMasksReg, LogType::GX2); cafeExportRegister("gx2", GX2SetTargetChannelMasks, LogType::GX2); cafeExportRegister("gx2", GX2GetTargetChannelMasksReg, LogType::GX2); cafeExportRegister("gx2", GX2InitBlendControlReg, LogType::GX2); cafeExportRegister("gx2", GX2SetBlendControlReg, LogType::GX2); cafeExportRegister("gx2", GX2SetBlendControl, LogType::GX2); cafeExportRegister("gx2", GX2InitBlendConstantColorReg, LogType::GX2); cafeExportRegister("gx2", GX2SetBlendConstantColorReg, LogType::GX2); cafeExportRegister("gx2", GX2SetBlendConstantColor, LogType::GX2); cafeExportRegister("gx2", GX2InitHiStencilInfoRegs, LogType::GX2); cafeExportRegister("gx2", GX2InitPointSizeReg, LogType::GX2); cafeExportRegister("gx2", GX2SetPointSizeReg, LogType::GX2); cafeExportRegister("gx2", GX2SetPointSize, LogType::GX2); cafeExportRegister("gx2", GX2InitPointLimitsReg, LogType::GX2); cafeExportRegister("gx2", GX2SetPointLimitsReg, LogType::GX2); cafeExportRegister("gx2", GX2SetPointLimits, LogType::GX2); cafeExportRegister("gx2", GX2SetSpecialState, LogType::GX2); } } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_State.h ================================================ #pragma once #include "Cafe/HW/Latte/ISA/LatteReg.h" namespace GX2 { struct GX2AlphaTestReg { betype<Latte::LATTE_SX_ALPHA_TEST_CONTROL> regAlphaTestControl; betype<Latte::LATTE_SX_ALPHA_REF> regAlphaTestRef; }; static_assert(sizeof(GX2AlphaTestReg) == 8); struct GX2ColorControlReg { betype<Latte::LATTE_CB_COLOR_CONTROL> reg; }; static_assert(sizeof(GX2ColorControlReg) == 4); struct GX2PolygonControlReg { betype<Latte::LATTE_PA_SU_SC_MODE_CNTL> reg; }; static_assert(sizeof(GX2PolygonControlReg) == 4); struct GX2PolygonOffsetReg { betype<Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_SCALE> regFrontScale; betype<Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_OFFSET> regFrontOffset; betype<Latte::LATTE_PA_SU_POLY_OFFSET_BACK_SCALE> regBackScale; betype<Latte::LATTE_PA_SU_POLY_OFFSET_BACK_OFFSET> regBackOffset; betype<Latte::LATTE_PA_SU_POLY_OFFSET_CLAMP> regClamp; }; static_assert(sizeof(GX2PolygonOffsetReg) == 0x14); struct GX2DepthStencilControlReg { betype<Latte::LATTE_DB_DEPTH_CONTROL> reg; }; static_assert(sizeof(GX2DepthStencilControlReg) == 4); struct GX2StencilMaskReg { betype<Latte::LATTE_DB_STENCILREFMASK> stencilRefMaskFrontReg; betype<Latte::LATTE_DB_STENCILREFMASK_BF> stencilRefMaskBackReg; }; static_assert(sizeof(GX2StencilMaskReg) == 8); struct GX2TargetChannelMaskReg { betype<Latte::LATTE_CB_TARGET_MASK> reg; }; static_assert(sizeof(GX2TargetChannelMaskReg) == 4); struct GX2HIStencilInfoData { /* +0x00 */ uint32be ukn00; /* +0x04 */ uint8be ukn04; /* +0x05 */ uint8be ukn05; /* +0x06 */ uint8be ukn06; // probably padding? /* +0x07 */ uint8be ukn07; // probably padding? /* +0x08 */ uint32be isEnable; // 0 or 1 }; static_assert(sizeof(GX2HIStencilInfoData) == 0xC); struct GX2HiStencilInfoReg { GX2HIStencilInfoData state[2]; uint32be reg[2]; // DB_SRESULTS_COMPARE_STATE0 and DB_SRESULTS_COMPARE_STATE1 }; static_assert(sizeof(GX2HiStencilInfoReg) == 0x20); struct GX2BlendControlReg { uint32be index; betype<Latte::LATTE_CB_BLENDN_CONTROL> reg; }; static_assert(sizeof(GX2BlendControlReg) == 8); struct GX2BlendConstantColorReg { betype<Latte::LATTE_CB_BLEND_RED> regRed; betype<Latte::LATTE_CB_BLEND_GREEN> regGreen; betype<Latte::LATTE_CB_BLEND_BLUE> regBlue; betype<Latte::LATTE_CB_BLEND_ALPHA> regAlpha; }; static_assert(sizeof(GX2BlendConstantColorReg) == 16); struct GX2PointSizeReg { betype<Latte::LATTE_PA_SU_POINT_SIZE> reg; }; static_assert(sizeof(GX2PointSizeReg) == 4); struct GX2PointLimitsReg { betype<Latte::LATTE_PA_SU_POINT_MINMAX> reg; }; static_assert(sizeof(GX2PointLimitsReg) == 4); struct GX2ViewportReg { betype<Latte::LATTE_PA_CL_VPORT_XSCALE> xScale; betype<Latte::LATTE_PA_CL_VPORT_XOFFSET> xOffset; betype<Latte::LATTE_PA_CL_VPORT_YSCALE> yScale; betype<Latte::LATTE_PA_CL_VPORT_YOFFSET> yOffset; betype<Latte::LATTE_PA_CL_VPORT_ZSCALE> zScale; betype<Latte::LATTE_PA_CL_VPORT_ZOFFSET> zOffset; uint32 ukn[6]; // clipping registers? }; static_assert(sizeof(GX2ViewportReg) == 48); struct GX2ScissorReg { betype<Latte::LATTE_PA_SC_GENERIC_SCISSOR_TL> scissorTL; betype<Latte::LATTE_PA_SC_GENERIC_SCISSOR_BR> scissorBR; }; static_assert(sizeof(GX2ScissorReg) == 8); using GX2_ALPHAFUNC = Latte::LATTE_SX_ALPHA_TEST_CONTROL::E_ALPHA_FUNC; // alias Latte::E_COMPAREFUNC using GX2_LOGICOP = Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP; using GX2_CHANNELMASK = uint32; using GX2_BLENDFACTOR = Latte::LATTE_CB_BLENDN_CONTROL::E_BLENDFACTOR; using GX2_BLENDFUNC = Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC; void GX2InitAlphaTestReg(GX2AlphaTestReg* reg, uint32 alphaTestEnable, GX2_ALPHAFUNC alphaFunc, float alphaRef); void GX2SetAlphaTestReg(GX2AlphaTestReg* reg); void GX2SetAlphaTest(uint32 alphaTestEnable, GX2_ALPHAFUNC alphaFunc, float alphaRef); void GX2InitColorControlReg(GX2ColorControlReg* reg, GX2_LOGICOP logicOp, uint32 blendMask, uint32 multiwriteEnable, uint32 colorBufferEnable); void GX2SetColorControl(GX2_LOGICOP logicOp, uint32 blendMask, uint32 multiwriteEnable, uint32 colorBufferEnable); void GX2SetColorControlReg(GX2ColorControlReg* reg); void GX2InitPolygonControlReg(GX2PolygonControlReg* reg, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE usePolygonMode, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeFront, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeBack, uint32 polygonOffsetFrontEnable, uint32 polygonOffsetBackEnable, uint32 paraOffsetEnable); void GX2SetPolygonControlReg(GX2PolygonControlReg* reg); void GX2SetPolygonControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE usePolygonMode, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeFront, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeBack, uint32 polygonOffsetFrontEnable, uint32 polygonOffsetBackEnable, uint32 paraOffsetEnable); void GX2SetCullOnlyControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack); void GX2InitPolygonOffsetReg(GX2PolygonOffsetReg* reg, float frontOffset, float frontScale, float backOffset, float backScale, float clampOffset); void GX2SetPolygonOffsetReg(GX2PolygonOffsetReg* reg); void GX2SetPolygonOffset(float frontOffset, float frontScale, float backOffset, float backScale, float clampOffset); void GX2InitPointSizeReg(GX2PointSizeReg* reg, float width, float height); void GX2SetPointSizeReg(GX2PointSizeReg* reg); void GX2SetPointSize(float width, float height); void GX2InitPointLimitsReg(GX2PointLimitsReg* reg, float minSize, float maxSize); void GX2SetPointLimitsReg(GX2PointLimitsReg* reg); void GX2SetPointLimits(float minSize, float maxSize); void GX2SetRasterizerClipControl(bool enableRasterizer, bool enableZClip); void GX2SetRasterizerClipControlHalfZ(bool enableRasterizer, bool enableZClip, bool enableHalfZ); void GX2SetRasterizerClipControlEx(bool enableRasterizer, bool enableZClip, bool enableHalfZ); void GX2SetPrimitiveRestartIndex(uint32 restartIndex); void GX2InitTargetChannelMasksReg(GX2TargetChannelMaskReg* reg, GX2_CHANNELMASK t0, GX2_CHANNELMASK t1, GX2_CHANNELMASK t2, GX2_CHANNELMASK t3, GX2_CHANNELMASK t4, GX2_CHANNELMASK t5, GX2_CHANNELMASK t6, GX2_CHANNELMASK t7); void GX2SetTargetChannelMasksReg(GX2TargetChannelMaskReg* reg); void GX2SetTargetChannelMasks(GX2_CHANNELMASK t0, GX2_CHANNELMASK t1, GX2_CHANNELMASK t2, GX2_CHANNELMASK t3, GX2_CHANNELMASK t4, GX2_CHANNELMASK t5, GX2_CHANNELMASK t6, GX2_CHANNELMASK t7); void GX2InitBlendControlReg(GX2BlendControlReg* reg, uint32 renderTargetIndex, GX2_BLENDFACTOR colorSrcFactor, GX2_BLENDFACTOR colorDstFactor, GX2_BLENDFUNC colorCombineFunc, uint32 separateAlphaBlend, GX2_BLENDFACTOR alphaSrcFactor, GX2_BLENDFACTOR alphaDstFactor, GX2_BLENDFUNC alphaCombineFunc); void GX2SetBlendControlReg(GX2BlendControlReg* reg); void GX2SetBlendControl(uint32 renderTargetIndex, GX2_BLENDFACTOR colorSrcFactor, GX2_BLENDFACTOR colorDstFactor, GX2_BLENDFUNC colorCombineFunc, uint32 separateAlphaBlend, GX2_BLENDFACTOR alphaSrcFactor, GX2_BLENDFACTOR alphaDstFactor, GX2_BLENDFUNC alphaCombineFunc); void GX2InitBlendConstantColorReg(GX2BlendConstantColorReg* reg, float red, float green, float blue, float alpha); void GX2SetBlendConstantColorReg(GX2BlendConstantColorReg* reg); void GX2SetBlendConstantColor(float red, float green, float blue, float alpha); void GX2StateInit(); } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Streamout.cpp ================================================ #include "GX2_Streamout.h" #include "GX2_Command.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/OS/common/OSCommon.h" namespace GX2 { void GX2SetStreamOutBuffer(uint32 bufferIndex, GX2StreamOutBuffer* streamOutBuffer) { if (bufferIndex >= GX2_MAX_STREAMOUT_BUFFERS) { cemu_assert_suspicious(); debug_printf("GX2SetStreamOutBuffer(): Set out-of-bounds buffer\n"); return; } MPTR bufferAddr; uint32 bufferSize; if (streamOutBuffer->dataPtr.IsNull()) { bufferAddr = streamOutBuffer->rBuffer.GetVirtualAddr(); bufferSize = streamOutBuffer->rBuffer.GetSize(); } else { bufferAddr = streamOutBuffer->dataPtr.GetMPTR(); bufferSize = streamOutBuffer->size; } GX2ReserveCmdSpace(3 + 3); // set buffer size gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1)); gx2WriteGather_submitU32AsBE((mmVGT_STRMOUT_BUFFER_SIZE_0 + bufferIndex * 4) - 0xA000); gx2WriteGather_submitU32AsBE((bufferSize >> 2)); // set buffer base uint32 physMem = memory_virtualToPhysical(bufferAddr); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1)); gx2WriteGather_submitU32AsBE((mmVGT_STRMOUT_BUFFER_BASE_0 + bufferIndex * 4) - 0xA000); gx2WriteGather_submitU32AsBE((physMem >> 8)); // todo: Research and send IT_STRMOUT_BASE_UPDATE (0x72) // note: Other stream out registers maybe set in GX2SetVertexShader() or GX2SetGeometryShader() } void GX2SetStreamOutEnable(uint32 enable) { cemu_assert_debug(enable == 0 || enable == 1); GX2ReserveCmdSpace(3); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1)); gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_EN - 0xA000); gx2WriteGather_submitU32AsBE(enable & 1); } void GX2SetStreamOutContext(uint32 bufferIndex, GX2StreamOutBuffer* streamOutBuffer, uint32 mode) { if (bufferIndex >= GX2_MAX_STREAMOUT_BUFFERS) { cemu_assert_suspicious(); debug_printf("GX2SetStreamOutContext(): Set out-of-bounds buffer\n"); return; } GX2ReserveCmdSpace(6); if (mode == 0) { gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_STRMOUT_BUFFER_UPDATE, 5)); gx2WriteGather_submitU32AsBE((2 << 1) | (bufferIndex << 8)); gx2WriteGather_submitU32AsBE(MPTR_NULL); gx2WriteGather_submitU32AsBE(0); gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(streamOutBuffer->ctxPtr.GetMPTR())); gx2WriteGather_submitU32AsBE(0); } else if (mode == 1) { gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_STRMOUT_BUFFER_UPDATE, 5)); gx2WriteGather_submitU32AsBE((0 << 1) | (bufferIndex << 8)); gx2WriteGather_submitU32AsBE(MPTR_NULL); gx2WriteGather_submitU32AsBE(0); gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(streamOutBuffer->ctxPtr.GetMPTR())); gx2WriteGather_submitU32AsBE(0); } else { cemu_assert_unimplemented(); } } void GX2SaveStreamOutContext(uint32 bufferIndex, GX2StreamOutBuffer* streamOutBuffer) { if (bufferIndex >= GX2_MAX_STREAMOUT_BUFFERS) { cemu_assert_suspicious(); debug_printf("GX2SaveStreamOutContext(): Set out-of-bounds buffer\n"); return; } GX2ReserveCmdSpace(6); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_STRMOUT_BUFFER_UPDATE, 5)); gx2WriteGather_submitU32AsBE(1 | (3 << 1) | (bufferIndex << 8)); gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(streamOutBuffer->ctxPtr.GetMPTR())); gx2WriteGather_submitU32AsBE(0); gx2WriteGather_submitU32AsBE(MPTR_NULL); gx2WriteGather_submitU32AsBE(0); } void GX2StreamoutInit() { cafeExportRegister("gx2", GX2SetStreamOutBuffer, LogType::GX2); cafeExportRegister("gx2", GX2SetStreamOutEnable, LogType::GX2); cafeExportRegister("gx2", GX2SetStreamOutContext, LogType::GX2); cafeExportRegister("gx2", GX2SaveStreamOutContext, LogType::GX2); } } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Streamout.h ================================================ #pragma once #include "GX2_Resource.h" #define GX2_MAX_STREAMOUT_BUFFERS 4 namespace GX2 { struct GX2StreamOutBuffer { /* +0x00 */ uint32be size; // size of buffer (if dataPtr is not NULL) /* +0x04 */ MEMPTR<void> dataPtr; /* +0x08 */ uint32be vertexStride; /* +0x0C */ GX2RBuffer rBuffer; // if dataPtr is NULL, use this as the buffer and size /* +0x1C */ MEMPTR<void> ctxPtr; // stream out context }; static_assert(sizeof(GX2StreamOutBuffer) == 0x20, "GX2StreamOutBuffer_t has invalid size"); void GX2SetStreamOutBuffer(uint32 bufferIndex, GX2StreamOutBuffer* streamOutBuffer); void GX2StreamoutInit(); } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Surface.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "GX2.h" #include "GX2_Surface.h" #include "GX2_Resource.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" namespace GX2 { uint32 GX2GetSurfaceMipPitch(GX2Surface* surface, uint32 level) { LatteAddrLib::AddrSurfaceInfo_OUT surfOut; GX2::GX2CalculateSurfaceInfo(surface, level, &surfOut); return surfOut.pitch; } uint32 GX2GetSurfaceFormatBits(Latte::E_GX2SURFFMT surfaceFormat) { uint32 bpp = Latte::GetFormatBits(surfaceFormat); if (Latte::IsCompressedFormat(surfaceFormat)) { cemu_assert_debug((bpp & 0xF) == 0); bpp /= (4 * 4); } return bpp; } uint32 _GX2CalculateSliceSize(GX2Surface* surface, const LatteAddrLib::AddrSurfaceInfo_OUT* surfaceInfo) { uint32 aaScaler = 1 << surface->aa; return aaScaler * (surfaceInfo->bpp >> 3) * surfaceInfo->height * surfaceInfo->pitch; } uint32 GX2GetSurfaceMipSliceSize(GX2Surface* surface, uint32 level) { LatteAddrLib::AddrSurfaceInfo_OUT surfOut; GX2::GX2CalculateSurfaceInfo(surface, level, &surfOut); return _GX2CalculateSliceSize(surface, &surfOut); } uint32 GX2GetSurfaceSwizzleOffset(GX2Surface* surface, uint32 level) { uint32 swizzleOffset = 0; uint32 swizzle = surface->swizzle; if (!Latte::TM_IsMacroTiled(surface->tileMode) || level >= ((swizzle >> 16) & 0xFF)) swizzleOffset = 0; else swizzleOffset = swizzle & 0xFFFF; return swizzleOffset; } uint32 GX2GetSurfaceSwizzle(GX2Surface* surface) { uint32 swizzle = surface->swizzle; swizzle = (swizzle >> 8) & 0xFF; return swizzle; } uint32 GX2SurfaceIsCompressed(Latte::E_GX2SURFFMT surfaceFormat) { return Latte::IsCompressedFormat(surfaceFormat) ? GX2_TRUE : GX2_FALSE; } void GX2CalcDepthBufferHiZInfo(GX2DepthBuffer* depthBuffer, uint32be* sizeOut, uint32be* alignOut) { *sizeOut = 0x1000; *alignOut = 0x100; // todo: implement } void GX2CalcColorBufferAuxInfo(GX2ColorBuffer* colorBuffer, uint32be* sizeOut, uint32be* alignOut) { *sizeOut = 0x1000; *alignOut = 0x100; // todo: implement } void GX2CalculateSurfaceInfo(GX2Surface* surfacePtr, uint32 level, LatteAddrLib::AddrSurfaceInfo_OUT* pSurfOut) { bool optimizeForDepthBuffer = (surfacePtr->resFlag & GX2_RESFLAG_USAGE_DEPTH_BUFFER) != 0; bool optimizeForScanBuffer = (surfacePtr->resFlag & GX2_RESFLAG_USAGE_SCAN_BUFFER) != 0; LatteAddrLib::GX2CalculateSurfaceInfo(surfacePtr->format, surfacePtr->width, surfacePtr->height, surfacePtr->depth, surfacePtr->dim, surfacePtr->tileMode, surfacePtr->aa, level, pSurfOut, optimizeForDepthBuffer, optimizeForScanBuffer); } uint32 _CalculateLevels(uint32 resolution) { uint32 x = 0x80000000; uint32 v = resolution; uint32 n = 0; while (!(v & x)) { n++; if (n == 32) break; x >>= 1; } return 32 - n; } uint32 _GX2AdjustLevelCount(GX2Surface* surfacePtr) { if (surfacePtr->numLevels <= 1) return 1; uint32 levels = std::max(_CalculateLevels(surfacePtr->width), _CalculateLevels(surfacePtr->height)); if (surfacePtr->dim == Latte::E_DIM::DIM_3D) levels = std::max(levels, _CalculateLevels(surfacePtr->depth)); return levels; } void _GX2CalcSurfaceSizeAndAlignmentWorkaround(GX2Surface* surface) { // this workaround catches an issue where the FFL (Mii) library embedded into games tries to use an uninitialized texture // and subsequently crashes. It only happens when FFL files are not present in mlc. // seen in Sonic Lost World and in Super Mario 3D World if ((uint32)surface->dim.value() >= 50 || surface->aa.value() >= 0x100 || (uint32)surface->width.value() >= 0x01000000 || (uint32)surface->height.value() >= 0x01000000 || (uint32)surface->depth.value() >= 0x01000000 || (uint32)surface->format.value() >= 0x10000) { cemuLog_log(LogType::Force, "GX2CalcSurfaceSizeAndAlignment(): Uninitialized surface encountered\n"); // overwrite surface parameters with placeholder values to avoid crashing later down the line surface->dim = Latte::E_DIM::DIM_2D; surface->width = 8; surface->height = 8; surface->depth = 1; surface->tileMode = Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1; surface->pitch = 8; surface->numLevels = 0; surface->imagePtr = MEMORY_TILINGAPERTURE_AREA_ADDR; surface->swizzle = 0; surface->aa = 0; surface->format = Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM; surface->alignment = 0x400; } } void GX2CalcSurfaceSizeAndAlignment(GX2Surface* surface) { _GX2CalcSurfaceSizeAndAlignmentWorkaround(surface); LatteAddrLib::AddrSurfaceInfo_OUT surfOut = { 0 }; uint32 firstMipOffset = 0; bool changeTilemode = false; Latte::E_GX2TILEMODE lastTilemode = surface->tileMode; bool hasTileMode32 = surface->tileMode == Latte::E_GX2TILEMODE::TM_32_SPECIAL; if (surface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_GENERAL || hasTileMode32) { if (surface->dim != Latte::E_DIM::DIM_1D || (surface->resFlag & GX2_RESFLAG_USAGE_DEPTH_BUFFER) != 0 || surface->aa) { if (surface->dim != Latte::E_DIM::DIM_3D || (surface->resFlag & GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0) surface->tileMode = Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1; else surface->tileMode = Latte::E_GX2TILEMODE::TM_2D_TILED_THICK; changeTilemode = true; } else { surface->tileMode = Latte::E_GX2TILEMODE::TM_LINEAR_ALIGNED; } lastTilemode = surface->tileMode; } if (surface->numLevels == 0) surface->numLevels = 1; surface->numLevels = std::min<uint32>(surface->numLevels, _GX2AdjustLevelCount(surface)); surface->mipOffset[0] = 0; if (Latte::TM_IsMacroTiled(surface->tileMode)) surface->swizzle = (surface->swizzle & 0xFF00FFFF) | 0xD0000; else surface->swizzle = surface->swizzle & 0xFF00FFFF; // FIX 32 uint32 fix32Mode; if (hasTileMode32) { if (Latte::IsCompressedFormat(surface->format)) fix32Mode = 2; else fix32Mode = 0; } else { fix32Mode = 0; } // setup levels uint32 prevSize = 0; for (uint32 level = 0; level < surface->numLevels; ++level) { GX2CalculateSurfaceInfo(surface, level, &surfOut); if (level) { uint32 pad = 0; if (Latte::TM_IsMacroTiled(lastTilemode) && !Latte::TM_IsMacroTiled(surfOut.hwTileMode)) { surface->swizzle = (surface->swizzle & 0xFF00FFFF) | (level << 16); lastTilemode = (Latte::E_GX2TILEMODE)surfOut.hwTileMode; if (level > 1) pad = surface->swizzle & 0xFFFF; } pad += (surfOut.baseAlign - prevSize % surfOut.baseAlign) % surfOut.baseAlign; if (level == 1) { firstMipOffset = pad + prevSize; } else if (level > 1) { surface->mipOffset[level - 1] = pad + prevSize + surface->mipOffset[level - 2]; } } else { if (changeTilemode) { if (surface->tileMode != (Latte::E_GX2TILEMODE)surfOut.hwTileMode) { surface->tileMode = (Latte::E_GX2TILEMODE)surfOut.hwTileMode; GX2CalculateSurfaceInfo(surface, 0, &surfOut); if (!Latte::TM_IsMacroTiled(surface->tileMode)) surface->swizzle = surface->swizzle & 0xFF00FFFF; lastTilemode = surface->tileMode; } if (surface->width < (surfOut.pitchAlign << fix32Mode) && surface->height < (surfOut.heightAlign << fix32Mode)) { if (surface->tileMode == Latte::E_GX2TILEMODE::TM_2D_TILED_THICK) surface->tileMode = Latte::E_GX2TILEMODE::TM_1D_TILED_THICK; else surface->tileMode = Latte::E_GX2TILEMODE::TM_1D_TILED_THIN1; GX2CalculateSurfaceInfo(surface, 0, &surfOut); surface->swizzle = surface->swizzle & 0xFF00FFFF; lastTilemode = surface->tileMode; } } surface->imageSize = (uint32)(surfOut.surfSize); surface->alignment = surfOut.baseAlign; surface->pitch = surfOut.pitch; } prevSize = (uint32)(surfOut.surfSize); } if (surface->numLevels > 1) surface->mipSize = prevSize + surface->mipOffset[surface->numLevels - 2]; else surface->mipSize = 0; surface->mipOffset[0] = firstMipOffset; if (surface->format == Latte::E_GX2SURFFMT::NV12_UNORM) { uint32 padding = (surface->alignment - surface->imageSize % surface->alignment) % surface->alignment; surface->mipOffset[0] = padding + surface->imageSize; surface->imageSize = surface->mipOffset[0] + ((uint32)surface->imageSize >> 1); } } Latte::E_ENDIAN_SWAP GetSurfaceFormatSwapMode(Latte::E_GX2SURFFMT fmt) { // swap mode is 0 for all formats return Latte::E_ENDIAN_SWAP::SWAP_NONE; } uint32 GetSurfaceColorBufferExportFormat(Latte::E_GX2SURFFMT fmt) { const uint8 table[0x40] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; uint32 fmtHW = (uint32)fmt & 0x3F; return table[fmtHW]; } uint32 GX2CheckSurfaceUseVsFormat(uint32 resFlags, uint32 surfaceFormat) { cemuLog_logDebug(LogType::Force, "GX2CheckSurfaceUseVsFormat - stub"); return 1; } void GX2SetSurfaceSwizzle(GX2Surface* surface, uint32 newSwizzle) { uint32 currentSwizzle = surface->swizzle; currentSwizzle &= ~0xFF00; currentSwizzle |= (newSwizzle << 8); // newSwizzle isn't actually masked and some games set it to values above 0xFF surface->swizzle = currentSwizzle; } void GX2SurfaceInit() { cafeExportRegister("gx2", GX2GetSurfaceMipPitch, LogType::GX2); cafeExportRegister("gx2", GX2GetSurfaceFormatBits, LogType::GX2); cafeExportRegister("gx2", GX2GetSurfaceMipSliceSize, LogType::GX2); cafeExportRegister("gx2", GX2GetSurfaceSwizzleOffset, LogType::GX2); cafeExportRegister("gx2", GX2GetSurfaceSwizzle, LogType::GX2); cafeExportRegister("gx2", GX2SurfaceIsCompressed, LogType::GX2); cafeExportRegister("gx2", GX2CalcDepthBufferHiZInfo, LogType::GX2); cafeExportRegister("gx2", GX2CalcColorBufferAuxInfo, LogType::GX2); cafeExportRegister("gx2", GX2CalcSurfaceSizeAndAlignment, LogType::GX2); cafeExportRegister("gx2", GX2CheckSurfaceUseVsFormat, LogType::GX2); cafeExportRegister("gx2", GX2SetSurfaceSwizzle, LogType::GX2); } }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Surface.h ================================================ #pragma once #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" // todo - move into GX2 namespace struct GX2Surface { /* +0x000 */ betype<Latte::E_DIM> dim; /* +0x004 */ uint32be width; /* +0x008 */ uint32be height; /* +0x00C */ uint32be depth; /* +0x010 */ uint32be numLevels; // number of mipmap levels including base image. Should be at least 1 /* +0x014 */ betype<Latte::E_GX2SURFFMT> format; /* +0x018 */ uint32be aa; // anti-aliasing mode /* +0x01C */ uint32be resFlag; // GX2_RESFLAG_* and GX2R_RESFLAG_* /* +0x020 */ uint32be imageSize; /* +0x024 */ uint32be imagePtr; /* +0x028 */ uint32be mipSize; /* +0x02C */ uint32be mipPtr; /* +0x030 */ betype<Latte::E_GX2TILEMODE> tileMode; /* +0x034 */ uint32be swizzle; /* +0x038 */ uint32be alignment; /* +0x03C */ uint32be pitch; /* +0x040 */ uint32be mipOffset[13]; }; // size: 0x74 static_assert(sizeof(betype<Latte::E_DIM>) == 4); static_assert(sizeof(betype<Latte::E_GX2TILEMODE>) == 4); static_assert(sizeof(GX2Surface) == 0x74); // color and depth buffer struct GX2ColorBuffer { /* +0x00 */ GX2Surface surface; /* +0x74 */ uint32be viewMip; /* +0x78 */ uint32be viewFirstSlice; /* +0x7C */ uint32be viewNumSlices; /* +0x80 */ MEMPTR<void> auxData; /* +0x84 */ uint32be auxSize2; /* +0x88 */ uint32be reg_size; // CB_COLOR*_SIZE /* +0x8C */ uint32be reg_info; // CB_COLOR*_INFO /* +0x90 */ uint32be reg_view; // CB_COLOR*_VIEW /* +0x94 */ uint32be reg_mask; // CB_COLOR*_MASK /* +0x98 */ uint32be reg4; // ? }; static_assert(sizeof(GX2ColorBuffer) == 0x9C); struct GX2DepthBuffer { /* +0x00 */ GX2Surface surface; /* +0x74 */ uint32be viewMip; /* +0x78 */ uint32be viewFirstSlice; /* +0x7C */ uint32be viewNumSlices; /* +0x80 */ MEMPTR<void> hiZPtr; /* +0x84 */ uint32be hiZSize; /* +0x88 */ float32be clearDepth; /* +0x8C */ uint32be clearStencil; /* +0x90 */ uint32be reg_size; /* +0x94 */ uint32be reg_view; /* +0x98 */ uint32be reg_base; /* +0x9C */ uint32be reg_htile_surface; /* +0xA0 */ uint32be reg_prefetch_limit; /* +0xA4 */ uint32be reg_preload_control; /* +0xA8 */ uint32be reg_poly_offset_db_fmt_cntl; }; static_assert(sizeof(GX2DepthBuffer) == 0xAC); namespace GX2 { void GX2CalculateSurfaceInfo(GX2Surface* surfacePtr, uint32 level, LatteAddrLib::AddrSurfaceInfo_OUT* pSurfOut); Latte::E_ENDIAN_SWAP GetSurfaceFormatSwapMode(Latte::E_GX2SURFFMT fmt); uint32 GetSurfaceColorBufferExportFormat(Latte::E_GX2SURFFMT fmt); void GX2CalcSurfaceSizeAndAlignment(GX2Surface* surface); void GX2SurfaceInit(); }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" #include "util/highresolutiontimer/HighResolutionTimer.h" #include "GX2.h" #include "GX2_Resource.h" template<uint32 copyBpp> void gx2SurfaceCopySoftware_specialized( uint8* inputData, sint32 surfSrcHeight, sint32 srcPitch, sint32 srcDepth, uint32 srcSlice, uint32 srcSwizzle, uint32 srcHwTileMode, uint8* outputData, sint32 surfDstHeight, sint32 dstPitch, sint32 dstDepth, uint32 dstSlice, uint32 dstSwizzle, uint32 dstHwTileMode, uint32 copyWidth, uint32 copyHeight) { uint32 srcPipeSwizzle = (srcSwizzle >> 8) & 1; uint32 srcBankSwizzle = ((srcSwizzle >> 9) & 3); uint32 dstPipeSwizzle = (dstSwizzle >> 8) & 1; uint32 dstBankSwizzle = ((dstSwizzle >> 9) & 3); for (uint32 y = 0; y < copyHeight; y++) { for (uint32 x = 0; x < copyWidth; x++) { // calculate address of input block uint32 srcOffset = 0; if (srcHwTileMode == 0 || srcHwTileMode == 1) srcOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x, y, srcSlice, 0, copyBpp, srcPitch, surfSrcHeight, srcDepth); else if (srcHwTileMode == 2 || srcHwTileMode == 3) srcOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMicroTiled(x, y, srcSlice, copyBpp, srcPitch, surfSrcHeight, (Latte::E_HWTILEMODE)srcHwTileMode, false); else srcOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiled(x, y, srcSlice, 0, copyBpp, srcPitch, surfSrcHeight, 1 * 1, (Latte::E_HWTILEMODE)srcHwTileMode, false, srcPipeSwizzle, srcBankSwizzle); uint8* inputBlockData = inputData + srcOffset; // calculate address of output block uint32 dstOffset = 0; if (dstHwTileMode == 0 || dstHwTileMode == 1) dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x, y, dstSlice, 0, copyBpp, dstPitch, surfDstHeight, dstDepth); else if (dstHwTileMode == 2 || dstHwTileMode == 3) dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMicroTiled(x, y, dstSlice, copyBpp, dstPitch, surfDstHeight, (Latte::E_HWTILEMODE)dstHwTileMode, false); else dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiled(x, y, dstSlice, 0, copyBpp, dstPitch, surfDstHeight, 1 * 1, (Latte::E_HWTILEMODE)dstHwTileMode, false, dstPipeSwizzle, dstBankSwizzle); uint8* outputBlockData = outputData + dstOffset; if constexpr (copyBpp == 8) { outputBlockData[0] = inputBlockData[0]; } else if constexpr (copyBpp == 16) { *(uint16*)outputBlockData = *(uint16*)inputBlockData; } else if constexpr (copyBpp == 32) { *(uint32*)outputBlockData = *(uint32*)inputBlockData; } else if constexpr (copyBpp == 64) { *(uint32*)(outputBlockData + 0) = *(uint32*)(inputBlockData + 0); *(uint32*)(outputBlockData + 4) = *(uint32*)(inputBlockData + 4); } else if constexpr (copyBpp == 128) { *(uint32*)(outputBlockData + 0) = *(uint32*)(inputBlockData + 0); *(uint32*)(outputBlockData + 4) = *(uint32*)(inputBlockData + 4); *(uint32*)(outputBlockData + 8) = *(uint32*)(inputBlockData + 8); *(uint32*)(outputBlockData + 12) = *(uint32*)(inputBlockData + 12); } } } } // fast copy for tilemode 4 to tilemode 4 // assumes aa 1 // this only supports the cases where every micro tile fits within 256 bytes (group size) // we could accelerate this even further if we copied whole macro blocks void gx2SurfaceCopySoftware_fastPath_tm4Copy(uint8* inputData, sint32 surfSrcHeight, sint32 srcPitch, sint32 srcDepth, uint32 srcSlice, uint32 srcSwizzle, uint8* outputData, sint32 surfDstHeight, sint32 dstPitch, sint32 dstDepth, uint32 dstSlice, uint32 dstSwizzle, uint32 copyWidth, uint32 copyHeight, uint32 copyBpp) { cemu_assert_debug((copyWidth & 7) == 0); cemu_assert_debug((copyHeight & 7) == 0); uint32 srcPipeSwizzle = (srcSwizzle >> 8) & 1; uint32 srcBankSwizzle = ((srcSwizzle >> 9) & 3); uint32 dstPipeSwizzle = (dstSwizzle >> 8) & 1; uint32 dstBankSwizzle = ((dstSwizzle >> 9) & 3); uint32 texelBytes = copyBpp / 8; if (srcSlice == dstSlice && srcSwizzle == dstSwizzle && surfSrcHeight == surfDstHeight && srcPitch == dstPitch) { // shared tile offsets for (uint32 y = 0; y < copyHeight; y += 8) { for (uint32 x = 0; x < copyWidth; x += 8) { // copy 8x8 micro tile uint32 offset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiled(x, y, srcSlice, 0, copyBpp, srcPitch, surfSrcHeight, 1 * 1, Latte::E_HWTILEMODE::TM_2D_TILED_THIN1, false, srcPipeSwizzle, srcBankSwizzle); uint8* inputBlockData = inputData + offset; uint8* outputBlockData = outputData + offset; memcpy(outputBlockData, inputBlockData, texelBytes * (8 * 8)); } } } else { // separate tile offsets for (uint32 y = 0; y < copyHeight; y += 8) { for (uint32 x = 0; x < copyWidth; x += 8) { // copy 8x8 micro tile uint32 srcOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiled(x, y, srcSlice, 0, copyBpp, srcPitch, surfSrcHeight, 1 * 1, Latte::E_HWTILEMODE::TM_2D_TILED_THIN1, false, srcPipeSwizzle, srcBankSwizzle); uint8* inputBlockData = inputData + srcOffset; uint32 dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiled(x, y, dstSlice, 0, copyBpp, dstPitch, surfDstHeight, 1 * 1, Latte::E_HWTILEMODE::TM_2D_TILED_THIN1, false, dstPipeSwizzle, dstBankSwizzle); uint8* outputBlockData = outputData + dstOffset; memcpy(outputBlockData, inputBlockData, texelBytes * (8 * 8)); } } } } void gx2SurfaceCopySoftware( uint8* inputData, sint32 surfSrcHeight, sint32 srcPitch, sint32 srcDepth, uint32 srcSlice, uint32 srcSwizzle, uint32 srcHwTileMode, uint8* outputData, sint32 surfDstHeight, sint32 dstPitch, sint32 dstDepth, uint32 dstSlice, uint32 dstSwizzle, uint32 dstHwTileMode, uint32 copyWidth, uint32 copyHeight, uint32 copyBpp) { if (srcHwTileMode == 4 && dstHwTileMode == 4 && (copyWidth & 7) == 0 && (copyHeight & 7) == 0 && copyBpp <= 32) // todo - check sample == 1 { gx2SurfaceCopySoftware_fastPath_tm4Copy(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, copyWidth, copyHeight, copyBpp); return; } if (copyBpp == 8) gx2SurfaceCopySoftware_specialized<8>(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight); else if (copyBpp == 16) gx2SurfaceCopySoftware_specialized<16>(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight); else if (copyBpp == 32) gx2SurfaceCopySoftware_specialized<32>(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight); else if (copyBpp == 64) gx2SurfaceCopySoftware_specialized<64>(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight); else if (copyBpp == 128) gx2SurfaceCopySoftware_specialized<128>(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight); else cemu_assert_debug(false); } void gx2Surface_GX2CopySurface(GX2Surface* srcSurface, uint32 srcMip, uint32 srcSlice, GX2Surface* dstSurface, uint32 dstMip, uint32 dstSlice) { sint32 dstWidth = dstSurface->width; sint32 dstHeight = dstSurface->height; sint32 srcWidth = srcSurface->width; sint32 srcHeight = srcSurface->height; sint32 dstMipWidth = std::max(dstWidth>>dstMip, 1); sint32 dstMipHeight = std::max(dstHeight>>dstMip, 1); sint32 srcMipWidth = std::max(srcWidth>>srcMip, 1); sint32 srcMipHeight = std::max(srcHeight>>srcMip, 1); if( dstMipWidth != srcMipWidth || dstMipHeight != srcMipHeight ) { cemuLog_logDebugOnce(LogType::Force, "GX2CopySurface: Mismatching mip resolution"); return; } // handle format Latte::E_GX2SURFFMT srcFormat = srcSurface->format; Latte::E_GX2SURFFMT dstFormat = dstSurface->format; uint32 srcBPP = Latte::GetFormatBits(srcFormat); uint32 dstBPP = Latte::GetFormatBits(dstFormat); auto srcHwFormat = Latte::GetHWFormat(srcFormat); auto dstHwFormat = Latte::GetHWFormat(dstFormat); // get texture info LatteAddrLib::AddrSurfaceInfo_OUT surfOutSrc = {0}; GX2::GX2CalculateSurfaceInfo(srcSurface, srcMip, &surfOutSrc); LatteAddrLib::AddrSurfaceInfo_OUT surfOutDst = {0}; GX2::GX2CalculateSurfaceInfo(dstSurface, dstMip, &surfOutDst); // check parameters if (srcSurface->numLevels == 0) { debug_printf("GX2CopySurface(): mip count is 0\n"); return; } // get input pointer uint8* inputData = NULL; cemu_assert(srcMip < srcSurface->numLevels); if( srcMip == 0 ) inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->imagePtr); else if( srcMip == 1 ) inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr); else { inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr + srcSurface->mipOffset[srcMip - 1]); } // get output pointer uint8* outputData = NULL; cemu_assert(dstMip < dstSurface->numLevels); if( dstMip == 0 ) outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->imagePtr); else if( dstMip == 1 ) outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr); else { outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr + dstSurface->mipOffset[dstMip - 1]); } if( srcHwFormat != dstHwFormat ) { // mismatching format cemuLog_logDebug(LogType::Force, "GX2CopySurface(): Format mismatch"); return; } // note: Do not trust values from the input GX2Surface* structs but rely on surfOutDst/surfOutSrc instead if possible. // src uint32 srcPitch = surfOutSrc.pitch; uint32 srcSwizzle = srcSurface->swizzle; uint32 srcHwTileMode = (uint32)surfOutSrc.hwTileMode; uint32 srcDepth = std::max<uint32>(surfOutSrc.depth, 1); if (srcHwTileMode == 0) // linear { srcPitch = srcSurface->pitch >> srcMip; srcPitch = std::max<uint32>(srcPitch, 1); } // dst uint32 dstPitch = surfOutDst.pitch; uint32 dstSwizzle = dstSurface->swizzle; uint32 dstHwTileMode = (uint32)surfOutDst.hwTileMode; uint32 dstDepth = std::max<uint32>(surfOutDst.depth, 1); uint32 dstBpp = surfOutDst.bpp; //debug_printf("Src Tex: %08X %dx%d Swizzle: %08x tm: %d fmt: %04x use: %02x\n", _swapEndianU32(srcSurface->imagePtr), _swapEndianU32(srcSurface->width), _swapEndianU32(srcSurface->height), _swapEndianU32(srcSurface->swizzle), _swapEndianU32(srcSurface->tileMode), _swapEndianU32(srcSurface->format), (uint32)srcSurface->resFlag); //debug_printf("Dst Tex: %08X %dx%d Swizzle: %08x tm: %d fmt: %04x use: %02x\n", _swapEndianU32(dstSurface->imagePtr), _swapEndianU32(dstSurface->width), _swapEndianU32(dstSurface->height), _swapEndianU32(dstSurface->swizzle), _swapEndianU32(dstSurface->tileMode), _swapEndianU32(dstSurface->format), (uint32)dstSurface->resFlag); bool requestGPURAMCopy = false; bool debugTestForceCPUCopy = false; if (srcSurface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL && dstSurface->tileMode == Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1) debugTestForceCPUCopy = true; if (srcSurface->tileMode == Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1 && dstSurface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL ) { LatteAsyncCommands_queueForceTextureReadback( srcSurface->imagePtr, srcSurface->mipPtr, srcSurface->swizzle, (uint32)srcSurface->format.value(), srcSurface->width, srcSurface->height, srcSurface->depth, srcSurface->pitch, srcSlice, (uint32)srcSurface->dim.value(), Latte::MakeHWTileMode(srcSurface->tileMode), srcSurface->aa, srcMip); LatteAsyncCommands_waitUntilAllProcessed(); debugTestForceCPUCopy = true; } // send copy command to GPU if( srcHwTileMode > 0 && srcHwTileMode < 16 && dstHwTileMode > 0 && dstHwTileMode < 16 || requestGPURAMCopy ) { GX2::GX2ReserveCmdSpace(1+13*2); gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13*2), // src (uint32)srcSurface->imagePtr, (uint32)srcSurface->mipPtr, (uint32)srcSurface->swizzle, (uint32)srcSurface->format.value(), (uint32)srcSurface->width, (uint32)srcSurface->height, (uint32)srcSurface->depth, (uint32)srcSurface->pitch, srcSlice, (uint32)srcSurface->dim.value(), (uint32)srcSurface->tileMode.value(), (uint32)srcSurface->aa, srcMip, // dst (uint32)dstSurface->imagePtr, (uint32)dstSurface->mipPtr, (uint32)dstSurface->swizzle, (uint32)dstSurface->format.value(), (uint32)dstSurface->width, (uint32)dstSurface->height, (uint32)dstSurface->depth, (uint32)dstSurface->pitch, dstSlice, (uint32)dstSurface->dim.value(), (uint32)dstSurface->tileMode.value(), (uint32)dstSurface->aa, dstMip); } if (requestGPURAMCopy) return; // if RAM copy happens on the GPU side we skip it here // manually exclude expensive CPU texture copies for some known game framebuffer textures // todo - find a better way to solve this bool isDynamicTexCopy = false; isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width >= 800 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT); // SM3DW isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width >= 800 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM); // Trine 2 isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 0xA0 && srcFormat == Latte::E_GX2SURFFMT::R32_FLOAT); // Little Inferno isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1280 && srcFormat == Latte::E_GX2SURFFMT::R32_FLOAT); // Donkey Kong Tropical Freeze isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 640 && srcSurface->height == 320 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT); // SM3DW Switch Scramble Circus isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && srcSurface->tileMode != Latte::E_GX2TILEMODE::TM_LINEAR_ALIGNED ); // Affordable Space Adventures isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM); // Affordable Space Adventures isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1152 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT && (srcSurface->resFlag&GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0 ); // Star Fox Zero isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 680 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT && (srcSurface->resFlag&GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0 ); // Star Fox Zero isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT ); // Qube isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 322 && srcSurface->height == 182 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM ); // Qube isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 640 && srcSurface->height == 360 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT ); // Qube isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1920 && srcSurface->height == 1080 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && dstSurface->resFlag == 0x80000003); // Cosmophony isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && dstSurface->resFlag == 0x3); // Cosmophony isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && dstSurface->resFlag == 0x3); // The Fall isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && dstSurface->resFlag == 0x3); // The Fall isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && dstSurface->resFlag == 0x80000003); // The Fall isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && srcSurface->resFlag == 0x80000003); // Nano Assault Neo isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM); // Mario Party 10 isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM); // Mario Party 10 isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1920 && srcSurface->height == 1080 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && dstSurface->resFlag == 0x3); // Hello Kitty Kruisers isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1024 && srcSurface->height == 1024 && srcFormat == Latte::E_GX2SURFFMT::R32_FLOAT && dstSurface->resFlag == 0x5); // Art Academy isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 260 && srcSurface->height == 148 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT && dstSurface->resFlag == 0x3); // Transformers: Rise of the Dark Spark isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1040 && srcSurface->height == 592 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT && dstSurface->resFlag == 0x3); // Transformers: Rise of the Dark Spark isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && srcSurface->resFlag == 0x3); // Nano Assault Neo isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1024 && srcSurface->height == 576 && srcFormat == Latte::E_GX2SURFFMT::D24_S8_UNORM && srcSurface->resFlag == 0x1); // Skylanders SuperChargers isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1152 && srcSurface->height == 648 && (srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) && srcSurface->resFlag == 0x1); // Watch Dogs isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 576 && srcSurface->height == 324 && (srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) && srcSurface->resFlag == 0x1); // Watch Dogs if( isDynamicTexCopy && debugTestForceCPUCopy == false) { debug_printf("Software tex copy blocked\n"); return; } sint32 copyWidth = dstMipWidth; sint32 copyHeight = dstMipHeight; if (Latte::IsCompressedFormat(dstHwFormat)) { copyWidth = (copyWidth + 3) / 4; copyHeight = (copyHeight + 3) / 4; } gx2SurfaceCopySoftware(inputData, surfOutSrc.height, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfOutDst.height, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight, dstBpp); } void gx2Export_GX2CopySurface(PPCInterpreter_t* hCPU) { GX2Surface* srcSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint32 srcMip = hCPU->gpr[4]; uint32 srcSlice = hCPU->gpr[5]; GX2Surface* dstSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[6]); uint32 dstMip = hCPU->gpr[7]; uint32 dstSlice = hCPU->gpr[8]; gx2Surface_GX2CopySurface(srcSurface, srcMip, srcSlice, dstSurface, dstMip, dstSlice); osLib_returnFromFunction(hCPU, 0); } typedef struct { sint32 left; sint32 top; sint32 right; sint32 bottom; }GX2Rect_t; typedef struct { sint32 x; sint32 y; }GX2Point_t; void gx2Export_GX2CopySurfaceEx(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GX2CopySurfaceEx(0x{:08x},{},{},0x{:08x},{},{},{},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9], hCPU->gpr[10], memory_readU32(hCPU->gpr[1]+0x8)); GX2Surface* srcSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint32 srcMip = hCPU->gpr[4]; uint32 srcSlice = hCPU->gpr[5]; GX2Surface* dstSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[6]); uint32 dstMip = hCPU->gpr[7]; uint32 dstSlice = hCPU->gpr[8]; sint32 rectCount = hCPU->gpr[9]; MPTR rectSrcArrayMPTR = hCPU->gpr[10]; MPTR pointDstArrayMPTR = memory_readU32(hCPU->gpr[1]+0x8); GX2Rect_t* rectSrc = (GX2Rect_t*)memory_getPointerFromVirtualOffset(rectSrcArrayMPTR); GX2Point_t* rectDst = (GX2Point_t*)memory_getPointerFromVirtualOffset(pointDstArrayMPTR); for (sint32 i = 0; i < rectCount; i++) { cemuLog_logDebug(LogType::Force, "rect left-top: {}/{} size: {}/{}", _swapEndianU32(rectSrc->left), _swapEndianU32(rectSrc->top), _swapEndianU32(rectSrc->right) - _swapEndianU32(rectSrc->left), _swapEndianU32(rectSrc->bottom) - _swapEndianU32(rectSrc->top)); } #ifdef CEMU_DEBUG_ASSERT if( rectCount != 1 ) assert_dbg(); if( srcMip != 0 ) assert_dbg(); if( srcSlice != 0 ) assert_dbg(); if( dstMip != 0 ) assert_dbg(); if( dstSlice != 0 ) assert_dbg(); #endif for(sint32 i=0; i<rectCount; i++) { uint32 srcWidth = srcSurface->width; uint32 srcHeight = srcSurface->height; // calculate rect size sint32 rectSrcX = (sint32)_swapEndianU32((uint32)rectSrc[i].left); sint32 rectSrcY = (sint32)_swapEndianU32((uint32)rectSrc[i].top); sint32 rectWidth = (sint32)_swapEndianU32((uint32)rectSrc[i].right) - rectSrcX; sint32 rectHeight = (sint32)_swapEndianU32((uint32)rectSrc[i].bottom) - rectSrcY; if( rectSrcX == 0 && rectSrcY == 0 && rectWidth == srcWidth && rectHeight == srcHeight ) { // special case in which GX2CopySurfaceEx acts like GX2CopySurface() gx2Surface_GX2CopySurface(srcSurface, srcMip, srcSlice, dstSurface, dstMip, dstSlice); } } osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2ResolveAAColorBuffer(PPCInterpreter_t* hCPU) { debug_printf("GX2ResolveAAColorBuffer(0x%08x,0x%08x,%d,%d)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); GX2ColorBuffer* srcColorBuffer = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); GX2Surface* srcSurface = &srcColorBuffer->surface; GX2Surface* dstSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); uint32 srcMip = srcColorBuffer->viewMip; uint32 dstMip = hCPU->gpr[5]; uint32 srcSlice = srcColorBuffer->viewFirstSlice; uint32 dstSlice = hCPU->gpr[6]; #ifdef CEMU_DEBUG_ASSERT if( srcColorBuffer->viewMip != 0 || srcColorBuffer->viewFirstSlice != 0 ) assert_dbg(); #endif // allocate pixel buffer sint32 dstWidth = dstSurface->width; sint32 dstHeight = dstSurface->height; sint32 srcWidth = srcSurface->width; sint32 srcHeight = srcSurface->height; uint32 dstMipWidth = std::max(dstWidth>>dstMip, 1); uint32 dstMipHeight = std::max(dstHeight>>dstMip, 1); uint32 srcMipWidth = std::max(srcWidth>>srcMip, 1); uint32 srcMipHeight = std::max(srcHeight>>srcMip, 1); // check if surface properties match if( srcSurface->width != dstSurface->width || srcSurface->height != dstSurface->height ) { osLib_returnFromFunction(hCPU, 0); return; } if( dstMipWidth != srcMipWidth || dstMipHeight != srcMipHeight ) { cemu_assert_suspicious(); osLib_returnFromFunction(hCPU, 0); return; } // handle format Latte::E_GX2SURFFMT srcFormat = srcSurface->format; Latte::E_GX2SURFFMT dstFormat = dstSurface->format; uint32 srcBPP = Latte::GetFormatBits(srcFormat); uint32 dstBPP = Latte::GetFormatBits(dstFormat); sint32 srcStepX = 1; sint32 srcStepY = 1; sint32 dstStepX = 1; sint32 dstStepY = 1; auto srcHwFormat = Latte::GetHWFormat(srcFormat); auto dstHwFormat = Latte::GetHWFormat(dstFormat); // get texture info LatteAddrLib::AddrSurfaceInfo_OUT surfOutSrc = {0}; GX2::GX2CalculateSurfaceInfo(srcSurface, srcMip, &surfOutSrc); LatteAddrLib::AddrSurfaceInfo_OUT surfOutDst = {0}; GX2::GX2CalculateSurfaceInfo(dstSurface, dstMip, &surfOutDst); // get input pointer uint8* inputData = NULL; cemu_assert(srcMip < srcSurface->numLevels); if( srcMip == 0 ) inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->imagePtr); else if( srcMip == 1 ) inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr); else inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr+srcSurface->mipOffset[srcMip-1]); // get output pointer uint8* outputData = NULL; cemu_assert(dstMip < dstSurface->numLevels); if( dstMip == 0 ) outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->imagePtr); else if( dstMip == 1 ) outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr); else outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr+dstSurface->mipOffset[dstMip-1]); // calculate step size for compressed textures if( Latte::IsCompressedFormat(srcHwFormat) ) { srcStepX = 4; srcStepY = 4; } if(Latte::IsCompressedFormat(dstHwFormat) ) { dstStepX = 4; dstStepY = 4; } if( srcStepX != dstStepX || srcStepY != dstStepY ) assert_dbg(); if( srcHwFormat != dstHwFormat ) { // mismatching format debug_printf("GX2CopySurface(): Format mismatch\n"); osLib_returnFromFunction(hCPU, 0); return; } // src uint32 srcPitch = surfOutSrc.pitch; uint32 srcSwizzle = srcSurface->swizzle; uint32 srcPipeSwizzle = (srcSwizzle>>8)&1; uint32 srcBankSwizzle = ((srcSwizzle>>9)&3); uint32 srcTileMode = (uint32)surfOutSrc.hwTileMode; uint32 srcDepth = std::max<uint32>(surfOutSrc.depth, 1); // dst uint32 dstPitch = surfOutDst.pitch; uint32 dstSwizzle = dstSurface->swizzle; uint32 dstPipeSwizzle = (dstSwizzle>>8)&1; uint32 dstBankSwizzle = ((dstSwizzle>>9)&3); uint32 dstTileMode = (uint32)surfOutDst.hwTileMode; uint32 dstDepth = std::max<uint32>(surfOutDst.depth, 1); // send copy command to GPU GX2::GX2ReserveCmdSpace(1 + 13 * 2); gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13 * 2), // src (uint32)srcSurface->imagePtr, (uint32)srcSurface->mipPtr, (uint32)srcSurface->swizzle, (uint32)srcSurface->format.value(), (uint32)srcSurface->width, (uint32)srcSurface->height, (uint32)srcSurface->depth, (uint32)srcSurface->pitch, srcSlice, (uint32)srcSurface->dim.value(), (uint32)srcSurface->tileMode.value(), (uint32)srcSurface->aa, srcMip, // dst (uint32)dstSurface->imagePtr, (uint32)dstSurface->mipPtr, (uint32)dstSurface->swizzle, (uint32)dstSurface->format.value(), (uint32)dstSurface->width, (uint32)dstSurface->height, (uint32)dstSurface->depth, (uint32)dstSurface->pitch, dstSlice, (uint32)dstSurface->dim.value(), (uint32)dstSurface->tileMode.value(), (uint32)dstSurface->aa, dstMip); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2ConvertDepthBufferToTextureSurface(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2ConvertDepthBufferToTextureSurface(0x{:x}, 0x{:x}, {}, {})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); GX2DepthBuffer* depthBuffer = (GX2DepthBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); GX2Surface* dstSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); uint32 dstMip = hCPU->gpr[5]; uint32 dstSlice = hCPU->gpr[6]; if (dstMip != 0 || dstSlice != 0) debugBreakpoint(); // get texture info LatteAddrLib::AddrSurfaceInfo_OUT surfOutSrc = { 0 }; GX2::GX2CalculateSurfaceInfo(&depthBuffer->surface, 0, &surfOutSrc); LatteAddrLib::AddrSurfaceInfo_OUT surfOutDst = { 0 }; GX2::GX2CalculateSurfaceInfo(dstSurface, 0, &surfOutDst); if (depthBuffer->surface.imagePtr == dstSurface->imagePtr) { // in-place re-tiling doesn't need any actual copy operation? if (dstMip != 0 || dstSlice != 0) debugBreakpoint(); debug_printf("In-place re-tiling\n"); osLib_returnFromFunction(hCPU, 0); return; } // note: Do not trust values from the input GX2Surface* structs but rely on surfOutDst/surfOutSrc instead if possible. // src uint32 srcPitch = surfOutSrc.pitch; uint32 srcSwizzle = depthBuffer->surface.swizzle; uint32 srcPipeSwizzle = (srcSwizzle >> 8) & 1; uint32 srcBankSwizzle = ((srcSwizzle >> 9) & 3); uint32 srcTileMode = (uint32)surfOutSrc.hwTileMode; uint32 srcDepth = std::max<uint32>(surfOutSrc.depth, 1); // dst uint32 dstPitch = surfOutDst.pitch; uint32 dstSwizzle = dstSurface->swizzle; uint32 dstPipeSwizzle = (dstSwizzle >> 8) & 1; uint32 dstBankSwizzle = ((dstSwizzle >> 9) & 3); uint32 dstTileMode = (uint32)surfOutDst.hwTileMode; uint32 dstDepth = srcDepth; sint32 srcMip = 0; uint32 numSlices = std::max<uint32>(depthBuffer->viewNumSlices, 1); GX2::GX2ReserveCmdSpace((1 + 13 * 2) * numSlices); for (uint32 subSliceIndex = 0; subSliceIndex < numSlices; subSliceIndex++) { // send copy command to GPU gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13 * 2), // src (uint32)(depthBuffer->surface.imagePtr), (uint32)(depthBuffer->surface.mipPtr), (uint32)(depthBuffer->surface.swizzle), (uint32)(depthBuffer->surface.format.value()), (uint32)(depthBuffer->surface.width), (uint32)(depthBuffer->surface.height), (uint32)(depthBuffer->surface.depth), (uint32)(depthBuffer->surface.pitch), (uint32)(depthBuffer->viewFirstSlice) + subSliceIndex, (uint32)(depthBuffer->surface.dim.value()), (uint32)(depthBuffer->surface.tileMode.value()), (uint32)(depthBuffer->surface.aa), srcMip, // dst (uint32)(dstSurface->imagePtr), (uint32)(dstSurface->mipPtr), (uint32)(dstSurface->swizzle), (uint32)(dstSurface->format.value()), (uint32)(dstSurface->width), (uint32)(dstSurface->height), (uint32)(dstSurface->depth), (uint32)(dstSurface->pitch), dstSlice + subSliceIndex, (uint32)(dstSurface->dim.value()), (uint32)(dstSurface->tileMode.value()), (uint32)(dstSurface->aa), dstMip); } osLib_returnFromFunction(hCPU, 0); } namespace GX2 { void GX2SurfaceCopyInit() { osLib_addFunction("gx2", "GX2CopySurface", gx2Export_GX2CopySurface); osLib_addFunction("gx2", "GX2CopySurfaceEx", gx2Export_GX2CopySurfaceEx); osLib_addFunction("gx2", "GX2ResolveAAColorBuffer", gx2Export_GX2ResolveAAColorBuffer); osLib_addFunction("gx2", "GX2ConvertDepthBufferToTextureSurface", gx2Export_GX2ConvertDepthBufferToTextureSurface); } }; void gx2CopySurfaceTest() { return; BenchmarkTimer bt; // copy 0 bt.Start(); for(sint32 i=0; i<100; i++) gx2SurfaceCopySoftware( memory_base + 0x10000000, 256, 256, 1, 0, 0, 4, memory_base + 0x20000000, 256, 256, 1, 0, 0, 4, 64, 64, 32 ); bt.Stop(); debug_printf("Copy 0 - %lfms\n", bt.GetElapsedMilliseconds()); // copy 1 bt.Start(); for (sint32 i = 0; i < 100; i++) gx2SurfaceCopySoftware( memory_base + 0x11000000, 256, 256, 1, 0, 0, 4, memory_base + 0x21000000, 256, 256, 1, 0, 0, 2, 64, 64, 32 ); bt.Stop(); debug_printf("Copy 1 - %lfms\n", bt.GetElapsedMilliseconds()); // copy 2 bt.Start(); for (sint32 i = 0; i < 100; i++) gx2SurfaceCopySoftware( memory_base + 0x12000000, 256, 256, 1, 0, 0, 1, memory_base + 0x22000000, 256, 256, 1, 0, 0, 4, 64, 64, 128 ); bt.Stop(); debug_printf("Copy 2 - %lfms\n", bt.GetElapsedMilliseconds()); // copy 3 bt.Start(); for (sint32 i = 0; i < 100; i++) gx2SurfaceCopySoftware( memory_base + 0x12000000, 256, 256, 1, 0, 0, 4, memory_base + 0x22000000, 256, 256, 1, 0, 0, 4, 64, 512, 32 ); bt.Stop(); debug_printf("Copy 3 - %lfms\n", bt.GetElapsedMilliseconds()); cemu_assert_debug(false); // with bpp switch optimized away: // Copy 0 - 19.777100ms // Copy 1 - 14.311300ms // Copy 2 - 10.837700ms // Copy 3 - 158.174400ms // Copy 0 - 19.846800ms // Copy 1 - 14.054000ms // Copy 2 - 11.013500ms // Copy 3 - 159.916000ms // with fast path added: // Copy 0 - 0.222400ms // Copy 1 - 14.125700ms // Copy 2 - 13.298100ms // Copy 3 - 1.764500ms // with shared offset: // Copy 0 - 0.143300ms // Copy 1 - 13.814200ms // Copy 2 - 10.309500ms // Copy 3 - 1.191900ms } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Surface_Copy.h ================================================ #pragma once namespace GX2 { void GX2SurfaceCopyInit(); }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Texture.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "GX2.h" #include "GX2_Texture.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" namespace GX2 { using namespace Latte; /****** Texture functions ******/ void GX2InitTextureRegs(GX2Texture* texture) { uint32 _regs[5] = { 0 }; // some values may not be zero if (texture->viewNumMips == 0) texture->viewNumMips = 1; if (texture->viewNumSlices == 0) texture->viewNumSlices = 1; if (texture->surface.height == 0) texture->surface.height = 1; if (texture->surface.depth == 0) texture->surface.depth = 1; if (texture->surface.numLevels == 0) texture->surface.numLevels = 1; // texture parameters uint32 viewNumMips = texture->viewNumMips; uint32 viewNumSlices = texture->viewNumSlices; uint32 viewFirstMip = texture->viewFirstMip; uint32 viewFirstSlice = texture->viewFirstSlice; uint32 compSel = texture->compSel; // surface parameters uint32 width = texture->surface.width; uint32 height = texture->surface.height; uint32 depth = texture->surface.depth; uint32 pitch = texture->surface.pitch; uint32 numMips = texture->surface.numLevels; Latte::E_GX2SURFFMT format = texture->surface.format; Latte::E_DIM dim = texture->surface.dim; uint32 tileMode = (uint32)texture->surface.tileMode.value(); uint32 surfaceFlags = texture->surface.resFlag; uint32 surfaceAA = texture->surface.aa; // calculate register word 0 Latte::E_HWSURFFMT formatHw = Latte::GetHWFormat(format); Latte::LATTE_SQ_TEX_RESOURCE_WORD0_N newRegWord0; newRegWord0.set_DIM(dim); newRegWord0.set_TILE_MODE(Latte::MakeHWTileMode(texture->surface.tileMode)); newRegWord0.set_TILE_TYPE((surfaceFlags&4) != 0); uint32 pixelPitch = pitch; if (Latte::IsCompressedFormat(formatHw)) pixelPitch *= 4; if(pixelPitch == 0) newRegWord0.set_PITCH(0x7FF); else newRegWord0.set_PITCH((pixelPitch >> 3) - 1); if (width == 0) newRegWord0.set_WIDTH(0x1FFF); else newRegWord0.set_WIDTH(width - 1); texture->regTexWord0 = newRegWord0; // calculate register word 1 Latte::LATTE_SQ_TEX_RESOURCE_WORD1_N newRegWord1; newRegWord1.set_HEIGHT(height - 1); if (dim == Latte::E_DIM::DIM_CUBEMAP) { newRegWord1.set_DEPTH((depth / 6) - 1); } else if (dim == E_DIM::DIM_3D || dim == E_DIM::DIM_2D_ARRAY_MSAA || dim == E_DIM::DIM_2D_ARRAY || dim == E_DIM::DIM_1D_ARRAY) { newRegWord1.set_DEPTH(depth - 1); } else { newRegWord1.set_DEPTH(0); } newRegWord1.set_DATA_FORMAT(formatHw); texture->regTexWord1 = newRegWord1; // calculate register word 2 LATTE_SQ_TEX_RESOURCE_WORD4_N newRegWord4; LATTE_SQ_TEX_RESOURCE_WORD4_N::E_FORMAT_COMP formatComp; if (HAS_FLAG(format, Latte::E_GX2SURFFMT::FMT_BIT_SIGNED)) formatComp = LATTE_SQ_TEX_RESOURCE_WORD4_N::E_FORMAT_COMP::COMP_SIGNED; else formatComp = LATTE_SQ_TEX_RESOURCE_WORD4_N::E_FORMAT_COMP::COMP_UNSIGNED; newRegWord4.set_FORMAT_COMP_X(formatComp); newRegWord4.set_FORMAT_COMP_Y(formatComp); newRegWord4.set_FORMAT_COMP_Z(formatComp); newRegWord4.set_FORMAT_COMP_W(formatComp); if (HAS_FLAG(format, Latte::E_GX2SURFFMT::FMT_BIT_FLOAT)) newRegWord4.set_NUM_FORM_ALL(LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_SCALED); else if (HAS_FLAG(format, Latte::E_GX2SURFFMT::FMT_BIT_INT)) newRegWord4.set_NUM_FORM_ALL(LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_INT); else newRegWord4.set_NUM_FORM_ALL(LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_NORM); if (HAS_FLAG(format, Latte::E_GX2SURFFMT::FMT_BIT_SRGB)) newRegWord4.set_FORCE_DEGAMMA(true); newRegWord4.set_ENDIAN_SWAP(GX2::GetSurfaceFormatSwapMode((Latte::E_GX2SURFFMT)format)); newRegWord4.set_REQUEST_SIZE(2); newRegWord4.set_DST_SEL_X((Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_SEL)((compSel >> 24) & 0x7)); newRegWord4.set_DST_SEL_Y((Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_SEL)((compSel >> 16) & 0x7)); newRegWord4.set_DST_SEL_Z((Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_SEL)((compSel >> 8) & 0x7)); newRegWord4.set_DST_SEL_W((Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_SEL)((compSel >> 0) & 0x7)); newRegWord4.set_BASE_LEVEL(viewFirstMip); texture->regTexWord4 = newRegWord4; // calculate register word 3 LATTE_SQ_TEX_RESOURCE_WORD5_N newRegWord5; newRegWord5.set_LAST_LEVEL(viewFirstMip + viewNumMips - 1); newRegWord5.set_BASE_ARRAY(viewFirstSlice); newRegWord5.set_LAST_ARRAY(viewFirstSlice + viewNumSlices - 1); if (dim == Latte::E_DIM::DIM_CUBEMAP && ((depth / 6) - 1) != 0) newRegWord5.set_UKN_BIT_30(true); if(surfaceAA >= 1 && surfaceAA <= 3) newRegWord5.set_LAST_LEVEL(surfaceAA); texture->regTexWord5 = newRegWord5; // calculate register word 4 LATTE_SQ_TEX_RESOURCE_WORD6_N newRegWord6; newRegWord6.set_MAX_ANISO(4); newRegWord6.set_PERF_MODULATION(7); newRegWord6.set_TYPE(Latte::LATTE_SQ_TEX_RESOURCE_WORD6_N::E_TYPE::VTX_VALID_TEXTURE); texture->regTexWord6 = newRegWord6; } void _GX2SetTexture(GX2Texture* tex, Latte::REGADDR baseRegister, uint32 textureUnitIndex) { GX2ReserveCmdSpace(2 + 7); MPTR imagePtr = tex->surface.imagePtr; MPTR mipPtr = tex->surface.mipPtr; if (mipPtr == MPTR_NULL) mipPtr = imagePtr; uint32 swizzle = tex->surface.swizzle; cemu_assert_debug((swizzle & 0xFF) == 0); // does the low byte in swizzle field have any meaning? if (Latte::TM_IsMacroTiled(tex->surface.tileMode)) { uint32 swizzleStopLevel = (swizzle >> 16) & 0xFF; // combine swizzle with image ptr if base level is macro tiled if (swizzleStopLevel > 0) imagePtr ^= (swizzle & 0xFFFF); // combine swizzle with mip ptr if first mip (level 1) is macro tiled if (swizzleStopLevel > 1) mipPtr ^= (swizzle & 0xFFFF); } gx2WriteGather_submit(pm4HeaderType3(IT_SET_RESOURCE, 8), baseRegister + textureUnitIndex * 7 - mmSQ_TEX_RESOURCE_WORD0, tex->regTexWord0, tex->regTexWord1, memory_virtualToPhysical(imagePtr) >> 8, memory_virtualToPhysical(mipPtr) >> 8, tex->regTexWord4, tex->regTexWord5, tex->regTexWord6); } void GX2SetPixelTexture(GX2Texture* tex, uint32 texUnit) { cemu_assert_debug(texUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE); _GX2SetTexture(tex, Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS, texUnit); } void GX2SetVertexTexture(GX2Texture* tex, uint32 texUnit) { cemu_assert_debug(texUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE); _GX2SetTexture(tex, Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS, texUnit); } void GX2SetGeometryTexture(GX2Texture* tex, uint32 texUnit) { cemu_assert_debug(texUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE); _GX2SetTexture(tex, Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS, texUnit); } void GX2SetComputeTexture(GX2Texture* tex, uint32 texUnit) { GX2SetVertexTexture(tex, texUnit); } /****** Sampler functions ******/ void GX2InitSampler(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_CLAMP clampXYZ, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER filterMinMag) { LATTE_SQ_TEX_SAMPLER_WORD0_0 word0{}; word0.set_CLAMP_X(clampXYZ).set_CLAMP_Y(clampXYZ).set_CLAMP_Z(clampXYZ); word0.set_XY_MAG_FILTER(filterMinMag).set_XY_MIN_FILTER(filterMinMag); word0.set_Z_FILTER(LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::POINT); word0.set_MIP_FILTER(LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::POINT); word0.set_TEX_ARRAY_OVERRIDE(true); LATTE_SQ_TEX_SAMPLER_WORD1_0 word1{}; word1.set_MAX_LOD(0x3FF); LATTE_SQ_TEX_SAMPLER_WORD2_0 word2{}; word2.set_TYPE(LATTE_SQ_TEX_SAMPLER_WORD2_0::E_SAMPLER_TYPE::UKN1); sampler->word0 = word0; sampler->word1 = word1; sampler->word2 = word2; } void GX2InitSamplerXYFilter(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER magFilter, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER minFilter, uint32 maxAnisoRatio) { LATTE_SQ_TEX_SAMPLER_WORD0_0 word0 = sampler->word0; if (maxAnisoRatio == 0) { word0.set_XY_MAG_FILTER(magFilter); word0.set_XY_MIN_FILTER(minFilter); word0.set_MAX_ANISO_RATIO(0); } else { auto getAnisoFilter = [](LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER filter) -> LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER { if (filter == LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT) return LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT; else if (filter == LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::BILINEAR) return LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_BILINEAR; else cemu_assert_debug(false); return LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT; }; word0.set_XY_MAG_FILTER(getAnisoFilter(magFilter)); word0.set_XY_MIN_FILTER(getAnisoFilter(minFilter)); word0.set_MAX_ANISO_RATIO(maxAnisoRatio); } sampler->word0 = word0; } void GX2InitSamplerZMFilter(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER zFilter, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER mipFilter) { LATTE_SQ_TEX_SAMPLER_WORD0_0 word0 = sampler->word0; word0.set_Z_FILTER(zFilter); word0.set_MIP_FILTER(mipFilter); sampler->word0 = word0; } void GX2InitSamplerLOD(GX2Sampler* sampler, float minLod, float maxLod, float lodBias) { // known special cases: Mario & Sonic Rio passes minimum and maximum float values for minLod/maxLod if (minLod < 0.0) minLod = 0.0; if (maxLod > 16.0) maxLod = 16.0; uint32 iMinLod = ((uint32)floorf(minLod * 64.0f)); uint32 iMaxLod = ((uint32)floorf(maxLod * 64.0f)); sint32 iLodBias = (sint32)((sint32)floorf(lodBias * 64.0f)); // input range: -32.0 to 32.0 iMinLod = std::clamp(iMinLod, 0u, 1023u); iMaxLod = std::clamp(iMaxLod, 0u, 1023u); iLodBias = std::clamp(iLodBias, -2048, 2047); LATTE_SQ_TEX_SAMPLER_WORD1_0 word1 = sampler->word1; word1.set_MIN_LOD(iMinLod); word1.set_MAX_LOD(iMaxLod); word1.set_LOD_BIAS(iLodBias); sampler->word1 = word1; } void GX2InitSamplerClamping(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_CLAMP clampX, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_CLAMP clampY, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_CLAMP clampZ) { LATTE_SQ_TEX_SAMPLER_WORD0_0 word0 = sampler->word0; word0.set_CLAMP_X(clampX); word0.set_CLAMP_Y(clampY); word0.set_CLAMP_Z(clampZ); sampler->word0 = word0; } void GX2InitSamplerBorderType(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE borderColorType) { LATTE_SQ_TEX_SAMPLER_WORD0_0 word0 = sampler->word0; word0.set_BORDER_COLOR_TYPE(borderColorType); sampler->word0 = word0; } void GX2InitSamplerDepthCompare(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_DEPTH_COMPARE depthCompareFunction) { LATTE_SQ_TEX_SAMPLER_WORD0_0 word0 = sampler->word0; word0.set_DEPTH_COMPARE_FUNCTION(depthCompareFunction); sampler->word0 = word0; } void _GX2SetSampler(GX2Sampler* sampler, uint32 samplerIndex) { GX2ReserveCmdSpace(5); gx2WriteGather_submit(pm4HeaderType3(IT_SET_SAMPLER, 1 + 3), samplerIndex * 3, sampler->word0, sampler->word1, sampler->word2); } void GX2SetPixelSampler(GX2Sampler* sampler, uint32 samplerIndex) { _GX2SetSampler(sampler, samplerIndex + SAMPLER_BASE_INDEX_PIXEL); } void GX2SetVertexSampler(GX2Sampler* sampler, uint32 vertexSamplerIndex) { _GX2SetSampler(sampler, vertexSamplerIndex + SAMPLER_BASE_INDEX_VERTEX); } void GX2SetGeometrySampler(GX2Sampler* sampler, uint32 geometrySamplerIndex) { _GX2SetSampler(sampler, geometrySamplerIndex + SAMPLER_BASE_INDEX_GEOMETRY); } void GX2SetComputeSampler(GX2Sampler* sampler, uint32 computeSamplerIndex) { _GX2SetSampler(sampler, computeSamplerIndex + SAMPLER_BASE_INDEX_VERTEX); // uses vertex shader stage } void GX2SetSamplerBorderColor(uint32 registerBaseOffset, uint32 samplerIndex, float red, float green, float blue, float alpha) { GX2ReserveCmdSpace(6); gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONFIG_REG, 1 + 4), registerBaseOffset + samplerIndex * 4 - LATTE_REG_BASE_CONFIG, red, green, blue, alpha); } void GX2SetPixelSamplerBorderColor(uint32 pixelSamplerIndex, float red, float green, float blue, float alpha) { GX2SetSamplerBorderColor(REGADDR::TD_PS_SAMPLER0_BORDER_RED, pixelSamplerIndex, red, green, blue, alpha); } void GX2SetVertexSamplerBorderColor(uint32 vertexSamplerIndex, float red, float green, float blue, float alpha) { GX2SetSamplerBorderColor(REGADDR::TD_VS_SAMPLER0_BORDER_RED, vertexSamplerIndex, red, green, blue, alpha); } void GX2SetGeometrySamplerBorderColor(uint32 geometrySamplerIndex, float red, float green, float blue, float alpha) { GX2SetSamplerBorderColor(REGADDR::TD_GS_SAMPLER0_BORDER_RED, geometrySamplerIndex, red, green, blue, alpha); } void GX2TextureInit() { // texture cafeExportRegister("gx2", GX2InitTextureRegs, LogType::GX2); cafeExportRegister("gx2", GX2SetPixelTexture, LogType::GX2); cafeExportRegister("gx2", GX2SetVertexTexture, LogType::GX2); cafeExportRegister("gx2", GX2SetGeometryTexture, LogType::GX2); cafeExportRegister("gx2", GX2SetComputeTexture, LogType::GX2); // sampler cafeExportRegister("gx2", GX2InitSampler, LogType::GX2); cafeExportRegister("gx2", GX2InitSamplerXYFilter, LogType::GX2); cafeExportRegister("gx2", GX2InitSamplerZMFilter, LogType::GX2); cafeExportRegister("gx2", GX2InitSamplerLOD, LogType::GX2); cafeExportRegister("gx2", GX2InitSamplerClamping, LogType::GX2); cafeExportRegister("gx2", GX2InitSamplerBorderType, LogType::GX2); cafeExportRegister("gx2", GX2InitSamplerDepthCompare, LogType::GX2); cafeExportRegister("gx2", GX2SetPixelSampler, LogType::GX2); cafeExportRegister("gx2", GX2SetVertexSampler, LogType::GX2); cafeExportRegister("gx2", GX2SetGeometrySampler, LogType::GX2); cafeExportRegister("gx2", GX2SetComputeSampler, LogType::GX2); cafeExportRegister("gx2", GX2SetPixelSamplerBorderColor, LogType::GX2); cafeExportRegister("gx2", GX2SetVertexSamplerBorderColor, LogType::GX2); cafeExportRegister("gx2", GX2SetGeometrySamplerBorderColor, LogType::GX2); } }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_Texture.h ================================================ #pragma once #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "GX2_Surface.h" namespace GX2 { struct GX2Texture { /* +0x00 */ GX2Surface surface; /* +0x74 */ uint32be viewFirstMip; /* +0x78 */ uint32be viewNumMips; /* +0x7C */ uint32be viewFirstSlice; /* +0x80 */ uint32be viewNumSlices; /* +0x84 */ uint32be compSel; /* +0x88 */ betype<Latte::LATTE_SQ_TEX_RESOURCE_WORD0_N> regTexWord0; /* +0x8C */ betype<Latte::LATTE_SQ_TEX_RESOURCE_WORD1_N> regTexWord1; // word2 and word3 are the base/mip address and are not stored /* +0x90 */ betype<Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N> regTexWord4; /* +0x94 */ betype<Latte::LATTE_SQ_TEX_RESOURCE_WORD5_N> regTexWord5; /* +0x98 */ betype<Latte::LATTE_SQ_TEX_RESOURCE_WORD6_N> regTexWord6; }; static_assert(sizeof(GX2Texture) == 0x9C); struct GX2Sampler { betype<Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0> word0; betype<Latte::LATTE_SQ_TEX_SAMPLER_WORD1_0> word1; betype<Latte::LATTE_SQ_TEX_SAMPLER_WORD2_0> word2; }; static_assert(sizeof(GX2Sampler) == 12); void GX2InitTextureRegs(GX2Texture* texture); void GX2TextureInit(); }; ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_TilingAperture.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "GX2.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" #include "Cafe/HW/Latte/Core/LatteTextureLoader.h" #define GX2_MAX_ACTIVE_TILING_APERATURES (32) struct ActiveTilingAperature { uint32 addr; uint32 size; uint32 handle; uint32 endianMode; // surface info GX2Surface surface; uint32 sliceIndex; uint32 mipLevel; }; ActiveTilingAperature activeTilingAperature[GX2_MAX_ACTIVE_TILING_APERATURES]; sint32 activeTilingAperatureCount = 0; MPTR GX2TilingAperature_allocateTilingMemory(uint32 size) { uint32 currentOffset = 0; while( true ) { // align offset currentOffset = (currentOffset+0xFFF)&~0xFFF; // check if out of range if( (currentOffset+size) >= MEMORY_TILINGAPERTURE_AREA_SIZE ) break; // check if range intersects with any already allocated range bool isAvailable = true; uint32 nextOffset = 0xFFFFFFFF; for(sint32 i=0; i<activeTilingAperatureCount; i++) { uint32 startA = currentOffset; uint32 endA = startA+size; uint32 startB = activeTilingAperature[i].addr - MEMORY_TILINGAPERTURE_AREA_ADDR; uint32 endB = startB+activeTilingAperature[i].size; if( startA < endB && endA >= startB ) { isAvailable = false; nextOffset = std::min(nextOffset, endB); } } if( isAvailable ) return currentOffset + MEMORY_TILINGAPERTURE_AREA_ADDR; currentOffset = nextOffset; } return MPTR_NULL; } std::atomic<uint32> sGenAperatureHandle{1}; uint32 GX2TilingAperature_GenerateHandle() { return sGenAperatureHandle.fetch_add(1); } template<typename copyType, int count, bool isWrite> void copyValue(uint8* outputBlockData, uint8* inputBlockData) { if (isWrite) { *(copyType*)outputBlockData = *(copyType*)inputBlockData; if (count >= 2) ((copyType*)outputBlockData)[1] = ((copyType*)inputBlockData)[1]; if (count >= 3) ((copyType*)outputBlockData)[2] = ((copyType*)inputBlockData)[2]; if (count >= 4) ((copyType*)outputBlockData)[3] = ((copyType*)inputBlockData)[3]; } else { *(copyType*)inputBlockData = *(copyType*)outputBlockData; if (count >= 2) ((copyType*)inputBlockData)[1] = ((copyType*)outputBlockData)[1]; if (count >= 3) ((copyType*)inputBlockData)[2] = ((copyType*)outputBlockData)[2]; if (count >= 4) ((copyType*)inputBlockData)[3] = ((copyType*)outputBlockData)[3]; } } template<int bpp, bool isWrite, int surfaceTileMode> void retileTexture(ActiveTilingAperature* tilingAperture, uint8* inputData, uint8* outputData, sint32 texelWidth, sint32 texelHeight, sint32 surfaceSlice, sint32 surfacePitch, sint32 surfaceHeight, sint32 surfaceDepth, LatteAddrLib::CachedSurfaceAddrInfo* cachedInfo) { for (sint32 y = 0; y < texelHeight; y++) { uint32 srcOffset; uint8* inputBlockData; if (bpp != 8) { srcOffset = (0 + y*surfacePitch)*(bpp / 8); inputBlockData = inputData + srcOffset; } for (sint32 x = 0; x < texelWidth; x++) { // calculate address of input block sint32 texelX = x; sint32 texelY = y; if (bpp == 8) { texelX ^= 8; texelY ^= 2; srcOffset = (texelX + texelY*surfacePitch)*(bpp / 8); inputBlockData = inputData + srcOffset; } // calculate address of output block uint32 dstBitPos = 0; uint32 dstOffset = 0; if (surfaceTileMode == 4) dstOffset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(x, y, cachedInfo); else if (surfaceTileMode == 2 || surfaceTileMode == 3) { dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMicroTiled(x, y, cachedInfo->slice, cachedInfo->bpp, cachedInfo->pitch, cachedInfo->height, (Latte::E_HWTILEMODE)cachedInfo->tileMode, false); } else if (surfaceTileMode == 1 || surfaceTileMode == 0) { dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x, y, cachedInfo->slice, 0, cachedInfo->bpp, cachedInfo->pitch, cachedInfo->height, cachedInfo->depth); } else dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiledCached(x, y, cachedInfo); uint8* outputBlockData = outputData + dstOffset; if (bpp == 32) copyValue<uint32, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 16) copyValue<uint16, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 8) copyValue<uint8, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 64) copyValue<uint64, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 128) copyValue<uint64, 2, isWrite>(outputBlockData, inputBlockData); else { cemu_assert_unimplemented(); } if (bpp != 8) { inputBlockData += (bpp / 8); } } } } template<int bpp, bool isWrite> void retileTexture_tm04_sample1(ActiveTilingAperature* tilingAperture, uint8* inputData, uint8* outputData, sint32 texelWidth, sint32 texelHeight, sint32 surfaceSlice, sint32 surfacePitch, sint32 surfaceHeight, sint32 surfaceDepth, LatteAddrLib::CachedSurfaceAddrInfo* cachedInfo) { uint16* tableBase = cachedInfo->microTilePixelIndexTable + ((cachedInfo->slice & 7) << 6); for (sint32 y = 0; y < texelHeight; y++) { uint32 srcOffset; uint8* inputBlockData; if (bpp != 8) { srcOffset = (0 + y*surfacePitch)*(bpp / 8); inputBlockData = inputData + srcOffset; } for (sint32 bx = 0; bx < texelWidth; bx += 8) { uint16* pixelOffsets = tableBase + ((y&7) << 3); uint32 baseOffset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(bx, y, cachedInfo); for (sint32 x = bx; x < bx+8; x++) { // calculate address of input block if (bpp == 8) { sint32 texelX = x; sint32 texelY = y; texelX ^= 8; texelY ^= 2; srcOffset = (texelX + texelY*surfacePitch)*(bpp / 8); inputBlockData = inputData + srcOffset; } // calculate address of output block uint32 dstBitPos = 0; uint32 pixelIndex = *pixelOffsets; pixelOffsets++; uint32 pixelOffset = pixelIndex * (bpp/8); uint32 elemOffset = pixelOffset; if ((bpp * 8) > 256) { // separate group bytes, for small formats this step is not necessary since elemOffset is never over 0xFF (maximum is 8*8*bpp) elemOffset = (elemOffset & 0xFF) | ((elemOffset&~0xFF) << 3); } sint32 offset = baseOffset + elemOffset; uint8* outputBlockData = outputData + offset; if (bpp == 32) copyValue<uint32, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 16) copyValue<uint16, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 8) copyValue<uint8, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 64) copyValue<uint64, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 128) copyValue<uint64, 2, isWrite>(outputBlockData, inputBlockData); else { cemu_assert_unimplemented(); } if (bpp != 8) { inputBlockData += (bpp / 8); } } } // copy remaining partial block for (sint32 x = (texelWidth&~7); x < texelWidth; x++) { // calculate address of input block sint32 texelX = x; sint32 texelY = y; if (bpp == 8) { texelX ^= 8; texelY ^= 2; srcOffset = (texelX + texelY*surfacePitch)*(bpp / 8); inputBlockData = inputData + srcOffset; } // calculate address of output block uint32 dstBitPos = 0; uint32 dstOffset = 0; dstOffset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(x, y, cachedInfo); uint8* outputBlockData = outputData + dstOffset; if (bpp == 32) copyValue<uint32, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 16) copyValue<uint16, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 8) copyValue<uint8, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 64) copyValue<uint64, 1, isWrite>(outputBlockData, inputBlockData); else if (bpp == 128) copyValue<uint64, 2, isWrite>(outputBlockData, inputBlockData); else { cemu_assert_unimplemented(); } if (bpp != 8) { inputBlockData += (bpp / 8); } } } } template<int bpp, bool isWrite> void retileTextureWrapper(ActiveTilingAperature* tilingAperture, uint8* inputData, uint8* outputData, sint32 texelWidth, sint32 texelHeight, sint32 surfaceSlice, sint32 surfaceTileMode, sint32 surfacePitch, sint32 surfaceHeight, sint32 surfaceDepth, LatteAddrLib::CachedSurfaceAddrInfo* cachedInfo) { if (surfaceTileMode == 0) retileTexture<bpp, isWrite, 0>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); else if (surfaceTileMode == 1) retileTexture<bpp, isWrite, 1>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); else if (surfaceTileMode == 2) retileTexture<bpp, isWrite, 2>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); else if (surfaceTileMode == 3) retileTexture<bpp, isWrite, 3>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); else if (surfaceTileMode == 4) retileTexture<bpp, isWrite, 4>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); else if (surfaceTileMode == 7) retileTexture<bpp, isWrite, 7>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); else { cemu_assert_unimplemented(); } } void LatteTextureLoader_begin(LatteTextureLoaderCtx* textureLoader, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle); void GX2TilingAperature_RetileTexture(ActiveTilingAperature* tilingAperture, bool doWrite) { //uint64 timerTilingStart = benchmarkTimer_start(); Latte::E_GX2SURFFMT surfaceFormat = tilingAperture->surface.format; uint32 surfaceSlice = tilingAperture->sliceIndex; LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo = {0}; GX2::GX2CalculateSurfaceInfo(&tilingAperture->surface, tilingAperture->mipLevel, &surfaceInfo); uint32 surfacePitch = surfaceInfo.pitch; uint32 surfaceSwizzle = tilingAperture->surface.swizzle; uint32 surfacePipeSwizzle = (surfaceSwizzle>>8)&1; uint32 surfaceBankSwizzle = ((surfaceSwizzle>>9)&3); Latte::E_HWTILEMODE surfaceTileMode = surfaceInfo.hwTileMode; uint32 surfaceDepth = std::max<uint32>(surfaceInfo.depth, 1); sint32 width = std::max<uint32>((uint32)tilingAperture->surface.width >> tilingAperture->mipLevel, 1); sint32 height = std::max<uint32>((uint32)tilingAperture->surface.height >> tilingAperture->mipLevel, 1); Latte::E_DIM surfaceDim = tilingAperture->surface.dim; uint32 surfaceMipSwizzle = 0; // todo uint32 mipLevels = tilingAperture->surface.numLevels; // get texture info uint8* inputData = (uint8*)memory_getPointerFromVirtualOffset(tilingAperture->addr); uint8* outputData; if( tilingAperture->mipLevel == 0 ) outputData = (uint8*)memory_getPointerFromVirtualOffset(tilingAperture->surface.imagePtr); else if( tilingAperture->mipLevel == 1 ) outputData = (uint8*)memory_getPointerFromVirtualOffset(tilingAperture->surface.mipPtr); else outputData = (uint8*)memory_getPointerFromVirtualOffset(tilingAperture->surface.mipPtr + tilingAperture->surface.mipOffset[tilingAperture->mipLevel-1]); sint32 stepX = 1; sint32 stepY = 1; bool isCompressed = false; if( Latte::IsCompressedFormat(surfaceFormat) ) { isCompressed = true; stepX = 4; stepY = 4; } uint32 bpp = surfaceInfo.bpp; uint32 bytesPerPixel = bpp/8; LatteAddrLib::CachedSurfaceAddrInfo computeAddrInfo = { 0 }; SetupCachedSurfaceAddrInfo(&computeAddrInfo, surfaceSlice, 0, bpp, surfacePitch, surfaceInfo.height, surfaceInfo.depth, 1 * 1, surfaceTileMode, false, surfacePipeSwizzle, surfaceBankSwizzle); // init info for swizzle encoder/decoder LatteTextureLoaderCtx textureLoaderCtx{}; LatteTextureLoader_begin(&textureLoaderCtx, surfaceSlice, 0, tilingAperture->surface.imagePtr, tilingAperture->surface.mipPtr, surfaceFormat, surfaceDim, width, height, surfaceDepth, mipLevels, surfacePitch, surfaceTileMode, surfaceSwizzle); textureLoaderCtx.decodedTexelCountX = surfacePitch; textureLoaderCtx.decodedTexelCountY = isCompressed ? (height + 3) / 4 : height; if( doWrite ) { if (surfaceTileMode == Latte::E_HWTILEMODE::TM_2D_TILED_THIN1 && bpp == 32 && isCompressed == false) { optimizedDecodeLoops<uint32, 1, true, false>(&textureLoaderCtx, inputData); } else if (bpp == 8) retileTextureWrapper<8, true>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); else if (bpp == 16) retileTextureWrapper<16, true>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); else if (bpp == 32) retileTextureWrapper<32, true>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); else if (bpp == 64) retileTextureWrapper<64, true>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); else if (bpp == 128) retileTextureWrapper<128, true>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); else { cemu_assert_unimplemented(); } } else { if (surfaceTileMode == Latte::E_HWTILEMODE::TM_2D_TILED_THIN1 && bpp == 32 && isCompressed == false) { optimizedDecodeLoops<uint32, 1, false, false>(&textureLoaderCtx, inputData); } else if (bpp == 8) retileTextureWrapper<8, false>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); else if (bpp == 16) retileTextureWrapper<16, false>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); else if (bpp == 32) retileTextureWrapper<32, false>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); else if (bpp == 64) retileTextureWrapper<64, false>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); else if (bpp == 128) retileTextureWrapper<128, false>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); else { cemu_assert_unimplemented(); } } //double benchmarkTime = benchmarkTimer_stop(timerTilingStart); //cemuLog_logDebug(LogType::Force, "TilingAperture res {:04}x{:04} fmt {:04x} tm {:02x} mip {} isWrite {}", (uint32)tilingAperture->surface.width, (uint32)tilingAperture->surface.height, (uint32)tilingAperture->surface.format, (uint32)tilingAperture->surface.tileMode, tilingAperture->mipLevel, doWrite?1:0); //cemuLog_logDebug(LogType::Force, "Tiling took {:.4}ms", benchmarkTime); } void gx2Export_GX2AllocateTilingApertureEx(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2AllocateTilingApertureEx(0x{:08x}, {}, {}, {}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]); GX2Surface* surface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); cemuLog_log(LogType::GX2, "Tiling Tex: {:08x} {}x{} Swizzle: {:08x} tm: {} fmt: {:04x} use: {:02x}", (uint32)surface->imagePtr, (uint32)surface->width, (uint32)surface->height, (uint32)surface->swizzle, (uint32)surface->tileMode.value(), (uint32)surface->format.value(), (uint32)surface->resFlag); if( activeTilingAperatureCount >= GX2_MAX_ACTIVE_TILING_APERATURES ) { debugBreakpoint(); memory_writeU32(hCPU->gpr[8], MPTR_NULL); memory_writeU32(hCPU->gpr[7], 0); osLib_returnFromFunction(hCPU, 0); return; } uint32 mipLevel = hCPU->gpr[4]; uint32 sliceIndex = hCPU->gpr[5]; uint32 tilingSize = 0; // calculate size of texture Latte::E_GX2SURFFMT surfaceFormat = surface->format; uint32 bitsPerPixel = Latte::GetFormatBits(surfaceFormat); if (Latte::IsCompressedFormat(surfaceFormat)) bitsPerPixel /= (4*4); // get surface pitch LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo = {0}; GX2::GX2CalculateSurfaceInfo(surface, 0, &surfaceInfo); uint32 surfacePitch = surfaceInfo.pitch; uint32 width = std::max<uint32>((uint32)surface->width >> mipLevel, 1); uint32 height = std::max<uint32>((uint32)surface->height >> mipLevel, 1); uint32 alignedWidth = (width+3)&~3; uint32 alignedHeight = (height+3)&~3; tilingSize = (surfacePitch*alignedHeight*bitsPerPixel+7)/8; uint32 taHandle = GX2TilingAperature_GenerateHandle(); // allocate memory for tiling space MPTR tilingAddress = GX2TilingAperature_allocateTilingMemory(tilingSize); if( tilingAddress == MPTR_NULL ) { cemu_assert_suspicious(); memory_writeU32(hCPU->gpr[8], MPTR_NULL); memory_writeU32(hCPU->gpr[7], 0); osLib_returnFromFunction(hCPU, 0); return; } // add tiling aperture entry activeTilingAperature[activeTilingAperatureCount].addr = tilingAddress; activeTilingAperature[activeTilingAperatureCount].size = tilingSize; activeTilingAperature[activeTilingAperatureCount].handle = taHandle; activeTilingAperature[activeTilingAperatureCount].endianMode = hCPU->gpr[6]; activeTilingAperature[activeTilingAperatureCount].sliceIndex = sliceIndex; activeTilingAperature[activeTilingAperatureCount].mipLevel = mipLevel; memcpy(&activeTilingAperature[activeTilingAperatureCount].surface, surface, sizeof(GX2Surface)); activeTilingAperatureCount++; // return values memory_writeU32(hCPU->gpr[8], tilingAddress); memory_writeU32(hCPU->gpr[7], taHandle); // load texture data into tiling area GX2TilingAperature_RetileTexture(activeTilingAperature+activeTilingAperatureCount-1, false); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2FreeTilingAperture(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2FreeTilingAperture(0x{:08x})", hCPU->gpr[3]); uint32 handle = hCPU->gpr[3]; for(sint32 i=0; i<activeTilingAperatureCount; i++) { if( activeTilingAperature[i].handle == handle ) { // flush texture GX2TilingAperature_RetileTexture(activeTilingAperature+i, true); // remove entry if( i+1 < activeTilingAperatureCount ) { memcpy(activeTilingAperature+i, activeTilingAperature+activeTilingAperatureCount-1, sizeof(ActiveTilingAperature)); } activeTilingAperatureCount--; osLib_returnFromFunction(hCPU, 0); return; } } osLib_returnFromFunction(hCPU, 0); } ================================================ FILE: src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Latte/Core/LattePM4.h" #include "GX2.h" #include "GX2_Shader.h" void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetPixelShader(0x{:08x})", hCPU->gpr[3]); GX2PixelShader_t* pixelShader = (GX2PixelShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint32 numInputs = _swapEndianU32(pixelShader->regs[4]); if( numInputs > 0x20 ) numInputs = 0x20; GX2::GX2ReserveCmdSpace(26 + numInputs); MPTR shaderProgramAddr; uint32 shaderProgramSize; if( _swapEndianU32(pixelShader->shaderPtr) != MPTR_NULL ) { // old format shaderProgramAddr = _swapEndianU32(pixelShader->shaderPtr); shaderProgramSize = _swapEndianU32(pixelShader->shaderSize); } else { shaderProgramAddr = pixelShader->rBuffer.GetVirtualAddr(); shaderProgramSize = pixelShader->rBuffer.GetSize(); } gx2WriteGather_submit( /* pixel shader program */ pm4HeaderType3(IT_SET_CONTEXT_REG, 6), mmSQ_PGM_START_PS - 0xA000, memory_virtualToPhysical(shaderProgramAddr)>>8, // address shaderProgramSize>>3, // size 0x100000, 0x100000, _swapEndianU32(pixelShader->regs[0]), // ukn /* setup pixel shader input control */ pm4HeaderType3(IT_SET_CONTEXT_REG, 3), mmSPI_PS_IN_CONTROL_0-0xA000, _swapEndianU32(pixelShader->regs[2]), _swapEndianU32(pixelShader->regs[3])); // setup pixel shader extended inputs control gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numInputs)); gx2WriteGather_submitU32AsBE(mmSPI_PS_INPUT_CNTL_0-0xA000); for(uint32 i=0; i<numInputs; i++) { uint32 inputData = _swapEndianU32(pixelShader->regs[5+i]); gx2WriteGather_submitU32AsBE(inputData); } gx2WriteGather_submit( /* mmCB_SHADER_MASK */ pm4HeaderType3(IT_SET_CONTEXT_REG, 2), mmCB_SHADER_MASK-0xA000, _swapEndianU32(pixelShader->regs[37]), /* mmCB_SHADER_CONTROL */ pm4HeaderType3(IT_SET_CONTEXT_REG, 2), mmCB_SHADER_CONTROL-0xA000, _swapEndianU32(pixelShader->regs[38]), /* mmDB_SHADER_CONTROL */ pm4HeaderType3(IT_SET_CONTEXT_REG, 2), mmDB_SHADER_CONTROL-0xA000, _swapEndianU32(pixelShader->regs[39]), /* SPI_INPUT_Z */ pm4HeaderType3(IT_SET_CONTEXT_REG, 2), mmSPI_INPUT_Z-0xA000, _swapEndianU32(pixelShader->regs[40])); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetGeometryShader(0x{:08x})", hCPU->gpr[3]); GX2GeometryShader_t* geometryShader = (GX2GeometryShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint32 numOutputIds = _swapEndianU32(geometryShader->regs[7]); numOutputIds = std::min<uint32>(numOutputIds, 0xA); uint32 reserveSize = 38; // 38 fixed parameters if (numOutputIds != 0) reserveSize += 2 + numOutputIds; if( _swapEndianU32(geometryShader->useStreamout) != 0 ) reserveSize += 2 + 12; GX2::GX2ReserveCmdSpace(reserveSize); MPTR shaderProgramAddr; uint32 shaderProgramSize; if( _swapEndianU32(geometryShader->shaderPtr) != MPTR_NULL ) { // old format shaderProgramAddr = _swapEndianU32(geometryShader->shaderPtr); shaderProgramSize = _swapEndianU32(geometryShader->shaderSize); } else { shaderProgramAddr = geometryShader->rBuffer.GetVirtualAddr(); shaderProgramSize = geometryShader->rBuffer.GetSize(); } gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 6)); gx2WriteGather_submitU32AsBE(mmSQ_PGM_START_GS-0xA000); gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(shaderProgramAddr)>>8); gx2WriteGather_submitU32AsBE(shaderProgramSize>>3); gx2WriteGather_submitU32AsBE(0x100000); gx2WriteGather_submitU32AsBE(0x100000); gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[0])); // unknown content (SQ_PGM_RESOURCES_GS) uint32 primitiveOut = _swapEndianU32(geometryShader->regs[1]); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmVGT_GS_OUT_PRIM_TYPE-0xA000); gx2WriteGather_submitU32AsLE(geometryShader->regs[1]); gx2WriteGather_submit( pm4HeaderType3(IT_SET_CONTEXT_REG, 2), Latte::REGADDR::VGT_GS_MODE - 0xA000, geometryShader->reg.VGT_GS_MODE ); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmSQ_PGM_RESOURCES_GS-0xA000); gx2WriteGather_submitU32AsLE(geometryShader->regs[0]); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmSQ_GS_VERT_ITEMSIZE-0xA000); gx2WriteGather_submitU32AsLE(geometryShader->regs[5]); if( _swapEndianU32(geometryShader->useStreamout) != 0 ) { // todo - IT_EVENT_WRITE packet here // stride 0 gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_0-0xA000); gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->streamoutStride[0])>>2); // stride 1 gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_1-0xA000); gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->streamoutStride[1])>>2); // stride 2 gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_2-0xA000); gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->streamoutStride[2])>>2); // stride 3 gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_3-0xA000); gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->streamoutStride[3])>>2); } gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_BUFFER_EN-0xA000); gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[18])); // set copy shader (written to vertex shader registers, vs in turn is written to es registers) MPTR copyShaderProgramAddr; uint32 copyShaderProgramSize; if( _swapEndianU32(geometryShader->copyShaderPtr) != MPTR_NULL ) { copyShaderProgramAddr = _swapEndianU32(geometryShader->copyShaderPtr); copyShaderProgramSize = _swapEndianU32(geometryShader->copyShaderSize); } else { copyShaderProgramAddr = geometryShader->rBufferCopyProgram.GetVirtualAddr(); copyShaderProgramSize = geometryShader->rBufferCopyProgram.GetSize(); } cemu_assert_debug((copyShaderProgramAddr>>8) != 0); cemu_assert_debug((copyShaderProgramSize>>3) != 0); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 6)); gx2WriteGather_submitU32AsBE(mmSQ_PGM_START_VS-0xA000); gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(copyShaderProgramAddr)>>8); gx2WriteGather_submitU32AsBE(copyShaderProgramSize>>3); gx2WriteGather_submitU32AsBE(0x100000); gx2WriteGather_submitU32AsBE(0x100000); gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[4])); // mmSQ_PGM_RESOURCES_VS gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmPA_CL_VS_OUT_CNTL-0xA000); gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[3])); // GS outputs if( numOutputIds != 0 ) { gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numOutputIds)); gx2WriteGather_submitU32AsBE(mmSPI_VS_OUT_ID_0-0xA000); for(uint32 i=0; i<numOutputIds; i++) { gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[8+i])); } } // output config gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmSPI_VS_OUT_CONFIG-0xA000); gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[6])); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmSQ_GSVS_RING_ITEMSIZE-0xA000); gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->ringItemsize)&0x7FFF); /* Geometry shader registers in regs[19]: 0 SQ_PGM_RESOURCES_GS ? 1 mmVGT_GS_OUT_PRIM_TYPE 2 mmVGT_GS_MODE 3 mmPA_CL_VS_OUT_CNTL 4 mmSQ_PGM_RESOURCES_VS (set in combination with mmSQ_PGM_START_VS) 5 mmSQ_GS_VERT_ITEMSIZE 6 mmSPI_VS_OUT_CONFIG 7 number of active mmSPI_VS_OUT_ID_* fields? 8-17 mmSPI_VS_OUT_ID_* 18 mmVGT_STRMOUT_BUFFER_EN */ osLib_returnFromFunction(hCPU, 0); } struct GX2ComputeShader { /* +0x00 */ uint32be regs[12]; /* +0x30 */ uint32be programSize; /* +0x34 */ uint32be programPtr; /* +0x38 */ uint32be ukn38; /* +0x3C */ uint32be ukn3C; /* +0x40 */ uint32be ukn40[8]; /* +0x60 */ uint32be workgroupSizeX; /* +0x64 */ uint32be workgroupSizeY; /* +0x68 */ uint32be workgroupSizeZ; /* +0x6C */ uint32be workgroupSizeSpecial; /* +0x70 */ uint32be ukn70; /* +0x74 */ GX2RBuffer rBuffer; }; static_assert(offsetof(GX2ComputeShader, programSize) == 0x30); static_assert(offsetof(GX2ComputeShader, workgroupSizeX) == 0x60); static_assert(offsetof(GX2ComputeShader, rBuffer) == 0x74); void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(computeShader, GX2ComputeShader, 0); cemuLog_log(LogType::GX2, "GX2SetComputeShader(0x{:08x})", hCPU->gpr[3]); MPTR shaderPtr; uint32 shaderSize; if (computeShader->programPtr) { shaderPtr = computeShader->programPtr; shaderSize = computeShader->programSize; } else { shaderPtr = computeShader->rBuffer.GetVirtualAddr(); shaderSize = computeShader->rBuffer.GetSize(); } GX2::GX2ReserveCmdSpace(0x11); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 6), mmSQ_PGM_START_ES-0xA000, memory_virtualToPhysical(shaderPtr) >> 8, shaderSize >> 3, 0x100000, 0x100000, computeShader->regs[0]); // todo: Other registers osLib_returnFromFunction(hCPU, 0); } void _GX2SubmitUniformBlock(uint32 registerBase, uint32 index, MPTR virtualAddress, uint32 size) { GX2::GX2ReserveCmdSpace(9); gx2WriteGather_submit(pm4HeaderType3(IT_SET_RESOURCE, 8), registerBase + index * 7, memory_virtualToPhysical(virtualAddress), size - 1, 0, 1, 0, // ukn 0, // ukn 0xC0000000); } void gx2Export_GX2SetVertexUniformBlock(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetVertexUniformBlock(0x{:08x},0x{:x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); _GX2SubmitUniformBlock(mmSQ_VTX_UNIFORM_BLOCK_START - mmSQ_TEX_RESOURCE_WORD0, hCPU->gpr[3], hCPU->gpr[5], hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetPixelUniformBlock(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetPixelUniformBlock(0x{:08x},0x{:x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); _GX2SubmitUniformBlock(mmSQ_PS_UNIFORM_BLOCK_START - mmSQ_TEX_RESOURCE_WORD0, hCPU->gpr[3], hCPU->gpr[5], hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetGeometryUniformBlock(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetGeometryUniformBlock(0x{:08x},0x{:x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); _GX2SubmitUniformBlock(mmSQ_GS_UNIFORM_BLOCK_START - mmSQ_TEX_RESOURCE_WORD0, hCPU->gpr[3], hCPU->gpr[5], hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2RSetVertexUniformBlock(PPCInterpreter_t* hCPU) { GX2::GX2ReserveCmdSpace(9); GX2RBuffer* bufferPtr = (GX2RBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint32 index = hCPU->gpr[4]; uint32 offset = hCPU->gpr[5]; _GX2SubmitUniformBlock(mmSQ_VTX_UNIFORM_BLOCK_START - mmSQ_TEX_RESOURCE_WORD0, index, bufferPtr->GetVirtualAddr() + offset, bufferPtr->GetSize() - offset); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetShaderModeEx(PPCInterpreter_t* hCPU) { GX2::GX2ReserveCmdSpace(8+4); uint32 mode = hCPU->gpr[3]; uint32 sqConfig = hCPU->gpr[3] == 0 ? 4 : 0; if (mode == GX2_SHADER_MODE_COMPUTE_SHADER) sqConfig |= 0xE4000000; // ES/GS/PS priority? // todo - other sqConfig bits gx2WriteGather_submit((uint32)(pm4HeaderType3(IT_SET_CONFIG_REG, 7)), (uint32)(mmSQ_CONFIG - 0x2000), sqConfig, 0, // ukn / todo 0, // ukn / todo 0, // ukn / todo 0, // ukn / todo 0 // ukn / todo ); // if not GS, then update mmVGT_GS_MODE if( mode != GX2_SHADER_MODE_GEOMETRY_SHADER ) { // update VGT_GS_MODE only if no geometry shader is used (else this register is already set by GX2SetGeometryShader) gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(Latte::REGADDR::VGT_GS_MODE-0xA000); if (mode == GX2_SHADER_MODE_COMPUTE_SHADER) gx2WriteGather_submitU32AsBE(Latte::LATTE_VGT_GS_MODE().set_MODE(Latte::LATTE_VGT_GS_MODE::E_MODE::SCENARIO_G).set_COMPUTE_MODE(Latte::LATTE_VGT_GS_MODE::E_COMPUTE_MODE::ON).set_PARTIAL_THD_AT_EOI(true).getRawValueBE()); else gx2WriteGather_submitU32AsBE(_swapEndianU32(0)); } osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2CalcGeometryShaderInputRingBufferSize(PPCInterpreter_t* hCPU) { uint32 size = (hCPU->gpr[3]*4) * 0x1000; osLib_returnFromFunction(hCPU, size); } void gx2Export_GX2CalcGeometryShaderOutputRingBufferSize(PPCInterpreter_t* hCPU) { uint32 size = (hCPU->gpr[3]*4) * 0x1000; osLib_returnFromFunction(hCPU, size); } ================================================ FILE: src/Cafe/OS/libs/h264_avc/H264Dec.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/h264_avc/parser/H264Parser.h" #include "Cafe/OS/libs/h264_avc/H264DecInternal.h" #include "util/highresolutiontimer/HighResolutionTimer.h" #include "Cafe/CafeSystem.h" #include "h264dec.h" enum class H264DEC_STATUS : uint32 { SUCCESS = 0x0, BAD_STREAM = 0x1000000, INVALID_PARAM = 0x1010000, }; namespace H264 { bool H264_IsBotW() { // Cemuhook has a hack where it always returns a small size for H264DECMemoryRequirement (256 bytes) // it also outputs images pre-cropped instead of giving the game raw uncropped images // both of these are required to allow Breath of the Wild to playback the higher res (1080p) videos from the Switch version // we mirror these hacks for user convenience and because there are no downsides uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); if (currentTitleId == 0x00050000101c9500 || currentTitleId == 0x00050000101c9400 || currentTitleId == 0x00050000101c9300) return true; return false; } struct H264Context { struct { MEMPTR<void> ptr{ nullptr }; uint32be length{ 0 }; float64be timestamp; }BitStream; struct { MEMPTR<void> outputFunc{ nullptr }; uint8be outputPerFrame{ 0 }; // whats the default? MEMPTR<void> userMemoryParam{ nullptr }; }Param; // misc uint32be sessionHandle; // decoder state struct { uint32 numFramesInFlight{0}; }decoderState; }; uint32 H264DECMemoryRequirement(uint32 codecProfile, uint32 codecLevel, uint32 width, uint32 height, uint32be* sizeRequirementOut) { if (H264_IsBotW()) { static_assert(sizeof(H264Context) < 256); *sizeRequirementOut = 256; return 0; } // note: On console this seems to check if maxWidth or maxHeight < 64 but Pikmin 3 passes 32x32 and crashes if this function fails ? if (width < 0x20 || height < 0x20 || width > 2800 || height > 1408 || sizeRequirementOut == MPTR_NULL || codecLevel >= 52 || (codecProfile != 0x42 && codecProfile != 0x4D && codecProfile != 0x64)) return 0x1010000; uint32 workbufferSize = 0; if (codecLevel < 0xB) { workbufferSize = 0x18C << 10; } else if (codecLevel == 0xB) { workbufferSize = 0x384 << 10; } else if (codecLevel >= 0xC && codecLevel <= 0x14) { workbufferSize = 0x948 << 10; } else if (codecLevel == 0x15) { workbufferSize = 0x1290 << 10; } else if (codecLevel >= 0x16 && codecLevel <= 0x1E) { workbufferSize = 0x1FA4 << 10; } else if (codecLevel == 0x1F) { workbufferSize = 0x4650 << 10; } else if (codecLevel == 0x20) { workbufferSize = 0x1400000; } else if (codecLevel >= 0x21 && codecLevel <= 0x29) { workbufferSize = 0x8000 << 10; } else if (codecLevel == 0x2A) { workbufferSize = 0x2200000; } else if (codecLevel >= 0x2B && codecLevel <= 0x32) { workbufferSize = 0x1AF40 << 10; } else if (codecLevel >= 0x33) { workbufferSize = 0x2D000 << 10; } workbufferSize += 0x447; *sizeRequirementOut = workbufferSize; return 0; } uint32 H264DECCheckMemSegmentation(MPTR memory, uint32 size) { // return 0 for valid, 1 for invalid // currently we allow any range return 0; } H264DEC_STATUS H264DECFindDecstartpoint(uint8* ptr, uint32 length, uint32be* offsetOut) { if (!ptr || length < 4 || !offsetOut) return H264DEC_STATUS::INVALID_PARAM; for (uint32 i = 0; i < length - 4; ++i) { uint8 b = ptr[i]; if (b != 0) continue; b = ptr[i + 1]; if (b != 0) continue; b = ptr[i + 2]; if (b != 1) continue; b = ptr[i + 3]; b &= 0x9F; if (b != 7) // check for NAL type SPS continue; if (i > 0) *offsetOut = i - 1; else *offsetOut = 0; return H264DEC_STATUS::SUCCESS; } return H264DEC_STATUS::BAD_STREAM; } H264DEC_STATUS H264DECFindIdrpoint(uint8* ptr, uint32 length, uint32be* offsetOut) { if (!ptr || length < 4 || !offsetOut) return H264DEC_STATUS::INVALID_PARAM; for (uint32 i = 0; i < length - 4; ++i) { uint8 b = ptr[i]; if (b != 0) continue; b = ptr[i + 1]; if (b != 0) continue; b = ptr[i + 2]; if (b != 1) continue; b = ptr[i + 3]; b &= 0x9F; if (b != 5 && b != 7 && b != 8) // check for NAL type IDR slice, but also accept SPS or PPS slices continue; if (i > 0) *offsetOut = i - 1; else *offsetOut = 0; return H264DEC_STATUS::SUCCESS; } return H264DEC_STATUS::BAD_STREAM; } H264DEC_STATUS H264DECGetImageSize(uint8* stream, uint32 length, uint32 offset, uint32be* outputWidth, uint32be* outputHeight) { if(!stream || length < 4 || !outputWidth || !outputHeight) return H264DEC_STATUS::INVALID_PARAM; if( (offset+4) > length ) return H264DEC_STATUS::INVALID_PARAM; uint8* cur = stream + offset; uint8* end = stream + length; cur += 2; // we access cur[-2] and cur[-1] so we need to start at offset 2 while(cur < end-2) { // check for start code if(*cur != 1) { cur++; continue; } // check if this is a valid NAL header if(cur[-2] != 0 || cur[-1] != 0 || cur[0] != 1) { cur++; continue; } uint8 nalHeader = cur[1]; if((nalHeader & 0x1F) != 7) { cur++; continue; } h264State_seq_parameter_set_t psp; bool r = h264Parser_ParseSPS(cur+2, end-cur-2, psp); if(!r) { cemu_assert_suspicious(); // should not happen return H264DEC_STATUS::BAD_STREAM; } *outputWidth = (psp.pic_width_in_mbs_minus1 + 1) * 16; *outputHeight = (psp.pic_height_in_map_units_minus1 + 1) * 16; // affected by frame_mbs_only_flag? return H264DEC_STATUS::SUCCESS; } return H264DEC_STATUS::BAD_STREAM; } uint32 H264DECInitParam(uint32 workMemorySize, void* workMemory) { H264Context* ctx = (H264Context*)workMemory; *ctx = {}; return 0; } std::unordered_map<uint32, H264DecoderBackend*> sDecoderSessions; std::mutex sDecoderSessionsMutex; std::atomic_uint32_t sCurrentSessionHandle{ 1 }; H264DecoderBackend* CreateAVCDecoder(); static H264DecoderBackend* _CreateDecoderSession(uint32& handleOut) { std::unique_lock _lock(sDecoderSessionsMutex); handleOut = sCurrentSessionHandle.fetch_add(1); H264DecoderBackend* session = CreateAVCDecoder(); sDecoderSessions.try_emplace(handleOut, session); return session; } static H264DecoderBackend* _AcquireDecoderSession(uint32 handle) { std::unique_lock _lock(sDecoderSessionsMutex); auto it = sDecoderSessions.find(handle); if (it == sDecoderSessions.end()) return nullptr; H264DecoderBackend* session = it->second; if (sDecoderSessions.size() >= 5) { cemuLog_log(LogType::Force, "H264: Warning - more than 5 active sessions"); cemu_assert_suspicious(); } return session; } static void _ReleaseDecoderSession(H264DecoderBackend* session) { std::unique_lock _lock(sDecoderSessionsMutex); } static void _DestroyDecoderSession(uint32 handle) { std::unique_lock _lock(sDecoderSessionsMutex); auto it = sDecoderSessions.find(handle); if (it == sDecoderSessions.end()) return; H264DecoderBackend* session = it->second; session->Destroy(); delete session; sDecoderSessions.erase(it); } uint32 H264DECOpen(void* workMemory) { H264Context* ctx = (H264Context*)workMemory; uint32 sessionHandle; _CreateDecoderSession(sessionHandle); ctx->sessionHandle = sessionHandle; return 0; } uint32 H264DECClose(void* workMemory) { if (workMemory) { H264Context* ctx = (H264Context*)workMemory; _DestroyDecoderSession(ctx->sessionHandle); } return 0; } uint32 H264DECBegin(void* workMemory) { H264Context* ctx = (H264Context*)workMemory; H264DecoderBackend* session = _AcquireDecoderSession(ctx->sessionHandle); if (!session) { cemuLog_log(LogType::Force, "H264DECBegin(): Invalid session"); return 0; } session->Init(ctx->Param.outputPerFrame == 0); ctx->decoderState.numFramesInFlight = 0; _ReleaseDecoderSession(session); return 0; } void H264DoFrameOutputCallback(H264Context* ctx, H264DecoderBackend::DecodeResult& decodeResult); H264DEC_STATUS H264DECEnd(void* workMemory) { H264Context* ctx = (H264Context*)workMemory; H264DecoderBackend* session = _AcquireDecoderSession(ctx->sessionHandle); if (!session) { cemuLog_log(LogType::Force, "H264DECEnd(): Invalid session"); return H264DEC_STATUS::SUCCESS; } coreinit::OSEvent* flushEvt = &session->GetFlushEvent(); coreinit::OSResetEvent(flushEvt); session->QueueFlush(); coreinit::OSWaitEvent(flushEvt); while(true) { H264DecoderBackend::DecodeResult decodeResult; if( !session->GetFrameOutputIfReady(decodeResult) ) break; // todo - output all frames in a single callback? H264DoFrameOutputCallback(ctx, decodeResult); ctx->decoderState.numFramesInFlight--; } cemu_assert_debug(ctx->decoderState.numFramesInFlight == 0); // no frames should be in flight anymore. Exact behavior is not well understood but we may have to output dummy frames if necessary _ReleaseDecoderSession(session); return H264DEC_STATUS::SUCCESS; } H264DEC_STATUS H264DECSetParam_FPTR_OUTPUT(H264Context* ctx, void* outputFunc) { ctx->Param.outputFunc = outputFunc; return H264DEC_STATUS::SUCCESS; } H264DEC_STATUS H264DECSetParam_OUTPUT_PER_FRAME(H264Context* ctx, uint32 outputPerFrame) { ctx->Param.outputPerFrame = outputPerFrame != 0 ? 1 : 0; return H264DEC_STATUS::SUCCESS; } H264DEC_STATUS H264DECSetParam_USER_MEMORY(H264Context* ctx, MEMPTR<void*>* userMemoryParamPtr) { ctx->Param.userMemoryParam = *userMemoryParamPtr; return H264DEC_STATUS::SUCCESS; } H264DEC_STATUS H264DECSetParam(H264Context* ctx, uint32 paramId, void* paramValue) { const uint32 PARAMID_FPTR_OUTPUT = 0x1; const uint32 PARAMID_OUTPUT_PER_FRAME = 0x20000002; const uint32 PARAMID_USER_MEMORY = 0x70000001; const uint32 PARAMID_UKN = 0x20000030; if (paramId == PARAMID_FPTR_OUTPUT) { ctx->Param.outputFunc = paramValue; } else if (paramId == PARAMID_USER_MEMORY) { ctx->Param.userMemoryParam = paramValue; } else if (paramId == PARAMID_OUTPUT_PER_FRAME) { ctx->Param.outputPerFrame = *(uint8be*)paramValue != 0; } else if (paramId == PARAMID_UKN) { // unknown purpose, seen in MK8. paramValue points to a bool } else { cemuLog_log(LogType::Force, "h264Export_H264DECSetParam(): Unsupported parameterId 0x{:08x}\n", paramId); cemu_assert_unimplemented(); } return H264DEC_STATUS::SUCCESS; } uint32 H264DECSetBitstream(void* workMemory, void* ptr, uint32 length, double timestamp) { H264Context* ctx = (H264Context*)workMemory; ctx->BitStream.ptr = ptr; ctx->BitStream.length = length; ctx->BitStream.timestamp = timestamp; return 0; } struct H264DECFrameOutput { /* +0x00 */ uint32be result; /* +0x04 */ uint32be padding04; /* +0x08 */ betype<double> timestamp; /* +0x10 */ uint32be frameWidth; /* +0x14 */ uint32be frameHeight; /* +0x18 */ uint32be bytesPerRow; /* +0x1C */ uint32be cropEnable; /* +0x20 */ uint32be cropTop; /* +0x24 */ uint32be cropBottom; /* +0x28 */ uint32be cropLeft; /* +0x2C */ uint32be cropRight; /* +0x30 */ uint32be ukn30; /* +0x34 */ uint32be ukn34; /* +0x38 */ uint32be ukn38; /* +0x3C */ uint32be ukn3C; /* +0x40 */ uint32be ukn40; /* +0x44 */ MEMPTR<uint8> imagePtr; /* +0x48 */ uint32 vuiEnable; /* +0x4C */ MPTR vuiPtr; /* +0x50 */ sint32 unused[10]; }; struct H264OutputCBStruct { uint32be frameCount; MEMPTR<MEMPTR<H264DECFrameOutput>> resultArray; uint32be userParam; }; static_assert(sizeof(H264OutputCBStruct) == 12); void H264DoFrameOutputCallback(H264Context* ctx, H264DecoderBackend::DecodeResult& decodeResult) { sint32 outputFrameCount = 1; cemu_assert(outputFrameCount < 8); StackAllocator<MEMPTR<void>, 8> stack_resultPtrArray; StackAllocator<H264DECFrameOutput, 8> stack_decodedFrameResult; for (sint32 i = 0; i < outputFrameCount; i++) stack_resultPtrArray[i] = &stack_decodedFrameResult + i; H264DECFrameOutput* frameOutput = &stack_decodedFrameResult + 0; memset(frameOutput, 0x00, sizeof(H264DECFrameOutput)); frameOutput->imagePtr = (uint8*)decodeResult.imageOutput; frameOutput->result = 100; frameOutput->timestamp = decodeResult.timestamp; frameOutput->frameWidth = decodeResult.frameWidth; frameOutput->frameHeight = decodeResult.frameHeight; frameOutput->bytesPerRow = decodeResult.bytesPerRow; frameOutput->cropEnable = decodeResult.cropEnable; frameOutput->cropTop = decodeResult.cropTop; frameOutput->cropBottom = decodeResult.cropBottom; frameOutput->cropLeft = decodeResult.cropLeft; frameOutput->cropRight = decodeResult.cropRight; StackAllocator<H264OutputCBStruct> stack_fptrOutputData; stack_fptrOutputData->frameCount = outputFrameCount; stack_fptrOutputData->resultArray = (MEMPTR<H264DECFrameOutput>*)stack_resultPtrArray.GetPointer(); stack_fptrOutputData->userParam = ctx->Param.userMemoryParam.GetBEValue(); // FPTR callback if (!ctx->Param.outputFunc.IsNull()) { cemuLog_log(LogType::H264, "H264: Outputting frame via callback. Timestamp: {} Buffer 0x{:08x} UserParam 0x{:08x}", (double)decodeResult.timestamp, (uint32)frameOutput->imagePtr.GetMPTR(), ctx->Param.userMemoryParam.GetMPTR()); PPCCoreCallback(ctx->Param.outputFunc.GetMPTR(), stack_fptrOutputData.GetMPTR()); } } uint32 H264DECExecute(void* workMemory, void* imageOutput) { BenchmarkTimer bt; bt.Start(); H264Context* ctx = (H264Context*)workMemory; H264DecoderBackend* session = _AcquireDecoderSession(ctx->sessionHandle); if (!session) { cemuLog_log(LogType::Force, "H264DECExecute(): Invalid session"); return 0; } // feed data to backend session->QueueForDecode((uint8*)ctx->BitStream.ptr.GetPtr(), ctx->BitStream.length, ctx->BitStream.timestamp, imageOutput); ctx->decoderState.numFramesInFlight++; // H264DECExecute is synchronous and will return a frame after either every call (non-buffered) or after 6 calls (buffered) // normally frame decoding happens only during H264DECExecute, but in order to hide the latency of our CPU decoder we will decode asynchronously in buffered mode uint32 numFramesToBuffer = (ctx->Param.outputPerFrame == 0) ? 5 : 0; if(ctx->decoderState.numFramesInFlight > numFramesToBuffer) { ctx->decoderState.numFramesInFlight--; while(true) { coreinit::OSEvent& evt = session->GetFrameOutputEvent(); coreinit::OSWaitEvent(&evt); H264DecoderBackend::DecodeResult decodeResult; if( !session->GetFrameOutputIfReady(decodeResult) ) continue; H264DoFrameOutputCallback(ctx, decodeResult); break; } } _ReleaseDecoderSession(session); bt.Stop(); double callTime = bt.GetElapsedMilliseconds(); cemuLog_log(LogType::H264, "H264Bench | H264DECExecute took {}ms", callTime); return 0x80 | 100; } H264DEC_STATUS H264DECCheckDecunitLength(void* workMemory, uint8* data, uint32 maxLength, uint32 offset, uint32be* unitLengthOut) { // todo: our implementation for this currently doesn't parse slice headers and instead assumes that each frame is encoded into a single NAL slice. For all known cases this is sufficient but it doesn't match console behavior for cases where frames are split into multiple NALs if (offset >= maxLength || maxLength < 4) { return H264DEC_STATUS::INVALID_PARAM; } data += offset; maxLength -= offset; NALInputBitstream nalStream(data, maxLength); if (nalStream.hasError()) { cemu_assert_debug(false); return H264DEC_STATUS::BAD_STREAM; } // search for start code sint32 startCodeOffset = 0; bool hasStartcode = false; while (startCodeOffset < (sint32)(maxLength - 3)) { if (data[startCodeOffset + 0] == 0x00 && data[startCodeOffset + 1] == 0x00 && data[startCodeOffset + 2] == 0x01) { hasStartcode = true; break; } startCodeOffset++; } if (hasStartcode == false) return H264DEC_STATUS::BAD_STREAM; data += startCodeOffset; maxLength -= startCodeOffset; // parse NAL data while (true) { if (nalStream.isEndOfStream()) break; RBSPInputBitstream rbspStream; if (nalStream.getNextRBSP(rbspStream, true) == false) break; sint32 streamSubOffset = (sint32)(rbspStream.getBasePtr() - data); sint32 streamSubLength = rbspStream.getBaseLength(); // parse NAL header uint8 nalHeaderByte = rbspStream.readU8(); if ((nalHeaderByte & 0x80) != 0) { // MSB must be zero cemu_assert_debug(false); continue; } uint8 nal_ref_idc = (nalHeaderByte >> 5) & 0x3; uint8 nal_unit_type = (nalHeaderByte >> 0) & 0x1f; if (nal_unit_type == 14 || nal_unit_type == 20 || nal_unit_type == 21) { cemu_assert_debug(false); continue; } switch (nal_unit_type) { case 1: case 5: { *unitLengthOut = (sint32)((rbspStream.getBasePtr() + rbspStream.getBaseLength()) - data) + startCodeOffset; return H264DEC_STATUS::SUCCESS; } case 6: // SEI break; case 7: // SPS break; case 8: // PPS break; case 9: // access unit delimiter break; case 10: // end of sequence break; default: cemuLog_logDebug(LogType::Force, "Unsupported NAL unit type {}", nal_unit_type); cemu_assert_unimplemented(); // todo break; } } return H264DEC_STATUS::BAD_STREAM; } class : public COSModule { public: std::string_view GetName() override { return "h264"; } void RPLMapped() override { cafeExportRegister("h264", H264DECCheckMemSegmentation, LogType::H264); cafeExportRegister("h264", H264DECMemoryRequirement, LogType::H264); cafeExportRegister("h264", H264DECFindDecstartpoint, LogType::H264); cafeExportRegister("h264", H264DECFindIdrpoint, LogType::H264); cafeExportRegister("h264", H264DECGetImageSize, LogType::H264); cafeExportRegister("h264", H264DECInitParam, LogType::H264); cafeExportRegister("h264", H264DECOpen, LogType::H264); cafeExportRegister("h264", H264DECClose, LogType::H264); cafeExportRegister("h264", H264DECBegin, LogType::H264); cafeExportRegister("h264", H264DECEnd, LogType::H264); cafeExportRegister("h264", H264DECSetParam_FPTR_OUTPUT, LogType::H264); cafeExportRegister("h264", H264DECSetParam_OUTPUT_PER_FRAME, LogType::H264); cafeExportRegister("h264", H264DECSetParam_USER_MEMORY, LogType::H264); cafeExportRegister("h264", H264DECSetParam, LogType::H264); cafeExportRegister("h264", H264DECSetBitstream, LogType::H264); cafeExportRegister("h264", H264DECExecute, LogType::H264); cafeExportRegister("h264", H264DECCheckDecunitLength, LogType::H264); }; }s_COSh264Module; COSModule* GetModule() { return &s_COSh264Module; } } ================================================ FILE: src/Cafe/OS/libs/h264_avc/H264DecBackendAVC.cpp ================================================ #include "H264DecInternal.h" #include "util/highresolutiontimer/HighResolutionTimer.h" extern "C" { #include "../dependencies/ih264d/common/ih264_typedefs.h" #include "../dependencies/ih264d/decoder/ih264d.h" }; namespace H264 { bool H264_IsBotW(); class H264AVCDecoder : public H264DecoderBackend { static void* ivd_aligned_malloc(void* ctxt, WORD32 alignment, WORD32 size) { #ifdef _WIN32 return _aligned_malloc(size, alignment); #else // alignment is atleast sizeof(void*) alignment = std::max<WORD32>(alignment, sizeof(void*)); //smallest multiple of 2 at least as large as alignment alignment--; alignment |= alignment << 1; alignment |= alignment >> 1; alignment |= alignment >> 2; alignment |= alignment >> 4; alignment |= alignment >> 8; alignment |= alignment >> 16; alignment ^= (alignment >> 1); void* temp; posix_memalign(&temp, (size_t)alignment, (size_t)size); return temp; #endif } static void ivd_aligned_free(void* ctxt, void* buf) { #ifdef _WIN32 _aligned_free(buf); #else free(buf); #endif } public: H264AVCDecoder() { m_decoderThread = std::thread(&H264AVCDecoder::DecoderThread, this); } ~H264AVCDecoder() { m_threadShouldExit = true; m_decodeSem.increment(); if (m_decoderThread.joinable()) m_decoderThread.join(); } void Init(bool isBufferedMode) { ih264d_create_ip_t s_create_ip{ 0 }; ih264d_create_op_t s_create_op{ 0 }; s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ih264d_create_ip_t); s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 1; // shared display buffer mode -> We give the decoder a list of buffers that it will use (?) s_create_op.s_ivd_create_op_t.u4_size = sizeof(ih264d_create_op_t); s_create_ip.s_ivd_create_ip_t.e_output_format = IV_YUV_420SP_UV; s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = NULL; WORD32 status = ih264d_api_function(m_codecCtx, &s_create_ip, &s_create_op); cemu_assert(!status); m_codecCtx = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; m_codecCtx->pv_fxns = (void*)&ih264d_api_function; m_codecCtx->u4_size = sizeof(iv_obj_t); SetDecoderCoreCount(1); m_isBufferedMode = isBufferedMode; UpdateParameters(false); m_numDecodedFrames = 0; m_hasBufferSizeInfo = false; } void Destroy() { if (!m_codecCtx) return; ih264d_delete_ip_t s_delete_ip{ 0 }; ih264d_delete_op_t s_delete_op{ 0 }; s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ih264d_delete_ip_t); s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ih264d_delete_op_t); WORD32 status = ih264d_api_function(m_codecCtx, &s_delete_ip, &s_delete_op); cemu_assert_debug(!status); m_codecCtx = nullptr; } void PushDecodedFrame(ivd_video_decode_op_t& s_dec_op) { // copy image data outside of lock since its an expensive operation CopyImageToResultBuffer((uint8*)s_dec_op.s_disp_frm_buf.pv_y_buf, (uint8*)s_dec_op.s_disp_frm_buf.pv_u_buf, (uint8*)m_decodedSliceArray[s_dec_op.u4_ts].result.imageOutput, s_dec_op); std::unique_lock _l(m_decodeQueueMtx); cemu_assert(s_dec_op.u4_ts < m_decodedSliceArray.size()); auto& result = m_decodedSliceArray[s_dec_op.u4_ts]; cemu_assert_debug(result.isUsed); cemu_assert_debug(s_dec_op.u4_output_present != 0); result.result.isDecoded = true; result.result.hasFrame = s_dec_op.u4_output_present != 0; result.result.frameWidth = s_dec_op.u4_pic_wd; result.result.frameHeight = s_dec_op.u4_pic_ht; result.result.bytesPerRow = (s_dec_op.u4_pic_wd + 0xFF) & ~0xFF; result.result.cropEnable = s_dec_op.u1_frame_cropping_flag; result.result.cropTop = s_dec_op.u1_frame_cropping_rect_top_ofst; result.result.cropBottom = s_dec_op.u1_frame_cropping_rect_bottom_ofst; result.result.cropLeft = s_dec_op.u1_frame_cropping_rect_left_ofst; result.result.cropRight = s_dec_op.u1_frame_cropping_rect_right_ofst; m_displayQueue.push_back(s_dec_op.u4_ts); _l.unlock(); coreinit::OSSignalEvent(m_displayQueueEvt); } // called from async worker thread void Decode(DecodedSlice& decodedSlice) { if (!m_hasBufferSizeInfo) { uint32 numByteConsumed = 0; if (!DetermineBufferSizes(decodedSlice.dataToDecode.m_data, decodedSlice.dataToDecode.m_length, numByteConsumed)) { cemuLog_log(LogType::Force, "H264AVC: Unable to determine picture size. Ignoring decode input"); std::unique_lock _l(m_decodeQueueMtx); decodedSlice.result.isDecoded = true; decodedSlice.result.hasFrame = false; coreinit::OSSignalEvent(m_displayQueueEvt); return; } decodedSlice.dataToDecode.m_length -= numByteConsumed; decodedSlice.dataToDecode.m_data = (uint8*)decodedSlice.dataToDecode.m_data + numByteConsumed; m_hasBufferSizeInfo = true; } ivd_video_decode_ip_t s_dec_ip{ 0 }; ivd_video_decode_op_t s_dec_op{ 0 }; s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; s_dec_ip.u4_ts = std::distance(m_decodedSliceArray.data(), &decodedSlice); cemu_assert_debug(s_dec_ip.u4_ts < m_decodedSliceArray.size()); s_dec_ip.pv_stream_buffer = (uint8*)decodedSlice.dataToDecode.m_data; s_dec_ip.u4_num_Bytes = decodedSlice.dataToDecode.m_length; s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; s_dec_ip.s_out_buffer.u4_num_bufs = 0; BenchmarkTimer bt; bt.Start(); WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); if (status != 0 && (s_dec_op.u4_error_code&0xFF) == IVD_RES_CHANGED) { // resolution change ResetDecoder(); m_hasBufferSizeInfo = false; Decode(decodedSlice); return; } else if (status != 0) { cemuLog_log(LogType::Force, "H264: Failed to decode frame (error 0x{:08x})", status); decodedSlice.result.hasFrame = false; cemu_assert_unimplemented(); return; } bt.Stop(); double decodeTime = bt.GetElapsedMilliseconds(); cemu_assert(s_dec_op.u4_frame_decoded_flag); cemu_assert_debug(s_dec_op.u4_num_bytes_consumed == decodedSlice.dataToDecode.m_length); cemu_assert_debug(m_isBufferedMode || s_dec_op.u4_output_present); // if buffered mode is disabled, then every input should output a frame (except for partial slices?) if (s_dec_op.u4_output_present) { cemu_assert(s_dec_op.e_output_format == IV_YUV_420SP_UV); if (H264_IsBotW()) { if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; } bt.Start(); PushDecodedFrame(s_dec_op); bt.Stop(); double copyTime = bt.GetElapsedMilliseconds(); // release buffer sint32 bufferId = -1; for (size_t i = 0; i < m_displayBuf.size(); i++) { if (s_dec_op.s_disp_frm_buf.pv_y_buf >= m_displayBuf[i].data() && s_dec_op.s_disp_frm_buf.pv_y_buf < (m_displayBuf[i].data() + m_displayBuf[i].size())) { bufferId = (sint32)i; break; } } cemu_assert_debug(bufferId == s_dec_op.u4_disp_buf_id); cemu_assert(bufferId >= 0); ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); s_video_rel_disp_ip.u4_disp_buf_id = bufferId; status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); cemu_assert(!status); cemuLog_log(LogType::H264, "H264Bench | DecodeTime {}ms CopyTime {}ms", decodeTime, copyTime); } else { cemuLog_log(LogType::H264, "H264Bench | DecodeTime {}ms (no frame output)", decodeTime); } if (s_dec_op.u4_frame_decoded_flag) m_numDecodedFrames++; // get VUI //ih264d_ctl_get_vui_params_ip_t s_ctl_get_vui_params_ip; //ih264d_ctl_get_vui_params_op_t s_ctl_get_vui_params_op; //s_ctl_get_vui_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; //s_ctl_get_vui_params_ip.e_sub_cmd = (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_GET_VUI_PARAMS; //s_ctl_get_vui_params_ip.u4_size = sizeof(ih264d_ctl_get_vui_params_ip_t); //s_ctl_get_vui_params_op.u4_size = sizeof(ih264d_ctl_get_vui_params_op_t); //status = ih264d_api_function(mCodecCtx, &s_ctl_get_vui_params_ip, &s_ctl_get_vui_params_op); //cemu_assert(status == 0); } void Flush() { // set flush mode ivd_ctl_flush_ip_t s_video_flush_ip{ 0 }; ivd_ctl_flush_op_t s_video_flush_op{ 0 }; s_video_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL; s_video_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH; s_video_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t); s_video_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t); WORD32 status = ih264d_api_function(m_codecCtx, &s_video_flush_ip, &s_video_flush_op); if (status != 0) cemuLog_log(LogType::Force, "H264Dec: Unexpected error during flush ({})", status); // get all frames from the decoder while (true) { ivd_video_decode_ip_t s_dec_ip{ 0 }; ivd_video_decode_op_t s_dec_op{ 0 }; s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; s_dec_ip.pv_stream_buffer = NULL; s_dec_ip.u4_num_Bytes = 0; s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; s_dec_ip.s_out_buffer.u4_num_bufs = 0; status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); if (status != 0) break; cemu_assert_debug(s_dec_op.u4_output_present != 0); // should never be false? if(s_dec_op.u4_output_present == 0) continue; if (H264_IsBotW()) { if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; } PushDecodedFrame(s_dec_op); } } void CopyImageToResultBuffer(uint8* yIn, uint8* uvIn, uint8* bufOut, ivd_video_decode_op_t& decodeInfo) { uint32 imageWidth = decodeInfo.s_disp_frm_buf.u4_y_wd; uint32 imageHeight = decodeInfo.s_disp_frm_buf.u4_y_ht; size_t inputStride = decodeInfo.s_disp_frm_buf.u4_y_strd; size_t outputStride = (imageWidth + 0xFF) & ~0xFF; // copy Y uint8* yOut = bufOut; for (uint32 row = 0; row < imageHeight; row++) { memcpy(yOut, yIn, imageWidth); yIn += inputStride; yOut += outputStride; } // copy UV uint8* uvOut = bufOut + outputStride * imageHeight; for (uint32 row = 0; row < imageHeight/2; row++) { memcpy(uvOut, uvIn, imageWidth); uvIn += inputStride; uvOut += outputStride; } } private: void SetDecoderCoreCount(uint32 coreCount) { ih264d_ctl_set_num_cores_ip_t s_set_cores_ip; ih264d_ctl_set_num_cores_op_t s_set_cores_op; s_set_cores_ip.e_cmd = IVD_CMD_VIDEO_CTL; s_set_cores_ip.e_sub_cmd = (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_SET_NUM_CORES; s_set_cores_ip.u4_num_cores = coreCount; // valid numbers are 1-4 s_set_cores_ip.u4_size = sizeof(ih264d_ctl_set_num_cores_ip_t); s_set_cores_op.u4_size = sizeof(ih264d_ctl_set_num_cores_op_t); IV_API_CALL_STATUS_T status = ih264d_api_function(m_codecCtx, (void *)&s_set_cores_ip, (void *)&s_set_cores_op); cemu_assert(status == IV_SUCCESS); } bool DetermineBufferSizes(void* data, uint32 length, uint32& numByteConsumed) { numByteConsumed = 0; UpdateParameters(true); ivd_video_decode_ip_t s_dec_ip{ 0 }; ivd_video_decode_op_t s_dec_op{ 0 }; s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; s_dec_ip.pv_stream_buffer = (uint8*)data; s_dec_ip.u4_num_Bytes = length; s_dec_ip.s_out_buffer.u4_num_bufs = 0; WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); if (status != 0) { cemuLog_log(LogType::Force, "H264: Unable to determine buffer sizes for stream"); return false; } numByteConsumed = s_dec_op.u4_num_bytes_consumed; cemu_assert(status == 0); if (s_dec_op.u4_pic_wd == 0 || s_dec_op.u4_pic_ht == 0) return false; UpdateParameters(false); ReinitBuffers(); return true; } void ReinitBuffers() { ivd_ctl_getbufinfo_ip_t s_ctl_ip{ 0 }; ivd_ctl_getbufinfo_op_t s_ctl_op{ 0 }; WORD32 outlen = 0; s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_GETBUFINFO; s_ctl_ip.u4_size = sizeof(ivd_ctl_getbufinfo_ip_t); s_ctl_op.u4_size = sizeof(ivd_ctl_getbufinfo_op_t); WORD32 status = ih264d_api_function(m_codecCtx, &s_ctl_ip, &s_ctl_op); cemu_assert(!status); //away with the old. m_displayBuf.clear(); // allocate for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) { m_displayBuf.emplace_back().resize(s_ctl_op.u4_min_out_buf_size[0] + s_ctl_op.u4_min_out_buf_size[1]); } // set ivd_set_display_frame_ip_t s_set_display_frame_ip{ 0 }; // make sure to zero-initialize this. The codec seems to check the first 3 pointers/sizes per frame, regardless of the value of u4_num_bufs ivd_set_display_frame_op_t s_set_display_frame_op{ 0 }; s_set_display_frame_ip.e_cmd = IVD_CMD_SET_DISPLAY_FRAME; s_set_display_frame_ip.u4_size = sizeof(ivd_set_display_frame_ip_t); s_set_display_frame_op.u4_size = sizeof(ivd_set_display_frame_op_t); cemu_assert_debug(s_ctl_op.u4_min_num_out_bufs == 2); cemu_assert_debug(s_ctl_op.u4_min_out_buf_size[0] != 0 && s_ctl_op.u4_min_out_buf_size[1] != 0); s_set_display_frame_ip.num_disp_bufs = s_ctl_op.u4_num_disp_bufs; for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) { s_set_display_frame_ip.s_disp_buffer[i].u4_num_bufs = 2; s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[0] = s_ctl_op.u4_min_out_buf_size[0]; s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[1] = s_ctl_op.u4_min_out_buf_size[1]; s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[0] = m_displayBuf[i].data() + 0; s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[1] = m_displayBuf[i].data() + s_ctl_op.u4_min_out_buf_size[0]; } status = ih264d_api_function(m_codecCtx, &s_set_display_frame_ip, &s_set_display_frame_op); cemu_assert(!status); // mark all as released (available) for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) { ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); s_video_rel_disp_ip.u4_disp_buf_id = i; status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); cemu_assert(!status); } } void ResetDecoder() { ivd_ctl_reset_ip_t s_ctl_ip; ivd_ctl_reset_op_t s_ctl_op; s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_RESET; s_ctl_ip.u4_size = sizeof(ivd_ctl_reset_ip_t); s_ctl_op.u4_size = sizeof(ivd_ctl_reset_op_t); WORD32 status = ih264d_api_function(m_codecCtx, (void*)&s_ctl_ip, (void*)&s_ctl_op); cemu_assert_debug(status == 0); } void UpdateParameters(bool headerDecodeOnly) { ih264d_ctl_set_config_ip_t s_h264d_ctl_ip{ 0 }; ih264d_ctl_set_config_op_t s_h264d_ctl_op{ 0 }; ivd_ctl_set_config_ip_t* ps_ctl_ip = &s_h264d_ctl_ip.s_ivd_ctl_set_config_ip_t; ivd_ctl_set_config_op_t* ps_ctl_op = &s_h264d_ctl_op.s_ivd_ctl_set_config_op_t; ps_ctl_ip->u4_disp_wd = 0; ps_ctl_ip->e_frm_skip_mode = IVD_SKIP_NONE; ps_ctl_ip->e_frm_out_mode = m_isBufferedMode ? IVD_DISPLAY_FRAME_OUT : IVD_DECODE_FRAME_OUT; ps_ctl_ip->e_vid_dec_mode = headerDecodeOnly ? IVD_DECODE_HEADER : IVD_DECODE_FRAME; ps_ctl_ip->e_cmd = IVD_CMD_VIDEO_CTL; ps_ctl_ip->e_sub_cmd = IVD_CMD_CTL_SETPARAMS; ps_ctl_ip->u4_size = sizeof(ih264d_ctl_set_config_ip_t); ps_ctl_op->u4_size = sizeof(ih264d_ctl_set_config_op_t); WORD32 status = ih264d_api_function(m_codecCtx, &s_h264d_ctl_ip, &s_h264d_ctl_op); cemu_assert(status == 0); } private: void DecoderThread() { while(!m_threadShouldExit) { m_decodeSem.decrementWithWait(); std::unique_lock _l(m_decodeQueueMtx); if (m_decodeQueue.empty()) continue; uint32 decodeIndex = m_decodeQueue.front(); m_decodeQueue.erase(m_decodeQueue.begin()); _l.unlock(); if(decodeIndex == CMD_FLUSH) { Flush(); _l.lock(); cemu_assert_debug(m_decodeQueue.empty()); // after flushing the queue should be empty since the sender is waiting for the flush to complete _l.unlock(); coreinit::OSSignalEvent(m_flushEvt); } else { auto& decodedSlice = m_decodedSliceArray[decodeIndex]; Decode(decodedSlice); } } } iv_obj_t* m_codecCtx{nullptr}; bool m_hasBufferSizeInfo{ false }; bool m_isBufferedMode{ false }; uint32 m_numDecodedFrames{0}; std::vector<std::vector<uint8>> m_displayBuf; std::thread m_decoderThread; std::atomic_bool m_threadShouldExit{false}; }; H264DecoderBackend* CreateAVCDecoder() { return new H264AVCDecoder(); } }; ================================================ FILE: src/Cafe/OS/libs/h264_avc/H264DecInternal.h ================================================ #pragma once #include "util/helpers/Semaphore.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h" #include "Cafe/OS/libs/h264_avc/parser/H264Parser.h" namespace H264 { class H264DecoderBackend { protected: struct DataToDecode { uint8* m_data; uint32 m_length; std::vector<uint8> m_buffer; }; static constexpr uint32 CMD_FLUSH = 0xFFFFFFFF; public: struct DecodeResult { bool isDecoded{false}; bool hasFrame{false}; // set to true if a full frame was successfully decoded double timestamp{}; void* imageOutput{nullptr}; sint32 frameWidth{0}; sint32 frameHeight{0}; uint32 bytesPerRow{0}; bool cropEnable{false}; sint32 cropTop{0}; sint32 cropBottom{0}; sint32 cropLeft{0}; sint32 cropRight{0}; }; struct DecodedSlice { bool isUsed{false}; DecodeResult result; DataToDecode dataToDecode; }; H264DecoderBackend() { m_displayQueueEvt = (coreinit::OSEvent*)coreinit::OSAllocFromSystem(sizeof(coreinit::OSEvent), 4); coreinit::OSInitEvent(m_displayQueueEvt, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); m_flushEvt = (coreinit::OSEvent*)coreinit::OSAllocFromSystem(sizeof(coreinit::OSEvent), 4); coreinit::OSInitEvent(m_flushEvt, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); }; virtual ~H264DecoderBackend() { coreinit::OSFreeToSystem(m_displayQueueEvt); coreinit::OSFreeToSystem(m_flushEvt); }; virtual void Init(bool isBufferedMode) = 0; virtual void Destroy() = 0; void QueueForDecode(uint8* data, uint32 length, double timestamp, void* imagePtr) { std::unique_lock _l(m_decodeQueueMtx); DecodedSlice& ds = GetFreeDecodedSliceEntry(); ds.dataToDecode.m_buffer.assign(data, data + length); ds.dataToDecode.m_data = ds.dataToDecode.m_buffer.data(); ds.dataToDecode.m_length = length; ds.result.isDecoded = false; ds.result.imageOutput = imagePtr; ds.result.timestamp = timestamp; m_decodeQueue.push_back(std::distance(m_decodedSliceArray.data(), &ds)); m_decodeSem.increment(); } void QueueFlush() { std::unique_lock _l(m_decodeQueueMtx); m_decodeQueue.push_back(CMD_FLUSH); m_decodeSem.increment(); } bool GetFrameOutputIfReady(DecodeResult& result) { std::unique_lock _l(m_decodeQueueMtx); if(m_displayQueue.empty()) return false; uint32 sliceIndex = m_displayQueue.front(); DecodedSlice& ds = m_decodedSliceArray[sliceIndex]; cemu_assert_debug(ds.result.isDecoded); std::swap(result, ds.result); ds.isUsed = false; m_displayQueue.erase(m_displayQueue.begin()); return true; } coreinit::OSEvent& GetFrameOutputEvent() { return *m_displayQueueEvt; } coreinit::OSEvent& GetFlushEvent() { return *m_flushEvt; } protected: DecodedSlice& GetFreeDecodedSliceEntry() { for (auto& slice : m_decodedSliceArray) { if (!slice.isUsed) { slice.isUsed = true; return slice; } } cemu_assert_suspicious(); return m_decodedSliceArray[0]; } std::mutex m_decodeQueueMtx; std::vector<uint32> m_decodeQueue; // indices into m_decodedSliceArray, in order of decode input CounterSemaphore m_decodeSem; std::vector<uint32> m_displayQueue; // indices into m_decodedSliceArray, in order of frame display output coreinit::OSEvent* m_displayQueueEvt; // signalled when a new frame is ready for display coreinit::OSEvent* m_flushEvt; // signalled after flush operation finished and all queued slices are decoded // frame output queue std::mutex m_frameOutputMtx; std::array<DecodedSlice, 32> m_decodedSliceArray; }; } ================================================ FILE: src/Cafe/OS/libs/h264_avc/h264dec.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace H264 { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp ================================================ #include "Cafe/OS/libs/h264_avc/parser/H264Parser.h" void parse_hrd_parameters(h264ParserState_t* h264ParserState, RBSPInputBitstream& nalStream) { uint32 cpb_cnt_minus1 = nalStream.readUV_E(); uint8 bit_rate_scale = nalStream.readBits<4>(); uint8 cpb_size_scale = nalStream.readBits<4>(); for (uint8 schedSelIdx = 0; schedSelIdx <= cpb_cnt_minus1; schedSelIdx++) { uint32 bit_rate_value_minus1 = nalStream.readUV_E(); uint32 cpb_size_value_minus1 = nalStream.readUV_E(); uint8 cbr_flag = nalStream.readBit(); } uint8 initial_cpb_removal_delay_length_minus1 = nalStream.readBits<5>(); uint8 cpb_removal_delay_length_minus1 = nalStream.readBits<5>(); uint8 dpb_output_delay_length_minus1 = nalStream.readBits<5>(); uint8 time_offset_length = nalStream.readBits<5>(); } void parseNAL_scaling_list4x4(RBSPInputBitstream& rbspStream, h264_scaling_matrix4x4_t& scalingMatrix4x4) { if (rbspStream.readBit() == 0) { scalingMatrix4x4.isPresent = 0; return; } scalingMatrix4x4.isPresent = 1; cemu_assert_debug(false); // needs testing sint32 lastScale = 8; sint32 nextScale = 8; for (sint32 j = 0; j < 4 * 4; j++) { if (nextScale != 0) { sint32 delta_scale = rbspStream.readSV_E(); nextScale = (lastScale + delta_scale + 256) % 256; scalingMatrix4x4.UseDefaultScalingMatrix = (j == 0 && nextScale == 0); } scalingMatrix4x4.list[j] = (nextScale == 0) ? lastScale : nextScale; lastScale = scalingMatrix4x4.list[j]; } } void parseNAL_scaling_list8x8(RBSPInputBitstream& rbspStream, h264_scaling_matrix8x8_t& scalingMatrix8x8) { if (rbspStream.readBit() == 0) { scalingMatrix8x8.isPresent = 0; return; } scalingMatrix8x8.isPresent = 1; cemu_assert_debug(false); // needs testing sint32 lastScale = 8; sint32 nextScale = 8; for (sint32 j = 0; j < 8 * 8; j++) { if (nextScale != 0) { sint32 delta_scale = rbspStream.readSV_E(); nextScale = (lastScale + delta_scale + 256) % 256; scalingMatrix8x8.UseDefaultScalingMatrix = (j == 0 && nextScale == 0); } scalingMatrix8x8.list[j] = (nextScale == 0) ? lastScale : nextScale; lastScale = scalingMatrix8x8.list[j]; } } void parseNAL_pps_scaling_lists(h264ParserState_t* h264ParserState, RBSPInputBitstream& nalStream) { parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[0]); parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[1]); parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[2]); parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[3]); parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[4]); parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[5]); if (h264ParserState->pps.transform_8x8_mode_flag) { parseNAL_scaling_list8x8(nalStream, h264ParserState->pps.ScalingList8x8[0]); parseNAL_scaling_list8x8(nalStream, h264ParserState->pps.ScalingList8x8[1]); if (h264ParserState->sps.chroma_format_idc == 3) cemu_assert(false); // todo - more scaling lists to parse } } void parseNAL_sps_scaling_lists(h264ParserState_t* h264ParserState, RBSPInputBitstream& nalStream) { parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[0]); parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[1]); parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[2]); parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[3]); parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[4]); parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[5]); parseNAL_scaling_list8x8(nalStream, h264ParserState->sps.ScalingList8x8[0]); parseNAL_scaling_list8x8(nalStream, h264ParserState->sps.ScalingList8x8[1]); if (h264ParserState->sps.chroma_format_idc == 3) cemu_assert(false); // todo - more scaling lists to parse } bool parseNAL_seq_parameter_set_rbsp(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, RBSPInputBitstream& nalStream) { memset(&h264ParserState->sps, 0, sizeof(h264State_seq_parameter_set_t)); h264ParserState->sps.profile_idc = nalStream.readU8(); // 0x64 = high profile h264ParserState->sps.constraint = nalStream.readU8(); // 6 flags + 2 reserved bits h264ParserState->sps.level_idc = nalStream.readU8(); // 0x29 = level 4.1 // some default values in case flags are not set h264ParserState->sps.separate_colour_plane_flag = 0; h264ParserState->sps.chroma_format_idc = 1; // Spec 7.4.2.1.1 h264ParserState->sps.qpprime_y_zero_transform_bypass_flag = 0; h264ParserState->sps.seq_scaling_matrix_present_flag = 0; //h264ParserState->sps.mb_adaptive_frame_field_flag = 0; uint32 seq_parameter_set_id = nalStream.readUV_E(); if (h264ParserState->sps.profile_idc == 100 || h264ParserState->sps.profile_idc == 110 || h264ParserState->sps.profile_idc == 122 || h264ParserState->sps.profile_idc == 244 || h264ParserState->sps.profile_idc == 44 || h264ParserState->sps.profile_idc == 83 || h264ParserState->sps.profile_idc == 86 || h264ParserState->sps.profile_idc == 118 || h264ParserState->sps.profile_idc == 128 || h264ParserState->sps.profile_idc == 138 || h264ParserState->sps.profile_idc == 139 || h264ParserState->sps.profile_idc == 134 || h264ParserState->sps.profile_idc == 135) { h264ParserState->sps.chroma_format_idc = nalStream.readUV_E(); if (h264ParserState->sps.chroma_format_idc == 3) { h264ParserState->sps.separate_colour_plane_flag = nalStream.readBit(); } h264ParserState->sps.bit_depth_luma_minus8 = nalStream.readUV_E(); h264ParserState->sps.bit_depth_chroma_minus8 = nalStream.readUV_E(); h264ParserState->sps.qpprime_y_zero_transform_bypass_flag = nalStream.readBit(); h264ParserState->sps.seq_scaling_matrix_present_flag = nalStream.readBit(); if (h264ParserState->sps.seq_scaling_matrix_present_flag) { parseNAL_sps_scaling_lists(h264ParserState, nalStream); } } h264ParserState->sps.log2_max_frame_num_minus4 = nalStream.readUV_E(); h264ParserState->sps.pic_order_cnt_type = nalStream.readUV_E(); if (h264ParserState->sps.pic_order_cnt_type == 0) { h264ParserState->sps.log2_max_pic_order_cnt_lsb_minus4 = nalStream.readUV_E(); } else if (h264ParserState->sps.pic_order_cnt_type == 2) { // nothing to parse } else { // todo - parse fields cemu_assert_debug(false); } h264ParserState->sps.num_ref_frames = nalStream.readUV_E(); h264ParserState->sps.gaps_in_frame_num_value_allowed_flag = nalStream.readBit(); h264ParserState->sps.pic_width_in_mbs_minus1 = nalStream.readUV_E(); h264ParserState->sps.pic_height_in_map_units_minus1 = nalStream.readUV_E(); h264ParserState->sps.frame_mbs_only_flag = nalStream.readBit(); if (h264ParserState->sps.frame_mbs_only_flag == 0) { h264ParserState->sps.mb_adaptive_frame_field_flag = nalStream.readBit(); cemu_assert_debug(false); } else h264ParserState->sps.mb_adaptive_frame_field_flag = 0; // default is zero? h264ParserState->sps.direct_8x8_inference_flag = nalStream.readBit(); if (h264ParserState->sps.frame_mbs_only_flag == 0 && h264ParserState->sps.direct_8x8_inference_flag != 1) { cemu_assert_debug(false); // not allowed } h264ParserState->sps.frame_cropping_flag = nalStream.readBit(); if (h264ParserState->sps.frame_cropping_flag) { h264ParserState->sps.frame_crop_left_offset = nalStream.readUV_E(); h264ParserState->sps.frame_crop_right_offset = nalStream.readUV_E(); h264ParserState->sps.frame_crop_top_offset = nalStream.readUV_E(); h264ParserState->sps.frame_crop_bottom_offset = nalStream.readUV_E(); } uint8 vui_parameters_present_flag = nalStream.readBit(); if (vui_parameters_present_flag) { // vui_parameters uint8 aspect_ratio_info_present_flag = nalStream.readBit(); if (aspect_ratio_info_present_flag) { uint32 aspect_ratio_idc = nalStream.readBits<8>(); if (aspect_ratio_idc == 255) // Extended_SAR { uint16 sar_width = nalStream.readBits<16>(); uint16 sar_height = nalStream.readBits<16>(); } } uint8 overscan_info_present_flag = nalStream.readBit(); if (overscan_info_present_flag) { cemu_assert_debug(false); } uint8 video_signal_type_present_flag = nalStream.readBit(); if (video_signal_type_present_flag) { uint8 video_format = nalStream.readBits<3>(); uint8 video_full_range_flag = nalStream.readBit(); uint8 colour_description_present_flag = nalStream.readBit(); if (colour_description_present_flag) { uint8 colour_primaries = nalStream.readBits<8>(); uint8 transfer_characteristics = nalStream.readBits<8>(); uint8 matrix_coefficients = nalStream.readBits<8>(); } } uint8 chroma_loc_info_present_flag = nalStream.readBit(); if (chroma_loc_info_present_flag) { uint32 chroma_sample_loc_type_top_field = nalStream.readUV_E(); uint32 chroma_sample_loc_type_bottom_field = nalStream.readUV_E(); } uint8 timing_info_present_flag = nalStream.readBit(); if (timing_info_present_flag) { uint32 num_units_in_tick = nalStream.readBits<32>(); uint32 time_scale = nalStream.readBits<32>(); uint8 fixed_frame_rate_flag = nalStream.readBits<1>(); } uint8 nal_hrd_parameters_present_flag = nalStream.readBit(); if (nal_hrd_parameters_present_flag) { parse_hrd_parameters(h264ParserState, nalStream); } uint8 vcl_hrd_parameters_present_flag = nalStream.readBit(); if (vcl_hrd_parameters_present_flag) { parse_hrd_parameters(h264ParserState, nalStream); } if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) { uint8 low_delay_hrd_flag = nalStream.readBit(); } uint8 pic_struct_present_flag = nalStream.readBit(); uint8 bitstream_restriction_flag = nalStream.readBit(); if (bitstream_restriction_flag) { uint8 motion_vectors_over_pic_boundaries_flag = nalStream.readBit(); uint32 max_bytes_per_pic_denom = nalStream.readUV_E(); uint32 max_bits_per_mb_denom = nalStream.readUV_E(); uint32 log2_max_mv_length_horizontal = nalStream.readUV_E(); uint32 log2_max_mv_length_vertical = nalStream.readUV_E(); uint32 max_num_reorder_frames = nalStream.readUV_E(); uint32 max_dec_frame_buffering = nalStream.readUV_E(); } } // trailing bits bool nalValid = true; if (nalStream.readTrailingRBSPBits() == false) nalValid = false; if (nalValid) { if(output) output->hasSPS = true; h264ParserState->hasSPS = true; } return true; } bool parseNAL_pic_parameter_set_rbsp(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, RBSPInputBitstream& nalStream) { memset(&h264ParserState->pps, 0, sizeof(h264State_pic_parameter_set_t)); h264ParserState->pps.pic_parameter_set_id = nalStream.readUV_E(); h264ParserState->pps.seq_parameter_set_id = nalStream.readUV_E(); h264ParserState->pps.entropy_coding_mode_flag = nalStream.readBit(); h264ParserState->pps.bottom_field_pic_order_in_frame_present_flag = nalStream.readBit(); h264ParserState->pps.num_slice_groups_minus1 = nalStream.readUV_E(); if (h264ParserState->pps.num_slice_groups_minus1 > 0) { cemu_assert_debug(false); return false; } h264ParserState->pps.num_ref_idx_l0_default_active_minus1 = nalStream.readUV_E(); h264ParserState->pps.num_ref_idx_l1_default_active_minus1 = nalStream.readUV_E(); h264ParserState->pps.weighted_pred_flag = nalStream.readBit(); h264ParserState->pps.weighted_bipred_idc = nalStream.readBits<2>(); h264ParserState->pps.pic_init_qp_minus26 = nalStream.readSV_E(); h264ParserState->pps.pic_init_qs_minus26 = nalStream.readSV_E(); h264ParserState->pps.chroma_qp_index_offset = nalStream.readSV_E(); h264ParserState->pps.deblocking_filter_control_present_flag = nalStream.readBit(); h264ParserState->pps.constrained_intra_pred_flag = nalStream.readBit(); h264ParserState->pps.redundant_pic_cnt_present_flag = nalStream.readBit(); if (nalStream.more_rbsp_data()) { h264ParserState->pps.transform_8x8_mode_flag = nalStream.readBit(); h264ParserState->pps.pic_scaling_matrix_present_flag = nalStream.readBit(); if (h264ParserState->pps.pic_scaling_matrix_present_flag) { parseNAL_pps_scaling_lists(h264ParserState, nalStream); } h264ParserState->pps.second_chroma_qp_index_offset = nalStream.readSV_E(); } // trailing bits bool nalValid = true; if (nalStream.readTrailingRBSPBits() == false) nalValid = false; if (nalValid) { if(output) output->hasPPS = true; h264ParserState->hasPPS = true; } return true; } bool h264Parser_ParseSPS(uint8* data, uint32 length, h264State_seq_parameter_set_t& sps) { h264ParserState_t parserState; RBSPInputBitstream nalStream(data, length); bool r = parseNAL_seq_parameter_set_rbsp(&parserState, nullptr, nalStream); if(!r || !parserState.hasSPS) return false; sps = parserState.sps; return true; } void parseNAL_ref_pic_list_modification(const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, RBSPInputBitstream& nalStream, nal_slice_header_t* sliceHeader) { if (!sliceHeader->slice_type.isSliceTypeI() && !sliceHeader->slice_type.isSliceTypeSI()) { uint8 ref_pic_list_modification_flag_l0 = nalStream.readBit(); if (ref_pic_list_modification_flag_l0) { sliceHeader->pic_list_modification0Count = 0; uint32 modType; while(true) { if (sliceHeader->pic_list_modification0Count >= 32) { cemu_assert_debug(false); return; } modType = nalStream.readUV_E(); sliceHeader->pic_list_modification0Array[sliceHeader->pic_list_modification0Count].type = modType; if (modType == 0 || modType == 1) { sliceHeader->pic_list_modification0Array[sliceHeader->pic_list_modification0Count].abs_diff_pic_num_minus1 = nalStream.readUV_E(); } else if (modType == 2) { sliceHeader->pic_list_modification0Array[sliceHeader->pic_list_modification0Count].long_term_pic_num = nalStream.readUV_E(); } else if (modType == 3) { // end of list break; } else { cemu_assert_debug(false); // invalid type return; } sliceHeader->pic_list_modification0Count++; } } } if (sliceHeader->slice_type.isSliceTypeB()) { uint8 ref_pic_list_modification_flag_l1 = nalStream.readBit(); if (ref_pic_list_modification_flag_l1) { cemu_assert_debug(false); // testing required while (true) { if (sliceHeader->pic_list_modification1Count >= 32) { cemu_assert_debug(false); return; } uint32 modType = nalStream.readUV_E(); // aka modification_of_pic_nums_idc sliceHeader->pic_list_modification1Array[sliceHeader->pic_list_modification1Count].type = modType; if (modType == 0 || modType == 1) { sliceHeader->pic_list_modification1Array[sliceHeader->pic_list_modification1Count].abs_diff_pic_num_minus1 = nalStream.readUV_E(); } else if (modType == 2) { sliceHeader->pic_list_modification1Array[sliceHeader->pic_list_modification1Count].long_term_pic_num = nalStream.readUV_E(); } else if(modType == 3) { break; } else { cemu_assert_debug(false); // invalid mode break; } sliceHeader->pic_list_modification0Count++; } if (sliceHeader->pic_list_modification1Count > 0) { cemuLog_logDebug(LogType::Force, "sliceHeader->pic_list_modification1Count non-zero is not supported"); cemu_assert_unimplemented(); } } } } void parseNAL_dec_ref_pic_marking(const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, RBSPInputBitstream& nalStream, nal_slice_header_t* sliceHeader) { sliceHeader->memory_management_control_operation_num = 0; if (sliceHeader->IdrPicFlag) { uint8 no_output_of_prior_pics_flag = nalStream.readBit(); if (no_output_of_prior_pics_flag) cemu_assert_debug(false); uint8 long_term_reference_flag = nalStream.readBit(); if (long_term_reference_flag) cemu_assert_debug(false); sliceHeader->adaptive_ref_pic_marking_mode_flag = 1; } else { sliceHeader->adaptive_ref_pic_marking_mode_flag = nalStream.readBit(); if (sliceHeader->adaptive_ref_pic_marking_mode_flag) { while (true) { uint32 memory_management_control_operation = nalStream.readUV_E(); if (memory_management_control_operation == nal_slice_header_t::MEMOP_END) break; if (sliceHeader->memory_management_control_operation_num >= 16) { cemu_assert_debug(false); return; } sliceHeader->memory_management_control_operation[sliceHeader->memory_management_control_operation_num].op = memory_management_control_operation; if (memory_management_control_operation == nal_slice_header_t::MEMOP_REMOVE_REF_FROM_SHORT_TERM || memory_management_control_operation == nal_slice_header_t::MEMOP_MAKE_LONG_TERM_REF) { sliceHeader->memory_management_control_operation[sliceHeader->memory_management_control_operation_num].difference_of_pic_nums_minus1 = nalStream.readUV_E(); } else if (memory_management_control_operation == nal_slice_header_t::MEMOP_REMOVE_REF_FROM_LONG_TERM) { sliceHeader->memory_management_control_operation[sliceHeader->memory_management_control_operation_num].long_term_pic_num = nalStream.readUV_E(); } if (memory_management_control_operation == nal_slice_header_t::MEMOP_MAKE_LONG_TERM_REF || memory_management_control_operation == nal_slice_header_t::MEMOP_MAKE_CURRENT_LONG_TERM_REF) { sliceHeader->memory_management_control_operation[sliceHeader->memory_management_control_operation_num].long_term_frame_idx = nalStream.readUV_E(); } if (memory_management_control_operation == nal_slice_header_t::MEMOP_MAX_LONG_TERM_INDEX) { sliceHeader->memory_management_control_operation[sliceHeader->memory_management_control_operation_num].max_long_term_frame_idx_plus1 = nalStream.readUV_E(); } sliceHeader->memory_management_control_operation_num++; } } } } void parseNAL_pred_weight_table(const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, RBSPInputBitstream& nalStream, nal_slice_header_t* sliceHeader) { uint8 luma_log2_weight_denom = nalStream.readUV_E(); uint32 ChromaArrayType; if (sps.separate_colour_plane_flag == 0) ChromaArrayType = sps.chroma_format_idc; else ChromaArrayType = 0; if (ChromaArrayType != 0) { uint32 chroma_log2_weight_denom = nalStream.readUV_E(); } for (uint32 i = 0; i <= sliceHeader->num_ref_idx_l0_active_minus1; i++) { uint8 luma_weight_l0_flag = nalStream.readBit(); if (luma_weight_l0_flag) { uint32 luma_weight_l0 = nalStream.readSV_E(); uint32 luma_offset_l0 = nalStream.readSV_E(); } if (ChromaArrayType != 0) { uint8 chroma_weight_l0_flag = nalStream.readBit(); if (chroma_weight_l0_flag) { for (sint32 j = 0; j < 2; j++) { uint32 chroma_weight_l0 = nalStream.readSV_E(); uint32 chroma_offset_l0 = nalStream.readSV_E(); } } } } if (sliceHeader->slice_type.isSliceTypeB()) { for (uint32 i = 0; i <= sliceHeader->num_ref_idx_l1_active_minus1; i++) { uint8 luma_weight_l1_flag = nalStream.readBit(); if (luma_weight_l1_flag) { cemu_assert_debug(false); //luma_weight_l1[i] //luma_offset_l1[i] } if (ChromaArrayType != 0) { cemu_assert_debug(false); //chroma_weight_l1_flag //if (chroma_weight_l1_flag) //{ // for (j = 0; j < 2; j++) // { // chroma_weight_l1[i][j] // chroma_offset_l1[i][j] // } //} } } } } void parseNAL_slice_header(const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, RBSPInputBitstream& nalStream, uint8 nal_unit_type, uint8 nal_ref_idc, nal_slice_header_t* sliceHeader) { bool IdrPicFlag = nal_unit_type == 5; memset(sliceHeader, 0, sizeof(nal_slice_header_t)); sliceHeader->nal_ref_idc = nal_ref_idc; sliceHeader->nal_unit_type = nal_unit_type; sliceHeader->first_mb_in_slice = nalStream.readUV_E(); // address of first macroblock in slice sliceHeader->slice_type = { nalStream.readUV_E() }; sliceHeader->pic_parameter_set_id = nalStream.readUV_E(); if (sps.separate_colour_plane_flag == 1) { cemu_assert_debug(false); return; } sliceHeader->frame_num = nalStream.readBits(sps.log2_max_frame_num_minus4 + 4); if (sps.frame_mbs_only_flag == 0) { sliceHeader->field_pic_flag = nalStream.readBit(); if (sliceHeader->field_pic_flag) { sliceHeader->bottom_field_flag = nalStream.readBit(); } } sliceHeader->IdrPicFlag = IdrPicFlag?1:0; if (IdrPicFlag) { sliceHeader->idr_pic_id = nalStream.readUV_E(); } if (sps.pic_order_cnt_type == 0) { sliceHeader->pic_order_cnt_lsb = nalStream.readBits(sps.log2_max_pic_order_cnt_lsb_minus4 + 4); if (pps.bottom_field_pic_order_in_frame_present_flag && sliceHeader->field_pic_flag == 0) sliceHeader->delta_pic_order_cnt_bottom = nalStream.readSV_E(); } else if (sps.pic_order_cnt_type == 1) { cemu_assert(false); } if (pps.redundant_pic_cnt_present_flag) { sliceHeader->redundant_pic_cnt = nalStream.readUV_E(); } if (sliceHeader->slice_type.isSliceTypeB()) { sliceHeader->direct_spatial_mv_pred_flag = nalStream.readBit(); } sliceHeader->num_ref_idx_l0_active_minus1 = pps.num_ref_idx_l0_default_active_minus1; sliceHeader->num_ref_idx_l1_active_minus1 = pps.num_ref_idx_l1_default_active_minus1; if (sliceHeader->slice_type.isSliceTypeP() || sliceHeader->slice_type.isSliceTypeSP() || sliceHeader->slice_type.isSliceTypeB()) { sliceHeader->num_ref_idx_active_override_flag = nalStream.readBit(); if (sliceHeader->num_ref_idx_active_override_flag) { sliceHeader->num_ref_idx_l0_active_minus1 = nalStream.readUV_E(); if (sliceHeader->slice_type.isSliceTypeB()) { sliceHeader->num_ref_idx_l1_active_minus1 = nalStream.readUV_E(); } } } // todo - ref_pic_list_mvc_modification etc if (nal_unit_type == 20 || nal_unit_type == 21) { cemu_assert_debug(false); } else { parseNAL_ref_pic_list_modification(sps, pps, nalStream, sliceHeader); } if ((pps.weighted_pred_flag && (sliceHeader->slice_type.isSliceTypeP() || sliceHeader->slice_type.isSliceTypeSP())) || (pps.weighted_bipred_idc == 1 && sliceHeader->slice_type.isSliceTypeB())) { parseNAL_pred_weight_table(sps, pps, nalStream, sliceHeader); } if (sliceHeader->nal_ref_idc != 0) { parseNAL_dec_ref_pic_marking(sps, pps, nalStream, sliceHeader); } if (pps.entropy_coding_mode_flag && !sliceHeader->slice_type.isSliceTypeI() && !sliceHeader->slice_type.isSliceTypeSI()) { uint32 cabac_init_idc = nalStream.readUV_E(); cemu_assert_debug(cabac_init_idc <= 2); // invalid value } sint32 slice_qp_delta = nalStream.readSV_E(); if (sliceHeader->slice_type.isSliceTypeSP() || sliceHeader->slice_type.isSliceTypeSI()) { if (sliceHeader->slice_type.isSliceTypeSP()) { uint8 sp_for_switch_flag = nalStream.readBit(); } sint32 slice_qs_delta = nalStream.readSV_E(); } if (pps.deblocking_filter_control_present_flag) { uint32 disable_deblocking_filter_idc = nalStream.readUV_E(); if (disable_deblocking_filter_idc != 1) { sint32 slice_alpha_c0_offset_div2 = nalStream.readSV_E(); sint32 slice_beta_offset_div2 = nalStream.readSV_E(); } } if (pps.num_slice_groups_minus1 > 0 && pps.slice_group_map_type >= 3 && pps.slice_group_map_type <= 5) { cemu_assert_debug(false); // todo } } void _calculateFrameOrder(h264ParserState_t* h264ParserState, const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, nal_slice_header_t* sliceHeader) { if (sps.pic_order_cnt_type == 0) { // calculate TopFieldOrderCnt uint32 prevPicOrderCntMsb; uint32 prevPicOrderCntLsb; if (sliceHeader->IdrPicFlag) { sliceHeader->calculated.TopFieldOrderCnt = 0; prevPicOrderCntMsb = 0; prevPicOrderCntLsb = 0; } else { uint32 prevRefPic_PicOrderCntMsb = h264ParserState->picture_order.prevPicOrderCntMsb; uint32 prevRefPic_pic_order_cnt_lsb = h264ParserState->picture_order.prevPicOrderCntLsb; // todo prevPicOrderCntMsb = prevRefPic_PicOrderCntMsb; prevPicOrderCntLsb = prevRefPic_pic_order_cnt_lsb; } uint32 MaxPicOrderCntLsb = 1 << (sps.log2_max_pic_order_cnt_lsb_minus4 + 4); // todo - verify uint32 PicOrderCntMsb; if ((sliceHeader->pic_order_cnt_lsb < prevPicOrderCntLsb) && (((sint32)prevPicOrderCntLsb - (sint32)sliceHeader->pic_order_cnt_lsb) >= (sint32)(MaxPicOrderCntLsb / 2))) PicOrderCntMsb = prevPicOrderCntMsb + MaxPicOrderCntLsb; else if ((sliceHeader->pic_order_cnt_lsb > prevPicOrderCntLsb) && (((sint32)sliceHeader->pic_order_cnt_lsb - (sint32)prevPicOrderCntLsb) > (sint32)(MaxPicOrderCntLsb / 2))) PicOrderCntMsb = prevPicOrderCntMsb - MaxPicOrderCntLsb; else PicOrderCntMsb = prevPicOrderCntMsb; uint32 TopFieldOrderCnt = PicOrderCntMsb + sliceHeader->pic_order_cnt_lsb; if (sliceHeader->IdrPicFlag != 0 && TopFieldOrderCnt != 0) cemu_assert(false); sliceHeader->calculated.TopFieldOrderCnt = TopFieldOrderCnt; h264ParserState->picture_order.prevPicOrderCntMsb = PicOrderCntMsb; h264ParserState->picture_order.prevPicOrderCntLsb = sliceHeader->pic_order_cnt_lsb; } else if (sps.pic_order_cnt_type == 2) { // display order matches decode order uint32 prevFrameNum = h264ParserState->picture_order.prevFrameNum; uint32 FrameNumOffset; if (sliceHeader->IdrPicFlag) { FrameNumOffset = 0; } else { // todo - check for memory_management_control_operation 5 // prevFrameNumOffset is set equal to the value of FrameNumOffset of the previous picture in decoding order. uint32 prevFrameNumOffset = h264ParserState->picture_order.prevFrameNumOffset; if (prevFrameNum > sliceHeader->frame_num) FrameNumOffset = prevFrameNumOffset + sps.getMaxFrameNum(); else FrameNumOffset = prevFrameNumOffset; } uint32 tempPicOrderCnt; if (sliceHeader->IdrPicFlag == 1) tempPicOrderCnt = 0; else if (sliceHeader->nal_ref_idc == 0) tempPicOrderCnt = 2 * (FrameNumOffset + sliceHeader->frame_num) - 1; else tempPicOrderCnt = 2 * (FrameNumOffset + sliceHeader->frame_num); uint32 TopFieldOrderCnt = 0; uint32 BottomFieldOrderCnt = 0; if (sliceHeader->field_pic_flag == 0) { TopFieldOrderCnt = tempPicOrderCnt; BottomFieldOrderCnt = tempPicOrderCnt; } else if (sliceHeader->bottom_field_flag != 0) { cemu_assert_debug(false); // fields aren't supported BottomFieldOrderCnt = tempPicOrderCnt; } else { cemu_assert_debug(false); // fields aren't supported TopFieldOrderCnt = tempPicOrderCnt; } sliceHeader->calculated.TopFieldOrderCnt = TopFieldOrderCnt; h264ParserState->picture_order.prevFrameNum = sliceHeader->frame_num; h264ParserState->picture_order.prevFrameNumOffset = FrameNumOffset; } else { cemu_assert_debug(false); } } void parseNAL_slice_layer_without_partitioning_rbsp(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, sint32 streamSubOffset, sint32 streamSubLength, RBSPInputBitstream& nalStream, uint8 nal_ref_idc, uint8 nal_unit_type) { nal_slice_t slice = {}; cemu_assert_debug(h264ParserState->hasPPS && h264ParserState->hasSPS); parseNAL_slice_header(h264ParserState->sps, h264ParserState->pps, nalStream, nal_unit_type, nal_ref_idc, &slice.slice_header); _calculateFrameOrder(h264ParserState, h264ParserState->sps, h264ParserState->pps, &slice.slice_header); if (output->sliceCount >= output->sliceInfo.size()) { cemu_assert_debug(false); // internal slice buffer full return; } output->sliceInfo[output->sliceCount].header = slice.slice_header; output->sliceInfo[output->sliceCount].streamSubOffset = streamSubOffset; output->sliceInfo[output->sliceCount].streamSubSize = streamSubLength; output->sliceCount++; } void h264Parse(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, uint8* data, uint32 length, bool parseSlices) { memset(output, 0, sizeof(h264ParserOutput_t)); NALInputBitstream nalStream(data, length); if (nalStream.hasError()) { cemu_assert_debug(false); return; } // parse NAL data while (true) { if (nalStream.isEndOfStream()) break; RBSPInputBitstream rbspStream; if (nalStream.getNextRBSP(rbspStream) == false) break; sint32 streamSubOffset = (sint32)(rbspStream.getBasePtr() - data); sint32 streamSubLength = rbspStream.getBaseLength(); // parse NAL header uint8 nalHeaderByte = rbspStream.readU8(); if ((nalHeaderByte & 0x80) != 0) { // MSB must be zero cemu_assert_debug(false); continue; } uint8 nal_ref_idc = (nalHeaderByte >> 5) & 0x3; uint8 nal_unit_type = (nalHeaderByte >> 0) & 0x1f; // nal_ref_idc -> If not 0, reference picture. Contains sequence set ?? // nal_unit_type -> RBSP type // nal_unit_type: // 0 - unspecified // 1 - Coded slice of a non-IDR picture slice_layer_without_partitioning_rbsp() // 2 - Coded slice data partition A slice_data_partition_a_layer_rbsp() // 3 - Coded slice data partition B slice_data_partition_b_layer_rbsp() // 4 - Coded slice data partition C slice_data_partition_c_layer_rbsp() // 5 - Coded slice of an IDR picture slice_layer_without_partitioning_rbsp() // 6 - Supplemental enhancement information (SEI) sei_rbsp() // 7 - Sequence parameter set seq_parameter_set_rbsp() // 8 - Picture parameter set pic_parameter_set_rbsp() // 9 - Access unit delimiter access_unit_delimiter_rbsp() // 10 - End of sequence end_of_seq_rbsp() // 11 - End of stream end_of_stream_rbsp() // 12 - Filler data filler_data_rbsp() // 13 to 23 - reserved // 24 to 31 - unspecified // VCL NAL -> nal_unit_type 1,2,3,4,5 // non-VCL NAL -> All other nal_unit_type values if (nal_unit_type == 14 || nal_unit_type == 20 || nal_unit_type == 21) { cemu_assert_debug(false); // todo - there are fields to parse here } switch (nal_unit_type) { case 1: if (parseSlices) parseNAL_slice_layer_without_partitioning_rbsp(h264ParserState, output, streamSubOffset, streamSubLength, rbspStream, nal_ref_idc, nal_unit_type); break; case 5: if (parseSlices) parseNAL_slice_layer_without_partitioning_rbsp(h264ParserState, output, streamSubOffset, streamSubLength, rbspStream, nal_ref_idc, nal_unit_type); break; case 6: // SEI break; case 7: cemu_assert_debug(parseNAL_seq_parameter_set_rbsp(h264ParserState, output, rbspStream)); break; case 8: cemu_assert_debug(parseNAL_pic_parameter_set_rbsp(h264ParserState, output, rbspStream)); break; case 9: // access unit delimiter break; case 10: // end of sequence break; case 12: // filler data // seen in Cocoto Magic Circus 2 intro video break; default: cemuLog_logDebug(LogType::Force, "Unsupported NAL unit type {}", nal_unit_type); cemu_assert_debug(false); // todo break; } } } sint32 h264GetUnitLength(h264ParserState_t* h264ParserState, uint8* data, uint32 length) { NALInputBitstream nalStream(data, length); if (nalStream.hasError()) { cemu_assert_debug(false); return -1; } // search for start code sint32 startCodeOffset = 0; bool hasStartcode = false; while (startCodeOffset < (sint32)(length - 3)) { if (data[startCodeOffset + 0] == 0x00 && data[startCodeOffset + 1] == 0x00 && data[startCodeOffset + 2] == 0x01) { hasStartcode = true; break; } startCodeOffset++; } if (hasStartcode == false) return -1; data += startCodeOffset; length -= startCodeOffset; // parse NAL data while (true) { if (nalStream.isEndOfStream()) break; RBSPInputBitstream rbspStream; if (nalStream.getNextRBSP(rbspStream, true) == false) break; sint32 streamSubOffset = (sint32)(rbspStream.getBasePtr() - data); sint32 streamSubLength = rbspStream.getBaseLength(); // parse NAL header uint8 nalHeaderByte = rbspStream.readU8(); if ((nalHeaderByte & 0x80) != 0) { // MSB must be zero cemu_assert_debug(false); continue; } uint8 nal_ref_idc = (nalHeaderByte >> 5) & 0x3; uint8 nal_unit_type = (nalHeaderByte >> 0) & 0x1f; if (nal_unit_type == 14 || nal_unit_type == 20 || nal_unit_type == 21) { cemu_assert_debug(false); continue; } switch (nal_unit_type) { case 1: case 5: { // note: We cant parse the slice data because we dont have SPS and PPS data reliably available // // currently we just assume there is 1 slice per unit return (sint32)((rbspStream.getBasePtr() + rbspStream.getBaseLength()) - data) + startCodeOffset; break; } case 6: // SEI break; case 7: cemu_assert_debug(parseNAL_seq_parameter_set_rbsp(h264ParserState, nullptr, rbspStream)); break; case 8: cemu_assert_debug(parseNAL_pic_parameter_set_rbsp(h264ParserState, nullptr, rbspStream)); break; case 9: // access unit delimiter break; case 10: // end of sequence break; default: cemuLog_logDebug(LogType::Force, "Unsupported NAL unit type {}", nal_unit_type); assert_dbg(); // todo - NAL 10 is used in DKC TF // todo break; } } return -1; } static const unsigned char h264_default_4x4_Intra[16] = { 6, 13, 13, 20, 20, 20, 28, 28, 28, 28, 32, 32, 32, 37, 37, 42 }; static const unsigned char h264_default_4x4_Inter[16] = { 10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34 }; static const unsigned char h264_default_8x8_Intra[64] = { 6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23, 23, 23, 23, 23, 23, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31, 31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42 }; static const unsigned char h264_default_8x8_Inter[64] = { 9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35 }; void h264Parser_getScalingMatrix4x4(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix4x4) { // use scaling lists from PPS first if (pps->pic_scaling_matrix_present_flag) { if (pps->ScalingList4x4[index].isPresent) { cemu_assert(false); // testing needed (what about UseDefaultScalingMatrix?) memcpy(matrix4x4, pps->ScalingList4x4[index].list, 4 * 4 * sizeof(uint8)); } else { if (index < 3) memcpy(matrix4x4, h264_default_4x4_Intra, 4 * 4 * sizeof(uint8)); else memcpy(matrix4x4, h264_default_4x4_Inter, 4 * 4 * sizeof(uint8)); } return; } // use scaling lists from SPS if (sps->seq_scaling_matrix_present_flag) { if (sps->ScalingList4x4[index].isPresent) { cemu_assert(false); // testing needed (what about UseDefaultScalingMatrix?) memcpy(matrix4x4, sps->ScalingList4x4[index].list, 4 * 4 * sizeof(uint8)); } else { if (index < 3) memcpy(matrix4x4, h264_default_4x4_Intra, 4 * 4 * sizeof(uint8)); else memcpy(matrix4x4, h264_default_4x4_Inter, 4 * 4 * sizeof(uint8)); } return; } // default (Flat_4x4_16) memset(matrix4x4, 16, 4 * 4 * sizeof(uint8)); } void h264Parser_getScalingMatrix8x8(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix8x8) { // use scaling lists from PPS first if (pps->pic_scaling_matrix_present_flag) { if (pps->ScalingList8x8[index].isPresent) { cemu_assert(false); // testing needed (what about UseDefaultScalingMatrix?) memcpy(matrix8x8, pps->ScalingList8x8[index].list, 8 * 8 * sizeof(uint8)); } else { if ((index&1) == 0) memcpy(matrix8x8, h264_default_8x8_Intra, 8 * 8 * sizeof(uint8)); else memcpy(matrix8x8, h264_default_8x8_Inter, 8 * 8 * sizeof(uint8)); } return; } // use scaling lists from SPS if (sps->seq_scaling_matrix_present_flag) { if (sps->ScalingList8x8[index].isPresent) { cemu_assert(false); // testing needed (what about UseDefaultScalingMatrix?) memcpy(matrix8x8, sps->ScalingList8x8[index].list, 8 * 8 * sizeof(uint8)); } else { if ((index & 1) == 0) memcpy(matrix8x8, h264_default_8x8_Intra, 8 * 8 * sizeof(uint8)); else memcpy(matrix8x8, h264_default_8x8_Inter, 8 * 8 * sizeof(uint8)); } return; } // default (Flat_8x8_16) memset(matrix8x8, 16, 8 * 8 * sizeof(uint8)); } ================================================ FILE: src/Cafe/OS/libs/h264_avc/parser/H264Parser.h ================================================ #pragma once /* stream parsers */ class RBSPInputBitstream { public: RBSPInputBitstream() {}; RBSPInputBitstream(uint8* stream, uint32 length) { this->streamPtr = stream; this->streamLength = length; if (streamLength >= 1) currentRBSPByte = streamPtr[0]; else currentRBSPByte = 0; errorFlag = false; } bool hasError() const { return errorFlag; } bool isByteAligned() const { return bitIndex == 0; } uint8 readU8() { if (readIndex >= streamLength) return 0; cemu_assert_debug(bitIndex == 0); uint8 v = _getCurrentRBSPByte(); _nextRBSPByte(); return v; } uint8 readBit() { if (readIndex >= streamLength) return 0; uint8 v = _getCurrentRBSPByte(); uint8 bitValue = (v >> (7 - bitIndex)) & 1; bitIndex += 1; if (bitIndex >= 8) { bitIndex = 0; _nextRBSPByte(); } return bitValue; } template<int N> uint32 readBits() { uint32 v = 0; for (sint32 i = 0; i < N; i++) { v <<= 1; v |= (readBit() ? 1 : 0); } return v; } uint32 readBits(sint32 N) { uint32 v = 0; for (sint32 i = 0; i < N; i++) { v <<= 1; v |= (readBit() ? 1 : 0); } return v; } uint32 readUV_E() { if (readBit() != 0) { return 0; } for (sint32 i = 1; i < 31; i++) { uint8 bitValue = readBit(); if (bitValue) { uint32 rangeBase = (1 << i) - 1; // read range bits uint32 suffixBits = 0; for (sint32 b = 0; b < i; b++) { suffixBits <<= 1; suffixBits |= (readBit() ? 1 : 0); } return rangeBase + suffixBits; } } cemu_assert_debug(false); return 0; } sint32 readSV_E() { uint32 t = readUV_E(); if (t == 0) return 0; if ((t & 1) != 0) { // positive return (sint32)((t + 1) / 2); } // negative return -(sint32)((t + 1) / 2); } bool readTrailingRBSPBits() { // read the stop bit which must always be 1 if (readBit() == 0) return false; // read zero bits until byte aligned while (bitIndex != 0) { if (readBit() != 0) return false; // invalid padding bit } // check if we reached end of stream if (readIndex >= streamLength) return true; return false; } bool more_rbsp_data() { uint32 storedReadIndex = readIndex; uint32 storedBitIndex = bitIndex; bool hasMoreRBSPData = readTrailingRBSPBits() == false; readIndex = storedReadIndex; bitIndex = storedBitIndex; currentRBSPByte = streamPtr[readIndex]; return hasMoreRBSPData; } bool isEndOfStream() { return readIndex >= streamLength; } uint8* getBasePtr() { return streamPtr; } sint32 getBaseLength() { return streamLength; } private: uint8 _getCurrentRBSPByte() { return currentRBSPByte; } void _nextRBSPByte() { readIndex += sizeof(uint8); if (readIndex >= 2 && streamPtr[readIndex - 2] == 0x00 && streamPtr[readIndex - 1] == 0x00 && streamPtr[readIndex] == 0x03) { readIndex += 1; currentRBSPByte = streamPtr[readIndex]; return; } else { currentRBSPByte = streamPtr[readIndex]; } } private: uint8* streamPtr; uint32 streamLength; uint32 readIndex{}; uint8 currentRBSPByte{}; sint32 bitIndex{}; bool errorFlag{}; }; class NALInputBitstream { public: NALInputBitstream(uint8* stream, uint32 length) { this->nalStreamPtr = stream; this->nalStreamLength = length; errorFlag = false; } bool hasError() { return errorFlag; } bool getNextRBSP(RBSPInputBitstream& rbspInputBitstream, bool mustEndAtStartCode = false) { sint32 indexNextStartSignature = findNextStartSignature(); if (indexNextStartSignature < 0) { if (mustEndAtStartCode) return false; indexNextStartSignature = nalStreamLength; } if (indexNextStartSignature <= readIndex) return false; // skip current start signature if ((nalStreamLength - readIndex) >= 3 && nalStreamPtr[readIndex + 0] == 0 && nalStreamPtr[readIndex + 1] == 0 && nalStreamPtr[readIndex + 2] == 1) { readIndex += 3; } else if ((nalStreamLength - readIndex) >= 3 && nalStreamPtr[readIndex + 0] == 0 && nalStreamPtr[readIndex + 1] == 0 && nalStreamPtr[readIndex + 2] == 0 && nalStreamPtr[readIndex + 3] == 1) { readIndex += 4; } else { // no start signature } cemu_assert(readIndex <= indexNextStartSignature); cemu_assert_debug(readIndex != indexNextStartSignature); // create rbsp substream rbspInputBitstream = RBSPInputBitstream(nalStreamPtr + readIndex, indexNextStartSignature - readIndex); readIndex = indexNextStartSignature; return true; } bool isEndOfStream() { return readIndex >= nalStreamLength; } private: sint32 findNextStartSignature() { if (readIndex >= nalStreamLength) return -1; sint32 offset = readIndex; // if there is a start signature at the current address, skip it if ((offset + 3) <= nalStreamLength && nalStreamPtr[offset + 0] == 0x00 && nalStreamPtr[offset + 1] == 0x00 && nalStreamPtr[offset + 2] == 0x01) { offset += 3; } else if ((offset + 4) <= nalStreamLength && nalStreamPtr[offset + 0] == 0x00 && nalStreamPtr[offset + 1] == 0x00 && nalStreamPtr[offset + 2] == 0x00 && nalStreamPtr[offset + 3] == 0x01) { offset += 4; } while (offset < (nalStreamLength - 3)) { if (nalStreamPtr[offset + 0] == 0x00 && nalStreamPtr[offset + 1] == 0x00) { if (nalStreamPtr[offset + 2] == 0x01) { return offset; } else if ((nalStreamLength - offset) >= 4 && nalStreamPtr[offset + 2] == 0x00 && nalStreamPtr[offset + 3] == 0x01) { return offset; } } offset++; } return -1; } private: uint8* nalStreamPtr; sint32 nalStreamLength; sint32 readIndex{}; bool errorFlag{}; }; /* H264 NAL parser */ struct h264_scaling_matrix4x4_t { uint8 isPresent; uint8 UseDefaultScalingMatrix; sint32 list[4*4]; }; struct h264_scaling_matrix8x8_t { uint8 isPresent; uint8 UseDefaultScalingMatrix; sint32 list[8*8]; }; struct h264State_pic_parameter_set_t { uint32 pic_parameter_set_id; uint32 seq_parameter_set_id; uint8 entropy_coding_mode_flag; uint8 bottom_field_pic_order_in_frame_present_flag; // alias pic_order_present_flag uint32 num_slice_groups_minus1; // slice group uint32 slice_group_map_type; // todo - some variable sized fields uint32 slice_group_change_rate_minus1; // todo uint32 num_ref_idx_l0_default_active_minus1; uint32 num_ref_idx_l1_default_active_minus1; uint8 weighted_pred_flag; uint8 weighted_bipred_idc; sint32 pic_init_qp_minus26; sint32 pic_init_qs_minus26; sint32 chroma_qp_index_offset; uint8 deblocking_filter_control_present_flag; uint8 constrained_intra_pred_flag; uint8 redundant_pic_cnt_present_flag; uint8 transform_8x8_mode_flag; uint8 pic_scaling_matrix_present_flag; h264_scaling_matrix4x4_t ScalingList4x4[6]; h264_scaling_matrix8x8_t ScalingList8x8[6]; sint32 second_chroma_qp_index_offset; }; struct h264State_seq_parameter_set_t { uint8 profile_idc; // 0x64 = high profile uint8 constraint; // 6 flags + 2 reserved bits uint8 level_idc; // 0x29 = level 4.1 uint32 seq_parameter_set_id; uint32 chroma_format_idc; uint8 separate_colour_plane_flag; // aka residual_colour_transform_flag uint32 bit_depth_luma_minus8; uint32 bit_depth_chroma_minus8; uint8 qpprime_y_zero_transform_bypass_flag; uint8 seq_scaling_matrix_present_flag; h264_scaling_matrix4x4_t ScalingList4x4[6]; h264_scaling_matrix8x8_t ScalingList8x8[6]; uint32 log2_max_frame_num_minus4; uint32 pic_order_cnt_type; // picture order count type 0 uint32 log2_max_pic_order_cnt_lsb_minus4; uint32 num_ref_frames; uint32 gaps_in_frame_num_value_allowed_flag; uint32 pic_width_in_mbs_minus1; uint32 pic_height_in_map_units_minus1; uint8 frame_mbs_only_flag; uint8 mb_adaptive_frame_field_flag; uint8 direct_8x8_inference_flag; uint8 frame_cropping_flag; uint32 frame_crop_left_offset; uint32 frame_crop_right_offset; uint32 frame_crop_top_offset; uint32 frame_crop_bottom_offset; uint32 getMaxFrameNum() const { return 1<<(log2_max_frame_num_minus4+4); } }; struct H264SliceType { H264SliceType() {}; H264SliceType(uint32 slice_type) : m_sliceType(slice_type) {}; bool isSliceTypeP() const { return (this->m_sliceType % 5) == 0; }; bool isSliceTypeB() const { return (this->m_sliceType % 5) == 1; }; bool isSliceTypeI() const { return (this->m_sliceType % 5) == 2; }; bool isSliceTypeSP() const { return (this->m_sliceType % 5) == 3; }; bool isSliceTypeSI() const { return (this->m_sliceType % 5) == 4; }; private: uint32 m_sliceType{}; }; typedef struct { uint8 nal_ref_idc; uint8 nal_unit_type; uint32 first_mb_in_slice; H264SliceType slice_type; uint32 pic_parameter_set_id; uint32 frame_num; uint8 field_pic_flag; uint8 bottom_field_flag; uint32 idr_pic_id; uint32 pic_order_cnt_lsb; sint32 delta_pic_order_cnt_bottom; uint32 redundant_pic_cnt; // slice type B uint8 direct_spatial_mv_pred_flag; // slice type P, SP, B uint8 num_ref_idx_active_override_flag; uint32 num_ref_idx_l0_active_minus1; uint32 num_ref_idx_l1_active_minus1; // ref_pic_list_modification_flag_l0 struct { uint8 type; uint32 abs_diff_pic_num_minus1; uint32 long_term_pic_num; }pic_list_modification0Array[32]; sint32 pic_list_modification0Count; // ref_pic_list_modification_flag_l1 struct { uint8 type; uint32 abs_diff_pic_num_minus1; uint32 long_term_pic_num; }pic_list_modification1Array[32]; sint32 pic_list_modification1Count; // memory_management_control_operation enum { MEMOP_END = 0, MEMOP_REMOVE_REF_FROM_SHORT_TERM = 1, MEMOP_REMOVE_REF_FROM_LONG_TERM = 2, MEMOP_MAKE_LONG_TERM_REF = 3, MEMOP_MAX_LONG_TERM_INDEX = 4, MEMOP_REMOVE_ALL_REFS = 5, MEMOP_MAKE_CURRENT_LONG_TERM_REF = 6 }; uint8 adaptive_ref_pic_marking_mode_flag; struct { uint8 op; uint32 difference_of_pic_nums_minus1; uint32 long_term_pic_num; uint32 long_term_frame_idx; uint32 max_long_term_frame_idx_plus1; }memory_management_control_operation[16]; sint32 memory_management_control_operation_num; // derived values uint8 IdrPicFlag; struct { uint32 TopFieldOrderCnt; }calculated; }nal_slice_header_t; typedef struct { sint32 streamSubOffset; sint32 streamSubSize; nal_slice_header_t header; }nal_slice_info_t; typedef struct { bool hasSPS; bool hasPPS; // list of NAL slices sint32 sliceCount; std::array<nal_slice_info_t, 32> sliceInfo; }h264ParserOutput_t; typedef struct { nal_slice_header_t slice_header; }nal_slice_t; typedef struct { //static const int MAX_SLICES = 32; bool hasSPS; bool hasPPS; h264State_seq_parameter_set_t sps; h264State_pic_parameter_set_t pps; struct { uint32 prevPicOrderCntMsb; uint32 prevPicOrderCntLsb; uint32 prevFrameNum; uint32 prevFrameNumOffset; }picture_order; }h264ParserState_t; void h264Parse(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, uint8* data, uint32 length, bool parseSlices = true); sint32 h264GetUnitLength(h264ParserState_t* h264ParserState, uint8* data, uint32 length); bool h264Parser_ParseSPS(uint8* data, uint32 length, h264State_seq_parameter_set_t& sps); void h264Parser_getScalingMatrix4x4(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix4x4); void h264Parser_getScalingMatrix8x8(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix8x8); static sint32 h264GetFramePitch(sint32 width) { return (width+0xFF)&~0xFF; } static sint32 h264GetFrameSize(sint32 width, sint32 height) { sint32 pitch = h264GetFramePitch(width); return height * pitch + (height / 2) * pitch; } ================================================ FILE: src/Cafe/OS/libs/mic/mic.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "input/InputManager.h" #include "audio/IAudioInputAPI.h" #include "config/CemuConfig.h" enum class MIC_RESULT { SUCCESS = 0, BAD_PARAM = -2, ALREADY_OPEN = -5, NOT_OPEN = -6, NOT_INITIALIZED = -7, NOT_CONNECTED = -8 }; #define MIC_SAMPLERATE 32000 const int MIC_SAMPLES_PER_3MS_32KHZ = (96); // 32000*3/1000 enum class MIC_STATUS_FLAGS : uint32 { FORMAT_PCM16 = (1 << 0), IS_OPEN = (1 << 1), IS_CONNECTED = (1 << 2) }; DEFINE_ENUM_FLAG_OPERATORS(MIC_STATUS_FLAGS); #define MIC_HANDLE_DRC0 0 #define MIC_HANDLE_DRC1 1 enum class MIC_STATEID { SAMPLERATE = 0, GAIN_DB = 1, GAIN_MIN = 2, GAIN_MAX = 3, GAIN_STEP = 4, MUTE = 5, ECHO_CANCELLATION = 7, AUTO_SELECTION = 8 }; struct { struct { bool isInited; bool isOpen; // ringbuffer information void* ringbufferSampleData; uint32 ringbufferSize; uint32 readIndex; uint32 writeIndex; // state uint32 echoCancellation; uint32 autoSelection; uint32 gainDB; }drc[2]; }MICStatus = {0}; typedef struct { uint32 size; MPTR samples; }micRingbuffer_t; struct micStatus_t { betype<MIC_STATUS_FLAGS> flags; uint32be numSamplesAvailable; uint32be readIndex; }; static_assert(sizeof(micStatus_t) == 0xC); bool mic_isConnected(uint32 drcIndex) { if( drcIndex != 0 ) return false; return InputManager::instance().get_vpad_controller(drcIndex) != nullptr; } sint32 mic_availableSamples(uint32 drcIndex) { return (MICStatus.drc[drcIndex].ringbufferSize+MICStatus.drc[drcIndex].writeIndex-MICStatus.drc[drcIndex].readIndex) % MICStatus.drc[drcIndex].ringbufferSize; } bool mic_isActive(uint32 drcIndex) { return MICStatus.drc[drcIndex].isOpen; } void mic_feedSamples(uint32 drcIndex, sint16* sampleData, sint32 numSamples) { uint16* sampleDataU16 = (uint16*)sampleData; sint32 ringBufferSize = MICStatus.drc[0].ringbufferSize; sint32 writeIndex = MICStatus.drc[0].writeIndex; uint16* ringBufferBase = (uint16*)MICStatus.drc[0].ringbufferSampleData; do { ringBufferBase[writeIndex] = _swapEndianU16(*sampleDataU16); sampleDataU16++; writeIndex++; writeIndex %= ringBufferSize; }while( (--numSamples) > 0 ); MICStatus.drc[0].writeIndex = writeIndex; } void micExport_MICInit(PPCInterpreter_t* hCPU) { debug_printf("MICInit(%d,%d,0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); uint32 drcIndex = hCPU->gpr[3]; if( drcIndex > 1 || (drcIndex == 0 && mic_isConnected(drcIndex) == false) ) { memory_writeU32(hCPU->gpr[6], (uint32)MIC_RESULT::NOT_CONNECTED); osLib_returnFromFunction(hCPU, -1); return; } if( drcIndex != 0 ) { // DRC1 microphone is not supported (only DRC0) memory_writeU32(hCPU->gpr[6], (uint32)MIC_RESULT::NOT_CONNECTED); osLib_returnFromFunction(hCPU, -1); return; } if( MICStatus.drc[drcIndex].isInited ) { memory_writeU32(hCPU->gpr[6], (uint32)MIC_RESULT::ALREADY_OPEN); osLib_returnFromFunction(hCPU, -1); return; } micRingbuffer_t* micRingbuffer = (micRingbuffer_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]); MICStatus.drc[drcIndex].ringbufferSampleData = memory_getPointerFromVirtualOffset(_swapEndianU32(micRingbuffer->samples)); MICStatus.drc[drcIndex].ringbufferSize = _swapEndianU32(micRingbuffer->size); MICStatus.drc[drcIndex].readIndex = 0; MICStatus.drc[drcIndex].writeIndex = 0; MICStatus.drc[drcIndex].isInited = true; // init default states MICStatus.drc[drcIndex].echoCancellation = 1; // guessed MICStatus.drc[drcIndex].autoSelection = 1; // guessed // return status memory_writeU32(hCPU->gpr[6], 0); // no error osLib_returnFromFunction(hCPU, (drcIndex==0)?MIC_HANDLE_DRC0:MIC_HANDLE_DRC1); // success auto& config = GetConfig(); const auto audio_api = IAudioInputAPI::Cubeb; // change this if more input apis get implemented std::unique_lock lock(g_audioInputMutex); if (!g_inputAudio) { IAudioInputAPI::DeviceDescriptionPtr device_description; if (IAudioInputAPI::IsAudioInputAPIAvailable(audio_api)) { auto devices = IAudioInputAPI::GetDevices(audio_api); const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.input_device; }); if (it != devices.end()) device_description = *it; } if (device_description) { try { g_inputAudio = IAudioInputAPI::CreateDevice(audio_api, device_description, MIC_SAMPLERATE, 1, MIC_SAMPLES_PER_3MS_32KHZ, 16); g_inputAudio->SetVolume(config.input_volume); } catch (std::runtime_error& ex) { cemuLog_log(LogType::Force, "can't initialize audio input: {}", ex.what()); exit(0); } } } } void micExport_MICOpen(PPCInterpreter_t* hCPU) { debug_printf("MICOpen(%d)\n", hCPU->gpr[3]); uint32 micHandle = hCPU->gpr[3]; if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); return; } uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; if( MICStatus.drc[drcIndex].isInited == false ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); return; } // check if already open if( MICStatus.drc[drcIndex].isOpen ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::ALREADY_OPEN); return; } // check if DRC is connected bool hasDRCConnected = InputManager::instance().get_vpad_controller(drcIndex) != nullptr; if( hasDRCConnected == false ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_CONNECTED); return; } // success MICStatus.drc[drcIndex].isOpen = true; osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); std::shared_lock lock(g_audioInputMutex); if(g_inputAudio) g_inputAudio->Play(); } void micExport_MICClose(PPCInterpreter_t* hCPU) { // debug_printf("MICClose(%d)\n", hCPU->gpr[3]); uint32 micHandle = hCPU->gpr[3]; if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); return; } uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; if( MICStatus.drc[drcIndex].isInited == false ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); return; } // check if already closed if( MICStatus.drc[drcIndex].isOpen == false ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::ALREADY_OPEN); return; } // success MICStatus.drc[drcIndex].isOpen = false; osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); std::shared_lock lock(g_audioInputMutex); if (g_inputAudio) g_inputAudio->Stop(); } void micExport_MICGetStatus(PPCInterpreter_t* hCPU) { debug_printf("MICGetStatus(%d,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); uint32 micHandle = hCPU->gpr[3]; if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); return; } uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; if( MICStatus.drc[drcIndex].isInited == false ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); return; } micStatus_t* micStatus = (micStatus_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); MIC_STATUS_FLAGS micFlags = MIC_STATUS_FLAGS::FORMAT_PCM16; if( mic_isConnected(drcIndex) ) micFlags |= MIC_STATUS_FLAGS::IS_CONNECTED; if( MICStatus.drc[drcIndex].isOpen ) micFlags |= MIC_STATUS_FLAGS::IS_OPEN; micStatus->flags = micFlags; micStatus->numSamplesAvailable = mic_availableSamples(drcIndex); micStatus->readIndex = MICStatus.drc[drcIndex].readIndex; osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); } void micExport_MICGetState(PPCInterpreter_t* hCPU) { // debug_printf("MICGetState(%d,%d,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); // parameters: // r3 uint32 micHandle // r4 uint32 stateId // r5 uint32* value uint32 micHandle = hCPU->gpr[3]; if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); return; } uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; if( MICStatus.drc[drcIndex].isInited == false ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); return; } // get state MIC_STATEID stateId = (MIC_STATEID)hCPU->gpr[4]; if (stateId == MIC_STATEID::SAMPLERATE) { memory_writeU32(hCPU->gpr[5], MIC_SAMPLERATE); } else if (stateId == MIC_STATEID::AUTO_SELECTION) { memory_writeU32(hCPU->gpr[5], MICStatus.drc[drcIndex].autoSelection); } else if (stateId == MIC_STATEID::GAIN_MIN) { // value guessed memory_writeU32(hCPU->gpr[5], 0x0000); // S7.8 } else if (stateId == MIC_STATEID::GAIN_MAX) { // value guessed memory_writeU32(hCPU->gpr[5], 0x0200); // S7.8 } else if (stateId == MIC_STATEID::GAIN_STEP) { // value guessed memory_writeU32(hCPU->gpr[5], 0x0001); // S7.8 } else if (stateId == MIC_STATEID::ECHO_CANCELLATION) { memory_writeU32(hCPU->gpr[5], MICStatus.drc[drcIndex].echoCancellation); } else if (stateId == MIC_STATEID::GAIN_DB) { // value guessed memory_writeU32(hCPU->gpr[5], MICStatus.drc[drcIndex].gainDB); // S7.8 } else cemu_assert_unimplemented(); osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); return; } void micExport_MICSetState(PPCInterpreter_t* hCPU) { uint32 micHandle = hCPU->gpr[3]; if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); return; } uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; if( MICStatus.drc[drcIndex].isInited == false ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); return; } // set state MIC_STATEID stateId = (MIC_STATEID)hCPU->gpr[4]; uint32 newValue = hCPU->gpr[5]; if( stateId == MIC_STATEID::ECHO_CANCELLATION ) { MICStatus.drc[drcIndex].echoCancellation = (newValue!=0)?1:0; } else if( stateId == MIC_STATEID::AUTO_SELECTION ) { MICStatus.drc[drcIndex].autoSelection = (newValue!=0)?1:0; } else if( stateId == MIC_STATEID::GAIN_DB ) { MICStatus.drc[drcIndex].gainDB = newValue; } else assert_dbg(); osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); return; } void micExport_MICSetDataConsumed(PPCInterpreter_t* hCPU) { // debug_printf("MICSetDataConsumed(%d,%d)\n", hCPU->gpr[3], hCPU->gpr[4]); // parameters: // r3 uint32 micHandle // r4 uint32 numConsumedSamples uint32 micHandle = hCPU->gpr[3]; if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); return; } uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; if( MICStatus.drc[drcIndex].isInited == false ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); return; } if( MICStatus.drc[drcIndex].isOpen == false ) { osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_OPEN); return; } sint32 numConsumedSamples = (sint32)hCPU->gpr[4]; //debug_printf("MIC consume samples 0x%04x\n", numConsumedSamples); if( mic_availableSamples(drcIndex) < numConsumedSamples ) { MICStatus.drc[drcIndex].readIndex = MICStatus.drc[drcIndex].writeIndex; osLib_returnFromFunction(hCPU, -81); } else { MICStatus.drc[drcIndex].readIndex += numConsumedSamples; MICStatus.drc[drcIndex].readIndex %= MICStatus.drc[drcIndex].ringbufferSize; osLib_returnFromFunction(hCPU, 0); } return; } void mic_updateDevicePlayState(bool isPlaying) { if (g_inputAudio) { if (isPlaying) g_inputAudio->Play(); else g_inputAudio->Stop(); } } void mic_updateOnAXFrame() { sint32 drcIndex = 0; if( mic_isActive(0) == false ) { drcIndex = 1; if (mic_isActive(1) == false) { std::shared_lock lock(g_audioInputMutex); mic_updateDevicePlayState(false); return; } } std::shared_lock lock(g_audioInputMutex); mic_updateDevicePlayState(true); if (g_inputAudio) { sint16 micSampleData[MIC_SAMPLES_PER_3MS_32KHZ]; g_inputAudio->ConsumeBlock(micSampleData); mic_feedSamples(0, micSampleData, MIC_SAMPLES_PER_3MS_32KHZ); } else { const sint32 micSampleCount = 32000 / 32; sint16 micSampleData[micSampleCount]; auto controller = InputManager::instance().get_vpad_controller(drcIndex); if( controller && controller->is_mic_active() ) { for(sint32 i=0; i<micSampleCount; i++) { micSampleData[i] = (sint16)(sin((float)GetTickCount()*0.1f+sin((float)GetTickCount()*0.0001f)*100.0f)*30000.0f); } } else { memset(micSampleData, 0x00, sizeof(micSampleData)); } mic_feedSamples(0, micSampleData, micSampleCount); } } namespace mic { class : public COSModule { public: std::string_view GetName() override { return "mic"; } void RPLMapped() override { osLib_addFunction("mic", "MICInit", micExport_MICInit); osLib_addFunction("mic", "MICOpen", micExport_MICOpen); osLib_addFunction("mic", "MICClose", micExport_MICClose); osLib_addFunction("mic", "MICGetStatus", micExport_MICGetStatus); osLib_addFunction("mic", "MICGetState", micExport_MICGetState); osLib_addFunction("mic", "MICSetState", micExport_MICSetState); osLib_addFunction("mic", "MICSetDataConsumed", micExport_MICSetDataConsumed); } }s_COSmicModule; COSModule* GetModule() { return &s_COSmicModule; } }; ================================================ FILE: src/Cafe/OS/libs/mic/mic.h ================================================ #include "Cafe/OS/RPL/COSModule.h" bool mic_isActive(uint32 drcIndex); void mic_updateOnAXFrame(); namespace mic { COSModule* GetModule(); }; ================================================ FILE: src/Cafe/OS/libs/nfc/TLV.cpp ================================================ #include "TLV.h" #include "stream.h" #include <cassert> TLV::TLV() { } TLV::TLV(Tag tag, std::vector<std::byte> value) : mTag(tag), mValue(std::move(value)) { } TLV::~TLV() { } std::vector<TLV> TLV::FromBytes(const std::span<std::byte>& data) { bool hasTerminator = false; std::vector<TLV> tlvs; SpanStream stream(data, std::endian::big); while (stream.GetRemaining() > 0 && !hasTerminator) { // Read the tag uint8 byte; stream >> byte; Tag tag = static_cast<Tag>(byte); switch (tag) { case TLV::TAG_NULL: // Don't need to do anything for NULL tags break; case TLV::TAG_TERMINATOR: tlvs.emplace_back(tag, std::vector<std::byte>{}); hasTerminator = true; break; default: { // Read the length uint16 length; stream >> byte; length = byte; // If the length is 0xff, 2 bytes with length follow if (length == 0xff) { stream >> length; } std::vector<std::byte> value; value.resize(length); stream.Read(value); tlvs.emplace_back(tag, value); break; } } if (stream.GetError() != Stream::ERROR_OK) { cemuLog_log(LogType::Force, "Error: TLV parsing read past end of stream"); // Clear tlvs to prevent further havoc while parsing ndef data tlvs.clear(); break; } } // This seems to be okay, at least NTAGs don't add a terminator tag // if (!hasTerminator) // { // cemuLog_log(LogType::Force, "Warning: TLV parsing reached end of stream without terminator tag"); // } return tlvs; } std::vector<std::byte> TLV::ToBytes() const { std::vector<std::byte> bytes; VectorStream stream(bytes, std::endian::big); // Write tag stream << uint8(mTag); switch (mTag) { case TLV::TAG_NULL: case TLV::TAG_TERMINATOR: // Nothing to do here break; default: { // Write length (decide if as a 8-bit or 16-bit value) if (mValue.size() >= 0xff) { stream << uint8(0xff); stream << uint16(mValue.size()); } else { stream << uint8(mValue.size()); } // Write value stream.Write(mValue); } } return bytes; } TLV::Tag TLV::GetTag() const { return mTag; } const std::vector<std::byte>& TLV::GetValue() const { return mValue; } void TLV::SetTag(Tag tag) { mTag = tag; } void TLV::SetValue(const std::span<const std::byte>& value) { // Can only write max 16-bit lengths into TLV cemu_assert(value.size() < 0x10000); mValue.assign(value.begin(), value.end()); } ================================================ FILE: src/Cafe/OS/libs/nfc/TLV.h ================================================ #pragma once #include <cstdint> #include <span> #include <vector> class TLV { public: enum Tag { TAG_NULL = 0x00, TAG_LOCK_CTRL = 0x01, TAG_MEM_CTRL = 0x02, TAG_NDEF = 0x03, TAG_PROPRIETARY = 0xFD, TAG_TERMINATOR = 0xFE, }; public: TLV(); TLV(Tag tag, std::vector<std::byte> value); virtual ~TLV(); static std::vector<TLV> FromBytes(const std::span<std::byte>& data); std::vector<std::byte> ToBytes() const; Tag GetTag() const; const std::vector<std::byte>& GetValue() const; void SetTag(Tag tag); void SetValue(const std::span<const std::byte>& value); private: Tag mTag; std::vector<std::byte> mValue; }; ================================================ FILE: src/Cafe/OS/libs/nfc/TagV0.cpp ================================================ #include "TagV0.h" #include "TLV.h" #include <algorithm> namespace { constexpr std::size_t kTagSize = 512u; constexpr std::size_t kMaxBlockCount = kTagSize / sizeof(TagV0::Block); constexpr uint8 kLockbyteBlock0 = 0xe; constexpr uint8 kLockbytesStart0 = 0x0; constexpr uint8 kLockbytesEnd0 = 0x2; constexpr uint8 kLockbyteBlock1 = 0xf; constexpr uint8 kLockbytesStart1 = 0x2; constexpr uint8 kLockbytesEnd1 = 0x8; constexpr uint8 kNDEFMagicNumber = 0xe1; // These blocks are not part of the locked area constexpr bool IsBlockLockedOrReserved(uint8 blockIdx) { // Block 0 is the UID if (blockIdx == 0x0) { return true; } // Block 0xd is reserved if (blockIdx == 0xd) { return true; } // Block 0xe and 0xf contains lock / reserved bytes if (blockIdx == 0xe || blockIdx == 0xf) { return true; } return false; } } // namespace TagV0::TagV0() { } TagV0::~TagV0() { } std::shared_ptr<TagV0> TagV0::FromBytes(const std::span<const std::byte>& data) { // Version 0 tags need at least 512 bytes if (data.size() != kTagSize) { cemuLog_log(LogType::Force, "Error: Version 0 tags should be {} bytes in size", kTagSize); return {}; } std::shared_ptr<TagV0> tag = std::make_shared<TagV0>(); // Parse the locked area before continuing if (!tag->ParseLockedArea(data)) { cemuLog_log(LogType::Force, "Error: Failed to parse locked area"); return {}; } // Now that the locked area is known, parse the data area std::vector<std::byte> dataArea; if (!tag->ParseDataArea(data, dataArea)) { cemuLog_log(LogType::Force, "Error: Failed to parse data area"); return {}; } // The first few bytes in the dataArea make up the capability container std::copy_n(dataArea.begin(), tag->mCapabilityContainer.size(), std::as_writable_bytes(std::span(tag->mCapabilityContainer)).begin()); if (!tag->ValidateCapabilityContainer()) { cemuLog_log(LogType::Force, "Error: Failed to validate capability container"); return {}; } // The rest of the dataArea contains the TLVs tag->mTLVs = TLV::FromBytes(std::span(dataArea).subspan(tag->mCapabilityContainer.size())); if (tag->mTLVs.empty()) { cemuLog_log(LogType::Force, "Error: Tag contains no TLVs"); return {}; } // Look for the NDEF tlv tag->mNdefTlvIdx = static_cast<size_t>(-1); for (std::size_t i = 0; i < tag->mTLVs.size(); i++) { if (tag->mTLVs[i].GetTag() == TLV::TAG_NDEF) { tag->mNdefTlvIdx = i; break; } } if (tag->mNdefTlvIdx == static_cast<size_t>(-1)) { cemuLog_log(LogType::Force, "Error: Tag contains no NDEF TLV"); return {}; } // Append locked data for (const auto& [key, value] : tag->mLockedBlocks) { tag->mLockedArea.insert(tag->mLockedArea.end(), value.begin(), value.end()); } return tag; } std::vector<std::byte> TagV0::ToBytes() const { std::vector<std::byte> bytes(kTagSize); // Insert locked or reserved blocks for (const auto& [key, value] : mLockedOrReservedBlocks) { std::copy(value.begin(), value.end(), bytes.begin() + key * sizeof(Block)); } // Insert locked area auto lockedDataIterator = mLockedArea.begin(); for (const auto& [key, value] : mLockedBlocks) { std::copy_n(lockedDataIterator, sizeof(Block), bytes.begin() + key * sizeof(Block)); lockedDataIterator += sizeof(Block); } // Pack the dataArea into a linear buffer std::vector<std::byte> dataArea; const auto ccBytes = std::as_bytes(std::span(mCapabilityContainer)); dataArea.insert(dataArea.end(), ccBytes.begin(), ccBytes.end()); for (const TLV& tlv : mTLVs) { const auto tlvBytes = tlv.ToBytes(); dataArea.insert(dataArea.end(), tlvBytes.begin(), tlvBytes.end()); } // Make sure the dataArea is block size aligned dataArea.resize((dataArea.size() + (sizeof(Block)-1)) & ~(sizeof(Block)-1)); // The rest will be the data area auto dataIterator = dataArea.begin(); for (uint8 currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) { // All blocks which aren't locked make up the dataArea if (!IsBlockLocked(currentBlock)) { std::copy_n(dataIterator, sizeof(Block), bytes.begin() + currentBlock * sizeof(Block)); dataIterator += sizeof(Block); } } return bytes; } const TagV0::Block& TagV0::GetUIDBlock() const { return mLockedOrReservedBlocks.at(0); } const std::vector<std::byte>& TagV0::GetNDEFData() const { return mTLVs[mNdefTlvIdx].GetValue(); } const std::vector<std::byte>& TagV0::GetLockedArea() const { return mLockedArea; } void TagV0::SetNDEFData(const std::span<const std::byte>& data) { // Update the ndef value mTLVs[mNdefTlvIdx].SetValue(data); } bool TagV0::ParseLockedArea(const std::span<const std::byte>& data) { uint8 currentBlock = 0; // Start by parsing the first set of lock bytes for (uint8 i = kLockbytesStart0; i < kLockbytesEnd0; i++) { uint8 lockByte = uint8(data[kLockbyteBlock0 * sizeof(Block) + i]); // Iterate over the individual bits in the lock byte for (uint8 j = 0; j < 8; j++) { // Is block locked? if (lockByte & (1u << j)) { Block blk; std::copy_n(data.begin() + currentBlock * sizeof(Block), sizeof(Block), blk.begin()); // The lock bytes themselves are not part of the locked area if (!IsBlockLockedOrReserved(currentBlock)) { mLockedBlocks.emplace(currentBlock, blk); } else { mLockedOrReservedBlocks.emplace(currentBlock, blk); } } currentBlock++; } } // Parse the second set of lock bytes for (uint8 i = kLockbytesStart1; i < kLockbytesEnd1; i++) { uint8 lockByte = uint8(data[kLockbyteBlock1 * sizeof(Block) + i]); // Iterate over the individual bits in the lock byte for (uint8 j = 0; j < 8; j++) { // Is block locked? if (lockByte & (1u << j)) { Block blk; std::copy_n(data.begin() + currentBlock * sizeof(Block), sizeof(Block), blk.begin()); // The lock bytes themselves are not part of the locked area if (!IsBlockLockedOrReserved(currentBlock)) { mLockedBlocks.emplace(currentBlock, blk); } else { mLockedOrReservedBlocks.emplace(currentBlock, blk); } } currentBlock++; } } return true; } bool TagV0::IsBlockLocked(uint8 blockIdx) const { return mLockedBlocks.contains(blockIdx) || IsBlockLockedOrReserved(blockIdx); } bool TagV0::ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea) { for (uint8 currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) { // All blocks which aren't locked make up the dataArea if (!IsBlockLocked(currentBlock)) { auto blockOffset = data.begin() + sizeof(Block) * currentBlock; dataArea.insert(dataArea.end(), blockOffset, blockOffset + sizeof(Block)); } } return true; } bool TagV0::ValidateCapabilityContainer() { // NDEF Magic Number uint8 nmn = mCapabilityContainer[0]; if (nmn != kNDEFMagicNumber) { cemuLog_log(LogType::Force, "Error: CC: Invalid NDEF Magic Number"); return false; } // Version Number uint8 vno = mCapabilityContainer[1]; if (vno >> 4 != 1) { cemuLog_log(LogType::Force, "Error: CC: Invalid Version Number"); return false; } // Tag memory size uint8 tms = mCapabilityContainer[2]; if (8u * (tms + 1) < kTagSize) { cemuLog_log(LogType::Force, "Error: CC: Incomplete tag memory size"); return false; } return true; } ================================================ FILE: src/Cafe/OS/libs/nfc/TagV0.h ================================================ #pragma once #include <memory> #include <span> #include <map> #include "TLV.h" class TagV0 { public: using Block = std::array<std::byte, 0x8>; public: TagV0(); virtual ~TagV0(); static std::shared_ptr<TagV0> FromBytes(const std::span<const std::byte>& data); std::vector<std::byte> ToBytes() const; const Block& GetUIDBlock() const; const std::vector<std::byte>& GetNDEFData() const; const std::vector<std::byte>& GetLockedArea() const; void SetNDEFData(const std::span<const std::byte>& data); private: bool ParseLockedArea(const std::span<const std::byte>& data); bool IsBlockLocked(uint8 blockIdx) const; bool ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea); bool ValidateCapabilityContainer(); std::map<uint8, Block> mLockedOrReservedBlocks; std::map<uint8, Block> mLockedBlocks; std::array<uint8, 0x4> mCapabilityContainer; std::vector<TLV> mTLVs; std::size_t mNdefTlvIdx; std::vector<std::byte> mLockedArea; }; ================================================ FILE: src/Cafe/OS/libs/nfc/ndef.cpp ================================================ #include "ndef.h" #include <cassert> namespace ndef { Record::Record() : mFlags(0), mTNF(NDEF_TNF_EMPTY) { } Record::~Record() { } std::optional<Record> Record::FromStream(Stream& stream) { Record rec; // Read record header uint8 recHdr; stream >> recHdr; rec.mFlags = recHdr & ~NDEF_TNF_MASK; rec.mTNF = static_cast<TypeNameFormat>(recHdr & NDEF_TNF_MASK); // Type length uint8 typeLen; stream >> typeLen; // Payload length; uint32 payloadLen; if (recHdr & NDEF_SR) { uint8 len; stream >> len; payloadLen = len; } else { stream >> payloadLen; } // Some sane limits for the payload size if (payloadLen > 2 * 1024 * 1024) { return {}; } // ID length uint8 idLen = 0; if (recHdr & NDEF_IL) { stream >> idLen; } // Make sure we didn't read past the end of the stream yet if (stream.GetError() != Stream::ERROR_OK) { return {}; } // Type rec.mType.resize(typeLen); stream.Read(rec.mType); // ID rec.mID.resize(idLen); stream.Read(rec.mID); // Payload rec.mPayload.resize(payloadLen); stream.Read(rec.mPayload); // Make sure we didn't read past the end of the stream again if (stream.GetError() != Stream::ERROR_OK) { return {}; } return rec; } std::vector<std::byte> Record::ToBytes(uint8 flags) const { std::vector<std::byte> bytes; VectorStream stream(bytes, std::endian::big); // Combine flags (clear message begin and end flags) uint8 finalFlags = mFlags & ~(NDEF_MB | NDEF_ME); finalFlags |= flags; // Write flags + tnf stream << uint8(finalFlags | uint8(mTNF)); // Type length stream << uint8(mType.size()); // Payload length if (IsShort()) { stream << uint8(mPayload.size()); } else { stream << uint32(mPayload.size()); } // ID length if (mFlags & NDEF_IL) { stream << uint8(mID.size()); } // Type stream.Write(mType); // ID stream.Write(mID); // Payload stream.Write(mPayload); return bytes; } Record::TypeNameFormat Record::GetTNF() const { return mTNF; } const std::vector<std::byte>& Record::GetID() const { return mID; } const std::vector<std::byte>& Record::GetType() const { return mType; } const std::vector<std::byte>& Record::GetPayload() const { return mPayload; } void Record::SetTNF(TypeNameFormat tnf) { mTNF = tnf; } void Record::SetID(const std::span<const std::byte>& id) { cemu_assert(id.size() < 0x100); if (id.size() > 0) { mFlags |= NDEF_IL; } else { mFlags &= ~NDEF_IL; } mID.assign(id.begin(), id.end()); } void Record::SetType(const std::span<const std::byte>& type) { cemu_assert(type.size() < 0x100); mType.assign(type.begin(), type.end()); } void Record::SetPayload(const std::span<const std::byte>& payload) { // Update short record flag if (payload.size() < 0xff) { mFlags |= NDEF_SR; } else { mFlags &= ~NDEF_SR; } mPayload.assign(payload.begin(), payload.end()); } bool Record::IsLast() const { return mFlags & NDEF_ME; } bool Record::IsShort() const { return mFlags & NDEF_SR; } Message::Message() { } Message::~Message() { } std::optional<Message> Message::FromBytes(const std::span<const std::byte>& data) { Message msg; SpanStream stream(data, std::endian::big); while (stream.GetRemaining() > 0) { std::optional<Record> rec = Record::FromStream(stream); if (!rec) { cemuLog_log(LogType::Force, "Warning: Failed to parse NDEF Record #{}." "Ignoring the remaining {} bytes in NDEF message", msg.mRecords.size(), stream.GetRemaining()); break; } msg.mRecords.emplace_back(*rec); if ((*rec).IsLast() && stream.GetRemaining() > 0) { cemuLog_log(LogType::Force, "Warning: Ignoring {} bytes in NDEF message", stream.GetRemaining()); break; } } if (msg.mRecords.empty()) { return {}; } if (!msg.mRecords.back().IsLast()) { cemuLog_log(LogType::Force, "Error: NDEF message missing end record"); return {}; } return msg; } std::vector<std::byte> Message::ToBytes() const { std::vector<std::byte> bytes; for (std::size_t i = 0; i < mRecords.size(); i++) { uint8 flags = 0; // Add message begin flag to first record if (i == 0) { flags |= Record::NDEF_MB; } // Add message end flag to last record if (i == mRecords.size() - 1) { flags |= Record::NDEF_ME; } std::vector<std::byte> recordBytes = mRecords[i].ToBytes(flags); bytes.insert(bytes.end(), recordBytes.begin(), recordBytes.end()); } return bytes; } void Message::append(const Record& r) { mRecords.push_back(r); } } // namespace ndef ================================================ FILE: src/Cafe/OS/libs/nfc/ndef.h ================================================ #pragma once #include <span> #include <vector> #include <optional> #include "stream.h" namespace ndef { class Record { public: enum HeaderFlag { NDEF_IL = 0x08, NDEF_SR = 0x10, NDEF_CF = 0x20, NDEF_ME = 0x40, NDEF_MB = 0x80, NDEF_TNF_MASK = 0x07, }; enum TypeNameFormat { NDEF_TNF_EMPTY = 0, NDEF_TNF_WKT = 1, NDEF_TNF_MEDIA = 2, NDEF_TNF_URI = 3, NDEF_TNF_EXT = 4, NDEF_TNF_UNKNOWN = 5, NDEF_TNF_UNCHANGED = 6, NDEF_TNF_RESERVED = 7, }; public: Record(); virtual ~Record(); static std::optional<Record> FromStream(Stream& stream); std::vector<std::byte> ToBytes(uint8 flags = 0) const; TypeNameFormat GetTNF() const; const std::vector<std::byte>& GetID() const; const std::vector<std::byte>& GetType() const; const std::vector<std::byte>& GetPayload() const; void SetTNF(TypeNameFormat tnf); void SetID(const std::span<const std::byte>& id); void SetType(const std::span<const std::byte>& type); void SetPayload(const std::span<const std::byte>& payload); bool IsLast() const; bool IsShort() const; private: uint8 mFlags; TypeNameFormat mTNF; std::vector<std::byte> mID; std::vector<std::byte> mType; std::vector<std::byte> mPayload; }; class Message { public: Message(); virtual ~Message(); static std::optional<Message> FromBytes(const std::span<const std::byte>& data); std::vector<std::byte> ToBytes() const; Record& operator[](int i) { return mRecords[i]; } const Record& operator[](int i) const { return mRecords[i]; } void append(const Record& r); auto begin() { return mRecords.begin(); } auto end() { return mRecords.end(); } auto begin() const { return mRecords.begin(); } auto end() const { return mRecords.end(); } private: std::vector<Record> mRecords; }; } // namespace ndef ================================================ FILE: src/Cafe/OS/libs/nfc/nfc.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/nfc/nfc.h" #include "Cafe/OS/libs/nn_nfp/nn_nfp.h" #include "Common/FileStream.h" #include "TagV0.h" #include "ndef.h" #define NFC_MODE_INVALID -1 #define NFC_MODE_IDLE 0 #define NFC_MODE_ACTIVE 1 #define NFC_STATE_UNINITIALIZED 0x0 #define NFC_STATE_INITIALIZED 0x1 #define NFC_STATE_IDLE 0x2 #define NFC_STATE_READ 0x3 #define NFC_STATE_WRITE 0x4 #define NFC_STATE_ABORT 0x5 #define NFC_STATE_FORMAT 0x6 #define NFC_STATE_SET_READ_ONLY 0x7 #define NFC_STATE_TAG_PRESENT 0x8 #define NFC_STATE_DETECT 0x9 #define NFC_STATE_SEND_RAW_DATA 0xA #define NFC_STATUS_COMMAND_COMPLETE 0x1 #define NFC_STATUS_READY 0x2 #define NFC_STATUS_HAS_TAG 0x4 namespace nfc { struct NFCContext { bool isInitialized; uint32 state; sint32 mode; bool hasTag; uint32 nfcStatus; std::chrono::time_point<std::chrono::system_clock> touchTime; std::chrono::time_point<std::chrono::system_clock> discoveryTimeout; struct { NFCUid uid; NFCUid mask; } filter; MPTR tagDetectCallback; void* tagDetectContext; MPTR abortCallback; void* abortContext; MPTR rawCallback; void* rawContext; MPTR readCallback; void* readContext; MPTR writeCallback; void* writeContext; MPTR getTagInfoCallback; SysAllocator<NFCTagInfo> tagInfo; fs::path tagPath; std::shared_ptr<TagV0> tag; ndef::Message writeMessage; }; NFCContext gNFCContexts[2]; sint32 NFCInit(uint32 chan) { return NFCInitEx(chan, 0); } void __NFCClearContext(NFCContext* context) { context->isInitialized = false; context->state = NFC_STATE_UNINITIALIZED; context->mode = NFC_MODE_IDLE; context->hasTag = false; context->nfcStatus = NFC_STATUS_READY; context->discoveryTimeout = {}; context->tagDetectCallback = MPTR_NULL; context->tagDetectContext = nullptr; context->abortCallback = MPTR_NULL; context->abortContext = nullptr; context->rawCallback = MPTR_NULL; context->rawContext = nullptr; context->readCallback = MPTR_NULL; context->readContext = nullptr; context->writeCallback = MPTR_NULL; context->writeContext = nullptr; context->tagPath = ""; context->tag = {}; } sint32 NFCInitEx(uint32 chan, uint32 powerMode) { cemu_assert(chan < 2); NFCContext* ctx = &gNFCContexts[chan]; __NFCClearContext(ctx); ctx->isInitialized = true; ctx->state = NFC_STATE_INITIALIZED; return NFC_RESULT_SUCCESS; } sint32 NFCShutdown(uint32 chan) { cemu_assert(chan < 2); NFCContext* ctx = &gNFCContexts[chan]; __NFCClearContext(ctx); return NFC_RESULT_SUCCESS; } bool NFCIsInit(uint32 chan) { cemu_assert(chan < 2); return gNFCContexts[chan].isInitialized; } bool __NFCCompareUid(NFCUid* uid, NFCUid* filterUid, NFCUid* filterMask) { for (int i = 0; i < sizeof(uid->uid); i++) { if ((uid->uid[i] & filterMask->uid[i]) != filterUid->uid[i]) { return false; } } return true; } void __NFCHandleRead(uint32 chan) { NFCContext* ctx = &gNFCContexts[chan]; ctx->state = NFC_STATE_IDLE; sint32 result; StackAllocator<NFCUid> uid; bool readOnly = false; uint32 dataSize = 0; StackAllocator<uint8, 0x200> data; uint32 lockedDataSize = 0; StackAllocator<uint8, 0x200> lockedData; if (ctx->tag) { // Compare UID memcpy(uid.GetPointer(), ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); if (__NFCCompareUid(uid.GetPointer(), &ctx->filter.uid, &ctx->filter.mask)) { // Try to parse ndef message auto ndefMsg = ndef::Message::FromBytes(ctx->tag->GetNDEFData()); if (ndefMsg) { // Look for the unknown TNF which contains the data we care about for (const auto& rec : *ndefMsg) { if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) { dataSize = rec.GetPayload().size(); cemu_assert(dataSize < 0x200); memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); break; } } if (dataSize) { // Get locked data lockedDataSize = ctx->tag->GetLockedArea().size(); memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); result = NFC_RESULT_SUCCESS; } else { result = NFC_MAKE_RESULT(NFC_RESULT_BASE_TAG_PARSE, NFC_RESULT_INVALID_TAG); } } else { result = NFC_MAKE_RESULT(NFC_RESULT_BASE_TAG_PARSE, NFC_RESULT_INVALID_TAG); } } else { result = NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_UID_MISMATCH); } } else { result = NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_NO_TAG); } PPCCoreCallback(ctx->readCallback, chan, result, uid.GetPointer(), readOnly, dataSize, data.GetPointer(), lockedDataSize, lockedData.GetPointer(), ctx->readContext); } void __NFCHandleWrite(uint32 chan) { NFCContext* ctx = &gNFCContexts[chan]; ctx->state = NFC_STATE_IDLE; sint32 result; if (ctx->tag) { NFCUid uid; memcpy(&uid, ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); if (__NFCCompareUid(&uid, &ctx->filter.uid, &ctx->filter.mask)) { // Update tag NDEF data ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); // open file for writing FileStream* fs = FileStream::openFile2(ctx->tagPath, true); if (!fs) { result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, 0x22); } else { auto tagBytes = ctx->tag->ToBytes(); fs->writeData(tagBytes.data(), tagBytes.size()); delete fs; result = NFC_RESULT_SUCCESS; } } else { result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_UID_MISMATCH); } } else { result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_NO_TAG); } PPCCoreCallback(ctx->writeCallback, chan, result, ctx->writeContext); } void __NFCHandleAbort(uint32 chan) { NFCContext* ctx = &gNFCContexts[chan]; ctx->state = NFC_STATE_IDLE; PPCCoreCallback(ctx->abortCallback, chan, 0, ctx->abortContext); } void __NFCHandleRaw(uint32 chan) { NFCContext* ctx = &gNFCContexts[chan]; ctx->state = NFC_STATE_IDLE; sint32 result; if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) { result = NFC_RESULT_SUCCESS; } else { result = NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_NO_TAG); } // We don't actually send any commands/responses uint32 responseSize = 0; void* responseData = nullptr; PPCCoreCallback(ctx->rawCallback, chan, result, responseSize, responseData, ctx->rawContext); } bool __NFCShouldHandleState(NFCContext* ctx) { // Always handle abort if (ctx->state == NFC_STATE_ABORT) { return true; } // Do we have a tag? if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) { return true; } // Did the timeout expire? if (ctx->discoveryTimeout < std::chrono::system_clock::now()) { return true; } return false; } void NFCProc(uint32 chan) { cemu_assert(chan < 2); NFCContext* ctx = &gNFCContexts[chan]; if (!ctx->isInitialized) { return; } if (ctx->state == NFC_STATE_INITIALIZED) { ctx->state = NFC_STATE_IDLE; } // Check if the detect callback should be called if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) { if (!ctx->hasTag && ctx->state > NFC_STATE_IDLE && ctx->state != NFC_STATE_ABORT) { if (ctx->tagDetectCallback) { PPCCoreCallback(ctx->tagDetectCallback, chan, true, ctx->tagDetectContext); } ctx->hasTag = true; } // Check if the tag should be removed again if (ctx->touchTime + std::chrono::seconds(2) < std::chrono::system_clock::now()) { ctx->nfcStatus &= ~NFC_STATUS_HAS_TAG; ctx->tag = {}; ctx->tagPath = ""; } } else { if (ctx->hasTag && ctx->state == NFC_STATE_IDLE) { if (ctx->tagDetectCallback) { PPCCoreCallback(ctx->tagDetectCallback, chan, false, ctx->tagDetectContext); } ctx->hasTag = false; } } if (__NFCShouldHandleState(ctx)) { switch (ctx->state) { case NFC_STATE_READ: __NFCHandleRead(chan); break; case NFC_STATE_WRITE: __NFCHandleWrite(chan); break; case NFC_STATE_ABORT: __NFCHandleAbort(chan); break; case NFC_STATE_SEND_RAW_DATA: __NFCHandleRaw(chan); break; default: break; } // Return back to idle mode ctx->mode = NFC_MODE_IDLE; } } sint32 NFCGetMode(uint32 chan) { cemu_assert(chan < 2); NFCContext* ctx = &gNFCContexts[chan]; if (!NFCIsInit(chan) || ctx->state == NFC_STATE_UNINITIALIZED) { return NFC_MODE_INVALID; } return ctx->mode; } sint32 NFCSetMode(uint32 chan, sint32 mode) { cemu_assert(chan < 2); NFCContext* ctx = &gNFCContexts[chan]; if (!NFCIsInit(chan)) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_SET_MODE, NFC_RESULT_UNINITIALIZED); } if (ctx->state == NFC_STATE_UNINITIALIZED) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_SET_MODE, NFC_RESULT_INVALID_STATE); } ctx->mode = mode; return NFC_RESULT_SUCCESS; } void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context) { cemu_assert(chan < 2); NFCContext* ctx = &gNFCContexts[chan]; ctx->tagDetectCallback = callback; ctx->tagDetectContext = context; } sint32 NFCAbort(uint32 chan, MPTR callback, void* context) { cemu_assert(chan < 2); NFCContext* ctx = &gNFCContexts[chan]; if (!NFCIsInit(chan)) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_ABORT, NFC_RESULT_UNINITIALIZED); } if (ctx->state <= NFC_STATE_IDLE) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_ABORT, NFC_RESULT_INVALID_STATE); } ctx->state = NFC_STATE_ABORT; ctx->abortCallback = callback; ctx->abortContext = context; return NFC_RESULT_SUCCESS; } sint32 __NFCConvertGetTagInfoResult(sint32 result) { if (result == NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_NO_TAG)) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_GET_TAG_INFO, NFC_RESULT_TAG_INFO_TIMEOUT); } // TODO convert the rest of the results return result; } void __NFCGetTagInfoCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(chan, 0); ppcDefineParamS32(error, 1); ppcDefineParamU32(responseSize, 2); ppcDefineParamPtr(responseData, void, 3); ppcDefineParamPtr(context, void, 4); NFCContext* ctx = &gNFCContexts[chan]; error = __NFCConvertGetTagInfoResult(error); if (error == 0 && ctx->tag) { // this is usually parsed from response data ctx->tagInfo->uidSize = sizeof(NFCUid); memcpy(ctx->tagInfo->uid, ctx->tag->GetUIDBlock().data(), ctx->tagInfo->uidSize); ctx->tagInfo->technology = NFC_TECHNOLOGY_A; ctx->tagInfo->protocol = NFC_PROTOCOL_T1T; } PPCCoreCallback(ctx->getTagInfoCallback, chan, error, ctx->tagInfo.GetPtr(), context); osLib_returnFromFunction(hCPU, 0); } sint32 NFCGetTagInfo(uint32 chan, uint32 discoveryTimeout, MPTR callback, void* context) { cemu_assert(chan < 2); // Forward this request to nn_nfp, if the title initialized it // TODO integrate nn_nfp/ntag/nfc if (nnNfp_isInitialized()) { return nn::nfp::NFCGetTagInfo(chan, discoveryTimeout, callback, context); } NFCContext* ctx = &gNFCContexts[chan]; ctx->getTagInfoCallback = callback; sint32 result = NFCSendRawData(chan, true, discoveryTimeout, 1000U, 0, 0, nullptr, RPLLoader_MakePPCCallable(__NFCGetTagInfoCallback), context); return __NFCConvertGetTagInfoResult(result); } sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context) { cemu_assert(chan < 2); NFCContext* ctx = &gNFCContexts[chan]; if (!NFCIsInit(chan)) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_UNINITIALIZED); } // Only allow discovery if (!startDiscovery) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_MODE); } if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_MODE); } if (ctx->state != NFC_STATE_IDLE) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_STATE); } ctx->state = NFC_STATE_SEND_RAW_DATA; ctx->rawCallback = callback; ctx->rawContext = context; // If the discoveryTimeout is 0, no timeout if (discoveryTimeout == 0) { ctx->discoveryTimeout = std::chrono::time_point<std::chrono::system_clock>::max(); } else { ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); } return NFC_RESULT_SUCCESS; } sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context) { cemu_assert(chan < 2); NFCContext* ctx = &gNFCContexts[chan]; if (!NFCIsInit(chan)) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_UNINITIALIZED); } if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_INVALID_MODE); } if (ctx->state != NFC_STATE_IDLE) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_INVALID_STATE); } ctx->state = NFC_STATE_READ; ctx->readCallback = callback; ctx->readContext = context; // If the discoveryTimeout is 0, no timeout if (discoveryTimeout == 0) { ctx->discoveryTimeout = std::chrono::time_point<std::chrono::system_clock>::max(); } else { ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); } memcpy(&ctx->filter.uid, uid, sizeof(*uid)); memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); return NFC_RESULT_SUCCESS; } sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context) { cemu_assert(chan < 2); NFCContext* ctx = &gNFCContexts[chan]; if (!NFCIsInit(chan)) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_UNINITIALIZED); } if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_INVALID_MODE); } if (ctx->state != NFC_STATE_IDLE) { return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_INVALID_STATE); } // Create unknown record which contains the rw area ndef::Record rec; rec.SetTNF(ndef::Record::NDEF_TNF_UNKNOWN); rec.SetPayload(std::span(reinterpret_cast<std::byte*>(data), size)); // Create ndef message which contains the record ndef::Message msg; msg.append(rec); ctx->writeMessage = msg; ctx->state = NFC_STATE_WRITE; ctx->writeCallback = callback; ctx->writeContext = context; // If the discoveryTimeout is 0, no timeout if (discoveryTimeout == 0) { ctx->discoveryTimeout = std::chrono::time_point<std::chrono::system_clock>::max(); } else { ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); } memcpy(&ctx->filter.uid, uid, sizeof(*uid)); memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); return NFC_RESULT_SUCCESS; } class : public COSModule { public: std::string_view GetName() override { return "nfc"; } void RPLMapped() override { cafeExportRegister("nfc", NFCInit, LogType::NFC); cafeExportRegister("nfc", NFCInitEx, LogType::NFC); cafeExportRegister("nfc", NFCShutdown, LogType::NFC); cafeExportRegister("nfc", NFCIsInit, LogType::NFC); cafeExportRegister("nfc", NFCProc, LogType::NFC); cafeExportRegister("nfc", NFCGetMode, LogType::NFC); cafeExportRegister("nfc", NFCSetMode, LogType::NFC); cafeExportRegister("nfc", NFCSetTagDetectCallback, LogType::NFC); cafeExportRegister("nfc", NFCGetTagInfo, LogType::NFC); cafeExportRegister("nfc", NFCSendRawData, LogType::NFC); cafeExportRegister("nfc", NFCAbort, LogType::NFC); cafeExportRegister("nfc", NFCRead, LogType::NFC); cafeExportRegister("nfc", NFCWrite, LogType::NFC); }; }s_COSnfcModule; COSModule* GetModule() { return &s_COSnfcModule; } bool TouchTagFromFile(const fs::path& filePath, uint32* nfcError) { // Forward this request to nn_nfp, if the title initialized it // TODO integrate nn_nfp/ntag/nfc if (nnNfp_isInitialized()) { return nnNfp_touchNfcTagFromFile(filePath, nfcError); } NFCContext* ctx = &gNFCContexts[0]; auto nfcData = FileStream::LoadIntoMemory(filePath); if (!nfcData) { *nfcError = NFC_TOUCH_TAG_ERROR_NO_ACCESS; return false; } ctx->tag = TagV0::FromBytes(std::as_bytes(std::span(nfcData->data(), nfcData->size()))); if (!ctx->tag) { *nfcError = NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT; return false; } ctx->nfcStatus |= NFC_STATUS_HAS_TAG; ctx->tagPath = filePath; ctx->touchTime = std::chrono::system_clock::now(); *nfcError = NFC_TOUCH_TAG_ERROR_NONE; return true; } } ================================================ FILE: src/Cafe/OS/libs/nfc/nfc.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" // CEMU NFC error codes #define NFC_TOUCH_TAG_ERROR_NONE (0) #define NFC_TOUCH_TAG_ERROR_NO_ACCESS (1) #define NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT (2) // NFC result base #define NFC_RESULT_BASE_INIT (-0x100) #define NFC_RESULT_BASE_READ (-0x200) #define NFC_RESULT_BASE_WRITE (-0x300) #define NFC_RESULT_BASE_FORMAT (-0x400) #define NFC_RESULT_BASE_SET_READ_ONLY (-0x500) #define NFC_RESULT_BASE_IS_TAG_PRESENT (-0x600) #define NFC_RESULT_BASE_ABORT (-0x700) #define NFC_RESULT_BASE_SHUTDOWN (-0x800) #define NFC_RESULT_BASE_DETECT (-0x900) #define NFC_RESULT_BASE_SEND_RAW_DATA (-0xA00) #define NFC_RESULT_BASE_SET_MODE (-0xB00) #define NFC_RESULT_BASE_TAG_PARSE (-0xC00) #define NFC_RESULT_BASE_GET_TAG_INFO (-0x1400) // NFC result status #define NFC_RESULT_NO_TAG (0x01) #define NFC_RESULT_INVALID_TAG (0x02) #define NFC_RESULT_UID_MISMATCH (0x0A) #define NFC_RESULT_UNINITIALIZED (0x20) #define NFC_RESULT_INVALID_STATE (0x21) #define NFC_RESULT_INVALID_MODE (0x24) #define NFC_RESULT_TAG_INFO_TIMEOUT (0x7A) // Result macros #define NFC_RESULT_SUCCESS (0) #define NFC_RESULT_BASE_MASK (0xFFFFFF00) #define NFC_RESULT_MASK (0x000000FF) #define NFC_MAKE_RESULT(base, result) ((base) | (result)) #define NFC_PROTOCOL_T1T 0x1 #define NFC_PROTOCOL_T2T 0x2 #define NFC_TECHNOLOGY_A 0x0 #define NFC_TECHNOLOGY_B 0x1 #define NFC_TECHNOLOGY_F 0x2 namespace nfc { struct NFCUid { /* +0x00 */ uint8 uid[7]; }; static_assert(sizeof(NFCUid) == 0x7); struct NFCTagInfo { /* +0x00 */ uint8 uidSize; /* +0x01 */ uint8 uid[10]; /* +0x0B */ uint8 technology; /* +0x0C */ uint8 protocol; /* +0x0D */ uint8 reserved[0x20]; }; static_assert(sizeof(NFCTagInfo) == 0x2D); sint32 NFCInit(uint32 chan); sint32 NFCInitEx(uint32 chan, uint32 powerMode); sint32 NFCShutdown(uint32 chan); bool NFCIsInit(uint32 chan); void NFCProc(uint32 chan); sint32 NFCGetMode(uint32 chan); sint32 NFCSetMode(uint32 chan, sint32 mode); void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context); sint32 NFCGetTagInfo(uint32 chan, uint32 discoveryTimeout, MPTR callback, void* context); sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context); sint32 NFCAbort(uint32 chan, MPTR callback, void* context); sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context); sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context); COSModule* GetModule(); bool TouchTagFromFile(const fs::path& filePath, uint32* nfcError); } ================================================ FILE: src/Cafe/OS/libs/nfc/stream.cpp ================================================ #include "stream.h" #include <algorithm> Stream::Stream(std::endian endianness) : mError(ERROR_OK), mEndianness(endianness) { } Stream::~Stream() { } Stream::Error Stream::GetError() const { return mError; } void Stream::SetEndianness(std::endian endianness) { mEndianness = endianness; } std::endian Stream::GetEndianness() const { return mEndianness; } Stream& Stream::operator>>(bool& val) { uint8 i; *this >> i; val = !!i; return *this; } Stream& Stream::operator>>(float& val) { uint32 i; *this >> i; val = std::bit_cast<float>(i); return *this; } Stream& Stream::operator>>(double& val) { uint64 i; *this >> i; val = std::bit_cast<double>(i); return *this; } Stream& Stream::operator<<(bool val) { uint8 i = val; *this >> i; return *this; } Stream& Stream::operator<<(float val) { uint32 i = std::bit_cast<uint32>(val); *this >> i; return *this; } Stream& Stream::operator<<(double val) { uint64 i = std::bit_cast<uint64>(val); *this >> i; return *this; } void Stream::SetError(Error error) { mError = error; } bool Stream::NeedsSwap() { return mEndianness != std::endian::native; } VectorStream::VectorStream(std::vector<std::byte>& vector, std::endian endianness) : Stream(endianness), mVector(vector), mPosition(0) { } VectorStream::~VectorStream() { } std::size_t VectorStream::Read(const std::span<std::byte>& data) { if (data.size() > GetRemaining()) { SetError(ERROR_READ_FAILED); std::fill(data.begin(), data.end(), std::byte(0)); return 0; } std::copy_n(mVector.get().begin() + mPosition, data.size(), data.begin()); mPosition += data.size(); return data.size(); } std::size_t VectorStream::Write(const std::span<const std::byte>& data) { // Resize vector if not enough bytes remain if (mPosition + data.size() > mVector.get().size()) { mVector.get().resize(mPosition + data.size()); } std::copy(data.begin(), data.end(), mVector.get().begin() + mPosition); mPosition += data.size(); return data.size(); } bool VectorStream::SetPosition(std::size_t position) { if (position >= mVector.get().size()) { return false; } mPosition = position; return true; } std::size_t VectorStream::GetPosition() const { return mPosition; } std::size_t VectorStream::GetRemaining() const { return mVector.get().size() - mPosition; } SpanStream::SpanStream(std::span<const std::byte> span, std::endian endianness) : Stream(endianness), mSpan(std::move(span)), mPosition(0) { } SpanStream::~SpanStream() { } std::size_t SpanStream::Read(const std::span<std::byte>& data) { if (data.size() > GetRemaining()) { SetError(ERROR_READ_FAILED); std::fill(data.begin(), data.end(), std::byte(0)); return 0; } std::copy_n(mSpan.begin() + mPosition, data.size(), data.begin()); mPosition += data.size(); return data.size(); } std::size_t SpanStream::Write(const std::span<const std::byte>& data) { // Cannot write to const span SetError(ERROR_WRITE_FAILED); return 0; } bool SpanStream::SetPosition(std::size_t position) { if (position >= mSpan.size()) { return false; } mPosition = position; return true; } std::size_t SpanStream::GetPosition() const { return mPosition; } std::size_t SpanStream::GetRemaining() const { if (mPosition > mSpan.size()) { return 0; } return mSpan.size() - mPosition; } ================================================ FILE: src/Cafe/OS/libs/nfc/stream.h ================================================ #pragma once #include <cstdint> #include <vector> #include <span> #include <bit> #include "Common/precompiled.h" class Stream { public: enum Error { ERROR_OK, ERROR_READ_FAILED, ERROR_WRITE_FAILED, }; public: Stream(std::endian endianness = std::endian::native); virtual ~Stream(); Error GetError() const; void SetEndianness(std::endian endianness); std::endian GetEndianness() const; virtual std::size_t Read(const std::span<std::byte>& data) = 0; virtual std::size_t Write(const std::span<const std::byte>& data) = 0; virtual bool SetPosition(std::size_t position) = 0; virtual std::size_t GetPosition() const = 0; virtual std::size_t GetRemaining() const = 0; // Stream read operators template<std::integral T> Stream& operator>>(T& val) { val = 0; if (Read(std::as_writable_bytes(std::span(std::addressof(val), 1))) == sizeof(val)) { if (NeedsSwap()) { if (sizeof(T) == 2) { val = _swapEndianU16(val); } else if (sizeof(T) == 4) { val = _swapEndianU32(val); } else if (sizeof(T) == 8) { val = _swapEndianU64(val); } } } return *this; } Stream& operator>>(bool& val); Stream& operator>>(float& val); Stream& operator>>(double& val); // Stream write operators template<std::integral T> Stream& operator<<(T val) { if (NeedsSwap()) { if (sizeof(T) == 2) { val = _swapEndianU16(val); } else if (sizeof(T) == 4) { val = _swapEndianU32(val); } else if (sizeof(T) == 8) { val = _swapEndianU64(val); } } Write(std::as_bytes(std::span(std::addressof(val), 1))); return *this; } Stream& operator<<(bool val); Stream& operator<<(float val); Stream& operator<<(double val); protected: void SetError(Error error); bool NeedsSwap(); Error mError; std::endian mEndianness; }; class VectorStream : public Stream { public: VectorStream(std::vector<std::byte>& vector, std::endian endianness = std::endian::native); virtual ~VectorStream(); virtual std::size_t Read(const std::span<std::byte>& data) override; virtual std::size_t Write(const std::span<const std::byte>& data) override; virtual bool SetPosition(std::size_t position) override; virtual std::size_t GetPosition() const override; virtual std::size_t GetRemaining() const override; private: std::reference_wrapper<std::vector<std::byte>> mVector; std::size_t mPosition; }; class SpanStream : public Stream { public: SpanStream(std::span<const std::byte> span, std::endian endianness = std::endian::native); virtual ~SpanStream(); virtual std::size_t Read(const std::span<std::byte>& data) override; virtual std::size_t Write(const std::span<const std::byte>& data) override; virtual bool SetPosition(std::size_t position) override; virtual std::size_t GetPosition() const override; virtual std::size_t GetRemaining() const override; private: std::span<const std::byte> mSpan; std::size_t mPosition; }; ================================================ FILE: src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/common/OSUtil.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "nlibcurl.h" #include "openssl/bn.h" #include "openssl/x509.h" #include "openssl/ssl.h" #define CURL_STRICTER #include "curl/curl.h" #include <unordered_map> #include <atomic> #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cafe/OS/libs/nsysnet/nsysnet.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "util/helpers/ConcurrentQueue.h" #include "Cafe/OS/common/PPCConcurrentQueue.h" #include "Common/FileStream.h" #include "config/ActiveSettings.h" namespace nlibcurl { #define CURL_MULTI_HANDLE (0x000bab1e) #define NSSL_VERIFY_NONE (0x0) #define NSSL_VERIFY_PEER (1<<0) #define NSSL_VERIFY_HOSTNAME (1<<1) #define NSSL_VERIFY_DATE (1<<2) enum QueueOrder { QueueOrder_None, QueueOrder_Result, QueueOrder_CBDone, QueueOrder_HeaderCB, QueueOrder_ReadCB, QueueOrder_WriteCB, QueueOrder_ProgressCB, QueueOrder_Perform, QueueOrder_Pause, }; struct QueueMsg_t { QueueOrder order; union { uint32 result; struct { char* buffer; uint32 size; uint32 nitems; } header_cb; struct { char* buffer; uint32 size; uint32 nitems; } read_cb; struct { char* buffer; uint32 size; uint32 nmemb; } write_cb; struct { uint32 clientp; double dltotal; double dlnow; double ultotal; double ulnow; } progress_cb; struct { sint32 bitmask; } pause; }; }; struct MEMPTRHash_t { size_t operator()(const MEMPTR<void>& p) const { return p.GetMPTR(); } }; struct WU_curl_slist { MEMPTR<char> data; MEMPTR<WU_curl_slist> next; }; enum class WU_CURLcode { placeholder = 0, }; struct { sint32 initialized; MEMPTR<void> proxyConfig; MEMPTR<curl_malloc_callback> malloc; MEMPTR<curl_free_callback> free; MEMPTR<curl_realloc_callback> realloc; MEMPTR<curl_strdup_callback> strdup; MEMPTR<curl_calloc_callback> calloc; } g_nlibcurl = {}; using WU_CURL_off_t = uint64be; enum class WU_HTTPREQ : uint32 { HTTPREQ_GET = 0x1, HTTPREQ_POST = 0x2, UKN_3 = 0x3, }; struct WU_UserDefined { // starting at 0xD8 (probably) in CURL_t /* 0x0D8 / +0x00 */ uint32be ukn0D8; /* 0x0DC / +0x04 */ uint32be ukn0DC; /* 0x0E0 / +0x08 */ MEMPTR<WU_curl_slist> headers; /* 0x0E4 / +0x0C */ uint32be ukn0E4; /* 0x0E8 / +0x10 */ uint32be ukn0E8; /* 0x0EC / +0x14 */ uint32be ukn0EC; /* 0x0F0 / +0x18 */ uint32be ukn0F0[4]; /* 0x100 / +0x28 */ uint32be ukn100[4]; /* 0x110 / +0x38 */ uint32be ukn110[4]; // +0x40 -> WU_CURL_off_t postfieldsize ? /* 0x120 / +0x48 */ uint32be ukn120[4]; /* 0x130 / +0x58 */ uint32be ukn130[4]; /* 0x140 / +0x68 */ uint32be ukn140[4]; /* 0x150 / +0x78 */ uint32be ukn150[4]; /* 0x160 / +0x88 */ uint32be ukn160[4]; /* 0x170 / +0x98 */ uint32be ukn170[4]; /* 0x180 / +0xA8 */ uint32be ukn180[4]; /* 0x190 / +0xB0 */ sint64be infilesize_190{0}; /* 0x198 / +0xB8 */ uint32be ukn198; /* 0x19C / +0xBC */ uint32be ukn19C; /* 0x1A0 / +0xC8 */ uint32be ukn1A0[4]; /* 0x1B0 / +0xD8 */ uint32be ukn1B0[4]; /* 0x1C0 / +0xE8 */ uint32be ukn1C0[4]; /* 0x1D0 / +0xF8 */ uint32be ukn1D0[4]; /* 0x1E0 / +0x108 */ uint32be ukn1E0; /* 0x1E4 / +0x108 */ uint32be ukn1E4; /* 0x1E8 / +0x108 */ uint32be ukn1E8; /* 0x1EC / +0x108 */ betype<WU_HTTPREQ> httpreq_1EC; /* 0x1F0 / +0x118 */ uint32be ukn1F0[4]; void SetToDefault() { memset(this, 0, sizeof(WU_UserDefined)); httpreq_1EC = WU_HTTPREQ::HTTPREQ_GET; } }; struct CURL_t { CURL* curl; uint32be hNSSL; uint32be nsslVerifyOptions; MEMPTR<void> out; // CURLOPT_WRITEDATA MEMPTR<void> in_set; // CURLOPT_READDATA MEMPTR<void> writeheader; // CURLOPT_HEADERDATA MEMPTR<void> fwrite_func; // CURLOPT_WRITEFUNCTION MEMPTR<void> fwrite_header; // CURLOPT_HEADERFUNCTION MEMPTR<void> fread_func_set; // CURLOPT_READFUNCTION MEMPTR<void> progress_client; // CURLOPT_PROGRESSDATA MEMPTR<void> fprogress; // CURLOPT_PROGRESSFUNCTION MEMPTR<void> fsockopt; // CURLOPT_SOCKOPTFUNCTION MEMPTR<void> sockopt_client; // CURLOPT_SOCKOPTDATA // Cemu specific OSThread_t* curlThread; MEMPTR<char> info_redirectUrl; // stores CURLINFO_REDIRECT_URL ptr MEMPTR<char> info_contentType; // stores CURLINFO_CONTENT_TYPE ptr bool isDirty{true}; // debug struct { uint32 activeRequestIndex{}; uint32 responseRequestIndex{}; // set to activeRequestIndex once perform is called bool hasDumpedResultInfo{}; // set once the response info has been dumped (reset when next request is performed) FileStream* file_requestParam{}; FileStream* file_responseHeaders{}; FileStream* file_responseRaw{}; }debug; // fields below match the actual memory layout, above still needs refactoring /* 0x78 */ uint32be ukn078; /* 0x7C */ uint32be ukn07C; /* 0x80 */ uint32be ukn080; /* 0x84 */ uint32be ukn084; /* 0x88 */ uint32be ukn088; /* 0x8C */ uint32be ukn08C; /* 0x90 */ uint32be ukn090[4]; /* 0xA0 */ uint32be ukn0A0[4]; /* 0xB0 */ uint32be ukn0B0[4]; /* 0xC0 */ uint32be ukn0C0[4]; /* 0xD0 */ uint32be ukn0D0; /* 0xD4 */ uint32be ukn0D4; /* 0xD8 */ WU_UserDefined set; /* 0x200 */ uint32be ukn200[4]; /* 0x210 */ uint32be ukn210[4]; /* 0x220 */ uint32be ukn220[4]; /* 0x230 */ uint32be ukn230[4]; /* 0x240 */ uint32be ukn240[4]; /* 0x250 */ uint32be ukn250[4]; /* 0x260 */ uint32be ukn260[4]; /* 0x270 */ uint32be ukn270[4]; /* 0x280 */ uint8be ukn280; /* 0x281 */ uint8be opt_no_body_281; /* 0x282 */ uint8be ukn282; /* 0x283 */ uint8be upload_283; }; static_assert(sizeof(CURL_t) <= 0x8698); static_assert(offsetof(CURL_t, ukn078) == 0x78); static_assert(offsetof(CURL_t, set) == 0xD8); static_assert(offsetof(CURL_t, set) + offsetof(WU_UserDefined, headers) == 0xE0); static_assert(offsetof(CURL_t, set) + offsetof(WU_UserDefined, infilesize_190) == 0x190); static_assert(offsetof(CURL_t, set) + offsetof(WU_UserDefined, httpreq_1EC) == 0x1EC); static_assert(offsetof(CURL_t, opt_no_body_281) == 0x281); typedef MEMPTR<CURL_t> CURLPtr; #pragma pack(1) // may affect structs below, we can probably remove this but lets keep it for now as the code below is fragile typedef struct { //uint32be specifier; // 0x00 //uint32be dirty; // 0x04 MEMPTR<void> lockfunc; // 0x08 MEMPTR<void> unlockfunc; // 0x0C MEMPTR<void> data; // 0x10 //MEMPTR<void> uk14; // 0x14 //MEMPTR<void> uk18; // 0x18 CURLSH* curlsh; MEMPTR <CURL_t> curl; uint32 uk1; }CURLSH_t; // SCURL static_assert(sizeof(CURLSH_t) <= 0x1C); typedef MEMPTR<CURLSH_t> CURLSHPtr; typedef struct { CURLM* curlm; std::vector<MEMPTR<CURL_t>> curl; }CURLM_t; static_assert(sizeof(CURLM_t) <= 0x80, "sizeof(CURLM_t)"); typedef MEMPTR<CURLM_t> CURLMPtr; static_assert(sizeof(WU_curl_slist) <= 0x8, "sizeof(curl_slist_t)"); struct CURLMsg_t { uint32be msg; MEMPTR<CURL_t> curl; uint32be result; // CURLcode }; static_assert(sizeof(CURLMsg_t) <= 0xC, "sizeof(CURLMsg_t)"); #pragma pack() #include "nlibcurlDebug.hpp" size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata); thread_local PPCConcurrentQueue<QueueMsg_t>* g_callerQueue; thread_local ConcurrentQueue<QueueMsg_t>* g_threadQueue; void CurlWorkerThread(CURL_t* curl, PPCConcurrentQueue<QueueMsg_t>* callerQueue, ConcurrentQueue<QueueMsg_t>* threadQueue) { g_callerQueue = callerQueue; g_threadQueue = threadQueue; const QueueMsg_t msg = threadQueue->pop(); QueueMsg_t resultMsg = {}; resultMsg.order = QueueOrder_Result; if (msg.order == QueueOrder_Perform) resultMsg.result = ::curl_easy_perform(curl->curl); else if(msg.order == QueueOrder_Pause) resultMsg.result = ::curl_easy_pause(curl->curl, msg.pause.bitmask); else assert_dbg(); callerQueue->push(resultMsg, curl->curlThread); } uint32 SendOrderToWorker(CURL_t* curl, QueueOrder order, uint32 arg1 = 0) { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); curl->curlThread = currentThread; // cemuLog_logDebug(LogType::Force, "CURRENTTHREAD: 0x{} -> {}",currentThread, order) PPCConcurrentQueue<QueueMsg_t> callerQueue; ConcurrentQueue<QueueMsg_t> threadQueue; std::thread worker(CurlWorkerThread, curl, &callerQueue, &threadQueue); worker.detach(); QueueMsg_t orderMsg = {}; orderMsg.order = order; if (order == QueueOrder_Pause) orderMsg.pause.bitmask = arg1; threadQueue.push(orderMsg); uint32 result; while (true) { const QueueMsg_t msg = callerQueue.pop(currentThread); if (msg.order == QueueOrder_ProgressCB) { QueueMsg_t sendMsg = {}; sendMsg.order = QueueOrder_CBDone; sendMsg.result = PPCCoreCallback(curl->fprogress.GetMPTR(), curl->progress_client.GetMPTR(), msg.progress_cb.dltotal, msg.progress_cb.dlnow, msg.progress_cb.ultotal, msg.progress_cb.ulnow); threadQueue.push(sendMsg); } else if (msg.order == QueueOrder_HeaderCB) { StackAllocator<char> tmp(msg.header_cb.size * msg.header_cb.nitems); memcpy(tmp.GetPointer(), msg.header_cb.buffer, msg.header_cb.size * msg.header_cb.nitems); QueueMsg_t sendMsg = {}; sendMsg.order = QueueOrder_CBDone; sendMsg.result = PPCCoreCallback(curl->fwrite_header.GetMPTR(), tmp.GetMPTR(), msg.header_cb.size, msg.header_cb.nitems, curl->writeheader.GetMPTR()); threadQueue.push(sendMsg); } else if (msg.order == QueueOrder_ReadCB) { StackAllocator<char> tmp(msg.read_cb.size * msg.read_cb.nitems); QueueMsg_t sendMsg = {}; sendMsg.order = QueueOrder_CBDone; cemuLog_logDebug(LogType::Force, "QueueOrder_ReadCB size: {} nitems: {}", msg.read_cb.size, msg.read_cb.nitems); sendMsg.result = PPCCoreCallback(curl->fread_func_set.GetMPTR(), tmp.GetMPTR(), msg.read_cb.size, msg.read_cb.nitems, curl->in_set.GetMPTR()); cemuLog_logDebug(LogType::Force, "readcb size: {}", (sint32)sendMsg.result); if (sendMsg.result > 0) memcpy(msg.read_cb.buffer, tmp.GetPointer(), sendMsg.result); threadQueue.push(sendMsg); } else if (msg.order == QueueOrder_WriteCB) { StackAllocator<char> tmp(msg.write_cb.size * msg.write_cb.nmemb); memcpy(tmp.GetPointer(), msg.write_cb.buffer, msg.write_cb.size * msg.write_cb.nmemb); QueueMsg_t sendMsg = {}; sendMsg.order = QueueOrder_CBDone; sendMsg.result = PPCCoreCallback(curl->fwrite_func.GetMPTR(), tmp.GetMPTR(), msg.write_cb.size, msg.write_cb.nmemb, curl->out.GetMPTR()); threadQueue.push(sendMsg); } else if (msg.order == QueueOrder_Result) { result = msg.result; break; } } return result; } static int curl_closesocket(void *clientp, curl_socket_t item) { nsysnet_notifyCloseSharedSocket((SOCKET)item); closesocket(item); return 0; } void _curl_set_default_parameters(CURL_t* curl) { curl->set.SetToDefault(); // default parameters curl_easy_setopt(curl->curl, CURLOPT_HEADERFUNCTION, header_callback); curl_easy_setopt(curl->curl, CURLOPT_HEADERDATA, curl); curl_easy_setopt(curl->curl, CURLOPT_CLOSESOCKETFUNCTION, curl_closesocket); curl_easy_setopt(curl->curl, CURLOPT_CLOSESOCKETDATA, nullptr); } void _curl_sync_parameters(CURL_t* curl) { // sync ppc curl to actual curl state // not all parameters are covered yet, many are still set directly in easy_setopt bool isPost = curl->set.httpreq_1EC == WU_HTTPREQ::HTTPREQ_POST; // http request type if(curl->set.httpreq_1EC == WU_HTTPREQ::HTTPREQ_GET) { ::curl_easy_setopt(curl->curl, CURLOPT_HTTPGET, 1); cemu_assert_debug(curl->opt_no_body_281 == 0); cemu_assert_debug(curl->upload_283 == 0); } else if(curl->set.httpreq_1EC == WU_HTTPREQ::HTTPREQ_POST) { ::curl_easy_setopt(curl->curl, CURLOPT_POST, 1); cemu_assert_debug(curl->upload_283 == 0); ::curl_easy_setopt(curl->curl, CURLOPT_NOBODY, curl->opt_no_body_281 ? 1 : 0); } else { cemu_assert_unimplemented(); } // CURLOPT_HTTPHEADER std::optional<uint64> manualHeaderContentLength; if (curl->set.headers) { struct curl_slist* list = nullptr; WU_curl_slist* ppcList = curl->set.headers; while(ppcList) { if(isPost) { // for recent libcurl manually adding Content-Length header is undefined behavior. Instead CURLOPT_INFILESIZE(_LARGE) should be set // here we remove Content-Length and instead substitute it with CURLOPT_INFILESIZE (NEX DataStore in Super Mario Maker requires this) if(strncmp(ppcList->data.GetPtr(), "Content-Length:", 15) == 0) { manualHeaderContentLength = std::stoull(ppcList->data.GetPtr() + 15); ppcList = ppcList->next; continue; } } cemuLog_logDebug(LogType::Force, "curl_slist_append: {}", ppcList->data.GetPtr()); curlDebug_logEasySetOptStr(curl, "CURLOPT_HTTPHEADER", (const char*)ppcList->data.GetPtr()); list = ::curl_slist_append(list, ppcList->data.GetPtr()); ppcList = ppcList->next; } ::curl_easy_setopt(curl->curl, CURLOPT_HTTPHEADER, list); // todo - prevent leaking of list (maybe store in host curl object, similar to how our zlib implementation does stuff) } else ::curl_easy_setopt(curl->curl, CURLOPT_HTTPHEADER, nullptr); // infile size (post data size) if (curl->set.infilesize_190) { cemu_assert_debug(manualHeaderContentLength == 0); // should not have both? ::curl_easy_setopt(curl->curl, CURLOPT_INFILESIZE_LARGE, curl->set.infilesize_190); } else { if(isPost && manualHeaderContentLength > 0) ::curl_easy_setopt(curl->curl, CURLOPT_INFILESIZE_LARGE, manualHeaderContentLength); else ::curl_easy_setopt(curl->curl, CURLOPT_INFILESIZE_LARGE, 0); } } void export_malloc(PPCInterpreter_t* hCPU) { ppcDefineParamU32(size, 0); MPTR memAddr = PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeap, size); osLib_returnFromFunction(hCPU, memAddr); } void export_calloc(PPCInterpreter_t* hCPU) { ppcDefineParamU32(count, 0); ppcDefineParamU32(size, 1); MPTR memAddr = PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeap, count*size); osLib_returnFromFunction(hCPU, memAddr); } void export_free(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(mem, void, 0); PPCCoreCallback(gCoreinitData->MEMFreeToDefaultHeap, mem.GetMPTR()); osLib_returnFromFunction(hCPU, 0); } void export_strdup(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(str, const char, 0); int len = (int)strlen(str.GetPtr()) + 1; MEMPTR<char> result = (char*)coreinit::default_MEMAllocFromDefaultHeap(len); strcpy(result.GetPtr(), str.GetPtr()); osLib_returnFromFunction(hCPU, result.GetMPTR()); } void export_realloc(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(mem, void, 0); ppcDefineParamU32(size, 1); MEMPTR<void> result = coreinit::default_MEMAllocFromDefaultHeap(size); memcpy(result.GetPtr(), mem.GetPtr(), size); coreinit::default_MEMFreeToDefaultHeap(mem.GetPtr()); osLib_returnFromFunction(hCPU, result.GetMPTR()); } CURLcode curl_global_init(uint32 flags) { if (g_nlibcurl.initialized++) { return CURLE_OK; } g_nlibcurl.malloc = PPCInterpreter_makeCallableExportDepr(export_malloc); g_nlibcurl.calloc = PPCInterpreter_makeCallableExportDepr(export_calloc); g_nlibcurl.free = PPCInterpreter_makeCallableExportDepr(export_free); g_nlibcurl.strdup = PPCInterpreter_makeCallableExportDepr(export_strdup); g_nlibcurl.realloc = PPCInterpreter_makeCallableExportDepr(export_realloc); // curl_read_default_proxy_config() return ::curl_global_init(flags); } void export_curl_multi_init(PPCInterpreter_t* hCPU) { CURLMPtr result{ PPCCoreCallback(g_nlibcurl.calloc, 1, ppcsizeof<CURLM_t>()) }; cemuLog_logDebug(LogType::Force, "curl_multi_init() -> 0x{:08x}", result.GetMPTR()); if (result) { memset(result.GetPtr(), 0, sizeof(CURLM_t)); *result = {}; result->curlm = curl_multi_init(); } osLib_returnFromFunction(hCPU, result.GetMPTR()); } void export_curl_multi_add_handle(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curlm, CURLM_t, 0); ppcDefineParamMEMPTR(curl, CURL_t, 1); curlDebug_markActiveRequest(curl.GetPtr()); curlDebug_notifySubmitRequest(curl.GetPtr()); CURLMcode result = ::curl_multi_add_handle(curlm->curlm, curl->curl); if (result == CURLM_OK) curlm->curl.push_back(curl.GetPtr()); cemuLog_logDebug(LogType::Force, "curl_multi_add_handle(0x{:08x}, 0x{:08x}) -> 0x{:x}", curlm.GetMPTR(), curl.GetMPTR(), result); osLib_returnFromFunction(hCPU, result); } void export_curl_multi_remove_handle(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curlm, CURLM_t, 0); ppcDefineParamMEMPTR(curl, CURL_t, 1); CURLMcode result = curl_multi_remove_handle(curlm->curlm, curl->curl); if (result == CURLM_OK) { curlm->curl.erase(std::remove(curlm->curl.begin(), curlm->curl.end(), (void*)curl.GetPtr()), curlm->curl.end()); } cemuLog_logDebug(LogType::Force, "curl_multi_remove_handle(0x{:08x}, 0x{:08x}) -> 0x{:x}", curlm.GetMPTR(), curl.GetMPTR(), result); osLib_returnFromFunction(hCPU, result); } void export_curl_multi_timeout(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curlm, CURLM_t, 0); ppcDefineParamMEMPTR(timeoParam, uint32be, 1); long timeoutLE = (long)(uint32)*timeoParam; CURLMcode result = ::curl_multi_timeout(curlm->curlm, &timeoutLE); *timeoParam = (uint32)timeoutLE; cemuLog_logDebug(LogType::Force, "curl_multi_timeout(0x{:08x}, 0x{:08x} [{}]) -> 0x{:x}", curlm.GetMPTR(), timeoParam.GetMPTR(), timeoutLE, result); osLib_returnFromFunction(hCPU, result); } void export_curl_multi_cleanup(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curlm, CURLM_t, 0); CURLMcode result = ::curl_multi_cleanup(curlm->curlm); cemuLog_logDebug(LogType::Force, "curl_multi_cleanup(0x{:08x}) -> 0x{:x}", curlm.GetMPTR(), result); curlm->curl.clear(); PPCCoreCallback(g_nlibcurl.free.GetMPTR(), curlm.GetMPTR()); osLib_returnFromFunction(hCPU, result); } void export_curl_multi_perform(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curlm, CURLM_t, 0); ppcDefineParamMEMPTR(runningHandles, uint32be, 1); //cemuLog_logDebug(LogType::Force, "curl_multi_perform(0x{:08x}, 0x{:08x})", curlm.GetMPTR(), runningHandles.GetMPTR()); //curl_multi_get_handles(curlm->curlm); for(auto _curl : curlm->curl) { CURL_t* curl = (CURL_t*)_curl.GetPtr(); if(curl->isDirty) { curl->isDirty = false; _curl_sync_parameters(curl); } } //g_callerQueue = curlm->callerQueue; //g_threadQueue = curlm->threadQueue; int tempRunningHandles = 0; CURLMcode result = curl_multi_perform(curlm->curlm, &tempRunningHandles); *(runningHandles.GetPtr()) = tempRunningHandles; cemuLog_logDebug(LogType::Force, "curl_multi_perform(0x{:08x}, 0x{:08x}) -> 0x{:x} (running handles: {})", curlm.GetMPTR(), runningHandles.GetMPTR(), result, tempRunningHandles); //const uint32 result = SendOrderToWorker(curlm.GetPtr(), QueueOrder_MultiPerform); osLib_returnFromFunction(hCPU, result); } void export_curl_multi_fdset(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curlm, CURLM_t, 0); ppcDefineParamMEMPTR(readFd, wu_fd_set, 1); ppcDefineParamMEMPTR(writeFd, wu_fd_set, 2); ppcDefineParamMEMPTR(exceptionFd, wu_fd_set, 3); ppcDefineParamU32BEPtr(maxFd, 4); fd_set h_readFd; fd_set h_writeFd; fd_set h_exceptionFd; FD_ZERO(&h_readFd); FD_ZERO(&h_writeFd); FD_ZERO(&h_exceptionFd); sint32 h_maxFd = 0; CURLMcode result = curl_multi_fdset(curlm->curlm, &h_readFd, &h_writeFd, &h_exceptionFd, &h_maxFd); nsysnet::wuResetFD(readFd.GetPtr()); nsysnet::wuResetFD(writeFd.GetPtr()); nsysnet::wuResetFD(exceptionFd.GetPtr()); sint32 c_maxFD = -1; auto hostFdSet = [&](SOCKET s, wu_fd_set* fds) { sint32 wuSocket = nsysnet_getVirtualSocketHandleFromHostHandle(s); if (wuSocket < 0) wuSocket = nsysnet_createVirtualSocketFromExistingSocket(s); if (wuSocket >= 0) { c_maxFD = std::max(wuSocket, c_maxFD); nsysnet::wuSetFD(fds, wuSocket); } }; #if BOOST_OS_UNIX for (int s = 0; s < h_maxFd + 1; s++) { if(FD_ISSET(s, &h_readFd)) hostFdSet(s, readFd.GetPtr()); if(FD_ISSET(s, &h_writeFd)) hostFdSet(s, writeFd.GetPtr()); if(FD_ISSET(s, &h_exceptionFd)) hostFdSet(s, exceptionFd.GetPtr()); } #else // fd read set for (uint32 i = 0; i < h_readFd.fd_count; i++) { hostFdSet(h_readFd.fd_array[i], readFd.GetPtr()); } // fd write set for (uint32 i = 0; i < h_writeFd.fd_count; i++) { hostFdSet(h_writeFd.fd_array[i], writeFd.GetPtr()); } // fd exception set for (uint32 i = 0; i < h_exceptionFd.fd_count; i++) { cemu_assert_debug(false); } #endif *maxFd = c_maxFD; osLib_returnFromFunction(hCPU, result); } void export_curl_multi_setopt(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curlm, CURLM_t, 0); ppcDefineParamU32(option, 1); ppcDefineParamMEMPTR(parameter, void, 2); ppcDefineParamU64(parameterU64, 2); CURLMcode result = CURLM_OK; switch (option) { case CURLMOPT_MAXCONNECTS: result = ::curl_multi_setopt(curlm->curlm, (CURLMoption)option, parameter.GetMPTR()); break; default: cemuLog_logDebug(LogType::Force, "curl_multi_setopt(0x{:08x}, {}, 0x{:08x}) unsupported option", curlm.GetMPTR(), option, parameter.GetMPTR()); } cemuLog_logDebug(LogType::Force, "curl_multi_setopt(0x{:08x}, {}, 0x{:08x}) -> 0x{:x}", curlm.GetMPTR(), option, parameter.GetMPTR(), result); osLib_returnFromFunction(hCPU, result); } void export_curl_multi_info_read(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curlm, CURLM_t, 0); ppcDefineParamMEMPTR(msgsInQueue, int, 1); CURLMsg* msg = ::curl_multi_info_read(curlm->curlm, msgsInQueue.GetPtr()); cemuLog_logDebug(LogType::Force, "curl_multi_info_read - todo"); if (msg) { MEMPTR<CURLMsg_t> result{ PPCCoreCallback(g_nlibcurl.malloc.GetMPTR(), ppcsizeof<CURLMsg_t>()) }; result->msg = msg->msg; result->result = msg->data.result; if (msg->easy_handle) { const auto it = find_if(curlm->curl.cbegin(), curlm->curl.cend(), [msg](const MEMPTR<CURL_t>& curl) { const MEMPTR<CURL_t> _curl{ curl }; return _curl->curl = msg->easy_handle; }); if (it != curlm->curl.cend()) result->curl = (CURL_t*)(*it).GetPtr(); } else result->curl = nullptr; cemuLog_logDebug(LogType::Force, "curl_multi_info_read(0x{:08x}, 0x{:08x} [{}]) -> 0x{:08x} (0x{:x}, 0x{:08x}, 0x{:x})", curlm.GetMPTR(), msgsInQueue.GetMPTR(), *msgsInQueue.GetPtr(), result.GetMPTR(), msg->msg, result->curl.GetMPTR(), msg->data.result); osLib_returnFromFunction(hCPU, result.GetMPTR()); } else { osLib_returnFromFunction(hCPU, 0); } } void lock_function(CURL* handle, curl_lock_data data, curl_lock_access access, void* userptr) { CURLSH_t* share = (CURLSH_t*)userptr; PPCCoreCallback(share->lockfunc.GetMPTR(), share->curl.GetMPTR(), (uint32)data, (uint32)access, share->data.GetMPTR()); } void unlock_function(CURL* handle, curl_lock_data data, void* userptr) { CURLSH_t* share = (CURLSH_t*)userptr; PPCCoreCallback(share->unlockfunc.GetMPTR(), share->curl.GetMPTR(), (uint32)data, share->data.GetMPTR()); } void export_curl_share_setopt(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(share, CURLSH_t, 0); ppcDefineParamU32(option, 1); ppcDefineParamMEMPTR(parameter, void, 2); /*if(share->dirty) { osLib_returnFromFunction(hCPU, CURLSHE_IN_USE); return; }*/ CURLSH* curlSh = share->curlsh; CURLSHcode result = CURLSHE_OK; switch (option) { case CURLSHOPT_USERDATA: share->data = parameter; result = curl_share_setopt(curlSh, CURLSHOPT_USERDATA, share.GetPtr()); break; case CURLSHOPT_LOCKFUNC: share->lockfunc = parameter; result = curl_share_setopt(curlSh, CURLSHOPT_LOCKFUNC, lock_function); break; case CURLSHOPT_UNLOCKFUNC: share->unlockfunc = parameter; result = curl_share_setopt(curlSh, CURLSHOPT_UNLOCKFUNC, unlock_function); break; case CURLSHOPT_SHARE: { const sint32 type = parameter.GetMPTR(); result = curl_share_setopt(curlSh, CURLSHOPT_SHARE, type); break; } case CURLSHOPT_UNSHARE: { const sint32 type = parameter.GetMPTR(); result = curl_share_setopt(curlSh, CURLSHOPT_UNSHARE, type); break; } default: cemu_assert_unimplemented(); } cemuLog_logDebug(LogType::Force, "curl_share_setopt(0x{:08x}, 0x{:x}, 0x{:08x}) -> 0x{:x}", share.GetMPTR(), option, parameter.GetMPTR(), result); osLib_returnFromFunction(hCPU, result); } void export_curl_share_init(PPCInterpreter_t* hCPU) { CURLSHPtr result{ PPCCoreCallback(g_nlibcurl.calloc.GetMPTR(), (uint32)1, ppcsizeof<CURLSH_t>()) }; cemuLog_logDebug(LogType::Force, "curl_share_init() -> 0x{:08x}", result.GetMPTR()); if (result) { memset(result.GetPtr(), 0, sizeof(CURLSH_t)); *result = {}; result->curlsh = curl_share_init(); } osLib_returnFromFunction(hCPU, result.GetMPTR()); } void export_curl_share_cleanup(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curlsh, CURLSH_t, 0); uint32 result = ::curl_share_cleanup(curlsh->curlsh); cemuLog_logDebug(LogType::Force, "curl_share_cleanup(0x{:08x}) -> 0x{:x}", curlsh.GetMPTR(), result); PPCCoreCallback(g_nlibcurl.free.GetMPTR(), curlsh.GetMPTR()); osLib_returnFromFunction(hCPU, 0); } CURL_t* curl_easy_init() { if (g_nlibcurl.initialized == 0) { if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { return nullptr; } } // Curl_open MEMPTR<CURL_t> result{ PPCCoreCallback(g_nlibcurl.calloc.GetMPTR(), (uint32)1, ppcsizeof<CURL_t>()) }; cemuLog_logDebug(LogType::Force, "curl_easy_init() -> 0x{:08x}", result.GetMPTR()); if (result) { memset(result.GetPtr(), 0, sizeof(CURL_t)); *result = {}; result->curl = ::curl_easy_init(); result->curlThread = coreinit::OSGetCurrentThread(); result->info_contentType = nullptr; result->info_redirectUrl = nullptr; _curl_set_default_parameters(result.GetPtr()); if (g_nlibcurl.proxyConfig) { // todo } } return result; } CURL_t* mw_curl_easy_init() { return curl_easy_init(); } void export_curl_easy_pause(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curl, CURL_t, 0); ppcDefineParamS32(bitmask, 1); cemuLog_logDebug(LogType::Force, "curl_easy_pause(0x{:08x}, 0x{:x})", curl.GetMPTR(), bitmask); //const CURLcode result = ::curl_easy_pause(curl->curl, bitmask); const uint32 result = SendOrderToWorker(curl.GetPtr(), QueueOrder_Pause, bitmask); cemuLog_logDebug(LogType::Force, "curl_easy_pause(0x{:08x}, 0x{:x}) DONE", curl.GetMPTR(), bitmask); osLib_returnFromFunction(hCPU, result); } void export_curl_easy_cleanup(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curl, CURL_t, 0); cemuLog_logDebug(LogType::Force, "curl_easy_cleanup(0x{:08x})", curl.GetMPTR()); curlDebug_cleanup(curl.GetPtr()); ::curl_easy_cleanup(curl->curl); PPCCoreCallback(g_nlibcurl.free.GetMPTR(), curl.GetMPTR()); if (curl->info_contentType.IsNull() == false) PPCCoreCallback(g_nlibcurl.free.GetMPTR(), curl->info_contentType.GetMPTR()); if (curl->info_redirectUrl.IsNull() == false) PPCCoreCallback(g_nlibcurl.free.GetMPTR(), curl->info_redirectUrl.GetMPTR()); osLib_returnFromFunction(hCPU, 0); } void export_curl_easy_reset(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curl, CURL_t, 0); cemuLog_logDebug(LogType::Force, "curl_easy_reset(0x{:08x})", curl.GetMPTR()); // curl_apply_default_proxy_config(); ::curl_easy_reset(curl->curl); osLib_returnFromFunction(hCPU, 0); } int ssl_verify_callback(int preverify_ok, X509_STORE_CTX* ctx) { if (preverify_ok) return preverify_ok; int err = X509_STORE_CTX_get_error(ctx); if(err != 0) cemuLog_logDebug(LogType::Force, "ssl_verify_callback: Error {} but allow certificate anyway", err); X509_STORE_CTX_set_error(ctx, 0); return 1; } CURLcode ssl_ctx_callback(CURL* curl, void* sslctx, void* param) { //peterBreak(); CURL_t* ppcCurl = (CURL_t*)param; nsysnet::NSSLInternalState_t* nssl = nsysnet::GetNSSLContext((uint32)ppcCurl->hNSSL); for (uint32 i : nssl->serverPKIs) { if (iosuCrypto_addCACertificate(sslctx, i) == false) { cemu_assert_suspicious(); return CURLE_SSL_CACERT; } } for (auto& customPKI : nssl->serverCustomPKIs) { if (iosuCrypto_addCustomCACertificate(sslctx, &customPKI[0], (sint32)customPKI.size()) == false) { cemu_assert_suspicious(); return CURLE_SSL_CACERT; } } if (nssl->clientPKI != 0 && iosuCrypto_addClientCertificate(sslctx, nssl->clientPKI) == false) { cemu_assert_suspicious(); return CURLE_SSL_CERTPROBLEM; } uint32 flags = ppcCurl->nsslVerifyOptions; if (HAS_FLAG(flags, NSSL_VERIFY_PEER)) { ::SSL_CTX_set_cipher_list((SSL_CTX*)sslctx, "AES256-SHA"); // TLS_RSA_WITH_AES_256_CBC_SHA (in CURL it's called rsa_aes_256_sha) ::SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); ::SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); ::SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, ssl_verify_callback); } else { ::SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_NONE, nullptr); } return CURLE_OK; } size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata) { //peterBreak(); CURL_t* curl = (CURL_t*)userdata; curlDebug_headerWrite(curl, buffer, size, nitems); if (curl->fwrite_header.IsNull()) { return size * nitems; } if (g_callerQueue == nullptr || g_threadQueue == nullptr) { StackAllocator<char> tmp((uint32)(size * nitems)); memcpy(tmp.GetPointer(), buffer, size * nitems); return PPCCoreCallback(curl->fwrite_header.GetMPTR(), tmp.GetMPTR(), (uint32)size, (uint32)nitems, curl->writeheader.GetMPTR()); } QueueMsg_t msg = {}; msg.order = QueueOrder_HeaderCB; msg.header_cb.buffer = buffer; msg.header_cb.size = (uint32)size; msg.header_cb.nitems = (uint32)nitems; g_callerQueue->push(msg, curl->curlThread); msg = g_threadQueue->pop(); if (msg.order != QueueOrder_CBDone) cemu_assert_suspicious(); #ifdef CEMU_DEBUG_ASSERT char debug[500]; cemu_assert_debug((size*nitems) < 500); memcpy(debug, buffer, size*nitems); debug[size*nitems] = 0; cemuLog_logDebug(LogType::Force, "header_callback(0x{}, 0x{:x}, 0x{:x}, 0x{:08x}) [{}]", (void*)buffer, size, nitems, curl->writeheader.GetMPTR(), debug); #endif return msg.result; } size_t write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) { CURL_t* curl = (CURL_t*)userdata; curlDebug_resultWrite(curl, ptr, size, nmemb); //StackAllocator<char> tmp(size * nmemb); //memcpy(tmp.GetPointer(), ptr, size * nmemb); cemuLog_logDebug(LogType::Force, "write_callback(0x{} 0x{:x}, 0x{:x}, 0x{:08x})", (void*)ptr, size, nmemb, curl->out.GetMPTR()); if (g_callerQueue == nullptr || g_threadQueue == nullptr) { StackAllocator<char> tmp((uint32)(size * nmemb)); memcpy(tmp.GetPointer(), ptr, size * nmemb); int r = PPCCoreCallback(curl->fwrite_func.GetMPTR(), tmp.GetMPTR(), (uint32)size, (uint32)nmemb, curl->out.GetMPTR()); return r; } QueueMsg_t msg = {}; msg.order = QueueOrder_WriteCB; msg.write_cb.buffer = ptr; msg.write_cb.size = (uint32)size; msg.write_cb.nmemb = (uint32)nmemb; g_callerQueue->push(msg, curl->curlThread); msg = g_threadQueue->pop(); if (msg.order != QueueOrder_CBDone) cemu_assert_suspicious(); return msg.result; } int sockopt_callback(void* clientp, curl_socket_t curlfd, curlsocktype purpose) { CURL_t* curl = (CURL_t*)clientp; cemuLog_logDebug(LogType::Force, "sockopt_callback called!"); sint32 r = 0; return r; } size_t read_callback(char* buffer, size_t size, size_t nitems, void* instream) { nitems = std::min<uint32>(nitems, 0x4000); CURL_t* curl = (CURL_t*)instream; cemuLog_logDebug(LogType::Force, "read_callback(0x{}, 0x{:x}, 0x{:x}, 0x{:08x}) [func: 0x{:x}]", (void*)buffer, size, nitems, curl->in_set.GetMPTR(), curl->fread_func_set.GetMPTR()); if (g_callerQueue == nullptr || g_threadQueue == nullptr) { StackAllocator<char> tmp((uint32)(size * nitems)); const sint32 result = PPCCoreCallback(curl->fread_func_set.GetMPTR(), tmp.GetMPTR(), (uint32)size, (uint32)nitems, curl->in_set.GetMPTR()); memcpy(buffer, tmp.GetPointer(), result); return result; } QueueMsg_t msg = {}; msg.order = QueueOrder_ReadCB; msg.read_cb.buffer = buffer; msg.read_cb.size = (uint32)size; msg.read_cb.nitems = (uint32)std::min<uint32>(nitems, 0x4000); // limit this to 16KB which is the limit in nlibcurl.rpl (Super Mario Maker crashes on level upload if the size is too big) cemuLog_logDebug(LogType::Force, "read_callback(0x{}, 0x{:x}, 0x{:x}, 0x{:08x}) [func: 0x{:x}] PUSH", (void*)buffer, size, nitems, curl->in_set.GetMPTR(), curl->fread_func_set.GetMPTR()); g_callerQueue->push(msg, curl->curlThread); msg = g_threadQueue->pop(); if (msg.order != QueueOrder_CBDone) cemu_assert_suspicious(); cemuLog_logDebug(LogType::Force, "read_callback(0x{}, 0x{:x}, 0x{:x}, 0x{:08x}) DONE Result: {}", (void*)buffer, size, nitems, curl->in_set.GetMPTR(), msg.result); return msg.result; } int progress_callback(void* clientp, double dltotal, double dlnow, double ultotal, double ulnow) { //peterBreak(); CURL_t* curl = (CURL_t*)clientp; //int result = PPCCoreCallback(curl->fprogress.GetMPTR(), curl->progress_client.GetMPTR(), dltotal, dlnow, ultotal, ulnow); if(g_callerQueue == nullptr || g_threadQueue == nullptr) return PPCCoreCallback(curl->fprogress.GetMPTR(), curl->progress_client.GetMPTR(), dltotal, dlnow, ultotal, ulnow); QueueMsg_t msg = {}; msg.order = QueueOrder_ProgressCB; msg.progress_cb.dltotal = dltotal; msg.progress_cb.dlnow = dlnow; msg.progress_cb.ultotal = ultotal; msg.progress_cb.ulnow = ulnow; g_callerQueue->push(msg, curl->curlThread); msg = g_threadQueue->pop(); if (msg.order != QueueOrder_CBDone) cemu_assert_suspicious(); cemuLog_logDebug(LogType::Force, "progress_callback({:.02}, {:.02}, {:.02}, {:.02}) -> {}", dltotal, dlnow, ultotal, ulnow, msg.result); return msg.result; } void export_curl_easy_setopt(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curl, CURL_t, 0); ppcDefineParamU32(option, 1); ppcDefineParamMEMPTR(parameter, void, 2); ppcDefineParamU64(parameterU64, 2); CURL* curlObj = curl->curl; curl->isDirty = true; CURLcode result = CURLE_OK; switch (option) { case CURLOPT_POST: { if(parameter) { curl->set.httpreq_1EC = WU_HTTPREQ::HTTPREQ_POST; curl->opt_no_body_281 = 0; } else curl->set.httpreq_1EC = WU_HTTPREQ::HTTPREQ_GET; break; } case CURLOPT_HTTPGET: { if (parameter) { curl->set.httpreq_1EC = WU_HTTPREQ::HTTPREQ_GET; curl->opt_no_body_281 = 0; curl->upload_283 = 0; } break; } case CURLOPT_INFILESIZE: { curl->set.infilesize_190 = (sint64)(sint32)(uint32)parameter.GetBEValue(); break; } case CURLOPT_INFILESIZE_LARGE: { curl->set.infilesize_190 = (sint64)(uint64)parameterU64; break; } case CURLOPT_NOSIGNAL: case CURLOPT_FOLLOWLOCATION: case CURLOPT_BUFFERSIZE: case CURLOPT_TIMEOUT: case CURLOPT_CONNECTTIMEOUT_MS: case CURLOPT_NOPROGRESS: case CURLOPT_LOW_SPEED_LIMIT: case CURLOPT_LOW_SPEED_TIME: case CURLOPT_CONNECTTIMEOUT: { result = ::curl_easy_setopt(curlObj, (CURLoption)option, parameter.GetMPTR()); break; } case CURLOPT_URL: { curlDebug_logEasySetOptStr(curl.GetPtr(), "CURLOPT_URL", (const char*)parameter.GetPtr()); cemuLog_logDebug(LogType::Force, "curl_easy_setopt({}) [{}]", option, parameter.GetPtr()); result = ::curl_easy_setopt(curlObj, (CURLoption)option, parameter.GetPtr()); break; } case CURLOPT_PROXY: case CURLOPT_USERAGENT: { cemuLog_logDebug(LogType::Force, "curl_easy_setopt({}) [{}]", option, parameter.GetPtr()); result = ::curl_easy_setopt(curlObj, (CURLoption)option, parameter.GetPtr()); break; } case CURLOPT_POSTFIELDS: { curlDebug_logEasySetOptStr(curl.GetPtr(), "CURLOPT_POSTFIELDS", (const char*)parameter.GetPtr()); cemuLog_logDebug(LogType::Force, "curl_easy_setopt({}) [{}]", option, parameter.GetPtr()); result = ::curl_easy_setopt(curlObj, (CURLoption)option, parameter.GetPtr()); break; } case CURLOPT_MAX_SEND_SPEED_LARGE: case CURLOPT_MAX_RECV_SPEED_LARGE: case CURLOPT_POSTFIELDSIZE_LARGE: { result = ::curl_easy_setopt(curlObj, (CURLoption)option, parameterU64); break; } case 211: // verifyOpt { uint32 flags = parameter.GetMPTR(); curl->nsslVerifyOptions = flags; if (HAS_FLAG(flags, NSSL_VERIFY_PEER)) ::curl_easy_setopt(curlObj, CURLOPT_SSL_VERIFYPEER, 1); else ::curl_easy_setopt(curlObj, CURLOPT_SSL_VERIFYPEER, 0); if (HAS_FLAG(flags, NSSL_VERIFY_HOSTNAME)) ::curl_easy_setopt(curlObj, CURLOPT_SSL_VERIFYHOST, 2); else ::curl_easy_setopt(curlObj, CURLOPT_SSL_VERIFYHOST, 0); break; } case 210: // SSL_CONTEXT -> set context created with NSSLCreateContext before { const uint32 nsslIndex = parameter.GetMPTR(); nsysnet::NSSLInternalState_t* nssl = nsysnet::GetNSSLContext(nsslIndex); if (nssl) { curl->hNSSL = nsslIndex; result = ::curl_easy_setopt(curlObj, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback); ::curl_easy_setopt(curlObj, CURLOPT_SSL_CTX_DATA, curl.GetPtr()); if (nssl->sslVersion == 2) ::curl_easy_setopt(curlObj, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3); else // auto = highest = 0 || 2 for v3 ::curl_easy_setopt(curlObj, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); } else cemu_assert_suspicious(); break; } case CURLOPT_SHARE: { CURLSH* shObj = nullptr; if (parameter) { auto curlSh = (MEMPTR<CURLSH_t>)parameter; curlSh->curl = curl; shObj = curlSh->curlsh; } result = ::curl_easy_setopt(curlObj, CURLOPT_SHARE, shObj); break; } case CURLOPT_HEADERFUNCTION: { curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_HEADERFUNCTION", parameter.GetMPTR()); curl->fwrite_header = parameter; break; } case CURLOPT_HEADERDATA: { curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_HEADERDATA", parameter.GetMPTR()); curl->writeheader = parameter; break; } case CURLOPT_WRITEFUNCTION: { curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_WRITEFUNCTION", parameter.GetMPTR()); curl->fwrite_func = parameter; result = ::curl_easy_setopt(curlObj, CURLOPT_WRITEFUNCTION, write_callback); ::curl_easy_setopt(curlObj, CURLOPT_WRITEDATA, curl.GetPtr()); break; } case CURLOPT_WRITEDATA: // aka CURLOPT_FILE { curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_WRITEDATA", parameter.GetMPTR()); curl->out = parameter; break; } case CURLOPT_HTTPHEADER: { curl->set.headers = (WU_curl_slist*)parameter.GetPtr(); result = CURLE_OK; break; } case CURLOPT_SOCKOPTFUNCTION: { curl->fsockopt = parameter; result = ::curl_easy_setopt(curlObj, CURLOPT_SOCKOPTFUNCTION, sockopt_callback); ::curl_easy_setopt(curlObj, CURLOPT_SOCKOPTDATA, curl.GetPtr()); break; } case CURLOPT_SOCKOPTDATA: { curl->sockopt_client = parameter; break; } case CURLOPT_READFUNCTION: { curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_READFUNCTION", parameter.GetMPTR()); curl->fread_func_set = parameter; result = ::curl_easy_setopt(curlObj, CURLOPT_READFUNCTION, read_callback); ::curl_easy_setopt(curlObj, CURLOPT_READDATA, curl.GetPtr()); break; } case CURLOPT_READDATA: { curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_READDATA", parameter.GetMPTR()); curl->in_set = parameter; break; } case CURLOPT_PROGRESSFUNCTION: { curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_PROGRESSFUNCTION", parameter.GetMPTR()); curl->fprogress = parameter; result = ::curl_easy_setopt(curlObj, CURLOPT_PROGRESSFUNCTION, progress_callback); ::curl_easy_setopt(curlObj, CURLOPT_PROGRESSDATA, curl.GetPtr()); break; } case CURLOPT_PROGRESSDATA: { curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_PROGRESSDATA", parameter.GetMPTR()); curl->progress_client = parameter; break; } default: cemuLog_logDebug(LogType::Force, "curl_easy_setopt(0x{:08x}, {}, 0x{:08x}) unsupported option", curl.GetMPTR(), option, parameter.GetMPTR()); } cemuLog_logDebug(LogType::Force, "curl_easy_setopt(0x{:08x}, {}, 0x{:08x}) -> 0x{:x}", curl.GetMPTR(), option, parameter.GetMPTR(), result); osLib_returnFromFunction(hCPU, result); } WU_CURLcode curl_easy_perform(CURL_t* curl) { curlDebug_markActiveRequest(curl); curlDebug_notifySubmitRequest(curl); if(curl->isDirty) { curl->isDirty = false; _curl_sync_parameters(curl); } const uint32 result = SendOrderToWorker(curl, QueueOrder_Perform); return static_cast<WU_CURLcode>(result); } void _updateGuestString(CURL_t* curl, MEMPTR<char>& ppcStr, char* hostStr) { // free current string if (ppcStr.IsNull() == false) { PPCCoreCallback(g_nlibcurl.free.GetMPTR(), ppcStr); ppcStr = nullptr; } if (hostStr == nullptr) return; sint32 length = (sint32)strlen(hostStr); ppcStr = PPCCoreCallback(g_nlibcurl.malloc.GetMPTR(), length+1); memcpy(ppcStr.GetPtr(), hostStr, length + 1); } void export_curl_easy_getinfo(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(curl, CURL_t, 0); ppcDefineParamU32(info, 1); ppcDefineParamMEMPTR(parameter, void, 2); CURL* curlObj = curl->curl; CURLcode result = CURLE_OK; switch (info) { case CURLINFO_SIZE_DOWNLOAD: case CURLINFO_SPEED_DOWNLOAD: case CURLINFO_SIZE_UPLOAD: case CURLINFO_SPEED_UPLOAD: case CURLINFO_CONTENT_LENGTH_DOWNLOAD: { double tempDouble = 0.0; result = curl_easy_getinfo(curlObj, (CURLINFO)info, &tempDouble); *(uint64*)parameter.GetPtr() = _swapEndianU64(*(uint64*)&tempDouble); break; } case CURLINFO_RESPONSE_CODE: case CURLINFO_SSL_VERIFYRESULT: { long tempLong = 0; result = curl_easy_getinfo(curlObj, (CURLINFO)info, &tempLong); *(uint32*)parameter.GetPtr() = _swapEndianU32((uint32)tempLong); break; } case CURLINFO_CONTENT_TYPE: { char* contentType = nullptr; result = curl_easy_getinfo(curlObj, CURLINFO_REDIRECT_URL, &contentType); _updateGuestString(curl.GetPtr(), curl->info_contentType, contentType); *(MEMPTR<char>*)parameter.GetPtr() = curl->info_contentType; break; } case CURLINFO_REDIRECT_URL: { char* redirectUrl = nullptr; result = curl_easy_getinfo(curlObj, CURLINFO_REDIRECT_URL, &redirectUrl); _updateGuestString(curl.GetPtr(), curl->info_redirectUrl, redirectUrl); *(MEMPTR<char>*)parameter.GetPtr() = curl->info_redirectUrl; break; } default: cemu_assert_unimplemented(); result = curl_easy_getinfo(curlObj, (CURLINFO)info, (double*)parameter.GetPtr()); } cemuLog_logDebug(LogType::Force, "curl_easy_getinfo(0x{:08x}, 0x{:x}, 0x{:08x}) -> 0x{:x}", curl.GetMPTR(), info, parameter.GetMPTR(), result); osLib_returnFromFunction(hCPU, result); } void export_curl_easy_strerror(PPCInterpreter_t* hCPU) { ppcDefineParamU32(code, 0); MEMPTR<char> result; const char* error = curl_easy_strerror((CURLcode)code); if (error) { cemuLog_logDebug(LogType::Force, "curl_easy_strerror: {}", error); int len = (int)strlen(error) + 1; result = coreinit_allocFromSysArea(len, 4); memcpy(result.GetPtr(), error, len); } osLib_returnFromFunction(hCPU, result.GetMPTR()); } WU_curl_slist* curl_slist_append(WU_curl_slist* list, const char* data) { MEMPTR<char> dupdata{ PPCCoreCallback(g_nlibcurl.strdup.GetMPTR(), data) }; if (!dupdata) { cemuLog_logDebug(LogType::Force, "curl_slist_append(): Failed to duplicate string"); return nullptr; } MEMPTR<WU_curl_slist> result{ PPCCoreCallback(g_nlibcurl.malloc.GetMPTR(), ppcsizeof<WU_curl_slist>()) }; if (result) { result->data = dupdata; result->next = nullptr; // update last obj of list if (list) { MEMPTR<WU_curl_slist> tmp = list; while (tmp->next) { tmp = tmp->next; } tmp->next = result; } } else { cemuLog_logDebug(LogType::Force, "curl_slist_append(): Failed to allocate memory"); PPCCoreCallback(g_nlibcurl.free.GetMPTR(), dupdata.GetMPTR()); } if(list) return list; return result; } void curl_slist_free_all(WU_curl_slist* list) { cemuLog_logDebug(LogType::Force, "export_curl_slist_free_all: TODO"); } CURLcode curl_global_init_mem(uint32 flags, MEMPTR<curl_malloc_callback> malloc_callback, MEMPTR<curl_free_callback> free_callback, MEMPTR<curl_realloc_callback> realloc_callback, MEMPTR<curl_strdup_callback> strdup_callback, MEMPTR<curl_calloc_callback> calloc_callback) { if(!malloc_callback || !free_callback || !realloc_callback || !strdup_callback || !calloc_callback) return CURLE_FAILED_INIT; CURLcode result = CURLE_OK; if (g_nlibcurl.initialized == 0) { result = curl_global_init(flags); if (result == CURLE_OK) { g_nlibcurl.malloc = malloc_callback; g_nlibcurl.free = free_callback; g_nlibcurl.realloc = realloc_callback; g_nlibcurl.strdup = strdup_callback; g_nlibcurl.calloc = calloc_callback; } } return result; } class : public COSModule { public: std::string_view GetName() override { return "nlibcurl"; } void RPLMapped() override { cafeExportRegister("nlibcurl", curl_global_init_mem, LogType::nlibcurl); cafeExportRegister("nlibcurl", curl_global_init, LogType::nlibcurl); cafeExportRegister("nlibcurl", curl_slist_append, LogType::nlibcurl); cafeExportRegister("nlibcurl", curl_slist_free_all, LogType::nlibcurl); osLib_addFunction("nlibcurl", "curl_easy_strerror", export_curl_easy_strerror); osLib_addFunction("nlibcurl", "curl_share_init", export_curl_share_init); osLib_addFunction("nlibcurl", "curl_share_setopt", export_curl_share_setopt); osLib_addFunction("nlibcurl", "curl_share_cleanup", export_curl_share_cleanup); cafeExportRegister("nlibcurl", mw_curl_easy_init, LogType::nlibcurl); osLib_addFunction("nlibcurl", "curl_multi_init", export_curl_multi_init); osLib_addFunction("nlibcurl", "curl_multi_add_handle", export_curl_multi_add_handle); osLib_addFunction("nlibcurl", "curl_multi_perform", export_curl_multi_perform); osLib_addFunction("nlibcurl", "curl_multi_info_read", export_curl_multi_info_read); osLib_addFunction("nlibcurl", "curl_multi_remove_handle", export_curl_multi_remove_handle); osLib_addFunction("nlibcurl", "curl_multi_setopt", export_curl_multi_setopt); osLib_addFunction("nlibcurl", "curl_multi_fdset", export_curl_multi_fdset); osLib_addFunction("nlibcurl", "curl_multi_cleanup", export_curl_multi_cleanup); osLib_addFunction("nlibcurl", "curl_multi_timeout", export_curl_multi_timeout); cafeExportRegister("nlibcurl", curl_easy_init, LogType::nlibcurl); osLib_addFunction("nlibcurl", "curl_easy_reset", export_curl_easy_reset); osLib_addFunction("nlibcurl", "curl_easy_setopt", export_curl_easy_setopt); osLib_addFunction("nlibcurl", "curl_easy_getinfo", export_curl_easy_getinfo); cafeExportRegister("nlibcurl", curl_easy_perform, LogType::nlibcurl); osLib_addFunction("nlibcurl", "curl_easy_cleanup", export_curl_easy_cleanup); osLib_addFunction("nlibcurl", "curl_easy_pause", export_curl_easy_pause); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { // todo } else if (reason == coreinit::RplEntryReason::Unloaded) { // todo } } }s_COSnlibcurlModule; COSModule* GetModule() { return &s_COSnlibcurlModule; } } ================================================ FILE: src/Cafe/OS/libs/nlibcurl/nlibcurl.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace nlibcurl { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nlibcurl/nlibcurlDebug.hpp ================================================ uint32 _curlDebugSessionId = 0; // begin a new request if one is not already active void curlDebug_markActiveRequest(CURL_t* curl) { if (!ActiveSettings::DumpLibcurlRequestsEnabled()) return; if (curl->debug.activeRequestIndex != 0) return; // already tracking request if (_curlDebugSessionId == 0) { _curlDebugSessionId = (uint32)std::chrono::seconds(std::time(NULL)).count(); if (_curlDebugSessionId == 0) _curlDebugSessionId = 1; wchar_t filePath[256]; swprintf(filePath, sizeof(filePath), L"dump//curl//session%u", _curlDebugSessionId); fs::create_directories(fs::path(filePath)); } static uint32 _nextRequestIndex = 1; curl->debug.activeRequestIndex = _nextRequestIndex; _nextRequestIndex++; wchar_t filePath[256]; swprintf(filePath, sizeof(filePath) / sizeof(wchar_t), L"dump//curl//session%u//request%04d_param.txt", _curlDebugSessionId, (sint32)curl->debug.activeRequestIndex); curl->debug.file_requestParam = FileStream::createFile(filePath); auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); if (curl->debug.file_requestParam) { auto t = std::localtime(&now); curl->debug.file_requestParam->writeStringFmt("Request %d %d-%d-%d %d:%02d:%02d\r\n", (sint32)curl->debug.activeRequestIndex, t->tm_year+1900, t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); } } void curlDebug_notifySubmitRequest(CURL_t* curl) { // start new response curl->debug.responseRequestIndex = curl->debug.activeRequestIndex; if (curl->debug.file_responseRaw) { delete curl->debug.file_responseRaw; curl->debug.file_responseRaw = nullptr; } if (curl->debug.file_responseHeaders) { delete curl->debug.file_responseHeaders; curl->debug.file_responseHeaders = nullptr; } curl->debug.hasDumpedResultInfo = false; // end current request curl->debug.activeRequestIndex = 0; if (curl->debug.file_requestParam) { delete curl->debug.file_requestParam; curl->debug.file_requestParam = nullptr; } } void curlDebug_logEasySetOptStr(CURL_t* curl, const char* optName, const char* optValue) { curlDebug_markActiveRequest(curl); if (curl->debug.file_requestParam) { curl->debug.file_requestParam->writeStringFmt("SetOpt %s: ", optName, optValue ? optValue : "NULL"); if(optValue) curl->debug.file_requestParam->writeString(optValue); else curl->debug.file_requestParam->writeString("NULL"); curl->debug.file_requestParam->writeString("\r\n"); } } void curlDebug_logEasySetOptPtr(CURL_t* curl, const char* optName, uint32 ppcPtr) { curlDebug_markActiveRequest(curl); if (curl->debug.file_requestParam) { curl->debug.file_requestParam->writeStringFmt("SetOpt %s: 0x%08x\r\n", optName, ppcPtr); } } void curlDebug_resultWrite(CURL_t* curl, char* ptr, size_t size, size_t nmemb) { if (!ActiveSettings::DumpLibcurlRequestsEnabled()) return; if (curl->debug.responseRequestIndex == 0) return; if (curl->debug.file_responseRaw == nullptr) { wchar_t filePath[256]; swprintf(filePath, sizeof(filePath) / sizeof(wchar_t), L"dump//curl//session%u//request%04d_responseRaw.bin", _curlDebugSessionId, (sint32)curl->debug.responseRequestIndex); curl->debug.file_responseRaw = FileStream::createFile(filePath); } if (curl->debug.file_responseRaw) { curl->debug.file_responseRaw->writeData(ptr, size*nmemb); } } void curlDebug_headerWrite(CURL_t* curl, char* ptr, size_t size, size_t nmemb) { if (!ActiveSettings::DumpLibcurlRequestsEnabled()) return; if (curl->debug.responseRequestIndex == 0) return; if (curl->debug.file_responseHeaders == nullptr) { wchar_t filePath[256]; swprintf(filePath, sizeof(filePath) / sizeof(wchar_t), L"dump//curl//session%u//request%04d_responseHeaders.txt", _curlDebugSessionId, (sint32)curl->debug.responseRequestIndex); curl->debug.file_responseHeaders = FileStream::createFile(filePath); } if (curl->debug.file_responseHeaders) { curl->debug.file_responseHeaders->writeData(ptr, size*nmemb); } } void curlDebug_cleanup(CURL_t* curl) { if (curl->debug.file_requestParam) { delete curl->debug.file_requestParam; curl->debug.file_requestParam = nullptr; } if (curl->debug.file_responseRaw) { delete curl->debug.file_responseRaw; curl->debug.file_responseRaw = nullptr; } if (curl->debug.file_responseHeaders) { delete curl->debug.file_responseHeaders; curl->debug.file_responseHeaders = nullptr; } } ================================================ FILE: src/Cafe/OS/libs/nlibnss/nlibnss.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "nlibnss.h" namespace nlibnss { int NSSExportDeviceCertChain(uint8* uknPtr1, uint32be* uknLength1, uint8* uknPtr2, uint32be* uknLength2, uint32 uknR7, uint32 uknR8) { cemuLog_logDebug(LogType::Force, "NSSExportDeviceCertChain called but not implemented"); cemu_assert_debug(false); // uknR3 = pointer (can be null, in which case only length is written) // uknR4 = length // uknR5 = pointer2 // uknR6 = length2 // uknR7 = some integer // uknR8 = ??? *uknLength1 = 0x100; *uknLength2 = 0x100; return 0; // failed } int NSSSignatureGetSignatureLength() { // parameters are unknown cemu_assert_debug(false); return 0x1C; // signature length } class : public COSModule { public: std::string_view GetName() override { return "nlibnss"; } void RPLMapped() override { cafeExportRegister("nlibnss", NSSSignatureGetSignatureLength, LogType::Placeholder); cafeExportRegister("nlibnss", NSSExportDeviceCertChain, LogType::Placeholder); }; }s_COSnlibnssModule; COSModule* GetModule() { return &s_COSnlibnssModule; } } ================================================ FILE: src/Cafe/OS/libs/nlibnss/nlibnss.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace nlibnss { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_ac/nn_ac.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/nn_common.h" #include "nn_ac.h" #if BOOST_OS_WINDOWS #include <iphlpapi.h> #endif // AC lib (manages internet connection) enum class AC_STATUS : uint32 { FAILED = (uint32)-1, OK = 0, }; static_assert(TRUE == 1, "TRUE not 1"); void _GetLocalIPAndSubnetMaskFallback(uint32& localIp, uint32& subnetMask) { // default to some hardcoded values localIp = (192 << 24) | (168 << 16) | (0 << 8) | (100 << 0); subnetMask = (255 << 24) | (255 << 16) | (255 << 8) | (0 << 0); } #if BOOST_OS_WINDOWS void _GetLocalIPAndSubnetMask(uint32& localIp, uint32& subnetMask) { std::vector<IP_ADAPTER_ADDRESSES> buf_adapter_addresses; buf_adapter_addresses.resize(32); DWORD buf_size; DWORD r; for (uint32 i = 0; i < 6; i++) { buf_size = (uint32)(buf_adapter_addresses.size() * sizeof(IP_ADAPTER_ADDRESSES)); r = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS, nullptr, buf_adapter_addresses.data(), &buf_size); if (r != ERROR_BUFFER_OVERFLOW) break; buf_adapter_addresses.resize(buf_adapter_addresses.size() * 2); } if (r != ERROR_SUCCESS) { cemuLog_log(LogType::Force, "Failed to acquire local IP and subnet mask"); _GetLocalIPAndSubnetMaskFallback(localIp, subnetMask); return; } IP_ADAPTER_ADDRESSES* currentAddress = buf_adapter_addresses.data(); while (currentAddress) { if (currentAddress->OperStatus != IfOperStatusUp) { currentAddress = currentAddress->Next; continue; } if (!currentAddress->FirstUnicastAddress || !currentAddress->FirstUnicastAddress->Address.lpSockaddr) { currentAddress = currentAddress->Next; continue; } if (!currentAddress->FirstGatewayAddress) { currentAddress = currentAddress->Next; continue; } SOCKADDR* sockAddr = currentAddress->FirstUnicastAddress->Address.lpSockaddr; if (sockAddr->sa_family == AF_INET) { ULONG mask = 0; if (ConvertLengthToIpv4Mask(currentAddress->FirstUnicastAddress->OnLinkPrefixLength, &mask) != NO_ERROR) mask = 0; sockaddr_in* inAddr = (sockaddr_in*)sockAddr; localIp = _byteswap_ulong(inAddr->sin_addr.S_un.S_addr); subnetMask = _byteswap_ulong(mask); return; } currentAddress = currentAddress->Next; } cemuLog_logDebug(LogType::Force, "_GetLocalIPAndSubnetMask(): Failed to find network IP and subnet mask"); _GetLocalIPAndSubnetMaskFallback(localIp, subnetMask); } #else void _GetLocalIPAndSubnetMask(uint32& localIp, uint32& subnetMask) { cemuLog_logDebug(LogType::Force, "_GetLocalIPAndSubnetMask(): Not implemented"); _GetLocalIPAndSubnetMaskFallback(localIp, subnetMask); } #endif void nnAcExport_GetAssignedAddress(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GetAssignedAddress() called"); ppcDefineParamU32BEPtr(ipAddrOut, 0); uint32 localIp; uint32 subnetMask; _GetLocalIPAndSubnetMask(localIp, subnetMask); *ipAddrOut = localIp; const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); osLib_returnFromFunction(hCPU, nnResultCode); } void nnAcExport_GetAssignedSubnet(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GetAssignedSubnet() called"); ppcDefineParamU32BEPtr(subnetMaskOut, 0); uint32 localIp; uint32 subnetMask; _GetLocalIPAndSubnetMask(localIp, subnetMask); *subnetMaskOut = subnetMask; const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); osLib_returnFromFunction(hCPU, nnResultCode); } void nnAcExport_ACGetAssignedAddress(PPCInterpreter_t* hCPU) { ppcDefineParamU32BEPtr(ipAddrOut, 0); uint32 localIp; uint32 subnetMask; _GetLocalIPAndSubnetMask(localIp, subnetMask); *ipAddrOut = localIp; const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); osLib_returnFromFunction(hCPU, nnResultCode); } void nnAcExport_IsSystemConnected(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(isConnectedOut, uint8, 0); ppcDefineParamTypePtr(apTypeOut, uint32be, 1); cemuLog_logDebug(LogType::Force, "nn_ac.IsSystemConnected() - placeholder"); *apTypeOut = 0; // ukn *isConnectedOut = 1; osLib_returnFromFunction(hCPU, 0); } void nnAcExport_IsConfigExisting(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_ac.IsConfigExisting() - placeholder"); ppcDefineParamU32(configId, 0); ppcDefineParamTypePtr(isConfigExisting, uint8, 1); *isConfigExisting = 0; osLib_returnFromFunction(hCPU, 0); } namespace nn_ac { nnResult Initialize() { return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); } nnResult ConnectAsync() { return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); } nnResult IsApplicationConnected(uint8be* connected) { if (connected) *connected = TRUE; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); } uint32 Connect() { // Terraria expects this (or GetLastErrorCode) to return 0 on success // investigate on the actual console // maybe all success codes are always 0 and dont have any of the other fields set? uint32 nnResultCode = 0;// BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); // Splatoon freezes if this function fails? return nnResultCode; } nnResult GetConnectStatus(betype<AC_STATUS>* status) { if (status) *status = AC_STATUS::OK; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); } nnResult GetStatus(betype<AC_STATUS>* status) { return GetConnectStatus(status); } nnResult GetLastErrorCode(uint32be* errorCode) { if (errorCode) *errorCode = 0; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); } nnResult GetConnectResult(uint32be* connectResult) { const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); if (connectResult) *connectResult = nnResultCode; return nnResultCode; } static_assert(sizeof(betype<AC_STATUS>) == 4); static_assert(sizeof(betype<nnResult>) == 4); nnResult ACInitialize() { return Initialize(); } bool ACIsSuccess(betype<nnResult>* r) { return NN_RESULT_IS_SUCCESS(*r) ? 1 : 0; } bool ACIsFailure(betype<nnResult>* r) { return NN_RESULT_IS_FAILURE(*r) ? 1 : 0; } nnResult ACGetConnectStatus(betype<AC_STATUS>* connectionStatus) { return GetConnectStatus(connectionStatus); } nnResult ACGetStatus(betype<AC_STATUS>* connectionStatus) { return GetStatus(connectionStatus); } nnResult ACConnectAsync() { return ConnectAsync(); } nnResult ACIsApplicationConnected(uint32be* connectedU32) { uint8be connected = 0; nnResult r = IsApplicationConnected(&connected); *connectedU32 = connected; // convert to uint32 return r; } void load() { cafeExportRegisterFunc(Initialize, "nn_ac", "Initialize__Q2_2nn2acFv", LogType::Placeholder); cafeExportRegisterFunc(Connect, "nn_ac", "Connect__Q2_2nn2acFv", LogType::Placeholder); cafeExportRegisterFunc(ConnectAsync, "nn_ac", "ConnectAsync__Q2_2nn2acFv", LogType::Placeholder); cafeExportRegisterFunc(GetConnectResult, "nn_ac", "GetConnectResult__Q2_2nn2acFPQ2_2nn6Result", LogType::Placeholder); cafeExportRegisterFunc(GetLastErrorCode, "nn_ac", "GetLastErrorCode__Q2_2nn2acFPUi", LogType::Placeholder); cafeExportRegisterFunc(GetConnectStatus, "nn_ac", "GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status", LogType::Placeholder); cafeExportRegisterFunc(GetStatus, "nn_ac", "GetStatus__Q2_2nn2acFPQ3_2nn2ac6Status", LogType::Placeholder); cafeExportRegisterFunc(IsApplicationConnected, "nn_ac", "IsApplicationConnected__Q2_2nn2acFPb", LogType::Placeholder); // AC also offers C-style wrappers cafeExportRegister("nn_ac", ACInitialize, LogType::Placeholder); cafeExportRegister("nn_ac", ACIsSuccess, LogType::Placeholder); cafeExportRegister("nn_ac", ACIsFailure, LogType::Placeholder); cafeExportRegister("nn_ac", ACGetConnectStatus, LogType::Placeholder); cafeExportRegister("nn_ac", ACGetStatus, LogType::Placeholder); cafeExportRegister("nn_ac", ACConnectAsync, LogType::Placeholder); cafeExportRegister("nn_ac", ACIsApplicationConnected, LogType::Placeholder); } } void nnAc_load() { } namespace nn::ac { class : public COSModule { public: std::string_view GetName() override { return "nn_ac"; } void RPLMapped() override { osLib_addFunction("nn_ac", "GetAssignedAddress__Q2_2nn2acFPUl", nnAcExport_GetAssignedAddress); osLib_addFunction("nn_ac", "GetAssignedSubnet__Q2_2nn2acFPUl", nnAcExport_GetAssignedSubnet); osLib_addFunction("nn_ac", "IsSystemConnected__Q2_2nn2acFPbPQ3_2nn2ac6ApType", nnAcExport_IsSystemConnected); osLib_addFunction("nn_ac", "IsConfigExisting__Q2_2nn2acFQ3_2nn2ac11ConfigIdNumPb", nnAcExport_IsConfigExisting); osLib_addFunction("nn_ac", "ACGetAssignedAddress", nnAcExport_ACGetAssignedAddress); nn_ac::load(); }; }s_COSnnAcModule; COSModule* GetModule() { return &s_COSnnAcModule; } } ================================================ FILE: src/Cafe/OS/libs/nn_ac/nn_ac.h ================================================ void nnAc_load(); #include "Cafe/OS/RPL/COSModule.h" namespace nn_ac { nnResult IsApplicationConnected(uint8be* connected); } namespace nn::ac { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_acp/nn_acp.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/Filesystem/fsc.h" #include "nn_acp.h" #include "Cafe/OS/libs/nn_common.h" #include "Cafe/OS/libs/coreinit/coreinit_IOS.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include <cinttypes> #include <filesystem> #include <fstream> #include "config/ActiveSettings.h" #include "Cafe/IOSU/legacy/iosu_acp.h" #include "Cafe/IOSU/legacy/iosu_ioctl.h" #include "Cafe/OS/libs/sysapp/sysapp.h" #include "Common/FileStream.h" #include "Cafe/CafeSystem.h" using ACPDeviceType = iosu::acp::ACPDeviceType; #define acpPrepareRequest() \ StackAllocator<iosuAcpCemuRequest_t> _buf_acpRequest; \ StackAllocator<ioBufferVector_t> _buf_bufferVector; \ iosuAcpCemuRequest_t* acpRequest = _buf_acpRequest.GetPointer(); \ ioBufferVector_t* acpBufferVector = _buf_bufferVector.GetPointer(); \ memset(acpRequest, 0, sizeof(iosuAcpCemuRequest_t)); \ memset(acpBufferVector, 0, sizeof(ioBufferVector_t)); \ acpBufferVector->buffer = (uint8*)acpRequest; namespace nn { namespace acp { ACPStatus ACPConvertResultToACPStatus(uint32* nnResult, const char* functionName, uint32 lineNumber) { // todo return ACPStatus::SUCCESS; } #define _ACPConvertResultToACPStatus(nnResult) ACPConvertResultToACPStatus(nnResult, __func__, __LINE__) ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId) { // todo *applicationBox = 3; // storage mlc return ACPStatus::SUCCESS; } ACPStatus ACPGetOlvAccesskey(uint32be* accessKey) { nnResult r = iosu::acp::ACPGetOlvAccesskey(accessKey); return _ACPConvertResultToACPStatus(&r); } bool sSaveDirMounted{false}; ACPStatus ACPMountSaveDir() { cemu_assert_debug(!sSaveDirMounted); uint64 titleId = CafeSystem::GetForegroundTitleId(); uint32 high = GetTitleIdHigh(titleId) & (~0xC); uint32 low = GetTitleIdLow(titleId); // mount save path const auto mlc = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/", high, low); FSCDeviceHostFS_Mount("/vol/save/", _pathToUtf8(mlc), FSC_PRIORITY_BASE); nnResult mountResult = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); return _ACPConvertResultToACPStatus(&mountResult); } ACPStatus ACPUnmountSaveDir() { cemu_assert_debug(!sSaveDirMounted); fsc_unmount("/vol/save/", FSC_PRIORITY_BASE); return ACPStatus::SUCCESS; } ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) { nnResult r = iosu::acp::ACPUpdateSaveTimeStamp(persistentId, titleId, deviceType); return ACPStatus::SUCCESS; } ACPStatus ACPCheckApplicationDeviceEmulation(uint32be* isEmulated) { *isEmulated = 0; return ACPStatus::SUCCESS; } ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type) { nnResult result = iosu::acp::ACPCreateSaveDir(persistentId, type); return _ACPConvertResultToACPStatus(&result); } nnResult ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId) { acpPrepareRequest(); acpRequest->requestCode = IOSU_ACP_CREATE_SAVE_DIR_EX; acpRequest->accountSlot = accountSlot; acpRequest->titleId = titleId; __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); nnResult result = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); return result; } void nnACPExport_ACPCreateSaveDirEx(PPCInterpreter_t* hCPU) { ppcDefineParamU8(accountSlot, 0); ppcDefineParamU64(titleId, 2); // index 2 because of alignment -> guessed parameters nnResult result = ACPCreateSaveDirEx(accountSlot, titleId); osLib_returnFromFunction(hCPU, _ACPConvertResultToACPStatus(&result)); } void export_ACPGetSaveDataTitleIdList(PPCInterpreter_t* hCPU) { ppcDefineParamU32(uknType, 0); ppcDefineParamStructPtr(titleIdList, acpTitleId_t, 1); ppcDefineParamU32(maxCount, 2); ppcDefineParamU32BEPtr(count, 3); if (uknType != 3) assert_dbg(); acpPrepareRequest(); acpRequest->requestCode = IOSU_ACP_GET_SAVE_DATA_TITLE_ID_LIST; acpRequest->ptr = titleIdList; acpRequest->maxCount = maxCount; acpRequest->type = uknType; __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); *count = acpRequest->resultU32.u32; osLib_returnFromFunction(hCPU, acpRequest->returnCode); } void export_ACPGetTitleSaveMetaXml(PPCInterpreter_t* hCPU) { // r3/r4 = titleId // r5 = pointer // r6 = 3 (probably some sort of type? Same as in ACPGetSaveDataTitleIdList?) ppcDefineParamU64(titleId, 0); ppcDefineParamStructPtr(acpMetaXml, acpMetaXml_t, 2); ppcDefineParamU32(uknR6, 3); if (uknR6 != 3) assert_dbg(); acpPrepareRequest(); acpRequest->requestCode = IOSU_ACP_GET_TITLE_SAVE_META_XML; acpRequest->ptr = acpMetaXml; acpRequest->titleId = titleId; acpRequest->type = uknR6; __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); osLib_returnFromFunction(hCPU, acpRequest->returnCode); } static_assert(sizeof(acpSaveDirInfo_t) == 0x80, "acpSaveDirInfo_t has invalid size"); void export_ACPGetTitleSaveDirEx(PPCInterpreter_t* hCPU) { ppcDefineParamU64(titleId, 0); ppcDefineParamU32(uknR5, 2); // storage device id? ppcDefineParamU32(uknR6, 3); // ukn ppcDefineParamStructPtr(saveDirInfoOut, acpSaveDirInfo_t, 4); ppcDefineParamU32(uknR8, 5); // max count? ppcDefineParamU32BEPtr(countOut, 6); if (uknR5 != 3) assert_dbg(); if (uknR6 != 0) assert_dbg(); acpPrepareRequest(); acpRequest->requestCode = IOSU_ACP_GET_TITLE_SAVE_DIR; acpRequest->ptr = saveDirInfoOut; acpRequest->titleId = titleId; acpRequest->type = uknR5; acpRequest->maxCount = uknR8; __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); *countOut = acpRequest->resultU32.u32; osLib_returnFromFunction(hCPU, acpRequest->returnCode); } void export_ACPCheckTitleNotReferAccountLaunch(PPCInterpreter_t* hCPU) { ppcDefineParamU64(titleId, 0); cemuLog_logDebug(LogType::Force, "ACPCheckTitleNotReferAccountLaunch(): Placeholder"); osLib_returnFromFunction(hCPU, 0); } void export_ACPGetLaunchMetaData(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(acpMetaData, acpMetaData_t, 0); cemuLog_logDebug(LogType::Force, "ACPGetLaunchMetaData(): Placeholder"); acpPrepareRequest(); acpRequest->requestCode = IOSU_ACP_GET_TITLE_META_DATA; acpRequest->ptr = acpMetaData; acpRequest->titleId = CafeSystem::GetForegroundTitleId(); __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); osLib_returnFromFunction(hCPU, acpRequest->returnCode); } void export_ACPGetLaunchMetaXml(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(acpMetaXml, acpMetaXml_t, 0); cemuLog_logDebug(LogType::Force, "ACPGetLaunchMetaXml(): Placeholder"); acpPrepareRequest(); acpRequest->requestCode = IOSU_ACP_GET_TITLE_META_XML; acpRequest->ptr = acpMetaXml; acpRequest->titleId = CafeSystem::GetForegroundTitleId(); __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); osLib_returnFromFunction(hCPU, acpRequest->returnCode); } void export_ACPGetTitleIdOfMainApplication(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(titleId, uint64be, 0); uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); *titleId = currentTitleId; // for applets we return the menu titleId if (((currentTitleId >> 32) & 0xFF) == 0x30) { // get menu titleId uint64 menuTitleId = _SYSGetSystemApplicationTitleId(0); *titleId = menuTitleId; } osLib_returnFromFunction(hCPU, 0); } void export_ACPGetTitleMetaDirByDevice(PPCInterpreter_t* hCPU) { ppcDefineParamU64(titleId, 0); ppcDefineParamStr(path, 2); ppcDefineParamU32(pathSize, 3); ppcDefineParamU32(deviceId, 4); if (deviceId != 3) assert_dbg(); if (((titleId >> 32) & 0xFF) == 0x10 || ((titleId >> 32) & 0xFF) == 0x30) { sprintf(path, "/vol/storage_mlc01/sys/title/%08x/%08x/meta", (uint32)(titleId>>32), (uint32)(titleId&0xFFFFFFFF)); } else { sprintf(path, "/vol/storage_mlc01/usr/title/%08x/%08x/meta", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); } osLib_returnFromFunction(hCPU, 0); } void export_ACPGetTitleMetaXmlByDevice(PPCInterpreter_t* hCPU) { ppcDefineParamU64(titleId, 0); ppcDefineParamStructPtr(acpMetaXml, acpMetaXml_t, 2); ppcDefineParamU32(deviceId, 3); if (deviceId != 3) cemuLog_logDebug(LogType::Force, "ACPGetTitleMetaXmlByDevice(): Unsupported deviceId"); acpPrepareRequest(); acpRequest->requestCode = IOSU_ACP_GET_TITLE_META_XML; acpRequest->ptr = acpMetaXml; acpRequest->titleId = titleId;//CafeSystem::GetForegroundTitleId(); __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); osLib_returnFromFunction(hCPU, acpRequest->returnCode); } uint32 ACPGetTitleMetaXml(uint64 titleId, acpMetaXml_t* acpMetaXml) { acpPrepareRequest(); acpRequest->requestCode = IOSU_ACP_GET_TITLE_META_XML; acpRequest->ptr = acpMetaXml; acpRequest->titleId = titleId; __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); return acpRequest->returnCode; } void export_ACPIsOverAgeEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(age, 0); ppcDefineParamU8(slot, 1); bool isOverAge = true; osLib_returnFromFunction(hCPU, isOverAge ? 1 : 0); } void export_ACPGetNetworkTime(PPCInterpreter_t* hCPU) { ppcDefineParamU32BEPtr(timestamp64, 0); ppcDefineParamU32BEPtr(ukn, 1); // probably timezone or offset? Could also be a bool for success/failed uint64 t = coreinit::OSGetTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL); timestamp64[0] = (uint32)(t >> 32); timestamp64[1] = (uint32)(t & 0xFFFFFFFF); *ukn = 1; // E-Shop doesnt want this to be zero (games also check for it) osLib_returnFromFunction(hCPU, 0); // error code } void export_ACPConvertNetworkTimeToOSCalendarTime(PPCInterpreter_t* hCPU) { ppcDefineParamU64(networkTime, 0); ppcDefineParamStructPtr(calendarTime, coreinit::OSCalendarTime_t, 2); coreinit::OSTicksToCalendarTime(networkTime, calendarTime); osLib_returnFromFunction(hCPU, 0); } class : public COSModule { public: std::string_view GetName() override { return "nn_acp"; } void RPLMapped() override { cafeExportRegister("nn_acp", ACPCheckApplicationDeviceEmulation, LogType::Placeholder); osLib_addFunction("nn_acp", "ACPCreateSaveDirEx", nnACPExport_ACPCreateSaveDirEx); cafeExportRegister("nn_acp", ACPUpdateSaveTimeStamp, LogType::Placeholder); osLib_addFunction("nn_acp", "ACPGetSaveDataTitleIdList", export_ACPGetSaveDataTitleIdList); osLib_addFunction("nn_acp", "ACPGetTitleSaveMetaXml", export_ACPGetTitleSaveMetaXml); osLib_addFunction("nn_acp", "ACPGetTitleSaveDirEx", export_ACPGetTitleSaveDirEx); osLib_addFunction("nn_acp", "ACPCheckTitleNotReferAccountLaunch", export_ACPCheckTitleNotReferAccountLaunch); osLib_addFunction("nn_acp", "ACPGetLaunchMetaData", export_ACPGetLaunchMetaData); osLib_addFunction("nn_acp", "ACPGetLaunchMetaXml", export_ACPGetLaunchMetaXml); osLib_addFunction("nn_acp", "ACPGetTitleIdOfMainApplication", export_ACPGetTitleIdOfMainApplication); osLib_addFunction("nn_acp", "ACPGetTitleMetaDirByDevice", export_ACPGetTitleMetaDirByDevice); osLib_addFunction("nn_acp", "ACPGetTitleMetaXmlByDevice", export_ACPGetTitleMetaXmlByDevice); cafeExportRegister("nn_acp", ACPGetTitleMetaXml, LogType::Placeholder); cafeExportRegister("nn_acp", ACPGetApplicationBox, LogType::Placeholder); cafeExportRegister("nn_acp", ACPGetOlvAccesskey, LogType::Placeholder); osLib_addFunction("nn_acp", "ACPIsOverAgeEx", export_ACPIsOverAgeEx); osLib_addFunction("nn_acp", "ACPGetNetworkTime", export_ACPGetNetworkTime); osLib_addFunction("nn_acp", "ACPConvertNetworkTimeToOSCalendarTime", export_ACPConvertNetworkTimeToOSCalendarTime); }; }s_COSnnAcpModule; COSModule* GetModule() { return &s_COSnnAcpModule; } } } ================================================ FILE: src/Cafe/OS/libs/nn_acp/nn_acp.h ================================================ #pragma once #include "Cafe/IOSU/legacy/iosu_acp.h" #include "Cafe/OS/RPL/COSModule.h" namespace nn { namespace acp { enum ACPStatus : uint32 { SUCCESS = 0, }; using ACPDeviceType = iosu::acp::ACPDeviceType; ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId); ACPStatus ACPMountSaveDir(); ACPStatus ACPUnmountSaveDir(); ACPStatus ACPCreateSaveDir(uint32 persistentId, iosu::acp::ACPDeviceType type); ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, iosu::acp::ACPDeviceType deviceType); COSModule* GetModule(); } } ================================================ FILE: src/Cafe/OS/libs/nn_act/nn_act.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_IOS.h" #include "Cafe/IOSU/legacy/iosu_act.h" #include "Cafe/IOSU/legacy/iosu_ioctl.h" #include "nn_act.h" #include "Cafe/OS/libs/nn_common.h" #include "Cafe/CafeSystem.h" #include "Common/CafeString.h" sint32 numAccounts = 1; #define actPrepareRequest() \ StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \ StackAllocator<ioBufferVector_t> _buf_bufferVector; \ iosuActCemuRequest_t* actRequest = _buf_actRequest.GetPointer(); \ ioBufferVector_t* actBufferVector = _buf_bufferVector.GetPointer(); \ memset(actRequest, 0, sizeof(iosuActCemuRequest_t)); \ memset(actBufferVector, 0, sizeof(ioBufferVector_t)); \ actBufferVector->buffer = (uint8*)actRequest; #define actPrepareRequest2() \ StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \ iosuActCemuRequest_t* actRequest = _buf_actRequest.GetPointer(); \ memset(actRequest, 0, sizeof(iosuActCemuRequest_t)); uint32 getNNReturnCode(uint32 iosError, iosuActCemuRequest_t* actRequest) { if ((iosError & 0x80000000) != 0) return iosError; return actRequest->returnCode; } uint32 _doCemuActRequest(iosuActCemuRequest_t* actRequest) { StackAllocator<ioBufferVector_t> _buf_bufferVector; ioBufferVector_t* actBufferVector = _buf_bufferVector.GetPointer(); memset(actBufferVector, 0, sizeof(ioBufferVector_t)); actBufferVector->buffer = (uint8*)actRequest; uint32 ioctlResult = __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); return getNNReturnCode(ioctlResult, actRequest); } namespace nn { namespace act { uint32 GetPersistentIdEx(uint8 slot) { actPrepareRequest(); actRequest->requestCode = IOSU_ARC_PERSISTENTID; actRequest->accountSlot = slot; __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); return actRequest->resultU32.u32; } uint32 GetMiiEx(void* miiData, uint8 slot) { actPrepareRequest2(); actRequest->requestCode = IOSU_ARC_MIIDATA; actRequest->accountSlot = slot; uint32 result = _doCemuActRequest(actRequest); memcpy(miiData, actRequest->resultBinary.binBuffer, MII_FFL_STORAGE_SIZE); return result; } uint32 GetUuidEx(uint8* uuid, uint8 slot, sint32 name) { actPrepareRequest2(); actRequest->requestCode = IOSU_ARC_UUID; actRequest->accountSlot = slot; actRequest->uuidName = name; uint32 result = _doCemuActRequest(actRequest); memcpy(uuid, actRequest->resultBinary.binBuffer, 16); return result; } uint32 GetSimpleAddressIdEx(uint32be* simpleAddressId, uint8 slot) { actPrepareRequest2(); actRequest->requestCode = IOSU_ARC_SIMPLEADDRESS; actRequest->accountSlot = slot; uint32 result = _doCemuActRequest(actRequest); *simpleAddressId = actRequest->resultU32.u32; return result; } uint32 GetTransferableIdEx(uint64* transferableId, uint32 unique, uint8 slot) { actPrepareRequest2(); actRequest->requestCode = IOSU_ARC_TRANSFERABLEID; actRequest->accountSlot = slot; actRequest->unique = unique; uint32 result = _doCemuActRequest(actRequest); *transferableId = _swapEndianU64(actRequest->resultU64.u64); return result; } uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds) { memset(token, 0, sizeof(independentServiceToken_t)); actPrepareRequest(); actRequest->accountSlot = iosu::act::ACT_SLOT_CURRENT; actRequest->requestCode = IOSU_ARC_ACQUIREINDEPENDENTTOKEN; actRequest->titleId = CafeSystem::GetForegroundTitleId(); actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); actRequest->expiresIn = cacheDurationInSeconds; strcpy(actRequest->clientId, clientId); uint32 resultCode = __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); memcpy(token, actRequest->resultBinary.binBuffer, sizeof(independentServiceToken_t)); return getNNReturnCode(resultCode, actRequest); } sint64 GetUtcOffset() { return ((ppcCyclesSince2000 / ESPRESSO_CORE_CLOCK) - (ppcCyclesSince2000_UTC / ESPRESSO_CORE_CLOCK)) * 1'000'000; } sint32 GetUtcOffsetEx(sint64be* pOutOffset, uint8 slotNo) { if (!pOutOffset) return 0xc0712c80; *pOutOffset = GetUtcOffset(); return 0; } nnResult GetTimeZoneId(CafeString<65>* outTimezoneId) { // return a placeholder timezone id for now // in the future we should emulated this correctly and read the timezone from the account via IOSU outTimezoneId->assign("Europe/London"); return 0; } sint32 g_initializeCount = 0; // inc in Initialize and dec in Finalize uint32 Initialize() { // usually uses a mutex which is initialized in -> global constructor keyed to'_17_act_shim_util_cpp if (g_initializeCount == 0) { actPrepareRequest(); actRequest->requestCode = IOSU_ARC_INIT; __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); } g_initializeCount++; return 0; } NN_ERROR_CODE GetErrorCode(betype<nnResult>* nnResult) { NN_ERROR_CODE errCode = NNResultToErrorCode(*nnResult, NN_RESULT_MODULE_NN_ACT); return errCode; } } } void nnActExport_CreateConsoleAccount(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "CreateConsoleAccount(...)"); //numAccounts++; osLib_returnFromFunction(hCPU, 0); } void nnActExport_GetNumOfAccounts(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetNumOfAccounts()"); osLib_returnFromFunction(hCPU, numAccounts); // account count } void nnActExport_IsSlotOccupied(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.IsSlotOccupied({})", hCPU->gpr[3]); ppcDefineParamU8(slot, 0); osLib_returnFromFunction(hCPU, nn::act::GetPersistentIdEx(slot) != 0 ? 1 : 0); } uint32 GetAccountIdEx(char* accountId, uint8 slot) { // returns zero for non-network accounts? actPrepareRequest2(); actRequest->requestCode = IOSU_ARC_ACCOUNT_ID; actRequest->accountSlot = slot; uint32 result = _doCemuActRequest(actRequest); strcpy(accountId, actRequest->resultString.strBuffer); return result; } uint32 GetPrincipalIdEx(uint32be* principalId, uint8 slot) { actPrepareRequest2(); actRequest->requestCode = IOSU_ARC_PRINCIPALID; actRequest->accountSlot = slot; uint32 result = _doCemuActRequest(actRequest); *principalId = actRequest->resultU32.u32; return result; } uint32 GetCountryEx(char* country, uint8 slot) { actPrepareRequest2(); actRequest->requestCode = IOSU_ARC_COUNTRY; actRequest->accountSlot = slot; uint32 result = _doCemuActRequest(actRequest); strcpy(country, actRequest->resultString.strBuffer); return result; } uint32 IsNetworkAccount(uint8* isNetworkAccount, uint8 slot) { actPrepareRequest2(); actRequest->requestCode = IOSU_ARC_ISNETWORKACCOUNT; actRequest->accountSlot = slot; uint32 result = _doCemuActRequest(actRequest); *isNetworkAccount = (actRequest->resultU32.u32 != 0) ? 1 : 0; return result; } void nnActExport_IsNetworkAccount(PPCInterpreter_t* hCPU) { //cemuLog_logDebug(LogType::Force, "nn_act.IsNetworkAccount()"); uint8 isNetAcc = 0; IsNetworkAccount(&isNetAcc, 0xFE); osLib_returnFromFunction(hCPU, isNetAcc); } void nnActExport_IsNetworkAccountEx(PPCInterpreter_t* hCPU) { ppcDefineParamU8(slot, 0); cemuLog_logDebug(LogType::Force, "nn_act.IsNetworkAccountEx({})", slot); uint8 isNetAcc = 0; IsNetworkAccount(&isNetAcc, slot); osLib_returnFromFunction(hCPU, isNetAcc); } void nnActExport_GetSimpleAddressId(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetSimpleAddressId()"); uint32be simpleAddressId; nn::act::GetSimpleAddressIdEx(&simpleAddressId, iosu::act::ACT_SLOT_CURRENT); osLib_returnFromFunction(hCPU, (uint32)simpleAddressId); } void nnActExport_GetSimpleAddressIdEx(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetSimpleAddressIdEx(0x{:08x}, {})", hCPU->gpr[3], hCPU->gpr[4] & 0xFF); ppcDefineParamU32BEPtr(simpleAddressId, 0); ppcDefineParamU8(slot, 1); nn::act::GetSimpleAddressIdEx(simpleAddressId, slot); osLib_returnFromFunction(hCPU, 0); // ResultSuccess } void nnActExport_GetPrincipalId(PPCInterpreter_t* hCPU) { // return error for non-nnid accounts? uint32be principalId; GetPrincipalIdEx(&principalId, iosu::act::ACT_SLOT_CURRENT); osLib_returnFromFunction(hCPU, (uint32)principalId); } void nnActExport_GetPrincipalIdEx(PPCInterpreter_t* hCPU) { // return error for non-nnid accounts? cemuLog_logDebug(LogType::Force, "nn_act.GetPrincipalIdEx(0x{:08x}, {})", hCPU->gpr[3], hCPU->gpr[4]&0xFF); ppcDefineParamU32BEPtr(principalId, 0); ppcDefineParamU8(slot, 1); GetPrincipalIdEx(principalId, slot); osLib_returnFromFunction(hCPU, 0); // ResultSuccess } void nnActExport_GetTransferableId(PPCInterpreter_t* hCPU) { ppcDefineParamU32(unique, 0); cemuLog_logDebug(LogType::Force, "nn_act.GetTransferableId(0x{:08x})", hCPU->gpr[3]); uint64 transferableId; uint32 r = nn::act::GetTransferableIdEx(&transferableId, unique, iosu::act::ACT_SLOT_CURRENT); if (NN_RESULT_IS_FAILURE(r)) { transferableId = 0; } osLib_returnFromFunction64(hCPU, _swapEndianU64(transferableId)); } void nnActExport_GetTransferableIdEx(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(transferableId, uint64, 0); ppcDefineParamU32(unique, 1); ppcDefineParamU8(slot, 2); cemuLog_logDebug(LogType::Force, "nn_act.GetTransferableIdEx(0x{:08x}, 0x{:08x}, {})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5] & 0xFF); uint32 r = nn::act::GetTransferableIdEx(transferableId, unique, slot); osLib_returnFromFunction(hCPU, 0); // ResultSuccess } void nnActExport_GetPersistentId(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetPersistentId()"); uint32 persistentId = nn::act::GetPersistentIdEx(iosu::act::ACT_SLOT_CURRENT); osLib_returnFromFunction(hCPU, persistentId); } void nnActExport_GetPersistentIdEx(PPCInterpreter_t* hCPU) { ppcDefineParamU8(slot, 0); cemuLog_logDebug(LogType::Force, "nn_act.GetPersistentIdEx({})", slot); uint32 persistentId = nn::act::GetPersistentIdEx(slot); osLib_returnFromFunction(hCPU, persistentId); } void nnActExport_GetCountry(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetCountry(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStr(country, 0); uint32 r = GetCountryEx(country, iosu::act::ACT_SLOT_CURRENT); osLib_returnFromFunction(hCPU, r); } bool g_isParentalControlCheckEnabled = false; void nnActExport_EnableParentalControlCheck(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.EnableParentalControlCheck({})", hCPU->gpr[3]); ppcDefineParamU8(isEnabled, 0); g_isParentalControlCheckEnabled = isEnabled != 0; osLib_returnFromFunction(hCPU, 0); } void nnActExport_IsParentalControlCheckEnabled(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.IsParentalControlCheckEnabled() -> {}", g_isParentalControlCheckEnabled); osLib_returnFromFunction(hCPU, g_isParentalControlCheckEnabled); } void nnActExport_GetHostServerSettings(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GetHostServerSettings() - stub"); ppcDefineParamStr(ukn, 1); strcpy(ukn, ""); osLib_returnFromFunction(hCPU, 0x00000000); } void nnActExport_GetMii(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetMii(...)"); ppcDefineParamUStr(miiData, 0); uint32 r = nn::act::GetMiiEx(miiData, iosu::act::ACT_SLOT_CURRENT); osLib_returnFromFunction(hCPU, r); } void nnActExport_GetMiiEx(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetMiiEx(...)"); ppcDefineParamUStr(miiData, 0); ppcDefineParamU8(slot, 1); uint32 r = nn::act::GetMiiEx(miiData, slot); osLib_returnFromFunction(hCPU, r); } void nnActExport_GetMiiImageEx(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GetMiiImageEx unimplemented"); osLib_returnFromFunction(hCPU, 0); } void nnActExport_GetMiiName(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GetMiiName(0x{:08x})", hCPU->gpr[3]); ppcDefineParamWStrBE(miiName, 0); StackAllocator<FFLData_t> miiData; uint32 r = nn::act::GetMiiEx(&miiData, iosu::act::ACT_SLOT_CURRENT); // extract name sint32 miiNameLength = 0; for (sint32 i = 0; i < MII_FFL_NAME_LENGTH; i++) { miiName[i] = miiData->miiName[i]; if (miiData->miiName[i] == (const uint16be)'\0') break; miiNameLength = i+1; } miiName[miiNameLength] = '\0'; osLib_returnFromFunction(hCPU, 0); } void nnActExport_GetMiiNameEx(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GetMiiNameEx(0x{:08x}, {})", hCPU->gpr[3], hCPU->gpr[4] & 0xFF); ppcDefineParamWStrBE(miiName, 0); ppcDefineParamU8(slot, 1); StackAllocator<FFLData_t> miiData; uint32 r = nn::act::GetMiiEx(&miiData, slot); // extract name sint32 miiNameLength = 0; for (sint32 i = 0; i < MII_FFL_NAME_LENGTH; i++) { miiName[i] = miiData->miiName[i]; if (miiData->miiName[i] == (const uint16be)'\0') break; miiNameLength = i + 1; } miiName[miiNameLength] = '\0'; osLib_returnFromFunction(hCPU, 0); } typedef struct { uint32be ukn00; uint32be ukn04; // transferable id high? uint32be accountTransferableIdLow; // uint32be ukn0C; uint32be ukn10; uint32be ukn14; uint32be ukn18; uint32be ukn1C; uint32be ukn20; uint32be ukn24; uint32be ukn28; uint32be ukn2C; uint32be ukn30; uint32be ukn34; uint32be ukn38; uint32be ukn3C; uint32be ukn40; uint32be ukn44; uint32be ukn48; uint32be ukn4C; uint32be ukn50; uint32be ukn54; uint32be tlsModuleIndex; uint32be ukn5C; }FFLStoreDataDepr_t; static_assert(sizeof(FFLStoreDataDepr_t) == 96); void nnActExport_UpdateMii(PPCInterpreter_t* hCPU) { if (sizeof(FFLStoreDataDepr_t) != MII_FFL_STORAGE_SIZE) assert_dbg(); ppcDefineParamU8(slot, 0); ppcDefineParamStructPtr(fflStoreData, FFLStoreDataDepr_t, 1); ppcDefineParamStructPtr(uknName, uint16, 2); // mii name ppcDefineParamStructPtr(uknNameR6, uint8, 3); ppcDefineParamStructPtr(uknNameR7, uint8, 4); ppcDefineParamStructPtr(uknNameR8, uint8, 5); ppcDefineParamStructPtr(uknNameR9, uint8, 6); ppcDefineParamStructPtr(uknNameR10, uint8, 7); ppcDefineParamStructPtr(uknNameSP4, uint8, 8); cemu_assert_unimplemented(); osLib_returnFromFunction(hCPU, 0); } void nnActExport_GetUuid(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetUuid(0x{:08x})", hCPU->gpr[3]); ppcDefineParamUStr(uuid, 0); nn::act::GetUuidEx(uuid, iosu::act::ACT_SLOT_CURRENT); osLib_returnFromFunction(hCPU, 0); // 0 -> result ok } void nnActExport_GetUuidEx(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetUuidEx(0x{:08x},0x{:02x})", hCPU->gpr[3], hCPU->gpr[3]&0xFF); ppcDefineParamUStr(uuid, 0); ppcDefineParamU8(slot, 1); nn::act::GetUuidEx(uuid, slot); osLib_returnFromFunction(hCPU, 0); // 0 -> result ok } void nnActExport_GetUuidEx2(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetUuidEx(0x{:08x},0x{:02x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); ppcDefineParamUStr(uuid, 0); ppcDefineParamU8(slot, 1); ppcDefineParamS32(name, 2); nn::act::GetUuidEx(uuid, iosu::act::ACT_SLOT_CURRENT, name); osLib_returnFromFunction(hCPU, 0); // 0 -> result ok } void nnActExport_GetAccountId(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetAccountId(0x{:08x})", hCPU->gpr[3]); ppcDefineParamUStr(accId, 0); GetAccountIdEx((char*)accId, iosu::act::ACT_SLOT_CURRENT); osLib_returnFromFunction(hCPU, 0); } void nnActExport_GetAccountIdEx(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetAccountIdEx(0x{:08x}, 0x{:02x})", hCPU->gpr[3], hCPU->gpr[3] & 0xFF); ppcDefineParamUStr(accId, 0); ppcDefineParamU8(slot, 1); GetAccountIdEx((char*)accId, slot); osLib_returnFromFunction(hCPU, 0); } void nnActExport_GetParentalControlSlotNoEx(PPCInterpreter_t* hCPU) { // GetParentalControlSlotNoEx(uint8* output, uint8 slot) cemuLog_logDebug(LogType::Force, "nn_act.GetParentalControlSlotNoEx(0x{:08x}, 0x{:02x})", hCPU->gpr[3], hCPU->gpr[4]); //memory_writeU8(hCPU->gpr[3], 0x01); // returned slot no (slot indices start at 1) memory_writeU8(hCPU->gpr[3], 1); // 0 -> No parental control for slot? //memory_writeU8(hCPU->gpr[3], 0); // 0 -> No parental control for slot? osLib_returnFromFunction(hCPU, 0); } void nnActExport_GetDefaultAccount(PPCInterpreter_t* hCPU) { // todo cemuLog_logDebug(LogType::Force, "GetDefaultAccount(): Return 1?"); osLib_returnFromFunction(hCPU, 1); } void nnActExport_GetSlotNo(PPCInterpreter_t* hCPU) { // id of active account osLib_returnFromFunction(hCPU, 1); // 1 is the first slot (0 is invalid) } void nnActExport_GetSlotNoEx(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.GetSlotNoEx(...)"); // get slot no by uuid ppcDefineParamUStr(uuid, 0); // get slot no for a specific uuid for (uint8 i = 1; i < 12; i++) { uint8 accUuid[16]{}; nn::act::GetUuidEx(accUuid, i); if (memcmp(uuid, accUuid, 16) == 0) { osLib_returnFromFunction(hCPU, i); return; } } cemu_assert_debug(false); // suspicious behavior? osLib_returnFromFunction(hCPU, 0); // account not found } void nnActExport_Initialize(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "nn_act.Initialize()"); nn::act::Initialize(); osLib_returnFromFunction(hCPU, 0); } void nnActExport_HasNfsAccount(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "Called nn_act.HasNfsAccount"); osLib_returnFromFunction(hCPU, 1); // Nfs = Nintendo Friend System? (Splatoon tries to call nn_fp.RegisterAccount if we set this to false) } typedef struct { /* +0x000 */ char token[0x201]; // /nex_token/token /* +0x201 */ uint8 padding201[3]; /* +0x204 */ char nexPassword[0x41]; // /nex_token/nex_password /* +0x245 */ uint8 padding245[3]; /* +0x248 */ char host[0x10]; // /nex_token/host /* +0x258 */ uint16be port; // /nex_token/port /* +0x25A */ uint16be padding25A; }nexServiceToken_t; static_assert(sizeof(nexServiceToken_t) == 0x25C, "nexServiceToken_t has invalid size"); void nnActExport_AcquireNexServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(token, nexServiceToken_t, 0); ppcDefineParamU32(serverId, 1); memset(token, 0, sizeof(nexServiceToken_t)); actPrepareRequest(); actRequest->accountSlot = iosu::act::ACT_SLOT_CURRENT; actRequest->requestCode = IOSU_ARC_ACQUIRENEXTOKEN; actRequest->titleId = CafeSystem::GetForegroundTitleId(); actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); actRequest->serverId = serverId; uint32 resultCode = __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); memcpy(token, actRequest->resultBinary.binBuffer, sizeof(nexServiceToken_t)); cemuLog_logDebug(LogType::Force, "Called nn_act.AcquireNexServiceToken"); osLib_returnFromFunction(hCPU, getNNReturnCode(resultCode, actRequest)); } void nnActExport_AcquireIndependentServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(token, independentServiceToken_t, 0); ppcDefineParamMEMPTR(clientId, const char, 1); uint32 result = nn::act::AcquireIndependentServiceToken(token.GetPtr(), clientId.GetPtr(), 0); osLib_returnFromFunction(hCPU, result); } void nnActExport_AcquireIndependentServiceToken2(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(token, independentServiceToken_t, 0); ppcDefineParamMEMPTR(clientId, const char, 1); ppcDefineParamU32(cacheDurationInSeconds, 2); uint32 result = nn::act::AcquireIndependentServiceToken(token, clientId.GetPtr(), cacheDurationInSeconds); osLib_returnFromFunction(hCPU, result); } void nnActExport_AcquireEcServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(pEcServiceToken, independentServiceToken_t, 0); uint32 result = nn::act::AcquireIndependentServiceToken(pEcServiceToken.GetPtr(), "71a6f5d6430ea0183e3917787d717c46", 0); osLib_returnFromFunction(hCPU, result); } void nnActExport_AcquirePrincipalIdByAccountId(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(principalId, uint32be, 0); ppcDefineParamMEMPTR(nnid, char, 1); ppcDefineParamU32(ukn, 2); // some option, can be 0 or 1 ? cemuLog_logDebug(LogType::Force, "nn_act.AcquirePrincipalIdByAccountId(0x{:08x},\"{}\", {}) - last param unknown", principalId.GetMPTR(), nnid.GetPtr(), ukn); actPrepareRequest2(); actRequest->requestCode = IOSU_ARC_ACQUIREPIDBYNNID; strcpy(actRequest->clientId, nnid.GetPtr()); uint32 result = _doCemuActRequest(actRequest); *principalId.GetPtr() = actRequest->resultU32.u32; osLib_returnFromFunction(hCPU, result); } namespace nn::act { class : public COSModule { public: std::string_view GetName() override { return "nn_act"; } void RPLMapped() override { osLib_addFunction("nn_act", "Initialize__Q2_2nn3actFv", nnActExport_Initialize); osLib_addFunction("nn_act", "CreateConsoleAccount__Q2_2nn3actFv", nnActExport_CreateConsoleAccount); osLib_addFunction("nn_act", "GetNumOfAccounts__Q2_2nn3actFv", nnActExport_GetNumOfAccounts); osLib_addFunction("nn_act", "IsSlotOccupied__Q2_2nn3actFUc", nnActExport_IsSlotOccupied); osLib_addFunction("nn_act", "GetSlotNo__Q2_2nn3actFv", nnActExport_GetSlotNo); osLib_addFunction("nn_act", "GetSlotNoEx__Q2_2nn3actFRC7ACTUuid", nnActExport_GetSlotNoEx); osLib_addFunction("nn_act", "IsNetworkAccount__Q2_2nn3actFv", nnActExport_IsNetworkAccount); osLib_addFunction("nn_act", "IsNetworkAccountEx__Q2_2nn3actFUc", nnActExport_IsNetworkAccountEx); // account id osLib_addFunction("nn_act", "GetAccountId__Q2_2nn3actFPc", nnActExport_GetAccountId); osLib_addFunction("nn_act", "GetAccountIdEx__Q2_2nn3actFPcUc", nnActExport_GetAccountIdEx); // simple address osLib_addFunction("nn_act", "GetSimpleAddressId__Q2_2nn3actFv", nnActExport_GetSimpleAddressId); osLib_addFunction("nn_act", "GetSimpleAddressIdEx__Q2_2nn3actFPUiUc", nnActExport_GetSimpleAddressIdEx); // principal id osLib_addFunction("nn_act", "GetPrincipalId__Q2_2nn3actFv", nnActExport_GetPrincipalId); osLib_addFunction("nn_act", "GetPrincipalIdEx__Q2_2nn3actFPUiUc", nnActExport_GetPrincipalIdEx); // transferable id osLib_addFunction("nn_act", "GetTransferableId__Q2_2nn3actFUi", nnActExport_GetTransferableId); osLib_addFunction("nn_act", "GetTransferableIdEx__Q2_2nn3actFPULUiUc", nnActExport_GetTransferableIdEx); // persistent id osLib_addFunction("nn_act", "GetPersistentId__Q2_2nn3actFv", nnActExport_GetPersistentId); osLib_addFunction("nn_act", "GetPersistentIdEx__Q2_2nn3actFUc", nnActExport_GetPersistentIdEx); // country osLib_addFunction("nn_act", "GetCountry__Q2_2nn3actFPc", nnActExport_GetCountry); // timezone cafeExportRegisterFunc(nn::act::GetTimeZoneId, "nn_act", "GetTimeZoneId__Q2_2nn3actFPc", LogType::Placeholder); // parental osLib_addFunction("nn_act", "EnableParentalControlCheck__Q2_2nn3actFb", nnActExport_EnableParentalControlCheck); osLib_addFunction("nn_act", "IsParentalControlCheckEnabled__Q2_2nn3actFv", nnActExport_IsParentalControlCheckEnabled); osLib_addFunction("nn_act", "GetMii__Q2_2nn3actFP12FFLStoreData", nnActExport_GetMii); osLib_addFunction("nn_act", "GetMiiEx__Q2_2nn3actFP12FFLStoreDataUc", nnActExport_GetMiiEx); osLib_addFunction("nn_act", "GetMiiImageEx__Q2_2nn3actFPUiPvUi15ACTMiiImageTypeUc", nnActExport_GetMiiImageEx); osLib_addFunction("nn_act", "GetMiiName__Q2_2nn3actFPw", nnActExport_GetMiiName); osLib_addFunction("nn_act", "GetMiiNameEx__Q2_2nn3actFPwUc", nnActExport_GetMiiNameEx); osLib_addFunction("nn_act", "UpdateMii__Q2_2nn3actFUcRC12FFLStoreDataPCwPCvUiT4T5T4T5T4T5T4T5T4T5T4T5T4T5T4T5", nnActExport_UpdateMii); osLib_addFunction("nn_act", "GetUuid__Q2_2nn3actFP7ACTUuid", nnActExport_GetUuid); osLib_addFunction("nn_act", "GetUuidEx__Q2_2nn3actFP7ACTUuidUc", nnActExport_GetUuidEx); osLib_addFunction("nn_act", "GetUuidEx__Q2_2nn3actFP7ACTUuidUcUi", nnActExport_GetUuidEx2); osLib_addFunction("nn_act", "GetParentalControlSlotNoEx__Q2_2nn3actFPUcUc", nnActExport_GetParentalControlSlotNoEx); osLib_addFunction("nn_act", "GetDefaultAccount__Q2_2nn3actFv", nnActExport_GetDefaultAccount); osLib_addFunction("nn_act", "AcquireEcServiceToken__Q2_2nn3actFPc", nnActExport_AcquireEcServiceToken); osLib_addFunction("nn_act", "AcquireNexServiceToken__Q2_2nn3actFP26ACTNexAuthenticationResultUi", nnActExport_AcquireNexServiceToken); osLib_addFunction("nn_act", "AcquireIndependentServiceToken__Q2_2nn3actFPcPCc", nnActExport_AcquireIndependentServiceToken); osLib_addFunction("nn_act", "AcquireIndependentServiceToken__Q2_2nn3actFPcPCcUibT4", nnActExport_AcquireIndependentServiceToken2); osLib_addFunction("nn_act", "AcquireIndependentServiceToken__Q2_2nn3actFPcPCcUi", nnActExport_AcquireIndependentServiceToken2); osLib_addFunction("nn_act", "AcquirePrincipalIdByAccountId__Q2_2nn3actFPUiPA17_CcUi", nnActExport_AcquirePrincipalIdByAccountId); cafeExportRegisterFunc(nn::act::GetErrorCode, "nn_act", "GetErrorCode__Q2_2nn3actFRCQ2_2nn6Result", LogType::Placeholder); // placeholders / incomplete implementations osLib_addFunction("nn_act", "HasNfsAccount__Q2_2nn3actFv", nnActExport_HasNfsAccount); osLib_addFunction("nn_act", "GetHostServerSettings__Q2_2nn3actFPcT1Uc", nnActExport_GetHostServerSettings); cafeExportRegisterFunc(nn::act::GetUtcOffset, "nn_act", "GetUtcOffset__Q2_2nn3actFv", LogType::Placeholder); cafeExportRegisterFunc(nn::act::GetUtcOffsetEx, "nn_act", "GetUtcOffsetEx__Q2_2nn3actFPLUc", LogType::Placeholder); }; }s_COSnnActModule; COSModule* GetModule() { return &s_COSnnActModule; } } ================================================ FILE: src/Cafe/OS/libs/nn_act/nn_act.h ================================================ #pragma once #include "Cafe/IOSU/legacy/iosu_act.h" #include "Cafe/OS/RPL/COSModule.h" struct independentServiceToken_t { /* +0x000 */ char token[0x201]; }; static_assert(sizeof(independentServiceToken_t) == 0x201); // todo - verify size namespace nn { namespace act { uint32 Initialize(); uint32 GetPersistentIdEx(uint8 slot); uint32 GetUuidEx(uint8* uuid, uint8 slot, sint32 name = -2); uint32 GetSimpleAddressIdEx(uint32be* simpleAddressId, uint8 slot); uint32 GetTransferableIdEx(uint64* transferableId, uint32 unique, uint8 slot); sint64 GetUtcOffset(); sint32 GetUtcOffsetEx(sint64be* pOutOffset, uint8 slotNo); uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds); static uint32 getCountryCodeFromSimpleAddress(uint32 simpleAddressId) { return (simpleAddressId>>24)&0xFF; } const uint8 ACT_SLOT_CURRENT = 0xFE; COSModule* GetModule(); } } ================================================ FILE: src/Cafe/OS/libs/nn_aoc/nn_aoc.cpp ================================================ #include "config/ActiveSettings.h" #include "Cafe/OS/libs/nn_aoc/nn_aoc.h" #include "Cafe/OS/common/OSCommon.h" #include "Cafe/Filesystem/fsc.h" #include "Cafe/TitleList/TitleId.h" #include "Cemu/ncrypto/ncrypto.h" #include "Common/FileStream.h" namespace nn { namespace aoc { struct AOCTitle { uint64be titleId; uint32be groupId; uint16be titleVersion; char path[88]; uint8 padding[2]; }; static_assert(sizeof(AOCTitle) == 0x68); enum class AOC_RESULT : uint32 { ERROR_OK = 0, }; uint32 AOC_CalculateWorkBufferSize(uint32 count) { count = std::min(count, (uint32)256); uint32 workBufferSize = 0x80 + count * 0x61; return workBufferSize; } struct AOCCacheEntry { AOCCacheEntry(uint64 titleId) : aocTitleId(titleId) {}; uint64 aocTitleId; std::string GetPath() { return fmt::format("/vol/aoc{:016x}", aocTitleId); } }; std::vector<AOCCacheEntry> sAocCache; bool sAocCacheGenerated = false; void generateAOCList() { if (sAocCacheGenerated) return; sAocCacheGenerated = true; sint32 fscStatus; FSCVirtualFile* volDirIterator = fsc_openDirIterator("/vol", &fscStatus); cemu_assert_debug(volDirIterator); // for valid titles /vol should always exist if (volDirIterator) { FSCDirEntry dirEntry; while (fsc_nextDir(volDirIterator, &dirEntry)) { std::string_view dirName = dirEntry.GetPath(); if(!dirEntry.isDirectory) continue; // check for pattern: aoc<titleId> if(dirName.size() != (3+16)) continue; if(dirName[0] != 'a' || dirName[1] != 'o' || dirName[2] != 'c') continue; TitleId aocTitleId; if( !TitleIdParser::ParseFromStr(dirName.substr(3), aocTitleId) ) continue; // add to list of known AOC sAocCache.emplace_back(aocTitleId); } fsc_close(volDirIterator); } } AOC_RESULT AOC_ListTitle(uint32be* titleCountOut, AOCTitle* titleList, uint32 maxCount, void* workBuffer, uint32 workBufferSize) { generateAOCList(); for (uint32 i = 0; i < std::min(maxCount, (uint32)sAocCache.size()); i++) { titleList[i].titleId = sAocCache[i].aocTitleId; titleList[i].groupId = 0; // todo titleList[i].titleVersion = 0; // todo strcpy(titleList[i].path, sAocCache[i].GetPath().c_str()); } *titleCountOut = std::min(maxCount, (uint32)sAocCache.size()); return AOC_RESULT::ERROR_OK; } AOC_RESULT AOC_OpenTitle(char* pathOut, AOCTitle* aocTitleInfo, void* workBuffer, uint32 workBufferSize) { strcpy(pathOut, aocTitleInfo->path); return AOC_RESULT::ERROR_OK; } AOC_RESULT AOC_CloseTitle(void* ukn) { return AOC_RESULT::ERROR_OK; } AOC_RESULT AOC_GetPurchaseInfo(uint32be* purchaseBoolArrayOut, uint64 titleId, uint16be* entryIds, uint32 entryCount, void* workBuffer, uint32 workBufferSize) { // open ticket file // on an actual Wii U they get stored to SLC but the download manager places these in the code folder currently const auto ticketPath = ActiveSettings::GetMlcPath(L"usr/title/{:08x}/{:08x}/code/title.tik", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); uint32 tikFileSize = 0; std::unique_ptr<FileStream> fileStream(FileStream::openFile2(ticketPath)); std::vector<uint8> tikData; if (fileStream) fileStream->extract(tikData); if (tikData.size() > 0) { NCrypto::ETicketParser eTicket; if (eTicket.parse(tikData.data(), tikData.size())) { for (uint32 i = 0; i < entryCount; i++) { uint16 id = entryIds[i]; if (eTicket.CheckRight(id)) purchaseBoolArrayOut[i] = 1; else purchaseBoolArrayOut[i] = 0; } cemuLog_log(LogType::Force, "Using content rights from AOC title.tik"); return AOC_RESULT::ERROR_OK; } else { cemuLog_log(LogType::Force, "Unable to parse AOC title.tik"); } } // fallback: return true for all contentIds for (uint32 i = 0; i < entryCount; i++) { uint16 id = entryIds[i]; purchaseBoolArrayOut[i] = 1; } return AOC_RESULT::ERROR_OK; } class : public COSModule { public: std::string_view GetName() override { return "nn_aoc"; } void RPLMapped() override { cafeExportRegister("nn_aoc", AOC_CalculateWorkBufferSize, LogType::NN_AOC); cafeExportRegister("nn_aoc", AOC_ListTitle, LogType::NN_AOC); cafeExportRegister("nn_aoc", AOC_OpenTitle, LogType::NN_AOC); cafeExportRegister("nn_aoc", AOC_CloseTitle, LogType::NN_AOC); cafeExportRegister("nn_aoc", AOC_GetPurchaseInfo, LogType::NN_AOC); }; }s_COSnnAocModule; COSModule* GetModule() { return &s_COSnnAocModule; } } } ================================================ FILE: src/Cafe/OS/libs/nn_aoc/nn_aoc.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace nn::aoc { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_boss/nn_boss.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/nn_common.h" #include "Cafe/OS/libs/nn_client_service.h" #include "Cafe/OS/libs/nn_act/nn_act.h" #include "Cafe/OS/libs/nn_ac/nn_ac.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "Cafe/IOSU/legacy/iosu_act.h" #include "config/ActiveSettings.h" #include "Cafe/CafeSystem.h" #include "Cafe/Filesystem/fsc.h" #include "Common/CafeString.h" #include "Cafe/IOSU/nn/boss/boss_common.h" namespace nn { namespace boss { IPCServiceClient s_ipcClient; static constexpr BossResult resultSuccess = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); static constexpr BossResult resultInvalidParam = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); static constexpr BossResult resultUknA0220300 = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_BOSS, 0x20300); SysAllocator<coreinit::OSMutex> g_mutex; sint32 g_initCounter = 0; bool g_isInitialized = false; #define DTOR_WRAPPER(__TYPE) RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { dtor(MEMPTR<__TYPE>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }) SysAllocator<uint8, 0x10000, 64> s_ipcBuffer; constexpr uint32 BOSS_MEM_MAGIC = 0xCAFE4321; template<typename T> MEMPTR<T> boss_new() { uint32 objSize = sizeof(T); uint32be* basePtr = (uint32be*)coreinit::_weak_MEMAllocFromDefaultHeapEx(objSize + 8, 0x8); basePtr[0] = BOSS_MEM_MAGIC; basePtr[1] = objSize; return (T*)(basePtr+2); } void boss_delete(MEMPTR<void> mem) { if(!mem) return; uint32be* basePtr = (uint32be*)mem.GetPtr() - 2; if(basePtr[0] != BOSS_MEM_MAGIC) { cemuLog_log(LogType::Force, "nn_boss: Detected memory corruption"); cemu_assert_suspicious(); } coreinit::_weak_MEMFreeToDefaultHeap(basePtr); } BossResult Initialize() // Initialize__Q2_2nn4bossFv { coreinit::OSLockMutex(&g_mutex); BossResult result = 0; if(g_initCounter == 0) { g_isInitialized = true; s_ipcClient.Initialize("/dev/boss", s_ipcBuffer.GetPtr(), s_ipcBuffer.GetByteSize()); result = resultSuccess; } g_initCounter++; coreinit::OSUnlockMutex(&g_mutex); return NN_RESULT_IS_SUCCESS(result) ? 0 : result; } uint32 IsInitialized() // IsInitialized__Q2_2nn4bossFv { return g_isInitialized; } void Finalize() // Finalize__Q2_2nn4bossFv { coreinit::OSLockMutex(&g_mutex); if(g_initCounter == 0) cemuLog_log(LogType::Force, "nn_boss: Finalize() called without corresponding Initialize()"); if(g_initCounter == 1) { s_ipcClient.Shutdown(); g_isInitialized = false; } g_initCounter--; coreinit::OSUnlockMutex(&g_mutex); } uint32 GetBossState(PPCInterpreter_t* hCPU) { cemuLog_logDebugOnce(LogType::Force, "nn_boss.GetBossState() - stub"); return 7; } /* TitleId */ bool TitleId::IsValid(TitleId* _thisptr) { return _thisptr->u64 != 0; } TitleId* TitleId::ctorDefault(TitleId* _thisptr) { if (!_thisptr) _thisptr = boss_new<TitleId>(); _thisptr->u64 = 0; return _thisptr; } TitleId* TitleId::ctorFromTitleId(TitleId* _thisptr, uint64 titleId) // __ct__Q3_2nn4boss7TitleIDFUL { if (!_thisptr) _thisptr = boss_new<TitleId>(); _thisptr->u64 = titleId; return _thisptr; } TitleId* TitleId::ctorCopy(TitleId* _thisptr, TitleId* titleId) // __ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID { if (!_thisptr) _thisptr = boss_new<TitleId>(); _thisptr->u64 = titleId->u64; return _thisptr; } bool TitleId::operator_ne(TitleId* _thisptr, TitleId* titleId) { return _thisptr->u64 != titleId->u64; } /* TaskId */ TaskId* TaskId::ctorDefault(TaskId* _thisptr) { if (!_thisptr) _thisptr = boss_new<TaskId>(); _thisptr->id.data[0] = 0; return _thisptr; } TaskId* TaskId::ctorFromString(TaskId* _thisptr, const char* taskId) { if (!_thisptr) _thisptr = boss_new<TaskId>(); _thisptr->id.assign(taskId); return _thisptr; } /* Title */ Title* Title::ctor(Title* _this) { if (!_this) _this = boss_new<Title>(); *_this = {}; _this->vTablePtr = s_titleVTable; return _this; } void Title::dtor(Title* _this, uint32 options) { if (_this && (options & 1)) boss_delete(_this); } void Title::InitVTable() { s_titleVTable->rtti.ptr = nullptr; // todo s_titleVTable->dtor.ptr = DTOR_WRAPPER(Title); } /* DirectoryName */ DirectoryName* DirectoryName::ctor(DirectoryName* _thisptr) { if (!_thisptr) _thisptr = boss_new<DirectoryName>(); _thisptr->name2.ClearAllBytes(); return _thisptr; } const char* DirectoryName::operator_const_char(DirectoryName* _thisptr) { return _thisptr->name2.c_str(); } /* BossAccount */ BossAccount* BossAccount::ctor(BossAccount* _this, uint32 accountId) { if (!_this) _this = boss_new<BossAccount>(); _this->accountId = accountId; _this->vTablePtr = s_VTable; return _this; } void BossAccount::dtor(BossAccount* _this, uint32 options) { if(_this && options & 1) boss_delete(_this); } void BossAccount::InitVTable() { s_VTable->rtti.ptr = nullptr; // todo s_VTable->dtor.ptr = DTOR_WRAPPER(BossAccount); } /* TaskSetting */ TaskSetting* TaskSetting::ctor(TaskSetting* _thisptr) { if(!_thisptr) _thisptr = boss_new<TaskSetting>(); _thisptr->vTablePtr = s_VTable; InitializeSetting(_thisptr); return _thisptr; } void TaskSetting::dtor(TaskSetting* _this, uint32 options) { if(options & 1) boss_delete(_this); } bool TaskSetting::IsPrivileged(TaskSetting* _thisptr) { const TaskType taskType = _thisptr->taskType; return taskType == TaskType::RawDlTaskSetting_1 || taskType == TaskType::RawDlTaskSetting_9 || taskType == TaskType::PlayLogUploadTaskSetting; } void TaskSetting::InitializeSetting(TaskSetting* _thisptr) { memset(_thisptr, 0x00, 0x1000); _thisptr->persistentId = 0; _thisptr->ukn08 = 0; _thisptr->ukn0C = 0; _thisptr->priority = 125; _thisptr->intervalB = 28800; _thisptr->lifeTime.high = 0; _thisptr->lifeTime.low = 0x76A700; } void TaskSetting::InitVTable() { s_VTable->rtti.ptr = nullptr; // todo s_VTable->dtor.ptr = DTOR_WRAPPER(TaskSetting); s_VTable->RegisterPreprocess.ptr = nullptr; // todo s_VTable->unk1.ptr = nullptr; // todo } /* NetTaskSetting */ NetTaskSetting* NetTaskSetting::ctor(NetTaskSetting* _thisptr) { if (!_thisptr) _thisptr = boss_new<NetTaskSetting>(); TaskSetting::ctor(_thisptr); _thisptr->httpTimeout = 120; _thisptr->vTablePtr = s_VTable; return _thisptr; } BossResult NetTaskSetting::AddCaCert(NetTaskSetting* _thisptr, const char* name) { if(name == nullptr || strnlen(name, 0x80) == 0x80) { cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddCaCert: name size is invalid"); return resultInvalidParam; } cemu_assert_unimplemented(); return 0xA0220D00; } BossResult NetTaskSetting::SetServiceToken(NetTaskSetting* _thisptr, const uint8* serviceToken) // input is uint8[512]? { memcpy(_thisptr->serviceToken.data, serviceToken, _thisptr->serviceToken.Size()); return resultSuccess; } BossResult NetTaskSetting::AddInternalCaCert(NetTaskSetting* _thisptr, char certId) { for(int i = 0; i < 3; i++) { if(_thisptr->internalCaCert[i] == 0) { _thisptr->internalCaCert[i] = (uint8)certId; return resultSuccess; } } cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert: Cannot store more than 3 certificates"); return 0xA0220D00; } void NetTaskSetting::SetInternalClientCert(NetTaskSetting* _thisptr, char certId) { _thisptr->internalClientCert = (uint8)certId; } void NetTaskSetting::InitVTable() { s_VTable->rtti.ptr = nullptr; // todo s_VTable->dtor.ptr = DTOR_WRAPPER(NetTaskSetting); s_VTable->RegisterPreprocess.ptr = nullptr; // todo s_VTable->unk1.ptr = nullptr; // todo } /* NbdlTaskSetting */ NbdlTaskSetting* NbdlTaskSetting::ctor(NbdlTaskSetting* _thisptr) { if (!_thisptr) _thisptr = boss_new<NbdlTaskSetting>(); NetTaskSetting::ctor(_thisptr); _thisptr->vTablePtr = s_VTable; return _thisptr; } BossResult NbdlTaskSetting::Initialize(NbdlTaskSetting* _thisptr, const char* bossCode, uint64 directorySizeLimit, const char* bossDirectory) // Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1 { if (!bossCode || !_thisptr->nbdl.bossCode.CanHoldString(bossCode)) return resultInvalidParam; if (bossDirectory && !_thisptr->nbdl.bossDirectory.CanHoldString(bossDirectory)) return resultInvalidParam; // directory is optional, but if a string is passed it must fit _thisptr->nbdl.bossCode.assign(bossCode); if (bossDirectory) _thisptr->nbdl.bossDirectory.assign(bossDirectory); _thisptr->nbdl.directorySizeLimitHigh = (uint32be)(directorySizeLimit >> 32); _thisptr->nbdl.directorySizeLimitLow = (uint32be)(directorySizeLimit & 0xFFFFFFFF); _thisptr->taskType = TaskType::NbdlTaskSetting; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } BossResult NbdlTaskSetting::SetFileName(NbdlTaskSetting* _thisptr, const char* fileName) { if (!fileName || !_thisptr->nbdl.fileName.CanHoldString(fileName)) return resultInvalidParam; _thisptr->nbdl.fileName.assign(fileName); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } void NbdlTaskSetting::InitVTable() { s_VTable->rtti.ptr = nullptr; // todo s_VTable->dtor.ptr = DTOR_WRAPPER(NbdlTaskSetting); s_VTable->RegisterPreprocess.ptr = nullptr; // todo s_VTable->unk1.ptr = nullptr; // todo s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo } /* RawUlTaskSetting */ RawUlTaskSetting* RawUlTaskSetting::ctor(RawUlTaskSetting* _thisptr) { if (!_thisptr) _thisptr = boss_new<RawUlTaskSetting>(); NetTaskSetting::ctor(_thisptr); _thisptr->vTablePtr = s_VTable; _thisptr->ukRaw1 = 0; _thisptr->ukRaw2 = 0; _thisptr->ukRaw3 = 0; memset(_thisptr->rawSpace, 0x00, 0x200); return _thisptr; } void RawUlTaskSetting::dtor(RawUlTaskSetting* _this, uint32 options) { cemuLog_logDebug(LogType::Force, "nn::boss::RawUlTaskSetting::dtor() is todo"); } void RawUlTaskSetting::InitVTable() { s_VTable->rtti.ptr = nullptr; // todo s_VTable->dtor.ptr = DTOR_WRAPPER(RawUlTaskSetting); s_VTable->RegisterPreprocess.ptr = nullptr; // todo s_VTable->unk1.ptr = nullptr; // todo s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo } /* RawDlTaskSetting */ RawDlTaskSetting* RawDlTaskSetting::ctor(RawDlTaskSetting* _thisptr) { if (!_thisptr) _thisptr = boss_new<RawDlTaskSetting>(); NetTaskSetting::ctor(_thisptr); _thisptr->vTablePtr = s_VTable; return _thisptr; } BossResult RawDlTaskSetting::Initialize(RawDlTaskSetting* _thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* bossDirectory) { if (!url || !_thisptr->url.CanHoldString(url)) return resultInvalidParam; cemuLog_logDebug(LogType::Force, "RawDlTaskSetting::Initialize url: {}", url); if (fileName && !_thisptr->rawDl.fileName.CanHoldString(fileName)) return resultInvalidParam; // fileName is optional, but if a string is passed it must fit if (!bossDirectory || !_thisptr->rawDl.bossDirectory.CanHoldString(bossDirectory)) return resultInvalidParam; _thisptr->url.assign(url); _thisptr->rawDl.fileName.assign(fileName ? fileName : "rawcontent.dat"); _thisptr->rawDl.bossDirectory.assign(bossDirectory); _thisptr->rawDl.newArrival = newArrival; _thisptr->rawDl.led = led; _thisptr->taskType = TaskType::RawDlTaskSetting_3; return resultSuccess; } void RawDlTaskSetting::InitVTable() { s_VTable->rtti.ptr = nullptr; // todo s_VTable->dtor.ptr = DTOR_WRAPPER(RawDlTaskSetting); s_VTable->RegisterPreprocess.ptr = nullptr; // todo s_VTable->unk1.ptr = nullptr; // todo s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo } /* PlayReportSetting */ PlayReportSetting* PlayReportSetting::ctor(PlayReportSetting* _this) { if(!_this) _this = boss_new<PlayReportSetting>(); RawUlTaskSetting::ctor(_this); _this->vTablePtr = s_VTable; _this->ukn1210_ptr = nullptr; _this->ukn1214_size = 0; _this->ukPlay3 = 0; _this->ukPlay4 = 0; return _this; } void PlayReportSetting::dtor(PlayReportSetting* _this, uint32 options) { RawUlTaskSetting::dtor(_this, 0); if(options&1) boss_delete(_this->ukn1210_ptr.GetPtr()); } void PlayReportSetting::Initialize(PlayReportSetting* _this, uint8* ptr, uint32 size) { if (!ptr || size == 0 || size > 0x19000) { cemuLog_logDebug(LogType::Force, "nn::boss::PlayReportSetting::Initialize: invalid parameter"); return; } *ptr = 0; _this->taskType = TaskType::PlayReportSetting; _this->mode |= 3; _this->rawUl.optionValue |= 2; _this->permission |= 0xA; _this->ukn1210_ptr = ptr; _this->ukn1214_size = size; _this->ukPlay3 = 0; _this->ukPlay4 = 0; _this->AddInternalCaCert(_this, 102); _this->SetInternalClientCert(_this, 1); // todo - incomplete } bool PlayReportSetting::Set(PlayReportSetting* _this, const char* keyname, uint32 value) { // TODO return true; } void PlayReportSetting::InitVTable() { s_VTable->rtti.ptr = nullptr; // todo s_VTable->dtor.ptr = DTOR_WRAPPER(PlayReportSetting); s_VTable->RegisterPreprocess.ptr = nullptr; // todo s_VTable->unk1.ptr = nullptr; // todo s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo } /* Task */ struct Task { struct VTableTask { VTableEntry rtti; VTableEntry dtor; }; static inline SysAllocator<VTableTask> s_vTable; uint32be persistentId; // 0x00 uint32be uk2; // 0x04 TaskId taskId; // 0x08 TitleId titleId; // 0x10 MEMPTR<VTableTask> vTablePtr; // 0x18 uint32be padding; // 0x1C static BossResult Initialize1(Task* _thisptr, const char* taskId, uint32 persistentId) // Initialize__Q3_2nn4boss4TaskFPCcUi { if (!taskId || !_thisptr->taskId.id.CanHoldString(taskId)) return resultInvalidParam; _thisptr->persistentId = persistentId; _thisptr->taskId.id.assign(taskId); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } static BossResult Initialize2(Task* _thisptr, uint8 slot, const char* taskId) // Initialize__Q3_2nn4boss4TaskFUcPCc { const uint32 persistentId = slot == 0 ? 0 : act::GetPersistentIdEx(slot); return Initialize1(_thisptr, taskId, persistentId); } static BossResult Initialize3(Task* _thisptr, const char* taskId) // Initialize__Q3_2nn4boss4TaskFPCc { return Initialize1(_thisptr, taskId, 0); } static Task* ctor2(Task* _thisptr, const char* taskId, uint32 persistentId) // __ct__Q3_2nn4boss4TaskFPCcUi { if (!_thisptr) _thisptr = boss_new<Task>(); _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; TaskId::ctorDefault(&_thisptr->taskId); TitleId::ctorFromTitleId(&_thisptr->titleId, 0); auto r = Initialize1(_thisptr, taskId, persistentId); cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); return _thisptr; } static Task* ctor1(Task* _thisptr, uint8 slot, const char* taskId) // __ct__Q3_2nn4boss4TaskFUcPCc { if (!_thisptr) _thisptr = boss_new<Task>(); _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; TaskId::ctorDefault(&_thisptr->taskId); TitleId::ctorFromTitleId(&_thisptr->titleId, 0); auto r = Initialize2(_thisptr, slot, taskId); cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); return _thisptr; } static Task* ctor3(Task* _thisptr, const char* taskId) // __ct__Q3_2nn4boss4TaskFPCc { if (!_thisptr) _thisptr = boss_new<Task>(); _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; TaskId::ctorDefault(&_thisptr->taskId); TitleId::ctorFromTitleId(&_thisptr->titleId, 0); auto r = Initialize3(_thisptr, taskId); cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); return _thisptr; } static Task* ctor4(Task* _thisptr) // __ct__Q3_2nn4boss4TaskFv { if (!_thisptr) _thisptr = boss_new<Task>(); _thisptr->persistentId = 0; _thisptr->vTablePtr = s_vTable; TaskId::ctorDefault(&_thisptr->taskId); TitleId::ctorFromTitleId(&_thisptr->titleId, 0); memset(&_thisptr->taskId, 0x00, sizeof(TaskId)); return _thisptr; } static void dtor(Task* _this, uint32 options) // __dt__Q3_2nn4boss4TaskFv { // todo - Task::Finalize if(options & 1) boss_delete(_this); } static BossResult Run(Task* _thisptr, bool isForegroundRun) { uint8be isConnected = 0; nn_ac::IsApplicationConnected(&isConnected); if (isForegroundRun && !isConnected) { cemuLog_logDebug(LogType::Force, "nn::boss::Task::Run: Application is not connected, returning error"); return 0xA0223A00; } IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskRun)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); serviceCall.WriteParam<uint8>(isForegroundRun ? 1 : 0); return serviceCall.Submit(); } static BossResult StartScheduling(Task* _thisptr, uint8 executeImmediately) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskStartScheduling)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); serviceCall.WriteParam<uint8be>(executeImmediately); return serviceCall.Submit(); } static nnResult StopScheduling(Task* _thisptr) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskStopScheduling)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); return serviceCall.Submit(); } static bool IsRegistered(Task* _thisptr) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskIsRegistered)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); nnResult r = serviceCall.Submit(); if (NN_RESULT_IS_FAILURE(r)) return false; return serviceCall.ReadResponse<uint8be>() != 0; } static bool Wait(Task* _thisptr, uint32 timeout, TaskWaitState waitState) // Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState { static_assert(sizeof(TaskSettingCore) == 0xC00); IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskWaitA)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); serviceCall.WriteParam<betype<TaskWaitState>>(waitState); serviceCall.WriteParam<uint32be>(timeout); nnResult r = serviceCall.Submit(); if (NN_RESULT_IS_FAILURE(r)) return false; uint8 isNotTimeout = serviceCall.ReadResponse<uint8be>(); return isNotTimeout; } static BossResult RegisterForImmediateRun(Task* _thisptr, TaskSetting* settings) // RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting { static_assert(sizeof(TaskSettingCore) == 0xC00); IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskRegisterForImmediateRunA)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); serviceCall.WriteParamBuffer(settings, sizeof(TaskSettingCore)); return serviceCall.Submit(); } static BossResult Unregister(Task* _thisptr) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskUnregister)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); return serviceCall.Submit(); } static BossResult Register(Task* _thisptr, TaskSetting* settings) { static_assert(sizeof(TaskSettingCore) == 0xC00); if (!settings) { cemuLog_logDebug(LogType::Force, "nn_boss_Task_Register - crash workaround (fix me)"); // settings should never be zero return 0; } // todo - missing vcall which leads to nn::boss::PlayReportSetting::RegisterPreprocess (and other functions?) being called? IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskRegisterA)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); serviceCall.WriteParamBuffer(settings, sizeof(TaskSettingCore)); return serviceCall.Submit(); } static TaskTurnState GetTurnState(Task* _thisptr, uint32be* executionCounter) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetTurnState)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); nnResult r = serviceCall.Submit(); if (NN_RESULT_IS_FAILURE(r)) return (TaskTurnState)0; uint32 executionCount = serviceCall.ReadResponse<uint32be>(); if (executionCounter) *executionCounter = executionCount; return serviceCall.ReadResponse<betype<TaskTurnState>>(); } static uint64 GetContentLength(Task* _thisptr, uint32be* executionCounter) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetContentLength)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); nnResult r = serviceCall.Submit(); if (NN_RESULT_IS_FAILURE(r)) return 0; uint32 executionCount = serviceCall.ReadResponse<uint32be>(); if (executionCounter) *executionCounter = executionCount; return serviceCall.ReadResponse<uint64be>(); } static uint64 GetProcessedLength(Task* _thisptr, uint32be* executionCounter) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetProcessedLength)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); nnResult r = serviceCall.Submit(); if (NN_RESULT_IS_FAILURE(r)) return 0; uint32 executionCount = serviceCall.ReadResponse<uint32be>(); if (executionCounter) *executionCounter = executionCount; return serviceCall.ReadResponse<uint64be>(); } static uint32 GetHttpStatusCode(Task* _thisptr, uint32be* executionCounter) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::TaskGetHttpStatusCodeA)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<TaskId>(_thisptr->taskId); nnResult r = serviceCall.Submit(); if (NN_RESULT_IS_FAILURE(r)) return 0; uint32 executionCount = serviceCall.ReadResponse<uint32be>(); if (executionCounter) *executionCounter = executionCount; return serviceCall.ReadResponse<uint32be>(); } static void InitVTable() { s_vTable->rtti.ptr = nullptr; // todo s_vTable->dtor.ptr = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { Task::dtor(MEMPTR<Task>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }); } }; static_assert(sizeof(Task) == 0x20); struct PrivilegedTask : Task { struct VTablePrivilegedTask : public VTableTask { VTableEntry rttiTask; }; static_assert(sizeof(VTablePrivilegedTask) == 8*3); static inline SysAllocator<VTablePrivilegedTask> s_VTable; static PrivilegedTask* ctor(PrivilegedTask* _thisptr) { if (!_thisptr) _thisptr = boss_new<PrivilegedTask>(); Task::ctor4(_thisptr); _thisptr->vTablePtr = s_VTable; return _thisptr; } static void dtor(PrivilegedTask* _this, uint32 options) { if(!_this) return; Task::dtor(_this, 0); if(options & 1) boss_delete(_this); } static void InitVTable() { s_VTable->rtti.ptr = nullptr; // todo s_VTable->dtor.ptr = DTOR_WRAPPER(PrivilegedTask); s_VTable->rttiTask.ptr = nullptr; // todo } }; static_assert(sizeof(PrivilegedTask) == 0x20); struct AlmightyTask : PrivilegedTask { struct VTableAlmightyTask : public VTablePrivilegedTask {}; static_assert(sizeof(VTableAlmightyTask) == 8*3); static inline SysAllocator<VTableAlmightyTask> s_VTable; static AlmightyTask* ctor(AlmightyTask* _thisptr) { if (!_thisptr) _thisptr = boss_new<AlmightyTask>(); PrivilegedTask::ctor(_thisptr); _thisptr->vTablePtr = s_VTable; return _thisptr; } static void dtor(AlmightyTask* _thisptr, uint32 options) { if (!_thisptr) return; PrivilegedTask::dtor(_thisptr, 0); if(options&1) boss_delete(_thisptr); } static uint32 Initialize(AlmightyTask* _thisptr, TitleId* titleId, const char* taskId, uint32 accountId) { if (!_thisptr) return resultInvalidParam; _thisptr->persistentId = accountId; _thisptr->titleId.u64 = titleId->u64; _thisptr->taskId.id.assign(taskId); return resultSuccess; } static void InitVTable() { s_VTable->rtti.ptr = nullptr; // todo s_VTable->dtor.ptr = DTOR_WRAPPER(AlmightyTask); s_VTable->rttiTask.ptr = nullptr; // todo } }; static_assert(sizeof(AlmightyTask) == 0x20); DataName* DataName::ctor(DataName* _this) { if(!_this) _this = boss_new<DataName>(); _this->name.ClearAllBytes(); return _this; } const char* DataName::operator_const_char(DataName* _this) { return _this->name.c_str(); } struct BossStorageFadEntry { CafeString<32> name; uint32be dataId; uint32 ukn24; uint32 ukn28; uint32 ukn2C; uint32 ukn30; uint32be timestampRelated; // unsure }; static_assert(sizeof(BossStorageFadEntry) == 0x38); struct Storage { static_assert(sizeof(DirectoryName) == 8); struct VTableStorage { VTableEntry rtti; VTableEntry dtor; }; static inline SysAllocator<VTableStorage> s_vTable; enum StorageKind { kStorageKind_NBDL, kStorageKind_RawDl, }; /* +0x00 */ uint32be persistentId; /* +0x04 */ uint32be storageKind; /* +0x08 */ uint8 ukn08Array[3]; /* +0x0B */ DirectoryName storageName; uint8 ukn13; uint8 ukn14; uint8 ukn15; uint8 ukn16; uint8 ukn17; /* +0x18 */ nn::boss::TitleId titleId; /* +0x20 */ MEMPTR<VTableStorage> vTablePtr; /* +0x24 */ uint32be ukn24; static nn::boss::Storage* ctor1(nn::boss::Storage* _this) // __ct__Q3_2nn4boss7StorageFv { if(!_this) _this = boss_new<nn::boss::Storage>(); _this->vTablePtr = s_vTable; _this->titleId.u64 = 0; return _this; } static void dtor(nn::boss::Storage* _this, uint32 options) // __dt__Q3_2nn4boss7StorageFv { Finalize(_this); if(options & 1) boss_delete(_this); } static BossResult Initialize(Storage* _thisptr, const char* storageName, uint32 accountId, StorageKind type) { if (!storageName) return resultInvalidParam; _thisptr->storageKind = type; _thisptr->titleId.u64 = 0; _thisptr->storageName.name2.assign(storageName); _thisptr->persistentId = accountId; return resultSuccess; } static BossResult Initialize2(Storage* _thisptr, const char* dirName, StorageKind type) { return Initialize(_thisptr, dirName, 0, type); } static void Finalize(Storage* _this) { memset(_this, 0, sizeof(Storage)); // todo - not all fields might be cleared } static bool Exist(nn::boss::Storage* _thisptr) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::StorageExist)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<DirectoryName>(_thisptr->storageName); serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storageKind); nnResult r = serviceCall.Submit(); if (NN_RESULT_IS_FAILURE(r)) return 0; return serviceCall.ReadResponse<uint8be>(); } static nnResult GetDataList(nn::boss::Storage* _thisptr, DataName* dataList, sint32 maxEntries, uint32be* outputEntryCount, uint32 startIndex) // GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2 { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::StorageGetDataList)); serviceCall.WriteParam<uint32be>(_thisptr->persistentId); serviceCall.WriteParam<DirectoryName>(_thisptr->storageName); serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storageKind); serviceCall.WriteResponseBuffer(dataList, sizeof(DataName) * maxEntries); serviceCall.WriteParam<uint32be>(startIndex); nnResult r = serviceCall.Submit(); uint32be outputCount = serviceCall.ReadResponse<uint32be>(); bool isSuccessByte = serviceCall.ReadResponse<uint8be>() != 0; if (!isSuccessByte) return resultUknA0220300; if (outputEntryCount) *outputEntryCount = outputCount; return r; } static void InitVTable() { s_vTable->rtti.ptr = nullptr; // todo s_vTable->dtor.ptr = DTOR_WRAPPER(Storage); } }; static_assert(sizeof(Storage) == 0x28); static_assert(offsetof(Storage, storageKind) == 0x04); static_assert(offsetof(Storage, ukn08Array) == 0x08); static_assert(offsetof(Storage, storageName) == 0x0B); static_assert(offsetof(Storage, titleId) == 0x18); struct AlmightyStorage : Storage { struct VTableAlmightyStorage : public VTableStorage { VTableEntry rttiStorage; }; static_assert(sizeof(VTableAlmightyStorage) == 8*3); static inline SysAllocator<VTableAlmightyStorage> s_VTable; static AlmightyStorage* ctor(AlmightyStorage* _thisptr) { if (!_thisptr) _thisptr = boss_new<AlmightyStorage>(); Storage::ctor1(_thisptr); _thisptr->vTablePtr = s_VTable; return _thisptr; } static uint32 Initialize(AlmightyStorage* _thisptr, TitleId* titleId, const char* storageName, uint32 accountId, StorageKind storageKind) { if (!_thisptr) return resultInvalidParam; _thisptr->persistentId = accountId; _thisptr->storageKind = storageKind; _thisptr->titleId.u64 = titleId->u64; _thisptr->storageName.name2.assign(storageName); return resultSuccess; } static void InitVTable() { s_VTable->rtti.ptr = nullptr; // todo s_VTable->dtor.ptr = DTOR_WRAPPER(AlmightyStorage); s_VTable->rttiStorage.ptr = nullptr; // todo } }; static_assert(sizeof(AlmightyStorage) == 0x28); // NsData struct NsData { struct VTableNsData { VTableEntry rtti; VTableEntry dtor; }; static inline SysAllocator<VTableNsData> s_vTable; /* +0x00 */ DataName name; /* +0x20 */ Storage storage; /* +0x48 */ uint64be currentSeek; /* +0x50 */ MEMPTR<void> vTablePtr; /* +0x54 */ uint32 ukn54; static NsData* ctor(NsData* _this) { if (!_this) _this = boss_new<NsData>(); _this->vTablePtr = s_vTable; DataName::ctor(&_this->name); _this->storage.ctor1(&_this->storage); _this->currentSeek = 0; return _this; } static void dtor(NsData* _this, uint32 options) // __dt__Q3_2nn4boss6NsDataFv { _this->storage.dtor(&_this->storage, 0); // todo if(options & 1) boss_delete(_this); } static BossResult Initialize(NsData* _this, nn::boss::Storage* storage, const char* dataName) { if(dataName == nullptr) { if (storage->storageKind != 1) { return resultInvalidParam; } } _this->storage.persistentId = storage->persistentId; _this->storage.storageKind = storage->storageKind; memcpy(_this->storage.ukn08Array, storage->ukn08Array, 3); _this->storage.storageName = storage->storageName; _this->storage.titleId = storage->titleId; _this->storage = *storage; if (dataName != nullptr || storage->storageKind != 1) _this->name.name.assign(dataName); else _this->name.name.assign("rawcontent.dat"); _this->currentSeek = 0; return resultSuccess; } static std::string _GetPath(NsData* nsData) { uint32 accountId = nsData->storage.persistentId; if (accountId == 0) accountId = iosuAct_getAccountIdOfCurrentAccount(); uint64 title_id = nsData->storage.titleId.u64; if (title_id == 0) title_id = CafeSystem::GetForegroundTitleId(); fs::path path = fmt::format("cemuBossStorage/{:08x}/{:08x}/user/{:08x}", (uint32)(title_id >> 32), (uint32)(title_id & 0xFFFFFFFF), accountId); path /= nsData->storage.storageName.name2.c_str(); path /= nsData->name.c_str(); return path.string(); } static BossResult DeleteRealFile(NsData* _thisptr) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataDeleteFile)); serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); serviceCall.WriteParam<DataName>(_thisptr->name); return serviceCall.Submit(); } static BossResult DeleteRealFileWithHistory(NsData* _thisptr) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataDeleteFileWithHistory)); serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); serviceCall.WriteParam<DataName>(_thisptr->name); return serviceCall.Submit(); } static uint32 Exist(NsData* _thisptr) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataExist)); serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); serviceCall.WriteParam<DataName>(_thisptr->name); nnResult r = serviceCall.Submit(); if (NN_RESULT_IS_FAILURE(r)) return 0; return serviceCall.ReadResponse<uint8be>(); } static uint64 GetSize(NsData* _thisptr) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataGetSize)); serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); serviceCall.WriteParam<DataName>(_thisptr->name); nnResult r = serviceCall.Submit(); if (NN_RESULT_IS_FAILURE(r)) return 0; return serviceCall.ReadResponse<uint64be>(); } static uint64 GetCreatedTime(NsData* nsData) { cemuLog_logDebug(LogType::Force, "nn_boss.NsData_GetCreatedTime() not implemented. Returning 0"); uint64 createdTime = 0; // cmdId 0x97 // probably uses FS stat query return createdTime; } static sint32 ReadWithSizeOut(NsData* _thisptr, uint64be* sizeOut, uint8* buffer, sint32 length) { IPCServiceClient::IPCServiceCall serviceCall = s_ipcClient.Begin(0, stdx::to_underlying(BossCommandId::NsDataRead)); serviceCall.WriteParam<uint32be>(_thisptr->storage.persistentId); serviceCall.WriteParam<DirectoryName>(_thisptr->storage.storageName); serviceCall.WriteParam<betype<StorageKind>>(_thisptr->storage.storageKind); serviceCall.WriteParam<DataName>(_thisptr->name); serviceCall.WriteResponseBuffer(buffer, length); uint64 readOffset = _thisptr->currentSeek; serviceCall.WriteParam<uint64be>(readOffset); nnResult r = serviceCall.Submit(); if (NN_RESULT_IS_FAILURE(r)) return r; uint64 numReadBytes = serviceCall.ReadResponse<uint64be>(); _thisptr->currentSeek += numReadBytes; cemu_assert(sizeOut); cemu_assert(numReadBytes <= length); *sizeOut = numReadBytes; return r; } static sint32 Read(NsData* _thisptr, uint8* buffer, sint32 length) { uint64be bytesRead = 0; return ReadWithSizeOut(_thisptr, &bytesRead, buffer, length); } enum class NsDataSeekMode { Beginning = 0, Relative = 1, Ending = 2 }; static BossResult Seek(NsData* _thisptr, sint64 seekPos, NsDataSeekMode mode) { if (mode == NsDataSeekMode::Beginning) { _thisptr->currentSeek = seekPos; } else if (mode == NsDataSeekMode::Relative) { _thisptr->currentSeek += seekPos; } else if (mode == NsDataSeekMode::Ending) { uint64 fileSize = GetSize(_thisptr); _thisptr->currentSeek = fileSize + seekPos; } else { cemu_assert_unimplemented(); } return resultSuccess; } static void InitVTable() { s_vTable->rtti.ptr = nullptr; // todo s_vTable->dtor.ptr = DTOR_WRAPPER(NsData); } }; static_assert(sizeof(NsData) == 0x58); } } namespace nn::boss { class : public COSModule { public: std::string_view GetName() override { return "nn_boss"; } void RPLMapped() override { cafeExportRegisterFunc(nn::boss::GetBossState, "nn_boss", "GetBossState__Q2_2nn4bossFv", LogType::NN_BOSS); // boss lib cafeExportRegisterFunc(nn::boss::Initialize, "nn_boss", "Initialize__Q2_2nn4bossFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::IsInitialized, "nn_boss", "IsInitialized__Q2_2nn4bossFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Finalize, "nn_boss", "Finalize__Q2_2nn4bossFv", LogType::NN_BOSS); // taskId cafeExportRegisterFunc(nn::boss::TaskId::ctorDefault, "nn_boss", "__ct__Q3_2nn4boss6TaskIDFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::TaskId::ctorFromString, "nn_boss", "__ct__Q3_2nn4boss6TaskIDFPCc", LogType::NN_BOSS); // task nn::boss::Task::InitVTable(); cafeExportRegisterFunc(nn::boss::Task::ctor1, "nn_boss", "__ct__Q3_2nn4boss4TaskFUcPCc", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::ctor2, "nn_boss", "__ct__Q3_2nn4boss4TaskFPCcUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::ctor3, "nn_boss", "__ct__Q3_2nn4boss4TaskFPCc", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::ctor4, "nn_boss", "__ct__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::dtor, "nn_boss", "__dt__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::Initialize1, "nn_boss", "Initialize__Q3_2nn4boss4TaskFPCcUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::Initialize2, "nn_boss", "Initialize__Q3_2nn4boss4TaskFUcPCc", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::Initialize3, "nn_boss", "Initialize__Q3_2nn4boss4TaskFPCc", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::Run, "nn_boss", "Run__Q3_2nn4boss4TaskFb", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::Wait, "nn_boss", "Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::GetTurnState, "nn_boss", "GetTurnState__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::GetHttpStatusCode, "nn_boss", "GetHttpStatusCode__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::GetContentLength, "nn_boss", "GetContentLength__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::GetProcessedLength, "nn_boss", "GetProcessedLength__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::Register, "nn_boss", "Register__Q3_2nn4boss4TaskFRQ3_2nn4boss11TaskSetting", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::Unregister, "nn_boss", "Unregister__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::IsRegistered, "nn_boss", "IsRegistered__Q3_2nn4boss4TaskCFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::RegisterForImmediateRun, "nn_boss", "RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::StartScheduling, "nn_boss", "StartScheduling__Q3_2nn4boss4TaskFb", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Task::StopScheduling, "nn_boss", "StopScheduling__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); // TaskSetting nn::boss::TaskSetting::InitVTable(); cafeExportRegisterFunc(nn::boss::TaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss11TaskSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::TaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss11TaskSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::TaskSetting::IsPrivileged, "nn_boss", "Initialize__Q3_2nn4boss11TaskSettingFPCcUi", LogType::NN_BOSS); // NbdlTaskSetting nn::boss::NbdlTaskSetting::InitVTable(); cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss15NbdlTaskSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss15NbdlTaskSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::SetFileName, "nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", LogType::NN_BOSS); // PlayReportSetting nn::boss::PlayReportSetting::InitVTable(); cafeExportRegisterFunc(nn::boss::PlayReportSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss17PlayReportSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::PlayReportSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss17PlayReportSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::PlayReportSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss17PlayReportSettingFPvUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::PlayReportSetting::Set, "nn_boss", "Set__Q3_2nn4boss17PlayReportSettingFPCcUi", LogType::NN_BOSS); // RawDlTaskSetting nn::boss::RawDlTaskSetting::InitVTable(); cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss16RawDlTaskSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss16RawDlTaskSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss16RawDlTaskSettingFPCcbT2N21", LogType::NN_BOSS); // NetTaskSetting nn::boss::NetTaskSetting::InitVTable(); cafeExportRegisterFunc(nn::boss::NetTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss14NetTaskSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NetTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss14NetTaskSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetServiceToken, "nn_boss", "SetServiceToken__Q3_2nn4boss14NetTaskSettingFPCUc", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NetTaskSetting::AddInternalCaCert, "nn_boss", "AddInternalCaCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetInternalClientCert, "nn_boss", "SetInternalClientCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::NN_BOSS); // Title nn::boss::Title::InitVTable(); cafeExportRegisterFunc(nn::boss::Title::ctor, "nn_boss", "__ct__Q3_2nn4boss5TitleFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Title::dtor, "nn_boss", "__dt__Q3_2nn4boss5TitleFv", LogType::NN_BOSS); // cafeExportMakeWrapper<nn::boss::Title::SetNewArrivalFlagOff>("nn_boss", "SetNewArrivalFlagOff__Q3_2nn4boss5TitleFv"); SMM bookmarks // TitleId cafeExportRegisterFunc(nn::boss::TitleId::ctorDefault, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::TitleId::ctorFromTitleId, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFUL", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::TitleId::ctorCopy, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::TitleId::operator_ne, "nn_boss", "__ne__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); // DataName cafeExportRegisterFunc(nn::boss::DataName::ctor, "nn_boss", "__ct__Q3_2nn4boss8DataNameFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::DataName::operator_const_char, "nn_boss", "__opPCc__Q3_2nn4boss8DataNameCFv", LogType::NN_BOSS); // DirectoryName cafeExportRegisterFunc(nn::boss::DirectoryName::ctor, "nn_boss", "__ct__Q3_2nn4boss13DirectoryNameFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::DirectoryName::operator_const_char, "nn_boss", "__opPCc__Q3_2nn4boss13DirectoryNameCFv", LogType::NN_BOSS); // Account nn::boss::BossAccount::InitVTable(); cafeExportRegisterFunc(nn::boss::BossAccount::ctor, "nn_boss", "__ct__Q3_2nn4boss7AccountFUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::BossAccount::dtor, "nn_boss", "__dt__Q3_2nn4boss7AccountFv", LogType::NN_BOSS); // AlmightyTask nn::boss::AlmightyTask::InitVTable(); cafeExportRegisterFunc(nn::boss::AlmightyTask::ctor, "nn_boss", "__ct__Q3_2nn4boss12AlmightyTaskFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::AlmightyTask::Initialize, "nn_boss", "Initialize__Q3_2nn4boss12AlmightyTaskFQ3_2nn4boss7TitleIDPCcUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::AlmightyTask::dtor, "nn_boss", "__dt__Q3_2nn4boss12AlmightyTaskFv", LogType::NN_BOSS); // Storage nn::boss::Storage::InitVTable(); cafeExportRegisterFunc(nn::boss::Storage::ctor1, "nn_boss", "__ct__Q3_2nn4boss7StorageFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Storage::dtor, "nn_boss", "__dt__Q3_2nn4boss7StorageFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Storage::Finalize, "nn_boss", "Finalize__Q3_2nn4boss7StorageFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Storage::Exist, "nn_boss", "Exist__Q3_2nn4boss7StorageCFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Storage::GetDataList, "nn_boss", "GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Storage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcUiQ3_2nn4boss11StorageKind", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::Storage::Initialize2, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcQ3_2nn4boss11StorageKind", LogType::NN_BOSS); // AlmightyStorage nn::boss::AlmightyStorage::InitVTable(); cafeExportRegisterFunc(nn::boss::AlmightyStorage::ctor, "nn_boss", "__ct__Q3_2nn4boss15AlmightyStorageFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::AlmightyStorage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15AlmightyStorageFQ3_2nn4boss7TitleIDPCcUiQ3_2nn4boss11StorageKind", LogType::NN_BOSS); // NsData nn::boss::NsData::InitVTable(); cafeExportRegisterFunc(nn::boss::NsData::ctor, "nn_boss", "__ct__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::dtor, "nn_boss", "__dt__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::Initialize, "nn_boss", "Initialize__Q3_2nn4boss6NsDataFRCQ3_2nn4boss7StoragePCc", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::DeleteRealFile, "nn_boss", "DeleteRealFile__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::DeleteRealFileWithHistory, "nn_boss", "DeleteRealFileWithHistory__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::Exist, "nn_boss", "Exist__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::GetSize, "nn_boss", "GetSize__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::GetCreatedTime, "nn_boss", "GetCreatedTime__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::Read, "nn_boss", "Read__Q3_2nn4boss6NsDataFPvUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::ReadWithSizeOut, "nn_boss", "Read__Q3_2nn4boss6NsDataFPLPvUi", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NsData::Seek, "nn_boss", "Seek__Q3_2nn4boss6NsDataFLQ3_2nn4boss12PositionBase", LogType::NN_BOSS); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { OSInitMutexEx(&nn::boss::g_mutex, nullptr); nn::boss::g_initCounter = 0; nn::boss::g_isInitialized = false; } else if (reason == coreinit::RplEntryReason::Unloaded) { } } }s_COSnnBossModule; COSModule* GetModule() { return &s_COSnnBossModule; } } ================================================ FILE: src/Cafe/OS/libs/nn_boss/nn_boss.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace nn::boss { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_ccr/nn_ccr.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "nn_ccr.h" namespace nn::ccr { sint32 CCRSysCaffeineBootCheck() { cemuLog_logDebug(LogType::Force, "CCRSysCaffeineBootCheck()"); return -1; } class : public COSModule { public: std::string_view GetName() override { return "nn_ccr"; } void RPLMapped() override { cafeExportRegister("nn_ccr", CCRSysCaffeineBootCheck, LogType::Placeholder); }; }s_COSnnccrModule; COSModule* GetModule() { return &s_COSnnccrModule; } } ================================================ FILE: src/Cafe/OS/libs/nn_ccr/nn_ccr.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace nn::ccr { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_client_service.h ================================================ #pragma once #include "Cafe/OS/libs/coreinit/coreinit_IPC.h" #include <boost/container/static_vector.hpp> class IPCServiceClient { public: class IPCServiceCall { struct IOVectorBuffer { IOVectorBuffer() = default; IOVectorBuffer(uint8* ptr, uint32 size, bool isExternalBuffer = false) : ptr(ptr), size(size), isExternalBuffer(isExternalBuffer) {} uint8* ptr; uint32 size; bool isExternalBuffer{false}; // buffer provided by caller }; public: IPCServiceCall(IPCServiceClient& client, uint32 serviceId, uint32 commandId) : m_client(client) { // allocate a parameter and response buffer IPCBuffer* cmdBuf = client.AllocateCommandBuffer(); m_paramBuffers.emplace_back(cmdBuf->data, sizeof(IPCBuffer)); cmdBuf = client.AllocateCommandBuffer(); m_responseBuffers.emplace_back(cmdBuf->data, sizeof(IPCBuffer)); // write the request header WriteParam<uint32be>(0); // ukn00 WriteParam<uint32be>(serviceId); // serviceId WriteParam<uint32be>(0); // ukn08 WriteParam<uint32be>(commandId); // commandId } ~IPCServiceCall() { for (auto& buf : m_paramBuffers) { if (buf.isExternalBuffer) continue; m_client.ReleaseCommandBuffer((IPCBuffer*)buf.ptr); } for (auto& buf : m_responseBuffers) { if (buf.isExternalBuffer) continue; m_client.ReleaseCommandBuffer((IPCBuffer*)buf.ptr); } } IPCServiceCall(const IPCServiceCall&) = delete; IPCServiceCall& operator=(const IPCServiceCall&) = delete; template<typename T> void WriteParam(const T& value) { cemu_assert(m_paramWriteIndex + sizeof(T) <= m_paramBuffers[0].size); memcpy(m_paramBuffers[0].ptr + m_paramWriteIndex, &value, sizeof(T)); m_paramWriteIndex += sizeof(T); } // ptr and size defines an input buffer (PPC->IOSU) void WriteParamBuffer(MEMPTR<void> ptr, uint32 size) { WriteInOutBuffer(MEMPTR<uint8>(ptr), size, false); } // ptr and size defines an output buffer (IOSU->PPC) void WriteResponseBuffer(MEMPTR<void> ptr, uint32 size) { WriteInOutBuffer(MEMPTR<uint8>(ptr), size, true); } nnResult Submit() { StackAllocator<IPCIoctlVector, 16> vectorArray; uint32 ioVecCount = m_paramBuffers.size() + m_responseBuffers.size(); cemu_assert(ioVecCount <= 16); // output buffers come first for (size_t i = 0; i < m_responseBuffers.size(); ++i) { vectorArray[i].baseVirt = MEMPTR<uint8>(m_responseBuffers[i].ptr); vectorArray[i].basePhys = nullptr; vectorArray[i].size = m_responseBuffers[i].size; } // input buffers for (size_t i = 0; i < m_paramBuffers.size(); ++i) { vectorArray[m_responseBuffers.size() + i].baseVirt = MEMPTR<uint8>(m_paramBuffers[i].ptr); vectorArray[m_responseBuffers.size() + i].basePhys = nullptr; vectorArray[m_responseBuffers.size() + i].size = m_paramBuffers[i].size; } IOS_ERROR r = coreinit::IOS_Ioctlv(m_client.GetDevHandle(), 0, m_responseBuffers.size(), m_paramBuffers.size(), vectorArray.GetPointer()); if ( (r&0x80000000) != 0) { cemu_assert_unimplemented(); // todo - handle submission errors } uint32be resultCode = ReadResponse<uint32be>(); if (!NN_RESULT_IS_FAILURE((uint32)resultCode)) { for (auto& bufCopy : m_bufferCopiesOut) { memcpy(bufCopy.dst, bufCopy.src, bufCopy.size); } } return static_cast<nnResult>(resultCode); } template<typename T> T ReadResponse() { cemu_assert(m_responseReadIndex + sizeof(T) <= m_responseBuffers[0].size); T value; memcpy(&value, m_responseBuffers[0].ptr + m_responseReadIndex, sizeof(T)); m_responseReadIndex += sizeof(T); return value; } private: struct BufferCopyOut { BufferCopyOut(void* dst, void* src, uint32 size) : dst(dst), src(src), size(size) {} void* dst; void* src; uint32 size; }; void WriteInOutBuffer(MEMPTR<uint8> ptr, uint32 size, bool isOutput) { uint32 headSize = (0x40 - (ptr.GetMPTR()&0x3F))&0x3F; headSize = std::min<uint32>(headSize, size); uint32 alignedSize = size - headSize; uint32 tailSize = alignedSize - (alignedSize&~0x3F); alignedSize -= tailSize; // verify cemu_assert_debug(headSize + alignedSize + tailSize == size); cemu_assert_debug(alignedSize == 0 || ((ptr.GetMPTR()+headSize)&0x3F) == 0); cemu_assert_debug(tailSize == 0 || ((ptr.GetMPTR()+headSize+alignedSize)&0x3F) == 0); if (isOutput) cemu_assert(m_responseBuffers.size()+2 <= m_responseBuffers.capacity()); else cemu_assert(m_paramBuffers.size()+2 <= m_paramBuffers.capacity()); IOVectorBuffer alignedBuffer; alignedBuffer.ptr = ptr + headSize; alignedBuffer.size = alignedSize; alignedBuffer.isExternalBuffer = true; if (isOutput) m_responseBuffers.emplace_back(alignedBuffer); else m_paramBuffers.emplace_back(alignedBuffer); IPCBuffer* headAndTailBuffer = m_client.AllocateCommandBuffer(); IOVectorBuffer headAndTail; headAndTail.ptr = headAndTailBuffer->data + (0x40 - headSize); headAndTail.size = 128 - (0x40 - headSize); headAndTail.isExternalBuffer = false; if (headSize > 0) { if (isOutput) m_bufferCopiesOut.emplace_back(ptr, headAndTailBuffer->data + 0x40 - headSize, headSize); else memcpy(headAndTailBuffer->data + 0x40 - headSize, ptr, headSize); } if (tailSize > 0) { if (isOutput) m_bufferCopiesOut.emplace_back(ptr + headSize + alignedSize, headAndTailBuffer->data + 0x40, tailSize); else memcpy(headAndTailBuffer->data + 0x40, ptr + headSize + alignedSize, tailSize); } if (isOutput) m_responseBuffers.emplace_back(headAndTail); else m_paramBuffers.emplace_back(headAndTail); // serialize into parameter stream WriteParam<uint32be>(alignedSize); WriteParam<uint8be>(0); // ukn4 WriteParam<uint8be>(0); // ukn5 WriteParam<uint8be>((uint8)headSize); WriteParam<uint8be>((uint8)tailSize); } IPCServiceClient& m_client; boost::container::static_vector<IOVectorBuffer, 8> m_paramBuffers; boost::container::static_vector<IOVectorBuffer, 8> m_responseBuffers; sint32 m_paramWriteIndex{0}; sint32 m_responseReadIndex{0}; boost::container::static_vector<BufferCopyOut, 16> m_bufferCopiesOut; }; IPCServiceClient() { } ~IPCServiceClient() { Shutdown(); } void Initialize(std::string_view devicePath, uint8_t* buffer, uint32_t bufferSize) { m_devicePath = devicePath; m_buffer = buffer; m_bufferSize = bufferSize; static_assert(sizeof(IPCBuffer) == 256); size_t numCommandBuffers = m_bufferSize / sizeof(IPCBuffer); m_commandBuffersFree.resize(numCommandBuffers); for (size_t i = 0; i < numCommandBuffers; ++i) { m_commandBuffersFree[i] = reinterpret_cast<IPCBuffer*>(m_buffer + i * sizeof(IPCBuffer)); } m_clientHandle = coreinit::IOS_Open(m_devicePath.c_str(), 0); } void Shutdown() { if (m_clientHandle != 0) { coreinit::IOS_Close(m_clientHandle); m_clientHandle = 0; } } IPCServiceCall Begin(uint32_t serviceId, uint32_t commandId) { return IPCServiceCall(*this, serviceId, commandId); } IOSDevHandle GetDevHandle() const { cemu_assert(m_clientHandle != 0); return m_clientHandle; } private: struct IPCBuffer { uint8 data[256]; }; IPCBuffer* AllocateCommandBuffer() { cemu_assert(m_commandBuffersFree.size() > 0); IPCBuffer* buf = m_commandBuffersFree.back(); m_commandBuffersFree.pop_back(); return buf; } void ReleaseCommandBuffer(IPCBuffer* buffer) { m_commandBuffersFree.emplace_back(buffer); } private: std::string m_devicePath; IOSDevHandle m_clientHandle{0}; uint8_t* m_buffer{nullptr}; uint32_t m_bufferSize{0}; std::vector<IPCBuffer*> m_commandBuffersFree; }; ================================================ FILE: src/Cafe/OS/libs/nn_cmpt/nn_cmpt.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "nn_cmpt.h" namespace nn::cmpt { uint32 CMPTAcctGetPcConf(uint32be* pcConf) { cemuLog_logDebug(LogType::Force, "CMPTAcctGetPcConf() - todo"); pcConf[0] = 0; pcConf[1] = 0; pcConf[2] = 0; return 0; } uint32 CMPTGetDataSize(uint32be* sizeOut) { *sizeOut = 0xC0000; return 0; } class : public COSModule { public: std::string_view GetName() override { return "nn_cmpt"; } void RPLMapped() override { cafeExportRegister("nn_cmpt", CMPTAcctGetPcConf, LogType::Placeholder); cafeExportRegister("nn_cmpt", CMPTGetDataSize, LogType::Placeholder); }; }s_COSnnCmptModule; COSModule* GetModule() { return &s_COSnnCmptModule; } } ================================================ FILE: src/Cafe/OS/libs/nn_cmpt/nn_cmpt.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace nn::cmpt { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_common.h ================================================ #pragma once using nnResult = uint32; inline bool NN_RESULT_IS_SUCCESS(nnResult r) { return (r & 0x80000000) == 0; } inline bool NN_RESULT_IS_FAILURE(nnResult r) { return (r & 0x80000000) != 0; } // any level with MSB set is considered an error #define NN_RESULT_LEVEL_FATAL (0x7) // 111 #define NN_RESULT_LEVEL_LVL6 (0x6) // 110 #define NN_RESULT_LEVEL_STATUS (0x5) // 101 #define NN_RESULT_LEVEL_SUCCESS (0x0) // 000 #define BUILD_NN_RESULT(__level, __module, __detailedErrorCode) ((((uint32)(__level)&0x7)<<29) | ((uint32)(__module)<<20) | ((uint32)(__detailedErrorCode)<<0) ) #define BUILD_NN_RESULT_STATUS(__module, __detailedErrorCode) ((((uint32)NN_RESULT_LEVEL_STATUS)<<29) | ((uint32)(__module)<<20) | ((uint32)(__detailedErrorCode)<<0) ) #define NN_RESULT_SUCCESS ((nnResult)0) #define NN_RESULT_PLACEHOLDER_ERROR ((nnResult)0x80000000) enum NN_RESULT_MODULE : uint32 { NN_RESULT_MODULE_COMMON = 0, NN_RESULT_MODULE_NN_IPC = 1, NN_RESULT_MODULE_NN_BOSS = 2, NN_RESULT_MODULE_NN_ACP = 3, NN_RESULT_MODULE_NN_IOS = 4, NN_RESULT_MODULE_NN_NIM = 5, NN_RESULT_MODULE_NN_PDM = 6, NN_RESULT_MODULE_NN_ACT = 7, NN_RESULT_MODULE_NN_NUP = 10, NN_RESULT_MODULE_NN_NDM = 11, NN_RESULT_MODULE_NN_FP = 12, NN_RESULT_MODULE_NN_AC = 13, NN_RESULT_MODULE_NN_DRMAPP = 15, NN_RESULT_MODULE_NN_OLV = 17, NN_RESULT_MODULE_NN_VCTL = 18, NN_RESULT_MODULE_NN_SPM = 20, NN_RESULT_MODULE_NN_EC = 22, NN_RESULT_MODULE_NN_SL = 24, NN_RESULT_MODULE_NN_ECO = 25, NN_RESULT_MODULE_NN_NFP = 27, NN_RESULT_MODULE_MCP = 511, }; // 0000-9999 // these are the user-facing error codes you see in ErrEula. Usually combined with a module-specific prefix. E.g. 102-1234 // in nnResult these are stored in the description field left-shifted by 7 enum class NN_ERROR_CODE : uint32 { ACT_UNKNOWN_SERVER_ERROR = 2932, }; // takes simplified error code in range 0-9999 constexpr inline nnResult nnResultStatus(NN_RESULT_MODULE module, NN_ERROR_CODE errorCode) { uint32 description = (uint32)errorCode; description <<= 7; return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, (uint32)module, description); } // nn_act description codes - todo: define these as NN_ERROR_CODE #define NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST 128128 // nn_nfp description codes #define NN_RESULT_NFP_CODE_APPAREAIDMISMATCH 0x11300 #define NN_RESULT_NFP_CODE_NOAPPAREA 0x10400 namespace nn { inline NN_RESULT_MODULE nnResult_GetModule(const nnResult res) { return (NN_RESULT_MODULE)((res >> 20) & 0x1FF); }; inline uint32 nnResult_GetDescription(const nnResult res) { return (uint32)((res >> 0) & 0xFFFFF); }; #define NN_ERRCODE_MODULE_PREFIX_ACT 1020000 inline NN_ERROR_CODE NNResultToErrorCode(nnResult result, NN_RESULT_MODULE expectedModule) { if (((uint32)result & 0x18000000) == 0x18000000) { cemu_assert_unimplemented(); } uint32 errorCodeBase = 0; if (expectedModule == NN_RESULT_MODULE_NN_ACT) errorCodeBase = NN_ERRCODE_MODULE_PREFIX_ACT; else { cemu_assert_unimplemented(); } NN_RESULT_MODULE module = nnResult_GetModule(result); if (module != expectedModule) return (NN_ERROR_CODE)(errorCodeBase + 9999); uint32 desc = nnResult_GetDescription(result); uint32 errCode = (desc >> 7); if (errCode > 9999) { cemu_assert_suspicious(); return (NN_ERROR_CODE)(errorCodeBase + 9999); } return (NN_ERROR_CODE)(errorCodeBase + errCode); } } // tests static_assert(BUILD_NN_RESULT_STATUS(NN_RESULT_MODULE_NN_ACT, 317696) == 0xA074D900); ================================================ FILE: src/Cafe/OS/libs/nn_ec/nn_ec.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "nn_ec.h" typedef struct { /* +0x00 */ uint8 ukn00[0x10]; /* +0x10 */ uint8 ukn10[0x0C]; /* +0x2C */ uint8 ukn2C[0x10]; // currency string? /* +0x3C */ uint32 ukn3C; // money amount? // size of struct is 0x40 }nnEcUknMoneyStruct_t; void nnEcExport___ct__Q3_2nn2ec5MoneyFPCcN21(PPCInterpreter_t* hCPU) { debug_printf("nn_ec.__ct__Q3_2nn2ec5MoneyFPCcN21(0x%08x, ...)\n", hCPU->gpr[3]); nnEcUknMoneyStruct_t* moneyStruct = (nnEcUknMoneyStruct_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); // todo -> Setup struct osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(moneyStruct)); } namespace nn::ec { class : public COSModule { public: std::string_view GetName() override { return "nn_ec"; } void RPLMapped() override { osLib_addFunction("nn_ec", "__ct__Q3_2nn2ec5MoneyFPCcN21", nnEcExport___ct__Q3_2nn2ec5MoneyFPCcN21); }; }s_COSnnEcModule; COSModule* GetModule() { return &s_COSnnEcModule; } } ================================================ FILE: src/Cafe/OS/libs/nn_ec/nn_ec.h ================================================ #include "Cafe/OS/RPL/COSModule.h" void nnEc_load(); namespace nn::ec { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_fp/nn_fp.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/IOSU/legacy/iosu_act.h" #include "Cafe/IOSU/legacy/iosu_fpd.h" #include "Cafe/IOSU/legacy/iosu_ioctl.h" // deprecated #include "Cafe/IOSU/iosu_ipc_common.h" #include "Cafe/OS/libs/coreinit/coreinit_IOS.h" #include "Cafe/OS/libs/coreinit/coreinit_IPC.h" #include "Cafe/OS/libs/nn_common.h" #include "util/ChunkedHeap/ChunkedHeap.h" #include "Common/CafeString.h" namespace nn { namespace fp { static const auto FPResult_OkZero = 0; static const auto FPResult_Ok = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_FP, 0); static const auto FPResult_InvalidIPCParam = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_FP, 0x680); static const auto FPResult_RequestFailed = BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_FP, 0); // figure out proper error code struct { uint32 initCounter; bool isAdminMode; bool isLoggedIn; IOSDevHandle fpdHandle; SysAllocator<coreinit::OSMutex> fpMutex; SysAllocator<uint8, 0x12000> g_fpdAllocatorSpace; VHeap* fpBufferHeap{nullptr}; // PPC buffers for async notification query SysAllocator<uint32be> notificationCount; SysAllocator<iosu::fpd::FPDNotification, 256> notificationBuffer; bool getNotificationCalled{false}; // notification handler MEMPTR<void> notificationHandler{nullptr}; MEMPTR<void> notificationHandlerParam{nullptr}; }g_fp = { }; class { public: void Init() { std::unique_lock _l(m_mtx); g_fp.fpBufferHeap = new VHeap(g_fp.g_fpdAllocatorSpace.GetPtr(), g_fp.g_fpdAllocatorSpace.GetByteSize()); } void Destroy() { std::unique_lock _l(m_mtx); delete g_fp.fpBufferHeap; } void* Allocate(uint32 size, uint32 alignment) { std::unique_lock _l(m_mtx); void* p = g_fp.fpBufferHeap->alloc(size, 32); if (!p) cemuLog_log(LogType::Force, "nn_fp: Internal heap is full"); return p; } void Free(void* ptr) { std::unique_lock _l(m_mtx); g_fp.fpBufferHeap->free(ptr); } private: std::mutex m_mtx; }FPIpcBufferAllocator; class FPIpcContext { static inline constexpr uint32 MAX_VEC_COUNT = 8; public: // use FP heap for this class static void* operator new(size_t size) { return FPIpcBufferAllocator.Allocate(size, (uint32)alignof(FPIpcContext)); } static void operator delete(void* ptr) { FPIpcBufferAllocator.Free(ptr); } FPIpcContext(iosu::fpd::FPD_REQUEST_ID requestId) : m_requestId(requestId) { } ~FPIpcContext() { if(m_dataBuffer) FPIpcBufferAllocator.Free(m_dataBuffer); } void AddInput(void* ptr, uint32 size) { size_t vecIndex = GetVecInIndex(m_numVecIn); m_vec[vecIndex].baseVirt = ptr; m_vec[vecIndex].size = size; m_numVecIn = m_numVecIn + 1; } void AddOutput(void* ptr, uint32 size) { cemu_assert_debug(m_numVecIn == 0); // all outputs need to be added before any inputs size_t vecIndex = GetVecOutIndex(m_numVecOut); m_vec[vecIndex].baseVirt = ptr; m_vec[vecIndex].size = size; m_numVecOut = m_numVecOut + 1; } uint32 Submit(std::unique_ptr<FPIpcContext> owner) { InitSubmissionBuffer(); // note: While generally, Ioctlv() usage has the order as input (app->IOSU) followed by output (IOSU->app), FP uses it the other way around nnResult r = coreinit::IOS_Ioctlv(g_fp.fpdHandle, (uint32)m_requestId.value(), m_numVecOut, m_numVecIn, m_vec); CopyBackOutputs(); owner.reset(); return r; } nnResult SubmitAsync(std::unique_ptr<FPIpcContext> owner, MEMPTR<void> callbackFunc, MEMPTR<void> callbackParam) { InitSubmissionBuffer(); this->m_callbackFunc = callbackFunc; this->m_callbackParam = callbackParam; nnResult r = coreinit::IOS_IoctlvAsync(g_fp.fpdHandle, (uint32)m_requestId.value(), m_numVecOut, m_numVecIn, m_vec, MEMPTR<void>(PPCInterpreter_makeCallableExportDepr(AsyncHandler)), MEMPTR<void>(this)); owner.release(); return r; } private: size_t GetVecInIndex(uint8 inIndex) { return m_numVecOut + inIndex; } size_t GetVecOutIndex(uint8 outIndex) { return outIndex; } void InitSubmissionBuffer() { // allocate a chunk of memory to hold the input/output vectors and their data uint32 vecOffset[MAX_VEC_COUNT]; uint32 totalBufferSize = 0; for(uint8 i=0; i<m_numVecIn + m_numVecOut; i++) { vecOffset[i] = totalBufferSize; totalBufferSize += m_vec[i].size; totalBufferSize = (totalBufferSize+31)&~31; } if(totalBufferSize > 0) { m_dataBuffer = FPIpcBufferAllocator.Allocate(totalBufferSize, 32); cemu_assert_debug(m_dataBuffer); } // update Ioctl vector addresses for(uint8 i=0; i<m_numVecIn + m_numVecOut; i++) { void* bufferAddr = (uint8be*)m_dataBuffer.GetPtr() + vecOffset[i]; m_vecOriginalAddress[i] = m_vec[i].baseVirt; m_vec[i].baseVirt = bufferAddr; } // copy input data to buffer for(uint8 i=0; i<m_numVecIn; i++) { uint8 vecIndex = GetVecInIndex(i); memcpy(MEMPTR<void>(m_vec[vecIndex].baseVirt).GetPtr(), MEMPTR<void>(m_vecOriginalAddress[vecIndex]).GetPtr(), m_vec[vecIndex].size); } } static void AsyncHandler(PPCInterpreter_t* hCPU) { ppcDefineParamU32(result, 0); ppcDefineParamPtr(ipcCtx, FPIpcContext, 1); ipcCtx->m_asyncResult = result; // store result in variable since FP callbacks pass a pointer to nnResult and not the value directly ipcCtx->CopyBackOutputs(); PPCCoreCallback(ipcCtx->m_callbackFunc, &ipcCtx->m_asyncResult, ipcCtx->m_callbackParam); delete ipcCtx; osLib_returnFromFunction(hCPU, 0); } void CopyBackOutputs() { if(m_numVecOut > 0) { // copy output from temporary output buffers to the original addresses for(uint8 i=0; i<m_numVecOut; i++) { uint32 vecOffset = (uint32)m_vec[GetVecOutIndex(i)].baseVirt.GetMPTR() - (uint32)m_vec[0].baseVirt.GetMPTR(); memcpy(m_vecOriginalAddress[GetVecOutIndex(i)].GetPtr(), (uint8be*)m_dataBuffer.GetPtr() + vecOffset, m_vec[GetVecOutIndex(i)].size); } } } betype<iosu::fpd::FPD_REQUEST_ID> m_requestId; uint8be m_numVecIn{0}; uint8be m_numVecOut{0}; IPCIoctlVector m_vec[MAX_VEC_COUNT]; MEMPTR<void> m_vecOriginalAddress[MAX_VEC_COUNT]{}; MEMPTR<void> m_dataBuffer{nullptr}; MEMPTR<void> m_callbackFunc{nullptr}; MEMPTR<void> m_callbackParam{nullptr}; betype<nnResult> m_asyncResult; }; struct FPGlobalLock { FPGlobalLock() { coreinit::OSLockMutex(&g_fp.fpMutex); } ~FPGlobalLock() { coreinit::OSUnlockMutex(&g_fp.fpMutex); } }; #define FP_API_BASE() if (g_fp.initCounter == 0) return 0xC0C00580; FPGlobalLock _fpLock; #define FP_API_BASE_ZeroOnError() if (g_fp.initCounter == 0) return 0; FPGlobalLock _fpLock; nnResult Initialize() { FPGlobalLock _fpLock; if (g_fp.initCounter == 0) { g_fp.fpdHandle = coreinit::IOS_Open("/dev/fpd", 0); } g_fp.initCounter++; return FPResult_OkZero; } uint32 IsInitialized() { FPGlobalLock _fpLock; return g_fp.initCounter > 0 ? 1 : 0; } nnResult InitializeAdmin(PPCInterpreter_t* hCPU) { FPGlobalLock _fpLock; g_fp.isAdminMode = true; return Initialize(); } uint32 IsInitializedAdmin() { FPGlobalLock _fpLock; return g_fp.initCounter > 0 ? 1 : 0; } nnResult Finalize() { FPGlobalLock _fpLock; if (g_fp.initCounter == 1) { g_fp.initCounter = 0; g_fp.isAdminMode = false; g_fp.isLoggedIn = false; coreinit::IOS_Close(g_fp.fpdHandle); g_fp.getNotificationCalled = false; } else if (g_fp.initCounter > 0) g_fp.initCounter--; return FPResult_OkZero; } nnResult FinalizeAdmin() { return Finalize(); } void GetNextNotificationAsync(); nnResult SetNotificationHandler(uint32 notificationMask, void* funcPtr, void* userParam) { FP_API_BASE(); g_fp.notificationHandler = funcPtr; g_fp.notificationHandlerParam = userParam; StackAllocator<uint32be> notificationMaskBuf; notificationMaskBuf = notificationMask; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::SetNotificationMask); ipcCtx->AddInput(¬ificationMaskBuf, sizeof(uint32be)); nnResult r = ipcCtx->Submit(std::move(ipcCtx)); if (NN_RESULT_IS_SUCCESS(r)) { // async query for notifications GetNextNotificationAsync(); } return r; } void GetNextNotificationAsyncHandler(PPCInterpreter_t* hCPU) { coreinit::OSLockMutex(&g_fp.fpMutex); cemu_assert_debug(g_fp.getNotificationCalled); g_fp.getNotificationCalled = false; auto bufPtr = g_fp.notificationBuffer.GetPtr(); uint32 count = g_fp.notificationCount->value(); if (count == 0) { GetNextNotificationAsync(); coreinit::OSUnlockMutex(&g_fp.fpMutex); osLib_returnFromFunction(hCPU, 0); return; } // copy notifications to temporary buffer using std::copy iosu::fpd::FPDNotification tempBuffer[256]; std::copy(g_fp.notificationBuffer.GetPtr(), g_fp.notificationBuffer.GetPtr() + count, tempBuffer); // call handler for each notification, but do it outside of the lock void* notificationHandler = g_fp.notificationHandler; void* notificationHandlerParam = g_fp.notificationHandlerParam; coreinit::OSUnlockMutex(&g_fp.fpMutex); iosu::fpd::FPDNotification* notificationBuffer = g_fp.notificationBuffer.GetPtr(); for (uint32 i = 0; i < count; i++) PPCCoreCallback(notificationHandler, (uint32)notificationBuffer[i].type, notificationBuffer[i].pid, notificationHandlerParam); coreinit::OSLockMutex(&g_fp.fpMutex); // query more notifications GetNextNotificationAsync(); coreinit::OSUnlockMutex(&g_fp.fpMutex); osLib_returnFromFunction(hCPU, 0); } void GetNextNotificationAsync() { if (g_fp.getNotificationCalled) return; g_fp.getNotificationCalled = true; g_fp.notificationCount = 0; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetNotificationAsync); ipcCtx->AddOutput(g_fp.notificationBuffer.GetPtr(), g_fp.notificationBuffer.GetByteSize()); ipcCtx->AddOutput(g_fp.notificationCount.GetPtr(), sizeof(uint32be)); cemu_assert_debug(g_fp.notificationBuffer.GetByteSize() == 0x800); nnResult r = ipcCtx->SubmitAsync(std::move(ipcCtx), MEMPTR<void>(PPCInterpreter_makeCallableExportDepr(GetNextNotificationAsyncHandler)), nullptr); } nnResult LoginAsync(void* funcPtr, void* userParam) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::LoginAsync); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, userParam); } uint32 HasLoggedIn() { FP_API_BASE_ZeroOnError(); // Sonic All Star Racing uses this // and Monster Hunter 3 Ultimate needs this to return false at least once to initiate login and not get stuck // this returns false until LoginAsync was called and has completed (?) even if the user is already logged in StackAllocator<uint32be> resultBuf; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::HasLoggedIn); ipcCtx->AddOutput(&resultBuf, sizeof(uint32be)); ipcCtx->Submit(std::move(ipcCtx)); return resultBuf != 0 ? 1 : 0; } uint32 IsOnline() { FP_API_BASE_ZeroOnError(); StackAllocator<uint32be> resultBuf; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::IsOnline); ipcCtx->AddOutput(&resultBuf, sizeof(uint32be)); ipcCtx->Submit(std::move(ipcCtx)); return resultBuf != 0 ? 1 : 0; } nnResult GetFriendList(uint32be* pidList, uint32be* returnedCount, uint32 startIndex, uint32 maxCount) { FP_API_BASE(); StackAllocator<uint32be> startIndexBuf; startIndexBuf = startIndex; StackAllocator<uint32be> maxCountBuf; maxCountBuf = maxCount; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetFriendList); ipcCtx->AddOutput(pidList, sizeof(uint32be) * maxCount); ipcCtx->AddOutput(returnedCount, sizeof(uint32be)); ipcCtx->AddInput(&startIndexBuf, sizeof(uint32be)); ipcCtx->AddInput(&maxCountBuf, sizeof(uint32be)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetFriendRequestList(uint32be* pidList, uint32be* returnedCount, uint32 startIndex, uint32 maxCount) { FP_API_BASE(); StackAllocator<uint32be> startIndexBuf; startIndexBuf = startIndex; StackAllocator<uint32be> maxCountBuf; maxCountBuf = maxCount; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetFriendRequestList); ipcCtx->AddOutput(pidList, sizeof(uint32be) * maxCount); ipcCtx->AddOutput(returnedCount, sizeof(uint32be)); ipcCtx->AddInput(&startIndexBuf, sizeof(uint32be)); ipcCtx->AddInput(&maxCountBuf, sizeof(uint32be)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetFriendListAll(uint32be* pidList, uint32be* returnedCount, uint32 startIndex, uint32 maxCount) { FP_API_BASE(); StackAllocator<uint32be> startIndexBuf; startIndexBuf = startIndex; StackAllocator<uint32be> maxCountBuf; maxCountBuf = maxCount; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetFriendListAll); ipcCtx->AddOutput(pidList, sizeof(uint32be) * maxCount); ipcCtx->AddOutput(returnedCount, sizeof(uint32be)); ipcCtx->AddInput(&startIndexBuf, sizeof(uint32be)); ipcCtx->AddInput(&maxCountBuf, sizeof(uint32be)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetFriendListEx(iosu::fpd::FriendData* friendData, uint32be* pidList, uint32 count) { FP_API_BASE(); StackAllocator<uint32be> countBuf; countBuf = count; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetFriendListEx); ipcCtx->AddOutput(friendData, sizeof(iosu::fpd::FriendData) * count); ipcCtx->AddInput(pidList, sizeof(uint32be) * count); ipcCtx->AddInput(&countBuf, sizeof(uint32be)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetFriendRequestListEx(iosu::fpd::FriendRequest* friendRequest, uint32be* pidList, uint32 count) { FP_API_BASE(); StackAllocator<uint32be> countBuf; countBuf = count; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetFriendRequestListEx); ipcCtx->AddOutput(friendRequest, sizeof(iosu::fpd::FriendRequest) * count); ipcCtx->AddInput(pidList, sizeof(uint32be) * count); ipcCtx->AddInput(&countBuf, sizeof(uint32be)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetBasicInfoAsync(iosu::fpd::FriendBasicInfo* basicInfo, uint32be* pidList, uint32 count, void* funcPtr, void* customParam) { FP_API_BASE(); StackAllocator<uint32be> countBuf; countBuf = count; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetBasicInfoAsync); ipcCtx->AddOutput(basicInfo, sizeof(iosu::fpd::FriendBasicInfo) * count); ipcCtx->AddInput(pidList, sizeof(uint32be) * count); ipcCtx->AddInput(&countBuf, sizeof(uint32be)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } uint32 GetMyPrincipalId() { FP_API_BASE_ZeroOnError(); StackAllocator<uint32be> resultBuf; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetMyPrincipalId); ipcCtx->AddOutput(&resultBuf, sizeof(uint32be)); ipcCtx->Submit(std::move(ipcCtx)); return resultBuf->value(); } nnResult GetMyAccountId(uint8be* accountId) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetMyAccountId); ipcCtx->AddOutput(accountId, ACT_ACCOUNTID_LENGTH); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetMyScreenName(uint16be* screenname) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetMyScreenName); ipcCtx->AddOutput(screenname, ACT_NICKNAME_SIZE*sizeof(uint16)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetMyPlayingGame(iosu::fpd::GameKey* myPlayingGame) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetMyPlayingGame); ipcCtx->AddOutput(myPlayingGame, sizeof(iosu::fpd::GameKey)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetMyPreference(iosu::fpd::FPDPreference* myPreference) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetMyPreference); ipcCtx->AddOutput(myPreference, sizeof(iosu::fpd::FPDPreference)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetMyComment(uint16be* myComment) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetMyComment); ipcCtx->AddOutput(myComment, iosu::fpd::MY_COMMENT_LENGTH * sizeof(uint16be)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetMyMii(FFLData_t* fflData) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetMyMii); ipcCtx->AddOutput(fflData, sizeof(FFLData_t)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetFriendAccountId(uint8be* accountIdArray, uint32be* pidList, uint32 count) { FP_API_BASE(); if (count == 0) return 0; StackAllocator<uint32be> countBuf; countBuf = count; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetFriendAccountId); ipcCtx->AddOutput(accountIdArray, ACT_ACCOUNTID_LENGTH * count); ipcCtx->AddInput(pidList, sizeof(uint32be) * count); ipcCtx->AddInput(&countBuf, sizeof(uint32be)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetFriendScreenName(uint16be* nameList, uint32be* pidList, uint32 count, uint8 replaceNonAscii, uint8be* languageList) { FP_API_BASE(); if (count == 0) return 0; StackAllocator<uint32be> countBuf; countBuf = count; StackAllocator<uint32be> replaceNonAsciiBuf; replaceNonAsciiBuf = replaceNonAscii; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetFriendScreenName); ipcCtx->AddOutput(nameList, ACT_NICKNAME_SIZE * sizeof(uint16be) * count); ipcCtx->AddOutput(languageList, languageList ? sizeof(uint8be) * count : 0); ipcCtx->AddInput(pidList, sizeof(uint32be) * count); ipcCtx->AddInput(&countBuf, sizeof(uint32be)); ipcCtx->AddInput(&replaceNonAsciiBuf, sizeof(uint8be)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetFriendMii(FFLData_t* miiList, uint32be* pidList, uint32 count) { FP_API_BASE(); if(count == 0) return 0; StackAllocator<uint32be> countBuf; countBuf = count; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetFriendMii); ipcCtx->AddOutput(miiList, sizeof(FFLData_t) * count); ipcCtx->AddInput(pidList, sizeof(uint32be) * count); ipcCtx->AddInput(&countBuf, sizeof(uint32be)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetFriendPresence(iosu::fpd::FriendPresence* presenceList, uint32be* pidList, uint32 count) { FP_API_BASE(); if(count == 0) return 0; StackAllocator<uint32be> countBuf; countBuf = count; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetFriendPresence); ipcCtx->AddOutput(presenceList, sizeof(iosu::fpd::FriendPresence) * count); ipcCtx->AddInput(pidList, sizeof(uint32be) * count); ipcCtx->AddInput(&countBuf, sizeof(uint32be)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult GetFriendRelationship(uint8* relationshipList, uint32be* pidList, uint32 count) { FP_API_BASE(); if(count == 0) return 0; StackAllocator<uint32be> countBuf; countBuf = count; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetFriendRelationship); ipcCtx->AddOutput(relationshipList, sizeof(uint8) * count); ipcCtx->AddInput(pidList, sizeof(uint32be) * count); ipcCtx->AddInput(&countBuf, sizeof(uint32be)); return ipcCtx->Submit(std::move(ipcCtx)); } uint32 IsJoinable(iosu::fpd::FriendPresence* presence, uint64 joinMask) { if (presence->isValid == 0 || presence->isOnline == 0 || presence->gameMode.joinGameId == 0 || presence->gameMode.matchmakeType == 0 || presence->gameMode.groupId == 0 || presence->gameMode.joinGameMode >= 64 ) { return 0; } uint32 joinGameMode = presence->gameMode.joinGameMode; uint64 joinModeMask = (1ULL<<joinGameMode); if ((joinModeMask&joinMask) == 0) return 0; // check relation ship uint32 joinFlagMask = presence->gameMode.joinFlagMask; if (joinFlagMask == 0) return 0; if (joinFlagMask == 1) return 1; if (joinFlagMask == 2) { // check relationship uint8 relationship[1] = { 0 }; StackAllocator<uint32be, 1> pidList; pidList = presence->gameMode.hostPid; GetFriendRelationship(relationship, &pidList, 1); if(relationship[0] == iosu::fpd::RELATIONSHIP_FRIEND) return 1; return 0; } if (joinFlagMask == 0x65 || joinFlagMask == 0x66) { cemuLog_log(LogType::Force, "Unsupported friend invite"); } return 0; } nnResult CheckSettingStatusAsync(uint8* status, void* funcPtr, void* customParam) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::CheckSettingStatusAsync); ipcCtx->AddOutput(status, sizeof(uint8be)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } uint32 IsPreferenceValid() { FP_API_BASE_ZeroOnError(); StackAllocator<uint32be> resultBuf; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::IsPreferenceValid); ipcCtx->AddOutput(&resultBuf, sizeof(uint32be)); ipcCtx->Submit(std::move(ipcCtx)); return resultBuf != 0 ? 1 : 0; } nnResult UpdateCommentAsync(uint16be* newComment, void* funcPtr, void* customParam) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::UpdateCommentAsync); uint32 commentLen = CafeStringHelpers::Length(newComment, iosu::fpd::MY_COMMENT_LENGTH-1); if (commentLen >= iosu::fpd::MY_COMMENT_LENGTH-1) { cemuLog_log(LogType::Force, "UpdateCommentAsync: message too long"); return FPResult_InvalidIPCParam; } ipcCtx->AddInput(newComment, sizeof(uint16be) * commentLen + 2); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } nnResult UpdatePreferenceAsync(iosu::fpd::FPDPreference* newPreference, void* funcPtr, void* customParam) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::UpdatePreferenceAsync); ipcCtx->AddInput(newPreference, sizeof(iosu::fpd::FPDPreference)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } nnResult UpdateGameModeWithUnusedParam(iosu::fpd::GameMode* gameMode, uint16be* gameModeMessage, uint32 unusedParam) { FP_API_BASE(); uint32 messageLen = CafeStringHelpers::Length(gameModeMessage, iosu::fpd::GAMEMODE_MAX_MESSAGE_LENGTH); if(messageLen >= iosu::fpd::GAMEMODE_MAX_MESSAGE_LENGTH) { cemuLog_log(LogType::Force, "UpdateGameMode: message too long"); return FPResult_InvalidIPCParam; } auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::UpdateGameModeVariation2); ipcCtx->AddInput(gameMode, sizeof(iosu::fpd::GameMode)); ipcCtx->AddInput(gameModeMessage, sizeof(uint16be) * (messageLen + 1)); return ipcCtx->Submit(std::move(ipcCtx)); } nnResult UpdateGameMode(iosu::fpd::GameMode* gameMode, uint16be* gameModeMessage) { return UpdateGameModeWithUnusedParam(gameMode, gameModeMessage, 0); } nnResult GetRequestBlockSettingAsync(uint8* blockSettingList, uint32be* pidList, uint32 count, void* funcPtr, void* customParam) { FP_API_BASE(); StackAllocator<uint32be> countBuf; countBuf = count; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetRequestBlockSettingAsync); ipcCtx->AddOutput(blockSettingList, sizeof(uint8be) * count); ipcCtx->AddInput(pidList, sizeof(uint32be) * count); ipcCtx->AddInput(&countBuf, sizeof(uint32be)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } // overload of AddFriendAsync nnResult AddFriendAsyncByPid(uint32 pid, void* funcPtr, void* customParam) { FP_API_BASE(); StackAllocator<uint32be> pidBuf; pidBuf = pid; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::AddFriendAsyncByPid); ipcCtx->AddInput(&pidBuf, sizeof(uint32be)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } nnResult DeleteFriendFlagsAsync(uint32be* pidList, uint32 pidCount, uint32 ukn, void* funcPtr, void* customParam) { // admin function? FP_API_BASE(); StackAllocator<uint32be> pidCountBuf; pidCountBuf = pidCount; StackAllocator<uint32be> uknBuf; uknBuf = ukn; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::DeleteFriendFlagsAsync); ipcCtx->AddInput(pidList, sizeof(uint32be) * pidCount); ipcCtx->AddInput(&pidCountBuf, sizeof(uint32be)); ipcCtx->AddInput(&uknBuf, sizeof(uint32be)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } // overload of AddFriendRequestAsync nnResult AddFriendRequestByPlayRecordAsync(iosu::fpd::RecentPlayRecordEx* playRecord, uint16be* message, void* funcPtr, void* customParam) { FP_API_BASE(); auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::AddFriendRequestByPlayRecordAsync); uint32 messageLen = 0; while(message[messageLen] != 0) messageLen++; ipcCtx->AddInput(playRecord, sizeof(iosu::fpd::RecentPlayRecordEx)); ipcCtx->AddInput(message, sizeof(uint16be) * (messageLen+1)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } nnResult RemoveFriendAsync(uint32 pid, void* funcPtr, void* customParam) { FP_API_BASE(); StackAllocator<uint32be> pidBuf; pidBuf = pid; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::RemoveFriendAsync); ipcCtx->AddInput(&pidBuf, sizeof(uint32be)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } nnResult MarkFriendRequestsAsReceivedAsync(uint64be* messageIdList, uint32 count, void* funcPtr, void* customParam) { FP_API_BASE(); StackAllocator<uint32be> countBuf; countBuf = count; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::MarkFriendRequestsAsReceivedAsync); ipcCtx->AddInput(messageIdList, sizeof(uint64be) * count); ipcCtx->AddInput(&countBuf, sizeof(uint32be)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } nnResult CancelFriendRequestAsync(uint64 requestId, void* funcPtr, void* customParam) { FP_API_BASE(); StackAllocator<uint64be> requestIdBuf; requestIdBuf = requestId; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::CancelFriendRequestAsync); ipcCtx->AddInput(&requestIdBuf, sizeof(uint64be)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } nnResult DeleteFriendRequestAsync(uint64 requestId, void* funcPtr, void* customParam) { FP_API_BASE(); StackAllocator<uint64be> requestIdBuf; requestIdBuf = requestId; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::DeleteFriendRequestAsync); ipcCtx->AddInput(&requestIdBuf, sizeof(uint64be)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } nnResult AcceptFriendRequestAsync(uint64 requestId, void* funcPtr, void* customParam) { FP_API_BASE(); StackAllocator<uint64be> requestIdBuf; requestIdBuf = requestId; auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::AcceptFriendRequestAsync); ipcCtx->AddInput(&requestIdBuf, sizeof(uint64be)); return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); } class : public COSModule { public: std::string_view GetName() override { return "nn_fp"; } void RPLMapped() override { g_fp.initCounter = 0; g_fp.isAdminMode = false; g_fp.isLoggedIn = false; g_fp.getNotificationCalled = false; g_fp.notificationHandler = nullptr; g_fp.notificationHandlerParam = nullptr; coreinit::OSInitMutex(&g_fp.fpMutex); FPIpcBufferAllocator.Init(); cafeExportRegisterFunc(Initialize, "nn_fp", "Initialize__Q2_2nn2fpFv", LogType::NN_FP); cafeExportRegisterFunc(InitializeAdmin, "nn_fp", "InitializeAdmin__Q2_2nn2fpFv", LogType::NN_FP); cafeExportRegisterFunc(IsInitialized, "nn_fp", "IsInitialized__Q2_2nn2fpFv", LogType::NN_FP); cafeExportRegisterFunc(IsInitializedAdmin, "nn_fp", "IsInitializedAdmin__Q2_2nn2fpFv", LogType::NN_FP); cafeExportRegisterFunc(Finalize, "nn_fp", "Finalize__Q2_2nn2fpFv", LogType::NN_FP); cafeExportRegisterFunc(FinalizeAdmin, "nn_fp", "FinalizeAdmin__Q2_2nn2fpFv", LogType::NN_FP); cafeExportRegisterFunc(SetNotificationHandler, "nn_fp", "SetNotificationHandler__Q2_2nn2fpFUiPFQ3_2nn2fp16NotificationTypeUiPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(LoginAsync, "nn_fp", "LoginAsync__Q2_2nn2fpFPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(HasLoggedIn, "nn_fp", "HasLoggedIn__Q2_2nn2fpFv", LogType::NN_FP); cafeExportRegisterFunc(IsOnline, "nn_fp", "IsOnline__Q2_2nn2fpFv", LogType::NN_FP); cafeExportRegisterFunc(GetFriendList, "nn_fp", "GetFriendList__Q2_2nn2fpFPUiT1UiT3", LogType::NN_FP); cafeExportRegisterFunc(GetFriendRequestList, "nn_fp", "GetFriendRequestList__Q2_2nn2fpFPUiT1UiT3", LogType::NN_FP); cafeExportRegisterFunc(GetFriendListAll, "nn_fp", "GetFriendListAll__Q2_2nn2fpFPUiT1UiT3", LogType::NN_FP); cafeExportRegisterFunc(GetFriendListEx, "nn_fp", "GetFriendListEx__Q2_2nn2fpFPQ3_2nn2fp10FriendDataPCUiUi", LogType::NN_FP); cafeExportRegisterFunc(GetFriendRequestListEx, "nn_fp", "GetFriendRequestListEx__Q2_2nn2fpFPQ3_2nn2fp13FriendRequestPCUiUi", LogType::NN_FP); cafeExportRegisterFunc(GetBasicInfoAsync, "nn_fp", "GetBasicInfoAsync__Q2_2nn2fpFPQ3_2nn2fp9BasicInfoPCUiUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(GetMyPrincipalId, "nn_fp", "GetMyPrincipalId__Q2_2nn2fpFv", LogType::NN_FP); cafeExportRegisterFunc(GetMyAccountId, "nn_fp", "GetMyAccountId__Q2_2nn2fpFPc", LogType::NN_FP); cafeExportRegisterFunc(GetMyScreenName, "nn_fp", "GetMyScreenName__Q2_2nn2fpFPw", LogType::NN_FP); cafeExportRegisterFunc(GetMyMii, "nn_fp", "GetMyMii__Q2_2nn2fpFP12FFLStoreData", LogType::NN_FP); cafeExportRegisterFunc(GetMyPlayingGame, "nn_fp", "GetMyPlayingGame__Q2_2nn2fpFPQ3_2nn2fp7GameKey", LogType::NN_FP); cafeExportRegisterFunc(GetMyPreference, "nn_fp", "GetMyPreference__Q2_2nn2fpFPQ3_2nn2fp10Preference", LogType::NN_FP); cafeExportRegisterFunc(GetMyComment, "nn_fp", "GetMyComment__Q2_2nn2fpFPQ3_2nn2fp7Comment", LogType::NN_FP); cafeExportRegisterFunc(GetFriendAccountId, "nn_fp", "GetFriendAccountId__Q2_2nn2fpFPA17_cPCUiUi", LogType::NN_FP); cafeExportRegisterFunc(GetFriendScreenName, "nn_fp", "GetFriendScreenName__Q2_2nn2fpFPA11_wPCUiUibPUc", LogType::NN_FP); cafeExportRegisterFunc(GetFriendMii, "nn_fp", "GetFriendMii__Q2_2nn2fpFP12FFLStoreDataPCUiUi", LogType::NN_FP); cafeExportRegisterFunc(GetFriendPresence, "nn_fp", "GetFriendPresence__Q2_2nn2fpFPQ3_2nn2fp14FriendPresencePCUiUi", LogType::NN_FP); cafeExportRegisterFunc(GetFriendRelationship, "nn_fp", "GetFriendRelationship__Q2_2nn2fpFPUcPCUiUi", LogType::NN_FP); cafeExportRegisterFunc(IsJoinable, "nn_fp", "IsJoinable__Q2_2nn2fpFPCQ3_2nn2fp14FriendPresenceUL", LogType::NN_FP); cafeExportRegisterFunc(CheckSettingStatusAsync, "nn_fp", "CheckSettingStatusAsync__Q2_2nn2fpFPUcPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(IsPreferenceValid, "nn_fp", "IsPreferenceValid__Q2_2nn2fpFv", LogType::NN_FP); cafeExportRegisterFunc(UpdateCommentAsync, "nn_fp", "UpdateCommentAsync__Q2_2nn2fpFPCwPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(UpdatePreferenceAsync, "nn_fp", "UpdatePreferenceAsync__Q2_2nn2fpFPCQ3_2nn2fp10PreferencePFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(GetRequestBlockSettingAsync, "nn_fp", "GetRequestBlockSettingAsync__Q2_2nn2fpFPUcPCUiUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(UpdateGameModeWithUnusedParam, "nn_fp", "UpdateGameMode__Q2_2nn2fpFPCQ3_2nn2fp8GameModePCwUi", LogType::NN_FP); cafeExportRegisterFunc(UpdateGameMode, "nn_fp", "UpdateGameMode__Q2_2nn2fpFPCQ3_2nn2fp8GameModePCw", LogType::NN_FP); cafeExportRegisterFunc(AddFriendAsyncByPid, "nn_fp", "AddFriendAsync__Q2_2nn2fpFUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(AddFriendRequestByPlayRecordAsync, "nn_fp", "AddFriendRequestAsync__Q2_2nn2fpFPCQ3_2nn2fp18RecentPlayRecordExPCwPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(DeleteFriendFlagsAsync, "nn_fp", "DeleteFriendFlagsAsync__Q2_2nn2fpFPCUiUiT2PFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(RemoveFriendAsync, "nn_fp", "RemoveFriendAsync__Q2_2nn2fpFUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(MarkFriendRequestsAsReceivedAsync, "nn_fp", "MarkFriendRequestsAsReceivedAsync__Q2_2nn2fpFPCULUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(CancelFriendRequestAsync, "nn_fp", "CancelFriendRequestAsync__Q2_2nn2fpFULPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(DeleteFriendRequestAsync, "nn_fp", "DeleteFriendRequestAsync__Q2_2nn2fpFULPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(AcceptFriendRequestAsync, "nn_fp", "AcceptFriendRequestAsync__Q2_2nn2fpFULPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); }; }s_COSnnFpModule; COSModule* GetModule() { return &s_COSnnFpModule; } } } ================================================ FILE: src/Cafe/OS/libs/nn_fp/nn_fp.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace nn::fp { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/nn_acp/nn_acp.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/nn_common.h" #include "util/crypto/aes128.h" #include "openssl/sha.h" #include "Cemu/napi/napi.h" namespace nn { namespace idbe { struct nnIdbeIconDataV0_t { // raw icon data as byte array // check NAPI::IDBEIconDataV0 for exact data layout uint8 rawData[0x12060]; uint8* GetTGAData() { return rawData + 0x2030; } }; static_assert(sizeof(nnIdbeIconDataV0_t) == 0x12060, ""); struct nnIdbeHeader_t { uint8 formatVersion; uint8 keyIndex; }; struct nnIdbeEncryptedIcon_t { nnIdbeHeader_t header; uint8 hashSHA256[32]; nnIdbeIconDataV0_t iconData; }; static_assert(offsetof(nnIdbeEncryptedIcon_t, hashSHA256) == 2, ""); static_assert(offsetof(nnIdbeEncryptedIcon_t, iconData) == 0x22, ""); static_assert(sizeof(nnIdbeEncryptedIcon_t) == 0x12082); void asyncDownloadIconFile(uint64 titleId, nnIdbeEncryptedIcon_t* iconOut, coreinit::OSEvent* event) { std::vector<uint8> idbeData = NAPI::IDBE_RequestRawEncrypted(ActiveSettings::GetNetworkService(), titleId); if (idbeData.size() != sizeof(nnIdbeEncryptedIcon_t)) { // icon does not exist or has the wrong size cemuLog_log(LogType::Force, "IDBE: Failed to retrieve icon for title {:016x}", titleId); memset(iconOut, 0, sizeof(nnIdbeEncryptedIcon_t)); coreinit::OSSignalEvent(event); return; } memcpy(iconOut, idbeData.data(), sizeof(nnIdbeEncryptedIcon_t)); coreinit::OSSignalEvent(event); } void export_DownloadIconFile(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(encryptedIconData, nnIdbeEncryptedIcon_t, 0); ppcDefineParamU64(titleId, 2); ppcDefineParamU32(uknR7, 4); ppcDefineParamU32(uknR8, 5); StackAllocator<coreinit::OSEvent> event; coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); auto asyncTask = std::async(std::launch::async, asyncDownloadIconFile, titleId, encryptedIconData, &event); coreinit::OSWaitEvent(&event); osLib_returnFromFunction(hCPU, 1); } static_assert(sizeof(nnIdbeHeader_t) == 0x2, ""); static uint8 idbeAesKeys[4 * 16] = { 0x4A,0xB9,0xA4,0x0E,0x14,0x69,0x75,0xA8,0x4B,0xB1,0xB4,0xF3,0xEC,0xEF,0xC4,0x7B, 0x90,0xA0,0xBB,0x1E,0x0E,0x86,0x4A,0xE8,0x7D,0x13,0xA6,0xA0,0x3D,0x28,0xC9,0xB8, 0xFF,0xBB,0x57,0xC1,0x4E,0x98,0xEC,0x69,0x75,0xB3,0x84,0xFC,0xF4,0x07,0x86,0xB5, 0x80,0x92,0x37,0x99,0xB4,0x1F,0x36,0xA6,0xA7,0x5F,0xB8,0xB4,0x8C,0x95,0xF6,0x6F }; static uint8 idbeAesIv[16] = { 0xA4,0x69,0x87,0xAE,0x47,0xD8,0x2B,0xB4,0xFA,0x8A,0xBC,0x04,0x50,0x28,0x5F,0xA4 }; bool decryptIcon(nnIdbeEncryptedIcon_t* iconInput, nnIdbeIconDataV0_t* iconOutput) { // check header nnIdbeHeader_t* idbeHeader = (nnIdbeHeader_t*)iconInput; if (idbeHeader->formatVersion != 0) { cemuLog_log(LogType::Force, "idbe header version unknown ({})", (sint32)idbeHeader->formatVersion); return false; } if (idbeHeader->keyIndex >= 4) { cemuLog_log(LogType::Force, "idbe header key count invalid ({})", (sint32)idbeHeader->keyIndex); return false; } // decrypt data uint8 iv[16]; memcpy(iv, idbeAesIv, sizeof(iv)); uint8 decryptedSHA256[SHA256_DIGEST_LENGTH]; AES128_CBC_decrypt_updateIV(decryptedSHA256, iconInput->hashSHA256, sizeof(decryptedSHA256), idbeAesKeys + 16 * idbeHeader->keyIndex, iv); AES128_CBC_decrypt((uint8*)iconOutput, (uint8*)&iconInput->iconData, sizeof(iconInput->iconData), idbeAesKeys + 16 * idbeHeader->keyIndex, iv); // calculate and compare sha256 uint8 calculatedSHA256[SHA256_DIGEST_LENGTH]; SHA256((const unsigned char*)iconOutput, sizeof(nnIdbeIconDataV0_t), calculatedSHA256); if (memcmp(calculatedSHA256, decryptedSHA256, SHA256_DIGEST_LENGTH) != 0) { cemuLog_logDebug(LogType::Force, "Idbe icon has incorrect sha256 hash"); return false; } return true; } struct TGAHeader { /* +0x00 */ uint8 idLength; /* +0x01 */ uint8 colorMap; /* +0x02 */ uint8 imageType; /* +0x03 */ uint8 colorMap_firstIndex_low; /* +0x04 */ uint8 colorMap_firstIndex_high; /* +0x05 */ uint8 colorMap_len_low; /* +0x06 */ uint8 colorMap_len_high; /* +0x07 */ uint8 colorMap_bpp; /* +0x08 */ uint16 image_xOrigin; /* +0x0A */ uint16 image_yOrigin; /* +0x0C */ uint16 image_width; /* +0x0E */ uint16 image_height; /* +0x10 */ uint8 image_bpp; /* +0x11 */ uint8 image_desc; }; static_assert(offsetof(TGAHeader, colorMap_firstIndex_low) == 0x03); static_assert(sizeof(TGAHeader) == 0x12); void export_DecryptIconFile(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(output, nnIdbeIconDataV0_t, 0); ppcDefineParamTypePtr(input, nnIdbeEncryptedIcon_t, 1); ppcDefineParamU32(platformMode, 2); cemuLog_logDebug(LogType::Force, "nn_idbe.DecryptIconFile(...)"); if (decryptIcon(input, output)) { osLib_returnFromFunction(hCPU, 1); return; } cemuLog_logDebug(LogType::Force, "Unable to decrypt idbe icon file, using default icon"); // return default icon TGAHeader* tgaHeader = (TGAHeader*)(output->GetTGAData()); memset(tgaHeader, 0, sizeof(TGAHeader)); tgaHeader->imageType = 2; tgaHeader->image_width = 256; tgaHeader->image_height = 256; tgaHeader->image_bpp = 32; tgaHeader->image_desc = (1 << 3); osLib_returnFromFunction(hCPU, 1); } // this module is used by: // Daily Log app // Download Manager app // and possibly other system titles? class : public COSModule { public: std::string_view GetName() override { return "nn_idbe"; } void RPLMapped() override { osLib_addFunction("nn_idbe", "DownloadIconFile__Q2_2nn4idbeFPvULUsb", export_DownloadIconFile); osLib_addFunction("nn_idbe", "DecryptIconFile__Q2_2nn4idbeFPvPCv", export_DecryptIconFile); }; }s_COSnnIdbeModule; COSModule* GetModule() { return &s_COSnnIdbeModule; } } } ================================================ FILE: src/Cafe/OS/libs/nn_idbe/nn_idbe.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace nn::idbe { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_ndm/nn_ndm.cpp ================================================ #include "nn_ndm.h" #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/nn_common.h" namespace nn { namespace ndm { enum class DAEMON_NAME : uint32 { UKN_0, // Boss related? UKN_1, // Download Manager? scope.rpx (Download Manager app) expects this to have status 0 or 1. Otherwise it will display downloads as disabled UKN_2, }; enum class DAEMON_STATUS : uint32 { STATUS_UKN_0 = 0, // probably: Ready or initializing? RUNNING = 1, // most likely running, but not 100% sure STATUS_UKN_2 = 2, // probably: ready, starting or something like that? SUSPENDED = 3, }; constexpr size_t NUM_DAEMONS = 3; DAEMON_STATUS s_daemonStatus[NUM_DAEMONS]; uint32 s_initializeRefCount; uint32 Initialize() { s_initializeRefCount++; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NDM, 0); } uint32 IsInitialized() { return s_initializeRefCount != 0 ? 1 : 0; } uint32 Finalize() { if(s_initializeRefCount == 0) return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NDM, 0); s_initializeRefCount++; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NDM, 0); } uint32 GetDaemonStatus(betype<DAEMON_STATUS>* statusOut, DAEMON_NAME daemonName) { size_t daemonIndex = (size_t)daemonName; if(daemonIndex >= NUM_DAEMONS) return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NDM, 0); *statusOut = s_daemonStatus[daemonIndex]; return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NDM, 0); } uint32 SuspendDaemons(uint32 daemonNameBitmask) { for(size_t i=0; i<NUM_DAEMONS; i++) { if(daemonNameBitmask & (1 << i)) s_daemonStatus[i] = DAEMON_STATUS::SUSPENDED; } return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NDM, 0); } uint32 ResumeDaemons(uint32 daemonNameBitmask) { for(size_t i=0; i<NUM_DAEMONS; i++) { if(daemonNameBitmask & (1 << i)) s_daemonStatus[i] = DAEMON_STATUS::RUNNING; } return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NDM, 0); } class : public COSModule { public: std::string_view GetName() override { return "nn_ndm"; } void RPLMapped() override { for(size_t i=0; i<NUM_DAEMONS; i++) s_daemonStatus[i] = DAEMON_STATUS::RUNNING; s_initializeRefCount = 0; cafeExportRegisterFunc(Initialize, "nn_ndm", "Initialize__Q2_2nn3ndmFv", LogType::Placeholder); cafeExportRegisterFunc(Finalize, "nn_ndm", "Finalize__Q2_2nn3ndmFv", LogType::Placeholder); cafeExportRegisterFunc(IsInitialized, "nn_ndm", "IsInitialized__Q2_2nn3ndmFv", LogType::Placeholder); cafeExportRegisterFunc(GetDaemonStatus, "nn_ndm", "GetDaemonStatus__Q2_2nn3ndmFPQ4_2nn3ndm7IDaemon6StatusQ4_2nn3ndm4Cafe10DaemonName", LogType::Placeholder); cafeExportRegisterFunc(SuspendDaemons, "nn_ndm", "SuspendDaemons__Q2_2nn3ndmFUi", LogType::Placeholder); cafeExportRegisterFunc(ResumeDaemons, "nn_ndm", "ResumeDaemons__Q2_2nn3ndmFUi", LogType::Placeholder); }; }s_COSnnNdmModule; COSModule* GetModule() { return &s_COSnnNdmModule; } } } ================================================ FILE: src/Cafe/OS/libs/nn_ndm/nn_ndm.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace nn { namespace ndm { COSModule* GetModule(); } } ================================================ FILE: src/Cafe/OS/libs/nn_nfp/AmiiboCrypto.h ================================================ #pragma once #include "util/crypto/aes128.h" #include "openssl/evp.h" #include "openssl/hmac.h" typedef struct { char typeString[14]; uint8 magicBytes[16]; uint8 magicBytesLen; uint8 xorPad[32]; uint8 hmacKey[16]; }amiiboInternalKeys_t; void amiiboCalcSeed(AmiiboInternal* internalData, uint8* seed) { memcpy(seed + 0x00, (uint8*)internalData + 0x029, 0x02); memset(seed + 0x02, 0x00, 0x0E); memcpy(seed + 0x10, (uint8*)internalData + 0x1D4, 0x08); memcpy(seed + 0x18, (uint8*)internalData + 0x1D4, 0x08); memcpy(seed + 0x20, (uint8*)internalData + 0x1E8, 0x20); } void amiiboGenKeyInternalPrepare(amiiboInternalKeys_t* keys, uint8* seed, uint8* output, sint32& outputLen) { sint32 index = 0; // concat: // typeString (including '\0') sint32 typeStringLen = (sint32)strlen(keys->typeString)+1; memcpy(output + index, keys->typeString, typeStringLen); index += typeStringLen; // seed (16 - magic byte len) sint32 seedPart1Len = 16 - keys->magicBytesLen; memcpy(output + index, seed, seedPart1Len); index += seedPart1Len; // magic bytes + 16 bytes at +0x10 from input seed memcpy(output + index, keys->magicBytes, keys->magicBytesLen); index += keys->magicBytesLen; // seed 16 bytes at +0x10 memcpy(output + index, seed + 0x10, 16); index += 16; // 32 bytes at +0x20 from input seed xored with xor pad for (sint32 i = 0; i < 32; i++) output[index + i] = seed[i + 32] ^ keys->xorPad[i]; index += 32; outputLen = index; } typedef struct { uint8 hmacKey[16]; uint8 buffer[sizeof(uint16) + 480]; sint32 bufferSize; uint16 counter; }amiiboCryptCtx_t; typedef struct { uint8 raw[32]; }drgbOutput32_t; void amiiboCryptInit(amiiboCryptCtx_t* ctx, const uint8* hmacKey, size_t hmacKeySize, const uint8* seed, sint32 seedSize) { ctx->counter = 0; ctx->bufferSize = (sint32)sizeof(ctx->counter) + seedSize; memcpy(ctx->hmacKey, hmacKey, 16); // set static part of buffer memcpy(ctx->buffer + sizeof(uint16), seed, seedSize); } void amiiboCryptStep(amiiboCryptCtx_t* ctx, drgbOutput32_t* output) { // update counter ctx->buffer[0] = ctx->counter >> 8; ctx->buffer[1] = ctx->counter >> 0; ctx->counter++; // generate bytes uint32 mdLen = (uint32)sizeof(drgbOutput32_t); HMAC(EVP_sha256(), ctx->hmacKey, sizeof(ctx->hmacKey), (const unsigned char *)ctx->buffer, ctx->bufferSize, output->raw, &mdLen); } typedef struct { uint8 aesKey[16]; uint8 aesIV[16]; uint8 hmacKey[16]; }amiiboDerivedKeys_t; static_assert(sizeof(amiiboDerivedKeys_t) == 0x30); void amiiboCrypto_genKey(amiiboInternalKeys_t* keys, AmiiboInternal* internalData, amiiboDerivedKeys_t* derivedKeys) { // generate seed uint8 seed[0x40]; amiiboCalcSeed(internalData, seed); // generate internal seed uint8 internalKey[512]; sint32 internalKeyLen = 0; amiiboGenKeyInternalPrepare(keys, seed, internalKey, internalKeyLen); // gen bytes for derived keys drgbOutput32_t temp[2]; amiiboCryptCtx_t rngCtx; amiiboCryptInit(&rngCtx, keys->hmacKey, sizeof(keys->hmacKey), internalKey, internalKeyLen); amiiboCryptStep(&rngCtx, temp + 0); amiiboCryptStep(&rngCtx, temp + 1); memcpy(derivedKeys, temp, sizeof(amiiboDerivedKeys_t)); } void amiiboCrypto_nfcFormatToInternal(AmiiboRawNFCData* nfcData, AmiiboInternal* internalData) { uint8* tag = (uint8*)nfcData; uint8* intl = (uint8*)internalData; memcpy(intl + 0x000, tag + 0x008, 0x008); memcpy(intl + 0x008, tag + 0x080, 0x020); // tag hmac memcpy(intl + 0x028, tag + 0x010, 0x024); memcpy(intl + 0x04C, tag + 0x0A0, 0x168); memcpy(intl + 0x1B4, tag + 0x034, 0x020); // data hmac memcpy(intl + 0x1D4, tag + 0x000, 0x008); memcpy(intl + 0x1DC, tag + 0x054, 0x02C); } void amiiboCrypto_internalToNfcFormat(AmiiboInternal* internalData, AmiiboRawNFCData* nfcData) { uint8* tag = (uint8*)nfcData; uint8* intl = (uint8*)internalData; memcpy(tag + 0x008, intl + 0x000, 0x008); memcpy(tag + 0x080, intl + 0x008, 0x020); // tag hmac memcpy(tag + 0x010, intl + 0x028, 0x024); memcpy(tag + 0x0A0, intl + 0x04C, 0x168); memcpy(tag + 0x034, intl + 0x1B4, 0x020); // data hmac memcpy(tag + 0x000, intl + 0x1D4, 0x008); memcpy(tag + 0x054, intl + 0x1DC, 0x02C); } void amiiboCrypto_transform(const amiiboDerivedKeys_t * keys, uint8 * in, uint8 * out) { uint8 nonce[16]; memcpy(nonce, keys->aesIV, sizeof(nonce)); AmiiboInternal internalCopy; memcpy(&internalCopy, in, sizeof(internalCopy)); if (out != in) memcpy(out, in, 0x188 + 0x2C); AES128CTR_transform(out + 0x2C, 0x188, (uint8*)keys->aesKey, nonce); memcpy(out + 0x000, ((uint8*)&internalCopy) + 0x000, 0x008); memcpy(out + 0x028, ((uint8*)&internalCopy) + 0x028, 0x004); memcpy(out + 0x1D4, ((uint8*)&internalCopy) + 0x1D4, 0x034); } void AES128_init(); void amiiboInitMasterKeys(amiiboInternalKeys_t& kData, amiiboInternalKeys_t& kTag) { uint8 dataKey_hmacKey[16] = { 0x1D, 0x16, 0x4B, 0x37, 0x5B, 0x72, 0xA5, 0x57, 0x28, 0xB9, 0x1D, 0x64, 0xB6, 0xA3, 0xC2, 0x05 }; uint8 dataKey_typeString[14] = { "unfixed infos" }; uint8 dataKey_magicBytesSize = 0x0E; uint8 dataKey_magicBytes[14] = { 0xDB, 0x4B, 0x9E, 0x3F, 0x45, 0x27, 0x8F, 0x39, 0x7E, 0xFF, 0x9B, 0x4F, 0xB9, 0x93 }; uint8 dataKey_xorPad[32] = { 0x04, 0x49, 0x17, 0xDC, 0x76, 0xB4, 0x96, 0x40, 0xD6, 0xF8, 0x39, 0x39, 0x96, 0x0F, 0xAE, 0xD4, 0xEF, 0x39, 0x2F, 0xAA, 0xB2, 0x14, 0x28, 0xAA, 0x21, 0xFB, 0x54, 0xE5, 0x45, 0x05, 0x47, 0x66 }; uint8 tagKey_hmacKey[16] = { 0x7F, 0x75, 0x2D, 0x28, 0x73, 0xA2, 0x00, 0x17, 0xFE, 0xF8, 0x5C, 0x05, 0x75, 0x90, 0x4B, 0x6D }; uint8 tagKey_typeString[14] = { "locked secret" }; uint8 tagKey_magicBytesSize = 0x10; uint8 tagKey_magicBytes[16] = { 0xFD, 0xC8, 0xA0, 0x76, 0x94, 0xB8, 0x9E, 0x4C, 0x47, 0xD3, 0x7D, 0xE8, 0xCE, 0x5C, 0x74, 0xC1 }; uint8 tagKey_xorPad[32] = { 0x04, 0x49, 0x17, 0xDC, 0x76, 0xB4, 0x96, 0x40, 0xD6, 0xF8, 0x39, 0x39, 0x96, 0x0F, 0xAE, 0xD4, 0xEF, 0x39, 0x2F, 0xAA, 0xB2, 0x14, 0x28, 0xAA, 0x21, 0xFB, 0x54, 0xE5, 0x45, 0x05, 0x47, 0x66 }; memcpy(kData.hmacKey, dataKey_hmacKey, 16); memcpy(kData.typeString, dataKey_typeString, 13); kData.magicBytesLen = dataKey_magicBytesSize; memcpy(kData.magicBytes, dataKey_magicBytes, 14); memcpy(kData.xorPad, dataKey_xorPad, 32); memcpy(kTag.hmacKey, tagKey_hmacKey, 16); memcpy(kTag.typeString, tagKey_typeString, 13); kTag.magicBytesLen = tagKey_magicBytesSize; memcpy(kTag.magicBytes, tagKey_magicBytes, 16); memcpy(kTag.xorPad, tagKey_xorPad, 32); } void amiiboDecrypt() { amiiboInternalKeys_t kData{}; amiiboInternalKeys_t kTag{}; amiiboInitMasterKeys(kData, kTag); amiiboDerivedKeys_t derivedKeysData{}; amiiboDerivedKeys_t derivedKeysTag{}; amiiboCrypto_nfcFormatToInternal(&nfp_data.amiiboNFCData, &nfp_data.amiiboInternal); amiiboCrypto_genKey(&kData, &nfp_data.amiiboInternal, &derivedKeysData); amiiboCrypto_genKey(&kTag, &nfp_data.amiiboInternal, &derivedKeysTag); amiiboCrypto_transform(&derivedKeysData, (uint8*)&nfp_data.amiiboInternal, (uint8*)&nfp_data.amiiboInternal); // generate tag hmac uint32 mdLen = 32; HMAC(EVP_sha256(), derivedKeysTag.hmacKey, 16, (((uint8*)&nfp_data.amiiboInternal) + 0x1D4), 0x34, nfp_data.amiiboInternal.tagHMAC, &mdLen); // generate data hmac mdLen = 32; HMAC(EVP_sha256(), derivedKeysData.hmacKey, 16, (((uint8*)&nfp_data.amiiboInternal) + 0x29), 0x1DF, ((uint8*)&nfp_data.amiiboInternal) + 0x8, &mdLen); bool isValidTagHMAC = memcmp(nfp_data.amiiboNFCData.tagHMAC, nfp_data.amiiboInternal.tagHMAC, 32) == 0; bool isValidDataHMAC = memcmp(nfp_data.amiiboNFCData.dataHMAC, nfp_data.amiiboInternal.dataHMAC, 32) == 0; if (!isValidTagHMAC) cemuLog_log(LogType::Force, "Decrypt amiibo has invalid tag HMAC"); if (!isValidDataHMAC) cemuLog_log(LogType::Force, "Decrypt amiibo has invalid data HMAC"); nfp_data.hasInvalidHMAC = !isValidTagHMAC || !isValidDataHMAC; } void amiiboEncrypt(AmiiboRawNFCData* nfcOutput) { amiiboInternalKeys_t kData{}; amiiboInternalKeys_t kTag{}; amiiboInitMasterKeys(kData, kTag); amiiboDerivedKeys_t derivedKeysData{}; amiiboDerivedKeys_t derivedKeysTag{}; // copy internal Amiibo data AmiiboInternal internalCopy; memcpy(&internalCopy, &nfp_data.amiiboInternal, sizeof(AmiiboInternal)); // gen key amiiboCrypto_genKey(&kData, &internalCopy, &derivedKeysData); amiiboCrypto_genKey(&kTag, &internalCopy, &derivedKeysTag); // generate new HMACs uint8 tagHMAC[32]; uint32 mdLen = 32; HMAC(EVP_sha256(), derivedKeysTag.hmacKey, 16, (((uint8*)&internalCopy) + 0x1D4), 0x34, tagHMAC, &mdLen); mdLen = 32; uint8 dataHMAC[32]; HMAC(EVP_sha256(), derivedKeysData.hmacKey, 16, (((uint8*)&internalCopy) + 0x29), 0x1DF, dataHMAC, &mdLen); memset(internalCopy.tagHMAC, 0, 32); memset(internalCopy.dataHMAC, 0, 32); // transform amiiboCrypto_transform(&derivedKeysData, (uint8*)&internalCopy, (uint8*)&internalCopy); // set HMACs memcpy(internalCopy.tagHMAC, tagHMAC, 32); memcpy(internalCopy.dataHMAC, dataHMAC, 32); // convert back to nfc order amiiboCrypto_internalToNfcFormat(&internalCopy, nfcOutput); // restore NFC values that aren't part of the internal representation memcpy(nfcOutput->dynamicLock, nfp_data.amiiboNFCData.dynamicLock, 4); memcpy(nfcOutput->cfg0, nfp_data.amiiboNFCData.cfg0, 4); memcpy(nfcOutput->cfg1, nfp_data.amiiboNFCData.cfg1, 4); } ================================================ FILE: src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/nn_common.h" #include "nn_nfp.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Common/FileStream.h" #include "Cafe/CafeSystem.h" std::recursive_mutex g_nfpMutex; void nnNfpLock() { g_nfpMutex.lock(); } bool nnNfpTryLock() { return g_nfpMutex.try_lock(); } void nnNfpUnlock() { g_nfpMutex.unlock(); } struct AmiiboInternal { /* +0x000 */ uint16 lockBytes; /* +0x002 */ uint16 staticLock; /* +0x004 */ uint32 cc; /* +0x008 */ uint8 dataHMAC[32]; /* +0x028 */ uint8 ukn_A5; // always 0xA5 /* +0x029 */ uint8 writeCounterHigh; /* +0x029 */ uint8 writeCounterLow; /* +0x02B */ uint8 unk02B; /* encrypted region starts here */ struct { /* +0x02C */ uint8 flags; /* +0x02D */ uint8 countryCode; /* +0x02E */ uint16be crcWriteCounter; /* +0x030 */ uint16be date1; /* +0x032 */ uint16be date2; /* +0x034 */ uint32be crc; /* +0x038 */ uint16be nickname[10]; /* +0x04C */ uint8 mii[0x60]; /* +0x0AC */ uint32be appDataTitleIdHigh; /* +0x0B0 */ uint32be appDataTitleIdLow; /* +0x0B4 */ uint16be appWriteCounter; /* +0x0B6 */ uint16be appDataIdHigh; /* +0x0B8 */ uint16be appDataIdLow; /* +0x0BA */ uint16be ukn0BA; /* +0x0BC */ uint32be ukn0BC; /* +0x0C0 */ uint32be ukn0C0; /* +0x0C4 */ uint32be ukn0C4; /* +0x0C8 */ uint32be ukn0C8; /* +0x0CC */ uint32be ukn0CC; /* +0x0D0 */ uint32be ukn0D0; /* +0x0D4 */ uint32be ukn0D4; /* +0x0D8 */ uint32be ukn0D8; uint32 getAppDataAppId() { uint32 appId = (uint32)appDataIdHigh << 16; appId |= (uint32)appDataIdLow; return appId; } void setAppDataAppId(uint32 appId) { appDataIdHigh = (uint16)(appId >> 16); appDataIdLow = (uint16)(appId & 0xFFFF); } }amiiboSettings; /* +0x0DC */ uint8 applicationData[0xD8]; /* encrypted region ends here */ /* +0x1B4 */ uint8 tagHMAC[32]; /* +0x1D4 */ uint8 ntagSerial[7]; /* +0x1DB */ uint8 nintendoId; struct { /* +0x1DC */ uint8 gameAndCharacterId[2]; /* +0x1DE */ uint8 characterVariation; /* +0x1DF */ uint8 amiiboFigureType; /* +0x1E0 */ uint8 amiiboModelNumber[2]; /* +0x1E2 */ uint8 amiiboSeries; /* +0x1E3 */ uint8 ukn_02; // always 0x02 ? /* +0x1E4 */ uint8 ukn5C[4]; }amiiboIdentificationBlock; /* +0x1E8 */ uint8 keygenSalt[32]; }; static_assert(sizeof(AmiiboInternal) == 0x208); static_assert(offsetof(AmiiboInternal, dataHMAC) == 0x8); static_assert(offsetof(AmiiboInternal, amiiboSettings.appDataIdHigh) == 0xB6); static_assert(offsetof(AmiiboInternal, amiiboSettings.ukn0D8) == 0xD8); static_assert(offsetof(AmiiboInternal, tagHMAC) == 0x1B4); union AmiiboRawNFCData { // each page is 4 bytes struct { uint8 page0[4]; uint8 page1[4]; uint8 page2[4]; uint8 page3[4]; uint8 page4[4]; uint8 page5[4]; uint8 page6[4]; uint8 page7[4]; uint8 page8[4]; uint8 page9[4]; uint8 page10[4]; uint8 page11[4]; uint8 page12[4]; uint8 page13[4]; uint8 page14[4]; uint8 page15[4]; uint8 page16[4]; }; struct { uint8 rawByte[16 * 4]; }; struct { /* +0x000 */ uint8 ntagSerial[7]; /* +0x007 */ uint8 nintendoId; /* +0x008 */ uint8 lockBytes[2]; /* +0x00A */ uint8 staticLock[2]; /* +0x00C */ uint8 cc[4]; // compatibility container /* +0x010 */ uint8 ukn_A5; // always 0xA5 /* +0x011 */ uint8 writeCounter[2]; /* +0x013 */ uint8 unk013; /* +0x014 */ uint8 encryptedSettings[32]; /* +0x034 */ uint8 tagHMAC[32]; // data hmac /* +0x054 */ struct { /* +0x54 */ uint8 gameAndCharacterId[2]; /* +0x56 */ uint8 characterVariation; /* +0x57 */ uint8 amiiboFigureType; /* +0x58 */ uint8 amiiboModelNumber[2]; /* +0x5A */ uint8 amiiboSeries; /* +0x5B */ uint8 ukn_02; // always 0x02 ? /* +0x5C */ uint8 ukn5C[4]; }amiiboIdentificationBlock; /* +0x060 */ uint8 keygenSalt[32]; /* +0x080 */ uint8 dataHMAC[32]; /* +0x0A0 */ uint8 encryptedMii[0x60]; /* +0x100 */ uint8 encryptedTitleId[8]; /* +0x108 */ uint8 encryptedApplicationWriteCounter[2]; /* +0x10A */ uint8 encryptedApplicationAreaId[4]; /* +0x10E */ uint8 ukn10E[2]; /* +0x110 */ uint8 unk110[32]; /* +0x130 */ uint8 encryptedApplicationArea[0xD8]; /* +0x208 */ uint8 dynamicLock[4]; /* +0x20C */ uint8 cfg0[4]; /* +0x210 */ uint8 cfg1[4]; }; }; static_assert(sizeof(AmiiboRawNFCData) == 532); struct AmiiboProcessedData { uint8 uidLength; uint8 uid[7]; }; struct { bool nfpIsInitialized; MPTR activateEvent; MPTR deactivateEvent; bool isDetecting; bool isMounted; bool isReadOnly; bool hasOpenApplicationArea; // set to true if application area was opened or created // currently active Amiibo bool hasActiveAmiibo; fs::path amiiboPath; bool hasInvalidHMAC; uint32 amiiboTouchTime; AmiiboRawNFCData amiiboNFCData; // raw data AmiiboInternal amiiboInternal; // decrypted internal format AmiiboProcessedData amiiboProcessedData; }nfp_data = { 0 }; bool nnNfp_writeCurrentAmiibo(); #include "AmiiboCrypto.h" void nnNfpExport_SetActivateEvent(PPCInterpreter_t* hCPU) { // parameters: // r3 OSEvent_t* ppcDefineParamStructPtr(osEvent, coreinit::OSEvent, 0); ppcDefineParamMPTR(osEventMPTR, 0); debug_printf("nn_nfp.SetActivateEvent(0x%08x)\n", osEventMPTR); coreinit::OSInitEvent(osEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); nnNfpLock(); nfp_data.activateEvent = osEventMPTR; nnNfpUnlock(); osLib_returnFromFunction(hCPU, 0); // return value ukn } void nnNfpExport_SetDeactivateEvent(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(osEvent, coreinit::OSEvent, 0); ppcDefineParamMPTR(osEventMPTR, 0); cemuLog_log(LogType::NN_NFP, "SetDeactivateEvent(0x{:08x})", osEventMPTR); coreinit::OSInitEvent(osEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); nnNfpLock(); nfp_data.deactivateEvent = osEventMPTR; nnNfpUnlock(); osLib_returnFromFunction(hCPU, 0); // return value ukn } void nnNfpExport_Initialize(PPCInterpreter_t* hCPU) { debug_printf("Nfp Initialize()\n"); nfp_data.nfpIsInitialized = true; nfp_data.isDetecting = false; nfp_data.hasActiveAmiibo = false; nfp_data.hasOpenApplicationArea = false; nfp_data.activateEvent = MPTR_NULL; nfp_data.deactivateEvent = MPTR_NULL; osLib_returnFromFunction(hCPU, 0); } void nnNfpExport_StartDetection(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "StartDetection()"); nnNfpLock(); nfp_data.isDetecting = true; nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_StopDetection(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "StopDetection()"); nnNfpLock(); nfp_data.isDetecting = false; nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } #define NFP_TAG_MAX_LENGTH (10) typedef struct { /* +0x00 */ uint8 uidLength; /* +0x01 */ uint8 uid[0xA]; /* +0x0B */ uint8 unused0B[0x15]; /* +0x20 */ uint32 ukn20[4]; uint32 ukn30[4]; uint32 ukn40[4]; uint32 ukn50; }nfpTagInfo_t; static_assert(sizeof(nfpTagInfo_t) == 0x54, "nfpTagInfo_t has invalid size"); void nnNfpExport_GetTagInfo(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "GetTagInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(tagInfo, nfpTagInfo_t, 0); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code return; } memset(tagInfo, 0x00, sizeof(nfpTagInfo_t)); memcpy(tagInfo->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); tagInfo->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; // todo - remaining values nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_Mount(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "Mount()"); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code return; } nfp_data.isMounted = true; nfp_data.isReadOnly = false; nfp_data.isDetecting = false; nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_Unmount(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "Unmount()"); nfp_data.hasOpenApplicationArea = false; osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_MountRom(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "MountRom()"); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code return; } nfp_data.isMounted = true; nfp_data.isReadOnly = true; nfp_data.isDetecting = false; nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } namespace nn::nfp { struct RomInfo { /* +0x00 */ uint8 characterId[3]; /* +0x03 */ uint8 amiiboSeries; /* +0x04 */ uint16be number; /* +0x06 */ uint8 nfpType; /* +0x07 */ uint8 unused[0x2F]; }; static_assert(offsetof(RomInfo, amiiboSeries) == 0x3); static_assert(offsetof(RomInfo, number) == 0x4); static_assert(offsetof(RomInfo, nfpType) == 0x6); static_assert(sizeof(RomInfo) == 0x36); using ReadOnlyInfo = RomInfo; // same layout void GetRomInfo(RomInfo* romInfo) { cemu_assert_debug(nfp_data.hasActiveAmiibo); memset(romInfo, 0x00, sizeof(RomInfo)); romInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0]; romInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1]; romInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; // guessed romInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; // guessed romInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; // guessed romInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed memset(romInfo->unused, 0x00, sizeof(romInfo->unused)); } nnResult GetNfpRomInfo(RomInfo* romInfo) { nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0); // todo: Return correct error code } GetRomInfo(romInfo); nnNfpUnlock(); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0); } nnResult GetNfpReadOnlyInfo(ReadOnlyInfo* readOnlyInfo) { nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0); // todo: Return correct error code } GetRomInfo(readOnlyInfo); nnNfpUnlock(); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0); } }; typedef struct { uint16be year; uint8 month; uint8 day; }nfpDate_t; typedef struct { /* +0x00 */ nfpDate_t date; /* +0x04 */ uint8 writeCount[2]; /* +0x06 */ uint8 characterId[3]; /* +0x09 */ uint8 amiiboSeries; /* +0x0A */ uint16be number; /* +0x0C */ uint8 nfpType; /* +0x0D */ uint8 nfpVersion; /* +0x0E */ uint16be applicationAreaSize; /* +0x10 */ uint8 unused[0x30]; }nfpCommonData_t; static_assert(sizeof(nfpCommonData_t) == 0x40, "nfpCommonData_t has invalid size"); static_assert(offsetof(nfpCommonData_t, writeCount) == 0x4, "nfpCommonData.writeCount has invalid offset"); static_assert(offsetof(nfpCommonData_t, amiiboSeries) == 0x9, "nfpCommonData.seriesId has invalid offset"); static_assert(offsetof(nfpCommonData_t, nfpType) == 0xC, "nfpCommonData.nfpType has invalid offset"); static_assert(offsetof(nfpCommonData_t, applicationAreaSize) == 0xE, "nfpCommonData.applicationAreaSize has invalid offset"); void nnNfpExport_GetNfpCommonInfo(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "GetNfpCommonInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(commonInfo, nfpCommonData_t, 0); nnNfpLock(); if (nfp_data.hasActiveAmiibo == false) { nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code return; } // tag info format is currently unknown, so we just set it to all zeros for now if (sizeof(nfpCommonData_t) != 0x40 ) assert_dbg(); memset(commonInfo, 0x00, sizeof(nfpCommonData_t)); cemuLog_logDebug(LogType::Force, "GetNfpCommonInfo(0x{:08x})"); commonInfo->writeCount[0] = nfp_data.amiiboNFCData.writeCounter[0]; commonInfo->writeCount[1] = nfp_data.amiiboNFCData.writeCounter[1]; commonInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0]; commonInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1]; commonInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; commonInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; commonInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; commonInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed commonInfo->applicationAreaSize = sizeof(nfp_data.amiiboInternal.applicationData); // not 100% sure if this is always set to the maximum nnNfpUnlock(); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } typedef struct { /* +0x00 */ uint8 ownerMii[0x60]; /* +0x60 */ uint8 nickname[0x14]; /* +0x74 */ uint16be ukn74; /* +0x76 */ uint8 ukn76; /* +0x77 */ uint8 _padding77; /* +0x78 */ nfpDate_t registerDate; /* +0x7C */ uint8 ukn7C[0x2C]; }nfpRegisterInfo_t; typedef struct { /* +0x00 */ uint8 ownerMii[0x60]; /* +0x60 */ uint8 nickname[0x14]; // maybe has more fields? }nfpRegisterInfoSet_t; void nnNfpExport_GetNfpRegisterInfo(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "GetNfpRegisterInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(registerInfo, nfpRegisterInfo_t, 0); if(!registerInfo) { osLib_returnFromFunction(hCPU, 0xC1B03780); return; } memset(registerInfo, 0, sizeof(nfpRegisterInfo_t)); if ((nfp_data.amiiboInternal.amiiboSettings.flags & 0x10) != 0) { memcpy(registerInfo->ownerMii, nfp_data.amiiboInternal.amiiboSettings.mii, sizeof(nfp_data.amiiboInternal.amiiboSettings.mii)); memcpy(registerInfo->nickname, nfp_data.amiiboInternal.amiiboSettings.nickname, sizeof(nfp_data.amiiboInternal.amiiboSettings.nickname)); } // todo - missing fields osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_InitializeRegisterInfoSet(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "InitializeRegisterInfoSet(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(registerInfoSet, nfpRegisterInfoSet_t, 0); memset(registerInfoSet, 0, sizeof(nfpRegisterInfoSet_t)); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_SetNfpRegisterInfo(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "SetNfpRegisterInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(registerInfoSet, nfpRegisterInfoSet_t, 0); memcpy(nfp_data.amiiboInternal.amiiboSettings.mii, registerInfoSet->ownerMii, sizeof(nfp_data.amiiboInternal.amiiboSettings.mii)); memcpy(nfp_data.amiiboInternal.amiiboSettings.nickname, registerInfoSet->nickname, sizeof(nfp_data.amiiboInternal.amiiboSettings.nickname)); // todo - set register date and other values nfp_data.amiiboInternal.amiiboSettings.flags |= 0x10; // set registered bit osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_IsExistApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "IsExistApplicationArea()"); if (!nfp_data.hasActiveAmiibo || !nfp_data.isMounted) { osLib_returnFromFunction(hCPU, 0); return; } bool appAreaExists = (nfp_data.amiiboInternal.amiiboSettings.flags & 0x20) != 0; osLib_returnFromFunction(hCPU, appAreaExists ? 1 : 0); } void nnNfpExport_OpenApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "OpenApplicationArea(0x{:08x})", hCPU->gpr[3]); ppcDefineParamU32(appAreaId, 0); // note - this API doesn't fail if the application area has already been opened? if (!(nfp_data.amiiboInternal.amiiboSettings.flags & 0x20)) { // no application data set osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, NN_RESULT_NFP_CODE_NOAPPAREA)); return; } uint32 nfpAppAreaId = nfp_data.amiiboInternal.amiiboSettings.getAppDataAppId(); if (nfpAppAreaId != appAreaId) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, NN_RESULT_NFP_CODE_APPAREAIDMISMATCH)); return; } nfp_data.hasOpenApplicationArea = true; osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_ReadApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "ReadApplicationArea(0x{:08x}, 0x{:x})", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamPtr(bufferPtr, uint8*, 0); ppcDefineParamU32(len, 1); if (!nfp_data.hasOpenApplicationArea) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } len = std::min(len, (uint32)sizeof(nfp_data.amiiboInternal.applicationData)); memcpy(bufferPtr, nfp_data.amiiboInternal.applicationData, len); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_WriteApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "WriteApplicationArea(0x{:08x}, 0x{:x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); ppcDefineParamPtr(bufferPtr, uint8*, 0); ppcDefineParamU32(len, 1); if (!nfp_data.hasOpenApplicationArea) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } if (nfp_data.isReadOnly) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } len = std::min(len, (uint32)sizeof(nfp_data.amiiboInternal.applicationData)); memcpy(nfp_data.amiiboInternal.applicationData, bufferPtr, len); // remaining data is filled with random bytes for (uint32 i = len; i < sizeof(nfp_data.amiiboInternal.applicationData); i++) nfp_data.amiiboInternal.applicationData[i] = rand() & 0xFF; nfp_data.amiiboInternal.amiiboSettings.appWriteCounter = nfp_data.amiiboInternal.amiiboSettings.appWriteCounter + 1; osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } typedef struct { uint32be appAreaId; MEMPTR<uint8> initialData; uint32be initialLen; // more? }NfpCreateInfo_t; void nnNfpExport_CreateApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "CreateApplicationArea(0x{:08x})", hCPU->gpr[3]); ppcDefineParamPtr(createInfo, NfpCreateInfo_t, 0); if (nfp_data.hasOpenApplicationArea || (nfp_data.amiiboInternal.amiiboSettings.flags&0x20)) { // cant create app area if it already exists osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } if (nfp_data.isReadOnly) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } void* writePtr = createInfo->initialData.GetPtr(); uint32 writeSize = createInfo->initialLen; if (writeSize > sizeof(nfp_data.amiiboInternal.applicationData)) { // requested write size larger than available space osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } nfp_data.amiiboInternal.amiiboSettings.setAppDataAppId(createInfo->appAreaId); nfp_data.amiiboInternal.amiiboSettings.flags |= 0x20; // set application data exists bit nfp_data.amiiboInternal.amiiboSettings.appWriteCounter = nfp_data.amiiboInternal.amiiboSettings.appWriteCounter + 1; nfp_data.hasOpenApplicationArea = false; // write initial data to application area memcpy(nfp_data.amiiboInternal.applicationData, writePtr, writeSize); // remaining data is filled with random bytes for (uint32 i = writeSize; i < sizeof(nfp_data.amiiboInternal.applicationData); i++) nfp_data.amiiboInternal.applicationData[i] = rand() & 0xFF; // this API forces a flush (unsure, but without this data written by Smash doesn't stick) if (!nnNfp_writeCurrentAmiibo()) { cemuLog_log(LogType::Force, "Failed to write Amiibo file data when trying to remove appArea"); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_DeleteApplicationArea(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "DeleteApplicationArea()"); if (nfp_data.isReadOnly) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } if ((nfp_data.amiiboInternal.amiiboSettings.flags & 0x20) == 0) { osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } nfp_data.amiiboInternal.amiiboSettings.setAppDataAppId(0); nfp_data.amiiboInternal.amiiboSettings.flags &= ~0x20; nfp_data.amiiboInternal.amiiboSettings.appWriteCounter = nfp_data.amiiboInternal.amiiboSettings.appWriteCounter + 1; // this API forces a flush if (!nnNfp_writeCurrentAmiibo()) { cemuLog_log(LogType::Force, "Failed to write Amiibo file data when trying to remove appArea"); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfpExport_Flush(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "Flush()"); // write Amiibo data if (nfp_data.isReadOnly) { cemuLog_log(LogType::Force, "Cannot write to Amiibo when it is mounted in read-only mode"); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } if (!nnNfp_writeCurrentAmiibo()) { cemuLog_log(LogType::Force, "Failed to write Amiibo data"); osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); return; } osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } typedef struct { /* +0x000 */ uint8 ukn00[0x10]; /* +0x010 */ uint32be mode; /* +0x014 */ uint8 tagInfo[0x54]; /* +0x068 */ uint8 isRegistered; // could be uint32be? /* +0x069 */ uint8 _padding69[3]; /* +0x06C */ uint8 registerInfo[0xA8]; /* +0x114 */ uint8 commonInfo[0x40]; /* +0x154 */ uint8 ukn154[0x20]; }AmiiboSettingsArgs_t; static_assert(sizeof(AmiiboSettingsArgs_t) == 0x174); static_assert(offsetof(AmiiboSettingsArgs_t, mode) == 0x10); static_assert(offsetof(AmiiboSettingsArgs_t, tagInfo) == 0x14); static_assert(offsetof(AmiiboSettingsArgs_t, isRegistered) == 0x68); static_assert(offsetof(AmiiboSettingsArgs_t, registerInfo) == 0x6C); static_assert(offsetof(AmiiboSettingsArgs_t, commonInfo) == 0x114); void nnNfpExport_GetAmiiboSettingsArgs(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "GetAmiiboSettingsArgs(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(settingsArg, AmiiboSettingsArgs_t, 0); memset(settingsArg, 0, sizeof(AmiiboSettingsArgs_t)); // modes: // 0 -> Register owner and nickname // 0x64 -> Launch normally settingsArg->mode = 0x64; osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } void nnNfp_unloadAmiibo() { nnNfpLock(); nfp_data.isMounted = false; nfp_data.hasActiveAmiibo = false; nnNfpUnlock(); } bool nnNfp_isInitialized() { return nfp_data.nfpIsInitialized; } // CEMU NFC error codes #define NFC_ERROR_NONE (0) #define NFC_ERROR_NO_ACCESS (1) #define NFC_ERROR_INVALID_FILE_FORMAT (2) bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError) { AmiiboRawNFCData rawData = { 0 }; auto nfcData = FileStream::LoadIntoMemory(filePath); if (!nfcData) { *nfcError = NFC_ERROR_NO_ACCESS; return false; } if (nfcData->size() < sizeof(AmiiboRawNFCData)) { *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; return false; } memcpy(&rawData, nfcData->data(), sizeof(AmiiboRawNFCData)); // verify if the file is a valid ntag215/amiibo file if (rawData.dynamicLock[0] != 0x01 || rawData.dynamicLock[1] != 0x00 || rawData.dynamicLock[2] != 0x0F || rawData.dynamicLock[3] != 0xBD) { // Temporary workaround to fix corrupted files by old cemu versions rawData.dynamicLock[0] = 0x01; rawData.dynamicLock[1] = 0x00; rawData.dynamicLock[2] = 0x0F; rawData.dynamicLock[3] = 0xBD; // *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; // return false; } if (rawData.cfg0[0] != 0x00 || rawData.cfg0[1] != 0x00 || rawData.cfg0[2] != 0x00 || rawData.cfg0[3] != 0x04) { *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; return false; } if (rawData.cfg1[0] != 0x5F || rawData.cfg1[1] != 0x00 || rawData.cfg1[2] != 0x00 || rawData.cfg1[3] != 0x00) { *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; return false; } if (rawData.staticLock[0] != 0x0F || rawData.staticLock[1] != 0xE0) { *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; return false; } if (rawData.cc[0] != 0xF1 || rawData.cc[1] != 0x10 || rawData.cc[2] != 0xFF || rawData.cc[3] != 0xEE) { *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; return false; } // process uid uint8 serialNumber[7]; serialNumber[0] = rawData.ntagSerial[0]; serialNumber[1] = rawData.ntagSerial[1]; serialNumber[2] = rawData.ntagSerial[2]; serialNumber[3] = rawData.ntagSerial[4]; serialNumber[4] = rawData.ntagSerial[5]; serialNumber[5] = rawData.ntagSerial[6]; serialNumber[6] = rawData.nintendoId; uint8 serialCheckByte0 = rawData.ntagSerial[3]; uint8 serialCheckByte1 = rawData.lockBytes[0]; uint8 bcc0 = serialNumber[0] ^ serialNumber[1] ^ serialNumber[2] ^ 0x88; uint8 bcc1 = serialNumber[3] ^ serialNumber[4] ^ serialNumber[5] ^ serialNumber[6]; if (serialCheckByte0 != bcc0 || serialCheckByte1 != bcc1) { cemuLog_log(LogType::Force, "nn_nfp: Mismatch in serial checksum of scanned NFC tag"); } nfp_data.amiiboProcessedData.uidLength = 7; memcpy(nfp_data.amiiboProcessedData.uid, serialNumber, 7); // signal activation event nnNfp_unloadAmiibo(); nnNfpLock(); memcpy(&nfp_data.amiiboNFCData, &rawData, sizeof(AmiiboRawNFCData)); // decrypt amiibo amiiboDecrypt(); nfp_data.amiiboPath = filePath; nfp_data.hasActiveAmiibo = true; if (nfp_data.activateEvent) { MEMPTR<coreinit::OSEvent> osEvent(nfp_data.activateEvent); coreinit::OSSignalEvent(osEvent); } nfp_data.amiiboTouchTime = GetTickCount(); nnNfpUnlock(); *nfcError = NFC_ERROR_NO_ACCESS; return true; } bool nnNfp_writeCurrentAmiibo() { nnNfpLock(); if (!nfp_data.hasActiveAmiibo) { nnNfpUnlock(); return false; } uint16 writeCounter = nfp_data.amiiboInternal.writeCounterLow + (nfp_data.amiiboInternal.writeCounterHigh << 8); writeCounter++; nfp_data.amiiboInternal.writeCounterLow = writeCounter & 0xFF; nfp_data.amiiboInternal.writeCounterHigh = (writeCounter >> 8) & 0xFF; // open file for writing FileStream* fs = FileStream::openFile2(nfp_data.amiiboPath, true); if (!fs) { nnNfpUnlock(); return false; } // encrypt Amiibo and convert to NFC format AmiiboRawNFCData nfcData; amiiboEncrypt(&nfcData); // write to file fs->writeData(&nfcData, sizeof(AmiiboRawNFCData)); delete fs; nnNfpUnlock(); return true; } void nnNfp_update() { // lock-free check if amiibo is touching if (nfp_data.hasActiveAmiibo == false) return; if (!nnNfpTryLock()) return; // make sure amiibo is still touching after acquiring lock if (nfp_data.hasActiveAmiibo == false) return; uint32 amiiboElapsedTouchTime = GetTickCount() - nfp_data.amiiboTouchTime; if (amiiboElapsedTouchTime >= 1500) { nnNfp_unloadAmiibo(); if (nfp_data.deactivateEvent) { coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent); coreinit::OSSignalEvent(osEvent); } } nnNfpUnlock(); } void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "GetNfpState()"); // workaround for Mario Party 10 eating CPU cycles in an infinite loop (maybe due to incorrect NFP detection handling?) uint64 titleId = CafeSystem::GetForegroundTitleId(); if (titleId == 0x0005000010162d00 || titleId == 0x0005000010162e00) { coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK / 100); // pause for 10ms } uint32 nfpState; if (nfp_data.nfpIsInitialized == false) { nfpState = NFP_STATE_NONE; } else { if (nfp_data.isMounted && nfp_data.hasActiveAmiibo) { if (nfp_data.isReadOnly) nfpState = NFP_STATE_RW_MOUNT_ROM; else nfpState = NFP_STATE_RW_MOUNT; } else if (nfp_data.isDetecting) { // todo: is this handled correctly? if (nfp_data.hasActiveAmiibo) { nfpState = NFP_STATE_RW_ACTIVE; } else nfpState = NFP_STATE_RW_SEARCH; } else { nfpState = NFP_STATE_INIT; } } // returns state of nfp library osLib_returnFromFunction(hCPU, nfpState); } namespace nn::nfp { typedef struct { /* +0x00 */ uint8 uidLength; /* +0x01 */ uint8 uid[0xA]; /* +0x0B */ uint8 ukn0B; /* +0x0C */ uint8 ukn0C; /* +0x0D */ uint8 ukn0D; // more? }NFCTagInfoCallbackParam_t; uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) { cemuLog_log(LogType::NN_NFP, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam ? memory_getVirtualOffsetFromPointer(userParam) : 0); cemu_assert(index == 0); nnNfpLock(); StackAllocator<NFCTagInfoCallbackParam_t> _callbackParam; NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); nnNfpUnlock(); return 0; // 0 -> success } uint32 GetErrorCode(uint32 result) { uint32 level = (result >> 0x1b) & 3; uint32 mask = 0x7f00000; if (level != 3) { mask = 0x1ff00000; } if (((result & mask) == 0x1b00000) && ((result & 0x80000000) != 0)) { mask = 0x3ff; if (level != 3) { mask = 0xfffff; } return ((result & mask) >> 7) + 1680000; } return 1680000; } void nnNfp_load() { osLib_addFunction("nn_nfp", "SetActivateEvent__Q2_2nn3nfpFP7OSEvent", nnNfpExport_SetActivateEvent); osLib_addFunction("nn_nfp", "SetDeactivateEvent__Q2_2nn3nfpFP7OSEvent", nnNfpExport_SetDeactivateEvent); osLib_addFunction("nn_nfp", "StartDetection__Q2_2nn3nfpFv", nnNfpExport_StartDetection); osLib_addFunction("nn_nfp", "StopDetection__Q2_2nn3nfpFv", nnNfpExport_StopDetection); osLib_addFunction("nn_nfp", "GetTagInfo__Q2_2nn3nfpFPQ3_2nn3nfp7TagInfo", nnNfpExport_GetTagInfo); osLib_addFunction("nn_nfp", "Mount__Q2_2nn3nfpFv", nnNfpExport_Mount); osLib_addFunction("nn_nfp", "MountRom__Q2_2nn3nfpFv", nnNfpExport_MountRom); osLib_addFunction("nn_nfp", "Unmount__Q2_2nn3nfpFv", nnNfpExport_Unmount); osLib_addFunction("nn_nfp", "GetNfpCommonInfo__Q2_2nn3nfpFPQ3_2nn3nfp10CommonInfo", nnNfpExport_GetNfpCommonInfo); osLib_addFunction("nn_nfp", "GetNfpRegisterInfo__Q2_2nn3nfpFPQ3_2nn3nfp12RegisterInfo", nnNfpExport_GetNfpRegisterInfo); osLib_addFunction("nn_nfp", "InitializeRegisterInfoSet__Q2_2nn3nfpFPQ3_2nn3nfp15RegisterInfoSet", nnNfpExport_InitializeRegisterInfoSet); osLib_addFunction("nn_nfp", "SetNfpRegisterInfo__Q2_2nn3nfpFRCQ3_2nn3nfp15RegisterInfoSet", nnNfpExport_SetNfpRegisterInfo); osLib_addFunction("nn_nfp", "IsExistApplicationArea__Q2_2nn3nfpFv", nnNfpExport_IsExistApplicationArea); osLib_addFunction("nn_nfp", "OpenApplicationArea__Q2_2nn3nfpFUi", nnNfpExport_OpenApplicationArea); osLib_addFunction("nn_nfp", "CreateApplicationArea__Q2_2nn3nfpFRCQ3_2nn3nfp25ApplicationAreaCreateInfo", nnNfpExport_CreateApplicationArea); osLib_addFunction("nn_nfp", "DeleteApplicationArea__Q2_2nn3nfpFv", nnNfpExport_DeleteApplicationArea); osLib_addFunction("nn_nfp", "ReadApplicationArea__Q2_2nn3nfpFPvUi", nnNfpExport_ReadApplicationArea); osLib_addFunction("nn_nfp", "WriteApplicationArea__Q2_2nn3nfpFPCvUiRCQ3_2nn3nfp5TagId", nnNfpExport_WriteApplicationArea); osLib_addFunction("nn_nfp", "Flush__Q2_2nn3nfpFv", nnNfpExport_Flush); osLib_addFunction("nn_nfp", "Initialize__Q2_2nn3nfpFv", nnNfpExport_Initialize); osLib_addFunction("nn_nfp", "GetNfpState__Q2_2nn3nfpFv", nnNfpExport_GetNfpState); osLib_addFunction("nn_nfp", "GetAmiiboSettingsArgs__Q2_2nn3nfpFPQ3_2nn3nfp18AmiiboSettingsArgs", nnNfpExport_GetAmiiboSettingsArgs); } class : public COSModule { public: std::string_view GetName() override { return "nn_nfp"; } void RPLMapped() override { nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::NN_NFP); cafeExportRegisterFunc(nn::nfp::GetNfpRomInfo, "nn_nfp", "GetNfpRomInfo__Q2_2nn3nfpFPQ3_2nn3nfp7RomInfo", LogType::NN_NFP); cafeExportRegisterFunc(nn::nfp::GetNfpReadOnlyInfo, "nn_nfp", "GetNfpReadOnlyInfo__Q2_2nn3nfpFPQ3_2nn3nfp12ReadOnlyInfo", LogType::NN_NFP); }; }s_COSnnNfpModule; COSModule* GetModule() { return &s_COSnnNfpModule; } } ================================================ FILE: src/Cafe/OS/libs/nn_nfp/nn_nfp.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace nn::nfp { uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam); COSModule* GetModule(); } void nnNfp_load(); void nnNfp_update(); bool nnNfp_isInitialized(); bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); #define NFP_STATE_NONE (0) #define NFP_STATE_INIT (1) #define NFP_STATE_RW_SEARCH (2) #define NFP_STATE_RW_ACTIVE (3) #define NFP_STATE_RW_DEACTIVE (4) #define NFP_STATE_RW_MOUNT (5) #define NFP_STATE_UNEXPECTED (6) #define NFP_STATE_RW_MOUNT_ROM (7) ================================================ FILE: src/Cafe/OS/libs/nn_nim/nn_nim.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/IOSU/legacy/iosu_ioctl.h" #include "Cafe/IOSU/legacy/iosu_nim.h" #include "Cafe/OS/libs/coreinit/coreinit_IOS.h" #include "Cafe/OS/libs/nn_common.h" #define nimPrepareRequest() \ StackAllocator<iosu::nim::iosuNimCemuRequest_t> _buf_nimRequest; \ StackAllocator<ioBufferVector_t> _buf_bufferVector; \ iosu::nim::iosuNimCemuRequest_t* nimRequest = _buf_nimRequest.GetPointer(); \ ioBufferVector_t* nimBufferVector = _buf_bufferVector.GetPointer(); \ memset(nimRequest, 0, sizeof(iosu::nim::iosuNimCemuRequest_t)); \ memset(nimBufferVector, 0, sizeof(ioBufferVector_t)); \ nimBufferVector->buffer = (uint8*)nimRequest; namespace nn { namespace nim { void export_NeedsNetworkUpdate(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "NeedsNetworkUpdate() - placeholder"); ppcDefineParamTypePtr(needsUpdate, uint8, 0); *needsUpdate = 0; osLib_returnFromFunction(hCPU, 0); } typedef struct { uint32be ukn00; uint32be ukn04; uint32be ukn08; uint32be ukn0C; uint32be ukn10; uint32be ukn14; uint32be ukn18; }updatePackageProgress_t; void export_GetUpdatePackageProgress(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GetUpdatePackageProgress() - placeholder"); ppcDefineParamTypePtr(updatePackageProgress, updatePackageProgress_t, 0); // status of system update download // values are unknown updatePackageProgress->ukn00 = 0; updatePackageProgress->ukn04 = 0; updatePackageProgress->ukn18 = 0; osLib_returnFromFunction(hCPU, 0); } void export_NeedsNotifyToUsers(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "NeedsNotifyToUsers() - placeholder"); ppcDefineParamTypePtr(updatePackageProgress, updatePackageProgress_t, 0); osLib_returnFromFunction(hCPU, 0); } void export_GetNumTitlePackages(PPCInterpreter_t* hCPU) { nimPrepareRequest(); nimRequest->requestCode = IOSU_NIM_GET_PACKAGE_COUNT; __depr__IOS_Ioctlv(IOS_DEVICE_NIM, IOSU_NIM_REQUEST_CEMU, 1, 1, nimBufferVector); uint32 numTitlePackages = nimRequest->resultU32.u32; osLib_returnFromFunction(hCPU, numTitlePackages); } static_assert(sizeof(iosu::nim::titlePackageInfo_t) == 0x50, "titlePackageInfo_t has invalid size"); static_assert(offsetof(iosu::nim::titlePackageInfo_t, ukn28DLProgressRelatedMax_u64be) == 0x28, "ukn28_u64be has invalid offset"); void export_ListTitlePackagesStatically(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "ListTitlePackagesStatically() - placeholder"); ppcDefineParamTypePtr(titleIdList, uint64, 0); ppcDefineParamS32(maxCount, 1); nimPrepareRequest(); nimRequest->requestCode = IOSU_NIM_GET_PACKAGES_TITLEID; nimRequest->maxCount = maxCount; nimRequest->ptr = (uint8*)(titleIdList); __depr__IOS_Ioctlv(IOS_DEVICE_NIM, IOSU_NIM_REQUEST_CEMU, 1, 1, nimBufferVector); osLib_returnFromFunction(hCPU, 0); } void export_GetTitlePackageInfos(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GetTitlePackageInfos() - placeholder"); ppcDefineParamTypePtr(titlePackageInfo, iosu::nim::titlePackageInfo_t, 0); ppcDefineParamTypePtr(titleIdList, uint64, 1); ppcDefineParamU32(count, 2); nimPrepareRequest(); nimRequest->requestCode = IOSU_NIM_GET_PACKAGES_INFO; nimRequest->maxCount = count; nimRequest->ptr = (uint8*)(titleIdList); nimRequest->ptr2 = (uint8*)(titlePackageInfo); __depr__IOS_Ioctlv(IOS_DEVICE_NIM, IOSU_NIM_REQUEST_CEMU, 1, 1, nimBufferVector); osLib_returnFromFunction(hCPU, 0); } void export_NeedsNotifyToUsersTitlePackage(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "NeedsNotifyToUsers() - placeholder"); ppcDefineParamTypePtr(titlePackageInfo, iosu::nim::titlePackageInfo_t, 0); osLib_returnFromFunction(hCPU, 0); } using IDBE_DATA = uint8[0x12060]; void export_GetIconDatabaseEntries(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "GetIconDatabaseEntries() - placeholder"); ppcDefineParamTypePtr(iconDatabaseEntries, IDBE_DATA, 0); ppcDefineParamTypePtr(titleIdList, uint64, 1); ppcDefineParamS32(count, 2); cemu_assert_debug(count == 1); // other count values are untested for (sint32 i = 0; i < count; i++) { nimPrepareRequest(); nimRequest->requestCode = IOSU_NIM_GET_ICON_DATABASE_ENTRY; nimRequest->titleId = _swapEndianU64(titleIdList[i]); nimRequest->ptr = (uint8*)(iconDatabaseEntries + i); __depr__IOS_Ioctlv(IOS_DEVICE_NIM, IOSU_NIM_REQUEST_CEMU, 1, 1, nimBufferVector); } osLib_returnFromFunction(hCPU, 0); } void export_QuerySchedulerStatus(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "QuerySchedulerStatus() - placeholder"); // scheduler status seems to be either a 4 byte array or 8 byte array (or structs)? // scope.rpx only checks the second byte and if it matches 0x01 then the scheduler is considered paused/stopped (displays that downloads are inactive) // men.rpx checks the first byte for == 1 and if true, it will show the download manager icon as downloading // downloads disabled: //memory_writeU32(hCPU->gpr[3], (0x00010000)); // downloads enabled: memory_writeU32(hCPU->gpr[3], (0x00000000)); osLib_returnFromFunction(hCPU, 0); } struct nimResultError { uint32be iosError; uint32be ukn04; }; void ConstructResultError(nimResultError* resultError, uint32be* nimErrorCodePtr, uint32 uknParam) { uint32 nnResultCode = *nimErrorCodePtr; resultError->iosError = nnResultCode; resultError->ukn04 = uknParam; if (nnResultCode == 0xFFFFFFFF) { // not a valid code, used by a Wii U menu return; } // IOS errors need to be translated if ( (nnResultCode&0x18000000) == 0x18000000) { // alternative error format cemu_assert_unimplemented(); } else { auto moduleId = nn::nnResult_GetModule(nnResultCode); if (moduleId == NN_RESULT_MODULE_NN_IOS) { // ios error cemu_assert_unimplemented(); } else { // other error resultError->iosError = 0; } } } void export_GetECommerceInfrastructureCountry(PPCInterpreter_t* hCPU) { ppcDefineParamU32BEPtr(country, 0); cemuLog_logDebug(LogType::Force, "GetECommerceInfrastructureCountry - todo"); *country = 0; osLib_returnFromFunction(hCPU, 0); } typedef struct { betype<uint32> titleIdHigh; betype<uint32> titleIdLow; uint32 regionOrLanguageRelated; uint8 uknByte0C; uint8 applicationBoxDevice1; // this is what E-Shop app reads to determine device (0 -> mlc, 1 -> extern storage?) uint8 uknByte0E; uint8 applicationBoxDevice2; uint32 ukn10; uint8 uknByte14; // set to 0 uint8 uknByte15; // set to 1 uint8 postDownloadAction; // 0 -> ?, 1 -> ?, 2 -> Use bg install policy uint8 uknBytes17; }TitlePackageTaskConfig_t; static_assert(sizeof(TitlePackageTaskConfig_t) == 0x18, ""); void export_MakeTitlePackageTaskConfigAutoUsingBgInstallPolicy(PPCInterpreter_t* hCPU) { ppcDefineParamPtr(titlePackageTastConfig, TitlePackageTaskConfig_t, 0); ppcDefineParamU64(titleId, 2); ppcDefineParamU32(regionOrLanguage, 4); ppcDefineParamU32(uknR8, 5); // title type? cemuLog_logDebug(LogType::Force, "MakeTitlePackageTaskConfigAutoUsingBgInstallPolicy - placeholder"); titlePackageTastConfig->titleIdHigh = (uint32)(titleId >> 32); titlePackageTastConfig->titleIdLow = (uint32)(titleId & 0xFFFFFFFF); titlePackageTastConfig->regionOrLanguageRelated = 0; // ? titlePackageTastConfig->uknByte0C = uknR8; titlePackageTastConfig->applicationBoxDevice1 = 1; // 1 -> mlc titlePackageTastConfig->applicationBoxDevice2 = 1; // 1 -> mlc titlePackageTastConfig->uknByte0E = 0; // ? titlePackageTastConfig->ukn10 = 0; // ? titlePackageTastConfig->uknByte14 = 0; // ? titlePackageTastConfig->uknByte15 = 1; // ? titlePackageTastConfig->postDownloadAction = 0; // ? titlePackageTastConfig->uknBytes17 = 0; // ? osLib_returnFromFunction(hCPU, 0); } void export_CalculateTitleInstallSize(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(installSize, betype<uint64>, 0); ppcDefineParamPtr(titlePackageTastConfig, TitlePackageTaskConfig_t, 1); // get install size of currently installed title, otherwise return -1 as size cemuLog_logDebug(LogType::Force, "CalculateTitleInstallSize - todo"); *installSize = 0xFFFFFFFFFFFFFFFF; osLib_returnFromFunction(hCPU, 0); } class : public COSModule { public: std::string_view GetName() override { return "nn_nim"; } void RPLMapped() override { osLib_addFunction("nn_nim", "NeedsNetworkUpdate__Q2_2nn3nimFPb", export_NeedsNetworkUpdate); osLib_addFunction("nn_nim", "GetUpdatePackageProgress__Q2_2nn3nimFPQ3_2nn3nim21UpdatePackageProgress", export_GetUpdatePackageProgress); osLib_addFunction("nn_nim", "NeedsNotifyToUsers__Q3_2nn3nim4utilFPCQ3_2nn3nim21UpdatePackageProgress", export_NeedsNotifyToUsers); osLib_addFunction("nn_nim", "GetNumTitlePackages__Q2_2nn3nimFv", export_GetNumTitlePackages); osLib_addFunction("nn_nim", "GetTitlePackageInfos__Q2_2nn3nimFPQ3_2nn3nim16TitlePackageInfoPCULUi", export_GetTitlePackageInfos); osLib_addFunction("nn_nim", "NeedsNotifyToUsers__Q3_2nn3nim4utilFPCQ3_2nn3nim16TitlePackageInfoPCQ3_2nn3nim11ResultError", export_NeedsNotifyToUsersTitlePackage); osLib_addFunction("nn_nim", "ListTitlePackagesStatically__Q2_2nn3nimFPULUi", export_ListTitlePackagesStatically); osLib_addFunction("nn_nim", "GetECommerceInfrastructureCountry__Q2_2nn3nimFPQ3_2nn3nim7Country", export_GetECommerceInfrastructureCountry); osLib_addFunction("nn_nim", "QuerySchedulerStatus__Q2_2nn3nimFPQ3_2nn3nim15SchedulerStatus", export_QuerySchedulerStatus); osLib_addFunction("nn_nim", "GetIconDatabaseEntries__Q2_2nn3nimFPQ3_2nn3nim17IconDatabaseEntryPCULUi", export_GetIconDatabaseEntries); cafeExportRegisterFunc(ConstructResultError, "nn_nim", "Construct__Q3_2nn3nim11ResultErrorFQ2_2nn6Resulti", LogType::Placeholder); osLib_addFunction("nn_nim", "MakeTitlePackageTaskConfigAutoUsingBgInstallPolicy__Q3_2nn3nim4utilFULiQ3_2nn4Cafe9TitleType", export_MakeTitlePackageTaskConfigAutoUsingBgInstallPolicy); osLib_addFunction("nn_nim", "CalculateTitleInstallSize__Q2_2nn3nimFPLRCQ3_2nn3nim22TitlePackageTaskConfigPCUsUi", export_CalculateTitleInstallSize); }; }s_COSnnNimModule; COSModule* GetModule() { return &s_COSnnNimModule; } } } ================================================ FILE: src/Cafe/OS/libs/nn_nim/nn_nim.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace nn { namespace nim { COSModule* GetModule(); } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv.cpp ================================================ #include "nn_olv.h" #include "nn_olv_InitializeTypes.h" #include "nn_olv_UploadCommunityTypes.h" #include "nn_olv_DownloadCommunityTypes.h" #include "nn_olv_UploadFavoriteTypes.h" #include "nn_olv_PostTypes.h" #include "nn_olv_OfflineDB.h" #include "Cafe/OS/libs/proc_ui/proc_ui.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit_Misc.h" namespace nn { namespace olv { static SysAllocator<OSThread_t> s_OlvReleaseBgThread; SysAllocator<uint8, 1024> s_OlvReleaseBgThreadStack; SysAllocator<char, 32> s_OlvReleaseBgThreadName; void StubPostAppReleaseBackground(PPCInterpreter_t* hCPU) { coreinit::OSSleepTicks(ESPRESSO_TIMER_CLOCK * 2); // Sleep 2s coreinit::StartBackgroundForegroundTransition(); } sint32 StubPostApp(void* pAnyPostParam) { coreinit::OSCreateThreadType(s_OlvReleaseBgThread.GetPtr(), RPLLoader_MakePPCCallable(StubPostAppReleaseBackground), 0, nullptr, s_OlvReleaseBgThreadStack.GetPtr() + s_OlvReleaseBgThreadStack.GetByteSize(), (sint32)s_OlvReleaseBgThreadStack.GetByteSize(), 0, (1 << 1) | (1 << 3), OSThread_t::THREAD_TYPE::TYPE_APP); coreinit::OSResumeThread(s_OlvReleaseBgThread.GetPtr()); strcpy(s_OlvReleaseBgThreadName.GetPtr(), "StubPostApp!"); coreinit::OSSetThreadName(s_OlvReleaseBgThread.GetPtr(),s_OlvReleaseBgThreadName.GetPtr()); return OLV_RESULT_SUCCESS; } sint32 StubPostAppResult() { return OLV_RESULT_STATUS(301); // Cancelled post app } // Somehow required, MK8 doesn't even seem to care about the error codes lol char* UploadedPostData_GetPostId(char* pPostData) { pPostData[4] = '\0'; return &pPostData[4]; } // https://github.com/kinnay/NintendoClients/wiki/Wii-U-Error-Codes#act-error-codes constexpr uint32 GetErrorCodeImpl(uint32 in) { uint32_t errorCode = in; uint32_t errorVersion = (errorCode >> 27) & 3; uint32_t errorModuleMask = (errorVersion != 3) ? 0x1FF00000 : 0x7F00000; bool isCodeFailure = errorCode & 0x80000000; if (((errorCode & errorModuleMask) >> 20) == NN_RESULT_MODULE_NN_ACT) { // BANNED_ACCOUNT_IN_INDEPENDENT_SERVICE or BANNED_ACCOUNT_IN_INDEPENDENT_SERVICE_TEMPORARILY if (errorCode == OLV_ACT_RESULT_STATUS(2805) || errorCode == OLV_ACT_RESULT_STATUS(2825)) { uint32 tmpCode = OLV_RESULT_STATUS(1008); return GetErrorCodeImpl(tmpCode); } // BANNED_DEVICE_IN_INDEPENDENT_SERVICE or BANNED_DEVICE_IN_INDEPENDENT_SERVICE_TEMPORARILY else if (errorCode == OLV_ACT_RESULT_STATUS(2815) || errorCode == OLV_ACT_RESULT_STATUS(2835)) { uint32 tmpCode = OLV_RESULT_STATUS(1009); return GetErrorCodeImpl(tmpCode); } else { // Check ACT error code return 1159999; } } else { if (((errorCode & errorModuleMask) >> 20) == NN_RESULT_MODULE_NN_OLV && isCodeFailure) { uint32_t errorValueMask = (errorVersion != 3) ? 0xFFFFF : 0x3FF; return ((errorCode & errorValueMask) >> 7) + 1150000; } else { return 1159999; } } } uint32 GetErrorCode(uint32be* pResult) { return GetErrorCodeImpl(pResult->value()); } static_assert(GetErrorCodeImpl(0xa119c600) == 1155004); class : public COSModule { public: std::string_view GetName() override { return "nn_olv"; } void RPLMapped() override { loadOliveInitializeTypes(); loadOliveUploadCommunityTypes(); loadOliveDownloadCommunityTypes(); loadOliveUploadFavoriteTypes(); loadOlivePostAndTopicTypes(); cafeExportRegisterFunc(GetErrorCode, "nn_olv", "GetErrorCode__Q2_2nn3olvFRCQ2_2nn6Result", LogType::NN_OLV); cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::NN_OLV); cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadCommentDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv31UploadCommentDataByPostAppParam", LogType::NN_OLV); cafeExportRegisterFunc(StubPostApp, "nn_olv", "UploadDirectMessageDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv37UploadDirectMessageDataByPostAppParam", LogType::NN_OLV); cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultByPostApp__Q2_2nn3olvFv", LogType::NN_OLV); cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedPostDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv16UploadedPostData", LogType::NN_OLV); cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedDirectMessageDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv25UploadedDirectMessageData", LogType::NN_OLV); cafeExportRegisterFunc(StubPostAppResult, "nn_olv", "GetResultWithUploadedCommentDataByPostApp__Q2_2nn3olvFPQ3_2nn3olv19UploadedCommentData", LogType::NN_OLV); cafeExportRegisterFunc(UploadedPostData_GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv16UploadedPostDataCFv", LogType::NN_OLV); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { g_ReportTypes = 0; g_IsOnlineMode = false; g_IsInitialized = false; g_IsOfflineDBMode = false; } else if (reason == coreinit::RplEntryReason::Unloaded) { OfflineDB_Shutdown(); } } }s_COSnnOlvModule; COSModule* GetModule() { return &s_COSnnOlvModule; } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv.h ================================================ #pragma once #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/nn_common.h" #include "Cafe/OS/libs/nn_act/nn_act.h" #include "Cafe/CafeSystem.h" #include "Cemu/napi/napi.h" #include "Cafe/OS/RPL/COSModule.h" #include "nn_olv_Common.h" namespace nn::olv { extern ParamPackStorage g_ParamPack; extern DiscoveryResultStorage g_DiscoveryResults; sint32 GetOlvAccessKey(uint32* pOutKey); COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_Common.cpp ================================================ #include "nn_olv_Common.h" #include <zlib.h> namespace nn { namespace olv { sint32 olv_copy_wstr(char16_t* dest, const char16_t* src, uint32_t maxSize, uint32_t destSize) { size_t len = maxSize + 1; if (olv_wstrnlen(src, len) > maxSize) return OLV_RESULT_NOT_ENOUGH_SIZE; memset(dest, 0, 2 * destSize); olv_wstrncpy(dest, src, len); return OLV_RESULT_SUCCESS; } size_t olv_wstrnlen(const char16_t* str, size_t max_len) { size_t len = 0; while (len < max_len && str[len] != u'\0') len++; return len; } char16_t* olv_wstrncpy(char16_t* dest, const char16_t* src, size_t n) { char16_t* ret = dest; while (n > 0 && *src != u'\0') { *dest++ = *src++; n--; } while (n > 0) { *dest++ = u'\0'; n--; } return ret; } bool CheckTGA(const uint8* pTgaFile, uint32 pTgaFileLen, TGACheckType checkType) { const TGAHeader* header = (const TGAHeader*)pTgaFile; try { if (checkType == TGACheckType::CHECK_PAINTING) { if ( header->idLength || header->colorMapType || header->imageType != 2 || // Uncompressed true color header->first_entry_idx || header->colormap_length || header->bpp || header->x_origin || header->y_origin || header->width != 320 || header->height != 120 || header->pixel_depth_bpp != 32 || header->image_desc_bits != 8 ) { throw std::runtime_error("TGACheckType::CHECK_PAINTING - Invalid TGA file!"); } } else if (checkType == TGACheckType::CHECK_COMMUNITY_ICON) { if (header->width != 128 || header->height != 128 || header->pixel_depth_bpp != 32) throw std::runtime_error("TGACheckType::CHECK_COMMUNITY_ICON - Invalid TGA file -> width, height or bpp is wrong"); } else if (checkType == TGACheckType::CHECK_100x100_200x200) { if (header->pixel_depth_bpp != 32) throw std::runtime_error("TGACheckType::CHECK_100x100_200x200 - Invalid TGA file -> bpp is wrong"); if (header->width == 100) { if (header->height != 100) throw std::runtime_error("TGACheckType::CHECK_100x100_200x200 - Invalid TGA file -> Not 100x100"); } else if (header->width != 200 || header->height != 200) throw std::runtime_error("TGACheckType::CHECK_100x100_200x200 - Invalid TGA file -> Not 100x100 or 200x200"); } } catch (const std::runtime_error& error) { // TGA Check Error! illegal format cemuLog_log(LogType::Force, error.what()); return false; } return true; } sint32 DecodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkType) { uint32 decompressedSize = outSize; if (DecompressTGA(pOutBuffer, &decompressedSize, pInBuffer, inSize)) { if (CheckTGA(pOutBuffer, decompressedSize, checkType)) return decompressedSize; return -2; } else { cemuLog_log(LogType::Force, "OLIVE uncompress error.\n"); return -1; } } sint32 EncodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkType) { if (inSize == outSize) { if (!CheckTGA(pInBuffer, inSize, checkType)) return -1; uint32 compressedSize = outSize; if (CompressTGA(pOutBuffer, &compressedSize, pInBuffer, inSize)) return compressedSize; else { cemuLog_log(LogType::Force, "OLIVE compress error.\n"); return -1; } } else { cemuLog_log(LogType::Force, "compress buffer size check error. uSrcBufSize({}) != uDstBufSize({})\n", inSize, outSize); return -1; } } bool DecompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize) { if (pOutBuffer == nullptr || pOutSize == nullptr || pInBuffer == nullptr || inSize == 0) return false; uLongf bufferSize = *pOutSize; int result = uncompress(pOutBuffer, &bufferSize, pInBuffer, inSize); if (result == Z_OK) { *pOutSize = static_cast<unsigned int>(bufferSize); return true; } else { const char* error_msg = (result == Z_MEM_ERROR) ? "Insufficient memory" : "Unknown decompression error"; cemuLog_log(LogType::Force, "OLIVE ZLIB - ERROR: {}\n", error_msg); return false; } } bool CompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize) { if (pOutBuffer == nullptr || pOutSize == nullptr || pInBuffer == nullptr || inSize == 0) return false; uLongf bufferSize = *pOutSize; int result = compress(pOutBuffer, &bufferSize, pInBuffer, inSize); if (result == Z_OK) { *pOutSize = static_cast<unsigned int>(bufferSize); return true; } else { const char* error_msg = (result == Z_MEM_ERROR) ? "Insufficient memory" : "Unknown compression error"; cemuLog_log(LogType::Force, "OLIVE ZLIB - ERROR: {}\n", error_msg); return false; } } constexpr uint32 CreateCommunityCodeById(uint32 communityId) { uint32 res = communityId ^ (communityId << 18) ^ (communityId << 24) ^ (communityId << 30); return res ^ (16 * (res & 0xF0F0F0F)) ^ ((res ^ (16 * (res & 0xF0F0F0F))) >> 17) ^ ((res ^ (16 * (res & 0xF0F0F0F))) >> 23) ^ ((res ^ (16 * (res & 0xF0F0F0F))) >> 29) ^ 0x20121002; } constexpr uint32 CreateCommunityIdByCode(uint32 code) { uint32 res = code ^ 0x20121002 ^ ((code ^ 0x20121002u) >> 17) ^ ((code ^ 0x20121002u) >> 23) ^ ((code ^ 0x20121002u) >> 29); return res ^ (16 * (res & 0xF0F0F0F)) ^ ((res ^ (16 * (res & 0xF0F0F0F))) << 18) ^ ((res ^ (16 * (res & 0xF0F0F0F))) << 24) ^ ((res ^ (16 * (res & 0xF0F0F0F))) << 30); } constexpr uint32 GetCommunityCodeTopByte(uint32 communityId) { uint8 code_byte3 = (uint8_t)(communityId >> 0x18); uint8 code_byte2 = (uint8_t)(communityId >> 0x10); uint8 code_byte1 = (uint8_t)(communityId >> 8); uint8 code_byte0 = (uint8_t)(communityId >> 0); return code_byte3 ^ code_byte2 ^ code_byte1 ^ code_byte0 ^ 0xff; } constexpr uint64 GetRealCommunityCode(uint32_t communityId) { uint64 topByte = GetCommunityCodeTopByte(communityId); if ((0xe7 < topByte) && ((0xe8 < topByte || (0xd4a50fff < communityId)))) return ((topByte << 32) | communityId) & 0x7fffffffff; return ((topByte << 32) | communityId); } void WriteCommunityCode(char* pOutCode, uint32 communityId) { uint32 code = CreateCommunityCodeById(communityId); uint64 communityCode = GetRealCommunityCode(code); sprintf(pOutCode, "%012llu", communityCode); } bool EnsureCommunityCode(char* pCode) { uint64 code; if (sscanf(pCode, "%012llu", &code) > 0) { uint32 lowerCode = code; uint64 newCode = GetRealCommunityCode(code); return code == newCode; } return false; } bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId) { bool result = false; if (communityId != -1) { if (communityId) { WriteCommunityCode(pOutCode, communityId); *outLen = strnlen(pOutCode, 12); if (EnsureCommunityCode(pOutCode)) result = 1; } } return result; } static_assert(GetRealCommunityCode(CreateCommunityCodeById(140500)) == 717651734336, "Wrong community code generation code, result must match."); uint32 ExtractCommunityIdFromCode(char* pCode) { uint32 id = 0; uint64 code; if (sscanf(pCode, "%012llu", &code) > 0) { uint32 lower_code = code; id = CreateCommunityIdByCode(lower_code); } return id; } bool GetCommunityIdFromCode(uint32* pOutId, const char* pCode) { if (!EnsureCommunityCode((char*)pCode)) return false; *pOutId = ExtractCommunityIdFromCode((char*)pCode); return true; } sint32 olv_curlformcode_to_error(CURLFORMcode code) { switch (code) { case CURL_FORMADD_OK: return OLV_RESULT_SUCCESS; case CURL_FORMADD_MEMORY: return OLV_RESULT_FATAL(25); case CURL_FORMADD_OPTION_TWICE: default: return OLV_RESULT_LVL6(50); } } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_Common.h ================================================ #pragma once #include "Cafe/OS/libs/nn_common.h" #include "Cafe/OS/common/OSCommon.h" #include "Cemu/napi/napi_helper.h" #include "util/helpers/StringHelpers.h" #include "pugixml.hpp" // https://github.com/kinnay/NintendoClients/wiki/Wii-U-Error-Codes#act-error-codes #define OLV_ACT_RESULT_STATUS(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) #define OLV_RESULT_STATUS(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) #define OLV_RESULT_LVL6(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) #define OLV_RESULT_FATAL(code) (BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_OLV, ((code) << 7))) #define OLV_RESULT_SUCCESS (BUILD_NN_RESULT(0, NN_RESULT_MODULE_NN_OLV, 1 << 7)) #define OLV_RESULT_INVALID_PARAMETER (OLV_RESULT_LVL6(201)) #define OLV_RESULT_INVALID_DATA (OLV_RESULT_LVL6(202)) #define OLV_RESULT_NOT_ENOUGH_SIZE (OLV_RESULT_LVL6(203)) #define OLV_RESULT_INVALID_PTR (OLV_RESULT_LVL6(204)) #define OLV_RESULT_NOT_INITIALIZED (OLV_RESULT_LVL6(205)) #define OLV_RESULT_ALREADY_INITIALIZED (OLV_RESULT_LVL6(206)) #define OLV_RESULT_OFFLINE_MODE_REQUEST (OLV_RESULT_LVL6(207)) #define OLV_RESULT_MISSING_DATA (OLV_RESULT_LVL6(208)) #define OLV_RESULT_INVALID_SIZE (OLV_RESULT_LVL6(209)) #define OLV_RESULT_BAD_VERSION (OLV_RESULT_STATUS(2001)) #define OLV_RESULT_FAILED_REQUEST (OLV_RESULT_STATUS(2003)) #define OLV_RESULT_INVALID_XML (OLV_RESULT_STATUS(2004)) #define OLV_RESULT_INVALID_TEXT_FIELD (OLV_RESULT_STATUS(2006)) #define OLV_RESULT_INVALID_INTEGER_FIELD (OLV_RESULT_STATUS(2007)) #define OLV_CLIENT_ID "87cd32617f1985439ea608c2746e4610" #define OLV_VERSION_MAJOR 5 #define OLV_VERSION_MINOR 0 #define OLV_VERSION_PATCH 3 namespace nn { namespace olv { struct ParamPackStorage { uint64_t titleId; uint32_t accessKey; uint32_t platformId; uint8_t regionId, languageId, countryId, areaId; uint8_t networkRestriction, friendRestriction; uint32_t ratingRestriction; uint8_t ratingOrganization; uint64_t transferableId; char tzName[72]; uint64_t utcOffset; char encodedParamPack[512]; }; struct DiscoveryResultStorage { sint32 has_error; char serviceToken[512]; char userAgent[64]; char apiEndpoint[256]; char portalEndpoint[256]; }; extern ParamPackStorage g_ParamPack; extern DiscoveryResultStorage g_DiscoveryResults; extern uint32_t g_ReportTypes; extern bool g_IsInitialized; extern bool g_IsOnlineMode; extern bool g_IsOfflineDBMode; // use offline cache for posts static void InitializeOliveRequest(CurlRequestHelper& req) { req.addHeaderField("X-Nintendo-ServiceToken", g_DiscoveryResults.serviceToken); req.addHeaderField("X-Nintendo-ParamPack", g_ParamPack.encodedParamPack); curl_easy_setopt(req.getCURL(), CURLOPT_USERAGENT, g_DiscoveryResults.userAgent); } static void appendQueryToURL(char* url, const char* query) { size_t urlLength = strlen(url); size_t queryLength = strlen(query); char* delimiter = strchr(url, '?'); if (delimiter) snprintf(url + urlLength, queryLength + 2, "&%s", query); else snprintf(url + urlLength, queryLength + 2, "?%s", query); } static sint32 CheckOliveResponse(pugi::xml_document& doc) { /* <result> <has_error>1</has_error> <version>1</version> <code>400</code> <error_code>4</error_code> <message>SERVICE_CLOSED</message> </result> */ pugi::xml_node resultNode = doc.child("result"); if (!resultNode) { cemuLog_log(LogType::Force, "Discovery response doesn't contain <result>...</result>"); return OLV_RESULT_INVALID_XML; } std::string_view has_error = resultNode.child_value("has_error"); std::string_view version = resultNode.child_value("version"); std::string_view code = resultNode.child_value("code"); std::string_view error_code = resultNode.child_value("error_code"); if (has_error.compare("1") == 0) { int codeVal = StringHelpers::ToInt(error_code, -1); if (codeVal < 0) { codeVal = StringHelpers::ToInt(code, -1); return OLV_RESULT_STATUS(codeVal + 4000); } return OLV_RESULT_STATUS(codeVal + 5000); } if (version.compare("1") != 0) return OLV_RESULT_BAD_VERSION; // Version mismatch return OLV_RESULT_SUCCESS; } sint32 olv_copy_wstr(char16_t* dest, const char16_t* src, uint32_t maxSize, uint32_t destSize); size_t olv_wstrnlen(const char16_t* str, size_t max_len); char16_t* olv_wstrncpy(char16_t* dest, const char16_t* src, size_t n); #pragma pack(push, 1) struct TGAHeader { uint8 idLength; uint8 colorMapType; uint8 imageType; uint16 first_entry_idx; uint16 colormap_length; uint8 bpp; uint16 x_origin; uint16 y_origin; uint16 width; uint16 height; uint8 pixel_depth_bpp; uint8 image_desc_bits; }; #pragma pack(pop) static_assert(sizeof(nn::olv::TGAHeader) == 0x12, "sizeof(nn::olv::TGAHeader != 0x12"); enum TGACheckType : uint32 { CHECK_PAINTING = 0, CHECK_COMMUNITY_ICON = 1, CHECK_100x100_200x200 = 2 }; bool CheckTGA(const uint8* pTgaFile, uint32 pTgaFileLen, TGACheckType checkType); sint32 DecodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkType); sint32 EncodeTGA(uint8* pInBuffer, uint32 inSize, uint8* pOutBuffer, uint32 outSize, TGACheckType checkTyp); bool CompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize); bool DecompressTGA(uint8* pOutBuffer, uint32* pOutSize, uint8* pInBuffer, uint32 inSize); bool GetCommunityIdFromCode(uint32* pOutId, const char* pCode); bool FormatCommunityCode(char* pOutCode, uint32* outLen, uint32 communityId); sint32 olv_curlformcode_to_error(CURLFORMcode code); // convert and copy utf8 string into UC2 big-endian array template<size_t TLength> uint32 SetStringUC2(uint16be(&str)[TLength], std::string_view sv, bool unescape = false) { if(unescape) { // todo } std::wstring ws = boost::nowide::widen(sv); size_t copyLen = std::min<size_t>(TLength-1, ws.size()); for(size_t i=0; i<copyLen; i++) str[i] = ws[i]; str[copyLen] = '\0'; return copyLen; } // safely copy null-terminated UC2 big-endian string into UC2 big-endian array template<size_t TLength> uint32 SetStringUC2(uint16be(&str)[TLength], const uint16be* strIn) { size_t copyLen = TLength-1; for(size_t i=0; i<copyLen; i++) { if(strIn[i] == 0) { str[i] = 0; return i; } str[i] = strIn[i]; } str[copyLen] = '\0'; return copyLen; } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp ================================================ #include "nn_olv_DownloadCommunityTypes.h" namespace nn { namespace olv { sint32 DownloadCommunityDataList_AsyncRequestImpl( CurlRequestHelper& req, const char* reqUrl, DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam); sint32 DownloadCommunityDataList_AsyncRequest( CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent, DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam ) { sint32 res = DownloadCommunityDataList_AsyncRequestImpl(req, reqUrl, pOutList, pOutNum, numMaxList, pParam); coreinit::OSSignalEvent(requestDoneEvent); return res; } sint32 DownloadCommunityDataList(DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam) { if (!g_IsInitialized) return OLV_RESULT_NOT_INITIALIZED; if (!g_IsOnlineMode) return OLV_RESULT_OFFLINE_MODE_REQUEST; if (!pOutList || !pOutNum || !pParam) return OLV_RESULT_INVALID_PTR; if (!numMaxList) return OLV_RESULT_NOT_ENOUGH_SIZE; for (int i = 0; i < numMaxList; i++) DownloadedCommunityData::Clean(&pOutList[i]); char reqUrl[2048]; sint32 res = pParam->GetRawDataUrl(reqUrl, sizeof(reqUrl)); if (res < 0) return res; CurlRequestHelper req; req.initate(ActiveSettings::GetNetworkService(), reqUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; coreinit::OSInitEvent(&requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); std::future<sint32> requestRes = std::async(std::launch::async, DownloadCommunityDataList_AsyncRequest, std::ref(req), reqUrl, requestDoneEvent.GetPointer(), pOutList, pOutNum, numMaxList, pParam); coreinit::OSWaitEvent(&requestDoneEvent); return requestRes.get(); } sint32 DownloadCommunityDataList_AsyncRequestImpl( CurlRequestHelper& req, const char* reqUrl, DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam ) { bool reqResult = req.submitRequest(); long httpCode = 0; curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); if (!reqResult) { cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, httpCode); if (!(httpCode >= 400)) return OLV_RESULT_FAILED_REQUEST; } pugi::xml_document doc; if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) { cemuLog_log(LogType::Force, fmt::format("Invalid XML in community download response")); return OLV_RESULT_INVALID_XML; } sint32 responseError = CheckOliveResponse(doc); if (responseError < 0) return responseError; if (httpCode != 200) return OLV_RESULT_STATUS(httpCode + 4000); std::string request_name = doc.select_node("//request_name").node().child_value(); if (request_name.size() == 0) { cemuLog_log(LogType::Force, "Community download response doesn't contain <request_name>"); return OLV_RESULT_INVALID_XML; } if ((request_name.compare("communities") != 0) && (request_name.compare("specified_communities") != 0)) { cemuLog_log(LogType::Force, "Community download response <request_name> isn't \"communities\" or \"specified_communities\""); return OLV_RESULT_INVALID_XML; } pugi::xml_node communities = doc.select_node("//communities").node(); if (!communities) { cemuLog_log(LogType::Force, "Community download response doesn't contain <communities>"); return OLV_RESULT_INVALID_XML; } int idx = 0; for (pugi::xml_node communityNode : communities.children("community")) { if (idx >= numMaxList) break; DownloadedCommunityData* pOutData = &pOutList[idx]; std::string_view app_data = communityNode.child_value("app_data"); std::string_view community_id = communityNode.child_value("community_id"); std::string_view name = communityNode.child_value("name"); std::string_view description = communityNode.child_value("description"); std::string_view pid = communityNode.child_value("pid"); std::string_view icon = communityNode.child_value("icon"); std::string_view mii = communityNode.child_value("mii"); std::string_view screen_name = communityNode.child_value("screen_name"); if (app_data.size() != 0) { auto app_data_bin = NCrypto::base64Decode(app_data); if (app_data_bin.size() != 0) { memcpy(pOutData->appData, app_data_bin.data(), std::min(size_t(0x400), app_data_bin.size())); pOutData->flags |= DownloadedCommunityData::FLAG_HAS_APP_DATA; pOutData->appDataLen = app_data_bin.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } sint64 community_id_val = StringHelpers::ToInt64(community_id, -1); if (community_id_val == -1) return OLV_RESULT_INVALID_INTEGER_FIELD; pOutData->communityId = community_id_val; if (name.size() != 0) { auto name_utf16 = StringHelpers::FromUtf8(name); name_utf16.resize(std::min<size_t>(name_utf16.size(), 128)); if (name_utf16.size() != 0) { for (int i = 0; i < name_utf16.size(); i++) pOutData->titleText[i] = name_utf16.at(i).bevalue(); pOutData->flags |= DownloadedCommunityData::FLAG_HAS_TITLE_TEXT; pOutData->titleTextMaxLen = name_utf16.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } if (description.size() != 0) { auto description_utf16 = StringHelpers::FromUtf8(description); description_utf16.resize(std::min<size_t>(description_utf16.size(), 256)); if (description_utf16.size() != 0) { for (int i = 0; i < description_utf16.size(); i++) pOutData->description[i] = description_utf16.at(i).bevalue(); pOutData->flags |= DownloadedCommunityData::FLAG_HAS_DESC_TEXT; pOutData->descriptionMaxLen = description_utf16.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } sint64 pid_val = StringHelpers::ToInt64(pid, -1); if (pid_val == -1) return OLV_RESULT_INVALID_INTEGER_FIELD; pOutData->pid = pid_val; if (icon.size() != 0) { auto icon_bin = NCrypto::base64Decode(icon); if (icon_bin.size() != 0) { memcpy(pOutData->iconData, icon_bin.data(), std::min(size_t(0x1002c), icon_bin.size())); pOutData->flags |= DownloadedCommunityData::FLAG_HAS_ICON_DATA; pOutData->iconDataSize = icon_bin.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } if (mii.size() != 0) { auto mii_bin = NCrypto::base64Decode(mii); if (mii_bin.size() != 0) { memcpy(pOutData->miiFFLStoreData, mii_bin.data(), std::min(size_t(96), mii_bin.size())); pOutData->flags |= DownloadedCommunityData::FLAG_HAS_MII_DATA; } else return OLV_RESULT_INVALID_TEXT_FIELD; } if (screen_name.size() != 0) { auto screen_name_utf16 = StringHelpers::FromUtf8(screen_name); screen_name_utf16.resize(std::min<size_t>(screen_name_utf16.size(), 32)); if (screen_name_utf16.size() != 0) { for (int i = 0; i < screen_name_utf16.size(); i++) pOutData->miiDisplayName[i] = screen_name_utf16.at(i).bevalue(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } idx++; } *pOutNum = _swapEndianU32(idx); sint32 res = OLV_RESULT_SUCCESS; if (idx > 0) res = 0; // nn_olv doesn't do it like that, but it's the same effect. I have no clue why it returns 0 when you have 1+ communities downloaded return res; } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.h ================================================ #pragma once #include "Cemu/ncrypto/ncrypto.h" #include "config/ActiveSettings.h" #include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" namespace nn { namespace olv { class DownloadedCommunityData { public: static const inline uint32 FLAG_HAS_TITLE_TEXT = (1 << 0); static const inline uint32 FLAG_HAS_DESC_TEXT = (1 << 1); static const inline uint32 FLAG_HAS_APP_DATA = (1 << 2); static const inline uint32 FLAG_HAS_ICON_DATA = (1 << 3); static const inline uint32 FLAG_HAS_MII_DATA = (1 << 4); DownloadedCommunityData() { this->titleTextMaxLen = 0; this->appDataLen = 0; this->descriptionMaxLen = 0; this->pid = 0; this->communityId = 0; this->flags = 0; this->iconDataSize = 0; this->miiDisplayName[0] = 0; } static DownloadedCommunityData* __ctor(DownloadedCommunityData* _this) { if (!_this) { assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN return nullptr; } else return new (_this) DownloadedCommunityData(); } static DownloadedCommunityData* Clean(DownloadedCommunityData* data) { data->flags = 0; data->communityId = 0; data->pid = 0; data->iconData[0] = 0; data->titleTextMaxLen = 0; data->appData[0] = 0; data->appDataLen = 0; data->description[0] = 0; data->descriptionMaxLen = 0; data->iconDataSize = 0; data->titleText[0] = 0; data->miiDisplayName[0] = 0; return data; } bool TestFlags(uint32 flags) const { return (this->flags & flags) != 0; } static bool __TestFlags(DownloadedCommunityData* _this, uint32 flags) { return _this->TestFlags(flags); } uint32 GetCommunityId() const { return this->communityId; } static uint32 __GetCommunityId(DownloadedCommunityData* _this) { return _this->GetCommunityId(); } sint32 GetCommunityCode(char* pBuffer, uint32 bufferSize) const { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (bufferSize <= 12) return OLV_RESULT_NOT_ENOUGH_SIZE; uint32 len = 0; if (FormatCommunityCode(pBuffer, &len, this->communityId)) return OLV_RESULT_SUCCESS; return OLV_RESULT_INVALID_PARAMETER; } static sint32 __GetCommunityCode(DownloadedCommunityData* _this, char* pBuffer, uint32 bufferSize) { return _this->GetCommunityCode(pBuffer, bufferSize); } uint32 GetOwnerPid() const { return this->pid; } static uint32 __GetOwnerPid(DownloadedCommunityData* _this) { return _this->GetOwnerPid(); } sint32 GetTitleText(char16_t* pBuffer, uint32 numChars) { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (numChars) { if (!this->TestFlags(FLAG_HAS_TITLE_TEXT)) return OLV_RESULT_MISSING_DATA; memset(pBuffer, 0, 2 * numChars); uint32 readSize = this->titleTextMaxLen; if (numChars < readSize) readSize = numChars; olv_wstrncpy(pBuffer, this->titleText, readSize); return OLV_RESULT_SUCCESS; } return OLV_RESULT_NOT_ENOUGH_SIZE; } static sint32 __GetTitleText(DownloadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) { return _this->GetTitleText(pBuffer, numChars); } sint32 GetDescriptionText(char16_t* pBuffer, uint32 numChars) { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (numChars) { if (!this->TestFlags(FLAG_HAS_DESC_TEXT)) return OLV_RESULT_MISSING_DATA; memset(pBuffer, 0, 2 * numChars); olv_wstrncpy(pBuffer, this->description, numChars); return OLV_RESULT_SUCCESS; } return OLV_RESULT_NOT_ENOUGH_SIZE; } static sint32 __GetDescriptionText(DownloadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) { return _this->GetDescriptionText(pBuffer, numChars); } sint32 GetAppData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { uint32 appDataSize = bufferSize; if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (bufferSize) { if (!this->TestFlags(FLAG_HAS_APP_DATA)) return OLV_RESULT_MISSING_DATA; if (this->appDataLen < appDataSize) appDataSize = this->appDataLen; memcpy(pBuffer, this->appData, appDataSize); if (pOutSize) *pOutSize = appDataSize; return OLV_RESULT_SUCCESS; } return OLV_RESULT_NOT_ENOUGH_SIZE; } static sint32 __GetAppData(DownloadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { return _this->GetAppData(pBuffer, pOutSize, bufferSize); } uint32 GetAppDataSize() const { if (this->TestFlags(FLAG_HAS_APP_DATA)) return this->appDataLen; return 0; } static uint32 __GetAppDataSize(DownloadedCommunityData* _this) { return _this->GetAppDataSize(); } sint32 GetIconData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (bufferSize < sizeof(this->iconData)) return OLV_RESULT_NOT_ENOUGH_SIZE; if (!this->TestFlags(FLAG_HAS_ICON_DATA)) return OLV_RESULT_MISSING_DATA; sint32 decodeRes = DecodeTGA(this->iconData, this->iconDataSize, pBuffer, bufferSize, TGACheckType::CHECK_COMMUNITY_ICON); if (decodeRes >= 0) { if (pOutSize) *pOutSize = (uint32)decodeRes; return OLV_RESULT_SUCCESS; } if (pOutSize) *pOutSize = 0; if (decodeRes == -1) cemuLog_log(LogType::Force, "OLIVE - icon uncompress failed.\n"); else if (decodeRes == -2) cemuLog_log(LogType::Force, "OLIVE - icon decode error. NOT TGA.\n"); return OLV_RESULT_INVALID_TEXT_FIELD; } static sint32 __GetIconData(DownloadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { return _this->GetIconData(pBuffer, pOutSize, bufferSize); } sint32 GetOwnerMiiData(/* FFLStoreData* */void* pBuffer) const { if (!this->TestFlags(FLAG_HAS_MII_DATA)) return OLV_RESULT_MISSING_DATA; if (!pBuffer) return OLV_RESULT_INVALID_PTR; memcpy(pBuffer, this->miiFFLStoreData, sizeof(this->miiFFLStoreData)); return OLV_RESULT_SUCCESS; } static sint32 __GetOwnerMiiData(DownloadedCommunityData* _this, /* FFLStoreData* */void* pBuffer) { return _this->GetOwnerMiiData(pBuffer); } const char16_t* GetOwnerMiiNickname() const { if (this->miiDisplayName[0]) return this->miiDisplayName; return nullptr; } static const char16_t* __GetOwnerMiiNickname(DownloadedCommunityData* _this) { return _this->GetOwnerMiiNickname(); } public: uint32be flags; uint32be communityId; uint32be pid; char16_t titleText[128]; uint32be titleTextMaxLen; char16_t description[256]; uint32be descriptionMaxLen; uint8 appData[1024]; uint32be appDataLen; uint8 iconData[65580]; uint32be iconDataSize; uint8 miiFFLStoreData[96]; char16_t miiDisplayName[32]; uint8 unk[6168]; }; static_assert(sizeof(nn::olv::DownloadedCommunityData) == 0x12000, "sizeof(nn::olv::DownloadedCommunityData) != 0x12000"); class DownloadCommunityDataListParam { public: static const inline uint32 FLAG_FILTER_FAVORITES = (1 << 0); static const inline uint32 FLAG_FILTER_OFFICIALS = (1 << 1); static const inline uint32 FLAG_FILTER_OWNED = (1 << 2); static const inline uint32 FLAG_QUERY_MII_DATA = (1 << 3); static const inline uint32 FLAG_QUERY_ICON_DATA = (1 << 4); DownloadCommunityDataListParam() { this->flags = 0; this->communityDownloadLimit = 0; this->communityId = 0; for (int i = 0; i < 20; i++) this->additionalCommunityIdList[i] = -2; } static DownloadCommunityDataListParam* __ctor(DownloadCommunityDataListParam* _this) { if (!_this) { assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN return nullptr; } else return new (_this) DownloadCommunityDataListParam(); } sint32 SetFlags(uint32 flags) { this->flags = flags; return OLV_RESULT_SUCCESS; } static sint32 __SetFlags(DownloadCommunityDataListParam* _this, uint32 flags) { return _this->SetFlags(flags); } sint32 SetCommunityId(uint32 communityId) { if (communityId == -1) return OLV_RESULT_INVALID_PARAMETER; this->communityId = communityId; if (communityId) { if (!this->communityDownloadLimit) this->communityDownloadLimit = 1; } return OLV_RESULT_SUCCESS; } static sint32 __SetCommunityId(DownloadCommunityDataListParam* _this, uint32 communityId) { return _this->SetCommunityId(communityId); } sint32 SetCommunityId(uint32 communityId, uint8 idx) { if (communityId == -1) return OLV_RESULT_INVALID_PARAMETER; if (idx >= 20) return OLV_RESULT_INVALID_PARAMETER; this->additionalCommunityIdList[idx] = communityId; int validIdsCount = 0; for (int i = 0; i < 20; i++ ) { if (this->additionalCommunityIdList[i] != -2) ++validIdsCount; } if (validIdsCount > this->communityDownloadLimit) this->communityDownloadLimit = validIdsCount; return OLV_RESULT_SUCCESS; } static sint32 __SetCommunityId(DownloadCommunityDataListParam* _this, uint32 communityId, uint8 idx) { return _this->SetCommunityId(communityId, idx); } sint32 SetCommunityDataMaxNum(uint32 num) { if (!num) return OLV_RESULT_INVALID_PARAMETER; int validIdsCount = 0; for (int i = 0; i < 20; ++i) { if (this->additionalCommunityIdList[i] != -2) ++validIdsCount; } if (validIdsCount > num) return OLV_RESULT_INVALID_PARAMETER; this->communityDownloadLimit = num; return OLV_RESULT_SUCCESS; } static sint32 __SetCommunityDataMaxNum(DownloadCommunityDataListParam* _this, uint32 num) { return _this->SetCommunityDataMaxNum(num); } sint32 GetRawDataUrl(char* pBuffer, uint32 bufferSize) const { if (!g_IsOnlineMode) return OLV_RESULT_OFFLINE_MODE_REQUEST; if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (!bufferSize) return OLV_RESULT_NOT_ENOUGH_SIZE; char tmpFormatBuffer[64]; char urlBuffer[1024]; memset(urlBuffer, 0, sizeof(urlBuffer)); uint32 communityId; int validIdsCount = 0; for (int i = 0; i < 20; ++i) { if (this->additionalCommunityIdList[i] != -2) { communityId = this->additionalCommunityIdList[i]; ++validIdsCount; } } if (validIdsCount) { if (this->communityId && this->communityId != -2) return OLV_RESULT_INVALID_PARAMETER; uint32 unkFlag = this->flags & 0xFFFFFFE7; if (unkFlag) return OLV_RESULT_INVALID_PARAMETER; // It's how it's done in the real nn_olv, what even the fuck is this, never seen used yet. snprintf(urlBuffer, sizeof(urlBuffer), "%s/v1/communities/%u.search", g_DiscoveryResults.apiEndpoint, communityId); for (int i = 0; i < 20; ++i) { if (this->additionalCommunityIdList[i] != -2) { snprintf(tmpFormatBuffer, 64, "community_id=%u", this->additionalCommunityIdList[i].value()); appendQueryToURL(urlBuffer, tmpFormatBuffer); ++unkFlag; } } } else snprintf(urlBuffer, sizeof(urlBuffer), "%s/v1/communities", g_DiscoveryResults.apiEndpoint); if (this->communityId) { snprintf(tmpFormatBuffer, 64, "community_id=%u", this->communityId.value()); appendQueryToURL(urlBuffer, tmpFormatBuffer); } else { uint32 filterBy_favorite = (this->flags & FLAG_FILTER_FAVORITES) != 0; uint32 filterBy_official = (this->flags & FLAG_FILTER_OFFICIALS) != 0; uint32 filterBy_selfmade = (this->flags & FLAG_FILTER_OWNED) != 0; if ((filterBy_favorite + filterBy_official + filterBy_selfmade) != 1) return OLV_RESULT_INVALID_PARAMETER; snprintf(tmpFormatBuffer, 64, "limit=%u", this->communityDownloadLimit.value()); appendQueryToURL(urlBuffer, tmpFormatBuffer); if (filterBy_favorite) { strncpy(tmpFormatBuffer, "type=favorite", 64); appendQueryToURL(urlBuffer, tmpFormatBuffer); } else if (filterBy_official) { strncpy(tmpFormatBuffer, "type=official", 64); appendQueryToURL(urlBuffer, tmpFormatBuffer); } else { strncpy(tmpFormatBuffer, "type=my", 64); appendQueryToURL(urlBuffer, tmpFormatBuffer); } } if (this->flags & FLAG_QUERY_MII_DATA) { strncpy(tmpFormatBuffer, "with_mii=1", 64); appendQueryToURL(urlBuffer, tmpFormatBuffer); } if (this->flags & FLAG_QUERY_ICON_DATA) { strncpy(tmpFormatBuffer, "with_icon=1", 64); appendQueryToURL(urlBuffer, tmpFormatBuffer); } int res = snprintf(pBuffer, bufferSize, "%s", urlBuffer); if (res < 0) return OLV_RESULT_NOT_ENOUGH_SIZE; return OLV_RESULT_SUCCESS; } static sint32 __GetRawDataUrl(DownloadCommunityDataListParam* _this, char* pBuffer, uint32 bufferSize) { return _this->GetRawDataUrl(pBuffer, bufferSize); } public: uint32be flags; uint32be communityId; uint32be communityDownloadLimit; uint32be additionalCommunityIdList[20]; // Additional community ID filter list uint8 unk[4004]; // Looks unused lol, probably reserved data }; static_assert(sizeof(nn::olv::DownloadCommunityDataListParam) == 0x1000, "sizeof(nn::olv::DownloadCommunityDataListParam) != 0x1000"); sint32 DownloadCommunityDataList(DownloadedCommunityData* pOutList, uint32* pOutNum, uint32 numMaxList, const DownloadCommunityDataListParam* pParam); static void loadOliveDownloadCommunityTypes() { cafeExportRegisterFunc(DownloadedCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv23DownloadedCommunityDataFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv23DownloadedCommunityDataCFUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv23DownloadedCommunityDataCFPcUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv23DownloadedCommunityDataCFPwUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv23DownloadedCommunityDataCFPUcPUiUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerMiiData, "nn_olv", "GetOwnerMiiData__Q3_2nn3olv23DownloadedCommunityDataCFP12FFLStoreData", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedCommunityData::__GetOwnerMiiNickname, "nn_olv", "GetOwnerMiiNickname__Q3_2nn3olv23DownloadedCommunityDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadCommunityDataListParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv30DownloadCommunityDataListParamFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadCommunityDataListParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadCommunityDataListParam::__SetCommunityDataMaxNum, "nn_olv", "SetCommunityDataMaxNum__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadCommunityDataListParam::__GetRawDataUrl, "nn_olv", "GetRawDataUrl__Q3_2nn3olv30DownloadCommunityDataListParamCFPcUi", LogType::NN_OLV); cafeExportRegisterFunc((sint32 (*)(DownloadCommunityDataListParam*, uint32))DownloadCommunityDataListParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv30DownloadCommunityDataListParamFUi", LogType::NN_OLV); cafeExportRegisterFunc((sint32(*)(DownloadCommunityDataListParam*, uint32, uint8))DownloadCommunityDataListParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv30DownloadCommunityDataListParamFUiUc", LogType::NN_OLV); cafeExportRegisterFunc(DownloadCommunityDataList, "nn_olv", "DownloadCommunityDataList__Q2_2nn3olvFPQ3_2nn3olv23DownloadedCommunityDataPUiUiPCQ3_2nn3olv30DownloadCommunityDataListParam", LogType::NN_OLV); } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp ================================================ #include "nn_olv_InitializeTypes.h" #include "CafeSystem.h" #include "Cafe/OS/libs/nn_act/nn_act.h" #include <time.h> namespace nn { namespace olv { uint32_t g_ReportTypes = 0; bool g_IsOnlineMode = false; bool g_IsInitialized = false; bool g_IsOfflineDBMode = false; ParamPackStorage g_ParamPack; DiscoveryResultStorage g_DiscoveryResults; sint32 GetOlvAccessKey(uint32* pOutKey) { *pOutKey = 0; uint32 accessKey = CafeSystem::GetForegroundTitleOlvAccesskey(); if (accessKey == -1) return OLV_RESULT_STATUS(1102); *pOutKey = accessKey; return OLV_RESULT_SUCCESS; } sint32 CreateParamPack(uint64_t titleId, uint32_t accessKey) { g_ParamPack.languageId = uint8(GetConfig().console_language.GetValue()); uint32be simpleAddress = 0; nn::act::GetSimpleAddressIdEx(&simpleAddress, nn::act::ACT_SLOT_CURRENT); uint32 countryCode = nn::act::getCountryCodeFromSimpleAddress(simpleAddress); g_ParamPack.countryId = countryCode; g_ParamPack.titleId = titleId; g_ParamPack.platformId = 1; g_ParamPack.areaId = (simpleAddress >> 8) & 0xff; MCPHANDLE handle = MCP_Open(); SysProdSettings sysProdSettings; MCP_GetSysProdSettings(handle, &sysProdSettings); MCP_Close(handle); g_ParamPack.regionId = sysProdSettings.platformRegion; g_ParamPack.accessKey = accessKey; g_ParamPack.networkRestriction = 0; g_ParamPack.friendRestriction = 0; g_ParamPack.ratingRestriction = 18; g_ParamPack.ratingOrganization = 4; // PEGI ? uint64 transferrableId; nn::act::GetTransferableIdEx(&transferrableId, (titleId >> 8) & 0xFFFFF, nn::act::ACT_SLOT_CURRENT); g_ParamPack.transferableId = transferrableId; strcpy(g_ParamPack.tzName, "CEMU/Olive"); // Should be nn::act::GetTimeZoneId g_ParamPack.utcOffset = (uint64_t)nn::act::GetUtcOffset() / 1'000'000; char paramPackStr[1024]; snprintf( paramPackStr, sizeof(paramPackStr), "\\%s\\%llu\\%s\\%u\\%s\\%u\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%d\\%s\\%u\\%s\\%d\\%s\\%llu\\" "%s\\%s\\%s\\%lld\\", "title_id", g_ParamPack.titleId, "access_key", g_ParamPack.accessKey, "platform_id", g_ParamPack.platformId, "region_id", g_ParamPack.regionId, "language_id", g_ParamPack.languageId, "country_id", g_ParamPack.countryId, "area_id", g_ParamPack.areaId, "network_restriction", g_ParamPack.networkRestriction, "friend_restriction", g_ParamPack.friendRestriction, "rating_restriction", g_ParamPack.ratingRestriction, "rating_organization", g_ParamPack.ratingOrganization, "transferable_id", g_ParamPack.transferableId, "tz_name", g_ParamPack.tzName, "utc_offset", g_ParamPack.utcOffset); std::string encodedParamPack = NCrypto::base64Encode(paramPackStr, strnlen(paramPackStr, 1024)); memset(&g_ParamPack.encodedParamPack, 0, sizeof(g_ParamPack.encodedParamPack)); memcpy(&g_ParamPack.encodedParamPack, encodedParamPack.data(), encodedParamPack.size()); return OLV_RESULT_SUCCESS; } sint32 MakeDiscoveryRequest_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl) { bool reqResult = req.submitRequest(); long httpCode = 0; curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); if (!reqResult) { cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, httpCode); if (!(httpCode >= 400)) return OLV_RESULT_FAILED_REQUEST; } pugi::xml_document doc; if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) { cemuLog_log(LogType::Force, fmt::format("Invalid XML in discovery service response")); return OLV_RESULT_INVALID_XML; } sint32 responseError = CheckOliveResponse(doc); if (responseError < 0) return responseError; if (httpCode != 200) return OLV_RESULT_STATUS(httpCode + 4000); /* <result> <has_error>0</has_error> <version>1</version> <endpoint> <host>api.olv.pretendo.cc</host> <api_host>api.olv.pretendo.cc</api_host> <portal_host>portal.olv.pretendo.cc</portal_host> <n3ds_host>ctr.olv.pretendo.cc</n3ds_host> </endpoint> </result> */ pugi::xml_node resultNode = doc.child("result"); if (!resultNode) { cemuLog_log(LogType::Force, "Discovery response doesn't contain <result>"); return OLV_RESULT_INVALID_XML; } pugi::xml_node endpointNode = resultNode.child("endpoint"); if (!endpointNode) { cemuLog_log(LogType::Force, "Discovery response doesn't contain <result><endpoint>"); return OLV_RESULT_INVALID_XML; } // Yes it only uses <host> and <portal_host> std::string_view host = endpointNode.child_value("host"); std::string_view portal_host = endpointNode.child_value("portal_host"); snprintf(g_DiscoveryResults.apiEndpoint, sizeof(g_DiscoveryResults.apiEndpoint), "https://%s", host.data()); snprintf(g_DiscoveryResults.portalEndpoint, sizeof(g_DiscoveryResults.portalEndpoint), "https://%s", portal_host.data()); return OLV_RESULT_SUCCESS; } sint32 MakeDiscoveryRequest_AsyncRequest(CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent) { sint32 res = MakeDiscoveryRequest_AsyncRequestImpl(req, reqUrl); coreinit::OSSignalEvent(requestDoneEvent); return res; } sint32 MakeDiscoveryRequest() { // ============================================================================= // Discovery request | https://discovery.olv.nintendo.net/v1/endpoint // ============================================================================= CurlRequestHelper req; std::string requestUrl; switch (ActiveSettings::GetNetworkService()) { case NetworkService::Pretendo: requestUrl = PretendoURLs::OLVURL; break; case NetworkService::Custom: requestUrl = GetNetworkConfig().urls.OLV.GetValue(); break; case NetworkService::Nintendo: default: requestUrl = NintendoURLs::OLVURL; break; } req.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; coreinit::OSInitEvent(&requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); std::future<sint32> requestRes = std::async(std::launch::async, MakeDiscoveryRequest_AsyncRequest, std::ref(req), requestUrl.c_str(), requestDoneEvent.GetPointer()); coreinit::OSWaitEvent(&requestDoneEvent); return requestRes.get(); } sint32 Initialize(nn::olv::InitializeParam* pParam) { if (g_IsInitialized) return OLV_RESULT_ALREADY_INITIALIZED; if (!pParam->m_Work) { g_IsInitialized = false; return OLV_RESULT_INVALID_PTR; } if (pParam->m_WorkSize < 0x10000) { g_IsInitialized = false; return OLV_RESULT_INVALID_SIZE; } uint32_t accessKey; int32_t olvAccessKeyStatus = GetOlvAccessKey(&accessKey); if (olvAccessKeyStatus < 0) { g_IsInitialized = false; return olvAccessKeyStatus; } uint64_t tid = CafeSystem::GetForegroundTitleId(); int32_t createParamPackResult = CreateParamPack(tid, accessKey); if (createParamPackResult < 0) { g_IsInitialized = false; return createParamPackResult; } g_IsInitialized = true; if(ActiveSettings::GetNetworkService() == NetworkService::Nintendo) { // since the official Miiverse was shut down, use local post archive instead g_IsOnlineMode = true; g_IsOfflineDBMode = true; return OLV_RESULT_SUCCESS; } if ((pParam->m_Flags & InitializeParam::FLAG_OFFLINE_MODE) == 0) { g_IsOnlineMode = true; independentServiceToken_t token; sint32 res = (sint32)nn::act::AcquireIndependentServiceToken(&token, OLV_CLIENT_ID, 0); if (res < 0) { g_IsInitialized = false; return res; } // Assuming we're always a production WiiU (non-dev) uint32 uniqueId = (CafeSystem::GetForegroundTitleId() >> 8) & 0xFFFFF; char versionBuffer[32]; snprintf(versionBuffer, sizeof(versionBuffer), "%d.%d.%d", OLV_VERSION_MAJOR, OLV_VERSION_MINOR, OLV_VERSION_PATCH); snprintf(g_DiscoveryResults.userAgent, sizeof(g_DiscoveryResults.userAgent), "%s/%s-%s/%d", "WiiU", "POLV", versionBuffer, uniqueId); memcpy(g_DiscoveryResults.serviceToken, token.token, sizeof(g_DiscoveryResults.serviceToken)); sint32 discoveryRes = MakeDiscoveryRequest(); if (discoveryRes < 0) g_IsInitialized = false; return discoveryRes; } return OLV_RESULT_SUCCESS; } sint32 InitializePortalApp(nn::olv::PortalAppParam* pPortalAppParam, nn::olv::InitializeParam* pInitializeParam) { sint32 result = Initialize(pInitializeParam); if (result != OLV_RESULT_SUCCESS) return result; memcpy(pPortalAppParam->m_ParamPack, g_ParamPack.encodedParamPack, sizeof(g_ParamPack.encodedParamPack)); memcpy(pPortalAppParam->m_ServiceToken, g_DiscoveryResults.serviceToken, sizeof(g_DiscoveryResults.serviceToken)); snprintf(reinterpret_cast<char*>(pPortalAppParam->m_StartUrl), sizeof(pPortalAppParam->m_StartUrl), "%s/titles/show?src=menu", g_DiscoveryResults.portalEndpoint); return OLV_RESULT_SUCCESS; } namespace Report { uint32 GetReportTypes() { return g_ReportTypes; } void SetReportTypes(uint32 reportTypes) { g_ReportTypes = reportTypes | 0x1000; } } bool IsInitialized() { return g_IsInitialized; } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.h ================================================ #pragma once #include "Cemu/ncrypto/ncrypto.h" #include "config/ActiveSettings.h" #include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" #include "Cafe/OS/libs/coreinit/coreinit_MCP.h" namespace nn { namespace olv { class InitializeParam { public: static const inline uint32 FLAG_OFFLINE_MODE = (1 << 0); InitializeParam() { this->m_Flags = 0; this->m_ReportTypes = 7039; this->m_SysArgsSize = 0; this->m_Work = MEMPTR<uint8_t>(nullptr); this->m_SysArgs = MEMPTR<const void>(nullptr); this->m_WorkSize = 0; } static InitializeParam* __ctor(InitializeParam* _this) { if (!_this) { assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN return nullptr; } else return new (_this) InitializeParam(); } sint32 SetFlags(uint32 flags) { this->m_Flags = flags; return OLV_RESULT_SUCCESS; } static sint32 __SetFlags(InitializeParam* _this, uint32 flags) { return _this->SetFlags(flags); } sint32 SetWork(MEMPTR<uint8_t> pWorkData, uint32 workDataSize) { if (!pWorkData) return OLV_RESULT_INVALID_PTR; if (workDataSize < 0x10000) return OLV_RESULT_NOT_ENOUGH_SIZE; this->m_Work = pWorkData; this->m_WorkSize = workDataSize; return OLV_RESULT_SUCCESS; } static uint32 __SetWork(InitializeParam* _this, MEMPTR<uint8> pWorkData, uint32 workDataSize) { return _this->SetWork(pWorkData, workDataSize); } sint32 SetReportTypes(uint32 reportTypes) { this->m_ReportTypes = reportTypes; return OLV_RESULT_SUCCESS; } static sint32 __SetReportTypes(InitializeParam* _this, uint32 reportTypes) { return _this->SetReportTypes(reportTypes); } sint32 SetSysArgs(MEMPTR<const void> pSysArgs, uint32 sysArgsSize) { if (!pSysArgs) return OLV_RESULT_INVALID_PTR; if (!sysArgsSize) return OLV_RESULT_INVALID_PARAMETER; this->m_SysArgs = pSysArgs; this->m_SysArgsSize = sysArgsSize; return OLV_RESULT_SUCCESS; } static sint32 __SetSysArgs(InitializeParam* _this, MEMPTR<const void> pSysArgs, uint32 sysArgsSize) { return _this->SetSysArgs(pSysArgs, sysArgsSize); } uint32be m_Flags; uint32be m_ReportTypes; MEMPTR<uint8_t> m_Work; uint32be m_WorkSize; MEMPTR<const void> m_SysArgs; uint32be m_SysArgsSize; char unk[0x28]; }; static_assert(sizeof(nn::olv::InitializeParam) == 0x40, "sizeof(nn::olv::InitializeParam) != 0x40"); class PortalAppParam { public: PortalAppParam() { m_ParamPack[0] = 0; m_ServiceToken[0] = 0; m_StartUrl[0] = 0; } static PortalAppParam* __ctor(PortalAppParam* _this) { if (!_this) { assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN return nullptr; } else return new (_this) PortalAppParam(); } uint8be* GetParamPack() { return m_ParamPack; } static uint8be* __GetParamPack(PortalAppParam* _this) { return _this->GetParamPack(); } uint8be* GetServiceToken() { return m_ServiceToken; } static uint8be* __GetServiceToken(PortalAppParam* _this) { return _this->GetServiceToken(); } uint8be* GetStartUrl() { return m_StartUrl; } static uint8be* __GetStartUrl(PortalAppParam* _this) { return _this->GetStartUrl(); } public: /* +0x1A5C3C */ uint8be m_ParamPack[0x200]; /* +0x1A663B */ uint8be m_ServiceToken[0x201]; // IndependentServiceToken for Miiverse title /* +0x1A5E3C */ uint8be m_StartUrl[0x7ff]; // https://portal-us.olv.nintendo.net/titles/show?src=menu }; namespace Report { uint32 GetReportTypes(); void SetReportTypes(uint32 reportTypes); } bool IsInitialized(); sint32 Initialize(nn::olv::InitializeParam* pParam); sint32 InitializePortalApp(nn::olv::PortalAppParam* pPortalAppParam, nn::olv::InitializeParam* pInitializeParam); static void loadOliveInitializeTypes() { cafeExportRegisterFunc(Initialize, "nn_olv", "Initialize__Q2_2nn3olvFPCQ3_2nn3olv15InitializeParam", LogType::NN_OLV); cafeExportRegisterFunc(IsInitialized, "nn_olv", "IsInitialized__Q2_2nn3olvFv", LogType::NN_OLV); cafeExportRegisterFunc(Report::GetReportTypes, "nn_olv", "GetReportTypes__Q3_2nn3olv6ReportFv", LogType::NN_OLV); cafeExportRegisterFunc(Report::SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv6ReportFUi", LogType::NN_OLV); cafeExportRegisterFunc(InitializeParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv15InitializeParamFv", LogType::NN_OLV); cafeExportRegisterFunc(InitializeParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv15InitializeParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(InitializeParam::__SetWork, "nn_olv", "SetWork__Q3_2nn3olv15InitializeParamFPUcUi", LogType::NN_OLV); cafeExportRegisterFunc(InitializeParam::__SetReportTypes, "nn_olv", "SetReportTypes__Q3_2nn3olv15InitializeParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(InitializeParam::__SetSysArgs, "nn_olv", "SetSysArgs__Q3_2nn3olv15InitializeParamFPCvUi", LogType::NN_OLV); cafeExportRegisterFunc(InitializePortalApp, "nn_olv", "InitializePortalApp__Q3_2nn3olv6hiddenFPQ4_2nn3olv6hidden14PortalAppParamPCQ3_2nn3olv15InitializeParam", LogType::NN_OLV); cafeExportRegisterFunc(PortalAppParam::__ctor, "nn_olv", "__ct__Q4_2nn3olv6hidden14PortalAppParamFv", LogType::NN_OLV); cafeExportRegisterFunc(PortalAppParam::__GetParamPack, "nn_olv", "GetParamPack__Q4_2nn3olv6hidden14PortalAppParamCFv", LogType::NN_OLV); cafeExportRegisterFunc(PortalAppParam::__GetServiceToken, "nn_olv", "GetServiceToken__Q4_2nn3olv6hidden14PortalAppParamCFv", LogType::NN_OLV); cafeExportRegisterFunc(PortalAppParam::__GetStartUrl, "nn_olv", "GetStartUrl__Q4_2nn3olv6hidden14PortalAppParamCFv", LogType::NN_OLV); } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp ================================================ #include "nn_olv_Common.h" #include "nn_olv_PostTypes.h" #include "nn_olv_OfflineDB.h" #include "Cemu/ncrypto/ncrypto.h" // for base64 encoder/decoder #include "util/helpers/helpers.h" #include "config/ActiveSettings.h" #include "Cafe/CafeSystem.h" #include <pugixml.hpp> #include <zlib.h> #include <zarchive/zarchivereader.h> namespace nn { namespace olv { std::mutex g_offlineDBMutex; bool g_offlineDBInitialized = false; ZArchiveReader* g_offlineDBArchive{nullptr}; void OfflineDB_LazyInit() { std::scoped_lock _l(g_offlineDBMutex); if(g_offlineDBInitialized) return; // open archive g_offlineDBArchive = ZArchiveReader::OpenFromFile(ActiveSettings::GetUserDataPath("resources/miiverse/OfflineDB.zar")); if(!g_offlineDBArchive) cemuLog_log(LogType::Force, "Offline miiverse posts are not available"); g_offlineDBInitialized = true; } void OfflineDB_Shutdown() { std::scoped_lock _l(g_offlineDBMutex); if(!g_offlineDBInitialized) return; delete g_offlineDBArchive; g_offlineDBInitialized = false; } bool CheckForOfflineDBFile(const char* filePath, uint32* fileSize) { if(!g_offlineDBArchive) return false; ZArchiveNodeHandle fileHandle = g_offlineDBArchive->LookUp(filePath); if (!g_offlineDBArchive->IsFile(fileHandle)) return false; if(fileSize) *fileSize = g_offlineDBArchive->GetFileSize(fileHandle); return true; } bool LoadOfflineDBFile(const char* filePath, std::vector<uint8>& fileData) { fileData.clear(); if(!g_offlineDBArchive) return false; ZArchiveNodeHandle fileHandle = g_offlineDBArchive->LookUp(filePath); if (!g_offlineDBArchive->IsFile(fileHandle)) return false; fileData.resize(g_offlineDBArchive->GetFileSize(fileHandle)); g_offlineDBArchive->ReadFromFile(fileHandle, 0, fileData.size(), fileData.data()); return true; } void TryLoadCompressedMemoImage(DownloadedPostData& downloadedPostData) { const unsigned char tgaHeader_320x120_32BPP[] = {0x0,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x1,0x78,0x0,0x20,0x8}; std::string memoImageFilename = fmt::format("memo/{}", (char*)downloadedPostData.downloadedDataBase.postId); std::vector<uint8> bitmaskCompressedImg; if (!LoadOfflineDBFile(memoImageFilename.c_str(), bitmaskCompressedImg)) return; if (bitmaskCompressedImg.size() != (320*120)/8) return; std::vector<uint8> decompressedImage; decompressedImage.resize(sizeof(tgaHeader_320x120_32BPP) + 320 * 120 * 4); memcpy(decompressedImage.data(), tgaHeader_320x120_32BPP, sizeof(tgaHeader_320x120_32BPP)); uint8* pOut = decompressedImage.data() + sizeof(tgaHeader_320x120_32BPP); for(int i=0; i<320*120; i++) { bool isWhite = (bitmaskCompressedImg[i/8] & (1 << (i%8))) != 0; if(isWhite) { pOut[0] = pOut[1] = pOut[2] = pOut[3] = 0xFF; } else { pOut[0] = pOut[1] = pOut[2] = 0; pOut[3] = 0xFF; } pOut += 4; } // store compressed image uLongf compressedDestLen = 40960; int r = compress((uint8*)downloadedPostData.downloadedDataBase.compressedMemoBody, &compressedDestLen, decompressedImage.data(), decompressedImage.size()); if( r != Z_OK) return; downloadedPostData.downloadedDataBase.compressedMemoBodySize = compressedDestLen; downloadedPostData.downloadedDataBase.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_MEMO); } void CheckForExternalImage(DownloadedPostData& downloadedPostData) { std::string externalImageFilename = fmt::format("image/{}.jpg", (char*)downloadedPostData.downloadedDataBase.postId); uint32 fileSize; if (!CheckForOfflineDBFile(externalImageFilename.c_str(), &fileSize)) return; strcpy((char*)downloadedPostData.downloadedDataBase.externalImageDataUrl, externalImageFilename.c_str()); downloadedPostData.downloadedDataBase.SetFlag(DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE); downloadedPostData.downloadedDataBase.externalImageDataSize = fileSize; } nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList(coreinit::OSEvent* event, DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) { stdx::scope_exit _se([&](){coreinit::OSSignalEvent(event);}); uint64 titleId = CafeSystem::GetForegroundTitleId(); memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); memset(downloadedPostData, 0, sizeof(DownloadedPostData) * maxCount); *postCountOut = 0; const char* postXmlFilename = nullptr; if(titleId == 0x0005000010143400 || titleId == 0x0005000010143500 || titleId == 0x0005000010143600) postXmlFilename = "PostList_WindWakerHD.xml"; if (!postXmlFilename) return OLV_RESULT_SUCCESS; // load post XML std::vector<uint8> xmlData; if (!LoadOfflineDBFile(postXmlFilename, xmlData)) return OLV_RESULT_SUCCESS; pugi::xml_document doc; pugi::xml_parse_result result = doc.load_buffer(xmlData.data(), xmlData.size()); if (!result) return OLV_RESULT_SUCCESS; // collect list of all post xml nodes std::vector<pugi::xml_node> postXmlNodes; for (pugi::xml_node postNode = doc.child("posts").child("post"); postNode; postNode = postNode.next_sibling("post")) postXmlNodes.push_back(postNode); // randomly select up to maxCount posts srand(GetTickCount()); uint32 postCount = 0; while(!postXmlNodes.empty() && postCount < maxCount) { uint32 index = rand() % postXmlNodes.size(); pugi::xml_node& postNode = postXmlNodes[index]; auto& addedPost = downloadedPostData[postCount]; memset(&addedPost, 0, sizeof(DownloadedPostData)); if (!ParseXML_DownloadedPostData(addedPost, postNode) ) continue; TryLoadCompressedMemoImage(addedPost); CheckForExternalImage(addedPost); postCount++; // remove from post list postXmlNodes[index] = postXmlNodes.back(); postXmlNodes.pop_back(); } *postCountOut = postCount; return OLV_RESULT_SUCCESS; } nnResult OfflineDB_DownloadPostDataListParam_DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) { OfflineDB_LazyInit(); memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); downloadedTopicData->communityId = param->communityId; *postCountOut = 0; if(param->_HasFlag(DownloadPostDataListParam::FLAGS::SELF_ONLY)) return OLV_RESULT_SUCCESS; // the offlineDB doesn't contain any self posts StackAllocator<coreinit::OSEvent> doneEvent; coreinit::OSInitEvent(&doneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); auto asyncTask = std::async(std::launch::async, _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList, doneEvent.GetPointer(), downloadedTopicData, downloadedPostData, postCountOut, maxCount, param); coreinit::OSWaitEvent(&doneEvent); nnResult r = asyncTask.get(); return r; } nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(coreinit::OSEvent* event, DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) { stdx::scope_exit _se([&](){coreinit::OSSignalEvent(event);}); if (!_this->TestFlags(_this, DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE)) return OLV_RESULT_MISSING_DATA; // not all games may use JPEG files? std::string externalImageFilename = fmt::format("image/{}.jpg", (char*)_this->postId); std::vector<uint8> jpegData; if (!LoadOfflineDBFile(externalImageFilename.c_str(), jpegData)) return OLV_RESULT_FAILED_REQUEST; memcpy(imageDataOut, jpegData.data(), jpegData.size()); *imageSizeOut = jpegData.size(); return OLV_RESULT_SUCCESS; } nnResult OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) { StackAllocator<coreinit::OSEvent> doneEvent; coreinit::OSInitEvent(&doneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); auto asyncTask = std::async(std::launch::async, _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData, doneEvent.GetPointer(), _this, imageDataOut, imageSizeOut, maxSize); coreinit::OSWaitEvent(&doneEvent); nnResult r = asyncTask.get(); return r; } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.h ================================================ #pragma once #include "Cafe/OS/libs/nn_common.h" #include "nn_olv_Common.h" namespace nn { namespace olv { void OfflineDB_Init(); void OfflineDB_Shutdown(); nnResult OfflineDB_DownloadPostDataListParam_DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param); nnResult OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize); } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.cpp ================================================ #include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" #include "nn_olv_PostTypes.h" #include "nn_olv_OfflineDB.h" #include "Cemu/ncrypto/ncrypto.h" // for base64 decoder #include "util/helpers/helpers.h" #include <pugixml.hpp> #include <zlib.h> namespace nn { namespace olv { bool ParseXml_DownloadedDataBase(DownloadedDataBase& obj, pugi::xml_node& xmlNode) { // todo: // painting with url? pugi::xml_node tokenNode; if(tokenNode = xmlNode.child("body"); tokenNode) { obj.bodyTextLength = SetStringUC2(obj.bodyText, tokenNode.child_value(), true); if(obj.bodyTextLength > 0) obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_TEXT); } if(tokenNode = xmlNode.child("topic_tag"); tokenNode) { SetStringUC2(obj.topicTag, tokenNode.child_value(), true); } if(tokenNode = xmlNode.child("feeling_id"); tokenNode) { obj.feeling = ConvertString<sint8>(tokenNode.child_value()); if(obj.feeling < 0 || obj.feeling >= 5) { cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: feeling_id out of range"); return false; } } if(tokenNode = xmlNode.child("id"); tokenNode) { std::string_view id_sv = tokenNode.child_value(); if(id_sv.size() > 22) { cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: id too long"); return false; } memcpy(obj.postId, id_sv.data(), id_sv.size()); obj.postId[id_sv.size()] = '\0'; } if(tokenNode = xmlNode.child("is_autopost"); tokenNode) { uint8 isAutopost = ConvertString<sint8>(tokenNode.child_value()); if(isAutopost == 1) obj.SetFlag(DownloadedDataBase::FLAGS::IS_AUTOPOST); else if(isAutopost == 0) obj.SetFlag(DownloadedDataBase::FLAGS::IS_NOT_AUTOPOST); else { cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase::ParseXml: is_autopost has invalid value"); return false; } } if(tokenNode = xmlNode.child("empathy_added"); tokenNode) { if(ConvertString<sint32>(tokenNode.child_value()) > 0) obj.SetFlag(DownloadedDataBase::FLAGS::HAS_EMPATHY_ADDED); } if(tokenNode = xmlNode.child("is_spoiler"); tokenNode) { if(ConvertString<sint32>(tokenNode.child_value()) > 0) obj.SetFlag(DownloadedDataBase::FLAGS::IS_SPOILER); } if(tokenNode = xmlNode.child("mii"); tokenNode) { std::vector<uint8> miiData = NCrypto::base64Decode(tokenNode.child_value()); if(miiData.size() != 96) { cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicData mii data is not valid (incorrect size)"); return false; } memcpy(obj.miiData, miiData.data(), miiData.size()); obj.SetFlag(DownloadedDataBase::FLAGS::HAS_MII_DATA); } if(tokenNode = xmlNode.child("pid"); tokenNode) { obj.userPid = ConvertString<uint32>(tokenNode.child_value()); } if(tokenNode = xmlNode.child("screen_name"); tokenNode) { SetStringUC2(obj.miiNickname, tokenNode.child_value(), true); } if(tokenNode = xmlNode.child("region_id"); tokenNode) { obj.regionId = ConvertString<uint32>(tokenNode.child_value()); } if(tokenNode = xmlNode.child("platform_id"); tokenNode) { obj.platformId = ConvertString<uint8>(tokenNode.child_value()); } if(tokenNode = xmlNode.child("language_id"); tokenNode) { obj.languageId = ConvertString<uint8>(tokenNode.child_value()); } if(tokenNode = xmlNode.child("country_id"); tokenNode) { obj.countryId = ConvertString<uint8>(tokenNode.child_value()); } if(tokenNode = xmlNode.child("painting"); tokenNode) { if(pugi::xml_node subNode = tokenNode.child("content"); subNode) { std::vector<uint8> paintingData = NCrypto::base64Decode(subNode.child_value()); if (paintingData.size() > 0xA000) { cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase painting content is too large"); return false; } memcpy(obj.compressedMemoBody, paintingData.data(), paintingData.size()); obj.SetFlag(DownloadedDataBase::FLAGS::HAS_BODY_MEMO); } if(pugi::xml_node subNode = tokenNode.child("size"); subNode) { obj.compressedMemoBodySize = ConvertString<uint32>(subNode.child_value()); } } if(tokenNode = xmlNode.child("app_data"); tokenNode) { std::vector<uint8> appData = NCrypto::base64Decode(tokenNode.child_value()); if (appData.size() > 0x400) { cemuLog_log(LogType::Force, "[Olive-XML] DownloadedDataBase AppData is too large"); return false; } memcpy(obj.appData, appData.data(), appData.size()); obj.appDataLength = appData.size(); obj.SetFlag(DownloadedDataBase::FLAGS::HAS_APP_DATA); } return true; } bool ParseXML_DownloadedPostData(DownloadedPostData& obj, pugi::xml_node& xmlNode) { pugi::xml_node tokenNode; if(tokenNode = xmlNode.child("community_id"); tokenNode) obj.communityId = ConvertString<uint32>(tokenNode.child_value()); if(tokenNode = xmlNode.child("empathy_count"); tokenNode) obj.empathyCount = ConvertString<uint32>(tokenNode.child_value()); if(tokenNode = xmlNode.child("reply_count"); tokenNode) obj.commentCount = ConvertString<uint32>(tokenNode.child_value()); return ParseXml_DownloadedDataBase(obj.downloadedDataBase, xmlNode); } bool ParseXML_DownloadedSystemPostData(hidden::DownloadedSystemPostData& obj, pugi::xml_node& xmlNode) { pugi::xml_node tokenNode; if(tokenNode = xmlNode.child("title_id"); tokenNode) obj.titleId = ConvertString<uint64>(tokenNode.child_value()); return ParseXML_DownloadedPostData(obj.downloadedPostData, xmlNode); } bool ParseXML_DownloadedTopicData(DownloadedTopicData& obj, pugi::xml_node& xmlNode) { pugi::xml_node tokenNode; if(tokenNode = xmlNode.child("community_id"); tokenNode) obj.communityId = ConvertString<uint32>(tokenNode.child_value()); return true; } bool Parse_DownloadedSystemTopicData(hidden::DownloadedSystemTopicData& obj, pugi::xml_node& xmlNode) { if(!ParseXML_DownloadedTopicData(obj.downloadedTopicData, xmlNode)) return false; pugi::xml_node tokenNode; if(tokenNode = xmlNode.child("name"); tokenNode) { SetStringUC2(obj.titleText, tokenNode.child_value(), true); obj.downloadedTopicData.SetFlag(DownloadedTopicData::FLAGS::HAS_TITLE); } if(tokenNode = xmlNode.child("is_recommended"); tokenNode) { uint32 isRecommended = ConvertString<uint32>(tokenNode.child_value()); if(isRecommended != 0) obj.downloadedTopicData.SetFlag(DownloadedTopicData::FLAGS::IS_RECOMMENDED); } if(tokenNode = xmlNode.child("title_id"); tokenNode) { obj.titleId = ConvertString<uint64>(tokenNode.child_value()); } if(tokenNode = xmlNode.child("title_ids"); tokenNode) { cemu_assert_unimplemented(); } if(tokenNode = xmlNode.child("icon"); tokenNode) { std::vector<uint8> iconData = NCrypto::base64Decode(tokenNode.child_value()); if(iconData.size() > sizeof(obj.iconData)) { cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicData icon data is not valid"); return false; } obj.iconDataSize = iconData.size(); memcpy(obj.iconData, iconData.data(), iconData.size()); obj.downloadedTopicData.SetFlag(DownloadedTopicData::FLAGS::HAS_ICON_DATA); } return true; } uint32 GetSystemTopicDataListFromRawData(hidden::DownloadedSystemTopicDataList* downloadedSystemTopicDataList, hidden::DownloadedSystemPostData* downloadedSystemPostData, uint32be* postCountOut, uint32 postCountMax, void* xmlData, uint32 xmlDataSize) { // copy xmlData into a temporary buffer since load_buffer_inplace will modify it std::vector<uint8> buffer; buffer.resize(xmlDataSize); memcpy(buffer.data(), xmlData, xmlDataSize); pugi::xml_document doc; if (!doc.load_buffer_inplace(buffer.data(), xmlDataSize, pugi::parse_default, pugi::xml_encoding::encoding_utf8)) return -1; memset(downloadedSystemTopicDataList, 0, sizeof(hidden::DownloadedSystemTopicDataList)); downloadedSystemTopicDataList->topicDataNum = 0; cemu_assert_debug(doc.child("result").child("topics")); size_t postCount = 0; // parse topics for (pugi::xml_node topicsChildNode : doc.child("result").child("topics").children()) { const char* name = topicsChildNode.name(); cemuLog_logDebug(LogType::Force, "topicsChildNode.name() = {}", name); if (strcmp(topicsChildNode.name(), "topic")) continue; // parse topic if(downloadedSystemTopicDataList->topicDataNum > 10) { cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicDataList exceeded maximum topic count (10)"); return false; } auto& topicEntry = downloadedSystemTopicDataList->topicData[downloadedSystemTopicDataList->topicDataNum]; memset(&topicEntry, 0, sizeof(hidden::DownloadedSystemTopicDataList::DownloadedSystemTopicWrapped)); Parse_DownloadedSystemTopicData(topicEntry.downloadedSystemTopicData, topicsChildNode); downloadedSystemTopicDataList->topicDataNum = downloadedSystemTopicDataList->topicDataNum + 1; topicEntry.postDataNum = 0; // parse all posts within the current topic for (pugi::xml_node personNode : topicsChildNode.child("people").children("person")) { for (pugi::xml_node postNode : personNode.child("posts").children("post")) { if(postCount >= postCountMax) { cemuLog_log(LogType::Force, "[Olive-XML] GetSystemTopicDataListFromRawData exceeded maximum post count"); return false; } auto& postEntry = downloadedSystemPostData[postCount]; memset(&postEntry, 0, sizeof(hidden::DownloadedSystemPostData)); bool r = ParseXML_DownloadedSystemPostData(postEntry, postNode); if(!r) { cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemPostData parsing failed"); return false; } postCount++; // add post to topic if(topicEntry.postDataNum >= hidden::DownloadedSystemTopicDataList::MAX_POSTS_PER_TOPIC) { cemuLog_log(LogType::Force, "[Olive-XML] DownloadedSystemTopicDataList has too many posts for a single topic (up to {})", hidden::DownloadedSystemTopicDataList::MAX_POSTS_PER_TOPIC); return false; } topicEntry.postDataList[topicEntry.postDataNum] = &postEntry; topicEntry.postDataNum = topicEntry.postDataNum + 1; } } } *postCountOut = postCount; return 0; } nnResult DownloadedDataBase::DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) { if(g_IsOfflineDBMode) return OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(_this, imageDataOut, imageSizeOut, maxSize); if(!g_IsOnlineMode) return OLV_RESULT_OFFLINE_MODE_REQUEST; if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_IMAGE)) return OLV_RESULT_MISSING_DATA; cemuLog_logDebug(LogType::Force, "DownloadedDataBase::DownloadExternalImageData not implemented"); return OLV_RESULT_FAILED_REQUEST; // placeholder error } nnResult DownloadPostDataListParam::GetRawDataUrl(DownloadPostDataListParam* _this, char* urlOut, uint32 urlMaxSize) { if(!g_IsOnlineMode) return OLV_RESULT_OFFLINE_MODE_REQUEST; //if(_this->communityId == 0) // cemuLog_log(LogType::Force, "DownloadPostDataListParam::GetRawDataUrl called with invalid communityId"); // get base url std::string baseUrl; baseUrl.append(g_DiscoveryResults.apiEndpoint); //baseUrl.append(fmt::format("/v1/communities/{}/posts", (uint32)_this->communityId)); cemu_assert_debug(_this->communityId == 0); baseUrl.append(fmt::format("/v1/posts.search", (uint32)_this->communityId)); // "v1/posts.search" // build parameter string std::string params; // this function behaves differently for the Wii U menu? Where it can lookup posts by titleId? if(_this->titleId != 0) { cemu_assert_unimplemented(); // Wii U menu mode } // todo: Generic parameters. Which includes: language_id, limit, type=text/memo // handle postIds for(size_t i=0; i<_this->MAX_NUM_POST_ID; i++) { if(_this->searchPostId[i].str[0] == '\0') continue; cemu_assert_unimplemented(); // todo // todo - postId parameter // handle filters if(_this->_HasFlag(DownloadPostDataListParam::FLAGS::WITH_MII)) params.append("&with_mii=1"); if(_this->_HasFlag(DownloadPostDataListParam::FLAGS::WITH_EMPATHY)) params.append("&with_empathy_added=1"); if(_this->bodyTextMaxLength != 0) params.append(fmt::format("&max_body_length={}", _this->bodyTextMaxLength)); } if(_this->titleId != 0) params.append(fmt::format("&title_id={}", (uint64)_this->titleId)); if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::FRIENDS_ONLY)) params.append("&by=friend"); if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::FOLLOWERS_ONLY)) params.append("&by=followings"); if (_this->_HasFlag(DownloadPostDataListParam::FLAGS::SELF_ONLY)) params.append("&by=self"); if(!params.empty()) params[0] = '?'; // replace the leading ampersand baseUrl.append(params); if(baseUrl.size()+1 > urlMaxSize) return OLV_RESULT_NOT_ENOUGH_SIZE; strncpy(urlOut, baseUrl.c_str(), urlMaxSize); return OLV_RESULT_SUCCESS; } nnResult DownloadPostDataList(DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) { if(g_IsOfflineDBMode) return OfflineDB_DownloadPostDataListParam_DownloadPostDataList(downloadedTopicData, downloadedPostData, postCountOut, maxCount, param); memset(downloadedTopicData, 0, sizeof(DownloadedTopicData)); downloadedTopicData->communityId = param->communityId; *postCountOut = 0; char urlBuffer[2048]; if (NN_RESULT_IS_FAILURE(DownloadPostDataListParam::GetRawDataUrl(param, urlBuffer, sizeof(urlBuffer)))) return OLV_RESULT_INVALID_PARAMETER; /* CurlRequestHelper req; req.initate(urlBuffer, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); bool reqResult = req.submitRequest(); if (!reqResult) { long httpCode = 0; curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); cemuLog_log(LogType::Force, "Failed request: {} ({})", urlBuffer, httpCode); if (!(httpCode >= 400)) return OLV_RESULT_FAILED_REQUEST; } pugi::xml_document doc; if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) { cemuLog_log(LogType::Force, fmt::format("Invalid XML in community download response")); return OLV_RESULT_INVALID_XML; } */ *postCountOut = 0; return OLV_RESULT_SUCCESS; } void loadOlivePostAndTopicTypes() { cafeExportRegisterFunc(GetSystemTopicDataListFromRawData, "nn_olv", "GetSystemTopicDataListFromRawData__Q3_2nn3olv6hiddenFPQ4_2nn3olv6hidden29DownloadedSystemTopicDataListPQ4_2nn3olv6hidden24DownloadedSystemPostDataPUiUiPCUcT4", LogType::NN_OLV); // DownloadedDataBase getters cafeExportRegisterFunc(DownloadedDataBase::TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetUserPid, "nn_olv", "GetUserPid__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetPostDate, "nn_olv", "GetPostDate__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetFeeling, "nn_olv", "GetFeeling__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetRegionId, "nn_olv", "GetRegionId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetPlatformId, "nn_olv", "GetPlatformId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetLanguageId, "nn_olv", "GetLanguageId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetCountryId, "nn_olv", "GetCountryId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetExternalUrl, "nn_olv", "GetExternalUrl__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetMiiData1, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFP12FFLStoreData", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetMiiNickname, "nn_olv", "GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetBodyText, "nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetBodyMemo, "nn_olv", "GetBodyMemo__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetTopicTag, "nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetMiiData2, "nn_olv", "GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::DownloadExternalImageData, "nn_olv", "DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedDataBase::GetExternalImageDataSize, "nn_olv", "GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv", LogType::NN_OLV); // DownloadedPostData getters cafeExportRegisterFunc(DownloadedPostData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedPostData::GetEmpathyCount, "nn_olv", "GetEmpathyCount__Q3_2nn3olv18DownloadedPostDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedPostData::GetCommentCount, "nn_olv", "GetCommentCount__Q3_2nn3olv18DownloadedPostDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadedPostData::GetPostId, "nn_olv", "GetPostId__Q3_2nn3olv18DownloadedPostDataCFv", LogType::NN_OLV); // DownloadedSystemPostData getters cafeExportRegisterFunc(hidden::DownloadedSystemPostData::GetTitleId, "nn_olv", "GetTitleId__Q4_2nn3olv6hidden24DownloadedSystemPostDataCFv", LogType::NN_OLV); // DownloadedTopicData getters cafeExportRegisterFunc(DownloadedTopicData::GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv19DownloadedTopicDataCFv", LogType::NN_OLV); // DownloadedSystemTopicData getters cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::TestFlags, "nn_olv", "TestFlags__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFUi", LogType::NN_OLV); cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleId, "nn_olv", "GetTitleId__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleIdNum, "nn_olv", "GetTitleIdNum__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleText, "nn_olv", "GetTitleText__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPwUi", LogType::NN_OLV); cafeExportRegisterFunc(hidden::DownloadedSystemTopicData::GetTitleIconData, "nn_olv", "GetTitleIconData__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPUcPUiUi", LogType::NN_OLV); // DownloadedSystemTopicDataList getters cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicDataNum, "nn_olv", "GetDownloadedSystemTopicDataNum__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFv", LogType::NN_OLV); cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostDataNum, "nn_olv", "GetDownloadedSystemPostDataNum__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::NN_OLV); cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemTopicData, "nn_olv", "GetDownloadedSystemTopicData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFi", LogType::NN_OLV); cafeExportRegisterFunc(hidden::DownloadedSystemTopicDataList::GetDownloadedSystemPostData, "nn_olv", "GetDownloadedSystemPostData__Q4_2nn3olv6hidden29DownloadedSystemTopicDataListCFiT1", LogType::NN_OLV); // DownloadPostDataListParam constructor and getters cafeExportRegisterFunc(DownloadPostDataListParam::Construct, "nn_olv", "__ct__Q3_2nn3olv25DownloadPostDataListParamFv", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataListParam::SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataListParam::SetLanguageId, "nn_olv", "SetLanguageId__Q3_2nn3olv25DownloadPostDataListParamFUc", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataListParam::SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchKey, "nn_olv", "SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCwUc", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchKeySingle, "nn_olv", "SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCw", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataListParam::SetSearchPid, "nn_olv", "SetSearchPid__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataListParam::SetPostId, "nn_olv", "SetPostId__Q3_2nn3olv25DownloadPostDataListParamFPCcUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataListParam::SetPostDate, "nn_olv", "SetPostDate__Q3_2nn3olv25DownloadPostDataListParamFL", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataListParam::SetPostDataMaxNum, "nn_olv", "SetPostDataMaxNum__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataListParam::SetBodyTextMaxLength, "nn_olv", "SetBodyTextMaxLength__Q3_2nn3olv25DownloadPostDataListParamFUi", LogType::NN_OLV); // URL and downloading functions cafeExportRegisterFunc(DownloadPostDataListParam::GetRawDataUrl, "nn_olv", "GetRawDataUrl__Q3_2nn3olv25DownloadPostDataListParamCFPcUi", LogType::NN_OLV); cafeExportRegisterFunc(DownloadPostDataList, "nn_olv", "DownloadPostDataList__Q2_2nn3olvFPQ3_2nn3olv19DownloadedTopicDataPQ3_2nn3olv18DownloadedPostDataPUiUiPCQ3_2nn3olv25DownloadPostDataListParam", LogType::NN_OLV); } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_PostTypes.h ================================================ #pragma once #include <zlib.h> #include "nn_olv_Common.h" namespace nn { namespace olv { struct DownloadedDataBase { enum class FLAGS : uint32 { HAS_BODY_TEXT = 0x01, HAS_BODY_MEMO = 0x02, HAS_EXTERNAL_IMAGE = 0x04, HAS_EXTERNAL_BINARY_DATA = 0x08, HAS_MII_DATA = 0x10, HAS_EXTERNAL_URL = 0x20, HAS_APP_DATA = 0x40, HAS_EMPATHY_ADDED = 0x80, IS_AUTOPOST = 0x100, IS_SPOILER = 0x200, IS_NOT_AUTOPOST = 0x400, // autopost flag was explicitly set to false }; void Reset() { memset(this, 0, sizeof(DownloadedDataBase)); } void SetFlag(FLAGS flag) { flags = (FLAGS)((uint32)flags.value() | (uint32)flag); } betype<FLAGS> flags; uint32be userPid; uint8be postId[32]; // string, up to 22 characters but the buffer is 32 bytes uint64be postDate; sint8be feeling; uint8be _padding0031[3]; uint32be regionId; uint8be platformId; uint8be languageId; uint8be countryId; uint8be _padding003B; uint16be bodyText[256]; uint32be bodyTextLength; uint8be compressedMemoBody[40960]; uint32be compressedMemoBodySize; uint16be topicTag[152]; uint8be appData[1024]; uint32be appDataLength; uint8be externalBinaryUrl[256]; uint32be externalBinaryDataSize; uint8be externalImageDataUrl[256]; uint32be externalImageDataSize; uint8be externalURL[256]; uint8be miiData[96]; uint16be miiNickname[16]; uint32be _paddingAB00[1344]; uint32be uknC000_someVTableMaybe; uint32be uknC004; // getters // TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi static bool TestFlags(DownloadedDataBase* _this, DownloadedDataBase::FLAGS flag) { return HAS_FLAG((uint32)_this->flags.value(), (uint32)flag); } // GetUserPid__Q3_2nn3olv18DownloadedDataBaseCFv static uint32 GetUserPid(DownloadedDataBase* _this) { return _this->userPid; } // GetPostDate__Q3_2nn3olv18DownloadedDataBaseCFv static uint64 GetPostDate(DownloadedDataBase* _this) { return _this->postDate; } // GetFeeling__Q3_2nn3olv18DownloadedDataBaseCFv static uint32 GetFeeling(DownloadedDataBase* _this) { if(_this->feeling >= 6) return 0; return _this->feeling; } // GetRegionId__Q3_2nn3olv18DownloadedDataBaseCFv static uint32 GetRegionId(DownloadedDataBase* _this) { return _this->regionId; } // GetPlatformId__Q3_2nn3olv18DownloadedDataBaseCFv static uint32 GetPlatformId(DownloadedDataBase* _this) { return _this->platformId; } // GetLanguageId__Q3_2nn3olv18DownloadedDataBaseCFv static uint32 GetLanguageId(DownloadedDataBase* _this) { return _this->languageId; } // GetCountryId__Q3_2nn3olv18DownloadedDataBaseCFv static uint32 GetCountryId(DownloadedDataBase* _this) { return _this->countryId; } // GetExternalUrl__Q3_2nn3olv18DownloadedDataBaseCFv static uint8be* GetExternalUrl(DownloadedDataBase* _this) { if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_URL)) return nullptr; return _this->externalURL; } // GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFP12FFLStoreData static nnResult GetMiiData1(DownloadedDataBase* _this, void* miiDataOut) { if (!TestFlags(_this, FLAGS::HAS_MII_DATA)) return OLV_RESULT_MISSING_DATA; if (!miiDataOut) return OLV_RESULT_INVALID_PTR; memcpy(miiDataOut, _this->miiData, 96); return OLV_RESULT_SUCCESS; } // GetMiiData__Q3_2nn3olv18DownloadedDataBaseCFv static uint8be* GetMiiData2(DownloadedDataBase* _this) { if (!TestFlags(_this, FLAGS::HAS_MII_DATA)) return nullptr; return _this->miiData; } // GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv static uint16be* GetMiiNickname(DownloadedDataBase* _this) { if (_this->miiNickname[0] == 0) return nullptr; return _this->miiNickname; } // GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi static nnResult GetBodyText(DownloadedDataBase* _this, uint16be* bodyTextOut, uint32 maxLength) { if (!bodyTextOut) return OLV_RESULT_INVALID_PTR; if (maxLength == 0) return OLV_RESULT_NOT_ENOUGH_SIZE; if (!TestFlags(_this, FLAGS::HAS_BODY_TEXT)) return OLV_RESULT_MISSING_DATA; memset(bodyTextOut, 0, maxLength * sizeof(uint16)); uint32 outputLength = std::min<uint32>(_this->bodyTextLength, maxLength); olv_wstrncpy((char16_t*)bodyTextOut, (char16_t*)_this->bodyText, outputLength); return OLV_RESULT_SUCCESS; } // GetBodyMemo__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi static nnResult GetBodyMemo(DownloadedDataBase* _this, uint8be* bodyMemoOut, uint32* bodyMemoSizeOut, uint32 maxSize) { if (!bodyMemoOut) return OLV_RESULT_INVALID_PTR; if (maxSize < 0x2582C) return OLV_RESULT_NOT_ENOUGH_SIZE; if (!TestFlags(_this, FLAGS::HAS_BODY_MEMO)) return OLV_RESULT_MISSING_DATA; // uncompress TGA uLongf decompressedSize = maxSize; if (uncompress((uint8*)bodyMemoOut, &decompressedSize, (uint8*)_this->compressedMemoBody, _this->compressedMemoBodySize) != Z_OK) { cemuLog_log(LogType::Force, "DownloadedSystemTopicData::GetTitleIconData: uncompress failed"); return OLV_RESULT_INVALID_TEXT_FIELD; // status } if(bodyMemoSizeOut) *bodyMemoSizeOut = decompressedSize; // todo - verify TGA header return OLV_RESULT_SUCCESS; } // GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv static uint16be* GetTopicTag(DownloadedDataBase* _this) { return _this->topicTag; } // GetAppData__Q3_2nn3olv18DownloadedDataBaseCFPUcPUiUi static nnResult GetAppData(DownloadedDataBase* _this, uint8be* appDataOut, uint32* appDataSizeOut, uint32 maxSize) { if (!appDataOut) return OLV_RESULT_INVALID_PTR; if (!TestFlags(_this, FLAGS::HAS_APP_DATA)) return OLV_RESULT_MISSING_DATA; uint32 outputSize = std::min<uint32>(maxSize, _this->appDataLength); memcpy(appDataOut, _this->appData, outputSize); if(appDataSizeOut) *appDataSizeOut = outputSize; return OLV_RESULT_SUCCESS; } // GetAppDataSize__Q3_2nn3olv18DownloadedDataBaseCFv static uint32 GetAppDataSize(DownloadedDataBase* _this) { return _this->appDataLength; } // GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv static uint8be* GetPostId(DownloadedDataBase* _this) { return _this->postId; } // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi static nnResult DownloadExternalImageData(DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize); // GetExternalImageDataSize__Q3_2nn3olv18DownloadedDataBaseCFv static uint32 GetExternalImageDataSize(DownloadedDataBase* _this) { if (!TestFlags(_this, FLAGS::HAS_EXTERNAL_IMAGE)) return 0; return _this->externalImageDataSize; } // todo: // DownloadExternalImageData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi (implement downloading) // DownloadExternalBinaryData__Q3_2nn3olv18DownloadedDataBaseCFPvPUiUi // GetExternalBinaryDataSize__Q3_2nn3olv18DownloadedDataBaseCFv }; static_assert(sizeof(DownloadedDataBase) == 0xC008); struct DownloadedPostData { DownloadedDataBase downloadedDataBase; uint32be communityId; uint32be empathyCount; uint32be commentCount; uint32be paddingC014[125]; // probably unused? // getters // GetCommunityId__Q3_2nn3olv18DownloadedPostDataCFv static uint32 GetCommunityId(DownloadedPostData* _this) { return _this->communityId; } // GetEmpathyCount__Q3_2nn3olv18DownloadedPostDataCFv static uint32 GetEmpathyCount(DownloadedPostData* _this) { return _this->empathyCount; } // GetCommentCount__Q3_2nn3olv18DownloadedPostDataCFv static uint32 GetCommentCount(DownloadedPostData* _this) { return _this->commentCount; } // GetPostId__Q3_2nn3olv18DownloadedPostDataCFv static uint8be* GetPostId(DownloadedPostData* _this) { return _this->downloadedDataBase.postId; } }; static_assert(sizeof(DownloadedPostData) == 0xC208); struct DownloadedTopicData { enum class FLAGS { IS_RECOMMENDED = 0x01, HAS_TITLE = 0x02, HAS_ICON_DATA = 0x04, }; betype<FLAGS> flags; uint32be communityId; int ukn[1022]; void SetFlag(FLAGS flag) { flags = (FLAGS)((uint32)flags.value() | (uint32)flag); } // GetCommunityId__Q3_2nn3olv19DownloadedTopicDataCFv static uint32 GetCommunityId(DownloadedTopicData* _this) { return _this->communityId; } }; static_assert(sizeof(DownloadedTopicData) == 0x1000); namespace hidden { struct DownloadedSystemPostData { DownloadedPostData downloadedPostData; uint64be titleId; uint32be uknC210[124]; uint32be uknC400; uint32be uknC404; // getters // GetTitleId__Q4_2nn3olv6hidden24DownloadedSystemPostDataCFv static uint64 GetTitleId(DownloadedSystemPostData* _this) { return _this->titleId; } }; static_assert(sizeof(DownloadedSystemPostData) == 0xC408); struct DownloadedSystemTopicData { DownloadedTopicData downloadedTopicData; uint64be titleId; uint16be titleText[128]; uint8be ukn1108[256]; uint8be iconData[0x1002C]; uint32be iconDataSize; uint64be titleIds[32]; uint32be titleIdsCount; uint32be ukn1133C[1841]; // implement getters as static methods for compatibility with CafeExportRegisterFunc() // TestFlags__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFUi static bool TestFlags(DownloadedSystemTopicData* _this, DownloadedTopicData::FLAGS flag) { return HAS_FLAG((uint32)_this->downloadedTopicData.flags.value(), (uint32)flag); } // GetTitleId__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv static uint64 GetTitleId(DownloadedSystemTopicData* _this) { return _this->titleId; } // GetTitleIdNum__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFv static uint32 GetTitleIdNum(DownloadedSystemTopicData* _this) { return _this->titleIdsCount; } // GetTitleText__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPwUi static nnResult GetTitleText(DownloadedSystemTopicData* _this, uint16be* titleTextOut, uint32 maxLength) { if (!TestFlags(_this, DownloadedTopicData::FLAGS::HAS_TITLE)) return OLV_RESULT_MISSING_DATA; if (!titleTextOut) return OLV_RESULT_INVALID_PTR; memset(titleTextOut, 0, maxLength * sizeof(uint16be)); if (maxLength > 128) maxLength = 128; olv_wstrncpy((char16_t*)titleTextOut, (char16_t*)_this->titleText, maxLength); return OLV_RESULT_SUCCESS; } // GetTitleIconData__Q4_2nn3olv6hidden25DownloadedSystemTopicDataCFPUcPUiUi static nnResult GetTitleIconData(DownloadedSystemTopicData* _this, void* iconDataOut, uint32be* iconSizeOut, uint32 iconDataMaxSize) { if (!TestFlags(_this, DownloadedTopicData::FLAGS::HAS_ICON_DATA)) return OLV_RESULT_MISSING_DATA; if (!iconDataOut) return OLV_RESULT_INVALID_PTR; if (iconDataMaxSize < 0x1002C) return OLV_RESULT_NOT_ENOUGH_SIZE; uLongf decompressedSize = iconDataMaxSize; if (uncompress((uint8*)iconDataOut, &decompressedSize, (uint8*)_this->iconData, _this->iconDataSize) != Z_OK) { cemuLog_log(LogType::Force, "DownloadedSystemTopicData::GetTitleIconData: uncompress failed"); return OLV_RESULT_INVALID_TEXT_FIELD; // status } *iconSizeOut = decompressedSize; // todo - check for TGA return OLV_RESULT_SUCCESS; } }; static_assert(sizeof(DownloadedSystemTopicData) == 0x13000); struct DownloadedSystemTopicDataList { static constexpr size_t MAX_TOPIC_COUNT = 10; static constexpr size_t MAX_POSTS_PER_TOPIC = 300; // 0x134B8 sized wrapper of DownloadedSystemTopicData struct DownloadedSystemTopicWrapped { DownloadedSystemTopicData downloadedSystemTopicData; uint32be postDataNum; MEMPTR<DownloadedSystemPostData> postDataList[MAX_POSTS_PER_TOPIC]; uint32 uknPadding; }; static_assert(offsetof(DownloadedSystemTopicWrapped, postDataNum) == 0x13000); static_assert(sizeof(DownloadedSystemTopicWrapped) == 0x134B8); static uint32 GetDownloadedSystemTopicDataNum(DownloadedSystemTopicDataList* _this) { return _this->topicDataNum; }; static uint32 GetDownloadedSystemPostDataNum(DownloadedSystemTopicDataList* _this, uint32 topicIndex) { if(topicIndex >= MAX_TOPIC_COUNT) return 0; return _this->topicData[topicIndex].postDataNum; }; static DownloadedSystemTopicData* GetDownloadedSystemTopicData(DownloadedSystemTopicDataList* _this, uint32 topicIndex) { if(topicIndex >= MAX_TOPIC_COUNT) return nullptr; return &_this->topicData[topicIndex].downloadedSystemTopicData; }; static DownloadedSystemPostData* GetDownloadedSystemPostData(DownloadedSystemTopicDataList* _this, sint32 topicIndex, sint32 postIndex) { if (topicIndex >= MAX_TOPIC_COUNT || postIndex >= MAX_POSTS_PER_TOPIC) return nullptr; return _this->topicData[topicIndex].postDataList[postIndex]; } // member variables uint32be topicDataNum; uint32be ukn4; DownloadedSystemTopicWrapped topicData[MAX_TOPIC_COUNT]; uint32be uknC0F38[50]; }; static_assert(sizeof(DownloadedSystemTopicDataList) == 0xC1000); } struct DownloadPostDataListParam { static constexpr size_t MAX_NUM_SEARCH_PID = 12; static constexpr size_t MAX_NUM_SEARCH_KEY = 5; static constexpr size_t MAX_NUM_POST_ID = 20; enum class FLAGS { FRIENDS_ONLY = 0x01, // friends only FOLLOWERS_ONLY = 0x02, // followers only SELF_ONLY = 0x04, // self only ONLY_TYPE_TEXT = 0x08, ONLY_TYPE_MEMO = 0x10, UKN_20 = 0x20, WITH_MII = 0x40, // with mii WITH_EMPATHY = 0x80, // with yeahs added UKN_100 = 0x100, UKN_200 = 0x200, // "is_delay" parameter UKN_400 = 0x400, // "is_hot" parameter }; struct SearchKey { uint16be str[152]; }; struct PostId { char str[32]; }; betype<FLAGS> flags; uint32be communityId; uint32be searchPid[MAX_NUM_SEARCH_PID]; uint8 languageId; uint8 hasLanguageId_039; uint8 padding03A[2]; uint32be postDataMaxNum; SearchKey searchKeyArray[MAX_NUM_SEARCH_KEY]; PostId searchPostId[MAX_NUM_POST_ID]; uint64be postDate; // OSTime? uint64be titleId; // only used by System posts? uint32be bodyTextMaxLength; uint8 padding8C4[1852]; bool _HasFlag(FLAGS flag) { return ((uint32)flags.value() & (uint32)flag) != 0; } void _SetFlags(FLAGS flag) { flags = (FLAGS)((uint32)flags.value() | (uint32)flag); } // constructor and getters // __ct__Q3_2nn3olv25DownloadPostDataListParamFv static DownloadPostDataListParam* Construct(DownloadPostDataListParam* _this) { memset(_this, 0, sizeof(DownloadPostDataListParam)); return _this; } // SetFlags__Q3_2nn3olv25DownloadPostDataListParamFUi static nnResult SetFlags(DownloadPostDataListParam* _this, FLAGS flags) { // todo - verify flag combos _this->flags = flags; return OLV_RESULT_SUCCESS; } // SetLanguageId__Q3_2nn3olv25DownloadPostDataListParamFUc static nnResult SetLanguageId(DownloadPostDataListParam* _this, uint8 languageId) { _this->languageId = languageId; _this->hasLanguageId_039 = 1; return OLV_RESULT_SUCCESS; } // SetCommunityId__Q3_2nn3olv25DownloadPostDataListParamFUi static nnResult SetCommunityId(DownloadPostDataListParam* _this, uint32 communityId) { _this->communityId = communityId; return OLV_RESULT_SUCCESS; } // SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCwUc static nnResult SetSearchKey(DownloadPostDataListParam* _this, const uint16be* searchKey, uint8 searchKeyIndex) { if( !searchKey ) { memset(&_this->searchKeyArray[searchKeyIndex], 0, sizeof(SearchKey)); return OLV_RESULT_SUCCESS; } if (searchKeyIndex >= MAX_NUM_SEARCH_KEY) return OLV_RESULT_INVALID_PARAMETER; memset(&_this->searchKeyArray[searchKeyIndex], 0, sizeof(SearchKey)); if(olv_wstrnlen((const char16_t*)searchKey, 152) > 50) { cemuLog_log(LogType::Force, "DownloadPostDataListParam::SetSearchKey: searchKey is too long\n"); return OLV_RESULT_INVALID_PARAMETER; } SetStringUC2(_this->searchKeyArray[searchKeyIndex].str, searchKey); return OLV_RESULT_SUCCESS; } // SetSearchKey__Q3_2nn3olv25DownloadPostDataListParamFPCw static nnResult SetSearchKeySingle(DownloadPostDataListParam* _this, const uint16be* searchKey) { if (searchKey == nullptr) { cemuLog_logDebug(LogType::NN_OLV, "DownloadPostDataListParam::SetSearchKeySingle: searchKeySingle is Null\n"); return OLV_RESULT_INVALID_PARAMETER; } return SetSearchKey(_this, searchKey, 0); } // SetSearchPid__Q3_2nn3olv25DownloadPostDataListParamFUi static nnResult SetSearchPid(DownloadPostDataListParam* _this, uint32 searchPid) { if(_this->_HasFlag(FLAGS::FRIENDS_ONLY) || _this->_HasFlag(FLAGS::FOLLOWERS_ONLY) || _this->_HasFlag(FLAGS::SELF_ONLY)) return OLV_RESULT_INVALID_PARAMETER; _this->searchPid[0] = searchPid; return OLV_RESULT_SUCCESS; } // SetPostId__Q3_2nn3olv25DownloadPostDataListParamFPCcUi static nnResult SetPostId(DownloadPostDataListParam* _this, const char* postId, uint32 postIdIndex) { if (postIdIndex >= MAX_NUM_POST_ID) return OLV_RESULT_INVALID_PARAMETER; memset(&_this->searchPostId[postIdIndex], 0, sizeof(PostId)); if (strlen(postId) > 22) { cemuLog_log(LogType::Force, "DownloadPostDataListParam::SetPostId: postId is too long\n"); return OLV_RESULT_INVALID_PARAMETER; } strcpy(_this->searchPostId[postIdIndex].str, postId); return OLV_RESULT_SUCCESS; } // SetPostDate__Q3_2nn3olv25DownloadPostDataListParamFL static nnResult SetPostDate(DownloadPostDataListParam* _this, uint64 postDate) { _this->postDate = postDate; return OLV_RESULT_SUCCESS; } // SetPostDataMaxNum__Q3_2nn3olv25DownloadPostDataListParamFUi static nnResult SetPostDataMaxNum(DownloadPostDataListParam* _this, uint32 postDataMaxNum) { if(postDataMaxNum == 0) return OLV_RESULT_INVALID_PARAMETER; _this->postDataMaxNum = postDataMaxNum; return OLV_RESULT_SUCCESS; } // SetBodyTextMaxLength__Q3_2nn3olv25DownloadPostDataListParamFUi static nnResult SetBodyTextMaxLength(DownloadPostDataListParam* _this, uint32 bodyTextMaxLength) { if(bodyTextMaxLength >= 256) return OLV_RESULT_INVALID_PARAMETER; _this->bodyTextMaxLength = bodyTextMaxLength; return OLV_RESULT_SUCCESS; } // GetRawDataUrl__Q3_2nn3olv25DownloadPostDataListParamCFPcUi static nnResult GetRawDataUrl(DownloadPostDataListParam* _this, char* urlOut, uint32 urlMaxSize); }; static_assert(sizeof(DownloadPostDataListParam) == 0x1000); // parsing functions bool ParseXML_DownloadedPostData(DownloadedPostData& obj, pugi::xml_node& xmlNode); void loadOlivePostAndTopicTypes(); } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp ================================================ #include "nn_olv_UploadCommunityTypes.h" #include <algorithm> namespace nn { namespace olv { sint32 UploadCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam); sint32 UploadCommunityData_AsyncRequest(CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent, UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam ) { sint32 res = UploadCommunityData_AsyncRequestImpl(req, reqUrl, pOutData, pParam); coreinit::OSSignalEvent(requestDoneEvent); return res; } sint32 UploadCommunityData(UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam) { if (!nn::olv::g_IsInitialized) return OLV_RESULT_NOT_INITIALIZED; if (!nn::olv::g_IsOnlineMode) return OLV_RESULT_OFFLINE_MODE_REQUEST; if (!pParam) return OLV_RESULT_INVALID_PTR; if (pOutData) UploadedCommunityData::Clean(pOutData); char requestUrl[512]; if (pParam->flags & UploadCommunityDataParam::FLAG_DELETION) { if (!pParam->communityId) return OLV_RESULT_INVALID_PARAMETER; snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%u.delete", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); } else { if (pParam->communityId) snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%u", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); else snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities", g_DiscoveryResults.apiEndpoint); } CurlRequestHelper req; req.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; coreinit::OSInitEvent(&requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); std::future<sint32> requestRes = std::async(std::launch::async, UploadCommunityData_AsyncRequest, std::ref(req), requestUrl, requestDoneEvent.GetPointer(), pOutData, pParam); coreinit::OSWaitEvent(&requestDoneEvent); return requestRes.get(); } sint32 UploadCommunityData(UploadCommunityDataParam const* pParam) { return UploadCommunityData(nullptr, pParam); } sint32 UploadCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam) { sint32 res = OLV_RESULT_SUCCESS; std::string base64icon; std::string form_name; std::string form_desc; std::string form_searchKey[5]; std::string encodedAppData; uint8* encodedIcon = nullptr; struct curl_httppost* post = nullptr; struct curl_httppost* last = nullptr; try { if (!pParam->iconData.IsNull()) { encodedIcon = new uint8[pParam->iconDataLen]; if (encodedIcon) { sint32 iconEncodeRes = EncodeTGA(pParam->iconData.GetPtr(), pParam->iconDataLen, encodedIcon, pParam->iconDataLen, TGACheckType::CHECK_COMMUNITY_ICON); if (iconEncodeRes <= 0) { delete[] encodedIcon; return OLV_RESULT_NOT_ENOUGH_SIZE; // ? } base64icon = NCrypto::base64Encode(encodedIcon, iconEncodeRes); res = olv_curlformcode_to_error( curl_formadd(&post, &last, CURLFORM_COPYNAME, "icon", CURLFORM_PTRCONTENTS, base64icon.data(), CURLFORM_CONTENTSLENGTH, base64icon.size(), CURLFORM_END) ); if (res < 0) throw std::runtime_error("curl_formadd() error! - icon"); } } if (pParam->titleText[0]) { form_name = StringHelpers::ToUtf8((const uint16be*)pParam->titleText, 127); res = olv_curlformcode_to_error( curl_formadd(&post, &last, CURLFORM_COPYNAME, "name", CURLFORM_PTRCONTENTS, form_name.data(), CURLFORM_CONTENTSLENGTH, form_name.size(), CURLFORM_END) ); if (res < 0) throw std::runtime_error("curl_formadd() error! - name"); } if (pParam->description[0]) { form_desc = StringHelpers::ToUtf8((const uint16be*)pParam->description, 255); res = olv_curlformcode_to_error( curl_formadd(&post, &last, CURLFORM_COPYNAME, "description", CURLFORM_PTRCONTENTS, form_desc.data(), CURLFORM_CONTENTSLENGTH, form_desc.size(), CURLFORM_END) ); if (res < 0) throw std::runtime_error("curl_formadd() error! - description"); } for (int i = 0; i < 5; i++) { if (pParam->searchKeys[i][0]) { form_searchKey[i] = StringHelpers::ToUtf8((const uint16be*)pParam->searchKeys[i], 151); res = olv_curlformcode_to_error( curl_formadd(&post, &last, CURLFORM_COPYNAME, "search_key", CURLFORM_PTRCONTENTS, form_searchKey[i].data(), CURLFORM_CONTENTSLENGTH, form_searchKey[i].size(), CURLFORM_END) ); if (res < 0) throw std::runtime_error("curl_formadd() error! - search_key"); } } if (!pParam->appData.IsNull()) { encodedAppData = NCrypto::base64Encode(pParam->appData.GetPtr(), pParam->appDataLen); if (encodedAppData.size() < pParam->appDataLen) res = OLV_RESULT_FATAL(101); else { res = olv_curlformcode_to_error( curl_formadd(&post, &last, CURLFORM_COPYNAME, "app_data", CURLFORM_PTRCONTENTS, encodedAppData.data(), CURLFORM_CONTENTSLENGTH, encodedAppData.size(), CURLFORM_END) ); if (res < 0) throw std::runtime_error("curl_formadd() error! - app_data"); } } } catch (const std::runtime_error& error) { cemuLog_log(LogType::Force, "Error in multipart curl -> {}", error.what()); curl_formfree(post); if (encodedIcon) delete[] encodedIcon; return res; } curl_easy_setopt(req.getCURL(), CURLOPT_HTTPPOST, post); req.setUseMultipartFormData(true); bool reqResult = req.submitRequest(true); long httpCode = 0; curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); if (encodedIcon) delete[] encodedIcon; if (!reqResult) { cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, httpCode); if (!(httpCode >= 400)) return OLV_RESULT_FAILED_REQUEST; } pugi::xml_document doc; if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) { cemuLog_log(LogType::Force, fmt::format("Invalid XML in community upload response")); return OLV_RESULT_INVALID_XML; } sint32 responseError = CheckOliveResponse(doc); if (responseError < 0) return responseError; if (httpCode != 200) return OLV_RESULT_STATUS(httpCode + 4000); if (pOutData) { std::string_view app_data = doc.select_node("//app_data").node().child_value(); std::string_view community_id = doc.select_node("//community_id").node().child_value(); std::string_view name = doc.select_node("//name").node().child_value(); std::string_view description = doc.select_node("//description").node().child_value(); std::string_view pid = doc.select_node("//pid").node().child_value(); std::string_view icon = doc.select_node("//icon").node().child_value(); if (app_data.size() != 0) { auto app_data_bin = NCrypto::base64Decode(app_data); if (app_data_bin.size() != 0) { memcpy(pOutData->appData, app_data_bin.data(), std::min(size_t(0x400), app_data_bin.size())); pOutData->flags |= UploadedCommunityData::FLAG_HAS_APP_DATA; pOutData->appDataLen = app_data_bin.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } sint64 community_id_val = StringHelpers::ToInt64(community_id, -1); if (community_id_val == -1) return OLV_RESULT_INVALID_INTEGER_FIELD; pOutData->communityId = community_id_val; if (name.size() != 0) { auto name_utf16 = StringHelpers::FromUtf8(name); name_utf16.resize(std::min<size_t>(name_utf16.size(), 128)); if (name_utf16.size() != 0) { for (int i = 0; i < name_utf16.size(); i++) pOutData->titleText[i] = name_utf16.at(i); pOutData->flags |= UploadedCommunityData::FLAG_HAS_TITLE_TEXT; pOutData->titleTextMaxLen = name_utf16.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } if (description.size() != 0) { auto description_utf16 = StringHelpers::FromUtf8(description); description_utf16.resize(std::min<size_t>(description_utf16.size(), 256)); if (description_utf16.size() != 0) { for (int i = 0; i < description_utf16.size(); i++) pOutData->description[i] = description_utf16.at(i); pOutData->flags |= UploadedCommunityData::FLAG_HAS_DESC_TEXT; pOutData->descriptionMaxLen = description_utf16.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } sint64 pid_val = StringHelpers::ToInt64(pid, -1); if (pid_val == -1) return OLV_RESULT_INVALID_INTEGER_FIELD; pOutData->pid = pid_val; if (icon.size() != 0) { auto icon_bin = NCrypto::base64Decode(icon); if (icon_bin.size() != 0) { memcpy(pOutData->iconData, icon_bin.data(), std::min(size_t(0x1002c), icon_bin.size())); pOutData->flags |= UploadedCommunityData::FLAG_HAS_ICON_DATA; pOutData->iconDataSize = icon_bin.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } } return OLV_RESULT_SUCCESS; } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.h ================================================ #pragma once #include "Cemu/ncrypto/ncrypto.h" #include "config/ActiveSettings.h" #include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" namespace nn { namespace olv { class UploadedCommunityData { public: static const inline uint32 FLAG_HAS_TITLE_TEXT = (1 << 0); static const inline uint32 FLAG_HAS_DESC_TEXT = (1 << 1); static const inline uint32 FLAG_HAS_APP_DATA = (1 << 2); static const inline uint32 FLAG_HAS_ICON_DATA = (1 << 3); UploadedCommunityData() { this->titleTextMaxLen = 0; this->appDataLen = 0; this->descriptionMaxLen = 0; this->pid = 0; this->communityId = 0; this->flags = 0; this->iconDataSize = 0; } static UploadedCommunityData* __ctor(UploadedCommunityData* _this) { if (!_this) { assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN return nullptr; } else return new (_this) UploadedCommunityData(); } static UploadedCommunityData* Clean(UploadedCommunityData* data) { data->appDataLen = 0; data->pid = 0; data->titleText[0] = 0; data->description[0] = 0; data->appData[0] = 0; data->titleTextMaxLen = 0; data->iconData[0] = 0; data->descriptionMaxLen = 0; data->communityId = 0; data->flags = 0; data->iconDataSize = 0; return data; } bool TestFlags(uint32 flags) const { return (this->flags & flags) != 0; } static bool __TestFlags(UploadedCommunityData* _this, uint32 flags) { return _this->TestFlags(flags); } uint32 GetCommunityId() const { return this->communityId; } static uint32 __GetCommunityId(UploadedCommunityData* _this) { return _this->GetCommunityId(); } sint32 GetCommunityCode(char* pBuffer, uint32 bufferSize) const { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (bufferSize <= 12) return OLV_RESULT_NOT_ENOUGH_SIZE; uint32 len = 0; if (FormatCommunityCode(pBuffer, &len, this->communityId)) return OLV_RESULT_SUCCESS; return OLV_RESULT_INVALID_PARAMETER; } static sint32 __GetCommunityCode(UploadedCommunityData* _this, char* pBuffer, uint32 bufferSize) { return _this->GetCommunityCode(pBuffer, bufferSize); } uint32 GetOwnerPid() const { return this->pid; } static uint32 __GetOwnerPid(UploadedCommunityData* _this) { return _this->GetOwnerPid(); } sint32 GetTitleText(char16_t* pBuffer, uint32 numChars) { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (numChars) { if (!this->TestFlags(FLAG_HAS_TITLE_TEXT)) return OLV_RESULT_MISSING_DATA; memset(pBuffer, 0, 2 * numChars); uint32 readSize = this->titleTextMaxLen; if (numChars < readSize) readSize = numChars; olv_wstrncpy(pBuffer, this->titleText, readSize); return OLV_RESULT_SUCCESS; } return OLV_RESULT_NOT_ENOUGH_SIZE; } static sint32 __GetTitleText(UploadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) { return _this->GetTitleText(pBuffer, numChars); } sint32 GetDescriptionText(char16_t* pBuffer, uint32 numChars) { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (numChars) { if (!this->TestFlags(FLAG_HAS_DESC_TEXT)) return OLV_RESULT_MISSING_DATA; memset(pBuffer, 0, 2 * numChars); olv_wstrncpy(pBuffer, this->description, numChars); return OLV_RESULT_SUCCESS; } return OLV_RESULT_NOT_ENOUGH_SIZE; } static sint32 __GetDescriptionText(UploadedCommunityData* _this, char16_t* pBuffer, uint32 numChars) { return _this->GetDescriptionText(pBuffer, numChars); } sint32 GetAppData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { uint32 appDataSize = bufferSize; if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (bufferSize) { if (!this->TestFlags(FLAG_HAS_APP_DATA)) return OLV_RESULT_MISSING_DATA; if (this->appDataLen < appDataSize) appDataSize = this->appDataLen; memcpy(pBuffer, this->appData, appDataSize); if (pOutSize) *pOutSize = appDataSize; return OLV_RESULT_SUCCESS; } return OLV_RESULT_NOT_ENOUGH_SIZE; } static sint32 __GetAppData(UploadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { return _this->GetAppData(pBuffer, pOutSize, bufferSize); } uint32 GetAppDataSize() const { if (this->TestFlags(FLAG_HAS_APP_DATA)) return this->appDataLen; return 0; } static uint32 __GetAppDataSize(UploadedCommunityData* _this) { return _this->GetAppDataSize(); } sint32 GetIconData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (bufferSize < sizeof(this->iconData)) return OLV_RESULT_NOT_ENOUGH_SIZE; if (!this->TestFlags(FLAG_HAS_ICON_DATA)) return OLV_RESULT_MISSING_DATA; sint32 decodeRes = DecodeTGA(this->iconData, this->iconDataSize, pBuffer, bufferSize, TGACheckType::CHECK_COMMUNITY_ICON); if (decodeRes >= 0) { if (pOutSize) *pOutSize = (uint32)decodeRes; return OLV_RESULT_SUCCESS; } if (pOutSize) *pOutSize = 0; if (decodeRes == -1) cemuLog_log(LogType::Force, "OLIVE - icon uncompress failed.\n"); else if (decodeRes == -2) cemuLog_log(LogType::Force, "OLIVE - icon decode error. NOT TGA.\n"); return OLV_RESULT_INVALID_TEXT_FIELD; } static sint32 __GetIconData(UploadedCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { return _this->GetIconData(pBuffer, pOutSize, bufferSize); } public: uint32be flags; uint32be communityId; uint32be pid; char16_t titleText[128]; uint32be titleTextMaxLen; char16_t description[256]; uint32be descriptionMaxLen; uint8 appData[1024]; uint32be appDataLen; uint8 iconData[65580]; uint32be iconDataSize; uint8 unk[6328]; }; static_assert(sizeof(nn::olv::UploadedCommunityData) == 0x12000, "sizeof(nn::olv::UploadedCommunityData) != 0x12000"); class UploadCommunityDataParam { public: static const inline uint32 FLAG_DELETION = (1 << 0); UploadCommunityDataParam() { this->appDataLen = 0; this->communityId = 0; this->titleId = 0; this->iconData = MEMPTR<uint8>(nullptr); this->appData = MEMPTR<uint8>(nullptr); this->iconDataLen = 0; this->flags = 0; memset(this->titleText, 0, sizeof(this->titleText)); memset(this->description, 0, sizeof(this->description)); for (int i = 0; i < 5; i++) memset(this->searchKeys[i], 0, sizeof(this->searchKeys[0])); } static UploadCommunityDataParam* __ctor(UploadCommunityDataParam* _this) { if (!_this) { assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN return nullptr; } else return new (_this) UploadCommunityDataParam(); } sint32 SetFlags(uint32 flags) { this->flags = flags; return OLV_RESULT_SUCCESS; } static sint32 __SetFlags(UploadCommunityDataParam* _this, uint32 flags) { return _this->SetFlags(flags); } sint32 SetCommunityId(uint32 communityId) { if (communityId == -1) return OLV_RESULT_INVALID_PARAMETER; this->communityId = communityId; return OLV_RESULT_SUCCESS; } static sint32 __SetCommunityId(UploadCommunityDataParam* _this, uint32 communityId) { return _this->SetCommunityId(communityId); } sint32 SetAppData(MEMPTR<uint8> pBuffer, uint32 bufferSize) { if (!pBuffer.IsNull()) { if (bufferSize - 1 >= 0x400) return OLV_RESULT_NOT_ENOUGH_SIZE; this->appData = pBuffer; this->appDataLen = bufferSize; } else { this->appData = MEMPTR<uint8>(nullptr); this->appDataLen = 0; } return OLV_RESULT_SUCCESS; } static sint32 __SetAppData(UploadCommunityDataParam* _this, MEMPTR<uint8> pBuffer, uint32 bufferSize) { return _this->SetAppData(pBuffer, bufferSize); } sint32 SetTitleText(char16_t const* pText) { if (pText) return olv_copy_wstr(this->titleText, pText, 127, 128); memset(this->titleText, 0, sizeof(this->titleText)); return OLV_RESULT_SUCCESS; } static sint32 __SetTitleText(UploadCommunityDataParam* _this, char16_t const* pText) { return _this->SetTitleText(pText); } sint32 SetDescriptionText(char16_t const* pText) { if (pText) return olv_copy_wstr(this->description, pText, 255, 256); memset(this->description, 0, sizeof(this->description)); return OLV_RESULT_SUCCESS; } static sint32 __SetDescriptionText(UploadCommunityDataParam* _this, char16_t const* pText) { return _this->SetDescriptionText(pText); } sint32 SetIconData(MEMPTR<uint8> pBuffer, uint32 bufferSize) { if (!pBuffer.IsNull()) { if (bufferSize) { if (bufferSize - 0x10012 < 0x1B) { if (CheckTGA(pBuffer.GetPtr(), bufferSize, TGACheckType::CHECK_COMMUNITY_ICON)) { this->iconData = pBuffer; this->iconDataLen = bufferSize; return OLV_RESULT_SUCCESS; } else { cemuLog_log(LogType::Force, "OLIVE - SetIconData: TGA Check Failed.\n"); return OLV_RESULT_INVALID_DATA; } } else return OLV_RESULT_NOT_ENOUGH_SIZE; } else return OLV_RESULT_NOT_ENOUGH_SIZE; } else { this->iconData = MEMPTR<uint8>(nullptr); this->iconDataLen = 0; return OLV_RESULT_SUCCESS; } } static sint32 __SetIconData(UploadCommunityDataParam* _this, MEMPTR<uint8> pBuffer, uint32 bufferSize) { return _this->SetIconData(pBuffer, bufferSize); } public: uint32be flags; sint32be ___padding_0c; uint64be titleId; uint32be communityId; char16_t titleText[128]; char16_t description[256]; char16_t searchKeys[5][152]; MEMPTR<uint8_t> appData; uint32be appDataLen; MEMPTR<uint8_t> iconData; uint32be iconDataLen; char unk3[1772]; }; static_assert(sizeof(nn::olv::UploadCommunityDataParam) == 0x1000, "sizeof(nn::olv::UploadCommunityDataParam) != 0x1000"); sint32 UploadCommunityData(UploadCommunityDataParam const* pParam); sint32 UploadCommunityData(UploadedCommunityData* pOutData, UploadCommunityDataParam const* pParam); static void loadOliveUploadCommunityTypes() { cafeExportRegisterFunc(UploadedCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv21UploadedCommunityDataFv", LogType::NN_OLV); cafeExportRegisterFunc(UploadedCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv21UploadedCommunityDataCFUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadedCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(UploadedCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv21UploadedCommunityDataCFPcUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadedCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(UploadedCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv21UploadedCommunityDataCFPwUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadedCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv21UploadedCommunityDataCFPwUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadedCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv21UploadedCommunityDataCFPUcPUiUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadedCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv21UploadedCommunityDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(UploadedCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv21UploadedCommunityDataCFPUcPUiUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadCommunityDataParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv24UploadCommunityDataParamFv", LogType::NN_OLV); cafeExportRegisterFunc(UploadCommunityDataParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv24UploadCommunityDataParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadCommunityDataParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv24UploadCommunityDataParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadCommunityDataParam::__SetAppData, "nn_olv", "SetAppData__Q3_2nn3olv24UploadCommunityDataParamFPCUcUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadCommunityDataParam::__SetTitleText, "nn_olv", "SetTitleText__Q3_2nn3olv24UploadCommunityDataParamFPCw", LogType::NN_OLV); cafeExportRegisterFunc(UploadCommunityDataParam::__SetDescriptionText, "nn_olv", "SetDescriptionText__Q3_2nn3olv24UploadCommunityDataParamFPCw", LogType::NN_OLV); cafeExportRegisterFunc(UploadCommunityDataParam::__SetIconData, "nn_olv", "SetIconData__Q3_2nn3olv24UploadCommunityDataParamFPCUcUi", LogType::NN_OLV); cafeExportRegisterFunc((sint32(*)(UploadCommunityDataParam const*))UploadCommunityData, "nn_olv", "UploadCommunityData__Q2_2nn3olvFPCQ3_2nn3olv24UploadCommunityDataParam", LogType::NN_OLV); cafeExportRegisterFunc((sint32(*)(UploadedCommunityData *, UploadCommunityDataParam const*))UploadCommunityData, "nn_olv", "UploadCommunityData__Q2_2nn3olvFPQ3_2nn3olv21UploadedCommunityDataPCQ3_2nn3olv24UploadCommunityDataParam", LogType::NN_OLV); } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp ================================================ #include "nn_olv_UploadFavoriteTypes.h" #include <algorithm> #include <cstddef> namespace nn { namespace olv { sint32 UploadFavoriteToCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam ); sint32 UploadFavoriteToCommunityData_AsyncRequest(CurlRequestHelper& req, const char* reqUrl, coreinit::OSEvent* requestDoneEvent, UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam ) { sint32 res = UploadFavoriteToCommunityData_AsyncRequestImpl(req, reqUrl, pOutData, pParam); coreinit::OSSignalEvent(requestDoneEvent); return res; } sint32 UploadFavoriteToCommunityData(UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam) { if (!nn::olv::g_IsInitialized) return OLV_RESULT_NOT_INITIALIZED; if (!nn::olv::g_IsOnlineMode) return OLV_RESULT_OFFLINE_MODE_REQUEST; if (!pParam) return OLV_RESULT_INVALID_PTR; if (pOutData) UploadedFavoriteToCommunityData::Clean(pOutData); char requestUrl[512]; if (pParam->flags & UploadFavoriteToCommunityDataParam::FLAG_DELETION) snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%u.unfavorite", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); else snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%u.favorite", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); CurlRequestHelper req; req.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; coreinit::OSInitEvent(&requestDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); std::future<sint32> requestRes = std::async(std::launch::async, UploadFavoriteToCommunityData_AsyncRequest, std::ref(req), requestUrl, requestDoneEvent.GetPointer(), pOutData, pParam); coreinit::OSWaitEvent(&requestDoneEvent); return requestRes.get(); } sint32 UploadFavoriteToCommunityData(const UploadFavoriteToCommunityDataParam* pParam) { return UploadFavoriteToCommunityData(nullptr, pParam); } sint32 UploadFavoriteToCommunityData_AsyncRequestImpl(CurlRequestHelper& req, const char* reqUrl, UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam ) { bool reqResult = req.submitRequest(true); long httpCode = 0; curl_easy_getinfo(req.getCURL(), CURLINFO_RESPONSE_CODE, &httpCode); if (!reqResult) { cemuLog_log(LogType::Force, "Failed request: {} ({})", reqUrl, httpCode); if (!(httpCode >= 400)) return OLV_RESULT_FAILED_REQUEST; } pugi::xml_document doc; if (!doc.load_buffer(req.getReceivedData().data(), req.getReceivedData().size())) { cemuLog_log(LogType::Force, fmt::format("Invalid XML in community favorite upload response")); return OLV_RESULT_INVALID_XML; } sint32 responseError = CheckOliveResponse(doc); if (responseError < 0) return responseError; if (httpCode != 200) return OLV_RESULT_STATUS(httpCode + 4000); if (pOutData) { std::string_view app_data = doc.select_node("//app_data").node().child_value(); std::string_view community_id = doc.select_node("//community_id").node().child_value(); std::string_view name = doc.select_node("//name").node().child_value(); std::string_view description = doc.select_node("//description").node().child_value(); std::string_view pid = doc.select_node("//pid").node().child_value(); std::string_view icon = doc.select_node("//icon").node().child_value(); if (app_data.size() != 0) { auto app_data_bin = NCrypto::base64Decode(app_data); if (app_data_bin.size() != 0) { memcpy(pOutData->appData, app_data_bin.data(), std::min(size_t(0x400), app_data_bin.size())); pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_APP_DATA; pOutData->appDataLen = app_data_bin.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } sint64 community_id_val = StringHelpers::ToInt64(community_id, -1); if (community_id_val == -1) return OLV_RESULT_INVALID_INTEGER_FIELD; pOutData->communityId = community_id_val; if (name.size() != 0) { auto name_utf16 = StringHelpers::FromUtf8(name); name_utf16.resize(std::min<size_t>(name_utf16.size(), 128)); if (name_utf16.size() != 0) { for (int i = 0; i < name_utf16.size(); i++) pOutData->titleText[i] = name_utf16.at(i); pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_TITLE_TEXT; pOutData->titleTextMaxLen = name_utf16.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } if (description.size() != 0) { auto description_utf16 = StringHelpers::FromUtf8(description); description_utf16.resize(std::min<size_t>(description_utf16.size(), 256)); if (description_utf16.size() != 0) { for (int i = 0; i < description_utf16.size(); i++) pOutData->description[i] = description_utf16.at(i); pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_DESC_TEXT; pOutData->descriptionMaxLen = description_utf16.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } sint64 pid_val = StringHelpers::ToInt64(pid, -1); if (pid_val == -1) return OLV_RESULT_INVALID_INTEGER_FIELD; pOutData->pid = pid_val; if (icon.size() != 0) { auto icon_bin = NCrypto::base64Decode(icon); if (icon_bin.size() != 0) { memcpy(pOutData->iconData, icon_bin.data(), std::min(size_t(0x1002c), icon_bin.size())); pOutData->flags |= UploadedFavoriteToCommunityData::FLAG_HAS_ICON_DATA; pOutData->iconDataSize = icon_bin.size(); } else return OLV_RESULT_INVALID_TEXT_FIELD; } } return OLV_RESULT_SUCCESS; } } } ================================================ FILE: src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.h ================================================ #pragma once #include "Cemu/ncrypto/ncrypto.h" #include "config/ActiveSettings.h" #include "Cafe/OS/libs/nn_olv/nn_olv_Common.h" namespace nn { namespace olv { class UploadedFavoriteToCommunityData { public: static const inline uint32 FLAG_HAS_TITLE_TEXT = (1 << 0); static const inline uint32 FLAG_HAS_DESC_TEXT = (1 << 1); static const inline uint32 FLAG_HAS_APP_DATA = (1 << 2); static const inline uint32 FLAG_HAS_ICON_DATA = (1 << 3); UploadedFavoriteToCommunityData() { this->titleTextMaxLen = 0; this->appDataLen = 0; this->descriptionMaxLen = 0; this->pid = 0; this->communityId = 0; this->flags = 0; this->iconDataSize = 0; } static UploadedFavoriteToCommunityData* __ctor(UploadedFavoriteToCommunityData* _this) { if (!_this) { assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN return nullptr; } else return new (_this) UploadedFavoriteToCommunityData(); } static UploadedFavoriteToCommunityData* Clean(UploadedFavoriteToCommunityData* data) { data->appDataLen = 0; data->pid = 0; data->titleText[0] = 0; data->description[0] = 0; data->appData[0] = 0; data->titleTextMaxLen = 0; data->iconData[0] = 0; data->descriptionMaxLen = 0; data->communityId = 0; data->flags = 0; data->iconDataSize = 0; return data; } bool TestFlags(uint32 flags) const { return (this->flags & flags) != 0; } static bool __TestFlags(UploadedFavoriteToCommunityData* _this, uint32 flags) { return _this->TestFlags(flags); } uint32 GetCommunityId() const { return this->communityId; } static uint32 __GetCommunityId(UploadedFavoriteToCommunityData* _this) { return _this->GetCommunityId(); } sint32 GetCommunityCode(char* pBuffer, uint32 bufferSize) const { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (bufferSize <= 12) return OLV_RESULT_NOT_ENOUGH_SIZE; uint32 len = 0; if (FormatCommunityCode(pBuffer, &len, this->communityId)) return OLV_RESULT_SUCCESS; return OLV_RESULT_INVALID_PARAMETER; } static sint32 __GetCommunityCode(UploadedFavoriteToCommunityData* _this, char* pBuffer, uint32 bufferSize) { return _this->GetCommunityCode(pBuffer, bufferSize); } uint32 GetOwnerPid() const { return this->pid; } static uint32 __GetOwnerPid(UploadedFavoriteToCommunityData* _this) { return _this->GetOwnerPid(); } sint32 GetTitleText(char16_t* pBuffer, uint32 numChars) { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (numChars) { if (!this->TestFlags(FLAG_HAS_TITLE_TEXT)) return OLV_RESULT_MISSING_DATA; memset(pBuffer, 0, 2 * numChars); uint32 readSize = this->titleTextMaxLen; if (numChars < readSize) readSize = numChars; olv_wstrncpy(pBuffer, this->titleText, readSize); return OLV_RESULT_SUCCESS; } return OLV_RESULT_NOT_ENOUGH_SIZE; } static sint32 __GetTitleText(UploadedFavoriteToCommunityData* _this, char16_t* pBuffer, uint32 numChars) { return _this->GetTitleText(pBuffer, numChars); } sint32 GetDescriptionText(char16_t* pBuffer, uint32 numChars) { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (numChars) { if (!this->TestFlags(FLAG_HAS_DESC_TEXT)) return OLV_RESULT_MISSING_DATA; memset(pBuffer, 0, 2 * numChars); olv_wstrncpy(pBuffer, this->description, numChars); return OLV_RESULT_SUCCESS; } return OLV_RESULT_NOT_ENOUGH_SIZE; } static sint32 __GetDescriptionText(UploadedFavoriteToCommunityData* _this, char16_t* pBuffer, uint32 numChars) { return _this->GetDescriptionText(pBuffer, numChars); } sint32 GetAppData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { uint32 appDataSize = bufferSize; if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (bufferSize) { if (!this->TestFlags(FLAG_HAS_APP_DATA)) return OLV_RESULT_MISSING_DATA; if (this->appDataLen < appDataSize) appDataSize = this->appDataLen; memcpy(pBuffer, this->appData, appDataSize); if (pOutSize) *pOutSize = appDataSize; return OLV_RESULT_SUCCESS; } return OLV_RESULT_NOT_ENOUGH_SIZE; } static sint32 __GetAppData(UploadedFavoriteToCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { return _this->GetAppData(pBuffer, pOutSize, bufferSize); } uint32 GetAppDataSize() const { if (this->TestFlags(FLAG_HAS_APP_DATA)) return this->appDataLen; return 0; } static uint32 __GetAppDataSize(UploadedFavoriteToCommunityData* _this) { return _this->GetAppDataSize(); } sint32 GetIconData(uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { if (!pBuffer) return OLV_RESULT_INVALID_PTR; if (bufferSize < sizeof(this->iconData)) return OLV_RESULT_NOT_ENOUGH_SIZE; if (!this->TestFlags(FLAG_HAS_ICON_DATA)) return OLV_RESULT_MISSING_DATA; sint32 decodeRes = DecodeTGA(this->iconData, this->iconDataSize, pBuffer, bufferSize, TGACheckType::CHECK_COMMUNITY_ICON); if (decodeRes >= 0) { if (pOutSize) *pOutSize = (uint32)decodeRes; return OLV_RESULT_SUCCESS; } if (pOutSize) *pOutSize = 0; if (decodeRes == -1) cemuLog_log(LogType::Force, "OLIVE - icon uncompress failed.\n"); else if (decodeRes == -2) cemuLog_log(LogType::Force, "OLIVE - icon decode error. NOT TGA.\n"); return OLV_RESULT_INVALID_TEXT_FIELD; } static sint32 __GetIconData(UploadedFavoriteToCommunityData* _this, uint8* pBuffer, uint32be* pOutSize, uint32 bufferSize) { return _this->GetIconData(pBuffer, pOutSize, bufferSize); } public: uint32be flags; uint32be communityId; uint32be pid; char16_t titleText[128]; uint32be titleTextMaxLen; char16_t description[256]; uint32be descriptionMaxLen; uint8 appData[1024]; uint32be appDataLen; uint8 iconData[65580]; uint32be iconDataSize; uint8 unk[6328]; }; static_assert(sizeof(nn::olv::UploadedFavoriteToCommunityData) == 0x12000, "sizeof(nn::olv::UploadedFavoriteToCommunityData) != 0x12000"); class UploadFavoriteToCommunityDataParam { public: static const inline uint32 FLAG_DELETION = (1 << 0); UploadFavoriteToCommunityDataParam() { this->communityId = 0; this->flags = 0; } static UploadFavoriteToCommunityDataParam* __ctor(UploadFavoriteToCommunityDataParam* _this) { if (!_this) { assert_dbg(); // DO NOT CONTINUE, SHOULD NEVER HAPPEN return nullptr; } else return new (_this) UploadFavoriteToCommunityDataParam(); } sint32 SetFlags(uint32 flags) { this->flags = flags; return OLV_RESULT_SUCCESS; } static sint32 __SetFlags(UploadFavoriteToCommunityDataParam* _this, uint32 flags) { return _this->SetFlags(flags); } sint32 SetCommunityCode(const char* pBuffer) { if (strnlen(pBuffer, 13) != 12) return OLV_RESULT_INVALID_TEXT_FIELD; uint32_t id; if (GetCommunityIdFromCode(&id, pBuffer)) { this->communityId = id; return OLV_RESULT_SUCCESS; } return OLV_RESULT_STATUS(1901); } static sint32 __SetCommunityCode(UploadFavoriteToCommunityDataParam* _this, char* pBuffer) { return _this->SetCommunityCode(pBuffer); } sint32 SetCommunityId(uint32 communityId) { if (communityId == -1) return OLV_RESULT_INVALID_PARAMETER; this->communityId = communityId; return OLV_RESULT_SUCCESS; } static sint32 __SetCommunityId(UploadFavoriteToCommunityDataParam* _this, uint32 communityId) { return _this->SetCommunityId(communityId); } public: uint32be flags; uint32be communityId; uint8 unk[54]; // Unused }; static_assert(sizeof(nn::olv::UploadFavoriteToCommunityDataParam) == 64, "sizeof(nn::olv::UploadFavoriteToCommunityDataParam) != 64"); sint32 UploadFavoriteToCommunityData(const UploadFavoriteToCommunityDataParam* pParam); sint32 UploadFavoriteToCommunityData(UploadedFavoriteToCommunityData* pOutData, const UploadFavoriteToCommunityDataParam* pParam); static void loadOliveUploadFavoriteTypes() { cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__ctor, "nn_olv", "__ct__Q3_2nn3olv31UploadedFavoriteToCommunityDataFv", LogType::NN_OLV); cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__TestFlags, "nn_olv", "TestFlags__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetCommunityId, "nn_olv", "GetCommunityId__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetCommunityCode, "nn_olv", "GetCommunityCode__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPcUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetOwnerPid, "nn_olv", "GetOwnerPid__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetTitleText, "nn_olv", "GetTitleText__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPwUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetDescriptionText, "nn_olv", "GetDescriptionText__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPwUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetAppData, "nn_olv", "GetAppData__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPUcPUiUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetAppDataSize, "nn_olv", "GetAppDataSize__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFv", LogType::NN_OLV); cafeExportRegisterFunc(UploadedFavoriteToCommunityData::__GetIconData, "nn_olv", "GetIconData__Q3_2nn3olv31UploadedFavoriteToCommunityDataCFPUcPUiUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__ctor, "nn_olv", "__ct__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFv", LogType::NN_OLV); cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetFlags, "nn_olv", "SetFlags__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFUi", LogType::NN_OLV); cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetCommunityCode, "nn_olv", "SetCommunityCode__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFPCc", LogType::NN_OLV); cafeExportRegisterFunc(UploadFavoriteToCommunityDataParam::__SetCommunityId, "nn_olv", "SetCommunityId__Q3_2nn3olv34UploadFavoriteToCommunityDataParamFUi", LogType::NN_OLV); cafeExportRegisterFunc((sint32(*)(const UploadFavoriteToCommunityDataParam*))UploadFavoriteToCommunityData, "nn_olv", "UploadFavoriteToCommunityData__Q2_2nn3olvFPCQ3_2nn3olv34UploadFavoriteToCommunityDataParam", LogType::NN_OLV); cafeExportRegisterFunc((sint32(*)(UploadedFavoriteToCommunityData*, const UploadFavoriteToCommunityDataParam*))UploadFavoriteToCommunityData, "nn_olv", "UploadFavoriteToCommunityData__Q2_2nn3olvFPQ3_2nn3olv31UploadedFavoriteToCommunityDataPCQ3_2nn3olv34UploadFavoriteToCommunityDataParam", LogType::NN_OLV); } } } ================================================ FILE: src/Cafe/OS/libs/nn_pdm/nn_pdm.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "config/ActiveSettings.h" #include "Cafe/TitleList/TitleId.h" #include "Cafe/IOSU/PDM/iosu_pdm.h" #include "nn_pdm.h" namespace nn { namespace pdm { using PlayDiary = iosu::pdm::PlayDiaryEntry; uint32 GetPlayDiaryMaxLength(uint32be* count) { *count = iosu::pdm::NUM_PLAY_DIARY_ENTRIES_MAX; return 0; } uint32 GetPlayStatsMaxLength(uint32be* count) { *count = iosu::pdm::NUM_PLAY_STATS_ENTRIES; return 0; } uint32 GetPlayDiary(uint32be* ukn1, PlayDiary* playDiary, uint32 accountSlot, uint32 maxNumEntries) { uint32 numReadEntries = iosu::pdm::GetDiaryEntries(accountSlot, playDiary, maxNumEntries); *ukn1 = numReadEntries; return 0; } class : public COSModule { public: std::string_view GetName() override { return "nn_pdm"; } void RPLMapped() override { cafeExportRegisterFunc(GetPlayDiaryMaxLength, "nn_pdm", "GetPlayDiaryMaxLength__Q2_2nn3pdmFPi", LogType::NN_PDM); cafeExportRegisterFunc(GetPlayStatsMaxLength, "nn_pdm", "GetPlayStatsMaxLength__Q2_2nn3pdmFPi", LogType::NN_PDM); cafeExportRegisterFunc(GetPlayDiary, "nn_pdm", "GetPlayDiary__Q2_2nn3pdmFPiPQ3_2nn3pdm9PlayDiaryiT3", LogType::NN_PDM); }; }s_COSnnPdmModule; COSModule* GetModule() { return &s_COSnnPdmModule; } } } ================================================ FILE: src/Cafe/OS/libs/nn_pdm/nn_pdm.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace nn::pdm { COSModule* GetModule(); }; ================================================ FILE: src/Cafe/OS/libs/nn_save/nn_save.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "nn_save.h" #include "Cafe/OS/libs/nn_acp/nn_acp.h" #include "Cafe/OS/libs/nn_act/nn_act.h" #include <filesystem> #include <sstream> #include "config/ActiveSettings.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_FS.h" #include "Cafe/CafeSystem.h" #include "Cafe/Filesystem/fsc.h" #define SAVE_STATUS_OK ((FSStatus)FS_RESULT::SUCCESS) #define SAVE_MAX_PATH_SIZE (FSA_CMD_PATH_MAX_LENGTH) #define SAVE_ACCOUNT_ID_MIN (1) #define SAVE_ACCOUNT_ID_MAX (0xC) #define SAVE_UNIQUE_TO_TITLE_ID(_unique_) (((((uint64)_unique_ >> 24ULL) | 0x50000) << 32ULL) | ((_unique_ << 8) | 0x10000000)) #define SAVE_UNIQUE_TO_TITLE_ID_VARIATION(_unique_,_variation_) (((((uint64)_unique_ >> 24ULL) | 0x50000 ) << 32) | ((_unique_ << 8) | 0x10000000 | _variation_)) #define SAVE_UNIQUE_DEMO_TO_TITLE_ID(_unique_) (((((uint64)_unique_ >> 24ULL) | 0x50002) << 32ULL) | ((_unique_ << 8) | 0x10000000)) #define SAVE_UNIQUE_DEMO_TO_TITLE_ID_VARIATION(_unique_,_variation_) (((((uint64)_unique_ >> 24ULL) | 0x50002 ) << 32ULL) | ((_unique_ << 8) | 0x10000000 | _variation_)) namespace nn { namespace save { typedef FSStatus SAVEStatus; typedef struct { bool initialized; coreinit::OSMutex mutex; coreinit::FSClient_t fsClient; coreinit::FSCmdBlock_t fsCmdBlock; uint32 persistentIdCache[0xC]; }nn_save_t; SysAllocator<nn_save_t> g_nn_save; uint32 GetPersistentIdFromLocalCache(uint8 accountSlot) { accountSlot--; if (accountSlot >= 0xC) return 0; return g_nn_save->persistentIdCache[accountSlot]; } void SetPersistentIdToLocalCache(uint8 accountSlot, uint32 persistentId) { accountSlot--; if (accountSlot >= 0xC) return; g_nn_save->persistentIdCache[accountSlot] = persistentId; } bool GetPersistentIdEx(uint8 accountSlot, uint32* persistentId) { if (accountSlot == 0xFF) { *persistentId = 0; return true; } const uint32 result = GetPersistentIdFromLocalCache(accountSlot); *persistentId = result; return result != 0; } bool GetCurrentTitleApplicationBox(nn::acp::ACPDeviceType* deviceType) { if (deviceType) { *deviceType = nn::acp::ACPDeviceType::InternalDeviceType; return true; } return false; } void UpdateSaveTimeStamp(uint32 persistentId) { nn::acp::ACPDeviceType deviceType; if (GetCurrentTitleApplicationBox(&deviceType)) ACPUpdateSaveTimeStamp(persistentId, CafeSystem::GetForegroundTitleId(), deviceType); } SAVEStatus ConvertACPToSaveStatus(acp::ACPStatus status) { cemu_assert_debug(status == 0); // todo return 0; } bool GetAbsoluteFullPath(uint32 persistentId, const char* subDir, char* outPath) { int size; if (persistentId != 0) { if (subDir) size = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/save/%08x/%s", persistentId, subDir); else size = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/save/%08x/", persistentId); } else { if (subDir) size = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/save/common/%s", subDir); else size = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/save/common/"); } if (size < SAVE_MAX_PATH_SIZE - 1) return true; return false; } FS_RESULT GetAbsoluteFullPathOtherApplication(uint32 persistentId, uint64 titleId, const char* subDir, char* outPath) { uint32be applicationBox; if(acp::ACPGetApplicationBox(&applicationBox, titleId) != acp::ACPStatus::SUCCESS) return FS_RESULT::NOT_FOUND; sint32 written = 0; if(applicationBox == 3) { if(persistentId != 0) { if (subDir) written = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/storage_mlc01/usr/save/%08x/%08x/user/%08x/%s", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, subDir); else written = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/storage_mlc01/usr/save/%08x/%08x/user/%08x/", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId); } else { if (subDir) written = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/storage_mlc01/usr/save/%08x/%08x/user/common/%s", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), subDir); else written = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/storage_mlc01/usr/save/%08x/%08x/user/common/", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); } } else if(applicationBox == 4) { cemu_assert_unimplemented(); } else return FS_RESULT::NOT_FOUND; if (written < SAVE_MAX_PATH_SIZE - 1) return FS_RESULT::SUCCESS; cemu_assert_suspicious(); return FS_RESULT::FATAL_ERROR; } struct AsyncResultData { MEMPTR<coreinit::OSEvent> event; betype<SAVEStatus> returnStatus; }; void SaveAsyncFinishCallback(PPCInterpreter_t* hCPU); struct AsyncToSyncWrapper : public FSAsyncParams { AsyncToSyncWrapper() { coreinit::OSInitEvent(&_event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); ioMsgQueue = nullptr; userContext = &_result; userCallback = RPLLoader_MakePPCCallable(SaveAsyncFinishCallback); _result.returnStatus = 0; _result.event = &_event; } ~AsyncToSyncWrapper() { } FSAsyncParams* GetAsyncParams() { return this; } SAVEStatus GetResult() { return _result.returnStatus; } void WaitForEvent() { coreinit::OSWaitEvent(&_event); } private: coreinit::OSEvent _event; AsyncResultData _result; }; void SaveAsyncFinishCallback(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(client, coreinit::FSClient_t, 0); ppcDefineParamMEMPTR(block, coreinit::FSCmdBlock_t, 1); ppcDefineParamU32(returnStatus, 2); ppcDefineParamMEMPTR(userContext, void, 3); MEMPTR<AsyncResultData> resultPtr{ userContext }; resultPtr->returnStatus = returnStatus; coreinit::OSSignalEvent(resultPtr->event); osLib_returnFromFunction(hCPU, 0); } SAVEStatus SAVEMountSaveDir() { acp::ACPStatus status = acp::ACPMountSaveDir(); return ConvertACPToSaveStatus(status); } SAVEStatus SAVEUnmountSaveDir() { return ConvertACPToSaveStatus(acp::ACPUnmountSaveDir()); } SAVEStatus SAVEInit() { const uint64 titleId = CafeSystem::GetForegroundTitleId(); if (!g_nn_save->initialized) { OSInitMutexEx(&g_nn_save->mutex, nullptr); act::Initialize(); coreinit::FSAddClientEx(&g_nn_save->fsClient, 0, 0); coreinit::FSInitCmdBlock(&g_nn_save->fsCmdBlock); for(uint8 accountId = SAVE_ACCOUNT_ID_MIN; accountId <= SAVE_ACCOUNT_ID_MAX; ++accountId) { uint32 persistentId = act::GetPersistentIdEx(accountId); SetPersistentIdToLocalCache(accountId, persistentId); } SAVEMountSaveDir(); g_nn_save->initialized = true; uint32 high = GetTitleIdHigh(titleId) & (~0xC); uint32 low = GetTitleIdLow(titleId); sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; char path[256]; sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); iosu::acp::CreateSaveMetaFiles(ActiveSettings::GetPersistentId(), titleId); } return SAVE_STATUS_OK; } SAVEStatus SAVEInitSaveDir(uint8 accountSlot) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { acp::ACPStatus status = nn::acp::ACPCreateSaveDir(persistentId, iosu::acp::ACPDeviceType::InternalDeviceType); result = ConvertACPToSaveStatus(status); } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEOpenDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEOpenDirOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); } SAVEStatus SAVEOpenDirOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); } SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); } SAVEStatus SAVEOpenDirOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); } SAVEStatus SAVEOpenFileAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, outFileHandle, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEOpenFileOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { if (strcmp(mode, "r") != 0) return (SAVEStatus)(FS_RESULT::PERMISSION_ERROR); SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, outFileHandle, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEOpenFileOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVEOpenFileOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, asyncParams); } SAVEStatus SAVEOpenFileOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling); } SAVEStatus SAVEOpenFileOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, asyncParams); } SAVEStatus SAVEOpenFileOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling); } SAVEStatus SAVEGetStatAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParams*)asyncParams); // FSGetStatAsync(...) } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEGetStat(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVEGetStatOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParams*)asyncParams); // FSGetStatAsync(...) } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEGetStatOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVEGetStatOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } SAVEStatus SAVEGetStatOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); } SAVEStatus SAVEGetStatOtherDemoApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } SAVEStatus SAVEGetStatOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } SAVEStatus SAVEGetStatOtherDemoApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } SAVEStatus SAVEGetStatOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { //peterBreak(); uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); } SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullOldPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) { char fullNewPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, asyncParams); } } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVERename(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVERenameAsync(client, block, accountSlot, oldPath, newPath, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, outFileHandle, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVEGetSharedDataTitlePath(uint64 titleId, const char* dataFileName, char* output, sint32 outputLength) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); sint32 written = snprintf(output, outputLength, "/vol/storage_mlc01/sys/title/%08x/%08x/content/%s", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), dataFileName); if (written >= 0 && written < outputLength) result = (FSStatus)FS_RESULT::SUCCESS; cemu_assert_debug(result != (FSStatus)(FS_RESULT::FATAL_ERROR)); return result; } SAVEStatus SAVEGetSharedSaveDataPath(uint64 titleId, const char* dataFileName, char* output, uint32 outputLength) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); int written = snprintf(output, outputLength, "/vol/storage_mlc01/usr/save/%08x/%08x/user/common/%s", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), dataFileName); if (written >= 0 && written < (sint32)outputLength) result = (FSStatus)FS_RESULT::SUCCESS; cemu_assert_debug(result != (FSStatus)(FS_RESULT::FATAL_ERROR)); return result; } SAVEStatus SAVEChangeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) result = coreinit::FSChangeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEChangeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } SAVEStatus SAVEFlushQuotaAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) { result = coreinit::FSFlushQuotaAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); // if(OSGetUPID != 0xF) UpdateSaveTimeStamp(persistentId); } } else result = (FSStatus)FS_RESULT::NOT_FOUND; OSUnlockMutex(&g_nn_save->mutex); return result; } SAVEStatus SAVEFlushQuota(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling) { StackAllocator<AsyncToSyncWrapper> asyncData; SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, asyncData->GetAsyncParams()); if (status != (FSStatus)FS_RESULT::SUCCESS) return status; asyncData->WaitForEvent(); return asyncData->GetResult(); } void ResetToDefaultState() { if(g_nn_save->initialized) { SAVEUnmountSaveDir(); g_nn_save->initialized = false; } } class : public COSModule { public: std::string_view GetName() override { return "nn_save"; } void RPLMapped() override { cafeExportRegister("nn_save", SAVEInit, LogType::Save); cafeExportRegister("nn_save", SAVEInitSaveDir, LogType::Save); cafeExportRegister("nn_save", SAVEGetSharedDataTitlePath, LogType::Save); cafeExportRegister("nn_save", SAVEGetSharedSaveDataPath, LogType::Save); cafeExportRegister("nn_save", SAVEGetFreeSpaceSize, LogType::Save); cafeExportRegister("nn_save", SAVEGetFreeSpaceSizeAsync, LogType::Save); cafeExportRegister("nn_save", SAVEMakeDir, LogType::Save); cafeExportRegister("nn_save", SAVEMakeDirAsync, LogType::Save); cafeExportRegister("nn_save", SAVERemove, LogType::Save); cafeExportRegister("nn_save", SAVERemoveAsync, LogType::Save); cafeExportRegister("nn_save", SAVEChangeDir, LogType::Save); cafeExportRegister("nn_save", SAVEChangeDirAsync, LogType::Save); cafeExportRegister("nn_save", SAVERename, LogType::Save); cafeExportRegister("nn_save", SAVERenameAsync, LogType::Save); cafeExportRegister("nn_save", SAVEFlushQuota, LogType::Save); cafeExportRegister("nn_save", SAVEFlushQuotaAsync, LogType::Save); cafeExportRegister("nn_save", SAVEGetStat, LogType::Save); cafeExportRegister("nn_save", SAVEGetStatAsync, LogType::Save); cafeExportRegister("nn_save", SAVEGetStatOtherApplication, LogType::Save); cafeExportRegister("nn_save", SAVEGetStatOtherApplicationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplication, LogType::Save); cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationVariation, LogType::Save); cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationVariationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFile, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileOtherApplication, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileOtherApplicationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplication, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariation, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenDir, LogType::Save); cafeExportRegister("nn_save", SAVEOpenDirAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenDirOtherApplication, LogType::Save); cafeExportRegister("nn_save", SAVEOpenDirOtherApplicationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplication, LogType::Save); cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationVariation, LogType::Save); cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationVariationAsync, LogType::Save); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { ResetToDefaultState(); } else if (reason == coreinit::RplEntryReason::Unloaded) { ResetToDefaultState(); } } }s_COSnnSaveModule; COSModule* GetModule() { return &s_COSnnSaveModule; } } } ================================================ FILE: src/Cafe/OS/libs/nn_save/nn_save.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace nn::save { void ResetToDefaultState(); bool GetPersistentIdEx(uint8 accountSlot, uint32* persistentId); COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_sl/nn_sl.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_IOS.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "config/ActiveSettings.h" #include "Cafe/CafeSystem.h" namespace nn { typedef uint32 Result; namespace sl { struct VTableEntry { uint16be offsetA{0}; uint16be offsetB{0}; MEMPTR<void> ptr; }; static_assert(sizeof(VTableEntry) == 8); constexpr uint32 SL_MEM_MAGIC = 0xCAFE4321; #define DTOR_WRAPPER(__TYPE) RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { dtor(MEMPTR<__TYPE>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }) template<typename T> MEMPTR<T> sl_new() { uint32 objSize = sizeof(T); uint32be* basePtr = (uint32be*)coreinit::_weak_MEMAllocFromDefaultHeapEx(objSize + 8, 0x8); basePtr[0] = SL_MEM_MAGIC; basePtr[1] = objSize; return (T*)(basePtr + 2); } void sl_delete(MEMPTR<void> mem) { if (!mem) return; uint32be* basePtr = (uint32be*)mem.GetPtr() - 2; if (basePtr[0] != SL_MEM_MAGIC) { cemuLog_log(LogType::Force, "nn_sl: Detected memory corruption"); cemu_assert_suspicious(); } coreinit::_weak_MEMFreeToDefaultHeap(basePtr); } #pragma pack(1) struct WhiteList { uint32be titleTypes[50]; uint32be titleTypesCount; uint32be padding; uint64be titleIds[50]; uint32be titleIdCount; }; static_assert(sizeof(WhiteList) == 0x264); #pragma pack() struct WhiteListAccessor { MEMPTR<void> vTablePtr{}; // 0x00 struct VTable { VTableEntry rtti; VTableEntry dtor; VTableEntry get; }; static inline SysAllocator<VTable> s_titleVTable; static WhiteListAccessor* ctor(WhiteListAccessor* _this) { if (!_this) _this = sl_new<WhiteListAccessor>(); *_this = {}; _this->vTablePtr = s_titleVTable; return _this; } static void dtor(WhiteListAccessor* _this, uint32 options) { if (_this && (options & 1)) sl_delete(_this); } static void Get(WhiteListAccessor* _this, nn::sl::WhiteList* outWhiteList) { *outWhiteList = {}; } static void InitVTable() { s_titleVTable->rtti.ptr = nullptr; // todo s_titleVTable->dtor.ptr = DTOR_WRAPPER(WhiteListAccessor); s_titleVTable->get.ptr = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { Get(MEMPTR<WhiteListAccessor>(hCPU->gpr[3]), MEMPTR<WhiteList>(hCPU->gpr[4])); osLib_returnFromFunction(hCPU, 0); }); } }; static_assert(sizeof(WhiteListAccessor) == 0x04); SysAllocator<WhiteListAccessor> s_defaultWhiteListAccessor; WhiteListAccessor* GetDefaultWhiteListAccessor() { return s_defaultWhiteListAccessor; } class : public COSModule { public: std::string_view GetName() override { return "nn_sl"; } void RPLMapped() override { cafeExportRegisterFunc(nn::sl::GetDefaultWhiteListAccessor, "nn_sl", "GetDefaultWhiteListAccessor__Q2_2nn2slFv", LogType::NN_SL); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { nn::sl::WhiteListAccessor::InitVTable(); nn::sl::WhiteListAccessor::ctor(nn::sl::s_defaultWhiteListAccessor); } else if (reason == coreinit::RplEntryReason::Unloaded) { // nothing to clean up } } }s_COSnnSlModule; COSModule* GetModule() { return &s_COSnnSlModule; } } // namespace sl } // namespace nn ================================================ FILE: src/Cafe/OS/libs/nn_sl/nn_sl.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace nn::sl { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nn_spm/nn_spm.cpp ================================================ #include "nn_spm.h" #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/nn_common.h" namespace nn { namespace spm { struct StorageIndex { void SetInvalid() { idHigh = 0; idLow = 0; } void Set(uint64 id) { idHigh = id >> 32; idLow = id & 0xFFFFFFFF; } uint64 Get() const { return ((uint64)idHigh << 32) | (uint64)idLow; } uint32be idHigh; uint32be idLow; }; enum class CemuStorageIndex { MLC = 1, SLC = 2, USB = 3, }; static_assert(sizeof(StorageIndex) == 8); struct VolumeId { char id[16]; }; static_assert(sizeof(VolumeId) == 16); enum class StorageType : uint32 { RAW, WFS, }; struct StorageInfo { char mountPath[640]; // For example: /vol/storage_usb01 char connectionType[8]; // usb char formatStr[8]; // raw / wfs uint8 ukn[4]; betype<StorageType> type; VolumeId volumeId; }; static_assert(sizeof(StorageInfo) == 680); struct StorageListItem { StorageIndex index; uint32be ukn04; betype<StorageType> type; }; static_assert(sizeof(StorageListItem) == 16); sint32 GetDefaultExtendedStorageVolumeId(StorageIndex* storageIndex) { cemuLog_logDebug(LogType::Force, "GetDefaultExtendedStorageVolumeId() - stub"); storageIndex->SetInvalid(); // we dont emulate USB storage yet return 0; } sint32 GetExtendedStorageIndex(StorageIndex* storageIndex) { cemuLog_logDebug(LogType::Force, "GetExtendedStorageIndex() - stub"); storageIndex->SetInvalid(); // we dont emulate USB storage yet return -1; // this fails if there is none? } // nn::spm::GetStorageList((nn::spm::StorageListItem *, unsigned int)) uint32 GetStorageList(StorageListItem* storageList, uint32 maxItems) { cemu_assert(maxItems >= 2); uint32 numItems = 0; // This should only return USB storages? // If we return two entries (for SLC and MLC supposedly) then the Wii U menu will complain about two usb storages // // mlc // storageList[numItems].index.Set((uint32)CemuStorageIndex::MLC); // storageList[numItems].ukn04 = 0; // storageList[numItems].type = StorageType::WFS; // numItems++; // // slc // storageList[numItems].index.Set((uint32)CemuStorageIndex::SLC); // storageList[numItems].ukn04 = 0; // storageList[numItems].type = StorageType::WFS; numItems++; return numItems; } sint32 GetStorageInfo(StorageInfo* storageInfo, StorageIndex* storageIndex) { cemuLog_logDebug(LogType::Force, "GetStorageInfo() - stub"); if(storageIndex->Get() == (uint64)CemuStorageIndex::MLC) { cemu_assert_unimplemented(); } else if(storageIndex->Get() == (uint64)CemuStorageIndex::SLC) { cemu_assert_unimplemented(); } else { cemu_assert_unimplemented(); } return 0; } sint32 VolumeId_Compare(VolumeId* volumeIdThis, VolumeId* volumeIdOther) { auto r = strncmp(volumeIdThis->id, volumeIdOther->id, 16); cemuLog_logDebug(LogType::Force, "VolumeId_Compare(\"{}\", \"{}\")", volumeIdThis->id, volumeIdOther->id); return (sint32)r; } sint32 WaitStateUpdated(uint64be* waitState) { // WaitStateUpdated__Q2_2nn3spmFPUL cemuLog_logDebug(LogType::Force, "WaitStateUpdated() called"); *waitState = 1; return 0; } class : public COSModule { public: std::string_view GetName() override { return "nn_spm"; } void RPLMapped() override { cafeExportRegisterFunc(GetDefaultExtendedStorageVolumeId, "nn_spm", "GetDefaultExtendedStorageVolumeId__Q2_2nn3spmFv", LogType::Placeholder); cafeExportRegisterFunc(GetExtendedStorageIndex, "nn_spm", "GetExtendedStorageIndex__Q2_2nn3spmFPQ3_2nn3spm12StorageIndex", LogType::Placeholder); cafeExportRegisterFunc(GetStorageList, "nn_spm", "GetStorageList__Q2_2nn3spmFPQ3_2nn3spm15StorageListItemUi", LogType::Placeholder); cafeExportRegisterFunc(GetStorageInfo, "nn_spm", "GetStorageInfo__Q2_2nn3spmFPQ3_2nn3spm11StorageInfoQ3_2nn3spm12StorageIndex", LogType::Placeholder); cafeExportRegisterFunc(VolumeId_Compare, "nn_spm", "Compare__Q3_2nn3spm8VolumeIdCFRCQ3_2nn3spm8VolumeId", LogType::Placeholder); cafeExportRegisterFunc(WaitStateUpdated, "nn_spm", "WaitStateUpdated__Q2_2nn3spmFPUL", LogType::Placeholder); }; }s_COSnnSpmModule; COSModule* GetModule() { return &s_COSnnSpmModule; } } } ================================================ FILE: src/Cafe/OS/libs/nn_spm/nn_spm.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace nn { namespace spm { COSModule* GetModule(); } } ================================================ FILE: src/Cafe/OS/libs/nn_temp/nn_temp.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" namespace nn::temp { uint64 tempIdGenerator = 0xdc1b04bd961f2c04ULL; void nnTempExport_TEMPCreateAndInitTempDir(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "TEMPCreateAndInitTempDir(...) - placeholder"); // create random temp id memory_writeU64(hCPU->gpr[5], tempIdGenerator); tempIdGenerator = (tempIdGenerator << 3) | (tempIdGenerator >> 61); tempIdGenerator += 0x56e28bd5f4ULL; osLib_returnFromFunction(hCPU, 0); } class : public COSModule { public: std::string_view GetName() override { return "nn_temp"; } void RPLMapped() override { osLib_addFunction("nn_temp", "TEMPCreateAndInitTempDir", nnTempExport_TEMPCreateAndInitTempDir); }; }s_COSnnTempModule; COSModule* GetModule() { return &s_COSnnTempModule; } }; ================================================ FILE: src/Cafe/OS/libs/nn_temp/nn_temp.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace nn::temp { COSModule* GetModule(); }; ================================================ FILE: src/Cafe/OS/libs/nn_uds/nn_uds.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" typedef struct { uint32 reserved; }udsWorkspace_t; udsWorkspace_t* udsWorkspace = nullptr; void nnUdsExport___sti___11_uds_Api_cpp_f5d9abb2(PPCInterpreter_t* hCPU) { debug_printf("__sti___11_uds_Api_cpp_f5d9abb2()\n"); if( udsWorkspace == NULL ) udsWorkspace = (udsWorkspace_t*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(32, 32)); osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(udsWorkspace)); } namespace nn::uds { class : public COSModule { public: std::string_view GetName() override { return "nn_uds"; } void RPLMapped() override { osLib_addFunction("nn_uds", "__sti___11_uds_Api_cpp_f5d9abb2", nnUdsExport___sti___11_uds_Api_cpp_f5d9abb2); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { udsWorkspace = nullptr; } else if (reason == coreinit::RplEntryReason::Unloaded) { udsWorkspace = nullptr; } } }s_COSnnUdsModule; COSModule* GetModule() { return &s_COSnnUdsModule; } } ================================================ FILE: src/Cafe/OS/libs/nn_uds/nn_uds.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace nn::uds { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp ================================================ #include "nsyshid.h" #include "Backend.h" #include "BackendEmulated.h" #include "BackendLibusb.h" namespace nsyshid::backend { void AttachDefaultBackends() { // add libusb backend { auto backendLibusb = std::make_shared<backend::libusb::BackendLibusb>(); if (backendLibusb->IsInitialisedOk()) { AttachBackend(backendLibusb); } } // add emulated backend { auto backendEmulated = std::make_shared<backend::emulated::BackendEmulated>(); if (backendEmulated->IsInitialisedOk()) { AttachBackend(backendEmulated); } } } } // namespace nsyshid::backend ================================================ FILE: src/Cafe/OS/libs/nsyshid/Backend.h ================================================ #pragma once #include <list> #include <memory> #include <mutex> #include "Common/precompiled.h" namespace nsyshid { typedef struct { /* +0x00 */ uint32be handle; /* +0x04 */ uint32 ukn04; /* +0x08 */ uint16 vendorId; // little-endian ? /* +0x0A */ uint16 productId; // little-endian ? /* +0x0C */ uint8 ifIndex; /* +0x0D */ uint8 subClass; /* +0x0E */ uint8 protocol; /* +0x0F */ uint8 paddingGuessed0F; /* +0x10 */ uint16be maxPacketSizeRX; /* +0x12 */ uint16be maxPacketSizeTX; } HID_t; struct TransferCommand { uint8* data; uint32 length; TransferCommand(uint8* data, uint32 length) : data(data), length(length) { } virtual ~TransferCommand() = default; }; struct ReadMessage final : TransferCommand { sint32 bytesRead; ReadMessage(uint8* data, uint32 length, sint32 bytesRead) : bytesRead(bytesRead), TransferCommand(data, length) { } using TransferCommand::TransferCommand; }; struct WriteMessage final : TransferCommand { sint32 bytesWritten; WriteMessage(uint8* data, uint32 length, sint32 bytesWritten) : bytesWritten(bytesWritten), TransferCommand(data, length) { } using TransferCommand::TransferCommand; }; struct ReportMessage final : TransferCommand { uint8 reportType; uint8 reportId; ReportMessage(uint8 reportType, uint8 reportId, uint8* data, uint32 length) : reportType(reportType), reportId(reportId), TransferCommand(data, length) { } using TransferCommand::TransferCommand; }; static_assert(offsetof(HID_t, vendorId) == 0x8, ""); static_assert(offsetof(HID_t, productId) == 0xA, ""); static_assert(offsetof(HID_t, ifIndex) == 0xC, ""); static_assert(offsetof(HID_t, protocol) == 0xE, ""); class Device { public: Device() = delete; Device(uint16 vendorId, uint16 productId, uint8 interfaceIndex, uint8 interfaceSubClass, uint8 protocol); Device(const Device& device) = delete; Device& operator=(const Device& device) = delete; virtual ~Device() = default; HID_t* m_hid; // this info is passed to applications and must remain intact uint16 m_vendorId; uint16 m_productId; uint8 m_interfaceIndex; uint8 m_interfaceSubClass; uint8 m_protocol; uint16 m_maxPacketSizeRX; uint16 m_maxPacketSizeTX; virtual void AssignHID(HID_t* hid); virtual bool Open() = 0; virtual void Close() = 0; virtual bool IsOpened() = 0; enum class ReadResult { Success, Error, ErrorTimeout, }; virtual ReadResult Read(ReadMessage* message) = 0; enum class WriteResult { Success, Error, ErrorTimeout, }; virtual WriteResult Write(WriteMessage* message) = 0; virtual bool GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) = 0; virtual bool SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) = 0; virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0; virtual bool SetReport(ReportMessage* message) = 0; }; class Backend { public: Backend(); Backend(const Backend& backend) = delete; Backend& operator=(const Backend& backend) = delete; virtual ~Backend() = default; void DetachAllDevices(); // called from nsyshid when this backend is attached - do not call this yourself! void OnAttach(); // called from nsyshid when this backend is detached - do not call this yourself! void OnDetach(); bool IsBackendAttached(); virtual bool IsInitialisedOk() = 0; protected: // try to attach a device - only works if this backend is attached bool AttachDevice(const std::shared_ptr<Device>& device); void DetachDevice(const std::shared_ptr<Device>& device); std::shared_ptr<Device> FindDevice(std::function<bool(const std::shared_ptr<Device>&)> isWantedDevice); std::shared_ptr<Device> FindDeviceById(uint16 vendorId, uint16 productId); bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); // called from OnAttach() - attach devices that your backend can see here virtual void AttachVisibleDevices() = 0; private: std::list<std::shared_ptr<Device>> m_devices; std::recursive_mutex m_devicesMutex; bool m_isAttached; }; namespace backend { void AttachDefaultBackends(); } } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp ================================================ #include "BackendEmulated.h" #include "Dimensions.h" #include "Infinity.h" #include "Skylander.h" #include "config/CemuConfig.h" #include "SkylanderXbox360.h" namespace nsyshid::backend::emulated { BackendEmulated::BackendEmulated() { cemuLog_logDebug(LogType::Force, "nsyshid::BackendEmulated: emulated backend initialised"); } BackendEmulated::~BackendEmulated() = default; bool BackendEmulated::IsInitialisedOk() { return true; } void BackendEmulated::AttachVisibleDevices() { if (GetConfig().emulated_usb_devices.emulate_skylander_portal && !FindDeviceById(0x1430, 0x0150)) { cemuLog_logDebug(LogType::Force, "Attaching Emulated Portal"); // Add Skylander Portal auto device = std::make_shared<SkylanderPortalDevice>(); AttachDevice(device); } else if (auto usb_portal = FindDeviceById(0x1430, 0x1F17)) { cemuLog_logDebug(LogType::Force, "Attaching Xbox 360 Portal"); // Add Skylander Xbox 360 Portal auto device = std::make_shared<SkylanderXbox360PortalLibusb>(usb_portal); AttachDevice(device); } if (GetConfig().emulated_usb_devices.emulate_infinity_base && !FindDeviceById(0x0E6F, 0x0129)) { cemuLog_logDebug(LogType::Force, "Attaching Emulated Base"); // Add Infinity Base auto device = std::make_shared<InfinityBaseDevice>(); AttachDevice(device); } if (GetConfig().emulated_usb_devices.emulate_dimensions_toypad && !FindDeviceById(0x0E6F, 0x0241)) { cemuLog_logDebug(LogType::Force, "Attaching Emulated Toypad"); // Add Dimensions Toypad auto device = std::make_shared<DimensionsToypadDevice>(); AttachDevice(device); } } } // namespace nsyshid::backend::emulated ================================================ FILE: src/Cafe/OS/libs/nsyshid/BackendEmulated.h ================================================ #include "nsyshid.h" #include "Backend.h" namespace nsyshid::backend::emulated { class BackendEmulated : public nsyshid::Backend { public: BackendEmulated(); ~BackendEmulated(); bool IsInitialisedOk() override; protected: void AttachVisibleDevices() override; }; } // namespace nsyshid::backend::emulated ================================================ FILE: src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp ================================================ #include "BackendLibusb.h" namespace nsyshid::backend::libusb { BackendLibusb::BackendLibusb() : m_ctx(nullptr), m_initReturnCode(0), m_callbackRegistered(false), m_hotplugCallbackHandle(0), m_hotplugThreadStop(false) { m_initReturnCode = libusb_init(&m_ctx); if (m_initReturnCode < 0) { m_ctx = nullptr; cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb, return code: {}", m_initReturnCode); return; } if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { int ret = libusb_hotplug_register_callback(m_ctx, (libusb_hotplug_event)(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), (libusb_hotplug_flag)0, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, HotplugCallback, this, &m_hotplugCallbackHandle); if (ret != LIBUSB_SUCCESS) { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to register hotplug callback with return code {}", ret); } else { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: registered hotplug callback"); m_callbackRegistered = true; m_hotplugThread = std::thread([this] { while (!m_hotplugThreadStop) { timeval timeout{ .tv_sec = 1, .tv_usec = 0, }; int ret = libusb_handle_events_timeout_completed(m_ctx, &timeout, nullptr); if (ret != 0) { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: hotplug thread: error handling events: {}", ret); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } }); } } else { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: hotplug not supported by this version of libusb"); } } bool BackendLibusb::IsInitialisedOk() { return m_initReturnCode == 0; } void BackendLibusb::AttachVisibleDevices() { // add all currently connected devices libusb_device** devices; ssize_t deviceCount = libusb_get_device_list(m_ctx, &devices); if (deviceCount < 0) { cemuLog_log(LogType::Force, "nsyshid::BackendLibusb: failed to get usb devices"); return; } libusb_device* dev; for (int i = 0; (dev = devices[i]) != nullptr; i++) { auto device = CheckAndCreateDevice(dev); if (device != nullptr) { if (IsDeviceWhitelisted(device->m_vendorId, device->m_productId)) { if (!AttachDevice(device)) { cemuLog_log(LogType::Force, "nsyshid::BackendLibusb: failed to attach device: {:04x}:{:04x}", device->m_vendorId, device->m_productId); } } else { cemuLog_log(LogType::Force, "nsyshid::BackendLibusb: device not on whitelist: {:04x}:{:04x}", device->m_vendorId, device->m_productId); } } } libusb_free_device_list(devices, 1); } int BackendLibusb::HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotplug_event event, void* user_data) { if (user_data) { BackendLibusb* backend = static_cast<BackendLibusb*>(user_data); return backend->OnHotplug(dev, event); } return 0; } int BackendLibusb::OnHotplug(libusb_device* dev, libusb_hotplug_event event) { struct libusb_device_descriptor desc; int ret = libusb_get_device_descriptor(dev, &desc); if (ret < 0) { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): failed to get device descriptor"); return 0; } switch (event) { case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device arrived: {:04x}:{:04x}", desc.idVendor, desc.idProduct); auto device = CheckAndCreateDevice(dev); if (device != nullptr) { if (IsDeviceWhitelisted(device->m_vendorId, device->m_productId)) { if (!AttachDevice(device)) { cemuLog_log(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): failed to attach device: {:04x}:{:04x}", device->m_vendorId, device->m_productId); } } else { cemuLog_log(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device not on whitelist: {:04x}:{:04x}", device->m_vendorId, device->m_productId); } } } break; case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device left: {:04x}:{:04x}", desc.idVendor, desc.idProduct); auto device = FindLibusbDevice(dev); if (device != nullptr) { DetachDevice(device); } } break; } return 0; } BackendLibusb::~BackendLibusb() { if (m_callbackRegistered) { m_hotplugThreadStop = true; libusb_hotplug_deregister_callback(m_ctx, m_hotplugCallbackHandle); m_hotplugThread.join(); } DetachAllDevices(); if (m_ctx) { libusb_exit(m_ctx); m_ctx = nullptr; } } std::shared_ptr<Device> BackendLibusb::FindLibusbDevice(libusb_device* dev) { libusb_device_descriptor desc; int ret = libusb_get_device_descriptor(dev, &desc); if (ret < 0) { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor"); return nullptr; } uint8 busNumber = libusb_get_bus_number(dev); uint8 deviceAddress = libusb_get_device_address(dev); auto device = FindDevice([desc, busNumber, deviceAddress](const std::shared_ptr<Device>& d) -> bool { auto device = std::dynamic_pointer_cast<DeviceLibusb>(d); if (device != nullptr && desc.idVendor == device->m_vendorId && desc.idProduct == device->m_productId && busNumber == device->m_libusbBusNumber && deviceAddress == device->m_libusbDeviceAddress) { // we found our device! return true; } return false; }); if (device != nullptr) { return device; } return nullptr; } std::pair<int, ConfigDescriptor> MakeConfigDescriptor(libusb_device* device, uint8 config_num) { libusb_config_descriptor* descriptor = nullptr; const int ret = libusb_get_config_descriptor(device, config_num, &descriptor); if (ret == LIBUSB_SUCCESS) return {ret, ConfigDescriptor{descriptor, libusb_free_config_descriptor}}; return {ret, ConfigDescriptor{nullptr, [](auto) { }}}; } std::shared_ptr<Device> BackendLibusb::CheckAndCreateDevice(libusb_device* dev) { struct libusb_device_descriptor desc; int ret = libusb_get_device_descriptor(dev, &desc); if (ret < 0) { cemuLog_log(LogType::Force, "nsyshid::BackendLibusb::CheckAndCreateDevice(): failed to get device descriptor; return code: %i", ret); return nullptr; } std::vector<ConfigDescriptor> config_descriptors{}; for (uint8 i = 0; i < desc.bNumConfigurations; ++i) { auto [ret, config_descriptor] = MakeConfigDescriptor(dev, i); if (ret != LIBUSB_SUCCESS || !config_descriptor) { cemuLog_log(LogType::Force, "Failed to make config descriptor {} for {:04x}:{:04x}: {}", i, desc.idVendor, desc.idProduct, libusb_error_name(ret)); } else { config_descriptors.emplace_back(std::move(config_descriptor)); } } if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241) { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); } auto device = std::make_shared<DeviceLibusb>(m_ctx, desc.idVendor, desc.idProduct, 0, 2, 0, libusb_get_bus_number(dev), libusb_get_device_address(dev), std::move(config_descriptors)); // figure out device endpoints if (!FindDefaultDeviceEndpoints(dev, device->m_libusbHasEndpointIn, device->m_libusbEndpointIn, device->m_maxPacketSizeRX, device->m_libusbHasEndpointOut, device->m_libusbEndpointOut, device->m_maxPacketSizeTX)) { // most likely couldn't read config descriptor cemuLog_log(LogType::Force, "nsyshid::BackendLibusb::CheckAndCreateDevice(): failed to find default endpoints for device: {:04x}:{:04x}", device->m_vendorId, device->m_productId); return nullptr; } return device; } bool BackendLibusb::FindDefaultDeviceEndpoints(libusb_device* dev, bool& endpointInFound, uint8& endpointIn, uint16& endpointInMaxPacketSize, bool& endpointOutFound, uint8& endpointOut, uint16& endpointOutMaxPacketSize) { endpointInFound = false; endpointIn = 0; endpointInMaxPacketSize = 0; endpointOutFound = false; endpointOut = 0; endpointOutMaxPacketSize = 0; struct libusb_config_descriptor* conf = nullptr; int ret = libusb_get_active_config_descriptor(dev, &conf); if (ret == 0) { for (uint8 interfaceIndex = 0; interfaceIndex < conf->bNumInterfaces; interfaceIndex++) { const struct libusb_interface& interface = conf->interface[interfaceIndex]; for (int altsettingIndex = 0; altsettingIndex < interface.num_altsetting; altsettingIndex++) { const struct libusb_interface_descriptor& altsetting = interface.altsetting[altsettingIndex]; for (uint8 endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++) { const struct libusb_endpoint_descriptor& endpoint = altsetting.endpoint[endpointIndex]; // figure out direction if ((endpoint.bEndpointAddress & (1 << 7)) != 0) { // in if (!endpointInFound) { endpointInFound = true; endpointIn = endpoint.bEndpointAddress; endpointInMaxPacketSize = endpoint.wMaxPacketSize; } } else { // out if (!endpointOutFound) { endpointOutFound = true; endpointOut = endpoint.bEndpointAddress; endpointOutMaxPacketSize = endpoint.wMaxPacketSize; } } } } } libusb_free_config_descriptor(conf); return true; } return false; } DeviceLibusb::DeviceLibusb(libusb_context* ctx, uint16 vendorId, uint16 productId, uint8 interfaceIndex, uint8 interfaceSubClass, uint8 protocol, uint8 libusbBusNumber, uint8 libusbDeviceAddress, std::vector<ConfigDescriptor> configs) : Device(vendorId, productId, interfaceIndex, interfaceSubClass, protocol), m_ctx(ctx), m_libusbHandle(nullptr), m_handleInUseCounter(-1), m_libusbBusNumber(libusbBusNumber), m_libusbDeviceAddress(libusbDeviceAddress), m_libusbHasEndpointIn(false), m_libusbEndpointIn(0), m_libusbHasEndpointOut(false), m_libusbEndpointOut(0) { m_config_descriptors = std::move(configs); } DeviceLibusb::~DeviceLibusb() { CloseDevice(); } bool DeviceLibusb::Open() { std::unique_lock<std::mutex> lock(m_handleMutex); if (IsOpened()) { return true; } // we may still be in the process of closing the device; wait for that to finish while (m_handleInUseCounter != -1) { m_handleInUseCounterDecremented.wait(lock); } libusb_device** devices; ssize_t deviceCount = libusb_get_device_list(m_ctx, &devices); if (deviceCount < 0) { cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::open(): failed to get usb devices"); return false; } libusb_device* dev; libusb_device* found = nullptr; for (int i = 0; (dev = devices[i]) != nullptr; i++) { struct libusb_device_descriptor desc; int ret = libusb_get_device_descriptor(dev, &desc); if (ret < 0) { cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::open(): failed to get device descriptor, return code: {}", ret); libusb_free_device_list(devices, 1); return false; } if (desc.idVendor == this->m_vendorId && desc.idProduct == this->m_productId && libusb_get_bus_number(dev) == this->m_libusbBusNumber && libusb_get_device_address(dev) == this->m_libusbDeviceAddress) { // we found our device! found = dev; break; } } if (found != nullptr) { { int ret = libusb_open(dev, &(this->m_libusbHandle)); if (ret < 0) { this->m_libusbHandle = nullptr; cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::open(): failed to open device: {}", libusb_strerror(ret)); libusb_free_device_list(devices, 1); return false; } this->m_handleInUseCounter = 0; } int ret = ClaimAllInterfaces(0); if (ret != 0) { cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface for config 0"); return false; } } libusb_free_device_list(devices, 1); return found != nullptr; } void DeviceLibusb::Close() { CloseDevice(); } void DeviceLibusb::CloseDevice() { std::unique_lock<std::mutex> lock(m_handleMutex); if (IsOpened()) { auto handle = m_libusbHandle; m_libusbHandle = nullptr; while (m_handleInUseCounter > 0) { m_handleInUseCounterDecremented.wait(lock); } ReleaseAllInterfacesForCurrentConfig(); libusb_close(handle); m_handleInUseCounter = -1; m_handleInUseCounterDecremented.notify_all(); } } bool DeviceLibusb::IsOpened() { return m_libusbHandle != nullptr && m_handleInUseCounter >= 0; } Device::ReadResult DeviceLibusb::Read(ReadMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n"); return ReadResult::Error; } for (int i = 0; i < m_config_descriptors.size(); i++) { ClaimAllInterfaces(i); } const unsigned int timeout = 50; int actualLength = 0; int ret = 0; do { ret = libusb_interrupt_transfer(handleLock->GetHandle(), this->m_libusbEndpointIn, message->data, message->length, &actualLength, timeout); } while (ret == LIBUSB_ERROR_TIMEOUT && actualLength == 0 && IsOpened()); if (ret == 0 || ret == LIBUSB_ERROR_TIMEOUT) { // success cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): read {} of {} bytes", actualLength, message->length); message->bytesRead = actualLength; return ReadResult::Success; } cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): failed at endpoint 0x{:02x} with error message: {}", this->m_libusbEndpointIn, libusb_error_name(ret)); return ReadResult::Error; } Device::WriteResult DeviceLibusb::Write(WriteMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n"); return WriteResult::Error; } for (int i = 0; i < m_config_descriptors.size(); i++) { ClaimAllInterfaces(i); } message->bytesWritten = 0; int actualLength = 0; int ret = libusb_interrupt_transfer(handleLock->GetHandle(), this->m_libusbEndpointOut, message->data, message->length, &actualLength, 0); if (ret == 0) { // success message->bytesWritten = actualLength; cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::write(): wrote {} of {} bytes", message->bytesWritten, message->length); return WriteResult::Success; } cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::write(): failed with error code: {}", ret); return WriteResult::Error; } bool DeviceLibusb::GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::getDescriptor(): device is not opened"); return false; } if (descType == 0x02) { struct libusb_config_descriptor* conf = nullptr; libusb_device* dev = libusb_get_device(handleLock->GetHandle()); int ret = libusb_get_active_config_descriptor(dev, &conf); if (ret == 0) { std::vector<uint8> configurationDescriptor(conf->wTotalLength); uint8* currentWritePtr = &configurationDescriptor[0]; // configuration descriptor cemu_assert_debug(conf->bLength == LIBUSB_DT_CONFIG_SIZE); *(uint8*)(currentWritePtr + 0) = conf->bLength; // bLength *(uint8*)(currentWritePtr + 1) = conf->bDescriptorType; // bDescriptorType *(uint16be*)(currentWritePtr + 2) = conf->wTotalLength; // wTotalLength *(uint8*)(currentWritePtr + 4) = conf->bNumInterfaces; // bNumInterfaces *(uint8*)(currentWritePtr + 5) = conf->bConfigurationValue; // bConfigurationValue *(uint8*)(currentWritePtr + 6) = conf->iConfiguration; // iConfiguration *(uint8*)(currentWritePtr + 7) = conf->bmAttributes; // bmAttributes *(uint8*)(currentWritePtr + 8) = conf->MaxPower; // MaxPower currentWritePtr = currentWritePtr + conf->bLength; for (uint8_t interfaceIndex = 0; interfaceIndex < conf->bNumInterfaces; interfaceIndex++) { const struct libusb_interface& interface = conf->interface[interfaceIndex]; for (int altsettingIndex = 0; altsettingIndex < interface.num_altsetting; altsettingIndex++) { // interface descriptor const struct libusb_interface_descriptor& altsetting = interface.altsetting[altsettingIndex]; cemu_assert_debug(altsetting.bLength == LIBUSB_DT_INTERFACE_SIZE); *(uint8*)(currentWritePtr + 0) = altsetting.bLength; // bLength *(uint8*)(currentWritePtr + 1) = altsetting.bDescriptorType; // bDescriptorType *(uint8*)(currentWritePtr + 2) = altsetting.bInterfaceNumber; // bInterfaceNumber *(uint8*)(currentWritePtr + 3) = altsetting.bAlternateSetting; // bAlternateSetting *(uint8*)(currentWritePtr + 4) = altsetting.bNumEndpoints; // bNumEndpoints *(uint8*)(currentWritePtr + 5) = altsetting.bInterfaceClass; // bInterfaceClass *(uint8*)(currentWritePtr + 6) = altsetting.bInterfaceSubClass; // bInterfaceSubClass *(uint8*)(currentWritePtr + 7) = altsetting.bInterfaceProtocol; // bInterfaceProtocol *(uint8*)(currentWritePtr + 8) = altsetting.iInterface; // iInterface currentWritePtr = currentWritePtr + altsetting.bLength; if (altsetting.extra_length > 0) { // unknown descriptors - copy the ones that we can identify ourselves const unsigned char* extraReadPointer = altsetting.extra; while (extraReadPointer - altsetting.extra < altsetting.extra_length) { uint8 bLength = *(uint8*)(extraReadPointer + 0); if (bLength == 0) { // prevent endless loop break; } if (extraReadPointer + bLength - altsetting.extra > altsetting.extra_length) { // prevent out of bounds read break; } uint8 bDescriptorType = *(uint8*)(extraReadPointer + 1); // HID descriptor if (bDescriptorType == LIBUSB_DT_HID && bLength == 9) { *(uint8*)(currentWritePtr + 0) = *(uint8*)(extraReadPointer + 0); // bLength *(uint8*)(currentWritePtr + 1) = *(uint8*)(extraReadPointer + 1); // bDescriptorType *(uint16be*)(currentWritePtr + 2) = *(uint16*)(extraReadPointer + 2); // bcdHID *(uint8*)(currentWritePtr + 4) = *(uint8*)(extraReadPointer + 4); // bCountryCode *(uint8*)(currentWritePtr + 5) = *(uint8*)(extraReadPointer + 5); // bNumDescriptors *(uint8*)(currentWritePtr + 6) = *(uint8*)(extraReadPointer + 6); // bDescriptorType *(uint16be*)(currentWritePtr + 7) = *(uint16*)(extraReadPointer + 7); // wDescriptorLength currentWritePtr += bLength; } extraReadPointer += bLength; } } for (int endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++) { // endpoint descriptor const struct libusb_endpoint_descriptor& endpoint = altsetting.endpoint[endpointIndex]; cemu_assert_debug(endpoint.bLength == LIBUSB_DT_ENDPOINT_SIZE || endpoint.bLength == LIBUSB_DT_ENDPOINT_AUDIO_SIZE); *(uint8*)(currentWritePtr + 0) = endpoint.bLength; *(uint8*)(currentWritePtr + 1) = endpoint.bDescriptorType; *(uint8*)(currentWritePtr + 2) = endpoint.bEndpointAddress; *(uint8*)(currentWritePtr + 3) = endpoint.bmAttributes; *(uint16be*)(currentWritePtr + 4) = endpoint.wMaxPacketSize; *(uint8*)(currentWritePtr + 6) = endpoint.bInterval; if (endpoint.bLength == LIBUSB_DT_ENDPOINT_AUDIO_SIZE) { *(uint8*)(currentWritePtr + 7) = endpoint.bRefresh; *(uint8*)(currentWritePtr + 8) = endpoint.bSynchAddress; } currentWritePtr += endpoint.bLength; } } } uint32 bytesWritten = currentWritePtr - &configurationDescriptor[0]; libusb_free_config_descriptor(conf); cemu_assert_debug(bytesWritten <= conf->wTotalLength); memcpy(output, &configurationDescriptor[0], std::min<uint32>(outputMaxLength, bytesWritten)); return true; } } else { uint16 wValue = uint16(descType) << 8 | uint16(descIndex); // HID Get_Descriptor requests are handled via libusb_control_transfer int ret = libusb_control_transfer(handleLock->GetHandle(), LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_ENDPOINT_IN, LIBUSB_REQUEST_GET_DESCRIPTOR, wValue, lang, output, outputMaxLength, 0); if (ret != outputMaxLength) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::GetDescriptor(): Control Transfer Failed: {}", libusb_error_name(ret)); return false; } } return true; } bool DeviceLibusb::SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) { cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::SetIdle(): device is not opened"); return false; } uint16 wValue = uint16(duration) << 8 | uint16(reportId); // HID Set_Idle requests are handled via libusb_control_transfer int ret = libusb_control_transfer(handleLock->GetHandle(), LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, HID_CLASS_SET_IDLE, // Defined in HID Class Specific Requests (7.2) wValue, ifIndex, nullptr, 0, 0); if (ret != 0) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetIdle(): Control Transfer Failed: {}", libusb_error_name(ret)); return false; } return true; } template<typename Configs, typename Function> static int DoForEachInterface(const Configs& configs, uint8 config_num, Function action) { int ret = LIBUSB_ERROR_NOT_FOUND; if (configs.size() <= config_num || !configs[config_num]) return ret; for (uint8 i = 0; i < configs[config_num]->bNumInterfaces; ++i) { ret = action(i); if (ret < LIBUSB_SUCCESS) break; } return ret; } int DeviceLibusb::ClaimAllInterfaces(uint8 config_num) { const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { // On macos detaching would fail without root or entitlement. // We assume user is using GCAdapterDriver and therefore don't want to detach anything #if !defined(__APPLE__) if (libusb_kernel_driver_active(this->m_libusbHandle, i)) { const int ret2 = libusb_detach_kernel_driver(this->m_libusbHandle, i); if (ret2 < LIBUSB_SUCCESS && ret2 != LIBUSB_ERROR_NOT_FOUND && ret2 != LIBUSB_ERROR_NOT_SUPPORTED) { cemuLog_log(LogType::Force, "Failed to detach kernel driver {}", libusb_error_name(ret2)); return ret2; } } #endif return libusb_claim_interface(this->m_libusbHandle, i); }); if (ret < LIBUSB_SUCCESS) { cemuLog_log(LogType::Force, "Failed to release all interfaces for config {}", config_num); } return ret; } int DeviceLibusb::ReleaseAllInterfaces(uint8 config_num) { const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { return libusb_release_interface(AquireHandleLock()->GetHandle(), i); }); if (ret < LIBUSB_SUCCESS && ret != LIBUSB_ERROR_NO_DEVICE && ret != LIBUSB_ERROR_NOT_FOUND) { cemuLog_log(LogType::Force, "Failed to release all interfaces for config {}", config_num); } return ret; } int DeviceLibusb::ReleaseAllInterfacesForCurrentConfig() { int config_num; const int get_config_ret = libusb_get_configuration(AquireHandleLock()->GetHandle(), &config_num); if (get_config_ret < LIBUSB_SUCCESS) return get_config_ret; return ReleaseAllInterfaces(config_num); } bool DeviceLibusb::SetProtocol(uint8 ifIndex, uint8 protocol) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened"); return false; } int ret = libusb_control_transfer(handleLock->GetHandle(), LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, HID_CLASS_SET_PROTOCOL, // Defined in HID Class Specific Requests (7.2) protocol, ifIndex, nullptr, 0, 0); if (ret != 0) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): Control Transfer Failed: {}", libusb_error_name(ret)); return false; } return true; } bool DeviceLibusb::SetReport(ReportMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): device is not opened"); return false; } uint16 wValue = uint16(message->reportType) << 8 | uint16(message->reportId); int ret = libusb_control_transfer(handleLock->GetHandle(), LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, HID_CLASS_SET_REPORT, // Defined in HID Class Specific Requests (7.2) wValue, m_interfaceIndex, message->data, uint16(message->length & 0xFFFF), 0); if (ret != message->length) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed at interface {} : {}", m_interfaceIndex, libusb_error_name(ret)); return false; } return true; } std::unique_ptr<DeviceLibusb::HandleLock> DeviceLibusb::AquireHandleLock() { return std::make_unique<HandleLock>(&m_libusbHandle, m_handleMutex, m_handleInUseCounter, m_handleInUseCounterDecremented, *this); } DeviceLibusb::HandleLock::HandleLock(libusb_device_handle** handle, std::mutex& handleMutex, std::atomic<sint32>& handleInUseCounter, std::condition_variable& handleInUseCounterDecremented, DeviceLibusb& device) : m_handle(nullptr), m_handleMutex(handleMutex), m_handleInUseCounter(handleInUseCounter), m_handleInUseCounterDecremented(handleInUseCounterDecremented) { std::lock_guard<std::mutex> lock(handleMutex); if (device.IsOpened() && handle != nullptr && handleInUseCounter >= 0) { this->m_handle = *handle; this->m_handleInUseCounter++; } } DeviceLibusb::HandleLock::~HandleLock() { if (IsValid()) { std::lock_guard<std::mutex> lock(m_handleMutex); m_handleInUseCounter--; m_handleInUseCounterDecremented.notify_all(); } } bool DeviceLibusb::HandleLock::IsValid() { return m_handle != nullptr; } libusb_device_handle* DeviceLibusb::HandleLock::GetHandle() { return m_handle; } } // namespace nsyshid::backend::libusb ================================================ FILE: src/Cafe/OS/libs/nsyshid/BackendLibusb.h ================================================ #include "nsyshid.h" #if BOOST_OS_BSD #include <libusb.h> #else #include <libusb-1.0/libusb.h> #endif #include "Backend.h" namespace nsyshid::backend::libusb { enum : uint8 { HID_CLASS_GET_REPORT = 0x01, HID_CLASS_GET_IDLE = 0x02, HID_CLASS_GET_PROTOCOL = 0x03, HID_CLASS_SET_REPORT = 0x09, HID_CLASS_SET_IDLE = 0x0A, HID_CLASS_SET_PROTOCOL = 0x0B }; class BackendLibusb : public nsyshid::Backend { public: BackendLibusb(); ~BackendLibusb(); bool IsInitialisedOk() override; protected: void AttachVisibleDevices() override; private: libusb_context* m_ctx; int m_initReturnCode; bool m_callbackRegistered; libusb_hotplug_callback_handle m_hotplugCallbackHandle; std::thread m_hotplugThread; std::atomic<bool> m_hotplugThreadStop; // called by libusb static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotplug_event event, void* user_data); int OnHotplug(libusb_device* dev, libusb_hotplug_event event); std::shared_ptr<Device> CheckAndCreateDevice(libusb_device* dev); std::shared_ptr<Device> FindLibusbDevice(libusb_device* dev); bool FindDefaultDeviceEndpoints(libusb_device* dev, bool& endpointInFound, uint8& endpointIn, uint16& endpointInMaxPacketSize, bool& endpointOutFound, uint8& endpointOut, uint16& endpointOutMaxPacketSize); }; template<typename T> using UniquePtr = std::unique_ptr<T, void (*)(T*)>; using ConfigDescriptor = UniquePtr<libusb_config_descriptor>; class DeviceLibusb : public nsyshid::Device { public: DeviceLibusb(libusb_context* ctx, uint16 vendorId, uint16 productId, uint8 interfaceIndex, uint8 interfaceSubClass, uint8 protocol, uint8 libusbBusNumber, uint8 libusbDeviceAddress, std::vector<ConfigDescriptor> configs); ~DeviceLibusb() override; bool Open() override; void Close() override; bool IsOpened() override; ReadResult Read(ReadMessage* message) override; WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) override; bool SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) override; bool SetProtocol(uint8 ifIndex, uint8 protocol) override; int ClaimAllInterfaces(uint8 config_num); int ReleaseAllInterfaces(uint8 config_num); int ReleaseAllInterfacesForCurrentConfig(); bool SetReport(ReportMessage* message) override; uint8 m_libusbBusNumber; uint8 m_libusbDeviceAddress; bool m_libusbHasEndpointIn; uint8 m_libusbEndpointIn; bool m_libusbHasEndpointOut; uint8 m_libusbEndpointOut; private: void CloseDevice(); libusb_context* m_ctx; std::mutex m_handleMutex; std::atomic<sint32> m_handleInUseCounter; std::condition_variable m_handleInUseCounterDecremented; libusb_device_handle* m_libusbHandle; std::vector<ConfigDescriptor> m_config_descriptors; class HandleLock { public: HandleLock() = delete; HandleLock(libusb_device_handle** handle, std::mutex& handleMutex, std::atomic<sint32>& handleInUseCounter, std::condition_variable& handleInUseCounterDecremented, DeviceLibusb& device); ~HandleLock(); HandleLock(const HandleLock&) = delete; HandleLock& operator=(const HandleLock&) = delete; bool IsValid(); libusb_device_handle* GetHandle(); private: libusb_device_handle* m_handle; std::mutex& m_handleMutex; std::atomic<sint32>& m_handleInUseCounter; std::condition_variable& m_handleInUseCounterDecremented; }; std::unique_ptr<HandleLock> AquireHandleLock(); }; } // namespace nsyshid::backend::libusb ================================================ FILE: src/Cafe/OS/libs/nsyshid/Dimensions.cpp ================================================ #include "Dimensions.h" #include "nsyshid.h" #include "Backend.h" #include "Common/FileStream.h" #include <array> #include <random> namespace nsyshid { static constexpr std::array<uint8, 16> COMMAND_KEY = {0x55, 0xFE, 0xF6, 0xB0, 0x62, 0xBF, 0x0B, 0x41, 0xC9, 0xB3, 0x7C, 0xB4, 0x97, 0x3E, 0x29, 0x7B}; static constexpr std::array<uint8, 17> CHAR_CONSTANT = {0xB7, 0xD5, 0xD7, 0xE6, 0xE7, 0xBA, 0x3C, 0xA8, 0xD8, 0x75, 0x47, 0x68, 0xCF, 0x23, 0xE9, 0xFE, 0xAA}; static constexpr std::array<uint8, 25> PWD_CONSTANT = {0x28, 0x63, 0x29, 0x20, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x4C, 0x45, 0x47, 0x4F, 0x20, 0x32, 0x30, 0x31, 0x34, 0xAA, 0xAA}; DimensionsUSB g_dimensionstoypad; const std::map<const uint32, const char*> s_listMinis = { {0, "Blank Tag"}, {1, "Batman"}, {2, "Gandalf"}, {3, "Wyldstyle"}, {4, "Aquaman"}, {5, "Bad Cop"}, {6, "Bane"}, {7, "Bart Simpson"}, {8, "Benny"}, {9, "Chell"}, {10, "Cole"}, {11, "Cragger"}, {12, "Cyborg"}, {13, "Cyberman"}, {14, "Doc Brown"}, {15, "The Doctor"}, {16, "Emmet"}, {17, "Eris"}, {18, "Gimli"}, {19, "Gollum"}, {20, "Harley Quinn"}, {21, "Homer Simpson"}, {22, "Jay"}, {23, "Joker"}, {24, "Kai"}, {25, "ACU Trooper"}, {26, "Gamer Kid"}, {27, "Krusty the Clown"}, {28, "Laval"}, {29, "Legolas"}, {30, "Lloyd"}, {31, "Marty McFly"}, {32, "Nya"}, {33, "Owen Grady"}, {34, "Peter Venkman"}, {35, "Slimer"}, {36, "Scooby-Doo"}, {37, "Sensei Wu"}, {38, "Shaggy"}, {39, "Stay Puft"}, {40, "Superman"}, {41, "Unikitty"}, {42, "Wicked Witch of the West"}, {43, "Wonder Woman"}, {44, "Zane"}, {45, "Green Arrow"}, {46, "Supergirl"}, {47, "Abby Yates"}, {48, "Finn the Human"}, {49, "Ethan Hunt"}, {50, "Lumpy Space Princess"}, {51, "Jake the Dog"}, {52, "Harry Potter"}, {53, "Lord Voldemort"}, {54, "Michael Knight"}, {55, "B.A. Baracus"}, {56, "Newt Scamander"}, {57, "Sonic the Hedgehog"}, {58, "Future Update (unreleased)"}, {59, "Gizmo"}, {60, "Stripe"}, {61, "E.T."}, {62, "Tina Goldstein"}, {63, "Marceline the Vampire Queen"}, {64, "Batgirl"}, {65, "Robin"}, {66, "Sloth"}, {67, "Hermione Granger"}, {68, "Chase McCain"}, {69, "Excalibur Batman"}, {70, "Raven"}, {71, "Beast Boy"}, {72, "Betelgeuse"}, {73, "Lord Vortech (unreleased)"}, {74, "Blossom"}, {75, "Bubbles"}, {76, "Buttercup"}, {77, "Starfire"}, {78, "World 15 (unreleased)"}, {79, "World 16 (unreleased)"}, {80, "World 17 (unreleased)"}, {81, "World 18 (unreleased)"}, {82, "World 19 (unreleased)"}, {83, "World 20 (unreleased)"}, {768, "Unknown 768"}, {769, "Supergirl Red Lantern"}, {770, "Unknown 770"}}; const std::map<const uint32, const char*> s_listTokens = { {1000, "Police Car"}, {1001, "Aerial Squad Car"}, {1002, "Missile Striker"}, {1003, "Gravity Sprinter"}, {1004, "Street Shredder"}, {1005, "Sky Clobberer"}, {1006, "Batmobile"}, {1007, "Batblaster"}, {1008, "Sonic Batray"}, {1009, "Benny's Spaceship"}, {1010, "Lasercraft"}, {1011, "The Annihilator"}, {1012, "DeLorean Time Machine"}, {1013, "Electric Time Machine"}, {1014, "Ultra Time Machine"}, {1015, "Hoverboard"}, {1016, "Cyclone Board"}, {1017, "Ultimate Hoverjet"}, {1018, "Eagle Interceptor"}, {1019, "Eagle Sky Blazer"}, {1020, "Eagle Swoop Diver"}, {1021, "Swamp Skimmer"}, {1022, "Cragger's Fireship"}, {1023, "Croc Command Sub"}, {1024, "Cyber-Guard"}, {1025, "Cyber-Wrecker"}, {1026, "Laser Robot Walker"}, {1027, "K-9"}, {1028, "K-9 Ruff Rover"}, {1029, "K-9 Laser Cutter"}, {1030, "TARDIS"}, {1031, "Laser-Pulse TARDIS"}, {1032, "Energy-Burst TARDIS"}, {1033, "Emmet's Excavator"}, {1034, "Destroy Dozer"}, {1035, "Destruct-o-Mech"}, {1036, "Winged Monkey"}, {1037, "Battle Monkey"}, {1038, "Commander Monkey"}, {1039, "Axe Chariot"}, {1040, "Axe Hurler"}, {1041, "Soaring Chariot"}, {1042, "Shelob the Great"}, {1043, "8-Legged Stalker"}, {1044, "Poison Slinger"}, {1045, "Homer's Car"}, {1047, "The SubmaHomer"}, {1046, "The Homercraft"}, {1048, "Taunt-o-Vision"}, {1050, "The MechaHomer"}, {1049, "Blast Cam"}, {1051, "Velociraptor"}, {1053, "Venom Raptor"}, {1052, "Spike Attack Raptor"}, {1054, "Gyrosphere"}, {1055, "Sonic Beam Gyrosphere"}, {1056, " Gyrosphere"}, {1057, "Clown Bike"}, {1058, "Cannon Bike"}, {1059, "Anti-Gravity Rocket Bike"}, {1060, "Mighty Lion Rider"}, {1061, "Lion Blazer"}, {1062, "Fire Lion"}, {1063, "Arrow Launcher"}, {1064, "Seeking Shooter"}, {1065, "Triple Ballista"}, {1066, "Mystery Machine"}, {1067, "Mystery Tow & Go"}, {1068, "Mystery Monster"}, {1069, "Boulder Bomber"}, {1070, "Boulder Blaster"}, {1071, "Cyclone Jet"}, {1072, "Storm Fighter"}, {1073, "Lightning Jet"}, {1074, "Electro-Shooter"}, {1075, "Blade Bike"}, {1076, "Flight Fire Bike"}, {1077, "Blades of Fire"}, {1078, "Samurai Mech"}, {1079, "Samurai Shooter"}, {1080, "Soaring Samurai Mech"}, {1081, "Companion Cube"}, {1082, "Laser Deflector"}, {1083, "Gold Heart Emitter"}, {1084, "Sentry Turret"}, {1085, "Turret Striker"}, {1086, "Flight Turret Carrier"}, {1087, "Scooby Snack"}, {1088, "Scooby Fire Snack"}, {1089, "Scooby Ghost Snack"}, {1090, "Cloud Cuckoo Car"}, {1091, "X-Stream Soaker"}, {1092, "Rainbow Cannon"}, {1093, "Invisible Jet"}, {1094, "Laser Shooter"}, {1095, "Torpedo Bomber"}, {1096, "NinjaCopter"}, {1097, "Glaciator"}, {1098, "Freeze Fighter"}, {1099, "Travelling Time Train"}, {1100, "Flight Time Train"}, {1101, "Missile Blast Time Train"}, {1102, "Aqua Watercraft"}, {1103, "Seven Seas Speeder"}, {1104, "Trident of Fire"}, {1105, "Drill Driver"}, {1106, "Bane Dig 'n' Drill"}, {1107, "Bane Drill 'n' Blast"}, {1108, "Quinn Mobile"}, {1109, "Quinn Ultra Racer"}, {1110, "Missile Launcher"}, {1111, "The Joker's Chopper"}, {1112, "Mischievous Missile Blaster"}, {1113, "Lock 'n' Laser Jet"}, {1114, "Hover Pod"}, {1115, "Krypton Striker"}, {1116, "Super Stealth Pod"}, {1117, "Dalek"}, {1118, "Fire 'n' Ride Dalek"}, {1119, "Silver Shooter Dalek"}, {1120, "Ecto-1"}, {1121, "Ecto-1 Blaster"}, {1122, "Ecto-1 Water Diver"}, {1123, "Ghost Trap"}, {1124, "Ghost Stun 'n' Trap"}, {1125, "Proton Zapper"}, {1126, "Unknown"}, {1127, "Unknown"}, {1128, "Unknown"}, {1129, "Unknown"}, {1130, "Unknown"}, {1131, "Unknown"}, {1132, "Lloyd's Golden Dragon"}, {1133, "Sword Projector Dragon"}, {1134, "Unknown"}, {1135, "Unknown"}, {1136, "Unknown"}, {1137, "Unknown"}, {1138, "Unknown"}, {1139, "Unknown"}, {1140, "Unknown"}, {1141, "Unknown"}, {1142, "Unknown"}, {1143, "Unknown"}, {1144, "Mega Flight Dragon"}, {1145, "Unknown"}, {1146, "Unknown"}, {1147, "Unknown"}, {1148, "Unknown"}, {1149, "Unknown"}, {1150, "Unknown"}, {1151, "Unknown"}, {1152, "Unknown"}, {1153, "Unknown"}, {1154, "Unknown"}, {1155, "Flying White Dragon"}, {1156, "Golden Fire Dragon"}, {1157, "Ultra Destruction Dragon"}, {1158, "Arcade Machine"}, {1159, "8-Bit Shooter"}, {1160, "The Pixelator"}, {1161, "G-6155 Spy Hunter"}, {1162, "Interdiver"}, {1163, "Aerial Spyhunter"}, {1164, "Slime Shooter"}, {1165, "Slime Exploder"}, {1166, "Slime Streamer"}, {1167, "Terror Dog"}, {1168, "Terror Dog Destroyer"}, {1169, "Soaring Terror Dog"}, {1170, "Ancient Psychic Tandem War Elephant"}, {1171, "Cosmic Squid"}, {1172, "Psychic Submarine"}, {1173, "BMO"}, {1174, "DOGMO"}, {1175, "SNAKEMO"}, {1176, "Jakemobile"}, {1177, "Snail Dude Jake"}, {1178, "Hover Jake"}, {1179, "Lumpy Car"}, {1181, "Lumpy Land Whale"}, {1180, "Lumpy Truck"}, {1182, "Lunatic Amp"}, {1183, "Shadow Scorpion"}, {1184, "Heavy Metal Monster"}, {1185, "B.A.'s Van"}, {1186, "Fool Smasher"}, {1187, "Pain Plane"}, {1188, "Phone Home"}, {1189, "Mobile Uplink"}, {1190, "Super-Charged Satellite"}, {1191, "Niffler"}, {1192, "Sinister Scorpion"}, {1193, "Vicious Vulture"}, {1194, "Swooping Evil"}, {1195, "Brutal Bloom"}, {1196, "Crawling Creeper"}, {1197, "Ecto-1 (2016)"}, {1198, "Ectozer"}, {1199, "PerfEcto"}, {1200, "Flash 'n' Finish"}, {1201, "Rampage Record Player"}, {1202, "Stripe's Throne"}, {1203, "R.C. Racer"}, {1204, "Gadget-O-Matic"}, {1205, "Scarlet Scorpion"}, {1206, "Hogwarts Express"}, {1208, "Steam Warrior"}, {1207, "Soaring Steam Plane"}, {1209, "Enchanted Car"}, {1210, "Shark Sub"}, {1211, "Monstrous Mouth"}, {1212, "IMF Scrambler"}, {1213, "Shock Cycle"}, {1214, "IMF Covert Jet"}, {1215, "IMF Sports Car"}, {1216, "IMF Tank"}, {1217, "IMF Splorer"}, {1218, "Sonic Speedster"}, {1219, "Blue Typhoon"}, {1220, "Moto Bug"}, {1221, "The Tornado"}, {1222, "Crabmeat"}, {1223, "Eggcatcher"}, {1224, "K.I.T.T."}, {1225, "Goliath Armored Semi"}, {1226, "K.I.T.T. Jet"}, {1227, "Police Helicopter"}, {1228, "Police Hovercraft"}, {1229, "Police Plane"}, {1230, "Bionic Steed"}, {1231, "Bat-Raptor"}, {1232, "Ultrabat"}, {1233, "Batwing"}, {1234, "The Black Thunder"}, {1235, "Bat-Tank"}, {1236, "Skeleton Organ"}, {1237, "Skeleton Jukebox"}, {1238, "Skele-Turkey"}, {1239, "One-Eyed Willy's Pirate Ship"}, {1240, "Fanged Fortune"}, {1241, "Inferno Cannon"}, {1242, "Buckbeak"}, {1243, "Giant Owl"}, {1244, "Fierce Falcon"}, {1245, "Saturn's Sandworm"}, {1247, "Haunted Vacuum"}, {1246, "Spooky Spider"}, {1248, "PPG Smartphone"}, {1249, "PPG Hotline"}, {1250, "Powerpuff Mag-Net"}, {1253, "Mega Blast Bot"}, {1251, "Ka-Pow Cannon"}, {1252, "Slammin' Guitar"}, {1254, "Octi"}, {1255, "Super Skunk"}, {1256, "Sonic Squid"}, {1257, "T-Car"}, {1258, "T-Forklift"}, {1259, "T-Plane"}, {1260, "Spellbook of Azarath"}, {1261, "Raven Wings"}, {1262, "Giant Hand"}, {1263, "Titan Robot"}, {1264, "T-Rocket"}, {1265, "Robot Retriever"}}; DimensionsToypadDevice::DimensionsToypadDevice() : Device(0x0E6F, 0x0241, 1, 2, 0) { m_IsOpened = false; } bool DimensionsToypadDevice::Open() { if (!IsOpened()) { m_IsOpened = true; } return true; } void DimensionsToypadDevice::Close() { if (IsOpened()) { m_IsOpened = false; } } bool DimensionsToypadDevice::IsOpened() { return m_IsOpened; } Device::ReadResult DimensionsToypadDevice::Read(ReadMessage* message) { memcpy(message->data, g_dimensionstoypad.GetStatus().data(), message->length); message->bytesRead = message->length; return Device::ReadResult::Success; } Device::WriteResult DimensionsToypadDevice::Write(WriteMessage* message) { if (message->length != 32) return Device::WriteResult::Error; g_dimensionstoypad.SendCommand(std::span<const uint8, 32>{message->data, 32}); message->bytesWritten = message->length; return Device::WriteResult::Success; } bool DimensionsToypadDevice::GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) { uint8 configurationDescriptor[0x29]; uint8* currentWritePtr; // configuration descriptor currentWritePtr = configurationDescriptor + 0; *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower currentWritePtr = currentWritePtr + 9; // configuration descriptor *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol *(uint8*)(currentWritePtr + 8) = 0; // iInterface currentWritePtr = currentWritePtr + 9; // configuration descriptor *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength currentWritePtr = currentWritePtr + 9; // endpoint descriptor 1 *(uint8*)(currentWritePtr + 0) = 7; // bLength *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; // endpoint descriptor 2 *(uint8*)(currentWritePtr + 0) = 7; // bLength *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType *(uint8*)(currentWritePtr + 1) = 0x02; // bEndpointAddress *(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize *(uint8*)(currentWritePtr + 5) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); memcpy(output, configurationDescriptor, std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); return true; } bool DimensionsToypadDevice::SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) { return true; } bool DimensionsToypadDevice::SetProtocol(uint8 ifIndex, uint8 protocol) { cemuLog_log(LogType::Force, "Toypad Protocol"); return true; } bool DimensionsToypadDevice::SetReport(ReportMessage* message) { cemuLog_log(LogType::Force, "Toypad Report"); return true; } std::array<uint8, 32> DimensionsUSB::GetStatus() { std::array<uint8, 32> response = {}; bool responded = false; do { if (!m_queries.empty()) { response = m_queries.front(); m_queries.pop(); responded = true; } else if (!m_figureAddedRemovedResponses.empty() && m_isAwake) { std::lock_guard lock(m_dimensionsMutex); response = m_figureAddedRemovedResponses.front(); m_figureAddedRemovedResponses.pop(); responded = true; } else { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } while (responded == false); return response; } void DimensionsUSB::SendCommand(std::span<const uint8, 32> buf) { const uint8 command = buf[2]; const uint8 sequence = buf[3]; std::array<uint8, 32> q_result{}; switch (command) { case 0xB0: // Wake { // Consistent device response to the wake command q_result = {0x55, 0x0e, 0x01, 0x28, 0x63, 0x29, 0x20, 0x4c, 0x45, 0x47, 0x4f, 0x20, 0x32, 0x30, 0x31, 0x34, 0x46}; break; } case 0xB1: // Seed { // Initialise a random number generator using the seed provided g_dimensionstoypad.GenerateRandomNumber(std::span<const uint8, 8>{buf.begin() + 4, 8}, sequence, q_result); break; } case 0xB3: // Challenge { // Get the next number in the sequence based on the RNG from 0xB1 command g_dimensionstoypad.GetChallengeResponse(std::span<const uint8, 8>{buf.begin() + 4, 8}, sequence, q_result); break; } case 0xC0: // Color case 0xC1: // Get Pad Color case 0xC2: // Fade case 0xC3: // Flash case 0xC4: // Fade Random case 0xC6: // Fade All case 0xC7: // Flash All case 0xC8: // Color All { // Send a blank response to acknowledge color has been sent to toypad q_result = {0x55, 0x01, sequence}; q_result[3] = GenerateChecksum(q_result, 3); break; } case 0xD2: // Read { // Read 4 pages from the figure at index (buf[4]), starting with page buf[5] g_dimensionstoypad.QueryBlock(buf[4], buf[5], q_result, sequence); break; } case 0xD3: // Write { // Write 4 bytes to page buf[5] to the figure at index buf[4] g_dimensionstoypad.WriteBlock(buf[4], buf[5], std::span<const uint8, 4>{buf.begin() + 6, 4}, q_result, sequence); break; } case 0xD4: // Model { // Get the model id of the figure at index buf[4] g_dimensionstoypad.GetModel(std::span<const uint8, 8>{buf.begin() + 4, 8}, sequence, q_result); break; } case 0xD0: // Tag List case 0xE1: // PWD case 0xE5: // Active case 0xFF: // LEDS Query { // Further investigation required cemuLog_log(LogType::Force, "Unimplemented LD Function: {:x}", command); break; } default: { cemuLog_log(LogType::Force, "Unknown LD Function: {:x}", command); break; } } m_queries.push(q_result); } uint32 DimensionsUSB::LoadFigure(const std::array<uint8, 0x2D * 0x04>& buf, std::unique_ptr<FileStream> file, uint8 pad, uint8 index) { std::lock_guard lock(m_dimensionsMutex); const uint32 id = GetFigureId(buf); DimensionsMini& figure = GetFigureByIndex(index); figure.dimFile = std::move(file); figure.id = id; figure.pad = pad; figure.index = index + 1; figure.data = buf; // When a figure is added to the toypad, respond to the game with the pad they were added to, their index, // the direction (0x00 in byte 6 for added) and their UID std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x00, buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]}; figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); m_figureAddedRemovedResponses.push(figureChangeResponse); return id; } bool DimensionsUSB::RemoveFigure(uint8 pad, uint8 index, bool fullRemove) { std::lock_guard lock(m_dimensionsMutex); DimensionsMini& figure = GetFigureByIndex(index); if (figure.index == 255) return false; // When a figure is removed from the toypad, respond to the game with the pad they were removed from, their index, // the direction (0x01 in byte 6 for removed) and their UID if (fullRemove) { std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x01, figure.data[0], figure.data[1], figure.data[2], figure.data[4], figure.data[5], figure.data[6], figure.data[7]}; figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); m_figureAddedRemovedResponses.push(figureChangeResponse); figure.Save(); figure.dimFile.reset(); } figure.index = 255; figure.pad = 255; figure.id = 0; return true; } bool DimensionsUSB::TempRemove(uint8 index) { std::lock_guard lock(m_dimensionsMutex); DimensionsMini& figure = GetFigureByIndex(index); if (figure.index == 255) return false; // Send a response to the game that the figure has been "Picked up" from existing slot, // until either the movement is cancelled, or user chooses a space to move to std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x01, figure.data[0], figure.data[1], figure.data[2], figure.data[4], figure.data[5], figure.data[6], figure.data[7]}; figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); m_figureAddedRemovedResponses.push(figureChangeResponse); return true; } bool DimensionsUSB::CancelRemove(uint8 index) { std::lock_guard lock(m_dimensionsMutex); DimensionsMini& figure = GetFigureByIndex(index); if (figure.index == 255) return false; // Cancel the previous movement of the figure std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x00, figure.data[0], figure.data[1], figure.data[2], figure.data[4], figure.data[5], figure.data[6], figure.data[7]}; figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); m_figureAddedRemovedResponses.push(figureChangeResponse); return true; } bool DimensionsUSB::CreateFigure(fs::path pathName, uint32 id) { FileStream* dimFile(FileStream::createFile2(pathName)); if (!dimFile) return false; std::array<uint8, 0x2D * 0x04> fileData{}; RandomUID(fileData); fileData[3] = id & 0xFF; std::array<uint8, 7> uid = {fileData[0], fileData[1], fileData[2], fileData[4], fileData[5], fileData[6], fileData[7]}; // Only characters are created with their ID encrypted and stored in pages 36 and 37, // as well as a password stored in page 43. Blank tags have their information populated // by the game when it calls the write_block command. if (id != 0) { const std::array<uint8, 16> figureKey = GenerateFigureKey(fileData); std::array<uint8, 8> valueToEncrypt = {uint8(id & 0xFF), uint8((id >> 8) & 0xFF), uint8((id >> 16) & 0xFF), uint8((id >> 24) & 0xFF), uint8(id & 0xFF), uint8((id >> 8) & 0xFF), uint8((id >> 16) & 0xFF), uint8((id >> 24) & 0xFF)}; std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, figureKey); std::memcpy(&fileData[36 * 4], &encrypted[0], 4); std::memcpy(&fileData[37 * 4], &encrypted[4], 4); std::memcpy(&fileData[43 * 4], PWDGenerate(fileData).data(), 4); } else { // Page 38 is used as verification for blank tags fileData[(38 * 4) + 1] = 1; } if (fileData.size() != dimFile->writeData(fileData.data(), fileData.size())) { delete dimFile; return false; } delete dimFile; return true; } bool DimensionsUSB::MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex) { if (oldIndex == index) { // Don't bother removing and loading again, just send response to the game CancelRemove(index); return true; } // When moving figures between spaces on the toypad, remove any figure from the space they are moving to, // then remove them from their current space, then load them to the space they are moving to RemoveFigure(pad, index, true); DimensionsMini& figure = GetFigureByIndex(oldIndex); const std::array<uint8, 0x2D * 0x04> data = figure.data; std::unique_ptr<FileStream> inFile = std::move(figure.dimFile); RemoveFigure(oldPad, oldIndex, false); LoadFigure(data, std::move(inFile), pad, index); return true; } void DimensionsUSB::GenerateRandomNumber(std::span<const uint8, 8> buf, uint8 sequence, std::array<uint8, 32>& replyBuf) { // Decrypt payload into an 8 byte array std::array<uint8, 8> value = Decrypt(buf, std::nullopt); // Seed is the first 4 bytes (little endian) of the decrypted payload uint32 seed = (uint32&)value[0]; // Confirmation is the second 4 bytes (big endian) of the decrypted payload uint32 conf = (uint32be&)value[4]; // Initialize rng using the seed from decrypted payload InitializeRNG(seed); // Encrypt 8 bytes, first 4 bytes is the decrypted confirmation from payload, 2nd 4 bytes are blank std::array<uint8, 8> valueToEncrypt = {value[4], value[5], value[6], value[7], 0, 0, 0, 0}; std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, std::nullopt); replyBuf[0] = 0x55; replyBuf[1] = 0x09; replyBuf[2] = sequence; // Copy encrypted value to response data memcpy(&replyBuf[3], encrypted.data(), encrypted.size()); replyBuf[11] = GenerateChecksum(replyBuf, 11); } void DimensionsUSB::GetChallengeResponse(std::span<const uint8, 8> buf, uint8 sequence, std::array<uint8, 32>& replyBuf) { // Decrypt payload into an 8 byte array std::array<uint8, 8> value = Decrypt(buf, std::nullopt); // Confirmation is the first 4 bytes of the decrypted payload uint32 conf = (uint32be&)value[0]; // Generate next random number based on RNG uint32 nextRandom = GetNext(); // Encrypt an 8 byte array, first 4 bytes are the next random number (little endian) // followed by the confirmation from the decrypted payload std::array<uint8, 8> valueToEncrypt = {uint8(nextRandom & 0xFF), uint8((nextRandom >> 8) & 0xFF), uint8((nextRandom >> 16) & 0xFF), uint8((nextRandom >> 24) & 0xFF), value[0], value[1], value[2], value[3]}; std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, std::nullopt); replyBuf[0] = 0x55; replyBuf[1] = 0x09; replyBuf[2] = sequence; // Copy encrypted value to response data memcpy(&replyBuf[3], encrypted.data(), encrypted.size()); replyBuf[11] = GenerateChecksum(replyBuf, 11); if (!m_isAwake) m_isAwake = true; } void DimensionsUSB::InitializeRNG(uint32 seed) { m_randomA = 0xF1EA5EED; m_randomB = seed; m_randomC = seed; m_randomD = seed; for (int i = 0; i < 42; i++) { GetNext(); } } uint32 DimensionsUSB::GetNext() { uint32 e = m_randomA - std::rotl(m_randomB, 21); m_randomA = m_randomB ^ std::rotl(m_randomC, 19); m_randomB = m_randomC + std::rotl(m_randomD, 6); m_randomC = m_randomD + e; m_randomD = e + m_randomA; return m_randomD; } std::array<uint8, 8> DimensionsUSB::Decrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key) { // Value to decrypt is separated in to two little endian 32 bit unsigned integers uint32 dataOne = (uint32&)buf[0]; uint32 dataTwo = (uint32&)buf[4]; // Use the key as 4 32 bit little endian unsigned integers uint32 keyOne; uint32 keyTwo; uint32 keyThree; uint32 keyFour; if (key) { keyOne = (uint32&)key.value()[0]; keyTwo = (uint32&)key.value()[4]; keyThree = (uint32&)key.value()[8]; keyFour = (uint32&)key.value()[12]; } else { keyOne = (uint32&)COMMAND_KEY[0]; keyTwo = (uint32&)COMMAND_KEY[4]; keyThree = (uint32&)COMMAND_KEY[8]; keyFour = (uint32&)COMMAND_KEY[12]; } uint32 sum = 0xC6EF3720; uint32 delta = 0x9E3779B9; for (int i = 0; i < 32; i++) { dataTwo -= (((dataOne << 4) + keyThree) ^ (dataOne + sum) ^ ((dataOne >> 5) + keyFour)); dataOne -= (((dataTwo << 4) + keyOne) ^ (dataTwo + sum) ^ ((dataTwo >> 5) + keyTwo)); sum -= delta; } cemu_assert(sum == 0); std::array<uint8, 8> decrypted = {uint8(dataOne & 0xFF), uint8((dataOne >> 8) & 0xFF), uint8((dataOne >> 16) & 0xFF), uint8((dataOne >> 24) & 0xFF), uint8(dataTwo & 0xFF), uint8((dataTwo >> 8) & 0xFF), uint8((dataTwo >> 16) & 0xFF), uint8((dataTwo >> 24) & 0xFF)}; return decrypted; } std::array<uint8, 8> DimensionsUSB::Encrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key) { // Value to encrypt is separated in to two little endian 32 bit unsigned integers uint32 dataOne = (uint32&)buf[0]; uint32 dataTwo = (uint32&)buf[4]; // Use the key as 4 32 bit little endian unsigned integers uint32 keyOne; uint32 keyTwo; uint32 keyThree; uint32 keyFour; if (key) { keyOne = (uint32&)key.value()[0]; keyTwo = (uint32&)key.value()[4]; keyThree = (uint32&)key.value()[8]; keyFour = (uint32&)key.value()[12]; } else { keyOne = (uint32&)COMMAND_KEY[0]; keyTwo = (uint32&)COMMAND_KEY[4]; keyThree = (uint32&)COMMAND_KEY[8]; keyFour = (uint32&)COMMAND_KEY[12]; } uint32 sum = 0; uint32 delta = 0x9E3779B9; for (int i = 0; i < 32; i++) { sum += delta; dataOne += (((dataTwo << 4) + keyOne) ^ (dataTwo + sum) ^ ((dataTwo >> 5) + keyTwo)); dataTwo += (((dataOne << 4) + keyThree) ^ (dataOne + sum) ^ ((dataOne >> 5) + keyFour)); } cemu_assert(sum == 0xC6EF3720); std::array<uint8, 8> encrypted = {uint8(dataOne & 0xFF), uint8((dataOne >> 8) & 0xFF), uint8((dataOne >> 16) & 0xFF), uint8((dataOne >> 24) & 0xFF), uint8(dataTwo & 0xFF), uint8((dataTwo >> 8) & 0xFF), uint8((dataTwo >> 16) & 0xFF), uint8((dataTwo >> 24) & 0xFF)}; return encrypted; } std::array<uint8, 16> DimensionsUSB::GenerateFigureKey(const std::array<uint8, 0x2D * 0x04>& buf) { std::array<uint8, 7> uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]}; uint32 scrambleA = Scramble(uid, 3); uint32 scrambleB = Scramble(uid, 4); uint32 scrambleC = Scramble(uid, 5); uint32 scrambleD = Scramble(uid, 6); return {uint8((scrambleA >> 24) & 0xFF), uint8((scrambleA >> 16) & 0xFF), uint8((scrambleA >> 8) & 0xFF), uint8(scrambleA & 0xFF), uint8((scrambleB >> 24) & 0xFF), uint8((scrambleB >> 16) & 0xFF), uint8((scrambleB >> 8) & 0xFF), uint8(scrambleB & 0xFF), uint8((scrambleC >> 24) & 0xFF), uint8((scrambleC >> 16) & 0xFF), uint8((scrambleC >> 8) & 0xFF), uint8(scrambleC & 0xFF), uint8((scrambleD >> 24) & 0xFF), uint8((scrambleD >> 16) & 0xFF), uint8((scrambleD >> 8) & 0xFF), uint8(scrambleD & 0xFF)}; } uint32 DimensionsUSB::Scramble(const std::array<uint8, 7>& uid, uint8 count) { std::vector<uint8> toScramble; toScramble.reserve(uid.size() + CHAR_CONSTANT.size()); for (uint8 x : uid) { toScramble.push_back(x); } for (uint8 c : CHAR_CONSTANT) { toScramble.push_back(c); } toScramble[(count * 4) - 1] = 0xaa; std::array<uint8, 4> randomized = DimensionsRandomize(toScramble, count); return (uint32be&)randomized[0]; } std::array<uint8, 4> DimensionsUSB::PWDGenerate(const std::array<uint8, 0x2D * 0x04>& buf) { std::array<uint8, 7> uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]}; std::vector<uint8> pwdCalc = {PWD_CONSTANT.begin(), PWD_CONSTANT.end() - 1}; for (uint8 i = 0; i < uid.size(); i++) { pwdCalc.insert(pwdCalc.begin() + i, uid[i]); } return DimensionsRandomize(pwdCalc, 8); } std::array<uint8, 4> DimensionsUSB::DimensionsRandomize(const std::vector<uint8> key, uint8 count) { uint32 scrambled = 0; for (uint8 i = 0; i < count; i++) { const uint32 v4 = std::rotr(scrambled, 25); const uint32 v5 = std::rotr(scrambled, 10); const uint32 b = (uint32&)key[i * 4]; scrambled = b + v4 + v5 - scrambled; } return {uint8(scrambled & 0xFF), uint8(scrambled >> 8 & 0xFF), uint8(scrambled >> 16 & 0xFF), uint8(scrambled >> 24 & 0xFF)}; } uint32 DimensionsUSB::GetFigureId(const std::array<uint8, 0x2D * 0x04>& buf) { const std::array<uint8, 16> figureKey = GenerateFigureKey(buf); const std::span<const uint8, 8> modelNumber = std::span<const uint8, 8>{buf.begin() + (36 * 4), 8}; const std::array<uint8, 8> decrypted = Decrypt(modelNumber, figureKey); const uint32 figNum = (uint32&)decrypted[0]; // Characters have their model number encrypted in page 36 if (figNum < 1000) { return figNum; } // Vehicles/Gadgets have their model number written as little endian in page 36 return (uint32&)modelNumber[0]; } DimensionsUSB::DimensionsMini& DimensionsUSB::GetFigureByIndex(uint8 index) { return m_figures[index]; } void DimensionsUSB::QueryBlock(uint8 index, uint8 page, std::array<uint8, 32>& replyBuf, uint8 sequence) { std::lock_guard lock(m_dimensionsMutex); replyBuf[0] = 0x55; replyBuf[1] = 0x12; replyBuf[2] = sequence; replyBuf[3] = 0x00; // Index from game begins at 1 rather than 0, so minus 1 here if (const uint8 figureIndex = index - 1; figureIndex < 7) { const DimensionsMini& figure = GetFigureByIndex(figureIndex); // Query 4 pages of 4 bytes from the figure, copy this to the response if (figure.index != 255 && (4 * page) < ((0x2D * 4) - 16)) { std::memcpy(&replyBuf[4], figure.data.data() + (4 * page), 16); } } replyBuf[20] = GenerateChecksum(replyBuf, 20); } void DimensionsUSB::WriteBlock(uint8 index, uint8 page, std::span<const uint8, 4> toWriteBuf, std::array<uint8, 32>& replyBuf, uint8 sequence) { std::lock_guard lock(m_dimensionsMutex); replyBuf[0] = 0x55; replyBuf[1] = 0x02; replyBuf[2] = sequence; replyBuf[3] = 0x00; // Index from game begins at 1 rather than 0, so minus 1 here if (const uint8 figureIndex = index - 1; figureIndex < 7) { DimensionsMini& figure = GetFigureByIndex(figureIndex); // Copy 4 bytes to the page on the figure requested by the game if (figure.index != 255 && page < 0x2D) { // Id is written to page 36 if (page == 36) { figure.id = (uint32&)toWriteBuf[0]; } std::memcpy(figure.data.data() + (page * 4), toWriteBuf.data(), 4); figure.Save(); } } replyBuf[4] = GenerateChecksum(replyBuf, 4); } void DimensionsUSB::GetModel(std::span<const uint8, 8> buf, uint8 sequence, std::array<uint8, 32>& replyBuf) { // Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation std::array<uint8, 8> value = Decrypt(buf, std::nullopt); uint8 index = value[0]; uint32 conf = (uint32be&)value[4]; // Response is the figure's id (little endian) followed by the confirmation from payload // Index from game begins at 1 rather than 0, so minus 1 here std::array<uint8, 8> valueToEncrypt = {}; if (const uint8 figureIndex = index - 1; figureIndex < 7) { const DimensionsMini& figure = GetFigureByIndex(figureIndex); valueToEncrypt = {uint8(figure.id & 0xFF), uint8((figure.id >> 8) & 0xFF), uint8((figure.id >> 16) & 0xFF), uint8((figure.id >> 24) & 0xFF), value[4], value[5], value[6], value[7]}; } std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, std::nullopt); replyBuf[0] = 0x55; replyBuf[1] = 0x0a; replyBuf[2] = sequence; replyBuf[3] = 0x00; memcpy(&replyBuf[4], encrypted.data(), encrypted.size()); replyBuf[12] = GenerateChecksum(replyBuf, 12); } void DimensionsUSB::RandomUID(std::array<uint8, 0x2D * 0x04>& uid_buffer) { uid_buffer[0] = 0x04; uid_buffer[7] = 0x80; std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution<int> dist(0, 255); uid_buffer[1] = dist(mt); uid_buffer[2] = dist(mt); uid_buffer[4] = dist(mt); uid_buffer[5] = dist(mt); uid_buffer[6] = dist(mt); } uint8 DimensionsUSB::GenerateChecksum(const std::array<uint8, 32>& data, int num_of_bytes) const { int checksum = 0; for (int i = 0; i < num_of_bytes; i++) { checksum += data[i]; } return (checksum & 0xFF); } void DimensionsUSB::DimensionsMini::Save() { if (!dimFile) return; dimFile->SetPosition(0); dimFile->writeData(data.data(), data.size()); } std::map<const uint32, const char*> DimensionsUSB::GetListMinifigs() { return s_listMinis; } std::map<const uint32, const char*> DimensionsUSB::GetListTokens() { return s_listTokens; } std::string DimensionsUSB::FindFigure(uint32 figNum) { for (const auto& it : GetListMinifigs()) { if (it.first == figNum) { return it.second; } } for (const auto& it : GetListTokens()) { if (it.first == figNum) { return it.second; } } return fmt::format("Unknown ({})", figNum); } } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/Dimensions.h ================================================ #include <mutex> #include "nsyshid.h" #include "Backend.h" #include "Common/FileStream.h" namespace nsyshid { class DimensionsToypadDevice final : public Device { public: DimensionsToypadDevice(); ~DimensionsToypadDevice() = default; bool Open() override; void Close() override; bool IsOpened() override; ReadResult Read(ReadMessage* message) override; WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) override; bool SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) override; bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; private: bool m_IsOpened; }; class DimensionsUSB { public: struct DimensionsMini final { std::unique_ptr<FileStream> dimFile; std::array<uint8, 0x2D * 0x04> data{}; uint8 index = 255; uint8 pad = 255; uint32 id = 0; void Save(); }; void SendCommand(std::span<const uint8, 32> buf); std::array<uint8, 32> GetStatus(); void GenerateRandomNumber(std::span<const uint8, 8> buf, uint8 sequence, std::array<uint8, 32>& replyBuf); void InitializeRNG(uint32 seed); void GetChallengeResponse(std::span<const uint8, 8> buf, uint8 sequence, std::array<uint8, 32>& replyBuf); void QueryBlock(uint8 index, uint8 page, std::array<uint8, 32>& replyBuf, uint8 sequence); void WriteBlock(uint8 index, uint8 page, std::span<const uint8, 4> toWriteBuf, std::array<uint8, 32>& replyBuf, uint8 sequence); void GetModel(std::span<const uint8, 8> buf, uint8 sequence, std::array<uint8, 32>& replyBuf); bool RemoveFigure(uint8 pad, uint8 index, bool fullRemove); bool TempRemove(uint8 index); bool CancelRemove(uint8 index); uint32 LoadFigure(const std::array<uint8, 0x2D * 0x04>& buf, std::unique_ptr<FileStream> file, uint8 pad, uint8 index); bool CreateFigure(fs::path pathName, uint32 id); bool MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex); static std::map<const uint32, const char*> GetListMinifigs(); static std::map<const uint32, const char*> GetListTokens(); std::string FindFigure(uint32 figNum); protected: std::mutex m_dimensionsMutex; std::array<DimensionsMini, 7> m_figures{}; private: void RandomUID(std::array<uint8, 0x2D * 0x04>& uidBuffer); uint8 GenerateChecksum(const std::array<uint8, 32>& data, int numOfBytes) const; std::array<uint8, 8> Decrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key); std::array<uint8, 8> Encrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key); std::array<uint8, 16> GenerateFigureKey(const std::array<uint8, 0x2D * 0x04>& uid); std::array<uint8, 4> PWDGenerate(const std::array<uint8, 0x2D * 0x04>& uid); std::array<uint8, 4> DimensionsRandomize(const std::vector<uint8> key, uint8 count); uint32 GetFigureId(const std::array<uint8, 0x2D * 0x04>& buf); uint32 Scramble(const std::array<uint8, 7>& uid, uint8 count); uint32 GetNext(); DimensionsMini& GetFigureByIndex(uint8 index); uint32 m_randomA; uint32 m_randomB; uint32 m_randomC; uint32 m_randomD; bool m_isAwake = false; std::queue<std::array<uint8, 32>> m_figureAddedRemovedResponses; std::queue<std::array<uint8, 32>> m_queries; }; extern DimensionsUSB g_dimensionstoypad; } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/Infinity.cpp ================================================ #include "Infinity.h" #include <random> #include "nsyshid.h" #include "Backend.h" #include "util/crypto/aes128.h" #include <openssl/crypto.h> #include "openssl/sha.h" namespace nsyshid { static constexpr std::array<uint8, 32> SHA1_CONSTANT = { 0xAF, 0x62, 0xD2, 0xEC, 0x04, 0x91, 0x96, 0x8C, 0xC5, 0x2A, 0x1A, 0x71, 0x65, 0xF8, 0x65, 0xFE, 0x28, 0x63, 0x29, 0x20, 0x44, 0x69, 0x73, 0x6e, 0x65, 0x79, 0x20, 0x32, 0x30, 0x31, 0x33}; InfinityUSB g_infinitybase; const std::map<const uint32, const std::pair<const uint8, const char*>> s_listFigures = { {0x0F4241, {1, "Mr. Incredible"}}, {0x0F4242, {1, "Sulley"}}, {0x0F4243, {1, "Jack Sparrow"}}, {0x0F4244, {1, "Lone Ranger"}}, {0x0F4245, {1, "Tonto"}}, {0x0F4246, {1, "Lightning McQueen"}}, {0x0F4247, {1, "Holley Shiftwell"}}, {0x0F4248, {1, "Buzz Lightyear"}}, {0x0F4249, {1, "Jessie"}}, {0x0F424A, {1, "Mike"}}, {0x0F424B, {1, "Mrs. Incredible"}}, {0x0F424C, {1, "Hector Barbossa"}}, {0x0F424D, {1, "Davy Jones"}}, {0x0F424E, {1, "Randy"}}, {0x0F424F, {1, "Syndrome"}}, {0x0F4250, {1, "Woody"}}, {0x0F4251, {1, "Mater"}}, {0x0F4252, {1, "Dash"}}, {0x0F4253, {1, "Violet"}}, {0x0F4254, {1, "Francesco Bernoulli"}}, {0x0F4255, {1, "Sorcerer's Apprentice Mickey"}}, {0x0F4256, {1, "Jack Skellington"}}, {0x0F4257, {1, "Rapunzel"}}, {0x0F4258, {1, "Anna"}}, {0x0F4259, {1, "Elsa"}}, {0x0F425A, {1, "Phineas"}}, {0x0F425B, {1, "Agent P"}}, {0x0F425C, {1, "Wreck-It Ralph"}}, {0x0F425D, {1, "Vanellope"}}, {0x0F425E, {1, "Mr. Incredible (Crystal)"}}, {0x0F425F, {1, "Jack Sparrow (Crystal)"}}, {0x0F4260, {1, "Sulley (Crystal)"}}, {0x0F4261, {1, "Lightning McQueen (Crystal)"}}, {0x0F4262, {1, "Lone Ranger (Crystal)"}}, {0x0F4263, {1, "Buzz Lightyear (Crystal)"}}, {0x0F4264, {1, "Agent P (Crystal)"}}, {0x0F4265, {1, "Sorcerer's Apprentice Mickey (Crystal)"}}, {0x0F4266, {1, "Buzz Lightyear (Glowing)"}}, {0x0F42A4, {2, "Captain America"}}, {0x0F42A5, {2, "Hulk"}}, {0x0F42A6, {2, "Iron Man"}}, {0x0F42A7, {2, "Thor"}}, {0x0F42A8, {2, "Groot"}}, {0x0F42A9, {2, "Rocket Raccoon"}}, {0x0F42AA, {2, "Star-Lord"}}, {0x0F42AB, {2, "Spider-Man"}}, {0x0F42AC, {2, "Nick Fury"}}, {0x0F42AD, {2, "Black Widow"}}, {0x0F42AE, {2, "Hawkeye"}}, {0x0F42AF, {2, "Drax"}}, {0x0F42B0, {2, "Gamora"}}, {0x0F42B1, {2, "Iron Fist"}}, {0x0F42B2, {2, "Nova"}}, {0x0F42B3, {2, "Venom"}}, {0x0F42B4, {2, "Donald Duck"}}, {0x0F42B5, {2, "Aladdin"}}, {0x0F42B6, {2, "Stitch"}}, {0x0F42B7, {2, "Merida"}}, {0x0F42B8, {2, "Tinker Bell"}}, {0x0F42B9, {2, "Maleficent"}}, {0x0F42BA, {2, "Hiro"}}, {0x0F42BB, {2, "Baymax"}}, {0x0F42BC, {2, "Loki"}}, {0x0F42BD, {2, "Ronan"}}, {0x0F42BE, {2, "Green Goblin"}}, {0x0F42BF, {2, "Falcon"}}, {0x0F42C0, {2, "Yondu"}}, {0x0F42C1, {2, "Jasmine"}}, {0x0F42C6, {2, "Black Suit Spider-Man"}}, {0x0F42D6, {3, "Sam Flynn"}}, {0x0F42D7, {3, "Quorra"}}, {0x0F4308, {3, "Anakin Skywalker"}}, {0x0F4309, {3, "Obi-Wan Kenobi"}}, {0x0F430A, {3, "Yoda"}}, {0x0F430B, {3, "Ahsoka Tano"}}, {0x0F430C, {3, "Darth Maul"}}, {0x0F430E, {3, "Luke Skywalker"}}, {0x0F430F, {3, "Han Solo"}}, {0x0F4310, {3, "Princess Leia"}}, {0x0F4311, {3, "Chewbacca"}}, {0x0F4312, {3, "Darth Vader"}}, {0x0F4313, {3, "Boba Fett"}}, {0x0F4314, {3, "Ezra Bridger"}}, {0x0F4315, {3, "Kanan Jarrus"}}, {0x0F4316, {3, "Sabine Wren"}}, {0x0F4317, {3, "Zeb Orrelios"}}, {0x0F4318, {3, "Joy"}}, {0x0F4319, {3, "Anger"}}, {0x0F431A, {3, "Fear"}}, {0x0F431B, {3, "Sadness"}}, {0x0F431C, {3, "Disgust"}}, {0x0F431D, {3, "Mickey Mouse"}}, {0x0F431E, {3, "Minnie Mouse"}}, {0x0F431F, {3, "Mulan"}}, {0x0F4320, {3, "Olaf"}}, {0x0F4321, {3, "Vision"}}, {0x0F4322, {3, "Ultron"}}, {0x0F4323, {3, "Ant-Man"}}, {0x0F4325, {3, "Captain America - The First Avenger"}}, {0x0F4326, {3, "Finn"}}, {0x0F4327, {3, "Kylo Ren"}}, {0x0F4328, {3, "Poe Dameron"}}, {0x0F4329, {3, "Rey"}}, {0x0F432B, {3, "Spot"}}, {0x0F432C, {3, "Nick Wilde"}}, {0x0F432D, {3, "Judy Hopps"}}, {0x0F432E, {3, "Hulkbuster"}}, {0x0F432F, {3, "Anakin Skywalker (Light FX)"}}, {0x0F4330, {3, "Obi-Wan Kenobi (Light FX)"}}, {0x0F4331, {3, "Yoda (Light FX)"}}, {0x0F4332, {3, "Luke Skywalker (Light FX)"}}, {0x0F4333, {3, "Darth Vader (Light FX)"}}, {0x0F4334, {3, "Kanan Jarrus (Light FX)"}}, {0x0F4335, {3, "Kylo Ren (Light FX)"}}, {0x0F4336, {3, "Black Panther"}}, {0x0F436C, {3, "Nemo"}}, {0x0F436D, {3, "Dory"}}, {0x0F436E, {3, "Baloo"}}, {0x0F436F, {3, "Alice"}}, {0x0F4370, {3, "Mad Hatter"}}, {0x0F4371, {3, "Time"}}, {0x0F4372, {3, "Peter Pan"}}, {0x1E8481, {1, "Starter Play Set"}}, {0x1E8482, {1, "Lone Ranger Play Set"}}, {0x1E8483, {1, "Cars Play Set"}}, {0x1E8484, {1, "Toy Story in Space Play Set"}}, {0x1E84E4, {2, "Marvel's The Avengers Play Set"}}, {0x1E84E5, {2, "Marvel's Spider-Man Play Set"}}, {0x1E84E6, {2, "Marvel's Guardians of the Galaxy Play Set"}}, {0x1E84E7, {2, "Assault on Asgard"}}, {0x1E84E8, {2, "Escape from the Kyln"}}, {0x1E84E9, {2, "Stitch's Tropical Rescue"}}, {0x1E84EA, {2, "Brave Forest Siege"}}, {0x1E8548, {3, "Inside Out Play Set"}}, {0x1E8549, {3, "Star Wars: Twilight of the Republic Play Set"}}, {0x1E854A, {3, "Star Wars: Rise Against the Empire Play Set"}}, {0x1E854B, {3, "Star Wars: The Force Awakens Play Set"}}, {0x1E854C, {3, "Marvel Battlegrounds Play Set"}}, {0x1E854D, {3, "Toy Box Speedway"}}, {0x1E854E, {3, "Toy Box Takeover"}}, {0x1E85AC, {3, "Finding Dory Play Set"}}, {0x2DC6C3, {1, "Bolt's Super Strength"}}, {0x2DC6C4, {1, "Ralph's Power of Destruction"}}, {0x2DC6C5, {1, "Chernabog's Power"}}, {0x2DC6C6, {1, "C.H.R.O.M.E. Damage Increaser"}}, {0x2DC6C7, {1, "Dr. Doofenshmirtz's Damage-Inator!"}}, {0x2DC6C8, {1, "Electro-Charge"}}, {0x2DC6C9, {1, "Fix-It Felix's Repair Power"}}, {0x2DC6CA, {1, "Rapunzel's Healing"}}, {0x2DC6CB, {1, "C.H.R.O.M.E. Armor Shield"}}, {0x2DC6CC, {1, "Star Command Shield"}}, {0x2DC6CD, {1, "Violet's Force Field"}}, {0x2DC6CE, {1, "Pieces of Eight"}}, {0x2DC6CF, {1, "Scrooge McDuck's Lucky Dime"}}, {0x2DC6D0, {1, "User Control"}}, {0x2DC6D1, {1, "Sorcerer Mickey's Hat"}}, {0x2DC6FE, {1, "Emperor Zurg's Wrath"}}, {0x2DC6FF, {1, "Merlin's Summon"}}, {0x2DC765, {2, "Enchanted Rose"}}, {0x2DC766, {2, "Mulan's Training Uniform"}}, {0x2DC767, {2, "Flubber"}}, {0x2DC768, {2, "S.H.I.E.L.D. Helicarrier Strike"}}, {0x2DC769, {2, "Zeus' Thunderbolts"}}, {0x2DC76A, {2, "King Louie's Monkeys"}}, {0x2DC76B, {2, "Infinity Gauntlet"}}, {0x2DC76D, {2, "Sorcerer Supreme"}}, {0x2DC76E, {2, "Maleficent's Spell Cast"}}, {0x2DC76F, {2, "Chernabog's Spirit Cyclone"}}, {0x2DC770, {2, "Marvel Team-Up: Capt. Marvel"}}, {0x2DC771, {2, "Marvel Team-Up: Iron Patriot"}}, {0x2DC772, {2, "Marvel Team-Up: Ant-Man"}}, {0x2DC773, {2, "Marvel Team-Up: White Tiger"}}, {0x2DC774, {2, "Marvel Team-Up: Yondu"}}, {0x2DC775, {2, "Marvel Team-Up: Winter Soldier"}}, {0x2DC776, {2, "Stark Arc Reactor"}}, {0x2DC777, {2, "Gamma Rays"}}, {0x2DC778, {2, "Alien Symbiote"}}, {0x2DC779, {2, "All for One"}}, {0x2DC77A, {2, "Sandy Claws Surprise"}}, {0x2DC77B, {2, "Glory Days"}}, {0x2DC77C, {2, "Cursed Pirate Gold"}}, {0x2DC77D, {2, "Sentinel of Liberty"}}, {0x2DC77E, {2, "The Immortal Iron Fist"}}, {0x2DC77F, {2, "Space Armor"}}, {0x2DC780, {2, "Rags to Riches"}}, {0x2DC781, {2, "Ultimate Falcon"}}, {0x2DC788, {3, "Tomorrowland Time Bomb"}}, {0x2DC78E, {3, "Galactic Team-Up: Mace Windu"}}, {0x2DC791, {3, "Luke's Rebel Alliance Flight Suit Costume"}}, {0x2DC798, {3, "Finn's Stormtrooper Costume"}}, {0x2DC799, {3, "Poe's Resistance Jacket"}}, {0x2DC79A, {3, "Resistance Tactical Strike"}}, {0x2DC79E, {3, "Officer Nick Wilde"}}, {0x2DC79F, {3, "Meter Maid Judy"}}, {0x2DC7A2, {3, "Darkhawk's Blast"}}, {0x2DC7A3, {3, "Cosmic Cube Blast"}}, {0x2DC7A4, {3, "Princess Leia's Boushh Disguise"}}, {0x2DC7A6, {3, "Nova Corps Strike"}}, {0x2DC7A7, {3, "King Mickey"}}, {0x3D0912, {1, "Mickey's Car"}}, {0x3D0913, {1, "Cinderella's Coach"}}, {0x3D0914, {1, "Electric Mayhem Bus"}}, {0x3D0915, {1, "Cruella De Vil's Car"}}, {0x3D0916, {1, "Pizza Planet Delivery Truck"}}, {0x3D0917, {1, "Mike's New Car"}}, {0x3D0919, {1, "Parking Lot Tram"}}, {0x3D091A, {1, "Captain Hook's Ship"}}, {0x3D091B, {1, "Dumbo"}}, {0x3D091C, {1, "Calico Helicopter"}}, {0x3D091D, {1, "Maximus"}}, {0x3D091E, {1, "Angus"}}, {0x3D091F, {1, "Abu the Elephant"}}, {0x3D0920, {1, "Headless Horseman's Horse"}}, {0x3D0921, {1, "Phillipe"}}, {0x3D0922, {1, "Khan"}}, {0x3D0923, {1, "Tantor"}}, {0x3D0924, {1, "Dragon Firework Cannon"}}, {0x3D0925, {1, "Stitch's Blaster"}}, {0x3D0926, {1, "Toy Story Mania Blaster"}}, {0x3D0927, {1, "Flamingo Croquet Mallet"}}, {0x3D0928, {1, "Carl Fredricksen's Cane"}}, {0x3D0929, {1, "Hangin' Ten Stitch With Surfboard"}}, {0x3D092A, {1, "Condorman Glider"}}, {0x3D092B, {1, "WALL-E's Fire Extinguisher"}}, {0x3D092C, {1, "On the Grid"}}, {0x3D092D, {1, "WALL-E's Collection"}}, {0x3D092E, {1, "King Candy's Dessert Toppings"}}, {0x3D0930, {1, "Victor's Experiments"}}, {0x3D0931, {1, "Jack's Scary Decorations"}}, {0x3D0933, {1, "Frozen Flourish"}}, {0x3D0934, {1, "Rapunzel's Kingdom"}}, {0x3D0935, {1, "TRON Interface"}}, {0x3D0936, {1, "Buy N Large Atmosphere"}}, {0x3D0937, {1, "Sugar Rush Sky"}}, {0x3D0939, {1, "New Holland Skyline"}}, {0x3D093A, {1, "Halloween Town Sky"}}, {0x3D093C, {1, "Chill in the Air"}}, {0x3D093D, {1, "Rapunzel's Birthday Sky"}}, {0x3D0940, {1, "Astro Blasters Space Cruiser"}}, {0x3D0941, {1, "Marlin's Reef"}}, {0x3D0942, {1, "Nemo's Seascape"}}, {0x3D0943, {1, "Alice's Wonderland"}}, {0x3D0944, {1, "Tulgey Wood"}}, {0x3D0945, {1, "Tri-State Area Terrain"}}, {0x3D0946, {1, "Danville Sky"}}, {0x3D0965, {2, "Stark Tech"}}, {0x3D0966, {2, "Spider-Streets"}}, {0x3D0967, {2, "World War Hulk"}}, {0x3D0968, {2, "Gravity Falls Forest"}}, {0x3D0969, {2, "Neverland"}}, {0x3D096A, {2, "Simba's Pridelands"}}, {0x3D096C, {2, "Calhoun's Command"}}, {0x3D096D, {2, "Star-Lord's Galaxy"}}, {0x3D096E, {2, "Dinosaur World"}}, {0x3D096F, {2, "Groot's Roots"}}, {0x3D0970, {2, "Mulan's Countryside"}}, {0x3D0971, {2, "The Sands of Agrabah"}}, {0x3D0974, {2, "A Small World"}}, {0x3D0975, {2, "View from the Suit"}}, {0x3D0976, {2, "Spider-Sky"}}, {0x3D0977, {2, "World War Hulk Sky"}}, {0x3D0978, {2, "Gravity Falls Sky"}}, {0x3D0979, {2, "Second Star to the Right"}}, {0x3D097A, {2, "The King's Domain"}}, {0x3D097C, {2, "CyBug Swarm"}}, {0x3D097D, {2, "The Rip"}}, {0x3D097E, {2, "Forgotten Skies"}}, {0x3D097F, {2, "Groot's View"}}, {0x3D0980, {2, "The Middle Kingdom"}}, {0x3D0984, {2, "Skies of the World"}}, {0x3D0985, {2, "S.H.I.E.L.D. Containment Truck"}}, {0x3D0986, {2, "Main Street Electrical Parade Float"}}, {0x3D0987, {2, "Mr. Toad's Motorcar"}}, {0x3D0988, {2, "Le Maximum"}}, {0x3D0989, {2, "Alice in Wonderland's Caterpillar"}}, {0x3D098A, {2, "Eglantine's Motorcycle"}}, {0x3D098B, {2, "Medusa's Swamp Mobile"}}, {0x3D098C, {2, "Hydra Motorcycle"}}, {0x3D098D, {2, "Darkwing Duck's Ratcatcher"}}, {0x3D098F, {2, "The USS Swinetrek"}}, {0x3D0991, {2, "Spider-Copter"}}, {0x3D0992, {2, "Aerial Area Rug"}}, {0x3D0993, {2, "Jack-O-Lantern's Glider"}}, {0x3D0994, {2, "Spider-Buggy"}}, {0x3D0995, {2, "Jack Skellington's Reindeer"}}, {0x3D0996, {2, "Fantasyland Carousel Horse"}}, {0x3D0997, {2, "Odin's Horse"}}, {0x3D0998, {2, "Gus the Mule"}}, {0x3D099A, {2, "Darkwing Duck's Grappling Gun"}}, {0x3D099C, {2, "Ghost Rider's Chain Whip"}}, {0x3D099D, {2, "Lew Zealand's Boomerang Fish"}}, {0x3D099E, {2, "Sergeant Calhoun's Blaster"}}, {0x3D09A0, {2, "Falcon's Wings"}}, {0x3D09A1, {2, "Mabel's Kittens for Fists"}}, {0x3D09A2, {2, "Jim Hawkins' Solar Board"}}, {0x3D09A3, {2, "Black Panther's Vibranium Knives"}}, {0x3D09A4, {2, "Cloak of Levitation"}}, {0x3D09A5, {2, "Aladdin's Magic Carpet"}}, {0x3D09A6, {2, "Honey Lemon's Ice Capsules"}}, {0x3D09A7, {2, "Jasmine's Palace View"}}, {0x3D09C1, {2, "Lola"}}, {0x3D09C2, {2, "Spider-Cycle"}}, {0x3D09C3, {2, "The Avenjet"}}, {0x3D09C4, {2, "Spider-Glider"}}, {0x3D09C5, {2, "Light Cycle"}}, {0x3D09C6, {2, "Light Jet"}}, {0x3D09C9, {3, "Retro Ray Gun"}}, {0x3D09CA, {3, "Tomorrowland Futurescape"}}, {0x3D09CB, {3, "Tomorrowland Stratosphere"}}, {0x3D09CC, {3, "Skies Over Felucia"}}, {0x3D09CD, {3, "Forests of Felucia"}}, {0x3D09CF, {3, "General Grievous' Wheel Bike"}}, {0x3D09D2, {3, "Slave I Flyer"}}, {0x3D09D3, {3, "Y-Wing Fighter"}}, {0x3D09D4, {3, "Arlo"}}, {0x3D09D5, {3, "Nash"}}, {0x3D09D6, {3, "Butch"}}, {0x3D09D7, {3, "Ramsey"}}, {0x3D09DC, {3, "Stars Over Sahara Square"}}, {0x3D09DD, {3, "Sahara Square Sands"}}, {0x3D09E0, {3, "Ghost Rider's Motorcycle"}}, {0x3D09E5, {3, "Quad Jumper"}}}; InfinityBaseDevice::InfinityBaseDevice() : Device(0x0E6F, 0x0129, 1, 2, 0) { m_IsOpened = false; } bool InfinityBaseDevice::Open() { if (!IsOpened()) { m_IsOpened = true; } return true; } void InfinityBaseDevice::Close() { if (IsOpened()) { m_IsOpened = false; } } bool InfinityBaseDevice::IsOpened() { return m_IsOpened; } Device::ReadResult InfinityBaseDevice::Read(ReadMessage* message) { memcpy(message->data, g_infinitybase.GetStatus().data(), message->length); message->bytesRead = message->length; std::this_thread::sleep_for(std::chrono::milliseconds(1)); return Device::ReadResult::Success; } Device::WriteResult InfinityBaseDevice::Write(WriteMessage* message) { g_infinitybase.SendCommand(message->data, message->length); message->bytesWritten = message->length; return Device::WriteResult::Success; } bool InfinityBaseDevice::GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) { uint8 configurationDescriptor[0x29]; uint8* currentWritePtr; // configuration descriptor currentWritePtr = configurationDescriptor + 0; *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower currentWritePtr = currentWritePtr + 9; // configuration descriptor *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol *(uint8*)(currentWritePtr + 8) = 0; // iInterface currentWritePtr = currentWritePtr + 9; // configuration descriptor *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength currentWritePtr = currentWritePtr + 9; // endpoint descriptor 1 *(uint8*)(currentWritePtr + 0) = 7; // bLength *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; // endpoint descriptor 2 *(uint8*)(currentWritePtr + 0) = 7; // bLength *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType *(uint8*)(currentWritePtr + 1) = 0x02; // bEndpointAddress *(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize *(uint8*)(currentWritePtr + 5) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); memcpy(output, configurationDescriptor, std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); return true; } bool InfinityBaseDevice::SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) { return true; } bool InfinityBaseDevice::SetProtocol(uint8 ifIndex, uint8 protocol) { return true; } bool InfinityBaseDevice::SetReport(ReportMessage* message) { return true; } std::array<uint8, 32> InfinityUSB::GetStatus() { std::array<uint8, 32> response = {}; bool responded = false; do { if (!m_figureAddedRemovedResponses.empty()) { memcpy(response.data(), m_figureAddedRemovedResponses.front().data(), 0x20); m_figureAddedRemovedResponses.pop(); responded = true; } else if (!m_queries.empty()) { memcpy(response.data(), m_queries.front().data(), 0x20); m_queries.pop(); responded = true; } else { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } /* code */ } while (!responded); return response; } void InfinityUSB::SendCommand(uint8* buf, uint32 length) { const uint8 command = buf[2]; const uint8 sequence = buf[3]; std::array<uint8, 32> q_result{}; switch (command) { case 0x80: { q_result = {0xaa, 0x15, 0x00, 0x00, 0x0f, 0x01, 0x00, 0x03, 0x02, 0x09, 0x09, 0x43, 0x20, 0x32, 0x62, 0x36, 0x36, 0x4b, 0x34, 0x99, 0x67, 0x31, 0x93, 0x8c}; break; } case 0x81: { // Initiate Challenge g_infinitybase.DescrambleAndSeed(buf, sequence, q_result); break; } case 0x83: { // Challenge Response g_infinitybase.GetNextAndScramble(sequence, q_result); break; } case 0x90: case 0x92: case 0x93: case 0x95: case 0x96: { // Color commands g_infinitybase.GetBlankResponse(sequence, q_result); break; } case 0xA1: { // Get Present Figures g_infinitybase.GetPresentFigures(sequence, q_result); break; } case 0xA2: { // Read Block from Figure g_infinitybase.QueryBlock(buf[4], buf[5], q_result, sequence); break; } case 0xA3: { // Write block to figure g_infinitybase.WriteBlock(buf[4], buf[5], &buf[7], q_result, sequence); break; } case 0xB4: { // Get figure ID g_infinitybase.GetFigureIdentifier(buf[4], sequence, q_result); break; } case 0xB5: { // Get status? g_infinitybase.GetBlankResponse(sequence, q_result); break; } default: cemu_assert_error(); break; } m_queries.push(q_result); } uint8 InfinityUSB::GenerateChecksum(const std::array<uint8, 32>& data, int numOfBytes) const { int checksum = 0; for (int i = 0; i < numOfBytes; i++) { checksum += data[i]; } return (checksum & 0xFF); } void InfinityUSB::GetBlankResponse(uint8 sequence, std::array<uint8, 32>& replyBuf) { replyBuf[0] = 0xaa; replyBuf[1] = 0x01; replyBuf[2] = sequence; replyBuf[3] = GenerateChecksum(replyBuf, 3); } void InfinityUSB::DescrambleAndSeed(uint8* buf, uint8 sequence, std::array<uint8, 32>& replyBuf) { uint64 value = uint64(buf[4]) << 56 | uint64(buf[5]) << 48 | uint64(buf[6]) << 40 | uint64(buf[7]) << 32 | uint64(buf[8]) << 24 | uint64(buf[9]) << 16 | uint64(buf[10]) << 8 | uint64(buf[11]); uint32 seed = Descramble(value); GenerateSeed(seed); GetBlankResponse(sequence, replyBuf); } void InfinityUSB::GetNextAndScramble(uint8 sequence, std::array<uint8, 32>& replyBuf) { const uint32 nextRandom = GetNext(); const uint64 scrambledNextRandom = Scramble(nextRandom, 0); replyBuf = {0xAA, 0x09, sequence}; replyBuf[3] = uint8((scrambledNextRandom >> 56) & 0xFF); replyBuf[4] = uint8((scrambledNextRandom >> 48) & 0xFF); replyBuf[5] = uint8((scrambledNextRandom >> 40) & 0xFF); replyBuf[6] = uint8((scrambledNextRandom >> 32) & 0xFF); replyBuf[7] = uint8((scrambledNextRandom >> 24) & 0xFF); replyBuf[8] = uint8((scrambledNextRandom >> 16) & 0xFF); replyBuf[9] = uint8((scrambledNextRandom >> 8) & 0xFF); replyBuf[10] = uint8(scrambledNextRandom & 0xFF); replyBuf[11] = GenerateChecksum(replyBuf, 11); } uint32 InfinityUSB::Descramble(uint64 numToDescramble) { uint64 mask = 0x8E55AA1B3999E8AA; uint32 ret = 0; for (int i = 0; i < 64; i++) { if (mask & 0x8000000000000000) { ret = (ret << 1) | (numToDescramble & 0x01); } numToDescramble >>= 1; mask <<= 1; } return ret; } uint64 InfinityUSB::Scramble(uint32 numToScramble, uint32 garbage) { uint64 mask = 0x8E55AA1B3999E8AA; uint64 ret = 0; for (int i = 0; i < 64; i++) { ret <<= 1; if ((mask & 1) != 0) { ret |= (numToScramble & 1); numToScramble >>= 1; } else { ret |= (garbage & 1); garbage >>= 1; } mask >>= 1; } return ret; } void InfinityUSB::GenerateSeed(uint32 seed) { m_randomA = 0xF1EA5EED; m_randomB = seed; m_randomC = seed; m_randomD = seed; for (int i = 0; i < 23; i++) { GetNext(); } } uint32 InfinityUSB::GetNext() { uint32 a = m_randomA; uint32 b = m_randomB; uint32 c = m_randomC; uint32 ret = std::rotl(m_randomB, 27); const uint32 temp = (a + ((ret ^ 0xFFFFFFFF) + 1)); b ^= std::rotl(c, 17); a = m_randomD; c += a; ret = b + temp; a += temp; m_randomC = a; m_randomA = b; m_randomB = c; m_randomD = ret; return ret; } void InfinityUSB::GetPresentFigures(uint8 sequence, std::array<uint8, 32>& replyBuf) { int x = 3; for (uint8 i = 0; i < m_figures.size(); i++) { uint8 slot = i == 0 ? 0x10 : (i < 4) ? 0x20 : 0x30; if (m_figures[i].present) { replyBuf[x] = slot + m_figures[i].orderAdded; replyBuf[x + 1] = 0x09; x += 2; } } replyBuf[0] = 0xaa; replyBuf[1] = x - 2; replyBuf[2] = sequence; replyBuf[x] = GenerateChecksum(replyBuf, x); } InfinityUSB::InfinityFigure& InfinityUSB::GetFigureByOrder(uint8 orderAdded) { for (uint8 i = 0; i < m_figures.size(); i++) { if (m_figures[i].orderAdded == orderAdded) { return m_figures[i]; } } return m_figures[0]; } void InfinityUSB::QueryBlock(uint8 fig_num, uint8 block, std::array<uint8, 32>& replyBuf, uint8 sequence) { std::lock_guard lock(m_infinityMutex); InfinityFigure& figure = GetFigureByOrder(fig_num); replyBuf[0] = 0xaa; replyBuf[1] = 0x12; replyBuf[2] = sequence; replyBuf[3] = 0x00; const uint8 file_block = (block == 0) ? 1 : (block * 4); if (figure.present && file_block < 20) { memcpy(&replyBuf[4], figure.data.data() + (16 * file_block), 16); } replyBuf[20] = GenerateChecksum(replyBuf, 20); } void InfinityUSB::WriteBlock(uint8 fig_num, uint8 block, const uint8* to_write_buf, std::array<uint8, 32>& replyBuf, uint8 sequence) { std::lock_guard lock(m_infinityMutex); InfinityFigure& figure = GetFigureByOrder(fig_num); replyBuf[0] = 0xaa; replyBuf[1] = 0x02; replyBuf[2] = sequence; replyBuf[3] = 0x00; const uint8 file_block = (block == 0) ? 1 : (block * 4); if (figure.present && file_block < 20) { memcpy(figure.data.data() + (file_block * 16), to_write_buf, 16); figure.Save(); } replyBuf[4] = GenerateChecksum(replyBuf, 4); } void InfinityUSB::GetFigureIdentifier(uint8 fig_num, uint8 sequence, std::array<uint8, 32>& replyBuf) { std::lock_guard lock(m_infinityMutex); InfinityFigure& figure = GetFigureByOrder(fig_num); replyBuf[0] = 0xaa; replyBuf[1] = 0x09; replyBuf[2] = sequence; replyBuf[3] = 0x00; if (figure.present) { memcpy(&replyBuf[4], figure.data.data(), 7); } replyBuf[11] = GenerateChecksum(replyBuf, 11); } std::pair<uint8, std::string> InfinityUSB::FindFigure(uint32 figNum) { for (const auto& it : GetFigureList()) { if (it.first == figNum) { return it.second; } } return {0, fmt::format("Unknown Figure ({})", figNum)}; } std::map<const uint32, const std::pair<const uint8, const char*>> InfinityUSB::GetFigureList() { return s_listFigures; } void InfinityUSB::InfinityFigure::Save() { if (!infFile) return; infFile->SetPosition(0); infFile->writeData(data.data(), data.size()); } bool InfinityUSB::RemoveFigure(uint8 position) { std::lock_guard lock(m_infinityMutex); InfinityFigure& figure = m_figures[position]; figure.Save(); figure.infFile.reset(); if (figure.present) { figure.present = false; position = DeriveFigurePosition(position); if (position == 0) { return false; } std::array<uint8, 32> figureChangeResponse = {0xab, 0x04, position, 0x09, figure.orderAdded, 0x01}; figureChangeResponse[6] = GenerateChecksum(figureChangeResponse, 6); m_figureAddedRemovedResponses.push(figureChangeResponse); return true; } return false; } uint32 InfinityUSB::LoadFigure(const std::array<uint8, INF_FIGURE_SIZE>& buf, std::unique_ptr<FileStream> inFile, uint8 position) { std::lock_guard lock(m_infinityMutex); uint8 orderAdded; std::vector<uint8> sha1Calc = {SHA1_CONSTANT.begin(), SHA1_CONSTANT.end() - 1}; for (int i = 0; i < 7; i++) { sha1Calc.push_back(buf[i]); } std::array<uint8, 16> key = GenerateInfinityFigureKey(sha1Calc); std::array<uint8, 16> infinity_decrypted_block = {}; std::array<uint8, 16> encryptedBlock = {}; memcpy(encryptedBlock.data(), &buf[16], 16); AES128_ECB_decrypt(encryptedBlock.data(), key.data(), infinity_decrypted_block.data()); uint32 number = uint32(infinity_decrypted_block[1]) << 16 | uint32(infinity_decrypted_block[2]) << 8 | uint32(infinity_decrypted_block[3]); InfinityFigure& figure = m_figures[position]; figure.infFile = std::move(inFile); memcpy(figure.data.data(), buf.data(), figure.data.size()); figure.present = true; if (figure.orderAdded == 255) { figure.orderAdded = m_figureOrder; m_figureOrder++; } orderAdded = figure.orderAdded; position = DeriveFigurePosition(position); if (position == 0) { return 0; } std::array<uint8, 32> figureChangeResponse = {0xab, 0x04, position, 0x09, orderAdded, 0x00}; figureChangeResponse[6] = GenerateChecksum(figureChangeResponse, 6); m_figureAddedRemovedResponses.push(figureChangeResponse); return number; } static uint32 InfinityCRC32(const std::array<uint8, 16>& buffer) { static constexpr std::array<uint32, 256> CRC32_TABLE{ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; // Infinity m_figures calculate their CRC32 based on 12 bytes in the block of 16 uint32 ret = 0; for (uint32 i = 0; i < 12; ++i) { uint8 index = uint8(ret & 0xFF) ^ buffer[i]; ret = ((ret >> 8) ^ CRC32_TABLE[index]); } return ret; } bool InfinityUSB::CreateFigure(fs::path pathName, uint32 figureNum, uint8 series) { FileStream* infFile(FileStream::createFile2(pathName)); if (!infFile) { return false; } std::array<uint8, INF_FIGURE_SIZE> fileData{}; uint32 firstBlock = 0x17878E; uint32 otherBlocks = 0x778788; for (sint8 i = 2; i >= 0; i--) { fileData[0x38 - i] = uint8((firstBlock >> i * 8) & 0xFF); } for (uint32 index = 1; index < 0x05; index++) { for (sint8 i = 2; i >= 0; i--) { fileData[((index * 0x40) + 0x38) - i] = uint8((otherBlocks >> i * 8) & 0xFF); } } // Create the vector to calculate the SHA1 hash with std::vector<uint8> sha1Calc = {SHA1_CONSTANT.begin(), SHA1_CONSTANT.end() - 1}; // Generate random UID, used for AES encrypt/decrypt std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution<int> dist(0, 255); std::array<uint8, 16> uid_data = {0, 0, 0, 0, 0, 0, 0, 0x89, 0x44, 0x00, 0xC2}; uid_data[0] = dist(mt); uid_data[1] = dist(mt); uid_data[2] = dist(mt); uid_data[3] = dist(mt); uid_data[4] = dist(mt); uid_data[5] = dist(mt); uid_data[6] = dist(mt); for (sint8 i = 0; i < 7; i++) { sha1Calc.push_back(uid_data[i]); } std::array<uint8, 16> figureData = GenerateBlankFigureData(figureNum, series); if (figureData[1] == 0x00) return false; std::array<uint8, 16> key = GenerateInfinityFigureKey(sha1Calc); std::array<uint8, 16> encryptedBlock = {}; std::array<uint8, 16> blankBlock = {}; std::array<uint8, 16> encryptedBlank = {}; AES128_ECB_encrypt(figureData.data(), key.data(), encryptedBlock.data()); AES128_ECB_encrypt(blankBlock.data(), key.data(), encryptedBlank.data()); memcpy(&fileData[0], uid_data.data(), uid_data.size()); memcpy(&fileData[16], encryptedBlock.data(), encryptedBlock.size()); memcpy(&fileData[16 * 0x04], encryptedBlank.data(), encryptedBlank.size()); memcpy(&fileData[16 * 0x08], encryptedBlank.data(), encryptedBlank.size()); memcpy(&fileData[16 * 0x0C], encryptedBlank.data(), encryptedBlank.size()); memcpy(&fileData[16 * 0x0D], encryptedBlank.data(), encryptedBlank.size()); infFile->writeData(fileData.data(), fileData.size()); delete infFile; return true; } std::array<uint8, 16> InfinityUSB::GenerateInfinityFigureKey(const std::vector<uint8>& sha1Data) { std::array<uint8, 20> digest = {}; SHA1(sha1Data.data(), sha1Data.size(), digest.data()); // Infinity AES keys are the first 16 bytes of the SHA1 Digest, every set of 4 bytes need to be // reversed due to endianness std::array<uint8, 16> key = {}; for (int i = 0; i < 4; i++) { for (int x = 3; x >= 0; x--) { key[(3 - x) + (i * 4)] = digest[x + (i * 4)]; } } return key; } std::array<uint8, 16> InfinityUSB::GenerateBlankFigureData(uint32 figureNum, uint8 series) { std::array<uint8, 16> figureData = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xD1, 0x1F}; // Figure Number, input by end user figureData[1] = uint8((figureNum >> 16) & 0xFF); figureData[2] = uint8((figureNum >> 8) & 0xFF); figureData[3] = uint8(figureNum & 0xFF); // Manufacture date, formatted as YY/MM/DD. Set to release date of figure's series if (series == 1) { figureData[4] = 0x0D; figureData[5] = 0x08; figureData[6] = 0x12; } else if (series == 2) { figureData[4] = 0x0E; figureData[5] = 0x09; figureData[6] = 0x12; } else if (series == 3) { figureData[4] = 0x0F; figureData[5] = 0x08; figureData[6] = 0x1C; } uint32 checksum = InfinityCRC32(figureData); for (sint8 i = 3; i >= 0; i--) { figureData[15 - i] = uint8((checksum >> i * 8) & 0xFF); } return figureData; } uint8 InfinityUSB::DeriveFigurePosition(uint8 position) { // In the added/removed response, position needs to be 1 for the hexagon, 2 for Player 1 and // Player 1's abilities, and 3 for Player 2 and Player 2's abilities. In the UI, positions 0, 1 // and 2 represent the hexagon slot, 3, 4 and 5 represent Player 1's slot and 6, 7 and 8 represent // Player 2's slot. switch (position) { case 0: case 1: case 2: return 1; case 3: case 4: case 5: return 2; case 6: case 7: case 8: return 3; default: return 0; } } } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/Infinity.h ================================================ #pragma once #include <mutex> #include "nsyshid.h" #include "Backend.h" #include "Common/FileStream.h" namespace nsyshid { class InfinityBaseDevice final : public Device { public: InfinityBaseDevice(); ~InfinityBaseDevice() = default; bool Open() override; void Close() override; bool IsOpened() override; ReadResult Read(ReadMessage* message) override; WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) override; bool SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) override; bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; private: bool m_IsOpened; }; constexpr uint16 INF_BLOCK_COUNT = 0x14; constexpr uint16 INF_BLOCK_SIZE = 0x10; constexpr uint16 INF_FIGURE_SIZE = INF_BLOCK_COUNT * INF_BLOCK_SIZE; constexpr uint8 MAX_FIGURES = 9; class InfinityUSB { public: struct InfinityFigure final { std::unique_ptr<FileStream> infFile; std::array<uint8, INF_FIGURE_SIZE> data{}; bool present = false; uint8 orderAdded = 255; void Save(); }; void SendCommand(uint8* buf, uint32 length); std::array<uint8, 32> GetStatus(); void GetBlankResponse(uint8 sequence, std::array<uint8, 32>& replyBuf); void DescrambleAndSeed(uint8* buf, uint8 sequence, std::array<uint8, 32>& replyBuf); void GetNextAndScramble(uint8 sequence, std::array<uint8, 32>& replyBuf); void GetPresentFigures(uint8 sequence, std::array<uint8, 32>& replyBuf); void QueryBlock(uint8 figNum, uint8 block, std::array<uint8, 32>& replyBuf, uint8 sequence); void WriteBlock(uint8 figNum, uint8 block, const uint8* toWriteBuf, std::array<uint8, 32>& replyBuf, uint8 sequence); void GetFigureIdentifier(uint8 figNum, uint8 sequence, std::array<uint8, 32>& replyBuf); bool RemoveFigure(uint8 position); uint32 LoadFigure(const std::array<uint8, INF_FIGURE_SIZE>& buf, std::unique_ptr<FileStream>, uint8 position); bool CreateFigure(fs::path pathName, uint32 figureNum, uint8 series); static std::map<const uint32, const std::pair<const uint8, const char*>> GetFigureList(); std::pair<uint8, std::string> FindFigure(uint32 figNum); protected: std::shared_mutex m_infinityMutex; std::array<InfinityFigure, 9> m_figures; private: uint8 GenerateChecksum(const std::array<uint8, 32>& data, int numOfBytes) const; uint32 Descramble(uint64 numToDescramble); uint64 Scramble(uint32 numToScramble, uint32 garbage); void GenerateSeed(uint32 seed); uint32 GetNext(); InfinityFigure& GetFigureByOrder(uint8 orderAdded); uint8 DeriveFigurePosition(uint8 position); std::array<uint8, 16> GenerateInfinityFigureKey(const std::vector<uint8>& sha1Data); std::array<uint8, 16> GenerateBlankFigureData(uint32 figureNum, uint8 series); uint32 m_randomA; uint32 m_randomB; uint32 m_randomC; uint32 m_randomD; uint8 m_figureOrder = 0; std::queue<std::array<uint8, 32>> m_figureAddedRemovedResponses; std::queue<std::array<uint8, 32>> m_queries; }; extern InfinityUSB g_infinitybase; } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/Skylander.cpp ================================================ #include "Skylander.h" #include <random> #include "nsyshid.h" #include "Backend.h" #include "Common/FileStream.h" #include "audio/IAudioAPI.h" #include "config/CemuConfig.h" namespace nsyshid { SkylanderUSB g_skyportal; const std::map<const std::pair<const uint16, const uint16>, const char*> s_listSkylanders = { {{0, 0x0000}, "Whirlwind"}, {{0, 0x1801}, "Series 2 Whirlwind"}, {{0, 0x1C02}, "Polar Whirlwind"}, {{0, 0x2805}, "Horn Blast Whirlwind"}, {{0, 0x3810}, "Eon's Elite Whirlwind"}, {{1, 0x0000}, "Sonic Boom"}, {{1, 0x1801}, "Series 2 Sonic Boom"}, {{2, 0x0000}, "Warnado"}, {{2, 0x2206}, "LightCore Warnado"}, {{3, 0x0000}, "Lightning Rod"}, {{3, 0x1801}, "Series 2 Lightning Rod"}, {{4, 0x0000}, "Bash"}, {{4, 0x1801}, "Series 2 Bash"}, {{5, 0x0000}, "Terrafin"}, {{5, 0x1801}, "Series 2 Terrafin"}, {{5, 0x2805}, "Knockout Terrafin"}, {{5, 0x3810}, "Eon's Elite Terrafin"}, {{6, 0x0000}, "Dino Rang"}, {{6, 0x4810}, "Eon's Elite Dino Rang"}, {{7, 0x0000}, "Prism Break"}, {{7, 0x1801}, "Series 2 Prism Break"}, {{7, 0x2805}, "Hyper Beam Prism Break"}, {{7, 0x1206}, "LightCore Prism Break"}, {{8, 0x0000}, "Sunburn"}, {{9, 0x0000}, "Eruptor"}, {{9, 0x1801}, "Series 2 Eruptor"}, {{9, 0x2C02}, "Volcanic Eruptor"}, {{9, 0x2805}, "Lava Barf Eruptor"}, {{9, 0x1206}, "LightCore Eruptor"}, {{9, 0x3810}, "Eon's Elite Eruptor"}, {{10, 0x0000}, "Ignitor"}, {{10, 0x1801}, "Series 2 Ignitor"}, {{10, 0x1C03}, "Legendary Ignitor"}, {{11, 0x0000}, "Flameslinger"}, {{11, 0x1801}, "Series 2 Flameslinger"}, {{12, 0x0000}, "Zap"}, {{12, 0x1801}, "Series 2 Zap"}, {{13, 0x0000}, "Wham Shell"}, {{13, 0x2206}, "LightCore Wham Shell"}, {{14, 0x0000}, "Gill Grunt"}, {{14, 0x1801}, "Series 2 Gill Grunt"}, {{14, 0x2805}, "Anchors Away Gill Grunt"}, {{14, 0x3805}, "Tidal Wave Gill Grunt"}, {{14, 0x3810}, "Eon's Elite Gill Grunt"}, {{15, 0x0000}, "Slam Bam"}, {{15, 0x1801}, "Series 2 Slam Bam"}, {{15, 0x1C03}, "Legendary Slam Bam"}, {{15, 0x4810}, "Eon's Elite Slam Bam"}, {{16, 0x0000}, "Spyro"}, {{16, 0x1801}, "Series 2 Spyro"}, {{16, 0x2C02}, "Dark Mega Ram Spyro"}, {{16, 0x2805}, "Mega Ram Spyro"}, {{16, 0x3810}, "Eon's Elite Spyro"}, {{17, 0x0000}, "Voodood"}, {{17, 0x4810}, "Eon's Elite Voodood"}, {{18, 0x0000}, "Double Trouble"}, {{18, 0x1801}, "Series 2 Double Trouble"}, {{18, 0x1C02}, "Royal Double Trouble"}, {{19, 0x0000}, "Trigger Happy"}, {{19, 0x1801}, "Series 2 Trigger Happy"}, {{19, 0x2C02}, "Springtime Trigger Happy"}, {{19, 0x2805}, "Big Bang Trigger Happy"}, {{19, 0x3810}, "Eon's Elite Trigger Happy"}, {{20, 0x0000}, "Drobot"}, {{20, 0x1801}, "Series 2 Drobot"}, {{20, 0x1206}, "LightCore Drobot"}, {{21, 0x0000}, "Drill Seargeant"}, {{21, 0x1801}, "Series 2 Drill Seargeant"}, {{22, 0x0000}, "Boomer"}, {{22, 0x4810}, "Eon's Elite Boomer"}, {{23, 0x0000}, "Wrecking Ball"}, {{23, 0x1801}, "Series 2 Wrecking Ball"}, {{24, 0x0000}, "Camo"}, {{24, 0x2805}, "Thorn Horn Camo"}, {{25, 0x0000}, "Zook"}, {{25, 0x1801}, "Series 2 Zook"}, {{25, 0x4810}, "Eon's Elite Zook"}, {{26, 0x0000}, "Stealth Elf"}, {{26, 0x1801}, "Series 2 Stealth Elf"}, {{26, 0x2C02}, "Dark Stealth Elf"}, {{26, 0x1C03}, "Legendary Stealth Elf"}, {{26, 0x2805}, "Ninja Stealth Elf"}, {{26, 0x3810}, "Eon's Elite Stealth Elf"}, {{27, 0x0000}, "Stump Smash"}, {{27, 0x1801}, "Series 2 Stump Smash"}, {{28, 0x0000}, "Dark Spyro"}, {{29, 0x0000}, "Hex"}, {{29, 0x1801}, "Series 2 Hex"}, {{29, 0x1206}, "LightCore Hex"}, {{30, 0x0000}, "Chop Chop"}, {{30, 0x1801}, "Series 2 Chop Chop"}, {{30, 0x2805}, "Twin Blade Chop Chop"}, {{30, 0x3810}, "Eon's Elite Chop Chop"}, {{31, 0x0000}, "Ghost Roaster"}, {{31, 0x4810}, "Eon's Elite Ghost Roaster"}, {{32, 0x0000}, "Cynder"}, {{32, 0x1801}, "Series 2 Cynder"}, {{32, 0x2805}, "Phantom Cynder"}, {{100, 0x0000}, "Jet Vac"}, {{100, 0x1403}, "Legendary Jet Vac"}, {{100, 0x2805}, "Turbo Jet Vac"}, {{100, 0x3805}, "Full Blast Jet Vac"}, {{100, 0x1206}, "LightCore Jet Vac"}, {{101, 0x0000}, "Swarm"}, {{102, 0x0000}, "Crusher"}, {{102, 0x1602}, "Granite Crusher"}, {{103, 0x0000}, "Flashwing"}, {{103, 0x1402}, "Jade Flash Wing"}, {{103, 0x2206}, "LightCore Flashwing"}, {{104, 0x0000}, "Hot Head"}, {{105, 0x0000}, "Hot Dog"}, {{105, 0x1402}, "Molten Hot Dog"}, {{105, 0x2805}, "Fire Bone Hot Dog"}, {{106, 0x0000}, "Chill"}, {{106, 0x1603}, "Legendary Chill"}, {{106, 0x2805}, "Blizzard Chill"}, {{106, 0x1206}, "LightCore Chill"}, {{107, 0x0000}, "Thumpback"}, {{108, 0x0000}, "Pop Fizz"}, {{108, 0x1402}, "Punch Pop Fizz"}, {{108, 0x3C02}, "Love Potion Pop Fizz"}, {{108, 0x2805}, "Super Gulp Pop Fizz"}, {{108, 0x3805}, "Fizzy Frenzy Pop Fizz"}, {{108, 0x1206}, "LightCore Pop Fizz"}, {{109, 0x0000}, "Ninjini"}, {{109, 0x1602}, "Scarlet Ninjini"}, {{110, 0x0000}, "Bouncer"}, {{110, 0x1603}, "Legendary Bouncer"}, {{111, 0x0000}, "Sprocket"}, {{111, 0x2805}, "Heavy Duty Sprocket"}, {{112, 0x0000}, "Tree Rex"}, {{112, 0x1602}, "Gnarly Tree Rex"}, {{113, 0x0000}, "Shroomboom"}, {{113, 0x3805}, "Sure Shot Shroomboom"}, {{113, 0x1206}, "LightCore Shroomboom"}, {{114, 0x0000}, "Eye Brawl"}, {{115, 0x0000}, "Fright Rider"}, {{200, 0x0000}, "Anvil Rain"}, {{201, 0x0000}, "Hidden Treasure"}, {{201, 0x2000}, "Platinum Hidden Treasure"}, {{202, 0x0000}, "Healing Elixir"}, {{203, 0x0000}, "Ghost Pirate Swords"}, {{204, 0x0000}, "Time Twist Hourglass"}, {{205, 0x0000}, "Sky Iron Shield"}, {{206, 0x0000}, "Winged Boots"}, {{207, 0x0000}, "Sparx the Dragonfly"}, {{208, 0x0000}, "Dragonfire Cannon"}, {{208, 0x1602}, "Golden Dragonfire Cannon"}, {{209, 0x0000}, "Scorpion Striker"}, {{210, 0x3002}, "Biter's Bane"}, {{210, 0x3008}, "Sorcerous Skull"}, {{210, 0x300B}, "Axe of Illusion"}, {{210, 0x300E}, "Arcane Hourglass"}, {{210, 0x3012}, "Spell Slapper"}, {{210, 0x3014}, "Rune Rocket"}, {{211, 0x3001}, "Tidal Tiki"}, {{211, 0x3002}, "Wet Walter"}, {{211, 0x3006}, "Flood Flask"}, {{211, 0x3406}, "Legendary Flood Flask"}, {{211, 0x3007}, "Soaking Staff"}, {{211, 0x300B}, "Aqua Axe"}, {{211, 0x3016}, "Frost Helm"}, {{212, 0x3003}, "Breezy Bird"}, {{212, 0x3006}, "Drafty Decanter"}, {{212, 0x300D}, "Tempest Timer"}, {{212, 0x3010}, "Cloudy Cobra"}, {{212, 0x3011}, "Storm Warning"}, {{212, 0x3018}, "Cyclone Saber"}, {{213, 0x3004}, "Spirit Sphere"}, {{213, 0x3404}, "Legendary Spirit Sphere"}, {{213, 0x3008}, "Spectral Skull"}, {{213, 0x3408}, "Legendary Spectral Skull"}, {{213, 0x300B}, "Haunted Hatchet"}, {{213, 0x300C}, "Grim Gripper"}, {{213, 0x3010}, "Spooky Snake"}, {{213, 0x3017}, "Dream Piercer"}, {{214, 0x3000}, "Tech Totem"}, {{214, 0x3007}, "Automatic Angel"}, {{214, 0x3009}, "Factory Flower"}, {{214, 0x300C}, "Grabbing Gadget"}, {{214, 0x3016}, "Makers Mana"}, {{214, 0x301A}, "Topsy Techy"}, {{215, 0x3005}, "Eternal Flame"}, {{215, 0x3009}, "Fire Flower"}, {{215, 0x3011}, "Scorching Stopper"}, {{215, 0x3012}, "Searing Spinner"}, {{215, 0x3017}, "Spark Spear"}, {{215, 0x301B}, "Blazing Belch"}, {{216, 0x3000}, "Banded Boulder"}, {{216, 0x3003}, "Rock Hawk"}, {{216, 0x300A}, "Slag Hammer"}, {{216, 0x300E}, "Dust Of Time"}, {{216, 0x3013}, "Spinning Sandstorm"}, {{216, 0x301A}, "Rubble Trouble"}, {{217, 0x3003}, "Oak Eagle"}, {{217, 0x3005}, "Emerald Energy"}, {{217, 0x300A}, "Weed Whacker"}, {{217, 0x3010}, "Seed Serpent"}, {{217, 0x3018}, "Jade Blade"}, {{217, 0x301B}, "Shrub Shrieker"}, {{218, 0x3000}, "Dark Dagger"}, {{218, 0x3014}, "Shadow Spider"}, {{218, 0x301A}, "Ghastly Grimace"}, {{219, 0x3000}, "Shining Ship"}, {{219, 0x300F}, "Heavenly Hawk"}, {{219, 0x301B}, "Beam Scream"}, {{220, 0x301E}, "Kaos Trap"}, {{220, 0x351F}, "Ultimate Kaos Trap"}, {{230, 0x0000}, "Hand of Fate"}, {{230, 0x3403}, "Legendary Hand of Fate"}, {{231, 0x0000}, "Piggy Bank"}, {{232, 0x0000}, "Rocket Ram"}, {{233, 0x0000}, "Tiki Speaky"}, {{300, 0x0000}, "Dragon’s Peak"}, {{301, 0x0000}, "Empire of Ice"}, {{302, 0x0000}, "Pirate Seas"}, {{303, 0x0000}, "Darklight Crypt"}, {{304, 0x0000}, "Volcanic Vault"}, {{305, 0x0000}, "Mirror of Mystery"}, {{306, 0x0000}, "Nightmare Express"}, {{307, 0x0000}, "Sunscraper Spire"}, {{308, 0x0000}, "Midnight Museum"}, {{404, 0x0000}, "Legendary Bash"}, {{416, 0x0000}, "Legendary Spyro"}, {{419, 0x0000}, "Legendary Trigger Happy"}, {{430, 0x0000}, "Legendary Chop Chop"}, {{450, 0x0000}, "Gusto"}, {{451, 0x0000}, "Thunderbolt"}, {{452, 0x0000}, "Fling Kong"}, {{453, 0x0000}, "Blades"}, {{453, 0x3403}, "Legendary Blades"}, {{454, 0x0000}, "Wallop"}, {{455, 0x0000}, "Head Rush"}, {{455, 0x3402}, "Nitro Head Rush"}, {{456, 0x0000}, "Fist Bump"}, {{457, 0x0000}, "Rocky Roll"}, {{458, 0x0000}, "Wildfire"}, {{458, 0x3402}, "Dark Wildfire"}, {{459, 0x0000}, "Ka Boom"}, {{460, 0x0000}, "Trail Blazer"}, {{461, 0x0000}, "Torch"}, {{462, 0x3000}, "Snap Shot"}, {{462, 0x3402}, "Dark Snap Shot"}, {{463, 0x0000}, "Lob Star"}, {{463, 0x3402}, "Winterfest Lob-Star"}, {{464, 0x0000}, "Flip Wreck"}, {{465, 0x0000}, "Echo"}, {{466, 0x0000}, "Blastermind"}, {{467, 0x0000}, "Enigma"}, {{468, 0x0000}, "Deja Vu"}, {{468, 0x3403}, "Legendary Deja Vu"}, {{469, 0x0000}, "Cobra Candabra"}, {{469, 0x3402}, "King Cobra Cadabra"}, {{470, 0x0000}, "Jawbreaker"}, {{470, 0x3403}, "Legendary Jawbreaker"}, {{471, 0x0000}, "Gearshift"}, {{472, 0x0000}, "Chopper"}, {{473, 0x0000}, "Tread Head"}, {{474, 0x0000}, "Bushwack"}, {{474, 0x3403}, "Legendary Bushwack"}, {{475, 0x0000}, "Tuff Luck"}, {{476, 0x0000}, "Food Fight"}, {{476, 0x3402}, "Dark Food Fight"}, {{477, 0x0000}, "High Five"}, {{478, 0x0000}, "Krypt King"}, {{478, 0x3402}, "Nitro Krypt King"}, {{479, 0x0000}, "Short Cut"}, {{480, 0x0000}, "Bat Spin"}, {{481, 0x0000}, "Funny Bone"}, {{482, 0x0000}, "Knight Light"}, {{483, 0x0000}, "Spotlight"}, {{484, 0x0000}, "Knight Mare"}, {{485, 0x0000}, "Blackout"}, {{502, 0x0000}, "Bop"}, {{505, 0x0000}, "Terrabite"}, {{506, 0x0000}, "Breeze"}, {{508, 0x0000}, "Pet Vac"}, {{508, 0x3402}, "Power Punch Pet Vac"}, {{507, 0x0000}, "Weeruptor"}, {{507, 0x3402}, "Eggcellent Weeruptor"}, {{509, 0x0000}, "Small Fry"}, {{510, 0x0000}, "Drobit"}, {{519, 0x0000}, "Trigger Snappy"}, {{526, 0x0000}, "Whisper Elf"}, {{540, 0x0000}, "Barkley"}, {{540, 0x3402}, "Gnarly Barkley"}, {{541, 0x0000}, "Thumpling"}, {{514, 0x0000}, "Gill Runt"}, {{542, 0x0000}, "Mini-Jini"}, {{503, 0x0000}, "Spry"}, {{504, 0x0000}, "Hijinx"}, {{543, 0x0000}, "Eye Small"}, {{601, 0x0000}, "King Pen"}, {{602, 0x0000}, "Tri-Tip"}, {{603, 0x0000}, "Chopscotch"}, {{604, 0x0000}, "Boom Bloom"}, {{605, 0x0000}, "Pit Boss"}, {{606, 0x0000}, "Barbella"}, {{607, 0x0000}, "Air Strike"}, {{608, 0x0000}, "Ember"}, {{609, 0x0000}, "Ambush"}, {{610, 0x0000}, "Dr. Krankcase"}, {{611, 0x0000}, "Hood Sickle"}, {{612, 0x0000}, "Tae Kwon Crow"}, {{613, 0x0000}, "Golden Queen"}, {{614, 0x0000}, "Wolfgang"}, {{615, 0x0000}, "Pain-Yatta"}, {{616, 0x0000}, "Mysticat"}, {{617, 0x0000}, "Starcast"}, {{618, 0x0000}, "Buckshot"}, {{619, 0x0000}, "Aurora"}, {{620, 0x0000}, "Flare Wolf"}, {{621, 0x0000}, "Chompy Mage"}, {{622, 0x0000}, "Bad Juju"}, {{623, 0x0000}, "Grave Clobber"}, {{624, 0x0000}, "Blaster-Tron"}, {{625, 0x0000}, "Ro-Bow"}, {{626, 0x0000}, "Chain Reaction"}, {{627, 0x0000}, "Kaos"}, {{628, 0x0000}, "Wild Storm"}, {{629, 0x0000}, "Tidepool"}, {{630, 0x0000}, "Crash Bandicoot"}, {{631, 0x0000}, "Dr. Neo Cortex"}, {{1000, 0x0000}, "Boom Jet (Bottom)"}, {{1001, 0x0000}, "Free Ranger (Bottom)"}, {{1001, 0x2403}, "Legendary Free Ranger (Bottom)"}, {{1002, 0x0000}, "Rubble Rouser (Bottom)"}, {{1003, 0x0000}, "Doom Stone (Bottom)"}, {{1004, 0x0000}, "Blast Zone (Bottom)"}, {{1004, 0x2402}, "Dark Blast Zone (Bottom)"}, {{1005, 0x0000}, "Fire Kraken (Bottom)"}, {{1005, 0x2402}, "Jade Fire Kraken (Bottom)"}, {{1006, 0x0000}, "Stink Bomb (Bottom)"}, {{1007, 0x0000}, "Grilla Drilla (Bottom)"}, {{1008, 0x0000}, "Hoot Loop (Bottom)"}, {{1008, 0x2402}, "Enchanted Hoot Loop (Bottom)"}, {{1009, 0x0000}, "Trap Shadow (Bottom)"}, {{1010, 0x0000}, "Magna Charge (Bottom)"}, {{1010, 0x2402}, "Nitro Magna Charge (Bottom)"}, {{1011, 0x0000}, "Spy Rise (Bottom)"}, {{1012, 0x0000}, "Night Shift (Bottom)"}, {{1012, 0x2403}, "Legendary Night Shift (Bottom)"}, {{1013, 0x0000}, "Rattle Shake (Bottom)"}, {{1013, 0x2402}, "Quick Draw Rattle Shake (Bottom)"}, {{1014, 0x0000}, "Freeze Blade (Bottom)"}, {{1014, 0x2402}, "Nitro Freeze Blade (Bottom)"}, {{1015, 0x0000}, "Wash Buckler (Bottom)"}, {{1015, 0x2402}, "Dark Wash Buckler (Bottom)"}, {{2000, 0x0000}, "Boom Jet (Top)"}, {{2001, 0x0000}, "Free Ranger (Top)"}, {{2001, 0x2403}, "Legendary Free Ranger (Top)"}, {{2002, 0x0000}, "Rubble Rouser (Top)"}, {{2003, 0x0000}, "Doom Stone (Top)"}, {{2004, 0x0000}, "Blast Zone (Top)"}, {{2004, 0x2402}, "Dark Blast Zone (Top)"}, {{2005, 0x0000}, "Fire Kraken (Top)"}, {{2005, 0x2402}, "Jade Fire Kraken (Top)"}, {{2006, 0x0000}, "Stink Bomb (Top)"}, {{2007, 0x0000}, "Grilla Drilla (Top)"}, {{2008, 0x0000}, "Hoot Loop (Top)"}, {{2008, 0x2402}, "Enchanted Hoot Loop (Top)"}, {{2009, 0x0000}, "Trap Shadow (Top)"}, {{2010, 0x0000}, "Magna Charge (Top)"}, {{2010, 0x2402}, "Nitro Magna Charge (Top)"}, {{2011, 0x0000}, "Spy Rise (Top)"}, {{2012, 0x0000}, "Night Shift (Top)"}, {{2012, 0x2403}, "Legendary Night Shift (Top)"}, {{2013, 0x0000}, "Rattle Shake (Top)"}, {{2013, 0x2402}, "Quick Draw Rattle Shake (Top)"}, {{2014, 0x0000}, "Freeze Blade (Top)"}, {{2014, 0x2402}, "Nitro Freeze Blade (Top)"}, {{2015, 0x0000}, "Wash Buckler (Top)"}, {{2015, 0x2402}, "Dark Wash Buckler (Top)"}, {{3000, 0x0000}, "Scratch"}, {{3001, 0x0000}, "Pop Thorn"}, {{3002, 0x0000}, "Slobber Tooth"}, {{3002, 0x2402}, "Dark Slobber Tooth"}, {{3003, 0x0000}, "Scorp"}, {{3004, 0x0000}, "Fryno"}, {{3004, 0x3805}, "Hog Wild Fryno"}, {{3005, 0x0000}, "Smolderdash"}, {{3005, 0x2206}, "LightCore Smolderdash"}, {{3006, 0x0000}, "Bumble Blast"}, {{3006, 0x2402}, "Jolly Bumble Blast"}, {{3006, 0x2206}, "LightCore Bumble Blast"}, {{3007, 0x0000}, "Zoo Lou"}, {{3007, 0x2403}, "Legendary Zoo Lou"}, {{3008, 0x0000}, "Dune Bug"}, {{3009, 0x0000}, "Star Strike"}, {{3009, 0x2602}, "Enchanted Star Strike"}, {{3009, 0x2206}, "LightCore Star Strike"}, {{3010, 0x0000}, "Countdown"}, {{3010, 0x2402}, "Kickoff Countdown"}, {{3010, 0x2206}, "LightCore Countdown"}, {{3011, 0x0000}, "Wind Up"}, {{3011, 0x2404}, "Gear Head VVind Up"}, {{3012, 0x0000}, "Roller Brawl"}, {{3013, 0x0000}, "Grim Creeper"}, {{3013, 0x2603}, "Legendary Grim Creeper"}, {{3013, 0x2206}, "LightCore Grim Creeper"}, {{3014, 0x0000}, "Rip Tide"}, {{3015, 0x0000}, "Punk Shock"}, {{3200, 0x0000}, "Battle Hammer"}, {{3201, 0x0000}, "Sky Diamond"}, {{3202, 0x0000}, "Platinum Sheep"}, {{3203, 0x0000}, "Groove Machine"}, {{3204, 0x0000}, "UFO Hat"}, {{3300, 0x0000}, "Sheep Wreck Island"}, {{3301, 0x0000}, "Tower of Time"}, {{3302, 0x0000}, "Fiery Forge"}, {{3303, 0x0000}, "Arkeyan Crossbow"}, {{3220, 0x0000}, "Jet Stream"}, {{3221, 0x0000}, "Tomb Buggy"}, {{3222, 0x0000}, "Reef Ripper"}, {{3223, 0x0000}, "Burn Cycle"}, {{3224, 0x0000}, "Hot Streak"}, {{3224, 0x4402}, "Dark Hot Streak"}, {{3224, 0x4004}, "E3 Hot Streak"}, {{3224, 0x441E}, "Golden Hot Streak"}, {{3225, 0x0000}, "Shark Tank"}, {{3226, 0x0000}, "Thump Truck"}, {{3227, 0x0000}, "Crypt Crusher"}, {{3228, 0x0000}, "Stealth Stinger"}, {{3228, 0x4402}, "Nitro Stealth Stinger"}, {{3231, 0x0000}, "Dive Bomber"}, {{3231, 0x4402}, "Spring Ahead Dive Bomber"}, {{3232, 0x0000}, "Sky Slicer"}, {{3233, 0x0000}, "Clown Cruiser (Nintendo Only)"}, {{3233, 0x4402}, "Dark Clown Cruiser (Nintendo Only)"}, {{3234, 0x0000}, "Gold Rusher"}, {{3234, 0x4402}, "Power Blue Gold Rusher"}, {{3235, 0x0000}, "Shield Striker"}, {{3236, 0x0000}, "Sun Runner"}, {{3236, 0x4403}, "Legendary Sun Runner"}, {{3237, 0x0000}, "Sea Shadow"}, {{3237, 0x4402}, "Dark Sea Shadow"}, {{3238, 0x0000}, "Splatter Splasher"}, {{3238, 0x4402}, "Power Blue Splatter Splasher"}, {{3239, 0x0000}, "Soda Skimmer"}, {{3239, 0x4402}, "Nitro Soda Skimmer"}, {{3240, 0x0000}, "Barrel Blaster (Nintendo Only)"}, {{3240, 0x4402}, "Dark Barrel Blaster (Nintendo Only)"}, {{3241, 0x0000}, "Buzz Wing"}, {{3400, 0x0000}, "Fiesta"}, {{3400, 0x4515}, "Frightful Fiesta"}, {{3401, 0x0000}, "High Volt"}, {{3402, 0x0000}, "Splat"}, {{3402, 0x4502}, "Power Blue Splat"}, {{3406, 0x0000}, "Stormblade"}, {{3411, 0x0000}, "Smash Hit"}, {{3411, 0x4502}, "Steel Plated Smash Hit"}, {{3412, 0x0000}, "Spitfire"}, {{3412, 0x4502}, "Dark Spitfire"}, {{3413, 0x0000}, "Hurricane Jet Vac"}, {{3413, 0x4503}, "Legendary Hurricane Jet Vac"}, {{3414, 0x0000}, "Double Dare Trigger Happy"}, {{3414, 0x4502}, "Power Blue Double Dare Trigger Happy"}, {{3415, 0x0000}, "Super Shot Stealth Elf"}, {{3415, 0x4502}, "Dark Super Shot Stealth Elf"}, {{3416, 0x0000}, "Shark Shooter Terrafin"}, {{3417, 0x0000}, "Bone Bash Roller Brawl"}, {{3417, 0x4503}, "Legendary Bone Bash Roller Brawl"}, {{3420, 0x0000}, "Big Bubble Pop Fizz"}, {{3420, 0x450E}, "Birthday Bash Big Bubble Pop Fizz"}, {{3421, 0x0000}, "Lava Lance Eruptor"}, {{3422, 0x0000}, "Deep Dive Gill Grunt"}, {{3423, 0x0000}, "Turbo Charge Donkey Kong (Nintendo Only)"}, {{3423, 0x4502}, "Dark Turbo Charge Donkey Kong (Nintendo Only)"}, {{3424, 0x0000}, "Hammer Slam Bowser (Nintendo Only)"}, {{3424, 0x4502}, "Dark Hammer Slam Bowser (Nintendo Only)"}, {{3425, 0x0000}, "Dive-Clops"}, {{3425, 0x450E}, "Missile-Tow Dive-Clops"}, {{3426, 0x0000}, "Astroblast"}, {{3426, 0x4503}, "Legendary Astroblast"}, {{3427, 0x0000}, "Nightfall"}, {{3428, 0x0000}, "Thrillipede"}, {{3428, 0x450D}, "Eggcited Thrillipede"}, {{3500, 0x0000}, "Sky Trophy"}, {{3501, 0x0000}, "Land Trophy"}, {{3502, 0x0000}, "Sea Trophy"}, {{3503, 0x0000}, "Kaos Trophy"}, }; uint16 SkylanderUSB::SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size) { const unsigned short CRC_CCITT_TABLE[256] = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}; uint16 crc = initValue; for (uint32 i = 0; i < size; i++) { const uint16 tmp = (crc >> 8) ^ buffer[i]; crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp]; } return crc; } SkylanderPortalDevice::SkylanderPortalDevice() : Device(0x1430, 0x0150, 1, 2, 0) { m_IsOpened = false; } bool SkylanderPortalDevice::Open() { if (!IsOpened()) { m_IsOpened = true; } return true; } void SkylanderPortalDevice::Close() { if (IsOpened()) { m_IsOpened = false; } } bool SkylanderPortalDevice::IsOpened() { return m_IsOpened; } Device::ReadResult SkylanderPortalDevice::Read(ReadMessage* message) { memcpy(message->data, g_skyportal.GetStatus().data(), message->length); message->bytesRead = message->length; std::this_thread::sleep_for(std::chrono::milliseconds(10)); return Device::ReadResult::Success; } Device::WriteResult SkylanderPortalDevice::Write(WriteMessage* message) { if (message->length != 64) { cemu_assert_error(); } if (!g_portalAudio) { // Portal audio is mono channel, 16 bit audio. // Audio is unsigned 16 bit, supplied as 64 bytes which is 32 samples per block g_portalAudio = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::Portal, 8000, 32, 16); } std::array<sint16, 32> mono_samples; for (unsigned int i = 0; i < mono_samples.size(); ++i) { sint16 sample = static_cast<uint16>(message->data[i * 2 + 1]) << 8 | static_cast<uint16>(message->data[i * 2]); mono_samples[i] = sample; } if (g_portalAudio) { g_portalAudio->FeedBlock(mono_samples.data()); } message->bytesWritten = message->length; return Device::WriteResult::Success; } bool SkylanderPortalDevice::GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) { uint8 configurationDescriptor[0x29]; uint8* currentWritePtr; // configuration descriptor currentWritePtr = configurationDescriptor + 0; *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower currentWritePtr = currentWritePtr + 9; // interface descriptor *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol *(uint8*)(currentWritePtr + 8) = 0; // iInterface currentWritePtr = currentWritePtr + 9; // HID descriptor *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength currentWritePtr = currentWritePtr + 9; // endpoint descriptor 1 *(uint8*)(currentWritePtr + 0) = 7; // bLength *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; // endpoint descriptor 2 *(uint8*)(currentWritePtr + 0) = 7; // bLength *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); memcpy(output, configurationDescriptor, std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); return true; } bool SkylanderPortalDevice::SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) { return true; } bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol) { return true; } bool SkylanderPortalDevice::SetReport(ReportMessage* message) { g_skyportal.ControlTransfer(message->data, message->length); std::this_thread::sleep_for(std::chrono::milliseconds(1)); return true; } void SkylanderUSB::ControlTransfer(uint8* buf, uint32 length) { std::array<uint8, 64> interruptResponse = {}; switch (buf[0]) { case 'A': { interruptResponse = {buf[0], buf[1], 0xFF, 0x77}; g_skyportal.Activate(); break; } case 'C': { g_skyportal.SetLeds(0x01, buf[1], buf[2], buf[3]); break; } case 'J': { g_skyportal.SetLeds(buf[1], buf[2], buf[3], buf[4]); interruptResponse = {buf[0]}; break; } case 'L': { uint8 side = buf[1]; if (side == 0x02) { side = 0x04; } g_skyportal.SetLeds(side, buf[2], buf[3], buf[4]); break; } case 'M': { interruptResponse = {buf[0], buf[1], 0x00, 0x19}; break; } case 'Q': { const uint8 skyNum = buf[1] & 0xF; const uint8 block = buf[2]; g_skyportal.QueryBlock(skyNum, block, interruptResponse.data()); break; } case 'R': { interruptResponse = {buf[0], 0x02, 0x1b}; break; } case 'S': case 'V': { // No response needed break; } case 'W': { const uint8 skyNum = buf[1] & 0xF; const uint8 block = buf[2]; g_skyportal.WriteBlock(skyNum, block, &buf[3], interruptResponse.data()); break; } default: cemu_assert_error(); break; } if (interruptResponse[0] != 0) { std::lock_guard lock(m_queryMutex); m_queries.push(interruptResponse); } } void SkylanderUSB::Activate() { std::lock_guard lock(m_skyMutex); if (m_activated) { // If the portal was already active no change is needed return; } // If not we need to advertise change to all the figures present on the portal for (auto& s : m_skylanders) { if (s.status & 1) { s.queuedStatus.push(3); s.queuedStatus.push(1); } } m_activated = true; } void SkylanderUSB::Deactivate() { std::lock_guard lock(m_skyMutex); for (auto& s : m_skylanders) { // check if at the end of the updates there would be a figure on the portal if (!s.queuedStatus.empty()) { s.status = s.queuedStatus.back(); s.queuedStatus = std::queue<uint8>(); } s.status &= 1; } m_activated = false; } void SkylanderUSB::SetLeds(uint8 side, uint8 r, uint8 g, uint8 b) { std::lock_guard lock(m_skyMutex); if (side == 0x00) { m_colorRight.red = r; m_colorRight.green = g; m_colorRight.blue = b; } else if (side == 0x01) { m_colorRight.red = r; m_colorRight.green = g; m_colorRight.blue = b; m_colorLeft.red = r; m_colorLeft.green = g; m_colorLeft.blue = b; } else if (side == 0x02) { m_colorLeft.red = r; m_colorLeft.green = g; m_colorLeft.blue = b; } else if (side == 0x03) { m_colorTrap.red = r; m_colorTrap.green = g; m_colorTrap.blue = b; } } uint8 SkylanderUSB::LoadSkylander(uint8* buf, std::unique_ptr<FileStream> file) { std::lock_guard lock(m_skyMutex); uint32 skySerial = 0; for (int i = 3; i > -1; i--) { skySerial <<= 8; skySerial |= buf[i]; } uint8 foundSlot = 0xFF; // mimics spot retaining on the portal for (auto i = 0; i < 16; i++) { if ((m_skylanders[i].status & 1) == 0) { if (m_skylanders[i].lastId == skySerial) { foundSlot = i; break; } if (i < foundSlot) { foundSlot = i; } } } if (foundSlot != 0xFF) { auto& skylander = m_skylanders[foundSlot]; memcpy(skylander.data.data(), buf, skylander.data.size()); skylander.skyFile = std::move(file); skylander.status = Skylander::ADDED; skylander.queuedStatus.push(Skylander::ADDED); skylander.queuedStatus.push(Skylander::READY); skylander.lastId = skySerial; } return foundSlot; } bool SkylanderUSB::RemoveSkylander(uint8 skyNum) { std::lock_guard lock(m_skyMutex); auto& thesky = m_skylanders[skyNum]; if (thesky.status & 1) { thesky.status = 2; thesky.queuedStatus.push(2); thesky.queuedStatus.push(0); thesky.Save(); thesky.skyFile.reset(); return true; } return false; } bool SkylanderUSB::CreateSkylander(fs::path pathName, uint16 skyId, uint16 skyVar) { FileStream* skyFile(FileStream::createFile2(pathName)); if (!skyFile) { return false; } std::array<uint8, SKY_FIGURE_SIZE> data{}; uint32 first_block = 0x690F0F0F; uint32 other_blocks = 0x69080F7F; memcpy(&data[0x36], &first_block, sizeof(first_block)); for (size_t index = 1; index < 0x10; index++) { memcpy(&data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks)); } std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution<int> dist(0, 255); data[0] = dist(mt); data[1] = dist(mt); data[2] = dist(mt); data[3] = dist(mt); data[4] = data[0] ^ data[1] ^ data[2] ^ data[3]; data[5] = 0x81; data[6] = 0x01; data[7] = 0x0F; memcpy(&data[0x10], &skyId, sizeof(skyId)); memcpy(&data[0x1C], &skyVar, sizeof(skyVar)); uint16 crc = nsyshid::g_skyportal.SkylanderCRC16(0xFFFF, data.data(), 0x1E); memcpy(&data[0x1E], &crc, sizeof(crc)); skyFile->writeData(data.data(), data.size()); delete skyFile; return true; } void SkylanderUSB::QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf) { std::lock_guard lock(m_skyMutex); const auto& skylander = m_skylanders[skyNum]; replyBuf[0] = 'Q'; replyBuf[2] = block; if (skylander.status & 1) { replyBuf[1] = (0x10 | skyNum); memcpy(replyBuf + 3, skylander.data.data() + (16 * block), 16); } else { replyBuf[1] = skyNum; } } void SkylanderUSB::WriteBlock(uint8 skyNum, uint8 block, const uint8* toWriteBuf, uint8* replyBuf) { std::lock_guard lock(m_skyMutex); auto& skylander = m_skylanders[skyNum]; replyBuf[0] = 'W'; replyBuf[2] = block; if (skylander.status & 1) { replyBuf[1] = (0x10 | skyNum); memcpy(skylander.data.data() + (block * 16), toWriteBuf, 16); skylander.Save(); } else { replyBuf[1] = skyNum; } } std::array<uint8, 64> SkylanderUSB::GetStatus() { std::lock_guard lock(m_queryMutex); std::array<uint8, 64> interruptResponse = {}; if (!m_queries.empty()) { interruptResponse = m_queries.front(); m_queries.pop(); // This needs to happen after ~22 milliseconds } else { uint32 status = 0; uint8 active = 0x00; if (m_activated) { active = 0x01; } for (int i = 16 - 1; i >= 0; i--) { auto& s = m_skylanders[i]; if (!s.queuedStatus.empty()) { s.status = s.queuedStatus.front(); s.queuedStatus.pop(); } status <<= 2; status |= s.status; } interruptResponse = {0x53, 0x00, 0x00, 0x00, 0x00, m_interruptCounter++, active, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; memcpy(&interruptResponse[1], &status, sizeof(status)); } return interruptResponse; } std::string SkylanderUSB::FindSkylander(uint16 skyId, uint16 skyVar) { for (const auto& it : GetListSkylanders()) { if (it.first.first == skyId && it.first.second == skyVar) { return it.second; } } return fmt::format("Unknown ({} {})", skyId, skyVar); } std::map<const std::pair<const uint16, const uint16>, const char*> SkylanderUSB::GetListSkylanders() { return s_listSkylanders; } void SkylanderUSB::Skylander::Save() { if (!skyFile) return; skyFile->SetPosition(0); skyFile->writeData(data.data(), data.size()); } } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/Skylander.h ================================================ #pragma once #include <mutex> #include "nsyshid.h" #include "Backend.h" #include "Common/FileStream.h" namespace nsyshid { class SkylanderPortalDevice final : public Device { public: SkylanderPortalDevice(); ~SkylanderPortalDevice() = default; bool Open() override; void Close() override; bool IsOpened() override; ReadResult Read(ReadMessage* message) override; WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) override; bool SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) override; bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; private: bool m_IsOpened; }; constexpr uint16 SKY_BLOCK_COUNT = 0x40; constexpr uint16 SKY_BLOCK_SIZE = 0x10; constexpr uint16 SKY_FIGURE_SIZE = SKY_BLOCK_COUNT * SKY_BLOCK_SIZE; constexpr uint8 MAX_SKYLANDERS = 16; class SkylanderUSB { public: struct Skylander final { std::unique_ptr<FileStream> skyFile; uint8 status = 0; std::queue<uint8> queuedStatus; std::array<uint8, SKY_FIGURE_SIZE> data{}; uint32 lastId = 0; void Save(); enum : uint8 { REMOVED = 0, READY = 1, REMOVING = 2, ADDED = 3 }; }; struct SkylanderLEDColor final { uint8 red = 0; uint8 green = 0; uint8 blue = 0; }; void ControlTransfer(uint8* buf, uint32 length); void Activate(); void Deactivate(); void SetLeds(uint8 side, uint8 r, uint8 g, uint8 b); std::array<uint8, 64> GetStatus(); void QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf); void WriteBlock(uint8 skyNum, uint8 block, const uint8* toWriteBuf, uint8* replyBuf); uint8 LoadSkylander(uint8* buf, std::unique_ptr<FileStream> file); bool RemoveSkylander(uint8 skyNum); bool CreateSkylander(fs::path pathName, uint16 skyId, uint16 skyVar); uint16 SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size); static std::map<const std::pair<const uint16, const uint16>, const char*> GetListSkylanders(); std::string FindSkylander(uint16 skyId, uint16 skyVar); protected: std::mutex m_skyMutex; std::mutex m_queryMutex; std::array<Skylander, MAX_SKYLANDERS> m_skylanders; private: std::queue<std::array<uint8, 64>> m_queries; bool m_activated = true; uint8 m_interruptCounter = 0; SkylanderLEDColor m_colorRight = {}; SkylanderLEDColor m_colorLeft = {}; SkylanderLEDColor m_colorTrap = {}; }; extern SkylanderUSB g_skyportal; } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp ================================================ #include "SkylanderXbox360.h" namespace nsyshid { SkylanderXbox360PortalLibusb::SkylanderXbox360PortalLibusb(std::shared_ptr<Device> usbPortal) : Device(0x1430, 0x0150, 1, 2, 0) { m_IsOpened = false; m_usbPortal = std::static_pointer_cast<backend::libusb::DeviceLibusb>(usbPortal); } bool SkylanderXbox360PortalLibusb::Open() { return m_usbPortal->Open(); } void SkylanderXbox360PortalLibusb::Close() { return m_usbPortal->Close(); } bool SkylanderXbox360PortalLibusb::IsOpened() { return m_usbPortal->IsOpened(); } Device::ReadResult SkylanderXbox360PortalLibusb::Read(ReadMessage* message) { std::vector<uint8> xboxData(std::min<uint32>(32, message->length + sizeof(XBOX_DATA_HEADER))); memcpy(xboxData.data(), XBOX_DATA_HEADER, sizeof(XBOX_DATA_HEADER)); memcpy(xboxData.data() + sizeof(XBOX_DATA_HEADER), message->data, message->length - sizeof(XBOX_DATA_HEADER)); ReadMessage xboxMessage(xboxData.data(), xboxData.size(), 0); auto result = m_usbPortal->Read(&xboxMessage); memcpy(message->data, xboxData.data() + sizeof(XBOX_DATA_HEADER), message->length); message->bytesRead = xboxMessage.bytesRead; return result; } // Use InterruptTransfer instead of ControlTransfer bool SkylanderXbox360PortalLibusb::SetReport(ReportMessage* message) { if (message->data[0] == 'M' && message->data[1] == 0x01) // Enables Speaker g72x_init_state(&m_state); std::vector<uint8> xboxData(message->length + sizeof(XBOX_DATA_HEADER)); memcpy(xboxData.data(), XBOX_DATA_HEADER, sizeof(XBOX_DATA_HEADER)); memcpy(xboxData.data() + sizeof(XBOX_DATA_HEADER), message->data, message->length); WriteMessage xboxMessage(xboxData.data(), xboxData.size(), 0); auto result = m_usbPortal->Write(&xboxMessage); memcpy(message->data, xboxData.data() + sizeof(XBOX_DATA_HEADER), message->length); return result == WriteResult::Success; } Device::WriteResult SkylanderXbox360PortalLibusb::Write(WriteMessage* message) { std::vector<uint8> audioData(message->data, message->data + message->length); std::vector<uint8_t> xboxAudioData(audioData.size() / 4); for (size_t i = 0; i < audioData.size(); i += 4) { int16_t sample1 = (static_cast<int16_t>(audioData[i + 1]) << 8) | audioData[i]; int16_t sample2 = (static_cast<int16_t>(audioData[i + 3]) << 8) | audioData[i + 2]; uint8_t encoded1 = g721_encoder(sample1, &m_state) & 0x0F; uint8_t encoded2 = g721_encoder(sample2, &m_state) & 0x0F; xboxAudioData[i / 4] = ((encoded2 << 4) | encoded1); } std::vector<uint8> xboxData(xboxAudioData.size() + sizeof(XBOX_AUDIO_DATA_HEADER)); memcpy(xboxData.data(), XBOX_AUDIO_DATA_HEADER, sizeof(XBOX_AUDIO_DATA_HEADER)); memcpy(xboxData.data() + sizeof(XBOX_AUDIO_DATA_HEADER), xboxAudioData.data(), xboxAudioData.size()); WriteMessage xboxMessage(xboxData.data(), xboxData.size(), 0); auto result = m_usbPortal->Write(&xboxMessage); memcpy(message->data, xboxData.data() + sizeof(XBOX_AUDIO_DATA_HEADER), xboxAudioData.size()); message->bytesWritten = xboxMessage.bytesWritten - sizeof(XBOX_AUDIO_DATA_HEADER); return result; } bool SkylanderXbox360PortalLibusb::GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) { uint8 configurationDescriptor[0x29]; uint8* currentWritePtr; // configuration descriptor currentWritePtr = configurationDescriptor + 0; *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower currentWritePtr = currentWritePtr + 9; // interface descriptor *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol *(uint8*)(currentWritePtr + 8) = 0; // iInterface currentWritePtr = currentWritePtr + 9; // HID descriptor *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength currentWritePtr = currentWritePtr + 9; // endpoint descriptor 1 *(uint8*)(currentWritePtr + 0) = 7; // bLength *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; // endpoint descriptor 2 *(uint8*)(currentWritePtr + 0) = 7; // bLength *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); memcpy(output, configurationDescriptor, std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); return true; } bool SkylanderXbox360PortalLibusb::SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) { return true; } bool SkylanderXbox360PortalLibusb::SetProtocol(uint8 ifIndex, uint8 protocol) { return true; } } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/SkylanderXbox360.h ================================================ #pragma once #include "nsyshid.h" #include "BackendLibusb.h" #include "g721/g721.h" namespace nsyshid { class SkylanderXbox360PortalLibusb final : public Device { public: SkylanderXbox360PortalLibusb(std::shared_ptr<Device> usbPortal); ~SkylanderXbox360PortalLibusb() = default; bool Open() override; void Close() override; bool IsOpened() override; ReadResult Read(ReadMessage* message) override; WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) override; bool SetIdle(uint8 ifIndex, uint8 reportId, uint8 duration) override; bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; private: std::shared_ptr<backend::libusb::DeviceLibusb> m_usbPortal; bool m_IsOpened; struct g72x_state m_state; }; constexpr uint8 XBOX_DATA_HEADER[] = { 0x0B, 0x14 }; constexpr uint8 XBOX_AUDIO_DATA_HEADER[] = { 0x0B, 0x17 }; } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/Whitelist.cpp ================================================ #include "Whitelist.h" namespace nsyshid { Whitelist& Whitelist::GetInstance() { static Whitelist whitelist; return whitelist; } Whitelist::Whitelist() { // add known devices { // lego dimensions portal m_devices.emplace_back(0x0e6f, 0x0241); // skylanders portal m_devices.emplace_back(0x1430, 0x0150); // skylanders 360 portal m_devices.emplace_back(0x1430, 0x1F17); // disney infinity base m_devices.emplace_back(0x0e6f, 0x0129); // kamen rider ride gate m_devices.emplace_back(0x0e6f, 0x200A); } } bool Whitelist::IsDeviceWhitelisted(uint16 vendorId, uint16 productId) { auto it = std::find(m_devices.begin(), m_devices.end(), std::tuple<uint16, uint16>(vendorId, productId)); return it != m_devices.end(); } void Whitelist::AddDevice(uint16 vendorId, uint16 productId) { if (!IsDeviceWhitelisted(vendorId, productId)) { m_devices.emplace_back(vendorId, productId); } } void Whitelist::RemoveDevice(uint16 vendorId, uint16 productId) { m_devices.remove(std::tuple<uint16, uint16>(vendorId, productId)); } std::list<std::tuple<uint16, uint16>> Whitelist::GetDevices() { return m_devices; } void Whitelist::RemoveAllDevices() { m_devices.clear(); } } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/Whitelist.h ================================================ #ifndef CEMU_NSYSHID_WHITELIST_H #define CEMU_NSYSHID_WHITELIST_H namespace nsyshid { class Whitelist { public: static Whitelist& GetInstance(); Whitelist(const Whitelist&) = delete; Whitelist& operator=(const Whitelist&) = delete; bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); void AddDevice(uint16 vendorId, uint16 productId); void RemoveDevice(uint16 vendorId, uint16 productId); std::list<std::tuple<uint16, uint16>> GetDevices(); void RemoveAllDevices(); private: Whitelist(); // vendorId, productId std::list<std::tuple<uint16, uint16>> m_devices; }; } // namespace nsyshid #endif // CEMU_NSYSHID_WHITELIST_H ================================================ FILE: src/Cafe/OS/libs/nsyshid/g721/g721.cpp ================================================ /* * This source code is a product of Sun Microsystems, Inc. and is provided * for unrestricted use. Users may copy or modify this source code without * charge. * * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. * * Sun source code is provided with no support and without any obligation on * the part of Sun Microsystems, Inc. to assist in its use, correction, * modification or enhancement. * * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE * OR ANY PART THEREOF. * * In no event will Sun Microsystems, Inc. be liable for any lost revenue * or profits or other special, indirect and consequential damages, even if * Sun has been advised of the possibility of such damages. * * Sun Microsystems, Inc. * 2550 Garcia Avenue * Mountain View, California 94043 */ /* * g721.c * * Description: * * g721_encoder(), g721_decoder() * * These routines comprise an implementation of the CCITT G.721 ADPCM * coding algorithm. Essentially, this implementation is identical to * the bit level description except for a few deviations which * take advantage of work station attributes, such as hardware 2's * complement arithmetic and large memory. Specifically, certain time * consuming operations such as multiplications are replaced * with lookup tables and software 2's complement operations are * replaced with hardware 2's complement. * * The deviation from the bit level specification (lookup tables) * preserves the bit level performance specifications. * * As outlined in the G.721 Recommendation, the algorithm is broken * down into modules. Each section of code below is preceded by * the name of the module which it is implementing. * */ #include "g721.h" #include <stdlib.h> static short qtab_721[7] = { -124, 80, 178, 246, 300, 349, 400 }; /* * Maps G.721 code word to reconstructed scale factor normalized log * magnitude values. */ static short _dqlntab[16] = { -2048, 4, 135, 213, 273, 323, 373, 425, 425, 373, 323, 273, 213, 135, 4, -2048 }; /* Maps G.721 code word to log of scale factor multiplier. */ static short _witab[16] = { -12, 18, 41, 64, 112, 198, 355, 1122, 1122, 355, 198, 112, 64, 41, 18, -12 }; /* * Maps G.721 code words to a set of values whose long and short * term averages are computed and then compared to give an indication * how stationary (steady state) the signal is. */ static short _fitab[16] = { 0, 0, 0, 0x200, 0x200, 0x200, 0x600, 0xE00, 0xE00, 0x600, 0x200, 0x200, 0x200, 0, 0, 0 }; /* * g721_encoder() * * Encodes the input value of linear PCM from sl and returns * the resulting code. */ int g721_encoder(int sl, struct g72x_state* state_ptr) { short sezi, se, sez; /* ACCUM */ short d; /* SUBTA */ short sr; /* ADDB */ short y; /* MIX */ short dqsez; /* ADDC */ short dq, i; sl >>= 2; /* linearize input sample to 14-bit PCM */ sezi = predictor_zero(state_ptr); sez = sezi >> 1; se = (sezi + predictor_pole(state_ptr)) >> 1; /* estimated signal */ d = sl - se; /* estimation difference */ /* quantize the prediction difference */ y = step_size(state_ptr); /* quantizer step size */ i = quantize(d, y, qtab_721, 7); /* i = ADPCM code */ dq = reconstruct(i & 8, _dqlntab[i], y); /* quantized est diff */ sr = (dq < 0) ? se - (dq & 0x3FFF) : se + dq; /* reconst. signal */ dqsez = sr + sez - se; /* pole prediction diff. */ update(4, y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state_ptr); return (i); } /* * g721_decoder() * * Description: * * Decodes a 4-bit code of G.721 encoded data of i and * returns the resulting linear PCM */ int g721_decoder(int i, struct g72x_state* state_ptr) { short sezi, sei, sez, se; /* ACCUM */ short y; /* MIX */ short sr; /* ADDB */ short dq; short dqsez; i &= 0x0f; /* mask to get proper bits */ sezi = predictor_zero(state_ptr); sez = sezi >> 1; sei = sezi + predictor_pole(state_ptr); se = sei >> 1; /* se = estimated signal */ y = step_size(state_ptr); /* dynamic quantizer step size */ dq = reconstruct(i & 0x08, _dqlntab[i], y); /* quantized diff. */ sr = (dq < 0) ? (se - (dq & 0x3FFF)) : se + dq; /* reconst. signal */ dqsez = sr - se + sez; /* pole prediction diff. */ update(4, y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state_ptr); return (sr << 2); } static short power2[15] = { 1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000 }; /* * quan() * * quantizes the input val against the table of size short integers. * It returns i if table[i - 1] <= val < table[i]. * * Using linear search for simple coding. */ static int quan(int val, short* table, int size) { int i; for (i = 0; i < size; i++) if (val < *table++) break; return (i); } /* * fmult() * * returns the integer product of the 14-bit integer "an" and * "floating point" representation (4-bit exponent, 6-bit mantessa) "srn". */ static int fmult(int an, int srn) { short anmag, anexp, anmant; short wanexp, wanmant; short retval; anmag = (an > 0) ? an : ((-an) & 0x1FFF); anexp = quan(anmag, power2, 15) - 6; anmant = (anmag == 0) ? 32 : (anexp >= 0) ? anmag >> anexp : anmag << -anexp; wanexp = anexp + ((srn >> 6) & 0xF) - 13; wanmant = (anmant * (srn & 077) + 0x30) >> 4; retval = (wanexp >= 0) ? ((wanmant << wanexp) & 0x7FFF) : (wanmant >> -wanexp); return (((an ^ srn) < 0) ? -retval : retval); } /* * update() * * updates the state variables for each output code */ void update(int code_size, /* distinguish 723_40 with others */ int y, /* quantizer step size */ int wi, /* scale factor multiplier */ int fi, /* for long/short term energies */ int dq, /* quantized prediction difference */ int sr, /* reconstructed signal */ int dqsez, /* difference from 2-pole predictor */ struct g72x_state* state_ptr) /* coder state pointer */ { int cnt; short mag, exp; /* Adaptive predictor, FLOAT A */ short a2p = 0; /* LIMC */ short a1ul; /* UPA1 */ short pks1; /* UPA2 */ short fa1; char tr; /* tone/transition detector */ short ylint, thr2, dqthr; short ylfrac, thr1; short pk0; pk0 = (dqsez < 0) ? 1 : 0; /* needed in updating predictor poles */ mag = dq & 0x7FFF; /* prediction difference magnitude */ /* TRANS */ ylint = state_ptr->yl >> 15; /* exponent part of yl */ ylfrac = (state_ptr->yl >> 10) & 0x1F; /* fractional part of yl */ thr1 = (32 + ylfrac) << ylint; /* threshold */ thr2 = (ylint > 9) ? 31 << 10 : thr1; /* limit thr2 to 31 << 10 */ dqthr = (thr2 + (thr2 >> 1)) >> 1; /* dqthr = 0.75 * thr2 */ if (state_ptr->td == 0) /* signal supposed voice */ tr = 0; else if (mag <= dqthr) /* supposed data, but small mag */ tr = 0; /* treated as voice */ else /* signal is data (modem) */ tr = 1; /* * Quantizer scale factor adaptation. */ /* FUNCTW & FILTD & DELAY */ /* update non-steady state step size multiplier */ state_ptr->yu = y + ((wi - y) >> 5); /* LIMB */ if (state_ptr->yu < 544) /* 544 <= yu <= 5120 */ state_ptr->yu = 544; else if (state_ptr->yu > 5120) state_ptr->yu = 5120; /* FILTE & DELAY */ /* update steady state step size multiplier */ state_ptr->yl += state_ptr->yu + ((-state_ptr->yl) >> 6); /* * Adaptive predictor coefficients. */ if (tr == 1) { /* reset a's and b's for modem signal */ state_ptr->a[0] = 0; state_ptr->a[1] = 0; state_ptr->b[0] = 0; state_ptr->b[1] = 0; state_ptr->b[2] = 0; state_ptr->b[3] = 0; state_ptr->b[4] = 0; state_ptr->b[5] = 0; } else { /* update a's and b's */ pks1 = pk0 ^ state_ptr->pk[0]; /* UPA2 */ /* update predictor pole a[1] */ a2p = state_ptr->a[1] - (state_ptr->a[1] >> 7); if (dqsez != 0) { fa1 = (pks1) ? state_ptr->a[0] : -state_ptr->a[0]; if (fa1 < -8191) /* a2p = function of fa1 */ a2p -= 0x100; else if (fa1 > 8191) a2p += 0xFF; else a2p += fa1 >> 5; if (pk0 ^ state_ptr->pk[1]) /* LIMC */ if (a2p <= -12160) a2p = -12288; else if (a2p >= 12416) a2p = 12288; else a2p -= 0x80; else if (a2p <= -12416) a2p = -12288; else if (a2p >= 12160) a2p = 12288; else a2p += 0x80; } /* TRIGB & DELAY */ state_ptr->a[1] = a2p; /* UPA1 */ /* update predictor pole a[0] */ state_ptr->a[0] -= state_ptr->a[0] >> 8; if (dqsez != 0) { if (pks1 == 0) state_ptr->a[0] += 192; else state_ptr->a[0] -= 192; } /* LIMD */ a1ul = 15360 - a2p; if (state_ptr->a[0] < -a1ul) state_ptr->a[0] = -a1ul; else if (state_ptr->a[0] > a1ul) state_ptr->a[0] = a1ul; /* UPB : update predictor zeros b[6] */ for (cnt = 0; cnt < 6; cnt++) { if (code_size == 5) /* for 40Kbps G.723 */ state_ptr->b[cnt] -= state_ptr->b[cnt] >> 9; else /* for G.721 and 24Kbps G.723 */ state_ptr->b[cnt] -= state_ptr->b[cnt] >> 8; if (dq & 0x7FFF) { /* XOR */ if ((dq ^ state_ptr->dq[cnt]) >= 0) state_ptr->b[cnt] += 128; else state_ptr->b[cnt] -= 128; } } } for (cnt = 5; cnt > 0; cnt--) state_ptr->dq[cnt] = state_ptr->dq[cnt - 1]; /* FLOAT A : convert dq[0] to 4-bit exp, 6-bit mantissa f.p. */ if (mag == 0) { state_ptr->dq[0] = (dq >= 0) ? 0x20 : 0xFC20; } else { exp = quan(mag, power2, 15); state_ptr->dq[0] = (dq >= 0) ? (exp << 6) + ((mag << 6) >> exp) : (exp << 6) + ((mag << 6) >> exp) - 0x400; } state_ptr->sr[1] = state_ptr->sr[0]; /* FLOAT B : convert sr to 4-bit exp., 6-bit mantissa f.p. */ if (sr == 0) { state_ptr->sr[0] = 0x20; } else if (sr > 0) { exp = quan(sr, power2, 15); state_ptr->sr[0] = (exp << 6) + ((sr << 6) >> exp); } else if (sr > -32768) { mag = -sr; exp = quan(mag, power2, 15); state_ptr->sr[0] = (exp << 6) + ((mag << 6) >> exp) - 0x400; } else state_ptr->sr[0] = 0xFC20; /* DELAY A */ state_ptr->pk[1] = state_ptr->pk[0]; state_ptr->pk[0] = pk0; /* TONE */ if (tr == 1) /* this sample has been treated as data */ state_ptr->td = 0; /* next one will be treated as voice */ else if (a2p < -11776) /* small sample-to-sample correlation */ state_ptr->td = 1; /* signal may be data */ else /* signal is voice */ state_ptr->td = 0; /* * Adaptation speed control. */ state_ptr->dms += (fi - state_ptr->dms) >> 5; /* FILTA */ state_ptr->dml += (((fi << 2) - state_ptr->dml) >> 7); /* FILTB */ if (tr == 1) state_ptr->ap = 256; else if (y < 1536) /* SUBTC */ state_ptr->ap += (0x200 - state_ptr->ap) >> 4; else if (state_ptr->td == 1) state_ptr->ap += (0x200 - state_ptr->ap) >> 4; else if (abs((state_ptr->dms << 2) - state_ptr->dml) >= (state_ptr->dml >> 3)) state_ptr->ap += (0x200 - state_ptr->ap) >> 4; else state_ptr->ap += (-state_ptr->ap) >> 4; } /* * g72x_init_state() * * This routine initializes and/or resets the g72x_state structure * pointed to by 'state_ptr'. * All the initial state values are specified in the CCITT G.721 document. */ void g72x_init_state(struct g72x_state* state_ptr) { int cnta; state_ptr->yl = 34816; state_ptr->yu = 544; state_ptr->dms = 0; state_ptr->dml = 0; state_ptr->ap = 0; for (cnta = 0; cnta < 2; cnta++) { state_ptr->a[cnta] = 0; state_ptr->pk[cnta] = 0; state_ptr->sr[cnta] = 32; } for (cnta = 0; cnta < 6; cnta++) { state_ptr->b[cnta] = 0; state_ptr->dq[cnta] = 32; } state_ptr->td = 0; } /* * predictor_zero() * * computes the estimated signal from 6-zero predictor. * */ int predictor_zero(struct g72x_state* state_ptr) { int i; int sezi; sezi = fmult(state_ptr->b[0] >> 2, state_ptr->dq[0]); for (i = 1; i < 6; i++) /* ACCUM */ sezi += fmult(state_ptr->b[i] >> 2, state_ptr->dq[i]); return (sezi); } /* * predictor_pole() * * computes the estimated signal from 2-pole predictor. * */ int predictor_pole(struct g72x_state* state_ptr) { return (fmult(state_ptr->a[1] >> 2, state_ptr->sr[1]) + fmult(state_ptr->a[0] >> 2, state_ptr->sr[0])); } /* * step_size() * * computes the quantization step size of the adaptive quantizer. * */ int step_size(struct g72x_state* state_ptr) { int y; int dif; int al; if (state_ptr->ap >= 256) return (state_ptr->yu); else { y = state_ptr->yl >> 6; dif = state_ptr->yu - y; al = state_ptr->ap >> 2; if (dif > 0) y += (dif * al) >> 6; else if (dif < 0) y += (dif * al + 0x3F) >> 6; return (y); } } /* * quantize() * * Given a raw sample, 'd', of the difference signal and a * quantization step size scale factor, 'y', this routine returns the * ADPCM codeword to which that sample gets quantized. The step * size scale factor division operation is done in the log base 2 domain * as a subtraction. */ int quantize(int d, /* Raw difference signal sample */ int y, /* Step size multiplier */ short* table, /* quantization table */ int size) /* table size of short integers */ { short dqm; /* Magnitude of 'd' */ short exp; /* Integer part of base 2 log of 'd' */ short mant; /* Fractional part of base 2 log */ short dl; /* Log of magnitude of 'd' */ short dln; /* Step size scale factor normalized log */ int i; /* * LOG * * Compute base 2 log of 'd', and store in 'dl'. */ dqm = abs(d); exp = quan(dqm >> 1, power2, 15); mant = ((dqm << 7) >> exp) & 0x7F; /* Fractional portion. */ dl = (exp << 7) + mant; /* * SUBTB * * "Divide" by step size multiplier. */ dln = dl - (y >> 2); /* * QUAN * * Obtain codword i for 'd'. */ i = quan(dln, table, size); if (d < 0) /* take 1's complement of i */ return ((size << 1) + 1 - i); else if (i == 0) /* take 1's complement of 0 */ return ((size << 1) + 1); /* new in 1988 */ else return (i); } /* * reconstruct() * * Returns reconstructed difference signal 'dq' obtained from * codeword 'i' and quantization step size scale factor 'y'. * Multiplication is performed in log base 2 domain as addition. */ int reconstruct(int sign, /* 0 for non-negative value */ int dqln, /* G.72x codeword */ int y) /* Step size multiplier */ { short dql; /* Log of 'dq' magnitude */ short dex; /* Integer part of log */ short dqt; short dq; /* Reconstructed difference signal sample */ dql = dqln + (y >> 2); /* ADDA */ if (dql < 0) { return ((sign) ? -0x8000 : 0); } else { /* ANTILOG */ dex = (dql >> 7) & 15; dqt = 128 + (dql & 127); dq = (dqt << 7) >> (14 - dex); return ((sign) ? (dq - 0x8000) : dq); } } ================================================ FILE: src/Cafe/OS/libs/nsyshid/g721/g721.h ================================================ /* * This source code is a product of Sun Microsystems, Inc. and is provided * for unrestricted use. Users may copy or modify this source code without * charge. * * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. * * Sun source code is provided with no support and without any obligation on * the part of Sun Microsystems, Inc. to assist in its use, correction, * modification or enhancement. * * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE * OR ANY PART THEREOF. * * In no event will Sun Microsystems, Inc. be liable for any lost revenue * or profits or other special, indirect and consequential damages, even if * Sun has been advised of the possibility of such damages. * * Sun Microsystems, Inc. * 2550 Garcia Avenue * Mountain View, California 94043 */ /* * g72x.h * * Header file for CCITT conversion routines. * */ #ifndef _G72X_H #define _G72X_H /* * The following is the definition of the state structure * used by the G.721/G.723 encoder and decoder to preserve their internal * state between successive calls. The meanings of the majority * of the state structure fields are explained in detail in the * CCITT Recommendation G.721. The field names are essentially identical * to variable names in the bit level description of the coding algorithm * included in this Recommendation. */ struct g72x_state { long yl; /* Locked or steady state step size multiplier. */ short yu; /* Unlocked or non-steady state step size multiplier. */ short dms; /* Short term energy estimate. */ short dml; /* Long term energy estimate. */ short ap; /* Linear weighting coefficient of 'yl' and 'yu'. */ short a[2]; /* Coefficients of pole portion of prediction filter. */ short b[6]; /* Coefficients of zero portion of prediction filter. */ short pk[2]; /* * Signs of previous two samples of a partially * reconstructed signal. */ short dq[6]; /* * Previous 6 samples of the quantized difference * signal represented in an internal floating point * format. */ short sr[2]; /* * Previous 2 samples of the quantized difference * signal represented in an internal floating point * format. */ char td; /* delayed tone detect, new in 1988 version */ }; /* External function definitions. */ void g72x_init_state(struct g72x_state*); int g721_encoder(int sample, struct g72x_state* state_ptr); int g721_decoder(int code, struct g72x_state* state_ptr); int quantize(int d, int y, short* table, int size); int reconstruct(int, int, int); void update(int code_size, int y, int wi, int fi, int dq, int sr, int dqsez, struct g72x_state* state_ptr); int predictor_zero(struct g72x_state* state_ptr); int predictor_pole(struct g72x_state* state_ptr); int step_size(struct g72x_state* state_ptr); #endif /* !_G72X_H */ ================================================ FILE: src/Cafe/OS/libs/nsyshid/nsyshid.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include <bitset> #include <mutex> #include "nsyshid.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Backend.h" #include "Whitelist.h" namespace nsyshid { std::list<std::shared_ptr<Backend>> backendList; std::list<std::shared_ptr<Device>> deviceList; typedef struct _HIDClient_t { uint32be callbackFunc; // attach/detach callback } HIDClient_t; std::list<HIDClient_t*> HIDClientList; std::recursive_mutex hidMutex; void AttachClientToList(HIDClient_t* hidClient) { std::lock_guard<std::recursive_mutex> lock(hidMutex); // todo - append at the beginning or end of the list? List order matters because it also controls the order in which attach callbacks are called HIDClientList.push_front(hidClient); } void DetachClientFromList(HIDClient_t* hidClient) { std::lock_guard<std::recursive_mutex> lock(hidMutex); HIDClientList.remove(hidClient); } std::shared_ptr<Device> GetDeviceByHandle(uint32 handle, bool openIfClosed = false) { std::shared_ptr<Device> device; { std::lock_guard<std::recursive_mutex> lock(hidMutex); for (const auto& d : deviceList) { if (d->m_hid->handle == handle) { device = d; break; } } } if (device != nullptr) { if (openIfClosed && !device->IsOpened()) { if (!device->Open()) { return nullptr; } } return device; } return nullptr; } uint32 _lastGeneratedHidHandle = 1; uint32 GenerateHIDHandle() { std::lock_guard<std::recursive_mutex> lock(hidMutex); _lastGeneratedHidHandle++; return _lastGeneratedHidHandle; } const int HID_MAX_NUM_DEVICES = 128; SysAllocator<HID_t, HID_MAX_NUM_DEVICES> HIDPool; std::queue<size_t> HIDPoolIndexQueue; void InitHIDPoolIndexQueue() { static bool HIDPoolIndexQueueInitialized = false; std::lock_guard<std::recursive_mutex> lock(hidMutex); if (HIDPoolIndexQueueInitialized) { return; } HIDPoolIndexQueueInitialized = true; for (size_t i = 0; i < HID_MAX_NUM_DEVICES; i++) { HIDPoolIndexQueue.push(i); } } HID_t* GetFreeHID() { std::lock_guard<std::recursive_mutex> lock(hidMutex); InitHIDPoolIndexQueue(); if (HIDPoolIndexQueue.empty()) { return nullptr; } size_t index = HIDPoolIndexQueue.front(); HIDPoolIndexQueue.pop(); return HIDPool.GetPtr() + index; } void ReleaseHID(HID_t* device) { // this should never happen, but having a safeguard can't hurt if (device == nullptr) { cemu_assert_error(); } std::lock_guard<std::recursive_mutex> lock(hidMutex); InitHIDPoolIndexQueue(); size_t index = device - HIDPool.GetPtr(); HIDPoolIndexQueue.push(index); } const int HID_CALLBACK_DETACH = 0; const int HID_CALLBACK_ATTACH = 1; uint32 DoAttachCallback(HIDClient_t* hidClient, const std::shared_ptr<Device>& device) { return PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_ATTACH); } void DoAttachCallbackAsync(HIDClient_t* hidClient, const std::shared_ptr<Device>& device) { coreinitAsyncCallback_add(hidClient->callbackFunc, 3, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_ATTACH); } void DoDetachCallback(HIDClient_t* hidClient, const std::shared_ptr<Device>& device) { PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_DETACH); } void DoDetachCallbackAsync(HIDClient_t* hidClient, const std::shared_ptr<Device>& device) { coreinitAsyncCallback_add(hidClient->callbackFunc, 3, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_DETACH); } void AttachBackend(const std::shared_ptr<Backend>& backend) { { std::lock_guard<std::recursive_mutex> lock(hidMutex); backendList.push_back(backend); } backend->OnAttach(); } void DetachBackend(const std::shared_ptr<Backend>& backend) { { std::lock_guard<std::recursive_mutex> lock(hidMutex); backendList.remove(backend); } backend->OnDetach(); } void DetachAllBackends() { std::list<std::shared_ptr<Backend>> backendListCopy; { std::lock_guard<std::recursive_mutex> lock(hidMutex); backendListCopy = backendList; backendList.clear(); } for (const auto& backend : backendListCopy) { backend->OnDetach(); } } void AttachDefaultBackends() { backend::AttachDefaultBackends(); } bool AttachDevice(const std::shared_ptr<Device>& device) { std::lock_guard<std::recursive_mutex> lock(hidMutex); // is the device already attached? { auto it = std::find(deviceList.begin(), deviceList.end(), device); if (it != deviceList.end()) { cemuLog_logDebug(LogType::Force, "nsyshid.AttachDevice(): failed to attach device: {:04x}:{:04x}: already attached", device->m_vendorId, device->m_productId); return false; } } HID_t* hidDevice = GetFreeHID(); if (hidDevice == nullptr) { cemuLog_logDebug(LogType::Force, "nsyshid.AttachDevice(): failed to attach device: {:04x}:{:04x}: no free device slots left", device->m_vendorId, device->m_productId); return false; } hidDevice->handle = GenerateHIDHandle(); device->AssignHID(hidDevice); deviceList.push_back(device); // do attach callbacks for (auto client : HIDClientList) { DoAttachCallbackAsync(client, device); } cemuLog_logDebug(LogType::Force, "nsyshid.AttachDevice(): device attached: {:04x}:{:04x}", device->m_vendorId, device->m_productId); return true; } void DetachDevice(const std::shared_ptr<Device>& device) { { std::lock_guard<std::recursive_mutex> lock(hidMutex); // remove from list auto it = std::find(deviceList.begin(), deviceList.end(), device); if (it == deviceList.end()) { cemuLog_logDebug(LogType::Force, "nsyshid.DetachDevice(): device not found: {:04x}:{:04x}", device->m_vendorId, device->m_productId); return; } deviceList.erase(it); // do detach callbacks for (auto client : HIDClientList) { DoDetachCallbackAsync(client, device); } ReleaseHID(device->m_hid); } device->Close(); cemuLog_logDebug(LogType::Force, "nsyshid.DetachDevice(): device removed: {:04x}:{:04x}", device->m_vendorId, device->m_productId); } std::shared_ptr<Device> FindDeviceById(uint16 vendorId, uint16 productId) { std::lock_guard<std::recursive_mutex> lock(hidMutex); for (const auto& device : deviceList) { if (device->m_vendorId == vendorId && device->m_productId == productId) { return device; } } return nullptr; } void export_HIDAddClient(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(hidClient, HIDClient_t, 0); ppcDefineParamMPTR(callbackFuncMPTR, 1); cemuLog_logDebug(LogType::Force, "nsyshid.HIDAddClient(0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]); hidClient->callbackFunc = callbackFuncMPTR; std::lock_guard<std::recursive_mutex> lock(hidMutex); AttachClientToList(hidClient); // do attach callbacks for (const auto& device : deviceList) { DoAttachCallback(hidClient, device); } osLib_returnFromFunction(hCPU, 0); } void export_HIDDelClient(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(hidClient, HIDClient_t, 0); cemuLog_logDebug(LogType::Force, "nsyshid.HIDDelClient(0x{:08x})", hCPU->gpr[3]); std::lock_guard<std::recursive_mutex> lock(hidMutex); DetachClientFromList(hidClient); // do detach callbacks for (const auto& device : deviceList) { DoDetachCallback(hidClient, device); } osLib_returnFromFunction(hCPU, 0); } void _debugPrintHex(const std::string prefix, const uint8* data, size_t size) { constexpr size_t BYTES_PER_LINE = 16; std::string out; for (size_t row_start = 0; row_start < size; row_start += BYTES_PER_LINE) { out += fmt::format("{:06x}: ", row_start); for (size_t i = 0; i < BYTES_PER_LINE; ++i) { if (row_start + i < size) { out += fmt::format("{:02x} ", data[row_start + i]); } else { out += " "; } } out += " "; for (size_t i = 0; i < BYTES_PER_LINE; ++i) { if (row_start + i < size) { char c = static_cast<char>(data[row_start + i]); out += std::isprint(c, std::locale::classic()) ? c : '.'; } } out += "\n"; } cemuLog_logDebug(LogType::Force, "[{}] Data: \n{}", prefix, out); } void DoHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode, MPTR buffer, sint32 length) { coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR); } void _hidGetDescriptorAsync(std::shared_ptr<Device> device, uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength)) { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, 0, 0, 0); } else { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, -1, 0, 0); } } void export_HIDGetDescriptor(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 ppcDefineParamU8(descType, 1); // r4 ppcDefineParamU8(descIndex, 2); // r5 ppcDefineParamU16(lang, 3); // r6 ppcDefineParamUStr(output, 4); // r7 ppcDefineParamU32(outputMaxLength, 5); // r8 ppcDefineParamMPTR(cbFuncMPTR, 6); // r9 ppcDefineParamMPTR(cbParamMPTR, 7); // r10 cemuLog_logDebug(LogType::Force, "nsyshid.HIDGetDescriptor(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:04x}, 0x{:x}, 0x{:08x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9], hCPU->gpr[10]); std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDGetDescriptor(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); return; } // issue request (synchronous or asynchronous) sint32 returnCode = 0; if (cbFuncMPTR == MPTR_NULL) { // synchronous returnCode = -1; if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength)) { returnCode = outputMaxLength; } } else { // asynchronous std::thread(&_hidGetDescriptorAsync, device, descType, descIndex, lang, output, outputMaxLength, cbFuncMPTR, cbParamMPTR) .detach(); returnCode = 0; } osLib_returnFromFunction(hCPU, returnCode); } void _hidSetIdleAsync(std::shared_ptr<Device> device, uint8 ifIndex, uint8 reportId, uint8 duration, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { if (device->SetIdle(ifIndex, reportId, duration)) { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, 0, 0, 0); } else { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, -1, 0, 0); } } void export_HIDSetIdle(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 ppcDefineParamU8(ifIndex, 1); // r4 ppcDefineParamU8(reportId, 2); // r5 ppcDefineParamU8(duration, 3); // r6 ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7 ppcDefineParamMPTR(callbackParamMPTR, 5); // r8 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]); std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDSetIdle(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); return; } // issue request (synchronous or asynchronous) sint32 returnCode = 0; if (callbackFuncMPTR == MPTR_NULL) { // synchronous returnCode = -1; if (device->SetIdle(ifIndex, reportId, duration)) { returnCode = 0; } } else { // asynchronous std::thread(&_hidSetIdleAsync, device, ifIndex, reportId, duration, callbackFuncMPTR, callbackParamMPTR) .detach(); returnCode = 0; } osLib_returnFromFunction(hCPU, returnCode); } void _hidSetProtocolAsync(std::shared_ptr<Device> device, uint8 ifIndex, uint8 protocol, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { if (device->SetProtocol(ifIndex, protocol)) { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, 0, 0, 0); } else { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, -1, 0, 0); } } void export_HIDSetProtocol(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 ppcDefineParamU8(ifIndex, 1); // r4 ppcDefineParamU8(protocol, 2); // r5 ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDSetProtocol(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); return; } // issue request (synchronous or asynchronous) sint32 returnCode = 0; if (callbackFuncMPTR == MPTR_NULL) { // synchronous returnCode = -1; if (device->SetProtocol(ifIndex, protocol)) { returnCode = 0; } } else { // asynchronous std::thread(&_hidSetProtocolAsync, device, ifIndex, protocol, callbackFuncMPTR, callbackParamMPTR) .detach(); returnCode = 0; } osLib_returnFromFunction(hCPU, returnCode); } // handler for async HIDSetReport transfers void _hidSetReportAsync(std::shared_ptr<Device> device, uint8 reportType, uint8 reportId, uint8* data, uint32 length, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin"); ReportMessage message(reportType, reportId, data, length); if (device->SetReport(&message)) { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, 0, memory_getVirtualOffsetFromPointer(data), length); } else { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, -1, memory_getVirtualOffsetFromPointer(data), length); } } // handler for synchronous HIDSetReport transfers sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8 reportType, uint8 reportId, uint8* data, uint32 length, coreinit::OSEvent* event) { _debugPrintHex("_hidSetReportSync Begin", data, length); sint32 returnCode = 0; ReportMessage message(reportType, reportId, data, length); if (device->SetReport(&message)) { returnCode = length; } cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode); coreinit::OSSignalEvent(event); return returnCode; } void export_HIDSetReport(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 ppcDefineParamU8(reportType, 1); // r4 ppcDefineParamU8(reportId, 2); // r5 ppcDefineParamUStr(data, 3); // r6 ppcDefineParamU32(dataLength, 4); // r7 ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8 ppcDefineParamMPTR(callbackParamMPTR, 6); // r9 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9]); _debugPrintHex("HIDSetReport", data, dataLength); #ifdef CEMU_DEBUG_ASSERT if (reportType != 2 || reportId != 0) assert_dbg(); #endif std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDSetReport(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); return; } // issue request (synchronous or asynchronous) sint32 returnCode = 0; if (callbackFuncMPTR == MPTR_NULL) { // synchronous StackAllocator<coreinit::OSEvent> event; coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, device, reportType, reportId, data, dataLength, &event); coreinit::OSWaitEvent(&event); returnCode = res.get(); } else { // asynchronous std::thread(&_hidSetReportAsync, device, reportType, reportId, data, dataLength, callbackFuncMPTR, callbackParamMPTR) .detach(); returnCode = 0; } osLib_returnFromFunction(hCPU, returnCode); } sint32 _hidReadInternalSync(std::shared_ptr<Device> device, uint8* data, sint32 maxLength) { cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", maxLength); if (!device->IsOpened()) { cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): cannot read from a non-opened device"); return -1; } memset(data, 0, maxLength); ReadMessage message(data, maxLength, 0); Device::ReadResult readResult = device->Read(&message); switch (readResult) { case Device::ReadResult::Success: { cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read {} of {} bytes", message.bytesRead, maxLength); return message.bytesRead; } break; case Device::ReadResult::Error: { cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read error"); return -1; } break; case Device::ReadResult::ErrorTimeout: { cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read error: timeout"); return -108; } break; } cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read error: unknown"); return -1; } void _hidReadAsync(std::shared_ptr<Device> device, uint8* data, sint32 maxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { sint32 returnCode = _hidReadInternalSync(device, data, maxLength); sint32 errorCode = 0; if (returnCode < 0) errorCode = returnCode; // don't return number of bytes in error code DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, errorCode, memory_getVirtualOffsetFromPointer(data), (returnCode > 0) ? returnCode : 0); } sint32 _hidReadSync(std::shared_ptr<Device> device, uint8* data, sint32 maxLength, coreinit::OSEvent* event) { sint32 returnCode = _hidReadInternalSync(device, data, maxLength); coreinit::OSSignalEvent(event); return returnCode; } void export_HIDRead(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 ppcDefineParamUStr(data, 1); // r4 ppcDefineParamU32(maxLength, 2); // r5 ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDRead(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); return; } sint32 returnCode = 0; if (callbackFuncMPTR != MPTR_NULL) { // asynchronous transfer std::thread(&_hidReadAsync, device, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); returnCode = 0; } else { // synchronous transfer StackAllocator<coreinit::OSEvent> event; coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); std::future<sint32> res = std::async(std::launch::async, &_hidReadSync, device, data, maxLength, &event); coreinit::OSWaitEvent(&event); returnCode = res.get(); } osLib_returnFromFunction(hCPU, returnCode); } sint32 _hidWriteInternalSync(std::shared_ptr<Device> device, uint8* data, sint32 maxLength) { cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", maxLength); if (!device->IsOpened()) { cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): cannot write to a non-opened device"); return -1; } WriteMessage message(data, maxLength, 0); Device::WriteResult writeResult = device->Write(&message); switch (writeResult) { case Device::WriteResult::Success: { cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", message.bytesWritten, maxLength); return message.bytesWritten; } break; case Device::WriteResult::Error: { cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): write error"); return -1; } break; case Device::WriteResult::ErrorTimeout: { cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): write error: timeout"); return -108; } break; } cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): write error: unknown"); return -1; } void _hidWriteAsync(std::shared_ptr<Device> device, uint8* data, sint32 maxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { sint32 returnCode = _hidWriteInternalSync(device, data, maxLength); sint32 errorCode = 0; if (returnCode < 0) errorCode = returnCode; // don't return number of bytes in error code DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, errorCode, memory_getVirtualOffsetFromPointer(data), (returnCode > 0) ? returnCode : 0); } sint32 _hidWriteSync(std::shared_ptr<Device> device, uint8* data, sint32 maxLength, coreinit::OSEvent* event) { sint32 returnCode = _hidWriteInternalSync(device, data, maxLength); coreinit::OSSignalEvent(event); return returnCode; } void export_HIDWrite(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 ppcDefineParamUStr(data, 1); // r4 ppcDefineParamU32(maxLength, 2); // r5 ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDWrite(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); return; } sint32 returnCode = 0; if (callbackFuncMPTR != MPTR_NULL) { // asynchronous transfer std::thread(&_hidWriteAsync, device, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); returnCode = 0; } else { // synchronous transfer StackAllocator<coreinit::OSEvent> event; coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); std::future<sint32> res = std::async(std::launch::async, &_hidWriteSync, device, data, maxLength, &event); coreinit::OSWaitEvent(&event); returnCode = res.get(); } osLib_returnFromFunction(hCPU, returnCode); } void export_HIDDecodeError(PPCInterpreter_t* hCPU) { ppcDefineParamU32(errorCode, 0); ppcDefineParamTypePtr(ukn0, uint32be, 1); ppcDefineParamTypePtr(ukn1, uint32be, 2); cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); // todo *ukn0 = 0x3FF; *ukn1 = (uint32)-0x7FFF; osLib_returnFromFunction(hCPU, 0); } void Backend::DetachAllDevices() { std::lock_guard<std::recursive_mutex> lock(this->m_devicesMutex); if (m_isAttached) { for (const auto& device : this->m_devices) { nsyshid::DetachDevice(device); } this->m_devices.clear(); } } bool Backend::AttachDevice(const std::shared_ptr<Device>& device) { std::lock_guard<std::recursive_mutex> lock(this->m_devicesMutex); if (m_isAttached && nsyshid::AttachDevice(device)) { this->m_devices.push_back(device); return true; } return false; } void Backend::DetachDevice(const std::shared_ptr<Device>& device) { std::lock_guard<std::recursive_mutex> lock(this->m_devicesMutex); if (m_isAttached) { nsyshid::DetachDevice(device); this->m_devices.remove(device); } } std::shared_ptr<Device> Backend::FindDevice(std::function<bool(const std::shared_ptr<Device>&)> isWantedDevice) { std::lock_guard<std::recursive_mutex> lock(this->m_devicesMutex); auto it = std::find_if(this->m_devices.begin(), this->m_devices.end(), std::move(isWantedDevice)); if (it != this->m_devices.end()) { return *it; } return nullptr; } std::shared_ptr<Device> Backend::FindDeviceById(uint16 vendorId, uint16 productId) { return nsyshid::FindDeviceById(vendorId, productId); } bool Backend::IsDeviceWhitelisted(uint16 vendorId, uint16 productId) { return Whitelist::GetInstance().IsDeviceWhitelisted(vendorId, productId); } Backend::Backend() : m_isAttached(false) { } void Backend::OnAttach() { std::lock_guard<std::recursive_mutex> lock(this->m_devicesMutex); m_isAttached = true; AttachVisibleDevices(); } void Backend::OnDetach() { std::lock_guard<std::recursive_mutex> lock(this->m_devicesMutex); DetachAllDevices(); m_isAttached = false; } bool Backend::IsBackendAttached() { std::lock_guard<std::recursive_mutex> lock(this->m_devicesMutex); return m_isAttached; } Device::Device(uint16 vendorId, uint16 productId, uint8 interfaceIndex, uint8 interfaceSubClass, uint8 protocol) : m_hid(nullptr), m_vendorId(vendorId), m_productId(productId), m_interfaceIndex(interfaceIndex), m_interfaceSubClass(interfaceSubClass), m_protocol(protocol), m_maxPacketSizeRX(0x20), m_maxPacketSizeTX(0x20) { } void Device::AssignHID(HID_t* hid) { if (hid != nullptr) { hid->vendorId = this->m_vendorId; hid->productId = this->m_productId; hid->ifIndex = this->m_interfaceIndex; hid->subClass = this->m_interfaceSubClass; hid->protocol = this->m_protocol; hid->ukn04 = 0x11223344; hid->paddingGuessed0F = 0; hid->maxPacketSizeRX = this->m_maxPacketSizeRX; hid->maxPacketSizeTX = this->m_maxPacketSizeTX; } this->m_hid = hid; } class : public COSModule { public: std::string_view GetName() override { return "nsyshid"; } void RPLMapped() override { osLib_addFunction("nsyshid", "HIDAddClient", export_HIDAddClient); osLib_addFunction("nsyshid", "HIDDelClient", export_HIDDelClient); osLib_addFunction("nsyshid", "HIDGetDescriptor", export_HIDGetDescriptor); osLib_addFunction("nsyshid", "HIDSetIdle", export_HIDSetIdle); osLib_addFunction("nsyshid", "HIDSetProtocol", export_HIDSetProtocol); osLib_addFunction("nsyshid", "HIDSetReport", export_HIDSetReport); osLib_addFunction("nsyshid", "HIDRead", export_HIDRead); osLib_addFunction("nsyshid", "HIDWrite", export_HIDWrite); osLib_addFunction("nsyshid", "HIDDecodeError", export_HIDDecodeError); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { // initialise whitelist Whitelist::GetInstance(); AttachDefaultBackends(); } else if (reason == coreinit::RplEntryReason::Unloaded) { } } }s_COSnsyshidModule; COSModule* GetModule() { return &s_COSnsyshidModule; } } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyshid/nsyshid.h ================================================ #pragma once #include "Cafe/OS/RPL/COSModule.h" namespace nsyshid { class Backend; void AttachBackend(const std::shared_ptr<Backend>& backend); void DetachBackend(const std::shared_ptr<Backend>& backend); COSModule* GetModule(); } // namespace nsyshid ================================================ FILE: src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/nn_common.h" namespace nsyskbd { bool IsValidChannel(uint32 channel) { return channel >= 0 && channel < 4; } uint32 KBDGetChannelStatus(uint32 channel, uint32be* status) { static bool loggedError = false; if (loggedError == false) { cemuLog_logDebug(LogType::Force, "KBDGetChannelStatus() placeholder"); loggedError = true; } *status = 1; // disconnected return 0; } #pragma pack(push, 1) struct KeyState { uint8be channel; uint8be ukn1; uint8be _padding[2]; uint32be ukn4; uint32be ukn8; uint16be uknC; }; #pragma pack(pop) static_assert(sizeof(KeyState) == 0xE); // actual size might be padded to 0x10? uint32 KBDGetKey(uint32 channel, KeyState* keyState) { // used by MSX VC if(!IsValidChannel(channel) || !keyState) { cemuLog_log(LogType::APIErrors, "KBDGetKey(): Invalid parameter"); return 0; } keyState->channel = channel; keyState->ukn1 = 0; keyState->ukn4 = 0; keyState->ukn8 = 0; keyState->uknC = 0; return 0; } class : public COSModule { public: std::string_view GetName() override { return "nsyskbd"; } void RPLMapped() override { cafeExportRegister("nsyskbd", KBDGetChannelStatus, LogType::Placeholder); cafeExportRegister("nsyskbd", KBDGetKey, LogType::Placeholder); }; void RPLUnmapped() override { } }s_COSnsyskbdModule; COSModule* GetModule() { return &s_COSnsyskbdModule; } } ================================================ FILE: src/Cafe/OS/libs/nsyskbd/nsyskbd.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace nsyskbd { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/nsysnet/nsysnet.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "nsysnet.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit_GHS.h" #include "Common/socket.h" #if BOOST_OS_UNIX #define WSAEWOULDBLOCK EWOULDBLOCK #define WSAEINPROGRESS EINPROGRESS #define WSAESHUTDOWN ESHUTDOWN #define WSAECONNABORTED ECONNABORTED #define WSAHOST_NOT_FOUND EAI_NONAME #define WSAENOTCONN ENOTCONN #define GETLASTERR errno #endif // BOOST_OS_UNIX #if BOOST_OS_WINDOWS #define GETLASTERR WSAGetLastError() #endif //BOOST_OS_WINDOWS #define WU_AF_INET 2 #define WU_SOCK_STREAM 1 #define WU_SOCK_DGRAM 2 #define WU_IPPROTO_IP 0 #define WU_IPPROTO_TCP 6 #define WU_IPPROTO_UDP 17 #define WU_SO_REUSEADDR 0x0004 #define WU_SO_KEEPALIVE 0x0008 #define WU_SO_DONTROUTE 0x0010 #define WU_SO_BROADCAST 0x0020 #define WU_SO_LINGER 0x0080 #define WU_SO_OOBINLINE 0x0100 #define WU_SO_TCPSACK 0x0200 #define WU_SO_WINSCALE 0x0400 #define WU_SO_SNDBUF 0x1001 #define WU_SO_RCVBUF 0x1002 #define WU_SO_SNDLOWAT 0x1003 #define WU_SO_RCVLOWAT 0x1004 #define WU_SO_LASTERROR 0x1007 #define WU_SO_TYPE 0x1008 #define WU_SO_HOPCNT 0x1009 #define WU_SO_MAXMSG 0x1010 #define WU_SO_RXDATA 0x1011 #define WU_SO_TXDATA 0x1012 #define WU_SO_MYADDR 0x1013 #define WU_SO_NBIO 0x1014 #define WU_SO_BIO 0x1015 #define WU_SO_NONBLOCK 0x1016 #define WU_SO_UNKNOWN1019 0x1019 // tcp related #define WU_SO_UNKNOWN101A 0x101A // tcp related #define WU_SO_UNKNOWN101B 0x101B // tcp related #define WU_SO_NOSLOWSTART 0x4000 #define WU_SO_RUSRBUF 0x10000 #define WU_TCP_ACKDELAYTIME 0x2001 #define WU_TCP_NOACKDELAY 0x2002 #define WU_TCP_MAXSEG 0x2003 #define WU_TCP_NODELAY 0x2004 #define WU_TCP_UNKNOWN 0x2005 // amount of mss received before sending an ack #define WU_IP_TOS 3 #define WU_IP_TTL 4 #define WU_IP_MULTICAST_IF 9 #define WU_IP_MULTICAST_TTL 10 #define WU_IP_MULTICAST_LOOP 11 #define WU_IP_ADD_MEMBERSHIP 12 #define WU_IP_DROP_MEMBERSHIP 13 #define WU_IP_UNKNOWN 14 #define WU_SOL_SOCKET -1 // this constant differs from Win32 socket API #define WU_MSG_PEEK 0x02 #define WU_MSG_DONTWAIT 0x20 // error codes #define WU_SO_SUCCESS 0x0000 #define WU_SO_EWOULDBLOCK 0x0006 #define WU_SO_ECONNRESET 0x0008 #define WU_SO_ENOTCONN 0x0009 #define WU_SO_EINVAL 0x000B #define WU_SO_EINPROGRESS 0x0016 #define WU_SO_EAFNOSUPPORT 0x0021 #define WU_SO_ESHUTDOWN 0x000F typedef signed int WUSOCKET; bool sockLibReady = false; void nsysnetExport_socket_lib_init(PPCInterpreter_t* hCPU) { sockLibReady = true; #if BOOST_OS_WINDOWS WSADATA wsa; WSAStartup(MAKEWORD(2, 2), &wsa); #endif // BOOST_OS_WINDOWS osLib_returnFromFunction(hCPU, 0); // 0 -> Success } void nsysnetExport_socket_lib_finish(PPCInterpreter_t* hCPU) { sockLibReady = false; #if BOOST_OS_WINDOWS WSACleanup(); #endif // BOOST_OS_WINDOWS osLib_returnFromFunction(hCPU, 0); // 0 -> Success } void _setSockError(sint32 errCode) { coreinit::__gh_set_errno(errCode); } sint32 _getSockError() { return coreinit::__gh_get_errno(); } // error translation modes for _translateError #define _ERROR_MODE_DEFAULT 0 #define _ERROR_MODE_CONNECT 1 #define _ERROR_MODE_ACCEPT 2 sint32 _translateError(sint32 returnCode, sint32 wsaError, sint32 mode = _ERROR_MODE_DEFAULT) { if (mode == _ERROR_MODE_ACCEPT) { // accept mode if (returnCode >= 0) { _setSockError(WU_SO_SUCCESS); return returnCode; } } else { // any other mode if (returnCode == 0) { _setSockError(WU_SO_SUCCESS); return 0; } } // handle WSA error switch (wsaError) { case 0: _setSockError(WU_SO_SUCCESS); break; case WSAEWOULDBLOCK: if( mode == _ERROR_MODE_CONNECT ) _setSockError(WU_SO_EINPROGRESS); else _setSockError(WU_SO_EWOULDBLOCK); break; case WSAEINPROGRESS: _setSockError(WU_SO_EINPROGRESS); break; case WSAECONNABORTED: debug_printf("WSAECONNABORTED\n"); #ifdef CEMU_DEBUG_ASSERT assert_dbg(); #endif break; case WSAESHUTDOWN: _setSockError(WU_SO_ESHUTDOWN); break; case WSAENOTCONN: _setSockError(WU_SO_ENOTCONN); break; default: cemuLog_logDebug(LogType::Force, "Unhandled wsaError {}", wsaError); _setSockError(99999); // unhandled error } return -1; } void nsysnetExport_socketlasterr(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Socket, "socketlasterr() -> {}", _getSockError()); osLib_returnFromFunction(hCPU, _getSockError()); } typedef struct { uint32 handle; bool isShutdownRecv; bool isShutdownSend; // socket creation info sint32 family; sint32 type; sint32 protocol; // host side info SOCKET s; // socket options bool isNonBlocking; }virtualSocket_t; typedef struct { uint32 wu_s_addr; }wu_in_addr; struct wu_sockaddr { uint16 sa_family; uint8 sa_data[14]; // IPv4 }; void sockaddr_guest2host(wu_sockaddr* input, sockaddr* output) { output->sa_family = _swapEndianU16(input->sa_family); memcpy(output->sa_data, input->sa_data, 14); } void sockaddr_host2guest(sockaddr* input, wu_sockaddr* output) { output->sa_family = _swapEndianU16(input->sa_family); memcpy(output->sa_data, input->sa_data, 14); } struct wu_addrinfo { sint32 ai_flags; sint32 ai_family; sint32 ai_socktype; sint32 ai_protocol; sint32 ai_addrlen; MPTR ai_canonname; MPTR ai_addr; MPTR ai_next; }; #define WU_SOCKET_LIMIT (32) // only 32 socket handles are supported per running process virtualSocket_t* virtualSocketTable[WU_SOCKET_LIMIT] = { 0 }; sint32 _getFreeSocketHandle() { for (sint32 i = 0; i < WU_SOCKET_LIMIT; i++) { if (virtualSocketTable[i] == NULL) return i + 1; } return 0; } #if BOOST_OS_WINDOWS #define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) #endif // BOOST_OS_WINDOWS WUSOCKET nsysnet_createVirtualSocket(sint32 family, sint32 type, sint32 protocol) { sint32 s = _getFreeSocketHandle(); if (s == 0) { cemuLog_logDebug(LogType::Force, "Ran out of socket handles"); cemu_assert(false); } virtualSocket_t* vs = (virtualSocket_t*)malloc(sizeof(virtualSocket_t)); memset(vs, 0, sizeof(virtualSocket_t)); vs->family = family; vs->type = type; vs->protocol = protocol; vs->handle = s; virtualSocketTable[s - 1] = vs; // init host socket vs->s = socket(family, type, protocol); #if BOOST_OS_WINDOWS // disable reporting of PORT_UNREACHABLE for UDP sockets if (protocol == IPPROTO_UDP) { BOOL bNewBehavior = FALSE; DWORD dwBytesReturned = 0; WSAIoctl(vs->s, SIO_UDP_CONNRESET, &bNewBehavior, sizeof bNewBehavior, NULL, 0, &dwBytesReturned, NULL, NULL); } #endif // BOOST_OS_WINDOWS return vs->handle; } WUSOCKET nsysnet_createVirtualSocketFromExistingSocket(SOCKET existingSocket) { cemuLog_logDebug(LogType::Force, "nsysnet_createVirtualSocketFromExistingSocket - incomplete"); sint32 s = _getFreeSocketHandle(); if (s == 0) { cemuLog_logDebug(LogType::Force, "Ran out of socket handles"); cemu_assert(false); } virtualSocket_t* vs = (virtualSocket_t*)malloc(sizeof(virtualSocket_t)); memset(vs, 0, sizeof(virtualSocket_t)); #if BOOST_OS_WINDOWS // SO_TYPE -> type // SO_BSP_STATE -> protocol + other info // SO_PROTOCOL_INFO -> protocol + type? WSAPROTOCOL_INFO protocolInfo = { 0 }; int optLen = sizeof(protocolInfo); getsockopt(existingSocket, SOL_SOCKET, SO_PROTOCOL_INFO, (char*)&protocolInfo, &optLen); // todo - translate protocolInfo vs->family = protocolInfo.iAddressFamily; vs->type = protocolInfo.iSocketType; vs->protocol = protocolInfo.iSocketType; #else { int type; socklen_t optlen; getsockopt(vs->s, SOL_SOCKET, SO_TYPE, &type, &optlen); vs->type = type; vs->protocol = type; } { sockaddr saddr; socklen_t len; getsockname(vs->s, &saddr, &len); vs->family = saddr.sa_family; } #endif vs->handle = s; virtualSocketTable[s - 1] = vs; vs->s = existingSocket; return vs->handle; } void nsysnet_notifyCloseSharedSocket(SOCKET existingSocket) { for (sint32 i = 0; i < WU_SOCKET_LIMIT; i++) { if (virtualSocketTable[i] && virtualSocketTable[i]->s == existingSocket) { // remove entry free(virtualSocketTable[i]); virtualSocketTable[i] = nullptr; return; } } } virtualSocket_t* nsysnet_getVirtualSocketObject(WUSOCKET s) { uint8 handleType = 0; s--; if (s < 0 || s >= WU_SOCKET_LIMIT) return NULL; return virtualSocketTable[s]; } sint32 nsysnet_getVirtualSocketHandleFromHostHandle(SOCKET s) { for (sint32 i = 0; i < WU_SOCKET_LIMIT; i++) { if (virtualSocketTable[i] && virtualSocketTable[i]->s == s) return i + 1; } return -1; } void nsysnetExport_socket(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "socket({},{},{})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); ppcDefineParamS32(family, 0); ppcDefineParamS32(type, 1); ppcDefineParamS32(protocol, 2); if (sockLibReady == false) { _setSockError(0x2B); osLib_returnFromFunction(hCPU, -1); return; } // below here Ioctl code should start // check family param if (family != WU_AF_INET) { cemuLog_logDebug(LogType::Force, "socket(): Unsupported family"); // todo - error code osLib_returnFromFunction(hCPU, -1); return; } // check type param if (type != WU_SOCK_STREAM && type != WU_SOCK_DGRAM) { cemuLog_logDebug(LogType::Force, "socket(): Unsupported family"); // todo - error code osLib_returnFromFunction(hCPU, -1); return; } if (protocol != WU_IPPROTO_TCP && protocol != WU_IPPROTO_UDP && protocol != WU_IPPROTO_IP) { cemuLog_logDebug(LogType::Force, "socket(): Unsupported protocol"); // todo - error code osLib_returnFromFunction(hCPU, -1); return; } WUSOCKET s = nsysnet_createVirtualSocket(family, type, protocol); cemuLog_log(LogType::Socket, "Created socket handle {}", s); osLib_returnFromFunction(hCPU, s); } void nsysnetExport_mw_socket(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "mw_socket"); nsysnetExport_socket(hCPU); } void nsysnetExport_shutdown(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "shutdown({},{})", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamS32(s, 0); ppcDefineParamS32(how, 1); sint32 r = 0; virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); if (vs == NULL) { assert_dbg(); } else { r = shutdown(vs->s, how); if (how == 0) { // shutdown recv vs->isShutdownRecv = true; } else if (how == 1) { // shutdown send vs->isShutdownSend = true; } else if (how == 2) { // shutdown recv & send vs->isShutdownRecv = true; vs->isShutdownSend = true; } else assert_dbg(); } _setSockError(0); // todo osLib_returnFromFunction(hCPU, r); } void nsysnetExport_socketclose(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "socketclose({})", hCPU->gpr[3]); ppcDefineParamS32(s, 0); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); if (vs) { closesocket(vs->s); free(vs); virtualSocketTable[s - 1] = NULL; } osLib_returnFromFunction(hCPU, 0); } sint32 _socket_nonblock(SOCKET s, u_long mode) { #if BOOST_OS_WINDOWS return ioctlsocket(s, FIONBIO, &mode); #else int flags = fcntl(s, F_GETFL); if(mode) flags |= O_NONBLOCK; else flags &= ~O_NONBLOCK; return fcntl(s, F_SETFL, flags); #endif } void nsysnetExport_setsockopt(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "setsockopt({},0x{:x},0x{:05x},0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); ppcDefineParamS32(s, 0); ppcDefineParamS32(level, 1); ppcDefineParamS32(optname, 2); ppcDefineParamStr(optval, 3); ppcDefineParamS32(optlen, 4); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); if (!vs) { cemu_assert_suspicious(); } else { // translate level to host API sint32 hostLevel; if (level == WU_SOL_SOCKET) { hostLevel = SOL_SOCKET; // handle op if (optname == WU_SO_REUSEADDR) { if (optlen != 4) cemu_assert_suspicious(); sint32 optvalLE = _swapEndianU32(*(uint32*)optval); sint32 r = setsockopt(vs->s, hostLevel, SO_REUSEADDR, (char*)&optvalLE, 4); if (r != 0) cemu_assert_suspicious(); } else if (optname == WU_SO_NBIO || optname == WU_SO_BIO) { // similar to WU_SO_NONBLOCK but always sets blocking (_BIO) or non-blocking (_NBIO) mode regardless of option value if (optlen == 4) { sint32 optvalLE = _swapEndianU32(*(uint32*)optval); } else if (optlen == 0) { // no opt needed } else cemu_assert_suspicious(); bool setNonBlocking = optname == WU_SO_NBIO; u_long mode = setNonBlocking ? 1 : 0; _socket_nonblock(vs->s, mode); vs->isNonBlocking = setNonBlocking; } else if (optname == WU_SO_NONBLOCK) { if (optlen != 4) assert_dbg(); sint32 optvalLE = _swapEndianU32(*(uint32*)optval); u_long mode = optvalLE; // 1 -> enable, 0 -> disable _socket_nonblock(vs->s, mode); vs->isNonBlocking = mode != 0; } else if (optname == WU_SO_KEEPALIVE) { cemuLog_logDebug(LogType::Socket, "todo: setsockopt() for WU_SO_KEEPALIVE"); } else if (optname == WU_SO_WINSCALE) { cemuLog_logDebug(LogType::Socket, "todo: setsockopt() for WU_SO_WINSCALE"); } else if (optname == WU_SO_RCVBUF) { cemuLog_log(LogType::Socket, "Set receive buffer size to 0x{:08x}", _swapEndianU32(*(uint32*)optval)); if (optlen != 4) assert_dbg(); sint32 optvalLE = _swapEndianU32(*(uint32*)optval); u_long mode = optvalLE; if (setsockopt(vs->s, SOL_SOCKET, SO_RCVBUF, (const char*)&mode, sizeof(u_long)) != 0) assert_dbg(); } else if (optname == WU_SO_SNDBUF) { cemuLog_log(LogType::Socket, "Set send buffer size to 0x{:08x}", _swapEndianU32(*(uint32*)optval)); if (optlen != 4) assert_dbg(); sint32 optvalLE = _swapEndianU32(*(uint32*)optval); u_long mode = optvalLE; if (setsockopt(vs->s, SOL_SOCKET, SO_SNDBUF, (const char*)&mode, sizeof(u_long)) != 0) assert_dbg(); } else { cemuLog_logDebug(LogType::Force, "setsockopt(WU_SOL_SOCKET): Unsupported optname 0x{:08x}", optname); } } else if (level == WU_IPPROTO_TCP) { hostLevel = IPPROTO_TCP; if (optname == WU_TCP_NODELAY) { if (optlen != 4) assert_dbg(); sint32 optvalLE = _swapEndianU32(*(uint32*)optval); u_long mode = optvalLE; // 1 -> enable, 0 -> disable if (setsockopt(vs->s, IPPROTO_TCP, TCP_NODELAY, (const char*)&mode, sizeof(u_long)) != 0) assert_dbg(); } else { cemuLog_logDebug(LogType::Force, "setsockopt(WU_IPPROTO_TCP): Unsupported optname 0x{:08x}", optname); } } else if (level == WU_IPPROTO_IP) { hostLevel = IPPROTO_IP; if (optname == WU_IP_MULTICAST_IF || optname == WU_IP_MULTICAST_TTL || optname == WU_IP_MULTICAST_LOOP || optname == WU_IP_ADD_MEMBERSHIP || optname == WU_IP_DROP_MEMBERSHIP) { cemuLog_logDebug(LogType::Socket, "todo: setsockopt() for multicast"); } else if(optname == WU_IP_TTL || optname == WU_IP_TOS) { cemuLog_logDebug(LogType::Force, "setsockopt(WU_IPPROTO_IP): Unsupported optname 0x{:08x}", optname); } else assert_dbg(); } else assert_dbg(); } osLib_returnFromFunction(hCPU, 0); } void nsysnetExport_getsockopt(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "getsockopt({},0x{:x},0x{:05x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); ppcDefineParamS32(s, 0); ppcDefineParamS32(level, 1); ppcDefineParamS32(optname, 2); ppcDefineParamStr(optval, 3); ppcDefineParamMPTR(optlenMPTR, 4); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); if (vs == NULL) { cemu_assert_debug(false); // todo return; } sint32 r = 0; // translate level to host API sint32 hostLevel; if (level == WU_SOL_SOCKET) { hostLevel = SOL_SOCKET; // handle op if (optname == WU_SO_LASTERROR) { int optvalLE = 0; socklen_t optlenLE = 4; r = getsockopt(vs->s, hostLevel, SO_ERROR, (char*)&optvalLE, &optlenLE); r = _translateError(r, GETLASTERR); if (memory_readU32(optlenMPTR) != 4) assert_dbg(); memory_writeU32(optlenMPTR, 4); if (optvalLE != 0) assert_dbg(); // todo -> Translate error code/status *(uint32*)optval = _swapEndianU32(optvalLE); // YouTube app uses this to check if the connection attempt was successful after non-blocking connect() } else if (optname == WU_SO_RCVBUF) { int optvalLE = 0; socklen_t optlenLE = 4; r = getsockopt(vs->s, hostLevel, SO_RCVBUF, (char*)&optvalLE, &optlenLE); r = _translateError(r, GETLASTERR); if (memory_readU32(optlenMPTR) != 4) assert_dbg(); memory_writeU32(optlenMPTR, 4); *(uint32*)optval = _swapEndianU32(optvalLE); // used by Amazon Video app when a video starts playing } else if (optname == WU_SO_SNDBUF) { int optvalLE = 0; socklen_t optlenLE = 4; r = getsockopt(vs->s, hostLevel, SO_SNDBUF, (char*)&optvalLE, &optlenLE); r = _translateError(r, GETLASTERR); if (memory_readU32(optlenMPTR) != 4) assert_dbg(); memory_writeU32(optlenMPTR, 4); *(uint32*)optval = _swapEndianU32(optvalLE); // used by Lost Reavers after some loading screens } else if (optname == WU_SO_TYPE) { if (memory_readU32(optlenMPTR) != 4) assert_dbg(); int optvalLE = 0; socklen_t optlenLE = 4; memory_writeU32(optlenMPTR, 4); *(uint32*)optval = _swapEndianU32(vs->type); r = WU_SO_SUCCESS; } else if (optname == WU_SO_NONBLOCK) { if (memory_readU32(optlenMPTR) != 4) assert_dbg(); int optvalLE = 0; socklen_t optlenLE = 4; memory_writeU32(optlenMPTR, 4); *(uint32*)optval = _swapEndianU32(vs->isNonBlocking ? 1 : 0); r = WU_SO_SUCCESS; } else { cemuLog_logDebug(LogType::Force, "getsockopt(WU_SOL_SOCKET): Unsupported optname 0x{:08x}", optname); } } else { cemuLog_logDebug(LogType::Force, "getsockopt(): Unsupported level 0x{:08x}", level); } osLib_returnFromFunction(hCPU, r); } void nsysnetExport_inet_aton(PPCInterpreter_t* hCPU) { ppcDefineParamStr(ip, 0); ppcDefineParamStructPtr(addr, wu_in_addr, 1); cemuLog_log(LogType::Socket, "inet_aton(\"{}\",0x{:08x})", ip, hCPU->gpr[4]); // _parse_ipad -> todo sint32 d0, d1, d2, d3; if (sscanf(ip, "%d.%d.%d.%d", &d0, &d1, &d2, &d3) != 4) { cemu_assert_debug(false); osLib_returnFromFunction(hCPU, 0); // todo - return correct error code return; } if (d0 < 0 || d0 > 255 || d1 < 0 || d1 > 255 || d2 < 0 || d2 > 255 || d3 < 0 || d3 > 255) { cemu_assert_debug(false); osLib_returnFromFunction(hCPU, 0); return; } addr->wu_s_addr = _swapEndianU32((d0 << 24) | (d1 << 16) | (d2 << 8) | (d3 << 0)); osLib_returnFromFunction(hCPU, 1); // 0 -> error, 1 -> success } void nsysnetExport_inet_pton(PPCInterpreter_t* hCPU) { ppcDefineParamS32(af, 0); ppcDefineParamStr(ip, 1); ppcDefineParamStructPtr(addr, wu_in_addr, 2); if (af != 2) { cemuLog_log(LogType::Force, "inet_pton() only supports AF_INET"); osLib_returnFromFunction(hCPU, 0); return; } sint32 d0, d1, d2, d3; bool invalidIp = false; if (sscanf(ip, "%d.%d.%d.%d", &d0, &d1, &d2, &d3) != 4) invalidIp = true; if (d0 < 0 || d0 > 255) invalidIp = true; if (d1 < 0 || d1 > 255) invalidIp = true; if (d2 < 0 || d2 > 255) invalidIp = true; if (d3 < 0 || d3 > 255) invalidIp = true; if (invalidIp) { cemuLog_log(LogType::Socket, "inet_pton({}, \"{}\", 0x{:08x}) -> Invalid ip", af, ip, hCPU->gpr[5]); _setSockError(WU_SO_EAFNOSUPPORT); osLib_returnFromFunction(hCPU, 0); // 0 -> invalid address return; } addr->wu_s_addr = _swapEndianU32((d0 << 24) | (d1 << 16) | (d2 << 8) | (d3 << 0)); cemuLog_log(LogType::Socket, "inet_pton({}, \"{}\", 0x{:08x}) -> Ok", af, ip, hCPU->gpr[5]); osLib_returnFromFunction(hCPU, 1); // 1 -> success } namespace nsysnet { const char* inet_ntop(sint32 af, const void* src, char* dst, uint32 size) { if( af != WU_AF_INET) { // set error _setSockError(WU_SO_EAFNOSUPPORT); return nullptr; } const uint8* ip = (const uint8*)src; char buf[32]; sprintf(buf, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); size_t bufLen = strlen(buf); if( (bufLen+1) > size ) { // set error _setSockError(WU_SO_EAFNOSUPPORT); return nullptr; } strcpy(dst, buf); cemuLog_log(LogType::Socket, "inet_ntop -> {}", buf); return dst; } } MEMPTR<char> _ntoa_tempString = nullptr; void nsysnetExport_inet_ntoa(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(addr, wu_in_addr, 0); cemuLog_log(LogType::Socket, "inet_ntoa(0x{:08x})", hCPU->gpr[3]); if (_ntoa_tempString == nullptr) _ntoa_tempString = (char*)memory_getPointerFromVirtualOffset(OSAllocFromSystem(64, 4)); sprintf(_ntoa_tempString.GetPtr(), "%d.%d.%d.%d", ((uint8*)addr)[0], ((uint8*)addr)[1], ((uint8*)addr)[2], ((uint8*)addr)[3]); osLib_returnFromFunction(hCPU, _ntoa_tempString.GetMPTR()); } void nsysnetExport_htons(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "htons(0x{:04x})", hCPU->gpr[3]); ppcDefineParamU32(v, 0); osLib_returnFromFunction(hCPU, v); // return value as-is } void nsysnetExport_htonl(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "htonl(0x{:08x})", hCPU->gpr[3]); ppcDefineParamU32(v, 0); osLib_returnFromFunction(hCPU, v); // return value as-is } void nsysnetExport_ntohs(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "ntohs(0x{:04x})", hCPU->gpr[3]); ppcDefineParamU32(v, 0); osLib_returnFromFunction(hCPU, v); // return value as-is } void nsysnetExport_ntohl(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "ntohl(0x{:08x})", hCPU->gpr[3]); ppcDefineParamU32(v, 0); osLib_returnFromFunction(hCPU, v); // return value as-is } void nsysnetExport_bind(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "bind({},0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); ppcDefineParamS32(s, 0); ppcDefineParamStructPtr(addr, struct wu_sockaddr, 1); ppcDefineParamS32(len, 2); if (len != sizeof(struct wu_sockaddr)) assert_dbg(); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); sint32 r = 0; if (vs == NULL) { assert_dbg(); } else { if (sizeof(sockaddr) != 16) assert_dbg(); sockaddr hostAddr; hostAddr.sa_family = _swapEndianU16(addr->sa_family); memcpy(hostAddr.sa_data, addr->sa_data, 14); sint32 hr = bind(vs->s, &hostAddr, sizeof(sockaddr)); r = _translateError(hr, GETLASTERR); cemuLog_log(LogType::Socket, "bind address: {}.{}.{}.{}:{} result: {}", addr->sa_data[2], addr->sa_data[3], addr->sa_data[4], addr->sa_data[5], _swapEndianU16(*(uint16*)addr->sa_data), hr); } osLib_returnFromFunction(hCPU, r); } void nsysnetExport_listen(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "listen({},{})", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamS32(s, 0); ppcDefineParamS32(queueSize, 1); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); sint32 r = 0; if (vs == NULL) { assert_dbg(); } else { sint32 hr = listen(vs->s, queueSize); if (hr != 0) r = -1; // todo: Set proper coreinit errno (via _setSockError) } osLib_returnFromFunction(hCPU, r); } void nsysnetExport_accept(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "accept({},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); ppcDefineParamS32(s, 0); ppcDefineParamStructPtr(addr, struct wu_sockaddr, 1); ppcDefineParamMPTR(lenMPTR, 2); sint32 r = 0; virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); if (vs == NULL) { assert_dbg(); // todo return; } if (memory_readU32(lenMPTR) != 16) { cemuLog_log(LogType::Force, "invalid sockaddr len in accept()"); cemu_assert_debug(false); osLib_returnFromFunction(hCPU, 0); return; } if (vs->isNonBlocking) { sockaddr hostAddr; socklen_t hostLen = sizeof(sockaddr); SOCKET hr = accept(vs->s, &hostAddr, &hostLen); if (hr != SOCKET_ERROR) { r = nsysnet_createVirtualSocketFromExistingSocket(hr); _setSockError(WU_SO_SUCCESS); } else { r = _translateError((sint32)hr, (sint32)GETLASTERR, _ERROR_MODE_ACCEPT); } sockaddr_host2guest(&hostAddr, addr); } else { // blocking accept is not supported yet cemuLog_log(LogType::Force, "blocking accept() not supported"); cemu_assert_debug(false); } osLib_returnFromFunction(hCPU, r); } void nsysnetExport_connect(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "connect({},0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); ppcDefineParamS32(s, 0); ppcDefineParamStructPtr(addr, struct wu_sockaddr, 1); ppcDefineParamS32(len, 2); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); sint32 r = 0; if (vs == NULL) { assert_dbg(); return; } if (sizeof(sockaddr) != 16) assert_dbg(); sockaddr hostAddr; hostAddr.sa_family = _swapEndianU16(addr->sa_family); memcpy(hostAddr.sa_data, addr->sa_data, 14); sint32 hr = connect(vs->s, &hostAddr, sizeof(sockaddr)); cemuLog_log(LogType::Force, "Attempt connect to {}.{}.{}.{}:{}", (sint32)(uint8)hostAddr.sa_data[2], (sint32)(uint8)hostAddr.sa_data[3], (sint32)(uint8)hostAddr.sa_data[4], (sint32)(uint8)hostAddr.sa_data[5], _swapEndianU16(*(uint16*)hostAddr.sa_data+0)); r = _translateError(hr, GETLASTERR, _ERROR_MODE_CONNECT); osLib_returnFromFunction(hCPU, r); } void _setSocketSendRecvNonBlockingMode(SOCKET s, bool isNonBlocking) { u_long mode = isNonBlocking ? 1 : 0; sint32 r = _socket_nonblock(s, mode); } void nsysnetExport_send(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "send({},0x{:08x},{},0x{:x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); ppcDefineParamS32(s, 0); ppcDefineParamStr(msg, 1); ppcDefineParamS32(len, 2); ppcDefineParamS32(flags, 3); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); sint32 r = 0; if (vs == NULL) { assert_dbg(); return; } int hostFlags = 0; bool requestIsNonBlocking = (flags&WU_MSG_DONTWAIT) != 0; flags &= ~WU_MSG_DONTWAIT; if (requestIsNonBlocking != vs->isNonBlocking && vs->isNonBlocking == false) assert_dbg(); if (flags) assert_dbg(); sint32 hr = send(vs->s, msg, len, hostFlags); cemuLog_log(LogType::Socket, "Sent {} bytes", hr); _translateError(hr <= 0 ? -1 : 0, GETLASTERR); r = hr; osLib_returnFromFunction(hCPU, r); } void nsysnetExport_recv(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "recv({},0x{:08x},{},0x{:x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); ppcDefineParamS32(s, 0); ppcDefineParamStr(msg, 1); ppcDefineParamS32(len, 2); ppcDefineParamS32(flags, 3); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); sint32 r = 0; if (vs == NULL) { assert_dbg(); return; } int hostFlags = 0; bool requestIsNonBlocking = (flags&WU_MSG_DONTWAIT) != 0; flags &= ~WU_MSG_DONTWAIT; if (flags&WU_MSG_PEEK) { hostFlags |= MSG_PEEK; flags &= ~WU_MSG_PEEK; } if (vs->isNonBlocking) requestIsNonBlocking = vs->isNonBlocking; // non-blocking sockets always operate in MSG_DONTWAIT mode? if (flags) assert_dbg(); if (requestIsNonBlocking != vs->isNonBlocking) _setSocketSendRecvNonBlockingMode(vs->s, requestIsNonBlocking); // if blocking, yield thread until there is an error or at least 1 byte was received if (requestIsNonBlocking == false) { _setSocketSendRecvNonBlockingMode(vs->s, true); while (true) { char tempBuffer[1]; sint32 tr = recv(vs->s, tempBuffer, 1, MSG_PEEK); if (tr == 1) break; if (tr == 0) break; // connection closed if (tr < 0 && GETLASTERR != WSAEWOULDBLOCK) break; // yield thread coreinit::OSSleepTicks(coreinit::EspressoTime::GetTimerClock() / 5000); // let thread wait 0.2ms to give other threads CPU time // todo - eventually we should find a way to asynchronously signal recv instead of busy-looping here } _setSocketSendRecvNonBlockingMode(vs->s, requestIsNonBlocking); } // receive sint32 hr = recv(vs->s, msg, len, hostFlags); _translateError(hr <= 0 ? -1 : 0, GETLASTERR); if (requestIsNonBlocking != vs->isNonBlocking) _setSocketSendRecvNonBlockingMode(vs->s, vs->isNonBlocking); cemuLog_log(LogType::Socket, "Received {} bytes", hr); r = hr; osLib_returnFromFunction(hCPU, r); } struct wu_timeval { uint32 tv_sec; uint32 tv_usec; }; void _translateFDSet(fd_set* hostSet, struct wu_fd_set* fdset, sint32 nfds, int *hostnfds) { FD_ZERO(hostSet); if (fdset == NULL) return; #if BOOST_OS_UNIX int maxfd; if(hostnfds) maxfd = *hostnfds; else maxfd = -1; #endif uint32 mask = fdset->mask; for (sint32 i = 0; i < nfds; i++) { if( ((mask>>i)&1) == 0 ) continue; sint32 socketHandle = i; virtualSocket_t* vs = nsysnet_getVirtualSocketObject(socketHandle); if(vs == NULL) continue; // socket invalid #if BOOST_OS_UNIX if(vs->s > maxfd) maxfd = vs->s; #endif FD_SET(vs->s, hostSet); } #if BOOST_OS_UNIX if(hostnfds) *hostnfds = maxfd; #endif } void _translateFDSetRev(struct wu_fd_set* fdset, fd_set* hostSet, sint32 nfds) { if (fdset == NULL) return; uint32 mask = _swapEndianU32(0); #if BOOST_OS_WINDOWS for (sint32 i = 0; i < (sint32)hostSet->fd_count; i++) { sint32 virtualSocketHandle = nsysnet_getVirtualSocketHandleFromHostHandle(hostSet->fd_array[i]); if (virtualSocketHandle < 0) cemu_assert_debug(false); mask |= (1<<virtualSocketHandle); } #else for (sint32 i = 0; i < WU_SOCKET_LIMIT; i++) { if (virtualSocketTable[i] && virtualSocketTable[i]->s && FD_ISSET(virtualSocketTable[i]->s, hostSet)) mask |= (1 << virtualSocketTable[i]->handle); } #endif fdset->mask = mask; } void nsysnetExport_select(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "select({},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); ppcDefineParamS32(nfds, 0); ppcDefineParamStructPtr(readfds, struct wu_fd_set, 1); ppcDefineParamStructPtr(writefds, struct wu_fd_set, 2); ppcDefineParamStructPtr(exceptfds, struct wu_fd_set, 3); ppcDefineParamStructPtr(timeOut, struct wu_timeval, 4); //cemuLog_log(LogType::Socket, "rm {:08x} wm {:08x} em {:08x}", readfds ? _swapEndianU32(readfds->mask) : 0, writefds ? _swapEndianU32(writefds->mask) : 0, exceptfds ? _swapEndianU32(exceptfds->mask) : 0); sint32 r = 0; // translate fd_set fd_set _readfds, _writefds, _exceptfds; // handle empty select if ((readfds == NULL || readfds->mask == 0) && (writefds == NULL || writefds->mask == 0) && (exceptfds == NULL || exceptfds->mask == 0)) { if (timeOut == NULL || (timeOut->tv_sec == 0 && timeOut->tv_usec == 0)) { // return immediately cemuLog_log(LogType::Socket, "select returned immediately because of empty fdsets without timeout"); osLib_returnFromFunction(hCPU, 0); return; } else { //// empty select with timeout is not allowed //_setSockError(WU_SO_EINVAL); //osLib_returnFromFunction(hCPU, -1); //cemuLog_log(LogType::Socket, "select returned SO_EINVAL because of empty fdsets with timeout"); // when fd sets are empty but timeout is set, then just wait and do nothing? // Lost Reavers seems to expect this case to return 0 (it hardcodes empty fd sets and timeout comes from curl_multi_timeout) timeval tv; tv.tv_sec = timeOut->tv_sec; tv.tv_usec = timeOut->tv_usec; select(0, nullptr, nullptr, nullptr, &tv); cemuLog_log(LogType::Socket, "select returned 0 because of empty fdsets with timeout"); osLib_returnFromFunction(hCPU, 0); return; } } timeval tv = { 0 }; if (timeOut == NULL) { // return immediately cemuLog_log(LogType::Socket, "select returned immediately because of null timeout"); osLib_returnFromFunction(hCPU, 0); return; } uint64 msTimeout = (_swapEndianU32(timeOut->tv_usec) / 1000) + (_swapEndianU32(timeOut->tv_sec) * 1000); uint32 startTime = GetTickCount(); while (true) { int hostnfds = -1; _translateFDSet(&_readfds, readfds, nfds, &hostnfds); _translateFDSet(&_writefds, writefds, nfds, &hostnfds); _translateFDSet(&_exceptfds, exceptfds, nfds, &hostnfds); r = select(hostnfds + 1, readfds ? &_readfds : NULL, writefds ? &_writefds : NULL, exceptfds ? &_exceptfds : NULL, &tv); if (r < 0) { cemuLog_logDebug(LogType::Force, "select() failed"); // timeout _translateError(r, GETLASTERR); //_setSockError(WU_SO_SUCCESS); // in case of error, clear all FD sets (?) if (readfds) readfds->mask = 0; if (writefds) writefds->mask = 0; if (exceptfds) exceptfds->mask = 0; break; } else if (r == 0) { // check for timeout if ((GetTickCount() - startTime) >= msTimeout ) { // timeout _setSockError(WU_SO_SUCCESS); r = 0; // in case of timeout, clear all FD sets if (readfds) readfds->mask = 0; if (writefds) writefds->mask = 0; if (exceptfds) exceptfds->mask = 0; break; } // yield thread PPCCore_switchToScheduler(); } else { // cemuLog_log(LogType::Socket, "select returned {}. Read {} Write {} Except {}", r, _readfds.fd_count, _writefds.fd_count, _exceptfds.fd_count); cemuLog_log(LogType::Socket, "select returned {}.", r); _translateFDSetRev(readfds, &_readfds, nfds); _translateFDSetRev(writefds, &_writefds, nfds); _translateFDSetRev(exceptfds, &_exceptfds, nfds); break; } } //cemuLog_log(LogType::Force, "selectEndTime {}", timeGetTime()); //extern int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, // struct timeval *timeout); cemuLog_log(LogType::Socket, "select returned {}", r); osLib_returnFromFunction(hCPU, r); } void nsysnetExport_getsockname(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "getsockname({},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); ppcDefineParamS32(s, 0); ppcDefineParamStructPtr(addr, struct wu_sockaddr, 1); ppcDefineParamStructPtr(lenPtr, uint32, 2); sint32 r = 0; virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); if (vs == NULL) { assert_dbg(); } else { struct sockaddr hostAddr; socklen_t hostLen = sizeof(struct sockaddr); sint32 hr = getsockname(vs->s, &hostAddr, &hostLen); if (hr == 0) { addr->sa_family = _swapEndianU16(hostAddr.sa_family); memcpy(addr->sa_data, hostAddr.sa_data, 14); } else { assert_dbg(); } //sint32 hr = listen(vs->s, queueSize); //if (hr != 0) // r = -1; //// todo: Set proper coreinit errno (via _setSockError) } osLib_returnFromFunction(hCPU, r); } void nsysnetExport_getpeername(PPCInterpreter_t* hCPU) { ppcDefineParamS32(s, 0); ppcDefineParamStructPtr(name, struct wu_sockaddr, 1); ppcDefineParamU32BEPtr(nameLen, 2); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); if (vs == NULL) { // todo: Return correct error osLib_returnFromFunction(hCPU, -1); return; } sockaddr saddr; socklen_t saddrLen = sizeof(sockaddr); if (*nameLen < (uint32be)16) assert_dbg(); sint32 r = getpeername(vs->s, &saddr, &saddrLen); r = _translateError(r, GETLASTERR); name->sa_family = _swapEndianU16(saddr.sa_family); memcpy(name->sa_data, saddr.sa_data, 14); *nameLen = 16; osLib_returnFromFunction(hCPU, r); } typedef struct { MPTR h_name; MPTR h_aliases; sint32 h_addrType; sint32 h_length; MPTR h_addr_list; }wu_hostent; MPTR _allocString(char* str) { sint32 len = (sint32)strlen(str); MPTR strMPTR = coreinit_allocFromSysArea(len+1, 4); strcpy((char*)memory_getPointerFromVirtualOffset(strMPTR), str); return strMPTR; } void nsysnetExport_gethostbyname(PPCInterpreter_t* hCPU) { ppcDefineParamStr(name, 0); cemuLog_log(LogType::Socket, "gethostbyname(\"{}\")", name); hostent* he = gethostbyname(name); if (he == NULL) { osLib_returnFromFunction(hCPU, MPTR_NULL); return; } MPTR hostentMPTR = coreinit_allocFromSysArea(sizeof(wu_hostent)*1, 4); MPTR hostentAddrListMPTR = coreinit_allocFromSysArea(sizeof(MPTR) * 2, 4); MPTR hostentAddrListEntriesMPTR = coreinit_allocFromSysArea(sizeof(wu_in_addr) * 1, 4); wu_hostent* wuHostent = (wu_hostent*)memory_getPointerFromVirtualOffset(hostentMPTR); MPTR* addrList = (MPTR*)memory_getPointerFromVirtualOffset(hostentAddrListMPTR); wuHostent->h_addrType = _swapEndianU32((uint32)he->h_addrtype); wuHostent->h_length = _swapEndianU32((uint32)he->h_length); wuHostent->h_name = _swapEndianU32(_allocString(he->h_name)); wuHostent->h_addr_list = _swapEndianU32(hostentAddrListMPTR); //memory_writeU32(hostentAddrListEntriesMPTR, _swapEndianU32(*(uint32*)he->h_addr_list[0])); wu_in_addr* addrListEntries = (wu_in_addr*)memory_getPointerFromVirtualOffset(hostentAddrListEntriesMPTR); addrListEntries->wu_s_addr = *(uint32*)he->h_addr_list[0]; // address is already in network (big-endian) order memory_writeU32(hostentAddrListMPTR + 4 * 0, hostentAddrListEntriesMPTR); memory_writeU32(hostentAddrListMPTR + 4 * 1, MPTR_NULL); osLib_returnFromFunction(hCPU, hostentMPTR); return; } SysAllocator<wu_hostent> _staticHostent; SysAllocator<char, 256> _staticHostentName; SysAllocator<MPTR, 32> _staticHostentPtrList; SysAllocator<wu_in_addr> _staticHostentEntries; void nsysnetExport_gethostbyaddr(PPCInterpreter_t* hCPU) { ppcDefineParamStr(addr, 0); ppcDefineParamS32(len, 1); ppcDefineParamS32(type, 2); sint32 maxNumEntries = 31; cemuLog_log(LogType::Socket, "gethostbyaddr(\"{}\", {}, {})", addr, len, type); hostent* he = gethostbyaddr(addr, len, type); if (he == nullptr) { cemuLog_log(LogType::Socket, "gethostbyaddr(\"{}\", {}, {}) failed", addr, len, type); osLib_returnFromFunction(hCPU, MPTR_NULL); return; } #ifdef CEMU_DEBUG_ASSERT if (he->h_addrtype != AF_INET) assert_dbg(); if (he->h_length != sizeof(in_addr)) assert_dbg(); #endif wu_hostent* wuHostent = _staticHostent.GetPtr(); // setup wuHostent->h_name wuHostent->h_name = _swapEndianU32(_staticHostentName.GetMPTR()); if (he->h_name && strlen(he->h_name) < 255) { strcpy(_staticHostentName.GetPtr(), he->h_name); } else { cemuLog_log(LogType::Force, "he->h_name not set or name too long"); strcpy(_staticHostentName.GetPtr(), ""); } // setup wuHostent address list wuHostent->h_addrType = _swapEndianU32(WU_AF_INET); wuHostent->h_addr_list = _swapEndianU32(_staticHostentPtrList.GetMPTR()); wuHostent->h_length = _swapEndianU32(sizeof(wu_in_addr)); for (sint32 i = 0; i < maxNumEntries; i++) { if (he->h_addr_list[i] == nullptr) { _staticHostentPtrList.GetPtr()[i] = MPTR_NULL; break; } memcpy(_staticHostentEntries.GetPtr() + i, he->h_addr_list[i], sizeof(in_addr)); _staticHostentPtrList.GetPtr()[i] = _swapEndianU32(memory_getVirtualOffsetFromPointer(_staticHostentEntries.GetPtr() + i)); } _staticHostentPtrList.GetPtr()[31] = MPTR_NULL; // aliases are ignored for now wuHostent->h_aliases = MPTR_NULL; osLib_returnFromFunction(hCPU, _staticHostent.GetMPTR()); return; } void nsysnetExport_getaddrinfo(PPCInterpreter_t* hCPU) { ppcDefineParamStr(nodeName, 0); ppcDefineParamStr(serviceName, 1); ppcDefineParamStructPtr(hints, struct wu_addrinfo, 2); ppcDefineParamMPTR(results, 3); cemuLog_log(LogType::Socket, "getaddrinfo(\"{}\",0x{:08x},0x{:08x},0x{:08x})", nodeName, hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); sint32 r = 0; // todo1: This is really slow. Make it asynchronous // todo2: Should this set the socket last error code? struct addrinfo hint = { 0 }; if (hints) { hint.ai_family = _swapEndianU32(hints->ai_family); hint.ai_socktype = _swapEndianU32(hints->ai_socktype); hint.ai_flags = _swapEndianU32(hints->ai_flags); hint.ai_protocol = _swapEndianU32(hints->ai_protocol); } else { hint.ai_family = 0; hint.ai_socktype = 0; hint.ai_flags = 0; hint.ai_protocol = 0; } struct addrinfo* result; sint32 hr = getaddrinfo(nodeName, serviceName, &hint, &result); if (hr != 0) { cemuLog_log(LogType::Socket, "getaddrinfo failed with error {}", hr); switch (hr) { case WSAHOST_NOT_FOUND: r = WU_SO_ECONNRESET; // todo - verify error code break; default: // unhandled error cemu_assert_debug(false); cemuLog_log(LogType::Socket, "getaddrinfo unhandled error code"); r = 1; break; } } else { // count how many results there are sint32 resultCount = 0; struct addrinfo* currentAddrInfo = result; while (currentAddrInfo) { resultCount++; currentAddrInfo = currentAddrInfo->ai_next; } if (resultCount == 0) { cemu_assert_debug(false); } // allocate entries MPTR addrInfoMPTR = coreinit_allocFromSysArea(sizeof(wu_addrinfo)*resultCount + sizeof(wu_sockaddr)*resultCount, 4); MPTR addrInfoSockAddrMPTR = addrInfoMPTR + sizeof(wu_addrinfo)*resultCount; wu_addrinfo* wuAddrInfo = (wu_addrinfo*)memory_getPointerFromVirtualOffset(addrInfoMPTR); // fill entries currentAddrInfo = result; sint32 entryIndex = 0; wu_addrinfo* previousWuAddrInfo = NULL; while (currentAddrInfo) { if (currentAddrInfo->ai_addrlen != 16) { // skip this entry (IPv6) currentAddrInfo = currentAddrInfo->ai_next; continue; } memset(&wuAddrInfo[entryIndex], 0, sizeof(wu_addrinfo)); // setup pointers wuAddrInfo[entryIndex].ai_addr = _swapEndianU32(addrInfoSockAddrMPTR + sizeof(wu_sockaddr)*entryIndex); wuAddrInfo[entryIndex].ai_next = MPTR_NULL; wuAddrInfo[entryIndex].ai_canonname = _swapEndianU32(MPTR_NULL); // set ai_next for previous element if (previousWuAddrInfo) { previousWuAddrInfo->ai_next = _swapEndianU32(memory_getVirtualOffsetFromPointer(&wuAddrInfo[entryIndex])); } previousWuAddrInfo = &wuAddrInfo[entryIndex]; // fill addrinfo struct wuAddrInfo[entryIndex].ai_addrlen = _swapEndianU32((uint32)currentAddrInfo->ai_addrlen); //wuAddrInfo[entryIndex].ai_canonname; todo wuAddrInfo[entryIndex].ai_family = _swapEndianU32(currentAddrInfo->ai_family); wuAddrInfo[entryIndex].ai_flags = _swapEndianU32(currentAddrInfo->ai_flags); // todo: These flags might need to be translated wuAddrInfo[entryIndex].ai_protocol = _swapEndianU32(currentAddrInfo->ai_protocol); wuAddrInfo[entryIndex].ai_socktype = _swapEndianU32(currentAddrInfo->ai_socktype); // fill ai_addr wu_sockaddr* sockAddr = (wu_sockaddr*)memory_getPointerFromVirtualOffset(_swapEndianU32(wuAddrInfo[entryIndex].ai_addr)); sockAddr->sa_family = _swapEndianU16(currentAddrInfo->ai_addr->sa_family); memcpy(sockAddr->sa_data, currentAddrInfo->ai_addr->sa_data, 14); // next entryIndex++; currentAddrInfo = currentAddrInfo->ai_next; } if (entryIndex == 0) assert_dbg(); // set results memory_writeU32(results, addrInfoMPTR); } osLib_returnFromFunction(hCPU, r); } void nsysnetExport_recvfrom(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "recvfrom({},0x{:08x},{},0x{:x},0x{:x},0x{:x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]); ppcDefineParamS32(s, 0); ppcDefineParamStr(msg, 1); ppcDefineParamS32(len, 2); ppcDefineParamS32(flags, 3); ppcDefineParamStructPtr(fromAddr, wu_sockaddr, 4); ppcDefineParamU32BEPtr(fromLen, 5); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); sint32 r = 0; if (vs == NULL) { assert_dbg(); return; } int hostFlags = 0; bool requestIsNonBlocking = (flags&WU_MSG_DONTWAIT) != 0; flags &= ~WU_MSG_DONTWAIT; if (flags&WU_MSG_PEEK) { assert_dbg(); hostFlags |= MSG_PEEK; flags &= ~WU_MSG_PEEK; } if (vs->isNonBlocking) requestIsNonBlocking = vs->isNonBlocking; sockaddr fromAddrHost; socklen_t fromLenHost = sizeof(fromAddrHost); sint32 wsaError = 0; while( true ) { // is socket recv shutdown? if (vs->isShutdownRecv) { // return with error _setSockError(WU_SO_ESHUTDOWN); osLib_returnFromFunction(hCPU, -1); return; } // use select to check for exceptions and read data fd_set fd_read; fd_set fd_exceptions; FD_ZERO(&fd_read); FD_ZERO(&fd_exceptions); FD_SET(vs->s, &fd_read); FD_SET(vs->s, &fd_exceptions); timeval t; t.tv_sec = 0; t.tv_usec = 0; int nfds = 0; #if BOOST_OS_UNIX nfds = vs->s + 1; #endif sint32 count = select(nfds, &fd_read, NULL, &fd_exceptions, &t); if (count > 0) { if (FD_ISSET(vs->s, &fd_exceptions)) { assert_dbg(); // exception } if (FD_ISSET(vs->s, &fd_read)) { // data available r = recvfrom(vs->s, msg, len, hostFlags, &fromAddrHost, &fromLenHost); wsaError = GETLASTERR; if (r < 0) cemu_assert_debug(false); cemuLog_logDebug(LogType::Force, "recvfrom returned {} bytes", r); // fromAddr and fromLen can be NULL if (fromAddr && fromLen) { *fromLen = fromLenHost; fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); } _setSockError(0); osLib_returnFromFunction(hCPU, r); return; } } // nothing to do if (requestIsNonBlocking) { // return with error _setSockError(WU_SO_EWOULDBLOCK); osLib_returnFromFunction(hCPU, -1); return; } else { // pause for a while and check again later coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK / 1000); // pause for 1ms PPCCore_switchToScheduler(); } } assert_dbg(); // should no longer be reached if (requestIsNonBlocking == false) { // blocking _setSocketSendRecvNonBlockingMode(vs->s, true); while (true) { r = recvfrom(vs->s, msg, len, hostFlags, &fromAddrHost, &fromLenHost); wsaError = GETLASTERR; if (r < 0) { if (wsaError != WSAEWOULDBLOCK) break; coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK/100); // pause for 10ms PPCCore_switchToScheduler(); continue; } assert_dbg(); } _setSocketSendRecvNonBlockingMode(vs->s, vs->isNonBlocking); } else { // non blocking assert_dbg(); } // fromAddr and fromLen can be NULL if (fromAddr && fromLen) { *fromLen = fromLenHost; fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); } _translateError(r <= 0 ? -1 : 0, wsaError); osLib_returnFromFunction(hCPU, r); } void nsysnetExport_recvfrom_ex(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "recvfrom_ex({},0x{:08x},{},0x{:x},0x{:08x},0x{:08x},0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9], hCPU->gpr[10]); ppcDefineParamS32(s, 0); ppcDefineParamStr(msg, 1); ppcDefineParamS32(len, 2); ppcDefineParamS32(flags, 3); ppcDefineParamStructPtr(fromAddr, wu_sockaddr, 4); ppcDefineParamU32BEPtr(fromLen, 5); ppcDefineParamUStr(extraData, 6); ppcDefineParamS32(extraDataLen, 7); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); sint32 r = 0; if (vs == NULL) { cemu_assert_debug(false); return; } int hostFlags = 0; bool requestIsNonBlocking = (flags&WU_MSG_DONTWAIT) != 0; flags &= ~WU_MSG_DONTWAIT; if (flags&WU_MSG_PEEK) { cemu_assert_debug(false); hostFlags |= MSG_PEEK; flags &= ~WU_MSG_PEEK; } if (flags & 0x40) { // read TTL if (extraDataLen != 1) { cemu_assert_debug(false); } extraData[0] = 0x5; // we currently always return 5 as TTL flags &= ~0x40; } if (vs->isNonBlocking) requestIsNonBlocking = vs->isNonBlocking; socklen_t fromLenHost = *fromLen; sockaddr fromAddrHost; sint32 wsaError = 0; while (true) { // is socket recv shutdown? if (vs->isShutdownRecv) { // return with error _setSockError(WU_SO_ESHUTDOWN); osLib_returnFromFunction(hCPU, -1); return; } // use select to check for exceptions and read data fd_set fd_read; fd_set fd_exceptions; FD_ZERO(&fd_read); FD_ZERO(&fd_exceptions); FD_SET(vs->s, &fd_read); FD_SET(vs->s, &fd_exceptions); timeval t; t.tv_sec = 0; t.tv_usec = 0; int nfds = 0; #if BOOST_OS_UNIX nfds = vs->s + 1; #endif sint32 count = select(nfds, &fd_read, NULL, &fd_exceptions, &t); if (count > 0) { if (FD_ISSET(vs->s, &fd_exceptions)) { cemu_assert_debug(false); // exception } if (FD_ISSET(vs->s, &fd_read)) { // data available r = recvfrom(vs->s, msg, len, hostFlags, &fromAddrHost, &fromLenHost); wsaError = GETLASTERR; if (r < 0) { cemu_assert_debug(false); } *fromLen = fromLenHost; fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); _setSockError(0); osLib_returnFromFunction(hCPU, r); return; } } // nothing to do if (requestIsNonBlocking) { // return with error _setSockError(WU_SO_EWOULDBLOCK); osLib_returnFromFunction(hCPU, -1); return; } else { // pause for a while and check again later coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK / 100); // pause for 10ms PPCCore_switchToScheduler(); } } cemu_assert_debug(false); // should no longer be reached } void _convertSockaddrToHostFormat(wu_sockaddr* sockaddru, sockaddr* sockaddrHost) { sockaddrHost->sa_family = _swapEndianU16(sockaddru->sa_family); memcpy(sockaddrHost->sa_data, sockaddru->sa_data, 14); } void nsysnetExport_sendto(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "sendto({},0x{:08x},{},0x{:x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); ppcDefineParamS32(s, 0); ppcDefineParamStr(msg, 1); ppcDefineParamS32(len, 2); ppcDefineParamS32(flags, 3); ppcDefineParamStructPtr(toAddr, wu_sockaddr, 4); ppcDefineParamU32(toLen, 5); virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); sint32 r = 0; if (vs == NULL) { assert_dbg(); return; } int hostFlags = 0; bool requestIsNonBlocking = (flags&WU_MSG_DONTWAIT) != 0; flags &= ~WU_MSG_DONTWAIT; if (flags&WU_MSG_PEEK) { assert_dbg(); hostFlags |= MSG_PEEK; flags &= ~WU_MSG_PEEK; } if (vs->isNonBlocking) requestIsNonBlocking = vs->isNonBlocking; sockaddr toAddrHost; toAddrHost.sa_family = _swapEndianU16(toAddr->sa_family); memcpy(toAddrHost.sa_data, toAddr->sa_data, 14); sint32 wsaError = 0; if (requestIsNonBlocking == false) { // blocking _setSocketSendRecvNonBlockingMode(vs->s, true); while (true) { r = sendto(vs->s, msg, len, hostFlags, &toAddrHost, toLen); wsaError = GETLASTERR; if (r < 0) { if (wsaError != WSAEWOULDBLOCK) break; coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK / 100); // pause for 10ms PPCCore_switchToScheduler(); continue; } break; } _setSocketSendRecvNonBlockingMode(vs->s, vs->isNonBlocking); } else { // non blocking _setSocketSendRecvNonBlockingMode(vs->s, true); r = sendto(vs->s, msg, len, hostFlags, &toAddrHost, toLen); wsaError = GETLASTERR; _setSocketSendRecvNonBlockingMode(vs->s, vs->isNonBlocking); } _translateError(r <= 0 ? -1 : 0, wsaError); osLib_returnFromFunction(hCPU, r); } void nsysnetExport_sendto_multi(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "sendto_multi({},0x{:08x},0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); ppcDefineParamS32(s, 0); ppcDefineParamStr(data, 1); ppcDefineParamS32(dataLen, 2); ppcDefineParamU32(flags, 3); ppcDefineParamStructPtr(destinationArray, wu_sockaddr, 4); ppcDefineParamU32(destinationCount, 5); if (flags != 0) assert_dbg(); // todo - somehow handle non-blocking virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); if (vs == NULL) { assert_dbg(); return; } for (uint32 i = 0; i < destinationCount; i++) { sockaddr destinationHost; _convertSockaddrToHostFormat(&destinationArray[i], &destinationHost); sint32 r = sendto(vs->s, (char*)data, (int)dataLen, 0, &destinationHost, sizeof(sockaddr)); if (r < dataLen) assert_dbg(); // todo } // success _setSockError(0); osLib_returnFromFunction(hCPU, dataLen); } typedef struct { MEMPTR<uint8> data; uint32be dataLen; MEMPTR<uint32be> sendLenArray; uint32be sendLenArraySize; MEMPTR<wu_sockaddr> destArray; uint32be destArraySize; MEMPTR<uint32be> resultArray; uint32be resultArrayLen; }sendtomultiBuffer_t; void nsysnetExport_sendto_multi_ex(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::Socket, "sendto_multi_ex({},0x{:08x},0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); ppcDefineParamS32(s, 0); ppcDefineParamU32(flags, 1); ppcDefineParamStructPtr(multiBuf, sendtomultiBuffer_t, 2); ppcDefineParamU32(num, 3); assert_dbg(); // todo - needs testing virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); if (vs == NULL) { assert_dbg(); return; } // todo - lots of validation for multiBuf parameters (pointers must not be null and must be aligned, lens must be padded to alignment too) // verify multiBuf if ((uint32)multiBuf->sendLenArraySize < num || (uint32)multiBuf->destArraySize < num || (uint32)multiBuf->resultArrayLen < num ) { cemu_assert_debug(false); } for (uint32 i = 0; i < num; i++) { multiBuf->resultArray[i] = 0; } uint8* data = multiBuf->data.GetPtr(); sint32 sendLenSum = 0; for (uint32 i = 0; i < num; i++) { sockaddr destination; _convertSockaddrToHostFormat(&multiBuf->destArray[i], &destination); uint32 sendLen = (uint32)(multiBuf->sendLenArray[i]); sint32 r = sendto(vs->s, (char*)data, (int)sendLen, 0, &destination, sizeof(sockaddr)); if (r < (sint32)sendLen) cemu_assert_debug(false); else multiBuf->resultArray[i] = r; data += sendLen; sendLenSum += sendLenSum; } osLib_returnFromFunction(hCPU, sendLenSum); // return value correct? } namespace nsysnet { std::vector<NSSLInternalState_t> g_nsslInternalStates; NSSLInternalState_t* GetNSSLContext(sint32 index) { if (g_nsslInternalStates.size() <= index) return nullptr; if (g_nsslInternalStates[index].destroyed) cemu_assert_suspicious(); return &g_nsslInternalStates[index]; } void export_NSSLCreateContext(PPCInterpreter_t* hCPU) { ppcDefineParamU32(version, 0); NSSLInternalState_t ssl = {}; ssl.sslVersion = version; g_nsslInternalStates.push_back(ssl); uint32 nsslHandle = (uint32)g_nsslInternalStates.size() - 1; cemuLog_logDebug(LogType::Force, "NSSLCreateContext(0x{:x}) -> 0x{:x}", version, nsslHandle); osLib_returnFromFunction(hCPU, nsslHandle); } void export_NSSLSetClientPKI(PPCInterpreter_t* hCPU) { ppcDefineParamU32(nsslHandle, 0); ppcDefineParamU32(clientPKI, 1); cemuLog_logDebug(LogType::Force, "NSSLSetClientPKI(0x{:x}, 0x{:x})", nsslHandle, clientPKI); if (g_nsslInternalStates.size() <= nsslHandle || g_nsslInternalStates[nsslHandle].destroyed) { osLib_returnFromFunction(hCPU, NSSL_INVALID_CTX); return; } g_nsslInternalStates[nsslHandle].clientPKI = clientPKI; osLib_returnFromFunction(hCPU, NSSL_OK); } void export_NSSLAddServerPKI(PPCInterpreter_t* hCPU) { ppcDefineParamU32(nsslHandle, 0); ppcDefineParamU32(serverPKI, 1); cemuLog_logDebug(LogType::Force, "NSSLAddServerPKI(0x{:x}, 0x{:x})", nsslHandle, serverPKI); if (g_nsslInternalStates.size() <= nsslHandle || g_nsslInternalStates[nsslHandle].destroyed) { osLib_returnFromFunction(hCPU, NSSL_INVALID_CTX); return; } g_nsslInternalStates[nsslHandle].serverPKIs.insert(serverPKI); osLib_returnFromFunction(hCPU, NSSL_OK); } void export_NSSLAddServerPKIExternal(PPCInterpreter_t* hCPU) { ppcDefineParamU32(nsslHandle, 0); ppcDefineParamMEMPTR(certData, uint8, 1); ppcDefineParamS32(certLen, 2); ppcDefineParamS32(certType, 3); cemuLog_logDebug(LogType::Force, "NSSLAddServerPKIExternal(0x{:x}, 0x{:08x}, 0x{:x}, {})", nsslHandle, certData.GetMPTR(), certLen, certType); if (g_nsslInternalStates.size() <= nsslHandle || g_nsslInternalStates[nsslHandle].destroyed) { osLib_returnFromFunction(hCPU, NSSL_INVALID_CTX); return; } g_nsslInternalStates[nsslHandle].serverCustomPKIs.push_back(std::vector<uint8>(certData.GetPtr(), certData.GetPtr()+certLen)); osLib_returnFromFunction(hCPU, NSSL_OK); } void export_NSSLAddServerPKIGroups(PPCInterpreter_t* hCPU) { ppcDefineParamU32(nsslHandle, 0); ppcDefineParamU32(groupMask, 1); ppcDefineParamMEMPTR(validCountOut, sint32, 2); ppcDefineParamMEMPTR(invalidCountOut, sint32, 3); cemuLog_logDebug(LogType::Force, "NSSLAddServerPKIGroups(0x{:x}, 0x{:x}, 0x{:08x}, 0x{:08x})", nsslHandle, groupMask, validCountOut.GetMPTR(), invalidCountOut.GetMPTR()); if (g_nsslInternalStates.size() <= nsslHandle || g_nsslInternalStates[nsslHandle].destroyed) { osLib_returnFromFunction(hCPU, NSSL_INVALID_CTX); return; } if (HAS_FLAG(groupMask, 1)) { g_nsslInternalStates[nsslHandle].serverPKIs.insert({ 100,101,102,103,104,105 }); } if (HAS_FLAG(groupMask, 2)) { g_nsslInternalStates[nsslHandle].serverPKIs.insert({ 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033 }); } if (validCountOut) *validCountOut = (sint32)g_nsslInternalStates[nsslHandle].serverPKIs.size(); if (invalidCountOut) *invalidCountOut = 0; osLib_returnFromFunction(hCPU, NSSL_OK); } void export_NSSLDestroyContext(PPCInterpreter_t* hCPU) { ppcDefineParamU32(nsslHandle, 0); cemuLog_logDebug(LogType::Force, "NSSLDestroyContext(0x{:x})", nsslHandle); if (g_nsslInternalStates.size() <= nsslHandle || g_nsslInternalStates[nsslHandle].destroyed) { osLib_returnFromFunction(hCPU, NSSL_INVALID_CTX); return; } g_nsslInternalStates[nsslHandle].destroyed = true; osLib_returnFromFunction(hCPU, NSSL_OK); } void export_NSSLExportInternalServerCertificate(PPCInterpreter_t* hCPU) { ppcDefineParamU32(serverCertId, 0); ppcDefineParamUStr(output, 1); ppcDefineParamU32BEPtr(outputSize, 2); ppcDefineParamU32BEPtr(certType, 3); sint32 certificateSize = 0; uint8* certificateData = iosuCrypto_getCertificateDataById(serverCertId, &certificateSize); if (certificateData == nullptr) { cemu_assert_debug(false); } if( output ) memcpy(output, certificateData, certificateSize); *outputSize = (uint32)certificateSize; *certType = 0; osLib_returnFromFunction(hCPU, NSSL_OK); } void export_NSSLExportInternalClientCertificate(PPCInterpreter_t* hCPU) { ppcDefineParamU32(serverCertId, 0); ppcDefineParamUStr(output, 1); ppcDefineParamU32BEPtr(outputSize, 2); ppcDefineParamU32BEPtr(certType, 3); ppcDefineParamUStr(pkeyOutput, 4); ppcDefineParamU32BEPtr(pkeyOutputSize, 5); ppcDefineParamU32BEPtr(pkeyCertType, 6); // certificate sint32 certificateSize = 0; uint8* certificateData = iosuCrypto_getCertificateDataById(serverCertId, &certificateSize); if (certificateData == nullptr) { assert_dbg(); // todo } if (output) memcpy(output, certificateData, certificateSize); *outputSize = (uint32)certificateSize; *certType = 0; // private key sint32 pkeySize = 0; uint8* pkeyData = iosuCrypto_getCertificatePrivateKeyById(serverCertId, &pkeySize); if (pkeyData == nullptr) { assert_dbg(); // todo } if (pkeyOutput) memcpy(pkeyOutput, pkeyData, pkeySize); *pkeyOutputSize = (uint32)pkeySize; *pkeyCertType = 0; osLib_returnFromFunction(hCPU, NSSL_OK); } void wuResetFD(struct wu_fd_set* fdset) { fdset->mask = 0; } void wuSetFD(struct wu_fd_set* fdset, sint32 fd) { fdset->mask |= (1 << fd); } } namespace nsysnet { class : public COSModule { public: std::string_view GetName() override { return "nsysnet"; } void RPLMapped() override { cafeExportRegister("nsysnet", inet_ntop, LogType::Socket); // the below code is the old way of registering API which is deprecated osLib_addFunction("nsysnet", "socket_lib_init", nsysnetExport_socket_lib_init); osLib_addFunction("nsysnet", "socket_lib_finish", nsysnetExport_socket_lib_finish); // socket API osLib_addFunction("nsysnet", "socket", nsysnetExport_socket); osLib_addFunction("nsysnet", "mw_socket", nsysnetExport_mw_socket); osLib_addFunction("nsysnet", "shutdown", nsysnetExport_shutdown); osLib_addFunction("nsysnet", "socketclose", nsysnetExport_socketclose); osLib_addFunction("nsysnet", "setsockopt", nsysnetExport_setsockopt); osLib_addFunction("nsysnet", "getsockopt", nsysnetExport_getsockopt); osLib_addFunction("nsysnet", "bind", nsysnetExport_bind); osLib_addFunction("nsysnet", "listen", nsysnetExport_listen); osLib_addFunction("nsysnet", "accept", nsysnetExport_accept); osLib_addFunction("nsysnet", "connect", nsysnetExport_connect); osLib_addFunction("nsysnet", "send", nsysnetExport_send); osLib_addFunction("nsysnet", "recv", nsysnetExport_recv); osLib_addFunction("nsysnet", "select", nsysnetExport_select); osLib_addFunction("nsysnet", "getsockname", nsysnetExport_getsockname); osLib_addFunction("nsysnet", "getpeername", nsysnetExport_getpeername); osLib_addFunction("nsysnet", "inet_aton", nsysnetExport_inet_aton); osLib_addFunction("nsysnet", "inet_pton", nsysnetExport_inet_pton); osLib_addFunction("nsysnet", "inet_ntoa", nsysnetExport_inet_ntoa); osLib_addFunction("nsysnet", "htons", nsysnetExport_htons); osLib_addFunction("nsysnet", "htonl", nsysnetExport_htonl); osLib_addFunction("nsysnet", "ntohs", nsysnetExport_ntohs); osLib_addFunction("nsysnet", "ntohl", nsysnetExport_ntohl); osLib_addFunction("nsysnet", "gethostbyname", nsysnetExport_gethostbyname); osLib_addFunction("nsysnet", "gethostbyaddr", nsysnetExport_gethostbyaddr); osLib_addFunction("nsysnet", "getaddrinfo", nsysnetExport_getaddrinfo); osLib_addFunction("nsysnet", "socketlasterr", nsysnetExport_socketlasterr); // unfinished implementations osLib_addFunction("nsysnet", "recvfrom", nsysnetExport_recvfrom); osLib_addFunction("nsysnet", "recvfrom_ex", nsysnetExport_recvfrom_ex); osLib_addFunction("nsysnet", "sendto", nsysnetExport_sendto); osLib_addFunction("nsysnet", "sendto_multi", nsysnetExport_sendto_multi); osLib_addFunction("nsysnet", "sendto_multi_ex", nsysnetExport_sendto_multi_ex); // NSSL API osLib_addFunction("nsysnet", "NSSLCreateContext", nsysnet::export_NSSLCreateContext); osLib_addFunction("nsysnet", "NSSLSetClientPKI", nsysnet::export_NSSLSetClientPKI); osLib_addFunction("nsysnet", "NSSLAddServerPKI", nsysnet::export_NSSLAddServerPKI); osLib_addFunction("nsysnet", "NSSLAddServerPKIExternal", nsysnet::export_NSSLAddServerPKIExternal); osLib_addFunction("nsysnet", "NSSLAddServerPKIGroups", nsysnet::export_NSSLAddServerPKIGroups); osLib_addFunction("nsysnet", "NSSLDestroyContext", nsysnet::export_NSSLDestroyContext); osLib_addFunction("nsysnet", "NSSLExportInternalServerCertificate", nsysnet::export_NSSLExportInternalServerCertificate); osLib_addFunction("nsysnet", "NSSLExportInternalClientCertificate", nsysnet::export_NSSLExportInternalClientCertificate); }; }s_COSnsysnetModule; COSModule* GetModule() { return &s_COSnsysnetModule; } } ================================================ FILE: src/Cafe/OS/libs/nsysnet/nsysnet.h ================================================ #pragma once #include <set> #include <vector> #include "Cafe/OS/RPL/COSModule.h" #if BOOST_OS_WINDOWS #include <WinSock2.h> #else #include <sys/socket.h> #define SOCKET int #define closesocket close #endif typedef signed int WUSOCKET; WUSOCKET nsysnet_createVirtualSocketFromExistingSocket(SOCKET existingSocket); void nsysnet_notifyCloseSharedSocket(SOCKET existingSocket); struct wu_fd_set { uint32be mask; }; void _translateFDSet(fd_set* hostSet, struct wu_fd_set* fdset, sint32 nfds); void _translateFDSetRev(struct wu_fd_set* fdset, fd_set* hostSet, sint32 nfds); sint32 nsysnet_getVirtualSocketHandleFromHostHandle(SOCKET s); namespace nsysnet { #define NSSL_OK (0) #define NSSL_INVALID_CTX (0xFFD7FFFF) struct NSSLInternalState_t { bool destroyed; uint32 sslVersion; uint32 clientPKI; std::set<uint32> serverPKIs; std::vector<std::vector<uint8>> serverCustomPKIs; }; NSSLInternalState_t* GetNSSLContext(sint32 index); void wuResetFD(struct wu_fd_set* fdset); void wuSetFD(struct wu_fd_set* fdset, sint32 fd); COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/ntag/ntag.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/ntag/ntag.h" #include "Cafe/OS/libs/nfc/nfc.h" #include "Cafe/OS/libs/coreinit/coreinit_IPC.h" #include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" namespace ntag { struct NTAGWriteData { uint16 size; uint8 data[0x1C8]; nfc::NFCUid uid; nfc::NFCUid uidMask; }; NTAGWriteData gWriteData[2]; bool ccrNfcOpened = false; IOSDevHandle gCcrNfcHandle; NTAGFormatSettings gFormatSettings; MPTR gDetectCallbacks[2]; MPTR gAbortCallbacks[2]; MPTR gReadCallbacks[2]; MPTR gWriteCallbacks[2]; sint32 __NTAGConvertNFCResult(sint32 result) { if (result == NFC_RESULT_SUCCESS) { return NTAG_RESULT_SUCCESS; } switch (result & NFC_RESULT_MASK) { case NFC_RESULT_UNINITIALIZED: return NTAG_RESULT_UNINITIALIZED; case NFC_RESULT_INVALID_STATE: return NTAG_RESULT_INVALID_STATE; case NFC_RESULT_NO_TAG: return NTAG_RESULT_NO_TAG; case NFC_RESULT_UID_MISMATCH: return NTAG_RESULT_UID_MISMATCH; } // TODO convert more errors return NTAG_RESULT_INVALID; } sint32 NTAGInit(uint32 chan) { return NTAGInitEx(chan); } sint32 NTAGInitEx(uint32 chan) { sint32 result = nfc::NFCInitEx(chan, 1); return __NTAGConvertNFCResult(result); } sint32 NTAGShutdown(uint32 chan) { sint32 result = nfc::NFCShutdown(chan); if (ccrNfcOpened) { coreinit::IOS_Close(gCcrNfcHandle); ccrNfcOpened = false; } gDetectCallbacks[chan] = MPTR_NULL; gAbortCallbacks[chan] = MPTR_NULL; gReadCallbacks[chan] = MPTR_NULL; gWriteCallbacks[chan] = MPTR_NULL; return __NTAGConvertNFCResult(result); } bool NTAGIsInit(uint32 chan) { return nfc::NFCIsInit(chan); } void NTAGProc(uint32 chan) { nfc::NFCProc(chan); } void NTAGSetFormatSettings(NTAGFormatSettings* formatSettings) { gFormatSettings.version = formatSettings->version; gFormatSettings.makerCode = _swapEndianU32(formatSettings->makerCode); gFormatSettings.identifyCode = _swapEndianU32(formatSettings->identifyCode); } void __NTAGDetectCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(chan, 0); ppcDefineParamU32(hasTag, 1); ppcDefineParamPtr(context, void, 2); cemuLog_log(LogType::NTAG, "__NTAGDetectCallback: {} {} {}", chan, hasTag, context); PPCCoreCallback(gDetectCallbacks[chan], chan, hasTag, context); osLib_returnFromFunction(hCPU, 0); } void NTAGSetTagDetectCallback(uint32 chan, MPTR callback, void* context) { cemu_assert(chan < 2); gDetectCallbacks[chan] = callback; nfc::NFCSetTagDetectCallback(chan, RPLLoader_MakePPCCallable(__NTAGDetectCallback), context); } void __NTAGAbortCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(chan, 0); ppcDefineParamS32(error, 1); ppcDefineParamPtr(context, void, 2); PPCCoreCallback(gAbortCallbacks[chan], chan, __NTAGConvertNFCResult(error), context); osLib_returnFromFunction(hCPU, 0); } sint32 NTAGAbort(uint32 chan, MPTR callback, void* context) { cemu_assert(chan < 2); // TODO is it normal that Rumble U calls this? gAbortCallbacks[chan] = callback; sint32 result = nfc::NFCAbort(chan, RPLLoader_MakePPCCallable(__NTAGAbortCallback), context); return __NTAGConvertNFCResult(result); } bool __NTAGRawDataToNfcData(iosu::ccr_nfc::CCRNFCCryptData* raw, iosu::ccr_nfc::CCRNFCCryptData* nfc) { memcpy(nfc, raw, sizeof(iosu::ccr_nfc::CCRNFCCryptData)); if (raw->version == 0) { nfc->version = 0; nfc->dataSize = 0x1C8; nfc->seedOffset = 0x25; nfc->keyGenSaltOffset = 0x1A8; nfc->uuidOffset = 0x198; nfc->unfixedInfosOffset = 0x28; nfc->unfixedInfosSize = 0x120; nfc->lockedSecretOffset = 0x168; nfc->lockedSecretSize = 0x30; nfc->unfixedInfosHmacOffset = 0; nfc->lockedSecretHmacOffset = 0x148; } else if (raw->version == 2) { nfc->version = 2; nfc->dataSize = 0x208; nfc->seedOffset = 0x29; nfc->keyGenSaltOffset = 0x1E8; nfc->uuidOffset = 0x1D4; nfc->unfixedInfosOffset = 0x2C; nfc->unfixedInfosSize = 0x188; nfc->lockedSecretOffset = 0x1DC; nfc->lockedSecretSize = 0; nfc->unfixedInfosHmacOffset = 0x8; nfc->lockedSecretHmacOffset = 0x1B4; memcpy(nfc->data + 0x1d4, raw->data, 0x8); memcpy(nfc->data, raw->data + 0x8, 0x8); memcpy(nfc->data + 0x28, raw->data + 0x10, 0x4); memcpy(nfc->data + nfc->unfixedInfosOffset, raw->data + 0x14, 0x20); memcpy(nfc->data + nfc->lockedSecretHmacOffset, raw->data + 0x34, 0x20); memcpy(nfc->data + nfc->lockedSecretOffset, raw->data + 0x54, 0xC); memcpy(nfc->data + nfc->keyGenSaltOffset, raw->data + 0x60, 0x20); memcpy(nfc->data + nfc->unfixedInfosHmacOffset, raw->data + 0x80, 0x20); memcpy(nfc->data + nfc->unfixedInfosOffset + 0x20, raw->data + 0xa0, 0x168); memcpy(nfc->data + 0x208, raw->data + 0x208, 0x14); } else { return false; } return true; } bool __NTAGNfcDataToRawData(iosu::ccr_nfc::CCRNFCCryptData* nfc, iosu::ccr_nfc::CCRNFCCryptData* raw) { memcpy(raw, nfc, sizeof(iosu::ccr_nfc::CCRNFCCryptData)); if (nfc->version == 0) { raw->version = 0; raw->dataSize = 0x1C8; raw->seedOffset = 0x25; raw->keyGenSaltOffset = 0x1A8; raw->uuidOffset = 0x198; raw->unfixedInfosOffset = 0x28; raw->unfixedInfosSize = 0x120; raw->lockedSecretOffset = 0x168; raw->lockedSecretSize = 0x30; raw->unfixedInfosHmacOffset = 0; raw->lockedSecretHmacOffset = 0x148; } else if (nfc->version == 2) { raw->version = 2; raw->dataSize = 0x208; raw->seedOffset = 0x11; raw->keyGenSaltOffset = 0x60; raw->uuidOffset = 0; raw->unfixedInfosOffset = 0x14; raw->unfixedInfosSize = 0x188; raw->lockedSecretOffset = 0x54; raw->lockedSecretSize = 0xC; raw->unfixedInfosHmacOffset = 0x80; raw->lockedSecretHmacOffset = 0x34; memcpy(raw->data + 0x8, nfc->data, 0x8); memcpy(raw->data + raw->unfixedInfosHmacOffset, nfc->data + 0x8, 0x20); memcpy(raw->data + 0x10, nfc->data + 0x28, 0x4); memcpy(raw->data + raw->unfixedInfosOffset, nfc->data + 0x2C, 0x20); memcpy(raw->data + 0xa0, nfc->data + 0x4C, 0x168); memcpy(raw->data + raw->lockedSecretHmacOffset, nfc->data + 0x1B4, 0x20); memcpy(raw->data + raw->uuidOffset, nfc->data + 0x1D4, 0x8); memcpy(raw->data + raw->lockedSecretOffset, nfc->data + 0x1DC, 0xC); memcpy(raw->data + raw->keyGenSaltOffset, nfc->data + 0x1E8, 0x20); memcpy(raw->data + 0x208, nfc->data + 0x208, 0x14); } else { return false; } return true; } sint32 __NTAGDecryptData(void* decryptedData, const void* rawData) { StackAllocator<iosu::ccr_nfc::CCRNFCCryptData> nfcRawData, nfcInData, nfcOutData; if (!ccrNfcOpened) { gCcrNfcHandle = coreinit::IOS_Open("/dev/ccr_nfc", 0); } // Prepare nfc buffer nfcRawData->version = 0; memcpy(nfcRawData->data, rawData, 0x1C8); __NTAGRawDataToNfcData(nfcRawData.GetPointer(), nfcInData.GetPointer()); // Decrypt sint32 result = coreinit::IOS_Ioctl(gCcrNfcHandle, 2, nfcInData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData), nfcOutData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData)); // Unpack nfc buffer __NTAGNfcDataToRawData(nfcOutData.GetPointer(), nfcRawData.GetPointer()); memcpy(decryptedData, nfcRawData->data, 0x1C8); // Convert result if (result == CCR_NFC_INVALID_UNFIXED_INFOS) { return -0x2708; } else if (result == CCR_NFC_INVALID_LOCKED_SECRET) { return -0x2707; } return result; } sint32 __NTAGValidateHeaders(NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) { if (infoHeader->formatVersion != gFormatSettings.version || noftHeader->version != 0x1) { cemuLog_log(LogType::Force, "Invalid format version"); return -0x2710; } if (_swapEndianU32(noftHeader->magic) != 0x4E4F4654 /* 'NOFT' */ || _swapEndianU16(rwHeader->magic) != 0x5257 /* 'RW' */ || _swapEndianU16(roHeader->magic) != 0x524F /* 'RO' */) { cemuLog_log(LogType::Force, "Invalid header magic"); return -0x270F; } if (_swapEndianU32(rwHeader->makerCode) != gFormatSettings.makerCode || _swapEndianU32(roHeader->makerCode) != gFormatSettings.makerCode) { cemuLog_log(LogType::Force, "Invalid maker code"); return -0x270E; } if (infoHeader->formatVersion != 0 && (_swapEndianU32(rwHeader->identifyCode) != gFormatSettings.identifyCode || _swapEndianU32(roHeader->identifyCode) != gFormatSettings.identifyCode)) { cemuLog_log(LogType::Force, "Invalid identify code"); return -0x2709; } if (_swapEndianU16(rwHeader->size) + _swapEndianU16(roHeader->size) != 0x130) { cemuLog_log(LogType::Force, "Invalid data size"); return -0x270D; } return 0; } sint32 __NTAGParseHeaders(const uint8* data, NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) { memcpy(noftHeader, data + 0x20, sizeof(NTAGNoftHeader)); memcpy(infoHeader, data + 0x198, sizeof(NTAGInfoHeader)); cemu_assert(_swapEndianU16(infoHeader->rwHeaderOffset) + sizeof(NTAGAreaHeader) < 0x200); cemu_assert(_swapEndianU16(infoHeader->roHeaderOffset) + sizeof(NTAGAreaHeader) < 0x200); memcpy(rwHeader, data + _swapEndianU16(infoHeader->rwHeaderOffset), sizeof(NTAGAreaHeader)); memcpy(roHeader, data + _swapEndianU16(infoHeader->roHeaderOffset), sizeof(NTAGAreaHeader)); return __NTAGValidateHeaders(noftHeader, infoHeader, rwHeader, roHeader); } sint32 __NTAGParseData(void* rawData, void* rwData, void* roData, nfc::NFCUid* uid, uint32 lockedDataSize, NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) { uint8 decryptedData[0x1C8]; sint32 result = __NTAGDecryptData(decryptedData, rawData); if (result < 0) { return result; } result = __NTAGParseHeaders(decryptedData, noftHeader, infoHeader, rwHeader, roHeader); if (result < 0) { return result; } if (_swapEndianU16(roHeader->size) + 0x70 != lockedDataSize) { cemuLog_log(LogType::Force, "Invalid locked area size"); return -0x270C; } if (memcmp(infoHeader->uid.uid, uid->uid, sizeof(nfc::NFCUid)) != 0) { cemuLog_log(LogType::Force, "UID mismatch"); return -0x270B; } cemu_assert(_swapEndianU16(rwHeader->offset) + _swapEndianU16(rwHeader->size) < 0x200); cemu_assert(_swapEndianU16(roHeader->offset) + _swapEndianU16(roHeader->size) < 0x200); memcpy(rwData, decryptedData + _swapEndianU16(rwHeader->offset), _swapEndianU16(rwHeader->size)); memcpy(roData, decryptedData + _swapEndianU16(roHeader->offset), _swapEndianU16(roHeader->size)); return 0; } void __NTAGReadCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(chan, 0); ppcDefineParamS32(error, 1); ppcDefineParamPtr(uid, nfc::NFCUid, 2); ppcDefineParamU32(readOnly, 3); ppcDefineParamU32(dataSize, 4); ppcDefineParamPtr(data, void, 5); ppcDefineParamU32(lockedDataSize, 6); ppcDefineParamPtr(lockedData, void, 7); ppcDefineParamPtr(context, void, 8); uint8 rawData[0x1C8]{}; StackAllocator<NTAGData> readResult; StackAllocator<uint8, 0x1C8> rwData; StackAllocator<uint8, 0x1C8> roData; NTAGNoftHeader noftHeader; NTAGInfoHeader infoHeader; NTAGAreaHeader rwHeader; NTAGAreaHeader roHeader; readResult->readOnly = readOnly; error = __NTAGConvertNFCResult(error); if (error == 0) { memset(rwData.GetPointer(), 0, 0x1C8); memset(roData.GetPointer(), 0, 0x1C8); // Copy raw and locked data into a contigous buffer memcpy(rawData, data, dataSize); memcpy(rawData + dataSize, lockedData, lockedDataSize); error = __NTAGParseData(rawData, rwData.GetPointer(), roData.GetPointer(), uid, lockedDataSize, &noftHeader, &infoHeader, &rwHeader, &roHeader); if (error == 0) { memcpy(readResult->uid.uid, uid->uid, sizeof(uid->uid)); readResult->rwInfo.data = _swapEndianU32(rwData.GetMPTR()); readResult->roInfo.data = _swapEndianU32(roData.GetMPTR()); readResult->rwInfo.makerCode = rwHeader.makerCode; readResult->rwInfo.size = rwHeader.size; readResult->roInfo.makerCode = roHeader.makerCode; readResult->rwInfo.identifyCode = rwHeader.identifyCode; readResult->roInfo.identifyCode = roHeader.identifyCode; readResult->formatVersion = infoHeader.formatVersion; readResult->roInfo.size = roHeader.size; cemuLog_log(LogType::NTAG, "__NTAGReadCallback: {} {} {}", chan, error, context); PPCCoreCallback(gReadCallbacks[chan], chan, 0, readResult.GetPointer(), context); osLib_returnFromFunction(hCPU, 0); return; } } if (uid) { memcpy(readResult->uid.uid, uid->uid, sizeof(uid->uid)); } readResult->roInfo.size = 0; readResult->rwInfo.size = 0; readResult->roInfo.data = MPTR_NULL; readResult->formatVersion = 0; readResult->rwInfo.data = MPTR_NULL; cemuLog_log(LogType::NTAG, "__NTAGReadCallback: {} {} {}", chan, error, context); PPCCoreCallback(gReadCallbacks[chan], chan, error, readResult.GetPointer(), context); osLib_returnFromFunction(hCPU, 0); } sint32 NTAGRead(uint32 chan, uint32 timeout, nfc::NFCUid* uid, nfc::NFCUid* uidMask, MPTR callback, void* context) { cemu_assert(chan < 2); gReadCallbacks[chan] = callback; nfc::NFCUid _uid{}, _uidMask{}; if (uid && uidMask) { memcpy(&_uid, uid, sizeof(*uid)); memcpy(&_uidMask, uidMask, sizeof(*uidMask)); } sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadCallback), context); return __NTAGConvertNFCResult(result); } sint32 __NTAGEncryptData(void* encryptedData, const void* rawData) { StackAllocator<iosu::ccr_nfc::CCRNFCCryptData> nfcRawData, nfcInData, nfcOutData; if (!ccrNfcOpened) { gCcrNfcHandle = coreinit::IOS_Open("/dev/ccr_nfc", 0); } // Prepare nfc buffer nfcRawData->version = 0; memcpy(nfcRawData->data, rawData, 0x1C8); __NTAGRawDataToNfcData(nfcRawData.GetPointer(), nfcInData.GetPointer()); // Encrypt sint32 result = coreinit::IOS_Ioctl(gCcrNfcHandle, 1, nfcInData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData), nfcOutData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData)); // Unpack nfc buffer __NTAGNfcDataToRawData(nfcOutData.GetPointer(), nfcRawData.GetPointer()); memcpy(encryptedData, nfcRawData->data, 0x1C8); return result; } sint32 __NTAGPrepareWriteData(void* outBuffer, uint32 dataSize, const void* data, const void* tagData, NTAGNoftHeader* noftHeader, NTAGAreaHeader* rwHeader) { uint8 decryptedBuffer[0x1C8]; uint8 encryptedBuffer[0x1C8]; memcpy(decryptedBuffer, tagData, 0x1C8); // Fill the rest of the rw area with random data if (dataSize < _swapEndianU16(rwHeader->size)) { uint8 randomBuffer[0x1C8]; for (int i = 0; i < sizeof(randomBuffer); i++) { randomBuffer[i] = rand() & 0xFF; } memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset) + dataSize, randomBuffer, _swapEndianU16(rwHeader->size) - dataSize); } // Make sure the data fits into the rw area if (_swapEndianU16(rwHeader->size) < dataSize) { return -0x270D; } // Update write count (check for overflow) if ((_swapEndianU16(noftHeader->writeCount) & 0x7fff) == 0x7fff) { noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) & 0x8000); } else { noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) + 1); } memcpy(decryptedBuffer + 0x20, noftHeader, sizeof(NTAGNoftHeader)); memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset), data, dataSize); // Encrypt sint32 result = __NTAGEncryptData(encryptedBuffer, decryptedBuffer); if (result < 0) { return result; } memcpy(outBuffer, encryptedBuffer, _swapEndianU16(rwHeader->size) + 0x28); return 0; } void __NTAGWriteCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(chan, 0); ppcDefineParamS32(error, 1); ppcDefineParamPtr(context, void, 2); PPCCoreCallback(gWriteCallbacks[chan], chan, __NTAGConvertNFCResult(error), context); osLib_returnFromFunction(hCPU, 0); } void __NTAGReadBeforeWriteCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(chan, 0); ppcDefineParamS32(error, 1); ppcDefineParamPtr(uid, nfc::NFCUid, 2); ppcDefineParamU32(readOnly, 3); ppcDefineParamU32(dataSize, 4); ppcDefineParamPtr(data, void, 5); ppcDefineParamU32(lockedDataSize, 6); ppcDefineParamPtr(lockedData, void, 7); ppcDefineParamPtr(context, void, 8); uint8 rawData[0x1C8]{}; uint8 rwData[0x1C8]{}; uint8 roData[0x1C8]{}; NTAGNoftHeader noftHeader; NTAGInfoHeader infoHeader; NTAGAreaHeader rwHeader; NTAGAreaHeader roHeader; uint8 writeBuffer[0x1C8]{}; error = __NTAGConvertNFCResult(error); if (error == 0) { // Copy raw and locked data into a contigous buffer memcpy(rawData, data, dataSize); memcpy(rawData + dataSize, lockedData, lockedDataSize); error = __NTAGParseData(rawData, rwData, roData, uid, lockedDataSize, &noftHeader, &infoHeader, &rwHeader, &roHeader); if (error < 0) { cemuLog_log(LogType::Force, "Failed to parse data before write"); PPCCoreCallback(gWriteCallbacks[chan], chan, -0x3E3, context); osLib_returnFromFunction(hCPU, 0); return; } // Prepare data memcpy(rawData + _swapEndianU16(infoHeader.rwHeaderOffset), &rwHeader, sizeof(rwHeader)); memcpy(rawData + _swapEndianU16(infoHeader.roHeaderOffset), &roHeader, sizeof(roHeader)); memcpy(rawData + _swapEndianU16(roHeader.offset), roData, _swapEndianU16(roHeader.size)); error = __NTAGPrepareWriteData(writeBuffer, gWriteData[chan].size, gWriteData[chan].data, rawData, &noftHeader, &rwHeader); if (error < 0) { cemuLog_log(LogType::Force, "Failed to prepare write data"); PPCCoreCallback(gWriteCallbacks[chan], chan, -0x3E3, context); osLib_returnFromFunction(hCPU, 0); return; } // Write data to tag error = nfc::NFCWrite(chan, 200, &gWriteData[chan].uid, &gWriteData[chan].uidMask, _swapEndianU16(rwHeader.size) + 0x28, writeBuffer, RPLLoader_MakePPCCallable(__NTAGWriteCallback), context); if (error >= 0) { osLib_returnFromFunction(hCPU, 0); return; } error = __NTAGConvertNFCResult(error); } PPCCoreCallback(gWriteCallbacks[chan], chan, error, context); osLib_returnFromFunction(hCPU, 0); } sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) { cemu_assert(chan < 2); cemu_assert(rwSize < 0x1C8); gWriteCallbacks[chan] = callback; if (uid) { memcpy(&gWriteData[chan].uid, uid, sizeof(nfc::NFCUid)); } memset(&gWriteData[chan].uidMask, 0xff, sizeof(nfc::NFCUid)); gWriteData[chan].size = rwSize; memcpy(gWriteData[chan].data, rwData, rwSize); sint32 result = nfc::NFCRead(chan, timeout, &gWriteData[chan].uid, &gWriteData[chan].uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); return __NTAGConvertNFCResult(result); } sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) { cemu_assert(chan < 2); // TODO cemu_assert_debug(false); return NTAG_RESULT_INVALID; } class : public COSModule { public: std::string_view GetName() override { return "ntag"; } void RPLMapped() override { cafeExportRegister("ntag", NTAGInit, LogType::NTAG); cafeExportRegister("ntag", NTAGInitEx, LogType::NTAG); cafeExportRegister("ntag", NTAGShutdown, LogType::NTAG); cafeExportRegister("ntag", NTAGIsInit, LogType::Placeholder); // disabled logging, since this gets spammed cafeExportRegister("ntag", NTAGProc, LogType::Placeholder); // disabled logging, since this gets spammed cafeExportRegister("ntag", NTAGSetFormatSettings, LogType::NTAG); cafeExportRegister("ntag", NTAGSetTagDetectCallback, LogType::NTAG); cafeExportRegister("ntag", NTAGAbort, LogType::NTAG); cafeExportRegister("ntag", NTAGRead, LogType::NTAG); cafeExportRegister("ntag", NTAGWrite, LogType::NTAG); cafeExportRegister("ntag", NTAGFormat, LogType::NTAG); }; }s_COSntagModule; COSModule* GetModule() { return &s_COSntagModule; } } ================================================ FILE: src/Cafe/OS/libs/ntag/ntag.h ================================================ #pragma once #include "Cafe/OS/libs/nfc/nfc.h" #include "Cafe/OS/RPL/COSModule.h" #define NTAG_RESULT_SUCCESS (0) #define NTAG_RESULT_UNINITIALIZED (-0x3E7) #define NTAG_RESULT_INVALID_STATE (-0x3E6) #define NTAG_RESULT_NO_TAG (-0x3E5) #define NTAG_RESULT_INVALID (-0x3E1) #define NTAG_RESULT_UID_MISMATCH (-0x3DB) namespace ntag { struct NTAGFormatSettings { /* +0x00 */ uint8 version; /* +0x04 */ uint32 makerCode; /* +0x08 */ uint32 identifyCode; /* +0x0C */ uint8 reserved[0x1C]; }; static_assert(sizeof(NTAGFormatSettings) == 0x28); #pragma pack(1) struct NTAGNoftHeader { /* +0x00 */ uint32 magic; /* +0x04 */ uint8 version; /* +0x05 */ uint16 writeCount; /* +0x07 */ uint8 unknown; }; static_assert(sizeof(NTAGNoftHeader) == 0x8); #pragma pack() struct NTAGInfoHeader { /* +0x00 */ uint16 rwHeaderOffset; /* +0x02 */ uint16 rwSize; /* +0x04 */ uint16 roHeaderOffset; /* +0x06 */ uint16 roSize; /* +0x08 */ nfc::NFCUid uid; /* +0x0F */ uint8 formatVersion; }; static_assert(sizeof(NTAGInfoHeader) == 0x10); struct NTAGAreaHeader { /* +0x00 */ uint16 magic; /* +0x02 */ uint16 offset; /* +0x04 */ uint16 size; /* +0x06 */ uint16 padding; /* +0x08 */ uint32 makerCode; /* +0x0C */ uint32 identifyCode; }; static_assert(sizeof(NTAGAreaHeader) == 0x10); struct NTAGAreaInfo { /* +0x00 */ MPTR data; /* +0x04 */ uint16 size; /* +0x06 */ uint16 padding; /* +0x08 */ uint32 makerCode; /* +0x0C */ uint32 identifyCode; /* +0x10 */ uint8 reserved[0x20]; }; static_assert(sizeof(NTAGAreaInfo) == 0x30); struct NTAGData { /* +0x00 */ nfc::NFCUid uid; /* +0x07 */ uint8 readOnly; /* +0x08 */ uint8 formatVersion; /* +0x09 */ uint8 padding[3]; /* +0x0C */ NTAGAreaInfo rwInfo; /* +0x3C */ NTAGAreaInfo roInfo; /* +0x6C */ uint8 reserved[0x20]; }; static_assert(sizeof(NTAGData) == 0x8C); sint32 NTAGInit(uint32 chan); sint32 NTAGInitEx(uint32 chan); sint32 NTAGShutdown(uint32 chan); bool NTAGIsInit(uint32 chan); void NTAGProc(uint32 chan); void NTAGSetFormatSettings(NTAGFormatSettings* formatSettings); void NTAGSetTagDetectCallback(uint32 chan, MPTR callback, void* context); sint32 NTAGAbort(uint32 chan, MPTR callback, void* context); sint32 NTAGRead(uint32 chan, uint32 timeout, nfc::NFCUid* uid, nfc::NFCUid* uidMask, MPTR callback, void* context); sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context); sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context); COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/padscore/padscore.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/padscore/padscore.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/coreinit/coreinit_SystemInfo.h" #include "WindowSystem.h" #include "input/InputManager.h" // KPAD enum class KPAD_ERROR : sint32 { NONE = 0, NO_SAMPLE_DATA = -1, NO_CONTROLLER = -2, NOT_INITIALIZED = -5, }; // WPAD (Wii Controller stuff) enum WPADRumble { kStopRumble = 0, kStartRumble = 1, }; enum class WPADBatteryLevel : uint8 { FULL = 4, }; enum class WPADLed : uint8 { CHAN0 = (1 << 0), CHAN1 = (1 << 1), CHAN2 = (1 << 2), CHAN3 = (1 << 3), }; namespace padscore { enum WPADState_t { kWPADStateMaster = 0, kWPADStateShutdown, kWPADStateInitializing, kWPADStateAcquired, kWPADStateReleased, }; KPADUnifiedWpadStatus_t* g_kpad_ringbuffer = nullptr; uint32 g_kpad_ringbuffer_length = 0; bool g_wpad_callback_by_kpad = false; WPADState_t g_wpad_state = kWPADStateMaster; struct { SysAllocator<coreinit::OSAlarm_t> alarm; bool kpad_initialized = false; struct WPADData { MEMPTR<void> extension_callback; MEMPTR<void> connectCallback; MEMPTR<void> sampling_callback; MEMPTR<void> dpd_callback; bool dpd_enabled = true; bool disconnectCalled = false; BtnRepeat btn_repeat{}; } controller_data[InputManager::kMaxWPADControllers] = {}; int max_controllers = kWPADMaxControllers; // max bt controllers? } g_padscore; } #pragma region WPAD void padscoreExport_WPADGetStatus(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::InputAPI, "WPADGetStatus()"); uint32 status = 1; osLib_returnFromFunction(hCPU, status); } void padscoreExport_WPADGetBatteryLevel(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::InputAPI, "WPADGetBatteryLevel()"); osLib_returnFromFunction(hCPU, (uint32)WPADBatteryLevel::FULL); } void padscoreExport_WPADProbe(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(type, uint32be, 1); cemuLog_log(LogType::InputAPI, "WPADProbe({})", channel); if(const auto controller = InputManager::instance().get_wpad_controller(channel)) { if(type) *type = controller->get_device_type(); osLib_returnFromFunction(hCPU, WPAD_ERR_NONE); } else { if(type) *type = 253; osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); } } typedef struct { uint32be dpd; uint32be speaker; uint32be attach; uint32be lowBat; uint32be nearempty; betype<WPADBatteryLevel> batteryLevel; betype<WPADLed> led; uint8 protocol; uint8 firmware; }WPADInfo_t; static_assert(sizeof(WPADInfo_t) == 0x18); // unsure void padscoreExport_WPADGetInfoAsync(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamStructPtr(wpadInfo, WPADInfo_t, 1); ppcDefineParamMPTR(callbackFunc, 2); cemuLog_log(LogType::InputAPI, "WPADGetInfoAsync({}, 0x{:08x}, 0x{:08x})", channel, memory_getVirtualOffsetFromPointer(wpadInfo), callbackFunc); if (channel < InputManager::kMaxWPADControllers) { if (const auto controller = InputManager::instance().get_wpad_controller(channel)) { wpadInfo->dpd = FALSE; wpadInfo->speaker = FALSE; wpadInfo->attach = FALSE; wpadInfo->lowBat = FALSE; wpadInfo->nearempty = FALSE; wpadInfo->batteryLevel = WPADBatteryLevel::FULL; wpadInfo->led = WPADLed::CHAN0; if(callbackFunc != MPTR_NULL) coreinitAsyncCallback_add(callbackFunc, 2, channel, (uint32)KPAD_ERROR::NONE); osLib_returnFromFunction(hCPU, WPAD_ERR_NONE); return; } } else { debugBreakpoint(); } if (callbackFunc != MPTR_NULL) coreinitAsyncCallback_add(callbackFunc, 2, channel, (uint32)KPAD_ERROR::NO_CONTROLLER); osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); } void padscoreExport_WPADRead(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(wpadStatus, WPADStatus_t, 1); cemuLog_log(LogType::InputAPI, "WPADRead({}, {:x})", channel, fmt::ptr(wpadStatus)); if (channel < InputManager::kMaxWPADControllers) { if(const auto controller = InputManager::instance().get_wpad_controller(channel) ) { controller->WPADRead(wpadStatus); } } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); } void padscoreExport_WPADSetDataFormat(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamU32(fmt, 1); cemuLog_log(LogType::InputAPI, "WPADSetDataFormat({}, {})", channel, fmt); if (channel < InputManager::kMaxWPADControllers) { if (const auto controller = InputManager::instance().get_wpad_controller(channel)) controller->set_data_format((WPADDataFormat)fmt); } osLib_returnFromFunction(hCPU, 0); } void padscoreExport_WPADGetDataFormat(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); cemuLog_log(LogType::InputAPI, "WPADGetDataFormat({})", channel); sint32 dataFormat = kDataFormat_CORE; if (channel < InputManager::kMaxWPADControllers) { if (const auto controller = InputManager::instance().get_wpad_controller(channel)) dataFormat = controller->get_data_format(); } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, dataFormat); } void padscoreExport_WPADGetInfo(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamStructPtr(wpadInfo, WPADInfo_t, 1); cemuLog_log(LogType::InputAPI, "WPADGetInfo({}, 0x{:08x})", channel, fmt::ptr(wpadInfo)); if (channel < InputManager::kMaxWPADControllers) { if (const auto controller = InputManager::instance().get_wpad_controller(channel)) { wpadInfo->dpd = FALSE; wpadInfo->speaker = FALSE; wpadInfo->attach = FALSE; wpadInfo->lowBat = FALSE; wpadInfo->nearempty = FALSE; wpadInfo->batteryLevel = WPADBatteryLevel::FULL; wpadInfo->led = WPADLed::CHAN0; osLib_returnFromFunction(hCPU, WPAD_ERR_NONE); return; } } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); } void padscoreExport_WPADIsMotorEnabled(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::InputAPI, "WPADIsMotorEnabled()"); osLib_returnFromFunction(hCPU, TRUE); } void padscoreExport_WPADControlMotor(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamU32(command, 1); cemuLog_log(LogType::InputAPI, "WPADControlMotor({}, {})", channel, command); if (channel < InputManager::kMaxWPADControllers) { if (const auto controller = InputManager::instance().get_wpad_controller(channel)) { if( command == kStartRumble ) controller->start_rumble(); else controller->stop_rumble(); } } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } #pragma endregion #pragma region KPAD void padscoreExport_KPADGetUnifiedWpadStatus(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(status, KPADUnifiedWpadStatus_t, 1); ppcDefineParamU32(count, 2); cemuLog_log(LogType::InputAPI, "KPADGetUnifiedWpadStatus({}, 0x{:08x}, 0x{:x})", channel, MEMPTR<void>(status).GetMPTR(), count); if (channel < InputManager::kMaxWPADControllers) { memset(status, 0x00, sizeof(KPADUnifiedWpadStatus_t) * count); if (const auto controller = InputManager::instance().get_wpad_controller(channel)) { switch (controller->get_data_format()) { case WPAD_FMT_CORE: case WPAD_FMT_CORE_ACC: case WPAD_FMT_CORE_ACC_DPD: { status->fmt = controller->get_data_format(); controller->WPADRead(&status->u.core); break; } default: debugBreakpoint(); } } else { status->u.core.err = WPAD_ERR_NO_CONTROLLER; } } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void padscoreExport_KPADSetBtnRepeat(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); float delaySec = hCPU->fpr[1].fpr; float pulseSec = hCPU->fpr[2].fpr; cemuLog_log(LogType::InputAPI, "KPADSetBtnRepeat({}, {}, {})", channel, delaySec, pulseSec); if (channel < InputManager::kMaxWPADControllers) { padscore::g_padscore.controller_data[channel].btn_repeat = { (int)delaySec, (int)pulseSec }; } osLib_returnFromFunction(hCPU, 0); } void padscoreExport_KPADSetSamplingCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamMPTR(callback, 1); cemuLog_log(LogType::InputAPI, "KPADSetSamplingCallback({}, 0x{:x})", channel, callback); if (channel >= InputManager::kMaxWPADControllers) { debugBreakpoint(); osLib_returnFromFunction(hCPU, MPTR_NULL); return; } const auto old_callback = padscore::g_padscore.controller_data[channel].sampling_callback; padscore::g_padscore.controller_data[channel].sampling_callback = callback; osLib_returnFromFunction(hCPU, old_callback.GetMPTR()); } void padscoreExport_WPADControlDpd(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamU32(command, 1); ppcDefineParamMPTR(callback, 2); cemuLog_log(LogType::InputAPI, "WPADControlDpd({}, {}, 0x{:x})", channel, command, callback); if (channel < InputManager::kMaxWPADControllers) { if (const auto controller = InputManager::instance().get_wpad_controller(channel)) { padscore::g_padscore.controller_data[channel].dpd_callback = callback; if (callback) { coreinitAsyncCallback_add(callback, 2, channel, WPAD_ERR_NONE); } osLib_returnFromFunction(hCPU, WPAD_ERR_NONE); return; } } osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); } void padscoreExport_WPADSetExtensionCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamMPTR(callback, 1); cemuLog_log(LogType::InputAPI, "WPADSetExtensionCallback({}, 0x{:x})", channel, callback); if (channel >= InputManager::kMaxWPADControllers) { debugBreakpoint(); osLib_returnFromFunction(hCPU, MPTR_NULL); return; } const auto old_callback = padscore::g_padscore.controller_data[channel].extension_callback; padscore::g_padscore.controller_data[channel].extension_callback = callback; osLib_returnFromFunction(hCPU, old_callback.GetMPTR()); } void padscoreExport_KPADSetConnectCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamMPTR(callback, 1); cemuLog_log(LogType::InputAPI, "KPADSetConnectCallback({}, 0x{:x})",channel, callback); if (channel >= InputManager::kMaxWPADControllers) { debugBreakpoint(); osLib_returnFromFunction(hCPU, MPTR_NULL); return; } const auto old_callback = padscore::g_padscore.controller_data[channel].connectCallback; padscore::g_padscore.controller_data[channel].connectCallback = callback; osLib_returnFromFunction(hCPU, old_callback.GetMPTR()); } uint64 g_kpadLastRead[InputManager::kMaxWPADControllers] = {0}; bool g_kpadIsInited = true; sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, betype<KPAD_ERROR>* errResult) { if (channel >= InputManager::kMaxWPADControllers) { debugBreakpoint(); return 0; } if (g_kpadIsInited == false) { if (errResult) *errResult = KPAD_ERROR::NOT_INITIALIZED; return 0; } const auto controller = InputManager::instance().get_wpad_controller(channel); if (!controller) { if (errResult) *errResult = KPAD_ERROR::NO_CONTROLLER; return 0; } // On console new input samples are only received every few ms and calling KPADRead(Ex) clears the internal queue regardless of length value // thus calling KPADRead(Ex) again too soon on the same channel will result in no data being returned // Games that depend on this: Affordable Space Adventures uint64 currentTime = coreinit::OSGetTime(); uint64 timeDif = currentTime - g_kpadLastRead[channel]; if(length == 0 || timeDif < coreinit::EspressoTime::ConvertNsToTimerTicks(1000000)) { if (errResult) *errResult = KPAD_ERROR::NO_SAMPLE_DATA; return 0; } g_kpadLastRead[channel] = currentTime; memset(samplingBufs, 0x00, sizeof(KPADStatus_t)); samplingBufs->wpadErr = WPAD_ERR_NONE; samplingBufs->data_format = controller->get_data_format(); samplingBufs->devType = controller->get_device_type(); if(!WindowSystem::InputConfigWindowHasFocus()) { const auto btn_repeat = padscore::g_padscore.controller_data[channel].btn_repeat; controller->KPADRead(*samplingBufs, btn_repeat); } if (errResult) *errResult = KPAD_ERROR::NONE; return 1; } void padscoreExport_KPADReadEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(kpadStatus, KPADStatus_t, 1); ppcDefineParamU32(length, 2); ppcDefineParamPtr(errResult, betype<KPAD_ERROR>, 3); cemuLog_log(LogType::InputAPI, "KPADReadEx({}, 0x{:x})", channel, length); sint32 samplesRead = _KPADRead(channel, kpadStatus, length, errResult); osLib_returnFromFunction(hCPU, samplesRead); } void padscoreExport_KPADRead(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(kpadStatus, KPADStatus_t, 1); ppcDefineParamU32(length, 2); cemuLog_log(LogType::InputAPI, "KPADRead({}, 0x{:x})", channel, length); sint32 samplesRead = _KPADRead(channel, kpadStatus, length, nullptr); osLib_returnFromFunction(hCPU, samplesRead); } #pragma endregion namespace padscore { void export_KPADEnableDPD(PPCInterpreter_t* hCPU) { ppcDefineParamS32(channel, 0); cemuLog_log(LogType::InputAPI, "KPADEnableDPD({})", channel); cemu_assert_debug(0 <= channel && channel < InputManager::kMaxWPADControllers); if(const auto controller = InputManager::instance().get_wpad_controller(channel)) { if (controller->get_data_format() != kDataFormat_FREESTYLE && controller->get_data_format() != 0x1F) g_padscore.controller_data[channel].dpd_enabled = true; } osLib_returnFromFunction(hCPU, 0); } void export_KPADGetMplsWorkSize(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::InputAPI, "KPADGetMplsWorkSize()"); osLib_returnFromFunction(hCPU, 0x5FE0); } void KPADInitEx(KPADUnifiedWpadStatus_t ring_buffer[], uint32 length) { if (g_padscore.kpad_initialized) return; for (uint32 i = 0; i < InputManager::kMaxWPADControllers; i++) { g_padscore.controller_data[i].dpd_enabled = true; } g_kpad_ringbuffer = ring_buffer; g_kpad_ringbuffer_length = length; g_padscore.kpad_initialized = true; } void export_KPADInit(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::InputAPI, "KPADInit()"); KPADInitEx(nullptr, 0); osLib_returnFromFunction(hCPU, 0); } void export_KPADInitEx(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(ring_buffer, KPADUnifiedWpadStatus_t, 0); ppcDefineParamU32(length, 1); cemuLog_log(LogType::InputAPI, "KPADInitEx(0x{:08x}, 0x{:x})", ring_buffer.GetMPTR(), length); KPADInitEx(ring_buffer.GetPtr(), length); osLib_returnFromFunction(hCPU, 0); } void WPADSetCallbackByKPAD(bool state) { g_wpad_callback_by_kpad = state; } void export_WPADSetCallbackByKPAD(PPCInterpreter_t* hCPU) { ppcDefineParamU32(state, 0); cemuLog_log(LogType::InputAPI, "WPADSetCallbackByKPAD({})", state); WPADSetCallbackByKPAD(state); osLib_returnFromFunction(hCPU, 0); } void export_KPADGetMaxControllers(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::InputAPI, "KPADGetMaxControllers()"); sint32 max_controllers = g_padscore.max_controllers; osLib_returnFromFunction(hCPU, max_controllers); } bool WPADIsUsedCallbackByKPAD() { return g_wpad_callback_by_kpad == TRUE; } void* WPADSetSamplingCallback(sint32 channel, void* callback) { cemu_assert_debug(0 <= channel && channel < kKPADMaxControllers); const auto result = g_padscore.controller_data[channel].sampling_callback; g_padscore.controller_data[channel].sampling_callback = callback; return result.GetPtr(); } void export_KPADSetMaxControllers(PPCInterpreter_t* hCPU) { ppcDefineParamU32(new_max_count, 0); cemuLog_log(LogType::InputAPI, "KPADSetMaxControllers({})", new_max_count); if (new_max_count != kKPADMaxControllers && new_max_count != kWPADMaxControllers) { debugBreakpoint(); osLib_returnFromFunction(hCPU, -4); return; } const uint32 max_controllers = g_padscore.max_controllers; if (max_controllers == new_max_count) { osLib_returnFromFunction(hCPU, 0); return; } WPADSetCallbackByKPAD(FALSE); for (sint32 i = 0; i < kKPADMaxControllers; i++) { //WPADSetSamplingCallback(i, 0); TODO } g_padscore.max_controllers = new_max_count; WPADSetCallbackByKPAD(true); osLib_returnFromFunction(hCPU, new_max_count); } void export_WPADInit() { if (g_wpad_state != kWPADStateShutdown) { if (g_wpad_state == kWPADStateInitializing || g_wpad_state == kWPADStateAcquired) return; } g_wpad_state = kWPADStateInitializing; } enum class WPADStatus : sint32 { Success = 0, NoController = -1, Busy = -2, }; WPADStatus WPADIsMplsAttached(sint32 index, uint32be* attached, void* callback) { if (index >= kKPADMaxControllers) return WPADStatus::NoController; const auto controller = InputManager::instance().get_wpad_controller(index); *attached = controller && controller->is_mpls_attached(); if (callback) PPCCoreCallback(MEMPTR(callback), index, controller ? WPADStatus::Success : WPADStatus::NoController); return WPADStatus::Success; } struct WPADAcc { sint32be x; sint32be y; sint32be z; }; void WPADGetAccGravityUnit(sint32 index, uint32 type, WPADAcc* acc) { if (index >= kKPADMaxControllers) return; // default values for wiimote usually are around 99 acc->x = 99; acc->y = 99; acc->z = 99; } #pragma endregion void TickFunction(PPCInterpreter_t* hCPU) { auto& instance = InputManager::instance(); // test for connected/disconnected controllers for (auto i = 0; i < InputManager::kMaxWPADControllers; ++i) { if (g_padscore.controller_data[i].connectCallback) { if(!g_padscore.controller_data[i].disconnectCalled) { g_padscore.controller_data[i].disconnectCalled = true; cemuLog_log(LogType::InputAPI, "Calling WPADConnectCallback({}, {})", i, WPAD_ERR_NO_CONTROLLER); PPCCoreCallback(g_padscore.controller_data[i].connectCallback, i, WPAD_ERR_NO_CONTROLLER); continue; } if (const auto controller = instance.get_wpad_controller(i)) { if (controller->m_status == WPADController::ConnectCallbackStatus::ReportDisconnect || controller->was_home_button_down()) // fixed! { controller->m_status = WPADController::ConnectCallbackStatus::ReportConnect; cemuLog_log(LogType::InputAPI, "Calling WPADConnectCallback({}, {})", i, WPAD_ERR_NO_CONTROLLER); PPCCoreCallback(g_padscore.controller_data[i].connectCallback, i, WPAD_ERR_NO_CONTROLLER); } else if (controller->m_status == WPADController::ConnectCallbackStatus::ReportConnect) { controller->m_status = WPADController::ConnectCallbackStatus::None; cemuLog_log(LogType::InputAPI, "Calling WPADConnectCallback({}, {})", i, WPAD_ERR_NONE); PPCCoreCallback(g_padscore.controller_data[i].connectCallback, i, WPAD_ERR_NONE); } } } } // test for connected/disconnected extensions for (auto i = 0; i < InputManager::kMaxWPADControllers; ++i) { if (g_padscore.controller_data[i].extension_callback) { if (const auto controller = instance.get_wpad_controller(i)) { if (controller->m_extension_status == WPADController::ConnectCallbackStatus::ReportConnect) { controller->m_extension_status = WPADController::ConnectCallbackStatus::None; cemuLog_log(LogType::InputAPI, "Calling WPADextensionCallback({})", i); PPCCoreCallback(g_padscore.controller_data[i].extension_callback, i, controller->get_device_type()); } } } } // call sampling callback for (auto i = 0; i < InputManager::kMaxWPADControllers; ++i) { if (g_padscore.controller_data[i].sampling_callback) { if (const auto controller = instance.get_wpad_controller(i)) { cemuLog_log(LogType::InputAPI, "Calling WPADsamplingCallback({})", i); PPCCoreCallback(g_padscore.controller_data[i].sampling_callback, i); } } } osLib_returnFromFunction(hCPU, 0); } void start() { OSCreateAlarm(&g_padscore.alarm); const uint64 start_tick = coreinit::OSGetTime(); const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction); OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler); } class : public COSModule { public: std::string_view GetName() override { return "padscore"; } void RPLMapped() override { cafeExportRegister("padscore", WPADIsMplsAttached, LogType::InputAPI); cafeExportRegister("padscore", WPADGetAccGravityUnit, LogType::InputAPI); // wpad //osLib_addFunction("padscore", "WPADInit", padscore::export_WPADInit); // kpad osLib_addFunction("padscore", "KPADSetMaxControllers", padscore::export_KPADSetMaxControllers); osLib_addFunction("padscore", "KPADGetMaxControllers", padscore::export_KPADGetMaxControllers); osLib_addFunction("padscore", "KPADEnableDPD", padscore::export_KPADEnableDPD); osLib_addFunction("padscore", "KPADGetMplsWorkSize", padscore::export_KPADGetMplsWorkSize); osLib_addFunction("padscore", "KPADInit", padscore::export_KPADInit); osLib_addFunction("padscore", "KPADInitEx", padscore::export_KPADInitEx); osLib_addFunction("padscore", "KPADSetConnectCallback", padscoreExport_KPADSetConnectCallback); osLib_addFunction("padscore", "KPADReadEx", padscoreExport_KPADReadEx); osLib_addFunction("padscore", "KPADRead", padscoreExport_KPADRead); osLib_addFunction("padscore", "KPADGetUnifiedWpadStatus", padscoreExport_KPADGetUnifiedWpadStatus); osLib_addFunction("padscore", "KPADSetSamplingCallback", padscoreExport_KPADSetSamplingCallback); osLib_addFunction("padscore", "KPADSetBtnRepeat", padscoreExport_KPADSetBtnRepeat); osLib_addFunction("padscore", "WPADGetBatteryLevel", padscoreExport_WPADGetBatteryLevel); osLib_addFunction("padscore", "WPADControlMotor", padscoreExport_WPADControlMotor); osLib_addFunction("padscore", "WPADIsMotorEnabled", padscoreExport_WPADIsMotorEnabled); osLib_addFunction("padscore", "WPADGetStatus", padscoreExport_WPADGetStatus); osLib_addFunction("padscore", "WPADProbe", padscoreExport_WPADProbe); osLib_addFunction("padscore", "WPADGetInfoAsync", padscoreExport_WPADGetInfoAsync); osLib_addFunction("padscore", "WPADGetInfo", padscoreExport_WPADGetInfo); osLib_addFunction("padscore", "WPADSetConnectCallback", padscoreExport_KPADSetConnectCallback); osLib_addFunction("padscore", "WPADSetDataFormat", padscoreExport_WPADSetDataFormat); osLib_addFunction("padscore", "WPADGetDataFormat", padscoreExport_WPADGetDataFormat); osLib_addFunction("padscore", "WPADRead", padscoreExport_WPADRead); osLib_addFunction("padscore", "WPADSetExtensionCallback", padscoreExport_WPADSetExtensionCallback); osLib_addFunction("padscore", "WPADSetSamplingCallback", padscoreExport_KPADSetSamplingCallback); osLib_addFunction("padscore", "WPADControlDpd", padscoreExport_WPADControlDpd); osLib_addFunction("padscore", "WPADSetCallbackByKPAD", padscore::export_WPADSetCallbackByKPAD); }; }s_COSCoreinitModule; COSModule* GetModule() { return &s_COSCoreinitModule; } } ================================================ FILE: src/Cafe/OS/libs/padscore/padscore.h ================================================ #pragma once #include "util/math/vector3.h" #include "Cafe/OS/RPL/COSModule.h" namespace padscore { void start(); COSModule* GetModule(); } constexpr int kWPADMaxControllers = 4; constexpr int kKPADMaxControllers = 7; #define WPAD_ERR_NONE 0 #define WPAD_ERR_NO_CONTROLLER -1 #define WPAD_ERR_BUSY -2 #define WPAD_ERR_INVALID -4 #define WPAD_PRESS_UNITS 4 #define WPAD_FMT_CORE 0 #define WPAD_FMT_CORE_ACC 1 #define WPAD_FMT_CORE_ACC_DPD 2 typedef struct { float x; float y; float z; }padVec3D_t; static_assert(sizeof(padVec3D_t) == 0xC); typedef struct beVec3D { float32be x; float32be y; float32be z; beVec3D() = default; beVec3D(const Vector3<float>& v) : x(v.x), y(v.y), z(v.z) {} beVec3D(const float32be& x, const float32be& y, const float32be& z) : x(x), y(y), z(z) {} }beVec3D_t; static_assert(sizeof(beVec3D_t) == 0xC); typedef struct { float x; float y; }padVec2D_t; static_assert(sizeof(padVec2D_t) == 0x8); typedef struct { float32be x; float32be y; }beVec2D_t; static_assert(sizeof(beVec2D_t) == 0x8); union KPADEXStatus_t { struct { beVec2D_t stick; beVec3D_t acc; float32be accValue; float32be accSpeed; }fs; struct { uint32be hold; uint32be trig; uint32be release; beVec2D_t lstick; beVec2D_t rstick; float32be ltrigger; float32be rtrigger; }cl; struct { uint32be hold; uint32be trig; uint32be release; beVec2D_t lstick; beVec2D_t rstick; uint32be charge; uint32be cable; }uc; struct { uint32be hold; uint32be trig; uint32be release; }cm; uint8 _padding[0x50]; }; static_assert(sizeof(KPADEXStatus_t) == 0x50); struct KPADMPDir_t { padVec3D_t X; padVec3D_t Y; padVec3D_t Z; }; static_assert(sizeof(KPADMPDir_t) == 0x24); struct KPADMPStatus_t { padVec3D_t mpls; padVec3D_t angle; KPADMPDir_t dir; }; static_assert(sizeof(KPADMPStatus_t) == 0x3C); struct KPADStatus_t { uint32be hold; uint32be trig; uint32be release; beVec3D_t acc; float32be acc_value; float32be acc_speed; beVec2D_t pos; beVec2D_t vec; float32be speed; beVec2D_t horizon; beVec2D_t horiVec; float32be horiSpeed; float32be dist; float32be distVec; float32be distSpeed; beVec2D_t accVertical; uint8 devType; sint8 wpadErr; sint8 dpd_valid_fg; uint8 data_format; KPADEXStatus_t ex_status; KPADMPStatus_t mpls; uint8 _unused[4]; }; static_assert(sizeof(KPADStatus_t) == 0xF0); #define WPAD_DPD_MAX_OBJECTS (4) struct DPDObject_t { uint16be x; uint16be y; uint16be size; uint8 traceId; uint8 padding; }; static_assert(sizeof(DPDObject_t) == 0x8); struct WPADStatus_t { uint16be button; uint16be accX; uint16be accY; uint16be accZ; DPDObject_t obj[WPAD_DPD_MAX_OBJECTS]; uint8 dev; sint8 err; }; static_assert(sizeof(WPADStatus_t) == 0x2A); struct WPADFSStatus_t : WPADStatus_t { uint16be fsAccX; uint16be fsAccY; uint16be fsAccZ; sint8 fsStickX; sint8 fsStickY; }; struct WPADCLStatus_t : WPADStatus_t { uint16be clButton; uint16be clLStickX; uint16be clLStickY; uint16be clRStickX; uint16be clRStickY; uint8 clTriggerL; uint8 clTriggerR; }; struct WPADUCStatus_t : WPADStatus_t { uint8 padding1[2]; // unsure uint32be ucButton; uint16be ucLStickX; uint16be ucLStickY; uint16be ucRStickX; uint16be ucRStickY; uint32be charge; uint32be cable; }; static_assert(sizeof(WPADUCStatus_t) == 0x40); struct WPADTRStatus_t : WPADStatus_t { uint16 trButton; uint8 brake; uint8 mascon; }; struct WPADBLStatus_t : WPADStatus_t { uint16be press[WPAD_PRESS_UNITS]; sint8 temp; uint8 battery; } ; static_assert(sizeof(WPADBLStatus_t) == 0x34); struct WPADMPStatus_t : WPADStatus_t { union { // for Nunchuk struct { uint16be fsAccX; uint16be fsAccY; uint16be fsAccZ; sint8 fsStickX; sint8 fsStickY; }fs; // for Classic struct { uint16be clButton; uint16be clLStickX; uint16be clLStickY; uint16be clRStickX; uint16be clRStickY; uint8 clTriggerL; uint8 clTriggerR; }cl; }status; uint8 stat; uint8 _ukn; uint16be pitch; uint16be yaw; uint16be roll; }; static_assert(sizeof(WPADMPStatus_t) == 0x3E); typedef struct KPADUnifiedWpadStatus { union { WPADStatus_t core; WPADFSStatus_t fs; WPADCLStatus_t cl; WPADTRStatus_t tr; WPADBLStatus_t bl; WPADMPStatus_t mp; }u; uint8 fmt; uint8 padding; uint32 _40; // padding? }KPADUnifiedWpadStatus_t; static_assert(sizeof(KPADUnifiedWpadStatus_t) == 0x44); ================================================ FILE: src/Cafe/OS/libs/proc_ui/proc_ui.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" #include "Cafe/OS/libs/coreinit/coreinit_Misc.h" #include "Cafe/OS/libs/coreinit/coreinit_Memory.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit_FG.h" #include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" #include "Cafe/OS/libs/gx2/GX2_Misc.h" #include "Cafe/OS/RPL/rpl.h" #include "Common/CafeString.h" #include "proc_ui.h" // proc_ui is a utility wrapper to help apps with the transition between foreground and background // some games (like Xenoblades Chronicles X) bypass proc_ui.rpl and listen to OSGetSystemMessageQueue() directly using namespace coreinit; namespace proc_ui { enum class ProcUICoreThreadCommand { AcquireForeground = 0x0, ReleaseForeground = 0x1, Exit = 0x2, NetIoStart = 0x3, NetIoStop = 0x4, HomeButtonDenied = 0x5, Initial = 0x6 }; struct ProcUIInternalCallbackEntry { coreinit::OSAlarm_t alarm; uint64be tickDelay; MEMPTR<void> funcPtr; MEMPTR<void> userParam; sint32be priority; MEMPTR<ProcUIInternalCallbackEntry> next; }; static_assert(sizeof(ProcUIInternalCallbackEntry) == 0x70); struct ProcUICallbackList { MEMPTR<ProcUIInternalCallbackEntry> first; }; static_assert(sizeof(ProcUICallbackList) == 0x4); std::atomic_bool s_isInitialized; bool s_isInForeground; bool s_isInShutdown; bool s_isForegroundProcess; bool s_previouslyWasBlocking; ProcUIStatus s_currentProcUIStatus; MEMPTR<void> s_saveCallback; // no param and no return value, set by ProcUIInit() MEMPTR<void> s_saveCallbackEx; // with custom param and return value, set by ProcUIInitEx() MEMPTR<void> s_saveCallbackExUserParam; MEMPTR<coreinit::OSMessageQueue> s_systemMessageQueuePtr; SysAllocator<coreinit::OSEvent> s_eventStateMessageReceived; SysAllocator<coreinit::OSEvent> s_eventWaitingBeforeReleaseForeground; SysAllocator<coreinit::OSEvent> s_eventBackgroundThreadGotMessage; // procUI core threads uint32 s_coreThreadStackSize; bool s_coreThreadsCreated; std::atomic<ProcUICoreThreadCommand> s_commandForCoreThread; SysAllocator<OSThread_t> s_coreThreadArray[Espresso::CORE_COUNT]; MEMPTR<void> s_coreThreadStackPerCore[Espresso::CORE_COUNT]; SysAllocator<CafeString<22>> s_coreThread0NameBuffer; SysAllocator<CafeString<22>> s_coreThread1NameBuffer; SysAllocator<CafeString<22>> s_coreThread2NameBuffer; SysAllocator<coreinit::OSEvent> s_eventCoreThreadsNewCommandReady; SysAllocator<coreinit::OSEvent> s_eventCoreThreadsCommandDone; SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousA; SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousB; SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousC; // background thread MEMPTR<void> s_backgroundThreadStack; SysAllocator<OSThread_t> s_backgroundThread; // user defined heap MEMPTR<void> s_memoryPoolHeapPtr; MEMPTR<void> s_memAllocPtr; MEMPTR<void> s_memFreePtr; // draw done release bool s_drawDoneReleaseCalled; // memory storage MEMPTR<void> s_bucketStorageBasePtr; MEMPTR<void> s_mem1StorageBasePtr; // callbacks ProcUICallbackList s_callbacksType0_AcquireForeground[Espresso::CORE_COUNT]; ProcUICallbackList s_callbacksType1_ReleaseForeground[Espresso::CORE_COUNT]; ProcUICallbackList s_callbacksType2_Exit[Espresso::CORE_COUNT]; ProcUICallbackList s_callbacksType3_NetIoStart[Espresso::CORE_COUNT]; ProcUICallbackList s_callbacksType4_NetIoStop[Espresso::CORE_COUNT]; ProcUICallbackList s_callbacksType5_HomeButtonDenied[Espresso::CORE_COUNT]; ProcUICallbackList* const s_CallbackTables[stdx::to_underlying(ProcUICallbackId::COUNT)] = {s_callbacksType0_AcquireForeground, s_callbacksType1_ReleaseForeground, s_callbacksType2_Exit, s_callbacksType3_NetIoStart, s_callbacksType4_NetIoStop, s_callbacksType5_HomeButtonDenied}; ProcUICallbackList s_backgroundCallbackList; // driver bool s_driverIsActive; uint32be s_driverArgUkn1; uint32be s_driverArgUkn2; bool s_driverInBackground; SysAllocator<OSDriverInterface> s_ProcUIDriver; SysAllocator<CafeString<16>> s_ProcUIDriverName; void* _AllocMem(uint32 size) { MEMPTR<void> r{PPCCoreCallback(s_memAllocPtr, size)}; return r.GetPtr(); } void _FreeMem(void* ptr) { PPCCoreCallback(s_memFreePtr.GetMPTR(), ptr); } void ClearCallbacksWithoutMemFree() { for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) { for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++) s_CallbackTables[i][coreIndex].first = nullptr; } s_backgroundCallbackList.first = nullptr; } void ShutdownThreads() { if ( !s_coreThreadsCreated) return; s_commandForCoreThread = ProcUICoreThreadCommand::Initial; coreinit::OSMemoryBarrier(); OSSignalEvent(&s_eventCoreThreadsNewCommandReady); for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) { coreinit::OSJoinThread(&s_coreThreadArray[coreIndex], nullptr); for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++) { s_CallbackTables[i][coreIndex].first = nullptr; // memory is not cleanly released? } } OSResetEvent(&s_eventCoreThreadsNewCommandReady); for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) { _FreeMem(s_coreThreadStackPerCore[coreIndex]); s_coreThreadStackPerCore[coreIndex] = nullptr; } _FreeMem(s_backgroundThreadStack); s_backgroundThreadStack = nullptr; s_backgroundCallbackList.first = nullptr; // memory is not cleanly released? s_coreThreadsCreated = false; } void DoCallbackChain(ProcUIInternalCallbackEntry* entry) { while (entry) { uint32 r = PPCCoreCallback(entry->funcPtr, entry->userParam); if ( r ) cemuLog_log(LogType::APIErrors, "ProcUI: Callback returned error {}\n", r); entry = entry->next; } } void AlarmDoBackgroundCallback(PPCInterpreter_t* hCPU) { coreinit::OSAlarm_t* arg = MEMPTR<coreinit::OSAlarm_t>(hCPU->gpr[3]); ProcUIInternalCallbackEntry* entry = (ProcUIInternalCallbackEntry*)arg; uint32 r = PPCCoreCallback(entry->funcPtr, entry->userParam); if ( r ) cemuLog_log(LogType::APIErrors, "ProcUI: Background callback returned error {}\n", r); osLib_returnFromFunction(hCPU, 0); // return type is void } void StartBackgroundAlarms() { ProcUIInternalCallbackEntry* cb = s_backgroundCallbackList.first; while(cb) { coreinit::OSCreateAlarm(&cb->alarm); uint64 currentTime = coreinit::OSGetTime(); coreinit::OSSetPeriodicAlarm(&cb->alarm, currentTime, cb->tickDelay, RPLLoader_MakePPCCallable(AlarmDoBackgroundCallback)); cb = cb->next; } } void CancelBackgroundAlarms() { ProcUIInternalCallbackEntry* entry = s_backgroundCallbackList.first; while (entry) { OSCancelAlarm(&entry->alarm); entry = entry->next; } } void ProcUICoreThread(PPCInterpreter_t* hCPU) { uint32 coreIndex = hCPU->gpr[3]; cemu_assert_debug(coreIndex == OSGetCoreId()); while (true) { OSWaitEvent(&s_eventCoreThreadsNewCommandReady); ProcUIInternalCallbackEntry* cbChain = nullptr; cemuLog_logDebug(LogType::Force, "ProcUI: Core {} got command {}", coreIndex, (uint32)s_commandForCoreThread.load()); auto cmd = s_commandForCoreThread.load(); switch(cmd) { case ProcUICoreThreadCommand::Initial: { // signal to shut down thread osLib_returnFromFunction(hCPU, 0); return; } case ProcUICoreThreadCommand::AcquireForeground: cbChain = s_callbacksType0_AcquireForeground[coreIndex].first; break; case ProcUICoreThreadCommand::ReleaseForeground: cbChain = s_callbacksType1_ReleaseForeground[coreIndex].first; break; case ProcUICoreThreadCommand::Exit: cbChain = s_callbacksType2_Exit[coreIndex].first; break; case ProcUICoreThreadCommand::NetIoStart: cbChain = s_callbacksType3_NetIoStart[coreIndex].first; break; case ProcUICoreThreadCommand::NetIoStop: cbChain = s_callbacksType4_NetIoStop[coreIndex].first; break; case ProcUICoreThreadCommand::HomeButtonDenied: cbChain = s_callbacksType5_HomeButtonDenied[coreIndex].first; break; default: cemu_assert_suspicious(); // invalid command } if(cmd == ProcUICoreThreadCommand::AcquireForeground) { if (coreIndex == 2) CancelBackgroundAlarms(); cbChain = s_callbacksType0_AcquireForeground[coreIndex].first; } else if(cmd == ProcUICoreThreadCommand::ReleaseForeground) { if (coreIndex == 2) StartBackgroundAlarms(); cbChain = s_callbacksType1_ReleaseForeground[coreIndex].first; } DoCallbackChain(cbChain); OSWaitRendezvous(&s_coreThreadRendezvousA, 7); if ( !coreIndex ) { OSInitRendezvous(&s_coreThreadRendezvousC); OSResetEvent(&s_eventCoreThreadsNewCommandReady); } OSWaitRendezvous(&s_coreThreadRendezvousB, 7); if ( !coreIndex ) { OSInitRendezvous(&s_coreThreadRendezvousA); OSSignalEvent(&s_eventCoreThreadsCommandDone); } OSWaitRendezvous(&s_coreThreadRendezvousC, 7); if ( !coreIndex ) OSInitRendezvous(&s_coreThreadRendezvousB); if (cmd == ProcUICoreThreadCommand::ReleaseForeground) { OSWaitEvent(&s_eventWaitingBeforeReleaseForeground); OSReleaseForeground(); } } osLib_returnFromFunction(hCPU, 0); } void RecreateProcUICoreThreads() { ShutdownThreads(); for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) { s_coreThreadStackPerCore[coreIndex] = _AllocMem(s_coreThreadStackSize); } s_backgroundThreadStack = _AllocMem(s_coreThreadStackSize); for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) { __OSCreateThreadType(&s_coreThreadArray[coreIndex], RPLLoader_MakePPCCallable(ProcUICoreThread), coreIndex, nullptr, (uint8*)s_coreThreadStackPerCore[coreIndex].GetPtr() + s_coreThreadStackSize, s_coreThreadStackSize, 16, (1<<coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER); OSResumeThread(&s_coreThreadArray[coreIndex]); } s_coreThread0NameBuffer->assign("{SYS ProcUI Core 0}"); s_coreThread1NameBuffer->assign("{SYS ProcUI Core 1}"); s_coreThread2NameBuffer->assign("{SYS ProcUI Core 2}"); OSSetThreadName(&s_coreThreadArray[0], s_coreThread0NameBuffer->c_str()); OSSetThreadName(&s_coreThreadArray[1], s_coreThread1NameBuffer->c_str()); OSSetThreadName(&s_coreThreadArray[2], s_coreThread2NameBuffer->c_str()); s_coreThreadsCreated = true; } void _SubmitCommandToCoreThreads(ProcUICoreThreadCommand cmd) { s_commandForCoreThread = cmd; OSMemoryBarrier(); OSResetEvent(&s_eventCoreThreadsCommandDone); OSSignalEvent(&s_eventCoreThreadsNewCommandReady); OSWaitEvent(&s_eventCoreThreadsCommandDone); } void ProcUIInitInternal() { if( s_isInitialized.exchange(true) ) return; if (!s_memoryPoolHeapPtr) { // user didn't specify a custom heap, use default heap instead s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); } OSInitEvent(&s_eventStateMessageReceived, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); OSInitEvent(&s_eventCoreThreadsNewCommandReady, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); OSInitEvent(&s_eventWaitingBeforeReleaseForeground, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); OSInitEvent(&s_eventCoreThreadsCommandDone, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); OSInitRendezvous(&s_coreThreadRendezvousA); OSInitRendezvous(&s_coreThreadRendezvousB); OSInitRendezvous(&s_coreThreadRendezvousC); OSInitEvent(&s_eventBackgroundThreadGotMessage, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); s_currentProcUIStatus = ProcUIStatus::Foreground; s_drawDoneReleaseCalled = false; s_isInForeground = true; s_coreThreadStackSize = 0x2000; s_systemMessageQueuePtr = coreinit::OSGetSystemMessageQueue(); uint32 upid = coreinit::OSGetUPID(); s_isForegroundProcess = upid == 2 || upid == 15; // either Wii U Menu or game title, both are RAMPID 7 (foreground process) RecreateProcUICoreThreads(); ClearCallbacksWithoutMemFree(); } void ProcUIInit(MEMPTR<void> callbackReadyToRelease) { s_saveCallback = callbackReadyToRelease; s_saveCallbackEx = nullptr; s_saveCallbackExUserParam = nullptr; ProcUIInitInternal(); } void ProcUIInitEx(MEMPTR<void> callbackReadyToReleaseEx, MEMPTR<void> userParam) { s_saveCallback = nullptr; s_saveCallbackEx = callbackReadyToReleaseEx; s_saveCallbackExUserParam = userParam; ProcUIInitInternal(); } void ProcUIShutdown() { if (!s_isInitialized.exchange(false)) return; if ( !s_isInForeground ) CancelBackgroundAlarms(); for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) OSSetThreadPriority(&s_coreThreadArray[i], 0); _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::Exit); ProcUIClearCallbacks(); ShutdownThreads(); } bool ProcUIIsRunning() { return s_isInitialized; } bool ProcUIInForeground() { return s_isInForeground; } bool ProcUIInShutdown() { return s_isInShutdown; } void AddCallbackInternal(void* funcPtr, void* userParam, uint64 tickDelay, sint32 priority, ProcUICallbackList& callbackList) { if ( __OSGetProcessSDKVersion() < 21102 ) { // in earlier COS versions it was possible/allowed to register a callback before initializing ProcUI s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); } else if ( !s_isInitialized ) { cemuLog_log(LogType::Force, "ProcUI: Trying to register callback before init"); cemu_assert_suspicious(); // this shouldn't happen but lets set the memory pointers anyway to prevent a crash in case the user has incorrect meta info s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); } ProcUIInternalCallbackEntry* entry = (ProcUIInternalCallbackEntry*)_AllocMem(sizeof(ProcUIInternalCallbackEntry)); entry->funcPtr = funcPtr; entry->userParam = userParam; entry->tickDelay = tickDelay; entry->priority = priority; ProcUIInternalCallbackEntry* cur = callbackList.first; cur = callbackList.first; if (!cur || cur->priority > priority) { // insert as the first element entry->next = cur; callbackList.first = entry; } else { // find the correct position to insert while (cur->next && cur->next->priority < priority) cur = cur->next; entry->next = cur->next; cur->next = entry; } } void ProcUIRegisterCallbackCore(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority, uint32 coreIndex) { if(callbackType >= ProcUICallbackId::COUNT) { cemuLog_log(LogType::Force, "ProcUIRegisterCallback: Invalid callback type {}", stdx::to_underlying(callbackType)); return; } if(callbackType != ProcUICallbackId::AcquireForeground) priority = -priority; AddCallbackInternal(funcPtr, userParam, 0, priority, s_CallbackTables[stdx::to_underlying(callbackType)][coreIndex]); } void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority) { ProcUIRegisterCallbackCore(callbackType, funcPtr, userParam, priority, OSGetCoreId()); } void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay) { AddCallbackInternal(funcPtr, userParam, tickDelay, 0, s_backgroundCallbackList); } void FreeCallbackChain(ProcUICallbackList& callbackList) { ProcUIInternalCallbackEntry* entry = callbackList.first; while (entry) { ProcUIInternalCallbackEntry* next = entry->next; _FreeMem(entry); entry = next; } callbackList.first = nullptr; } void ProcUIClearCallbacks() { for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) { for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++) { FreeCallbackChain(s_CallbackTables[i][coreIndex]); } } if (!s_isInForeground) CancelBackgroundAlarms(); FreeCallbackChain(s_backgroundCallbackList); } void ProcUISetSaveCallback(void* funcPtr, void* userParam) { s_saveCallback = nullptr; s_saveCallbackEx = funcPtr; s_saveCallbackExUserParam = userParam; } void ProcUISetCallbackStackSize(uint32 newStackSize) { s_coreThreadStackSize = newStackSize; if( s_isInitialized ) RecreateProcUICoreThreads(); } uint32 ProcUICalcMemorySize(uint32 numCallbacks) { // each callback entry is 0x70 bytes with 0x14 bytes of allocator overhead (for ExpHeap). But for some reason proc_ui on 5.5.5 seems to reserve 0x8C0 bytes per callback? uint32 stackReserveSize = (Espresso::CORE_COUNT + 1) * s_coreThreadStackSize; // 3 core threads + 1 message receive thread uint32 callbackReserveSize = 0x8C0 * numCallbacks; return stackReserveSize + callbackReserveSize + 100; } void _MemAllocFromMemoryPool(PPCInterpreter_t* hCPU) { uint32 size = hCPU->gpr[3]; MEMPTR<void> r = MEMAllocFromExpHeapEx((MEMHeapHandle)s_memoryPoolHeapPtr.GetPtr(), size, 4); osLib_returnFromFunction(hCPU, r.GetMPTR()); } void _FreeToMemoryPoolExpHeap(PPCInterpreter_t* hCPU) { MEMPTR<void> mem{hCPU->gpr[3]}; MEMFreeToExpHeap((MEMHeapHandle)s_memoryPoolHeapPtr.GetPtr(), mem.GetPtr()); osLib_returnFromFunction(hCPU, 0); } sint32 ProcUISetMemoryPool(void* memBase, uint32 size) { s_memAllocPtr = RPLLoader_MakePPCCallable(_MemAllocFromMemoryPool); s_memFreePtr = RPLLoader_MakePPCCallable(_FreeToMemoryPoolExpHeap); s_memoryPoolHeapPtr = MEMCreateExpHeapEx(memBase, size, MEM_HEAP_OPTION_THREADSAFE); return s_memoryPoolHeapPtr ? 0 : -1; } void ProcUISetBucketStorage(void* memBase, uint32 size) { MEMPTR<void> fgBase; uint32be fgFreeSize; OSGetForegroundBucketFreeArea(&fgBase, &fgFreeSize); if(fgFreeSize < size) cemuLog_log(LogType::Force, "ProcUISetBucketStorage: Buffer size too small"); s_bucketStorageBasePtr = memBase; } void ProcUISetMEM1Storage(void* memBase, uint32 size) { MEMPTR<void> memBound; uint32be memBoundSize; OSGetMemBound(1, &memBound, &memBoundSize); if(memBoundSize < size) cemuLog_log(LogType::Force, "ProcUISetMEM1Storage: Buffer size too small"); s_mem1StorageBasePtr = memBase; } void ProcUIDrawDoneRelease() { s_drawDoneReleaseCalled = true; } OSMessage g_lastMsg; void ProcUI_BackgroundThread_ReceiveSingleMessage(PPCInterpreter_t* hCPU) { // the background thread receives messages in a loop until the title is either exited or foreground is acquired while ( true ) { OSReceiveMessage(s_systemMessageQueuePtr, &g_lastMsg, OS_MESSAGE_BLOCK); // blocking receive SysMessageId lastMsgId = static_cast<SysMessageId>((uint32)g_lastMsg.data0); if(lastMsgId == SysMessageId::MsgExit || lastMsgId == SysMessageId::MsgAcquireForeground) break; else if (lastMsgId == SysMessageId::HomeButtonDenied) { cemu_assert_suspicious(); // Home button denied should not be sent to background app } else if ( lastMsgId == SysMessageId::NetIoStartOrStop ) { if (g_lastMsg.data1 ) { // NetIo start message for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) DoCallbackChain(s_callbacksType3_NetIoStart[i].first); } else { // NetIo stop message for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) DoCallbackChain(s_callbacksType4_NetIoStop[i].first); } } else { cemuLog_log(LogType::Force, "ProcUI: BackgroundThread received invalid message 0x{:08x}", lastMsgId); } } OSSignalEvent(&s_eventBackgroundThreadGotMessage); osLib_returnFromFunction(hCPU, 0); } // handle received message // if the message is Exit this function returns false, in all other cases it returns true bool ProcessSysMessage(OSMessage* msg) { SysMessageId lastMsgId = static_cast<SysMessageId>((uint32)msg->data0); if ( lastMsgId == SysMessageId::MsgAcquireForeground ) { cemuLog_logDebug(LogType::Force, "ProcUI: Received Acquire Foreground message"); s_isInShutdown = false; _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::AcquireForeground); s_currentProcUIStatus = ProcUIStatus::Foreground; s_isInForeground = true; OSMemoryBarrier(); OSSignalEvent(&s_eventStateMessageReceived); return true; } else if (lastMsgId == SysMessageId::MsgExit) { cemuLog_logDebug(LogType::Force, "ProcUI: Received Exit message"); s_isInShutdown = true; _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::Exit); for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) FreeCallbackChain(s_callbacksType2_Exit[i]); s_currentProcUIStatus = ProcUIStatus::Exit; OSMemoryBarrier(); OSSignalEvent(&s_eventStateMessageReceived); return 0; } if (lastMsgId == SysMessageId::MsgReleaseForeground) { if (msg->data1 != 0) { cemuLog_logDebug(LogType::Force, "ProcUI: Received Release Foreground message as part of shutdown initiation"); s_isInShutdown = true; } else { cemuLog_logDebug(LogType::Force, "ProcUI: Received Release Foreground message"); } s_currentProcUIStatus = ProcUIStatus::Releasing; OSResetEvent(&s_eventStateMessageReceived); // dont submit a command for the core threads yet, we need to wait for ProcUIDrawDoneRelease() } else if (lastMsgId == SysMessageId::HomeButtonDenied) { cemuLog_logDebug(LogType::Force, "ProcUI: Received Home Button Denied message"); _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::HomeButtonDenied); } else if ( lastMsgId == SysMessageId::NetIoStartOrStop ) { if (msg->data1 != 0) { cemuLog_logDebug(LogType::Force, "ProcUI: Received Net IO Start message"); _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::NetIoStart); } else { cemuLog_logDebug(LogType::Force, "ProcUI: Received Net IO Stop message"); _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::NetIoStop); } } else { cemuLog_log(LogType::Force, "ProcUI: Received unknown message 0x{:08x}", (uint32)lastMsgId); } return true; } ProcUIStatus ProcUIProcessMessages(bool isBlockingInBackground) { OSMessage msg; if (!s_isInitialized) { cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: ProcUI not initialized"); cemu_assert_suspicious(); return ProcUIStatus::Foreground; } if ( !isBlockingInBackground && OSGetCoreId() != 2 ) { cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: Non-blocking call must run on core 2"); } if (s_previouslyWasBlocking && isBlockingInBackground ) { cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: Cannot switch to blocking mode when in background"); } s_currentProcUIStatus = s_isInForeground ? ProcUIStatus::Foreground : ProcUIStatus::Background; if (s_drawDoneReleaseCalled) { s_isInForeground = false; s_currentProcUIStatus = ProcUIStatus::Background; _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::ReleaseForeground); OSResetEvent(&s_eventWaitingBeforeReleaseForeground); if(s_saveCallback) PPCCoreCallback(s_saveCallback); if(s_saveCallbackEx) PPCCoreCallback(s_saveCallbackEx, s_saveCallbackExUserParam); if (s_isForegroundProcess && isBlockingInBackground) { // start background thread __OSCreateThreadType(&s_backgroundThread, RPLLoader_MakePPCCallable(ProcUI_BackgroundThread_ReceiveSingleMessage), 0, nullptr, (uint8*)s_backgroundThreadStack.GetPtr() + s_coreThreadStackSize, s_coreThreadStackSize, 16, (1<<2), OSThread_t::THREAD_TYPE::TYPE_DRIVER); OSResumeThread(&s_backgroundThread); s_previouslyWasBlocking = true; } cemuLog_logDebug(LogType::Force, "ProcUI: Releasing foreground"); OSSignalEvent(&s_eventWaitingBeforeReleaseForeground); s_drawDoneReleaseCalled = false; } if (s_isInForeground || !isBlockingInBackground) { // non-blocking mode if ( OSReceiveMessage(s_systemMessageQueuePtr, &msg, 0) ) { s_previouslyWasBlocking = false; if ( !ProcessSysMessage(&msg) ) return s_currentProcUIStatus; // continue below, if we are now in background then ProcUIProcessMessages enters blocking mode } } // blocking mode (if in background and param is true) while (!s_isInForeground && isBlockingInBackground) { if ( !s_isForegroundProcess) { OSReceiveMessage(s_systemMessageQueuePtr, &msg, OS_MESSAGE_BLOCK); s_previouslyWasBlocking = false; if ( !ProcessSysMessage(&msg) ) return s_currentProcUIStatus; } // this code should only run if the background thread was started? Maybe rearrange the code to make this more clear OSWaitEvent(&s_eventBackgroundThreadGotMessage); OSResetEvent(&s_eventBackgroundThreadGotMessage); OSJoinThread(&s_backgroundThread, nullptr); msg = g_lastMsg; // g_lastMsg is set by the background thread s_previouslyWasBlocking = false; if ( !ProcessSysMessage(&msg) ) return s_currentProcUIStatus; } return s_currentProcUIStatus; } ProcUIStatus ProcUISubProcessMessages(bool isBlockingInBackground) { if (isBlockingInBackground) { while (s_currentProcUIStatus == ProcUIStatus::Background) OSWaitEvent(&s_eventStateMessageReceived); } return s_currentProcUIStatus; } const char* ProcUIDriver_GetName() { s_ProcUIDriverName->assign("ProcUI"); return s_ProcUIDriverName->c_str(); } void ProcUIDriver_Init(/* parameters unknown */) { s_driverIsActive = true; OSMemoryBarrier(); } void ProcUIDriver_OnDone(/* parameters unknown */) { if (s_driverIsActive) { ProcUIShutdown(); s_driverIsActive = false; OSMemoryBarrier(); } } void StoreMEM1AndFGBucket() { if (s_mem1StorageBasePtr) { MEMPTR<void> memBound; uint32be memBoundSize; OSGetMemBound(1, &memBound, &memBoundSize); OSBlockMove(s_mem1StorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true); } if (s_bucketStorageBasePtr) { MEMPTR<void> memBound; uint32be memBoundSize; OSGetForegroundBucketFreeArea(&memBound, &memBoundSize); OSBlockMove(s_bucketStorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true); } } void RestoreMEM1AndFGBucket() { if (s_mem1StorageBasePtr) { MEMPTR<void> memBound; uint32be memBoundSize; OSGetMemBound(1, &memBound, &memBoundSize); OSBlockMove(memBound.GetPtr(), s_mem1StorageBasePtr, memBoundSize, true); GX2::GX2Invalidate(0x40, s_mem1StorageBasePtr.GetMPTR(), memBoundSize); } if (s_bucketStorageBasePtr) { MEMPTR<void> memBound; uint32be memBoundSize; OSGetForegroundBucketFreeArea(&memBound, &memBoundSize); OSBlockMove(memBound.GetPtr(), s_bucketStorageBasePtr, memBoundSize, true); GX2::GX2Invalidate(0x40, memBound.GetMPTR(), memBoundSize); } } void ProcUIDriver_OnAcquiredForeground(/* parameters unknown */) { if (s_driverInBackground) { ProcUIDriver_Init(); s_driverInBackground = false; } else { RestoreMEM1AndFGBucket(); s_driverIsActive = true; OSMemoryBarrier(); } } void ProcUIDriver_OnReleaseForeground(/* parameters unknown */) { StoreMEM1AndFGBucket(); s_driverIsActive = false; OSMemoryBarrier(); } void reset() { // set variables to their initial state as if the RPL was just loaded s_isInitialized = false; s_isInShutdown = false; s_isInForeground = false; // ProcUIInForeground returns false until ProcUIInit(Ex) is called s_isForegroundProcess = true; s_saveCallback = nullptr; s_saveCallbackEx = nullptr; s_systemMessageQueuePtr = nullptr; ClearCallbacksWithoutMemFree(); s_currentProcUIStatus = ProcUIStatus::Foreground; s_bucketStorageBasePtr = nullptr; s_mem1StorageBasePtr = nullptr; s_drawDoneReleaseCalled = false; s_previouslyWasBlocking = false; // core threads s_coreThreadStackSize = 0; s_coreThreadsCreated = false; s_commandForCoreThread = ProcUICoreThreadCommand::Initial; // background thread s_backgroundThreadStack = nullptr; // user defined heap s_memoryPoolHeapPtr = nullptr; s_memAllocPtr = nullptr; s_memFreePtr = nullptr; // driver s_driverIsActive = false; s_driverInBackground = false; } void load() { } class : public COSModule { public: std::string_view GetName() override { return "proc_ui"; } void RPLMapped() override { reset(); cafeExportRegister("proc_ui", ProcUIInit, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIInitEx, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIShutdown, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIIsRunning, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIInForeground, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIInShutdown, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIRegisterCallbackCore, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIRegisterCallback, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIRegisterBackgroundCallback, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIClearCallbacks, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUISetSaveCallback, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUISetCallbackStackSize, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUICalcMemorySize, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUISetMemoryPool, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUISetBucketStorage, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUISetMEM1Storage, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIDrawDoneRelease, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUIProcessMessages, LogType::ProcUi); cafeExportRegister("proc_ui", ProcUISubProcessMessages, LogType::ProcUi); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if ( reason == RplEntryReason::Loaded ) { s_ProcUIDriver->getDriverName = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {MEMPTR<const char> namePtr(ProcUIDriver_GetName()); osLib_returnFromFunction(hCPU, namePtr.GetMPTR()); }); s_ProcUIDriver->init = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_Init(); osLib_returnFromFunction(hCPU, 0); }); s_ProcUIDriver->onAcquireForeground = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnAcquiredForeground(); osLib_returnFromFunction(hCPU, 0); }); s_ProcUIDriver->onReleaseForeground = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnReleaseForeground(); osLib_returnFromFunction(hCPU, 0); }); s_ProcUIDriver->done = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnDone(); osLib_returnFromFunction(hCPU, 0); }); s_driverIsActive = false; s_driverArgUkn1 = 0; s_driverArgUkn2 = 0; s_driverInBackground = false; uint32be ukn3; OSDriver_Register(moduleHandle, 200, &s_ProcUIDriver, 0, &s_driverArgUkn1, &s_driverArgUkn2, &ukn3); if ( ukn3 ) { if ( OSGetForegroundBucket(nullptr, nullptr) ) { ProcUIDriver_Init(); OSMemoryBarrier(); return; } s_driverInBackground = true; } OSMemoryBarrier(); } else if ( reason == RplEntryReason::Unloaded ) { ProcUIDriver_OnDone(); OSDriver_Deregister(moduleHandle, 0); } } }s_COSprocuiModule; COSModule* GetModule() { return &s_COSprocuiModule; } }; ================================================ FILE: src/Cafe/OS/libs/proc_ui/proc_ui.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace proc_ui { enum class ProcUIStatus { Foreground = 0, Background = 1, Releasing = 2, Exit = 3 }; enum class ProcUICallbackId { AcquireForeground = 0, ReleaseForeground = 1, Exit = 2, NetIoStart = 3, NetIoStop = 4, HomeButtonDenied = 5, COUNT = 6 }; void ProcUIInit(MEMPTR<void> callbackReadyToRelease); void ProcUIInitEx(MEMPTR<void> callbackReadyToReleaseEx, MEMPTR<void> userParam); void ProcUIShutdown(); bool ProcUIIsRunning(); bool ProcUIInForeground(); bool ProcUIInShutdown(); void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority); void ProcUIRegisterCallbackCore(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority, uint32 coreIndex); void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay); void ProcUIClearCallbacks(); void ProcUISetSaveCallback(void* funcPtr, void* userParam); void ProcUISetCallbackStackSize(uint32 newStackSize); uint32 ProcUICalcMemorySize(uint32 numCallbacks); sint32 ProcUISetMemoryPool(void* memBase, uint32 size); void ProcUISetBucketStorage(void* memBase, uint32 size); void ProcUISetMEM1Storage(void* memBase, uint32 size); void ProcUIDrawDoneRelease(); ProcUIStatus ProcUIProcessMessages(bool isBlockingInBackground); ProcUIStatus ProcUISubProcessMessages(bool isBlockingInBackground); COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/snd_core/ax.h ================================================ #pragma once #include "util/helpers/fspinlock.h" #include "Cafe/OS/RPL/COSModule.h" struct PPCInterpreter_t; namespace snd_core { // sndcore2 - AX init param config const int AX_RENDERER_FREQ_32KHZ = 0; const int AX_RENDERER_FREQ_48KHZ = 1; const int AX_FRAMELENGTH_3MS = 0; const int AX_SAMPLES_PER_3MS_48KHZ = (144); // 48000*3/1000 const int AX_SAMPLES_PER_3MS_32KHZ = (96); // 32000*3/1000 const int AX_SAMPLES_MAX = AX_SAMPLES_PER_3MS_48KHZ; // the maximum amount of samples in a single frame const int AX_DEV_TV = 0; const int AX_DEV_DRC = 1; const int AX_DEV_RMT = 2; const int AX_DEV_COUNT = 3; const int AX_UPSAMPLE_STAGE_BEFORE_FINALMIX = 0; const int AX_UPSAMPLE_STAGE_AFTER_FINALMIX = 1; const int AX_PIPELINE_SINGLE = 0; struct AXINITPARAM { uint32be freq; // AX_RENDERER_FREQ_* uint32be frameLength; // AX_FRAMELENGTH_* uint32be pipelineMode; // AX_PIPELINE_* }; // maximum number of supported channels per device const int AX_TV_CHANNEL_COUNT = 6; const int AX_DRC_CHANNEL_COUNT = 4; const int AX_RMT_CHANNEL_COUNT = 1; const int AX_APP_FRAME_CALLBACK_MAX = 64; const int AX_MODE_STEREO = 0; const int AX_MODE_SURROUND = 1; const int AX_MODE_DPL2 = 2; const int AX_MODE_6CH = 3; const int AX_MODE_MONO = 5; const int AX_PRIORITY_MAX = 32; const int AX_PRIORITY_FREE = 0; const int AX_PRIORITY_NODROP = 31; const int AX_PRIORITY_LOWEST = 1; const int AX_MAX_VOICES = 96; const int AX_AUX_BUS_COUNT = 3; const int AX_MAX_NUM_BUS = 4; const int AX_FORMAT_ADPCM = 0x0; const int AX_FORMAT_PCM16 = 0xA; const int AX_FORMAT_PCM8 = 0x19; const int AX_LPF_OFF = 0x0; const int AX_BIQUAD_OFF = 0x0; const int AX_SRC_TYPE_NONE = 0x0; const int AX_SRC_TYPE_LINEAR = 0x1; const int AX_SRC_TYPE_LOWPASS1 = 0x2; const int AX_SRC_TYPE_LOWPASS2 = 0x3; const int AX_SRC_TYPE_LOWPASS3 = 0x4; const int AX_FILTER_MODE_TAP = 0x0; const int AX_FILTER_MODE_LINEAR = 0x1; const int AX_FILTER_MODE_NONE = 0x2; const int AX_FILTER_LOWPASS_8K = 0x0; const int AX_FILTER_LOWPASS_12K = 0x1; const int AX_FILTER_LOWPASS_16K = 0x2; bool isInitialized(); void reset(); // AX VPB struct AXAUXCBCHANNELINFO { /* +0x00 */ uint32be numChannels; /* +0x04 */ uint32be numSamples; }; struct AXPBOFFSET_t { /* +0x00 | +0x34 */ uint16 format; /* +0x02 | +0x36 */ uint16 loopFlag; /* +0x04 | +0x38 */ uint32 loopOffset; /* +0x08 | +0x3C */ uint32 endOffset; /* +0x0C | +0x40 */ uint32 currentOffset; /* +0x10 | +0x44 */ MPTR samples; }; struct AXPBVE { uint16be currentVolume; sint16be currentDelta; }; struct AXVPBItd { uint8 ukn[64]; }; struct AXVPB { /* +0x00 */ uint32be index; /* +0x04 */ uint32be playbackState; /* +0x08 */ uint32be ukn08; /* +0x0C */ uint32be mixerSelect; /* +0x10 */ MEMPTR<AXVPB> next; /* +0x14 */ MEMPTR<AXVPB> prev; /* +0x18 */ uint32be ukn18; /* +0x1C */ uint32be priority; /* +0x20 */ uint32be callback; /* +0x24 */ uint32be userParam; /* +0x28 */ uint32be sync; /* +0x2C */ uint32be depop; /* +0x30 */ MEMPTR<AXVPBItd> itd; /* +0x34 */ AXPBOFFSET_t offsets; /* +0x48 */ uint32be callbackEx; // AXAcquireVoiceEx /* +0x4C */ uint32be ukn4C_dropReason; /* +0x50 */ float32be dspLoad; /* +0x54 */ float32be ppcLoad; }; struct AXPBLPF_t { /* +0x00 */ uint16 on; /* +0x02 */ sint16 yn1; /* +0x04 */ sint16 a0; /* +0x06 */ sint16 b0; }; struct AXPBBIQUAD_t { /* +0x00 */ uint16 on; /* +0x02 */ sint16 xn1; /* +0x04 */ sint16 xn2; /* +0x06 */ sint16 yn1; /* +0x08 */ sint16 yn2; /* +0x0A */ uint16 b0; /* +0x0C */ uint16 b1; /* +0x0E */ uint16 b2; /* +0x10 */ uint16 a1; /* +0x12 */ uint16 a2; }; struct AXRemixMatrix_t { /* +0x00 */ uint32be channelIn; /* +0x04 */ uint32be channelOut; /* +0x08 */ MEMPTR<float32be> matrix; }; struct AXRemixMatrices_t { /* +0x00 */ AXRemixMatrix_t deviceEntry[3]; // tv, drc, remote }; struct AXPBADPCM_t { uint16 a[16]; uint16 gain; uint16 scale; uint16 yn1; uint16 yn2; }; struct AXPBADPCMLOOP_t { uint16 loopScale; uint16 loopYn1; uint16 loopYn2; }; struct AXPBSRC_t { /* +0x1B8 */ uint16 ratioHigh; /* +0x1BA */ uint16 ratioLow; /* +0x1BC */ uint16 currentFrac; /* +0x1BE */ uint16 historySamples[4]; uint32 GetSrcRatio32() const { uint32 offset = (uint32)_swapEndianU16(ratioHigh) << 16; return offset | (uint32)_swapEndianU16(ratioLow); } }; struct AXCHMIX_DEPR { uint16 vol; sint16 delta; }; struct AXCHMIX2 { uint16be vol; sint16be delta; }; void AXVPB_Init(); void AXResetToDefaultState(); sint32 AXIsValidDevice(sint32 device, sint32 deviceIndex); AXVPB* AXAcquireVoiceEx(uint32 priority, MPTR callbackEx, MPTR userParam); void AXFreeVoice(AXVPB* vpb); bool AXUserIsProtected(); sint32 AXUserBegin(); sint32 AXUserEnd(); bool AXVoiceProtection_IsProtectedByAnyThread(AXVPB* vpb); bool AXVoiceProtection_IsProtectedByCurrentThread(AXVPB* vpb); sint32 AXVoiceBegin(AXVPB* voice); sint32 AXVoiceEnd(AXVPB* voice); sint32 AXSetVoiceDeviceMix(AXVPB* vpb, sint32 device, sint32 deviceIndex, AXCHMIX_DEPR* mix); void AXSetVoiceState(AXVPB* vpb, sint32 voiceState); sint32 AXIsVoiceRunning(AXVPB* vpb); void AXSetVoiceType(AXVPB* vpb, uint16 voiceType); void AXSetVoiceAdpcm(AXVPB* vpb, AXPBADPCM_t* adpcm); void AXSetVoiceAdpcmLoop(AXVPB* vpb, AXPBADPCMLOOP_t* adpcmLoop); void AXSetVoiceSrc(AXVPB* vpb, AXPBSRC_t* src); void AXSetVoiceSrcType(AXVPB* vpb, uint32 srcType); sint32 AXSetVoiceSrcRatio(AXVPB* vpb, float ratio); void AXSetVoiceVe(AXVPB* vpb, AXPBVE* ve); void AXComputeLpfCoefs(uint32 freq, uint16be* a0, uint16be* b0); void AXSetVoiceLpf(AXVPB* vpb, AXPBLPF_t* lpf); void AXSetVoiceLpfCoefs(AXVPB* vpb, uint16 a0, uint16 b0); void AXSetVoiceBiquad(AXVPB* vpb, AXPBBIQUAD_t* biquad); void AXSetVoiceBiquadCoefs(AXVPB* vpb, uint16 b0, uint16 b1, uint16 b2, uint16 a1, uint16 a2); void AXSetVoiceOffsets(AXVPB* vpb, AXPBOFFSET_t* pbOffset); void AXSetVoiceOffsetsEx(AXVPB* vpb, AXPBOFFSET_t* pbOffset, void* sampleBase); void AXGetVoiceOffsets(AXVPB* vpb, AXPBOFFSET_t* pbOffset); void AXGetVoiceOffsetsEx(AXVPB* vpb, AXPBOFFSET_t* pbOffset, MPTR sampleBase); void AXSetVoiceSamplesAddr(AXVPB* vpb, void* sampleBase); void AXSetVoiceCurrentOffset(AXVPB* vpb, uint32 currentOffset); void AXSetVoiceLoopOffset(AXVPB* vpb, uint32 loopOffset); void AXSetVoiceEndOffset(AXVPB* vpb, uint32 endOffset); void AXSetVoiceCurrentOffsetEx(AXVPB* vpb, uint32 currentOffset, MPTR sampleBase); void AXSetVoiceLoopOffsetEx(AXVPB* vpb, uint32 loopOffset, MPTR sampleBase); void AXSetVoiceEndOffsetEx(AXVPB* vpb, uint32 endOffset, MPTR sampleBase); uint32 AXGetVoiceCurrentOffsetEx(AXVPB* vpb, MPTR sampleBase); void AXSetVoiceLoop(AXVPB* vpb, uint16 loopState); sint32 AXGetVoiceLoopCount(AXVPB* vpb); // AXIst void AXIst_Init(); void AXIst_ThreadEntry(PPCInterpreter_t* hCPU); void AXIst_QueueFrame(); void AXResetCallbacks(); bool AXIst_IsFrameBeingProcessed(); sint32 AXSetDeviceUpsampleStage(sint32 device, int upsampleStage); sint32 AXGetDeviceUpsampleStage(sint32 device, uint32be* upsampleStage); sint32 AXGetDeviceFinalMixCallback(sint32 device, uint32be* funcAddrPtr); sint32 AXRegisterDeviceFinalMixCallback(sint32 device, MPTR funcAddr); sint32 AXSetDeviceRemixMatrix(sint32 deviceId, uint32 inputChannelCount, uint32 outputChannelCount, const MEMPTR<float32be>& matrix); sint32 AXGetDeviceRemixMatrix(uint32 deviceId, uint32 inputChannelCount, uint32 outputChannelCount, MEMPTR<MEMPTR<float32be>>& matrix); sint32 AXRegisterAppFrameCallback(MPTR funcAddr); sint32 AXDeregisterAppFrameCallback(MPTR funcAddr); MPTR AXRegisterFrameCallback(MPTR funcAddr); sint32 AXGetInputSamplesPerFrame(); sint32 AXGetInputSamplesPerSec(); // AXOut void resetNumProcessedFrames(); uint32 getNumProcessedFrames(); void AXOut_Init(); sint32 AIGetSamplesPerChannel(uint32 device); sint32 AIGetChannelCount(uint32 device); sint16* AIGetCurrentDMABuffer(uint32 device); void AXOut_SubmitTVFrame(sint32 frameIndex); void AXOut_SubmitDRCFrame(sint32 frameIndex); sint32 AXGetDeviceMode(sint32 device); extern uint32 numProcessedFrames; // AUX void AXAux_Init(); void AXAux_Process(); sint32be* AXAux_GetInputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus); sint32be* AXAux_GetOutputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus); sint32 AXGetAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MEMPTR<uint32be> funcPtrOut, MEMPTR<uint32be> contextPtrOut); sint32 AXRegisterAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MPTR funcMPTR, MPTR userParam); sint32 AXSetAuxReturnVolume(uint32 device, uint32 deviceIndex, uint32 auxBus, uint16 volume); extern uint16 __AXTVAuxReturnVolume[AX_AUX_BUS_COUNT]; // AXMix // mixer select constants (for AXSetDefaultMixerSelect / AXGetDefaultMixerSelect) const int AX_MIXER_SELECT_DSP = (0); const int AX_MIXER_SELECT_PPC = (1); const int AX_MIXER_SELECT_BOTH = (2); void AXMix_Init(); void AXSetDefaultMixerSelect(uint32 mixerSelect); uint32 AXGetDefaultMixerSelect(); void AXMix_DepopVoice(struct AXVPBInternal_t* internalShadowCopy); void AXMix_process(struct AXVPBInternal_t* internalShadowCopyHead); extern FSpinlock __AXVoiceListSpinlock; // AX multi voice struct AXVPBMULTI { /* +0x00 */ betype<uint32> isUsed; /* +0x04 */ betype<uint32> channelCount; /* +0x08 */ MEMPTR<AXVPB> voice[6]; // size: 0x20 }; static_assert(sizeof(AXVPBMULTI) == 0x20); struct AXMULTIVOICEUKNSTRUCT { uint8 ukn[0x4A]; betype<sint16> channelCount; }; struct AXDSPADPCM { /* +0x00 */ uint32be numSamples; /* +0x04 */ uint32be ukn04; /* +0x08 */ uint32be sampleRate; /* +0x0C */ uint16be isLooped; /* +0x0E */ uint16be format; /* +0x10 */ uint32be ukn10; /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be ukn18; /* +0x1C */ uint16be coef[16]; /* +0x3C */ uint16be gain; /* +0x3E */ uint16be scale; /* +0x40 */ uint16be yn1; /* +0x42 */ uint16be yn2; /* +0x44 */ uint16be ukn44; /* +0x46 */ uint16be ukn46; /* +0x48 */ uint16be ukn48; /* +0x4A */ uint16be channelCount; /* +0x4C */ uint16be ukn4C; /* +0x4E */ uint8 _padding4E[0x12]; }; static_assert(sizeof(AXDSPADPCM) == 0x60); void AXMultiVoice_Init(); sint32 AXAcquireMultiVoice(sint32 voicePriority, void* cbFunc, void* cbData, AXMULTIVOICEUKNSTRUCT* uknR6, MEMPTR<AXVPBMULTI>* multiVoiceOut); void AXFreeMultiVoice(AXVPBMULTI* multiVoice); sint32 AXGetMultiVoiceReformatBufferSize(sint32 voiceFormat, uint32 channelCount, uint32 sizeInBytes, uint32be* sizeOutput); void AXSetMultiVoiceType(AXVPBMULTI* mv, uint16 type); void AXSetMultiVoiceAdpcm(AXVPBMULTI* mv, AXDSPADPCM* data); void AXSetMultiVoiceSrcType(AXVPBMULTI* mv, uint32 type); void AXSetMultiVoiceOffsets(AXVPBMULTI* mv, AXPBOFFSET_t* offsets); void AXSetMultiVoiceVe(AXVPBMULTI* mv, AXPBVE* ve); void AXSetMultiVoiceSrcRatio(AXVPBMULTI* mv, float ratio); void AXSetMultiVoiceSrc(AXVPBMULTI* mv, AXPBSRC_t* src); void AXSetMultiVoiceLoop(AXVPBMULTI* mv, uint16 loop); void AXSetMultiVoiceState(AXVPBMULTI* mv, uint16 state); void AXSetMultiVoiceAdpcmLoop(AXVPBMULTI* mv, AXPBADPCMLOOP_t* loops); sint32 AXIsMultiVoiceRunning(AXVPBMULTI* mv); void AXOut_init(); void AXOut_reset(); void AXOut_update(); COSModule* GetModuleSndCore1(); COSModule* GetModuleSndCore2(); } ================================================ FILE: src/Cafe/OS/libs/snd_core/ax_aux.cpp ================================================ #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/snd_core/ax_internal.h" #include "Cafe/HW/Espresso/PPCState.h" namespace snd_core { const int AX_AUX_FRAME_COUNT = 2; // old (deprecated) style AUX callbacks MPTR __AXOldAuxDRCCallbackFunc[AX_AUX_BUS_COUNT * 2]; MPTR __AXOldAuxDRCCallbackUserParam[AX_AUX_BUS_COUNT * 2]; MPTR __AXOldAuxTVCallbackFunc[AX_AUX_BUS_COUNT]; MPTR __AXOldAuxTVCallbackUserParam[AX_AUX_BUS_COUNT]; // new style AUX callbacks MPTR __AXAuxDRCCallbackFunc[AX_AUX_BUS_COUNT * 2]; // 2 DRCs MPTR __AXAuxDRCCallbackUserParam[AX_AUX_BUS_COUNT * 2]; MPTR __AXAuxTVCallbackFunc[AX_AUX_BUS_COUNT]; MPTR __AXAuxTVCallbackUserParam[AX_AUX_BUS_COUNT]; struct AUXTVBuffer { sint32be _buf[AX_AUX_FRAME_COUNT * AX_TV_CHANNEL_COUNT * AX_AUX_BUS_COUNT * AX_SAMPLES_MAX]; sint32be* GetBuffer(uint32 auxBus, uint32 auxFrame, uint32 channel = 0) { const size_t samplesPerChannel = AXGetInputSamplesPerFrame(); const size_t samplesPerBus = AX_SAMPLES_PER_3MS_48KHZ * AX_TV_CHANNEL_COUNT; const size_t samplesPerFrame = samplesPerBus * AX_AUX_BUS_COUNT; return _buf + auxFrame * samplesPerFrame + auxBus * samplesPerBus + channel * samplesPerChannel; } void ClearBuffer() { memset(_buf, 0, sizeof(_buf)); } }; struct AUXDRCBuffer { sint32be _buf[AX_AUX_FRAME_COUNT * AX_DRC_CHANNEL_COUNT * AX_AUX_BUS_COUNT * AX_SAMPLES_MAX]; sint32be* GetBuffer(uint32 auxBus, uint32 auxFrame, uint32 channel = 0) { const size_t samplesPerChannel = AXGetInputSamplesPerFrame(); const size_t samplesPerBus = AX_SAMPLES_PER_3MS_48KHZ * AX_DRC_CHANNEL_COUNT; const size_t samplesPerFrame = samplesPerBus * AX_AUX_BUS_COUNT; return _buf + auxFrame * samplesPerFrame + auxBus * samplesPerBus + channel * samplesPerChannel; } void ClearBuffer() { memset(_buf, 0, sizeof(_buf)); } }; SysAllocator<AUXTVBuffer> __AXAuxTVBuffer; SysAllocator<AUXDRCBuffer, 2> __AXAuxDRCBuffer; uint32 __AXCurrentAuxInputFrameIndex = 0; uint32 AXAux_GetOutputFrameIndex() { return 1 - __AXCurrentAuxInputFrameIndex; } sint32be* AXAux_GetInputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus) { if (auxBus < 0 || auxBus >= AX_AUX_BUS_COUNT) return nullptr; if (device == AX_DEV_TV) { cemu_assert_debug(deviceIndex == 0); if (__AXOldAuxTVCallbackFunc[auxBus] == MPTR_NULL && __AXAuxTVCallbackFunc[auxBus] == MPTR_NULL) return nullptr; return __AXAuxTVBuffer->GetBuffer(auxBus, __AXCurrentAuxInputFrameIndex); } else if (device == AX_DEV_DRC) { cemu_assert_debug(deviceIndex >= 0 && deviceIndex <= 1); if (__AXOldAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL && __AXAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL) return nullptr; return __AXAuxDRCBuffer[deviceIndex].GetBuffer(auxBus, __AXCurrentAuxInputFrameIndex); } else { cemu_assert_debug(false); } return nullptr; } sint32be* AXAux_GetOutputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus) { uint32 outputFrameIndex = AXAux_GetOutputFrameIndex(); if (device == AX_DEV_TV) { cemu_assert_debug(deviceIndex == 0); if (__AXOldAuxTVCallbackFunc[auxBus] == MPTR_NULL && __AXAuxTVCallbackFunc[auxBus] == MPTR_NULL) return nullptr; return __AXAuxTVBuffer->GetBuffer(auxBus, outputFrameIndex); } else if (device == AX_DEV_DRC) { cemu_assert_debug(deviceIndex >= 0 && deviceIndex <= 1); if (__AXOldAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL && __AXAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL) return nullptr; return __AXAuxDRCBuffer[deviceIndex].GetBuffer(auxBus, outputFrameIndex); } else { cemu_assert_debug(false); } return nullptr; } void AXAux_Init() { __AXCurrentAuxInputFrameIndex = 0; __AXAuxTVBuffer->ClearBuffer(); __AXAuxDRCBuffer[0].ClearBuffer(); __AXAuxDRCBuffer[1].ClearBuffer(); memset(__AXAuxTVCallbackFunc, 0, sizeof(__AXAuxTVCallbackFunc)); memset(__AXAuxTVCallbackUserParam, 0, sizeof(__AXAuxTVCallbackUserParam)); memset(__AXOldAuxTVCallbackFunc, 0, sizeof(__AXOldAuxTVCallbackFunc)); memset(__AXOldAuxTVCallbackUserParam, 0, sizeof(__AXOldAuxTVCallbackUserParam)); memset(__AXAuxDRCCallbackFunc, 0, sizeof(__AXAuxDRCCallbackFunc)); memset(__AXAuxDRCCallbackUserParam, 0, sizeof(__AXAuxDRCCallbackUserParam)); memset(__AXOldAuxDRCCallbackFunc, 0, sizeof(__AXOldAuxDRCCallbackFunc)); memset(__AXOldAuxDRCCallbackUserParam, 0, sizeof(__AXOldAuxDRCCallbackUserParam)); // init aux return volume __AXTVAuxReturnVolume[0] = 0x8000; __AXTVAuxReturnVolume[1] = 0x8000; __AXTVAuxReturnVolume[2] = 0x8000; } sint32 AXRegisterAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MPTR funcMPTR, MPTR userParam) { sint32 r = AXIsValidDevice(device, deviceIndex); if (r != 0) return r; if (auxBusIndex >= AX_AUX_BUS_COUNT) return -5; if (device == AX_DEV_TV) { __AXAuxTVCallbackFunc[auxBusIndex] = funcMPTR; __AXAuxTVCallbackUserParam[auxBusIndex] = userParam; } else if (device == AX_DEV_DRC) { __AXAuxDRCCallbackFunc[auxBusIndex + deviceIndex * 3] = funcMPTR; __AXAuxDRCCallbackUserParam[auxBusIndex + deviceIndex * 3] = userParam; } else if (device == AX_DEV_RMT) { cemu_assert_debug(false); } return 0; } sint32 AXGetAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MEMPTR<uint32be> funcPtrOut, MEMPTR<uint32be> contextPtrOut) { sint32 r = AXIsValidDevice(device, deviceIndex); if (r != 0) return r; if (auxBusIndex >= AX_AUX_BUS_COUNT) return -5; if (device == AX_DEV_TV) { *funcPtrOut = __AXAuxTVCallbackFunc[auxBusIndex]; *contextPtrOut = __AXAuxTVCallbackUserParam[auxBusIndex]; } else if (device == AX_DEV_DRC) { *funcPtrOut = __AXAuxDRCCallbackFunc[auxBusIndex + deviceIndex * 3]; *contextPtrOut = __AXAuxDRCCallbackUserParam[auxBusIndex + deviceIndex * 3]; } else if (device == AX_DEV_RMT) { cemu_assert_debug(false); *funcPtrOut = MPTR_NULL; *contextPtrOut = MPTR_NULL; } return 0; } SysAllocator<MEMPTR<sint32be>, 6> __AXAuxCB_dataPtrs; SysAllocator<AXAUXCBCHANNELINFO> __AXAuxCB_auxCBStruct; void AXAux_Process() { uint32 processedAuxFrameIndex = AXAux_GetOutputFrameIndex(); uint32 sampleCount = AXGetInputSamplesPerFrame(); // TV aux callbacks uint32 tvChannelCount = AX_TV_CHANNEL_COUNT; for (sint32 auxBusIndex = 0; auxBusIndex < AX_AUX_BUS_COUNT; auxBusIndex++) { MPTR auxCBFuncMPTR = MPTR_NULL; auxCBFuncMPTR = __AXAuxTVCallbackFunc[auxBusIndex]; if (auxCBFuncMPTR == MPTR_NULL) auxCBFuncMPTR = __AXOldAuxTVCallbackFunc[auxBusIndex]; if (auxCBFuncMPTR == MPTR_NULL) { void* auxOutput = __AXAuxTVBuffer->GetBuffer(auxBusIndex, processedAuxFrameIndex); memset(auxOutput, 0, sampleCount * AX_TV_CHANNEL_COUNT * sizeof(sint32)); continue; } for (sint32 channelIndex = 0; channelIndex < AX_TV_CHANNEL_COUNT; channelIndex++) __AXAuxCB_dataPtrs[channelIndex] = __AXAuxTVBuffer->GetBuffer(auxBusIndex, processedAuxFrameIndex, channelIndex); // do callback if (__AXAuxTVCallbackFunc[auxBusIndex] != MPTR_NULL) { // new style callback AXAUXCBCHANNELINFO* cbStruct = __AXAuxCB_auxCBStruct.GetPtr(); cbStruct->numChannels = tvChannelCount; cbStruct->numSamples = sampleCount; PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->gpr[3] = __AXAuxCB_dataPtrs.GetMPTR(); hCPU->gpr[4] = __AXAuxTVCallbackUserParam[auxBusIndex]; hCPU->gpr[5] = __AXAuxCB_auxCBStruct.GetMPTR(); PPCCore_executeCallbackInternal(auxCBFuncMPTR); } else { // old style callback cemu_assert_debug(false); // todo } } // DRC aux callbacks for (sint32 drcIndex = 0; drcIndex < 2; drcIndex++) { uint32 drcChannelCount = AX_DRC_CHANNEL_COUNT; for (sint32 auxBusIndex = 0; auxBusIndex < AX_AUX_BUS_COUNT; auxBusIndex++) { MPTR auxCBFuncMPTR = MPTR_NULL; auxCBFuncMPTR = __AXAuxDRCCallbackFunc[auxBusIndex + drcIndex * 3]; if (auxCBFuncMPTR == MPTR_NULL) { auxCBFuncMPTR = __AXOldAuxDRCCallbackFunc[auxBusIndex + drcIndex * 3]; } if (auxCBFuncMPTR == MPTR_NULL) { void* auxOutput = __AXAuxDRCBuffer[drcIndex].GetBuffer(auxBusIndex, processedAuxFrameIndex); memset(auxOutput, 0, 96 * 4 * sizeof(sint32)); continue; } if (__AXAuxDRCCallbackFunc[auxBusIndex + drcIndex * 3] != MPTR_NULL) { // new style callback for (sint32 channelIndex = 0; channelIndex < AX_DRC_CHANNEL_COUNT; channelIndex++) __AXAuxCB_dataPtrs[channelIndex] = __AXAuxDRCBuffer[drcIndex].GetBuffer(auxBusIndex, processedAuxFrameIndex, channelIndex); AXAUXCBCHANNELINFO* cbStruct = __AXAuxCB_auxCBStruct.GetPtr(); cbStruct->numChannels = drcChannelCount; cbStruct->numSamples = sampleCount; PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->gpr[3] = __AXAuxCB_dataPtrs.GetMPTR(); hCPU->gpr[4] = __AXAuxDRCCallbackUserParam[auxBusIndex + drcIndex * 3]; hCPU->gpr[5] = __AXAuxCB_auxCBStruct.GetMPTR(); PPCCore_executeCallbackInternal(auxCBFuncMPTR); } else { // old style callback cemu_assert_debug(false); } } } } void AXAux_incrementBufferIndex() { __AXCurrentAuxInputFrameIndex = 1 - __AXCurrentAuxInputFrameIndex; } sint32 AXSetAuxReturnVolume(uint32 device, uint32 deviceIndex, uint32 auxBus, uint16 volume) { sint32 r = AXIsValidDevice(device, deviceIndex); if (r) return r; if (auxBus >= AX_AUX_BUS_COUNT) return -5; if( device == AX_DEV_TV ) { __AXTVAuxReturnVolume[auxBus] = volume; } else { cemuLog_logDebug(LogType::Force, "sndcore2.AXSetAuxReturnVolume() - unsupported device {}", device); } return 0; } } ================================================ FILE: src/Cafe/OS/libs/snd_core/ax_exports.cpp ================================================ #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/snd_core/ax_internal.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" #include "OS/libs/coreinit/coreinit_DynLoad.h" namespace snd_core { sndGeneric_t sndGeneric; void AXResetToDefaultState() { sndGeneric = {}; resetNumProcessedFrames(); AXVBP_Reset(); } bool AXIsInit() { return sndGeneric.isInitialized; } void __AXInit(bool isSoundCore2, uint32 frameLength, uint32 rendererFreq, uint32 pipelineMode) { cemu_assert(frameLength == AX_FRAMELENGTH_3MS); cemu_assert(rendererFreq == AX_RENDERER_FREQ_32KHZ || rendererFreq == AX_RENDERER_FREQ_48KHZ); sndGeneric.isSoundCore2 = isSoundCore2; sndGeneric.initParam.frameLength = frameLength; sndGeneric.initParam.rendererFreq = rendererFreq; sndGeneric.initParam.pipelineMode = pipelineMode; // init submodules AXIst_Init(); AXOut_Init(); AXVPB_Init(); AXAux_Init(); AXMix_Init(); AXMultiVoice_Init(); AXIst_InitThread(); sndGeneric.isInitialized = true; } void sndcore2_AXInitWithParams(AXINITPARAM* initParam) { if (sndGeneric.isInitialized) return; __AXInit(true, initParam->frameLength, initParam->freq, initParam->pipelineMode); } void sndcore2_AXInit() { if (sndGeneric.isInitialized) return; __AXInit(true, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE); } void sndcore1_AXInit() { if (sndGeneric.isInitialized) return; __AXInit(false, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE); } void sndcore2_AXInitEx(uint32 uknParam) { cemu_assert_debug(uknParam == 0); if (sndGeneric.isInitialized) return; __AXInit(true, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE); } void sndcore1_AXInitEx(uint32 uknParam) { cemu_assert_debug(uknParam == 0); if (sndGeneric.isInitialized) return; __AXInit(false, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE); } void AXQuit() { AXResetCallbacks(); // todo - should we wait to make sure any active callbacks are finished with execution before we exit AXQuit? // request worker thread stop and wait until complete AXIst_StopThread(); // clean up subsystems AXVBP_Reset(); sndGeneric.isInitialized = false; } sint32 AXGetMaxVoices() { return sndGeneric.isInitialized ? AX_MAX_VOICES : 0; } void export_AXGetDeviceFinalMixCallback(PPCInterpreter_t* hCPU) { ppcDefineParamS32(device, 0); ppcDefineParamU32BEPtr(funcAddrPtr, 1); sint32 r = AXGetDeviceFinalMixCallback(device, funcAddrPtr); cemuLog_log(LogType::SoundAPI, "AXGetDeviceFinalMixCallback({},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]); osLib_returnFromFunction(hCPU, r); } void export_AXRegisterDeviceFinalMixCallback(PPCInterpreter_t* hCPU) { ppcDefineParamS32(device, 0); ppcDefineParamMPTR(funcAddr, 1); cemuLog_log(LogType::SoundAPI, "AXRegisterDeviceFinalMixCallback({},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]); sint32 r = AXRegisterDeviceFinalMixCallback(device, funcAddr); osLib_returnFromFunction(hCPU, r); } void export_AXRegisterAppFrameCallback(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXRegisterAppFrameCallback(0x{:08x})", hCPU->gpr[3]); ppcDefineParamMPTR(funcAddr, 0); sint32 r = AXRegisterAppFrameCallback(funcAddr); osLib_returnFromFunction(hCPU, r); } void export_AXDeregisterAppFrameCallback(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXDeregisterAppFrameCallback(0x{:08x})", hCPU->gpr[3]); ppcDefineParamMPTR(funcAddr, 0); sint32 r = AXDeregisterAppFrameCallback(funcAddr); osLib_returnFromFunction(hCPU, r); } void export_AXRegisterFrameCallback(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXRegisterFrameCallback(0x{:08x})", hCPU->gpr[3]); ppcDefineParamMPTR(funcAddr, 0); sint32 r = AXRegisterFrameCallback(funcAddr); osLib_returnFromFunction(hCPU, r); } void export_AXRegisterCallback(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXRegisterCallback(0x{:08x})", hCPU->gpr[3]); ppcDefineParamMPTR(funcAddr, 0); sint32 r = AXRegisterFrameCallback(funcAddr); osLib_returnFromFunction(hCPU, r); } void export_AXRegisterAuxCallback(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXRegisterAuxCallback(0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x}) LR {:08x}", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->spr.LR); ppcDefineParamU32(device, 0); ppcDefineParamU32(deviceIndex, 1); ppcDefineParamU32(auxBusIndex, 2); ppcDefineParamMPTR(funcAddr, 3); ppcDefineParamMPTR(userParam, 4); sint32 r = AXRegisterAuxCallback(device, deviceIndex, auxBusIndex, funcAddr, userParam); osLib_returnFromFunction(hCPU, r); } void export_AXGetAuxCallback(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXGetAuxCallback(0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); ppcDefineParamU32(device, 0); ppcDefineParamU32(deviceIndex, 1); ppcDefineParamU32(auxBusIndex, 2); ppcDefineParamMEMPTR(funcAddrOut, uint32be, 3); ppcDefineParamMEMPTR(userParamOut, uint32be, 4); sint32 r = AXGetAuxCallback(device, deviceIndex, auxBusIndex, funcAddrOut, userParamOut); osLib_returnFromFunction(hCPU, r); } void export_AXSetAuxReturnVolume(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXSetAuxReturnVolume(0x{:08x},0x{:08x},0x{:08x},0x{:04x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); ppcDefineParamU32(device, 0); ppcDefineParamU32(deviceIndex, 1); ppcDefineParamU32(auxBusIndex, 2); ppcDefineParamU16(volume, 3); sint32 r = AXSetAuxReturnVolume(device, deviceIndex, auxBusIndex, volume); osLib_returnFromFunction(hCPU, r); } void export_AXGetDeviceMode(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXGetDeviceMode({})", hCPU->gpr[3]); ppcDefineParamS32(device, 0); ppcDefineParamU32BEPtr(mode, 1); *mode = AXGetDeviceMode(device); osLib_returnFromFunction(hCPU, 0); } void export_AXSetDeviceUpsampleStage(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXSetDeviceUpsampleStage({},{})", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamS32(device, 0); ppcDefineParamS32(upsampleStage, 1); sint32 r = AXSetDeviceUpsampleStage(device, upsampleStage); osLib_returnFromFunction(hCPU, r); } void export_AXGetDeviceUpsampleStage(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXGetDeviceUpsampleStage({},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamS32(device, 0); ppcDefineParamU32BEPtr(upsampleStagePtr, 1); sint32 r = AXGetDeviceUpsampleStage(device, upsampleStagePtr); osLib_returnFromFunction(hCPU, r); } void export_AXAcquireVoiceEx(PPCInterpreter_t* hCPU) { ppcDefineParamS32(priority, 0); ppcDefineParamMPTR(callbackEx, 1); ppcDefineParamMPTR(userParam, 2); cemuLog_log(LogType::SoundAPI, "AXAcquireVoiceEx({},0x{:08x},0x{:08x})", priority, callbackEx, userParam); MEMPTR<AXVPB> r = AXAcquireVoiceEx(priority, callbackEx, userParam); osLib_returnFromFunction(hCPU, r.GetMPTR()); } void export_AXAcquireVoice(PPCInterpreter_t* hCPU) { ppcDefineParamS32(priority, 0); ppcDefineParamMPTR(callback, 1); ppcDefineParamMPTR(userParam, 2); cemuLog_log(LogType::SoundAPI, "AXAcquireVoice({},0x{:08x},0x{:08x})", priority, callback, userParam); MEMPTR<AXVPB> r = AXAcquireVoiceEx(priority, MPTR_NULL, MPTR_NULL); if (r.IsNull() == false) { r->callback = (uint32be)callback; r->userParam = (uint32be)userParam; } osLib_returnFromFunction(hCPU, r.GetMPTR()); } void export_AXFreeVoice(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(vpb, AXVPB, 0); cemuLog_log(LogType::SoundAPI, "AXFreeVoice(0x{:08x})", hCPU->gpr[3]); AXFreeVoice(vpb); osLib_returnFromFunction(hCPU, 0); } void export_AXUserIsProtected(PPCInterpreter_t* hCPU) { sint32 r = AXUserIsProtected(); cemuLog_log(LogType::SoundAPI, "AXUserIsProtected() -> {}", r!=0?"true":"false"); osLib_returnFromFunction(hCPU, r); } void export_AXUserBegin(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXUserBegin()"); sint32 r = AXUserBegin(); osLib_returnFromFunction(hCPU, r); } void export_AXUserEnd(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXUserEnd()"); sint32 r = AXUserEnd(); osLib_returnFromFunction(hCPU, r); } void export_AXVoiceBegin(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(vpb, AXVPB, 0); cemuLog_log(LogType::SoundAPI, "AXVoiceBegin(0x{:08x})", hCPU->gpr[3]); sint32 r = AXVoiceBegin(vpb); osLib_returnFromFunction(hCPU, r); } void export_AXVoiceEnd(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(vpb, AXVPB, 0); cemuLog_log(LogType::SoundAPI, "AXVoiceEnd(0x{:08x})", hCPU->gpr[3]); sint32 r = AXVoiceEnd(vpb); osLib_returnFromFunction(hCPU, r); } void export_AXVoiceIsProtected(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(vpb, AXVPB, 0); cemuLog_log(LogType::SoundAPI, "AXVoiceIsProtected(0x{:08x})", hCPU->gpr[3]); sint32 r = AXVoiceProtection_IsProtectedByCurrentThread(vpb)?1:0; osLib_returnFromFunction(hCPU, r); } uint32 __AXCalculatePointerHighExtension(uint16 format, MPTR sampleBase, uint32 offset) { sampleBase = memory_virtualToPhysical(sampleBase); uint32 ptrHighExtension; if (format == AX_FORMAT_PCM8) { ptrHighExtension = ((sampleBase + offset) >> 29); } else if (format == AX_FORMAT_PCM16) { ptrHighExtension = ((sampleBase + offset * 2) >> 29); } else if (format == AX_FORMAT_ADPCM) { ptrHighExtension = ((sampleBase + offset / 2) >> 29); } return ptrHighExtension; } void export_AXCheckVoiceOffsets(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::SoundAPI, "AXCheckVoiceOffsets(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(pbOffset, AXPBOFFSET_t, 0); uint16 format = _swapEndianU16(pbOffset->format); MPTR sampleBase = _swapEndianU32(pbOffset->samples); uint32 highExtLoop = __AXCalculatePointerHighExtension(format, sampleBase, _swapEndianU32(pbOffset->loopOffset)); uint32 highExtEnd = __AXCalculatePointerHighExtension(format, sampleBase, _swapEndianU32(pbOffset->endOffset)); uint32 highExtCurrent = __AXCalculatePointerHighExtension(format, sampleBase, _swapEndianU32(pbOffset->currentOffset)); bool isSameRange; if (highExtLoop == highExtEnd && highExtEnd == highExtCurrent) isSameRange = true; else isSameRange = false; osLib_returnFromFunction(hCPU, isSameRange ? 1 : 0); } void export_AXSetDeviceRemixMatrix(PPCInterpreter_t* hCPU) { ppcDefineParamS32(device, 0); ppcDefineParamU32(chanIn, 1); ppcDefineParamU32(chanOut, 2); ppcDefineParamMEMPTR(matrix, float32be, 3); cemuLog_log(LogType::SoundAPI, "AXSetDeviceRemixMatrix({},{},{},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); const auto result = AXSetDeviceRemixMatrix(device, chanIn, chanOut, matrix); osLib_returnFromFunction(hCPU, result); } void export_AXGetDeviceRemixMatrix(PPCInterpreter_t* hCPU) { ppcDefineParamS32(device, 0); ppcDefineParamU32(chanIn, 1); ppcDefineParamU32(chanOut, 2); ppcDefineParamMEMPTR(matrix, MEMPTR<float32be>, 3); cemuLog_log(LogType::SoundAPI, "AXGetDeviceRemixMatrix({},{},{},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); const auto result = AXGetDeviceRemixMatrix(device, chanIn, chanOut, matrix); osLib_returnFromFunction(hCPU, result); } struct AXGetDeviceFinalOutput_t { /* +0x00 */ uint32be channelCount; /* +0x04 */ uint32be uknValue; /* +0x08 */ uint32be ukn08; /* +0x0C */ uint32be ukn0C; /* +0x10 */ uint32be size; // struct might be bigger? }; sint32 AXGetDeviceFinalOutput(uint32 device, sint16be* sampleBufferOutput, uint32 bufferSize, AXGetDeviceFinalOutput_t* output) { if (device != AX_DEV_TV && device != AX_DEV_DRC) return -1; sint32 channelCount = AIGetChannelCount(device); sint32 samplesPerChannel = AIGetSamplesPerChannel(device); sint32 samplesToCopy = samplesPerChannel * channelCount; if (bufferSize < (samplesToCopy * sizeof(sint16be))) return -11; // buffer not large enough // copy samples to buffer sint16* samplesBuffer = AIGetCurrentDMABuffer(device); for (sint32 i = 0; i < samplesToCopy; i++) sampleBufferOutput[i] = samplesBuffer[i]; // set output struct output->size = samplesToCopy * sizeof(sint16be); output->channelCount = channelCount; output->uknValue = 1; // always set to 1/true? return 0; } // void loadExportsSndCore1() // { // cafeExportRegisterFunc(sndcore1_AXInit, "snd_core", "AXInit", LogType::SoundAPI); // cafeExportRegisterFunc(sndcore1_AXInitEx, "snd_core", "AXInitEx", LogType::SoundAPI); // cafeExportRegister("snd_core", AXIsInit, LogType::SoundAPI); // cafeExportRegister("snd_core", AXQuit, LogType::SoundAPI); // // cafeExportRegister("snd_core", AXGetMaxVoices, LogType::SoundAPI); // cafeExportRegister("snd_core", AXGetInputSamplesPerFrame, LogType::SoundAPI); // cafeExportRegister("snd_core", AXGetInputSamplesPerSec, LogType::SoundAPI); // cafeExportRegister("snd_core", AXSetDefaultMixerSelect, LogType::SoundAPI); // cafeExportRegister("snd_core", AXGetDefaultMixerSelect, LogType::SoundAPI); // // osLib_addFunction("snd_core", "AXGetDeviceFinalMixCallback", export_AXGetDeviceFinalMixCallback); // osLib_addFunction("snd_core", "AXRegisterDeviceFinalMixCallback", export_AXRegisterDeviceFinalMixCallback); // // osLib_addFunction("snd_core", "AXRegisterAppFrameCallback", export_AXRegisterAppFrameCallback); // osLib_addFunction("snd_core", "AXDeregisterAppFrameCallback", export_AXDeregisterAppFrameCallback); // // osLib_addFunction("snd_core", "AXRegisterFrameCallback", export_AXRegisterFrameCallback); // osLib_addFunction("snd_core", "AXRegisterCallback", export_AXRegisterCallback); // // osLib_addFunction("snd_core", "AXRegisterAuxCallback", export_AXRegisterAuxCallback); // osLib_addFunction("snd_core", "AXGetAuxCallback", export_AXGetAuxCallback); // // osLib_addFunction("snd_core", "AXSetAuxReturnVolume", export_AXSetAuxReturnVolume); // // osLib_addFunction("snd_core", "AXGetDeviceMode", export_AXGetDeviceMode); // // osLib_addFunction("snd_core", "AXSetDeviceUpsampleStage", export_AXSetDeviceUpsampleStage); // osLib_addFunction("snd_core", "AXGetDeviceUpsampleStage", export_AXGetDeviceUpsampleStage); // // osLib_addFunction("snd_core", "AXAcquireVoiceEx", export_AXAcquireVoiceEx); // osLib_addFunction("snd_core", "AXAcquireVoice", export_AXAcquireVoice); // osLib_addFunction("snd_core", "AXFreeVoice", export_AXFreeVoice); // // osLib_addFunction("snd_core", "AXUserIsProtected", export_AXUserIsProtected); // osLib_addFunction("snd_core", "AXUserBegin", export_AXUserBegin); // osLib_addFunction("snd_core", "AXUserEnd", export_AXUserEnd); // osLib_addFunction("snd_core", "AXVoiceBegin", export_AXVoiceBegin); // osLib_addFunction("snd_core", "AXVoiceEnd", export_AXVoiceEnd); // osLib_addFunction("snd_core", "AXVoiceIsProtected", export_AXVoiceIsProtected); // // osLib_addFunction("snd_core", "AXCheckVoiceOffsets", export_AXCheckVoiceOffsets); // // osLib_addFunction("snd_core", "AXSetDeviceRemixMatrix", export_AXSetDeviceRemixMatrix); // osLib_addFunction("snd_core", "AXGetDeviceRemixMatrix", export_AXGetDeviceRemixMatrix); // // cafeExportRegister("snd_core", AXGetDeviceFinalOutput, LogType::SoundAPI); // } // // void loadExportsSndCore2() // { // cafeExportRegisterFunc(sndcore2_AXInitWithParams, "sndcore2", "AXInitWithParams", LogType::SoundAPI); // cafeExportRegisterFunc(sndcore2_AXInit, "sndcore2", "AXInit", LogType::SoundAPI); // cafeExportRegisterFunc(sndcore2_AXInitEx, "sndcore2", "AXInitEx", LogType::SoundAPI); // cafeExportRegister("sndcore2", AXIsInit, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXQuit, LogType::SoundAPI); // // cafeExportRegister("sndcore2", AXGetMaxVoices, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXGetInputSamplesPerFrame, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXGetInputSamplesPerSec, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetDefaultMixerSelect, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXGetDefaultMixerSelect, LogType::SoundAPI); // // osLib_addFunction("sndcore2", "AXGetDeviceFinalMixCallback", export_AXGetDeviceFinalMixCallback); // osLib_addFunction("sndcore2", "AXRegisterDeviceFinalMixCallback", export_AXRegisterDeviceFinalMixCallback); // // osLib_addFunction("sndcore2", "AXRegisterAppFrameCallback", export_AXRegisterAppFrameCallback); // osLib_addFunction("sndcore2", "AXDeregisterAppFrameCallback", export_AXDeregisterAppFrameCallback); // // osLib_addFunction("sndcore2", "AXRegisterFrameCallback", export_AXRegisterFrameCallback); // osLib_addFunction("sndcore2", "AXRegisterCallback", export_AXRegisterCallback); // // osLib_addFunction("sndcore2", "AXRegisterAuxCallback", export_AXRegisterAuxCallback); // osLib_addFunction("sndcore2", "AXGetAuxCallback", export_AXGetAuxCallback); // // osLib_addFunction("sndcore2", "AXSetAuxReturnVolume", export_AXSetAuxReturnVolume); // // osLib_addFunction("sndcore2", "AXGetDeviceMode", export_AXGetDeviceMode); // // osLib_addFunction("sndcore2", "AXSetDeviceUpsampleStage", export_AXSetDeviceUpsampleStage); // osLib_addFunction("sndcore2", "AXGetDeviceUpsampleStage", export_AXGetDeviceUpsampleStage); // // osLib_addFunction("sndcore2", "AXAcquireVoiceEx", export_AXAcquireVoiceEx); // osLib_addFunction("sndcore2", "AXAcquireVoice", export_AXAcquireVoice); // osLib_addFunction("sndcore2", "AXFreeVoice", export_AXFreeVoice); // // osLib_addFunction("sndcore2", "AXUserIsProtected", export_AXUserIsProtected); // osLib_addFunction("sndcore2", "AXUserBegin", export_AXUserBegin); // osLib_addFunction("sndcore2", "AXUserEnd", export_AXUserEnd); // osLib_addFunction("sndcore2", "AXVoiceBegin", export_AXVoiceBegin); // osLib_addFunction("sndcore2", "AXVoiceEnd", export_AXVoiceEnd); // // osLib_addFunction("sndcore2", "AXVoiceIsProtected", export_AXVoiceIsProtected); // // osLib_addFunction("sndcore2", "AXCheckVoiceOffsets", export_AXCheckVoiceOffsets); // // osLib_addFunction("sndcore2", "AXSetDeviceRemixMatrix", export_AXSetDeviceRemixMatrix); // osLib_addFunction("sndcore2", "AXGetDeviceRemixMatrix", export_AXGetDeviceRemixMatrix); // // cafeExportRegister("sndcore2", AXGetDeviceFinalOutput, LogType::SoundAPI); // // // multi voice // cafeExportRegister("sndcore2", AXAcquireMultiVoice, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXFreeMultiVoice, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXGetMultiVoiceReformatBufferSize, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetMultiVoiceType, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetMultiVoiceAdpcm, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetMultiVoiceSrcType, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetMultiVoiceOffsets, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetMultiVoiceVe, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetMultiVoiceSrcRatio, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetMultiVoiceSrc, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetMultiVoiceLoop, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetMultiVoiceState, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXSetMultiVoiceAdpcmLoop, LogType::SoundAPI); // cafeExportRegister("sndcore2", AXIsMultiVoiceRunning, LogType::SoundAPI); // } // void loadExports() // { // AXResetToDefaultState(); // // loadExportsSndCore1(); // loadExportsSndCore2(); // } bool isInitialized() { return sndGeneric.isInitialized; } void reset() { AXOut_reset(); AXResetToDefaultState(); sndGeneric.isInitialized = false; } void RegisterVoiceFunctions() { // snd_core cafeExportRegister("snd_core", AXSetVoiceDeviceMix, LogType::SoundAPI); cafeExportRegister("snd_core", AXComputeLpfCoefs, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceState, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceType, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceAdpcmLoop, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceSrc, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceSrcType, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceSrcRatio, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceVe, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceAdpcm, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceLoop, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceLpf, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceLpfCoefs, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceBiquad, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceBiquadCoefs, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceOffsets, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceOffsetsEx, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceCurrentOffset, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceCurrentOffsetEx, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceLoopOffset, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceLoopOffsetEx, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceEndOffset, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceEndOffsetEx, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetVoiceSamplesAddr, LogType::SoundAPI); cafeExportRegister("snd_core", AXIsVoiceRunning, LogType::SoundAPI); cafeExportRegister("snd_core", AXGetVoiceLoopCount, LogType::SoundAPI); cafeExportRegister("snd_core", AXGetVoiceOffsets, LogType::SoundAPI); cafeExportRegister("snd_core", AXGetVoiceCurrentOffsetEx, LogType::SoundAPI); // sndcore2 cafeExportRegister("sndcore2", AXSetVoiceDeviceMix, LogType::SoundAPI); cafeExportRegister("sndcore2", AXComputeLpfCoefs, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceState, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceType, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceAdpcmLoop, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceSrc, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceSrcType, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceSrcRatio, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceVe, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceAdpcm, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceLoop, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceLpf, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceLpfCoefs, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceBiquad, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceBiquadCoefs, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceOffsets, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceOffsetsEx, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceCurrentOffset, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceCurrentOffsetEx, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceLoopOffset, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceLoopOffsetEx, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceEndOffset, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceEndOffsetEx, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetVoiceSamplesAddr, LogType::SoundAPI); cafeExportRegister("sndcore2", AXIsVoiceRunning, LogType::SoundAPI); cafeExportRegister("sndcore2", AXGetVoiceLoopCount, LogType::SoundAPI); cafeExportRegister("sndcore2", AXGetVoiceOffsets, LogType::SoundAPI); cafeExportRegister("sndcore2", AXGetVoiceCurrentOffsetEx, LogType::SoundAPI); } class : public COSModule { public: std::string_view GetName() override { return "snd_core"; } void RPLMapped() override { AXResetToDefaultState(); cafeExportRegisterFunc(sndcore1_AXInit, "snd_core", "AXInit", LogType::SoundAPI); cafeExportRegisterFunc(sndcore1_AXInitEx, "snd_core", "AXInitEx", LogType::SoundAPI); cafeExportRegister("snd_core", AXIsInit, LogType::SoundAPI); cafeExportRegister("snd_core", AXQuit, LogType::SoundAPI); cafeExportRegister("snd_core", AXGetMaxVoices, LogType::SoundAPI); cafeExportRegister("snd_core", AXGetInputSamplesPerFrame, LogType::SoundAPI); cafeExportRegister("snd_core", AXGetInputSamplesPerSec, LogType::SoundAPI); cafeExportRegister("snd_core", AXSetDefaultMixerSelect, LogType::SoundAPI); cafeExportRegister("snd_core", AXGetDefaultMixerSelect, LogType::SoundAPI); osLib_addFunction("snd_core", "AXGetDeviceFinalMixCallback", export_AXGetDeviceFinalMixCallback); osLib_addFunction("snd_core", "AXRegisterDeviceFinalMixCallback", export_AXRegisterDeviceFinalMixCallback); osLib_addFunction("snd_core", "AXRegisterAppFrameCallback", export_AXRegisterAppFrameCallback); osLib_addFunction("snd_core", "AXDeregisterAppFrameCallback", export_AXDeregisterAppFrameCallback); osLib_addFunction("snd_core", "AXRegisterFrameCallback", export_AXRegisterFrameCallback); osLib_addFunction("snd_core", "AXRegisterCallback", export_AXRegisterCallback); osLib_addFunction("snd_core", "AXRegisterAuxCallback", export_AXRegisterAuxCallback); osLib_addFunction("snd_core", "AXGetAuxCallback", export_AXGetAuxCallback); osLib_addFunction("snd_core", "AXSetAuxReturnVolume", export_AXSetAuxReturnVolume); osLib_addFunction("snd_core", "AXGetDeviceMode", export_AXGetDeviceMode); osLib_addFunction("snd_core", "AXSetDeviceUpsampleStage", export_AXSetDeviceUpsampleStage); osLib_addFunction("snd_core", "AXGetDeviceUpsampleStage", export_AXGetDeviceUpsampleStage); osLib_addFunction("snd_core", "AXAcquireVoiceEx", export_AXAcquireVoiceEx); osLib_addFunction("snd_core", "AXAcquireVoice", export_AXAcquireVoice); osLib_addFunction("snd_core", "AXFreeVoice", export_AXFreeVoice); osLib_addFunction("snd_core", "AXUserIsProtected", export_AXUserIsProtected); osLib_addFunction("snd_core", "AXUserBegin", export_AXUserBegin); osLib_addFunction("snd_core", "AXUserEnd", export_AXUserEnd); osLib_addFunction("snd_core", "AXVoiceBegin", export_AXVoiceBegin); osLib_addFunction("snd_core", "AXVoiceEnd", export_AXVoiceEnd); osLib_addFunction("snd_core", "AXVoiceIsProtected", export_AXVoiceIsProtected); osLib_addFunction("snd_core", "AXCheckVoiceOffsets", export_AXCheckVoiceOffsets); osLib_addFunction("snd_core", "AXSetDeviceRemixMatrix", export_AXSetDeviceRemixMatrix); osLib_addFunction("snd_core", "AXGetDeviceRemixMatrix", export_AXGetDeviceRemixMatrix); cafeExportRegister("snd_core", AXGetDeviceFinalOutput, LogType::SoundAPI); RegisterVoiceFunctions(); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { AXResetToDefaultState(); } else if (reason == coreinit::RplEntryReason::Unloaded) { AXResetToDefaultState(); } } }s_COSsndcore1Module; class : public COSModule { public: std::string_view GetName() override { return "sndcore2"; } void RPLMapped() override { cafeExportRegisterFunc(sndcore2_AXInitWithParams, "sndcore2", "AXInitWithParams", LogType::SoundAPI); cafeExportRegisterFunc(sndcore2_AXInit, "sndcore2", "AXInit", LogType::SoundAPI); cafeExportRegisterFunc(sndcore2_AXInitEx, "sndcore2", "AXInitEx", LogType::SoundAPI); cafeExportRegister("sndcore2", AXIsInit, LogType::SoundAPI); cafeExportRegister("sndcore2", AXQuit, LogType::SoundAPI); cafeExportRegister("sndcore2", AXGetMaxVoices, LogType::SoundAPI); cafeExportRegister("sndcore2", AXGetInputSamplesPerFrame, LogType::SoundAPI); cafeExportRegister("sndcore2", AXGetInputSamplesPerSec, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetDefaultMixerSelect, LogType::SoundAPI); cafeExportRegister("sndcore2", AXGetDefaultMixerSelect, LogType::SoundAPI); osLib_addFunction("sndcore2", "AXGetDeviceFinalMixCallback", export_AXGetDeviceFinalMixCallback); osLib_addFunction("sndcore2", "AXRegisterDeviceFinalMixCallback", export_AXRegisterDeviceFinalMixCallback); osLib_addFunction("sndcore2", "AXRegisterAppFrameCallback", export_AXRegisterAppFrameCallback); osLib_addFunction("sndcore2", "AXDeregisterAppFrameCallback", export_AXDeregisterAppFrameCallback); osLib_addFunction("sndcore2", "AXRegisterFrameCallback", export_AXRegisterFrameCallback); osLib_addFunction("sndcore2", "AXRegisterCallback", export_AXRegisterCallback); osLib_addFunction("sndcore2", "AXRegisterAuxCallback", export_AXRegisterAuxCallback); osLib_addFunction("sndcore2", "AXGetAuxCallback", export_AXGetAuxCallback); osLib_addFunction("sndcore2", "AXSetAuxReturnVolume", export_AXSetAuxReturnVolume); osLib_addFunction("sndcore2", "AXGetDeviceMode", export_AXGetDeviceMode); osLib_addFunction("sndcore2", "AXSetDeviceUpsampleStage", export_AXSetDeviceUpsampleStage); osLib_addFunction("sndcore2", "AXGetDeviceUpsampleStage", export_AXGetDeviceUpsampleStage); osLib_addFunction("sndcore2", "AXAcquireVoiceEx", export_AXAcquireVoiceEx); osLib_addFunction("sndcore2", "AXAcquireVoice", export_AXAcquireVoice); osLib_addFunction("sndcore2", "AXFreeVoice", export_AXFreeVoice); osLib_addFunction("sndcore2", "AXUserIsProtected", export_AXUserIsProtected); osLib_addFunction("sndcore2", "AXUserBegin", export_AXUserBegin); osLib_addFunction("sndcore2", "AXUserEnd", export_AXUserEnd); osLib_addFunction("sndcore2", "AXVoiceBegin", export_AXVoiceBegin); osLib_addFunction("sndcore2", "AXVoiceEnd", export_AXVoiceEnd); osLib_addFunction("sndcore2", "AXVoiceIsProtected", export_AXVoiceIsProtected); osLib_addFunction("sndcore2", "AXCheckVoiceOffsets", export_AXCheckVoiceOffsets); osLib_addFunction("sndcore2", "AXSetDeviceRemixMatrix", export_AXSetDeviceRemixMatrix); osLib_addFunction("sndcore2", "AXGetDeviceRemixMatrix", export_AXGetDeviceRemixMatrix); cafeExportRegister("sndcore2", AXGetDeviceFinalOutput, LogType::SoundAPI); // multi voice cafeExportRegister("sndcore2", AXAcquireMultiVoice, LogType::SoundAPI); cafeExportRegister("sndcore2", AXFreeMultiVoice, LogType::SoundAPI); cafeExportRegister("sndcore2", AXGetMultiVoiceReformatBufferSize, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetMultiVoiceType, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetMultiVoiceAdpcm, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetMultiVoiceSrcType, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetMultiVoiceOffsets, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetMultiVoiceVe, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetMultiVoiceSrcRatio, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetMultiVoiceSrc, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetMultiVoiceLoop, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetMultiVoiceState, LogType::SoundAPI); cafeExportRegister("sndcore2", AXSetMultiVoiceAdpcmLoop, LogType::SoundAPI); cafeExportRegister("sndcore2", AXIsMultiVoiceRunning, LogType::SoundAPI); RegisterVoiceFunctions(); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { AXResetToDefaultState(); } else if (reason == coreinit::RplEntryReason::Unloaded) { AXResetToDefaultState(); } } }s_COSsndcore2Module; COSModule* GetModuleSndCore1() { return &s_COSsndcore1Module; } COSModule* GetModuleSndCore2() { return &s_COSsndcore2Module; } } ================================================ FILE: src/Cafe/OS/libs/snd_core/ax_internal.h ================================================ #pragma once #include "Cafe/OS/libs/snd_core/ax.h" namespace snd_core { struct sndGeneric_t { bool isInitialized{false}; bool isSoundCore2{false}; // init params struct { uint32 rendererFreq{0}; // 32Khz or 48Khz uint32 frameLength{0}; // 3MS uint32 pipelineMode{0}; }initParam; }; extern sndGeneric_t sndGeneric; const uint32 AX_SYNCFLAG_SRCFILTER = 0x1; // Voice src type (AXSetVoiceSrcType) const uint32 AX_SYNCFLAG_DEVICEMIXMASK = 0x2; // Voice mix related (AXSetVoiceDeviceMix) const uint32 AX_SYNCFLAG_PLAYBACKSTATE = 0x4; // Voice play state (AXSetVoiceState) const uint32 AX_SYNCFLAG_VOICETYPE = 0x8; // Voice type (AXSetVoiceType) const uint32 AX_SYNCFLAG_DEVICEMIX = 0x10; // Voice mix related (AXSetVoiceDeviceMix) const uint32 AX_SYNCFLAG_ITD20 = 0x20; // Voice initial time delay (AXSetVoiceItdOn) const uint32 AX_SYNCFLAG_ITD40 = 0x40; // Voice initial time delay (AXSetVoiceItdOn, AXSetVoiceItdTarget) const uint32 AX_SYNCFLAG_VE = 0x100; // Voice ve (AXSetVoiceVe) const uint32 AX_SYNCFLAG_VEDELTA = 0x200; // Voice ve delta (AXSetVoiceVeDelta) const uint32 AX_SYNCFLAG_OFFSETS = 0x400; // Voice offset data (AXSetVoiceOffsets) const uint32 AX_SYNCFLAG_LOOPFLAG = 0x800; // Voice loop flag (AXSetVoiceLoop) const uint32 AX_SYNCFLAG_LOOPOFFSET = 0x1000; // Voice loop offset (AXSetVoiceLoopOffset) const uint32 AX_SYNCFLAG_ENDOFFSET = 0x2000; // Voice end offset (AXSetVoiceEndOffset) const uint32 AX_SYNCFLAG_CURRENTOFFSET = 0x4000; // Voice current offset (AXSetVoiceCurrentOffset) const uint32 AX_SYNCFLAG_ADPCMDATA = 0x8000; // Voice adpcm data (AXSetVoiceAdpcm) const uint32 AX_SYNCFLAG_SRCDATA = 0x10000; // Voice src + src ratio (AXSetVoiceSrc) const uint32 AX_SYNCFLAG_SRCRATIO = 0x20000; // Voice src ratio (AXSetVoiceSrcRatio) const uint32 AX_SYNCFLAG_ADPCMLOOP = 0x40000; // Voice adpcm loop (AXSetVoiceAdpcmLoop) const uint32 AX_SYNCFLAG_LPFDATA = 0x80000; // Voice lpf (AXSetVoiceLpf) const uint32 AX_SYNCFLAG_LPFCOEF = 0x100000; // Voice lpf coef (AXSetVoiceLpfCoefs) const uint32 AX_SYNCFLAG_BIQUADDATA = 0x200000; // Voice biquad (AXSetVoiceBiquad) const uint32 AX_SYNCFLAG_BIQUADCOEF = 0x400000; // Voice biquad coef (AXSetVoiceBiquadCoefs) const uint32 AX_SYNCFLAG_VOICEREMOTEON = 0x800000; // ??? (AXSetVoiceRmtOn?) const uint32 AX_SYNCFLAG_4000000 = 0x4000000; // ??? const uint32 AX_SYNCFLAG_8000000 = 0x8000000; // ??? struct axADPCMInternal_t { /* +0x00 | +0x190 */ uint16 coef[16]; /* +0x20 | +0x1B0 */ uint16 gain; /* +0x22 | +0x1B2 */ uint16 scale; /* +0x24 | +0x1B4 */ uint16 yn1; /* +0x26 | +0x1B6 */ uint16 yn2; // size: 0x28 }; struct axADPCMLoopInternal_t { /* +0x00 | 0x1C6 */ uint16 loopScale; /* +0x02 | 0x1C8 */ uint16 loopYn1; /* +0x04 | 0x1CA */ uint16 loopYn2; // size: 0x6 }; struct axOffsetsInternal_t { /* +0x00 / 0x17E */ uint16 loopFlag; /* +0x02 / 0x180 */ uint16 format; /* +0x04 / 0x182 */ uint16 ptrHighExtension; // defines 512mb range (highest 3 bit of current offset ptr counted in bytes) // offsets (relative to NULL, counted in sample words) // note: All offset ptr variables can only store values up to 512MB (PCM8 mask -> 0x1FFFFFFF, PCM16 mask -> 0x0FFFFFFF, ADPCM mask -> 0x3FFFFFFF) /* +0x06 / 0x184 */ uint16 loopOffsetPtrHigh; /* +0x08 / 0x186 */ uint16 loopOffsetPtrLow; /* +0x0A / 0x188 */ uint16 endOffsetPtrHigh; /* +0x0C / 0x18A */ uint16 endOffsetPtrLow; /* +0x0E / 0x18C */ uint16 currentOffsetPtrHigh; /* +0x10 / 0x18E */ uint16 currentOffsetPtrLow; uint32 GetLoopOffset32() const { uint32 offset = (uint32)_swapEndianU16(loopOffsetPtrHigh) << 16; return offset | (uint32)_swapEndianU16(loopOffsetPtrLow); } uint32 GetEndOffset32() const { uint32 offset = (uint32)_swapEndianU16(endOffsetPtrHigh) << 16; return offset | (uint32)_swapEndianU16(endOffsetPtrLow); } uint32 GetCurrentOffset32() const { uint32 offset = (uint32)_swapEndianU16(currentOffsetPtrHigh) << 16; return offset | (uint32)_swapEndianU16(currentOffsetPtrLow); } }; const int AX_BUS_COUNT = 4; struct AXVPBInternal_t { /* matches what the DSP expects */ /* +0x000 */ uint16be nextAddrHigh; // points to next shadow copy (physical pointer, NULL for last voice in list) /* +0x002 */ uint16be nextAddrLow; /* +0x004 */ uint16be selfAddrHigh; // points to shadow copy of self (physical pointer) /* +0x006 */ uint16be selfAddrLow; /* +0x008 */ uint16 srcFilterMode; // AX_FILTER_MODE_* /* +0x00A */ uint16 srcTapFilter; // AX_FILTER_TAP_* /* +0x00C */ uint16be mixerSelect; /* +0x00E */ uint16 voiceType; /* +0x010 */ uint16 deviceMixMaskTV[4]; /* +0x018 */ uint16 deviceMixMaskDRC[4 * 2]; /* +0x028 */ AXCHMIX_DEPR deviceMixTV[AX_BUS_COUNT * AX_TV_CHANNEL_COUNT]; // TV device mix /* +0x088 */ AXCHMIX_DEPR deviceMixDRC[AX_BUS_COUNT * AX_DRC_CHANNEL_COUNT * 2]; // DRC device mix /* +0x108 */ AXCHMIX_DEPR deviceMixRMT[0x40 / 4]; // RMT device mix (size unknown) /* +0x148 */ uint16 reserved148_voiceRmtOn; /* +0x14A */ uint16 deviceMixMaskRMT[0x10]; /* +0x16A */ uint16 playbackState; // itd (0x16C - 0x1B4?) /* +0x16C */ uint16 reserved16C; /* +0x16E */ uint16be itdAddrHigh; // points to AXItd_t (physical pointer) /* +0x170 */ uint16be itdAddrLow; /* +0x172 */ uint16 reserved172; /* +0x174 */ uint16 reserved174; /* +0x176 */ uint16 reserved176_itdRelated; /* +0x178 */ uint16 reserved178_itdRelated; /* +0x17A */ uint16be veVolume; /* +0x17C */ uint16be veDelta; /* +0x17E */ axOffsetsInternal_t internalOffsets; /* +0x190 */ axADPCMInternal_t adpcmData; /* +0x1B8 */ AXPBSRC_t src; /* +0x1C6 */ axADPCMLoopInternal_t adpcmLoop; struct { /* +0x1CC */ uint16 on; /* +0x1CE */ uint16 yn1; /* +0x1D0 */ uint16 a0; /* +0x1D2 */ uint16 b0; }lpf; struct { /* +0x1D4 */ uint16 on; /* +0x1D6 */ sint16 xn1; /* +0x1D8 */ sint16 xn2; /* +0x1DA */ sint16 yn1; /* +0x1DC */ sint16 yn2; /* +0x1DE */ uint16 b0; /* +0x1E0 */ uint16 b1; /* +0x1E2 */ uint16 b2; /* +0x1E4 */ uint16 a1; /* +0x1E6 */ uint16 a2; }biquad; uint16 reserved1E8[0x18]; uint16 reserved218[0x20]; // not related to device mix? uint16 reserved258[0x10]; // not related to device mix? // rmt src related uint16 reserved278; uint16 reserved27A; uint16 reserved27C; uint16 reserved27E; uint16 reserved280; uint16 reserved282_rmtIIRGuessed; uint32 reserved284; uint32 reserved288; uint32 reserved28C; uint32 reserved290; uint16 reserved294; uint16 reserved296; /* +0x298 */ uint16 reserved298; /* +0x29A */ uint16 reserved29A; /* +0x29C */ uint16 reserved29C; /* +0x29E */ uint16 reserved29E; /* +0x2A0 */ uint16be index; /* +0x2A2 */ uint16be ukn2A2; // voice active/valid and being processed? uint16 reserved2A4; uint16 reserved2A6; uint16 reserved2A8; uint16 reserved2AA; /* +0x2AC */ MEMPTR<AXVPBInternal_t> nextToProcess; uint32 reserved2B0; uint32 reserved2B4; uint32 reserved2B8; uint32 reserved2BC; // size: 0x2C0 }; static_assert(sizeof(AXVPBInternal_t) == 0x2C0); extern AXVPBInternal_t* __AXVPBInternalVoiceArray; extern AXVPBInternal_t* __AXVPBInternalVoiceShadowCopyArrayPtr; extern AXVPB* __AXVPBArrayPtr; void AXResetVoiceLoopCount(AXVPB* vpb); std::vector<AXVPB*>& AXVoiceList_GetListByPriority(uint32 priority); std::vector<AXVPB*>& AXVoiceList_GetFreeVoices(); inline AXVPBInternal_t* GetInternalVoice(const AXVPB* vpb) { return __AXVPBInternalVoiceArray + (size_t)vpb->index; } inline uint32 GetVoiceIndex(const AXVPB* vpb) { return (uint32)vpb->index; } void AXVBP_Reset(); // AXIst void AXIst_InitThread(); OSThread_t* AXIst_GetThread(); void AXIst_StopThread(); void AXIst_HandleFrameCallbacks(); // AXAux void AXAux_incrementBufferIndex(); // internal mix buffers extern SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTVOutputBuffer; extern SysAllocator<sint32, AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * 2> __AXDRCOutputBuffer; } ================================================ FILE: src/Cafe/OS/libs/snd_core/ax_ist.cpp ================================================ #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/snd_core/ax_internal.h" #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" namespace snd_core { sint32 __AXDeviceUpsampleStage[AX_DEV_COUNT]; // AX_UPSAMPLE_STAGE_* sint32 AXSetDeviceUpsampleStage(sint32 device, int upsampleStage) { if (device != AX_DEV_TV && device != AX_DEV_DRC) return -1; __AXDeviceUpsampleStage[device] = upsampleStage; return 0; } sint32 AXGetDeviceUpsampleStage(sint32 device, uint32be* upsampleStage) { if (device != AX_DEV_TV && device != AX_DEV_DRC) return -1; *upsampleStage = __AXDeviceUpsampleStage[device]; return 0; } sint32 AXGetInputSamplesPerFrame() { if (sndGeneric.initParam.rendererFreq == AX_RENDERER_FREQ_48KHZ) return AX_SAMPLES_PER_3MS_48KHZ; return AX_SAMPLES_PER_3MS_32KHZ; } sint32 AXGetInputSamplesPerSec() { sint32 samplesPerFrame = AXGetInputSamplesPerFrame(); sint32 samplesPerSecond = (samplesPerFrame * 1000) / 3; return samplesPerSecond; } struct AXUpsampler { sint32 samples[AX_SAMPLES_MAX]; }; struct { AXUpsampler upsamplerArray[6]; bool useLinearUpsampler; // false -> FIR, true -> linear }__AXTVUpsampler; struct { AXUpsampler upsamplerArray[4]; bool useLinearUpsampler; // false -> FIR, true -> linear }__AXDRCUpsampler[2]; void AXUpsampler_Init(AXUpsampler* upsampler) { memset(upsampler, 0, sizeof(AXUpsampler)); } struct AXFINALMIXCBPARAM { /* +0x00 */ MEMPTR<MEMPTR<sint32>> data; /* +0x04 */ uint16be numChannelInput; /* +0x06 */ uint16be numSamples; /* +0x08 */ uint16be numDevices; /* +0x0A */ uint16be numChannelOutput; }; static_assert(offsetof(AXFINALMIXCBPARAM, data) == 0x00); static_assert(offsetof(AXFINALMIXCBPARAM, numChannelInput) == 0x04); static_assert(offsetof(AXFINALMIXCBPARAM, numSamples) == 0x06); static_assert(offsetof(AXFINALMIXCBPARAM, numDevices) == 0x08); static_assert(offsetof(AXFINALMIXCBPARAM, numChannelOutput) == 0x0A); SysAllocator<AXFINALMIXCBPARAM> __AXFinalMixCBStructTV; SysAllocator<AXFINALMIXCBPARAM> __AXFinalMixCBStructDRC; SysAllocator<AXFINALMIXCBPARAM> __AXFinalMixCBStructRMT; SysAllocator<MEMPTR<sint32>, 6> __AXFinalMixCBStructTV_dataPtrArray; SysAllocator<MEMPTR<sint32>, 4 * 2> __AXFinalMixCBStructDRC_dataPtrArray; sint32 __AXFinalMixOutputChannelCount[AX_DEV_COUNT] = { 0 }; // number of output channels returned by final mix callback // callbacks MPTR __AXFrameCallback = MPTR_NULL; // set via AXRegisterFrameCallback() MPTR __AXAppFrameCallback[AX_APP_FRAME_CALLBACK_MAX]; MPTR __AXDeviceFinalMixCallback[AX_DEV_COUNT]; SysAllocator<coreinit::OSMutex, 1> __AXAppFrameCallbackMutex; void AXResetCallbacks() { __AXFrameCallback = MPTR_NULL; for (sint32 i = 0; i < AX_APP_FRAME_CALLBACK_MAX; i++) { __AXAppFrameCallback[i] = MPTR_NULL; } coreinit::OSInitMutexEx(__AXAppFrameCallbackMutex.GetPtr(), NULL); for (sint32 i = 0; i < AX_DEV_COUNT; i++) __AXDeviceFinalMixCallback[i] = MPTR_NULL; } sint32 AXRegisterAppFrameCallback(MPTR funcAddr) { if (funcAddr == MPTR_NULL) return -17; OSLockMutex(__AXAppFrameCallbackMutex.GetPtr()); for (sint32 i = 0; i < AX_APP_FRAME_CALLBACK_MAX; i++) { if (__AXAppFrameCallback[i] == MPTR_NULL) { __AXAppFrameCallback[i] = funcAddr; OSUnlockMutex(__AXAppFrameCallbackMutex.GetPtr()); return 0; } } OSUnlockMutex(__AXAppFrameCallbackMutex.GetPtr()); return -15; } sint32 AXDeregisterAppFrameCallback(MPTR funcAddr) { if (funcAddr == MPTR_NULL) return -17; OSLockMutex(__AXAppFrameCallbackMutex.GetPtr()); for (sint32 i = 0; i < AX_APP_FRAME_CALLBACK_MAX; i++) { if (__AXAppFrameCallback[i] == funcAddr) { __AXAppFrameCallback[i] = MPTR_NULL; OSUnlockMutex(__AXAppFrameCallbackMutex.GetPtr()); return 0; } } OSUnlockMutex(__AXAppFrameCallbackMutex.GetPtr()); return -16; // not found } MPTR AXRegisterFrameCallback(MPTR funcAddr) { MPTR prevCallbackFunc = __AXFrameCallback; __AXFrameCallback = funcAddr; return prevCallbackFunc; } sint32 __AXTVUpsamplerSampleHistory[AX_TV_CHANNEL_COUNT] = { 0 }; sint32 __AXDRC0UpsamplerSampleHistory[AX_DRC_CHANNEL_COUNT] = { 0 }; sint32 __AXDRC1UpsamplerSampleHistory[AX_DRC_CHANNEL_COUNT] = { 0 }; void AXIst_Init() { // todo - double check defaults __AXDeviceUpsampleStage[AX_DEV_TV] = AX_UPSAMPLE_STAGE_BEFORE_FINALMIX; __AXDeviceUpsampleStage[AX_DEV_DRC] = AX_UPSAMPLE_STAGE_BEFORE_FINALMIX; __AXDeviceUpsampleStage[AX_DEV_RMT] = AX_UPSAMPLE_STAGE_BEFORE_FINALMIX; for (sint32 i = 0; i < AX_TV_CHANNEL_COUNT; i++) AXUpsampler_Init(__AXTVUpsampler.upsamplerArray + i); for (sint32 i = 0; i < AX_DRC_CHANNEL_COUNT; i++) { AXUpsampler_Init(__AXDRCUpsampler[0].upsamplerArray + i); AXUpsampler_Init(__AXDRCUpsampler[1].upsamplerArray + i); } __AXTVUpsampler.useLinearUpsampler = false; __AXDRCUpsampler[0].useLinearUpsampler = false; __AXDRCUpsampler[1].useLinearUpsampler = false; memset(__AXTVUpsamplerSampleHistory, 0, sizeof(__AXTVUpsamplerSampleHistory)); memset(__AXDRC0UpsamplerSampleHistory, 0, sizeof(__AXDRC0UpsamplerSampleHistory)); memset(__AXDRC1UpsamplerSampleHistory, 0, sizeof(__AXDRC1UpsamplerSampleHistory)); AXResetCallbacks(); } void AXOut_ResetFinalMixCBData() { sint32 inputSamplesPerFrame = AXGetInputSamplesPerFrame(); // TV __AXFinalMixCBStructTV->data = nullptr; __AXFinalMixCBStructTV->numChannelInput = 6; __AXFinalMixCBStructTV->numChannelOutput = 6; __AXFinalMixCBStructTV->numSamples = inputSamplesPerFrame; __AXFinalMixCBStructTV->numDevices = 1; // DRC __AXFinalMixCBStructDRC->data = nullptr; __AXFinalMixCBStructDRC->numChannelInput = 4; __AXFinalMixCBStructDRC->numChannelOutput = 4; __AXFinalMixCBStructDRC->numSamples = inputSamplesPerFrame; __AXFinalMixCBStructDRC->numDevices = 2; // RMT __AXFinalMixCBStructRMT->data = nullptr; __AXFinalMixCBStructRMT->numChannelInput = 1; __AXFinalMixCBStructRMT->numChannelOutput = 1; __AXFinalMixCBStructRMT->numSamples = 18; // verify __AXFinalMixCBStructRMT->numDevices = 4; } sint32 AXGetDeviceFinalMixCallback(sint32 device, uint32be* funcAddrPtr) { if (device != AX_DEV_TV && device != AX_DEV_DRC) return -1; *funcAddrPtr = __AXDeviceFinalMixCallback[device]; return 0; } sint32 AXRegisterDeviceFinalMixCallback(sint32 device, MPTR funcAddr) { if (device != AX_DEV_TV && device != AX_DEV_DRC) return -1; __AXDeviceFinalMixCallback[device] = funcAddr; return 0; } SysAllocator<AXRemixMatrices_t, 12> g_remix_matrices; sint32 AXSetDeviceRemixMatrix(sint32 deviceId, uint32 inputChannelCount, uint32 outputChannelCount, const MEMPTR<float32be>& matrix) { // validate parameters if (deviceId == AX_DEV_TV) { if(inputChannelCount > AX_TV_CHANNEL_COUNT) { cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Input channel count must be smaller or equal to 6 for TV device"); return -7; } if(outputChannelCount != 1 && outputChannelCount != 2 && outputChannelCount != 6) { // seems like Watch Dogs uses 4 as outputChannelCount for some reason? cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Output channel count must be 1, 2 or 6 for TV device"); return -8; } } else if (deviceId == AX_DEV_DRC) { if(inputChannelCount > AX_DRC_CHANNEL_COUNT) { cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Input channel count must be smaller or equal to 4 for DRC device"); return -7; } if(outputChannelCount != 1 && outputChannelCount != 2 && outputChannelCount != 4) { cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Output channel count must be 1, 2 or 4 for DRC device"); return -8; } } else { cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Only TV (0) and DRC (1) device are supported"); return -1; } auto matrices = g_remix_matrices.GetPtr(); // test if we already have an entry and just need to update the matrix data for (uint32 i = 0; i < g_remix_matrices.GetCount(); ++i) { if (g_remix_matrices[i].deviceEntry[deviceId].channelIn == inputChannelCount && g_remix_matrices[i].deviceEntry[deviceId].channelOut == outputChannelCount) { g_remix_matrices[i].deviceEntry[deviceId].matrix = matrix; return 0; } } // add new entry for (uint32 i = 0; i < g_remix_matrices.GetCount(); ++i) { if (g_remix_matrices[i].deviceEntry[deviceId].channelIn == 0 && g_remix_matrices[i].deviceEntry[deviceId].channelOut == 0) { g_remix_matrices[i].deviceEntry[deviceId].channelIn = inputChannelCount; g_remix_matrices[i].deviceEntry[deviceId].channelOut = outputChannelCount; g_remix_matrices[i].deviceEntry[deviceId].matrix = matrix; return 0; } } return -9; } sint32 AXGetDeviceRemixMatrix(uint32 deviceId, uint32 inputChannelCount, uint32 outputChannelCount, MEMPTR<MEMPTR<float32be>>& matrix) { // validate parameters if (deviceId == AX_DEV_TV) { cemu_assert(inputChannelCount <= AX_TV_CHANNEL_COUNT); cemu_assert(outputChannelCount == 2 || outputChannelCount == 6); } else if (deviceId == AX_DEV_DRC) { cemu_assert(inputChannelCount <= AX_DRC_CHANNEL_COUNT); cemu_assert(outputChannelCount == 1 || outputChannelCount == 2); } else if (deviceId == AX_DEV_RMT) { cemu_assert(false); } else return -1; for (uint32 i = 0; i < g_remix_matrices.GetCount(); ++i) { if (g_remix_matrices[i].deviceEntry[deviceId].channelIn == inputChannelCount && g_remix_matrices[i].deviceEntry[deviceId].channelOut == outputChannelCount) { *matrix = g_remix_matrices[i].deviceEntry[deviceId].matrix; return 0; } } return -10; } SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTVOutputBuffer; SysAllocator<sint32, AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * 2> __AXDRCOutputBuffer; SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTempFinalMixTVBuffer; SysAllocator<sint32, AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * 2> __AXTempFinalMixDRCBuffer; // 48KHz buffers SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTVBuffer48; SysAllocator<sint32, AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * 2> __AXDRCBuffer48; sint32 AXUpsampleLinear32To48(sint32* inputBuffer, sint32* outputBuffer, sint32* sampleHistory, sint32 sampleCount, bool shiftSamples, sint32 channelCount) { if (shiftSamples) { for (sint32 c = 0; c < channelCount; c++) { float samplePrev = (float)sampleHistory[c]; for (sint32 i = 0; i < sampleCount; i += 2) { float sample0 = (float)_swapEndianS32(inputBuffer[0]); float sample1 = (float)_swapEndianS32(inputBuffer[1]); inputBuffer += 2; float s0 = samplePrev * 0.66666669f + sample0 * 0.33333331f; float s1 = sample0; float s2 = sample1 * 0.66666669f + sample0 * 0.33333331f; outputBuffer[0] = _swapEndianS32(((sint32)s0) >> 8); outputBuffer[1] = _swapEndianS32(((sint32)s1) >> 8); outputBuffer[2] = _swapEndianS32(((sint32)s2) >> 8); outputBuffer += 3; samplePrev = sample1; } sampleHistory[c] = (sint32)samplePrev; } } else { for (sint32 c = 0; c < channelCount; c++) { float samplePrev = (float)sampleHistory[c]; for (sint32 i = 0; i < sampleCount; i += 2) { float sample0 = (float)_swapEndianS32(inputBuffer[0]); float sample1 = (float)_swapEndianS32(inputBuffer[1]); inputBuffer += 2; float s0 = samplePrev * 0.66666669f + sample0 * 0.33333331f; float s1 = sample0; float s2 = sample1 * 0.66666669f + sample0 * 0.33333331f; outputBuffer[0] = _swapEndianS32(((sint32)s0)); outputBuffer[1] = _swapEndianS32(((sint32)s1)); outputBuffer[2] = _swapEndianS32(((sint32)s2)); outputBuffer += 3; samplePrev = sample1; } sampleHistory[c] = (sint32)samplePrev; } } return (sampleCount / 2) * 3; } void AXTransferSamples(sint32* input, sint32* output, sint32 count, bool shiftSamples) { if (shiftSamples) { for (sint32 i = 0; i < count; i++) { sint32 s = _swapEndianS32(input[i]); s >>= 8; output[i] = _swapEndianS32(s); } } else { for (sint32 i = 0; i < count; i++) output[i] = input[i]; } } void AXIst_ProcessFinalMixCallback() { bool forceLinearUpsampler = true; bool isRenderer48 = sndGeneric.initParam.rendererFreq == AX_RENDERER_FREQ_48KHZ; sint32 inputSampleCount = AXGetInputSamplesPerFrame(); // TV if (isRenderer48 || __AXDeviceUpsampleStage[AX_DEV_TV] != AX_UPSAMPLE_STAGE_BEFORE_FINALMIX) { // only copy sint32* inputBuffer = __AXTVOutputBuffer; sint32* outputBuffer = __AXTempFinalMixTVBuffer.GetPtr(); sint32 sampleCount = inputSampleCount * AX_TV_CHANNEL_COUNT; for (sint32 i = 0; i < sampleCount; i++) { sint32 sample0 = _swapEndianS32(inputBuffer[0]); sample0 >>= 8; inputBuffer++; *outputBuffer = _swapEndianS32(sample0); outputBuffer++; } for (sint32 c = 0; c < AX_TV_CHANNEL_COUNT; c++) { __AXFinalMixCBStructTV_dataPtrArray[c] = __AXTempFinalMixTVBuffer.GetPtr() + c * inputSampleCount; } __AXFinalMixCBStructTV->numSamples = (uint16)inputSampleCount; __AXFinalMixCBStructTV->data = __AXFinalMixCBStructTV_dataPtrArray.GetPtr(); } else { // upsample sint32 upsampledSampleCount; if (__AXTVUpsampler.useLinearUpsampler || forceLinearUpsampler) { upsampledSampleCount = AXUpsampleLinear32To48(__AXTVOutputBuffer, __AXTVBuffer48.GetPtr(), __AXTVUpsamplerSampleHistory, inputSampleCount, true, AX_TV_CHANNEL_COUNT); } else { cemu_assert(false); // todo } for (sint32 c = 0; c < AX_TV_CHANNEL_COUNT; c++) { __AXFinalMixCBStructTV_dataPtrArray[c] = __AXTVBuffer48.GetPtr() + c * upsampledSampleCount; } __AXFinalMixCBStructTV->numSamples = (uint16)upsampledSampleCount; __AXFinalMixCBStructTV->data = __AXFinalMixCBStructTV_dataPtrArray.GetPtr(); } // DRC if (isRenderer48 || __AXDeviceUpsampleStage[AX_DEV_DRC] != AX_UPSAMPLE_STAGE_BEFORE_FINALMIX) { // only copy sint32* inputBuffer = __AXDRCOutputBuffer; sint32* outputBuffer = __AXTempFinalMixDRCBuffer.GetPtr(); sint32 sampleCount = inputSampleCount * AX_DRC_CHANNEL_COUNT * 2; for (sint32 i = 0; i < sampleCount; i++) { sint32 sample0 = _swapEndianS32(inputBuffer[0]); sample0 >>= 8; inputBuffer++; *outputBuffer = _swapEndianS32(sample0); outputBuffer++; } for (sint32 c = 0; c < AX_DRC_CHANNEL_COUNT*2; c++) { __AXFinalMixCBStructDRC_dataPtrArray[c] = __AXTempFinalMixDRCBuffer.GetPtr() + c * inputSampleCount; } __AXFinalMixCBStructDRC->numSamples = (uint16)inputSampleCount; __AXFinalMixCBStructDRC->data = __AXFinalMixCBStructDRC_dataPtrArray.GetPtr(); } else { // upsample sint32 upsampledSampleCount; // DRC0 if (__AXDRCUpsampler[0].useLinearUpsampler || forceLinearUpsampler) upsampledSampleCount = AXUpsampleLinear32To48(__AXDRCOutputBuffer, __AXDRCBuffer48.GetPtr(), __AXDRC0UpsamplerSampleHistory, inputSampleCount, true, AX_DRC_CHANNEL_COUNT); else { cemu_assert(false); // todo } // DRC1 if (__AXDRCUpsampler[1].useLinearUpsampler || forceLinearUpsampler) upsampledSampleCount = AXUpsampleLinear32To48(__AXDRCOutputBuffer + (upsampledSampleCount*AX_DRC_CHANNEL_COUNT), __AXDRCBuffer48.GetPtr()+inputSampleCount*AX_DRC_CHANNEL_COUNT, __AXDRC1UpsamplerSampleHistory, inputSampleCount, true, AX_DRC_CHANNEL_COUNT); else { cemu_assert(false); // todo } for (sint32 c = 0; c < AX_DRC_CHANNEL_COUNT * 2; c++) __AXFinalMixCBStructDRC_dataPtrArray[c] = __AXDRCBuffer48.GetPtr() + c * upsampledSampleCount; __AXFinalMixCBStructDRC->numSamples = (uint16)upsampledSampleCount; __AXFinalMixCBStructDRC->data = __AXFinalMixCBStructDRC_dataPtrArray.GetPtr(); } // do callbacks __AXFinalMixOutputChannelCount[0] = AX_TV_CHANNEL_COUNT; __AXFinalMixOutputChannelCount[1] = AX_DRC_CHANNEL_COUNT; __AXFinalMixOutputChannelCount[2] = AX_RMT_CHANNEL_COUNT; for (sint32 i = 0; i < AX_DEV_COUNT; i++) { if (__AXDeviceFinalMixCallback[i] == MPTR_NULL) continue; MEMPTR<AXFINALMIXCBPARAM> cbStruct; if (i == AX_DEV_TV) cbStruct = &__AXFinalMixCBStructTV; else if (i == AX_DEV_DRC) cbStruct = &__AXFinalMixCBStructDRC; else if (i == AX_DEV_RMT) cbStruct = &__AXFinalMixCBStructRMT; if (i == 2 && __AXDeviceFinalMixCallback[i]) { cemu_assert_debug(false); // RMT callbacks need testing } PPCCoreCallback(__AXDeviceFinalMixCallback[i], cbStruct); __AXFinalMixOutputChannelCount[i] = (sint32)cbStruct->numChannelOutput; } // handle upsampling if (isRenderer48) { // copy TV AXTransferSamples(__AXTempFinalMixTVBuffer.GetPtr(), __AXTVBuffer48.GetPtr(), AX_SAMPLES_PER_3MS_48KHZ*AX_TV_CHANNEL_COUNT, false); // copy DRC 0 AXTransferSamples(__AXTempFinalMixDRCBuffer.GetPtr(), __AXDRCBuffer48.GetPtr(), AX_SAMPLES_PER_3MS_48KHZ*AX_DRC_CHANNEL_COUNT, false); } else { if (__AXDeviceUpsampleStage[AX_DEV_TV] == AX_UPSAMPLE_STAGE_BEFORE_FINALMIX) { // final mix is 48KHz // no need to copy since samples are already in right buffer } else { // final mix is 32KHz -> upsample // TV sint32 upsampledSampleCount; if (__AXTVUpsampler.useLinearUpsampler || forceLinearUpsampler) { upsampledSampleCount = AXUpsampleLinear32To48(__AXTempFinalMixTVBuffer.GetPtr(), __AXTVBuffer48.GetPtr(), __AXTVUpsamplerSampleHistory, inputSampleCount, false, AX_TV_CHANNEL_COUNT); } else { cemu_assert(false); } // DRC0 if (__AXDRCUpsampler[0].useLinearUpsampler || forceLinearUpsampler) { AXUpsampleLinear32To48(__AXTempFinalMixDRCBuffer.GetPtr(), __AXDRCBuffer48.GetPtr(), __AXDRC0UpsamplerSampleHistory, inputSampleCount, false, AX_DRC_CHANNEL_COUNT); } else { cemu_assert(false); } } } } void AXIst_SyncSingleVPB(AXVPB* vpb) { uint32 index = vpb->index; uint32 sync = vpb->sync; AXVPBInternal_t* internalVPB = __AXVPBInternalVoiceArray + index; AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + index; // shadow copy -> internal data internalShadowCopy->nextAddrHigh = internalVPB->nextAddrHigh; internalShadowCopy->nextAddrLow = internalVPB->nextAddrLow; if (internalVPB->nextToProcess != nullptr) internalShadowCopy->nextToProcess = __AXVPBInternalVoiceShadowCopyArrayPtr + (sint32)internalVPB->nextToProcess->index; else internalShadowCopy->nextToProcess = nullptr; internalShadowCopy->mixerSelect = internalVPB->mixerSelect; internalShadowCopy->ukn2A2 = internalVPB->ukn2A2; internalShadowCopy->reserved296 = internalVPB->reserved296; internalShadowCopy->reserved298 = internalVPB->reserved298; internalShadowCopy->reserved29A = internalVPB->reserved29A; internalShadowCopy->reserved29C = internalVPB->reserved29C; internalShadowCopy->reserved29E = internalVPB->reserved29E; // sync current playback state if ((sync&AX_SYNCFLAG_PLAYBACKSTATE) == 0) { uint32 playbackState = _swapEndianU16(internalShadowCopy->playbackState); vpb->playbackState = playbackState; internalVPB->playbackState = _swapEndianU16(playbackState); } // sync current offset if ((sync&(AX_SYNCFLAG_CURRENTOFFSET | AX_SYNCFLAG_OFFSETS)) == 0) { internalVPB->internalOffsets.currentOffsetPtrHigh = internalShadowCopy->internalOffsets.currentOffsetPtrHigh; internalVPB->internalOffsets.currentOffsetPtrLow = internalShadowCopy->internalOffsets.currentOffsetPtrLow; } // sync volume if ((sync&AX_SYNCFLAG_VE) == 0) { internalVPB->veVolume = internalShadowCopy->veVolume; } // sync adpcm data if ((sync&AX_SYNCFLAG_ADPCMDATA) == 0) { for (sint32 i = 0; i < 16; i++) internalVPB->adpcmData.coef[i] = internalShadowCopy->adpcmData.coef[i]; internalVPB->adpcmData.gain = internalShadowCopy->adpcmData.gain; internalVPB->adpcmData.scale = internalShadowCopy->adpcmData.scale; internalVPB->adpcmData.yn1 = internalShadowCopy->adpcmData.yn1; internalVPB->adpcmData.yn2 = internalShadowCopy->adpcmData.yn2; } // sync src data if ((sync&AX_SYNCFLAG_SRCDATA) == 0) { internalVPB->src.currentFrac = internalShadowCopy->src.currentFrac; internalVPB->src.historySamples[0] = internalShadowCopy->src.historySamples[0]; internalVPB->src.historySamples[1] = internalShadowCopy->src.historySamples[1]; internalVPB->src.historySamples[2] = internalShadowCopy->src.historySamples[2]; internalVPB->src.historySamples[3] = internalShadowCopy->src.historySamples[3]; } if (AXVoiceProtection_IsProtectedByAnyThread(vpb)) { // if voice is currently protected, dont sync remaining flags return; } // internal data -> shadow copy // sync src type if ((sync&AX_SYNCFLAG_SRCFILTER) != 0) { internalShadowCopy->srcFilterMode = internalVPB->srcFilterMode; internalShadowCopy->srcTapFilter = internalVPB->srcTapFilter; } // Sync device mix if ((sync&AX_SYNCFLAG_DEVICEMIXMASK) != 0) { memcpy(internalShadowCopy->deviceMixMaskTV, internalVPB->deviceMixMaskTV, 8); memcpy(internalShadowCopy->deviceMixMaskDRC, internalVPB->deviceMixMaskDRC, 0x10); memcpy(internalShadowCopy->deviceMixMaskRMT, internalVPB->deviceMixMaskRMT, 0x20); } // sync device mix if ((sync & AX_SYNCFLAG_DEVICEMIX) != 0) { memcpy(internalShadowCopy->deviceMixTV, internalVPB->deviceMixTV, 0x60); memcpy(internalShadowCopy->deviceMixDRC, internalVPB->deviceMixDRC, 0x80); memcpy(internalShadowCopy->deviceMixRMT, internalVPB->deviceMixRMT, 0x40); } // sync playback state if ((sync&AX_SYNCFLAG_PLAYBACKSTATE) != 0) { internalShadowCopy->playbackState = internalVPB->playbackState; } // sync voice type if ((sync&AX_SYNCFLAG_VOICETYPE) != 0) { internalShadowCopy->voiceType = internalVPB->voiceType; } // itd if ((sync&AX_SYNCFLAG_ITD40) == 0) { if ((sync&AX_SYNCFLAG_ITD20) != 0) { //cemu_assert_debug(false); // sync PB itd } } else { // sync itd internalShadowCopy->reserved176_itdRelated = internalVPB->reserved176_itdRelated; internalShadowCopy->reserved178_itdRelated = internalVPB->reserved178_itdRelated; } // sync volume envelope // the part below could be incorrect (it seems strange that the delta flag overwrites the full ve flag? But PPC code looks like this) if ((sync&AX_SYNCFLAG_VEDELTA) != 0) { internalShadowCopy->veDelta = internalVPB->veDelta; } else if ((sync&AX_SYNCFLAG_VE) != 0) { internalShadowCopy->veVolume = internalVPB->veVolume; internalShadowCopy->veDelta = internalVPB->veDelta; } // sync offsets if ((sync&AX_SYNCFLAG_OFFSETS) != 0) { // sync entire offsets block memcpy(&internalShadowCopy->internalOffsets, &internalVPB->internalOffsets, sizeof(axOffsetsInternal_t)); } else { // sync individual offset fields if ((sync&AX_SYNCFLAG_LOOPFLAG) != 0) { // sync loop flag internalShadowCopy->internalOffsets.loopFlag = internalVPB->internalOffsets.loopFlag; } if ((sync&AX_SYNCFLAG_LOOPOFFSET) != 0) { // sync loop offset internalShadowCopy->internalOffsets.loopOffsetPtrLow = internalVPB->internalOffsets.loopOffsetPtrLow; internalShadowCopy->internalOffsets.loopOffsetPtrHigh = internalVPB->internalOffsets.loopOffsetPtrHigh; } if ((sync&AX_SYNCFLAG_ENDOFFSET) != 0) { // sync end offset internalShadowCopy->internalOffsets.endOffsetPtrLow = internalVPB->internalOffsets.endOffsetPtrLow; internalShadowCopy->internalOffsets.endOffsetPtrHigh = internalVPB->internalOffsets.endOffsetPtrHigh; } if ((sync&AX_SYNCFLAG_CURRENTOFFSET) != 0) { // sync current offset internalShadowCopy->internalOffsets.currentOffsetPtrLow = internalVPB->internalOffsets.currentOffsetPtrLow; internalShadowCopy->internalOffsets.currentOffsetPtrHigh = internalVPB->internalOffsets.currentOffsetPtrHigh; } } if ((sync&AX_SYNCFLAG_ADPCMDATA) != 0) { // sync adpcm data for (sint32 i = 0; i < 16; i++) internalShadowCopy->adpcmData.coef[i] = internalVPB->adpcmData.coef[i]; internalShadowCopy->adpcmData.gain = internalVPB->adpcmData.gain; internalShadowCopy->adpcmData.scale = internalVPB->adpcmData.scale; } if ((sync&AX_SYNCFLAG_SRCDATA) != 0) { // sync voice all src data internalShadowCopy->src.ratioHigh = internalVPB->src.ratioHigh; internalShadowCopy->src.ratioLow = internalVPB->src.ratioLow; internalShadowCopy->src.currentFrac = internalVPB->src.currentFrac; internalShadowCopy->src.historySamples[0] = internalVPB->src.historySamples[0]; internalShadowCopy->src.historySamples[1] = internalVPB->src.historySamples[1]; internalShadowCopy->src.historySamples[2] = internalVPB->src.historySamples[2]; internalShadowCopy->src.historySamples[3] = internalVPB->src.historySamples[3]; } else { if ((sync&AX_SYNCFLAG_SRCRATIO) != 0) { // sync voice src ratio internalShadowCopy->src.ratioHigh = internalVPB->src.ratioHigh; internalShadowCopy->src.ratioLow = internalVPB->src.ratioLow; } } if ((sync&AX_SYNCFLAG_ADPCMLOOP) != 0) { // sync voice adpcm loop internalShadowCopy->adpcmLoop.loopScale = internalVPB->adpcmLoop.loopScale; internalShadowCopy->adpcmLoop.loopYn1 = internalVPB->adpcmLoop.loopYn1; internalShadowCopy->adpcmLoop.loopYn2 = internalVPB->adpcmLoop.loopYn2; } if ((sync&AX_SYNCFLAG_LPFCOEF) != 0) { // sync lpf coef internalShadowCopy->lpf.a0 = internalVPB->lpf.a0; internalShadowCopy->lpf.b0 = internalVPB->lpf.b0; } else { if ((sync&AX_SYNCFLAG_LPFDATA) != 0) { // sync lpf internalShadowCopy->lpf.on = internalVPB->lpf.on; internalShadowCopy->lpf.yn1 = internalVPB->lpf.yn1; internalShadowCopy->lpf.a0 = internalVPB->lpf.a0; internalShadowCopy->lpf.b0 = internalVPB->lpf.b0; } } if ((sync&AX_SYNCFLAG_BIQUADCOEF) != 0) { // sync biquad coef internalShadowCopy->biquad.b0 = internalVPB->biquad.b0; internalShadowCopy->biquad.b1 = internalVPB->biquad.b1; internalShadowCopy->biquad.b2 = internalVPB->biquad.b2; internalShadowCopy->biquad.a1 = internalVPB->biquad.a1; internalShadowCopy->biquad.a2 = internalVPB->biquad.a2; } else if ((sync&AX_SYNCFLAG_BIQUADDATA) != 0) { // sync biquad internalShadowCopy->biquad.on = internalVPB->biquad.on; internalShadowCopy->biquad.xn1 = internalVPB->biquad.xn1; internalShadowCopy->biquad.xn2 = internalVPB->biquad.xn2; internalShadowCopy->biquad.yn1 = internalVPB->biquad.yn1; internalShadowCopy->biquad.yn2 = internalVPB->biquad.yn2; internalShadowCopy->biquad.b0 = internalVPB->biquad.b0; internalShadowCopy->biquad.b1 = internalVPB->biquad.b1; internalShadowCopy->biquad.b2 = internalVPB->biquad.b2; internalShadowCopy->biquad.a1 = internalVPB->biquad.a1; internalShadowCopy->biquad.a2 = internalVPB->biquad.a2; } if ((sync&AX_SYNCFLAG_VOICEREMOTEON) != 0) { // sync VoiceRmtOn (AXSetVoiceRmtOn) internalShadowCopy->reserved148_voiceRmtOn = internalVPB->reserved148_voiceRmtOn; } if ((sync&AX_SYNCFLAG_4000000) != 0) { // todo } if ((sync&AX_SYNCFLAG_8000000) != 0) { // todo // AXSetVoiceRmtSrc } // other flags todo: 0x10000000, 0x20000000, 0x40000000 for RmtIIR } void AXIst_SyncVPB(AXVPBInternal_t** lastProcessedDSPShadowCopy, AXVPBInternal_t** lastProcessedPPCShadowCopy) { __AXVoiceListSpinlock.lock(); AXVPBInternal_t* previousInternalDSP = nullptr; AXVPBInternal_t* previousInternalPPC = nullptr; for (sint32 priority = AX_PRIORITY_MAX - 1; priority >= AX_PRIORITY_LOWEST; priority--) { auto& voiceArray = AXVoiceList_GetListByPriority(priority); for(auto vpb : voiceArray) { sint32 index = vpb->index; sint32 depop = vpb->depop; AXVPBInternal_t* internalVPB = __AXVPBInternalVoiceArray + index; AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + index; internalVPB->mixerSelect = vpb->mixerSelect; AXVPB* nextVpb = vpb->next.GetPtr(); if (depop) { AXMix_DepopVoice(internalShadowCopy); vpb->depop = 0; } if (internalVPB->playbackState != _swapEndianU16(1) && vpb->sync == 0) { internalVPB->ukn2A2 = 2; internalShadowCopy->ukn2A2 = 2; internalVPB->nextAddrHigh = 0; internalVPB->nextAddrLow = 0; } else { internalVPB->ukn2A2 = 1; if (previousInternalPPC) { internalVPB->nextAddrHigh = previousInternalPPC->selfAddrHigh; internalVPB->nextAddrLow = previousInternalPPC->selfAddrLow; internalVPB->nextToProcess = previousInternalPPC; previousInternalPPC = internalVPB; } else { internalVPB->nextAddrHigh = 0; internalVPB->nextAddrLow = 0; internalVPB->nextToProcess = nullptr; previousInternalPPC = internalVPB; } AXIst_SyncSingleVPB(vpb); if (!AXVoiceProtection_IsProtectedByAnyThread(vpb)) { vpb->depop = 0; vpb->sync = 0; } } } } // depop and reset voices which just stopped playing auto& freeVoicesArray = AXVoiceList_GetFreeVoices(); for(auto vpb : freeVoicesArray) { AXVPBInternal_t* internalVPB = __AXVPBInternalVoiceArray + (sint32)vpb->index; AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + (sint32)vpb->index; if (vpb->depop != (uint32be)0) { AXMix_DepopVoice(internalShadowCopy); vpb->depop = 0; } vpb->playbackState = 0; internalVPB->ukn2A2 = 2; internalShadowCopy->ukn2A2 = 2; internalVPB->playbackState = 0; internalShadowCopy->playbackState = 0; } // return last processed DSP/PPC voice internal shadow copy if (lastProcessedDSPShadowCopy) { if (previousInternalDSP) { AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + (sint32)previousInternalDSP->index; *lastProcessedDSPShadowCopy = internalShadowCopy; } else *lastProcessedDSPShadowCopy = nullptr; } if (lastProcessedPPCShadowCopy) { if (previousInternalPPC) { AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + (sint32)previousInternalPPC->index; *lastProcessedPPCShadowCopy = internalShadowCopy; } else *lastProcessedPPCShadowCopy = nullptr; } __AXVoiceListSpinlock.unlock(); } void AXIst_HandleFrameCallbacks() { // frame callback if (__AXFrameCallback != MPTR_NULL) { // execute frame callback (no params) PPCCore_executeCallbackInternal(__AXFrameCallback); } // app frame callback for (sint32 i = 0; i < AX_APP_FRAME_CALLBACK_MAX; i++) { if (__AXAppFrameCallback[i] == MPTR_NULL) continue; // execute app frame callback (no params) PPCCore_executeCallbackInternal(__AXAppFrameCallback[i]); } } void AXIst_ApplyDeviceRemix(sint32be* samples, float32be* matrix, sint32 inputChannelCount, sint32 outputChannelCount, sint32 sampleCount) { for (auto i = 0; i < sampleCount; ++i) { float tmp[6]{}; for(auto j = 0; j < inputChannelCount; ++j) { tmp[j] = (float)samples[j * sampleCount + i]; } float32be* mtx = matrix; int tmpOut[10]{}; for(auto j = 0; j < outputChannelCount; ++j) { tmpOut[j] = 0; for (auto k = 0; k < inputChannelCount; ++k) { tmpOut[j] += (int)(tmp[k] * (*mtx)); mtx++; } } for (auto j = 0; j < outputChannelCount; ++j) { samples[j * sampleCount + i] = tmpOut[j]; } } } void AXIst_HandleDeviceRemix() { extern SysAllocator<AXRemixMatrices_t, 12> g_remix_matrices; extern sint32 __AXOutTVOutputChannelCount; extern sint32 __AXOutDRCOutputChannelCount; // tv remix matrix for(uint32 i = 0; i < g_remix_matrices.GetCount(); ++i) { const auto& entry = g_remix_matrices[i]; if(entry.deviceEntry[0].channelIn == __AXFinalMixCBStructTV->numChannelInput && entry.deviceEntry[0].channelOut == __AXOutTVOutputChannelCount && !entry.deviceEntry[0].matrix.IsNull()) { AXIst_ApplyDeviceRemix((sint32be*)__AXTVBuffer48.GetPtr(), entry.deviceEntry[0].matrix.GetPtr(), __AXFinalMixCBStructTV->numChannelInput, __AXOutTVOutputChannelCount, AX_SAMPLES_PER_3MS_48KHZ); break; } } // drc remix matrix for (uint32 i = 0; i < g_remix_matrices.GetCount(); ++i) { const auto& entry = g_remix_matrices[i]; if (entry.deviceEntry[1].channelIn == __AXFinalMixCBStructDRC->numChannelInput && entry.deviceEntry[1].channelOut == __AXOutDRCOutputChannelCount && !entry.deviceEntry[0].matrix.IsNull()) { AXIst_ApplyDeviceRemix((sint32be*)__AXDRCBuffer48.GetPtr(), entry.deviceEntry[1].matrix.GetPtr(), __AXFinalMixCBStructDRC->numChannelInput, __AXOutDRCOutputChannelCount, AX_SAMPLES_PER_3MS_48KHZ); break; } } } std::atomic_bool __AXIstIsProcessingFrame = false; SysAllocator<OSThread_t> __AXIstThread; SysAllocator<uint8, 0x4000> __AXIstThreadStack; SysAllocator<coreinit::OSMessage, 0x10> __AXIstThreadMsgArray; SysAllocator<coreinit::OSMessageQueue, 1> __AXIstThreadMsgQueue; void AXIst_InitThread() { __AXIstIsProcessingFrame = false; // create ist message queue OSInitMessageQueue(__AXIstThreadMsgQueue.GetPtr(), __AXIstThreadMsgArray.GetPtr(), 0x10); // create thread uint8 istThreadAttr = 0; coreinit::__OSCreateThreadType(__AXIstThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(AXIst_ThreadEntry), 0, &__AXIstThreadMsgQueue, __AXIstThreadStack.GetPtr() + 0x4000, 0x4000, 14, istThreadAttr, OSThread_t::THREAD_TYPE::TYPE_DRIVER); coreinit::OSResumeThread(__AXIstThread.GetPtr()); } OSThread_t* AXIst_GetThread() { return __AXIstThread.GetPtr(); } void AXIst_GenerateFrame() { // generate one frame (3MS) of audio __AXIstIsProcessingFrame.store(true); memset(__AXTVOutputBuffer.GetPtr(), 0, AX_SAMPLES_PER_3MS_48KHZ * AX_TV_CHANNEL_COUNT * sizeof(sint32)); memset(__AXDRCOutputBuffer.GetPtr(), 0, AX_SAMPLES_PER_3MS_48KHZ * AX_DRC_CHANNEL_COUNT * sizeof(sint32)); AXVPBInternal_t* internalShadowCopyDSPHead = nullptr; AXVPBInternal_t* internalShadowCopyPPCHead = nullptr; AXIst_SyncVPB(&internalShadowCopyDSPHead, &internalShadowCopyPPCHead); if (internalShadowCopyDSPHead) assert_dbg(); AXMix_process(internalShadowCopyPPCHead); AXOut_ResetFinalMixCBData(); AXIst_ProcessFinalMixCallback(); AXIst_HandleDeviceRemix(); // todo - additional phases. See unimplemented API: // AXSetDRCVSMode // AXSetDeviceCompressor // AXRegisterPostFinalMixCallback AXOut_SubmitTVFrame(0); AXOut_SubmitDRCFrame(0); __AXIstIsProcessingFrame.store(false); } void AXIst_ThreadEntry(PPCInterpreter_t* hCPU) { while (true) { StackAllocator<coreinit::OSMessage, 1> msg; OSReceiveMessage(__AXIstThreadMsgQueue.GetPtr(), msg.GetPointer(), OS_MESSAGE_BLOCK); if (msg.GetPointer()->message == 2) { cemuLog_logDebug(LogType::Force, "Shut down of AX thread requested"); coreinit::OSExitThread(0); break; } else if (msg.GetPointer()->message != 1) assert_dbg(); AXIst_GenerateFrame(); numProcessedFrames++; } } SysAllocator<coreinit::OSMessage> _queueFrameMsg; void AXIst_QueueFrame() { coreinit::OSMessage* msg = _queueFrameMsg.GetPtr(); msg->message = 1; msg->data0 = 0; msg->data1 = 0; msg->data2 = 0; OSSendMessage(__AXIstThreadMsgQueue.GetPtr(), msg, 0); } void AXIst_StopThread() { cemu_assert_debug(coreinit::OSIsThreadTerminated(AXIst_GetThread()) == false); // request thread stop coreinit::OSMessage* msg = _queueFrameMsg.GetPtr(); msg->message = 2; msg->data0 = 0; msg->data1 = 0; msg->data2 = 0; OSSendMessage(__AXIstThreadMsgQueue.GetPtr(), msg, 0); while (coreinit::OSIsThreadTerminated(AXIst_GetThread()) == false) PPCCore_switchToScheduler(); } bool AXIst_IsFrameBeingProcessed() { return __AXIstIsProcessingFrame.load(); } } ================================================ FILE: src/Cafe/OS/libs/snd_core/ax_mix.cpp ================================================ #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/snd_core/ax_internal.h" #include "Cafe/HW/MMU/MMU.h" #include "config/ActiveSettings.h" void mic_updateOnAXFrame(); namespace snd_core { uint32 __AXDefaultMixerSelect = AX_MIXER_SELECT_BOTH; uint16 __AXTVAuxReturnVolume[AX_AUX_BUS_COUNT]; void AXSetDefaultMixerSelect(uint32 mixerSelect) { __AXDefaultMixerSelect = mixerSelect; } uint32 AXGetDefaultMixerSelect() { return __AXDefaultMixerSelect; } void AXMix_Init() { AXSetDefaultMixerSelect(AX_MIXER_SELECT_PPC); } void AXMix_DepopVoice(AXVPBInternal_t* internalShadowCopy) { // todo } #define handleAdpcmDecodeLoop() \ if (internalShadowCopy->internalOffsets.loopFlag != 0) \ { \ scale = _swapEndianU16(internalShadowCopy->adpcmLoop.loopScale); \ delta = 1 << (scale & 0xF); \ coefIndex = (scale >> 4) & 7; \ coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]); \ coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]); \ playbackNibbleOffset = vpbLoopOffset; \ } \ else \ { \ /* no loop */ \ internalShadowCopy->playbackState = 0; \ memset(outputWriter, 0, sampleCount * sizeof(sint16)); \ break; \ } uint32 _AX_fixAdpcmEndOffset(uint32 adpcmOffset) { // How to Survive uses an end offset of 0x40000 which is not a valid adpcm sample offset and thus can never be reached (the ppc side decoder jumps from 0x3FFFF to 0x40002) // todo - investigate if the DSP decoder routines can handle invalid ADPCM end offsets // for now we use this workaround to force a valid address if ((adpcmOffset & 0xF) <= 1) return adpcmOffset + 2; return adpcmOffset; } void AX_readADPCMSamples(AXVPBInternal_t* internalShadowCopy, sint16* output, sint32 sampleCount) { if (internalShadowCopy->playbackState == 0) { memset(output, 0, sampleCount * sizeof(sint16)); return; } AXVPB* vpb = __AXVPBArrayPtr + (sint32)internalShadowCopy->index; uint8* sampleBase = (uint8*)memory_getPointerFromVirtualOffset(_swapEndianU32(vpb->offsets.samples)); uint32 vpbLoopOffset = _swapEndianU32(*(uint32*)&vpb->offsets.loopOffset); uint32 vpbEndOffset = _swapEndianU32(*(uint32*)&vpb->offsets.endOffset); vpbEndOffset = _AX_fixAdpcmEndOffset(vpbEndOffset); uint32 internalCurrentOffset = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh); uint32 internalLoopOffset = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh); sint32 scale = (sint32)_swapEndianU16(internalShadowCopy->adpcmData.scale); sint32 delta = 1 << (scale & 0xF); sint32 coefIndex = (scale >> 4) & 7; sint32 coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]); sint32 coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]); sint32 hist0 = (sint32)_swapEndianS16(internalShadowCopy->adpcmData.yn1); sint32 hist1 = (sint32)_swapEndianS16(internalShadowCopy->adpcmData.yn2); uint32 playbackNibbleOffset = vpbLoopOffset + (internalCurrentOffset - internalLoopOffset); uint32 playbackNibbleOffsetStart = playbackNibbleOffset; sint16* outputWriter = output; // decode samples from current partial ADPCM block while (sampleCount && (playbackNibbleOffset & 0xF)) { sint8 nibble = (sint8)sampleBase[(sint32)playbackNibbleOffset >> 1]; nibble <<= (4 * ((playbackNibbleOffset & 1))); nibble &= 0xF0; sint32 v = (sint32)nibble; v <<= (11 - 4); v *= delta; v += (hist0 * coefA + hist1 * coefB); v = (v + 0x400) >> 11; v = std::clamp(v, -32768, 32767); hist1 = hist0; hist0 = v; *outputWriter = v; outputWriter++; // check for loop / end offset if (playbackNibbleOffset == vpbEndOffset) { handleAdpcmDecodeLoop(); } else { playbackNibbleOffset++; } sampleCount--; } // optimized code to decode whole blocks if (!(playbackNibbleOffset <= vpbEndOffset && ((uint64)playbackNibbleOffset + (uint64)(sampleCount * 16 / 14)) >= (uint64)vpbEndOffset)) { while (sampleCount >= 14) // 14 samples per 16 byte block { // decode header sint8* sampleInputData = (sint8*)sampleBase + ((sint32)playbackNibbleOffset >> 1); scale = (uint8)sampleInputData[0]; delta = 1 << (scale & 0xF); coefIndex = (scale >> 4) & 7; coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]); coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]); playbackNibbleOffset += 2; // decode samples sampleInputData++; for (sint32 i = 0; i < 7; i++) { // n0 sint8 nibble = *sampleInputData; sampleInputData++; sint32 v; // upper nibble v = (sint32)(sint8)(nibble & 0xF0); v <<= (11 - 4); v *= delta; v += (hist0 * coefA + hist1 * coefB); v = (v + 0x400) >> 11; v = std::min(v, 32767); v = std::max(v, -32768); hist1 = hist0; hist0 = v; *outputWriter = v; outputWriter++; // lower nibble v = (sint32)(sint8)(nibble << 4); v <<= (11 - 4); v *= delta; v += (hist0 * coefA + hist1 * coefB); v = (v + 0x400) >> 11; v = std::min(v, 32767); v = std::max(v, -32768); hist1 = hist0; hist0 = v; *outputWriter = v; outputWriter++; } playbackNibbleOffset += 7 * 2; sampleCount -= 7 * 2; } } // decode remaining samples while (sampleCount) { if ((playbackNibbleOffset & 0xF) == 0) { scale = sampleBase[(sint32)playbackNibbleOffset >> 1]; delta = 1 << (scale & 0xF); coefIndex = (scale >> 4) & 7; coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]); coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]); playbackNibbleOffset += 2; } sint8 nibble = (sint8)sampleBase[(sint32)playbackNibbleOffset >> 1]; nibble <<= (4 * ((playbackNibbleOffset & 1))); nibble &= 0xF0; sint32 v = (sint32)nibble; v <<= (11 - 4); v *= delta; v += (hist0 * coefA + hist1 * coefB); v = (v + 0x400) >> 11; v = std::min(v, 32767); v = std::max(v, -32768); hist1 = hist0; hist0 = v; *outputWriter = v; outputWriter++; // check for loop / end offset if (playbackNibbleOffset == vpbEndOffset) { handleAdpcmDecodeLoop(); } else { playbackNibbleOffset++; } sampleCount--; } // write updated values internalShadowCopy->adpcmData.scale = _swapEndianU16(scale); internalShadowCopy->adpcmData.yn1 = (uint16)_swapEndianS16(hist0); internalShadowCopy->adpcmData.yn2 = (uint16)_swapEndianS16(hist1); uint32 newInternalCurrentOffset = internalCurrentOffset + (playbackNibbleOffset - playbackNibbleOffsetStart); *(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(newInternalCurrentOffset); } void AX_DecodeSamplesADPCM_NoSrc(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) { sint16 sampleBuffer[1024]; cemu_assert(sampleCount <= (sizeof(sampleBuffer) / sizeof(sampleBuffer[0]))); AX_readADPCMSamples(internalShadowCopy, sampleBuffer, sampleCount); for (sint32 i = 0; i < sampleCount; i++) { // decode next sample sint32 s = sampleBuffer[i]; s <<= 8; *output = (float)s; output++; } } void AX_DecodeSamplesADPCM_Linear(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) { uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac); uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh); sint16 historySamples[4]; historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]); historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]); historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]); historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]); sint32 historyIndex = 0; sint32 numberOfDecodedAdpcmSamples = (sint32)((currentFracPos + ratio * sampleCount) >> 16); sint16 adpcmSampleBuffer[4096]; if (numberOfDecodedAdpcmSamples >= 4096) { memset(output, 0, sizeof(float)*sampleCount); cemuLog_log(LogType::Force, "Too many ADPCM samples to decode. ratio = {:08x}", ratio); return; } AX_readADPCMSamples(internalShadowCopy, adpcmSampleBuffer, numberOfDecodedAdpcmSamples); sint32 readSampleCount = 0; if (ratio == 0x10000 && currentFracPos == 0) { // optimized path when accessing only fully aligned samples for (sint32 i = 0; i < sampleCount; i++) { cemu_assert_debug(readSampleCount < numberOfDecodedAdpcmSamples); // get next sample sint16 s = adpcmSampleBuffer[readSampleCount]; readSampleCount++; historyIndex = (historyIndex + 1) & 3; historySamples[historyIndex] = s; // use previous sample sint32 previousSample = historySamples[(historyIndex + 3) & 3]; sint32 p0 = previousSample << 8; *output = (float)p0; output++; } } else { for (sint32 i = 0; i < sampleCount; i++) { // move playback pos currentFracPos += ratio; // get more samples if needed while (currentFracPos >= 0x10000) { cemu_assert_debug(readSampleCount < numberOfDecodedAdpcmSamples); sint16 s = adpcmSampleBuffer[readSampleCount]; readSampleCount++; currentFracPos -= 0x10000; historyIndex = (historyIndex + 1) & 3; historySamples[historyIndex] = s; } // linear interpolation of current sample sint32 previousSample = historySamples[(historyIndex + 3) & 3]; sint32 nextSample = historySamples[historyIndex]; sint32 p0 = (sint32)previousSample * (sint32)(0x10000 - currentFracPos); sint32 p1 = (sint32)nextSample * (sint32)(currentFracPos); p0 >>= 7; p1 >>= 7; sint32 interpolatedSample = p0 + p1; interpolatedSample >>= 1; *output = (float)interpolatedSample; output++; } } cemu_assert_debug(readSampleCount == numberOfDecodedAdpcmSamples); // set variables internalShadowCopy->src.currentFrac = _swapEndianU16((uint16)(currentFracPos)); internalShadowCopy->src.historySamples[0] = _swapEndianS16(historySamples[(historyIndex + 0) & 3]); internalShadowCopy->src.historySamples[1] = _swapEndianS16(historySamples[(historyIndex + 1) & 3]); internalShadowCopy->src.historySamples[2] = _swapEndianS16(historySamples[(historyIndex + 2) & 3]); internalShadowCopy->src.historySamples[3] = _swapEndianS16(historySamples[(historyIndex + 3) & 3]); } void AX_DecodeSamplesADPCM_Tap(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) { // todo - implement this AX_DecodeSamplesADPCM_Linear(internalShadowCopy, output, sampleCount); } void AX_DecodeSamplesPCM8_Linear(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) { // get variables uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac); uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh); uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh); uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh); uint32 loopOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh); uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension); uint8* endOffsetAddr = memory_base + (endOffsetPtr | (ptrHighExtension << 29)); uint8* currentOffsetAddr = memory_base + (currentOffsetPtr | (ptrHighExtension << 29)); sint16 historySamples[4]; historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]); historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]); historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]); historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]); sint32 historyIndex = 0; for (sint32 i = 0; i<sampleCount; i++) { currentFracPos += ratio; while (currentFracPos >= 0x10000) { // read next sample historyIndex = (historyIndex + 1) & 3; if (internalShadowCopy->playbackState) { sint32 s = (sint32)(sint8)*currentOffsetAddr; s <<= 8; historySamples[historyIndex] = s; if (currentOffsetAddr == endOffsetAddr) { if (internalShadowCopy->internalOffsets.loopFlag) { // loop currentOffsetAddr = memory_base + (loopOffsetPtr | (ptrHighExtension << 29)); } else { // stop playing internalShadowCopy->playbackState = 0; } } else { currentOffsetAddr++; } } else { // voice not playing, read sample as 0 historySamples[historyIndex] = 0; } currentFracPos -= 0x10000; } // interpolate sample sint32 previousSample = historySamples[(historyIndex + 3) & 3]; sint32 nextSample = historySamples[historyIndex]; sint32 p0 = (sint32)previousSample * (sint32)(0x10000 - currentFracPos); sint32 p1 = (sint32)nextSample * (sint32)(currentFracPos); p0 >>= 7; p1 >>= 7; sint32 interpolatedSample = p0 + p1; interpolatedSample >>= 1; *output = (float)interpolatedSample; output++; } // set variables internalShadowCopy->src.currentFrac = _swapEndianU16((uint16)(currentFracPos)); internalShadowCopy->src.historySamples[0] = _swapEndianS16(historySamples[(historyIndex + 0) & 3]); internalShadowCopy->src.historySamples[1] = _swapEndianS16(historySamples[(historyIndex + 1) & 3]); internalShadowCopy->src.historySamples[2] = _swapEndianS16(historySamples[(historyIndex + 2) & 3]); internalShadowCopy->src.historySamples[3] = _swapEndianS16(historySamples[(historyIndex + 3) & 3]); // store current offset currentOffsetPtr = (uint32)((uint8*)currentOffsetAddr - memory_base); currentOffsetPtr &= 0x1FFFFFFF; // is this correct? *(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(currentOffsetPtr); } void AX_DecodeSamplesPCM8_Tap(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) { // todo - implement this AX_DecodeSamplesPCM8_Linear(internalShadowCopy, output, sampleCount); } void AX_DecodeSamplesPCM8_NoSrc(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) { // get variables uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac); uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh); uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh); uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh); uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension); uint8* endOffsetAddr = memory_base + (endOffsetPtr | (ptrHighExtension << 29)); uint8* currentOffsetAddr = memory_base + (currentOffsetPtr | (ptrHighExtension << 29)); sint16 historySamples[4]; historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]); historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]); historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]); historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]); cemu_assert_debug(false); // todo } void AX_DecodeSamplesPCM16_Linear(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) { uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac); uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh); uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh); uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh); uint32 loopOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh); uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension); uint16* endOffsetAddr = (uint16*)(memory_base + ((endOffsetPtr * 2) | (ptrHighExtension << 29))); uint16* currentOffsetAddr = (uint16*)(memory_base + ((currentOffsetPtr * 2) | (ptrHighExtension << 29))); uint16* loopOffsetAddrDebug = (uint16*)(memory_base + ((loopOffsetPtr * 2) | (ptrHighExtension << 29))); sint16 historySamples[4]; historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]); historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]); historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]); historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]); sint32 historyIndex = 0; for (sint32 i = 0; i < sampleCount; i++) { currentFracPos += ratio; while (currentFracPos >= 0x10000) { // read next sample historyIndex = (historyIndex + 1) & 3; if (internalShadowCopy->playbackState) { sint32 s = _swapEndianS16(*currentOffsetAddr); historySamples[historyIndex] = s; if (currentOffsetAddr == endOffsetAddr) { if (internalShadowCopy->internalOffsets.loopFlag) { // loop currentOffsetAddr = (uint16*)(memory_base + ((loopOffsetPtr * 2) | (ptrHighExtension << 29))); } else { // stop playing internalShadowCopy->playbackState = 0; } } else { currentOffsetAddr++; // increment pointer only if not at end offset } } else { // voice not playing -> sample is silent historySamples[historyIndex] = 0; } currentFracPos -= 0x10000; } // interpolate sample sint32 previousSample = historySamples[(historyIndex + 3) & 3]; sint32 nextSample = historySamples[historyIndex]; sint32 p0 = (sint32)previousSample * (sint32)(0x10000 - currentFracPos); sint32 p1 = (sint32)nextSample * (sint32)(currentFracPos); p0 >>= 7; p1 >>= 7; sint32 interpolatedSample = p0 + p1; interpolatedSample >>= 1; *output = (float)interpolatedSample; output++; } // set variables internalShadowCopy->src.currentFrac = _swapEndianU16((uint16)(currentFracPos)); internalShadowCopy->src.historySamples[0] = _swapEndianS16(historySamples[(historyIndex + 0) & 3]); internalShadowCopy->src.historySamples[1] = _swapEndianS16(historySamples[(historyIndex + 1) & 3]); internalShadowCopy->src.historySamples[2] = _swapEndianS16(historySamples[(historyIndex + 2) & 3]); internalShadowCopy->src.historySamples[3] = _swapEndianS16(historySamples[(historyIndex + 3) & 3]); // store current offset currentOffsetPtr = (uint32)((uint8*)currentOffsetAddr - memory_base); currentOffsetPtr &= 0x1FFFFFFF; currentOffsetPtr >>= 1; *(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(currentOffsetPtr); } void AX_DecodeSamplesPCM16_Tap(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) { // todo - implement this AX_DecodeSamplesPCM16_Linear(internalShadowCopy, output, sampleCount); } void AX_DecodeSamplesPCM16_NoSrc(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) { // get variables uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac); uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh); uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh); uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh); uint32 loopOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh); uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension); uint16* endOffsetAddr = (uint16*)(memory_base + (endOffsetPtr * 2 | (ptrHighExtension << 29))); uint16* currentOffsetAddr = (uint16*)(memory_base + (currentOffsetPtr * 2 | (ptrHighExtension << 29))); if (internalShadowCopy->playbackState == 0) { memset(output, 0, sizeof(float)*sampleCount); return; } for (sint32 i = 0; i < sampleCount; i++) { sint32 s = _swapEndianS16(*currentOffsetAddr); s <<= 8; output[i] = (float)s; if (currentOffsetAddr == endOffsetAddr) { if (internalShadowCopy->internalOffsets.loopFlag) { currentOffsetAddr = (uint16*)(memory_base + (loopOffsetPtr * 2 | (ptrHighExtension << 29))); } else { internalShadowCopy->playbackState = 0; for (; i < sampleCount; i++) { output[i] = 0.0f; } break; } } else currentOffsetAddr++; } // store current offset currentOffsetPtr = (uint32)((uint8*)currentOffsetAddr - memory_base); currentOffsetPtr &= 0x1FFFFFFF; currentOffsetPtr >>= 1; *(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(currentOffsetPtr); } void AXVoiceMix_DecodeSamples(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) { uint32 srcFilterMode = _swapEndianU16(internalShadowCopy->srcFilterMode); uint16 format = _swapEndianU16(internalShadowCopy->internalOffsets.format); if (srcFilterMode == AX_FILTER_MODE_TAP) { if (format == AX_FORMAT_ADPCM) AX_DecodeSamplesADPCM_Tap(internalShadowCopy, output, sampleCount); else if (format == AX_FORMAT_PCM16) AX_DecodeSamplesPCM16_Tap(internalShadowCopy, output, sampleCount); else if (format == AX_FORMAT_PCM8) AX_DecodeSamplesPCM8_Tap(internalShadowCopy, output, sampleCount); else cemu_assert_debug(false); } else if (srcFilterMode == AX_FILTER_MODE_LINEAR) { if (format == AX_FORMAT_ADPCM) AX_DecodeSamplesADPCM_Linear(internalShadowCopy, output, sampleCount); else if (format == AX_FORMAT_PCM16) AX_DecodeSamplesPCM16_Linear(internalShadowCopy, output, sampleCount); else if (format == AX_FORMAT_PCM8) AX_DecodeSamplesPCM8_Linear(internalShadowCopy, output, sampleCount); else cemu_assert_debug(false); } else if (srcFilterMode == AX_FILTER_MODE_NONE) { if (format == AX_FORMAT_ADPCM) AX_DecodeSamplesADPCM_NoSrc(internalShadowCopy, output, sampleCount); else if (format == AX_FORMAT_PCM16) AX_DecodeSamplesPCM16_NoSrc(internalShadowCopy, output, sampleCount); else if (format == AX_FORMAT_PCM8) AX_DecodeSamplesPCM8_NoSrc(internalShadowCopy, output, sampleCount); else cemu_assert_debug(false); } } sint32 AXVoiceMix_MergeInto(float* inputSamples, float* outputSamples, sint32 sampleCount, AXCHMIX_DEPR* mix, sint16 deltaI) { float vol = (float)_swapEndianU16(mix->vol) / (float)0x8000; if (deltaI != 0) { float delta = (float)deltaI / (float)0x8000; for (sint32 i = 0; i < sampleCount; i++) { vol += delta; outputSamples[i] += inputSamples[i] * vol; } } else { // optimized version for delta == 0.0 for (sint32 i = 0; i < sampleCount; i++) { outputSamples[i] += inputSamples[i] * vol; } } uint16 volI = (uint16)(vol * 32768.0f); mix->vol = _swapEndianU16(volI); return volI; } float __AXMixBufferTV[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT * AX_BUS_COUNT]; float __AXMixBufferDRC[2 * AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * AX_BUS_COUNT]; void AXVoiceMix_ApplyADSR(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount) { uint16 volume = internalShadowCopy->veVolume; sint16 volumeDelta = (sint16)internalShadowCopy->veDelta; if (volume == 0x8000 && volumeDelta == 0) return; float volumeScaler = (float)volume / 32768.0f; if (volumeDelta == 0) { // without delta for (sint32 i = 0; i < sampleCount; i++) sampleData[i] *= volumeScaler; return; } // with delta double volumeScalerDelta = (double)volumeDelta / 32768.0; volumeScalerDelta = volumeScalerDelta + volumeScalerDelta; for (sint32 i = 0; i < sampleCount; i++) { volumeScaler += (float)volumeScalerDelta; sampleData[i] *= volumeScaler; } if (volumeDelta != 0) { volume = (uint16)(volumeScaler * 32768.0); internalShadowCopy->veVolume = volume; } } void AXVoiceMix_ApplyBiquad(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount) { if (internalShadowCopy->biquad.on == AX_BIQUAD_OFF) return; #ifdef CEMU_DEBUG_ASSERT if (internalShadowCopy->biquad.on != 0x0200) { cemuLog_logDebug(LogType::Force, "AX_ApplyBiquad() with incorrect biquad.on value 0x{:04x}", _swapEndianU16(internalShadowCopy->biquad.on)); } #endif float a1 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.a1) / 16384.0f; float a2 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.a2) / 16384.0f; float b0 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.b0) / 16384.0f; float b1 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.b1) / 16384.0f; float b2 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.b2) / 16384.0f; float yn1 = (float)_swapEndianS16(internalShadowCopy->biquad.yn1); float yn2 = (float)_swapEndianS16(internalShadowCopy->biquad.yn2); float xn1 = (float)_swapEndianS16(internalShadowCopy->biquad.xn1); float xn2 = (float)_swapEndianS16(internalShadowCopy->biquad.xn2); if (internalShadowCopy->biquad.b1 != 0) { for (sint32 i = 0; i < sampleCount; i++) { float inputSample = sampleData[i] / 256.0f; float temp = b0 * inputSample + b1 * xn1 + b2 * xn2 + a1 * yn1 + a2 * yn2; sampleData[i] = temp * 256.0f; temp = std::min(32767.0f, temp); temp = std::max(-32768.0f, temp); yn2 = yn1; xn2 = xn1; yn1 = temp; xn1 = inputSample; } } else { // optimized variant where voiceInternal->biquad.b1 is hardcoded as zero (used heavily in BotW and Splatoon) for (sint32 i = 0; i < sampleCount; i++) { float inputSample = sampleData[i] / 256.0f; float temp = b0 * inputSample + b2 * xn2 + a1 * yn1 + a2 * yn2; sampleData[i] = temp * 256.0f; temp = std::min(32767.0f, temp); temp = std::max(-32768.0f, temp); yn2 = yn1; xn2 = xn1; yn1 = temp; xn1 = inputSample; } } internalShadowCopy->biquad.yn1 = _swapEndianU16((sint16)(yn1)); internalShadowCopy->biquad.yn2 = _swapEndianU16((sint16)(yn2)); internalShadowCopy->biquad.xn1 = _swapEndianU16((sint16)(xn1)); internalShadowCopy->biquad.xn2 = _swapEndianU16((sint16)(xn2)); } void AXVoiceMix_ApplyLowPass(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount) { if (internalShadowCopy->lpf.on == _swapEndianU16(AX_LPF_OFF)) return; float a0 = (float)_swapEndianS16(internalShadowCopy->lpf.a0) / 32767.0f; float b0 = (float)_swapEndianS16(internalShadowCopy->lpf.b0) / 32767.0f; float prevSample = (float)_swapEndianS16((sint16)internalShadowCopy->lpf.yn1) * 256.0f / 32767.0f; for (sint32 i = 0; i < sampleCount; i++) { sampleData[i] = a0 * sampleData[i] - b0 * prevSample; prevSample = sampleData[i]; } internalShadowCopy->lpf.yn1 = (uint16)_swapEndianS16((sint16)(prevSample / 256.0f * 32767.0f)); } // mix audio generated from voice into main bus and aux buses void AXVoiceMix_MixIntoBuses(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount, sint32 samplesPerFrame) { // TV mixing for (sint32 busIndex = 0; busIndex < AX_BUS_COUNT; busIndex++) { for (sint32 channel = 0; channel < 6; channel++) { uint32 channelMixMask = (_swapEndianU16(internalShadowCopy->deviceMixMaskTV[busIndex]) >> (channel * 2)) & 3; if (channelMixMask == 0) { internalShadowCopy->reserved1E8[busIndex*AX_TV_CHANNEL_COUNT + channel] = 0; continue; } AXCHMIX_DEPR* mix = internalShadowCopy->deviceMixTV + channel * 4 + busIndex; float* output = __AXMixBufferTV + (busIndex * 6 + channel) * samplesPerFrame; AXVoiceMix_MergeInto(sampleData, output, sampleCount, mix, _swapEndianS16(mix->delta)); internalShadowCopy->reserved1E8[busIndex*AX_TV_CHANNEL_COUNT + channel] = mix->vol; } } // DRC0 mixing for (sint32 busIndex = 0; busIndex < AX_BUS_COUNT; busIndex++) { for (sint32 channel = 0; channel < AX_DRC_CHANNEL_COUNT; channel++) { uint32 channelMixMask = (_swapEndianU16(internalShadowCopy->deviceMixMaskDRC[busIndex]) >> (channel * 2)) & 3; if (channelMixMask == 0) { //internalShadowCopy->reserved1E8[busIndex*AX_DRC_CHANNEL_COUNT + channel] = 0; continue; } AXCHMIX_DEPR* mix = internalShadowCopy->deviceMixDRC + channel * 4 + busIndex; float* output = __AXMixBufferDRC + (busIndex * AX_DRC_CHANNEL_COUNT + channel) * samplesPerFrame; AXVoiceMix_MergeInto(sampleData, output, sampleCount, mix, _swapEndianS16(mix->delta)); } } // DRC1 mixing + RMT mixing // todo } void AXMix_ProcessVoices(AXVPBInternal_t* firstVoice) { if (firstVoice == nullptr) return; size_t sampleCount = AXGetInputSamplesPerFrame(); AXVPBInternal_t* internalVoice = firstVoice; cemu_assert_debug(sndGeneric.initParam.frameLength == 0); float tmpSampleBuffer[AX_SAMPLES_MAX]; while (internalVoice) { AXVoiceMix_DecodeSamples(internalVoice, tmpSampleBuffer, sampleCount); AXVoiceMix_ApplyADSR(internalVoice, tmpSampleBuffer, sampleCount); AXVoiceMix_ApplyBiquad(internalVoice, tmpSampleBuffer, sampleCount); AXVoiceMix_ApplyLowPass(internalVoice, tmpSampleBuffer, sampleCount); AXVoiceMix_MixIntoBuses(internalVoice, tmpSampleBuffer, sampleCount, sampleCount); // next internalVoice = internalVoice->nextToProcess.GetPtr(); } } void AXMix_MergeBusSamples(float* input, sint32* output, sint32 sampleCount, uint16& volume, sint16 delta) { float volumeF = (float)volume / 32768.0f; float deltaF = (float)delta / 32768.0f; if (delta) { for (sint32 i = 0; i < sampleCount; i++) { float s = *input; input++; s *= volumeF; volumeF += deltaF; *output = _swapEndianS32(_swapEndianS32(*output) + (sint32)s); output++; } volume = (uint16)(volumeF * 32768.0f); } else { // no delta for (sint32 i = 0; i < sampleCount; i++) { float s = *input; input++; s *= volumeF; *output = _swapEndianS32(_swapEndianS32(*output) + (sint32)s); output++; } } } void AXAuxMix_StoreAuxSamples(float* input, sint32be* output, sint32 sampleCount) { // Not 100% sure why but we need to temporarily right shift the aux samples by 8 to get the sample range the games expect for the AUX callback // without this, Color Splash will apply it's effects incorrectly // Its probably because AUX mixing always goes through the DSP which uses 16bit arithmetic? // no delta for (sint32 i = 0; i < sampleCount; i++) { float s = *input; input++; *output = ((sint32)s) >> 8; output++; } } void AXAuxMix_MixProcessedAuxSamplesIntoOutput(sint32be* input, float* output, sint32 sampleCount, uint16* volumePtr, sint16 delta) { uint16 volume = *volumePtr; float volumeF = (float)volume / 32768.0f; float deltaF = (float)delta / 32768.0f; cemu_assert_debug(delta == 0); // todo for (sint32 i = 0; i < sampleCount; i++) { float s = (float)(((sint32)*input)<<8); input++; s *= volumeF; *output += s; output++; } *volumePtr = volume; } uint16 __AXMasterVolume = 0x8000; uint16 __AXDRCMasterVolume = 0x8000; // mix into __AXTVOutputBuffer void AXMix_mergeTVBuses() { size_t sampleCount = AXGetInputSamplesPerFrame(); // debug - Erase main bus and only output AUX if (ActiveSettings::AudioOutputOnlyAux()) { memset(__AXMixBufferTV, 0, sizeof(float) * sampleCount * 6); } // Mix aux into TV main bus for (sint32 auxBus = 0; auxBus < AX_AUX_BUS_COUNT; auxBus++) { sint32be* auxOutput = AXAux_GetOutputBuffer(AX_DEV_TV, 0, auxBus); if (auxOutput == nullptr) continue; // AUX return from output buffer uint16 auxReturnVolume = __AXTVAuxReturnVolume[auxBus]; sint16 auxReturnDelta = 0; AXAuxMix_MixProcessedAuxSamplesIntoOutput(auxOutput, __AXMixBufferTV, sampleCount * AX_TV_CHANNEL_COUNT, &auxReturnVolume, auxReturnDelta); } // mix TV main bus into output float* input = __AXMixBufferTV; uint16 masterVolume = __AXMasterVolume; sint32* output = __AXTVOutputBuffer.GetPtr(); cemu_assert_debug(masterVolume == 0x8000); // todo -> Calculate delta between old master volume and new volume sint16 delta = 0; uint16 volVar; for (uint16 c = 0; c < AX_TV_CHANNEL_COUNT; c++) { volVar = _swapEndianU16(masterVolume); AXMix_MergeBusSamples(input, output, sampleCount, masterVolume, delta); output += sampleCount; input += sampleCount; } } // mix into __AXDRCOutputBuffer void AXMix_mergeDRC0Buses() { sint32* output = __AXDRCOutputBuffer.GetPtr(); uint16 masterVolume = __AXDRCMasterVolume; size_t sampleCount = AXGetInputSamplesPerFrame(); // todo - drc0 AUX // mix DRC0 main bus into output float* input = __AXMixBufferDRC; cemu_assert_debug(masterVolume == 0x8000); // todo -> Calculate delta between old master volume and new volume sint16 delta = 0; for (uint16 c = 0; c < AX_DRC_CHANNEL_COUNT; c++) { AXMix_MergeBusSamples(input, output, sampleCount, masterVolume, delta); output += sampleCount; input += sampleCount; } } void AXMix_process(AXVPBInternal_t* internalShadowCopyHead) { memset(__AXMixBufferTV, 0, sizeof(__AXMixBufferTV)); memset(__AXMixBufferDRC, 0, sizeof(__AXMixBufferDRC)); AXMix_ProcessVoices(internalShadowCopyHead); AXAux_Process(); // apply AUX effects to previous frame AXIst_HandleFrameCallbacks(); size_t sampleCount = AXGetInputSamplesPerFrame(); // TV aux store for (sint32 auxBus = 0; auxBus < AX_AUX_BUS_COUNT; auxBus++) { sint32be* auxInput = AXAux_GetInputBuffer(AX_DEV_TV, 0, auxBus); if (auxInput == nullptr) continue; float* tvInput = __AXMixBufferTV + (1 + auxBus) * (sampleCount * AX_TV_CHANNEL_COUNT); AXAuxMix_StoreAuxSamples(tvInput, auxInput, sampleCount * AX_TV_CHANNEL_COUNT); } // DRC aux store // todo // merge main and aux buses AXMix_mergeTVBuses(); AXMix_mergeDRC0Buses(); AXAux_incrementBufferIndex(); // update microphone mic_updateOnAXFrame(); } } ================================================ FILE: src/Cafe/OS/libs/snd_core/ax_multivoice.cpp ================================================ #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/snd_core/ax_internal.h" #include "Cafe/HW/MMU/MMU.h" namespace snd_core { static_assert(sizeof(AXVPBMULTI) == 0x20, ""); SysAllocator<AXVPBMULTI, AX_MAX_VOICES> _buffer__AXVPBMultiVoiceArray; AXVPBMULTI* __AXVPBMultiVoiceArray; void AXMultiVoice_Init() { __AXVPBMultiVoiceArray = _buffer__AXVPBMultiVoiceArray.GetPtr(); for (sint32 i = 0; i < AX_MAX_VOICES; i++) { __AXVPBMultiVoiceArray[i].isUsed = 0; } } sint32 AXAcquireMultiVoice(sint32 voicePriority, void* cbFunc, void* cbData, AXMULTIVOICEUKNSTRUCT* uknR6, MEMPTR<AXVPBMULTI>* multiVoiceOut) { for (sint32 i = 0; i < AX_MAX_VOICES; i++) { if (__AXVPBMultiVoiceArray[i].isUsed == (uint32)0) { sint16 channelCount = uknR6->channelCount; if (channelCount <= 0 || channelCount > 6) { return -0x15; } for (sint32 f = 0; f < channelCount; f++) { __AXVPBMultiVoiceArray[i].voice[f] = nullptr; } __AXVPBMultiVoiceArray[i].isUsed = 1; for (sint32 f = 0; f < channelCount; f++) { AXVPB* vpb = AXAcquireVoiceEx(voicePriority, memory_getVirtualOffsetFromPointer(cbFunc), memory_getVirtualOffsetFromPointer(cbData)); if (vpb == nullptr) { AXFreeMultiVoice(__AXVPBMultiVoiceArray + i); return -0x16; } __AXVPBMultiVoiceArray[i].voice[f] = vpb; } __AXVPBMultiVoiceArray[i].channelCount = channelCount; *multiVoiceOut = (__AXVPBMultiVoiceArray+i); return 0; } } return -0x14; } void AXFreeMultiVoice(AXVPBMULTI* multiVoice) { cemu_assert_debug(multiVoice->isUsed != (uint32)0); uint16 numChannels = multiVoice->channelCount; for (uint16 i = 0; i < numChannels; i++) { if(multiVoice->voice[i] != nullptr) AXFreeVoice(multiVoice->voice[i].GetPtr()); multiVoice->voice[i] = nullptr; } multiVoice->isUsed = 0; } sint32 AXGetMultiVoiceReformatBufferSize(sint32 voiceFormat, uint32 channelCount, uint32 sizeInBytes, uint32be* sizeOutput) { // used by Axiom Verge if (voiceFormat == AX_FORMAT_ADPCM) { sint32 alignedSize = (sizeInBytes + 7) & ~7; *sizeOutput = alignedSize * channelCount; } else if (voiceFormat == AX_FORMAT_PCM16) { *sizeOutput = sizeInBytes; } else if (voiceFormat == AX_FORMAT_PCM8) { *sizeOutput = sizeInBytes<<1; } else return -23; return 0; } void AXSetMultiVoiceType(AXVPBMULTI* mv, uint16 type) { for(uint32 i = 0; i < mv->channelCount; ++i) AXSetVoiceType(mv->voice[i].GetPtr(), type); } void AXSetMultiVoiceAdpcm(AXVPBMULTI* mv, AXDSPADPCM* adpcm) { static_assert(sizeof(AXDSPADPCM) == 0x60); for (uint32 i = 0; i < mv->channelCount; ++i) { AXPBADPCM_t tmp; tmp.gain = adpcm[i].gain.bevalue(); tmp.yn1 = adpcm[i].yn1.bevalue(); tmp.yn2 = adpcm[i].yn2.bevalue(); tmp.scale = adpcm[i].scale.bevalue(); static_assert(sizeof(tmp.a) == sizeof(adpcm->coef)); memcpy(tmp.a, adpcm[i].coef, sizeof(tmp.a)); AXSetVoiceAdpcm(mv->voice[i].GetPtr(), &tmp); } } void AXSetMultiVoiceSrcType(AXVPBMULTI* mv, uint32 type) { for (uint32 i = 0; i < mv->channelCount; ++i) AXSetVoiceSrcType(mv->voice[i].GetPtr(), type); } void AXSetMultiVoiceOffsets(AXVPBMULTI* mv, AXPBOFFSET_t* offsets) { for (uint32 i = 0; i < mv->channelCount; ++i) AXSetVoiceOffsets(mv->voice[i].GetPtr(), offsets + i); } void AXSetMultiVoiceVe(AXVPBMULTI* mv, AXPBVE* ve) { for (uint32 i = 0; i < mv->channelCount; ++i) AXSetVoiceVe(mv->voice[i].GetPtr(), ve); } void AXSetMultiVoiceSrcRatio(AXVPBMULTI* mv, float ratio) { for (uint32 i = 0; i < mv->channelCount; ++i) AXSetVoiceSrcRatio(mv->voice[i].GetPtr(), ratio); } void AXSetMultiVoiceSrc(AXVPBMULTI* mv, AXPBSRC_t* src) { for (uint32 i = 0; i < mv->channelCount; ++i) AXSetVoiceSrc(mv->voice[i].GetPtr(), src); } void AXSetMultiVoiceLoop(AXVPBMULTI* mv, uint16 loop) { for (uint32 i = 0; i < mv->channelCount; ++i) AXSetVoiceLoop(mv->voice[i].GetPtr(), loop); } void AXSetMultiVoiceState(AXVPBMULTI* mv, uint16 state) { for (uint32 i = 0; i < mv->channelCount; ++i) AXSetVoiceState(mv->voice[i].GetPtr(), state); } void AXSetMultiVoiceAdpcmLoop(AXVPBMULTI* mv, AXPBADPCMLOOP_t* loops) { for (uint32 i = 0; i < mv->channelCount; ++i) AXSetVoiceAdpcmLoop(mv->voice[i].GetPtr(), loops + i); } sint32 AXIsMultiVoiceRunning(AXVPBMULTI* mv) { const sint32 result = AXIsVoiceRunning(mv->voice[0].GetPtr()); return result; } } ================================================ FILE: src/Cafe/OS/libs/snd_core/ax_out.cpp ================================================ #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/snd_core/ax_internal.h" #include "Cafe/HW/MMU/MMU.h" #include "audio/IAudioAPI.h" //#include "ax.h" #include "config/CemuConfig.h" namespace snd_core { uint32 numProcessedFrames = 0; void resetNumProcessedFrames() { numProcessedFrames = 0; } uint32 getNumProcessedFrames() { return numProcessedFrames; } sint32 __AXMode[AX_DEV_COUNT]; // audio mode (AX_MODE_*) per device bool AVMGetTVAudioMode(uint32be* tvAudioMode) { // 0 -> mono // 1,2 -> stereo // 3 -> surround // 4 -> unknown mode switch (GetConfig().tv_channels) { case kMono: *tvAudioMode = 0; break; case kSurround: *tvAudioMode = 3; break; default: *tvAudioMode = 2; break; } return true; } bool AVMGetDRCSystemAudioMode(uint32be* drcAudioMode) { *drcAudioMode = 1; // apparently the default is Stereo(?), MH3U exits if AXGetDeviceMode doesn't return 0 (DRCSystemAudioMode must return 1 to set DRC mode to 0) return true; } sint32 __AXOutTVOutputChannelCount; sint32 __AXOutDRCOutputChannelCount; void __AXSetTVMode(sint32 mode) { cemu_assert(mode == AX_MODE_STEREO || mode == AX_MODE_6CH || mode == AX_MODE_MONO); __AXMode[AX_DEV_TV] = mode; } void __AXSetDeviceMode(sint32 device, sint32 mode) { if (device == AX_DEV_TV) __AXMode[AX_DEV_TV] = mode; else if (device == AX_DEV_DRC) __AXMode[AX_DEV_DRC] = mode; else if (device == AX_DEV_RMT) __AXMode[AX_DEV_RMT] = mode; else { cemu_assert_debug(false); } } sint32 AXGetDeviceMode(sint32 device) { if (device == AX_DEV_TV || device == AX_DEV_DRC || device == AX_DEV_RMT) return __AXMode[device]; cemu_assert_debug(false); return 0; } void _AXOutInitDeviceModes() { // TV mode uint32be tvAudioMode; AVMGetTVAudioMode(&tvAudioMode); if (tvAudioMode == 0) { // mono __AXSetTVMode(AX_MODE_MONO); __AXOutTVOutputChannelCount = 1; } else if (tvAudioMode == 1 || tvAudioMode == 2) { // stereo __AXSetTVMode(AX_MODE_STEREO); __AXOutTVOutputChannelCount = 2; } else if (tvAudioMode == 3) { // surround (6ch) __AXSetTVMode(AX_MODE_6CH); __AXOutTVOutputChannelCount = 6; } else { assert_dbg(); } // DRC mode uint32be drcAudioMode; AVMGetDRCSystemAudioMode(&drcAudioMode); if (drcAudioMode == 0) { // mono __AXSetDeviceMode(1, AX_MODE_MONO); __AXOutDRCOutputChannelCount = 1; } else if (drcAudioMode == 2) { // surround __AXSetDeviceMode(1, AX_MODE_SURROUND); __AXOutDRCOutputChannelCount = 2; // output channel count still 2 for DRC 'surround' } else if (drcAudioMode == 1) { // stereo __AXSetDeviceMode(1, AX_MODE_STEREO); __AXOutDRCOutputChannelCount = 2; } else { assert_dbg(); } } void AXOut_Init() { _AXOutInitDeviceModes(); } extern SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTVBuffer48; extern SysAllocator<sint32, AX_SAMPLES_MAX* AX_DRC_CHANNEL_COUNT * 2> __AXDRCBuffer48; sint16 __buf_AXTVDMABuffers_0[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT]; sint16 __buf_AXTVDMABuffers_1[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT]; sint16 __buf_AXTVDMABuffers_2[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT]; sint16* __AXTVDMABuffers[3] = {__buf_AXTVDMABuffers_0, __buf_AXTVDMABuffers_1, __buf_AXTVDMABuffers_2}; #define AX_FRAMES_PER_GROUP (4) sint16 tempTVChannelData[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT * AX_FRAMES_PER_GROUP] = {}; sint32 tempAudioBlockCounter = 0; sint16 __buf_AXDRCDMABuffers_0[AX_SAMPLES_MAX * 6]; sint16 __buf_AXDRCDMABuffers_1[AX_SAMPLES_MAX * 6]; sint16 __buf_AXDRCDMABuffers_2[AX_SAMPLES_MAX * 6]; sint16* __AXDRCDMABuffers[3] = { __buf_AXDRCDMABuffers_0, __buf_AXDRCDMABuffers_1, __buf_AXDRCDMABuffers_2 }; sint16 tempDRCChannelData[AX_SAMPLES_MAX * 6 * AX_FRAMES_PER_GROUP] = {}; sint32 tempDRCAudioBlockCounter = 0; void AIInitDMA(sint16* sampleData, sint32 size) { sint32 sampleCount = size / sizeof(sint16); // sample count in total (summed up for all channels) if (sndGeneric.initParam.frameLength != 0) { cemu_assert(false); } std::shared_lock lock(g_audioMutex); const uint32 channels = g_tvAudio ? g_tvAudio->GetChannels() : AX_TV_CHANNEL_COUNT; sint16* outputChannel = tempTVChannelData + AX_SAMPLES_PER_3MS_48KHZ * tempAudioBlockCounter * channels; for (sint32 i = 0; i < sampleCount; ++i) { outputChannel[i] = _swapEndianS16(sampleData[i]); } tempAudioBlockCounter++; if (tempAudioBlockCounter == AX_FRAMES_PER_GROUP) { if(g_tvAudio) g_tvAudio->FeedBlock(tempTVChannelData); tempAudioBlockCounter = 0; } } sint32 AIGetSamplesPerChannel(uint32 device) { // TV and DRC output the same number of samples return AX_SAMPLES_PER_3MS_48KHZ; } sint32 AIGetChannelCount(uint32 device) { if (__AXMode[device] == AX_MODE_6CH) return 6; if (__AXMode[device] == AX_MODE_STEREO) return 2; // default to mono return 1; } sint16* AIGetCurrentDMABuffer(uint32 device) { if (device == AX_DEV_TV) return __AXTVDMABuffers[0]; else if (device == AX_DEV_DRC) return __AXDRCDMABuffers[0]; cemu_assert_debug(false); return nullptr; } void AXOut_SubmitTVFrame(sint32 frameIndex) { sint32 numSamples = AIGetSamplesPerChannel(AX_DEV_TV); if (__AXMode[AX_DEV_TV] == AX_MODE_6CH) { sint32* inputChannel0 = __AXTVBuffer48.GetPtr() + numSamples * 0; sint32* inputChannel1 = __AXTVBuffer48.GetPtr() + numSamples * 1; sint32* inputChannel2 = __AXTVBuffer48.GetPtr() + numSamples * 2; sint32* inputChannel3 = __AXTVBuffer48.GetPtr() + numSamples * 3; sint32* inputChannel4 = __AXTVBuffer48.GetPtr() + numSamples * 4; sint32* inputChannel5 = __AXTVBuffer48.GetPtr() + numSamples * 5; sint16* dmaOutputBuffer = AIGetCurrentDMABuffer(AX_DEV_TV); for (sint32 i = 0; i < numSamples; i++) { /* * DirectSound surround order LEFT 0 RIGHT 1 SUR_LEFT 2 SUR_RIGHT 3 CH_FC 4 CH_LFE 5 => Front Left - FL 0 Front Right - FR 1 Front Center - FC 2 Low Frequency - LF 3 Back Left - BL 4 Back Right - BR 5 */ dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767)); dmaOutputBuffer[4] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel2), -32768), 32767)); dmaOutputBuffer[5] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel3), -32768), 32767)); dmaOutputBuffer[2] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel4), -32768), 32767)); dmaOutputBuffer[3] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel5), -32768), 32767)); dmaOutputBuffer += 6; // next sample inputChannel0++; inputChannel1++; inputChannel2++; inputChannel3++; inputChannel4++; inputChannel5++; } AIInitDMA(__AXTVDMABuffers[frameIndex], numSamples * 6 * sizeof(sint16)); // 6ch output } else if (__AXMode[AX_DEV_TV] == AX_MODE_STEREO) { sint32* inputChannel0 = __AXTVBuffer48.GetPtr() + numSamples * 0; sint32* inputChannel1 = __AXTVBuffer48.GetPtr() + numSamples * 1; sint16* dmaOutputBuffer = __AXTVDMABuffers[frameIndex]; for (sint32 i = 0; i < numSamples; i++) { dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767)); dmaOutputBuffer += 2; // next sample inputChannel0++; inputChannel1++; } AIInitDMA(__AXTVDMABuffers[frameIndex], numSamples * 2 * sizeof(sint16)); // 2ch output } else if (__AXMode[AX_DEV_TV] == AX_MODE_MONO) { sint32* inputChannel0 = __AXTVBuffer48.GetPtr() + numSamples * 0; sint16* dmaOutputBuffer = __AXTVDMABuffers[frameIndex]; for (sint32 i = 0; i < numSamples; i++) { dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); dmaOutputBuffer++; // next sample inputChannel0++; } AIInitDMA(__AXTVDMABuffers[frameIndex], numSamples * 1 * sizeof(sint16)); // 1ch (output as stereo) } else assert_dbg(); } void AIInitDRCDMA(sint16* sampleData, sint32 size) { sint32 sampleCount = size / sizeof(sint16); // sample count in total (summed up for all channels) if (sndGeneric.initParam.frameLength != 0) { cemu_assert(false); } std::shared_lock lock(g_audioMutex); const uint32 channels = g_padAudio ? g_padAudio->GetChannels() : AX_DRC_CHANNEL_COUNT; sint16* outputChannel = tempDRCChannelData + AX_SAMPLES_PER_3MS_48KHZ * tempDRCAudioBlockCounter * channels; for (sint32 i = 0; i < sampleCount; ++i) { outputChannel[i] = _swapEndianS16(sampleData[i]); } tempDRCAudioBlockCounter++; if (tempDRCAudioBlockCounter == AX_FRAMES_PER_GROUP) { if (g_padAudio) g_padAudio->FeedBlock(tempDRCChannelData); tempDRCAudioBlockCounter = 0; } } void AXOut_SubmitDRCFrame(sint32 frameIndex) { sint32 numSamples = AIGetSamplesPerChannel(AX_DEV_DRC); if (__AXMode[AX_DEV_DRC] == AX_MODE_6CH) { sint32* inputChannel0 = __AXDRCBuffer48.GetPtr() + numSamples * 0; sint32* inputChannel1 = __AXDRCBuffer48.GetPtr() + numSamples * 1; sint32* inputChannel2 = __AXDRCBuffer48.GetPtr() + numSamples * 2; sint32* inputChannel3 = __AXDRCBuffer48.GetPtr() + numSamples * 3; sint16* dmaOutputBuffer = AIGetCurrentDMABuffer(AX_DEV_DRC); for (sint32 i = 0; i < numSamples; i++) { dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767)); dmaOutputBuffer[4] = 0; dmaOutputBuffer[5] = 0; dmaOutputBuffer[2] = 0; dmaOutputBuffer[3] = 0; dmaOutputBuffer += 6; // next sample inputChannel0++; inputChannel1++; inputChannel2++; inputChannel3++; } AIInitDRCDMA(__AXDRCDMABuffers[frameIndex], numSamples * 6 * sizeof(sint16)); // 6ch output } else if (__AXMode[AX_DEV_DRC] == AX_MODE_STEREO) { sint32* inputChannel0 = __AXDRCBuffer48.GetPtr() + numSamples * 0; sint32* inputChannel1 = __AXDRCBuffer48.GetPtr() + numSamples * 1; sint16* dmaOutputBuffer = __AXDRCDMABuffers[frameIndex]; for (sint32 i = 0; i < numSamples; i++) { dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767)); dmaOutputBuffer += 2; // next sample inputChannel0++; inputChannel1++; } AIInitDRCDMA(__AXDRCDMABuffers[frameIndex], numSamples * 2 * sizeof(sint16)); // 2ch output } else if (__AXMode[AX_DEV_DRC] == AX_MODE_MONO) { sint32* inputChannel0 = __AXDRCBuffer48.GetPtr() + numSamples * 0; sint16* dmaOutputBuffer = __AXDRCDMABuffers[frameIndex]; for (sint32 i = 0; i < numSamples; i++) { // write mono input as stereo output dmaOutputBuffer[1] = dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); dmaOutputBuffer += 2; // next sample inputChannel0++; } AIInitDRCDMA(__AXDRCDMABuffers[frameIndex], numSamples * 2 * sizeof(sint16)); // 1ch (output as stereo) } else assert_dbg(); } /* AX output */ uint32 numQueuedFramesSndGeneric = 0; void AXOut_init() { numQueuedFramesSndGeneric = 0; std::unique_lock lock(g_audioMutex); if (!g_tvAudio) { try { g_tvAudio = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::TV, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); } catch (std::runtime_error& ex) { cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what()); } } g_padVolume = GetConfig().pad_volume; if (!g_padAudio) { try { g_padAudio = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::Gamepad, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); } catch (std::runtime_error& ex) { cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what()); } } } void AXOut_reset() { std::unique_lock lock(g_audioMutex); if (g_tvAudio) { g_tvAudio->Stop(); g_tvAudio.reset(); } if (g_padAudio) { g_padAudio->Stop(); g_padAudio.reset(); } if (g_portalAudio) { g_portalAudio->Stop(); g_portalAudio.reset(); } } void AXOut_updateDevicePlayState(bool isPlaying) { std::shared_lock lock(g_audioMutex); if (g_tvAudio) { if (isPlaying) g_tvAudio->Play(); else g_tvAudio->Stop(); } if (g_padAudio) { if (isPlaying) g_padAudio->Play(); else g_padAudio->Stop(); } if (g_portalAudio) { if (isPlaying) g_portalAudio->Play(); else g_portalAudio->Stop(); } } // called periodically to check for AX updates void AXOut_update() { constexpr static auto kTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(((IAudioAPI::kBlockCount * 3) / 4) * (AX_FRAMES_PER_GROUP * 3))); constexpr static auto kWaitDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(3)); constexpr static auto kWaitDurationFast = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(2900)); constexpr static auto kWaitDurationMinimum = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(1700)); // if we haven't buffered any blocks, we will wait less time than usual bool additional_blocks_required = false; { const std::shared_lock lock(g_audioMutex, std::try_to_lock); if (lock) additional_blocks_required = (g_tvAudio && g_tvAudio->NeedAdditionalBlocks()) || (g_padAudio && g_padAudio->NeedAdditionalBlocks()); } const auto wait_duration = additional_blocks_required ? kWaitDurationFast : kWaitDuration; // s_ax_interval_timer increases by the wait period // it can lag behind by multiple periods (up to kTimeout) if there is minor stutter in the CPU thread // s_last_check is always set to the timestamp at the time of firing // it's used to enforce the minimum wait delay (we want to avoid calling AX update in quick succession because other threads may need to do work first) static auto s_ax_interval_timer = now_cached() - kWaitDuration; static auto s_last_check = now_cached(); const auto now = now_cached(); const auto diff = (now - s_ax_interval_timer); if (diff < wait_duration) return; // handle minimum wait time (1.7MS) if ((now - s_last_check) < kWaitDurationMinimum) return; s_last_check = now; // if we're too far behind, skip forward if (diff >= kTimeout) s_ax_interval_timer = (now - wait_duration); else s_ax_interval_timer += wait_duration; if (snd_core::isInitialized()) { if (numQueuedFramesSndGeneric == snd_core::getNumProcessedFrames()) { AXOut_updateDevicePlayState(true); snd_core::AXIst_QueueFrame(); numQueuedFramesSndGeneric++; } } } } ================================================ FILE: src/Cafe/OS/libs/snd_core/ax_voice.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/snd_core/ax_internal.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "util/helpers/fspinlock.h" namespace snd_core { inline void AXSetSyncFlag(AXVPB* vpb, uint32 flags) { vpb->sync = (uint32be)((uint32)vpb->sync | flags); } inline void AXResetSyncFlag(AXVPB* vpb, uint32 flags) { vpb->sync = (uint32be)((uint32)vpb->sync & ~flags); } /* voice lists */ FSpinlock __AXVoiceListSpinlock; std::vector<AXVPB*> __AXVoicesPerPriority[AX_PRIORITY_MAX]; std::vector<AXVPB*> __AXFreeVoices; void AXVoiceList_AddFreeVoice(AXVPB* vpb) { cemu_assert(vpb->priority != AX_PRIORITY_FREE); __AXFreeVoices.push_back(vpb); vpb->prev = nullptr; vpb->next = nullptr; } AXVPB* AXVoiceList_GetFreeVoice() { if (__AXFreeVoices.empty()) return nullptr; AXVPB* vpb = __AXFreeVoices.back(); __AXFreeVoices.pop_back(); return vpb; } std::vector<AXVPB*>& AXVoiceList_GetFreeVoices() { return __AXFreeVoices; } void AXVoiceList_AddVoice(AXVPB* vpb, sint32 priority) { cemu_assert(priority != AX_PRIORITY_FREE && priority < AX_PRIORITY_MAX); __AXVoicesPerPriority[priority].push_back(vpb); vpb->next = nullptr; vpb->prev = nullptr; vpb->priority = priority; } void AXVoiceList_RemoveVoice(AXVPB* vpb) { uint32 priority = (uint32)vpb->priority; cemu_assert(priority != AX_PRIORITY_FREE && priority < AX_PRIORITY_MAX); vectorRemoveByValue(__AXVoicesPerPriority[priority], vpb); } AXVPB* AXVoiceList_GetLeastRecentVoiceByPriority(uint32 priority) { cemu_assert(priority != AX_PRIORITY_FREE && priority < AX_PRIORITY_MAX); if (__AXVoicesPerPriority[priority].empty()) return nullptr; return __AXVoicesPerPriority[priority].front(); } std::vector<AXVPB*>& AXVoiceList_GetListByPriority(uint32 priority) { cemu_assert(priority != AX_PRIORITY_FREE && priority < AX_PRIORITY_MAX); return __AXVoicesPerPriority[priority]; } void AXVoiceList_Reset() { __AXFreeVoices.clear(); for (uint32 i = 0; i < AX_PRIORITY_MAX; i++) __AXVoicesPerPriority[i].clear(); } SysAllocator<AXVPBInternal_t, AX_MAX_VOICES> _buffer__AXVPBInternalVoiceArray; AXVPBInternal_t* __AXVPBInternalVoiceArray; SysAllocator<AXVPBInternal_t, AX_MAX_VOICES> _buffer__AXVPBInternalVoiceShadowCopyArray; AXVPBInternal_t* __AXVPBInternalVoiceShadowCopyArrayPtr; // this is the array used by audio mixing (it's synced at the beginning of every audio frame with __AXVPBInternalVoiceArray) SysAllocator<AXVPBItd, AX_MAX_VOICES> _buffer__AXVPBItdArray; AXVPBItd* __AXVPBItdArrayPtr; SysAllocator<AXVPB, AX_MAX_VOICES> _buffer__AXVPBArray; AXVPB* __AXVPBArrayPtr; struct AXUSERPROTECTION { MPTR threadMPTR; uint32 count; }; uint32 __AXUserProtectionArraySize = 0; AXUSERPROTECTION __AXUserProtectionArray[AX_MAX_VOICES] = { 0 }; AXUSERPROTECTION __AXVoiceProtection[AX_MAX_VOICES] = { 0 }; bool AXUserIsProtected() { return __AXUserProtectionArraySize != 0; } sint32 AXUserBegin() { // some games (e.g. Color Splash) can block themselves from calling AXSetVoice* API in time if a thread gets // rescheduled while inside a AXUserBegin() + AXUserEnd() block // to prevent this from happening we extend the current thread's quantum while the protection is raised PPCCore_boostQuantum(10000); if (AXIst_IsFrameBeingProcessed()) { return -2; } MPTR currentThreadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); for (sint32 i = __AXUserProtectionArraySize - 1; i >= 0; i--) { if (__AXUserProtectionArray[i].threadMPTR == currentThreadMPTR) { sint32 newCount = __AXUserProtectionArray[i].count + 1; __AXUserProtectionArray[i].count = newCount; return newCount; } } // no matching entry found if (__AXUserProtectionArraySize >= AX_MAX_VOICES) { // no entry available return -4; } // create new entry if (__AXUserProtectionArraySize < 0) assert_dbg(); sint32 entryIndex = __AXUserProtectionArraySize; __AXUserProtectionArray[entryIndex].threadMPTR = currentThreadMPTR; __AXUserProtectionArray[entryIndex].count = 1; __AXUserProtectionArraySize++; return 1; } sint32 AXUserEnd() { PPCCore_deboostQuantum(10000); if (AXIst_IsFrameBeingProcessed()) return -2; MPTR currentThreadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); for (sint32 i = __AXUserProtectionArraySize - 1; i >= 0; i--) { if (__AXUserProtectionArray[i].threadMPTR == currentThreadMPTR) { sint32 newCount = __AXUserProtectionArray[i].count - 1; __AXUserProtectionArray[i].count = newCount; if (__AXUserProtectionArray[i].count == 0) { // count == 0 -> remove entry if (i >= (sint32)(__AXUserProtectionArraySize - 1)) { // entry is last in array, can just decrease array size __AXUserProtectionArraySize--; __AXUserProtectionArray[i].threadMPTR = MPTR_NULL; } else { // remove entry by shifting all remaining entries //for (sint32 f = i; f >= 0; f--) //{ // __AXUserProtectionArray[f].threadMPTR = __AXUserProtectionArray[f + 1].threadMPTR; // __AXUserProtectionArray[f].count = __AXUserProtectionArray[f + 1].count; //} cemu_assert_debug(false); } // remove entries associated with the current thread from __AXVoiceProtection[] if the count is zero for (sint32 f = 0; f < AX_MAX_VOICES; f++) { if (__AXVoiceProtection[f].threadMPTR == currentThreadMPTR && __AXVoiceProtection[f].count == 0) { __AXVoiceProtection[f].threadMPTR = MPTR_NULL; } } } return newCount; } } cemu_assert_debug(false); // voice not found in list, did the game not call AXUserBegin()? return -3; } bool AXVoiceProtection_IsProtectedByAnyThread(AXVPB* vpb) { sint32 index = vpb->index; return __AXVoiceProtection[index].threadMPTR != MPTR_NULL; } bool AXVoiceProtection_IsProtectedByCurrentThread(AXVPB* vpb) { sint32 index = vpb->index; bool isProtected = false; if (AXIst_IsFrameBeingProcessed()) isProtected = __AXVoiceProtection[index].threadMPTR != MPTR_NULL; else isProtected = __AXVoiceProtection[index].threadMPTR != memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); return isProtected; } void AXVoiceProtection_Acquire(AXVPB* vpb) { sint32 index = vpb->index; if (AXUserIsProtected() == false) return; if (AXIst_IsFrameBeingProcessed()) return; if (__AXVoiceProtection[index].threadMPTR == MPTR_NULL) { __AXVoiceProtection[index].threadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); // does not set count? } } void AXVoiceProtection_Release(AXVPB* vpb) { sint32 index = vpb->index; __AXVoiceProtection[index].threadMPTR = MPTR_NULL; __AXVoiceProtection[index].count = 0; } sint32 AXVoiceBegin(AXVPB* voice) { if (voice == nullptr) { cemuLog_log(LogType::Force, "AXVoiceBegin(): Invalid voice"); return -1; } uint32 index = (uint32)voice->index; if (index >= AX_MAX_VOICES) { cemuLog_log(LogType::Force, "AXVoiceBegin(): Invalid voice index"); return -1; } if (AXIst_IsFrameBeingProcessed()) return -2; MPTR currentThreadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); if (__AXVoiceProtection[index].threadMPTR == MPTR_NULL) { __AXVoiceProtection[index].threadMPTR = currentThreadMPTR; __AXVoiceProtection[index].count = 1; return 1; } else if (__AXVoiceProtection[index].threadMPTR == currentThreadMPTR) { __AXVoiceProtection[index].count++; return __AXVoiceProtection[index].count; } return -1; } sint32 __GetThreadProtection(MPTR threadMPTR) { for (sint32 i = __AXUserProtectionArraySize - 1; i >= 0; i--) { if (__AXUserProtectionArray[i].threadMPTR == threadMPTR) return i; } return -1; } sint32 AXVoiceEnd(AXVPB* voice) { if (voice == nullptr) { cemuLog_log(LogType::Force, "AXVoiceBegin(): Invalid voice"); return -1; } uint32 index = (uint32)voice->index; if (index >= AX_MAX_VOICES) { cemuLog_log(LogType::Force, "AXVoiceBegin(): Invalid voice index"); return -1; } if (AXIst_IsFrameBeingProcessed()) return -2; MPTR currentThreadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); if (__AXVoiceProtection[index].threadMPTR == currentThreadMPTR) { if (__AXVoiceProtection[index].count > 0) __AXVoiceProtection[index].count--; if (__AXVoiceProtection[index].count == 0) { if (__GetThreadProtection(currentThreadMPTR) == -1) { __AXVoiceProtection[index].threadMPTR = 0; } } sint32 count = __AXVoiceProtection[index].count; return count; } else { if (__AXVoiceProtection[index].threadMPTR == MPTR_NULL) { return -3; } else { return -1; } } } void AXVPB_SetVoiceDefault(AXVPB* vpb) { AXVPBInternal_t* internal = GetInternalVoice(vpb); uint32 index = GetVoiceIndex(vpb); vpb->playbackState = 0; vpb->sync = 0; AXSetSyncFlag(vpb, AX_SYNCFLAG_PLAYBACKSTATE); AXSetSyncFlag(vpb, AX_SYNCFLAG_SRCDATA); AXSetSyncFlag(vpb, AX_SYNCFLAG_LPFDATA); AXSetSyncFlag(vpb, AX_SYNCFLAG_BIQUADDATA); AXSetSyncFlag(vpb, AX_SYNCFLAG_VOICEREMOTEON); AXSetSyncFlag(vpb, AX_SYNCFLAG_ITD20); AXSetSyncFlag(vpb, AX_SYNCFLAG_8000000); AXSetSyncFlag(vpb, 0x10000000); internal->reserved282_rmtIIRGuessed = 0; internal->reserved16C = 0; internal->reserved148_voiceRmtOn = 0; internal->biquad.on = 0; internal->lpf.on = 0; internal->playbackState = 0; uint32 defaultMixer = AXGetDefaultMixerSelect(); internal->mixerSelect = defaultMixer; vpb->mixerSelect = defaultMixer; AXResetVoiceLoopCount(vpb); internal->reserved280 = 0; internal->src.currentFrac = 0; internal->src.historySamples[0] = 0; internal->src.historySamples[1] = 0; internal->src.historySamples[2] = 0; internal->src.historySamples[3] = 0; internal->reserved278 = 0; internal->reserved27A = 0; internal->reserved27C = 0; internal->reserved27E = 0; internal->srcFilterMode = 0; memset(&internal->deviceMixMaskTV, 0, 8); memset(&internal->deviceMixMaskDRC, 0, 16); memset(&internal->deviceMixMaskRMT, 0, 0x20); memset(&internal->reserved1E8, 0, 0x30); memset(&internal->reserved218, 0, 0x40); memset(&internal->reserved258, 0, 0x20); } AXVPB* AXVPB_DropVoice(sint32 priority) { for (sint32 i = 1; i < priority; i++) { AXVPB* voiceItr = AXVoiceList_GetLeastRecentVoiceByPriority(i); if (voiceItr) { // get last voice in chain while (voiceItr->next) voiceItr = voiceItr->next.GetPtr(); cemuLog_logDebug(LogType::Force, "Dropped voice {}", (uint32)voiceItr->index); // drop voice if (voiceItr->playbackState != 0) { voiceItr->depop = 1; } // do drop callback if (voiceItr->callback) { PPCCoreCallback(voiceItr->callback, voiceItr); } voiceItr->ukn4C_dropReason = 0; // probably drop reason? if (voiceItr->callbackEx) { PPCCoreCallback(voiceItr->callbackEx, voiceItr, voiceItr->userParam, voiceItr->ukn4C_dropReason); } // move voice to new stack AXVoiceList_RemoveVoice(voiceItr); AXVoiceList_AddVoice(voiceItr, priority); return voiceItr; } } return nullptr; } AXVPB* AXAcquireVoiceEx(uint32 priority, MPTR callbackEx, MPTR userParam) { cemu_assert(priority != AX_PRIORITY_FREE && priority < AX_PRIORITY_MAX); __AXVoiceListSpinlock.lock(); AXVPB* vpb = AXVoiceList_GetFreeVoice(); if (vpb != nullptr) { AXVoiceList_AddVoice(vpb, priority); } else { // no free voice available, try to drop a voice with lower priority vpb = AXVPB_DropVoice(priority); if (!vpb) { // no voice available __AXVoiceListSpinlock.unlock(); return nullptr; } } vpb->userParam = userParam; vpb->callback = MPTR_NULL; vpb->callbackEx = callbackEx; AXVPB_SetVoiceDefault(vpb); __AXVoiceListSpinlock.unlock(); return vpb; } void AXFreeVoice(AXVPB* vpb) { cemu_assert(vpb != nullptr); __AXVoiceListSpinlock.lock(); if (vpb->priority == (uint32be)AX_PRIORITY_FREE) { cemuLog_log(LogType::Force, "AXFreeVoice() called on free voice"); __AXVoiceListSpinlock.unlock(); return; } AXVoiceProtection_Release(vpb); AXVoiceList_RemoveVoice(vpb); if (vpb->playbackState != (uint32be)0) { vpb->depop = (uint32be)1; } AXVPB_SetVoiceDefault(vpb); vpb->callback = MPTR_NULL; vpb->callbackEx = MPTR_NULL; AXVoiceList_AddFreeVoice(vpb); __AXVoiceListSpinlock.unlock(); } void __AXVPBResetVoices() { __AXVPBInternalVoiceArray = _buffer__AXVPBInternalVoiceArray.GetPtr(); __AXVPBInternalVoiceShadowCopyArrayPtr = _buffer__AXVPBInternalVoiceShadowCopyArray.GetPtr(); __AXVPBArrayPtr = _buffer__AXVPBArray.GetPtr(); __AXVPBItdArrayPtr = _buffer__AXVPBItdArray.GetPtr(); memset(__AXVPBInternalVoiceShadowCopyArrayPtr, 0, sizeof(AXVPBInternal_t)*AX_MAX_VOICES); memset(__AXVPBInternalVoiceArray, 0, sizeof(AXVPBInternal_t)*AX_MAX_VOICES); memset(__AXVPBItdArrayPtr, 0, sizeof(AXVPBItd)*AX_MAX_VOICES); memset(__AXVPBArrayPtr, 0, sizeof(AXVPB)*AX_MAX_VOICES); } void AXVPBInit() { __AXVPBResetVoices(); for (sint32 i = 0; i < AX_MAX_VOICES; i++) { AXVPBItd* itd = __AXVPBItdArrayPtr + i; AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + i; AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + i; AXVPB* vpb = __AXVPBArrayPtr + i; MPTR internalShadowCopyPhys = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(internalShadowCopy)); MPTR itdPhys = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(itd)); vpb->callbackEx = MPTR_NULL; vpb->itd = itd; vpb->callback = MPTR_NULL; vpb->index = i; AXVPB_SetVoiceDefault(vpb); if (i == (AX_MAX_VOICES - 1)) { internal->nextAddrHigh = 0; internal->nextAddrLow = 0; internalShadowCopy->nextAddrHigh = 0; internalShadowCopy->nextAddrLow = 0; } else { MPTR nextShadowCopyPhys = internalShadowCopyPhys + sizeof(AXVPBInternal_t); internalShadowCopy->nextAddrHigh = internal->nextAddrHigh = (nextShadowCopyPhys >> 16); internalShadowCopy->nextAddrLow = internal->nextAddrLow = (nextShadowCopyPhys & 0xFFFF); } internalShadowCopy->index = internal->index = i; internalShadowCopy->selfAddrHigh = internal->selfAddrHigh = (internalShadowCopyPhys >> 16); internalShadowCopy->selfAddrLow = internal->selfAddrLow = (internalShadowCopyPhys & 0xFFFF); internalShadowCopy->itdAddrHigh = internal->itdAddrHigh = (itdPhys >> 16); internalShadowCopy->itdAddrLow = internal->itdAddrLow = (itdPhys & 0xFFFF); vpb->priority = 1; AXVoiceList_AddFreeVoice(vpb); } } void AXVPB_Init() { __AXVPBResetVoices(); AXVPBInit(); } void AXVBP_Reset() { AXVoiceList_Reset(); __AXVPBResetVoices(); } sint32 AXIsValidDevice(sint32 device, sint32 deviceIndex) { if (device == AX_DEV_TV) { if (deviceIndex != 0) return -2; } else if (device == AX_DEV_DRC) { if (deviceIndex != 0 && deviceIndex != 1) return -2; } else if (device == AX_DEV_TV) { if (deviceIndex < 0 || deviceIndex >= 4) return -2; } else return -1; return 0; } void __AXSetVoiceChannelMix(AXCHMIX_DEPR* mixOut, AXCHMIX_DEPR* mixIn, sint16* mixMask) { for (sint32 i = 0; i < AX_BUS_COUNT; i++) { mixOut[i].vol = mixIn[i].vol; mixOut[i].delta = mixIn[i].delta; if (mixIn[i].delta) mixMask[i] = 3; else if (mixIn[i].vol) mixMask[i] = 1; else mixMask[i] = 0; } } sint32 AXSetVoiceDeviceMix(AXVPB* vpb, sint32 device, sint32 deviceIndex, AXCHMIX_DEPR* mix) { if (vpb == nullptr) return -4; if (mix == nullptr) return -3; sint32 r = AXIsValidDevice(device, deviceIndex); if (r) return r; AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; sint32 channelCount; uint16* deviceMixMask; AXCHMIX_DEPR* voiceMix; if (device == AX_DEV_TV) { channelCount = AX_TV_CHANNEL_COUNT; voiceMix = internal->deviceMixTV + deviceIndex * 0x60 / 4; deviceMixMask = internal->deviceMixMaskTV; } else if (device == AX_DEV_DRC) { channelCount = AX_DRC_CHANNEL_COUNT; voiceMix = internal->deviceMixDRC + deviceIndex * 16; deviceMixMask = internal->deviceMixMaskDRC; } else if (device == AX_DEV_RMT) { assert_dbg(); channelCount = AX_RMT_CHANNEL_COUNT; } sint16 updatedMixMask[AX_BUS_COUNT]; for (sint32 i = 0; i < AX_BUS_COUNT; i++) { updatedMixMask[i] = 0; } sint16 channelMixMask[AX_BUS_COUNT]; for (sint32 c = 0; c < channelCount; c++) { __AXSetVoiceChannelMix(voiceMix, mix, channelMixMask); for (sint32 i = 0; i < AX_BUS_COUNT; i++) { updatedMixMask[i] |= (channelMixMask[i] << (c * 2)); } // next channel voiceMix += AX_BUS_COUNT; mix += AX_BUS_COUNT; } for (sint32 i = 0; i < AX_BUS_COUNT; i++) { deviceMixMask[i] = _swapEndianU16(updatedMixMask[i]); } vpb->sync = (uint32)vpb->sync | (AX_SYNCFLAG_DEVICEMIXMASK | AX_SYNCFLAG_DEVICEMIX); AXVoiceProtection_Acquire(vpb); return 0; } void AXSetVoiceState(AXVPB* vpb, sint32 voiceState) { if (vpb->playbackState != (uint32be)voiceState) { vpb->playbackState = voiceState; AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; internal->playbackState = _swapEndianU16(voiceState); AXSetSyncFlag(vpb, AX_SYNCFLAG_PLAYBACKSTATE); AXVoiceProtection_Acquire(vpb); if (voiceState == 0) { vpb->depop = (uint32be)1; } } } sint32 AXIsVoiceRunning(AXVPB* vpb) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; return (_swapEndianU16(internal->playbackState) == 1) ? 1 : 0; } void AXSetVoiceType(AXVPB* vpb, uint16 voiceType) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; internal->voiceType = _swapEndianU16(voiceType); AXSetSyncFlag(vpb, AX_SYNCFLAG_VOICETYPE); AXVoiceProtection_Acquire(vpb); } void AXSetVoiceAdpcm(AXVPB* vpb, AXPBADPCM_t* adpcm) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)(vpb->index); for (sint32 i = 0; i < 16; i++) internal->adpcmData.coef[i] = adpcm->a[i]; internal->adpcmData.gain = adpcm->gain; internal->adpcmData.scale = adpcm->scale; AXSetSyncFlag(vpb, AX_SYNCFLAG_ADPCMDATA); AXVoiceProtection_Acquire(vpb); } void AXSetVoiceAdpcmLoop(AXVPB* vpb, AXPBADPCMLOOP_t* adpcmLoop) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; internal->adpcmLoop.loopScale = adpcmLoop->loopScale; if (internal->voiceType == 0) { internal->adpcmLoop.loopYn1 = adpcmLoop->loopYn1; internal->adpcmLoop.loopYn2 = adpcmLoop->loopYn2; } AXSetSyncFlag(vpb, AX_SYNCFLAG_ADPCMLOOP); AXVoiceProtection_Acquire(vpb); } void AXSetVoiceSrc(AXVPB* vpb, AXPBSRC_t* src) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; internal->src.ratioHigh = src->ratioHigh; internal->src.ratioLow = src->ratioLow; internal->src.currentFrac = src->currentFrac; internal->src.historySamples[0] = src->historySamples[0]; internal->src.historySamples[1] = src->historySamples[1]; internal->src.historySamples[2] = src->historySamples[2]; internal->src.historySamples[3] = src->historySamples[3]; AXResetSyncFlag(vpb, AX_SYNCFLAG_SRCRATIO); AXSetSyncFlag(vpb, AX_SYNCFLAG_SRCDATA); AXVoiceProtection_Acquire(vpb); } void AXSetVoiceSrcType(AXVPB* vpb, uint32 srcType) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; if (srcType == AX_SRC_TYPE_NONE) { internal->srcFilterMode = _swapEndianU16(AX_FILTER_MODE_NONE); } else if (srcType == AX_SRC_TYPE_LINEAR) { internal->srcFilterMode = _swapEndianU16(AX_FILTER_MODE_LINEAR); } else if (srcType == AX_SRC_TYPE_LOWPASS1) { internal->srcFilterMode = _swapEndianU16(AX_FILTER_MODE_TAP); internal->srcTapFilter = _swapEndianU16(AX_FILTER_LOWPASS_8K); } else if (srcType == AX_SRC_TYPE_LOWPASS2) { internal->srcFilterMode = _swapEndianU16(AX_FILTER_MODE_TAP); internal->srcTapFilter = _swapEndianU16(AX_FILTER_LOWPASS_12K); } else if (srcType == AX_SRC_TYPE_LOWPASS3) { internal->srcFilterMode = _swapEndianU16(AX_FILTER_MODE_TAP); internal->srcTapFilter = _swapEndianU16(AX_FILTER_LOWPASS_16K); } else { cemuLog_log(LogType::Force, "AXSetVoiceSrcType(): Unsupported src type {}", srcType); } AXSetSyncFlag(vpb, AX_SYNCFLAG_SRCFILTER); AXVoiceProtection_Acquire(vpb); } sint32 AXSetVoiceSrcRatio(AXVPB* vpb, float ratio) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; ratio *= 65536.0f; #ifdef CEMU_DEBUG_ASSERT if (ratio >= 4294967296.0f) assert_dbg(); #endif sint32 ratioI = (sint32)ratio; if (ratioI < 0) ratioI = 0; else if (ratioI > 0x80000) ratioI = 0x80000; uint16 ratioHigh = (uint16)(ratioI >> 16); uint16 ratioLow = (uint16)(ratioI & 0xFFFF); ratioHigh = _swapEndianU16(ratioHigh); ratioLow = _swapEndianU16(ratioLow); if (internal->src.ratioHigh != ratioHigh || internal->src.ratioLow != ratioLow) { internal->src.ratioHigh = ratioHigh; internal->src.ratioLow = ratioLow; AXSetSyncFlag(vpb, AX_SYNCFLAG_SRCRATIO); AXVoiceProtection_Acquire(vpb); } return 0; } void AXSetVoiceVe(AXVPB* vpb, AXPBVE* ve) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; internal->veVolume = ve->currentVolume; internal->veDelta = ve->currentDelta; AXSetSyncFlag(vpb, AX_SYNCFLAG_VE); AXVoiceProtection_Acquire(vpb); } void AXComputeLpfCoefs(uint32 freq, uint16be* a0, uint16be* b0) { // todo - verify algorithm float t1 = cos((float)freq / 32000.0f * 6.2831855f); float t2 = 2.0f - t1; t1 = (float)sqrt(t2 * t2 - 1.0f); t1 = t1 - t2; t1 = t1 * 32768.0f; t1 = -t1; uint32 r = (uint16)t1; *a0 = 0x7FFF - r; *b0 = r; } void AXSetVoiceLpf(AXVPB* vpb, AXPBLPF_t* lpf) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; internal->lpf.on = lpf->on; internal->lpf.yn1 = lpf->yn1; internal->lpf.a0 = lpf->a0; internal->lpf.b0 = lpf->b0; AXSetSyncFlag(vpb, AX_SYNCFLAG_LPFDATA); AXVoiceProtection_Acquire(vpb); } void AXSetVoiceLpfCoefs(AXVPB* vpb, uint16 a0, uint16 b0) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; internal->lpf.a0 = _swapEndianU16(a0); internal->lpf.b0 = _swapEndianU16(b0); AXSetSyncFlag(vpb, AX_SYNCFLAG_LPFCOEF); AXVoiceProtection_Acquire(vpb); } void AXSetVoiceBiquad(AXVPB* vpb, AXPBBIQUAD_t* biquad) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; internal->biquad.on = biquad->on; internal->biquad.xn1 = biquad->xn1; internal->biquad.xn2 = biquad->xn2; internal->biquad.yn1 = biquad->yn1; internal->biquad.yn2 = biquad->yn2; internal->biquad.b0 = biquad->b0; internal->biquad.b1 = biquad->b1; internal->biquad.b2 = biquad->b2; internal->biquad.a1 = biquad->a1; internal->biquad.a2 = biquad->a2; AXSetSyncFlag(vpb, AX_SYNCFLAG_BIQUADDATA); AXVoiceProtection_Acquire(vpb); } void AXSetVoiceBiquadCoefs(AXVPB* vpb, uint16 b0, uint16 b1, uint16 b2, uint16 a1, uint16 a2) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; internal->biquad.b0 = _swapEndianU16(b0); internal->biquad.b1 = _swapEndianU16(b1); internal->biquad.b2 = _swapEndianU16(b2); internal->biquad.a1 = _swapEndianU16(a1); internal->biquad.a2 = _swapEndianU16(a2); AXSetSyncFlag(vpb, AX_SYNCFLAG_BIQUADCOEF); AXVoiceProtection_Acquire(vpb); } void __AXSetVoiceAddr(AXVPB* vpb, axOffsetsInternal_t* voiceAddr) { sint32 voiceIndex = (sint32)(vpb->index); AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + voiceIndex; memcpy(&internal->internalOffsets, voiceAddr, sizeof(axOffsetsInternal_t)); uint16 format = _swapEndianU16(voiceAddr->format); if (format == AX_FORMAT_PCM8) { memset(&internal->adpcmData, 0x00, sizeof(axADPCMInternal_t)); internal->adpcmData.gain = 0x100; AXSetSyncFlag(vpb, AX_SYNCFLAG_ADPCMDATA); AXResetSyncFlag(vpb, AX_SYNCFLAG_LOOPFLAG | AX_SYNCFLAG_LOOPOFFSET | AX_SYNCFLAG_ENDOFFSET | AX_SYNCFLAG_CURRENTOFFSET); AXSetSyncFlag(vpb, AX_SYNCFLAG_OFFSETS); AXVoiceProtection_Acquire(vpb); } else if (format == AX_FORMAT_PCM16) { memset(&internal->adpcmData, 0x00, sizeof(axADPCMInternal_t)); internal->adpcmData.gain = 0x800; AXSetSyncFlag(vpb, AX_SYNCFLAG_ADPCMDATA); AXResetSyncFlag(vpb, AX_SYNCFLAG_LOOPFLAG | AX_SYNCFLAG_LOOPOFFSET | AX_SYNCFLAG_ENDOFFSET | AX_SYNCFLAG_CURRENTOFFSET); AXSetSyncFlag(vpb, AX_SYNCFLAG_OFFSETS); AXVoiceProtection_Acquire(vpb); } else if (format == AX_FORMAT_ADPCM) { // .adpcmData is left intact AXResetSyncFlag(vpb, AX_SYNCFLAG_LOOPFLAG | AX_SYNCFLAG_LOOPOFFSET | AX_SYNCFLAG_ENDOFFSET | AX_SYNCFLAG_CURRENTOFFSET); AXSetSyncFlag(vpb, AX_SYNCFLAG_OFFSETS); AXVoiceProtection_Acquire(vpb); } else { cemu_assert_debug(false); } } void AXSetVoiceOffsets(AXVPB* vpb, AXPBOFFSET_t* pbOffset) { cemuLog_log(LogType::SoundAPI, fmt::format("AXSetVoiceOffsets() -> Format: {0:04x} Current: {1:08x} End: {2:08x} Loop: {3:08x}", _swapEndianU16(pbOffset->format), _swapEndianU32(pbOffset->currentOffset), _swapEndianU32(pbOffset->endOffset), _swapEndianU32(pbOffset->loopOffset))); MPTR sampleBase = _swapEndianU32(pbOffset->samples); if (sampleBase == MPTR_NULL) { cemuLog_log(LogType::Force, "AXSetVoiceOffsets(): Invalid sample address"); cemu_assert_debug(false); return; } memcpy(&vpb->offsets, pbOffset, sizeof(AXPBOFFSET_t)); sampleBase = memory_virtualToPhysical(sampleBase); uint16 format = _swapEndianU16(pbOffset->format); axOffsetsInternal_t voiceAddr; if (format == AX_FORMAT_PCM8) { uint32 loopOffsetPtr = sampleBase + _swapEndianU32(pbOffset->loopOffset); uint32 endOffsetPtr = sampleBase + _swapEndianU32(pbOffset->endOffset); uint32 currentOffsetPtr = sampleBase + _swapEndianU32(pbOffset->currentOffset); voiceAddr.format = _swapEndianU16(pbOffset->format); voiceAddr.loopFlag = _swapEndianU16(pbOffset->loopFlag); voiceAddr.loopOffsetPtrHigh = (loopOffsetPtr >> 16) & 0x1FFF; voiceAddr.loopOffsetPtrLow = loopOffsetPtr & 0xFFFF; voiceAddr.endOffsetPtrHigh = (endOffsetPtr >> 16) & 0x1FFF; voiceAddr.endOffsetPtrLow = endOffsetPtr & 0xFFFF; voiceAddr.currentOffsetPtrHigh = (currentOffsetPtr >> 16) & 0x1FFF; voiceAddr.currentOffsetPtrLow = currentOffsetPtr & 0xFFFF; voiceAddr.ptrHighExtension = (currentOffsetPtr >> 29); // convert to big endian voiceAddr.format = _swapEndianU16(voiceAddr.format); voiceAddr.loopFlag = _swapEndianU16(voiceAddr.loopFlag); voiceAddr.loopOffsetPtrHigh = _swapEndianU16(voiceAddr.loopOffsetPtrHigh); voiceAddr.loopOffsetPtrLow = _swapEndianU16(voiceAddr.loopOffsetPtrLow); voiceAddr.endOffsetPtrHigh = _swapEndianU16(voiceAddr.endOffsetPtrHigh); voiceAddr.endOffsetPtrLow = _swapEndianU16(voiceAddr.endOffsetPtrLow); voiceAddr.currentOffsetPtrHigh = _swapEndianU16(voiceAddr.currentOffsetPtrHigh); voiceAddr.currentOffsetPtrLow = _swapEndianU16(voiceAddr.currentOffsetPtrLow); voiceAddr.ptrHighExtension = _swapEndianU16(voiceAddr.ptrHighExtension); __AXSetVoiceAddr(vpb, &voiceAddr); } else if (format == AX_FORMAT_PCM16) { uint32 loopOffsetPtr = sampleBase / 2 + _swapEndianU32(pbOffset->loopOffset); uint32 endOffsetPtr = sampleBase / 2 + _swapEndianU32(pbOffset->endOffset); uint32 currentOffset = _swapEndianU32(pbOffset->currentOffset); uint32 currentOffsetPtr = sampleBase / 2 + currentOffset; voiceAddr.format = _swapEndianU16(pbOffset->format); voiceAddr.loopFlag = _swapEndianU16(pbOffset->loopFlag); voiceAddr.loopOffsetPtrHigh = (loopOffsetPtr >> 16) & 0x0FFF; voiceAddr.loopOffsetPtrLow = loopOffsetPtr & 0xFFFF; voiceAddr.endOffsetPtrHigh = (endOffsetPtr >> 16) & 0x0FFF; voiceAddr.endOffsetPtrLow = endOffsetPtr & 0xFFFF; voiceAddr.currentOffsetPtrHigh = (currentOffsetPtr >> 16) & 0x0FFF; voiceAddr.currentOffsetPtrLow = currentOffsetPtr & 0xFFFF; voiceAddr.ptrHighExtension = ((sampleBase + currentOffset * 2) >> 29); // convert to big endian voiceAddr.format = _swapEndianU16(voiceAddr.format); voiceAddr.loopFlag = _swapEndianU16(voiceAddr.loopFlag); voiceAddr.loopOffsetPtrHigh = _swapEndianU16(voiceAddr.loopOffsetPtrHigh); voiceAddr.loopOffsetPtrLow = _swapEndianU16(voiceAddr.loopOffsetPtrLow); voiceAddr.endOffsetPtrHigh = _swapEndianU16(voiceAddr.endOffsetPtrHigh); voiceAddr.endOffsetPtrLow = _swapEndianU16(voiceAddr.endOffsetPtrLow); voiceAddr.currentOffsetPtrHigh = _swapEndianU16(voiceAddr.currentOffsetPtrHigh); voiceAddr.currentOffsetPtrLow = _swapEndianU16(voiceAddr.currentOffsetPtrLow); voiceAddr.ptrHighExtension = _swapEndianU16(voiceAddr.ptrHighExtension); __AXSetVoiceAddr(vpb, &voiceAddr); } else if (format == AX_FORMAT_ADPCM) { uint32 loopOffsetPtr = sampleBase * 2 + _swapEndianU32(pbOffset->loopOffset); uint32 endOffsetPtr = sampleBase * 2 + _swapEndianU32(pbOffset->endOffset); uint32 currentOffset = _swapEndianU32(pbOffset->currentOffset); uint32 currentOffsetPtr = sampleBase * 2 + currentOffset; voiceAddr.format = _swapEndianU16(pbOffset->format); voiceAddr.loopFlag = _swapEndianU16(pbOffset->loopFlag); voiceAddr.loopOffsetPtrHigh = (loopOffsetPtr >> 16) & 0x3FFF; voiceAddr.loopOffsetPtrLow = loopOffsetPtr & 0xFFFF; voiceAddr.endOffsetPtrHigh = (endOffsetPtr >> 16) & 0x3FFF; voiceAddr.endOffsetPtrLow = endOffsetPtr & 0xFFFF; voiceAddr.currentOffsetPtrHigh = (currentOffsetPtr >> 16) & 0x3FFF; voiceAddr.currentOffsetPtrLow = currentOffsetPtr & 0xFFFF; voiceAddr.ptrHighExtension = ((sampleBase + currentOffset / 2) >> 29); // convert to big endian voiceAddr.format = _swapEndianU16(voiceAddr.format); voiceAddr.loopFlag = _swapEndianU16(voiceAddr.loopFlag); voiceAddr.loopOffsetPtrHigh = _swapEndianU16(voiceAddr.loopOffsetPtrHigh); voiceAddr.loopOffsetPtrLow = _swapEndianU16(voiceAddr.loopOffsetPtrLow); voiceAddr.endOffsetPtrHigh = _swapEndianU16(voiceAddr.endOffsetPtrHigh); voiceAddr.endOffsetPtrLow = _swapEndianU16(voiceAddr.endOffsetPtrLow); voiceAddr.currentOffsetPtrHigh = _swapEndianU16(voiceAddr.currentOffsetPtrHigh); voiceAddr.currentOffsetPtrLow = _swapEndianU16(voiceAddr.currentOffsetPtrLow); voiceAddr.ptrHighExtension = _swapEndianU16(voiceAddr.ptrHighExtension); __AXSetVoiceAddr(vpb, &voiceAddr); } else { cemu_assert_debug(false); } } void AXSetVoiceOffsetsEx(AXVPB* vpb, AXPBOFFSET_t* pbOffset, void* sampleBase) { // used by F1 Racing cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); AXPBOFFSET_t tmpOffsets = *pbOffset; tmpOffsets.samples = _swapEndianU32(memory_getVirtualOffsetFromPointer(sampleBase)); AXSetVoiceOffsets(vpb, &tmpOffsets); } void AXSetVoiceSamplesAddr(AXVPB* vpb, void* sampleBase) { vpb->offsets.samples = _swapEndianU32(memory_getVirtualOffsetFromPointer(sampleBase)); AXGetVoiceOffsets(vpb, &vpb->offsets); } void AXGetVoiceOffsets(AXVPB* vpb, AXPBOFFSET_t* pbOffset) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; memcpy(pbOffset, &vpb->offsets, sizeof(AXPBOFFSET_t)); MPTR sampleBase = _swapEndianU32(vpb->offsets.samples); sampleBase = memory_virtualToPhysical(sampleBase); uint16 format = _swapEndianU16(pbOffset->format); uint32 loopOffsetPtrLow = _swapEndianU16(internal->internalOffsets.loopOffsetPtrLow); uint32 loopOffsetPtrHigh = _swapEndianU16(internal->internalOffsets.loopOffsetPtrHigh); uint32 endOffsetPtrLow = _swapEndianU16(internal->internalOffsets.endOffsetPtrLow); uint32 endOffsetPtrHigh = _swapEndianU16(internal->internalOffsets.endOffsetPtrHigh); uint32 currentOffsetPtrLow = _swapEndianU16(internal->internalOffsets.currentOffsetPtrLow); uint32 currentOffsetPtrHigh = _swapEndianU16(internal->internalOffsets.currentOffsetPtrHigh); uint32 ptrHighExtension = _swapEndianU16(internal->internalOffsets.ptrHighExtension); if (format == AX_FORMAT_PCM8) { uint32 loopOffset = (loopOffsetPtrLow | (loopOffsetPtrHigh << 16) | (ptrHighExtension << 29)) - sampleBase; uint32 endOffset = (endOffsetPtrLow | (endOffsetPtrHigh << 16) | (ptrHighExtension << 29)) - sampleBase; uint32 currentOffset = (currentOffsetPtrLow | (currentOffsetPtrHigh << 16) | (ptrHighExtension << 29)) - sampleBase; pbOffset->loopOffset = _swapEndianU32(loopOffset); pbOffset->endOffset = _swapEndianU32(endOffset); pbOffset->currentOffset = _swapEndianU32(currentOffset); } else if (format == AX_FORMAT_PCM16) { uint32 loopOffset = (loopOffsetPtrLow | (loopOffsetPtrHigh << 16) | ((ptrHighExtension << 29) / 2)) - sampleBase / 2; uint32 endOffset = (endOffsetPtrLow | (endOffsetPtrHigh << 16) | ((ptrHighExtension << 29) / 2)) - sampleBase / 2; uint32 currentOffset = (currentOffsetPtrLow | (currentOffsetPtrHigh << 16) | ((ptrHighExtension << 29) / 2)) - sampleBase / 2; pbOffset->loopOffset = _swapEndianU32(loopOffset); pbOffset->endOffset = _swapEndianU32(endOffset); pbOffset->currentOffset = _swapEndianU32(currentOffset); } else if (format == AX_FORMAT_ADPCM) { uint32 loopOffset = (loopOffsetPtrLow | (loopOffsetPtrHigh << 16) | ((ptrHighExtension << 29) * 2)) - sampleBase * 2; uint32 endOffset = (endOffsetPtrLow | (endOffsetPtrHigh << 16) | ((ptrHighExtension << 29) * 2)) - sampleBase * 2; uint32 currentOffset = (currentOffsetPtrLow | (currentOffsetPtrHigh << 16) | ((ptrHighExtension << 29) * 2)) - sampleBase * 2; pbOffset->loopOffset = _swapEndianU32(loopOffset); pbOffset->endOffset = _swapEndianU32(endOffset); pbOffset->currentOffset = _swapEndianU32(currentOffset); } else { cemu_assert_debug(false); } cemuLog_log(LogType::SoundAPI, "Retrieved voice offsets for voice {:08x} - base {:08x} current {:08x} loopFlag {:04x} loop {:08x} end {:08x}", memory_getVirtualOffsetFromPointer(vpb), _swapEndianU32(pbOffset->samples), _swapEndianU32(pbOffset->currentOffset), _swapEndianU16(pbOffset->loopFlag), _swapEndianU32(pbOffset->loopOffset), _swapEndianU32(pbOffset->endOffset)); } void AXGetVoiceOffsetsEx(AXVPB* vpb, AXPBOFFSET_t* pbOffset, MPTR sampleBase) { cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); vpb->offsets.samples = _swapEndianU32(sampleBase); AXGetVoiceOffsets(vpb, pbOffset); memcpy(&vpb->offsets, pbOffset, sizeof(AXPBOFFSET_t)); } void AXSetVoiceCurrentOffset(AXVPB* vpb, uint32 currentOffset) { // untested AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)(vpb->index); MPTR sampleBase = memory_virtualToPhysical(_swapEndianU32(vpb->offsets.samples)); vpb->offsets.currentOffset = _swapEndianU32(currentOffset); uint16 voiceFormat = _swapEndianU16(internal->internalOffsets.format); uint32 currentOffsetPtr; sampleBase &= 0x1FFFFFFF; if (voiceFormat == AX_FORMAT_PCM8) { // calculate offset (in bytes) currentOffsetPtr = sampleBase + currentOffset; } else if (voiceFormat == AX_FORMAT_PCM16) { // calculate offset (in shorts) currentOffsetPtr = sampleBase / 2 + currentOffset; } else if (voiceFormat == AX_FORMAT_ADPCM) { // calculate offset (in nibbles) currentOffsetPtr = sampleBase * 2 + currentOffset; } else { cemu_assert_debug(false); } internal->internalOffsets.currentOffsetPtrHigh = _swapEndianU16(currentOffsetPtr >> 16); internal->internalOffsets.currentOffsetPtrLow = _swapEndianU16(currentOffsetPtr & 0xFFFF); AXSetSyncFlag(vpb, AX_SYNCFLAG_CURRENTOFFSET); AXVoiceProtection_Acquire(vpb); } void AXSetVoiceLoopOffset(AXVPB* vpb, uint32 loopOffset) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)(vpb->index); MPTR sampleBase = memory_virtualToPhysical(_swapEndianU32(vpb->offsets.samples)); vpb->offsets.loopOffset = _swapEndianU32(loopOffset); uint16 voiceFormat = _swapEndianU16(internal->internalOffsets.format); uint32 loopOffsetPtr; sampleBase &= 0x1FFFFFFF; if (voiceFormat == AX_FORMAT_PCM8) { // calculate offset (in bytes) loopOffsetPtr = sampleBase + loopOffset; } else if (voiceFormat == AX_FORMAT_PCM16) { // calculate offset (in shorts) loopOffsetPtr = sampleBase / 2 + loopOffset; } else if (voiceFormat == AX_FORMAT_ADPCM) { // calculate offset (in nibbles) loopOffsetPtr = sampleBase * 2 + loopOffset; } else { cemu_assert_debug(false); } internal->internalOffsets.loopOffsetPtrHigh = _swapEndianU16(loopOffsetPtr >> 16); internal->internalOffsets.loopOffsetPtrLow = _swapEndianU16(loopOffsetPtr & 0xFFFF); AXSetSyncFlag(vpb, AX_SYNCFLAG_LOOPOFFSET); AXVoiceProtection_Acquire(vpb); } void AXSetVoiceEndOffset(AXVPB* vpb, uint32 endOffset) { // untested AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)(vpb->index); MPTR sampleBase = memory_virtualToPhysical(_swapEndianU32(vpb->offsets.samples)); vpb->offsets.endOffset = _swapEndianU32(endOffset); uint16 voiceFormat = _swapEndianU16(internal->internalOffsets.format); uint32 endOffsetPtr; sampleBase &= 0x1FFFFFFF; if (voiceFormat == AX_FORMAT_PCM8) { // calculate offset (in bytes) endOffsetPtr = sampleBase + endOffset; } else if (voiceFormat == AX_FORMAT_PCM16) { // calculate offset (in shorts) endOffsetPtr = sampleBase / 2 + endOffset; } else if (voiceFormat == AX_FORMAT_ADPCM) { // calculate offset (in nibbles) endOffsetPtr = sampleBase * 2 + endOffset; } else { cemu_assert_debug(false); } internal->internalOffsets.endOffsetPtrHigh = _swapEndianU16(endOffsetPtr >> 16); internal->internalOffsets.endOffsetPtrLow = _swapEndianU16(endOffsetPtr & 0xFFFF); AXSetSyncFlag(vpb, AX_SYNCFLAG_ENDOFFSET); AXVoiceProtection_Acquire(vpb); } void AXSetVoiceCurrentOffsetEx(AXVPB* vpb, uint32 currentOffset, MPTR sampleBase) { cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); AXPBOFFSET_t pbOffset; vpb->offsets.samples = _swapEndianU32(sampleBase); AXGetVoiceOffsets(vpb, &pbOffset); AXSetVoiceCurrentOffset(vpb, currentOffset); } void AXSetVoiceLoopOffsetEx(AXVPB* vpb, uint32 loopOffset, MPTR sampleBase) { cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); AXPBOFFSET_t pbOffset; vpb->offsets.samples = _swapEndianU32(sampleBase); AXGetVoiceOffsets(vpb, &pbOffset); AXSetVoiceLoopOffset(vpb, loopOffset); } void AXSetVoiceEndOffsetEx(AXVPB* vpb, uint32 endOffset, MPTR sampleBase) { cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); AXPBOFFSET_t pbOffset; vpb->offsets.samples = _swapEndianU32(sampleBase); AXGetVoiceOffsets(vpb, &pbOffset); AXSetVoiceEndOffset(vpb, endOffset); } uint32 AXGetVoiceCurrentOffsetEx(AXVPB* vpb, MPTR sampleBase) { cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); AXPBOFFSET_t pbOffset; AXGetVoiceOffsetsEx(vpb, &pbOffset, sampleBase); return _swapEndianU32(pbOffset.currentOffset); } void AXSetVoiceLoop(AXVPB* vpb, uint16 loopState) { AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)(vpb->index); vpb->offsets.loopFlag = _swapEndianU16(loopState); internal->internalOffsets.loopFlag = _swapEndianU16(loopState); AXSetSyncFlag(vpb, AX_SYNCFLAG_LOOPFLAG); AXVoiceProtection_Acquire(vpb); } uint32 vpbLoopTracker_loopCount[AX_MAX_VOICES]; uint32 vpbLoopTracker_prevCurrentOffset[AX_MAX_VOICES]; void AXResetVoiceLoopCount(AXVPB* vpb) { if (!vpb) return; uint32 voiceIndex = vpb->index; vpbLoopTracker_loopCount[voiceIndex] = 0; vpbLoopTracker_prevCurrentOffset[voiceIndex] = 0; } sint32 AXGetVoiceLoopCount(AXVPB* vpb) { if (!vpb) return 0; uint32 voiceIndex = vpb->index; AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + voiceIndex; uint32 loopOffset = internal->internalOffsets.GetLoopOffset32(); uint32 endOffset = internal->internalOffsets.GetEndOffset32(); uint32 currentOffset = internal->internalOffsets.GetCurrentOffset32(); uint32 srcRatio; if (internal->srcFilterMode == AX_FILTER_MODE_NONE) srcRatio = 0x10000; else srcRatio = internal->src.GetSrcRatio32(); uint32 loopLength = 0; if (srcRatio != 0) loopLength = ((endOffset - loopOffset) * 0x10000) / srcRatio; uint32 prevCurrentOffset = vpbLoopTracker_prevCurrentOffset[voiceIndex]; uint32 voiceSamplesPerFrame = AXGetInputSamplesPerFrame(); if (loopOffset < endOffset && srcRatio != 0 && loopLength <= voiceSamplesPerFrame) { // handle loops shorter than one frame uint32 loopsInFrame = ((prevCurrentOffset - loopOffset) + voiceSamplesPerFrame) / loopLength; vpbLoopTracker_loopCount[voiceIndex] += loopsInFrame; } else // loopOffset >= endOffset { if (prevCurrentOffset <= endOffset) // loop could only happen if playback cursor was before end { if (loopOffset > endOffset && currentOffset >= loopOffset) vpbLoopTracker_loopCount[voiceIndex]++; else if (currentOffset < prevCurrentOffset && currentOffset >= loopOffset) vpbLoopTracker_loopCount[voiceIndex]++; } } vpbLoopTracker_prevCurrentOffset[voiceIndex] = currentOffset; return vpbLoopTracker_loopCount[voiceIndex]; } } ================================================ FILE: src/Cafe/OS/libs/snd_user/snd_user.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/snd_user/snd_user.h" #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/RPL/rpl.h" using namespace snd_core; namespace snd_user { #define AX_MAX_NUM_DRC (2) #define AX_MAX_NUM_RMT (4) #define AX_UPDATE_MODE_10000000 (0x10000000) #define AX_UPDATE_MODE_20000000 (0x20000000) #define AX_UPDATE_MODE_40000000_VOLUME (0x40000000) #define AX_UPDATE_MODE_50000000 (0x50000000) #define AX_UPDATE_MODE_40000007 (0x40000007) #define AX_UPDATE_MODE_80000000 (0x80000000) struct VolumeData { sint16 volume; // 0x00 sint16 volume_target; // 0x02 }; static_assert(sizeof(VolumeData) == 0x4, "sizeof(VolumeData)"); struct MixControl { sint16 aux[AX_AUX_BUS_COUNT]; sint16 pan; sint16 span; sint16 fader; sint16 lfe; }; static_assert(sizeof(MixControl) == 0xE, "sizeof(MixControl)"); using MixMode = uint32_t; struct MixChannel { MEMPTR<AXVPB> voice; uint32 update_mode; sint16 input_level; VolumeData volume; MixControl tv_control; sint16 tv_channels[AX_TV_CHANNEL_COUNT]; VolumeData tv_volume[AX_MAX_NUM_BUS][AX_TV_CHANNEL_COUNT]; MixMode tv_mode; MixControl drc_control[AX_MAX_NUM_DRC]; sint16 drc_channels[AX_MAX_NUM_DRC][AX_DRC_CHANNEL_COUNT]; VolumeData drc_volume[AX_MAX_NUM_DRC][AX_MAX_NUM_BUS][AX_DRC_CHANNEL_COUNT]; MixMode drc_mode[AX_MAX_NUM_DRC]; MixControl rmt_control[AX_MAX_NUM_RMT]; sint16 rmt_channels[AX_MAX_NUM_RMT][AX_RMT_CHANNEL_COUNT]; VolumeData rmt_volume[AX_MAX_NUM_RMT][AX_MAX_NUM_BUS][AX_RMT_CHANNEL_COUNT]; MixMode rmt_mode[AX_MAX_NUM_RMT]; MixControl& GetMixControl(uint32 device, uint32 deviceIndex) { if (device == AX_DEV_TV) { cemu_assert(deviceIndex == 0); return tv_control; } else if (device == AX_DEV_DRC) { cemu_assert(deviceIndex < AX_MAX_NUM_DRC); return drc_control[deviceIndex]; } else if (device == AX_DEV_RMT) { cemu_assert(deviceIndex < AX_MAX_NUM_RMT); return rmt_control[deviceIndex]; } cemuLog_log(LogType::Force, "GetMixControl({}, {}): Invalid device/deviceIndex", device, deviceIndex); cemu_assert(false); return tv_control; } MixMode& GetMode(uint32 device, uint32 deviceIndex) { if (device == AX_DEV_TV) { cemu_assert(deviceIndex == 0); return tv_mode; } else if (device == AX_DEV_DRC) { cemu_assert(deviceIndex < AX_MAX_NUM_DRC); return drc_mode[deviceIndex]; } else if (device == AX_DEV_RMT) { cemu_assert(deviceIndex < AX_MAX_NUM_RMT); return rmt_mode[deviceIndex]; } cemuLog_log(LogType::Force, "GetMode({}, {}): Invalid device/deviceIndex", device, deviceIndex); cemu_assert(false); return tv_mode; } sint16* GetChannels(uint32 device, uint32 deviceIndex) { if (device == AX_DEV_TV) { cemu_assert(deviceIndex == 0); return tv_channels; } else if (device == AX_DEV_DRC) { cemu_assert(deviceIndex < AX_MAX_NUM_DRC); return drc_channels[deviceIndex]; } else if (device == AX_DEV_RMT) { cemu_assert(deviceIndex < AX_MAX_NUM_RMT); return rmt_channels[deviceIndex]; } cemuLog_log(LogType::Force, "GetChannels({}, {}): Invalid device/deviceIndex", device, deviceIndex); cemu_assert(false); return tv_channels; } }; static_assert(sizeof(MixChannel) == 0x1D0, "sizeof(MixChannel)"); struct DeviceInfo { uint32 tv_sound_mode; // 0x00 uint32 drc_sound_mode; // 0x04 uint32 rmt_sound_mode; // 0x08 }; struct snd_user_data_t { bool initialized; DeviceInfo device_info; sint32 max_voices; MixChannel mix_channel[AX_MAX_VOICES]; const uint16 volume[0x388 + 0x3C + 1] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0xF, 0xF, 0xF, 0xF, 0xF, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x1A, 0x1A, 0x1A, 0x1A, 0x1B, 0x1B, 0x1B, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1D, 0x1E, 0x1E, 0x1E, 0x1F, 0x1F, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, 0x2C, 0x2C, 0x2D, 0x2D, 0x2E, 0x2E, 0x2F, 0x2F, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x33, 0x34, 0x35, 0x35, 0x36, 0x37, 0x37, 0x38, 0x38, 0x39, 0x3A, 0x3A, 0x3B, 0x3C, 0x3D, 0x3D, 0x3E, 0x3F, 0x3F, 0x40, 0x41, 0x42, 0x42, 0x43, 0x44, 0x45, 0x46, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x64, 0x65, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6F, 0x70, 0x71, 0x72, 0x74, 0x75, 0x76, 0x78, 0x79, 0x7B, 0x7C, 0x7E, 0x7F, 0x80, 0x82, 0x83, 0x85, 0x87, 0x88, 0x8A, 0x8B, 0x8D, 0x8F, 0x90, 0x92, 0x94, 0x95, 0x97, 0x99, 0x9B, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAB, 0xAD, 0xAF, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBE, 0xC0, 0xC3, 0xC5, 0xC7, 0xCA, 0xCC, 0xCE, 0xD1, 0xD3, 0xD6, 0xD8, 0xDB, 0xDD, 0xE0, 0xE2, 0xE5, 0xE7, 0xEA, 0xED, 0xF0, 0xF2, 0xF5, 0xF8, 0xFB, 0xFE, 0x101, 0x104, 0x107, 0x10A, 0x10D, 0x110, 0x113, 0x116, 0x11A, 0x11D, 0x120, 0x124, 0x127, 0x12A, 0x12E, 0x131, 0x135, 0x138, 0x13C, 0x140, 0x143, 0x147, 0x14B, 0x14F, 0x153, 0x157, 0x15B, 0x15F, 0x163, 0x167, 0x16B, 0x16F, 0x173, 0x178, 0x17C, 0x180, 0x185, 0x189, 0x18E, 0x193, 0x197, 0x19C, 0x1A1, 0x1A6, 0x1AB, 0x1AF, 0x1B4, 0x1BA, 0x1BF, 0x1C4, 0x1C9, 0x1CE, 0x1D4, 0x1D9, 0x1DF, 0x1E4, 0x1EA, 0x1EF, 0x1F5, 0x1FB, 0x201, 0x207, 0x20D, 0x213, 0x219, 0x21F, 0x226, 0x22C, 0x232, 0x239, 0x240, 0x246, 0x24D, 0x254, 0x25B, 0x262, 0x269, 0x270, 0x277, 0x27E, 0x286, 0x28D, 0x295, 0x29D, 0x2A4, 0x2AC, 0x2B4, 0x2BC, 0x2C4, 0x2CC, 0x2D5, 0x2DD, 0x2E6, 0x2EE, 0x2F7, 0x300, 0x309, 0x312, 0x31B, 0x324, 0x32D, 0x337, 0x340, 0x34A, 0x354, 0x35D, 0x367, 0x371, 0x37C, 0x386, 0x390, 0x39B, 0x3A6, 0x3B1, 0x3BB, 0x3C7, 0x3D2, 0x3DD, 0x3E9, 0x3F4, 0x400, 0x40C, 0x418, 0x424, 0x430, 0x43D, 0x449, 0x456, 0x463, 0x470, 0x47D, 0x48A, 0x498, 0x4A5, 0x4B3, 0x4C1, 0x4CF, 0x4DD, 0x4EC, 0x4FA, 0x509, 0x518, 0x527, 0x536, 0x546, 0x555, 0x565, 0x575, 0x586, 0x596, 0x5A6, 0x5B7, 0x5C8, 0x5D9, 0x5EB, 0x5FC, 0x60E, 0x620, 0x632, 0x644, 0x657, 0x66A, 0x67D, 0x690, 0x6A4, 0x6B7, 0x6CB, 0x6DF, 0x6F4, 0x708, 0x71D, 0x732, 0x748, 0x75D, 0x773, 0x789, 0x79F, 0x7B6, 0x7CD, 0x7E4, 0x7FB, 0x813, 0x82B, 0x843, 0x85C, 0x874, 0x88E, 0x8A7, 0x8C1, 0x8DA, 0x8F5, 0x90F, 0x92A, 0x945, 0x961, 0x97D, 0x999, 0x9B5, 0x9D2, 0x9EF, 0xA0D, 0xA2A, 0xA48, 0xA67, 0xA86, 0xAA5, 0xAC5, 0xAE5, 0xB05, 0xB25, 0xB47, 0xB68, 0xB8A, 0xBAC, 0xBCF, 0xBF2, 0xC15, 0xC39, 0xC5D, 0xC82, 0xCA7, 0xCCC, 0xCF2, 0xD19, 0xD3F, 0xD67, 0xD8E, 0xDB7, 0xDDF, 0xE08, 0xE32, 0xE5C, 0xE87, 0xEB2, 0xEDD, 0xF09, 0xF36, 0xF63, 0xF91, 0xFBF, 0xFEE, 0x101D, 0x104D, 0x107D, 0x10AE, 0x10DF, 0x1111, 0x1144, 0x1177, 0x11AB, 0x11DF, 0x1214, 0x124A, 0x1280, 0x12B7, 0x12EE, 0x1326, 0x135F, 0x1399, 0x13D3, 0x140D, 0x1449, 0x1485, 0x14C2, 0x14FF, 0x153E, 0x157D, 0x15BC, 0x15FD, 0x163E, 0x1680, 0x16C3, 0x1706, 0x174A, 0x178F, 0x17D5, 0x181C, 0x1863, 0x18AC, 0x18F5, 0x193F, 0x198A, 0x19D5, 0x1A22, 0x1A6F, 0x1ABE, 0x1B0D, 0x1B5D, 0x1BAE, 0x1C00, 0x1C53, 0x1CA7, 0x1CFC, 0x1D52, 0x1DA9, 0x1E01, 0x1E5A, 0x1EB4, 0x1F0F, 0x1F6B, 0x1FC8, 0x2026, 0x2086, 0x20E6, 0x2148, 0x21AA, 0x220E, 0x2273, 0x22D9, 0x2341, 0x23A9, 0x2413, 0x247E, 0x24EA, 0x2557, 0x25C6, 0x2636, 0x26A7, 0x271A, 0x278E, 0x2803, 0x287A, 0x28F2, 0x296B, 0x29E6, 0x2A62, 0x2AE0, 0x2B5F, 0x2BDF, 0x2C61, 0x2CE5, 0x2D6A, 0x2DF1, 0x2E79, 0x2F03, 0x2F8E, 0x301B, 0x30AA, 0x313A, 0x31CC, 0x325F, 0x32F5, 0x338C, 0x3425, 0x34BF, 0x355B, 0x35FA, 0x369A, 0x373C, 0x37DF, 0x3885, 0x392C, 0x39D6, 0x3A81, 0x3B2F, 0x3BDE, 0x3C90, 0x3D43, 0x3DF9, 0x3EB1, 0x3F6A, 0x4026, 0x40E5, 0x41A5, 0x4268, 0x432C, 0x43F4, 0x44BD, 0x4589, 0x4657, 0x4727, 0x47FA, 0x48D0, 0x49A8, 0x4A82, 0x4B5F, 0x4C3E, 0x4D20, 0x4E05, 0x4EEC, 0x4FD6, 0x50C3, 0x51B2, 0x52A4, 0x5399, 0x5491, 0x558C, 0x5689, 0x578A, 0x588D, 0x5994, 0x5A9D, 0x5BAA, 0x5CBA, 0x5DCD, 0x5EE3, 0x5FFC, 0x6119, 0x6238, 0x635C, 0x6482, 0x65AC, 0x66D9, 0x680A, 0x693F, 0x6A77, 0x6BB2, 0x6CF2, 0x6E35, 0x6F7B, 0x70C6, 0x7214, 0x7366, 0x74BC, 0x7616, 0x7774, 0x78D6, 0x7A3D, 0x7BA7, 0x7D16, 0x7E88, 0x7FFF, 0x817B, 0x82FB, 0x847F, 0x8608, 0x8795, 0x8927, 0x8ABE, 0x8C59, 0x8DF9, 0x8F9E, 0x9148, 0x92F6, 0x94AA, 0x9663, 0x9820, 0x99E3, 0x9BAB, 0x9D79, 0x9F4C, 0xA124, 0xA302, 0xA4E5, 0xA6CE, 0xA8BC, 0xAAB0, 0xACAA, 0xAEAA, 0xB0B0, 0xB2BC, 0xB4CE, 0xB6E5, 0xB904, 0xBB28, 0xBD53, 0xBF84, 0xC1BC, 0xC3FA, 0xC63F, 0xC88B, 0xCADD, 0xCD37, 0xCF97, 0xD1FE, 0xD46D, 0xD6E3, 0xD960, 0xDBE4, 0xDE70, 0xE103, 0xE39E, 0xE641, 0xE8EB, 0xEB9E, 0xEE58, 0xF11B, 0xF3E6, 0xF6B9, 0xF994, 0xFC78, 0xFF64 }; const uint32 pan_values[128] = { 00, 00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFE, 0xFFFFFFFE, 0xFFFFFFFD, 0xFFFFFFFD, 0xFFFFFFFC, 0xFFFFFFFC, 0xFFFFFFFC, 0xFFFFFFFB, 0xFFFFFFFB, 0xFFFFFFFB, 0xFFFFFFFA, 0xFFFFFFFA, 0xFFFFFFF9, 0xFFFFFFF9, 0xFFFFFFF9, 0xFFFFFFF8, 0xFFFFFFF8, 0xFFFFFFF7, 0xFFFFFFF7, 0xFFFFFFF6, 0xFFFFFFF6, 0xFFFFFFF6, 0xFFFFFFF5, 0xFFFFFFF5, 0xFFFFFFF4, 0xFFFFFFF4, 0xFFFFFFF3, 0xFFFFFFF3, 0xFFFFFFF2, 0xFFFFFFF2, 0xFFFFFFF2, 0xFFFFFFF1, 0xFFFFFFF1, 0xFFFFFFF0, 0xFFFFFFF0, 0xFFFFFFEF, 0xFFFFFFEF, 0xFFFFFFEE, 0xFFFFFFEE, 0xFFFFFFED, 0xFFFFFFEC, 0xFFFFFFEC, 0xFFFFFFEB, 0xFFFFFFEB, 0xFFFFFFEA, 0xFFFFFFEA, 0xFFFFFFE9, 0xFFFFFFE9, 0xFFFFFFE8, 0xFFFFFFE7, 0xFFFFFFE7, 0xFFFFFFE6, 0xFFFFFFE6, 0xFFFFFFE5, 0xFFFFFFE4, 0xFFFFFFE4, 0xFFFFFFE3, 0xFFFFFFE2, 0xFFFFFFE2, 0xFFFFFFE1, 0xFFFFFFE0, 0xFFFFFFDF, 0xFFFFFFDF, 0xFFFFFFDE, 0xFFFFFFDD, 0xFFFFFFDC, 0xFFFFFFDC, 0xFFFFFFDB, 0xFFFFFFDA, 0xFFFFFFD9, 0xFFFFFFD8, 0xFFFFFFD8, 0xFFFFFFD7, 0xFFFFFFD6, 0xFFFFFFD5, 0xFFFFFFD4, 0xFFFFFFD3, 0xFFFFFFD2, 0xFFFFFFD1, 0xFFFFFFD0, 0xFFFFFFCF, 0xFFFFFFCE, 0xFFFFFFCD, 0xFFFFFFCC, 0xFFFFFFCA, 0xFFFFFFC9, 0xFFFFFFC8, 0xFFFFFFC7, 0xFFFFFFC5, 0xFFFFFFC4, 0xFFFFFFC3, 0xFFFFFFC1, 0xFFFFFFC0, 0xFFFFFFBE, 0xFFFFFFBD, 0xFFFFFFBB, 0xFFFFFFB9, 0xFFFFFFB8, 0xFFFFFFB6, 0xFFFFFFB4, 0xFFFFFFB2, 0xFFFFFFB0, 0xFFFFFFAD, 0xFFFFFFAB, 0xFFFFFFA9, 0xFFFFFFA6, 0xFFFFFFA3, 0xFFFFFFA0, 0xFFFFFF9D, 0xFFFFFF9A, 0xFFFFFF96, 0xFFFFFF92, 0xFFFFFF8D, 0xFFFFFF88, 0xFFFFFF82, 0xFFFFFF7B, 0xFFFFFF74, 0xFFFFFF6A, 0xFFFFFF5D, 0xFFFFFF4C, 0xFFFFFF2E, 0xFFFFFC78 }; const uint16 pan_values_low[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFC, 0xFFFC, 0xFFFC, 0xFFFB, 0xFFFB, 0xFFFA, 0xFFFA, 0xFFFA, 0xFFF9, 0xFFF9, 0xFFF8, 0xFFF8, 0xFFF7, 0xFFF7, 0xFFF6, 0xFFF5, 0xFFF5, 0xFFF4, 0xFFF4, 0xFFF3, 0xFFF2, 0xFFF2, 0xFFF1, 0xFFF0, 0xFFEF, 0xFFEF, 0xFFEE, 0xFFED, 0xFFEC, 0xFFEB, 0xFFEB, 0xFFEA, 0xFFE9, 0xFFE8, 0xFFE7, 0xFFE6, 0xFFE5, 0xFFE4, 0xFFE3, 0xFFE2, 0xFFE1, 0xFFE0, 0xFFDE, 0xFFDD, 0xFFDC, 0xFFDB, 0xFFDA, 0xFFD8, 0xFFD7, 0xFFD6, 0xFFD4, 0xFFD3, 0xFFD1, 0xFFD0, 0xFFCE, 0xFFCC, 0xFFCB, 0xFFC9, 0xFFC7, 0xFFC6, 0xFFC4, 0xFFC2, 0xFFC0, 0xFFBE, 0xFFBC, 0xFFBA, 0xFFB7, 0xFFB5, 0xFFB3, 0xFFB0, 0xFFAE, 0xFFAB, 0xFFA8, 0xFFA6, 0xFFA3, 0xFFA0, 0xFF9C, 0xFF99, 0xFF96, 0xFF92, 0xFF8E, 0xFF8A, 0xFF86, 0xFF82, 0xFF7D, 0xFF78, 0xFF73, 0xFF6E, 0xFF68, 0xFF61, 0xFF5A, 0xFF53, 0xFF4B, 0xFF42, 0xFF37, 0xFF2C, 0xFF1F, 0xFF0F, 0xFEFB, 0xFEE2, 0xFEBF, 0xFE83, 0xFC40 }; const uint16 pan_values_high[128] = { 0xFFC3, 0xFFC3, 0xFFC4, 0xFFC5, 0xFFC5, 0xFFC6, 0xFFC6, 0xFFC7, 0xFFC8, 0xFFC8, 0xFFC9, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCB, 0xFFCC, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCE, 0xFFCF, 0xFFCF, 0xFFD0, 0xFFD0, 0xFFD1, 0xFFD1, 0xFFD2, 0xFFD2, 0xFFD3, 0xFFD3, 0xFFD4, 0xFFD4, 0xFFD5, 0xFFD5, 0xFFD6, 0xFFD6, 0xFFD7, 0xFFD7, 0xFFD8, 0xFFD8, 0xFFD9, 0xFFD9, 0xFFDA, 0xFFDA, 0xFFDA, 0xFFDB, 0xFFDB, 0xFFDC, 0xFFDC, 0xFFDD, 0xFFDD, 0xFFDD, 0xFFDE, 0xFFDE, 0xFFDF, 0xFFDF, 0xFFE0, 0xFFE0, 0xFFE0, 0xFFE1, 0xFFE1, 0xFFE1, 0xFFE2, 0xFFE2, 0xFFE3, 0xFFE3, 0xFFE3, 0xFFE4, 0xFFE4, 0xFFE4, 0xFFE5, 0xFFE5, 0xFFE5, 0xFFE6, 0xFFE6, 0xFFE6, 0xFFE7, 0xFFE7, 0xFFE7, 0xFFE8, 0xFFE8, 0xFFE8, 0xFFE9, 0xFFE9, 0xFFE9, 0xFFEA, 0xFFEA, 0xFFEA, 0xFFEB, 0xFFEB, 0xFFEB, 0xFFEC, 0xFFEC, 0xFFEC, 0xFFEC, 0xFFED, 0xFFED, 0xFFED, 0xFFEE, 0xFFEE, 0xFFEE, 0xFFEE, 0xFFEF, 0xFFEF, 0xFFEF, 0xFFEF, 0xFFF0, 0xFFF0, 0xFFF0, 0xFFF0, 0xFFF1, 0xFFF1, 0xFFF1, 0xFFF1, 0xFFF2, 0xFFF2, 0xFFF2, 0xFFF2, 0xFFF3, 0xFFF3, 0xFFF3, 0xFFF3, 0xFFF3, 0xFFF4, 0xFFF4, 0xFFF4, 0xFFF4, 0xFFF5 }; } g_snd_user_data{}; void _MIXChannelResetTV(MixChannel* channel, sint32 index) { assert(index == 0); channel->tv_mode = 0; channel->tv_control.pan = 0x40; channel->tv_control.span = 0x7F; channel->tv_control.fader = 0; channel->tv_control.lfe = -960; for (size_t i = 0; i < AX_AUX_BUS_COUNT; ++i) { channel->tv_control.aux[i] = -960; } for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) { for (size_t j = 0; j < AX_TV_CHANNEL_COUNT; ++j) { channel->tv_volume[i][j].volume = 0; channel->tv_volume[i][j].volume_target = 0; } } } void _MIXChannelResetDRC(MixChannel* channel, sint32 index) { assert(index < AX_MAX_NUM_DRC); channel->drc_mode[index] = 0; channel->drc_control[index].pan = 0x40; channel->drc_control[index].span = 0x7F; channel->drc_control[index].fader = 0; channel->drc_control[index].lfe = -960; for (size_t i = 0; i < AX_AUX_BUS_COUNT; ++i) { channel->drc_control[index].aux[i] = -960; } for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) { for (size_t j = 0; j < AX_DRC_CHANNEL_COUNT; ++j) { channel->drc_volume[index][i][j].volume = 0; channel->drc_volume[index][i][j].volume_target = 0; } } } void _MIXChannelResetRmt(MixChannel* channel, sint32 index) { assert(index < AX_MAX_NUM_RMT); channel->rmt_mode[index] = 0; channel->rmt_control[index].pan = 0x40; channel->rmt_control[index].span = 0x7F; channel->rmt_control[index].fader = 0; channel->rmt_control[index].lfe = -960; for (size_t i = 0; i < AX_AUX_BUS_COUNT; ++i) { channel->rmt_control[index].aux[i] = -960; } for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) { for (size_t j = 0; j < AX_RMT_CHANNEL_COUNT; ++j) { channel->rmt_volume[index][i][j].volume = 0; channel->rmt_volume[index][i][j].volume_target = 0; } } } void MIXResetChannelData(MixChannel* channel) { channel->update_mode = AX_UPDATE_MODE_50000000; channel->input_level = 0; channel->volume.volume = 0; channel->volume.volume_target = 0; _MIXChannelResetTV(channel, 0); for (int i = 0; i < AX_MAX_NUM_DRC; ++i) _MIXChannelResetDRC(channel, i); for (int i = 0; i < AX_MAX_NUM_RMT; ++i) _MIXChannelResetRmt(channel, i); } void _MIXControl_SetDevicePan(MixControl* control, int device_type, sint16 channels[]) { const auto pandiff = 0x7F - control->pan; const auto spandiff = 0x7F - control->span; if (device_type == AX_DEV_TV) { const uint32 sound_mode = g_snd_user_data.device_info.tv_sound_mode; if (sound_mode == 3) { channels[0] = g_snd_user_data.pan_values_low[control->pan]; channels[1] = g_snd_user_data.pan_values_low[pandiff]; channels[2] = g_snd_user_data.pan_values_high[pandiff]; channels[3] = g_snd_user_data.pan_values_high[control->pan]; channels[4] = g_snd_user_data.pan_values_high[spandiff]; channels[5] = g_snd_user_data.pan_values_high[control->span]; } else if (sound_mode != 4) { channels[0] = g_snd_user_data.pan_values[control->pan]; channels[1] = g_snd_user_data.pan_values[pandiff]; channels[2] = 0; channels[3] = 0; channels[4] = g_snd_user_data.pan_values[spandiff]; channels[5] = g_snd_user_data.pan_values[control->span]; } else { uint32 pan = 0x7F; if (((uint32)control->pan >> 1) < 0x7F) pan = (uint32)control->pan >> 1; channels[0] = g_snd_user_data.pan_values[pan] + g_snd_user_data.pan_values[spandiff]; uint32 span = 0x7F; if (((uint32)pandiff >> 1) < 0x7E) span = (uint32)pandiff >> 1; channels[1] = g_snd_user_data.pan_values[span] + g_snd_user_data.pan_values[spandiff]; // TODO } } else if (device_type == AX_DEV_DRC) { // TODO } } sint16 __MIXTranslateVolume(sint16 input) { if (input <= -904) return 0; if (input > 0x3C) return -156; return (sint16)g_snd_user_data.volume[input + 903]; } void AXFXInitDefaultHooks(); void MIXInit() { cemuLog_log(LogType::SoundAPI, "MIXInit()"); if (g_snd_user_data.initialized) return; g_snd_user_data.max_voices = AX_MAX_VOICES; // AXGetMaxVoices(); for (sint32 i = 0; i < g_snd_user_data.max_voices; ++i) { MIXResetChannelData(&g_snd_user_data.mix_channel[i]); } g_snd_user_data.initialized = true; g_snd_user_data.device_info.tv_sound_mode = 1; g_snd_user_data.device_info.drc_sound_mode = 1; g_snd_user_data.device_info.rmt_sound_mode = 0; AXFXInitDefaultHooks(); } void MIXSetSoundMode(uint32 sound_mode) { cemuLog_log(LogType::SoundAPI, "MIXSetSoundMode(0x{:x})", sound_mode); if (sound_mode >= 2) sound_mode = 1; g_snd_user_data.device_info.tv_sound_mode = sound_mode; } uint32 MIXGetSoundMode() { cemuLog_log(LogType::SoundAPI, "MIXGetSoundMode()"); return g_snd_user_data.device_info.tv_sound_mode; } void _MIXUpdateTV(MixChannel* channel, sint32 index) { assert(index == 0); bool updated_volume = false; if ((channel->tv_mode & AX_UPDATE_MODE_80000000) != 0) { for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) { for (size_t j = 0; j < AX_TV_CHANNEL_COUNT; ++j) { channel->tv_volume[i][j].volume = channel->tv_volume[i][j].volume_target; } } channel->tv_mode &= ~AX_UPDATE_MODE_80000000; updated_volume = true; } if ((channel->tv_mode & AX_UPDATE_MODE_40000000_VOLUME) == 0) { if (!updated_volume) return; } else { if (g_snd_user_data.device_info.tv_sound_mode == 0) { sint32 chan4 = channel->tv_channels[4]; if (chan4 < -0x78) chan4 = -0x78; const sint32 fader = channel->tv_control.fader; channel->tv_volume[0][0].volume_target = __MIXTranslateVolume(chan4 + fader); channel->tv_volume[0][1].volume_target = __MIXTranslateVolume(chan4 + fader); channel->tv_volume[0][2].volume_target = 0; channel->tv_volume[0][3].volume_target = 0; channel->tv_volume[0][4].volume_target = 0; channel->tv_volume[0][5].volume_target = 0; for (int i = 0; i < 3; ++i) { const sint32 aux = channel->tv_control.aux[i]; if ((channel->tv_mode & (1 << i)) == 0) { channel->tv_volume[1 + i][0].volume_target = __MIXTranslateVolume(chan4 + fader + aux); channel->tv_volume[1 + i][1].volume_target = __MIXTranslateVolume(chan4 + fader + aux); } else { channel->tv_volume[1 + i][0].volume_target = __MIXTranslateVolume(chan4 + aux); channel->tv_volume[1 + i][1].volume_target = __MIXTranslateVolume(chan4 + aux); } channel->tv_volume[1 + i][2].volume_target = 0; channel->tv_volume[1 + i][3].volume_target = 0; channel->tv_volume[1 + i][4].volume_target = 0; channel->tv_volume[1 + i][5].volume_target = 0; } channel->tv_mode &= ~AX_UPDATE_MODE_40000000_VOLUME; channel->tv_mode |= AX_UPDATE_MODE_80000000; } else if (g_snd_user_data.device_info.tv_sound_mode < 3) { sint32 chan4 = channel->tv_channels[4]; if (chan4 < -0x78) chan4 = -0x78; const sint32 fader = channel->tv_control.fader; const sint32 chan0 = channel->tv_channels[0]; const sint32 chan1 = channel->tv_channels[1]; channel->tv_volume[0][0].volume_target = __MIXTranslateVolume(chan4 + chan0 + fader); channel->tv_volume[0][1].volume_target = __MIXTranslateVolume(chan4 + chan1 + fader); channel->tv_volume[0][2].volume_target = 0; channel->tv_volume[0][3].volume_target = 0; channel->tv_volume[0][4].volume_target = 0; channel->tv_volume[0][5].volume_target = 0; for (int i = 0; i < 3; ++i) { const sint32 aux = channel->tv_control.aux[i]; if ((channel->tv_mode & (1 << i)) == 0) { channel->tv_volume[1 + i][0].volume_target = __MIXTranslateVolume(chan4 + chan0 + fader + aux); channel->tv_volume[1 + i][1].volume_target = __MIXTranslateVolume(chan4 + chan1 + fader + aux); } else { channel->tv_volume[1 + i][0].volume_target = __MIXTranslateVolume(chan4 + chan0 + aux); channel->tv_volume[1 + i][1].volume_target = __MIXTranslateVolume(chan4 + chan1 + aux); } channel->tv_volume[1 + i][2].volume_target = 0; channel->tv_volume[1 + i][3].volume_target = 0; channel->tv_volume[1 + i][4].volume_target = 0; channel->tv_volume[1 + i][5].volume_target = 0; } channel->tv_mode &= ~AX_UPDATE_MODE_40000000_VOLUME; channel->tv_mode |= AX_UPDATE_MODE_80000000; } else if (g_snd_user_data.device_info.tv_sound_mode == 3) { // TODO } else if (g_snd_user_data.device_info.tv_sound_mode == 4) { // TODO } else { channel->tv_mode &= ~AX_UPDATE_MODE_40000000_VOLUME; channel->tv_mode |= AX_UPDATE_MODE_80000000; } } AXCHMIX2 mix[AX_TV_CHANNEL_COUNT][AX_MAX_NUM_BUS]; for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) { for (size_t j = 0; j < AX_TV_CHANNEL_COUNT; ++j) { const sint16 target = channel->tv_volume[i][j].volume_target; const sint16 volume = channel->tv_volume[i][j].volume; mix[j][i].vol = volume; mix[j][i].delta = (target - volume) / 96; // 32000HZ SAMPLES_3MS } } AXSetVoiceDeviceMix(channel->voice.GetPtr(), AX_DEV_TV, index, (snd_core::AXCHMIX_DEPR*)&mix[0][0]); } void _MIXUpdateDRC(MixChannel* channel, sint32 index) { // todo } void _MIXUpdateRmt(MixChannel* channel, sint32 index) { // todo } void MIXInitChannel(AXVPB* voice, uint16 mode, uint16 input, uint16 aux1, uint16 aux2, uint16 aux3, uint16 pan, uint16 span, uint16 fader) { cemuLog_log(LogType::SoundAPI, "MIXInitChannel(0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x})", MEMPTR(voice).GetMPTR(), mode, input, aux1, aux2, aux3, pan, span, fader); cemu_assert_debug(voice); AXVoiceBegin(voice); MIXAssignChannel(voice); MIXInitInputControl(voice, input, mode); const uint32 index = voice->index; auto& channel = g_snd_user_data.mix_channel[index]; channel.tv_control.aux[0] = aux1; channel.tv_control.aux[1] = aux2; channel.tv_control.aux[2] = aux3; channel.tv_control.pan = pan; channel.tv_control.span = span; channel.tv_control.fader = fader; // channel.tv_control.lfe = lfe; // 0x1A -> not set? channel.tv_mode = AX_UPDATE_MODE_40000007 & mode; _MIXControl_SetDevicePan(&channel.tv_control, AX_DEV_TV, channel.tv_channels); channel.tv_mode |= AX_UPDATE_MODE_40000000_VOLUME; _MIXUpdateTV(&channel, 0); AXVoiceEnd(voice); } void MIXAssignChannel(AXVPB* voice) { cemuLog_log(LogType::SoundAPI, "MIXAssignChannel(0x{:x})", MEMPTR(voice).GetMPTR()); cemu_assert_debug(voice); AXVoiceBegin(voice); const uint32 voice_index = voice->index; auto channel = &g_snd_user_data.mix_channel[voice_index]; MIXResetChannelData(channel); channel->voice = voice; AXVoiceEnd(voice); } void MIXDRCInitChannel(AXVPB* voice, uint16 mode, uint16 vol1, uint16 vol2, uint16 vol3) { cemuLog_log(LogType::SoundAPI, "MIXDRCInitChannel(0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x})", MEMPTR(voice).GetMPTR(), mode, vol1, vol2, vol3); cemu_assert_debug(voice); AXVoiceBegin(voice); const uint32 index = voice->index; auto& channel = g_snd_user_data.mix_channel[index]; _MIXChannelResetDRC(&channel, 0); channel.drc_volume[1][1][1].volume = vol1; channel.drc_volume[1][1][2].volume_target = vol2; channel.drc_volume[1][1][3].volume_target = vol3; channel.drc_mode[0] = AX_UPDATE_MODE_40000007 & mode; _MIXControl_SetDevicePan(&channel.drc_control[0], AX_DEV_DRC, &channel.drc_channels[0][0]); channel.drc_mode[0] |= AX_UPDATE_MODE_40000000_VOLUME; _MIXUpdateDRC(&channel, 0); AXVoiceEnd(voice); } void MIXSetInput(AXVPB* voice, uint16 input) { cemuLog_log(LogType::SoundAPI, "MIXSetInput(0x{:x}, 0x{:x})", MEMPTR(voice).GetMPTR(), input); const uint32 voice_index = voice->index; const auto channel = &g_snd_user_data.mix_channel[voice_index]; channel->input_level = input; channel->update_mode |= AX_UPDATE_MODE_10000000; } void MIXUpdateSettings() { cemuLog_log(LogType::SoundAPI, "MIXUpdateSettings()"); if (!g_snd_user_data.initialized) return; if (g_snd_user_data.max_voices <= 0) return; for (sint32 i = 0; i < g_snd_user_data.max_voices; ++i) { auto& channel = g_snd_user_data.mix_channel[i]; if (!channel.voice) continue; const auto voice = channel.voice.GetPtr(); AXVoiceBegin(voice); bool volume_updated = false; if ((channel.update_mode & AX_UPDATE_MODE_20000000) != 0) { channel.volume.volume = channel.volume.volume_target; channel.update_mode &= ~AX_UPDATE_MODE_20000000; volume_updated = true; } if ((channel.update_mode & AX_UPDATE_MODE_10000000) == 0) { if (volume_updated) { AXPBVE ve; ve.currentVolume = channel.volume.volume; ve.currentDelta = (channel.volume.volume_target - channel.volume.volume) / 96; AXSetVoiceVe(voice, &ve); } } else { sint32 volume = 0; if ((channel.update_mode & 8) == 0) volume = __MIXTranslateVolume(channel.input_level); channel.volume.volume_target = volume; channel.update_mode &= ~AX_UPDATE_MODE_10000000; channel.update_mode |= AX_UPDATE_MODE_20000000; AXPBVE ve; ve.currentVolume = channel.volume.volume; ve.currentDelta = (channel.volume.volume_target - channel.volume.volume) / 96; AXSetVoiceVe(voice, &ve); } _MIXUpdateTV(&channel, 0); for (int i = 0; i < 2; ++i) _MIXUpdateDRC(&channel, i); // TODO remote mix AXCHMIX2 mix[4]; for (int j = 0; j < 4; ++j) { AXSetVoiceDeviceMix(voice, AX_DEV_RMT, i, (snd_core::AXCHMIX_DEPR*)mix); } AXVoiceEnd(voice); } // TODO } void MIXSetDeviceSoundMode(uint32 device, uint32 mode) { cemuLog_log(LogType::SoundAPI, "MIXSetDeviceSoundMode(0x{:x}, 0x{:x})", device, mode); cemu_assert_debug(device < AX_DEV_COUNT); cemu_assert_debug(mode <= 4); bool is_tv_device = false; bool is_drc_device = false; if (device == AX_DEV_TV) { g_snd_user_data.device_info.tv_sound_mode = mode; is_tv_device = true; } else if (device == AX_DEV_DRC) { cemu_assert_debug(mode <= 2); g_snd_user_data.device_info.drc_sound_mode = mode; is_drc_device = true; } else if (device == AX_DEV_RMT) { cemu_assert_debug(mode == 0); g_snd_user_data.device_info.rmt_sound_mode = mode; } else { cemuLog_log(LogType::SoundAPI, "ERROR: MIXSetDeviceSoundMode(0x{:x}, 0x{:x}) -> wrong device", device, mode); } for (sint32 i = 0; i < g_snd_user_data.max_voices; ++i) { auto& channel = g_snd_user_data.mix_channel[i]; if (!channel.voice) continue; const auto voice = channel.voice.GetPtr(); AXVoiceBegin(voice); if (is_tv_device) { channel.tv_mode |= AX_UPDATE_MODE_40000000_VOLUME; _MIXControl_SetDevicePan(&channel.tv_control, AX_DEV_TV, channel.tv_channels); } if (is_drc_device) { for (sint32 j = 0; j < AX_MAX_NUM_DRC; ++j) { channel.drc_mode[j] |= AX_UPDATE_MODE_40000000_VOLUME; _MIXControl_SetDevicePan(&channel.drc_control[j], AX_DEV_DRC, channel.drc_channels[j]); } } AXVoiceEnd(voice); } } void MIXInitDeviceControl(AXVPB* voice, uint32 device_type, uint32 index, MixControl* control, uint32 mode) { cemuLog_log(LogType::SoundAPI, "MIXInitDeviceControl(0x{:0x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x} )", MEMPTR(voice).GetMPTR(), device_type, index, MEMPTR(control).GetMPTR(), mode); cemu_assert_debug(device_type < AX_DEV_COUNT); cemu_assert_debug(voice); cemu_assert_debug(control); AXVoiceBegin(voice); const uint32 voice_index = voice->index; auto& channel = g_snd_user_data.mix_channel[voice_index]; channel.voice = voice; if (device_type == AX_DEV_TV) { cemu_assert_debug(index == 0); _MIXChannelResetTV(&channel, index); memcpy(&channel.tv_control, control, sizeof(MixControl)); _MIXControl_SetDevicePan(&channel.tv_control, device_type, channel.tv_channels); channel.tv_mode |= AX_UPDATE_MODE_40000000_VOLUME; _MIXUpdateTV(&channel, index); } else if (device_type == AX_DEV_DRC) { cemu_assert_debug(index < 2); _MIXChannelResetDRC(&channel, index); memcpy(&channel.drc_control[index], control, sizeof(MixControl)); _MIXControl_SetDevicePan(&channel.drc_control[index], device_type, channel.drc_channels[index]); channel.drc_mode[index] |= AX_UPDATE_MODE_40000000_VOLUME; _MIXUpdateDRC(&channel, index); } else if (device_type == AX_DEV_RMT) { cemu_assert_debug(index < 4); _MIXChannelResetRmt(&channel, index); memcpy(&channel.rmt_control[index], control, sizeof(MixControl)); _MIXControl_SetDevicePan(&channel.rmt_control[index], device_type, channel.rmt_channels[index]); channel.rmt_mode[index] = mode & 0xf; _MIXUpdateRmt(&channel, index); } AXVoiceEnd(voice); } void MIXInitInputControl(AXVPB* voice, uint16 input, uint32 mode) { cemuLog_log(LogType::SoundAPI, "MIXInitInputControl(0x{:x}, 0x{:x}, 0x{:x} )", MEMPTR(voice).GetMPTR(), input, mode); cemu_assert_debug(voice); AXVoiceBegin(voice); const uint32 voice_index = voice->index; auto& channel = g_snd_user_data.mix_channel[voice_index]; mode &= 8; mode |= AX_UPDATE_MODE_10000000; channel.update_mode = mode; channel.input_level = input; AXVoiceEnd(voice); } void MIXSetDeviceFader(AXVPB* vpb, uint32 device, uint32 deviceIndex, sint16 newFader) { // not well tested cemu_assert(device < AX_DEV_COUNT); MixChannel& mixChannel = g_snd_user_data.mix_channel[vpb->index]; AXVoiceBegin(vpb); MixControl& mixControl = mixChannel.GetMixControl(device, deviceIndex); MixMode& mixMode = mixChannel.GetMode(device, deviceIndex); if (mixControl.fader == newFader) { AXVoiceEnd(vpb); return; } mixControl.fader = newFader; mixMode |= AX_UPDATE_MODE_40000000_VOLUME; AXVoiceEnd(vpb); } void MIXSetDevicePan(AXVPB* vpb, uint32 device, uint32 deviceIndex, sint16 newPan) { // not well tested cemu_assert(device < AX_DEV_COUNT); MixChannel& mixChannel = g_snd_user_data.mix_channel[vpb->index]; AXVoiceBegin(vpb); MixControl& mixControl = mixChannel.GetMixControl(device, deviceIndex); MixMode& mixMode = mixChannel.GetMode(device, deviceIndex); sint16* deviceChannels = mixChannel.GetChannels(device, deviceIndex); if (mixControl.fader == newPan) { AXVoiceEnd(vpb); return; } mixControl.pan = newPan; _MIXControl_SetDevicePan(&mixControl, device, deviceChannels); mixMode |= AX_UPDATE_MODE_40000000_VOLUME; AXVoiceEnd(vpb); } struct AXPBADPCM { uint16be table[8][2]; // 0x00 uint16be gain; uint16be predicator; uint16be yn1; uint16be yn2; }; struct AXPBADPCMLOOP { uint16be predicator; // 0x00 uint16be yn1; // 0x02 uint16be yn2; // 0x04 }; struct SPAdpcmEntry { AXPBADPCM adpcm; AXPBADPCMLOOP adpcmLoop; }; struct SPSoundEntry { uint32be type; // 0x00 uint32be sampleRate; // 0x04 uint32be loopAddress; // 0x08 uint32be loopEndAddress; // 0x0C uint32be endOffset; // 0x10 uint32be currentOffset; // 0x14 MEMPTR<SPAdpcmEntry> adpcmEntry; // 0x18 }; static_assert(sizeof(SPSoundEntry) == 0x1C); struct SPSoundTable { uint32be count; SPSoundEntry entries[1]; }; void SPInitSoundTable(SPSoundTable* soundTable, uint8* samples, uint32be* endOffsetPtr) { cemuLog_log(LogType::SoundAPI, "SPInitSoundTable(0x{:x}, 0x{:x}, 0x{:x} )", MEMPTR(soundTable).GetMPTR(), MEMPTR(samples).GetMPTR(), MEMPTR(endOffsetPtr).GetMPTR()); if (!soundTable) return; if (!samples) return; uint32be endOffset = 0; for(uint32 i = 0; i < soundTable->count; ++i) { auto& entryPtr = soundTable->entries[i]; if(entryPtr.type == 0) { entryPtr.loopAddress = entryPtr.currentOffset; entryPtr.loopEndAddress = 0; uint32be tmp = entryPtr.endOffset >> 1; if (tmp > endOffset) endOffset = tmp; /// ??? } else if (entryPtr.type == 1) { uint32be tmp = entryPtr.endOffset >> 1; if (tmp > endOffset) endOffset = tmp; /// ??? } else if (entryPtr.type == 2) { entryPtr.loopAddress = entryPtr.currentOffset; entryPtr.loopEndAddress = 0; uint32be tmp = entryPtr.endOffset << 1; if (tmp > endOffset) endOffset = tmp; } else if (entryPtr.type == 3) { uint32be tmp = entryPtr.endOffset << 1; if (tmp > endOffset) endOffset = tmp; } else if (entryPtr.type == 4) { entryPtr.loopAddress = entryPtr.currentOffset; entryPtr.loopEndAddress = 0; if (entryPtr.endOffset > endOffset) endOffset = entryPtr.endOffset; } else if (entryPtr.type == 5) { if (entryPtr.endOffset > endOffset) endOffset = entryPtr.endOffset; } } if (endOffsetPtr) *endOffsetPtr = endOffset; } SPSoundEntry* SPGetSoundEntry(SPSoundTable* soundTable, uint32 index) { cemuLog_log(LogType::SoundAPI, "SPGetSoundEntry(0x{:x}, {})", MEMPTR(soundTable).GetMPTR(), index); if (!soundTable) return nullptr; if (soundTable->count <= index) return nullptr; return &soundTable->entries[index]; } MEMPTR<void> s_fxAlloc = nullptr; MEMPTR<void> s_fxFree = nullptr; void _AXDefaultHook_alloc(PPCInterpreter_t* hCPU) { uint32 size = hCPU->gpr[3]; MEMPTR<void> mem = coreinit::_weak_MEMAllocFromDefaultHeap(size); osLib_returnFromFunction(hCPU, mem.GetMPTR()); } void _AXDefaultHook_free(PPCInterpreter_t* hCPU) { MEMPTR<void> mem{ hCPU->gpr[3] }; return coreinit::_weak_MEMFreeToDefaultHeap(mem.GetPtr()); } void AXFXInitDefaultHooks() { // todo - this should only be applied when the library is loaded? MIXInit() does not affect this? if (!s_fxAlloc) { s_fxAlloc = RPLLoader_MakePPCCallable(_AXDefaultHook_alloc); s_fxFree = RPLLoader_MakePPCCallable(_AXDefaultHook_free); } } void AXFXSetHooks(void* allocFunc, void* freeFunc) { s_fxAlloc = allocFunc; s_fxFree = freeFunc; } void AXFXGetHooks(MEMPTR<void>* allocFuncOut, MEMPTR<void>* freeFuncOut) { *allocFuncOut = s_fxAlloc; *freeFuncOut = s_fxFree; } void* AXFXInternalAlloc(uint32 size, bool clearToZero) { if (s_fxAlloc) { MEMPTR<void> mem{ PPCCoreCallback(s_fxAlloc, size) }; if (clearToZero) memset(mem.GetPtr(), 0, size); return mem.GetPtr(); } void* mem = coreinit::_weak_MEMAllocFromDefaultHeapEx(size, 4); if (clearToZero) memset(mem, 0, size); return mem; } void AXFXInternalFree(void* mem) { if (s_fxFree) { PPCCoreCallback(s_fxFree, mem); return; } coreinit::_weak_MEMFreeToDefaultHeap(mem); } /* AUX callback */ struct AUXCBSAMPLEDATA { MEMPTR<sint32be> channelSamples[6]; }; bool gUnsupportedSoundEffectWarning = false; void PrintUnsupportedSoundEffectWarning() { if (gUnsupportedSoundEffectWarning) return; cemuLog_log(LogType::Force, "The currently running title is trying to utilize an unsupported audio effect"); cemuLog_log(LogType::Force, "To emulate these correctly, place snd_user.rpl and snduser2.rpl from the original Wii U firmware in /cafeLibs/ folder"); gUnsupportedSoundEffectWarning = true; } void __UnimplementedFXCallback(AUXCBSAMPLEDATA* auxSamples, size_t sampleCount, bool clearCh0, bool clearCh1, bool clearCh2, bool clearCh3, bool clearCh4, bool clearCh5) { PrintUnsupportedSoundEffectWarning(); bool clearChannel[6] = { clearCh0, clearCh1, clearCh2, clearCh3, clearCh4, clearCh5 }; for (sint32 channel = 0; channel < 6; channel++) { if(!clearChannel[channel]) continue; sint32be* channelPtr = auxSamples->channelSamples[channel].GetPtr(); while (sampleCount) { *channelPtr = 0; channelPtr++; sampleCount--; } } } /* AXFXReverbHi */ struct AXFXReverbHiData { // todo - implement uint32be placeholder; }; void AXFXReverbHiInit(AXFXReverbHiData* param) { cemuLog_log(LogType::Force, "AXFXReverbHiInit - stub"); } void AXFXReverbHiSettings(AXFXReverbHiData* param) { cemuLog_log(LogType::Force, "AXFXReverbHiSettings - stub"); } void AXFXReverbHiShutdown(AXFXReverbHiData* param) { cemuLog_log(LogType::Force, "AXFXReverbHiShutdown - stub"); } void AXFXReverbHiCallback(AUXCBSAMPLEDATA* auxSamples, AXFXReverbHiData* reverbHi) { // todo - implement me __UnimplementedFXCallback(auxSamples, 96, true, true, true, false, false, false); } /* AXFXMultiChReverb */ struct AXFXMultiChReverbData { // todo - implement uint32 placeholder; }; void AXFXMultiChReverbInit(AXFXMultiChReverbData* param, int ukn2, int ukn3) { cemuLog_log(LogType::Force, "AXFXMultiChReverbInit (Stubbed)"); } void AXFXMultiChReverbSettingsUpdate(AXFXMultiChReverbData* param) { // todo } void AXFXMultiChReverbShutdown(AXFXMultiChReverbData* param) { // todo } void AXFXMultiChReverbCallback(AUXCBSAMPLEDATA* auxSamples, AXFXMultiChReverbData* reverbHi, AXAUXCBCHANNELINFO* auxInfo) { // todo - implement me __UnimplementedFXCallback(auxSamples, auxInfo->numSamples, true, true, true, true, true, true); } /* AXFXReverbStd */ struct AXFXReverbStdData { uint32be placeholder; }; uint32 AXFXReverbStdExpGetMemSize(AXFXReverbStdData* reverbParam) { return 0xC; // placeholder size } bool AXFXReverbStdExpInit(AXFXReverbStdData* reverbParam) { return true; } void AXFXReverbStdExpShutdown(AXFXReverbStdData* reverbParam) { } void AXFXReverbStdExpCallback(AUXCBSAMPLEDATA* auxSamples, AXFXReverbStdData* reverbData) { // todo - implement me __UnimplementedFXCallback(auxSamples, 96, true, true, true, false, false, false); } class : public COSModule { public: std::string_view GetName() override { return "snd_user"; } void RPLMapped() override { /* snd_user */ cafeExportRegister("snd_user", MIXInit, LogType::SoundAPI); cafeExportRegister("snd_user", MIXSetSoundMode, LogType::SoundAPI); cafeExportRegister("snd_user", MIXGetSoundMode, LogType::SoundAPI); cafeExportRegister("snd_user", MIXInitChannel, LogType::SoundAPI); cafeExportRegister("snd_user", MIXAssignChannel, LogType::SoundAPI); cafeExportRegister("snd_user", MIXDRCInitChannel, LogType::SoundAPI); cafeExportRegister("snd_user", MIXSetInput, LogType::SoundAPI); cafeExportRegister("snd_user", MIXUpdateSettings, LogType::SoundAPI); cafeExportRegister("snd_user", MIXSetDeviceSoundMode, LogType::SoundAPI); cafeExportRegister("snd_user", MIXSetDeviceFader, LogType::SoundAPI); cafeExportRegister("snd_user", MIXSetDevicePan, LogType::SoundAPI); cafeExportRegister("snd_user", MIXInitDeviceControl, LogType::SoundAPI); cafeExportRegister("snd_user", MIXInitInputControl, LogType::SoundAPI); cafeExportRegister("snd_user", SPInitSoundTable, LogType::SoundAPI); cafeExportRegister("snd_user", SPGetSoundEntry, LogType::SoundAPI); //cafeExportRegister("snd_user", SPPrepareSound); cafeExportRegister("snd_user", AXFXSetHooks, LogType::SoundAPI); cafeExportRegister("snd_user", AXFXGetHooks, LogType::SoundAPI); cafeExportRegister("snd_user", AXFXReverbHiInit, LogType::SoundAPI); cafeExportRegister("snd_user", AXFXReverbHiSettings, LogType::SoundAPI); cafeExportRegister("snd_user", AXFXReverbHiShutdown, LogType::SoundAPI); cafeExportRegister("snd_user", AXFXReverbHiCallback, LogType::SoundAPI); cafeExportRegister("snd_user", AXFXMultiChReverbInit, LogType::SoundAPI); cafeExportRegister("snd_user", AXFXMultiChReverbSettingsUpdate, LogType::SoundAPI); cafeExportRegister("snd_user", AXFXMultiChReverbShutdown, LogType::SoundAPI); cafeExportRegister("snd_user", AXFXMultiChReverbCallback, LogType::SoundAPI); }; }s_COSsnduser1Module; class : public COSModule { public: std::string_view GetName() override { return "snduser2"; } void RPLMapped() override { /* snduser2 */ cafeExportRegister("snduser2", MIXInit, LogType::SoundAPI); cafeExportRegister("snduser2", MIXSetSoundMode, LogType::SoundAPI); cafeExportRegister("snduser2", MIXGetSoundMode, LogType::SoundAPI); cafeExportRegister("snduser2", MIXInitChannel, LogType::SoundAPI); cafeExportRegister("snduser2", MIXAssignChannel, LogType::SoundAPI); cafeExportRegister("snduser2", MIXDRCInitChannel, LogType::SoundAPI); cafeExportRegister("snduser2", MIXSetInput, LogType::SoundAPI); cafeExportRegister("snduser2", MIXUpdateSettings, LogType::SoundAPI); cafeExportRegister("snduser2", MIXSetDeviceSoundMode, LogType::SoundAPI); cafeExportRegister("snduser2", MIXSetDeviceFader, LogType::SoundAPI); cafeExportRegister("snduser2", MIXSetDevicePan, LogType::SoundAPI); cafeExportRegister("snduser2", MIXInitDeviceControl, LogType::SoundAPI); cafeExportRegister("snduser2", MIXInitInputControl, LogType::SoundAPI); cafeExportRegister("snduser2", AXFXSetHooks, LogType::Placeholder); cafeExportRegister("snduser2", AXFXGetHooks, LogType::Placeholder); cafeExportRegister("snduser2", AXFXReverbStdExpGetMemSize, LogType::Placeholder); cafeExportRegister("snduser2", AXFXReverbStdExpInit, LogType::Placeholder); cafeExportRegister("snduser2", AXFXReverbStdExpShutdown, LogType::Placeholder); cafeExportRegister("snduser2", AXFXReverbStdExpCallback, LogType::Placeholder); cafeExportRegister("snduser2", AXFXReverbHiInit, LogType::SoundAPI); cafeExportRegister("snduser2", AXFXReverbHiSettings, LogType::SoundAPI); cafeExportRegister("snduser2", AXFXReverbHiShutdown, LogType::SoundAPI); cafeExportRegister("snduser2", AXFXReverbHiCallback, LogType::SoundAPI); cafeExportRegister("snduser2", AXFXMultiChReverbInit, LogType::SoundAPI); cafeExportRegister("snduser2", AXFXMultiChReverbSettingsUpdate, LogType::SoundAPI); cafeExportRegister("snduser2", AXFXMultiChReverbShutdown, LogType::SoundAPI); cafeExportRegister("snduser2", AXFXMultiChReverbCallback, LogType::SoundAPI); }; }s_COSsnduser2Module; COSModule* GetModuleSndUser1() { return &s_COSsnduser1Module; } COSModule* GetModuleSndUser2() { return &s_COSsnduser2Module; } } ================================================ FILE: src/Cafe/OS/libs/snd_user/snd_user.h ================================================ #pragma once #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/RPL/COSModule.h" namespace snd_user { struct MixControl; void MIXInit(); void MIXInitInputControl(snd_core::AXVPB* voice, uint16 input, uint32 mode); void MIXInitDeviceControl(snd_core::AXVPB* voice, uint32 device_type, uint32 index, MixControl* control, uint32 mode); void MIXSetDeviceSoundMode(uint32 device, uint32 mode); void MIXUpdateSettings(); void MIXSetInput(snd_core::AXVPB* voice, uint16 input); void MIXDRCInitChannel(snd_core::AXVPB* voice, uint16 mode, uint16 vol1, uint16 vol2, uint16 vol3); void MIXAssignChannel(snd_core::AXVPB* voice); void MIXInitChannel(snd_core::AXVPB* voice, uint16 mode, uint16 input, uint16 aux1, uint16 aux2, uint16 aux3, uint16 pan, uint16 span, uint16 fader); uint32 MIXGetSoundMode(); void MIXSetSoundMode(uint32 sound_mode); COSModule* GetModuleSndUser1(); COSModule* GetModuleSndUser2(); } ================================================ FILE: src/Cafe/OS/libs/swkbd/swkbd.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include <imgui.h> #include "imgui/imgui_extension.h" #include "util/helpers/helpers.h" #include "resource/IconsFontAwesome5.h" #define SWKBD_FORM_STRING_MAX_LENGTH (4096) // counted in 16-bit characters #define SWKBD_STATE_BLANK (0) // not visible #define SWKBD_STATE_APPEARING (1) // fade-in ? #define SWKBD_STATE_DISPLAYED (2) // visible #define SWKBD_STATE_DISAPPEARING (3) // fade-out ? typedef struct { uint32 ukn00; // constructor? uint32 ukn04; // destructor? uint32 ukn08; // ? MEMPTR<void> changeString; // some function address }SwkbdIEventReceiverVTable_t; typedef struct { MEMPTR<SwkbdIEventReceiverVTable_t> vTable; // todo - more elements? (currently separated from this struct) }SwkbdIEventReceiver_t; struct swkbdReceiverArg_t { MEMPTR<SwkbdIEventReceiver_t> IEventReceiver; MEMPTR<sint16be> stringBuf; sint32be stringBufSize; sint32be fixedCharLimit; sint32be cursorPos; sint32be selectFrom; }; typedef struct { uint32 ukn000; uint32 controllerType; uint32 keyboardMode; // guessed uint32 ukn00C; uint32 ukn010; uint32 ukn014; uint32 ukn018; uint32 ukn01C; // ok string? uint32 ukn020[4]; uint32 ukn030[4]; uint32 ukn040[4]; uint32 ukn050[4]; uint32 ukn060[4]; uint32 ukn070[4]; uint32 ukn080[4]; uint32 ukn090[4]; uint32 ukn0A0; uint32 ukn0A4; //uint32 ukn0A8; //MEMPTR<SwkbdIEventReceiver_t> IEventReceiver; swkbdReceiverArg_t receiverArg; }SwkbdKeyboardArg_t; typedef struct { // this structure resides in PPC addressable memory space wchar_t formStringBuffer[SWKBD_FORM_STRING_MAX_LENGTH]; sint32 formStringLength; // big endian version of the string buffer (converted whenever GetInputFormString is called) uint16 formStringBufferBE[SWKBD_FORM_STRING_MAX_LENGTH]; bool isActive; // set when SwkbdAppearInputForm() is called //bool isDisplayed; // set when keyboard is rendering bool decideButtonWasPressed; // set to false when keyboard appears, and set to true when enter is pressed. Remains on true after the keyboard is disappeared (todo: Investigate how this really works) // keyboard only mode (no input form) bool keyboardOnlyMode; SwkbdKeyboardArg_t keyboardArg; // input form appear args sint32 maxTextLength; // imgui keyboard drawing stuff bool shiftActivated; bool returnState; bool cancelState; }swkbdInternalState_t; swkbdInternalState_t* swkbdInternalState = NULL; void swkbdExport_SwkbdCreate(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "swkbd.SwkbdCreate(0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); if( swkbdInternalState == NULL ) { MPTR swkbdInternalStateMPTR = coreinit_allocFromSysArea(sizeof(swkbdInternalState_t), 4); swkbdInternalState = (swkbdInternalState_t*)memory_getPointerFromVirtualOffset(swkbdInternalStateMPTR); memset(swkbdInternalState, 0x00, sizeof(swkbdInternalState_t)); } osLib_returnFromFunction(hCPU, 0); // should return true? } void swkbdExport_SwkbdGetStateKeyboard(PPCInterpreter_t* hCPU) { uint32 r = SWKBD_STATE_BLANK; if( swkbdInternalState->isActive ) r = SWKBD_STATE_DISPLAYED; osLib_returnFromFunction(hCPU, r); } void swkbdExport_SwkbdGetStateInputForm(PPCInterpreter_t* hCPU) { //debug_printf("SwkbdGetStateInputForm__3RplFv LR: %08x\n", hCPU->sprNew.LR); uint32 r = SWKBD_STATE_BLANK; if( swkbdInternalState->isActive ) r = SWKBD_STATE_DISPLAYED; osLib_returnFromFunction(hCPU, r); } //ReceiverArg: //+0x00 IEventReceiver* //+0x04 stringBuf //+ 0x08 stringBufSize //+ 0x0C fixedCharNumLimit(-1) //+ 0x10 cursorPos //+ 0x14 selectFrom(-1) // //IEventReceiver: //+0x00 IEventReceiver_vTable* //+0x04 ? //+0x08 ? //+0x0C ? // //IEventReceiver_vTable : // +0x00 ? // +0x04 ? // +0x08 ? // +0x0C functionPtr onDirtyString(const DirtyInfo& info) = 0; ->DirtyInfo is just two DWORDs.From and to ? // ? typedef struct { MPTR vTable; // guessed }swkdbIEventReceiver_t; typedef struct { uint32 ukn00; uint32 ukn04; uint32 ukn08; MPTR onDirtyString; }swkdbIEventReceiverVTable_t; void swkbdExport_SwkbdSetReceiver(PPCInterpreter_t* hCPU) { debug_printf("SwkbdSetReceiver(0x%08x)\n", hCPU->gpr[3]); swkbdReceiverArg_t* receiverArg = (swkbdReceiverArg_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); if(swkbdInternalState == nullptr) { osLib_returnFromFunction(hCPU, 0); return; } swkbdInternalState->keyboardArg.receiverArg = *receiverArg; osLib_returnFromFunction(hCPU, 0); } typedef struct { /* +0x00 */ uint32 ukn00; /* +0x04 */ uint32 ukn04; /* +0x08 */ uint32 ukn08; /* +0x0C */ uint32 ukn0C; /* +0x10 */ uint32 ukn10; /* +0x14 */ uint32 ukn14; /* +0x18 */ uint32 ukn18; /* +0x1C */ uint32 ukn1C; // pointer to OK string /* +0x20 */ uint32 ukn20[4]; /* +0x30 */ uint32 ukn30[4]; /* +0x40 */ uint32 ukn40[4]; /* +0x50 */ uint32 ukn50[4]; /* +0x60 */ uint32 ukn60[4]; /* +0x70 */ uint32 ukn70[4]; /* +0x80 */ uint32 ukn80[4]; /* +0x90 */ uint32 ukn90[4]; /* +0xA0 */ uint32 uknA0[4]; /* +0xB0 */ uint32 uknB0[4]; /* +0xC0 */ uint32 inputFormType; /* +0xC4 */ uint32be cursorIndex; /* +0xC8 */ MEMPTR<uint16be> initialText; /* +0xCC */ MEMPTR<uint16be> infoText; /* +0xD0 */ uint32be maxTextLength; }swkbdAppearArg_t; static_assert(offsetof(swkbdAppearArg_t, cursorIndex) == 0xC4, "appearArg.cursorIndex has invalid offset"); void swkbdExport_SwkbdAppearInputForm(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(appearArg, swkbdAppearArg_t, 0); cemuLog_logDebug(LogType::Force, "SwkbdAppearInputForm__3RplFRCQ3_2nn5swkbd9AppearArg"); swkbdInternalState->formStringLength = 0; swkbdInternalState->isActive = true; swkbdInternalState->decideButtonWasPressed = false; swkbdInternalState->keyboardOnlyMode = false; // setup max text length swkbdInternalState->maxTextLength = (sint32)(uint32)appearArg->maxTextLength; if (swkbdInternalState->maxTextLength <= 0) swkbdInternalState->maxTextLength = SWKBD_FORM_STRING_MAX_LENGTH - 1; else swkbdInternalState->maxTextLength = std::min(swkbdInternalState->maxTextLength, SWKBD_FORM_STRING_MAX_LENGTH - 1); // setup initial string uint16be* initialString = appearArg->initialText.GetPtr(); if (initialString) { swkbdInternalState->formStringLength = 0; for (sint32 i = 0; i < swkbdInternalState->maxTextLength; i++) { wchar_t c = (uint16)initialString[i]; if( c == '\0' ) break; swkbdInternalState->formStringBuffer[i] = c; swkbdInternalState->formStringLength++; } } else { swkbdInternalState->formStringBuffer[0] = '\0'; swkbdInternalState->formStringLength = 0; } osLib_returnFromFunction(hCPU, 1); } void swkbdExport_SwkbdAppearKeyboard(PPCInterpreter_t* hCPU) { // todo: Figure out what the difference between AppearInputForm and AppearKeyboard is? cemuLog_logDebug(LogType::Force, "SwkbdAppearKeyboard__3RplFRCQ3_2nn5swkbd11KeyboardArg"); SwkbdKeyboardArg_t* keyboardArg = (SwkbdKeyboardArg_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint32 argPtr = hCPU->gpr[3]; for(sint32 i=0; i<0x180; i += 4) { debug_printf("+0x%03x: 0x%08x\n", i, memory_readU32(argPtr+i)); } swkbdInternalState->formStringLength = 0; swkbdInternalState->isActive = true; swkbdInternalState->keyboardOnlyMode = true; swkbdInternalState->decideButtonWasPressed = false; swkbdInternalState->formStringBuffer[0] = '\0'; swkbdInternalState->formStringLength = 0; swkbdInternalState->keyboardArg = *keyboardArg; osLib_returnFromFunction(hCPU, 1); } void swkbdExport_SwkbdDisappearInputForm(PPCInterpreter_t* hCPU) { debug_printf("SwkbdDisappearInputForm__3RplFv LR: %08x\n", hCPU->spr.LR); swkbdInternalState->isActive = false; osLib_returnFromFunction(hCPU, 1); } void swkbdExport_SwkbdDisappearKeyboard(PPCInterpreter_t* hCPU) { debug_printf("SwkbdDisappearKeyboard__3RplFv LR: %08x\n", hCPU->spr.LR); swkbdInternalState->isActive = false; osLib_returnFromFunction(hCPU, 1); } void swkbdExport_SwkbdGetInputFormString(PPCInterpreter_t* hCPU) { for(sint32 i=0; i<swkbdInternalState->formStringLength; i++) { swkbdInternalState->formStringBufferBE[i] = _swapEndianU16(swkbdInternalState->formStringBuffer[i]); } swkbdInternalState->formStringBufferBE[swkbdInternalState->formStringLength] = '\0'; osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(swkbdInternalState->formStringBufferBE)); } void swkbdExport_SwkbdIsDecideOkButton(PPCInterpreter_t* hCPU) { if (swkbdInternalState->decideButtonWasPressed) osLib_returnFromFunction(hCPU, 1); else osLib_returnFromFunction(hCPU, 0); } typedef struct { uint32be ukn00; uint32be ukn04; uint32be ukn08; uint32be ukn0C; uint32be ukn10; uint32be ukn14; uint8 ukn18; // there might be padding here? }SwkbdDrawStringInfo_t; static_assert(sizeof(SwkbdDrawStringInfo_t) != 0x19, "SwkbdDrawStringInfo_t has invalid size"); void swkbdExport_SwkbdGetDrawStringInfo(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "SwkbdGetDrawStringInfo(0x{:08x})", hCPU->gpr[3]); ppcDefineParamStructPtr(drawStringInfo, SwkbdDrawStringInfo_t, 0); drawStringInfo->ukn00 = -1; drawStringInfo->ukn04 = -1; drawStringInfo->ukn08 = -1; drawStringInfo->ukn0C = -1; drawStringInfo->ukn10 = -1; drawStringInfo->ukn14 = -1; drawStringInfo->ukn18 = 0; osLib_returnFromFunction(hCPU, 0); } void swkbdExport_SwkbdInitLearnDic(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "SwkbdInitLearnDic(0x{:08x})", hCPU->gpr[3]); // todo // this has to fail (at least once?) or MH3U will not boot osLib_returnFromFunction(hCPU, 1); } bool isNeedCalc0 = true; bool isNeedCalc1 = true; void swkbdExport_SwkbdIsNeedCalcSubThreadFont(PPCInterpreter_t* hCPU) { // SwkbdIsNeedCalcSubThreadFont__3RplFv bool r = false; osLib_returnFromFunction(hCPU, r?1:0); } void swkbdExport_SwkbdIsNeedCalcSubThreadPredict(PPCInterpreter_t* hCPU) { // SwkbdIsNeedCalcSubThreadPredict__3RplFv bool r = false; osLib_returnFromFunction(hCPU, r?1:0); } void swkbd_keyInput(uint32 keyCode); void swkbd_render(bool mainWindow) { // only render if active if( swkbdInternalState == NULL || swkbdInternalState->isActive == false) return; auto& io = ImGui::GetIO(); const auto font = ImGui_GetFont(48.0f); const auto textFont = ImGui_GetFont(24.0f); if (!font || !textFont) return; const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings; ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); ImGui::SetNextWindowPos({ 0,0 }, ImGuiCond_Always); ImGui::SetNextWindowSize(io.DisplaySize, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0,0 }); ImGui::SetNextWindowBgAlpha(0.8f); ImGui::Begin("Background overlay", nullptr, kPopupFlags | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus); ImGui::End(); ImGui::PopStyleVar(2); ImVec2 position = { io.DisplaySize.x / 2.0f, io.DisplaySize.y / 3.0f }; ImVec2 pivot = { 0.5f, 0.5f }; const auto button_len = font->GetCharAdvance('W'); const float len = button_len * std::max(4, std::max(swkbdInternalState->maxTextLength, (sint32)swkbdInternalState->keyboardArg.receiverArg.stringBufSize)); ImVec2 box_size = { std::min(io.DisplaySize.x * 0.9f, len + 90), 0 }; ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(box_size, ImGuiCond_Always); ImGui::SetNextWindowBgAlpha(0.9f); ImGui::PushFont(font); if (ImGui::Begin("Keyboard Input", nullptr, kPopupFlags)) { ImGui::Text("%s", _utf8WrapperPtr(ICON_FA_KEYBOARD)); ImGui::SameLine(70); auto text = boost::nowide::narrow(fmt::format(L"{}", swkbdInternalState->formStringBuffer)); static std::chrono::steady_clock::time_point s_last_tick = tick_cached(); static bool s_blink_state = false; const auto now = tick_cached(); if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 500) { s_blink_state = !s_blink_state; s_last_tick = now; } if (s_blink_state) text += "|"; ImGui::PushTextWrapPos(); ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); ImGui::PopTextWrapPos(); position.y += ImGui::GetWindowSize().y + 100.0f; } ImGui::End(); ImGui::PopFont(); ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); //ImGui::SetNextWindowSize({ io.DisplaySize.x * 0.9f , 0.0f}, ImGuiCond_Always); ImGui::SetNextWindowBgAlpha(0.9f); ImGui::PushFont(textFont); if (ImGui::Begin(fmt::format("Software keyboard##SoftwareKeyboard{}",mainWindow).c_str(), nullptr, kPopupFlags)) { if(swkbdInternalState->shiftActivated) { const char* keys[] = { "#", "[", "]", "$", "%", "^", "&", "*", "(", ")", "_", _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT), "\n", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "@", "\n", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "\"", "\n", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "+", "=", "\n", _utf8WrapperPtr(ICON_FA_ARROW_UP), " ", _utf8WrapperPtr(ICON_FA_CHECK) }; for (auto key : keys) { if (*key != '\n') { if (ImGui::Button(key, { *key == ' ' ? 537 : (button_len + 5), 0})) { if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT)) == 0) swkbd_keyInput(8); else if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_UP)) == 0) swkbdInternalState->shiftActivated = !swkbdInternalState->shiftActivated; else if (strcmp(key, _utf8WrapperPtr(ICON_FA_CHECK)) == 0) swkbd_keyInput(13); else swkbd_keyInput(*key); } ImGui::SameLine(); } else ImGui::NewLine(); } } else { const char* keys[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT), "\n", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "/", "\n", "a", "s", "d", "f", "g", "h", "j", "k", "l", ":", "'", "\n", "z", "x", "c", "v", "b", "n", "m", ",", ".", "?", "!", "\n", _utf8WrapperPtr(ICON_FA_ARROW_UP), " ", _utf8WrapperPtr(ICON_FA_CHECK) }; for (auto key : keys) { if (*key != '\n') { if (ImGui::Button(key, { *key == ' ' ? 537 : (button_len + 5), 0 })) { if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT)) == 0) swkbd_keyInput(8); else if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_UP)) == 0) swkbdInternalState->shiftActivated = !swkbdInternalState->shiftActivated; else if (strcmp(key, _utf8WrapperPtr(ICON_FA_CHECK)) == 0) swkbd_keyInput(13); else swkbd_keyInput(*key); } ImGui::SameLine(); } else ImGui::NewLine(); } } ImGui::NewLine(); } ImGui::End(); if (io.NavInputs[ImGuiNavInput_Cancel] > 0) { if(!swkbdInternalState->cancelState) swkbd_keyInput(8); // backspace swkbdInternalState->cancelState = true; } else swkbdInternalState->cancelState = false; if (io.NavInputs[ImGuiNavInput_Input] > 0) { if (!swkbdInternalState->returnState) swkbd_keyInput(13); // return swkbdInternalState->returnState = true; } else swkbdInternalState->returnState = false; ImGui::PopFont(); ImGui::PopStyleColor(); } bool swkbd_hasKeyboardInputHook() { return swkbdInternalState != NULL && swkbdInternalState->isActive; } void swkbd_finishInput() { swkbdInternalState->decideButtonWasPressed = true; // currently we always accept the input } typedef struct { uint32be beginIndex; uint32be endIndex; }changeStringParam_t; SysAllocator<changeStringParam_t> _changeStringParam; void swkbd_inputStringChanged() { if( true )//swkbdInternalState->keyboardOnlyMode ) { // write changed string to application's string buffer uint32 stringBufferSize = swkbdInternalState->keyboardArg.receiverArg.stringBufSize; // in 2-byte words if( stringBufferSize > 1 ) { stringBufferSize--; // don't count the null-termination character const auto stringBufferBE = swkbdInternalState->keyboardArg.receiverArg.stringBuf.GetPtr(); sint32 copyLength = std::min((sint32)stringBufferSize, swkbdInternalState->formStringLength); for(sint32 i=0; i<copyLength; i++) { stringBufferBE[i] = swkbdInternalState->formStringBuffer[i]; } stringBufferBE[copyLength] = '\0'; //swkbdInternalState->keyboardArg.cursorPos = copyLength; } // IEventReceiver callback if (swkbdInternalState->keyboardArg.receiverArg.IEventReceiver) { SwkbdIEventReceiver_t* eventReceiver = swkbdInternalState->keyboardArg.receiverArg.IEventReceiver.GetPtr(); MPTR cbChangeString = eventReceiver->vTable->changeString.GetMPTR(); if (cbChangeString) { changeStringParam_t* changeStringParam = _changeStringParam.GetPtr(); changeStringParam->beginIndex = 0; changeStringParam->endIndex = 0; coreinitAsyncCallback_add(cbChangeString, 2, memory_getVirtualOffsetFromPointer(eventReceiver), _changeStringParam.GetMPTR()); } } } } void swkbd_keyInput(uint32 keyCode) { if (keyCode == 8 || keyCode == 127) // backspace || backwards delete { if (swkbdInternalState->formStringLength > 0) swkbdInternalState->formStringLength--; swkbdInternalState->formStringBuffer[swkbdInternalState->formStringLength] = '\0'; swkbd_inputStringChanged(); return; } else if (keyCode == 13) // return { swkbd_finishInput(); return; } // check if allowed character if ((keyCode >= 'a' && keyCode <= 'z') || (keyCode >= 'A' && keyCode <= 'Z')) { // allowed } else if ((keyCode >= '0' && keyCode <= '9')) { // allowed } else if (keyCode == ' ' || keyCode == '.' || keyCode == '/' || keyCode == '?' || keyCode == '=') { // allowed } else if (keyCode < 32) { // control key return; } else ;// return; // get max length sint32 maxLength = swkbdInternalState->maxTextLength; if (swkbdInternalState->keyboardOnlyMode) { uint32 stringBufferSize = swkbdInternalState->keyboardArg.receiverArg.stringBufSize; if (stringBufferSize > 1) { maxLength = stringBufferSize - 1; // don't count the null-termination character } else maxLength = 0; } // append character if (swkbdInternalState->formStringLength < maxLength) { swkbdInternalState->formStringBuffer[swkbdInternalState->formStringLength] = keyCode; swkbdInternalState->formStringLength++; swkbdInternalState->formStringBuffer[swkbdInternalState->formStringLength] = '\0'; swkbd_inputStringChanged(); } } namespace swkbd { class : public COSModule { public: std::string_view GetName() override { return "swkbd"; } void RPLMapped() override { osLib_addFunction("swkbd", "SwkbdCreate__3RplFPUcQ3_2nn5swkbd10RegionTypeUiP8FSClient", swkbdExport_SwkbdCreate); osLib_addFunction("swkbd", "SwkbdGetStateKeyboard__3RplFv", swkbdExport_SwkbdGetStateKeyboard); osLib_addFunction("swkbd", "SwkbdGetStateInputForm__3RplFv", swkbdExport_SwkbdGetStateInputForm); osLib_addFunction("swkbd", "SwkbdSetReceiver__3RplFRCQ3_2nn5swkbd11ReceiverArg", swkbdExport_SwkbdSetReceiver); osLib_addFunction("swkbd", "SwkbdAppearInputForm__3RplFRCQ3_2nn5swkbd9AppearArg", swkbdExport_SwkbdAppearInputForm); osLib_addFunction("swkbd", "SwkbdDisappearInputForm__3RplFv", swkbdExport_SwkbdDisappearInputForm); osLib_addFunction("swkbd", "SwkbdDisappearKeyboard__3RplFv", swkbdExport_SwkbdDisappearKeyboard); osLib_addFunction("swkbd", "SwkbdAppearKeyboard__3RplFRCQ3_2nn5swkbd11KeyboardArg", swkbdExport_SwkbdAppearKeyboard); osLib_addFunction("swkbd", "SwkbdGetInputFormString__3RplFv", swkbdExport_SwkbdGetInputFormString); osLib_addFunction("swkbd", "SwkbdIsDecideOkButton__3RplFPb", swkbdExport_SwkbdIsDecideOkButton); osLib_addFunction("swkbd", "SwkbdInitLearnDic__3RplFPv", swkbdExport_SwkbdInitLearnDic); osLib_addFunction("swkbd", "SwkbdGetDrawStringInfo__3RplFPQ3_2nn5swkbd14DrawStringInfo", swkbdExport_SwkbdGetDrawStringInfo); osLib_addFunction("swkbd", "SwkbdIsNeedCalcSubThreadFont__3RplFv", swkbdExport_SwkbdIsNeedCalcSubThreadFont); osLib_addFunction("swkbd", "SwkbdIsNeedCalcSubThreadPredict__3RplFv", swkbdExport_SwkbdIsNeedCalcSubThreadPredict); }; void rpl_entry(uint32 moduleHandle, coreinit::RplEntryReason reason) override { if (reason == coreinit::RplEntryReason::Loaded) { // todo } else if (reason == coreinit::RplEntryReason::Unloaded) { // todo } } }s_COSswkbdModule; COSModule* GetModule() { return &s_COSswkbdModule; } } ================================================ FILE: src/Cafe/OS/libs/swkbd/swkbd.h ================================================ #include "Cafe/OS/RPL/COSModule.h" void swkbd_render(bool mainWindow); bool swkbd_hasKeyboardInputHook(); void swkbd_keyInput(uint32 keyCode); namespace swkbd { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/sysapp/sysapp.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "sysapp.h" #include "Cafe/CafeSystem.h" #include "Cafe/OS/libs/coreinit/coreinit_FG.h" #include "Cafe/OS/libs/coreinit/coreinit_Misc.h" typedef struct { MEMPTR<char> argStr; uint32be size; }sysStandardArguments_t; typedef struct { // guessed /* +0x00 */ MEMPTR<uint8> sysAnchor; /* +0x04 */ uint32be sysAnchorSize; /* +0x08 */ MEMPTR<uint8> sysResult; /* +0x0C */ uint32be sysResultSize; }sysDeserializeStandardArguments_t; typedef struct { // used for setting arguments, maybe wrong? /* +0x00 */ sysStandardArguments_t standardArguments; /* +0x08 */ uint32be mode; /* +0x0C */ uint32be slot_id; /* +0x10 */ MEMPTR<uint8> mii; }sysMiiStudioArguments_t; typedef struct { // used for getting arguments /* +0x00 */ sysStandardArguments_t standardArguments; // ? /* +0x08 */ uint32 ukn08; /* +0x0C */ uint32 ukn0C; /* +0x10 */ uint32be mode; /* +0x14 */ uint32be slot_id; /* +0x18 */ MEMPTR<uint8> mii; /* +0x1C */ uint32be miiSize; }sysMiiStudioArguments2_t; typedef struct { /* +0x00 */ sysDeserializeStandardArguments_t standardArguments; /* +0x10 */ uint32be jumpTo; // see below for list of available values /* +0x14 */ uint32be needsReturn; /* +0x18 */ uint32be firstBootMode; }sysSettingsArguments_t; static_assert(sizeof(sysSettingsArguments_t) == 0x1C); static_assert(offsetof(sysSettingsArguments_t, jumpTo) == 0x10); static_assert(offsetof(sysSettingsArguments_t, needsReturn) == 0x14); static_assert(offsetof(sysSettingsArguments_t, firstBootMode) == 0x18); /* Sys settings jumpTo values: 0x00 -> Normal launch 0x01 -> Internet settings 0x02 -> Data management 0x03 -> TV remote 0x04 -> Date settings 0x05 -> Country settings 0x06 -> System update 0x64 -> Initial system configuration (after Wii U GamePad was configured) 0x65 -> Crashes 0x66 -> Delete All Content and Settings (throws erreula after a second) 0x67 -> Quick Start Settings 0x68 -> TV connection type 0x69 -> Data management 0xFF -> Data transfer */ typedef struct { /* +0x00 */ sysDeserializeStandardArguments_t standardArguments; // guessed /* +0x10 */ uint32be ukn10; /* +0x14 */ uint32be ukn14; }eshopArguments_t; static_assert(sizeof(eshopArguments_t) == 0x18); #define SYS_STANDARD_ARGS_MAX_LEN 0x1000 #define OS_COPY_MAX_DATA_SIZE (0x400000-4) void appendDataToBuffer(uint32 currentCopyDataSize, char* str, sint32 size) { cemu_assert_unimplemented(); } sint32 _serializeArgsToBuffer(uint32 currentCopyDataSize, const char* argName, char* str, sint32 size, void (*appendFunc)(uint32 currentCopyDataSize, char* str, sint32 size)) { if (strnlen(argName, 0x41) == 0x40) return -0x2710; if (size > 0x200000) return -0x7530; char tempStr[0x80]; if (size != 0 && str != nullptr) { sint32 textSize = sprintf(tempStr, "<%s %s=%u>", argName, "size", size); if ((currentCopyDataSize + textSize + size) >= OS_COPY_MAX_DATA_SIZE) return 0xFFFF63C0; appendFunc(currentCopyDataSize, str, size); appendFunc(currentCopyDataSize, tempStr, textSize); } else { sint32 textSize = sprintf(tempStr, "<%s>", argName); appendFunc(currentCopyDataSize, tempStr, textSize); } return 0; } sint32 SYSPackArgs() { uint32 currentCopyDataSize = coreinit::__OSGetCopyDataSize(); char sysPackStr[128]; sint32 textSize = sprintf(sysPackStr, "<%s %s=%u>", "sys:pack", "size", currentCopyDataSize); if ((currentCopyDataSize + textSize) > OS_COPY_MAX_DATA_SIZE) return 0xFFFF4480; coreinit::__OSAppendCopyData((uint8*)sysPackStr, textSize); return 0; } sint32 SYSSerializeSysArgs(const char* argName, char* str, sint32 size) { uint32 currentCopyDataSize = coreinit::__OSGetCopyDataSize(); sint32 r = _serializeArgsToBuffer(currentCopyDataSize, argName, str, size, appendDataToBuffer); if (r < 0) return r; return 0; } sint32 _SYSSerializeStandardArgsIn(sysStandardArguments_t* standardArgs) { if (strnlen(standardArgs->argStr.GetPtr(), SYS_STANDARD_ARGS_MAX_LEN + 4) > SYS_STANDARD_ARGS_MAX_LEN) { return 0xFFFF63C0; } SYSSerializeSysArgs("sys:anchor", standardArgs->argStr.GetPtr(), standardArgs->size); SYSPackArgs(); return 0; } sint32 _SYSAppendCallerInfo() { // todo return 0; } typedef struct { uint32be id; uint32be type; // union data here - todo }sysArgSlot_t; #define SYS_ARG_ID_END 0 // indicates end of sysArgSlot_t array #define SYS_ARG_ID_ANCHOR 100 typedef struct { char* argument; uint32 size; uint8* data; }deserializedArg_t; uint32 _sysArg_packSize = 0; void clearSysArgs() { _sysArg_packSize = 0; } void deserializeSysArg(deserializedArg_t* deserializedArg) { if (strcmp(deserializedArg->argument, "sys:pack") == 0) _sysArg_packSize = deserializedArg->size; // todo } sint32 _deserializeSysArgsEx2(uint8* copyDataPtr, sint32 copyDataSize, void(*cbDeserializeArg)(deserializedArg_t* deserializedArg, void* customParam), void* customParam) { sint32 idx = copyDataSize - 1; sint32 argSlotIndex = 0; while (idx >= 0) { if (copyDataPtr[idx] == '>') { // find matching '<' sint32 idxStart = idx; while (true) { idxStart--; if (idxStart < 0) return 1; if (copyDataPtr[idxStart] == '<') break; } sint32 idxDataEnd = idxStart; idxStart++; // parse argument name char argumentName[64]; sint32 idxCurrent = idxStart; while( idxCurrent <= idx ) { argumentName[idxCurrent - idxStart] = copyDataPtr[idxCurrent]; if ((idxCurrent - idxStart) >= 60) return 1; if (copyDataPtr[idxCurrent] == ' ' || copyDataPtr[idxCurrent] == '>') { argumentName[idxCurrent - idxStart] = '\0'; break; } idxCurrent++; } // parse size (if any) while (copyDataPtr[idxCurrent] == ' ') idxCurrent++; sint32 argumentDataSize; if (copyDataPtr[idxCurrent] == '>') { argumentDataSize = 0; } else if ((copyDataSize - idxCurrent) >= 5 && memcmp(copyDataPtr+idxCurrent, "size", 4) == 0) { idxCurrent += 4; // skip whitespaces and '=' while (copyDataPtr[idxCurrent] == ' ' || copyDataPtr[idxCurrent] == '=') idxCurrent++; // parse size value char tempSizeStr[32]; sint32 sizeParamLen = idx - idxCurrent; if (sizeParamLen < 0 || sizeParamLen >= 30) return 1; memcpy(tempSizeStr, copyDataPtr+idxCurrent, sizeParamLen); tempSizeStr[sizeParamLen] = '\0'; argumentDataSize = atol(tempSizeStr); } else { assert_dbg(); return 1; } idx = idxStart - argumentDataSize - 1; // beginning of data deserializedArg_t deserializedArg = { 0 }; deserializedArg.argument = argumentName; deserializedArg.size = argumentDataSize; deserializedArg.data = copyDataPtr + idx; deserializeSysArg(&deserializedArg); if (cbDeserializeArg) cbDeserializeArg(&deserializedArg, customParam); idx--; } else if (copyDataPtr[idx] == ']') { assert_dbg(); } else return 1; } return 0; } void _deserializeSysArgsEx(void(*cbDeserializeArg)(deserializedArg_t* deserializedArg, void* customParam), void* customParam) { sint32 copyDataSize = coreinit::__OSGetCopyDataSize(); uint8* copyDataPtr = coreinit::__OSGetCopyDataPtr(); _deserializeSysArgsEx2(copyDataPtr, copyDataSize, cbDeserializeArg, customParam); } void SYSDeserializeSysArgs(void(*cbDeserializeArg)(deserializedArg_t* deserializedArg, void* customParam), sysArgSlot_t* argSlots) { _deserializeSysArgsEx(cbDeserializeArg, argSlots); } sint32 _deserializeArgs(void* customParam, sint32 customParamSize, void(*cbDeserializeArg)(deserializedArg_t* deserializedArg, void* customParam)) { memset(customParam, 0, customParamSize); clearSysArgs(); _deserializeSysArgsEx(cbDeserializeArg, customParam); return 0; } void _unpackSysWorkaround() { // unpack sys clearSysArgs(); _deserializeSysArgsEx(NULL, NULL); if (_sysArg_packSize != 0) { coreinit::__OSResizeCopyData(_sysArg_packSize); } } void cbDeserializeArg_MiiMaker(deserializedArg_t* deserializedArg, void* customParam) { sysMiiStudioArguments2_t* miiStudioArgs = (sysMiiStudioArguments2_t*)customParam; if (strcmp(deserializedArg->argument, "mode") == 0) { miiStudioArgs->mode = *(uint32be*)deserializedArg->data; } else if (strcmp(deserializedArg->argument, "mii") == 0) { assert_dbg(); } else if (strcmp(deserializedArg->argument, "slot_id") == 0) { #ifdef CEMU_DEBUG_ASSERT assert_dbg(); #endif } else { #ifdef CEMU_DEBUG_ASSERT assert_dbg(); #endif } } sint32 _SYSGetMiiStudioArgs(sysMiiStudioArguments2_t* miiStudioArgs) { _unpackSysWorkaround(); _deserializeArgs(miiStudioArgs, sizeof(sysMiiStudioArguments2_t), cbDeserializeArg_MiiMaker); return 0; } void sysappExport__SYSGetMiiStudioArgs(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(miiStudioArgs, sysMiiStudioArguments2_t, 0); sint32 r = _SYSGetMiiStudioArgs(miiStudioArgs); osLib_returnFromFunction(hCPU, r); } void cbDeserializeArg_SysSettings(deserializedArg_t* deserializedArg, void* customParam) { sysSettingsArguments_t* settingsArgs = (sysSettingsArguments_t*)customParam; if (boost::iequals(deserializedArg->argument, "jump_to")) { cemu_assert_unimplemented(); } else if (boost::iequals(deserializedArg->argument, "first_boot_kind")) { cemu_assert_unimplemented(); } else if (boost::iequals(deserializedArg->argument, "needs_return")) { settingsArgs->needsReturn = 1; } else cemu_assert_unimplemented(); } sint32 _SYSGetSettingsArgs(sysSettingsArguments_t* settingsArgs) { _unpackSysWorkaround(); memset(settingsArgs, 0, sizeof(sysSettingsArguments_t)); return _deserializeArgs(settingsArgs, sizeof(sysSettingsArguments_t), cbDeserializeArg_SysSettings); } void sysappExport__SYSGetSettingsArgs(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(settingsArgs, sysSettingsArguments_t, 0); sint32 r = _SYSGetSettingsArgs(settingsArgs); if (settingsArgs->jumpTo == 0) // workaround when launching sys settings directly settingsArgs->jumpTo = 0x00; osLib_returnFromFunction(hCPU, r); } struct { uint64 t0; uint64 t1; uint64 t2; }systemApplicationTitleId[] = { { 0x5001010040000, 0x5001010040100, 0x5001010040200 }, // Wii U Menu { 0x5001010047000, 0x5001010047100, 0x5001010047200 }, // System Settings { 0x5001010048000, 0x5001010048100, 0x5001010048200 }, // Parental Controls { 0x5001010049000, 0x5001010049100, 0x5001010049200 }, // User Settings { 0x500101004A000, 0x500101004A100, 0x500101004A200 }, // Mii Maker { 0x500101004B000, 0x500101004B100, 0x500101004B200 }, // Account Settings { 0x500101004C000, 0x500101004C100, 0x500101004C200 }, // Daily Log { 0x500101004D000, 0x500101004D100, 0x500101004D200 }, // Notifications { 0x500101004E000, 0x500101004E100, 0x500101004E200 }, // Health and Safety Information { 0x5001B10059000, 0x5001B10059100, 0x5001B10059200 }, // Wii U Electronic Manual { 0x500101005A000, 0x500101005A100, 0x500101005A200 }, // Wii U Chat { 0x5001010062000, 0x5001010062100, 0x5001010062200 } // Software/Data Transfer }; uint64 _SYSGetSystemApplicationTitleIdByProdArea(uint32 systemApplicationId, uint32 platformRegion) { if (systemApplicationId >= (sizeof(systemApplicationTitleId) / sizeof(systemApplicationTitleId[0])) ) assert_dbg(); if (platformRegion == 1) { return systemApplicationTitleId[systemApplicationId].t0; } else if (platformRegion == 4 || platformRegion == 8) { return systemApplicationTitleId[systemApplicationId].t2; } return systemApplicationTitleId[systemApplicationId].t1; } uint64 _SYSGetSystemApplicationTitleId(sint32 index) { uint32 region = (uint32)CafeSystem::GetPlatformRegion(); return _SYSGetSystemApplicationTitleIdByProdArea(index, region); } void sysappExport__SYSGetSystemApplicationTitleId(PPCInterpreter_t* hCPU) { ppcDefineParamU32(systemApplicationId, 0); cemuLog_logDebug(LogType::Force, "_SYSGetSystemApplicationTitleId(0x{:x})", hCPU->gpr[3]); uint64 titleId = _SYSGetSystemApplicationTitleId(systemApplicationId); osLib_returnFromFunction64(hCPU, titleId); } void __LaunchMiiMaker(sysMiiStudioArguments_t* args, uint32 platformRegion) { coreinit::__OSClearCopyData(); if (args) { _SYSSerializeStandardArgsIn(&args->standardArguments); SYSSerializeSysArgs("mode", (char*)&args->mode, 4); SYSSerializeSysArgs("slot_id", (char*)&args->slot_id, 4); if(args->mii) SYSSerializeSysArgs("mii", (char*)args->mii.GetPtr(), 0x60); } _SYSAppendCallerInfo(); uint64 titleIdToLaunch = _SYSGetSystemApplicationTitleIdByProdArea(4, platformRegion); cemu_assert_unimplemented(); } void _SYSLaunchMiiStudio(sysMiiStudioArguments_t* args) { __LaunchMiiMaker(args, (uint32)CafeSystem::GetPlatformRegion()); } void sysappExport__SYSLaunchMiiStudio(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(args, sysMiiStudioArguments_t, 0); _SYSLaunchMiiStudio(args); } void sysappExport__SYSReturnToCallerWithStandardResult(PPCInterpreter_t* hCPU) { ppcDefineParamU32BEPtr(resultPtr, 0); cemuLog_log(LogType::Force, "_SYSReturnToCallerWithStandardResult(0x{:08x}) result: 0x{:08x}", hCPU->gpr[3], (uint32)*resultPtr); while (true) std::this_thread::sleep_for(std::chrono::milliseconds(10)); } void sysappExport__SYSGetEShopArgs(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(args, eshopArguments_t, 0); cemuLog_logDebug(LogType::Force, "_SYSGetEShopArgs() - placeholder"); memset(args, 0, sizeof(eshopArguments_t)); osLib_returnFromFunction(hCPU, 0); } void sysappExport_SYSGetUPIDFromTitleID(PPCInterpreter_t* hCPU) { ppcDefineParamU64(titleId, 0); uint32 titleIdHigh = (titleId >> 32); uint32 titleIdLow = (uint32)(titleId & 0xFFFFFFFF); if ((titleIdHigh & 0xFFFF0000) != 0x50000) { osLib_returnFromFunction(hCPU, -1); return; } if ((titleIdLow & 0xF0000000) != 0x10000000) { osLib_returnFromFunction(hCPU, -1); return; } uint32 titleId_type = (titleIdHigh & 0xFF); if (titleId_type == 0x30) { // applet uint32 ukn = ((titleIdLow >> 12) & 0xFFFF); if (ukn < 0x10) { osLib_returnFromFunction(hCPU, -1); return; } else if (ukn <= 0x19) { uint8 appletTable10[10] = { 5,6,8,3,0xA,0xB,9,4,0xC,7 }; osLib_returnFromFunction(hCPU, appletTable10[ukn - 0x10]); return; } else if (ukn <= 0x20) { assert_dbg(); osLib_returnFromFunction(hCPU, -1); return; } else if (ukn <= 0x29) { uint8 appletTable20[10] = {5,6,8,3,0xA,0xB,9,4,0xC,7}; osLib_returnFromFunction(hCPU, appletTable20[ukn-0x20]); return; } assert_dbg(); osLib_returnFromFunction(hCPU, -1); return; } else if (titleId_type == 0x10) { // system application if (((titleIdLow >> 12) & 0xFFFF) == 0x40) { // Wii U menu osLib_returnFromFunction(hCPU, 2); return; } osLib_returnFromFunction(hCPU, 0xF); return; } else if (titleId_type == 0x00 || titleId_type == 0x02) { osLib_returnFromFunction(hCPU, 0xF); return; } osLib_returnFromFunction(hCPU, -1); } void sysappExport_SYSGetVodArgs(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "SYSGetVodArgs() - todo"); osLib_returnFromFunction(hCPU, 1); } struct SysLauncherArgs18 { uint64be caller_id; // titleId uint64be launch_title; // titleId uint32be mode; uint32be slot_id; }; static_assert(sizeof(SysLauncherArgs18) == 0x18); struct SysLauncherArgs28 { uint32 ukn00; uint32 ukn04; uint32 ukn08; uint32 ukn0C; // standard args above uint64be caller_id; // titleId uint64be launch_title; // titleId uint32be mode; uint32be slot_id; }; static_assert(sizeof(SysLauncherArgs28) == 0x28); uint32 _SYSGetLauncherArgs(void* argsOut) { uint32 sdkVersion = coreinit::__OSGetProcessSDKVersion(); if(sdkVersion < 21103) { // old format SysLauncherArgs18* launcherArgs18 = (SysLauncherArgs18*)argsOut; memset(launcherArgs18, 0, sizeof(SysLauncherArgs18)); } else { // new format SysLauncherArgs28* launcherArgs28 = (SysLauncherArgs28*)argsOut; memset(launcherArgs28, 0, sizeof(SysLauncherArgs28)); } return 0; // return argument is todo } struct SysAccountArgs18 { uint32be ukn00; uint32be ukn04; uint32be ukn08; uint32be ukn0C; // shares part above with Standard arg uint32be slotId; // "slot_id" uint32be mode; // "mode" }; uint32 _SYSGetAccountArgs(SysAccountArgs18* argsOut) { memset(argsOut, 0, sizeof(SysAccountArgs18)); // sysDeserializeStandardArguments_t ? return 0; } void sysappExport_SYSGetStandardResult(PPCInterpreter_t* hCPU) { cemuLog_logDebug(LogType::Force, "SYSGetStandardResult(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); memset(memory_getPointerFromVirtualOffset(hCPU->gpr[3]), 0, 4); // r3 = uint32be* output // r4 = pointer to data to parse? // r5 = size to parse? osLib_returnFromFunction(hCPU, 0); } namespace sysapp { void SYSClearSysArgs() { cemuLog_logDebug(LogType::Force, "SYSClearSysArgs()"); coreinit::__OSClearCopyData(); } uint32 _SYSLaunchTitleByPathFromLauncher(const char* path, uint32 pathLength) { coreinit::__OSClearCopyData(); _SYSAppendCallerInfo(); return coreinit::OSLaunchTitleByPathl(path, pathLength, 0); } uint32 SYSRelaunchTitle(uint32 argc, MEMPTR<char>* argv) { // calls ACPCheckSelfTitleNotReferAccountLaunch? coreinit::__OSClearCopyData(); _SYSAppendCallerInfo(); return coreinit::OSRestartGame(argc, argv); } struct EManualArgs { sysStandardArguments_t stdArgs; uint64be titleId; }; static_assert(sizeof(EManualArgs) == 0x10); void _SYSSwitchToEManual(EManualArgs* args) { // the struct has the titleId at offset 8 and standard args at 0 (total size is most likely 0x10) cemuLog_log(LogType::Force, "SYSSwitchToEManual called. Opening the manual is not supported"); coreinit::StartBackgroundForegroundTransition(); } void SYSSwitchToEManual() { EManualArgs args{}; args.titleId = coreinit::OSGetTitleID(); _SYSSwitchToEManual(&args); } void load() { cafeExportRegisterFunc(SYSClearSysArgs, "sysapp", "SYSClearSysArgs", LogType::Placeholder); cafeExportRegisterFunc(_SYSLaunchTitleByPathFromLauncher, "sysapp", "_SYSLaunchTitleByPathFromLauncher", LogType::Placeholder); cafeExportRegisterFunc(SYSRelaunchTitle, "sysapp", "SYSRelaunchTitle", LogType::Placeholder); cafeExportRegister("sysapp", _SYSSwitchToEManual, LogType::Placeholder); cafeExportRegister("sysapp", SYSSwitchToEManual, LogType::Placeholder); } } namespace sysapp { class : public COSModule { public: std::string_view GetName() override { return "sysapp"; } void RPLMapped() override { osLib_addFunction("sysapp", "_SYSLaunchMiiStudio", sysappExport__SYSLaunchMiiStudio); osLib_addFunction("sysapp", "_SYSGetMiiStudioArgs", sysappExport__SYSGetMiiStudioArgs); osLib_addFunction("sysapp", "_SYSGetSettingsArgs", sysappExport__SYSGetSettingsArgs); osLib_addFunction("sysapp", "_SYSReturnToCallerWithStandardResult", sysappExport__SYSReturnToCallerWithStandardResult); osLib_addFunction("sysapp", "_SYSGetSystemApplicationTitleId", sysappExport__SYSGetSystemApplicationTitleId); osLib_addFunction("sysapp", "SYSGetUPIDFromTitleID", sysappExport_SYSGetUPIDFromTitleID); osLib_addFunction("sysapp", "_SYSGetEShopArgs", sysappExport__SYSGetEShopArgs); osLib_addFunction("sysapp", "SYSGetVodArgs", sysappExport_SYSGetVodArgs); osLib_addFunction("sysapp", "SYSGetStandardResult", sysappExport_SYSGetStandardResult); cafeExportRegisterFunc(_SYSGetLauncherArgs, "sysapp", "_SYSGetLauncherArgs", LogType::Placeholder); cafeExportRegisterFunc(_SYSGetAccountArgs, "sysapp", "_SYSGetAccountArgs", LogType::Placeholder); sysapp::load(); }; }s_COSsysappModule; COSModule* GetModule() { return &s_COSsysappModule; } } ================================================ FILE: src/Cafe/OS/libs/sysapp/sysapp.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace sysapp { COSModule* GetModule(); } uint64 _SYSGetSystemApplicationTitleId(sint32 index); ================================================ FILE: src/Cafe/OS/libs/vpad/vpad.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/vpad/vpad.h" #include "audio/IAudioAPI.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "config/ActiveSettings.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "input/InputManager.h" #include "WindowSystem.h" #ifdef PUBLIC_RELASE #define vpadbreak() #else #define vpadbreak()// __debugbreak(); #endif #define VPAD_READ_ERR_NONE 0 #define VPAD_READ_ERR_NO_DATA -1 #define VPAD_READ_ERR_NO_CONTROLLER -2 #define VPAD_READ_ERR_SETUP -3 #define VPAD_READ_ERR_LOCKED -4 #define VPAD_READ_ERR_INIT -5 #define VPAD_TP_VALIDITY_VALID 0 #define VPAD_TP_VALIDITY_INVALID_X 1 #define VPAD_TP_VALIDITY_INVALID_Y 2 #define VPAD_TP_VALIDITY_INVALID_XY (VPAD_TP_VALIDITY_INVALID_X | VPAD_TP_VALIDITY_INVALID_Y) #define VPAD_GYRO_ZERODRIFT_LOOSE 0 #define VPAD_GYRO_ZERODRIFT_STANDARD 1 #define VPAD_GYRO_ZERODRIFT_TIGHT 2 #define VPAD_GYRO_ZERODRIFT_NONE 3 #define VPAD_PLAY_MODE_LOOSE 0 #define VPAD_PLAY_MODE_TIGHT 1 #define VPAD_BUTTON_PROC_MODE_LOOSE 0 #define VPAD_BUTTON_PROC_MODE_TIGHT 1 #define VPAD_LCD_MODE_MUTE 0 #define VPAD_LCD_MODE_GAME_CONTROLLER 1 #define VPAD_LCD_MODE_ON 0xFF #define VPAD_MOTOR_PATTERN_SIZE_MAX 15 #define VPAD_MOTOR_PATTERN_LENGTH_MAX 120 #define VPAD_TP_1920x1080 0 #define VPAD_TP_1280x720 1 #define VPAD_TP_854x480 2 extern bool isLaunchTypeELF; VPADDir g_vpadGyroDirOverwrite[VPAD_MAX_CONTROLLERS] = { {{1.0f,0.0f,0.0f}, {0.0f,1.0f,0.0f}, {0.0f, 0.0f, 0.1f}}, {{1.0f,0.0f,0.0f}, {0.0f,1.0f,0.0f}, {0.0f, 0.0f, 0.1f}} }; uint32 g_vpadGyroZeroDriftMode[VPAD_MAX_CONTROLLERS] = { VPAD_GYRO_ZERODRIFT_STANDARD, VPAD_GYRO_ZERODRIFT_STANDARD }; struct VPACGyroDirRevise_t { bool enabled; VPADDir vpadGyroDirReviseBase; float weight; } g_vpadGyroDirRevise[VPAD_MAX_CONTROLLERS] = {}; struct VPADAccParam_t { // TODO P: use float playRadius; float sensitivity; } g_vpadAccParam[VPAD_MAX_CONTROLLERS] = { {0, 1}, {0, 1} }; uint32 g_vpadPlayMode[VPAD_MAX_CONTROLLERS] = { VPAD_PLAY_MODE_TIGHT, VPAD_PLAY_MODE_TIGHT }; // TODO P: use #define VPAD_MIN_CLAMP (0x102) #define VPAD_MAX_CLAMP (0x397) struct VPADStickClamp { // TODO P: use bool crossMode; // default is circular mode sint32 leftMax; sint32 leftMin; sint32 rightMax; sint32 rightMin; } vpadStickClamp[VPAD_MAX_CONTROLLERS] = { {false, VPAD_MAX_CLAMP, VPAD_MIN_CLAMP}, {false, VPAD_MAX_CLAMP, VPAD_MIN_CLAMP} }; struct VPADCrossStickEmulationParams { // TODO P: use float leftRotation; float leftInputRange; float leftRadius; float rightRotation; float rightInputRange; float rightRadius; } vpadCrossStickEmulationParams[VPAD_MAX_CONTROLLERS] = {}; uint8 vpadButtonProcMode[VPAD_MAX_CONTROLLERS] = { VPAD_BUTTON_PROC_MODE_TIGHT, VPAD_BUTTON_PROC_MODE_TIGHT }; // TODO P: use uint32 vpadLcdMode[VPAD_MAX_CONTROLLERS] = { VPAD_LCD_MODE_ON, VPAD_LCD_MODE_ON }; struct VPADTPCalibrationParam { // TODO P: use uint16be offsetX; uint16be offsetY; float32be scaleX; float32be scaleY; } vpadTPCalibrationParam[VPAD_MAX_CONTROLLERS] = { {92, 254, (1280.0f / 3883.0f), (720.0f / 3694.0f)}, {92, 254, (1280.0f / 3883.0f), (720.0f / 3694.0f)} }; void _tpRawToResolution(sint32 x, sint32 y, sint32* outX, sint32* outY, sint32 width, sint32 height) { x -= 92; y = 4095.0 - y - 254; x = std::max(x, 0); y = std::max(y, 0); *outX = (sint32)(((double)x / 3883.0) * (double)width); *outY = (sint32)(((double)y / 3694.0) * (double)height); } namespace vpad { enum class PlayMode : sint32 { Loose = 0, Tight = 1 }; enum class LcdMode { Off = 0, ControllerOnly = 1, On = 0xFF, }; struct VPADTPCalibrationParam { sint16be x, y; float32be scale_x, scale_y; }; struct VPADTPData { uint16be x, y, touch, valid; }; enum class VPADTPResolution { _1920x1080 = 0, _1280x720 = 1, _854_480 = 2, }; enum class ButtonProcMode : uint8 { Loose = 0, Tight = 1, }; struct { SysAllocator<coreinit::OSAlarm_t> alarm; struct { uint64 drcLastCallTime = 0; struct AccParam { float radius, sensitivity; } acc_param; BtnRepeat btn_repeat; MEMPTR<void> sampling_callback; PlayMode acc_play_mode; VPADTPCalibrationParam tp_calibration_param{}; uint16 tp_size = 0xc; struct { float rotation, range, radius; }cross_stick_emulation_l{}, cross_stick_emulation_r{}; ButtonProcMode button_proc_mode; struct { bool enabled = false; struct { sint32 min = 0x102, max = 0x397; } left{}, right{}; }stick_cross_clamp{}; }controller_data[VPAD_MAX_CONTROLLERS]{}; } g_vpad; void VPADSetAccParam(sint32 channel, float radius, float sensitivity) { cemuLog_log(LogType::InputAPI, "VPADSetAccParam({}, {}, {})", channel, radius, sensitivity); vpadbreak(); g_vpad.controller_data[channel].acc_param.radius = radius; g_vpad.controller_data[channel].acc_param.sensitivity = sensitivity; } void VPADGetAccParam(sint32 channel, float* radius, float* sensitivity) { cemuLog_log(LogType::InputAPI, "VPADGetAccParam({}, {}, {})", channel, (void*)radius, (void*)sensitivity); vpadbreak(); *radius = g_vpad.controller_data[channel].acc_param.radius; *sensitivity = g_vpad.controller_data[channel].acc_param.sensitivity; } sint32 VPADRead(sint32 channel, VPADStatus* status, uint32 length, sint32be* error) { //printf("VPADRead(%d,0x%08X,%d,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); /*ppcDefineParamU32(channel, 0); ppcDefineParamStructPtr(status, VPADStatus_t, 1); ppcDefineParamU32(length, 2); ppcDefineParamPtr(error, uint32be, 3); cemuLog_log(LogType::InputAPI, "VPADRead({}, _, {})", channel, length);*/ // default init which should be always set memset(status, 0x00, sizeof(VPADStatus_t)); // default misc status->batteryLevel = 0xC0; // full battery status->slideVolume = status->slideVolume2 = (uint8)((g_padVolume * 0xFF) / 100); // default touch status->tpData.validity = VPAD_TP_VALIDITY_INVALID_XY; status->tpProcessed1.validity = VPAD_TP_VALIDITY_INVALID_XY; status->tpProcessed2.validity = VPAD_TP_VALIDITY_INVALID_XY; const auto controller = InputManager::instance().get_vpad_controller(channel); if (!controller) { // most games expect the Wii U GamePad to be connected, so even if the user has not set it up we should still return empty samples for channel 0 if(channel != 0) { if (error) *error = VPAD_READ_ERR_NO_CONTROLLER; if (length > 0) status->vpadErr = -1; return 0; } if (error) *error = VPAD_READ_ERR_NONE; return 1; } const bool vpadDelayEnabled = ActiveSettings::VPADDelayEnabled(); if (isLaunchTypeELF) { // hacky workaround for homebrew games calling VPADRead in an infinite loop PPCCore_switchToScheduler(); } if (!WindowSystem::InputConfigWindowHasFocus()) { if (channel <= 1 && vpadDelayEnabled) { uint64 currentTime = coreinit::OSGetTime(); const auto dif = currentTime - vpad::g_vpad.controller_data[channel].drcLastCallTime; if (dif <= (ESPRESSO_TIMER_CLOCK / 60ull)) { // not ready yet if (error) *error = VPAD_READ_ERR_NONE; return 0; } else if (dif <= ESPRESSO_TIMER_CLOCK) { vpad::g_vpad.controller_data[channel].drcLastCallTime += (ESPRESSO_TIMER_CLOCK / 60ull); } else { vpad::g_vpad.controller_data[channel].drcLastCallTime = currentTime; } } controller->VPADRead(*status, vpad::g_vpad.controller_data[channel].btn_repeat); if (error) *error = VPAD_READ_ERR_NONE; return 1; } else { if (error) *error = VPAD_READ_ERR_NONE; return 1; } } void VPADSetBtnRepeat(sint32 channel, float delay, float pulse) { cemuLog_log(LogType::InputAPI, "VPADSetBtnRepeat({}, {}, {})", channel, delay, pulse); if(pulse == 0) { g_vpad.controller_data[channel].btn_repeat.delay = 40000; g_vpad.controller_data[channel].btn_repeat.pulse = 0; } else { g_vpad.controller_data[channel].btn_repeat.delay = (sint32)((delay * 200.0f) + 0.5f); g_vpad.controller_data[channel].btn_repeat.pulse = (sint32)((pulse * 200.0f) + 0.5f); } } void VPADSetAccPlayMode(sint32 channel, PlayMode play_mode) { cemuLog_log(LogType::InputAPI, "VPADSetAccPlayMode({}, {})", channel, (int)play_mode); vpadbreak(); g_vpad.controller_data[channel].acc_play_mode = play_mode; } PlayMode VPADGetAccPlayMode(sint32 channel) { cemuLog_log(LogType::InputAPI, "VPADGetAccPlayMode({})", channel); vpadbreak(); return g_vpad.controller_data[channel].acc_play_mode; } void* VPADSetSamplingCallback(sint32 channel, void* callback) { cemuLog_log(LogType::InputAPI, "VPADSetSamplingCallback({}, 0x{:x})", channel, MEMPTR(callback).GetMPTR()); vpadbreak(); void* result = g_vpad.controller_data[channel].sampling_callback; g_vpad.controller_data[channel].sampling_callback = callback; return result; } sint32 VPADCalcTPCalibrationParam(VPADTPCalibrationParam* p, uint16 raw_x1, uint16 raw_y1, uint16 x1, uint16 y1, uint16 raw_x2, uint16 raw_y2, uint16 x2, uint16 y2) { cemu_assert_unimplemented(); return 1; } void VPADGetTPCalibrationParam(sint32 channel, VPADTPCalibrationParam* param) { cemuLog_log(LogType::InputAPI, "VPADGetTPCalibrationParam({}, 0x{:x})", channel, MEMPTR(param).GetMPTR()); vpadbreak(); *param = g_vpad.controller_data[channel].tp_calibration_param; } void VPADSetTPCalibrationParam(sint32 channel, VPADTPCalibrationParam* param) { cemuLog_log(LogType::InputAPI, "VPADSetTPCalibrationParam({}, 0x{:x})", channel, MEMPTR(param).GetMPTR()); vpadbreak(); g_vpad.controller_data[channel].tp_calibration_param = *param; } void VPADGetTPCalibratedPoint(sint32 channel, VPADTPData* data, VPADTPData* raw) { cemuLog_log(LogType::InputAPI, "VPADGetTPCalibratedPoint({}, 0x{:x}, 0x{:x})", channel, MEMPTR(data).GetMPTR(), MEMPTR(raw).GetMPTR()); vpadbreak(); const auto& controller_data = g_vpad.controller_data[channel]; uint16 x = (uint16)((float)raw->x - ((float)controller_data.tp_calibration_param.x * controller_data.tp_calibration_param.scale_x)); uint16 y = (uint16)((float)raw->x - ((float)controller_data.tp_calibration_param.y * controller_data.tp_calibration_param.scale_y)); const int tp_size = (int)controller_data.tp_size; int tmpx = x; if(x <= (int)controller_data.tp_size) tmpx = (int)controller_data.tp_size; int tmpy = y; if(y <= (int)controller_data.tp_size) tmpy = (int)controller_data.tp_size; if((0x500 - tp_size) <= tmpx) x = (0x500 - tp_size); if((0x2d0 - tp_size) <= tmpy) y = (0x2d0 - tp_size); data->x = x; data->y = y; data->touch = raw->touch; data->valid = raw->valid; } void VPADGetTPCalibratedPointEx(sint32 channel, VPADTPResolution resolution, VPADTPData* data, VPADTPData* raw) { cemuLog_log(LogType::InputAPI, "VPADGetTPCalibratedPointEx({}, {}, 0x{:x}, 0x{:x})", channel, (int)resolution, MEMPTR(data).GetMPTR(), MEMPTR(raw).GetMPTR()); vpadbreak(); } void VPADSetCrossStickEmulationParamsL(sint32 channel, float rotation, float range, float radius) { cemuLog_log(LogType::InputAPI, "VPADSetCrossStickEmulationParamsL({}, {}, {}, {})", channel, rotation, range, radius); vpadbreak(); if (range < 0 || 90.0f < range) return; if (radius < 0 || 1.0f < radius) return; g_vpad.controller_data[channel].cross_stick_emulation_l.rotation = rotation; g_vpad.controller_data[channel].cross_stick_emulation_l.range = range; g_vpad.controller_data[channel].cross_stick_emulation_l.radius = radius; } void VPADSetCrossStickEmulationParamsR(sint32 channel, float rotation, float range, float radius) { cemuLog_log(LogType::InputAPI, "VPADSetCrossStickEmulationParamsR({}, {}, {}, {})", channel, rotation, range, radius); vpadbreak(); if (range < 0 || 90.0f < range) return; if (radius < 0 || 1.0f < radius) return; g_vpad.controller_data[channel].cross_stick_emulation_r.rotation = rotation; g_vpad.controller_data[channel].cross_stick_emulation_r.range = range; g_vpad.controller_data[channel].cross_stick_emulation_r.radius = radius; } void VPADGetCrossStickEmulationParamsL(sint32 channel, float* rotation, float* range, float* radius) { cemuLog_log(LogType::InputAPI, "VPADGetCrossStickEmulationParamsL({}, 0x{:x}, 0x{:x}, 0x{:x})", channel, MEMPTR(rotation).GetMPTR(), MEMPTR(range).GetMPTR(), MEMPTR(radius).GetMPTR()); vpadbreak(); *rotation = g_vpad.controller_data[channel].cross_stick_emulation_l.rotation; *range = g_vpad.controller_data[channel].cross_stick_emulation_l.range; *radius = g_vpad.controller_data[channel].cross_stick_emulation_l.radius; } void VPADGetCrossStickEmulationParamsR(sint32 channel, float* rotation, float* range, float* radius) { cemuLog_log(LogType::InputAPI, "VPADGetCrossStickEmulationParamsR({}, 0x{:x}, 0x{:x}, 0x{:x})", channel, MEMPTR(rotation).GetMPTR(), MEMPTR(range).GetMPTR(), MEMPTR(radius).GetMPTR()); vpadbreak(); *rotation = g_vpad.controller_data[channel].cross_stick_emulation_r.rotation; *range = g_vpad.controller_data[channel].cross_stick_emulation_r.range; *radius = g_vpad.controller_data[channel].cross_stick_emulation_r.radius; } ButtonProcMode VPADGetButtonProcMode(sint32 channel) { cemuLog_log(LogType::InputAPI, "VPADGetButtonProcMode({})", channel); vpadbreak(); return g_vpad.controller_data[channel].button_proc_mode; } void VPADSetButtonProcMode(sint32 channel, ButtonProcMode mode) { cemuLog_log(LogType::InputAPI, "VPADSetButtonProcMode({}, {})", channel, (int)mode); vpadbreak(); g_vpad.controller_data[channel].button_proc_mode = mode; } void VPADEnableStickCrossClamp(sint32 channel) { cemuLog_log(LogType::InputAPI, "VPADEnableStickCrossClamp({})", channel); vpadbreak(); g_vpad.controller_data[channel].stick_cross_clamp.enabled = true; } void VPADDisableStickCrossClamp(sint32 channel) { cemuLog_log(LogType::InputAPI, "VPADDisableStickCrossClamp({})", channel); vpadbreak(); g_vpad.controller_data[channel].stick_cross_clamp.enabled = false; } void VPADSetLStickClampThreshold(sint32 channel, sint32 max, sint32 min) { cemuLog_log(LogType::InputAPI, "VPADSetLStickClampThreshold({}, {}, {})", channel, max, min); vpadbreak(); g_vpad.controller_data[channel].stick_cross_clamp.left.max = std::min(0x397, max); g_vpad.controller_data[channel].stick_cross_clamp.left.min = std::max(0x102, min); } void VPADSetRStickClampThreshold(sint32 channel, sint32 max, sint32 min) { cemuLog_log(LogType::InputAPI, "VPADSetRStickClampThreshold({}, {}, {})", channel, max, min); vpadbreak(); g_vpad.controller_data[channel].stick_cross_clamp.right.max = std::min(0x397, max); g_vpad.controller_data[channel].stick_cross_clamp.right.min = std::max(0x102, min); } void VPADGetLStickClampThreshold(sint32 channel, sint32* max, sint32* min) { cemuLog_log(LogType::InputAPI, "VPADGetLStickClampThreshold({}, 0x{:x}, 0x{:x})", channel, MEMPTR(max).GetMPTR(), MEMPTR(min).GetMPTR()); vpadbreak(); *max = g_vpad.controller_data[channel].stick_cross_clamp.left.max; *min = g_vpad.controller_data[channel].stick_cross_clamp.left.min; } void VPADGetRStickClampThreshold(sint32 channel, sint32* max, sint32* min) { cemuLog_log(LogType::InputAPI, "VPADGetRStickClampThreshold({}, 0x{:x}, 0x{:x})", channel, MEMPTR(max).GetMPTR(), MEMPTR(min).GetMPTR()); vpadbreak(); *max = g_vpad.controller_data[channel].stick_cross_clamp.right.max; *min = g_vpad.controller_data[channel].stick_cross_clamp.right.min; } } void vpadExport_VPADGetAccParam(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(playRadius, float32be, 1); ppcDefineParamPtr(sensitivity, float32be, 2); cemuLog_log(LogType::InputAPI, "VPADGetAccParam({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { *playRadius = g_vpadAccParam[channel].playRadius; *sensitivity = g_vpadAccParam[channel].sensitivity; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetAccParam(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); cemuLog_log(LogType::InputAPI, "VPADSetAccParam({}, {}, {})", channel, hCPU->fpr[1].fpr, hCPU->fpr[2].fpr); if (channel < VPAD_MAX_CONTROLLERS) { g_vpadAccParam[channel].playRadius = hCPU->fpr[1].fpr; g_vpadAccParam[channel].sensitivity = hCPU->fpr[2].fpr; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetAccPlayMode(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); cemuLog_log(LogType::InputAPI, "VPADGetAccPlayMode({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { osLib_returnFromFunction(hCPU, g_vpadPlayMode[channel]); } else { debugBreakpoint(); osLib_returnFromFunction(hCPU, VPAD_PLAY_MODE_TIGHT); } } void vpadExport_VPADSetAccPlayMode(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamU32(playMode, 1); cemuLog_log(LogType::InputAPI, "VPADSetAccPlayMode({}, {})", channel, playMode); if (channel < VPAD_MAX_CONTROLLERS) { g_vpadPlayMode[channel] = playMode; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADEnableStickCrossClamp(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); cemuLog_log(LogType::InputAPI, "VPADEnableStickCrossClamp({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { vpadStickClamp[channel].crossMode = true; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADDisableStickCrossClamp(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); cemuLog_log(LogType::InputAPI, "VPADDisableStickCrossClamp({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { vpadStickClamp[channel].crossMode = false; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetLStickClampThreshold(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamS32(maxValue, 1); ppcDefineParamS32(minValue, 2); cemuLog_log(LogType::InputAPI, "VPADSetLStickClampThreshold({}, {}, {})", channel, maxValue, minValue); if (channel < VPAD_MAX_CONTROLLERS) { vpadStickClamp[channel].leftMax = std::min(VPAD_MAX_CLAMP, maxValue); vpadStickClamp[channel].leftMin = std::max(VPAD_MIN_CLAMP, minValue); } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetRStickClampThreshold(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamS32(maxValue, 1); ppcDefineParamS32(minValue, 2); cemuLog_log(LogType::InputAPI, "VPADSetRStickClampThreshold({}, {}, {})", channel, maxValue, minValue); if (channel < VPAD_MAX_CONTROLLERS) { vpadStickClamp[channel].rightMax = std::min(VPAD_MAX_CLAMP, maxValue); vpadStickClamp[channel].rightMin = std::max(VPAD_MIN_CLAMP, minValue); } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetLStickClampThreshold(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(maxValue, uint32be, 1); ppcDefineParamPtr(minValue, uint32be, 2); cemuLog_log(LogType::InputAPI, "VPADGetLStickClampThreshold({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { *maxValue = vpadStickClamp[channel].leftMax; *minValue = vpadStickClamp[channel].leftMin; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetRStickClampThreshold(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(maxValue, uint32be, 1); ppcDefineParamPtr(minValue, uint32be, 2); cemuLog_log(LogType::InputAPI, "VPADGetRStickClampThreshold({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { *maxValue = vpadStickClamp[channel].rightMax; *minValue = vpadStickClamp[channel].rightMin; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetCrossStickEmulationParamsL(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); cemuLog_log(LogType::InputAPI, "VPADSetCrossStickEmulationParamsL({}, {}, {}, {})", channel, hCPU->fpr[1].fpr, hCPU->fpr[2].fpr, hCPU->fpr[3].fpr); if (channel < VPAD_MAX_CONTROLLERS) { vpadCrossStickEmulationParams[channel].leftRotation = hCPU->fpr[1].fpr; vpadCrossStickEmulationParams[channel].leftInputRange = hCPU->fpr[2].fpr; vpadCrossStickEmulationParams[channel].leftRadius = hCPU->fpr[3].fpr; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetCrossStickEmulationParamsR(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); cemuLog_log(LogType::InputAPI, "VPADSetCrossStickEmulationParamsR({}, {}, {}, {})", channel, hCPU->fpr[1].fpr, hCPU->fpr[2].fpr, hCPU->fpr[3].fpr); if (channel < VPAD_MAX_CONTROLLERS) { vpadCrossStickEmulationParams[channel].rightRotation = hCPU->fpr[1].fpr; vpadCrossStickEmulationParams[channel].rightInputRange = hCPU->fpr[2].fpr; vpadCrossStickEmulationParams[channel].rightRadius = hCPU->fpr[3].fpr; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetCrossStickEmulationParamsL(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(rotation, float32be, 1); ppcDefineParamPtr(inputRange, float32be, 2); ppcDefineParamPtr(radius, float32be, 3); cemuLog_log(LogType::InputAPI, "VPADGetCrossStickEmulationParamsL({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { *rotation = vpadCrossStickEmulationParams[channel].leftRotation; *inputRange = vpadCrossStickEmulationParams[channel].leftInputRange; *radius = vpadCrossStickEmulationParams[channel].leftRadius; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetCrossStickEmulationParamsR(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(rotation, float32be, 1); ppcDefineParamPtr(inputRange, float32be, 2); ppcDefineParamPtr(radius, float32be, 3); cemuLog_log(LogType::InputAPI, "VPADGetCrossStickEmulationParamsR({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { *rotation = vpadCrossStickEmulationParams[channel].rightRotation; *inputRange = vpadCrossStickEmulationParams[channel].rightInputRange; *radius = vpadCrossStickEmulationParams[channel].rightRadius; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetButtonProcMode(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); cemuLog_log(LogType::InputAPI, "VPADGetButtonProcMode({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { osLib_returnFromFunction(hCPU, vpadButtonProcMode[channel]); } else { debugBreakpoint(); osLib_returnFromFunction(hCPU, VPAD_BUTTON_PROC_MODE_TIGHT); } } void vpadExport_VPADSetButtonProcMode(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamU8(mode, 1); cemuLog_log(LogType::InputAPI, "VPADSetButtonProcMode({}, {})", channel, mode); if (channel < VPAD_MAX_CONTROLLERS) { vpadButtonProcMode[channel] = mode; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetLcdMode(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamU32(mode, 1); cemuLog_log(LogType::InputAPI, "VPADSetLcdMode({}, {})", channel, mode); if (channel < VPAD_MAX_CONTROLLERS) { vpadLcdMode[channel] = mode; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetLcdMode(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamPtr(mode, uint32be, 1); cemuLog_log(LogType::InputAPI, "VPADGetLcdMode({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { *mode = vpadLcdMode[channel]; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADControlMotor(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamUStr(pattern, 1); ppcDefineParamU8(length, 2); cemuLog_log(LogType::InputAPI, "VPADControlMotor({}, _, {})", channel, length); if (length > 120) { cemuLog_log(LogType::InputAPI, "VPADControlMotor() - length too high with {} of 120", length); length = 120; } if (const auto controller = InputManager::instance().get_vpad_controller(channel)) { // if length is zero -> stop vibration if (length == 0) { controller->clear_rumble(); } else { // check for max queue length if (!controller->push_rumble(pattern, length)) { osLib_returnFromFunction(hCPU, -1); // TODO P: not sure about the exact return value return; } } } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADStopMotor(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); cemuLog_log(LogType::InputAPI, "VPADStopMotor({})", channel); if (const auto controller = InputManager::instance().get_vpad_controller(channel)) { controller->clear_rumble(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetTPCalibrationParam(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamMEMPTR(params, VPADTPCalibrationParam, 1); debugBreakpoint(); if (channel < VPAD_MAX_CONTROLLERS) { VPADTPCalibrationParam* calibrationParam = params.GetPtr(); cemuLog_log(LogType::InputAPI, "VPADSetTPCalibrationParam({}, {}, {}, {}, {})", channel, (uint16)calibrationParam->offsetX, (uint16)calibrationParam->offsetX, (float)calibrationParam->scaleX, (float)calibrationParam->scaleY); vpadTPCalibrationParam[channel].offsetX = calibrationParam->offsetX; vpadTPCalibrationParam[channel].offsetX = calibrationParam->offsetY; vpadTPCalibrationParam[channel].scaleX = calibrationParam->scaleX; vpadTPCalibrationParam[channel].scaleY = calibrationParam->scaleY; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetTPCalibrationParam(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamStructPtr(calibrationParam, VPADTPCalibrationParam, 1); cemuLog_log(LogType::InputAPI, "VPADSetTPCalibrationParam({})", channel); calibrationParam->offsetX = vpadTPCalibrationParam[channel].offsetX; calibrationParam->offsetY = vpadTPCalibrationParam[channel].offsetY; calibrationParam->scaleX = vpadTPCalibrationParam[channel].scaleX; calibrationParam->scaleY = vpadTPCalibrationParam[channel].scaleY; osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetTPCalibratedPoint(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamStructPtr(outputDisplay, VPADTPData_t, 1); ppcDefineParamStructPtr(inputRaw, VPADTPData_t, 2); cemuLog_log(LogType::InputAPI, "VPADGetTPCalibratedPoint({})", channel); memmove(outputDisplay, inputRaw, sizeof(VPADTPData_t)); // vpadTPCalibrationParam[channel] sint16 x = outputDisplay->x; sint16 y = outputDisplay->y; sint32 outputX; sint32 outputY; _tpRawToResolution(x, y, &outputX, &outputY, 1280, 720); outputDisplay->x = outputX; outputDisplay->y = outputY; outputDisplay->touch = inputRaw->touch; outputDisplay->validity = inputRaw->validity; osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetTPCalibratedPointEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamS32(tpResolution, 1); ppcDefineParamStructPtr(outputDisplay, VPADTPData_t, 2); ppcDefineParamStructPtr(inputRaw, VPADTPData_t, 3); cemuLog_log(LogType::InputAPI, "VPADGetTPCalibratedPointEx({})", channel); //debug_printf("TPInput: %d %d %04x %04x\n", _swapEndianU16(inputRaw->touch), _swapEndianU16(inputRaw->validity), _swapEndianU16(inputRaw->x), _swapEndianU16(inputRaw->y)); memmove(outputDisplay, inputRaw, sizeof(VPADTPData_t)); //debug_printf("VPADGetTPCalibratedPointEx(): Resolution %d\n", hCPU->gpr[4]); sint16 x = outputDisplay->x; sint16 y = outputDisplay->y; sint32 outputX = 0; sint32 outputY = 0; if (tpResolution == VPAD_TP_1920x1080) { _tpRawToResolution(x, y, &outputX, &outputY, 1920, 1080); } else if (tpResolution == VPAD_TP_1280x720) { _tpRawToResolution(x, y, &outputX, &outputY, 1280, 720); } else if (tpResolution == VPAD_TP_854x480) { _tpRawToResolution(x, y, &outputX, &outputY, 854, 480); } else { debugBreakpoint(); debug_printf("VPADGetTPCalibratedPointEx(): Unsupported tp resolution\n"); } outputDisplay->x = outputX; outputDisplay->y = outputY; outputDisplay->touch = inputRaw->touch; outputDisplay->validity = inputRaw->validity; //debug_printf("VPADGetTPCalibratedPointEx %d %d\n", _swapEndianU16(outputDisplay->x), _swapEndianU16(outputDisplay->y)); osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetGyroDirection(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamStructPtr(dir, VPADDir, 1); cemuLog_log(LogType::InputAPI, "VPADSetGyroDirection({}, <<{:f}, {:f}, {:f}>, <{:f}, {:f}, {:f}>, <{:f}, {:f}, {:f}>>)", channel , (float)dir->x.x, (float)dir->x.y, (float)dir->x.z , (float)dir->y.x, (float)dir->y.y, (float)dir->y.z , (float)dir->z.x, (float)dir->z.y, (float)dir->z.z); if (channel < VPAD_MAX_CONTROLLERS) { g_vpadGyroDirOverwrite[channel] = *dir; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADGetGyroZeroDriftMode(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamMEMPTR(gyroMode, uint32be, 1); cemuLog_log(LogType::InputAPI, "VPADGetGyroZeroDriftMode({})", channel); if (channel < VPAD_MAX_CONTROLLERS) { *gyroMode = g_vpadGyroZeroDriftMode[channel]; } else { debugBreakpoint(); *gyroMode = VPAD_GYRO_ZERODRIFT_NONE; } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetGyroZeroDriftMode(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamU32(gyroMode, 1); cemuLog_log(LogType::InputAPI, "VPADSetGyroZeroDriftMode({}, {})", channel, gyroMode); if (channel < VPAD_MAX_CONTROLLERS) { if (gyroMode > VPAD_GYRO_ZERODRIFT_NONE) { debugBreakpoint(); } else { g_vpadGyroZeroDriftMode[channel] = gyroMode; } } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetGyroDirReviseBase(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); ppcDefineParamStructPtr(dir, VPADDir, 1); if (channel < VPAD_MAX_CONTROLLERS) { g_vpadGyroDirRevise[channel].vpadGyroDirReviseBase = *dir; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADDisableGyroDirRevise(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); if (channel < VPAD_MAX_CONTROLLERS) { g_vpadGyroDirRevise[channel].enabled = false; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } void vpadExport_VPADSetGyroDirReviseParam(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); if (channel < VPAD_MAX_CONTROLLERS) { g_vpadGyroDirRevise[channel].weight = (float)hCPU->fpr[1].fpr; } else { debugBreakpoint(); } osLib_returnFromFunction(hCPU, 0); } namespace vpad { void TickFunction(PPCInterpreter_t* hCPU) { // check if homebutton is pressed // check connection to drc const auto& instance = InputManager::instance(); for (auto i = 0; i < InputManager::kMaxVPADControllers; ++i) { if (!g_vpad.controller_data[i].sampling_callback) continue; if(const auto controller = instance.get_vpad_controller(i)) { cemuLog_log(LogType::InputAPI, "Calling VPADSamplingCallback({})", i); PPCCoreCallback(g_vpad.controller_data[i].sampling_callback, i); } } osLib_returnFromFunction(hCPU, 0); } void start() { coreinit::OSCreateAlarm(&g_vpad.alarm); const uint64 start_tick = coreinit::OSGetTime(); const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() * 5 / 1000; const MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction); coreinit::OSSetPeriodicAlarm(&g_vpad.alarm, start_tick, period_tick, handler); } void load() { } class : public COSModule { public: std::string_view GetName() override { return "vpad"; } void RPLMapped() override { cafeExportRegister("vpad", VPADSetBtnRepeat, LogType::InputAPI); cafeExportRegister("vpad", VPADSetSamplingCallback, LogType::InputAPI); cafeExportRegister("vpad", VPADRead, LogType::InputAPI); osLib_addFunction("vpad", "VPADGetAccParam", vpadExport_VPADGetAccParam); osLib_addFunction("vpad", "VPADSetAccParam", vpadExport_VPADSetAccParam); osLib_addFunction("vpad", "VPADGetAccPlayMode", vpadExport_VPADGetAccPlayMode); osLib_addFunction("vpad", "VPADSetAccPlayMode", vpadExport_VPADSetAccPlayMode); osLib_addFunction("vpad", "VPADEnableStickCrossClamp", vpadExport_VPADEnableStickCrossClamp); osLib_addFunction("vpad", "VPADDisableStickCrossClamp", vpadExport_VPADDisableStickCrossClamp); osLib_addFunction("vpad", "VPADSetLStickClampThreshold", vpadExport_VPADSetLStickClampThreshold); osLib_addFunction("vpad", "VPADSetRStickClampThreshold", vpadExport_VPADSetRStickClampThreshold); osLib_addFunction("vpad", "VPADGetLStickClampThreshold", vpadExport_VPADGetLStickClampThreshold); osLib_addFunction("vpad", "VPADGetRStickClampThreshold", vpadExport_VPADGetRStickClampThreshold); osLib_addFunction("vpad", "VPADSetCrossStickEmulationParamsL", vpadExport_VPADSetCrossStickEmulationParamsL); osLib_addFunction("vpad", "VPADSetCrossStickEmulationParamsR", vpadExport_VPADSetCrossStickEmulationParamsR); osLib_addFunction("vpad", "VPADGetCrossStickEmulationParamsL", vpadExport_VPADGetCrossStickEmulationParamsL); osLib_addFunction("vpad", "VPADGetCrossStickEmulationParamsR", vpadExport_VPADGetCrossStickEmulationParamsR); osLib_addFunction("vpad", "VPADGetButtonProcMode", vpadExport_VPADGetButtonProcMode); osLib_addFunction("vpad", "VPADSetButtonProcMode", vpadExport_VPADSetButtonProcMode); osLib_addFunction("vpad", "VPADGetLcdMode", vpadExport_VPADGetLcdMode); osLib_addFunction("vpad", "VPADSetLcdMode", vpadExport_VPADSetLcdMode); osLib_addFunction("vpad", "VPADControlMotor", vpadExport_VPADControlMotor); osLib_addFunction("vpad", "VPADStopMotor", vpadExport_VPADStopMotor); osLib_addFunction("vpad", "VPADGetTPCalibrationParam", vpadExport_VPADGetTPCalibrationParam); osLib_addFunction("vpad", "VPADSetTPCalibrationParam", vpadExport_VPADSetTPCalibrationParam); osLib_addFunction("vpad", "VPADGetTPCalibratedPoint", vpadExport_VPADGetTPCalibratedPoint); osLib_addFunction("vpad", "VPADGetTPCalibratedPointEx", vpadExport_VPADGetTPCalibratedPointEx); //osLib_addFunction("vpad", "VPADRead", vpadExport_VPADRead); //osLib_addFunction("vpad", "VPADSetSamplingCallback", vpadExport_VPADSetSamplingCallback); //osLib_addFunction("vpad", "VPADSetBtnRepeat", vpadExport_VPADSetBtnRepeat); osLib_addFunction("vpad", "VPADGetGyroZeroDriftMode", vpadExport_VPADGetGyroZeroDriftMode); osLib_addFunction("vpad", "VPADSetGyroDirection", vpadExport_VPADSetGyroDirection); osLib_addFunction("vpad", "VPADSetGyroZeroDriftMode", vpadExport_VPADSetGyroZeroDriftMode); osLib_addFunction("vpad", "VPADSetGyroDirReviseBase", vpadExport_VPADSetGyroDirReviseBase); osLib_addFunction("vpad", "VPADDisableGyroDirRevise", vpadExport_VPADDisableGyroDirRevise); osLib_addFunction("vpad", "VPADSetGyroDirReviseParam", vpadExport_VPADSetGyroDirReviseParam); }; }s_COSVPADModule; COSModule* GetModule() { return &s_COSVPADModule; } } ================================================ FILE: src/Cafe/OS/libs/vpad/vpad.h ================================================ #pragma once #include "Cafe/OS/libs/padscore/padscore.h" #include "Cafe/OS/RPL/COSModule.h" namespace vpad { void load(); void start(); } #define VPAD_MAX_CONTROLLERS (2) struct BtnRepeat { sint32 delay, pulse; }; enum VPADTouchValidity { kTpValid = 0, kTpInvalidX = 1, // only x invalid kTpInvalidY = 2, // only y invalid kTpInvalid = kTpInvalidX | kTpInvalidY, }; enum VPADTouchState { kTpTouchOff = 0, kTpTouchOn = 1, }; struct VPADDir { beVec3D_t x; beVec3D_t y; beVec3D_t z; VPADDir() = default; VPADDir(const beVec3D_t& x, const beVec3D_t& y, const beVec3D_t& z) : x(x), y(y), z(z) {} }; static_assert(sizeof(VPADDir) == 0x24); struct VPADTPData_t { uint16be x; uint16be y; uint16be touch; uint16be validity; }; static_assert(sizeof(VPADTPData_t) == 8); typedef struct VPADStatus { /* +0x00 */ uint32be hold; /* +0x04 */ uint32be trig; /* +0x08 */ uint32be release; /* +0x0C */ beVec2D_t leftStick; /* +0x14 */ beVec2D_t rightStick; /* +0x1C */ beVec3D_t acc; /* +0x28 */ float32be accMagnitude; /* +0x2C */ float32be accAcceleration; /* +0x30 */ beVec2D_t accXY; /* +0x38 */ beVec3D_t gyroChange; /* +0x44 */ beVec3D_t gyroOrientation; /* +0x50 */ sint8 vpadErr; /* +0x51 */ uint8 padding1[1]; /* +0x52 */ VPADTPData_t tpData; /* +0x5A */ VPADTPData_t tpProcessed1; /* +0x62 */ VPADTPData_t tpProcessed2; /* +0x6A */ uint8 padding2[2]; /* +0x6C */ VPADDir dir; /* +0x90 */ uint8 headphoneStatus; /* +0x91 */ uint8 padding3[3]; /* +0x94 */ beVec3D_t magnet; /* +0xA0 */ uint8 slideVolume; /* +0xA1 */ uint8 batteryLevel; /* +0xA2 */ uint8 micStatus; /* +0xA3 */ uint8 slideVolume2; /* +0xA4 */ uint8 padding4[8]; }VPADStatus_t; static_assert(sizeof(VPADStatus) == 0xAC); namespace vpad { COSModule* GetModule(); } ================================================ FILE: src/Cafe/OS/libs/zlib125/zlib125.cpp ================================================ #include "Cafe/OS/common/OSCommon.h" #include "zlib125.h" #include "zlib.h" typedef struct { /* +0x00 */ MEMPTR<uint8> next_in; /* next input byte */ /* +0x04 */ uint32be avail_in; /* number of bytes available at next_in */ /* +0x08 */ uint32be total_in; /* total number of input bytes read so far */ /* +0x0C */ MEMPTR<uint8> next_out; /* next output byte should be put there */ /* +0x10 */ uint32be avail_out; /* remaining free space at next_out */ /* +0x14 */ uint32be total_out; /* total number of bytes output so far */ /* +0x18 */ MEMPTR<char> msg; /* last error message, NULL if no error */ /* +0x1C */ MEMPTR<void> state; /* not visible by applications */ /* +0x20 */ MEMPTR<void> zalloc; /* used to allocate the internal state */ /* +0x24 */ MEMPTR<void> zfree; /* used to free the internal state */ /* +0x28 */ MEMPTR<void> opaque; /* private data object passed to zalloc and zfree */ /* +0x2C */ uint32be data_type; /* best guess about the data type: binary or text */ /* +0x30 */ uint32be adler; /* adler32 value of the uncompressed data */ /* +0x34 */ uint32be reserved; /* reserved for future use */ }z_stream_ppc2; static_assert(sizeof(z_stream_ppc2) == 0x38); voidpf zcallocWrapper(voidpf opaque, uInt items, uInt size) { z_stream_ppc2* zstream = (z_stream_ppc2*)opaque; PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->gpr[3] = zstream->opaque.GetMPTR(); hCPU->gpr[4] = items; hCPU->gpr[5] = size; PPCCore_executeCallbackInternal(zstream->zalloc.GetMPTR()); memset(memory_getPointerFromVirtualOffset(hCPU->gpr[3]), 0, items*size); return memory_getPointerFromVirtualOffset(hCPU->gpr[3]); } void zcfreeWrapper(voidpf opaque, voidpf baseIndex) { z_stream_ppc2* zstream = (z_stream_ppc2*)opaque; PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); hCPU->gpr[3] = zstream->opaque.GetMPTR(); hCPU->gpr[4] = memory_getVirtualOffsetFromPointer(baseIndex); PPCCore_executeCallbackInternal(zstream->zfree.GetMPTR()); return; } void zlib125_zcalloc(PPCInterpreter_t* hCPU) { ppcDefineParamU32(opaque, 0); ppcDefineParamU32(items, 1); ppcDefineParamU32(size, 2); hCPU->gpr[3] = items*size; hCPU->instructionPointer = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); } void zlib125_zcfree(PPCInterpreter_t* hCPU) { ppcDefineParamU32(opaque, 0); ppcDefineParamMPTR(baseIndex, 1); hCPU->gpr[3] = baseIndex; hCPU->instructionPointer = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); } void zlib125_setupHostZStream(z_stream_ppc2* input, z_stream* output, bool fixInternalStreamPtr = true) { output->next_in = input->next_in.GetPtr(); output->avail_in = (uint32)input->avail_in; output->total_in = (uint32)input->total_in; output->next_out = input->next_out.GetPtr(); output->avail_out = (uint32)input->avail_out; output->total_out = (uint32)input->total_out; output->msg = input->msg.GetPtr(); output->state = (internal_state*)input->state.GetPtr(); output->zalloc = zcallocWrapper; output->zfree = zcfreeWrapper; output->opaque = (void*)input; output->data_type = input->data_type; output->adler = (uint32)input->adler; output->reserved = (uint32)input->reserved; if (fixInternalStreamPtr && output->state) { // in newer zLib versions the internal state has a pointer to the stream at the beginning, we have to update it manually // todo - find better solution (*(void**)(output->state)) = output; } } void zlib125_setupUpdateZStream(z_stream* input, z_stream_ppc2* output) { output->next_in = input->next_in; output->avail_in = (uint32)input->avail_in; output->total_in = (uint32)input->total_in; output->next_out = input->next_out; output->avail_out = (uint32)input->avail_out; output->total_out = (uint32)input->total_out; output->msg = input->msg; output->state = (void*)input->state; output->data_type = input->data_type; output->adler = input->adler; output->reserved = input->reserved; } void zlib125Export_inflateInit_(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); ppcDefineParamStr(version, 1); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs, false); // setup internal memory allocator if requested if (zstream->zalloc == nullptr) zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); if (zstream->zfree == nullptr) zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); sint32 r = inflateInit_(&hzs, version, sizeof(z_stream)); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_inflateInit2_(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); ppcDefineParamS32(windowBits, 1); ppcDefineParamStr(version, 2); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs, false); // setup internal memory allocator if requested if (zstream->zalloc == nullptr) zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); if (zstream->zfree == nullptr) zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); sint32 r = inflateInit2_(&hzs, windowBits, version, sizeof(z_stream)); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_inflate(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); ppcDefineParamS32(flushParam, 1); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs); sint32 r = inflate(&hzs, flushParam); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_inflateEnd(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs); sint32 r = inflateEnd(&hzs); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_inflateReset(PPCInterpreter_t* hCPU) { debug_printf("inflateReset(0x%08x)\n", hCPU->gpr[3]); ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs); sint32 r = inflateReset(&hzs); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_inflateReset2(PPCInterpreter_t* hCPU) { debug_printf("inflateReset2(0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); ppcDefineParamS32(windowBits, 1); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs); sint32 r = inflateReset2(&hzs, windowBits); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_deflateInit_(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); ppcDefineParamS32(level, 1); ppcDefineParamStr(version, 2); ppcDefineParamS32(streamsize, 3); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs, false); // setup internal memory allocator if requested if (zstream->zalloc == nullptr) zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); if (zstream->zfree == nullptr) zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); if (streamsize != sizeof(z_stream_ppc2)) assert_dbg(); sint32 r = deflateInit_(&hzs, level, version, sizeof(z_stream)); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_deflateInit2_(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); ppcDefineParamS32(level, 1); ppcDefineParamS32(method, 2); ppcDefineParamS32(windowBits, 3); ppcDefineParamS32(memLevel, 4); ppcDefineParamS32(strategy, 5); ppcDefineParamStr(version, 6); ppcDefineParamS32(streamsize, 7); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs, false); // setup internal memory allocator if requested if (zstream->zalloc == nullptr) zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); if (zstream->zfree == nullptr) zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); // workaround for Splatoon (it allocates a too small buffer for our version of zLib and its zalloc returns NULL) zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); if (streamsize != sizeof(z_stream_ppc2)) assert_dbg(); sint32 r = deflateInit2_(&hzs, level, method, windowBits, memLevel, strategy, version, sizeof(z_stream)); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_deflateBound(PPCInterpreter_t* hCPU) { debug_printf("deflateBound(0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); ppcDefineParamS32(sourceLen, 1); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs); sint32 r = deflateBound(&hzs, sourceLen); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_deflate(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); ppcDefineParamS32(flushParam, 1); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs); sint32 r = deflate(&hzs, flushParam); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_deflateEnd(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); z_stream hzs; zlib125_setupHostZStream(zstream, &hzs); sint32 r = deflateEnd(&hzs); zlib125_setupUpdateZStream(&hzs, zstream); osLib_returnFromFunction(hCPU, r); } void zlib125Export_uncompress(PPCInterpreter_t* hCPU) { // Bytef * dest, uLongf * destLen, const Bytef * source, uLong sourceLen uint8* memDst = memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint8* memSrc = memory_getPointerFromVirtualOffset(hCPU->gpr[5]); uint32* pDestLenBE = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); uint32 sourceLen = hCPU->gpr[6]; uLong destLen = _swapEndianU32(*pDestLenBE); sint32 r = uncompress(memDst, &destLen, memSrc, sourceLen); *pDestLenBE = _swapEndianU32(destLen); osLib_returnFromFunction(hCPU, r); } void zlib125Export_compress(PPCInterpreter_t* hCPU) { // Bytef* dest, uLongf* destLen, const Bytef* source, uLong sourceLen uint8* memDst = memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint8* memSrc = memory_getPointerFromVirtualOffset(hCPU->gpr[5]); uint32* pDestLenBE = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); uint32 sourceLen = hCPU->gpr[6]; uLong destLen = _swapEndianU32(*pDestLenBE); sint32 r = compress(memDst, &destLen, memSrc, sourceLen); *pDestLenBE = _swapEndianU32(destLen); osLib_returnFromFunction(hCPU, r); } void zlib125Export_crc32(PPCInterpreter_t* hCPU) { uint32 crc = hCPU->gpr[3]; uint8* buf = (uint8*)memory_getPointerFromVirtualOffsetAllowNull(hCPU->gpr[4]); uint32 len = hCPU->gpr[5]; uint32 crcResult = crc32(crc, buf, len); osLib_returnFromFunction(hCPU, crcResult); } void zlib125Export_compressBound(PPCInterpreter_t* hCPU) { uint32 result = compressBound(hCPU->gpr[3]); osLib_returnFromFunction(hCPU, result); } namespace zlib { void load() { } class : public COSModule { public: std::string_view GetName() override { return "zlib125"; } void RPLMapped() override { osLib_addFunction("zlib125", "inflateInit2_", zlib125Export_inflateInit2_); osLib_addFunction("zlib125", "inflateInit_", zlib125Export_inflateInit_); osLib_addFunction("zlib125", "inflateEnd", zlib125Export_inflateEnd); osLib_addFunction("zlib125", "inflate", zlib125Export_inflate); osLib_addFunction("zlib125", "inflateReset", zlib125Export_inflateReset); osLib_addFunction("zlib125", "inflateReset2", zlib125Export_inflateReset2); osLib_addFunction("zlib125", "deflateInit_", zlib125Export_deflateInit_); osLib_addFunction("zlib125", "deflateInit2_", zlib125Export_deflateInit2_); osLib_addFunction("zlib125", "deflateBound", zlib125Export_deflateBound); osLib_addFunction("zlib125", "deflate", zlib125Export_deflate); osLib_addFunction("zlib125", "deflateEnd", zlib125Export_deflateEnd); osLib_addFunction("zlib125", "uncompress", zlib125Export_uncompress); osLib_addFunction("zlib125", "compress", zlib125Export_compress); osLib_addFunction("zlib125", "crc32", zlib125Export_crc32); osLib_addFunction("zlib125", "compressBound", zlib125Export_compressBound); }; }s_COSZlib125Module; COSModule* GetModule() { return &s_COSZlib125Module; } } ================================================ FILE: src/Cafe/OS/libs/zlib125/zlib125.h ================================================ #include "Cafe/OS/RPL/COSModule.h" namespace zlib { void load(); COSModule* GetModule(); } ================================================ FILE: src/Cafe/TitleList/AppType.h ================================================ #pragma once enum class APP_TYPE : uint32 { GAME = 0x80000000, GAME_UPDATE = 0x0800001B, GAME_DLC = 0x0800000E, // data titles VERSION_DATA_TITLE = 0x10000015, DRC_FIRMWARE = 0x10000013, DRC_TEXTURE_ATLAS = 0x1000001A, }; // allow direct comparison with uint32 inline bool operator==(APP_TYPE lhs, uint32 rhs) { return static_cast<uint32>(lhs) == rhs; } inline bool operator==(uint32 lhs, APP_TYPE rhs) { return lhs == static_cast<uint32>(rhs); } ================================================ FILE: src/Cafe/TitleList/GameInfo.h ================================================ #pragma once #include "config/CemuConfig.h" #include "TitleInfo.h" #include "config/ActiveSettings.h" class GameInfo2 { public: ~GameInfo2() { m_base.UnmountAll(); m_update.UnmountAll(); for (auto& it : m_aoc) it.UnmountAll(); } bool IsValid() const { return m_base.IsValid(); // at least the base must be valid for this to be a runnable title } bool IsSystemDataTitle() const { return m_base.IsSystemDataTitle(); } void SetBase(const TitleInfo& titleInfo) { if (IsPrioritizedVersionOrFormat(m_base, titleInfo)) m_base = titleInfo; } void SetUpdate(const TitleInfo& titleInfo) { if (IsPrioritizedVersionOrFormat(m_update, titleInfo)) m_update = titleInfo; } bool HasUpdate() const { return m_update.IsValid(); } void AddAOC(const TitleInfo& titleInfo) { TitleId aocTitleId = titleInfo.GetAppTitleId(); uint16 aocVersion = titleInfo.GetAppTitleVersion(); auto it = std::find_if(m_aoc.begin(), m_aoc.end(), [aocTitleId](const TitleInfo& rhs) { return rhs.GetAppTitleId() == aocTitleId; }); if (it != m_aoc.end()) { if (!IsPrioritizedVersionOrFormat(*it, titleInfo)) return; m_aoc.erase(it); } m_aoc.emplace_back(titleInfo); } bool HasAOC() const { return !m_aoc.empty(); } TitleInfo& GetBase() { return m_base; } TitleInfo& GetUpdate() { return m_update; } std::span<TitleInfo> GetAOC() { return m_aoc; } TitleId GetBaseTitleId() { cemu_assert_debug(m_base.IsValid()); return m_base.GetAppTitleId(); } std::string GetTitleName() { cemu_assert_debug(m_base.IsValid()); return m_base.GetMetaTitleName(); // long name } uint16 GetVersion() const { if (m_update.IsValid()) return m_update.GetAppTitleVersion(); return m_base.GetAppTitleVersion(); } uint32 GetSDKVersion() const { if (m_update.IsValid()) return m_update.GetAppSDKVersion(); return m_base.GetAppSDKVersion(); } CafeConsoleRegion GetRegion() const { if (m_update.IsValid()) return m_update.GetMetaRegion(); return m_base.GetMetaRegion(); } uint16 GetAOCVersion() const { if (m_aoc.empty()) return 0; return m_aoc.front().GetAppTitleVersion(); } fs::path GetSaveFolder() { return ActiveSettings::GetMlcPath(fmt::format("usr/save/{:08x}/{:08x}", (GetBaseTitleId() >> 32), GetBaseTitleId() & 0xFFFFFFFF)); } private: bool IsPrioritizedVersionOrFormat(const TitleInfo& currentTitle, const TitleInfo& newTitle) { if (!currentTitle.IsValid()) return true; // always prefer a valid title over an invalid one // always prefer higher version if (newTitle.GetAppTitleVersion() > currentTitle.GetAppTitleVersion()) return true; // never prefer lower version if (newTitle.GetAppTitleVersion() < currentTitle.GetAppTitleVersion()) return false; // for users which have both NUS and non-NUS titles in their games folder we want to prioritize non-NUS formats // this is to stay consistent with previous Cemu versions which did not support NUS format at all TitleInfo::TitleDataFormat currentFormat = currentTitle.GetFormat(); TitleInfo::TitleDataFormat newFormat = newTitle.GetFormat(); if (currentFormat != TitleInfo::TitleDataFormat::NUS && newFormat == TitleInfo::TitleDataFormat::NUS) return false; return true; }; TitleInfo m_base; TitleInfo m_update; std::vector<TitleInfo> m_aoc; }; ================================================ FILE: src/Cafe/TitleList/ParsedMetaXml.h ================================================ #pragma once #include <pugixml.hpp> #include "config/CemuConfig.h" struct ParsedMetaXml { uint32 m_version; std::string m_product_code; std::string m_company_code; std::string m_content_platform; uint64 m_title_id; CafeConsoleRegion m_region; std::array<std::string, 12> m_long_name; std::array<std::string, 12> m_short_name; std::array<std::string, 12> m_publisher; uint32 m_olv_accesskey; std::string GetShortName(CafeConsoleLanguage languageId) const { return m_short_name[(size_t)languageId].empty() ? m_short_name[(size_t)CafeConsoleLanguage::EN] : m_short_name[(size_t)languageId]; } std::string GetLongName(CafeConsoleLanguage languageId) const { return m_long_name[(size_t)languageId].empty() ? m_long_name[(size_t)CafeConsoleLanguage::EN] : m_long_name[(size_t)languageId]; } TitleId GetTitleId() const { return m_title_id; } uint16 GetTitleVersion() const { return (uint16)m_version; } CafeConsoleRegion GetRegion() const { return m_region; } std::string GetProductCode() const { return m_product_code; } std::string GetCompanyCode() const { return m_company_code; } uint32 GetOlvAccesskey() const { return m_olv_accesskey; } static ParsedMetaXml* Parse(uint8* xmlData, size_t xmlSize) { if (xmlSize == 0) return nullptr; pugi::xml_document meta_doc; if (!meta_doc.load_buffer_inplace(xmlData, xmlSize)) return nullptr; const auto root = meta_doc.child("menu"); if (!root) return nullptr; ParsedMetaXml* parsedMetaXml = new ParsedMetaXml(); for (const auto& child : root.children()) { std::string_view name = child.name(); if (name == "title_version") parsedMetaXml->m_version = child.text().as_uint(); else if (name == "product_code") parsedMetaXml->m_product_code = child.text().as_string(); else if (name == "company_code") parsedMetaXml->m_company_code = child.text().as_string(); else if (name == "content_platform") parsedMetaXml->m_content_platform = child.text().as_string(); else if (name == "title_id") parsedMetaXml->m_title_id = std::stoull(child.text().as_string(), nullptr, 16); else if (name == "region") parsedMetaXml->m_region = (CafeConsoleRegion)child.text().as_uint(); else if (boost::starts_with(name, "longname_")) { const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1)); if (index != -1){ std::string longname = child.text().as_string(); std::replace_if(longname.begin(), longname.end(), [](char c) { return c == '\r' || c == '\n';}, ' '); parsedMetaXml->m_long_name[index] = longname; } } else if (boost::starts_with(name, L"shortname_")) { const sint32 index = GetLanguageIndex(name.substr(std::size("shortname_") - 1)); if (index != -1) parsedMetaXml->m_short_name[index] = child.text().as_string(); } else if (boost::starts_with(name, L"publisher_")) { const sint32 index = GetLanguageIndex(name.substr(std::size("publisher_") - 1)); if (index != -1) parsedMetaXml->m_publisher[index] = child.text().as_string(); } else if (boost::starts_with(name, L"olv_accesskey")) parsedMetaXml->m_olv_accesskey = child.text().as_uint(-1); } if (parsedMetaXml->m_title_id == 0) { // not valid delete parsedMetaXml; return nullptr; } return parsedMetaXml; } private: static sint32 GetLanguageIndex(std::string_view language) // move to NCrypto ? { if (language == "ja") return (sint32)CafeConsoleLanguage::JA; else if (language == "en") return (sint32)CafeConsoleLanguage::EN; else if (language == "fr") return (sint32)CafeConsoleLanguage::FR; else if (language == "de") return (sint32)CafeConsoleLanguage::DE; else if (language == "it") return (sint32)CafeConsoleLanguage::IT; else if (language == "es") return (sint32)CafeConsoleLanguage::ES; else if (language == "zhs") return (sint32)CafeConsoleLanguage::ZH; else if (language == "ko") return (sint32)CafeConsoleLanguage::KO; else if (language == "nl") return (sint32)CafeConsoleLanguage::NL; else if (language == "pt") return (sint32)CafeConsoleLanguage::PT; else if (language == "ru") return (sint32)CafeConsoleLanguage::RU; else if (language == "zht") return (sint32)CafeConsoleLanguage::TW; // if return ZH here, xxx_zht values may cover xxx_zh values in function Parse() return -1; } }; ================================================ FILE: src/Cafe/TitleList/SaveInfo.cpp ================================================ #include "SaveInfo.h" #include "config/ActiveSettings.h" #include "Common/FileStream.h" #include "ParsedMetaXml.h" SaveInfo::SaveInfo(TitleId titleId) : m_titleId(titleId) { m_path = GetSavePath(titleId); std::error_code ec; m_isValid = fs::is_directory(m_path, ec); } std::string SaveInfo::GetStorageSubpathByTitleId(TitleId titleId) { // usr/save/<titleIdHigh>/<titleIdLow>/ return fmt::format("usr/save/{:08x}/{:08x}", ((uint64)titleId) >> 32, (uint64)titleId & 0xFFFFFFFF); } fs::path SaveInfo::GetSavePath(TitleId titleId) { return ActiveSettings::GetMlcPath(GetStorageSubpathByTitleId(titleId)); } bool SaveInfo::ParseMetaData() { if (m_hasMetaLoaded) return m_parsedMetaXml != nullptr; m_hasMetaLoaded = true; auto xmlData = FileStream::LoadIntoMemory(m_path / "meta/meta.xml"); if (!xmlData) return false; m_parsedMetaXml = ParsedMetaXml::Parse(xmlData->data(), xmlData->size()); return m_parsedMetaXml != nullptr; } ================================================ FILE: src/Cafe/TitleList/SaveInfo.h ================================================ #pragma once #include "TitleId.h" #include "ParsedMetaXml.h" class SaveInfo { public: SaveInfo() {}; SaveInfo(TitleId titleId); bool IsValid() const { return m_isValid; } TitleId GetTitleId() const { return m_titleId; } fs::path GetPath() const { return m_path; } // meta data bool ParseMetaData(); ParsedMetaXml* GetMetaInfo() { return m_parsedMetaXml; } private: static std::string GetStorageSubpathByTitleId(TitleId titleId); static fs::path GetSavePath(TitleId titleId); TitleId m_titleId; fs::path m_path; bool m_isValid{false}; bool m_hasMetaLoaded{false}; ParsedMetaXml* m_parsedMetaXml{nullptr}; }; ================================================ FILE: src/Cafe/TitleList/SaveList.cpp ================================================ #include "SaveList.h" #include <charconv> #include <util/helpers/helpers.h> std::mutex sSLMutex; fs::path sSLMLCPath; std::vector<SaveInfo*> sSLList; // callback list struct SaveListCallbackEntry { SaveListCallbackEntry(void(*cb)(CafeSaveListCallbackEvent* evt, void* ctx), void* ctx, uint64 uniqueId) : cb(cb), ctx(ctx), uniqueId(uniqueId) {}; void (*cb)(CafeSaveListCallbackEvent* evt, void* ctx); void* ctx; uint64 uniqueId; }; std::vector<SaveListCallbackEntry> sSLCallbackList; // worker thread std::atomic_bool sSLWorkerThreadActive{false}; void CafeSaveList::Initialize() { } void CafeSaveList::SetMLCPath(fs::path mlcPath) { std::unique_lock _lock(sSLMutex); sSLMLCPath = mlcPath; } void CafeSaveList::Refresh() { std::unique_lock _lock(sSLMutex); if (sSLWorkerThreadActive) return; sSLWorkerThreadActive = true; std::thread t(RefreshThreadWorker); t.detach(); } void CafeSaveList::RefreshThreadWorker() { SetThreadName("SaveListWorker"); // clear save list for (auto& itSaveInfo : sSLList) { for (auto& it : sSLCallbackList) { CafeSaveListCallbackEvent evt; evt.eventType = CafeSaveListCallbackEvent::TYPE::SAVE_REMOVED; evt.saveInfo = itSaveInfo; it.cb(&evt, it.ctx); } delete itSaveInfo; } sSLList.clear(); sSLMutex.lock(); fs::path mlcPath = sSLMLCPath; sSLMutex.unlock(); std::error_code ec; for (auto it_titleHigh : fs::directory_iterator(mlcPath / "usr/save", ec)) { if(!it_titleHigh.is_directory(ec)) continue; std::string dirName = _pathToUtf8(it_titleHigh.path().filename()); if(dirName.empty()) continue; uint32 titleIdHigh; std::from_chars_result r = std::from_chars(dirName.data(), dirName.data() + dirName.size(), titleIdHigh, 16); if (r.ec != std::errc()) continue; fs::path tmp = it_titleHigh.path(); for (auto it_titleLow : fs::directory_iterator(tmp, ec)) { if (!it_titleLow.is_directory(ec)) continue; dirName = _pathToUtf8(it_titleLow.path().filename()); if (dirName.empty()) continue; uint32 titleIdLow; std::from_chars_result r = std::from_chars(dirName.data(), dirName.data() + dirName.size(), titleIdLow, 16); if (r.ec != std::errc()) continue; // found save TitleId titleId = (uint64)titleIdHigh << 32 | (uint64)titleIdLow; SaveInfo* saveInfo = new SaveInfo(titleId); if (saveInfo->IsValid()) DiscoveredSave(saveInfo); else delete saveInfo; } } sSLMutex.lock(); sSLWorkerThreadActive = false; sSLMutex.unlock(); // send notification about finished scan for (auto& it : sSLCallbackList) { CafeSaveListCallbackEvent evt; evt.eventType = CafeSaveListCallbackEvent::TYPE::SCAN_FINISHED; evt.saveInfo = nullptr; it.cb(&evt, it.ctx); } } void CafeSaveList::DiscoveredSave(SaveInfo* saveInfo) { if (!saveInfo->ParseMetaData()) { delete saveInfo; return; } std::unique_lock _lock(sSLMutex); auto it = std::find_if(sSLList.begin(), sSLList.end(), [saveInfo](const SaveInfo* rhs) { return saveInfo->GetTitleId() == rhs->GetTitleId(); }); if (it != sSLList.end()) { // save already known delete saveInfo; return; } sSLList.emplace_back(saveInfo); // send notification for (auto& it : sSLCallbackList) { CafeSaveListCallbackEvent evt; evt.eventType = CafeSaveListCallbackEvent::TYPE::SAVE_DISCOVERED; evt.saveInfo = saveInfo; it.cb(&evt, it.ctx); } } uint64 CafeSaveList::RegisterCallback(void(*cb)(CafeSaveListCallbackEvent* evt, void* ctx), void* ctx) { static std::atomic<uint64_t> sCallbackIdGen = 1; uint64 id = sCallbackIdGen.fetch_add(1); std::unique_lock _lock(sSLMutex); sSLCallbackList.emplace_back(cb, ctx, id); // immediately notify of all known titles for (auto& it : sSLList) { CafeSaveListCallbackEvent evt; evt.eventType = CafeSaveListCallbackEvent::TYPE::SAVE_DISCOVERED; evt.saveInfo = it; cb(&evt, ctx); } // if not scanning then send out scan finished notification if (!sSLWorkerThreadActive) { CafeSaveListCallbackEvent evt; evt.eventType = CafeSaveListCallbackEvent::TYPE::SCAN_FINISHED; evt.saveInfo = nullptr; for (auto& it : sSLCallbackList) it.cb(&evt, it.ctx); } return id; } void CafeSaveList::UnregisterCallback(uint64 id) { std::unique_lock _lock(sSLMutex); auto it = std::find_if(sSLCallbackList.begin(), sSLCallbackList.end(), [id](auto& e) { return e.uniqueId == id; }); cemu_assert(it != sSLCallbackList.end()); sSLCallbackList.erase(it); } SaveInfo CafeSaveList::GetSaveByTitleId(TitleId titleId) { std::unique_lock _lock(sSLMutex); for (auto& it : sSLList) if (it->GetTitleId() == titleId) return *it; return {}; } ================================================ FILE: src/Cafe/TitleList/SaveList.h ================================================ #pragma once #include "SaveInfo.h" struct CafeSaveListCallbackEvent { enum class TYPE { SAVE_DISCOVERED, SAVE_REMOVED, SCAN_FINISHED, }; TYPE eventType; SaveInfo* saveInfo; }; class CafeSaveList { public: static void Initialize(); static void SetMLCPath(fs::path mlcPath); static void Refresh(); static SaveInfo GetSaveByTitleId(TitleId titleId); // callback static uint64 RegisterCallback(void(*cb)(CafeSaveListCallbackEvent* evt, void* ctx), void* ctx); // on register, the callback will be invoked for every already known save static void UnregisterCallback(uint64 id); private: static void RefreshThreadWorker(); static void DiscoveredSave(SaveInfo* saveInfo); }; ================================================ FILE: src/Cafe/TitleList/TitleId.h ================================================ #pragma once using TitleId = uint64; static_assert(sizeof(TitleId) == 8); class TitleIdParser { public: enum class TITLE_TYPE { /* XX */ UNKNOWN = 0xFF, // placeholder /* 00 */ BASE_TITLE = 0x00, // eShop and disc titles /* 02 */ BASE_TITLE_DEMO = 0x02, /* 0E */ BASE_TITLE_UPDATE = 0x0E, // update for BASE_TITLE (and maybe BASE_TITLE_DEMO?) /* 0F */ HOMEBREW = 0x0F, /* 0C */ AOC = 0x0C, // DLC /* 10 */ SYSTEM_TITLE = 0x10, // eShop etc /* 1B */ SYSTEM_DATA = 0x1B, /* 30 */ SYSTEM_OVERLAY_TITLE = 0x30, }; TitleIdParser(uint64 titleId) : m_titleId(titleId) {}; // controls whether this title installs to /usr/title or /sys/title bool IsSystemTitle() const { return (GetTypeByte() & 0x10) != 0; }; bool IsBaseTitleUpdate() const { return GetType() == TITLE_TYPE::BASE_TITLE_UPDATE; } TITLE_TYPE GetType() const { uint8 b = GetTypeByte(); switch (b) { case 0x00: return TITLE_TYPE::BASE_TITLE; case 0x02: return TITLE_TYPE::BASE_TITLE_DEMO; case 0x0E: return TITLE_TYPE::BASE_TITLE_UPDATE; case 0x0F: return TITLE_TYPE::HOMEBREW; case 0x0C: return TITLE_TYPE::AOC; case 0x10: return TITLE_TYPE::SYSTEM_TITLE; case 0x1B: return TITLE_TYPE::SYSTEM_DATA; case 0x30: return TITLE_TYPE::SYSTEM_OVERLAY_TITLE; } cemuLog_log(LogType::Force, "Unknown title type ({0:016x})", m_titleId); return TITLE_TYPE::UNKNOWN; } bool IsPlatformCafe() const { return GetPlatformWord() == 0x0005; } bool CanHaveSeparateUpdateTitleId() const { return GetType() == TITLE_TYPE::BASE_TITLE; } TitleId GetSeparateUpdateTitleId() const { cemu_assert_debug(CanHaveSeparateUpdateTitleId()); return MakeTitleIdWithType(TITLE_TYPE::BASE_TITLE_UPDATE); // e.g. 00050000-11223344 -> 0005000E-11223344 } static TitleId MakeBaseTitleId(TitleId titleId) { TitleIdParser titleIdParser(titleId); if (titleIdParser.GetType() == TITLE_TYPE::BASE_TITLE_UPDATE) return titleIdParser.MakeTitleIdWithType(TITLE_TYPE::BASE_TITLE); return titleId; } static bool ParseFromStr(std::string_view strView, TitleId& titleIdOut) { if (strView.size() < 16) return false; uint64 tmp = 0; for (size_t i = 0; i < 8*2; i++) { tmp <<= 4; char c = strView[i]; if (c >= 'A' && c <= 'F') tmp += (uint64)(c - 'A' + 10); else if (c >= 'a' && c <= 'f') tmp += (uint64)(c - 'a' + 10); else if (c >= '0' && c <= '9') tmp += (uint64)(c - '0'); else return false; } titleIdOut = tmp; return true; } private: uint8 GetTypeByte() const { return (m_titleId >> 32) & 0xFF; } TitleId MakeTitleIdWithType(TITLE_TYPE newType) const { TitleId t = m_titleId; t &= ~(0xFFull << 32); t |= ((uint64)newType << 32); return t; } uint16 GetPlatformWord() const // might not be a whole word? { return (m_titleId >> 48) & 0xFFFF; } TitleId m_titleId; }; ================================================ FILE: src/Cafe/TitleList/TitleInfo.cpp ================================================ #include "TitleInfo.h" #include "Cafe/Filesystem/fscDeviceHostFS.h" #include "Cafe/Filesystem/WUHB/WUHBReader.h" #include "Cafe/Filesystem/FST/FST.h" #include "pugixml.hpp" #include "Common/FileStream.h" #include <zarchive/zarchivereader.h> #include "util/IniParser/IniParser.h" #include "util/crypto/crc32.h" #include "config/ActiveSettings.h" #include "util/helpers/helpers.h" // detect format by reading file header/footer CafeTitleFileType DetermineCafeSystemFileType(fs::path filePath) { std::unique_ptr<FileStream> fs(FileStream::openFile2(filePath)); if (!fs) return CafeTitleFileType::UNKNOWN; // very small files (<32 bytes) are always considered unknown uint64 fileSize = fs->GetSize(); if (fileSize < 32) return CafeTitleFileType::UNKNOWN; // read header bytes uint8 headerRaw[32]{}; fs->readData(headerRaw, sizeof(headerRaw)); // check for WUX uint8 wuxHeaderMagic[8] = { 0x57,0x55,0x58,0x30,0x2E,0xD0,0x99,0x10 }; if (memcmp(headerRaw, wuxHeaderMagic, sizeof(wuxHeaderMagic)) == 0) return CafeTitleFileType::WUX; // check for RPX uint8 rpxHeaderMagic[9] = { 0x7F,0x45,0x4C,0x46,0x01,0x02,0x01,0xCA,0xFE }; if (memcmp(headerRaw, rpxHeaderMagic, sizeof(rpxHeaderMagic)) == 0) return CafeTitleFileType::RPX; // check for ELF uint8 elfHeaderMagic[9] = { 0x7F,0x45,0x4C,0x46,0x01,0x02,0x01,0x00,0x00 }; if (memcmp(headerRaw, elfHeaderMagic, sizeof(elfHeaderMagic)) == 0) return CafeTitleFileType::ELF; // check for WUD uint8 wudMagic1[4] = { 0x57,0x55,0x50,0x2D }; // wud files should always start with "WUP-..." uint8 wudMagic2[4] = { 0xCC,0x54,0x9E,0xB9 }; if (fileSize >= 0x10000) { uint8 magic1[4]; fs->SetPosition(0); fs->readData(magic1, 4); if (memcmp(magic1, wudMagic1, 4) == 0) { uint8 magic2[4]; fs->SetPosition(0x10000); fs->readData(magic2, 4); if (memcmp(magic2, wudMagic2, 4) == 0) { return CafeTitleFileType::WUD; } } } // check for WUA // todo return CafeTitleFileType::UNKNOWN; } TitleInfo::TitleInfo(const fs::path& path) { m_isValid = DetectFormat(path, m_fullPath, m_titleFormat); if (!m_isValid) m_titleFormat = TitleDataFormat::INVALID_STRUCTURE; else { m_isValid = ParseXmlInfo(); } if (m_isValid) CalcUID(); } TitleInfo::TitleInfo(const fs::path& path, std::string_view subPath) { // path must point to a (wua) file if (!path.has_filename()) { m_isValid = false; SetInvalidReason(InvalidReason::BAD_PATH_OR_INACCESSIBLE); return; } m_isValid = true; m_titleFormat = TitleDataFormat::WIIU_ARCHIVE; m_fullPath = path; m_subPath = subPath; m_isValid = ParseXmlInfo(); if (m_isValid) CalcUID(); } TitleInfo::TitleInfo(const TitleInfo::CachedInfo& cachedInfo) { m_cachedInfo = new CachedInfo(cachedInfo); m_fullPath = cachedInfo.path; m_subPath = cachedInfo.subPath; m_titleFormat = cachedInfo.titleDataFormat; // verify some parameters m_isValid = false; if (cachedInfo.titleDataFormat != TitleDataFormat::HOST_FS && cachedInfo.titleDataFormat != TitleDataFormat::WIIU_ARCHIVE && cachedInfo.titleDataFormat != TitleDataFormat::WUHB && cachedInfo.titleDataFormat != TitleDataFormat::WUD && cachedInfo.titleDataFormat != TitleDataFormat::NUS && cachedInfo.titleDataFormat != TitleDataFormat::INVALID_STRUCTURE) return; if (cachedInfo.path.empty()) return; if (cachedInfo.titleDataFormat == TitleDataFormat::WIIU_ARCHIVE && m_subPath.empty()) return; // for wua files the subpath must never be empty (title must not be stored in root of archive) m_isValid = true; CalcUID(); } TitleInfo::~TitleInfo() { cemu_assert(m_mountpoints.empty()); delete m_parsedMetaXml; delete m_parsedAppXml; delete m_parsedCosXml; delete m_cachedInfo; } TitleInfo::CachedInfo TitleInfo::MakeCacheEntry() { cemu_assert_debug(IsValid()); CachedInfo e; e.titleDataFormat = m_titleFormat; e.path = m_fullPath; e.subPath = m_subPath; e.titleId = GetAppTitleId(); e.titleVersion = GetAppTitleVersion(); e.sdkVersion = GetAppSDKVersion(); e.titleName = GetMetaTitleName(); e.region = GetMetaRegion(); e.group_id = GetAppGroup(); e.app_type = GetAppType(); return e; } // WUA can contain multiple titles. Root directory contains one directory for each title. The name must match: <titleId>_v<version> bool TitleInfo::ParseWuaTitleFolderName(std::string_view name, TitleId& titleIdOut, uint16& titleVersionOut) { std::string_view sv = name; if (sv.size() < 16 + 2) return false; TitleId parsedId; if (!TitleIdParser::ParseFromStr(sv, parsedId)) return false; sv.remove_prefix(16); if (sv[0] != '_' || (sv[1] != 'v' && sv[1] != 'v')) return false; sv.remove_prefix(2); if (sv.empty()) return false; if (sv[0] == '0' && sv.size() != 1) // leading zero not allowed return false; uint32 v = 0; while (!sv.empty()) { uint8 c = sv[0]; sv.remove_prefix(1); v *= 10; if (c >= '0' && c <= '9') v += (uint32)(c - '0'); else { v = 0xFFFFFFFF; break; } } if (v > 0xFFFF) return false; titleIdOut = parsedId; titleVersionOut = v; return true; } bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut) { std::error_code ec; if (path.has_extension() && fs::is_regular_file(path, ec)) { std::string filenameStr = _pathToUtf8(path.filename()); if (boost::iends_with(filenameStr, ".rpx")) { // is in code folder? fs::path parentPath = path.parent_path(); if (boost::iequals(_pathToUtf8(parentPath.filename()), "code")) { parentPath = parentPath.parent_path(); // next to content and meta? std::error_code ec; if (fs::exists(parentPath / "content", ec) && fs::exists(parentPath / "meta", ec)) { formatOut = TitleDataFormat::HOST_FS; pathOut = parentPath; return true; } } } else if (boost::iends_with(filenameStr, ".wud") || boost::iends_with(filenameStr, ".wux") || boost::iends_with(filenameStr, ".iso")) { formatOut = TitleDataFormat::WUD; pathOut = path; return true; } else if (boost::iequals(filenameStr, "title.tmd")) { formatOut = TitleDataFormat::NUS; pathOut = path; return true; } else if (boost::iends_with(filenameStr, ".wua")) { formatOut = TitleDataFormat::WIIU_ARCHIVE; pathOut = path; // a Wii U archive file can contain multiple titles but TitleInfo only maps to one // we use the first base title that we find. This is the most intuitive behavior when someone launches "game.wua" ZArchiveReader* zar = ZArchiveReader::OpenFromFile(path); if (!zar) return false; ZArchiveNodeHandle rootDir = zar->LookUp("", false, true); bool foundBase = false; for (uint32 i = 0; i < zar->GetDirEntryCount(rootDir); i++) { ZArchiveReader::DirEntry dirEntry; if (!zar->GetDirEntry(rootDir, i, dirEntry)) continue; if (!dirEntry.isDirectory) continue; TitleId parsedId; uint16 parsedVersion; if (!TitleInfo::ParseWuaTitleFolderName(dirEntry.name, parsedId, parsedVersion)) continue; TitleIdParser tip(parsedId); TitleIdParser::TITLE_TYPE tt = tip.GetType(); if (tt == TitleIdParser::TITLE_TYPE::BASE_TITLE || tt == TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO || tt == TitleIdParser::TITLE_TYPE::SYSTEM_TITLE || tt == TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE) { m_subPath = dirEntry.name; foundBase = true; break; } } delete zar; return foundBase; } else if (boost::iends_with(filenameStr, ".wuhb")) { std::unique_ptr<WUHBReader> reader{WUHBReader::FromPath(path)}; if(reader) { formatOut = TitleDataFormat::WUHB; pathOut = path; return true; } } // note: Since a Wii U archive file (.wua) contains multiple titles we shouldn't auto-detect them here // instead TitleInfo has a second constructor which takes a subpath // unable to determine type by extension, check contents CafeTitleFileType fileType = DetermineCafeSystemFileType(path); if (fileType == CafeTitleFileType::WUD || fileType == CafeTitleFileType::WUX) { formatOut = TitleDataFormat::WUD; pathOut = path; return true; } } else { // does it point to the root folder of a title? std::error_code ec; if (fs::exists(path / "content", ec) && fs::exists(path / "meta", ec) && fs::exists(path / "code", ec)) { formatOut = TitleDataFormat::HOST_FS; pathOut = path; return true; } } SetInvalidReason(InvalidReason::UNKNOWN_FORMAT); return false; } bool TitleInfo::IsValid() const { return m_isValid; } fs::path TitleInfo::GetPath() const { if (!m_isValid) { cemu_assert_suspicious(); return {}; } return m_fullPath; } void TitleInfo::CalcUID() { cemu_assert_debug(m_isValid); if (!m_isValid) { m_uid = 0; return; } // get absolute normalized path fs::path normalizedPath; if (m_fullPath.is_relative()) { normalizedPath = ActiveSettings::GetUserDataPath(); normalizedPath /= m_fullPath; } else normalizedPath = m_fullPath; normalizedPath = normalizedPath.lexically_normal(); uint64 h = fs::hash_value(normalizedPath); // for WUA files also hash the subpath if (m_titleFormat == TitleDataFormat::WIIU_ARCHIVE) { uint64 subHash = std::hash<std::string_view>{}(m_subPath); h += subHash; } m_uid = h; } uint64 TitleInfo::GetUID() { cemu_assert_debug(m_isValid); return m_uid; } void TitleInfo::SetInvalidReason(InvalidReason reason) { if(m_invalidReason == InvalidReason::NONE) m_invalidReason = reason; // only update reason when it hasn't been set before } std::mutex sZArchivePoolMtx; std::map<fs::path, std::pair<uint32, ZArchiveReader*>> sZArchivePool; ZArchiveReader* _ZArchivePool_AcquireInstance(const fs::path& path) { std::unique_lock _lock(sZArchivePoolMtx); auto it = sZArchivePool.find(path); if (it != sZArchivePool.end()) { it->second.first++; // increment ref count return it->second.second; } _lock.unlock(); // opening wua files can be expensive, so we do it outside of the lock ZArchiveReader* zar = ZArchiveReader::OpenFromFile(path); if (!zar) return nullptr; _lock.lock(); // check if another instance was allocated in the meantime it = sZArchivePool.find(path); if (it != sZArchivePool.end()) { delete zar; it->second.first++; // increment ref count return it->second.second; } sZArchivePool.emplace(std::piecewise_construct, std::forward_as_tuple(path), std::forward_as_tuple(1, zar) ); return zar; } void _ZArchivePool_ReleaseInstance(const fs::path& path, ZArchiveReader* zar) { std::unique_lock _lock(sZArchivePoolMtx); auto it = sZArchivePool.find(path); cemu_assert(it != sZArchivePool.end()); cemu_assert(it->second.second == zar); it->second.first--; // decrement ref count if (it->second.first == 0) { delete it->second.second; sZArchivePool.erase(it); } } bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder, sint32 mountPriority) { cemu_assert_debug(subfolder.empty() || (subfolder.front() != '/' || subfolder.front() != '\\')); // only relative subfolder allowed cemu_assert(m_isValid); if (m_titleFormat == TitleDataFormat::HOST_FS) { fs::path hostFSPath = m_fullPath; hostFSPath.append(subfolder); bool r = FSCDeviceHostFS_Mount(std::string(virtualPath).c_str(), _pathToUtf8(hostFSPath), mountPriority); cemu_assert_debug(r); if (!r) { cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); SetInvalidReason(InvalidReason::BAD_PATH_OR_INACCESSIBLE); return false; } } else if (m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS) { FSTVolume::ErrorCode fstError; if (m_mountpoints.empty()) { cemu_assert_debug(!m_wudVolume); if(m_titleFormat == TitleDataFormat::WUD) m_wudVolume = FSTVolume::OpenFromDiscImage(m_fullPath, &fstError); // open wud/wux else m_wudVolume = FSTVolume::OpenFromContentFolder(m_fullPath.parent_path(), &fstError); // open from .app files directory, the path points to /title.tmd } if (!m_wudVolume) { if (fstError == FSTVolume::ErrorCode::DISC_KEY_MISSING) SetInvalidReason(InvalidReason::NO_DISC_KEY); else if (fstError == FSTVolume::ErrorCode::TITLE_TIK_MISSING) SetInvalidReason(InvalidReason::NO_TITLE_TIK); return false; } bool r = FSCDeviceWUD_Mount(virtualPath, subfolder, m_wudVolume, mountPriority); cemu_assert_debug(r); if (!r) { cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); delete m_wudVolume; return false; } } else if (m_titleFormat == TitleDataFormat::WIIU_ARCHIVE) { if (!m_zarchive) { m_zarchive = _ZArchivePool_AcquireInstance(m_fullPath); if (!m_zarchive) return false; } bool r = FSCDeviceWUA_Mount(virtualPath, std::string(m_subPath).append("/").append(subfolder), m_zarchive, mountPriority); if (!r) { cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); _ZArchivePool_ReleaseInstance(m_fullPath, m_zarchive); return false; } } else if (m_titleFormat == TitleDataFormat::WUHB) { if (!m_wuhbreader) { m_wuhbreader = WUHBReader::FromPath(m_fullPath); if (!m_wuhbreader) return false; } bool r = FSCDeviceWUHB_Mount(virtualPath, subfolder, m_wuhbreader, mountPriority); if (!r) { cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); delete m_wuhbreader; m_wuhbreader = nullptr; return false; } } else { cemu_assert_unimplemented(); } m_mountpoints.emplace_back(mountPriority, virtualPath); return true; } void TitleInfo::Unmount(std::string_view virtualPath) { for (auto& itr : m_mountpoints) { if (!boost::equals(itr.second, virtualPath)) continue; fsc_unmount(itr.second.c_str(), itr.first); std::erase(m_mountpoints, itr); // if the last mount point got unmounted, close any open devices if (m_mountpoints.empty()) { if (m_wudVolume) { cemu_assert_debug(m_titleFormat == TitleDataFormat::WUD || m_titleFormat == TitleDataFormat::NUS); delete m_wudVolume; m_wudVolume = nullptr; } if (m_zarchive) { _ZArchivePool_ReleaseInstance(m_fullPath, m_zarchive); if (m_mountpoints.empty()) m_zarchive = nullptr; } if (m_wuhbreader) { cemu_assert_debug(m_titleFormat == TitleDataFormat::WUHB); delete m_wuhbreader; m_wuhbreader = nullptr; } } return; } cemu_assert_suspicious(); // unmount on unknown path } void TitleInfo::UnmountAll() { while (!m_mountpoints.empty()) Unmount(m_mountpoints.front().second); } std::atomic_uint64_t sTempMountingPathCounter = 1; std::string TitleInfo::GetUniqueTempMountingPath() { uint64_t v = sTempMountingPathCounter.fetch_add(1); return fmt::format("/internal/tempMount{:016x}/", v); } bool TitleInfo::ParseXmlInfo() { cemu_assert(m_isValid); if (m_hasParsedXmlFiles) return m_isValid; m_hasParsedXmlFiles = true; std::string mountPath = GetUniqueTempMountingPath(); bool r = Mount(mountPath, "", FSC_PRIORITY_BASE); if (!r) return false; // meta/meta.xml auto xmlData = fsc_extractFile(fmt::format("{}meta/meta.xml", mountPath).c_str()); if(xmlData) m_parsedMetaXml = ParsedMetaXml::Parse(xmlData->data(), xmlData->size()); if(!m_parsedMetaXml) { // meta/meta.ini (WUHB) auto iniData = fsc_extractFile(fmt::format("{}meta/meta.ini", mountPath).c_str()); if (iniData) m_parsedMetaXml = ParseAromaIni(*iniData); if(m_parsedMetaXml) { m_parsedCosXml = new ParsedCosXml{.argstr = "root.rpx"}; m_parsedAppXml = new ParsedAppXml{m_parsedMetaXml->m_title_id, 0, 0, 0, 0}; } } // code/app.xml xmlData = fsc_extractFile(fmt::format("{}code/app.xml", mountPath).c_str()); if(xmlData) ParseAppXml(*xmlData); // code/cos.xml xmlData = fsc_extractFile(fmt::format("{}code/cos.xml", mountPath).c_str()); if (xmlData) m_parsedCosXml = ParsedCosXml::Parse(xmlData->data(), xmlData->size()); Unmount(mountPath); // some system titles dont have a meta.xml file bool allowMissingMetaXml = false; if(m_parsedAppXml && this->IsSystemDataTitle()) { allowMissingMetaXml = true; } if ((allowMissingMetaXml == false && !m_parsedMetaXml) || !m_parsedAppXml || !m_parsedCosXml) { bool hasAnyXml = m_parsedMetaXml || m_parsedAppXml || m_parsedCosXml; if (hasAnyXml) cemuLog_log(LogType::Force, "Title has missing meta .xml files. Title path: {}", _pathToUtf8(m_fullPath)); delete m_parsedMetaXml; delete m_parsedAppXml; delete m_parsedCosXml; m_parsedMetaXml = nullptr; m_parsedAppXml = nullptr; m_parsedCosXml = nullptr; m_isValid = false; SetInvalidReason(InvalidReason::MISSING_XML_FILES); return false; } m_isValid = true; return true; } ParsedMetaXml* TitleInfo::ParseAromaIni(std::span<unsigned char> content) { IniParser parser{content}; while (parser.NextSection() && parser.GetCurrentSectionName() != "menu") continue; if (parser.GetCurrentSectionName() != "menu") return nullptr; auto parsed = std::make_unique<ParsedMetaXml>(); const auto author = parser.FindOption("author"); if (author) parsed->m_publisher[(size_t)CafeConsoleLanguage::EN] = *author; const auto longName = parser.FindOption("longname"); if (longName) parsed->m_long_name[(size_t)CafeConsoleLanguage::EN] = *longName; const auto shortName = parser.FindOption("shortname"); if (shortName) parsed->m_short_name[(size_t)CafeConsoleLanguage::EN] = *shortName; auto checksumInput = std::string{*author}.append(*longName).append(*shortName); parsed->m_title_id = (0x0005000Full<<32) | crc32_calc(checksumInput.data(), checksumInput.length()); return parsed.release(); } bool TitleInfo::ParseAppXml(std::vector<uint8>& appXmlData) { pugi::xml_document app_doc; if (!app_doc.load_buffer_inplace(appXmlData.data(), appXmlData.size())) return false; const auto root = app_doc.child("app"); if (!root) return false; m_parsedAppXml = new ParsedAppXml(); for (const auto& child : root.children()) { std::string_view name = child.name(); if (name == "title_version") m_parsedAppXml->title_version = (uint16)std::stoull(child.text().as_string(), nullptr, 16); else if (name == "title_id") m_parsedAppXml->title_id = std::stoull(child.text().as_string(), nullptr, 16); else if (name == "app_type") m_parsedAppXml->app_type = (uint32)std::stoull(child.text().as_string(), nullptr, 16); else if (name == "group_id") m_parsedAppXml->group_id = (uint32)std::stoull(child.text().as_string(), nullptr, 16); else if (name == "sdk_version") m_parsedAppXml->sdk_version = (uint32)std::stoull(child.text().as_string(), nullptr, 10); } return true; } TitleId TitleInfo::GetAppTitleId() const { cemu_assert_debug(m_isValid); if (m_parsedAppXml) return m_parsedAppXml->title_id; if (m_cachedInfo) return m_cachedInfo->titleId; cemu_assert_suspicious(); return 0; } uint16 TitleInfo::GetAppTitleVersion() const { cemu_assert_debug(m_isValid); if (m_parsedAppXml) return m_parsedAppXml->title_version; if (m_cachedInfo) return m_cachedInfo->titleVersion; cemu_assert_suspicious(); return 0; } uint32 TitleInfo::GetAppSDKVersion() const { cemu_assert_debug(m_isValid); if (m_parsedAppXml) return m_parsedAppXml->sdk_version; if (m_cachedInfo) return m_cachedInfo->sdkVersion; cemu_assert_suspicious(); return 0; } uint32 TitleInfo::GetAppGroup() const { cemu_assert_debug(m_isValid); if (m_parsedAppXml) return m_parsedAppXml->group_id; if (m_cachedInfo) return m_cachedInfo->group_id; cemu_assert_suspicious(); return 0; } uint32 TitleInfo::GetAppType() const { cemu_assert_debug(m_isValid); if (m_parsedAppXml) return m_parsedAppXml->app_type; if (m_cachedInfo) return m_cachedInfo->app_type; cemu_assert_suspicious(); return 0; } TitleIdParser::TITLE_TYPE TitleInfo::GetTitleType() { TitleIdParser tip(GetAppTitleId()); return tip.GetType(); } std::string TitleInfo::GetMetaTitleName() const { cemu_assert_debug(m_isValid); if (m_parsedMetaXml) { std::string titleNameCfgLanguage; titleNameCfgLanguage = m_parsedMetaXml->GetLongName(GetConfig().console_language); if (titleNameCfgLanguage.empty()) //Get English Title titleNameCfgLanguage = m_parsedMetaXml->GetLongName(CafeConsoleLanguage::EN); if (titleNameCfgLanguage.empty()) //Unknown Title titleNameCfgLanguage = "Unknown Title"; return titleNameCfgLanguage; } if (m_cachedInfo) return m_cachedInfo->titleName; return ""; } CafeConsoleRegion TitleInfo::GetMetaRegion() const { cemu_assert_debug(m_isValid); if (m_parsedMetaXml) return m_parsedMetaXml->GetRegion(); if (m_cachedInfo) return m_cachedInfo->region; return CafeConsoleRegion::JPN; } uint32 TitleInfo::GetOlvAccesskey() const { cemu_assert_debug(m_isValid); if (m_parsedMetaXml) return m_parsedMetaXml->GetOlvAccesskey(); cemu_assert_suspicious(); return -1; } std::string TitleInfo::GetArgStr() const { cemu_assert_debug(m_parsedCosXml); if (!m_parsedCosXml) return ""; return m_parsedCosXml->argstr; } std::string TitleInfo::GetPrintPath() const { if (!m_isValid) return "invalid"; std::string tmp; tmp.append(_pathToUtf8(m_fullPath)); switch (m_titleFormat) { case TitleDataFormat::HOST_FS: tmp.append(" [Folder]"); break; case TitleDataFormat::WUD: tmp.append(" [WUD]"); break; case TitleDataFormat::NUS: tmp.append(" [NUS]"); break; case TitleDataFormat::WIIU_ARCHIVE: tmp.append(" [WUA]"); break; case TitleDataFormat::WUHB: tmp.append(" [WUHB]"); break; default: break; } if (m_titleFormat == TitleDataFormat::WIIU_ARCHIVE) tmp.append(fmt::format(" [{}]", m_subPath)); return tmp; } std::string TitleInfo::GetInstallPath() const { TitleId titleId = GetAppTitleId(); TitleIdParser tip(titleId); std::string tmp; if (tip.IsSystemTitle()) tmp = fmt::format("sys/title/{:08x}/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); else tmp = fmt::format("usr/title/{:08x}/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); return tmp; } ParsedCosXml* ParsedCosXml::Parse(uint8* xmlData, size_t xmlLen) { pugi::xml_document app_doc; if (!app_doc.load_buffer_inplace(xmlData, xmlLen)) return nullptr; const auto root = app_doc.child("app"); if (!root) return nullptr; ParsedCosXml* parsedCos = new ParsedCosXml(); auto node = root.child("argstr"); if (node) parsedCos->argstr = node.text().as_string(); // parse permissions auto permissionsNode = root.child("permissions"); for(uint32 permissionIndex = 0; permissionIndex < 19; ++permissionIndex) { std::string permissionName = fmt::format("p{}", permissionIndex); auto permissionNode = permissionsNode.child(permissionName.c_str()); if (!permissionNode) break; parsedCos->permissions[permissionIndex].group = static_cast<CosCapabilityGroup>(ConvertString<uint32>(permissionNode.child("group").text().as_string(), 10)); parsedCos->permissions[permissionIndex].mask = static_cast<CosCapabilityBits>(ConvertString<uint64>(permissionNode.child("mask").text().as_string(), 16)); } return parsedCos; } ================================================ FILE: src/Cafe/TitleList/TitleInfo.h ================================================ #pragma once #include "Cafe/Filesystem/fsc.h" #include "config/CemuConfig.h" // for CafeConsoleRegion. Move to NCrypto? #include "TitleId.h" #include "AppType.h" #include "ParsedMetaXml.h" enum class CafeTitleFileType { UNKNOWN, WUD, WUX, RPX, ELF, }; CafeTitleFileType DetermineCafeSystemFileType(fs::path filePath); struct ParsedAppXml { uint64 title_id; uint16 title_version; uint32 app_type; uint32 group_id; uint32 sdk_version; }; enum class CosCapabilityGroup : uint32 { None = 0, BSP = 1, DK = 3, USB = 9, UHS = 12, FS = 11, MCP = 13, NIM = 14, ACT = 15, FPD = 16, BOSS = 17, ACP = 18, PDM = 19, AC = 20, NDM = 21, NSEC = 22 }; enum class CosCapabilityBits : uint64 { All = 0xFFFFFFFFFFFFFFFFull }; enum class CosCapabilityBitsFS : uint64 { ODD_READ = (1llu << 0), ODD_WRITE = (1llu << 1), ODD_RAW_OPEN = (1llu << 2), ODD_MOUNT = (1llu << 3), SLCCMPT_READ = (1llu << 4), SLCCMPT_WRITE = (1llu << 5), SLCCMPT_RAW_OPEN = (1llu << 6), SLCCMPT_MOUNT = (1llu << 7), SLC_READ = (1llu << 8), SLC_WRITE = (1llu << 9), SLC_RAW_OPEN = (1llu << 10), SLC_MOUNT = (1llu << 11), MLC_READ = (1llu << 12), MLC_WRITE = (1llu << 13), MLC_RAW_OPEN = (1llu << 14), MLC_MOUNT = (1llu << 15), SDCARD_READ = (1llu << 16), SDCARD_WRITE = (1llu << 17), SDCARD_RAW_OPEN = (1llu << 18), SDCARD_MOUNT = (1llu << 19), HFIO_READ = (1llu << 20), HFIO_WRITE = (1llu << 21), HFIO_RAW_OPEN = (1llu << 22), HFIO_MOUNT = (1llu << 23), RAMDISK_READ = (1llu << 24), RAMDISK_WRITE = (1llu << 25), RAMDISK_RAW_OPEN = (1llu << 26), RAMDISK_MOUNT = (1llu << 27), USB_READ = (1llu << 28), USB_WRITE = (1llu << 29), USB_RAW_OPEN = (1llu << 30), USB_MOUNT = (1llu << 31), OTHER_READ = (1llu << 32), OTHER_WRITE = (1llu << 33), OTHER_RAW_OPEN = (1llu << 34), OTHER_MOUNT = (1llu << 35) }; ENABLE_BITMASK_OPERATORS(CosCapabilityBitsFS); struct ParsedCosXml { public: std::string argstr; struct Permission { CosCapabilityGroup group{CosCapabilityGroup::None}; CosCapabilityBits mask{CosCapabilityBits::All}; }; Permission permissions[19]{}; static ParsedCosXml* Parse(uint8* xmlData, size_t xmlLen); CosCapabilityBits GetCapabilityBits(CosCapabilityGroup group) const { for (const auto& perm : permissions) { if (perm.group == group) return perm.mask; } return CosCapabilityBits::All; } }; class TitleInfo { public: enum class TitleDataFormat { HOST_FS = 1, // host filesystem directory (fullPath points to root with content/code/meta subfolders) WUD = 2, // WUD or WUX WIIU_ARCHIVE = 3, // Wii U compressed single-file archive (.wua) NUS = 4, // NUS format. Directory with .app files, title.tik and title.tmd WUHB = 5, // error INVALID_STRUCTURE = 0, }; enum class InvalidReason : uint8 { NONE = 0, BAD_PATH_OR_INACCESSIBLE = 1, UNKNOWN_FORMAT = 2, NO_DISC_KEY = 3, NO_TITLE_TIK = 4, MISSING_XML_FILES = 4, }; struct CachedInfo { TitleDataFormat titleDataFormat; fs::path path; std::string subPath; // for WUA uint64 titleId; uint16 titleVersion; uint32 sdkVersion; std::string titleName; CafeConsoleRegion region; uint32 group_id; uint32 app_type; }; TitleInfo() : m_isValid(false) {}; TitleInfo(const fs::path& path); TitleInfo(const fs::path& path, std::string_view subPath); TitleInfo(const CachedInfo& cachedInfo); ~TitleInfo(); TitleInfo(const TitleInfo& other) { Copy(other); } TitleInfo& operator=(TitleInfo other) { Copy(other); return *this; } bool IsCached() { return m_cachedInfo; }; // returns true if this TitleInfo was loaded from cache and has not yet been parsed CachedInfo MakeCacheEntry(); bool IsValid() const; InvalidReason GetInvalidReason() const { return m_invalidReason; } uint64 GetUID(); // returns a unique identifier derived from the absolute canonical title location which can be used to identify this title by its location. May not persist across sessions, especially when Cemu is used portable fs::path GetPath() const; TitleDataFormat GetFormat() const { return m_titleFormat; }; bool Mount(std::string_view virtualPath, std::string_view subfolder, sint32 mountPriority); void Unmount(std::string_view virtualPath); void UnmountAll(); bool IsMounted() const { return !m_mountpoints.empty(); } bool ParseXmlInfo(); bool HasValidXmlInfo() const { return m_parsedMetaXml && m_parsedAppXml && m_parsedCosXml; }; bool IsEqualByLocation(const TitleInfo& rhs) const { return m_uid == rhs.m_uid; } bool IsSystemDataTitle() const { if(!IsValid()) return false; uint32 appType = GetAppType(); return appType == APP_TYPE::DRC_FIRMWARE || appType == APP_TYPE::DRC_TEXTURE_ATLAS || appType == APP_TYPE::VERSION_DATA_TITLE; } // API which requires parsed meta data or cached info TitleId GetAppTitleId() const; // from app.xml uint16 GetAppTitleVersion() const; // from app.xml uint32 GetAppSDKVersion() const; // from app.xml uint32 GetAppGroup() const; // from app.xml uint32 GetAppType() const; // from app.xml std::string GetMetaTitleName() const; // from meta.xml CafeConsoleRegion GetMetaRegion() const; // from meta.xml uint32 GetOlvAccesskey() const; // cos.xml std::string GetArgStr() const; // meta.xml also contains a version field which seems to match the one from app.xml // the titleId in meta.xml seems to be the title id of the base game for updates specifically. For AOC content it's the AOC's titleId TitleIdParser::TITLE_TYPE GetTitleType(); ParsedMetaXml* GetMetaInfo() { return m_parsedMetaXml; } ParsedCosXml* GetCosInfo() { return m_parsedCosXml; } std::string GetPrintPath() const; // formatted path including type and WUA subpath. Intended for logging and user-facing information std::string GetInstallPath() const; // installation subpath, relative to storage base. E.g. "usr/title/.../..." or "sys/title/.../..." static std::string GetUniqueTempMountingPath(); static bool ParseWuaTitleFolderName(std::string_view name, TitleId& titleIdOut, uint16& titleVersionOut); private: void Copy(const TitleInfo& other) { m_isValid = other.m_isValid; m_titleFormat = other.m_titleFormat; m_fullPath = other.m_fullPath; m_subPath = other.m_subPath; m_hasParsedXmlFiles = other.m_hasParsedXmlFiles; m_parsedMetaXml = nullptr; m_parsedAppXml = nullptr; if (other.m_parsedMetaXml) m_parsedMetaXml = new ParsedMetaXml(*other.m_parsedMetaXml); if (other.m_parsedAppXml) m_parsedAppXml = new ParsedAppXml(*other.m_parsedAppXml); if (other.m_parsedCosXml) m_parsedCosXml = new ParsedCosXml(*other.m_parsedCosXml); if (other.m_cachedInfo) m_cachedInfo = new CachedInfo(*other.m_cachedInfo); m_mountpoints.clear(); m_wudVolume = nullptr; } bool DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut); void CalcUID(); void SetInvalidReason(InvalidReason reason); ParsedMetaXml* ParseAromaIni(std::span<unsigned char> content); bool ParseAppXml(std::vector<uint8>& appXmlData); bool m_isValid{ false }; TitleDataFormat m_titleFormat{ TitleDataFormat::INVALID_STRUCTURE }; fs::path m_fullPath; std::string m_subPath; // used for formats where fullPath isn't unique on its own (like WUA) uint64 m_uid{}; InvalidReason m_invalidReason{ InvalidReason::NONE }; // if m_isValid == false, this contains a more detailed error code // mounting info std::vector<std::pair<sint32, std::string>> m_mountpoints; class FSTVolume* m_wudVolume{}; class ZArchiveReader* m_zarchive{}; class WUHBReader* m_wuhbreader{}; // xml info bool m_hasParsedXmlFiles{ false }; ParsedMetaXml* m_parsedMetaXml{}; ParsedAppXml* m_parsedAppXml{}; ParsedCosXml* m_parsedCosXml{}; // cached info if called with cache constructor CachedInfo* m_cachedInfo{nullptr}; }; ================================================ FILE: src/Cafe/TitleList/TitleList.cpp ================================================ #include "TitleList.h" #include "Common/FileStream.h" #include "util/helpers/helpers.h" #include <zarchive/zarchivereader.h> bool sTLInitialized{ false }; fs::path sTLCacheFilePath; // lists for tracking known titles // note: The list may only contain titles with valid meta data (except for certain system titles). Entries loaded from the cache may not have been parsed yet, but they will use a cached value for titleId and titleVersion std::mutex sTLMutex; std::vector<TitleInfo*> sTLList; std::vector<TitleInfo*> sTLListPending; std::unordered_multimap<uint64, TitleInfo*> sTLMap; bool sTLCacheDirty{false}; // paths fs::path sTLMLCPath; std::vector<fs::path> sTLScanPaths; // worker std::thread sTLRefreshWorker; bool sTLRefreshWorkerActive{false}; std::atomic_uint32_t sTLRefreshRequests{}; std::atomic_bool sTLIsScanMandatory{ false }; // callback list struct TitleListCallbackEntry { TitleListCallbackEntry(void(*cb)(CafeTitleListCallbackEvent* evt, void* ctx), void* ctx, uint64 uniqueId) : cb(cb), ctx(ctx), uniqueId(uniqueId) {}; void (*cb)(CafeTitleListCallbackEvent* evt, void* ctx); void* ctx; uint64 uniqueId; }; std::vector<TitleListCallbackEntry> sTLCallbackList; void CafeTitleList::Initialize(const fs::path cacheXmlFile) { std::unique_lock _lock(sTLMutex); sTLInitialized = true; sTLCacheFilePath = cacheXmlFile; LoadCacheFile(); } void CafeTitleList::LoadCacheFile() { sTLIsScanMandatory = true; cemu_assert_debug(sTLInitialized); cemu_assert_debug(sTLList.empty()); auto xmlData = FileStream::LoadIntoMemory(sTLCacheFilePath); if (!xmlData) return; pugi::xml_document doc; if (!doc.load_buffer_inplace(xmlData->data(), xmlData->size(), pugi::parse_default, pugi::xml_encoding::encoding_utf8)) return; auto titleListNode = doc.child("title_list"); pugi::xml_node itNode = titleListNode.first_child(); for (const auto& titleInfoNode : doc.child("title_list")) { TitleId titleId; if( !TitleIdParser::ParseFromStr(titleInfoNode.attribute("titleId").as_string(), titleId)) continue; uint16 titleVersion = titleInfoNode.attribute("version").as_uint(); uint32 sdkVersion = titleInfoNode.attribute("sdk_version").as_uint(); TitleInfo::TitleDataFormat format = (TitleInfo::TitleDataFormat)ConvertString<uint32>(titleInfoNode.child_value("format")); CafeConsoleRegion region = (CafeConsoleRegion)ConvertString<uint32>(titleInfoNode.child_value("region")); std::string name = titleInfoNode.child_value("name"); std::string path = titleInfoNode.child_value("path"); std::string sub_path = titleInfoNode.child_value("sub_path"); uint32 group_id = ConvertString<uint32>(titleInfoNode.attribute("group_id").as_string(), 16); uint32 app_type = ConvertString<uint32>(titleInfoNode.attribute("app_type").as_string(), 16); TitleInfo::CachedInfo cacheEntry; cacheEntry.titleId = titleId; cacheEntry.titleVersion = titleVersion; cacheEntry.sdkVersion = sdkVersion; cacheEntry.titleDataFormat = format; cacheEntry.region = region; cacheEntry.titleName = std::move(name); cacheEntry.path = _utf8ToPath(path); cacheEntry.subPath = std::move(sub_path); cacheEntry.group_id = group_id; cacheEntry.app_type = app_type; TitleInfo* ti = new TitleInfo(cacheEntry); if (!ti->IsValid()) { cemuLog_log(LogType::Force, "Title cache contained invalid title"); delete ti; continue; } AddTitle(ti); } sTLIsScanMandatory = false; } void CafeTitleList::StoreCacheFile() { cemu_assert_debug(sTLInitialized); if (sTLCacheFilePath.empty()) return; std::unique_lock _lock(sTLMutex); pugi::xml_document doc; auto declarationNode = doc.append_child(pugi::node_declaration); declarationNode.append_attribute("version") = "1.0"; declarationNode.append_attribute("encoding") = "UTF-8"; auto title_list_node = doc.append_child("title_list"); for (auto& tiIt : sTLList) { TitleInfo::CachedInfo info = tiIt->MakeCacheEntry(); auto titleInfoNode = title_list_node.append_child("title"); titleInfoNode.append_attribute("titleId").set_value(fmt::format("{:016x}", info.titleId).c_str()); titleInfoNode.append_attribute("version").set_value(fmt::format("{:}", info.titleVersion).c_str()); titleInfoNode.append_attribute("sdk_version").set_value(fmt::format("{:}", info.sdkVersion).c_str()); titleInfoNode.append_attribute("group_id").set_value(fmt::format("{:08x}", info.group_id).c_str()); titleInfoNode.append_attribute("app_type").set_value(fmt::format("{:08x}", info.app_type).c_str()); titleInfoNode.append_child("region").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (uint32)info.region).c_str()); titleInfoNode.append_child("name").append_child(pugi::node_pcdata).set_value(info.titleName.c_str()); titleInfoNode.append_child("format").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (uint32)info.titleDataFormat).c_str()); titleInfoNode.append_child("path").append_child(pugi::node_pcdata).set_value(_pathToUtf8(info.path).c_str()); if(!info.subPath.empty()) titleInfoNode.append_child("sub_path").append_child(pugi::node_pcdata).set_value(_pathToUtf8(info.subPath).c_str()); } fs::path tmpPath = fs::path(sTLCacheFilePath.parent_path()).append(fmt::format("{}__tmp", _pathToUtf8(sTLCacheFilePath.filename()))); std::ofstream fileOut(tmpPath, std::ios::out | std::ios::binary | std::ios::trunc); if (!fileOut.is_open()) { cemuLog_log(LogType::Force, "Unable to store title list in {}", _pathToUtf8(tmpPath)); return; } doc.save(fileOut, " ", 1, pugi::xml_encoding::encoding_utf8); fileOut.flush(); fileOut.close(); std::error_code ec; fs::rename(tmpPath, sTLCacheFilePath, ec); } void CafeTitleList::ClearScanPaths() { std::unique_lock _lock(sTLMutex); sTLScanPaths.clear(); } void CafeTitleList::AddScanPath(fs::path path) { std::unique_lock _lock(sTLMutex); sTLScanPaths.emplace_back(path); } void CafeTitleList::SetMLCPath(fs::path path) { std::unique_lock _lock(sTLMutex); std::error_code ec; if (!fs::is_directory(path, ec)) { cemuLog_log(LogType::Force, "MLC set to invalid path: {}", _pathToUtf8(path)); return; } sTLMLCPath = path; } void CafeTitleList::Refresh() { std::unique_lock _lock(sTLMutex); cemu_assert_debug(sTLInitialized); sTLRefreshRequests++; if (!sTLRefreshWorkerActive) { if (sTLRefreshWorker.joinable()) sTLRefreshWorker.join(); sTLRefreshWorkerActive = true; sTLRefreshWorker = std::thread(RefreshWorkerThread); } sTLIsScanMandatory = false; } bool CafeTitleList::IsScanning() { std::unique_lock _lock(sTLMutex); return sTLRefreshWorkerActive; } void CafeTitleList::WaitForMandatoryScan() { if (!sTLIsScanMandatory) return; while (IsScanning()) std::this_thread::sleep_for(std::chrono::milliseconds(100)); } void _RemoveTitleFromMultimap(TitleInfo* titleInfo) { auto mapRange = sTLMap.equal_range(titleInfo->GetAppTitleId()); for (auto mapIt = mapRange.first; mapIt != mapRange.second; ++mapIt) { if (mapIt->second == titleInfo) { sTLMap.erase(mapIt); return; } } cemu_assert_suspicious(); } // check if path is a valid title and if it is, permanently add it to the title list // in the special case that path points to a WUA file, all contained titles will be added void CafeTitleList::AddTitleFromPath(fs::path path) { if (path.has_extension() && boost::iequals(_pathToUtf8(path.extension()), ".wua")) { ZArchiveReader* zar = ZArchiveReader::OpenFromFile(path); if (!zar) { cemuLog_log(LogType::Force, "Found {} but it is not a valid Wii U archive file", _pathToUtf8(path)); return; } // enumerate all contained titles ZArchiveNodeHandle rootDir = zar->LookUp("", false, true); cemu_assert(rootDir != ZARCHIVE_INVALID_NODE); for (uint32 i = 0; i < zar->GetDirEntryCount(rootDir); i++) { ZArchiveReader::DirEntry dirEntry; if( !zar->GetDirEntry(rootDir, i, dirEntry) ) continue; if(!dirEntry.isDirectory) continue; TitleId parsedId; uint16 parsedVersion; if (!TitleInfo::ParseWuaTitleFolderName(dirEntry.name, parsedId, parsedVersion)) { cemuLog_log(LogType::Force, "Invalid title directory in {}: \"{}\"", _pathToUtf8(path), dirEntry.name); continue; } // valid subdirectory TitleInfo* titleInfo = new TitleInfo(path, dirEntry.name); if (titleInfo->IsValid()) AddDiscoveredTitle(titleInfo); else delete titleInfo; } delete zar; return; } TitleInfo* titleInfo = new TitleInfo(path); if (titleInfo->IsValid()) AddDiscoveredTitle(titleInfo); else delete titleInfo; } bool CafeTitleList::RefreshWorkerThread() { SetThreadName("TitleListWorker"); while (sTLRefreshRequests.load()) { sTLRefreshRequests.store(0); // create copies of all the paths sTLMutex.lock(); fs::path mlcPath = sTLMLCPath; std::vector<fs::path> gamePaths = sTLScanPaths; // remember the current list of known titles // during the scanning process we will erase matches from the pending list // at the end of scanning, we can then use this list to identify and remove any titles that are no longer discoverable sTLListPending = sTLList; sTLMutex.unlock(); // scan game paths for (auto& it : gamePaths) ScanGamePath(it); // scan MLC if (!mlcPath.empty()) { std::error_code ec; for (auto& it : fs::directory_iterator(mlcPath / "usr/title", ec)) { if (!it.is_directory(ec)) continue; ScanMLCPath(it.path()); } ScanMLCPath(mlcPath / "sys/title/00050010"); ScanMLCPath(mlcPath / "sys/title/00050030"); } // remove any titles that are still pending for (auto& itPending : sTLListPending) { _RemoveTitleFromMultimap(itPending); std::erase(sTLList, itPending); } // send notifications for removed titles, but only if there exists no other title with the same titleId and version if (!sTLListPending.empty()) sTLCacheDirty = true; for (auto& itPending : sTLListPending) { CafeTitleListCallbackEvent evt; evt.eventType = CafeTitleListCallbackEvent::TYPE::TITLE_REMOVED; evt.titleInfo = itPending; for (auto& it : sTLCallbackList) it.cb(&evt, it.ctx); delete itPending; } sTLListPending.clear(); } sTLMutex.lock(); sTLRefreshWorkerActive = false; // send notification that scanning finished CafeTitleListCallbackEvent evt; evt.eventType = CafeTitleListCallbackEvent::TYPE::SCAN_FINISHED; evt.titleInfo = nullptr; for (auto& it : sTLCallbackList) it.cb(&evt, it.ctx); sTLMutex.unlock(); if (sTLCacheDirty) { StoreCacheFile(); sTLCacheDirty = false; } return true; } bool _IsKnownFileNameOrExtension(const fs::path& path) { std::string fileExtension = _pathToUtf8(path.extension()); for (auto& it : fileExtension) it = _ansiToLower(it); if(fileExtension == ".tmd") { // must be "title.tmd" std::string fileName = _pathToUtf8(path.filename()); for (auto& it : fileName) it = _ansiToLower(it); return fileName == "title.tmd"; } return fileExtension == ".wud" || fileExtension == ".wux" || fileExtension == ".iso" || fileExtension == ".wua" || fileExtension == ".wuhb"; // note: To detect extracted titles with RPX we rely on the presence of the content,code,meta directory structure } void CafeTitleList::ScanGamePath(const fs::path& path) { // scan the whole directory first to determine if this is a title folder std::vector<fs::path> filesInDirectory; std::vector<fs::path> dirsInDirectory; bool hasContentFolder = false, hasCodeFolder = false, hasMetaFolder = false; std::error_code ec; for (auto& it : fs::directory_iterator(path, ec)) { if (it.is_regular_file(ec)) { filesInDirectory.emplace_back(it.path()); } else if (it.is_directory(ec)) { dirsInDirectory.emplace_back(it.path()); std::string dirName = _pathToUtf8(it.path().filename()); if (boost::iequals(dirName, "content")) hasContentFolder = true; else if (boost::iequals(dirName, "code")) hasCodeFolder = true; else if (boost::iequals(dirName, "meta")) hasMetaFolder = true; } } // always check individual files for (auto& it : filesInDirectory) { // since checking individual files is slow, we limit it to known file names or extensions if (!it.has_extension()) continue; if (!_IsKnownFileNameOrExtension(it)) continue; AddTitleFromPath(it); } // is the current directory a title folder? if (hasContentFolder && hasCodeFolder && hasMetaFolder) { // verify if this folder is a valid title TitleInfo* titleInfo = new TitleInfo(path); if (titleInfo->IsValid()) AddDiscoveredTitle(titleInfo); else delete titleInfo; // if there are other folders besides content/code/meta then traverse those if (dirsInDirectory.size() > 3) { for (auto& it : dirsInDirectory) { std::string dirName = _pathToUtf8(it.filename()); if (!boost::iequals(dirName, "content") && !boost::iequals(dirName, "code") && !boost::iequals(dirName, "meta")) ScanGamePath(it); } } } else { // scan subdirectories for (auto& it : dirsInDirectory) ScanGamePath(it); } } void CafeTitleList::ScanMLCPath(const fs::path& path) { std::error_code ec; for (auto& it : fs::directory_iterator(path, ec)) { if (!it.is_directory()) continue; // only scan directories which match the title id naming scheme std::string dirName = _pathToUtf8(it.path().filename()); if(dirName.size() != 8) continue; bool containsNoHexCharacter = false; for (auto& it : dirName) { if(it >= 'A' && it <= 'F' || it >= 'a' && it <= 'f' || it >= '0' && it <= '9') continue; containsNoHexCharacter = true; break; } if(containsNoHexCharacter) continue; if (fs::is_directory(it.path() / "code", ec) && fs::is_directory(it.path() / "content", ec) && fs::is_directory(it.path() / "meta", ec)) { TitleInfo* titleInfo = new TitleInfo(it); if (titleInfo->IsValid() && titleInfo->ParseXmlInfo()) AddDiscoveredTitle(titleInfo); else delete titleInfo; } } } void CafeTitleList::AddDiscoveredTitle(TitleInfo* titleInfo) { cemu_assert_debug(titleInfo->ParseXmlInfo()); std::unique_lock _lock(sTLMutex); // remove from pending list auto pendingIt = std::find_if(sTLListPending.begin(), sTLListPending.end(), [titleInfo](const TitleInfo* it) { return it->IsEqualByLocation(*titleInfo); }); if (pendingIt != sTLListPending.end()) sTLListPending.erase(pendingIt); AddTitle(titleInfo); } void CafeTitleList::AddTitle(TitleInfo* titleInfo) { // check if title is already known if (titleInfo->IsCached()) { bool isKnown = std::any_of(sTLList.cbegin(), sTLList.cend(), [&titleInfo](const TitleInfo* ti) { return titleInfo->IsEqualByLocation(*ti); }); if (isKnown) { delete titleInfo; return; } } else { auto it = std::find_if(sTLList.begin(), sTLList.end(), [titleInfo](const TitleInfo* it) { return it->IsEqualByLocation(*titleInfo); }); if (it != sTLList.end()) { if ((*it)->IsCached()) { // replace cached entry with newly parsed title TitleInfo* deletedInfo = *it; sTLList.erase(it); _RemoveTitleFromMultimap(deletedInfo); delete deletedInfo; } else { // title already known delete titleInfo; return; } } } sTLList.emplace_back(titleInfo); sTLMap.emplace(titleInfo->GetAppTitleId(), titleInfo); // send out notification CafeTitleListCallbackEvent evt; evt.eventType = CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED; evt.titleInfo = titleInfo; for (auto& it : sTLCallbackList) it.cb(&evt, it.ctx); sTLCacheDirty = true; } uint64 CafeTitleList::RegisterCallback(void(*cb)(CafeTitleListCallbackEvent* evt, void* ctx), void* ctx) { static std::atomic<uint64_t> sCallbackIdGen = 1; uint64 id = sCallbackIdGen.fetch_add(1); std::unique_lock _lock(sTLMutex); sTLCallbackList.emplace_back(cb, ctx, id); // immediately notify of all known titles for (auto& it : sTLList) { CafeTitleListCallbackEvent evt; evt.eventType = CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED; evt.titleInfo = it; cb(&evt, ctx); } // if not scanning then send out scan finished notification if (!sTLRefreshWorkerActive) { CafeTitleListCallbackEvent evt; evt.eventType = CafeTitleListCallbackEvent::TYPE::SCAN_FINISHED; evt.titleInfo = nullptr; for (auto& it : sTLCallbackList) it.cb(&evt, it.ctx); } return id; } void CafeTitleList::UnregisterCallback(uint64 id) { std::unique_lock _lock(sTLMutex); auto it = std::find_if(sTLCallbackList.begin(), sTLCallbackList.end(), [id](auto& e) { return e.uniqueId == id; }); cemu_assert(it != sTLCallbackList.end()); // must be a valid callback sTLCallbackList.erase(it); } bool CafeTitleList::HasTitle(TitleId titleId, uint16& versionOut) { // todo - optimize? bool matchFound = false; versionOut = 0; std::unique_lock _lock(sTLMutex); for (auto& it : sTLList) { if (it->GetAppTitleId() == titleId) { uint16 titleVersion = it->GetAppTitleVersion(); if (titleVersion > versionOut) versionOut = titleVersion; matchFound = true; } } return matchFound; } bool CafeTitleList::HasTitleAndVersion(TitleId titleId, uint16 version) { std::unique_lock _lock(sTLMutex); for (auto& it : sTLList) { if (it->GetAppTitleId() == titleId && it->GetAppTitleVersion() == version) return true; } return false; } std::vector<TitleId> CafeTitleList::GetAllTitleIds() { std::unordered_set<TitleId> visitedTitleIds; std::unique_lock _lock(sTLMutex); std::vector<TitleId> titleIds; titleIds.reserve(sTLList.size()); for (auto& it : sTLList) { TitleId tid = it->GetAppTitleId(); if (visitedTitleIds.find(tid) != visitedTitleIds.end()) continue; titleIds.emplace_back(tid); visitedTitleIds.emplace(tid); } return titleIds; } std::span<TitleInfo*> CafeTitleList::AcquireInternalList() { sTLMutex.lock(); return { sTLList.data(), sTLList.size() }; } void CafeTitleList::ReleaseInternalList() { sTLMutex.unlock(); } bool CafeTitleList::GetFirstByTitleId(TitleId titleId, TitleInfo& titleInfoOut) { std::unique_lock _lock(sTLMutex); auto it = sTLMap.find(titleId); if (it != sTLMap.end()) { cemu_assert_debug(it->first == titleId); titleInfoOut = *it->second; return true; } return false; } // takes update or AOC title id and returns the title id of the associated base title // this can fail if trying to translate an AOC title id without having the base title meta information bool CafeTitleList::FindBaseTitleId(TitleId titleId, TitleId& titleIdBaseOut) { titleId = TitleIdParser::MakeBaseTitleId(titleId); // aoc to base // todo - this requires scanning all base titles and their updates to see if they reference this title id // for now we assume there is a direct match of ids if (((titleId >> 32) & 0xFF) == 0x0C) { titleId &= ~0xFF00000000; titleId |= 0x0000000000; } titleIdBaseOut = titleId; return true; } GameInfo2 CafeTitleList::GetGameInfo(TitleId titleId) { GameInfo2 gameInfo; // find base title id uint64 baseTitleId; if (!FindBaseTitleId(titleId, baseTitleId)) { cemu_assert_suspicious(); } // determine if an optional update title id exists TitleIdParser tip(baseTitleId); bool hasSeparateUpdateTitleId = tip.CanHaveSeparateUpdateTitleId(); uint64 updateTitleId = 0; if (hasSeparateUpdateTitleId) updateTitleId = tip.GetSeparateUpdateTitleId(); // scan the title list for base and update std::unique_lock _lock(sTLMutex); for (auto& it : sTLList) { TitleId appTitleId = it->GetAppTitleId(); if (appTitleId == baseTitleId) { gameInfo.SetBase(*it); } if (hasSeparateUpdateTitleId && appTitleId == updateTitleId) { gameInfo.SetUpdate(*it); } } // if this title can have AOC content then do a second scan // todo - get a list of all AOC title ids from the base/update meta information // for now we assume there is a direct match between the base titleId and the aoc titleId if (tip.CanHaveSeparateUpdateTitleId()) { uint64 aocTitleId = baseTitleId | 0xC00000000; for (auto& it : sTLList) { TitleId appTitleId = it->GetAppTitleId(); if (appTitleId == aocTitleId) { gameInfo.AddAOC(*it); // stores the AOC with the highest title version } } } return gameInfo; } TitleInfo CafeTitleList::GetTitleInfoByUID(uint64 uid) { TitleInfo titleInfo; std::unique_lock _lock(sTLMutex); for (auto& it : sTLList) { if (it->GetUID() == uid) { titleInfo = *it; break; } } return titleInfo; } ================================================ FILE: src/Cafe/TitleList/TitleList.h ================================================ #pragma once #include "TitleInfo.h" #include "GameInfo.h" struct CafeTitleListCallbackEvent { enum class TYPE { TITLE_DISCOVERED, TITLE_REMOVED, SCAN_FINISHED, }; TYPE eventType; TitleInfo* titleInfo; }; class CafeTitleList { public: static void Initialize(const fs::path cacheXmlFile); static void LoadCacheFile(); static void StoreCacheFile(); static void ClearScanPaths(); static void AddScanPath(fs::path path); static void SetMLCPath(fs::path path); static void Refresh(); // scan all paths static bool IsScanning(); // returns true if async refresh is currently active static void WaitForMandatoryScan(); // wait for current scan result if no cached info is available static void AddTitleFromPath(fs::path path); static uint64 RegisterCallback(void(*cb)(CafeTitleListCallbackEvent* evt, void* ctx), void* ctx); // on register, the callback will be invoked for every already known title static void UnregisterCallback(uint64 id); // utility functions static bool HasTitle(TitleId titleId, uint16& versionOut); static bool HasTitleAndVersion(TitleId titleId, uint16 version); static std::vector<TitleId> GetAllTitleIds(); static bool GetFirstByTitleId(TitleId titleId, TitleInfo& titleInfoOut); static bool FindBaseTitleId(TitleId titleId, TitleId& titleIdBaseOut); static std::span<TitleInfo*> AcquireInternalList(); static void ReleaseInternalList(); static GameInfo2 GetGameInfo(TitleId titleId); static TitleInfo GetTitleInfoByUID(uint64 uid); private: static bool RefreshWorkerThread(); static void ScanGamePath(const fs::path& path); static void ScanMLCPath(const fs::path& path); static void AddDiscoveredTitle(TitleInfo* titleInfo); static void AddTitle(TitleInfo* titleInfo); }; ================================================ FILE: src/Cemu/CMakeLists.txt ================================================ add_library(CemuComponents ExpressionParser/ExpressionParser.cpp ExpressionParser/ExpressionParser.h FileCache/FileCache.cpp FileCache/FileCache.h Logging/CemuDebugLogging.h Logging/CemuLogging.cpp Logging/CemuLogging.h napi/napi_act.cpp napi/napi_ec.cpp napi/napi.h napi/napi_helper.cpp napi/napi_helper.h napi/napi_idbe.cpp napi/napi_version.cpp ncrypto/ncrypto.cpp ncrypto/ncrypto.h nex/nex.cpp nex/nexFriends.cpp nex/nexFriends.h nex/nex.h nex/nexThread.cpp nex/nexThread.h nex/nexTypes.h nex/prudp.cpp nex/prudp.h PPCAssembler/ppcAssembler.cpp PPCAssembler/ppcAssembler.h Tools/DownloadManager/DownloadManager.cpp Tools/DownloadManager/DownloadManager.h ) if(ENABLE_DISCORD_RPC) target_sources(CemuComponents PRIVATE DiscordPresence/DiscordPresence.cpp DiscordPresence/DiscordPresence.h DiscordPresence/DiscordRPCLite.cpp DiscordPresence/DiscordRPCLite.h) endif() set_property(TARGET CemuComponents PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") target_include_directories(CemuComponents PUBLIC "../") target_link_libraries(CemuComponents PRIVATE CemuCommon CemuGui ) # PUBLIC because fmt/format.h is included in ExpressionParser/ExpressionParser.h target_link_libraries(CemuComponents PUBLIC fmt::fmt) ================================================ FILE: src/Cemu/DiscordPresence/DiscordPresence.cpp ================================================ #include "DiscordPresence.h" #include "Common/version.h" #include "DiscordRPCLite.h" DiscordPresence::DiscordPresence() { m_rpcClient = new DiscordRPCLite("460807638964371468"); UpdatePresence(Idling); } DiscordPresence::~DiscordPresence() { ClearPresence(); if (m_rpcClient) delete m_rpcClient; } void DiscordPresence::UpdatePresence(State state, const std::string& text) const { if (!m_rpcClient) return; DiscordLiteRichPresence discordPresence{}; std::string stateString, detailsString; switch (state) { case Idling: detailsString = "Idling"; stateString = ""; break; case Playing: detailsString = "Ingame"; stateString = "Playing " + text; break; default: assert(false); break; } discordPresence.details = detailsString; discordPresence.state = stateString; discordPresence.startTimestamp = time(nullptr); discordPresence.largeImageText = BUILD_VERSION_WITH_NAME_STRING; discordPresence.largeImageKey = "logo_icon_big_png"; m_rpcClient->UpdateRichPresence(discordPresence); } void DiscordPresence::ClearPresence() const { if (!m_rpcClient) return; m_rpcClient->ClearRichPresence(); } ================================================ FILE: src/Cemu/DiscordPresence/DiscordPresence.h ================================================ #pragma once class DiscordPresence { public: enum State { Idling, Playing, }; DiscordPresence(); ~DiscordPresence(); void UpdatePresence(State state, const std::string& text = {}) const; void ClearPresence() const; private: class DiscordRPCLite* m_rpcClient = nullptr; }; ================================================ FILE: src/Cemu/DiscordPresence/DiscordRPCLite.cpp ================================================ #include "DiscordRPCLite.h" #include <string_view> #include <optional> #include <chrono> #if defined(__cpp_lib_format) #include <format> namespace shim { using std::format; } #else #include <fmt/format.h> namespace shim { using fmt::format; } #endif #ifdef _WIN32 #include <windows.h> #else #include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #endif enum RPCOpcode : uint32_t { OpcodeHandshake = 0, OpcodeFrame = 1, OpcodeClose = 2, OpcodePing = 3, OpcodePong = 4, }; #ifdef _WIN32 class NamedPipeImpl { public: NamedPipeImpl() { m_readEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); } ~NamedPipeImpl() { CloseHandle(m_readEvent); ClosePipe(); } bool OpenPipe() { std::string fullPipeName = R"(\\.\pipe\discord-ipc-0)"; m_handle = CreateFileA(fullPipeName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr); if (m_handle == INVALID_HANDLE_VALUE) return false; return true; } void ClosePipe() { if (m_handle != INVALID_HANDLE_VALUE) { CloseHandle(m_handle); m_handle = INVALID_HANDLE_VALUE; } } bool IsOpen() const { return m_handle != INVALID_HANDLE_VALUE; } int Read(uint8_t* buffer, size_t maxSize, int timeoutMs) { if (m_handle == INVALID_HANDLE_VALUE || m_readEvent == nullptr) return -1; OVERLAPPED overlapped = {0}; overlapped.hEvent = m_readEvent; ResetEvent(m_readEvent); DWORD bytesRead = 0; BOOL success = ReadFile(m_handle, buffer, static_cast<DWORD>(maxSize), nullptr, &overlapped); if (!success && GetLastError() == ERROR_IO_PENDING) { DWORD waitResult = WaitForSingleObject(m_readEvent, timeoutMs); if (waitResult == WAIT_TIMEOUT) { CancelIo(m_handle); return 0; } else if (waitResult != WAIT_OBJECT_0) // error { ClosePipe(); return -1; } if (!GetOverlappedResult(m_handle, &overlapped, &bytesRead, FALSE)) { ClosePipe(); return -1; } } else if (!success) { ClosePipe(); return -1; } else { GetOverlappedResult(m_handle, &overlapped, &bytesRead, FALSE); // immediate completion } return static_cast<int>(bytesRead); } int Write(const uint8_t* buffer, size_t size) { if (m_handle == INVALID_HANDLE_VALUE) { return -1; } DWORD bytesWritten = 0; if (!WriteFile(m_handle, buffer, static_cast<DWORD>(size), &bytesWritten, nullptr)) { ClosePipe(); return -1; } return static_cast<int>(bytesWritten); } private: HANDLE m_handle = INVALID_HANDLE_VALUE; HANDLE m_readEvent; }; #else class NamedPipeImpl // posix { public: NamedPipeImpl() {} ~NamedPipeImpl() { ClosePipe(); } bool OpenPipe() { const char* tempPath = getenv("XDG_RUNTIME_DIR"); if (!tempPath) tempPath = getenv("TMPDIR"); if (!tempPath) tempPath = getenv("TMP"); if (!tempPath) tempPath = getenv("TEMP"); if (!tempPath) tempPath = "/tmp"; sockaddr_un pipeAddr{}; pipeAddr.sun_family = AF_UNIX; m_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (m_fd == -1) return false; fcntl(m_fd, F_SETFL, O_NONBLOCK); #ifdef SO_NOSIGPIPE int optval = 1; setsockopt(m_fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)); #endif for (int pipeIndex = 0; pipeIndex < 10; pipeIndex++) { snprintf(pipeAddr.sun_path, sizeof(pipeAddr.sun_path), "%s/discord-ipc-%d", tempPath, pipeIndex); int r = connect(m_fd, (const sockaddr*)&pipeAddr, sizeof(pipeAddr)); if (r == 0) return true; } ClosePipe(); return false; } void ClosePipe() { if (m_fd != -1) { close(m_fd); m_fd = -1; } } bool IsOpen() const { return m_fd != -1; } int Read(uint8_t* buffer, size_t maxSize, int timeoutMs) { if (m_fd == -1) return -1; if (!buffer || maxSize == 0) return -1; struct pollfd pfd; pfd.fd = m_fd; pfd.events = POLLIN; pfd.revents = 0; int ret = poll(&pfd, 1, timeoutMs); if (ret < 0) { ClosePipe(); return -1; } else if (ret == 0) { return 0; } else if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { ClosePipe(); return -1; } #ifdef MSG_NOSIGNAL int MsgFlags = MSG_NOSIGNAL; #else int MsgFlags = 0; #endif int res = (int)recv(m_fd, buffer, maxSize, MsgFlags); if (res <= 0) { ClosePipe(); return -1; } return res; } int Write(const uint8_t* buffer, size_t size) { if (m_fd == -1 || !buffer || size == 0) return -1; #ifdef MSG_NOSIGNAL int MsgFlags = MSG_NOSIGNAL; #else int MsgFlags = 0; #endif int sentBytes = send(m_fd, buffer, size, MsgFlags); if (sentBytes == -1) { ClosePipe(); return -1; } return sentBytes; } private: int m_fd = -1; }; #endif #ifdef _WIN32 int GetProcessId() { return (int)GetCurrentProcessId(); } #else int GetProcessId() { return getpid(); } #endif class NamedPipe : public NamedPipeImpl { public: NamedPipe() : NamedPipeImpl() {}; void SendJsonMsg(uint32_t opcode, std::string_view jsonText) { std::vector<uint8_t> buffer; buffer.resize(8 + jsonText.size()); buffer[0] = (opcode >> 0) & 0xFF; buffer[1] = (opcode >> 8) & 0xFF; buffer[2] = (opcode >> 16) & 0xFF; buffer[3] = (opcode >> 24) & 0xFF; uint32_t length = static_cast<uint32_t>(jsonText.size()); buffer[4] = (length >> 0) & 0xFF; buffer[5] = (length >> 8) & 0xFF; buffer[6] = (length >> 16) & 0xFF; buffer[7] = (length >> 24) & 0xFF; std::copy(jsonText.begin(), jsonText.end(), buffer.begin() + 8); Write(buffer.data(), buffer.size()); } void SendPresenceUpdate(const DiscordLiteRichPresence& presence) { // note: According to older documentation SET_ACTIVITY has a rate limit of 15 seconds std::string jsonPayload; jsonPayload.append("{"); jsonPayload.append(R"("cmd": "SET_ACTIVITY")"); jsonPayload.append(shim::format(R"(,"nonce": "{}")", GetNonceStr())); jsonPayload.append(R"(,"args": {)"); // args std::string pidStr = shim::format("{}", GetProcessId()); jsonPayload.append(R"("pid": )").append(pidStr).append(R"(,)"); // args->activity if (!presence.state.empty() || !presence.details.empty()) { jsonPayload.append(R"("activity": {)"); if (!presence.state.empty()) jsonPayload.append(shim::format(R"("state": "{}",)", presence.state)); if (!presence.details.empty()) jsonPayload.append(shim::format(R"("details": "{}",)", presence.details)); if (presence.startTimestamp || presence.endTimestamp) { jsonPayload.append(R"("timestamps": {)"); if (presence.startTimestamp) jsonPayload.append(shim::format(R"("start": {},)", presence.startTimestamp)); if (presence.endTimestamp) jsonPayload.append(shim::format(R"("end": {},)", presence.endTimestamp)); if (jsonPayload.back() == ',') jsonPayload.pop_back(); jsonPayload.append("},"); } if (!presence.largeImageKey.empty() && !presence.largeImageText.empty() && !presence.smallImageKey.empty() && !presence.smallImageText.empty()) { jsonPayload.append(R"("assets": {)"); jsonPayload.append(shim::format(R"("large_image": "{}",)", presence.largeImageKey)); jsonPayload.append(shim::format(R"("large_text": "{}",)", presence.largeImageText)); jsonPayload.append(shim::format(R"("small_image": "{}",)", presence.smallImageKey)); jsonPayload.append(shim::format(R"("small_text": "{}",)", presence.smallImageText)); jsonPayload.append("},"); } // party if (!presence.partyId.empty() || (presence.partySize > 0 && presence.partyMax > 0)) { jsonPayload.append(R"("party": {)"); if (!presence.partyId.empty()) jsonPayload.append(shim::format(R"("id": "{}",)", presence.partyId)); if (presence.partySize > 0 && presence.partyMax > 0) jsonPayload.append(shim::format(R"("size": [{}, {}],)", presence.partySize, presence.partyMax)); jsonPayload.append(R"("privacy": )").append(std::to_string(presence.partyPrivacy)).append(","); jsonPayload.append("},"); } // match secret if (!presence.matchSecret.empty() || !presence.joinSecret.empty() || !presence.spectateSecret.empty()) { jsonPayload.append(R"("secrets": {)"); if (!presence.matchSecret.empty()) jsonPayload.append(shim::format(R"("match": "{}",)", presence.matchSecret)); if (!presence.joinSecret.empty()) jsonPayload.append(shim::format(R"("join": "{}",)", presence.joinSecret)); if (!presence.spectateSecret.empty()) jsonPayload.append(shim::format(R"("spectate": "{}",)", presence.spectateSecret)); if (jsonPayload.back() == ',') jsonPayload.pop_back(); jsonPayload.append("},"); } if (jsonPayload.back() == ',') jsonPayload.pop_back(); // end of args->activity jsonPayload.append("}"); } if (jsonPayload.back() == ',') jsonPayload.pop_back(); jsonPayload.append("}}"); SendJsonMsg(OpcodeFrame, jsonPayload); } private: std::string GetNonceStr() { std::string nonceStr = shim::format("{:016x}", nonce); nonce++; return nonceStr; } int64_t nonce = 1; }; static std::optional<std::string> ParseJSONField(std::string_view json, std::string_view fieldPath) { size_t pos = 0; auto skipWhitespace = [&]() { while (pos < json.size() && isspace(json[pos])) ++pos; }; auto nextToken = [&]() -> std::string_view { skipWhitespace(); if (pos >= json.size()) return ""; char c = json[pos]; if (c == '{' || c == '}' || c == '[' || c == ']' || c == ':' || c == ',') { return json.substr(pos++, 1); } if (c == '"') { size_t start = ++pos; while (pos < json.size() && json[pos] != '"') ++pos; auto tok = json.substr(start, pos > start ? pos - start : 0); if (pos < json.size()) ++pos; return tok; } size_t start = pos; while (pos < json.size() && !isspace(json[pos]) && json[pos] != ',' && json[pos] != '}' && json[pos] != ']') ++pos; return json.substr(start, pos - start); }; auto skipValue = [&]() { skipWhitespace(); if (pos >= json.size()) return; char c = json[pos]; if (c == '{') { int depth = 1; ++pos; while (pos < json.size() && depth > 0) { auto tok = nextToken(); if (tok == "{") ++depth; else if (tok == "}") --depth; } } else if (c == '[') { int depth = 1; ++pos; while (pos < json.size() && depth > 0) { auto tok = nextToken(); if (tok == "[") ++depth; else if (tok == "]") --depth; } } else { nextToken(); } }; auto findField = [&](std::string_view key) { while (true) { auto tok = nextToken(); if (tok == "}") return false; if (tok == "") return false; if (tok == key) { if (nextToken() == ":") return true; return false; } if (nextToken() != ":") return false; skipValue(); auto sep = nextToken(); if (sep == "}") return false; if (sep != ",") return false; } }; // initial "{" skipWhitespace(); if (nextToken() != "{") return std::nullopt; // traverse the path size_t off = 0; while (off <= fieldPath.size()) { size_t sep = fieldPath.find('/', off); if (sep == std::string_view::npos) sep = fieldPath.size(); std::string_view seg = fieldPath.substr(off, sep - off); if (!findField(seg)) return std::nullopt; auto val = nextToken(); // after ":" if (sep == fieldPath.size()) return std::string(val); if (val == "{") { // traverse object } else if (val == "[") { // array return std::nullopt; } else { return std::nullopt; // not object } off = sep + 1; } return std::nullopt; } DiscordRPCLite::DiscordRPCLite(const char* applicationId) { m_workerThread = std::thread(&DiscordRPCLite::WorkerThread, this, std::string(applicationId)); } DiscordRPCLite::~DiscordRPCLite() { m_shutdownSignal.store(true); if (m_workerThread.joinable()) m_workerThread.join(); } static bool CompareStringI(std::string_view a, std::string_view b) { auto toLower = [](char c) -> char { if (c >= 'A' && c <= 'Z') return c + ('a' - 'A'); return c; }; if (a.size() != b.size()) return false; for (size_t i = 0; i < a.size(); i++) { if (toLower(a[i]) != toLower(b[i])) return false; } return true; } static int64_t GetMs() { auto now = std::chrono::system_clock::now(); auto duration = now.time_since_epoch(); return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); } void DiscordRPCLite::WorkerThread(std::string applicationId) { enum class ConnectionState { Disconnected, ConnectingBeforeHandshake, ConnectingAfterHandshake, Connected }; ConnectionState connectionState = ConnectionState::Disconnected; std::vector<uint8_t> recvBuffer; uint8_t recvMsgHeader[8] = {0}; size_t recvSize = 0; // if < 8 then we are still receiving the header. If >= 8 then we are receiving the body NamedPipe pipe; auto SetDisconnected = [&]() { pipe.ClosePipe(); connectionState = ConnectionState::Disconnected; recvSize = 0; m_richPresenceDirty = true; // set so we always send current rich presence after (re)connecting }; SetDisconnected(); // set initial state int64_t lastConnectionAttempt = 0; int32_t numFailedConnectionAttempts = 0; // increases every time we try to connect but fail, resets to 0 when we successfully connect and receive a READY event int64_t lastRichPresenceUpdate = 0; while (!m_shutdownSignal) { if (connectionState == ConnectionState::Disconnected) { // wait at least one second before trying to reconnect int64_t now = GetMs(); int64_t delayTime = 1000 + (numFailedConnectionAttempts * 500); // increase delay by 500ms for each failed connection attempt if (delayTime >= 90000) delayTime = 90000; // max delay of 90 seconds if ((now - lastConnectionAttempt) < delayTime) { std::this_thread::sleep_for(std::chrono::milliseconds(200)); continue; } lastConnectionAttempt = now; numFailedConnectionAttempts++; if (!pipe.OpenPipe()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue; } connectionState = ConnectionState::ConnectingBeforeHandshake; continue; } if (connectionState == ConnectionState::ConnectingBeforeHandshake) { std::string handshakeJson = shim::format(R"({{"v":1,"client_id":"{}"}})", applicationId); pipe.SendJsonMsg(OpcodeHandshake, handshakeJson); connectionState = ConnectionState::ConnectingAfterHandshake; } // check if we should send a presence update (rate limited to 1 per 15 seconds) if (connectionState == ConnectionState::Connected && m_richPresenceDirty) { int64_t now = GetMs(); if ((now - lastRichPresenceUpdate) >= 15000) { DiscordLiteRichPresence presence = {}; { std::lock_guard lock(m_presenceMutex); presence = m_presence; m_richPresenceDirty = false; } pipe.SendPresenceUpdate(presence); lastRichPresenceUpdate = now; } } // continue receiving messages if (recvSize < 8) { // receive header int readSize = pipe.Read(recvMsgHeader + recvSize, 8 - recvSize, 200); if (readSize < 0) { SetDisconnected(); continue; } recvSize += readSize; } else { // receive body uint32_t opcode = (recvMsgHeader[0] << 0) | (recvMsgHeader[1] << 8) | (recvMsgHeader[2] << 16) | (recvMsgHeader[3] << 24); uint32_t bodyLength = (recvMsgHeader[4] << 0) | (recvMsgHeader[5] << 8) | (recvMsgHeader[6] << 16) | (recvMsgHeader[7] << 24); if (bodyLength >= 8 * 1024 * 1024) { SetDisconnected(); continue; } if (recvBuffer.size() < bodyLength) recvBuffer.resize(bodyLength); int readSize = pipe.Read(recvBuffer.data() + (recvSize - 8), bodyLength - (recvSize - 8), 1000); if (readSize < 0) { SetDisconnected(); continue; } recvSize += readSize; if (recvSize == bodyLength + 8) { std::string_view jsonText((const char*)recvBuffer.data(), bodyLength); recvSize = 0; // full message received if (opcode == OpcodeFrame) { std::optional<std::string> cmd = ParseJSONField(jsonText, "cmd"); std::optional<std::string> evt = ParseJSONField(jsonText, "evt"); if (CompareStringI(*cmd, "DISPATCH")) { if (evt && CompareStringI(*evt, "READY")) { if (connectionState == ConnectionState::ConnectingAfterHandshake) { connectionState = ConnectionState::Connected; numFailedConnectionAttempts = 0; } } } } else if (opcode == OpcodePing) { pipe.SendJsonMsg(OpcodePong, jsonText); } else if (opcode == OpcodeClose) { SetDisconnected(); } if (recvBuffer.size() > 2048) { recvBuffer.resize(2048); recvBuffer.shrink_to_fit(); } } } } // if we are still connected then explicitly clear presence just in case if (pipe.IsOpen()) { pipe.SendPresenceUpdate({}); std::this_thread::sleep_for(std::chrono::milliseconds(75)); } } ================================================ FILE: src/Cemu/DiscordPresence/DiscordRPCLite.h ================================================ /* Standalone client for Discord RPC for the sole purpose of updating rich presence */ #pragma once #include <thread> #include <atomic> #include <string> #include <cstdint> #include <mutex> struct DiscordLiteRichPresence { std::string state; std::string details; int64_t startTimestamp; int64_t endTimestamp; std::string largeImageKey; std::string largeImageText; std::string smallImageKey; std::string smallImageText; std::string partyId; int partySize; int partyMax; int partyPrivacy; std::string matchSecret; std::string joinSecret; std::string spectateSecret; int8_t instance; }; class DiscordRPCLite { public: DiscordRPCLite(const char* applicationId); ~DiscordRPCLite(); void UpdateRichPresence(const DiscordLiteRichPresence& presence) { std::unique_lock _l(m_presenceMutex); m_presence = presence; m_richPresenceDirty = true; } void ClearRichPresence() { std::unique_lock _l(m_presenceMutex); m_presence = {}; m_richPresenceDirty = false; } private: void WorkerThread(std::string applicationId); std::thread m_workerThread; std::atomic_bool m_shutdownSignal{false}; // rich presence state std::mutex m_presenceMutex; std::atomic_bool m_richPresenceDirty{false}; DiscordLiteRichPresence m_presence; }; ================================================ FILE: src/Cemu/ExpressionParser/ExpressionParser.cpp ================================================ #include "ExpressionParser.h" template<typename T, typename TInternalType = double> T _testEvaluateToType(const char* expr) { TExpressionParser<TInternalType> ep; T result = (T)ep.Evaluate(expr); return result; } void ExpressionParser_test() { ExpressionParser ep; cemu_assert_debug(_testEvaluateToType<sint64>("0x100005E0") == 0x100005E0); cemu_assert_debug(_testEvaluateToType<uint64>("0x10+0x20+0x40") == 0x70); cemu_assert_debug(_testEvaluateToType<sint64>("0+-3") == -3); cemu_assert_debug(_testEvaluateToType<sint64>("0x0-3") == -3); cemu_assert_debug(_testEvaluateToType<sint64>("01C+0x10") == 0x2C); cemu_assert_debug(_testEvaluateToType<sint64>("+0x10") == 0x10); cemu_assert_debug(_testEvaluateToType<sint64>("01C++0x10") == 0x2C); cemu_assert_debug(_testEvaluateToType<sint64>("01C+(+0x10)") == 0x2C); // arithmetic cemu_assert_debug(_testEvaluateToType<sint64>("(62156250 / 1000) * 30") == 1864687); // truncated 1864687.5 // arithmetic with internal type sint64 cemu_assert_debug(_testEvaluateToType<sint64, sint64>("(62156250 / 1000) * 30") == 1864680); // comparison operators cemu_assert_debug(_testEvaluateToType<sint64>("5 > 3") == 0x1); cemu_assert_debug(_testEvaluateToType<sint64>("5 < -10") == 0x0); cemu_assert_debug(_testEvaluateToType<float>("5 > 3") == 1.0f); cemu_assert_debug(_testEvaluateToType<float>("-5 > 3") == 0.0f); cemu_assert_debug(_testEvaluateToType<float>("5 < 10") == 1.0f); cemu_assert_debug(_testEvaluateToType<float>("5 <= 5") == 1.0f); cemu_assert_debug(_testEvaluateToType<float>("5 <= 4.999") == 0.0f); cemu_assert_debug(_testEvaluateToType<float>("5 <= (4+0.999)") == 0.0f); cemu_assert_debug(_testEvaluateToType<float>("5 >= 3") == 1.0f); cemu_assert_debug(_testEvaluateToType<float>("5 >= 10") == 0.0f); cemu_assert_debug(_testEvaluateToType<float>("10 >= 5") == 1.0f); // complex operations cemu_assert_debug(_testEvaluateToType<float>("5 > 4 > 3 > 2") == 0.0f); // this should evaluate the operations from left to right, (5 > 4) -> 0.0, (0.0 > 4) -> 0.0, (0.0 > 3) -> 0.0, (0.0 > 2) -> 0.0 cemu_assert_debug(_testEvaluateToType<float>("5 > 4 > 3 > -2") == 1.0f); // this should evaluate the operations from left to right, (5 > 4) -> 0.0, (0.0 > 4) -> 0.0, (0.0 > 3) -> 0.0, (0.0 > -2) -> 1.0 cemu_assert_debug(_testEvaluateToType<float>("(5 == 5) > (5 == 6)") == 1.0f); } ================================================ FILE: src/Cemu/ExpressionParser/ExpressionParser.h ================================================ #pragma once #include "Common/precompiled.h" #include <string> #include <queue> #include <stack> #include <unordered_map> #include <memory> #include <charconv> #include "boost/functional/hash_fwd.hpp" #include <fmt/format.h> #ifdef __clang__ #include "Common/unix/fast_float.h" #define _EP_FROM_CHARS_DBL(...) _convFastFloatResult(fast_float::from_chars(__VA_ARGS__)) inline std::from_chars_result _convFastFloatResult(fast_float::from_chars_result r) { std::from_chars_result nr; nr.ptr = r.ptr; nr.ec = r.ec; return nr; } #else #define _EP_FROM_CHARS_DBL(...) std::from_chars(__VA_ARGS__) #endif template<class TType = double> class TExpressionParser { public: static_assert(std::is_arithmetic_v<TType>); using ConstantCallback_t = TType(*)(std::string_view var_name); using FunctionCallback_t = TType(*)(std::string_view var_name, TType parameter); template<typename T> T Evaluate(std::string_view expression) const { static_assert(std::is_arithmetic_v<T>, "type T must be an arithmetic type"); return (T)Evaluate(expression); } [[nodiscard]] TType Evaluate(std::string_view expression) const { std::queue<std::shared_ptr<TokenBase>> output; std::stack<std::shared_ptr<TokenBase>> operators; if (expression.empty()) { throw std::runtime_error(fmt::format("empty expression is not allowed")); } bool last_operator_token = true; // tokenize and apply shunting-yard for (size_t i = 0; i < expression.size(); ) { const char c = expression[i]; // skip whitespaces if (isblank(c)) { ++i; continue; } // extract numbers if (isdigit(c) || (last_operator_token && (c == '-' || c == '+'))) { const std::string_view view(expression.data() + i, expression.size() - i); size_t offset = 0; auto converted = (TType)ConvertString(view, &offset); output.emplace(std::make_shared<TokenNumber>(converted)); i += offset; last_operator_token = false; continue; } // check for variables if (isalpha(c) || c == '_' || c == '$') { size_t j; for (j = i + 1; j < expression.size(); ++j) { const char v = expression[j]; if (!isalpha(v) && !isdigit(v) && v != '_' && v != '@' && v != '.') { break; } } const size_t len = j - i; const std::string_view view = expression.substr(i, len); // check for function if (m_function_callback) { // todo skip whitespaces if (j < expression.size() && expression[j] == '(') { operators.emplace(std::make_shared<TokenUnaryOperator>(TokenUnaryOperator::kFunction, view)); operators.emplace(std::make_shared<TokenParenthese>()); i += len + 1; last_operator_token = true; continue; } } TType value; const auto it = m_constants.find(std::string{ view }); if (it == m_constants.cend()) { if (m_constant_callback == nullptr) throw std::runtime_error(fmt::format("unknown constant found \"{}\" in expression: {}", view, expression)); value = m_constant_callback(view); } else value = it->second; output.emplace(std::make_shared<TokenNumber>(value)); i += len; last_operator_token = false; continue; } // parenthese if (c == '(') { operators.emplace(std::make_shared<TokenParenthese>()); ++i; last_operator_token = true; continue; } if (c == ')') { bool match = false; while (!operators.empty()) { const auto op = std::dynamic_pointer_cast<TokenParenthese>(operators.top()); if (!op) { output.emplace(operators.top()); operators.pop(); continue; } operators.pop(); match = true; break; } if (!match && operators.empty()) { throw std::runtime_error(fmt::format("parentheses mismatch: {}", expression)); } ++i; continue; } // supported operations std::shared_ptr<TokenOperator> operator_token; switch (c) { case '+': operator_token = std::make_shared<TokenOperator>(TokenOperator::kAddition, 2); break; case '-': operator_token = std::make_shared<TokenOperator>(TokenOperator::kSubtraction, 2); break; case '*': operator_token = std::make_shared<TokenOperator>(TokenOperator::kMultiplication, 3); break; case '/': operator_token = std::make_shared<TokenOperator>(TokenOperator::kDivision, 3); break; case '^': operator_token = std::make_shared<TokenOperator>(TokenOperator::kPow, 4, true); break; case '%': operator_token = std::make_shared<TokenOperator>(TokenOperator::kModulo, 3); break; case '=': if ((i + 1) < expression.size() && expression[i + 1] == '=') { operator_token = std::make_shared<TokenOperator>(TokenOperator::kEqual, 1); ++i; } break; case '!': if ((i + 1) < expression.size() && expression[i + 1] == '=') { operator_token = std::make_shared<TokenOperator>(TokenOperator::kNotEqual, 1); ++i; } break; case '<': if ((i + 1) < expression.size() && expression[i + 1] == '=') { operator_token = std::make_shared<TokenOperator>(TokenOperator::kLessOrEqual, 1); ++i; } else { operator_token = std::make_shared<TokenOperator>(TokenOperator::kLessThan, 1); } break; case '>': if ((i + 1) < expression.size() && expression[i + 1] == '=') { operator_token = std::make_shared<TokenOperator>(TokenOperator::kGreaterOrEqual, 1); ++i; } else { operator_token = std::make_shared<TokenOperator>(TokenOperator::kGreaterThan, 1); } break; } if (!operator_token) throw std::runtime_error(fmt::format("invalid operator found in expression: {}", expression)); while (!operators.empty()) { const auto op = std::dynamic_pointer_cast<TokenOperator>(operators.top()); if (op && ((!operator_token->right_ass && operator_token->prec <= op->prec) || (operator_token->right_ass && operator_token->prec < op->prec))) { output.emplace(operators.top()); operators.pop(); continue; } break; } operators.emplace(operator_token); ++i; last_operator_token = true; } while (!operators.empty()) { const auto parenthese = std::dynamic_pointer_cast<TokenParenthese>(operators.top()); if (parenthese) { throw std::runtime_error(fmt::format("parentheses mismatch in expression: {}", expression)); } output.push(operators.top()); operators.pop(); } // evaluate tokens in output queue std::stack<TType> evaluation; while (!output.empty()) { const auto number = std::dynamic_pointer_cast<TokenNumber>(output.front()); if (number) { evaluation.emplace(number->number); output.pop(); continue; } if (const auto unop = std::dynamic_pointer_cast<TokenUnaryOperator>(output.front())) { if (evaluation.size() < 1) throw std::runtime_error("not enough parameters for equation"); const auto parameter = evaluation.top(); evaluation.pop(); switch (unop->op) { case TokenUnaryOperator::kFunction: evaluation.emplace(m_function_callback(unop->symbol, parameter)); break; default: throw std::runtime_error("unsupported operation constant"); } output.pop(); continue; } else if (const auto op = std::dynamic_pointer_cast<TokenOperator>(output.front())) { if (evaluation.size() < 2) throw std::runtime_error("not enough parameters for equation"); const auto rhs = evaluation.top(); evaluation.pop(); const auto lhs = evaluation.top(); evaluation.pop(); switch (op->op) { case TokenOperator::kAddition: evaluation.emplace(lhs + rhs); break; case TokenOperator::kSubtraction: evaluation.emplace(lhs - rhs); break; case TokenOperator::kMultiplication: evaluation.emplace(lhs * rhs); break; case TokenOperator::kDivision: if (rhs == 0.0) evaluation.emplace((TType)0.0); else evaluation.emplace(lhs / rhs); break; case TokenOperator::kPow: evaluation.emplace((TType)std::pow(lhs, rhs)); break; case TokenOperator::kModulo: if (std::round(rhs) == 0.0) evaluation.emplace((TType)0.0); else evaluation.emplace((TType)((sint64)std::round(lhs) % (sint64)std::round(rhs))); break; case TokenOperator::kEqual: if constexpr (std::is_floating_point_v<TType>) evaluation.emplace((TType)(std::abs(lhs - rhs) <= 0.0000000001 ? 1 : 0)); else evaluation.emplace(lhs == rhs ? 1 : 0); break; case TokenOperator::kNotEqual: if constexpr (std::is_floating_point_v<TType>) evaluation.emplace((TType)(std::abs(lhs - rhs) > 0.0000000001 ? 1 : 0)); else evaluation.emplace(lhs != rhs ? 1 : 0); break; case TokenOperator::kLessThan: evaluation.emplace((TType)(lhs < rhs ? 1 : 0)); break; case TokenOperator::kLessOrEqual: evaluation.emplace((TType)(lhs <= rhs ? 1 : 0)); break; case TokenOperator::kGreaterThan: evaluation.emplace((TType)(lhs > rhs ? 1 : 0)); break; case TokenOperator::kGreaterOrEqual: evaluation.emplace((TType)(lhs >= rhs ? 1 : 0)); break; default: throw std::runtime_error("unsupported operation constant"); } output.pop(); continue; } } return evaluation.top(); } [[nodiscard]] bool IsValidExpression(std::string_view expression) const { try { TExpressionParser<TType> ep; ep.AddConstantCallback( [](std::string_view sv) -> TType { return (TType)1; }); static_cast<void>(ep.Evaluate(expression)); return true; } catch (...) { return false; } } [[nodiscard]] bool IsConstantExpression(std::string_view expression) const { try { static_cast<void>(this->Evaluate(expression)); return true; } catch (...) { return false; } } void AddConstant(std::string_view name, TType value) { m_constants[std::string(name)] = value; } // only adds constant if not added yet void TryAddConstant(std::string_view name, TType value) { m_constants.try_emplace(std::string{ name }, value); } void AddConstant(std::string_view name, std::string_view value) { AddConstant(name, ConvertString(value)); } void AddConstantCallback(ConstantCallback_t callback) { m_constant_callback = callback; } void SetFunctionCallback(FunctionCallback_t callback) { m_function_callback = callback; } private: std::unordered_map<std::string, TType> m_constants; ConstantCallback_t m_constant_callback = nullptr; FunctionCallback_t m_function_callback = nullptr; static bool _isNumberWithDecimalPoint(std::string_view str) { return str.find('.') != std::string_view::npos; } double ConvertString(std::string_view str, size_t* index_after = nullptr) const { const char* strInitial = str.data(); if (str.empty()) throw std::runtime_error("can't parse empty number"); double result; std::from_chars_result chars_result; const bool is_negative = str[0] == '-'; if (is_negative || str[0] == '+') str = str.substr(1); if (str.size() >= 3 && str[0] == '0' && tolower(str[1]) == 'x') { str = str.substr(2); // find end of number const auto end = std::find_if_not(str.cbegin(), str.cend(), isxdigit); int64_t tmp; chars_result = std::from_chars(str.data(), str.data() + std::distance(str.cbegin(), end), tmp, 16); result = (double)tmp; } // a number prefixed with 0 and no decimal point is considered a hex number else if (str.size() >= 2 && str[0] == '0' && isxdigit(str[1]) && !_isNumberWithDecimalPoint(str)) { str = str.substr(1); // find end of number const auto end = std::find_if_not(str.cbegin(), str.cend(), isxdigit); int64_t tmp; chars_result = std::from_chars(str.data(), str.data() + std::distance(str.cbegin(), end), tmp, 16); result = (double)tmp; } else { if (str[0] == '+') str = str.substr(1); bool has_point = false; bool has_exponent = false; const auto end = std::find_if_not(str.cbegin(), str.cend(), [&has_point, &has_exponent](char c) -> bool { if (isdigit(c)) return true; if (c == '.' && !has_point) { has_point = true; return true; } if (tolower(c) == 'e' && !has_exponent) { has_exponent = true; // TODO: after exponent might be + and - allowed? return true; } return false; }); chars_result = _EP_FROM_CHARS_DBL(str.data(), str.data() + std::distance(str.cbegin(), end), result); } if (chars_result.ec == std::errc()) { if (index_after) *index_after = chars_result.ptr - strInitial; if (is_negative) result *= -1.0; return result; } else throw std::runtime_error(fmt::format("can't parse number: {}", str)); } struct TokenBase { virtual ~TokenBase() = default; }; struct TokenUnaryOperator : TokenBase { enum Operator { kFunction, }; TokenUnaryOperator(Operator op) : op(op) {} TokenUnaryOperator(Operator op, std::string_view symbol) : op(op), symbol(symbol) {} Operator op; std::string_view symbol; }; struct TokenOperator : TokenBase { enum Operator { kAddition, kSubtraction, kMultiplication, kDivision, kPow, kModulo, kEqual, kNotEqual, kLessThan, kLessOrEqual, kGreaterThan, kGreaterOrEqual, }; TokenOperator(Operator op, int prec, bool right_ass = false) : op(op), prec(prec), right_ass(right_ass) {} Operator op; int prec; bool right_ass; }; struct TokenParenthese : TokenBase {}; struct TokenNumber : TokenBase { TokenNumber(TType number) : number(number) {} TType number; }; }; class ExpressionParser : public TExpressionParser<double> {}; void ExpressionParser_test(); ================================================ FILE: src/Cemu/FileCache/FileCache.cpp ================================================ #include "FileCache.h" #include "util/helpers/helpers.h" #include <mutex> #include <condition_variable> #include "zlib.h" #include "Common/FileStream.h" struct FileCacheAsyncJob { FileCache* fileCache; uint64 name1; uint64 name2; std::vector<uint8> fileData; }; struct _FileCacheAsyncWriter { _FileCacheAsyncWriter() { m_isRunning.store(true); m_fileCacheThread = std::thread(&_FileCacheAsyncWriter::FileCacheThread, this); } ~_FileCacheAsyncWriter() { if (m_isRunning.load()) { m_isRunning.store(false); m_fileCacheCondVar.notify_one(); m_fileCacheThread.join(); } } void AddJob(FileCache* fileCache, const FileCache::FileName& name, const uint8* fileData, sint32 fileSize) { FileCacheAsyncJob async; async.fileCache = fileCache; async.name1 = name.name1; async.name2 = name.name2; async.fileData = { fileData, fileData + fileSize }; std::unique_lock lock(m_fileCacheMutex); m_writeRequests.emplace_back(std::move(async)); lock.unlock(); m_fileCacheCondVar.notify_one(); } private: void FileCacheThread() { SetThreadName("fileCache"); while (true) { std::unique_lock lock(m_fileCacheMutex); while (m_writeRequests.empty()) { m_fileCacheCondVar.wait(lock); if (!m_isRunning.load(std::memory_order::relaxed)) return; } std::vector<FileCacheAsyncJob> requestsCopy; requestsCopy.swap(m_writeRequests); // fast copy & clear lock.unlock(); for (const auto& entry : requestsCopy) { entry.fileCache->AddFile({ entry.name1, entry.name2 }, entry.fileData.data(), (sint32)entry.fileData.size()); } } } std::thread m_fileCacheThread; std::mutex m_fileCacheMutex; std::condition_variable m_fileCacheCondVar; std::vector<FileCacheAsyncJob> m_writeRequests; std::atomic_bool m_isRunning; }FileCacheAsyncWriter; #define FILECACHE_MAGIC_V1 0x8371b694 // used prior to Cemu 1.7.4, only supported caches up to 4GB #define FILECACHE_MAGIC_V2 0x8371b695 // added support for large caches #define FILECACHE_MAGIC_V3 0x8371b696 // introduced in Cemu 1.16.0 (non-WIP). Adds zlib compression #define FILECACHE_HEADER_RESV 128 // number of bytes reserved for the header #define FILECACHE_FILETABLE_NAME1 0xEFEFEFEFEFEFEFEFULL #define FILECACHE_FILETABLE_NAME2 0xFEFEFEFEFEFEFEFEULL #define FILECACHE_FILETABLE_FREE_NAME 0ULL FileCache* FileCache::Create(const fs::path& path, uint32 extraVersion) { FileStream* fs = FileStream::createFile2(path); if (!fs) { cemuLog_log(LogType::Force, "Failed to create cache file \"{}\"", _pathToUtf8(path)); return nullptr; } // init file cache auto* fileCache = new FileCache(); fileCache->fileStream = fs; fileCache->dataOffset = FILECACHE_HEADER_RESV; fileCache->fileTableEntryCount = 32; fileCache->fileTableOffset = 0; fileCache->fileTableSize = sizeof(FileTableEntry) * fileCache->fileTableEntryCount; fileCache->fileTableEntries = (FileTableEntry*)malloc(fileCache->fileTableSize); fileCache->extraVersion = extraVersion; memset(fileCache->fileTableEntries, 0, fileCache->fileTableSize); // file table stores info about itself fileCache->fileTableEntries[0].name1 = FILECACHE_FILETABLE_NAME1; fileCache->fileTableEntries[0].name2 = FILECACHE_FILETABLE_NAME2; fileCache->fileTableEntries[0].fileOffset = fileCache->fileTableOffset; fileCache->fileTableEntries[0].fileSize = fileCache->fileTableSize; // write header fs->writeU32(FILECACHE_MAGIC_V3); fs->writeU32(fileCache->extraVersion); fs->writeU64(fileCache->dataOffset); fs->writeU64(fileCache->fileTableOffset); fs->writeU32(fileCache->fileTableSize); // write file table fs->SetPosition(fileCache->dataOffset+fileCache->fileTableOffset); fs->writeData(fileCache->fileTableEntries, fileCache->fileTableSize); // done return fileCache; } FileCache* FileCache::_OpenExisting(const fs::path& path, bool compareExtraVersion, uint32 extraVersion) { FileStream* fs = FileStream::openFile2(path, true); if (!fs) return nullptr; // read header uint32 headerMagic = 0; fs->readU32(headerMagic); bool isV2 = false; if (headerMagic != FILECACHE_MAGIC_V1 && headerMagic != FILECACHE_MAGIC_V2 && headerMagic != FILECACHE_MAGIC_V3) { delete fs; return nullptr; } if (headerMagic == FILECACHE_MAGIC_V1) { // support for V1 file format removed with the addition of V3 delete fs; return nullptr; } if (headerMagic == FILECACHE_MAGIC_V2) isV2 = true; uint32 headerExtraVersion = 0xFFFFFFFF; fs->readU32(headerExtraVersion); if (compareExtraVersion && headerExtraVersion != extraVersion) { delete fs; return nullptr; } if (!compareExtraVersion) { extraVersion = headerExtraVersion; } uint64 headerDataOffset = 0; uint64 headerFileTableOffset = 0; uint32 headerFileTableSize = 0; fs->readU64(headerDataOffset); fs->readU64(headerFileTableOffset); if (!fs->readU32(headerFileTableSize)) { cemuLog_log(LogType::Force, "\"{}\" is corrupted", _pathToUtf8(path)); delete fs; return nullptr; } uint32 fileTableEntryCount = 0; bool invalidFileTableSize = false; // V2 and V3 fileTableEntryCount = headerFileTableSize / sizeof(FileTableEntry); invalidFileTableSize = (headerFileTableSize % sizeof(FileTableEntry)) != 0; if (invalidFileTableSize) { cemuLog_log(LogType::Force, "\"{}\" is corrupted", _pathToUtf8(path)); delete fs; return nullptr; } // init struct auto* fileCache = new FileCache(); fileCache->fileStream = fs; fileCache->extraVersion = extraVersion; fileCache->dataOffset = headerDataOffset; fileCache->fileTableEntryCount = fileTableEntryCount; fileCache->fileTableOffset = headerFileTableOffset; fileCache->fileTableSize = fileTableEntryCount * sizeof(FileTableEntry); fileCache->fileTableEntries = (FileTableEntry*)malloc(fileTableEntryCount * sizeof(FileTableEntry)); memset(fileCache->fileTableEntries, 0, fileTableEntryCount * sizeof(FileTableEntry)); // read file table fileCache->fileStream->SetPosition(fileCache->dataOffset + fileCache->fileTableOffset); bool incompleteFileTable = false; if (isV2) { // read file table entries in old format incompleteFileTable = fileCache->fileStream->readData(fileCache->fileTableEntries, fileCache->fileTableSize) != fileCache->fileTableSize; // in V2 the extra field wasn't guaranteed to have well defined values for (uint32 i = 0; i < fileTableEntryCount; i++) { fileCache->fileTableEntries[i].flags = FileTableEntry::FLAGS::FLAG_NONE; fileCache->fileTableEntries[i].extraReserved1 = 0; fileCache->fileTableEntries[i].extraReserved2 = 0; fileCache->fileTableEntries[i].extraReserved3 = 0; } } else { incompleteFileTable = fileCache->fileStream->readData(fileCache->fileTableEntries, fileCache->fileTableSize) != fileCache->fileTableSize; } if (incompleteFileTable) { cemuLog_log(LogType::Force, "\"{}\" is corrupted (incomplete file table)", _pathToUtf8(path)); delete fileCache; return nullptr; } return fileCache; } FileCache* FileCache::Open(const fs::path& path, bool allowCreate, uint32 extraVersion) { FileCache* fileCache = _OpenExisting(path, true, extraVersion); if (fileCache) return fileCache; if (!allowCreate) return nullptr; return Create(path, extraVersion); } FileCache* FileCache::Open(const fs::path& path) { return _OpenExisting(path, false, 0); } FileCache::~FileCache() { free(this->fileTableEntries); delete fileStream; } void FileCache::fileCache_updateFiletable(sint32 extraEntriesToAllocate) { // recreate file table with bigger size (optional) this->fileTableEntries[0].name1 = FILECACHE_FILETABLE_FREE_NAME; this->fileTableEntries[0].name2 = FILECACHE_FILETABLE_FREE_NAME; sint32 newFileTableEntryCount = this->fileTableEntryCount + extraEntriesToAllocate; this->fileTableEntries = (FileTableEntry*)realloc(this->fileTableEntries, sizeof(FileTableEntry)*newFileTableEntryCount); for (sint32 f = this->fileTableEntryCount; f < newFileTableEntryCount; f++) { this->fileTableEntries[f].name1 = FILECACHE_FILETABLE_FREE_NAME; this->fileTableEntries[f].name2 = FILECACHE_FILETABLE_FREE_NAME; this->fileTableEntries[f].fileOffset = 0; this->fileTableEntries[f].fileSize = 0; this->fileTableEntries[f].flags = FileTableEntry::FLAGS::FLAG_NONE; this->fileTableEntries[f].extraReserved1 = 0; this->fileTableEntries[f].extraReserved2 = 0; this->fileTableEntries[f].extraReserved3 = 0; } this->fileTableEntryCount = newFileTableEntryCount; this->_addFileInternal(FILECACHE_FILETABLE_NAME1, FILECACHE_FILETABLE_NAME2, (uint8*)this->fileTableEntries, sizeof(FileTableEntry)*newFileTableEntryCount, true); // update file table info in struct if (this->fileTableEntries[0].name1 != FILECACHE_FILETABLE_NAME1 || this->fileTableEntries[0].name2 != FILECACHE_FILETABLE_NAME2) { cemuLog_log(LogType::Force, "Corruption in cache file detected"); assert_dbg(); } this->fileTableOffset = this->fileTableEntries[0].fileOffset; this->fileTableSize = this->fileTableEntries[0].fileSize; // update header fileStream->SetPosition(0); fileStream->writeU32(FILECACHE_MAGIC_V3); fileStream->writeU32(this->extraVersion); fileStream->writeU64(this->dataOffset); fileStream->writeU64(this->fileTableOffset); fileStream->writeU32(this->fileTableSize); } uint8* _fileCache_compressFileData(const uint8* fileData, uint32 fileSize, sint32& compressedSize) { // compress data using zlib deflate // stores the size of the uncompressed file in the first 4 bytes Bytef* uncompressedInput = (Bytef*)fileData; uLongf uncompressedLen = fileSize; uLongf compressedLen = compressBound(fileSize); Bytef* compressedData = (Bytef*)malloc(4 + compressedLen); int zret = compress2(compressedData + 4, &compressedLen, uncompressedInput, uncompressedLen, 4); // level 4 has good compression to performance ratio if (zret != Z_OK) { free(compressedData); return nullptr; } compressedData[0] = ((uint32)fileSize >> 24) & 0xFF; compressedData[1] = ((uint32)fileSize >> 16) & 0xFF; compressedData[2] = ((uint32)fileSize >> 8) & 0xFF; compressedData[3] = ((uint32)fileSize >> 0) & 0xFF; compressedSize = 4 + compressedLen; return compressedData; } bool _uncompressFileData(const uint8* rawData, size_t rawSize, std::vector<uint8>& dataOut) { if (rawSize < 4) { dataOut.clear(); return false; } // get size of uncompressed file uint32 fileSize = 0; fileSize |= ((uint32)rawData[0] << 24); fileSize |= ((uint32)rawData[1] << 16); fileSize |= ((uint32)rawData[2] << 8); fileSize |= ((uint32)rawData[3] << 0); // allocate buffer Bytef* compressedInput = (Bytef*)rawData + 4; uLongf compressedLen = (uLongf)(rawSize - 4); uLongf uncompressedLen = fileSize; dataOut.resize(fileSize); int zret = uncompress2(dataOut.data(), &uncompressedLen, compressedInput, &compressedLen); if (zret != Z_OK) { dataOut.clear(); return false; } if (uncompressedLen != fileSize || compressedLen != (rawSize - 4)) { // uncompressed size does not match stored size dataOut.clear(); return false; } return true; } void FileCache::_addFileInternal(uint64 name1, uint64 name2, const uint8* fileData, sint32 fileSize, bool noCompression) { if (fileSize < 0) return; if (!enableCompression) noCompression = true; // compress data sint32 rawSize = 0; uint8* rawData = nullptr; bool isCompressed = false; if (noCompression) { rawData = (uint8*)fileData; rawSize = fileSize; } else { // compress file rawData = _fileCache_compressFileData(fileData, fileSize, rawSize); if (rawData) { isCompressed = true; } else { rawData = (uint8*)fileData; rawSize = fileSize; } } std::unique_lock lock(this->mutex); // find free entry in file table sint32 entryIndex = -1; // scan for already existing entry for (sint32 i = 0; i < this->fileTableEntryCount; i++) { if (this->fileTableEntries[i].name1 == name1 && this->fileTableEntries[i].name2 == name2) { entryIndex = i; break; } } if (entryIndex == -1) { while (true) { // if no entry exists, search for empty one for (sint32 i = 0; i < this->fileTableEntryCount; i++) { if (this->fileTableEntries[i].name1 == FILECACHE_FILETABLE_FREE_NAME && this->fileTableEntries[i].name2 == FILECACHE_FILETABLE_FREE_NAME) { entryIndex = i; break; } } if (entryIndex == -1) { if (name1 == FILECACHE_FILETABLE_NAME1 && name2 == FILECACHE_FILETABLE_NAME2) { cemuLog_log(LogType::Force, "Error in cache file"); cemu_assert_debug(false); } // no free entry, recreate file table with larger size fileCache_updateFiletable(64); // try again continue; } else break; } } // find free space sint64 currentStartOffset = 0; while (true) { bool hasCollision = false; sint64 currentEndOffset = currentStartOffset + rawSize; FileTableEntry* entry = this->fileTableEntries; FileTableEntry* entryLast = this->fileTableEntries + this->fileTableEntryCount; while (entry < entryLast) { if (entry->name1 == FILECACHE_FILETABLE_FREE_NAME && entry->name2 == FILECACHE_FILETABLE_FREE_NAME) { entry++; continue; } if (currentEndOffset >= (sint64)entry->fileOffset && currentStartOffset < (sint64)(entry->fileOffset + entry->fileSize)) { currentStartOffset = entry->fileOffset + entry->fileSize; hasCollision = true; break; } entry++; } // optimized logic to speed up scanning for free offsets // assumes that most of the time entries are stored in direct succession (holds true more often than not) if (hasCollision && (entry + 1) < entryLast) { entry++; while (entry < entryLast) { if (entry->name1 == FILECACHE_FILETABLE_FREE_NAME && entry->name2 == FILECACHE_FILETABLE_FREE_NAME) { entry++; continue; } if (entry->fileOffset == currentStartOffset) { currentStartOffset = entry->fileOffset + entry->fileSize; entry++; continue; } break; } } // retry in case of collision if (hasCollision == false) break; } // update file table entry this->fileTableEntries[entryIndex].name1 = name1; this->fileTableEntries[entryIndex].name2 = name2; this->fileTableEntries[entryIndex].fileOffset = currentStartOffset; this->fileTableEntries[entryIndex].fileSize = rawSize; this->fileTableEntries[entryIndex].flags = isCompressed ? FileTableEntry::FLAGS::FLAG_COMPRESSED : FileTableEntry::FLAGS::FLAG_NONE; this->fileTableEntries[entryIndex].extraReserved1 = 0; this->fileTableEntries[entryIndex].extraReserved2 = 0; this->fileTableEntries[entryIndex].extraReserved3 = 0; // write file data fileStream->SetPosition(this->dataOffset + currentStartOffset); fileStream->writeData(rawData, rawSize); #ifdef __APPLE__ fileStream->Flush(); #endif // write file table entry fileStream->SetPosition(this->dataOffset + this->fileTableOffset + (uint64)(sizeof(FileTableEntry)*entryIndex)); fileStream->writeData(this->fileTableEntries + entryIndex, sizeof(FileTableEntry)); #ifdef __APPLE__ fileStream->Flush(); #endif if (isCompressed) free(rawData); } void FileCache::AddFile(const FileName&& name, const uint8* fileData, sint32 fileSize) { this->_addFileInternal(name.name1, name.name2, fileData, fileSize, false); } bool FileCache::DeleteFile(const FileName&& name) { if( name.name1 == FILECACHE_FILETABLE_NAME1 && name.name2 == FILECACHE_FILETABLE_NAME2 ) return false; // prevent filetable from being deleted std::unique_lock lock(this->mutex); FileTableEntry* entry = this->fileTableEntries; FileTableEntry* entryLast = this->fileTableEntries+this->fileTableEntryCount; while( entry < entryLast ) { if( entry->name1 == name.name1 && entry->name2 == name.name2 ) { entry->name1 = FILECACHE_FILETABLE_FREE_NAME; entry->name2 = FILECACHE_FILETABLE_FREE_NAME; entry->fileOffset = 0; entry->fileSize = 0; // store updated entry to file cache size_t entryIndex = entry - this->fileTableEntries; fileStream->SetPosition(this->dataOffset+this->fileTableOffset+(uint64)(sizeof(FileTableEntry)*entryIndex)); fileStream->writeData(this->fileTableEntries+entryIndex, sizeof(FileTableEntry)); return true; } entry++; } return false; } void FileCache::AddFileAsync(const FileName& name, const uint8* fileData, sint32 fileSize) { FileCacheAsyncWriter.AddJob(this, name, fileData, fileSize); } bool FileCache::_getFileDataInternal(const FileTableEntry* entry, std::vector<uint8>& dataOut) { std::vector<uint8> rawData(entry->fileSize); fileStream->SetPosition(this->dataOffset + entry->fileOffset); fileStream->readData(rawData.data(), entry->fileSize); if ((entry->flags&FileTableEntry::FLAG_COMPRESSED) == 0) { // uncompressed std::swap(rawData, dataOut); return true; } // decompress sint32 uncompressedSize = 0; if (!_uncompressFileData(rawData.data(), rawData.size(), dataOut)) { dataOut.clear(); return false; } return true; } bool FileCache::GetFile(const FileName&& name, std::vector<uint8>& dataOut) { std::unique_lock lock(this->mutex); FileTableEntry* entry = this->fileTableEntries; FileTableEntry* entryLast = this->fileTableEntries+this->fileTableEntryCount; while( entry < entryLast ) { if( entry->name1 == name.name1 && entry->name2 == name.name2 ) { return _getFileDataInternal(entry, dataOut); } entry++; } dataOut.clear(); return false; } bool FileCache::GetFileByIndex(sint32 index, uint64* name1, uint64* name2, std::vector<uint8>& dataOut) { if (index < 0 || index >= this->fileTableEntryCount) return false; FileTableEntry* entry = this->fileTableEntries + index; if (this->fileTableEntries == nullptr) { cemuLog_log(LogType::Force, "GetFileByIndex() fileTable is NULL"); return false; } if (entry->name1 == FILECACHE_FILETABLE_FREE_NAME && entry->name2 == FILECACHE_FILETABLE_FREE_NAME) return false; if (entry->name1 == FILECACHE_FILETABLE_NAME1 && entry->name2 == FILECACHE_FILETABLE_NAME2) return false; std::unique_lock lock(this->mutex); if(name1) *name1 = entry->name1; if(name2) *name2 = entry->name2; return _getFileDataInternal(entry, dataOut); } bool FileCache::HasFile(const FileName&& name) { std::unique_lock lock(this->mutex); FileTableEntry* entry = this->fileTableEntries; FileTableEntry* entryLast = this->fileTableEntries + this->fileTableEntryCount; while (entry < entryLast) { if (entry->name1 == name.name1 && entry->name2 == name.name2) return true; entry++; } return false; } sint32 FileCache::GetMaximumFileIndex() { return this->fileTableEntryCount; } sint32 FileCache::GetFileCount() { std::unique_lock lock(this->mutex); sint32 fileCount = 0; FileTableEntry* entry = this->fileTableEntries; FileTableEntry* entryLast = this->fileTableEntries+this->fileTableEntryCount; while( entry < entryLast ) { if( entry->name1 == FILECACHE_FILETABLE_FREE_NAME && entry->name2 == FILECACHE_FILETABLE_FREE_NAME ) { entry++; continue; } if( entry->name1 == FILECACHE_FILETABLE_NAME1 && entry->name2 == FILECACHE_FILETABLE_NAME2 ) { entry++; continue; } fileCount++; entry++; } return fileCount; } void fileCache_test() { FileCache* fc = FileCache::Create("testCache.bin", 0); uint32 time1 = GetTickCount(); char* testString1 = (char*)malloc(1024 * 1024 * 8); char* testString2 = (char*)malloc(1024 * 1024 * 8); char* testString3 = (char*)malloc(1024 * 1024 * 8); for (sint32 f = 0; f < 1024 * 1024 * 8; f++) { testString1[f] = 'a' + (f & 7); testString2[f] = 'd' + (f & 3); testString3[f] = 'f' + (f & 3); } for(sint32 i=0; i<2200; i++) { fc->AddFile({ 0x1000001ULL, (uint64)i }, (uint8*)testString1, 1024 * 1024 * 1); fc->AddFile({ 0x1000002ULL, (uint64)i }, (uint8*)testString2, 1024 * 1024 * 1); fc->AddFile({ 0x1000003ULL, (uint64)i }, (uint8*)testString3, 1024 * 1024 * 1); } uint32 time2 = GetTickCount(); debug_printf("Writing took %dms\n", time2-time1); delete fc; // verify if all entries are still valid FileCache* fcRead = FileCache::Open("testCache.bin", 0); uint32 time3 = GetTickCount(); for(sint32 i=0; i<2200; i++) { std::vector<uint8> fileData; bool r = fcRead->GetFile({ 0x1000001ULL, (uint64)i }, fileData); if (!r || fileData.size() != 1024 * 1024 * 1 || memcmp(fileData.data(), testString1, 1024 * 1024 * 1) != 0) cemu_assert_debug(false); r = fcRead->GetFile({ 0x1000002ULL, (uint64)i }, fileData); if( !r || fileData.size() != 1024 * 1024 * 1 || memcmp(fileData.data(), testString2, 1024 * 1024 * 1) != 0 ) cemu_assert_debug(false); r = fcRead->GetFile({ 0x1000003ULL, (uint64)i }, fileData); if( !r || fileData.size() != 1024 * 1024 * 1 || memcmp(fileData.data(), testString3, 1024 * 1024 * 1) != 0 ) cemu_assert_debug(false); } uint32 time4 = GetTickCount(); debug_printf("Reading took %dms\n", time4-time3); delete fcRead; cemu_assert_debug(false); exit(0); } ================================================ FILE: src/Cemu/FileCache/FileCache.h ================================================ #pragma once #include <mutex> class FileCache { public: struct FileName { FileName(uint64 name1, uint64 name2) : name1(name1), name2(name2) {}; FileName(std::string_view filePath) { // name from string hash uint64 h1 = 0xa2cc2c49386a75fdull; uint64 h2 = 0x5182d367734c2ce8ull; const char* c = filePath.data(); const char* cEnd = filePath.data() + filePath.size(); while (c < cEnd) { uint64 t = (uint64)*c; c++; h1 = (h1 << 7) | (h1 >> (64 - 7)); h1 += t; h2 = h2 * 7841u + t; } name1 = h1; name2 = h2; }; FileName(const std::string& filePath) : FileName(std::basic_string_view(filePath.data(), filePath.size())) {}; uint64 name1; uint64 name2; }; ~FileCache(); static FileCache* Create(const fs::path& path, uint32 extraVersion = 0); static FileCache* Open(const fs::path& path, bool allowCreate, uint32 extraVersion = 0); static FileCache* Open(const fs::path& path); // open without extraVersion check void UseCompression(bool enable) { enableCompression = enable; }; void AddFile(const FileName&& name, const uint8* fileData, sint32 fileSize); void AddFileAsync(const FileName& name, const uint8* fileData, sint32 fileSize); bool DeleteFile(const FileName&& name); bool GetFile(const FileName&& name, std::vector<uint8>& dataOut); bool GetFileByIndex(sint32 index, uint64* name1, uint64* name2, std::vector<uint8>& dataOut); bool HasFile(const FileName&& name); sint32 GetFileCount(); sint32 GetMaximumFileIndex(); private: struct FileTableEntry { enum FLAGS : uint8 { FLAG_NONE = 0x00, FLAG_COMPRESSED = (1 << 0), // zLib compressed }; uint64 name1; uint64 name2; uint64 fileOffset; uint32 fileSize; FLAGS flags; uint8 extraReserved1; uint8 extraReserved2; uint8 extraReserved3; }; static_assert(sizeof(FileTableEntry) == 0x20); FileCache() {}; static FileCache* _OpenExisting(const fs::path& path, bool compareExtraVersion, uint32 extraVersion = 0); void fileCache_updateFiletable(sint32 extraEntriesToAllocate); void _addFileInternal(uint64 name1, uint64 name2, const uint8* fileData, sint32 fileSize, bool noCompression); bool _getFileDataInternal(const FileTableEntry* entry, std::vector<uint8>& dataOut); class FileStream* fileStream{}; uint64 dataOffset{}; uint32 extraVersion{}; // file table FileTableEntry* fileTableEntries{}; sint32 fileTableEntryCount{}; // file table (as stored in file) uint64 fileTableOffset{}; uint32 fileTableSize{}; // options bool enableCompression{true}; std::recursive_mutex mutex; }; ================================================ FILE: src/Cemu/Logging/CemuDebugLogging.h ================================================ #pragma once // printf-style macros that are only active in non-release builds #ifndef CEMU_DEBUG_ASSERT #define debug_printf(...) static void debugBreakpoint() { } #else #define debug_printf(...) printf(__VA_ARGS__) static void debugBreakpoint() {} #endif ================================================ FILE: src/Cemu/Logging/CemuLogging.cpp ================================================ #include "CemuLogging.h" #include "Common/precompiled.h" #include "util/helpers/helpers.h" #include "config/CemuConfig.h" #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" #include <mutex> #include <condition_variable> #include <chrono> #include <fmt/printf.h> uint64 s_loggingFlagMask = cemuLog_getFlag(LogType::Force); class LoggingDispatcher { private: static inline class DefaultLoggingCallbacks : public LoggingCallbacks { } s_defaultCallbacks; LoggingCallbacks* m_loggingCallbacks = &s_defaultCallbacks; std::shared_mutex m_mutex; public: void Log(std::string_view filter, std::string_view message) { std::shared_lock lock(m_mutex); m_loggingCallbacks->Log(filter, message); } void Log(std::string_view message) { Log("", message); } void Log(std::string_view filter, std::wstring_view message) { std::shared_lock lock(m_mutex); m_loggingCallbacks->Log(filter, message); } void Log(std::wstring_view message) { Log("", message); } void setCallbacks(LoggingCallbacks* loggingCallbacks) { std::unique_lock lock(m_mutex); cemu_assert_debug(m_loggingCallbacks == &s_defaultCallbacks); m_loggingCallbacks = loggingCallbacks; } void clearCallbacks() { std::unique_lock lock(m_mutex); cemu_assert_debug(m_loggingCallbacks != &s_defaultCallbacks); m_loggingCallbacks = &s_defaultCallbacks; } } s_loggingDispatcher; void cemuLog_setCallbacks(LoggingCallbacks* loggingCallbacks) { s_loggingDispatcher.setCallbacks(loggingCallbacks); } void cemuLog_clearCallbacks() { s_loggingDispatcher.clearCallbacks(); } struct _LogContext { std::condition_variable_any log_condition; std::recursive_mutex log_mutex; std::ofstream file_stream; std::vector<std::string> text_cache; std::thread log_writer; std::atomic<bool> threadRunning = false; ~_LogContext() { // safely shut down the log writer thread threadRunning.store(false); if (log_writer.joinable()) { log_condition.notify_one(); log_writer.join(); } } }LogContext; const std::map<LogType, std::string> g_logging_window_mapping { {LogType::UnsupportedAPI, "Unsupported API calls"}, {LogType::APIErrors, "Invalid API usage"}, {LogType::CoreinitLogging, "Coreinit Logging"}, {LogType::CoreinitFile, "Coreinit File-Access"}, {LogType::CoreinitThreadSync, "Coreinit Thread-Synchronization"}, {LogType::CoreinitMem, "Coreinit Memory"}, {LogType::CoreinitMP, "Coreinit MP"}, {LogType::CoreinitThread, "Coreinit Thread"}, {LogType::NN_NFP, "nn::nfp"}, {LogType::NN_FP, "nn::fp"}, {LogType::NN_BOSS, "nn::boss"}, {LogType::GX2, "GX2"}, {LogType::SoundAPI, "Audio"}, {LogType::InputAPI, "Input"}, {LogType::Socket, "Socket"}, {LogType::Save, "Save"}, {LogType::H264, "H264"}, {LogType::NFC, "NFC"}, {LogType::NTAG, "NTAG"}, {LogType::Patches, "Graphic pack patches"}, {LogType::TextureCache, "Texture cache"}, {LogType::TextureReadback, "Texture readback"}, {LogType::OpenGLLogging, "OpenGL debug output"}, {LogType::VulkanValidation, "Vulkan validation layer"}, }; bool cemuLog_advancedPPCLoggingEnabled() { return GetConfig().advanced_ppc_logging; } void cemuLog_thread() { SetThreadName("cemuLog_thread"); while (true) { std::unique_lock lock(LogContext.log_mutex); while (LogContext.text_cache.empty()) { LogContext.log_condition.wait(lock); if (!LogContext.threadRunning.load(std::memory_order::relaxed)) return; } std::vector<std::string> cache_copy; cache_copy.swap(LogContext.text_cache); // fast copy & clear lock.unlock(); for (const auto& entry : cache_copy) LogContext.file_stream.write(entry.data(), entry.size()); LogContext.file_stream.flush(); } } fs::path cemuLog_GetLogFilePath() { return ActiveSettings::GetUserDataPath("log.txt"); } void cemuLog_createLogFile(bool triggeredByCrash) { std::unique_lock lock(LogContext.log_mutex); if (LogContext.file_stream.is_open()) return; const auto path = cemuLog_GetLogFilePath(); LogContext.file_stream.open(path, std::ios::out); if (LogContext.file_stream.fail()) { cemu_assert_debug(false); return; } LogContext.threadRunning.store(true); LogContext.log_writer = std::thread(cemuLog_thread); lock.unlock(); } void cemuLog_writeLineToLog(std::string_view text, bool date, bool new_line) { std::unique_lock lock(LogContext.log_mutex); if (date) { const auto now = std::chrono::system_clock::now(); const auto temp_time = std::chrono::system_clock::to_time_t(now); const auto& time = *std::localtime(&temp_time); auto time_str = fmt::format("[{:02d}:{:02d}:{:02d}.{:03d}] ", time.tm_hour, time.tm_min, time.tm_sec, std::chrono::duration_cast<std::chrono::milliseconds>(now - std::chrono::time_point_cast<std::chrono::seconds>(now)).count()); LogContext.text_cache.emplace_back(std::move(time_str)); } LogContext.text_cache.emplace_back(text); if (new_line) LogContext.text_cache.emplace_back("\n"); lock.unlock(); LogContext.log_condition.notify_one(); } bool cemuLog_log(LogType type, std::string_view text) { if (!cemuLog_isLoggingEnabled(type)) return false; if (LaunchSettings::Verbose()) std::cout << text << std::endl; cemuLog_writeLineToLog(text); const auto it = std::find_if(g_logging_window_mapping.cbegin(), g_logging_window_mapping.cend(), [type](const auto& entry) { return entry.first == type; }); if (it == g_logging_window_mapping.cend()) s_loggingDispatcher.Log(text); else s_loggingDispatcher.Log(it->second, text); return true; } bool cemuLog_log(LogType type, std::u8string_view text) { std::basic_string_view<char> s((char*)text.data(), text.size()); return cemuLog_log(type, s); } void cemuLog_logHexDump(LogType type, const void* data, size_t size, size_t lineSize) { const uint8* dataU8 = static_cast<const uint8*>(data); std::vector<char> hexLine; hexLine.resize(6 + lineSize * 3 + 1, ' '); for (size_t i = 0; i < size; i += lineSize) { sprintf(hexLine.data(), "%04X: ", (int)i); size_t remainingSize = std::min<size_t>(lineSize, size - i); for (size_t j=0; j<remainingSize; j++) sprintf(hexLine.data() + 6 + j * 3, "%02X ", dataU8[j]); dataU8 += remainingSize; if (remainingSize == lineSize) hexLine.resize(6 + remainingSize * 3 + 1); cemuLog_log(type, hexLine.data()); } } void cemuLog_waitForFlush() { cemuLog_createLogFile(false); std::unique_lock lock(LogContext.log_mutex); while(!LogContext.text_cache.empty()) { lock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::yield(); lock.lock(); } } // used to atomically write multiple lines to the log std::unique_lock<decltype(LogContext.log_mutex)> cemuLog_acquire() { return std::unique_lock(LogContext.log_mutex); } void cemuLog_setActiveLoggingFlags(uint64 flagMask) { s_loggingFlagMask = flagMask | cemuLog_getFlag(LogType::Force); } ================================================ FILE: src/Cemu/Logging/CemuLogging.h ================================================ #pragma once extern uint64 s_loggingFlagMask; enum class LogType : sint32 { // note: IDs must be in range 1-64 Force = 63, // always enabled Placeholder = 62, // always disabled APIErrors = 61, // Logs bad parameters or other API usage mistakes or unintended errors in OS libs. Intended for homebrew developers CoreinitFile = 0, GX2 = 1, UnsupportedAPI = 2, SoundAPI = 4, // any audio related API InputAPI = 5, // any input related API Socket = 6, Save = 7, H264 = 9, OpenGLLogging = 10, // OpenGL debug logging TextureCache = 11, // texture cache warnings and info VulkanValidation = 12, // Vulkan validation layer Patches = 14, CoreinitMem = 8, // coreinit memory functions CoreinitMP = 15, CoreinitThread = 16, CoreinitLogging = 17, // OSReport, OSConsoleWrite etc. CoreinitMemoryMapping = 18, // OSGetAvailPhysAddrRange, OSAllocVirtAddr, OSMapMemory etc. CoreinitAlarm = 22, CoreinitThreadSync = 3, PPC_IPC = 19, NN_AOC = 20, NN_PDM = 21, NN_OLV = 23, NN_NFP = 13, NN_FP = 24, NN_BOSS = 25, NN_SL = 26, TextureReadback = 29, ProcUi = 39, nlibcurl = 41, PRUDP = 40, NFC = 41, NTAG = 42, Recompiler = 60, }; template <> struct fmt::formatter<std::u8string_view> : formatter<string_view> { template <typename FormatContext> auto format(std::u8string_view v, FormatContext& ctx) { string_view s((char*)v.data(), v.size()); return formatter<string_view>::format(s, ctx); } }; void cemuLog_writeLineToLog(std::string_view text, bool date = true, bool new_line = true); inline void cemuLog_writePlainToLog(std::string_view text) { cemuLog_writeLineToLog(text, false, false); } void cemuLog_setActiveLoggingFlags(uint64 flagMask); inline uint64 cemuLog_getFlag(LogType type) { return 1ULL << (uint64)type; } inline bool cemuLog_isLoggingEnabled(LogType type) { return (s_loggingFlagMask & cemuLog_getFlag(type)) != 0; } bool cemuLog_log(LogType type, std::string_view text); bool cemuLog_log(LogType type, std::u8string_view text); void cemuLog_waitForFlush(); // wait until all log lines are written template<typename T, typename ... TArgs> bool cemuLog_log(LogType type, std::basic_string<T> formatStr, TArgs&&... args) { if (!cemuLog_isLoggingEnabled(type)) return false; if constexpr (sizeof...(TArgs) == 0) { cemuLog_log(type, std::basic_string_view<T>(formatStr.data(), formatStr.size())); return true; } else { const auto format_view = fmt::basic_string_view<T>(formatStr); #if FMT_VERSION >= 110000 const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffered_context<T>>(args...)); #else const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffer_context<T>>(args...)); #endif cemuLog_log(type, std::basic_string_view(text.data(), text.size())); } return true; } template<typename T, typename ... TArgs> bool cemuLog_log(LogType type, const T* format, TArgs&&... args) { if (!cemuLog_isLoggingEnabled(type)) return false; auto format_str = std::basic_string<T>(format); return cemuLog_log(type, format_str, std::forward<TArgs>(args)...); } #define cemuLog_logOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_log(__VA_ARGS__); } } // same as cemuLog_log, but only outputs in debug mode template<typename TFmt, typename ... TArgs> bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args) { #ifdef CEMU_DEBUG_ASSERT return cemuLog_log(type, format, std::forward<TArgs>(args)...); #else return false; #endif } #define cemuLog_logDebugOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_logDebug(__VA_ARGS__); } } // utility function for logging binary data as a hex dump void cemuLog_logHexDump(LogType type, const void* data, size_t size, size_t lineSize = 16); // cafe lib calls bool cemuLog_advancedPPCLoggingEnabled(); uint64 cemuLog_getFlag(LogType type); fs::path cemuLog_GetLogFilePath(); void cemuLog_createLogFile(bool triggeredByCrash); [[nodiscard]] std::unique_lock<std::recursive_mutex> cemuLog_acquire(); // used for logging multiple lines at once class LoggingCallbacks { public: virtual void Log(std::string_view filter, std::string_view message) {}; virtual void Log(std::string_view filter, std::wstring_view message) {}; virtual ~LoggingCallbacks() = default; }; void cemuLog_setCallbacks(LoggingCallbacks* loggingCallbacks); void cemuLog_clearCallbacks(); ================================================ FILE: src/Cemu/PPCAssembler/ppcAssembler.cpp ================================================ #include "Cemu/PPCAssembler/ppcAssembler.h" #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cemu/ExpressionParser/ExpressionParser.h" #include "util/helpers/helpers.h" #include <boost/container/small_vector.hpp> #include <boost/static_string/static_string.hpp> struct ppcAssemblerStr_t { ppcAssemblerStr_t(const char* start, const char* end) : str(start, end - start) {}; ppcAssemblerStr_t(const char* start, size_t len) : str(start, len) {}; std::string_view str; }; struct PPCAssemblerContext { PPCAssemblerInOut* ctx; boost::container::small_vector<ppcAssemblerStr_t, 8> listOperandStr; struct PPCInstructionDef* iDef; uint32 opcode; }; // table based assembler/disassembler #define operand0Bit (1<<0) #define operand1Bit (1<<1) #define operand2Bit (1<<2) #define operand3Bit (1<<3) #define operand4Bit (1<<4) #define OPC_NONE (0xFFFF) #define OPC_EXTENDED_BIT (0x8000) // use extended sub opcode enum { OP_FORM_UNUSED, OP_FORM_XL_CR, // CRXOR, CRAND etc. OP_FORM_OP3_A_CMP, OP_FORM_OP3_A_IMM, // rA, rS, rB is imm - has RC bit OP_FORM_BRANCH_S16, OP_FORM_BRANCH_S24, OP_FORM_OP2_D_HSIMM, // rD, signed imm shifted imm (high half) OP_FORM_RLWINM, OP_FORM_RLWINM_EXTENDED, // alternative mnemonics of rlwinm OP_FORM_RLWNM, OP_FORM_RLWNM_EXTENDED, // alternative mnemonics of rlwnm OP_FORM_CMP_SIMM, // cr, rD, r OP_FORM_NO_OPERAND, // FP OP_FORM_X_FP_CMP, // new generic form with operand encoding stored in the table entry, everything above is deprecated OP_FORM_DYNAMIC, }; const char* ppcAssembler_getInstructionName(uint32 ppcAsmOp) { switch (ppcAsmOp) { case PPCASM_OP_UKN: return "UKN"; case PPCASM_OP_ADDI: return "ADDI"; case PPCASM_OP_SUBI: return "SUBI"; case PPCASM_OP_ADDIS: return "ADDIS"; case PPCASM_OP_ADDIC: return "ADDIC"; case PPCASM_OP_ADDIC_: return "ADDIC."; case PPCASM_OP_ADD: return "ADD"; case PPCASM_OP_ADD_: return "ADD."; case PPCASM_OP_SUBF: return "SUBF"; case PPCASM_OP_SUBF_: return "SUBF."; case PPCASM_OP_SUBFC: return "SUBFC"; case PPCASM_OP_SUBFC_: return "SUBFC."; case PPCASM_OP_SUBFE: return "SUBFE"; case PPCASM_OP_SUBFE_: return "SUBFE."; case PPCASM_OP_SUBFIC: return "SUBFIC"; case PPCASM_OP_SUB: return "SUB"; case PPCASM_OP_SUB_: return "SUB."; case PPCASM_OP_MULLI: return "MULLI"; case PPCASM_OP_MULLW: return "MULLW"; case PPCASM_OP_MULLW_: return "MULLW."; case PPCASM_OP_MULHW: return "MULHW"; case PPCASM_OP_MULHW_: return "MULHW."; case PPCASM_OP_MULHWU: return "MULHWU"; case PPCASM_OP_MULHWU_: return "MULHWU."; case PPCASM_OP_DIVW: return "DIVW"; case PPCASM_OP_DIVW_: return "DIVW."; case PPCASM_OP_DIVWU: return "DIVWU"; case PPCASM_OP_DIVWU_: return "DIVWU."; case PPCASM_OP_AND: return "AND"; case PPCASM_OP_AND_: return "AND."; case PPCASM_OP_ANDC: return "ANDC"; case PPCASM_OP_ANDC_: return "ANDC."; case PPCASM_OP_ANDI_: return "ANDI."; case PPCASM_OP_ANDIS_: return "ANDIS."; case PPCASM_OP_OR: return "OR"; case PPCASM_OP_OR_: return "OR."; case PPCASM_OP_ORI: return "ORI"; case PPCASM_OP_ORIS: return "ORIS"; case PPCASM_OP_ORC: return "ORC"; case PPCASM_OP_XOR: return "XOR"; case PPCASM_OP_NOR: return "NOR"; case PPCASM_OP_NOR_: return "NOR."; case PPCASM_OP_NOT: return "NOT"; case PPCASM_OP_NOT_: return "NOT."; case PPCASM_OP_NEG: return "NEG"; case PPCASM_OP_NEG_: return "NEG."; case PPCASM_OP_XORI: return "XORI"; case PPCASM_OP_XORIS: return "XORIS"; case PPCASM_OP_SRAW: return "SRAW"; case PPCASM_OP_SRAW_: return "SRAW."; case PPCASM_OP_SRAWI: return "SRAWI"; case PPCASM_OP_SRAWI_: return "SRAWI."; case PPCASM_OP_SLW: return "SLW"; case PPCASM_OP_SLW_: return "SLW."; case PPCASM_OP_SRW: return "SRW"; case PPCASM_OP_SRW_: return "SRW."; case PPCASM_OP_RLWINM: return "RLWINM"; case PPCASM_OP_RLWINM_: return "RLWINM."; case PPCASM_OP_RLWIMI: return "RLWIMI"; case PPCASM_OP_RLWIMI_: return "RLWIMI."; case PPCASM_OP_EXTLWI: return "EXTLWI"; case PPCASM_OP_EXTLWI_: return "EXTLWI."; case PPCASM_OP_EXTRWI: return "EXTRWI"; case PPCASM_OP_EXTRWI_: return "EXTRWI."; case PPCASM_OP_ROTLWI: return "ROTLWI"; case PPCASM_OP_ROTLWI_: return "ROTLWI."; case PPCASM_OP_ROTRWI: return "ROTRWI"; case PPCASM_OP_ROTRWI_: return "ROTRWI."; case PPCASM_OP_SLWI: return "SLWI"; case PPCASM_OP_SLWI_: return "SLWI."; case PPCASM_OP_SRWI: return "SRWI"; case PPCASM_OP_SRWI_: return "SRWI."; case PPCASM_OP_CLRLWI: return "CLRLWI"; case PPCASM_OP_CLRLWI_: return "CLRLWI."; case PPCASM_OP_CLRRWI: return "CLRRWI"; case PPCASM_OP_CLRRWI_: return "CLRRWI."; case PPCASM_OP_RLWNM: return "RLWNM"; case PPCASM_OP_RLWNM_: return "RLWNM."; case PPCASM_OP_ROTLW: return "ROTLW"; case PPCASM_OP_ROTLW_: return "ROTLW."; case PPCASM_OP_CMPWI: return "CMPWI"; case PPCASM_OP_CMPLWI: return "CMPLWI"; case PPCASM_OP_CMPW: return "CMPW"; case PPCASM_OP_CMPLW: return "CMPLW"; case PPCASM_OP_MR: return "MR"; case PPCASM_OP_MR_: return "MR."; case PPCASM_OP_EXTSB: return "EXTSB"; case PPCASM_OP_EXTSH: return "EXTSH"; case PPCASM_OP_CNTLZW: return "CNTLZW"; case PPCASM_OP_EXTSB_: return "EXTSB."; case PPCASM_OP_EXTSH_: return "EXTSH."; case PPCASM_OP_CNTLZW_: return "CNTLZW."; case PPCASM_OP_MFSPR: return "MFSPR"; case PPCASM_OP_MTSPR: return "MTSPR"; case PPCASM_OP_LMW: return "LMW"; case PPCASM_OP_LWZ: return "LWZ"; case PPCASM_OP_LWZU: return "LWZU"; case PPCASM_OP_LWZX: return "LWZX"; case PPCASM_OP_LWZUX: return "LWZUX"; case PPCASM_OP_LHZ: return "LHZ"; case PPCASM_OP_LHZU: return "LHZU"; case PPCASM_OP_LHZX: return "LHZX"; case PPCASM_OP_LHZUX: return "LHZUX"; case PPCASM_OP_LHA: return "LHA"; case PPCASM_OP_LHAU: return "LHAU"; case PPCASM_OP_LHAX: return "LHAX"; case PPCASM_OP_LHAUX: return "LHAUX"; case PPCASM_OP_LBZ: return "LBZ"; case PPCASM_OP_LBZU: return "LBZU"; case PPCASM_OP_LBZX: return "LBZX"; case PPCASM_OP_LBZUX: return "LBZUX"; case PPCASM_OP_STMW: return "STMW"; case PPCASM_OP_STW: return "STW"; case PPCASM_OP_STWU: return "STWU"; case PPCASM_OP_STWX: return "STWX"; case PPCASM_OP_STWUX: return "STWUX"; case PPCASM_OP_STH: return "STH"; case PPCASM_OP_STHU: return "STHU"; case PPCASM_OP_STHX: return "STHX"; case PPCASM_OP_STHUX: return "STHUX"; case PPCASM_OP_STB: return "STB"; case PPCASM_OP_STBU: return "STBU"; case PPCASM_OP_STBX: return "STBX"; case PPCASM_OP_STBUX: return "STBUX"; case PPCASM_OP_STSWI: return "STSWI"; case PPCASM_OP_STWBRX: return "STWBRX"; case PPCASM_OP_STHBRX: return "STHBRX"; case PPCASM_OP_LWARX: return "LWARX"; case PPCASM_OP_STWCX_: return "STWCX."; case PPCASM_OP_B: return "B"; case PPCASM_OP_BL: return "BL"; case PPCASM_OP_BA: return "BA"; case PPCASM_OP_BLA: return "BLA"; case PPCASM_OP_BC: return "BC"; case PPCASM_OP_BNE: return "BNE"; case PPCASM_OP_BEQ: return "BEQ"; case PPCASM_OP_BGE: return "BGE"; case PPCASM_OP_BGT: return "BGT"; case PPCASM_OP_BLT: return "BLT"; case PPCASM_OP_BLE: return "BLE"; case PPCASM_OP_BDZ: return "BDZ"; case PPCASM_OP_BDNZ: return "BDNZ"; case PPCASM_OP_BLR: return "BLR"; case PPCASM_OP_BLTLR: return "BLTLR"; case PPCASM_OP_BLELR: return "BLELR"; case PPCASM_OP_BEQLR: return "BEQLR"; case PPCASM_OP_BGELR: return "BGELR"; case PPCASM_OP_BGTLR: return "BGTLR"; case PPCASM_OP_BNELR: return "BNELR"; case PPCASM_OP_BCTR: return "BCTR"; case PPCASM_OP_BCTRL: return "BCTRL"; case PPCASM_OP_LFS: return "LFS"; case PPCASM_OP_LFSU: return "LFSU"; case PPCASM_OP_LFSX: return "LFSX"; case PPCASM_OP_LFSUX: return "LFSUX"; case PPCASM_OP_LFD: return "LFD"; case PPCASM_OP_LFDU: return "LFDU"; case PPCASM_OP_LFDX: return "LFDX"; case PPCASM_OP_LFDUX: return "LFDUX"; case PPCASM_OP_STFS: return "STFS"; case PPCASM_OP_STFSU: return "STFSU"; case PPCASM_OP_STFSX: return "STFSX"; case PPCASM_OP_STFSUX: return "STFSUX"; case PPCASM_OP_STFD: return "STFD"; case PPCASM_OP_STFDU: return "STFDU"; case PPCASM_OP_STFDX: return "STFDX"; case PPCASM_OP_STFDUX: return "STFDUX"; case PPCASM_OP_STFIWX: return "STFIWX"; case PPCASM_OP_PS_MERGE00: return "PS_MERGE00"; case PPCASM_OP_PS_MERGE01: return "PS_MERGE01"; case PPCASM_OP_PS_MERGE10: return "PS_MERGE10"; case PPCASM_OP_PS_MERGE11: return "PS_MERGE11"; case PPCASM_OP_PS_ADD: return "PS_ADD"; case PPCASM_OP_PS_SUB: return "PS_SUB"; case PPCASM_OP_PS_DIV: return "PS_DIV"; case PPCASM_OP_PS_MUL: return "PS_MUL"; case PPCASM_OP_PS_MADD: return "PS_MADD"; case PPCASM_OP_PS_MSUB: return "PS_MSUB"; case PPCASM_OP_PS_NMADD: return "PS_NMADD"; case PPCASM_OP_PS_NMSUB: return "PS_NMSUB"; case PPCASM_OP_FMR: return "FMR"; case PPCASM_OP_FABS: return "FABS"; case PPCASM_OP_FNEG: return "FNEG"; case PPCASM_OP_FRSP: return "FRSP"; case PPCASM_OP_FRSQRTE: return "FRSQRTE"; case PPCASM_OP_FADD: return "FADD"; case PPCASM_OP_FADDS: return "FADDS"; case PPCASM_OP_FSUB: return "FSUB"; case PPCASM_OP_FSUBS: return "FSUBS"; case PPCASM_OP_FMUL: return "FMUL"; case PPCASM_OP_FMULS: return "FMULS"; case PPCASM_OP_FDIV: return "FDIV"; case PPCASM_OP_FDIVS: return "FDIVS"; case PPCASM_OP_FMADD: return "FMADD"; case PPCASM_OP_FMADDS: return "FMADDS"; case PPCASM_OP_FNMADD: return "FNMADD"; case PPCASM_OP_FNMADDS: return "FNMADDS"; case PPCASM_OP_FMSUB: return "FMSUB"; case PPCASM_OP_FMSUBS: return "FMSUBS"; case PPCASM_OP_FNMSUB: return "FNMSUB"; case PPCASM_OP_FNMSUBS: return "FNMSUBS"; case PPCASM_OP_FCTIWZ: return "FCTIWZ"; case PPCASM_OP_FCMPU: return "FCMPU"; case PPCASM_OP_FCMPO: return "FCMPO"; case PPCASM_OP_ISYNC: return "ISYNC"; case PPCASM_OP_NOP: return "NOP"; case PPCASM_OP_LI: return "LI"; case PPCASM_OP_LIS: return "LIS"; case PPCASM_OP_MFLR: return "MFLR"; case PPCASM_OP_MTLR: return "MTLR"; case PPCASM_OP_MFCTR: return "MFCTR"; case PPCASM_OP_MTCTR: return "MTCTR"; case PPCASM_OP_CROR: return "CROR"; case PPCASM_OP_CRNOR: return "CRNOR"; case PPCASM_OP_CRORC: return "CRORC"; case PPCASM_OP_CRXOR: return "CRXOR"; case PPCASM_OP_CREQV: return "CREQV"; case PPCASM_OP_CRAND: return "CRAND"; case PPCASM_OP_CRNAND: return "CRNAND"; case PPCASM_OP_CRANDC: return "CRANDC"; case PPCASM_OP_CRSET: return "CRSET"; case PPCASM_OP_CRCLR: return "CRCLR"; case PPCASM_OP_CRMOVE: return "CRMOVE"; case PPCASM_OP_CRNOT: return "CRNOT"; default: return "UNDEF"; } return ""; } #define C_MASK_RC (PPC_OPC_RC) #define C_MASK_LK (PPC_OPC_LK) #define C_MASK_AA (PPC_OPC_AA) #define C_MASK_OE (1<<10) #define C_MASK_BO (0x1F<<21) #define C_MASK_BO_COND (0x1E<<21) // BO mask for true/false conditions. With hint bit excluded #define C_MASK_BI_CRBIT (0x3<<16) #define C_MASK_BI_CRIDX (0x7<<18) #define C_BIT_RC (PPC_OPC_RC) #define C_BIT_LK (PPC_OPC_LK) #define C_BIT_AA (PPC_OPC_AA) #define C_BIT_BO_FALSE (0x4<<21) // CR bit not set #define C_BIT_BO_TRUE (0xC<<21) // CR bit set #define C_BIT_BO_ALWAYS (0x14<<21) #define C_BIT_BO_DNZ (0x10<<21) #define C_BIT_BO_DZ (0x12<<21) #define C_BITS_BI_LT (0x0<<16) #define C_BITS_BI_GT (0x1<<16) #define C_BITS_BI_EQ (0x2<<16) #define C_BITS_BI_SO (0x3<<16) void ppcAssembler_setError(PPCAssemblerInOut* ctx, std::string_view errorMsg); bool ppcOp_extraCheck_extlwi(uint32 opcode) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); return MB == 0; } bool ppcOp_extraCheck_extrwi(uint32 opcode) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); if (ME != 31) return false; sint8 n = 32 - MB; sint8 b = SH - n; return b >= 0; } bool ppcOp_extraCheck_slwi(uint32 opcode) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); return MB == 0 && SH == (31 - ME); } bool ppcOp_extraCheck_srwi(uint32 opcode) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); return ME == 31 && (32 - SH) == MB; } bool ppcOp_extraCheck_clrlwi(uint32 opcode) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); return SH == 0 && ME == 31; } bool ppcOp_extraCheck_clrrwi(uint32 opcode) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); return SH == 0 && MB == 0; } bool ppcOp_extraCheck_rotlw(uint32 opcode) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); return MB == 0 && ME == 31; } #define FLG_DEFAULT 0 #define FLG_SKIP_OP0 operand0Bit #define FLG_SKIP_OP1 operand1Bit #define FLG_SKIP_OP2 operand2Bit #define FLG_SKIP_OP3 operand3Bit #define FLG_SKIP_OP4 operand4Bit #define FLG_SWAP_OP0_OP1 (1<<6) // todo - maybe this should be implemented as a fully configurable matrix of indices instead of predefined constants? #define FLG_SWAP_OP1_OP2 (1<<7) #define FLG_SWAP_OP2_OP3 (1<<8) #define FLG_UNSIGNED_IMM (1<<10) // always consider immediate unsigned but still allow signed values when assembling class EncodedOperand_None { public: EncodedOperand_None() {} bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) { if (index < assemblerCtx->listOperandStr.size() && !assemblerCtx->listOperandStr[index].str.empty()) { ppcAssembler_setError(assemblerCtx->ctx, "Too many operands"); return false; } return true; } void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) { } }; static int _parseRegIndex(std::string_view sv, const char* prefix) { while (*prefix) { if (sv.empty()) return -1; char c = sv[0]; if (c >= 'A' && c <= 'Z') c -= ('A' - 'a'); if (c != *prefix) return -1; sv.remove_prefix(1); prefix++; } int r = 0; const std::from_chars_result result = std::from_chars(sv.data(), sv.data() + sv.size(), r); if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) return -1; if (result.ptr != sv.data() + sv.size()) return -1; return r; } template<bool TAllowEAZero = false> // if true, "r0" can be substituted with "0" class EncodedOperand_GPR { public: EncodedOperand_GPR(uint8 bitPos) : m_bitPos(bitPos) {} bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) { if (index >= assemblerCtx->listOperandStr.size()) { ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); return false; } auto operandStr = assemblerCtx->listOperandStr[index].str; if constexpr (TAllowEAZero) { if (operandStr.size() == 1 && operandStr[0] == '0') { opcode &= ~((uint32)0x1F << m_bitPos); opcode |= ((uint32)0 << m_bitPos); return true; } } sint32 regIndex = _parseRegIndex(operandStr, "r"); if (regIndex < 0 || regIndex >= 32) { ppcAssembler_setError(assemblerCtx->ctx, fmt::format("Operand \"{}\" is not a valid GPR (expected r0 - r31)", assemblerCtx->listOperandStr[index].str)); return false; } opcode &= ~((uint32)0x1F << m_bitPos); opcode |= ((uint32)regIndex << m_bitPos); return true; } void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) { uint32 regIndex = (opcode >> m_bitPos) & 0x1F; disInstr->operandMask |= (1 << index); disInstr->operand[index].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[index].registerIndex = regIndex; } private: uint8 m_bitPos; }; class EncodedOperand_FPR { public: EncodedOperand_FPR(uint8 bitPos) : m_bitPos(bitPos) {} bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) { if (index >= assemblerCtx->listOperandStr.size()) { ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); return false; } sint32 regIndex = _parseRegIndex(assemblerCtx->listOperandStr[index].str, "f"); if (regIndex < 0 || regIndex >= 32) { ppcAssembler_setError(assemblerCtx->ctx, fmt::format("Operand \"{}\" is not a valid FPR (expected f0 - f31)", assemblerCtx->listOperandStr[index].str)); return false; } opcode &= ~((uint32)0x1F << m_bitPos); opcode |= ((uint32)regIndex << m_bitPos); return true; } void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) { uint32 regIndex = (opcode >> m_bitPos) & 0x1F; disInstr->operandMask |= (1<<index); disInstr->operand[index].type = PPCASM_OPERAND_TYPE_FPR; disInstr->operand[index].registerIndex = regIndex; } private: uint8 m_bitPos; }; class EncodedOperand_SPR { public: EncodedOperand_SPR() {} bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) { if (index >= assemblerCtx->listOperandStr.size()) { ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); return false; } sint32 regIndex = _parseRegIndex(assemblerCtx->listOperandStr[index].str, "spr"); if (regIndex < 0 || regIndex >= 1024) { ppcAssembler_setError(assemblerCtx->ctx, fmt::format("Operand \"{}\" is not a valid GPR (expected spr0 - spr1023)", assemblerCtx->listOperandStr[index].str)); return false; } sint32 sprLow = (regIndex) & 0x1F; sint32 sprHigh = (regIndex>>5) & 0x1F; opcode &= ~((uint32)0x1F << 16); opcode |= ((uint32)sprLow << 16); opcode &= ~((uint32)0x1F << 11); opcode |= ((uint32)sprHigh << 11); return true; } void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) { uint32 sprLow = (opcode >> 16) & 0x1F; uint32 sprHigh = (opcode >> 11) & 0x1F; disInstr->operandMask |= (1 << index); disInstr->operand[index].type = PPCASM_OPERAND_TYPE_SPR; disInstr->operand[index].registerIndex = sprLow | (sprHigh << 5); } }; class EncodedOperand_MemLoc { public: EncodedOperand_MemLoc() {} bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) { if (index >= assemblerCtx->listOperandStr.size()) { ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); return false; } std::basic_string_view<char> svOpText(assemblerCtx->listOperandStr[index].str); // first we parse the memory register part at the end auto startPtr = svOpText.data(); auto endPtr = startPtr + svOpText.size(); // trim whitespaces while (endPtr > startPtr) { const char c = endPtr[-1]; if (c != ' ' && c != '\t') break; endPtr--; } if (endPtr == startPtr || endPtr[-1] != ')') { ppcAssembler_setError(assemblerCtx->ctx, fmt::format("\'{}\' does not end with valid memory register syntax. Memory operand must have the form offset(gpr). Example: 0x20(r3)", svOpText)); return false; } endPtr--; // find parenthesis open auto memoryRegEnd = endPtr; const char* memoryRegBegin = nullptr; while (endPtr > startPtr) { const char c = endPtr[-1]; if (c == '(') { memoryRegBegin = endPtr; endPtr--; // move end pointer beyond the parenthesis break; } endPtr--; } if (memoryRegBegin == nullptr) { ppcAssembler_setError(assemblerCtx->ctx, fmt::format("\'{}\' does not end with valid memory register syntax. Memory operand must have the form offset(gpr). Example: 0x20(r3)", svOpText)); return false; } std::string_view svExpressionPart(startPtr, endPtr - startPtr); std::string_view svRegPart(memoryRegBegin, memoryRegEnd - memoryRegBegin); sint32 memGpr = _parseRegIndex(svRegPart, "r"); //if (_ppcAssembler_parseRegister(svRegPart, "r", memGpr) == false || (memGpr < 0 || memGpr >= 32)) //{ // sprintf(_assemblerErrorMessageDepr, "\'%.*s\' is not a valid GPR", (int)(memoryRegEnd - memoryRegBegin), memoryRegBegin); // ppcAssembler_setError(internalCtx.ctx, _assemblerErrorMessageDepr); // return false; //} if (memGpr < 0 || memGpr >= 32) { ppcAssembler_setError(assemblerCtx->ctx, fmt::format("Memory operand register \"{}\" is not a valid GPR (expected r0 - r31)", svRegPart)); return false; } opcode &= ~(0x1F << 16); opcode |= (memGpr << 16); // parse expression ExpressionParser ep; double immD = 0.0f; try { if (ep.IsConstantExpression(svExpressionPart)) { immD = ep.Evaluate(svExpressionPart); sint32 imm = (sint32)immD; if (imm < -32768 || imm > 32767) { std::string msg = fmt::format("\"{}\" evaluates to offset out of range (Valid range is -32768 to 32767)", svExpressionPart); ppcAssembler_setError(assemblerCtx->ctx, msg); return false; } opcode &= ~0xFFFF; opcode |= ((uint32)imm & 0xFFFF); } else { assemblerCtx->ctx->list_relocs.emplace_back(PPCASM_RELOC::U32_MASKED_IMM, std::string(svExpressionPart), 0, 0, 16); return true; } } catch (std::exception* e) { std::string msg = fmt::format("\"{}\" could not be evaluated. Error: {}", svExpressionPart, e->what()); ppcAssembler_setError(assemblerCtx->ctx, msg); return false; } return true; } void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) { uint32 imm = (opcode & 0xFFFF); if (imm & 0x8000) imm |= 0xFFFF0000; uint32 regIndex = (opcode >> 16) & 0x1F; disInstr->operandMask |= (1 << index); disInstr->operand[index].type = PPCASM_OPERAND_TYPE_MEM; disInstr->operand[index].registerIndex = regIndex; disInstr->operand[index].immS32 = (sint32)imm; disInstr->operand[index].immWidth = 16; disInstr->operand[index].isSignedImm = true; } }; bool _CanStoreInteger(uint32 value, uint32 numBits, bool isSigned) { if (isSigned) { sint32 storedValue = (sint32)value; storedValue <<= (32 - numBits); storedValue >>= (32 - numBits); return (uint32)storedValue == value; } // unsigned uint32 storedValue = value; storedValue <<= (32 - numBits); storedValue >>= (32 - numBits); return storedValue == value; } class EncodedOperand_IMM { public: EncodedOperand_IMM(uint8 bitPos, uint8 bits, bool isSigned, bool negate = false, bool extendedRange = false) : m_bitPos(bitPos), m_bits(bits), m_isSigned(isSigned), m_negate(negate), m_extendedRange(extendedRange) {} bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) { if (index >= assemblerCtx->listOperandStr.size()) { ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); return false; } // parse expression std::string expressionString(assemblerCtx->listOperandStr[index].str); if (m_negate) { expressionString.insert(0, "0-("); expressionString.append(")"); } ExpressionParser ep; double immD = 0.0f; try { if (ep.IsConstantExpression(expressionString)) { immD = ep.Evaluate(expressionString); } else { assemblerCtx->ctx->list_relocs.emplace_back(PPCASM_RELOC::U32_MASKED_IMM, expressionString, 0, m_bitPos, m_bits); return true; } } catch (std::exception* e) { // check if expression is invalid or if it contains unknown constants std::string msg = fmt::format("\"{}\" could not be evaluated. Error: {}", expressionString.c_str(), e->what()); ppcAssembler_setError(assemblerCtx->ctx, msg); return false; } uint32 imm = (uint32)(sint32)immD; bool canStore = _CanStoreInteger(imm, m_bits, m_isSigned); if(!canStore && m_extendedRange) // always allow unsigned canStore = _CanStoreInteger(imm, m_bits, false); if (!canStore) { std::string msg = fmt::format("Value of operand \"{}\" is out of range", assemblerCtx->listOperandStr[index].str); ppcAssembler_setError(assemblerCtx->ctx, msg); return false; } uint32 mask = (1<<m_bits)-1; imm &= mask; opcode &= ~(mask << m_bitPos); opcode |= (imm << m_bitPos); return true; } void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) { uint32 mask = (1 << m_bits) - 1; uint32 immValue = (opcode >> m_bitPos) & mask; if (m_isSigned) { sint32 tmpValue = (sint32)immValue; tmpValue <<= (32 - m_bits); tmpValue >>= (32 - m_bits); immValue = (uint32)tmpValue; } disInstr->operandMask |= (1 << index); disInstr->operand[index].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[index].immS32 = immValue; disInstr->operand[index].immWidth = m_bits; disInstr->operand[index].isSignedImm = m_isSigned; } private: uint8 m_bitPos; uint8 m_bits; bool m_isSigned; bool m_negate; bool m_extendedRange; }; class EncodedOperand_U5Reverse { public: EncodedOperand_U5Reverse(uint8 bitPos, uint8 base) : m_bitPos(bitPos), m_base(base) {} bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) { if (index >= assemblerCtx->listOperandStr.size()) { ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); return false; } // parse expression std::string expressionString(assemblerCtx->listOperandStr[index].str); expressionString.insert(0, fmt::format("{}-(", m_base)); expressionString.append(")"); ExpressionParser ep; double immD = 0.0f; try { if (ep.IsConstantExpression(expressionString)) { immD = ep.Evaluate(expressionString); } else { assemblerCtx->ctx->list_relocs.emplace_back(PPCASM_RELOC::U32_MASKED_IMM, expressionString, 0, m_bitPos, 5); return true; } } catch (std::exception* e) { // check if expression is invalid or if it contains unknown constants std::string msg = fmt::format("\"{}\" could not be evaluated. Error: {}", expressionString.c_str(), e->what()); ppcAssembler_setError(assemblerCtx->ctx, msg); return false; } uint32 imm = (uint32)(sint32)immD; bool canStore = _CanStoreInteger(imm, 5, false); if (!canStore) { std::string msg = fmt::format("Value of operand \"{}\" is out of range", assemblerCtx->listOperandStr[index].str); ppcAssembler_setError(assemblerCtx->ctx, msg); return false; } uint32 mask = (1 << 5) - 1; imm &= mask; opcode &= ~(mask << m_bitPos); opcode |= (imm << m_bitPos); return true; } void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) { uint32 mask = (1 << 5) - 1; uint32 immValue = (opcode >> m_bitPos) & mask; immValue = m_base - immValue; disInstr->operandMask |= (1 << index); disInstr->operand[index].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[index].immS32 = immValue; disInstr->operand[index].immWidth = 5; disInstr->operand[index].isSignedImm = false; } private: uint8 m_bitPos; uint8 m_base; }; class EncodedConstraint_None { public: EncodedConstraint_None() {} void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) { } bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) { return true; } }; class EncodedConstraint_MirrorU5 { public: EncodedConstraint_MirrorU5(uint8 srcBitPos, uint8 dstBitPos) : m_srcBitPos(srcBitPos), m_dstBitPos(dstBitPos) {} void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) { uint32 regIndex = (opcode >> m_srcBitPos) & 0x1F; opcode &= ~((uint32)0x1F << m_dstBitPos); opcode |= ((uint32)regIndex << m_dstBitPos); } bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) { uint32 regSrc = (opcode >> m_srcBitPos) & 0x1F; uint32 regDst = (opcode >> m_dstBitPos) & 0x1F; return regSrc == regDst; } private: const uint8 m_srcBitPos, m_dstBitPos; }; // same as _MirrorU5, but the destination must match invBase - src class EncodedConstraint_MirrorReverseU5 { public: EncodedConstraint_MirrorReverseU5(uint8 srcBitPos, uint8 dstBitPos, uint8 invBase) : m_srcBitPos(srcBitPos), m_dstBitPos(dstBitPos), m_invBase(invBase) {} void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) { uint32 regIndex = (opcode >> m_srcBitPos) & 0x1F; regIndex = m_invBase - regIndex; opcode &= ~((uint32)0x1F << m_dstBitPos); opcode |= ((uint32)regIndex << m_dstBitPos); } bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) { uint32 regSrc = (opcode >> m_srcBitPos) & 0x1F; uint32 regDst = (opcode >> m_dstBitPos) & 0x1F; regSrc = m_invBase - regSrc; if (regSrc >= 32) return false; return regSrc == regDst; } private: const uint8 m_srcBitPos, m_dstBitPos, m_invBase; }; class EncodedConstraint_FixedU5 { public: EncodedConstraint_FixedU5(uint8 bitPos, uint8 expectedReg) : m_bitPos(bitPos), m_expectedReg(expectedReg) {} void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) { uint32 regIndex = m_expectedReg; opcode &= ~((uint32)0x1F << m_bitPos); opcode |= ((uint32)regIndex << m_bitPos); } bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) { uint32 reg = (opcode >> m_bitPos) & 0x1F; return (uint8)reg == m_expectedReg; } private: const uint8 m_bitPos; const uint8 m_expectedReg; }; using EncodedConstraint_FixedRegister = EncodedConstraint_FixedU5; using EncodedConstraint_MirrorRegister = EncodedConstraint_MirrorU5; // checks bit value, but does not overwrite it on assemble class EncodedConstraint_CheckSignBit { public: EncodedConstraint_CheckSignBit(uint8 bitPos, uint8 expectedValue) : m_bitPos(bitPos), m_expectedValue(expectedValue) {} void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) { // dont overwrite the existing sign bit } bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) { return ((opcode >> m_bitPos) & 1) == m_expectedValue; } private: const uint8 m_bitPos; const uint8 m_expectedValue; }; //class EncodedConstraint_ExpectBit //{ //public: // EncodedConstraint_ExpectBit(uint8 bitPos, bool val) : m_bitPos(bitPos), m_expectedValue(val ? 1 : 0) {} // // void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) // { // if (m_expectedValue) // opcode |= (1 << m_bitPos); // else // opcode &= ~(1 << m_bitPos); // } // // bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) // { // return ((opcode >> m_bitPos) & 1) == m_expectedValue; // } // //private: // const uint8 m_bitPos; // const uint8 m_expectedValue; //}; class EncodedConstraint_FixedSPR { public: EncodedConstraint_FixedSPR(uint16 sprIndex) : m_sprIndex(sprIndex) {} void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) { sint32 sprLow = (m_sprIndex) & 0x1F; sint32 sprHigh = (m_sprIndex >> 5) & 0x1F; opcode &= ~((uint32)0x1F << 16); opcode |= ((uint32)sprLow << 16); opcode &= ~((uint32)0x1F << 11); opcode |= ((uint32)sprHigh << 11); } bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) { uint32 sprLow = (opcode >> 16) & 0x1F; uint32 sprHigh = (opcode >> 11) & 0x1F; uint32 sprIndex = sprLow | (sprHigh << 5); return (uint16)sprIndex == m_sprIndex; } private: const uint16 m_sprIndex; }; struct PPCInstructionDef { uint16 ppcAsmOp; // instruction type identifier uint8 priority; uint16 opc0; uint16 opc1; uint16 opc2; uint8 instructionForm; // encoding uint16 flags; uint32 maskBits; uint32 compareBits; bool(*extraCheck)(uint32 opcode); // used for unique criteria (e.g. SRWI checks SH/mask) -> Replaced by constraints std::array<std::variant<EncodedOperand_None, EncodedOperand_GPR<false>, EncodedOperand_GPR<true>, EncodedOperand_FPR, EncodedOperand_SPR, EncodedOperand_IMM, EncodedOperand_U5Reverse, EncodedOperand_MemLoc>, 4> encodedOperands{}; // note: The default constructor of std::variant will default-construct the first type (which we want to be EncodedOperand_None) std::array<std::variant<EncodedConstraint_None, EncodedConstraint_MirrorRegister, EncodedConstraint_MirrorReverseU5, EncodedConstraint_FixedRegister, EncodedConstraint_FixedSPR, EncodedConstraint_CheckSignBit>, 3> constraints{}; }; PPCInstructionDef ppcInstructionTable[] = { {PPCASM_OP_PS_MERGE00, 0, 4, 16, 16, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, {PPCASM_OP_PS_MERGE01, 0, 4, 16, 17, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, {PPCASM_OP_PS_MERGE10, 0, 4, 16, 18, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, {PPCASM_OP_PS_MERGE11, 0, 4, 16, 19, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, {PPCASM_OP_PS_DIV, 0, 4, 18, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, {PPCASM_OP_PS_SUB, 0, 4, 20, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, {PPCASM_OP_PS_ADD, 0, 4, 21, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, {PPCASM_OP_PS_MUL, 0, 4, 25, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6)}}, {PPCASM_OP_PS_MSUB, 0, 4, 28, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11)}}, {PPCASM_OP_PS_MADD, 0, 4, 29, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11)}}, {PPCASM_OP_PS_NMSUB, 0, 4, 30, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11)}}, {PPCASM_OP_PS_NMADD, 0, 4, 31, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11)}}, {PPCASM_OP_MULLI, 0, 7, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true)}}, {PPCASM_OP_SUBFIC, 0, 8, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true)} }, {PPCASM_OP_CMPLWI, 0, 10, OPC_NONE, OPC_NONE, OP_FORM_CMP_SIMM, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_CMPWI, 0, 11, OPC_NONE, OPC_NONE, OP_FORM_CMP_SIMM, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_ADDIC, 0, 12, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true)}}, {PPCASM_OP_ADDIC_, 0, 13, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true)}}, {PPCASM_OP_ADDI, 0, 14, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true)}}, {PPCASM_OP_SUBI, 1, 14, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true, true)}, {EncodedConstraint_CheckSignBit(15, 1)}}, // special form of ADDI for negative immediate {PPCASM_OP_LI, 1, 14, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, true, false, true)}, {EncodedConstraint_FixedRegister(16, 0)}}, {PPCASM_OP_ADDIS, 0, 15, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true, false, true)}}, {PPCASM_OP_LIS, 1, 15, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, true, false, true)}, {EncodedConstraint_FixedRegister(16, 0)}}, {PPCASM_OP_BC, 0, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_BNE, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_FALSE | C_BITS_BI_EQ, nullptr}, {PPCASM_OP_BEQ, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_TRUE | C_BITS_BI_EQ, nullptr}, {PPCASM_OP_BGE, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_FALSE | C_BITS_BI_LT, nullptr}, {PPCASM_OP_BGT, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_TRUE | C_BITS_BI_GT, nullptr}, {PPCASM_OP_BLT, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_TRUE | C_BITS_BI_LT, nullptr}, {PPCASM_OP_BLE, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_FALSE | C_BITS_BI_GT, nullptr}, {PPCASM_OP_BDZ, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO, C_BIT_BO_DZ, nullptr}, {PPCASM_OP_BDNZ, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO, C_BIT_BO_DNZ, nullptr}, {PPCASM_OP_B, 0, 18, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S24, FLG_DEFAULT, C_MASK_LK | C_MASK_AA, 0, nullptr}, {PPCASM_OP_BL, 0, 18, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S24, FLG_DEFAULT, C_MASK_LK | C_MASK_AA, C_BIT_LK, nullptr}, {PPCASM_OP_BA, 0, 18, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S24, FLG_DEFAULT, C_MASK_LK | C_MASK_AA, C_BIT_AA, nullptr}, {PPCASM_OP_BLA, 0, 18, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S24, FLG_DEFAULT, C_MASK_LK | C_MASK_AA, C_BIT_LK|C_BIT_AA, nullptr}, {PPCASM_OP_BLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_LK, C_BIT_BO_ALWAYS, nullptr}, {PPCASM_OP_BLTLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_LT, nullptr}, // less {PPCASM_OP_BGTLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_GT, nullptr}, // greater {PPCASM_OP_BEQLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_EQ, nullptr}, // equal {PPCASM_OP_BLELR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_GT, nullptr}, // less or equal (not greater) {PPCASM_OP_BGELR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_LT, nullptr}, // greater or equal (not less) {PPCASM_OP_BNELR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_EQ, nullptr}, // not equal {PPCASM_OP_ISYNC, 0, 19, 150, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_CRNOR, 0, 19, 33, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_CRANDC, 0, 19, 129, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_CRXOR, 0, 19, 193, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_CRNAND, 0, 19, 255, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_CRAND, 0, 19, 257, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_CREQV, 0, 19, 289, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_CRORC, 0, 19, 417, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_CROR, 0, 19, 449, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_BCTR, 0, 19, 528, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_LK, C_BIT_BO_ALWAYS, nullptr}, {PPCASM_OP_BCTRL, 0, 19, 528, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_LK, C_BIT_BO_ALWAYS | C_BIT_LK, nullptr}, {PPCASM_OP_RLWIMI, 0, 20, OPC_NONE, OPC_NONE, OP_FORM_RLWINM, FLG_DEFAULT, C_MASK_RC, 0, nullptr}, {PPCASM_OP_RLWIMI_, 0, 20, OPC_NONE, OPC_NONE, OP_FORM_RLWINM, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr}, {PPCASM_OP_RLWINM, 0, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM, FLG_DEFAULT, C_MASK_RC, 0, nullptr}, {PPCASM_OP_RLWINM_, 0, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr}, {PPCASM_OP_ROTLWI, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(11, 5, false)}, {EncodedConstraint_FixedU5(6, 0), EncodedConstraint_FixedU5(1, 31)}}, {PPCASM_OP_ROTLWI_, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(11, 5, false)}, {EncodedConstraint_FixedU5(6, 0), EncodedConstraint_FixedU5(1, 31)}}, // rotrwi RA, RS, n -> rlwinm RA, RS, 32-n, 0, 31 // only assembler {PPCASM_OP_ROTRWI, 0, 21, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_U5Reverse(11, 32)}, {EncodedConstraint_FixedU5(6, 0), EncodedConstraint_FixedU5(1, 31)}}, {PPCASM_OP_ROTRWI_, 0, 21, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_U5Reverse(11, 32)}, {EncodedConstraint_FixedU5(6, 0), EncodedConstraint_FixedU5(1, 31)}}, {PPCASM_OP_EXTLWI, 1, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_extlwi}, {PPCASM_OP_EXTLWI_, 1, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_extlwi}, {PPCASM_OP_EXTRWI, 1, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_extrwi}, {PPCASM_OP_EXTRWI_, 1, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_extrwi}, {PPCASM_OP_SLWI, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_slwi}, {PPCASM_OP_SLWI_, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_slwi}, {PPCASM_OP_SRWI, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_srwi}, {PPCASM_OP_SRWI_, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_srwi}, {PPCASM_OP_CLRLWI, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_clrlwi}, {PPCASM_OP_CLRLWI_, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_clrlwi}, {PPCASM_OP_CLRRWI, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_clrrwi}, {PPCASM_OP_CLRRWI_, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_clrrwi}, {PPCASM_OP_RLWNM, 0, 23, OPC_NONE, OPC_NONE, OP_FORM_RLWNM, FLG_DEFAULT, C_MASK_RC, 0, nullptr}, {PPCASM_OP_RLWNM_, 0, 23, OPC_NONE, OPC_NONE, OP_FORM_RLWNM, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr}, {PPCASM_OP_ROTLW, 0, 23, OPC_NONE, OPC_NONE, OP_FORM_RLWNM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_rotlw}, {PPCASM_OP_ROTLW_, 0, 23, OPC_NONE, OPC_NONE, OP_FORM_RLWNM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_rotlw}, {PPCASM_OP_ORI, 0, 24, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, false)}}, {PPCASM_OP_NOP, 1, 24, OPC_NONE, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, 0x3FFFFFF, 0, nullptr}, // ORI r0, r0, 0 -> NOP {PPCASM_OP_ORIS, 0, 25, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, true, false, true)}}, {PPCASM_OP_XORI, 0, 26, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, false)} }, {PPCASM_OP_XORIS, 0, 27, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, true, false, true)} }, {PPCASM_OP_ANDI_, 0, 28, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, false)} }, {PPCASM_OP_ANDIS_, 0, 29, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, true, false, true)} }, // group 31 {PPCASM_OP_CMPW, 0, 31, 0, OPC_NONE, OP_FORM_OP3_A_CMP, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_SUBFC, 1, 31, 8, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_SUBFC_, 1, 31, 8, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_MULHWU, 0, 31, 11, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_MULHWU_, 0, 31, 11, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_CMPLW, 0, 31, 32, OPC_NONE, OP_FORM_OP3_A_CMP, FLG_DEFAULT, 0, 0, nullptr}, {PPCASM_OP_SUBF, 1, 31, 40, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_SUBF_, 1, 31, 40, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_SUB, 0, 31, 40, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(11), EncodedOperand_GPR(16)} }, {PPCASM_OP_SUB_, 0, 31, 40, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(11), EncodedOperand_GPR(16)} }, {PPCASM_OP_MULHW, 0, 31, 75, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_MULHW_, 0, 31, 75, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_NEG, 0, 31, 104, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC | C_MASK_OE, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16)}}, {PPCASM_OP_NEG_, 0, 31, 104, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC | C_MASK_OE, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16)} }, {PPCASM_OP_NOR, 0, 31, 124, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_NOR_, 0, 31, 124, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, // alias for NOR where rA == rB {PPCASM_OP_NOT, 1, 31, 124, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(11)}, {EncodedConstraint_MirrorRegister(11, 21)}}, {PPCASM_OP_NOT_, 1, 31, 124, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(11)}, {EncodedConstraint_MirrorRegister(11, 21)} }, {PPCASM_OP_SUBFE, 1, 31, 136, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_SUBFE_, 1, 31, 136, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_MULLW, 0, 31, 235, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_MULLW_, 0, 31, 235, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_MFSPR, 0, 31, 339, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_SPR()} }, {PPCASM_OP_MTSPR, 0, 31, 467, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_SPR(), EncodedOperand_GPR(21)} }, {PPCASM_OP_MFLR, 0, 31, 339, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21)}, {EncodedConstraint_FixedSPR(8)}}, {PPCASM_OP_MTLR, 0, 31, 467, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21)}, {EncodedConstraint_FixedSPR(8)} }, {PPCASM_OP_MFCTR, 0, 31, 339, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21)}, {EncodedConstraint_FixedSPR(9)} }, {PPCASM_OP_MTCTR, 0, 31, 467, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21)}, {EncodedConstraint_FixedSPR(9)} }, {PPCASM_OP_ADD, 0, 31, 266, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_ADD_, 0, 31, 266, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_SLW, 0, 31, 24, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_SLW_, 0, 31, 24, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_SRW, 0, 31, 536, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_SRW_, 0, 31, 536, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_SRAW, 0, 31, 792, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_SRAW_, 0, 31, 792, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_AND, 0, 31, 28, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_AND_, 0, 31, 28, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_ANDC, 0, 31, 60, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_ANDC_, 0, 31, 60, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_XOR, 0, 31, 316, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_ORC, 0, 31, 412, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, // has RC? {PPCASM_OP_OR, 0, 31, 444, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_OR_, 0, 31, 444, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, {PPCASM_OP_MR, 1, 31, 444, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(11)}, {EncodedConstraint_MirrorRegister(11, 21)} }, {PPCASM_OP_MR_, 1, 31, 444, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(11)}, {EncodedConstraint_MirrorRegister(11, 21)} }, {PPCASM_OP_DIVWU, 0, 31, 459, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_DIVWU_, 0, 31, 459, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_DIVW, 0, 31, 491, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_DIVW_, 0, 31, 491, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_SRAWI, 0, 31, 824, OPC_NONE, OP_FORM_OP3_A_IMM, FLG_DEFAULT, C_MASK_RC, 0, nullptr}, {PPCASM_OP_SRAWI_, 0, 31, 824, OPC_NONE, OP_FORM_OP3_A_IMM, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr}, {PPCASM_OP_CNTLZW, 0, 31, 26, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, {PPCASM_OP_EXTSB, 0, 31, 954, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, {PPCASM_OP_EXTSH, 0, 31, 922, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, {PPCASM_OP_CNTLZW_, 0, 31, 26, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, {PPCASM_OP_EXTSB_, 0, 31, 954, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, {PPCASM_OP_EXTSH_, 0, 31, 922, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, {PPCASM_OP_LWZX, 0, 31, 23, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LWZUX, 0, 31, 55, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LBZX, 0, 31, 87, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LBZUX, 0, 31, 119, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LHZX, 0, 31, 279, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LHZUX, 0, 31, 311, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LHAX, 0, 31, 343, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LHAUX, 0, 31, 375, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STWX, 0, 31, 151, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STWUX, 0, 31, 183, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STHX, 0, 31, 407, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STHUX, 0, 31, 439, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STBX, 0, 31, 215, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STBUX, 0, 31, 247, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STWBRX, 0, 31, 662, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STHBRX, 0, 31, 918, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LFSX, 0, 31, 535, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LFSUX, 0, 31, 567, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STFSX, 0, 31, 663, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STFSUX, 0, 31, 695, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STSWI, 0, 31, 725, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(11, 5, false)} }, {PPCASM_OP_LWARX, 0, 31, 20, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STWCX_, 0, 31, 150, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LFDX, 0, 31, 599, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_LFDUX, 0, 31, 631, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STFDX, 0, 31, 727, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STFDUX, 0, 31, 759, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, {PPCASM_OP_STFIWX, 0, 31, 983, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, // load/store {PPCASM_OP_LWZ, 0, 32, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LWZU, 0, 33, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LBZ, 0, 34, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LBZU, 0, 35, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STW, 0, 36, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STWU, 0, 37, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STB, 0, 38, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STBU, 0, 39, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LHZ, 0, 40, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LHZU, 0, 41, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LHA, 0, 42, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LHAU, 0, 43, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STH, 0, 44, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STHU, 0, 45, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LMW, 0, 46, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STMW, 0, 47, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LFS, 0, 48, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LFSU, 0, 49, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LFD, 0, 50, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_LFDU, 0, 51, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STFS, 0, 52, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STFSU, 0, 53, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STFD, 0, 54, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, {PPCASM_OP_STFDU, 0, 55, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, // FP {PPCASM_OP_FDIVS, 0, 59, 18, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, {PPCASM_OP_FSUBS, 0, 59, 20, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, {PPCASM_OP_FADDS, 0, 59, 21, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, {PPCASM_OP_FMULS, 0, 59, 25, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6) } }, {PPCASM_OP_FMSUBS, 0, 59, 28, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, {PPCASM_OP_FMADDS, 0, 59, 29, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, {PPCASM_OP_FNMSUBS, 0, 59, 30, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, {PPCASM_OP_FNMADDS, 0, 59, 31, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, {PPCASM_OP_FCMPU, 0, 63, 0, OPC_NONE, OP_FORM_X_FP_CMP, FLG_DEFAULT, 0, 0, nullptr }, {PPCASM_OP_FCTIWZ, 0, 63, 15, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(11)}}, {PPCASM_OP_FDIV, 0, 63, 18, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, {PPCASM_OP_FSUB, 0, 63, 20, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, {PPCASM_OP_FADD, 0, 63, 21, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, {PPCASM_OP_FMUL, 0, 63, 25, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6) } }, {PPCASM_OP_FRSQRTE, 0, 63, 26, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, {PPCASM_OP_FMSUB, 0, 63, 28, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, {PPCASM_OP_FMADD, 0, 63, 29, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, {PPCASM_OP_FNMSUB, 0, 63, 30, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, {PPCASM_OP_FNMADD, 0, 63, 31, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, {PPCASM_OP_FRSP, 0, 63, 12, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, // 63 extended opcode {PPCASM_OP_FCMPO, 0, 63, 32|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_X_FP_CMP, FLG_DEFAULT, 0, 0, nullptr }, {PPCASM_OP_FNEG, 0, 63, 40|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, {PPCASM_OP_FMR, 0, 63, 72|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, {PPCASM_OP_FABS, 0, 63, 264|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, }; #define opcMask_setBits(__index, __bitCount, __value) opcodeMask |= (((1<<__bitCount)-1)<<(31-__index)); opcodeBits |= ((__value)<<(31-__index)); void ppcAssembler_buildOpcMask(PPCInstructionDef* iDef, uint32& maskOut, uint32& bitsOut) { uint32 opc0 = iDef->opc0; uint32 opc1 = iDef->opc1; uint32 opc2 = iDef->opc2; uint32 opcodeMask = 0x3F<<26; uint32 opcodeBits = opc0<<26; // handle groups if (opc0 == 31) { // group 31 opcMask_setBits(30, 10, opc1); } else if (opc0 == 19) { // group 19 opcMask_setBits(30, 10, opc1); } else if (opc0 == 4) { // group 4 (paired single) opcMask_setBits(30, 5, opc1); //opc1 = PPC_getBits(opcode, 30, 5); if (opc1 == 16) { // group 4->16 (ps_merge) opcMask_setBits(25, 5, opc2); //opc2 = PPC_getBits(opcode, 25, 5); } } else if (opc0 == 59) { // group 59 (FP float) opcMask_setBits(30, 5, opc1); //opc1 = PPC_getBits(opcode, 30, 5); } else if (opc0 == 63) { // group 63 (FP double) if ((opc1&OPC_EXTENDED_BIT) != 0) { opcMask_setBits(30, 10, (opc1&~OPC_EXTENDED_BIT)); } else { opcMask_setBits(30, 5, opc1); } } maskOut = opcodeMask; bitsOut = opcodeBits; } // given an internal instruction operand index, return the text encoding index sint32 _getOpIndex(PPCInstructionDef* iDef, sint32 operandIndex) { if ((operandIndex == 0 || operandIndex == 1) && (iDef->flags & FLG_SWAP_OP0_OP1) != 0) operandIndex ^= 1; if ((iDef->flags & FLG_SWAP_OP1_OP2) != 0) { if (operandIndex == 1) operandIndex = 2; else if (operandIndex == 2) operandIndex = 1; } if ((iDef->flags & FLG_SWAP_OP2_OP3) != 0) { if (operandIndex == 2) operandIndex = 3; else if (operandIndex == 3) operandIndex = 2; } return operandIndex; } // given an internal instruction operand index, return the operand index for the text encoding. Returns -1 when operand is not present // replaces _getOpIndex //sint32 _operandInternalToTextIndex(ppcInstructionDef_t* iDef, sint32 operandIndex) //{ // if ((operandIndex == 0 || operandIndex == 1) && (iDef->flags & FLG_SWAP_OP0_OP1) != 0) // operandIndex ^= 1; // if ((operandIndex == 2 || operandIndex == 3) && (iDef->flags & FLG_SWAP_OP2_OP3) != 0) // operandIndex ^= 1; // sint32 outputIndex = operandIndex; // if ((iDef->flags & (1 << outputIndex))) // return -1; // for (sint32 i = 0; i < operandIndex; i++) // { // if ((iDef->flags & (1 << i))) // outputIndex--; // } // return outputIndex; //} // //int _operandTextToInternalIndex(ppcInstructionDef_t* iDef, sint32 operandIndex) //{ // for (sint32 i = 0; i < 8; i++) // { // if (_operandInternalToTextIndex(iDef, i) == operandIndex) // return i; // } // return -1; //} void _disasmOpGPR(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, uint32 operandIndex, sint32 regIndex) { if ((iDef->flags & (FLG_SKIP_OP0 << operandIndex)) != 0) return; disInstr->operandMask |= (1 << operandIndex); sint32 opIdx = _getOpIndex(iDef, operandIndex); disInstr->operand[opIdx].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[opIdx].registerIndex = regIndex; } void ppcAssembler_disassemble(uint32 virtualAddress, uint32 opcode, PPCDisassembledInstruction* disInstr) { auto makeOpCRBit = [&](size_t opIndex, uint8 regIndex) { disInstr->operandMask |= (1 << opIndex); disInstr->operand[opIndex].type = PPCASM_OPERAND_TYPE_CR_BIT; disInstr->operand[opIndex].registerIndex = regIndex; }; auto makeOpFPR = [&](size_t opIndex, uint8 regIndex) { disInstr->operandMask |= (1 << opIndex); disInstr->operand[opIndex].type = PPCASM_OPERAND_TYPE_FPR; disInstr->operand[opIndex].registerIndex = regIndex; }; memset(disInstr, 0, sizeof(PPCDisassembledInstruction)); // find match in table sint32 bestMatchIndex = -1; uint8 bestMatchPriority = 0; for (sint32 i = 0; i < sizeof(ppcInstructionTable) / sizeof(PPCInstructionDef); i++) { PPCInstructionDef* iDef = ppcInstructionTable + i; // check opcode uint32 opcMask; uint32 opcBits; ppcAssembler_buildOpcMask(iDef, opcMask, opcBits); if ((opcode&opcMask) != opcBits) continue; // check bits if((opcode&iDef->maskBits) != iDef->compareBits) continue; // check special condition if(iDef->extraCheck && iDef->extraCheck(opcode) == false ) continue; // check priority if(iDef->priority < bestMatchPriority) continue; // check constraints bool allConstraintsMatch = true; for (auto& it : iDef->constraints) { if (!std::visit([&](auto&& op) -> bool { return op.DisassembleCheckConstraint(disInstr, iDef, opcode); }, it)) { allConstraintsMatch = false; break; } } if (!allConstraintsMatch) continue; // select entry bestMatchIndex = i; bestMatchPriority = iDef->priority; } // if we have found an entry, parse operand data if (bestMatchIndex >= 0) { PPCInstructionDef* iDef = ppcInstructionTable + bestMatchIndex; // setup general instruction info disInstr->ppcAsmCode = iDef->ppcAsmOp; // parse operands if (iDef->instructionForm == OP_FORM_DYNAMIC) { disInstr->operandMask = 0; for (size_t i = 0; i < iDef->encodedOperands.size(); i++) { std::visit([&](auto&& op) { op.DisassembleOperand(disInstr, iDef, opcode, i); }, iDef->encodedOperands[i]); } } else if (iDef->instructionForm == OP_FORM_OP3_A_CMP) { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); disInstr->operandMask = (rS!=0?operand0Bit:0); disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CR; disInstr->operand[0].registerIndex = rS >> 2; if((rS & 3) != 0) cemuLog_log(LogType::Force, "[PPC-Disassembler] Unexpected CR encoding for instruction 0x{0:08x}", opcode); _disasmOpGPR(disInstr, iDef, 1, rA); _disasmOpGPR(disInstr, iDef, 2, rB); } else if (iDef->instructionForm == OP_FORM_OP3_A_IMM) { sint32 rS, rA, SH; PPC_OPC_TEMPL_X(opcode, rS, rA, SH); disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; sint32 op0Idx = _getOpIndex(iDef, 0); sint32 op1Idx = _getOpIndex(iDef, 1); sint32 op2Idx = _getOpIndex(iDef, 2); // operand 0 disInstr->operand[op0Idx].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[op0Idx].registerIndex = rA; // operand 1 disInstr->operand[op1Idx].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[op1Idx].registerIndex = rS; // operand 2 disInstr->operand[op2Idx].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[op2Idx].immS32 = SH; disInstr->operand[op2Idx].immWidth = 5; disInstr->operand[op2Idx].isSignedImm = false; } else if (iDef->instructionForm == OP_FORM_BRANCH_S16) { uint32 BO, BI, dest; PPC_OPC_TEMPL_B(opcode, BO, BI, dest); uint8 crIndex = BI >> 2; if ((opcode & PPC_OPC_AA) == 0) dest += virtualAddress; if (iDef->ppcAsmOp == PPCASM_OP_BC) { // generic conditional branch of form <BO>, CR+LT/GT/EQ/SO, <dst> disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; // operand 0 disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CIMM; disInstr->operand[0].registerIndex = BO; // operand 1 disInstr->operand[1].type = PPCASM_OPERAND_TYPE_CR_BIT; disInstr->operand[1].registerIndex = BI; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_CIMM; disInstr->operand[2].immU32 = dest; // hint bit disInstr->branchHintBitSet = (BO & 1) != 0; } else { if (crIndex != 0) { disInstr->operandMask = operand0Bit | operand1Bit; // operand 0 disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CR; disInstr->operand[0].registerIndex = crIndex; // operand 1 disInstr->operand[1].type = PPCASM_OPERAND_TYPE_CIMM; disInstr->operand[1].immU32 = dest; } else { disInstr->operandMask = operand0Bit; disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CIMM; disInstr->operand[0].immU32 = dest; } // hint bit disInstr->branchHintBitSet = (BO & 1) != 0; } } else if (iDef->instructionForm == OP_FORM_BRANCH_S24) { uint32 dest; PPC_OPC_TEMPL_I(opcode, dest); if ((opcode & PPC_OPC_AA) == 0) dest += virtualAddress; disInstr->operandMask = operand0Bit; // operand 0 disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CIMM; disInstr->operand[0].immU32 = dest; } else if (iDef->instructionForm == OP_FORM_OP2_D_HSIMM) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); disInstr->operandMask = operand0Bit | operand1Bit; // operand 0 disInstr->operand[0].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[0].registerIndex = rD; // operand 1 disInstr->operand[1].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[1].immS32 = imm & 0xFFFF; disInstr->operand[1].immWidth = 16; disInstr->operand[1].isSignedImm = true; } else if (iDef->instructionForm == OP_FORM_CMP_SIMM) { uint32 cr; sint32 rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, cr, rA, imm); cr /= 4; // rS is cr disInstr->operandMask = operand1Bit | operand2Bit; if (cr != 0) disInstr->operandMask |= operand0Bit; // operand 0 disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CR; disInstr->operand[0].registerIndex = cr; // operand 1 disInstr->operand[1].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[1].registerIndex = rA; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[2].immS32 = imm; disInstr->operand[2].immWidth = 16; disInstr->operand[2].isSignedImm = true; } else if (iDef->instructionForm == OP_FORM_NO_OPERAND) { disInstr->operandMask = 0; } else if (iDef->instructionForm == OP_FORM_X_FP_CMP) { sint32 rA, crIndex, rB; PPC_OPC_TEMPL_X(opcode, crIndex, rA, rB); cemu_assert_debug((crIndex % 4) == 0); crIndex /= 4; disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; // operand 0 disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CR; disInstr->operand[0].registerIndex = crIndex; // operand 1 disInstr->operand[1].type = PPCASM_OPERAND_TYPE_FPR; disInstr->operand[1].registerIndex = rA; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_FPR; disInstr->operand[2].registerIndex = rB; } else if (iDef->instructionForm == OP_FORM_RLWINM) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit | operand3Bit | operand4Bit; // operand 0 disInstr->operand[0].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[0].registerIndex = rA; // operand 1 disInstr->operand[1].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[1].registerIndex = rS; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[2].immS32 = SH; disInstr->operand[2].immWidth = 8; // operand 3 disInstr->operand[3].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[3].immS32 = MB; disInstr->operand[3].immWidth = 8; // operand 4 disInstr->operand[4].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[4].immS32 = ME; disInstr->operand[4].immWidth = 8; } else if (iDef->instructionForm == OP_FORM_RLWINM_EXTENDED) { sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); // operand 0 disInstr->operand[0].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[0].registerIndex = rA; // operand 1 disInstr->operand[1].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[1].registerIndex = rS; if (iDef->ppcAsmOp == PPCASM_OP_EXTLWI || iDef->ppcAsmOp == PPCASM_OP_EXTLWI_) { disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit | operand3Bit; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[2].immS32 = ME + 1; disInstr->operand[2].immWidth = 8; // operand 3 disInstr->operand[3].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[3].immS32 = SH; disInstr->operand[3].immWidth = 8; } else if (iDef->ppcAsmOp == PPCASM_OP_EXTRWI || iDef->ppcAsmOp == PPCASM_OP_EXTRWI_) { disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit | operand3Bit; sint8 n = 32 - MB; sint8 b = SH - n; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[2].immS32 = n; disInstr->operand[2].immWidth = 8; // operand 3 disInstr->operand[3].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[3].immS32 = b; disInstr->operand[3].immWidth = 8; } else if (iDef->ppcAsmOp == PPCASM_OP_SLWI || iDef->ppcAsmOp == PPCASM_OP_SLWI_) { disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; sint8 n = SH; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[2].immS32 = n; disInstr->operand[2].immWidth = 8; } else if (iDef->ppcAsmOp == PPCASM_OP_SRWI || iDef->ppcAsmOp == PPCASM_OP_SRWI_) { disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; sint8 n = 32 - SH; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[2].immS32 = n; disInstr->operand[2].immWidth = 8; } else if (iDef->ppcAsmOp == PPCASM_OP_CLRLWI || iDef->ppcAsmOp == PPCASM_OP_CLRLWI_) { disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; sint8 n = MB; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[2].immS32 = n; disInstr->operand[2].immWidth = 8; } else if (iDef->ppcAsmOp == PPCASM_OP_CLRRWI || iDef->ppcAsmOp == PPCASM_OP_CLRRWI_) { disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; sint8 n = 31 - ME; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[2].immS32 = n; disInstr->operand[2].immWidth = 8; } else { cemu_assert_debug(false); } } else if (iDef->instructionForm == OP_FORM_RLWNM) { sint32 rS, rA, rB, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, rB, MB, ME); disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit | operand3Bit | operand4Bit; // operand 0 disInstr->operand[0].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[0].registerIndex = rA; // operand 1 disInstr->operand[1].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[1].registerIndex = rS; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[2].registerIndex = rB; // operand 3 disInstr->operand[3].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[3].immS32 = MB; disInstr->operand[3].immWidth = 8; // operand 4 disInstr->operand[4].type = PPCASM_OPERAND_TYPE_IMM; disInstr->operand[4].immS32 = ME; disInstr->operand[4].immWidth = 8; } else if (iDef->instructionForm == OP_FORM_RLWNM_EXTENDED) { sint32 rS, rA, rB, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, rB, MB, ME); // operand 0 disInstr->operand[0].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[0].registerIndex = rA; // operand 1 disInstr->operand[1].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[1].registerIndex = rS; // operand 2 disInstr->operand[2].type = PPCASM_OPERAND_TYPE_GPR; disInstr->operand[2].registerIndex = rB; if (iDef->ppcAsmOp == PPCASM_OP_ROTLW || iDef->ppcAsmOp == PPCASM_OP_ROTLW_) { disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; // no additional operands displayed } } else if (iDef->instructionForm == OP_FORM_XL_CR) { PPC_OPC_TEMPL_X_CR(); // todo - detect and emit simplified mnemonics (e.g. CRSET) makeOpCRBit(0, crD); makeOpCRBit(1, crA); makeOpCRBit(2, crB); } else { cemu_assert_debug(false); } disInstr->operandMask &= ~(iDef->flags & 0x1F); } else { // no match disInstr->ppcAsmCode = PPCASM_OP_UKN; } } void ppcAssembler_setError(PPCAssemblerInOut* ctx, char const* errorMsg) { ctx->errorMsg = errorMsg; } void ppcAssembler_setError(PPCAssemblerInOut* ctx, std::string_view errorMsg) { ctx->errorMsg = errorMsg; } char _assemblerErrorMessageDepr[1024]; bool _ppcAssembler_getOperandTextIndex(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32& textIndex) { // get swapped index operandIndex = _getOpIndex(internalCtx.iDef, operandIndex); // check if operand is optional if ((internalCtx.iDef->flags&(1 << operandIndex))) { // operand not used textIndex = -1; return true; } // get operand text index sint32 opTextIdx = 0; for (sint32 i = 0; i < operandIndex; i++) { if ((internalCtx.iDef->flags&(1 << i))) continue; // skip operand opTextIdx++; } if (opTextIdx >= internalCtx.listOperandStr.size()) { ppcAssembler_setError(internalCtx.ctx, "Missing operand"); return false; } textIndex = opTextIdx; return true; } bool _ppcAssembler_parseRegister(std::string_view str, const char* prefix, sint32& regIndex) { const char* startPtr = str.data(); const char* endPtr = str.data() + str.size(); // skip whitespaces while (startPtr < endPtr) { if (*startPtr != ' ') break; startPtr++; } // trim whitespaces at end while (endPtr > startPtr) { if (endPtr[-1] != ' ') break; endPtr--; } // parse register name if (startPtr + 2 > endPtr) return false; while (true) { if (startPtr >= endPtr) return false; if (*prefix == '\0') break; if (tolower(*prefix) != tolower(*startPtr)) return false; prefix++; startPtr++; } sint32 parsedIndex = 0; while (startPtr < endPtr) { parsedIndex *= 10; if (*startPtr < '0' || *startPtr > '9') return false; parsedIndex += (*startPtr - '0'); startPtr++; } if (parsedIndex >= 32) return false; regIndex = parsedIndex; return true; } bool _ppcAssembler_processRegisterOperand(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex, const char* prefix, const sint32 registerCount) { sint32 opTextIdx; if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) return false; if (opTextIdx < 0) return true; // skipped operand // parse GPR sint32 gprIndex; if (_ppcAssembler_parseRegister(internalCtx.listOperandStr[opTextIdx].str, prefix, gprIndex) == false) { ppcAssembler_setError(internalCtx.ctx, fmt::format("\'{0}\' is not a valid register operand (must be {1}0 to {1}{2})", internalCtx.listOperandStr[opTextIdx].str, prefix, registerCount-1)); return false; } if (gprIndex < 0 || gprIndex >= registerCount) { ppcAssembler_setError(internalCtx.ctx, fmt::format("\'{0}\' is not a valid register operand (must be {1}0 to {1}{2})", internalCtx.listOperandStr[opTextIdx].str, prefix, registerCount - 1)); return false; } internalCtx.opcode |= (gprIndex << bitIndex); return true; } bool _ppcAssembler_processGPROperand(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex) { return _ppcAssembler_processRegisterOperand(internalCtx, operandIndex, bitIndex, "r", 32); } bool _ppcAssembler_processCROperand(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex, bool isEncodedAsBitIndex) { return _ppcAssembler_processRegisterOperand(internalCtx, operandIndex, bitIndex + (isEncodedAsBitIndex?2:0), "cr", 8); } template<typename TExprResult> bool _ppcAssembler_evaluateConstantExpression(PPCAssemblerContext& internalCtx, TExpressionParser<TExprResult>& ep, std::string_view expr, TExprResult& result) { if (!ep.IsValidExpression(expr)) { ppcAssembler_setError(internalCtx.ctx, fmt::format("'{}' is not a valid expression", expr)); return false; } if (!ep.IsConstantExpression(expr)) { ppcAssembler_setError(internalCtx.ctx, fmt::format("'{}' does not evaluate to a constant expression", expr)); return false; } try { result = ep.Evaluate(expr); } catch (std::exception* e) { ppcAssembler_setError(internalCtx.ctx, fmt::format("\'{0}\' could not be evaluated. Error: {1}", expr, e->what())); return false; } return true; } bool _ppcAssembler_processBIOperand(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex) { sint32 opTextIdx; if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) return false; if (opTextIdx < 0) return true; // skipped operand // parse expression // syntax examples: // single condition bit of cr0: lt, gt, eq, so // condition bit with cr index: 4*cr1+lt TExpressionParser<sint32> ep; ep.AddConstant("lt", 0); ep.AddConstant("gt", 1); ep.AddConstant("eq", 2); ep.AddConstant("so", 3); ep.AddConstant("cr0", 0); ep.AddConstant("cr1", 1); ep.AddConstant("cr2", 2); ep.AddConstant("cr3", 3); ep.AddConstant("cr4", 4); ep.AddConstant("cr5", 5); ep.AddConstant("cr6", 6); ep.AddConstant("cr7", 7); std::string_view expr = internalCtx.listOperandStr[opTextIdx].str; sint32 bi = 0; if (!_ppcAssembler_evaluateConstantExpression(internalCtx, ep, expr, bi)) return false; if (bi < 0 || bi >= (8 * 4)) { ppcAssembler_setError(internalCtx.ctx, fmt::format("CR bit operand \'{0}\' evaluated to {1} which is out of range", expr, bi)); return false; } internalCtx.opcode &= ~(0x1F << bitIndex); internalCtx.opcode |= (bi << bitIndex); return true; } bool _ppcAssembler_processFPROperand(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex) { return _ppcAssembler_processRegisterOperand(internalCtx, operandIndex, bitIndex, "f", 32); } bool _ppcAssembler_processImmediateOperandS16(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex, bool isNegative = false) { sint32 opTextIdx; if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) return false; if (opTextIdx < 0) return true; // skipped operand // parse expression std::string expressionString(internalCtx.listOperandStr[opTextIdx].str); if (isNegative) { expressionString.insert(0, "0-("); expressionString.append(")"); } ExpressionParser ep; double immD = 0.0f; try { if (ep.IsConstantExpression(expressionString)) { immD = ep.Evaluate(expressionString); } else { internalCtx.ctx->list_relocs.emplace_back(PPCASM_RELOC::U32_MASKED_IMM, expressionString, 0, bitIndex, 16); return true; } } catch (std::exception* e) { // check if expression is invalid or if it contains unknown constants sprintf(_assemblerErrorMessageDepr, "\'%s\' could not be evaluated. Error: %s", expressionString.c_str(), e->what()); ppcAssembler_setError(internalCtx.ctx, _assemblerErrorMessageDepr); return false; } uint32 imm = (uint32)(sint32)immD; imm &= 0xFFFF; internalCtx.opcode |= (imm << bitIndex); return true; } bool _ppcAssembler_processImmediateOperandU5(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex) { sint32 opTextIdx; if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) return false; if (opTextIdx < 0) return true; // skipped operand // parse expression ExpressionParser ep; double immD = 0.0f; try { immD = ep.Evaluate(internalCtx.listOperandStr[opTextIdx].str); } catch (std::exception*) { // check if expression is invalid or if it contains unknown constants ppcAssembler_setError(internalCtx.ctx, fmt::format("\'{}\' is not a valid expression", internalCtx.listOperandStr[opTextIdx].str)); return false; } sint32 immS32 = (sint32)immD; if (immS32 < 0 || immS32 >= 32) { ppcAssembler_setError(internalCtx.ctx, fmt::format("\'{}\' is not in range 0-31", internalCtx.listOperandStr[opTextIdx].str)); return false; } uint32 imm = (uint32)immS32; imm &= 0x1F; internalCtx.opcode |= (imm << bitIndex); return true; } bool _ppcAssembler_processImmediateOperandU5Const(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint8& value) { sint32 opTextIdx; if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) return false; if (opTextIdx < 0) return true; // skipped operand // parse expression TExpressionParser<sint32> ep; auto expr = internalCtx.listOperandStr[opTextIdx].str; sint32 r; if (!_ppcAssembler_evaluateConstantExpression(internalCtx, ep, expr, r)) return false; if (r < 0 || r >= 32) { ppcAssembler_setError(internalCtx.ctx, fmt::format("Expression '\'{0}\' which evaluates to {1} is not in range 0-31", expr, r)); return false; } value = (sint8)r; return true; } bool _ppcAssembler_isConstantBranchTargetExpr(std::string& expressionString, sint32& relativeAddr) { if (expressionString.length() > 0 && expressionString[0] == '.') { ExpressionParser ep; double branchDistance = 0.0f; try { branchDistance = ep.Evaluate(expressionString.substr(1).insert(0, "0")); } catch (std::exception&) { return false; } relativeAddr = (sint32)branchDistance; return true; } return false; } bool _ppcAssembler_processBranchOperandS16(PPCAssemblerContext& internalCtx, sint32 operandIndex) { sint32 opTextIdx; if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) return false; if (opTextIdx < 0) return true; // skipped operand std::string expressionString(internalCtx.listOperandStr[opTextIdx].str); sint32 relativeAddr; if (_ppcAssembler_isConstantBranchTargetExpr(expressionString, relativeAddr)) { if (relativeAddr < -32768 || relativeAddr > 32767) { sprintf(_assemblerErrorMessageDepr, "Branch target out of range"); ppcAssembler_setError(internalCtx.ctx, _assemblerErrorMessageDepr); return false; } if (relativeAddr&3) { sprintf(_assemblerErrorMessageDepr, "Branch target must be aligned to 4"); ppcAssembler_setError(internalCtx.ctx, _assemblerErrorMessageDepr); return false; } internalCtx.opcode |= (relativeAddr & 0xFFFC); return true; } // create reloc internalCtx.ctx->list_relocs.emplace_back(PPCASM_RELOC::BRANCH_S16, expressionString, 0, 0, 0); return true; } bool _ppcAssembler_processBranchOperandS26(PPCAssemblerContext& internalCtx, sint32 operandIndex) { sint32 opTextIdx; if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) return false; if (opTextIdx < 0) return true; // skipped operand std::string expressionString(internalCtx.listOperandStr[opTextIdx].str); sint32 relativeAddr; if (_ppcAssembler_isConstantBranchTargetExpr(expressionString, relativeAddr)) { if (relativeAddr < -33554432 || relativeAddr > 33554431) { ppcAssembler_setError(internalCtx.ctx, "Branch target out of range"); return false; } if (relativeAddr & 3) { ppcAssembler_setError(internalCtx.ctx, "Branch target must be aligned to 4"); return false; } internalCtx.opcode |= (relativeAddr & 0x3FFFFFC); return true; } // create reloc internalCtx.ctx->list_relocs.emplace_back(PPCASM_RELOC::BRANCH_S26, expressionString, 0, 0, 0); return true; } void _ppcAssembler_setAlignment(PPCAssemblerContext& internalInfo, uint32 alignment) { if (internalInfo.ctx->forceNoAlignment) { internalInfo.ctx->alignmentRequirement = 1; internalInfo.ctx->alignmentPaddingSize = 0; internalInfo.ctx->virtualAddressAligned = internalInfo.ctx->virtualAddress; return; } internalInfo.ctx->alignmentRequirement = alignment; if (alignment == 0) { internalInfo.ctx->alignmentPaddingSize = 0; internalInfo.ctx->virtualAddressAligned = internalInfo.ctx->virtualAddress; return; } uint32 alignedVA = (internalInfo.ctx->virtualAddress + alignment - 1) & ~(alignment - 1); internalInfo.ctx->alignmentPaddingSize = alignedVA - internalInfo.ctx->virtualAddress; internalInfo.ctx->virtualAddressAligned = alignedVA; } enum class ASM_DATA_DIRECTIVE { NONE, FLOAT, DOUBLE, U32, U16, U8, // alias to .string }; bool _ppcAssembler_emitDataDirective(PPCAssemblerContext& internalInfo, ASM_DATA_DIRECTIVE dataDirective) { PPCASM_RELOC relocType; switch (dataDirective) { case ASM_DATA_DIRECTIVE::FLOAT: relocType = PPCASM_RELOC::FLOAT; break; case ASM_DATA_DIRECTIVE::DOUBLE: relocType = PPCASM_RELOC::DOUBLE; break; case ASM_DATA_DIRECTIVE::U32: relocType = PPCASM_RELOC::U32; break; case ASM_DATA_DIRECTIVE::U16: relocType = PPCASM_RELOC::U16; break; case ASM_DATA_DIRECTIVE::U8: relocType = PPCASM_RELOC::U8; break; default: cemu_assert_debug(false); return false; } uint32 elementSize = 0; if (relocType == PPCASM_RELOC::FLOAT) elementSize = 4; else if (relocType == PPCASM_RELOC::DOUBLE) elementSize = 8; else if (relocType == PPCASM_RELOC::U32) elementSize = 4; else if (relocType == PPCASM_RELOC::U16) elementSize = 2; else if (relocType == PPCASM_RELOC::U8) elementSize = 1; else cemu_assert_debug(false); _ppcAssembler_setAlignment(internalInfo, elementSize); size_t elementCount = internalInfo.listOperandStr.size(); internalInfo.ctx->outputData.reserve(elementSize * elementCount); size_t writeIndex = 0; for (size_t i = 0; i < elementCount; i++) { std::string_view expressionStr = internalInfo.listOperandStr[i].str; // handle string constants if (internalInfo.listOperandStr[i].str.size() >= 1 && expressionStr[0] == '"') { if (dataDirective != ASM_DATA_DIRECTIVE::U8) { ppcAssembler_setError(internalInfo.ctx, "Strings constants are only allowed in .byte or .string data directives"); return false; } if (expressionStr.size() < 2 || expressionStr[expressionStr.size()-1] != '"') { ppcAssembler_setError(internalInfo.ctx, "String constants must end with a quotation mark. Example: \"text\""); return false; } // write string bytes + null-termination character size_t strConstantLength = expressionStr.size() - 2; internalInfo.ctx->outputData.insert(internalInfo.ctx->outputData.end(), expressionStr.data() + 1, expressionStr.data() + 1 + strConstantLength); internalInfo.ctx->outputData.emplace_back(0); continue; } // numeric constants internalInfo.ctx->outputData.resize(writeIndex + elementSize); uint8* elementPtr = internalInfo.ctx->outputData.data() + writeIndex; for (uint32 b = 0; b < elementSize; b++) { internalInfo.ctx->outputData[writeIndex] = 0; writeIndex++; } ExpressionParser ep; if (ep.IsConstantExpression(expressionStr)) { double solution = ep.Evaluate(expressionStr); switch (relocType) { case PPCASM_RELOC::U32: *(uint32be*)elementPtr = (uint32)solution; break; case PPCASM_RELOC::U16: *(uint16be*)elementPtr = (uint16)solution; break; case PPCASM_RELOC::U8: *(uint8be*)elementPtr = (uint8)solution; break; case PPCASM_RELOC::DOUBLE: *(float64be*)elementPtr = solution; break; case PPCASM_RELOC::FLOAT: *(float32be*)elementPtr = (float)solution; break; default: cemu_assert_debug(false); } } else { // generate reloc if we cant resolve immediately internalInfo.ctx->list_relocs.emplace_back(relocType, std::string(internalInfo.listOperandStr[i].str), (uint32)(elementPtr - internalInfo.ctx->outputData.data()), 0, 0); } } return true; } void _ppcAssembler_emitAlignDirective(PPCAssemblerContext& internalInfo, sint32 alignmentValue) { _ppcAssembler_setAlignment(internalInfo, 1); uint32 currentAddr = internalInfo.ctx->virtualAddress; while ((currentAddr % (uint32)alignmentValue)) { internalInfo.ctx->outputData.emplace_back(0); currentAddr++; } } void _ppcAssembler_translateAlias(boost::static_string<32>& instructionName) { if (instructionName.compare("BNL") == 0) instructionName.assign("BGT"); if (instructionName.compare("BNG") == 0) instructionName.assign("BLE"); } bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut* ctx) { PPCAssemblerContext internalInfo; internalInfo.ctx = ctx; // tokenize input char const* currentPtr = text; char const* startPtr = text; char const* endPtr = startPtr+strlen(startPtr); // cut off comments for (const char* itrPtr = startPtr; itrPtr < endPtr; itrPtr++) { if (*itrPtr == '#') { endPtr = itrPtr; break; } } // trim whitespaces at end and beginning while (endPtr > currentPtr) { if (endPtr[-1] != ' ' && endPtr[-1] != '\t') break; endPtr--; } while (currentPtr < endPtr) { if (currentPtr[0] != ' ' && currentPtr[0] != '\t') break; currentPtr++; } // parse name of instruction boost::static_string<32> instructionName; while (*currentPtr != ' ' && *currentPtr != '\t' && currentPtr < endPtr) { if (instructionName.size() >= instructionName.capacity()) { ppcAssembler_setError(ctx, "Instruction name exceeds maximum allowed length"); return false; } instructionName.push_back((char)toupper(*currentPtr)); currentPtr++; } if (instructionName.empty()) { ppcAssembler_setError(ctx, "Instruction name is invalid"); return false; } // handle branch hint suffix bool hasBranchHint = false; bool branchHintTaken = false; // '+' -> true, '-' -> false if (instructionName.back() == '+') { hasBranchHint = true; branchHintTaken = true; instructionName.pop_back(); } if (instructionName.back() == '-') { hasBranchHint = true; branchHintTaken = false; instructionName.pop_back(); } // handle instruction name aliases _ppcAssembler_translateAlias(instructionName); // parse operands internalInfo.listOperandStr.clear(); bool isInString = false; while (currentPtr < endPtr) { currentPtr++; startPtr = currentPtr; // find end of operand while (currentPtr < endPtr) { if (*currentPtr == '"') isInString=!isInString; if (*currentPtr == ',' && !isInString) break; currentPtr++; } // trim whitespaces at the beginning and end const char* operandStartPtr = startPtr; const char* operandEndPtr = currentPtr; while (operandStartPtr < endPtr) { if (*operandStartPtr != ' ' && *operandStartPtr != '\t') break; operandStartPtr++; } while (operandEndPtr > operandStartPtr) { if (operandEndPtr[-1] != ' ' && operandEndPtr[-1] != '\t') break; operandEndPtr--; } internalInfo.listOperandStr.emplace_back(operandStartPtr, operandEndPtr); } // check for data directives ASM_DATA_DIRECTIVE dataDirective = ASM_DATA_DIRECTIVE::NONE; if (instructionName.compare(".FLOAT") == 0) dataDirective = ASM_DATA_DIRECTIVE::FLOAT; else if (instructionName.compare(".DOUBLE") == 0) dataDirective = ASM_DATA_DIRECTIVE::DOUBLE; else if (instructionName.compare(".INT") == 0 || instructionName.compare(".UINT") == 0 || instructionName.compare(".PTR") == 0 || instructionName.compare(".U32") == 0 || instructionName.compare(".LONG") == 0) dataDirective = ASM_DATA_DIRECTIVE::U32; else if (instructionName.compare(".WORD") == 0 || instructionName.compare(".U16") == 0 || instructionName.compare(".SHORT") == 0) dataDirective = ASM_DATA_DIRECTIVE::U16; else if (instructionName.compare(".BYTE") == 0 || instructionName.compare(".STRING") == 0 || instructionName.compare(".U8") == 0 || instructionName.compare(".CHAR") == 0) dataDirective = ASM_DATA_DIRECTIVE::U8; if (dataDirective != ASM_DATA_DIRECTIVE::NONE) { if (internalInfo.listOperandStr.size() < 1) { ppcAssembler_setError(ctx, fmt::format("Value expected after data directive {}", instructionName.c_str())); return false; } return _ppcAssembler_emitDataDirective(internalInfo, dataDirective); } // handle .align directive if (instructionName.compare(".ALIGN") == 0) { // handle align data directive if (internalInfo.listOperandStr.size() != 1) { ppcAssembler_setError(ctx, ".align directive must have exactly one operand"); return false; } ExpressionParser ep; try { if (ep.IsConstantExpression(internalInfo.listOperandStr[0].str)) { sint32 alignmentValue = ep.Evaluate<sint32>(internalInfo.listOperandStr[0].str); if (alignmentValue <= 0 || alignmentValue >= 256) { ppcAssembler_setError(ctx, fmt::format("Alignment value \'{}\' is not within the allowed range (1-256)", alignmentValue)); return false; } _ppcAssembler_emitAlignDirective(internalInfo, alignmentValue); return true; } else { ppcAssembler_setError(ctx, fmt::format("Expression \'{}\' for .align directive is not a constant", internalInfo.listOperandStr[0].str)); return false; } } catch (std::exception* e) { ppcAssembler_setError(ctx, fmt::format("Expression \'{}\' for .align directive could not be evaluated. Error: {}", internalInfo.listOperandStr[0].str, e->what())); return false; } } // find match in instruction definition table PPCInstructionDef* iDef = nullptr; for (sint32 i = 0; i < sizeof(ppcInstructionTable) / sizeof(PPCInstructionDef); i++) { PPCInstructionDef* instr = ppcInstructionTable + i; if (instructionName.compare(ppcAssembler_getInstructionName(instr->ppcAsmOp)) == 0) { iDef = instr; internalInfo.iDef = iDef; break; } } if (iDef == nullptr) { ppcAssembler_setError(ctx, fmt::format("Instruction \'{}\' is unknown or not supported", instructionName.c_str())); return false; } // build opcode _ppcAssembler_setAlignment(internalInfo, 4); uint32 opcMask; uint32 opcBits; ppcAssembler_buildOpcMask(iDef, opcMask, opcBits); internalInfo.opcode = opcBits & opcMask; internalInfo.opcode |= (iDef->compareBits&iDef->maskBits); // handle operands if (iDef->instructionForm == OP_FORM_DYNAMIC) { for (size_t i = 0; i < iDef->encodedOperands.size(); i++) { bool r = std::visit([&](auto&& op) -> bool { return op.AssembleOperand(&internalInfo, iDef, internalInfo.opcode, i); }, iDef->encodedOperands[i]); if (!r) return false; } for(auto& it : iDef->constraints) { std::visit([&](auto&& op) { op.AssembleConstraint(&internalInfo, iDef, internalInfo.opcode); }, it); } } else if (iDef->instructionForm == OP_FORM_NO_OPERAND) { // do nothing } else if (iDef->instructionForm == OP_FORM_OP3_A_CMP) { if (internalInfo.listOperandStr.size() == 2) { // implicit cr0 if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 1, 11) == false) return false; } else { if (_ppcAssembler_processCROperand(internalInfo, 0, 21, true) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 1, 16) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 2, 11) == false) return false; } } else if (iDef->instructionForm == OP_FORM_CMP_SIMM) { if (internalInfo.listOperandStr.size() == 2) { // implicit cr0 if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) return false; if (_ppcAssembler_processImmediateOperandS16(internalInfo, 1, 0) == false) return false; } else { if (_ppcAssembler_processCROperand(internalInfo, 0, 21, true) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 1, 16) == false) return false; if (_ppcAssembler_processImmediateOperandS16(internalInfo, 2, 0) == false) return false; } } else if (iDef->instructionForm == OP_FORM_OP3_A_IMM) { if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 1, 21) == false) return false; if (_ppcAssembler_processImmediateOperandU5(internalInfo, _getOpIndex(iDef, 2), 11) == false) return false; } else if (iDef->instructionForm == OP_FORM_X_FP_CMP) { if (internalInfo.listOperandStr.size() == 2) { // implicit cr0 if (_ppcAssembler_processFPROperand(internalInfo, 0, 16) == false) return false; if (_ppcAssembler_processFPROperand(internalInfo, 1, 11) == false) return false; } else { if (_ppcAssembler_processCROperand(internalInfo, 0, 21, true) == false) return false; if (_ppcAssembler_processFPROperand(internalInfo, 1, 16) == false) return false; if (_ppcAssembler_processFPROperand(internalInfo, 2, 11) == false) return false; } } else if (iDef->instructionForm == OP_FORM_RLWINM) { if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 1, 21) == false) return false; if (_ppcAssembler_processImmediateOperandU5(internalInfo, 2, 11) == false) return false; if (_ppcAssembler_processImmediateOperandU5(internalInfo, 3, 6) == false) return false; if (_ppcAssembler_processImmediateOperandU5(internalInfo, 4, 1) == false) return false; } else if (iDef->instructionForm == OP_FORM_RLWINM_EXTENDED) { if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 1, 21) == false) return false; uint8 SH = 0; uint8 MB = 0; uint8 ME = 0; if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_EXTLWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_EXTLWI_) { sint8 n, b; if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) return false; if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 3, b)) return false; SH = b; MB = 0; ME = n - 1; } else if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_EXTRWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_EXTRWI_) { sint8 n, b; if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) return false; if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 3, b)) return false; SH = b + n; MB = 32 - n; ME = 31; } else if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_SLWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_SLWI_) { sint8 n; if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) return false; SH = n; MB = 0; ME = 31 - n; } else if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_SRWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_SRWI_) { sint8 n; if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) return false; SH = 32 - n; MB = n; ME = 31; } else if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_CLRLWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_CLRLWI_) { sint8 n; if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) return false; SH = 0; MB = n; ME = 31; } else if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_CLRRWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_CLRRWI_) { sint8 n; if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) return false; SH = 0; MB = 0; ME = 31 - n; } else { cemu_assert_debug(false); } internalInfo.opcode |= (SH << 11); internalInfo.opcode |= (MB << 6); internalInfo.opcode |= (ME << 1); } else if (iDef->instructionForm == OP_FORM_RLWNM) { if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 1, 21) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 2, 11) == false) return false; if (_ppcAssembler_processImmediateOperandU5(internalInfo, 3, 6) == false) return false; if (_ppcAssembler_processImmediateOperandU5(internalInfo, 4, 1) == false) return false; } else if (iDef->instructionForm == OP_FORM_RLWNM_EXTENDED) { if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 1, 21) == false) return false; if (_ppcAssembler_processGPROperand(internalInfo, 2, 11) == false) return false; uint32 MB = 0, ME = 0; if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_ROTLW || internalInfo.iDef->ppcAsmOp == PPCASM_OP_ROTLW_) { MB = 0; ME = 31; } internalInfo.opcode |= (MB << 6); internalInfo.opcode |= (ME << 1); } else if (iDef->instructionForm == OP_FORM_BRANCH_S16) { if (iDef->ppcAsmOp == PPCASM_OP_BC) { // generic conditional branch // BC <BO>, <BI>, <dst> sint8 bo; if (_ppcAssembler_processImmediateOperandU5Const(internalInfo, 0, bo) == false) return false; if (_ppcAssembler_processBIOperand(internalInfo, 1, 16) == false) return false; if (_ppcAssembler_processBranchOperandS16(internalInfo, 2) == false) return false; internalInfo.opcode |= (bo << 21); } else { // BLE, BGT etc. if (internalInfo.listOperandStr.size() == 2) { // explicit CR if (_ppcAssembler_processCROperand(internalInfo, 0, 16, true) == false) return false; if (_ppcAssembler_processBranchOperandS16(internalInfo, 1) == false) return false; } else { if (_ppcAssembler_processBranchOperandS16(internalInfo, 0) == false) return false; } if (hasBranchHint) internalInfo.opcode |= (1 << 21); } } else if (iDef->instructionForm == OP_FORM_BRANCH_S24) { if (_ppcAssembler_processBranchOperandS26(internalInfo, 0) == false) return false; } else if (iDef->instructionForm == OP_FORM_XL_CR) { if (_ppcAssembler_processBIOperand(internalInfo, 0, 21) == false) return false; if (_ppcAssembler_processBIOperand(internalInfo, 1, 16) == false) return false; if (_ppcAssembler_processBIOperand(internalInfo, 2, 11) == false) return false; } else { cemu_assert_debug(false); // unsupported instruction form return false; } ctx->outputData.resize(4); ctx->outputData[0] = (uint8)((internalInfo.opcode >> 24) & 0xFF); ctx->outputData[1] = (uint8)((internalInfo.opcode >> 16) & 0xFF); ctx->outputData[2] = (uint8)((internalInfo.opcode >> 8) & 0xFF); ctx->outputData[3] = (uint8)((internalInfo.opcode >> 0) & 0xFF); return true; } void _testAsm(uint32 expected, const char* iText) { PPCAssemblerInOut ctx{}; ctx.virtualAddress = 0; if (!ppcAssembler_assembleSingleInstruction(iText, &ctx)) { cemu_assert_debug(false); return; } cemu_assert_debug(ctx.outputData.size() == 4); uint32 opcode = 0; opcode |= ((uint32)ctx.outputData[0] << 24); opcode |= ((uint32)ctx.outputData[1] << 16); opcode |= ((uint32)ctx.outputData[2] << 8); opcode |= ((uint32)ctx.outputData[3] << 0); cemu_assert_debug(expected == opcode); } void _testAsmFail(const char* iText) { PPCAssemblerInOut ctx{}; ctx.virtualAddress = 0; if (!ppcAssembler_assembleSingleInstruction(iText, &ctx)) return; cemu_assert_debug(false); // should fail } void _testAsmArray(std::vector<uint8> expectedArray, const char* iText) { PPCAssemblerInOut ctx{}; ctx.virtualAddress = 0; if (!ppcAssembler_assembleSingleInstruction(iText, &ctx)) { cemu_assert_debug(false); return; } cemu_assert_debug(ctx.outputData.size() == expectedArray.size()); for (size_t offset = 0; offset < expectedArray.size(); offset++) { cemu_assert_debug(ctx.outputData[offset] == expectedArray[offset]); } } void ppcAsmTestDisassembler() { cemu_assert_debug(_CanStoreInteger(5, 10, false)); cemu_assert_debug(_CanStoreInteger(1023, 10, false)); cemu_assert_debug(!_CanStoreInteger(1024, 10, false)); cemu_assert_debug(_CanStoreInteger(511, 10, true)); cemu_assert_debug(!_CanStoreInteger(512, 10, true)); cemu_assert_debug(_CanStoreInteger(-511, 10, true)); cemu_assert_debug(_CanStoreInteger(-512, 10, true)); cemu_assert_debug(!_CanStoreInteger(-513, 10, true)); PPCDisassembledInstruction disasm; auto disassemble = [&](uint32 opcode, PPCASM_OP ppcAsmCode) { disasm = { 0 }; ppcAssembler_disassemble(0x10000000, opcode, &disasm); cemu_assert_debug(disasm.ppcAsmCode == ppcAsmCode); }; auto checkOperandMask = [&](bool op0 = false, bool op1 = false, bool op2 = false, bool op3 = false) { bool hasOp0 = (disasm.operandMask & (1 << 0)); bool hasOp1 = (disasm.operandMask & (1 << 1)); bool hasOp2 = (disasm.operandMask & (1 << 2)); bool hasOp3 = (disasm.operandMask & (1 << 3)); cemu_assert_debug(hasOp0 == op0); cemu_assert_debug(hasOp1 == op1); cemu_assert_debug(hasOp2 == op2); cemu_assert_debug(hasOp3 == op3); }; auto checkOpCR = [&](size_t index, uint8 crIndex) { cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_CR); cemu_assert_debug(disasm.operand[index].registerIndex == crIndex); }; auto checkOpCRBit = [&](size_t index, uint8 crIndex) { cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_CR_BIT); cemu_assert_debug(disasm.operand[index].registerIndex == crIndex); }; auto checkOpFPR = [&](size_t index, uint8 fprIndex) { cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_FPR); cemu_assert_debug(disasm.operand[index].registerIndex == fprIndex); }; auto checkOpGPR = [&](size_t index, uint8 gprIndex) { cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_GPR); cemu_assert_debug(disasm.operand[index].registerIndex == gprIndex); }; auto checkOpImm = [&](size_t index, sint32 imm) { cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_IMM); cemu_assert_debug(disasm.operand[index].immS32 == imm); }; auto checkOpBranchDst = [&](size_t index, sint32 relOffset) { cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_CIMM); cemu_assert_debug(disasm.operand[index].immU32 == 0x10000000 + relOffset); }; auto checkBranchHint = [&](bool isSet) { cemu_assert_debug(disasm.branchHintBitSet == isSet); }; // addi / subi _testAsm(0x3863FFFF, "addi r3, r3, -1"); _testAsm(0x3863FFFF, "subi r3, r3, 1"); _testAsm(0x387E0134, "addi r3, r30, 0x134"); _testAsm(0x387E0134, "subi r3, r30, 0-0x134"); disassemble(0x387E0134, PPCASM_OP_ADDI); checkOperandMask(true, true, true); checkOpGPR(0, 3); checkOpGPR(1, 30); checkOpImm(2, 0x134); // mulli _testAsm(0x1D1E0005, "mulli r8, r30, 5"); disassemble(0x1D1E0005, PPCASM_OP_MULLI); checkOperandMask(true, true, true); checkOpGPR(0, 8); checkOpGPR(1, 30); checkOpImm(2, 5); // mulli _testAsm(0x1CFEFFF8, "mulli r7, r30, 0-(2 + 2 + 2 + 2)"); // -8 disassemble(0x1CFEFFF8, PPCASM_OP_MULLI); checkOperandMask(true, true, true); checkOpGPR(0, 7); checkOpGPR(1, 30); checkOpImm(2, -8); // subfic _testAsm(0x2304002F, "subfic r24, r4, 0x2F"); disassemble(0x2304002F, PPCASM_OP_SUBFIC); checkOperandMask(true, true, true); checkOpGPR(0, 24); checkOpGPR(1, 4); checkOpImm(2, 0x2F); // fcmpu cr7, f1, f0 _testAsm(0xFF810000, "fcmpu cr7, f1, f0"); disassemble(0xFF810000, PPCASM_OP_FCMPU); checkOperandMask(true, true, true); checkOpCR(0, 7); checkOpFPR(1, 1); checkOpFPR(2, 0); // fcmpu cr0, f1, f0 _testAsm(0xFC010000, "fcmpu cr0, f1, f0"); disassemble(0xFC010000, PPCASM_OP_FCMPU); checkOperandMask(true, true, true); checkOpCR(0, 0); checkOpFPR(1, 1); checkOpFPR(2, 0); // cmpwi r9, -1 _testAsm(0x2C09FFFF, "cmpwi r9, -1"); disassemble(0x2C09FFFF, PPCASM_OP_CMPWI); checkOperandMask(false, true, true); checkOpGPR(1, 9); checkOpImm(2, -1); // cmpwi cr7, r9, 0 _testAsm(0x2F890000, "cmpwi cr7, r9, 0"); disassemble(0x2F890000, PPCASM_OP_CMPWI); checkOperandMask(true, true, true); checkOpCR(0, 7); checkOpGPR(1, 9); checkOpImm(2, 0); // cmplwi r3, 0xF _testAsm(0x2803000F, "cmplwi r3, 0xF"); disassemble(0x2803000F, PPCASM_OP_CMPLWI); checkOperandMask(false, true, true); checkOpGPR(1, 3); checkOpImm(2, 0xF); // cmpw cr7, r4, r10 _testAsm(0x7F845000, "cmpw cr7, r4, r10"); disassemble(0x7F845000, PPCASM_OP_CMPW); checkOperandMask(true, true, true); checkOpCR(0, 7); checkOpGPR(1, 4); checkOpGPR(2, 10); // cmplw cr7, r31, r9 _testAsm(0x7F9F4840, "cmplw cr7, r31, r9"); disassemble(0x7F9F4840, PPCASM_OP_CMPLW); checkOperandMask(true, true, true); checkOpCR(0, 7); checkOpGPR(1, 31); checkOpGPR(2, 9); // cmplw r24, r28 _testAsm(0x7C18E040, "cmplw r24, r28"); disassemble(0x7C18E040, PPCASM_OP_CMPLW); checkOperandMask(false, true, true); checkOpGPR(1, 24); checkOpGPR(2, 28); // b .+0x18 _testAsm(0x48000018, "b .+0x18"); // b 0x10000018 //_testAsm(0x48000018, "b 0x10000018"); // bgt cr7, .+0x40 _testAsm(0x419D0040, "bgt cr7, .+0x40"); disassemble(0x419D0040, PPCASM_OP_BGT); checkOperandMask(true, true); checkOpCR(0, 7); checkOpBranchDst(1, 0x40); checkBranchHint(false); // bnl cr7, .+0x40 (alias to bgt) _testAsm(0x419D0040, "bnl cr7, .+0x40"); // beq cr7, .+0x14 _testAsm(0x419E0014, "beq cr7, .+0x14"); disassemble(0x419E0014, PPCASM_OP_BEQ); checkOperandMask(true, true); checkOpCR(0, 7); checkOpBranchDst(1, 0x14); checkBranchHint(false); // beq .-0x4C _testAsm(0x4182FFB4, "beq .-0x4C"); disassemble(0x4182FFB4, PPCASM_OP_BEQ); checkOperandMask(true); checkOpBranchDst(0, -0x4C); checkBranchHint(false); // beq+ .+8 _testAsm(0x41A20008, "beq+ .+8"); disassemble(0x41A20008, PPCASM_OP_BEQ); checkOperandMask(true, false); checkOpBranchDst(0, +0x8); checkBranchHint(true); // cror _testAsm(0x4F5DF382, "cror 4*cr6+eq, 4*cr7+gt, 4*cr7+eq"); _testAsm(0x4C411B82, "cror eq, gt, so"); disassemble(0x4F5DF382, PPCASM_OP_CROR); checkOperandMask(true, true, true); checkOpCRBit(0, 6 * 4 + 2); checkOpCRBit(1, 7 * 4 + 1); checkOpCRBit(2, 7 * 4 + 2); // slw & srw _testAsm(0x7D202030, "slw r0, r9, r4"); _testAsm(0x7D80FC31, "srw. r0, r12, r31"); disassemble(0x7D202030, PPCASM_OP_SLW); checkOperandMask(true, true, true); checkOpGPR(0, 0); checkOpGPR(1, 9); checkOpGPR(2, 4); // FADD _testAsm(0xFC29502A, "fadd f1, f9, f10"); disassemble(0xFC29502A, PPCASM_OP_FADD); checkOperandMask(true, true, true); checkOpFPR(0, 1); checkOpFPR(1, 9); checkOpFPR(2, 10); // FSUB _testAsm(0xFDAB4028, "fsub f13, f11, f8"); _testAsm(0xFD0D4828, "fsub f8, f13, f9"); disassemble(0xFD0D4828, PPCASM_OP_FSUB); checkOperandMask(true, true, true); checkOpFPR(0, 8); checkOpFPR(1, 13); checkOpFPR(2, 9); // FMUL _testAsm(0xFD4B0332, "fmul f10, f11, f12"); disassemble(0xFD4B0332, PPCASM_OP_FMUL); checkOperandMask(true, true, true); checkOpFPR(0, 10); checkOpFPR(1, 11); checkOpFPR(2, 12); // FDIV _testAsm(0xFD885824, "fdiv f12, f8, f11"); disassemble(0xFD885824, PPCASM_OP_FDIV); checkOperandMask(true, true, true); checkOpFPR(0, 12); checkOpFPR(1, 8); checkOpFPR(2, 11); // FMADD _testAsm(0xFC2A0A3A, "fmadd f1, f10, f8, f1"); _testAsm(0xFC0A0B7A, "fmadd f0, f10, f13, f1"); _testAsm(0xFFE0F33A, "fmadd f31, f0, f12, f30"); disassemble(0xFFE0F33A, PPCASM_OP_FMADD); checkOperandMask(true, true, true, true); checkOpFPR(0, 31); checkOpFPR(1, 0); checkOpFPR(2, 12); checkOpFPR(3, 30); // FNMADDS _testAsm(0xED8A627E, "fnmadds f12, f10, f9, f12"); disassemble(0xED8A627E, PPCASM_OP_FNMADDS); checkOperandMask(true, true, true, true); checkOpFPR(0, 12); checkOpFPR(1, 10); checkOpFPR(2, 9); checkOpFPR(3, 12); // still missing test cases: FNMADD, FMADDS // FMSUB _testAsm(0xFC1C0338, "fmsub f0, f28, f12, f0"); _testAsm(0xFD204B38, "fmsub f9, f0, f12, f9"); disassemble(0xFD204B38, PPCASM_OP_FMSUB); checkOperandMask(true, true, true, true); checkOpFPR(0, 9); checkOpFPR(1, 0); checkOpFPR(2, 12); checkOpFPR(3, 9); // FNMSUB _testAsm(0xFD7DCB3C, "fnmsub f11, f29, f12, f25"); disassemble(0xFD7DCB3C, PPCASM_OP_FNMSUB); checkOperandMask(true, true, true, true); checkOpFPR(0, 11); checkOpFPR(1, 29); checkOpFPR(2, 12); checkOpFPR(3, 25); // FMSUBS _testAsm(0xEE3D5838, "fmsubs f17, f29, f0, f11"); _testAsm(0xEDB84AB8, "fmsubs f13, f24, f10, f9"); disassemble(0xEDB84AB8, PPCASM_OP_FMSUBS); checkOperandMask(true, true, true, true); checkOpFPR(0, 13); checkOpFPR(1, 24); checkOpFPR(2, 10); checkOpFPR(3, 9); // FNMSUBS _testAsm(0xED4951BC, "fnmsubs f10, f9, f6, f10"); _testAsm(0xED253AFC, "fnmsubs f9, f5, f11, f7"); disassemble(0xED253AFC, PPCASM_OP_FNMSUBS); checkOperandMask(true, true, true, true); checkOpFPR(0, 9); checkOpFPR(1, 5); checkOpFPR(2, 11); checkOpFPR(3, 7); // FRSQRTE _testAsm(0xFCE06034, "frsqrte f7, f12"); _testAsm(0xFCC06834, "frsqrte f6, f13"); disassemble(0xFCC06834, PPCASM_OP_FRSQRTE); checkOperandMask(true, true); checkOpFPR(0, 6); checkOpFPR(1, 13); // FRSP _testAsm(0xFFA03018, "frsp f29, f6"); disassemble(0xFFA03018, PPCASM_OP_FRSP); checkOperandMask(true, true); checkOpFPR(0, 29); checkOpFPR(1, 6); // FNEG _testAsm(0xFDA05850, "fneg f13, f11"); disassemble(0xFDA05850, PPCASM_OP_FNEG); checkOperandMask(true, true); checkOpFPR(0, 13); checkOpFPR(1, 11); // FMR _testAsm(0xFCE06090, "fmr f7, f12"); disassemble(0xFCE06090, PPCASM_OP_FMR); checkOperandMask(true, true); checkOpFPR(0, 7); checkOpFPR(1, 12); // FABS _testAsm(0xFC200A10, "fabs f1, f1"); disassemble(0xFC200A10, PPCASM_OP_FABS); checkOperandMask(true, true); checkOpFPR(0, 1); checkOpFPR(1, 1); // FCTIWZ _testAsm(0xFD80401E, "fctiwz f12, f8"); disassemble(0xFD80401E, PPCASM_OP_FCTIWZ); checkOperandMask(true, true); checkOpFPR(0, 12); checkOpFPR(1, 8); // PS_MADD _testAsm(0x1168637A, "ps_madd f11, f8, f13, f12"); disassemble(0x1168637A, PPCASM_OP_PS_MADD); checkOperandMask(true, true, true, true); checkOpFPR(0, 11); checkOpFPR(1, 8); checkOpFPR(2, 13); checkOpFPR(3, 12); // PS_MSUB _testAsm(0x11A55FF8, "ps_msub f13, f5, f31, f11"); disassemble(0x11A55FF8, PPCASM_OP_PS_MSUB); checkOperandMask(true, true, true, true); checkOpFPR(0, 13); checkOpFPR(1, 5); checkOpFPR(2, 31); checkOpFPR(3, 11); // PS_NMADD _testAsm(0x118850FE, "ps_nmadd f12, f8, f3, f10"); disassemble(0x118850FE, PPCASM_OP_PS_NMADD); checkOperandMask(true, true, true, true); checkOpFPR(0, 12); checkOpFPR(1, 8); checkOpFPR(2, 3); checkOpFPR(3, 10); // PS_NMSUB _testAsm(0x112832FC, "ps_nmsub f9, f8, f11, f6"); disassemble(0x112832FC, PPCASM_OP_PS_NMSUB); checkOperandMask(true, true, true, true); checkOpFPR(0, 9); checkOpFPR(1, 8); checkOpFPR(2, 11); checkOpFPR(3, 6); // PS_ADD _testAsm(0x112A582A, "ps_add f9, f10, f11"); disassemble(0x112A582A, PPCASM_OP_PS_ADD); checkOperandMask(true, true, true); checkOpFPR(0, 9); checkOpFPR(1, 10); checkOpFPR(2, 11); // PS_SUB _testAsm(0x11663828, "ps_sub f11, f6, f7"); disassemble(0x11663828, PPCASM_OP_PS_SUB); checkOperandMask(true, true, true); checkOpFPR(0, 11); checkOpFPR(1, 6); checkOpFPR(2, 7); // PS_MUL _testAsm(0x11680332, "ps_mul f11, f8, f12"); disassemble(0x11680332, PPCASM_OP_PS_MUL); checkOperandMask(true, true, true); checkOpFPR(0, 11); checkOpFPR(1, 8); checkOpFPR(2, 12); // PS_DIV _testAsm(0x10A45824, "ps_div f5, f4, f11"); disassemble(0x10A45824, PPCASM_OP_PS_DIV); checkOperandMask(true, true, true); checkOpFPR(0, 5); checkOpFPR(1, 4); checkOpFPR(2, 11); // PS_MERGE10 _testAsm(0x10CC6C20, "ps_merge00 f6, f12, f13"); disassemble(0x10CC6C20, PPCASM_OP_PS_MERGE00); checkOperandMask(true, true, true); checkOpFPR(0, 6); checkOpFPR(1, 12); checkOpFPR(2, 13); // random extra tests _testAsm(0x419D0040, "bgt cr7, .+0x40"); _testAsm(0x50AB042E, "rlwimi r11, r5, 0,16,23"); _testAsm(0xFF810000, "fcmpu cr7, f1, f0"); _testAsm(0xFC010000, "fcmpu cr0, f1, f0"); _testAsm(0x7F845000, "cmpw cr7, r4, r10"); _testAsm(0x2C090000, "cmpwi r9, 0"); // implicit cr0 _testAsm(0x2C090000, "cmpwi cr0, r9, 0"); _testAsm(0x2F9E001F, "cmpwi cr7, r30, 0x1F"); _testAsm(0x7D573850, "subf r10, r23, r7"); _testAsm(0x7D573850, "sub r10, r7, r23"); // alias for subf _testAsm(0x7D862851, "subf. r12, r6, r5"); _testAsm(0x7D7C1896, "mulhw r11, r28, r3"); _testAsm(0x7D436016, "mulhwu r10, r3, r12"); _testAsm(0x7FE318F8, "nor r3, r31, r3"); _testAsm(0x7D29F8F8, "nor r9, r9, r31"); _testAsm(0x7F26C8F8, "nor r6, r25, r25"); _testAsm(0x7F26C8F8, "not r6, r25"); // alias for nor where rA == rB _testAsm(0x7FE4FB78, "mr r4, r31"); // alias for or where rA == rB _testAsm(0x7C7EEAA6, "mfspr r3, spr958"); _testAsm(0x7C78E3A6, "mtspr spr920, r3"); _testAsm(0x7D6802A6, "mflr r11"); _testAsm(0x7E6902A6, "mfctr r19"); _testAsm(0x7D430034, "cntlzw r3, r10"); _testAsm(0x7C640774, "extsb r4, r3"); _testAsm(0x1C8A00B8, "mulli r4, r10, 0xB8"); _testAsm(0x1CFEFFF8, "mulli r7, r30, -8"); _testAsm(0x1CFE8000, "mulli r7, r30, -0x8000"); _testAsm(0x1CFE7FFF, "mulli r7, r30, 0x7FFF"); _testAsmFail("mulli r7, r30, -0x8001"); _testAsmFail("mulli r7, r30, 0x8000"); _testAsm(0x38E0FFFF, "li r7, 0xFFFF"); // li/lis allows both signed and unsigned 16-bit range _testAsm(0x38E0FFFF, "li r7, -1"); _testAsm(0x38E08000, "li r7, -0x8000"); _testAsmFail("li r7, -0x8001"); _testAsmFail("li r7, 0xFFFF+1"); // test set 2 _testAsm(0x7c0903a6, "mtctr r0"); _testAsm(0x7c6903a6, "mtctr r3"); _testAsm(0x7d0903a6, "mtctr r8"); _testAsm(0x7d2903a6, "mtctr r9"); _testAsm(0x7c0803a6, "mtlr r0"); _testAsm(0x7c6803a6, "mtlr r3"); _testAsm(0x7c8803a6, "mtlr r4"); _testAsm(0x7c065896, "mulhw r0, r6, r11"); _testAsm(0x7ce85096, "mulhw r7, r8, r10"); _testAsm(0x7d494096, "mulhw r10, r9, r8"); _testAsm(0x7ca4c016, "mulhwu r5, r4, r24"); _testAsm(0x7ce53016, "mulhwu r7, r5, r6"); _testAsm(0x7d373816, "mulhwu r9, r23, r7"); _testAsm(0x7d664816, "mulhwu r11, r6, r9"); _testAsm(0x1d2ae09e, "mulli r9, r10, -0x1F62"); _testAsm(0x1fe900ff, "mulli r31, r9, 0xFF"); _testAsm(0x1ffefffe, "mulli r31, r30, -2"); _testAsm(0x7e4531d6, "mullw r18, r5, r6"); _testAsm(0x7fe429d6, "mullw r31, r4, r5"); _testAsm(0x7d0951d7, "mullw. r8, r9, r10"); _testAsm(0x7f0941d7, "mullw. r24, r9, r8"); _testAsm(0x7c6600d0, "neg r3, r6"); _testAsm(0x7df600d0, "neg r15, r22"); _testAsm(0x7eeb00d0, "neg r23, r11"); _testAsm(0x7ca428f8, "not r4, r5"); _testAsm(0x7df578f8, "not r21, r15"); _testAsm(0x7fe9f8f8, "not r9, r31"); _testAsm(0x7fc83378, "or r8, r30, r6"); _testAsm(0x7de6a379, "or. r6, r15, r20"); _testAsm(0x7f29fb79, "or. r9, r25, r31"); _testAsm(0x7ceafb38, "orc r10, r7, r31"); _testAsm(0x7fbe4338, "orc r30, r29, r8"); _testAsm(0x63a90001, "ori r9, r29, 1"); _testAsm(0x63c3b46e, "ori r3, r30, 0xB46E"); _testAsm(0x63e4ffff, "ori r4, r31, 0xFFFF"); _testAsm(0x650a8000, "oris r10, r8, 0x8000"); _testAsm(0x676c792e, "oris r12, r27, 0x792E"); _testAsm(0x6788ffff, "oris r8, r28, 0xFFFF"); _testAsm(0x67ea3fe0, "oris r10, r31, 0x3FE0"); _testAsm(0x51231fb8, "rlwimi r3, r9, 3,30,28"); _testAsm(0x5249fefe, "rlwimi r9, r18, 31,27,31"); _testAsm(0x52513a20, "rlwimi r17, r18, 7,8,16"); _testAsm(0x53494420, "rlwimi r9, r26, 8,16,16"); _testAsm(0x53657269, "rlwimi. r5, r27, 14,9,20"); _testAsm(0x546906f2, "rlwinm r9, r3, 0,27,25"); _testAsm(0x55284ad6, "rlwinm r8, r9, 9,11,11"); _testAsm(0x57c90428, "rlwinm r9, r30, 0,16,20"); _testAsm(0x57e5d57a, "rlwinm r5, r31, 26,21,29"); _testAsm(0x57ea34b0, "rlwinm r10, r31, 6,18,24"); _testAsm(0x54e1bbb1, "rlwinm. r1, r7, 23,14,24"); _testAsm(0x550707fb, "rlwinm. r7, r8, 0,31,29"); _testAsm(0x552003f1, "rlwinm. r0, r9, 0,15,24"); _testAsm(0x5527022f, "rlwinm. r7, r9, 0,8,23"); _testAsm(0x55270777, "rlwinm. r7, r9, 0,29,27"); _testAsm(0x552a0777, "rlwinm. r10, r9, 0,29,27"); _testAsm(0x552a07b9, "rlwinm. r10, r9, 0,30,28"); _testAsm(0x5d5f583e, "rotlw r31, r10, r11"); _testAsm(0x5eea703e, "rotlw r10, r23, r14"); _testAsm(0x5f0ab03e, "rotlw r10, r24, r22"); _testAsm(0x541a783e, "rotlwi r26, r0, 15"); _testAsm(0x5488383e, "rotlwi r8, r4, 7"); _testAsm(0x5779683e, "rotlwi r25, r27, 13"); _testAsm(0x54e4c83e, "rotrwi r4, r7, 7"); _testAsm(0x7eeaf830, "slw r10, r23, r31"); _testAsm(0x7f68c030, "slw r8, r27, r24"); _testAsm(0x7fe8f030, "slw r8, r31, r30"); _testAsm(0x7f484831, "slw. r8, r26, r9"); _testAsm(0x7f844031, "slw. r4, r28, r8"); _testAsm(0x57e42036, "slwi r4, r31, 4"); _testAsm(0x57e42834, "slwi r4, r31, 5"); _testAsm(0x57f37820, "slwi r19, r31, 15"); _testAsm(0x5488083d, "slwi. r8, r4, 1"); _testAsm(0x552a3033, "slwi. r10, r9, 6"); _testAsm(0x5647083d, "slwi. r7, r18, 1"); _testAsm(0x7c879e30, "sraw r7, r4, r19"); _testAsm(0x7c891e30, "sraw r9, r4, r3"); _testAsm(0x7fca4e30, "sraw r10, r30, r9"); _testAsm(0x7d380e70, "srawi r24, r9, 1"); _testAsm(0x7c79fe71, "srawi. r25, r3, 0x1F"); _testAsm(0x7fb91e71, "srawi. r25, r29, 3"); _testAsm(0x7cc33c30, "srw r3, r6, r7"); _testAsm(0x7d09e430, "srw r9, r8, r28"); _testAsm(0x7d3d4430, "srw r29, r9, r8"); _testAsm(0x5403d97e, "srwi r3, r0, 5"); _testAsm(0x57dc843e, "srwi r28, r30, 16"); _testAsm(0x552a463f, "srwi. r10, r9, 24"); _testAsm(0x5532c9ff, "srwi. r18, r9, 7"); _testAsm(0x57f8f87f, "srwi. r24, r31, 1"); _testAsm(0x9be80201, "stb r31, 0x201(r8)"); _testAsm(0x9bfafffc, "stb r31, -4(r26)"); _testAsm(0x9bfeffff, "stb r31, -1(r30)"); _testAsm(0x9dc3ffff, "stbu r14, -1(r3)"); _testAsm(0x9e55006f, "stbu r18, 0x6F(r21)"); _testAsm(0x9fdc2008, "stbu r30, 0x2008(r28)"); _testAsm(0x7ce521ee, "stbux r7, r5, r4"); _testAsm(0x7d4919ee, "stbux r10, r9, r3"); _testAsm(0x7f6919ee, "stbux r27, r9, r3"); _testAsm(0x7c0531ae, "stbx r0, r5, r6"); _testAsm(0x7d4941ae, "stbx r10, r9, r8"); _testAsm(0x7fdfe9ae, "stbx r30, r31, r29"); _testAsm(0xdb690038, "stfd f27, 0x38(r9)"); _testAsm(0xdb890040, "stfd f28, 0x40(r9)"); _testAsm(0xde230008, "stfdu f17, 8(r3)"); _testAsm(0x7c1235ae, "stfdx f0, r18, r6"); _testAsm(0x7c1f3dae, "stfdx f0, r31, r7"); _testAsm(0x7c1f45ae, "stfdx f0, r31, r8"); _testAsm(0x7c001fae, "stfiwx f0, 0, r3"); _testAsm(0x7c3e4fae, "stfiwx f1, r30, r9"); _testAsm(0x7fa04fae, "stfiwx f29, 0, r9"); _testAsm(0xd008fffc, "stfs f0, -4(r8)"); _testAsm(0xd3ff1604, "stfs f31, 0x1604(r31)"); _testAsm(0xd41e001c, "stfsu f0, 0x1C(r30)"); _testAsm(0x7d87356e, "stfsux f12, r7, r6"); _testAsm(0x7c104d2e, "stfsx f0, r16, r9"); _testAsm(0x7f5d452e, "stfsx f26, r29, r8"); _testAsm(0xb3bf000e, "sth r29, 0xE(r31)"); _testAsm(0xb3f8fffc, "sth r31, -4(r24)"); _testAsm(0xb3fa0000, "sth r31, 0(r26)"); _testAsm(0xb3fd0000, "sth r31, 0(r29)"); _testAsm(0xb528105a, "sthu r9, 0x105A(r8)"); _testAsm(0xb52afffe, "sthu r9, -2(r10)"); _testAsm(0xb53f0002, "sthu r9, 2(r31)"); _testAsm(0x7c69536e, "sthux r3, r9, r10"); _testAsm(0x7c8a3b6e, "sthux r4, r10, r7"); _testAsm(0x7c0ca32e, "sthx r0, r12, r20"); _testAsm(0x7d242b2e, "sthx r9, r4, r5"); _testAsm(0x7fe93b2e, "sthx r31, r9, r7"); _testAsm(0xbdd0b4d0, "stmw r14, -0x4B30(r16)"); _testAsm(0xbed182d1, "stmw r22, -0x7D2F(r17)"); _testAsm(0x7cbb65aa, "stswi r5, r27, 0xC"); _testAsm(0x7cbe05aa, "stswi r5, r30, 0"); _testAsm(0x91348378, "stw r9, -0x7C88(r20)"); _testAsm(0x93e9fddc, "stw r31, -0x224(r9)"); _testAsm(0x93fe45c8, "stw r31, 0x45C8(r30)"); _testAsm(0x7c604d2c, "stwbrx r3, 0, r9"); _testAsm(0x7d20f52c, "stwbrx r9, 0, r30"); _testAsm(0x7fa04d2c, "stwbrx r29, 0, r9"); _testAsm(0x7d20512d, "stwcx. r9, 0, r10"); _testAsm(0x94650208, "stwu r3, 0x208(r5)"); _testAsm(0x953ffffc, "stwu r9, -4(r31)"); _testAsm(0x97fe0208, "stwu r31, 0x208(r30)"); _testAsm(0x7d21196e, "stwux r9, r1, r3"); _testAsm(0x7d23516e, "stwux r9, r3, r10"); _testAsm(0x7d41496e, "stwux r10, r1, r9"); _testAsm(0x7cbc512e, "stwx r5, r28, r10"); _testAsm(0x7d5ce92e, "stwx r10, r28, r29"); _testAsm(0x7ffee92e, "stwx r31, r30, r29"); _testAsm(0x7c1ec050, "subf r0, r30, r24"); _testAsm(0x7fbee050, "subf r29, r30, r28"); _testAsm(0x7fe91850, "subf r31, r9, r3"); _testAsm(0x7c1e5851, "subf. r0, r30, r11"); _testAsm(0x7fc42851, "subf. r30, r4, r5"); _testAsm(0x7fc74851, "subf. r30, r7, r9"); _testAsm(0x7cffd810, "subfc r7, r31, r27"); _testAsm(0x7d053010, "subfc r8, r5, r6"); _testAsm(0x7d093810, "subfc r8, r9, r7"); _testAsm(0x7f884910, "subfe r28, r8, r9"); _testAsm(0x7fe95110, "subfe r31, r9, r10"); _testAsm(0x7fea4910, "subfe r31, r10, r9"); _testAsm(0x20d0b2d1, "subfic r6, r16, -0x4D2F"); _testAsm(0x2109fc02, "subfic r8, r9, -0x3FE"); _testAsm(0x2144001f, "subfic r10, r4, 0x1F"); _testAsm(0x7c04f278, "xor r4, r0, r30"); _testAsm(0x7c08fa78, "xor r8, r0, r31"); _testAsm(0x7c0b4a78, "xor r11, r0, r9"); _testAsm(0x68640008, "xori r4, r3, 8"); _testAsm(0x68652064, "xori r5, r3, 0x2064"); _testAsm(0x692076c3, "xori r0, r9, 0x76C3"); _testAsm(0x6c0a8000, "xoris r10, r0, 0x8000"); _testAsm(0x6c68ffff, "xoris r8, r3, 0xFFFF"); _testAsm(0x6c617267, "xoris r1, r3, 0x7267"); _testAsm(0x6fe98000, "xoris r9, r31, 0x8000"); _testAsm(0x6fe9f000, "xoris r9, r31, 0xF000"); _testAsm(0x6fe9ff00, "xoris r9, r31, 0xFF00"); _testAsm(0x6fe9ffff, "xoris r9, r31, 0xFFFF"); // data directives _testAsmArray({ 0x00, 0x00, 0x00, 0x01 }, ".int 1"); _testAsmArray({ 0x00, 0x00, 0x00, 0x01, 0x11, 0x22, 0x33, 0x44 }, ".int 1, 0x11223344"); _testAsmArray({ 0x42, 0xf6, 0x00, 0x00 }, ".float 123.0"); _testAsmArray({ 0x7f }, ".byte 0x7f"); _testAsmArray({ 0x74, 0x65, 0x73, 0x74, 0x00 }, ".byte \"test\""); _testAsmArray({ 0x41, 0x42, 0x43, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00 }, ".byte \"ABC\", \"test\""); } void ppcAsmTest() { #ifdef CEMU_DEBUG_ASSERT ppcAsmTestDisassembler(); #endif } ================================================ FILE: src/Cemu/PPCAssembler/ppcAssembler.h ================================================ #pragma once #include <boost/container/small_vector.hpp> #define PPCASM_OPERAND_COUNT 5 #define PPCASM_OPERAND_TYPE_GPR 0 // r0 - r31 #define PPCASM_OPERAND_TYPE_FPR 1 // f0 - f31 #define PPCASM_OPERAND_TYPE_SPR 2 // spr0 - spr511 #define PPCASM_OPERAND_TYPE_IMM 3 // integer constants. E.g. 0x123 #define PPCASM_OPERAND_TYPE_MEM 4 // [r0 + 1234] #define PPCASM_OPERAND_TYPE_CIMM 5 // virtual addr of code destination (used for branches) #define PPCASM_OPERAND_TYPE_CR 6 // cr0-cr7 #define PPCASM_OPERAND_TYPE_CR_BIT 7 // cr bit 0-31. Example display form: '4*cr1+eq' #define PPCASM_OPERAND_TYPE_PSQMODE 8 // single or paired mode control for PSQ_L*/PSQ_ST* instructions enum PPCASM_OP { PPCASM_OP_UKN, PPCASM_OP_ADDI, PPCASM_OP_SUBI, // special form of ADDI PPCASM_OP_ADDIS, PPCASM_OP_ADDIC, PPCASM_OP_ADDIC_, PPCASM_OP_ADD, PPCASM_OP_ADD_, PPCASM_OP_SUBF, PPCASM_OP_SUBF_, PPCASM_OP_SUBFC, PPCASM_OP_SUBFC_, PPCASM_OP_SUBFE, PPCASM_OP_SUBFE_, PPCASM_OP_SUBFIC, PPCASM_OP_SUB, // alias mnemonic for subf with second and third operand swapped PPCASM_OP_SUB_, PPCASM_OP_MULLI, PPCASM_OP_MULLW, PPCASM_OP_MULLW_, PPCASM_OP_MULHW, PPCASM_OP_MULHW_, PPCASM_OP_MULHWU, PPCASM_OP_MULHWU_, PPCASM_OP_DIVW, PPCASM_OP_DIVW_, PPCASM_OP_DIVWU, PPCASM_OP_DIVWU_, PPCASM_OP_AND, PPCASM_OP_AND_, PPCASM_OP_ANDC, PPCASM_OP_ANDC_, PPCASM_OP_OR, PPCASM_OP_OR_, PPCASM_OP_ORC, PPCASM_OP_XOR, PPCASM_OP_NOR, PPCASM_OP_NOR_, PPCASM_OP_NOT, // alias to NOR PPCASM_OP_NOT_, PPCASM_OP_NEG, PPCASM_OP_NEG_, PPCASM_OP_ANDI_, PPCASM_OP_ANDIS_, PPCASM_OP_ORI, PPCASM_OP_ORIS, PPCASM_OP_XORI, PPCASM_OP_XORIS, PPCASM_OP_CNTLZW, PPCASM_OP_EXTSB, PPCASM_OP_EXTSH, PPCASM_OP_CNTLZW_, PPCASM_OP_EXTSB_, PPCASM_OP_EXTSH_, PPCASM_OP_SRAW, PPCASM_OP_SRAW_, PPCASM_OP_SRAWI, PPCASM_OP_SRAWI_, PPCASM_OP_SLW, PPCASM_OP_SLW_, PPCASM_OP_SRW, PPCASM_OP_SRW_, PPCASM_OP_RLWINM, PPCASM_OP_RLWINM_, PPCASM_OP_RLWIMI, PPCASM_OP_RLWIMI_, // rlwinm extended mnemonics PPCASM_OP_EXTLWI, PPCASM_OP_EXTLWI_, PPCASM_OP_EXTRWI, PPCASM_OP_EXTRWI_, PPCASM_OP_ROTLWI, PPCASM_OP_ROTLWI_, PPCASM_OP_ROTRWI, PPCASM_OP_ROTRWI_, PPCASM_OP_SLWI, PPCASM_OP_SLWI_, PPCASM_OP_SRWI, PPCASM_OP_SRWI_, PPCASM_OP_CLRLWI, PPCASM_OP_CLRLWI_, PPCASM_OP_CLRRWI, PPCASM_OP_CLRRWI_, // rlwimi extended mnemonics //PPCASM_OP_INSLWI, rlwimi //PPCASM_OP_INSLWI_, rlwimi //PPCASM_OP_INSRWI, rlwimi //PPCASM_OP_INSRWI_, rlwimi // rlwnm extended mnemonics PPCASM_OP_RLWNM, PPCASM_OP_RLWNM_, PPCASM_OP_ROTLW, PPCASM_OP_ROTLW_, PPCASM_OP_CMPWI, PPCASM_OP_CMPLWI, PPCASM_OP_CMPW, PPCASM_OP_CMPLW, PPCASM_OP_MR, PPCASM_OP_MR_, PPCASM_OP_MFSPR, PPCASM_OP_MTSPR, PPCASM_OP_LMW, PPCASM_OP_LWZ, PPCASM_OP_LWZU, PPCASM_OP_LWZX, PPCASM_OP_LWZUX, PPCASM_OP_LHZ, PPCASM_OP_LHZU, PPCASM_OP_LHZX, PPCASM_OP_LHZUX, PPCASM_OP_LHA, PPCASM_OP_LHAU, PPCASM_OP_LHAX, PPCASM_OP_LHAUX, PPCASM_OP_LBZ, PPCASM_OP_LBZU, PPCASM_OP_LBZX, PPCASM_OP_LBZUX, PPCASM_OP_STMW, PPCASM_OP_STW, PPCASM_OP_STWU, PPCASM_OP_STWX, PPCASM_OP_STWUX, PPCASM_OP_STH, PPCASM_OP_STHU, PPCASM_OP_STHX, PPCASM_OP_STHUX, PPCASM_OP_STB, PPCASM_OP_STBU, PPCASM_OP_STBX, PPCASM_OP_STBUX, PPCASM_OP_STWBRX, PPCASM_OP_STHBRX, PPCASM_OP_STSWI, PPCASM_OP_LWARX, PPCASM_OP_STWCX_, PPCASM_OP_B, PPCASM_OP_BA, PPCASM_OP_BL, PPCASM_OP_BLA, PPCASM_OP_BC, PPCASM_OP_BNE, PPCASM_OP_BEQ, PPCASM_OP_BGE, PPCASM_OP_BGT, PPCASM_OP_BLT, PPCASM_OP_BLE, PPCASM_OP_BDZ, PPCASM_OP_BDNZ, PPCASM_OP_BLR, PPCASM_OP_BLTLR, // less PPCASM_OP_BLELR, // less or equal PPCASM_OP_BEQLR, // equal PPCASM_OP_BGELR, // greater or equal PPCASM_OP_BGTLR, // greater PPCASM_OP_BNELR, // not equal PPCASM_OP_BCTR, PPCASM_OP_BCTRL, // CR PPCASM_OP_CROR, PPCASM_OP_CRORC, PPCASM_OP_CRXOR, PPCASM_OP_CREQV, PPCASM_OP_CRNOR, PPCASM_OP_CRAND, PPCASM_OP_CRNAND, PPCASM_OP_CRANDC, PPCASM_OP_CRSET, // simplified mnemonic for CREQV PPCASM_OP_CRCLR, // simplified mnemonic for CRXOR PPCASM_OP_CRMOVE, // simplified mnemonic for CROR PPCASM_OP_CRNOT, // simplified mnemonic for CRNOR // floating point load/store PPCASM_OP_LFS, PPCASM_OP_LFSU, PPCASM_OP_LFSX, PPCASM_OP_LFSUX, PPCASM_OP_LFD, PPCASM_OP_LFDU, PPCASM_OP_LFDX, PPCASM_OP_LFDUX, PPCASM_OP_STFS, PPCASM_OP_STFSU, PPCASM_OP_STFSX, PPCASM_OP_STFSUX, PPCASM_OP_STFD, PPCASM_OP_STFDU, PPCASM_OP_STFDX, PPCASM_OP_STFDUX, PPCASM_OP_STFIWX, // FP PPCASM_OP_FMR, PPCASM_OP_FABS, PPCASM_OP_FNEG, PPCASM_OP_FRSP, PPCASM_OP_FRSQRTE, PPCASM_OP_FADD, PPCASM_OP_FADDS, PPCASM_OP_FSUB, PPCASM_OP_FSUBS, PPCASM_OP_FMUL, PPCASM_OP_FMULS, PPCASM_OP_FDIV, PPCASM_OP_FDIVS, PPCASM_OP_FMADD, PPCASM_OP_FMADDS, PPCASM_OP_FNMADD, PPCASM_OP_FNMADDS, PPCASM_OP_FMSUB, PPCASM_OP_FMSUBS, PPCASM_OP_FNMSUB, PPCASM_OP_FNMSUBS, PPCASM_OP_FCTIWZ, PPCASM_OP_FCMPU, PPCASM_OP_FCMPO, // PS PPCASM_OP_PS_MERGE00, PPCASM_OP_PS_MERGE01, PPCASM_OP_PS_MERGE10, PPCASM_OP_PS_MERGE11, PPCASM_OP_PS_ADD, PPCASM_OP_PS_SUB, PPCASM_OP_PS_DIV, PPCASM_OP_PS_MUL, PPCASM_OP_PS_MADD, PPCASM_OP_PS_MSUB, PPCASM_OP_PS_NMADD, PPCASM_OP_PS_NMSUB, // cache & misc PPCASM_OP_ISYNC, // extended mnemonics PPCASM_OP_NOP, // ORI PPCASM_OP_LI, // ADDI PPCASM_OP_LIS, // ADDIS PPCASM_OP_MFLR, // MFSPR PPCASM_OP_MTLR, // MTSPR PPCASM_OP_MFCTR, // MFSPR PPCASM_OP_MTCTR, // MTSPR }; struct PPCDisassemblerOperand { uint8 type; uint16 registerIndex; union { sint32 immS32; uint32 immU32; }; bool isSignedImm; uint8 immWidth; // in bits. E.g. 16 for ADDI }; struct PPCDisassembledInstruction { // output uint32 ppcAsmCode; uint8 operandMask; PPCDisassemblerOperand operand[PPCASM_OPERAND_COUNT]; // conditional branches bool branchHintBitSet{false}; }; const char* ppcAssembler_getInstructionName(uint32 ppcAsmOp); void ppcAssembler_disassemble(uint32 virtualAddress, uint32 opcode, PPCDisassembledInstruction* disInstr); enum class PPCASM_RELOC { // code U32_MASKED_IMM, BRANCH_S16, BRANCH_S26, // data constants FLOAT, // 4 byte float data DOUBLE, // 8 byte double precision float data U32, // 4 byte unsigned integer U16, // 2 byte unsigned integer U8, // 1 byte unsigned integer }; struct PPCAssemblerReloc { PPCAssemblerReloc(PPCASM_RELOC relocType, std::string expression, uint32 byteOffset, uint8 bitOffset, uint8 bitCount) : m_relocType(relocType), m_expression(expression), m_byteOffset(byteOffset), m_bitOffset(bitOffset), m_bitCount(bitCount) {}; PPCASM_RELOC m_relocType; std::string m_expression; uint32 m_byteOffset; uint8 m_bitOffset; uint8 m_bitCount; bool m_isApplied{}; bool isApplied() { return m_isApplied; }; void setApplied() { m_isApplied = true; }; }; struct PPCAssemblerInOut { // in uint32 virtualAddress; bool forceNoAlignment{}; // if set, alignment will always be set to 1 (even for .align directive!) // out boost::container::small_vector<uint8, 16> outputData; std::vector<PPCAssemblerReloc> list_relocs; std::string errorMsg; uint32 alignmentRequirement{}; // alignment requirement, 0 if none uint32 alignmentPaddingSize{}; // number of bytes to fill with alignment padding before instruction uint32 virtualAddressAligned{}; // effective virtualAddress }; bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut* ctx); static uint32 ppcAssembler_generateMaskRLW(int MB, int ME) { uint32 maskMB = 0xFFFFFFFF >> MB; uint32 maskME = 0xFFFFFFFF << (31 - ME); uint32 mask = (MB <= ME) ? maskMB & maskME : maskMB | maskME; return mask; } ================================================ FILE: src/Cemu/Tools/DownloadManager/DownloadManager.cpp ================================================ #include "Cemu/Tools/DownloadManager/DownloadManager.h" #include "Cafe/Account/Account.h" #include "util/crypto/md5.h" #include "Cafe/TitleList/TitleId.h" #include "Common/FileStream.h" #include "Cemu/FileCache/FileCache.h" #include "Cemu/ncrypto/ncrypto.h" #include "config/ActiveSettings.h" #include "util/ThreadPool/ThreadPool.h" #include "util/helpers/enum_array.hpp" #include "Cafe/Filesystem/FST/FST.h" #include "Cafe/TitleList/TitleList.h" #include <cinttypes> #include <charconv> #include <curl/curl.h> #include <pugixml.hpp> #include "WindowSystem.h" #include "Cemu/napi/napi.h" #include "util/helpers/Serializer.h" FileCache* s_nupFileCache = nullptr; /* version list */ void DownloadManager::downloadTitleVersionList() { if (m_hasTitleVersionList) return; NAPI::AuthInfo authInfo = GetAuthInfo(false); auto versionListVersionResult = NAPI::TAG_GetVersionListVersion(authInfo); if (!versionListVersionResult.isValid) return; auto versionListResult = TAG_GetVersionList(authInfo, versionListVersionResult.fqdnURL, versionListVersionResult.version); if (!versionListResult.isValid) return; m_titleVersionList = versionListResult.titleVersionList; m_hasTitleVersionList = true; } // grab latest version from TAG version list. Returns false if titleId is not found in the list bool DownloadManager::getTitleLatestVersion(TitleId titleId, uint16& version) { auto titleVersionAvailability = m_titleVersionList.find(titleId); if (titleVersionAvailability == m_titleVersionList.end()) return false; version = titleVersionAvailability->second; return true; } /* helper method to generate list of owned base titles */ DownloadManager::TitleInstallState::TitleInstallState(DownloadManager* dlMgr, uint64 titleId) : titleId(titleId) { uint16 vers; if (CafeTitleList::HasTitle(titleId, vers)) { isInstalled = true; installedTitleVersion = vers; } else { isInstalled = false; installedTitleVersion = 0; } installedTicketVersion = 0; // for DLC we also retrieve the ticket version from the installed title.tik // todo - avoid file reads here due to stalling the UI thread if (TitleIdParser(titleId).GetType() == TitleIdParser::TITLE_TYPE::AOC) { const auto ticketPath = ActiveSettings::GetMlcPath(L"usr/title/{:08x}/{:08x}/code/title.tik", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); uint32 tikFileSize = 0; FileStream* fileStream = FileStream::openFile2(ticketPath); if (fileStream) { std::vector<uint8> tikData; fileStream->extract(tikData); NCrypto::ETicketParser eTicket; if (eTicket.parse(tikData.data(), tikData.size())) installedTicketVersion = eTicket.GetTicketVersion(); delete fileStream; } } } DownloadManager::TitleDownloadAvailableState::TitleDownloadAvailableState(DownloadManager* dlMgr, uint64 titleId) : TitleInstallState(dlMgr, titleId) { // get latest available version of this title (from the TAG version list) uint16 vers; if (dlMgr->getTitleLatestVersion(titleId, vers)) { isUpdateAvailable = true; availableTitleVersion = vers; } else { isUpdateAvailable = false; availableTitleVersion = 0; } }; std::set<DownloadManager::TitleInstallState> DownloadManager::getOwnedTitleList() { std::set<DownloadManager::TitleInstallState> ownedTitleList; // add installed games and DLC std::vector<TitleId> installedTitleIds = CafeTitleList::GetAllTitleIds(); for (auto& itr : installedTitleIds) { TitleIdParser titleIdParser(itr); auto titleType = titleIdParser.GetType(); if (titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE || titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO || titleType == TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE) { ownedTitleList.emplace(this, itr); } } // add ticket cache for (auto& itr : m_ticketCache) { TitleIdParser titleIdParser(itr.titleId); auto titleType = titleIdParser.GetType(); if (titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE || titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO || titleType == TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE || titleType == TitleIdParser::TITLE_TYPE::AOC) { ownedTitleList.emplace(this, itr.titleId); } } return ownedTitleList; } std::set<DownloadManager::TitleDownloadAvailableState> DownloadManager::getFullDownloadList() { std::set<DownloadManager::TitleDownloadAvailableState> fullList; // get list of owned titles std::set<DownloadManager::TitleInstallState> ownedTitleList = getOwnedTitleList(); // add each owned title, but also check for separate updates if available for (auto& itr : ownedTitleList) { TitleIdParser titleIdParser(itr.titleId); fullList.emplace(this, itr.titleId); if (titleIdParser.CanHaveSeparateUpdateTitleId()) { uint64 updateTitleId = titleIdParser.GetSeparateUpdateTitleId(); uint16 tempVers; if (getTitleLatestVersion(updateTitleId, tempVers)) fullList.emplace(this, updateTitleId); } } return fullList; } /* connect */ struct StoredTokenInfo : public SerializerHelper { bool serializeImpl(MemStreamWriter& streamWriter) { streamWriter.writeBE<uint8>(0); streamWriter.writeBE<std::string>(accountId); streamWriter.writeBE<std::string>(deviceToken); return true; } bool deserializeImpl(MemStreamReader& streamReader) { auto version = streamReader.readBE<uint8>(); if (version != 0) return false; accountId = streamReader.readBE<std::string>(); deviceToken = streamReader.readBE<std::string>(); return !streamReader.hasError(); } public: std::string accountId; std::string deviceToken; }; bool DownloadManager::_connect_refreshIASAccountIdAndDeviceToken() { NAPI::AuthInfo authInfo = GetAuthInfo(false); // query IAS/ECS account id and device token (if not cached) auto rChallenge = NAPI::IAS_GetChallenge(authInfo); if (rChallenge.apiError != NAPI_RESULT::SUCCESS) return false; auto rRegistrationInfo = NAPI::IAS_GetRegistrationInfo_QueryInfo(authInfo, rChallenge.challenge); if (rRegistrationInfo.apiError != NAPI_RESULT::SUCCESS) return false; m_iasToken.serviceAccountId = rRegistrationInfo.accountId; m_iasToken.deviceToken = rRegistrationInfo.deviceToken; // store to cache StoredTokenInfo storedTokenInfo; storedTokenInfo.accountId = rRegistrationInfo.accountId; storedTokenInfo.deviceToken = rRegistrationInfo.deviceToken; std::vector<uint8> serializedData; if (!storedTokenInfo.serialize(serializedData)) return false; s_nupFileCache->AddFileAsync({ fmt::format("{}/token_info", m_authInfo.cachefileName) }, serializedData.data(), serializedData.size()); return true; } bool DownloadManager::_connect_queryAccountStatusAndServiceURLs() { NAPI::AuthInfo authInfo = GetAuthInfo(true); NAPI::NAPI_ECSGetAccountStatus_Result accountStatusResult = NAPI::ECS_GetAccountStatus(authInfo); if (accountStatusResult.apiError != NAPI_RESULT::SUCCESS) { cemuLog_log(LogType::Force, "ECS - Failed to query account status (error: {0} {1})", accountStatusResult.apiError, accountStatusResult.serviceError); return false; } if (accountStatusResult.accountStatus == NAPI::NAPI_ECSGetAccountStatus_Result::AccountStatus::UNREGISTERED) { cemuLog_log(LogType::Force, fmt::format("ECS - Account is not registered")); return false; } return true; } // constructor for ticket cache entry DownloadManager::ETicketInfo::ETicketInfo(SOURCE source, uint64 ticketId, uint32 ticketVersion, std::vector<uint8>& eTicket) : source(source), ticketId(ticketId), ticketVersion(ticketVersion), eTicket(eTicket) { NCrypto::ETicketParser eTicketParser; if (!eTicketParser.parse(eTicket.data(), eTicket.size())) { titleId = (uint64)-1; return; } cemu_assert_debug(!eTicketParser.IsPersonalized()); // ticket should have been depersonalized already titleId = eTicketParser.GetTitleId(); ticketVersion = eTicketParser.GetTicketVersion(); cemu_assert_debug(ticketId == eTicketParser.GetTicketId()); cemu_assert_debug(ticketVersion == eTicketParser.GetTicketVersion()); } DownloadManager::ETicketInfo::ETicketInfo(SOURCE source, uint64 ticketId, uint32 ticketVersion, std::vector<uint8>& eTicket, std::vector<std::vector<uint8>>& eTicketCerts) : ETicketInfo(source, ticketId, ticketVersion, eTicket) { this->eTicketCerts = eTicketCerts; } void DownloadManager::ETicketInfo::GetTitleKey(NCrypto::AesKey& key) { NCrypto::ETicketParser eTicketParser; cemu_assert_debug(eTicketParser.parse(eTicket.data(), eTicket.size())); eTicketParser.GetTitleKey(key); } void DownloadManager::loadTicketCache() { m_ticketCache.clear(); cemu_assert_debug(m_ticketCache.empty()); std::vector<uint8> ticketCacheBlob; if (!s_nupFileCache->GetFile({ fmt::format("{}/eticket_cache", m_authInfo.cachefileName) }, ticketCacheBlob)) return; MemStreamReader memReader(ticketCacheBlob.data(), ticketCacheBlob.size()); uint8 version = memReader.readBE<uint8>(); if (version != 1) return; // unsupported version uint32 numTickets = memReader.readBE<uint32>(); for (uint32 i = 0; i < numTickets; i++) { if (memReader.hasError()) { m_ticketCache.clear(); return; } ETicketInfo::SOURCE source = (ETicketInfo::SOURCE)memReader.readBE<uint8>(); if (source != ETicketInfo::SOURCE::ECS_TICKET && source != ETicketInfo::SOURCE::PUBLIC_TICKET) { m_ticketCache.clear(); return; } uint64 ticketId = memReader.readBE<uint64>(); uint64 ticketVersion = memReader.readBE<uint32>(); std::vector<uint8> eTicketData = memReader.readPODVector<uint8>(); std::vector<std::vector<uint8>> eTicketCerts; uint8 certCount = memReader.readBE<uint8>(); for (uint32 c = 0; c < certCount; c++) eTicketCerts.emplace_back(memReader.readPODVector<uint8>()); if (memReader.hasError()) { m_ticketCache.clear(); return; } m_ticketCache.emplace_back(source, ticketId, ticketVersion, eTicketData, eTicketCerts); } } void DownloadManager::storeTicketCache() { MemStreamWriter memWriter(1024*32); memWriter.writeBE<uint8>(1); // version memWriter.writeBE<uint32>((uint32)m_ticketCache.size()); for (auto& eTicket : m_ticketCache) { memWriter.writeBE<uint8>((uint8)eTicket.source); memWriter.writeBE<uint64>(eTicket.ticketId); memWriter.writeBE<uint32>(eTicket.ticketVersion); memWriter.writePODVector(eTicket.eTicket); memWriter.writeBE<uint8>(eTicket.eTicketCerts.size()); for (auto& cert : eTicket.eTicketCerts) memWriter.writePODVector(cert); } auto serializedBlob = memWriter.getResult(); s_nupFileCache->AddFileAsync({ fmt::format("{}/eticket_cache", m_authInfo.cachefileName) }, serializedBlob.data(), serializedBlob.size()); } bool DownloadManager::syncAccountTickets() { NAPI::AuthInfo authInfo = GetAuthInfo(true); // query TIV list from server NAPI::NAPI_ECSAccountListETicketIds_Result resultTicketIds = NAPI::ECS_AccountListETicketIds(authInfo); if (!resultTicketIds.isValid()) return false; // download uncached tickets size_t count = resultTicketIds.tivs.size(); size_t index = 0; for (auto& tiv : resultTicketIds.tivs) { index++; std::string msg = _tr("Downloading account ticket"); msg.append(fmt::format(" {0}/{1}", index, count)); setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING); // skip if already cached ETicketInfo* cachedTicket = findTicketByTicketId(tiv.ticketId); if (cachedTicket) { if(cachedTicket->ticketVersion == tiv.ticketVersion) continue; // ticket version mismatch, redownload deleteTicketByTicketId(tiv.ticketId); } // get ECS ticket auto resultETickets = NAPI::ECS_AccountGetETickets(authInfo, tiv.ticketId); if (!resultETickets.isValid()) { cemuLog_log(LogType::Force, "SyncTicketCache: Account ETicket invalid"); continue; } // verify ticket integrity NCrypto::ETicketParser eTicketParser; if (!eTicketParser.parse(resultETickets.eTickets.data(), resultETickets.eTickets.size())) continue; uint64 titleId = eTicketParser.GetTitleId(); cemu_assert_debug(eTicketParser.GetTicketId() == tiv.ticketId); cemu_assert_debug(eTicketParser.GetTicketVersion() == tiv.ticketVersion); // depersonalize the ticket if (eTicketParser.IsPersonalized()) { NCrypto::ECCPrivKey privKey = NCrypto::ECCPrivKey::getDeviceCertPrivateKey(); if (!eTicketParser.Depersonalize(resultETickets.eTickets.data(), resultETickets.eTickets.size(), m_authInfo.deviceId, privKey)) { cemuLog_log(LogType::Force, "DownloadManager: Failed to depersonalize ticket"); continue; } // reparse cemu_assert(eTicketParser.parse(resultETickets.eTickets.data(), resultETickets.eTickets.size())); } else { cemuLog_log(LogType::Force, "DownloadManager: Unexpected result. ECS ticket not personalized"); continue; } ETicketInfo eTicket(ETicketInfo::SOURCE::ECS_TICKET, tiv.ticketId, tiv.ticketVersion, resultETickets.eTickets, resultETickets.certs); m_ticketCache.emplace_back(eTicket); } return true; } bool DownloadManager::syncSystemTitleTickets() { setStatusMessage(_tr("Downloading system tickets..."), DLMGR_STATUS_CODE::CONNECTING); NAPI::AuthInfo authInfo = GetAuthInfo(true); auto querySystemTitleTicket = [&](uint64 titleId) -> void { // check if cached already // todo - how do we know which version to query? System titles seem to use hashes? if (findFirstTicketByTitleId(titleId)) return; // request ticket auto resultCommonETicket = NAPI::NUS_GetSystemCommonETicket(authInfo, titleId); if (!resultCommonETicket.isValid()) return; // parse and validate ticket NCrypto::ETicketParser eTicketParser; if (!eTicketParser.parse(resultCommonETicket.eTicket.data(), resultCommonETicket.eTicket.size())) return; if (eTicketParser.GetTitleId() != titleId) return; if (eTicketParser.IsPersonalized()) return; // add to eTicket cache ETicketInfo eTicket(ETicketInfo::SOURCE::PUBLIC_TICKET, eTicketParser.GetTicketId(), eTicketParser.GetTicketVersion(), resultCommonETicket.eTicket, resultCommonETicket.certs); m_ticketCache.emplace_back(eTicket); }; if (m_authInfo.region == CafeConsoleRegion::EUR) { querySystemTitleTicket(0x000500301001420A); // eShop querySystemTitleTicket(0x000500301001520A); // Friend List querySystemTitleTicket(0x000500301001220A); // Internet browser } else if (m_authInfo.region == CafeConsoleRegion::USA) { querySystemTitleTicket(0x000500301001410A); // eShop querySystemTitleTicket(0x000500301001510A); // Friend List querySystemTitleTicket(0x000500301001210A); // Internet browser } else if (m_authInfo.region == CafeConsoleRegion::JPN) { querySystemTitleTicket(0x000500301001400A); // eShop querySystemTitleTicket(0x000500301001500A); // Friend List querySystemTitleTicket(0x000500301001200A); // Internet browser } return true; } // build list of updates for which either an installed game exists or the base title ticket is cached bool DownloadManager::syncUpdateTickets() { setStatusMessage(_tr("Retrieving update information..."), DLMGR_STATUS_CODE::CONNECTING); // download update version list downloadTitleVersionList(); if (!m_hasTitleVersionList) return false; std::set<DownloadManager::TitleDownloadAvailableState> downloadList = getFullDownloadList(); // count updates size_t numUpdates = 0; for (auto& itr : downloadList) { TitleIdParser titleIdParser(itr.titleId); if (titleIdParser.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) numUpdates++; } // get tickets for all the updates size_t updateIndex = 0; for (auto& itr : downloadList) { TitleIdParser titleIdParser(itr.titleId); if (titleIdParser.GetType() != TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) continue; std::string msg = _tr("Downloading ticket"); msg.append(fmt::format(" {0}/{1}", updateIndex, numUpdates)); updateIndex++; setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING); if (!itr.isUpdateAvailable) continue; // skip if already cached if (findTicketByTitleIdAndVersion(itr.titleId, itr.availableTitleVersion)) continue; auto cetkResult = NAPI::CCS_GetCETK(GetDownloadMgrNetworkService(), itr.titleId, itr.availableTitleVersion); if (!cetkResult.isValid) continue; NCrypto::ETicketParser ticketParser; if (!ticketParser.parse(cetkResult.cetkData.data(), cetkResult.cetkData.size())) continue; uint64 ticketId = ticketParser.GetTicketId(); uint64 ticketTitleId = ticketParser.GetTitleId(); uint16 ticketTitleVersion = ticketParser.GetTicketVersion(); uint16 ticketVersion = ticketTitleVersion; if (ticketTitleId != itr.titleId) continue; if (ticketTitleVersion != itr.availableTitleVersion) { cemuLog_log(LogType::Force, "Ticket for title update has a mismatching version"); continue; } // add to eTicket cache ETicketInfo eTicket(ETicketInfo::SOURCE::PUBLIC_TICKET, ticketId, ticketVersion, cetkResult.cetkData); m_ticketCache.emplace_back(eTicket); } return true; } // synchronize ticket cache with server and request uncached ticket data bool DownloadManager::syncTicketCache() { if (!syncAccountTickets()) return false; syncSystemTitleTickets(); syncUpdateTickets(); storeTicketCache(); // make sure IDBE's are loaded into memory for all eTickets (potential downloads) // this will only download them if they aren't already in the on-disk cache size_t count = m_ticketCache.size(); size_t index = 0; for (auto& ticketInfo : m_ticketCache) { index++; std::string msg = _tr("Downloading meta data"); msg.append(fmt::format(" {0}/{1}", index, count)); setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING); prepareIDBE(ticketInfo.titleId); } setStatusMessage(_tr("Connected. Right click entries in the list to start downloading"), DLMGR_STATUS_CODE::CONNECTED); return true; } void DownloadManager::searchForIncompleteDownloads() { const fs::path packagePath = ActiveSettings::GetMlcPath("usr/packages/title/"); if (!fs::exists(packagePath)) return; for (auto& p : fs::directory_iterator(packagePath)) { uint64 titleId; uint32 version; std::string name = p.path().filename().generic_string(); if( sscanf(name.c_str(), "cemu_%" PRIx64 "_v%u", &titleId, &version) != 2) continue; std::unique_lock<std::recursive_mutex> _l(m_mutex); for (auto& itr : m_ticketCache) m_unfinishedDownloads.emplace_back(titleId, version); } } void DownloadManager::reportAvailableTitles() { if (!m_cbAddDownloadableTitle) return; std::set<DownloadManager::TitleDownloadAvailableState> downloadList = getFullDownloadList(); for (auto& itr : downloadList) { TitleIdParser titleIdParser(itr.titleId); TitleIdParser::TITLE_TYPE titleType = titleIdParser.GetType(); if (titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE || titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO || titleType == TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE || titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE || titleType == TitleIdParser::TITLE_TYPE::AOC) { // show entries only if we were able to retrieve the ticket if (itr.isInstalled) { if (!findFirstTicketByTitleId(itr.titleId)) continue; } DlMgrTitleReport::STATUS status = DlMgrTitleReport::STATUS::INSTALLABLE; bool aocHasUpdate = false; if (itr.isInstalled && titleType == TitleIdParser::TITLE_TYPE::AOC) { ETicketInfo* eTicketInfo = findFirstTicketByTitleId(itr.titleId); if (eTicketInfo && eTicketInfo->ticketVersion > itr.installedTicketVersion) aocHasUpdate = true; } if (itr.isInstalled && itr.installedTitleVersion >= itr.availableTitleVersion && aocHasUpdate == false) { status = DlMgrTitleReport::STATUS::INSTALLED; } if (status == DlMgrTitleReport::STATUS::INSTALLABLE) { if (hasPartialDownload(itr.titleId, itr.availableTitleVersion)) status = DlMgrTitleReport::STATUS::INSTALLABLE_UNFINISHED; if (aocHasUpdate) status = DlMgrTitleReport::STATUS::INSTALLABLE_UPDATE; } DlMgrTitleReport titleInfo(status, itr.titleId, itr.availableTitleVersion, getNameFromCachedIDBE(itr.titleId), 0, 0, false); m_cbAddDownloadableTitle(titleInfo); } else { cemu_assert_debug(false); // unsupported title type } } } /* connection logic */ void DownloadManager::_handle_connect() { m_connectState.store(CONNECT_STATE::PROCESSING); // reset login state m_iasToken.serviceAccountId.clear(); m_iasToken.deviceToken.clear(); setStatusMessage(_tr("Logging in..."), DLMGR_STATUS_CODE::CONNECTING); // retrieve ECS AccountId + DeviceToken from cache if (s_nupFileCache) { std::vector<uint8> serializationBlob; if (s_nupFileCache->GetFile({ fmt::format("{}/token_info", m_authInfo.cachefileName) }, serializationBlob)) { StoredTokenInfo storedTokenInfo; if (storedTokenInfo.deserialize(serializationBlob)) { m_iasToken.serviceAccountId = storedTokenInfo.accountId; m_iasToken.deviceToken = storedTokenInfo.deviceToken; } } } // .. or request AccountId and DeviceToken if not cached if (m_iasToken.serviceAccountId.empty() || m_iasToken.deviceToken.empty()) { if (!_connect_refreshIASAccountIdAndDeviceToken()) { cemuLog_log(LogType::Force, "Failed to request IAS token"); cemu_assert_debug(false); m_connectState.store(CONNECT_STATE::FAILED); setStatusMessage(_tr("Login failed. Outdated or incomplete online files?"), DLMGR_STATUS_CODE::FAILED); return; } } // get EC account status and service urls if (!_connect_queryAccountStatusAndServiceURLs()) { m_connectState.store(CONNECT_STATE::FAILED); setStatusMessage(_tr("Failed to query account status"), DLMGR_STATUS_CODE::FAILED); return; } // load ticket cache and sync setStatusMessage(_tr("Updating ticket cache"), DLMGR_STATUS_CODE::CONNECTING); loadTicketCache(); if (!syncTicketCache()) { m_connectState.store(CONNECT_STATE::FAILED); setStatusMessage(_tr("Failed to request tickets"), DLMGR_STATUS_CODE::FAILED); return; } searchForIncompleteDownloads(); // notify about all available downloadable titles reportAvailableTitles(); // print ticket info m_connectState.store(CONNECT_STATE::COMPLETE); } void DownloadManager::connect( std::string_view nnidAccountName, const std::array<uint8, 32>& passwordHash, CafeConsoleRegion region, std::string_view country, uint32 deviceId, std::string_view serial, std::string_view deviceCertBase64) { runManager(); m_authInfo.nnidAccountName = nnidAccountName; m_authInfo.passwordHash = passwordHash; m_authInfo.cachefileName = nnidAccountName.empty() ? "DefaultName" : nnidAccountName; m_authInfo.region = region; m_authInfo.country = country; m_authInfo.deviceCertBase64 = deviceCertBase64; m_authInfo.deviceId = deviceId; m_authInfo.serial = serial; m_connectState.store(CONNECT_STATE::REQUESTED); notifyManager(); queueManagerJob([this]() {_handle_connect(); }); } bool DownloadManager::IsConnected() const { return m_connectState.load() != CONNECT_STATE::UNINITIALIZED; } NetworkService DownloadManager::GetDownloadMgrNetworkService() { return NetworkService::Nintendo; } NAPI::AuthInfo DownloadManager::GetAuthInfo(bool withIasToken) { NAPI::AuthInfo authInfo; authInfo.serviceOverwrite = GetDownloadMgrNetworkService(); authInfo.accountId = m_authInfo.nnidAccountName; authInfo.passwordHash = m_authInfo.passwordHash; authInfo.deviceId = m_authInfo.deviceId; authInfo.serial = m_authInfo.serial; authInfo.country = m_authInfo.country; authInfo.region = m_authInfo.region; authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; if(withIasToken) { cemu_assert_debug(!m_iasToken.serviceAccountId.empty()); authInfo.IASToken.accountId = m_iasToken.serviceAccountId; authInfo.IASToken.deviceToken = m_iasToken.deviceToken; } return authInfo; } /* package / downloading */ // start/resume/retry download void DownloadManager::initiateDownload(uint64 titleId, uint16 version) { std::unique_lock<std::recursive_mutex> _l(m_mutex); Package* package = getPackage(titleId, version); if (package) { // remove pause state package->state.isPaused = false; // already exists, erase error state if (package->state.hasError) { package->state.hasError = false; reportPackageStatus(package); } checkPackagesState(); return; } // find matching eTicket and get key std::vector<uint8>* ticketData = nullptr; for (auto& ticket : m_ticketCache) { if (ticket.titleId == titleId )//&& ticket.version == version) { ticketData = &ticket.eTicket; break; } } if (!ticketData) return; package = new Package(titleId, version, *ticketData); m_packageList.emplace_back(package); reportPackageStatus(package); checkPackagesState(); // will start downloading this package if none already active } void DownloadManager::pauseDownload(uint64 titleId, uint16 version) { std::unique_lock<std::recursive_mutex> _l(m_mutex); Package* package = getPackage(titleId, version); if (!package || !package->state.isActive) return; package->state.isPaused = true; package->state.isActive = false; reportPackageStatus(package); checkPackagesState(); } DownloadManager::Package* DownloadManager::getPackage(uint64 titleId, uint16 version) { std::unique_lock<std::recursive_mutex> _l(m_mutex); auto itr = std::find_if(m_packageList.begin(), m_packageList.end(), [titleId, version](const Package* v) { return v->titleId == titleId && v->version == version; }); if (itr == m_packageList.end()) return nullptr; return *itr; } fs::path DownloadManager::getPackageDownloadPath(Package* package) { return ActiveSettings::GetMlcPath(fmt::format("usr/packages/title/cemu_{:016x}_v{}/", package->titleId, package->version)); } fs::path DownloadManager::getPackageInstallPath(Package* package) { TitleIdParser tParser(package->titleId); const char* titleBasePath = "usr/title/"; if(tParser.IsSystemTitle()) titleBasePath = "sys/title/"; return ActiveSettings::GetMlcPath(fmt::format("{}{:08x}/{:08x}/", titleBasePath, (uint32)(package->titleId>>32), (uint32)package->titleId)); } // called when a package becomes active (queued to downloading) or when any of it's async download operations finishes // initiates new async download, decrypt or install tasks void DownloadManager::updatePackage(Package* package) { std::unique_lock<std::recursive_mutex> _l(m_mutex); if (!package->state.isActive || package->state.isPaused || package->state.hasError) return; // do we have the TMD downloaded yet? if (!package->state.tmd) { if (!package->state.isDownloadingTMD) { package->state.isDownloadingTMD = true; ThreadPool::FireAndForget(&DownloadManager::asyncPackageDownloadTMD, this, package); } return; } using ContentState = Package::ContentFile::STATE; // count state totals struct ContentCountInfo { uint32 total{}; uint32 processing{}; }; enum_array<Package::ContentFile::STATE, ContentCountInfo> contentCountTable; for (auto& itr : package->state.contentFiles) { auto state = itr.second.currentState; contentCountTable[state].total++; if(itr.second.isBeingProcessed) contentCountTable[state].processing++; } // utility method to grab next inactive content entry with a specific state auto getFirstInactiveContentByState = [&](ContentState state) -> Package::ContentFile* { auto itr = std::find_if(package->state.contentFiles.begin(), package->state.contentFiles.end(), [state](const auto& contentFile) {return contentFile.second.currentState == state && !contentFile.second.isBeingProcessed; }); if (itr == package->state.contentFiles.end()) return nullptr; return &(itr->second); }; /************* content check phase *************/ if (package->state.currentState == Package::STATE::INITIAL) { package->state.currentState = Package::STATE::CHECKING; package->state.progress = 0; package->state.progressMax = (uint32)package->state.contentFiles.size(); reportPackageStatus(package); } while (contentCountTable[ContentState::CHECK].total > 0 && contentCountTable[ContentState::CHECK].processing < 2) { Package::ContentFile* contentPtr = getFirstInactiveContentByState(ContentState::CHECK); if (!contentPtr) break; contentPtr->isBeingProcessed = true; ThreadPool::FireAndForget(&DownloadManager::asyncPackageVerifyFile, this, package, contentPtr->index, true); contentCountTable[ContentState::CHECK].processing++; } if (contentCountTable[ContentState::CHECK].total > 0) return; // dont proceed to next phase until done /************* content download phase *************/ while (contentCountTable[ContentState::DOWNLOAD].total > 0 && contentCountTable[ContentState::DOWNLOAD].processing < 2) { if (package->state.currentState == Package::STATE::CHECKING || package->state.currentState == Package::STATE::VERIFYING) { package->state.currentState = Package::STATE::DOWNLOADING; package->state.progress = 0; package->state.progressMax = 0; reportPackageStatus(package); } // download next file if there aren't 2 active downloads already Package::ContentFile* contentPtr = getFirstInactiveContentByState(ContentState::DOWNLOAD); if (!contentPtr) break; contentPtr->isBeingProcessed = true; ThreadPool::FireAndForget(&DownloadManager::asyncPackageDownloadContentFile, this, package, contentPtr->index); contentCountTable[ContentState::DOWNLOAD].processing++; } if (contentCountTable[ContentState::DOWNLOAD].total > 0) return; /************* content verification phase *************/ if (package->state.currentState != Package::STATE::VERIFYING) { package->state.currentState = Package::STATE::VERIFYING; package->state.progress = 0; package->state.progressMax = (uint32)package->state.contentFiles.size(); reportPackageStatus(package); } while (contentCountTable[ContentState::VERIFY].total > 0 && contentCountTable[ContentState::VERIFY].processing < 2) { Package::ContentFile* contentPtr = getFirstInactiveContentByState(ContentState::VERIFY); if (!contentPtr) break; contentPtr->isBeingProcessed = true; ThreadPool::FireAndForget(&DownloadManager::asyncPackageVerifyFile, this, package, contentPtr->index, true); contentCountTable[ContentState::VERIFY].processing++; } if (contentCountTable[ContentState::VERIFY].total > 0) return; /************* installing phase *************/ if (!package->state.isInstalling) { if (package->state.currentState != Package::STATE::INSTALLING) { package->state.currentState = Package::STATE::INSTALLING; package->state.progress = 0; package->state.progressMax = 0; reportPackageStatus(package); } package->state.isInstalling = true; ThreadPool::FireAndForget(&DownloadManager::asyncPackageInstall, this, package); } } // checks for new packages to download if none are currently active void DownloadManager::checkPackagesState() { std::unique_lock<std::recursive_mutex> _l(m_mutex); bool hasActive = false; hasActive = std::find_if(m_packageList.begin(), m_packageList.end(), [](const Package* p) { return p->state.isActive; }) != m_packageList.end(); if (!hasActive) { // start new download auto it = std::find_if(m_packageList.begin(), m_packageList.end(), [](const Package* p) { return !p->state.isActive && !p->state.hasError && !p->state.isPaused && p->state.currentState != Package::STATE::INSTALLED; }); if (it != m_packageList.end()) { Package* startedPackage = *it; startedPackage->state.isActive = true; updatePackage(startedPackage); reportPackageStatus(startedPackage); } } } void DownloadManager::setPackageError(Package* package, std::string errorMsg) { package->state.isActive = false; if (package->state.hasError) return; // dont overwrite already set error message package->state.hasError = true; package->state.errorMsg = std::move(errorMsg); reportPackageStatus(package); } void DownloadManager::reportPackageStatus(Package* package) { if (!m_cbAddDownloadableTitle) return; m_mutex.lock(); DlMgrTitleReport::STATUS status = DlMgrTitleReport::STATUS::INITIALIZING; if (package->state.hasError) { status = DlMgrTitleReport::STATUS::HAS_ERROR; } else if (package->state.currentState == Package::STATE::INSTALLED) status = DlMgrTitleReport::STATUS::INSTALLED; else if (!package->state.isActive) { if (package->state.tmd) status = DlMgrTitleReport::STATUS::PAUSED; else status = DlMgrTitleReport::STATUS::QUEUED; } else if (package->state.tmd) { if (package->state.currentState == Package::STATE::CHECKING) status = DlMgrTitleReport::STATUS::CHECKING; else if (package->state.currentState == Package::STATE::VERIFYING) status = DlMgrTitleReport::STATUS::VERIFYING; else if (package->state.currentState == Package::STATE::INSTALLING) status = DlMgrTitleReport::STATUS::INSTALLING; else if (package->state.currentState == Package::STATE::INSTALLED) status = DlMgrTitleReport::STATUS::INSTALLED; else status = DlMgrTitleReport::STATUS::DOWNLOADING; } DlMgrTitleReport reportInfo(status, package->titleId, package->version, getNameFromCachedIDBE(package->titleId), package->state.progress, package->state.progressMax, package->state.isPaused); if (package->state.hasError) reportInfo.errorMsg = package->state.errorMsg; m_mutex.unlock(); m_cbAddDownloadableTitle(reportInfo); } void DownloadManager::reportPackageProgress(Package* package, uint32 currentProgress) { // todo - cooldown timer to avoid spamming too many events package->state.progress = currentProgress; reportPackageStatus(package); } void DownloadManager::asyncPackageDownloadTMD(Package* package) { NAPI::AuthInfo authInfo = GetAuthInfo(true); TitleIdParser titleIdParser(package->titleId); NAPI::NAPI_CCSGetTMD_Result tmdResult; if (titleIdParser.GetType() == TitleIdParser::TITLE_TYPE::AOC) { // for AOC we always download the latest TMD // is there a way to get the version beforehand? It doesn't seem to be stored in either the .tik file or the update version list tmdResult = CCS_GetTMD(authInfo, package->titleId); } else { tmdResult = NAPI::CCS_GetTMD(authInfo, package->titleId, package->version); } if (!tmdResult.isValid) { // failed, try to get latest TMD instead tmdResult = CCS_GetTMD(authInfo, package->titleId); } std::unique_lock<std::recursive_mutex> _l(m_mutex); if (!tmdResult.isValid) { setPackageError(package, _tr("TMD download failed")); package->state.isDownloadingTMD = false; return; } _l.unlock(); // parse NCrypto::TMDParser tmdParser; if (!tmdParser.parse(tmdResult.tmdData.data(), tmdResult.tmdData.size())) { setPackageError(package, _tr("Invalid TMD")); package->state.isDownloadingTMD = false; return; } // set TMD _l.lock(); package->state.tmdData = tmdResult.tmdData; package->state.tmd = new NCrypto::TMDParser(tmdParser); package->state.isDownloadingTMD = false; // prepare list of content files package->state.contentFiles.clear(); for (auto& itr : package->state.tmd->GetContentList()) { if (package->state.contentFiles.find(itr.index) != package->state.contentFiles.end()) { cemu_assert_debug(false); continue; } package->state.contentFiles.emplace(std::piecewise_construct, std::forward_as_tuple(itr.index), std::forward_as_tuple(itr.index, itr.contentId, itr.size, itr.contentFlags, itr.hash32)); } // create folder auto dir = getPackageDownloadPath(package); fs::create_directories(dir); // continue with downloading reportPackageStatus(package); updatePackage(package); } void DownloadManager::calcPackageDownloadProgress(Package* package) { if (package->state.currentState == Package::STATE::DOWNLOADING) { uint64 totalSize = 0; uint64 totalDownloaded = 0; for (auto& itr : package->state.contentFiles) { totalSize += itr.second.paddedSize; if (itr.second.currentState == Package::ContentFile::STATE::INSTALL || itr.second.currentState == Package::ContentFile::STATE::VERIFY) totalDownloaded += itr.second.paddedSize; // already downloaded, add full size else totalDownloaded += itr.second.amountDownloaded; } uint32 pct10 = (uint32)(totalDownloaded * 1000ull / totalSize); if (package->state.progress != pct10) { package->state.progress = pct10; package->state.progressMax = 1000; reportPackageProgress(package, package->state.progress); } } } void DownloadManager::asyncPackageDownloadContentFile(Package* package, uint16 index) { // get titleId, contentId and file path std::unique_lock<std::recursive_mutex> _l(m_mutex); uint64 titleId = package->titleId; auto contentFileItr = package->state.contentFiles.find(index); cemu_assert(contentFileItr != package->state.contentFiles.end()); uint32 contentId = contentFileItr->second.contentId; contentFileItr->second.amountDownloaded = 0; auto packageDownloadPath = getPackageDownloadPath(package); _l.unlock(); // download h3 hash file (.h3) if flag 0x0002 is set (-> we are using the TMD to verify the hash of the content files) //auto h3Result = NAPI::CCS_GetContentH3File(titleId, contentId); //auto h3Result = NAPI::CCS_GetContentH3File(titleId, contentId); //if (!h3Result.isValid) //{ // setPackageError(package, "Download failed (h3)"); // return; //} //filePathStr = (packageDownloadPath / fmt::format("{:08x}.h3", index)).generic_u8string(); //auto h3File = FileStream::createFile(filePathStr); //if (!h3File) //{ // setPackageError(package, "Cannot create file"); // return; //} //if (h3File->writeData(h3Result.tmdData.data(), h3Result.tmdData.size()) != h3Result.tmdData.size()) //{ // setPackageError(package, "Cannot write file (h3). Disk full?"); // return; //} //delete h3File; // streamed download of content file (.app) // prepare callback parameter struct struct CallbackInfo { DownloadManager* downloadMgr; Package* package; Package::ContentFile* contentFile; std::vector<uint8> receiveBuffer; FileStream* fileOutput; static bool writeCallback(void* userData, const void* ptr, size_t len, bool isLast) { CallbackInfo* callbackInfo = (CallbackInfo*)userData; // append bytes to buffer callbackInfo->receiveBuffer.insert(callbackInfo->receiveBuffer.end(), (const uint8*)ptr, (const uint8*)ptr + len); // flush cache to file if it exceeds 128KiB or if this is the final callback if (callbackInfo->receiveBuffer.size() >= (128 * 1024) || (isLast && !callbackInfo->receiveBuffer.empty())) { size_t bytesWritten = callbackInfo->receiveBuffer.size(); if (callbackInfo->fileOutput->writeData(callbackInfo->receiveBuffer.data(), callbackInfo->receiveBuffer.size()) != (uint32)callbackInfo->receiveBuffer.size()) { callbackInfo->downloadMgr->setPackageError(callbackInfo->package, _tr("Cannot write file. Disk full?")); return false; } callbackInfo->receiveBuffer.clear(); if (bytesWritten > 0) { callbackInfo->downloadMgr->m_mutex.lock(); callbackInfo->contentFile->amountDownloaded += bytesWritten; callbackInfo->downloadMgr->calcPackageDownloadProgress(callbackInfo->package); callbackInfo->downloadMgr->m_mutex.unlock(); } } return true; } }callbackInfoData{}; callbackInfoData.downloadMgr = this; callbackInfoData.package = package; callbackInfoData.contentFile = &contentFileItr->second; callbackInfoData.fileOutput = FileStream::createFile2(packageDownloadPath / fmt::format("{:08x}.app", contentId)); if (!callbackInfoData.fileOutput) { setPackageError(package, _tr("Cannot create file")); return; } if (!NAPI::CCS_GetContentFile(GetDownloadMgrNetworkService(), titleId, contentId, CallbackInfo::writeCallback, &callbackInfoData)) { setPackageError(package, _tr("Download failed")); delete callbackInfoData.fileOutput; return; } delete callbackInfoData.fileOutput; callbackInfoData.fileOutput = nullptr; // mark file as downloaded by requesting verify state _l.lock(); contentFileItr->second.finishProcessing(Package::ContentFile::STATE::VERIFY); _l.unlock(); // start next task updatePackage(package); } void DownloadManager::asyncPackageVerifyFile(Package* package, uint16 index, bool isCheckState) { uint8 tmdContentHash[32]; // get titleId, contentId and file path std::unique_lock<std::recursive_mutex> _l(m_mutex); uint64 titleId = package->titleId; auto contentFileItr = package->state.contentFiles.find(index); cemu_assert(contentFileItr != package->state.contentFiles.end()); uint16 contentIndex = contentFileItr->second.index; uint32 contentId = contentFileItr->second.contentId; uint64 contentSize = contentFileItr->second.size; uint64 contentPaddedSize = contentFileItr->second.paddedSize; auto contentFlags = contentFileItr->second.contentFlags; std::memcpy(tmdContentHash, contentFileItr->second.contentHash, 32); auto packageDownloadPath = getPackageDownloadPath(package); _l.unlock(); NCrypto::AesKey ecsTicketKey = package->ticketKey; Package::ContentFile::STATE newStateOnError = Package::ContentFile::STATE::DOWNLOAD; Package::ContentFile::STATE newStateOnSuccess = Package::ContentFile::STATE::INSTALL; // verify file std::unique_ptr<FileStream> fileStream(FileStream::openFile2(packageDownloadPath / fmt::format("{:08x}.app", contentId))); if (!fileStream) { _l.lock(); contentFileItr->second.finishProcessing(newStateOnError); if (!isCheckState) setPackageError(package, "Missing file during verification"); _l.unlock(); updatePackage(package); return; } bool isSHA1 = HAS_FLAG(contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_SHA1); bool isValid = false; if (HAS_FLAG(contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_HASHED_CONTENT)) isValid = FSTVerifier::VerifyHashedContentFile(fileStream.get(), &ecsTicketKey, contentIndex, contentSize, contentPaddedSize, isSHA1, tmdContentHash); else isValid = FSTVerifier::VerifyContentFile(fileStream.get(), &ecsTicketKey, contentIndex, contentSize, contentPaddedSize, isSHA1, tmdContentHash); if (!isValid) { _l.lock(); contentFileItr->second.finishProcessing(newStateOnError); if (!isCheckState) setPackageError(package, "Verification failed"); _l.unlock(); updatePackage(package); return; } // file verified successfully _l.lock(); contentFileItr->second.finishProcessing(newStateOnSuccess); reportPackageProgress(package, package->state.progress + 1); _l.unlock(); // start next task updatePackage(package); } bool DownloadManager::asyncPackageInstallRecursiveExtractFiles(Package* package, FSTVolume* fstVolume, const std::string& sourcePath, const fs::path& destinationPath) { std::error_code ec; fs::create_directories(destinationPath, ec); // we dont check the error because it is OS/implementation specific (on Windows this returns ec=0 with false when directory already exists) cemu_assert_debug(sourcePath.back() == '/'); FSTDirectoryIterator dirItr; if (!fstVolume->OpenDirectoryIterator(sourcePath, dirItr)) { std::unique_lock<std::recursive_mutex> _l(m_mutex); setPackageError(package, "Internal error"); return false; } if (fstVolume->HasLinkFlag(dirItr.GetDirHandle())) { cemu_assert_suspicious(); return true; } FSTFileHandle itr; while (fstVolume->Next(dirItr, itr)) { std::string_view nodeName = fstVolume->GetName(itr); if(nodeName.empty() || boost::equals(nodeName, ".") || boost::equals(nodeName, "..") || boost::contains(nodeName, "/") || boost::contains(nodeName, "\\")) continue; std::string sourceFilePath = sourcePath; sourceFilePath.append(nodeName); fs::path nodeDestinationPath = destinationPath; nodeDestinationPath.append(nodeName.data(), nodeName.data() + nodeName.size()); if (fstVolume->IsDirectory(itr)) { if (fstVolume->HasLinkFlag(itr)) { // delete link directories fs::remove_all(nodeDestinationPath, ec); } else { // iterate sourceFilePath.push_back('/'); asyncPackageInstallRecursiveExtractFiles(package, fstVolume, sourceFilePath, nodeDestinationPath); } } else if (fstVolume->IsFile(itr)) { if (fstVolume->HasLinkFlag(itr)) { // delete link files fs::remove_all(nodeDestinationPath, ec); } else { // extract std::vector<uint8> buffer(64 * 1024); FileStream* fileOut = FileStream::createFile2(nodeDestinationPath); if (!fileOut) { setPackageError(package, "Failed to create file"); return false; } uint32 fileSize = fstVolume->GetFileSize(itr); uint32 currentPos = 0; while (currentPos < fileSize) { uint32 numBytesToTransfer = std::min(fileSize - currentPos, (uint32)buffer.size()); if (fstVolume->ReadFile(itr, currentPos, numBytesToTransfer, buffer.data()) != numBytesToTransfer) { setPackageError(package, "Failed to extract data"); return false; } if (fileOut->writeData(buffer.data(), numBytesToTransfer) != numBytesToTransfer) { setPackageError(package, "Failed to write to file. Disk full?"); return false; } currentPos += numBytesToTransfer; } delete fileOut; } // advance progress std::unique_lock<std::recursive_mutex> _l(m_mutex); reportPackageProgress(package, package->state.progress + 1); } else { cemu_assert_debug(false); // unknown node type } } return true; } void DownloadManager::asyncPackageInstall(Package* package) { std::unique_lock<std::recursive_mutex> _l(m_mutex); auto packageDownloadPath = getPackageDownloadPath(package); fs::path installPath = getPackageInstallPath(package); _l.unlock(); // store title.tmd std::unique_ptr<FileStream> fileStream(FileStream::createFile2(packageDownloadPath / "title.tmd")); if (!fileStream || fileStream->writeData(package->state.tmdData.data(), package->state.tmdData.size()) != package->state.tmdData.size()) { _l.lock(); setPackageError(package, "Failed to write title.tmd"); package->state.isInstalling = false; return; } fileStream.reset(); // store title.tik fileStream.reset(FileStream::createFile2(packageDownloadPath / "title.tik")); if (!fileStream || fileStream->writeData(package->eTicketData.data(), package->eTicketData.size()) != package->eTicketData.size()) { _l.lock(); setPackageError(package, "Failed to write title.tik"); package->state.isInstalling = false; return; } fileStream.reset(); // for AOC titles we also 'install' the ticket by copying it next to the code/content/meta folders // on an actual Wii U the ticket gets installed to SLC but currently we only emulate MLC if (TitleIdParser(package->titleId).GetType() == TitleIdParser::TITLE_TYPE::AOC) { std::error_code ec; fs::create_directories(installPath, ec); fs::create_directories(installPath / "code/", ec); fileStream.reset(FileStream::createFile2(installPath / "code/title.tik")); if (!fileStream || fileStream->writeData(package->eTicketData.data(), package->eTicketData.size()) != package->eTicketData.size()) { _l.lock(); setPackageError(package, "Failed to install title.tik"); package->state.isInstalling = false; return; } fileStream.reset(); } // open app FST FSTVolume* fst = FSTVolume::OpenFromContentFolder(packageDownloadPath); if (!fst) { _l.lock(); setPackageError(package, "Failed to extract content"); package->state.isInstalling = false; return; } // count number of files for progress tracking package->state.progressMax = fst->GetFileCount(); package->state.progress = 0; // extract code/content/meta folders into installation directory if (!asyncPackageInstallRecursiveExtractFiles(package, fst, "code/", installPath / "code")) { _l.lock(); setPackageError(package, "Failed to extract code folder"); package->state.isInstalling = false; return; } if (!asyncPackageInstallRecursiveExtractFiles(package, fst, "content/", installPath / "content")) { _l.lock(); setPackageError(package, "Failed to extract content folder"); package->state.isInstalling = false; return; } if (!asyncPackageInstallRecursiveExtractFiles(package, fst, "meta/", installPath / "meta")) { _l.lock(); setPackageError(package, "Failed to extract meta folder"); package->state.isInstalling = false; return; } delete fst; // delete package folder std::error_code ec; fs::remove_all(packageDownloadPath, ec); // mark as complete _l.lock(); package->state.currentState = Package::STATE::INSTALLED; package->state.isInstalling = false; package->state.isActive = false; CafeTitleList::AddTitleFromPath(installPath); reportPackageStatus(package); checkPackagesState(); // lastly request game list to be refreshed WindowSystem::RefreshGameList(); return; } /* IDBE cache */ std::unordered_map<uint64, NAPI::IDBEIconDataV0*> s_idbeCache; std::mutex s_idbeCacheMutex; // load IDBE from disk or server into memory cache // stalls while reading disk/downloading void DownloadManager::prepareIDBE(uint64 titleId) { auto hasInCache = [](uint64 titleId) -> bool { s_idbeCacheMutex.lock(); bool hasCached = s_idbeCache.find(titleId) != s_idbeCache.end(); s_idbeCacheMutex.unlock(); return hasCached; }; auto addToCache = [](uint64 titleId, NAPI::IDBEIconDataV0* iconData) -> void { NAPI::IDBEIconDataV0* iconInstance = new NAPI::IDBEIconDataV0(); *iconInstance = *iconData; s_idbeCacheMutex.lock(); if (!s_idbeCache.try_emplace(titleId, iconInstance).second) delete iconInstance; s_idbeCacheMutex.unlock(); }; if (hasInCache(titleId)) return; // try to load from disk cache std::vector<uint8> idbeFile; if (s_nupFileCache->GetFile({ fmt::format("idbe/{0:016x}", titleId) }, idbeFile) && idbeFile.size() == sizeof(NAPI::IDBEIconDataV0)) return addToCache(titleId, (NAPI::IDBEIconDataV0*)(idbeFile.data())); // not cached, query from server std::optional<NAPI::IDBEIconDataV0> iconData = NAPI::IDBE_Request(GetDownloadMgrNetworkService(), titleId); if (!iconData) return; s_nupFileCache->AddFileAsync({ fmt::format("idbe/{0:016x}", titleId) }, (uint8*)&(*iconData), sizeof(NAPI::IDBEIconDataV0)); addToCache(titleId, &*iconData); } std::string DownloadManager::getNameFromCachedIDBE(uint64 titleId) { // workaround for Friend List not having an IDBE if (titleId == 0x000500301001500A || titleId == 0x000500301001510A || titleId == 0x000500301001520A) { return "Friend List"; } std::unique_lock<std::mutex> _l(s_idbeCacheMutex); NAPI::IDBEIconDataV0* iconData = getIDBE(titleId); if (iconData) return iconData->GetLanguageStrings(GetConfig().console_language).GetGameNameUTF8(); return fmt::format("Title {0:016x}", titleId); } // returns IDBE by titleId (or nullptr if not in cache) // assumes s_idbeCacheMutex is held by caller NAPI::IDBEIconDataV0* DownloadManager::getIDBE(uint64 titleId) { auto it = s_idbeCache.find(titleId); if (it == s_idbeCache.end()) return nullptr; return it->second; } /* package manager / downloading */ void DownloadManager::threadFunc() { while (true) { auto cb = m_jobQueue.pop(); cb(); } } // init download manager worker thread and cache void DownloadManager::runManager() { bool prevBool = false; if (!m_threadLaunched.compare_exchange_weak(prevBool, true)) return; // open cache auto cacheFilePath = ActiveSettings::GetMlcPath("usr/save/system/nim/nup/"); fs::create_directories(cacheFilePath); cacheFilePath /= "cemu_cache.dat"; s_nupFileCache = FileCache::Open(cacheFilePath, true); // launch worker thread std::thread t(&DownloadManager::threadFunc, this); t.detach(); } // let manager known there is a new event that needs processing void DownloadManager::notifyManager() { m_queuedEvents.increment(); } void DownloadManager::queueManagerJob(const std::function<void()>& callback) { m_jobQueue.push(callback); } ================================================ FILE: src/Cemu/Tools/DownloadManager/DownloadManager.h ================================================ #pragma once #include "util/helpers/Semaphore.h" #include "Cemu/ncrypto/ncrypto.h" #include "Cafe/TitleList/TitleId.h" #include "util/helpers/ConcurrentQueue.h" #include "config/NetworkSettings.h" // forward declarations namespace NAPI { struct IDBEIconDataV0; struct AuthInfo; } namespace NCrypto { class TMDParser; } struct DlMgrTitleReport { enum class STATUS { INSTALLABLE, // not a package INSTALLABLE_UNFINISHED, // same as INSTALLABLE, but a previous unfinished package was detected INSTALLABLE_UPDATE, // same as INSTALLABLE, but an older version is already installed (used for DLC updates after purchasing more content) // below are packages QUEUED, PAUSED, INITIALIZING, // not active yet, downloading TMD CHECKING, // checking for previously downloaded files DOWNLOADING, // downloading content files VERIFYING, // verifying downloaded files INSTALLING, INSTALLED, HAS_ERROR }; DlMgrTitleReport(STATUS status, uint64 titleId, uint16 version, std::string name, uint32 progress, uint32 progressMax, bool isPaused) : status(status), titleId(titleId), version(version), name(name), progress(progress), progressMax(progressMax), isPaused(isPaused) {} uint64 titleId; uint16 version; std::string name; // utf8 STATUS status; uint32 progress; uint32 progressMax; bool isPaused; std::string errorMsg; }; enum class DLMGR_STATUS_CODE { UNINITIALIZED, CONNECTING, FAILED, CONNECTED }; class DownloadManager { public: /* singleton */ static DownloadManager* GetInstance(bool createIfNotExist = true) { static DownloadManager* s_instance = nullptr; if (s_instance) return s_instance; if (createIfNotExist) s_instance = new DownloadManager(); return s_instance; } // login void connect( std::string_view nnidAccountName, const std::array<uint8, 32>& passwordHash, CafeConsoleRegion region, std::string_view country, uint32 deviceId, std::string_view serial, std::string_view deviceCertBase64); bool IsConnected() const; private: /* connect / login */ enum class CONNECT_STATE { UNINITIALIZED = 0, // connect() not called REQUESTED = 1, // connect() requested, but not being processed yet PROCESSING = 2, // processing login request COMPLETE = 3, // login complete / succeeded FAILED = 4, // failed to login }; struct { std::string cachefileName; std::string nnidAccountName; std::array<uint8, 32> passwordHash; std::string deviceCertBase64; CafeConsoleRegion region; std::string country; uint32 deviceId; // deviceId without platform (0x5<<32 for WiiU) std::string serial; }m_authInfo{}; struct { // auth info we have to request from the server std::string serviceAccountId; // internal account id (integer) provided by server when registering account (GetRegistrationInfo) std::string deviceToken; }m_iasToken{}; std::atomic<CONNECT_STATE> m_connectState{ CONNECT_STATE::UNINITIALIZED }; void _handle_connect(); bool _connect_refreshIASAccountIdAndDeviceToken(); bool _connect_queryAccountStatusAndServiceURLs(); NetworkService GetDownloadMgrNetworkService(); NAPI::AuthInfo GetAuthInfo(bool withIasToken); /* idbe cache */ public: void prepareIDBE(uint64 titleId); std::string getNameFromCachedIDBE(uint64 titleId); private: NAPI::IDBEIconDataV0* getIDBE(uint64 titleId); /* ticket cache */ public: struct ETicketInfo { enum class SOURCE : uint8 { ECS_TICKET = 0, // personalized ticket of owned title PUBLIC_TICKET = 1, // public ticket file (available as /CETK from NUS server or via SOAP GetSystemCommonETicket. The former is from Wii era while the latter is how Wii U requests public tickets?) // note: These ids are baked into the serialized cache format. Do not modify }; ETicketInfo(SOURCE source, uint64 ticketId, uint32 ticketVersion, std::vector<uint8>& eTicket); ETicketInfo(SOURCE source, uint64 ticketId, uint32 ticketVersion, std::vector<uint8>& eTicket, std::vector<std::vector<uint8>>& eTicketCerts); void GetTitleKey(NCrypto::AesKey& key); SOURCE source; // tiv uint64 ticketId; uint32 ticketVersion; // titleInfo uint64 titleId{0}; // parsed from eTicket std::vector<uint8> eTicket; std::vector<std::vector<uint8>> eTicketCerts; }; std::vector<ETicketInfo> m_ticketCache; struct UnfinishedDownload { UnfinishedDownload(uint64 titleId, uint16 titleVersion) : titleId(titleId), titleVersion(titleVersion) {}; uint64 titleId; uint16 titleVersion; }; std::vector<UnfinishedDownload> m_unfinishedDownloads; void loadTicketCache(); void storeTicketCache(); bool syncAccountTickets(); bool syncSystemTitleTickets(); bool syncUpdateTickets(); bool syncTicketCache(); void searchForIncompleteDownloads(); void reportAvailableTitles(); private: ETicketInfo* findTicketByTicketId(uint64 ticketId) { for (auto& itr : m_ticketCache) { if (itr.ticketId == ticketId) return &itr; } return nullptr; } void deleteTicketByTicketId(uint64 ticketId) { auto itr = m_ticketCache.begin(); while (itr != m_ticketCache.end()) { if (itr->ticketId == ticketId) { m_ticketCache.erase(itr); return; } itr++; } cemu_assert_debug(false); // ticketId not found } ETicketInfo* findTicketByTitleIdAndVersion(uint64 titleId, uint16 version) { for (auto& itr : m_ticketCache) { if (itr.titleId == titleId && itr.ticketVersion == version) return &itr; } return nullptr; } ETicketInfo* findFirstTicketByTitleId(uint64 titleId) { for (auto& itr : m_ticketCache) { if (itr.titleId == titleId) return &itr; } return nullptr; } bool hasPartialDownload(uint64 titleId, uint16 titleVersion) { for (auto& itr : m_unfinishedDownloads) { if (itr.titleId == titleId && itr.titleVersion == titleVersion) return true; } return false; } void deleteUnfinishedDownloadRecord(uint64 titleId) { auto itr = m_unfinishedDownloads.begin(); while (itr != m_unfinishedDownloads.end()) { if (itr->titleId == titleId) { itr = m_unfinishedDownloads.erase(itr); continue; } itr++; } } /* packages / downloading */ struct Package { Package(uint64 titleId, uint16 version, std::span<uint8> ticketData) : titleId(titleId), version(version), eTicketData(ticketData.data(), ticketData.data() + ticketData.size()) { NCrypto::ETicketParser eTicketParser; cemu_assert(eTicketParser.parse(ticketData.data(), ticketData.size())); eTicketParser.GetTitleKey(ticketKey); }; ~Package() { delete state.tmd; } enum class STATE { INITIAL, CHECKING, DOWNLOADING, VERIFYING, INSTALLING, INSTALLED // done }; uint64 titleId; uint16 version; std::vector<uint8> eTicketData; NCrypto::AesKey ticketKey; // internal struct ContentFile { ContentFile(uint16 index, uint32 contentId, uint64 size, NCrypto::TMDParser::TMDContentFlags contentFlags, uint8 hash[32]) : index(index), contentId(contentId), size(size), contentFlags(contentFlags) { std::memcpy(contentHash, hash, 32); CalcPaddedSize(); }; const uint16 index; const uint32 contentId; const uint64 size; uint64 paddedSize; // includes padding forced by encryption/hashing (e.g. Nintendo Land update has one non-hashed content size of 0x8001, padded to 0x8010) const NCrypto::TMDParser::TMDContentFlags contentFlags; uint8 contentHash[32]; enum class STATE { CHECK, DOWNLOAD, VERIFY, INSTALL, ENUM_COUNT, // for enum_array }; STATE currentState{STATE::CHECK}; bool isBeingProcessed{false}; // worked thread assigned and processing current state void finishProcessing(STATE newState) { currentState = newState; isBeingProcessed = false; }; // progress tracking uint64 amountDownloaded{}; private: void CalcPaddedSize() { if (HAS_FLAG(contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_HASHED_CONTENT)) { // pad to 0x10000 bytes paddedSize = (size + 0xFFFFull) & ~0xFFFFull; } else { // pad to 16 bytes paddedSize = (size + 0xFull) & ~0xFull; } } }; struct { bool isActive{}; // actively downloading bool isPaused{}; STATE currentState{ STATE::INITIAL }; // tmd bool isDownloadingTMD{}; std::vector<uint8> tmdData; NCrypto::TMDParser* tmd{}; // app/h3 tracking std::unordered_map<uint16, ContentFile> contentFiles; // progress of current operation uint32 progress{}; // for downloading: in 1/10th of a percent (0-1000), for installing: number of files uint32 progressMax{}; // maximum (downloading: unused, installing: total number of files) // installing bool isInstalling{}; // error state bool hasError{}; std::string errorMsg; }state; }; std::recursive_mutex m_mutex; std::vector<Package*> m_packageList; public: void initiateDownload(uint64 titleId, uint16 version); void pauseDownload(uint64 titleId, uint16 version); private: Package* getPackage(uint64 titleId, uint16 version); fs::path getPackageDownloadPath(Package* package); fs::path getPackageInstallPath(Package* package); void updatePackage(Package* package); void checkPackagesState(); void setPackageError(Package* package, std::string errorMsg); void reportPackageStatus(Package* package); void reportPackageProgress(Package* package, uint32 newProgress); void asyncPackageDownloadTMD(Package* package); void calcPackageDownloadProgress(Package* package); void asyncPackageDownloadContentFile(Package* package, uint16 index); void asyncPackageVerifyFile(Package* package, uint16 index, bool isCheckState); void asyncPackageInstall(Package* package); bool asyncPackageInstallRecursiveExtractFiles(Package* package, class FSTVolume* fstVolume, const std::string& sourcePath, const fs::path& destinationPath); /* callback interface */ public: void setUserData(void* ptr) { m_userData = ptr; } void* getUserData() const { return m_userData; } // register/unregister callbacks // setting valid callbacks will also trigger transfer of the entire title/package state and the current status message void registerCallbacks( void(*cbUpdateConnectStatus)(std::string statusText, DLMGR_STATUS_CODE statusCode), void(*cbAddDownloadableTitle)(const DlMgrTitleReport& titleInfo), void(*cbRemoveDownloadableTitle)(uint64 titleId, uint16 version) ) { std::unique_lock<std::recursive_mutex> _l(m_mutex); m_cbUpdateConnectStatus = cbUpdateConnectStatus; m_cbAddDownloadableTitle = cbAddDownloadableTitle; m_cbRemoveDownloadableTitle = cbRemoveDownloadableTitle; // resend data if (m_cbUpdateConnectStatus || m_cbAddDownloadableTitle) { std::unique_lock<std::recursive_mutex> _l(m_mutex); setStatusMessage(m_statusMessage, m_statusCode); reportAvailableTitles(); for (auto& p : m_packageList) reportPackageStatus(p); } } void setStatusMessage(std::string_view msg, DLMGR_STATUS_CODE statusCode) { m_statusMessage = msg; m_statusCode = statusCode; if (m_cbUpdateConnectStatus) m_cbUpdateConnectStatus(m_statusMessage, statusCode); } std::string m_statusMessage{}; DLMGR_STATUS_CODE m_statusCode{ DLMGR_STATUS_CODE::UNINITIALIZED }; bool hasActiveDownloads() { std::unique_lock<std::recursive_mutex> _l(m_mutex); for (auto& p : m_packageList) { if (p->state.isActive && !p->state.hasError && (p->state.currentState != Package::STATE::INSTALLED)) { return true; } } return false; } void reset() { std::unique_lock<std::recursive_mutex> _l(m_mutex); m_packageList.clear(); m_statusCode = DLMGR_STATUS_CODE::UNINITIALIZED; m_statusMessage.clear(); } private: void(*m_cbUpdateConnectStatus)(std::string statusText, DLMGR_STATUS_CODE statusCode) { nullptr }; void(*m_cbAddDownloadableTitle)(const DlMgrTitleReport& titleInfo); void(*m_cbRemoveDownloadableTitle)(uint64 titleId, uint16 version); void* m_userData{}; /* title version list */ bool m_hasTitleVersionList{}; std::unordered_map<uint64, uint16> m_titleVersionList; void downloadTitleVersionList(); bool getTitleLatestVersion(TitleId titleId, uint16& version); /* helper for building list of owned titles (is installed or has cached ticket) */ struct TitleInstallState { TitleInstallState(DownloadManager* dlMgr, uint64 titleId); uint64 titleId; bool isInstalled; uint16 installedTitleVersion; uint16 installedTicketVersion{}; bool operator<(const TitleInstallState& comp) const { return this->titleId < comp.titleId; } }; struct TitleDownloadAvailableState : TitleInstallState { TitleDownloadAvailableState(DownloadManager* dlMgr, uint64 titleId); bool isUpdateAvailable; // update available for this specific titleId uint16 availableTitleVersion; }; std::set<TitleInstallState> getOwnedTitleList(); std::set<TitleDownloadAvailableState> getFullDownloadList(); /* thread */ void threadFunc(); void runManager(); void notifyManager(); void queueManagerJob(const std::function<void()>& callback); std::atomic_bool m_threadLaunched{ false }; CounterSemaphore m_queuedEvents; ConcurrentQueue<std::function<void()>> m_jobQueue; std::mutex m_updateList; // not needed? }; ================================================ FILE: src/Cemu/napi/napi.h ================================================ #pragma once #include "config/CemuConfig.h" // for ConsoleLanguage #include "config/NetworkSettings.h" // for NetworkService #include "config/ActiveSettings.h" // for GetNetworkService() enum class NAPI_RESULT { SUCCESS = 0, FAILED = 1, // general failure XML_ERROR = 2, // XML response unexpected DATA_ERROR = 3, // incorrect values SERVICE_ERROR = 4, // server reply indicates error. Extended error code (serviceError) is set }; namespace NAPI { // common auth info structure shared by ACT, ECS and IAS service struct AuthInfo { // nnid std::string accountId; std::array<uint8, 32> passwordHash; // console uint32 deviceId; std::string serial; CafeConsoleRegion region; std::string country; std::string deviceCertBase64; uint64 getDeviceIdWithPlatform() { uint64 deviceType = 5; return (deviceType << 32) | ((uint64)deviceId); } // IAS token (for ECS and other SOAP service requests) struct { std::string accountId; // console account id std::string deviceToken; }IASToken; // service selection, if not set fall back to global setting std::optional<NetworkService> serviceOverwrite; NetworkService GetService() const { return serviceOverwrite.value_or(ActiveSettings::GetNetworkService()); } }; bool NAPI_MakeAuthInfoFromCurrentAccount(AuthInfo& authInfo); // helper function. Returns false if online credentials/dumped files are not available /* Shared result */ // ErrorCodes IAS: // 928 -> IAS - Device cert does not verify. // 954 -> IAS - Invalid Challenge enum class EC_ERROR_CODE { NONE = 0, ECS_INVALID_DEVICE_TOKEN = 903, IAS_DEVICE_CERT_ERROR = 928, // either invalid signature or mismatching incorrect device cert chain IAS_INVALID_CHALLENGE = 954, ECS_NO_PERMISSION = 701, // AccountGetETickets NUS_SOAP_INVALID_VERSION = 1301, // seen when accidentally passing wrong SOAP version to GetSystemCommonETicket }; enum class ACT_ERROR_CODE // are these shared with EC_ERROR_CODE? { NONE = 0, ACT_GAME_SERVER_NOT_FOUND = 1021, // seen when passing wrong serverId to nex token request }; struct _NAPI_CommonResultSOAP { NAPI_RESULT apiError{ NAPI_RESULT::FAILED }; EC_ERROR_CODE serviceError{ EC_ERROR_CODE::NONE }; bool isValid() const { return apiError == NAPI_RESULT::SUCCESS; } }; struct _NAPI_CommonResultACT { NAPI_RESULT apiError{ NAPI_RESULT::FAILED }; ACT_ERROR_CODE serviceError{ ACT_ERROR_CODE::NONE }; bool isValid() const { return apiError == NAPI_RESULT::SUCCESS; } }; /* account service (account.nintendo.net) */ struct ACTGetProfileResult { int todo; }; bool ACT_GetProfile(AuthInfo& authInfo, ACTGetProfileResult& result); struct ACTNexToken { /* +0x000 */ char token[0x201]; /* +0x201 */ uint8 _padding201[3]; /* +0x204 */ char nexPassword[0x41]; /* +0x245 */ uint8 _padding245[3]; /* +0x248 */ char host[0x10]; /* +0x258 */ uint16be port; /* +0x25A */ uint8 _padding25A[2]; }; static_assert(sizeof(ACTNexToken) == 0x25C); struct ACTGetNexTokenResult : public _NAPI_CommonResultACT { ACTNexToken nexToken; }; ACTGetNexTokenResult ACT_GetNexToken_WithCache(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion, uint32 serverId); struct ACTGetIndependentTokenResult : public _NAPI_CommonResultACT { std::string token; sint64 expiresIn{}; }; ACTGetIndependentTokenResult ACT_GetIndependentToken_WithCache(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion, std::string_view clientId); struct ACTConvertNnidToPrincipalIdResult : public _NAPI_CommonResultACT { bool isFound{false}; uint32 principalId{}; }; ACTConvertNnidToPrincipalIdResult ACT_ACTConvertNnidToPrincipalId(AuthInfo& authInfo, std::string_view nnid); /* NUS */ struct NAPI_NUSGetSystemCommonETicket_Result : public _NAPI_CommonResultSOAP { std::vector<uint8> eTicket; std::vector<std::vector<uint8>> certs; }; NAPI_NUSGetSystemCommonETicket_Result NUS_GetSystemCommonETicket(AuthInfo& authInfo, uint64 titleId); /* IAS/ECS */ struct NAPI_IASGetChallenge_Result : public _NAPI_CommonResultSOAP { std::string challenge; }; struct NAPI_IASGetRegistrationInfo_Result : public _NAPI_CommonResultSOAP { std::string accountId; std::string deviceToken; }; struct NAPI_ECSGetAccountStatus_Result : public _NAPI_CommonResultSOAP { enum class AccountStatus { REGISTERED = 'R', TRANSFERRED = 'T', UNREGISTERED = 'U', UNKNOWN = '\0', }; std::string accountId; AccountStatus accountStatus{ AccountStatus::UNKNOWN }; struct { std::string ContentPrefixURL; std::string UncachedContentPrefixURL; std::string SystemContentPrefixURL; std::string SystemUncachedContentPrefixURL; std::string EcsURL; std::string IasURL; std::string CasURL; std::string NusURL; }serviceURLs; }; struct NAPI_ECSAccountListETicketIds_Result : public _NAPI_CommonResultSOAP { struct TIV { sint64 ticketId; uint32 ticketVersion; }; std::vector<TIV> tivs; }; struct NAPI_ECSAccountGetETickets_Result : public _NAPI_CommonResultSOAP { std::vector<uint8> eTickets; std::vector<std::vector<uint8>> certs; }; NAPI_IASGetChallenge_Result IAS_GetChallenge(AuthInfo& authInfo); NAPI_IASGetRegistrationInfo_Result IAS_GetRegistrationInfo_QueryInfo(AuthInfo& authInfo, std::string challenge); NAPI_ECSGetAccountStatus_Result ECS_GetAccountStatus(AuthInfo& authInfo); NAPI_ECSAccountListETicketIds_Result ECS_AccountListETicketIds(AuthInfo& authInfo); NAPI_ECSAccountGetETickets_Result ECS_AccountGetETickets(AuthInfo& authInfo, sint64 ticketId); /* CCS */ struct NAPI_CCSGetTMD_Result { bool isValid{ false }; std::vector<uint8> tmdData; }; struct NAPI_CCSGetETicket_Result { bool isValid{ false }; std::vector<uint8> cetkData; }; struct NAPI_CCSGetContentH3_Result { bool isValid{ false }; std::vector<uint8> tmdData; }; NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion); NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId); NAPI_CCSGetETicket_Result CCS_GetCETK(NetworkService service, uint64 titleId, uint16 titleVersion); bool CCS_GetContentFile(NetworkService service, uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData); NAPI_CCSGetContentH3_Result CCS_GetContentH3File(NetworkService service, uint64 titleId, uint32 contentId); /* IDBE */ struct IDBEIconDataV0 { struct LanguageInfo { std::string GetGameNameUTF8(); std::string GetGameLongNameUTF8(); std::string GetPublisherNameUTF8(); private: uint16be gameName[0x40]{}; uint16be gameLongName[0x80]{}; uint16be publisherName[0x40]{}; }; LanguageInfo& GetLanguageStrings(CafeConsoleLanguage index) { if ((uint32)index >= 16) return languageInfo[0]; return languageInfo[(uint32)index]; } private: /* +0x00000 */ uint32be titleIdHigh{}; /* +0x00004 */ uint32be titleIdLow{}; /* +0x00008 */ uint32 ukn00008; /* +0x0000C */ uint32 ukn0000C; /* +0x00010 */ uint32 ukn00010; /* +0x00014 */ uint32 ukn00014; /* +0x00018 */ uint32 ukn00018; /* +0x0001C */ uint32 ukn0001C; /* +0x00020 */ uint32 ukn00020; /* +0x00024 */ uint32 ukn00024; /* +0x00028 */ uint32 ukn00028; /* +0x0002C */ uint32 ukn0002C; /* +0x00030 */ LanguageInfo languageInfo[16]; /* +0x02030 */ uint8 tgaData[0x10030]{}; }; static_assert(sizeof(IDBEIconDataV0) == 0x12060); struct IDBEHeader { uint8 formatVersion; uint8 keyIndex; uint8 hashSHA256[32]; }; static_assert(sizeof(IDBEHeader) == 2+32); std::optional<IDBEIconDataV0> IDBE_Request(NetworkService networkService, uint64 titleId); std::vector<uint8> IDBE_RequestRawEncrypted(NetworkService networkService, uint64 titleId); // same as IDBE_Request but doesn't strip the header and decrypt the IDBE /* Version list */ struct NAPI_VersionListVersion_Result { bool isValid{false}; uint32 version{0}; std::string fqdnURL; }; NAPI_VersionListVersion_Result TAG_GetVersionListVersion(AuthInfo& authInfo); struct NAPI_VersionList_Result { bool isValid{ false }; std::unordered_map<uint64, uint16> titleVersionList; // key = titleId, value = version }; NAPI_VersionList_Result TAG_GetVersionList(AuthInfo& authInfo, std::string_view fqdnURL, uint32 versionListVersion); } void NAPI_NUS_GetSystemUpdate(); void NAPI_NUS_GetSystemTitleHash(); ================================================ FILE: src/Cemu/napi/napi_act.cpp ================================================ #include "Common/precompiled.h" #include "Cemu/ncrypto/ncrypto.h" #include "napi.h" #include "napi_helper.h" #include "curl/curl.h" #include "pugixml.hpp" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "config/ActiveSettings.h" #include "util/helpers/StringHelpers.h" #include "util/highresolutiontimer/HighResolutionTimer.h" #include "config/LaunchSettings.h" namespace NAPI { std::string _getACTUrl(NetworkService service) { switch (service) { case NetworkService::Nintendo: return NintendoURLs::ACTURL; case NetworkService::Pretendo: return PretendoURLs::ACTURL; case NetworkService::Custom: return GetNetworkConfig().urls.ACT.GetValue(); default: return NintendoURLs::ACTURL; } } struct ACTOauthToken : public _NAPI_CommonResultACT { std::string token; std::string refreshToken; }; bool _parseActResponse(CurlRequestHelper& requestHelper, _NAPI_CommonResultACT& result, pugi::xml_document& doc) { if (!doc.load_buffer(requestHelper.getReceivedData().data(), requestHelper.getReceivedData().size())) { cemuLog_log(LogType::Force, fmt::format("Invalid XML in account service response")); result.apiError = NAPI_RESULT::XML_ERROR; return false; } // check for error codes pugi::xml_node errors = doc.child("errors"); if (errors) { pugi::xml_node error = errors.child("error"); if (error) { std::string_view errorCodeStr = error.child_value("code"); std::string_view errorCodeMsg = error.child_value("message"); sint32 errorCode = StringHelpers::ToInt(errorCodeStr); if (errorCode == 0) { cemuLog_log(LogType::Force, "Account response with unexpected error code 0"); result.apiError = NAPI_RESULT::XML_ERROR; } else { result.apiError = NAPI_RESULT::SERVICE_ERROR; result.serviceError = (ACT_ERROR_CODE)errorCode; cemuLog_log(LogType::Force, "Account response with error code {}", errorCode); if(!errorCodeMsg.empty()) cemuLog_log(LogType::Force, "Message from server: {}", errorCodeMsg); } } else { result.apiError = NAPI_RESULT::XML_ERROR; } return false; } return true; } void _ACTSetCommonHeaderParameters(CurlRequestHelper& req, AuthInfo& authInfo) { req.addHeaderField("X-Nintendo-Platform-ID", "1"); req.addHeaderField("X-Nintendo-Device-Type", "2"); req.addHeaderField("X-Nintendo-Client-ID", "a2efa818a34fa16b8afbc8a74eba3eda"); req.addHeaderField("X-Nintendo-Client-Secret", "c91cdb5658bd4954ade78533a339cf9a"); req.addHeaderField("Accept", "*/*"); if(authInfo.region == CafeConsoleRegion::USA) req.addHeaderField("X-Nintendo-System-Version", "0270"); else req.addHeaderField("X-Nintendo-System-Version", "0260"); } void _ACTSetDeviceParameters(CurlRequestHelper& req, AuthInfo& authInfo) { req.addHeaderField("X-Nintendo-Device-ID", fmt::format("{}", authInfo.deviceId)); // deviceId without platform field req.addHeaderField("X-Nintendo-Serial-Number", authInfo.serial); } void _ACTSetRegionAndCountryParameters(CurlRequestHelper& req, AuthInfo& authInfo) { req.addHeaderField("X-Nintendo-Region", fmt::format("{}", (uint32)authInfo.region)); req.addHeaderField("X-Nintendo-Country", authInfo.country); } struct OAuthTokenCacheEntry { OAuthTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, std::string_view token, std::string_view refreshToken, uint64 expiresIn, NetworkService service) : accountId(accountId), passwordHash(passwordHash), token(token), refreshToken(refreshToken), service(service) { expires = HighResolutionTimer::now().getTickInSeconds() + expiresIn; }; bool CheckIfSameAccount(const AuthInfo& authInfo) const { return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash; } bool CheckIfExpired() const { return HighResolutionTimer::now().getTickInSeconds() >= expires; } std::string accountId; std::array<uint8, 32> passwordHash; std::string token; std::string refreshToken; uint64 expires; NetworkService service; }; std::vector<OAuthTokenCacheEntry> g_oauthTokenCache; std::mutex g_oauthTokenCacheMtx; // look up oauth token in cache, otherwise request from server ACTOauthToken ACT_GetOauthToken_WithCache(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion) { ACTOauthToken result{}; // check cache first NetworkService service = authInfo.GetService(); g_oauthTokenCacheMtx.lock(); auto cacheItr = g_oauthTokenCache.begin(); while (cacheItr != g_oauthTokenCache.end()) { if (cacheItr->CheckIfSameAccount(authInfo) && cacheItr->service == service) { if (cacheItr->CheckIfExpired()) { cacheItr = g_oauthTokenCache.erase(cacheItr); continue; } result.token = cacheItr->token; result.refreshToken = cacheItr->refreshToken; result.apiError = NAPI_RESULT::SUCCESS; g_oauthTokenCacheMtx.unlock(); return result; } cacheItr++; } g_oauthTokenCacheMtx.unlock(); // token not cached, request from server via oauth2 CurlRequestHelper req; req.initate(authInfo.GetService(), fmt::format("{}/v1/api/oauth20/access_token/generate", _getACTUrl(authInfo.GetService())), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); req.addHeaderField("X-Nintendo-Device-Cert", authInfo.deviceCertBase64); req.addHeaderField("X-Nintendo-FPD-Version", "0000"); req.addHeaderField("X-Nintendo-Environment", "L1"); req.addHeaderField("X-Nintendo-Title-ID", fmt::format("{:016x}", titleId)); uint32 uniqueId = ((titleId >> 8) & 0xFFFFF); req.addHeaderField("X-Nintendo-Unique-ID", fmt::format("{:05x}", uniqueId)); req.addHeaderField("X-Nintendo-Application-Version", fmt::format("{:04x}", titleVersion)); // convert password hash to string char passwordHashString[128]; for (sint32 i = 0; i < 32; i++) sprintf(passwordHashString + i * 2, "%02x", authInfo.passwordHash[i]); req.addPostField("grant_type", "password"); req.addPostField("user_id", authInfo.accountId); req.addPostField("password", passwordHashString); req.addPostField("password_type", "hash"); req.addHeaderField("Content-type", "application/x-www-form-urlencoded"); if (!req.submitRequest(true)) { cemuLog_log(LogType::Force, fmt::format("Failed request /oauth20/access_token/generate")); result.apiError = NAPI_RESULT::FAILED; return result; } /* Response example: <OAuth20> <access_token> <token>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</token> <refresh_token>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</refresh_token> <expires_in>3600</expires_in> </access_token> </OAuth20> */ // parse result pugi::xml_document doc; if (!_parseActResponse(req, result, doc)) return result; pugi::xml_node node = doc.child("OAuth20"); if (!node) { cemuLog_log(LogType::Force, fmt::format("Response does not contain OAuth20 node")); result.apiError = NAPI_RESULT::XML_ERROR; return result; } node = node.child("access_token"); if (!node) { cemuLog_log(LogType::Force, fmt::format("Response does not contain OAuth20/access_token node")); result.apiError = NAPI_RESULT::XML_ERROR; return result; } result.token = node.child_value("token"); result.refreshToken = node.child_value("refresh_token"); std::string_view expires_in = node.child_value("expires_in"); result.apiError = NAPI_RESULT::SUCCESS; if (result.token.empty()) cemuLog_log(LogType::Force, "OAuth20/token is empty"); sint64 expiration = StringHelpers::ToInt64(expires_in); expiration = std::max(expiration - 30LL, 0LL); // subtract a few seconds to compensate for the web request delay // update cache if (expiration > 0) { g_oauthTokenCacheMtx.lock(); g_oauthTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, result.token, result.refreshToken, expiration, service); g_oauthTokenCacheMtx.unlock(); } return result; } bool ACT_GetProfile(AuthInfo& authInfo, ACTGetProfileResult& result) { CurlRequestHelper req; req.initate(authInfo.GetService(), fmt::format("{}/v1/api/people/@me/profile", _getACTUrl(authInfo.GetService())), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); // get oauth2 token ACTOauthToken oauthToken = ACT_GetOauthToken_WithCache(authInfo, 0x0005001010001C00, 0x0001C); cemu_assert_unimplemented(); return true; } struct NexTokenCacheEntry { NexTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, NetworkService networkService, uint32 gameServerId, ACTNexToken& nexToken) : accountId(accountId), passwordHash(passwordHash), networkService(networkService), nexToken(nexToken), gameServerId(gameServerId) {}; bool IsMatch(const AuthInfo& authInfo, const uint32 gameServerId) const { return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && authInfo.GetService() == networkService && this->gameServerId == gameServerId; } std::string accountId; std::array<uint8, 32> passwordHash; NetworkService networkService; uint32 gameServerId; ACTNexToken nexToken; }; std::vector<NexTokenCacheEntry> g_nexTokenCache; std::mutex g_nexTokenCacheMtx; ACTGetNexTokenResult ACT_GetNexToken_WithCache(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion, uint32 serverId) { ACTGetNexTokenResult result{}; // check cache g_nexTokenCacheMtx.lock(); for (auto& itr : g_nexTokenCache) { if (itr.IsMatch(authInfo, serverId)) { result.nexToken = itr.nexToken; result.apiError = NAPI_RESULT::SUCCESS; g_nexTokenCacheMtx.unlock(); return result; } } g_nexTokenCacheMtx.unlock(); // get Nex token ACTOauthToken oauthToken = ACT_GetOauthToken_WithCache(authInfo, titleId, titleVersion); if (!oauthToken.isValid()) { cemuLog_log(LogType::Force, "ACT_GetNexToken(): Failed to retrieve OAuth token"); if (oauthToken.apiError == NAPI_RESULT::SERVICE_ERROR) { result.apiError = NAPI_RESULT::SERVICE_ERROR; result.serviceError = oauthToken.serviceError; } else { result.apiError = NAPI_RESULT::DATA_ERROR; } return result; } // do request CurlRequestHelper req; req.initate(authInfo.GetService(), fmt::format("{}/v1/api/provider/nex_token/@me?game_server_id={:08X}", _getACTUrl(authInfo.GetService()), serverId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); req.addHeaderField("X-Nintendo-FPD-Version", "0000"); req.addHeaderField("X-Nintendo-Environment", "L1"); req.addHeaderField("X-Nintendo-Title-ID", fmt::format("{:016x}", titleId)); uint32 uniqueId = ((titleId >> 8) & 0xFFFFF); req.addHeaderField("X-Nintendo-Unique-ID", fmt::format("{:05x}", uniqueId)); req.addHeaderField("X-Nintendo-Application-Version", fmt::format("{:04x}", titleVersion)); req.addHeaderField("Authorization", fmt::format("Bearer {}", oauthToken.token)); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed request /provider/nex_token/@me")); result.apiError = NAPI_RESULT::FAILED; return result; } /* Example response (success): <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <nex_token> <host>HOST</host> <nex_password>xxxxxxxxxxxxxxxx</nex_password> <pid>123456</pid> <port>60200</port> <token>xxxxxxxxxxxxxxx</token> </nex_token> Example response (error case): <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <errors> <error> <code>1021</code> <message>The requested game server was not found.</message> </error> </errors> */ // code 0124 -> Version lower than useable registered // parse result pugi::xml_document doc; if (!_parseActResponse(req, result, doc)) return result; pugi::xml_node tokenNode = doc.child("nex_token"); if (!tokenNode) { cemuLog_log(LogType::Force, "Response does not contain NexToken node"); result.apiError = NAPI_RESULT::XML_ERROR; return result; } std::string_view host = tokenNode.child_value("host"); std::string_view nex_password = tokenNode.child_value("nex_password"); std::string_view port = tokenNode.child_value("port"); std::string_view token = tokenNode.child_value("token"); memset(&result.nexToken, 0, sizeof(ACTNexToken)); if (host.size() > 15) cemuLog_log(LogType::Force, "NexToken response: host field too long"); if (nex_password.size() > 64) cemuLog_log(LogType::Force, "NexToken response: nex_password field too long"); if (token.size() > 512) cemuLog_log(LogType::Force, "NexToken response: token field too long"); for (size_t i = 0; i < std::min(host.size(), (size_t)15); i++) result.nexToken.host[i] = host[i]; for (size_t i = 0; i < std::min(nex_password.size(), (size_t)64); i++) result.nexToken.nexPassword[i] = nex_password[i]; for (size_t i = 0; i < std::min(token.size(), (size_t)512); i++) result.nexToken.token[i] = token[i]; result.nexToken.port = (uint16)StringHelpers::ToInt(port); result.apiError = NAPI_RESULT::SUCCESS; g_nexTokenCacheMtx.lock(); g_nexTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, authInfo.GetService(), serverId, result.nexToken); g_nexTokenCacheMtx.unlock(); return result; } struct IndependentTokenCacheEntry { IndependentTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, NetworkService networkService, std::string_view clientId, std::string_view independentToken, sint64 expiresIn) : accountId(accountId), passwordHash(passwordHash), networkService(networkService), clientId(clientId), independentToken(independentToken) { expires = HighResolutionTimer::now().getTickInSeconds() + expiresIn; }; bool IsMatch(const AuthInfo& authInfo, const std::string_view clientId) const { return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && authInfo.GetService() == networkService && this->clientId == clientId; } bool CheckIfExpired() const { return (sint64)HighResolutionTimer::now().getTickInSeconds() >= expires; } std::string accountId; std::array<uint8, 32> passwordHash; NetworkService networkService; std::string clientId; sint64 expires; std::string independentToken; }; std::vector<IndependentTokenCacheEntry> g_IndependentTokenCache; std::mutex g_IndependentTokenCacheMtx; ACTGetIndependentTokenResult ACT_GetIndependentToken_WithCache(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion, std::string_view clientId) { ACTGetIndependentTokenResult result{}; // check cache g_IndependentTokenCacheMtx.lock(); auto itr = g_IndependentTokenCache.begin(); while(itr != g_IndependentTokenCache.end()) { if (itr->CheckIfExpired()) { itr = g_IndependentTokenCache.erase(itr); continue; } else if (itr->IsMatch(authInfo, clientId)) { result.token = itr->independentToken; result.expiresIn = std::max(itr->expires - (sint64)HighResolutionTimer::now().getTickInSeconds(), (sint64)0); result.apiError = NAPI_RESULT::SUCCESS; g_IndependentTokenCacheMtx.unlock(); return result; } itr++; } g_IndependentTokenCacheMtx.unlock(); // get Independent token ACTOauthToken oauthToken = ACT_GetOauthToken_WithCache(authInfo, titleId, titleVersion); if (!oauthToken.isValid()) { cemuLog_log(LogType::Force, "ACT_GetIndependentToken(): Failed to retrieve OAuth token"); if (oauthToken.apiError == NAPI_RESULT::SERVICE_ERROR) { result.apiError = NAPI_RESULT::SERVICE_ERROR; result.serviceError = oauthToken.serviceError; } else { result.apiError = NAPI_RESULT::DATA_ERROR; } return result; } // do request CurlRequestHelper req; req.initate(authInfo.GetService(), fmt::format("{}/v1/api/provider/service_token/@me?client_id={}", _getACTUrl(authInfo.GetService()), clientId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); req.addHeaderField("X-Nintendo-FPD-Version", "0000"); req.addHeaderField("X-Nintendo-Environment", "L1"); req.addHeaderField("X-Nintendo-Title-ID", fmt::format("{:016x}", titleId)); uint32 uniqueId = ((titleId >> 8) & 0xFFFFF); req.addHeaderField("X-Nintendo-Unique-ID", fmt::format("{:05x}", uniqueId)); req.addHeaderField("X-Nintendo-Application-Version", fmt::format("{:04x}", titleVersion)); req.addHeaderField("Authorization", fmt::format("Bearer {}", oauthToken.token)); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed request /provider/service_token/@me")); result.apiError = NAPI_RESULT::FAILED; return result; } /* Example response (success): <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <service_token> <token>xxxxxxxxxxxx</token> </service_token> */ // parse result pugi::xml_document doc; if (!_parseActResponse(req, result, doc)) return result; pugi::xml_node tokenNode = doc.child("service_token"); if (!tokenNode) { cemuLog_log(LogType::Force, "Response does not contain service_token node"); result.apiError = NAPI_RESULT::XML_ERROR; return result; } std::string_view token = tokenNode.child_value("token"); result.token = token; result.apiError = NAPI_RESULT::SUCCESS; g_IndependentTokenCacheMtx.lock(); g_IndependentTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, authInfo.GetService(), clientId, result.token, 3600); g_IndependentTokenCacheMtx.unlock(); return result; } ACTConvertNnidToPrincipalIdResult ACT_ACTConvertNnidToPrincipalId(AuthInfo& authInfo, std::string_view nnid) { ACTConvertNnidToPrincipalIdResult result{}; // get Independent token ACTOauthToken oauthToken = ACT_GetOauthToken_WithCache(authInfo, 0x0005001010001C00, 0); if (!oauthToken.isValid()) { cemuLog_log(LogType::Force, "ACT_ACTConvertNnidToPrincipalId(): Failed to retrieve OAuth token"); if (oauthToken.apiError == NAPI_RESULT::SERVICE_ERROR) { result.apiError = NAPI_RESULT::SERVICE_ERROR; result.serviceError = oauthToken.serviceError; } else { result.apiError = NAPI_RESULT::DATA_ERROR; } return result; } // do request CurlRequestHelper req; req.initate(authInfo.GetService(), fmt::format("{}/v1/api/admin/mapped_ids?input_type=user_id&output_type=pid&input={}", _getACTUrl(authInfo.GetService()), nnid), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); req.addHeaderField("X-Nintendo-FPD-Version", "0000"); req.addHeaderField("X-Nintendo-Environment", "L1"); req.addHeaderField("X-Nintendo-Title-ID", fmt::format("{:016x}", 0x0005001010001C00)); uint32 uniqueId = 0x50010; req.addHeaderField("X-Nintendo-Unique-ID", fmt::format("{:05x}", uniqueId)); req.addHeaderField("X-Nintendo-Application-Version", fmt::format("{:04x}", 0)); req.addHeaderField("Authorization", fmt::format("Bearer {}", oauthToken.token)); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed request /admin/mapped_ids")); result.apiError = NAPI_RESULT::FAILED; return result; } /* Example response (success): <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <mapped_ids> <mapped_id> <in_id>input-nnid</in_id> <out_id>12345</out_id> </mapped_id> </mapped_ids> */ // parse result pugi::xml_document doc; if (!_parseActResponse(req, result, doc)) return result; pugi::xml_node tokenNode = doc.child("mapped_ids"); if (!tokenNode) { cemuLog_log(LogType::Force, "Response does not contain mapped_ids node"); result.apiError = NAPI_RESULT::XML_ERROR; return result; } tokenNode = tokenNode.child("mapped_id"); if (!tokenNode) { cemuLog_log(LogType::Force, "Response does not contain mapped_id node"); result.apiError = NAPI_RESULT::XML_ERROR; return result; } std::string_view pidString = tokenNode.child_value("out_id"); if (!pidString.empty()) { result.isFound = true; result.principalId = StringHelpers::ToInt(pidString); } else { result.isFound = false; result.principalId = 0; } result.apiError = NAPI_RESULT::SUCCESS; return result; } bool NAPI_MakeAuthInfoFromCurrentAccount(AuthInfo& authInfo) { authInfo = {}; if (!NCrypto::SEEPROM_IsPresent()) return false; const Account& account = Account::GetCurrentAccount(); authInfo.accountId = account.GetAccountId(); auto passwordHash = account.GetAccountPasswordCache(); authInfo.passwordHash = passwordHash; if (std::all_of(passwordHash.cbegin(), passwordHash.cend(), [](uint8 v) { return v == 0; })) { static bool s_showedLoginError = false; if (!s_showedLoginError) { cemuLog_log(LogType::Force, "Account login is impossible because the cached password hash is not set"); s_showedLoginError = true; } return false; // password hash not set } authInfo.deviceId = NCrypto::GetDeviceId(); authInfo.serial = NCrypto::GetSerial(); authInfo.region = NCrypto::SEEPROM_GetRegion(); authInfo.country = NCrypto::GetCountryAsString(account.GetCountry()); authInfo.deviceCertBase64 = NCrypto::CertECC::GetDeviceCertificate().encodeToBase64(); return true; } } ================================================ FILE: src/Cemu/napi/napi_ec.cpp ================================================ #include "Common/precompiled.h" #include "napi.h" #include "napi_helper.h" #include "curl/curl.h" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cemu/ncrypto/ncrypto.h" #include "util/crypto/md5.h" #include "config/LaunchSettings.h" #include "config/ActiveSettings.h" #include "config/NetworkSettings.h" #include "pugixml.hpp" #include <charconv> namespace NAPI { /* Service URL manager */ struct CachedServiceUrls { std::string s_serviceURL_ContentPrefixURL; std::string s_serviceURL_UncachedContentPrefixURL; std::string s_serviceURL_EcsURL; std::string s_serviceURL_IasURL; std::string s_serviceURL_CasURL; std::string s_serviceURL_NusURL; }; std::unordered_map<NetworkService, CachedServiceUrls> s_cachedServiceUrlsMap; CachedServiceUrls& GetCachedServiceUrls(NetworkService service) { return s_cachedServiceUrlsMap[service]; } std::string _getNUSUrl(NetworkService service) { auto& cachedServiceUrls = GetCachedServiceUrls(service); if (!cachedServiceUrls.s_serviceURL_NusURL.empty()) return cachedServiceUrls.s_serviceURL_NusURL; switch (service) { case NetworkService::Nintendo: return NintendoURLs::NUSURL; case NetworkService::Pretendo: return PretendoURLs::NUSURL; case NetworkService::Custom: return GetNetworkConfig().urls.NUS; default: return NintendoURLs::NUSURL; } } std::string _getIASUrl(NetworkService service) { auto& cachedServiceUrls = GetCachedServiceUrls(service); if (!cachedServiceUrls.s_serviceURL_IasURL.empty()) return cachedServiceUrls.s_serviceURL_IasURL; switch (service) { case NetworkService::Nintendo: return NintendoURLs::IASURL; case NetworkService::Pretendo: return PretendoURLs::IASURL; case NetworkService::Custom: return GetNetworkConfig().urls.IAS; default: return NintendoURLs::IASURL; } } std::string _getECSUrl(NetworkService service) { // this is the first url queried (GetAccountStatus). The others are dynamically set if provided by the server but will fallback to hardcoded defaults otherwise auto& cachedServiceUrls = GetCachedServiceUrls(service); if (!cachedServiceUrls.s_serviceURL_EcsURL.empty()) return cachedServiceUrls.s_serviceURL_EcsURL; switch (service) { case NetworkService::Nintendo: return NintendoURLs::ECSURL; case NetworkService::Pretendo: return PretendoURLs::ECSURL; case NetworkService::Custom: return GetNetworkConfig().urls.ECS; default: return NintendoURLs::ECSURL; } } std::string _getCCSUncachedUrl(NetworkService service) // used for TMD requests { auto& cachedServiceUrls = GetCachedServiceUrls(service); if (!cachedServiceUrls.s_serviceURL_UncachedContentPrefixURL.empty()) return cachedServiceUrls.s_serviceURL_UncachedContentPrefixURL; switch (service) { case NetworkService::Nintendo: return NintendoURLs::CCSUURL; case NetworkService::Pretendo: return PretendoURLs::CCSUURL; case NetworkService::Custom: return GetNetworkConfig().urls.CCSU; default: return NintendoURLs::CCSUURL; } } std::string _getCCSUrl(NetworkService service) // used for game data downloads { auto& cachedServiceUrls = GetCachedServiceUrls(service); if (!cachedServiceUrls.s_serviceURL_ContentPrefixURL.empty()) return cachedServiceUrls.s_serviceURL_ContentPrefixURL; switch (service) { case NetworkService::Nintendo: return NintendoURLs::CCSURL; case NetworkService::Pretendo: return PretendoURLs::CCSURL; case NetworkService::Custom: return GetNetworkConfig().urls.CCS; default: return NintendoURLs::CCSURL; } } /* NUS */ // request ticket for titles which have a public eTicket (usually updates and system titles) NAPI_NUSGetSystemCommonETicket_Result NUS_GetSystemCommonETicket(AuthInfo& authInfo, uint64 titleId) { NAPI_NUSGetSystemCommonETicket_Result result{}; CurlSOAPHelper soapHelper(authInfo.GetService()); soapHelper.SOAP_initate("nus", _getNUSUrl(authInfo.GetService()), "GetSystemCommonETicket", "1.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("RegionId", NCrypto::GetRegionAsString(authInfo.region)); soapHelper.SOAP_addRequestField("CountryCode", authInfo.country); soapHelper.SOAP_addRequestField("SerialNo", authInfo.serial); soapHelper.SOAP_addRequestField("TitleId", fmt::format("{:016X}", titleId)); if (!soapHelper.submitRequest()) { result.apiError = NAPI_RESULT::FAILED; return result; } // parse result pugi::xml_document doc; pugi::xml_node responseNode; if (!_parseResponseInit(soapHelper, "GetSystemCommonETicketResponse", responseNode, result, doc, responseNode)) return result; const char* eTicketsStr = responseNode.child_value("CommonETicket"); result.eTicket = NCrypto::base64Decode(eTicketsStr); if (result.eTicket.empty()) { cemuLog_log(LogType::Force, "GetSystemCommonETicketResponse: Invalid eTicket data in response"); result.apiError = NAPI_RESULT::DATA_ERROR; return result; } for (pugi::xml_node certNode : responseNode.children("Certs")) { const char* certStringValue = certNode.child_value(); auto certData = NCrypto::base64Decode(certStringValue); if (certData.empty()) { cemuLog_log(LogType::Force, "GetSystemCommonETicketResponse: Invalid cert data in response"); result.apiError = NAPI_RESULT::DATA_ERROR; return result; } result.certs.emplace_back(NCrypto::base64Decode(certStringValue)); } return result; } /* IAS */ NAPI_IASGetChallenge_Result IAS_GetChallenge(AuthInfo& authInfo) { NAPI_IASGetChallenge_Result result{}; CurlSOAPHelper soapHelper(authInfo.GetService()); soapHelper.SOAP_initate("ias", _getIASUrl(authInfo.GetService()), "GetChallenge", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); // not validated but the generated Challenge is bound to this DeviceId soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); soapHelper.SOAP_addRequestField("Country", authInfo.country); if (!soapHelper.submitRequest()) { result.apiError = NAPI_RESULT::FAILED; return result; } /* parse result */ pugi::xml_document doc; pugi::xml_node responseNode; if (!_parseResponseInit(soapHelper, "GetChallengeResponse", responseNode, result, doc, responseNode)) return result; result.challenge = responseNode.child_value("Challenge"); return result; } NAPI_IASGetRegistrationInfo_Result IAS_GetRegistrationInfo_QueryInfo(AuthInfo& authInfo, std::string challenge) { NAPI_IASGetRegistrationInfo_Result result; CurlSOAPHelper soapHelper(authInfo.GetService()); soapHelper.SOAP_initate("ias", _getIASUrl(authInfo.GetService()), "GetRegistrationInfo", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); // this must match the DeviceId used to generate Challenge soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); soapHelper.SOAP_addRequestField("Country", authInfo.country); // string to sign // differs depending on the call mode std::string signString; signString.reserve(1024); signString.append(fmt::format("<Challenge>{}</Challenge>", challenge)); soapHelper.SOAP_addRequestField("Challenge", challenge); uint32 signerTitleIdHigh = 0x00050010; uint32 signerTitleIdLow = 0x10040300; NCrypto::CertECC deviceCert; if (!deviceCert.decodeFromBase64(authInfo.deviceCertBase64)) { result.apiError = NAPI_RESULT::FAILED; return result; } NCrypto::CHash256 hash; NCrypto::GenerateHashSHA256(signString.data(), signString.size(), hash); NCrypto::CertECC certChain; NCrypto::ECCSig signature = NCrypto::signHash(signerTitleIdHigh, signerTitleIdLow, hash.b, sizeof(hash.b), certChain); auto certChainStr = certChain.encodeToBase64(); soapHelper.SOAP_addRequestField("Signature", signature.encodeToBase64()); soapHelper.SOAP_addRequestField("CertChain", certChainStr); soapHelper.SOAP_addRequestField("DeviceCert", deviceCert.encodeToBase64()); if (!soapHelper.submitRequest()) { result.apiError = NAPI_RESULT::FAILED; return result; } // parse result pugi::xml_document doc; pugi::xml_node responseNode; if (!_parseResponseInit(soapHelper, "GetRegistrationInfoResponse", responseNode, result, doc, responseNode)) return result; result.accountId = responseNode.child_value("AccountId"); result.deviceToken = responseNode.child_value("DeviceToken"); if (boost::iequals(responseNode.child_value("DeviceTokenExpired"), "true")) cemuLog_log(LogType::Force, "Unexpected server response: Device token expired"); /* example response: <Version>2.0</Version> <DeviceId>1234567</DeviceId> <MessageId>EC-1234-1234</MessageId> <TimeStamp>123456789</TimeStamp> <ErrorCode>0</ErrorCode> <ServiceStandbyMode>false/true</ServiceStandbyMode> <AccountId>123456</AccountId> <DeviceToken>DEVICE_TOKEN_STR</DeviceToken> <DeviceTokenExpired>false/true</DeviceTokenExpired> <Country>COUNTRYCODE</Country> <ExtAccountId></ExtAccountId> <DeviceStatus>R</DeviceStatus> <Currency>EUR</Currency> */ return result; } /* ECS */ std::string _getDeviceTokenWT(std::string_view deviceToken) { uint8 wtHash[16]; MD5_CTX md5Ctx; MD5_Init(&md5Ctx); MD5_Update(&md5Ctx, deviceToken.data(), (unsigned long)deviceToken.size()); MD5_Final(wtHash, &md5Ctx); std::string wtString; wtString.reserve(4 + 32); wtString.append("WT-"); for (uint8& b : wtHash) { wtString.append(fmt::format("{0:02x}", b)); } return wtString; } NAPI_ECSGetAccountStatus_Result ECS_GetAccountStatus(AuthInfo& authInfo) { NAPI_ECSGetAccountStatus_Result result{}; CurlSOAPHelper soapHelper(authInfo.GetService()); soapHelper.SOAP_initate("ecs", _getECSUrl(authInfo.GetService()), "GetAccountStatus", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); soapHelper.SOAP_addRequestField("Country", authInfo.country); if(!authInfo.IASToken.accountId.empty()) soapHelper.SOAP_addRequestField("AccountId", authInfo.IASToken.accountId); if (!authInfo.IASToken.deviceToken.empty()) soapHelper.SOAP_addRequestField("DeviceToken", _getDeviceTokenWT(authInfo.IASToken.deviceToken)); if (!soapHelper.submitRequest()) { result.apiError = NAPI_RESULT::FAILED; return result; } pugi::xml_document doc; pugi::xml_node responseNode; if (!_parseResponseInit(soapHelper, "GetAccountStatusResponse", responseNode, result, doc, responseNode)) return result; result.accountId = responseNode.child_value("AccountId"); const char* accountStatusStr = responseNode.child_value("AccountStatus"); if (accountStatusStr) { if (accountStatusStr[0] == 'R') result.accountStatus = NAPI_ECSGetAccountStatus_Result::AccountStatus::REGISTERED; else if (accountStatusStr[0] == 'T') result.accountStatus = NAPI_ECSGetAccountStatus_Result::AccountStatus::TRANSFERRED; else if (accountStatusStr[0] == 'U') result.accountStatus = NAPI_ECSGetAccountStatus_Result::AccountStatus::UNREGISTERED; else { cemuLog_log(LogType::Force, "ECS_GetAccountStatus: Account has unknown status code {}", accountStatusStr); } } // extract service URLs for (pugi::xml_node serviceURLNode : responseNode.children("ServiceURLs")) { std::string_view serviceType = serviceURLNode.child_value("Name"); std::string_view url = serviceURLNode.child_value("URI"); if(serviceType.empty() || url.empty()) continue; if (boost::iequals(serviceType, "ContentPrefixURL")) result.serviceURLs.ContentPrefixURL = url; else if (boost::iequals(serviceType, "UncachedContentPrefixURL")) result.serviceURLs.UncachedContentPrefixURL = url; else if (boost::iequals(serviceType, "SystemContentPrefixURL")) result.serviceURLs.SystemContentPrefixURL = url; else if (boost::iequals(serviceType, "SystemUncachedContentPrefixURL")) result.serviceURLs.SystemUncachedContentPrefixURL = url; else if (boost::iequals(serviceType, "EcsURL")) result.serviceURLs.EcsURL = url; else if (boost::iequals(serviceType, "IasURL")) result.serviceURLs.IasURL = url; else if (boost::iequals(serviceType, "CasURL")) result.serviceURLs.CasURL = url; else if (boost::iequals(serviceType, "NusURL")) result.serviceURLs.NusURL = url; else cemuLog_log(LogType::Force, "GetAccountStatus: Unknown service URI type {}", serviceType); } // assign service URLs auto& cachedServiceUrls = GetCachedServiceUrls(authInfo.GetService()); if (!result.serviceURLs.ContentPrefixURL.empty()) cachedServiceUrls.s_serviceURL_ContentPrefixURL = result.serviceURLs.ContentPrefixURL; if (!result.serviceURLs.UncachedContentPrefixURL.empty()) cachedServiceUrls.s_serviceURL_UncachedContentPrefixURL = result.serviceURLs.UncachedContentPrefixURL; if (!result.serviceURLs.IasURL.empty()) cachedServiceUrls.s_serviceURL_IasURL = result.serviceURLs.IasURL; if (!result.serviceURLs.CasURL.empty()) cachedServiceUrls.s_serviceURL_CasURL = result.serviceURLs.CasURL; if (!result.serviceURLs.NusURL.empty()) cachedServiceUrls.s_serviceURL_NusURL = result.serviceURLs.NusURL; if (!result.serviceURLs.EcsURL.empty()) cachedServiceUrls.s_serviceURL_EcsURL = result.serviceURLs.EcsURL; return result; } NAPI_ECSAccountListETicketIds_Result ECS_AccountListETicketIds(AuthInfo& authInfo) { NAPI_ECSAccountListETicketIds_Result result{}; CurlSOAPHelper soapHelper(authInfo.GetService()); soapHelper.SOAP_initate("ecs", _getECSUrl(authInfo.GetService()), "AccountListETicketIds", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); soapHelper.SOAP_addRequestField("Country", authInfo.country); if (!authInfo.IASToken.accountId.empty()) soapHelper.SOAP_addRequestField("AccountId", authInfo.IASToken.accountId); if (!authInfo.IASToken.deviceToken.empty()) soapHelper.SOAP_addRequestField("DeviceToken", _getDeviceTokenWT(authInfo.IASToken.deviceToken)); if (!soapHelper.submitRequest()) { result.apiError = NAPI_RESULT::FAILED; return result; } pugi::xml_document doc; pugi::xml_node responseNode; if (!_parseResponseInit(soapHelper, "AccountListETicketIdsResponse", responseNode, result, doc, responseNode)) return result; // extract ticket IVs for (pugi::xml_node tivNode : responseNode.children("TIV")) { // TIV is encoded as <ticketId>.<ticketVersion> // ticketVersion starts at 0 and increments every time the ticket is updated (e.g. for AOC content, when purchasing additional DLC packages) const char* tivValue = tivNode.child_value(); const char* tivValueEnd = tivValue + strlen(tivValue); const char* tivValueSeparator = std::strchr(tivValue, '.'); if (tivValueSeparator == nullptr) tivValueSeparator = tivValueEnd; // parse ticket id sint64 ticketId = 0; std::from_chars_result fcr = std::from_chars(tivValue, tivValueSeparator, ticketId); if (fcr.ec == std::errc::invalid_argument || fcr.ec == std::errc::result_out_of_range) { result.apiError = NAPI_RESULT::XML_ERROR; return result; } // parse ticket version uint32 ticketVersion = 0; fcr = std::from_chars(tivValueSeparator+1, tivValueEnd, ticketVersion); if (fcr.ec == std::errc::invalid_argument || fcr.ec == std::errc::result_out_of_range) { result.apiError = NAPI_RESULT::XML_ERROR; return result; } result.tivs.push_back({ ticketId , ticketVersion }); } return result; } NAPI_ECSAccountGetETickets_Result ECS_AccountGetETickets(AuthInfo& authInfo, sint64 ticketId) { NAPI_ECSAccountGetETickets_Result result{}; CurlSOAPHelper soapHelper(authInfo.GetService()); soapHelper.SOAP_initate("ecs", _getECSUrl(authInfo.GetService()), "AccountGetETickets", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); soapHelper.SOAP_addRequestField("Country", authInfo.country); if (!authInfo.IASToken.accountId.empty()) soapHelper.SOAP_addRequestField("AccountId", authInfo.IASToken.accountId); if (!authInfo.IASToken.deviceToken.empty()) soapHelper.SOAP_addRequestField("DeviceToken", _getDeviceTokenWT(authInfo.IASToken.deviceToken)); soapHelper.SOAP_addRequestField("DeviceCert", authInfo.deviceCertBase64); soapHelper.SOAP_addRequestField("TicketId", fmt::format("{}", ticketId)); if (!soapHelper.submitRequest()) { result.apiError = NAPI_RESULT::FAILED; return result; } // parse result pugi::xml_document doc; pugi::xml_node responseNode; if (!_parseResponseInit(soapHelper, "AccountGetETicketsResponse", responseNode, result, doc, responseNode)) return result; const char* eTicketsStr = responseNode.child_value("ETickets"); result.eTickets = NCrypto::base64Decode(eTicketsStr); if (result.eTickets.empty()) { cemuLog_log(LogType::Force, "AccountGetETickets: Invalid eTickets data in response"); result.apiError = NAPI_RESULT::DATA_ERROR; return result; } for (pugi::xml_node certNode : responseNode.children("Certs")) { const char* certStringValue = certNode.child_value(); auto certData = NCrypto::base64Decode(certStringValue); if (certData.empty()) { cemuLog_log(LogType::Force, "AccountGetETickets: Invalid cert data in response"); result.apiError = NAPI_RESULT::DATA_ERROR; return result; } result.certs.emplace_back(NCrypto::base64Decode(certStringValue)); } /* example response: <ETickets>BASE64_TIK?</ETickets> <Certs>BASE64_CERT</Certs> <Certs>BASE64_CERT</Certs> */ return result; } /* CCS (content server for raw files, does not use SOAP API) */ NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion) { NAPI_CCSGetTMD_Result result{}; CurlRequestHelper req; req.initate(authInfo.GetService(), fmt::format("{}/{:016x}/tmd.{}?deviceId={}&accountId={}", _getCCSUncachedUrl(authInfo.GetService()), titleId, titleVersion, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setTimeout(180); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request TMD for title {0:016X} v{1}", titleId, titleVersion)); return result; } result.tmdData = req.getReceivedData(); result.isValid = true; return result; } NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId) { NAPI_CCSGetTMD_Result result{}; CurlRequestHelper req; req.initate(authInfo.GetService(), fmt::format("{}/{:016x}/tmd?deviceId={}&accountId={}", _getCCSUncachedUrl(authInfo.GetService()), titleId, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setTimeout(180); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request TMD for title {0:016X}", titleId)); return result; } result.tmdData = req.getReceivedData(); result.isValid = true; return result; } NAPI_CCSGetETicket_Result CCS_GetCETK(NetworkService service, uint64 titleId, uint16 titleVersion) { NAPI_CCSGetETicket_Result result{}; CurlRequestHelper req; req.initate(service, fmt::format("{}/{:016x}/cetk", _getCCSUncachedUrl(service), titleId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setTimeout(180); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request eTicket for title {0:016X} v{1}", titleId, titleVersion)); return result; } result.cetkData = req.getReceivedData(); result.isValid = true; return result; } bool CCS_GetContentFile(NetworkService service, uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData) { CurlRequestHelper req; req.initate(service, fmt::format("{}/{:016x}/{:08x}", _getCCSUrl(service), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setWriteCallback(cbWriteCallback, userData); req.setTimeout(0); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request content file {:08x} for title {:016X}", contentId, titleId)); return false; } return true; } NAPI_CCSGetContentH3_Result CCS_GetContentH3File(NetworkService service, uint64 titleId, uint32 contentId) { NAPI_CCSGetContentH3_Result result{}; CurlRequestHelper req; req.initate(service, fmt::format("{}/{:016x}/{:08x}.h3", _getCCSUrl(service), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request content hash file {:08x}.h3 for title {:016X}", contentId, titleId)); return result; } result.tmdData = req.getReceivedData(); result.isValid = true; return result; } }; ================================================ FILE: src/Cemu/napi/napi_helper.cpp ================================================ #include "Common/precompiled.h" #include "napi.h" #include "curl/curl.h" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cemu/ncrypto/ncrypto.h" #include "napi_helper.h" #include "util/highresolutiontimer/HighResolutionTimer.h" #include "config/ActiveSettings.h" #include "config/NetworkSettings.h" #include "config/LaunchSettings.h" #include "pugixml.hpp" #include <charconv> #include"openssl/bn.h" #include"openssl/x509.h" #include"openssl/ssl.h" CURLcode _sslctx_function_NUS(CURL* curl, void* sslctx, void* param) { if (iosuCrypto_addCACertificate(sslctx, 102) == false) { cemuLog_log(LogType::Force, "Invalid CA certificate (102)"); } if (iosuCrypto_addCACertificate(sslctx, 105) == false) { cemuLog_log(LogType::Force, "Invalid CA certificate (105)"); } if (iosuCrypto_addClientCertificate(sslctx, 3) == false) { cemuLog_log(LogType::Force, "Certificate error"); } SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); return CURLE_OK; } CURLcode _sslctx_function_IDBE(CURL* curl, void* sslctx, void* param) { if (iosuCrypto_addCACertificate(sslctx, 105) == false) { cemuLog_log(LogType::Force, "Invalid CA certificate (105)"); } SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); return CURLE_OK; } CURLcode _sslctx_function_SOAP(CURL* curl, void* sslctx, void* param) { if (iosuCrypto_addCACertificate(sslctx, 102) == false) { cemuLog_log(LogType::Force, "Invalid CA certificate (102)"); cemuLog_log(LogType::Force, "Certificate error"); } if (iosuCrypto_addClientCertificate(sslctx, 1) == false) { cemuLog_log(LogType::Force, "Certificate error"); } SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); return CURLE_OK; } CURLcode _sslctx_function_OLIVE(CURL* curl, void* sslctx, void* param) { if (iosuCrypto_addCACertificate(sslctx, 105) == false) { cemuLog_log(LogType::Force, "Invalid CA certificate (105)"); cemuLog_log(LogType::Force, "Certificate error"); } if (iosuCrypto_addClientCertificate(sslctx, 7) == false) { cemuLog_log(LogType::Force, "Olive client certificate error"); } { std::vector<sint16> certGroups = { 100, 101, 102, 103, 104, 105, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033 }; for (auto& certId : certGroups) iosuCrypto_addCACertificate(sslctx, certId); } SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); return CURLE_OK; } CURLcode _sslctx_function_CUSTOM(CURL* curl, void* sslctx, void* param) { CurlRequestHelper* requestHelper = (CurlRequestHelper*)param; for (auto& caCertId : requestHelper->GetCaCertIds()) { if (iosuCrypto_addCACertificate(sslctx, caCertId) == false) { cemuLog_log(LogType::Force, "Invalid CA certificate ({})", caCertId); } } for (auto& clientCertId : requestHelper->GetClientCertIds()) { if (iosuCrypto_addCACertificate(sslctx, clientCertId) == false) { cemuLog_log(LogType::Force, "Invalid client certificate ({})", clientCertId); } } SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); if (requestHelper->GetClientCertIds().empty()) SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_NONE, nullptr); else SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); return CURLE_OK; } CurlRequestHelper::CurlRequestHelper() { m_curl = curl_easy_init(); curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, __curlWriteCallback); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(m_curl, CURLOPT_MAXREDIRS, 2); curl_easy_setopt(m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); if(GetConfig().proxy_server.GetValue() != "") { curl_easy_setopt(m_curl, CURLOPT_PROXY, GetConfig().proxy_server.GetValue().c_str()); } } CurlRequestHelper::~CurlRequestHelper() { curl_easy_cleanup(m_curl); } void CurlRequestHelper::initate(NetworkService service, std::string url, SERVER_SSL_CONTEXT sslContext) { // reset parameters m_headerExtraFields.clear(); m_postData.clear(); m_cbWriteCallback = nullptr; curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(m_curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT); curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, 60); // SSL curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L); if (IsNetworkServiceSSLDisabled(service)) { curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); } else if (sslContext == SERVER_SSL_CONTEXT::ACT || sslContext == SERVER_SSL_CONTEXT::TAGAYA) { curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_NUS); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); } else if (sslContext == SERVER_SSL_CONTEXT::IDBE) { curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_IDBE); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); } else if (sslContext == SERVER_SSL_CONTEXT::IAS || sslContext == SERVER_SSL_CONTEXT::ECS) { curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); } else if (sslContext == SERVER_SSL_CONTEXT::CCS) { curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); } else if (sslContext == SERVER_SSL_CONTEXT::OLIVE) { curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_OLIVE); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); } else if (sslContext == SERVER_SSL_CONTEXT::CUSTOM) { curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_CUSTOM); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, this); } else { cemu_assert(false); } } void CurlRequestHelper::addHeaderField(const char* fieldName, std::string_view value) { m_headerExtraFields.emplace_back(fieldName, value); } void CurlRequestHelper::addPostField(const char* fieldName, std::string_view value) { if (!m_postData.empty()) m_postData.emplace_back('&'); m_postData.insert(m_postData.end(), (uint8*)fieldName, (uint8*)fieldName + strlen(fieldName)); m_postData.emplace_back('='); m_postData.insert(m_postData.end(), (uint8*)value.data(), (uint8*)value.data() + value.size()); } void CurlRequestHelper::setWriteCallback(bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData) { m_cbWriteCallback = cbWriteCallback; m_writeCallbackUserData = userData; } void CurlRequestHelper::setTimeout(sint32 time) { curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, time); } size_t CurlRequestHelper::__curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) { size_t writeByteSize = (size_t)(size * nmemb); CurlRequestHelper* curlHelper = (CurlRequestHelper*)userdata; if (curlHelper->m_cbWriteCallback) { if (!curlHelper->m_cbWriteCallback(curlHelper->m_writeCallbackUserData, ptr, writeByteSize, false)) return 0; return writeByteSize; } curlHelper->m_receiveBuffer.insert(curlHelper->m_receiveBuffer.end(), ptr, ptr + writeByteSize); return writeByteSize; } bool CurlRequestHelper::submitRequest(bool isPost) { // HTTP headers struct curl_slist* headers = nullptr; for (auto& itr : m_headerExtraFields) headers = curl_slist_append(headers, itr.data.c_str()); curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, headers); // post if (isPost) { if (!m_isUsingMultipartFormData) { curl_easy_setopt(m_curl, CURLOPT_POST, 1); curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_postData.data()); curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_postData.size()); } } else curl_easy_setopt(m_curl, CURLOPT_POST, 0); // submit int res = curl_easy_perform(m_curl); if (res != CURLE_OK) { cemuLog_log(LogType::Force, "CURL web request failed with error {}. Retrying...", res); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // retry res = curl_easy_perform(m_curl); if (res != CURLE_OK) return false; } // check response code long httpCode = 0; curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &httpCode); m_httpStatusCode = httpCode; if (httpCode != 200) { cemuLog_log(LogType::Force, "HTTP request received response {} but expected 200", httpCode); char* effectiveUrl = nullptr; curl_easy_getinfo(m_curl, CURLINFO_EFFECTIVE_URL, &effectiveUrl); if (effectiveUrl) cemuLog_log(LogType::Force, "Request: {}", effectiveUrl); // error status codes (4xx or 5xx range) are always considered a failed request, except for code 400 which is usually returned as a response to failed logins etc. if (httpCode >= 400 && httpCode <= 599 && httpCode != 400) return false; // for other status codes we assume success if the message is non-empty if(m_receiveBuffer.empty()) return false; } if (m_cbWriteCallback) m_cbWriteCallback(m_writeCallbackUserData, nullptr, 0, true); // flush write return true; } CurlSOAPHelper::CurlSOAPHelper(NetworkService service) { m_curl = curl_easy_init(); curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, __curlWriteCallback); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); curl_easy_setopt(m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); // SSL if (!IsNetworkServiceSSLDisabled(service)) { curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L); } else { curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); } if (GetConfig().proxy_server.GetValue() != "") { curl_easy_setopt(m_curl, CURLOPT_PROXY, GetConfig().proxy_server.GetValue().c_str()); } } CurlSOAPHelper::~CurlSOAPHelper() { curl_easy_cleanup(m_curl); } void CurlSOAPHelper::SOAP_initate(std::string_view serviceType, std::string url, std::string_view requestMethod, std::string_view requestVersion) { curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str()); m_serviceType = serviceType; m_requestMethod = requestMethod; m_requestVersion = requestVersion; m_envelopeExtraParam.reserve(512); m_envelopeExtraParam.clear(); } void CurlSOAPHelper::SOAP_addRequestField(const char* fieldName, std::string_view value) { m_envelopeExtraParam.append(fmt::format("<{}:{}>{}</{}:{}>", m_serviceType, fieldName, value, m_serviceType, fieldName)); } void CurlSOAPHelper::SOAP_generateEnvelope() { m_envelopeStr.reserve(4096); m_envelopeStr.clear(); m_envelopeStr.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); m_envelopeStr.append("<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"\n"); m_envelopeStr.append(" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\"\n"); m_envelopeStr.append(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"); m_envelopeStr.append(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"); m_envelopeStr.append(fmt::format(" xmlns:{}=\"urn:{}.wsapi.broadon.com\">\n", m_serviceType, m_serviceType)); m_envelopeStr.append("<SOAP-ENV:Body>\n"); m_envelopeStr.append(fmt::format("<{}:{} xsi:type=\"{}:{}RequestType\">\n", m_serviceType, m_requestMethod, m_serviceType, m_requestMethod)); m_envelopeStr.append(fmt::format("<{}:Version>{}</{}:Version>\n", m_serviceType, m_requestVersion, m_serviceType)); // the server echos the message id static uint64 s_msgHigh = 1 + (uint64)HighResolutionTimer::now().getTick()/7; uint64 msgId_high = s_msgHigh; // usually this is set to the deviceId uint64 msgId_low = (uint64)HighResolutionTimer::now().getTick(); // uptime m_envelopeStr.append(fmt::format("<{}:MessageId>EC-{}-{}</{}:MessageId>", m_serviceType, msgId_high, msgId_low, m_serviceType)); m_envelopeStr.append(m_envelopeExtraParam); // some fields are specific to services? // ECS doesnt seem to like RegionId and CountryCode // following fields are shared: // >>> Region, Country, Language // following fields are present when NUS: // >>> RegionId (instead of Region), CountryCode (instead of Country) // following fields are present when CAS or ECS: // >>> ApplicationId, TIN, SerialNo // following fields are present when CAS: // >>> Age // following fields are present when ECS: // >>> SessionHandle, ServiceTicket, ServiceId // following fields for anything that isn't BGS: // >>> DeviceId (the serial) // DeviceId -> All except BGS (deviceId is the console serial) // DeviceToken -> Everything except BGS and NUS // ECS: //m_envelopeStr.append(fmt::format("<{}:Region>EUR</{}:Region>", serviceType, serviceType)); //m_envelopeStr.append(fmt::format("<{}:Country>AT</{}:Country>", serviceType, serviceType)); // device token format: // <ECS:DeviceToken>WT-<md5hash_in_hex></ECS:DeviceToken> // unknown fields: // VirtualDeviceType (shared but optional?) // device cert not needed for ECS:GetAccountStatus ? (it complains if present) //char deviceCertStr[1024 * 4]; //iosuCrypto_getDeviceCertificateBase64Encoded(deviceCertStr); //m_envelopeStr.append(fmt::format("<{}:DeviceCert>{}</{}:DeviceCert>", serviceType, deviceCertStr, serviceType)); // only device token needed // DeviceToken comes from GetRegistrationInfo and is then stored in ec_account_info.exi m_envelopeStr.append(fmt::format("</{}:{}>\n", m_serviceType, m_requestMethod)); m_envelopeStr.append("</SOAP-ENV:Body>\n"); m_envelopeStr.append("</SOAP-ENV:Envelope>\n"); } sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output); bool CurlSOAPHelper::submitRequest() { // generate and set envelope SOAP_generateEnvelope(); curl_easy_setopt(m_curl, CURLOPT_POST, 1); curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_envelopeStr.c_str()); curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_envelopeStr.size()); // generate and set headers struct curl_slist* headers = NULL; headers = curl_slist_append(headers, "Content-Type: text/xml; charset=utf-8"); headers = curl_slist_append(headers, "Accept-Charset: UTF-8"); headers = curl_slist_append(headers, fmt::format("SOAPAction: urn:{}.wsapi.broadon.com/{}", m_serviceType, m_requestMethod).c_str()); headers = curl_slist_append(headers, "Accept: */*"); curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(m_curl, CURLOPT_USERAGENT, "EVL NUP 040800 Sep 18 2012 20:20:02"); // send request auto res = curl_easy_perform(m_curl); return res == CURLE_OK; } /* helper functions */ namespace NAPI { bool _findXmlNode(pugi::xml_node& doc, pugi::xml_node& nodeOut, const char* name) { for (auto& itr : doc.children()) { if (boost::iequals(itr.name(), name)) { nodeOut = itr; return true; } if (_findXmlNode(itr, nodeOut, name)) return true; } return false; } bool _parseResponseInit(const CurlSOAPHelper& soapHelper, const char* responseNodeName, pugi::xml_node& node, _NAPI_CommonResultSOAP& result, pugi::xml_document& doc, pugi::xml_node& responseNode) { // parse XML response if (!doc.load_buffer(soapHelper.getReceivedData().data(), soapHelper.getReceivedData().size())) { cemuLog_log(LogType::Force, "Failed to parse GetRegistrationInfo() response"); result.apiError = NAPI_RESULT::XML_ERROR; return false; } if (!_findXmlNode(doc, node, responseNodeName)) { result.apiError = NAPI_RESULT::XML_ERROR; return false; } // parse error code auto errorCodeStr = node.child_value("ErrorCode"); if (!errorCodeStr) { result.apiError = NAPI_RESULT::XML_ERROR; return false; } int parsedErrorCode = 0; std::from_chars_result fcr = std::from_chars(errorCodeStr, errorCodeStr + strlen(errorCodeStr), parsedErrorCode); if (fcr.ec == std::errc::invalid_argument || fcr.ec == std::errc::result_out_of_range) { result.apiError = NAPI_RESULT::XML_ERROR; return false; } if (parsedErrorCode != 0) { result.serviceError = (EC_ERROR_CODE)parsedErrorCode; result.apiError = NAPI_RESULT::SERVICE_ERROR; return false; } result.apiError = NAPI_RESULT::SUCCESS; return true; } }; ================================================ FILE: src/Cemu/napi/napi_helper.h ================================================ #pragma once #include "napi.h" #include "curl/curl.h" #include "pugixml.hpp" typedef void CURL; class CurlRequestHelper { struct HeaderExtraField { HeaderExtraField(std::string_view name, std::string_view value) { data.assign(name); data.append(": "); data.append(value); }; std::string data; }; public: enum class SERVER_SSL_CONTEXT { ACT, // account.nintendo.net ECS, // ecs. IAS, // ias. CCS, // ccs. IDBE, // idbe-wup. TAGAYA, // tagaya.wup.shop.nintendo.net OLIVE, // olv. CUSTOM, // use cert parameters }; CurlRequestHelper(); ~CurlRequestHelper(); CURL* getCURL() { return m_curl; } void initate(NetworkService service, std::string url, SERVER_SSL_CONTEXT sslContext); void addHeaderField(const char* fieldName, std::string_view value); void addPostField(const char* fieldName, std::string_view value); void setWriteCallback(bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData); void setTimeout(sint32 timeoutSeconds); // maximum duration of the request after connecting. Set to zero to disable limit bool submitRequest(bool isPost = false); std::vector<uint8>& getReceivedData() { return m_receiveBuffer; } void setUseMultipartFormData(bool isUsingMultipartFormData) { m_isUsingMultipartFormData = isUsingMultipartFormData; } void ClearCaCertIds() { m_caCertIds.clear(); } void ClearClientCertIds() { m_clientCertIds.clear(); } void AddCaCertId(sint32 caCertId) { m_caCertIds.emplace_back(caCertId); } void AddClientCertId(sint32 clientCertId) { m_clientCertIds.emplace_back(clientCertId); } std::vector<sint32> GetCaCertIds() const { return m_caCertIds; } std::vector<sint32> GetClientCertIds() const { return m_clientCertIds; } sint32 GetHTTPStatusCode() const { return m_httpStatusCode; } private: static size_t __curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata); CURL* m_curl; std::vector<uint8> m_receiveBuffer; // input parameters std::vector<HeaderExtraField> m_headerExtraFields; std::vector<uint8> m_postData; // write callback redirect bool (*m_cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast); void* m_writeCallbackUserData{}; bool m_isUsingMultipartFormData = false; // cert data std::vector<sint32> m_caCertIds; std::vector<sint32> m_clientCertIds; // result sint32 m_httpStatusCode{ 0 }; }; class CurlSOAPHelper // todo - make this use CurlRequestHelper { public: CurlSOAPHelper(NetworkService service); ~CurlSOAPHelper(); CURL* getCURL() { return m_curl; } void SOAP_initate(std::string_view serviceType, std::string url, std::string_view requestMethod, std::string_view requestVersion); void SOAP_addRequestField(const char* fieldName, std::string_view value); bool submitRequest(); const std::vector<uint8>& getReceivedData() const { return m_receiveBuffer; } private: void SOAP_generateEnvelope(); static size_t __curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) { CurlSOAPHelper* curlHelper = (CurlSOAPHelper*)userdata; sint32 writeByteSize = (sint32)(size * nmemb); curlHelper->m_receiveBuffer.insert(curlHelper->m_receiveBuffer.end(), ptr, ptr + writeByteSize); return writeByteSize; } CURL* m_curl; std::vector<uint8> m_receiveBuffer; // input parameters std::string m_serviceType; std::string m_requestMethod; std::string m_requestVersion; // generated / internal std::string m_envelopeStr; std::string m_envelopeExtraParam; }; namespace NAPI { bool _findXmlNode(pugi::xml_node& doc, pugi::xml_node& nodeOut, const char* name); bool _parseResponseInit(const CurlSOAPHelper& soapHelper, const char* responseNodeName, pugi::xml_node& node, _NAPI_CommonResultSOAP& result, pugi::xml_document& doc, pugi::xml_node& responseNode); }; ================================================ FILE: src/Cemu/napi/napi_idbe.cpp ================================================ #include "Common/precompiled.h" #include "napi.h" #include "napi_helper.h" #include "curl/curl.h" #include "pugixml.hpp" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cemu/ncrypto/ncrypto.h" #include "openssl/sha.h" #include "util/crypto/aes128.h" #include "util/helpers/StringHelpers.h" #include "config/ActiveSettings.h" #include "config/NetworkSettings.h" namespace NAPI { std::string IDBEIconDataV0::LanguageInfo::GetGameNameUTF8() { return StringHelpers::ToUtf8(gameName); } std::string IDBEIconDataV0::LanguageInfo::GetGameLongNameUTF8() { return StringHelpers::ToUtf8(gameLongName); } std::string IDBEIconDataV0::LanguageInfo::GetPublisherNameUTF8() { return StringHelpers::ToUtf8(publisherName); } void _decryptIDBEAndHash(IDBEIconDataV0* iconData, uint8* hash, uint8 keyIndex) { static uint8 idbeAesKeys[4 * 16] = { 0x4A,0xB9,0xA4,0x0E,0x14,0x69,0x75,0xA8,0x4B,0xB1,0xB4,0xF3,0xEC,0xEF,0xC4,0x7B, 0x90,0xA0,0xBB,0x1E,0x0E,0x86,0x4A,0xE8,0x7D,0x13,0xA6,0xA0,0x3D,0x28,0xC9,0xB8, 0xFF,0xBB,0x57,0xC1,0x4E,0x98,0xEC,0x69,0x75,0xB3,0x84,0xFC,0xF4,0x07,0x86,0xB5, 0x80,0x92,0x37,0x99,0xB4,0x1F,0x36,0xA6,0xA7,0x5F,0xB8,0xB4,0x8C,0x95,0xF6,0x6F }; static uint8 idbeAesIv[16] = { 0xA4,0x69,0x87,0xAE,0x47,0xD8,0x2B,0xB4,0xFA,0x8A,0xBC,0x04,0x50,0x28,0x5F,0xA4 }; const uint8* aesKey = idbeAesKeys + 16 * keyIndex; uint8 iv[16]; memcpy(iv, hash + 16, sizeof(iv)); AES128_CBC_decrypt(hash, hash, 32, aesKey, idbeAesIv); AES128_CBC_decrypt((uint8*)iconData, (uint8*)iconData, sizeof(IDBEIconDataV0), aesKey, iv); } std::vector<uint8> IDBE_RequestRawEncrypted(NetworkService networkService, uint64 titleId) { CurlRequestHelper req; std::string requestUrl; switch (networkService) { case NetworkService::Pretendo: requestUrl = PretendoURLs::IDBEURL; break; case NetworkService::Custom: requestUrl = GetNetworkConfig().urls.IDBE.GetValue(); break; case NetworkService::Nintendo: default: requestUrl = NintendoURLs::IDBEURL; break; } requestUrl.append(fmt::format(fmt::runtime("/{0:02X}/{1:016X}.idbe"), (uint32)((titleId >> 8) & 0xFF), titleId)); req.initate(networkService, requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::IDBE); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request IDBE icon for title {0:016X}", titleId)); return {}; } /* format: +0x00 uint8 version (0) +0x01 uint8 keyIndex +0x02 uint8[32] hashSHA256 +0x22 uint8[] EncryptedIconData */ auto& receivedData = req.getReceivedData(); return receivedData; } std::optional<IDBEIconDataV0> IDBE_Request(NetworkService networkService, uint64 titleId) { if (titleId == 0x000500301001500A || titleId == 0x000500301001510A || titleId == 0x000500301001520A) { // friend list has no icon, just fail immediately cemuLog_logDebug(LogType::Force, "Requesting IDBE for Friend List. Return none instead"); return std::nullopt; } std::vector<uint8> idbeData = IDBE_RequestRawEncrypted(networkService, titleId); if (idbeData.size() < 0x22) return std::nullopt; if (idbeData[0] != 0) { cemuLog_log(LogType::Force, "IDBE_Request: File has invalid version"); return std::nullopt; } uint8 keyIndex = idbeData[1]; if (keyIndex >= 4) { cemuLog_log(LogType::Force, "IDBE_Request: Key index out of range"); return std::nullopt; } if (idbeData.size() < (0x22 + sizeof(IDBEIconDataV0))) { cemuLog_log(LogType::Force, "IDBE_Request: File size does not match"); return std::nullopt; } // extract hash and encrypted icon data uint8 hash[32]; std::memcpy(hash, idbeData.data() + 0x2, 32); IDBEIconDataV0 iconDataV0; std::memcpy(&iconDataV0, idbeData.data() + 0x22, sizeof(IDBEIconDataV0)); // decrypt icon data and hash _decryptIDBEAndHash(&iconDataV0, hash, keyIndex); // verify hash of decrypted data uint8 calcHash[SHA256_DIGEST_LENGTH]; SHA256((const unsigned char*) &iconDataV0, sizeof(IDBEIconDataV0), calcHash); if (std::memcmp(calcHash, hash, SHA256_DIGEST_LENGTH) != 0) { cemuLog_log(LogType::Force, "IDBE_Request: Hash mismatch"); return std::nullopt; } return std::optional(iconDataV0); } }; ================================================ FILE: src/Cemu/napi/napi_version.cpp ================================================ #include "Common/precompiled.h" #include "napi.h" #include "napi_helper.h" #include "curl/curl.h" #include "pugixml.hpp" #include "Cemu/ncrypto/ncrypto.h" #include <charconv> #include "config/ActiveSettings.h" #include "config/NetworkSettings.h" namespace NAPI { NAPI_VersionListVersion_Result TAG_GetVersionListVersion(AuthInfo& authInfo) { NAPI_VersionListVersion_Result result; CurlRequestHelper req; std::string requestUrl; switch (authInfo.GetService()) { case NetworkService::Pretendo: requestUrl = PretendoURLs::TAGAYAURL; break; case NetworkService::Custom: requestUrl = GetNetworkConfig().urls.TAGAYA.GetValue(); break; case NetworkService::Nintendo: default: requestUrl = NintendoURLs::TAGAYAURL; break; } requestUrl.append(fmt::format(fmt::runtime("/{}/{}/latest_version"), NCrypto::GetRegionAsString(authInfo.region), authInfo.country.empty() ? "NN" : authInfo.country)); req.initate(authInfo.GetService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request version of update list")); return result; } auto& receivedData = req.getReceivedData(); pugi::xml_document doc; if (!doc.load_buffer(receivedData.data(), receivedData.size())) { cemuLog_log(LogType::Force, "Failed to parse title list XML"); return result; } if (!doc.child("version_list_info").child("version") || !doc.child("version_list_info").child("fqdn")) { cemuLog_log(LogType::Force, "Title list XML has missing field"); return result; } result.version = atoi(doc.child("version_list_info").child("version").child_value()); result.fqdnURL = doc.child("version_list_info").child("fqdn").child_value(); result.isValid = true; return result; } NAPI_VersionList_Result TAG_GetVersionList(AuthInfo& authInfo, std::string_view fqdnURL, uint32 versionListVersion) { NAPI_VersionList_Result result; CurlRequestHelper req; req.initate(authInfo.GetService(), fmt::format("https://{}/tagaya/versionlist/{}/{}/list/{}.versionlist", fqdnURL, NCrypto::GetRegionAsString(authInfo.region), authInfo.country.empty() ? "NN" : authInfo.country, versionListVersion), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request update list")); return result; } auto& receivedData = req.getReceivedData(); pugi::xml_document doc; if (!doc.load_buffer(receivedData.data(), receivedData.size())) { cemuLog_log(LogType::Force, "Failed to parse update list XML"); return result; } // example: // <?xml version="1.0" encoding="UTF-8" standalone="yes"?> // <version_list format_version="1.0"><version>1615</version> // <titles> // <title><id>0005000E10100600</id><version>16</version> // <id>0005000E10101B00</id><version>16</version> // ... for (pugi::xml_node title : doc.child("version_list").child("titles").children("title")) { uint64 titleId = 0; uint32 titleVersion = 0; std::string_view str = title.child_value("id"); if (const auto res = std::from_chars(str.data(), str.data() + str.size(), titleId, 16); res.ec != std::errc()) continue; str = title.child_value("version"); if (const auto res = std::from_chars(str.data(), str.data() + str.size(), titleVersion, 10); res.ec != std::errc()) continue; result.titleVersionList.emplace(titleId, titleVersion); } result.isValid = true; return result; } }; ================================================ FILE: src/Cemu/ncrypto/ncrypto.cpp ================================================ #include "Common/precompiled.h" #include "Cemu/ncrypto/ncrypto.h" #include "util/helpers/helpers.h" #include "openssl/bn.h" #include "openssl/ec.h" #include "openssl/x509.h" #include "openssl/ssl.h" #include "openssl/sha.h" #include "openssl/ecdsa.h" #include "util/crypto/aes128.h" void iosuCrypto_getDeviceCertificate(void* certOut, sint32 len); void iosuCrypto_getDeviceCertPrivateKey(void* privKeyOut, sint32 len); bool iosuCrypto_getDeviceId(uint32* deviceId); void iosuCrypto_getDeviceSerialString(char* serialString); void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size); void iosuCrypto_readSeepromData(void* output, sint32 wordIndex, sint32 size); extern bool hasSeepromMem; // remove later (migrate otp/seeprom loading & parsing to this class) extern bool hasOtpMem; // remove later namespace NCrypto { std::string base64Encode(const void* inputMem, size_t inputLen) { const uint8* input = (const uint8*)inputMem; static const char* base64_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; std::string strBase64; strBase64.resize((inputLen * 4) / 3 + 16); int i = 0; int j = 0; unsigned char charArray_3[3]; unsigned char charArray_4[4]; sint32 outputLength = 0; while (inputLen--) { charArray_3[i++] = *(input++); if (i == 3) { charArray_4[0] = (charArray_3[0] & 0xfc) >> 2; charArray_4[1] = ((charArray_3[0] & 0x03) << 4) + ((charArray_3[1] & 0xf0) >> 4); charArray_4[2] = ((charArray_3[1] & 0x0f) << 2) + ((charArray_3[2] & 0xc0) >> 6); charArray_4[3] = charArray_3[2] & 0x3f; for (i = 0; (i < 4); i++) { strBase64[outputLength] = base64_charset[charArray_4[i]]; outputLength++; } i = 0; } } if (i) { for (j = i; j < 3; j++) charArray_3[j] = '\0'; charArray_4[0] = (charArray_3[0] & 0xfc) >> 2; charArray_4[1] = ((charArray_3[0] & 0x03) << 4) + ((charArray_3[1] & 0xf0) >> 4); charArray_4[2] = ((charArray_3[1] & 0x0f) << 2) + ((charArray_3[2] & 0xc0) >> 6); charArray_4[3] = charArray_3[2] & 0x3f; for (j = 0; j < (i + 1); j++) { strBase64[outputLength] = base64_charset[charArray_4[j]]; outputLength++; } while (i++ < 3) { strBase64[outputLength] = '='; outputLength++; } } cemu_assert(outputLength <= strBase64.size()); strBase64.resize(outputLength); return strBase64; } std::vector base64Decode(std::string_view inputStr) { static constexpr unsigned char kDecodingTable[] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 }; size_t in_len = inputStr.size(); if (in_len <= 3 || (in_len & 3) != 0) return std::vector(); // invalid length std::vector output; size_t out_len = in_len / 4 * 3; if (inputStr[in_len - 1] == '=') out_len--; if (inputStr[in_len - 2] == '=') out_len--; output.resize(out_len); for (size_t i = 0, j = 0; i < in_len;) { uint32 a = inputStr[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(inputStr[i++])]; uint32 b = inputStr[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(inputStr[i++])]; uint32 c = inputStr[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(inputStr[i++])]; uint32 d = inputStr[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(inputStr[i++])]; uint32 triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6); if (j < out_len) output[j++] = (triple >> 2 * 8) & 0xFF; if (j < out_len) output[j++] = (triple >> 1 * 8) & 0xFF; if (j < out_len) output[j++] = (triple >> 0 * 8) & 0xFF; } return output; } void base64Tests() { std::vector random; for (sint32 i = 0; i < 100; i++) { random.resize(0 + i); for (size_t x = 0; x < random.size(); x++) random[x] = (uint8)(i * 21 + x * 133); std::string b64 = base64Encode(random.data(), random.size()); std::vector dec = base64Decode(b64); cemu_assert(random == dec); } } /* Hashing */ void GenerateHashSHA1(const void* data, size_t len, CHash160& hashOut) { SHA1((const unsigned char*) data, len, hashOut.b); } void GenerateHashSHA256(const void* data, size_t len, CHash256& hashOut) { SHA256((const unsigned char*) data, len, hashOut.b); } /* Ticket */ struct ETicketFileHeaderWiiU { /* Ticket version 0: SHA1 hash Ticket version 1: SHA256 hash + has item rights */ /* +0x000 */ uint32be signatureType; /* +0x004 */ uint8 sig[0x100]; /* +0x104 */ uint8 _ukn104[0x180 - 0x104]; /* +0x180 */ ECCPubKey publicKey; /* +0x1BC */ uint8 ticketFormatVersion; /* +0x1BD */ uint8 _ukn1BD; /* +0x1BE */ uint8 _ukn1BE; /* +0x1BF */ uint8 encryptedTitleKey[16]; /* +0x1CF */ uint8 _ukn1CF; // probably padding /* +0x1D0 */ uint32be ticketIdHigh; /* +0x1D4 */ uint32be ticketIdLow; /* +0x1D8 */ uint32be deviceId; // ticket personalized to this deviceId. Zero if not personalized /* +0x1DC */ uint32be titleIdHigh; /* +0x1E0 */ uint32be titleIdLow; /* +0x1E4 */ uint16be ukn1E4; /* +0x1E6 */ uint16be titleVersion; // also used as ticket version (for AOC content)? /* +0x1E8 */ uint8 _ukn1E8[0x21C - 0x1E8]; /* +0x21C */ uint32be accountId; /* V1 extension header starts at +0x2A4 */ }; struct ETicketFileHeaderExtV1 { /* starts at +0x2A4 */ /* +0x000 */ uint16be headerVersion; /* +0x002 */ uint16be headerSize; /* +0x004 */ uint32be ukn008; /* +0x008 */ uint32be sectionTableOffset; /* +0x00C */ uint16be sectionTableNumEntries; /* +0x00E */ uint16be sectionTableEntrySize; /**/ }; struct ETicketFileHeaderExtV1SectionHeader { enum { SECTION_TYPE_CONTENT_RIGHTS = 3, // content rights }; /* +0x00 */ uint32be sectionOffset; /* +0x04 */ uint32be entryCount; /* +0x08 */ uint32be entrySize; /* +0x0C */ uint32be sectionSize; /* +0x10 */ uint16be type; /* +0x12 */ uint16be ukn00; }; static_assert(sizeof(ETicketFileHeaderWiiU) == 0x220); struct ETicketV1_ContentRights { uint32be baseIndex; // first index for bitmask? uint8be rightBitmask[0x80]; uint32 GetRightsCount() const { return sizeof(rightBitmask) * 8; } bool GetRight(uint32 index) const { cemu_assert_debug(index < GetRightsCount()); if (index >= GetRightsCount()) return false; return ((rightBitmask[(index/8)]>>(index & 7)) & 1) != 0; } }; static_assert(sizeof(ETicketV1_ContentRights) == 0x84); bool ETicketParser::parse(const uint8* data, size_t size) { auto readStruct = [&](uint32 offset, uint32 readSize) -> void* { if ((offset + readSize) > size) return nullptr; return (void*)((const uint8*)data + offset); }; if (size < sizeof(ETicketFileHeaderWiiU)) return false; ETicketFileHeaderWiiU* header = (ETicketFileHeaderWiiU*)readStruct(0, sizeof(ETicketFileHeaderWiiU)); if (!header) return false; m_titleId = MakeU64(header->titleIdHigh, header->titleIdLow); m_ticketId = MakeU64(header->ticketIdHigh, header->ticketIdLow); m_ticketFormatVersion = header->ticketFormatVersion; m_titleVersion = header->titleVersion; uint32 titleIdHigh = (m_titleId >> 32); if ((titleIdHigh >> 16) != 0x5) return false; // title id should start with 0005... (Wii U platform id?) m_isPersonalized = header->deviceId != 0; m_deviceId = header->deviceId; m_publicKey = header->publicKey; std::memcpy(m_encryptedTitleKey, header->encryptedTitleKey, 16); cemu_assert_debug(header->ukn1E4 == 0); // read V1 extension if (m_ticketFormatVersion >= 1) { if ((titleIdHigh) == 0x0005000c) { ETicketFileHeaderExtV1* extHeader = (ETicketFileHeaderExtV1*)readStruct(0x2A4, sizeof(ETicketFileHeaderExtV1)); // (ETicketFileHeaderExtV1*)((uint8*)header + 0x2A4); if (!extHeader) return false; cemu_assert_debug(extHeader->sectionTableEntrySize == 0x14); for (uint32 i = 0; i < extHeader->sectionTableNumEntries; i++) { ETicketFileHeaderExtV1SectionHeader* sectHeader = (ETicketFileHeaderExtV1SectionHeader*)readStruct(0x2A4 + extHeader->sectionTableOffset, sizeof(ETicketFileHeaderExtV1SectionHeader)); if (!sectHeader) return false; if (sectHeader->type == ETicketFileHeaderExtV1SectionHeader::SECTION_TYPE_CONTENT_RIGHTS) { if (sectHeader->entrySize != sizeof(ETicketV1_ContentRights)) { cemuLog_log(LogType::Force, "ETicket: Failed to parse ticket with invalid rights size"); return false; } cemu_assert_debug(sectHeader->entryCount == 1); for (uint32 r = 0; r < sectHeader->entryCount; r++) { ETicketV1_ContentRights* rights = (ETicketV1_ContentRights*)readStruct(0x2A4 + sectHeader->sectionOffset + r * sectHeader->entrySize, sizeof(ETicketV1_ContentRights)); cemu_assert_debug(rights->baseIndex == 0); if (rights->baseIndex > 0x1000) { cemuLog_log(LogType::Force, "ETicket: Invalid content rights index ({})", (uint32)rights->baseIndex); continue; } size_t maxRightsCount = rights->baseIndex + rights->GetRightsCount(); if (maxRightsCount > m_contentRights.size()) m_contentRights.resize(maxRightsCount); for (uint32 x = 0; x < rights->GetRightsCount(); x++) m_contentRights[x + rights->baseIndex] = rights->GetRight(x); } } else { cemu_assert_debug(false); } } } } return true; } void ETicketParser::GetTitleKey(AesKey& key) { // the key is encrypted using the titleId as IV + 8 zero bytes uint8 iv[16]{}; *(uint64be*)iv = m_titleId; uint8 commonKey[16] = { 0xD7,0xB0,0x04,0x02,0x65,0x9B,0xA2,0xAB,0xD2,0xCB,0x0D,0xB2,0x7F,0xA2,0xB6,0x56 }; AES128_CBC_decrypt(key.b, m_encryptedTitleKey, 16, commonKey, iv); } // personalized tickets have an extra layer of encryption for the title key bool ETicketParser::Depersonalize(uint8* ticketData, size_t ticketSize, uint32 deviceId, const ECCPrivKey& devicePrivKey) { ETicketParser ticketParser; if (!ticketParser.parse(ticketData, ticketSize)) return false; if (!ticketParser.IsPersonalized()) return false; if (ticketParser.m_deviceId != deviceId) { cemuLog_log(LogType::Force, "Personalized ticket does not match deviceId"); return false; } // decrypt personalized titlekey EC_KEY* ec_privKey = devicePrivKey.getPrivateKey(); EC_POINT* ec_publicKey = ticketParser.m_publicKey.getPublicKeyAsPoint(); uint8 sharedKey[128]{}; int sharedKeyLen = ECDH_compute_key(sharedKey, sizeof(sharedKey), ec_publicKey, ec_privKey, nullptr); cemu_assert(sharedKeyLen > 16); EC_KEY_free(ec_privKey); EC_POINT_free(ec_publicKey); NCrypto::CHash160 sharedKeySHA1; NCrypto::GenerateHashSHA1(sharedKey, sharedKeyLen, sharedKeySHA1); uint8 aesSharedKey[16]{}; std::memcpy(aesSharedKey, sharedKeySHA1.b, 16); uint8 iv[16]{}; *(uint64be*)iv = ticketParser.m_ticketId; uint8 ticketKey[16]; AES128_CBC_decrypt(ticketKey, ticketParser.m_encryptedTitleKey, 16, aesSharedKey, iv); // store de-personalized key and remove personal data from ticket ETicketFileHeaderWiiU* header = (ETicketFileHeaderWiiU*)ticketData; std::memcpy(header->encryptedTitleKey, ticketKey, 16); header->deviceId = 0; header->accountId = 0; return true; } /* Title meta data */ struct TMDFileHeaderWiiU { /* +0x000 */ uint32be signatureType; /* +0x004 */ uint8be sig[0x100]; /* +0x104 */ uint8be _padding104[0x140 - 0x104]; /* +0x140 */ uint8be _ukn140[0x40]; /* +0x180 */ uint8be tmdVersion; /* +0x181 */ uint8be _ukn181; /* +0x182 */ uint8be _ukn182; /* +0x183 */ uint8be isVWii; /* +0x184 */ uint32be iosTitleIdHigh; /* +0x188 */ uint32be iosTitleIdLow; /* +0x18C */ uint32be titleIdHigh; /* +0x190 */ uint32be titleIdLow; /* +0x194 */ uint32be titleType; /* +0x198 */ uint16be group; /* +0x19A */ uint16be _ukn19A; /* +0x19C */ uint16be region; /* +0x19E */ uint8be ratings[16]; /* +0x1AE */ uint8be _ukn1AE[12]; /* +0x1BA */ uint8be _ipcMask[12]; /* +0x1C6 */ uint8be _ukn1C6[18]; /* +0x1D8 */ uint32be accessRightsMask; /* +0x1DC */ uint16be titleVersion; /* +0x1DE */ uint16be numContent; /* +0x1E0 */ uint32be _ukn1E0; /* +0x1E4 */ uint8 uknHash[32]; // hash of array at 0x204 /* +0x204 */ struct { // pointer to cert data and cert hash? uint16 ukn00; // index? uint16 ukn02; uint8 hash[32]; }ContentInfo[64]; }; static_assert(sizeof(TMDFileHeaderWiiU) == 0x204 + 64*36); struct TMDFileContentEntryWiiU { /* +0x00 */ uint32be contentId; /* +0x04 */ uint16be index; /* +0x06 */ uint16be type; /* +0x08 */ uint32be sizeHigh; /* +0x0C */ uint32be sizeLow; /* +0x10 */ uint8 hashSHA256[32]; // only the first 20 bytes of the hash seem to be stored? }; static_assert(sizeof(TMDFileContentEntryWiiU) == 0x30); bool TMDParser::parse(const uint8* data, size_t size) { if (size < sizeof(TMDFileHeaderWiiU)) { cemuLog_log(LogType::Force, "TMD size {} below minimum size of {}", size, sizeof(TMDFileHeaderWiiU)); return false; } TMDFileHeaderWiiU* header = (TMDFileHeaderWiiU*)data; m_titleId = ((uint64)header->titleIdHigh << 32) | ((uint64)header->titleIdLow); m_titleVersion = header->titleVersion; size_t expectedSize = sizeof(TMDFileHeaderWiiU) + sizeof(TMDFileContentEntryWiiU) * header->numContent; if (size < expectedSize) { cemuLog_log(LogType::Force, "TMD size {} below expected size of {}. Content count: {}", size, expectedSize, (uint16)header->numContent); return false; } // parse content TMDFileContentEntryWiiU* contentEntry = (TMDFileContentEntryWiiU*)(header + 1); for (uint32 i = 0; i < header->numContent; i++) { ContentEntry c{}; c.contentId = contentEntry->contentId; c.index = contentEntry->index; c.size = MakeU64(contentEntry->sizeHigh, contentEntry->sizeLow); c.contentFlags = static_cast((uint16)contentEntry->type); std::memcpy(c.hash32, contentEntry->hashSHA256, sizeof(c.hash32)); m_content.emplace_back(c); contentEntry++; } // todo - parse certificates return true; } /* ECC PrivateKey helper functions */ void ECCPrivKey::setPrivateKey(EC_KEY* key) { const BIGNUM* bnPrivKey = EC_KEY_get0_private_key(key); memset(this->keyData, 0, sizeof(this->keyData)); BN_bn2binpad(bnPrivKey, this->keyData, sizeof(this->keyData)); } EC_KEY* ECCPrivKey::getPrivateKey() const { BIGNUM* bn_privKey = BN_new(); BN_bin2bn(this->keyData, sizeof(this->keyData), bn_privKey); EC_KEY* ec_privKey = EC_KEY_new_by_curve_name(NID_sect233r1); EC_KEY_set_private_key(ec_privKey, bn_privKey); BN_free(bn_privKey); return ec_privKey; } ECCPrivKey ECCPrivKey::getDeviceCertPrivateKey() { ECCPrivKey key{}; iosuCrypto_getDeviceCertPrivateKey(key.keyData, sizeof(key.keyData)); return key; } /* ECC PublicKey helper functions */ EC_KEY* ECCPubKey::getPublicKey() { BIGNUM* bn_x = BN_new(); BIGNUM* bn_y = BN_new(); BN_bin2bn(this->x, sizeof(this->x), bn_x); BN_bin2bn(this->y, sizeof(this->y), bn_y); EC_KEY* ec_pubKey = EC_KEY_new_by_curve_name(NID_sect233r1); int r = EC_KEY_set_public_key_affine_coordinates(ec_pubKey, bn_x, bn_y); BN_free(bn_x); BN_free(bn_y); return ec_pubKey; } EC_POINT* ECCPubKey::getPublicKeyAsPoint() { BN_CTX* ctx = BN_CTX_new(); BIGNUM* bn_x = BN_new(); BIGNUM* bn_y = BN_new(); BN_bin2bn(this->x, sizeof(this->x), bn_x); BN_bin2bn(this->y, sizeof(this->y), bn_y); EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sect233r1); EC_POINT* pubkey = EC_POINT_new(group); EC_POINT_set_affine_coordinates(group, pubkey, bn_x, bn_y, ctx); EC_GROUP_free(group); BN_CTX_free(ctx); BN_free(bn_x); BN_free(bn_y); return pubkey; } ECCPubKey ECCPubKey::generateFromPrivateKey(ECCPrivKey& privKey) { BIGNUM* bn_privKey = BN_new(); BN_bin2bn(privKey.keyData, sizeof(privKey.keyData), bn_privKey); // gen public key from private key EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sect233r1); EC_POINT* pubkey = EC_POINT_new(group); EC_POINT_mul(group, pubkey, bn_privKey, NULL, NULL, NULL); BIGNUM* bn_x = BN_new(); BIGNUM* bn_y = BN_new(); EC_POINT_get_affine_coordinates(group, pubkey, bn_x, bn_y, NULL); // store public key ECCPubKey genPubKey; BN_bn2binpad(bn_x, genPubKey.x, sizeof(genPubKey.x)); BN_bn2binpad(bn_y, genPubKey.y, sizeof(genPubKey.y)); // clean up and return EC_POINT_free(pubkey); BN_free(bn_y); BN_free(bn_x); BN_free(bn_privKey); return genPubKey; } /* Signature helper functions */ ECDSA_SIG* ECCSig::getSignature() { BIGNUM* bn_r = BN_new(); BIGNUM* bn_s = BN_new(); BN_bin2bn(this->r, 30, bn_r); BN_bin2bn(this->s, 30, bn_s); ECDSA_SIG* ec_sig = ECDSA_SIG_new(); ECDSA_SIG_set0(ec_sig, bn_r, bn_s); // ownership of bn_r and bn_s transferred to SIG as well, do not free manually return ec_sig; } void ECCSig::setSignature(ECDSA_SIG* sig) { const BIGNUM* sig_r = nullptr, * sig_s = nullptr; ECDSA_SIG_get0(sig, &sig_r, &sig_s); sint32 lenR = BN_num_bytes(sig_r); sint32 lenS = BN_num_bytes(sig_s); cemu_assert_debug(lenR <= 30); cemu_assert_debug(lenS <= 30); memset(this->r, 0, sizeof(this->r)); memset(this->s, 0, sizeof(this->s)); BN_bn2binpad(sig_r, this->r, 30); BN_bn2binpad(sig_s, this->s, 30); } /* Certificate */ bool CertECC::decodeFromBase64(std::string_view input) { auto v = base64Decode(input); if (v.size() != sizeof(CertECC)) return false; memcpy(this, v.data(), sizeof(CertECC)); return true; } std::string CertECC::encodeToBase64() { return base64Encode(this, sizeof(CertECC)); } bool CertECC::verifySignatureViaPubKey(ECCPubKey& signerPubKey) { uint8 hash[SHA256_DIGEST_LENGTH]; SHA256((const unsigned char *) this->issuer, 0x100, hash); EC_KEY* ecPubKey = signerPubKey.getPublicKey(); ECDSA_SIG* ecSig = this->signature.getSignature(); int r = ECDSA_do_verify(hash, sizeof(hash), ecSig, ecPubKey); ECDSA_SIG_free(ecSig); EC_KEY_free(ecPubKey); return r == 1; // true if valid signature } void CertECC::sign(ECCPrivKey& signerPrivKey) { uint8 hash[SHA256_DIGEST_LENGTH]; SHA256((const unsigned char *) this->issuer, 0x100, hash); // generate signature EC_KEY* ec_privKey = signerPrivKey.getPrivateKey(); ECDSA_SIG* sig = ECDSA_do_sign(hash, sizeof(hash), ec_privKey); EC_KEY_free(ec_privKey); // store signature const BIGNUM* bn_r = nullptr, *bn_s = nullptr; ECDSA_SIG_get0(sig, &bn_r, &bn_s); BN_bn2binpad(bn_r, this->signature.r, sizeof(this->signature.r)); BN_bn2binpad(bn_s, this->signature.s, sizeof(this->signature.s)); ECDSA_SIG_free(sig); } CertECC CertECC::GetDeviceCertificate() { CertECC deviceCert{}; iosuCrypto_getDeviceCertificate(&deviceCert, sizeof(CertECC)); return deviceCert; } // generate a new public key + certificate from privateKey. Certificate is signed with the device key CertECC CertECC::generateCertificate(uint32 signerTitleIdHigh, uint32 signerTitleIdLow, ECCPrivKey& privKeySigner, ECCPrivKey& privKeyIn, ECCPubKey& pubKeyOut) { CertECC deviceCert = GetDeviceCertificate(); CertECC newCert = deviceCert; cemu_assert(newCert.signatureType == CertECC::SIGTYPE::ECC_SHA256); // update date newCert.date = 0; // update issuer strcat(newCert.issuer, "-"); strcat(newCert.issuer, newCert.ngName); // update subject memset(newCert.ngName, 0, sizeof(newCert.ngName)); sprintf(newCert.ngName, "AP%08x%08x", signerTitleIdHigh, signerTitleIdLow); // calculate public key from input private key newCert.publicKey = ECCPubKey::generateFromPrivateKey(privKeyIn); pubKeyOut = newCert.publicKey; // sign certificate newCert.sign(privKeySigner); return newCert; } ECCSig signHash(uint32 signerTitleIdHigh, uint32 signerTitleIdLow, uint8* hash, sint32 hashLen, CertECC& certChainOut) { // generate key pair (we only care about the private key) EC_KEY* ec_keyPair = EC_KEY_new_by_curve_name(NID_sect233r1); EC_KEY_generate_key(ec_keyPair); ECCPrivKey privKey; privKey.setPrivateKey(ec_keyPair); EC_KEY_free(ec_keyPair); // get public key and certificate ECCPubKey pubKey; ECCPrivKey signerPrivKey = ECCPrivKey::getDeviceCertPrivateKey(); certChainOut = CertECC::generateCertificate(signerTitleIdHigh, signerTitleIdLow, signerPrivKey, privKey, pubKey); // generate signature cemu_assert_debug(hashLen == 32); EC_KEY* ec_privKey = privKey.getPrivateKey(); ECDSA_SIG* sig = ECDSA_do_sign(hash, hashLen, ec_privKey); EC_KEY_free(ec_privKey); // verify EC_KEY* ec_pubKey = pubKey.getPublicKey(); bool isValid = ECDSA_do_verify(hash, hashLen, sig, ec_pubKey) == 1; EC_KEY_free(ec_pubKey); cemu_assert(isValid); // store signature ECCSig eccSig; const BIGNUM* bn_r = nullptr, * bn_s = nullptr; ECDSA_SIG_get0(sig, &bn_r, &bn_s); BN_bn2binpad(bn_r, eccSig.r, sizeof(eccSig.r)); BN_bn2binpad(bn_s, eccSig.s, sizeof(eccSig.s)); ECDSA_SIG_free(sig); return eccSig; } bool verifyHashSignature(uint8* hash, sint32 hashLen, ECCPubKey& certChainPubKey, ECCSig& sig) { EC_KEY* ec_pubKey = certChainPubKey.getPublicKey(); ECDSA_SIG* ecdsa_sig = sig.getSignature(); bool r = ECDSA_do_verify(hash, hashLen, ecdsa_sig, ec_pubKey) == 1; EC_KEY_free(ec_pubKey); ECDSA_SIG_free(ecdsa_sig); return r; } bool verifyCert(CertECC& cert, NCrypto::ECCPubKey& signerPubKey) { CertECC::SIGTYPE sigType = cert.signatureType; if (sigType == CertECC::SIGTYPE::ECC_SHA256) { // used for Wii U certs NCrypto::CHash256 hash; NCrypto::GenerateHashSHA256(cert.issuer, 0x100, hash); return NCrypto::verifyHashSignature(hash.b, 32, signerPubKey, cert.signature); } else if (sigType == CertECC::SIGTYPE::ECC_SHA1) { // from Wii era NCrypto::CHash160 hash; NCrypto::GenerateHashSHA1(cert.issuer, 0x100, hash); return NCrypto::verifyHashSignature(hash.b, 20, signerPubKey, cert.signature); } else { cemu_assert_unimplemented(); } return false; } uint32 GetDeviceId() { uint32 deviceId; if (!iosuCrypto_getDeviceId(&deviceId)) return 0x11223344; return deviceId; } std::string GetSerial() { char serialBuffer[128]{}; iosuCrypto_getDeviceSerialString(serialBuffer); return serialBuffer; } bool SEEPROM_IsPresent() { return hasSeepromMem; } CafeConsoleRegion SEEPROM_GetRegion() { uint8 seepromRegionU32[4] = {}; iosuCrypto_readSeepromData(seepromRegionU32, 0xA4, 4); if (seepromRegionU32[3] == 0) { cemuLog_log(LogType::Force, "SEEPROM region is invalid (0)"); } return (CafeConsoleRegion)seepromRegionU32[3]; } bool OTP_IsPresent() { return hasOtpMem; } bool HasDataForConsoleCert() { return SEEPROM_IsPresent() && OTP_IsPresent(); } std::string GetRegionAsString(CafeConsoleRegion regionCode) { if (regionCode == CafeConsoleRegion::EUR) return "EUR"; else if (regionCode == CafeConsoleRegion::USA) return "USA"; else if (regionCode == CafeConsoleRegion::JPN) return "JPN"; else if (regionCode == CafeConsoleRegion::CHN) return "CHN"; else if (regionCode == CafeConsoleRegion::KOR) return "KOR"; else if (regionCode == CafeConsoleRegion::TWN) return "TWN"; cemuLog_log(LogType::Force, "Unknown region code 0x{:x}", (uint32)regionCode); return "UKN"; } const std::unordered_map g_countryTable = { {1,"JP"}, {8,"AI"}, {9,"AG"}, {10,"AR"}, {11,"AW"}, {12,"BS"}, {13,"BB"}, {14,"BZ"}, {15,"BO"}, {16,"BR"}, {17,"VG"}, {18,"CA"}, {19,"KY"}, {20,"CL"}, {21,"CO"}, {22,"CR"}, {23,"DM"}, {24,"DO"}, {25,"EC"}, {26,"SV"}, {27,"GF"}, {28,"GD"}, {29,"GP"}, {30,"GT"}, {31,"GY"}, {32,"HT"}, {33,"HN"}, {34,"JM"}, {35,"MQ"}, {36,"MX"}, {37,"MS"}, {38,"AN"}, {39,"NI"}, {40,"PA"}, {41,"PY"}, {42,"PE"}, {43,"KN"}, {44,"LC"}, {45,"VC"}, {46,"SR"}, {47,"TT"}, {48,"TC"}, {49,"US"}, {50,"UY"}, {51,"VI"}, {52,"VE"}, {64,"AL"}, {65,"AU"}, {66,"AT"}, {67,"BE"}, {68,"BA"}, {69,"BW"}, {70,"BG"}, {71,"HR"}, {72,"CY"}, {73,"CZ"}, {74,"DK"}, {75,"EE"}, {76,"FI"}, {77,"FR"}, {78,"DE"}, {79,"GR"}, {80,"HU"}, {81,"IS"}, {82,"IE"}, {83,"IT"}, {84,"LV"}, {85,"LS"}, {86,"LI"}, {87,"LT"}, {88,"LU"}, {89,"MK"}, {90,"MT"}, {91,"ME"}, {92,"MZ"}, {93,"NA"}, {94,"NL"}, {95,"NZ"}, {96,"NO"}, {97,"PL"}, {98,"PT"}, {99,"RO"}, {100,"RU"}, {101,"RS"}, {102,"SK"}, {103,"SI"}, {104,"ZA"}, {105,"ES"}, {106,"SZ"}, {107,"SE"}, {108,"CH"}, {109,"TR"}, {110,"GB"}, {111,"ZM"}, {112,"ZW"}, {113,"AZ"}, {114,"MR"}, {115,"ML"}, {116,"NE"}, {117,"TD"}, {118,"SD"}, {119,"ER"}, {120,"DJ"}, {121,"SO"}, {122,"AD"}, {123,"GI"}, {124,"GG"}, {125,"IM"}, {126,"JE"}, {127,"MC"}, {128,"TW"}, {136,"KR"}, {144,"HK"}, {145,"MO"}, {152,"ID"}, {153,"SG"}, {154,"TH"}, {155,"PH"}, {156,"MY"}, {160,"CN"}, {168,"AE"}, {170,"EG"}, {171,"OM"}, {172,"QA"}, {173,"KW"}, {174,"SA"}, {175,"SY"}, {176,"BH"}, {177,"JO"}, {184,"SM"}, {185,"VA"}, {186,"BM"}, {187,"IN"}, {192,"NG"}, {193,"AO"}, {194,"GH"} }; const char* GetCountryAsString(sint32 index) { const auto it = g_countryTable.find(index); if (it == g_countryTable.cend()) return "NN"; return it->second; } size_t GetCountryCount() { return g_countryTable.size(); } void unitTests() { base64Tests(); } }; ================================================ FILE: src/Cemu/ncrypto/ncrypto.h ================================================ #pragma once #include "Common/betype.h" #include "config/CemuConfig.h" // for ConsoleRegion /* OpenSSL forward declarations */ typedef struct ec_key_st EC_KEY; typedef struct ec_point_st EC_POINT; typedef struct ECDSA_SIG_st ECDSA_SIG; namespace NCrypto { /* Base 64 */ std::string base64Encode(const void* inputMem, size_t inputLen); std::vector base64Decode(std::string_view inputStr); /* key and iv helper struct */ struct AesKey { static constexpr size_t SIZE = 16; uint8 b[SIZE]; }; struct AesIv { static constexpr size_t SIZE = 16; uint8 iv[SIZE]; }; /* ECC Certificate */ struct ECCPrivKey { uint8 keyData[30]; void setPrivateKey(EC_KEY* key); EC_KEY* getPrivateKey() const; static ECCPrivKey getDeviceCertPrivateKey(); }; struct ECCPubKey { uint8 x[30]; uint8 y[30]; EC_KEY* getPublicKey(); EC_POINT* getPublicKeyAsPoint(); [[nodiscard]] static ECCPubKey generateFromPrivateKey(ECCPrivKey& privKey); }; struct ECCSig { uint8 r[30]; uint8 s[30]; ECDSA_SIG* getSignature(); void setSignature(ECDSA_SIG* sig); bool doesMatch(ECCSig& otherSig) const { return memcmp(this->r, otherSig.r, sizeof(this->r)) == 0 && memcmp(this->s, otherSig.s, sizeof(this->s)) == 0; } std::string encodeToBase64() { return base64Encode(this, 60); } }; struct CHash256 // SHA256 { uint8 b[32]; }; struct CHash160 // SHA1 { uint8 b[20]; }; struct CertECC { enum class SIGTYPE : uint32 { ECC_SHA1 = 0x00010002, // guessed ECC_SHA256 = 0x00010005 }; /* +0x000 */ betype signatureType; // 01 00 02 00 /* +0x004 */ ECCSig signature; //uint8 signature[0x3C]; // from OTP 0xA3*4 /* +0x040 */ uint8 ukn040[0x40]; // seems to be just padding /* +0x080 */ char issuer[0x40]; // "Root - CA%08x - MS%08x" /* +0x0C0 */ char ukn0C0[0x4]; // ??? 00 00 00 02 ? /* +0x0C4 */ char ngName[0x40]; // "NG%08X" /* +0x104 */ uint32 date; // big endian? (from OTP 0xA2*4) /* +0x108 */ ECCPubKey publicKey; //uint8 publicKey[0x3C]; /* +0x144 */ uint8 padding[0x180 - 0x144]; bool decodeFromBase64(std::string_view input); std::string encodeToBase64(); bool verifySignatureViaPubKey(ECCPubKey& signerPubKey); void sign(ECCPrivKey& signerPrivKey); static CertECC GetDeviceCertificate(); static CertECC generateCertificate(uint32 signerTitleIdHigh, uint32 signerTitleIdLow, ECCPrivKey& privKeySigner, ECCPrivKey& privKeyIn, ECCPubKey& pubKeyOut); }; static_assert(sizeof(CertECC) == 0x180); /* ETicket */ class ETicketParser // .tik parser { public: bool parse(const uint8* data, size_t size); static bool Depersonalize(uint8* ticketData, size_t ticketSize, uint32 deviceId, const ECCPrivKey& devicePrivKey); uint64 GetTicketId() const { return m_ticketId; } uint64 GetTitleId() const { return m_titleId; } uint16 GetTicketVersion() const { return m_titleVersion; } void GetTitleKey(AesKey& key); bool IsPersonalized() { return m_isPersonalized; } bool CheckRight(size_t index) { if (index >= m_contentRights.size()) return false; return m_contentRights[index]; } private: uint64 m_titleId; uint16 m_titleVersion; uint64 m_ticketId; uint8 m_ticketFormatVersion; uint8 m_encryptedTitleKey[16]; // personalized data bool m_isPersonalized; uint32 m_deviceId; ECCPubKey m_publicKey; // rights std::vector m_contentRights; }; /* Title meta data */ class TMDParser { public: enum class TMDContentFlags : uint16 { // 0x0001 -> Is encrypted? FLAG_SHA1 = 0x2000, // if not set, use SHA256 FLAG_HASHED_CONTENT = 0x0002, // .app uses hashed format }; struct ContentEntry { uint32 contentId; uint16 index; TMDContentFlags contentFlags; uint64 size; uint8 hash32[32]; }; uint64 getTitleId() const { return m_titleId; } uint16 getTitleVersion() const { return m_titleVersion; } bool parse(const uint8* data, size_t size); std::vector& GetContentList() { return m_content; } private: uint64 m_titleId; uint16 m_titleVersion; std::vector m_content; }; DEFINE_ENUM_FLAG_OPERATORS(TMDParser::TMDContentFlags); void GenerateHashSHA256(const void* data, size_t len, CHash256& hashOut); ECCSig signHash(uint32 signerTitleIdHigh, uint32 signerTitleIdLow, uint8* hash, sint32 hashLen, CertECC& certChainOut); uint32 GetDeviceId(); std::string GetSerial(); CafeConsoleRegion SEEPROM_GetRegion(); std::string GetRegionAsString(CafeConsoleRegion regionCode); const char* GetCountryAsString(sint32 index); // returns NN if index is not valid or known size_t GetCountryCount(); bool SEEPROM_IsPresent(); bool OTP_IsPresent(); bool HasDataForConsoleCert(); void unitTests(); } ================================================ FILE: src/Cemu/nex/nex.cpp ================================================ #include "prudp.h" #include "nex.h" #include "nexThread.h" #include "util/crypto/md5.h" // for inet_pton: #if BOOST_OS_WINDOWS #include #else #include #endif uint32 _currentCallId = 1; sint32 nexService_parseResponse(uint8* data, sint32 length, nexServiceResponse_t* response) { if (length < 4) return 0; // get length field uint32 responseLength = *(uint32*)(data + 0x0); length -= 4; if (responseLength > (uint32)length) return 0; if (responseLength < 6) return 0; uint8 protocolId = *(uint8*)(data + 0x04); bool isRequest = (protocolId & 0x80) != 0; protocolId &= 0x7F; if (isRequest) { #ifdef CEMU_DEBUG_ASSERT assert_dbg(); // should never reach since we handle requests before this function is called #endif } uint8 success = *(uint8*)(data + 0x5); if (success == 0) { // error uint32 errorCode; if (length < 0xA) { return 0; } errorCode = *(uint32*)(data + 0x6); response->errorCode = errorCode; response->callId = *(uint32*)(data + 0xA); response->isSuccessful = false; response->protocolId = protocolId; response->methodId = 0xFFFFFFFF; return responseLength + 4; } else { if (responseLength < 0xA) return 0; response->errorCode = 0; response->isSuccessful = true; response->protocolId = protocolId; response->callId = *(uint32*)(data + 0x6); response->methodId = (*(uint32*)(data + 0xA)) & 0x7FFF; response->data = nexPacketBuffer(data + 0xE, responseLength - (0xE - 4), false); return responseLength+4; } return 0; } sint32 nexService_parseRequest(uint8* data, sint32 length, nexServiceRequest_t* request) { if (length < 4) return 0; // get length field uint32 requestLength = *(uint32*)(data + 0x0) + 4; if (requestLength > (uint32)length) return 0; if (requestLength < 13) return 0; uint8 protocolId = *(uint8*)(data + 0x4); bool isRequest = (protocolId & 0x80) != 0; protocolId &= 0x7F; if(isRequest == false) assert_dbg(); uint32 callId = *(uint32*)(data + 0x5); uint32 methodId = *(uint32*)(data + 0x9); uint8* dataPtr = (data + 0xD); sint32 dataLength = (sint32)(requestLength - 0xD); request->callId = callId; request->methodId = methodId; request->protocolId = protocolId; request->data = nexPacketBuffer(dataPtr, dataLength, false); return requestLength; } nexService::nexService() { connectionState = STATE_CONNECTING; conNexService = nullptr; isAsync = false; isDestroyed = false; isSecureService = false; } nexService::nexService(prudpClient* con) : nexService() { if (con->IsConnected() == false) cemu_assert_suspicious(); this->conNexService = con; bufferReceive = std::vector(1024 * 4); } nexService::nexService(uint32 ip, uint16 port, const char* accessKey) : nexService() { // unsecured connection isSecureService = false; conNexService = new prudpClient(ip, port, accessKey); bufferReceive = std::vector(1024 * 4); } nexService::~nexService() { // call error handlers for unfinished method calls for (auto& it : list_activeRequests) { nexServiceResponse_t response = { 0 }; response.isSuccessful = false; response.errorCode = ERR_TIMEOUT; response.custom = it.custom; if (it.nexServiceResponse) it.nexServiceResponse(this, &response); else { it.cb2(&response); } } if (conNexService) delete conNexService; } void nexService::destroy() { if (nexThread_isCurrentThread()) { delete this; return; } if (this->isAsync) isDestroyed = true; else delete this; } bool nexService::isMarkedForDestruction() { return isDestroyed; } void nexService::callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* parameter, void(*nexServiceResponse)(nexService* nex, nexServiceResponse_t* serviceResponse), void* custom, bool callHandlerIfError) { queuedRequest_t queueRequest = { 0 }; queueRequest.protocolId = protocolId; queueRequest.methodId = methodId; queueRequest.parameterData.assign(parameter->getDataPtr(), parameter->getDataPtr() + parameter->getWriteIndex()); queueRequest.nexServiceResponse = nexServiceResponse; queueRequest.custom = custom; queueRequest.callHandlerIfError = callHandlerIfError; mtx_queuedRequests.lock(); queuedRequests.push_back(queueRequest); mtx_queuedRequests.unlock(); } void nexService::callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* parameter, std::function cb, bool callHandlerIfError) { queuedRequest_t queueRequest = { 0 }; queueRequest.protocolId = protocolId; queueRequest.methodId = methodId; queueRequest.parameterData.assign(parameter->getDataPtr(), parameter->getDataPtr() + parameter->getWriteIndex()); queueRequest.nexServiceResponse = nullptr; queueRequest.cb2 = cb; queueRequest.callHandlerIfError = callHandlerIfError; mtx_queuedRequests.lock(); queuedRequests.push_back(queueRequest); mtx_queuedRequests.unlock(); } void nexService::processQueuedRequest(queuedRequest_t* queuedRequest) { uint32 callId = _currentCallId; _currentCallId++; // check state of connection if (conNexService->GetConnectionState() != prudpClient::ConnectionState::Connected) { nexServiceResponse_t response = { 0 }; response.isSuccessful = false; response.errorCode = ERR_NO_CONNECTION; response.custom = queuedRequest->custom; if (queuedRequest->nexServiceResponse) queuedRequest->nexServiceResponse(this, &response); else { queuedRequest->cb2(&response); } return; } uint8 packetBuffer[1024 * 8]; *(uint32*)(packetBuffer + 0x00) = 1 + 4 + 4 + (uint32)queuedRequest->parameterData.size(); // size *(uint8*)(packetBuffer + 0x04) = queuedRequest->protocolId | PROTOCOL_BIT_REQUEST; *(uint32*)(packetBuffer + 0x05) = callId; *(uint32*)(packetBuffer + 0x09) = queuedRequest->methodId; if (queuedRequest->parameterData.size() >= 1024 * 7) assert_dbg(); memcpy((packetBuffer + 0x0D), &queuedRequest->parameterData.front(), queuedRequest->parameterData.size()); sint32 length = 0xD + (sint32)queuedRequest->parameterData.size(); conNexService->SendDatagram(packetBuffer, length, true); // remember request nexActiveRequestInfo_t requestInfo = { 0 }; requestInfo.callId = callId; requestInfo.methodId = queuedRequest->methodId; requestInfo.protocolId = queuedRequest->protocolId; if (queuedRequest->nexServiceResponse) { requestInfo.nexServiceResponse = queuedRequest->nexServiceResponse; requestInfo.custom = queuedRequest->custom; } else { requestInfo.cb2 = queuedRequest->cb2; } requestInfo.handleError = queuedRequest->callHandlerIfError; requestInfo.requestTime = prudpGetMSTimestamp(); list_activeRequests.push_back(requestInfo); } void nexService::update() { if (connectionState == STATE_CONNECTED) { updateNexServiceConnection(); // process any queued requests mtx_queuedRequests.lock(); for (auto& request : queuedRequests) { processQueuedRequest(&request); } queuedRequests.clear(); mtx_queuedRequests.unlock(); } else if (connectionState == STATE_CONNECTING) { updateTemporaryConnections(); } // handle request timeouts uint32 currentTimestamp = prudpGetMSTimestamp(); sint32 idx = 0; while (idx < list_activeRequests.size()) { auto& it = list_activeRequests[idx]; if ((uint32)(currentTimestamp - it.requestTime) >= 10000) { // time out after 10 seconds nexServiceResponse_t response = { 0 }; response.isSuccessful = false; response.errorCode = ERR_TIMEOUT; response.custom = it.custom; if (it.nexServiceResponse) it.nexServiceResponse(this, &response); else { it.cb2(&response); } list_activeRequests.erase(list_activeRequests.cbegin()+idx); continue; } idx++; } } prudpClient* nexService::getPRUDPConnection() { return conNexService; } sint32 nexService::getState() { return connectionState; } void nexService::registerForAsyncProcessing() { if (isAsync) return; nexThread_registerService(this); isAsync = true; } void nexService::updateTemporaryConnections() { // check for connection conNexService->Update(); if (conNexService->IsConnected()) { if (connectionState == STATE_CONNECTING) connectionState = STATE_CONNECTED; } if (conNexService->GetConnectionState() == prudpClient::ConnectionState::Disconnected) connectionState = STATE_DISCONNECTED; } // returns true if the packet is a nex RPC request, in case of error false is returned bool nexIsRequest(uint8* data, sint32 length) { if (length < 5) return false; uint8 protocolId = *(uint8*)(data + 0x04); bool isRequest = (protocolId & 0x80) != 0; return isRequest; } void nexService::registerProtocolRequestHandler(uint8 protocol, void(*processRequest)(nexServiceRequest_t* request), void* custom) { protocolHandler_t protocolHandler; protocolHandler.protocol = protocol; protocolHandler.processRequest = processRequest; protocolHandler.custom = custom; list_requestHandlers.push_back(protocolHandler); } void nexService::sendRequestResponse(nexServiceRequest_t* request, uint32 errorCode, uint8* data, sint32 length) { cemu_assert_debug(length == 0); // non-zero length is todo uint8 packetBuffer[256]; nexPacketBuffer response(packetBuffer, sizeof(packetBuffer), true); bool isSuccess = (errorCode == 0); // write placeholder for response length response.writeU32(0); // header fields response.writeU8(request->protocolId&0x7F); response.writeU8(isSuccess); if (isSuccess) { response.writeU32(request->callId); response.writeU32(request->methodId); // write data // todo } else { response.writeU32(errorCode); response.writeU32(request->callId); } // update length field *(uint32*)response.getDataPtr() = response.getWriteIndex()-4; if(request->nex->conNexService) request->nex->conNexService->SendDatagram(response.getDataPtr(), response.getWriteIndex(), true); } void nexService::updateNexServiceConnection() { if (conNexService->GetConnectionState() == prudpClient::ConnectionState::Disconnected) { this->connectionState = STATE_DISCONNECTED; return; } conNexService->Update(); sint32 datagramLen = conNexService->ReceiveDatagram(bufferReceive); if (datagramLen > 0) { if (nexIsRequest(&bufferReceive[0], datagramLen)) { // request nexServiceRequest_t nexServiceRequest; sint32 parsedLength = nexService_parseRequest(&bufferReceive[0], datagramLen, &nexServiceRequest); if (parsedLength > 0) { nexServiceRequest.nex = this; // call request handler for (auto& it : list_requestHandlers) { if (it.protocol == nexServiceRequest.protocolId) { nexServiceRequest.custom = it.custom; it.processRequest(&nexServiceRequest); break; } } } cemu_assert_debug(parsedLength == datagramLen); } else { // response nexServiceResponse_t nexServiceResponse; sint32 parsedLength = nexService_parseResponse(&bufferReceive[0], datagramLen, &nexServiceResponse); if (parsedLength > 0) { nexServiceResponse.nex = this; // call response handler for (sint32 i = 0; i < list_activeRequests.size(); i++) { if (nexServiceResponse.callId == list_activeRequests[i].callId && nexServiceResponse.protocolId == list_activeRequests[i].protocolId && (nexServiceResponse.methodId == list_activeRequests[i].methodId || nexServiceResponse.methodId == 0xFFFFFFFF)) { nexServiceResponse.custom = list_activeRequests[i].custom; if (nexServiceResponse.isSuccessful || list_activeRequests[i].handleError) { if (list_activeRequests[i].nexServiceResponse) list_activeRequests[i].nexServiceResponse(this, &nexServiceResponse); else { list_activeRequests[i].cb2(&nexServiceResponse); } } // remove entry list_activeRequests.erase(list_activeRequests.begin() + i); break; } } } cemu_assert_debug(parsedLength == datagramLen); } } } bool _extractStationUrlParamValue(const char* urlStr, const char* paramName, char* output, sint32 maxLength) { size_t paramNameLen = strlen(paramName); const char* optionPtr = strstr(urlStr, paramName); while (optionPtr) { if (optionPtr[paramNameLen] == '=') { output[maxLength - 1] = '\0'; for (sint32 i = 0; i < maxLength - 1; i++) { char c = optionPtr[paramNameLen + 1 + i]; if (c == ';' || c == '\0') { output[i] = '\0'; break; } output[i] = c; } return true; } // next optionPtr = strstr(optionPtr+1, paramName); } return false; } void nexServiceAuthentication_parseStationURL(char* urlStr, prudpStationUrl* stationUrl) { // example: // prudps:/address=34.210.xxx.xxx;port=60181;CID=1;PID=2;sid=1;stream=10;type=2 memset(stationUrl, 0, sizeof(prudpStationUrl)); char optionValue[128]; if (_extractStationUrlParamValue(urlStr, "address", optionValue, sizeof(optionValue))) { inet_pton(AF_INET, optionValue, &stationUrl->ip); } if (_extractStationUrlParamValue(urlStr, "port", optionValue, sizeof(optionValue))) { stationUrl->port = atoi(optionValue); } if (_extractStationUrlParamValue(urlStr, "CID", optionValue, sizeof(optionValue))) { stationUrl->cid = atoi(optionValue); } if (_extractStationUrlParamValue(urlStr, "PID", optionValue, sizeof(optionValue))) { stationUrl->pid = atoi(optionValue); } if (_extractStationUrlParamValue(urlStr, "sid", optionValue, sizeof(optionValue))) { stationUrl->sid = atoi(optionValue); } if (_extractStationUrlParamValue(urlStr, "stream", optionValue, sizeof(optionValue))) { stationUrl->stream = atoi(optionValue); } if (_extractStationUrlParamValue(urlStr, "type", optionValue, sizeof(optionValue))) { stationUrl->type = atoi(optionValue); } } typedef struct { uint32 userPid; uint8 kerberosTicket[1024]; sint32 kerberosTicketSize; uint8 kerberosTicket2[4096]; sint32 kerberosTicket2Size; prudpStationUrl server; // progress info bool hasError; bool done; }authenticationService_t; void nexServiceAuthentication_handleResponse_requestTicket(nexService* nex, nexServiceResponse_t* response) { authenticationService_t* authService = (authenticationService_t*)response->custom; if (response->isSuccessful == false) { cemuLog_log(LogType::Force, "NEX: RPC error while requesting auth ticket with error code 0x{:08x}", response->errorCode); authService->hasError = true; return; } uint32 returnValue = response->data.readU32(); if (returnValue & 0x80000000) { cemuLog_log(LogType::Force, "NEX: Failed to request auth ticket with error code 0x{:08x}", returnValue); authService->hasError = true; } authService->kerberosTicket2Size = response->data.readBuffer(authService->kerberosTicket2, sizeof(authService->kerberosTicket2)); if (response->data.hasReadOutOfBounds()) { authService->hasError = true; cemuLog_log(LogType::Force, "NEX: Out of bounds error while reading auth ticket"); return; } authService->done = true; } void nexServiceAuthentication_handleResponse_login(nexService* nex, nexServiceResponse_t* response) { authenticationService_t* authService = (authenticationService_t*)response->custom; if (response->isSuccessful == false) { authService->hasError = true; cemuLog_log(LogType::Force, "NEX: RPC error in login response 0x{:08x}", response->errorCode); return; } uint32 returnValue = response->data.readU32(); if (returnValue & 0x80000000) { authService->hasError = true; cemuLog_log(LogType::Force, "NEX: Error 0x{:08x} in login response (returnCode 0x{:08x})", response->errorCode, returnValue); return; } uint32 userPid = response->data.readU32(); // kerberos ticket authService->kerberosTicketSize = response->data.readBuffer(authService->kerberosTicket, sizeof(authService->kerberosTicket)); // RVConnection data // server address (regular protocol) char serverAddress[1024]; response->data.readString(serverAddress, sizeof(serverAddress)); nexServiceAuthentication_parseStationURL(serverAddress, &authService->server); // special protocols if (response->data.readU32() != 0) assert_dbg(); // list not empty char specialAddress[32]; response->data.readString(specialAddress, sizeof(specialAddress)); // V1 has extra info here // server name char serverName[256]; response->data.readString(serverName, sizeof(serverName)); if (response->data.hasReadOutOfBounds()) { authService->hasError = true; cemuLog_log(LogType::Force, "NEX: Read out of bounds"); return; } // request ticket data uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(authService->userPid); packetBuffer.writeU32(authService->server.pid); nex->callMethod(NEX_PROTOCOL_AUTHENTICATION, 3, &packetBuffer, nexServiceAuthentication_handleResponse_requestTicket, authService); } typedef struct { bool isComplete; bool isSuccessful; }nexServiceSecureRegisterExData_t; void nexServiceSecure_handleResponse_RegisterEx(nexService* nex, nexServiceResponse_t* response) { nexServiceSecureRegisterExData_t* info = (nexServiceSecureRegisterExData_t*)response->custom; info->isComplete = true; uint32 returnCode = response->data.readU32(); if (response->isSuccessful == false || response->data.hasReadOutOfBounds()) { cemuLog_log(LogType::Force, "NEX: RPC error in secure register"); info->isSuccessful = false; info->isComplete = true; return; } if (returnCode & 0x80000000) { cemuLog_log(LogType::Force, "NEX: Secure register failed with error code 0x{:08x}", returnCode); info->isSuccessful = false; info->isComplete = true; return; } // remaining data is skipped info->isSuccessful = true; info->isComplete = true; return; } nexService* nex_secureLogin(prudpAuthServerInfo* authServerInfo, const char* accessKey, const char* nexToken) { prudpClient* prudpSecureSock = new prudpClient(authServerInfo->server.ip, authServerInfo->server.port, accessKey, authServerInfo); // wait until connected while (true) { prudpSecureSock->Update(); if (prudpSecureSock->IsConnected()) { break; } if (prudpSecureSock->GetConnectionState() == prudpClient::ConnectionState::Disconnected) { // timeout or disconnected cemuLog_log(LogType::Force, "NEX: Secure login connection time-out"); delete prudpSecureSock; return nullptr; } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } // create nex service layer nexService* nex = new nexService(prudpSecureSock); // secureService: RegisterEx uint8 tempNexBufferArray[4096]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); char clientStationUrl[256]; sprintf(clientStationUrl, "prudp:/port=%u;natf=0;natm=0;pmp=0;sid=15;type=2;upnp=0", (uint32)nex->getPRUDPConnection()->GetSourcePort()); // station url list packetBuffer.writeU32(1); packetBuffer.writeString(clientStationUrl); // login data packetBuffer.writeCustomType(nexNintendoLoginData(nexToken)); nexServiceSecureRegisterExData_t secureRegisterExData = { 0 }; nex->callMethod(NEX_PROTOCOL_SECURE, 4, &packetBuffer, nexServiceSecure_handleResponse_RegisterEx, &secureRegisterExData); while (true) { nex->update(); if (secureRegisterExData.isComplete) break; if (nex->getState() == nexService::STATE_DISCONNECTED) { cemuLog_log(LogType::Force, "NEX: Connection error while registering"); break; } } if (secureRegisterExData.isSuccessful == false) { cemuLog_log(LogType::Force, "NEX: Failed to register to secure server"); nex->destroy(); return nullptr; } return nex; } nexService* nex_establishSecureConnection(uint32 authServerIp, uint16 authServerPort, const char* accessKey, uint32 pid, const char* nexPassword, const char* nexToken) { nexService* authConnection = new nexService(authServerIp, authServerPort, accessKey); // wait until connected while (authConnection->getState() == nexService::STATE_CONNECTING) { authConnection->update(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } if (authConnection->getState() != nexService::STATE_CONNECTED) { // error authConnection->destroy(); cemuLog_log(LogType::Force, "NEX: Failed to connect to the NEX server"); return nullptr; } // send auth login request uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); char pidStr[32]; sprintf(pidStr, "%u", pid); packetBuffer.writeString(pidStr); authenticationService_t nexAuthService = { 0 }; nexAuthService.userPid = pid; authConnection->callMethod(NEX_PROTOCOL_AUTHENTICATION, 1, &packetBuffer, nexServiceAuthentication_handleResponse_login, &nexAuthService); while (true) { if (nexAuthService.hasError || nexAuthService.done || authConnection->getState() != nexService::STATE_CONNECTED) break; authConnection->update(); } if (nexAuthService.hasError || authConnection->getState() != nexService::STATE_CONNECTED) { // error authConnection->destroy(); cemuLog_log(LogType::Force, "NEX: Error during authentication"); return nullptr; } // close connection to auth server authConnection->destroy(); // calculate kerberos decryption key uint32 md5LoopCount = 65000 + (pid % 1024); md5LoopCount--; uint8 kerberosKey[16]; MD5_CTX md5Ctx; MD5_Init(&md5Ctx); MD5_Update(&md5Ctx, nexPassword, (unsigned long)strlen(nexPassword)); MD5_Final(kerberosKey, &md5Ctx); for (uint32 i = 0; i < md5LoopCount; i++) { MD5_Init(&md5Ctx); MD5_Update(&md5Ctx, kerberosKey, 16); MD5_Final(kerberosKey, &md5Ctx); } if (nexAuthService.kerberosTicket2Size < 16) { nexAuthService.hasError = true; cemuLog_log(LogType::Force, "NEX: Kerberos ticket too short"); return nullptr; } // check hmac of ticket uint8 hmacTicket[16]; hmacMD5(kerberosKey, 16, nexAuthService.kerberosTicket2, nexAuthService.kerberosTicket2Size - 16, hmacTicket); if (memcmp(hmacTicket, nexAuthService.kerberosTicket2 + nexAuthService.kerberosTicket2Size - 16, 16) != 0) { nexAuthService.hasError = true; cemuLog_log(LogType::Force, "NEX: Kerberos ticket hmac invalid"); return nullptr; } // auth info auto authServerInfo = std::make_unique(); // decrypt ticket RC4Ctx rc4Ticket; RC4_initCtx(&rc4Ticket, kerberosKey, 16); RC4_transform(&rc4Ticket, nexAuthService.kerberosTicket2, nexAuthService.kerberosTicket2Size - 16, nexAuthService.kerberosTicket2); nexPacketBuffer packetKerberosTicket(nexAuthService.kerberosTicket2, nexAuthService.kerberosTicket2Size - 16, false); uint8 secureKey[16]; // for friends server it's 16 bytes, all others use 32 bytes packetKerberosTicket.readData(secureKey, 16); uint32 ukn = packetKerberosTicket.readU32(); authServerInfo->secureTicketLength = packetKerberosTicket.readBuffer(authServerInfo->secureTicket, sizeof(authServerInfo->secureTicket)); if (packetKerberosTicket.hasReadOutOfBounds()) { cemuLog_log(LogType::Force, "NEX: Parse error"); return nullptr; } memcpy(authServerInfo->kerberosKey, kerberosKey, 16); memcpy(authServerInfo->secureKey, secureKey, 16); memcpy(&authServerInfo->server, &nexAuthService.server, sizeof(prudpStationUrl)); authServerInfo->userPid = pid; return nex_secureLogin(authServerInfo.get(), accessKey, nexToken); } ================================================ FILE: src/Cemu/nex/nex.h ================================================ #pragma once #include "nexTypes.h" #include const int NEX_PROTOCOL_AUTHENTICATION = 0xA; const int NEX_PROTOCOL_SECURE = 0xB; const int NEX_PROTOCOL_FRIENDS_WIIU = 0x66; class nexService; typedef struct { nexService* nex; bool isSuccessful; uint32 errorCode; uint32 callId; uint32 methodId; uint8 protocolId; nexPacketBuffer data; void* custom; }nexServiceResponse_t; typedef struct { nexService* nex; uint32 callId; uint32 methodId; uint8 protocolId; nexPacketBuffer data; void* custom; }nexServiceRequest_t; class prudpClient; class nexService { private: typedef struct { uint8 protocolId; uint32 methodId; uint32 callId; void(*nexServiceResponse)(nexService* nex, nexServiceResponse_t* serviceResponse); void* custom; bool handleError; // if set to true, call nexServiceResponse with errorCode set (else the callback is skipped when the call is not successful) uint32 requestTime; // timestamp of when the request was sent // alternative callback handler std::function cb2; }nexActiveRequestInfo_t; typedef struct { uint8 protocolId; uint32 methodId; bool callHandlerIfError; //nexPacketBuffer* parameter; std::vector parameterData; void(*nexServiceResponse)(nexService* nex, nexServiceResponse_t* serviceResponse); void* custom; // alternative callback handler std::function cb2; }queuedRequest_t; typedef struct { uint8 protocol; void(*processRequest)(nexServiceRequest_t* request); void* custom; }protocolHandler_t; public: static const int STATE_CONNECTING = 0; static const int STATE_CONNECTED = 1; static const int STATE_DISCONNECTED = 2; static const unsigned int ERR_TIMEOUT = (0xFFFFFFFF); static const unsigned int ERR_NO_CONNECTION = (0xFFFFFFFE); nexService(prudpClient* con); nexService(uint32 ip, uint16 port, const char* accessKey); const uint8 PROTOCOL_BIT_REQUEST = 0x80; void callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* parameter, void(*nexServiceResponse)(nexService* nex, nexServiceResponse_t* serviceResponse), void* custom, bool callHandlerIfError = false); void callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* parameter, std::function cb, bool callHandlerIfError); void registerProtocolRequestHandler(uint8 protocol, void(*processRequest)(nexServiceRequest_t* request), void* custom); void sendRequestResponse(nexServiceRequest_t* request, uint32 errorCode, uint8* data, sint32 length); void update(); prudpClient* getPRUDPConnection(); sint32 getState(); void registerForAsyncProcessing(); void destroy(); bool isMarkedForDestruction(); private: nexService(); ~nexService(); void updateTemporaryConnections(); void updateNexServiceConnection(); void processQueuedRequest(queuedRequest_t* queuedRequest); private: //bool serviceIsConnected; uint8 connectionState; prudpClient* conNexService; bool isAsync; bool isDestroyed; // if set, delete object asynchronously std::vector list_activeRequests; // protocol request handlers std::vector list_requestHandlers; // packet buffer std::vector bufferReceive; // auth bool isSecureService; // request queue std::mutex mtx_queuedRequests; std::vector queuedRequests; }; nexService* nex_establishSecureConnection(uint32 authServerIp, uint16 authServerPort, const char* accessKey, uint32 pid, const char* nexPassword, const char* nexToken); ================================================ FILE: src/Cemu/nex/nexFriends.cpp ================================================ #include "prudp.h" #include "nex.h" #include "nexFriends.h" #include "Cafe/CafeSystem.h" static const int NOTIFICATION_SRV_FRIEND_OFFLINE = 0x0A; // the opposite event (friend online) is notified via _PRESENCE_CHANGE static const int NOTIFICATION_SRV_FRIEND_PRESENCE_CHANGE = 0x18; static const int NOTIFICATION_SRV_FRIEND_REMOVED = 0x1A; // also used to indicate that an incoming friend request was canceled static const int NOTIFICATION_SRV_FRIEND_REQUEST_INCOMING = 0x1B; // someone sent a friend request to us static const int NOTIFICATION_SRV_FRIEND_ADDED = 0x1E; // not sent when friend request is accepted locally void NexFriends::processServerNotification_friendOffline(uint32 pid) { std::unique_lock listLock(mtx_lists); for (auto& it : list_friends) { if (it.nnaInfo.principalInfo.principalId == pid) { it.presence.isOnline = 0; generateNotification(NOTIFICATION_TYPE_FRIEND_LOGOFF, pid); return; } } } void NexFriends::processServerNotification_presenceChange(uint32 pid, nexPresenceV2& presence) { std::unique_lock listLock(mtx_lists); for (auto& it : list_friends) { if (it.nnaInfo.principalInfo.principalId == pid) { bool isOnlineChange = (presence.isOnline != it.presence.isOnline); it.presence = presence; if (isOnlineChange) generateNotification((presence.isOnline!=0)?NOTIFICATION_TYPE_FRIEND_LOGIN:NOTIFICATION_TYPE_FRIEND_LOGOFF, pid); generateNotification(NOTIFICATION_TYPE_FRIEND_PRESENCE_CHANGE, pid); return; } } } void NexFriends::processServerNotification_removeFriendOrFriendRequest(uint32 pid) { std::unique_lock listLock(mtx_lists); for (auto it = list_friends.begin(); it != list_friends.end();) { if (it->nnaInfo.principalInfo.principalId == pid) { list_friends.erase(it); generateNotification(NOTIFICATION_TYPE_REMOVED_FRIEND, pid); return; } it++; } for (auto it = list_friendReqIncoming.begin(); it != list_friendReqIncoming.end();) { if (it->principalInfo.principalId == pid) { list_friendReqIncoming.erase(it); generateNotification(NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST, pid); return; } it++; } } void NexFriends::processServerNotification_incomingFriendRequest(uint32 pid, nexFriendRequest& friendRequest) { std::unique_lock listLock(mtx_lists); // check for duplicate for (auto& it : list_friendReqIncoming) { if (it.principalInfo.principalId == pid) return; } // add friend request and send notification list_friendReqIncoming.push_back(friendRequest); generateNotification(NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST, pid); } void NexFriends::processServerNotification_addedFriend(uint32 pid, nexFriend& frd) { std::unique_lock listLock(mtx_lists); // remove any related incoming friend request for (auto it = list_friendReqIncoming.begin(); it != list_friendReqIncoming.end();) { if (it->principalInfo.principalId == pid) { list_friendReqIncoming.erase(it); generateNotification(NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST, pid); break; } it++; } // remove any related outgoing friend request for (auto it = list_friendReqOutgoing.begin(); it != list_friendReqOutgoing.end();) { if (it->principalInfo.principalId == pid) { list_friendReqOutgoing.erase(it); generateNotification(NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST, pid); break; } it++; } // check for duplicate for (auto& it : list_friendReqIncoming) { if (it.principalInfo.principalId == pid) return; } // add friend list_friends.push_back(frd); generateNotification(NOTIFICATION_TYPE_ADDED_FRIEND, pid); } void NexFriends::processServerNotification(uint32 notificationType, uint32 pid, nexPacketBuffer* notificationData) { nexNotificationEventGeneral notificationGeneral; if (notificationType == NOTIFICATION_SRV_FRIEND_PRESENCE_CHANGE) { nexPresenceV2 presence; if (notificationData->readPlaceholderType(presence)) { this->processServerNotification_presenceChange(pid, presence); } } else if (notificationType == 0x21) { // change comment text if (notificationData->readPlaceholderType(notificationGeneral)) { // todo } } else if (notificationType == NOTIFICATION_SRV_FRIEND_OFFLINE) { // friend now offline if (notificationData->readPlaceholderType(notificationGeneral)) { this->processServerNotification_friendOffline(pid); } } else if (notificationType == NOTIFICATION_SRV_FRIEND_REMOVED) { // friend removed or friend-request removed if (notificationData->readPlaceholderType(notificationGeneral)) { this->processServerNotification_removeFriendOrFriendRequest(pid); } } else if (notificationType == NOTIFICATION_SRV_FRIEND_REQUEST_INCOMING) { nexFriendRequest friendRequest; if (notificationData->readPlaceholderType(friendRequest)) { this->processServerNotification_incomingFriendRequest(pid, friendRequest); } } else if (notificationType == NOTIFICATION_SRV_FRIEND_ADDED) { nexFriend frd; if (notificationData->readPlaceholderType(frd)) { this->processServerNotification_addedFriend(pid, frd); } } else if (true) { cemuLog_logDebug(LogType::Force, "Unsupported friend server notification type 0x{:02x}", notificationType); } } void nexFriends_protocolNotification_processRequest(nexServiceRequest_t* request) { NexFriends* nexFriends = (NexFriends*)request->custom; if (request->methodId == 0x1 || request->methodId == 0x2) { // notification uint32 notificationType = request->data.readU32(); uint32 pid = request->data.readU32(); if (request->data.hasReadOutOfBounds()) { request->nex->sendRequestResponse(request, 0, nullptr, 0); return; } cemuLog_logDebug(LogType::Force, "NN_NOTIFICATION methodId {} type {:02x} pid {:08x}", request->methodId, notificationType, pid); nexFriends->processServerNotification(notificationType, pid, &request->data); request->nex->sendRequestResponse(request, 0, nullptr, 0); return; } // unknown method request->nex->sendRequestResponse(request, 0x80000000, nullptr, 0); } NexFriends::NexFriends(uint32 authServerIp, uint16 authServerPort, const char* accessKey, uint32 pid, const char* nexPassword, const char* nexToken, const char* nnid, uint8* miiData, const wchar_t* miiNickname, uint8 countryCode, nexPresenceV2& myPresence) : nexCon(nullptr) { memcpy(this->miiData, miiData, FFL_SIZE); strcpy(this->nnid, nnid); this->pid = pid; this->countryCode = countryCode; this->myPresence = myPresence; this->miiNickname = boost::nowide::narrow(miiNickname); cemu_assert_debug(this->miiNickname.size() <= 96-1); auth.serverIp = authServerIp; auth.port = authServerPort; auth.accessKey = std::string(accessKey); auth.nexPassword = std::string(nexPassword); auth.nexToken = std::string(nexToken); // login related variables this->lastLoginAttemptTime = prudpGetMSTimestamp() - 1000*60*60; this->isCurrentlyConnected = false; this->hasData = false; this->loginInProcess = false; this->numFailedLogins = 0; this->numSuccessfulLogins = 0; } NexFriends::~NexFriends() { if(nexCon) nexCon->destroy(); } void NexFriends::doAsyncLogin() { nexCon = nex_establishSecureConnection(auth.serverIp, auth.port, auth.accessKey.c_str(), pid, auth.nexPassword.c_str(), auth.nexToken.c_str()); if (nexCon == nullptr) { this->numFailedLogins++; this->loginInProcess = false; return; } nexCon->registerProtocolRequestHandler(0x64, nexFriends_protocolNotification_processRequest, this); nexCon->registerForAsyncProcessing(); this->isCurrentlyConnected = true; this->firstInformationRequest = true; this->loginInProcess = false; this->numSuccessfulLogins++; requestGetAllInformation(); } void NexFriends::initiateLogin() { if (this->isCurrentlyConnected) return; if (this->loginInProcess) return; std::unique_lock loginLock(mtx_login); this->loginInProcess = true; // reset all data std::unique_lock listLock(mtx_lists); list_friends.resize(0); list_friendReqIncoming.resize(0); list_friendReqOutgoing.resize(0); previousState.list_friends.resize(0); previousState.list_friendReqIncoming.resize(0); previousState.list_friendReqOutgoing.resize(0); this->hasData = false; // start login attempt cemuLog_logDebug(LogType::Force, "Attempt to log into friend server..."); std::thread t(&NexFriends::doAsyncLogin, this); t.detach(); } void NexFriends::handleResponse_getAllInformation(nexServiceResponse_t* response, NexFriends* nexFriends, std::function cb) { if (response->isSuccessful == false) { if (cb) cb(ERR_RPC_FAILED); return; } NexFriends* session = (NexFriends*)nexFriends; session->myPreference = nexPrincipalPreference(&response->data); auto comment = nexComment(&response->data); session->myComment = comment; if (response->data.hasReadOutOfBounds()) return; // acquire lock on lists std::unique_lock listLock(session->mtx_lists); // remember current state of lists for change tracking session->previousState.list_friends = session->list_friends; session->previousState.list_friendReqIncoming = session->list_friendReqIncoming; session->previousState.list_friendReqOutgoing = session->list_friendReqOutgoing; // parse response and update lists // friends uint32 friendCount = response->data.readU32(); session->list_friends.resize(friendCount); for (uint32 i = 0; i < friendCount; i++) session->list_friends[i].readData(&response->data); // friend requests (outgoing) uint32 friendRequestsOutCount = response->data.readU32(); if (response->data.hasReadOutOfBounds()) return; session->list_friendReqOutgoing.resize(friendRequestsOutCount); for (uint32 i = 0; i < friendRequestsOutCount; i++) session->list_friendReqOutgoing[i].readData(&response->data); // friend requests (incoming) uint32 friendRequestsInCount = response->data.readU32(); if (response->data.hasReadOutOfBounds()) return; session->list_friendReqIncoming.resize(friendRequestsInCount); for (uint32 i = 0; i < friendRequestsInCount; i++) session->list_friendReqIncoming[i].readData(&response->data); if (response->data.hasReadOutOfBounds()) return; // blacklist uint32 blacklistCount = response->data.readU32(); for (uint32 i = 0; i < blacklistCount; i++) { nexBlacklisted blacklisted(&response->data); } // ukn uint8 uknSetting = response->data.readU8(); // ? (usually zero) // persistent notifications uint32 perstNotificationCount = response->data.readU32(); for (uint32 i = 0; i < perstNotificationCount; i++) { nexPersistentNotification notification(&response->data); //printf("--- Notification ---\n"); //printf("ID %016llx pid1 %08x pid2 %08x type %08x\n", notification.messageId, notification.pid1, notification.pid2, notification.type); //printf("Msg %s\n", notification.msg.c_str()); } uint8 isPreferenceInvalid = response->data.readU8(); // if not zero, preferences must be setup if (isPreferenceInvalid) { cemuLog_log(LogType::Force, "NEX: First time login into friend account, setting up default preferences"); session->updatePreferencesAsync(nexPrincipalPreference(1, 1, 0), [](RpcErrorCode err){}); } if (session->firstInformationRequest == false) session->trackNotifications(); else { // first request after login -> send online notification session->generateNotification(NOTIFICATION_TYPE_ONLINE, session->pid); } session->firstInformationRequest = false; session->hasData = true; if (cb) cb(ERR_NONE); } bool NexFriends::requestGetAllInformation() { uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); nexNNAInfo(this->countryCode, 0, nexPrincipalBasicInfo(pid, nnid, nexMiiV2(miiNickname.c_str(), miiData))).writeData(&packetBuffer); myPresence.writeData(&packetBuffer); packetBuffer.writeU64(0x1f18420000); // birthday (set to 1990-1-1) nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 1, &packetBuffer, std::bind(handleResponse_getAllInformation, std::placeholders::_1, this, nullptr), true); return true; } bool NexFriends::requestGetAllInformation(std::function cb) { uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); nexNNAInfo(this->countryCode, 0, nexPrincipalBasicInfo(pid, nnid, nexMiiV2(miiNickname.c_str(), miiData))).writeData(&packetBuffer); nexPresenceV2(0).writeData(&packetBuffer); packetBuffer.writeU64(0x1f18420000); // birthday (set to 1990-1-1) nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 1, &packetBuffer, std::bind(handleResponse_getAllInformation, std::placeholders::_1, this, cb), true); return true; } bool NexFriends::updatePreferencesAsync(nexPrincipalPreference newPreferences, std::function cb) { uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); newPreferences.writeData(&packetBuffer); nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 16, &packetBuffer, [this, cb, newPreferences](nexServiceResponse_t* response) -> void { if (!response->isSuccessful) return cb(NexFriends::ERR_RPC_FAILED); this->myPreference = newPreferences; return cb(NexFriends::ERR_NONE); }, true); // TEST return true; } void NexFriends::getMyPreference(nexPrincipalPreference& preference) { preference = myPreference; } bool NexFriends::updateCommentAsync(nexComment newComment, std::function cb) { uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); newComment.writeData(&packetBuffer); nexCon->callMethod( NEX_PROTOCOL_FRIENDS_WIIU, 15, &packetBuffer, [this, cb, newComment](nexServiceResponse_t* response) -> void { if (!response->isSuccessful) return cb(NexFriends::ERR_RPC_FAILED); this->myComment = newComment; return cb(NexFriends::ERR_NONE); }, true); // TEST return true; } void NexFriends::getMyComment(nexComment& comment) { comment = myComment; } bool NexFriends::addProvisionalFriendByPidGuessed(uint32 principalId) { uint8 tempNexBufferArray[512]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(principalId); cemu_assert_unimplemented(); //nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 2, &packetBuffer, handleResponse_test, this); return true; } // returns true once connection is established and friend list data is available bool NexFriends::isOnline() { return isCurrentlyConnected && hasData; } void NexFriends::handleResponse_acceptFriendRequest(nexService* nex, nexServiceResponse_t* response) { NexFriends* session = (NexFriends*)response->custom; // returns new state of FriendRequest + FriendInfo (if friend request holds no valid data, it means the friend request was successfully accepted?) } void NexFriends::setNotificationHandler(void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid)) { this->notificationHandler = notificationHandler; } void NexFriends::generateNotification(NOTIFICATION_TYPE notificationType, uint32 pid) { if (this->notificationHandler == nullptr) return; this->notificationHandler(notificationType, pid); } // compare previous and current state of friend(-request) lists and generate change-notifications void NexFriends::trackNotifications() { // search for changed and added friend entries for (auto& frNew : list_friends) { bool entryFound = false; for (auto& frPrevious : previousState.list_friends) { if (frNew.nnaInfo.principalInfo.principalId == frPrevious.nnaInfo.principalInfo.principalId) { // todo - scan for changes entryFound = true; break; } } if (entryFound == false) { // friend added generateNotification(NOTIFICATION_TYPE_ADDED_FRIEND, frNew.nnaInfo.principalInfo.principalId); } } // search for removed friends for (auto& frPrevious : previousState.list_friends) { bool entryFound = false; for (auto& frNew : list_friends) { if (frNew.nnaInfo.principalInfo.principalId == frPrevious.nnaInfo.principalInfo.principalId) { entryFound = true; break; } } if (entryFound == false) { // friend removed generateNotification(NOTIFICATION_TYPE_REMOVED_FRIEND, frPrevious.nnaInfo.principalInfo.principalId); } } // search for added friend requests (incoming) for (auto& frqNew : list_friendReqIncoming) { bool entryFound = false; for (auto& frqPrevious : previousState.list_friendReqIncoming) { if (frqNew.principalInfo.principalId == frqPrevious.principalInfo.principalId) { entryFound = true; break; } } if (entryFound == false) { // incoming friend request added generateNotification(NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST, frqNew.principalInfo.principalId); } } // search for removed friend requests (incoming) for (auto& frqPrevious : previousState.list_friendReqIncoming) { bool entryFound = false; for (auto& frqNew : list_friendReqIncoming) { if (frqNew.principalInfo.principalId == frqPrevious.principalInfo.principalId) { entryFound = true; break; } } if (entryFound == false) { // incoming friend request removed generateNotification(NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST, frqPrevious.principalInfo.principalId); } } // search for added friend requests (outgoing) for (auto& frqNew : list_friendReqOutgoing) { bool entryFound = false; for (auto& frqPrevious : previousState.list_friendReqOutgoing) { if (frqNew.principalInfo.principalId == frqPrevious.principalInfo.principalId) { entryFound = true; break; } } if (entryFound == false) { // outgoing friend request added generateNotification(NOTIFICATION_TYPE_ADDED_OUTGOING_REQUEST, frqNew.principalInfo.principalId); } } // search for removed friend requests (outgoing) for (auto& frqPrevious : previousState.list_friendReqOutgoing) { bool entryFound = false; for (auto& frqNew : list_friendReqOutgoing) { if (frqNew.principalInfo.principalId == frqPrevious.principalInfo.principalId) { entryFound = true; break; } } if (entryFound == false) { // outgoing friend request removed generateNotification(NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST, frqPrevious.principalInfo.principalId); } } } void addUniquePidToList(std::vector& pidList, uint32 pid) { bool alreadyAdded = false; for (auto& it2 : pidList) { if (it2 == pid) { alreadyAdded = true; break; } } if (alreadyAdded) return; pidList.push_back(pid); } void NexFriends::getFriendPIDs(uint32* pidList, uint32* pidCount, sint32 offset, sint32 count, bool includeFriendRequests) { if (count < 0) { *pidCount = 0; return; } // parse response and update lists std::vector allPids; std::unique_lock listLock(this->mtx_lists); for (auto& it : this->list_friends) { uint32 pid = it.nnaInfo.principalInfo.principalId; addUniquePidToList(allPids, pid); } if (includeFriendRequests) { // incoming friend requests are not included for (auto& it : this->list_friendReqOutgoing) { uint32 pid = it.principalInfo.principalId; addUniquePidToList(allPids, pid); } } sint32 copyCount = std::max((sint32)allPids.size() - offset, 0); copyCount = std::min(copyCount, count); if (pidList) { for (sint32 i = 0; i < copyCount; i++) pidList[i] = allPids[offset + i]; } *pidCount = copyCount; } void NexFriends::getFriendRequestPIDs(uint32* pidList, uint32* pidCount, sint32 offset, sint32 count, bool includeIncoming, bool includeOutgoing) { if (count < 0) { *pidCount = 0; return; } // parse response and update lists std::vector allPids; std::unique_lock listLock(this->mtx_lists); if (includeIncoming) { for (auto& it : this->list_friendReqIncoming) { uint32 pid = it.principalInfo.principalId; addUniquePidToList(allPids, pid); } } if (includeOutgoing) { for (auto& it : this->list_friendReqOutgoing) { uint32 pid = it.principalInfo.principalId; addUniquePidToList(allPids, pid); } } sint32 copyCount = std::max((sint32)allPids.size() - offset, 0); copyCount = std::min(copyCount, count); if (pidList) { for (sint32 i = 0; i < copyCount; i++) pidList[i] = allPids[offset + i]; } *pidCount = copyCount; } std::string NexFriends::getAccountNameByPid(uint32 principalId) { if (this->pid == principalId) return this->nnid; nexFriend friendData{}; if(getFriendByPID(friendData, principalId)) return friendData.nnaInfo.principalInfo.nnid; nexFriendRequest requestData{}; if (getFriendRequestByPID(requestData, nullptr, principalId)) return requestData.principalInfo.nnid; return {}; } int NexFriends::getPendingFriendRequestCount() { std::unique_lock listLock(this->mtx_lists); return (int)this->list_friendReqIncoming.size(); } bool NexFriends::getFriendByPID(nexFriend& friendData, uint32 searchedPid) { std::unique_lock listLock(this->mtx_lists); for (auto& it : this->list_friends) { uint32 pid = it.nnaInfo.principalInfo.principalId; if (pid == searchedPid) { friendData = it; return true; } } return false; } bool NexFriends::getFriendRequestByPID(nexFriendRequest& friendRequestData, bool* isIncoming, uint32 searchedPid) { std::unique_lock listLock(this->mtx_lists); for (auto& it : this->list_friendReqIncoming) { uint32 pid = it.principalInfo.principalId; if (pid == searchedPid) { friendRequestData = it; if(isIncoming) *isIncoming = true; return true; } } for (auto& it : this->list_friendReqOutgoing) { uint32 pid = it.principalInfo.principalId; if (pid == searchedPid) { friendRequestData = it; if (isIncoming) *isIncoming = false; return true; } } return false; } bool NexFriends::getFriendRequestByMessageId(nexFriendRequest& friendRequestData, bool* isIncoming, uint64 messageId) { std::unique_lock listLock(this->mtx_lists); for (auto& it : this->list_friendReqIncoming) { uint64 mid = it.message.messageId; if(mid == 0) continue; if (mid == messageId) { friendRequestData = it; *isIncoming = true; return true; } } for (auto& it : this->list_friendReqOutgoing) { uint64 mid = it.message.messageId; if (mid == 0) continue; if (mid == messageId) { friendRequestData = it; *isIncoming = false; return true; } } return false; } void addProvisionalFriendHandler(nexServiceResponse_t* nexResponse, std::function cb) { if (nexResponse->isSuccessful) cb(0); else { // todo: Properly handle returned error code and data cb(NexFriends::ERR_RPC_FAILED); } } bool NexFriends::addProvisionalFriend(char* name, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { // not connected cb(ERR_NOT_CONNECTED); return false; } uint8 tempNexBufferArray[128]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeString(name); nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 3, &packetBuffer, std::bind(addProvisionalFriendHandler, std::placeholders::_1, cb), true); return true; } void addFriendRequestHandler(nexServiceResponse_t* nexResponse, NexFriends* nexFriends, std::function cb) { if (nexResponse->isSuccessful) cb(0); else { // todo: Properly handle returned error code cb(NexFriends::ERR_RPC_FAILED); nexFriends->requestGetAllInformation(); // refresh friend list and send add/remove notifications } } void NexFriends::addFriendRequest(uint32 pid, const char* comment, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { // not connected cb(ERR_NOT_CONNECTED); return; } uint8 tempNexBufferArray[2048]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(pid); packetBuffer.writeU8(0); // ukn (language of comment?) packetBuffer.writeString(comment); packetBuffer.writeU8(0); // ukn (language of next string?) packetBuffer.writeString(""); // ukn nexGameKey(0, 0).writeData(&packetBuffer); packetBuffer.writeU64(0); // ukn nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 5, &packetBuffer, std::bind(addFriendRequestHandler, std::placeholders::_1, this, cb), true); } void NexFriends::requestPrincipleBaseInfoByPID(uint32* pidList, sint32 count, const std::function basicInfo)>& cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) return cb(ERR_NOT_CONNECTED, {}); uint8 tempNexBufferArray[512]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(count); for(sint32 i=0; icallMethod(NEX_PROTOCOL_FRIENDS_WIIU, 17, &packetBuffer, [cb, count](nexServiceResponse_t* response) { if (!response->isSuccessful) return cb(NexFriends::ERR_RPC_FAILED, {}); // process result uint32 resultCount = response->data.readU32(); if (resultCount != count) return cb(NexFriends::ERR_UNEXPECTED_RESULT, {}); std::vector nexBasicInfo; nexBasicInfo.resize(count); for (uint32 i = 0; i < resultCount; i++) nexBasicInfo[i].readData(&response->data); if (response->data.hasReadOutOfBounds()) return cb(NexFriends::ERR_UNEXPECTED_RESULT, {}); return cb(NexFriends::ERR_NONE, nexBasicInfo); }, true); } void genericFriendServiceNoResponseHandler(nexServiceResponse_t* nexResponse, std::function cb) { if (nexResponse->isSuccessful) cb(0); else { // todo: Properly handle returned error code cb(NexFriends::ERR_RPC_FAILED); } } void NexFriends::removeFriend(uint32 pid, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { // not connected cb(ERR_NOT_CONNECTED); return; } uint8 tempNexBufferArray[512]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(pid); nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 4, &packetBuffer, [this, cb](nexServiceResponse_t* response) -> void { if (!response->isSuccessful) return cb(NexFriends::ERR_RPC_FAILED); else { cb(NexFriends::ERR_NONE); this->requestGetAllInformation(); // refresh friend list and send add/remove notifications return; } }, true); } void NexFriends::cancelOutgoingProvisionalFriendRequest(uint32 pid, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { // not connected cb(ERR_NOT_CONNECTED); return; } uint8 tempNexBufferArray[512]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(pid); nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 4, &packetBuffer, [cb](nexServiceResponse_t* response) -> void { if (!response->isSuccessful) return cb(NexFriends::ERR_RPC_FAILED); else return cb(NexFriends::ERR_NONE); }, true); } void NexFriends::acceptFriendRequest(uint64 messageId, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) return cb(ERR_NOT_CONNECTED); uint8 tempNexBufferArray[128]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU64(messageId); nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 7, &packetBuffer, [cb](nexServiceResponse_t* response) -> void { if (!response->isSuccessful) return cb(NexFriends::ERR_RPC_FAILED); else return cb(NexFriends::ERR_NONE); }, true); } void NexFriends::deleteFriendRequest(uint64 messageId, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) return cb(ERR_NOT_CONNECTED); uint8 tempNexBufferArray[128]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU64(messageId); nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 8, &packetBuffer, [this, cb](nexServiceResponse_t* response) -> void { if (!response->isSuccessful) return cb(NexFriends::ERR_RPC_FAILED); cb(NexFriends::ERR_NONE); this->requestGetAllInformation(); // refresh friend list and send add/remove notifications }, true); } void NexFriends::markFriendRequestsAsReceived(uint64* messageIdList, sint32 count, std::function cb) { if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) return cb(ERR_NOT_CONNECTED); uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); packetBuffer.writeU32(count); for(sint32 i=0; icallMethod(NEX_PROTOCOL_FRIENDS_WIIU, 10, &packetBuffer, [cb](nexServiceResponse_t* response) -> void { if (!response->isSuccessful) return cb(NexFriends::ERR_RPC_FAILED); else return cb(NexFriends::ERR_NONE); }, true); } void NexFriends::updateMyPresence(nexPresenceV2& myPresence) { this->myPresence = myPresence; if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) == 0x00050000) { myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId(); myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion(); } else { myPresence.gameKey.titleId = 0; // icon will not be ??? or invalid to others myPresence.gameKey.ukn = 0; } if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { // not connected return; } uint8 tempNexBufferArray[1024]; nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); myPresence.writeData(&packetBuffer); nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 13, &packetBuffer, +[](nexServiceResponse_t* nexResponse){}, false); } void NexFriends::update() { std::unique_lock loginLock(mtx_login); if (!isCurrentlyConnected) { if (loginInProcess) { // wait for login attempt to finish } else { // should we try to reconnect? uint32 timeSinceLastLoginAttempt = prudpGetMSTimestamp() - this->lastLoginAttemptTime; uint32 delayTime = 30; // 30 seconds by default if (this->numFailedLogins < 3) delayTime = 30; else { if (this->numSuccessfulLogins == 0) return; // never try again if (this->numFailedLogins >= 10) return; // stop after 10 failed attempts delayTime = 60 + (this->numFailedLogins - 3) * 60; // add one minute each time it fails } if (timeSinceLastLoginAttempt < delayTime) return; cemuLog_log(LogType::Force, "NEX: Attempt async friend service login"); initiateLogin(); } } else { if (this->nexCon == nullptr || this->nexCon->getState() != nexService::STATE_CONNECTED) { cemuLog_log(LogType::Force, "NEX: Lost friend server session"); if (this->nexCon) { this->nexCon->destroy(); this->nexCon = nullptr; } isCurrentlyConnected = false; } } } ================================================ FILE: src/Cemu/nex/nexFriends.h ================================================ #pragma once #ifndef FFL_SIZE #define FFL_SIZE 0x60 #endif class nexGameKey : public nexType { public: nexGameKey() = default; nexGameKey(uint64 titleId, uint16 ukn) { this->titleId = titleId; this->ukn = ukn; } nexGameKey(nexPacketBuffer* pb) { readData(pb); } void writeData(nexPacketBuffer* pb) const override { pb->writeU64(titleId); pb->writeU16(ukn); } void readData(nexPacketBuffer* pb) override { titleId = pb->readU64(); ukn = pb->readU16(); } public: uint64 titleId; uint16 ukn; }; class nexMiiV2 : public nexType { public: nexMiiV2() { miiNickname[0] = '\0'; } nexMiiV2(const char* miiNickname, uint8* miiData) { strncpy(this->miiNickname, miiNickname, 127); this->miiNickname[127] = '\0'; memcpy(this->miiData, miiData, FFL_SIZE); } nexMiiV2(nexPacketBuffer* pb) { readData(pb); } void writeData(nexPacketBuffer* pb) const override { pb->writeString(this->miiNickname); pb->writeU8(0); // ukn pb->writeU8(0); // ukn pb->writeBuffer(this->miiData, FFL_SIZE); pb->writeU64(0); // ukn } void readData(nexPacketBuffer* pb) override { pb->readString(this->miiNickname, sizeof(this->miiNickname)); this->miiNickname[127] = '\0'; pb->readU8(); // ukn pb->readU8(); // ukn memset(miiData, 0, sizeof(miiData)); pb->readBuffer(this->miiData, FFL_SIZE); pb->readU64(); // ukn } public: uint8 miiData[FFL_SIZE]; char miiNickname[128]; }; class nexPresenceV2 : public nexType { public: nexPresenceV2() { } nexPresenceV2(int ukn) { // todo } nexPresenceV2(nexPacketBuffer* pb) { readData(pb); } const char* getMetaName() override { return "NintendoPresenceV2"; } void writeData(nexPacketBuffer* pb) const override { pb->writeU32(1); // isValid? pb->writeU8(isOnline ? 1 : 0); // gameKey: pb->writeU64(gameKey.titleId); pb->writeU16(gameKey.ukn); pb->writeU8(1); // uint8 ukn3(example value : 1) pb->writeString("");// String msg(current game ? ) pb->writeU32(joinFlagMask); pb->writeU8(joinAvailability); pb->writeU32(gameId); pb->writeU32(gameMode); pb->writeU32(hostPid); pb->writeU32(groupId); pb->writeBuffer(appSpecificData, sizeof(appSpecificData)); pb->writeU8(3); // ukn pb->writeU8(1); // ukn pb->writeU8(3); // ukn } void readData(nexPacketBuffer* pb) override { ukn0 = pb->readU32(); isOnline = pb->readU8(); gameKey.readData(pb); ukn3 = pb->readU8(); char msgBuffer[1024]; pb->readString(msgBuffer, sizeof(msgBuffer)); msgBuffer[1023] = '\0'; this->msg = std::string(msgBuffer); joinFlagMask = pb->readU32(); joinAvailability = pb->readU8(); gameId = pb->readU32(); gameMode = pb->readU32(); hostPid = pb->readU32(); groupId = pb->readU32(); pb->readBuffer(appSpecificData, sizeof(appSpecificData)); ukn11 = pb->readU8(); ukn12 = pb->readU8(); ukn13 = pb->readU8(); } public: uint32 ukn0; // seen values: 0 -> offline, 1 -> online (menu, but not always), 8 and 0x1E6 -> in Splatoon uint8 isOnline; nexGameKey gameKey; uint8 ukn3; // message language? std::string msg; uint32 joinFlagMask; uint8 joinAvailability; uint32 gameId; uint32 gameMode; uint32 hostPid; uint32 groupId; uint8 appSpecificData[0x14]; uint8 ukn11; uint8 ukn12; uint8 ukn13; }; class nexPrincipalBasicInfo : public nexType { public: nexPrincipalBasicInfo() { this->nnid[0] = '\0'; } nexPrincipalBasicInfo(uint32 principalId, char* nnid, const nexMiiV2& mii) : principalId(principalId), mii(mii) { strcpy(this->nnid, nnid); } nexPrincipalBasicInfo(nexPacketBuffer* pb) { readData(pb); } void writeData(nexPacketBuffer* pb) const override { pb->writeU32(principalId); pb->writeString(this->nnid); mii.writeData(pb); pb->writeU8(2); // todo } void readData(nexPacketBuffer* pb) override { principalId = pb->readU32(); pb->readString(nnid, sizeof(nnid)); mii.readData(pb); regionGuessed = pb->readU8(); } public: uint32 principalId; char nnid[32]; nexMiiV2 mii; uint8 regionGuessed; }; class nexNNAInfo : public nexType { public: nexNNAInfo() { } nexNNAInfo(uint8 countryCode, uint8 countrySubCode, const nexPrincipalBasicInfo& principalBasicInfo) : countryCode(countryCode), countrySubCode(countrySubCode), principalInfo(principalBasicInfo) { } nexNNAInfo(nexPacketBuffer* pb) { readData(pb); } void writeData(nexPacketBuffer* pb) const override { principalInfo.writeData(pb); pb->writeU8(countryCode); pb->writeU8(countrySubCode); } void readData(nexPacketBuffer* pb) override { principalInfo.readData(pb); countryCode = pb->readU8(); countrySubCode = pb->readU8(); } public: uint8 countryCode; uint8 countrySubCode; nexPrincipalBasicInfo principalInfo; }; class nexPrincipalPreference : public nexType { public: nexPrincipalPreference() = default; nexPrincipalPreference(uint8 ukn0, uint8 ukn1, uint8 ukn2) { this->showOnline = ukn0; this->showGame = ukn1; this->blockFriendRequests = ukn2; } nexPrincipalPreference(nexPacketBuffer* pb) { readData(pb); } void writeData(nexPacketBuffer* pb) const override { pb->writeU8(showOnline); pb->writeU8(showGame); pb->writeU8(blockFriendRequests); } void readData(nexPacketBuffer* pb) override { showOnline = pb->readU8(); showGame = pb->readU8(); blockFriendRequests = pb->readU8(); } public: uint8 showOnline; uint8 showGame; uint8 blockFriendRequests; }; class nexComment : public nexType { public: nexComment() { } nexComment(uint8 todo) { cemu_assert_unimplemented(); } nexComment(nexPacketBuffer* pb) { readData(pb); } void writeData(nexPacketBuffer* pb) const override { pb->writeU8(ukn0); pb->writeString(commentString.c_str()); pb->writeU64(ukn1); } void readData(nexPacketBuffer* pb) override { ukn0 = pb->readU8(); char stringBuffer[1024]; pb->readString(stringBuffer, 1024); stringBuffer[1023] = '\0'; commentString = std::string(stringBuffer); ukn1 = pb->readU64(); } public: uint8 ukn0; std::string commentString; uint64 ukn1; }; class nexFriendRequestMessage : public nexType { public: nexFriendRequestMessage() { } nexFriendRequestMessage(uint8 todo) { cemu_assert_unimplemented(); } nexFriendRequestMessage(nexPacketBuffer* pb) { readData(pb); } void writeData(nexPacketBuffer* pb) const override { cemu_assert_unimplemented(); } void readData(nexPacketBuffer* pb) override { messageId = pb->readU64(); isMarkedAsReceived = pb->readU8(); ukn2 = pb->readU8(); char stringBuffer[1024]; pb->readString(stringBuffer, sizeof(stringBuffer)); stringBuffer[1023] = '\0'; commentStr = std::string(stringBuffer); ukn4 = pb->readU8(); pb->readString(stringBuffer, sizeof(stringBuffer)); stringBuffer[1023] = '\0'; ukn5Str = std::string(stringBuffer); gameKey.readData(pb); ukn7 = pb->readU64(); expireTimestamp = pb->readU64(); } public: uint64 messageId; uint8 isMarkedAsReceived; uint8 ukn2; std::string commentStr; uint8 ukn4; std::string ukn5Str; nexGameKey gameKey; uint64 ukn7; uint64 expireTimestamp; }; class nexFriendRequest : public nexType { public: nexFriendRequest() { } nexFriendRequest(uint64 titleId, uint16 ukn) { cemu_assert_unimplemented(); } nexFriendRequest(nexPacketBuffer* pb) { readData(pb); } const char* getMetaName() override { return "FriendRequest"; } void writeData(nexPacketBuffer* pb) const override { cemu_assert_unimplemented(); } void readData(nexPacketBuffer* pb) override { principalInfo.readData(pb); message.readData(pb); ukn = pb->readU64(); } public: nexPrincipalBasicInfo principalInfo; nexFriendRequestMessage message; uint64 ukn; }; class nexFriend : public nexType { public: nexFriend() { } nexFriend(nexPacketBuffer* pb) { readData(pb); } const char* getMetaName() override { return "FriendInfo"; } void writeData(nexPacketBuffer* pb) const override { cemu_assert_unimplemented(); } void readData(nexPacketBuffer* pb) override { nnaInfo.readData(pb); presence.readData(pb); comment.readData(pb); friendsSinceTimestamp = pb->readU64(); lastOnlineTimestamp = pb->readU64(); ukn6 = pb->readU64(); } public: nexNNAInfo nnaInfo; nexPresenceV2 presence; nexComment comment; uint64 friendsSinceTimestamp; uint64 lastOnlineTimestamp; uint64 ukn6; }; class nexBlacklisted : public nexType { public: nexBlacklisted() { } nexBlacklisted(nexPacketBuffer* pb) { readData(pb); } void writeData(nexPacketBuffer* pb) const override { cemu_assert_unimplemented(); } void readData(nexPacketBuffer* pb) override { basicInfo.readData(pb); gameKey.readData(pb); ukn = pb->readU64(); } public: nexPrincipalBasicInfo basicInfo; nexGameKey gameKey; uint64 ukn; }; class nexPersistentNotification : public nexType { public: nexPersistentNotification() { } nexPersistentNotification(nexPacketBuffer* pb) { readData(pb); } void writeData(nexPacketBuffer* pb) const override { cemu_assert_unimplemented(); } void readData(nexPacketBuffer* pb) override { messageId = pb->readU64(); pid1 = pb->readU32(); pid2 = pb->readU32(); type = pb->readU32(); pb->readStdString(msg); } public: uint64 messageId; uint32 pid1; // principal id 1 (might differ depending on type) uint32 pid2; // principal id 2 (might differ depending on type) uint32 type; std::string msg; }; class NexFriends { public: using RpcErrorCode = int; // replace with enum class later static const int ERR_NONE = 0; static const int ERR_RPC_FAILED = 1; static const int ERR_UNEXPECTED_RESULT = 2; static const int ERR_NOT_CONNECTED = 3; enum NOTIFICATION_TYPE { NOTIFICATION_TYPE_ONLINE = 0, NOTIFICATION_TYPE_FRIEND_LOGIN = 4, NOTIFICATION_TYPE_FRIEND_LOGOFF = 5, NOTIFICATION_TYPE_FRIEND_PRESENCE_CHANGE = 6, NOTIFICATION_TYPE_ADDED_FRIEND = 9, NOTIFICATION_TYPE_REMOVED_FRIEND = 10, NOTIFICATION_TYPE_ADDED_OUTGOING_REQUEST = 11, NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST = 12, NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST = 17, NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST = 18 }; public: NexFriends(uint32 authServerIp, uint16 authServerPort, const char* accessKey, uint32 pid, const char* nexPassword, const char* nexToken, const char* nnid, uint8* miiData, const wchar_t* miiNickname, uint8 countryCode, nexPresenceV2& myPresence); ~NexFriends(); std::string getAccountNameByPid(uint32 principalId); int getPendingFriendRequestCount(); bool requestGetAllInformation(); bool addProvisionalFriendByPidGuessed(uint32 principalId); // synchronous API (returns immediately) bool requestGetAllInformation(std::function cb); void getFriendPIDs(uint32* pidList, uint32* pidCount, sint32 offset, sint32 count, bool includeFriendRequests); void getFriendRequestPIDs(uint32* pidList, uint32* pidCount, sint32 offset, sint32 count, bool includeIncoming, bool includeOutgoing); bool getFriendByPID(nexFriend& friendData, uint32 pid); bool getFriendRequestByPID(nexFriendRequest& friendRequestData, bool* isIncoming, uint32 searchedPid); bool getFriendRequestByMessageId(nexFriendRequest& friendRequestData, bool* isIncoming, uint64 messageId); bool isOnline(); void getMyPreference(nexPrincipalPreference& preference); void getMyComment(nexComment& comment); // asynchronous API (data has to be requested) bool addProvisionalFriend(char* name, std::function cb); void addFriendRequest(uint32 pid, const char* comment, std::function cb); void requestPrincipleBaseInfoByPID(uint32* pidList, sint32 count, const std::function basicInfo)>& cb); void removeFriend(uint32 pid, std::function cb); void cancelOutgoingProvisionalFriendRequest(uint32 pid, std::function cb); void markFriendRequestsAsReceived(uint64* messageIdList, sint32 count, std::function cb); void acceptFriendRequest(uint64 messageId, std::function cb); void deleteFriendRequest(uint64 messageId, std::function cb); // rejecting incoming friend request (differs from blocking friend requests) bool updatePreferencesAsync(const nexPrincipalPreference newPreferences, std::function cb); bool updateCommentAsync(const nexComment newComment, std::function cb); void updateMyPresence(nexPresenceV2& myPresence); void setNotificationHandler(void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid)); void update(); void processServerNotification(uint32 notificationType, uint32 pid, nexPacketBuffer* notificationData); private: void doAsyncLogin(); void initiateLogin(); static void handleResponse_acceptFriendRequest(nexService* nex, nexServiceResponse_t* response); static void handleResponse_getAllInformation(nexServiceResponse_t* response, NexFriends* nexFriends, std::function cb); void generateNotification(NOTIFICATION_TYPE notificationType, uint32 pid); void trackNotifications(); // notification-service handlers void processServerNotification_friendOffline(uint32 pid); void processServerNotification_presenceChange(uint32 pid, nexPresenceV2& presence); void processServerNotification_removeFriendOrFriendRequest(uint32 pid); void processServerNotification_incomingFriendRequest(uint32 pid, nexFriendRequest& friendRequest); void processServerNotification_addedFriend(uint32 pid, nexFriend& frd); private: void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid); bool isCurrentlyConnected; bool hasData; // set after connect when information request response was received bool firstInformationRequest; nexService* nexCon; uint8 miiData[FFL_SIZE]; std::string miiNickname; char nnid[96]; uint32 pid; uint8 countryCode; // login tracking std::recursive_mutex mtx_login; bool loginInProcess; uint32 lastLoginAttemptTime; uint32 numFailedLogins; uint32 numSuccessfulLogins; // auth struct { uint32 serverIp; uint16 port; std::string accessKey; std::string nexPassword; std::string nexToken; }auth; // local friend state nexPresenceV2 myPresence; nexPrincipalPreference myPreference; nexComment myComment; std::recursive_mutex mtx_lists; std::vector list_friends; std::vector list_friendReqOutgoing; std::vector list_friendReqIncoming; struct { std::vector list_friends; std::vector list_friendReqOutgoing; std::vector list_friendReqIncoming; }previousState; }; ================================================ FILE: src/Cemu/nex/nexThread.cpp ================================================ #include "prudp.h" #include "nex.h" #include "nexThread.h" std::mutex mtx_queuedServices; std::vector list_queuedServices; std::vector list_activeNexServices; void nexThread_run() { while (true) { // check for new services mtx_queuedServices.lock(); for(auto& it : list_queuedServices) list_activeNexServices.push_back(it); list_queuedServices.clear(); mtx_queuedServices.unlock(); // if service list is empty then pause if (list_activeNexServices.empty()) { std::this_thread::sleep_for(std::chrono::milliseconds(200)); continue; } // update for (auto& it : list_activeNexServices) { it->update(); } // delete services marked for destruction sint32 idx = 0; sint32 listSize = (sint32)list_activeNexServices.size(); while (idx < listSize) { if (list_activeNexServices[idx]->isMarkedForDestruction()) { list_activeNexServices[idx]->destroy(); listSize--; list_activeNexServices[idx] = list_activeNexServices[listSize]; } idx++; } if (listSize != list_activeNexServices.size()) { list_activeNexServices.resize(listSize); } // idle for short time // todo - find a better way to handle this with lower latency. Maybe using select() with loopback socket for interrupting the select on service change std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } bool _nexThreadLaunched = false; std::thread::id nexThreadId; void nexThread_init() { if (_nexThreadLaunched) return; std::thread t(nexThread_run); nexThreadId = t.get_id(); t.detach(); _nexThreadLaunched = true; } void nexThread_registerService(nexService* service) { mtx_queuedServices.lock(); nexThread_init(); list_queuedServices.push_back(service); mtx_queuedServices.unlock(); } bool nexThread_isCurrentThread() { if (_nexThreadLaunched == false) return false; return std::this_thread::get_id() == nexThreadId; } ================================================ FILE: src/Cemu/nex/nexThread.h ================================================ #pragma once void nexThread_registerService(nexService* service); bool nexThread_isCurrentThread(); ================================================ FILE: src/Cemu/nex/nexTypes.h ================================================ #pragma once #include #ifndef FFL_SIZE #define FFL_SIZE (0x60) #endif class nexPacketBuffer; class nexMetaType { public: virtual const char* getMetaName() const = 0; virtual void writeData(nexPacketBuffer* pb) const = 0; }; class nexType { public: virtual ~nexType(){}; virtual const char* getMetaName() { cemu_assert_unimplemented(); return ""; } virtual void writeData(nexPacketBuffer* pb) const = 0; virtual void readData(nexPacketBuffer* pb) = 0; }; class nexPacketBuffer { public: nexPacketBuffer(uint8* data, sint32 size, bool isWrite) { this->buffer = data; this->size = size; this->currentIndex = 0; this->isWrite = isWrite; this->readOutOfBounds = false; } nexPacketBuffer() { this->buffer = 0; this->size = 0; this->currentIndex = 0; } void writeData(const uint8* data, sint32 len) { if (this->currentIndex + len > this->size) return; memcpy(this->buffer + this->currentIndex, data, len); this->currentIndex += len; } void writeU8(uint8 v) { if (this->currentIndex + sizeof(uint8) > this->size) return; *(uint8*)(this->buffer + this->currentIndex) = v; this->currentIndex += sizeof(uint8); } void writeU16(uint16 v) { if (this->currentIndex + sizeof(uint16) > this->size) return; *(uint16*)(this->buffer + this->currentIndex) = v; this->currentIndex += sizeof(uint16); } void writeU32(uint32 v) { if (this->currentIndex + sizeof(uint32) > this->size) return; *(uint32*)(this->buffer + this->currentIndex) = v; this->currentIndex += sizeof(uint32); } void writeU64(uint64 v) { if (this->currentIndex + sizeof(uint64) > this->size) return; *(uint64*)(this->buffer + this->currentIndex) = v; this->currentIndex += sizeof(uint64); } void writeString(const char* v) { sint32 len = (sint32)strlen(v) + 1; writeU16(len); writeData((uint8*)v, len); } void writeBuffer(const uint8* v, sint32 len) { writeU32(len); writeData(v, len); } void writeCustomType(const nexMetaType& v) { // write meta name writeString(v.getMetaName()); // write length 0 placeholder uint32* lengthPtr0 = (uint32*)(this->buffer + this->currentIndex); writeU32(0); // write length 1 placeholder uint32* lengthPtr1 = (uint32*)(this->buffer + this->currentIndex); writeU32(0); // write data uint32 preWriteIndex = this->currentIndex; v.writeData(this); uint32 writeSize = this->currentIndex - preWriteIndex; // update lengths *lengthPtr1 = writeSize; *lengthPtr0 = writeSize + 4; } uint64 readU64() { if (this->currentIndex + sizeof(uint64) > this->size) { readOutOfBounds = true; return 0; } uint64 v = *(uint64*)(this->buffer + this->currentIndex); this->currentIndex += sizeof(uint64); return v; } uint32 readU32() { if (this->currentIndex + sizeof(uint32) > this->size) { readOutOfBounds = true; return 0; } uint32 v = *(uint32*)(this->buffer + this->currentIndex); this->currentIndex += sizeof(uint32); return v; } uint16 readU16() { if (this->currentIndex + sizeof(uint16) > this->size) { readOutOfBounds = true; return 0; } uint16 v = *(uint16*)(this->buffer + this->currentIndex); this->currentIndex += sizeof(uint16); return v; } uint8 readU8() { if (this->currentIndex + sizeof(uint8) > this->size) { readOutOfBounds = true; return 0; } uint8 v = *(uint8*)(this->buffer + this->currentIndex); this->currentIndex += sizeof(uint8); return v; } sint32 readData(void* buffer, sint32 length) { if (this->currentIndex + length > this->size) { readOutOfBounds = true; return 0; } memcpy(buffer, (this->buffer + this->currentIndex), length); this->currentIndex += length; return length; } // reads buffer data from packet and returns amount of bytes copied into buffer // if buffer is larger than maxLength, the read data is silently truncated sint32 readBuffer(void* buffer, sint32 maxLength) { sint32 bufferLength = (sint32)readU32(); if (bufferLength < 0 || bufferLength >= 0x10000000) { readOutOfBounds = true; return 0; } if (this->currentIndex + bufferLength > this->size) { readOutOfBounds = true; return 0; } uint32 copiedLength = std::min(bufferLength, maxLength); memcpy(buffer, (this->buffer + this->currentIndex), copiedLength); this->currentIndex += bufferLength; return copiedLength; } sint32 readString(char* buffer, sint32 maxLength) { sint32 bufferLength = readU16(); if (this->currentIndex + bufferLength > this->size) { readOutOfBounds = true; buffer[0] = '\0'; return 0; } uint32 copiedLength = std::min(bufferLength, maxLength - 1); memcpy(buffer, (this->buffer + this->currentIndex), copiedLength); buffer[copiedLength] = '\0'; this->currentIndex += bufferLength; return copiedLength; } sint32 readStdString(std::string& outputStr) { sint32 bufferLength = readU16(); if (this->currentIndex + bufferLength > this->size) { readOutOfBounds = true; outputStr.clear(); return 0; } sint32 copiedLength = bufferLength; if (bufferLength > 0 && this->buffer[this->currentIndex+bufferLength-1] == '\0') { // cut off trailing '\0' copiedLength--; } outputStr = std::string((char*)(this->buffer + this->currentIndex), copiedLength); this->currentIndex += bufferLength; return copiedLength; } bool readPlaceholderType(nexType& v) { char name[128]; readString(name, sizeof(name)); name[sizeof(name)-1] = '\0'; if (hasReadOutOfBounds()) return false; if (strcmp(name, v.getMetaName()) != 0) return false; // read sizes uint32 len0 = readU32(); uint32 len1 = readU32(); if (hasReadOutOfBounds()) return false; if (len1 != (len0 - 4)) return false; // parse type data v.readData(this); if (hasReadOutOfBounds()) return false; return true; //// write meta name //writeString(v.getMetaName()); //// write length 0 placeholder //uint32* lengthPtr0 = (uint32*)(this->buffer + this->currentIndex); //writeU32(0); //// write length 1 placeholder //uint32* lengthPtr1 = (uint32*)(this->buffer + this->currentIndex); //writeU32(0); //// write data //uint32 preWriteIndex = this->currentIndex; //v.writeData(this); //uint32 writeSize = this->currentIndex - preWriteIndex; //// update lengths //*lengthPtr1 = writeSize; //*lengthPtr0 = writeSize + 4; } bool hasReadOutOfBounds() { return readOutOfBounds; } uint8* getDataPtr() { return buffer; } sint32 getWriteIndex() { return currentIndex; } sint32 getSize() { return size; } void setSize(sint32 length) { size = length; } private: uint8* buffer; sint32 size; sint32 currentIndex; bool isWrite; bool readOutOfBounds; // set if any read operation failed because it exceeded the buffer }; class nexNintendoLoginData : public nexMetaType { public: nexNintendoLoginData(const char* nexToken) { this->nexToken = new std::string(nexToken); } const char* getMetaName() const override { return "NintendoLoginData"; } void writeData(nexPacketBuffer* pb) const override { pb->writeString(nexToken->c_str()); } private: std::string* nexToken; }; class nexNotificationEventGeneral : public nexType { public: nexNotificationEventGeneral() { } nexNotificationEventGeneral(nexPacketBuffer* pb) { readData(pb); } const char* getMetaName() override { return "NintendoNotificationEventGeneral"; } void writeData(nexPacketBuffer* pb) const override { cemu_assert_unimplemented(); } void readData(nexPacketBuffer* pb) override { param1 = pb->readU32(); param2 = pb->readU64(); param3 = pb->readU64(); pb->readStdString(paramStr); } public: uint32 param1; uint64 param2; uint64 param3; std::string paramStr; }; ================================================ FILE: src/Cemu/nex/prudp.cpp ================================================ #include "prudp.h" #include "util/crypto/md5.h" #include #include #include static void KSA(unsigned char* key, int keyLen, unsigned char* S) { for (int i = 0; i < RC4_N; i++) S[i] = i; int j = 0; for (int i = 0; i < RC4_N; i++) { j = (j + S[i] + key[i % keyLen]) % RC4_N; std::swap(S[i], S[j]); } } static void PRGA(unsigned char* S, unsigned char* input, int len, unsigned char* output) { for (size_t n = 0; n < len; n++) { int i = (i + 1) % RC4_N; int j = (j + S[i]) % RC4_N; std::swap(S[i], S[j]); int rnd = S[(S[i] + S[j]) % RC4_N]; output[n] = rnd ^ input[n]; } } static void RC4(char* key, unsigned char* input, int len, unsigned char* output) { unsigned char S[RC4_N]; KSA((unsigned char*)key, (int)strlen(key), S); PRGA(S, input, len, output); } void RC4_initCtx(RC4Ctx* rc4Ctx, const char* key) { rc4Ctx->i = 0; rc4Ctx->j = 0; KSA((unsigned char*)key, (int)strlen(key), rc4Ctx->S); } void RC4_initCtx(RC4Ctx* rc4Ctx, unsigned char* key, int keyLen) { rc4Ctx->i = 0; rc4Ctx->j = 0; KSA(key, keyLen, rc4Ctx->S); } void RC4_transform(RC4Ctx* rc4Ctx, unsigned char* input, int len, unsigned char* output) { int i = rc4Ctx->i; int j = rc4Ctx->j; for (size_t n = 0; n < len; n++) { i = (i + 1) % RC4_N; j = (j + rc4Ctx->S[i]) % RC4_N; std::swap(rc4Ctx->S[i], rc4Ctx->S[j]); int rnd = rc4Ctx->S[(rc4Ctx->S[i] + rc4Ctx->S[j]) % RC4_N]; output[n] = rnd ^ input[n]; } rc4Ctx->i = i; rc4Ctx->j = j; } uint32 prudpGetMSTimestamp() { return GetTickCount(); } std::mt19937_64 prudpRG(GetTickCount()); // workaround for static asserts when using uniform_int_distribution (see https://github.com/cemu-project/Cemu/issues/48) boost::random::uniform_int_distribution prudpRandomDistribution8(0, 0xFF); boost::random::uniform_int_distribution prudpRandomDistributionPortGen(0, 10000); uint8 prudp_generateRandomU8() { return prudpRandomDistribution8(prudpRG); } uint32 prudp_generateRandomU32() { uint32 v = prudp_generateRandomU8(); v <<= 8; v |= prudp_generateRandomU8(); v <<= 8; v |= prudp_generateRandomU8(); v <<= 8; v |= prudp_generateRandomU8(); return v; } std::bitset<10000> _portUsageMask; static uint16 AllocateRandomSrcPRUDPPort() { while (true) { sint32 p = prudpRandomDistributionPortGen(prudpRG); if (_portUsageMask.test(p)) continue; _portUsageMask.set(p); return 40000 + p; } } static void ReleasePRUDPSrcPort(uint16 port) { cemu_assert_debug(port >= 40000); uint32 bitIndex = port - 40000; cemu_assert_debug(_portUsageMask.test(bitIndex)); _portUsageMask.reset(bitIndex); } static uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length) { uint32 checksum32 = 0; for (sint32 i = 0; i < length / 4; i++) { checksum32 += *(uint32*)(data + i * 4); } uint8 checksum = checksumBase; for (sint32 i = length & (~3); i < length; i++) { checksum += data[i]; } checksum += (uint8)((checksum32 >> 0) & 0xFF); checksum += (uint8)((checksum32 >> 8) & 0xFF); checksum += (uint8)((checksum32 >> 16) & 0xFF); checksum += (uint8)((checksum32 >> 24) & 0xFF); return checksum; } // calculate the actual size of the packet from the unprocessed raw data // returns 0 on error sint32 prudpPacket::calculateSizeFromPacketData(uint8* data, sint32 length) { // check if packet is long enough to hold basic header if (length < 0xB) return 0; // get flags fields uint16 typeAndFlags = *(uint16*)(data + 0x02); uint16 type = (typeAndFlags & 0xF); uint16 flags = (typeAndFlags >> 4); if ((flags & FLAG_HAS_SIZE) == 0) return length; // without a size field, we cant calculate the length sint32 calculatedSize; if (type == TYPE_SYN) { if (length < (0xB + 0x4 + 2)) return 0; uint16 payloadSize = *(uint16*)(data + 0xB + 0x4); calculatedSize = 0xB + 0x4 + 2 + (sint32)payloadSize + 1; // base header + connection signature (SYN param) + payloadSize field + checksum after payload if (calculatedSize > length) return 0; return calculatedSize; } else if (type == TYPE_CON) { if (length < (0xB + 0x4 + 2)) return 0; uint16 payloadSize = *(uint16*)(data + 0xB + 0x4); calculatedSize = 0xB + 0x4 + 2 + (sint32)payloadSize + 1; // base header + connection signature (CON param) + payloadSize field + checksum after payload // note: For secure connections the extra data is part of the payload if (calculatedSize > length) return 0; return calculatedSize; } else if (type == TYPE_DATA) { if (length < (0xB + 1 + 2)) return 0; uint16 payloadSize = *(uint16*)(data + 0xB + 1); calculatedSize = 0xB + 1 + 2 + (sint32)payloadSize + 1; // base header + fragmentIndex (DATA param) + payloadSize field + checksum after payload if (calculatedSize > length) return 0; return calculatedSize; } else if (type == TYPE_PING) { if (length < (0xB + 2)) return 0; uint16 payloadSize = *(uint16*)(data + 0xB); calculatedSize = 0xB + 2 + (sint32)payloadSize + 1; // base header + payloadSize field + checksum after payload if (calculatedSize > length) return 0; return calculatedSize; } else assert_dbg(); // unknown packet type (todo - add disconnect and ping packet, then make this function return 0 for all unknown types) return length; } prudpPacket::prudpPacket(prudpStreamSettings* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature) { this->src = src; this->dst = dst; this->type = type; this->flags = flags; this->sessionId = sessionId; this->m_sequenceId = sequenceId; this->specifiedPacketSignature = packetSignature; this->streamSettings = streamSettings; this->fragmentIndex = 0; this->isEncrypted = false; } bool prudpPacket::requiresAck() { return (flags & FLAG_NEED_ACK) != 0; } sint32 prudpPacket::buildData(uint8* output, sint32 maxLength) { // PRUDP V0 // encrypt packet data if (this->type == prudpPacket::TYPE_DATA) { // dont save new rc4 state if the packet is not reliable or requires no ack? if (packetData.size() > 0) { if (isEncrypted == false) // only encrypt once { RC4_transform(&streamSettings->rc4Client, &packetData.front(), (int)packetData.size(), &packetData.front()); isEncrypted = true; } } } // build packet uint8* packetBuffer = output; sint32 writeIndex = 0; // write constant header *(uint8*)(packetBuffer + 0x00) = src; *(uint8*)(packetBuffer + 0x01) = dst; uint16 typeAndFlags = (this->flags << 4) | (this->type); *(uint16*)(packetBuffer + 0x02) = typeAndFlags; *(uint8*)(packetBuffer + 0x04) = sessionId; *(uint32*)(packetBuffer + 0x05) = packetSignature(); *(uint16*)(packetBuffer + 0x09) = m_sequenceId; writeIndex = 0xB; // variable fields if (this->type == TYPE_SYN) { *(uint32*)(packetBuffer + writeIndex) = 0; // connection signature (always 0 for SYN packet) writeIndex += 4; } else if (this->type == TYPE_CON) { // connection signature (+ kerberos data if secure connection) memcpy(packetBuffer + writeIndex, &packetData.front(), packetData.size()); writeIndex += (int)packetData.size(); } else if (this->type == TYPE_DATA) { *(uint8*)(packetBuffer + writeIndex) = fragmentIndex; // fragment index writeIndex += 1; if (packetData.empty() == false) { memcpy(packetBuffer + writeIndex, &packetData.front(), packetData.size()); writeIndex += (int)packetData.size(); } } else if (this->type == TYPE_PING) { // no data } else { cemu_assert_suspicious(); } // checksum *(uint8*)(packetBuffer + writeIndex) = calculateChecksum(packetBuffer, writeIndex); writeIndex++; return writeIndex; } uint32 prudpPacket::packetSignature() { if (type == TYPE_SYN) return 0; else if (type == TYPE_CON || type == TYPE_PING) return specifiedPacketSignature; else if (type == TYPE_DATA) { if (packetData.empty()) return 0x12345678; HMACMD5Ctx ctx; hmacMD5_init_limK_to_64(streamSettings->accessKeyDigest, 16, &ctx); hmacMD5_update(&packetData.front(), (int)packetData.size(), &ctx); uint8 digest[16]; hmacMD5_final(digest, &ctx); uint32 pSig = *(uint32*)digest; return pSig; } assert_dbg(); return 0; } void prudpPacket::setData(uint8* data, sint32 length) { packetData.assign(data, data + length); } void prudpPacket::setFragmentIndex(uint8 fragmentIndex) { this->fragmentIndex = fragmentIndex; } uint8 prudpPacket::calculateChecksum(uint8* data, sint32 length) { return prudp_calculateChecksum(streamSettings->checksumBase, data, length); } prudpIncomingPacket::prudpIncomingPacket() { src = 0; dst = 0; flags = 0; type = 0; sessionId = 0; packetSignature = 0; sequenceId = 0; fragmentIndex = 0; hasData = false; isInvalid = false; streamSettings = nullptr; } prudpIncomingPacket::prudpIncomingPacket(prudpStreamSettings* streamSettings, uint8* data, sint32 length) : prudpIncomingPacket() { if (length < 0xB + 1) { isInvalid = true; return; } this->streamSettings = streamSettings; // verify checksum first uint8 actualChecksum = calculateChecksum(data, length - 1); uint8 packetChecksum = *(uint8*)(data + length - 1); if (actualChecksum != packetChecksum) { isInvalid = true; return; } length--; // remove checksum from length // verify constant header this->src = *(uint8*)(data + 0x00); this->dst = *(uint8*)(data + 0x01); uint16 typeAndFlags = *(uint16*)(data + 0x02); this->flags = (typeAndFlags >> 4) & 0xFFF; this->type = (typeAndFlags & 0xF); this->sessionId = *(uint8*)(data + 0x4); this->packetSignature = *(uint32*)(data + 0x5); this->sequenceId = *(uint16*)(data + 0x9); // read dynamic fields sint32 readIndex = 0xB; if (this->type == prudpPacket::TYPE_SYN) { // SYN packet if (readIndex < 4) { isInvalid = true; return; } packetData.resize(4); *(uint32*)(&packetData.front()) = *(uint32*)(data + readIndex); hasData = true; readIndex += 4; // ignore FLAG_HAS_SIZE (would come here, usually with a value of 0) return; } else if (this->type == prudpPacket::TYPE_CON) { // CON packet if (readIndex < 4) { isInvalid = true; return; } // this packet has another 4 byte signature but it's always zero? (value is ignored for now) // ignore FLAG_HAS_SIZE } else if (this->type == prudpPacket::TYPE_PING) { // PING packet // ignore payload } else if (this->type == prudpPacket::TYPE_DATA) { // can we assume that reliable data packets always need to have a unique sequence id? (Even if it's a multi-fragment frame) // unreliable packets must have a sequence id too or how else would we know when to decrypt it? bool hasPayloadSize = (this->flags & prudpPacket::FLAG_HAS_SIZE) != 0; // verify length if ((length - readIndex) < 1 + (hasPayloadSize ? 2 : 0)) { // too short isInvalid = true; return; } // read fragment index this->fragmentIndex = *(uint8*)(data + readIndex); readIndex += sizeof(uint8); // read payload size (optional) if (hasPayloadSize) { uint16 payloadSize = *(uint32*)(data + readIndex); readIndex += sizeof(uint16); // verify payload size if ((length - readIndex) != payloadSize) { assert_dbg(); // mismatch, ignore packet or use specified payload size? } } // read payload if (readIndex < length) { sint32 dataSize = length - readIndex; packetData.resize(dataSize); memcpy(&packetData.front(), data + readIndex, dataSize); } return; } else if (this->type == prudpPacket::TYPE_DISCONNECT) { // DISCONNECT packet // ignore payload } else { cemu_assert_suspicious(); } } bool prudpIncomingPacket::hasError() { return isInvalid; } uint8 prudpIncomingPacket::calculateChecksum(uint8* data, sint32 length) { return prudp_calculateChecksum(streamSettings->checksumBase, data, length); } void prudpIncomingPacket::decrypt() { if (packetData.empty()) return; RC4_transform(&streamSettings->rc4Server, &packetData.front(), (int)packetData.size(), &packetData.front()); } #define PRUDP_VPORT(__streamType, __port) (((__streamType) << 4) | (__port)) prudpClient::prudpClient() { m_currentConnectionState = ConnectionState::Connecting; m_serverConnectionSignature = 0; m_clientConnectionSignature = 0; m_incomingSequenceId = 0; m_clientSessionId = 0; m_serverSessionId = 0; } prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key) : prudpClient() { m_dstIp = dstIp; m_dstPort = dstPort; // get unused random source port for (sint32 tries = 0; tries < 5; tries++) { m_srcPort = AllocateRandomSrcPRUDPPort(); // create and bind udp socket m_socketUdp = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in udpServer; udpServer.sin_family = AF_INET; udpServer.sin_addr.s_addr = INADDR_ANY; udpServer.sin_port = htons(m_srcPort); if (bind(m_socketUdp, (struct sockaddr*)&udpServer, sizeof(udpServer)) == SOCKET_ERROR) { ReleasePRUDPSrcPort(m_srcPort); m_srcPort = 0; if (tries == 4) { cemuLog_log(LogType::Force, "PRUDP: Failed to bind UDP socket"); m_currentConnectionState = ConnectionState::Disconnected; return; } closesocket(m_socketUdp); continue; } else break; } // set socket to non-blocking mode #if BOOST_OS_WINDOWS u_long nonBlockingMode = 1; // 1 to enable non-blocking socket ioctlsocket(m_socketUdp, FIONBIO, &nonBlockingMode); #else int flags = fcntl(m_socketUdp, F_GETFL); fcntl(m_socketUdp, F_SETFL, flags | O_NONBLOCK); #endif // generate frequently used parameters m_srcVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0xF); m_dstVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0x1); // set stream settings uint8 checksumBase = 0; for (sint32 i = 0; key[i] != '\0'; i++) { checksumBase += key[i]; } m_streamSettings.checksumBase = checksumBase; MD5_CTX md5Ctx; MD5_Init(&md5Ctx); MD5_Update(&md5Ctx, key, (int)strlen(key)); MD5_Final(m_streamSettings.accessKeyDigest, &md5Ctx); // init stream ciphers RC4_initCtx(&m_streamSettings.rc4Server, "CD&ML"); RC4_initCtx(&m_streamSettings.rc4Client, "CD&ML"); // send syn packet SendCurrentHandshakePacket(); // set incoming sequence id to 1 m_incomingSequenceId = 1; } prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key, prudpAuthServerInfo* authInfo) : prudpClient(dstIp, dstPort, key) { RC4_initCtx(&m_streamSettings.rc4Server, authInfo->secureKey, 16); RC4_initCtx(&m_streamSettings.rc4Client, authInfo->secureKey, 16); m_isSecureConnection = true; memcpy(&m_authInfo, authInfo, sizeof(prudpAuthServerInfo)); } prudpClient::~prudpClient() { if (m_srcPort != 0) { ReleasePRUDPSrcPort(m_srcPort); closesocket(m_socketUdp); } } void prudpClient::AcknowledgePacket(uint16 sequenceId) { auto it = std::begin(m_dataPacketsWithAckReq); while (it != std::end(m_dataPacketsWithAckReq)) { if (it->packet->GetSequenceId() == sequenceId) { delete it->packet; m_dataPacketsWithAckReq.erase(it); return; } it++; } } void prudpClient::SortIncomingDataPacket(std::unique_ptr incomingPacket) { uint16 sequenceIdIncomingPacket = incomingPacket->sequenceId; // find insert index sint32 insertIndex = 0; while (insertIndex < m_incomingPacketQueue.size()) { uint16 seqDif = sequenceIdIncomingPacket - m_incomingPacketQueue[insertIndex]->sequenceId; if (seqDif & 0x8000) break; // negative seqDif -> insert before current element #ifdef CEMU_DEBUG_ASSERT if (seqDif == 0) assert_dbg(); // same sequence id, sort by fragment index? #endif insertIndex++; } m_incomingPacketQueue.insert(m_incomingPacketQueue.begin() + insertIndex, std::move(incomingPacket)); // debug check if packets are really ordered by sequence id #ifdef CEMU_DEBUG_ASSERT for (sint32 i = 1; i < m_incomingPacketQueue.size(); i++) { uint16 seqDif = m_incomingPacketQueue[i]->sequenceId - m_incomingPacketQueue[i - 1]->sequenceId; if (seqDif & 0x8000) seqDif = -seqDif; if (seqDif >= 0x8000) assert_dbg(); } #endif } sint32 prudpClient::KerberosEncryptData(uint8* input, sint32 length, uint8* output) { RC4Ctx rc4Kerberos; RC4_initCtx(&rc4Kerberos, m_authInfo.secureKey, 16); memcpy(output, input, length); RC4_transform(&rc4Kerberos, output, length, output); // calculate and append hmac hmacMD5(this->m_authInfo.secureKey, 16, output, length, output + length); return length + 16; } // (re)sends either CON or SYN based on what stage of the login we are at // the sequenceId for both is hardcoded for both because we'll never send anything in between void prudpClient::SendCurrentHandshakePacket() { if (!m_hasSynAck) { // send syn (with a fixed sequenceId of 0) prudpPacket synPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_SYN, prudpPacket::FLAG_NEED_ACK, 0, 0, 0); DirectSendPacket(&synPacket); } else { // send con (with a fixed sequenceId of 1) prudpPacket conPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_CON, prudpPacket::FLAG_NEED_ACK | prudpPacket::FLAG_RELIABLE, this->m_clientSessionId, 1, m_serverConnectionSignature); if (this->m_isSecureConnection) { uint8 tempBuffer[512]; nexPacketBuffer conData(tempBuffer, sizeof(tempBuffer), true); conData.writeU32(m_clientConnectionSignature); conData.writeBuffer(m_authInfo.secureTicket, m_authInfo.secureTicketLength); // encrypted request data uint8 requestData[4 * 3]; uint8 requestDataEncrypted[4 * 3 + 0x10]; *(uint32*)(requestData + 0x0) = m_authInfo.userPid; *(uint32*)(requestData + 0x4) = m_authInfo.server.cid; *(uint32*)(requestData + 0x8) = prudp_generateRandomU32(); // todo - check value sint32 encryptedSize = KerberosEncryptData(requestData, sizeof(requestData), requestDataEncrypted); conData.writeBuffer(requestDataEncrypted, encryptedSize); conPacket.setData(conData.getDataPtr(), conData.getWriteIndex()); } else { conPacket.setData((uint8*)&m_clientConnectionSignature, sizeof(uint32)); } DirectSendPacket(&conPacket); } m_lastHandshakeTimestamp = prudpGetMSTimestamp(); m_handshakeRetryCount++; } void prudpClient::HandleIncomingPacket(std::unique_ptr incomingPacket) { if (incomingPacket->type == prudpPacket::TYPE_PING) { if (incomingPacket->flags & prudpPacket::FLAG_ACK) { // ack for our ping packet if (incomingPacket->flags & prudpPacket::FLAG_NEED_ACK) cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected ping packet with both ACK and NEED_ACK set"); if (m_unacknowledgedPingCount > 0) { if (incomingPacket->sequenceId == m_outgoingSequenceId_ping) { cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK (unacknowledged count: {})", m_unacknowledgedPingCount); m_unacknowledgedPingCount = 0; } else { cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK with wrong sequenceId (expected: {}, received: {})", m_outgoingSequenceId_ping, incomingPacket->sequenceId); } } else { cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK which we dont need"); } } else if (incomingPacket->flags & prudpPacket::FLAG_NEED_ACK) { // other side is asking for ping ack cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet with NEED_ACK set. Sending ACK back"); prudpPacket ackPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_ACK, this->m_clientSessionId, incomingPacket->sequenceId, 0); if(!incomingPacket->packetData.empty()) ackPacket.setData(incomingPacket->packetData.data(), incomingPacket->packetData.size()); DirectSendPacket(&ackPacket); } return; } else if (incomingPacket->type == prudpPacket::TYPE_SYN) { // syn packet from server is expected to have ACK set if (!(incomingPacket->flags & prudpPacket::FLAG_ACK)) { cemuLog_log(LogType::Force, "[PRUDP] Received SYN packet without ACK flag set"); // always log this return; } if (m_hasSynAck || !incomingPacket->hasData || incomingPacket->packetData.size() != 4) { // syn already acked or not a valid syn packet cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected SYN packet"); return; } m_hasSynAck = true; this->m_serverConnectionSignature = *(uint32*)&incomingPacket->packetData.front(); // generate client session id and connection signature this->m_clientSessionId = prudp_generateRandomU8(); this->m_clientConnectionSignature = prudp_generateRandomU32(); // send con packet m_handshakeRetryCount = 0; SendCurrentHandshakePacket(); return; } else if (incomingPacket->type == prudpPacket::TYPE_CON) { if (!m_hasSynAck || m_hasConAck) { cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected CON packet"); return; } // make sure the packet has the ACK flag set if (!(incomingPacket->flags & prudpPacket::FLAG_ACK)) { cemuLog_log(LogType::Force, "[PRUDP] Received CON packet without ACK flag set"); return; } m_hasConAck = true; m_handshakeRetryCount = 0; cemu_assert_debug(m_currentConnectionState == ConnectionState::Connecting); // connected! m_lastPingTimestamp = prudpGetMSTimestamp(); cemu_assert_debug(m_serverSessionId == 0); m_serverSessionId = incomingPacket->sessionId; m_currentConnectionState = ConnectionState::Connected; cemuLog_log(LogType::PRUDP, "[PRUDP] Connection established. ClientSession {:02x} ServerSession {:02x}", m_clientSessionId, m_serverSessionId); return; } else if (incomingPacket->type == prudpPacket::TYPE_DATA) { // handle ACK if (incomingPacket->flags & prudpPacket::FLAG_ACK) { AcknowledgePacket(incomingPacket->sequenceId); if(!incomingPacket->packetData.empty()) cemuLog_log(LogType::PRUDP, "[PRUDP] Received ACK data packet with payload"); return; } // send ack back if requested if (incomingPacket->flags & prudpPacket::FLAG_NEED_ACK) { prudpPacket ackPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_DATA, prudpPacket::FLAG_ACK, this->m_clientSessionId, incomingPacket->sequenceId, 0); DirectSendPacket(&ackPacket); } // skip data packets without payload if (incomingPacket->packetData.empty()) return; // verify sequence id uint16 seqDist = incomingPacket->sequenceId - m_incomingSequenceId; if (seqDist >= 0xC000) { // outdated return; } // check if packet is already queued for (auto& it : m_incomingPacketQueue) { if (it->sequenceId == incomingPacket->sequenceId) { // already queued (should check other values too, like packet type?) cemuLog_log(LogType::PRUDP, "Duplicate PRUDP packet received"); return; } } // put into ordered receive queue SortIncomingDataPacket(std::move(incomingPacket)); } else if (incomingPacket->type == prudpPacket::TYPE_DISCONNECT) { m_currentConnectionState = ConnectionState::Disconnected; return; } else { cemuLog_log(LogType::PRUDP, "[PRUDP] Received unknown packet type"); } } bool prudpClient::Update() { if (m_currentConnectionState == ConnectionState::Disconnected) return false; uint32 currentTimestamp = prudpGetMSTimestamp(); // check for incoming packets uint8 receiveBuffer[4096]; while (true) { sockaddr receiveFrom = {0}; socklen_t receiveFromLen = sizeof(receiveFrom); sint32 r = recvfrom(m_socketUdp, (char*)receiveBuffer, sizeof(receiveBuffer), 0, &receiveFrom, &receiveFromLen); if (r >= 0) { // todo: Verify sender (receiveFrom) // calculate packet size sint32 pIdx = 0; while (pIdx < r) { sint32 packetLength = prudpPacket::calculateSizeFromPacketData(receiveBuffer + pIdx, r - pIdx); if (packetLength <= 0 || (pIdx + packetLength) > r) { cemuLog_log(LogType::Force, "[PRUDP] Invalid packet length"); break; } auto incomingPacket = std::make_unique(&m_streamSettings, receiveBuffer + pIdx, packetLength); pIdx += packetLength; if (incomingPacket->hasError()) { cemuLog_log(LogType::Force, "[PRUDP] Packet error"); break; } if (incomingPacket->type != prudpPacket::TYPE_CON && incomingPacket->sessionId != m_serverSessionId) { cemuLog_log(LogType::PRUDP, "[PRUDP] Invalid session id"); continue; // different session } HandleIncomingPacket(std::move(incomingPacket)); } } else break; } // check for ack timeouts for (auto& it : m_dataPacketsWithAckReq) { if ((currentTimestamp - it.lastRetryTimestamp) >= 2300) { if (it.retryCount >= 7) { // after too many retries consider the connection dead m_currentConnectionState = ConnectionState::Disconnected; } // resend DirectSendPacket(it.packet); it.lastRetryTimestamp = currentTimestamp; it.retryCount++; } } if (m_currentConnectionState == ConnectionState::Connecting) { // check if we need to resend SYN or CON uint32 timeSinceLastHandshake = currentTimestamp - m_lastHandshakeTimestamp; if (timeSinceLastHandshake >= 1200) { if (m_handshakeRetryCount >= 5) { // too many retries, assume the other side doesn't listen m_currentConnectionState = ConnectionState::Disconnected; cemuLog_log(LogType::PRUDP, "PRUDP: Failed to connect"); return false; } SendCurrentHandshakePacket(); } } else if (m_currentConnectionState == ConnectionState::Connected) { // handle pings if (m_unacknowledgedPingCount != 0) // counts how many times we sent a ping packet (for the current sequenceId) without receiving an ack { // we are waiting for the ack of the previous ping, but it hasn't arrived yet so send another ping packet if ((currentTimestamp - m_lastPingTimestamp) >= 1500) { cemuLog_log(LogType::PRUDP, "[PRUDP] Resending ping packet (no ack received)"); if (m_unacknowledgedPingCount >= 10) { // too many unacknowledged pings, assume the connection is dead m_currentConnectionState = ConnectionState::Disconnected; cemuLog_log(LogType::PRUDP, "PRUDP: Connection did not receive a ping response in a while. Assuming disconnect"); return false; } // resend the ping packet prudpPacket pingPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->m_clientSessionId, this->m_outgoingSequenceId_ping, m_serverConnectionSignature); DirectSendPacket(&pingPacket); m_unacknowledgedPingCount++; m_lastPingTimestamp = currentTimestamp; } } else { if ((currentTimestamp - m_lastPingTimestamp) >= 20000) { cemuLog_log(LogType::PRUDP, "[PRUDP] Sending new ping packet with sequenceId {}", this->m_outgoingSequenceId_ping + 1); // start a new ping packet with a new sequenceId. Note that ping packets have their own sequenceId and acknowledgement happens by manually comparing the incoming ping ACK against the last sent sequenceId // only one unacknowledged ping packet can be in flight at a time. We will resend the same ping packet until we receive an ack m_outgoingSequenceId_ping++; // increment before sending. The first ping has a sequenceId of 1 prudpPacket pingPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->m_clientSessionId, this->m_outgoingSequenceId_ping, m_serverConnectionSignature); DirectSendPacket(&pingPacket); m_unacknowledgedPingCount++; m_lastPingTimestamp = currentTimestamp; } } } return false; } void prudpClient::DirectSendPacket(prudpPacket* packet) { uint8 packetBuffer[prudpPacket::PACKET_RAW_SIZE_MAX]; sint32 len = packet->buildData(packetBuffer, prudpPacket::PACKET_RAW_SIZE_MAX); sockaddr_in destAddr; destAddr.sin_family = AF_INET; destAddr.sin_port = htons(m_dstPort); destAddr.sin_addr.s_addr = m_dstIp; sendto(m_socketUdp, (const char*)packetBuffer, len, 0, (const sockaddr*)&destAddr, sizeof(destAddr)); } void prudpClient::QueuePacket(prudpPacket* packet) { cemu_assert_debug(packet->GetType() == prudpPacket::TYPE_DATA); // only data packets should be queued if (packet->requiresAck()) { // remember this packet until we receive the ack m_dataPacketsWithAckReq.emplace_back(packet, prudpGetMSTimestamp()); DirectSendPacket(packet); } else { DirectSendPacket(packet); delete packet; } } void prudpClient::SendDatagram(uint8* input, sint32 length, bool reliable) { cemu_assert_debug(reliable); // non-reliable packets require correct sequenceId handling and testing cemu_assert_debug(m_hasSynAck && m_hasConAck); // cant send data packets before we are connected if (length >= 0x300) { cemuLog_logOnce(LogType::Force, "PRUDP: Datagram too long. Fragmentation not implemented yet"); } // single fragment data packet uint16 flags = prudpPacket::FLAG_NEED_ACK; if (reliable) flags |= prudpPacket::FLAG_RELIABLE; prudpPacket* packet = new prudpPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_DATA, flags, m_clientSessionId, m_outgoingReliableSequenceId, 0); if (reliable) m_outgoingReliableSequenceId++; packet->setFragmentIndex(0); packet->setData(input, length); QueuePacket(packet); } sint32 prudpClient::ReceiveDatagram(std::vector& outputBuffer) { outputBuffer.clear(); if (m_incomingPacketQueue.empty()) return -1; prudpIncomingPacket* frontPacket = m_incomingPacketQueue[0].get(); if (frontPacket->sequenceId != this->m_incomingSequenceId) return -1; if (frontPacket->fragmentIndex == 0) { // single-fragment packet // decrypt frontPacket->decrypt(); // read data if (!frontPacket->packetData.empty()) { // to conserve memory we will also shrink the buffer if it was previously extended beyond 32KB constexpr size_t BUFFER_TARGET_SIZE = 1024 * 32; if (frontPacket->packetData.size() < BUFFER_TARGET_SIZE && outputBuffer.capacity() > BUFFER_TARGET_SIZE) { outputBuffer.resize(BUFFER_TARGET_SIZE); outputBuffer.shrink_to_fit(); outputBuffer.clear(); } // write packet data to output buffer cemu_assert_debug(outputBuffer.empty()); outputBuffer.insert(outputBuffer.end(), frontPacket->packetData.begin(), frontPacket->packetData.end()); } m_incomingPacketQueue.erase(m_incomingPacketQueue.begin()); // advance expected sequence id this->m_incomingSequenceId++; return (sint32)outputBuffer.size(); } else { // multi-fragment packet if (frontPacket->fragmentIndex != 1) return -1; // first packet of the chain not received yet // verify chain sint32 packetIndex = 1; sint32 chainLength = -1; // if full chain found, set to count of packets for (sint32 i = 1; i < m_incomingPacketQueue.size(); i++) { uint8 itFragmentIndex = m_incomingPacketQueue[packetIndex]->fragmentIndex; // sequence id must increase by 1 for every packet if (m_incomingPacketQueue[packetIndex]->sequenceId != (m_incomingSequenceId + i)) return -1; // missing packets // last fragment in chain is marked by fragment index 0 if (itFragmentIndex == 0) { chainLength = i + 1; break; } packetIndex++; } if (chainLength < 1) return -1; // chain not complete // extract data from packet chain cemu_assert_debug(outputBuffer.empty()); for (sint32 i = 0; i < chainLength; i++) { prudpIncomingPacket* incomingPacket = m_incomingPacketQueue[i].get(); incomingPacket->decrypt(); outputBuffer.insert(outputBuffer.end(), incomingPacket->packetData.begin(), incomingPacket->packetData.end()); } // remove packets from queue m_incomingPacketQueue.erase(m_incomingPacketQueue.begin(), m_incomingPacketQueue.begin() + chainLength); m_incomingSequenceId += chainLength; return (sint32)outputBuffer.size(); } return -1; } ================================================ FILE: src/Cemu/nex/prudp.h ================================================ #pragma once #include "nexTypes.h" #include "Common/socket.h" #define RC4_N 256 struct RC4Ctx { unsigned char S[RC4_N]; int i; int j; }; void RC4_initCtx(RC4Ctx* rc4Ctx, const char* key); void RC4_initCtx(RC4Ctx* rc4Ctx, unsigned char* key, int keyLen); void RC4_transform(RC4Ctx* rc4Ctx, unsigned char* input, int len, unsigned char* output); struct prudpStreamSettings { uint8 checksumBase; // calculated from key uint8 accessKeyDigest[16]; // MD5 hash of key RC4Ctx rc4Client; RC4Ctx rc4Server; }; struct prudpStationUrl { uint32 ip; uint16 port; sint32 cid; sint32 pid; sint32 sid; sint32 stream; sint32 type; }; struct prudpAuthServerInfo { uint32 userPid; uint8 secureKey[16]; uint8 kerberosKey[16]; uint8 secureTicket[1024]; sint32 secureTicketLength; prudpStationUrl server; }; class prudpPacket { public: static const int PACKET_RAW_SIZE_MAX = 500; static const int TYPE_SYN = 0; static const int TYPE_CON = 1; static const int TYPE_DATA = 2; static const int TYPE_DISCONNECT = 3; static const int TYPE_PING = 4; static const int STREAM_TYPE_SECURE = 0xA; static const int FLAG_ACK = 0x1; static const int FLAG_RELIABLE = 0x2; // if this flag is set, increase sequenceId static const int FLAG_NEED_ACK = 0x4; static const int FLAG_HAS_SIZE = 0x8; static sint32 calculateSizeFromPacketData(uint8* data, sint32 length); prudpPacket(prudpStreamSettings* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature); bool requiresAck(); void setData(uint8* data, sint32 length); void setFragmentIndex(uint8 fragmentIndex); sint32 buildData(uint8* output, sint32 maxLength); uint8 GetType() const { return type; } uint16 GetSequenceId() const { return m_sequenceId; } private: uint32 packetSignature(); uint8 calculateChecksum(uint8* data, sint32 length); private: uint8 src; uint8 dst; uint8 type; uint8 fragmentIndex; uint16 flags; uint8 sessionId; uint32 specifiedPacketSignature; prudpStreamSettings* streamSettings; std::vector packetData; bool isEncrypted; uint16 m_sequenceId{0}; }; class prudpIncomingPacket { public: prudpIncomingPacket(prudpStreamSettings* streamSettings, uint8* data, sint32 length); bool hasError(); uint8 calculateChecksum(uint8* data, sint32 length); void decrypt(); private: prudpIncomingPacket(); public: // prudp header uint8 src; uint8 dst; uint16 flags; uint8 type; uint8 sessionId; uint32 packetSignature; uint16 sequenceId; uint8 fragmentIndex; bool hasData = false; std::vector packetData; private: bool isInvalid = false; prudpStreamSettings* streamSettings = nullptr; }; class prudpClient { struct PacketWithAckRequired { PacketWithAckRequired(prudpPacket* packet, uint32 initialSendTimestamp) : packet(packet), initialSendTimestamp(initialSendTimestamp), lastRetryTimestamp(initialSendTimestamp) { } prudpPacket* packet; uint32 initialSendTimestamp; uint32 lastRetryTimestamp; sint32 retryCount{0}; }; public: enum class ConnectionState : uint8 { Connecting, Connected, Disconnected }; prudpClient(uint32 dstIp, uint16 dstPort, const char* key); prudpClient(uint32 dstIp, uint16 dstPort, const char* key, prudpAuthServerInfo* authInfo); ~prudpClient(); bool IsConnected() const { return m_currentConnectionState == ConnectionState::Connected; } ConnectionState GetConnectionState() const { return m_currentConnectionState; } uint16 GetSourcePort() const { return m_srcPort; } bool Update(); // update connection state and check for incoming packets. Returns true if ReceiveDatagram() should be called sint32 ReceiveDatagram(std::vector& outputBuffer); void SendDatagram(uint8* input, sint32 length, bool reliable = true); private: prudpClient(); void HandleIncomingPacket(std::unique_ptr incomingPacket); void DirectSendPacket(prudpPacket* packet); sint32 KerberosEncryptData(uint8* input, sint32 length, uint8* output); void QueuePacket(prudpPacket* packet); void AcknowledgePacket(uint16 sequenceId); void SortIncomingDataPacket(std::unique_ptr incomingPacket); void SendCurrentHandshakePacket(); private: uint16 m_srcPort; uint32 m_dstIp; uint16 m_dstPort; uint8 m_srcVPort; uint8 m_dstVPort; prudpStreamSettings m_streamSettings; std::vector m_dataPacketsWithAckReq; std::vector> m_incomingPacketQueue; // connection handshake state bool m_hasSynAck{false}; bool m_hasConAck{false}; uint32 m_lastHandshakeTimestamp{0}; uint8 m_handshakeRetryCount{0}; // connection ConnectionState m_currentConnectionState; uint32 m_serverConnectionSignature; uint32 m_clientConnectionSignature; uint32 m_lastPingTimestamp; uint16 m_outgoingReliableSequenceId{2}; // 1 is reserved for CON uint16 m_incomingSequenceId; uint16 m_outgoingSequenceId_ping{0}; uint8 m_unacknowledgedPingCount{0}; uint8 m_clientSessionId; uint8 m_serverSessionId; // secure bool m_isSecureConnection{false}; prudpAuthServerInfo m_authInfo; // socket SOCKET m_socketUdp; }; uint32 prudpGetMSTimestamp(); ================================================ FILE: src/Common/CMakeLists.txt ================================================ add_library(CemuCommon betype.h cpu_features.cpp cpu_features.h enumFlags.h ExceptionHandler/ExceptionHandler.cpp ExceptionHandler/ExceptionHandler.h FileStream.h GLInclude/glext.h GLInclude/glFunctions.h GLInclude/GLInclude.h GLInclude/glxext.h GLInclude/khrplatform.h GLInclude/wglext.h MemPtr.h platform.h precompiled.cpp precompiled.h socket.h StackAllocator.h SysAllocator.cpp SysAllocator.h CafeString.h version.h ) set_property(TARGET CemuCommon PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") if(WIN32) target_sources(CemuCommon PRIVATE windows/platform.cpp windows/platform.h windows/FileStream_win32.cpp ExceptionHandler/ExceptionHandler_win32.cpp ) else() target_sources(CemuCommon PRIVATE unix/date.h unix/fast_float.h unix/platform.cpp unix/platform.h unix/FileStream_unix.cpp unix/FileStream_unix.h ExceptionHandler/ExceptionHandler_posix.cpp ) endif() if(LINUX) target_sources(CemuCommon PRIVATE ExceptionHandler/ELFSymbolTable.cpp ExceptionHandler/ELFSymbolTable.h ) endif() # All the targets wanting to use the precompiled.h header # have to link to CemuCommon target_precompile_headers(CemuCommon PUBLIC precompiled.h) target_include_directories(CemuCommon PUBLIC "../") target_link_libraries(CemuCommon PRIVATE Boost::nowide Boost::filesystem glm::glm ) if (UNIX AND NOT APPLE) target_link_libraries(CemuCommon PRIVATE X11::X11 X11::Xrender X11::Xutil) endif() # PUBLIC because: # - boost/predef/os.h is included in platform.h # - fmt/core.h is included in precompiled.h target_link_libraries(CemuCommon PUBLIC Boost::headers fmt::fmt) ================================================ FILE: src/Common/CafeString.h ================================================ #pragma once #include "betype.h" #include "util/helpers/StringHelpers.h" /* Helper classes to represent CafeOS strings in emulated memory */ template class CafeString // fixed buffer size, null-terminated, PPC char { public: constexpr static size_t Size() { return N; } // checks whether the string and a null terminator can fit into the buffer bool CanHoldString(std::string_view sv) const { return sv.size() < N; } bool assign(std::string_view sv) { if (sv.size() >= N) { memcpy(data, sv.data(), N-1); data[N-1] = '\0'; return false; } memcpy(data, sv.data(), sv.size()); data[sv.size()] = '\0'; return true; } void Copy(CafeString& other) { memcpy(data, other.data, N); } bool empty() const { return data[0] == '\0'; } const char* c_str() const { return (const char*)data; } void ClearAllBytes() { memset(data, 0, N); } auto operator<=>(const CafeString& other) const { for (size_t i = 0; i < N; i++) { if (data[i] != other.data[i]) return data[i] <=> other.data[i]; if (data[i] == '\0') return std::strong_ordering::equal; } return std::strong_ordering::equal; } bool operator==(const CafeString& other) const { for (size_t i = 0; i < N; i++) { if (data[i] != other.data[i]) return false; if (data[i] == '\0') return true; } return true; } uint8be data[N]; }; template class CafeWideString // fixed buffer size, null-terminated, PPC wchar_t (16bit big-endian) { public: bool assign(const uint16be* input) { size_t i = 0; while(input[i]) { if(i >= N-1) { data[N-1] = 0; return false; } data[i] = input[i]; i++; } data[i] = 0; return true; } bool assignFromUTF8(std::string_view sv) { std::vector beStr = StringHelpers::FromUtf8(sv); if(beStr.size() > N-1) { memcpy(data, beStr.data(), (N-1)*sizeof(uint16be)); data[N-1] = 0; return false; } memcpy(data, beStr.data(), beStr.size()*sizeof(uint16be)); data[beStr.size()] = '\0'; return true; } uint16be data[N]; }; namespace CafeStringHelpers { static uint32 Length(const uint16be* input, uint32 maxLength) { uint32 i = 0; while(input[i] && i < maxLength) i++; return i; } }; ================================================ FILE: src/Common/ExceptionHandler/ELFSymbolTable.cpp ================================================ #include "Common/ExceptionHandler/ELFSymbolTable.h" #include "Common/FileStream.h" #include #include uint16 ELFSymbolTable::FindSection(int type, const std::string_view& name) { if (!shTable || !shStrTable) return 0; for (uint16 i = 0; i < header->e_shnum; ++i) { auto& entry = shTable[i]; if(entry.sh_type == type && std::string_view{&shStrTable[entry.sh_name]} == name) { return i; } } return 0; } void* ELFSymbolTable::SectionPointer(uint16 index) { return SectionPointer(shTable[index]); } void* ELFSymbolTable::SectionPointer(const Elf64_Shdr& section) { return (void*)(mappedExecutable + section.sh_offset); } ELFSymbolTable::ELFSymbolTable() { // create file handle int fd = open("/proc/self/exe", O_RDONLY); if (!fd) return; // retrieve file size. struct stat filestats; if (fstat(fd, &filestats)) { close(fd); return; } mappedExecutableSize = filestats.st_size; // attempt to map the file mappedExecutable = (uint8*)(mmap(nullptr, mappedExecutableSize, PROT_READ, MAP_PRIVATE, fd, 0)); close(fd); if (!mappedExecutable) return; // verify signature header = (Elf64_Ehdr*)(mappedExecutable); constexpr uint8 signature[] = {0x7f, 0x45, 0x4c, 0x46}; for (size_t i = 0; i < 4; ++i) { if (signature[i] != header->e_ident[i]) { return; } } shTable = (Elf64_Shdr*)(mappedExecutable + header->e_shoff); Elf64_Shdr& shStrn = shTable[header->e_shstrndx]; shStrTable = (char*)(mappedExecutable + shStrn.sh_offset); strTable = (char*)SectionPointer(FindSection(SHT_STRTAB, ".strtab")); Elf64_Shdr& symTabShdr = shTable[FindSection(SHT_SYMTAB, ".symtab")]; if (symTabShdr.sh_entsize == 0) return; symTableLen = symTabShdr.sh_size / symTabShdr.sh_entsize; symTable = (Elf64_Sym*)(SectionPointer(symTabShdr)); } ELFSymbolTable::~ELFSymbolTable() { if (mappedExecutable) munmap(mappedExecutable, mappedExecutableSize); } std::string_view ELFSymbolTable::OffsetToSymbol(uint64 ptr, uint64& fromStart) const { if(!symTable || !strTable) { fromStart = -1; return {}; } for (auto entry = symTable+1; entry < symTable+symTableLen; ++entry) { if (ELF64_ST_TYPE(entry->st_info) != STT_FUNC) continue; auto begin = entry->st_value; auto size = entry->st_size; if(ptr >= begin && ptr < begin+size) { fromStart = ptr-begin; return &strTable[entry->st_name]; } } fromStart = -1; return {}; } ================================================ FILE: src/Common/ExceptionHandler/ELFSymbolTable.h ================================================ #pragma once #include #include class ELFSymbolTable { public: std::string_view OffsetToSymbol(uint64 ptr, uint64& fromStart) const; ELFSymbolTable(); ~ELFSymbolTable(); private: uint8* mappedExecutable = nullptr; size_t mappedExecutableSize = 0; Elf64_Ehdr* header = nullptr; Elf64_Shdr* shTable = nullptr; char* shStrTable = nullptr; Elf64_Sym* symTable = nullptr; uint64 symTableLen = 0; char* strTable = nullptr; uint16 FindSection(int type, const std::string_view& name); void* SectionPointer (uint16 index); void* SectionPointer(const Elf64_Shdr& section); // ownership of mapped memory, cannot copy. ELFSymbolTable(const ELFSymbolTable&) = delete; }; ================================================ FILE: src/Common/ExceptionHandler/ExceptionHandler.cpp ================================================ #include "config/ActiveSettings.h" #include "config/CemuConfig.h" #include "Cafe/CafeSystem.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/Debugger/GDBStub.h" #include "ExceptionHandler.h" bool crashLogCreated = false; bool CrashLog_Create() { if (crashLogCreated) return false; // only create one crashlog crashLogCreated = true; cemuLog_createLogFile(true); CrashLog_SetOutputChannels(true, true); return true; } static bool s_writeToStdErr{true}; static bool s_writeToLogTxt{true}; void CrashLog_SetOutputChannels(bool writeToStdErr, bool writeToLogTxt) { s_writeToStdErr = writeToStdErr; s_writeToLogTxt = writeToLogTxt; } // outputs to both stderr and log.txt void CrashLog_WriteLine(std::string_view text, bool newLine) { if(s_writeToLogTxt) cemuLog_writeLineToLog(text, false, newLine); if(s_writeToStdErr) { fwrite(text.data(), sizeof(char), text.size(), stderr); if(newLine) fwrite("\n", sizeof(char), 1, stderr); } } void CrashLog_WriteHeader(const char* header) { CrashLog_WriteLine("-----------------------------------------"); CrashLog_WriteLine(" ", false); CrashLog_WriteLine(header); CrashLog_WriteLine("-----------------------------------------"); } void ExceptionHandler_LogGeneralInfo() { char dumpLine[1024]; // info about game CrashLog_WriteLine(""); CrashLog_WriteHeader("Game info"); if (CafeSystem::IsTitleRunning()) { CrashLog_WriteLine("Game: ", false); CrashLog_WriteLine(CafeSystem::GetForegroundTitleName()); // title id CrashLog_WriteLine(fmt::format("TitleId: {:016x}", CafeSystem::GetForegroundTitleId())); // rpx hash sprintf(dumpLine, "RPXHash: %08x (Update: %08x)", CafeSystem::GetRPXHashBase(), CafeSystem::GetRPXHashUpdated()); CrashLog_WriteLine(dumpLine); } else { CrashLog_WriteLine("Not running"); } // info about active PPC instance: CrashLog_WriteLine(""); CrashLog_WriteHeader("Active PPC instance"); PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if (hCPU) { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); uint32 threadPtr = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); sprintf(dumpLine, "IP 0x%08x LR 0x%08x Thread 0x%08x", hCPU->instructionPointer, hCPU->spr.LR, threadPtr); CrashLog_WriteLine(dumpLine); // GPR info CrashLog_WriteLine(""); auto gprs = hCPU->gpr; sprintf(dumpLine, "r0 =%08x r1 =%08x r2 =%08x r3 =%08x r4 =%08x r5 =%08x r6 =%08x r7 =%08x", gprs[0], gprs[1], gprs[2], gprs[3], gprs[4], gprs[5], gprs[6], gprs[7]); CrashLog_WriteLine(dumpLine); sprintf(dumpLine, "r8 =%08x r9 =%08x r10=%08x r11=%08x r12=%08x r13=%08x r14=%08x r15=%08x", gprs[8], gprs[9], gprs[10], gprs[11], gprs[12], gprs[13], gprs[14], gprs[15]); CrashLog_WriteLine(dumpLine); sprintf(dumpLine, "r16=%08x r17=%08x r18=%08x r19=%08x r20=%08x r21=%08x r22=%08x r23=%08x", gprs[16], gprs[17], gprs[18], gprs[19], gprs[20], gprs[21], gprs[22], gprs[23]); CrashLog_WriteLine(dumpLine); sprintf(dumpLine, "r24=%08x r25=%08x r26=%08x r27=%08x r28=%08x r29=%08x r30=%08x r31=%08x", gprs[24], gprs[25], gprs[26], gprs[27], gprs[28], gprs[29], gprs[30], gprs[31]); CrashLog_WriteLine(dumpLine); // stack trace MPTR currentStackVAddr = hCPU->gpr[1]; CrashLog_WriteLine(""); CrashLog_WriteHeader("PPC stack trace"); DebugLogStackTrace(currentThread, currentStackVAddr, true); // stack dump CrashLog_WriteLine(""); CrashLog_WriteHeader("PPC stack dump"); for (uint32 i = 0; i < 16; i++) { MPTR lineAddr = currentStackVAddr + i * 8 * 4; if (memory_isAddressRangeAccessible(lineAddr, 8 * 4)) { sprintf(dumpLine, "[0x%08x] %08x %08x %08x %08x - %08x %08x %08x %08x", lineAddr, memory_readU32(lineAddr + 0), memory_readU32(lineAddr + 4), memory_readU32(lineAddr + 8), memory_readU32(lineAddr + 12), memory_readU32(lineAddr + 16), memory_readU32(lineAddr + 20), memory_readU32(lineAddr + 24), memory_readU32(lineAddr + 28)); CrashLog_WriteLine(dumpLine); } else { sprintf(dumpLine, "[0x%08x] ?", lineAddr); CrashLog_WriteLine(dumpLine); } } } else { CrashLog_WriteLine("Not active"); } // PPC thread log CrashLog_WriteLine(""); CrashLog_WriteHeader("PPC threads"); if (activeThreadCount == 0) { CrashLog_WriteLine("None active"); } for (sint32 i = 0; i < activeThreadCount; i++) { MPTR threadItrMPTR = activeThread[i]; OSThread_t* threadItrBE = (OSThread_t*)memory_getPointerFromVirtualOffset(threadItrMPTR); // get thread state OSThread_t::THREAD_STATE threadState = threadItrBE->state; const char* threadStateStr = "UNDEFINED"; if (threadItrBE->suspendCounter != 0) threadStateStr = "SUSPENDED"; else if (threadState == OSThread_t::THREAD_STATE::STATE_NONE) threadStateStr = "NONE"; else if (threadState == OSThread_t::THREAD_STATE::STATE_READY) threadStateStr = "READY"; else if (threadState == OSThread_t::THREAD_STATE::STATE_RUNNING) threadStateStr = "RUNNING"; else if (threadState == OSThread_t::THREAD_STATE::STATE_WAITING) threadStateStr = "WAITING"; else if (threadState == OSThread_t::THREAD_STATE::STATE_MORIBUND) threadStateStr = "MORIBUND"; // generate log line uint8 affinity = threadItrBE->attr; sint32 effectivePriority = threadItrBE->effectivePriority; const char* threadName = "NULL"; if (!threadItrBE->threadName.IsNull()) threadName = threadItrBE->threadName.GetPtr(); sprintf(dumpLine, "%08x Ent %08x IP %08x LR %08x %-9s Aff %d%d%d Pri %2d Name %s", threadItrMPTR, threadItrBE->entrypoint.GetMPTR(), threadItrBE->context.srr0, _swapEndianU32(threadItrBE->context.lr), threadStateStr, (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, effectivePriority, threadName); // write line to log CrashLog_WriteLine(dumpLine); } } ================================================ FILE: src/Common/ExceptionHandler/ExceptionHandler.h ================================================ #pragma once void ExceptionHandler_Init(); bool CrashLog_Create(); void CrashLog_SetOutputChannels(bool writeToStdErr, bool writeToLogTxt); void CrashLog_WriteLine(std::string_view text, bool newLine = true); void CrashLog_WriteHeader(const char* header); void ExceptionHandler_LogGeneralInfo(); ================================================ FILE: src/Common/ExceptionHandler/ExceptionHandler_posix.cpp ================================================ #include #include #include #include #include "config/CemuConfig.h" #include "util/helpers/StringHelpers.h" #include "ExceptionHandler.h" #include "Cafe/HW/Espresso/Debugger/GDBStub.h" #include "Cafe/HW/Espresso/Debugger/GDBBreakpoints.h" #if BOOST_OS_LINUX #include "ELFSymbolTable.h" #endif #if BOOST_OS_LINUX void DemangleAndPrintBacktrace(char** backtrace, size_t size) { ELFSymbolTable symTable; for (char** i = backtrace; i < backtrace + size; i++) { std::string_view traceLine{*i}; // basic check to see if the backtrace line matches expected format size_t parenthesesOpen = traceLine.find_last_of('('); size_t parenthesesClose = traceLine.find_last_of(')'); size_t offsetPlus = traceLine.find_last_of('+'); if (!parenthesesOpen || !parenthesesClose || !offsetPlus || offsetPlus < parenthesesOpen || offsetPlus > parenthesesClose) { // fall back to default string CrashLog_WriteLine(traceLine); continue; } // attempt to resolve symbol from regular symbol table if missing from dynamic symbol table uint64 newOffset = -1; std::string_view symbolName = traceLine.substr(parenthesesOpen+1, offsetPlus-parenthesesOpen-1); if (symbolName.empty()) { uint64 symbolOffset = StringHelpers::ToInt64(traceLine.substr(offsetPlus+1,offsetPlus+1-parenthesesClose-1)); symbolName = symTable.OffsetToSymbol(symbolOffset, newOffset); } CrashLog_WriteLine(traceLine.substr(0, parenthesesOpen+1), false); CrashLog_WriteLine(boost::core::demangle(symbolName.empty() ? "" : symbolName.data()), false); // print relative or existing symbol offset. CrashLog_WriteLine("+", false); if (newOffset != -1) { CrashLog_WriteLine(fmt::format("0x{:x}", newOffset), false); CrashLog_WriteLine(traceLine.substr(parenthesesClose)); } else { CrashLog_WriteLine(traceLine.substr(offsetPlus+1)); } } } #endif // handle signals that would dump core, print stacktrace and then dump depending on config void handlerDumpingSignal(int sig, siginfo_t *info, void *context) { #if defined(ARCH_X86_64) && BOOST_OS_LINUX // Check for hardware breakpoints if (info->si_signo == SIGTRAP && info->si_code == TRAP_HWBKPT) { uint64 dr6 = _ReadDR6(); g_gdbstub->HandleAccessException(dr6); return; } #endif if(!CrashLog_Create()) return; // give up if crashlog was already created char* sigName = strsignal(sig); if (sigName) { printf("%s!\n", sigName); } else { // should never be the case printf("Unknown core dumping signal!\n"); } void* backtraceArray[128]; size_t size; // get void*'s for all entries on the stack size = backtrace(backtraceArray, 128); // replace the deepest entry with the actual crash address #if defined(ARCH_X86_64) && BOOST_OS_LINUX > 0 ucontext_t *uc = (ucontext_t *)context; backtraceArray[0] = (void *)uc->uc_mcontext.gregs[REG_RIP]; #endif CrashLog_WriteLine(fmt::format("Error: signal {}:", sig)); #if BOOST_OS_LINUX char** symbol_trace = backtrace_symbols(backtraceArray, size); if (symbol_trace) { DemangleAndPrintBacktrace(symbol_trace, size); free(symbol_trace); } else { CrashLog_WriteLine("Failed to read backtrace"); } #else backtrace_symbols_fd(backtraceArray, size, STDERR_FILENO); #endif std::cerr << fmt::format("\nStacktrace and additional info written to:") << std::endl; std::cerr << cemuLog_GetLogFilePath().generic_string() << std::endl; CrashLog_SetOutputChannels(false, true); ExceptionHandler_LogGeneralInfo(); CrashLog_SetOutputChannels(true, true); if (GetConfig().crash_dump == CrashDump::Enabled) { // reset signal handler to default and re-raise signal to dump core signal(sig, SIG_DFL); raise(sig); return; } // exit process ignoring all issues _Exit(1); } void handler_SIGINT(int sig) { /* * Received when pressing CTRL + C in a console * Ideally should be exiting cleanly after saving settings but currently * there's no clean exit pathway (at least on linux) and exiting the app * by any mean ends up with a SIGABRT from the standard library destroying * threads. */ _Exit(0); } void ExceptionHandler_Init() { struct sigaction action; action.sa_flags = 0; sigfillset(&action.sa_mask); // don't allow signals to be interrupted action.sa_handler = handler_SIGINT; sigaction(SIGINT, &action, nullptr); sigaction(SIGTERM, &action, nullptr); action.sa_flags = SA_SIGINFO; action.sa_handler = nullptr; action.sa_sigaction = handlerDumpingSignal; sigaction(SIGABRT, &action, nullptr); sigaction(SIGBUS, &action, nullptr); sigaction(SIGFPE, &action, nullptr); sigaction(SIGILL, &action, nullptr); sigaction(SIGIOT, &action, nullptr); sigaction(SIGQUIT, &action, nullptr); sigaction(SIGSEGV, &action, nullptr); sigaction(SIGSYS, &action, nullptr); sigaction(SIGTRAP, &action, nullptr); } ================================================ FILE: src/Common/ExceptionHandler/ExceptionHandler_win32.cpp ================================================ #include "Common/precompiled.h" #include "Cafe/CafeSystem.h" #include "ExceptionHandler.h" #include #include #include #include "config/ActiveSettings.h" #include "config/CemuConfig.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/Debugger/GDBStub.h" LONG handleException_SINGLE_STEP(PEXCEPTION_POINTERS pExceptionInfo) { return EXCEPTION_CONTINUE_SEARCH; } #include BOOL CALLBACK MyMiniDumpCallback(PVOID pParam, const PMINIDUMP_CALLBACK_INPUT pInput, PMINIDUMP_CALLBACK_OUTPUT pOutput) { if (!pInput || !pOutput) return FALSE; switch (pInput->CallbackType) { case IncludeModuleCallback: case IncludeThreadCallback: case ThreadCallback: case ThreadExCallback: return TRUE; case ModuleCallback: if (!(pOutput->ModuleWriteFlags & ModuleReferencedByMemory)) pOutput->ModuleWriteFlags &= ~ModuleWriteModule; return TRUE; } return FALSE; } bool CreateMiniDump(CrashDump dump, EXCEPTION_POINTERS* pep) { if (dump == CrashDump::Disabled) return true; fs::path p = ActiveSettings::GetUserDataPath("crashdump"); std::error_code ec; fs::create_directories(p, ec); if (ec) return false; const auto now = std::chrono::system_clock::now(); const auto temp_time = std::chrono::system_clock::to_time_t(now); const auto& time = *std::gmtime(&temp_time); p /= fmt::format("crash_{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}.dmp", 1900 + time.tm_year, time.tm_mon + 1, time.tm_mday, time.tm_year, time.tm_hour, time.tm_min, time.tm_sec); const auto hFile = CreateFileW(p.wstring().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile == INVALID_HANDLE_VALUE) return false; MINIDUMP_EXCEPTION_INFORMATION mdei; mdei.ThreadId = GetCurrentThreadId(); mdei.ExceptionPointers = pep; mdei.ClientPointers = FALSE; MINIDUMP_CALLBACK_INFORMATION mci; mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MyMiniDumpCallback; mci.CallbackParam = nullptr; MINIDUMP_TYPE mdt; if (dump == CrashDump::Full) { mdt = (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules); } else { mdt = (MINIDUMP_TYPE)(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory); } const auto result = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, &mdei, nullptr, &mci); CloseHandle(hFile); return result != FALSE; } void DumpThreadStackTrace() { HANDLE process = GetCurrentProcess(); SymInitialize(process, NULL, TRUE); char dumpLine[1024 * 4]; void* stack[100]; const unsigned short frames = CaptureStackBackTrace(0, 40, stack, NULL); SYMBOL_INFO* symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1); symbol->MaxNameLen = 255; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); CrashLog_WriteHeader("Stack trace"); for (unsigned int i = 0; i < frames; i++) { DWORD64 stackTraceOffset = (DWORD64)stack[i]; SymFromAddr(process, stackTraceOffset, 0, symbol); sprintf(dumpLine, "0x%016I64x ", (uint64)(size_t)stack[i]); cemuLog_writePlainToLog(dumpLine); // module name HMODULE stackModule; if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)stackTraceOffset, &stackModule)) { char moduleName[512]; moduleName[0] = '\0'; GetModuleFileNameA(stackModule, moduleName, 512); sint32 moduleNameStartIndex = std::max((sint32)0, (sint32)strlen(moduleName) - 1); while (moduleNameStartIndex > 0) { if (moduleName[moduleNameStartIndex] == '\\' || moduleName[moduleNameStartIndex] == '/') { moduleNameStartIndex++; break; } moduleNameStartIndex--; } DWORD64 moduleAddress = (DWORD64)GetModuleHandleA(moduleName); sint32 relativeOffset = 0; if (moduleAddress != 0) relativeOffset = stackTraceOffset - moduleAddress; sprintf(dumpLine, "+0x%08x %-16s", relativeOffset, moduleName + moduleNameStartIndex); cemuLog_writePlainToLog(dumpLine); } else { sprintf(dumpLine, "+0x00000000 %-16s", "NULL"); cemuLog_writePlainToLog(dumpLine); } // function name sprintf(dumpLine, " %s\n", symbol->Name); cemuLog_writePlainToLog(dumpLine); } free(symbol); } void createCrashlog(EXCEPTION_POINTERS* e, PCONTEXT context) { if(!CrashLog_Create()) return; // give up if crashlog was already created const auto crash_dump = GetConfig().crash_dump.GetValue(); const auto dump_written = CreateMiniDump(crash_dump, e); if (!dump_written) cemuLog_writeLineToLog(fmt::format("couldn't write minidump {:#x}", GetLastError()), false, true); char dumpLine[1024 * 4]; // info about Cemu version sprintf(dumpLine, "\nCrashlog for %s\n", BUILD_VERSION_WITH_NAME_STRING); cemuLog_writePlainToLog(dumpLine); SYSTEMTIME sysTime; GetSystemTime(&sysTime); sprintf(dumpLine, "Date: %02d-%02d-%04d %02d:%02d:%02d\n\n", (sint32)sysTime.wDay, (sint32)sysTime.wMonth, (sint32)sysTime.wYear, (sint32)sysTime.wHour, (sint32)sysTime.wMinute, (sint32)sysTime.wSecond); cemuLog_writePlainToLog(dumpLine); DumpThreadStackTrace(); // info about exception if (e->ExceptionRecord) { HMODULE exceptionModule; if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)(e->ExceptionRecord->ExceptionAddress), &exceptionModule)) { char moduleName[512]; moduleName[0] = '\0'; GetModuleFileNameA(exceptionModule, moduleName, 512); sint32 moduleNameStartIndex = std::max((sint32)0, (sint32)strlen(moduleName) - 1); while (moduleNameStartIndex > 0) { if (moduleName[moduleNameStartIndex] == '\\' || moduleName[moduleNameStartIndex] == '/') { moduleNameStartIndex++; break; } moduleNameStartIndex--; } sprintf(dumpLine, "Exception 0x%08x at 0x%I64x(+0x%I64x) in module %s\n", (uint32)e->ExceptionRecord->ExceptionCode, (uint64)e->ExceptionRecord->ExceptionAddress, (uint64)e->ExceptionRecord->ExceptionAddress - (uint64)exceptionModule, moduleName + moduleNameStartIndex); cemuLog_writePlainToLog(dumpLine); } else { sprintf(dumpLine, "Exception 0x%08x at 0x%I64x\n", (uint32)e->ExceptionRecord->ExceptionCode, (uint64)e->ExceptionRecord->ExceptionAddress); cemuLog_writePlainToLog(dumpLine); } } sprintf(dumpLine, "cemu.exe at 0x%I64x\n", (uint64)GetModuleHandle(NULL)); cemuLog_writePlainToLog(dumpLine); // register info sprintf(dumpLine, "\n"); cemuLog_writePlainToLog(dumpLine); sprintf(dumpLine, "RAX=%016I64x RBX=%016I64x RCX=%016I64x RDX=%016I64x\n", context->Rax, context->Rbx, context->Rcx, context->Rdx); cemuLog_writePlainToLog(dumpLine); sprintf(dumpLine, "RSP=%016I64x RBP=%016I64x RDI=%016I64x RSI=%016I64x\n", context->Rsp, context->Rbp, context->Rdi, context->Rsi); cemuLog_writePlainToLog(dumpLine); sprintf(dumpLine, "R8 =%016I64x R9 =%016I64x R10=%016I64x R11=%016I64x\n", context->R8, context->R9, context->R10, context->R11); cemuLog_writePlainToLog(dumpLine); sprintf(dumpLine, "R12=%016I64x R13=%016I64x R14=%016I64x R15=%016I64x\n", context->R12, context->R13, context->R14, context->R15); cemuLog_writePlainToLog(dumpLine); CrashLog_SetOutputChannels(false, true); ExceptionHandler_LogGeneralInfo(); CrashLog_SetOutputChannels(true, true); cemuLog_waitForFlush(); // save log with the dump if (dump_written && crash_dump != CrashDump::Disabled) { const auto now = std::chrono::system_clock::now(); const auto temp_time = std::chrono::system_clock::to_time_t(now); const auto& time = *std::gmtime(&temp_time); fs::path p = ActiveSettings::GetUserDataPath("crashdump"); p /= fmt::format("log_{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}.txt", 1900 + time.tm_year, time.tm_mon + 1, time.tm_mday, time.tm_year, time.tm_hour, time.tm_min, time.tm_sec); std::error_code ec; fs::copy_file(ActiveSettings::GetUserDataPath("log.txt"), p, ec); } exit(0); return; } bool logCrashlog; int crashlogThread(void* exceptionInfoRawPtr) { PEXCEPTION_POINTERS pExceptionInfo = (PEXCEPTION_POINTERS)exceptionInfoRawPtr; createCrashlog(pExceptionInfo, pExceptionInfo->ContextRecord); logCrashlog = true; return 0; } void debugger_handleSingleStepException(uint64 dr6); LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) { LONG r = handleException_SINGLE_STEP(pExceptionInfo); if (r != EXCEPTION_CONTINUE_SEARCH) return r; if (GetBits(pExceptionInfo->ContextRecord->Dr6, 0, 1) || GetBits(pExceptionInfo->ContextRecord->Dr6, 1, 1)) debugger_handleSingleStepException(pExceptionInfo->ContextRecord->Dr6); else if (GetBits(pExceptionInfo->ContextRecord->Dr6, 2, 1) || GetBits(pExceptionInfo->ContextRecord->Dr6, 3, 1)) g_gdbstub->HandleAccessException(pExceptionInfo->ContextRecord->Dr6); return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } LONG WINAPI cemu_unhandledExceptionFilter(EXCEPTION_POINTERS* pExceptionInfo) { createCrashlog(pExceptionInfo, pExceptionInfo->ContextRecord); return EXCEPTION_NONCONTINUABLE_EXCEPTION; } void ExceptionHandler_Init() { SetUnhandledExceptionFilter(cemu_unhandledExceptionFilter); AddVectoredExceptionHandler(1, VectoredExceptionHandler); SetErrorMode(SEM_FAILCRITICALERRORS); } ================================================ FILE: src/Common/FileStream.h ================================================ #pragma once #include "Common/precompiled.h" #ifdef _WIN32 #include "Common/windows/FileStream_win32.h" #else #include "Common/unix/FileStream_unix.h" #endif ================================================ FILE: src/Common/GLInclude/GLInclude.h ================================================ #pragma once #include "glext.h" #if BOOST_OS_WINDOWS > 0 #include "wglext.h" #endif #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) > 0 // from Xlib #define Bool int #define Status int #define True 1 #define False 0 // from system glx.h typedef XID GLXContextID; typedef XID GLXPixmap; typedef XID GLXDrawable; typedef XID GLXPbuffer; typedef XID GLXWindow; typedef XID GLXFBConfigID; typedef struct __GLXcontextRec *GLXContext; typedef struct __GLXFBConfigRec *GLXFBConfig; #define EGL_EGL_PROTOTYPES 0 #include "egl.h" #undef EGL_EGL_PROTOTYPES #include "glxext.h" #undef Bool #undef Status #undef True #undef False #endif namespace CemuGL { #define GLFUNC(__type, __name) extern __type __name; #define EGLFUNC(__type, __name) extern __type __name; #include "glFunctions.h" #undef GLFUNC #undef EGLFUNC // DSA-style helpers with fallback to legacy API if DSA is not supported #define DSA_FORCE_DISABLE false // set to true to simulate DSA not being supported static GLenum GetGLBindingFromTextureTarget(GLenum texTarget) { switch(texTarget) { case GL_TEXTURE_1D: return GL_TEXTURE_BINDING_1D; case GL_TEXTURE_2D: return GL_TEXTURE_BINDING_2D; case GL_TEXTURE_3D: return GL_TEXTURE_BINDING_3D; case GL_TEXTURE_2D_ARRAY: return GL_TEXTURE_BINDING_2D_ARRAY; case GL_TEXTURE_CUBE_MAP: return GL_TEXTURE_BINDING_CUBE_MAP; case GL_TEXTURE_CUBE_MAP_ARRAY: return GL_TEXTURE_BINDING_CUBE_MAP_ARRAY; default: cemu_assert_unimplemented(); return 0; } } static GLuint glCreateTextureWrapper(GLenum target) { GLuint tex; if (glCreateTextures && !DSA_FORCE_DISABLE) { glCreateTextures(target, 1, &tex); return tex; } GLint originalTexture; glGetIntegerv(GetGLBindingFromTextureTarget(target), &originalTexture); glGenTextures(1, &tex); glBindTexture(target, tex); glBindTexture(target, originalTexture); return tex; } static void glTextureStorage1DWrapper(GLenum target, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width) { if (glTextureStorage1D && !DSA_FORCE_DISABLE) { glTextureStorage1D(texture, levels, internalformat, width); return; } GLenum binding = GetGLBindingFromTextureTarget(target); GLint originalTexture; glGetIntegerv(binding, &originalTexture); glBindTexture(target, texture); glTexStorage1D(target, levels, internalformat, width); glBindTexture(target, originalTexture); } static void glTextureStorage2DWrapper(GLenum target, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) { if (glTextureStorage2D && !DSA_FORCE_DISABLE) { glTextureStorage2D(texture, levels, internalformat, width, height); return; } GLenum binding = GetGLBindingFromTextureTarget(target); GLint originalTexture; glGetIntegerv(binding, &originalTexture); glBindTexture(target, texture); glTexStorage2D(target, levels, internalformat, width, height); glBindTexture(target, originalTexture); } static void glTextureStorage3DWrapper(GLenum target, GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) { if (glTextureStorage3D && !DSA_FORCE_DISABLE) { glTextureStorage3D(texture, levels, internalformat, width, height, depth); return; } GLenum binding = GetGLBindingFromTextureTarget(target); GLint originalTexture; glGetIntegerv(binding, &originalTexture); glBindTexture(target, texture); glTexStorage3D(target, levels, internalformat, width, height, depth); glBindTexture(target, originalTexture); } static void glTextureSubImage1DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels) { if (glTextureSubImage1D && !DSA_FORCE_DISABLE) { glTextureSubImage1D(texture, level, xoffset, width, format, type, pixels); return; } GLenum binding = GetGLBindingFromTextureTarget(target); GLint originalTexture; glGetIntegerv(binding, &originalTexture); glBindTexture(target, texture); glTexSubImage1D(target, level, xoffset, width, format, type, pixels); glBindTexture(target, originalTexture); } static void glCompressedTextureSubImage1DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) { if (glCompressedTextureSubImage1D && !DSA_FORCE_DISABLE) { glCompressedTextureSubImage1D(texture, level, xoffset, width, format, imageSize, data); return; } GLenum binding = GetGLBindingFromTextureTarget(target); GLint originalTexture; glGetIntegerv(binding, &originalTexture); glBindTexture(target, texture); glCompressedTexSubImage1D(target, level, xoffset, width, format, imageSize, data); glBindTexture(target, originalTexture); } static void glTextureSubImage2DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) { if (glTextureSubImage2D && !DSA_FORCE_DISABLE) { glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels); return; } GLenum binding = GetGLBindingFromTextureTarget(target); GLint originalTexture; glGetIntegerv(binding, &originalTexture); glBindTexture(target, texture); glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); glBindTexture(target, originalTexture); } static void glCompressedTextureSubImage2DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) { if (glCompressedTextureSubImage2D && !DSA_FORCE_DISABLE) { glCompressedTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, imageSize, data); return; } GLenum binding = GetGLBindingFromTextureTarget(target); GLint originalTexture; glGetIntegerv(binding, &originalTexture); glBindTexture(target, texture); glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data); glBindTexture(target, originalTexture); } static void glTextureSubImage3DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) { if(glTextureSubImage3D && !DSA_FORCE_DISABLE) { glTextureSubImage3D(texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); return; } GLenum binding = GetGLBindingFromTextureTarget(target); GLint originalTexture; glGetIntegerv(binding, &originalTexture); glBindTexture(target, texture); glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); glBindTexture(target, originalTexture); } static void glCompressedTextureSubImage3DWrapper(GLenum target, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) { if(glCompressedTextureSubImage3D && !DSA_FORCE_DISABLE) { glCompressedTextureSubImage3D(texture, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data); return; } GLenum binding = GetGLBindingFromTextureTarget(target); GLint originalTexture; glGetIntegerv(binding, &originalTexture); glBindTexture(target, texture); glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data); glBindTexture(target, originalTexture); } } using namespace CemuGL; // this prevents Windows GL.h from being included: #define __gl_h_ #define __GL_H__ ================================================ FILE: src/Common/GLInclude/egl.h ================================================ #ifndef __egl_h_ #define __egl_h_ 1 #ifdef __cplusplus extern "C" { #endif /* ** Copyright 2013-2020 The Khronos Group Inc. ** SPDX-License-Identifier: Apache-2.0 ** ** This header is generated from the Khronos EGL XML API Registry. ** The current version of the Registry, generator scripts ** used to make the header, and the header can be found at ** http://www.khronos.org/registry/egl ** ** Khronos $Git commit SHA1: 8c62b915dd $ on $Git commit date: 2021-11-05 23:32:01 -0400 $ */ #include #ifndef EGL_EGL_PROTOTYPES #define EGL_EGL_PROTOTYPES 1 #endif /* Generated on date 20211116 */ /* Generated C header for: * API: egl * Versions considered: .* * Versions emitted: .* * Default extensions included: None * Additional extensions included: _nomatch_^ * Extensions removed: _nomatch_^ */ #ifndef EGL_VERSION_1_0 #define EGL_VERSION_1_0 1 typedef unsigned int EGLBoolean; typedef void *EGLDisplay; #include #include typedef void *EGLConfig; typedef void *EGLSurface; typedef void *EGLContext; typedef void (*__eglMustCastToProperFunctionPointerType)(void); #define EGL_ALPHA_SIZE 0x3021 #define EGL_BAD_ACCESS 0x3002 #define EGL_BAD_ALLOC 0x3003 #define EGL_BAD_ATTRIBUTE 0x3004 #define EGL_BAD_CONFIG 0x3005 #define EGL_BAD_CONTEXT 0x3006 #define EGL_BAD_CURRENT_SURFACE 0x3007 #define EGL_BAD_DISPLAY 0x3008 #define EGL_BAD_MATCH 0x3009 #define EGL_BAD_NATIVE_PIXMAP 0x300A #define EGL_BAD_NATIVE_WINDOW 0x300B #define EGL_BAD_PARAMETER 0x300C #define EGL_BAD_SURFACE 0x300D #define EGL_BLUE_SIZE 0x3022 #define EGL_BUFFER_SIZE 0x3020 #define EGL_CONFIG_CAVEAT 0x3027 #define EGL_CONFIG_ID 0x3028 #define EGL_CORE_NATIVE_ENGINE 0x305B #define EGL_DEPTH_SIZE 0x3025 #define EGL_DONT_CARE EGL_CAST(EGLint,-1) #define EGL_DRAW 0x3059 #define EGL_EXTENSIONS 0x3055 #define EGL_FALSE 0 #define EGL_GREEN_SIZE 0x3023 #define EGL_HEIGHT 0x3056 #define EGL_LARGEST_PBUFFER 0x3058 #define EGL_LEVEL 0x3029 #define EGL_MAX_PBUFFER_HEIGHT 0x302A #define EGL_MAX_PBUFFER_PIXELS 0x302B #define EGL_MAX_PBUFFER_WIDTH 0x302C #define EGL_NATIVE_RENDERABLE 0x302D #define EGL_NATIVE_VISUAL_ID 0x302E #define EGL_NATIVE_VISUAL_TYPE 0x302F #define EGL_NONE 0x3038 #define EGL_NON_CONFORMANT_CONFIG 0x3051 #define EGL_NOT_INITIALIZED 0x3001 #define EGL_NO_CONTEXT EGL_CAST(EGLContext,0) #define EGL_NO_DISPLAY EGL_CAST(EGLDisplay,0) #define EGL_NO_SURFACE EGL_CAST(EGLSurface,0) #define EGL_PBUFFER_BIT 0x0001 #define EGL_PIXMAP_BIT 0x0002 #define EGL_READ 0x305A #define EGL_RED_SIZE 0x3024 #define EGL_SAMPLES 0x3031 #define EGL_SAMPLE_BUFFERS 0x3032 #define EGL_SLOW_CONFIG 0x3050 #define EGL_STENCIL_SIZE 0x3026 #define EGL_SUCCESS 0x3000 #define EGL_SURFACE_TYPE 0x3033 #define EGL_TRANSPARENT_BLUE_VALUE 0x3035 #define EGL_TRANSPARENT_GREEN_VALUE 0x3036 #define EGL_TRANSPARENT_RED_VALUE 0x3037 #define EGL_TRANSPARENT_RGB 0x3052 #define EGL_TRANSPARENT_TYPE 0x3034 #define EGL_TRUE 1 #define EGL_VENDOR 0x3053 #define EGL_VERSION 0x3054 #define EGL_WIDTH 0x3057 #define EGL_WINDOW_BIT 0x0004 typedef EGLBoolean (EGLAPIENTRYP PFNEGLCHOOSECONFIGPROC) (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOPYBUFFERSPROC) (EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target); typedef EGLContext (EGLAPIENTRYP PFNEGLCREATECONTEXTPROC) (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPBUFFERSURFACEPROC) (EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list); typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPIXMAPSURFACEPROC) (EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list); typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEWINDOWSURFACEPROC) (EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYCONTEXTPROC) (EGLDisplay dpy, EGLContext ctx); typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSURFACEPROC) (EGLDisplay dpy, EGLSurface surface); typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETCONFIGATTRIBPROC) (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETCONFIGSPROC) (EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETCURRENTDISPLAYPROC) (void); typedef EGLSurface (EGLAPIENTRYP PFNEGLGETCURRENTSURFACEPROC) (EGLint readdraw); typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETDISPLAYPROC) (EGLNativeDisplayType display_id); typedef EGLint (EGLAPIENTRYP PFNEGLGETERRORPROC) (void); typedef __eglMustCastToProperFunctionPointerType (EGLAPIENTRYP PFNEGLGETPROCADDRESSPROC) (const char *procname); typedef EGLBoolean (EGLAPIENTRYP PFNEGLINITIALIZEPROC) (EGLDisplay dpy, EGLint *major, EGLint *minor); typedef EGLBoolean (EGLAPIENTRYP PFNEGLMAKECURRENTPROC) (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYCONTEXTPROC) (EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value); typedef const char *(EGLAPIENTRYP PFNEGLQUERYSTRINGPROC) (EGLDisplay dpy, EGLint name); typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSURFACEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value); typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSPROC) (EGLDisplay dpy, EGLSurface surface); typedef EGLBoolean (EGLAPIENTRYP PFNEGLTERMINATEPROC) (EGLDisplay dpy); typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITGLPROC) (void); typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITNATIVEPROC) (EGLint engine); #if EGL_EGL_PROTOTYPES EGLAPI EGLBoolean EGLAPIENTRY eglChooseConfig (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); EGLAPI EGLBoolean EGLAPIENTRY eglCopyBuffers (EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target); EGLAPI EGLContext EGLAPIENTRY eglCreateContext (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferSurface (EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list); EGLAPI EGLSurface EGLAPIENTRY eglCreatePixmapSurface (EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list); EGLAPI EGLSurface EGLAPIENTRY eglCreateWindowSurface (EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); EGLAPI EGLBoolean EGLAPIENTRY eglDestroyContext (EGLDisplay dpy, EGLContext ctx); EGLAPI EGLBoolean EGLAPIENTRY eglDestroySurface (EGLDisplay dpy, EGLSurface surface); EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigAttrib (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigs (EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); EGLAPI EGLDisplay EGLAPIENTRY eglGetCurrentDisplay (void); EGLAPI EGLSurface EGLAPIENTRY eglGetCurrentSurface (EGLint readdraw); EGLAPI EGLDisplay EGLAPIENTRY eglGetDisplay (EGLNativeDisplayType display_id); EGLAPI EGLint EGLAPIENTRY eglGetError (void); EGLAPI __eglMustCastToProperFunctionPointerType EGLAPIENTRY eglGetProcAddress (const char *procname); EGLAPI EGLBoolean EGLAPIENTRY eglInitialize (EGLDisplay dpy, EGLint *major, EGLint *minor); EGLAPI EGLBoolean EGLAPIENTRY eglMakeCurrent (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); EGLAPI EGLBoolean EGLAPIENTRY eglQueryContext (EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value); EGLAPI const char *EGLAPIENTRY eglQueryString (EGLDisplay dpy, EGLint name); EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurface (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value); EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffers (EGLDisplay dpy, EGLSurface surface); EGLAPI EGLBoolean EGLAPIENTRY eglTerminate (EGLDisplay dpy); EGLAPI EGLBoolean EGLAPIENTRY eglWaitGL (void); EGLAPI EGLBoolean EGLAPIENTRY eglWaitNative (EGLint engine); #endif #endif /* EGL_VERSION_1_0 */ #ifndef EGL_VERSION_1_1 #define EGL_VERSION_1_1 1 #define EGL_BACK_BUFFER 0x3084 #define EGL_BIND_TO_TEXTURE_RGB 0x3039 #define EGL_BIND_TO_TEXTURE_RGBA 0x303A #define EGL_CONTEXT_LOST 0x300E #define EGL_MIN_SWAP_INTERVAL 0x303B #define EGL_MAX_SWAP_INTERVAL 0x303C #define EGL_MIPMAP_TEXTURE 0x3082 #define EGL_MIPMAP_LEVEL 0x3083 #define EGL_NO_TEXTURE 0x305C #define EGL_TEXTURE_2D 0x305F #define EGL_TEXTURE_FORMAT 0x3080 #define EGL_TEXTURE_RGB 0x305D #define EGL_TEXTURE_RGBA 0x305E #define EGL_TEXTURE_TARGET 0x3081 typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDTEXIMAGEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint buffer); typedef EGLBoolean (EGLAPIENTRYP PFNEGLRELEASETEXIMAGEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint buffer); typedef EGLBoolean (EGLAPIENTRYP PFNEGLSURFACEATTRIBPROC) (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value); typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPINTERVALPROC) (EGLDisplay dpy, EGLint interval); #if EGL_EGL_PROTOTYPES EGLAPI EGLBoolean EGLAPIENTRY eglBindTexImage (EGLDisplay dpy, EGLSurface surface, EGLint buffer); EGLAPI EGLBoolean EGLAPIENTRY eglReleaseTexImage (EGLDisplay dpy, EGLSurface surface, EGLint buffer); EGLAPI EGLBoolean EGLAPIENTRY eglSurfaceAttrib (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value); EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval (EGLDisplay dpy, EGLint interval); #endif #endif /* EGL_VERSION_1_1 */ #ifndef EGL_VERSION_1_2 #define EGL_VERSION_1_2 1 typedef unsigned int EGLenum; typedef void *EGLClientBuffer; #define EGL_ALPHA_FORMAT 0x3088 #define EGL_ALPHA_FORMAT_NONPRE 0x308B #define EGL_ALPHA_FORMAT_PRE 0x308C #define EGL_ALPHA_MASK_SIZE 0x303E #define EGL_BUFFER_PRESERVED 0x3094 #define EGL_BUFFER_DESTROYED 0x3095 #define EGL_CLIENT_APIS 0x308D #define EGL_COLORSPACE 0x3087 #define EGL_COLORSPACE_sRGB 0x3089 #define EGL_COLORSPACE_LINEAR 0x308A #define EGL_COLOR_BUFFER_TYPE 0x303F #define EGL_CONTEXT_CLIENT_TYPE 0x3097 #define EGL_DISPLAY_SCALING 10000 #define EGL_HORIZONTAL_RESOLUTION 0x3090 #define EGL_LUMINANCE_BUFFER 0x308F #define EGL_LUMINANCE_SIZE 0x303D #define EGL_OPENGL_ES_BIT 0x0001 #define EGL_OPENVG_BIT 0x0002 #define EGL_OPENGL_ES_API 0x30A0 #define EGL_OPENVG_API 0x30A1 #define EGL_OPENVG_IMAGE 0x3096 #define EGL_PIXEL_ASPECT_RATIO 0x3092 #define EGL_RENDERABLE_TYPE 0x3040 #define EGL_RENDER_BUFFER 0x3086 #define EGL_RGB_BUFFER 0x308E #define EGL_SINGLE_BUFFER 0x3085 #define EGL_SWAP_BEHAVIOR 0x3093 #define EGL_UNKNOWN EGL_CAST(EGLint,-1) #define EGL_VERTICAL_RESOLUTION 0x3091 typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDAPIPROC) (EGLenum api); typedef EGLenum (EGLAPIENTRYP PFNEGLQUERYAPIPROC) (void); typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPBUFFERFROMCLIENTBUFFERPROC) (EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list); typedef EGLBoolean (EGLAPIENTRYP PFNEGLRELEASETHREADPROC) (void); typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITCLIENTPROC) (void); #if EGL_EGL_PROTOTYPES EGLAPI EGLBoolean EGLAPIENTRY eglBindAPI (EGLenum api); EGLAPI EGLenum EGLAPIENTRY eglQueryAPI (void); EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferFromClientBuffer (EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list); EGLAPI EGLBoolean EGLAPIENTRY eglReleaseThread (void); EGLAPI EGLBoolean EGLAPIENTRY eglWaitClient (void); #endif #endif /* EGL_VERSION_1_2 */ #ifndef EGL_VERSION_1_3 #define EGL_VERSION_1_3 1 #define EGL_CONFORMANT 0x3042 #define EGL_CONTEXT_CLIENT_VERSION 0x3098 #define EGL_MATCH_NATIVE_PIXMAP 0x3041 #define EGL_OPENGL_ES2_BIT 0x0004 #define EGL_VG_ALPHA_FORMAT 0x3088 #define EGL_VG_ALPHA_FORMAT_NONPRE 0x308B #define EGL_VG_ALPHA_FORMAT_PRE 0x308C #define EGL_VG_ALPHA_FORMAT_PRE_BIT 0x0040 #define EGL_VG_COLORSPACE 0x3087 #define EGL_VG_COLORSPACE_sRGB 0x3089 #define EGL_VG_COLORSPACE_LINEAR 0x308A #define EGL_VG_COLORSPACE_LINEAR_BIT 0x0020 #endif /* EGL_VERSION_1_3 */ #ifndef EGL_VERSION_1_4 #define EGL_VERSION_1_4 1 #define EGL_DEFAULT_DISPLAY EGL_CAST(EGLNativeDisplayType,0) #define EGL_MULTISAMPLE_RESOLVE_BOX_BIT 0x0200 #define EGL_MULTISAMPLE_RESOLVE 0x3099 #define EGL_MULTISAMPLE_RESOLVE_DEFAULT 0x309A #define EGL_MULTISAMPLE_RESOLVE_BOX 0x309B #define EGL_OPENGL_API 0x30A2 #define EGL_OPENGL_BIT 0x0008 #define EGL_SWAP_BEHAVIOR_PRESERVED_BIT 0x0400 typedef EGLContext (EGLAPIENTRYP PFNEGLGETCURRENTCONTEXTPROC) (void); #if EGL_EGL_PROTOTYPES EGLAPI EGLContext EGLAPIENTRY eglGetCurrentContext (void); #endif #endif /* EGL_VERSION_1_4 */ #ifndef EGL_VERSION_1_5 #define EGL_VERSION_1_5 1 typedef void *EGLSync; typedef intptr_t EGLAttrib; typedef khronos_utime_nanoseconds_t EGLTime; typedef void *EGLImage; #define EGL_CONTEXT_MAJOR_VERSION 0x3098 #define EGL_CONTEXT_MINOR_VERSION 0x30FB #define EGL_CONTEXT_OPENGL_PROFILE_MASK 0x30FD #define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY 0x31BD #define EGL_NO_RESET_NOTIFICATION 0x31BE #define EGL_LOSE_CONTEXT_ON_RESET 0x31BF #define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT 0x00000001 #define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT 0x00000002 #define EGL_CONTEXT_OPENGL_DEBUG 0x31B0 #define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE 0x31B1 #define EGL_CONTEXT_OPENGL_ROBUST_ACCESS 0x31B2 #define EGL_OPENGL_ES3_BIT 0x00000040 #define EGL_CL_EVENT_HANDLE 0x309C #define EGL_SYNC_CL_EVENT 0x30FE #define EGL_SYNC_CL_EVENT_COMPLETE 0x30FF #define EGL_SYNC_PRIOR_COMMANDS_COMPLETE 0x30F0 #define EGL_SYNC_TYPE 0x30F7 #define EGL_SYNC_STATUS 0x30F1 #define EGL_SYNC_CONDITION 0x30F8 #define EGL_SIGNALED 0x30F2 #define EGL_UNSIGNALED 0x30F3 #define EGL_SYNC_FLUSH_COMMANDS_BIT 0x0001 #define EGL_FOREVER 0xFFFFFFFFFFFFFFFFull #define EGL_TIMEOUT_EXPIRED 0x30F5 #define EGL_CONDITION_SATISFIED 0x30F6 #define EGL_NO_SYNC EGL_CAST(EGLSync,0) #define EGL_SYNC_FENCE 0x30F9 #define EGL_GL_COLORSPACE 0x309D #define EGL_GL_COLORSPACE_SRGB 0x3089 #define EGL_GL_COLORSPACE_LINEAR 0x308A #define EGL_GL_RENDERBUFFER 0x30B9 #define EGL_GL_TEXTURE_2D 0x30B1 #define EGL_GL_TEXTURE_LEVEL 0x30BC #define EGL_GL_TEXTURE_3D 0x30B2 #define EGL_GL_TEXTURE_ZOFFSET 0x30BD #define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x30B3 #define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x30B4 #define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x30B5 #define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x30B6 #define EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x30B7 #define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x30B8 #define EGL_IMAGE_PRESERVED 0x30D2 #define EGL_NO_IMAGE EGL_CAST(EGLImage,0) typedef EGLSync (EGLAPIENTRYP PFNEGLCREATESYNCPROC) (EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list); typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCPROC) (EGLDisplay dpy, EGLSync sync); typedef EGLint (EGLAPIENTRYP PFNEGLCLIENTWAITSYNCPROC) (EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout); typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETSYNCATTRIBPROC) (EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib *value); typedef EGLImage (EGLAPIENTRYP PFNEGLCREATEIMAGEPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLAttrib *attrib_list); typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEPROC) (EGLDisplay dpy, EGLImage image); typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYPROC) (EGLenum platform, void *native_display, const EGLAttrib *attrib_list); typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list); typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEPROC) (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLAttrib *attrib_list); typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITSYNCPROC) (EGLDisplay dpy, EGLSync sync, EGLint flags); #if EGL_EGL_PROTOTYPES EGLAPI EGLSync EGLAPIENTRY eglCreateSync (EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list); EGLAPI EGLBoolean EGLAPIENTRY eglDestroySync (EGLDisplay dpy, EGLSync sync); EGLAPI EGLint EGLAPIENTRY eglClientWaitSync (EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout); EGLAPI EGLBoolean EGLAPIENTRY eglGetSyncAttrib (EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib *value); EGLAPI EGLImage EGLAPIENTRY eglCreateImage (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLAttrib *attrib_list); EGLAPI EGLBoolean EGLAPIENTRY eglDestroyImage (EGLDisplay dpy, EGLImage image); EGLAPI EGLDisplay EGLAPIENTRY eglGetPlatformDisplay (EGLenum platform, void *native_display, const EGLAttrib *attrib_list); EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurface (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list); EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformPixmapSurface (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLAttrib *attrib_list); EGLAPI EGLBoolean EGLAPIENTRY eglWaitSync (EGLDisplay dpy, EGLSync sync, EGLint flags); #endif #endif /* EGL_VERSION_1_5 */ #ifdef __cplusplus } #endif #endif ================================================ FILE: src/Common/GLInclude/glFunctions.h ================================================ // OpenGL 1.1 - 2.0 GLFUNC(PFNGLDRAWBUFFERPROC, glDrawBuffer) GLFUNC(PFNGLGENTEXTURESPROC, glGenTextures) GLFUNC(PFNGLDELETETEXTURESPROC, glDeleteTextures) GLFUNC(PFNGLBINDTEXTUREPROC, glBindTexture) GLFUNC(PFNGLTEXPARAMETERIPROC, glTexParameteri) GLFUNC(PFNGLTEXIMAGE2DPROC, glTexImage2D) GLFUNC(PFNGLTEXSUBIMAGE2DPROC, glTexSubImage2D) GLFUNC(PFNGLTEXIMAGE1DPROC, glTexImage1D) GLFUNC(PFNGLTEXSUBIMAGE1DPROC, glTexSubImage1D) GLFUNC(PFNGLGETTEXIMAGEPROC, glGetTexImage) GLFUNC(PFNGLENABLEPROC, glEnable) GLFUNC(PFNGLDISABLEPROC, glDisable) GLFUNC(PFNGLISENABLEDPROC, glIsEnabled) GLFUNC(PFNGLCLEARPROC, glClear) GLFUNC(PFNGLCLEARCOLORPROC, glClearColor) GLFUNC(PFNGLCLEARDEPTHPROC, glClearDepth) GLFUNC(PFNGLCLEARSTENCILPROC, glClearStencil) GLFUNC(PFNGLFLUSHPROC, glFlush) GLFUNC(PFNGLFINISHPROC, glFinish) GLFUNC(PFNGLPIXELSTOREIPROC, glPixelStorei) GLFUNC(PFNGLGETSTRINGPROC, glGetString) GLFUNC(PFNGLGETINTEGERVPROC, glGetIntegerv) GLFUNC(PFNGLGETTEXLEVELPARAMETERIVPROC, glGetTexLevelParameteriv) GLFUNC(PFNGLTEXPARAMETERFPROC, glTexParameterf) GLFUNC(PFNGLTEXPARAMETERFVPROC, glTexParameterfv) GLFUNC(PFNGLDEPTHFUNCPROC, glDepthFunc) GLFUNC(PFNGLDEPTHMASKPROC, glDepthMask) GLFUNC(PFNGLDEPTHRANGEPROC, glDepthRange) GLFUNC(PFNGLFRONTFACEPROC, glFrontFace) GLFUNC(PFNGLCULLFACEPROC, glCullFace) GLFUNC(PFNGLPOLYGONOFFSETPROC, glPolygonOffset) GLFUNC(PFNGLPOINTSIZEPROC, glPointSize) GLFUNC(PFNGLLOGICOPPROC, glLogicOp) GLFUNC(PFNGLPOLYGONMODEPROC, glPolygonMode) GLFUNC(PFNGLSCISSORPROC, glScissor) GLFUNC(PFNGLVIEWPORTPROC, glViewport) GLFUNC(PFNGLGETERRORPROC, glGetError) GLFUNC(PFNGLDRAWARRAYSPROC, glDrawArrays) // everything else GLFUNC(PFNGLUNIFORM1IPROC, glUniform1i) GLFUNC(PFNGLUNIFORM2IPROC, glUniform2i) GLFUNC(PFNGLUNIFORM2FPROC, glUniform2f) GLFUNC(PFNGLUNIFORM4FVPROC, glUniform4fv) GLFUNC(PFNGLUNIFORMMATRIX4FVPROC, glUniformMatrix4fv) GLFUNC(PFNGLGETSHADERINFOLOGPROC, glGetShaderInfoLog) GLFUNC(PFNGLUSEPROGRAMPROC, glUseProgram) GLFUNC(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation) GLFUNC(PFNGLACTIVETEXTUREPROC, glActiveTexture) GLFUNC(PFNGLCLIENTACTIVETEXTUREPROC, glClientActiveTexture) GLFUNC(PFNGLPRIMITIVERESTARTINDEXPROC, glPrimitiveRestartIndex) GLFUNC(PFNGLDRAWRANGEELEMENTSPROC, glDrawRangeElements) GLFUNC(PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC, glDrawRangeElementsBaseVertex) GLFUNC(PFNGLGENBUFFERSPROC, glGenBuffers) GLFUNC(PFNGLBINDBUFFERPROC, glBindBuffer) GLFUNC(PFNGLBUFFERDATAPROC, glBufferData) GLFUNC(PFNGLBUFFERSUBDATAPROC, glBufferSubData) GLFUNC(PFNGLMAPBUFFERPROC, glMapBuffer) GLFUNC(PFNGLUNMAPBUFFERPROC, glUnmapBuffer) GLFUNC(PFNGLDELETEBUFFERSARBPROC, glDeleteBuffers) GLFUNC(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray) GLFUNC(PFNGLDISABLEVERTEXATTRIBARRAYPROC, glDisableVertexAttribArray) GLFUNC(PFNGLGETATTRIBLOCATIONPROC, glGetAttribLocation) GLFUNC(PFNGLBINDATTRIBLOCATIONPROC, glBindAttribLocation) GLFUNC(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer) GLFUNC(PFNGLVERTEXATTRIBIPOINTERPROC, glVertexAttribIPointer) GLFUNC(PFNGLBLENDEQUATIONPROC, glBlendEquation) GLFUNC(PFNGLBLENDFUNCSEPARATEEXTPROC, glBlendFuncSeparate) GLFUNC(PFNGLBLENDEQUATIONSEPARATEEXTPROC, glBlendEquationSeparate) GLFUNC(PFNGLBLENDEQUATIONSEPARATEIPROC, glBlendEquationSeparatei) GLFUNC(PFNGLBLENDFUNCSEPARATEIPROC, glBlendFuncSeparatei) GLFUNC(PFNGLBLENDCOLORPROC, glBlendColor) GLFUNC(PFNGLDRAWBUFFERSPROC, glDrawBuffers) GLFUNC(PFNGLCLAMPCOLORARBPROC, glClampColor) GLFUNC(PFNGLBINDFRAGDATALOCATIONEXTPROC, glBindFragDataLocation) GLFUNC(PFNGLSHADERSOURCEPROC, glShaderSource) GLFUNC(PFNGLCOMPILESHADERPROC, glCompileShader) GLFUNC(PFNGLATTACHSHADERPROC, glAttachShader) GLFUNC(PFNGLLINKPROGRAMPROC, glLinkProgram) GLFUNC(PFNGLGETSHADERIVPROC, glGetShaderiv) GLFUNC(PFNGLGETPROGRAMIVPROC, glGetProgramiv) GLFUNC(PFNGLGETPROGRAMINFOLOGPROC, glGetProgramInfoLog) GLFUNC(PFNGLGETPROGRAMBINARYPROC, glGetProgramBinary) GLFUNC(PFNGLPROGRAMBINARYPROC, glProgramBinary) GLFUNC(PFNGLPROGRAMPARAMETERIPROC, glProgramParameteri) GLFUNC(PFNGLCREATEPROGRAMPROC, glCreateProgram) GLFUNC(PFNGLCREATESHADERPROC, glCreateShader) GLFUNC(PFNGLDELETEPROGRAMPROC, glDeleteProgram) GLFUNC(PFNGLDELETESHADERPROC, glDeleteShader) GLFUNC(PFNGLDETACHSHADERPROC, glDetachShader) GLFUNC(PFNGLGETATTACHEDSHADERSPROC, glGetAttachedShaders) GLFUNC(PFNGLCREATESHADERPROGRAMVPROC, glCreateShaderProgramv) GLFUNC(PFNGLUSEPROGRAMSTAGESPROC, glUseProgramStages) GLFUNC(PFNGLBINDPROGRAMPIPELINEPROC, glBindProgramPipeline) GLFUNC(PFNGLGENPROGRAMPIPELINESPROC, glGenProgramPipelines) GLFUNC(PFNGLACTIVESHADERPROGRAMPROC, glActiveShaderProgram) GLFUNC(PFNGLDELETEPROGRAMPIPELINESPROC, glDeleteProgramPipelines) GLFUNC(PFNGLPROGRAMUNIFORM1IPROC, glProgramUniform1i) GLFUNC(PFNGLPROGRAMUNIFORM2IPROC, glProgramUniform2i) GLFUNC(PFNGLPROGRAMUNIFORM1IVPROC, glProgramUniform1iv) GLFUNC(PFNGLPROGRAMUNIFORM4IVPROC, glProgramUniform4iv) GLFUNC(PFNGLPROGRAMUNIFORM1IPROC, glProgramUniform1f) GLFUNC(PFNGLPROGRAMUNIFORM1FVPROC, glProgramUniform1fv) GLFUNC(PFNGLPROGRAMUNIFORM2FVPROC, glProgramUniform2fv) // FBO GLFUNC(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers) GLFUNC(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer) GLFUNC(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D) GLFUNC(PFNGLFRAMEBUFFERTEXTURELAYERPROC, glFramebufferTextureLayer) GLFUNC(PFNGLNAMEDFRAMEBUFFERTEXTUREPROC, glNamedFramebufferTexture) GLFUNC(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus) GLFUNC(PFNGLINVALIDATEFRAMEBUFFERPROC, glInvalidateFramebuffer) GLFUNC(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers) //GLFUNC(PFNGLNAMEDFRAMEBUFFERTEXTUREPROC, glNamedFramebufferTexture) GLFUNC(PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC, glNamedFramebufferTexture2DEXT) GLFUNC(PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC, glNamedFramebufferTextureLayer) GLFUNC(PFNGLENABLEIPROC, glEnablei) GLFUNC(PFNGLDISABLEIPROC, glDisablei) GLFUNC(PFNGLBEGINQUERYINDEXEDPROC, glBeginQueryIndexed) GLFUNC(PFNGLENDQUERYINDEXEDPROC, glEndQueryIndexed) GLFUNC(PFNGLGETQUERYINDEXEDIVPROC, glGetQueryIndexediv) GLFUNC(PFNGLGETQUERYOBJECTI64VPROC, glGetQueryObjecti64v) GLFUNC(PFNGLGENQUERIESPROC, glGenQueries) GLFUNC(PFNGLDELETEQUERIESPROC, glDeleteQueries) GLFUNC(PFNGLQUERYCOUNTERPROC, glQueryCounter) GLFUNC(PFNGLGETQUERYOBJECTIVPROC, glGetQueryObjectiv) // FBO DSA GLFUNC(PFNGLCREATEFRAMEBUFFERSPROC, glCreateFramebuffers) GLFUNC(PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC, glInvalidateNamedFramebufferData) // misc GLFUNC(PFNGLCOLORMASKIPROC, glColorMaski) GLFUNC(PFNGLTEXTUREBARRIERPROC, glTextureBarrier) GLFUNC(PFNGLCLIPCONTROLPROC, glClipControl) GLFUNC(PFNGLVIEWPORTINDEXEDFPROC, glViewportIndexedf) GLFUNC(PFNGLMAXSHADERCOMPILERTHREADSARBPROC, glMaxShaderCompilerThreadsARB) GLFUNC(PFNGLDEPTHRANGEDNVPROC, glDepthRangedNV) GLFUNC(PFNGLPOLYGONOFFSETCLAMPEXTPROC, glPolygonOffsetClampEXT) // texture GLFUNC(PFNGLTEXTUREVIEWPROC, glTextureView) GLFUNC(PFNGLTEXSTORAGE1DPROC, glTexStorage1D) GLFUNC(PFNGLTEXSTORAGE2DPROC, glTexStorage2D) GLFUNC(PFNGLTEXSTORAGE3DPROC, glTexStorage3D) GLFUNC(PFNGLTEXIMAGE3DPROC, glTexImage3D) GLFUNC(PFNGLTEXSUBIMAGE3DPROC, glTexSubImage3D) GLFUNC(PFNGLCOMPRESSEDTEXIMAGE1DPROC, glCompressedTexImage1D) GLFUNC(PFNGLCOMPRESSEDTEXIMAGE2DPROC, glCompressedTexImage2D) GLFUNC(PFNGLCOMPRESSEDTEXIMAGE3DPROC, glCompressedTexImage3D) GLFUNC(PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC, glCompressedTexSubImage1D) GLFUNC(PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC, glCompressedTexSubImage2D) GLFUNC(PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC, glCompressedTexSubImage3D) GLFUNC(PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC, glCompressedTextureSubImage1D) GLFUNC(PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC, glCompressedTextureSubImage2D) GLFUNC(PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC, glCompressedTextureSubImage3D) GLFUNC(PFNGLCOPYIMAGESUBDATAPROC, glCopyImageSubData) GLFUNC(PFNGLCLEARTEXIMAGEPROC, glClearTexImage) GLFUNC(PFNGLCLEARTEXSUBIMAGEPROC, glClearTexSubImage) GLFUNC(PFNGLINVALIDATETEXIMAGEPROC, glInvalidateTexImage) // texture DSA GLFUNC(PFNGLCREATETEXTURESPROC, glCreateTextures) GLFUNC(PFNGLBINDTEXTUREUNITPROC, glBindTextureUnit) GLFUNC(PFNGLGETTEXTURELEVELPARAMETERIVPROC, glGetTextureLevelParameteriv) GLFUNC(PFNGLTEXTUREPARAMETERIPROC, glTextureParameteri) GLFUNC(PFNGLGETTEXTURESUBIMAGEPROC, glGetTextureSubImage) GLFUNC(PFNGLTEXTURESUBIMAGE1DPROC, glTextureSubImage1D) GLFUNC(PFNGLTEXTURESUBIMAGE2DPROC, glTextureSubImage2D); GLFUNC(PFNGLTEXTURESUBIMAGE3DPROC, glTextureSubImage3D) GLFUNC(PFNGLTEXTURESTORAGE1DPROC, glTextureStorage1D) GLFUNC(PFNGLTEXTURESTORAGE2DPROC, glTextureStorage2D) GLFUNC(PFNGLTEXTURESTORAGE3DPROC, glTextureStorage3D) // instancing / draw GLFUNC(PFNGLDRAWELEMENTSBASEVERTEXPROC, glDrawElementsBaseVertex) GLFUNC(PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC, glDrawElementsInstancedBaseVertexBaseInstance) GLFUNC(PFNGLDRAWARRAYSINSTANCEDPROC, glDrawArraysInstanced) // vertex array GLFUNC(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays) GLFUNC(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray) GLFUNC(PFNGLDELETEVERTEXARRAYSPROC, glDeleteVertexArrays) GLFUNC(PFNGLBINDVERTEXBUFFERPROC, glBindVertexBuffer) GLFUNC(PFNGLVERTEXATTRIBFORMATPROC, glVertexAttribFormat) GLFUNC(PFNGLVERTEXATTRIBIFORMATPROC, glVertexAttribIFormat) GLFUNC(PFNGLVERTEXATTRIBBINDINGPROC, glVertexAttribBinding) GLFUNC(PFNGLVERTEXBINDINGDIVISORPROC, glVertexBindingDivisor) GLFUNC(PFNGLVERTEXATTRIBDIVISORPROC, glVertexAttribDivisor) // vertex array DSA GLFUNC(PFNGLCREATEVERTEXARRAYSPROC, glCreateVertexArrays) GLFUNC(PFNGLDISABLEVERTEXARRAYATTRIBPROC, glDisableVertexArrayAttrib) GLFUNC(PFNGLENABLEVERTEXARRAYATTRIBPROC, glEnableVertexArrayAttrib) GLFUNC(PFNGLVERTEXARRAYELEMENTBUFFERPROC, glVertexArrayElementBuffer) GLFUNC(PFNGLVERTEXARRAYVERTEXBUFFERPROC, glVertexArrayVertexBuffer) GLFUNC(PFNGLVERTEXARRAYATTRIBBINDINGPROC, glVertexArrayAttribBinding) GLFUNC(PFNGLVERTEXARRAYATTRIBIFORMATPROC, glVertexArrayAttribIFormat) GLFUNC(PFNGLVERTEXARRAYBINDINGDIVISORPROC, glVertexArrayBindingDivisor) // sampler GLFUNC(PFNGLGENSAMPLERSPROC, glGenSamplers) GLFUNC(PFNGLBINDSAMPLERPROC, glBindSampler) GLFUNC(PFNGLSAMPLERPARAMETERIPROC, glSamplerParameteri) GLFUNC(PFNGLSAMPLERPARAMETERFPROC, glSamplerParameterf) GLFUNC(PFNGLSAMPLERPARAMETERIVPROC, glSamplerParameteriv) GLFUNC(PFNGLSAMPLERPARAMETERFVPROC, glSamplerParameterfv) // buffer object GLFUNC(PFNGLGETUNIFORMBLOCKINDEXPROC, glGetUniformBlockIndex) GLFUNC(PFNGLUNIFORMBLOCKBINDINGPROC, glUniformBlockBinding) GLFUNC(PFNGLBINDBUFFERBASEPROC, glBindBufferBase) GLFUNC(PFNGLBINDBUFFERRANGEPROC, glBindBufferRange) GLFUNC(PFNGLGETBUFFERSUBDATAPROC, glGetBufferSubData) // uniform storage buffer object GLFUNC(PFNGLGETPROGRAMRESOURCEINDEXPROC, glGetProgramResourceIndex) GLFUNC(PFNGLSHADERSTORAGEBLOCKBINDINGPROC, glShaderStorageBlockBinding) // stencil GLFUNC(PFNGLSTENCILOPSEPARATEPROC, glStencilOpSeparate) GLFUNC(PFNGLSTENCILFUNCSEPARATEPROC, glStencilFuncSeparate) GLFUNC(PFNGLSTENCILMASKSEPARATEPROC, glStencilMaskSeparate) // buffer GLFUNC(PFNGLCREATEBUFFERSPROC, glCreateBuffers) GLFUNC(PFNGLBUFFERSTORAGEPROC, glBufferStorage) GLFUNC(PFNGLNAMEDBUFFERSTORAGEPROC, glNamedBufferStorage) GLFUNC(PFNGLMAPNAMEDBUFFERPROC, glMapNamedBuffer) GLFUNC(PFNGLMAPNAMEDBUFFERRANGEPROC, glMapNamedBufferRange) GLFUNC(PFNGLMAPBUFFERRANGEPROC, glMapBufferRange) GLFUNC(PFNGLFLUSHMAPPEDBUFFERRANGEPROC, glFlushMappedBufferRange) GLFUNC(PFNGLMEMORYBARRIERPROC, glMemoryBarrier) GLFUNC(PFNGLCOPYBUFFERSUBDATAPROC, glCopyBufferSubData) GLFUNC(PFNGLCOPYNAMEDBUFFERSUBDATAPROC, glCopyNamedBufferSubData) GLFUNC(PFNGLNAMEDBUFFERSUBDATAPROC, glNamedBufferSubData) GLFUNC(PFNGLGETNAMEDBUFFERSUBDATAPROC, glGetNamedBufferSubData); // transform feedback GLFUNC(PFNGLBEGINTRANSFORMFEEDBACKPROC, glBeginTransformFeedback) GLFUNC(PFNGLENDTRANSFORMFEEDBACKPROC, glEndTransformFeedback) GLFUNC(PFNGLTRANSFORMFEEDBACKVARYINGSPROC, glTransformFeedbackVaryings) // sync / fence GLFUNC(PFNGLFENCESYNCPROC, glFenceSync) GLFUNC(PFNGLCLIENTWAITSYNCPROC, glClientWaitSync) GLFUNC(PFNGLDELETESYNCPROC, glDeleteSync) // debugging GLFUNC(PFNGLOBJECTLABELPROC, glObjectLabel) GLFUNC(PFNGLDEBUGMESSAGECALLBACKPROC, glDebugMessageCallback) GLFUNC(PFNGLDEBUGMESSAGECONTROLPROC, glDebugMessageControl) // wgl #if BOOST_OS_WINDOWS GLFUNC(PFNWGLSWAPINTERVALEXTPROC, wglSwapIntervalEXT) #endif // x #if BOOST_OS_LINUX || BOOST_OS_BSD EGLFUNC(PFNEGLSWAPINTERVALPROC, eglSwapInterval) EGLFUNC(PFNEGLGETCURRENTDISPLAYPROC, eglGetCurrentDisplay) #endif ================================================ FILE: src/Common/GLInclude/glext.h ================================================ #ifndef __gl_glext_custom_h_ #define __gl_glext_custom_h_ 1 #ifdef __cplusplus extern "C" { #endif /* ** Copyright 2013-2020 The Khronos Group Inc. ** SPDX-License-Identifier: MIT ** ** This header is generated from the Khronos OpenGL / OpenGL ES XML ** API Registry. The current version of the Registry, generator scripts ** used to make the header, and the header can be found at ** https://github.com/KhronosGroup/OpenGL-Registry */ #if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 #endif #include #endif #ifndef APIENTRY #define APIENTRY #endif #ifndef APIENTRYP #define APIENTRYP APIENTRY * #endif #ifndef GLAPI #define GLAPI extern #endif #define GL_GLEXT_VERSION 20220309 #include "khrplatform.h" /* Generated C header for: * API: gl * Profile: compatibility * Versions considered: .* * Versions emitted: .* * Default extensions included: gl * Additional extensions included: _nomatch_^ * Extensions removed: _nomatch_^ */ #ifndef GL_VERSION_1_0 #define GL_VERSION_1_0 1 typedef void GLvoid; typedef unsigned int GLenum; typedef khronos_float_t GLfloat; typedef int GLint; typedef int GLsizei; typedef unsigned int GLbitfield; typedef double GLdouble; typedef unsigned int GLuint; typedef unsigned char GLboolean; typedef khronos_uint8_t GLubyte; typedef khronos_int8_t GLbyte; typedef khronos_int16_t GLshort; typedef khronos_uint16_t GLushort; #define GL_DEPTH_BUFFER_BIT 0x00000100 #define GL_STENCIL_BUFFER_BIT 0x00000400 #define GL_COLOR_BUFFER_BIT 0x00004000 #define GL_FALSE 0 #define GL_TRUE 1 #define GL_POINTS 0x0000 #define GL_LINES 0x0001 #define GL_LINE_LOOP 0x0002 #define GL_LINE_STRIP 0x0003 #define GL_TRIANGLES 0x0004 #define GL_TRIANGLE_STRIP 0x0005 #define GL_TRIANGLE_FAN 0x0006 #define GL_QUADS 0x0007 #define GL_NEVER 0x0200 #define GL_LESS 0x0201 #define GL_EQUAL 0x0202 #define GL_LEQUAL 0x0203 #define GL_GREATER 0x0204 #define GL_NOTEQUAL 0x0205 #define GL_GEQUAL 0x0206 #define GL_ALWAYS 0x0207 #define GL_ZERO 0 #define GL_ONE 1 #define GL_SRC_COLOR 0x0300 #define GL_ONE_MINUS_SRC_COLOR 0x0301 #define GL_SRC_ALPHA 0x0302 #define GL_ONE_MINUS_SRC_ALPHA 0x0303 #define GL_DST_ALPHA 0x0304 #define GL_ONE_MINUS_DST_ALPHA 0x0305 #define GL_DST_COLOR 0x0306 #define GL_ONE_MINUS_DST_COLOR 0x0307 #define GL_SRC_ALPHA_SATURATE 0x0308 #define GL_NONE 0 #define GL_FRONT_LEFT 0x0400 #define GL_FRONT_RIGHT 0x0401 #define GL_BACK_LEFT 0x0402 #define GL_BACK_RIGHT 0x0403 #define GL_FRONT 0x0404 #define GL_BACK 0x0405 #define GL_LEFT 0x0406 #define GL_RIGHT 0x0407 #define GL_FRONT_AND_BACK 0x0408 #define GL_NO_ERROR 0 #define GL_INVALID_ENUM 0x0500 #define GL_INVALID_VALUE 0x0501 #define GL_INVALID_OPERATION 0x0502 #define GL_OUT_OF_MEMORY 0x0505 #define GL_CW 0x0900 #define GL_CCW 0x0901 #define GL_POINT_SIZE 0x0B11 #define GL_POINT_SIZE_RANGE 0x0B12 #define GL_POINT_SIZE_GRANULARITY 0x0B13 #define GL_LINE_SMOOTH 0x0B20 #define GL_LINE_WIDTH 0x0B21 #define GL_LINE_WIDTH_RANGE 0x0B22 #define GL_LINE_WIDTH_GRANULARITY 0x0B23 #define GL_POLYGON_MODE 0x0B40 #define GL_POLYGON_SMOOTH 0x0B41 #define GL_CULL_FACE 0x0B44 #define GL_CULL_FACE_MODE 0x0B45 #define GL_FRONT_FACE 0x0B46 #define GL_DEPTH_RANGE 0x0B70 #define GL_DEPTH_TEST 0x0B71 #define GL_DEPTH_WRITEMASK 0x0B72 #define GL_DEPTH_CLEAR_VALUE 0x0B73 #define GL_DEPTH_FUNC 0x0B74 #define GL_STENCIL_TEST 0x0B90 #define GL_STENCIL_CLEAR_VALUE 0x0B91 #define GL_STENCIL_FUNC 0x0B92 #define GL_STENCIL_VALUE_MASK 0x0B93 #define GL_STENCIL_FAIL 0x0B94 #define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 #define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 #define GL_STENCIL_REF 0x0B97 #define GL_STENCIL_WRITEMASK 0x0B98 #define GL_VIEWPORT 0x0BA2 #define GL_DITHER 0x0BD0 #define GL_BLEND_DST 0x0BE0 #define GL_BLEND_SRC 0x0BE1 #define GL_BLEND 0x0BE2 #define GL_LOGIC_OP_MODE 0x0BF0 #define GL_DRAW_BUFFER 0x0C01 #define GL_READ_BUFFER 0x0C02 #define GL_SCISSOR_BOX 0x0C10 #define GL_SCISSOR_TEST 0x0C11 #define GL_COLOR_CLEAR_VALUE 0x0C22 #define GL_COLOR_WRITEMASK 0x0C23 #define GL_DOUBLEBUFFER 0x0C32 #define GL_STEREO 0x0C33 #define GL_LINE_SMOOTH_HINT 0x0C52 #define GL_POLYGON_SMOOTH_HINT 0x0C53 #define GL_UNPACK_SWAP_BYTES 0x0CF0 #define GL_UNPACK_LSB_FIRST 0x0CF1 #define GL_UNPACK_ROW_LENGTH 0x0CF2 #define GL_UNPACK_SKIP_ROWS 0x0CF3 #define GL_UNPACK_SKIP_PIXELS 0x0CF4 #define GL_UNPACK_ALIGNMENT 0x0CF5 #define GL_PACK_SWAP_BYTES 0x0D00 #define GL_PACK_LSB_FIRST 0x0D01 #define GL_PACK_ROW_LENGTH 0x0D02 #define GL_PACK_SKIP_ROWS 0x0D03 #define GL_PACK_SKIP_PIXELS 0x0D04 #define GL_PACK_ALIGNMENT 0x0D05 #define GL_MAX_TEXTURE_SIZE 0x0D33 #define GL_MAX_VIEWPORT_DIMS 0x0D3A #define GL_SUBPIXEL_BITS 0x0D50 #define GL_TEXTURE_1D 0x0DE0 #define GL_TEXTURE_2D 0x0DE1 #define GL_TEXTURE_WIDTH 0x1000 #define GL_TEXTURE_HEIGHT 0x1001 #define GL_TEXTURE_BORDER_COLOR 0x1004 #define GL_DONT_CARE 0x1100 #define GL_FASTEST 0x1101 #define GL_NICEST 0x1102 #define GL_BYTE 0x1400 #define GL_UNSIGNED_BYTE 0x1401 #define GL_SHORT 0x1402 #define GL_UNSIGNED_SHORT 0x1403 #define GL_INT 0x1404 #define GL_UNSIGNED_INT 0x1405 #define GL_FLOAT 0x1406 #define GL_STACK_OVERFLOW 0x0503 #define GL_STACK_UNDERFLOW 0x0504 #define GL_CLEAR 0x1500 #define GL_AND 0x1501 #define GL_AND_REVERSE 0x1502 #define GL_COPY 0x1503 #define GL_AND_INVERTED 0x1504 #define GL_NOOP 0x1505 #define GL_XOR 0x1506 #define GL_OR 0x1507 #define GL_NOR 0x1508 #define GL_EQUIV 0x1509 #define GL_INVERT 0x150A #define GL_OR_REVERSE 0x150B #define GL_COPY_INVERTED 0x150C #define GL_OR_INVERTED 0x150D #define GL_NAND 0x150E #define GL_SET 0x150F #define GL_TEXTURE 0x1702 #define GL_COLOR 0x1800 #define GL_DEPTH 0x1801 #define GL_STENCIL 0x1802 #define GL_STENCIL_INDEX 0x1901 #define GL_DEPTH_COMPONENT 0x1902 #define GL_RED 0x1903 #define GL_GREEN 0x1904 #define GL_BLUE 0x1905 #define GL_ALPHA 0x1906 #define GL_RGB 0x1907 #define GL_RGBA 0x1908 #define GL_POINT 0x1B00 #define GL_LINE 0x1B01 #define GL_FILL 0x1B02 #define GL_KEEP 0x1E00 #define GL_REPLACE 0x1E01 #define GL_INCR 0x1E02 #define GL_DECR 0x1E03 #define GL_VENDOR 0x1F00 #define GL_RENDERER 0x1F01 #define GL_VERSION 0x1F02 #define GL_EXTENSIONS 0x1F03 #define GL_NEAREST 0x2600 #define GL_LINEAR 0x2601 #define GL_NEAREST_MIPMAP_NEAREST 0x2700 #define GL_LINEAR_MIPMAP_NEAREST 0x2701 #define GL_NEAREST_MIPMAP_LINEAR 0x2702 #define GL_LINEAR_MIPMAP_LINEAR 0x2703 #define GL_TEXTURE_MAG_FILTER 0x2800 #define GL_TEXTURE_MIN_FILTER 0x2801 #define GL_TEXTURE_WRAP_S 0x2802 #define GL_TEXTURE_WRAP_T 0x2803 #define GL_REPEAT 0x2901 #define GL_CURRENT_BIT 0x00000001 #define GL_POINT_BIT 0x00000002 #define GL_LINE_BIT 0x00000004 #define GL_POLYGON_BIT 0x00000008 #define GL_POLYGON_STIPPLE_BIT 0x00000010 #define GL_PIXEL_MODE_BIT 0x00000020 #define GL_LIGHTING_BIT 0x00000040 #define GL_FOG_BIT 0x00000080 #define GL_ACCUM_BUFFER_BIT 0x00000200 #define GL_VIEWPORT_BIT 0x00000800 #define GL_TRANSFORM_BIT 0x00001000 #define GL_ENABLE_BIT 0x00002000 #define GL_HINT_BIT 0x00008000 #define GL_EVAL_BIT 0x00010000 #define GL_LIST_BIT 0x00020000 #define GL_TEXTURE_BIT 0x00040000 #define GL_SCISSOR_BIT 0x00080000 #define GL_ALL_ATTRIB_BITS 0xFFFFFFFF #define GL_QUAD_STRIP 0x0008 #define GL_POLYGON 0x0009 #define GL_ACCUM 0x0100 #define GL_LOAD 0x0101 #define GL_RETURN 0x0102 #define GL_MULT 0x0103 #define GL_ADD 0x0104 #define GL_AUX0 0x0409 #define GL_AUX1 0x040A #define GL_AUX2 0x040B #define GL_AUX3 0x040C #define GL_2D 0x0600 #define GL_3D 0x0601 #define GL_3D_COLOR 0x0602 #define GL_3D_COLOR_TEXTURE 0x0603 #define GL_4D_COLOR_TEXTURE 0x0604 #define GL_PASS_THROUGH_TOKEN 0x0700 #define GL_POINT_TOKEN 0x0701 #define GL_LINE_TOKEN 0x0702 #define GL_POLYGON_TOKEN 0x0703 #define GL_BITMAP_TOKEN 0x0704 #define GL_DRAW_PIXEL_TOKEN 0x0705 #define GL_COPY_PIXEL_TOKEN 0x0706 #define GL_LINE_RESET_TOKEN 0x0707 #define GL_EXP 0x0800 #define GL_EXP2 0x0801 #define GL_COEFF 0x0A00 #define GL_ORDER 0x0A01 #define GL_DOMAIN 0x0A02 #define GL_PIXEL_MAP_I_TO_I 0x0C70 #define GL_PIXEL_MAP_S_TO_S 0x0C71 #define GL_PIXEL_MAP_I_TO_R 0x0C72 #define GL_PIXEL_MAP_I_TO_G 0x0C73 #define GL_PIXEL_MAP_I_TO_B 0x0C74 #define GL_PIXEL_MAP_I_TO_A 0x0C75 #define GL_PIXEL_MAP_R_TO_R 0x0C76 #define GL_PIXEL_MAP_G_TO_G 0x0C77 #define GL_PIXEL_MAP_B_TO_B 0x0C78 #define GL_PIXEL_MAP_A_TO_A 0x0C79 #define GL_CURRENT_COLOR 0x0B00 #define GL_CURRENT_INDEX 0x0B01 #define GL_CURRENT_NORMAL 0x0B02 #define GL_CURRENT_TEXTURE_COORDS 0x0B03 #define GL_CURRENT_RASTER_COLOR 0x0B04 #define GL_CURRENT_RASTER_INDEX 0x0B05 #define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 #define GL_CURRENT_RASTER_POSITION 0x0B07 #define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 #define GL_CURRENT_RASTER_DISTANCE 0x0B09 #define GL_POINT_SMOOTH 0x0B10 #define GL_LINE_STIPPLE 0x0B24 #define GL_LINE_STIPPLE_PATTERN 0x0B25 #define GL_LINE_STIPPLE_REPEAT 0x0B26 #define GL_LIST_MODE 0x0B30 #define GL_MAX_LIST_NESTING 0x0B31 #define GL_LIST_BASE 0x0B32 #define GL_LIST_INDEX 0x0B33 #define GL_POLYGON_STIPPLE 0x0B42 #define GL_EDGE_FLAG 0x0B43 #define GL_LIGHTING 0x0B50 #define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 #define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 #define GL_LIGHT_MODEL_AMBIENT 0x0B53 #define GL_SHADE_MODEL 0x0B54 #define GL_COLOR_MATERIAL_FACE 0x0B55 #define GL_COLOR_MATERIAL_PARAMETER 0x0B56 #define GL_COLOR_MATERIAL 0x0B57 #define GL_FOG 0x0B60 #define GL_FOG_INDEX 0x0B61 #define GL_FOG_DENSITY 0x0B62 #define GL_FOG_START 0x0B63 #define GL_FOG_END 0x0B64 #define GL_FOG_MODE 0x0B65 #define GL_FOG_COLOR 0x0B66 #define GL_ACCUM_CLEAR_VALUE 0x0B80 #define GL_MATRIX_MODE 0x0BA0 #define GL_NORMALIZE 0x0BA1 #define GL_MODELVIEW_STACK_DEPTH 0x0BA3 #define GL_PROJECTION_STACK_DEPTH 0x0BA4 #define GL_TEXTURE_STACK_DEPTH 0x0BA5 #define GL_MODELVIEW_MATRIX 0x0BA6 #define GL_PROJECTION_MATRIX 0x0BA7 #define GL_TEXTURE_MATRIX 0x0BA8 #define GL_ATTRIB_STACK_DEPTH 0x0BB0 #define GL_ALPHA_TEST 0x0BC0 #define GL_ALPHA_TEST_FUNC 0x0BC1 #define GL_ALPHA_TEST_REF 0x0BC2 #define GL_LOGIC_OP 0x0BF1 #define GL_AUX_BUFFERS 0x0C00 #define GL_INDEX_CLEAR_VALUE 0x0C20 #define GL_INDEX_WRITEMASK 0x0C21 #define GL_INDEX_MODE 0x0C30 #define GL_RGBA_MODE 0x0C31 #define GL_RENDER_MODE 0x0C40 #define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 #define GL_POINT_SMOOTH_HINT 0x0C51 #define GL_FOG_HINT 0x0C54 #define GL_TEXTURE_GEN_S 0x0C60 #define GL_TEXTURE_GEN_T 0x0C61 #define GL_TEXTURE_GEN_R 0x0C62 #define GL_TEXTURE_GEN_Q 0x0C63 #define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 #define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 #define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 #define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 #define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 #define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 #define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 #define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 #define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 #define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 #define GL_MAP_COLOR 0x0D10 #define GL_MAP_STENCIL 0x0D11 #define GL_INDEX_SHIFT 0x0D12 #define GL_INDEX_OFFSET 0x0D13 #define GL_RED_SCALE 0x0D14 #define GL_RED_BIAS 0x0D15 #define GL_ZOOM_X 0x0D16 #define GL_ZOOM_Y 0x0D17 #define GL_GREEN_SCALE 0x0D18 #define GL_GREEN_BIAS 0x0D19 #define GL_BLUE_SCALE 0x0D1A #define GL_BLUE_BIAS 0x0D1B #define GL_ALPHA_SCALE 0x0D1C #define GL_ALPHA_BIAS 0x0D1D #define GL_DEPTH_SCALE 0x0D1E #define GL_DEPTH_BIAS 0x0D1F #define GL_MAX_EVAL_ORDER 0x0D30 #define GL_MAX_LIGHTS 0x0D31 #define GL_MAX_CLIP_PLANES 0x0D32 #define GL_MAX_PIXEL_MAP_TABLE 0x0D34 #define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 #define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 #define GL_MAX_NAME_STACK_DEPTH 0x0D37 #define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 #define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 #define GL_INDEX_BITS 0x0D51 #define GL_RED_BITS 0x0D52 #define GL_GREEN_BITS 0x0D53 #define GL_BLUE_BITS 0x0D54 #define GL_ALPHA_BITS 0x0D55 #define GL_DEPTH_BITS 0x0D56 #define GL_STENCIL_BITS 0x0D57 #define GL_ACCUM_RED_BITS 0x0D58 #define GL_ACCUM_GREEN_BITS 0x0D59 #define GL_ACCUM_BLUE_BITS 0x0D5A #define GL_ACCUM_ALPHA_BITS 0x0D5B #define GL_NAME_STACK_DEPTH 0x0D70 #define GL_AUTO_NORMAL 0x0D80 #define GL_MAP1_COLOR_4 0x0D90 #define GL_MAP1_INDEX 0x0D91 #define GL_MAP1_NORMAL 0x0D92 #define GL_MAP1_TEXTURE_COORD_1 0x0D93 #define GL_MAP1_TEXTURE_COORD_2 0x0D94 #define GL_MAP1_TEXTURE_COORD_3 0x0D95 #define GL_MAP1_TEXTURE_COORD_4 0x0D96 #define GL_MAP1_VERTEX_3 0x0D97 #define GL_MAP1_VERTEX_4 0x0D98 #define GL_MAP2_COLOR_4 0x0DB0 #define GL_MAP2_INDEX 0x0DB1 #define GL_MAP2_NORMAL 0x0DB2 #define GL_MAP2_TEXTURE_COORD_1 0x0DB3 #define GL_MAP2_TEXTURE_COORD_2 0x0DB4 #define GL_MAP2_TEXTURE_COORD_3 0x0DB5 #define GL_MAP2_TEXTURE_COORD_4 0x0DB6 #define GL_MAP2_VERTEX_3 0x0DB7 #define GL_MAP2_VERTEX_4 0x0DB8 #define GL_MAP1_GRID_DOMAIN 0x0DD0 #define GL_MAP1_GRID_SEGMENTS 0x0DD1 #define GL_MAP2_GRID_DOMAIN 0x0DD2 #define GL_MAP2_GRID_SEGMENTS 0x0DD3 #define GL_TEXTURE_COMPONENTS 0x1003 #define GL_TEXTURE_BORDER 0x1005 #define GL_AMBIENT 0x1200 #define GL_DIFFUSE 0x1201 #define GL_SPECULAR 0x1202 #define GL_POSITION 0x1203 #define GL_SPOT_DIRECTION 0x1204 #define GL_SPOT_EXPONENT 0x1205 #define GL_SPOT_CUTOFF 0x1206 #define GL_CONSTANT_ATTENUATION 0x1207 #define GL_LINEAR_ATTENUATION 0x1208 #define GL_QUADRATIC_ATTENUATION 0x1209 #define GL_COMPILE 0x1300 #define GL_COMPILE_AND_EXECUTE 0x1301 #define GL_2_BYTES 0x1407 #define GL_3_BYTES 0x1408 #define GL_4_BYTES 0x1409 #define GL_EMISSION 0x1600 #define GL_SHININESS 0x1601 #define GL_AMBIENT_AND_DIFFUSE 0x1602 #define GL_COLOR_INDEXES 0x1603 #define GL_MODELVIEW 0x1700 #define GL_PROJECTION 0x1701 #define GL_COLOR_INDEX 0x1900 #define GL_LUMINANCE 0x1909 #define GL_LUMINANCE_ALPHA 0x190A #define GL_BITMAP 0x1A00 #define GL_RENDER 0x1C00 #define GL_FEEDBACK 0x1C01 #define GL_SELECT 0x1C02 #define GL_FLAT 0x1D00 #define GL_SMOOTH 0x1D01 #define GL_S 0x2000 #define GL_T 0x2001 #define GL_R 0x2002 #define GL_Q 0x2003 #define GL_MODULATE 0x2100 #define GL_DECAL 0x2101 #define GL_TEXTURE_ENV_MODE 0x2200 #define GL_TEXTURE_ENV_COLOR 0x2201 #define GL_TEXTURE_ENV 0x2300 #define GL_EYE_LINEAR 0x2400 #define GL_OBJECT_LINEAR 0x2401 #define GL_SPHERE_MAP 0x2402 #define GL_TEXTURE_GEN_MODE 0x2500 #define GL_OBJECT_PLANE 0x2501 #define GL_EYE_PLANE 0x2502 #define GL_CLAMP 0x2900 #define GL_CLIP_PLANE0 0x3000 #define GL_CLIP_PLANE1 0x3001 #define GL_CLIP_PLANE2 0x3002 #define GL_CLIP_PLANE3 0x3003 #define GL_CLIP_PLANE4 0x3004 #define GL_CLIP_PLANE5 0x3005 #define GL_LIGHT0 0x4000 #define GL_LIGHT1 0x4001 #define GL_LIGHT2 0x4002 #define GL_LIGHT3 0x4003 #define GL_LIGHT4 0x4004 #define GL_LIGHT5 0x4005 #define GL_LIGHT6 0x4006 #define GL_LIGHT7 0x4007 typedef void (APIENTRYP PFNGLCULLFACEPROC) (GLenum mode); typedef void (APIENTRYP PFNGLFRONTFACEPROC) (GLenum mode); typedef void (APIENTRYP PFNGLHINTPROC) (GLenum target, GLenum mode); typedef void (APIENTRYP PFNGLLINEWIDTHPROC) (GLfloat width); typedef void (APIENTRYP PFNGLPOINTSIZEPROC) (GLfloat size); typedef void (APIENTRYP PFNGLPOLYGONMODEPROC) (GLenum face, GLenum mode); typedef void (APIENTRYP PFNGLSCISSORPROC) (GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLTEXPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLTEXPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLTEXPARAMETERIPROC) (GLenum target, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLTEXPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLTEXIMAGE1DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXIMAGE2DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLDRAWBUFFERPROC) (GLenum buf); typedef void (APIENTRYP PFNGLCLEARPROC) (GLbitfield mask); typedef void (APIENTRYP PFNGLCLEARCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); typedef void (APIENTRYP PFNGLCLEARSTENCILPROC) (GLint s); typedef void (APIENTRYP PFNGLCLEARDEPTHPROC) (GLdouble depth); typedef void (APIENTRYP PFNGLSTENCILMASKPROC) (GLuint mask); typedef void (APIENTRYP PFNGLCOLORMASKPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); typedef void (APIENTRYP PFNGLDEPTHMASKPROC) (GLboolean flag); typedef void (APIENTRYP PFNGLDISABLEPROC) (GLenum cap); typedef void (APIENTRYP PFNGLENABLEPROC) (GLenum cap); typedef void (APIENTRYP PFNGLFINISHPROC) (void); typedef void (APIENTRYP PFNGLFLUSHPROC) (void); typedef void (APIENTRYP PFNGLBLENDFUNCPROC) (GLenum sfactor, GLenum dfactor); typedef void (APIENTRYP PFNGLLOGICOPPROC) (GLenum opcode); typedef void (APIENTRYP PFNGLSTENCILFUNCPROC) (GLenum func, GLint ref, GLuint mask); typedef void (APIENTRYP PFNGLSTENCILOPPROC) (GLenum fail, GLenum zfail, GLenum zpass); typedef void (APIENTRYP PFNGLDEPTHFUNCPROC) (GLenum func); typedef void (APIENTRYP PFNGLPIXELSTOREFPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLPIXELSTOREIPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLREADBUFFERPROC) (GLenum src); typedef void (APIENTRYP PFNGLREADPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); typedef void (APIENTRYP PFNGLGETBOOLEANVPROC) (GLenum pname, GLboolean *data); typedef void (APIENTRYP PFNGLGETDOUBLEVPROC) (GLenum pname, GLdouble *data); typedef GLenum (APIENTRYP PFNGLGETERRORPROC) (void); typedef void (APIENTRYP PFNGLGETFLOATVPROC) (GLenum pname, GLfloat *data); typedef void (APIENTRYP PFNGLGETINTEGERVPROC) (GLenum pname, GLint *data); typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGPROC) (GLenum name); typedef void (APIENTRYP PFNGLGETTEXIMAGEPROC) (GLenum target, GLint level, GLenum format, GLenum type, void *pixels); typedef void (APIENTRYP PFNGLGETTEXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETTEXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERFVPROC) (GLenum target, GLint level, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERIVPROC) (GLenum target, GLint level, GLenum pname, GLint *params); typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC) (GLenum cap); typedef void (APIENTRYP PFNGLDEPTHRANGEPROC) (GLdouble n, GLdouble f); typedef void (APIENTRYP PFNGLVIEWPORTPROC) (GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLNEWLISTPROC) (GLuint list, GLenum mode); typedef void (APIENTRYP PFNGLENDLISTPROC) (void); typedef void (APIENTRYP PFNGLCALLLISTPROC) (GLuint list); typedef void (APIENTRYP PFNGLCALLLISTSPROC) (GLsizei n, GLenum type, const void *lists); typedef void (APIENTRYP PFNGLDELETELISTSPROC) (GLuint list, GLsizei range); typedef GLuint (APIENTRYP PFNGLGENLISTSPROC) (GLsizei range); typedef void (APIENTRYP PFNGLLISTBASEPROC) (GLuint base); typedef void (APIENTRYP PFNGLBEGINPROC) (GLenum mode); typedef void (APIENTRYP PFNGLBITMAPPROC) (GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); typedef void (APIENTRYP PFNGLCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); typedef void (APIENTRYP PFNGLCOLOR3BVPROC) (const GLbyte *v); typedef void (APIENTRYP PFNGLCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); typedef void (APIENTRYP PFNGLCOLOR3DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); typedef void (APIENTRYP PFNGLCOLOR3FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLCOLOR3IPROC) (GLint red, GLint green, GLint blue); typedef void (APIENTRYP PFNGLCOLOR3IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); typedef void (APIENTRYP PFNGLCOLOR3SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); typedef void (APIENTRYP PFNGLCOLOR3UBVPROC) (const GLubyte *v); typedef void (APIENTRYP PFNGLCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); typedef void (APIENTRYP PFNGLCOLOR3UIVPROC) (const GLuint *v); typedef void (APIENTRYP PFNGLCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); typedef void (APIENTRYP PFNGLCOLOR3USVPROC) (const GLushort *v); typedef void (APIENTRYP PFNGLCOLOR4BPROC) (GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); typedef void (APIENTRYP PFNGLCOLOR4BVPROC) (const GLbyte *v); typedef void (APIENTRYP PFNGLCOLOR4DPROC) (GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); typedef void (APIENTRYP PFNGLCOLOR4DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLCOLOR4FPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); typedef void (APIENTRYP PFNGLCOLOR4FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLCOLOR4IPROC) (GLint red, GLint green, GLint blue, GLint alpha); typedef void (APIENTRYP PFNGLCOLOR4IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLCOLOR4SPROC) (GLshort red, GLshort green, GLshort blue, GLshort alpha); typedef void (APIENTRYP PFNGLCOLOR4SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLCOLOR4UBPROC) (GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); typedef void (APIENTRYP PFNGLCOLOR4UBVPROC) (const GLubyte *v); typedef void (APIENTRYP PFNGLCOLOR4UIPROC) (GLuint red, GLuint green, GLuint blue, GLuint alpha); typedef void (APIENTRYP PFNGLCOLOR4UIVPROC) (const GLuint *v); typedef void (APIENTRYP PFNGLCOLOR4USPROC) (GLushort red, GLushort green, GLushort blue, GLushort alpha); typedef void (APIENTRYP PFNGLCOLOR4USVPROC) (const GLushort *v); typedef void (APIENTRYP PFNGLEDGEFLAGPROC) (GLboolean flag); typedef void (APIENTRYP PFNGLEDGEFLAGVPROC) (const GLboolean *flag); typedef void (APIENTRYP PFNGLENDPROC) (void); typedef void (APIENTRYP PFNGLINDEXDPROC) (GLdouble c); typedef void (APIENTRYP PFNGLINDEXDVPROC) (const GLdouble *c); typedef void (APIENTRYP PFNGLINDEXFPROC) (GLfloat c); typedef void (APIENTRYP PFNGLINDEXFVPROC) (const GLfloat *c); typedef void (APIENTRYP PFNGLINDEXIPROC) (GLint c); typedef void (APIENTRYP PFNGLINDEXIVPROC) (const GLint *c); typedef void (APIENTRYP PFNGLINDEXSPROC) (GLshort c); typedef void (APIENTRYP PFNGLINDEXSVPROC) (const GLshort *c); typedef void (APIENTRYP PFNGLNORMAL3BPROC) (GLbyte nx, GLbyte ny, GLbyte nz); typedef void (APIENTRYP PFNGLNORMAL3BVPROC) (const GLbyte *v); typedef void (APIENTRYP PFNGLNORMAL3DPROC) (GLdouble nx, GLdouble ny, GLdouble nz); typedef void (APIENTRYP PFNGLNORMAL3DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLNORMAL3FPROC) (GLfloat nx, GLfloat ny, GLfloat nz); typedef void (APIENTRYP PFNGLNORMAL3FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLNORMAL3IPROC) (GLint nx, GLint ny, GLint nz); typedef void (APIENTRYP PFNGLNORMAL3IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLNORMAL3SPROC) (GLshort nx, GLshort ny, GLshort nz); typedef void (APIENTRYP PFNGLNORMAL3SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLRASTERPOS2DPROC) (GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLRASTERPOS2DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLRASTERPOS2FPROC) (GLfloat x, GLfloat y); typedef void (APIENTRYP PFNGLRASTERPOS2FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLRASTERPOS2IPROC) (GLint x, GLint y); typedef void (APIENTRYP PFNGLRASTERPOS2IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLRASTERPOS2SPROC) (GLshort x, GLshort y); typedef void (APIENTRYP PFNGLRASTERPOS2SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLRASTERPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLRASTERPOS3DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLRASTERPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLRASTERPOS3FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLRASTERPOS3IPROC) (GLint x, GLint y, GLint z); typedef void (APIENTRYP PFNGLRASTERPOS3IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLRASTERPOS3SPROC) (GLshort x, GLshort y, GLshort z); typedef void (APIENTRYP PFNGLRASTERPOS3SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLRASTERPOS4DPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLRASTERPOS4DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLRASTERPOS4FPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLRASTERPOS4FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLRASTERPOS4IPROC) (GLint x, GLint y, GLint z, GLint w); typedef void (APIENTRYP PFNGLRASTERPOS4IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLRASTERPOS4SPROC) (GLshort x, GLshort y, GLshort z, GLshort w); typedef void (APIENTRYP PFNGLRASTERPOS4SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLRECTDPROC) (GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); typedef void (APIENTRYP PFNGLRECTDVPROC) (const GLdouble *v1, const GLdouble *v2); typedef void (APIENTRYP PFNGLRECTFPROC) (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); typedef void (APIENTRYP PFNGLRECTFVPROC) (const GLfloat *v1, const GLfloat *v2); typedef void (APIENTRYP PFNGLRECTIPROC) (GLint x1, GLint y1, GLint x2, GLint y2); typedef void (APIENTRYP PFNGLRECTIVPROC) (const GLint *v1, const GLint *v2); typedef void (APIENTRYP PFNGLRECTSPROC) (GLshort x1, GLshort y1, GLshort x2, GLshort y2); typedef void (APIENTRYP PFNGLRECTSVPROC) (const GLshort *v1, const GLshort *v2); typedef void (APIENTRYP PFNGLTEXCOORD1DPROC) (GLdouble s); typedef void (APIENTRYP PFNGLTEXCOORD1DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLTEXCOORD1FPROC) (GLfloat s); typedef void (APIENTRYP PFNGLTEXCOORD1FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD1IPROC) (GLint s); typedef void (APIENTRYP PFNGLTEXCOORD1IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLTEXCOORD1SPROC) (GLshort s); typedef void (APIENTRYP PFNGLTEXCOORD1SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLTEXCOORD2DPROC) (GLdouble s, GLdouble t); typedef void (APIENTRYP PFNGLTEXCOORD2DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLTEXCOORD2FPROC) (GLfloat s, GLfloat t); typedef void (APIENTRYP PFNGLTEXCOORD2FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD2IPROC) (GLint s, GLint t); typedef void (APIENTRYP PFNGLTEXCOORD2IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLTEXCOORD2SPROC) (GLshort s, GLshort t); typedef void (APIENTRYP PFNGLTEXCOORD2SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLTEXCOORD3DPROC) (GLdouble s, GLdouble t, GLdouble r); typedef void (APIENTRYP PFNGLTEXCOORD3DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLTEXCOORD3FPROC) (GLfloat s, GLfloat t, GLfloat r); typedef void (APIENTRYP PFNGLTEXCOORD3FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD3IPROC) (GLint s, GLint t, GLint r); typedef void (APIENTRYP PFNGLTEXCOORD3IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLTEXCOORD3SPROC) (GLshort s, GLshort t, GLshort r); typedef void (APIENTRYP PFNGLTEXCOORD3SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLTEXCOORD4DPROC) (GLdouble s, GLdouble t, GLdouble r, GLdouble q); typedef void (APIENTRYP PFNGLTEXCOORD4DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLTEXCOORD4FPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat q); typedef void (APIENTRYP PFNGLTEXCOORD4FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD4IPROC) (GLint s, GLint t, GLint r, GLint q); typedef void (APIENTRYP PFNGLTEXCOORD4IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLTEXCOORD4SPROC) (GLshort s, GLshort t, GLshort r, GLshort q); typedef void (APIENTRYP PFNGLTEXCOORD4SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLVERTEX2DPROC) (GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLVERTEX2DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEX2FPROC) (GLfloat x, GLfloat y); typedef void (APIENTRYP PFNGLVERTEX2FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEX2IPROC) (GLint x, GLint y); typedef void (APIENTRYP PFNGLVERTEX2IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLVERTEX2SPROC) (GLshort x, GLshort y); typedef void (APIENTRYP PFNGLVERTEX2SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLVERTEX3DPROC) (GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLVERTEX3DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEX3FPROC) (GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLVERTEX3FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEX3IPROC) (GLint x, GLint y, GLint z); typedef void (APIENTRYP PFNGLVERTEX3IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLVERTEX3SPROC) (GLshort x, GLshort y, GLshort z); typedef void (APIENTRYP PFNGLVERTEX3SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLVERTEX4DPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLVERTEX4DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEX4FPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLVERTEX4FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEX4IPROC) (GLint x, GLint y, GLint z, GLint w); typedef void (APIENTRYP PFNGLVERTEX4IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLVERTEX4SPROC) (GLshort x, GLshort y, GLshort z, GLshort w); typedef void (APIENTRYP PFNGLVERTEX4SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLCLIPPLANEPROC) (GLenum plane, const GLdouble *equation); typedef void (APIENTRYP PFNGLCOLORMATERIALPROC) (GLenum face, GLenum mode); typedef void (APIENTRYP PFNGLFOGFPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLFOGFVPROC) (GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLFOGIPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLFOGIVPROC) (GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLLIGHTFPROC) (GLenum light, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLLIGHTFVPROC) (GLenum light, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLLIGHTIPROC) (GLenum light, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLLIGHTIVPROC) (GLenum light, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLLIGHTMODELFPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLLIGHTMODELFVPROC) (GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLLIGHTMODELIPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLLIGHTMODELIVPROC) (GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLLINESTIPPLEPROC) (GLint factor, GLushort pattern); typedef void (APIENTRYP PFNGLMATERIALFPROC) (GLenum face, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLMATERIALFVPROC) (GLenum face, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLMATERIALIPROC) (GLenum face, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLMATERIALIVPROC) (GLenum face, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLPOLYGONSTIPPLEPROC) (const GLubyte *mask); typedef void (APIENTRYP PFNGLSHADEMODELPROC) (GLenum mode); typedef void (APIENTRYP PFNGLTEXENVFPROC) (GLenum target, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLTEXENVFVPROC) (GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLTEXENVIPROC) (GLenum target, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLTEXENVIVPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLTEXGENDPROC) (GLenum coord, GLenum pname, GLdouble param); typedef void (APIENTRYP PFNGLTEXGENDVPROC) (GLenum coord, GLenum pname, const GLdouble *params); typedef void (APIENTRYP PFNGLTEXGENFPROC) (GLenum coord, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLTEXGENFVPROC) (GLenum coord, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLTEXGENIPROC) (GLenum coord, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLTEXGENIVPROC) (GLenum coord, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLFEEDBACKBUFFERPROC) (GLsizei size, GLenum type, GLfloat *buffer); typedef void (APIENTRYP PFNGLSELECTBUFFERPROC) (GLsizei size, GLuint *buffer); typedef GLint (APIENTRYP PFNGLRENDERMODEPROC) (GLenum mode); typedef void (APIENTRYP PFNGLINITNAMESPROC) (void); typedef void (APIENTRYP PFNGLLOADNAMEPROC) (GLuint name); typedef void (APIENTRYP PFNGLPASSTHROUGHPROC) (GLfloat token); typedef void (APIENTRYP PFNGLPOPNAMEPROC) (void); typedef void (APIENTRYP PFNGLPUSHNAMEPROC) (GLuint name); typedef void (APIENTRYP PFNGLCLEARACCUMPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); typedef void (APIENTRYP PFNGLCLEARINDEXPROC) (GLfloat c); typedef void (APIENTRYP PFNGLINDEXMASKPROC) (GLuint mask); typedef void (APIENTRYP PFNGLACCUMPROC) (GLenum op, GLfloat value); typedef void (APIENTRYP PFNGLPOPATTRIBPROC) (void); typedef void (APIENTRYP PFNGLPUSHATTRIBPROC) (GLbitfield mask); typedef void (APIENTRYP PFNGLMAP1DPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); typedef void (APIENTRYP PFNGLMAP1FPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); typedef void (APIENTRYP PFNGLMAP2DPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); typedef void (APIENTRYP PFNGLMAP2FPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); typedef void (APIENTRYP PFNGLMAPGRID1DPROC) (GLint un, GLdouble u1, GLdouble u2); typedef void (APIENTRYP PFNGLMAPGRID1FPROC) (GLint un, GLfloat u1, GLfloat u2); typedef void (APIENTRYP PFNGLMAPGRID2DPROC) (GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); typedef void (APIENTRYP PFNGLMAPGRID2FPROC) (GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); typedef void (APIENTRYP PFNGLEVALCOORD1DPROC) (GLdouble u); typedef void (APIENTRYP PFNGLEVALCOORD1DVPROC) (const GLdouble *u); typedef void (APIENTRYP PFNGLEVALCOORD1FPROC) (GLfloat u); typedef void (APIENTRYP PFNGLEVALCOORD1FVPROC) (const GLfloat *u); typedef void (APIENTRYP PFNGLEVALCOORD2DPROC) (GLdouble u, GLdouble v); typedef void (APIENTRYP PFNGLEVALCOORD2DVPROC) (const GLdouble *u); typedef void (APIENTRYP PFNGLEVALCOORD2FPROC) (GLfloat u, GLfloat v); typedef void (APIENTRYP PFNGLEVALCOORD2FVPROC) (const GLfloat *u); typedef void (APIENTRYP PFNGLEVALMESH1PROC) (GLenum mode, GLint i1, GLint i2); typedef void (APIENTRYP PFNGLEVALPOINT1PROC) (GLint i); typedef void (APIENTRYP PFNGLEVALMESH2PROC) (GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); typedef void (APIENTRYP PFNGLEVALPOINT2PROC) (GLint i, GLint j); typedef void (APIENTRYP PFNGLALPHAFUNCPROC) (GLenum func, GLfloat ref); typedef void (APIENTRYP PFNGLPIXELZOOMPROC) (GLfloat xfactor, GLfloat yfactor); typedef void (APIENTRYP PFNGLPIXELTRANSFERFPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLPIXELTRANSFERIPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLPIXELMAPFVPROC) (GLenum map, GLsizei mapsize, const GLfloat *values); typedef void (APIENTRYP PFNGLPIXELMAPUIVPROC) (GLenum map, GLsizei mapsize, const GLuint *values); typedef void (APIENTRYP PFNGLPIXELMAPUSVPROC) (GLenum map, GLsizei mapsize, const GLushort *values); typedef void (APIENTRYP PFNGLCOPYPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); typedef void (APIENTRYP PFNGLDRAWPIXELSPROC) (GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLGETCLIPPLANEPROC) (GLenum plane, GLdouble *equation); typedef void (APIENTRYP PFNGLGETLIGHTFVPROC) (GLenum light, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETLIGHTIVPROC) (GLenum light, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETMAPDVPROC) (GLenum target, GLenum query, GLdouble *v); typedef void (APIENTRYP PFNGLGETMAPFVPROC) (GLenum target, GLenum query, GLfloat *v); typedef void (APIENTRYP PFNGLGETMAPIVPROC) (GLenum target, GLenum query, GLint *v); typedef void (APIENTRYP PFNGLGETMATERIALFVPROC) (GLenum face, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETMATERIALIVPROC) (GLenum face, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETPIXELMAPFVPROC) (GLenum map, GLfloat *values); typedef void (APIENTRYP PFNGLGETPIXELMAPUIVPROC) (GLenum map, GLuint *values); typedef void (APIENTRYP PFNGLGETPIXELMAPUSVPROC) (GLenum map, GLushort *values); typedef void (APIENTRYP PFNGLGETPOLYGONSTIPPLEPROC) (GLubyte *mask); typedef void (APIENTRYP PFNGLGETTEXENVFVPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETTEXENVIVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETTEXGENDVPROC) (GLenum coord, GLenum pname, GLdouble *params); typedef void (APIENTRYP PFNGLGETTEXGENFVPROC) (GLenum coord, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETTEXGENIVPROC) (GLenum coord, GLenum pname, GLint *params); typedef GLboolean (APIENTRYP PFNGLISLISTPROC) (GLuint list); typedef void (APIENTRYP PFNGLFRUSTUMPROC) (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); typedef void (APIENTRYP PFNGLLOADIDENTITYPROC) (void); typedef void (APIENTRYP PFNGLLOADMATRIXFPROC) (const GLfloat *m); typedef void (APIENTRYP PFNGLLOADMATRIXDPROC) (const GLdouble *m); typedef void (APIENTRYP PFNGLMATRIXMODEPROC) (GLenum mode); typedef void (APIENTRYP PFNGLMULTMATRIXFPROC) (const GLfloat *m); typedef void (APIENTRYP PFNGLMULTMATRIXDPROC) (const GLdouble *m); typedef void (APIENTRYP PFNGLORTHOPROC) (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); typedef void (APIENTRYP PFNGLPOPMATRIXPROC) (void); typedef void (APIENTRYP PFNGLPUSHMATRIXPROC) (void); typedef void (APIENTRYP PFNGLROTATEDPROC) (GLdouble angle, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLROTATEFPROC) (GLfloat angle, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLSCALEDPROC) (GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLSCALEFPROC) (GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLTRANSLATEDPROC) (GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLTRANSLATEFPROC) (GLfloat x, GLfloat y, GLfloat z); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCullFace (GLenum mode); GLAPI void APIENTRY glFrontFace (GLenum mode); GLAPI void APIENTRY glHint (GLenum target, GLenum mode); GLAPI void APIENTRY glLineWidth (GLfloat width); GLAPI void APIENTRY glPointSize (GLfloat size); GLAPI void APIENTRY glPolygonMode (GLenum face, GLenum mode); GLAPI void APIENTRY glScissor (GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glTexParameterf (GLenum target, GLenum pname, GLfloat param); GLAPI void APIENTRY glTexParameterfv (GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param); GLAPI void APIENTRY glTexParameteriv (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glTexImage1D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glDrawBuffer (GLenum buf); GLAPI void APIENTRY glClear (GLbitfield mask); GLAPI void APIENTRY glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); GLAPI void APIENTRY glClearStencil (GLint s); GLAPI void APIENTRY glClearDepth (GLdouble depth); GLAPI void APIENTRY glStencilMask (GLuint mask); GLAPI void APIENTRY glColorMask (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); GLAPI void APIENTRY glDepthMask (GLboolean flag); GLAPI void APIENTRY glDisable (GLenum cap); GLAPI void APIENTRY glEnable (GLenum cap); GLAPI void APIENTRY glFinish (void); GLAPI void APIENTRY glFlush (void); GLAPI void APIENTRY glBlendFunc (GLenum sfactor, GLenum dfactor); GLAPI void APIENTRY glLogicOp (GLenum opcode); GLAPI void APIENTRY glStencilFunc (GLenum func, GLint ref, GLuint mask); GLAPI void APIENTRY glStencilOp (GLenum fail, GLenum zfail, GLenum zpass); GLAPI void APIENTRY glDepthFunc (GLenum func); GLAPI void APIENTRY glPixelStoref (GLenum pname, GLfloat param); GLAPI void APIENTRY glPixelStorei (GLenum pname, GLint param); GLAPI void APIENTRY glReadBuffer (GLenum src); GLAPI void APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); GLAPI void APIENTRY glGetBooleanv (GLenum pname, GLboolean *data); GLAPI void APIENTRY glGetDoublev (GLenum pname, GLdouble *data); GLAPI GLenum APIENTRY glGetError (void); GLAPI void APIENTRY glGetFloatv (GLenum pname, GLfloat *data); GLAPI void APIENTRY glGetIntegerv (GLenum pname, GLint *data); GLAPI const GLubyte *APIENTRY glGetString (GLenum name); GLAPI void APIENTRY glGetTexImage (GLenum target, GLint level, GLenum format, GLenum type, void *pixels); GLAPI void APIENTRY glGetTexParameterfv (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetTexParameteriv (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetTexLevelParameterfv (GLenum target, GLint level, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetTexLevelParameteriv (GLenum target, GLint level, GLenum pname, GLint *params); GLAPI GLboolean APIENTRY glIsEnabled (GLenum cap); GLAPI void APIENTRY glDepthRange (GLdouble n, GLdouble f); GLAPI void APIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glNewList (GLuint list, GLenum mode); GLAPI void APIENTRY glEndList (void); GLAPI void APIENTRY glCallList (GLuint list); GLAPI void APIENTRY glCallLists (GLsizei n, GLenum type, const void *lists); GLAPI void APIENTRY glDeleteLists (GLuint list, GLsizei range); GLAPI GLuint APIENTRY glGenLists (GLsizei range); GLAPI void APIENTRY glListBase (GLuint base); GLAPI void APIENTRY glBegin (GLenum mode); GLAPI void APIENTRY glBitmap (GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); GLAPI void APIENTRY glColor3b (GLbyte red, GLbyte green, GLbyte blue); GLAPI void APIENTRY glColor3bv (const GLbyte *v); GLAPI void APIENTRY glColor3d (GLdouble red, GLdouble green, GLdouble blue); GLAPI void APIENTRY glColor3dv (const GLdouble *v); GLAPI void APIENTRY glColor3f (GLfloat red, GLfloat green, GLfloat blue); GLAPI void APIENTRY glColor3fv (const GLfloat *v); GLAPI void APIENTRY glColor3i (GLint red, GLint green, GLint blue); GLAPI void APIENTRY glColor3iv (const GLint *v); GLAPI void APIENTRY glColor3s (GLshort red, GLshort green, GLshort blue); GLAPI void APIENTRY glColor3sv (const GLshort *v); GLAPI void APIENTRY glColor3ub (GLubyte red, GLubyte green, GLubyte blue); GLAPI void APIENTRY glColor3ubv (const GLubyte *v); GLAPI void APIENTRY glColor3ui (GLuint red, GLuint green, GLuint blue); GLAPI void APIENTRY glColor3uiv (const GLuint *v); GLAPI void APIENTRY glColor3us (GLushort red, GLushort green, GLushort blue); GLAPI void APIENTRY glColor3usv (const GLushort *v); GLAPI void APIENTRY glColor4b (GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); GLAPI void APIENTRY glColor4bv (const GLbyte *v); GLAPI void APIENTRY glColor4d (GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); GLAPI void APIENTRY glColor4dv (const GLdouble *v); GLAPI void APIENTRY glColor4f (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); GLAPI void APIENTRY glColor4fv (const GLfloat *v); GLAPI void APIENTRY glColor4i (GLint red, GLint green, GLint blue, GLint alpha); GLAPI void APIENTRY glColor4iv (const GLint *v); GLAPI void APIENTRY glColor4s (GLshort red, GLshort green, GLshort blue, GLshort alpha); GLAPI void APIENTRY glColor4sv (const GLshort *v); GLAPI void APIENTRY glColor4ub (GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); GLAPI void APIENTRY glColor4ubv (const GLubyte *v); GLAPI void APIENTRY glColor4ui (GLuint red, GLuint green, GLuint blue, GLuint alpha); GLAPI void APIENTRY glColor4uiv (const GLuint *v); GLAPI void APIENTRY glColor4us (GLushort red, GLushort green, GLushort blue, GLushort alpha); GLAPI void APIENTRY glColor4usv (const GLushort *v); GLAPI void APIENTRY glEdgeFlag (GLboolean flag); GLAPI void APIENTRY glEdgeFlagv (const GLboolean *flag); GLAPI void APIENTRY glEnd (void); GLAPI void APIENTRY glIndexd (GLdouble c); GLAPI void APIENTRY glIndexdv (const GLdouble *c); GLAPI void APIENTRY glIndexf (GLfloat c); GLAPI void APIENTRY glIndexfv (const GLfloat *c); GLAPI void APIENTRY glIndexi (GLint c); GLAPI void APIENTRY glIndexiv (const GLint *c); GLAPI void APIENTRY glIndexs (GLshort c); GLAPI void APIENTRY glIndexsv (const GLshort *c); GLAPI void APIENTRY glNormal3b (GLbyte nx, GLbyte ny, GLbyte nz); GLAPI void APIENTRY glNormal3bv (const GLbyte *v); GLAPI void APIENTRY glNormal3d (GLdouble nx, GLdouble ny, GLdouble nz); GLAPI void APIENTRY glNormal3dv (const GLdouble *v); GLAPI void APIENTRY glNormal3f (GLfloat nx, GLfloat ny, GLfloat nz); GLAPI void APIENTRY glNormal3fv (const GLfloat *v); GLAPI void APIENTRY glNormal3i (GLint nx, GLint ny, GLint nz); GLAPI void APIENTRY glNormal3iv (const GLint *v); GLAPI void APIENTRY glNormal3s (GLshort nx, GLshort ny, GLshort nz); GLAPI void APIENTRY glNormal3sv (const GLshort *v); GLAPI void APIENTRY glRasterPos2d (GLdouble x, GLdouble y); GLAPI void APIENTRY glRasterPos2dv (const GLdouble *v); GLAPI void APIENTRY glRasterPos2f (GLfloat x, GLfloat y); GLAPI void APIENTRY glRasterPos2fv (const GLfloat *v); GLAPI void APIENTRY glRasterPos2i (GLint x, GLint y); GLAPI void APIENTRY glRasterPos2iv (const GLint *v); GLAPI void APIENTRY glRasterPos2s (GLshort x, GLshort y); GLAPI void APIENTRY glRasterPos2sv (const GLshort *v); GLAPI void APIENTRY glRasterPos3d (GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glRasterPos3dv (const GLdouble *v); GLAPI void APIENTRY glRasterPos3f (GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glRasterPos3fv (const GLfloat *v); GLAPI void APIENTRY glRasterPos3i (GLint x, GLint y, GLint z); GLAPI void APIENTRY glRasterPos3iv (const GLint *v); GLAPI void APIENTRY glRasterPos3s (GLshort x, GLshort y, GLshort z); GLAPI void APIENTRY glRasterPos3sv (const GLshort *v); GLAPI void APIENTRY glRasterPos4d (GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glRasterPos4dv (const GLdouble *v); GLAPI void APIENTRY glRasterPos4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glRasterPos4fv (const GLfloat *v); GLAPI void APIENTRY glRasterPos4i (GLint x, GLint y, GLint z, GLint w); GLAPI void APIENTRY glRasterPos4iv (const GLint *v); GLAPI void APIENTRY glRasterPos4s (GLshort x, GLshort y, GLshort z, GLshort w); GLAPI void APIENTRY glRasterPos4sv (const GLshort *v); GLAPI void APIENTRY glRectd (GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); GLAPI void APIENTRY glRectdv (const GLdouble *v1, const GLdouble *v2); GLAPI void APIENTRY glRectf (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); GLAPI void APIENTRY glRectfv (const GLfloat *v1, const GLfloat *v2); GLAPI void APIENTRY glRecti (GLint x1, GLint y1, GLint x2, GLint y2); GLAPI void APIENTRY glRectiv (const GLint *v1, const GLint *v2); GLAPI void APIENTRY glRects (GLshort x1, GLshort y1, GLshort x2, GLshort y2); GLAPI void APIENTRY glRectsv (const GLshort *v1, const GLshort *v2); GLAPI void APIENTRY glTexCoord1d (GLdouble s); GLAPI void APIENTRY glTexCoord1dv (const GLdouble *v); GLAPI void APIENTRY glTexCoord1f (GLfloat s); GLAPI void APIENTRY glTexCoord1fv (const GLfloat *v); GLAPI void APIENTRY glTexCoord1i (GLint s); GLAPI void APIENTRY glTexCoord1iv (const GLint *v); GLAPI void APIENTRY glTexCoord1s (GLshort s); GLAPI void APIENTRY glTexCoord1sv (const GLshort *v); GLAPI void APIENTRY glTexCoord2d (GLdouble s, GLdouble t); GLAPI void APIENTRY glTexCoord2dv (const GLdouble *v); GLAPI void APIENTRY glTexCoord2f (GLfloat s, GLfloat t); GLAPI void APIENTRY glTexCoord2fv (const GLfloat *v); GLAPI void APIENTRY glTexCoord2i (GLint s, GLint t); GLAPI void APIENTRY glTexCoord2iv (const GLint *v); GLAPI void APIENTRY glTexCoord2s (GLshort s, GLshort t); GLAPI void APIENTRY glTexCoord2sv (const GLshort *v); GLAPI void APIENTRY glTexCoord3d (GLdouble s, GLdouble t, GLdouble r); GLAPI void APIENTRY glTexCoord3dv (const GLdouble *v); GLAPI void APIENTRY glTexCoord3f (GLfloat s, GLfloat t, GLfloat r); GLAPI void APIENTRY glTexCoord3fv (const GLfloat *v); GLAPI void APIENTRY glTexCoord3i (GLint s, GLint t, GLint r); GLAPI void APIENTRY glTexCoord3iv (const GLint *v); GLAPI void APIENTRY glTexCoord3s (GLshort s, GLshort t, GLshort r); GLAPI void APIENTRY glTexCoord3sv (const GLshort *v); GLAPI void APIENTRY glTexCoord4d (GLdouble s, GLdouble t, GLdouble r, GLdouble q); GLAPI void APIENTRY glTexCoord4dv (const GLdouble *v); GLAPI void APIENTRY glTexCoord4f (GLfloat s, GLfloat t, GLfloat r, GLfloat q); GLAPI void APIENTRY glTexCoord4fv (const GLfloat *v); GLAPI void APIENTRY glTexCoord4i (GLint s, GLint t, GLint r, GLint q); GLAPI void APIENTRY glTexCoord4iv (const GLint *v); GLAPI void APIENTRY glTexCoord4s (GLshort s, GLshort t, GLshort r, GLshort q); GLAPI void APIENTRY glTexCoord4sv (const GLshort *v); GLAPI void APIENTRY glVertex2d (GLdouble x, GLdouble y); GLAPI void APIENTRY glVertex2dv (const GLdouble *v); GLAPI void APIENTRY glVertex2f (GLfloat x, GLfloat y); GLAPI void APIENTRY glVertex2fv (const GLfloat *v); GLAPI void APIENTRY glVertex2i (GLint x, GLint y); GLAPI void APIENTRY glVertex2iv (const GLint *v); GLAPI void APIENTRY glVertex2s (GLshort x, GLshort y); GLAPI void APIENTRY glVertex2sv (const GLshort *v); GLAPI void APIENTRY glVertex3d (GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glVertex3dv (const GLdouble *v); GLAPI void APIENTRY glVertex3f (GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glVertex3fv (const GLfloat *v); GLAPI void APIENTRY glVertex3i (GLint x, GLint y, GLint z); GLAPI void APIENTRY glVertex3iv (const GLint *v); GLAPI void APIENTRY glVertex3s (GLshort x, GLshort y, GLshort z); GLAPI void APIENTRY glVertex3sv (const GLshort *v); GLAPI void APIENTRY glVertex4d (GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glVertex4dv (const GLdouble *v); GLAPI void APIENTRY glVertex4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glVertex4fv (const GLfloat *v); GLAPI void APIENTRY glVertex4i (GLint x, GLint y, GLint z, GLint w); GLAPI void APIENTRY glVertex4iv (const GLint *v); GLAPI void APIENTRY glVertex4s (GLshort x, GLshort y, GLshort z, GLshort w); GLAPI void APIENTRY glVertex4sv (const GLshort *v); GLAPI void APIENTRY glClipPlane (GLenum plane, const GLdouble *equation); GLAPI void APIENTRY glColorMaterial (GLenum face, GLenum mode); GLAPI void APIENTRY glFogf (GLenum pname, GLfloat param); GLAPI void APIENTRY glFogfv (GLenum pname, const GLfloat *params); GLAPI void APIENTRY glFogi (GLenum pname, GLint param); GLAPI void APIENTRY glFogiv (GLenum pname, const GLint *params); GLAPI void APIENTRY glLightf (GLenum light, GLenum pname, GLfloat param); GLAPI void APIENTRY glLightfv (GLenum light, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glLighti (GLenum light, GLenum pname, GLint param); GLAPI void APIENTRY glLightiv (GLenum light, GLenum pname, const GLint *params); GLAPI void APIENTRY glLightModelf (GLenum pname, GLfloat param); GLAPI void APIENTRY glLightModelfv (GLenum pname, const GLfloat *params); GLAPI void APIENTRY glLightModeli (GLenum pname, GLint param); GLAPI void APIENTRY glLightModeliv (GLenum pname, const GLint *params); GLAPI void APIENTRY glLineStipple (GLint factor, GLushort pattern); GLAPI void APIENTRY glMaterialf (GLenum face, GLenum pname, GLfloat param); GLAPI void APIENTRY glMaterialfv (GLenum face, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glMateriali (GLenum face, GLenum pname, GLint param); GLAPI void APIENTRY glMaterialiv (GLenum face, GLenum pname, const GLint *params); GLAPI void APIENTRY glPolygonStipple (const GLubyte *mask); GLAPI void APIENTRY glShadeModel (GLenum mode); GLAPI void APIENTRY glTexEnvf (GLenum target, GLenum pname, GLfloat param); GLAPI void APIENTRY glTexEnvfv (GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glTexEnvi (GLenum target, GLenum pname, GLint param); GLAPI void APIENTRY glTexEnviv (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glTexGend (GLenum coord, GLenum pname, GLdouble param); GLAPI void APIENTRY glTexGendv (GLenum coord, GLenum pname, const GLdouble *params); GLAPI void APIENTRY glTexGenf (GLenum coord, GLenum pname, GLfloat param); GLAPI void APIENTRY glTexGenfv (GLenum coord, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glTexGeni (GLenum coord, GLenum pname, GLint param); GLAPI void APIENTRY glTexGeniv (GLenum coord, GLenum pname, const GLint *params); GLAPI void APIENTRY glFeedbackBuffer (GLsizei size, GLenum type, GLfloat *buffer); GLAPI void APIENTRY glSelectBuffer (GLsizei size, GLuint *buffer); GLAPI GLint APIENTRY glRenderMode (GLenum mode); GLAPI void APIENTRY glInitNames (void); GLAPI void APIENTRY glLoadName (GLuint name); GLAPI void APIENTRY glPassThrough (GLfloat token); GLAPI void APIENTRY glPopName (void); GLAPI void APIENTRY glPushName (GLuint name); GLAPI void APIENTRY glClearAccum (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); GLAPI void APIENTRY glClearIndex (GLfloat c); GLAPI void APIENTRY glIndexMask (GLuint mask); GLAPI void APIENTRY glAccum (GLenum op, GLfloat value); GLAPI void APIENTRY glPopAttrib (void); GLAPI void APIENTRY glPushAttrib (GLbitfield mask); GLAPI void APIENTRY glMap1d (GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); GLAPI void APIENTRY glMap1f (GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); GLAPI void APIENTRY glMap2d (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); GLAPI void APIENTRY glMap2f (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); GLAPI void APIENTRY glMapGrid1d (GLint un, GLdouble u1, GLdouble u2); GLAPI void APIENTRY glMapGrid1f (GLint un, GLfloat u1, GLfloat u2); GLAPI void APIENTRY glMapGrid2d (GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); GLAPI void APIENTRY glMapGrid2f (GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); GLAPI void APIENTRY glEvalCoord1d (GLdouble u); GLAPI void APIENTRY glEvalCoord1dv (const GLdouble *u); GLAPI void APIENTRY glEvalCoord1f (GLfloat u); GLAPI void APIENTRY glEvalCoord1fv (const GLfloat *u); GLAPI void APIENTRY glEvalCoord2d (GLdouble u, GLdouble v); GLAPI void APIENTRY glEvalCoord2dv (const GLdouble *u); GLAPI void APIENTRY glEvalCoord2f (GLfloat u, GLfloat v); GLAPI void APIENTRY glEvalCoord2fv (const GLfloat *u); GLAPI void APIENTRY glEvalMesh1 (GLenum mode, GLint i1, GLint i2); GLAPI void APIENTRY glEvalPoint1 (GLint i); GLAPI void APIENTRY glEvalMesh2 (GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); GLAPI void APIENTRY glEvalPoint2 (GLint i, GLint j); GLAPI void APIENTRY glAlphaFunc (GLenum func, GLfloat ref); GLAPI void APIENTRY glPixelZoom (GLfloat xfactor, GLfloat yfactor); GLAPI void APIENTRY glPixelTransferf (GLenum pname, GLfloat param); GLAPI void APIENTRY glPixelTransferi (GLenum pname, GLint param); GLAPI void APIENTRY glPixelMapfv (GLenum map, GLsizei mapsize, const GLfloat *values); GLAPI void APIENTRY glPixelMapuiv (GLenum map, GLsizei mapsize, const GLuint *values); GLAPI void APIENTRY glPixelMapusv (GLenum map, GLsizei mapsize, const GLushort *values); GLAPI void APIENTRY glCopyPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); GLAPI void APIENTRY glDrawPixels (GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glGetClipPlane (GLenum plane, GLdouble *equation); GLAPI void APIENTRY glGetLightfv (GLenum light, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetLightiv (GLenum light, GLenum pname, GLint *params); GLAPI void APIENTRY glGetMapdv (GLenum target, GLenum query, GLdouble *v); GLAPI void APIENTRY glGetMapfv (GLenum target, GLenum query, GLfloat *v); GLAPI void APIENTRY glGetMapiv (GLenum target, GLenum query, GLint *v); GLAPI void APIENTRY glGetMaterialfv (GLenum face, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetMaterialiv (GLenum face, GLenum pname, GLint *params); GLAPI void APIENTRY glGetPixelMapfv (GLenum map, GLfloat *values); GLAPI void APIENTRY glGetPixelMapuiv (GLenum map, GLuint *values); GLAPI void APIENTRY glGetPixelMapusv (GLenum map, GLushort *values); GLAPI void APIENTRY glGetPolygonStipple (GLubyte *mask); GLAPI void APIENTRY glGetTexEnvfv (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetTexEnviv (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetTexGendv (GLenum coord, GLenum pname, GLdouble *params); GLAPI void APIENTRY glGetTexGenfv (GLenum coord, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetTexGeniv (GLenum coord, GLenum pname, GLint *params); GLAPI GLboolean APIENTRY glIsList (GLuint list); GLAPI void APIENTRY glFrustum (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); GLAPI void APIENTRY glLoadIdentity (void); GLAPI void APIENTRY glLoadMatrixf (const GLfloat *m); GLAPI void APIENTRY glLoadMatrixd (const GLdouble *m); GLAPI void APIENTRY glMatrixMode (GLenum mode); GLAPI void APIENTRY glMultMatrixf (const GLfloat *m); GLAPI void APIENTRY glMultMatrixd (const GLdouble *m); GLAPI void APIENTRY glOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); GLAPI void APIENTRY glPopMatrix (void); GLAPI void APIENTRY glPushMatrix (void); GLAPI void APIENTRY glRotated (GLdouble angle, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glRotatef (GLfloat angle, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glScaled (GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glScalef (GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glTranslated (GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glTranslatef (GLfloat x, GLfloat y, GLfloat z); #endif #endif /* GL_VERSION_1_0 */ #ifndef GL_VERSION_1_1 #define GL_VERSION_1_1 1 typedef khronos_float_t GLclampf; typedef double GLclampd; #define GL_COLOR_LOGIC_OP 0x0BF2 #define GL_POLYGON_OFFSET_UNITS 0x2A00 #define GL_POLYGON_OFFSET_POINT 0x2A01 #define GL_POLYGON_OFFSET_LINE 0x2A02 #define GL_POLYGON_OFFSET_FILL 0x8037 #define GL_POLYGON_OFFSET_FACTOR 0x8038 #define GL_TEXTURE_BINDING_1D 0x8068 #define GL_TEXTURE_BINDING_2D 0x8069 #define GL_TEXTURE_INTERNAL_FORMAT 0x1003 #define GL_TEXTURE_RED_SIZE 0x805C #define GL_TEXTURE_GREEN_SIZE 0x805D #define GL_TEXTURE_BLUE_SIZE 0x805E #define GL_TEXTURE_ALPHA_SIZE 0x805F #define GL_DOUBLE 0x140A #define GL_PROXY_TEXTURE_1D 0x8063 #define GL_PROXY_TEXTURE_2D 0x8064 #define GL_R3_G3_B2 0x2A10 #define GL_RGB4 0x804F #define GL_RGB5 0x8050 #define GL_RGB8 0x8051 #define GL_RGB10 0x8052 #define GL_RGB12 0x8053 #define GL_RGB16 0x8054 #define GL_RGBA2 0x8055 #define GL_RGBA4 0x8056 #define GL_RGB5_A1 0x8057 #define GL_RGBA8 0x8058 #define GL_RGB10_A2 0x8059 #define GL_RGBA12 0x805A #define GL_RGBA16 0x805B #define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 #define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 #define GL_CLIENT_ALL_ATTRIB_BITS 0xFFFFFFFF #define GL_VERTEX_ARRAY_POINTER 0x808E #define GL_NORMAL_ARRAY_POINTER 0x808F #define GL_COLOR_ARRAY_POINTER 0x8090 #define GL_INDEX_ARRAY_POINTER 0x8091 #define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 #define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 #define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 #define GL_SELECTION_BUFFER_POINTER 0x0DF3 #define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 #define GL_INDEX_LOGIC_OP 0x0BF1 #define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B #define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 #define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 #define GL_SELECTION_BUFFER_SIZE 0x0DF4 #define GL_VERTEX_ARRAY 0x8074 #define GL_NORMAL_ARRAY 0x8075 #define GL_COLOR_ARRAY 0x8076 #define GL_INDEX_ARRAY 0x8077 #define GL_TEXTURE_COORD_ARRAY 0x8078 #define GL_EDGE_FLAG_ARRAY 0x8079 #define GL_VERTEX_ARRAY_SIZE 0x807A #define GL_VERTEX_ARRAY_TYPE 0x807B #define GL_VERTEX_ARRAY_STRIDE 0x807C #define GL_NORMAL_ARRAY_TYPE 0x807E #define GL_NORMAL_ARRAY_STRIDE 0x807F #define GL_COLOR_ARRAY_SIZE 0x8081 #define GL_COLOR_ARRAY_TYPE 0x8082 #define GL_COLOR_ARRAY_STRIDE 0x8083 #define GL_INDEX_ARRAY_TYPE 0x8085 #define GL_INDEX_ARRAY_STRIDE 0x8086 #define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 #define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 #define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A #define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C #define GL_TEXTURE_LUMINANCE_SIZE 0x8060 #define GL_TEXTURE_INTENSITY_SIZE 0x8061 #define GL_TEXTURE_PRIORITY 0x8066 #define GL_TEXTURE_RESIDENT 0x8067 #define GL_ALPHA4 0x803B #define GL_ALPHA8 0x803C #define GL_ALPHA12 0x803D #define GL_ALPHA16 0x803E #define GL_LUMINANCE4 0x803F #define GL_LUMINANCE8 0x8040 #define GL_LUMINANCE12 0x8041 #define GL_LUMINANCE16 0x8042 #define GL_LUMINANCE4_ALPHA4 0x8043 #define GL_LUMINANCE6_ALPHA2 0x8044 #define GL_LUMINANCE8_ALPHA8 0x8045 #define GL_LUMINANCE12_ALPHA4 0x8046 #define GL_LUMINANCE12_ALPHA12 0x8047 #define GL_LUMINANCE16_ALPHA16 0x8048 #define GL_INTENSITY 0x8049 #define GL_INTENSITY4 0x804A #define GL_INTENSITY8 0x804B #define GL_INTENSITY12 0x804C #define GL_INTENSITY16 0x804D #define GL_V2F 0x2A20 #define GL_V3F 0x2A21 #define GL_C4UB_V2F 0x2A22 #define GL_C4UB_V3F 0x2A23 #define GL_C3F_V3F 0x2A24 #define GL_N3F_V3F 0x2A25 #define GL_C4F_N3F_V3F 0x2A26 #define GL_T2F_V3F 0x2A27 #define GL_T4F_V4F 0x2A28 #define GL_T2F_C4UB_V3F 0x2A29 #define GL_T2F_C3F_V3F 0x2A2A #define GL_T2F_N3F_V3F 0x2A2B #define GL_T2F_C4F_N3F_V3F 0x2A2C #define GL_T4F_C4F_N3F_V4F 0x2A2D typedef void (APIENTRYP PFNGLDRAWARRAYSPROC) (GLenum mode, GLint first, GLsizei count); typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices); typedef void (APIENTRYP PFNGLGETPOINTERVPROC) (GLenum pname, void **params); typedef void (APIENTRYP PFNGLPOLYGONOFFSETPROC) (GLfloat factor, GLfloat units); typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture); typedef void (APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures); typedef void (APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures); typedef GLboolean (APIENTRYP PFNGLISTEXTUREPROC) (GLuint texture); typedef void (APIENTRYP PFNGLARRAYELEMENTPROC) (GLint i); typedef void (APIENTRYP PFNGLCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEPROC) (GLenum array); typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERPROC) (GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLENABLECLIENTSTATEPROC) (GLenum array); typedef void (APIENTRYP PFNGLINDEXPOINTERPROC) (GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLINTERLEAVEDARRAYSPROC) (GLenum format, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLNORMALPOINTERPROC) (GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLTEXCOORDPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLVERTEXPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESPROC) (GLsizei n, const GLuint *textures, const GLfloat *priorities); typedef void (APIENTRYP PFNGLINDEXUBPROC) (GLubyte c); typedef void (APIENTRYP PFNGLINDEXUBVPROC) (const GLubyte *c); typedef void (APIENTRYP PFNGLPOPCLIENTATTRIBPROC) (void); typedef void (APIENTRYP PFNGLPUSHCLIENTATTRIBPROC) (GLbitfield mask); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawArrays (GLenum mode, GLint first, GLsizei count); GLAPI void APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices); GLAPI void APIENTRY glGetPointerv (GLenum pname, void **params); GLAPI void APIENTRY glPolygonOffset (GLfloat factor, GLfloat units); GLAPI void APIENTRY glCopyTexImage1D (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); GLAPI void APIENTRY glCopyTexImage2D (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); GLAPI void APIENTRY glCopyTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); GLAPI void APIENTRY glCopyTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glBindTexture (GLenum target, GLuint texture); GLAPI void APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures); GLAPI void APIENTRY glGenTextures (GLsizei n, GLuint *textures); GLAPI GLboolean APIENTRY glIsTexture (GLuint texture); GLAPI void APIENTRY glArrayElement (GLint i); GLAPI void APIENTRY glColorPointer (GLint size, GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glDisableClientState (GLenum array); GLAPI void APIENTRY glEdgeFlagPointer (GLsizei stride, const void *pointer); GLAPI void APIENTRY glEnableClientState (GLenum array); GLAPI void APIENTRY glIndexPointer (GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glInterleavedArrays (GLenum format, GLsizei stride, const void *pointer); GLAPI void APIENTRY glNormalPointer (GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glTexCoordPointer (GLint size, GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glVertexPointer (GLint size, GLenum type, GLsizei stride, const void *pointer); GLAPI GLboolean APIENTRY glAreTexturesResident (GLsizei n, const GLuint *textures, GLboolean *residences); GLAPI void APIENTRY glPrioritizeTextures (GLsizei n, const GLuint *textures, const GLfloat *priorities); GLAPI void APIENTRY glIndexub (GLubyte c); GLAPI void APIENTRY glIndexubv (const GLubyte *c); GLAPI void APIENTRY glPopClientAttrib (void); GLAPI void APIENTRY glPushClientAttrib (GLbitfield mask); #endif #endif /* GL_VERSION_1_1 */ #ifndef GL_VERSION_1_2 #define GL_VERSION_1_2 1 #define GL_UNSIGNED_BYTE_3_3_2 0x8032 #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 #define GL_UNSIGNED_INT_8_8_8_8 0x8035 #define GL_UNSIGNED_INT_10_10_10_2 0x8036 #define GL_TEXTURE_BINDING_3D 0x806A #define GL_PACK_SKIP_IMAGES 0x806B #define GL_PACK_IMAGE_HEIGHT 0x806C #define GL_UNPACK_SKIP_IMAGES 0x806D #define GL_UNPACK_IMAGE_HEIGHT 0x806E #define GL_TEXTURE_3D 0x806F #define GL_PROXY_TEXTURE_3D 0x8070 #define GL_TEXTURE_DEPTH 0x8071 #define GL_TEXTURE_WRAP_R 0x8072 #define GL_MAX_3D_TEXTURE_SIZE 0x8073 #define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 #define GL_UNSIGNED_SHORT_5_6_5 0x8363 #define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 #define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 #define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 #define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 #define GL_BGR 0x80E0 #define GL_BGRA 0x80E1 #define GL_MAX_ELEMENTS_VERTICES 0x80E8 #define GL_MAX_ELEMENTS_INDICES 0x80E9 #define GL_CLAMP_TO_EDGE 0x812F #define GL_TEXTURE_MIN_LOD 0x813A #define GL_TEXTURE_MAX_LOD 0x813B #define GL_TEXTURE_BASE_LEVEL 0x813C #define GL_TEXTURE_MAX_LEVEL 0x813D #define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 #define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 #define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 #define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 #define GL_ALIASED_LINE_WIDTH_RANGE 0x846E #define GL_RESCALE_NORMAL 0x803A #define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 #define GL_SINGLE_COLOR 0x81F9 #define GL_SEPARATE_SPECULAR_COLOR 0x81FA #define GL_ALIASED_POINT_SIZE_RANGE 0x846D typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawRangeElements (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); GLAPI void APIENTRY glTexImage3D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glCopyTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); #endif #endif /* GL_VERSION_1_2 */ #ifndef GL_VERSION_1_3 #define GL_VERSION_1_3 1 #define GL_TEXTURE0 0x84C0 #define GL_TEXTURE1 0x84C1 #define GL_TEXTURE2 0x84C2 #define GL_TEXTURE3 0x84C3 #define GL_TEXTURE4 0x84C4 #define GL_TEXTURE5 0x84C5 #define GL_TEXTURE6 0x84C6 #define GL_TEXTURE7 0x84C7 #define GL_TEXTURE8 0x84C8 #define GL_TEXTURE9 0x84C9 #define GL_TEXTURE10 0x84CA #define GL_TEXTURE11 0x84CB #define GL_TEXTURE12 0x84CC #define GL_TEXTURE13 0x84CD #define GL_TEXTURE14 0x84CE #define GL_TEXTURE15 0x84CF #define GL_TEXTURE16 0x84D0 #define GL_TEXTURE17 0x84D1 #define GL_TEXTURE18 0x84D2 #define GL_TEXTURE19 0x84D3 #define GL_TEXTURE20 0x84D4 #define GL_TEXTURE21 0x84D5 #define GL_TEXTURE22 0x84D6 #define GL_TEXTURE23 0x84D7 #define GL_TEXTURE24 0x84D8 #define GL_TEXTURE25 0x84D9 #define GL_TEXTURE26 0x84DA #define GL_TEXTURE27 0x84DB #define GL_TEXTURE28 0x84DC #define GL_TEXTURE29 0x84DD #define GL_TEXTURE30 0x84DE #define GL_TEXTURE31 0x84DF #define GL_ACTIVE_TEXTURE 0x84E0 #define GL_MULTISAMPLE 0x809D #define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E #define GL_SAMPLE_ALPHA_TO_ONE 0x809F #define GL_SAMPLE_COVERAGE 0x80A0 #define GL_SAMPLE_BUFFERS 0x80A8 #define GL_SAMPLES 0x80A9 #define GL_SAMPLE_COVERAGE_VALUE 0x80AA #define GL_SAMPLE_COVERAGE_INVERT 0x80AB #define GL_TEXTURE_CUBE_MAP 0x8513 #define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A #define GL_PROXY_TEXTURE_CUBE_MAP 0x851B #define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C #define GL_COMPRESSED_RGB 0x84ED #define GL_COMPRESSED_RGBA 0x84EE #define GL_TEXTURE_COMPRESSION_HINT 0x84EF #define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 #define GL_TEXTURE_COMPRESSED 0x86A1 #define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 #define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 #define GL_CLAMP_TO_BORDER 0x812D #define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 #define GL_MAX_TEXTURE_UNITS 0x84E2 #define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 #define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 #define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 #define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 #define GL_MULTISAMPLE_BIT 0x20000000 #define GL_NORMAL_MAP 0x8511 #define GL_REFLECTION_MAP 0x8512 #define GL_COMPRESSED_ALPHA 0x84E9 #define GL_COMPRESSED_LUMINANCE 0x84EA #define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB #define GL_COMPRESSED_INTENSITY 0x84EC #define GL_COMBINE 0x8570 #define GL_COMBINE_RGB 0x8571 #define GL_COMBINE_ALPHA 0x8572 #define GL_SOURCE0_RGB 0x8580 #define GL_SOURCE1_RGB 0x8581 #define GL_SOURCE2_RGB 0x8582 #define GL_SOURCE0_ALPHA 0x8588 #define GL_SOURCE1_ALPHA 0x8589 #define GL_SOURCE2_ALPHA 0x858A #define GL_OPERAND0_RGB 0x8590 #define GL_OPERAND1_RGB 0x8591 #define GL_OPERAND2_RGB 0x8592 #define GL_OPERAND0_ALPHA 0x8598 #define GL_OPERAND1_ALPHA 0x8599 #define GL_OPERAND2_ALPHA 0x859A #define GL_RGB_SCALE 0x8573 #define GL_ADD_SIGNED 0x8574 #define GL_INTERPOLATE 0x8575 #define GL_SUBTRACT 0x84E7 #define GL_CONSTANT 0x8576 #define GL_PRIMARY_COLOR 0x8577 #define GL_PREVIOUS 0x8578 #define GL_DOT3_RGB 0x86AE #define GL_DOT3_RGBA 0x86AF typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLfloat value, GLboolean invert); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, void *img); typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat *m); typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble *m); typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat *m); typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble *m); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glActiveTexture (GLenum texture); GLAPI void APIENTRY glSampleCoverage (GLfloat value, GLboolean invert); GLAPI void APIENTRY glCompressedTexImage3D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTexImage2D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTexImage1D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); GLAPI void APIENTRY glGetCompressedTexImage (GLenum target, GLint level, void *img); GLAPI void APIENTRY glClientActiveTexture (GLenum texture); GLAPI void APIENTRY glMultiTexCoord1d (GLenum target, GLdouble s); GLAPI void APIENTRY glMultiTexCoord1dv (GLenum target, const GLdouble *v); GLAPI void APIENTRY glMultiTexCoord1f (GLenum target, GLfloat s); GLAPI void APIENTRY glMultiTexCoord1fv (GLenum target, const GLfloat *v); GLAPI void APIENTRY glMultiTexCoord1i (GLenum target, GLint s); GLAPI void APIENTRY glMultiTexCoord1iv (GLenum target, const GLint *v); GLAPI void APIENTRY glMultiTexCoord1s (GLenum target, GLshort s); GLAPI void APIENTRY glMultiTexCoord1sv (GLenum target, const GLshort *v); GLAPI void APIENTRY glMultiTexCoord2d (GLenum target, GLdouble s, GLdouble t); GLAPI void APIENTRY glMultiTexCoord2dv (GLenum target, const GLdouble *v); GLAPI void APIENTRY glMultiTexCoord2f (GLenum target, GLfloat s, GLfloat t); GLAPI void APIENTRY glMultiTexCoord2fv (GLenum target, const GLfloat *v); GLAPI void APIENTRY glMultiTexCoord2i (GLenum target, GLint s, GLint t); GLAPI void APIENTRY glMultiTexCoord2iv (GLenum target, const GLint *v); GLAPI void APIENTRY glMultiTexCoord2s (GLenum target, GLshort s, GLshort t); GLAPI void APIENTRY glMultiTexCoord2sv (GLenum target, const GLshort *v); GLAPI void APIENTRY glMultiTexCoord3d (GLenum target, GLdouble s, GLdouble t, GLdouble r); GLAPI void APIENTRY glMultiTexCoord3dv (GLenum target, const GLdouble *v); GLAPI void APIENTRY glMultiTexCoord3f (GLenum target, GLfloat s, GLfloat t, GLfloat r); GLAPI void APIENTRY glMultiTexCoord3fv (GLenum target, const GLfloat *v); GLAPI void APIENTRY glMultiTexCoord3i (GLenum target, GLint s, GLint t, GLint r); GLAPI void APIENTRY glMultiTexCoord3iv (GLenum target, const GLint *v); GLAPI void APIENTRY glMultiTexCoord3s (GLenum target, GLshort s, GLshort t, GLshort r); GLAPI void APIENTRY glMultiTexCoord3sv (GLenum target, const GLshort *v); GLAPI void APIENTRY glMultiTexCoord4d (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); GLAPI void APIENTRY glMultiTexCoord4dv (GLenum target, const GLdouble *v); GLAPI void APIENTRY glMultiTexCoord4f (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); GLAPI void APIENTRY glMultiTexCoord4fv (GLenum target, const GLfloat *v); GLAPI void APIENTRY glMultiTexCoord4i (GLenum target, GLint s, GLint t, GLint r, GLint q); GLAPI void APIENTRY glMultiTexCoord4iv (GLenum target, const GLint *v); GLAPI void APIENTRY glMultiTexCoord4s (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); GLAPI void APIENTRY glMultiTexCoord4sv (GLenum target, const GLshort *v); GLAPI void APIENTRY glLoadTransposeMatrixf (const GLfloat *m); GLAPI void APIENTRY glLoadTransposeMatrixd (const GLdouble *m); GLAPI void APIENTRY glMultTransposeMatrixf (const GLfloat *m); GLAPI void APIENTRY glMultTransposeMatrixd (const GLdouble *m); #endif #endif /* GL_VERSION_1_3 */ #ifndef GL_VERSION_1_4 #define GL_VERSION_1_4 1 #define GL_BLEND_DST_RGB 0x80C8 #define GL_BLEND_SRC_RGB 0x80C9 #define GL_BLEND_DST_ALPHA 0x80CA #define GL_BLEND_SRC_ALPHA 0x80CB #define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 #define GL_DEPTH_COMPONENT16 0x81A5 #define GL_DEPTH_COMPONENT24 0x81A6 #define GL_DEPTH_COMPONENT32 0x81A7 #define GL_MIRRORED_REPEAT 0x8370 #define GL_MAX_TEXTURE_LOD_BIAS 0x84FD #define GL_TEXTURE_LOD_BIAS 0x8501 #define GL_INCR_WRAP 0x8507 #define GL_DECR_WRAP 0x8508 #define GL_TEXTURE_DEPTH_SIZE 0x884A #define GL_TEXTURE_COMPARE_MODE 0x884C #define GL_TEXTURE_COMPARE_FUNC 0x884D #define GL_POINT_SIZE_MIN 0x8126 #define GL_POINT_SIZE_MAX 0x8127 #define GL_POINT_DISTANCE_ATTENUATION 0x8129 #define GL_GENERATE_MIPMAP 0x8191 #define GL_GENERATE_MIPMAP_HINT 0x8192 #define GL_FOG_COORDINATE_SOURCE 0x8450 #define GL_FOG_COORDINATE 0x8451 #define GL_FRAGMENT_DEPTH 0x8452 #define GL_CURRENT_FOG_COORDINATE 0x8453 #define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 #define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 #define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 #define GL_FOG_COORDINATE_ARRAY 0x8457 #define GL_COLOR_SUM 0x8458 #define GL_CURRENT_SECONDARY_COLOR 0x8459 #define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A #define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B #define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C #define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D #define GL_SECONDARY_COLOR_ARRAY 0x845E #define GL_TEXTURE_FILTER_CONTROL 0x8500 #define GL_DEPTH_TEXTURE_MODE 0x884B #define GL_COMPARE_R_TO_TEXTURE 0x884E #define GL_BLEND_COLOR 0x8005 #define GL_BLEND_EQUATION 0x8009 #define GL_CONSTANT_COLOR 0x8001 #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 #define GL_CONSTANT_ALPHA 0x8003 #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 #define GL_FUNC_ADD 0x8006 #define GL_FUNC_REVERSE_SUBTRACT 0x800B #define GL_FUNC_SUBTRACT 0x800A #define GL_MIN 0x8007 #define GL_MAX 0x8008 typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount); typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC) (GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC) (GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLFOGCOORDFPROC) (GLfloat coord); typedef void (APIENTRYP PFNGLFOGCOORDFVPROC) (const GLfloat *coord); typedef void (APIENTRYP PFNGLFOGCOORDDPROC) (GLdouble coord); typedef void (APIENTRYP PFNGLFOGCOORDDVPROC) (const GLdouble *coord); typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC) (const GLint *v); typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLBLENDCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); GLAPI void APIENTRY glMultiDrawArrays (GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); GLAPI void APIENTRY glMultiDrawElements (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount); GLAPI void APIENTRY glPointParameterf (GLenum pname, GLfloat param); GLAPI void APIENTRY glPointParameterfv (GLenum pname, const GLfloat *params); GLAPI void APIENTRY glPointParameteri (GLenum pname, GLint param); GLAPI void APIENTRY glPointParameteriv (GLenum pname, const GLint *params); GLAPI void APIENTRY glFogCoordf (GLfloat coord); GLAPI void APIENTRY glFogCoordfv (const GLfloat *coord); GLAPI void APIENTRY glFogCoordd (GLdouble coord); GLAPI void APIENTRY glFogCoorddv (const GLdouble *coord); GLAPI void APIENTRY glFogCoordPointer (GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glSecondaryColor3b (GLbyte red, GLbyte green, GLbyte blue); GLAPI void APIENTRY glSecondaryColor3bv (const GLbyte *v); GLAPI void APIENTRY glSecondaryColor3d (GLdouble red, GLdouble green, GLdouble blue); GLAPI void APIENTRY glSecondaryColor3dv (const GLdouble *v); GLAPI void APIENTRY glSecondaryColor3f (GLfloat red, GLfloat green, GLfloat blue); GLAPI void APIENTRY glSecondaryColor3fv (const GLfloat *v); GLAPI void APIENTRY glSecondaryColor3i (GLint red, GLint green, GLint blue); GLAPI void APIENTRY glSecondaryColor3iv (const GLint *v); GLAPI void APIENTRY glSecondaryColor3s (GLshort red, GLshort green, GLshort blue); GLAPI void APIENTRY glSecondaryColor3sv (const GLshort *v); GLAPI void APIENTRY glSecondaryColor3ub (GLubyte red, GLubyte green, GLubyte blue); GLAPI void APIENTRY glSecondaryColor3ubv (const GLubyte *v); GLAPI void APIENTRY glSecondaryColor3ui (GLuint red, GLuint green, GLuint blue); GLAPI void APIENTRY glSecondaryColor3uiv (const GLuint *v); GLAPI void APIENTRY glSecondaryColor3us (GLushort red, GLushort green, GLushort blue); GLAPI void APIENTRY glSecondaryColor3usv (const GLushort *v); GLAPI void APIENTRY glSecondaryColorPointer (GLint size, GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glWindowPos2d (GLdouble x, GLdouble y); GLAPI void APIENTRY glWindowPos2dv (const GLdouble *v); GLAPI void APIENTRY glWindowPos2f (GLfloat x, GLfloat y); GLAPI void APIENTRY glWindowPos2fv (const GLfloat *v); GLAPI void APIENTRY glWindowPos2i (GLint x, GLint y); GLAPI void APIENTRY glWindowPos2iv (const GLint *v); GLAPI void APIENTRY glWindowPos2s (GLshort x, GLshort y); GLAPI void APIENTRY glWindowPos2sv (const GLshort *v); GLAPI void APIENTRY glWindowPos3d (GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glWindowPos3dv (const GLdouble *v); GLAPI void APIENTRY glWindowPos3f (GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glWindowPos3fv (const GLfloat *v); GLAPI void APIENTRY glWindowPos3i (GLint x, GLint y, GLint z); GLAPI void APIENTRY glWindowPos3iv (const GLint *v); GLAPI void APIENTRY glWindowPos3s (GLshort x, GLshort y, GLshort z); GLAPI void APIENTRY glWindowPos3sv (const GLshort *v); GLAPI void APIENTRY glBlendColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); GLAPI void APIENTRY glBlendEquation (GLenum mode); #endif #endif /* GL_VERSION_1_4 */ #ifndef GL_VERSION_1_5 #define GL_VERSION_1_5 1 typedef khronos_ssize_t GLsizeiptr; typedef khronos_intptr_t GLintptr; #define GL_BUFFER_SIZE 0x8764 #define GL_BUFFER_USAGE 0x8765 #define GL_QUERY_COUNTER_BITS 0x8864 #define GL_CURRENT_QUERY 0x8865 #define GL_QUERY_RESULT 0x8866 #define GL_QUERY_RESULT_AVAILABLE 0x8867 #define GL_ARRAY_BUFFER 0x8892 #define GL_ELEMENT_ARRAY_BUFFER 0x8893 #define GL_ARRAY_BUFFER_BINDING 0x8894 #define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 #define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F #define GL_READ_ONLY 0x88B8 #define GL_WRITE_ONLY 0x88B9 #define GL_READ_WRITE 0x88BA #define GL_BUFFER_ACCESS 0x88BB #define GL_BUFFER_MAPPED 0x88BC #define GL_BUFFER_MAP_POINTER 0x88BD #define GL_STREAM_DRAW 0x88E0 #define GL_STREAM_READ 0x88E1 #define GL_STREAM_COPY 0x88E2 #define GL_STATIC_DRAW 0x88E4 #define GL_STATIC_READ 0x88E5 #define GL_STATIC_COPY 0x88E6 #define GL_DYNAMIC_DRAW 0x88E8 #define GL_DYNAMIC_READ 0x88E9 #define GL_DYNAMIC_COPY 0x88EA #define GL_SAMPLES_PASSED 0x8914 #define GL_SRC1_ALPHA 0x8589 #define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 #define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 #define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 #define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 #define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A #define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B #define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C #define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D #define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E #define GL_FOG_COORD_SRC 0x8450 #define GL_FOG_COORD 0x8451 #define GL_CURRENT_FOG_COORD 0x8453 #define GL_FOG_COORD_ARRAY_TYPE 0x8454 #define GL_FOG_COORD_ARRAY_STRIDE 0x8455 #define GL_FOG_COORD_ARRAY_POINTER 0x8456 #define GL_FOG_COORD_ARRAY 0x8457 #define GL_FOG_COORD_ARRAY_BUFFER_BINDING 0x889D #define GL_SRC0_RGB 0x8580 #define GL_SRC1_RGB 0x8581 #define GL_SRC2_RGB 0x8582 #define GL_SRC0_ALPHA 0x8588 #define GL_SRC2_ALPHA 0x858A typedef void (APIENTRYP PFNGLGENQUERIESPROC) (GLsizei n, GLuint *ids); typedef void (APIENTRYP PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint *ids); typedef GLboolean (APIENTRYP PFNGLISQUERYPROC) (GLuint id); typedef void (APIENTRYP PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); typedef void (APIENTRYP PFNGLENDQUERYPROC) (GLenum target); typedef void (APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage); typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, void *data); typedef void *(APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target); typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, void **params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGenQueries (GLsizei n, GLuint *ids); GLAPI void APIENTRY glDeleteQueries (GLsizei n, const GLuint *ids); GLAPI GLboolean APIENTRY glIsQuery (GLuint id); GLAPI void APIENTRY glBeginQuery (GLenum target, GLuint id); GLAPI void APIENTRY glEndQuery (GLenum target); GLAPI void APIENTRY glGetQueryiv (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetQueryObjectiv (GLuint id, GLenum pname, GLint *params); GLAPI void APIENTRY glGetQueryObjectuiv (GLuint id, GLenum pname, GLuint *params); GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer); GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers); GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers); GLAPI GLboolean APIENTRY glIsBuffer (GLuint buffer); GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage); GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); GLAPI void APIENTRY glGetBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, void *data); GLAPI void *APIENTRY glMapBuffer (GLenum target, GLenum access); GLAPI GLboolean APIENTRY glUnmapBuffer (GLenum target); GLAPI void APIENTRY glGetBufferParameteriv (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetBufferPointerv (GLenum target, GLenum pname, void **params); #endif #endif /* GL_VERSION_1_5 */ #ifndef GL_VERSION_2_0 #define GL_VERSION_2_0 1 typedef char GLchar; #define GL_BLEND_EQUATION_RGB 0x8009 #define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 #define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 #define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 #define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 #define GL_CURRENT_VERTEX_ATTRIB 0x8626 #define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 #define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 #define GL_STENCIL_BACK_FUNC 0x8800 #define GL_STENCIL_BACK_FAIL 0x8801 #define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 #define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 #define GL_MAX_DRAW_BUFFERS 0x8824 #define GL_DRAW_BUFFER0 0x8825 #define GL_DRAW_BUFFER1 0x8826 #define GL_DRAW_BUFFER2 0x8827 #define GL_DRAW_BUFFER3 0x8828 #define GL_DRAW_BUFFER4 0x8829 #define GL_DRAW_BUFFER5 0x882A #define GL_DRAW_BUFFER6 0x882B #define GL_DRAW_BUFFER7 0x882C #define GL_DRAW_BUFFER8 0x882D #define GL_DRAW_BUFFER9 0x882E #define GL_DRAW_BUFFER10 0x882F #define GL_DRAW_BUFFER11 0x8830 #define GL_DRAW_BUFFER12 0x8831 #define GL_DRAW_BUFFER13 0x8832 #define GL_DRAW_BUFFER14 0x8833 #define GL_DRAW_BUFFER15 0x8834 #define GL_BLEND_EQUATION_ALPHA 0x883D #define GL_MAX_VERTEX_ATTRIBS 0x8869 #define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A #define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 #define GL_FRAGMENT_SHADER 0x8B30 #define GL_VERTEX_SHADER 0x8B31 #define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 #define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A #define GL_MAX_VARYING_FLOATS 0x8B4B #define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D #define GL_SHADER_TYPE 0x8B4F #define GL_FLOAT_VEC2 0x8B50 #define GL_FLOAT_VEC3 0x8B51 #define GL_FLOAT_VEC4 0x8B52 #define GL_INT_VEC2 0x8B53 #define GL_INT_VEC3 0x8B54 #define GL_INT_VEC4 0x8B55 #define GL_BOOL 0x8B56 #define GL_BOOL_VEC2 0x8B57 #define GL_BOOL_VEC3 0x8B58 #define GL_BOOL_VEC4 0x8B59 #define GL_FLOAT_MAT2 0x8B5A #define GL_FLOAT_MAT3 0x8B5B #define GL_FLOAT_MAT4 0x8B5C #define GL_SAMPLER_1D 0x8B5D #define GL_SAMPLER_2D 0x8B5E #define GL_SAMPLER_3D 0x8B5F #define GL_SAMPLER_CUBE 0x8B60 #define GL_SAMPLER_1D_SHADOW 0x8B61 #define GL_SAMPLER_2D_SHADOW 0x8B62 #define GL_DELETE_STATUS 0x8B80 #define GL_COMPILE_STATUS 0x8B81 #define GL_LINK_STATUS 0x8B82 #define GL_VALIDATE_STATUS 0x8B83 #define GL_INFO_LOG_LENGTH 0x8B84 #define GL_ATTACHED_SHADERS 0x8B85 #define GL_ACTIVE_UNIFORMS 0x8B86 #define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 #define GL_SHADER_SOURCE_LENGTH 0x8B88 #define GL_ACTIVE_ATTRIBUTES 0x8B89 #define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A #define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B #define GL_SHADING_LANGUAGE_VERSION 0x8B8C #define GL_CURRENT_PROGRAM 0x8B8D #define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 #define GL_LOWER_LEFT 0x8CA1 #define GL_UPPER_LEFT 0x8CA2 #define GL_STENCIL_BACK_REF 0x8CA3 #define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 #define GL_STENCIL_BACK_WRITEMASK 0x8CA5 #define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 #define GL_POINT_SPRITE 0x8861 #define GL_COORD_REPLACE 0x8862 #define GL_MAX_TEXTURE_COORDS 0x8871 typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); typedef void (APIENTRYP PFNGLDRAWBUFFERSPROC) (GLsizei n, const GLenum *bufs); typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC) (GLenum face, GLenum func, GLint ref, GLuint mask); typedef void (APIENTRYP PFNGLSTENCILMASKSEPARATEPROC) (GLenum face, GLuint mask); typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar *name); typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); typedef void (APIENTRYP PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); typedef void (APIENTRYP PFNGLGETATTACHEDSHADERSPROC) (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); typedef void (APIENTRYP PFNGLGETSHADERSOURCEPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); typedef void (APIENTRYP PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat *params); typedef void (APIENTRYP PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVPROC) (GLuint index, GLenum pname, GLdouble *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVPROC) (GLuint index, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, void **pointer); typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); typedef GLboolean (APIENTRYP PFNGLISSHADERPROC) (GLuint shader); typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); typedef void (APIENTRYP PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); typedef void (APIENTRYP PFNGLUNIFORM2IPROC) (GLint location, GLint v0, GLint v1); typedef void (APIENTRYP PFNGLUNIFORM3IPROC) (GLint location, GLint v0, GLint v1, GLint v2); typedef void (APIENTRYP PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); typedef void (APIENTRYP PFNGLUNIFORM1FVPROC) (GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORM2FVPROC) (GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORM1IVPROC) (GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLUNIFORM2IVPROC) (GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLUNIFORM3IVPROC) (GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLUNIFORM4IVPROC) (GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC) (GLuint program); typedef void (APIENTRYP PFNGLVERTEXATTRIB1DPROC) (GLuint index, GLdouble x); typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB1FPROC) (GLuint index, GLfloat x); typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB1SPROC) (GLuint index, GLshort x); typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB2DPROC) (GLuint index, GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB2FPROC) (GLuint index, GLfloat x, GLfloat y); typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB2SPROC) (GLuint index, GLshort x, GLshort y); typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB3FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB3SPROC) (GLuint index, GLshort x, GLshort y, GLshort z); typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVPROC) (GLuint index, const GLbyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC) (GLuint index, const GLubyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC) (GLuint index, const GLushort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVPROC) (GLuint index, const GLbyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4SPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVPROC) (GLuint index, const GLubyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVPROC) (GLuint index, const GLushort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha); GLAPI void APIENTRY glDrawBuffers (GLsizei n, const GLenum *bufs); GLAPI void APIENTRY glStencilOpSeparate (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); GLAPI void APIENTRY glStencilFuncSeparate (GLenum face, GLenum func, GLint ref, GLuint mask); GLAPI void APIENTRY glStencilMaskSeparate (GLenum face, GLuint mask); GLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader); GLAPI void APIENTRY glBindAttribLocation (GLuint program, GLuint index, const GLchar *name); GLAPI void APIENTRY glCompileShader (GLuint shader); GLAPI GLuint APIENTRY glCreateProgram (void); GLAPI GLuint APIENTRY glCreateShader (GLenum type); GLAPI void APIENTRY glDeleteProgram (GLuint program); GLAPI void APIENTRY glDeleteShader (GLuint shader); GLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader); GLAPI void APIENTRY glDisableVertexAttribArray (GLuint index); GLAPI void APIENTRY glEnableVertexAttribArray (GLuint index); GLAPI void APIENTRY glGetActiveAttrib (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); GLAPI void APIENTRY glGetActiveUniform (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); GLAPI void APIENTRY glGetAttachedShaders (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); GLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name); GLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params); GLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); GLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params); GLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); GLAPI void APIENTRY glGetShaderSource (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name); GLAPI void APIENTRY glGetUniformfv (GLuint program, GLint location, GLfloat *params); GLAPI void APIENTRY glGetUniformiv (GLuint program, GLint location, GLint *params); GLAPI void APIENTRY glGetVertexAttribdv (GLuint index, GLenum pname, GLdouble *params); GLAPI void APIENTRY glGetVertexAttribfv (GLuint index, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params); GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, void **pointer); GLAPI GLboolean APIENTRY glIsProgram (GLuint program); GLAPI GLboolean APIENTRY glIsShader (GLuint shader); GLAPI void APIENTRY glLinkProgram (GLuint program); GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); GLAPI void APIENTRY glUseProgram (GLuint program); GLAPI void APIENTRY glUniform1f (GLint location, GLfloat v0); GLAPI void APIENTRY glUniform2f (GLint location, GLfloat v0, GLfloat v1); GLAPI void APIENTRY glUniform3f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); GLAPI void APIENTRY glUniform4f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); GLAPI void APIENTRY glUniform1i (GLint location, GLint v0); GLAPI void APIENTRY glUniform2i (GLint location, GLint v0, GLint v1); GLAPI void APIENTRY glUniform3i (GLint location, GLint v0, GLint v1, GLint v2); GLAPI void APIENTRY glUniform4i (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); GLAPI void APIENTRY glUniform1fv (GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glUniform2fv (GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glUniform3fv (GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glUniform4fv (GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glUniform1iv (GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glUniform2iv (GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glUniform3iv (GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glUniform4iv (GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glUniformMatrix2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glUniformMatrix3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glValidateProgram (GLuint program); GLAPI void APIENTRY glVertexAttrib1d (GLuint index, GLdouble x); GLAPI void APIENTRY glVertexAttrib1dv (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib1f (GLuint index, GLfloat x); GLAPI void APIENTRY glVertexAttrib1fv (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib1s (GLuint index, GLshort x); GLAPI void APIENTRY glVertexAttrib1sv (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib2d (GLuint index, GLdouble x, GLdouble y); GLAPI void APIENTRY glVertexAttrib2dv (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib2f (GLuint index, GLfloat x, GLfloat y); GLAPI void APIENTRY glVertexAttrib2fv (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib2s (GLuint index, GLshort x, GLshort y); GLAPI void APIENTRY glVertexAttrib2sv (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glVertexAttrib3dv (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib3f (GLuint index, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glVertexAttrib3fv (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib3s (GLuint index, GLshort x, GLshort y, GLshort z); GLAPI void APIENTRY glVertexAttrib3sv (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib4Nbv (GLuint index, const GLbyte *v); GLAPI void APIENTRY glVertexAttrib4Niv (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttrib4Nsv (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib4Nub (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); GLAPI void APIENTRY glVertexAttrib4Nubv (GLuint index, const GLubyte *v); GLAPI void APIENTRY glVertexAttrib4Nuiv (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttrib4Nusv (GLuint index, const GLushort *v); GLAPI void APIENTRY glVertexAttrib4bv (GLuint index, const GLbyte *v); GLAPI void APIENTRY glVertexAttrib4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glVertexAttrib4dv (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib4f (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glVertexAttrib4fv (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib4iv (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttrib4s (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); GLAPI void APIENTRY glVertexAttrib4sv (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib4ubv (GLuint index, const GLubyte *v); GLAPI void APIENTRY glVertexAttrib4uiv (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttrib4usv (GLuint index, const GLushort *v); GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); #endif #endif /* GL_VERSION_2_0 */ #ifndef GL_VERSION_2_1 #define GL_VERSION_2_1 1 #define GL_PIXEL_PACK_BUFFER 0x88EB #define GL_PIXEL_UNPACK_BUFFER 0x88EC #define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED #define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF #define GL_FLOAT_MAT2x3 0x8B65 #define GL_FLOAT_MAT2x4 0x8B66 #define GL_FLOAT_MAT3x2 0x8B67 #define GL_FLOAT_MAT3x4 0x8B68 #define GL_FLOAT_MAT4x2 0x8B69 #define GL_FLOAT_MAT4x3 0x8B6A #define GL_SRGB 0x8C40 #define GL_SRGB8 0x8C41 #define GL_SRGB_ALPHA 0x8C42 #define GL_SRGB8_ALPHA8 0x8C43 #define GL_COMPRESSED_SRGB 0x8C48 #define GL_COMPRESSED_SRGB_ALPHA 0x8C49 #define GL_CURRENT_RASTER_SECONDARY_COLOR 0x845F #define GL_SLUMINANCE_ALPHA 0x8C44 #define GL_SLUMINANCE8_ALPHA8 0x8C45 #define GL_SLUMINANCE 0x8C46 #define GL_SLUMINANCE8 0x8C47 #define GL_COMPRESSED_SLUMINANCE 0x8C4A #define GL_COMPRESSED_SLUMINANCE_ALPHA 0x8C4B typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glUniformMatrix2x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glUniformMatrix3x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glUniformMatrix2x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glUniformMatrix4x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glUniformMatrix3x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glUniformMatrix4x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); #endif #endif /* GL_VERSION_2_1 */ #ifndef GL_VERSION_3_0 #define GL_VERSION_3_0 1 typedef khronos_uint16_t GLhalf; #define GL_COMPARE_REF_TO_TEXTURE 0x884E #define GL_CLIP_DISTANCE0 0x3000 #define GL_CLIP_DISTANCE1 0x3001 #define GL_CLIP_DISTANCE2 0x3002 #define GL_CLIP_DISTANCE3 0x3003 #define GL_CLIP_DISTANCE4 0x3004 #define GL_CLIP_DISTANCE5 0x3005 #define GL_CLIP_DISTANCE6 0x3006 #define GL_CLIP_DISTANCE7 0x3007 #define GL_MAX_CLIP_DISTANCES 0x0D32 #define GL_MAJOR_VERSION 0x821B #define GL_MINOR_VERSION 0x821C #define GL_NUM_EXTENSIONS 0x821D #define GL_CONTEXT_FLAGS 0x821E #define GL_COMPRESSED_RED 0x8225 #define GL_COMPRESSED_RG 0x8226 #define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x00000001 #define GL_RGBA32F 0x8814 #define GL_RGB32F 0x8815 #define GL_RGBA16F 0x881A #define GL_RGB16F 0x881B #define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD #define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF #define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 #define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 #define GL_CLAMP_READ_COLOR 0x891C #define GL_FIXED_ONLY 0x891D #define GL_MAX_VARYING_COMPONENTS 0x8B4B #define GL_TEXTURE_1D_ARRAY 0x8C18 #define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 #define GL_TEXTURE_2D_ARRAY 0x8C1A #define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B #define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C #define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D #define GL_R11F_G11F_B10F 0x8C3A #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B #define GL_RGB9_E5 0x8C3D #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E #define GL_TEXTURE_SHARED_SIZE 0x8C3F #define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 #define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F #define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 #define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 #define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 #define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 #define GL_PRIMITIVES_GENERATED 0x8C87 #define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 #define GL_RASTERIZER_DISCARD 0x8C89 #define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A #define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B #define GL_INTERLEAVED_ATTRIBS 0x8C8C #define GL_SEPARATE_ATTRIBS 0x8C8D #define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E #define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F #define GL_RGBA32UI 0x8D70 #define GL_RGB32UI 0x8D71 #define GL_RGBA16UI 0x8D76 #define GL_RGB16UI 0x8D77 #define GL_RGBA8UI 0x8D7C #define GL_RGB8UI 0x8D7D #define GL_RGBA32I 0x8D82 #define GL_RGB32I 0x8D83 #define GL_RGBA16I 0x8D88 #define GL_RGB16I 0x8D89 #define GL_RGBA8I 0x8D8E #define GL_RGB8I 0x8D8F #define GL_RED_INTEGER 0x8D94 #define GL_GREEN_INTEGER 0x8D95 #define GL_BLUE_INTEGER 0x8D96 #define GL_RGB_INTEGER 0x8D98 #define GL_RGBA_INTEGER 0x8D99 #define GL_BGR_INTEGER 0x8D9A #define GL_BGRA_INTEGER 0x8D9B #define GL_SAMPLER_1D_ARRAY 0x8DC0 #define GL_SAMPLER_2D_ARRAY 0x8DC1 #define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 #define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 #define GL_SAMPLER_CUBE_SHADOW 0x8DC5 #define GL_UNSIGNED_INT_VEC2 0x8DC6 #define GL_UNSIGNED_INT_VEC3 0x8DC7 #define GL_UNSIGNED_INT_VEC4 0x8DC8 #define GL_INT_SAMPLER_1D 0x8DC9 #define GL_INT_SAMPLER_2D 0x8DCA #define GL_INT_SAMPLER_3D 0x8DCB #define GL_INT_SAMPLER_CUBE 0x8DCC #define GL_INT_SAMPLER_1D_ARRAY 0x8DCE #define GL_INT_SAMPLER_2D_ARRAY 0x8DCF #define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 #define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 #define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 #define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 #define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 #define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 #define GL_QUERY_WAIT 0x8E13 #define GL_QUERY_NO_WAIT 0x8E14 #define GL_QUERY_BY_REGION_WAIT 0x8E15 #define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 #define GL_BUFFER_ACCESS_FLAGS 0x911F #define GL_BUFFER_MAP_LENGTH 0x9120 #define GL_BUFFER_MAP_OFFSET 0x9121 #define GL_DEPTH_COMPONENT32F 0x8CAC #define GL_DEPTH32F_STENCIL8 0x8CAD #define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD #define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 #define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 #define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 #define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 #define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 #define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 #define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 #define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 #define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 #define GL_FRAMEBUFFER_DEFAULT 0x8218 #define GL_FRAMEBUFFER_UNDEFINED 0x8219 #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A #define GL_MAX_RENDERBUFFER_SIZE 0x84E8 #define GL_DEPTH_STENCIL 0x84F9 #define GL_UNSIGNED_INT_24_8 0x84FA #define GL_DEPTH24_STENCIL8 0x88F0 #define GL_TEXTURE_STENCIL_SIZE 0x88F1 #define GL_TEXTURE_RED_TYPE 0x8C10 #define GL_TEXTURE_GREEN_TYPE 0x8C11 #define GL_TEXTURE_BLUE_TYPE 0x8C12 #define GL_TEXTURE_ALPHA_TYPE 0x8C13 #define GL_TEXTURE_DEPTH_TYPE 0x8C16 #define GL_UNSIGNED_NORMALIZED 0x8C17 #define GL_FRAMEBUFFER_BINDING 0x8CA6 #define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 #define GL_RENDERBUFFER_BINDING 0x8CA7 #define GL_READ_FRAMEBUFFER 0x8CA8 #define GL_DRAW_FRAMEBUFFER 0x8CA9 #define GL_READ_FRAMEBUFFER_BINDING 0x8CAA #define GL_RENDERBUFFER_SAMPLES 0x8CAB #define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 #define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 #define GL_FRAMEBUFFER_COMPLETE 0x8CD5 #define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 #define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 #define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB #define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC #define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD #define GL_MAX_COLOR_ATTACHMENTS 0x8CDF #define GL_COLOR_ATTACHMENT0 0x8CE0 #define GL_COLOR_ATTACHMENT1 0x8CE1 #define GL_COLOR_ATTACHMENT2 0x8CE2 #define GL_COLOR_ATTACHMENT3 0x8CE3 #define GL_COLOR_ATTACHMENT4 0x8CE4 #define GL_COLOR_ATTACHMENT5 0x8CE5 #define GL_COLOR_ATTACHMENT6 0x8CE6 #define GL_COLOR_ATTACHMENT7 0x8CE7 #define GL_COLOR_ATTACHMENT8 0x8CE8 #define GL_COLOR_ATTACHMENT9 0x8CE9 #define GL_COLOR_ATTACHMENT10 0x8CEA #define GL_COLOR_ATTACHMENT11 0x8CEB #define GL_COLOR_ATTACHMENT12 0x8CEC #define GL_COLOR_ATTACHMENT13 0x8CED #define GL_COLOR_ATTACHMENT14 0x8CEE #define GL_COLOR_ATTACHMENT15 0x8CEF #define GL_COLOR_ATTACHMENT16 0x8CF0 #define GL_COLOR_ATTACHMENT17 0x8CF1 #define GL_COLOR_ATTACHMENT18 0x8CF2 #define GL_COLOR_ATTACHMENT19 0x8CF3 #define GL_COLOR_ATTACHMENT20 0x8CF4 #define GL_COLOR_ATTACHMENT21 0x8CF5 #define GL_COLOR_ATTACHMENT22 0x8CF6 #define GL_COLOR_ATTACHMENT23 0x8CF7 #define GL_COLOR_ATTACHMENT24 0x8CF8 #define GL_COLOR_ATTACHMENT25 0x8CF9 #define GL_COLOR_ATTACHMENT26 0x8CFA #define GL_COLOR_ATTACHMENT27 0x8CFB #define GL_COLOR_ATTACHMENT28 0x8CFC #define GL_COLOR_ATTACHMENT29 0x8CFD #define GL_COLOR_ATTACHMENT30 0x8CFE #define GL_COLOR_ATTACHMENT31 0x8CFF #define GL_DEPTH_ATTACHMENT 0x8D00 #define GL_STENCIL_ATTACHMENT 0x8D20 #define GL_FRAMEBUFFER 0x8D40 #define GL_RENDERBUFFER 0x8D41 #define GL_RENDERBUFFER_WIDTH 0x8D42 #define GL_RENDERBUFFER_HEIGHT 0x8D43 #define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 #define GL_STENCIL_INDEX1 0x8D46 #define GL_STENCIL_INDEX4 0x8D47 #define GL_STENCIL_INDEX8 0x8D48 #define GL_STENCIL_INDEX16 0x8D49 #define GL_RENDERBUFFER_RED_SIZE 0x8D50 #define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 #define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 #define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 #define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 #define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 #define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 #define GL_MAX_SAMPLES 0x8D57 #define GL_INDEX 0x8222 #define GL_TEXTURE_LUMINANCE_TYPE 0x8C14 #define GL_TEXTURE_INTENSITY_TYPE 0x8C15 #define GL_FRAMEBUFFER_SRGB 0x8DB9 #define GL_HALF_FLOAT 0x140B #define GL_MAP_READ_BIT 0x0001 #define GL_MAP_WRITE_BIT 0x0002 #define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 #define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 #define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 #define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 #define GL_COMPRESSED_RED_RGTC1 0x8DBB #define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC #define GL_COMPRESSED_RG_RGTC2 0x8DBD #define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE #define GL_RG 0x8227 #define GL_RG_INTEGER 0x8228 #define GL_R8 0x8229 #define GL_R16 0x822A #define GL_RG8 0x822B #define GL_RG16 0x822C #define GL_R16F 0x822D #define GL_R32F 0x822E #define GL_RG16F 0x822F #define GL_RG32F 0x8230 #define GL_R8I 0x8231 #define GL_R8UI 0x8232 #define GL_R16I 0x8233 #define GL_R16UI 0x8234 #define GL_R32I 0x8235 #define GL_R32UI 0x8236 #define GL_RG8I 0x8237 #define GL_RG8UI 0x8238 #define GL_RG16I 0x8239 #define GL_RG16UI 0x823A #define GL_RG32I 0x823B #define GL_RG32UI 0x823C #define GL_VERTEX_ARRAY_BINDING 0x85B5 #define GL_CLAMP_VERTEX_COLOR 0x891A #define GL_CLAMP_FRAGMENT_COLOR 0x891B #define GL_ALPHA_INTEGER 0x8D97 typedef void (APIENTRYP PFNGLCOLORMASKIPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data); typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data); typedef void (APIENTRYP PFNGLENABLEIPROC) (GLenum target, GLuint index); typedef void (APIENTRYP PFNGLDISABLEIPROC) (GLenum target, GLuint index); typedef GLboolean (APIENTRYP PFNGLISENABLEDIPROC) (GLenum target, GLuint index); typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKPROC) (GLenum primitiveMode); typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC) (void); typedef void (APIENTRYP PFNGLBINDBUFFERRANGEPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer); typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSPROC) (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); typedef void (APIENTRYP PFNGLCLAMPCOLORPROC) (GLenum target, GLenum clamp); typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERPROC) (GLuint id, GLenum mode); typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC) (void); typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVPROC) (GLuint index, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVPROC) (GLuint index, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IPROC) (GLuint index, GLint x); typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IPROC) (GLuint index, GLint x, GLint y); typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IPROC) (GLuint index, GLint x, GLint y, GLint z); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIPROC) (GLuint index, GLuint x); typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIPROC) (GLuint index, GLuint x, GLuint y); typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVPROC) (GLuint index, const GLbyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVPROC) (GLuint index, const GLubyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVPROC) (GLuint index, const GLushort *v); typedef void (APIENTRYP PFNGLGETUNIFORMUIVPROC) (GLuint program, GLint location, GLuint *params); typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONPROC) (GLuint program, GLuint color, const GLchar *name); typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONPROC) (GLuint program, const GLchar *name); typedef void (APIENTRYP PFNGLUNIFORM1UIPROC) (GLint location, GLuint v0); typedef void (APIENTRYP PFNGLUNIFORM2UIPROC) (GLint location, GLuint v0, GLuint v1); typedef void (APIENTRYP PFNGLUNIFORM3UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); typedef void (APIENTRYP PFNGLUNIFORM4UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); typedef void (APIENTRYP PFNGLUNIFORM1UIVPROC) (GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLUNIFORM2UIVPROC) (GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLUNIFORM3UIVPROC) (GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLUNIFORM4UIVPROC) (GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, const GLuint *params); typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLCLEARBUFFERIVPROC) (GLenum buffer, GLint drawbuffer, const GLint *value); typedef void (APIENTRYP PFNGLCLEARBUFFERUIVPROC) (GLenum buffer, GLint drawbuffer, const GLuint *value); typedef void (APIENTRYP PFNGLCLEARBUFFERFVPROC) (GLenum buffer, GLint drawbuffer, const GLfloat *value); typedef void (APIENTRYP PFNGLCLEARBUFFERFIPROC) (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index); typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFERPROC) (GLuint renderbuffer); typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer); typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers); typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFERPROC) (GLuint framebuffer); typedef void (APIENTRYP PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer); typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers); typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers); typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGENERATEMIPMAPPROC) (GLenum target); typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); typedef void *(APIENTRYP PFNGLMAPBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length); typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array); typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYPROC) (GLuint array); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glColorMaski (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); GLAPI void APIENTRY glGetBooleani_v (GLenum target, GLuint index, GLboolean *data); GLAPI void APIENTRY glGetIntegeri_v (GLenum target, GLuint index, GLint *data); GLAPI void APIENTRY glEnablei (GLenum target, GLuint index); GLAPI void APIENTRY glDisablei (GLenum target, GLuint index); GLAPI GLboolean APIENTRY glIsEnabledi (GLenum target, GLuint index); GLAPI void APIENTRY glBeginTransformFeedback (GLenum primitiveMode); GLAPI void APIENTRY glEndTransformFeedback (void); GLAPI void APIENTRY glBindBufferRange (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); GLAPI void APIENTRY glBindBufferBase (GLenum target, GLuint index, GLuint buffer); GLAPI void APIENTRY glTransformFeedbackVaryings (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); GLAPI void APIENTRY glGetTransformFeedbackVarying (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); GLAPI void APIENTRY glClampColor (GLenum target, GLenum clamp); GLAPI void APIENTRY glBeginConditionalRender (GLuint id, GLenum mode); GLAPI void APIENTRY glEndConditionalRender (void); GLAPI void APIENTRY glVertexAttribIPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glGetVertexAttribIiv (GLuint index, GLenum pname, GLint *params); GLAPI void APIENTRY glGetVertexAttribIuiv (GLuint index, GLenum pname, GLuint *params); GLAPI void APIENTRY glVertexAttribI1i (GLuint index, GLint x); GLAPI void APIENTRY glVertexAttribI2i (GLuint index, GLint x, GLint y); GLAPI void APIENTRY glVertexAttribI3i (GLuint index, GLint x, GLint y, GLint z); GLAPI void APIENTRY glVertexAttribI4i (GLuint index, GLint x, GLint y, GLint z, GLint w); GLAPI void APIENTRY glVertexAttribI1ui (GLuint index, GLuint x); GLAPI void APIENTRY glVertexAttribI2ui (GLuint index, GLuint x, GLuint y); GLAPI void APIENTRY glVertexAttribI3ui (GLuint index, GLuint x, GLuint y, GLuint z); GLAPI void APIENTRY glVertexAttribI4ui (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); GLAPI void APIENTRY glVertexAttribI1iv (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttribI2iv (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttribI3iv (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttribI4iv (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttribI1uiv (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttribI2uiv (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttribI3uiv (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttribI4uiv (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttribI4bv (GLuint index, const GLbyte *v); GLAPI void APIENTRY glVertexAttribI4sv (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttribI4ubv (GLuint index, const GLubyte *v); GLAPI void APIENTRY glVertexAttribI4usv (GLuint index, const GLushort *v); GLAPI void APIENTRY glGetUniformuiv (GLuint program, GLint location, GLuint *params); GLAPI void APIENTRY glBindFragDataLocation (GLuint program, GLuint color, const GLchar *name); GLAPI GLint APIENTRY glGetFragDataLocation (GLuint program, const GLchar *name); GLAPI void APIENTRY glUniform1ui (GLint location, GLuint v0); GLAPI void APIENTRY glUniform2ui (GLint location, GLuint v0, GLuint v1); GLAPI void APIENTRY glUniform3ui (GLint location, GLuint v0, GLuint v1, GLuint v2); GLAPI void APIENTRY glUniform4ui (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); GLAPI void APIENTRY glUniform1uiv (GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glUniform2uiv (GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glUniform3uiv (GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glUniform4uiv (GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glTexParameterIiv (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glTexParameterIuiv (GLenum target, GLenum pname, const GLuint *params); GLAPI void APIENTRY glGetTexParameterIiv (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetTexParameterIuiv (GLenum target, GLenum pname, GLuint *params); GLAPI void APIENTRY glClearBufferiv (GLenum buffer, GLint drawbuffer, const GLint *value); GLAPI void APIENTRY glClearBufferuiv (GLenum buffer, GLint drawbuffer, const GLuint *value); GLAPI void APIENTRY glClearBufferfv (GLenum buffer, GLint drawbuffer, const GLfloat *value); GLAPI void APIENTRY glClearBufferfi (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); GLAPI const GLubyte *APIENTRY glGetStringi (GLenum name, GLuint index); GLAPI GLboolean APIENTRY glIsRenderbuffer (GLuint renderbuffer); GLAPI void APIENTRY glBindRenderbuffer (GLenum target, GLuint renderbuffer); GLAPI void APIENTRY glDeleteRenderbuffers (GLsizei n, const GLuint *renderbuffers); GLAPI void APIENTRY glGenRenderbuffers (GLsizei n, GLuint *renderbuffers); GLAPI void APIENTRY glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glGetRenderbufferParameteriv (GLenum target, GLenum pname, GLint *params); GLAPI GLboolean APIENTRY glIsFramebuffer (GLuint framebuffer); GLAPI void APIENTRY glBindFramebuffer (GLenum target, GLuint framebuffer); GLAPI void APIENTRY glDeleteFramebuffers (GLsizei n, const GLuint *framebuffers); GLAPI void APIENTRY glGenFramebuffers (GLsizei n, GLuint *framebuffers); GLAPI GLenum APIENTRY glCheckFramebufferStatus (GLenum target); GLAPI void APIENTRY glFramebufferTexture1D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); GLAPI void APIENTRY glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); GLAPI void APIENTRY glFramebufferTexture3D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); GLAPI void APIENTRY glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); GLAPI void APIENTRY glGetFramebufferAttachmentParameteriv (GLenum target, GLenum attachment, GLenum pname, GLint *params); GLAPI void APIENTRY glGenerateMipmap (GLenum target); GLAPI void APIENTRY glBlitFramebuffer (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); GLAPI void APIENTRY glRenderbufferStorageMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glFramebufferTextureLayer (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); GLAPI void *APIENTRY glMapBufferRange (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); GLAPI void APIENTRY glFlushMappedBufferRange (GLenum target, GLintptr offset, GLsizeiptr length); GLAPI void APIENTRY glBindVertexArray (GLuint array); GLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays); GLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays); GLAPI GLboolean APIENTRY glIsVertexArray (GLuint array); #endif #endif /* GL_VERSION_3_0 */ #ifndef GL_VERSION_3_1 #define GL_VERSION_3_1 1 #define GL_SAMPLER_2D_RECT 0x8B63 #define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 #define GL_SAMPLER_BUFFER 0x8DC2 #define GL_INT_SAMPLER_2D_RECT 0x8DCD #define GL_INT_SAMPLER_BUFFER 0x8DD0 #define GL_UNSIGNED_INT_SAMPLER_2D_RECT 0x8DD5 #define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 #define GL_TEXTURE_BUFFER 0x8C2A #define GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B #define GL_TEXTURE_BINDING_BUFFER 0x8C2C #define GL_TEXTURE_BUFFER_DATA_STORE_BINDING 0x8C2D #define GL_TEXTURE_RECTANGLE 0x84F5 #define GL_TEXTURE_BINDING_RECTANGLE 0x84F6 #define GL_PROXY_TEXTURE_RECTANGLE 0x84F7 #define GL_MAX_RECTANGLE_TEXTURE_SIZE 0x84F8 #define GL_R8_SNORM 0x8F94 #define GL_RG8_SNORM 0x8F95 #define GL_RGB8_SNORM 0x8F96 #define GL_RGBA8_SNORM 0x8F97 #define GL_R16_SNORM 0x8F98 #define GL_RG16_SNORM 0x8F99 #define GL_RGB16_SNORM 0x8F9A #define GL_RGBA16_SNORM 0x8F9B #define GL_SIGNED_NORMALIZED 0x8F9C #define GL_PRIMITIVE_RESTART 0x8F9D #define GL_PRIMITIVE_RESTART_INDEX 0x8F9E #define GL_COPY_READ_BUFFER 0x8F36 #define GL_COPY_WRITE_BUFFER 0x8F37 #define GL_UNIFORM_BUFFER 0x8A11 #define GL_UNIFORM_BUFFER_BINDING 0x8A28 #define GL_UNIFORM_BUFFER_START 0x8A29 #define GL_UNIFORM_BUFFER_SIZE 0x8A2A #define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B #define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C #define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D #define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E #define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F #define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 #define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 #define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 #define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 #define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 #define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 #define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 #define GL_UNIFORM_TYPE 0x8A37 #define GL_UNIFORM_SIZE 0x8A38 #define GL_UNIFORM_NAME_LENGTH 0x8A39 #define GL_UNIFORM_BLOCK_INDEX 0x8A3A #define GL_UNIFORM_OFFSET 0x8A3B #define GL_UNIFORM_ARRAY_STRIDE 0x8A3C #define GL_UNIFORM_MATRIX_STRIDE 0x8A3D #define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E #define GL_UNIFORM_BLOCK_BINDING 0x8A3F #define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 #define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 #define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 #define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 #define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 #define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 #define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 #define GL_INVALID_INDEX 0xFFFFFFFFu typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDPROC) (GLenum mode, GLint first, GLsizei count, GLsizei instancecount); typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); typedef void (APIENTRYP PFNGLTEXBUFFERPROC) (GLenum target, GLenum internalformat, GLuint buffer); typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXPROC) (GLuint index); typedef void (APIENTRYP PFNGLCOPYBUFFERSUBDATAPROC) (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); typedef void (APIENTRYP PFNGLGETUNIFORMINDICESPROC) (GLuint program, GLsizei uniformCount, const GLchar *const*uniformNames, GLuint *uniformIndices); typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMSIVPROC) (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMNAMEPROC) (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar *uniformBlockName); typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKIVPROC) (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC) (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei instancecount); GLAPI void APIENTRY glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); GLAPI void APIENTRY glTexBuffer (GLenum target, GLenum internalformat, GLuint buffer); GLAPI void APIENTRY glPrimitiveRestartIndex (GLuint index); GLAPI void APIENTRY glCopyBufferSubData (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); GLAPI void APIENTRY glGetUniformIndices (GLuint program, GLsizei uniformCount, const GLchar *const*uniformNames, GLuint *uniformIndices); GLAPI void APIENTRY glGetActiveUniformsiv (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); GLAPI void APIENTRY glGetActiveUniformName (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); GLAPI GLuint APIENTRY glGetUniformBlockIndex (GLuint program, const GLchar *uniformBlockName); GLAPI void APIENTRY glGetActiveUniformBlockiv (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); GLAPI void APIENTRY glGetActiveUniformBlockName (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); GLAPI void APIENTRY glUniformBlockBinding (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); #endif #endif /* GL_VERSION_3_1 */ #ifndef GL_VERSION_3_2 #define GL_VERSION_3_2 1 typedef struct __GLsync *GLsync; typedef khronos_uint64_t GLuint64; typedef khronos_int64_t GLint64; #define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 #define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 #define GL_LINES_ADJACENCY 0x000A #define GL_LINE_STRIP_ADJACENCY 0x000B #define GL_TRIANGLES_ADJACENCY 0x000C #define GL_TRIANGLE_STRIP_ADJACENCY 0x000D #define GL_PROGRAM_POINT_SIZE 0x8642 #define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS 0x8C29 #define GL_FRAMEBUFFER_ATTACHMENT_LAYERED 0x8DA7 #define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS 0x8DA8 #define GL_GEOMETRY_SHADER 0x8DD9 #define GL_GEOMETRY_VERTICES_OUT 0x8916 #define GL_GEOMETRY_INPUT_TYPE 0x8917 #define GL_GEOMETRY_OUTPUT_TYPE 0x8918 #define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS 0x8DDF #define GL_MAX_GEOMETRY_OUTPUT_VERTICES 0x8DE0 #define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS 0x8DE1 #define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 #define GL_MAX_GEOMETRY_INPUT_COMPONENTS 0x9123 #define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS 0x9124 #define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125 #define GL_CONTEXT_PROFILE_MASK 0x9126 #define GL_DEPTH_CLAMP 0x864F #define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION 0x8E4C #define GL_FIRST_VERTEX_CONVENTION 0x8E4D #define GL_LAST_VERTEX_CONVENTION 0x8E4E #define GL_PROVOKING_VERTEX 0x8E4F #define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F #define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111 #define GL_OBJECT_TYPE 0x9112 #define GL_SYNC_CONDITION 0x9113 #define GL_SYNC_STATUS 0x9114 #define GL_SYNC_FLAGS 0x9115 #define GL_SYNC_FENCE 0x9116 #define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 #define GL_UNSIGNALED 0x9118 #define GL_SIGNALED 0x9119 #define GL_ALREADY_SIGNALED 0x911A #define GL_TIMEOUT_EXPIRED 0x911B #define GL_CONDITION_SATISFIED 0x911C #define GL_WAIT_FAILED 0x911D #define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull #define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 #define GL_SAMPLE_POSITION 0x8E50 #define GL_SAMPLE_MASK 0x8E51 #define GL_SAMPLE_MASK_VALUE 0x8E52 #define GL_MAX_SAMPLE_MASK_WORDS 0x8E59 #define GL_TEXTURE_2D_MULTISAMPLE 0x9100 #define GL_PROXY_TEXTURE_2D_MULTISAMPLE 0x9101 #define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 #define GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9103 #define GL_TEXTURE_BINDING_2D_MULTISAMPLE 0x9104 #define GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY 0x9105 #define GL_TEXTURE_SAMPLES 0x9106 #define GL_TEXTURE_FIXED_SAMPLE_LOCATIONS 0x9107 #define GL_SAMPLER_2D_MULTISAMPLE 0x9108 #define GL_INT_SAMPLER_2D_MULTISAMPLE 0x9109 #define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE 0x910A #define GL_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910B #define GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910C #define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910D #define GL_MAX_COLOR_TEXTURE_SAMPLES 0x910E #define GL_MAX_DEPTH_TEXTURE_SAMPLES 0x910F #define GL_MAX_INTEGER_SAMPLES 0x9110 typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); typedef void (APIENTRYP PFNGLPROVOKINGVERTEXPROC) (GLenum mode); typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC) (GLenum condition, GLbitfield flags); typedef GLboolean (APIENTRYP PFNGLISSYNCPROC) (GLsync sync); typedef void (APIENTRYP PFNGLDELETESYNCPROC) (GLsync sync); typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); typedef void (APIENTRYP PFNGLWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); typedef void (APIENTRYP PFNGLGETINTEGER64VPROC) (GLenum pname, GLint64 *data); typedef void (APIENTRYP PFNGLGETSYNCIVPROC) (GLsync sync, GLenum pname, GLsizei count, GLsizei *length, GLint *values); typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data); typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERI64VPROC) (GLenum target, GLenum pname, GLint64 *params); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVPROC) (GLenum pname, GLuint index, GLfloat *val); typedef void (APIENTRYP PFNGLSAMPLEMASKIPROC) (GLuint maskNumber, GLbitfield mask); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); GLAPI void APIENTRY glDrawRangeElementsBaseVertex (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); GLAPI void APIENTRY glDrawElementsInstancedBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); GLAPI void APIENTRY glMultiDrawElementsBaseVertex (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); GLAPI void APIENTRY glProvokingVertex (GLenum mode); GLAPI GLsync APIENTRY glFenceSync (GLenum condition, GLbitfield flags); GLAPI GLboolean APIENTRY glIsSync (GLsync sync); GLAPI void APIENTRY glDeleteSync (GLsync sync); GLAPI GLenum APIENTRY glClientWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); GLAPI void APIENTRY glWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); GLAPI void APIENTRY glGetInteger64v (GLenum pname, GLint64 *data); GLAPI void APIENTRY glGetSynciv (GLsync sync, GLenum pname, GLsizei count, GLsizei *length, GLint *values); GLAPI void APIENTRY glGetInteger64i_v (GLenum target, GLuint index, GLint64 *data); GLAPI void APIENTRY glGetBufferParameteri64v (GLenum target, GLenum pname, GLint64 *params); GLAPI void APIENTRY glFramebufferTexture (GLenum target, GLenum attachment, GLuint texture, GLint level); GLAPI void APIENTRY glTexImage2DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); GLAPI void APIENTRY glTexImage3DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); GLAPI void APIENTRY glGetMultisamplefv (GLenum pname, GLuint index, GLfloat *val); GLAPI void APIENTRY glSampleMaski (GLuint maskNumber, GLbitfield mask); #endif #endif /* GL_VERSION_3_2 */ #ifndef GL_VERSION_3_3 #define GL_VERSION_3_3 1 #define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE #define GL_SRC1_COLOR 0x88F9 #define GL_ONE_MINUS_SRC1_COLOR 0x88FA #define GL_ONE_MINUS_SRC1_ALPHA 0x88FB #define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS 0x88FC #define GL_ANY_SAMPLES_PASSED 0x8C2F #define GL_SAMPLER_BINDING 0x8919 #define GL_RGB10_A2UI 0x906F #define GL_TEXTURE_SWIZZLE_R 0x8E42 #define GL_TEXTURE_SWIZZLE_G 0x8E43 #define GL_TEXTURE_SWIZZLE_B 0x8E44 #define GL_TEXTURE_SWIZZLE_A 0x8E45 #define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 #define GL_TIME_ELAPSED 0x88BF #define GL_TIMESTAMP 0x8E28 #define GL_INT_2_10_10_10_REV 0x8D9F typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONINDEXEDPROC) (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); typedef GLint (APIENTRYP PFNGLGETFRAGDATAINDEXPROC) (GLuint program, const GLchar *name); typedef void (APIENTRYP PFNGLGENSAMPLERSPROC) (GLsizei count, GLuint *samplers); typedef void (APIENTRYP PFNGLDELETESAMPLERSPROC) (GLsizei count, const GLuint *samplers); typedef GLboolean (APIENTRYP PFNGLISSAMPLERPROC) (GLuint sampler); typedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler); typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIPROC) (GLuint sampler, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, const GLint *param); typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFPROC) (GLuint sampler, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, const GLfloat *param); typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, const GLint *param); typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, const GLuint *param); typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLQUERYCOUNTERPROC) (GLuint id, GLenum target); typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VPROC) (GLuint id, GLenum pname, GLint64 *params); typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VPROC) (GLuint id, GLenum pname, GLuint64 *params); typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORPROC) (GLuint index, GLuint divisor); typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); typedef void (APIENTRYP PFNGLVERTEXP2UIPROC) (GLenum type, GLuint value); typedef void (APIENTRYP PFNGLVERTEXP2UIVPROC) (GLenum type, const GLuint *value); typedef void (APIENTRYP PFNGLVERTEXP3UIPROC) (GLenum type, GLuint value); typedef void (APIENTRYP PFNGLVERTEXP3UIVPROC) (GLenum type, const GLuint *value); typedef void (APIENTRYP PFNGLVERTEXP4UIPROC) (GLenum type, GLuint value); typedef void (APIENTRYP PFNGLVERTEXP4UIVPROC) (GLenum type, const GLuint *value); typedef void (APIENTRYP PFNGLTEXCOORDP1UIPROC) (GLenum type, GLuint coords); typedef void (APIENTRYP PFNGLTEXCOORDP1UIVPROC) (GLenum type, const GLuint *coords); typedef void (APIENTRYP PFNGLTEXCOORDP2UIPROC) (GLenum type, GLuint coords); typedef void (APIENTRYP PFNGLTEXCOORDP2UIVPROC) (GLenum type, const GLuint *coords); typedef void (APIENTRYP PFNGLTEXCOORDP3UIPROC) (GLenum type, GLuint coords); typedef void (APIENTRYP PFNGLTEXCOORDP3UIVPROC) (GLenum type, const GLuint *coords); typedef void (APIENTRYP PFNGLTEXCOORDP4UIPROC) (GLenum type, GLuint coords); typedef void (APIENTRYP PFNGLTEXCOORDP4UIVPROC) (GLenum type, const GLuint *coords); typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIPROC) (GLenum texture, GLenum type, GLuint coords); typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIPROC) (GLenum texture, GLenum type, GLuint coords); typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIPROC) (GLenum texture, GLenum type, GLuint coords); typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIPROC) (GLenum texture, GLenum type, GLuint coords); typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); typedef void (APIENTRYP PFNGLNORMALP3UIPROC) (GLenum type, GLuint coords); typedef void (APIENTRYP PFNGLNORMALP3UIVPROC) (GLenum type, const GLuint *coords); typedef void (APIENTRYP PFNGLCOLORP3UIPROC) (GLenum type, GLuint color); typedef void (APIENTRYP PFNGLCOLORP3UIVPROC) (GLenum type, const GLuint *color); typedef void (APIENTRYP PFNGLCOLORP4UIPROC) (GLenum type, GLuint color); typedef void (APIENTRYP PFNGLCOLORP4UIVPROC) (GLenum type, const GLuint *color); typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIPROC) (GLenum type, GLuint color); typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIVPROC) (GLenum type, const GLuint *color); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBindFragDataLocationIndexed (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); GLAPI GLint APIENTRY glGetFragDataIndex (GLuint program, const GLchar *name); GLAPI void APIENTRY glGenSamplers (GLsizei count, GLuint *samplers); GLAPI void APIENTRY glDeleteSamplers (GLsizei count, const GLuint *samplers); GLAPI GLboolean APIENTRY glIsSampler (GLuint sampler); GLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler); GLAPI void APIENTRY glSamplerParameteri (GLuint sampler, GLenum pname, GLint param); GLAPI void APIENTRY glSamplerParameteriv (GLuint sampler, GLenum pname, const GLint *param); GLAPI void APIENTRY glSamplerParameterf (GLuint sampler, GLenum pname, GLfloat param); GLAPI void APIENTRY glSamplerParameterfv (GLuint sampler, GLenum pname, const GLfloat *param); GLAPI void APIENTRY glSamplerParameterIiv (GLuint sampler, GLenum pname, const GLint *param); GLAPI void APIENTRY glSamplerParameterIuiv (GLuint sampler, GLenum pname, const GLuint *param); GLAPI void APIENTRY glGetSamplerParameteriv (GLuint sampler, GLenum pname, GLint *params); GLAPI void APIENTRY glGetSamplerParameterIiv (GLuint sampler, GLenum pname, GLint *params); GLAPI void APIENTRY glGetSamplerParameterfv (GLuint sampler, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetSamplerParameterIuiv (GLuint sampler, GLenum pname, GLuint *params); GLAPI void APIENTRY glQueryCounter (GLuint id, GLenum target); GLAPI void APIENTRY glGetQueryObjecti64v (GLuint id, GLenum pname, GLint64 *params); GLAPI void APIENTRY glGetQueryObjectui64v (GLuint id, GLenum pname, GLuint64 *params); GLAPI void APIENTRY glVertexAttribDivisor (GLuint index, GLuint divisor); GLAPI void APIENTRY glVertexAttribP1ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); GLAPI void APIENTRY glVertexAttribP1uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); GLAPI void APIENTRY glVertexAttribP2ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); GLAPI void APIENTRY glVertexAttribP2uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); GLAPI void APIENTRY glVertexAttribP3ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); GLAPI void APIENTRY glVertexAttribP3uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); GLAPI void APIENTRY glVertexAttribP4ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); GLAPI void APIENTRY glVertexAttribP4uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); GLAPI void APIENTRY glVertexP2ui (GLenum type, GLuint value); GLAPI void APIENTRY glVertexP2uiv (GLenum type, const GLuint *value); GLAPI void APIENTRY glVertexP3ui (GLenum type, GLuint value); GLAPI void APIENTRY glVertexP3uiv (GLenum type, const GLuint *value); GLAPI void APIENTRY glVertexP4ui (GLenum type, GLuint value); GLAPI void APIENTRY glVertexP4uiv (GLenum type, const GLuint *value); GLAPI void APIENTRY glTexCoordP1ui (GLenum type, GLuint coords); GLAPI void APIENTRY glTexCoordP1uiv (GLenum type, const GLuint *coords); GLAPI void APIENTRY glTexCoordP2ui (GLenum type, GLuint coords); GLAPI void APIENTRY glTexCoordP2uiv (GLenum type, const GLuint *coords); GLAPI void APIENTRY glTexCoordP3ui (GLenum type, GLuint coords); GLAPI void APIENTRY glTexCoordP3uiv (GLenum type, const GLuint *coords); GLAPI void APIENTRY glTexCoordP4ui (GLenum type, GLuint coords); GLAPI void APIENTRY glTexCoordP4uiv (GLenum type, const GLuint *coords); GLAPI void APIENTRY glMultiTexCoordP1ui (GLenum texture, GLenum type, GLuint coords); GLAPI void APIENTRY glMultiTexCoordP1uiv (GLenum texture, GLenum type, const GLuint *coords); GLAPI void APIENTRY glMultiTexCoordP2ui (GLenum texture, GLenum type, GLuint coords); GLAPI void APIENTRY glMultiTexCoordP2uiv (GLenum texture, GLenum type, const GLuint *coords); GLAPI void APIENTRY glMultiTexCoordP3ui (GLenum texture, GLenum type, GLuint coords); GLAPI void APIENTRY glMultiTexCoordP3uiv (GLenum texture, GLenum type, const GLuint *coords); GLAPI void APIENTRY glMultiTexCoordP4ui (GLenum texture, GLenum type, GLuint coords); GLAPI void APIENTRY glMultiTexCoordP4uiv (GLenum texture, GLenum type, const GLuint *coords); GLAPI void APIENTRY glNormalP3ui (GLenum type, GLuint coords); GLAPI void APIENTRY glNormalP3uiv (GLenum type, const GLuint *coords); GLAPI void APIENTRY glColorP3ui (GLenum type, GLuint color); GLAPI void APIENTRY glColorP3uiv (GLenum type, const GLuint *color); GLAPI void APIENTRY glColorP4ui (GLenum type, GLuint color); GLAPI void APIENTRY glColorP4uiv (GLenum type, const GLuint *color); GLAPI void APIENTRY glSecondaryColorP3ui (GLenum type, GLuint color); GLAPI void APIENTRY glSecondaryColorP3uiv (GLenum type, const GLuint *color); #endif #endif /* GL_VERSION_3_3 */ #ifndef GL_VERSION_4_0 #define GL_VERSION_4_0 1 #define GL_SAMPLE_SHADING 0x8C36 #define GL_MIN_SAMPLE_SHADING_VALUE 0x8C37 #define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5E #define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5F #define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009 #define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A #define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY 0x900B #define GL_SAMPLER_CUBE_MAP_ARRAY 0x900C #define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW 0x900D #define GL_INT_SAMPLER_CUBE_MAP_ARRAY 0x900E #define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY 0x900F #define GL_DRAW_INDIRECT_BUFFER 0x8F3F #define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43 #define GL_GEOMETRY_SHADER_INVOCATIONS 0x887F #define GL_MAX_GEOMETRY_SHADER_INVOCATIONS 0x8E5A #define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET 0x8E5B #define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET 0x8E5C #define GL_FRAGMENT_INTERPOLATION_OFFSET_BITS 0x8E5D #define GL_MAX_VERTEX_STREAMS 0x8E71 #define GL_DOUBLE_VEC2 0x8FFC #define GL_DOUBLE_VEC3 0x8FFD #define GL_DOUBLE_VEC4 0x8FFE #define GL_DOUBLE_MAT2 0x8F46 #define GL_DOUBLE_MAT3 0x8F47 #define GL_DOUBLE_MAT4 0x8F48 #define GL_DOUBLE_MAT2x3 0x8F49 #define GL_DOUBLE_MAT2x4 0x8F4A #define GL_DOUBLE_MAT3x2 0x8F4B #define GL_DOUBLE_MAT3x4 0x8F4C #define GL_DOUBLE_MAT4x2 0x8F4D #define GL_DOUBLE_MAT4x3 0x8F4E #define GL_ACTIVE_SUBROUTINES 0x8DE5 #define GL_ACTIVE_SUBROUTINE_UNIFORMS 0x8DE6 #define GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS 0x8E47 #define GL_ACTIVE_SUBROUTINE_MAX_LENGTH 0x8E48 #define GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH 0x8E49 #define GL_MAX_SUBROUTINES 0x8DE7 #define GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS 0x8DE8 #define GL_NUM_COMPATIBLE_SUBROUTINES 0x8E4A #define GL_COMPATIBLE_SUBROUTINES 0x8E4B #define GL_PATCHES 0x000E #define GL_PATCH_VERTICES 0x8E72 #define GL_PATCH_DEFAULT_INNER_LEVEL 0x8E73 #define GL_PATCH_DEFAULT_OUTER_LEVEL 0x8E74 #define GL_TESS_CONTROL_OUTPUT_VERTICES 0x8E75 #define GL_TESS_GEN_MODE 0x8E76 #define GL_TESS_GEN_SPACING 0x8E77 #define GL_TESS_GEN_VERTEX_ORDER 0x8E78 #define GL_TESS_GEN_POINT_MODE 0x8E79 #define GL_ISOLINES 0x8E7A #define GL_FRACTIONAL_ODD 0x8E7B #define GL_FRACTIONAL_EVEN 0x8E7C #define GL_MAX_PATCH_VERTICES 0x8E7D #define GL_MAX_TESS_GEN_LEVEL 0x8E7E #define GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E7F #define GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E80 #define GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS 0x8E81 #define GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS 0x8E82 #define GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS 0x8E83 #define GL_MAX_TESS_PATCH_COMPONENTS 0x8E84 #define GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS 0x8E85 #define GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS 0x8E86 #define GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS 0x8E89 #define GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS 0x8E8A #define GL_MAX_TESS_CONTROL_INPUT_COMPONENTS 0x886C #define GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS 0x886D #define GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E1E #define GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E1F #define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER 0x84F0 #define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER 0x84F1 #define GL_TESS_EVALUATION_SHADER 0x8E87 #define GL_TESS_CONTROL_SHADER 0x8E88 #define GL_TRANSFORM_FEEDBACK 0x8E22 #define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED 0x8E23 #define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE 0x8E24 #define GL_TRANSFORM_FEEDBACK_BINDING 0x8E25 #define GL_MAX_TRANSFORM_FEEDBACK_BUFFERS 0x8E70 typedef void (APIENTRYP PFNGLMINSAMPLESHADINGPROC) (GLfloat value); typedef void (APIENTRYP PFNGLBLENDEQUATIONIPROC) (GLuint buf, GLenum mode); typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); typedef void (APIENTRYP PFNGLBLENDFUNCIPROC) (GLuint buf, GLenum src, GLenum dst); typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); typedef void (APIENTRYP PFNGLDRAWARRAYSINDIRECTPROC) (GLenum mode, const void *indirect); typedef void (APIENTRYP PFNGLDRAWELEMENTSINDIRECTPROC) (GLenum mode, GLenum type, const void *indirect); typedef void (APIENTRYP PFNGLUNIFORM1DPROC) (GLint location, GLdouble x); typedef void (APIENTRYP PFNGLUNIFORM2DPROC) (GLint location, GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLUNIFORM3DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLUNIFORM4DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLUNIFORM1DVPROC) (GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORM2DVPROC) (GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORM3DVPROC) (GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORM4DVPROC) (GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLGETUNIFORMDVPROC) (GLuint program, GLint location, GLdouble *params); typedef GLint (APIENTRYP PFNGLGETSUBROUTINEUNIFORMLOCATIONPROC) (GLuint program, GLenum shadertype, const GLchar *name); typedef GLuint (APIENTRYP PFNGLGETSUBROUTINEINDEXPROC) (GLuint program, GLenum shadertype, const GLchar *name); typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMIVPROC) (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMNAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINENAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); typedef void (APIENTRYP PFNGLUNIFORMSUBROUTINESUIVPROC) (GLenum shadertype, GLsizei count, const GLuint *indices); typedef void (APIENTRYP PFNGLGETUNIFORMSUBROUTINEUIVPROC) (GLenum shadertype, GLint location, GLuint *params); typedef void (APIENTRYP PFNGLGETPROGRAMSTAGEIVPROC) (GLuint program, GLenum shadertype, GLenum pname, GLint *values); typedef void (APIENTRYP PFNGLPATCHPARAMETERIPROC) (GLenum pname, GLint value); typedef void (APIENTRYP PFNGLPATCHPARAMETERFVPROC) (GLenum pname, const GLfloat *values); typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKPROC) (GLenum target, GLuint id); typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSPROC) (GLsizei n, const GLuint *ids); typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSPROC) (GLsizei n, GLuint *ids); typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKPROC) (GLuint id); typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKPROC) (void); typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKPROC) (void); typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKPROC) (GLenum mode, GLuint id); typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC) (GLenum mode, GLuint id, GLuint stream); typedef void (APIENTRYP PFNGLBEGINQUERYINDEXEDPROC) (GLenum target, GLuint index, GLuint id); typedef void (APIENTRYP PFNGLENDQUERYINDEXEDPROC) (GLenum target, GLuint index); typedef void (APIENTRYP PFNGLGETQUERYINDEXEDIVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMinSampleShading (GLfloat value); GLAPI void APIENTRY glBlendEquationi (GLuint buf, GLenum mode); GLAPI void APIENTRY glBlendEquationSeparatei (GLuint buf, GLenum modeRGB, GLenum modeAlpha); GLAPI void APIENTRY glBlendFunci (GLuint buf, GLenum src, GLenum dst); GLAPI void APIENTRY glBlendFuncSeparatei (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); GLAPI void APIENTRY glDrawArraysIndirect (GLenum mode, const void *indirect); GLAPI void APIENTRY glDrawElementsIndirect (GLenum mode, GLenum type, const void *indirect); GLAPI void APIENTRY glUniform1d (GLint location, GLdouble x); GLAPI void APIENTRY glUniform2d (GLint location, GLdouble x, GLdouble y); GLAPI void APIENTRY glUniform3d (GLint location, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glUniform4d (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glUniform1dv (GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glUniform2dv (GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glUniform3dv (GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glUniform4dv (GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glUniformMatrix2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glUniformMatrix3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glUniformMatrix4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glUniformMatrix2x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glUniformMatrix2x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glUniformMatrix3x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glUniformMatrix3x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glUniformMatrix4x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glUniformMatrix4x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glGetUniformdv (GLuint program, GLint location, GLdouble *params); GLAPI GLint APIENTRY glGetSubroutineUniformLocation (GLuint program, GLenum shadertype, const GLchar *name); GLAPI GLuint APIENTRY glGetSubroutineIndex (GLuint program, GLenum shadertype, const GLchar *name); GLAPI void APIENTRY glGetActiveSubroutineUniformiv (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); GLAPI void APIENTRY glGetActiveSubroutineUniformName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); GLAPI void APIENTRY glGetActiveSubroutineName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); GLAPI void APIENTRY glUniformSubroutinesuiv (GLenum shadertype, GLsizei count, const GLuint *indices); GLAPI void APIENTRY glGetUniformSubroutineuiv (GLenum shadertype, GLint location, GLuint *params); GLAPI void APIENTRY glGetProgramStageiv (GLuint program, GLenum shadertype, GLenum pname, GLint *values); GLAPI void APIENTRY glPatchParameteri (GLenum pname, GLint value); GLAPI void APIENTRY glPatchParameterfv (GLenum pname, const GLfloat *values); GLAPI void APIENTRY glBindTransformFeedback (GLenum target, GLuint id); GLAPI void APIENTRY glDeleteTransformFeedbacks (GLsizei n, const GLuint *ids); GLAPI void APIENTRY glGenTransformFeedbacks (GLsizei n, GLuint *ids); GLAPI GLboolean APIENTRY glIsTransformFeedback (GLuint id); GLAPI void APIENTRY glPauseTransformFeedback (void); GLAPI void APIENTRY glResumeTransformFeedback (void); GLAPI void APIENTRY glDrawTransformFeedback (GLenum mode, GLuint id); GLAPI void APIENTRY glDrawTransformFeedbackStream (GLenum mode, GLuint id, GLuint stream); GLAPI void APIENTRY glBeginQueryIndexed (GLenum target, GLuint index, GLuint id); GLAPI void APIENTRY glEndQueryIndexed (GLenum target, GLuint index); GLAPI void APIENTRY glGetQueryIndexediv (GLenum target, GLuint index, GLenum pname, GLint *params); #endif #endif /* GL_VERSION_4_0 */ #ifndef GL_VERSION_4_1 #define GL_VERSION_4_1 1 #define GL_FIXED 0x140C #define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A #define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B #define GL_LOW_FLOAT 0x8DF0 #define GL_MEDIUM_FLOAT 0x8DF1 #define GL_HIGH_FLOAT 0x8DF2 #define GL_LOW_INT 0x8DF3 #define GL_MEDIUM_INT 0x8DF4 #define GL_HIGH_INT 0x8DF5 #define GL_SHADER_COMPILER 0x8DFA #define GL_SHADER_BINARY_FORMATS 0x8DF8 #define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9 #define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB #define GL_MAX_VARYING_VECTORS 0x8DFC #define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD #define GL_RGB565 0x8D62 #define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 #define GL_PROGRAM_BINARY_LENGTH 0x8741 #define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE #define GL_PROGRAM_BINARY_FORMATS 0x87FF #define GL_VERTEX_SHADER_BIT 0x00000001 #define GL_FRAGMENT_SHADER_BIT 0x00000002 #define GL_GEOMETRY_SHADER_BIT 0x00000004 #define GL_TESS_CONTROL_SHADER_BIT 0x00000008 #define GL_TESS_EVALUATION_SHADER_BIT 0x00000010 #define GL_ALL_SHADER_BITS 0xFFFFFFFF #define GL_PROGRAM_SEPARABLE 0x8258 #define GL_ACTIVE_PROGRAM 0x8259 #define GL_PROGRAM_PIPELINE_BINDING 0x825A #define GL_MAX_VIEWPORTS 0x825B #define GL_VIEWPORT_SUBPIXEL_BITS 0x825C #define GL_VIEWPORT_BOUNDS_RANGE 0x825D #define GL_LAYER_PROVOKING_VERTEX 0x825E #define GL_VIEWPORT_INDEX_PROVOKING_VERTEX 0x825F #define GL_UNDEFINED_VERTEX 0x8260 typedef void (APIENTRYP PFNGLRELEASESHADERCOMPILERPROC) (void); typedef void (APIENTRYP PFNGLSHADERBINARYPROC) (GLsizei count, const GLuint *shaders, GLenum binaryFormat, const void *binary, GLsizei length); typedef void (APIENTRYP PFNGLGETSHADERPRECISIONFORMATPROC) (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); typedef void (APIENTRYP PFNGLDEPTHRANGEFPROC) (GLfloat n, GLfloat f); typedef void (APIENTRYP PFNGLCLEARDEPTHFPROC) (GLfloat d); typedef void (APIENTRYP PFNGLGETPROGRAMBINARYPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); typedef void (APIENTRYP PFNGLPROGRAMBINARYPROC) (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length); typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIPROC) (GLuint program, GLenum pname, GLint value); typedef void (APIENTRYP PFNGLUSEPROGRAMSTAGESPROC) (GLuint pipeline, GLbitfield stages, GLuint program); typedef void (APIENTRYP PFNGLACTIVESHADERPROGRAMPROC) (GLuint pipeline, GLuint program); typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMVPROC) (GLenum type, GLsizei count, const GLchar *const*strings); typedef void (APIENTRYP PFNGLBINDPROGRAMPIPELINEPROC) (GLuint pipeline); typedef void (APIENTRYP PFNGLDELETEPROGRAMPIPELINESPROC) (GLsizei n, const GLuint *pipelines); typedef void (APIENTRYP PFNGLGENPROGRAMPIPELINESPROC) (GLsizei n, GLuint *pipelines); typedef GLboolean (APIENTRYP PFNGLISPROGRAMPIPELINEPROC) (GLuint pipeline); typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEIVPROC) (GLuint pipeline, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IPROC) (GLuint program, GLint location, GLint v0); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FPROC) (GLuint program, GLint location, GLfloat v0); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DPROC) (GLuint program, GLint location, GLdouble v0); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIPROC) (GLuint program, GLint location, GLuint v0); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IPROC) (GLuint program, GLint location, GLint v0, GLint v1); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPIPELINEPROC) (GLuint pipeline); typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEINFOLOGPROC) (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DPROC) (GLuint index, GLdouble x); typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DPROC) (GLuint index, GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVPROC) (GLuint index, GLenum pname, GLdouble *params); typedef void (APIENTRYP PFNGLVIEWPORTARRAYVPROC) (GLuint first, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFVPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLSCISSORARRAYVPROC) (GLuint first, GLsizei count, const GLint *v); typedef void (APIENTRYP PFNGLSCISSORINDEXEDPROC) (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLSCISSORINDEXEDVPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLDEPTHRANGEARRAYVPROC) (GLuint first, GLsizei count, const GLdouble *v); typedef void (APIENTRYP PFNGLDEPTHRANGEINDEXEDPROC) (GLuint index, GLdouble n, GLdouble f); typedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data); typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glReleaseShaderCompiler (void); GLAPI void APIENTRY glShaderBinary (GLsizei count, const GLuint *shaders, GLenum binaryFormat, const void *binary, GLsizei length); GLAPI void APIENTRY glGetShaderPrecisionFormat (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); GLAPI void APIENTRY glDepthRangef (GLfloat n, GLfloat f); GLAPI void APIENTRY glClearDepthf (GLfloat d); GLAPI void APIENTRY glGetProgramBinary (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); GLAPI void APIENTRY glProgramBinary (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length); GLAPI void APIENTRY glProgramParameteri (GLuint program, GLenum pname, GLint value); GLAPI void APIENTRY glUseProgramStages (GLuint pipeline, GLbitfield stages, GLuint program); GLAPI void APIENTRY glActiveShaderProgram (GLuint pipeline, GLuint program); GLAPI GLuint APIENTRY glCreateShaderProgramv (GLenum type, GLsizei count, const GLchar *const*strings); GLAPI void APIENTRY glBindProgramPipeline (GLuint pipeline); GLAPI void APIENTRY glDeleteProgramPipelines (GLsizei n, const GLuint *pipelines); GLAPI void APIENTRY glGenProgramPipelines (GLsizei n, GLuint *pipelines); GLAPI GLboolean APIENTRY glIsProgramPipeline (GLuint pipeline); GLAPI void APIENTRY glGetProgramPipelineiv (GLuint pipeline, GLenum pname, GLint *params); GLAPI void APIENTRY glProgramUniform1i (GLuint program, GLint location, GLint v0); GLAPI void APIENTRY glProgramUniform1iv (GLuint program, GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glProgramUniform1f (GLuint program, GLint location, GLfloat v0); GLAPI void APIENTRY glProgramUniform1fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glProgramUniform1d (GLuint program, GLint location, GLdouble v0); GLAPI void APIENTRY glProgramUniform1dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glProgramUniform1ui (GLuint program, GLint location, GLuint v0); GLAPI void APIENTRY glProgramUniform1uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glProgramUniform2i (GLuint program, GLint location, GLint v0, GLint v1); GLAPI void APIENTRY glProgramUniform2iv (GLuint program, GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glProgramUniform2f (GLuint program, GLint location, GLfloat v0, GLfloat v1); GLAPI void APIENTRY glProgramUniform2fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glProgramUniform2d (GLuint program, GLint location, GLdouble v0, GLdouble v1); GLAPI void APIENTRY glProgramUniform2dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glProgramUniform2ui (GLuint program, GLint location, GLuint v0, GLuint v1); GLAPI void APIENTRY glProgramUniform2uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glProgramUniform3i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); GLAPI void APIENTRY glProgramUniform3iv (GLuint program, GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glProgramUniform3f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); GLAPI void APIENTRY glProgramUniform3fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glProgramUniform3d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); GLAPI void APIENTRY glProgramUniform3dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glProgramUniform3ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); GLAPI void APIENTRY glProgramUniform3uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glProgramUniform4i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); GLAPI void APIENTRY glProgramUniform4iv (GLuint program, GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glProgramUniform4f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); GLAPI void APIENTRY glProgramUniform4fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glProgramUniform4d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); GLAPI void APIENTRY glProgramUniform4dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glProgramUniform4ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); GLAPI void APIENTRY glProgramUniform4uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glProgramUniformMatrix2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix2x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix3x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix2x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix4x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix3x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix4x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix2x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix3x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix2x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix4x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix3x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix4x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glValidateProgramPipeline (GLuint pipeline); GLAPI void APIENTRY glGetProgramPipelineInfoLog (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); GLAPI void APIENTRY glVertexAttribL1d (GLuint index, GLdouble x); GLAPI void APIENTRY glVertexAttribL2d (GLuint index, GLdouble x, GLdouble y); GLAPI void APIENTRY glVertexAttribL3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glVertexAttribL4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glVertexAttribL1dv (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttribL2dv (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttribL3dv (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttribL4dv (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttribLPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glGetVertexAttribLdv (GLuint index, GLenum pname, GLdouble *params); GLAPI void APIENTRY glViewportArrayv (GLuint first, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glViewportIndexedf (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); GLAPI void APIENTRY glViewportIndexedfv (GLuint index, const GLfloat *v); GLAPI void APIENTRY glScissorArrayv (GLuint first, GLsizei count, const GLint *v); GLAPI void APIENTRY glScissorIndexed (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); GLAPI void APIENTRY glScissorIndexedv (GLuint index, const GLint *v); GLAPI void APIENTRY glDepthRangeArrayv (GLuint first, GLsizei count, const GLdouble *v); GLAPI void APIENTRY glDepthRangeIndexed (GLuint index, GLdouble n, GLdouble f); GLAPI void APIENTRY glGetFloati_v (GLenum target, GLuint index, GLfloat *data); GLAPI void APIENTRY glGetDoublei_v (GLenum target, GLuint index, GLdouble *data); #endif #endif /* GL_VERSION_4_1 */ #ifndef GL_VERSION_4_2 #define GL_VERSION_4_2 1 #define GL_COPY_READ_BUFFER_BINDING 0x8F36 #define GL_COPY_WRITE_BUFFER_BINDING 0x8F37 #define GL_TRANSFORM_FEEDBACK_ACTIVE 0x8E24 #define GL_TRANSFORM_FEEDBACK_PAUSED 0x8E23 #define GL_UNPACK_COMPRESSED_BLOCK_WIDTH 0x9127 #define GL_UNPACK_COMPRESSED_BLOCK_HEIGHT 0x9128 #define GL_UNPACK_COMPRESSED_BLOCK_DEPTH 0x9129 #define GL_UNPACK_COMPRESSED_BLOCK_SIZE 0x912A #define GL_PACK_COMPRESSED_BLOCK_WIDTH 0x912B #define GL_PACK_COMPRESSED_BLOCK_HEIGHT 0x912C #define GL_PACK_COMPRESSED_BLOCK_DEPTH 0x912D #define GL_PACK_COMPRESSED_BLOCK_SIZE 0x912E #define GL_NUM_SAMPLE_COUNTS 0x9380 #define GL_MIN_MAP_BUFFER_ALIGNMENT 0x90BC #define GL_ATOMIC_COUNTER_BUFFER 0x92C0 #define GL_ATOMIC_COUNTER_BUFFER_BINDING 0x92C1 #define GL_ATOMIC_COUNTER_BUFFER_START 0x92C2 #define GL_ATOMIC_COUNTER_BUFFER_SIZE 0x92C3 #define GL_ATOMIC_COUNTER_BUFFER_DATA_SIZE 0x92C4 #define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTERS 0x92C5 #define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES 0x92C6 #define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_VERTEX_SHADER 0x92C7 #define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_CONTROL_SHADER 0x92C8 #define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_EVALUATION_SHADER 0x92C9 #define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_GEOMETRY_SHADER 0x92CA #define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_FRAGMENT_SHADER 0x92CB #define GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS 0x92CC #define GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS 0x92CD #define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS 0x92CE #define GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS 0x92CF #define GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS 0x92D0 #define GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS 0x92D1 #define GL_MAX_VERTEX_ATOMIC_COUNTERS 0x92D2 #define GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS 0x92D3 #define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS 0x92D4 #define GL_MAX_GEOMETRY_ATOMIC_COUNTERS 0x92D5 #define GL_MAX_FRAGMENT_ATOMIC_COUNTERS 0x92D6 #define GL_MAX_COMBINED_ATOMIC_COUNTERS 0x92D7 #define GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE 0x92D8 #define GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS 0x92DC #define GL_ACTIVE_ATOMIC_COUNTER_BUFFERS 0x92D9 #define GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX 0x92DA #define GL_UNSIGNED_INT_ATOMIC_COUNTER 0x92DB #define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT 0x00000001 #define GL_ELEMENT_ARRAY_BARRIER_BIT 0x00000002 #define GL_UNIFORM_BARRIER_BIT 0x00000004 #define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008 #define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 #define GL_COMMAND_BARRIER_BIT 0x00000040 #define GL_PIXEL_BUFFER_BARRIER_BIT 0x00000080 #define GL_TEXTURE_UPDATE_BARRIER_BIT 0x00000100 #define GL_BUFFER_UPDATE_BARRIER_BIT 0x00000200 #define GL_FRAMEBUFFER_BARRIER_BIT 0x00000400 #define GL_TRANSFORM_FEEDBACK_BARRIER_BIT 0x00000800 #define GL_ATOMIC_COUNTER_BARRIER_BIT 0x00001000 #define GL_ALL_BARRIER_BITS 0xFFFFFFFF #define GL_MAX_IMAGE_UNITS 0x8F38 #define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS 0x8F39 #define GL_IMAGE_BINDING_NAME 0x8F3A #define GL_IMAGE_BINDING_LEVEL 0x8F3B #define GL_IMAGE_BINDING_LAYERED 0x8F3C #define GL_IMAGE_BINDING_LAYER 0x8F3D #define GL_IMAGE_BINDING_ACCESS 0x8F3E #define GL_IMAGE_1D 0x904C #define GL_IMAGE_2D 0x904D #define GL_IMAGE_3D 0x904E #define GL_IMAGE_2D_RECT 0x904F #define GL_IMAGE_CUBE 0x9050 #define GL_IMAGE_BUFFER 0x9051 #define GL_IMAGE_1D_ARRAY 0x9052 #define GL_IMAGE_2D_ARRAY 0x9053 #define GL_IMAGE_CUBE_MAP_ARRAY 0x9054 #define GL_IMAGE_2D_MULTISAMPLE 0x9055 #define GL_IMAGE_2D_MULTISAMPLE_ARRAY 0x9056 #define GL_INT_IMAGE_1D 0x9057 #define GL_INT_IMAGE_2D 0x9058 #define GL_INT_IMAGE_3D 0x9059 #define GL_INT_IMAGE_2D_RECT 0x905A #define GL_INT_IMAGE_CUBE 0x905B #define GL_INT_IMAGE_BUFFER 0x905C #define GL_INT_IMAGE_1D_ARRAY 0x905D #define GL_INT_IMAGE_2D_ARRAY 0x905E #define GL_INT_IMAGE_CUBE_MAP_ARRAY 0x905F #define GL_INT_IMAGE_2D_MULTISAMPLE 0x9060 #define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x9061 #define GL_UNSIGNED_INT_IMAGE_1D 0x9062 #define GL_UNSIGNED_INT_IMAGE_2D 0x9063 #define GL_UNSIGNED_INT_IMAGE_3D 0x9064 #define GL_UNSIGNED_INT_IMAGE_2D_RECT 0x9065 #define GL_UNSIGNED_INT_IMAGE_CUBE 0x9066 #define GL_UNSIGNED_INT_IMAGE_BUFFER 0x9067 #define GL_UNSIGNED_INT_IMAGE_1D_ARRAY 0x9068 #define GL_UNSIGNED_INT_IMAGE_2D_ARRAY 0x9069 #define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY 0x906A #define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE 0x906B #define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x906C #define GL_MAX_IMAGE_SAMPLES 0x906D #define GL_IMAGE_BINDING_FORMAT 0x906E #define GL_IMAGE_FORMAT_COMPATIBILITY_TYPE 0x90C7 #define GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE 0x90C8 #define GL_IMAGE_FORMAT_COMPATIBILITY_BY_CLASS 0x90C9 #define GL_MAX_VERTEX_IMAGE_UNIFORMS 0x90CA #define GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS 0x90CB #define GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS 0x90CC #define GL_MAX_GEOMETRY_IMAGE_UNIFORMS 0x90CD #define GL_MAX_FRAGMENT_IMAGE_UNIFORMS 0x90CE #define GL_MAX_COMBINED_IMAGE_UNIFORMS 0x90CF #define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C #define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D #define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E #define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F #define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEPROC) (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance); typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLuint baseinstance); typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance); typedef void (APIENTRYP PFNGLGETINTERNALFORMATIVPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint *params); typedef void (APIENTRYP PFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC) (GLuint program, GLuint bufferIndex, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC) (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); typedef void (APIENTRYP PFNGLMEMORYBARRIERPROC) (GLbitfield barriers); typedef void (APIENTRYP PFNGLTEXSTORAGE1DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); typedef void (APIENTRYP PFNGLTEXSTORAGE2DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLTEXSTORAGE3DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDPROC) (GLenum mode, GLuint id, GLsizei instancecount); typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMINSTANCEDPROC) (GLenum mode, GLuint id, GLuint stream, GLsizei instancecount); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawArraysInstancedBaseInstance (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance); GLAPI void APIENTRY glDrawElementsInstancedBaseInstance (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLuint baseinstance); GLAPI void APIENTRY glDrawElementsInstancedBaseVertexBaseInstance (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance); GLAPI void APIENTRY glGetInternalformativ (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint *params); GLAPI void APIENTRY glGetActiveAtomicCounterBufferiv (GLuint program, GLuint bufferIndex, GLenum pname, GLint *params); GLAPI void APIENTRY glBindImageTexture (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); GLAPI void APIENTRY glMemoryBarrier (GLbitfield barriers); GLAPI void APIENTRY glTexStorage1D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); GLAPI void APIENTRY glTexStorage2D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glTexStorage3D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); GLAPI void APIENTRY glDrawTransformFeedbackInstanced (GLenum mode, GLuint id, GLsizei instancecount); GLAPI void APIENTRY glDrawTransformFeedbackStreamInstanced (GLenum mode, GLuint id, GLuint stream, GLsizei instancecount); #endif #endif /* GL_VERSION_4_2 */ #ifndef GL_VERSION_4_3 #define GL_VERSION_4_3 1 typedef void (APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); #define GL_NUM_SHADING_LANGUAGE_VERSIONS 0x82E9 #define GL_VERTEX_ATTRIB_ARRAY_LONG 0x874E #define GL_COMPRESSED_RGB8_ETC2 0x9274 #define GL_COMPRESSED_SRGB8_ETC2 0x9275 #define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 #define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 #define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 #define GL_COMPRESSED_R11_EAC 0x9270 #define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 #define GL_COMPRESSED_RG11_EAC 0x9272 #define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 #define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69 #define GL_ANY_SAMPLES_PASSED_CONSERVATIVE 0x8D6A #define GL_MAX_ELEMENT_INDEX 0x8D6B #define GL_COMPUTE_SHADER 0x91B9 #define GL_MAX_COMPUTE_UNIFORM_BLOCKS 0x91BB #define GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS 0x91BC #define GL_MAX_COMPUTE_IMAGE_UNIFORMS 0x91BD #define GL_MAX_COMPUTE_SHARED_MEMORY_SIZE 0x8262 #define GL_MAX_COMPUTE_UNIFORM_COMPONENTS 0x8263 #define GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS 0x8264 #define GL_MAX_COMPUTE_ATOMIC_COUNTERS 0x8265 #define GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS 0x8266 #define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB #define GL_MAX_COMPUTE_WORK_GROUP_COUNT 0x91BE #define GL_MAX_COMPUTE_WORK_GROUP_SIZE 0x91BF #define GL_COMPUTE_WORK_GROUP_SIZE 0x8267 #define GL_UNIFORM_BLOCK_REFERENCED_BY_COMPUTE_SHADER 0x90EC #define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_COMPUTE_SHADER 0x90ED #define GL_DISPATCH_INDIRECT_BUFFER 0x90EE #define GL_DISPATCH_INDIRECT_BUFFER_BINDING 0x90EF #define GL_COMPUTE_SHADER_BIT 0x00000020 #define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 #define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH 0x8243 #define GL_DEBUG_CALLBACK_FUNCTION 0x8244 #define GL_DEBUG_CALLBACK_USER_PARAM 0x8245 #define GL_DEBUG_SOURCE_API 0x8246 #define GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247 #define GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248 #define GL_DEBUG_SOURCE_THIRD_PARTY 0x8249 #define GL_DEBUG_SOURCE_APPLICATION 0x824A #define GL_DEBUG_SOURCE_OTHER 0x824B #define GL_DEBUG_TYPE_ERROR 0x824C #define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D #define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E #define GL_DEBUG_TYPE_PORTABILITY 0x824F #define GL_DEBUG_TYPE_PERFORMANCE 0x8250 #define GL_DEBUG_TYPE_OTHER 0x8251 #define GL_MAX_DEBUG_MESSAGE_LENGTH 0x9143 #define GL_MAX_DEBUG_LOGGED_MESSAGES 0x9144 #define GL_DEBUG_LOGGED_MESSAGES 0x9145 #define GL_DEBUG_SEVERITY_HIGH 0x9146 #define GL_DEBUG_SEVERITY_MEDIUM 0x9147 #define GL_DEBUG_SEVERITY_LOW 0x9148 #define GL_DEBUG_TYPE_MARKER 0x8268 #define GL_DEBUG_TYPE_PUSH_GROUP 0x8269 #define GL_DEBUG_TYPE_POP_GROUP 0x826A #define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B #define GL_MAX_DEBUG_GROUP_STACK_DEPTH 0x826C #define GL_DEBUG_GROUP_STACK_DEPTH 0x826D #define GL_BUFFER 0x82E0 #define GL_SHADER 0x82E1 #define GL_PROGRAM 0x82E2 #define GL_QUERY 0x82E3 #define GL_PROGRAM_PIPELINE 0x82E4 #define GL_SAMPLER 0x82E6 #define GL_MAX_LABEL_LENGTH 0x82E8 #define GL_DEBUG_OUTPUT 0x92E0 #define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 #define GL_MAX_UNIFORM_LOCATIONS 0x826E #define GL_FRAMEBUFFER_DEFAULT_WIDTH 0x9310 #define GL_FRAMEBUFFER_DEFAULT_HEIGHT 0x9311 #define GL_FRAMEBUFFER_DEFAULT_LAYERS 0x9312 #define GL_FRAMEBUFFER_DEFAULT_SAMPLES 0x9313 #define GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS 0x9314 #define GL_MAX_FRAMEBUFFER_WIDTH 0x9315 #define GL_MAX_FRAMEBUFFER_HEIGHT 0x9316 #define GL_MAX_FRAMEBUFFER_LAYERS 0x9317 #define GL_MAX_FRAMEBUFFER_SAMPLES 0x9318 #define GL_INTERNALFORMAT_SUPPORTED 0x826F #define GL_INTERNALFORMAT_PREFERRED 0x8270 #define GL_INTERNALFORMAT_RED_SIZE 0x8271 #define GL_INTERNALFORMAT_GREEN_SIZE 0x8272 #define GL_INTERNALFORMAT_BLUE_SIZE 0x8273 #define GL_INTERNALFORMAT_ALPHA_SIZE 0x8274 #define GL_INTERNALFORMAT_DEPTH_SIZE 0x8275 #define GL_INTERNALFORMAT_STENCIL_SIZE 0x8276 #define GL_INTERNALFORMAT_SHARED_SIZE 0x8277 #define GL_INTERNALFORMAT_RED_TYPE 0x8278 #define GL_INTERNALFORMAT_GREEN_TYPE 0x8279 #define GL_INTERNALFORMAT_BLUE_TYPE 0x827A #define GL_INTERNALFORMAT_ALPHA_TYPE 0x827B #define GL_INTERNALFORMAT_DEPTH_TYPE 0x827C #define GL_INTERNALFORMAT_STENCIL_TYPE 0x827D #define GL_MAX_WIDTH 0x827E #define GL_MAX_HEIGHT 0x827F #define GL_MAX_DEPTH 0x8280 #define GL_MAX_LAYERS 0x8281 #define GL_MAX_COMBINED_DIMENSIONS 0x8282 #define GL_COLOR_COMPONENTS 0x8283 #define GL_DEPTH_COMPONENTS 0x8284 #define GL_STENCIL_COMPONENTS 0x8285 #define GL_COLOR_RENDERABLE 0x8286 #define GL_DEPTH_RENDERABLE 0x8287 #define GL_STENCIL_RENDERABLE 0x8288 #define GL_FRAMEBUFFER_RENDERABLE 0x8289 #define GL_FRAMEBUFFER_RENDERABLE_LAYERED 0x828A #define GL_FRAMEBUFFER_BLEND 0x828B #define GL_READ_PIXELS 0x828C #define GL_READ_PIXELS_FORMAT 0x828D #define GL_READ_PIXELS_TYPE 0x828E #define GL_TEXTURE_IMAGE_FORMAT 0x828F #define GL_TEXTURE_IMAGE_TYPE 0x8290 #define GL_GET_TEXTURE_IMAGE_FORMAT 0x8291 #define GL_GET_TEXTURE_IMAGE_TYPE 0x8292 #define GL_MIPMAP 0x8293 #define GL_MANUAL_GENERATE_MIPMAP 0x8294 #define GL_AUTO_GENERATE_MIPMAP 0x8295 #define GL_COLOR_ENCODING 0x8296 #define GL_SRGB_READ 0x8297 #define GL_SRGB_WRITE 0x8298 #define GL_FILTER 0x829A #define GL_VERTEX_TEXTURE 0x829B #define GL_TESS_CONTROL_TEXTURE 0x829C #define GL_TESS_EVALUATION_TEXTURE 0x829D #define GL_GEOMETRY_TEXTURE 0x829E #define GL_FRAGMENT_TEXTURE 0x829F #define GL_COMPUTE_TEXTURE 0x82A0 #define GL_TEXTURE_SHADOW 0x82A1 #define GL_TEXTURE_GATHER 0x82A2 #define GL_TEXTURE_GATHER_SHADOW 0x82A3 #define GL_SHADER_IMAGE_LOAD 0x82A4 #define GL_SHADER_IMAGE_STORE 0x82A5 #define GL_SHADER_IMAGE_ATOMIC 0x82A6 #define GL_IMAGE_TEXEL_SIZE 0x82A7 #define GL_IMAGE_COMPATIBILITY_CLASS 0x82A8 #define GL_IMAGE_PIXEL_FORMAT 0x82A9 #define GL_IMAGE_PIXEL_TYPE 0x82AA #define GL_SIMULTANEOUS_TEXTURE_AND_DEPTH_TEST 0x82AC #define GL_SIMULTANEOUS_TEXTURE_AND_STENCIL_TEST 0x82AD #define GL_SIMULTANEOUS_TEXTURE_AND_DEPTH_WRITE 0x82AE #define GL_SIMULTANEOUS_TEXTURE_AND_STENCIL_WRITE 0x82AF #define GL_TEXTURE_COMPRESSED_BLOCK_WIDTH 0x82B1 #define GL_TEXTURE_COMPRESSED_BLOCK_HEIGHT 0x82B2 #define GL_TEXTURE_COMPRESSED_BLOCK_SIZE 0x82B3 #define GL_CLEAR_BUFFER 0x82B4 #define GL_TEXTURE_VIEW 0x82B5 #define GL_VIEW_COMPATIBILITY_CLASS 0x82B6 #define GL_FULL_SUPPORT 0x82B7 #define GL_CAVEAT_SUPPORT 0x82B8 #define GL_IMAGE_CLASS_4_X_32 0x82B9 #define GL_IMAGE_CLASS_2_X_32 0x82BA #define GL_IMAGE_CLASS_1_X_32 0x82BB #define GL_IMAGE_CLASS_4_X_16 0x82BC #define GL_IMAGE_CLASS_2_X_16 0x82BD #define GL_IMAGE_CLASS_1_X_16 0x82BE #define GL_IMAGE_CLASS_4_X_8 0x82BF #define GL_IMAGE_CLASS_2_X_8 0x82C0 #define GL_IMAGE_CLASS_1_X_8 0x82C1 #define GL_IMAGE_CLASS_11_11_10 0x82C2 #define GL_IMAGE_CLASS_10_10_10_2 0x82C3 #define GL_VIEW_CLASS_128_BITS 0x82C4 #define GL_VIEW_CLASS_96_BITS 0x82C5 #define GL_VIEW_CLASS_64_BITS 0x82C6 #define GL_VIEW_CLASS_48_BITS 0x82C7 #define GL_VIEW_CLASS_32_BITS 0x82C8 #define GL_VIEW_CLASS_24_BITS 0x82C9 #define GL_VIEW_CLASS_16_BITS 0x82CA #define GL_VIEW_CLASS_8_BITS 0x82CB #define GL_VIEW_CLASS_S3TC_DXT1_RGB 0x82CC #define GL_VIEW_CLASS_S3TC_DXT1_RGBA 0x82CD #define GL_VIEW_CLASS_S3TC_DXT3_RGBA 0x82CE #define GL_VIEW_CLASS_S3TC_DXT5_RGBA 0x82CF #define GL_VIEW_CLASS_RGTC1_RED 0x82D0 #define GL_VIEW_CLASS_RGTC2_RG 0x82D1 #define GL_VIEW_CLASS_BPTC_UNORM 0x82D2 #define GL_VIEW_CLASS_BPTC_FLOAT 0x82D3 #define GL_UNIFORM 0x92E1 #define GL_UNIFORM_BLOCK 0x92E2 #define GL_PROGRAM_INPUT 0x92E3 #define GL_PROGRAM_OUTPUT 0x92E4 #define GL_BUFFER_VARIABLE 0x92E5 #define GL_SHADER_STORAGE_BLOCK 0x92E6 #define GL_VERTEX_SUBROUTINE 0x92E8 #define GL_TESS_CONTROL_SUBROUTINE 0x92E9 #define GL_TESS_EVALUATION_SUBROUTINE 0x92EA #define GL_GEOMETRY_SUBROUTINE 0x92EB #define GL_FRAGMENT_SUBROUTINE 0x92EC #define GL_COMPUTE_SUBROUTINE 0x92ED #define GL_VERTEX_SUBROUTINE_UNIFORM 0x92EE #define GL_TESS_CONTROL_SUBROUTINE_UNIFORM 0x92EF #define GL_TESS_EVALUATION_SUBROUTINE_UNIFORM 0x92F0 #define GL_GEOMETRY_SUBROUTINE_UNIFORM 0x92F1 #define GL_FRAGMENT_SUBROUTINE_UNIFORM 0x92F2 #define GL_COMPUTE_SUBROUTINE_UNIFORM 0x92F3 #define GL_TRANSFORM_FEEDBACK_VARYING 0x92F4 #define GL_ACTIVE_RESOURCES 0x92F5 #define GL_MAX_NAME_LENGTH 0x92F6 #define GL_MAX_NUM_ACTIVE_VARIABLES 0x92F7 #define GL_MAX_NUM_COMPATIBLE_SUBROUTINES 0x92F8 #define GL_NAME_LENGTH 0x92F9 #define GL_TYPE 0x92FA #define GL_ARRAY_SIZE 0x92FB #define GL_OFFSET 0x92FC #define GL_BLOCK_INDEX 0x92FD #define GL_ARRAY_STRIDE 0x92FE #define GL_MATRIX_STRIDE 0x92FF #define GL_IS_ROW_MAJOR 0x9300 #define GL_ATOMIC_COUNTER_BUFFER_INDEX 0x9301 #define GL_BUFFER_BINDING 0x9302 #define GL_BUFFER_DATA_SIZE 0x9303 #define GL_NUM_ACTIVE_VARIABLES 0x9304 #define GL_ACTIVE_VARIABLES 0x9305 #define GL_REFERENCED_BY_VERTEX_SHADER 0x9306 #define GL_REFERENCED_BY_TESS_CONTROL_SHADER 0x9307 #define GL_REFERENCED_BY_TESS_EVALUATION_SHADER 0x9308 #define GL_REFERENCED_BY_GEOMETRY_SHADER 0x9309 #define GL_REFERENCED_BY_FRAGMENT_SHADER 0x930A #define GL_REFERENCED_BY_COMPUTE_SHADER 0x930B #define GL_TOP_LEVEL_ARRAY_SIZE 0x930C #define GL_TOP_LEVEL_ARRAY_STRIDE 0x930D #define GL_LOCATION 0x930E #define GL_LOCATION_INDEX 0x930F #define GL_IS_PER_PATCH 0x92E7 #define GL_SHADER_STORAGE_BUFFER 0x90D2 #define GL_SHADER_STORAGE_BUFFER_BINDING 0x90D3 #define GL_SHADER_STORAGE_BUFFER_START 0x90D4 #define GL_SHADER_STORAGE_BUFFER_SIZE 0x90D5 #define GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS 0x90D6 #define GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS 0x90D7 #define GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS 0x90D8 #define GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS 0x90D9 #define GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS 0x90DA #define GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS 0x90DB #define GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS 0x90DC #define GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS 0x90DD #define GL_MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE #define GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT 0x90DF #define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000 #define GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES 0x8F39 #define GL_DEPTH_STENCIL_TEXTURE_MODE 0x90EA #define GL_TEXTURE_BUFFER_OFFSET 0x919D #define GL_TEXTURE_BUFFER_SIZE 0x919E #define GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT 0x919F #define GL_TEXTURE_VIEW_MIN_LEVEL 0x82DB #define GL_TEXTURE_VIEW_NUM_LEVELS 0x82DC #define GL_TEXTURE_VIEW_MIN_LAYER 0x82DD #define GL_TEXTURE_VIEW_NUM_LAYERS 0x82DE #define GL_TEXTURE_IMMUTABLE_LEVELS 0x82DF #define GL_VERTEX_ATTRIB_BINDING 0x82D4 #define GL_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D5 #define GL_VERTEX_BINDING_DIVISOR 0x82D6 #define GL_VERTEX_BINDING_OFFSET 0x82D7 #define GL_VERTEX_BINDING_STRIDE 0x82D8 #define GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D9 #define GL_MAX_VERTEX_ATTRIB_BINDINGS 0x82DA #define GL_VERTEX_BINDING_BUFFER 0x8F4F #define GL_DISPLAY_LIST 0x82E7 typedef void (APIENTRYP PFNGLCLEARBUFFERDATAPROC) (GLenum target, GLenum internalformat, GLenum format, GLenum type, const void *data); typedef void (APIENTRYP PFNGLCLEARBUFFERSUBDATAPROC) (GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEPROC) (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEINDIRECTPROC) (GLintptr indirect); typedef void (APIENTRYP PFNGLCOPYIMAGESUBDATAPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); typedef void (APIENTRYP PFNGLFRAMEBUFFERPARAMETERIPROC) (GLenum target, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETINTERNALFORMATI64VPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64 *params); typedef void (APIENTRYP PFNGLINVALIDATETEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth); typedef void (APIENTRYP PFNGLINVALIDATETEXIMAGEPROC) (GLuint texture, GLint level); typedef void (APIENTRYP PFNGLINVALIDATEBUFFERSUBDATAPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); typedef void (APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC) (GLuint buffer); typedef void (APIENTRYP PFNGLINVALIDATEFRAMEBUFFERPROC) (GLenum target, GLsizei numAttachments, const GLenum *attachments); typedef void (APIENTRYP PFNGLINVALIDATESUBFRAMEBUFFERPROC) (GLenum target, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTPROC) (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); typedef void (APIENTRYP PFNGLGETPROGRAMINTERFACEIVPROC) (GLuint program, GLenum programInterface, GLenum pname, GLint *params); typedef GLuint (APIENTRYP PFNGLGETPROGRAMRESOURCEINDEXPROC) (GLuint program, GLenum programInterface, const GLchar *name); typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCENAMEPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCEIVPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLint *params); typedef GLint (APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONPROC) (GLuint program, GLenum programInterface, const GLchar *name); typedef GLint (APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONINDEXPROC) (GLuint program, GLenum programInterface, const GLchar *name); typedef void (APIENTRYP PFNGLSHADERSTORAGEBLOCKBINDINGPROC) (GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); typedef void (APIENTRYP PFNGLTEXBUFFERRANGEPROC) (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); typedef void (APIENTRYP PFNGLTEXSTORAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); typedef void (APIENTRYP PFNGLTEXSTORAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); typedef void (APIENTRYP PFNGLTEXTUREVIEWPROC) (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); typedef void (APIENTRYP PFNGLBINDVERTEXBUFFERPROC) (GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); typedef void (APIENTRYP PFNGLVERTEXATTRIBLFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); typedef void (APIENTRYP PFNGLVERTEXATTRIBBINDINGPROC) (GLuint attribindex, GLuint bindingindex); typedef void (APIENTRYP PFNGLVERTEXBINDINGDIVISORPROC) (GLuint bindingindex, GLuint divisor); typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKPROC) (GLDEBUGPROC callback, const void *userParam); typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGPROC) (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPPROC) (GLenum source, GLuint id, GLsizei length, const GLchar *message); typedef void (APIENTRYP PFNGLPOPDEBUGGROUPPROC) (void); typedef void (APIENTRYP PFNGLOBJECTLABELPROC) (GLenum identifier, GLuint name, GLsizei length, const GLchar *label); typedef void (APIENTRYP PFNGLGETOBJECTLABELPROC) (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); typedef void (APIENTRYP PFNGLOBJECTPTRLABELPROC) (const void *ptr, GLsizei length, const GLchar *label); typedef void (APIENTRYP PFNGLGETOBJECTPTRLABELPROC) (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glClearBufferData (GLenum target, GLenum internalformat, GLenum format, GLenum type, const void *data); GLAPI void APIENTRY glClearBufferSubData (GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); GLAPI void APIENTRY glDispatchCompute (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); GLAPI void APIENTRY glDispatchComputeIndirect (GLintptr indirect); GLAPI void APIENTRY glCopyImageSubData (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); GLAPI void APIENTRY glFramebufferParameteri (GLenum target, GLenum pname, GLint param); GLAPI void APIENTRY glGetFramebufferParameteriv (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetInternalformati64v (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64 *params); GLAPI void APIENTRY glInvalidateTexSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth); GLAPI void APIENTRY glInvalidateTexImage (GLuint texture, GLint level); GLAPI void APIENTRY glInvalidateBufferSubData (GLuint buffer, GLintptr offset, GLsizeiptr length); GLAPI void APIENTRY glInvalidateBufferData (GLuint buffer); GLAPI void APIENTRY glInvalidateFramebuffer (GLenum target, GLsizei numAttachments, const GLenum *attachments); GLAPI void APIENTRY glInvalidateSubFramebuffer (GLenum target, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glMultiDrawArraysIndirect (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); GLAPI void APIENTRY glMultiDrawElementsIndirect (GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); GLAPI void APIENTRY glGetProgramInterfaceiv (GLuint program, GLenum programInterface, GLenum pname, GLint *params); GLAPI GLuint APIENTRY glGetProgramResourceIndex (GLuint program, GLenum programInterface, const GLchar *name); GLAPI void APIENTRY glGetProgramResourceName (GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); GLAPI void APIENTRY glGetProgramResourceiv (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLint *params); GLAPI GLint APIENTRY glGetProgramResourceLocation (GLuint program, GLenum programInterface, const GLchar *name); GLAPI GLint APIENTRY glGetProgramResourceLocationIndex (GLuint program, GLenum programInterface, const GLchar *name); GLAPI void APIENTRY glShaderStorageBlockBinding (GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); GLAPI void APIENTRY glTexBufferRange (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); GLAPI void APIENTRY glTexStorage2DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); GLAPI void APIENTRY glTexStorage3DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); GLAPI void APIENTRY glTextureView (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); GLAPI void APIENTRY glBindVertexBuffer (GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); GLAPI void APIENTRY glVertexAttribFormat (GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); GLAPI void APIENTRY glVertexAttribIFormat (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); GLAPI void APIENTRY glVertexAttribLFormat (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); GLAPI void APIENTRY glVertexAttribBinding (GLuint attribindex, GLuint bindingindex); GLAPI void APIENTRY glVertexBindingDivisor (GLuint bindingindex, GLuint divisor); GLAPI void APIENTRY glDebugMessageControl (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); GLAPI void APIENTRY glDebugMessageInsert (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); GLAPI void APIENTRY glDebugMessageCallback (GLDEBUGPROC callback, const void *userParam); GLAPI GLuint APIENTRY glGetDebugMessageLog (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); GLAPI void APIENTRY glPushDebugGroup (GLenum source, GLuint id, GLsizei length, const GLchar *message); GLAPI void APIENTRY glPopDebugGroup (void); GLAPI void APIENTRY glObjectLabel (GLenum identifier, GLuint name, GLsizei length, const GLchar *label); GLAPI void APIENTRY glGetObjectLabel (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); GLAPI void APIENTRY glObjectPtrLabel (const void *ptr, GLsizei length, const GLchar *label); GLAPI void APIENTRY glGetObjectPtrLabel (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); #endif #endif /* GL_VERSION_4_3 */ #ifndef GL_VERSION_4_4 #define GL_VERSION_4_4 1 #define GL_MAX_VERTEX_ATTRIB_STRIDE 0x82E5 #define GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED 0x8221 #define GL_TEXTURE_BUFFER_BINDING 0x8C2A #define GL_MAP_PERSISTENT_BIT 0x0040 #define GL_MAP_COHERENT_BIT 0x0080 #define GL_DYNAMIC_STORAGE_BIT 0x0100 #define GL_CLIENT_STORAGE_BIT 0x0200 #define GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT 0x00004000 #define GL_BUFFER_IMMUTABLE_STORAGE 0x821F #define GL_BUFFER_STORAGE_FLAGS 0x8220 #define GL_CLEAR_TEXTURE 0x9365 #define GL_LOCATION_COMPONENT 0x934A #define GL_TRANSFORM_FEEDBACK_BUFFER_INDEX 0x934B #define GL_TRANSFORM_FEEDBACK_BUFFER_STRIDE 0x934C #define GL_QUERY_BUFFER 0x9192 #define GL_QUERY_BUFFER_BARRIER_BIT 0x00008000 #define GL_QUERY_BUFFER_BINDING 0x9193 #define GL_QUERY_RESULT_NO_WAIT 0x9194 #define GL_MIRROR_CLAMP_TO_EDGE 0x8743 typedef void (APIENTRYP PFNGLBUFFERSTORAGEPROC) (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); typedef void (APIENTRYP PFNGLBINDBUFFERSBASEPROC) (GLenum target, GLuint first, GLsizei count, const GLuint *buffers); typedef void (APIENTRYP PFNGLBINDBUFFERSRANGEPROC) (GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes); typedef void (APIENTRYP PFNGLBINDTEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures); typedef void (APIENTRYP PFNGLBINDSAMPLERSPROC) (GLuint first, GLsizei count, const GLuint *samplers); typedef void (APIENTRYP PFNGLBINDIMAGETEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures); typedef void (APIENTRYP PFNGLBINDVERTEXBUFFERSPROC) (GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBufferStorage (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); GLAPI void APIENTRY glClearTexImage (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); GLAPI void APIENTRY glClearTexSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); GLAPI void APIENTRY glBindBuffersBase (GLenum target, GLuint first, GLsizei count, const GLuint *buffers); GLAPI void APIENTRY glBindBuffersRange (GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes); GLAPI void APIENTRY glBindTextures (GLuint first, GLsizei count, const GLuint *textures); GLAPI void APIENTRY glBindSamplers (GLuint first, GLsizei count, const GLuint *samplers); GLAPI void APIENTRY glBindImageTextures (GLuint first, GLsizei count, const GLuint *textures); GLAPI void APIENTRY glBindVertexBuffers (GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); #endif #endif /* GL_VERSION_4_4 */ #ifndef GL_VERSION_4_5 #define GL_VERSION_4_5 1 #define GL_CONTEXT_LOST 0x0507 #define GL_NEGATIVE_ONE_TO_ONE 0x935E #define GL_ZERO_TO_ONE 0x935F #define GL_CLIP_ORIGIN 0x935C #define GL_CLIP_DEPTH_MODE 0x935D #define GL_QUERY_WAIT_INVERTED 0x8E17 #define GL_QUERY_NO_WAIT_INVERTED 0x8E18 #define GL_QUERY_BY_REGION_WAIT_INVERTED 0x8E19 #define GL_QUERY_BY_REGION_NO_WAIT_INVERTED 0x8E1A #define GL_MAX_CULL_DISTANCES 0x82F9 #define GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES 0x82FA #define GL_TEXTURE_TARGET 0x1006 #define GL_QUERY_TARGET 0x82EA #define GL_GUILTY_CONTEXT_RESET 0x8253 #define GL_INNOCENT_CONTEXT_RESET 0x8254 #define GL_UNKNOWN_CONTEXT_RESET 0x8255 #define GL_RESET_NOTIFICATION_STRATEGY 0x8256 #define GL_LOSE_CONTEXT_ON_RESET 0x8252 #define GL_NO_RESET_NOTIFICATION 0x8261 #define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT 0x00000004 #define GL_COLOR_TABLE 0x80D0 #define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 #define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 #define GL_PROXY_COLOR_TABLE 0x80D3 #define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 #define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 #define GL_CONVOLUTION_1D 0x8010 #define GL_CONVOLUTION_2D 0x8011 #define GL_SEPARABLE_2D 0x8012 #define GL_HISTOGRAM 0x8024 #define GL_PROXY_HISTOGRAM 0x8025 #define GL_MINMAX 0x802E #define GL_CONTEXT_RELEASE_BEHAVIOR 0x82FB #define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH 0x82FC typedef void (APIENTRYP PFNGLCLIPCONTROLPROC) (GLenum origin, GLenum depth); typedef void (APIENTRYP PFNGLCREATETRANSFORMFEEDBACKSPROC) (GLsizei n, GLuint *ids); typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKBUFFERBASEPROC) (GLuint xfb, GLuint index, GLuint buffer); typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC) (GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKIVPROC) (GLuint xfb, GLenum pname, GLint *param); typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint *param); typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint64 *param); typedef void (APIENTRYP PFNGLCREATEBUFFERSPROC) (GLsizei n, GLuint *buffers); typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); typedef void (APIENTRYP PFNGLNAMEDBUFFERDATAPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); typedef void (APIENTRYP PFNGLNAMEDBUFFERSUBDATAPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); typedef void (APIENTRYP PFNGLCOPYNAMEDBUFFERSUBDATAPROC) (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERDATAPROC) (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERSUBDATAPROC) (GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFERPROC) (GLuint buffer, GLenum access); typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFERRANGEPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); typedef GLboolean (APIENTRYP PFNGLUNMAPNAMEDBUFFERPROC) (GLuint buffer); typedef void (APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVPROC) (GLuint buffer, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERI64VPROC) (GLuint buffer, GLenum pname, GLint64 *params); typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVPROC) (GLuint buffer, GLenum pname, void **params); typedef void (APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); typedef void (APIENTRYP PFNGLCREATEFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC) (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERPARAMETERIPROC) (GLuint framebuffer, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC) (GLuint framebuffer, GLenum buf); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC) (GLuint framebuffer, GLsizei n, const GLenum *bufs); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC) (GLuint framebuffer, GLenum src); typedef void (APIENTRYP PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC) (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments); typedef void (APIENTRYP PFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC) (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERIVPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint *value); typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERUIVPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint *value); typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERFVPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat *value); typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERFIPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); typedef void (APIENTRYP PFNGLBLITNAMEDFRAMEBUFFERPROC) (GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); typedef GLenum (APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC) (GLuint framebuffer, GLenum target); typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC) (GLuint framebuffer, GLenum pname, GLint *param); typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLCREATERENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEPROC) (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC) (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC) (GLuint renderbuffer, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLCREATETEXTURESPROC) (GLenum target, GLsizei n, GLuint *textures); typedef void (APIENTRYP PFNGLTEXTUREBUFFERPROC) (GLuint texture, GLenum internalformat, GLuint buffer); typedef void (APIENTRYP PFNGLTEXTUREBUFFERRANGEPROC) (GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); typedef void (APIENTRYP PFNGLTEXTURESTORAGE1DPROC) (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width); typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DPROC) (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DPROC) (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DMULTISAMPLEPROC) (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DMULTISAMPLEPROC) (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE1DPROC) (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE2DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE3DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC) (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DPROC) (GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFPROC) (GLuint texture, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFVPROC) (GLuint texture, GLenum pname, const GLfloat *param); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIPROC) (GLuint texture, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIIVPROC) (GLuint texture, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIUIVPROC) (GLuint texture, GLenum pname, const GLuint *params); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIVPROC) (GLuint texture, GLenum pname, const GLint *param); typedef void (APIENTRYP PFNGLGENERATETEXTUREMIPMAPPROC) (GLuint texture); typedef void (APIENTRYP PFNGLBINDTEXTUREUNITPROC) (GLuint unit, GLuint texture); typedef void (APIENTRYP PFNGLGETTEXTUREIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEPROC) (GLuint texture, GLint level, GLsizei bufSize, void *pixels); typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVPROC) (GLuint texture, GLint level, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVPROC) (GLuint texture, GLint level, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERFVPROC) (GLuint texture, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIIVPROC) (GLuint texture, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVPROC) (GLuint texture, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIVPROC) (GLuint texture, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLCREATEVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYATTRIBPROC) (GLuint vaobj, GLuint index); typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYATTRIBPROC) (GLuint vaobj, GLuint index); typedef void (APIENTRYP PFNGLVERTEXARRAYELEMENTBUFFERPROC) (GLuint vaobj, GLuint buffer); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBUFFERPROC) (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBUFFERSPROC) (GLuint vaobj, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBBINDINGPROC) (GLuint vaobj, GLuint attribindex, GLuint bindingindex); typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBFORMATPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBIFORMATPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBLFORMATPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); typedef void (APIENTRYP PFNGLVERTEXARRAYBINDINGDIVISORPROC) (GLuint vaobj, GLuint bindingindex, GLuint divisor); typedef void (APIENTRYP PFNGLGETVERTEXARRAYIVPROC) (GLuint vaobj, GLenum pname, GLint *param); typedef void (APIENTRYP PFNGLGETVERTEXARRAYINDEXEDIVPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param); typedef void (APIENTRYP PFNGLGETVERTEXARRAYINDEXED64IVPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint64 *param); typedef void (APIENTRYP PFNGLCREATESAMPLERSPROC) (GLsizei n, GLuint *samplers); typedef void (APIENTRYP PFNGLCREATEPROGRAMPIPELINESPROC) (GLsizei n, GLuint *pipelines); typedef void (APIENTRYP PFNGLCREATEQUERIESPROC) (GLenum target, GLsizei n, GLuint *ids); typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTI64VPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTIVPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTUI64VPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTUIVPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); typedef void (APIENTRYP PFNGLMEMORYBARRIERBYREGIONPROC) (GLbitfield barriers); typedef void (APIENTRYP PFNGLGETTEXTURESUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void *pixels); typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void *pixels); typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSPROC) (void); typedef void (APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint lod, GLsizei bufSize, void *pixels); typedef void (APIENTRYP PFNGLGETNTEXIMAGEPROC) (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); typedef void (APIENTRYP PFNGLGETNUNIFORMDVPROC) (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); typedef void (APIENTRYP PFNGLGETNUNIFORMFVPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); typedef void (APIENTRYP PFNGLGETNUNIFORMIVPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); typedef void (APIENTRYP PFNGLGETNUNIFORMUIVPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint *params); typedef void (APIENTRYP PFNGLREADNPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); typedef void (APIENTRYP PFNGLGETNMAPDVPROC) (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); typedef void (APIENTRYP PFNGLGETNMAPFVPROC) (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); typedef void (APIENTRYP PFNGLGETNMAPIVPROC) (GLenum target, GLenum query, GLsizei bufSize, GLint *v); typedef void (APIENTRYP PFNGLGETNPIXELMAPFVPROC) (GLenum map, GLsizei bufSize, GLfloat *values); typedef void (APIENTRYP PFNGLGETNPIXELMAPUIVPROC) (GLenum map, GLsizei bufSize, GLuint *values); typedef void (APIENTRYP PFNGLGETNPIXELMAPUSVPROC) (GLenum map, GLsizei bufSize, GLushort *values); typedef void (APIENTRYP PFNGLGETNPOLYGONSTIPPLEPROC) (GLsizei bufSize, GLubyte *pattern); typedef void (APIENTRYP PFNGLGETNCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); typedef void (APIENTRYP PFNGLGETNCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); typedef void (APIENTRYP PFNGLGETNSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); typedef void (APIENTRYP PFNGLGETNHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); typedef void (APIENTRYP PFNGLGETNMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); typedef void (APIENTRYP PFNGLTEXTUREBARRIERPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glClipControl (GLenum origin, GLenum depth); GLAPI void APIENTRY glCreateTransformFeedbacks (GLsizei n, GLuint *ids); GLAPI void APIENTRY glTransformFeedbackBufferBase (GLuint xfb, GLuint index, GLuint buffer); GLAPI void APIENTRY glTransformFeedbackBufferRange (GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); GLAPI void APIENTRY glGetTransformFeedbackiv (GLuint xfb, GLenum pname, GLint *param); GLAPI void APIENTRY glGetTransformFeedbacki_v (GLuint xfb, GLenum pname, GLuint index, GLint *param); GLAPI void APIENTRY glGetTransformFeedbacki64_v (GLuint xfb, GLenum pname, GLuint index, GLint64 *param); GLAPI void APIENTRY glCreateBuffers (GLsizei n, GLuint *buffers); GLAPI void APIENTRY glNamedBufferStorage (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); GLAPI void APIENTRY glNamedBufferData (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); GLAPI void APIENTRY glNamedBufferSubData (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); GLAPI void APIENTRY glCopyNamedBufferSubData (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); GLAPI void APIENTRY glClearNamedBufferData (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); GLAPI void APIENTRY glClearNamedBufferSubData (GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); GLAPI void *APIENTRY glMapNamedBuffer (GLuint buffer, GLenum access); GLAPI void *APIENTRY glMapNamedBufferRange (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); GLAPI GLboolean APIENTRY glUnmapNamedBuffer (GLuint buffer); GLAPI void APIENTRY glFlushMappedNamedBufferRange (GLuint buffer, GLintptr offset, GLsizeiptr length); GLAPI void APIENTRY glGetNamedBufferParameteriv (GLuint buffer, GLenum pname, GLint *params); GLAPI void APIENTRY glGetNamedBufferParameteri64v (GLuint buffer, GLenum pname, GLint64 *params); GLAPI void APIENTRY glGetNamedBufferPointerv (GLuint buffer, GLenum pname, void **params); GLAPI void APIENTRY glGetNamedBufferSubData (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); GLAPI void APIENTRY glCreateFramebuffers (GLsizei n, GLuint *framebuffers); GLAPI void APIENTRY glNamedFramebufferRenderbuffer (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); GLAPI void APIENTRY glNamedFramebufferParameteri (GLuint framebuffer, GLenum pname, GLint param); GLAPI void APIENTRY glNamedFramebufferTexture (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); GLAPI void APIENTRY glNamedFramebufferTextureLayer (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); GLAPI void APIENTRY glNamedFramebufferDrawBuffer (GLuint framebuffer, GLenum buf); GLAPI void APIENTRY glNamedFramebufferDrawBuffers (GLuint framebuffer, GLsizei n, const GLenum *bufs); GLAPI void APIENTRY glNamedFramebufferReadBuffer (GLuint framebuffer, GLenum src); GLAPI void APIENTRY glInvalidateNamedFramebufferData (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments); GLAPI void APIENTRY glInvalidateNamedFramebufferSubData (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glClearNamedFramebufferiv (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint *value); GLAPI void APIENTRY glClearNamedFramebufferuiv (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint *value); GLAPI void APIENTRY glClearNamedFramebufferfv (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat *value); GLAPI void APIENTRY glClearNamedFramebufferfi (GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); GLAPI void APIENTRY glBlitNamedFramebuffer (GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); GLAPI GLenum APIENTRY glCheckNamedFramebufferStatus (GLuint framebuffer, GLenum target); GLAPI void APIENTRY glGetNamedFramebufferParameteriv (GLuint framebuffer, GLenum pname, GLint *param); GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameteriv (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); GLAPI void APIENTRY glCreateRenderbuffers (GLsizei n, GLuint *renderbuffers); GLAPI void APIENTRY glNamedRenderbufferStorage (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glNamedRenderbufferStorageMultisample (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glGetNamedRenderbufferParameteriv (GLuint renderbuffer, GLenum pname, GLint *params); GLAPI void APIENTRY glCreateTextures (GLenum target, GLsizei n, GLuint *textures); GLAPI void APIENTRY glTextureBuffer (GLuint texture, GLenum internalformat, GLuint buffer); GLAPI void APIENTRY glTextureBufferRange (GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); GLAPI void APIENTRY glTextureStorage1D (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width); GLAPI void APIENTRY glTextureStorage2D (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glTextureStorage3D (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); GLAPI void APIENTRY glTextureStorage2DMultisample (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); GLAPI void APIENTRY glTextureStorage3DMultisample (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); GLAPI void APIENTRY glTextureSubImage1D (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTextureSubImage2D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTextureSubImage3D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glCompressedTextureSubImage1D (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTextureSubImage2D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTextureSubImage3D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCopyTextureSubImage1D (GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); GLAPI void APIENTRY glCopyTextureSubImage2D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glCopyTextureSubImage3D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glTextureParameterf (GLuint texture, GLenum pname, GLfloat param); GLAPI void APIENTRY glTextureParameterfv (GLuint texture, GLenum pname, const GLfloat *param); GLAPI void APIENTRY glTextureParameteri (GLuint texture, GLenum pname, GLint param); GLAPI void APIENTRY glTextureParameterIiv (GLuint texture, GLenum pname, const GLint *params); GLAPI void APIENTRY glTextureParameterIuiv (GLuint texture, GLenum pname, const GLuint *params); GLAPI void APIENTRY glTextureParameteriv (GLuint texture, GLenum pname, const GLint *param); GLAPI void APIENTRY glGenerateTextureMipmap (GLuint texture); GLAPI void APIENTRY glBindTextureUnit (GLuint unit, GLuint texture); GLAPI void APIENTRY glGetTextureImage (GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); GLAPI void APIENTRY glGetCompressedTextureImage (GLuint texture, GLint level, GLsizei bufSize, void *pixels); GLAPI void APIENTRY glGetTextureLevelParameterfv (GLuint texture, GLint level, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetTextureLevelParameteriv (GLuint texture, GLint level, GLenum pname, GLint *params); GLAPI void APIENTRY glGetTextureParameterfv (GLuint texture, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetTextureParameterIiv (GLuint texture, GLenum pname, GLint *params); GLAPI void APIENTRY glGetTextureParameterIuiv (GLuint texture, GLenum pname, GLuint *params); GLAPI void APIENTRY glGetTextureParameteriv (GLuint texture, GLenum pname, GLint *params); GLAPI void APIENTRY glCreateVertexArrays (GLsizei n, GLuint *arrays); GLAPI void APIENTRY glDisableVertexArrayAttrib (GLuint vaobj, GLuint index); GLAPI void APIENTRY glEnableVertexArrayAttrib (GLuint vaobj, GLuint index); GLAPI void APIENTRY glVertexArrayElementBuffer (GLuint vaobj, GLuint buffer); GLAPI void APIENTRY glVertexArrayVertexBuffer (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); GLAPI void APIENTRY glVertexArrayVertexBuffers (GLuint vaobj, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); GLAPI void APIENTRY glVertexArrayAttribBinding (GLuint vaobj, GLuint attribindex, GLuint bindingindex); GLAPI void APIENTRY glVertexArrayAttribFormat (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); GLAPI void APIENTRY glVertexArrayAttribIFormat (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); GLAPI void APIENTRY glVertexArrayAttribLFormat (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); GLAPI void APIENTRY glVertexArrayBindingDivisor (GLuint vaobj, GLuint bindingindex, GLuint divisor); GLAPI void APIENTRY glGetVertexArrayiv (GLuint vaobj, GLenum pname, GLint *param); GLAPI void APIENTRY glGetVertexArrayIndexediv (GLuint vaobj, GLuint index, GLenum pname, GLint *param); GLAPI void APIENTRY glGetVertexArrayIndexed64iv (GLuint vaobj, GLuint index, GLenum pname, GLint64 *param); GLAPI void APIENTRY glCreateSamplers (GLsizei n, GLuint *samplers); GLAPI void APIENTRY glCreateProgramPipelines (GLsizei n, GLuint *pipelines); GLAPI void APIENTRY glCreateQueries (GLenum target, GLsizei n, GLuint *ids); GLAPI void APIENTRY glGetQueryBufferObjecti64v (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); GLAPI void APIENTRY glGetQueryBufferObjectiv (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); GLAPI void APIENTRY glGetQueryBufferObjectui64v (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); GLAPI void APIENTRY glGetQueryBufferObjectuiv (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); GLAPI void APIENTRY glMemoryBarrierByRegion (GLbitfield barriers); GLAPI void APIENTRY glGetTextureSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void *pixels); GLAPI void APIENTRY glGetCompressedTextureSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void *pixels); GLAPI GLenum APIENTRY glGetGraphicsResetStatus (void); GLAPI void APIENTRY glGetnCompressedTexImage (GLenum target, GLint lod, GLsizei bufSize, void *pixels); GLAPI void APIENTRY glGetnTexImage (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); GLAPI void APIENTRY glGetnUniformdv (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); GLAPI void APIENTRY glGetnUniformfv (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); GLAPI void APIENTRY glGetnUniformiv (GLuint program, GLint location, GLsizei bufSize, GLint *params); GLAPI void APIENTRY glGetnUniformuiv (GLuint program, GLint location, GLsizei bufSize, GLuint *params); GLAPI void APIENTRY glReadnPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); GLAPI void APIENTRY glGetnMapdv (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); GLAPI void APIENTRY glGetnMapfv (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); GLAPI void APIENTRY glGetnMapiv (GLenum target, GLenum query, GLsizei bufSize, GLint *v); GLAPI void APIENTRY glGetnPixelMapfv (GLenum map, GLsizei bufSize, GLfloat *values); GLAPI void APIENTRY glGetnPixelMapuiv (GLenum map, GLsizei bufSize, GLuint *values); GLAPI void APIENTRY glGetnPixelMapusv (GLenum map, GLsizei bufSize, GLushort *values); GLAPI void APIENTRY glGetnPolygonStipple (GLsizei bufSize, GLubyte *pattern); GLAPI void APIENTRY glGetnColorTable (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); GLAPI void APIENTRY glGetnConvolutionFilter (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); GLAPI void APIENTRY glGetnSeparableFilter (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); GLAPI void APIENTRY glGetnHistogram (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); GLAPI void APIENTRY glGetnMinmax (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); GLAPI void APIENTRY glTextureBarrier (void); #endif #endif /* GL_VERSION_4_5 */ #ifndef GL_VERSION_4_6 #define GL_VERSION_4_6 1 #define GL_SHADER_BINARY_FORMAT_SPIR_V 0x9551 #define GL_SPIR_V_BINARY 0x9552 #define GL_PARAMETER_BUFFER 0x80EE #define GL_PARAMETER_BUFFER_BINDING 0x80EF #define GL_CONTEXT_FLAG_NO_ERROR_BIT 0x00000008 #define GL_VERTICES_SUBMITTED 0x82EE #define GL_PRIMITIVES_SUBMITTED 0x82EF #define GL_VERTEX_SHADER_INVOCATIONS 0x82F0 #define GL_TESS_CONTROL_SHADER_PATCHES 0x82F1 #define GL_TESS_EVALUATION_SHADER_INVOCATIONS 0x82F2 #define GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED 0x82F3 #define GL_FRAGMENT_SHADER_INVOCATIONS 0x82F4 #define GL_COMPUTE_SHADER_INVOCATIONS 0x82F5 #define GL_CLIPPING_INPUT_PRIMITIVES 0x82F6 #define GL_CLIPPING_OUTPUT_PRIMITIVES 0x82F7 #define GL_POLYGON_OFFSET_CLAMP 0x8E1B #define GL_SPIR_V_EXTENSIONS 0x9553 #define GL_NUM_SPIR_V_EXTENSIONS 0x9554 #define GL_TEXTURE_MAX_ANISOTROPY 0x84FE #define GL_MAX_TEXTURE_MAX_ANISOTROPY 0x84FF #define GL_TRANSFORM_FEEDBACK_OVERFLOW 0x82EC #define GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW 0x82ED typedef void (APIENTRYP PFNGLSPECIALIZESHADERPROC) (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTCOUNTPROC) (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTPROC) (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); typedef void (APIENTRYP PFNGLPOLYGONOFFSETCLAMPPROC) (GLfloat factor, GLfloat units, GLfloat clamp); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSpecializeShader (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); GLAPI void APIENTRY glMultiDrawArraysIndirectCount (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); GLAPI void APIENTRY glMultiDrawElementsIndirectCount (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); GLAPI void APIENTRY glPolygonOffsetClamp (GLfloat factor, GLfloat units, GLfloat clamp); #endif #endif /* GL_VERSION_4_6 */ #ifndef GL_ARB_ES2_compatibility #define GL_ARB_ES2_compatibility 1 #endif /* GL_ARB_ES2_compatibility */ #ifndef GL_ARB_ES3_1_compatibility #define GL_ARB_ES3_1_compatibility 1 #endif /* GL_ARB_ES3_1_compatibility */ #ifndef GL_ARB_ES3_2_compatibility #define GL_ARB_ES3_2_compatibility 1 #define GL_PRIMITIVE_BOUNDING_BOX_ARB 0x92BE #define GL_MULTISAMPLE_LINE_WIDTH_RANGE_ARB 0x9381 #define GL_MULTISAMPLE_LINE_WIDTH_GRANULARITY_ARB 0x9382 typedef void (APIENTRYP PFNGLPRIMITIVEBOUNDINGBOXARBPROC) (GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPrimitiveBoundingBoxARB (GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); #endif #endif /* GL_ARB_ES3_2_compatibility */ #ifndef GL_ARB_ES3_compatibility #define GL_ARB_ES3_compatibility 1 #endif /* GL_ARB_ES3_compatibility */ #ifndef GL_ARB_arrays_of_arrays #define GL_ARB_arrays_of_arrays 1 #endif /* GL_ARB_arrays_of_arrays */ #ifndef GL_ARB_base_instance #define GL_ARB_base_instance 1 #endif /* GL_ARB_base_instance */ #ifndef GL_ARB_bindless_texture #define GL_ARB_bindless_texture 1 typedef khronos_uint64_t GLuint64EXT; #define GL_UNSIGNED_INT64_ARB 0x140F typedef GLuint64 (APIENTRYP PFNGLGETTEXTUREHANDLEARBPROC) (GLuint texture); typedef GLuint64 (APIENTRYP PFNGLGETTEXTURESAMPLERHANDLEARBPROC) (GLuint texture, GLuint sampler); typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTARBPROC) (GLuint64 handle); typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC) (GLuint64 handle); typedef GLuint64 (APIENTRYP PFNGLGETIMAGEHANDLEARBPROC) (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTARBPROC) (GLuint64 handle, GLenum access); typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC) (GLuint64 handle); typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64ARBPROC) (GLint location, GLuint64 value); typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC) (GLuint program, GLint location, GLuint64 value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); typedef GLboolean (APIENTRYP PFNGLISTEXTUREHANDLERESIDENTARBPROC) (GLuint64 handle); typedef GLboolean (APIENTRYP PFNGLISIMAGEHANDLERESIDENTARBPROC) (GLuint64 handle); typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64ARBPROC) (GLuint index, GLuint64EXT x); typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VARBPROC) (GLuint index, const GLuint64EXT *v); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VARBPROC) (GLuint index, GLenum pname, GLuint64EXT *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLuint64 APIENTRY glGetTextureHandleARB (GLuint texture); GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleARB (GLuint texture, GLuint sampler); GLAPI void APIENTRY glMakeTextureHandleResidentARB (GLuint64 handle); GLAPI void APIENTRY glMakeTextureHandleNonResidentARB (GLuint64 handle); GLAPI GLuint64 APIENTRY glGetImageHandleARB (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); GLAPI void APIENTRY glMakeImageHandleResidentARB (GLuint64 handle, GLenum access); GLAPI void APIENTRY glMakeImageHandleNonResidentARB (GLuint64 handle); GLAPI void APIENTRY glUniformHandleui64ARB (GLint location, GLuint64 value); GLAPI void APIENTRY glUniformHandleui64vARB (GLint location, GLsizei count, const GLuint64 *value); GLAPI void APIENTRY glProgramUniformHandleui64ARB (GLuint program, GLint location, GLuint64 value); GLAPI void APIENTRY glProgramUniformHandleui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *values); GLAPI GLboolean APIENTRY glIsTextureHandleResidentARB (GLuint64 handle); GLAPI GLboolean APIENTRY glIsImageHandleResidentARB (GLuint64 handle); GLAPI void APIENTRY glVertexAttribL1ui64ARB (GLuint index, GLuint64EXT x); GLAPI void APIENTRY glVertexAttribL1ui64vARB (GLuint index, const GLuint64EXT *v); GLAPI void APIENTRY glGetVertexAttribLui64vARB (GLuint index, GLenum pname, GLuint64EXT *params); #endif #endif /* GL_ARB_bindless_texture */ #ifndef GL_ARB_blend_func_extended #define GL_ARB_blend_func_extended 1 #endif /* GL_ARB_blend_func_extended */ #ifndef GL_ARB_buffer_storage #define GL_ARB_buffer_storage 1 #endif /* GL_ARB_buffer_storage */ #ifndef GL_ARB_cl_event #define GL_ARB_cl_event 1 struct _cl_context; struct _cl_event; #define GL_SYNC_CL_EVENT_ARB 0x8240 #define GL_SYNC_CL_EVENT_COMPLETE_ARB 0x8241 typedef GLsync (APIENTRYP PFNGLCREATESYNCFROMCLEVENTARBPROC) (struct _cl_context *context, struct _cl_event *event, GLbitfield flags); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLsync APIENTRY glCreateSyncFromCLeventARB (struct _cl_context *context, struct _cl_event *event, GLbitfield flags); #endif #endif /* GL_ARB_cl_event */ #ifndef GL_ARB_clear_buffer_object #define GL_ARB_clear_buffer_object 1 #endif /* GL_ARB_clear_buffer_object */ #ifndef GL_ARB_clear_texture #define GL_ARB_clear_texture 1 #endif /* GL_ARB_clear_texture */ #ifndef GL_ARB_clip_control #define GL_ARB_clip_control 1 #endif /* GL_ARB_clip_control */ #ifndef GL_ARB_color_buffer_float #define GL_ARB_color_buffer_float 1 #define GL_RGBA_FLOAT_MODE_ARB 0x8820 #define GL_CLAMP_VERTEX_COLOR_ARB 0x891A #define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B #define GL_CLAMP_READ_COLOR_ARB 0x891C #define GL_FIXED_ONLY_ARB 0x891D typedef void (APIENTRYP PFNGLCLAMPCOLORARBPROC) (GLenum target, GLenum clamp); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glClampColorARB (GLenum target, GLenum clamp); #endif #endif /* GL_ARB_color_buffer_float */ #ifndef GL_ARB_compatibility #define GL_ARB_compatibility 1 #endif /* GL_ARB_compatibility */ #ifndef GL_ARB_compressed_texture_pixel_storage #define GL_ARB_compressed_texture_pixel_storage 1 #endif /* GL_ARB_compressed_texture_pixel_storage */ #ifndef GL_ARB_compute_shader #define GL_ARB_compute_shader 1 #endif /* GL_ARB_compute_shader */ #ifndef GL_ARB_compute_variable_group_size #define GL_ARB_compute_variable_group_size 1 #define GL_MAX_COMPUTE_VARIABLE_GROUP_INVOCATIONS_ARB 0x9344 #define GL_MAX_COMPUTE_FIXED_GROUP_INVOCATIONS_ARB 0x90EB #define GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB 0x9345 #define GL_MAX_COMPUTE_FIXED_GROUP_SIZE_ARB 0x91BF typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEGROUPSIZEARBPROC) (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z, GLuint group_size_x, GLuint group_size_y, GLuint group_size_z); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDispatchComputeGroupSizeARB (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z, GLuint group_size_x, GLuint group_size_y, GLuint group_size_z); #endif #endif /* GL_ARB_compute_variable_group_size */ #ifndef GL_ARB_conditional_render_inverted #define GL_ARB_conditional_render_inverted 1 #endif /* GL_ARB_conditional_render_inverted */ #ifndef GL_ARB_conservative_depth #define GL_ARB_conservative_depth 1 #endif /* GL_ARB_conservative_depth */ #ifndef GL_ARB_copy_buffer #define GL_ARB_copy_buffer 1 #endif /* GL_ARB_copy_buffer */ #ifndef GL_ARB_copy_image #define GL_ARB_copy_image 1 #endif /* GL_ARB_copy_image */ #ifndef GL_ARB_cull_distance #define GL_ARB_cull_distance 1 #endif /* GL_ARB_cull_distance */ #ifndef GL_ARB_debug_output #define GL_ARB_debug_output 1 typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); #define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 #define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 #define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 #define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245 #define GL_DEBUG_SOURCE_API_ARB 0x8246 #define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247 #define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248 #define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249 #define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A #define GL_DEBUG_SOURCE_OTHER_ARB 0x824B #define GL_DEBUG_TYPE_ERROR_ARB 0x824C #define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D #define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E #define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F #define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250 #define GL_DEBUG_TYPE_OTHER_ARB 0x8251 #define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143 #define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144 #define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145 #define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146 #define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 #define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLARBPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTARBPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKARBPROC) (GLDEBUGPROCARB callback, const void *userParam); typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGARBPROC) (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDebugMessageControlARB (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); GLAPI void APIENTRY glDebugMessageInsertARB (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); GLAPI void APIENTRY glDebugMessageCallbackARB (GLDEBUGPROCARB callback, const void *userParam); GLAPI GLuint APIENTRY glGetDebugMessageLogARB (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); #endif #endif /* GL_ARB_debug_output */ #ifndef GL_ARB_depth_buffer_float #define GL_ARB_depth_buffer_float 1 #endif /* GL_ARB_depth_buffer_float */ #ifndef GL_ARB_depth_clamp #define GL_ARB_depth_clamp 1 #endif /* GL_ARB_depth_clamp */ #ifndef GL_ARB_depth_texture #define GL_ARB_depth_texture 1 #define GL_DEPTH_COMPONENT16_ARB 0x81A5 #define GL_DEPTH_COMPONENT24_ARB 0x81A6 #define GL_DEPTH_COMPONENT32_ARB 0x81A7 #define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A #define GL_DEPTH_TEXTURE_MODE_ARB 0x884B #endif /* GL_ARB_depth_texture */ #ifndef GL_ARB_derivative_control #define GL_ARB_derivative_control 1 #endif /* GL_ARB_derivative_control */ #ifndef GL_ARB_direct_state_access #define GL_ARB_direct_state_access 1 #endif /* GL_ARB_direct_state_access */ #ifndef GL_ARB_draw_buffers #define GL_ARB_draw_buffers 1 #define GL_MAX_DRAW_BUFFERS_ARB 0x8824 #define GL_DRAW_BUFFER0_ARB 0x8825 #define GL_DRAW_BUFFER1_ARB 0x8826 #define GL_DRAW_BUFFER2_ARB 0x8827 #define GL_DRAW_BUFFER3_ARB 0x8828 #define GL_DRAW_BUFFER4_ARB 0x8829 #define GL_DRAW_BUFFER5_ARB 0x882A #define GL_DRAW_BUFFER6_ARB 0x882B #define GL_DRAW_BUFFER7_ARB 0x882C #define GL_DRAW_BUFFER8_ARB 0x882D #define GL_DRAW_BUFFER9_ARB 0x882E #define GL_DRAW_BUFFER10_ARB 0x882F #define GL_DRAW_BUFFER11_ARB 0x8830 #define GL_DRAW_BUFFER12_ARB 0x8831 #define GL_DRAW_BUFFER13_ARB 0x8832 #define GL_DRAW_BUFFER14_ARB 0x8833 #define GL_DRAW_BUFFER15_ARB 0x8834 typedef void (APIENTRYP PFNGLDRAWBUFFERSARBPROC) (GLsizei n, const GLenum *bufs); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawBuffersARB (GLsizei n, const GLenum *bufs); #endif #endif /* GL_ARB_draw_buffers */ #ifndef GL_ARB_draw_buffers_blend #define GL_ARB_draw_buffers_blend 1 typedef void (APIENTRYP PFNGLBLENDEQUATIONIARBPROC) (GLuint buf, GLenum mode); typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIARBPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); typedef void (APIENTRYP PFNGLBLENDFUNCIARBPROC) (GLuint buf, GLenum src, GLenum dst); typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIARBPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendEquationiARB (GLuint buf, GLenum mode); GLAPI void APIENTRY glBlendEquationSeparateiARB (GLuint buf, GLenum modeRGB, GLenum modeAlpha); GLAPI void APIENTRY glBlendFunciARB (GLuint buf, GLenum src, GLenum dst); GLAPI void APIENTRY glBlendFuncSeparateiARB (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); #endif #endif /* GL_ARB_draw_buffers_blend */ #ifndef GL_ARB_draw_elements_base_vertex #define GL_ARB_draw_elements_base_vertex 1 #endif /* GL_ARB_draw_elements_base_vertex */ #ifndef GL_ARB_draw_indirect #define GL_ARB_draw_indirect 1 #endif /* GL_ARB_draw_indirect */ #ifndef GL_ARB_draw_instanced #define GL_ARB_draw_instanced 1 typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDARBPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDARBPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawArraysInstancedARB (GLenum mode, GLint first, GLsizei count, GLsizei primcount); GLAPI void APIENTRY glDrawElementsInstancedARB (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); #endif #endif /* GL_ARB_draw_instanced */ #ifndef GL_ARB_enhanced_layouts #define GL_ARB_enhanced_layouts 1 #endif /* GL_ARB_enhanced_layouts */ #ifndef GL_ARB_explicit_attrib_location #define GL_ARB_explicit_attrib_location 1 #endif /* GL_ARB_explicit_attrib_location */ #ifndef GL_ARB_explicit_uniform_location #define GL_ARB_explicit_uniform_location 1 #endif /* GL_ARB_explicit_uniform_location */ #ifndef GL_ARB_fragment_coord_conventions #define GL_ARB_fragment_coord_conventions 1 #endif /* GL_ARB_fragment_coord_conventions */ #ifndef GL_ARB_fragment_layer_viewport #define GL_ARB_fragment_layer_viewport 1 #endif /* GL_ARB_fragment_layer_viewport */ #ifndef GL_ARB_fragment_program #define GL_ARB_fragment_program 1 #define GL_FRAGMENT_PROGRAM_ARB 0x8804 #define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 #define GL_PROGRAM_LENGTH_ARB 0x8627 #define GL_PROGRAM_FORMAT_ARB 0x8876 #define GL_PROGRAM_BINDING_ARB 0x8677 #define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 #define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 #define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 #define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 #define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 #define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 #define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 #define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 #define GL_PROGRAM_PARAMETERS_ARB 0x88A8 #define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 #define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA #define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB #define GL_PROGRAM_ATTRIBS_ARB 0x88AC #define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD #define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE #define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF #define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 #define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 #define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 #define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 #define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 #define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 #define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 #define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 #define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A #define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B #define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C #define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D #define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E #define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F #define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 #define GL_PROGRAM_STRING_ARB 0x8628 #define GL_PROGRAM_ERROR_POSITION_ARB 0x864B #define GL_CURRENT_MATRIX_ARB 0x8641 #define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 #define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 #define GL_MAX_PROGRAM_MATRICES_ARB 0x862F #define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E #define GL_MAX_TEXTURE_COORDS_ARB 0x8871 #define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 #define GL_PROGRAM_ERROR_STRING_ARB 0x8874 #define GL_MATRIX0_ARB 0x88C0 #define GL_MATRIX1_ARB 0x88C1 #define GL_MATRIX2_ARB 0x88C2 #define GL_MATRIX3_ARB 0x88C3 #define GL_MATRIX4_ARB 0x88C4 #define GL_MATRIX5_ARB 0x88C5 #define GL_MATRIX6_ARB 0x88C6 #define GL_MATRIX7_ARB 0x88C7 #define GL_MATRIX8_ARB 0x88C8 #define GL_MATRIX9_ARB 0x88C9 #define GL_MATRIX10_ARB 0x88CA #define GL_MATRIX11_ARB 0x88CB #define GL_MATRIX12_ARB 0x88CC #define GL_MATRIX13_ARB 0x88CD #define GL_MATRIX14_ARB 0x88CE #define GL_MATRIX15_ARB 0x88CF #define GL_MATRIX16_ARB 0x88D0 #define GL_MATRIX17_ARB 0x88D1 #define GL_MATRIX18_ARB 0x88D2 #define GL_MATRIX19_ARB 0x88D3 #define GL_MATRIX20_ARB 0x88D4 #define GL_MATRIX21_ARB 0x88D5 #define GL_MATRIX22_ARB 0x88D6 #define GL_MATRIX23_ARB 0x88D7 #define GL_MATRIX24_ARB 0x88D8 #define GL_MATRIX25_ARB 0x88D9 #define GL_MATRIX26_ARB 0x88DA #define GL_MATRIX27_ARB 0x88DB #define GL_MATRIX28_ARB 0x88DC #define GL_MATRIX29_ARB 0x88DD #define GL_MATRIX30_ARB 0x88DE #define GL_MATRIX31_ARB 0x88DF typedef void (APIENTRYP PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const void *string); typedef void (APIENTRYP PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); typedef void (APIENTRYP PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); typedef void (APIENTRYP PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); typedef void (APIENTRYP PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, void *string); typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glProgramStringARB (GLenum target, GLenum format, GLsizei len, const void *string); GLAPI void APIENTRY glBindProgramARB (GLenum target, GLuint program); GLAPI void APIENTRY glDeleteProgramsARB (GLsizei n, const GLuint *programs); GLAPI void APIENTRY glGenProgramsARB (GLsizei n, GLuint *programs); GLAPI void APIENTRY glProgramEnvParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glProgramEnvParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); GLAPI void APIENTRY glProgramEnvParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glProgramEnvParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); GLAPI void APIENTRY glProgramLocalParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glProgramLocalParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); GLAPI void APIENTRY glProgramLocalParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glProgramLocalParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); GLAPI void APIENTRY glGetProgramEnvParameterdvARB (GLenum target, GLuint index, GLdouble *params); GLAPI void APIENTRY glGetProgramEnvParameterfvARB (GLenum target, GLuint index, GLfloat *params); GLAPI void APIENTRY glGetProgramLocalParameterdvARB (GLenum target, GLuint index, GLdouble *params); GLAPI void APIENTRY glGetProgramLocalParameterfvARB (GLenum target, GLuint index, GLfloat *params); GLAPI void APIENTRY glGetProgramivARB (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetProgramStringARB (GLenum target, GLenum pname, void *string); GLAPI GLboolean APIENTRY glIsProgramARB (GLuint program); #endif #endif /* GL_ARB_fragment_program */ #ifndef GL_ARB_fragment_program_shadow #define GL_ARB_fragment_program_shadow 1 #endif /* GL_ARB_fragment_program_shadow */ #ifndef GL_ARB_fragment_shader #define GL_ARB_fragment_shader 1 #define GL_FRAGMENT_SHADER_ARB 0x8B30 #define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 #define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B #endif /* GL_ARB_fragment_shader */ #ifndef GL_ARB_fragment_shader_interlock #define GL_ARB_fragment_shader_interlock 1 #endif /* GL_ARB_fragment_shader_interlock */ #ifndef GL_ARB_framebuffer_no_attachments #define GL_ARB_framebuffer_no_attachments 1 #endif /* GL_ARB_framebuffer_no_attachments */ #ifndef GL_ARB_framebuffer_object #define GL_ARB_framebuffer_object 1 #endif /* GL_ARB_framebuffer_object */ #ifndef GL_ARB_framebuffer_sRGB #define GL_ARB_framebuffer_sRGB 1 #endif /* GL_ARB_framebuffer_sRGB */ #ifndef GL_ARB_geometry_shader4 #define GL_ARB_geometry_shader4 1 #define GL_LINES_ADJACENCY_ARB 0x000A #define GL_LINE_STRIP_ADJACENCY_ARB 0x000B #define GL_TRIANGLES_ADJACENCY_ARB 0x000C #define GL_TRIANGLE_STRIP_ADJACENCY_ARB 0x000D #define GL_PROGRAM_POINT_SIZE_ARB 0x8642 #define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB 0x8C29 #define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_ARB 0x8DA7 #define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_ARB 0x8DA8 #define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB 0x8DA9 #define GL_GEOMETRY_SHADER_ARB 0x8DD9 #define GL_GEOMETRY_VERTICES_OUT_ARB 0x8DDA #define GL_GEOMETRY_INPUT_TYPE_ARB 0x8DDB #define GL_GEOMETRY_OUTPUT_TYPE_ARB 0x8DDC #define GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB 0x8DDD #define GL_MAX_VERTEX_VARYING_COMPONENTS_ARB 0x8DDE #define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB 0x8DDF #define GL_MAX_GEOMETRY_OUTPUT_VERTICES_ARB 0x8DE0 #define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB 0x8DE1 typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIARBPROC) (GLuint program, GLenum pname, GLint value); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glProgramParameteriARB (GLuint program, GLenum pname, GLint value); GLAPI void APIENTRY glFramebufferTextureARB (GLenum target, GLenum attachment, GLuint texture, GLint level); GLAPI void APIENTRY glFramebufferTextureLayerARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); GLAPI void APIENTRY glFramebufferTextureFaceARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); #endif #endif /* GL_ARB_geometry_shader4 */ #ifndef GL_ARB_get_program_binary #define GL_ARB_get_program_binary 1 #endif /* GL_ARB_get_program_binary */ #ifndef GL_ARB_get_texture_sub_image #define GL_ARB_get_texture_sub_image 1 #endif /* GL_ARB_get_texture_sub_image */ #ifndef GL_ARB_gl_spirv #define GL_ARB_gl_spirv 1 #define GL_SHADER_BINARY_FORMAT_SPIR_V_ARB 0x9551 #define GL_SPIR_V_BINARY_ARB 0x9552 typedef void (APIENTRYP PFNGLSPECIALIZESHADERARBPROC) (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSpecializeShaderARB (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); #endif #endif /* GL_ARB_gl_spirv */ #ifndef GL_ARB_gpu_shader5 #define GL_ARB_gpu_shader5 1 #endif /* GL_ARB_gpu_shader5 */ #ifndef GL_ARB_gpu_shader_fp64 #define GL_ARB_gpu_shader_fp64 1 #endif /* GL_ARB_gpu_shader_fp64 */ #ifndef GL_ARB_gpu_shader_int64 #define GL_ARB_gpu_shader_int64 1 #define GL_INT64_ARB 0x140E #define GL_INT64_VEC2_ARB 0x8FE9 #define GL_INT64_VEC3_ARB 0x8FEA #define GL_INT64_VEC4_ARB 0x8FEB #define GL_UNSIGNED_INT64_VEC2_ARB 0x8FF5 #define GL_UNSIGNED_INT64_VEC3_ARB 0x8FF6 #define GL_UNSIGNED_INT64_VEC4_ARB 0x8FF7 typedef void (APIENTRYP PFNGLUNIFORM1I64ARBPROC) (GLint location, GLint64 x); typedef void (APIENTRYP PFNGLUNIFORM2I64ARBPROC) (GLint location, GLint64 x, GLint64 y); typedef void (APIENTRYP PFNGLUNIFORM3I64ARBPROC) (GLint location, GLint64 x, GLint64 y, GLint64 z); typedef void (APIENTRYP PFNGLUNIFORM4I64ARBPROC) (GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); typedef void (APIENTRYP PFNGLUNIFORM1I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); typedef void (APIENTRYP PFNGLUNIFORM2I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); typedef void (APIENTRYP PFNGLUNIFORM3I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); typedef void (APIENTRYP PFNGLUNIFORM4I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); typedef void (APIENTRYP PFNGLUNIFORM1UI64ARBPROC) (GLint location, GLuint64 x); typedef void (APIENTRYP PFNGLUNIFORM2UI64ARBPROC) (GLint location, GLuint64 x, GLuint64 y); typedef void (APIENTRYP PFNGLUNIFORM3UI64ARBPROC) (GLint location, GLuint64 x, GLuint64 y, GLuint64 z); typedef void (APIENTRYP PFNGLUNIFORM4UI64ARBPROC) (GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); typedef void (APIENTRYP PFNGLUNIFORM1UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); typedef void (APIENTRYP PFNGLUNIFORM2UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); typedef void (APIENTRYP PFNGLUNIFORM3UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); typedef void (APIENTRYP PFNGLUNIFORM4UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); typedef void (APIENTRYP PFNGLGETUNIFORMI64VARBPROC) (GLuint program, GLint location, GLint64 *params); typedef void (APIENTRYP PFNGLGETUNIFORMUI64VARBPROC) (GLuint program, GLint location, GLuint64 *params); typedef void (APIENTRYP PFNGLGETNUNIFORMI64VARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLint64 *params); typedef void (APIENTRYP PFNGLGETNUNIFORMUI64VARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint64 *params); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64ARBPROC) (GLuint program, GLint location, GLint64 x); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64ARBPROC) (GLuint program, GLint location, GLint64 x, GLint64 y); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64ARBPROC) (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64ARBPROC) (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64ARBPROC) (GLuint program, GLint location, GLuint64 x); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64ARBPROC) (GLuint program, GLint location, GLuint64 x, GLuint64 y); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64ARBPROC) (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64ARBPROC) (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glUniform1i64ARB (GLint location, GLint64 x); GLAPI void APIENTRY glUniform2i64ARB (GLint location, GLint64 x, GLint64 y); GLAPI void APIENTRY glUniform3i64ARB (GLint location, GLint64 x, GLint64 y, GLint64 z); GLAPI void APIENTRY glUniform4i64ARB (GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); GLAPI void APIENTRY glUniform1i64vARB (GLint location, GLsizei count, const GLint64 *value); GLAPI void APIENTRY glUniform2i64vARB (GLint location, GLsizei count, const GLint64 *value); GLAPI void APIENTRY glUniform3i64vARB (GLint location, GLsizei count, const GLint64 *value); GLAPI void APIENTRY glUniform4i64vARB (GLint location, GLsizei count, const GLint64 *value); GLAPI void APIENTRY glUniform1ui64ARB (GLint location, GLuint64 x); GLAPI void APIENTRY glUniform2ui64ARB (GLint location, GLuint64 x, GLuint64 y); GLAPI void APIENTRY glUniform3ui64ARB (GLint location, GLuint64 x, GLuint64 y, GLuint64 z); GLAPI void APIENTRY glUniform4ui64ARB (GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); GLAPI void APIENTRY glUniform1ui64vARB (GLint location, GLsizei count, const GLuint64 *value); GLAPI void APIENTRY glUniform2ui64vARB (GLint location, GLsizei count, const GLuint64 *value); GLAPI void APIENTRY glUniform3ui64vARB (GLint location, GLsizei count, const GLuint64 *value); GLAPI void APIENTRY glUniform4ui64vARB (GLint location, GLsizei count, const GLuint64 *value); GLAPI void APIENTRY glGetUniformi64vARB (GLuint program, GLint location, GLint64 *params); GLAPI void APIENTRY glGetUniformui64vARB (GLuint program, GLint location, GLuint64 *params); GLAPI void APIENTRY glGetnUniformi64vARB (GLuint program, GLint location, GLsizei bufSize, GLint64 *params); GLAPI void APIENTRY glGetnUniformui64vARB (GLuint program, GLint location, GLsizei bufSize, GLuint64 *params); GLAPI void APIENTRY glProgramUniform1i64ARB (GLuint program, GLint location, GLint64 x); GLAPI void APIENTRY glProgramUniform2i64ARB (GLuint program, GLint location, GLint64 x, GLint64 y); GLAPI void APIENTRY glProgramUniform3i64ARB (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z); GLAPI void APIENTRY glProgramUniform4i64ARB (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); GLAPI void APIENTRY glProgramUniform1i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); GLAPI void APIENTRY glProgramUniform2i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); GLAPI void APIENTRY glProgramUniform3i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); GLAPI void APIENTRY glProgramUniform4i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); GLAPI void APIENTRY glProgramUniform1ui64ARB (GLuint program, GLint location, GLuint64 x); GLAPI void APIENTRY glProgramUniform2ui64ARB (GLuint program, GLint location, GLuint64 x, GLuint64 y); GLAPI void APIENTRY glProgramUniform3ui64ARB (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z); GLAPI void APIENTRY glProgramUniform4ui64ARB (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); GLAPI void APIENTRY glProgramUniform1ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); GLAPI void APIENTRY glProgramUniform2ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); GLAPI void APIENTRY glProgramUniform3ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); GLAPI void APIENTRY glProgramUniform4ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); #endif #endif /* GL_ARB_gpu_shader_int64 */ #ifndef GL_ARB_half_float_pixel #define GL_ARB_half_float_pixel 1 typedef khronos_uint16_t GLhalfARB; #define GL_HALF_FLOAT_ARB 0x140B #endif /* GL_ARB_half_float_pixel */ #ifndef GL_ARB_half_float_vertex #define GL_ARB_half_float_vertex 1 #endif /* GL_ARB_half_float_vertex */ #ifndef GL_ARB_imaging #define GL_ARB_imaging 1 #define GL_CONVOLUTION_BORDER_MODE 0x8013 #define GL_CONVOLUTION_FILTER_SCALE 0x8014 #define GL_CONVOLUTION_FILTER_BIAS 0x8015 #define GL_REDUCE 0x8016 #define GL_CONVOLUTION_FORMAT 0x8017 #define GL_CONVOLUTION_WIDTH 0x8018 #define GL_CONVOLUTION_HEIGHT 0x8019 #define GL_MAX_CONVOLUTION_WIDTH 0x801A #define GL_MAX_CONVOLUTION_HEIGHT 0x801B #define GL_POST_CONVOLUTION_RED_SCALE 0x801C #define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D #define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E #define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F #define GL_POST_CONVOLUTION_RED_BIAS 0x8020 #define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 #define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 #define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 #define GL_HISTOGRAM_WIDTH 0x8026 #define GL_HISTOGRAM_FORMAT 0x8027 #define GL_HISTOGRAM_RED_SIZE 0x8028 #define GL_HISTOGRAM_GREEN_SIZE 0x8029 #define GL_HISTOGRAM_BLUE_SIZE 0x802A #define GL_HISTOGRAM_ALPHA_SIZE 0x802B #define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C #define GL_HISTOGRAM_SINK 0x802D #define GL_MINMAX_FORMAT 0x802F #define GL_MINMAX_SINK 0x8030 #define GL_TABLE_TOO_LARGE 0x8031 #define GL_COLOR_MATRIX 0x80B1 #define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 #define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 #define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 #define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 #define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 #define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 #define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 #define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 #define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA #define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB #define GL_COLOR_TABLE_SCALE 0x80D6 #define GL_COLOR_TABLE_BIAS 0x80D7 #define GL_COLOR_TABLE_FORMAT 0x80D8 #define GL_COLOR_TABLE_WIDTH 0x80D9 #define GL_COLOR_TABLE_RED_SIZE 0x80DA #define GL_COLOR_TABLE_GREEN_SIZE 0x80DB #define GL_COLOR_TABLE_BLUE_SIZE 0x80DC #define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD #define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE #define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF #define GL_CONSTANT_BORDER 0x8151 #define GL_REPLICATE_BORDER 0x8153 #define GL_CONVOLUTION_BORDER_COLOR 0x8154 typedef void (APIENTRYP PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); typedef void (APIENTRYP PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, void *table); typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, void *image); typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); typedef void (APIENTRYP PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); typedef void (APIENTRYP PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); typedef void (APIENTRYP PFNGLRESETHISTOGRAMPROC) (GLenum target); typedef void (APIENTRYP PFNGLRESETMINMAXPROC) (GLenum target); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glColorTable (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); GLAPI void APIENTRY glColorTableParameterfv (GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glColorTableParameteriv (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glCopyColorTable (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); GLAPI void APIENTRY glGetColorTable (GLenum target, GLenum format, GLenum type, void *table); GLAPI void APIENTRY glGetColorTableParameterfv (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetColorTableParameteriv (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glColorSubTable (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); GLAPI void APIENTRY glCopyColorSubTable (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); GLAPI void APIENTRY glConvolutionFilter1D (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); GLAPI void APIENTRY glConvolutionFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); GLAPI void APIENTRY glConvolutionParameterf (GLenum target, GLenum pname, GLfloat params); GLAPI void APIENTRY glConvolutionParameterfv (GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glConvolutionParameteri (GLenum target, GLenum pname, GLint params); GLAPI void APIENTRY glConvolutionParameteriv (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glCopyConvolutionFilter1D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); GLAPI void APIENTRY glCopyConvolutionFilter2D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glGetConvolutionFilter (GLenum target, GLenum format, GLenum type, void *image); GLAPI void APIENTRY glGetConvolutionParameterfv (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetConvolutionParameteriv (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetSeparableFilter (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); GLAPI void APIENTRY glSeparableFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); GLAPI void APIENTRY glGetHistogram (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); GLAPI void APIENTRY glGetHistogramParameterfv (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetHistogramParameteriv (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetMinmax (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); GLAPI void APIENTRY glGetMinmaxParameterfv (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetMinmaxParameteriv (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glHistogram (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); GLAPI void APIENTRY glMinmax (GLenum target, GLenum internalformat, GLboolean sink); GLAPI void APIENTRY glResetHistogram (GLenum target); GLAPI void APIENTRY glResetMinmax (GLenum target); #endif #endif /* GL_ARB_imaging */ #ifndef GL_ARB_indirect_parameters #define GL_ARB_indirect_parameters 1 #define GL_PARAMETER_BUFFER_ARB 0x80EE #define GL_PARAMETER_BUFFER_BINDING_ARB 0x80EF typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTCOUNTARBPROC) (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTARBPROC) (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMultiDrawArraysIndirectCountARB (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); GLAPI void APIENTRY glMultiDrawElementsIndirectCountARB (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); #endif #endif /* GL_ARB_indirect_parameters */ #ifndef GL_ARB_instanced_arrays #define GL_ARB_instanced_arrays 1 #define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB 0x88FE typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORARBPROC) (GLuint index, GLuint divisor); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertexAttribDivisorARB (GLuint index, GLuint divisor); #endif #endif /* GL_ARB_instanced_arrays */ #ifndef GL_ARB_internalformat_query #define GL_ARB_internalformat_query 1 #endif /* GL_ARB_internalformat_query */ #ifndef GL_ARB_internalformat_query2 #define GL_ARB_internalformat_query2 1 #define GL_SRGB_DECODE_ARB 0x8299 #define GL_VIEW_CLASS_EAC_R11 0x9383 #define GL_VIEW_CLASS_EAC_RG11 0x9384 #define GL_VIEW_CLASS_ETC2_RGB 0x9385 #define GL_VIEW_CLASS_ETC2_RGBA 0x9386 #define GL_VIEW_CLASS_ETC2_EAC_RGBA 0x9387 #define GL_VIEW_CLASS_ASTC_4x4_RGBA 0x9388 #define GL_VIEW_CLASS_ASTC_5x4_RGBA 0x9389 #define GL_VIEW_CLASS_ASTC_5x5_RGBA 0x938A #define GL_VIEW_CLASS_ASTC_6x5_RGBA 0x938B #define GL_VIEW_CLASS_ASTC_6x6_RGBA 0x938C #define GL_VIEW_CLASS_ASTC_8x5_RGBA 0x938D #define GL_VIEW_CLASS_ASTC_8x6_RGBA 0x938E #define GL_VIEW_CLASS_ASTC_8x8_RGBA 0x938F #define GL_VIEW_CLASS_ASTC_10x5_RGBA 0x9390 #define GL_VIEW_CLASS_ASTC_10x6_RGBA 0x9391 #define GL_VIEW_CLASS_ASTC_10x8_RGBA 0x9392 #define GL_VIEW_CLASS_ASTC_10x10_RGBA 0x9393 #define GL_VIEW_CLASS_ASTC_12x10_RGBA 0x9394 #define GL_VIEW_CLASS_ASTC_12x12_RGBA 0x9395 #endif /* GL_ARB_internalformat_query2 */ #ifndef GL_ARB_invalidate_subdata #define GL_ARB_invalidate_subdata 1 #endif /* GL_ARB_invalidate_subdata */ #ifndef GL_ARB_map_buffer_alignment #define GL_ARB_map_buffer_alignment 1 #endif /* GL_ARB_map_buffer_alignment */ #ifndef GL_ARB_map_buffer_range #define GL_ARB_map_buffer_range 1 #endif /* GL_ARB_map_buffer_range */ #ifndef GL_ARB_matrix_palette #define GL_ARB_matrix_palette 1 #define GL_MATRIX_PALETTE_ARB 0x8840 #define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 #define GL_MAX_PALETTE_MATRICES_ARB 0x8842 #define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 #define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 #define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 #define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 #define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 #define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 #define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 typedef void (APIENTRYP PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); typedef void (APIENTRYP PFNGLMATRIXINDEXUBVARBPROC) (GLint size, const GLubyte *indices); typedef void (APIENTRYP PFNGLMATRIXINDEXUSVARBPROC) (GLint size, const GLushort *indices); typedef void (APIENTRYP PFNGLMATRIXINDEXUIVARBPROC) (GLint size, const GLuint *indices); typedef void (APIENTRYP PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCurrentPaletteMatrixARB (GLint index); GLAPI void APIENTRY glMatrixIndexubvARB (GLint size, const GLubyte *indices); GLAPI void APIENTRY glMatrixIndexusvARB (GLint size, const GLushort *indices); GLAPI void APIENTRY glMatrixIndexuivARB (GLint size, const GLuint *indices); GLAPI void APIENTRY glMatrixIndexPointerARB (GLint size, GLenum type, GLsizei stride, const void *pointer); #endif #endif /* GL_ARB_matrix_palette */ #ifndef GL_ARB_multi_bind #define GL_ARB_multi_bind 1 #endif /* GL_ARB_multi_bind */ #ifndef GL_ARB_multi_draw_indirect #define GL_ARB_multi_draw_indirect 1 #endif /* GL_ARB_multi_draw_indirect */ #ifndef GL_ARB_multisample #define GL_ARB_multisample 1 #define GL_MULTISAMPLE_ARB 0x809D #define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E #define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F #define GL_SAMPLE_COVERAGE_ARB 0x80A0 #define GL_SAMPLE_BUFFERS_ARB 0x80A8 #define GL_SAMPLES_ARB 0x80A9 #define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA #define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB #define GL_MULTISAMPLE_BIT_ARB 0x20000000 typedef void (APIENTRYP PFNGLSAMPLECOVERAGEARBPROC) (GLfloat value, GLboolean invert); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSampleCoverageARB (GLfloat value, GLboolean invert); #endif #endif /* GL_ARB_multisample */ #ifndef GL_ARB_multitexture #define GL_ARB_multitexture 1 #define GL_TEXTURE0_ARB 0x84C0 #define GL_TEXTURE1_ARB 0x84C1 #define GL_TEXTURE2_ARB 0x84C2 #define GL_TEXTURE3_ARB 0x84C3 #define GL_TEXTURE4_ARB 0x84C4 #define GL_TEXTURE5_ARB 0x84C5 #define GL_TEXTURE6_ARB 0x84C6 #define GL_TEXTURE7_ARB 0x84C7 #define GL_TEXTURE8_ARB 0x84C8 #define GL_TEXTURE9_ARB 0x84C9 #define GL_TEXTURE10_ARB 0x84CA #define GL_TEXTURE11_ARB 0x84CB #define GL_TEXTURE12_ARB 0x84CC #define GL_TEXTURE13_ARB 0x84CD #define GL_TEXTURE14_ARB 0x84CE #define GL_TEXTURE15_ARB 0x84CF #define GL_TEXTURE16_ARB 0x84D0 #define GL_TEXTURE17_ARB 0x84D1 #define GL_TEXTURE18_ARB 0x84D2 #define GL_TEXTURE19_ARB 0x84D3 #define GL_TEXTURE20_ARB 0x84D4 #define GL_TEXTURE21_ARB 0x84D5 #define GL_TEXTURE22_ARB 0x84D6 #define GL_TEXTURE23_ARB 0x84D7 #define GL_TEXTURE24_ARB 0x84D8 #define GL_TEXTURE25_ARB 0x84D9 #define GL_TEXTURE26_ARB 0x84DA #define GL_TEXTURE27_ARB 0x84DB #define GL_TEXTURE28_ARB 0x84DC #define GL_TEXTURE29_ARB 0x84DD #define GL_TEXTURE30_ARB 0x84DE #define GL_TEXTURE31_ARB 0x84DF #define GL_ACTIVE_TEXTURE_ARB 0x84E0 #define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 #define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glActiveTextureARB (GLenum texture); GLAPI void APIENTRY glClientActiveTextureARB (GLenum texture); GLAPI void APIENTRY glMultiTexCoord1dARB (GLenum target, GLdouble s); GLAPI void APIENTRY glMultiTexCoord1dvARB (GLenum target, const GLdouble *v); GLAPI void APIENTRY glMultiTexCoord1fARB (GLenum target, GLfloat s); GLAPI void APIENTRY glMultiTexCoord1fvARB (GLenum target, const GLfloat *v); GLAPI void APIENTRY glMultiTexCoord1iARB (GLenum target, GLint s); GLAPI void APIENTRY glMultiTexCoord1ivARB (GLenum target, const GLint *v); GLAPI void APIENTRY glMultiTexCoord1sARB (GLenum target, GLshort s); GLAPI void APIENTRY glMultiTexCoord1svARB (GLenum target, const GLshort *v); GLAPI void APIENTRY glMultiTexCoord2dARB (GLenum target, GLdouble s, GLdouble t); GLAPI void APIENTRY glMultiTexCoord2dvARB (GLenum target, const GLdouble *v); GLAPI void APIENTRY glMultiTexCoord2fARB (GLenum target, GLfloat s, GLfloat t); GLAPI void APIENTRY glMultiTexCoord2fvARB (GLenum target, const GLfloat *v); GLAPI void APIENTRY glMultiTexCoord2iARB (GLenum target, GLint s, GLint t); GLAPI void APIENTRY glMultiTexCoord2ivARB (GLenum target, const GLint *v); GLAPI void APIENTRY glMultiTexCoord2sARB (GLenum target, GLshort s, GLshort t); GLAPI void APIENTRY glMultiTexCoord2svARB (GLenum target, const GLshort *v); GLAPI void APIENTRY glMultiTexCoord3dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r); GLAPI void APIENTRY glMultiTexCoord3dvARB (GLenum target, const GLdouble *v); GLAPI void APIENTRY glMultiTexCoord3fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r); GLAPI void APIENTRY glMultiTexCoord3fvARB (GLenum target, const GLfloat *v); GLAPI void APIENTRY glMultiTexCoord3iARB (GLenum target, GLint s, GLint t, GLint r); GLAPI void APIENTRY glMultiTexCoord3ivARB (GLenum target, const GLint *v); GLAPI void APIENTRY glMultiTexCoord3sARB (GLenum target, GLshort s, GLshort t, GLshort r); GLAPI void APIENTRY glMultiTexCoord3svARB (GLenum target, const GLshort *v); GLAPI void APIENTRY glMultiTexCoord4dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); GLAPI void APIENTRY glMultiTexCoord4dvARB (GLenum target, const GLdouble *v); GLAPI void APIENTRY glMultiTexCoord4fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); GLAPI void APIENTRY glMultiTexCoord4fvARB (GLenum target, const GLfloat *v); GLAPI void APIENTRY glMultiTexCoord4iARB (GLenum target, GLint s, GLint t, GLint r, GLint q); GLAPI void APIENTRY glMultiTexCoord4ivARB (GLenum target, const GLint *v); GLAPI void APIENTRY glMultiTexCoord4sARB (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); GLAPI void APIENTRY glMultiTexCoord4svARB (GLenum target, const GLshort *v); #endif #endif /* GL_ARB_multitexture */ #ifndef GL_ARB_occlusion_query #define GL_ARB_occlusion_query 1 #define GL_QUERY_COUNTER_BITS_ARB 0x8864 #define GL_CURRENT_QUERY_ARB 0x8865 #define GL_QUERY_RESULT_ARB 0x8866 #define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 #define GL_SAMPLES_PASSED_ARB 0x8914 typedef void (APIENTRYP PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint *ids); typedef void (APIENTRYP PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint *ids); typedef GLboolean (APIENTRYP PFNGLISQUERYARBPROC) (GLuint id); typedef void (APIENTRYP PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); typedef void (APIENTRYP PFNGLENDQUERYARBPROC) (GLenum target); typedef void (APIENTRYP PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGenQueriesARB (GLsizei n, GLuint *ids); GLAPI void APIENTRY glDeleteQueriesARB (GLsizei n, const GLuint *ids); GLAPI GLboolean APIENTRY glIsQueryARB (GLuint id); GLAPI void APIENTRY glBeginQueryARB (GLenum target, GLuint id); GLAPI void APIENTRY glEndQueryARB (GLenum target); GLAPI void APIENTRY glGetQueryivARB (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetQueryObjectivARB (GLuint id, GLenum pname, GLint *params); GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint id, GLenum pname, GLuint *params); #endif #endif /* GL_ARB_occlusion_query */ #ifndef GL_ARB_occlusion_query2 #define GL_ARB_occlusion_query2 1 #endif /* GL_ARB_occlusion_query2 */ #ifndef GL_ARB_parallel_shader_compile #define GL_ARB_parallel_shader_compile 1 #define GL_MAX_SHADER_COMPILER_THREADS_ARB 0x91B0 #define GL_COMPLETION_STATUS_ARB 0x91B1 typedef void (APIENTRYP PFNGLMAXSHADERCOMPILERTHREADSARBPROC) (GLuint count); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMaxShaderCompilerThreadsARB (GLuint count); #endif #endif /* GL_ARB_parallel_shader_compile */ #ifndef GL_ARB_pipeline_statistics_query #define GL_ARB_pipeline_statistics_query 1 #define GL_VERTICES_SUBMITTED_ARB 0x82EE #define GL_PRIMITIVES_SUBMITTED_ARB 0x82EF #define GL_VERTEX_SHADER_INVOCATIONS_ARB 0x82F0 #define GL_TESS_CONTROL_SHADER_PATCHES_ARB 0x82F1 #define GL_TESS_EVALUATION_SHADER_INVOCATIONS_ARB 0x82F2 #define GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED_ARB 0x82F3 #define GL_FRAGMENT_SHADER_INVOCATIONS_ARB 0x82F4 #define GL_COMPUTE_SHADER_INVOCATIONS_ARB 0x82F5 #define GL_CLIPPING_INPUT_PRIMITIVES_ARB 0x82F6 #define GL_CLIPPING_OUTPUT_PRIMITIVES_ARB 0x82F7 #endif /* GL_ARB_pipeline_statistics_query */ #ifndef GL_ARB_pixel_buffer_object #define GL_ARB_pixel_buffer_object 1 #define GL_PIXEL_PACK_BUFFER_ARB 0x88EB #define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC #define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED #define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF #endif /* GL_ARB_pixel_buffer_object */ #ifndef GL_ARB_point_parameters #define GL_ARB_point_parameters 1 #define GL_POINT_SIZE_MIN_ARB 0x8126 #define GL_POINT_SIZE_MAX_ARB 0x8127 #define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 #define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 typedef void (APIENTRYP PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, const GLfloat *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPointParameterfARB (GLenum pname, GLfloat param); GLAPI void APIENTRY glPointParameterfvARB (GLenum pname, const GLfloat *params); #endif #endif /* GL_ARB_point_parameters */ #ifndef GL_ARB_point_sprite #define GL_ARB_point_sprite 1 #define GL_POINT_SPRITE_ARB 0x8861 #define GL_COORD_REPLACE_ARB 0x8862 #endif /* GL_ARB_point_sprite */ #ifndef GL_ARB_polygon_offset_clamp #define GL_ARB_polygon_offset_clamp 1 #endif /* GL_ARB_polygon_offset_clamp */ #ifndef GL_ARB_post_depth_coverage #define GL_ARB_post_depth_coverage 1 #endif /* GL_ARB_post_depth_coverage */ #ifndef GL_ARB_program_interface_query #define GL_ARB_program_interface_query 1 #endif /* GL_ARB_program_interface_query */ #ifndef GL_ARB_provoking_vertex #define GL_ARB_provoking_vertex 1 #endif /* GL_ARB_provoking_vertex */ #ifndef GL_ARB_query_buffer_object #define GL_ARB_query_buffer_object 1 #endif /* GL_ARB_query_buffer_object */ #ifndef GL_ARB_robust_buffer_access_behavior #define GL_ARB_robust_buffer_access_behavior 1 #endif /* GL_ARB_robust_buffer_access_behavior */ #ifndef GL_ARB_robustness #define GL_ARB_robustness 1 #define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB 0x00000004 #define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 #define GL_GUILTY_CONTEXT_RESET_ARB 0x8253 #define GL_INNOCENT_CONTEXT_RESET_ARB 0x8254 #define GL_UNKNOWN_CONTEXT_RESET_ARB 0x8255 #define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 #define GL_NO_RESET_NOTIFICATION_ARB 0x8261 typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC) (void); typedef void (APIENTRYP PFNGLGETNTEXIMAGEARBPROC) (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *img); typedef void (APIENTRYP PFNGLREADNPIXELSARBPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); typedef void (APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint lod, GLsizei bufSize, void *img); typedef void (APIENTRYP PFNGLGETNUNIFORMFVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); typedef void (APIENTRYP PFNGLGETNUNIFORMIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); typedef void (APIENTRYP PFNGLGETNUNIFORMUIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint *params); typedef void (APIENTRYP PFNGLGETNUNIFORMDVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); typedef void (APIENTRYP PFNGLGETNMAPDVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); typedef void (APIENTRYP PFNGLGETNMAPFVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); typedef void (APIENTRYP PFNGLGETNMAPIVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLint *v); typedef void (APIENTRYP PFNGLGETNPIXELMAPFVARBPROC) (GLenum map, GLsizei bufSize, GLfloat *values); typedef void (APIENTRYP PFNGLGETNPIXELMAPUIVARBPROC) (GLenum map, GLsizei bufSize, GLuint *values); typedef void (APIENTRYP PFNGLGETNPIXELMAPUSVARBPROC) (GLenum map, GLsizei bufSize, GLushort *values); typedef void (APIENTRYP PFNGLGETNPOLYGONSTIPPLEARBPROC) (GLsizei bufSize, GLubyte *pattern); typedef void (APIENTRYP PFNGLGETNCOLORTABLEARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); typedef void (APIENTRYP PFNGLGETNCONVOLUTIONFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); typedef void (APIENTRYP PFNGLGETNSEPARABLEFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); typedef void (APIENTRYP PFNGLGETNHISTOGRAMARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); typedef void (APIENTRYP PFNGLGETNMINMAXARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLenum APIENTRY glGetGraphicsResetStatusARB (void); GLAPI void APIENTRY glGetnTexImageARB (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *img); GLAPI void APIENTRY glReadnPixelsARB (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); GLAPI void APIENTRY glGetnCompressedTexImageARB (GLenum target, GLint lod, GLsizei bufSize, void *img); GLAPI void APIENTRY glGetnUniformfvARB (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); GLAPI void APIENTRY glGetnUniformivARB (GLuint program, GLint location, GLsizei bufSize, GLint *params); GLAPI void APIENTRY glGetnUniformuivARB (GLuint program, GLint location, GLsizei bufSize, GLuint *params); GLAPI void APIENTRY glGetnUniformdvARB (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); GLAPI void APIENTRY glGetnMapdvARB (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); GLAPI void APIENTRY glGetnMapfvARB (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); GLAPI void APIENTRY glGetnMapivARB (GLenum target, GLenum query, GLsizei bufSize, GLint *v); GLAPI void APIENTRY glGetnPixelMapfvARB (GLenum map, GLsizei bufSize, GLfloat *values); GLAPI void APIENTRY glGetnPixelMapuivARB (GLenum map, GLsizei bufSize, GLuint *values); GLAPI void APIENTRY glGetnPixelMapusvARB (GLenum map, GLsizei bufSize, GLushort *values); GLAPI void APIENTRY glGetnPolygonStippleARB (GLsizei bufSize, GLubyte *pattern); GLAPI void APIENTRY glGetnColorTableARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); GLAPI void APIENTRY glGetnConvolutionFilterARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); GLAPI void APIENTRY glGetnSeparableFilterARB (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); GLAPI void APIENTRY glGetnHistogramARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); GLAPI void APIENTRY glGetnMinmaxARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); #endif #endif /* GL_ARB_robustness */ #ifndef GL_ARB_robustness_isolation #define GL_ARB_robustness_isolation 1 #endif /* GL_ARB_robustness_isolation */ #ifndef GL_ARB_sample_locations #define GL_ARB_sample_locations 1 #define GL_SAMPLE_LOCATION_SUBPIXEL_BITS_ARB 0x933D #define GL_SAMPLE_LOCATION_PIXEL_GRID_WIDTH_ARB 0x933E #define GL_SAMPLE_LOCATION_PIXEL_GRID_HEIGHT_ARB 0x933F #define GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_ARB 0x9340 #define GL_SAMPLE_LOCATION_ARB 0x8E50 #define GL_PROGRAMMABLE_SAMPLE_LOCATION_ARB 0x9341 #define GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_ARB 0x9342 #define GL_FRAMEBUFFER_SAMPLE_LOCATION_PIXEL_GRID_ARB 0x9343 typedef void (APIENTRYP PFNGLFRAMEBUFFERSAMPLELOCATIONSFVARBPROC) (GLenum target, GLuint start, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVARBPROC) (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLEVALUATEDEPTHVALUESARBPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFramebufferSampleLocationsfvARB (GLenum target, GLuint start, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glNamedFramebufferSampleLocationsfvARB (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glEvaluateDepthValuesARB (void); #endif #endif /* GL_ARB_sample_locations */ #ifndef GL_ARB_sample_shading #define GL_ARB_sample_shading 1 #define GL_SAMPLE_SHADING_ARB 0x8C36 #define GL_MIN_SAMPLE_SHADING_VALUE_ARB 0x8C37 typedef void (APIENTRYP PFNGLMINSAMPLESHADINGARBPROC) (GLfloat value); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMinSampleShadingARB (GLfloat value); #endif #endif /* GL_ARB_sample_shading */ #ifndef GL_ARB_sampler_objects #define GL_ARB_sampler_objects 1 #endif /* GL_ARB_sampler_objects */ #ifndef GL_ARB_seamless_cube_map #define GL_ARB_seamless_cube_map 1 #endif /* GL_ARB_seamless_cube_map */ #ifndef GL_ARB_seamless_cubemap_per_texture #define GL_ARB_seamless_cubemap_per_texture 1 #endif /* GL_ARB_seamless_cubemap_per_texture */ #ifndef GL_ARB_separate_shader_objects #define GL_ARB_separate_shader_objects 1 #endif /* GL_ARB_separate_shader_objects */ #ifndef GL_ARB_shader_atomic_counter_ops #define GL_ARB_shader_atomic_counter_ops 1 #endif /* GL_ARB_shader_atomic_counter_ops */ #ifndef GL_ARB_shader_atomic_counters #define GL_ARB_shader_atomic_counters 1 #endif /* GL_ARB_shader_atomic_counters */ #ifndef GL_ARB_shader_ballot #define GL_ARB_shader_ballot 1 #endif /* GL_ARB_shader_ballot */ #ifndef GL_ARB_shader_bit_encoding #define GL_ARB_shader_bit_encoding 1 #endif /* GL_ARB_shader_bit_encoding */ #ifndef GL_ARB_shader_clock #define GL_ARB_shader_clock 1 #endif /* GL_ARB_shader_clock */ #ifndef GL_ARB_shader_draw_parameters #define GL_ARB_shader_draw_parameters 1 #endif /* GL_ARB_shader_draw_parameters */ #ifndef GL_ARB_shader_group_vote #define GL_ARB_shader_group_vote 1 #endif /* GL_ARB_shader_group_vote */ #ifndef GL_ARB_shader_image_load_store #define GL_ARB_shader_image_load_store 1 #endif /* GL_ARB_shader_image_load_store */ #ifndef GL_ARB_shader_image_size #define GL_ARB_shader_image_size 1 #endif /* GL_ARB_shader_image_size */ #ifndef GL_ARB_shader_objects #define GL_ARB_shader_objects 1 #ifdef __APPLE__ typedef void *GLhandleARB; #else typedef unsigned int GLhandleARB; #endif typedef char GLcharARB; #define GL_PROGRAM_OBJECT_ARB 0x8B40 #define GL_SHADER_OBJECT_ARB 0x8B48 #define GL_OBJECT_TYPE_ARB 0x8B4E #define GL_OBJECT_SUBTYPE_ARB 0x8B4F #define GL_FLOAT_VEC2_ARB 0x8B50 #define GL_FLOAT_VEC3_ARB 0x8B51 #define GL_FLOAT_VEC4_ARB 0x8B52 #define GL_INT_VEC2_ARB 0x8B53 #define GL_INT_VEC3_ARB 0x8B54 #define GL_INT_VEC4_ARB 0x8B55 #define GL_BOOL_ARB 0x8B56 #define GL_BOOL_VEC2_ARB 0x8B57 #define GL_BOOL_VEC3_ARB 0x8B58 #define GL_BOOL_VEC4_ARB 0x8B59 #define GL_FLOAT_MAT2_ARB 0x8B5A #define GL_FLOAT_MAT3_ARB 0x8B5B #define GL_FLOAT_MAT4_ARB 0x8B5C #define GL_SAMPLER_1D_ARB 0x8B5D #define GL_SAMPLER_2D_ARB 0x8B5E #define GL_SAMPLER_3D_ARB 0x8B5F #define GL_SAMPLER_CUBE_ARB 0x8B60 #define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 #define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 #define GL_SAMPLER_2D_RECT_ARB 0x8B63 #define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 #define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 #define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 #define GL_OBJECT_LINK_STATUS_ARB 0x8B82 #define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 #define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 #define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 #define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 #define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 #define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 typedef void (APIENTRYP PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); typedef GLhandleARB (APIENTRYP PFNGLGETHANDLEARBPROC) (GLenum pname); typedef void (APIENTRYP PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); typedef GLhandleARB (APIENTRYP PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); typedef void (APIENTRYP PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); typedef void (APIENTRYP PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); typedef GLhandleARB (APIENTRYP PFNGLCREATEPROGRAMOBJECTARBPROC) (void); typedef void (APIENTRYP PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); typedef void (APIENTRYP PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); typedef void (APIENTRYP PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); typedef void (APIENTRYP PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); typedef void (APIENTRYP PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); typedef void (APIENTRYP PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); typedef void (APIENTRYP PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); typedef void (APIENTRYP PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); typedef void (APIENTRYP PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); typedef void (APIENTRYP PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); typedef void (APIENTRYP PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); typedef void (APIENTRYP PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); typedef void (APIENTRYP PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); typedef void (APIENTRYP PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); typedef void (APIENTRYP PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat *params); typedef void (APIENTRYP PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint *params); typedef void (APIENTRYP PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDeleteObjectARB (GLhandleARB obj); GLAPI GLhandleARB APIENTRY glGetHandleARB (GLenum pname); GLAPI void APIENTRY glDetachObjectARB (GLhandleARB containerObj, GLhandleARB attachedObj); GLAPI GLhandleARB APIENTRY glCreateShaderObjectARB (GLenum shaderType); GLAPI void APIENTRY glShaderSourceARB (GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); GLAPI void APIENTRY glCompileShaderARB (GLhandleARB shaderObj); GLAPI GLhandleARB APIENTRY glCreateProgramObjectARB (void); GLAPI void APIENTRY glAttachObjectARB (GLhandleARB containerObj, GLhandleARB obj); GLAPI void APIENTRY glLinkProgramARB (GLhandleARB programObj); GLAPI void APIENTRY glUseProgramObjectARB (GLhandleARB programObj); GLAPI void APIENTRY glValidateProgramARB (GLhandleARB programObj); GLAPI void APIENTRY glUniform1fARB (GLint location, GLfloat v0); GLAPI void APIENTRY glUniform2fARB (GLint location, GLfloat v0, GLfloat v1); GLAPI void APIENTRY glUniform3fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); GLAPI void APIENTRY glUniform4fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); GLAPI void APIENTRY glUniform1iARB (GLint location, GLint v0); GLAPI void APIENTRY glUniform2iARB (GLint location, GLint v0, GLint v1); GLAPI void APIENTRY glUniform3iARB (GLint location, GLint v0, GLint v1, GLint v2); GLAPI void APIENTRY glUniform4iARB (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); GLAPI void APIENTRY glUniform1fvARB (GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glUniform2fvARB (GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glUniform3fvARB (GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glUniform4fvARB (GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glUniform1ivARB (GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glUniform2ivARB (GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glUniform3ivARB (GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glUniform4ivARB (GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glUniformMatrix2fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glUniformMatrix3fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glUniformMatrix4fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glGetObjectParameterfvARB (GLhandleARB obj, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetObjectParameterivARB (GLhandleARB obj, GLenum pname, GLint *params); GLAPI void APIENTRY glGetInfoLogARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); GLAPI void APIENTRY glGetAttachedObjectsARB (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); GLAPI GLint APIENTRY glGetUniformLocationARB (GLhandleARB programObj, const GLcharARB *name); GLAPI void APIENTRY glGetActiveUniformARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); GLAPI void APIENTRY glGetUniformfvARB (GLhandleARB programObj, GLint location, GLfloat *params); GLAPI void APIENTRY glGetUniformivARB (GLhandleARB programObj, GLint location, GLint *params); GLAPI void APIENTRY glGetShaderSourceARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); #endif #endif /* GL_ARB_shader_objects */ #ifndef GL_ARB_shader_precision #define GL_ARB_shader_precision 1 #endif /* GL_ARB_shader_precision */ #ifndef GL_ARB_shader_stencil_export #define GL_ARB_shader_stencil_export 1 #endif /* GL_ARB_shader_stencil_export */ #ifndef GL_ARB_shader_storage_buffer_object #define GL_ARB_shader_storage_buffer_object 1 #endif /* GL_ARB_shader_storage_buffer_object */ #ifndef GL_ARB_shader_subroutine #define GL_ARB_shader_subroutine 1 #endif /* GL_ARB_shader_subroutine */ #ifndef GL_ARB_shader_texture_image_samples #define GL_ARB_shader_texture_image_samples 1 #endif /* GL_ARB_shader_texture_image_samples */ #ifndef GL_ARB_shader_texture_lod #define GL_ARB_shader_texture_lod 1 #endif /* GL_ARB_shader_texture_lod */ #ifndef GL_ARB_shader_viewport_layer_array #define GL_ARB_shader_viewport_layer_array 1 #endif /* GL_ARB_shader_viewport_layer_array */ #ifndef GL_ARB_shading_language_100 #define GL_ARB_shading_language_100 1 #define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C #endif /* GL_ARB_shading_language_100 */ #ifndef GL_ARB_shading_language_420pack #define GL_ARB_shading_language_420pack 1 #endif /* GL_ARB_shading_language_420pack */ #ifndef GL_ARB_shading_language_include #define GL_ARB_shading_language_include 1 #define GL_SHADER_INCLUDE_ARB 0x8DAE #define GL_NAMED_STRING_LENGTH_ARB 0x8DE9 #define GL_NAMED_STRING_TYPE_ARB 0x8DEA typedef void (APIENTRYP PFNGLNAMEDSTRINGARBPROC) (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); typedef void (APIENTRYP PFNGLDELETENAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); typedef void (APIENTRYP PFNGLCOMPILESHADERINCLUDEARBPROC) (GLuint shader, GLsizei count, const GLchar *const*path, const GLint *length); typedef GLboolean (APIENTRYP PFNGLISNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); typedef void (APIENTRYP PFNGLGETNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); typedef void (APIENTRYP PFNGLGETNAMEDSTRINGIVARBPROC) (GLint namelen, const GLchar *name, GLenum pname, GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glNamedStringARB (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); GLAPI void APIENTRY glDeleteNamedStringARB (GLint namelen, const GLchar *name); GLAPI void APIENTRY glCompileShaderIncludeARB (GLuint shader, GLsizei count, const GLchar *const*path, const GLint *length); GLAPI GLboolean APIENTRY glIsNamedStringARB (GLint namelen, const GLchar *name); GLAPI void APIENTRY glGetNamedStringARB (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); GLAPI void APIENTRY glGetNamedStringivARB (GLint namelen, const GLchar *name, GLenum pname, GLint *params); #endif #endif /* GL_ARB_shading_language_include */ #ifndef GL_ARB_shading_language_packing #define GL_ARB_shading_language_packing 1 #endif /* GL_ARB_shading_language_packing */ #ifndef GL_ARB_shadow #define GL_ARB_shadow 1 #define GL_TEXTURE_COMPARE_MODE_ARB 0x884C #define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D #define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E #endif /* GL_ARB_shadow */ #ifndef GL_ARB_shadow_ambient #define GL_ARB_shadow_ambient 1 #define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF #endif /* GL_ARB_shadow_ambient */ #ifndef GL_ARB_sparse_buffer #define GL_ARB_sparse_buffer 1 #define GL_SPARSE_STORAGE_BIT_ARB 0x0400 #define GL_SPARSE_BUFFER_PAGE_SIZE_ARB 0x82F8 typedef void (APIENTRYP PFNGLBUFFERPAGECOMMITMENTARBPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLboolean commit); typedef void (APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); typedef void (APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTARBPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBufferPageCommitmentARB (GLenum target, GLintptr offset, GLsizeiptr size, GLboolean commit); GLAPI void APIENTRY glNamedBufferPageCommitmentEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); GLAPI void APIENTRY glNamedBufferPageCommitmentARB (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); #endif #endif /* GL_ARB_sparse_buffer */ #ifndef GL_ARB_sparse_texture #define GL_ARB_sparse_texture 1 #define GL_TEXTURE_SPARSE_ARB 0x91A6 #define GL_VIRTUAL_PAGE_SIZE_INDEX_ARB 0x91A7 #define GL_NUM_SPARSE_LEVELS_ARB 0x91AA #define GL_NUM_VIRTUAL_PAGE_SIZES_ARB 0x91A8 #define GL_VIRTUAL_PAGE_SIZE_X_ARB 0x9195 #define GL_VIRTUAL_PAGE_SIZE_Y_ARB 0x9196 #define GL_VIRTUAL_PAGE_SIZE_Z_ARB 0x9197 #define GL_MAX_SPARSE_TEXTURE_SIZE_ARB 0x9198 #define GL_MAX_SPARSE_3D_TEXTURE_SIZE_ARB 0x9199 #define GL_MAX_SPARSE_ARRAY_TEXTURE_LAYERS_ARB 0x919A #define GL_SPARSE_TEXTURE_FULL_ARRAY_CUBE_MIPMAPS_ARB 0x91A9 typedef void (APIENTRYP PFNGLTEXPAGECOMMITMENTARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexPageCommitmentARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); #endif #endif /* GL_ARB_sparse_texture */ #ifndef GL_ARB_sparse_texture2 #define GL_ARB_sparse_texture2 1 #endif /* GL_ARB_sparse_texture2 */ #ifndef GL_ARB_sparse_texture_clamp #define GL_ARB_sparse_texture_clamp 1 #endif /* GL_ARB_sparse_texture_clamp */ #ifndef GL_ARB_spirv_extensions #define GL_ARB_spirv_extensions 1 #endif /* GL_ARB_spirv_extensions */ #ifndef GL_ARB_stencil_texturing #define GL_ARB_stencil_texturing 1 #endif /* GL_ARB_stencil_texturing */ #ifndef GL_ARB_sync #define GL_ARB_sync 1 #endif /* GL_ARB_sync */ #ifndef GL_ARB_tessellation_shader #define GL_ARB_tessellation_shader 1 #endif /* GL_ARB_tessellation_shader */ #ifndef GL_ARB_texture_barrier #define GL_ARB_texture_barrier 1 #endif /* GL_ARB_texture_barrier */ #ifndef GL_ARB_texture_border_clamp #define GL_ARB_texture_border_clamp 1 #define GL_CLAMP_TO_BORDER_ARB 0x812D #endif /* GL_ARB_texture_border_clamp */ #ifndef GL_ARB_texture_buffer_object #define GL_ARB_texture_buffer_object 1 #define GL_TEXTURE_BUFFER_ARB 0x8C2A #define GL_MAX_TEXTURE_BUFFER_SIZE_ARB 0x8C2B #define GL_TEXTURE_BINDING_BUFFER_ARB 0x8C2C #define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_ARB 0x8C2D #define GL_TEXTURE_BUFFER_FORMAT_ARB 0x8C2E typedef void (APIENTRYP PFNGLTEXBUFFERARBPROC) (GLenum target, GLenum internalformat, GLuint buffer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexBufferARB (GLenum target, GLenum internalformat, GLuint buffer); #endif #endif /* GL_ARB_texture_buffer_object */ #ifndef GL_ARB_texture_buffer_object_rgb32 #define GL_ARB_texture_buffer_object_rgb32 1 #endif /* GL_ARB_texture_buffer_object_rgb32 */ #ifndef GL_ARB_texture_buffer_range #define GL_ARB_texture_buffer_range 1 #endif /* GL_ARB_texture_buffer_range */ #ifndef GL_ARB_texture_compression #define GL_ARB_texture_compression 1 #define GL_COMPRESSED_ALPHA_ARB 0x84E9 #define GL_COMPRESSED_LUMINANCE_ARB 0x84EA #define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB #define GL_COMPRESSED_INTENSITY_ARB 0x84EC #define GL_COMPRESSED_RGB_ARB 0x84ED #define GL_COMPRESSED_RGBA_ARB 0x84EE #define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF #define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 #define GL_TEXTURE_COMPRESSED_ARB 0x86A1 #define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 #define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, void *img); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCompressedTexImage3DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTexImage2DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTexImage1DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTexSubImage3DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTexSubImage2DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); GLAPI void APIENTRY glCompressedTexSubImage1DARB (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); GLAPI void APIENTRY glGetCompressedTexImageARB (GLenum target, GLint level, void *img); #endif #endif /* GL_ARB_texture_compression */ #ifndef GL_ARB_texture_compression_bptc #define GL_ARB_texture_compression_bptc 1 #define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C #define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D #define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E #define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F #endif /* GL_ARB_texture_compression_bptc */ #ifndef GL_ARB_texture_compression_rgtc #define GL_ARB_texture_compression_rgtc 1 #endif /* GL_ARB_texture_compression_rgtc */ #ifndef GL_ARB_texture_cube_map #define GL_ARB_texture_cube_map 1 #define GL_NORMAL_MAP_ARB 0x8511 #define GL_REFLECTION_MAP_ARB 0x8512 #define GL_TEXTURE_CUBE_MAP_ARB 0x8513 #define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 #define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A #define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B #define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C #endif /* GL_ARB_texture_cube_map */ #ifndef GL_ARB_texture_cube_map_array #define GL_ARB_texture_cube_map_array 1 #define GL_TEXTURE_CUBE_MAP_ARRAY_ARB 0x9009 #define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY_ARB 0x900A #define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY_ARB 0x900B #define GL_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900C #define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW_ARB 0x900D #define GL_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900E #define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900F #endif /* GL_ARB_texture_cube_map_array */ #ifndef GL_ARB_texture_env_add #define GL_ARB_texture_env_add 1 #endif /* GL_ARB_texture_env_add */ #ifndef GL_ARB_texture_env_combine #define GL_ARB_texture_env_combine 1 #define GL_COMBINE_ARB 0x8570 #define GL_COMBINE_RGB_ARB 0x8571 #define GL_COMBINE_ALPHA_ARB 0x8572 #define GL_SOURCE0_RGB_ARB 0x8580 #define GL_SOURCE1_RGB_ARB 0x8581 #define GL_SOURCE2_RGB_ARB 0x8582 #define GL_SOURCE0_ALPHA_ARB 0x8588 #define GL_SOURCE1_ALPHA_ARB 0x8589 #define GL_SOURCE2_ALPHA_ARB 0x858A #define GL_OPERAND0_RGB_ARB 0x8590 #define GL_OPERAND1_RGB_ARB 0x8591 #define GL_OPERAND2_RGB_ARB 0x8592 #define GL_OPERAND0_ALPHA_ARB 0x8598 #define GL_OPERAND1_ALPHA_ARB 0x8599 #define GL_OPERAND2_ALPHA_ARB 0x859A #define GL_RGB_SCALE_ARB 0x8573 #define GL_ADD_SIGNED_ARB 0x8574 #define GL_INTERPOLATE_ARB 0x8575 #define GL_SUBTRACT_ARB 0x84E7 #define GL_CONSTANT_ARB 0x8576 #define GL_PRIMARY_COLOR_ARB 0x8577 #define GL_PREVIOUS_ARB 0x8578 #endif /* GL_ARB_texture_env_combine */ #ifndef GL_ARB_texture_env_crossbar #define GL_ARB_texture_env_crossbar 1 #endif /* GL_ARB_texture_env_crossbar */ #ifndef GL_ARB_texture_env_dot3 #define GL_ARB_texture_env_dot3 1 #define GL_DOT3_RGB_ARB 0x86AE #define GL_DOT3_RGBA_ARB 0x86AF #endif /* GL_ARB_texture_env_dot3 */ #ifndef GL_ARB_texture_filter_anisotropic #define GL_ARB_texture_filter_anisotropic 1 #endif /* GL_ARB_texture_filter_anisotropic */ #ifndef GL_ARB_texture_filter_minmax #define GL_ARB_texture_filter_minmax 1 #define GL_TEXTURE_REDUCTION_MODE_ARB 0x9366 #define GL_WEIGHTED_AVERAGE_ARB 0x9367 #endif /* GL_ARB_texture_filter_minmax */ #ifndef GL_ARB_texture_float #define GL_ARB_texture_float 1 #define GL_TEXTURE_RED_TYPE_ARB 0x8C10 #define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 #define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 #define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 #define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 #define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 #define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 #define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 #define GL_RGBA32F_ARB 0x8814 #define GL_RGB32F_ARB 0x8815 #define GL_ALPHA32F_ARB 0x8816 #define GL_INTENSITY32F_ARB 0x8817 #define GL_LUMINANCE32F_ARB 0x8818 #define GL_LUMINANCE_ALPHA32F_ARB 0x8819 #define GL_RGBA16F_ARB 0x881A #define GL_RGB16F_ARB 0x881B #define GL_ALPHA16F_ARB 0x881C #define GL_INTENSITY16F_ARB 0x881D #define GL_LUMINANCE16F_ARB 0x881E #define GL_LUMINANCE_ALPHA16F_ARB 0x881F #endif /* GL_ARB_texture_float */ #ifndef GL_ARB_texture_gather #define GL_ARB_texture_gather 1 #define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5E #define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5F #define GL_MAX_PROGRAM_TEXTURE_GATHER_COMPONENTS_ARB 0x8F9F #endif /* GL_ARB_texture_gather */ #ifndef GL_ARB_texture_mirror_clamp_to_edge #define GL_ARB_texture_mirror_clamp_to_edge 1 #endif /* GL_ARB_texture_mirror_clamp_to_edge */ #ifndef GL_ARB_texture_mirrored_repeat #define GL_ARB_texture_mirrored_repeat 1 #define GL_MIRRORED_REPEAT_ARB 0x8370 #endif /* GL_ARB_texture_mirrored_repeat */ #ifndef GL_ARB_texture_multisample #define GL_ARB_texture_multisample 1 #endif /* GL_ARB_texture_multisample */ #ifndef GL_ARB_texture_non_power_of_two #define GL_ARB_texture_non_power_of_two 1 #endif /* GL_ARB_texture_non_power_of_two */ #ifndef GL_ARB_texture_query_levels #define GL_ARB_texture_query_levels 1 #endif /* GL_ARB_texture_query_levels */ #ifndef GL_ARB_texture_query_lod #define GL_ARB_texture_query_lod 1 #endif /* GL_ARB_texture_query_lod */ #ifndef GL_ARB_texture_rectangle #define GL_ARB_texture_rectangle 1 #define GL_TEXTURE_RECTANGLE_ARB 0x84F5 #define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6 #define GL_PROXY_TEXTURE_RECTANGLE_ARB 0x84F7 #define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 #endif /* GL_ARB_texture_rectangle */ #ifndef GL_ARB_texture_rg #define GL_ARB_texture_rg 1 #endif /* GL_ARB_texture_rg */ #ifndef GL_ARB_texture_rgb10_a2ui #define GL_ARB_texture_rgb10_a2ui 1 #endif /* GL_ARB_texture_rgb10_a2ui */ #ifndef GL_ARB_texture_stencil8 #define GL_ARB_texture_stencil8 1 #endif /* GL_ARB_texture_stencil8 */ #ifndef GL_ARB_texture_storage #define GL_ARB_texture_storage 1 #endif /* GL_ARB_texture_storage */ #ifndef GL_ARB_texture_storage_multisample #define GL_ARB_texture_storage_multisample 1 #endif /* GL_ARB_texture_storage_multisample */ #ifndef GL_ARB_texture_swizzle #define GL_ARB_texture_swizzle 1 #endif /* GL_ARB_texture_swizzle */ #ifndef GL_ARB_texture_view #define GL_ARB_texture_view 1 #endif /* GL_ARB_texture_view */ #ifndef GL_ARB_timer_query #define GL_ARB_timer_query 1 #endif /* GL_ARB_timer_query */ #ifndef GL_ARB_transform_feedback2 #define GL_ARB_transform_feedback2 1 #endif /* GL_ARB_transform_feedback2 */ #ifndef GL_ARB_transform_feedback3 #define GL_ARB_transform_feedback3 1 #endif /* GL_ARB_transform_feedback3 */ #ifndef GL_ARB_transform_feedback_instanced #define GL_ARB_transform_feedback_instanced 1 #endif /* GL_ARB_transform_feedback_instanced */ #ifndef GL_ARB_transform_feedback_overflow_query #define GL_ARB_transform_feedback_overflow_query 1 #define GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB 0x82EC #define GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW_ARB 0x82ED #endif /* GL_ARB_transform_feedback_overflow_query */ #ifndef GL_ARB_transpose_matrix #define GL_ARB_transpose_matrix 1 #define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 #define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 #define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 #define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *m); GLAPI void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *m); GLAPI void APIENTRY glMultTransposeMatrixfARB (const GLfloat *m); GLAPI void APIENTRY glMultTransposeMatrixdARB (const GLdouble *m); #endif #endif /* GL_ARB_transpose_matrix */ #ifndef GL_ARB_uniform_buffer_object #define GL_ARB_uniform_buffer_object 1 #endif /* GL_ARB_uniform_buffer_object */ #ifndef GL_ARB_vertex_array_bgra #define GL_ARB_vertex_array_bgra 1 #endif /* GL_ARB_vertex_array_bgra */ #ifndef GL_ARB_vertex_array_object #define GL_ARB_vertex_array_object 1 #endif /* GL_ARB_vertex_array_object */ #ifndef GL_ARB_vertex_attrib_64bit #define GL_ARB_vertex_attrib_64bit 1 #endif /* GL_ARB_vertex_attrib_64bit */ #ifndef GL_ARB_vertex_attrib_binding #define GL_ARB_vertex_attrib_binding 1 #endif /* GL_ARB_vertex_attrib_binding */ #ifndef GL_ARB_vertex_blend #define GL_ARB_vertex_blend 1 #define GL_MAX_VERTEX_UNITS_ARB 0x86A4 #define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 #define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 #define GL_VERTEX_BLEND_ARB 0x86A7 #define GL_CURRENT_WEIGHT_ARB 0x86A8 #define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 #define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA #define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB #define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC #define GL_WEIGHT_ARRAY_ARB 0x86AD #define GL_MODELVIEW0_ARB 0x1700 #define GL_MODELVIEW1_ARB 0x850A #define GL_MODELVIEW2_ARB 0x8722 #define GL_MODELVIEW3_ARB 0x8723 #define GL_MODELVIEW4_ARB 0x8724 #define GL_MODELVIEW5_ARB 0x8725 #define GL_MODELVIEW6_ARB 0x8726 #define GL_MODELVIEW7_ARB 0x8727 #define GL_MODELVIEW8_ARB 0x8728 #define GL_MODELVIEW9_ARB 0x8729 #define GL_MODELVIEW10_ARB 0x872A #define GL_MODELVIEW11_ARB 0x872B #define GL_MODELVIEW12_ARB 0x872C #define GL_MODELVIEW13_ARB 0x872D #define GL_MODELVIEW14_ARB 0x872E #define GL_MODELVIEW15_ARB 0x872F #define GL_MODELVIEW16_ARB 0x8730 #define GL_MODELVIEW17_ARB 0x8731 #define GL_MODELVIEW18_ARB 0x8732 #define GL_MODELVIEW19_ARB 0x8733 #define GL_MODELVIEW20_ARB 0x8734 #define GL_MODELVIEW21_ARB 0x8735 #define GL_MODELVIEW22_ARB 0x8736 #define GL_MODELVIEW23_ARB 0x8737 #define GL_MODELVIEW24_ARB 0x8738 #define GL_MODELVIEW25_ARB 0x8739 #define GL_MODELVIEW26_ARB 0x873A #define GL_MODELVIEW27_ARB 0x873B #define GL_MODELVIEW28_ARB 0x873C #define GL_MODELVIEW29_ARB 0x873D #define GL_MODELVIEW30_ARB 0x873E #define GL_MODELVIEW31_ARB 0x873F typedef void (APIENTRYP PFNGLWEIGHTBVARBPROC) (GLint size, const GLbyte *weights); typedef void (APIENTRYP PFNGLWEIGHTSVARBPROC) (GLint size, const GLshort *weights); typedef void (APIENTRYP PFNGLWEIGHTIVARBPROC) (GLint size, const GLint *weights); typedef void (APIENTRYP PFNGLWEIGHTFVARBPROC) (GLint size, const GLfloat *weights); typedef void (APIENTRYP PFNGLWEIGHTDVARBPROC) (GLint size, const GLdouble *weights); typedef void (APIENTRYP PFNGLWEIGHTUBVARBPROC) (GLint size, const GLubyte *weights); typedef void (APIENTRYP PFNGLWEIGHTUSVARBPROC) (GLint size, const GLushort *weights); typedef void (APIENTRYP PFNGLWEIGHTUIVARBPROC) (GLint size, const GLuint *weights); typedef void (APIENTRYP PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLVERTEXBLENDARBPROC) (GLint count); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glWeightbvARB (GLint size, const GLbyte *weights); GLAPI void APIENTRY glWeightsvARB (GLint size, const GLshort *weights); GLAPI void APIENTRY glWeightivARB (GLint size, const GLint *weights); GLAPI void APIENTRY glWeightfvARB (GLint size, const GLfloat *weights); GLAPI void APIENTRY glWeightdvARB (GLint size, const GLdouble *weights); GLAPI void APIENTRY glWeightubvARB (GLint size, const GLubyte *weights); GLAPI void APIENTRY glWeightusvARB (GLint size, const GLushort *weights); GLAPI void APIENTRY glWeightuivARB (GLint size, const GLuint *weights); GLAPI void APIENTRY glWeightPointerARB (GLint size, GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glVertexBlendARB (GLint count); #endif #endif /* GL_ARB_vertex_blend */ #ifndef GL_ARB_vertex_buffer_object #define GL_ARB_vertex_buffer_object 1 typedef khronos_ssize_t GLsizeiptrARB; typedef khronos_intptr_t GLintptrARB; #define GL_BUFFER_SIZE_ARB 0x8764 #define GL_BUFFER_USAGE_ARB 0x8765 #define GL_ARRAY_BUFFER_ARB 0x8892 #define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 #define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 #define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 #define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 #define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 #define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 #define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 #define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A #define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B #define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C #define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D #define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E #define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F #define GL_READ_ONLY_ARB 0x88B8 #define GL_WRITE_ONLY_ARB 0x88B9 #define GL_READ_WRITE_ARB 0x88BA #define GL_BUFFER_ACCESS_ARB 0x88BB #define GL_BUFFER_MAPPED_ARB 0x88BC #define GL_BUFFER_MAP_POINTER_ARB 0x88BD #define GL_STREAM_DRAW_ARB 0x88E0 #define GL_STREAM_READ_ARB 0x88E1 #define GL_STREAM_COPY_ARB 0x88E2 #define GL_STATIC_DRAW_ARB 0x88E4 #define GL_STATIC_READ_ARB 0x88E5 #define GL_STATIC_COPY_ARB 0x88E6 #define GL_DYNAMIC_DRAW_ARB 0x88E8 #define GL_DYNAMIC_READ_ARB 0x88E9 #define GL_DYNAMIC_COPY_ARB 0x88EA typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); typedef GLboolean (APIENTRYP PFNGLISBUFFERARBPROC) (GLuint buffer); typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const void *data, GLenum usage); typedef void (APIENTRYP PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void *data); typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, void *data); typedef void *(APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target); typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, void **params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBindBufferARB (GLenum target, GLuint buffer); GLAPI void APIENTRY glDeleteBuffersARB (GLsizei n, const GLuint *buffers); GLAPI void APIENTRY glGenBuffersARB (GLsizei n, GLuint *buffers); GLAPI GLboolean APIENTRY glIsBufferARB (GLuint buffer); GLAPI void APIENTRY glBufferDataARB (GLenum target, GLsizeiptrARB size, const void *data, GLenum usage); GLAPI void APIENTRY glBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void *data); GLAPI void APIENTRY glGetBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, void *data); GLAPI void *APIENTRY glMapBufferARB (GLenum target, GLenum access); GLAPI GLboolean APIENTRY glUnmapBufferARB (GLenum target); GLAPI void APIENTRY glGetBufferParameterivARB (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetBufferPointervARB (GLenum target, GLenum pname, void **params); #endif #endif /* GL_ARB_vertex_buffer_object */ #ifndef GL_ARB_vertex_program #define GL_ARB_vertex_program 1 #define GL_COLOR_SUM_ARB 0x8458 #define GL_VERTEX_PROGRAM_ARB 0x8620 #define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 #define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 #define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 #define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 #define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 #define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 #define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 #define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 #define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 #define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A #define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 #define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 #define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 #define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 typedef void (APIENTRYP PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, void **pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertexAttrib1dARB (GLuint index, GLdouble x); GLAPI void APIENTRY glVertexAttrib1dvARB (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib1fARB (GLuint index, GLfloat x); GLAPI void APIENTRY glVertexAttrib1fvARB (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib1sARB (GLuint index, GLshort x); GLAPI void APIENTRY glVertexAttrib1svARB (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib2dARB (GLuint index, GLdouble x, GLdouble y); GLAPI void APIENTRY glVertexAttrib2dvARB (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib2fARB (GLuint index, GLfloat x, GLfloat y); GLAPI void APIENTRY glVertexAttrib2fvARB (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib2sARB (GLuint index, GLshort x, GLshort y); GLAPI void APIENTRY glVertexAttrib2svARB (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib3dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glVertexAttrib3dvARB (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib3fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glVertexAttrib3fvARB (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib3sARB (GLuint index, GLshort x, GLshort y, GLshort z); GLAPI void APIENTRY glVertexAttrib3svARB (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib4NbvARB (GLuint index, const GLbyte *v); GLAPI void APIENTRY glVertexAttrib4NivARB (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttrib4NsvARB (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib4NubARB (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); GLAPI void APIENTRY glVertexAttrib4NubvARB (GLuint index, const GLubyte *v); GLAPI void APIENTRY glVertexAttrib4NuivARB (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttrib4NusvARB (GLuint index, const GLushort *v); GLAPI void APIENTRY glVertexAttrib4bvARB (GLuint index, const GLbyte *v); GLAPI void APIENTRY glVertexAttrib4dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glVertexAttrib4dvARB (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib4fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glVertexAttrib4fvARB (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib4ivARB (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttrib4sARB (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); GLAPI void APIENTRY glVertexAttrib4svARB (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib4ubvARB (GLuint index, const GLubyte *v); GLAPI void APIENTRY glVertexAttrib4uivARB (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttrib4usvARB (GLuint index, const GLushort *v); GLAPI void APIENTRY glVertexAttribPointerARB (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); GLAPI void APIENTRY glEnableVertexAttribArrayARB (GLuint index); GLAPI void APIENTRY glDisableVertexAttribArrayARB (GLuint index); GLAPI void APIENTRY glGetVertexAttribdvARB (GLuint index, GLenum pname, GLdouble *params); GLAPI void APIENTRY glGetVertexAttribfvARB (GLuint index, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetVertexAttribivARB (GLuint index, GLenum pname, GLint *params); GLAPI void APIENTRY glGetVertexAttribPointervARB (GLuint index, GLenum pname, void **pointer); #endif #endif /* GL_ARB_vertex_program */ #ifndef GL_ARB_vertex_shader #define GL_ARB_vertex_shader 1 #define GL_VERTEX_SHADER_ARB 0x8B31 #define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A #define GL_MAX_VARYING_FLOATS_ARB 0x8B4B #define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D #define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 #define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB *name); typedef void (APIENTRYP PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBindAttribLocationARB (GLhandleARB programObj, GLuint index, const GLcharARB *name); GLAPI void APIENTRY glGetActiveAttribARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); GLAPI GLint APIENTRY glGetAttribLocationARB (GLhandleARB programObj, const GLcharARB *name); #endif #endif /* GL_ARB_vertex_shader */ #ifndef GL_ARB_vertex_type_10f_11f_11f_rev #define GL_ARB_vertex_type_10f_11f_11f_rev 1 #endif /* GL_ARB_vertex_type_10f_11f_11f_rev */ #ifndef GL_ARB_vertex_type_2_10_10_10_rev #define GL_ARB_vertex_type_2_10_10_10_rev 1 #endif /* GL_ARB_vertex_type_2_10_10_10_rev */ #ifndef GL_ARB_viewport_array #define GL_ARB_viewport_array 1 typedef void (APIENTRYP PFNGLDEPTHRANGEARRAYDVNVPROC) (GLuint first, GLsizei count, const GLdouble *v); typedef void (APIENTRYP PFNGLDEPTHRANGEINDEXEDDNVPROC) (GLuint index, GLdouble n, GLdouble f); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDepthRangeArraydvNV (GLuint first, GLsizei count, const GLdouble *v); GLAPI void APIENTRY glDepthRangeIndexeddNV (GLuint index, GLdouble n, GLdouble f); #endif #endif /* GL_ARB_viewport_array */ #ifndef GL_ARB_window_pos #define GL_ARB_window_pos 1 typedef void (APIENTRYP PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLWINDOWPOS2DVARBPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); typedef void (APIENTRYP PFNGLWINDOWPOS2FVARBPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); typedef void (APIENTRYP PFNGLWINDOWPOS2IVARBPROC) (const GLint *v); typedef void (APIENTRYP PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); typedef void (APIENTRYP PFNGLWINDOWPOS2SVARBPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLWINDOWPOS3DVARBPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLWINDOWPOS3FVARBPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); typedef void (APIENTRYP PFNGLWINDOWPOS3IVARBPROC) (const GLint *v); typedef void (APIENTRYP PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); typedef void (APIENTRYP PFNGLWINDOWPOS3SVARBPROC) (const GLshort *v); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glWindowPos2dARB (GLdouble x, GLdouble y); GLAPI void APIENTRY glWindowPos2dvARB (const GLdouble *v); GLAPI void APIENTRY glWindowPos2fARB (GLfloat x, GLfloat y); GLAPI void APIENTRY glWindowPos2fvARB (const GLfloat *v); GLAPI void APIENTRY glWindowPos2iARB (GLint x, GLint y); GLAPI void APIENTRY glWindowPos2ivARB (const GLint *v); GLAPI void APIENTRY glWindowPos2sARB (GLshort x, GLshort y); GLAPI void APIENTRY glWindowPos2svARB (const GLshort *v); GLAPI void APIENTRY glWindowPos3dARB (GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glWindowPos3dvARB (const GLdouble *v); GLAPI void APIENTRY glWindowPos3fARB (GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glWindowPos3fvARB (const GLfloat *v); GLAPI void APIENTRY glWindowPos3iARB (GLint x, GLint y, GLint z); GLAPI void APIENTRY glWindowPos3ivARB (const GLint *v); GLAPI void APIENTRY glWindowPos3sARB (GLshort x, GLshort y, GLshort z); GLAPI void APIENTRY glWindowPos3svARB (const GLshort *v); #endif #endif /* GL_ARB_window_pos */ #ifndef GL_KHR_blend_equation_advanced #define GL_KHR_blend_equation_advanced 1 #define GL_MULTIPLY_KHR 0x9294 #define GL_SCREEN_KHR 0x9295 #define GL_OVERLAY_KHR 0x9296 #define GL_DARKEN_KHR 0x9297 #define GL_LIGHTEN_KHR 0x9298 #define GL_COLORDODGE_KHR 0x9299 #define GL_COLORBURN_KHR 0x929A #define GL_HARDLIGHT_KHR 0x929B #define GL_SOFTLIGHT_KHR 0x929C #define GL_DIFFERENCE_KHR 0x929E #define GL_EXCLUSION_KHR 0x92A0 #define GL_HSL_HUE_KHR 0x92AD #define GL_HSL_SATURATION_KHR 0x92AE #define GL_HSL_COLOR_KHR 0x92AF #define GL_HSL_LUMINOSITY_KHR 0x92B0 typedef void (APIENTRYP PFNGLBLENDBARRIERKHRPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendBarrierKHR (void); #endif #endif /* GL_KHR_blend_equation_advanced */ #ifndef GL_KHR_blend_equation_advanced_coherent #define GL_KHR_blend_equation_advanced_coherent 1 #define GL_BLEND_ADVANCED_COHERENT_KHR 0x9285 #endif /* GL_KHR_blend_equation_advanced_coherent */ #ifndef GL_KHR_context_flush_control #define GL_KHR_context_flush_control 1 #endif /* GL_KHR_context_flush_control */ #ifndef GL_KHR_debug #define GL_KHR_debug 1 #endif /* GL_KHR_debug */ #ifndef GL_KHR_no_error #define GL_KHR_no_error 1 #define GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR 0x00000008 #endif /* GL_KHR_no_error */ #ifndef GL_KHR_parallel_shader_compile #define GL_KHR_parallel_shader_compile 1 #define GL_MAX_SHADER_COMPILER_THREADS_KHR 0x91B0 #define GL_COMPLETION_STATUS_KHR 0x91B1 typedef void (APIENTRYP PFNGLMAXSHADERCOMPILERTHREADSKHRPROC) (GLuint count); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMaxShaderCompilerThreadsKHR (GLuint count); #endif #endif /* GL_KHR_parallel_shader_compile */ #ifndef GL_KHR_robust_buffer_access_behavior #define GL_KHR_robust_buffer_access_behavior 1 #endif /* GL_KHR_robust_buffer_access_behavior */ #ifndef GL_KHR_robustness #define GL_KHR_robustness 1 #define GL_CONTEXT_ROBUST_ACCESS 0x90F3 #endif /* GL_KHR_robustness */ #ifndef GL_KHR_shader_subgroup #define GL_KHR_shader_subgroup 1 #define GL_SUBGROUP_SIZE_KHR 0x9532 #define GL_SUBGROUP_SUPPORTED_STAGES_KHR 0x9533 #define GL_SUBGROUP_SUPPORTED_FEATURES_KHR 0x9534 #define GL_SUBGROUP_QUAD_ALL_STAGES_KHR 0x9535 #define GL_SUBGROUP_FEATURE_BASIC_BIT_KHR 0x00000001 #define GL_SUBGROUP_FEATURE_VOTE_BIT_KHR 0x00000002 #define GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR 0x00000004 #define GL_SUBGROUP_FEATURE_BALLOT_BIT_KHR 0x00000008 #define GL_SUBGROUP_FEATURE_SHUFFLE_BIT_KHR 0x00000010 #define GL_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT_KHR 0x00000020 #define GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR 0x00000040 #define GL_SUBGROUP_FEATURE_QUAD_BIT_KHR 0x00000080 #endif /* GL_KHR_shader_subgroup */ #ifndef GL_KHR_texture_compression_astc_hdr #define GL_KHR_texture_compression_astc_hdr 1 #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0 #define GL_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1 #define GL_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2 #define GL_COMPRESSED_RGBA_ASTC_6x5_KHR 0x93B3 #define GL_COMPRESSED_RGBA_ASTC_6x6_KHR 0x93B4 #define GL_COMPRESSED_RGBA_ASTC_8x5_KHR 0x93B5 #define GL_COMPRESSED_RGBA_ASTC_8x6_KHR 0x93B6 #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93B7 #define GL_COMPRESSED_RGBA_ASTC_10x5_KHR 0x93B8 #define GL_COMPRESSED_RGBA_ASTC_10x6_KHR 0x93B9 #define GL_COMPRESSED_RGBA_ASTC_10x8_KHR 0x93BA #define GL_COMPRESSED_RGBA_ASTC_10x10_KHR 0x93BB #define GL_COMPRESSED_RGBA_ASTC_12x10_KHR 0x93BC #define GL_COMPRESSED_RGBA_ASTC_12x12_KHR 0x93BD #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR 0x93D0 #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR 0x93D1 #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR 0x93D2 #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR 0x93D3 #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR 0x93D4 #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR 0x93D5 #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR 0x93D6 #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR 0x93D7 #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR 0x93D8 #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR 0x93D9 #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR 0x93DA #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR 0x93DB #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR 0x93DC #define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR 0x93DD #endif /* GL_KHR_texture_compression_astc_hdr */ #ifndef GL_KHR_texture_compression_astc_ldr #define GL_KHR_texture_compression_astc_ldr 1 #endif /* GL_KHR_texture_compression_astc_ldr */ #ifndef GL_KHR_texture_compression_astc_sliced_3d #define GL_KHR_texture_compression_astc_sliced_3d 1 #endif /* GL_KHR_texture_compression_astc_sliced_3d */ #ifndef GL_OES_byte_coordinates #define GL_OES_byte_coordinates 1 typedef void (APIENTRYP PFNGLMULTITEXCOORD1BOESPROC) (GLenum texture, GLbyte s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1BVOESPROC) (GLenum texture, const GLbyte *coords); typedef void (APIENTRYP PFNGLMULTITEXCOORD2BOESPROC) (GLenum texture, GLbyte s, GLbyte t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2BVOESPROC) (GLenum texture, const GLbyte *coords); typedef void (APIENTRYP PFNGLMULTITEXCOORD3BOESPROC) (GLenum texture, GLbyte s, GLbyte t, GLbyte r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3BVOESPROC) (GLenum texture, const GLbyte *coords); typedef void (APIENTRYP PFNGLMULTITEXCOORD4BOESPROC) (GLenum texture, GLbyte s, GLbyte t, GLbyte r, GLbyte q); typedef void (APIENTRYP PFNGLMULTITEXCOORD4BVOESPROC) (GLenum texture, const GLbyte *coords); typedef void (APIENTRYP PFNGLTEXCOORD1BOESPROC) (GLbyte s); typedef void (APIENTRYP PFNGLTEXCOORD1BVOESPROC) (const GLbyte *coords); typedef void (APIENTRYP PFNGLTEXCOORD2BOESPROC) (GLbyte s, GLbyte t); typedef void (APIENTRYP PFNGLTEXCOORD2BVOESPROC) (const GLbyte *coords); typedef void (APIENTRYP PFNGLTEXCOORD3BOESPROC) (GLbyte s, GLbyte t, GLbyte r); typedef void (APIENTRYP PFNGLTEXCOORD3BVOESPROC) (const GLbyte *coords); typedef void (APIENTRYP PFNGLTEXCOORD4BOESPROC) (GLbyte s, GLbyte t, GLbyte r, GLbyte q); typedef void (APIENTRYP PFNGLTEXCOORD4BVOESPROC) (const GLbyte *coords); typedef void (APIENTRYP PFNGLVERTEX2BOESPROC) (GLbyte x, GLbyte y); typedef void (APIENTRYP PFNGLVERTEX2BVOESPROC) (const GLbyte *coords); typedef void (APIENTRYP PFNGLVERTEX3BOESPROC) (GLbyte x, GLbyte y, GLbyte z); typedef void (APIENTRYP PFNGLVERTEX3BVOESPROC) (const GLbyte *coords); typedef void (APIENTRYP PFNGLVERTEX4BOESPROC) (GLbyte x, GLbyte y, GLbyte z, GLbyte w); typedef void (APIENTRYP PFNGLVERTEX4BVOESPROC) (const GLbyte *coords); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMultiTexCoord1bOES (GLenum texture, GLbyte s); GLAPI void APIENTRY glMultiTexCoord1bvOES (GLenum texture, const GLbyte *coords); GLAPI void APIENTRY glMultiTexCoord2bOES (GLenum texture, GLbyte s, GLbyte t); GLAPI void APIENTRY glMultiTexCoord2bvOES (GLenum texture, const GLbyte *coords); GLAPI void APIENTRY glMultiTexCoord3bOES (GLenum texture, GLbyte s, GLbyte t, GLbyte r); GLAPI void APIENTRY glMultiTexCoord3bvOES (GLenum texture, const GLbyte *coords); GLAPI void APIENTRY glMultiTexCoord4bOES (GLenum texture, GLbyte s, GLbyte t, GLbyte r, GLbyte q); GLAPI void APIENTRY glMultiTexCoord4bvOES (GLenum texture, const GLbyte *coords); GLAPI void APIENTRY glTexCoord1bOES (GLbyte s); GLAPI void APIENTRY glTexCoord1bvOES (const GLbyte *coords); GLAPI void APIENTRY glTexCoord2bOES (GLbyte s, GLbyte t); GLAPI void APIENTRY glTexCoord2bvOES (const GLbyte *coords); GLAPI void APIENTRY glTexCoord3bOES (GLbyte s, GLbyte t, GLbyte r); GLAPI void APIENTRY glTexCoord3bvOES (const GLbyte *coords); GLAPI void APIENTRY glTexCoord4bOES (GLbyte s, GLbyte t, GLbyte r, GLbyte q); GLAPI void APIENTRY glTexCoord4bvOES (const GLbyte *coords); GLAPI void APIENTRY glVertex2bOES (GLbyte x, GLbyte y); GLAPI void APIENTRY glVertex2bvOES (const GLbyte *coords); GLAPI void APIENTRY glVertex3bOES (GLbyte x, GLbyte y, GLbyte z); GLAPI void APIENTRY glVertex3bvOES (const GLbyte *coords); GLAPI void APIENTRY glVertex4bOES (GLbyte x, GLbyte y, GLbyte z, GLbyte w); GLAPI void APIENTRY glVertex4bvOES (const GLbyte *coords); #endif #endif /* GL_OES_byte_coordinates */ #ifndef GL_OES_compressed_paletted_texture #define GL_OES_compressed_paletted_texture 1 #define GL_PALETTE4_RGB8_OES 0x8B90 #define GL_PALETTE4_RGBA8_OES 0x8B91 #define GL_PALETTE4_R5_G6_B5_OES 0x8B92 #define GL_PALETTE4_RGBA4_OES 0x8B93 #define GL_PALETTE4_RGB5_A1_OES 0x8B94 #define GL_PALETTE8_RGB8_OES 0x8B95 #define GL_PALETTE8_RGBA8_OES 0x8B96 #define GL_PALETTE8_R5_G6_B5_OES 0x8B97 #define GL_PALETTE8_RGBA4_OES 0x8B98 #define GL_PALETTE8_RGB5_A1_OES 0x8B99 #endif /* GL_OES_compressed_paletted_texture */ #ifndef GL_OES_fixed_point #define GL_OES_fixed_point 1 typedef khronos_int32_t GLfixed; #define GL_FIXED_OES 0x140C typedef void (APIENTRYP PFNGLALPHAFUNCXOESPROC) (GLenum func, GLfixed ref); typedef void (APIENTRYP PFNGLCLEARCOLORXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); typedef void (APIENTRYP PFNGLCLEARDEPTHXOESPROC) (GLfixed depth); typedef void (APIENTRYP PFNGLCLIPPLANEXOESPROC) (GLenum plane, const GLfixed *equation); typedef void (APIENTRYP PFNGLCOLOR4XOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); typedef void (APIENTRYP PFNGLDEPTHRANGEXOESPROC) (GLfixed n, GLfixed f); typedef void (APIENTRYP PFNGLFOGXOESPROC) (GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLFOGXVOESPROC) (GLenum pname, const GLfixed *param); typedef void (APIENTRYP PFNGLFRUSTUMXOESPROC) (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); typedef void (APIENTRYP PFNGLGETCLIPPLANEXOESPROC) (GLenum plane, GLfixed *equation); typedef void (APIENTRYP PFNGLGETFIXEDVOESPROC) (GLenum pname, GLfixed *params); typedef void (APIENTRYP PFNGLGETTEXENVXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); typedef void (APIENTRYP PFNGLGETTEXPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); typedef void (APIENTRYP PFNGLLIGHTMODELXOESPROC) (GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLLIGHTMODELXVOESPROC) (GLenum pname, const GLfixed *param); typedef void (APIENTRYP PFNGLLIGHTXOESPROC) (GLenum light, GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLLIGHTXVOESPROC) (GLenum light, GLenum pname, const GLfixed *params); typedef void (APIENTRYP PFNGLLINEWIDTHXOESPROC) (GLfixed width); typedef void (APIENTRYP PFNGLLOADMATRIXXOESPROC) (const GLfixed *m); typedef void (APIENTRYP PFNGLMATERIALXOESPROC) (GLenum face, GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLMATERIALXVOESPROC) (GLenum face, GLenum pname, const GLfixed *param); typedef void (APIENTRYP PFNGLMULTMATRIXXOESPROC) (const GLfixed *m); typedef void (APIENTRYP PFNGLMULTITEXCOORD4XOESPROC) (GLenum texture, GLfixed s, GLfixed t, GLfixed r, GLfixed q); typedef void (APIENTRYP PFNGLNORMAL3XOESPROC) (GLfixed nx, GLfixed ny, GLfixed nz); typedef void (APIENTRYP PFNGLORTHOXOESPROC) (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); typedef void (APIENTRYP PFNGLPOINTPARAMETERXVOESPROC) (GLenum pname, const GLfixed *params); typedef void (APIENTRYP PFNGLPOINTSIZEXOESPROC) (GLfixed size); typedef void (APIENTRYP PFNGLPOLYGONOFFSETXOESPROC) (GLfixed factor, GLfixed units); typedef void (APIENTRYP PFNGLROTATEXOESPROC) (GLfixed angle, GLfixed x, GLfixed y, GLfixed z); typedef void (APIENTRYP PFNGLSCALEXOESPROC) (GLfixed x, GLfixed y, GLfixed z); typedef void (APIENTRYP PFNGLTEXENVXOESPROC) (GLenum target, GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLTEXENVXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); typedef void (APIENTRYP PFNGLTEXPARAMETERXOESPROC) (GLenum target, GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLTEXPARAMETERXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); typedef void (APIENTRYP PFNGLTRANSLATEXOESPROC) (GLfixed x, GLfixed y, GLfixed z); typedef void (APIENTRYP PFNGLACCUMXOESPROC) (GLenum op, GLfixed value); typedef void (APIENTRYP PFNGLBITMAPXOESPROC) (GLsizei width, GLsizei height, GLfixed xorig, GLfixed yorig, GLfixed xmove, GLfixed ymove, const GLubyte *bitmap); typedef void (APIENTRYP PFNGLBLENDCOLORXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); typedef void (APIENTRYP PFNGLCLEARACCUMXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); typedef void (APIENTRYP PFNGLCOLOR3XOESPROC) (GLfixed red, GLfixed green, GLfixed blue); typedef void (APIENTRYP PFNGLCOLOR3XVOESPROC) (const GLfixed *components); typedef void (APIENTRYP PFNGLCOLOR4XVOESPROC) (const GLfixed *components); typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERXOESPROC) (GLenum target, GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); typedef void (APIENTRYP PFNGLEVALCOORD1XOESPROC) (GLfixed u); typedef void (APIENTRYP PFNGLEVALCOORD1XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLEVALCOORD2XOESPROC) (GLfixed u, GLfixed v); typedef void (APIENTRYP PFNGLEVALCOORD2XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLFEEDBACKBUFFERXOESPROC) (GLsizei n, GLenum type, const GLfixed *buffer); typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); typedef void (APIENTRYP PFNGLGETLIGHTXOESPROC) (GLenum light, GLenum pname, GLfixed *params); typedef void (APIENTRYP PFNGLGETMAPXVOESPROC) (GLenum target, GLenum query, GLfixed *v); typedef void (APIENTRYP PFNGLGETMATERIALXOESPROC) (GLenum face, GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLGETPIXELMAPXVPROC) (GLenum map, GLint size, GLfixed *values); typedef void (APIENTRYP PFNGLGETTEXGENXVOESPROC) (GLenum coord, GLenum pname, GLfixed *params); typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERXVOESPROC) (GLenum target, GLint level, GLenum pname, GLfixed *params); typedef void (APIENTRYP PFNGLINDEXXOESPROC) (GLfixed component); typedef void (APIENTRYP PFNGLINDEXXVOESPROC) (const GLfixed *component); typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXXOESPROC) (const GLfixed *m); typedef void (APIENTRYP PFNGLMAP1XOESPROC) (GLenum target, GLfixed u1, GLfixed u2, GLint stride, GLint order, GLfixed points); typedef void (APIENTRYP PFNGLMAP2XOESPROC) (GLenum target, GLfixed u1, GLfixed u2, GLint ustride, GLint uorder, GLfixed v1, GLfixed v2, GLint vstride, GLint vorder, GLfixed points); typedef void (APIENTRYP PFNGLMAPGRID1XOESPROC) (GLint n, GLfixed u1, GLfixed u2); typedef void (APIENTRYP PFNGLMAPGRID2XOESPROC) (GLint n, GLfixed u1, GLfixed u2, GLfixed v1, GLfixed v2); typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXXOESPROC) (const GLfixed *m); typedef void (APIENTRYP PFNGLMULTITEXCOORD1XOESPROC) (GLenum texture, GLfixed s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1XVOESPROC) (GLenum texture, const GLfixed *coords); typedef void (APIENTRYP PFNGLMULTITEXCOORD2XOESPROC) (GLenum texture, GLfixed s, GLfixed t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2XVOESPROC) (GLenum texture, const GLfixed *coords); typedef void (APIENTRYP PFNGLMULTITEXCOORD3XOESPROC) (GLenum texture, GLfixed s, GLfixed t, GLfixed r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3XVOESPROC) (GLenum texture, const GLfixed *coords); typedef void (APIENTRYP PFNGLMULTITEXCOORD4XVOESPROC) (GLenum texture, const GLfixed *coords); typedef void (APIENTRYP PFNGLNORMAL3XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLPASSTHROUGHXOESPROC) (GLfixed token); typedef void (APIENTRYP PFNGLPIXELMAPXPROC) (GLenum map, GLint size, const GLfixed *values); typedef void (APIENTRYP PFNGLPIXELSTOREXPROC) (GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLPIXELTRANSFERXOESPROC) (GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLPIXELZOOMXOESPROC) (GLfixed xfactor, GLfixed yfactor); typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESXOESPROC) (GLsizei n, const GLuint *textures, const GLfixed *priorities); typedef void (APIENTRYP PFNGLRASTERPOS2XOESPROC) (GLfixed x, GLfixed y); typedef void (APIENTRYP PFNGLRASTERPOS2XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLRASTERPOS3XOESPROC) (GLfixed x, GLfixed y, GLfixed z); typedef void (APIENTRYP PFNGLRASTERPOS3XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLRASTERPOS4XOESPROC) (GLfixed x, GLfixed y, GLfixed z, GLfixed w); typedef void (APIENTRYP PFNGLRASTERPOS4XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLRECTXOESPROC) (GLfixed x1, GLfixed y1, GLfixed x2, GLfixed y2); typedef void (APIENTRYP PFNGLRECTXVOESPROC) (const GLfixed *v1, const GLfixed *v2); typedef void (APIENTRYP PFNGLTEXCOORD1XOESPROC) (GLfixed s); typedef void (APIENTRYP PFNGLTEXCOORD1XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLTEXCOORD2XOESPROC) (GLfixed s, GLfixed t); typedef void (APIENTRYP PFNGLTEXCOORD2XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLTEXCOORD3XOESPROC) (GLfixed s, GLfixed t, GLfixed r); typedef void (APIENTRYP PFNGLTEXCOORD3XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLTEXCOORD4XOESPROC) (GLfixed s, GLfixed t, GLfixed r, GLfixed q); typedef void (APIENTRYP PFNGLTEXCOORD4XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLTEXGENXOESPROC) (GLenum coord, GLenum pname, GLfixed param); typedef void (APIENTRYP PFNGLTEXGENXVOESPROC) (GLenum coord, GLenum pname, const GLfixed *params); typedef void (APIENTRYP PFNGLVERTEX2XOESPROC) (GLfixed x); typedef void (APIENTRYP PFNGLVERTEX2XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLVERTEX3XOESPROC) (GLfixed x, GLfixed y); typedef void (APIENTRYP PFNGLVERTEX3XVOESPROC) (const GLfixed *coords); typedef void (APIENTRYP PFNGLVERTEX4XOESPROC) (GLfixed x, GLfixed y, GLfixed z); typedef void (APIENTRYP PFNGLVERTEX4XVOESPROC) (const GLfixed *coords); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glAlphaFuncxOES (GLenum func, GLfixed ref); GLAPI void APIENTRY glClearColorxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); GLAPI void APIENTRY glClearDepthxOES (GLfixed depth); GLAPI void APIENTRY glClipPlanexOES (GLenum plane, const GLfixed *equation); GLAPI void APIENTRY glColor4xOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); GLAPI void APIENTRY glDepthRangexOES (GLfixed n, GLfixed f); GLAPI void APIENTRY glFogxOES (GLenum pname, GLfixed param); GLAPI void APIENTRY glFogxvOES (GLenum pname, const GLfixed *param); GLAPI void APIENTRY glFrustumxOES (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); GLAPI void APIENTRY glGetClipPlanexOES (GLenum plane, GLfixed *equation); GLAPI void APIENTRY glGetFixedvOES (GLenum pname, GLfixed *params); GLAPI void APIENTRY glGetTexEnvxvOES (GLenum target, GLenum pname, GLfixed *params); GLAPI void APIENTRY glGetTexParameterxvOES (GLenum target, GLenum pname, GLfixed *params); GLAPI void APIENTRY glLightModelxOES (GLenum pname, GLfixed param); GLAPI void APIENTRY glLightModelxvOES (GLenum pname, const GLfixed *param); GLAPI void APIENTRY glLightxOES (GLenum light, GLenum pname, GLfixed param); GLAPI void APIENTRY glLightxvOES (GLenum light, GLenum pname, const GLfixed *params); GLAPI void APIENTRY glLineWidthxOES (GLfixed width); GLAPI void APIENTRY glLoadMatrixxOES (const GLfixed *m); GLAPI void APIENTRY glMaterialxOES (GLenum face, GLenum pname, GLfixed param); GLAPI void APIENTRY glMaterialxvOES (GLenum face, GLenum pname, const GLfixed *param); GLAPI void APIENTRY glMultMatrixxOES (const GLfixed *m); GLAPI void APIENTRY glMultiTexCoord4xOES (GLenum texture, GLfixed s, GLfixed t, GLfixed r, GLfixed q); GLAPI void APIENTRY glNormal3xOES (GLfixed nx, GLfixed ny, GLfixed nz); GLAPI void APIENTRY glOrthoxOES (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); GLAPI void APIENTRY glPointParameterxvOES (GLenum pname, const GLfixed *params); GLAPI void APIENTRY glPointSizexOES (GLfixed size); GLAPI void APIENTRY glPolygonOffsetxOES (GLfixed factor, GLfixed units); GLAPI void APIENTRY glRotatexOES (GLfixed angle, GLfixed x, GLfixed y, GLfixed z); GLAPI void APIENTRY glScalexOES (GLfixed x, GLfixed y, GLfixed z); GLAPI void APIENTRY glTexEnvxOES (GLenum target, GLenum pname, GLfixed param); GLAPI void APIENTRY glTexEnvxvOES (GLenum target, GLenum pname, const GLfixed *params); GLAPI void APIENTRY glTexParameterxOES (GLenum target, GLenum pname, GLfixed param); GLAPI void APIENTRY glTexParameterxvOES (GLenum target, GLenum pname, const GLfixed *params); GLAPI void APIENTRY glTranslatexOES (GLfixed x, GLfixed y, GLfixed z); GLAPI void APIENTRY glAccumxOES (GLenum op, GLfixed value); GLAPI void APIENTRY glBitmapxOES (GLsizei width, GLsizei height, GLfixed xorig, GLfixed yorig, GLfixed xmove, GLfixed ymove, const GLubyte *bitmap); GLAPI void APIENTRY glBlendColorxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); GLAPI void APIENTRY glClearAccumxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); GLAPI void APIENTRY glColor3xOES (GLfixed red, GLfixed green, GLfixed blue); GLAPI void APIENTRY glColor3xvOES (const GLfixed *components); GLAPI void APIENTRY glColor4xvOES (const GLfixed *components); GLAPI void APIENTRY glConvolutionParameterxOES (GLenum target, GLenum pname, GLfixed param); GLAPI void APIENTRY glConvolutionParameterxvOES (GLenum target, GLenum pname, const GLfixed *params); GLAPI void APIENTRY glEvalCoord1xOES (GLfixed u); GLAPI void APIENTRY glEvalCoord1xvOES (const GLfixed *coords); GLAPI void APIENTRY glEvalCoord2xOES (GLfixed u, GLfixed v); GLAPI void APIENTRY glEvalCoord2xvOES (const GLfixed *coords); GLAPI void APIENTRY glFeedbackBufferxOES (GLsizei n, GLenum type, const GLfixed *buffer); GLAPI void APIENTRY glGetConvolutionParameterxvOES (GLenum target, GLenum pname, GLfixed *params); GLAPI void APIENTRY glGetHistogramParameterxvOES (GLenum target, GLenum pname, GLfixed *params); GLAPI void APIENTRY glGetLightxOES (GLenum light, GLenum pname, GLfixed *params); GLAPI void APIENTRY glGetMapxvOES (GLenum target, GLenum query, GLfixed *v); GLAPI void APIENTRY glGetMaterialxOES (GLenum face, GLenum pname, GLfixed param); GLAPI void APIENTRY glGetPixelMapxv (GLenum map, GLint size, GLfixed *values); GLAPI void APIENTRY glGetTexGenxvOES (GLenum coord, GLenum pname, GLfixed *params); GLAPI void APIENTRY glGetTexLevelParameterxvOES (GLenum target, GLint level, GLenum pname, GLfixed *params); GLAPI void APIENTRY glIndexxOES (GLfixed component); GLAPI void APIENTRY glIndexxvOES (const GLfixed *component); GLAPI void APIENTRY glLoadTransposeMatrixxOES (const GLfixed *m); GLAPI void APIENTRY glMap1xOES (GLenum target, GLfixed u1, GLfixed u2, GLint stride, GLint order, GLfixed points); GLAPI void APIENTRY glMap2xOES (GLenum target, GLfixed u1, GLfixed u2, GLint ustride, GLint uorder, GLfixed v1, GLfixed v2, GLint vstride, GLint vorder, GLfixed points); GLAPI void APIENTRY glMapGrid1xOES (GLint n, GLfixed u1, GLfixed u2); GLAPI void APIENTRY glMapGrid2xOES (GLint n, GLfixed u1, GLfixed u2, GLfixed v1, GLfixed v2); GLAPI void APIENTRY glMultTransposeMatrixxOES (const GLfixed *m); GLAPI void APIENTRY glMultiTexCoord1xOES (GLenum texture, GLfixed s); GLAPI void APIENTRY glMultiTexCoord1xvOES (GLenum texture, const GLfixed *coords); GLAPI void APIENTRY glMultiTexCoord2xOES (GLenum texture, GLfixed s, GLfixed t); GLAPI void APIENTRY glMultiTexCoord2xvOES (GLenum texture, const GLfixed *coords); GLAPI void APIENTRY glMultiTexCoord3xOES (GLenum texture, GLfixed s, GLfixed t, GLfixed r); GLAPI void APIENTRY glMultiTexCoord3xvOES (GLenum texture, const GLfixed *coords); GLAPI void APIENTRY glMultiTexCoord4xvOES (GLenum texture, const GLfixed *coords); GLAPI void APIENTRY glNormal3xvOES (const GLfixed *coords); GLAPI void APIENTRY glPassThroughxOES (GLfixed token); GLAPI void APIENTRY glPixelMapx (GLenum map, GLint size, const GLfixed *values); GLAPI void APIENTRY glPixelStorex (GLenum pname, GLfixed param); GLAPI void APIENTRY glPixelTransferxOES (GLenum pname, GLfixed param); GLAPI void APIENTRY glPixelZoomxOES (GLfixed xfactor, GLfixed yfactor); GLAPI void APIENTRY glPrioritizeTexturesxOES (GLsizei n, const GLuint *textures, const GLfixed *priorities); GLAPI void APIENTRY glRasterPos2xOES (GLfixed x, GLfixed y); GLAPI void APIENTRY glRasterPos2xvOES (const GLfixed *coords); GLAPI void APIENTRY glRasterPos3xOES (GLfixed x, GLfixed y, GLfixed z); GLAPI void APIENTRY glRasterPos3xvOES (const GLfixed *coords); GLAPI void APIENTRY glRasterPos4xOES (GLfixed x, GLfixed y, GLfixed z, GLfixed w); GLAPI void APIENTRY glRasterPos4xvOES (const GLfixed *coords); GLAPI void APIENTRY glRectxOES (GLfixed x1, GLfixed y1, GLfixed x2, GLfixed y2); GLAPI void APIENTRY glRectxvOES (const GLfixed *v1, const GLfixed *v2); GLAPI void APIENTRY glTexCoord1xOES (GLfixed s); GLAPI void APIENTRY glTexCoord1xvOES (const GLfixed *coords); GLAPI void APIENTRY glTexCoord2xOES (GLfixed s, GLfixed t); GLAPI void APIENTRY glTexCoord2xvOES (const GLfixed *coords); GLAPI void APIENTRY glTexCoord3xOES (GLfixed s, GLfixed t, GLfixed r); GLAPI void APIENTRY glTexCoord3xvOES (const GLfixed *coords); GLAPI void APIENTRY glTexCoord4xOES (GLfixed s, GLfixed t, GLfixed r, GLfixed q); GLAPI void APIENTRY glTexCoord4xvOES (const GLfixed *coords); GLAPI void APIENTRY glTexGenxOES (GLenum coord, GLenum pname, GLfixed param); GLAPI void APIENTRY glTexGenxvOES (GLenum coord, GLenum pname, const GLfixed *params); GLAPI void APIENTRY glVertex2xOES (GLfixed x); GLAPI void APIENTRY glVertex2xvOES (const GLfixed *coords); GLAPI void APIENTRY glVertex3xOES (GLfixed x, GLfixed y); GLAPI void APIENTRY glVertex3xvOES (const GLfixed *coords); GLAPI void APIENTRY glVertex4xOES (GLfixed x, GLfixed y, GLfixed z); GLAPI void APIENTRY glVertex4xvOES (const GLfixed *coords); #endif #endif /* GL_OES_fixed_point */ #ifndef GL_OES_query_matrix #define GL_OES_query_matrix 1 typedef GLbitfield (APIENTRYP PFNGLQUERYMATRIXXOESPROC) (GLfixed *mantissa, GLint *exponent); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLbitfield APIENTRY glQueryMatrixxOES (GLfixed *mantissa, GLint *exponent); #endif #endif /* GL_OES_query_matrix */ #ifndef GL_OES_read_format #define GL_OES_read_format 1 #define GL_IMPLEMENTATION_COLOR_READ_TYPE_OES 0x8B9A #define GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES 0x8B9B #endif /* GL_OES_read_format */ #ifndef GL_OES_single_precision #define GL_OES_single_precision 1 typedef void (APIENTRYP PFNGLCLEARDEPTHFOESPROC) (GLclampf depth); typedef void (APIENTRYP PFNGLCLIPPLANEFOESPROC) (GLenum plane, const GLfloat *equation); typedef void (APIENTRYP PFNGLDEPTHRANGEFOESPROC) (GLclampf n, GLclampf f); typedef void (APIENTRYP PFNGLFRUSTUMFOESPROC) (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); typedef void (APIENTRYP PFNGLGETCLIPPLANEFOESPROC) (GLenum plane, GLfloat *equation); typedef void (APIENTRYP PFNGLORTHOFOESPROC) (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glClearDepthfOES (GLclampf depth); GLAPI void APIENTRY glClipPlanefOES (GLenum plane, const GLfloat *equation); GLAPI void APIENTRY glDepthRangefOES (GLclampf n, GLclampf f); GLAPI void APIENTRY glFrustumfOES (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); GLAPI void APIENTRY glGetClipPlanefOES (GLenum plane, GLfloat *equation); GLAPI void APIENTRY glOrthofOES (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); #endif #endif /* GL_OES_single_precision */ #ifndef GL_3DFX_multisample #define GL_3DFX_multisample 1 #define GL_MULTISAMPLE_3DFX 0x86B2 #define GL_SAMPLE_BUFFERS_3DFX 0x86B3 #define GL_SAMPLES_3DFX 0x86B4 #define GL_MULTISAMPLE_BIT_3DFX 0x20000000 #endif /* GL_3DFX_multisample */ #ifndef GL_3DFX_tbuffer #define GL_3DFX_tbuffer 1 typedef void (APIENTRYP PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTbufferMask3DFX (GLuint mask); #endif #endif /* GL_3DFX_tbuffer */ #ifndef GL_3DFX_texture_compression_FXT1 #define GL_3DFX_texture_compression_FXT1 1 #define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 #define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 #endif /* GL_3DFX_texture_compression_FXT1 */ #ifndef GL_AMD_blend_minmax_factor #define GL_AMD_blend_minmax_factor 1 #define GL_FACTOR_MIN_AMD 0x901C #define GL_FACTOR_MAX_AMD 0x901D #endif /* GL_AMD_blend_minmax_factor */ #ifndef GL_AMD_conservative_depth #define GL_AMD_conservative_depth 1 #endif /* GL_AMD_conservative_depth */ #ifndef GL_AMD_debug_output #define GL_AMD_debug_output 1 typedef void (APIENTRY *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,void *userParam); #define GL_MAX_DEBUG_MESSAGE_LENGTH_AMD 0x9143 #define GL_MAX_DEBUG_LOGGED_MESSAGES_AMD 0x9144 #define GL_DEBUG_LOGGED_MESSAGES_AMD 0x9145 #define GL_DEBUG_SEVERITY_HIGH_AMD 0x9146 #define GL_DEBUG_SEVERITY_MEDIUM_AMD 0x9147 #define GL_DEBUG_SEVERITY_LOW_AMD 0x9148 #define GL_DEBUG_CATEGORY_API_ERROR_AMD 0x9149 #define GL_DEBUG_CATEGORY_WINDOW_SYSTEM_AMD 0x914A #define GL_DEBUG_CATEGORY_DEPRECATION_AMD 0x914B #define GL_DEBUG_CATEGORY_UNDEFINED_BEHAVIOR_AMD 0x914C #define GL_DEBUG_CATEGORY_PERFORMANCE_AMD 0x914D #define GL_DEBUG_CATEGORY_SHADER_COMPILER_AMD 0x914E #define GL_DEBUG_CATEGORY_APPLICATION_AMD 0x914F #define GL_DEBUG_CATEGORY_OTHER_AMD 0x9150 typedef void (APIENTRYP PFNGLDEBUGMESSAGEENABLEAMDPROC) (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTAMDPROC) (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKAMDPROC) (GLDEBUGPROCAMD callback, void *userParam); typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGAMDPROC) (GLuint count, GLsizei bufSize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDebugMessageEnableAMD (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); GLAPI void APIENTRY glDebugMessageInsertAMD (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); GLAPI void APIENTRY glDebugMessageCallbackAMD (GLDEBUGPROCAMD callback, void *userParam); GLAPI GLuint APIENTRY glGetDebugMessageLogAMD (GLuint count, GLsizei bufSize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); #endif #endif /* GL_AMD_debug_output */ #ifndef GL_AMD_depth_clamp_separate #define GL_AMD_depth_clamp_separate 1 #define GL_DEPTH_CLAMP_NEAR_AMD 0x901E #define GL_DEPTH_CLAMP_FAR_AMD 0x901F #endif /* GL_AMD_depth_clamp_separate */ #ifndef GL_AMD_draw_buffers_blend #define GL_AMD_draw_buffers_blend 1 typedef void (APIENTRYP PFNGLBLENDFUNCINDEXEDAMDPROC) (GLuint buf, GLenum src, GLenum dst); typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); typedef void (APIENTRYP PFNGLBLENDEQUATIONINDEXEDAMDPROC) (GLuint buf, GLenum mode); typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendFuncIndexedAMD (GLuint buf, GLenum src, GLenum dst); GLAPI void APIENTRY glBlendFuncSeparateIndexedAMD (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); GLAPI void APIENTRY glBlendEquationIndexedAMD (GLuint buf, GLenum mode); GLAPI void APIENTRY glBlendEquationSeparateIndexedAMD (GLuint buf, GLenum modeRGB, GLenum modeAlpha); #endif #endif /* GL_AMD_draw_buffers_blend */ #ifndef GL_AMD_framebuffer_multisample_advanced #define GL_AMD_framebuffer_multisample_advanced 1 #define GL_RENDERBUFFER_STORAGE_SAMPLES_AMD 0x91B2 #define GL_MAX_COLOR_FRAMEBUFFER_SAMPLES_AMD 0x91B3 #define GL_MAX_COLOR_FRAMEBUFFER_STORAGE_SAMPLES_AMD 0x91B4 #define GL_MAX_DEPTH_STENCIL_FRAMEBUFFER_SAMPLES_AMD 0x91B5 #define GL_NUM_SUPPORTED_MULTISAMPLE_MODES_AMD 0x91B6 #define GL_SUPPORTED_MULTISAMPLE_MODES_AMD 0x91B7 typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC) (GLenum target, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC) (GLuint renderbuffer, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glRenderbufferStorageMultisampleAdvancedAMD (GLenum target, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleAdvancedAMD (GLuint renderbuffer, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); #endif #endif /* GL_AMD_framebuffer_multisample_advanced */ #ifndef GL_AMD_framebuffer_sample_positions #define GL_AMD_framebuffer_sample_positions 1 #define GL_SUBSAMPLE_DISTANCE_AMD 0x883F #define GL_PIXELS_PER_SAMPLE_PATTERN_X_AMD 0x91AE #define GL_PIXELS_PER_SAMPLE_PATTERN_Y_AMD 0x91AF #define GL_ALL_PIXELS_AMD 0xFFFFFFFF typedef void (APIENTRYP PFNGLFRAMEBUFFERSAMPLEPOSITIONSFVAMDPROC) (GLenum target, GLuint numsamples, GLuint pixelindex, const GLfloat *values); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLEPOSITIONSFVAMDPROC) (GLuint framebuffer, GLuint numsamples, GLuint pixelindex, const GLfloat *values); typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERFVAMDPROC) (GLenum target, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERFVAMDPROC) (GLuint framebuffer, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFramebufferSamplePositionsfvAMD (GLenum target, GLuint numsamples, GLuint pixelindex, const GLfloat *values); GLAPI void APIENTRY glNamedFramebufferSamplePositionsfvAMD (GLuint framebuffer, GLuint numsamples, GLuint pixelindex, const GLfloat *values); GLAPI void APIENTRY glGetFramebufferParameterfvAMD (GLenum target, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); GLAPI void APIENTRY glGetNamedFramebufferParameterfvAMD (GLuint framebuffer, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); #endif #endif /* GL_AMD_framebuffer_sample_positions */ #ifndef GL_AMD_gcn_shader #define GL_AMD_gcn_shader 1 #endif /* GL_AMD_gcn_shader */ #ifndef GL_AMD_gpu_shader_half_float #define GL_AMD_gpu_shader_half_float 1 #define GL_FLOAT16_NV 0x8FF8 #define GL_FLOAT16_VEC2_NV 0x8FF9 #define GL_FLOAT16_VEC3_NV 0x8FFA #define GL_FLOAT16_VEC4_NV 0x8FFB #define GL_FLOAT16_MAT2_AMD 0x91C5 #define GL_FLOAT16_MAT3_AMD 0x91C6 #define GL_FLOAT16_MAT4_AMD 0x91C7 #define GL_FLOAT16_MAT2x3_AMD 0x91C8 #define GL_FLOAT16_MAT2x4_AMD 0x91C9 #define GL_FLOAT16_MAT3x2_AMD 0x91CA #define GL_FLOAT16_MAT3x4_AMD 0x91CB #define GL_FLOAT16_MAT4x2_AMD 0x91CC #define GL_FLOAT16_MAT4x3_AMD 0x91CD #endif /* GL_AMD_gpu_shader_half_float */ #ifndef GL_AMD_gpu_shader_int16 #define GL_AMD_gpu_shader_int16 1 #endif /* GL_AMD_gpu_shader_int16 */ #ifndef GL_AMD_gpu_shader_int64 #define GL_AMD_gpu_shader_int64 1 typedef khronos_int64_t GLint64EXT; #define GL_INT64_NV 0x140E #define GL_UNSIGNED_INT64_NV 0x140F #define GL_INT8_NV 0x8FE0 #define GL_INT8_VEC2_NV 0x8FE1 #define GL_INT8_VEC3_NV 0x8FE2 #define GL_INT8_VEC4_NV 0x8FE3 #define GL_INT16_NV 0x8FE4 #define GL_INT16_VEC2_NV 0x8FE5 #define GL_INT16_VEC3_NV 0x8FE6 #define GL_INT16_VEC4_NV 0x8FE7 #define GL_INT64_VEC2_NV 0x8FE9 #define GL_INT64_VEC3_NV 0x8FEA #define GL_INT64_VEC4_NV 0x8FEB #define GL_UNSIGNED_INT8_NV 0x8FEC #define GL_UNSIGNED_INT8_VEC2_NV 0x8FED #define GL_UNSIGNED_INT8_VEC3_NV 0x8FEE #define GL_UNSIGNED_INT8_VEC4_NV 0x8FEF #define GL_UNSIGNED_INT16_NV 0x8FF0 #define GL_UNSIGNED_INT16_VEC2_NV 0x8FF1 #define GL_UNSIGNED_INT16_VEC3_NV 0x8FF2 #define GL_UNSIGNED_INT16_VEC4_NV 0x8FF3 #define GL_UNSIGNED_INT64_VEC2_NV 0x8FF5 #define GL_UNSIGNED_INT64_VEC3_NV 0x8FF6 #define GL_UNSIGNED_INT64_VEC4_NV 0x8FF7 typedef void (APIENTRYP PFNGLUNIFORM1I64NVPROC) (GLint location, GLint64EXT x); typedef void (APIENTRYP PFNGLUNIFORM2I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y); typedef void (APIENTRYP PFNGLUNIFORM3I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); typedef void (APIENTRYP PFNGLUNIFORM4I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); typedef void (APIENTRYP PFNGLUNIFORM1I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); typedef void (APIENTRYP PFNGLUNIFORM2I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); typedef void (APIENTRYP PFNGLUNIFORM3I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); typedef void (APIENTRYP PFNGLUNIFORM4I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); typedef void (APIENTRYP PFNGLUNIFORM1UI64NVPROC) (GLint location, GLuint64EXT x); typedef void (APIENTRYP PFNGLUNIFORM2UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y); typedef void (APIENTRYP PFNGLUNIFORM3UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); typedef void (APIENTRYP PFNGLUNIFORM4UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); typedef void (APIENTRYP PFNGLUNIFORM1UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); typedef void (APIENTRYP PFNGLUNIFORM2UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); typedef void (APIENTRYP PFNGLUNIFORM3UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); typedef void (APIENTRYP PFNGLUNIFORM4UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); typedef void (APIENTRYP PFNGLGETUNIFORMI64VNVPROC) (GLuint program, GLint location, GLint64EXT *params); typedef void (APIENTRYP PFNGLGETUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLuint64EXT *params); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64NVPROC) (GLuint program, GLint location, GLint64EXT x); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glUniform1i64NV (GLint location, GLint64EXT x); GLAPI void APIENTRY glUniform2i64NV (GLint location, GLint64EXT x, GLint64EXT y); GLAPI void APIENTRY glUniform3i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); GLAPI void APIENTRY glUniform4i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); GLAPI void APIENTRY glUniform1i64vNV (GLint location, GLsizei count, const GLint64EXT *value); GLAPI void APIENTRY glUniform2i64vNV (GLint location, GLsizei count, const GLint64EXT *value); GLAPI void APIENTRY glUniform3i64vNV (GLint location, GLsizei count, const GLint64EXT *value); GLAPI void APIENTRY glUniform4i64vNV (GLint location, GLsizei count, const GLint64EXT *value); GLAPI void APIENTRY glUniform1ui64NV (GLint location, GLuint64EXT x); GLAPI void APIENTRY glUniform2ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y); GLAPI void APIENTRY glUniform3ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); GLAPI void APIENTRY glUniform4ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); GLAPI void APIENTRY glUniform1ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); GLAPI void APIENTRY glUniform2ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); GLAPI void APIENTRY glUniform3ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); GLAPI void APIENTRY glUniform4ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); GLAPI void APIENTRY glGetUniformi64vNV (GLuint program, GLint location, GLint64EXT *params); GLAPI void APIENTRY glGetUniformui64vNV (GLuint program, GLint location, GLuint64EXT *params); GLAPI void APIENTRY glProgramUniform1i64NV (GLuint program, GLint location, GLint64EXT x); GLAPI void APIENTRY glProgramUniform2i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); GLAPI void APIENTRY glProgramUniform3i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); GLAPI void APIENTRY glProgramUniform4i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); GLAPI void APIENTRY glProgramUniform1i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); GLAPI void APIENTRY glProgramUniform2i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); GLAPI void APIENTRY glProgramUniform3i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); GLAPI void APIENTRY glProgramUniform4i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); GLAPI void APIENTRY glProgramUniform1ui64NV (GLuint program, GLint location, GLuint64EXT x); GLAPI void APIENTRY glProgramUniform2ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); GLAPI void APIENTRY glProgramUniform3ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); GLAPI void APIENTRY glProgramUniform4ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); GLAPI void APIENTRY glProgramUniform1ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); GLAPI void APIENTRY glProgramUniform2ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); GLAPI void APIENTRY glProgramUniform3ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); GLAPI void APIENTRY glProgramUniform4ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); #endif #endif /* GL_AMD_gpu_shader_int64 */ #ifndef GL_AMD_interleaved_elements #define GL_AMD_interleaved_elements 1 #define GL_VERTEX_ELEMENT_SWIZZLE_AMD 0x91A4 #define GL_VERTEX_ID_SWIZZLE_AMD 0x91A5 typedef void (APIENTRYP PFNGLVERTEXATTRIBPARAMETERIAMDPROC) (GLuint index, GLenum pname, GLint param); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertexAttribParameteriAMD (GLuint index, GLenum pname, GLint param); #endif #endif /* GL_AMD_interleaved_elements */ #ifndef GL_AMD_multi_draw_indirect #define GL_AMD_multi_draw_indirect 1 typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTAMDPROC) (GLenum mode, const void *indirect, GLsizei primcount, GLsizei stride); typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTAMDPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei primcount, GLsizei stride); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMultiDrawArraysIndirectAMD (GLenum mode, const void *indirect, GLsizei primcount, GLsizei stride); GLAPI void APIENTRY glMultiDrawElementsIndirectAMD (GLenum mode, GLenum type, const void *indirect, GLsizei primcount, GLsizei stride); #endif #endif /* GL_AMD_multi_draw_indirect */ #ifndef GL_AMD_name_gen_delete #define GL_AMD_name_gen_delete 1 #define GL_DATA_BUFFER_AMD 0x9151 #define GL_PERFORMANCE_MONITOR_AMD 0x9152 #define GL_QUERY_OBJECT_AMD 0x9153 #define GL_VERTEX_ARRAY_OBJECT_AMD 0x9154 #define GL_SAMPLER_OBJECT_AMD 0x9155 typedef void (APIENTRYP PFNGLGENNAMESAMDPROC) (GLenum identifier, GLuint num, GLuint *names); typedef void (APIENTRYP PFNGLDELETENAMESAMDPROC) (GLenum identifier, GLuint num, const GLuint *names); typedef GLboolean (APIENTRYP PFNGLISNAMEAMDPROC) (GLenum identifier, GLuint name); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGenNamesAMD (GLenum identifier, GLuint num, GLuint *names); GLAPI void APIENTRY glDeleteNamesAMD (GLenum identifier, GLuint num, const GLuint *names); GLAPI GLboolean APIENTRY glIsNameAMD (GLenum identifier, GLuint name); #endif #endif /* GL_AMD_name_gen_delete */ #ifndef GL_AMD_occlusion_query_event #define GL_AMD_occlusion_query_event 1 #define GL_OCCLUSION_QUERY_EVENT_MASK_AMD 0x874F #define GL_QUERY_DEPTH_PASS_EVENT_BIT_AMD 0x00000001 #define GL_QUERY_DEPTH_FAIL_EVENT_BIT_AMD 0x00000002 #define GL_QUERY_STENCIL_FAIL_EVENT_BIT_AMD 0x00000004 #define GL_QUERY_DEPTH_BOUNDS_FAIL_EVENT_BIT_AMD 0x00000008 #define GL_QUERY_ALL_EVENT_BITS_AMD 0xFFFFFFFF typedef void (APIENTRYP PFNGLQUERYOBJECTPARAMETERUIAMDPROC) (GLenum target, GLuint id, GLenum pname, GLuint param); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glQueryObjectParameteruiAMD (GLenum target, GLuint id, GLenum pname, GLuint param); #endif #endif /* GL_AMD_occlusion_query_event */ #ifndef GL_AMD_performance_monitor #define GL_AMD_performance_monitor 1 #define GL_COUNTER_TYPE_AMD 0x8BC0 #define GL_COUNTER_RANGE_AMD 0x8BC1 #define GL_UNSIGNED_INT64_AMD 0x8BC2 #define GL_PERCENTAGE_AMD 0x8BC3 #define GL_PERFMON_RESULT_AVAILABLE_AMD 0x8BC4 #define GL_PERFMON_RESULT_SIZE_AMD 0x8BC5 #define GL_PERFMON_RESULT_AMD 0x8BC6 typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSAMDPROC) (GLint *numGroups, GLsizei groupsSize, GLuint *groups); typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSAMDPROC) (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSTRINGAMDPROC) (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC) (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERINFOAMDPROC) (GLuint group, GLuint counter, GLenum pname, void *data); typedef void (APIENTRYP PFNGLGENPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); typedef void (APIENTRYP PFNGLDELETEPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); typedef void (APIENTRYP PFNGLSELECTPERFMONITORCOUNTERSAMDPROC) (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); typedef void (APIENTRYP PFNGLBEGINPERFMONITORAMDPROC) (GLuint monitor); typedef void (APIENTRYP PFNGLENDPERFMONITORAMDPROC) (GLuint monitor); typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERDATAAMDPROC) (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGetPerfMonitorGroupsAMD (GLint *numGroups, GLsizei groupsSize, GLuint *groups); GLAPI void APIENTRY glGetPerfMonitorCountersAMD (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); GLAPI void APIENTRY glGetPerfMonitorGroupStringAMD (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); GLAPI void APIENTRY glGetPerfMonitorCounterStringAMD (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); GLAPI void APIENTRY glGetPerfMonitorCounterInfoAMD (GLuint group, GLuint counter, GLenum pname, void *data); GLAPI void APIENTRY glGenPerfMonitorsAMD (GLsizei n, GLuint *monitors); GLAPI void APIENTRY glDeletePerfMonitorsAMD (GLsizei n, GLuint *monitors); GLAPI void APIENTRY glSelectPerfMonitorCountersAMD (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); GLAPI void APIENTRY glBeginPerfMonitorAMD (GLuint monitor); GLAPI void APIENTRY glEndPerfMonitorAMD (GLuint monitor); GLAPI void APIENTRY glGetPerfMonitorCounterDataAMD (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); #endif #endif /* GL_AMD_performance_monitor */ #ifndef GL_AMD_pinned_memory #define GL_AMD_pinned_memory 1 #define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160 #endif /* GL_AMD_pinned_memory */ #ifndef GL_AMD_query_buffer_object #define GL_AMD_query_buffer_object 1 #define GL_QUERY_BUFFER_AMD 0x9192 #define GL_QUERY_BUFFER_BINDING_AMD 0x9193 #define GL_QUERY_RESULT_NO_WAIT_AMD 0x9194 #endif /* GL_AMD_query_buffer_object */ #ifndef GL_AMD_sample_positions #define GL_AMD_sample_positions 1 typedef void (APIENTRYP PFNGLSETMULTISAMPLEFVAMDPROC) (GLenum pname, GLuint index, const GLfloat *val); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSetMultisamplefvAMD (GLenum pname, GLuint index, const GLfloat *val); #endif #endif /* GL_AMD_sample_positions */ #ifndef GL_AMD_seamless_cubemap_per_texture #define GL_AMD_seamless_cubemap_per_texture 1 #endif /* GL_AMD_seamless_cubemap_per_texture */ #ifndef GL_AMD_shader_atomic_counter_ops #define GL_AMD_shader_atomic_counter_ops 1 #endif /* GL_AMD_shader_atomic_counter_ops */ #ifndef GL_AMD_shader_ballot #define GL_AMD_shader_ballot 1 #endif /* GL_AMD_shader_ballot */ #ifndef GL_AMD_shader_explicit_vertex_parameter #define GL_AMD_shader_explicit_vertex_parameter 1 #endif /* GL_AMD_shader_explicit_vertex_parameter */ #ifndef GL_AMD_shader_gpu_shader_half_float_fetch #define GL_AMD_shader_gpu_shader_half_float_fetch 1 #endif /* GL_AMD_shader_gpu_shader_half_float_fetch */ #ifndef GL_AMD_shader_image_load_store_lod #define GL_AMD_shader_image_load_store_lod 1 #endif /* GL_AMD_shader_image_load_store_lod */ #ifndef GL_AMD_shader_stencil_export #define GL_AMD_shader_stencil_export 1 #endif /* GL_AMD_shader_stencil_export */ #ifndef GL_AMD_shader_trinary_minmax #define GL_AMD_shader_trinary_minmax 1 #endif /* GL_AMD_shader_trinary_minmax */ #ifndef GL_AMD_sparse_texture #define GL_AMD_sparse_texture 1 #define GL_VIRTUAL_PAGE_SIZE_X_AMD 0x9195 #define GL_VIRTUAL_PAGE_SIZE_Y_AMD 0x9196 #define GL_VIRTUAL_PAGE_SIZE_Z_AMD 0x9197 #define GL_MAX_SPARSE_TEXTURE_SIZE_AMD 0x9198 #define GL_MAX_SPARSE_3D_TEXTURE_SIZE_AMD 0x9199 #define GL_MAX_SPARSE_ARRAY_TEXTURE_LAYERS 0x919A #define GL_MIN_SPARSE_LEVEL_AMD 0x919B #define GL_MIN_LOD_WARNING_AMD 0x919C #define GL_TEXTURE_STORAGE_SPARSE_BIT_AMD 0x00000001 typedef void (APIENTRYP PFNGLTEXSTORAGESPARSEAMDPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); typedef void (APIENTRYP PFNGLTEXTURESTORAGESPARSEAMDPROC) (GLuint texture, GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexStorageSparseAMD (GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); GLAPI void APIENTRY glTextureStorageSparseAMD (GLuint texture, GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); #endif #endif /* GL_AMD_sparse_texture */ #ifndef GL_AMD_stencil_operation_extended #define GL_AMD_stencil_operation_extended 1 #define GL_SET_AMD 0x874A #define GL_REPLACE_VALUE_AMD 0x874B #define GL_STENCIL_OP_VALUE_AMD 0x874C #define GL_STENCIL_BACK_OP_VALUE_AMD 0x874D typedef void (APIENTRYP PFNGLSTENCILOPVALUEAMDPROC) (GLenum face, GLuint value); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glStencilOpValueAMD (GLenum face, GLuint value); #endif #endif /* GL_AMD_stencil_operation_extended */ #ifndef GL_AMD_texture_gather_bias_lod #define GL_AMD_texture_gather_bias_lod 1 #endif /* GL_AMD_texture_gather_bias_lod */ #ifndef GL_AMD_texture_texture4 #define GL_AMD_texture_texture4 1 #endif /* GL_AMD_texture_texture4 */ #ifndef GL_AMD_transform_feedback3_lines_triangles #define GL_AMD_transform_feedback3_lines_triangles 1 #endif /* GL_AMD_transform_feedback3_lines_triangles */ #ifndef GL_AMD_transform_feedback4 #define GL_AMD_transform_feedback4 1 #define GL_STREAM_RASTERIZATION_AMD 0x91A0 #endif /* GL_AMD_transform_feedback4 */ #ifndef GL_AMD_vertex_shader_layer #define GL_AMD_vertex_shader_layer 1 #endif /* GL_AMD_vertex_shader_layer */ #ifndef GL_AMD_vertex_shader_tessellator #define GL_AMD_vertex_shader_tessellator 1 #define GL_SAMPLER_BUFFER_AMD 0x9001 #define GL_INT_SAMPLER_BUFFER_AMD 0x9002 #define GL_UNSIGNED_INT_SAMPLER_BUFFER_AMD 0x9003 #define GL_TESSELLATION_MODE_AMD 0x9004 #define GL_TESSELLATION_FACTOR_AMD 0x9005 #define GL_DISCRETE_AMD 0x9006 #define GL_CONTINUOUS_AMD 0x9007 typedef void (APIENTRYP PFNGLTESSELLATIONFACTORAMDPROC) (GLfloat factor); typedef void (APIENTRYP PFNGLTESSELLATIONMODEAMDPROC) (GLenum mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTessellationFactorAMD (GLfloat factor); GLAPI void APIENTRY glTessellationModeAMD (GLenum mode); #endif #endif /* GL_AMD_vertex_shader_tessellator */ #ifndef GL_AMD_vertex_shader_viewport_index #define GL_AMD_vertex_shader_viewport_index 1 #endif /* GL_AMD_vertex_shader_viewport_index */ #ifndef GL_APPLE_aux_depth_stencil #define GL_APPLE_aux_depth_stencil 1 #define GL_AUX_DEPTH_STENCIL_APPLE 0x8A14 #endif /* GL_APPLE_aux_depth_stencil */ #ifndef GL_APPLE_client_storage #define GL_APPLE_client_storage 1 #define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 #endif /* GL_APPLE_client_storage */ #ifndef GL_APPLE_element_array #define GL_APPLE_element_array 1 #define GL_ELEMENT_ARRAY_APPLE 0x8A0C #define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8A0D #define GL_ELEMENT_ARRAY_POINTER_APPLE 0x8A0E typedef void (APIENTRYP PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const void *pointer); typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); typedef void (APIENTRYP PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glElementPointerAPPLE (GLenum type, const void *pointer); GLAPI void APIENTRY glDrawElementArrayAPPLE (GLenum mode, GLint first, GLsizei count); GLAPI void APIENTRY glDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); GLAPI void APIENTRY glMultiDrawElementArrayAPPLE (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); GLAPI void APIENTRY glMultiDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); #endif #endif /* GL_APPLE_element_array */ #ifndef GL_APPLE_fence #define GL_APPLE_fence 1 #define GL_DRAW_PIXELS_APPLE 0x8A0A #define GL_FENCE_APPLE 0x8A0B typedef void (APIENTRYP PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint *fences); typedef void (APIENTRYP PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint *fences); typedef void (APIENTRYP PFNGLSETFENCEAPPLEPROC) (GLuint fence); typedef GLboolean (APIENTRYP PFNGLISFENCEAPPLEPROC) (GLuint fence); typedef GLboolean (APIENTRYP PFNGLTESTFENCEAPPLEPROC) (GLuint fence); typedef void (APIENTRYP PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); typedef GLboolean (APIENTRYP PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); typedef void (APIENTRYP PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGenFencesAPPLE (GLsizei n, GLuint *fences); GLAPI void APIENTRY glDeleteFencesAPPLE (GLsizei n, const GLuint *fences); GLAPI void APIENTRY glSetFenceAPPLE (GLuint fence); GLAPI GLboolean APIENTRY glIsFenceAPPLE (GLuint fence); GLAPI GLboolean APIENTRY glTestFenceAPPLE (GLuint fence); GLAPI void APIENTRY glFinishFenceAPPLE (GLuint fence); GLAPI GLboolean APIENTRY glTestObjectAPPLE (GLenum object, GLuint name); GLAPI void APIENTRY glFinishObjectAPPLE (GLenum object, GLint name); #endif #endif /* GL_APPLE_fence */ #ifndef GL_APPLE_float_pixels #define GL_APPLE_float_pixels 1 #define GL_HALF_APPLE 0x140B #define GL_RGBA_FLOAT32_APPLE 0x8814 #define GL_RGB_FLOAT32_APPLE 0x8815 #define GL_ALPHA_FLOAT32_APPLE 0x8816 #define GL_INTENSITY_FLOAT32_APPLE 0x8817 #define GL_LUMINANCE_FLOAT32_APPLE 0x8818 #define GL_LUMINANCE_ALPHA_FLOAT32_APPLE 0x8819 #define GL_RGBA_FLOAT16_APPLE 0x881A #define GL_RGB_FLOAT16_APPLE 0x881B #define GL_ALPHA_FLOAT16_APPLE 0x881C #define GL_INTENSITY_FLOAT16_APPLE 0x881D #define GL_LUMINANCE_FLOAT16_APPLE 0x881E #define GL_LUMINANCE_ALPHA_FLOAT16_APPLE 0x881F #define GL_COLOR_FLOAT_APPLE 0x8A0F #endif /* GL_APPLE_float_pixels */ #ifndef GL_APPLE_flush_buffer_range #define GL_APPLE_flush_buffer_range 1 #define GL_BUFFER_SERIALIZED_MODIFY_APPLE 0x8A12 #define GL_BUFFER_FLUSHING_UNMAP_APPLE 0x8A13 typedef void (APIENTRYP PFNGLBUFFERPARAMETERIAPPLEPROC) (GLenum target, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC) (GLenum target, GLintptr offset, GLsizeiptr size); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBufferParameteriAPPLE (GLenum target, GLenum pname, GLint param); GLAPI void APIENTRY glFlushMappedBufferRangeAPPLE (GLenum target, GLintptr offset, GLsizeiptr size); #endif #endif /* GL_APPLE_flush_buffer_range */ #ifndef GL_APPLE_object_purgeable #define GL_APPLE_object_purgeable 1 #define GL_BUFFER_OBJECT_APPLE 0x85B3 #define GL_RELEASED_APPLE 0x8A19 #define GL_VOLATILE_APPLE 0x8A1A #define GL_RETAINED_APPLE 0x8A1B #define GL_UNDEFINED_APPLE 0x8A1C #define GL_PURGEABLE_APPLE 0x8A1D typedef GLenum (APIENTRYP PFNGLOBJECTPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); typedef GLenum (APIENTRYP PFNGLOBJECTUNPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVAPPLEPROC) (GLenum objectType, GLuint name, GLenum pname, GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLenum APIENTRY glObjectPurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); GLAPI GLenum APIENTRY glObjectUnpurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); GLAPI void APIENTRY glGetObjectParameterivAPPLE (GLenum objectType, GLuint name, GLenum pname, GLint *params); #endif #endif /* GL_APPLE_object_purgeable */ #ifndef GL_APPLE_rgb_422 #define GL_APPLE_rgb_422 1 #define GL_RGB_422_APPLE 0x8A1F #define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA #define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB #define GL_RGB_RAW_422_APPLE 0x8A51 #endif /* GL_APPLE_rgb_422 */ #ifndef GL_APPLE_row_bytes #define GL_APPLE_row_bytes 1 #define GL_PACK_ROW_BYTES_APPLE 0x8A15 #define GL_UNPACK_ROW_BYTES_APPLE 0x8A16 #endif /* GL_APPLE_row_bytes */ #ifndef GL_APPLE_specular_vector #define GL_APPLE_specular_vector 1 #define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 #endif /* GL_APPLE_specular_vector */ #ifndef GL_APPLE_texture_range #define GL_APPLE_texture_range 1 #define GL_TEXTURE_RANGE_LENGTH_APPLE 0x85B7 #define GL_TEXTURE_RANGE_POINTER_APPLE 0x85B8 #define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC #define GL_STORAGE_PRIVATE_APPLE 0x85BD #define GL_STORAGE_CACHED_APPLE 0x85BE #define GL_STORAGE_SHARED_APPLE 0x85BF typedef void (APIENTRYP PFNGLTEXTURERANGEAPPLEPROC) (GLenum target, GLsizei length, const void *pointer); typedef void (APIENTRYP PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC) (GLenum target, GLenum pname, void **params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTextureRangeAPPLE (GLenum target, GLsizei length, const void *pointer); GLAPI void APIENTRY glGetTexParameterPointervAPPLE (GLenum target, GLenum pname, void **params); #endif #endif /* GL_APPLE_texture_range */ #ifndef GL_APPLE_transform_hint #define GL_APPLE_transform_hint 1 #define GL_TRANSFORM_HINT_APPLE 0x85B1 #endif /* GL_APPLE_transform_hint */ #ifndef GL_APPLE_vertex_array_object #define GL_APPLE_vertex_array_object 1 #define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 typedef void (APIENTRYP PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); typedef void (APIENTRYP PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, GLuint *arrays); typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBindVertexArrayAPPLE (GLuint array); GLAPI void APIENTRY glDeleteVertexArraysAPPLE (GLsizei n, const GLuint *arrays); GLAPI void APIENTRY glGenVertexArraysAPPLE (GLsizei n, GLuint *arrays); GLAPI GLboolean APIENTRY glIsVertexArrayAPPLE (GLuint array); #endif #endif /* GL_APPLE_vertex_array_object */ #ifndef GL_APPLE_vertex_array_range #define GL_APPLE_vertex_array_range 1 #define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D #define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E #define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F #define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 #define GL_STORAGE_CLIENT_APPLE 0x85B4 typedef void (APIENTRYP PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, void *pointer); typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, void *pointer); typedef void (APIENTRYP PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertexArrayRangeAPPLE (GLsizei length, void *pointer); GLAPI void APIENTRY glFlushVertexArrayRangeAPPLE (GLsizei length, void *pointer); GLAPI void APIENTRY glVertexArrayParameteriAPPLE (GLenum pname, GLint param); #endif #endif /* GL_APPLE_vertex_array_range */ #ifndef GL_APPLE_vertex_program_evaluators #define GL_APPLE_vertex_program_evaluators 1 #define GL_VERTEX_ATTRIB_MAP1_APPLE 0x8A00 #define GL_VERTEX_ATTRIB_MAP2_APPLE 0x8A01 #define GL_VERTEX_ATTRIB_MAP1_SIZE_APPLE 0x8A02 #define GL_VERTEX_ATTRIB_MAP1_COEFF_APPLE 0x8A03 #define GL_VERTEX_ATTRIB_MAP1_ORDER_APPLE 0x8A04 #define GL_VERTEX_ATTRIB_MAP1_DOMAIN_APPLE 0x8A05 #define GL_VERTEX_ATTRIB_MAP2_SIZE_APPLE 0x8A06 #define GL_VERTEX_ATTRIB_MAP2_COEFF_APPLE 0x8A07 #define GL_VERTEX_ATTRIB_MAP2_ORDER_APPLE 0x8A08 #define GL_VERTEX_ATTRIB_MAP2_DOMAIN_APPLE 0x8A09 typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); typedef GLboolean (APIENTRYP PFNGLISVERTEXATTRIBENABLEDAPPLEPROC) (GLuint index, GLenum pname); typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glEnableVertexAttribAPPLE (GLuint index, GLenum pname); GLAPI void APIENTRY glDisableVertexAttribAPPLE (GLuint index, GLenum pname); GLAPI GLboolean APIENTRY glIsVertexAttribEnabledAPPLE (GLuint index, GLenum pname); GLAPI void APIENTRY glMapVertexAttrib1dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); GLAPI void APIENTRY glMapVertexAttrib1fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); GLAPI void APIENTRY glMapVertexAttrib2dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); GLAPI void APIENTRY glMapVertexAttrib2fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); #endif #endif /* GL_APPLE_vertex_program_evaluators */ #ifndef GL_APPLE_ycbcr_422 #define GL_APPLE_ycbcr_422 1 #define GL_YCBCR_422_APPLE 0x85B9 #endif /* GL_APPLE_ycbcr_422 */ #ifndef GL_ATI_draw_buffers #define GL_ATI_draw_buffers 1 #define GL_MAX_DRAW_BUFFERS_ATI 0x8824 #define GL_DRAW_BUFFER0_ATI 0x8825 #define GL_DRAW_BUFFER1_ATI 0x8826 #define GL_DRAW_BUFFER2_ATI 0x8827 #define GL_DRAW_BUFFER3_ATI 0x8828 #define GL_DRAW_BUFFER4_ATI 0x8829 #define GL_DRAW_BUFFER5_ATI 0x882A #define GL_DRAW_BUFFER6_ATI 0x882B #define GL_DRAW_BUFFER7_ATI 0x882C #define GL_DRAW_BUFFER8_ATI 0x882D #define GL_DRAW_BUFFER9_ATI 0x882E #define GL_DRAW_BUFFER10_ATI 0x882F #define GL_DRAW_BUFFER11_ATI 0x8830 #define GL_DRAW_BUFFER12_ATI 0x8831 #define GL_DRAW_BUFFER13_ATI 0x8832 #define GL_DRAW_BUFFER14_ATI 0x8833 #define GL_DRAW_BUFFER15_ATI 0x8834 typedef void (APIENTRYP PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum *bufs); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawBuffersATI (GLsizei n, const GLenum *bufs); #endif #endif /* GL_ATI_draw_buffers */ #ifndef GL_ATI_element_array #define GL_ATI_element_array 1 #define GL_ELEMENT_ARRAY_ATI 0x8768 #define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 #define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A typedef void (APIENTRYP PFNGLELEMENTPOINTERATIPROC) (GLenum type, const void *pointer); typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glElementPointerATI (GLenum type, const void *pointer); GLAPI void APIENTRY glDrawElementArrayATI (GLenum mode, GLsizei count); GLAPI void APIENTRY glDrawRangeElementArrayATI (GLenum mode, GLuint start, GLuint end, GLsizei count); #endif #endif /* GL_ATI_element_array */ #ifndef GL_ATI_envmap_bumpmap #define GL_ATI_envmap_bumpmap 1 #define GL_BUMP_ROT_MATRIX_ATI 0x8775 #define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 #define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 #define GL_BUMP_TEX_UNITS_ATI 0x8778 #define GL_DUDV_ATI 0x8779 #define GL_DU8DV8_ATI 0x877A #define GL_BUMP_ENVMAP_ATI 0x877B #define GL_BUMP_TARGET_ATI 0x877C typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, const GLint *param); typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, const GLfloat *param); typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexBumpParameterivATI (GLenum pname, const GLint *param); GLAPI void APIENTRY glTexBumpParameterfvATI (GLenum pname, const GLfloat *param); GLAPI void APIENTRY glGetTexBumpParameterivATI (GLenum pname, GLint *param); GLAPI void APIENTRY glGetTexBumpParameterfvATI (GLenum pname, GLfloat *param); #endif #endif /* GL_ATI_envmap_bumpmap */ #ifndef GL_ATI_fragment_shader #define GL_ATI_fragment_shader 1 #define GL_FRAGMENT_SHADER_ATI 0x8920 #define GL_REG_0_ATI 0x8921 #define GL_REG_1_ATI 0x8922 #define GL_REG_2_ATI 0x8923 #define GL_REG_3_ATI 0x8924 #define GL_REG_4_ATI 0x8925 #define GL_REG_5_ATI 0x8926 #define GL_REG_6_ATI 0x8927 #define GL_REG_7_ATI 0x8928 #define GL_REG_8_ATI 0x8929 #define GL_REG_9_ATI 0x892A #define GL_REG_10_ATI 0x892B #define GL_REG_11_ATI 0x892C #define GL_REG_12_ATI 0x892D #define GL_REG_13_ATI 0x892E #define GL_REG_14_ATI 0x892F #define GL_REG_15_ATI 0x8930 #define GL_REG_16_ATI 0x8931 #define GL_REG_17_ATI 0x8932 #define GL_REG_18_ATI 0x8933 #define GL_REG_19_ATI 0x8934 #define GL_REG_20_ATI 0x8935 #define GL_REG_21_ATI 0x8936 #define GL_REG_22_ATI 0x8937 #define GL_REG_23_ATI 0x8938 #define GL_REG_24_ATI 0x8939 #define GL_REG_25_ATI 0x893A #define GL_REG_26_ATI 0x893B #define GL_REG_27_ATI 0x893C #define GL_REG_28_ATI 0x893D #define GL_REG_29_ATI 0x893E #define GL_REG_30_ATI 0x893F #define GL_REG_31_ATI 0x8940 #define GL_CON_0_ATI 0x8941 #define GL_CON_1_ATI 0x8942 #define GL_CON_2_ATI 0x8943 #define GL_CON_3_ATI 0x8944 #define GL_CON_4_ATI 0x8945 #define GL_CON_5_ATI 0x8946 #define GL_CON_6_ATI 0x8947 #define GL_CON_7_ATI 0x8948 #define GL_CON_8_ATI 0x8949 #define GL_CON_9_ATI 0x894A #define GL_CON_10_ATI 0x894B #define GL_CON_11_ATI 0x894C #define GL_CON_12_ATI 0x894D #define GL_CON_13_ATI 0x894E #define GL_CON_14_ATI 0x894F #define GL_CON_15_ATI 0x8950 #define GL_CON_16_ATI 0x8951 #define GL_CON_17_ATI 0x8952 #define GL_CON_18_ATI 0x8953 #define GL_CON_19_ATI 0x8954 #define GL_CON_20_ATI 0x8955 #define GL_CON_21_ATI 0x8956 #define GL_CON_22_ATI 0x8957 #define GL_CON_23_ATI 0x8958 #define GL_CON_24_ATI 0x8959 #define GL_CON_25_ATI 0x895A #define GL_CON_26_ATI 0x895B #define GL_CON_27_ATI 0x895C #define GL_CON_28_ATI 0x895D #define GL_CON_29_ATI 0x895E #define GL_CON_30_ATI 0x895F #define GL_CON_31_ATI 0x8960 #define GL_MOV_ATI 0x8961 #define GL_ADD_ATI 0x8963 #define GL_MUL_ATI 0x8964 #define GL_SUB_ATI 0x8965 #define GL_DOT3_ATI 0x8966 #define GL_DOT4_ATI 0x8967 #define GL_MAD_ATI 0x8968 #define GL_LERP_ATI 0x8969 #define GL_CND_ATI 0x896A #define GL_CND0_ATI 0x896B #define GL_DOT2_ADD_ATI 0x896C #define GL_SECONDARY_INTERPOLATOR_ATI 0x896D #define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E #define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F #define GL_NUM_PASSES_ATI 0x8970 #define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 #define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 #define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 #define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 #define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 #define GL_SWIZZLE_STR_ATI 0x8976 #define GL_SWIZZLE_STQ_ATI 0x8977 #define GL_SWIZZLE_STR_DR_ATI 0x8978 #define GL_SWIZZLE_STQ_DQ_ATI 0x8979 #define GL_SWIZZLE_STRQ_ATI 0x897A #define GL_SWIZZLE_STRQ_DQ_ATI 0x897B #define GL_RED_BIT_ATI 0x00000001 #define GL_GREEN_BIT_ATI 0x00000002 #define GL_BLUE_BIT_ATI 0x00000004 #define GL_2X_BIT_ATI 0x00000001 #define GL_4X_BIT_ATI 0x00000002 #define GL_8X_BIT_ATI 0x00000004 #define GL_HALF_BIT_ATI 0x00000008 #define GL_QUARTER_BIT_ATI 0x00000010 #define GL_EIGHTH_BIT_ATI 0x00000020 #define GL_SATURATE_BIT_ATI 0x00000040 #define GL_COMP_BIT_ATI 0x00000002 #define GL_NEGATE_BIT_ATI 0x00000004 #define GL_BIAS_BIT_ATI 0x00000008 typedef GLuint (APIENTRYP PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); typedef void (APIENTRYP PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); typedef void (APIENTRYP PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); typedef void (APIENTRYP PFNGLBEGINFRAGMENTSHADERATIPROC) (void); typedef void (APIENTRYP PFNGLENDFRAGMENTSHADERATIPROC) (void); typedef void (APIENTRYP PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); typedef void (APIENTRYP PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); typedef void (APIENTRYP PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat *value); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLuint APIENTRY glGenFragmentShadersATI (GLuint range); GLAPI void APIENTRY glBindFragmentShaderATI (GLuint id); GLAPI void APIENTRY glDeleteFragmentShaderATI (GLuint id); GLAPI void APIENTRY glBeginFragmentShaderATI (void); GLAPI void APIENTRY glEndFragmentShaderATI (void); GLAPI void APIENTRY glPassTexCoordATI (GLuint dst, GLuint coord, GLenum swizzle); GLAPI void APIENTRY glSampleMapATI (GLuint dst, GLuint interp, GLenum swizzle); GLAPI void APIENTRY glColorFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); GLAPI void APIENTRY glColorFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); GLAPI void APIENTRY glColorFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); GLAPI void APIENTRY glAlphaFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); GLAPI void APIENTRY glAlphaFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); GLAPI void APIENTRY glAlphaFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); GLAPI void APIENTRY glSetFragmentShaderConstantATI (GLuint dst, const GLfloat *value); #endif #endif /* GL_ATI_fragment_shader */ #ifndef GL_ATI_map_object_buffer #define GL_ATI_map_object_buffer 1 typedef void *(APIENTRYP PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); typedef void (APIENTRYP PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void *APIENTRY glMapObjectBufferATI (GLuint buffer); GLAPI void APIENTRY glUnmapObjectBufferATI (GLuint buffer); #endif #endif /* GL_ATI_map_object_buffer */ #ifndef GL_ATI_meminfo #define GL_ATI_meminfo 1 #define GL_VBO_FREE_MEMORY_ATI 0x87FB #define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC #define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD #endif /* GL_ATI_meminfo */ #ifndef GL_ATI_pixel_format_float #define GL_ATI_pixel_format_float 1 #define GL_RGBA_FLOAT_MODE_ATI 0x8820 #define GL_COLOR_CLEAR_UNCLAMPED_VALUE_ATI 0x8835 #endif /* GL_ATI_pixel_format_float */ #ifndef GL_ATI_pn_triangles #define GL_ATI_pn_triangles 1 #define GL_PN_TRIANGLES_ATI 0x87F0 #define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 #define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 #define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 #define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 #define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 #define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 #define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 #define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 typedef void (APIENTRYP PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPNTrianglesiATI (GLenum pname, GLint param); GLAPI void APIENTRY glPNTrianglesfATI (GLenum pname, GLfloat param); #endif #endif /* GL_ATI_pn_triangles */ #ifndef GL_ATI_separate_stencil #define GL_ATI_separate_stencil 1 #define GL_STENCIL_BACK_FUNC_ATI 0x8800 #define GL_STENCIL_BACK_FAIL_ATI 0x8801 #define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 #define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glStencilOpSeparateATI (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); GLAPI void APIENTRY glStencilFuncSeparateATI (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); #endif #endif /* GL_ATI_separate_stencil */ #ifndef GL_ATI_text_fragment_shader #define GL_ATI_text_fragment_shader 1 #define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 #endif /* GL_ATI_text_fragment_shader */ #ifndef GL_ATI_texture_env_combine3 #define GL_ATI_texture_env_combine3 1 #define GL_MODULATE_ADD_ATI 0x8744 #define GL_MODULATE_SIGNED_ADD_ATI 0x8745 #define GL_MODULATE_SUBTRACT_ATI 0x8746 #endif /* GL_ATI_texture_env_combine3 */ #ifndef GL_ATI_texture_float #define GL_ATI_texture_float 1 #define GL_RGBA_FLOAT32_ATI 0x8814 #define GL_RGB_FLOAT32_ATI 0x8815 #define GL_ALPHA_FLOAT32_ATI 0x8816 #define GL_INTENSITY_FLOAT32_ATI 0x8817 #define GL_LUMINANCE_FLOAT32_ATI 0x8818 #define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 #define GL_RGBA_FLOAT16_ATI 0x881A #define GL_RGB_FLOAT16_ATI 0x881B #define GL_ALPHA_FLOAT16_ATI 0x881C #define GL_INTENSITY_FLOAT16_ATI 0x881D #define GL_LUMINANCE_FLOAT16_ATI 0x881E #define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F #endif /* GL_ATI_texture_float */ #ifndef GL_ATI_texture_mirror_once #define GL_ATI_texture_mirror_once 1 #define GL_MIRROR_CLAMP_ATI 0x8742 #define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 #endif /* GL_ATI_texture_mirror_once */ #ifndef GL_ATI_vertex_array_object #define GL_ATI_vertex_array_object 1 #define GL_STATIC_ATI 0x8760 #define GL_DYNAMIC_ATI 0x8761 #define GL_PRESERVE_ATI 0x8762 #define GL_DISCARD_ATI 0x8763 #define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 #define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 #define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 #define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 typedef GLuint (APIENTRYP PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const void *pointer, GLenum usage); typedef GLboolean (APIENTRYP PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); typedef void (APIENTRYP PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const void *pointer, GLenum preserve); typedef void (APIENTRYP PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); typedef void (APIENTRYP PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); typedef void (APIENTRYP PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLuint APIENTRY glNewObjectBufferATI (GLsizei size, const void *pointer, GLenum usage); GLAPI GLboolean APIENTRY glIsObjectBufferATI (GLuint buffer); GLAPI void APIENTRY glUpdateObjectBufferATI (GLuint buffer, GLuint offset, GLsizei size, const void *pointer, GLenum preserve); GLAPI void APIENTRY glGetObjectBufferfvATI (GLuint buffer, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetObjectBufferivATI (GLuint buffer, GLenum pname, GLint *params); GLAPI void APIENTRY glFreeObjectBufferATI (GLuint buffer); GLAPI void APIENTRY glArrayObjectATI (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); GLAPI void APIENTRY glGetArrayObjectfvATI (GLenum array, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetArrayObjectivATI (GLenum array, GLenum pname, GLint *params); GLAPI void APIENTRY glVariantArrayObjectATI (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); GLAPI void APIENTRY glGetVariantArrayObjectfvATI (GLuint id, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetVariantArrayObjectivATI (GLuint id, GLenum pname, GLint *params); #endif #endif /* GL_ATI_vertex_array_object */ #ifndef GL_ATI_vertex_attrib_array_object #define GL_ATI_vertex_attrib_array_object 1 typedef void (APIENTRYP PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertexAttribArrayObjectATI (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); GLAPI void APIENTRY glGetVertexAttribArrayObjectfvATI (GLuint index, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetVertexAttribArrayObjectivATI (GLuint index, GLenum pname, GLint *params); #endif #endif /* GL_ATI_vertex_attrib_array_object */ #ifndef GL_ATI_vertex_streams #define GL_ATI_vertex_streams 1 #define GL_MAX_VERTEX_STREAMS_ATI 0x876B #define GL_VERTEX_STREAM0_ATI 0x876C #define GL_VERTEX_STREAM1_ATI 0x876D #define GL_VERTEX_STREAM2_ATI 0x876E #define GL_VERTEX_STREAM3_ATI 0x876F #define GL_VERTEX_STREAM4_ATI 0x8770 #define GL_VERTEX_STREAM5_ATI 0x8771 #define GL_VERTEX_STREAM6_ATI 0x8772 #define GL_VERTEX_STREAM7_ATI 0x8773 #define GL_VERTEX_SOURCE_ATI 0x8774 typedef void (APIENTRYP PFNGLVERTEXSTREAM1SATIPROC) (GLenum stream, GLshort x); typedef void (APIENTRYP PFNGLVERTEXSTREAM1SVATIPROC) (GLenum stream, const GLshort *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM1IATIPROC) (GLenum stream, GLint x); typedef void (APIENTRYP PFNGLVERTEXSTREAM1IVATIPROC) (GLenum stream, const GLint *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM1FATIPROC) (GLenum stream, GLfloat x); typedef void (APIENTRYP PFNGLVERTEXSTREAM1FVATIPROC) (GLenum stream, const GLfloat *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM1DATIPROC) (GLenum stream, GLdouble x); typedef void (APIENTRYP PFNGLVERTEXSTREAM1DVATIPROC) (GLenum stream, const GLdouble *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); typedef void (APIENTRYP PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); typedef void (APIENTRYP PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); typedef void (APIENTRYP PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); typedef void (APIENTRYP PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); typedef void (APIENTRYP PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); typedef void (APIENTRYP PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); typedef void (APIENTRYP PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *coords); typedef void (APIENTRYP PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *coords); typedef void (APIENTRYP PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); typedef void (APIENTRYP PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *coords); typedef void (APIENTRYP PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort nx, GLshort ny, GLshort nz); typedef void (APIENTRYP PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); typedef void (APIENTRYP PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint nx, GLint ny, GLint nz); typedef void (APIENTRYP PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); typedef void (APIENTRYP PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); typedef void (APIENTRYP PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); typedef void (APIENTRYP PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); typedef void (APIENTRYP PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); typedef void (APIENTRYP PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); typedef void (APIENTRYP PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertexStream1sATI (GLenum stream, GLshort x); GLAPI void APIENTRY glVertexStream1svATI (GLenum stream, const GLshort *coords); GLAPI void APIENTRY glVertexStream1iATI (GLenum stream, GLint x); GLAPI void APIENTRY glVertexStream1ivATI (GLenum stream, const GLint *coords); GLAPI void APIENTRY glVertexStream1fATI (GLenum stream, GLfloat x); GLAPI void APIENTRY glVertexStream1fvATI (GLenum stream, const GLfloat *coords); GLAPI void APIENTRY glVertexStream1dATI (GLenum stream, GLdouble x); GLAPI void APIENTRY glVertexStream1dvATI (GLenum stream, const GLdouble *coords); GLAPI void APIENTRY glVertexStream2sATI (GLenum stream, GLshort x, GLshort y); GLAPI void APIENTRY glVertexStream2svATI (GLenum stream, const GLshort *coords); GLAPI void APIENTRY glVertexStream2iATI (GLenum stream, GLint x, GLint y); GLAPI void APIENTRY glVertexStream2ivATI (GLenum stream, const GLint *coords); GLAPI void APIENTRY glVertexStream2fATI (GLenum stream, GLfloat x, GLfloat y); GLAPI void APIENTRY glVertexStream2fvATI (GLenum stream, const GLfloat *coords); GLAPI void APIENTRY glVertexStream2dATI (GLenum stream, GLdouble x, GLdouble y); GLAPI void APIENTRY glVertexStream2dvATI (GLenum stream, const GLdouble *coords); GLAPI void APIENTRY glVertexStream3sATI (GLenum stream, GLshort x, GLshort y, GLshort z); GLAPI void APIENTRY glVertexStream3svATI (GLenum stream, const GLshort *coords); GLAPI void APIENTRY glVertexStream3iATI (GLenum stream, GLint x, GLint y, GLint z); GLAPI void APIENTRY glVertexStream3ivATI (GLenum stream, const GLint *coords); GLAPI void APIENTRY glVertexStream3fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glVertexStream3fvATI (GLenum stream, const GLfloat *coords); GLAPI void APIENTRY glVertexStream3dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glVertexStream3dvATI (GLenum stream, const GLdouble *coords); GLAPI void APIENTRY glVertexStream4sATI (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); GLAPI void APIENTRY glVertexStream4svATI (GLenum stream, const GLshort *coords); GLAPI void APIENTRY glVertexStream4iATI (GLenum stream, GLint x, GLint y, GLint z, GLint w); GLAPI void APIENTRY glVertexStream4ivATI (GLenum stream, const GLint *coords); GLAPI void APIENTRY glVertexStream4fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glVertexStream4fvATI (GLenum stream, const GLfloat *coords); GLAPI void APIENTRY glVertexStream4dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glVertexStream4dvATI (GLenum stream, const GLdouble *coords); GLAPI void APIENTRY glNormalStream3bATI (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); GLAPI void APIENTRY glNormalStream3bvATI (GLenum stream, const GLbyte *coords); GLAPI void APIENTRY glNormalStream3sATI (GLenum stream, GLshort nx, GLshort ny, GLshort nz); GLAPI void APIENTRY glNormalStream3svATI (GLenum stream, const GLshort *coords); GLAPI void APIENTRY glNormalStream3iATI (GLenum stream, GLint nx, GLint ny, GLint nz); GLAPI void APIENTRY glNormalStream3ivATI (GLenum stream, const GLint *coords); GLAPI void APIENTRY glNormalStream3fATI (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); GLAPI void APIENTRY glNormalStream3fvATI (GLenum stream, const GLfloat *coords); GLAPI void APIENTRY glNormalStream3dATI (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); GLAPI void APIENTRY glNormalStream3dvATI (GLenum stream, const GLdouble *coords); GLAPI void APIENTRY glClientActiveVertexStreamATI (GLenum stream); GLAPI void APIENTRY glVertexBlendEnviATI (GLenum pname, GLint param); GLAPI void APIENTRY glVertexBlendEnvfATI (GLenum pname, GLfloat param); #endif #endif /* GL_ATI_vertex_streams */ #ifndef GL_EXT_422_pixels #define GL_EXT_422_pixels 1 #define GL_422_EXT 0x80CC #define GL_422_REV_EXT 0x80CD #define GL_422_AVERAGE_EXT 0x80CE #define GL_422_REV_AVERAGE_EXT 0x80CF #endif /* GL_EXT_422_pixels */ #ifndef GL_EXT_EGL_image_storage #define GL_EXT_EGL_image_storage 1 typedef void *GLeglImageOES; typedef void (APIENTRYP PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC) (GLenum target, GLeglImageOES image, const GLint* attrib_list); typedef void (APIENTRYP PFNGLEGLIMAGETARGETTEXTURESTORAGEEXTPROC) (GLuint texture, GLeglImageOES image, const GLint* attrib_list); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glEGLImageTargetTexStorageEXT (GLenum target, GLeglImageOES image, const GLint* attrib_list); GLAPI void APIENTRY glEGLImageTargetTextureStorageEXT (GLuint texture, GLeglImageOES image, const GLint* attrib_list); #endif #endif /* GL_EXT_EGL_image_storage */ #ifndef GL_EXT_EGL_sync #define GL_EXT_EGL_sync 1 #endif /* GL_EXT_EGL_sync */ #ifndef GL_EXT_abgr #define GL_EXT_abgr 1 #define GL_ABGR_EXT 0x8000 #endif /* GL_EXT_abgr */ #ifndef GL_EXT_bgra #define GL_EXT_bgra 1 #define GL_BGR_EXT 0x80E0 #define GL_BGRA_EXT 0x80E1 #endif /* GL_EXT_bgra */ #ifndef GL_EXT_bindable_uniform #define GL_EXT_bindable_uniform 1 #define GL_MAX_VERTEX_BINDABLE_UNIFORMS_EXT 0x8DE2 #define GL_MAX_FRAGMENT_BINDABLE_UNIFORMS_EXT 0x8DE3 #define GL_MAX_GEOMETRY_BINDABLE_UNIFORMS_EXT 0x8DE4 #define GL_MAX_BINDABLE_UNIFORM_SIZE_EXT 0x8DED #define GL_UNIFORM_BUFFER_EXT 0x8DEE #define GL_UNIFORM_BUFFER_BINDING_EXT 0x8DEF typedef void (APIENTRYP PFNGLUNIFORMBUFFEREXTPROC) (GLuint program, GLint location, GLuint buffer); typedef GLint (APIENTRYP PFNGLGETUNIFORMBUFFERSIZEEXTPROC) (GLuint program, GLint location); typedef GLintptr (APIENTRYP PFNGLGETUNIFORMOFFSETEXTPROC) (GLuint program, GLint location); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glUniformBufferEXT (GLuint program, GLint location, GLuint buffer); GLAPI GLint APIENTRY glGetUniformBufferSizeEXT (GLuint program, GLint location); GLAPI GLintptr APIENTRY glGetUniformOffsetEXT (GLuint program, GLint location); #endif #endif /* GL_EXT_bindable_uniform */ #ifndef GL_EXT_blend_color #define GL_EXT_blend_color 1 #define GL_CONSTANT_COLOR_EXT 0x8001 #define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 #define GL_CONSTANT_ALPHA_EXT 0x8003 #define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 #define GL_BLEND_COLOR_EXT 0x8005 typedef void (APIENTRYP PFNGLBLENDCOLOREXTPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendColorEXT (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); #endif #endif /* GL_EXT_blend_color */ #ifndef GL_EXT_blend_equation_separate #define GL_EXT_blend_equation_separate 1 #define GL_BLEND_EQUATION_RGB_EXT 0x8009 #define GL_BLEND_EQUATION_ALPHA_EXT 0x883D typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendEquationSeparateEXT (GLenum modeRGB, GLenum modeAlpha); #endif #endif /* GL_EXT_blend_equation_separate */ #ifndef GL_EXT_blend_func_separate #define GL_EXT_blend_func_separate 1 #define GL_BLEND_DST_RGB_EXT 0x80C8 #define GL_BLEND_SRC_RGB_EXT 0x80C9 #define GL_BLEND_DST_ALPHA_EXT 0x80CA #define GL_BLEND_SRC_ALPHA_EXT 0x80CB typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendFuncSeparateEXT (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); #endif #endif /* GL_EXT_blend_func_separate */ #ifndef GL_EXT_blend_logic_op #define GL_EXT_blend_logic_op 1 #endif /* GL_EXT_blend_logic_op */ #ifndef GL_EXT_blend_minmax #define GL_EXT_blend_minmax 1 #define GL_MIN_EXT 0x8007 #define GL_MAX_EXT 0x8008 #define GL_FUNC_ADD_EXT 0x8006 #define GL_BLEND_EQUATION_EXT 0x8009 typedef void (APIENTRYP PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendEquationEXT (GLenum mode); #endif #endif /* GL_EXT_blend_minmax */ #ifndef GL_EXT_blend_subtract #define GL_EXT_blend_subtract 1 #define GL_FUNC_SUBTRACT_EXT 0x800A #define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B #endif /* GL_EXT_blend_subtract */ #ifndef GL_EXT_clip_volume_hint #define GL_EXT_clip_volume_hint 1 #define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 #endif /* GL_EXT_clip_volume_hint */ #ifndef GL_EXT_cmyka #define GL_EXT_cmyka 1 #define GL_CMYK_EXT 0x800C #define GL_CMYKA_EXT 0x800D #define GL_PACK_CMYK_HINT_EXT 0x800E #define GL_UNPACK_CMYK_HINT_EXT 0x800F #endif /* GL_EXT_cmyka */ #ifndef GL_EXT_color_subtable #define GL_EXT_color_subtable 1 typedef void (APIENTRYP PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glColorSubTableEXT (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); GLAPI void APIENTRY glCopyColorSubTableEXT (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); #endif #endif /* GL_EXT_color_subtable */ #ifndef GL_EXT_compiled_vertex_array #define GL_EXT_compiled_vertex_array 1 #define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 #define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 typedef void (APIENTRYP PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); typedef void (APIENTRYP PFNGLUNLOCKARRAYSEXTPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glLockArraysEXT (GLint first, GLsizei count); GLAPI void APIENTRY glUnlockArraysEXT (void); #endif #endif /* GL_EXT_compiled_vertex_array */ #ifndef GL_EXT_convolution #define GL_EXT_convolution 1 #define GL_CONVOLUTION_1D_EXT 0x8010 #define GL_CONVOLUTION_2D_EXT 0x8011 #define GL_SEPARABLE_2D_EXT 0x8012 #define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 #define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 #define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 #define GL_REDUCE_EXT 0x8016 #define GL_CONVOLUTION_FORMAT_EXT 0x8017 #define GL_CONVOLUTION_WIDTH_EXT 0x8018 #define GL_CONVOLUTION_HEIGHT_EXT 0x8019 #define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A #define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B #define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C #define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D #define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E #define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F #define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 #define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 #define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 #define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, void *image); typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); GLAPI void APIENTRY glConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); GLAPI void APIENTRY glConvolutionParameterfEXT (GLenum target, GLenum pname, GLfloat params); GLAPI void APIENTRY glConvolutionParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glConvolutionParameteriEXT (GLenum target, GLenum pname, GLint params); GLAPI void APIENTRY glConvolutionParameterivEXT (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glCopyConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); GLAPI void APIENTRY glCopyConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glGetConvolutionFilterEXT (GLenum target, GLenum format, GLenum type, void *image); GLAPI void APIENTRY glGetConvolutionParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetConvolutionParameterivEXT (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetSeparableFilterEXT (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); GLAPI void APIENTRY glSeparableFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); #endif #endif /* GL_EXT_convolution */ #ifndef GL_EXT_coordinate_frame #define GL_EXT_coordinate_frame 1 #define GL_TANGENT_ARRAY_EXT 0x8439 #define GL_BINORMAL_ARRAY_EXT 0x843A #define GL_CURRENT_TANGENT_EXT 0x843B #define GL_CURRENT_BINORMAL_EXT 0x843C #define GL_TANGENT_ARRAY_TYPE_EXT 0x843E #define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F #define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 #define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 #define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 #define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 #define GL_MAP1_TANGENT_EXT 0x8444 #define GL_MAP2_TANGENT_EXT 0x8445 #define GL_MAP1_BINORMAL_EXT 0x8446 #define GL_MAP2_BINORMAL_EXT 0x8447 typedef void (APIENTRYP PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); typedef void (APIENTRYP PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); typedef void (APIENTRYP PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); typedef void (APIENTRYP PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); typedef void (APIENTRYP PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); typedef void (APIENTRYP PFNGLTANGENT3IVEXTPROC) (const GLint *v); typedef void (APIENTRYP PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); typedef void (APIENTRYP PFNGLTANGENT3SVEXTPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); typedef void (APIENTRYP PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); typedef void (APIENTRYP PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); typedef void (APIENTRYP PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); typedef void (APIENTRYP PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); typedef void (APIENTRYP PFNGLBINORMAL3IVEXTPROC) (const GLint *v); typedef void (APIENTRYP PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); typedef void (APIENTRYP PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTangent3bEXT (GLbyte tx, GLbyte ty, GLbyte tz); GLAPI void APIENTRY glTangent3bvEXT (const GLbyte *v); GLAPI void APIENTRY glTangent3dEXT (GLdouble tx, GLdouble ty, GLdouble tz); GLAPI void APIENTRY glTangent3dvEXT (const GLdouble *v); GLAPI void APIENTRY glTangent3fEXT (GLfloat tx, GLfloat ty, GLfloat tz); GLAPI void APIENTRY glTangent3fvEXT (const GLfloat *v); GLAPI void APIENTRY glTangent3iEXT (GLint tx, GLint ty, GLint tz); GLAPI void APIENTRY glTangent3ivEXT (const GLint *v); GLAPI void APIENTRY glTangent3sEXT (GLshort tx, GLshort ty, GLshort tz); GLAPI void APIENTRY glTangent3svEXT (const GLshort *v); GLAPI void APIENTRY glBinormal3bEXT (GLbyte bx, GLbyte by, GLbyte bz); GLAPI void APIENTRY glBinormal3bvEXT (const GLbyte *v); GLAPI void APIENTRY glBinormal3dEXT (GLdouble bx, GLdouble by, GLdouble bz); GLAPI void APIENTRY glBinormal3dvEXT (const GLdouble *v); GLAPI void APIENTRY glBinormal3fEXT (GLfloat bx, GLfloat by, GLfloat bz); GLAPI void APIENTRY glBinormal3fvEXT (const GLfloat *v); GLAPI void APIENTRY glBinormal3iEXT (GLint bx, GLint by, GLint bz); GLAPI void APIENTRY glBinormal3ivEXT (const GLint *v); GLAPI void APIENTRY glBinormal3sEXT (GLshort bx, GLshort by, GLshort bz); GLAPI void APIENTRY glBinormal3svEXT (const GLshort *v); GLAPI void APIENTRY glTangentPointerEXT (GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glBinormalPointerEXT (GLenum type, GLsizei stride, const void *pointer); #endif #endif /* GL_EXT_coordinate_frame */ #ifndef GL_EXT_copy_texture #define GL_EXT_copy_texture 1 typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCopyTexImage1DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); GLAPI void APIENTRY glCopyTexImage2DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); GLAPI void APIENTRY glCopyTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); GLAPI void APIENTRY glCopyTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glCopyTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); #endif #endif /* GL_EXT_copy_texture */ #ifndef GL_EXT_cull_vertex #define GL_EXT_cull_vertex 1 #define GL_CULL_VERTEX_EXT 0x81AA #define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB #define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC typedef void (APIENTRYP PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); typedef void (APIENTRYP PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCullParameterdvEXT (GLenum pname, GLdouble *params); GLAPI void APIENTRY glCullParameterfvEXT (GLenum pname, GLfloat *params); #endif #endif /* GL_EXT_cull_vertex */ #ifndef GL_EXT_debug_label #define GL_EXT_debug_label 1 #define GL_PROGRAM_PIPELINE_OBJECT_EXT 0x8A4F #define GL_PROGRAM_OBJECT_EXT 0x8B40 #define GL_SHADER_OBJECT_EXT 0x8B48 #define GL_BUFFER_OBJECT_EXT 0x9151 #define GL_QUERY_OBJECT_EXT 0x9153 #define GL_VERTEX_ARRAY_OBJECT_EXT 0x9154 typedef void (APIENTRYP PFNGLLABELOBJECTEXTPROC) (GLenum type, GLuint object, GLsizei length, const GLchar *label); typedef void (APIENTRYP PFNGLGETOBJECTLABELEXTPROC) (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glLabelObjectEXT (GLenum type, GLuint object, GLsizei length, const GLchar *label); GLAPI void APIENTRY glGetObjectLabelEXT (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label); #endif #endif /* GL_EXT_debug_label */ #ifndef GL_EXT_debug_marker #define GL_EXT_debug_marker 1 typedef void (APIENTRYP PFNGLINSERTEVENTMARKEREXTPROC) (GLsizei length, const GLchar *marker); typedef void (APIENTRYP PFNGLPUSHGROUPMARKEREXTPROC) (GLsizei length, const GLchar *marker); typedef void (APIENTRYP PFNGLPOPGROUPMARKEREXTPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glInsertEventMarkerEXT (GLsizei length, const GLchar *marker); GLAPI void APIENTRY glPushGroupMarkerEXT (GLsizei length, const GLchar *marker); GLAPI void APIENTRY glPopGroupMarkerEXT (void); #endif #endif /* GL_EXT_debug_marker */ #ifndef GL_EXT_depth_bounds_test #define GL_EXT_depth_bounds_test 1 #define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 #define GL_DEPTH_BOUNDS_EXT 0x8891 typedef void (APIENTRYP PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDepthBoundsEXT (GLclampd zmin, GLclampd zmax); #endif #endif /* GL_EXT_depth_bounds_test */ #ifndef GL_EXT_direct_state_access #define GL_EXT_direct_state_access 1 #define GL_PROGRAM_MATRIX_EXT 0x8E2D #define GL_TRANSPOSE_PROGRAM_MATRIX_EXT 0x8E2E #define GL_PROGRAM_MATRIX_STACK_DEPTH_EXT 0x8E2F typedef void (APIENTRYP PFNGLMATRIXLOADFEXTPROC) (GLenum mode, const GLfloat *m); typedef void (APIENTRYP PFNGLMATRIXLOADDEXTPROC) (GLenum mode, const GLdouble *m); typedef void (APIENTRYP PFNGLMATRIXMULTFEXTPROC) (GLenum mode, const GLfloat *m); typedef void (APIENTRYP PFNGLMATRIXMULTDEXTPROC) (GLenum mode, const GLdouble *m); typedef void (APIENTRYP PFNGLMATRIXLOADIDENTITYEXTPROC) (GLenum mode); typedef void (APIENTRYP PFNGLMATRIXROTATEFEXTPROC) (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLMATRIXROTATEDEXTPROC) (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLMATRIXSCALEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLMATRIXSCALEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLMATRIXTRANSLATEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLMATRIXTRANSLATEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLMATRIXFRUSTUMEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); typedef void (APIENTRYP PFNGLMATRIXORTHOEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); typedef void (APIENTRYP PFNGLMATRIXPOPEXTPROC) (GLenum mode); typedef void (APIENTRYP PFNGLMATRIXPUSHEXTPROC) (GLenum mode); typedef void (APIENTRYP PFNGLCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); typedef void (APIENTRYP PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLGETTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLBINDMULTITEXTUREEXTPROC) (GLenum texunit, GLenum target, GLuint texture); typedef void (APIENTRYP PFNGLMULTITEXCOORDPOINTEREXTPROC) (GLenum texunit, GLint size, GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLMULTITEXENVFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLMULTITEXENVIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLMULTITEXGENDEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); typedef void (APIENTRYP PFNGLMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); typedef void (APIENTRYP PFNGLMULTITEXGENFEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLMULTITEXGENIEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLGETMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); typedef void (APIENTRYP PFNGLGETMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLGETMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLENABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); typedef void (APIENTRYP PFNGLGETFLOATINDEXEDVEXTPROC) (GLenum target, GLuint index, GLfloat *data); typedef void (APIENTRYP PFNGLGETDOUBLEINDEXEDVEXTPROC) (GLenum target, GLuint index, GLdouble *data); typedef void (APIENTRYP PFNGLGETPOINTERINDEXEDVEXTPROC) (GLenum target, GLuint index, void **data); typedef void (APIENTRYP PFNGLENABLEINDEXEDEXTPROC) (GLenum target, GLuint index); typedef void (APIENTRYP PFNGLDISABLEINDEXEDEXTPROC) (GLenum target, GLuint index); typedef GLboolean (APIENTRYP PFNGLISENABLEDINDEXEDEXTPROC) (GLenum target, GLuint index); typedef void (APIENTRYP PFNGLGETINTEGERINDEXEDVEXTPROC) (GLenum target, GLuint index, GLint *data); typedef void (APIENTRYP PFNGLGETBOOLEANINDEXEDVEXTPROC) (GLenum target, GLuint index, GLboolean *data); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint lod, void *img); typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); typedef void (APIENTRYP PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint lod, void *img); typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); typedef void (APIENTRYP PFNGLNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); typedef void (APIENTRYP PFNGLNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFEREXTPROC) (GLuint buffer, GLenum access); typedef GLboolean (APIENTRYP PFNGLUNMAPNAMEDBUFFEREXTPROC) (GLuint buffer); typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC) (GLuint buffer, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVEXTPROC) (GLuint buffer, GLenum pname, void **params); typedef void (APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FEXTPROC) (GLuint program, GLint location, GLfloat v0); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IEXTPROC) (GLuint program, GLint location, GLint v0); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); typedef void (APIENTRYP PFNGLTEXTUREBUFFEREXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); typedef void (APIENTRYP PFNGLMULTITEXBUFFEREXTPROC) (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLuint *params); typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIEXTPROC) (GLuint program, GLint location, GLuint v0); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC) (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLint *params); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLuint *params); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLint *params); typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint *params); typedef void (APIENTRYP PFNGLENABLECLIENTSTATEIEXTPROC) (GLenum array, GLuint index); typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEIEXTPROC) (GLenum array, GLuint index); typedef void (APIENTRYP PFNGLGETFLOATI_VEXTPROC) (GLenum pname, GLuint index, GLfloat *params); typedef void (APIENTRYP PFNGLGETDOUBLEI_VEXTPROC) (GLenum pname, GLuint index, GLdouble *params); typedef void (APIENTRYP PFNGLGETPOINTERI_VEXTPROC) (GLenum pname, GLuint index, void **params); typedef void (APIENTRYP PFNGLNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum format, GLsizei len, const void *string); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLdouble *params); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLfloat *params); typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble *params); typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat *params); typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMIVEXTPROC) (GLuint program, GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum pname, void *string); typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC) (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC) (GLuint renderbuffer, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC) (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); typedef GLenum (APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC) (GLuint framebuffer, GLenum target); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC) (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGENERATETEXTUREMIPMAPEXTPROC) (GLuint texture, GLenum target); typedef void (APIENTRYP PFNGLGENERATEMULTITEXMIPMAPEXTPROC) (GLenum texunit, GLenum target); typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC) (GLuint framebuffer, GLsizei n, const GLenum *bufs); typedef void (APIENTRYP PFNGLFRAMEBUFFERREADBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLNAMEDCOPYBUFFERSUBDATAEXTPROC) (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); typedef void (APIENTRYP PFNGLTEXTURERENDERBUFFEREXTPROC) (GLuint texture, GLenum target, GLuint renderbuffer); typedef void (APIENTRYP PFNGLMULTITEXRENDERBUFFEREXTPROC) (GLenum texunit, GLenum target, GLuint renderbuffer); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLVERTEXARRAYCOLOROFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLVERTEXARRAYEDGEFLAGOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLVERTEXARRAYINDEXOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLVERTEXARRAYNORMALOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLVERTEXARRAYTEXCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLVERTEXARRAYMULTITEXCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum texunit, GLint size, GLenum type, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLVERTEXARRAYFOGCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLVERTEXARRAYSECONDARYCOLOROFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBIOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYEXTPROC) (GLuint vaobj, GLenum array); typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYEXTPROC) (GLuint vaobj, GLenum array); typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYATTRIBEXTPROC) (GLuint vaobj, GLuint index); typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYATTRIBEXTPROC) (GLuint vaobj, GLuint index); typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERVEXTPROC) (GLuint vaobj, GLenum pname, GLint *param); typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERVEXTPROC) (GLuint vaobj, GLenum pname, void **param); typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param); typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, void **param); typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); typedef void (APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEEXTPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLenum internalformat, GLsizeiptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERPARAMETERIEXTPROC) (GLuint framebuffer, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DEXTPROC) (GLuint program, GLint location, GLdouble x); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); typedef void (APIENTRYP PFNGLTEXTUREBUFFERRANGEEXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); typedef void (APIENTRYP PFNGLTEXTURESTORAGE1DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DMULTISAMPLEEXTPROC) (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DMULTISAMPLEEXTPROC) (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); typedef void (APIENTRYP PFNGLVERTEXARRAYBINDVERTEXBUFFEREXTPROC) (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBIFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBBINDINGEXTPROC) (GLuint vaobj, GLuint attribindex, GLuint bindingindex); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBINDINGDIVISOREXTPROC) (GLuint vaobj, GLuint bindingindex, GLuint divisor); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); typedef void (APIENTRYP PFNGLTEXTUREPAGECOMMITMENTEXTPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBDIVISOREXTPROC) (GLuint vaobj, GLuint index, GLuint divisor); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMatrixLoadfEXT (GLenum mode, const GLfloat *m); GLAPI void APIENTRY glMatrixLoaddEXT (GLenum mode, const GLdouble *m); GLAPI void APIENTRY glMatrixMultfEXT (GLenum mode, const GLfloat *m); GLAPI void APIENTRY glMatrixMultdEXT (GLenum mode, const GLdouble *m); GLAPI void APIENTRY glMatrixLoadIdentityEXT (GLenum mode); GLAPI void APIENTRY glMatrixRotatefEXT (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glMatrixRotatedEXT (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glMatrixScalefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glMatrixScaledEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glMatrixTranslatefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glMatrixTranslatedEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glMatrixFrustumEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); GLAPI void APIENTRY glMatrixOrthoEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); GLAPI void APIENTRY glMatrixPopEXT (GLenum mode); GLAPI void APIENTRY glMatrixPushEXT (GLenum mode); GLAPI void APIENTRY glClientAttribDefaultEXT (GLbitfield mask); GLAPI void APIENTRY glPushClientAttribDefaultEXT (GLbitfield mask); GLAPI void APIENTRY glTextureParameterfEXT (GLuint texture, GLenum target, GLenum pname, GLfloat param); GLAPI void APIENTRY glTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glTextureParameteriEXT (GLuint texture, GLenum target, GLenum pname, GLint param); GLAPI void APIENTRY glTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glCopyTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); GLAPI void APIENTRY glCopyTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); GLAPI void APIENTRY glCopyTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); GLAPI void APIENTRY glCopyTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glGetTextureImageEXT (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); GLAPI void APIENTRY glGetTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetTextureLevelParameterfvEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetTextureLevelParameterivEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); GLAPI void APIENTRY glTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glCopyTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glBindMultiTextureEXT (GLenum texunit, GLenum target, GLuint texture); GLAPI void APIENTRY glMultiTexCoordPointerEXT (GLenum texunit, GLint size, GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glMultiTexEnvfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); GLAPI void APIENTRY glMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glMultiTexEnviEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); GLAPI void APIENTRY glMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glMultiTexGendEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); GLAPI void APIENTRY glMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); GLAPI void APIENTRY glMultiTexGenfEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); GLAPI void APIENTRY glMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glMultiTexGeniEXT (GLenum texunit, GLenum coord, GLenum pname, GLint param); GLAPI void APIENTRY glMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); GLAPI void APIENTRY glGetMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); GLAPI void APIENTRY glGetMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, GLint *params); GLAPI void APIENTRY glMultiTexParameteriEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); GLAPI void APIENTRY glMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glMultiTexParameterfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); GLAPI void APIENTRY glMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glCopyMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); GLAPI void APIENTRY glCopyMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); GLAPI void APIENTRY glCopyMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); GLAPI void APIENTRY glCopyMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glGetMultiTexImageEXT (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); GLAPI void APIENTRY glGetMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetMultiTexLevelParameterfvEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetMultiTexLevelParameterivEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); GLAPI void APIENTRY glMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glCopyMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glEnableClientStateIndexedEXT (GLenum array, GLuint index); GLAPI void APIENTRY glDisableClientStateIndexedEXT (GLenum array, GLuint index); GLAPI void APIENTRY glGetFloatIndexedvEXT (GLenum target, GLuint index, GLfloat *data); GLAPI void APIENTRY glGetDoubleIndexedvEXT (GLenum target, GLuint index, GLdouble *data); GLAPI void APIENTRY glGetPointerIndexedvEXT (GLenum target, GLuint index, void **data); GLAPI void APIENTRY glEnableIndexedEXT (GLenum target, GLuint index); GLAPI void APIENTRY glDisableIndexedEXT (GLenum target, GLuint index); GLAPI GLboolean APIENTRY glIsEnabledIndexedEXT (GLenum target, GLuint index); GLAPI void APIENTRY glGetIntegerIndexedvEXT (GLenum target, GLuint index, GLint *data); GLAPI void APIENTRY glGetBooleanIndexedvEXT (GLenum target, GLuint index, GLboolean *data); GLAPI void APIENTRY glCompressedTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glCompressedTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glCompressedTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glCompressedTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glCompressedTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glCompressedTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glGetCompressedTextureImageEXT (GLuint texture, GLenum target, GLint lod, void *img); GLAPI void APIENTRY glCompressedMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glCompressedMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glCompressedMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glCompressedMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glCompressedMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glCompressedMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); GLAPI void APIENTRY glGetCompressedMultiTexImageEXT (GLenum texunit, GLenum target, GLint lod, void *img); GLAPI void APIENTRY glMatrixLoadTransposefEXT (GLenum mode, const GLfloat *m); GLAPI void APIENTRY glMatrixLoadTransposedEXT (GLenum mode, const GLdouble *m); GLAPI void APIENTRY glMatrixMultTransposefEXT (GLenum mode, const GLfloat *m); GLAPI void APIENTRY glMatrixMultTransposedEXT (GLenum mode, const GLdouble *m); GLAPI void APIENTRY glNamedBufferDataEXT (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); GLAPI void APIENTRY glNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); GLAPI void *APIENTRY glMapNamedBufferEXT (GLuint buffer, GLenum access); GLAPI GLboolean APIENTRY glUnmapNamedBufferEXT (GLuint buffer); GLAPI void APIENTRY glGetNamedBufferParameterivEXT (GLuint buffer, GLenum pname, GLint *params); GLAPI void APIENTRY glGetNamedBufferPointervEXT (GLuint buffer, GLenum pname, void **params); GLAPI void APIENTRY glGetNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); GLAPI void APIENTRY glProgramUniform1fEXT (GLuint program, GLint location, GLfloat v0); GLAPI void APIENTRY glProgramUniform2fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1); GLAPI void APIENTRY glProgramUniform3fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); GLAPI void APIENTRY glProgramUniform4fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); GLAPI void APIENTRY glProgramUniform1iEXT (GLuint program, GLint location, GLint v0); GLAPI void APIENTRY glProgramUniform2iEXT (GLuint program, GLint location, GLint v0, GLint v1); GLAPI void APIENTRY glProgramUniform3iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); GLAPI void APIENTRY glProgramUniform4iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); GLAPI void APIENTRY glProgramUniform1fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glProgramUniform2fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glProgramUniform3fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glProgramUniform4fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); GLAPI void APIENTRY glProgramUniform1ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glProgramUniform2ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glProgramUniform3ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glProgramUniform4ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); GLAPI void APIENTRY glProgramUniformMatrix2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix2x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix3x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix2x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix4x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix3x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glProgramUniformMatrix4x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); GLAPI void APIENTRY glTextureBufferEXT (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); GLAPI void APIENTRY glMultiTexBufferEXT (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); GLAPI void APIENTRY glTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, const GLuint *params); GLAPI void APIENTRY glGetTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, GLuint *params); GLAPI void APIENTRY glMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); GLAPI void APIENTRY glGetMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, GLuint *params); GLAPI void APIENTRY glProgramUniform1uiEXT (GLuint program, GLint location, GLuint v0); GLAPI void APIENTRY glProgramUniform2uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1); GLAPI void APIENTRY glProgramUniform3uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); GLAPI void APIENTRY glProgramUniform4uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); GLAPI void APIENTRY glProgramUniform1uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glProgramUniform2uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glProgramUniform3uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glProgramUniform4uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glNamedProgramLocalParameters4fvEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); GLAPI void APIENTRY glNamedProgramLocalParameterI4iEXT (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); GLAPI void APIENTRY glNamedProgramLocalParameterI4ivEXT (GLuint program, GLenum target, GLuint index, const GLint *params); GLAPI void APIENTRY glNamedProgramLocalParametersI4ivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); GLAPI void APIENTRY glNamedProgramLocalParameterI4uiEXT (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); GLAPI void APIENTRY glNamedProgramLocalParameterI4uivEXT (GLuint program, GLenum target, GLuint index, const GLuint *params); GLAPI void APIENTRY glNamedProgramLocalParametersI4uivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); GLAPI void APIENTRY glGetNamedProgramLocalParameterIivEXT (GLuint program, GLenum target, GLuint index, GLint *params); GLAPI void APIENTRY glGetNamedProgramLocalParameterIuivEXT (GLuint program, GLenum target, GLuint index, GLuint *params); GLAPI void APIENTRY glEnableClientStateiEXT (GLenum array, GLuint index); GLAPI void APIENTRY glDisableClientStateiEXT (GLenum array, GLuint index); GLAPI void APIENTRY glGetFloati_vEXT (GLenum pname, GLuint index, GLfloat *params); GLAPI void APIENTRY glGetDoublei_vEXT (GLenum pname, GLuint index, GLdouble *params); GLAPI void APIENTRY glGetPointeri_vEXT (GLenum pname, GLuint index, void **params); GLAPI void APIENTRY glNamedProgramStringEXT (GLuint program, GLenum target, GLenum format, GLsizei len, const void *string); GLAPI void APIENTRY glNamedProgramLocalParameter4dEXT (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glNamedProgramLocalParameter4dvEXT (GLuint program, GLenum target, GLuint index, const GLdouble *params); GLAPI void APIENTRY glNamedProgramLocalParameter4fEXT (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glNamedProgramLocalParameter4fvEXT (GLuint program, GLenum target, GLuint index, const GLfloat *params); GLAPI void APIENTRY glGetNamedProgramLocalParameterdvEXT (GLuint program, GLenum target, GLuint index, GLdouble *params); GLAPI void APIENTRY glGetNamedProgramLocalParameterfvEXT (GLuint program, GLenum target, GLuint index, GLfloat *params); GLAPI void APIENTRY glGetNamedProgramivEXT (GLuint program, GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetNamedProgramStringEXT (GLuint program, GLenum target, GLenum pname, void *string); GLAPI void APIENTRY glNamedRenderbufferStorageEXT (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glGetNamedRenderbufferParameterivEXT (GLuint renderbuffer, GLenum pname, GLint *params); GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleEXT (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleCoverageEXT (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); GLAPI GLenum APIENTRY glCheckNamedFramebufferStatusEXT (GLuint framebuffer, GLenum target); GLAPI void APIENTRY glNamedFramebufferTexture1DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); GLAPI void APIENTRY glNamedFramebufferTexture2DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); GLAPI void APIENTRY glNamedFramebufferTexture3DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); GLAPI void APIENTRY glNamedFramebufferRenderbufferEXT (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameterivEXT (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); GLAPI void APIENTRY glGenerateTextureMipmapEXT (GLuint texture, GLenum target); GLAPI void APIENTRY glGenerateMultiTexMipmapEXT (GLenum texunit, GLenum target); GLAPI void APIENTRY glFramebufferDrawBufferEXT (GLuint framebuffer, GLenum mode); GLAPI void APIENTRY glFramebufferDrawBuffersEXT (GLuint framebuffer, GLsizei n, const GLenum *bufs); GLAPI void APIENTRY glFramebufferReadBufferEXT (GLuint framebuffer, GLenum mode); GLAPI void APIENTRY glGetFramebufferParameterivEXT (GLuint framebuffer, GLenum pname, GLint *params); GLAPI void APIENTRY glNamedCopyBufferSubDataEXT (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); GLAPI void APIENTRY glNamedFramebufferTextureEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); GLAPI void APIENTRY glNamedFramebufferTextureLayerEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); GLAPI void APIENTRY glNamedFramebufferTextureFaceEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); GLAPI void APIENTRY glTextureRenderbufferEXT (GLuint texture, GLenum target, GLuint renderbuffer); GLAPI void APIENTRY glMultiTexRenderbufferEXT (GLenum texunit, GLenum target, GLuint renderbuffer); GLAPI void APIENTRY glVertexArrayVertexOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glVertexArrayColorOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glVertexArrayEdgeFlagOffsetEXT (GLuint vaobj, GLuint buffer, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glVertexArrayIndexOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glVertexArrayNormalOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glVertexArrayTexCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glVertexArrayMultiTexCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLenum texunit, GLint size, GLenum type, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glVertexArrayFogCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glVertexArraySecondaryColorOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glVertexArrayVertexAttribOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glVertexArrayVertexAttribIOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glEnableVertexArrayEXT (GLuint vaobj, GLenum array); GLAPI void APIENTRY glDisableVertexArrayEXT (GLuint vaobj, GLenum array); GLAPI void APIENTRY glEnableVertexArrayAttribEXT (GLuint vaobj, GLuint index); GLAPI void APIENTRY glDisableVertexArrayAttribEXT (GLuint vaobj, GLuint index); GLAPI void APIENTRY glGetVertexArrayIntegervEXT (GLuint vaobj, GLenum pname, GLint *param); GLAPI void APIENTRY glGetVertexArrayPointervEXT (GLuint vaobj, GLenum pname, void **param); GLAPI void APIENTRY glGetVertexArrayIntegeri_vEXT (GLuint vaobj, GLuint index, GLenum pname, GLint *param); GLAPI void APIENTRY glGetVertexArrayPointeri_vEXT (GLuint vaobj, GLuint index, GLenum pname, void **param); GLAPI void *APIENTRY glMapNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); GLAPI void APIENTRY glFlushMappedNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length); GLAPI void APIENTRY glNamedBufferStorageEXT (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); GLAPI void APIENTRY glClearNamedBufferDataEXT (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); GLAPI void APIENTRY glClearNamedBufferSubDataEXT (GLuint buffer, GLenum internalformat, GLsizeiptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); GLAPI void APIENTRY glNamedFramebufferParameteriEXT (GLuint framebuffer, GLenum pname, GLint param); GLAPI void APIENTRY glGetNamedFramebufferParameterivEXT (GLuint framebuffer, GLenum pname, GLint *params); GLAPI void APIENTRY glProgramUniform1dEXT (GLuint program, GLint location, GLdouble x); GLAPI void APIENTRY glProgramUniform2dEXT (GLuint program, GLint location, GLdouble x, GLdouble y); GLAPI void APIENTRY glProgramUniform3dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glProgramUniform4dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glProgramUniform1dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glProgramUniform2dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glProgramUniform3dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glProgramUniform4dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix2x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix2x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix3x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix3x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix4x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glProgramUniformMatrix4x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); GLAPI void APIENTRY glTextureBufferRangeEXT (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); GLAPI void APIENTRY glTextureStorage1DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); GLAPI void APIENTRY glTextureStorage2DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glTextureStorage3DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); GLAPI void APIENTRY glTextureStorage2DMultisampleEXT (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); GLAPI void APIENTRY glTextureStorage3DMultisampleEXT (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); GLAPI void APIENTRY glVertexArrayBindVertexBufferEXT (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); GLAPI void APIENTRY glVertexArrayVertexAttribFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); GLAPI void APIENTRY glVertexArrayVertexAttribIFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); GLAPI void APIENTRY glVertexArrayVertexAttribLFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); GLAPI void APIENTRY glVertexArrayVertexAttribBindingEXT (GLuint vaobj, GLuint attribindex, GLuint bindingindex); GLAPI void APIENTRY glVertexArrayVertexBindingDivisorEXT (GLuint vaobj, GLuint bindingindex, GLuint divisor); GLAPI void APIENTRY glVertexArrayVertexAttribLOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); GLAPI void APIENTRY glTexturePageCommitmentEXT (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); GLAPI void APIENTRY glVertexArrayVertexAttribDivisorEXT (GLuint vaobj, GLuint index, GLuint divisor); #endif #endif /* GL_EXT_direct_state_access */ #ifndef GL_EXT_draw_buffers2 #define GL_EXT_draw_buffers2 1 typedef void (APIENTRYP PFNGLCOLORMASKINDEXEDEXTPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glColorMaskIndexedEXT (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); #endif #endif /* GL_EXT_draw_buffers2 */ #ifndef GL_EXT_draw_instanced #define GL_EXT_draw_instanced 1 typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawArraysInstancedEXT (GLenum mode, GLint start, GLsizei count, GLsizei primcount); GLAPI void APIENTRY glDrawElementsInstancedEXT (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); #endif #endif /* GL_EXT_draw_instanced */ #ifndef GL_EXT_draw_range_elements #define GL_EXT_draw_range_elements 1 #define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 #define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawRangeElementsEXT (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); #endif #endif /* GL_EXT_draw_range_elements */ #ifndef GL_EXT_external_buffer #define GL_EXT_external_buffer 1 typedef void *GLeglClientBufferEXT; typedef void (APIENTRYP PFNGLBUFFERSTORAGEEXTERNALEXTPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEEXTERNALEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBufferStorageExternalEXT (GLenum target, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); GLAPI void APIENTRY glNamedBufferStorageExternalEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); #endif #endif /* GL_EXT_external_buffer */ #ifndef GL_EXT_fog_coord #define GL_EXT_fog_coord 1 #define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 #define GL_FOG_COORDINATE_EXT 0x8451 #define GL_FRAGMENT_DEPTH_EXT 0x8452 #define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 #define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 #define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 #define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 #define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 typedef void (APIENTRYP PFNGLFOGCOORDFEXTPROC) (GLfloat coord); typedef void (APIENTRYP PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); typedef void (APIENTRYP PFNGLFOGCOORDDEXTPROC) (GLdouble coord); typedef void (APIENTRYP PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); typedef void (APIENTRYP PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFogCoordfEXT (GLfloat coord); GLAPI void APIENTRY glFogCoordfvEXT (const GLfloat *coord); GLAPI void APIENTRY glFogCoorddEXT (GLdouble coord); GLAPI void APIENTRY glFogCoorddvEXT (const GLdouble *coord); GLAPI void APIENTRY glFogCoordPointerEXT (GLenum type, GLsizei stride, const void *pointer); #endif #endif /* GL_EXT_fog_coord */ #ifndef GL_EXT_framebuffer_blit #define GL_EXT_framebuffer_blit 1 #define GL_READ_FRAMEBUFFER_EXT 0x8CA8 #define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9 #define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6 #define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA typedef void (APIENTRYP PFNGLBLITFRAMEBUFFEREXTPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlitFramebufferEXT (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); #endif #endif /* GL_EXT_framebuffer_blit */ #ifndef GL_EXT_framebuffer_multisample #define GL_EXT_framebuffer_multisample 1 #define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB #define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 #define GL_MAX_SAMPLES_EXT 0x8D57 typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glRenderbufferStorageMultisampleEXT (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); #endif #endif /* GL_EXT_framebuffer_multisample */ #ifndef GL_EXT_framebuffer_multisample_blit_scaled #define GL_EXT_framebuffer_multisample_blit_scaled 1 #define GL_SCALED_RESOLVE_FASTEST_EXT 0x90BA #define GL_SCALED_RESOLVE_NICEST_EXT 0x90BB #endif /* GL_EXT_framebuffer_multisample_blit_scaled */ #ifndef GL_EXT_framebuffer_object #define GL_EXT_framebuffer_object 1 #define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 #define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 #define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 #define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 #define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 #define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 #define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 #define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 #define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 #define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 #define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA #define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB #define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC #define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD #define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF #define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 #define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 #define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 #define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 #define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 #define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 #define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 #define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 #define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 #define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 #define GL_COLOR_ATTACHMENT10_EXT 0x8CEA #define GL_COLOR_ATTACHMENT11_EXT 0x8CEB #define GL_COLOR_ATTACHMENT12_EXT 0x8CEC #define GL_COLOR_ATTACHMENT13_EXT 0x8CED #define GL_COLOR_ATTACHMENT14_EXT 0x8CEE #define GL_COLOR_ATTACHMENT15_EXT 0x8CEF #define GL_DEPTH_ATTACHMENT_EXT 0x8D00 #define GL_STENCIL_ATTACHMENT_EXT 0x8D20 #define GL_FRAMEBUFFER_EXT 0x8D40 #define GL_RENDERBUFFER_EXT 0x8D41 #define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 #define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 #define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 #define GL_STENCIL_INDEX1_EXT 0x8D46 #define GL_STENCIL_INDEX4_EXT 0x8D47 #define GL_STENCIL_INDEX8_EXT 0x8D48 #define GL_STENCIL_INDEX16_EXT 0x8D49 #define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 #define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 #define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 #define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 #define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 #define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFEREXTPROC) (GLuint renderbuffer); typedef void (APIENTRYP PFNGLBINDRENDERBUFFEREXTPROC) (GLenum target, GLuint renderbuffer); typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSEXTPROC) (GLsizei n, const GLuint *renderbuffers); typedef void (APIENTRYP PFNGLGENRENDERBUFFERSEXTPROC) (GLsizei n, GLuint *renderbuffers); typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFEREXTPROC) (GLuint framebuffer); typedef void (APIENTRYP PFNGLBINDFRAMEBUFFEREXTPROC) (GLenum target, GLuint framebuffer); typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSEXTPROC) (GLsizei n, const GLuint *framebuffers); typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSEXTPROC) (GLsizei n, GLuint *framebuffers); typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) (GLenum target); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGENERATEMIPMAPEXTPROC) (GLenum target); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLboolean APIENTRY glIsRenderbufferEXT (GLuint renderbuffer); GLAPI void APIENTRY glBindRenderbufferEXT (GLenum target, GLuint renderbuffer); GLAPI void APIENTRY glDeleteRenderbuffersEXT (GLsizei n, const GLuint *renderbuffers); GLAPI void APIENTRY glGenRenderbuffersEXT (GLsizei n, GLuint *renderbuffers); GLAPI void APIENTRY glRenderbufferStorageEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glGetRenderbufferParameterivEXT (GLenum target, GLenum pname, GLint *params); GLAPI GLboolean APIENTRY glIsFramebufferEXT (GLuint framebuffer); GLAPI void APIENTRY glBindFramebufferEXT (GLenum target, GLuint framebuffer); GLAPI void APIENTRY glDeleteFramebuffersEXT (GLsizei n, const GLuint *framebuffers); GLAPI void APIENTRY glGenFramebuffersEXT (GLsizei n, GLuint *framebuffers); GLAPI GLenum APIENTRY glCheckFramebufferStatusEXT (GLenum target); GLAPI void APIENTRY glFramebufferTexture1DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); GLAPI void APIENTRY glFramebufferTexture2DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); GLAPI void APIENTRY glFramebufferTexture3DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); GLAPI void APIENTRY glFramebufferRenderbufferEXT (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); GLAPI void APIENTRY glGetFramebufferAttachmentParameterivEXT (GLenum target, GLenum attachment, GLenum pname, GLint *params); GLAPI void APIENTRY glGenerateMipmapEXT (GLenum target); #endif #endif /* GL_EXT_framebuffer_object */ #ifndef GL_EXT_framebuffer_sRGB #define GL_EXT_framebuffer_sRGB 1 #define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 #define GL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x8DBA #endif /* GL_EXT_framebuffer_sRGB */ #ifndef GL_EXT_geometry_shader4 #define GL_EXT_geometry_shader4 1 #define GL_GEOMETRY_SHADER_EXT 0x8DD9 #define GL_GEOMETRY_VERTICES_OUT_EXT 0x8DDA #define GL_GEOMETRY_INPUT_TYPE_EXT 0x8DDB #define GL_GEOMETRY_OUTPUT_TYPE_EXT 0x8DDC #define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT 0x8C29 #define GL_MAX_GEOMETRY_VARYING_COMPONENTS_EXT 0x8DDD #define GL_MAX_VERTEX_VARYING_COMPONENTS_EXT 0x8DDE #define GL_MAX_VARYING_COMPONENTS_EXT 0x8B4B #define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_EXT 0x8DDF #define GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT 0x8DE0 #define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_EXT 0x8DE1 #define GL_LINES_ADJACENCY_EXT 0x000A #define GL_LINE_STRIP_ADJACENCY_EXT 0x000B #define GL_TRIANGLES_ADJACENCY_EXT 0x000C #define GL_TRIANGLE_STRIP_ADJACENCY_EXT 0x000D #define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT 0x8DA8 #define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT 0x8DA9 #define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT 0x8DA7 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT 0x8CD4 #define GL_PROGRAM_POINT_SIZE_EXT 0x8642 typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIEXTPROC) (GLuint program, GLenum pname, GLint value); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glProgramParameteriEXT (GLuint program, GLenum pname, GLint value); #endif #endif /* GL_EXT_geometry_shader4 */ #ifndef GL_EXT_gpu_program_parameters #define GL_EXT_gpu_program_parameters 1 typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glProgramEnvParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); GLAPI void APIENTRY glProgramLocalParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); #endif #endif /* GL_EXT_gpu_program_parameters */ #ifndef GL_EXT_gpu_shader4 #define GL_EXT_gpu_shader4 1 #define GL_SAMPLER_1D_ARRAY_EXT 0x8DC0 #define GL_SAMPLER_2D_ARRAY_EXT 0x8DC1 #define GL_SAMPLER_BUFFER_EXT 0x8DC2 #define GL_SAMPLER_1D_ARRAY_SHADOW_EXT 0x8DC3 #define GL_SAMPLER_2D_ARRAY_SHADOW_EXT 0x8DC4 #define GL_SAMPLER_CUBE_SHADOW_EXT 0x8DC5 #define GL_UNSIGNED_INT_VEC2_EXT 0x8DC6 #define GL_UNSIGNED_INT_VEC3_EXT 0x8DC7 #define GL_UNSIGNED_INT_VEC4_EXT 0x8DC8 #define GL_INT_SAMPLER_1D_EXT 0x8DC9 #define GL_INT_SAMPLER_2D_EXT 0x8DCA #define GL_INT_SAMPLER_3D_EXT 0x8DCB #define GL_INT_SAMPLER_CUBE_EXT 0x8DCC #define GL_INT_SAMPLER_2D_RECT_EXT 0x8DCD #define GL_INT_SAMPLER_1D_ARRAY_EXT 0x8DCE #define GL_INT_SAMPLER_2D_ARRAY_EXT 0x8DCF #define GL_INT_SAMPLER_BUFFER_EXT 0x8DD0 #define GL_UNSIGNED_INT_SAMPLER_1D_EXT 0x8DD1 #define GL_UNSIGNED_INT_SAMPLER_2D_EXT 0x8DD2 #define GL_UNSIGNED_INT_SAMPLER_3D_EXT 0x8DD3 #define GL_UNSIGNED_INT_SAMPLER_CUBE_EXT 0x8DD4 #define GL_UNSIGNED_INT_SAMPLER_2D_RECT_EXT 0x8DD5 #define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY_EXT 0x8DD6 #define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY_EXT 0x8DD7 #define GL_UNSIGNED_INT_SAMPLER_BUFFER_EXT 0x8DD8 #define GL_MIN_PROGRAM_TEXEL_OFFSET_EXT 0x8904 #define GL_MAX_PROGRAM_TEXEL_OFFSET_EXT 0x8905 #define GL_VERTEX_ATTRIB_ARRAY_INTEGER_EXT 0x88FD typedef void (APIENTRYP PFNGLGETUNIFORMUIVEXTPROC) (GLuint program, GLint location, GLuint *params); typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONEXTPROC) (GLuint program, GLuint color, const GLchar *name); typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONEXTPROC) (GLuint program, const GLchar *name); typedef void (APIENTRYP PFNGLUNIFORM1UIEXTPROC) (GLint location, GLuint v0); typedef void (APIENTRYP PFNGLUNIFORM2UIEXTPROC) (GLint location, GLuint v0, GLuint v1); typedef void (APIENTRYP PFNGLUNIFORM3UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); typedef void (APIENTRYP PFNGLUNIFORM4UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); typedef void (APIENTRYP PFNGLUNIFORM1UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLUNIFORM2UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLUNIFORM3UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLUNIFORM4UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IEXTPROC) (GLuint index, GLint x); typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IEXTPROC) (GLuint index, GLint x, GLint y); typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IEXTPROC) (GLuint index, GLint x, GLint y, GLint z); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IEXTPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIEXTPROC) (GLuint index, GLuint x); typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIEXTPROC) (GLuint index, GLuint x, GLuint y); typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVEXTPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVEXTPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVEXTPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVEXTPROC) (GLuint index, const GLint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVEXTPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVEXTPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVEXTPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVEXTPROC) (GLuint index, const GLuint *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVEXTPROC) (GLuint index, const GLbyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVEXTPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVEXTPROC) (GLuint index, const GLubyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVEXTPROC) (GLuint index, const GLushort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVEXTPROC) (GLuint index, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVEXTPROC) (GLuint index, GLenum pname, GLuint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGetUniformuivEXT (GLuint program, GLint location, GLuint *params); GLAPI void APIENTRY glBindFragDataLocationEXT (GLuint program, GLuint color, const GLchar *name); GLAPI GLint APIENTRY glGetFragDataLocationEXT (GLuint program, const GLchar *name); GLAPI void APIENTRY glUniform1uiEXT (GLint location, GLuint v0); GLAPI void APIENTRY glUniform2uiEXT (GLint location, GLuint v0, GLuint v1); GLAPI void APIENTRY glUniform3uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2); GLAPI void APIENTRY glUniform4uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); GLAPI void APIENTRY glUniform1uivEXT (GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glUniform2uivEXT (GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glUniform3uivEXT (GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glUniform4uivEXT (GLint location, GLsizei count, const GLuint *value); GLAPI void APIENTRY glVertexAttribI1iEXT (GLuint index, GLint x); GLAPI void APIENTRY glVertexAttribI2iEXT (GLuint index, GLint x, GLint y); GLAPI void APIENTRY glVertexAttribI3iEXT (GLuint index, GLint x, GLint y, GLint z); GLAPI void APIENTRY glVertexAttribI4iEXT (GLuint index, GLint x, GLint y, GLint z, GLint w); GLAPI void APIENTRY glVertexAttribI1uiEXT (GLuint index, GLuint x); GLAPI void APIENTRY glVertexAttribI2uiEXT (GLuint index, GLuint x, GLuint y); GLAPI void APIENTRY glVertexAttribI3uiEXT (GLuint index, GLuint x, GLuint y, GLuint z); GLAPI void APIENTRY glVertexAttribI4uiEXT (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); GLAPI void APIENTRY glVertexAttribI1ivEXT (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttribI2ivEXT (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttribI3ivEXT (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttribI4ivEXT (GLuint index, const GLint *v); GLAPI void APIENTRY glVertexAttribI1uivEXT (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttribI2uivEXT (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttribI3uivEXT (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttribI4uivEXT (GLuint index, const GLuint *v); GLAPI void APIENTRY glVertexAttribI4bvEXT (GLuint index, const GLbyte *v); GLAPI void APIENTRY glVertexAttribI4svEXT (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttribI4ubvEXT (GLuint index, const GLubyte *v); GLAPI void APIENTRY glVertexAttribI4usvEXT (GLuint index, const GLushort *v); GLAPI void APIENTRY glVertexAttribIPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glGetVertexAttribIivEXT (GLuint index, GLenum pname, GLint *params); GLAPI void APIENTRY glGetVertexAttribIuivEXT (GLuint index, GLenum pname, GLuint *params); #endif #endif /* GL_EXT_gpu_shader4 */ #ifndef GL_EXT_histogram #define GL_EXT_histogram 1 #define GL_HISTOGRAM_EXT 0x8024 #define GL_PROXY_HISTOGRAM_EXT 0x8025 #define GL_HISTOGRAM_WIDTH_EXT 0x8026 #define GL_HISTOGRAM_FORMAT_EXT 0x8027 #define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 #define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 #define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A #define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B #define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C #define GL_HISTOGRAM_SINK_EXT 0x802D #define GL_MINMAX_EXT 0x802E #define GL_MINMAX_FORMAT_EXT 0x802F #define GL_MINMAX_SINK_EXT 0x8030 #define GL_TABLE_TOO_LARGE_EXT 0x8031 typedef void (APIENTRYP PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); typedef void (APIENTRYP PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); typedef void (APIENTRYP PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); typedef void (APIENTRYP PFNGLRESETMINMAXEXTPROC) (GLenum target); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGetHistogramEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); GLAPI void APIENTRY glGetHistogramParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetHistogramParameterivEXT (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetMinmaxEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); GLAPI void APIENTRY glGetMinmaxParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetMinmaxParameterivEXT (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glHistogramEXT (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); GLAPI void APIENTRY glMinmaxEXT (GLenum target, GLenum internalformat, GLboolean sink); GLAPI void APIENTRY glResetHistogramEXT (GLenum target); GLAPI void APIENTRY glResetMinmaxEXT (GLenum target); #endif #endif /* GL_EXT_histogram */ #ifndef GL_EXT_index_array_formats #define GL_EXT_index_array_formats 1 #define GL_IUI_V2F_EXT 0x81AD #define GL_IUI_V3F_EXT 0x81AE #define GL_IUI_N3F_V2F_EXT 0x81AF #define GL_IUI_N3F_V3F_EXT 0x81B0 #define GL_T2F_IUI_V2F_EXT 0x81B1 #define GL_T2F_IUI_V3F_EXT 0x81B2 #define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 #define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 #endif /* GL_EXT_index_array_formats */ #ifndef GL_EXT_index_func #define GL_EXT_index_func 1 #define GL_INDEX_TEST_EXT 0x81B5 #define GL_INDEX_TEST_FUNC_EXT 0x81B6 #define GL_INDEX_TEST_REF_EXT 0x81B7 typedef void (APIENTRYP PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glIndexFuncEXT (GLenum func, GLclampf ref); #endif #endif /* GL_EXT_index_func */ #ifndef GL_EXT_index_material #define GL_EXT_index_material 1 #define GL_INDEX_MATERIAL_EXT 0x81B8 #define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 #define GL_INDEX_MATERIAL_FACE_EXT 0x81BA typedef void (APIENTRYP PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glIndexMaterialEXT (GLenum face, GLenum mode); #endif #endif /* GL_EXT_index_material */ #ifndef GL_EXT_index_texture #define GL_EXT_index_texture 1 #endif /* GL_EXT_index_texture */ #ifndef GL_EXT_light_texture #define GL_EXT_light_texture 1 #define GL_FRAGMENT_MATERIAL_EXT 0x8349 #define GL_FRAGMENT_NORMAL_EXT 0x834A #define GL_FRAGMENT_COLOR_EXT 0x834C #define GL_ATTENUATION_EXT 0x834D #define GL_SHADOW_ATTENUATION_EXT 0x834E #define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F #define GL_TEXTURE_LIGHT_EXT 0x8350 #define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 #define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 typedef void (APIENTRYP PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); typedef void (APIENTRYP PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); typedef void (APIENTRYP PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glApplyTextureEXT (GLenum mode); GLAPI void APIENTRY glTextureLightEXT (GLenum pname); GLAPI void APIENTRY glTextureMaterialEXT (GLenum face, GLenum mode); #endif #endif /* GL_EXT_light_texture */ #ifndef GL_EXT_memory_object #define GL_EXT_memory_object 1 #define GL_TEXTURE_TILING_EXT 0x9580 #define GL_DEDICATED_MEMORY_OBJECT_EXT 0x9581 #define GL_PROTECTED_MEMORY_OBJECT_EXT 0x959B #define GL_NUM_TILING_TYPES_EXT 0x9582 #define GL_TILING_TYPES_EXT 0x9583 #define GL_OPTIMAL_TILING_EXT 0x9584 #define GL_LINEAR_TILING_EXT 0x9585 #define GL_NUM_DEVICE_UUIDS_EXT 0x9596 #define GL_DEVICE_UUID_EXT 0x9597 #define GL_DRIVER_UUID_EXT 0x9598 #define GL_UUID_SIZE_EXT 16 typedef void (APIENTRYP PFNGLGETUNSIGNEDBYTEVEXTPROC) (GLenum pname, GLubyte *data); typedef void (APIENTRYP PFNGLGETUNSIGNEDBYTEI_VEXTPROC) (GLenum target, GLuint index, GLubyte *data); typedef void (APIENTRYP PFNGLDELETEMEMORYOBJECTSEXTPROC) (GLsizei n, const GLuint *memoryObjects); typedef GLboolean (APIENTRYP PFNGLISMEMORYOBJECTEXTPROC) (GLuint memoryObject); typedef void (APIENTRYP PFNGLCREATEMEMORYOBJECTSEXTPROC) (GLsizei n, GLuint *memoryObjects); typedef void (APIENTRYP PFNGLMEMORYOBJECTPARAMETERIVEXTPROC) (GLuint memoryObject, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLGETMEMORYOBJECTPARAMETERIVEXTPROC) (GLuint memoryObject, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLTEXSTORAGEMEM2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLTEXSTORAGEMEM2DMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLTEXSTORAGEMEM3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLTEXSTORAGEMEM3DMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLBUFFERSTORAGEMEMEXTPROC) (GLenum target, GLsizeiptr size, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM2DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM2DMULTISAMPLEEXTPROC) (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM3DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM3DMULTISAMPLEEXTPROC) (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEMEMEXTPROC) (GLuint buffer, GLsizeiptr size, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLTEXSTORAGEMEM1DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM1DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGetUnsignedBytevEXT (GLenum pname, GLubyte *data); GLAPI void APIENTRY glGetUnsignedBytei_vEXT (GLenum target, GLuint index, GLubyte *data); GLAPI void APIENTRY glDeleteMemoryObjectsEXT (GLsizei n, const GLuint *memoryObjects); GLAPI GLboolean APIENTRY glIsMemoryObjectEXT (GLuint memoryObject); GLAPI void APIENTRY glCreateMemoryObjectsEXT (GLsizei n, GLuint *memoryObjects); GLAPI void APIENTRY glMemoryObjectParameterivEXT (GLuint memoryObject, GLenum pname, const GLint *params); GLAPI void APIENTRY glGetMemoryObjectParameterivEXT (GLuint memoryObject, GLenum pname, GLint *params); GLAPI void APIENTRY glTexStorageMem2DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glTexStorageMem2DMultisampleEXT (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glTexStorageMem3DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glTexStorageMem3DMultisampleEXT (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glBufferStorageMemEXT (GLenum target, GLsizeiptr size, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glTextureStorageMem2DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glTextureStorageMem2DMultisampleEXT (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glTextureStorageMem3DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glTextureStorageMem3DMultisampleEXT (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glNamedBufferStorageMemEXT (GLuint buffer, GLsizeiptr size, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glTexStorageMem1DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glTextureStorageMem1DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); #endif #endif /* GL_EXT_memory_object */ #ifndef GL_EXT_memory_object_fd #define GL_EXT_memory_object_fd 1 #define GL_HANDLE_TYPE_OPAQUE_FD_EXT 0x9586 typedef void (APIENTRYP PFNGLIMPORTMEMORYFDEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, GLint fd); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glImportMemoryFdEXT (GLuint memory, GLuint64 size, GLenum handleType, GLint fd); #endif #endif /* GL_EXT_memory_object_fd */ #ifndef GL_EXT_memory_object_win32 #define GL_EXT_memory_object_win32 1 #define GL_HANDLE_TYPE_OPAQUE_WIN32_EXT 0x9587 #define GL_HANDLE_TYPE_OPAQUE_WIN32_KMT_EXT 0x9588 #define GL_DEVICE_LUID_EXT 0x9599 #define GL_DEVICE_NODE_MASK_EXT 0x959A #define GL_LUID_SIZE_EXT 8 #define GL_HANDLE_TYPE_D3D12_TILEPOOL_EXT 0x9589 #define GL_HANDLE_TYPE_D3D12_RESOURCE_EXT 0x958A #define GL_HANDLE_TYPE_D3D11_IMAGE_EXT 0x958B #define GL_HANDLE_TYPE_D3D11_IMAGE_KMT_EXT 0x958C typedef void (APIENTRYP PFNGLIMPORTMEMORYWIN32HANDLEEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, void *handle); typedef void (APIENTRYP PFNGLIMPORTMEMORYWIN32NAMEEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, const void *name); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glImportMemoryWin32HandleEXT (GLuint memory, GLuint64 size, GLenum handleType, void *handle); GLAPI void APIENTRY glImportMemoryWin32NameEXT (GLuint memory, GLuint64 size, GLenum handleType, const void *name); #endif #endif /* GL_EXT_memory_object_win32 */ #ifndef GL_EXT_misc_attribute #define GL_EXT_misc_attribute 1 #endif /* GL_EXT_misc_attribute */ #ifndef GL_EXT_multi_draw_arrays #define GL_EXT_multi_draw_arrays 1 typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMultiDrawArraysEXT (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); GLAPI void APIENTRY glMultiDrawElementsEXT (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount); #endif #endif /* GL_EXT_multi_draw_arrays */ #ifndef GL_EXT_multisample #define GL_EXT_multisample 1 #define GL_MULTISAMPLE_EXT 0x809D #define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E #define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F #define GL_SAMPLE_MASK_EXT 0x80A0 #define GL_1PASS_EXT 0x80A1 #define GL_2PASS_0_EXT 0x80A2 #define GL_2PASS_1_EXT 0x80A3 #define GL_4PASS_0_EXT 0x80A4 #define GL_4PASS_1_EXT 0x80A5 #define GL_4PASS_2_EXT 0x80A6 #define GL_4PASS_3_EXT 0x80A7 #define GL_SAMPLE_BUFFERS_EXT 0x80A8 #define GL_SAMPLES_EXT 0x80A9 #define GL_SAMPLE_MASK_VALUE_EXT 0x80AA #define GL_SAMPLE_MASK_INVERT_EXT 0x80AB #define GL_SAMPLE_PATTERN_EXT 0x80AC #define GL_MULTISAMPLE_BIT_EXT 0x20000000 typedef void (APIENTRYP PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); typedef void (APIENTRYP PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSampleMaskEXT (GLclampf value, GLboolean invert); GLAPI void APIENTRY glSamplePatternEXT (GLenum pattern); #endif #endif /* GL_EXT_multisample */ #ifndef GL_EXT_multiview_tessellation_geometry_shader #define GL_EXT_multiview_tessellation_geometry_shader 1 #endif /* GL_EXT_multiview_tessellation_geometry_shader */ #ifndef GL_EXT_multiview_texture_multisample #define GL_EXT_multiview_texture_multisample 1 #endif /* GL_EXT_multiview_texture_multisample */ #ifndef GL_EXT_multiview_timer_query #define GL_EXT_multiview_timer_query 1 #endif /* GL_EXT_multiview_timer_query */ #ifndef GL_EXT_packed_depth_stencil #define GL_EXT_packed_depth_stencil 1 #define GL_DEPTH_STENCIL_EXT 0x84F9 #define GL_UNSIGNED_INT_24_8_EXT 0x84FA #define GL_DEPTH24_STENCIL8_EXT 0x88F0 #define GL_TEXTURE_STENCIL_SIZE_EXT 0x88F1 #endif /* GL_EXT_packed_depth_stencil */ #ifndef GL_EXT_packed_float #define GL_EXT_packed_float 1 #define GL_R11F_G11F_B10F_EXT 0x8C3A #define GL_UNSIGNED_INT_10F_11F_11F_REV_EXT 0x8C3B #define GL_RGBA_SIGNED_COMPONENTS_EXT 0x8C3C #endif /* GL_EXT_packed_float */ #ifndef GL_EXT_packed_pixels #define GL_EXT_packed_pixels 1 #define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 #define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 #define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 #define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 #define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 #endif /* GL_EXT_packed_pixels */ #ifndef GL_EXT_paletted_texture #define GL_EXT_paletted_texture 1 #define GL_COLOR_INDEX1_EXT 0x80E2 #define GL_COLOR_INDEX2_EXT 0x80E3 #define GL_COLOR_INDEX4_EXT 0x80E4 #define GL_COLOR_INDEX8_EXT 0x80E5 #define GL_COLOR_INDEX12_EXT 0x80E6 #define GL_COLOR_INDEX16_EXT 0x80E7 #define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED typedef void (APIENTRYP PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void *table); typedef void (APIENTRYP PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, void *data); typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glColorTableEXT (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void *table); GLAPI void APIENTRY glGetColorTableEXT (GLenum target, GLenum format, GLenum type, void *data); GLAPI void APIENTRY glGetColorTableParameterivEXT (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetColorTableParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); #endif #endif /* GL_EXT_paletted_texture */ #ifndef GL_EXT_pixel_buffer_object #define GL_EXT_pixel_buffer_object 1 #define GL_PIXEL_PACK_BUFFER_EXT 0x88EB #define GL_PIXEL_UNPACK_BUFFER_EXT 0x88EC #define GL_PIXEL_PACK_BUFFER_BINDING_EXT 0x88ED #define GL_PIXEL_UNPACK_BUFFER_BINDING_EXT 0x88EF #endif /* GL_EXT_pixel_buffer_object */ #ifndef GL_EXT_pixel_transform #define GL_EXT_pixel_transform 1 #define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 #define GL_PIXEL_MAG_FILTER_EXT 0x8331 #define GL_PIXEL_MIN_FILTER_EXT 0x8332 #define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 #define GL_CUBIC_EXT 0x8334 #define GL_AVERAGE_EXT 0x8335 #define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 #define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 #define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLGETPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPixelTransformParameteriEXT (GLenum target, GLenum pname, GLint param); GLAPI void APIENTRY glPixelTransformParameterfEXT (GLenum target, GLenum pname, GLfloat param); GLAPI void APIENTRY glPixelTransformParameterivEXT (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glPixelTransformParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glGetPixelTransformParameterivEXT (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetPixelTransformParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); #endif #endif /* GL_EXT_pixel_transform */ #ifndef GL_EXT_pixel_transform_color_table #define GL_EXT_pixel_transform_color_table 1 #endif /* GL_EXT_pixel_transform_color_table */ #ifndef GL_EXT_point_parameters #define GL_EXT_point_parameters 1 #define GL_POINT_SIZE_MIN_EXT 0x8126 #define GL_POINT_SIZE_MAX_EXT 0x8127 #define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 #define GL_DISTANCE_ATTENUATION_EXT 0x8129 typedef void (APIENTRYP PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPointParameterfEXT (GLenum pname, GLfloat param); GLAPI void APIENTRY glPointParameterfvEXT (GLenum pname, const GLfloat *params); #endif #endif /* GL_EXT_point_parameters */ #ifndef GL_EXT_polygon_offset #define GL_EXT_polygon_offset 1 #define GL_POLYGON_OFFSET_EXT 0x8037 #define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 #define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 typedef void (APIENTRYP PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPolygonOffsetEXT (GLfloat factor, GLfloat bias); #endif #endif /* GL_EXT_polygon_offset */ #ifndef GL_EXT_polygon_offset_clamp #define GL_EXT_polygon_offset_clamp 1 #define GL_POLYGON_OFFSET_CLAMP_EXT 0x8E1B typedef void (APIENTRYP PFNGLPOLYGONOFFSETCLAMPEXTPROC) (GLfloat factor, GLfloat units, GLfloat clamp); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPolygonOffsetClampEXT (GLfloat factor, GLfloat units, GLfloat clamp); #endif #endif /* GL_EXT_polygon_offset_clamp */ #ifndef GL_EXT_post_depth_coverage #define GL_EXT_post_depth_coverage 1 #endif /* GL_EXT_post_depth_coverage */ #ifndef GL_EXT_provoking_vertex #define GL_EXT_provoking_vertex 1 #define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION_EXT 0x8E4C #define GL_FIRST_VERTEX_CONVENTION_EXT 0x8E4D #define GL_LAST_VERTEX_CONVENTION_EXT 0x8E4E #define GL_PROVOKING_VERTEX_EXT 0x8E4F typedef void (APIENTRYP PFNGLPROVOKINGVERTEXEXTPROC) (GLenum mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glProvokingVertexEXT (GLenum mode); #endif #endif /* GL_EXT_provoking_vertex */ #ifndef GL_EXT_raster_multisample #define GL_EXT_raster_multisample 1 #define GL_RASTER_MULTISAMPLE_EXT 0x9327 #define GL_RASTER_SAMPLES_EXT 0x9328 #define GL_MAX_RASTER_SAMPLES_EXT 0x9329 #define GL_RASTER_FIXED_SAMPLE_LOCATIONS_EXT 0x932A #define GL_MULTISAMPLE_RASTERIZATION_ALLOWED_EXT 0x932B #define GL_EFFECTIVE_RASTER_SAMPLES_EXT 0x932C typedef void (APIENTRYP PFNGLRASTERSAMPLESEXTPROC) (GLuint samples, GLboolean fixedsamplelocations); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glRasterSamplesEXT (GLuint samples, GLboolean fixedsamplelocations); #endif #endif /* GL_EXT_raster_multisample */ #ifndef GL_EXT_rescale_normal #define GL_EXT_rescale_normal 1 #define GL_RESCALE_NORMAL_EXT 0x803A #endif /* GL_EXT_rescale_normal */ #ifndef GL_EXT_secondary_color #define GL_EXT_secondary_color 1 #define GL_COLOR_SUM_EXT 0x8458 #define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 #define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A #define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B #define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C #define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D #define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSecondaryColor3bEXT (GLbyte red, GLbyte green, GLbyte blue); GLAPI void APIENTRY glSecondaryColor3bvEXT (const GLbyte *v); GLAPI void APIENTRY glSecondaryColor3dEXT (GLdouble red, GLdouble green, GLdouble blue); GLAPI void APIENTRY glSecondaryColor3dvEXT (const GLdouble *v); GLAPI void APIENTRY glSecondaryColor3fEXT (GLfloat red, GLfloat green, GLfloat blue); GLAPI void APIENTRY glSecondaryColor3fvEXT (const GLfloat *v); GLAPI void APIENTRY glSecondaryColor3iEXT (GLint red, GLint green, GLint blue); GLAPI void APIENTRY glSecondaryColor3ivEXT (const GLint *v); GLAPI void APIENTRY glSecondaryColor3sEXT (GLshort red, GLshort green, GLshort blue); GLAPI void APIENTRY glSecondaryColor3svEXT (const GLshort *v); GLAPI void APIENTRY glSecondaryColor3ubEXT (GLubyte red, GLubyte green, GLubyte blue); GLAPI void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *v); GLAPI void APIENTRY glSecondaryColor3uiEXT (GLuint red, GLuint green, GLuint blue); GLAPI void APIENTRY glSecondaryColor3uivEXT (const GLuint *v); GLAPI void APIENTRY glSecondaryColor3usEXT (GLushort red, GLushort green, GLushort blue); GLAPI void APIENTRY glSecondaryColor3usvEXT (const GLushort *v); GLAPI void APIENTRY glSecondaryColorPointerEXT (GLint size, GLenum type, GLsizei stride, const void *pointer); #endif #endif /* GL_EXT_secondary_color */ #ifndef GL_EXT_semaphore #define GL_EXT_semaphore 1 #define GL_LAYOUT_GENERAL_EXT 0x958D #define GL_LAYOUT_COLOR_ATTACHMENT_EXT 0x958E #define GL_LAYOUT_DEPTH_STENCIL_ATTACHMENT_EXT 0x958F #define GL_LAYOUT_DEPTH_STENCIL_READ_ONLY_EXT 0x9590 #define GL_LAYOUT_SHADER_READ_ONLY_EXT 0x9591 #define GL_LAYOUT_TRANSFER_SRC_EXT 0x9592 #define GL_LAYOUT_TRANSFER_DST_EXT 0x9593 #define GL_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_EXT 0x9530 #define GL_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_EXT 0x9531 typedef void (APIENTRYP PFNGLGENSEMAPHORESEXTPROC) (GLsizei n, GLuint *semaphores); typedef void (APIENTRYP PFNGLDELETESEMAPHORESEXTPROC) (GLsizei n, const GLuint *semaphores); typedef GLboolean (APIENTRYP PFNGLISSEMAPHOREEXTPROC) (GLuint semaphore); typedef void (APIENTRYP PFNGLSEMAPHOREPARAMETERUI64VEXTPROC) (GLuint semaphore, GLenum pname, const GLuint64 *params); typedef void (APIENTRYP PFNGLGETSEMAPHOREPARAMETERUI64VEXTPROC) (GLuint semaphore, GLenum pname, GLuint64 *params); typedef void (APIENTRYP PFNGLWAITSEMAPHOREEXTPROC) (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *srcLayouts); typedef void (APIENTRYP PFNGLSIGNALSEMAPHOREEXTPROC) (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *dstLayouts); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGenSemaphoresEXT (GLsizei n, GLuint *semaphores); GLAPI void APIENTRY glDeleteSemaphoresEXT (GLsizei n, const GLuint *semaphores); GLAPI GLboolean APIENTRY glIsSemaphoreEXT (GLuint semaphore); GLAPI void APIENTRY glSemaphoreParameterui64vEXT (GLuint semaphore, GLenum pname, const GLuint64 *params); GLAPI void APIENTRY glGetSemaphoreParameterui64vEXT (GLuint semaphore, GLenum pname, GLuint64 *params); GLAPI void APIENTRY glWaitSemaphoreEXT (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *srcLayouts); GLAPI void APIENTRY glSignalSemaphoreEXT (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *dstLayouts); #endif #endif /* GL_EXT_semaphore */ #ifndef GL_EXT_semaphore_fd #define GL_EXT_semaphore_fd 1 typedef void (APIENTRYP PFNGLIMPORTSEMAPHOREFDEXTPROC) (GLuint semaphore, GLenum handleType, GLint fd); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glImportSemaphoreFdEXT (GLuint semaphore, GLenum handleType, GLint fd); #endif #endif /* GL_EXT_semaphore_fd */ #ifndef GL_EXT_semaphore_win32 #define GL_EXT_semaphore_win32 1 #define GL_HANDLE_TYPE_D3D12_FENCE_EXT 0x9594 #define GL_D3D12_FENCE_VALUE_EXT 0x9595 typedef void (APIENTRYP PFNGLIMPORTSEMAPHOREWIN32HANDLEEXTPROC) (GLuint semaphore, GLenum handleType, void *handle); typedef void (APIENTRYP PFNGLIMPORTSEMAPHOREWIN32NAMEEXTPROC) (GLuint semaphore, GLenum handleType, const void *name); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glImportSemaphoreWin32HandleEXT (GLuint semaphore, GLenum handleType, void *handle); GLAPI void APIENTRY glImportSemaphoreWin32NameEXT (GLuint semaphore, GLenum handleType, const void *name); #endif #endif /* GL_EXT_semaphore_win32 */ #ifndef GL_EXT_separate_shader_objects #define GL_EXT_separate_shader_objects 1 #define GL_ACTIVE_PROGRAM_EXT 0x8B8D typedef void (APIENTRYP PFNGLUSESHADERPROGRAMEXTPROC) (GLenum type, GLuint program); typedef void (APIENTRYP PFNGLACTIVEPROGRAMEXTPROC) (GLuint program); typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMEXTPROC) (GLenum type, const GLchar *string); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glUseShaderProgramEXT (GLenum type, GLuint program); GLAPI void APIENTRY glActiveProgramEXT (GLuint program); GLAPI GLuint APIENTRY glCreateShaderProgramEXT (GLenum type, const GLchar *string); #endif #endif /* GL_EXT_separate_shader_objects */ #ifndef GL_EXT_separate_specular_color #define GL_EXT_separate_specular_color 1 #define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 #define GL_SINGLE_COLOR_EXT 0x81F9 #define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA #endif /* GL_EXT_separate_specular_color */ #ifndef GL_EXT_shader_framebuffer_fetch #define GL_EXT_shader_framebuffer_fetch 1 #define GL_FRAGMENT_SHADER_DISCARDS_SAMPLES_EXT 0x8A52 #endif /* GL_EXT_shader_framebuffer_fetch */ #ifndef GL_EXT_shader_framebuffer_fetch_non_coherent #define GL_EXT_shader_framebuffer_fetch_non_coherent 1 typedef void (APIENTRYP PFNGLFRAMEBUFFERFETCHBARRIEREXTPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFramebufferFetchBarrierEXT (void); #endif #endif /* GL_EXT_shader_framebuffer_fetch_non_coherent */ #ifndef GL_EXT_shader_image_load_formatted #define GL_EXT_shader_image_load_formatted 1 #endif /* GL_EXT_shader_image_load_formatted */ #ifndef GL_EXT_shader_image_load_store #define GL_EXT_shader_image_load_store 1 #define GL_MAX_IMAGE_UNITS_EXT 0x8F38 #define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS_EXT 0x8F39 #define GL_IMAGE_BINDING_NAME_EXT 0x8F3A #define GL_IMAGE_BINDING_LEVEL_EXT 0x8F3B #define GL_IMAGE_BINDING_LAYERED_EXT 0x8F3C #define GL_IMAGE_BINDING_LAYER_EXT 0x8F3D #define GL_IMAGE_BINDING_ACCESS_EXT 0x8F3E #define GL_IMAGE_1D_EXT 0x904C #define GL_IMAGE_2D_EXT 0x904D #define GL_IMAGE_3D_EXT 0x904E #define GL_IMAGE_2D_RECT_EXT 0x904F #define GL_IMAGE_CUBE_EXT 0x9050 #define GL_IMAGE_BUFFER_EXT 0x9051 #define GL_IMAGE_1D_ARRAY_EXT 0x9052 #define GL_IMAGE_2D_ARRAY_EXT 0x9053 #define GL_IMAGE_CUBE_MAP_ARRAY_EXT 0x9054 #define GL_IMAGE_2D_MULTISAMPLE_EXT 0x9055 #define GL_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9056 #define GL_INT_IMAGE_1D_EXT 0x9057 #define GL_INT_IMAGE_2D_EXT 0x9058 #define GL_INT_IMAGE_3D_EXT 0x9059 #define GL_INT_IMAGE_2D_RECT_EXT 0x905A #define GL_INT_IMAGE_CUBE_EXT 0x905B #define GL_INT_IMAGE_BUFFER_EXT 0x905C #define GL_INT_IMAGE_1D_ARRAY_EXT 0x905D #define GL_INT_IMAGE_2D_ARRAY_EXT 0x905E #define GL_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x905F #define GL_INT_IMAGE_2D_MULTISAMPLE_EXT 0x9060 #define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9061 #define GL_UNSIGNED_INT_IMAGE_1D_EXT 0x9062 #define GL_UNSIGNED_INT_IMAGE_2D_EXT 0x9063 #define GL_UNSIGNED_INT_IMAGE_3D_EXT 0x9064 #define GL_UNSIGNED_INT_IMAGE_2D_RECT_EXT 0x9065 #define GL_UNSIGNED_INT_IMAGE_CUBE_EXT 0x9066 #define GL_UNSIGNED_INT_IMAGE_BUFFER_EXT 0x9067 #define GL_UNSIGNED_INT_IMAGE_1D_ARRAY_EXT 0x9068 #define GL_UNSIGNED_INT_IMAGE_2D_ARRAY_EXT 0x9069 #define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x906A #define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_EXT 0x906B #define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x906C #define GL_MAX_IMAGE_SAMPLES_EXT 0x906D #define GL_IMAGE_BINDING_FORMAT_EXT 0x906E #define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT_EXT 0x00000001 #define GL_ELEMENT_ARRAY_BARRIER_BIT_EXT 0x00000002 #define GL_UNIFORM_BARRIER_BIT_EXT 0x00000004 #define GL_TEXTURE_FETCH_BARRIER_BIT_EXT 0x00000008 #define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT_EXT 0x00000020 #define GL_COMMAND_BARRIER_BIT_EXT 0x00000040 #define GL_PIXEL_BUFFER_BARRIER_BIT_EXT 0x00000080 #define GL_TEXTURE_UPDATE_BARRIER_BIT_EXT 0x00000100 #define GL_BUFFER_UPDATE_BARRIER_BIT_EXT 0x00000200 #define GL_FRAMEBUFFER_BARRIER_BIT_EXT 0x00000400 #define GL_TRANSFORM_FEEDBACK_BARRIER_BIT_EXT 0x00000800 #define GL_ATOMIC_COUNTER_BARRIER_BIT_EXT 0x00001000 #define GL_ALL_BARRIER_BITS_EXT 0xFFFFFFFF typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREEXTPROC) (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); typedef void (APIENTRYP PFNGLMEMORYBARRIEREXTPROC) (GLbitfield barriers); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBindImageTextureEXT (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); GLAPI void APIENTRY glMemoryBarrierEXT (GLbitfield barriers); #endif #endif /* GL_EXT_shader_image_load_store */ #ifndef GL_EXT_shader_integer_mix #define GL_EXT_shader_integer_mix 1 #endif /* GL_EXT_shader_integer_mix */ #ifndef GL_EXT_shadow_funcs #define GL_EXT_shadow_funcs 1 #endif /* GL_EXT_shadow_funcs */ #ifndef GL_EXT_shared_texture_palette #define GL_EXT_shared_texture_palette 1 #define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB #endif /* GL_EXT_shared_texture_palette */ #ifndef GL_EXT_sparse_texture2 #define GL_EXT_sparse_texture2 1 #endif /* GL_EXT_sparse_texture2 */ #ifndef GL_EXT_stencil_clear_tag #define GL_EXT_stencil_clear_tag 1 #define GL_STENCIL_TAG_BITS_EXT 0x88F2 #define GL_STENCIL_CLEAR_TAG_VALUE_EXT 0x88F3 typedef void (APIENTRYP PFNGLSTENCILCLEARTAGEXTPROC) (GLsizei stencilTagBits, GLuint stencilClearTag); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glStencilClearTagEXT (GLsizei stencilTagBits, GLuint stencilClearTag); #endif #endif /* GL_EXT_stencil_clear_tag */ #ifndef GL_EXT_stencil_two_side #define GL_EXT_stencil_two_side 1 #define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 #define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 typedef void (APIENTRYP PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glActiveStencilFaceEXT (GLenum face); #endif #endif /* GL_EXT_stencil_two_side */ #ifndef GL_EXT_stencil_wrap #define GL_EXT_stencil_wrap 1 #define GL_INCR_WRAP_EXT 0x8507 #define GL_DECR_WRAP_EXT 0x8508 #endif /* GL_EXT_stencil_wrap */ #ifndef GL_EXT_subtexture #define GL_EXT_subtexture 1 typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); #endif #endif /* GL_EXT_subtexture */ #ifndef GL_EXT_texture #define GL_EXT_texture 1 #define GL_ALPHA4_EXT 0x803B #define GL_ALPHA8_EXT 0x803C #define GL_ALPHA12_EXT 0x803D #define GL_ALPHA16_EXT 0x803E #define GL_LUMINANCE4_EXT 0x803F #define GL_LUMINANCE8_EXT 0x8040 #define GL_LUMINANCE12_EXT 0x8041 #define GL_LUMINANCE16_EXT 0x8042 #define GL_LUMINANCE4_ALPHA4_EXT 0x8043 #define GL_LUMINANCE6_ALPHA2_EXT 0x8044 #define GL_LUMINANCE8_ALPHA8_EXT 0x8045 #define GL_LUMINANCE12_ALPHA4_EXT 0x8046 #define GL_LUMINANCE12_ALPHA12_EXT 0x8047 #define GL_LUMINANCE16_ALPHA16_EXT 0x8048 #define GL_INTENSITY_EXT 0x8049 #define GL_INTENSITY4_EXT 0x804A #define GL_INTENSITY8_EXT 0x804B #define GL_INTENSITY12_EXT 0x804C #define GL_INTENSITY16_EXT 0x804D #define GL_RGB2_EXT 0x804E #define GL_RGB4_EXT 0x804F #define GL_RGB5_EXT 0x8050 #define GL_RGB8_EXT 0x8051 #define GL_RGB10_EXT 0x8052 #define GL_RGB12_EXT 0x8053 #define GL_RGB16_EXT 0x8054 #define GL_RGBA2_EXT 0x8055 #define GL_RGBA4_EXT 0x8056 #define GL_RGB5_A1_EXT 0x8057 #define GL_RGBA8_EXT 0x8058 #define GL_RGB10_A2_EXT 0x8059 #define GL_RGBA12_EXT 0x805A #define GL_RGBA16_EXT 0x805B #define GL_TEXTURE_RED_SIZE_EXT 0x805C #define GL_TEXTURE_GREEN_SIZE_EXT 0x805D #define GL_TEXTURE_BLUE_SIZE_EXT 0x805E #define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F #define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 #define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 #define GL_REPLACE_EXT 0x8062 #define GL_PROXY_TEXTURE_1D_EXT 0x8063 #define GL_PROXY_TEXTURE_2D_EXT 0x8064 #define GL_TEXTURE_TOO_LARGE_EXT 0x8065 #endif /* GL_EXT_texture */ #ifndef GL_EXT_texture3D #define GL_EXT_texture3D 1 #define GL_PACK_SKIP_IMAGES_EXT 0x806B #define GL_PACK_IMAGE_HEIGHT_EXT 0x806C #define GL_UNPACK_SKIP_IMAGES_EXT 0x806D #define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E #define GL_TEXTURE_3D_EXT 0x806F #define GL_PROXY_TEXTURE_3D_EXT 0x8070 #define GL_TEXTURE_DEPTH_EXT 0x8071 #define GL_TEXTURE_WRAP_R_EXT 0x8072 #define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 typedef void (APIENTRYP PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexImage3DEXT (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); #endif #endif /* GL_EXT_texture3D */ #ifndef GL_EXT_texture_array #define GL_EXT_texture_array 1 #define GL_TEXTURE_1D_ARRAY_EXT 0x8C18 #define GL_PROXY_TEXTURE_1D_ARRAY_EXT 0x8C19 #define GL_TEXTURE_2D_ARRAY_EXT 0x8C1A #define GL_PROXY_TEXTURE_2D_ARRAY_EXT 0x8C1B #define GL_TEXTURE_BINDING_1D_ARRAY_EXT 0x8C1C #define GL_TEXTURE_BINDING_2D_ARRAY_EXT 0x8C1D #define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT 0x88FF #define GL_COMPARE_REF_DEPTH_TO_TEXTURE_EXT 0x884E typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFramebufferTextureLayerEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); #endif #endif /* GL_EXT_texture_array */ #ifndef GL_EXT_texture_buffer_object #define GL_EXT_texture_buffer_object 1 #define GL_TEXTURE_BUFFER_EXT 0x8C2A #define GL_MAX_TEXTURE_BUFFER_SIZE_EXT 0x8C2B #define GL_TEXTURE_BINDING_BUFFER_EXT 0x8C2C #define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_EXT 0x8C2D #define GL_TEXTURE_BUFFER_FORMAT_EXT 0x8C2E typedef void (APIENTRYP PFNGLTEXBUFFEREXTPROC) (GLenum target, GLenum internalformat, GLuint buffer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexBufferEXT (GLenum target, GLenum internalformat, GLuint buffer); #endif #endif /* GL_EXT_texture_buffer_object */ #ifndef GL_EXT_texture_compression_latc #define GL_EXT_texture_compression_latc 1 #define GL_COMPRESSED_LUMINANCE_LATC1_EXT 0x8C70 #define GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT 0x8C71 #define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT 0x8C72 #define GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT 0x8C73 #endif /* GL_EXT_texture_compression_latc */ #ifndef GL_EXT_texture_compression_rgtc #define GL_EXT_texture_compression_rgtc 1 #define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB #define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC #define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD #define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE #endif /* GL_EXT_texture_compression_rgtc */ #ifndef GL_EXT_texture_compression_s3tc #define GL_EXT_texture_compression_s3tc 1 #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 #endif /* GL_EXT_texture_compression_s3tc */ #ifndef GL_EXT_texture_cube_map #define GL_EXT_texture_cube_map 1 #define GL_NORMAL_MAP_EXT 0x8511 #define GL_REFLECTION_MAP_EXT 0x8512 #define GL_TEXTURE_CUBE_MAP_EXT 0x8513 #define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 #define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A #define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B #define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C #endif /* GL_EXT_texture_cube_map */ #ifndef GL_EXT_texture_env_add #define GL_EXT_texture_env_add 1 #endif /* GL_EXT_texture_env_add */ #ifndef GL_EXT_texture_env_combine #define GL_EXT_texture_env_combine 1 #define GL_COMBINE_EXT 0x8570 #define GL_COMBINE_RGB_EXT 0x8571 #define GL_COMBINE_ALPHA_EXT 0x8572 #define GL_RGB_SCALE_EXT 0x8573 #define GL_ADD_SIGNED_EXT 0x8574 #define GL_INTERPOLATE_EXT 0x8575 #define GL_CONSTANT_EXT 0x8576 #define GL_PRIMARY_COLOR_EXT 0x8577 #define GL_PREVIOUS_EXT 0x8578 #define GL_SOURCE0_RGB_EXT 0x8580 #define GL_SOURCE1_RGB_EXT 0x8581 #define GL_SOURCE2_RGB_EXT 0x8582 #define GL_SOURCE0_ALPHA_EXT 0x8588 #define GL_SOURCE1_ALPHA_EXT 0x8589 #define GL_SOURCE2_ALPHA_EXT 0x858A #define GL_OPERAND0_RGB_EXT 0x8590 #define GL_OPERAND1_RGB_EXT 0x8591 #define GL_OPERAND2_RGB_EXT 0x8592 #define GL_OPERAND0_ALPHA_EXT 0x8598 #define GL_OPERAND1_ALPHA_EXT 0x8599 #define GL_OPERAND2_ALPHA_EXT 0x859A #endif /* GL_EXT_texture_env_combine */ #ifndef GL_EXT_texture_env_dot3 #define GL_EXT_texture_env_dot3 1 #define GL_DOT3_RGB_EXT 0x8740 #define GL_DOT3_RGBA_EXT 0x8741 #endif /* GL_EXT_texture_env_dot3 */ #ifndef GL_EXT_texture_filter_anisotropic #define GL_EXT_texture_filter_anisotropic 1 #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF #endif /* GL_EXT_texture_filter_anisotropic */ #ifndef GL_EXT_texture_filter_minmax #define GL_EXT_texture_filter_minmax 1 #define GL_TEXTURE_REDUCTION_MODE_EXT 0x9366 #define GL_WEIGHTED_AVERAGE_EXT 0x9367 #endif /* GL_EXT_texture_filter_minmax */ #ifndef GL_EXT_texture_integer #define GL_EXT_texture_integer 1 #define GL_RGBA32UI_EXT 0x8D70 #define GL_RGB32UI_EXT 0x8D71 #define GL_ALPHA32UI_EXT 0x8D72 #define GL_INTENSITY32UI_EXT 0x8D73 #define GL_LUMINANCE32UI_EXT 0x8D74 #define GL_LUMINANCE_ALPHA32UI_EXT 0x8D75 #define GL_RGBA16UI_EXT 0x8D76 #define GL_RGB16UI_EXT 0x8D77 #define GL_ALPHA16UI_EXT 0x8D78 #define GL_INTENSITY16UI_EXT 0x8D79 #define GL_LUMINANCE16UI_EXT 0x8D7A #define GL_LUMINANCE_ALPHA16UI_EXT 0x8D7B #define GL_RGBA8UI_EXT 0x8D7C #define GL_RGB8UI_EXT 0x8D7D #define GL_ALPHA8UI_EXT 0x8D7E #define GL_INTENSITY8UI_EXT 0x8D7F #define GL_LUMINANCE8UI_EXT 0x8D80 #define GL_LUMINANCE_ALPHA8UI_EXT 0x8D81 #define GL_RGBA32I_EXT 0x8D82 #define GL_RGB32I_EXT 0x8D83 #define GL_ALPHA32I_EXT 0x8D84 #define GL_INTENSITY32I_EXT 0x8D85 #define GL_LUMINANCE32I_EXT 0x8D86 #define GL_LUMINANCE_ALPHA32I_EXT 0x8D87 #define GL_RGBA16I_EXT 0x8D88 #define GL_RGB16I_EXT 0x8D89 #define GL_ALPHA16I_EXT 0x8D8A #define GL_INTENSITY16I_EXT 0x8D8B #define GL_LUMINANCE16I_EXT 0x8D8C #define GL_LUMINANCE_ALPHA16I_EXT 0x8D8D #define GL_RGBA8I_EXT 0x8D8E #define GL_RGB8I_EXT 0x8D8F #define GL_ALPHA8I_EXT 0x8D90 #define GL_INTENSITY8I_EXT 0x8D91 #define GL_LUMINANCE8I_EXT 0x8D92 #define GL_LUMINANCE_ALPHA8I_EXT 0x8D93 #define GL_RED_INTEGER_EXT 0x8D94 #define GL_GREEN_INTEGER_EXT 0x8D95 #define GL_BLUE_INTEGER_EXT 0x8D96 #define GL_ALPHA_INTEGER_EXT 0x8D97 #define GL_RGB_INTEGER_EXT 0x8D98 #define GL_RGBA_INTEGER_EXT 0x8D99 #define GL_BGR_INTEGER_EXT 0x8D9A #define GL_BGRA_INTEGER_EXT 0x8D9B #define GL_LUMINANCE_INTEGER_EXT 0x8D9C #define GL_LUMINANCE_ALPHA_INTEGER_EXT 0x8D9D #define GL_RGBA_INTEGER_MODE_EXT 0x8D9E typedef void (APIENTRYP PFNGLTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, const GLuint *params); typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLCLEARCOLORIIEXTPROC) (GLint red, GLint green, GLint blue, GLint alpha); typedef void (APIENTRYP PFNGLCLEARCOLORIUIEXTPROC) (GLuint red, GLuint green, GLuint blue, GLuint alpha); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexParameterIivEXT (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glTexParameterIuivEXT (GLenum target, GLenum pname, const GLuint *params); GLAPI void APIENTRY glGetTexParameterIivEXT (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetTexParameterIuivEXT (GLenum target, GLenum pname, GLuint *params); GLAPI void APIENTRY glClearColorIiEXT (GLint red, GLint green, GLint blue, GLint alpha); GLAPI void APIENTRY glClearColorIuiEXT (GLuint red, GLuint green, GLuint blue, GLuint alpha); #endif #endif /* GL_EXT_texture_integer */ #ifndef GL_EXT_texture_lod_bias #define GL_EXT_texture_lod_bias 1 #define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD #define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 #define GL_TEXTURE_LOD_BIAS_EXT 0x8501 #endif /* GL_EXT_texture_lod_bias */ #ifndef GL_EXT_texture_mirror_clamp #define GL_EXT_texture_mirror_clamp 1 #define GL_MIRROR_CLAMP_EXT 0x8742 #define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 #define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 #endif /* GL_EXT_texture_mirror_clamp */ #ifndef GL_EXT_texture_object #define GL_EXT_texture_object 1 #define GL_TEXTURE_PRIORITY_EXT 0x8066 #define GL_TEXTURE_RESIDENT_EXT 0x8067 #define GL_TEXTURE_1D_BINDING_EXT 0x8068 #define GL_TEXTURE_2D_BINDING_EXT 0x8069 #define GL_TEXTURE_3D_BINDING_EXT 0x806A typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); typedef void (APIENTRYP PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); typedef void (APIENTRYP PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); typedef void (APIENTRYP PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); typedef GLboolean (APIENTRYP PFNGLISTEXTUREEXTPROC) (GLuint texture); typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei n, const GLuint *textures, GLboolean *residences); GLAPI void APIENTRY glBindTextureEXT (GLenum target, GLuint texture); GLAPI void APIENTRY glDeleteTexturesEXT (GLsizei n, const GLuint *textures); GLAPI void APIENTRY glGenTexturesEXT (GLsizei n, GLuint *textures); GLAPI GLboolean APIENTRY glIsTextureEXT (GLuint texture); GLAPI void APIENTRY glPrioritizeTexturesEXT (GLsizei n, const GLuint *textures, const GLclampf *priorities); #endif #endif /* GL_EXT_texture_object */ #ifndef GL_EXT_texture_perturb_normal #define GL_EXT_texture_perturb_normal 1 #define GL_PERTURB_EXT 0x85AE #define GL_TEXTURE_NORMAL_EXT 0x85AF typedef void (APIENTRYP PFNGLTEXTURENORMALEXTPROC) (GLenum mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTextureNormalEXT (GLenum mode); #endif #endif /* GL_EXT_texture_perturb_normal */ #ifndef GL_EXT_texture_sRGB #define GL_EXT_texture_sRGB 1 #define GL_SRGB_EXT 0x8C40 #define GL_SRGB8_EXT 0x8C41 #define GL_SRGB_ALPHA_EXT 0x8C42 #define GL_SRGB8_ALPHA8_EXT 0x8C43 #define GL_SLUMINANCE_ALPHA_EXT 0x8C44 #define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 #define GL_SLUMINANCE_EXT 0x8C46 #define GL_SLUMINANCE8_EXT 0x8C47 #define GL_COMPRESSED_SRGB_EXT 0x8C48 #define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 #define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A #define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B #define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F #endif /* GL_EXT_texture_sRGB */ #ifndef GL_EXT_texture_sRGB_R8 #define GL_EXT_texture_sRGB_R8 1 #define GL_SR8_EXT 0x8FBD #endif /* GL_EXT_texture_sRGB_R8 */ #ifndef GL_EXT_texture_sRGB_RG8 #define GL_EXT_texture_sRGB_RG8 1 #define GL_SRG8_EXT 0x8FBE #endif /* GL_EXT_texture_sRGB_RG8 */ #ifndef GL_EXT_texture_sRGB_decode #define GL_EXT_texture_sRGB_decode 1 #define GL_TEXTURE_SRGB_DECODE_EXT 0x8A48 #define GL_DECODE_EXT 0x8A49 #define GL_SKIP_DECODE_EXT 0x8A4A #endif /* GL_EXT_texture_sRGB_decode */ #ifndef GL_EXT_texture_shadow_lod #define GL_EXT_texture_shadow_lod 1 #endif /* GL_EXT_texture_shadow_lod */ #ifndef GL_EXT_texture_shared_exponent #define GL_EXT_texture_shared_exponent 1 #define GL_RGB9_E5_EXT 0x8C3D #define GL_UNSIGNED_INT_5_9_9_9_REV_EXT 0x8C3E #define GL_TEXTURE_SHARED_SIZE_EXT 0x8C3F #endif /* GL_EXT_texture_shared_exponent */ #ifndef GL_EXT_texture_snorm #define GL_EXT_texture_snorm 1 #define GL_ALPHA_SNORM 0x9010 #define GL_LUMINANCE_SNORM 0x9011 #define GL_LUMINANCE_ALPHA_SNORM 0x9012 #define GL_INTENSITY_SNORM 0x9013 #define GL_ALPHA8_SNORM 0x9014 #define GL_LUMINANCE8_SNORM 0x9015 #define GL_LUMINANCE8_ALPHA8_SNORM 0x9016 #define GL_INTENSITY8_SNORM 0x9017 #define GL_ALPHA16_SNORM 0x9018 #define GL_LUMINANCE16_SNORM 0x9019 #define GL_LUMINANCE16_ALPHA16_SNORM 0x901A #define GL_INTENSITY16_SNORM 0x901B #define GL_RED_SNORM 0x8F90 #define GL_RG_SNORM 0x8F91 #define GL_RGB_SNORM 0x8F92 #define GL_RGBA_SNORM 0x8F93 #endif /* GL_EXT_texture_snorm */ #ifndef GL_EXT_texture_storage #define GL_EXT_texture_storage 1 #define GL_TEXTURE_IMMUTABLE_FORMAT_EXT 0x912F #define GL_RGBA32F_EXT 0x8814 #define GL_RGB32F_EXT 0x8815 #define GL_ALPHA32F_EXT 0x8816 #define GL_LUMINANCE32F_EXT 0x8818 #define GL_LUMINANCE_ALPHA32F_EXT 0x8819 #define GL_RGBA16F_EXT 0x881A #define GL_RGB16F_EXT 0x881B #define GL_ALPHA16F_EXT 0x881C #define GL_LUMINANCE16F_EXT 0x881E #define GL_LUMINANCE_ALPHA16F_EXT 0x881F #define GL_BGRA8_EXT 0x93A1 #define GL_R8_EXT 0x8229 #define GL_RG8_EXT 0x822B #define GL_R32F_EXT 0x822E #define GL_RG32F_EXT 0x8230 #define GL_R16F_EXT 0x822D #define GL_RG16F_EXT 0x822F typedef void (APIENTRYP PFNGLTEXSTORAGE1DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); typedef void (APIENTRYP PFNGLTEXSTORAGE2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLTEXSTORAGE3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexStorage1DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); GLAPI void APIENTRY glTexStorage2DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); GLAPI void APIENTRY glTexStorage3DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); #endif #endif /* GL_EXT_texture_storage */ #ifndef GL_EXT_texture_swizzle #define GL_EXT_texture_swizzle 1 #define GL_TEXTURE_SWIZZLE_R_EXT 0x8E42 #define GL_TEXTURE_SWIZZLE_G_EXT 0x8E43 #define GL_TEXTURE_SWIZZLE_B_EXT 0x8E44 #define GL_TEXTURE_SWIZZLE_A_EXT 0x8E45 #define GL_TEXTURE_SWIZZLE_RGBA_EXT 0x8E46 #endif /* GL_EXT_texture_swizzle */ #ifndef GL_EXT_timer_query #define GL_EXT_timer_query 1 #define GL_TIME_ELAPSED_EXT 0x88BF typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VEXTPROC) (GLuint id, GLenum pname, GLint64 *params); typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VEXTPROC) (GLuint id, GLenum pname, GLuint64 *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGetQueryObjecti64vEXT (GLuint id, GLenum pname, GLint64 *params); GLAPI void APIENTRY glGetQueryObjectui64vEXT (GLuint id, GLenum pname, GLuint64 *params); #endif #endif /* GL_EXT_timer_query */ #ifndef GL_EXT_transform_feedback #define GL_EXT_transform_feedback 1 #define GL_TRANSFORM_FEEDBACK_BUFFER_EXT 0x8C8E #define GL_TRANSFORM_FEEDBACK_BUFFER_START_EXT 0x8C84 #define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_EXT 0x8C85 #define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_EXT 0x8C8F #define GL_INTERLEAVED_ATTRIBS_EXT 0x8C8C #define GL_SEPARATE_ATTRIBS_EXT 0x8C8D #define GL_PRIMITIVES_GENERATED_EXT 0x8C87 #define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT 0x8C88 #define GL_RASTERIZER_DISCARD_EXT 0x8C89 #define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT 0x8C8A #define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT 0x8C8B #define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT 0x8C80 #define GL_TRANSFORM_FEEDBACK_VARYINGS_EXT 0x8C83 #define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_EXT 0x8C7F #define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH_EXT 0x8C76 typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKEXTPROC) (GLenum primitiveMode); typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKEXTPROC) (void); typedef void (APIENTRYP PFNGLBINDBUFFERRANGEEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); typedef void (APIENTRYP PFNGLBINDBUFFERBASEEXTPROC) (GLenum target, GLuint index, GLuint buffer); typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC) (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBeginTransformFeedbackEXT (GLenum primitiveMode); GLAPI void APIENTRY glEndTransformFeedbackEXT (void); GLAPI void APIENTRY glBindBufferRangeEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); GLAPI void APIENTRY glBindBufferOffsetEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset); GLAPI void APIENTRY glBindBufferBaseEXT (GLenum target, GLuint index, GLuint buffer); GLAPI void APIENTRY glTransformFeedbackVaryingsEXT (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); GLAPI void APIENTRY glGetTransformFeedbackVaryingEXT (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); #endif #endif /* GL_EXT_transform_feedback */ #ifndef GL_EXT_vertex_array #define GL_EXT_vertex_array 1 #define GL_VERTEX_ARRAY_EXT 0x8074 #define GL_NORMAL_ARRAY_EXT 0x8075 #define GL_COLOR_ARRAY_EXT 0x8076 #define GL_INDEX_ARRAY_EXT 0x8077 #define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 #define GL_EDGE_FLAG_ARRAY_EXT 0x8079 #define GL_VERTEX_ARRAY_SIZE_EXT 0x807A #define GL_VERTEX_ARRAY_TYPE_EXT 0x807B #define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C #define GL_VERTEX_ARRAY_COUNT_EXT 0x807D #define GL_NORMAL_ARRAY_TYPE_EXT 0x807E #define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F #define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 #define GL_COLOR_ARRAY_SIZE_EXT 0x8081 #define GL_COLOR_ARRAY_TYPE_EXT 0x8082 #define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 #define GL_COLOR_ARRAY_COUNT_EXT 0x8084 #define GL_INDEX_ARRAY_TYPE_EXT 0x8085 #define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 #define GL_INDEX_ARRAY_COUNT_EXT 0x8087 #define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 #define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 #define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A #define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B #define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C #define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D #define GL_VERTEX_ARRAY_POINTER_EXT 0x808E #define GL_NORMAL_ARRAY_POINTER_EXT 0x808F #define GL_COLOR_ARRAY_POINTER_EXT 0x8090 #define GL_INDEX_ARRAY_POINTER_EXT 0x8091 #define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 #define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 typedef void (APIENTRYP PFNGLARRAYELEMENTEXTPROC) (GLint i); typedef void (APIENTRYP PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); typedef void (APIENTRYP PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); typedef void (APIENTRYP PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); typedef void (APIENTRYP PFNGLGETPOINTERVEXTPROC) (GLenum pname, void **params); typedef void (APIENTRYP PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const void *pointer); typedef void (APIENTRYP PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const void *pointer); typedef void (APIENTRYP PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); typedef void (APIENTRYP PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glArrayElementEXT (GLint i); GLAPI void APIENTRY glColorPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); GLAPI void APIENTRY glDrawArraysEXT (GLenum mode, GLint first, GLsizei count); GLAPI void APIENTRY glEdgeFlagPointerEXT (GLsizei stride, GLsizei count, const GLboolean *pointer); GLAPI void APIENTRY glGetPointervEXT (GLenum pname, void **params); GLAPI void APIENTRY glIndexPointerEXT (GLenum type, GLsizei stride, GLsizei count, const void *pointer); GLAPI void APIENTRY glNormalPointerEXT (GLenum type, GLsizei stride, GLsizei count, const void *pointer); GLAPI void APIENTRY glTexCoordPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); GLAPI void APIENTRY glVertexPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); #endif #endif /* GL_EXT_vertex_array */ #ifndef GL_EXT_vertex_array_bgra #define GL_EXT_vertex_array_bgra 1 #endif /* GL_EXT_vertex_array_bgra */ #ifndef GL_EXT_vertex_attrib_64bit #define GL_EXT_vertex_attrib_64bit 1 #define GL_DOUBLE_VEC2_EXT 0x8FFC #define GL_DOUBLE_VEC3_EXT 0x8FFD #define GL_DOUBLE_VEC4_EXT 0x8FFE #define GL_DOUBLE_MAT2_EXT 0x8F46 #define GL_DOUBLE_MAT3_EXT 0x8F47 #define GL_DOUBLE_MAT4_EXT 0x8F48 #define GL_DOUBLE_MAT2x3_EXT 0x8F49 #define GL_DOUBLE_MAT2x4_EXT 0x8F4A #define GL_DOUBLE_MAT3x2_EXT 0x8F4B #define GL_DOUBLE_MAT3x4_EXT 0x8F4C #define GL_DOUBLE_MAT4x2_EXT 0x8F4D #define GL_DOUBLE_MAT4x3_EXT 0x8F4E typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DEXTPROC) (GLuint index, GLdouble x); typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DEXTPROC) (GLuint index, GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVEXTPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVEXTPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVEXTPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVEXTPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVEXTPROC) (GLuint index, GLenum pname, GLdouble *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertexAttribL1dEXT (GLuint index, GLdouble x); GLAPI void APIENTRY glVertexAttribL2dEXT (GLuint index, GLdouble x, GLdouble y); GLAPI void APIENTRY glVertexAttribL3dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glVertexAttribL4dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glVertexAttribL1dvEXT (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttribL2dvEXT (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttribL3dvEXT (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttribL4dvEXT (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttribLPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glGetVertexAttribLdvEXT (GLuint index, GLenum pname, GLdouble *params); #endif #endif /* GL_EXT_vertex_attrib_64bit */ #ifndef GL_EXT_vertex_shader #define GL_EXT_vertex_shader 1 #define GL_VERTEX_SHADER_EXT 0x8780 #define GL_VERTEX_SHADER_BINDING_EXT 0x8781 #define GL_OP_INDEX_EXT 0x8782 #define GL_OP_NEGATE_EXT 0x8783 #define GL_OP_DOT3_EXT 0x8784 #define GL_OP_DOT4_EXT 0x8785 #define GL_OP_MUL_EXT 0x8786 #define GL_OP_ADD_EXT 0x8787 #define GL_OP_MADD_EXT 0x8788 #define GL_OP_FRAC_EXT 0x8789 #define GL_OP_MAX_EXT 0x878A #define GL_OP_MIN_EXT 0x878B #define GL_OP_SET_GE_EXT 0x878C #define GL_OP_SET_LT_EXT 0x878D #define GL_OP_CLAMP_EXT 0x878E #define GL_OP_FLOOR_EXT 0x878F #define GL_OP_ROUND_EXT 0x8790 #define GL_OP_EXP_BASE_2_EXT 0x8791 #define GL_OP_LOG_BASE_2_EXT 0x8792 #define GL_OP_POWER_EXT 0x8793 #define GL_OP_RECIP_EXT 0x8794 #define GL_OP_RECIP_SQRT_EXT 0x8795 #define GL_OP_SUB_EXT 0x8796 #define GL_OP_CROSS_PRODUCT_EXT 0x8797 #define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 #define GL_OP_MOV_EXT 0x8799 #define GL_OUTPUT_VERTEX_EXT 0x879A #define GL_OUTPUT_COLOR0_EXT 0x879B #define GL_OUTPUT_COLOR1_EXT 0x879C #define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D #define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E #define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F #define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 #define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 #define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 #define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 #define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 #define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 #define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 #define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 #define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 #define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 #define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA #define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB #define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC #define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD #define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE #define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF #define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 #define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 #define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 #define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 #define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 #define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 #define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 #define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 #define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 #define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 #define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA #define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB #define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC #define GL_OUTPUT_FOG_EXT 0x87BD #define GL_SCALAR_EXT 0x87BE #define GL_VECTOR_EXT 0x87BF #define GL_MATRIX_EXT 0x87C0 #define GL_VARIANT_EXT 0x87C1 #define GL_INVARIANT_EXT 0x87C2 #define GL_LOCAL_CONSTANT_EXT 0x87C3 #define GL_LOCAL_EXT 0x87C4 #define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 #define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 #define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 #define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 #define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 #define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA #define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB #define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CC #define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CD #define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE #define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF #define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 #define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 #define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 #define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 #define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 #define GL_X_EXT 0x87D5 #define GL_Y_EXT 0x87D6 #define GL_Z_EXT 0x87D7 #define GL_W_EXT 0x87D8 #define GL_NEGATIVE_X_EXT 0x87D9 #define GL_NEGATIVE_Y_EXT 0x87DA #define GL_NEGATIVE_Z_EXT 0x87DB #define GL_NEGATIVE_W_EXT 0x87DC #define GL_ZERO_EXT 0x87DD #define GL_ONE_EXT 0x87DE #define GL_NEGATIVE_ONE_EXT 0x87DF #define GL_NORMALIZED_RANGE_EXT 0x87E0 #define GL_FULL_RANGE_EXT 0x87E1 #define GL_CURRENT_VERTEX_EXT 0x87E2 #define GL_MVP_MATRIX_EXT 0x87E3 #define GL_VARIANT_VALUE_EXT 0x87E4 #define GL_VARIANT_DATATYPE_EXT 0x87E5 #define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 #define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 #define GL_VARIANT_ARRAY_EXT 0x87E8 #define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 #define GL_INVARIANT_VALUE_EXT 0x87EA #define GL_INVARIANT_DATATYPE_EXT 0x87EB #define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC #define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED typedef void (APIENTRYP PFNGLBEGINVERTEXSHADEREXTPROC) (void); typedef void (APIENTRYP PFNGLENDVERTEXSHADEREXTPROC) (void); typedef void (APIENTRYP PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); typedef GLuint (APIENTRYP PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); typedef void (APIENTRYP PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); typedef void (APIENTRYP PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); typedef void (APIENTRYP PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); typedef void (APIENTRYP PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); typedef void (APIENTRYP PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); typedef void (APIENTRYP PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); typedef void (APIENTRYP PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); typedef void (APIENTRYP PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); typedef GLuint (APIENTRYP PFNGLGENSYMBOLSEXTPROC) (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); typedef void (APIENTRYP PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, const void *addr); typedef void (APIENTRYP PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, const void *addr); typedef void (APIENTRYP PFNGLVARIANTBVEXTPROC) (GLuint id, const GLbyte *addr); typedef void (APIENTRYP PFNGLVARIANTSVEXTPROC) (GLuint id, const GLshort *addr); typedef void (APIENTRYP PFNGLVARIANTIVEXTPROC) (GLuint id, const GLint *addr); typedef void (APIENTRYP PFNGLVARIANTFVEXTPROC) (GLuint id, const GLfloat *addr); typedef void (APIENTRYP PFNGLVARIANTDVEXTPROC) (GLuint id, const GLdouble *addr); typedef void (APIENTRYP PFNGLVARIANTUBVEXTPROC) (GLuint id, const GLubyte *addr); typedef void (APIENTRYP PFNGLVARIANTUSVEXTPROC) (GLuint id, const GLushort *addr); typedef void (APIENTRYP PFNGLVARIANTUIVEXTPROC) (GLuint id, const GLuint *addr); typedef void (APIENTRYP PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, const void *addr); typedef void (APIENTRYP PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); typedef void (APIENTRYP PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); typedef GLuint (APIENTRYP PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); typedef GLuint (APIENTRYP PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); typedef GLuint (APIENTRYP PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); typedef GLuint (APIENTRYP PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); typedef GLuint (APIENTRYP PFNGLBINDPARAMETEREXTPROC) (GLenum value); typedef GLboolean (APIENTRYP PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); typedef void (APIENTRYP PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); typedef void (APIENTRYP PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); typedef void (APIENTRYP PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); typedef void (APIENTRYP PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, void **data); typedef void (APIENTRYP PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); typedef void (APIENTRYP PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); typedef void (APIENTRYP PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); typedef void (APIENTRYP PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); typedef void (APIENTRYP PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); typedef void (APIENTRYP PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBeginVertexShaderEXT (void); GLAPI void APIENTRY glEndVertexShaderEXT (void); GLAPI void APIENTRY glBindVertexShaderEXT (GLuint id); GLAPI GLuint APIENTRY glGenVertexShadersEXT (GLuint range); GLAPI void APIENTRY glDeleteVertexShaderEXT (GLuint id); GLAPI void APIENTRY glShaderOp1EXT (GLenum op, GLuint res, GLuint arg1); GLAPI void APIENTRY glShaderOp2EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2); GLAPI void APIENTRY glShaderOp3EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); GLAPI void APIENTRY glSwizzleEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); GLAPI void APIENTRY glWriteMaskEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); GLAPI void APIENTRY glInsertComponentEXT (GLuint res, GLuint src, GLuint num); GLAPI void APIENTRY glExtractComponentEXT (GLuint res, GLuint src, GLuint num); GLAPI GLuint APIENTRY glGenSymbolsEXT (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); GLAPI void APIENTRY glSetInvariantEXT (GLuint id, GLenum type, const void *addr); GLAPI void APIENTRY glSetLocalConstantEXT (GLuint id, GLenum type, const void *addr); GLAPI void APIENTRY glVariantbvEXT (GLuint id, const GLbyte *addr); GLAPI void APIENTRY glVariantsvEXT (GLuint id, const GLshort *addr); GLAPI void APIENTRY glVariantivEXT (GLuint id, const GLint *addr); GLAPI void APIENTRY glVariantfvEXT (GLuint id, const GLfloat *addr); GLAPI void APIENTRY glVariantdvEXT (GLuint id, const GLdouble *addr); GLAPI void APIENTRY glVariantubvEXT (GLuint id, const GLubyte *addr); GLAPI void APIENTRY glVariantusvEXT (GLuint id, const GLushort *addr); GLAPI void APIENTRY glVariantuivEXT (GLuint id, const GLuint *addr); GLAPI void APIENTRY glVariantPointerEXT (GLuint id, GLenum type, GLuint stride, const void *addr); GLAPI void APIENTRY glEnableVariantClientStateEXT (GLuint id); GLAPI void APIENTRY glDisableVariantClientStateEXT (GLuint id); GLAPI GLuint APIENTRY glBindLightParameterEXT (GLenum light, GLenum value); GLAPI GLuint APIENTRY glBindMaterialParameterEXT (GLenum face, GLenum value); GLAPI GLuint APIENTRY glBindTexGenParameterEXT (GLenum unit, GLenum coord, GLenum value); GLAPI GLuint APIENTRY glBindTextureUnitParameterEXT (GLenum unit, GLenum value); GLAPI GLuint APIENTRY glBindParameterEXT (GLenum value); GLAPI GLboolean APIENTRY glIsVariantEnabledEXT (GLuint id, GLenum cap); GLAPI void APIENTRY glGetVariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); GLAPI void APIENTRY glGetVariantIntegervEXT (GLuint id, GLenum value, GLint *data); GLAPI void APIENTRY glGetVariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); GLAPI void APIENTRY glGetVariantPointervEXT (GLuint id, GLenum value, void **data); GLAPI void APIENTRY glGetInvariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); GLAPI void APIENTRY glGetInvariantIntegervEXT (GLuint id, GLenum value, GLint *data); GLAPI void APIENTRY glGetInvariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); GLAPI void APIENTRY glGetLocalConstantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); GLAPI void APIENTRY glGetLocalConstantIntegervEXT (GLuint id, GLenum value, GLint *data); GLAPI void APIENTRY glGetLocalConstantFloatvEXT (GLuint id, GLenum value, GLfloat *data); #endif #endif /* GL_EXT_vertex_shader */ #ifndef GL_EXT_vertex_weighting #define GL_EXT_vertex_weighting 1 #define GL_MODELVIEW0_STACK_DEPTH_EXT 0x0BA3 #define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 #define GL_MODELVIEW0_MATRIX_EXT 0x0BA6 #define GL_MODELVIEW1_MATRIX_EXT 0x8506 #define GL_VERTEX_WEIGHTING_EXT 0x8509 #define GL_MODELVIEW0_EXT 0x1700 #define GL_MODELVIEW1_EXT 0x850A #define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B #define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C #define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D #define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E #define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F #define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 typedef void (APIENTRYP PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); typedef void (APIENTRYP PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); typedef void (APIENTRYP PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertexWeightfEXT (GLfloat weight); GLAPI void APIENTRY glVertexWeightfvEXT (const GLfloat *weight); GLAPI void APIENTRY glVertexWeightPointerEXT (GLint size, GLenum type, GLsizei stride, const void *pointer); #endif #endif /* GL_EXT_vertex_weighting */ #ifndef GL_EXT_win32_keyed_mutex #define GL_EXT_win32_keyed_mutex 1 typedef GLboolean (APIENTRYP PFNGLACQUIREKEYEDMUTEXWIN32EXTPROC) (GLuint memory, GLuint64 key, GLuint timeout); typedef GLboolean (APIENTRYP PFNGLRELEASEKEYEDMUTEXWIN32EXTPROC) (GLuint memory, GLuint64 key); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLboolean APIENTRY glAcquireKeyedMutexWin32EXT (GLuint memory, GLuint64 key, GLuint timeout); GLAPI GLboolean APIENTRY glReleaseKeyedMutexWin32EXT (GLuint memory, GLuint64 key); #endif #endif /* GL_EXT_win32_keyed_mutex */ #ifndef GL_EXT_window_rectangles #define GL_EXT_window_rectangles 1 #define GL_INCLUSIVE_EXT 0x8F10 #define GL_EXCLUSIVE_EXT 0x8F11 #define GL_WINDOW_RECTANGLE_EXT 0x8F12 #define GL_WINDOW_RECTANGLE_MODE_EXT 0x8F13 #define GL_MAX_WINDOW_RECTANGLES_EXT 0x8F14 #define GL_NUM_WINDOW_RECTANGLES_EXT 0x8F15 typedef void (APIENTRYP PFNGLWINDOWRECTANGLESEXTPROC) (GLenum mode, GLsizei count, const GLint *box); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glWindowRectanglesEXT (GLenum mode, GLsizei count, const GLint *box); #endif #endif /* GL_EXT_window_rectangles */ #ifndef GL_EXT_x11_sync_object #define GL_EXT_x11_sync_object 1 #define GL_SYNC_X11_FENCE_EXT 0x90E1 typedef GLsync (APIENTRYP PFNGLIMPORTSYNCEXTPROC) (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLsync APIENTRY glImportSyncEXT (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); #endif #endif /* GL_EXT_x11_sync_object */ #ifndef GL_GREMEDY_frame_terminator #define GL_GREMEDY_frame_terminator 1 typedef void (APIENTRYP PFNGLFRAMETERMINATORGREMEDYPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFrameTerminatorGREMEDY (void); #endif #endif /* GL_GREMEDY_frame_terminator */ #ifndef GL_GREMEDY_string_marker #define GL_GREMEDY_string_marker 1 typedef void (APIENTRYP PFNGLSTRINGMARKERGREMEDYPROC) (GLsizei len, const void *string); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glStringMarkerGREMEDY (GLsizei len, const void *string); #endif #endif /* GL_GREMEDY_string_marker */ #ifndef GL_HP_convolution_border_modes #define GL_HP_convolution_border_modes 1 #define GL_IGNORE_BORDER_HP 0x8150 #define GL_CONSTANT_BORDER_HP 0x8151 #define GL_REPLICATE_BORDER_HP 0x8153 #define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 #endif /* GL_HP_convolution_border_modes */ #ifndef GL_HP_image_transform #define GL_HP_image_transform 1 #define GL_IMAGE_SCALE_X_HP 0x8155 #define GL_IMAGE_SCALE_Y_HP 0x8156 #define GL_IMAGE_TRANSLATE_X_HP 0x8157 #define GL_IMAGE_TRANSLATE_Y_HP 0x8158 #define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 #define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A #define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B #define GL_IMAGE_MAG_FILTER_HP 0x815C #define GL_IMAGE_MIN_FILTER_HP 0x815D #define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E #define GL_CUBIC_HP 0x815F #define GL_AVERAGE_HP 0x8160 #define GL_IMAGE_TRANSFORM_2D_HP 0x8161 #define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 #define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glImageTransformParameteriHP (GLenum target, GLenum pname, GLint param); GLAPI void APIENTRY glImageTransformParameterfHP (GLenum target, GLenum pname, GLfloat param); GLAPI void APIENTRY glImageTransformParameterivHP (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glImageTransformParameterfvHP (GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glGetImageTransformParameterivHP (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetImageTransformParameterfvHP (GLenum target, GLenum pname, GLfloat *params); #endif #endif /* GL_HP_image_transform */ #ifndef GL_HP_occlusion_test #define GL_HP_occlusion_test 1 #define GL_OCCLUSION_TEST_HP 0x8165 #define GL_OCCLUSION_TEST_RESULT_HP 0x8166 #endif /* GL_HP_occlusion_test */ #ifndef GL_HP_texture_lighting #define GL_HP_texture_lighting 1 #define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 #define GL_TEXTURE_POST_SPECULAR_HP 0x8168 #define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 #endif /* GL_HP_texture_lighting */ #ifndef GL_IBM_cull_vertex #define GL_IBM_cull_vertex 1 #define GL_CULL_VERTEX_IBM 103050 #endif /* GL_IBM_cull_vertex */ #ifndef GL_IBM_multimode_draw_arrays #define GL_IBM_multimode_draw_arrays 1 typedef void (APIENTRYP PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); typedef void (APIENTRYP PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, GLint modestride); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMultiModeDrawArraysIBM (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); GLAPI void APIENTRY glMultiModeDrawElementsIBM (const GLenum *mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, GLint modestride); #endif #endif /* GL_IBM_multimode_draw_arrays */ #ifndef GL_IBM_rasterpos_clip #define GL_IBM_rasterpos_clip 1 #define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 #endif /* GL_IBM_rasterpos_clip */ #ifndef GL_IBM_static_data #define GL_IBM_static_data 1 #define GL_ALL_STATIC_DATA_IBM 103060 #define GL_STATIC_VERTEX_ARRAY_IBM 103061 typedef void (APIENTRYP PFNGLFLUSHSTATICDATAIBMPROC) (GLenum target); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFlushStaticDataIBM (GLenum target); #endif #endif /* GL_IBM_static_data */ #ifndef GL_IBM_texture_mirrored_repeat #define GL_IBM_texture_mirrored_repeat 1 #define GL_MIRRORED_REPEAT_IBM 0x8370 #endif /* GL_IBM_texture_mirrored_repeat */ #ifndef GL_IBM_vertex_array_lists #define GL_IBM_vertex_array_lists 1 #define GL_VERTEX_ARRAY_LIST_IBM 103070 #define GL_NORMAL_ARRAY_LIST_IBM 103071 #define GL_COLOR_ARRAY_LIST_IBM 103072 #define GL_INDEX_ARRAY_LIST_IBM 103073 #define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 #define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 #define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 #define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 #define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 #define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 #define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 #define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 #define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 #define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 #define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 #define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 typedef void (APIENTRYP PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean **pointer, GLint ptrstride); typedef void (APIENTRYP PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); typedef void (APIENTRYP PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); typedef void (APIENTRYP PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); typedef void (APIENTRYP PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); typedef void (APIENTRYP PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glColorPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); GLAPI void APIENTRY glSecondaryColorPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); GLAPI void APIENTRY glEdgeFlagPointerListIBM (GLint stride, const GLboolean **pointer, GLint ptrstride); GLAPI void APIENTRY glFogCoordPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); GLAPI void APIENTRY glIndexPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); GLAPI void APIENTRY glNormalPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); GLAPI void APIENTRY glTexCoordPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); GLAPI void APIENTRY glVertexPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); #endif #endif /* GL_IBM_vertex_array_lists */ #ifndef GL_INGR_blend_func_separate #define GL_INGR_blend_func_separate 1 typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINGRPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendFuncSeparateINGR (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); #endif #endif /* GL_INGR_blend_func_separate */ #ifndef GL_INGR_color_clamp #define GL_INGR_color_clamp 1 #define GL_RED_MIN_CLAMP_INGR 0x8560 #define GL_GREEN_MIN_CLAMP_INGR 0x8561 #define GL_BLUE_MIN_CLAMP_INGR 0x8562 #define GL_ALPHA_MIN_CLAMP_INGR 0x8563 #define GL_RED_MAX_CLAMP_INGR 0x8564 #define GL_GREEN_MAX_CLAMP_INGR 0x8565 #define GL_BLUE_MAX_CLAMP_INGR 0x8566 #define GL_ALPHA_MAX_CLAMP_INGR 0x8567 #endif /* GL_INGR_color_clamp */ #ifndef GL_INGR_interlace_read #define GL_INGR_interlace_read 1 #define GL_INTERLACE_READ_INGR 0x8568 #endif /* GL_INGR_interlace_read */ #ifndef GL_INTEL_blackhole_render #define GL_INTEL_blackhole_render 1 #define GL_BLACKHOLE_RENDER_INTEL 0x83FC #endif /* GL_INTEL_blackhole_render */ #ifndef GL_INTEL_conservative_rasterization #define GL_INTEL_conservative_rasterization 1 #define GL_CONSERVATIVE_RASTERIZATION_INTEL 0x83FE #endif /* GL_INTEL_conservative_rasterization */ #ifndef GL_INTEL_fragment_shader_ordering #define GL_INTEL_fragment_shader_ordering 1 #endif /* GL_INTEL_fragment_shader_ordering */ #ifndef GL_INTEL_framebuffer_CMAA #define GL_INTEL_framebuffer_CMAA 1 typedef void (APIENTRYP PFNGLAPPLYFRAMEBUFFERATTACHMENTCMAAINTELPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glApplyFramebufferAttachmentCMAAINTEL (void); #endif #endif /* GL_INTEL_framebuffer_CMAA */ #ifndef GL_INTEL_map_texture #define GL_INTEL_map_texture 1 #define GL_TEXTURE_MEMORY_LAYOUT_INTEL 0x83FF #define GL_LAYOUT_DEFAULT_INTEL 0 #define GL_LAYOUT_LINEAR_INTEL 1 #define GL_LAYOUT_LINEAR_CPU_CACHED_INTEL 2 typedef void (APIENTRYP PFNGLSYNCTEXTUREINTELPROC) (GLuint texture); typedef void (APIENTRYP PFNGLUNMAPTEXTURE2DINTELPROC) (GLuint texture, GLint level); typedef void *(APIENTRYP PFNGLMAPTEXTURE2DINTELPROC) (GLuint texture, GLint level, GLbitfield access, GLint *stride, GLenum *layout); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSyncTextureINTEL (GLuint texture); GLAPI void APIENTRY glUnmapTexture2DINTEL (GLuint texture, GLint level); GLAPI void *APIENTRY glMapTexture2DINTEL (GLuint texture, GLint level, GLbitfield access, GLint *stride, GLenum *layout); #endif #endif /* GL_INTEL_map_texture */ #ifndef GL_INTEL_parallel_arrays #define GL_INTEL_parallel_arrays 1 #define GL_PARALLEL_ARRAYS_INTEL 0x83F4 #define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 #define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 #define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 #define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 typedef void (APIENTRYP PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); typedef void (APIENTRYP PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const void **pointer); typedef void (APIENTRYP PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); typedef void (APIENTRYP PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertexPointervINTEL (GLint size, GLenum type, const void **pointer); GLAPI void APIENTRY glNormalPointervINTEL (GLenum type, const void **pointer); GLAPI void APIENTRY glColorPointervINTEL (GLint size, GLenum type, const void **pointer); GLAPI void APIENTRY glTexCoordPointervINTEL (GLint size, GLenum type, const void **pointer); #endif #endif /* GL_INTEL_parallel_arrays */ #ifndef GL_INTEL_performance_query #define GL_INTEL_performance_query 1 #define GL_PERFQUERY_SINGLE_CONTEXT_INTEL 0x00000000 #define GL_PERFQUERY_GLOBAL_CONTEXT_INTEL 0x00000001 #define GL_PERFQUERY_WAIT_INTEL 0x83FB #define GL_PERFQUERY_FLUSH_INTEL 0x83FA #define GL_PERFQUERY_DONOT_FLUSH_INTEL 0x83F9 #define GL_PERFQUERY_COUNTER_EVENT_INTEL 0x94F0 #define GL_PERFQUERY_COUNTER_DURATION_NORM_INTEL 0x94F1 #define GL_PERFQUERY_COUNTER_DURATION_RAW_INTEL 0x94F2 #define GL_PERFQUERY_COUNTER_THROUGHPUT_INTEL 0x94F3 #define GL_PERFQUERY_COUNTER_RAW_INTEL 0x94F4 #define GL_PERFQUERY_COUNTER_TIMESTAMP_INTEL 0x94F5 #define GL_PERFQUERY_COUNTER_DATA_UINT32_INTEL 0x94F8 #define GL_PERFQUERY_COUNTER_DATA_UINT64_INTEL 0x94F9 #define GL_PERFQUERY_COUNTER_DATA_FLOAT_INTEL 0x94FA #define GL_PERFQUERY_COUNTER_DATA_DOUBLE_INTEL 0x94FB #define GL_PERFQUERY_COUNTER_DATA_BOOL32_INTEL 0x94FC #define GL_PERFQUERY_QUERY_NAME_LENGTH_MAX_INTEL 0x94FD #define GL_PERFQUERY_COUNTER_NAME_LENGTH_MAX_INTEL 0x94FE #define GL_PERFQUERY_COUNTER_DESC_LENGTH_MAX_INTEL 0x94FF #define GL_PERFQUERY_GPA_EXTENDED_COUNTERS_INTEL 0x9500 typedef void (APIENTRYP PFNGLBEGINPERFQUERYINTELPROC) (GLuint queryHandle); typedef void (APIENTRYP PFNGLCREATEPERFQUERYINTELPROC) (GLuint queryId, GLuint *queryHandle); typedef void (APIENTRYP PFNGLDELETEPERFQUERYINTELPROC) (GLuint queryHandle); typedef void (APIENTRYP PFNGLENDPERFQUERYINTELPROC) (GLuint queryHandle); typedef void (APIENTRYP PFNGLGETFIRSTPERFQUERYIDINTELPROC) (GLuint *queryId); typedef void (APIENTRYP PFNGLGETNEXTPERFQUERYIDINTELPROC) (GLuint queryId, GLuint *nextQueryId); typedef void (APIENTRYP PFNGLGETPERFCOUNTERINFOINTELPROC) (GLuint queryId, GLuint counterId, GLuint counterNameLength, GLchar *counterName, GLuint counterDescLength, GLchar *counterDesc, GLuint *counterOffset, GLuint *counterDataSize, GLuint *counterTypeEnum, GLuint *counterDataTypeEnum, GLuint64 *rawCounterMaxValue); typedef void (APIENTRYP PFNGLGETPERFQUERYDATAINTELPROC) (GLuint queryHandle, GLuint flags, GLsizei dataSize, void *data, GLuint *bytesWritten); typedef void (APIENTRYP PFNGLGETPERFQUERYIDBYNAMEINTELPROC) (GLchar *queryName, GLuint *queryId); typedef void (APIENTRYP PFNGLGETPERFQUERYINFOINTELPROC) (GLuint queryId, GLuint queryNameLength, GLchar *queryName, GLuint *dataSize, GLuint *noCounters, GLuint *noInstances, GLuint *capsMask); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBeginPerfQueryINTEL (GLuint queryHandle); GLAPI void APIENTRY glCreatePerfQueryINTEL (GLuint queryId, GLuint *queryHandle); GLAPI void APIENTRY glDeletePerfQueryINTEL (GLuint queryHandle); GLAPI void APIENTRY glEndPerfQueryINTEL (GLuint queryHandle); GLAPI void APIENTRY glGetFirstPerfQueryIdINTEL (GLuint *queryId); GLAPI void APIENTRY glGetNextPerfQueryIdINTEL (GLuint queryId, GLuint *nextQueryId); GLAPI void APIENTRY glGetPerfCounterInfoINTEL (GLuint queryId, GLuint counterId, GLuint counterNameLength, GLchar *counterName, GLuint counterDescLength, GLchar *counterDesc, GLuint *counterOffset, GLuint *counterDataSize, GLuint *counterTypeEnum, GLuint *counterDataTypeEnum, GLuint64 *rawCounterMaxValue); GLAPI void APIENTRY glGetPerfQueryDataINTEL (GLuint queryHandle, GLuint flags, GLsizei dataSize, void *data, GLuint *bytesWritten); GLAPI void APIENTRY glGetPerfQueryIdByNameINTEL (GLchar *queryName, GLuint *queryId); GLAPI void APIENTRY glGetPerfQueryInfoINTEL (GLuint queryId, GLuint queryNameLength, GLchar *queryName, GLuint *dataSize, GLuint *noCounters, GLuint *noInstances, GLuint *capsMask); #endif #endif /* GL_INTEL_performance_query */ #ifndef GL_MESAX_texture_stack #define GL_MESAX_texture_stack 1 #define GL_TEXTURE_1D_STACK_MESAX 0x8759 #define GL_TEXTURE_2D_STACK_MESAX 0x875A #define GL_PROXY_TEXTURE_1D_STACK_MESAX 0x875B #define GL_PROXY_TEXTURE_2D_STACK_MESAX 0x875C #define GL_TEXTURE_1D_STACK_BINDING_MESAX 0x875D #define GL_TEXTURE_2D_STACK_BINDING_MESAX 0x875E #endif /* GL_MESAX_texture_stack */ #ifndef GL_MESA_framebuffer_flip_x #define GL_MESA_framebuffer_flip_x 1 #define GL_FRAMEBUFFER_FLIP_X_MESA 0x8BBC #endif /* GL_MESA_framebuffer_flip_x */ #ifndef GL_MESA_framebuffer_flip_y #define GL_MESA_framebuffer_flip_y 1 #define GL_FRAMEBUFFER_FLIP_Y_MESA 0x8BBB typedef void (APIENTRYP PFNGLFRAMEBUFFERPARAMETERIMESAPROC) (GLenum target, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVMESAPROC) (GLenum target, GLenum pname, GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFramebufferParameteriMESA (GLenum target, GLenum pname, GLint param); GLAPI void APIENTRY glGetFramebufferParameterivMESA (GLenum target, GLenum pname, GLint *params); #endif #endif /* GL_MESA_framebuffer_flip_y */ #ifndef GL_MESA_framebuffer_swap_xy #define GL_MESA_framebuffer_swap_xy 1 #define GL_FRAMEBUFFER_SWAP_XY_MESA 0x8BBD #endif /* GL_MESA_framebuffer_swap_xy */ #ifndef GL_MESA_pack_invert #define GL_MESA_pack_invert 1 #define GL_PACK_INVERT_MESA 0x8758 #endif /* GL_MESA_pack_invert */ #ifndef GL_MESA_program_binary_formats #define GL_MESA_program_binary_formats 1 #define GL_PROGRAM_BINARY_FORMAT_MESA 0x875F #endif /* GL_MESA_program_binary_formats */ #ifndef GL_MESA_resize_buffers #define GL_MESA_resize_buffers 1 typedef void (APIENTRYP PFNGLRESIZEBUFFERSMESAPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glResizeBuffersMESA (void); #endif #endif /* GL_MESA_resize_buffers */ #ifndef GL_MESA_shader_integer_functions #define GL_MESA_shader_integer_functions 1 #endif /* GL_MESA_shader_integer_functions */ #ifndef GL_MESA_tile_raster_order #define GL_MESA_tile_raster_order 1 #define GL_TILE_RASTER_ORDER_FIXED_MESA 0x8BB8 #define GL_TILE_RASTER_ORDER_INCREASING_X_MESA 0x8BB9 #define GL_TILE_RASTER_ORDER_INCREASING_Y_MESA 0x8BBA #endif /* GL_MESA_tile_raster_order */ #ifndef GL_MESA_window_pos #define GL_MESA_window_pos 1 typedef void (APIENTRYP PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); typedef void (APIENTRYP PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); typedef void (APIENTRYP PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); typedef void (APIENTRYP PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); typedef void (APIENTRYP PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); typedef void (APIENTRYP PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); typedef void (APIENTRYP PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); typedef void (APIENTRYP PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); typedef void (APIENTRYP PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); typedef void (APIENTRYP PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); typedef void (APIENTRYP PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); typedef void (APIENTRYP PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); typedef void (APIENTRYP PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); typedef void (APIENTRYP PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glWindowPos2dMESA (GLdouble x, GLdouble y); GLAPI void APIENTRY glWindowPos2dvMESA (const GLdouble *v); GLAPI void APIENTRY glWindowPos2fMESA (GLfloat x, GLfloat y); GLAPI void APIENTRY glWindowPos2fvMESA (const GLfloat *v); GLAPI void APIENTRY glWindowPos2iMESA (GLint x, GLint y); GLAPI void APIENTRY glWindowPos2ivMESA (const GLint *v); GLAPI void APIENTRY glWindowPos2sMESA (GLshort x, GLshort y); GLAPI void APIENTRY glWindowPos2svMESA (const GLshort *v); GLAPI void APIENTRY glWindowPos3dMESA (GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glWindowPos3dvMESA (const GLdouble *v); GLAPI void APIENTRY glWindowPos3fMESA (GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glWindowPos3fvMESA (const GLfloat *v); GLAPI void APIENTRY glWindowPos3iMESA (GLint x, GLint y, GLint z); GLAPI void APIENTRY glWindowPos3ivMESA (const GLint *v); GLAPI void APIENTRY glWindowPos3sMESA (GLshort x, GLshort y, GLshort z); GLAPI void APIENTRY glWindowPos3svMESA (const GLshort *v); GLAPI void APIENTRY glWindowPos4dMESA (GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glWindowPos4dvMESA (const GLdouble *v); GLAPI void APIENTRY glWindowPos4fMESA (GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glWindowPos4fvMESA (const GLfloat *v); GLAPI void APIENTRY glWindowPos4iMESA (GLint x, GLint y, GLint z, GLint w); GLAPI void APIENTRY glWindowPos4ivMESA (const GLint *v); GLAPI void APIENTRY glWindowPos4sMESA (GLshort x, GLshort y, GLshort z, GLshort w); GLAPI void APIENTRY glWindowPos4svMESA (const GLshort *v); #endif #endif /* GL_MESA_window_pos */ #ifndef GL_MESA_ycbcr_texture #define GL_MESA_ycbcr_texture 1 #define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA #define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB #define GL_YCBCR_MESA 0x8757 #endif /* GL_MESA_ycbcr_texture */ #ifndef GL_NVX_blend_equation_advanced_multi_draw_buffers #define GL_NVX_blend_equation_advanced_multi_draw_buffers 1 #endif /* GL_NVX_blend_equation_advanced_multi_draw_buffers */ #ifndef GL_NVX_conditional_render #define GL_NVX_conditional_render 1 typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERNVXPROC) (GLuint id); typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERNVXPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBeginConditionalRenderNVX (GLuint id); GLAPI void APIENTRY glEndConditionalRenderNVX (void); #endif #endif /* GL_NVX_conditional_render */ #ifndef GL_NVX_gpu_memory_info #define GL_NVX_gpu_memory_info 1 #define GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX 0x9047 #define GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX 0x9048 #define GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX 0x9049 #define GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX 0x904A #define GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX 0x904B #endif /* GL_NVX_gpu_memory_info */ #ifndef GL_NVX_gpu_multicast2 #define GL_NVX_gpu_multicast2 1 #define GL_UPLOAD_GPU_MASK_NVX 0x954A typedef void (APIENTRYP PFNGLUPLOADGPUMASKNVXPROC) (GLbitfield mask); typedef void (APIENTRYP PFNGLMULTICASTVIEWPORTARRAYVNVXPROC) (GLuint gpu, GLuint first, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTICASTVIEWPORTPOSITIONWSCALENVXPROC) (GLuint gpu, GLuint index, GLfloat xcoeff, GLfloat ycoeff); typedef void (APIENTRYP PFNGLMULTICASTSCISSORARRAYVNVXPROC) (GLuint gpu, GLuint first, GLsizei count, const GLint *v); typedef GLuint (APIENTRYP PFNGLASYNCCOPYBUFFERSUBDATANVXPROC) (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *fenceValueArray, GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); typedef GLuint (APIENTRYP PFNGLASYNCCOPYIMAGESUBDATANVXPROC) (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *waitValueArray, GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glUploadGpuMaskNVX (GLbitfield mask); GLAPI void APIENTRY glMulticastViewportArrayvNVX (GLuint gpu, GLuint first, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glMulticastViewportPositionWScaleNVX (GLuint gpu, GLuint index, GLfloat xcoeff, GLfloat ycoeff); GLAPI void APIENTRY glMulticastScissorArrayvNVX (GLuint gpu, GLuint first, GLsizei count, const GLint *v); GLAPI GLuint APIENTRY glAsyncCopyBufferSubDataNVX (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *fenceValueArray, GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); GLAPI GLuint APIENTRY glAsyncCopyImageSubDataNVX (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *waitValueArray, GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); #endif #endif /* GL_NVX_gpu_multicast2 */ #ifndef GL_NVX_linked_gpu_multicast #define GL_NVX_linked_gpu_multicast 1 #define GL_LGPU_SEPARATE_STORAGE_BIT_NVX 0x0800 #define GL_MAX_LGPU_GPUS_NVX 0x92BA typedef void (APIENTRYP PFNGLLGPUNAMEDBUFFERSUBDATANVXPROC) (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); typedef void (APIENTRYP PFNGLLGPUCOPYIMAGESUBDATANVXPROC) (GLuint sourceGpu, GLbitfield destinationGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srxY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); typedef void (APIENTRYP PFNGLLGPUINTERLOCKNVXPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glLGPUNamedBufferSubDataNVX (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); GLAPI void APIENTRY glLGPUCopyImageSubDataNVX (GLuint sourceGpu, GLbitfield destinationGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srxY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); GLAPI void APIENTRY glLGPUInterlockNVX (void); #endif #endif /* GL_NVX_linked_gpu_multicast */ #ifndef GL_NVX_progress_fence #define GL_NVX_progress_fence 1 typedef GLuint (APIENTRYP PFNGLCREATEPROGRESSFENCENVXPROC) (void); typedef void (APIENTRYP PFNGLSIGNALSEMAPHOREUI64NVXPROC) (GLuint signalGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); typedef void (APIENTRYP PFNGLWAITSEMAPHOREUI64NVXPROC) (GLuint waitGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); typedef void (APIENTRYP PFNGLCLIENTWAITSEMAPHOREUI64NVXPROC) (GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLuint APIENTRY glCreateProgressFenceNVX (void); GLAPI void APIENTRY glSignalSemaphoreui64NVX (GLuint signalGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); GLAPI void APIENTRY glWaitSemaphoreui64NVX (GLuint waitGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); GLAPI void APIENTRY glClientWaitSemaphoreui64NVX (GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); #endif #endif /* GL_NVX_progress_fence */ #ifndef GL_NV_alpha_to_coverage_dither_control #define GL_NV_alpha_to_coverage_dither_control 1 #define GL_ALPHA_TO_COVERAGE_DITHER_DEFAULT_NV 0x934D #define GL_ALPHA_TO_COVERAGE_DITHER_ENABLE_NV 0x934E #define GL_ALPHA_TO_COVERAGE_DITHER_DISABLE_NV 0x934F #define GL_ALPHA_TO_COVERAGE_DITHER_MODE_NV 0x92BF typedef void (APIENTRYP PFNGLALPHATOCOVERAGEDITHERCONTROLNVPROC) (GLenum mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glAlphaToCoverageDitherControlNV (GLenum mode); #endif #endif /* GL_NV_alpha_to_coverage_dither_control */ #ifndef GL_NV_bindless_multi_draw_indirect #define GL_NV_bindless_multi_draw_indirect 1 typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSNVPROC) (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSNVPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMultiDrawArraysIndirectBindlessNV (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); GLAPI void APIENTRY glMultiDrawElementsIndirectBindlessNV (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); #endif #endif /* GL_NV_bindless_multi_draw_indirect */ #ifndef GL_NV_bindless_multi_draw_indirect_count #define GL_NV_bindless_multi_draw_indirect_count 1 typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSCOUNTNVPROC) (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSCOUNTNVPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMultiDrawArraysIndirectBindlessCountNV (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); GLAPI void APIENTRY glMultiDrawElementsIndirectBindlessCountNV (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); #endif #endif /* GL_NV_bindless_multi_draw_indirect_count */ #ifndef GL_NV_bindless_texture #define GL_NV_bindless_texture 1 typedef GLuint64 (APIENTRYP PFNGLGETTEXTUREHANDLENVPROC) (GLuint texture); typedef GLuint64 (APIENTRYP PFNGLGETTEXTURESAMPLERHANDLENVPROC) (GLuint texture, GLuint sampler); typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTNVPROC) (GLuint64 handle); typedef GLuint64 (APIENTRYP PFNGLGETIMAGEHANDLENVPROC) (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle, GLenum access); typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTNVPROC) (GLuint64 handle); typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64NVPROC) (GLint location, GLuint64 value); typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64VNVPROC) (GLint location, GLsizei count, const GLuint64 *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64NVPROC) (GLuint program, GLint location, GLuint64 value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); typedef GLboolean (APIENTRYP PFNGLISTEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); typedef GLboolean (APIENTRYP PFNGLISIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLuint64 APIENTRY glGetTextureHandleNV (GLuint texture); GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleNV (GLuint texture, GLuint sampler); GLAPI void APIENTRY glMakeTextureHandleResidentNV (GLuint64 handle); GLAPI void APIENTRY glMakeTextureHandleNonResidentNV (GLuint64 handle); GLAPI GLuint64 APIENTRY glGetImageHandleNV (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); GLAPI void APIENTRY glMakeImageHandleResidentNV (GLuint64 handle, GLenum access); GLAPI void APIENTRY glMakeImageHandleNonResidentNV (GLuint64 handle); GLAPI void APIENTRY glUniformHandleui64NV (GLint location, GLuint64 value); GLAPI void APIENTRY glUniformHandleui64vNV (GLint location, GLsizei count, const GLuint64 *value); GLAPI void APIENTRY glProgramUniformHandleui64NV (GLuint program, GLint location, GLuint64 value); GLAPI void APIENTRY glProgramUniformHandleui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64 *values); GLAPI GLboolean APIENTRY glIsTextureHandleResidentNV (GLuint64 handle); GLAPI GLboolean APIENTRY glIsImageHandleResidentNV (GLuint64 handle); #endif #endif /* GL_NV_bindless_texture */ #ifndef GL_NV_blend_equation_advanced #define GL_NV_blend_equation_advanced 1 #define GL_BLEND_OVERLAP_NV 0x9281 #define GL_BLEND_PREMULTIPLIED_SRC_NV 0x9280 #define GL_BLUE_NV 0x1905 #define GL_COLORBURN_NV 0x929A #define GL_COLORDODGE_NV 0x9299 #define GL_CONJOINT_NV 0x9284 #define GL_CONTRAST_NV 0x92A1 #define GL_DARKEN_NV 0x9297 #define GL_DIFFERENCE_NV 0x929E #define GL_DISJOINT_NV 0x9283 #define GL_DST_ATOP_NV 0x928F #define GL_DST_IN_NV 0x928B #define GL_DST_NV 0x9287 #define GL_DST_OUT_NV 0x928D #define GL_DST_OVER_NV 0x9289 #define GL_EXCLUSION_NV 0x92A0 #define GL_GREEN_NV 0x1904 #define GL_HARDLIGHT_NV 0x929B #define GL_HARDMIX_NV 0x92A9 #define GL_HSL_COLOR_NV 0x92AF #define GL_HSL_HUE_NV 0x92AD #define GL_HSL_LUMINOSITY_NV 0x92B0 #define GL_HSL_SATURATION_NV 0x92AE #define GL_INVERT_OVG_NV 0x92B4 #define GL_INVERT_RGB_NV 0x92A3 #define GL_LIGHTEN_NV 0x9298 #define GL_LINEARBURN_NV 0x92A5 #define GL_LINEARDODGE_NV 0x92A4 #define GL_LINEARLIGHT_NV 0x92A7 #define GL_MINUS_CLAMPED_NV 0x92B3 #define GL_MINUS_NV 0x929F #define GL_MULTIPLY_NV 0x9294 #define GL_OVERLAY_NV 0x9296 #define GL_PINLIGHT_NV 0x92A8 #define GL_PLUS_CLAMPED_ALPHA_NV 0x92B2 #define GL_PLUS_CLAMPED_NV 0x92B1 #define GL_PLUS_DARKER_NV 0x9292 #define GL_PLUS_NV 0x9291 #define GL_RED_NV 0x1903 #define GL_SCREEN_NV 0x9295 #define GL_SOFTLIGHT_NV 0x929C #define GL_SRC_ATOP_NV 0x928E #define GL_SRC_IN_NV 0x928A #define GL_SRC_NV 0x9286 #define GL_SRC_OUT_NV 0x928C #define GL_SRC_OVER_NV 0x9288 #define GL_UNCORRELATED_NV 0x9282 #define GL_VIVIDLIGHT_NV 0x92A6 #define GL_XOR_NV 0x1506 typedef void (APIENTRYP PFNGLBLENDPARAMETERINVPROC) (GLenum pname, GLint value); typedef void (APIENTRYP PFNGLBLENDBARRIERNVPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBlendParameteriNV (GLenum pname, GLint value); GLAPI void APIENTRY glBlendBarrierNV (void); #endif #endif /* GL_NV_blend_equation_advanced */ #ifndef GL_NV_blend_equation_advanced_coherent #define GL_NV_blend_equation_advanced_coherent 1 #define GL_BLEND_ADVANCED_COHERENT_NV 0x9285 #endif /* GL_NV_blend_equation_advanced_coherent */ #ifndef GL_NV_blend_minmax_factor #define GL_NV_blend_minmax_factor 1 #endif /* GL_NV_blend_minmax_factor */ #ifndef GL_NV_blend_square #define GL_NV_blend_square 1 #endif /* GL_NV_blend_square */ #ifndef GL_NV_clip_space_w_scaling #define GL_NV_clip_space_w_scaling 1 #define GL_VIEWPORT_POSITION_W_SCALE_NV 0x937C #define GL_VIEWPORT_POSITION_W_SCALE_X_COEFF_NV 0x937D #define GL_VIEWPORT_POSITION_W_SCALE_Y_COEFF_NV 0x937E typedef void (APIENTRYP PFNGLVIEWPORTPOSITIONWSCALENVPROC) (GLuint index, GLfloat xcoeff, GLfloat ycoeff); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glViewportPositionWScaleNV (GLuint index, GLfloat xcoeff, GLfloat ycoeff); #endif #endif /* GL_NV_clip_space_w_scaling */ #ifndef GL_NV_command_list #define GL_NV_command_list 1 #define GL_TERMINATE_SEQUENCE_COMMAND_NV 0x0000 #define GL_NOP_COMMAND_NV 0x0001 #define GL_DRAW_ELEMENTS_COMMAND_NV 0x0002 #define GL_DRAW_ARRAYS_COMMAND_NV 0x0003 #define GL_DRAW_ELEMENTS_STRIP_COMMAND_NV 0x0004 #define GL_DRAW_ARRAYS_STRIP_COMMAND_NV 0x0005 #define GL_DRAW_ELEMENTS_INSTANCED_COMMAND_NV 0x0006 #define GL_DRAW_ARRAYS_INSTANCED_COMMAND_NV 0x0007 #define GL_ELEMENT_ADDRESS_COMMAND_NV 0x0008 #define GL_ATTRIBUTE_ADDRESS_COMMAND_NV 0x0009 #define GL_UNIFORM_ADDRESS_COMMAND_NV 0x000A #define GL_BLEND_COLOR_COMMAND_NV 0x000B #define GL_STENCIL_REF_COMMAND_NV 0x000C #define GL_LINE_WIDTH_COMMAND_NV 0x000D #define GL_POLYGON_OFFSET_COMMAND_NV 0x000E #define GL_ALPHA_REF_COMMAND_NV 0x000F #define GL_VIEWPORT_COMMAND_NV 0x0010 #define GL_SCISSOR_COMMAND_NV 0x0011 #define GL_FRONT_FACE_COMMAND_NV 0x0012 typedef void (APIENTRYP PFNGLCREATESTATESNVPROC) (GLsizei n, GLuint *states); typedef void (APIENTRYP PFNGLDELETESTATESNVPROC) (GLsizei n, const GLuint *states); typedef GLboolean (APIENTRYP PFNGLISSTATENVPROC) (GLuint state); typedef void (APIENTRYP PFNGLSTATECAPTURENVPROC) (GLuint state, GLenum mode); typedef GLuint (APIENTRYP PFNGLGETCOMMANDHEADERNVPROC) (GLenum tokenID, GLuint size); typedef GLushort (APIENTRYP PFNGLGETSTAGEINDEXNVPROC) (GLenum shadertype); typedef void (APIENTRYP PFNGLDRAWCOMMANDSNVPROC) (GLenum primitiveMode, GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, GLuint count); typedef void (APIENTRYP PFNGLDRAWCOMMANDSADDRESSNVPROC) (GLenum primitiveMode, const GLuint64 *indirects, const GLsizei *sizes, GLuint count); typedef void (APIENTRYP PFNGLDRAWCOMMANDSSTATESNVPROC) (GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); typedef void (APIENTRYP PFNGLDRAWCOMMANDSSTATESADDRESSNVPROC) (const GLuint64 *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); typedef void (APIENTRYP PFNGLCREATECOMMANDLISTSNVPROC) (GLsizei n, GLuint *lists); typedef void (APIENTRYP PFNGLDELETECOMMANDLISTSNVPROC) (GLsizei n, const GLuint *lists); typedef GLboolean (APIENTRYP PFNGLISCOMMANDLISTNVPROC) (GLuint list); typedef void (APIENTRYP PFNGLLISTDRAWCOMMANDSSTATESCLIENTNVPROC) (GLuint list, GLuint segment, const void **indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); typedef void (APIENTRYP PFNGLCOMMANDLISTSEGMENTSNVPROC) (GLuint list, GLuint segments); typedef void (APIENTRYP PFNGLCOMPILECOMMANDLISTNVPROC) (GLuint list); typedef void (APIENTRYP PFNGLCALLCOMMANDLISTNVPROC) (GLuint list); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCreateStatesNV (GLsizei n, GLuint *states); GLAPI void APIENTRY glDeleteStatesNV (GLsizei n, const GLuint *states); GLAPI GLboolean APIENTRY glIsStateNV (GLuint state); GLAPI void APIENTRY glStateCaptureNV (GLuint state, GLenum mode); GLAPI GLuint APIENTRY glGetCommandHeaderNV (GLenum tokenID, GLuint size); GLAPI GLushort APIENTRY glGetStageIndexNV (GLenum shadertype); GLAPI void APIENTRY glDrawCommandsNV (GLenum primitiveMode, GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, GLuint count); GLAPI void APIENTRY glDrawCommandsAddressNV (GLenum primitiveMode, const GLuint64 *indirects, const GLsizei *sizes, GLuint count); GLAPI void APIENTRY glDrawCommandsStatesNV (GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); GLAPI void APIENTRY glDrawCommandsStatesAddressNV (const GLuint64 *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); GLAPI void APIENTRY glCreateCommandListsNV (GLsizei n, GLuint *lists); GLAPI void APIENTRY glDeleteCommandListsNV (GLsizei n, const GLuint *lists); GLAPI GLboolean APIENTRY glIsCommandListNV (GLuint list); GLAPI void APIENTRY glListDrawCommandsStatesClientNV (GLuint list, GLuint segment, const void **indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); GLAPI void APIENTRY glCommandListSegmentsNV (GLuint list, GLuint segments); GLAPI void APIENTRY glCompileCommandListNV (GLuint list); GLAPI void APIENTRY glCallCommandListNV (GLuint list); #endif #endif /* GL_NV_command_list */ #ifndef GL_NV_compute_program5 #define GL_NV_compute_program5 1 #define GL_COMPUTE_PROGRAM_NV 0x90FB #define GL_COMPUTE_PROGRAM_PARAMETER_BUFFER_NV 0x90FC #endif /* GL_NV_compute_program5 */ #ifndef GL_NV_compute_shader_derivatives #define GL_NV_compute_shader_derivatives 1 #endif /* GL_NV_compute_shader_derivatives */ #ifndef GL_NV_conditional_render #define GL_NV_conditional_render 1 #define GL_QUERY_WAIT_NV 0x8E13 #define GL_QUERY_NO_WAIT_NV 0x8E14 #define GL_QUERY_BY_REGION_WAIT_NV 0x8E15 #define GL_QUERY_BY_REGION_NO_WAIT_NV 0x8E16 typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERNVPROC) (GLuint id, GLenum mode); typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERNVPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBeginConditionalRenderNV (GLuint id, GLenum mode); GLAPI void APIENTRY glEndConditionalRenderNV (void); #endif #endif /* GL_NV_conditional_render */ #ifndef GL_NV_conservative_raster #define GL_NV_conservative_raster 1 #define GL_CONSERVATIVE_RASTERIZATION_NV 0x9346 #define GL_SUBPIXEL_PRECISION_BIAS_X_BITS_NV 0x9347 #define GL_SUBPIXEL_PRECISION_BIAS_Y_BITS_NV 0x9348 #define GL_MAX_SUBPIXEL_PRECISION_BIAS_BITS_NV 0x9349 typedef void (APIENTRYP PFNGLSUBPIXELPRECISIONBIASNVPROC) (GLuint xbits, GLuint ybits); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSubpixelPrecisionBiasNV (GLuint xbits, GLuint ybits); #endif #endif /* GL_NV_conservative_raster */ #ifndef GL_NV_conservative_raster_dilate #define GL_NV_conservative_raster_dilate 1 #define GL_CONSERVATIVE_RASTER_DILATE_NV 0x9379 #define GL_CONSERVATIVE_RASTER_DILATE_RANGE_NV 0x937A #define GL_CONSERVATIVE_RASTER_DILATE_GRANULARITY_NV 0x937B typedef void (APIENTRYP PFNGLCONSERVATIVERASTERPARAMETERFNVPROC) (GLenum pname, GLfloat value); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glConservativeRasterParameterfNV (GLenum pname, GLfloat value); #endif #endif /* GL_NV_conservative_raster_dilate */ #ifndef GL_NV_conservative_raster_pre_snap #define GL_NV_conservative_raster_pre_snap 1 #define GL_CONSERVATIVE_RASTER_MODE_PRE_SNAP_NV 0x9550 #endif /* GL_NV_conservative_raster_pre_snap */ #ifndef GL_NV_conservative_raster_pre_snap_triangles #define GL_NV_conservative_raster_pre_snap_triangles 1 #define GL_CONSERVATIVE_RASTER_MODE_NV 0x954D #define GL_CONSERVATIVE_RASTER_MODE_POST_SNAP_NV 0x954E #define GL_CONSERVATIVE_RASTER_MODE_PRE_SNAP_TRIANGLES_NV 0x954F typedef void (APIENTRYP PFNGLCONSERVATIVERASTERPARAMETERINVPROC) (GLenum pname, GLint param); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glConservativeRasterParameteriNV (GLenum pname, GLint param); #endif #endif /* GL_NV_conservative_raster_pre_snap_triangles */ #ifndef GL_NV_conservative_raster_underestimation #define GL_NV_conservative_raster_underestimation 1 #endif /* GL_NV_conservative_raster_underestimation */ #ifndef GL_NV_copy_depth_to_color #define GL_NV_copy_depth_to_color 1 #define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E #define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F #endif /* GL_NV_copy_depth_to_color */ #ifndef GL_NV_copy_image #define GL_NV_copy_image 1 typedef void (APIENTRYP PFNGLCOPYIMAGESUBDATANVPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCopyImageSubDataNV (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); #endif #endif /* GL_NV_copy_image */ #ifndef GL_NV_deep_texture3D #define GL_NV_deep_texture3D 1 #define GL_MAX_DEEP_3D_TEXTURE_WIDTH_HEIGHT_NV 0x90D0 #define GL_MAX_DEEP_3D_TEXTURE_DEPTH_NV 0x90D1 #endif /* GL_NV_deep_texture3D */ #ifndef GL_NV_depth_buffer_float #define GL_NV_depth_buffer_float 1 #define GL_DEPTH_COMPONENT32F_NV 0x8DAB #define GL_DEPTH32F_STENCIL8_NV 0x8DAC #define GL_FLOAT_32_UNSIGNED_INT_24_8_REV_NV 0x8DAD #define GL_DEPTH_BUFFER_FLOAT_MODE_NV 0x8DAF typedef void (APIENTRYP PFNGLDEPTHRANGEDNVPROC) (GLdouble zNear, GLdouble zFar); typedef void (APIENTRYP PFNGLCLEARDEPTHDNVPROC) (GLdouble depth); typedef void (APIENTRYP PFNGLDEPTHBOUNDSDNVPROC) (GLdouble zmin, GLdouble zmax); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDepthRangedNV (GLdouble zNear, GLdouble zFar); GLAPI void APIENTRY glClearDepthdNV (GLdouble depth); GLAPI void APIENTRY glDepthBoundsdNV (GLdouble zmin, GLdouble zmax); #endif #endif /* GL_NV_depth_buffer_float */ #ifndef GL_NV_depth_clamp #define GL_NV_depth_clamp 1 #define GL_DEPTH_CLAMP_NV 0x864F #endif /* GL_NV_depth_clamp */ #ifndef GL_NV_draw_texture #define GL_NV_draw_texture 1 typedef void (APIENTRYP PFNGLDRAWTEXTURENVPROC) (GLuint texture, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawTextureNV (GLuint texture, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); #endif #endif /* GL_NV_draw_texture */ #ifndef GL_NV_draw_vulkan_image #define GL_NV_draw_vulkan_image 1 typedef void (APIENTRY *GLVULKANPROCNV)(void); typedef void (APIENTRYP PFNGLDRAWVKIMAGENVPROC) (GLuint64 vkImage, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); typedef GLVULKANPROCNV (APIENTRYP PFNGLGETVKPROCADDRNVPROC) (const GLchar *name); typedef void (APIENTRYP PFNGLWAITVKSEMAPHORENVPROC) (GLuint64 vkSemaphore); typedef void (APIENTRYP PFNGLSIGNALVKSEMAPHORENVPROC) (GLuint64 vkSemaphore); typedef void (APIENTRYP PFNGLSIGNALVKFENCENVPROC) (GLuint64 vkFence); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawVkImageNV (GLuint64 vkImage, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); GLAPI GLVULKANPROCNV APIENTRY glGetVkProcAddrNV (const GLchar *name); GLAPI void APIENTRY glWaitVkSemaphoreNV (GLuint64 vkSemaphore); GLAPI void APIENTRY glSignalVkSemaphoreNV (GLuint64 vkSemaphore); GLAPI void APIENTRY glSignalVkFenceNV (GLuint64 vkFence); #endif #endif /* GL_NV_draw_vulkan_image */ #ifndef GL_NV_evaluators #define GL_NV_evaluators 1 #define GL_EVAL_2D_NV 0x86C0 #define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 #define GL_MAP_TESSELLATION_NV 0x86C2 #define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 #define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 #define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 #define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 #define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 #define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 #define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 #define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA #define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB #define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC #define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD #define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE #define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF #define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 #define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 #define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 #define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 #define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 #define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 #define GL_MAX_MAP_TESSELLATION_NV 0x86D6 #define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 typedef void (APIENTRYP PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const void *points); typedef void (APIENTRYP PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, void *points); typedef void (APIENTRYP PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const void *points); GLAPI void APIENTRY glMapParameterivNV (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glMapParameterfvNV (GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glGetMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, void *points); GLAPI void APIENTRY glGetMapParameterivNV (GLenum target, GLenum pname, GLint *params); GLAPI void APIENTRY glGetMapParameterfvNV (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetMapAttribParameterivNV (GLenum target, GLuint index, GLenum pname, GLint *params); GLAPI void APIENTRY glGetMapAttribParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); GLAPI void APIENTRY glEvalMapsNV (GLenum target, GLenum mode); #endif #endif /* GL_NV_evaluators */ #ifndef GL_NV_explicit_multisample #define GL_NV_explicit_multisample 1 #define GL_SAMPLE_POSITION_NV 0x8E50 #define GL_SAMPLE_MASK_NV 0x8E51 #define GL_SAMPLE_MASK_VALUE_NV 0x8E52 #define GL_TEXTURE_BINDING_RENDERBUFFER_NV 0x8E53 #define GL_TEXTURE_RENDERBUFFER_DATA_STORE_BINDING_NV 0x8E54 #define GL_TEXTURE_RENDERBUFFER_NV 0x8E55 #define GL_SAMPLER_RENDERBUFFER_NV 0x8E56 #define GL_INT_SAMPLER_RENDERBUFFER_NV 0x8E57 #define GL_UNSIGNED_INT_SAMPLER_RENDERBUFFER_NV 0x8E58 #define GL_MAX_SAMPLE_MASK_WORDS_NV 0x8E59 typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVNVPROC) (GLenum pname, GLuint index, GLfloat *val); typedef void (APIENTRYP PFNGLSAMPLEMASKINDEXEDNVPROC) (GLuint index, GLbitfield mask); typedef void (APIENTRYP PFNGLTEXRENDERBUFFERNVPROC) (GLenum target, GLuint renderbuffer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGetMultisamplefvNV (GLenum pname, GLuint index, GLfloat *val); GLAPI void APIENTRY glSampleMaskIndexedNV (GLuint index, GLbitfield mask); GLAPI void APIENTRY glTexRenderbufferNV (GLenum target, GLuint renderbuffer); #endif #endif /* GL_NV_explicit_multisample */ #ifndef GL_NV_fence #define GL_NV_fence 1 #define GL_ALL_COMPLETED_NV 0x84F2 #define GL_FENCE_STATUS_NV 0x84F3 #define GL_FENCE_CONDITION_NV 0x84F4 typedef void (APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); typedef void (APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); typedef GLboolean (APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); typedef GLboolean (APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); typedef void (APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); typedef void (APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDeleteFencesNV (GLsizei n, const GLuint *fences); GLAPI void APIENTRY glGenFencesNV (GLsizei n, GLuint *fences); GLAPI GLboolean APIENTRY glIsFenceNV (GLuint fence); GLAPI GLboolean APIENTRY glTestFenceNV (GLuint fence); GLAPI void APIENTRY glGetFenceivNV (GLuint fence, GLenum pname, GLint *params); GLAPI void APIENTRY glFinishFenceNV (GLuint fence); GLAPI void APIENTRY glSetFenceNV (GLuint fence, GLenum condition); #endif #endif /* GL_NV_fence */ #ifndef GL_NV_fill_rectangle #define GL_NV_fill_rectangle 1 #define GL_FILL_RECTANGLE_NV 0x933C #endif /* GL_NV_fill_rectangle */ #ifndef GL_NV_float_buffer #define GL_NV_float_buffer 1 #define GL_FLOAT_R_NV 0x8880 #define GL_FLOAT_RG_NV 0x8881 #define GL_FLOAT_RGB_NV 0x8882 #define GL_FLOAT_RGBA_NV 0x8883 #define GL_FLOAT_R16_NV 0x8884 #define GL_FLOAT_R32_NV 0x8885 #define GL_FLOAT_RG16_NV 0x8886 #define GL_FLOAT_RG32_NV 0x8887 #define GL_FLOAT_RGB16_NV 0x8888 #define GL_FLOAT_RGB32_NV 0x8889 #define GL_FLOAT_RGBA16_NV 0x888A #define GL_FLOAT_RGBA32_NV 0x888B #define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C #define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D #define GL_FLOAT_RGBA_MODE_NV 0x888E #endif /* GL_NV_float_buffer */ #ifndef GL_NV_fog_distance #define GL_NV_fog_distance 1 #define GL_FOG_DISTANCE_MODE_NV 0x855A #define GL_EYE_RADIAL_NV 0x855B #define GL_EYE_PLANE_ABSOLUTE_NV 0x855C #endif /* GL_NV_fog_distance */ #ifndef GL_NV_fragment_coverage_to_color #define GL_NV_fragment_coverage_to_color 1 #define GL_FRAGMENT_COVERAGE_TO_COLOR_NV 0x92DD #define GL_FRAGMENT_COVERAGE_COLOR_NV 0x92DE typedef void (APIENTRYP PFNGLFRAGMENTCOVERAGECOLORNVPROC) (GLuint color); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFragmentCoverageColorNV (GLuint color); #endif #endif /* GL_NV_fragment_coverage_to_color */ #ifndef GL_NV_fragment_program #define GL_NV_fragment_program 1 #define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 #define GL_FRAGMENT_PROGRAM_NV 0x8870 #define GL_MAX_TEXTURE_COORDS_NV 0x8871 #define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 #define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 #define GL_PROGRAM_ERROR_STRING_NV 0x8874 typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glProgramNamedParameter4fNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glProgramNamedParameter4fvNV (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); GLAPI void APIENTRY glProgramNamedParameter4dNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glProgramNamedParameter4dvNV (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); GLAPI void APIENTRY glGetProgramNamedParameterfvNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); GLAPI void APIENTRY glGetProgramNamedParameterdvNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); #endif #endif /* GL_NV_fragment_program */ #ifndef GL_NV_fragment_program2 #define GL_NV_fragment_program2 1 #define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 #define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 #define GL_MAX_PROGRAM_IF_DEPTH_NV 0x88F6 #define GL_MAX_PROGRAM_LOOP_DEPTH_NV 0x88F7 #define GL_MAX_PROGRAM_LOOP_COUNT_NV 0x88F8 #endif /* GL_NV_fragment_program2 */ #ifndef GL_NV_fragment_program4 #define GL_NV_fragment_program4 1 #endif /* GL_NV_fragment_program4 */ #ifndef GL_NV_fragment_program_option #define GL_NV_fragment_program_option 1 #endif /* GL_NV_fragment_program_option */ #ifndef GL_NV_fragment_shader_barycentric #define GL_NV_fragment_shader_barycentric 1 #endif /* GL_NV_fragment_shader_barycentric */ #ifndef GL_NV_fragment_shader_interlock #define GL_NV_fragment_shader_interlock 1 #endif /* GL_NV_fragment_shader_interlock */ #ifndef GL_NV_framebuffer_mixed_samples #define GL_NV_framebuffer_mixed_samples 1 #define GL_COVERAGE_MODULATION_TABLE_NV 0x9331 #define GL_COLOR_SAMPLES_NV 0x8E20 #define GL_DEPTH_SAMPLES_NV 0x932D #define GL_STENCIL_SAMPLES_NV 0x932E #define GL_MIXED_DEPTH_SAMPLES_SUPPORTED_NV 0x932F #define GL_MIXED_STENCIL_SAMPLES_SUPPORTED_NV 0x9330 #define GL_COVERAGE_MODULATION_NV 0x9332 #define GL_COVERAGE_MODULATION_TABLE_SIZE_NV 0x9333 typedef void (APIENTRYP PFNGLCOVERAGEMODULATIONTABLENVPROC) (GLsizei n, const GLfloat *v); typedef void (APIENTRYP PFNGLGETCOVERAGEMODULATIONTABLENVPROC) (GLsizei bufSize, GLfloat *v); typedef void (APIENTRYP PFNGLCOVERAGEMODULATIONNVPROC) (GLenum components); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCoverageModulationTableNV (GLsizei n, const GLfloat *v); GLAPI void APIENTRY glGetCoverageModulationTableNV (GLsizei bufSize, GLfloat *v); GLAPI void APIENTRY glCoverageModulationNV (GLenum components); #endif #endif /* GL_NV_framebuffer_mixed_samples */ #ifndef GL_NV_framebuffer_multisample_coverage #define GL_NV_framebuffer_multisample_coverage 1 #define GL_RENDERBUFFER_COVERAGE_SAMPLES_NV 0x8CAB #define GL_RENDERBUFFER_COLOR_SAMPLES_NV 0x8E10 #define GL_MAX_MULTISAMPLE_COVERAGE_MODES_NV 0x8E11 #define GL_MULTISAMPLE_COVERAGE_MODES_NV 0x8E12 typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glRenderbufferStorageMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); #endif #endif /* GL_NV_framebuffer_multisample_coverage */ #ifndef GL_NV_geometry_program4 #define GL_NV_geometry_program4 1 #define GL_GEOMETRY_PROGRAM_NV 0x8C26 #define GL_MAX_PROGRAM_OUTPUT_VERTICES_NV 0x8C27 #define GL_MAX_PROGRAM_TOTAL_OUTPUT_COMPONENTS_NV 0x8C28 typedef void (APIENTRYP PFNGLPROGRAMVERTEXLIMITNVPROC) (GLenum target, GLint limit); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glProgramVertexLimitNV (GLenum target, GLint limit); GLAPI void APIENTRY glFramebufferTextureEXT (GLenum target, GLenum attachment, GLuint texture, GLint level); GLAPI void APIENTRY glFramebufferTextureFaceEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); #endif #endif /* GL_NV_geometry_program4 */ #ifndef GL_NV_geometry_shader4 #define GL_NV_geometry_shader4 1 #endif /* GL_NV_geometry_shader4 */ #ifndef GL_NV_geometry_shader_passthrough #define GL_NV_geometry_shader_passthrough 1 #endif /* GL_NV_geometry_shader_passthrough */ #ifndef GL_NV_gpu_multicast #define GL_NV_gpu_multicast 1 #define GL_PER_GPU_STORAGE_BIT_NV 0x0800 #define GL_MULTICAST_GPUS_NV 0x92BA #define GL_RENDER_GPU_MASK_NV 0x9558 #define GL_PER_GPU_STORAGE_NV 0x9548 #define GL_MULTICAST_PROGRAMMABLE_SAMPLE_LOCATION_NV 0x9549 typedef void (APIENTRYP PFNGLRENDERGPUMASKNVPROC) (GLbitfield mask); typedef void (APIENTRYP PFNGLMULTICASTBUFFERSUBDATANVPROC) (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); typedef void (APIENTRYP PFNGLMULTICASTCOPYBUFFERSUBDATANVPROC) (GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); typedef void (APIENTRYP PFNGLMULTICASTCOPYIMAGESUBDATANVPROC) (GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); typedef void (APIENTRYP PFNGLMULTICASTBLITFRAMEBUFFERNVPROC) (GLuint srcGpu, GLuint dstGpu, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); typedef void (APIENTRYP PFNGLMULTICASTFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLuint gpu, GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLMULTICASTBARRIERNVPROC) (void); typedef void (APIENTRYP PFNGLMULTICASTWAITSYNCNVPROC) (GLuint signalGpu, GLbitfield waitGpuMask); typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTIVNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTUIVNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTI64VNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLint64 *params); typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTUI64VNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLuint64 *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glRenderGpuMaskNV (GLbitfield mask); GLAPI void APIENTRY glMulticastBufferSubDataNV (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); GLAPI void APIENTRY glMulticastCopyBufferSubDataNV (GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); GLAPI void APIENTRY glMulticastCopyImageSubDataNV (GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); GLAPI void APIENTRY glMulticastBlitFramebufferNV (GLuint srcGpu, GLuint dstGpu, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); GLAPI void APIENTRY glMulticastFramebufferSampleLocationsfvNV (GLuint gpu, GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glMulticastBarrierNV (void); GLAPI void APIENTRY glMulticastWaitSyncNV (GLuint signalGpu, GLbitfield waitGpuMask); GLAPI void APIENTRY glMulticastGetQueryObjectivNV (GLuint gpu, GLuint id, GLenum pname, GLint *params); GLAPI void APIENTRY glMulticastGetQueryObjectuivNV (GLuint gpu, GLuint id, GLenum pname, GLuint *params); GLAPI void APIENTRY glMulticastGetQueryObjecti64vNV (GLuint gpu, GLuint id, GLenum pname, GLint64 *params); GLAPI void APIENTRY glMulticastGetQueryObjectui64vNV (GLuint gpu, GLuint id, GLenum pname, GLuint64 *params); #endif #endif /* GL_NV_gpu_multicast */ #ifndef GL_NV_gpu_program4 #define GL_NV_gpu_program4 1 #define GL_MIN_PROGRAM_TEXEL_OFFSET_NV 0x8904 #define GL_MAX_PROGRAM_TEXEL_OFFSET_NV 0x8905 #define GL_PROGRAM_ATTRIB_COMPONENTS_NV 0x8906 #define GL_PROGRAM_RESULT_COMPONENTS_NV 0x8907 #define GL_MAX_PROGRAM_ATTRIB_COMPONENTS_NV 0x8908 #define GL_MAX_PROGRAM_RESULT_COMPONENTS_NV 0x8909 #define GL_MAX_PROGRAM_GENERIC_ATTRIBS_NV 0x8DA5 #define GL_MAX_PROGRAM_GENERIC_RESULTS_NV 0x8DA6 typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glProgramLocalParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); GLAPI void APIENTRY glProgramLocalParameterI4ivNV (GLenum target, GLuint index, const GLint *params); GLAPI void APIENTRY glProgramLocalParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); GLAPI void APIENTRY glProgramLocalParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); GLAPI void APIENTRY glProgramLocalParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); GLAPI void APIENTRY glProgramLocalParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); GLAPI void APIENTRY glProgramEnvParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); GLAPI void APIENTRY glProgramEnvParameterI4ivNV (GLenum target, GLuint index, const GLint *params); GLAPI void APIENTRY glProgramEnvParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); GLAPI void APIENTRY glProgramEnvParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); GLAPI void APIENTRY glProgramEnvParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); GLAPI void APIENTRY glProgramEnvParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); GLAPI void APIENTRY glGetProgramLocalParameterIivNV (GLenum target, GLuint index, GLint *params); GLAPI void APIENTRY glGetProgramLocalParameterIuivNV (GLenum target, GLuint index, GLuint *params); GLAPI void APIENTRY glGetProgramEnvParameterIivNV (GLenum target, GLuint index, GLint *params); GLAPI void APIENTRY glGetProgramEnvParameterIuivNV (GLenum target, GLuint index, GLuint *params); #endif #endif /* GL_NV_gpu_program4 */ #ifndef GL_NV_gpu_program5 #define GL_NV_gpu_program5 1 #define GL_MAX_GEOMETRY_PROGRAM_INVOCATIONS_NV 0x8E5A #define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5B #define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5C #define GL_FRAGMENT_PROGRAM_INTERPOLATION_OFFSET_BITS_NV 0x8E5D #define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_NV 0x8E5E #define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_NV 0x8E5F #define GL_MAX_PROGRAM_SUBROUTINE_PARAMETERS_NV 0x8F44 #define GL_MAX_PROGRAM_SUBROUTINE_NUM_NV 0x8F45 typedef void (APIENTRYP PFNGLPROGRAMSUBROUTINEPARAMETERSUIVNVPROC) (GLenum target, GLsizei count, const GLuint *params); typedef void (APIENTRYP PFNGLGETPROGRAMSUBROUTINEPARAMETERUIVNVPROC) (GLenum target, GLuint index, GLuint *param); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glProgramSubroutineParametersuivNV (GLenum target, GLsizei count, const GLuint *params); GLAPI void APIENTRY glGetProgramSubroutineParameteruivNV (GLenum target, GLuint index, GLuint *param); #endif #endif /* GL_NV_gpu_program5 */ #ifndef GL_NV_gpu_program5_mem_extended #define GL_NV_gpu_program5_mem_extended 1 #endif /* GL_NV_gpu_program5_mem_extended */ #ifndef GL_NV_gpu_shader5 #define GL_NV_gpu_shader5 1 #endif /* GL_NV_gpu_shader5 */ #ifndef GL_NV_half_float #define GL_NV_half_float 1 typedef unsigned short GLhalfNV; #define GL_HALF_FLOAT_NV 0x140B typedef void (APIENTRYP PFNGLVERTEX2HNVPROC) (GLhalfNV x, GLhalfNV y); typedef void (APIENTRYP PFNGLVERTEX2HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLVERTEX3HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z); typedef void (APIENTRYP PFNGLVERTEX3HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLVERTEX4HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); typedef void (APIENTRYP PFNGLVERTEX4HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLNORMAL3HNVPROC) (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); typedef void (APIENTRYP PFNGLNORMAL3HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); typedef void (APIENTRYP PFNGLCOLOR3HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLCOLOR4HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); typedef void (APIENTRYP PFNGLCOLOR4HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLTEXCOORD1HNVPROC) (GLhalfNV s); typedef void (APIENTRYP PFNGLTEXCOORD1HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLTEXCOORD2HNVPROC) (GLhalfNV s, GLhalfNV t); typedef void (APIENTRYP PFNGLTEXCOORD2HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLTEXCOORD3HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r); typedef void (APIENTRYP PFNGLTEXCOORD3HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLTEXCOORD4HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); typedef void (APIENTRYP PFNGLTEXCOORD4HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalfNV s); typedef void (APIENTRYP PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalfNV *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t); typedef void (APIENTRYP PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalfNV *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); typedef void (APIENTRYP PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalfNV *v); typedef void (APIENTRYP PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); typedef void (APIENTRYP PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalfNV *v); typedef void (APIENTRYP PFNGLFOGCOORDHNVPROC) (GLhalfNV fog); typedef void (APIENTRYP PFNGLFOGCOORDHVNVPROC) (const GLhalfNV *fog); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalfNV *v); typedef void (APIENTRYP PFNGLVERTEXWEIGHTHNVPROC) (GLhalfNV weight); typedef void (APIENTRYP PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalfNV *weight); typedef void (APIENTRYP PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalfNV x); typedef void (APIENTRYP PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalfNV *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y); typedef void (APIENTRYP PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalfNV *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); typedef void (APIENTRYP PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalfNV *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalfNV *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertex2hNV (GLhalfNV x, GLhalfNV y); GLAPI void APIENTRY glVertex2hvNV (const GLhalfNV *v); GLAPI void APIENTRY glVertex3hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z); GLAPI void APIENTRY glVertex3hvNV (const GLhalfNV *v); GLAPI void APIENTRY glVertex4hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); GLAPI void APIENTRY glVertex4hvNV (const GLhalfNV *v); GLAPI void APIENTRY glNormal3hNV (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); GLAPI void APIENTRY glNormal3hvNV (const GLhalfNV *v); GLAPI void APIENTRY glColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); GLAPI void APIENTRY glColor3hvNV (const GLhalfNV *v); GLAPI void APIENTRY glColor4hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); GLAPI void APIENTRY glColor4hvNV (const GLhalfNV *v); GLAPI void APIENTRY glTexCoord1hNV (GLhalfNV s); GLAPI void APIENTRY glTexCoord1hvNV (const GLhalfNV *v); GLAPI void APIENTRY glTexCoord2hNV (GLhalfNV s, GLhalfNV t); GLAPI void APIENTRY glTexCoord2hvNV (const GLhalfNV *v); GLAPI void APIENTRY glTexCoord3hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r); GLAPI void APIENTRY glTexCoord3hvNV (const GLhalfNV *v); GLAPI void APIENTRY glTexCoord4hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); GLAPI void APIENTRY glTexCoord4hvNV (const GLhalfNV *v); GLAPI void APIENTRY glMultiTexCoord1hNV (GLenum target, GLhalfNV s); GLAPI void APIENTRY glMultiTexCoord1hvNV (GLenum target, const GLhalfNV *v); GLAPI void APIENTRY glMultiTexCoord2hNV (GLenum target, GLhalfNV s, GLhalfNV t); GLAPI void APIENTRY glMultiTexCoord2hvNV (GLenum target, const GLhalfNV *v); GLAPI void APIENTRY glMultiTexCoord3hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); GLAPI void APIENTRY glMultiTexCoord3hvNV (GLenum target, const GLhalfNV *v); GLAPI void APIENTRY glMultiTexCoord4hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); GLAPI void APIENTRY glMultiTexCoord4hvNV (GLenum target, const GLhalfNV *v); GLAPI void APIENTRY glFogCoordhNV (GLhalfNV fog); GLAPI void APIENTRY glFogCoordhvNV (const GLhalfNV *fog); GLAPI void APIENTRY glSecondaryColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); GLAPI void APIENTRY glSecondaryColor3hvNV (const GLhalfNV *v); GLAPI void APIENTRY glVertexWeighthNV (GLhalfNV weight); GLAPI void APIENTRY glVertexWeighthvNV (const GLhalfNV *weight); GLAPI void APIENTRY glVertexAttrib1hNV (GLuint index, GLhalfNV x); GLAPI void APIENTRY glVertexAttrib1hvNV (GLuint index, const GLhalfNV *v); GLAPI void APIENTRY glVertexAttrib2hNV (GLuint index, GLhalfNV x, GLhalfNV y); GLAPI void APIENTRY glVertexAttrib2hvNV (GLuint index, const GLhalfNV *v); GLAPI void APIENTRY glVertexAttrib3hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); GLAPI void APIENTRY glVertexAttrib3hvNV (GLuint index, const GLhalfNV *v); GLAPI void APIENTRY glVertexAttrib4hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); GLAPI void APIENTRY glVertexAttrib4hvNV (GLuint index, const GLhalfNV *v); GLAPI void APIENTRY glVertexAttribs1hvNV (GLuint index, GLsizei n, const GLhalfNV *v); GLAPI void APIENTRY glVertexAttribs2hvNV (GLuint index, GLsizei n, const GLhalfNV *v); GLAPI void APIENTRY glVertexAttribs3hvNV (GLuint index, GLsizei n, const GLhalfNV *v); GLAPI void APIENTRY glVertexAttribs4hvNV (GLuint index, GLsizei n, const GLhalfNV *v); #endif #endif /* GL_NV_half_float */ #ifndef GL_NV_internalformat_sample_query #define GL_NV_internalformat_sample_query 1 #define GL_MULTISAMPLES_NV 0x9371 #define GL_SUPERSAMPLE_SCALE_X_NV 0x9372 #define GL_SUPERSAMPLE_SCALE_Y_NV 0x9373 #define GL_CONFORMANT_NV 0x9374 typedef void (APIENTRYP PFNGLGETINTERNALFORMATSAMPLEIVNVPROC) (GLenum target, GLenum internalformat, GLsizei samples, GLenum pname, GLsizei count, GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGetInternalformatSampleivNV (GLenum target, GLenum internalformat, GLsizei samples, GLenum pname, GLsizei count, GLint *params); #endif #endif /* GL_NV_internalformat_sample_query */ #ifndef GL_NV_light_max_exponent #define GL_NV_light_max_exponent 1 #define GL_MAX_SHININESS_NV 0x8504 #define GL_MAX_SPOT_EXPONENT_NV 0x8505 #endif /* GL_NV_light_max_exponent */ #ifndef GL_NV_memory_attachment #define GL_NV_memory_attachment 1 #define GL_ATTACHED_MEMORY_OBJECT_NV 0x95A4 #define GL_ATTACHED_MEMORY_OFFSET_NV 0x95A5 #define GL_MEMORY_ATTACHABLE_ALIGNMENT_NV 0x95A6 #define GL_MEMORY_ATTACHABLE_SIZE_NV 0x95A7 #define GL_MEMORY_ATTACHABLE_NV 0x95A8 #define GL_DETACHED_MEMORY_INCARNATION_NV 0x95A9 #define GL_DETACHED_TEXTURES_NV 0x95AA #define GL_DETACHED_BUFFERS_NV 0x95AB #define GL_MAX_DETACHED_TEXTURES_NV 0x95AC #define GL_MAX_DETACHED_BUFFERS_NV 0x95AD typedef void (APIENTRYP PFNGLGETMEMORYOBJECTDETACHEDRESOURCESUIVNVPROC) (GLuint memory, GLenum pname, GLint first, GLsizei count, GLuint *params); typedef void (APIENTRYP PFNGLRESETMEMORYOBJECTPARAMETERNVPROC) (GLuint memory, GLenum pname); typedef void (APIENTRYP PFNGLTEXATTACHMEMORYNVPROC) (GLenum target, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLBUFFERATTACHMEMORYNVPROC) (GLenum target, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLTEXTUREATTACHMEMORYNVPROC) (GLuint texture, GLuint memory, GLuint64 offset); typedef void (APIENTRYP PFNGLNAMEDBUFFERATTACHMEMORYNVPROC) (GLuint buffer, GLuint memory, GLuint64 offset); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGetMemoryObjectDetachedResourcesuivNV (GLuint memory, GLenum pname, GLint first, GLsizei count, GLuint *params); GLAPI void APIENTRY glResetMemoryObjectParameterNV (GLuint memory, GLenum pname); GLAPI void APIENTRY glTexAttachMemoryNV (GLenum target, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glBufferAttachMemoryNV (GLenum target, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glTextureAttachMemoryNV (GLuint texture, GLuint memory, GLuint64 offset); GLAPI void APIENTRY glNamedBufferAttachMemoryNV (GLuint buffer, GLuint memory, GLuint64 offset); #endif #endif /* GL_NV_memory_attachment */ #ifndef GL_NV_memory_object_sparse #define GL_NV_memory_object_sparse 1 typedef void (APIENTRYP PFNGLBUFFERPAGECOMMITMENTMEMNVPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); typedef void (APIENTRYP PFNGLTEXPAGECOMMITMENTMEMNVPROC) (GLenum target, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); typedef void (APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTMEMNVPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); typedef void (APIENTRYP PFNGLTEXTUREPAGECOMMITMENTMEMNVPROC) (GLuint texture, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBufferPageCommitmentMemNV (GLenum target, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); GLAPI void APIENTRY glTexPageCommitmentMemNV (GLenum target, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); GLAPI void APIENTRY glNamedBufferPageCommitmentMemNV (GLuint buffer, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); GLAPI void APIENTRY glTexturePageCommitmentMemNV (GLuint texture, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); #endif #endif /* GL_NV_memory_object_sparse */ #ifndef GL_NV_mesh_shader #define GL_NV_mesh_shader 1 #define GL_MESH_SHADER_NV 0x9559 #define GL_TASK_SHADER_NV 0x955A #define GL_MAX_MESH_UNIFORM_BLOCKS_NV 0x8E60 #define GL_MAX_MESH_TEXTURE_IMAGE_UNITS_NV 0x8E61 #define GL_MAX_MESH_IMAGE_UNIFORMS_NV 0x8E62 #define GL_MAX_MESH_UNIFORM_COMPONENTS_NV 0x8E63 #define GL_MAX_MESH_ATOMIC_COUNTER_BUFFERS_NV 0x8E64 #define GL_MAX_MESH_ATOMIC_COUNTERS_NV 0x8E65 #define GL_MAX_MESH_SHADER_STORAGE_BLOCKS_NV 0x8E66 #define GL_MAX_COMBINED_MESH_UNIFORM_COMPONENTS_NV 0x8E67 #define GL_MAX_TASK_UNIFORM_BLOCKS_NV 0x8E68 #define GL_MAX_TASK_TEXTURE_IMAGE_UNITS_NV 0x8E69 #define GL_MAX_TASK_IMAGE_UNIFORMS_NV 0x8E6A #define GL_MAX_TASK_UNIFORM_COMPONENTS_NV 0x8E6B #define GL_MAX_TASK_ATOMIC_COUNTER_BUFFERS_NV 0x8E6C #define GL_MAX_TASK_ATOMIC_COUNTERS_NV 0x8E6D #define GL_MAX_TASK_SHADER_STORAGE_BLOCKS_NV 0x8E6E #define GL_MAX_COMBINED_TASK_UNIFORM_COMPONENTS_NV 0x8E6F #define GL_MAX_MESH_WORK_GROUP_INVOCATIONS_NV 0x95A2 #define GL_MAX_TASK_WORK_GROUP_INVOCATIONS_NV 0x95A3 #define GL_MAX_MESH_TOTAL_MEMORY_SIZE_NV 0x9536 #define GL_MAX_TASK_TOTAL_MEMORY_SIZE_NV 0x9537 #define GL_MAX_MESH_OUTPUT_VERTICES_NV 0x9538 #define GL_MAX_MESH_OUTPUT_PRIMITIVES_NV 0x9539 #define GL_MAX_TASK_OUTPUT_COUNT_NV 0x953A #define GL_MAX_DRAW_MESH_TASKS_COUNT_NV 0x953D #define GL_MAX_MESH_VIEWS_NV 0x9557 #define GL_MESH_OUTPUT_PER_VERTEX_GRANULARITY_NV 0x92DF #define GL_MESH_OUTPUT_PER_PRIMITIVE_GRANULARITY_NV 0x9543 #define GL_MAX_MESH_WORK_GROUP_SIZE_NV 0x953B #define GL_MAX_TASK_WORK_GROUP_SIZE_NV 0x953C #define GL_MESH_WORK_GROUP_SIZE_NV 0x953E #define GL_TASK_WORK_GROUP_SIZE_NV 0x953F #define GL_MESH_VERTICES_OUT_NV 0x9579 #define GL_MESH_PRIMITIVES_OUT_NV 0x957A #define GL_MESH_OUTPUT_TYPE_NV 0x957B #define GL_UNIFORM_BLOCK_REFERENCED_BY_MESH_SHADER_NV 0x959C #define GL_UNIFORM_BLOCK_REFERENCED_BY_TASK_SHADER_NV 0x959D #define GL_REFERENCED_BY_MESH_SHADER_NV 0x95A0 #define GL_REFERENCED_BY_TASK_SHADER_NV 0x95A1 #define GL_MESH_SHADER_BIT_NV 0x00000040 #define GL_TASK_SHADER_BIT_NV 0x00000080 #define GL_MESH_SUBROUTINE_NV 0x957C #define GL_TASK_SUBROUTINE_NV 0x957D #define GL_MESH_SUBROUTINE_UNIFORM_NV 0x957E #define GL_TASK_SUBROUTINE_UNIFORM_NV 0x957F #define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_MESH_SHADER_NV 0x959E #define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TASK_SHADER_NV 0x959F typedef void (APIENTRYP PFNGLDRAWMESHTASKSNVPROC) (GLuint first, GLuint count); typedef void (APIENTRYP PFNGLDRAWMESHTASKSINDIRECTNVPROC) (GLintptr indirect); typedef void (APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTNVPROC) (GLintptr indirect, GLsizei drawcount, GLsizei stride); typedef void (APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTCOUNTNVPROC) (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawMeshTasksNV (GLuint first, GLuint count); GLAPI void APIENTRY glDrawMeshTasksIndirectNV (GLintptr indirect); GLAPI void APIENTRY glMultiDrawMeshTasksIndirectNV (GLintptr indirect, GLsizei drawcount, GLsizei stride); GLAPI void APIENTRY glMultiDrawMeshTasksIndirectCountNV (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); #endif #endif /* GL_NV_mesh_shader */ #ifndef GL_NV_multisample_coverage #define GL_NV_multisample_coverage 1 #endif /* GL_NV_multisample_coverage */ #ifndef GL_NV_multisample_filter_hint #define GL_NV_multisample_filter_hint 1 #define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 #endif /* GL_NV_multisample_filter_hint */ #ifndef GL_NV_occlusion_query #define GL_NV_occlusion_query 1 #define GL_PIXEL_COUNTER_BITS_NV 0x8864 #define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 #define GL_PIXEL_COUNT_NV 0x8866 #define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 typedef void (APIENTRYP PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint *ids); typedef void (APIENTRYP PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint *ids); typedef GLboolean (APIENTRYP PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); typedef void (APIENTRYP PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); typedef void (APIENTRYP PFNGLENDOCCLUSIONQUERYNVPROC) (void); typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei n, GLuint *ids); GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei n, const GLuint *ids); GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint id); GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint id); GLAPI void APIENTRY glEndOcclusionQueryNV (void); GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint id, GLenum pname, GLint *params); GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint id, GLenum pname, GLuint *params); #endif #endif /* GL_NV_occlusion_query */ #ifndef GL_NV_packed_depth_stencil #define GL_NV_packed_depth_stencil 1 #define GL_DEPTH_STENCIL_NV 0x84F9 #define GL_UNSIGNED_INT_24_8_NV 0x84FA #endif /* GL_NV_packed_depth_stencil */ #ifndef GL_NV_parameter_buffer_object #define GL_NV_parameter_buffer_object 1 #define GL_MAX_PROGRAM_PARAMETER_BUFFER_BINDINGS_NV 0x8DA0 #define GL_MAX_PROGRAM_PARAMETER_BUFFER_SIZE_NV 0x8DA1 #define GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV 0x8DA2 #define GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV 0x8DA3 #define GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV 0x8DA4 typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLfloat *params); typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLint *params); typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLuint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glProgramBufferParametersfvNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLfloat *params); GLAPI void APIENTRY glProgramBufferParametersIivNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLint *params); GLAPI void APIENTRY glProgramBufferParametersIuivNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLuint *params); #endif #endif /* GL_NV_parameter_buffer_object */ #ifndef GL_NV_parameter_buffer_object2 #define GL_NV_parameter_buffer_object2 1 #endif /* GL_NV_parameter_buffer_object2 */ #ifndef GL_NV_path_rendering #define GL_NV_path_rendering 1 #define GL_PATH_FORMAT_SVG_NV 0x9070 #define GL_PATH_FORMAT_PS_NV 0x9071 #define GL_STANDARD_FONT_NAME_NV 0x9072 #define GL_SYSTEM_FONT_NAME_NV 0x9073 #define GL_FILE_NAME_NV 0x9074 #define GL_PATH_STROKE_WIDTH_NV 0x9075 #define GL_PATH_END_CAPS_NV 0x9076 #define GL_PATH_INITIAL_END_CAP_NV 0x9077 #define GL_PATH_TERMINAL_END_CAP_NV 0x9078 #define GL_PATH_JOIN_STYLE_NV 0x9079 #define GL_PATH_MITER_LIMIT_NV 0x907A #define GL_PATH_DASH_CAPS_NV 0x907B #define GL_PATH_INITIAL_DASH_CAP_NV 0x907C #define GL_PATH_TERMINAL_DASH_CAP_NV 0x907D #define GL_PATH_DASH_OFFSET_NV 0x907E #define GL_PATH_CLIENT_LENGTH_NV 0x907F #define GL_PATH_FILL_MODE_NV 0x9080 #define GL_PATH_FILL_MASK_NV 0x9081 #define GL_PATH_FILL_COVER_MODE_NV 0x9082 #define GL_PATH_STROKE_COVER_MODE_NV 0x9083 #define GL_PATH_STROKE_MASK_NV 0x9084 #define GL_COUNT_UP_NV 0x9088 #define GL_COUNT_DOWN_NV 0x9089 #define GL_PATH_OBJECT_BOUNDING_BOX_NV 0x908A #define GL_CONVEX_HULL_NV 0x908B #define GL_BOUNDING_BOX_NV 0x908D #define GL_TRANSLATE_X_NV 0x908E #define GL_TRANSLATE_Y_NV 0x908F #define GL_TRANSLATE_2D_NV 0x9090 #define GL_TRANSLATE_3D_NV 0x9091 #define GL_AFFINE_2D_NV 0x9092 #define GL_AFFINE_3D_NV 0x9094 #define GL_TRANSPOSE_AFFINE_2D_NV 0x9096 #define GL_TRANSPOSE_AFFINE_3D_NV 0x9098 #define GL_UTF8_NV 0x909A #define GL_UTF16_NV 0x909B #define GL_BOUNDING_BOX_OF_BOUNDING_BOXES_NV 0x909C #define GL_PATH_COMMAND_COUNT_NV 0x909D #define GL_PATH_COORD_COUNT_NV 0x909E #define GL_PATH_DASH_ARRAY_COUNT_NV 0x909F #define GL_PATH_COMPUTED_LENGTH_NV 0x90A0 #define GL_PATH_FILL_BOUNDING_BOX_NV 0x90A1 #define GL_PATH_STROKE_BOUNDING_BOX_NV 0x90A2 #define GL_SQUARE_NV 0x90A3 #define GL_ROUND_NV 0x90A4 #define GL_TRIANGULAR_NV 0x90A5 #define GL_BEVEL_NV 0x90A6 #define GL_MITER_REVERT_NV 0x90A7 #define GL_MITER_TRUNCATE_NV 0x90A8 #define GL_SKIP_MISSING_GLYPH_NV 0x90A9 #define GL_USE_MISSING_GLYPH_NV 0x90AA #define GL_PATH_ERROR_POSITION_NV 0x90AB #define GL_ACCUM_ADJACENT_PAIRS_NV 0x90AD #define GL_ADJACENT_PAIRS_NV 0x90AE #define GL_FIRST_TO_REST_NV 0x90AF #define GL_PATH_GEN_MODE_NV 0x90B0 #define GL_PATH_GEN_COEFF_NV 0x90B1 #define GL_PATH_GEN_COMPONENTS_NV 0x90B3 #define GL_PATH_STENCIL_FUNC_NV 0x90B7 #define GL_PATH_STENCIL_REF_NV 0x90B8 #define GL_PATH_STENCIL_VALUE_MASK_NV 0x90B9 #define GL_PATH_STENCIL_DEPTH_OFFSET_FACTOR_NV 0x90BD #define GL_PATH_STENCIL_DEPTH_OFFSET_UNITS_NV 0x90BE #define GL_PATH_COVER_DEPTH_FUNC_NV 0x90BF #define GL_PATH_DASH_OFFSET_RESET_NV 0x90B4 #define GL_MOVE_TO_RESETS_NV 0x90B5 #define GL_MOVE_TO_CONTINUES_NV 0x90B6 #define GL_CLOSE_PATH_NV 0x00 #define GL_MOVE_TO_NV 0x02 #define GL_RELATIVE_MOVE_TO_NV 0x03 #define GL_LINE_TO_NV 0x04 #define GL_RELATIVE_LINE_TO_NV 0x05 #define GL_HORIZONTAL_LINE_TO_NV 0x06 #define GL_RELATIVE_HORIZONTAL_LINE_TO_NV 0x07 #define GL_VERTICAL_LINE_TO_NV 0x08 #define GL_RELATIVE_VERTICAL_LINE_TO_NV 0x09 #define GL_QUADRATIC_CURVE_TO_NV 0x0A #define GL_RELATIVE_QUADRATIC_CURVE_TO_NV 0x0B #define GL_CUBIC_CURVE_TO_NV 0x0C #define GL_RELATIVE_CUBIC_CURVE_TO_NV 0x0D #define GL_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0E #define GL_RELATIVE_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0F #define GL_SMOOTH_CUBIC_CURVE_TO_NV 0x10 #define GL_RELATIVE_SMOOTH_CUBIC_CURVE_TO_NV 0x11 #define GL_SMALL_CCW_ARC_TO_NV 0x12 #define GL_RELATIVE_SMALL_CCW_ARC_TO_NV 0x13 #define GL_SMALL_CW_ARC_TO_NV 0x14 #define GL_RELATIVE_SMALL_CW_ARC_TO_NV 0x15 #define GL_LARGE_CCW_ARC_TO_NV 0x16 #define GL_RELATIVE_LARGE_CCW_ARC_TO_NV 0x17 #define GL_LARGE_CW_ARC_TO_NV 0x18 #define GL_RELATIVE_LARGE_CW_ARC_TO_NV 0x19 #define GL_RESTART_PATH_NV 0xF0 #define GL_DUP_FIRST_CUBIC_CURVE_TO_NV 0xF2 #define GL_DUP_LAST_CUBIC_CURVE_TO_NV 0xF4 #define GL_RECT_NV 0xF6 #define GL_CIRCULAR_CCW_ARC_TO_NV 0xF8 #define GL_CIRCULAR_CW_ARC_TO_NV 0xFA #define GL_CIRCULAR_TANGENT_ARC_TO_NV 0xFC #define GL_ARC_TO_NV 0xFE #define GL_RELATIVE_ARC_TO_NV 0xFF #define GL_BOLD_BIT_NV 0x01 #define GL_ITALIC_BIT_NV 0x02 #define GL_GLYPH_WIDTH_BIT_NV 0x01 #define GL_GLYPH_HEIGHT_BIT_NV 0x02 #define GL_GLYPH_HORIZONTAL_BEARING_X_BIT_NV 0x04 #define GL_GLYPH_HORIZONTAL_BEARING_Y_BIT_NV 0x08 #define GL_GLYPH_HORIZONTAL_BEARING_ADVANCE_BIT_NV 0x10 #define GL_GLYPH_VERTICAL_BEARING_X_BIT_NV 0x20 #define GL_GLYPH_VERTICAL_BEARING_Y_BIT_NV 0x40 #define GL_GLYPH_VERTICAL_BEARING_ADVANCE_BIT_NV 0x80 #define GL_GLYPH_HAS_KERNING_BIT_NV 0x100 #define GL_FONT_X_MIN_BOUNDS_BIT_NV 0x00010000 #define GL_FONT_Y_MIN_BOUNDS_BIT_NV 0x00020000 #define GL_FONT_X_MAX_BOUNDS_BIT_NV 0x00040000 #define GL_FONT_Y_MAX_BOUNDS_BIT_NV 0x00080000 #define GL_FONT_UNITS_PER_EM_BIT_NV 0x00100000 #define GL_FONT_ASCENDER_BIT_NV 0x00200000 #define GL_FONT_DESCENDER_BIT_NV 0x00400000 #define GL_FONT_HEIGHT_BIT_NV 0x00800000 #define GL_FONT_MAX_ADVANCE_WIDTH_BIT_NV 0x01000000 #define GL_FONT_MAX_ADVANCE_HEIGHT_BIT_NV 0x02000000 #define GL_FONT_UNDERLINE_POSITION_BIT_NV 0x04000000 #define GL_FONT_UNDERLINE_THICKNESS_BIT_NV 0x08000000 #define GL_FONT_HAS_KERNING_BIT_NV 0x10000000 #define GL_ROUNDED_RECT_NV 0xE8 #define GL_RELATIVE_ROUNDED_RECT_NV 0xE9 #define GL_ROUNDED_RECT2_NV 0xEA #define GL_RELATIVE_ROUNDED_RECT2_NV 0xEB #define GL_ROUNDED_RECT4_NV 0xEC #define GL_RELATIVE_ROUNDED_RECT4_NV 0xED #define GL_ROUNDED_RECT8_NV 0xEE #define GL_RELATIVE_ROUNDED_RECT8_NV 0xEF #define GL_RELATIVE_RECT_NV 0xF7 #define GL_FONT_GLYPHS_AVAILABLE_NV 0x9368 #define GL_FONT_TARGET_UNAVAILABLE_NV 0x9369 #define GL_FONT_UNAVAILABLE_NV 0x936A #define GL_FONT_UNINTELLIGIBLE_NV 0x936B #define GL_CONIC_CURVE_TO_NV 0x1A #define GL_RELATIVE_CONIC_CURVE_TO_NV 0x1B #define GL_FONT_NUM_GLYPH_INDICES_BIT_NV 0x20000000 #define GL_STANDARD_FONT_FORMAT_NV 0x936C #define GL_2_BYTES_NV 0x1407 #define GL_3_BYTES_NV 0x1408 #define GL_4_BYTES_NV 0x1409 #define GL_EYE_LINEAR_NV 0x2400 #define GL_OBJECT_LINEAR_NV 0x2401 #define GL_CONSTANT_NV 0x8576 #define GL_PATH_FOG_GEN_MODE_NV 0x90AC #define GL_PRIMARY_COLOR_NV 0x852C #define GL_SECONDARY_COLOR_NV 0x852D #define GL_PATH_GEN_COLOR_FORMAT_NV 0x90B2 #define GL_PATH_PROJECTION_NV 0x1701 #define GL_PATH_MODELVIEW_NV 0x1700 #define GL_PATH_MODELVIEW_STACK_DEPTH_NV 0x0BA3 #define GL_PATH_MODELVIEW_MATRIX_NV 0x0BA6 #define GL_PATH_MAX_MODELVIEW_STACK_DEPTH_NV 0x0D36 #define GL_PATH_TRANSPOSE_MODELVIEW_MATRIX_NV 0x84E3 #define GL_PATH_PROJECTION_STACK_DEPTH_NV 0x0BA4 #define GL_PATH_PROJECTION_MATRIX_NV 0x0BA7 #define GL_PATH_MAX_PROJECTION_STACK_DEPTH_NV 0x0D38 #define GL_PATH_TRANSPOSE_PROJECTION_MATRIX_NV 0x84E4 #define GL_FRAGMENT_INPUT_NV 0x936D typedef GLuint (APIENTRYP PFNGLGENPATHSNVPROC) (GLsizei range); typedef void (APIENTRYP PFNGLDELETEPATHSNVPROC) (GLuint path, GLsizei range); typedef GLboolean (APIENTRYP PFNGLISPATHNVPROC) (GLuint path); typedef void (APIENTRYP PFNGLPATHCOMMANDSNVPROC) (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); typedef void (APIENTRYP PFNGLPATHCOORDSNVPROC) (GLuint path, GLsizei numCoords, GLenum coordType, const void *coords); typedef void (APIENTRYP PFNGLPATHSUBCOMMANDSNVPROC) (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); typedef void (APIENTRYP PFNGLPATHSUBCOORDSNVPROC) (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const void *coords); typedef void (APIENTRYP PFNGLPATHSTRINGNVPROC) (GLuint path, GLenum format, GLsizei length, const void *pathString); typedef void (APIENTRYP PFNGLPATHGLYPHSNVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const void *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); typedef void (APIENTRYP PFNGLPATHGLYPHRANGENVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); typedef void (APIENTRYP PFNGLWEIGHTPATHSNVPROC) (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); typedef void (APIENTRYP PFNGLCOPYPATHNVPROC) (GLuint resultPath, GLuint srcPath); typedef void (APIENTRYP PFNGLINTERPOLATEPATHSNVPROC) (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); typedef void (APIENTRYP PFNGLTRANSFORMPATHNVPROC) (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); typedef void (APIENTRYP PFNGLPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, const GLint *value); typedef void (APIENTRYP PFNGLPATHPARAMETERINVPROC) (GLuint path, GLenum pname, GLint value); typedef void (APIENTRYP PFNGLPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, const GLfloat *value); typedef void (APIENTRYP PFNGLPATHPARAMETERFNVPROC) (GLuint path, GLenum pname, GLfloat value); typedef void (APIENTRYP PFNGLPATHDASHARRAYNVPROC) (GLuint path, GLsizei dashCount, const GLfloat *dashArray); typedef void (APIENTRYP PFNGLPATHSTENCILFUNCNVPROC) (GLenum func, GLint ref, GLuint mask); typedef void (APIENTRYP PFNGLPATHSTENCILDEPTHOFFSETNVPROC) (GLfloat factor, GLfloat units); typedef void (APIENTRYP PFNGLSTENCILFILLPATHNVPROC) (GLuint path, GLenum fillMode, GLuint mask); typedef void (APIENTRYP PFNGLSTENCILSTROKEPATHNVPROC) (GLuint path, GLint reference, GLuint mask); typedef void (APIENTRYP PFNGLSTENCILFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); typedef void (APIENTRYP PFNGLSTENCILSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); typedef void (APIENTRYP PFNGLPATHCOVERDEPTHFUNCNVPROC) (GLenum func); typedef void (APIENTRYP PFNGLCOVERFILLPATHNVPROC) (GLuint path, GLenum coverMode); typedef void (APIENTRYP PFNGLCOVERSTROKEPATHNVPROC) (GLuint path, GLenum coverMode); typedef void (APIENTRYP PFNGLCOVERFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); typedef void (APIENTRYP PFNGLCOVERSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); typedef void (APIENTRYP PFNGLGETPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, GLint *value); typedef void (APIENTRYP PFNGLGETPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, GLfloat *value); typedef void (APIENTRYP PFNGLGETPATHCOMMANDSNVPROC) (GLuint path, GLubyte *commands); typedef void (APIENTRYP PFNGLGETPATHCOORDSNVPROC) (GLuint path, GLfloat *coords); typedef void (APIENTRYP PFNGLGETPATHDASHARRAYNVPROC) (GLuint path, GLfloat *dashArray); typedef void (APIENTRYP PFNGLGETPATHMETRICSNVPROC) (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); typedef void (APIENTRYP PFNGLGETPATHMETRICRANGENVPROC) (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); typedef void (APIENTRYP PFNGLGETPATHSPACINGNVPROC) (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); typedef GLboolean (APIENTRYP PFNGLISPOINTINFILLPATHNVPROC) (GLuint path, GLuint mask, GLfloat x, GLfloat y); typedef GLboolean (APIENTRYP PFNGLISPOINTINSTROKEPATHNVPROC) (GLuint path, GLfloat x, GLfloat y); typedef GLfloat (APIENTRYP PFNGLGETPATHLENGTHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments); typedef GLboolean (APIENTRYP PFNGLPOINTALONGPATHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); typedef void (APIENTRYP PFNGLMATRIXLOAD3X2FNVPROC) (GLenum matrixMode, const GLfloat *m); typedef void (APIENTRYP PFNGLMATRIXLOAD3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSE3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); typedef void (APIENTRYP PFNGLMATRIXMULT3X2FNVPROC) (GLenum matrixMode, const GLfloat *m); typedef void (APIENTRYP PFNGLMATRIXMULT3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSE3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); typedef void (APIENTRYP PFNGLSTENCILTHENCOVERFILLPATHNVPROC) (GLuint path, GLenum fillMode, GLuint mask, GLenum coverMode); typedef void (APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHNVPROC) (GLuint path, GLint reference, GLuint mask, GLenum coverMode); typedef void (APIENTRYP PFNGLSTENCILTHENCOVERFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); typedef void (APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); typedef GLenum (APIENTRYP PFNGLPATHGLYPHINDEXRANGENVPROC) (GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint pathParameterTemplate, GLfloat emScale, GLuint *baseAndCount); typedef GLenum (APIENTRYP PFNGLPATHGLYPHINDEXARRAYNVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); typedef GLenum (APIENTRYP PFNGLPATHMEMORYGLYPHINDEXARRAYNVPROC) (GLuint firstPathName, GLenum fontTarget, GLsizeiptr fontSize, const void *fontData, GLsizei faceIndex, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); typedef void (APIENTRYP PFNGLPROGRAMPATHFRAGMENTINPUTGENNVPROC) (GLuint program, GLint location, GLenum genMode, GLint components, const GLfloat *coeffs); typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCEFVNVPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLfloat *params); typedef void (APIENTRYP PFNGLPATHCOLORGENNVPROC) (GLenum color, GLenum genMode, GLenum colorFormat, const GLfloat *coeffs); typedef void (APIENTRYP PFNGLPATHTEXGENNVPROC) (GLenum texCoordSet, GLenum genMode, GLint components, const GLfloat *coeffs); typedef void (APIENTRYP PFNGLPATHFOGGENNVPROC) (GLenum genMode); typedef void (APIENTRYP PFNGLGETPATHCOLORGENIVNVPROC) (GLenum color, GLenum pname, GLint *value); typedef void (APIENTRYP PFNGLGETPATHCOLORGENFVNVPROC) (GLenum color, GLenum pname, GLfloat *value); typedef void (APIENTRYP PFNGLGETPATHTEXGENIVNVPROC) (GLenum texCoordSet, GLenum pname, GLint *value); typedef void (APIENTRYP PFNGLGETPATHTEXGENFVNVPROC) (GLenum texCoordSet, GLenum pname, GLfloat *value); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLuint APIENTRY glGenPathsNV (GLsizei range); GLAPI void APIENTRY glDeletePathsNV (GLuint path, GLsizei range); GLAPI GLboolean APIENTRY glIsPathNV (GLuint path); GLAPI void APIENTRY glPathCommandsNV (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); GLAPI void APIENTRY glPathCoordsNV (GLuint path, GLsizei numCoords, GLenum coordType, const void *coords); GLAPI void APIENTRY glPathSubCommandsNV (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); GLAPI void APIENTRY glPathSubCoordsNV (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const void *coords); GLAPI void APIENTRY glPathStringNV (GLuint path, GLenum format, GLsizei length, const void *pathString); GLAPI void APIENTRY glPathGlyphsNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const void *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); GLAPI void APIENTRY glPathGlyphRangeNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); GLAPI void APIENTRY glWeightPathsNV (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); GLAPI void APIENTRY glCopyPathNV (GLuint resultPath, GLuint srcPath); GLAPI void APIENTRY glInterpolatePathsNV (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); GLAPI void APIENTRY glTransformPathNV (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); GLAPI void APIENTRY glPathParameterivNV (GLuint path, GLenum pname, const GLint *value); GLAPI void APIENTRY glPathParameteriNV (GLuint path, GLenum pname, GLint value); GLAPI void APIENTRY glPathParameterfvNV (GLuint path, GLenum pname, const GLfloat *value); GLAPI void APIENTRY glPathParameterfNV (GLuint path, GLenum pname, GLfloat value); GLAPI void APIENTRY glPathDashArrayNV (GLuint path, GLsizei dashCount, const GLfloat *dashArray); GLAPI void APIENTRY glPathStencilFuncNV (GLenum func, GLint ref, GLuint mask); GLAPI void APIENTRY glPathStencilDepthOffsetNV (GLfloat factor, GLfloat units); GLAPI void APIENTRY glStencilFillPathNV (GLuint path, GLenum fillMode, GLuint mask); GLAPI void APIENTRY glStencilStrokePathNV (GLuint path, GLint reference, GLuint mask); GLAPI void APIENTRY glStencilFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); GLAPI void APIENTRY glStencilStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); GLAPI void APIENTRY glPathCoverDepthFuncNV (GLenum func); GLAPI void APIENTRY glCoverFillPathNV (GLuint path, GLenum coverMode); GLAPI void APIENTRY glCoverStrokePathNV (GLuint path, GLenum coverMode); GLAPI void APIENTRY glCoverFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); GLAPI void APIENTRY glCoverStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); GLAPI void APIENTRY glGetPathParameterivNV (GLuint path, GLenum pname, GLint *value); GLAPI void APIENTRY glGetPathParameterfvNV (GLuint path, GLenum pname, GLfloat *value); GLAPI void APIENTRY glGetPathCommandsNV (GLuint path, GLubyte *commands); GLAPI void APIENTRY glGetPathCoordsNV (GLuint path, GLfloat *coords); GLAPI void APIENTRY glGetPathDashArrayNV (GLuint path, GLfloat *dashArray); GLAPI void APIENTRY glGetPathMetricsNV (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); GLAPI void APIENTRY glGetPathMetricRangeNV (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); GLAPI void APIENTRY glGetPathSpacingNV (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); GLAPI GLboolean APIENTRY glIsPointInFillPathNV (GLuint path, GLuint mask, GLfloat x, GLfloat y); GLAPI GLboolean APIENTRY glIsPointInStrokePathNV (GLuint path, GLfloat x, GLfloat y); GLAPI GLfloat APIENTRY glGetPathLengthNV (GLuint path, GLsizei startSegment, GLsizei numSegments); GLAPI GLboolean APIENTRY glPointAlongPathNV (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); GLAPI void APIENTRY glMatrixLoad3x2fNV (GLenum matrixMode, const GLfloat *m); GLAPI void APIENTRY glMatrixLoad3x3fNV (GLenum matrixMode, const GLfloat *m); GLAPI void APIENTRY glMatrixLoadTranspose3x3fNV (GLenum matrixMode, const GLfloat *m); GLAPI void APIENTRY glMatrixMult3x2fNV (GLenum matrixMode, const GLfloat *m); GLAPI void APIENTRY glMatrixMult3x3fNV (GLenum matrixMode, const GLfloat *m); GLAPI void APIENTRY glMatrixMultTranspose3x3fNV (GLenum matrixMode, const GLfloat *m); GLAPI void APIENTRY glStencilThenCoverFillPathNV (GLuint path, GLenum fillMode, GLuint mask, GLenum coverMode); GLAPI void APIENTRY glStencilThenCoverStrokePathNV (GLuint path, GLint reference, GLuint mask, GLenum coverMode); GLAPI void APIENTRY glStencilThenCoverFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); GLAPI void APIENTRY glStencilThenCoverStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); GLAPI GLenum APIENTRY glPathGlyphIndexRangeNV (GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint pathParameterTemplate, GLfloat emScale, GLuint *baseAndCount); GLAPI GLenum APIENTRY glPathGlyphIndexArrayNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); GLAPI GLenum APIENTRY glPathMemoryGlyphIndexArrayNV (GLuint firstPathName, GLenum fontTarget, GLsizeiptr fontSize, const void *fontData, GLsizei faceIndex, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); GLAPI void APIENTRY glProgramPathFragmentInputGenNV (GLuint program, GLint location, GLenum genMode, GLint components, const GLfloat *coeffs); GLAPI void APIENTRY glGetProgramResourcefvNV (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLfloat *params); GLAPI void APIENTRY glPathColorGenNV (GLenum color, GLenum genMode, GLenum colorFormat, const GLfloat *coeffs); GLAPI void APIENTRY glPathTexGenNV (GLenum texCoordSet, GLenum genMode, GLint components, const GLfloat *coeffs); GLAPI void APIENTRY glPathFogGenNV (GLenum genMode); GLAPI void APIENTRY glGetPathColorGenivNV (GLenum color, GLenum pname, GLint *value); GLAPI void APIENTRY glGetPathColorGenfvNV (GLenum color, GLenum pname, GLfloat *value); GLAPI void APIENTRY glGetPathTexGenivNV (GLenum texCoordSet, GLenum pname, GLint *value); GLAPI void APIENTRY glGetPathTexGenfvNV (GLenum texCoordSet, GLenum pname, GLfloat *value); #endif #endif /* GL_NV_path_rendering */ #ifndef GL_NV_path_rendering_shared_edge #define GL_NV_path_rendering_shared_edge 1 #define GL_SHARED_EDGE_NV 0xC0 #endif /* GL_NV_path_rendering_shared_edge */ #ifndef GL_NV_pixel_data_range #define GL_NV_pixel_data_range 1 #define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 #define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 #define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A #define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B #define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C #define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, const void *pointer); typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPixelDataRangeNV (GLenum target, GLsizei length, const void *pointer); GLAPI void APIENTRY glFlushPixelDataRangeNV (GLenum target); #endif #endif /* GL_NV_pixel_data_range */ #ifndef GL_NV_point_sprite #define GL_NV_point_sprite 1 #define GL_POINT_SPRITE_NV 0x8861 #define GL_COORD_REPLACE_NV 0x8862 #define GL_POINT_SPRITE_R_MODE_NV 0x8863 typedef void (APIENTRYP PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPointParameteriNV (GLenum pname, GLint param); GLAPI void APIENTRY glPointParameterivNV (GLenum pname, const GLint *params); #endif #endif /* GL_NV_point_sprite */ #ifndef GL_NV_present_video #define GL_NV_present_video 1 #define GL_FRAME_NV 0x8E26 #define GL_FIELDS_NV 0x8E27 #define GL_CURRENT_TIME_NV 0x8E28 #define GL_NUM_FILL_STREAMS_NV 0x8E29 #define GL_PRESENT_TIME_NV 0x8E2A #define GL_PRESENT_DURATION_NV 0x8E2B typedef void (APIENTRYP PFNGLPRESENTFRAMEKEYEDNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); typedef void (APIENTRYP PFNGLPRESENTFRAMEDUALFILLNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); typedef void (APIENTRYP PFNGLGETVIDEOIVNVPROC) (GLuint video_slot, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETVIDEOUIVNVPROC) (GLuint video_slot, GLenum pname, GLuint *params); typedef void (APIENTRYP PFNGLGETVIDEOI64VNVPROC) (GLuint video_slot, GLenum pname, GLint64EXT *params); typedef void (APIENTRYP PFNGLGETVIDEOUI64VNVPROC) (GLuint video_slot, GLenum pname, GLuint64EXT *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPresentFrameKeyedNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); GLAPI void APIENTRY glPresentFrameDualFillNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); GLAPI void APIENTRY glGetVideoivNV (GLuint video_slot, GLenum pname, GLint *params); GLAPI void APIENTRY glGetVideouivNV (GLuint video_slot, GLenum pname, GLuint *params); GLAPI void APIENTRY glGetVideoi64vNV (GLuint video_slot, GLenum pname, GLint64EXT *params); GLAPI void APIENTRY glGetVideoui64vNV (GLuint video_slot, GLenum pname, GLuint64EXT *params); #endif #endif /* GL_NV_present_video */ #ifndef GL_NV_primitive_restart #define GL_NV_primitive_restart 1 #define GL_PRIMITIVE_RESTART_NV 0x8558 #define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 typedef void (APIENTRYP PFNGLPRIMITIVERESTARTNVPROC) (void); typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPrimitiveRestartNV (void); GLAPI void APIENTRY glPrimitiveRestartIndexNV (GLuint index); #endif #endif /* GL_NV_primitive_restart */ #ifndef GL_NV_primitive_shading_rate #define GL_NV_primitive_shading_rate 1 #define GL_SHADING_RATE_IMAGE_PER_PRIMITIVE_NV 0x95B1 #define GL_SHADING_RATE_IMAGE_PALETTE_COUNT_NV 0x95B2 #endif /* GL_NV_primitive_shading_rate */ #ifndef GL_NV_query_resource #define GL_NV_query_resource 1 #define GL_QUERY_RESOURCE_TYPE_VIDMEM_ALLOC_NV 0x9540 #define GL_QUERY_RESOURCE_MEMTYPE_VIDMEM_NV 0x9542 #define GL_QUERY_RESOURCE_SYS_RESERVED_NV 0x9544 #define GL_QUERY_RESOURCE_TEXTURE_NV 0x9545 #define GL_QUERY_RESOURCE_RENDERBUFFER_NV 0x9546 #define GL_QUERY_RESOURCE_BUFFEROBJECT_NV 0x9547 typedef GLint (APIENTRYP PFNGLQUERYRESOURCENVPROC) (GLenum queryType, GLint tagId, GLuint count, GLint *buffer); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLint APIENTRY glQueryResourceNV (GLenum queryType, GLint tagId, GLuint count, GLint *buffer); #endif #endif /* GL_NV_query_resource */ #ifndef GL_NV_query_resource_tag #define GL_NV_query_resource_tag 1 typedef void (APIENTRYP PFNGLGENQUERYRESOURCETAGNVPROC) (GLsizei n, GLint *tagIds); typedef void (APIENTRYP PFNGLDELETEQUERYRESOURCETAGNVPROC) (GLsizei n, const GLint *tagIds); typedef void (APIENTRYP PFNGLQUERYRESOURCETAGNVPROC) (GLint tagId, const GLchar *tagString); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGenQueryResourceTagNV (GLsizei n, GLint *tagIds); GLAPI void APIENTRY glDeleteQueryResourceTagNV (GLsizei n, const GLint *tagIds); GLAPI void APIENTRY glQueryResourceTagNV (GLint tagId, const GLchar *tagString); #endif #endif /* GL_NV_query_resource_tag */ #ifndef GL_NV_register_combiners #define GL_NV_register_combiners 1 #define GL_REGISTER_COMBINERS_NV 0x8522 #define GL_VARIABLE_A_NV 0x8523 #define GL_VARIABLE_B_NV 0x8524 #define GL_VARIABLE_C_NV 0x8525 #define GL_VARIABLE_D_NV 0x8526 #define GL_VARIABLE_E_NV 0x8527 #define GL_VARIABLE_F_NV 0x8528 #define GL_VARIABLE_G_NV 0x8529 #define GL_CONSTANT_COLOR0_NV 0x852A #define GL_CONSTANT_COLOR1_NV 0x852B #define GL_SPARE0_NV 0x852E #define GL_SPARE1_NV 0x852F #define GL_DISCARD_NV 0x8530 #define GL_E_TIMES_F_NV 0x8531 #define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 #define GL_UNSIGNED_IDENTITY_NV 0x8536 #define GL_UNSIGNED_INVERT_NV 0x8537 #define GL_EXPAND_NORMAL_NV 0x8538 #define GL_EXPAND_NEGATE_NV 0x8539 #define GL_HALF_BIAS_NORMAL_NV 0x853A #define GL_HALF_BIAS_NEGATE_NV 0x853B #define GL_SIGNED_IDENTITY_NV 0x853C #define GL_SIGNED_NEGATE_NV 0x853D #define GL_SCALE_BY_TWO_NV 0x853E #define GL_SCALE_BY_FOUR_NV 0x853F #define GL_SCALE_BY_ONE_HALF_NV 0x8540 #define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 #define GL_COMBINER_INPUT_NV 0x8542 #define GL_COMBINER_MAPPING_NV 0x8543 #define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 #define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 #define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 #define GL_COMBINER_MUX_SUM_NV 0x8547 #define GL_COMBINER_SCALE_NV 0x8548 #define GL_COMBINER_BIAS_NV 0x8549 #define GL_COMBINER_AB_OUTPUT_NV 0x854A #define GL_COMBINER_CD_OUTPUT_NV 0x854B #define GL_COMBINER_SUM_OUTPUT_NV 0x854C #define GL_MAX_GENERAL_COMBINERS_NV 0x854D #define GL_NUM_GENERAL_COMBINERS_NV 0x854E #define GL_COLOR_SUM_CLAMP_NV 0x854F #define GL_COMBINER0_NV 0x8550 #define GL_COMBINER1_NV 0x8551 #define GL_COMBINER2_NV 0x8552 #define GL_COMBINER3_NV 0x8553 #define GL_COMBINER4_NV 0x8554 #define GL_COMBINER5_NV 0x8555 #define GL_COMBINER6_NV 0x8556 #define GL_COMBINER7_NV 0x8557 typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); typedef void (APIENTRYP PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); typedef void (APIENTRYP PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCombinerParameterfvNV (GLenum pname, const GLfloat *params); GLAPI void APIENTRY glCombinerParameterfNV (GLenum pname, GLfloat param); GLAPI void APIENTRY glCombinerParameterivNV (GLenum pname, const GLint *params); GLAPI void APIENTRY glCombinerParameteriNV (GLenum pname, GLint param); GLAPI void APIENTRY glCombinerInputNV (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); GLAPI void APIENTRY glCombinerOutputNV (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); GLAPI void APIENTRY glFinalCombinerInputNV (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); GLAPI void APIENTRY glGetCombinerInputParameterfvNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetCombinerInputParameterivNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); GLAPI void APIENTRY glGetCombinerOutputParameterfvNV (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetCombinerOutputParameterivNV (GLenum stage, GLenum portion, GLenum pname, GLint *params); GLAPI void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum variable, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum variable, GLenum pname, GLint *params); #endif #endif /* GL_NV_register_combiners */ #ifndef GL_NV_register_combiners2 #define GL_NV_register_combiners2 1 #define GL_PER_STAGE_CONSTANTS_NV 0x8535 typedef void (APIENTRYP PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCombinerStageParameterfvNV (GLenum stage, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glGetCombinerStageParameterfvNV (GLenum stage, GLenum pname, GLfloat *params); #endif #endif /* GL_NV_register_combiners2 */ #ifndef GL_NV_representative_fragment_test #define GL_NV_representative_fragment_test 1 #define GL_REPRESENTATIVE_FRAGMENT_TEST_NV 0x937F #endif /* GL_NV_representative_fragment_test */ #ifndef GL_NV_robustness_video_memory_purge #define GL_NV_robustness_video_memory_purge 1 #define GL_PURGED_CONTEXT_RESET_NV 0x92BB #endif /* GL_NV_robustness_video_memory_purge */ #ifndef GL_NV_sample_locations #define GL_NV_sample_locations 1 #define GL_SAMPLE_LOCATION_SUBPIXEL_BITS_NV 0x933D #define GL_SAMPLE_LOCATION_PIXEL_GRID_WIDTH_NV 0x933E #define GL_SAMPLE_LOCATION_PIXEL_GRID_HEIGHT_NV 0x933F #define GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_NV 0x9340 #define GL_SAMPLE_LOCATION_NV 0x8E50 #define GL_PROGRAMMABLE_SAMPLE_LOCATION_NV 0x9341 #define GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_NV 0x9342 #define GL_FRAMEBUFFER_SAMPLE_LOCATION_PIXEL_GRID_NV 0x9343 typedef void (APIENTRYP PFNGLFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLenum target, GLuint start, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLRESOLVEDEPTHVALUESNVPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFramebufferSampleLocationsfvNV (GLenum target, GLuint start, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glNamedFramebufferSampleLocationsfvNV (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glResolveDepthValuesNV (void); #endif #endif /* GL_NV_sample_locations */ #ifndef GL_NV_sample_mask_override_coverage #define GL_NV_sample_mask_override_coverage 1 #endif /* GL_NV_sample_mask_override_coverage */ #ifndef GL_NV_scissor_exclusive #define GL_NV_scissor_exclusive 1 #define GL_SCISSOR_TEST_EXCLUSIVE_NV 0x9555 #define GL_SCISSOR_BOX_EXCLUSIVE_NV 0x9556 typedef void (APIENTRYP PFNGLSCISSOREXCLUSIVENVPROC) (GLint x, GLint y, GLsizei width, GLsizei height); typedef void (APIENTRYP PFNGLSCISSOREXCLUSIVEARRAYVNVPROC) (GLuint first, GLsizei count, const GLint *v); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glScissorExclusiveNV (GLint x, GLint y, GLsizei width, GLsizei height); GLAPI void APIENTRY glScissorExclusiveArrayvNV (GLuint first, GLsizei count, const GLint *v); #endif #endif /* GL_NV_scissor_exclusive */ #ifndef GL_NV_shader_atomic_counters #define GL_NV_shader_atomic_counters 1 #endif /* GL_NV_shader_atomic_counters */ #ifndef GL_NV_shader_atomic_float #define GL_NV_shader_atomic_float 1 #endif /* GL_NV_shader_atomic_float */ #ifndef GL_NV_shader_atomic_float64 #define GL_NV_shader_atomic_float64 1 #endif /* GL_NV_shader_atomic_float64 */ #ifndef GL_NV_shader_atomic_fp16_vector #define GL_NV_shader_atomic_fp16_vector 1 #endif /* GL_NV_shader_atomic_fp16_vector */ #ifndef GL_NV_shader_atomic_int64 #define GL_NV_shader_atomic_int64 1 #endif /* GL_NV_shader_atomic_int64 */ #ifndef GL_NV_shader_buffer_load #define GL_NV_shader_buffer_load 1 #define GL_BUFFER_GPU_ADDRESS_NV 0x8F1D #define GL_GPU_ADDRESS_NV 0x8F34 #define GL_MAX_SHADER_BUFFER_ADDRESS_NV 0x8F35 typedef void (APIENTRYP PFNGLMAKEBUFFERRESIDENTNVPROC) (GLenum target, GLenum access); typedef void (APIENTRYP PFNGLMAKEBUFFERNONRESIDENTNVPROC) (GLenum target); typedef GLboolean (APIENTRYP PFNGLISBUFFERRESIDENTNVPROC) (GLenum target); typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERRESIDENTNVPROC) (GLuint buffer, GLenum access); typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERNONRESIDENTNVPROC) (GLuint buffer); typedef GLboolean (APIENTRYP PFNGLISNAMEDBUFFERRESIDENTNVPROC) (GLuint buffer); typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERUI64VNVPROC) (GLenum target, GLenum pname, GLuint64EXT *params); typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERUI64VNVPROC) (GLuint buffer, GLenum pname, GLuint64EXT *params); typedef void (APIENTRYP PFNGLGETINTEGERUI64VNVPROC) (GLenum value, GLuint64EXT *result); typedef void (APIENTRYP PFNGLUNIFORMUI64NVPROC) (GLint location, GLuint64EXT value); typedef void (APIENTRYP PFNGLUNIFORMUI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64NVPROC) (GLuint program, GLint location, GLuint64EXT value); typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glMakeBufferResidentNV (GLenum target, GLenum access); GLAPI void APIENTRY glMakeBufferNonResidentNV (GLenum target); GLAPI GLboolean APIENTRY glIsBufferResidentNV (GLenum target); GLAPI void APIENTRY glMakeNamedBufferResidentNV (GLuint buffer, GLenum access); GLAPI void APIENTRY glMakeNamedBufferNonResidentNV (GLuint buffer); GLAPI GLboolean APIENTRY glIsNamedBufferResidentNV (GLuint buffer); GLAPI void APIENTRY glGetBufferParameterui64vNV (GLenum target, GLenum pname, GLuint64EXT *params); GLAPI void APIENTRY glGetNamedBufferParameterui64vNV (GLuint buffer, GLenum pname, GLuint64EXT *params); GLAPI void APIENTRY glGetIntegerui64vNV (GLenum value, GLuint64EXT *result); GLAPI void APIENTRY glUniformui64NV (GLint location, GLuint64EXT value); GLAPI void APIENTRY glUniformui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); GLAPI void APIENTRY glProgramUniformui64NV (GLuint program, GLint location, GLuint64EXT value); GLAPI void APIENTRY glProgramUniformui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); #endif #endif /* GL_NV_shader_buffer_load */ #ifndef GL_NV_shader_buffer_store #define GL_NV_shader_buffer_store 1 #define GL_SHADER_GLOBAL_ACCESS_BARRIER_BIT_NV 0x00000010 #endif /* GL_NV_shader_buffer_store */ #ifndef GL_NV_shader_storage_buffer_object #define GL_NV_shader_storage_buffer_object 1 #endif /* GL_NV_shader_storage_buffer_object */ #ifndef GL_NV_shader_subgroup_partitioned #define GL_NV_shader_subgroup_partitioned 1 #define GL_SUBGROUP_FEATURE_PARTITIONED_BIT_NV 0x00000100 #endif /* GL_NV_shader_subgroup_partitioned */ #ifndef GL_NV_shader_texture_footprint #define GL_NV_shader_texture_footprint 1 #endif /* GL_NV_shader_texture_footprint */ #ifndef GL_NV_shader_thread_group #define GL_NV_shader_thread_group 1 #define GL_WARP_SIZE_NV 0x9339 #define GL_WARPS_PER_SM_NV 0x933A #define GL_SM_COUNT_NV 0x933B #endif /* GL_NV_shader_thread_group */ #ifndef GL_NV_shader_thread_shuffle #define GL_NV_shader_thread_shuffle 1 #endif /* GL_NV_shader_thread_shuffle */ #ifndef GL_NV_shading_rate_image #define GL_NV_shading_rate_image 1 #define GL_SHADING_RATE_IMAGE_NV 0x9563 #define GL_SHADING_RATE_NO_INVOCATIONS_NV 0x9564 #define GL_SHADING_RATE_1_INVOCATION_PER_PIXEL_NV 0x9565 #define GL_SHADING_RATE_1_INVOCATION_PER_1X2_PIXELS_NV 0x9566 #define GL_SHADING_RATE_1_INVOCATION_PER_2X1_PIXELS_NV 0x9567 #define GL_SHADING_RATE_1_INVOCATION_PER_2X2_PIXELS_NV 0x9568 #define GL_SHADING_RATE_1_INVOCATION_PER_2X4_PIXELS_NV 0x9569 #define GL_SHADING_RATE_1_INVOCATION_PER_4X2_PIXELS_NV 0x956A #define GL_SHADING_RATE_1_INVOCATION_PER_4X4_PIXELS_NV 0x956B #define GL_SHADING_RATE_2_INVOCATIONS_PER_PIXEL_NV 0x956C #define GL_SHADING_RATE_4_INVOCATIONS_PER_PIXEL_NV 0x956D #define GL_SHADING_RATE_8_INVOCATIONS_PER_PIXEL_NV 0x956E #define GL_SHADING_RATE_16_INVOCATIONS_PER_PIXEL_NV 0x956F #define GL_SHADING_RATE_IMAGE_BINDING_NV 0x955B #define GL_SHADING_RATE_IMAGE_TEXEL_WIDTH_NV 0x955C #define GL_SHADING_RATE_IMAGE_TEXEL_HEIGHT_NV 0x955D #define GL_SHADING_RATE_IMAGE_PALETTE_SIZE_NV 0x955E #define GL_MAX_COARSE_FRAGMENT_SAMPLES_NV 0x955F #define GL_SHADING_RATE_SAMPLE_ORDER_DEFAULT_NV 0x95AE #define GL_SHADING_RATE_SAMPLE_ORDER_PIXEL_MAJOR_NV 0x95AF #define GL_SHADING_RATE_SAMPLE_ORDER_SAMPLE_MAJOR_NV 0x95B0 typedef void (APIENTRYP PFNGLBINDSHADINGRATEIMAGENVPROC) (GLuint texture); typedef void (APIENTRYP PFNGLGETSHADINGRATEIMAGEPALETTENVPROC) (GLuint viewport, GLuint entry, GLenum *rate); typedef void (APIENTRYP PFNGLGETSHADINGRATESAMPLELOCATIONIVNVPROC) (GLenum rate, GLuint samples, GLuint index, GLint *location); typedef void (APIENTRYP PFNGLSHADINGRATEIMAGEBARRIERNVPROC) (GLboolean synchronize); typedef void (APIENTRYP PFNGLSHADINGRATEIMAGEPALETTENVPROC) (GLuint viewport, GLuint first, GLsizei count, const GLenum *rates); typedef void (APIENTRYP PFNGLSHADINGRATESAMPLEORDERNVPROC) (GLenum order); typedef void (APIENTRYP PFNGLSHADINGRATESAMPLEORDERCUSTOMNVPROC) (GLenum rate, GLuint samples, const GLint *locations); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBindShadingRateImageNV (GLuint texture); GLAPI void APIENTRY glGetShadingRateImagePaletteNV (GLuint viewport, GLuint entry, GLenum *rate); GLAPI void APIENTRY glGetShadingRateSampleLocationivNV (GLenum rate, GLuint samples, GLuint index, GLint *location); GLAPI void APIENTRY glShadingRateImageBarrierNV (GLboolean synchronize); GLAPI void APIENTRY glShadingRateImagePaletteNV (GLuint viewport, GLuint first, GLsizei count, const GLenum *rates); GLAPI void APIENTRY glShadingRateSampleOrderNV (GLenum order); GLAPI void APIENTRY glShadingRateSampleOrderCustomNV (GLenum rate, GLuint samples, const GLint *locations); #endif #endif /* GL_NV_shading_rate_image */ #ifndef GL_NV_stereo_view_rendering #define GL_NV_stereo_view_rendering 1 #endif /* GL_NV_stereo_view_rendering */ #ifndef GL_NV_tessellation_program5 #define GL_NV_tessellation_program5 1 #define GL_MAX_PROGRAM_PATCH_ATTRIBS_NV 0x86D8 #define GL_TESS_CONTROL_PROGRAM_NV 0x891E #define GL_TESS_EVALUATION_PROGRAM_NV 0x891F #define GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV 0x8C74 #define GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV 0x8C75 #endif /* GL_NV_tessellation_program5 */ #ifndef GL_NV_texgen_emboss #define GL_NV_texgen_emboss 1 #define GL_EMBOSS_LIGHT_NV 0x855D #define GL_EMBOSS_CONSTANT_NV 0x855E #define GL_EMBOSS_MAP_NV 0x855F #endif /* GL_NV_texgen_emboss */ #ifndef GL_NV_texgen_reflection #define GL_NV_texgen_reflection 1 #define GL_NORMAL_MAP_NV 0x8511 #define GL_REFLECTION_MAP_NV 0x8512 #endif /* GL_NV_texgen_reflection */ #ifndef GL_NV_texture_barrier #define GL_NV_texture_barrier 1 typedef void (APIENTRYP PFNGLTEXTUREBARRIERNVPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTextureBarrierNV (void); #endif #endif /* GL_NV_texture_barrier */ #ifndef GL_NV_texture_compression_vtc #define GL_NV_texture_compression_vtc 1 #endif /* GL_NV_texture_compression_vtc */ #ifndef GL_NV_texture_env_combine4 #define GL_NV_texture_env_combine4 1 #define GL_COMBINE4_NV 0x8503 #define GL_SOURCE3_RGB_NV 0x8583 #define GL_SOURCE3_ALPHA_NV 0x858B #define GL_OPERAND3_RGB_NV 0x8593 #define GL_OPERAND3_ALPHA_NV 0x859B #endif /* GL_NV_texture_env_combine4 */ #ifndef GL_NV_texture_expand_normal #define GL_NV_texture_expand_normal 1 #define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F #endif /* GL_NV_texture_expand_normal */ #ifndef GL_NV_texture_multisample #define GL_NV_texture_multisample 1 #define GL_TEXTURE_COVERAGE_SAMPLES_NV 0x9045 #define GL_TEXTURE_COLOR_SAMPLES_NV 0x9046 typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DMULTISAMPLENVPROC) (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DMULTISAMPLENVPROC) (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DMULTISAMPLECOVERAGENVPROC) (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DMULTISAMPLECOVERAGENVPROC) (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexImage2DMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); GLAPI void APIENTRY glTexImage3DMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); GLAPI void APIENTRY glTextureImage2DMultisampleNV (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); GLAPI void APIENTRY glTextureImage3DMultisampleNV (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); GLAPI void APIENTRY glTextureImage2DMultisampleCoverageNV (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); GLAPI void APIENTRY glTextureImage3DMultisampleCoverageNV (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); #endif #endif /* GL_NV_texture_multisample */ #ifndef GL_NV_texture_rectangle #define GL_NV_texture_rectangle 1 #define GL_TEXTURE_RECTANGLE_NV 0x84F5 #define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 #define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 #define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 #endif /* GL_NV_texture_rectangle */ #ifndef GL_NV_texture_rectangle_compressed #define GL_NV_texture_rectangle_compressed 1 #endif /* GL_NV_texture_rectangle_compressed */ #ifndef GL_NV_texture_shader #define GL_NV_texture_shader 1 #define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C #define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D #define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E #define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 #define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA #define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB #define GL_DSDT_MAG_INTENSITY_NV 0x86DC #define GL_SHADER_CONSISTENT_NV 0x86DD #define GL_TEXTURE_SHADER_NV 0x86DE #define GL_SHADER_OPERATION_NV 0x86DF #define GL_CULL_MODES_NV 0x86E0 #define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 #define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 #define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 #define GL_OFFSET_TEXTURE_2D_MATRIX_NV 0x86E1 #define GL_OFFSET_TEXTURE_2D_SCALE_NV 0x86E2 #define GL_OFFSET_TEXTURE_2D_BIAS_NV 0x86E3 #define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 #define GL_CONST_EYE_NV 0x86E5 #define GL_PASS_THROUGH_NV 0x86E6 #define GL_CULL_FRAGMENT_NV 0x86E7 #define GL_OFFSET_TEXTURE_2D_NV 0x86E8 #define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 #define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA #define GL_DOT_PRODUCT_NV 0x86EC #define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED #define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE #define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 #define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 #define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 #define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 #define GL_HILO_NV 0x86F4 #define GL_DSDT_NV 0x86F5 #define GL_DSDT_MAG_NV 0x86F6 #define GL_DSDT_MAG_VIB_NV 0x86F7 #define GL_HILO16_NV 0x86F8 #define GL_SIGNED_HILO_NV 0x86F9 #define GL_SIGNED_HILO16_NV 0x86FA #define GL_SIGNED_RGBA_NV 0x86FB #define GL_SIGNED_RGBA8_NV 0x86FC #define GL_SIGNED_RGB_NV 0x86FE #define GL_SIGNED_RGB8_NV 0x86FF #define GL_SIGNED_LUMINANCE_NV 0x8701 #define GL_SIGNED_LUMINANCE8_NV 0x8702 #define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 #define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 #define GL_SIGNED_ALPHA_NV 0x8705 #define GL_SIGNED_ALPHA8_NV 0x8706 #define GL_SIGNED_INTENSITY_NV 0x8707 #define GL_SIGNED_INTENSITY8_NV 0x8708 #define GL_DSDT8_NV 0x8709 #define GL_DSDT8_MAG8_NV 0x870A #define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B #define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C #define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D #define GL_HI_SCALE_NV 0x870E #define GL_LO_SCALE_NV 0x870F #define GL_DS_SCALE_NV 0x8710 #define GL_DT_SCALE_NV 0x8711 #define GL_MAGNITUDE_SCALE_NV 0x8712 #define GL_VIBRANCE_SCALE_NV 0x8713 #define GL_HI_BIAS_NV 0x8714 #define GL_LO_BIAS_NV 0x8715 #define GL_DS_BIAS_NV 0x8716 #define GL_DT_BIAS_NV 0x8717 #define GL_MAGNITUDE_BIAS_NV 0x8718 #define GL_VIBRANCE_BIAS_NV 0x8719 #define GL_TEXTURE_BORDER_VALUES_NV 0x871A #define GL_TEXTURE_HI_SIZE_NV 0x871B #define GL_TEXTURE_LO_SIZE_NV 0x871C #define GL_TEXTURE_DS_SIZE_NV 0x871D #define GL_TEXTURE_DT_SIZE_NV 0x871E #define GL_TEXTURE_MAG_SIZE_NV 0x871F #endif /* GL_NV_texture_shader */ #ifndef GL_NV_texture_shader2 #define GL_NV_texture_shader2 1 #define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF #endif /* GL_NV_texture_shader2 */ #ifndef GL_NV_texture_shader3 #define GL_NV_texture_shader3 1 #define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 #define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 #define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 #define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 #define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 #define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 #define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 #define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 #define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 #define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 #define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A #define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B #define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C #define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D #define GL_HILO8_NV 0x885E #define GL_SIGNED_HILO8_NV 0x885F #define GL_FORCE_BLUE_TO_ONE_NV 0x8860 #endif /* GL_NV_texture_shader3 */ #ifndef GL_NV_timeline_semaphore #define GL_NV_timeline_semaphore 1 #define GL_TIMELINE_SEMAPHORE_VALUE_NV 0x9595 #define GL_SEMAPHORE_TYPE_NV 0x95B3 #define GL_SEMAPHORE_TYPE_BINARY_NV 0x95B4 #define GL_SEMAPHORE_TYPE_TIMELINE_NV 0x95B5 #define GL_MAX_TIMELINE_SEMAPHORE_VALUE_DIFFERENCE_NV 0x95B6 typedef void (APIENTRYP PFNGLCREATESEMAPHORESNVPROC) (GLsizei n, GLuint *semaphores); typedef void (APIENTRYP PFNGLSEMAPHOREPARAMETERIVNVPROC) (GLuint semaphore, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLGETSEMAPHOREPARAMETERIVNVPROC) (GLuint semaphore, GLenum pname, GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glCreateSemaphoresNV (GLsizei n, GLuint *semaphores); GLAPI void APIENTRY glSemaphoreParameterivNV (GLuint semaphore, GLenum pname, const GLint *params); GLAPI void APIENTRY glGetSemaphoreParameterivNV (GLuint semaphore, GLenum pname, GLint *params); #endif #endif /* GL_NV_timeline_semaphore */ #ifndef GL_NV_transform_feedback #define GL_NV_transform_feedback 1 #define GL_BACK_PRIMARY_COLOR_NV 0x8C77 #define GL_BACK_SECONDARY_COLOR_NV 0x8C78 #define GL_TEXTURE_COORD_NV 0x8C79 #define GL_CLIP_DISTANCE_NV 0x8C7A #define GL_VERTEX_ID_NV 0x8C7B #define GL_PRIMITIVE_ID_NV 0x8C7C #define GL_GENERIC_ATTRIB_NV 0x8C7D #define GL_TRANSFORM_FEEDBACK_ATTRIBS_NV 0x8C7E #define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_NV 0x8C7F #define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_NV 0x8C80 #define GL_ACTIVE_VARYINGS_NV 0x8C81 #define GL_ACTIVE_VARYING_MAX_LENGTH_NV 0x8C82 #define GL_TRANSFORM_FEEDBACK_VARYINGS_NV 0x8C83 #define GL_TRANSFORM_FEEDBACK_BUFFER_START_NV 0x8C84 #define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_NV 0x8C85 #define GL_TRANSFORM_FEEDBACK_RECORD_NV 0x8C86 #define GL_PRIMITIVES_GENERATED_NV 0x8C87 #define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV 0x8C88 #define GL_RASTERIZER_DISCARD_NV 0x8C89 #define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_NV 0x8C8A #define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_NV 0x8C8B #define GL_INTERLEAVED_ATTRIBS_NV 0x8C8C #define GL_SEPARATE_ATTRIBS_NV 0x8C8D #define GL_TRANSFORM_FEEDBACK_BUFFER_NV 0x8C8E #define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_NV 0x8C8F #define GL_LAYER_NV 0x8DAA #define GL_NEXT_BUFFER_NV -2 #define GL_SKIP_COMPONENTS4_NV -3 #define GL_SKIP_COMPONENTS3_NV -4 #define GL_SKIP_COMPONENTS2_NV -5 #define GL_SKIP_COMPONENTS1_NV -6 typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKNVPROC) (GLenum primitiveMode); typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKNVPROC) (void); typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC) (GLsizei count, const GLint *attribs, GLenum bufferMode); typedef void (APIENTRYP PFNGLBINDBUFFERRANGENVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETNVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); typedef void (APIENTRYP PFNGLBINDBUFFERBASENVPROC) (GLenum target, GLuint index, GLuint buffer); typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC) (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); typedef void (APIENTRYP PFNGLACTIVEVARYINGNVPROC) (GLuint program, const GLchar *name); typedef GLint (APIENTRYP PFNGLGETVARYINGLOCATIONNVPROC) (GLuint program, const GLchar *name); typedef void (APIENTRYP PFNGLGETACTIVEVARYINGNVPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC) (GLuint program, GLuint index, GLint *location); typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKSTREAMATTRIBSNVPROC) (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBeginTransformFeedbackNV (GLenum primitiveMode); GLAPI void APIENTRY glEndTransformFeedbackNV (void); GLAPI void APIENTRY glTransformFeedbackAttribsNV (GLsizei count, const GLint *attribs, GLenum bufferMode); GLAPI void APIENTRY glBindBufferRangeNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); GLAPI void APIENTRY glBindBufferOffsetNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset); GLAPI void APIENTRY glBindBufferBaseNV (GLenum target, GLuint index, GLuint buffer); GLAPI void APIENTRY glTransformFeedbackVaryingsNV (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); GLAPI void APIENTRY glActiveVaryingNV (GLuint program, const GLchar *name); GLAPI GLint APIENTRY glGetVaryingLocationNV (GLuint program, const GLchar *name); GLAPI void APIENTRY glGetActiveVaryingNV (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); GLAPI void APIENTRY glGetTransformFeedbackVaryingNV (GLuint program, GLuint index, GLint *location); GLAPI void APIENTRY glTransformFeedbackStreamAttribsNV (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); #endif #endif /* GL_NV_transform_feedback */ #ifndef GL_NV_transform_feedback2 #define GL_NV_transform_feedback2 1 #define GL_TRANSFORM_FEEDBACK_NV 0x8E22 #define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED_NV 0x8E23 #define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE_NV 0x8E24 #define GL_TRANSFORM_FEEDBACK_BINDING_NV 0x8E25 typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKNVPROC) (GLenum target, GLuint id); typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSNVPROC) (GLsizei n, const GLuint *ids); typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSNVPROC) (GLsizei n, GLuint *ids); typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKNVPROC) (GLuint id); typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKNVPROC) (void); typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKNVPROC) (void); typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKNVPROC) (GLenum mode, GLuint id); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBindTransformFeedbackNV (GLenum target, GLuint id); GLAPI void APIENTRY glDeleteTransformFeedbacksNV (GLsizei n, const GLuint *ids); GLAPI void APIENTRY glGenTransformFeedbacksNV (GLsizei n, GLuint *ids); GLAPI GLboolean APIENTRY glIsTransformFeedbackNV (GLuint id); GLAPI void APIENTRY glPauseTransformFeedbackNV (void); GLAPI void APIENTRY glResumeTransformFeedbackNV (void); GLAPI void APIENTRY glDrawTransformFeedbackNV (GLenum mode, GLuint id); #endif #endif /* GL_NV_transform_feedback2 */ #ifndef GL_NV_uniform_buffer_unified_memory #define GL_NV_uniform_buffer_unified_memory 1 #define GL_UNIFORM_BUFFER_UNIFIED_NV 0x936E #define GL_UNIFORM_BUFFER_ADDRESS_NV 0x936F #define GL_UNIFORM_BUFFER_LENGTH_NV 0x9370 #endif /* GL_NV_uniform_buffer_unified_memory */ #ifndef GL_NV_vdpau_interop #define GL_NV_vdpau_interop 1 typedef GLintptr GLvdpauSurfaceNV; #define GL_SURFACE_STATE_NV 0x86EB #define GL_SURFACE_REGISTERED_NV 0x86FD #define GL_SURFACE_MAPPED_NV 0x8700 #define GL_WRITE_DISCARD_NV 0x88BE typedef void (APIENTRYP PFNGLVDPAUINITNVPROC) (const void *vdpDevice, const void *getProcAddress); typedef void (APIENTRYP PFNGLVDPAUFININVPROC) (void); typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTERVIDEOSURFACENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTEROUTPUTSURFACENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); typedef GLboolean (APIENTRYP PFNGLVDPAUISSURFACENVPROC) (GLvdpauSurfaceNV surface); typedef void (APIENTRYP PFNGLVDPAUUNREGISTERSURFACENVPROC) (GLvdpauSurfaceNV surface); typedef void (APIENTRYP PFNGLVDPAUGETSURFACEIVNVPROC) (GLvdpauSurfaceNV surface, GLenum pname, GLsizei count, GLsizei *length, GLint *values); typedef void (APIENTRYP PFNGLVDPAUSURFACEACCESSNVPROC) (GLvdpauSurfaceNV surface, GLenum access); typedef void (APIENTRYP PFNGLVDPAUMAPSURFACESNVPROC) (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); typedef void (APIENTRYP PFNGLVDPAUUNMAPSURFACESNVPROC) (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVDPAUInitNV (const void *vdpDevice, const void *getProcAddress); GLAPI void APIENTRY glVDPAUFiniNV (void); GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterVideoSurfaceNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterOutputSurfaceNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); GLAPI GLboolean APIENTRY glVDPAUIsSurfaceNV (GLvdpauSurfaceNV surface); GLAPI void APIENTRY glVDPAUUnregisterSurfaceNV (GLvdpauSurfaceNV surface); GLAPI void APIENTRY glVDPAUGetSurfaceivNV (GLvdpauSurfaceNV surface, GLenum pname, GLsizei count, GLsizei *length, GLint *values); GLAPI void APIENTRY glVDPAUSurfaceAccessNV (GLvdpauSurfaceNV surface, GLenum access); GLAPI void APIENTRY glVDPAUMapSurfacesNV (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); GLAPI void APIENTRY glVDPAUUnmapSurfacesNV (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); #endif #endif /* GL_NV_vdpau_interop */ #ifndef GL_NV_vdpau_interop2 #define GL_NV_vdpau_interop2 1 typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTERVIDEOSURFACEWITHPICTURESTRUCTURENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames, GLboolean isFrameStructure); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterVideoSurfaceWithPictureStructureNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames, GLboolean isFrameStructure); #endif #endif /* GL_NV_vdpau_interop2 */ #ifndef GL_NV_vertex_array_range #define GL_NV_vertex_array_range 1 #define GL_VERTEX_ARRAY_RANGE_NV 0x851D #define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E #define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F #define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 #define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); typedef void (APIENTRYP PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, const void *pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFlushVertexArrayRangeNV (void); GLAPI void APIENTRY glVertexArrayRangeNV (GLsizei length, const void *pointer); #endif #endif /* GL_NV_vertex_array_range */ #ifndef GL_NV_vertex_array_range2 #define GL_NV_vertex_array_range2 1 #define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 #endif /* GL_NV_vertex_array_range2 */ #ifndef GL_NV_vertex_attrib_integer_64bit #define GL_NV_vertex_attrib_integer_64bit 1 typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64NVPROC) (GLuint index, GLint64EXT x); typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y); typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64VNVPROC) (GLuint index, const GLint64EXT *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64VNVPROC) (GLuint index, const GLint64EXT *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64VNVPROC) (GLuint index, const GLint64EXT *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64VNVPROC) (GLuint index, const GLint64EXT *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64NVPROC) (GLuint index, GLuint64EXT x); typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y); typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VNVPROC) (GLuint index, const GLuint64EXT *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64VNVPROC) (GLuint index, const GLuint64EXT *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64VNVPROC) (GLuint index, const GLuint64EXT *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64VNVPROC) (GLuint index, const GLuint64EXT *v); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLI64VNVPROC) (GLuint index, GLenum pname, GLint64EXT *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VNVPROC) (GLuint index, GLenum pname, GLuint64EXT *params); typedef void (APIENTRYP PFNGLVERTEXATTRIBLFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glVertexAttribL1i64NV (GLuint index, GLint64EXT x); GLAPI void APIENTRY glVertexAttribL2i64NV (GLuint index, GLint64EXT x, GLint64EXT y); GLAPI void APIENTRY glVertexAttribL3i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); GLAPI void APIENTRY glVertexAttribL4i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); GLAPI void APIENTRY glVertexAttribL1i64vNV (GLuint index, const GLint64EXT *v); GLAPI void APIENTRY glVertexAttribL2i64vNV (GLuint index, const GLint64EXT *v); GLAPI void APIENTRY glVertexAttribL3i64vNV (GLuint index, const GLint64EXT *v); GLAPI void APIENTRY glVertexAttribL4i64vNV (GLuint index, const GLint64EXT *v); GLAPI void APIENTRY glVertexAttribL1ui64NV (GLuint index, GLuint64EXT x); GLAPI void APIENTRY glVertexAttribL2ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y); GLAPI void APIENTRY glVertexAttribL3ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); GLAPI void APIENTRY glVertexAttribL4ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); GLAPI void APIENTRY glVertexAttribL1ui64vNV (GLuint index, const GLuint64EXT *v); GLAPI void APIENTRY glVertexAttribL2ui64vNV (GLuint index, const GLuint64EXT *v); GLAPI void APIENTRY glVertexAttribL3ui64vNV (GLuint index, const GLuint64EXT *v); GLAPI void APIENTRY glVertexAttribL4ui64vNV (GLuint index, const GLuint64EXT *v); GLAPI void APIENTRY glGetVertexAttribLi64vNV (GLuint index, GLenum pname, GLint64EXT *params); GLAPI void APIENTRY glGetVertexAttribLui64vNV (GLuint index, GLenum pname, GLuint64EXT *params); GLAPI void APIENTRY glVertexAttribLFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); #endif #endif /* GL_NV_vertex_attrib_integer_64bit */ #ifndef GL_NV_vertex_buffer_unified_memory #define GL_NV_vertex_buffer_unified_memory 1 #define GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV 0x8F1E #define GL_ELEMENT_ARRAY_UNIFIED_NV 0x8F1F #define GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV 0x8F20 #define GL_VERTEX_ARRAY_ADDRESS_NV 0x8F21 #define GL_NORMAL_ARRAY_ADDRESS_NV 0x8F22 #define GL_COLOR_ARRAY_ADDRESS_NV 0x8F23 #define GL_INDEX_ARRAY_ADDRESS_NV 0x8F24 #define GL_TEXTURE_COORD_ARRAY_ADDRESS_NV 0x8F25 #define GL_EDGE_FLAG_ARRAY_ADDRESS_NV 0x8F26 #define GL_SECONDARY_COLOR_ARRAY_ADDRESS_NV 0x8F27 #define GL_FOG_COORD_ARRAY_ADDRESS_NV 0x8F28 #define GL_ELEMENT_ARRAY_ADDRESS_NV 0x8F29 #define GL_VERTEX_ATTRIB_ARRAY_LENGTH_NV 0x8F2A #define GL_VERTEX_ARRAY_LENGTH_NV 0x8F2B #define GL_NORMAL_ARRAY_LENGTH_NV 0x8F2C #define GL_COLOR_ARRAY_LENGTH_NV 0x8F2D #define GL_INDEX_ARRAY_LENGTH_NV 0x8F2E #define GL_TEXTURE_COORD_ARRAY_LENGTH_NV 0x8F2F #define GL_EDGE_FLAG_ARRAY_LENGTH_NV 0x8F30 #define GL_SECONDARY_COLOR_ARRAY_LENGTH_NV 0x8F31 #define GL_FOG_COORD_ARRAY_LENGTH_NV 0x8F32 #define GL_ELEMENT_ARRAY_LENGTH_NV 0x8F33 #define GL_DRAW_INDIRECT_UNIFIED_NV 0x8F40 #define GL_DRAW_INDIRECT_ADDRESS_NV 0x8F41 #define GL_DRAW_INDIRECT_LENGTH_NV 0x8F42 typedef void (APIENTRYP PFNGLBUFFERADDRESSRANGENVPROC) (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); typedef void (APIENTRYP PFNGLVERTEXFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); typedef void (APIENTRYP PFNGLNORMALFORMATNVPROC) (GLenum type, GLsizei stride); typedef void (APIENTRYP PFNGLCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); typedef void (APIENTRYP PFNGLINDEXFORMATNVPROC) (GLenum type, GLsizei stride); typedef void (APIENTRYP PFNGLTEXCOORDFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); typedef void (APIENTRYP PFNGLEDGEFLAGFORMATNVPROC) (GLsizei stride); typedef void (APIENTRYP PFNGLSECONDARYCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); typedef void (APIENTRYP PFNGLFOGCOORDFORMATNVPROC) (GLenum type, GLsizei stride); typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); typedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBufferAddressRangeNV (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); GLAPI void APIENTRY glVertexFormatNV (GLint size, GLenum type, GLsizei stride); GLAPI void APIENTRY glNormalFormatNV (GLenum type, GLsizei stride); GLAPI void APIENTRY glColorFormatNV (GLint size, GLenum type, GLsizei stride); GLAPI void APIENTRY glIndexFormatNV (GLenum type, GLsizei stride); GLAPI void APIENTRY glTexCoordFormatNV (GLint size, GLenum type, GLsizei stride); GLAPI void APIENTRY glEdgeFlagFormatNV (GLsizei stride); GLAPI void APIENTRY glSecondaryColorFormatNV (GLint size, GLenum type, GLsizei stride); GLAPI void APIENTRY glFogCoordFormatNV (GLenum type, GLsizei stride); GLAPI void APIENTRY glVertexAttribFormatNV (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); GLAPI void APIENTRY glVertexAttribIFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); GLAPI void APIENTRY glGetIntegerui64i_vNV (GLenum value, GLuint index, GLuint64EXT *result); #endif #endif /* GL_NV_vertex_buffer_unified_memory */ #ifndef GL_NV_vertex_program #define GL_NV_vertex_program 1 #define GL_VERTEX_PROGRAM_NV 0x8620 #define GL_VERTEX_STATE_PROGRAM_NV 0x8621 #define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 #define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 #define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 #define GL_CURRENT_ATTRIB_NV 0x8626 #define GL_PROGRAM_LENGTH_NV 0x8627 #define GL_PROGRAM_STRING_NV 0x8628 #define GL_MODELVIEW_PROJECTION_NV 0x8629 #define GL_IDENTITY_NV 0x862A #define GL_INVERSE_NV 0x862B #define GL_TRANSPOSE_NV 0x862C #define GL_INVERSE_TRANSPOSE_NV 0x862D #define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E #define GL_MAX_TRACK_MATRICES_NV 0x862F #define GL_MATRIX0_NV 0x8630 #define GL_MATRIX1_NV 0x8631 #define GL_MATRIX2_NV 0x8632 #define GL_MATRIX3_NV 0x8633 #define GL_MATRIX4_NV 0x8634 #define GL_MATRIX5_NV 0x8635 #define GL_MATRIX6_NV 0x8636 #define GL_MATRIX7_NV 0x8637 #define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 #define GL_CURRENT_MATRIX_NV 0x8641 #define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 #define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 #define GL_PROGRAM_PARAMETER_NV 0x8644 #define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 #define GL_PROGRAM_TARGET_NV 0x8646 #define GL_PROGRAM_RESIDENT_NV 0x8647 #define GL_TRACK_MATRIX_NV 0x8648 #define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 #define GL_VERTEX_PROGRAM_BINDING_NV 0x864A #define GL_PROGRAM_ERROR_POSITION_NV 0x864B #define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 #define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 #define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 #define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 #define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 #define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 #define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 #define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 #define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 #define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 #define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A #define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B #define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C #define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D #define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E #define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F #define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 #define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 #define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 #define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 #define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 #define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 #define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 #define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 #define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 #define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 #define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A #define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B #define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C #define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D #define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E #define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F #define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 #define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 #define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 #define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 #define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 #define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 #define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 #define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 #define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 #define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 #define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A #define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B #define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C #define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D #define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E #define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F typedef GLboolean (APIENTRYP PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint *programs, GLboolean *residences); typedef void (APIENTRYP PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); typedef void (APIENTRYP PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); typedef void (APIENTRYP PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat *params); typedef void (APIENTRYP PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint *programs); typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble *params); typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte *program); typedef void (APIENTRYP PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, void **pointer); typedef GLboolean (APIENTRYP PFNGLISPROGRAMNVPROC) (GLuint id); typedef void (APIENTRYP PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte *program); typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLdouble *v); typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); typedef void (APIENTRYP PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint fsize, GLenum type, GLsizei stride, const void *pointer); typedef void (APIENTRYP PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); typedef void (APIENTRYP PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei count, const GLubyte *v); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLboolean APIENTRY glAreProgramsResidentNV (GLsizei n, const GLuint *programs, GLboolean *residences); GLAPI void APIENTRY glBindProgramNV (GLenum target, GLuint id); GLAPI void APIENTRY glDeleteProgramsNV (GLsizei n, const GLuint *programs); GLAPI void APIENTRY glExecuteProgramNV (GLenum target, GLuint id, const GLfloat *params); GLAPI void APIENTRY glGenProgramsNV (GLsizei n, GLuint *programs); GLAPI void APIENTRY glGetProgramParameterdvNV (GLenum target, GLuint index, GLenum pname, GLdouble *params); GLAPI void APIENTRY glGetProgramParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetProgramivNV (GLuint id, GLenum pname, GLint *params); GLAPI void APIENTRY glGetProgramStringNV (GLuint id, GLenum pname, GLubyte *program); GLAPI void APIENTRY glGetTrackMatrixivNV (GLenum target, GLuint address, GLenum pname, GLint *params); GLAPI void APIENTRY glGetVertexAttribdvNV (GLuint index, GLenum pname, GLdouble *params); GLAPI void APIENTRY glGetVertexAttribfvNV (GLuint index, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetVertexAttribivNV (GLuint index, GLenum pname, GLint *params); GLAPI void APIENTRY glGetVertexAttribPointervNV (GLuint index, GLenum pname, void **pointer); GLAPI GLboolean APIENTRY glIsProgramNV (GLuint id); GLAPI void APIENTRY glLoadProgramNV (GLenum target, GLuint id, GLsizei len, const GLubyte *program); GLAPI void APIENTRY glProgramParameter4dNV (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glProgramParameter4dvNV (GLenum target, GLuint index, const GLdouble *v); GLAPI void APIENTRY glProgramParameter4fNV (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glProgramParameter4fvNV (GLenum target, GLuint index, const GLfloat *v); GLAPI void APIENTRY glProgramParameters4dvNV (GLenum target, GLuint index, GLsizei count, const GLdouble *v); GLAPI void APIENTRY glProgramParameters4fvNV (GLenum target, GLuint index, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glRequestResidentProgramsNV (GLsizei n, const GLuint *programs); GLAPI void APIENTRY glTrackMatrixNV (GLenum target, GLuint address, GLenum matrix, GLenum transform); GLAPI void APIENTRY glVertexAttribPointerNV (GLuint index, GLint fsize, GLenum type, GLsizei stride, const void *pointer); GLAPI void APIENTRY glVertexAttrib1dNV (GLuint index, GLdouble x); GLAPI void APIENTRY glVertexAttrib1dvNV (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib1fNV (GLuint index, GLfloat x); GLAPI void APIENTRY glVertexAttrib1fvNV (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib1sNV (GLuint index, GLshort x); GLAPI void APIENTRY glVertexAttrib1svNV (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib2dNV (GLuint index, GLdouble x, GLdouble y); GLAPI void APIENTRY glVertexAttrib2dvNV (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib2fNV (GLuint index, GLfloat x, GLfloat y); GLAPI void APIENTRY glVertexAttrib2fvNV (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib2sNV (GLuint index, GLshort x, GLshort y); GLAPI void APIENTRY glVertexAttrib2svNV (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib3dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z); GLAPI void APIENTRY glVertexAttrib3dvNV (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib3fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glVertexAttrib3fvNV (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib3sNV (GLuint index, GLshort x, GLshort y, GLshort z); GLAPI void APIENTRY glVertexAttrib3svNV (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib4dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); GLAPI void APIENTRY glVertexAttrib4dvNV (GLuint index, const GLdouble *v); GLAPI void APIENTRY glVertexAttrib4fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glVertexAttrib4fvNV (GLuint index, const GLfloat *v); GLAPI void APIENTRY glVertexAttrib4sNV (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); GLAPI void APIENTRY glVertexAttrib4svNV (GLuint index, const GLshort *v); GLAPI void APIENTRY glVertexAttrib4ubNV (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); GLAPI void APIENTRY glVertexAttrib4ubvNV (GLuint index, const GLubyte *v); GLAPI void APIENTRY glVertexAttribs1dvNV (GLuint index, GLsizei count, const GLdouble *v); GLAPI void APIENTRY glVertexAttribs1fvNV (GLuint index, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glVertexAttribs1svNV (GLuint index, GLsizei count, const GLshort *v); GLAPI void APIENTRY glVertexAttribs2dvNV (GLuint index, GLsizei count, const GLdouble *v); GLAPI void APIENTRY glVertexAttribs2fvNV (GLuint index, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glVertexAttribs2svNV (GLuint index, GLsizei count, const GLshort *v); GLAPI void APIENTRY glVertexAttribs3dvNV (GLuint index, GLsizei count, const GLdouble *v); GLAPI void APIENTRY glVertexAttribs3fvNV (GLuint index, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glVertexAttribs3svNV (GLuint index, GLsizei count, const GLshort *v); GLAPI void APIENTRY glVertexAttribs4dvNV (GLuint index, GLsizei count, const GLdouble *v); GLAPI void APIENTRY glVertexAttribs4fvNV (GLuint index, GLsizei count, const GLfloat *v); GLAPI void APIENTRY glVertexAttribs4svNV (GLuint index, GLsizei count, const GLshort *v); GLAPI void APIENTRY glVertexAttribs4ubvNV (GLuint index, GLsizei count, const GLubyte *v); #endif #endif /* GL_NV_vertex_program */ #ifndef GL_NV_vertex_program1_1 #define GL_NV_vertex_program1_1 1 #endif /* GL_NV_vertex_program1_1 */ #ifndef GL_NV_vertex_program2 #define GL_NV_vertex_program2 1 #endif /* GL_NV_vertex_program2 */ #ifndef GL_NV_vertex_program2_option #define GL_NV_vertex_program2_option 1 #endif /* GL_NV_vertex_program2_option */ #ifndef GL_NV_vertex_program3 #define GL_NV_vertex_program3 1 #endif /* GL_NV_vertex_program3 */ #ifndef GL_NV_vertex_program4 #define GL_NV_vertex_program4 1 #define GL_VERTEX_ATTRIB_ARRAY_INTEGER_NV 0x88FD #endif /* GL_NV_vertex_program4 */ #ifndef GL_NV_video_capture #define GL_NV_video_capture 1 #define GL_VIDEO_BUFFER_NV 0x9020 #define GL_VIDEO_BUFFER_BINDING_NV 0x9021 #define GL_FIELD_UPPER_NV 0x9022 #define GL_FIELD_LOWER_NV 0x9023 #define GL_NUM_VIDEO_CAPTURE_STREAMS_NV 0x9024 #define GL_NEXT_VIDEO_CAPTURE_BUFFER_STATUS_NV 0x9025 #define GL_VIDEO_CAPTURE_TO_422_SUPPORTED_NV 0x9026 #define GL_LAST_VIDEO_CAPTURE_STATUS_NV 0x9027 #define GL_VIDEO_BUFFER_PITCH_NV 0x9028 #define GL_VIDEO_COLOR_CONVERSION_MATRIX_NV 0x9029 #define GL_VIDEO_COLOR_CONVERSION_MAX_NV 0x902A #define GL_VIDEO_COLOR_CONVERSION_MIN_NV 0x902B #define GL_VIDEO_COLOR_CONVERSION_OFFSET_NV 0x902C #define GL_VIDEO_BUFFER_INTERNAL_FORMAT_NV 0x902D #define GL_PARTIAL_SUCCESS_NV 0x902E #define GL_SUCCESS_NV 0x902F #define GL_FAILURE_NV 0x9030 #define GL_YCBYCR8_422_NV 0x9031 #define GL_YCBAYCR8A_4224_NV 0x9032 #define GL_Z6Y10Z6CB10Z6Y10Z6CR10_422_NV 0x9033 #define GL_Z6Y10Z6CB10Z6A10Z6Y10Z6CR10Z6A10_4224_NV 0x9034 #define GL_Z4Y12Z4CB12Z4Y12Z4CR12_422_NV 0x9035 #define GL_Z4Y12Z4CB12Z4A12Z4Y12Z4CR12Z4A12_4224_NV 0x9036 #define GL_Z4Y12Z4CB12Z4CR12_444_NV 0x9037 #define GL_VIDEO_CAPTURE_FRAME_WIDTH_NV 0x9038 #define GL_VIDEO_CAPTURE_FRAME_HEIGHT_NV 0x9039 #define GL_VIDEO_CAPTURE_FIELD_UPPER_HEIGHT_NV 0x903A #define GL_VIDEO_CAPTURE_FIELD_LOWER_HEIGHT_NV 0x903B #define GL_VIDEO_CAPTURE_SURFACE_ORIGIN_NV 0x903C typedef void (APIENTRYP PFNGLBEGINVIDEOCAPTURENVPROC) (GLuint video_capture_slot); typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMBUFFERNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMTEXTURENVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); typedef void (APIENTRYP PFNGLENDVIDEOCAPTURENVPROC) (GLuint video_capture_slot); typedef void (APIENTRYP PFNGLGETVIDEOCAPTUREIVNVPROC) (GLuint video_capture_slot, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); typedef GLenum (APIENTRYP PFNGLVIDEOCAPTURENVPROC) (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glBeginVideoCaptureNV (GLuint video_capture_slot); GLAPI void APIENTRY glBindVideoCaptureStreamBufferNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); GLAPI void APIENTRY glBindVideoCaptureStreamTextureNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); GLAPI void APIENTRY glEndVideoCaptureNV (GLuint video_capture_slot); GLAPI void APIENTRY glGetVideoCaptureivNV (GLuint video_capture_slot, GLenum pname, GLint *params); GLAPI void APIENTRY glGetVideoCaptureStreamivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); GLAPI void APIENTRY glGetVideoCaptureStreamfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetVideoCaptureStreamdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); GLAPI GLenum APIENTRY glVideoCaptureNV (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); GLAPI void APIENTRY glVideoCaptureStreamParameterivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); GLAPI void APIENTRY glVideoCaptureStreamParameterfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glVideoCaptureStreamParameterdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); #endif #endif /* GL_NV_video_capture */ #ifndef GL_NV_viewport_array2 #define GL_NV_viewport_array2 1 #endif /* GL_NV_viewport_array2 */ #ifndef GL_NV_viewport_swizzle #define GL_NV_viewport_swizzle 1 #define GL_VIEWPORT_SWIZZLE_POSITIVE_X_NV 0x9350 #define GL_VIEWPORT_SWIZZLE_NEGATIVE_X_NV 0x9351 #define GL_VIEWPORT_SWIZZLE_POSITIVE_Y_NV 0x9352 #define GL_VIEWPORT_SWIZZLE_NEGATIVE_Y_NV 0x9353 #define GL_VIEWPORT_SWIZZLE_POSITIVE_Z_NV 0x9354 #define GL_VIEWPORT_SWIZZLE_NEGATIVE_Z_NV 0x9355 #define GL_VIEWPORT_SWIZZLE_POSITIVE_W_NV 0x9356 #define GL_VIEWPORT_SWIZZLE_NEGATIVE_W_NV 0x9357 #define GL_VIEWPORT_SWIZZLE_X_NV 0x9358 #define GL_VIEWPORT_SWIZZLE_Y_NV 0x9359 #define GL_VIEWPORT_SWIZZLE_Z_NV 0x935A #define GL_VIEWPORT_SWIZZLE_W_NV 0x935B typedef void (APIENTRYP PFNGLVIEWPORTSWIZZLENVPROC) (GLuint index, GLenum swizzlex, GLenum swizzley, GLenum swizzlez, GLenum swizzlew); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glViewportSwizzleNV (GLuint index, GLenum swizzlex, GLenum swizzley, GLenum swizzlez, GLenum swizzlew); #endif #endif /* GL_NV_viewport_swizzle */ #ifndef GL_OML_interlace #define GL_OML_interlace 1 #define GL_INTERLACE_OML 0x8980 #define GL_INTERLACE_READ_OML 0x8981 #endif /* GL_OML_interlace */ #ifndef GL_OML_resample #define GL_OML_resample 1 #define GL_PACK_RESAMPLE_OML 0x8984 #define GL_UNPACK_RESAMPLE_OML 0x8985 #define GL_RESAMPLE_REPLICATE_OML 0x8986 #define GL_RESAMPLE_ZERO_FILL_OML 0x8987 #define GL_RESAMPLE_AVERAGE_OML 0x8988 #define GL_RESAMPLE_DECIMATE_OML 0x8989 #endif /* GL_OML_resample */ #ifndef GL_OML_subsample #define GL_OML_subsample 1 #define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 #define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 #endif /* GL_OML_subsample */ #ifndef GL_OVR_multiview #define GL_OVR_multiview 1 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR 0x9630 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR 0x9632 #define GL_MAX_VIEWS_OVR 0x9631 #define GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR 0x9633 typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFramebufferTextureMultiviewOVR (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews); #endif #endif /* GL_OVR_multiview */ #ifndef GL_OVR_multiview2 #define GL_OVR_multiview2 1 #endif /* GL_OVR_multiview2 */ #ifndef GL_PGI_misc_hints #define GL_PGI_misc_hints 1 #define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 #define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD #define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE #define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 #define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 #define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 #define GL_ALWAYS_FAST_HINT_PGI 0x1A20C #define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D #define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E #define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F #define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 #define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 #define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 #define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 #define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 #define GL_FULL_STIPPLE_HINT_PGI 0x1A219 #define GL_CLIP_NEAR_HINT_PGI 0x1A220 #define GL_CLIP_FAR_HINT_PGI 0x1A221 #define GL_WIDE_LINE_HINT_PGI 0x1A222 #define GL_BACK_NORMALS_HINT_PGI 0x1A223 typedef void (APIENTRYP PFNGLHINTPGIPROC) (GLenum target, GLint mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glHintPGI (GLenum target, GLint mode); #endif #endif /* GL_PGI_misc_hints */ #ifndef GL_PGI_vertex_hints #define GL_PGI_vertex_hints 1 #define GL_VERTEX_DATA_HINT_PGI 0x1A22A #define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B #define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C #define GL_MAX_VERTEX_HINT_PGI 0x1A22D #define GL_COLOR3_BIT_PGI 0x00010000 #define GL_COLOR4_BIT_PGI 0x00020000 #define GL_EDGEFLAG_BIT_PGI 0x00040000 #define GL_INDEX_BIT_PGI 0x00080000 #define GL_MAT_AMBIENT_BIT_PGI 0x00100000 #define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 #define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 #define GL_MAT_EMISSION_BIT_PGI 0x00800000 #define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 #define GL_MAT_SHININESS_BIT_PGI 0x02000000 #define GL_MAT_SPECULAR_BIT_PGI 0x04000000 #define GL_NORMAL_BIT_PGI 0x08000000 #define GL_TEXCOORD1_BIT_PGI 0x10000000 #define GL_TEXCOORD2_BIT_PGI 0x20000000 #define GL_TEXCOORD3_BIT_PGI 0x40000000 #define GL_TEXCOORD4_BIT_PGI 0x80000000 #define GL_VERTEX23_BIT_PGI 0x00000004 #define GL_VERTEX4_BIT_PGI 0x00000008 #endif /* GL_PGI_vertex_hints */ #ifndef GL_REND_screen_coordinates #define GL_REND_screen_coordinates 1 #define GL_SCREEN_COORDINATES_REND 0x8490 #define GL_INVERTED_SCREEN_W_REND 0x8491 #endif /* GL_REND_screen_coordinates */ #ifndef GL_S3_s3tc #define GL_S3_s3tc 1 #define GL_RGB_S3TC 0x83A0 #define GL_RGB4_S3TC 0x83A1 #define GL_RGBA_S3TC 0x83A2 #define GL_RGBA4_S3TC 0x83A3 #define GL_RGBA_DXT5_S3TC 0x83A4 #define GL_RGBA4_DXT5_S3TC 0x83A5 #endif /* GL_S3_s3tc */ #ifndef GL_SGIS_detail_texture #define GL_SGIS_detail_texture 1 #define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 #define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 #define GL_LINEAR_DETAIL_SGIS 0x8097 #define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 #define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 #define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A #define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B #define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C typedef void (APIENTRYP PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); typedef void (APIENTRYP PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDetailTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); GLAPI void APIENTRY glGetDetailTexFuncSGIS (GLenum target, GLfloat *points); #endif #endif /* GL_SGIS_detail_texture */ #ifndef GL_SGIS_fog_function #define GL_SGIS_fog_function 1 #define GL_FOG_FUNC_SGIS 0x812A #define GL_FOG_FUNC_POINTS_SGIS 0x812B #define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C typedef void (APIENTRYP PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); typedef void (APIENTRYP PFNGLGETFOGFUNCSGISPROC) (GLfloat *points); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFogFuncSGIS (GLsizei n, const GLfloat *points); GLAPI void APIENTRY glGetFogFuncSGIS (GLfloat *points); #endif #endif /* GL_SGIS_fog_function */ #ifndef GL_SGIS_generate_mipmap #define GL_SGIS_generate_mipmap 1 #define GL_GENERATE_MIPMAP_SGIS 0x8191 #define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 #endif /* GL_SGIS_generate_mipmap */ #ifndef GL_SGIS_multisample #define GL_SGIS_multisample 1 #define GL_MULTISAMPLE_SGIS 0x809D #define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E #define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F #define GL_SAMPLE_MASK_SGIS 0x80A0 #define GL_1PASS_SGIS 0x80A1 #define GL_2PASS_0_SGIS 0x80A2 #define GL_2PASS_1_SGIS 0x80A3 #define GL_4PASS_0_SGIS 0x80A4 #define GL_4PASS_1_SGIS 0x80A5 #define GL_4PASS_2_SGIS 0x80A6 #define GL_4PASS_3_SGIS 0x80A7 #define GL_SAMPLE_BUFFERS_SGIS 0x80A8 #define GL_SAMPLES_SGIS 0x80A9 #define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA #define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB #define GL_SAMPLE_PATTERN_SGIS 0x80AC typedef void (APIENTRYP PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); typedef void (APIENTRYP PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSampleMaskSGIS (GLclampf value, GLboolean invert); GLAPI void APIENTRY glSamplePatternSGIS (GLenum pattern); #endif #endif /* GL_SGIS_multisample */ #ifndef GL_SGIS_pixel_texture #define GL_SGIS_pixel_texture 1 #define GL_PIXEL_TEXTURE_SGIS 0x8353 #define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 #define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 #define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPixelTexGenParameteriSGIS (GLenum pname, GLint param); GLAPI void APIENTRY glPixelTexGenParameterivSGIS (GLenum pname, const GLint *params); GLAPI void APIENTRY glPixelTexGenParameterfSGIS (GLenum pname, GLfloat param); GLAPI void APIENTRY glPixelTexGenParameterfvSGIS (GLenum pname, const GLfloat *params); GLAPI void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum pname, GLint *params); GLAPI void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum pname, GLfloat *params); #endif #endif /* GL_SGIS_pixel_texture */ #ifndef GL_SGIS_point_line_texgen #define GL_SGIS_point_line_texgen 1 #define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 #define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 #define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 #define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 #define GL_EYE_POINT_SGIS 0x81F4 #define GL_OBJECT_POINT_SGIS 0x81F5 #define GL_EYE_LINE_SGIS 0x81F6 #define GL_OBJECT_LINE_SGIS 0x81F7 #endif /* GL_SGIS_point_line_texgen */ #ifndef GL_SGIS_point_parameters #define GL_SGIS_point_parameters 1 #define GL_POINT_SIZE_MIN_SGIS 0x8126 #define GL_POINT_SIZE_MAX_SGIS 0x8127 #define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 #define GL_DISTANCE_ATTENUATION_SGIS 0x8129 typedef void (APIENTRYP PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPointParameterfSGIS (GLenum pname, GLfloat param); GLAPI void APIENTRY glPointParameterfvSGIS (GLenum pname, const GLfloat *params); #endif #endif /* GL_SGIS_point_parameters */ #ifndef GL_SGIS_sharpen_texture #define GL_SGIS_sharpen_texture 1 #define GL_LINEAR_SHARPEN_SGIS 0x80AD #define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE #define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF #define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 typedef void (APIENTRYP PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); typedef void (APIENTRYP PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSharpenTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); GLAPI void APIENTRY glGetSharpenTexFuncSGIS (GLenum target, GLfloat *points); #endif #endif /* GL_SGIS_sharpen_texture */ #ifndef GL_SGIS_texture4D #define GL_SGIS_texture4D 1 #define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 #define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 #define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 #define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 #define GL_TEXTURE_4D_SGIS 0x8134 #define GL_PROXY_TEXTURE_4D_SGIS 0x8135 #define GL_TEXTURE_4DSIZE_SGIS 0x8136 #define GL_TEXTURE_WRAP_Q_SGIS 0x8137 #define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 #define GL_TEXTURE_4D_BINDING_SGIS 0x814F typedef void (APIENTRYP PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const void *pixels); typedef void (APIENTRYP PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const void *pixels); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTexImage4DSGIS (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const void *pixels); GLAPI void APIENTRY glTexSubImage4DSGIS (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const void *pixels); #endif #endif /* GL_SGIS_texture4D */ #ifndef GL_SGIS_texture_border_clamp #define GL_SGIS_texture_border_clamp 1 #define GL_CLAMP_TO_BORDER_SGIS 0x812D #endif /* GL_SGIS_texture_border_clamp */ #ifndef GL_SGIS_texture_color_mask #define GL_SGIS_texture_color_mask 1 #define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF typedef void (APIENTRYP PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTextureColorMaskSGIS (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); #endif #endif /* GL_SGIS_texture_color_mask */ #ifndef GL_SGIS_texture_edge_clamp #define GL_SGIS_texture_edge_clamp 1 #define GL_CLAMP_TO_EDGE_SGIS 0x812F #endif /* GL_SGIS_texture_edge_clamp */ #ifndef GL_SGIS_texture_filter4 #define GL_SGIS_texture_filter4 1 #define GL_FILTER4_SGIS 0x8146 #define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 typedef void (APIENTRYP PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); typedef void (APIENTRYP PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGetTexFilterFuncSGIS (GLenum target, GLenum filter, GLfloat *weights); GLAPI void APIENTRY glTexFilterFuncSGIS (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); #endif #endif /* GL_SGIS_texture_filter4 */ #ifndef GL_SGIS_texture_lod #define GL_SGIS_texture_lod 1 #define GL_TEXTURE_MIN_LOD_SGIS 0x813A #define GL_TEXTURE_MAX_LOD_SGIS 0x813B #define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C #define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D #endif /* GL_SGIS_texture_lod */ #ifndef GL_SGIS_texture_select #define GL_SGIS_texture_select 1 #define GL_DUAL_ALPHA4_SGIS 0x8110 #define GL_DUAL_ALPHA8_SGIS 0x8111 #define GL_DUAL_ALPHA12_SGIS 0x8112 #define GL_DUAL_ALPHA16_SGIS 0x8113 #define GL_DUAL_LUMINANCE4_SGIS 0x8114 #define GL_DUAL_LUMINANCE8_SGIS 0x8115 #define GL_DUAL_LUMINANCE12_SGIS 0x8116 #define GL_DUAL_LUMINANCE16_SGIS 0x8117 #define GL_DUAL_INTENSITY4_SGIS 0x8118 #define GL_DUAL_INTENSITY8_SGIS 0x8119 #define GL_DUAL_INTENSITY12_SGIS 0x811A #define GL_DUAL_INTENSITY16_SGIS 0x811B #define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C #define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D #define GL_QUAD_ALPHA4_SGIS 0x811E #define GL_QUAD_ALPHA8_SGIS 0x811F #define GL_QUAD_LUMINANCE4_SGIS 0x8120 #define GL_QUAD_LUMINANCE8_SGIS 0x8121 #define GL_QUAD_INTENSITY4_SGIS 0x8122 #define GL_QUAD_INTENSITY8_SGIS 0x8123 #define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 #define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 #endif /* GL_SGIS_texture_select */ #ifndef GL_SGIX_async #define GL_SGIX_async 1 #define GL_ASYNC_MARKER_SGIX 0x8329 typedef void (APIENTRYP PFNGLASYNCMARKERSGIXPROC) (GLuint marker); typedef GLint (APIENTRYP PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); typedef GLint (APIENTRYP PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); typedef GLuint (APIENTRYP PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); typedef void (APIENTRYP PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); typedef GLboolean (APIENTRYP PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glAsyncMarkerSGIX (GLuint marker); GLAPI GLint APIENTRY glFinishAsyncSGIX (GLuint *markerp); GLAPI GLint APIENTRY glPollAsyncSGIX (GLuint *markerp); GLAPI GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei range); GLAPI void APIENTRY glDeleteAsyncMarkersSGIX (GLuint marker, GLsizei range); GLAPI GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint marker); #endif #endif /* GL_SGIX_async */ #ifndef GL_SGIX_async_histogram #define GL_SGIX_async_histogram 1 #define GL_ASYNC_HISTOGRAM_SGIX 0x832C #define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D #endif /* GL_SGIX_async_histogram */ #ifndef GL_SGIX_async_pixel #define GL_SGIX_async_pixel 1 #define GL_ASYNC_TEX_IMAGE_SGIX 0x835C #define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D #define GL_ASYNC_READ_PIXELS_SGIX 0x835E #define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F #define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 #define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 #endif /* GL_SGIX_async_pixel */ #ifndef GL_SGIX_blend_alpha_minmax #define GL_SGIX_blend_alpha_minmax 1 #define GL_ALPHA_MIN_SGIX 0x8320 #define GL_ALPHA_MAX_SGIX 0x8321 #endif /* GL_SGIX_blend_alpha_minmax */ #ifndef GL_SGIX_calligraphic_fragment #define GL_SGIX_calligraphic_fragment 1 #define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 #endif /* GL_SGIX_calligraphic_fragment */ #ifndef GL_SGIX_clipmap #define GL_SGIX_clipmap 1 #define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 #define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 #define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 #define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 #define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 #define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 #define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 #define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 #define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 #define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D #define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E #define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F #endif /* GL_SGIX_clipmap */ #ifndef GL_SGIX_convolution_accuracy #define GL_SGIX_convolution_accuracy 1 #define GL_CONVOLUTION_HINT_SGIX 0x8316 #endif /* GL_SGIX_convolution_accuracy */ #ifndef GL_SGIX_depth_pass_instrument #define GL_SGIX_depth_pass_instrument 1 #endif /* GL_SGIX_depth_pass_instrument */ #ifndef GL_SGIX_depth_texture #define GL_SGIX_depth_texture 1 #define GL_DEPTH_COMPONENT16_SGIX 0x81A5 #define GL_DEPTH_COMPONENT24_SGIX 0x81A6 #define GL_DEPTH_COMPONENT32_SGIX 0x81A7 #endif /* GL_SGIX_depth_texture */ #ifndef GL_SGIX_flush_raster #define GL_SGIX_flush_raster 1 typedef void (APIENTRYP PFNGLFLUSHRASTERSGIXPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFlushRasterSGIX (void); #endif #endif /* GL_SGIX_flush_raster */ #ifndef GL_SGIX_fog_offset #define GL_SGIX_fog_offset 1 #define GL_FOG_OFFSET_SGIX 0x8198 #define GL_FOG_OFFSET_VALUE_SGIX 0x8199 #endif /* GL_SGIX_fog_offset */ #ifndef GL_SGIX_fragment_lighting #define GL_SGIX_fragment_lighting 1 #define GL_FRAGMENT_LIGHTING_SGIX 0x8400 #define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 #define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 #define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 #define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 #define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 #define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 #define GL_LIGHT_ENV_MODE_SGIX 0x8407 #define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 #define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 #define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A #define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B #define GL_FRAGMENT_LIGHT0_SGIX 0x840C #define GL_FRAGMENT_LIGHT1_SGIX 0x840D #define GL_FRAGMENT_LIGHT2_SGIX 0x840E #define GL_FRAGMENT_LIGHT3_SGIX 0x840F #define GL_FRAGMENT_LIGHT4_SGIX 0x8410 #define GL_FRAGMENT_LIGHT5_SGIX 0x8411 #define GL_FRAGMENT_LIGHT6_SGIX 0x8412 #define GL_FRAGMENT_LIGHT7_SGIX 0x8413 typedef void (APIENTRYP PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFragmentColorMaterialSGIX (GLenum face, GLenum mode); GLAPI void APIENTRY glFragmentLightfSGIX (GLenum light, GLenum pname, GLfloat param); GLAPI void APIENTRY glFragmentLightfvSGIX (GLenum light, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glFragmentLightiSGIX (GLenum light, GLenum pname, GLint param); GLAPI void APIENTRY glFragmentLightivSGIX (GLenum light, GLenum pname, const GLint *params); GLAPI void APIENTRY glFragmentLightModelfSGIX (GLenum pname, GLfloat param); GLAPI void APIENTRY glFragmentLightModelfvSGIX (GLenum pname, const GLfloat *params); GLAPI void APIENTRY glFragmentLightModeliSGIX (GLenum pname, GLint param); GLAPI void APIENTRY glFragmentLightModelivSGIX (GLenum pname, const GLint *params); GLAPI void APIENTRY glFragmentMaterialfSGIX (GLenum face, GLenum pname, GLfloat param); GLAPI void APIENTRY glFragmentMaterialfvSGIX (GLenum face, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glFragmentMaterialiSGIX (GLenum face, GLenum pname, GLint param); GLAPI void APIENTRY glFragmentMaterialivSGIX (GLenum face, GLenum pname, const GLint *params); GLAPI void APIENTRY glGetFragmentLightfvSGIX (GLenum light, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetFragmentLightivSGIX (GLenum light, GLenum pname, GLint *params); GLAPI void APIENTRY glGetFragmentMaterialfvSGIX (GLenum face, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetFragmentMaterialivSGIX (GLenum face, GLenum pname, GLint *params); GLAPI void APIENTRY glLightEnviSGIX (GLenum pname, GLint param); #endif #endif /* GL_SGIX_fragment_lighting */ #ifndef GL_SGIX_framezoom #define GL_SGIX_framezoom 1 #define GL_FRAMEZOOM_SGIX 0x818B #define GL_FRAMEZOOM_FACTOR_SGIX 0x818C #define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D typedef void (APIENTRYP PFNGLFRAMEZOOMSGIXPROC) (GLint factor); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFrameZoomSGIX (GLint factor); #endif #endif /* GL_SGIX_framezoom */ #ifndef GL_SGIX_igloo_interface #define GL_SGIX_igloo_interface 1 typedef void (APIENTRYP PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const void *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glIglooInterfaceSGIX (GLenum pname, const void *params); #endif #endif /* GL_SGIX_igloo_interface */ #ifndef GL_SGIX_instruments #define GL_SGIX_instruments 1 #define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 #define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 typedef GLint (APIENTRYP PFNGLGETINSTRUMENTSSGIXPROC) (void); typedef void (APIENTRYP PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); typedef GLint (APIENTRYP PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); typedef void (APIENTRYP PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); typedef void (APIENTRYP PFNGLSTARTINSTRUMENTSSGIXPROC) (void); typedef void (APIENTRYP PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); #ifdef GL_GLEXT_PROTOTYPES GLAPI GLint APIENTRY glGetInstrumentsSGIX (void); GLAPI void APIENTRY glInstrumentsBufferSGIX (GLsizei size, GLint *buffer); GLAPI GLint APIENTRY glPollInstrumentsSGIX (GLint *marker_p); GLAPI void APIENTRY glReadInstrumentsSGIX (GLint marker); GLAPI void APIENTRY glStartInstrumentsSGIX (void); GLAPI void APIENTRY glStopInstrumentsSGIX (GLint marker); #endif #endif /* GL_SGIX_instruments */ #ifndef GL_SGIX_interlace #define GL_SGIX_interlace 1 #define GL_INTERLACE_SGIX 0x8094 #endif /* GL_SGIX_interlace */ #ifndef GL_SGIX_ir_instrument1 #define GL_SGIX_ir_instrument1 1 #define GL_IR_INSTRUMENT1_SGIX 0x817F #endif /* GL_SGIX_ir_instrument1 */ #ifndef GL_SGIX_list_priority #define GL_SGIX_list_priority 1 #define GL_LIST_PRIORITY_SGIX 0x8182 typedef void (APIENTRYP PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); typedef void (APIENTRYP PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); typedef void (APIENTRYP PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGetListParameterfvSGIX (GLuint list, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetListParameterivSGIX (GLuint list, GLenum pname, GLint *params); GLAPI void APIENTRY glListParameterfSGIX (GLuint list, GLenum pname, GLfloat param); GLAPI void APIENTRY glListParameterfvSGIX (GLuint list, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glListParameteriSGIX (GLuint list, GLenum pname, GLint param); GLAPI void APIENTRY glListParameterivSGIX (GLuint list, GLenum pname, const GLint *params); #endif #endif /* GL_SGIX_list_priority */ #ifndef GL_SGIX_pixel_texture #define GL_SGIX_pixel_texture 1 #define GL_PIXEL_TEX_GEN_SGIX 0x8139 #define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B typedef void (APIENTRYP PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glPixelTexGenSGIX (GLenum mode); #endif #endif /* GL_SGIX_pixel_texture */ #ifndef GL_SGIX_pixel_tiles #define GL_SGIX_pixel_tiles 1 #define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E #define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F #define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 #define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 #define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 #define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 #define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 #define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 #endif /* GL_SGIX_pixel_tiles */ #ifndef GL_SGIX_polynomial_ffd #define GL_SGIX_polynomial_ffd 1 #define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 #define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 #define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 #define GL_TEXTURE_DEFORMATION_SGIX 0x8195 #define GL_DEFORMATIONS_MASK_SGIX 0x8196 #define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 typedef void (APIENTRYP PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); typedef void (APIENTRYP PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); typedef void (APIENTRYP PFNGLDEFORMSGIXPROC) (GLbitfield mask); typedef void (APIENTRYP PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDeformationMap3dSGIX (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); GLAPI void APIENTRY glDeformationMap3fSGIX (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); GLAPI void APIENTRY glDeformSGIX (GLbitfield mask); GLAPI void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield mask); #endif #endif /* GL_SGIX_polynomial_ffd */ #ifndef GL_SGIX_reference_plane #define GL_SGIX_reference_plane 1 #define GL_REFERENCE_PLANE_SGIX 0x817D #define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E typedef void (APIENTRYP PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glReferencePlaneSGIX (const GLdouble *equation); #endif #endif /* GL_SGIX_reference_plane */ #ifndef GL_SGIX_resample #define GL_SGIX_resample 1 #define GL_PACK_RESAMPLE_SGIX 0x842E #define GL_UNPACK_RESAMPLE_SGIX 0x842F #define GL_RESAMPLE_REPLICATE_SGIX 0x8433 #define GL_RESAMPLE_ZERO_FILL_SGIX 0x8434 #define GL_RESAMPLE_DECIMATE_SGIX 0x8430 #endif /* GL_SGIX_resample */ #ifndef GL_SGIX_scalebias_hint #define GL_SGIX_scalebias_hint 1 #define GL_SCALEBIAS_HINT_SGIX 0x8322 #endif /* GL_SGIX_scalebias_hint */ #ifndef GL_SGIX_shadow #define GL_SGIX_shadow 1 #define GL_TEXTURE_COMPARE_SGIX 0x819A #define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B #define GL_TEXTURE_LEQUAL_R_SGIX 0x819C #define GL_TEXTURE_GEQUAL_R_SGIX 0x819D #endif /* GL_SGIX_shadow */ #ifndef GL_SGIX_shadow_ambient #define GL_SGIX_shadow_ambient 1 #define GL_SHADOW_AMBIENT_SGIX 0x80BF #endif /* GL_SGIX_shadow_ambient */ #ifndef GL_SGIX_sprite #define GL_SGIX_sprite 1 #define GL_SPRITE_SGIX 0x8148 #define GL_SPRITE_MODE_SGIX 0x8149 #define GL_SPRITE_AXIS_SGIX 0x814A #define GL_SPRITE_TRANSLATION_SGIX 0x814B #define GL_SPRITE_AXIAL_SGIX 0x814C #define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D #define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E typedef void (APIENTRYP PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); typedef void (APIENTRYP PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); typedef void (APIENTRYP PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glSpriteParameterfSGIX (GLenum pname, GLfloat param); GLAPI void APIENTRY glSpriteParameterfvSGIX (GLenum pname, const GLfloat *params); GLAPI void APIENTRY glSpriteParameteriSGIX (GLenum pname, GLint param); GLAPI void APIENTRY glSpriteParameterivSGIX (GLenum pname, const GLint *params); #endif #endif /* GL_SGIX_sprite */ #ifndef GL_SGIX_subsample #define GL_SGIX_subsample 1 #define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 #define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 #define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 #define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 #define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 #endif /* GL_SGIX_subsample */ #ifndef GL_SGIX_tag_sample_buffer #define GL_SGIX_tag_sample_buffer 1 typedef void (APIENTRYP PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glTagSampleBufferSGIX (void); #endif #endif /* GL_SGIX_tag_sample_buffer */ #ifndef GL_SGIX_texture_add_env #define GL_SGIX_texture_add_env 1 #define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE #endif /* GL_SGIX_texture_add_env */ #ifndef GL_SGIX_texture_coordinate_clamp #define GL_SGIX_texture_coordinate_clamp 1 #define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 #define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A #define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B #endif /* GL_SGIX_texture_coordinate_clamp */ #ifndef GL_SGIX_texture_lod_bias #define GL_SGIX_texture_lod_bias 1 #define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E #define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F #define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 #endif /* GL_SGIX_texture_lod_bias */ #ifndef GL_SGIX_texture_multi_buffer #define GL_SGIX_texture_multi_buffer 1 #define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E #endif /* GL_SGIX_texture_multi_buffer */ #ifndef GL_SGIX_texture_scale_bias #define GL_SGIX_texture_scale_bias 1 #define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 #define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A #define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B #define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C #endif /* GL_SGIX_texture_scale_bias */ #ifndef GL_SGIX_vertex_preclip #define GL_SGIX_vertex_preclip 1 #define GL_VERTEX_PRECLIP_SGIX 0x83EE #define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF #endif /* GL_SGIX_vertex_preclip */ #ifndef GL_SGIX_ycrcb #define GL_SGIX_ycrcb 1 #define GL_YCRCB_422_SGIX 0x81BB #define GL_YCRCB_444_SGIX 0x81BC #endif /* GL_SGIX_ycrcb */ #ifndef GL_SGIX_ycrcb_subsample #define GL_SGIX_ycrcb_subsample 1 #endif /* GL_SGIX_ycrcb_subsample */ #ifndef GL_SGIX_ycrcba #define GL_SGIX_ycrcba 1 #define GL_YCRCB_SGIX 0x8318 #define GL_YCRCBA_SGIX 0x8319 #endif /* GL_SGIX_ycrcba */ #ifndef GL_SGI_color_matrix #define GL_SGI_color_matrix 1 #define GL_COLOR_MATRIX_SGI 0x80B1 #define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 #define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 #define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 #define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 #define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 #define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 #define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 #define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 #define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA #define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB #endif /* GL_SGI_color_matrix */ #ifndef GL_SGI_color_table #define GL_SGI_color_table 1 #define GL_COLOR_TABLE_SGI 0x80D0 #define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 #define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 #define GL_PROXY_COLOR_TABLE_SGI 0x80D3 #define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 #define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 #define GL_COLOR_TABLE_SCALE_SGI 0x80D6 #define GL_COLOR_TABLE_BIAS_SGI 0x80D7 #define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 #define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 #define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA #define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB #define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC #define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD #define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE #define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF typedef void (APIENTRYP PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); typedef void (APIENTRYP PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); typedef void (APIENTRYP PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, void *table); typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glColorTableSGI (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); GLAPI void APIENTRY glColorTableParameterfvSGI (GLenum target, GLenum pname, const GLfloat *params); GLAPI void APIENTRY glColorTableParameterivSGI (GLenum target, GLenum pname, const GLint *params); GLAPI void APIENTRY glCopyColorTableSGI (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); GLAPI void APIENTRY glGetColorTableSGI (GLenum target, GLenum format, GLenum type, void *table); GLAPI void APIENTRY glGetColorTableParameterfvSGI (GLenum target, GLenum pname, GLfloat *params); GLAPI void APIENTRY glGetColorTableParameterivSGI (GLenum target, GLenum pname, GLint *params); #endif #endif /* GL_SGI_color_table */ #ifndef GL_SGI_texture_color_table #define GL_SGI_texture_color_table 1 #define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC #define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD #endif /* GL_SGI_texture_color_table */ #ifndef GL_SUNX_constant_data #define GL_SUNX_constant_data 1 #define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 #define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 typedef void (APIENTRYP PFNGLFINISHTEXTURESUNXPROC) (void); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glFinishTextureSUNX (void); #endif #endif /* GL_SUNX_constant_data */ #ifndef GL_SUN_convolution_border_modes #define GL_SUN_convolution_border_modes 1 #define GL_WRAP_BORDER_SUN 0x81D4 #endif /* GL_SUN_convolution_border_modes */ #ifndef GL_SUN_global_alpha #define GL_SUN_global_alpha 1 #define GL_GLOBAL_ALPHA_SUN 0x81D9 #define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glGlobalAlphaFactorbSUN (GLbyte factor); GLAPI void APIENTRY glGlobalAlphaFactorsSUN (GLshort factor); GLAPI void APIENTRY glGlobalAlphaFactoriSUN (GLint factor); GLAPI void APIENTRY glGlobalAlphaFactorfSUN (GLfloat factor); GLAPI void APIENTRY glGlobalAlphaFactordSUN (GLdouble factor); GLAPI void APIENTRY glGlobalAlphaFactorubSUN (GLubyte factor); GLAPI void APIENTRY glGlobalAlphaFactorusSUN (GLushort factor); GLAPI void APIENTRY glGlobalAlphaFactoruiSUN (GLuint factor); #endif #endif /* GL_SUN_global_alpha */ #ifndef GL_SUN_mesh_array #define GL_SUN_mesh_array 1 #define GL_QUAD_MESH_SUN 0x8614 #define GL_TRIANGLE_MESH_SUN 0x8615 typedef void (APIENTRYP PFNGLDRAWMESHARRAYSSUNPROC) (GLenum mode, GLint first, GLsizei count, GLsizei width); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glDrawMeshArraysSUN (GLenum mode, GLint first, GLsizei count, GLsizei width); #endif #endif /* GL_SUN_mesh_array */ #ifndef GL_SUN_slice_accum #define GL_SUN_slice_accum 1 #define GL_SLICE_ACCUM_SUN 0x85CC #endif /* GL_SUN_slice_accum */ #ifndef GL_SUN_triangle_list #define GL_SUN_triangle_list 1 #define GL_RESTART_SUN 0x0001 #define GL_REPLACE_MIDDLE_SUN 0x0002 #define GL_REPLACE_OLDEST_SUN 0x0003 #define GL_TRIANGLE_LIST_SUN 0x81D7 #define GL_REPLACEMENT_CODE_SUN 0x81D8 #define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 #define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 #define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 #define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 #define GL_R1UI_V3F_SUN 0x85C4 #define GL_R1UI_C4UB_V3F_SUN 0x85C5 #define GL_R1UI_C3F_V3F_SUN 0x85C6 #define GL_R1UI_N3F_V3F_SUN 0x85C7 #define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 #define GL_R1UI_T2F_V3F_SUN 0x85C9 #define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA #define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB typedef void (APIENTRYP PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); typedef void (APIENTRYP PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const void **pointer); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glReplacementCodeuiSUN (GLuint code); GLAPI void APIENTRY glReplacementCodeusSUN (GLushort code); GLAPI void APIENTRY glReplacementCodeubSUN (GLubyte code); GLAPI void APIENTRY glReplacementCodeuivSUN (const GLuint *code); GLAPI void APIENTRY glReplacementCodeusvSUN (const GLushort *code); GLAPI void APIENTRY glReplacementCodeubvSUN (const GLubyte *code); GLAPI void APIENTRY glReplacementCodePointerSUN (GLenum type, GLsizei stride, const void **pointer); #endif #endif /* GL_SUN_triangle_list */ #ifndef GL_SUN_vertex #define GL_SUN_vertex 1 typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *v); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint *rc, const GLubyte *c, const GLfloat *v); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *v); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *n, const GLfloat *v); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *v); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); #ifdef GL_GLEXT_PROTOTYPES GLAPI void APIENTRY glColor4ubVertex2fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); GLAPI void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *c, const GLfloat *v); GLAPI void APIENTRY glColor4ubVertex3fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *c, const GLfloat *v); GLAPI void APIENTRY glColor3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glColor3fVertex3fvSUN (const GLfloat *c, const GLfloat *v); GLAPI void APIENTRY glNormal3fVertex3fSUN (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *n, const GLfloat *v); GLAPI void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *c, const GLfloat *n, const GLfloat *v); GLAPI void APIENTRY glTexCoord2fVertex3fSUN (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *tc, const GLfloat *v); GLAPI void APIENTRY glTexCoord4fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *tc, const GLfloat *v); GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *tc, const GLubyte *c, const GLfloat *v); GLAPI void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *v); GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *n, const GLfloat *v); GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); GLAPI void APIENTRY glReplacementCodeuiVertex3fSUN (GLuint rc, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLuint *rc, const GLfloat *v); GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLuint *rc, const GLubyte *c, const GLfloat *v); GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *v); GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *n, const GLfloat *v); GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *v); GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); #endif #endif /* GL_SUN_vertex */ #ifndef GL_WIN_phong_shading #define GL_WIN_phong_shading 1 #define GL_PHONG_WIN 0x80EA #define GL_PHONG_HINT_WIN 0x80EB #endif /* GL_WIN_phong_shading */ #ifndef GL_WIN_specular_fog #define GL_WIN_specular_fog 1 #define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC #endif /* GL_WIN_specular_fog */ #ifdef __cplusplus } #endif #endif ================================================ FILE: src/Common/GLInclude/glxext.h ================================================ #ifndef __glx_glxext_h_ #define __glx_glxext_h_ 1 #ifdef __cplusplus extern "C" { #endif /* ** Copyright 2013-2020 The Khronos Group Inc. ** SPDX-License-Identifier: MIT ** ** This header is generated from the Khronos OpenGL / OpenGL ES XML ** API Registry. The current version of the Registry, generator scripts ** used to make the header, and the header can be found at ** https://github.com/KhronosGroup/OpenGL-Registry */ #define GLX_GLXEXT_VERSION 20211115 /* Generated C header for: * API: glx * Versions considered: .* * Versions emitted: 1\.[3-9] * Default extensions included: glx * Additional extensions included: _nomatch_^ * Extensions removed: _nomatch_^ */ #ifndef GLX_VERSION_1_3 #define GLX_VERSION_1_3 1 typedef XID GLXContextID; typedef struct __GLXFBConfigRec *GLXFBConfig; typedef XID GLXWindow; typedef XID GLXPbuffer; #define GLX_WINDOW_BIT 0x00000001 #define GLX_PIXMAP_BIT 0x00000002 #define GLX_PBUFFER_BIT 0x00000004 #define GLX_RGBA_BIT 0x00000001 #define GLX_COLOR_INDEX_BIT 0x00000002 #define GLX_PBUFFER_CLOBBER_MASK 0x08000000 #define GLX_FRONT_LEFT_BUFFER_BIT 0x00000001 #define GLX_FRONT_RIGHT_BUFFER_BIT 0x00000002 #define GLX_BACK_LEFT_BUFFER_BIT 0x00000004 #define GLX_BACK_RIGHT_BUFFER_BIT 0x00000008 #define GLX_AUX_BUFFERS_BIT 0x00000010 #define GLX_DEPTH_BUFFER_BIT 0x00000020 #define GLX_STENCIL_BUFFER_BIT 0x00000040 #define GLX_ACCUM_BUFFER_BIT 0x00000080 #define GLX_CONFIG_CAVEAT 0x20 #define GLX_X_VISUAL_TYPE 0x22 #define GLX_TRANSPARENT_TYPE 0x23 #define GLX_TRANSPARENT_INDEX_VALUE 0x24 #define GLX_TRANSPARENT_RED_VALUE 0x25 #define GLX_TRANSPARENT_GREEN_VALUE 0x26 #define GLX_TRANSPARENT_BLUE_VALUE 0x27 #define GLX_TRANSPARENT_ALPHA_VALUE 0x28 #define GLX_DONT_CARE 0xFFFFFFFF #define GLX_NONE 0x8000 #define GLX_SLOW_CONFIG 0x8001 #define GLX_TRUE_COLOR 0x8002 #define GLX_DIRECT_COLOR 0x8003 #define GLX_PSEUDO_COLOR 0x8004 #define GLX_STATIC_COLOR 0x8005 #define GLX_GRAY_SCALE 0x8006 #define GLX_STATIC_GRAY 0x8007 #define GLX_TRANSPARENT_RGB 0x8008 #define GLX_TRANSPARENT_INDEX 0x8009 #define GLX_VISUAL_ID 0x800B #define GLX_SCREEN 0x800C #define GLX_NON_CONFORMANT_CONFIG 0x800D #define GLX_DRAWABLE_TYPE 0x8010 #define GLX_RENDER_TYPE 0x8011 #define GLX_X_RENDERABLE 0x8012 #define GLX_FBCONFIG_ID 0x8013 #define GLX_RGBA_TYPE 0x8014 #define GLX_COLOR_INDEX_TYPE 0x8015 #define GLX_MAX_PBUFFER_WIDTH 0x8016 #define GLX_MAX_PBUFFER_HEIGHT 0x8017 #define GLX_MAX_PBUFFER_PIXELS 0x8018 #define GLX_PRESERVED_CONTENTS 0x801B #define GLX_LARGEST_PBUFFER 0x801C #define GLX_WIDTH 0x801D #define GLX_HEIGHT 0x801E #define GLX_EVENT_MASK 0x801F #define GLX_DAMAGED 0x8020 #define GLX_SAVED 0x8021 #define GLX_WINDOW 0x8022 #define GLX_PBUFFER 0x8023 #define GLX_PBUFFER_HEIGHT 0x8040 #define GLX_PBUFFER_WIDTH 0x8041 typedef GLXFBConfig *( *PFNGLXGETFBCONFIGSPROC) (Display *dpy, int screen, int *nelements); typedef GLXFBConfig *( *PFNGLXCHOOSEFBCONFIGPROC) (Display *dpy, int screen, const int *attrib_list, int *nelements); typedef int ( *PFNGLXGETFBCONFIGATTRIBPROC) (Display *dpy, GLXFBConfig config, int attribute, int *value); typedef XVisualInfo *( *PFNGLXGETVISUALFROMFBCONFIGPROC) (Display *dpy, GLXFBConfig config); typedef GLXWindow ( *PFNGLXCREATEWINDOWPROC) (Display *dpy, GLXFBConfig config, Window win, const int *attrib_list); typedef void ( *PFNGLXDESTROYWINDOWPROC) (Display *dpy, GLXWindow win); typedef GLXPixmap ( *PFNGLXCREATEPIXMAPPROC) (Display *dpy, GLXFBConfig config, Pixmap pixmap, const int *attrib_list); typedef void ( *PFNGLXDESTROYPIXMAPPROC) (Display *dpy, GLXPixmap pixmap); typedef GLXPbuffer ( *PFNGLXCREATEPBUFFERPROC) (Display *dpy, GLXFBConfig config, const int *attrib_list); typedef void ( *PFNGLXDESTROYPBUFFERPROC) (Display *dpy, GLXPbuffer pbuf); typedef void ( *PFNGLXQUERYDRAWABLEPROC) (Display *dpy, GLXDrawable draw, int attribute, unsigned int *value); typedef GLXContext ( *PFNGLXCREATENEWCONTEXTPROC) (Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); typedef Bool ( *PFNGLXMAKECONTEXTCURRENTPROC) (Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); typedef GLXDrawable ( *PFNGLXGETCURRENTREADDRAWABLEPROC) (void); typedef int ( *PFNGLXQUERYCONTEXTPROC) (Display *dpy, GLXContext ctx, int attribute, int *value); typedef void ( *PFNGLXSELECTEVENTPROC) (Display *dpy, GLXDrawable draw, unsigned long event_mask); typedef void ( *PFNGLXGETSELECTEDEVENTPROC) (Display *dpy, GLXDrawable draw, unsigned long *event_mask); #ifdef GLX_GLXEXT_PROTOTYPES GLXFBConfig *glXGetFBConfigs (Display *dpy, int screen, int *nelements); GLXFBConfig *glXChooseFBConfig (Display *dpy, int screen, const int *attrib_list, int *nelements); int glXGetFBConfigAttrib (Display *dpy, GLXFBConfig config, int attribute, int *value); XVisualInfo *glXGetVisualFromFBConfig (Display *dpy, GLXFBConfig config); GLXWindow glXCreateWindow (Display *dpy, GLXFBConfig config, Window win, const int *attrib_list); void glXDestroyWindow (Display *dpy, GLXWindow win); GLXPixmap glXCreatePixmap (Display *dpy, GLXFBConfig config, Pixmap pixmap, const int *attrib_list); void glXDestroyPixmap (Display *dpy, GLXPixmap pixmap); GLXPbuffer glXCreatePbuffer (Display *dpy, GLXFBConfig config, const int *attrib_list); void glXDestroyPbuffer (Display *dpy, GLXPbuffer pbuf); void glXQueryDrawable (Display *dpy, GLXDrawable draw, int attribute, unsigned int *value); GLXContext glXCreateNewContext (Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); Bool glXMakeContextCurrent (Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); GLXDrawable glXGetCurrentReadDrawable (void); int glXQueryContext (Display *dpy, GLXContext ctx, int attribute, int *value); void glXSelectEvent (Display *dpy, GLXDrawable draw, unsigned long event_mask); void glXGetSelectedEvent (Display *dpy, GLXDrawable draw, unsigned long *event_mask); #endif #endif /* GLX_VERSION_1_3 */ #ifndef GLX_VERSION_1_4 #define GLX_VERSION_1_4 1 typedef void ( *__GLXextFuncPtr)(void); #define GLX_SAMPLE_BUFFERS 100000 #define GLX_SAMPLES 100001 typedef __GLXextFuncPtr ( *PFNGLXGETPROCADDRESSPROC) (const GLubyte *procName); #ifdef GLX_GLXEXT_PROTOTYPES __GLXextFuncPtr glXGetProcAddress (const GLubyte *procName); #endif #endif /* GLX_VERSION_1_4 */ #ifndef GLX_ARB_context_flush_control #define GLX_ARB_context_flush_control 1 #define GLX_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 #define GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0 #define GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 #endif /* GLX_ARB_context_flush_control */ #ifndef GLX_ARB_create_context #define GLX_ARB_create_context 1 #define GLX_CONTEXT_DEBUG_BIT_ARB 0x00000001 #define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 #define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 #define GLX_CONTEXT_FLAGS_ARB 0x2094 typedef GLXContext ( *PFNGLXCREATECONTEXTATTRIBSARBPROC) (Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list); #ifdef GLX_GLXEXT_PROTOTYPES GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list); #endif #endif /* GLX_ARB_create_context */ #ifndef GLX_ARB_create_context_no_error #define GLX_ARB_create_context_no_error 1 #define GLX_CONTEXT_OPENGL_NO_ERROR_ARB 0x31B3 #endif /* GLX_ARB_create_context_no_error */ #ifndef GLX_ARB_create_context_profile #define GLX_ARB_create_context_profile 1 #define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 #define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 #endif /* GLX_ARB_create_context_profile */ #ifndef GLX_ARB_create_context_robustness #define GLX_ARB_create_context_robustness 1 #define GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 #define GLX_LOSE_CONTEXT_ON_RESET_ARB 0x8252 #define GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 #define GLX_NO_RESET_NOTIFICATION_ARB 0x8261 #endif /* GLX_ARB_create_context_robustness */ #ifndef GLX_ARB_fbconfig_float #define GLX_ARB_fbconfig_float 1 #define GLX_RGBA_FLOAT_TYPE_ARB 0x20B9 #define GLX_RGBA_FLOAT_BIT_ARB 0x00000004 #endif /* GLX_ARB_fbconfig_float */ #ifndef GLX_ARB_framebuffer_sRGB #define GLX_ARB_framebuffer_sRGB 1 #define GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20B2 #endif /* GLX_ARB_framebuffer_sRGB */ #ifndef GLX_ARB_get_proc_address #define GLX_ARB_get_proc_address 1 typedef __GLXextFuncPtr ( *PFNGLXGETPROCADDRESSARBPROC) (const GLubyte *procName); #ifdef GLX_GLXEXT_PROTOTYPES __GLXextFuncPtr glXGetProcAddressARB (const GLubyte *procName); #endif #endif /* GLX_ARB_get_proc_address */ #ifndef GLX_ARB_multisample #define GLX_ARB_multisample 1 #define GLX_SAMPLE_BUFFERS_ARB 100000 #define GLX_SAMPLES_ARB 100001 #endif /* GLX_ARB_multisample */ #ifndef GLX_ARB_robustness_application_isolation #define GLX_ARB_robustness_application_isolation 1 #define GLX_CONTEXT_RESET_ISOLATION_BIT_ARB 0x00000008 #endif /* GLX_ARB_robustness_application_isolation */ #ifndef GLX_ARB_robustness_share_group_isolation #define GLX_ARB_robustness_share_group_isolation 1 #endif /* GLX_ARB_robustness_share_group_isolation */ #ifndef GLX_ARB_vertex_buffer_object #define GLX_ARB_vertex_buffer_object 1 #define GLX_CONTEXT_ALLOW_BUFFER_BYTE_ORDER_MISMATCH_ARB 0x2095 #endif /* GLX_ARB_vertex_buffer_object */ #ifndef GLX_3DFX_multisample #define GLX_3DFX_multisample 1 #define GLX_SAMPLE_BUFFERS_3DFX 0x8050 #define GLX_SAMPLES_3DFX 0x8051 #endif /* GLX_3DFX_multisample */ #ifndef GLX_AMD_gpu_association #define GLX_AMD_gpu_association 1 #define GLX_GPU_VENDOR_AMD 0x1F00 #define GLX_GPU_RENDERER_STRING_AMD 0x1F01 #define GLX_GPU_OPENGL_VERSION_STRING_AMD 0x1F02 #define GLX_GPU_FASTEST_TARGET_GPUS_AMD 0x21A2 #define GLX_GPU_RAM_AMD 0x21A3 #define GLX_GPU_CLOCK_AMD 0x21A4 #define GLX_GPU_NUM_PIPES_AMD 0x21A5 #define GLX_GPU_NUM_SIMD_AMD 0x21A6 #define GLX_GPU_NUM_RB_AMD 0x21A7 #define GLX_GPU_NUM_SPI_AMD 0x21A8 typedef unsigned int ( *PFNGLXGETGPUIDSAMDPROC) (unsigned int maxCount, unsigned int *ids); typedef int ( *PFNGLXGETGPUINFOAMDPROC) (unsigned int id, int property, GLenum dataType, unsigned int size, void *data); typedef unsigned int ( *PFNGLXGETCONTEXTGPUIDAMDPROC) (GLXContext ctx); typedef GLXContext ( *PFNGLXCREATEASSOCIATEDCONTEXTAMDPROC) (unsigned int id, GLXContext share_list); typedef GLXContext ( *PFNGLXCREATEASSOCIATEDCONTEXTATTRIBSAMDPROC) (unsigned int id, GLXContext share_context, const int *attribList); typedef Bool ( *PFNGLXDELETEASSOCIATEDCONTEXTAMDPROC) (GLXContext ctx); typedef Bool ( *PFNGLXMAKEASSOCIATEDCONTEXTCURRENTAMDPROC) (GLXContext ctx); typedef GLXContext ( *PFNGLXGETCURRENTASSOCIATEDCONTEXTAMDPROC) (void); typedef void ( *PFNGLXBLITCONTEXTFRAMEBUFFERAMDPROC) (GLXContext dstCtx, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); #ifdef GLX_GLXEXT_PROTOTYPES unsigned int glXGetGPUIDsAMD (unsigned int maxCount, unsigned int *ids); int glXGetGPUInfoAMD (unsigned int id, int property, GLenum dataType, unsigned int size, void *data); unsigned int glXGetContextGPUIDAMD (GLXContext ctx); GLXContext glXCreateAssociatedContextAMD (unsigned int id, GLXContext share_list); GLXContext glXCreateAssociatedContextAttribsAMD (unsigned int id, GLXContext share_context, const int *attribList); Bool glXDeleteAssociatedContextAMD (GLXContext ctx); Bool glXMakeAssociatedContextCurrentAMD (GLXContext ctx); GLXContext glXGetCurrentAssociatedContextAMD (void); void glXBlitContextFramebufferAMD (GLXContext dstCtx, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); #endif #endif /* GLX_AMD_gpu_association */ #ifndef GLX_EXT_buffer_age #define GLX_EXT_buffer_age 1 #define GLX_BACK_BUFFER_AGE_EXT 0x20F4 #endif /* GLX_EXT_buffer_age */ #ifndef GLX_EXT_context_priority #define GLX_EXT_context_priority 1 #define GLX_CONTEXT_PRIORITY_LEVEL_EXT 0x3100 #define GLX_CONTEXT_PRIORITY_HIGH_EXT 0x3101 #define GLX_CONTEXT_PRIORITY_MEDIUM_EXT 0x3102 #define GLX_CONTEXT_PRIORITY_LOW_EXT 0x3103 #endif /* GLX_EXT_context_priority */ #ifndef GLX_EXT_create_context_es2_profile #define GLX_EXT_create_context_es2_profile 1 #define GLX_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 #endif /* GLX_EXT_create_context_es2_profile */ #ifndef GLX_EXT_create_context_es_profile #define GLX_EXT_create_context_es_profile 1 #define GLX_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 #endif /* GLX_EXT_create_context_es_profile */ #ifndef GLX_EXT_fbconfig_packed_float #define GLX_EXT_fbconfig_packed_float 1 #define GLX_RGBA_UNSIGNED_FLOAT_TYPE_EXT 0x20B1 #define GLX_RGBA_UNSIGNED_FLOAT_BIT_EXT 0x00000008 #endif /* GLX_EXT_fbconfig_packed_float */ #ifndef GLX_EXT_framebuffer_sRGB #define GLX_EXT_framebuffer_sRGB 1 #define GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x20B2 #endif /* GLX_EXT_framebuffer_sRGB */ #ifndef GLX_EXT_get_drawable_type #define GLX_EXT_get_drawable_type 1 #endif /* GLX_EXT_get_drawable_type */ #ifndef GLX_EXT_import_context #define GLX_EXT_import_context 1 #define GLX_SHARE_CONTEXT_EXT 0x800A #define GLX_VISUAL_ID_EXT 0x800B #define GLX_SCREEN_EXT 0x800C typedef Display *( *PFNGLXGETCURRENTDISPLAYEXTPROC) (void); typedef int ( *PFNGLXQUERYCONTEXTINFOEXTPROC) (Display *dpy, GLXContext context, int attribute, int *value); typedef GLXContextID ( *PFNGLXGETCONTEXTIDEXTPROC) (const GLXContext context); typedef GLXContext ( *PFNGLXIMPORTCONTEXTEXTPROC) (Display *dpy, GLXContextID contextID); typedef void ( *PFNGLXFREECONTEXTEXTPROC) (Display *dpy, GLXContext context); #ifdef GLX_GLXEXT_PROTOTYPES Display *glXGetCurrentDisplayEXT (void); int glXQueryContextInfoEXT (Display *dpy, GLXContext context, int attribute, int *value); GLXContextID glXGetContextIDEXT (const GLXContext context); GLXContext glXImportContextEXT (Display *dpy, GLXContextID contextID); void glXFreeContextEXT (Display *dpy, GLXContext context); #endif #endif /* GLX_EXT_import_context */ #ifndef GLX_EXT_libglvnd #define GLX_EXT_libglvnd 1 #define GLX_VENDOR_NAMES_EXT 0x20F6 #endif /* GLX_EXT_libglvnd */ #ifndef GLX_EXT_no_config_context #define GLX_EXT_no_config_context 1 #endif /* GLX_EXT_no_config_context */ #ifndef GLX_EXT_stereo_tree #define GLX_EXT_stereo_tree 1 typedef struct { int type; unsigned long serial; Bool send_event; Display *display; int extension; int evtype; GLXDrawable window; Bool stereo_tree; } GLXStereoNotifyEventEXT; #define GLX_STEREO_TREE_EXT 0x20F5 #define GLX_STEREO_NOTIFY_MASK_EXT 0x00000001 #define GLX_STEREO_NOTIFY_EXT 0x00000000 #endif /* GLX_EXT_stereo_tree */ #ifndef GLX_EXT_swap_control #define GLX_EXT_swap_control 1 #define GLX_SWAP_INTERVAL_EXT 0x20F1 #define GLX_MAX_SWAP_INTERVAL_EXT 0x20F2 typedef void ( *PFNGLXSWAPINTERVALEXTPROC) (Display *dpy, GLXDrawable drawable, int interval); #ifdef GLX_GLXEXT_PROTOTYPES void glXSwapIntervalEXT (Display *dpy, GLXDrawable drawable, int interval); #endif #endif /* GLX_EXT_swap_control */ #ifndef GLX_EXT_swap_control_tear #define GLX_EXT_swap_control_tear 1 #define GLX_LATE_SWAPS_TEAR_EXT 0x20F3 #endif /* GLX_EXT_swap_control_tear */ #ifndef GLX_EXT_texture_from_pixmap #define GLX_EXT_texture_from_pixmap 1 #define GLX_TEXTURE_1D_BIT_EXT 0x00000001 #define GLX_TEXTURE_2D_BIT_EXT 0x00000002 #define GLX_TEXTURE_RECTANGLE_BIT_EXT 0x00000004 #define GLX_BIND_TO_TEXTURE_RGB_EXT 0x20D0 #define GLX_BIND_TO_TEXTURE_RGBA_EXT 0x20D1 #define GLX_BIND_TO_MIPMAP_TEXTURE_EXT 0x20D2 #define GLX_BIND_TO_TEXTURE_TARGETS_EXT 0x20D3 #define GLX_Y_INVERTED_EXT 0x20D4 #define GLX_TEXTURE_FORMAT_EXT 0x20D5 #define GLX_TEXTURE_TARGET_EXT 0x20D6 #define GLX_MIPMAP_TEXTURE_EXT 0x20D7 #define GLX_TEXTURE_FORMAT_NONE_EXT 0x20D8 #define GLX_TEXTURE_FORMAT_RGB_EXT 0x20D9 #define GLX_TEXTURE_FORMAT_RGBA_EXT 0x20DA #define GLX_TEXTURE_1D_EXT 0x20DB #define GLX_TEXTURE_2D_EXT 0x20DC #define GLX_TEXTURE_RECTANGLE_EXT 0x20DD #define GLX_FRONT_LEFT_EXT 0x20DE #define GLX_FRONT_RIGHT_EXT 0x20DF #define GLX_BACK_LEFT_EXT 0x20E0 #define GLX_BACK_RIGHT_EXT 0x20E1 #define GLX_FRONT_EXT 0x20DE #define GLX_BACK_EXT 0x20E0 #define GLX_AUX0_EXT 0x20E2 #define GLX_AUX1_EXT 0x20E3 #define GLX_AUX2_EXT 0x20E4 #define GLX_AUX3_EXT 0x20E5 #define GLX_AUX4_EXT 0x20E6 #define GLX_AUX5_EXT 0x20E7 #define GLX_AUX6_EXT 0x20E8 #define GLX_AUX7_EXT 0x20E9 #define GLX_AUX8_EXT 0x20EA #define GLX_AUX9_EXT 0x20EB typedef void ( *PFNGLXBINDTEXIMAGEEXTPROC) (Display *dpy, GLXDrawable drawable, int buffer, const int *attrib_list); typedef void ( *PFNGLXRELEASETEXIMAGEEXTPROC) (Display *dpy, GLXDrawable drawable, int buffer); #ifdef GLX_GLXEXT_PROTOTYPES void glXBindTexImageEXT (Display *dpy, GLXDrawable drawable, int buffer, const int *attrib_list); void glXReleaseTexImageEXT (Display *dpy, GLXDrawable drawable, int buffer); #endif #endif /* GLX_EXT_texture_from_pixmap */ #ifndef GLX_EXT_visual_info #define GLX_EXT_visual_info 1 #define GLX_X_VISUAL_TYPE_EXT 0x22 #define GLX_TRANSPARENT_TYPE_EXT 0x23 #define GLX_TRANSPARENT_INDEX_VALUE_EXT 0x24 #define GLX_TRANSPARENT_RED_VALUE_EXT 0x25 #define GLX_TRANSPARENT_GREEN_VALUE_EXT 0x26 #define GLX_TRANSPARENT_BLUE_VALUE_EXT 0x27 #define GLX_TRANSPARENT_ALPHA_VALUE_EXT 0x28 #define GLX_NONE_EXT 0x8000 #define GLX_TRUE_COLOR_EXT 0x8002 #define GLX_DIRECT_COLOR_EXT 0x8003 #define GLX_PSEUDO_COLOR_EXT 0x8004 #define GLX_STATIC_COLOR_EXT 0x8005 #define GLX_GRAY_SCALE_EXT 0x8006 #define GLX_STATIC_GRAY_EXT 0x8007 #define GLX_TRANSPARENT_RGB_EXT 0x8008 #define GLX_TRANSPARENT_INDEX_EXT 0x8009 #endif /* GLX_EXT_visual_info */ #ifndef GLX_EXT_visual_rating #define GLX_EXT_visual_rating 1 #define GLX_VISUAL_CAVEAT_EXT 0x20 #define GLX_SLOW_VISUAL_EXT 0x8001 #define GLX_NON_CONFORMANT_VISUAL_EXT 0x800D #endif /* GLX_EXT_visual_rating */ #ifndef GLX_INTEL_swap_event #define GLX_INTEL_swap_event 1 #define GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK 0x04000000 #define GLX_EXCHANGE_COMPLETE_INTEL 0x8180 #define GLX_COPY_COMPLETE_INTEL 0x8181 #define GLX_FLIP_COMPLETE_INTEL 0x8182 #endif /* GLX_INTEL_swap_event */ #ifndef GLX_MESA_agp_offset #define GLX_MESA_agp_offset 1 typedef unsigned int ( *PFNGLXGETAGPOFFSETMESAPROC) (const void *pointer); #ifdef GLX_GLXEXT_PROTOTYPES unsigned int glXGetAGPOffsetMESA (const void *pointer); #endif #endif /* GLX_MESA_agp_offset */ #ifndef GLX_MESA_copy_sub_buffer #define GLX_MESA_copy_sub_buffer 1 typedef void ( *PFNGLXCOPYSUBBUFFERMESAPROC) (Display *dpy, GLXDrawable drawable, int x, int y, int width, int height); #ifdef GLX_GLXEXT_PROTOTYPES void glXCopySubBufferMESA (Display *dpy, GLXDrawable drawable, int x, int y, int width, int height); #endif #endif /* GLX_MESA_copy_sub_buffer */ #ifndef GLX_MESA_pixmap_colormap #define GLX_MESA_pixmap_colormap 1 typedef GLXPixmap ( *PFNGLXCREATEGLXPIXMAPMESAPROC) (Display *dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap); #ifdef GLX_GLXEXT_PROTOTYPES GLXPixmap glXCreateGLXPixmapMESA (Display *dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap); #endif #endif /* GLX_MESA_pixmap_colormap */ #ifndef GLX_MESA_query_renderer #define GLX_MESA_query_renderer 1 #define GLX_RENDERER_VENDOR_ID_MESA 0x8183 #define GLX_RENDERER_DEVICE_ID_MESA 0x8184 #define GLX_RENDERER_VERSION_MESA 0x8185 #define GLX_RENDERER_ACCELERATED_MESA 0x8186 #define GLX_RENDERER_VIDEO_MEMORY_MESA 0x8187 #define GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA 0x8188 #define GLX_RENDERER_PREFERRED_PROFILE_MESA 0x8189 #define GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA 0x818A #define GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA 0x818B #define GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA 0x818C #define GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA 0x818D typedef Bool ( *PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC) (int attribute, unsigned int *value); typedef const char *( *PFNGLXQUERYCURRENTRENDERERSTRINGMESAPROC) (int attribute); typedef Bool ( *PFNGLXQUERYRENDERERINTEGERMESAPROC) (Display *dpy, int screen, int renderer, int attribute, unsigned int *value); typedef const char *( *PFNGLXQUERYRENDERERSTRINGMESAPROC) (Display *dpy, int screen, int renderer, int attribute); #ifdef GLX_GLXEXT_PROTOTYPES Bool glXQueryCurrentRendererIntegerMESA (int attribute, unsigned int *value); const char *glXQueryCurrentRendererStringMESA (int attribute); Bool glXQueryRendererIntegerMESA (Display *dpy, int screen, int renderer, int attribute, unsigned int *value); const char *glXQueryRendererStringMESA (Display *dpy, int screen, int renderer, int attribute); #endif #endif /* GLX_MESA_query_renderer */ #ifndef GLX_MESA_release_buffers #define GLX_MESA_release_buffers 1 typedef Bool ( *PFNGLXRELEASEBUFFERSMESAPROC) (Display *dpy, GLXDrawable drawable); #ifdef GLX_GLXEXT_PROTOTYPES Bool glXReleaseBuffersMESA (Display *dpy, GLXDrawable drawable); #endif #endif /* GLX_MESA_release_buffers */ #ifndef GLX_MESA_set_3dfx_mode #define GLX_MESA_set_3dfx_mode 1 #define GLX_3DFX_WINDOW_MODE_MESA 0x1 #define GLX_3DFX_FULLSCREEN_MODE_MESA 0x2 typedef GLboolean ( *PFNGLXSET3DFXMODEMESAPROC) (GLint mode); #ifdef GLX_GLXEXT_PROTOTYPES GLboolean glXSet3DfxModeMESA (GLint mode); #endif #endif /* GLX_MESA_set_3dfx_mode */ #ifndef GLX_MESA_swap_control #define GLX_MESA_swap_control 1 typedef int ( *PFNGLXGETSWAPINTERVALMESAPROC) (void); typedef int ( *PFNGLXSWAPINTERVALMESAPROC) (unsigned int interval); #ifdef GLX_GLXEXT_PROTOTYPES int glXGetSwapIntervalMESA (void); int glXSwapIntervalMESA (unsigned int interval); #endif #endif /* GLX_MESA_swap_control */ #ifndef GLX_NV_copy_buffer #define GLX_NV_copy_buffer 1 typedef void ( *PFNGLXCOPYBUFFERSUBDATANVPROC) (Display *dpy, GLXContext readCtx, GLXContext writeCtx, GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); typedef void ( *PFNGLXNAMEDCOPYBUFFERSUBDATANVPROC) (Display *dpy, GLXContext readCtx, GLXContext writeCtx, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); #ifdef GLX_GLXEXT_PROTOTYPES void glXCopyBufferSubDataNV (Display *dpy, GLXContext readCtx, GLXContext writeCtx, GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); void glXNamedCopyBufferSubDataNV (Display *dpy, GLXContext readCtx, GLXContext writeCtx, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); #endif #endif /* GLX_NV_copy_buffer */ #ifndef GLX_NV_copy_image #define GLX_NV_copy_image 1 typedef void ( *PFNGLXCOPYIMAGESUBDATANVPROC) (Display *dpy, GLXContext srcCtx, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLXContext dstCtx, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); #ifdef GLX_GLXEXT_PROTOTYPES void glXCopyImageSubDataNV (Display *dpy, GLXContext srcCtx, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLXContext dstCtx, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); #endif #endif /* GLX_NV_copy_image */ #ifndef GLX_NV_delay_before_swap #define GLX_NV_delay_before_swap 1 typedef Bool ( *PFNGLXDELAYBEFORESWAPNVPROC) (Display *dpy, GLXDrawable drawable, GLfloat seconds); #ifdef GLX_GLXEXT_PROTOTYPES Bool glXDelayBeforeSwapNV (Display *dpy, GLXDrawable drawable, GLfloat seconds); #endif #endif /* GLX_NV_delay_before_swap */ #ifndef GLX_NV_float_buffer #define GLX_NV_float_buffer 1 #define GLX_FLOAT_COMPONENTS_NV 0x20B0 #endif /* GLX_NV_float_buffer */ #ifndef GLX_NV_multigpu_context #define GLX_NV_multigpu_context 1 #define GLX_CONTEXT_MULTIGPU_ATTRIB_NV 0x20AA #define GLX_CONTEXT_MULTIGPU_ATTRIB_SINGLE_NV 0x20AB #define GLX_CONTEXT_MULTIGPU_ATTRIB_AFR_NV 0x20AC #define GLX_CONTEXT_MULTIGPU_ATTRIB_MULTICAST_NV 0x20AD #define GLX_CONTEXT_MULTIGPU_ATTRIB_MULTI_DISPLAY_MULTICAST_NV 0x20AE #endif /* GLX_NV_multigpu_context */ #ifndef GLX_NV_multisample_coverage #define GLX_NV_multisample_coverage 1 #define GLX_COVERAGE_SAMPLES_NV 100001 #define GLX_COLOR_SAMPLES_NV 0x20B3 #endif /* GLX_NV_multisample_coverage */ #ifndef GLX_NV_present_video #define GLX_NV_present_video 1 #define GLX_NUM_VIDEO_SLOTS_NV 0x20F0 typedef unsigned int *( *PFNGLXENUMERATEVIDEODEVICESNVPROC) (Display *dpy, int screen, int *nelements); typedef int ( *PFNGLXBINDVIDEODEVICENVPROC) (Display *dpy, unsigned int video_slot, unsigned int video_device, const int *attrib_list); #ifdef GLX_GLXEXT_PROTOTYPES unsigned int *glXEnumerateVideoDevicesNV (Display *dpy, int screen, int *nelements); int glXBindVideoDeviceNV (Display *dpy, unsigned int video_slot, unsigned int video_device, const int *attrib_list); #endif #endif /* GLX_NV_present_video */ #ifndef GLX_NV_robustness_video_memory_purge #define GLX_NV_robustness_video_memory_purge 1 #define GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV 0x20F7 #endif /* GLX_NV_robustness_video_memory_purge */ #ifndef GLX_NV_swap_group #define GLX_NV_swap_group 1 typedef Bool ( *PFNGLXJOINSWAPGROUPNVPROC) (Display *dpy, GLXDrawable drawable, GLuint group); typedef Bool ( *PFNGLXBINDSWAPBARRIERNVPROC) (Display *dpy, GLuint group, GLuint barrier); typedef Bool ( *PFNGLXQUERYSWAPGROUPNVPROC) (Display *dpy, GLXDrawable drawable, GLuint *group, GLuint *barrier); typedef Bool ( *PFNGLXQUERYMAXSWAPGROUPSNVPROC) (Display *dpy, int screen, GLuint *maxGroups, GLuint *maxBarriers); typedef Bool ( *PFNGLXQUERYFRAMECOUNTNVPROC) (Display *dpy, int screen, GLuint *count); typedef Bool ( *PFNGLXRESETFRAMECOUNTNVPROC) (Display *dpy, int screen); #ifdef GLX_GLXEXT_PROTOTYPES Bool glXJoinSwapGroupNV (Display *dpy, GLXDrawable drawable, GLuint group); Bool glXBindSwapBarrierNV (Display *dpy, GLuint group, GLuint barrier); Bool glXQuerySwapGroupNV (Display *dpy, GLXDrawable drawable, GLuint *group, GLuint *barrier); Bool glXQueryMaxSwapGroupsNV (Display *dpy, int screen, GLuint *maxGroups, GLuint *maxBarriers); Bool glXQueryFrameCountNV (Display *dpy, int screen, GLuint *count); Bool glXResetFrameCountNV (Display *dpy, int screen); #endif #endif /* GLX_NV_swap_group */ #ifndef GLX_NV_video_capture #define GLX_NV_video_capture 1 typedef XID GLXVideoCaptureDeviceNV; #define GLX_DEVICE_ID_NV 0x20CD #define GLX_UNIQUE_ID_NV 0x20CE #define GLX_NUM_VIDEO_CAPTURE_SLOTS_NV 0x20CF typedef int ( *PFNGLXBINDVIDEOCAPTUREDEVICENVPROC) (Display *dpy, unsigned int video_capture_slot, GLXVideoCaptureDeviceNV device); typedef GLXVideoCaptureDeviceNV *( *PFNGLXENUMERATEVIDEOCAPTUREDEVICESNVPROC) (Display *dpy, int screen, int *nelements); typedef void ( *PFNGLXLOCKVIDEOCAPTUREDEVICENVPROC) (Display *dpy, GLXVideoCaptureDeviceNV device); typedef int ( *PFNGLXQUERYVIDEOCAPTUREDEVICENVPROC) (Display *dpy, GLXVideoCaptureDeviceNV device, int attribute, int *value); typedef void ( *PFNGLXRELEASEVIDEOCAPTUREDEVICENVPROC) (Display *dpy, GLXVideoCaptureDeviceNV device); #ifdef GLX_GLXEXT_PROTOTYPES int glXBindVideoCaptureDeviceNV (Display *dpy, unsigned int video_capture_slot, GLXVideoCaptureDeviceNV device); GLXVideoCaptureDeviceNV *glXEnumerateVideoCaptureDevicesNV (Display *dpy, int screen, int *nelements); void glXLockVideoCaptureDeviceNV (Display *dpy, GLXVideoCaptureDeviceNV device); int glXQueryVideoCaptureDeviceNV (Display *dpy, GLXVideoCaptureDeviceNV device, int attribute, int *value); void glXReleaseVideoCaptureDeviceNV (Display *dpy, GLXVideoCaptureDeviceNV device); #endif #endif /* GLX_NV_video_capture */ #ifndef GLX_NV_video_out #define GLX_NV_video_out 1 typedef unsigned int GLXVideoDeviceNV; #define GLX_VIDEO_OUT_COLOR_NV 0x20C3 #define GLX_VIDEO_OUT_ALPHA_NV 0x20C4 #define GLX_VIDEO_OUT_DEPTH_NV 0x20C5 #define GLX_VIDEO_OUT_COLOR_AND_ALPHA_NV 0x20C6 #define GLX_VIDEO_OUT_COLOR_AND_DEPTH_NV 0x20C7 #define GLX_VIDEO_OUT_FRAME_NV 0x20C8 #define GLX_VIDEO_OUT_FIELD_1_NV 0x20C9 #define GLX_VIDEO_OUT_FIELD_2_NV 0x20CA #define GLX_VIDEO_OUT_STACKED_FIELDS_1_2_NV 0x20CB #define GLX_VIDEO_OUT_STACKED_FIELDS_2_1_NV 0x20CC typedef int ( *PFNGLXGETVIDEODEVICENVPROC) (Display *dpy, int screen, int numVideoDevices, GLXVideoDeviceNV *pVideoDevice); typedef int ( *PFNGLXRELEASEVIDEODEVICENVPROC) (Display *dpy, int screen, GLXVideoDeviceNV VideoDevice); typedef int ( *PFNGLXBINDVIDEOIMAGENVPROC) (Display *dpy, GLXVideoDeviceNV VideoDevice, GLXPbuffer pbuf, int iVideoBuffer); typedef int ( *PFNGLXRELEASEVIDEOIMAGENVPROC) (Display *dpy, GLXPbuffer pbuf); typedef int ( *PFNGLXSENDPBUFFERTOVIDEONVPROC) (Display *dpy, GLXPbuffer pbuf, int iBufferType, unsigned long *pulCounterPbuffer, GLboolean bBlock); typedef int ( *PFNGLXGETVIDEOINFONVPROC) (Display *dpy, int screen, GLXVideoDeviceNV VideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); #ifdef GLX_GLXEXT_PROTOTYPES int glXGetVideoDeviceNV (Display *dpy, int screen, int numVideoDevices, GLXVideoDeviceNV *pVideoDevice); int glXReleaseVideoDeviceNV (Display *dpy, int screen, GLXVideoDeviceNV VideoDevice); int glXBindVideoImageNV (Display *dpy, GLXVideoDeviceNV VideoDevice, GLXPbuffer pbuf, int iVideoBuffer); int glXReleaseVideoImageNV (Display *dpy, GLXPbuffer pbuf); int glXSendPbufferToVideoNV (Display *dpy, GLXPbuffer pbuf, int iBufferType, unsigned long *pulCounterPbuffer, GLboolean bBlock); int glXGetVideoInfoNV (Display *dpy, int screen, GLXVideoDeviceNV VideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); #endif #endif /* GLX_NV_video_out */ #ifndef GLX_OML_swap_method #define GLX_OML_swap_method 1 #define GLX_SWAP_METHOD_OML 0x8060 #define GLX_SWAP_EXCHANGE_OML 0x8061 #define GLX_SWAP_COPY_OML 0x8062 #define GLX_SWAP_UNDEFINED_OML 0x8063 #endif /* GLX_OML_swap_method */ #ifndef GLX_OML_sync_control #define GLX_OML_sync_control 1 #ifndef GLEXT_64_TYPES_DEFINED /* This code block is duplicated in glext.h, so must be protected */ #define GLEXT_64_TYPES_DEFINED /* Define int32_t, int64_t, and uint64_t types for UST/MSC */ /* (as used in the GLX_OML_sync_control extension). */ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #include #elif defined(__sun__) || defined(__digital__) #include #if defined(__STDC__) #if defined(__arch64__) || defined(_LP64) typedef long int int64_t; typedef unsigned long int uint64_t; #else typedef long long int int64_t; typedef unsigned long long int uint64_t; #endif /* __arch64__ */ #endif /* __STDC__ */ #elif defined( __VMS ) || defined(__sgi) #include #elif defined(__SCO__) || defined(__USLC__) #include #elif defined(__UNIXOS2__) || defined(__SOL64__) typedef long int int32_t; typedef long long int int64_t; typedef unsigned long long int uint64_t; #elif defined(_WIN32) && defined(__GNUC__) #include #elif defined(_WIN32) typedef __int32 int32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #else /* Fallback if nothing above works */ #include #endif #endif typedef Bool ( *PFNGLXGETSYNCVALUESOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t *ust, int64_t *msc, int64_t *sbc); typedef Bool ( *PFNGLXGETMSCRATEOMLPROC) (Display *dpy, GLXDrawable drawable, int32_t *numerator, int32_t *denominator); typedef int64_t ( *PFNGLXSWAPBUFFERSMSCOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder); typedef Bool ( *PFNGLXWAITFORMSCOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder, int64_t *ust, int64_t *msc, int64_t *sbc); typedef Bool ( *PFNGLXWAITFORSBCOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t target_sbc, int64_t *ust, int64_t *msc, int64_t *sbc); #ifdef GLX_GLXEXT_PROTOTYPES Bool glXGetSyncValuesOML (Display *dpy, GLXDrawable drawable, int64_t *ust, int64_t *msc, int64_t *sbc); Bool glXGetMscRateOML (Display *dpy, GLXDrawable drawable, int32_t *numerator, int32_t *denominator); int64_t glXSwapBuffersMscOML (Display *dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder); Bool glXWaitForMscOML (Display *dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder, int64_t *ust, int64_t *msc, int64_t *sbc); Bool glXWaitForSbcOML (Display *dpy, GLXDrawable drawable, int64_t target_sbc, int64_t *ust, int64_t *msc, int64_t *sbc); #endif #endif /* GLX_OML_sync_control */ #ifndef GLX_SGIS_blended_overlay #define GLX_SGIS_blended_overlay 1 #define GLX_BLENDED_RGBA_SGIS 0x8025 #endif /* GLX_SGIS_blended_overlay */ #ifndef GLX_SGIS_multisample #define GLX_SGIS_multisample 1 #define GLX_SAMPLE_BUFFERS_SGIS 100000 #define GLX_SAMPLES_SGIS 100001 #endif /* GLX_SGIS_multisample */ #ifndef GLX_SGIS_shared_multisample #define GLX_SGIS_shared_multisample 1 #define GLX_MULTISAMPLE_SUB_RECT_WIDTH_SGIS 0x8026 #define GLX_MULTISAMPLE_SUB_RECT_HEIGHT_SGIS 0x8027 #endif /* GLX_SGIS_shared_multisample */ #ifndef GLX_SGIX_dmbuffer #define GLX_SGIX_dmbuffer 1 typedef XID GLXPbufferSGIX; #ifdef _DM_BUFFER_H_ #define GLX_DIGITAL_MEDIA_PBUFFER_SGIX 0x8024 typedef Bool ( *PFNGLXASSOCIATEDMPBUFFERSGIXPROC) (Display *dpy, GLXPbufferSGIX pbuffer, DMparams *params, DMbuffer dmbuffer); #ifdef GLX_GLXEXT_PROTOTYPES Bool glXAssociateDMPbufferSGIX (Display *dpy, GLXPbufferSGIX pbuffer, DMparams *params, DMbuffer dmbuffer); #endif #endif /* _DM_BUFFER_H_ */ #endif /* GLX_SGIX_dmbuffer */ #ifndef GLX_SGIX_fbconfig #define GLX_SGIX_fbconfig 1 typedef struct __GLXFBConfigRec *GLXFBConfigSGIX; #define GLX_WINDOW_BIT_SGIX 0x00000001 #define GLX_PIXMAP_BIT_SGIX 0x00000002 #define GLX_RGBA_BIT_SGIX 0x00000001 #define GLX_COLOR_INDEX_BIT_SGIX 0x00000002 #define GLX_DRAWABLE_TYPE_SGIX 0x8010 #define GLX_RENDER_TYPE_SGIX 0x8011 #define GLX_X_RENDERABLE_SGIX 0x8012 #define GLX_FBCONFIG_ID_SGIX 0x8013 #define GLX_RGBA_TYPE_SGIX 0x8014 #define GLX_COLOR_INDEX_TYPE_SGIX 0x8015 typedef int ( *PFNGLXGETFBCONFIGATTRIBSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, int attribute, int *value); typedef GLXFBConfigSGIX *( *PFNGLXCHOOSEFBCONFIGSGIXPROC) (Display *dpy, int screen, int *attrib_list, int *nelements); typedef GLXPixmap ( *PFNGLXCREATEGLXPIXMAPWITHCONFIGSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, Pixmap pixmap); typedef GLXContext ( *PFNGLXCREATECONTEXTWITHCONFIGSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, int render_type, GLXContext share_list, Bool direct); typedef XVisualInfo *( *PFNGLXGETVISUALFROMFBCONFIGSGIXPROC) (Display *dpy, GLXFBConfigSGIX config); typedef GLXFBConfigSGIX ( *PFNGLXGETFBCONFIGFROMVISUALSGIXPROC) (Display *dpy, XVisualInfo *vis); #ifdef GLX_GLXEXT_PROTOTYPES int glXGetFBConfigAttribSGIX (Display *dpy, GLXFBConfigSGIX config, int attribute, int *value); GLXFBConfigSGIX *glXChooseFBConfigSGIX (Display *dpy, int screen, int *attrib_list, int *nelements); GLXPixmap glXCreateGLXPixmapWithConfigSGIX (Display *dpy, GLXFBConfigSGIX config, Pixmap pixmap); GLXContext glXCreateContextWithConfigSGIX (Display *dpy, GLXFBConfigSGIX config, int render_type, GLXContext share_list, Bool direct); XVisualInfo *glXGetVisualFromFBConfigSGIX (Display *dpy, GLXFBConfigSGIX config); GLXFBConfigSGIX glXGetFBConfigFromVisualSGIX (Display *dpy, XVisualInfo *vis); #endif #endif /* GLX_SGIX_fbconfig */ #ifndef GLX_SGIX_hyperpipe #define GLX_SGIX_hyperpipe 1 typedef struct { char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ int networkId; } GLXHyperpipeNetworkSGIX; typedef struct { char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ int channel; unsigned int participationType; int timeSlice; } GLXHyperpipeConfigSGIX; typedef struct { char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ int srcXOrigin, srcYOrigin, srcWidth, srcHeight; int destXOrigin, destYOrigin, destWidth, destHeight; } GLXPipeRect; typedef struct { char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ int XOrigin, YOrigin, maxHeight, maxWidth; } GLXPipeRectLimits; #define GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX 80 #define GLX_BAD_HYPERPIPE_CONFIG_SGIX 91 #define GLX_BAD_HYPERPIPE_SGIX 92 #define GLX_HYPERPIPE_DISPLAY_PIPE_SGIX 0x00000001 #define GLX_HYPERPIPE_RENDER_PIPE_SGIX 0x00000002 #define GLX_PIPE_RECT_SGIX 0x00000001 #define GLX_PIPE_RECT_LIMITS_SGIX 0x00000002 #define GLX_HYPERPIPE_STEREO_SGIX 0x00000003 #define GLX_HYPERPIPE_PIXEL_AVERAGE_SGIX 0x00000004 #define GLX_HYPERPIPE_ID_SGIX 0x8030 typedef GLXHyperpipeNetworkSGIX *( *PFNGLXQUERYHYPERPIPENETWORKSGIXPROC) (Display *dpy, int *npipes); typedef int ( *PFNGLXHYPERPIPECONFIGSGIXPROC) (Display *dpy, int networkId, int npipes, GLXHyperpipeConfigSGIX *cfg, int *hpId); typedef GLXHyperpipeConfigSGIX *( *PFNGLXQUERYHYPERPIPECONFIGSGIXPROC) (Display *dpy, int hpId, int *npipes); typedef int ( *PFNGLXDESTROYHYPERPIPECONFIGSGIXPROC) (Display *dpy, int hpId); typedef int ( *PFNGLXBINDHYPERPIPESGIXPROC) (Display *dpy, int hpId); typedef int ( *PFNGLXQUERYHYPERPIPEBESTATTRIBSGIXPROC) (Display *dpy, int timeSlice, int attrib, int size, void *attribList, void *returnAttribList); typedef int ( *PFNGLXHYPERPIPEATTRIBSGIXPROC) (Display *dpy, int timeSlice, int attrib, int size, void *attribList); typedef int ( *PFNGLXQUERYHYPERPIPEATTRIBSGIXPROC) (Display *dpy, int timeSlice, int attrib, int size, void *returnAttribList); #ifdef GLX_GLXEXT_PROTOTYPES GLXHyperpipeNetworkSGIX *glXQueryHyperpipeNetworkSGIX (Display *dpy, int *npipes); int glXHyperpipeConfigSGIX (Display *dpy, int networkId, int npipes, GLXHyperpipeConfigSGIX *cfg, int *hpId); GLXHyperpipeConfigSGIX *glXQueryHyperpipeConfigSGIX (Display *dpy, int hpId, int *npipes); int glXDestroyHyperpipeConfigSGIX (Display *dpy, int hpId); int glXBindHyperpipeSGIX (Display *dpy, int hpId); int glXQueryHyperpipeBestAttribSGIX (Display *dpy, int timeSlice, int attrib, int size, void *attribList, void *returnAttribList); int glXHyperpipeAttribSGIX (Display *dpy, int timeSlice, int attrib, int size, void *attribList); int glXQueryHyperpipeAttribSGIX (Display *dpy, int timeSlice, int attrib, int size, void *returnAttribList); #endif #endif /* GLX_SGIX_hyperpipe */ #ifndef GLX_SGIX_pbuffer #define GLX_SGIX_pbuffer 1 #define GLX_PBUFFER_BIT_SGIX 0x00000004 #define GLX_BUFFER_CLOBBER_MASK_SGIX 0x08000000 #define GLX_FRONT_LEFT_BUFFER_BIT_SGIX 0x00000001 #define GLX_FRONT_RIGHT_BUFFER_BIT_SGIX 0x00000002 #define GLX_BACK_LEFT_BUFFER_BIT_SGIX 0x00000004 #define GLX_BACK_RIGHT_BUFFER_BIT_SGIX 0x00000008 #define GLX_AUX_BUFFERS_BIT_SGIX 0x00000010 #define GLX_DEPTH_BUFFER_BIT_SGIX 0x00000020 #define GLX_STENCIL_BUFFER_BIT_SGIX 0x00000040 #define GLX_ACCUM_BUFFER_BIT_SGIX 0x00000080 #define GLX_SAMPLE_BUFFERS_BIT_SGIX 0x00000100 #define GLX_MAX_PBUFFER_WIDTH_SGIX 0x8016 #define GLX_MAX_PBUFFER_HEIGHT_SGIX 0x8017 #define GLX_MAX_PBUFFER_PIXELS_SGIX 0x8018 #define GLX_OPTIMAL_PBUFFER_WIDTH_SGIX 0x8019 #define GLX_OPTIMAL_PBUFFER_HEIGHT_SGIX 0x801A #define GLX_PRESERVED_CONTENTS_SGIX 0x801B #define GLX_LARGEST_PBUFFER_SGIX 0x801C #define GLX_WIDTH_SGIX 0x801D #define GLX_HEIGHT_SGIX 0x801E #define GLX_EVENT_MASK_SGIX 0x801F #define GLX_DAMAGED_SGIX 0x8020 #define GLX_SAVED_SGIX 0x8021 #define GLX_WINDOW_SGIX 0x8022 #define GLX_PBUFFER_SGIX 0x8023 typedef GLXPbufferSGIX ( *PFNGLXCREATEGLXPBUFFERSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, unsigned int width, unsigned int height, int *attrib_list); typedef void ( *PFNGLXDESTROYGLXPBUFFERSGIXPROC) (Display *dpy, GLXPbufferSGIX pbuf); typedef void ( *PFNGLXQUERYGLXPBUFFERSGIXPROC) (Display *dpy, GLXPbufferSGIX pbuf, int attribute, unsigned int *value); typedef void ( *PFNGLXSELECTEVENTSGIXPROC) (Display *dpy, GLXDrawable drawable, unsigned long mask); typedef void ( *PFNGLXGETSELECTEDEVENTSGIXPROC) (Display *dpy, GLXDrawable drawable, unsigned long *mask); #ifdef GLX_GLXEXT_PROTOTYPES GLXPbufferSGIX glXCreateGLXPbufferSGIX (Display *dpy, GLXFBConfigSGIX config, unsigned int width, unsigned int height, int *attrib_list); void glXDestroyGLXPbufferSGIX (Display *dpy, GLXPbufferSGIX pbuf); void glXQueryGLXPbufferSGIX (Display *dpy, GLXPbufferSGIX pbuf, int attribute, unsigned int *value); void glXSelectEventSGIX (Display *dpy, GLXDrawable drawable, unsigned long mask); void glXGetSelectedEventSGIX (Display *dpy, GLXDrawable drawable, unsigned long *mask); #endif #endif /* GLX_SGIX_pbuffer */ #ifndef GLX_SGIX_swap_barrier #define GLX_SGIX_swap_barrier 1 typedef void ( *PFNGLXBINDSWAPBARRIERSGIXPROC) (Display *dpy, GLXDrawable drawable, int barrier); typedef Bool ( *PFNGLXQUERYMAXSWAPBARRIERSSGIXPROC) (Display *dpy, int screen, int *max); #ifdef GLX_GLXEXT_PROTOTYPES void glXBindSwapBarrierSGIX (Display *dpy, GLXDrawable drawable, int barrier); Bool glXQueryMaxSwapBarriersSGIX (Display *dpy, int screen, int *max); #endif #endif /* GLX_SGIX_swap_barrier */ #ifndef GLX_SGIX_swap_group #define GLX_SGIX_swap_group 1 typedef void ( *PFNGLXJOINSWAPGROUPSGIXPROC) (Display *dpy, GLXDrawable drawable, GLXDrawable member); #ifdef GLX_GLXEXT_PROTOTYPES void glXJoinSwapGroupSGIX (Display *dpy, GLXDrawable drawable, GLXDrawable member); #endif #endif /* GLX_SGIX_swap_group */ #ifndef GLX_SGIX_video_resize #define GLX_SGIX_video_resize 1 #define GLX_SYNC_FRAME_SGIX 0x00000000 #define GLX_SYNC_SWAP_SGIX 0x00000001 typedef int ( *PFNGLXBINDCHANNELTOWINDOWSGIXPROC) (Display *display, int screen, int channel, Window window); typedef int ( *PFNGLXCHANNELRECTSGIXPROC) (Display *display, int screen, int channel, int x, int y, int w, int h); typedef int ( *PFNGLXQUERYCHANNELRECTSGIXPROC) (Display *display, int screen, int channel, int *dx, int *dy, int *dw, int *dh); typedef int ( *PFNGLXQUERYCHANNELDELTASSGIXPROC) (Display *display, int screen, int channel, int *x, int *y, int *w, int *h); typedef int ( *PFNGLXCHANNELRECTSYNCSGIXPROC) (Display *display, int screen, int channel, GLenum synctype); #ifdef GLX_GLXEXT_PROTOTYPES int glXBindChannelToWindowSGIX (Display *display, int screen, int channel, Window window); int glXChannelRectSGIX (Display *display, int screen, int channel, int x, int y, int w, int h); int glXQueryChannelRectSGIX (Display *display, int screen, int channel, int *dx, int *dy, int *dw, int *dh); int glXQueryChannelDeltasSGIX (Display *display, int screen, int channel, int *x, int *y, int *w, int *h); int glXChannelRectSyncSGIX (Display *display, int screen, int channel, GLenum synctype); #endif #endif /* GLX_SGIX_video_resize */ #ifndef GLX_SGIX_video_source #define GLX_SGIX_video_source 1 typedef XID GLXVideoSourceSGIX; #ifdef _VL_H typedef GLXVideoSourceSGIX ( *PFNGLXCREATEGLXVIDEOSOURCESGIXPROC) (Display *display, int screen, VLServer server, VLPath path, int nodeClass, VLNode drainNode); typedef void ( *PFNGLXDESTROYGLXVIDEOSOURCESGIXPROC) (Display *dpy, GLXVideoSourceSGIX glxvideosource); #ifdef GLX_GLXEXT_PROTOTYPES GLXVideoSourceSGIX glXCreateGLXVideoSourceSGIX (Display *display, int screen, VLServer server, VLPath path, int nodeClass, VLNode drainNode); void glXDestroyGLXVideoSourceSGIX (Display *dpy, GLXVideoSourceSGIX glxvideosource); #endif #endif /* _VL_H */ #endif /* GLX_SGIX_video_source */ #ifndef GLX_SGIX_visual_select_group #define GLX_SGIX_visual_select_group 1 #define GLX_VISUAL_SELECT_GROUP_SGIX 0x8028 #endif /* GLX_SGIX_visual_select_group */ #ifndef GLX_SGI_cushion #define GLX_SGI_cushion 1 typedef void ( *PFNGLXCUSHIONSGIPROC) (Display *dpy, Window window, float cushion); #ifdef GLX_GLXEXT_PROTOTYPES void glXCushionSGI (Display *dpy, Window window, float cushion); #endif #endif /* GLX_SGI_cushion */ #ifndef GLX_SGI_make_current_read #define GLX_SGI_make_current_read 1 typedef Bool ( *PFNGLXMAKECURRENTREADSGIPROC) (Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); typedef GLXDrawable ( *PFNGLXGETCURRENTREADDRAWABLESGIPROC) (void); #ifdef GLX_GLXEXT_PROTOTYPES Bool glXMakeCurrentReadSGI (Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); GLXDrawable glXGetCurrentReadDrawableSGI (void); #endif #endif /* GLX_SGI_make_current_read */ #ifndef GLX_SGI_swap_control #define GLX_SGI_swap_control 1 typedef int ( *PFNGLXSWAPINTERVALSGIPROC) (int interval); #ifdef GLX_GLXEXT_PROTOTYPES int glXSwapIntervalSGI (int interval); #endif #endif /* GLX_SGI_swap_control */ #ifndef GLX_SGI_video_sync #define GLX_SGI_video_sync 1 typedef int ( *PFNGLXGETVIDEOSYNCSGIPROC) (unsigned int *count); typedef int ( *PFNGLXWAITVIDEOSYNCSGIPROC) (int divisor, int remainder, unsigned int *count); #ifdef GLX_GLXEXT_PROTOTYPES int glXGetVideoSyncSGI (unsigned int *count); int glXWaitVideoSyncSGI (int divisor, int remainder, unsigned int *count); #endif #endif /* GLX_SGI_video_sync */ #ifndef GLX_SUN_get_transparent_index #define GLX_SUN_get_transparent_index 1 typedef Status ( *PFNGLXGETTRANSPARENTINDEXSUNPROC) (Display *dpy, Window overlay, Window underlay, unsigned long *pTransparentIndex); #ifdef GLX_GLXEXT_PROTOTYPES Status glXGetTransparentIndexSUN (Display *dpy, Window overlay, Window underlay, unsigned long *pTransparentIndex); #endif #endif /* GLX_SUN_get_transparent_index */ #ifdef __cplusplus } #endif #endif ================================================ FILE: src/Common/GLInclude/khrplatform.h ================================================ #ifndef __khrplatform_h_ #define __khrplatform_h_ /* ** Copyright (c) 2008-2018 The Khronos Group Inc. ** ** Permission is hereby granted, free of charge, to any person obtaining a ** copy of this software and/or associated documentation files (the ** "Materials"), to deal in the Materials without restriction, including ** without limitation the rights to use, copy, modify, merge, publish, ** distribute, sublicense, and/or sell copies of the Materials, and to ** permit persons to whom the Materials are 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 Materials. ** ** THE MATERIALS ARE 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 ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. */ /* Khronos platform-specific types and definitions. * * The master copy of khrplatform.h is maintained in the Khronos EGL * Registry repository at https://github.com/KhronosGroup/EGL-Registry * The last semantic modification to khrplatform.h was at commit ID: * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 * * Adopters may modify this file to suit their platform. Adopters are * encouraged to submit platform specific modifications to the Khronos * group so that they can be included in future versions of this file. * Please submit changes by filing pull requests or issues on * the EGL Registry repository linked above. * * * See the Implementer's Guidelines for information about where this file * should be located on your system and for more details of its use: * http://www.khronos.org/registry/implementers_guide.pdf * * This file should be included as * #include * by Khronos client API header files that use its types and defines. * * The types in khrplatform.h should only be used to define API-specific types. * * Types defined in khrplatform.h: * khronos_int8_t signed 8 bit * khronos_uint8_t unsigned 8 bit * khronos_int16_t signed 16 bit * khronos_uint16_t unsigned 16 bit * khronos_int32_t signed 32 bit * khronos_uint32_t unsigned 32 bit * khronos_int64_t signed 64 bit * khronos_uint64_t unsigned 64 bit * khronos_intptr_t signed same number of bits as a pointer * khronos_uintptr_t unsigned same number of bits as a pointer * khronos_ssize_t signed size * khronos_usize_t unsigned size * khronos_float_t signed 32 bit floating point * khronos_time_ns_t unsigned 64 bit time in nanoseconds * khronos_utime_nanoseconds_t unsigned time interval or absolute time in * nanoseconds * khronos_stime_nanoseconds_t signed time interval in nanoseconds * khronos_boolean_enum_t enumerated boolean type. This should * only be used as a base type when a client API's boolean type is * an enum. Client APIs which use an integer or other type for * booleans cannot use this as the base type for their boolean. * * Tokens defined in khrplatform.h: * * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. * * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. * * Calling convention macros defined in this file: * KHRONOS_APICALL * KHRONOS_APIENTRY * KHRONOS_APIATTRIBUTES * * These may be used in function prototypes as: * * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( * int arg1, * int arg2) KHRONOS_APIATTRIBUTES; */ #if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) # define KHRONOS_STATIC 1 #endif /*------------------------------------------------------------------------- * Definition of KHRONOS_APICALL *------------------------------------------------------------------------- * This precedes the return type of the function in the function prototype. */ #if defined(KHRONOS_STATIC) /* If the preprocessor constant KHRONOS_STATIC is defined, make the * header compatible with static linking. */ # define KHRONOS_APICALL #elif defined(_WIN32) # define KHRONOS_APICALL __declspec(dllimport) #elif defined (__SYMBIAN32__) # define KHRONOS_APICALL IMPORT_C #elif defined(__ANDROID__) # define KHRONOS_APICALL __attribute__((visibility("default"))) #else # define KHRONOS_APICALL #endif /*------------------------------------------------------------------------- * Definition of KHRONOS_APIENTRY *------------------------------------------------------------------------- * This follows the return type of the function and precedes the function * name in the function prototype. */ #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) /* Win32 but not WinCE */ # define KHRONOS_APIENTRY __stdcall #else # define KHRONOS_APIENTRY #endif /*------------------------------------------------------------------------- * Definition of KHRONOS_APIATTRIBUTES *------------------------------------------------------------------------- * This follows the closing parenthesis of the function prototype arguments. */ #if defined (__ARMCC_2__) #define KHRONOS_APIATTRIBUTES __softfp #else #define KHRONOS_APIATTRIBUTES #endif /*------------------------------------------------------------------------- * basic type definitions *-----------------------------------------------------------------------*/ #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) /* * Using */ #include typedef int32_t khronos_int32_t; typedef uint32_t khronos_uint32_t; typedef int64_t khronos_int64_t; typedef uint64_t khronos_uint64_t; #define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_FLOAT 1 /* * To support platform where unsigned long cannot be used interchangeably with * inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t. * Ideally, we could just use (u)intptr_t everywhere, but this could result in * ABI breakage if khronos_uintptr_t is changed from unsigned long to * unsigned long long or similar (this results in different C++ name mangling). * To avoid changes for existing platforms, we restrict usage of intptr_t to * platforms where the size of a pointer is larger than the size of long. */ #if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__) #if __SIZEOF_POINTER__ > __SIZEOF_LONG__ #define KHRONOS_USE_INTPTR_T #endif #endif #elif defined(__VMS ) || defined(__sgi) /* * Using */ #include typedef int32_t khronos_int32_t; typedef uint32_t khronos_uint32_t; typedef int64_t khronos_int64_t; typedef uint64_t khronos_uint64_t; #define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_FLOAT 1 #elif defined(_WIN32) && !defined(__SCITECH_SNAP__) /* * Win32 */ typedef __int32 khronos_int32_t; typedef unsigned __int32 khronos_uint32_t; typedef __int64 khronos_int64_t; typedef unsigned __int64 khronos_uint64_t; #define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_FLOAT 1 #elif defined(__sun__) || defined(__digital__) /* * Sun or Digital */ typedef int khronos_int32_t; typedef unsigned int khronos_uint32_t; #if defined(__arch64__) || defined(_LP64) typedef long int khronos_int64_t; typedef unsigned long int khronos_uint64_t; #else typedef long long int khronos_int64_t; typedef unsigned long long int khronos_uint64_t; #endif /* __arch64__ */ #define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_FLOAT 1 #elif 0 /* * Hypothetical platform with no float or int64 support */ typedef int khronos_int32_t; typedef unsigned int khronos_uint32_t; #define KHRONOS_SUPPORT_INT64 0 #define KHRONOS_SUPPORT_FLOAT 0 #else /* * Generic fallback */ #include typedef int32_t khronos_int32_t; typedef uint32_t khronos_uint32_t; typedef int64_t khronos_int64_t; typedef uint64_t khronos_uint64_t; #define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_FLOAT 1 #endif /* * Types that are (so far) the same on all platforms */ typedef signed char khronos_int8_t; typedef unsigned char khronos_uint8_t; typedef signed short int khronos_int16_t; typedef unsigned short int khronos_uint16_t; /* * Types that differ between LLP64 and LP64 architectures - in LLP64, * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears * to be the only LLP64 architecture in current use. */ #ifdef KHRONOS_USE_INTPTR_T typedef intptr_t khronos_intptr_t; typedef uintptr_t khronos_uintptr_t; #elif defined(_WIN64) typedef signed long long int khronos_intptr_t; typedef unsigned long long int khronos_uintptr_t; #else typedef signed long int khronos_intptr_t; typedef unsigned long int khronos_uintptr_t; #endif #if defined(_WIN64) typedef signed long long int khronos_ssize_t; typedef unsigned long long int khronos_usize_t; #else typedef signed long int khronos_ssize_t; typedef unsigned long int khronos_usize_t; #endif #if KHRONOS_SUPPORT_FLOAT /* * Float type */ typedef float khronos_float_t; #endif #if KHRONOS_SUPPORT_INT64 /* Time types * * These types can be used to represent a time interval in nanoseconds or * an absolute Unadjusted System Time. Unadjusted System Time is the number * of nanoseconds since some arbitrary system event (e.g. since the last * time the system booted). The Unadjusted System Time is an unsigned * 64 bit value that wraps back to 0 every 584 years. Time intervals * may be either signed or unsigned. */ typedef khronos_uint64_t khronos_utime_nanoseconds_t; typedef khronos_int64_t khronos_stime_nanoseconds_t; #endif /* * Dummy value used to pad enum types to 32 bits. */ #ifndef KHRONOS_MAX_ENUM #define KHRONOS_MAX_ENUM 0x7FFFFFFF #endif /* * Enumerated boolean type * * Values other than zero should be considered to be true. Therefore * comparisons should not be made against KHRONOS_TRUE. */ typedef enum { KHRONOS_FALSE = 0, KHRONOS_TRUE = 1, KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM } khronos_boolean_enum_t; #endif /* __khrplatform_h_ */ ================================================ FILE: src/Common/GLInclude/wglext.h ================================================ #ifndef __wgl_wglext_h_ #define __wgl_wglext_h_ 1 #ifdef __cplusplus extern "C" { #endif /* ** Copyright 2013-2020 The Khronos Group Inc. ** SPDX-License-Identifier: MIT ** ** This header is generated from the Khronos OpenGL / OpenGL ES XML ** API Registry. The current version of the Registry, generator scripts ** used to make the header, and the header can be found at ** https://github.com/KhronosGroup/OpenGL-Registry */ #if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) #define WIN32_LEAN_AND_MEAN 1 #include #endif #define WGL_WGLEXT_VERSION 20211115 /* Generated C header for: * API: wgl * Versions considered: .* * Versions emitted: _nomatch_^ * Default extensions included: wgl * Additional extensions included: _nomatch_^ * Extensions removed: _nomatch_^ */ #ifndef WGL_ARB_buffer_region #define WGL_ARB_buffer_region 1 #define WGL_FRONT_COLOR_BUFFER_BIT_ARB 0x00000001 #define WGL_BACK_COLOR_BUFFER_BIT_ARB 0x00000002 #define WGL_DEPTH_BUFFER_BIT_ARB 0x00000004 #define WGL_STENCIL_BUFFER_BIT_ARB 0x00000008 typedef HANDLE (WINAPI * PFNWGLCREATEBUFFERREGIONARBPROC) (HDC hDC, int iLayerPlane, UINT uType); typedef VOID (WINAPI * PFNWGLDELETEBUFFERREGIONARBPROC) (HANDLE hRegion); typedef BOOL (WINAPI * PFNWGLSAVEBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height); typedef BOOL (WINAPI * PFNWGLRESTOREBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height, int xSrc, int ySrc); #ifdef WGL_WGLEXT_PROTOTYPES HANDLE WINAPI wglCreateBufferRegionARB (HDC hDC, int iLayerPlane, UINT uType); VOID WINAPI wglDeleteBufferRegionARB (HANDLE hRegion); BOOL WINAPI wglSaveBufferRegionARB (HANDLE hRegion, int x, int y, int width, int height); BOOL WINAPI wglRestoreBufferRegionARB (HANDLE hRegion, int x, int y, int width, int height, int xSrc, int ySrc); #endif #endif /* WGL_ARB_buffer_region */ #ifndef WGL_ARB_context_flush_control #define WGL_ARB_context_flush_control 1 #define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 #define WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0 #define WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 #endif /* WGL_ARB_context_flush_control */ #ifndef WGL_ARB_create_context #define WGL_ARB_create_context 1 #define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 #define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 #define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 #define WGL_CONTEXT_FLAGS_ARB 0x2094 #define ERROR_INVALID_VERSION_ARB 0x2095 typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC, HGLRC hShareContext, const int *attribList); #ifdef WGL_WGLEXT_PROTOTYPES HGLRC WINAPI wglCreateContextAttribsARB (HDC hDC, HGLRC hShareContext, const int *attribList); #endif #endif /* WGL_ARB_create_context */ #ifndef WGL_ARB_create_context_no_error #define WGL_ARB_create_context_no_error 1 #define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31B3 #endif /* WGL_ARB_create_context_no_error */ #ifndef WGL_ARB_create_context_profile #define WGL_ARB_create_context_profile 1 #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 #define ERROR_INVALID_PROFILE_ARB 0x2096 #endif /* WGL_ARB_create_context_profile */ #ifndef WGL_ARB_create_context_robustness #define WGL_ARB_create_context_robustness 1 #define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 #define WGL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 #define WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 #define WGL_NO_RESET_NOTIFICATION_ARB 0x8261 #endif /* WGL_ARB_create_context_robustness */ #ifndef WGL_ARB_extensions_string #define WGL_ARB_extensions_string 1 typedef const char *(WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC) (HDC hdc); #ifdef WGL_WGLEXT_PROTOTYPES const char *WINAPI wglGetExtensionsStringARB (HDC hdc); #endif #endif /* WGL_ARB_extensions_string */ #ifndef WGL_ARB_framebuffer_sRGB #define WGL_ARB_framebuffer_sRGB 1 #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9 #endif /* WGL_ARB_framebuffer_sRGB */ #ifndef WGL_ARB_make_current_read #define WGL_ARB_make_current_read 1 #define ERROR_INVALID_PIXEL_TYPE_ARB 0x2043 #define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTARBPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCARBPROC) (void); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglMakeContextCurrentARB (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); HDC WINAPI wglGetCurrentReadDCARB (void); #endif #endif /* WGL_ARB_make_current_read */ #ifndef WGL_ARB_multisample #define WGL_ARB_multisample 1 #define WGL_SAMPLE_BUFFERS_ARB 0x2041 #define WGL_SAMPLES_ARB 0x2042 #endif /* WGL_ARB_multisample */ #ifndef WGL_ARB_pbuffer #define WGL_ARB_pbuffer 1 DECLARE_HANDLE(HPBUFFERARB); #define WGL_DRAW_TO_PBUFFER_ARB 0x202D #define WGL_MAX_PBUFFER_PIXELS_ARB 0x202E #define WGL_MAX_PBUFFER_WIDTH_ARB 0x202F #define WGL_MAX_PBUFFER_HEIGHT_ARB 0x2030 #define WGL_PBUFFER_LARGEST_ARB 0x2033 #define WGL_PBUFFER_WIDTH_ARB 0x2034 #define WGL_PBUFFER_HEIGHT_ARB 0x2035 #define WGL_PBUFFER_LOST_ARB 0x2036 typedef HPBUFFERARB (WINAPI * PFNWGLCREATEPBUFFERARBPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); typedef HDC (WINAPI * PFNWGLGETPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer); typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer, HDC hDC); typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFERARBPROC) (HPBUFFERARB hPbuffer); typedef BOOL (WINAPI * PFNWGLQUERYPBUFFERARBPROC) (HPBUFFERARB hPbuffer, int iAttribute, int *piValue); #ifdef WGL_WGLEXT_PROTOTYPES HPBUFFERARB WINAPI wglCreatePbufferARB (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); HDC WINAPI wglGetPbufferDCARB (HPBUFFERARB hPbuffer); int WINAPI wglReleasePbufferDCARB (HPBUFFERARB hPbuffer, HDC hDC); BOOL WINAPI wglDestroyPbufferARB (HPBUFFERARB hPbuffer); BOOL WINAPI wglQueryPbufferARB (HPBUFFERARB hPbuffer, int iAttribute, int *piValue); #endif #endif /* WGL_ARB_pbuffer */ #ifndef WGL_ARB_pixel_format #define WGL_ARB_pixel_format 1 #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 #define WGL_DRAW_TO_WINDOW_ARB 0x2001 #define WGL_DRAW_TO_BITMAP_ARB 0x2002 #define WGL_ACCELERATION_ARB 0x2003 #define WGL_NEED_PALETTE_ARB 0x2004 #define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 #define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 #define WGL_SWAP_METHOD_ARB 0x2007 #define WGL_NUMBER_OVERLAYS_ARB 0x2008 #define WGL_NUMBER_UNDERLAYS_ARB 0x2009 #define WGL_TRANSPARENT_ARB 0x200A #define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 #define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 #define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 #define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A #define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B #define WGL_SHARE_DEPTH_ARB 0x200C #define WGL_SHARE_STENCIL_ARB 0x200D #define WGL_SHARE_ACCUM_ARB 0x200E #define WGL_SUPPORT_GDI_ARB 0x200F #define WGL_SUPPORT_OPENGL_ARB 0x2010 #define WGL_DOUBLE_BUFFER_ARB 0x2011 #define WGL_STEREO_ARB 0x2012 #define WGL_PIXEL_TYPE_ARB 0x2013 #define WGL_COLOR_BITS_ARB 0x2014 #define WGL_RED_BITS_ARB 0x2015 #define WGL_RED_SHIFT_ARB 0x2016 #define WGL_GREEN_BITS_ARB 0x2017 #define WGL_GREEN_SHIFT_ARB 0x2018 #define WGL_BLUE_BITS_ARB 0x2019 #define WGL_BLUE_SHIFT_ARB 0x201A #define WGL_ALPHA_BITS_ARB 0x201B #define WGL_ALPHA_SHIFT_ARB 0x201C #define WGL_ACCUM_BITS_ARB 0x201D #define WGL_ACCUM_RED_BITS_ARB 0x201E #define WGL_ACCUM_GREEN_BITS_ARB 0x201F #define WGL_ACCUM_BLUE_BITS_ARB 0x2020 #define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 #define WGL_DEPTH_BITS_ARB 0x2022 #define WGL_STENCIL_BITS_ARB 0x2023 #define WGL_AUX_BUFFERS_ARB 0x2024 #define WGL_NO_ACCELERATION_ARB 0x2025 #define WGL_GENERIC_ACCELERATION_ARB 0x2026 #define WGL_FULL_ACCELERATION_ARB 0x2027 #define WGL_SWAP_EXCHANGE_ARB 0x2028 #define WGL_SWAP_COPY_ARB 0x2029 #define WGL_SWAP_UNDEFINED_ARB 0x202A #define WGL_TYPE_RGBA_ARB 0x202B #define WGL_TYPE_COLORINDEX_ARB 0x202C typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues); typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglGetPixelFormatAttribivARB (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); BOOL WINAPI wglGetPixelFormatAttribfvARB (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues); BOOL WINAPI wglChoosePixelFormatARB (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); #endif #endif /* WGL_ARB_pixel_format */ #ifndef WGL_ARB_pixel_format_float #define WGL_ARB_pixel_format_float 1 #define WGL_TYPE_RGBA_FLOAT_ARB 0x21A0 #endif /* WGL_ARB_pixel_format_float */ #ifndef WGL_ARB_render_texture #define WGL_ARB_render_texture 1 #define WGL_BIND_TO_TEXTURE_RGB_ARB 0x2070 #define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 #define WGL_TEXTURE_FORMAT_ARB 0x2072 #define WGL_TEXTURE_TARGET_ARB 0x2073 #define WGL_MIPMAP_TEXTURE_ARB 0x2074 #define WGL_TEXTURE_RGB_ARB 0x2075 #define WGL_TEXTURE_RGBA_ARB 0x2076 #define WGL_NO_TEXTURE_ARB 0x2077 #define WGL_TEXTURE_CUBE_MAP_ARB 0x2078 #define WGL_TEXTURE_1D_ARB 0x2079 #define WGL_TEXTURE_2D_ARB 0x207A #define WGL_MIPMAP_LEVEL_ARB 0x207B #define WGL_CUBE_MAP_FACE_ARB 0x207C #define WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x207D #define WGL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x207E #define WGL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x207F #define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x2080 #define WGL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x2081 #define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x2082 #define WGL_FRONT_LEFT_ARB 0x2083 #define WGL_FRONT_RIGHT_ARB 0x2084 #define WGL_BACK_LEFT_ARB 0x2085 #define WGL_BACK_RIGHT_ARB 0x2086 #define WGL_AUX0_ARB 0x2087 #define WGL_AUX1_ARB 0x2088 #define WGL_AUX2_ARB 0x2089 #define WGL_AUX3_ARB 0x208A #define WGL_AUX4_ARB 0x208B #define WGL_AUX5_ARB 0x208C #define WGL_AUX6_ARB 0x208D #define WGL_AUX7_ARB 0x208E #define WGL_AUX8_ARB 0x208F #define WGL_AUX9_ARB 0x2090 typedef BOOL (WINAPI * PFNWGLBINDTEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); typedef BOOL (WINAPI * PFNWGLRELEASETEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); typedef BOOL (WINAPI * PFNWGLSETPBUFFERATTRIBARBPROC) (HPBUFFERARB hPbuffer, const int *piAttribList); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglBindTexImageARB (HPBUFFERARB hPbuffer, int iBuffer); BOOL WINAPI wglReleaseTexImageARB (HPBUFFERARB hPbuffer, int iBuffer); BOOL WINAPI wglSetPbufferAttribARB (HPBUFFERARB hPbuffer, const int *piAttribList); #endif #endif /* WGL_ARB_render_texture */ #ifndef WGL_ARB_robustness_application_isolation #define WGL_ARB_robustness_application_isolation 1 #define WGL_CONTEXT_RESET_ISOLATION_BIT_ARB 0x00000008 #endif /* WGL_ARB_robustness_application_isolation */ #ifndef WGL_ARB_robustness_share_group_isolation #define WGL_ARB_robustness_share_group_isolation 1 #endif /* WGL_ARB_robustness_share_group_isolation */ #ifndef WGL_3DFX_multisample #define WGL_3DFX_multisample 1 #define WGL_SAMPLE_BUFFERS_3DFX 0x2060 #define WGL_SAMPLES_3DFX 0x2061 #endif /* WGL_3DFX_multisample */ #ifndef WGL_3DL_stereo_control #define WGL_3DL_stereo_control 1 #define WGL_STEREO_EMITTER_ENABLE_3DL 0x2055 #define WGL_STEREO_EMITTER_DISABLE_3DL 0x2056 #define WGL_STEREO_POLARITY_NORMAL_3DL 0x2057 #define WGL_STEREO_POLARITY_INVERT_3DL 0x2058 typedef BOOL (WINAPI * PFNWGLSETSTEREOEMITTERSTATE3DLPROC) (HDC hDC, UINT uState); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglSetStereoEmitterState3DL (HDC hDC, UINT uState); #endif #endif /* WGL_3DL_stereo_control */ #ifndef WGL_AMD_gpu_association #define WGL_AMD_gpu_association 1 #define WGL_GPU_VENDOR_AMD 0x1F00 #define WGL_GPU_RENDERER_STRING_AMD 0x1F01 #define WGL_GPU_OPENGL_VERSION_STRING_AMD 0x1F02 #define WGL_GPU_FASTEST_TARGET_GPUS_AMD 0x21A2 #define WGL_GPU_RAM_AMD 0x21A3 #define WGL_GPU_CLOCK_AMD 0x21A4 #define WGL_GPU_NUM_PIPES_AMD 0x21A5 #define WGL_GPU_NUM_SIMD_AMD 0x21A6 #define WGL_GPU_NUM_RB_AMD 0x21A7 #define WGL_GPU_NUM_SPI_AMD 0x21A8 typedef UINT (WINAPI * PFNWGLGETGPUIDSAMDPROC) (UINT maxCount, UINT *ids); typedef INT (WINAPI * PFNWGLGETGPUINFOAMDPROC) (UINT id, INT property, GLenum dataType, UINT size, void *data); typedef UINT (WINAPI * PFNWGLGETCONTEXTGPUIDAMDPROC) (HGLRC hglrc); typedef HGLRC (WINAPI * PFNWGLCREATEASSOCIATEDCONTEXTAMDPROC) (UINT id); typedef HGLRC (WINAPI * PFNWGLCREATEASSOCIATEDCONTEXTATTRIBSAMDPROC) (UINT id, HGLRC hShareContext, const int *attribList); typedef BOOL (WINAPI * PFNWGLDELETEASSOCIATEDCONTEXTAMDPROC) (HGLRC hglrc); typedef BOOL (WINAPI * PFNWGLMAKEASSOCIATEDCONTEXTCURRENTAMDPROC) (HGLRC hglrc); typedef HGLRC (WINAPI * PFNWGLGETCURRENTASSOCIATEDCONTEXTAMDPROC) (void); typedef VOID (WINAPI * PFNWGLBLITCONTEXTFRAMEBUFFERAMDPROC) (HGLRC dstCtx, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); #ifdef WGL_WGLEXT_PROTOTYPES UINT WINAPI wglGetGPUIDsAMD (UINT maxCount, UINT *ids); INT WINAPI wglGetGPUInfoAMD (UINT id, INT property, GLenum dataType, UINT size, void *data); UINT WINAPI wglGetContextGPUIDAMD (HGLRC hglrc); HGLRC WINAPI wglCreateAssociatedContextAMD (UINT id); HGLRC WINAPI wglCreateAssociatedContextAttribsAMD (UINT id, HGLRC hShareContext, const int *attribList); BOOL WINAPI wglDeleteAssociatedContextAMD (HGLRC hglrc); BOOL WINAPI wglMakeAssociatedContextCurrentAMD (HGLRC hglrc); HGLRC WINAPI wglGetCurrentAssociatedContextAMD (void); VOID WINAPI wglBlitContextFramebufferAMD (HGLRC dstCtx, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); #endif #endif /* WGL_AMD_gpu_association */ #ifndef WGL_ATI_pixel_format_float #define WGL_ATI_pixel_format_float 1 #define WGL_TYPE_RGBA_FLOAT_ATI 0x21A0 #endif /* WGL_ATI_pixel_format_float */ #ifndef WGL_ATI_render_texture_rectangle #define WGL_ATI_render_texture_rectangle 1 #define WGL_TEXTURE_RECTANGLE_ATI 0x21A5 #endif /* WGL_ATI_render_texture_rectangle */ #ifndef WGL_EXT_colorspace #define WGL_EXT_colorspace 1 #define WGL_COLORSPACE_EXT 0x309D #define WGL_COLORSPACE_SRGB_EXT 0x3089 #define WGL_COLORSPACE_LINEAR_EXT 0x308A #endif /* WGL_EXT_colorspace */ #ifndef WGL_EXT_create_context_es2_profile #define WGL_EXT_create_context_es2_profile 1 #define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 #endif /* WGL_EXT_create_context_es2_profile */ #ifndef WGL_EXT_create_context_es_profile #define WGL_EXT_create_context_es_profile 1 #define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 #endif /* WGL_EXT_create_context_es_profile */ #ifndef WGL_EXT_depth_float #define WGL_EXT_depth_float 1 #define WGL_DEPTH_FLOAT_EXT 0x2040 #endif /* WGL_EXT_depth_float */ #ifndef WGL_EXT_display_color_table #define WGL_EXT_display_color_table 1 typedef GLboolean (WINAPI * PFNWGLCREATEDISPLAYCOLORTABLEEXTPROC) (GLushort id); typedef GLboolean (WINAPI * PFNWGLLOADDISPLAYCOLORTABLEEXTPROC) (const GLushort *table, GLuint length); typedef GLboolean (WINAPI * PFNWGLBINDDISPLAYCOLORTABLEEXTPROC) (GLushort id); typedef VOID (WINAPI * PFNWGLDESTROYDISPLAYCOLORTABLEEXTPROC) (GLushort id); #ifdef WGL_WGLEXT_PROTOTYPES GLboolean WINAPI wglCreateDisplayColorTableEXT (GLushort id); GLboolean WINAPI wglLoadDisplayColorTableEXT (const GLushort *table, GLuint length); GLboolean WINAPI wglBindDisplayColorTableEXT (GLushort id); VOID WINAPI wglDestroyDisplayColorTableEXT (GLushort id); #endif #endif /* WGL_EXT_display_color_table */ #ifndef WGL_EXT_extensions_string #define WGL_EXT_extensions_string 1 typedef const char *(WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC) (void); #ifdef WGL_WGLEXT_PROTOTYPES const char *WINAPI wglGetExtensionsStringEXT (void); #endif #endif /* WGL_EXT_extensions_string */ #ifndef WGL_EXT_framebuffer_sRGB #define WGL_EXT_framebuffer_sRGB 1 #define WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x20A9 #endif /* WGL_EXT_framebuffer_sRGB */ #ifndef WGL_EXT_make_current_read #define WGL_EXT_make_current_read 1 #define ERROR_INVALID_PIXEL_TYPE_EXT 0x2043 typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTEXTPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCEXTPROC) (void); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglMakeContextCurrentEXT (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); HDC WINAPI wglGetCurrentReadDCEXT (void); #endif #endif /* WGL_EXT_make_current_read */ #ifndef WGL_EXT_multisample #define WGL_EXT_multisample 1 #define WGL_SAMPLE_BUFFERS_EXT 0x2041 #define WGL_SAMPLES_EXT 0x2042 #endif /* WGL_EXT_multisample */ #ifndef WGL_EXT_pbuffer #define WGL_EXT_pbuffer 1 DECLARE_HANDLE(HPBUFFEREXT); #define WGL_DRAW_TO_PBUFFER_EXT 0x202D #define WGL_MAX_PBUFFER_PIXELS_EXT 0x202E #define WGL_MAX_PBUFFER_WIDTH_EXT 0x202F #define WGL_MAX_PBUFFER_HEIGHT_EXT 0x2030 #define WGL_OPTIMAL_PBUFFER_WIDTH_EXT 0x2031 #define WGL_OPTIMAL_PBUFFER_HEIGHT_EXT 0x2032 #define WGL_PBUFFER_LARGEST_EXT 0x2033 #define WGL_PBUFFER_WIDTH_EXT 0x2034 #define WGL_PBUFFER_HEIGHT_EXT 0x2035 typedef HPBUFFEREXT (WINAPI * PFNWGLCREATEPBUFFEREXTPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); typedef HDC (WINAPI * PFNWGLGETPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer); typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer, HDC hDC); typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer); typedef BOOL (WINAPI * PFNWGLQUERYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer, int iAttribute, int *piValue); #ifdef WGL_WGLEXT_PROTOTYPES HPBUFFEREXT WINAPI wglCreatePbufferEXT (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); HDC WINAPI wglGetPbufferDCEXT (HPBUFFEREXT hPbuffer); int WINAPI wglReleasePbufferDCEXT (HPBUFFEREXT hPbuffer, HDC hDC); BOOL WINAPI wglDestroyPbufferEXT (HPBUFFEREXT hPbuffer); BOOL WINAPI wglQueryPbufferEXT (HPBUFFEREXT hPbuffer, int iAttribute, int *piValue); #endif #endif /* WGL_EXT_pbuffer */ #ifndef WGL_EXT_pixel_format #define WGL_EXT_pixel_format 1 #define WGL_NUMBER_PIXEL_FORMATS_EXT 0x2000 #define WGL_DRAW_TO_WINDOW_EXT 0x2001 #define WGL_DRAW_TO_BITMAP_EXT 0x2002 #define WGL_ACCELERATION_EXT 0x2003 #define WGL_NEED_PALETTE_EXT 0x2004 #define WGL_NEED_SYSTEM_PALETTE_EXT 0x2005 #define WGL_SWAP_LAYER_BUFFERS_EXT 0x2006 #define WGL_SWAP_METHOD_EXT 0x2007 #define WGL_NUMBER_OVERLAYS_EXT 0x2008 #define WGL_NUMBER_UNDERLAYS_EXT 0x2009 #define WGL_TRANSPARENT_EXT 0x200A #define WGL_TRANSPARENT_VALUE_EXT 0x200B #define WGL_SHARE_DEPTH_EXT 0x200C #define WGL_SHARE_STENCIL_EXT 0x200D #define WGL_SHARE_ACCUM_EXT 0x200E #define WGL_SUPPORT_GDI_EXT 0x200F #define WGL_SUPPORT_OPENGL_EXT 0x2010 #define WGL_DOUBLE_BUFFER_EXT 0x2011 #define WGL_STEREO_EXT 0x2012 #define WGL_PIXEL_TYPE_EXT 0x2013 #define WGL_COLOR_BITS_EXT 0x2014 #define WGL_RED_BITS_EXT 0x2015 #define WGL_RED_SHIFT_EXT 0x2016 #define WGL_GREEN_BITS_EXT 0x2017 #define WGL_GREEN_SHIFT_EXT 0x2018 #define WGL_BLUE_BITS_EXT 0x2019 #define WGL_BLUE_SHIFT_EXT 0x201A #define WGL_ALPHA_BITS_EXT 0x201B #define WGL_ALPHA_SHIFT_EXT 0x201C #define WGL_ACCUM_BITS_EXT 0x201D #define WGL_ACCUM_RED_BITS_EXT 0x201E #define WGL_ACCUM_GREEN_BITS_EXT 0x201F #define WGL_ACCUM_BLUE_BITS_EXT 0x2020 #define WGL_ACCUM_ALPHA_BITS_EXT 0x2021 #define WGL_DEPTH_BITS_EXT 0x2022 #define WGL_STENCIL_BITS_EXT 0x2023 #define WGL_AUX_BUFFERS_EXT 0x2024 #define WGL_NO_ACCELERATION_EXT 0x2025 #define WGL_GENERIC_ACCELERATION_EXT 0x2026 #define WGL_FULL_ACCELERATION_EXT 0x2027 #define WGL_SWAP_EXCHANGE_EXT 0x2028 #define WGL_SWAP_COPY_EXT 0x2029 #define WGL_SWAP_UNDEFINED_EXT 0x202A #define WGL_TYPE_RGBA_EXT 0x202B #define WGL_TYPE_COLORINDEX_EXT 0x202C typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, int *piValues); typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, FLOAT *pfValues); typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATEXTPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglGetPixelFormatAttribivEXT (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, int *piValues); BOOL WINAPI wglGetPixelFormatAttribfvEXT (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, FLOAT *pfValues); BOOL WINAPI wglChoosePixelFormatEXT (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); #endif #endif /* WGL_EXT_pixel_format */ #ifndef WGL_EXT_pixel_format_packed_float #define WGL_EXT_pixel_format_packed_float 1 #define WGL_TYPE_RGBA_UNSIGNED_FLOAT_EXT 0x20A8 #endif /* WGL_EXT_pixel_format_packed_float */ #ifndef WGL_EXT_swap_control #define WGL_EXT_swap_control 1 typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval); typedef int (WINAPI * PFNWGLGETSWAPINTERVALEXTPROC) (void); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglSwapIntervalEXT (int interval); int WINAPI wglGetSwapIntervalEXT (void); #endif #endif /* WGL_EXT_swap_control */ #ifndef WGL_EXT_swap_control_tear #define WGL_EXT_swap_control_tear 1 #endif /* WGL_EXT_swap_control_tear */ #ifndef WGL_I3D_digital_video_control #define WGL_I3D_digital_video_control 1 #define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_FRAMEBUFFER_I3D 0x2050 #define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_VALUE_I3D 0x2051 #define WGL_DIGITAL_VIDEO_CURSOR_INCLUDED_I3D 0x2052 #define WGL_DIGITAL_VIDEO_GAMMA_CORRECTED_I3D 0x2053 typedef BOOL (WINAPI * PFNWGLGETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int *piValue); typedef BOOL (WINAPI * PFNWGLSETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int *piValue); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglGetDigitalVideoParametersI3D (HDC hDC, int iAttribute, int *piValue); BOOL WINAPI wglSetDigitalVideoParametersI3D (HDC hDC, int iAttribute, const int *piValue); #endif #endif /* WGL_I3D_digital_video_control */ #ifndef WGL_I3D_gamma #define WGL_I3D_gamma 1 #define WGL_GAMMA_TABLE_SIZE_I3D 0x204E #define WGL_GAMMA_EXCLUDE_DESKTOP_I3D 0x204F typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int *piValue); typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int *piValue); typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, USHORT *puRed, USHORT *puGreen, USHORT *puBlue); typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, const USHORT *puRed, const USHORT *puGreen, const USHORT *puBlue); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglGetGammaTableParametersI3D (HDC hDC, int iAttribute, int *piValue); BOOL WINAPI wglSetGammaTableParametersI3D (HDC hDC, int iAttribute, const int *piValue); BOOL WINAPI wglGetGammaTableI3D (HDC hDC, int iEntries, USHORT *puRed, USHORT *puGreen, USHORT *puBlue); BOOL WINAPI wglSetGammaTableI3D (HDC hDC, int iEntries, const USHORT *puRed, const USHORT *puGreen, const USHORT *puBlue); #endif #endif /* WGL_I3D_gamma */ #ifndef WGL_I3D_genlock #define WGL_I3D_genlock 1 #define WGL_GENLOCK_SOURCE_MULTIVIEW_I3D 0x2044 #define WGL_GENLOCK_SOURCE_EXTERNAL_SYNC_I3D 0x2045 #define WGL_GENLOCK_SOURCE_EXTERNAL_FIELD_I3D 0x2046 #define WGL_GENLOCK_SOURCE_EXTERNAL_TTL_I3D 0x2047 #define WGL_GENLOCK_SOURCE_DIGITAL_SYNC_I3D 0x2048 #define WGL_GENLOCK_SOURCE_DIGITAL_FIELD_I3D 0x2049 #define WGL_GENLOCK_SOURCE_EDGE_FALLING_I3D 0x204A #define WGL_GENLOCK_SOURCE_EDGE_RISING_I3D 0x204B #define WGL_GENLOCK_SOURCE_EDGE_BOTH_I3D 0x204C typedef BOOL (WINAPI * PFNWGLENABLEGENLOCKI3DPROC) (HDC hDC); typedef BOOL (WINAPI * PFNWGLDISABLEGENLOCKI3DPROC) (HDC hDC); typedef BOOL (WINAPI * PFNWGLISENABLEDGENLOCKI3DPROC) (HDC hDC, BOOL *pFlag); typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEI3DPROC) (HDC hDC, UINT uSource); typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEI3DPROC) (HDC hDC, UINT *uSource); typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT uEdge); typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT *uEdge); typedef BOOL (WINAPI * PFNWGLGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT uRate); typedef BOOL (WINAPI * PFNWGLGETGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT *uRate); typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT uDelay); typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT *uDelay); typedef BOOL (WINAPI * PFNWGLQUERYGENLOCKMAXSOURCEDELAYI3DPROC) (HDC hDC, UINT *uMaxLineDelay, UINT *uMaxPixelDelay); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglEnableGenlockI3D (HDC hDC); BOOL WINAPI wglDisableGenlockI3D (HDC hDC); BOOL WINAPI wglIsEnabledGenlockI3D (HDC hDC, BOOL *pFlag); BOOL WINAPI wglGenlockSourceI3D (HDC hDC, UINT uSource); BOOL WINAPI wglGetGenlockSourceI3D (HDC hDC, UINT *uSource); BOOL WINAPI wglGenlockSourceEdgeI3D (HDC hDC, UINT uEdge); BOOL WINAPI wglGetGenlockSourceEdgeI3D (HDC hDC, UINT *uEdge); BOOL WINAPI wglGenlockSampleRateI3D (HDC hDC, UINT uRate); BOOL WINAPI wglGetGenlockSampleRateI3D (HDC hDC, UINT *uRate); BOOL WINAPI wglGenlockSourceDelayI3D (HDC hDC, UINT uDelay); BOOL WINAPI wglGetGenlockSourceDelayI3D (HDC hDC, UINT *uDelay); BOOL WINAPI wglQueryGenlockMaxSourceDelayI3D (HDC hDC, UINT *uMaxLineDelay, UINT *uMaxPixelDelay); #endif #endif /* WGL_I3D_genlock */ #ifndef WGL_I3D_image_buffer #define WGL_I3D_image_buffer 1 #define WGL_IMAGE_BUFFER_MIN_ACCESS_I3D 0x00000001 #define WGL_IMAGE_BUFFER_LOCK_I3D 0x00000002 typedef LPVOID (WINAPI * PFNWGLCREATEIMAGEBUFFERI3DPROC) (HDC hDC, DWORD dwSize, UINT uFlags); typedef BOOL (WINAPI * PFNWGLDESTROYIMAGEBUFFERI3DPROC) (HDC hDC, LPVOID pAddress); typedef BOOL (WINAPI * PFNWGLASSOCIATEIMAGEBUFFEREVENTSI3DPROC) (HDC hDC, const HANDLE *pEvent, const LPVOID *pAddress, const DWORD *pSize, UINT count); typedef BOOL (WINAPI * PFNWGLRELEASEIMAGEBUFFEREVENTSI3DPROC) (HDC hDC, const LPVOID *pAddress, UINT count); #ifdef WGL_WGLEXT_PROTOTYPES LPVOID WINAPI wglCreateImageBufferI3D (HDC hDC, DWORD dwSize, UINT uFlags); BOOL WINAPI wglDestroyImageBufferI3D (HDC hDC, LPVOID pAddress); BOOL WINAPI wglAssociateImageBufferEventsI3D (HDC hDC, const HANDLE *pEvent, const LPVOID *pAddress, const DWORD *pSize, UINT count); BOOL WINAPI wglReleaseImageBufferEventsI3D (HDC hDC, const LPVOID *pAddress, UINT count); #endif #endif /* WGL_I3D_image_buffer */ #ifndef WGL_I3D_swap_frame_lock #define WGL_I3D_swap_frame_lock 1 typedef BOOL (WINAPI * PFNWGLENABLEFRAMELOCKI3DPROC) (void); typedef BOOL (WINAPI * PFNWGLDISABLEFRAMELOCKI3DPROC) (void); typedef BOOL (WINAPI * PFNWGLISENABLEDFRAMELOCKI3DPROC) (BOOL *pFlag); typedef BOOL (WINAPI * PFNWGLQUERYFRAMELOCKMASTERI3DPROC) (BOOL *pFlag); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglEnableFrameLockI3D (void); BOOL WINAPI wglDisableFrameLockI3D (void); BOOL WINAPI wglIsEnabledFrameLockI3D (BOOL *pFlag); BOOL WINAPI wglQueryFrameLockMasterI3D (BOOL *pFlag); #endif #endif /* WGL_I3D_swap_frame_lock */ #ifndef WGL_I3D_swap_frame_usage #define WGL_I3D_swap_frame_usage 1 typedef BOOL (WINAPI * PFNWGLGETFRAMEUSAGEI3DPROC) (float *pUsage); typedef BOOL (WINAPI * PFNWGLBEGINFRAMETRACKINGI3DPROC) (void); typedef BOOL (WINAPI * PFNWGLENDFRAMETRACKINGI3DPROC) (void); typedef BOOL (WINAPI * PFNWGLQUERYFRAMETRACKINGI3DPROC) (DWORD *pFrameCount, DWORD *pMissedFrames, float *pLastMissedUsage); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglGetFrameUsageI3D (float *pUsage); BOOL WINAPI wglBeginFrameTrackingI3D (void); BOOL WINAPI wglEndFrameTrackingI3D (void); BOOL WINAPI wglQueryFrameTrackingI3D (DWORD *pFrameCount, DWORD *pMissedFrames, float *pLastMissedUsage); #endif #endif /* WGL_I3D_swap_frame_usage */ #ifndef WGL_NV_DX_interop #define WGL_NV_DX_interop 1 #define WGL_ACCESS_READ_ONLY_NV 0x00000000 #define WGL_ACCESS_READ_WRITE_NV 0x00000001 #define WGL_ACCESS_WRITE_DISCARD_NV 0x00000002 typedef BOOL (WINAPI * PFNWGLDXSETRESOURCESHAREHANDLENVPROC) (void *dxObject, HANDLE shareHandle); typedef HANDLE (WINAPI * PFNWGLDXOPENDEVICENVPROC) (void *dxDevice); typedef BOOL (WINAPI * PFNWGLDXCLOSEDEVICENVPROC) (HANDLE hDevice); typedef HANDLE (WINAPI * PFNWGLDXREGISTEROBJECTNVPROC) (HANDLE hDevice, void *dxObject, GLuint name, GLenum type, GLenum access); typedef BOOL (WINAPI * PFNWGLDXUNREGISTEROBJECTNVPROC) (HANDLE hDevice, HANDLE hObject); typedef BOOL (WINAPI * PFNWGLDXOBJECTACCESSNVPROC) (HANDLE hObject, GLenum access); typedef BOOL (WINAPI * PFNWGLDXLOCKOBJECTSNVPROC) (HANDLE hDevice, GLint count, HANDLE *hObjects); typedef BOOL (WINAPI * PFNWGLDXUNLOCKOBJECTSNVPROC) (HANDLE hDevice, GLint count, HANDLE *hObjects); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglDXSetResourceShareHandleNV (void *dxObject, HANDLE shareHandle); HANDLE WINAPI wglDXOpenDeviceNV (void *dxDevice); BOOL WINAPI wglDXCloseDeviceNV (HANDLE hDevice); HANDLE WINAPI wglDXRegisterObjectNV (HANDLE hDevice, void *dxObject, GLuint name, GLenum type, GLenum access); BOOL WINAPI wglDXUnregisterObjectNV (HANDLE hDevice, HANDLE hObject); BOOL WINAPI wglDXObjectAccessNV (HANDLE hObject, GLenum access); BOOL WINAPI wglDXLockObjectsNV (HANDLE hDevice, GLint count, HANDLE *hObjects); BOOL WINAPI wglDXUnlockObjectsNV (HANDLE hDevice, GLint count, HANDLE *hObjects); #endif #endif /* WGL_NV_DX_interop */ #ifndef WGL_NV_DX_interop2 #define WGL_NV_DX_interop2 1 #endif /* WGL_NV_DX_interop2 */ #ifndef WGL_NV_copy_image #define WGL_NV_copy_image 1 typedef BOOL (WINAPI * PFNWGLCOPYIMAGESUBDATANVPROC) (HGLRC hSrcRC, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, HGLRC hDstRC, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglCopyImageSubDataNV (HGLRC hSrcRC, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, HGLRC hDstRC, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); #endif #endif /* WGL_NV_copy_image */ #ifndef WGL_NV_delay_before_swap #define WGL_NV_delay_before_swap 1 typedef BOOL (WINAPI * PFNWGLDELAYBEFORESWAPNVPROC) (HDC hDC, GLfloat seconds); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglDelayBeforeSwapNV (HDC hDC, GLfloat seconds); #endif #endif /* WGL_NV_delay_before_swap */ #ifndef WGL_NV_float_buffer #define WGL_NV_float_buffer 1 #define WGL_FLOAT_COMPONENTS_NV 0x20B0 #define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV 0x20B1 #define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV 0x20B2 #define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV 0x20B3 #define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV 0x20B4 #define WGL_TEXTURE_FLOAT_R_NV 0x20B5 #define WGL_TEXTURE_FLOAT_RG_NV 0x20B6 #define WGL_TEXTURE_FLOAT_RGB_NV 0x20B7 #define WGL_TEXTURE_FLOAT_RGBA_NV 0x20B8 #endif /* WGL_NV_float_buffer */ #ifndef WGL_NV_gpu_affinity #define WGL_NV_gpu_affinity 1 DECLARE_HANDLE(HGPUNV); struct _GPU_DEVICE { DWORD cb; CHAR DeviceName[32]; CHAR DeviceString[128]; DWORD Flags; RECT rcVirtualScreen; }; typedef struct _GPU_DEVICE *PGPU_DEVICE; #define ERROR_INCOMPATIBLE_AFFINITY_MASKS_NV 0x20D0 #define ERROR_MISSING_AFFINITY_MASK_NV 0x20D1 typedef BOOL (WINAPI * PFNWGLENUMGPUSNVPROC) (UINT iGpuIndex, HGPUNV *phGpu); typedef BOOL (WINAPI * PFNWGLENUMGPUDEVICESNVPROC) (HGPUNV hGpu, UINT iDeviceIndex, PGPU_DEVICE lpGpuDevice); typedef HDC (WINAPI * PFNWGLCREATEAFFINITYDCNVPROC) (const HGPUNV *phGpuList); typedef BOOL (WINAPI * PFNWGLENUMGPUSFROMAFFINITYDCNVPROC) (HDC hAffinityDC, UINT iGpuIndex, HGPUNV *hGpu); typedef BOOL (WINAPI * PFNWGLDELETEDCNVPROC) (HDC hdc); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglEnumGpusNV (UINT iGpuIndex, HGPUNV *phGpu); BOOL WINAPI wglEnumGpuDevicesNV (HGPUNV hGpu, UINT iDeviceIndex, PGPU_DEVICE lpGpuDevice); HDC WINAPI wglCreateAffinityDCNV (const HGPUNV *phGpuList); BOOL WINAPI wglEnumGpusFromAffinityDCNV (HDC hAffinityDC, UINT iGpuIndex, HGPUNV *hGpu); BOOL WINAPI wglDeleteDCNV (HDC hdc); #endif #endif /* WGL_NV_gpu_affinity */ #ifndef WGL_NV_multigpu_context #define WGL_NV_multigpu_context 1 #define WGL_CONTEXT_MULTIGPU_ATTRIB_NV 0x20AA #define WGL_CONTEXT_MULTIGPU_ATTRIB_SINGLE_NV 0x20AB #define WGL_CONTEXT_MULTIGPU_ATTRIB_AFR_NV 0x20AC #define WGL_CONTEXT_MULTIGPU_ATTRIB_MULTICAST_NV 0x20AD #define WGL_CONTEXT_MULTIGPU_ATTRIB_MULTI_DISPLAY_MULTICAST_NV 0x20AE #endif /* WGL_NV_multigpu_context */ #ifndef WGL_NV_multisample_coverage #define WGL_NV_multisample_coverage 1 #define WGL_COVERAGE_SAMPLES_NV 0x2042 #define WGL_COLOR_SAMPLES_NV 0x20B9 #endif /* WGL_NV_multisample_coverage */ #ifndef WGL_NV_present_video #define WGL_NV_present_video 1 DECLARE_HANDLE(HVIDEOOUTPUTDEVICENV); #define WGL_NUM_VIDEO_SLOTS_NV 0x20F0 typedef int (WINAPI * PFNWGLENUMERATEVIDEODEVICESNVPROC) (HDC hDc, HVIDEOOUTPUTDEVICENV *phDeviceList); typedef BOOL (WINAPI * PFNWGLBINDVIDEODEVICENVPROC) (HDC hDc, unsigned int uVideoSlot, HVIDEOOUTPUTDEVICENV hVideoDevice, const int *piAttribList); typedef BOOL (WINAPI * PFNWGLQUERYCURRENTCONTEXTNVPROC) (int iAttribute, int *piValue); #ifdef WGL_WGLEXT_PROTOTYPES int WINAPI wglEnumerateVideoDevicesNV (HDC hDc, HVIDEOOUTPUTDEVICENV *phDeviceList); BOOL WINAPI wglBindVideoDeviceNV (HDC hDc, unsigned int uVideoSlot, HVIDEOOUTPUTDEVICENV hVideoDevice, const int *piAttribList); BOOL WINAPI wglQueryCurrentContextNV (int iAttribute, int *piValue); #endif #endif /* WGL_NV_present_video */ #ifndef WGL_NV_render_depth_texture #define WGL_NV_render_depth_texture 1 #define WGL_BIND_TO_TEXTURE_DEPTH_NV 0x20A3 #define WGL_BIND_TO_TEXTURE_RECTANGLE_DEPTH_NV 0x20A4 #define WGL_DEPTH_TEXTURE_FORMAT_NV 0x20A5 #define WGL_TEXTURE_DEPTH_COMPONENT_NV 0x20A6 #define WGL_DEPTH_COMPONENT_NV 0x20A7 #endif /* WGL_NV_render_depth_texture */ #ifndef WGL_NV_render_texture_rectangle #define WGL_NV_render_texture_rectangle 1 #define WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV 0x20A0 #define WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV 0x20A1 #define WGL_TEXTURE_RECTANGLE_NV 0x20A2 #endif /* WGL_NV_render_texture_rectangle */ #ifndef WGL_NV_swap_group #define WGL_NV_swap_group 1 typedef BOOL (WINAPI * PFNWGLJOINSWAPGROUPNVPROC) (HDC hDC, GLuint group); typedef BOOL (WINAPI * PFNWGLBINDSWAPBARRIERNVPROC) (GLuint group, GLuint barrier); typedef BOOL (WINAPI * PFNWGLQUERYSWAPGROUPNVPROC) (HDC hDC, GLuint *group, GLuint *barrier); typedef BOOL (WINAPI * PFNWGLQUERYMAXSWAPGROUPSNVPROC) (HDC hDC, GLuint *maxGroups, GLuint *maxBarriers); typedef BOOL (WINAPI * PFNWGLQUERYFRAMECOUNTNVPROC) (HDC hDC, GLuint *count); typedef BOOL (WINAPI * PFNWGLRESETFRAMECOUNTNVPROC) (HDC hDC); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglJoinSwapGroupNV (HDC hDC, GLuint group); BOOL WINAPI wglBindSwapBarrierNV (GLuint group, GLuint barrier); BOOL WINAPI wglQuerySwapGroupNV (HDC hDC, GLuint *group, GLuint *barrier); BOOL WINAPI wglQueryMaxSwapGroupsNV (HDC hDC, GLuint *maxGroups, GLuint *maxBarriers); BOOL WINAPI wglQueryFrameCountNV (HDC hDC, GLuint *count); BOOL WINAPI wglResetFrameCountNV (HDC hDC); #endif #endif /* WGL_NV_swap_group */ #ifndef WGL_NV_vertex_array_range #define WGL_NV_vertex_array_range 1 typedef void *(WINAPI * PFNWGLALLOCATEMEMORYNVPROC) (GLsizei size, GLfloat readfreq, GLfloat writefreq, GLfloat priority); typedef void (WINAPI * PFNWGLFREEMEMORYNVPROC) (void *pointer); #ifdef WGL_WGLEXT_PROTOTYPES void *WINAPI wglAllocateMemoryNV (GLsizei size, GLfloat readfreq, GLfloat writefreq, GLfloat priority); void WINAPI wglFreeMemoryNV (void *pointer); #endif #endif /* WGL_NV_vertex_array_range */ #ifndef WGL_NV_video_capture #define WGL_NV_video_capture 1 DECLARE_HANDLE(HVIDEOINPUTDEVICENV); #define WGL_UNIQUE_ID_NV 0x20CE #define WGL_NUM_VIDEO_CAPTURE_SLOTS_NV 0x20CF typedef BOOL (WINAPI * PFNWGLBINDVIDEOCAPTUREDEVICENVPROC) (UINT uVideoSlot, HVIDEOINPUTDEVICENV hDevice); typedef UINT (WINAPI * PFNWGLENUMERATEVIDEOCAPTUREDEVICESNVPROC) (HDC hDc, HVIDEOINPUTDEVICENV *phDeviceList); typedef BOOL (WINAPI * PFNWGLLOCKVIDEOCAPTUREDEVICENVPROC) (HDC hDc, HVIDEOINPUTDEVICENV hDevice); typedef BOOL (WINAPI * PFNWGLQUERYVIDEOCAPTUREDEVICENVPROC) (HDC hDc, HVIDEOINPUTDEVICENV hDevice, int iAttribute, int *piValue); typedef BOOL (WINAPI * PFNWGLRELEASEVIDEOCAPTUREDEVICENVPROC) (HDC hDc, HVIDEOINPUTDEVICENV hDevice); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglBindVideoCaptureDeviceNV (UINT uVideoSlot, HVIDEOINPUTDEVICENV hDevice); UINT WINAPI wglEnumerateVideoCaptureDevicesNV (HDC hDc, HVIDEOINPUTDEVICENV *phDeviceList); BOOL WINAPI wglLockVideoCaptureDeviceNV (HDC hDc, HVIDEOINPUTDEVICENV hDevice); BOOL WINAPI wglQueryVideoCaptureDeviceNV (HDC hDc, HVIDEOINPUTDEVICENV hDevice, int iAttribute, int *piValue); BOOL WINAPI wglReleaseVideoCaptureDeviceNV (HDC hDc, HVIDEOINPUTDEVICENV hDevice); #endif #endif /* WGL_NV_video_capture */ #ifndef WGL_NV_video_output #define WGL_NV_video_output 1 DECLARE_HANDLE(HPVIDEODEV); #define WGL_BIND_TO_VIDEO_RGB_NV 0x20C0 #define WGL_BIND_TO_VIDEO_RGBA_NV 0x20C1 #define WGL_BIND_TO_VIDEO_RGB_AND_DEPTH_NV 0x20C2 #define WGL_VIDEO_OUT_COLOR_NV 0x20C3 #define WGL_VIDEO_OUT_ALPHA_NV 0x20C4 #define WGL_VIDEO_OUT_DEPTH_NV 0x20C5 #define WGL_VIDEO_OUT_COLOR_AND_ALPHA_NV 0x20C6 #define WGL_VIDEO_OUT_COLOR_AND_DEPTH_NV 0x20C7 #define WGL_VIDEO_OUT_FRAME 0x20C8 #define WGL_VIDEO_OUT_FIELD_1 0x20C9 #define WGL_VIDEO_OUT_FIELD_2 0x20CA #define WGL_VIDEO_OUT_STACKED_FIELDS_1_2 0x20CB #define WGL_VIDEO_OUT_STACKED_FIELDS_2_1 0x20CC typedef BOOL (WINAPI * PFNWGLGETVIDEODEVICENVPROC) (HDC hDC, int numDevices, HPVIDEODEV *hVideoDevice); typedef BOOL (WINAPI * PFNWGLRELEASEVIDEODEVICENVPROC) (HPVIDEODEV hVideoDevice); typedef BOOL (WINAPI * PFNWGLBINDVIDEOIMAGENVPROC) (HPVIDEODEV hVideoDevice, HPBUFFERARB hPbuffer, int iVideoBuffer); typedef BOOL (WINAPI * PFNWGLRELEASEVIDEOIMAGENVPROC) (HPBUFFERARB hPbuffer, int iVideoBuffer); typedef BOOL (WINAPI * PFNWGLSENDPBUFFERTOVIDEONVPROC) (HPBUFFERARB hPbuffer, int iBufferType, unsigned long *pulCounterPbuffer, BOOL bBlock); typedef BOOL (WINAPI * PFNWGLGETVIDEOINFONVPROC) (HPVIDEODEV hpVideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglGetVideoDeviceNV (HDC hDC, int numDevices, HPVIDEODEV *hVideoDevice); BOOL WINAPI wglReleaseVideoDeviceNV (HPVIDEODEV hVideoDevice); BOOL WINAPI wglBindVideoImageNV (HPVIDEODEV hVideoDevice, HPBUFFERARB hPbuffer, int iVideoBuffer); BOOL WINAPI wglReleaseVideoImageNV (HPBUFFERARB hPbuffer, int iVideoBuffer); BOOL WINAPI wglSendPbufferToVideoNV (HPBUFFERARB hPbuffer, int iBufferType, unsigned long *pulCounterPbuffer, BOOL bBlock); BOOL WINAPI wglGetVideoInfoNV (HPVIDEODEV hpVideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); #endif #endif /* WGL_NV_video_output */ #ifndef WGL_OML_sync_control #define WGL_OML_sync_control 1 typedef BOOL (WINAPI * PFNWGLGETSYNCVALUESOMLPROC) (HDC hdc, INT64 *ust, INT64 *msc, INT64 *sbc); typedef BOOL (WINAPI * PFNWGLGETMSCRATEOMLPROC) (HDC hdc, INT32 *numerator, INT32 *denominator); typedef INT64 (WINAPI * PFNWGLSWAPBUFFERSMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder); typedef INT64 (WINAPI * PFNWGLSWAPLAYERBUFFERSMSCOMLPROC) (HDC hdc, INT fuPlanes, INT64 target_msc, INT64 divisor, INT64 remainder); typedef BOOL (WINAPI * PFNWGLWAITFORMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder, INT64 *ust, INT64 *msc, INT64 *sbc); typedef BOOL (WINAPI * PFNWGLWAITFORSBCOMLPROC) (HDC hdc, INT64 target_sbc, INT64 *ust, INT64 *msc, INT64 *sbc); #ifdef WGL_WGLEXT_PROTOTYPES BOOL WINAPI wglGetSyncValuesOML (HDC hdc, INT64 *ust, INT64 *msc, INT64 *sbc); BOOL WINAPI wglGetMscRateOML (HDC hdc, INT32 *numerator, INT32 *denominator); INT64 WINAPI wglSwapBuffersMscOML (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder); INT64 WINAPI wglSwapLayerBuffersMscOML (HDC hdc, INT fuPlanes, INT64 target_msc, INT64 divisor, INT64 remainder); BOOL WINAPI wglWaitForMscOML (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder, INT64 *ust, INT64 *msc, INT64 *sbc); BOOL WINAPI wglWaitForSbcOML (HDC hdc, INT64 target_sbc, INT64 *ust, INT64 *msc, INT64 *sbc); #endif #endif /* WGL_OML_sync_control */ #ifdef __cplusplus } #endif #endif ================================================ FILE: src/Common/MemPtr.h ================================================ #pragma once #include #include "betype.h" using MPTR = uint32; // generic address in PowerPC memory space #define MPTR_NULL (0) using VAddr = uint32; // virtual address using PAddr = uint32; // physical address extern uint8* memory_base; extern uint8* PPCInterpreterGetStackPointer(); extern uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset); extern void PPCInterpreterModifyStackPointer(sint32 offset); class MEMPTRBase { }; template class MEMPTR : MEMPTRBase { public: constexpr MEMPTR() noexcept : m_value(0) {} explicit constexpr MEMPTR(uint32 offset) noexcept : m_value(offset) {} explicit constexpr MEMPTR(const uint32be& offset) noexcept : m_value(offset) {} constexpr MEMPTR(std::nullptr_t) noexcept : m_value(0) {} MEMPTR(T* ptr) noexcept { if (ptr == nullptr) m_value = 0; else { cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000); m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); } } constexpr MEMPTR(const MEMPTR&) noexcept = default; constexpr MEMPTR& operator=(const MEMPTR&) noexcept = default; constexpr MEMPTR& operator=(const uint32& offset) noexcept { m_value = offset; return *this; } constexpr MEMPTR& operator=(std::nullptr_t) noexcept { m_value = 0; return *this; } MEMPTR& operator=(T* ptr) noexcept { if (ptr == nullptr) m_value = 0; else { cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000); m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); } return *this; } bool atomic_compare_exchange(T* comparePtr, T* newPtr) noexcept { MEMPTR mp_compare = comparePtr; MEMPTR mp_new = newPtr; auto* thisValueAtomic = reinterpret_cast*>(&m_value); return thisValueAtomic->compare_exchange_strong(mp_compare.m_value, mp_new.m_value); } explicit constexpr operator bool() const noexcept { return m_value != 0; } // allow implicit cast to wrapped pointer type constexpr operator T*() const noexcept { return GetPtr(); } template explicit operator MEMPTR() const noexcept { return MEMPTR(this->m_value); } sint32 operator-(const MEMPTR& ptr) noexcept requires(!std::is_void_v) { return static_cast(this->GetMPTR() - ptr.GetMPTR()); } MEMPTR operator+(sint32 v) noexcept requires(!std::is_void_v) { // pointer arithmetic return MEMPTR(this->GetMPTR() + v * sizeof(T)); } MEMPTR operator-(sint32 v) noexcept requires(!std::is_void_v) { // pointer arithmetic return MEMPTR(this->GetMPTR() - v * sizeof(T)); } MEMPTR& operator+=(sint32 v) noexcept requires(!std::is_void_v) { m_value += v * sizeof(T); return *this; } template requires(!std::is_void_v) Q& operator*() const noexcept { return *GetPtr(); } constexpr T* operator->() const noexcept { return GetPtr(); } template requires(!std::is_void_v) Q& operator[](int index) noexcept { return GetPtr()[index]; } T* GetPtr() const noexcept { return (T*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value); } template C* GetPtr() const noexcept { return static_cast(GetPtr()); } [[nodiscard]] constexpr uint32 GetMPTR() const noexcept { return m_value.value(); } [[nodiscard]] constexpr const uint32be& GetBEValue() const noexcept { return m_value; } [[nodiscard]] constexpr bool IsNull() const noexcept { return m_value == 0; } private: uint32be m_value; }; static_assert(sizeof(MEMPTR) == sizeof(uint32be)); static_assert(std::is_trivially_copyable_v>); #include "StackAllocator.h" #include "SysAllocator.h" template struct fmt::formatter> : formatter { template auto format(const MEMPTR& v, FormatContext& ctx) const -> format_context::iterator { return fmt::format_to(ctx.out(), "{:#x}", v.GetMPTR()); } }; ================================================ FILE: src/Common/StackAllocator.h ================================================ #pragma once template class StackAllocator { public: StackAllocator() : StackAllocator(1) {} explicit StackAllocator(const uint32 items) { m_items = items; m_modified_size = count * sizeof(T) * items + kStaticMemOffset * 2; m_modified_size = (m_modified_size/8+7) * 8; // pad to 8 bytes m_ptr = new(PPCInterpreter_PushAndReturnStackPointer(m_modified_size) + kStaticMemOffset) T[count * items](); } ~StackAllocator() { for (size_t i = 0; i < count * m_items; ++i) m_ptr[i].~T(); PPCInterpreterModifyStackPointer(-m_modified_size); } T* operator->() const { return GetPointer(); } T* GetPointer() const { return m_ptr; } uint32 GetMPTR() const { return MEMPTR(m_ptr).GetMPTR(); } T* operator&() { return GetPointer(); } explicit operator T*() const { return GetPointer(); } explicit operator uint32() const { return GetMPTR(); } explicit operator bool() const { return *m_ptr != 0; } // for arrays (count > 1) allow direct access via [] operator template requires (c > 1) T& operator[](const uint32 index) { return m_ptr[index]; } // if count is 1, then allow direct value assignment via = operator template requires (c == 1) T& operator=(const T& rhs) { *m_ptr = rhs; return *m_ptr; } // if count is 1, then allow == and != operators template requires (c == 1) bool operator==(const T& rhs) const { return *m_ptr == rhs; } private: static const uint32 kStaticMemOffset = 64; T* m_ptr; sint32 m_modified_size; uint32 m_items; }; ================================================ FILE: src/Common/SysAllocator.cpp ================================================ #include "SysAllocator.h" void SysAllocatorContainer::Initialize() { for (SysAllocatorBase* sysAlloc : m_sysAllocList) { sysAlloc->Initialize(); } } void SysAllocatorContainer::PushSysAllocator(SysAllocatorBase* base) { m_sysAllocList.push_back(base); } SysAllocatorContainer& SysAllocatorContainer::GetInstance() { static SysAllocatorContainer s_instance; return s_instance; } SysAllocatorBase::SysAllocatorBase() { SysAllocatorContainer::GetInstance().PushSysAllocator(this); } ================================================ FILE: src/Common/SysAllocator.h ================================================ #pragma once #include #include #include uint32 coreinit_allocFromSysArea(uint32 size, uint32 alignment); class SysAllocatorBase; #define SYSALLOCATOR_GUARDS 0 // if 1, create a magic constant at the top of each memory allocation which is used to check for memory corruption class SysAllocatorContainer { public: void Initialize(); void PushSysAllocator(SysAllocatorBase* base); static SysAllocatorContainer& GetInstance(); private: std::vector m_sysAllocList; }; class SysAllocatorBase { friend class SysAllocatorContainer; public: SysAllocatorBase(); virtual ~SysAllocatorBase() = default; private: virtual void Initialize() = 0; }; template class SysAllocator : public SysAllocatorBase { public: SysAllocator() { m_tempData.resize(count); } SysAllocator(std::initializer_list l) { m_tempData.reserve(count); m_tempData.insert(m_tempData.begin(), l); if (count > l.size()) m_tempData.insert(m_tempData.end(), count - l.size(), T()); } template SysAllocator(const char(&str)[N]) { m_tempData.reserve(count); m_tempData.insert(m_tempData.begin(), str, str + N); } constexpr uint32 GetCount() const { return count; } constexpr size_t GetByteSize() const { return count * sizeof(T); } T* GetPtr() const { #if SYSALLOCATOR_GUARDS cemu_assert(*(uint32*)((uint8*)m_sysMem.GetPtr()+(sizeof(T) * count)) == 0x112A33C4); #endif return m_sysMem.GetPtr(); } uint32 GetMPTR() const { #if SYSALLOCATOR_GUARDS cemu_assert(*(uint32*)((uint8*)m_sysMem.GetPtr()+(sizeof(T) * count)) == 0x112A33C4); #endif return m_sysMem.GetMPTR(); } T& operator*() { return *GetPtr(); } operator T*() { return GetPtr(); } SysAllocator operator+(const SysAllocator& ptr) { return SysAllocator(this->GetMPTR() + ptr.GetMPTR()); } SysAllocator operator-(const SysAllocator& ptr) { return SysAllocator(this->GetMPTR() - ptr.GetMPTR()); } T* operator+(sint32 v) { return this->GetPtr() + v; } T* operator-(sint32 v) { return this->GetPtr() - v; } operator void*() { return m_sysMem->GetPtr(); } // for all arrays except bool T& operator[](int index) requires(count != 1) && (!std::is_same_v) { // return tmp data until we allocated in sys mem if (m_sysMem.GetMPTR() == 0) { return m_tempData[index]; } return m_sysMem[index]; } private: SysAllocator(uint32 memptr) : m_sysMem(memptr) {} void Initialize() override { if (m_sysMem.GetMPTR() != 0) return; // alloc mem uint32 guardSize = 0; #if SYSALLOCATOR_GUARDS guardSize = 4; #endif m_sysMem = { coreinit_allocFromSysArea(sizeof(T) * count + guardSize, alignment) }; // copy temp buffer to mem and clear it memcpy(m_sysMem.GetPtr(), m_tempData.data(), sizeof(T)*count); #if SYSALLOCATOR_GUARDS *(uint32*)((uint8*)m_sysMem.GetPtr()+(sizeof(T) * count)) = 0x112A33C4; #endif m_tempData.clear(); } MEMPTR m_sysMem; std::vector m_tempData; }; template SysAllocator(const char(&str)[N]) -> SysAllocator; template class SysAllocator : public SysAllocatorBase { public: SysAllocator() { m_tempData = {}; } SysAllocator(const T& value) { m_tempData = value; } SysAllocator& operator=(const T& value) { *m_sysMem = value; return *this; } operator T() { return *GetPtr(); } operator T*() { return GetPtr(); } T* GetPtr() const { return m_sysMem.GetPtr(); } uint32 GetMPTR() const { return m_sysMem.GetMPTR(); } T* operator&() { return (T*)m_sysMem.GetPtr(); } T* operator->() const { return this->GetPtr(); } private: void Initialize() override { if (m_sysMem.GetMPTR() != 0) return; // alloc mem m_sysMem = { coreinit_allocFromSysArea(sizeof(T), 32) }; // copy temp buffer to mem and clear it *m_sysMem = m_tempData; } MEMPTR m_sysMem; T m_tempData; }; ================================================ FILE: src/Common/betype.h ================================================ #pragma once #include namespace Latte { class LATTEREG; }; template constexpr T bswap_impl(T i, std::index_sequence) { return ((((i >> (N * CHAR_BIT)) & (T)(uint8_t)(-1)) << ((sizeof(T) - 1 - N) * CHAR_BIT)) | ...); } template> constexpr T bswap(T i) { return (T)bswap_impl((U)i, std::make_index_sequence{}); } template constexpr T SwapEndian(T value) { if constexpr (std::is_integral_v) { #ifdef _MSC_VER if constexpr (sizeof(T) == sizeof(uint32_t)) { return (T)_byteswap_ulong(value); } #endif return (T)bswap((std::make_unsigned_t)value); } else if constexpr (std::is_floating_point_v) { if constexpr (sizeof(T) == sizeof(uint32_t)) { const auto tmp = bswap(*(uint32_t*)&value); return *(T*)&tmp; } if constexpr (sizeof(T) == sizeof(uint64_t)) { const auto tmp = bswap(*(uint64_t*)&value); return *(T*)&tmp; } } else if constexpr (std::is_enum_v) { return (T)SwapEndian((std::underlying_type_t)value); } else if constexpr (std::is_base_of_v) { const auto tmp = bswap(*(uint32_t*)&value); return *(T*)&tmp; } else { static_assert(std::is_integral_v, "unsupported betype specialization!"); } return value; } #ifndef WIN32 //static_assert(false, "_BE and _LE need to be adjusted"); #endif // swap if native isn't big endian template constexpr T _BE(T value) { return SwapEndian(value); } // swap if native isn't little endian template constexpr T _LE(T value) { return value; } template class betype { public: constexpr betype() = default; // copy constexpr betype(T value) : m_value(SwapEndian(value)) {} constexpr betype(const betype& value) = default; // required for trivially_copyable //constexpr betype(const betype& value) // : m_value(value.m_value) {} template constexpr betype(const betype& value) : betype((T)value.value()) {} // assigns static betype from_bevalue(T value) { betype result; result.m_value = value; return result; } // returns LE value constexpr T value() const { return SwapEndian(m_value); } // returns BE value constexpr T bevalue() const { return m_value; } constexpr operator T() const { return value(); } betype& operator+=(const betype& v) { m_value = SwapEndian(T(value() + v.value())); return *this; } betype& operator+=(const T& v) requires std::integral { m_value = SwapEndian(T(value() + v)); return *this; } betype& operator-=(const betype& v) { m_value = SwapEndian(T(value() - v.value())); return *this; } betype& operator*=(const betype& v) { m_value = SwapEndian(T(value() * v.value())); return *this; } betype& operator/=(const betype& v) { m_value = SwapEndian(T(value() / v.value())); return *this; } betype& operator&=(const betype& v) requires (requires (T& x, const T& y) { x &= y; }) { m_value &= v.m_value; return *this; } betype& operator|=(const betype& v) requires (requires (T& x, const T& y) { x |= y; }) { m_value |= v.m_value; return *this; } betype& operator|(const betype& v) const requires (requires (T& x, const T& y) { x | y; }) { betype tmp(*this); tmp.m_value = tmp.m_value | v.m_value; return tmp; } betype operator|(const T& v) const requires (requires (T& x, const T& y) { x | y; }) { betype tmp(*this); tmp.m_value = tmp.m_value | SwapEndian(v); return tmp; } betype& operator^=(const betype& v) requires std::integral { m_value ^= v.m_value; return *this; } betype& operator>>=(std::size_t idx) requires std::integral { m_value = SwapEndian(T(value() >> idx)); return *this; } betype& operator<<=(std::size_t idx) requires std::integral { m_value = SwapEndian(T(value() << idx)); return *this; } betype operator~() const requires std::integral { return from_bevalue(T(~m_value)); } // pre-increment betype& operator++() requires std::integral { m_value = SwapEndian(T(value() + 1)); return *this; } // post-increment betype operator++(int) requires std::integral { betype tmp(*this); m_value = SwapEndian(T(value() + 1)); return tmp; } // pre-decrement betype& operator--() requires std::integral { m_value = SwapEndian(T(value() - 1)); return *this; } // post-decrement betype operator--(int) requires std::integral { betype tmp(*this); m_value = SwapEndian(T(value() - 1)); return tmp; } private: //T m_value{}; // before 1.26.2 T m_value; }; using uint64be = betype; using uint32be = betype; using uint16be = betype; using uint8be = betype; using sint64be = betype; using sint32be = betype; using sint16be = betype; using sint8be = betype; using float32be = betype; using float64be = betype; static_assert(sizeof(betype) == sizeof(uint64)); static_assert(sizeof(betype) == sizeof(uint32)); static_assert(sizeof(betype) == sizeof(uint16)); static_assert(sizeof(betype) == sizeof(uint8)); static_assert(sizeof(betype) == sizeof(float)); static_assert(sizeof(betype) == sizeof(double)); static_assert(std::is_trivially_copyable_v); static_assert(std::is_trivially_constructible_v); static_assert(std::is_copy_constructible_v); static_assert(std::is_move_constructible_v); static_assert(std::is_copy_assignable_v); static_assert(std::is_move_assignable_v); ================================================ FILE: src/Common/cpu_features.cpp ================================================ #include "cpu_features.h" // wrappers with uniform prototype for implementation-specific x86 CPU id #if defined(ARCH_X86_64) #ifdef __GNUC__ #include #endif inline void cpuid(int cpuInfo[4], int functionId) { #if defined(_MSC_VER) __cpuid(cpuInfo, functionId); #elif defined(__GNUC__) __cpuid(functionId, cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]); #else #error No definition for cpuid #endif } inline void cpuidex(int cpuInfo[4], int functionId, int subFunctionId) { #if defined(_MSC_VER) __cpuidex(cpuInfo, functionId, subFunctionId); #elif defined(__GNUC__) __cpuid_count(functionId, subFunctionId, cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]); #else #error No definition for cpuidex #endif } #endif CPUFeaturesImpl::CPUFeaturesImpl() { #if defined(ARCH_X86_64) int cpuInfo[4]; cpuid(cpuInfo, 0x80000001); x86.lzcnt = ((cpuInfo[2] >> 5) & 1) != 0; cpuid(cpuInfo, 0x1); x86.movbe = ((cpuInfo[2] >> 22) & 1) != 0; x86.avx = ((cpuInfo[2] >> 28) & 1) != 0; x86.aesni = ((cpuInfo[2] >> 25) & 1) != 0; x86.ssse3 = ((cpuInfo[2] >> 9) & 1) != 0; x86.sse4_1 = ((cpuInfo[2] >> 19) & 1) != 0; cpuidex(cpuInfo, 0x7, 0); x86.avx2 = ((cpuInfo[1] >> 5) & 1) != 0; x86.bmi2 = ((cpuInfo[1] >> 8) & 1) != 0; cpuid(cpuInfo, 0x80000007); x86.invariant_tsc = ((cpuInfo[3] >> 8) & 1); // get CPU brand name uint32_t nExIds, i = 0; memset(m_cpuBrandName, 0, sizeof(m_cpuBrandName)); cpuid(cpuInfo, 0x80000000); nExIds = (uint32_t)cpuInfo[0]; for (uint32_t i = 0x80000000; i <= nExIds; ++i) { cpuid(cpuInfo, i); if (i == 0x80000002) memcpy(m_cpuBrandName, cpuInfo, sizeof(cpuInfo)); else if (i == 0x80000003) memcpy(m_cpuBrandName + 16, cpuInfo, sizeof(cpuInfo)); else if (i == 0x80000004) memcpy(m_cpuBrandName + 32, cpuInfo, sizeof(cpuInfo)); } #endif } std::string CPUFeaturesImpl::GetCPUName() { return { m_cpuBrandName }; } std::string CPUFeaturesImpl::GetCommaSeparatedExtensionList() { std::string tmp; auto appendExt = [&tmp](const char* str) { if (!tmp.empty()) tmp.append(", "); tmp.append(str); }; if (x86.ssse3) appendExt("SSSE3"); if (x86.sse4_1) appendExt("SSE4.1"); if (x86.avx) appendExt("AVX"); if (x86.avx2) appendExt("AVX2"); if (x86.lzcnt) appendExt("LZCNT"); if (x86.movbe) appendExt("MOVBE"); if (x86.bmi2) appendExt("BMI2"); if (x86.aesni) appendExt("AES-NI"); if(x86.invariant_tsc) appendExt("INVARIANT-TSC"); return tmp; } CPUFeaturesImpl g_CPUFeatures; ================================================ FILE: src/Common/cpu_features.h ================================================ #ifdef __GNUC__ #define ATTRIBUTE_AVX2 __attribute__((target("avx2"))) #define ATTRIBUTE_SSE41 __attribute__((target("sse4.1"))) #define ATTRIBUTE_AESNI __attribute__((target("aes"))) #else #define ATTRIBUTE_AVX2 #define ATTRIBUTE_SSE41 #define ATTRIBUTE_AESNI #endif #include class CPUFeaturesImpl { public: CPUFeaturesImpl(); std::string GetCPUName(); // empty if not available std::string GetCommaSeparatedExtensionList(); struct { bool ssse3{ false }; bool sse4_1{ false }; bool avx{ false }; bool avx2{ false }; bool lzcnt{ false }; bool movbe{ false }; bool bmi2{ false }; bool aesni{ false }; bool invariant_tsc{ false }; }x86; private: char m_cpuBrandName[0x40]{ 0 }; }; extern CPUFeaturesImpl g_CPUFeatures; ================================================ FILE: src/Common/enumFlags.h ================================================ #pragma once #include // enum flag helpers template struct EnableBitMaskOperators { static const bool enable = false; }; template requires EnableBitMaskOperators::enable TEnum operator &(TEnum lhs, TEnum rhs) { return static_cast (static_cast::type>(lhs) & static_cast::type>(rhs)); } template requires EnableBitMaskOperators::enable TEnum operator |(TEnum lhs, TEnum rhs) { return static_cast (static_cast::type>(lhs) | static_cast::type>(rhs)); } template requires EnableBitMaskOperators::enable TEnum operator ^(TEnum lhs, TEnum rhs) { return static_cast (static_cast::type>(lhs) ^ static_cast::type>(rhs)); } template requires EnableBitMaskOperators::enable TEnum operator ~(TEnum rhs) { return static_cast (~static_cast::type>(rhs)); } template requires EnableBitMaskOperators::enable TEnum& operator &=(TEnum& lhs, TEnum rhs) { lhs = static_cast (static_cast::type>(lhs) & static_cast::type>(rhs)); return lhs; } template requires EnableBitMaskOperators::enable TEnum& operator |=(TEnum& lhs, TEnum rhs) { lhs = static_cast (static_cast::type>(lhs) | static_cast::type>(rhs)); return lhs; } template requires EnableBitMaskOperators::enable TEnum& operator ^=(TEnum& lhs, TEnum rhs) { lhs = static_cast (static_cast::type>(lhs) ^ static_cast::type>(rhs)); return lhs; } template requires EnableBitMaskOperators::enable constexpr bool operator==(TEnum lhs, std::underlying_type_t rhs) { return static_cast>(lhs) == rhs; } template requires EnableBitMaskOperators::enable constexpr bool operator!=(TEnum lhs, std::underlying_type_t rhs) { return static_cast>(lhs) != rhs; } #define ENABLE_BITMASK_OPERATORS(x) template<> struct EnableBitMaskOperators { static const bool enable = true; }; template struct EnableEnumIterators { static const bool enable = false; }; template requires EnableEnumIterators::enable TEnum& operator++(TEnum& lhs) { lhs = static_cast(static_cast::type>(lhs) + 1); return lhs; } template requires EnableEnumIterators::enable TEnum operator*(TEnum rhs) { return rhs; } template requires EnableEnumIterators::enable TEnum begin(TEnum value) { return EnableEnumIterators::begin; } template requires EnableEnumIterators::enable TEnum rbegin(TEnum value) { return EnableEnumIterators::rbegin; } template requires EnableEnumIterators::enable TEnum end(TEnum r) { return EnableEnumIterators::end; } template requires EnableEnumIterators::enable TEnum rend(TEnum r) { return EnableEnumIterators::rend; } #define ENABLE_ENUM_ITERATORS(x, first_value, last_value) template<> struct EnableEnumIterators {\ static const bool enable = true;\ static const x begin = first_value;\ static const x rbegin = last_value;\ static const x end = static_cast(static_cast::type>(last_value) + 1);\ static const x rend = static_cast(static_cast::type>(first_value) - 1);\ }; // todo: rend type must be signed? ================================================ FILE: src/Common/platform.h ================================================ #pragma once #include #include #if BOOST_OS_WINDOWS #include "Common/windows/platform.h" #elif BOOST_OS_LINUX || BOOST_OS_BSD #if BOOST_OS_LINUX #include #elif BOOST_OS_BSD #include #endif #include #include #include #include "Common/unix/platform.h" #elif BOOST_OS_MACOS #include #include "Common/unix/platform.h" #endif ================================================ FILE: src/Common/precompiled.cpp ================================================ #include "precompiled.h" ================================================ FILE: src/Common/precompiled.h ================================================ #pragma once #include // for size_t #include "version.h" #include "platform.h" #define FMT_HEADER_ONLY #define FMT_USE_GRISU 1 #include #include #include #if FMT_VERSION > 80000 #include // needed for wchar_t support #endif #define SDL_MAIN_HANDLED // #if FMT_VERSION > 80102 // // restore generic formatter for enum (class) to underlying. We currently utilize this for HLE call logging // template ::value, int> = 0> // constexpr auto format_as(Enum e) noexcept -> std::underlying_type { // return static_cast>(e); // } // #endif // arch defines #if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64) #define ARCH_X86_64 #endif // c includes #include #include #include #include #include #if defined(ARCH_X86_64) #include #endif // c++ includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; #include "enumFlags.h" // base types using uint64 = uint64_t; using uint32 = uint32_t; using uint16 = uint16_t; using uint8 = uint8_t; using sint64 = int64_t; using sint32 = int32_t; using sint16 = int16_t; using sint8 = int8_t; #if _MSC_VER #ifndef _SSIZE_T_DEFINED #define _SSIZE_T_DEFINED #include typedef SSIZE_T ssize_t; #endif #endif // types with explicit big endian order #include "betype.h" // types with explicit little endian order (on a big-endian system these would be implemented like betype.h) using uint64le = uint64_t; using uint32le = uint32_t; using uint16le = uint16_t; using uint8le = uint8_t; // logging #include "Cemu/Logging/CemuDebugLogging.h" #include "Cemu/Logging/CemuLogging.h" // localization namespace { std::function g_translate; } inline void SetTranslationCallback(std::function translate) { g_translate = translate; } #define TR_NOOP(str) str inline std::string _tr(std::string_view text) { if (g_translate) return g_translate(text); return std::string{text}; } template inline std::string _tr(fmt::format_string text, TArgs... args) { if (g_translate) { std::string_view textSV{text.get().data(), text.get().size()}; return fmt::format(fmt::runtime(g_translate(textSV)), std::forward(args)...); } return fmt::format(text, std::forward(args)...); } // manual endian-swapping #if _MSC_VER inline uint64 _swapEndianU64(uint64 v) { return _byteswap_uint64(v); } inline uint32 _swapEndianU32(uint32 v) { return _byteswap_ulong(v); } inline sint32 _swapEndianS32(sint32 v) { return (sint32)_byteswap_ulong((uint32)v); } inline uint16 _swapEndianU16(uint16 v) { return (v >> 8) | (v << 8); } inline sint16 _swapEndianS16(sint16 v) { return (sint16)(((uint16)v >> 8) | ((uint16)v << 8)); } #else inline uint64 _swapEndianU64(uint64 v) { #if BOOST_OS_MACOS return OSSwapInt64(v); #elif BOOST_OS_BSD #ifdef __OpenBSD__ return swap64(v); #else // FreeBSD and NetBSD return bswap64(v); #endif #else return bswap_64(v); #endif } inline uint32 _swapEndianU32(uint32 v) { #if BOOST_OS_MACOS return OSSwapInt32(v); #elif BOOST_OS_BSD #ifdef __OpenBSD__ return swap32(v); #else // FreeBSD and NetBSD return bswap32(v); #endif #else return bswap_32(v); #endif } inline sint32 _swapEndianS32(sint32 v) { #if BOOST_OS_MACOS return (sint32)OSSwapInt32((uint32)v); #elif BOOST_OS_BSD #ifdef __OpenBSD__ return (sint32)swap32((uint32)v); #else // FreeBSD and NetBSD return (sint32)bswap32((uint32)v); #endif #else return (sint32)bswap_32((uint32)v); #endif } inline uint16 _swapEndianU16(uint16 v) { return (v >> 8) | (v << 8); } inline sint16 _swapEndianS16(sint16 v) { return (sint16)(((uint16)v >> 8) | ((uint16)v << 8)); } inline uint64 _umul128(uint64 multiplier, uint64 multiplicand, uint64 *highProduct) { unsigned __int128 x = (unsigned __int128)multiplier * (unsigned __int128)multiplicand; *highProduct = (x >> 64); return x & 0xFFFFFFFFFFFFFFFF; } typedef uint8_t BYTE; typedef uint32_t DWORD; typedef int32_t LONG; typedef int64_t LONGLONG; typedef union _LARGE_INTEGER { struct { DWORD LowPart; LONG HighPart; }; struct { DWORD LowPart; LONG HighPart; } u; LONGLONG QuadPart; } LARGE_INTEGER, *PLARGE_INTEGER; #define DEFINE_ENUM_FLAG_OPERATORS(T) \ inline T operator~ (T a) { return static_cast( ~static_cast::type>(a) ); } \ inline T operator| (T a, T b) { return static_cast( static_cast::type>(a) | static_cast::type>(b) ); } \ inline T operator& (T a, T b) { return static_cast( static_cast::type>(a) & static_cast::type>(b) ); } \ inline T operator^ (T a, T b) { return static_cast( static_cast::type>(a) ^ static_cast::type>(b) ); } \ inline T& operator|= (T& a, T b) { return reinterpret_cast( reinterpret_cast::type&>(a) |= static_cast::type>(b) ); } \ inline T& operator&= (T& a, T b) { return reinterpret_cast( reinterpret_cast::type&>(a) &= static_cast::type>(b) ); } \ inline T& operator^= (T& a, T b) { return reinterpret_cast( reinterpret_cast::type&>(a) ^= static_cast::type>(b) ); } #endif template inline T GetBits(T value, uint32 index, uint32 numBits) { T mask = (1<>index) & mask; } template inline void SetBits(T& value, uint32 index, uint32 numBits, uint32 bitValue) { T mask = (1< #define DEBUG_BREAK raise(SIGTRAP) #endif #if defined(_MSC_VER) #define DLLEXPORT __declspec(dllexport) #elif defined(__GNUC__) #if BOOST_OS_WINDOWS #define DLLEXPORT __attribute__((dllexport)) #else #define DLLEXPORT #endif #else #error No definition for DLLEXPORT #endif #if BOOST_OS_WINDOWS #define NOEXPORT #elif defined(__GNUC__) #define NOEXPORT __attribute__ ((visibility ("hidden"))) #endif #if defined(_MSC_VER) #define FORCE_INLINE __forceinline #elif defined(__GNUC__) || defined(__clang__) #define FORCE_INLINE inline __attribute__((always_inline)) #else #define FORCE_INLINE inline #endif FORCE_INLINE int BSF(uint32 v) // returns index of first bit set, counting from LSB. If v is 0 then result is undefined { #if defined(_MSC_VER) return _tzcnt_u32(v); // TZCNT requires BMI1. But if not supported it will execute as BSF #elif defined(__GNUC__) || defined(__clang__) return __builtin_ctz(v); #else return std::countr_zero(v); #endif } // On aarch64 we handle some of the x86 intrinsics by implementing them as wrappers #if defined(__aarch64__) inline void _mm_pause() { asm volatile("yield"); } inline uint64 __rdtsc() { uint64 t; asm volatile("mrs %0, cntvct_el0" : "=r" (t)); return t; } inline void _mm_mfence() { asm volatile("" ::: "memory"); std::atomic_thread_fence(std::memory_order_seq_cst); } inline unsigned char _addcarry_u64(unsigned char carry, unsigned long long a, unsigned long long b, unsigned long long *result) { *result = a + b + (unsigned long long)carry; if (*result < a) return 1; return 0; } #endif // asserts inline void cemu_assert(bool _condition) { if ((_condition) == false) { DEBUG_BREAK; } } #ifndef CEMU_DEBUG_ASSERT //#define cemu_assert_debug(__cond) -> Forcing __cond not to be evaluated currently has unexpected side-effects inline void cemu_assert_debug(bool _condition) { } inline void cemu_assert_unimplemented() { } inline void cemu_assert_suspicious() { } inline void cemu_assert_error() { DEBUG_BREAK; } #else inline void cemu_assert_debug(bool _condition) { if ((_condition) == false) DEBUG_BREAK; } inline void cemu_assert_unimplemented() { DEBUG_BREAK; } inline void cemu_assert_suspicious() { DEBUG_BREAK; } inline void cemu_assert_error() { DEBUG_BREAK; } #endif #define assert_dbg() DEBUG_BREAK // old style unconditional generic assert // MEMPTR #include "Common/MemPtr.h" template constexpr bool HAS_FLAG(T1 flags, T2 test_flag) { return (flags & (T1)test_flag) == (T1)test_flag; } template constexpr bool HAS_BIT(T1 value, T2 index) { return (value & ((T1)1 << index)) != 0; } template constexpr uint32_t ppcsizeof() { return (uint32_t) sizeof(T); } // common utility functions template void vectorAppendUnique(std::vector& vec, const T& val) // for cases where a small vector is more efficient than a set { if (std::find(vec.begin(), vec.end(), val) != vec.end()) return; vec.emplace_back(val); } template void vectorRemoveByValue(std::vector& vec, const T& val) { vec.erase(std::remove(vec.begin(), vec.end(), val), vec.end()); } template void vectorRemoveByIndex(std::vector& vec, const size_t index) { vec.erase(vec.begin() + index); } template bool match_any_of(T1&& value, Types&&... others) { return ((value == others) || ...); } // we cache the frequency in a static variable [[nodiscard]] static std::chrono::high_resolution_clock::time_point now_cached() noexcept { #ifdef _WIN32 // get current time static const long long _Freq = _Query_perf_frequency(); // doesn't change after system boot const long long _Ctr = _Query_perf_counter(); static_assert(std::nano::num == 1, "This assumes period::num == 1."); const long long _Whole = (_Ctr / _Freq) * std::nano::den; const long long _Part = (_Ctr % _Freq) * std::nano::den / _Freq; return (std::chrono::high_resolution_clock::time_point(std::chrono::nanoseconds(_Whole + _Part))); #else return std::chrono::high_resolution_clock::now(); #endif } [[nodiscard]] static std::chrono::steady_clock::time_point tick_cached() noexcept { #if BOOST_OS_WINDOWS // get current time static const long long _Freq = _Query_perf_frequency(); // doesn't change after system boot const long long _Ctr = _Query_perf_counter(); static_assert(std::nano::num == 1, "This assumes period::num == 1."); const long long _Whole = (_Ctr / _Freq) * std::nano::den; const long long _Part = (_Ctr % _Freq) * std::nano::den / _Freq; return (std::chrono::steady_clock::time_point(std::chrono::nanoseconds(_Whole + _Part))); #elif BOOST_OS_LINUX struct timespec tp; clock_gettime(CLOCK_MONOTONIC_RAW, &tp); return std::chrono::steady_clock::time_point( std::chrono::seconds(tp.tv_sec) + std::chrono::nanoseconds(tp.tv_nsec)); #elif BOOST_OS_MACOS return std::chrono::steady_clock::time_point( std::chrono::nanoseconds(clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW))); #elif BOOST_OS_BSD struct timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); return std::chrono::steady_clock::time_point( std::chrono::seconds(tp.tv_sec) + std::chrono::nanoseconds(tp.tv_nsec)); #endif } // Some string conversion helpers because C++20 std::u8string is too cumbersome to use in practice // mixing string types generally causes loads of issues and many of the libraries we use dont expose interfaces for u8string inline const char* _utf8WrapperPtr(const char8_t* input) { // use with care return (const char*)input; } inline std::string_view _utf8Wrapper(std::u8string_view input) { std::basic_string_view v((char*)input.data(), input.size()); return v; } // convert fs::path to utf8 encoded string inline std::string _pathToUtf8(const fs::path& path) { std::u8string strU8 = path.generic_u8string(); std::string v((const char*)strU8.data(), strU8.size()); return v; } // convert utf8 encoded string to fs::path inline fs::path _utf8ToPath(std::string_view input) { std::basic_string_view v((char8_t*)input.data(), input.size()); return fs::path(v); } // locale-independent variant of tolower() which also matches Wii U behavior inline char _ansiToLower(char c) { if (c >= 'A' && c <= 'Z') c -= ('A' - 'a'); return c; } class RunAtCemuBoot // -> replaces this with direct function calls. Linkers other than MSVC may optimize way object files entirely if they are not referenced from outside. So a source file self-registering using this would be causing issues { public: RunAtCemuBoot(void(*f)()) { f(); } }; // temporary wrapper until Concurrency TS gives us std::future .is_ready() template bool future_is_ready(std::future& f) { #if defined(__GNUC__) return f.wait_for(std::chrono::nanoseconds(0)) == std::future_status::ready; #else return f._Is_ready(); #endif } // helper function to cast raw pointers to std::atomic // this is technically not legal but works on most platforms as long as alignment restrictions are met and the implementation of atomic doesnt come with additional members template std::atomic* _rawPtrToAtomic(T* ptr) { static_assert(sizeof(T) == sizeof(std::atomic)); cemu_assert_debug((reinterpret_cast(ptr) % alignof(std::atomic)) == 0); return reinterpret_cast*>(ptr); } #if defined(__GNUC__) #define ATTR_MS_ABI __attribute__((ms_abi)) #else #define ATTR_MS_ABI #endif #if !defined(TRUE) or !defined(FALSE) #define TRUE (1) #define FALSE (0) #endif inline uint32 GetTitleIdHigh(uint64 titleId) { return titleId >> 32; } inline uint32 GetTitleIdLow(uint64 titleId) { return titleId & 0xFFFFFFFF; } #if defined(__GNUC__) #define memcpy_dwords(__dest, __src, __numDwords) memcpy((__dest), (__src), (__numDwords) * sizeof(uint32)) #define memcpy_qwords(__dest, __src, __numQwords) memcpy((__dest), (__src), (__numQwords) * sizeof(uint64)) #else #define memcpy_dwords(__dest, __src, __numDwords) __movsd((unsigned long*)(__dest), (const unsigned long*)(__src), __numDwords) #define memcpy_qwords(__dest, __src, __numQwords) __movsq((unsigned long long*)(__dest), (const unsigned long long*)(__src), __numQwords) #endif // PPC context and memory functions #include "Cafe/HW/MMU/MMU.h" #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/PPCCallback.h" // PPC stack trace printer void DebugLogStackTrace(struct OSThread_t* thread, MPTR sp, bool printSymbols = false); // generic formatter for enums (to underlying) template requires std::is_enum_v struct fmt::formatter : fmt::formatter> { auto format(const Enum& e, format_context& ctx) const { //return fmt::format_to(ctx.out(), "{}", fmt::underlying(e)); return formatter>::format(fmt::underlying(e), ctx); } }; // formatter for betype template struct fmt::formatter> : fmt::formatter { auto format(const betype& e, format_context& ctx) const { return formatter::format(static_cast(e), ctx); } }; // useful future C++ stuff namespace stdx { // std::to_underlying template requires (std::is_enum_v) constexpr std::underlying_type_t to_underlying(EnumT e) noexcept { return static_cast>(e); }; // std::scope_exit template class scope_exit { Fn m_func; bool m_released = false; public: explicit scope_exit(Fn&& f) noexcept : m_func(std::forward(f)) {} ~scope_exit() { if (!m_released) m_func(); } scope_exit(scope_exit&& other) noexcept : m_func(std::move(other.m_func)), m_released(std::exchange(other.m_released, true)) {} scope_exit(const scope_exit&) = delete; scope_exit& operator=(scope_exit) = delete; void release() { m_released = true;} }; // Xcode 16 doesn't have std::atomic_ref support and we provide a minimalist reimplementation as fallback #ifdef __cpp_lib_atomic_ref #include template using atomic_ref = std::atomic_ref; #else template class atomic_ref { static_assert(std::is_trivially_copyable_v, "atomic_ref requires trivially copyable types"); public: using value_type = T; explicit atomic_ref(T& obj) noexcept : ptr_(std::addressof(obj)) {} T load(std::memory_order order = std::memory_order_seq_cst) const noexcept { auto aptr = reinterpret_cast*>(ptr_); return aptr->load(order); } void store(T desired, std::memory_order order = std::memory_order_seq_cst) const noexcept { auto aptr = reinterpret_cast*>(ptr_); aptr->store(desired, order); } private: T* ptr_; }; #endif } ================================================ FILE: src/Common/socket.h ================================================ #pragma once #if BOOST_OS_WINDOWS #include typedef int socklen_t; #else #include #define SOCKET int #define closesocket close #define SOCKET_ERROR -1 #define INVALID_SOCKET -1 #endif ================================================ FILE: src/Common/unix/FileStream_unix.cpp ================================================ #include "Common/unix/FileStream_unix.h" #include fs::path findPathCI(const fs::path& path) { if (fs::exists(path)) return path; fs::path fName = path.filename(); fs::path parentPath = path.parent_path(); if (!fs::exists(parentPath)) { auto CIParent = findPathCI(parentPath); if (fs::exists(CIParent)) return findPathCI(CIParent / fName); } std::error_code listErr; for (auto&& dirEntry : fs::directory_iterator(parentPath, listErr)) if (boost::iequals(dirEntry.path().filename().string(), fName.string())) return dirEntry; return parentPath / fName; } FileStream* FileStream::openFile(std::string_view path) { return openFile2(path, false); } FileStream* FileStream::openFile(const wchar_t* path, bool allowWrite) { return openFile2(path, allowWrite); } FileStream* FileStream::openFile2(const fs::path& path, bool allowWrite) { FileStream* fs = new FileStream(path, true, allowWrite); if (fs->m_isValid) return fs; delete fs; return nullptr; } FileStream* FileStream::createFile(const wchar_t* path) { return createFile2(path); } FileStream* FileStream::createFile(std::string_view path) { return createFile2(path); } FileStream* FileStream::createFile2(const fs::path& path) { FileStream* fs = new FileStream(path, false, false); if (fs->m_isValid) return fs; delete fs; return nullptr; } std::optional> FileStream::LoadIntoMemory(const fs::path& path) { FileStream* fs = openFile2(path); if (!fs) return std::nullopt; uint64 fileSize = fs->GetSize(); if (fileSize > 0xFFFFFFFFull) { delete fs; return std::nullopt; } std::optional> v(fileSize); if (fs->readData(v->data(), (uint32)fileSize) != (uint32)fileSize) { delete fs; return std::nullopt; } delete fs; return v; } void FileStream::SetPosition(uint64 pos) { cemu_assert(m_isValid); if (m_prevOperationWasWrite) m_fileStream.seekp((std::streampos)pos); else m_fileStream.seekg((std::streampos)pos); } uint64 FileStream::GetSize() { cemu_assert(m_isValid); auto currentPos = m_fileStream.tellg(); m_fileStream.seekg(0, std::ios::end); auto fileSize = m_fileStream.tellg(); m_fileStream.seekg(currentPos, std::ios::beg); uint64 fs = (uint64)fileSize; return fs; } bool FileStream::SetEndOfFile() { assert_dbg(); return true; //return ::SetEndOfFile(m_hFile) != 0; } void FileStream::extract(std::vector& data) { uint64 fileSize = GetSize(); SetPosition(0); data.resize(fileSize); readData(data.data(), fileSize); } void FileStream::Flush() { m_fileStream.flush(); } uint32 FileStream::readData(void* data, uint32 length) { SyncReadWriteSeek(false); m_fileStream.read((char*)data, length); size_t bytesRead = m_fileStream.gcount(); return (uint32)bytesRead; } bool FileStream::readU64(uint64& v) { return readData(&v, sizeof(uint64)) == sizeof(uint64); } bool FileStream::readU32(uint32& v) { return readData(&v, sizeof(uint32)) == sizeof(uint32); } bool FileStream::readU8(uint8& v) { return readData(&v, sizeof(uint8)) == sizeof(uint8); } bool FileStream::readLine(std::string& line) { line.clear(); uint8 c; bool isEOF = true; while (readU8(c)) { isEOF = false; if (c == '\r') continue; if (c == '\n') break; line.push_back((char)c); } return !isEOF; } sint32 FileStream::writeData(const void* data, sint32 length) { SyncReadWriteSeek(true); m_fileStream.write((const char*)data, length); return length; } void FileStream::writeU64(uint64 v) { writeData(&v, sizeof(uint64)); } void FileStream::writeU32(uint32 v) { writeData(&v, sizeof(uint32)); } void FileStream::writeU8(uint8 v) { writeData(&v, sizeof(uint8)); } void FileStream::writeStringFmt(const char* format, ...) { char buffer[2048]; va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); writeData(buffer, (sint32)strlen(buffer)); } void FileStream::writeString(const char* str) { writeData(str, (sint32)strlen(str)); } void FileStream::writeLine(const char* str) { writeData(str, (sint32)strlen(str)); writeData("\r\n", 2); } FileStream::~FileStream() { if (m_isValid) { m_fileStream.close(); } // CloseHandle(m_hFile); } FileStream::FileStream(const fs::path& path, bool isOpen, bool isWriteable) { fs::path CIPath = findPathCI(path); if (isOpen) { m_fileStream.open(CIPath, isWriteable ? (std::ios_base::in | std::ios_base::out | std::ios_base::binary) : (std::ios_base::in | std::ios_base::binary)); m_isValid = m_fileStream.is_open(); } else { m_fileStream.open(CIPath, std::ios_base::in | std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); m_isValid = m_fileStream.is_open(); } if(m_isValid && fs::is_directory(path)) { m_isValid = false; m_fileStream.close(); } } void FileStream::SyncReadWriteSeek(bool nextOpIsWrite) { // nextOpIsWrite == false -> read. Otherwise write if (nextOpIsWrite == m_prevOperationWasWrite) return; if (nextOpIsWrite) m_fileStream.seekp(m_fileStream.tellg(), std::ios::beg); else m_fileStream.seekg(m_fileStream.tellp(), std::ios::beg); m_prevOperationWasWrite = nextOpIsWrite; } ================================================ FILE: src/Common/unix/FileStream_unix.h ================================================ #pragma once #include "Common/precompiled.h" class FileStream { public: static FileStream* openFile(std::string_view path); static FileStream* openFile(const wchar_t* path, bool allowWrite = false); static FileStream* openFile2(const fs::path& path, bool allowWrite = false); static FileStream* createFile(const wchar_t* path); static FileStream* createFile(std::string_view path); static FileStream* createFile2(const fs::path& path); // helper function to load a file into memory static std::optional> LoadIntoMemory(const fs::path& path); // size and seek void SetPosition(uint64 pos); uint64 GetSize(); bool SetEndOfFile(); void extract(std::vector& data); void Flush(); // reading uint32 readData(void* data, uint32 length); bool readU64(uint64& v); bool readU32(uint32& v); bool readU16(uint16& v); bool readU8(uint8& v); bool readLine(std::string& line); // writing (binary) sint32 writeData(const void* data, sint32 length); void writeU64(uint64 v); void writeU32(uint32 v); void writeU16(uint16 v); void writeU8(uint8 v); // writing (strings) void writeStringFmt(const char* format, ...); void writeString(const char* str); void writeLine(const char* str); ~FileStream(); FileStream() {}; private: void SyncReadWriteSeek(bool nextOpIsWrite); FileStream(const fs::path& path, bool isOpen, bool isWriteable); bool m_isValid{}; std::fstream m_fileStream; bool m_prevOperationWasWrite{false}; }; ================================================ FILE: src/Common/unix/date.h ================================================ #ifndef DATE_H #define DATE_H // The MIT License (MIT) // // Copyright (c) 2015, 2016, 2017 Howard Hinnant // Copyright (c) 2016 Adrian Colomitchi // Copyright (c) 2017 Florian Dang // Copyright (c) 2017 Paul Thompson // Copyright (c) 2018, 2019 Tomasz Kamiński // Copyright (c) 2019 Jiangang Zhuang // // 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. // // Our apologies. When the previous paragraph was written, lowercase had not yet // been invented (that would involve another several millennia of evolution). // We did not mean to shout. #ifndef HAS_STRING_VIEW # if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define HAS_STRING_VIEW 1 # else # define HAS_STRING_VIEW 0 # endif #endif // HAS_STRING_VIEW #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAS_STRING_VIEW # include #endif #include #include #ifdef __GNUC__ # pragma GCC diagnostic push # if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 7) # pragma GCC diagnostic ignored "-Wpedantic" # endif # if __GNUC__ < 5 // GCC 4.9 Bug 61489 Wrong warning with -Wmissing-field-initializers # pragma GCC diagnostic ignored "-Wmissing-field-initializers" # endif #endif #ifdef _MSC_VER # pragma warning(push) // warning C4127: conditional expression is constant # pragma warning(disable : 4127) #endif namespace date { //---------------+ // Configuration | //---------------+ #ifndef ONLY_C_LOCALE # define ONLY_C_LOCALE 0 #endif #if defined(_MSC_VER) && (!defined(__clang__) || (_MSC_VER < 1910)) // MSVC # ifndef _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING # define _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING # endif # if _MSC_VER < 1910 // before VS2017 # define CONSTDATA const # define CONSTCD11 # define CONSTCD14 # define NOEXCEPT _NOEXCEPT # else // VS2017 and later # define CONSTDATA constexpr const # define CONSTCD11 constexpr # define CONSTCD14 constexpr # define NOEXCEPT noexcept # endif #elif defined(__SUNPRO_CC) && __SUNPRO_CC <= 0x5150 // Oracle Developer Studio 12.6 and earlier # define CONSTDATA constexpr const # define CONSTCD11 constexpr # define CONSTCD14 # define NOEXCEPT noexcept #elif __cplusplus >= 201402 // C++14 # define CONSTDATA constexpr const # define CONSTCD11 constexpr # define CONSTCD14 constexpr # define NOEXCEPT noexcept #else // C++11 # define CONSTDATA constexpr const # define CONSTCD11 constexpr # define CONSTCD14 # define NOEXCEPT noexcept #endif #ifndef HAS_UNCAUGHT_EXCEPTIONS # if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define HAS_UNCAUGHT_EXCEPTIONS 1 # else # define HAS_UNCAUGHT_EXCEPTIONS 0 # endif #endif // HAS_UNCAUGHT_EXCEPTIONS #ifndef HAS_VOID_T # if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define HAS_VOID_T 1 # else # define HAS_VOID_T 0 # endif #endif // HAS_VOID_T // Protect from Oracle sun macro #ifdef sun # undef sun #endif // Work around for a NVCC compiler bug which causes it to fail // to compile std::ratio_{multiply,divide} when used directly // in the std::chrono::duration template instantiations below namespace detail { template using ratio_multiply = decltype(std::ratio_multiply{}); template using ratio_divide = decltype(std::ratio_divide{}); } // namespace detail //-----------+ // Interface | //-----------+ // durations using days = std::chrono::duration , std::chrono::hours::period>>; using weeks = std::chrono::duration , days::period>>; using years = std::chrono::duration , days::period>>; using months = std::chrono::duration >>; // time_point template using sys_time = std::chrono::time_point; using sys_days = sys_time; using sys_seconds = sys_time; struct local_t {}; template using local_time = std::chrono::time_point; using local_seconds = local_time; using local_days = local_time; // types struct last_spec { explicit last_spec() = default; }; class day; class month; class year; class weekday; class weekday_indexed; class weekday_last; class month_day; class month_day_last; class month_weekday; class month_weekday_last; class year_month; class year_month_day; class year_month_day_last; class year_month_weekday; class year_month_weekday_last; // date composition operators CONSTCD11 year_month operator/(const year& y, const month& m) NOEXCEPT; CONSTCD11 year_month operator/(const year& y, int m) NOEXCEPT; CONSTCD11 month_day operator/(const day& d, const month& m) NOEXCEPT; CONSTCD11 month_day operator/(const day& d, int m) NOEXCEPT; CONSTCD11 month_day operator/(const month& m, const day& d) NOEXCEPT; CONSTCD11 month_day operator/(const month& m, int d) NOEXCEPT; CONSTCD11 month_day operator/(int m, const day& d) NOEXCEPT; CONSTCD11 month_day_last operator/(const month& m, last_spec) NOEXCEPT; CONSTCD11 month_day_last operator/(int m, last_spec) NOEXCEPT; CONSTCD11 month_day_last operator/(last_spec, const month& m) NOEXCEPT; CONSTCD11 month_day_last operator/(last_spec, int m) NOEXCEPT; CONSTCD11 month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT; CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT; CONSTCD11 month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT; CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT; CONSTCD11 year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT; CONSTCD11 year_month_day operator/(const year_month& ym, int d) NOEXCEPT; CONSTCD11 year_month_day operator/(const year& y, const month_day& md) NOEXCEPT; CONSTCD11 year_month_day operator/(int y, const month_day& md) NOEXCEPT; CONSTCD11 year_month_day operator/(const month_day& md, const year& y) NOEXCEPT; CONSTCD11 year_month_day operator/(const month_day& md, int y) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT; CONSTCD11 year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT; CONSTCD11 year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const year& y, const month_weekday& mwd) NOEXCEPT; CONSTCD11 year_month_weekday operator/(int y, const month_weekday& mwd) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const month_weekday& mwd, const year& y) NOEXCEPT; CONSTCD11 year_month_weekday operator/(const month_weekday& mwd, int y) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(int y, const month_weekday_last& mwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT; CONSTCD11 year_month_weekday_last operator/(const month_weekday_last& mwdl, int y) NOEXCEPT; // Detailed interface // day class day { unsigned char d_; public: day() = default; explicit CONSTCD11 day(unsigned d) NOEXCEPT; CONSTCD14 day& operator++() NOEXCEPT; CONSTCD14 day operator++(int) NOEXCEPT; CONSTCD14 day& operator--() NOEXCEPT; CONSTCD14 day operator--(int) NOEXCEPT; CONSTCD14 day& operator+=(const days& d) NOEXCEPT; CONSTCD14 day& operator-=(const days& d) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator!=(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator< (const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator> (const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator<=(const day& x, const day& y) NOEXCEPT; CONSTCD11 bool operator>=(const day& x, const day& y) NOEXCEPT; CONSTCD11 day operator+(const day& x, const days& y) NOEXCEPT; CONSTCD11 day operator+(const days& x, const day& y) NOEXCEPT; CONSTCD11 day operator-(const day& x, const days& y) NOEXCEPT; CONSTCD11 days operator-(const day& x, const day& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const day& d); // month class month { unsigned char m_; public: month() = default; explicit CONSTCD11 month(unsigned m) NOEXCEPT; CONSTCD14 month& operator++() NOEXCEPT; CONSTCD14 month operator++(int) NOEXCEPT; CONSTCD14 month& operator--() NOEXCEPT; CONSTCD14 month operator--(int) NOEXCEPT; CONSTCD14 month& operator+=(const months& m) NOEXCEPT; CONSTCD14 month& operator-=(const months& m) NOEXCEPT; CONSTCD11 explicit operator unsigned() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator!=(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator< (const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator> (const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator<=(const month& x, const month& y) NOEXCEPT; CONSTCD11 bool operator>=(const month& x, const month& y) NOEXCEPT; CONSTCD14 month operator+(const month& x, const months& y) NOEXCEPT; CONSTCD14 month operator+(const months& x, const month& y) NOEXCEPT; CONSTCD14 month operator-(const month& x, const months& y) NOEXCEPT; CONSTCD14 months operator-(const month& x, const month& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month& m); // year class year { short y_; public: year() = default; explicit CONSTCD11 year(int y) NOEXCEPT; CONSTCD14 year& operator++() NOEXCEPT; CONSTCD14 year operator++(int) NOEXCEPT; CONSTCD14 year& operator--() NOEXCEPT; CONSTCD14 year operator--(int) NOEXCEPT; CONSTCD14 year& operator+=(const years& y) NOEXCEPT; CONSTCD14 year& operator-=(const years& y) NOEXCEPT; CONSTCD11 year operator-() const NOEXCEPT; CONSTCD11 year operator+() const NOEXCEPT; CONSTCD11 bool is_leap() const NOEXCEPT; CONSTCD11 explicit operator int() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; static CONSTCD11 year min() NOEXCEPT { return year{-32767}; } static CONSTCD11 year max() NOEXCEPT { return year{32767}; } }; CONSTCD11 bool operator==(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator!=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator< (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator> (const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator<=(const year& x, const year& y) NOEXCEPT; CONSTCD11 bool operator>=(const year& x, const year& y) NOEXCEPT; CONSTCD11 year operator+(const year& x, const years& y) NOEXCEPT; CONSTCD11 year operator+(const years& x, const year& y) NOEXCEPT; CONSTCD11 year operator-(const year& x, const years& y) NOEXCEPT; CONSTCD11 years operator-(const year& x, const year& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year& y); // weekday class weekday { unsigned char wd_; public: weekday() = default; explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; CONSTCD14 weekday(const sys_days& dp) NOEXCEPT; CONSTCD14 explicit weekday(const local_days& dp) NOEXCEPT; CONSTCD14 weekday& operator++() NOEXCEPT; CONSTCD14 weekday operator++(int) NOEXCEPT; CONSTCD14 weekday& operator--() NOEXCEPT; CONSTCD14 weekday operator--(int) NOEXCEPT; CONSTCD14 weekday& operator+=(const days& d) NOEXCEPT; CONSTCD14 weekday& operator-=(const days& d) NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; CONSTCD11 unsigned c_encoding() const NOEXCEPT; CONSTCD11 unsigned iso_encoding() const NOEXCEPT; CONSTCD11 weekday_indexed operator[](unsigned index) const NOEXCEPT; CONSTCD11 weekday_last operator[](last_spec) const NOEXCEPT; private: static CONSTCD14 unsigned char weekday_from_days(int z) NOEXCEPT; friend CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; friend CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; friend CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; template friend std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd); friend class weekday_indexed; }; CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 weekday operator+(const days& x, const weekday& y) NOEXCEPT; CONSTCD14 weekday operator-(const weekday& x, const days& y) NOEXCEPT; CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd); // weekday_indexed class weekday_indexed { unsigned char wd_ : 4; unsigned char index_ : 4; public: weekday_indexed() = default; CONSTCD11 weekday_indexed(const date::weekday& wd, unsigned index) NOEXCEPT; CONSTCD11 date::weekday weekday() const NOEXCEPT; CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_indexed& wdi); // weekday_last class weekday_last { date::weekday wd_; public: explicit CONSTCD11 weekday_last(const date::weekday& wd) NOEXCEPT; CONSTCD11 date::weekday weekday() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_last& wdl); namespace detail { struct unspecified_month_disambiguator {}; } // namespace detail // year_month class year_month { date::year y_; date::month m_; public: year_month() = default; CONSTCD11 year_month(const date::year& y, const date::month& m) NOEXCEPT; CONSTCD11 date::year year() const NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; template CONSTCD14 year_month& operator+=(const months& dm) NOEXCEPT; template CONSTCD14 year_month& operator-=(const months& dm) NOEXCEPT; CONSTCD14 year_month& operator+=(const years& dy) NOEXCEPT; CONSTCD14 year_month& operator-=(const years& dy) NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month& x, const year_month& y) NOEXCEPT; template CONSTCD14 year_month operator+(const year_month& ym, const months& dm) NOEXCEPT; template CONSTCD14 year_month operator+(const months& dm, const year_month& ym) NOEXCEPT; template CONSTCD14 year_month operator-(const year_month& ym, const months& dm) NOEXCEPT; CONSTCD11 months operator-(const year_month& x, const year_month& y) NOEXCEPT; CONSTCD11 year_month operator+(const year_month& ym, const years& dy) NOEXCEPT; CONSTCD11 year_month operator+(const years& dy, const year_month& ym) NOEXCEPT; CONSTCD11 year_month operator-(const year_month& ym, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month& ym); // month_day class month_day { date::month m_; date::day d_; public: month_day() = default; CONSTCD11 month_day(const date::month& m, const date::day& d) NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::day day() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator< (const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator> (const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator<=(const month_day& x, const month_day& y) NOEXCEPT; CONSTCD11 bool operator>=(const month_day& x, const month_day& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_day& md); // month_day_last class month_day_last { date::month m_; public: CONSTCD11 explicit month_day_last(const date::month& m) NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator< (const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator> (const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT; CONSTCD11 bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_day_last& mdl); // month_weekday class month_weekday { date::month m_; date::weekday_indexed wdi_; public: CONSTCD11 month_weekday(const date::month& m, const date::weekday_indexed& wdi) NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday& mwd); // month_weekday_last class month_weekday_last { date::month m_; date::weekday_last wdl_; public: CONSTCD11 month_weekday_last(const date::month& m, const date::weekday_last& wd) NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday_last& mwdl); // class year_month_day class year_month_day { date::year y_; date::month m_; date::day d_; public: year_month_day() = default; CONSTCD11 year_month_day(const date::year& y, const date::month& m, const date::day& d) NOEXCEPT; CONSTCD14 year_month_day(const year_month_day_last& ymdl) NOEXCEPT; CONSTCD14 year_month_day(sys_days dp) NOEXCEPT; CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT; template CONSTCD14 year_month_day& operator+=(const months& m) NOEXCEPT; template CONSTCD14 year_month_day& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_day& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_day& operator-=(const years& y) NOEXCEPT; CONSTCD11 date::year year() const NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::day day() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT; template CONSTCD14 year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT; template CONSTCD14 year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT; template CONSTCD14 year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT; CONSTCD11 year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT; CONSTCD11 year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT; CONSTCD11 year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day& ymd); // year_month_day_last class year_month_day_last { date::year y_; date::month_day_last mdl_; public: CONSTCD11 year_month_day_last(const date::year& y, const date::month_day_last& mdl) NOEXCEPT; template CONSTCD14 year_month_day_last& operator+=(const months& m) NOEXCEPT; template CONSTCD14 year_month_day_last& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_day_last& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_day_last& operator-=(const years& y) NOEXCEPT; CONSTCD11 date::year year() const NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::month_day_last month_day_last() const NOEXCEPT; CONSTCD14 date::day day() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator< (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator> (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; CONSTCD11 bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; template CONSTCD14 year_month_day_last operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; template CONSTCD14 year_month_day_last operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT; CONSTCD11 year_month_day_last operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; CONSTCD11 year_month_day_last operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT; template CONSTCD14 year_month_day_last operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; CONSTCD11 year_month_day_last operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day_last& ymdl); // year_month_weekday class year_month_weekday { date::year y_; date::month m_; date::weekday_indexed wdi_; public: year_month_weekday() = default; CONSTCD11 year_month_weekday(const date::year& y, const date::month& m, const date::weekday_indexed& wdi) NOEXCEPT; CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT; CONSTCD14 explicit year_month_weekday(const local_days& dp) NOEXCEPT; template CONSTCD14 year_month_weekday& operator+=(const months& m) NOEXCEPT; template CONSTCD14 year_month_weekday& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_weekday& operator-=(const years& y) NOEXCEPT; CONSTCD11 date::year year() const NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::weekday weekday() const NOEXCEPT; CONSTCD11 unsigned index() const NOEXCEPT; CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD14 bool ok() const NOEXCEPT; private: static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; template CONSTCD14 year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; template CONSTCD14 year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT; CONSTCD11 year_month_weekday operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; CONSTCD11 year_month_weekday operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT; template CONSTCD14 year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; CONSTCD11 year_month_weekday operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi); // year_month_weekday_last class year_month_weekday_last { date::year y_; date::month m_; date::weekday_last wdl_; public: CONSTCD11 year_month_weekday_last(const date::year& y, const date::month& m, const date::weekday_last& wdl) NOEXCEPT; template CONSTCD14 year_month_weekday_last& operator+=(const months& m) NOEXCEPT; template CONSTCD14 year_month_weekday_last& operator-=(const months& m) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator+=(const years& y) NOEXCEPT; CONSTCD14 year_month_weekday_last& operator-=(const years& y) NOEXCEPT; CONSTCD11 date::year year() const NOEXCEPT; CONSTCD11 date::month month() const NOEXCEPT; CONSTCD11 date::weekday weekday() const NOEXCEPT; CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; CONSTCD14 operator sys_days() const NOEXCEPT; CONSTCD14 explicit operator local_days() const NOEXCEPT; CONSTCD11 bool ok() const NOEXCEPT; private: CONSTCD14 days to_days() const NOEXCEPT; }; CONSTCD11 bool operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; CONSTCD11 bool operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; template CONSTCD14 year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; template CONSTCD14 year_month_weekday_last operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT; CONSTCD11 year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; CONSTCD11 year_month_weekday_last operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT; template CONSTCD14 year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; CONSTCD11 year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; template std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl); #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 date::day operator "" _d(unsigned long long d) NOEXCEPT; CONSTCD11 date::year operator "" _y(unsigned long long y) NOEXCEPT; } // inline namespace literals #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) // CONSTDATA date::month January{1}; // CONSTDATA date::month February{2}; // CONSTDATA date::month March{3}; // CONSTDATA date::month April{4}; // CONSTDATA date::month May{5}; // CONSTDATA date::month June{6}; // CONSTDATA date::month July{7}; // CONSTDATA date::month August{8}; // CONSTDATA date::month September{9}; // CONSTDATA date::month October{10}; // CONSTDATA date::month November{11}; // CONSTDATA date::month December{12}; // // CONSTDATA date::weekday Sunday{0u}; // CONSTDATA date::weekday Monday{1u}; // CONSTDATA date::weekday Tuesday{2u}; // CONSTDATA date::weekday Wednesday{3u}; // CONSTDATA date::weekday Thursday{4u}; // CONSTDATA date::weekday Friday{5u}; // CONSTDATA date::weekday Saturday{6u}; #if HAS_VOID_T template > struct is_clock : std::false_type {}; template struct is_clock> : std::true_type {}; template inline constexpr bool is_clock_v = is_clock::value; #endif // HAS_VOID_T //----------------+ // Implementation | //----------------+ // utilities namespace detail { template> class save_istream { protected: std::basic_ios& is_; CharT fill_; std::ios::fmtflags flags_; std::streamsize precision_; std::streamsize width_; std::basic_ostream* tie_; std::locale loc_; public: ~save_istream() { is_.fill(fill_); is_.flags(flags_); is_.precision(precision_); is_.width(width_); is_.imbue(loc_); is_.tie(tie_); } save_istream(const save_istream&) = delete; save_istream& operator=(const save_istream&) = delete; explicit save_istream(std::basic_ios& is) : is_(is) , fill_(is.fill()) , flags_(is.flags()) , precision_(is.precision()) , width_(is.width(0)) , tie_(is.tie(nullptr)) , loc_(is.getloc()) { if (tie_ != nullptr) tie_->flush(); } }; template> class save_ostream : private save_istream { public: ~save_ostream() { if ((this->flags_ & std::ios::unitbuf) && #if HAS_UNCAUGHT_EXCEPTIONS std::uncaught_exceptions() == 0 && #else !std::uncaught_exception() && #endif this->is_.good()) this->is_.rdbuf()->pubsync(); } save_ostream(const save_ostream&) = delete; save_ostream& operator=(const save_ostream&) = delete; explicit save_ostream(std::basic_ios& os) : save_istream(os) { } }; template struct choose_trunc_type { static const int digits = std::numeric_limits::digits; using type = typename std::conditional < digits < 32, std::int32_t, typename std::conditional < digits < 64, std::int64_t, #ifdef __SIZEOF_INT128__ __int128 #else std::int64_t #endif >::type >::type; }; template CONSTCD11 inline typename std::enable_if < !std::chrono::treat_as_floating_point::value, T >::type trunc(T t) NOEXCEPT { return t; } template CONSTCD14 inline typename std::enable_if < std::chrono::treat_as_floating_point::value, T >::type trunc(T t) NOEXCEPT { using std::numeric_limits; using I = typename choose_trunc_type::type; CONSTDATA auto digits = numeric_limits::digits; static_assert(digits < numeric_limits::digits, ""); CONSTDATA auto max = I{1} << (digits-1); CONSTDATA auto min = -max; const auto negative = t < T{0}; if (min <= t && t <= max && t != 0 && t == t) { t = static_cast(static_cast(t)); if (t == 0 && negative) t = -t; } return t; } template struct static_gcd { static const std::intmax_t value = static_gcd::value; }; template struct static_gcd { static const std::intmax_t value = Xp; }; template <> struct static_gcd<0, 0> { static const std::intmax_t value = 1; }; template struct no_overflow { private: static const std::intmax_t gcd_n1_n2 = static_gcd::value; static const std::intmax_t gcd_d1_d2 = static_gcd::value; static const std::intmax_t n1 = R1::num / gcd_n1_n2; static const std::intmax_t d1 = R1::den / gcd_d1_d2; static const std::intmax_t n2 = R2::num / gcd_n1_n2; static const std::intmax_t d2 = R2::den / gcd_d1_d2; #ifdef __cpp_constexpr static const std::intmax_t max = std::numeric_limits::max(); #else static const std::intmax_t max = LLONG_MAX; #endif template struct mul // overflow == false { static const std::intmax_t value = Xp * Yp; }; template struct mul { static const std::intmax_t value = 1; }; public: static const bool value = (n1 <= max / d2) && (n2 <= max / d1); typedef std::ratio::value, mul::value> type; }; } // detail // trunc towards zero template CONSTCD11 inline typename std::enable_if < detail::no_overflow::value, To >::type trunc(const std::chrono::duration& d) { return To{detail::trunc(std::chrono::duration_cast(d).count())}; } template CONSTCD11 inline typename std::enable_if < !detail::no_overflow::value, To >::type trunc(const std::chrono::duration& d) { using std::chrono::duration_cast; using std::chrono::duration; using rep = typename std::common_type::type; return To{detail::trunc(duration_cast(duration_cast>(d)).count())}; } #ifndef HAS_CHRONO_ROUNDING # if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 190023918 || (_MSC_FULL_VER >= 190000000 && defined (__clang__))) # define HAS_CHRONO_ROUNDING 1 # elif defined(__cpp_lib_chrono) && __cplusplus > 201402 && __cpp_lib_chrono >= 201510 # define HAS_CHRONO_ROUNDING 1 # elif defined(_LIBCPP_VERSION) && __cplusplus > 201402 && _LIBCPP_VERSION >= 3800 # define HAS_CHRONO_ROUNDING 1 # else # define HAS_CHRONO_ROUNDING 0 # endif #endif // HAS_CHRONO_ROUNDING #if HAS_CHRONO_ROUNDING == 0 // round down template CONSTCD14 inline typename std::enable_if < detail::no_overflow::value, To >::type floor(const std::chrono::duration& d) { auto t = trunc(d); if (t > d) return t - To{1}; return t; } template CONSTCD14 inline typename std::enable_if < !detail::no_overflow::value, To >::type floor(const std::chrono::duration& d) { using rep = typename std::common_type::type; return floor(floor>(d)); } // round to nearest, to even on tie template CONSTCD14 inline To round(const std::chrono::duration& d) { auto t0 = floor(d); auto t1 = t0 + To{1}; if (t1 == To{0} && t0 < To{0}) t1 = -t1; auto diff0 = d - t0; auto diff1 = t1 - d; if (diff0 == diff1) { if (t0 - trunc(t0/2)*2 == To{0}) return t0; return t1; } if (diff0 < diff1) return t0; return t1; } // round up template CONSTCD14 inline To ceil(const std::chrono::duration& d) { auto t = trunc(d); if (t < d) return t + To{1}; return t; } template ::is_signed >::type> CONSTCD11 std::chrono::duration abs(std::chrono::duration d) { return d >= d.zero() ? d : static_cast(-d); } // round down template CONSTCD11 inline std::chrono::time_point floor(const std::chrono::time_point& tp) { using std::chrono::time_point; return time_point{date::floor(tp.time_since_epoch())}; } // round to nearest, to even on tie template CONSTCD11 inline std::chrono::time_point round(const std::chrono::time_point& tp) { using std::chrono::time_point; return time_point{round(tp.time_since_epoch())}; } // round up template CONSTCD11 inline std::chrono::time_point ceil(const std::chrono::time_point& tp) { using std::chrono::time_point; return time_point{ceil(tp.time_since_epoch())}; } #else // HAS_CHRONO_ROUNDING == 1 using std::chrono::floor; using std::chrono::ceil; using std::chrono::round; using std::chrono::abs; #endif // HAS_CHRONO_ROUNDING namespace detail { template CONSTCD14 inline typename std::enable_if < !std::chrono::treat_as_floating_point::value, To >::type round_i(const std::chrono::duration& d) { return round(d); } template CONSTCD14 inline typename std::enable_if < std::chrono::treat_as_floating_point::value, To >::type round_i(const std::chrono::duration& d) { return d; } template CONSTCD11 inline std::chrono::time_point round_i(const std::chrono::time_point& tp) { using std::chrono::time_point; return time_point{round_i(tp.time_since_epoch())}; } } // detail // trunc towards zero template CONSTCD11 inline std::chrono::time_point trunc(const std::chrono::time_point& tp) { using std::chrono::time_point; return time_point{trunc(tp.time_since_epoch())}; } // day CONSTCD11 inline day::day(unsigned d) NOEXCEPT : d_(static_cast(d)) {} CONSTCD14 inline day& day::operator++() NOEXCEPT {++d_; return *this;} CONSTCD14 inline day day::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline day& day::operator--() NOEXCEPT {--d_; return *this;} CONSTCD14 inline day day::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline day& day::operator+=(const days& d) NOEXCEPT {*this = *this + d; return *this;} CONSTCD14 inline day& day::operator-=(const days& d) NOEXCEPT {*this = *this - d; return *this;} CONSTCD11 inline day::operator unsigned() const NOEXCEPT {return d_;} CONSTCD11 inline bool day::ok() const NOEXCEPT {return 1 <= d_ && d_ <= 31;} CONSTCD11 inline bool operator==(const day& x, const day& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const day& x, const day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const day& x, const day& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const day& x, const day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const day& x, const day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const day& x, const day& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline days operator-(const day& x, const day& y) NOEXCEPT { return days{static_cast(static_cast(x) - static_cast(y))}; } CONSTCD11 inline day operator+(const day& x, const days& y) NOEXCEPT { return day{static_cast(x) + static_cast(y.count())}; } CONSTCD11 inline day operator+(const days& x, const day& y) NOEXCEPT { return y + x; } CONSTCD11 inline day operator-(const day& x, const days& y) NOEXCEPT { return x + -y; } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const day& d) { detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << static_cast(d); return os; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const day& d) { detail::low_level_fmt(os, d); if (!d.ok()) os << " is not a valid day"; return os; } // month CONSTCD11 inline month::month(unsigned m) NOEXCEPT : m_(static_cast(m)) {} CONSTCD14 inline month& month::operator++() NOEXCEPT {*this += months{1}; return *this;} CONSTCD14 inline month month::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline month& month::operator--() NOEXCEPT {*this -= months{1}; return *this;} CONSTCD14 inline month month::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline month& month::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } CONSTCD14 inline month& month::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD11 inline month::operator unsigned() const NOEXCEPT {return m_;} CONSTCD11 inline bool month::ok() const NOEXCEPT {return 1 <= m_ && m_ <= 12;} CONSTCD11 inline bool operator==(const month& x, const month& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const month& x, const month& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month& x, const month& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const month& x, const month& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month& x, const month& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month& x, const month& y) NOEXCEPT { return !(x < y); } CONSTCD14 inline months operator-(const month& x, const month& y) NOEXCEPT { auto const d = static_cast(x) - static_cast(y); return months(d <= 11 ? d : d + 12); } CONSTCD14 inline month operator+(const month& x, const months& y) NOEXCEPT { auto const mu = static_cast(static_cast(x)) + y.count() - 1; auto const yr = (mu >= 0 ? mu : mu-11) / 12; return month{static_cast(mu - yr * 12 + 1)}; } CONSTCD14 inline month operator+(const months& x, const month& y) NOEXCEPT { return y + x; } CONSTCD14 inline month operator-(const month& x, const months& y) NOEXCEPT { return x + -y; } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const month& m) { if (m.ok()) { CharT fmt[] = {'%', 'b', 0}; os << format(os.getloc(), fmt, m); } else os << static_cast(m); return os; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month& m) { detail::low_level_fmt(os, m); if (!m.ok()) os << " is not a valid month"; return os; } // year CONSTCD11 inline year::year(int y) NOEXCEPT : y_(static_cast(y)) {} CONSTCD14 inline year& year::operator++() NOEXCEPT {++y_; return *this;} CONSTCD14 inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline year& year::operator--() NOEXCEPT {--y_; return *this;} CONSTCD14 inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;} CONSTCD14 inline year& year::operator-=(const years& y) NOEXCEPT {*this = *this - y; return *this;} CONSTCD11 inline year year::operator-() const NOEXCEPT {return year{-y_};} CONSTCD11 inline year year::operator+() const NOEXCEPT {return *this;} CONSTCD11 inline bool year::is_leap() const NOEXCEPT { return y_ % 4 == 0 && (y_ % 100 != 0 || y_ % 400 == 0); } CONSTCD11 inline year::operator int() const NOEXCEPT {return y_;} CONSTCD11 inline bool year::ok() const NOEXCEPT { return y_ != std::numeric_limits::min(); } CONSTCD11 inline bool operator==(const year& x, const year& y) NOEXCEPT { return static_cast(x) == static_cast(y); } CONSTCD11 inline bool operator!=(const year& x, const year& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year& x, const year& y) NOEXCEPT { return static_cast(x) < static_cast(y); } CONSTCD11 inline bool operator>(const year& x, const year& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year& x, const year& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year& x, const year& y) NOEXCEPT { return !(x < y); } CONSTCD11 inline years operator-(const year& x, const year& y) NOEXCEPT { return years{static_cast(x) - static_cast(y)}; } CONSTCD11 inline year operator+(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) + y.count()}; } CONSTCD11 inline year operator+(const years& x, const year& y) NOEXCEPT { return y + x; } CONSTCD11 inline year operator-(const year& x, const years& y) NOEXCEPT { return year{static_cast(x) - y.count()}; } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const year& y) { detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::internal); os.width(4 + (y < year{0})); os.imbue(std::locale::classic()); os << static_cast(y); return os; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year& y) { detail::low_level_fmt(os, y); if (!y.ok()) os << " is not a valid year"; return os; } // weekday CONSTCD14 inline unsigned char weekday::weekday_from_days(int z) NOEXCEPT { auto u = static_cast(z); return static_cast(z >= -4 ? (u+4) % 7 : u % 7); } CONSTCD11 inline weekday::weekday(unsigned wd) NOEXCEPT : wd_(static_cast(wd != 7 ? wd : 0)) {} CONSTCD14 inline weekday::weekday(const sys_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD14 inline weekday::weekday(const local_days& dp) NOEXCEPT : wd_(weekday_from_days(dp.time_since_epoch().count())) {} CONSTCD14 inline weekday& weekday::operator++() NOEXCEPT {*this += days{1}; return *this;} CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} CONSTCD14 inline weekday& weekday::operator--() NOEXCEPT {*this -= days{1}; return *this;} CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} CONSTCD14 inline weekday& weekday::operator+=(const days& d) NOEXCEPT { *this = *this + d; return *this; } CONSTCD14 inline weekday& weekday::operator-=(const days& d) NOEXCEPT { *this = *this - d; return *this; } CONSTCD11 inline bool weekday::ok() const NOEXCEPT {return wd_ <= 6;} CONSTCD11 inline unsigned weekday::c_encoding() const NOEXCEPT { return unsigned{wd_}; } CONSTCD11 inline unsigned weekday::iso_encoding() const NOEXCEPT { return unsigned{((wd_ == 0u) ? 7u : wd_)}; } CONSTCD11 inline bool operator==(const weekday& x, const weekday& y) NOEXCEPT { return x.wd_ == y.wd_; } CONSTCD11 inline bool operator!=(const weekday& x, const weekday& y) NOEXCEPT { return !(x == y); } CONSTCD14 inline days operator-(const weekday& x, const weekday& y) NOEXCEPT { auto const wdu = x.wd_ - y.wd_; auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; return days{wdu - wk * 7}; } CONSTCD14 inline weekday operator+(const weekday& x, const days& y) NOEXCEPT { auto const wdu = static_cast(static_cast(x.wd_)) + y.count(); auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; return weekday{static_cast(wdu - wk * 7)}; } CONSTCD14 inline weekday operator+(const days& x, const weekday& y) NOEXCEPT { return y + x; } CONSTCD14 inline weekday operator-(const weekday& x, const days& y) NOEXCEPT { return x + -y; } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const weekday& wd) { if (wd.ok()) { CharT fmt[] = {'%', 'a', 0}; os << format(fmt, wd); } else os << wd.c_encoding(); return os; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday& wd) { detail::low_level_fmt(os, wd); if (!wd.ok()) os << " is not a valid weekday"; return os; } #if !defined(_MSC_VER) || (_MSC_VER >= 1900) inline namespace literals { CONSTCD11 inline date::day operator "" _d(unsigned long long d) NOEXCEPT { return date::day{static_cast(d)}; } CONSTCD11 inline date::year operator "" _y(unsigned long long y) NOEXCEPT { return date::year(static_cast(y)); } #endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) CONSTDATA date::last_spec last{}; CONSTDATA date::month jan{1}; CONSTDATA date::month feb{2}; CONSTDATA date::month mar{3}; CONSTDATA date::month apr{4}; CONSTDATA date::month may{5}; CONSTDATA date::month jun{6}; CONSTDATA date::month jul{7}; CONSTDATA date::month aug{8}; CONSTDATA date::month sep{9}; CONSTDATA date::month oct{10}; CONSTDATA date::month nov{11}; CONSTDATA date::month dec{12}; CONSTDATA date::weekday sun{0u}; CONSTDATA date::weekday mon{1u}; CONSTDATA date::weekday tue{2u}; CONSTDATA date::weekday wed{3u}; CONSTDATA date::weekday thu{4u}; CONSTDATA date::weekday fri{5u}; CONSTDATA date::weekday sat{6u}; #if !defined(_MSC_VER) || (_MSC_VER >= 1900) } // inline namespace literals #endif CONSTDATA date::month January{1}; CONSTDATA date::month February{2}; CONSTDATA date::month March{3}; CONSTDATA date::month April{4}; CONSTDATA date::month May{5}; CONSTDATA date::month June{6}; CONSTDATA date::month July{7}; CONSTDATA date::month August{8}; CONSTDATA date::month September{9}; CONSTDATA date::month October{10}; CONSTDATA date::month November{11}; CONSTDATA date::month December{12}; CONSTDATA date::weekday Monday{1}; CONSTDATA date::weekday Tuesday{2}; CONSTDATA date::weekday Wednesday{3}; CONSTDATA date::weekday Thursday{4}; CONSTDATA date::weekday Friday{5}; CONSTDATA date::weekday Saturday{6}; CONSTDATA date::weekday Sunday{7}; // weekday_indexed CONSTCD11 inline weekday weekday_indexed::weekday() const NOEXCEPT { return date::weekday{static_cast(wd_)}; } CONSTCD11 inline unsigned weekday_indexed::index() const NOEXCEPT {return index_;} CONSTCD11 inline bool weekday_indexed::ok() const NOEXCEPT { return weekday().ok() && 1 <= index_ && index_ <= 5; } #ifdef __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wconversion" #endif // __GNUC__ CONSTCD11 inline weekday_indexed::weekday_indexed(const date::weekday& wd, unsigned index) NOEXCEPT : wd_(static_cast(static_cast(wd.wd_))) , index_(static_cast(index)) {} #ifdef __GNUC__ # pragma GCC diagnostic pop #endif // __GNUC__ namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const weekday_indexed& wdi) { return low_level_fmt(os, wdi.weekday()) << '[' << wdi.index() << ']'; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_indexed& wdi) { detail::low_level_fmt(os, wdi); if (!wdi.ok()) os << " is not a valid weekday_indexed"; return os; } CONSTCD11 inline weekday_indexed weekday::operator[](unsigned index) const NOEXCEPT { return {*this, index}; } CONSTCD11 inline bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT { return x.weekday() == y.weekday() && x.index() == y.index(); } CONSTCD11 inline bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT { return !(x == y); } // weekday_last CONSTCD11 inline date::weekday weekday_last::weekday() const NOEXCEPT {return wd_;} CONSTCD11 inline bool weekday_last::ok() const NOEXCEPT {return wd_.ok();} CONSTCD11 inline weekday_last::weekday_last(const date::weekday& wd) NOEXCEPT : wd_(wd) {} CONSTCD11 inline bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT { return x.weekday() == y.weekday(); } CONSTCD11 inline bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT { return !(x == y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const weekday_last& wdl) { return low_level_fmt(os, wdl.weekday()) << "[last]"; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const weekday_last& wdl) { detail::low_level_fmt(os, wdl); if (!wdl.ok()) os << " is not a valid weekday_last"; return os; } CONSTCD11 inline weekday_last weekday::operator[](last_spec) const NOEXCEPT { return weekday_last{*this}; } // year_month CONSTCD11 inline year_month::year_month(const date::year& y, const date::month& m) NOEXCEPT : y_(y) , m_(m) {} CONSTCD11 inline year year_month::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month::month() const NOEXCEPT {return m_;} CONSTCD11 inline bool year_month::ok() const NOEXCEPT {return y_.ok() && m_.ok();} template CONSTCD14 inline year_month& year_month::operator+=(const months& dm) NOEXCEPT { *this = *this + dm; return *this; } template CONSTCD14 inline year_month& year_month::operator-=(const months& dm) NOEXCEPT { *this = *this - dm; return *this; } CONSTCD14 inline year_month& year_month::operator+=(const years& dy) NOEXCEPT { *this = *this + dy; return *this; } CONSTCD14 inline year_month& year_month::operator-=(const years& dy) NOEXCEPT { *this = *this - dy; return *this; } CONSTCD11 inline bool operator==(const year_month& x, const year_month& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month(); } CONSTCD11 inline bool operator!=(const year_month& x, const year_month& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month& x, const year_month& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month() < y.month())); } CONSTCD11 inline bool operator>(const year_month& x, const year_month& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month& x, const year_month& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month& x, const year_month& y) NOEXCEPT { return !(x < y); } template CONSTCD14 inline year_month operator+(const year_month& ym, const months& dm) NOEXCEPT { auto dmi = static_cast(static_cast(ym.month())) - 1 + dm.count(); auto dy = (dmi >= 0 ? dmi : dmi-11) / 12; dmi = dmi - dy * 12 + 1; return (ym.year() + years(dy)) / month(static_cast(dmi)); } template CONSTCD14 inline year_month operator+(const months& dm, const year_month& ym) NOEXCEPT { return ym + dm; } template CONSTCD14 inline year_month operator-(const year_month& ym, const months& dm) NOEXCEPT { return ym + -dm; } CONSTCD11 inline months operator-(const year_month& x, const year_month& y) NOEXCEPT { return (x.year() - y.year()) + months(static_cast(x.month()) - static_cast(y.month())); } CONSTCD11 inline year_month operator+(const year_month& ym, const years& dy) NOEXCEPT { return (ym.year() + dy) / ym.month(); } CONSTCD11 inline year_month operator+(const years& dy, const year_month& ym) NOEXCEPT { return ym + dy; } CONSTCD11 inline year_month operator-(const year_month& ym, const years& dy) NOEXCEPT { return ym + -dy; } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const year_month& ym) { low_level_fmt(os, ym.year()) << '/'; return low_level_fmt(os, ym.month()); } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month& ym) { detail::low_level_fmt(os, ym); if (!ym.ok()) os << " is not a valid year_month"; return os; } // month_day CONSTCD11 inline month_day::month_day(const date::month& m, const date::day& d) NOEXCEPT : m_(m) , d_(d) {} CONSTCD11 inline date::month month_day::month() const NOEXCEPT {return m_;} CONSTCD11 inline date::day month_day::day() const NOEXCEPT {return d_;} CONSTCD14 inline bool month_day::ok() const NOEXCEPT { CONSTDATA date::day d[] = { date::day(31), date::day(29), date::day(31), date::day(30), date::day(31), date::day(30), date::day(31), date::day(31), date::day(30), date::day(31), date::day(30), date::day(31) }; return m_.ok() && date::day{1} <= d_ && d_ <= d[static_cast(m_)-1]; } CONSTCD11 inline bool operator==(const month_day& x, const month_day& y) NOEXCEPT { return x.month() == y.month() && x.day() == y.day(); } CONSTCD11 inline bool operator!=(const month_day& x, const month_day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month_day& x, const month_day& y) NOEXCEPT { return x.month() < y.month() ? true : (x.month() > y.month() ? false : (x.day() < y.day())); } CONSTCD11 inline bool operator>(const month_day& x, const month_day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month_day& x, const month_day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month_day& x, const month_day& y) NOEXCEPT { return !(x < y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const month_day& md) { low_level_fmt(os, md.month()) << '/'; return low_level_fmt(os, md.day()); } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_day& md) { detail::low_level_fmt(os, md); if (!md.ok()) os << " is not a valid month_day"; return os; } // month_day_last CONSTCD11 inline month month_day_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline bool month_day_last::ok() const NOEXCEPT {return m_.ok();} CONSTCD11 inline month_day_last::month_day_last(const date::month& m) NOEXCEPT : m_(m) {} CONSTCD11 inline bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT { return x.month() == y.month(); } CONSTCD11 inline bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const month_day_last& x, const month_day_last& y) NOEXCEPT { return x.month() < y.month(); } CONSTCD11 inline bool operator>(const month_day_last& x, const month_day_last& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT { return !(x < y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const month_day_last& mdl) { return low_level_fmt(os, mdl.month()) << "/last"; } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_day_last& mdl) { detail::low_level_fmt(os, mdl); if (!mdl.ok()) os << " is not a valid month_day_last"; return os; } // month_weekday CONSTCD11 inline month_weekday::month_weekday(const date::month& m, const date::weekday_indexed& wdi) NOEXCEPT : m_(m) , wdi_(wdi) {} CONSTCD11 inline month month_weekday::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday_indexed month_weekday::weekday_indexed() const NOEXCEPT { return wdi_; } CONSTCD11 inline bool month_weekday::ok() const NOEXCEPT { return m_.ok() && wdi_.ok(); } CONSTCD11 inline bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT { return x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); } CONSTCD11 inline bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT { return !(x == y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const month_weekday& mwd) { low_level_fmt(os, mwd.month()) << '/'; return low_level_fmt(os, mwd.weekday_indexed()); } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday& mwd) { detail::low_level_fmt(os, mwd); if (!mwd.ok()) os << " is not a valid month_weekday"; return os; } // month_weekday_last CONSTCD11 inline month_weekday_last::month_weekday_last(const date::month& m, const date::weekday_last& wdl) NOEXCEPT : m_(m) , wdl_(wdl) {} CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday_last month_weekday_last::weekday_last() const NOEXCEPT { return wdl_; } CONSTCD11 inline bool month_weekday_last::ok() const NOEXCEPT { return m_.ok() && wdl_.ok(); } CONSTCD11 inline bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT { return x.month() == y.month() && x.weekday_last() == y.weekday_last(); } CONSTCD11 inline bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT { return !(x == y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const month_weekday_last& mwdl) { low_level_fmt(os, mwdl.month()) << '/'; return low_level_fmt(os, mwdl.weekday_last()); } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const month_weekday_last& mwdl) { detail::low_level_fmt(os, mwdl); if (!mwdl.ok()) os << " is not a valid month_weekday_last"; return os; } // year_month_day_last CONSTCD11 inline year_month_day_last::year_month_day_last(const date::year& y, const date::month_day_last& mdl) NOEXCEPT : y_(y) , mdl_(mdl) {} template CONSTCD14 inline year_month_day_last& year_month_day_last::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } template CONSTCD14 inline year_month_day_last& year_month_day_last::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_day_last& year_month_day_last::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_day_last::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_day_last::month() const NOEXCEPT {return mdl_.month();} CONSTCD11 inline month_day_last year_month_day_last::month_day_last() const NOEXCEPT { return mdl_; } CONSTCD14 inline day year_month_day_last::day() const NOEXCEPT { CONSTDATA date::day d[] = { date::day(31), date::day(28), date::day(31), date::day(30), date::day(31), date::day(30), date::day(31), date::day(31), date::day(30), date::day(31), date::day(30), date::day(31) }; return (month() != February || !y_.is_leap()) && mdl_.ok() ? d[static_cast(month()) - 1] : date::day{29}; } CONSTCD14 inline year_month_day_last::operator sys_days() const NOEXCEPT { return sys_days(year()/month()/day()); } CONSTCD14 inline year_month_day_last::operator local_days() const NOEXCEPT { return local_days(year()/month()/day()); } CONSTCD11 inline bool year_month_day_last::ok() const NOEXCEPT { return y_.ok() && mdl_.ok(); } CONSTCD11 inline bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return x.year() == y.year() && x.month_day_last() == y.month_day_last(); } CONSTCD11 inline bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month_day_last() < y.month_day_last())); } CONSTCD11 inline bool operator>(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT { return !(x < y); } namespace detail { template std::basic_ostream& low_level_fmt(std::basic_ostream& os, const year_month_day_last& ymdl) { low_level_fmt(os, ymdl.year()) << '/'; return low_level_fmt(os, ymdl.month_day_last()); } } // namespace detail template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day_last& ymdl) { detail::low_level_fmt(os, ymdl); if (!ymdl.ok()) os << " is not a valid year_month_day_last"; return os; } template CONSTCD14 inline year_month_day_last operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT { return (ymdl.year() / ymdl.month() + dm) / last; } template CONSTCD14 inline year_month_day_last operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT { return ymdl + dm; } template CONSTCD14 inline year_month_day_last operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT { return ymdl + (-dm); } CONSTCD11 inline year_month_day_last operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT { return {ymdl.year()+dy, ymdl.month_day_last()}; } CONSTCD11 inline year_month_day_last operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT { return ymdl + dy; } CONSTCD11 inline year_month_day_last operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT { return ymdl + (-dy); } // year_month_day CONSTCD11 inline year_month_day::year_month_day(const date::year& y, const date::month& m, const date::day& d) NOEXCEPT : y_(y) , m_(m) , d_(d) {} CONSTCD14 inline year_month_day::year_month_day(const year_month_day_last& ymdl) NOEXCEPT : y_(ymdl.year()) , m_(ymdl.month()) , d_(ymdl.day()) {} CONSTCD14 inline year_month_day::year_month_day(sys_days dp) NOEXCEPT : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_day::year_month_day(local_days dp) NOEXCEPT : year_month_day(from_days(dp.time_since_epoch())) {} CONSTCD11 inline year year_month_day::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_day::month() const NOEXCEPT {return m_;} CONSTCD11 inline day year_month_day::day() const NOEXCEPT {return d_;} template CONSTCD14 inline year_month_day& year_month_day::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } template CONSTCD14 inline year_month_day& year_month_day::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_day& year_month_day::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD14 inline days year_month_day::to_days() const NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); auto const y = static_cast(y_) - (m_ <= February); auto const m = static_cast(m_); auto const d = static_cast(d_); auto const era = (y >= 0 ? y : y-399) / 400; auto const yoe = static_cast(y - era * 400); // [0, 399] auto const doy = (153*(m > 2 ? m-3 : m+9) + 2)/5 + d-1; // [0, 365] auto const doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096] return days{era * 146097 + static_cast(doe) - 719468}; } CONSTCD14 inline year_month_day::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_day::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD14 inline bool year_month_day::ok() const NOEXCEPT { if (!(y_.ok() && m_.ok())) return false; return date::day{1} <= d_ && d_ <= (y_ / m_ / last).day(); } CONSTCD11 inline bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.day() == y.day(); } CONSTCD11 inline bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(x == y); } CONSTCD11 inline bool operator<(const year_month_day& x, const year_month_day& y) NOEXCEPT { return x.year() < y.year() ? true : (x.year() > y.year() ? false : (x.month() < y.month() ? true : (x.month() > y.month() ? false : (x.day() < y.day())))); } CONSTCD11 inline bool operator>(const year_month_day& x, const year_month_day& y) NOEXCEPT { return y < x; } CONSTCD11 inline bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(y < x); } CONSTCD11 inline bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT { return !(x < y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_day& ymd) { detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.imbue(std::locale::classic()); os << static_cast(ymd.year()) << '-'; os.width(2); os << static_cast(ymd.month()) << '-'; os.width(2); os << static_cast(ymd.day()); if (!ymd.ok()) os << " is not a valid year_month_day"; return os; } CONSTCD14 inline year_month_day year_month_day::from_days(days dp) NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); auto const z = dp.count() + 719468; auto const era = (z >= 0 ? z : z - 146096) / 146097; auto const doe = static_cast(z - era * 146097); // [0, 146096] auto const yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; // [0, 399] auto const y = static_cast(yoe) + era * 400; auto const doy = doe - (365*yoe + yoe/4 - yoe/100); // [0, 365] auto const mp = (5*doy + 2)/153; // [0, 11] auto const d = doy - (153*mp+2)/5 + 1; // [1, 31] auto const m = mp < 10 ? mp+3 : mp-9; // [1, 12] return year_month_day{date::year{y + (m <= 2)}, date::month(m), date::day(d)}; } template CONSTCD14 inline year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT { return (ymd.year() / ymd.month() + dm) / ymd.day(); } template CONSTCD14 inline year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT { return ymd + dm; } template CONSTCD14 inline year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT { return ymd + (-dm); } CONSTCD11 inline year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT { return (ymd.year() + dy) / ymd.month() / ymd.day(); } CONSTCD11 inline year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT { return ymd + dy; } CONSTCD11 inline year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT { return ymd + (-dy); } // year_month_weekday CONSTCD11 inline year_month_weekday::year_month_weekday(const date::year& y, const date::month& m, const date::weekday_indexed& wdi) NOEXCEPT : y_(y) , m_(m) , wdi_(wdi) {} CONSTCD14 inline year_month_weekday::year_month_weekday(const sys_days& dp) NOEXCEPT : year_month_weekday(from_days(dp.time_since_epoch())) {} CONSTCD14 inline year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT : year_month_weekday(from_days(dp.time_since_epoch())) {} template CONSTCD14 inline year_month_weekday& year_month_weekday::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } template CONSTCD14 inline year_month_weekday& year_month_weekday::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_weekday& year_month_weekday::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_weekday::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_weekday::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday year_month_weekday::weekday() const NOEXCEPT { return wdi_.weekday(); } CONSTCD11 inline unsigned year_month_weekday::index() const NOEXCEPT { return wdi_.index(); } CONSTCD11 inline weekday_indexed year_month_weekday::weekday_indexed() const NOEXCEPT { return wdi_; } CONSTCD14 inline year_month_weekday::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_weekday::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD14 inline bool year_month_weekday::ok() const NOEXCEPT { if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) return false; if (wdi_.index() <= 4) return true; auto d2 = wdi_.weekday() - date::weekday(static_cast(y_/m_/1)) + days((wdi_.index()-1)*7 + 1); return static_cast(d2.count()) <= static_cast((y_/m_/last).day()); } CONSTCD14 inline year_month_weekday year_month_weekday::from_days(days d) NOEXCEPT { sys_days dp{d}; auto const wd = date::weekday(dp); auto const ymd = year_month_day(dp); return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; } CONSTCD14 inline days year_month_weekday::to_days() const NOEXCEPT { auto d = sys_days(y_/m_/1); return (d + (wdi_.weekday() - date::weekday(d) + days{(wdi_.index()-1)*7}) ).time_since_epoch(); } CONSTCD11 inline bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); } CONSTCD11 inline bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi) { detail::low_level_fmt(os, ymwdi.year()) << '/'; detail::low_level_fmt(os, ymwdi.month()) << '/'; detail::low_level_fmt(os, ymwdi.weekday_indexed()); if (!ymwdi.ok()) os << " is not a valid year_month_weekday"; return os; } template CONSTCD14 inline year_month_weekday operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT { return (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed(); } template CONSTCD14 inline year_month_weekday operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT { return ymwd + dm; } template CONSTCD14 inline year_month_weekday operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT { return ymwd + (-dm); } CONSTCD11 inline year_month_weekday operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT { return {ymwd.year()+dy, ymwd.month(), ymwd.weekday_indexed()}; } CONSTCD11 inline year_month_weekday operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT { return ymwd + dy; } CONSTCD11 inline year_month_weekday operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT { return ymwd + (-dy); } // year_month_weekday_last CONSTCD11 inline year_month_weekday_last::year_month_weekday_last(const date::year& y, const date::month& m, const date::weekday_last& wdl) NOEXCEPT : y_(y) , m_(m) , wdl_(wdl) {} template CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator+=(const months& m) NOEXCEPT { *this = *this + m; return *this; } template CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator-=(const months& m) NOEXCEPT { *this = *this - m; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator+=(const years& y) NOEXCEPT { *this = *this + y; return *this; } CONSTCD14 inline year_month_weekday_last& year_month_weekday_last::operator-=(const years& y) NOEXCEPT { *this = *this - y; return *this; } CONSTCD11 inline year year_month_weekday_last::year() const NOEXCEPT {return y_;} CONSTCD11 inline month year_month_weekday_last::month() const NOEXCEPT {return m_;} CONSTCD11 inline weekday year_month_weekday_last::weekday() const NOEXCEPT { return wdl_.weekday(); } CONSTCD11 inline weekday_last year_month_weekday_last::weekday_last() const NOEXCEPT { return wdl_; } CONSTCD14 inline year_month_weekday_last::operator sys_days() const NOEXCEPT { return sys_days{to_days()}; } CONSTCD14 inline year_month_weekday_last::operator local_days() const NOEXCEPT { return local_days{to_days()}; } CONSTCD11 inline bool year_month_weekday_last::ok() const NOEXCEPT { return y_.ok() && m_.ok() && wdl_.ok(); } CONSTCD14 inline days year_month_weekday_last::to_days() const NOEXCEPT { auto const d = sys_days(y_/m_/last); return (d - (date::weekday{d} - wdl_.weekday())).time_since_epoch(); } CONSTCD11 inline bool operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT { return x.year() == y.year() && x.month() == y.month() && x.weekday_last() == y.weekday_last(); } CONSTCD11 inline bool operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT { return !(x == y); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl) { detail::low_level_fmt(os, ymwdl.year()) << '/'; detail::low_level_fmt(os, ymwdl.month()) << '/'; detail::low_level_fmt(os, ymwdl.weekday_last()); if (!ymwdl.ok()) os << " is not a valid year_month_weekday_last"; return os; } template CONSTCD14 inline year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT { return (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last(); } template CONSTCD14 inline year_month_weekday_last operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT { return ymwdl + dm; } template CONSTCD14 inline year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT { return ymwdl + (-dm); } CONSTCD11 inline year_month_weekday_last operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT { return {ymwdl.year()+dy, ymwdl.month(), ymwdl.weekday_last()}; } CONSTCD11 inline year_month_weekday_last operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT { return ymwdl + dy; } CONSTCD11 inline year_month_weekday_last operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT { return ymwdl + (-dy); } // year_month from operator/() CONSTCD11 inline year_month operator/(const year& y, const month& m) NOEXCEPT { return {y, m}; } CONSTCD11 inline year_month operator/(const year& y, int m) NOEXCEPT { return y / month(static_cast(m)); } // month_day from operator/() CONSTCD11 inline month_day operator/(const month& m, const day& d) NOEXCEPT { return {m, d}; } CONSTCD11 inline month_day operator/(const day& d, const month& m) NOEXCEPT { return m / d; } CONSTCD11 inline month_day operator/(const month& m, int d) NOEXCEPT { return m / day(static_cast(d)); } CONSTCD11 inline month_day operator/(int m, const day& d) NOEXCEPT { return month(static_cast(m)) / d; } CONSTCD11 inline month_day operator/(const day& d, int m) NOEXCEPT {return m / d;} // month_day_last from operator/() CONSTCD11 inline month_day_last operator/(const month& m, last_spec) NOEXCEPT { return month_day_last{m}; } CONSTCD11 inline month_day_last operator/(last_spec, const month& m) NOEXCEPT { return m/last; } CONSTCD11 inline month_day_last operator/(int m, last_spec) NOEXCEPT { return month(static_cast(m))/last; } CONSTCD11 inline month_day_last operator/(last_spec, int m) NOEXCEPT { return m/last; } // month_weekday from operator/() CONSTCD11 inline month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT { return {m, wdi}; } CONSTCD11 inline month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT { return m / wdi; } CONSTCD11 inline month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT { return month(static_cast(m)) / wdi; } CONSTCD11 inline month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT { return m / wdi; } // month_weekday_last from operator/() CONSTCD11 inline month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT { return {m, wdl}; } CONSTCD11 inline month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT { return m / wdl; } CONSTCD11 inline month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT { return month(static_cast(m)) / wdl; } CONSTCD11 inline month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT { return m / wdl; } // year_month_day from operator/() CONSTCD11 inline year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT { return {ym.year(), ym.month(), d}; } CONSTCD11 inline year_month_day operator/(const year_month& ym, int d) NOEXCEPT { return ym / day(static_cast(d)); } CONSTCD11 inline year_month_day operator/(const year& y, const month_day& md) NOEXCEPT { return y / md.month() / md.day(); } CONSTCD11 inline year_month_day operator/(int y, const month_day& md) NOEXCEPT { return year(y) / md; } CONSTCD11 inline year_month_day operator/(const month_day& md, const year& y) NOEXCEPT { return y / md; } CONSTCD11 inline year_month_day operator/(const month_day& md, int y) NOEXCEPT { return year(y) / md; } // year_month_day_last from operator/() CONSTCD11 inline year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT { return {ym.year(), month_day_last{ym.month()}}; } CONSTCD11 inline year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT { return {y, mdl}; } CONSTCD11 inline year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT { return year(y) / mdl; } CONSTCD11 inline year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT { return y / mdl; } CONSTCD11 inline year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT { return year(y) / mdl; } // year_month_weekday from operator/() CONSTCD11 inline year_month_weekday operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT { return {ym.year(), ym.month(), wdi}; } CONSTCD11 inline year_month_weekday operator/(const year& y, const month_weekday& mwd) NOEXCEPT { return {y, mwd.month(), mwd.weekday_indexed()}; } CONSTCD11 inline year_month_weekday operator/(int y, const month_weekday& mwd) NOEXCEPT { return year(y) / mwd; } CONSTCD11 inline year_month_weekday operator/(const month_weekday& mwd, const year& y) NOEXCEPT { return y / mwd; } CONSTCD11 inline year_month_weekday operator/(const month_weekday& mwd, int y) NOEXCEPT { return year(y) / mwd; } // year_month_weekday_last from operator/() CONSTCD11 inline year_month_weekday_last operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT { return {ym.year(), ym.month(), wdl}; } CONSTCD11 inline year_month_weekday_last operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT { return {y, mwdl.month(), mwdl.weekday_last()}; } CONSTCD11 inline year_month_weekday_last operator/(int y, const month_weekday_last& mwdl) NOEXCEPT { return year(y) / mwdl; } CONSTCD11 inline year_month_weekday_last operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT { return y / mwdl; } CONSTCD11 inline year_month_weekday_last operator/(const month_weekday_last& mwdl, int y) NOEXCEPT { return year(y) / mwdl; } template struct fields; template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const fields& fds, const std::string* abbrev = nullptr, const std::chrono::seconds* offset_sec = nullptr); template std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, fields& fds, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr); // hh_mm_ss namespace detail { struct undocumented {explicit undocumented() = default;}; // width::value is the number of fractional decimal digits in 1/n // width<0>::value and width<1>::value are defined to be 0 // If 1/n takes more than 18 fractional decimal digits, // the result is truncated to 19. // Example: width<2>::value == 1 // Example: width<3>::value == 19 // Example: width<4>::value == 2 // Example: width<10>::value == 1 // Example: width<1000>::value == 3 template struct width { static_assert(d > 0, "width called with zero denominator"); static CONSTDATA unsigned value = 1 + width::value; }; template struct width { static CONSTDATA unsigned value = 0; }; template struct static_pow10 { private: static CONSTDATA std::uint64_t h = static_pow10::value; public: static CONSTDATA std::uint64_t value = h * h * (exp % 2 ? 10 : 1); }; template <> struct static_pow10<0> { static CONSTDATA std::uint64_t value = 1; }; template class decimal_format_seconds { using CT = typename std::common_type::type; using rep = typename CT::rep; static unsigned CONSTDATA trial_width = detail::width::value; public: static unsigned CONSTDATA width = trial_width < 19 ? trial_width : 6u; using precision = std::chrono::duration::value>>; private: std::chrono::seconds s_; precision sub_s_; public: CONSTCD11 decimal_format_seconds() : s_() , sub_s_() {} CONSTCD11 explicit decimal_format_seconds(const Duration& d) NOEXCEPT : s_(std::chrono::duration_cast(d)) , sub_s_(std::chrono::duration_cast(d - s_)) {} CONSTCD14 std::chrono::seconds& seconds() NOEXCEPT {return s_;} CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_;} CONSTCD11 precision subseconds() const NOEXCEPT {return sub_s_;} CONSTCD14 precision to_duration() const NOEXCEPT { return s_ + sub_s_; } CONSTCD11 bool in_conventional_range() const NOEXCEPT { return sub_s_ < std::chrono::seconds{1} && s_ < std::chrono::minutes{1}; } template friend std::basic_ostream& operator<<(std::basic_ostream& os, const decimal_format_seconds& x) { return x.print(os, std::chrono::treat_as_floating_point{}); } template std::basic_ostream& print(std::basic_ostream& os, std::true_type) const { date::detail::save_ostream _(os); std::chrono::duration d = s_ + sub_s_; if (d < std::chrono::seconds{10}) os << '0'; os.precision(width+6); os << std::fixed << d.count(); return os; } template std::basic_ostream& print(std::basic_ostream& os, std::false_type) const { date::detail::save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << s_.count(); if (width > 0) { #if !ONLY_C_LOCALE os << std::use_facet>(os.getloc()).decimal_point(); #else os << '.'; #endif date::detail::save_ostream _s(os); os.imbue(std::locale::classic()); os.width(width); os << sub_s_.count(); } return os; } }; template inline CONSTCD11 typename std::enable_if < std::numeric_limits::is_signed, std::chrono::duration >::type abs(std::chrono::duration d) { return d >= d.zero() ? +d : -d; } template inline CONSTCD11 typename std::enable_if < !std::numeric_limits::is_signed, std::chrono::duration >::type abs(std::chrono::duration d) { return d; } } // namespace detail template class hh_mm_ss { using dfs = detail::decimal_format_seconds::type>; std::chrono::hours h_; std::chrono::minutes m_; dfs s_; bool neg_; public: static unsigned CONSTDATA fractional_width = dfs::width; using precision = typename dfs::precision; CONSTCD11 hh_mm_ss() NOEXCEPT : hh_mm_ss(Duration::zero()) {} CONSTCD11 explicit hh_mm_ss(Duration d) NOEXCEPT : h_(std::chrono::duration_cast(detail::abs(d))) , m_(std::chrono::duration_cast(detail::abs(d)) - h_) , s_(detail::abs(d) - h_ - m_) , neg_(d < Duration::zero()) {} CONSTCD11 std::chrono::hours hours() const NOEXCEPT {return h_;} CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT {return m_;} CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_.seconds();} CONSTCD14 std::chrono::seconds& seconds(detail::undocumented) NOEXCEPT {return s_.seconds();} CONSTCD11 precision subseconds() const NOEXCEPT {return s_.subseconds();} CONSTCD11 bool is_negative() const NOEXCEPT {return neg_;} CONSTCD11 explicit operator precision() const NOEXCEPT {return to_duration();} CONSTCD11 precision to_duration() const NOEXCEPT {return (s_.to_duration() + m_ + h_) * (1-2*neg_);} CONSTCD11 bool in_conventional_range() const NOEXCEPT { return !neg_ && h_ < days{1} && m_ < std::chrono::hours{1} && s_.in_conventional_range(); } private: template friend std::basic_ostream& operator<<(std::basic_ostream& os, hh_mm_ss const& tod) { if (tod.is_negative()) os << '-'; if (tod.h_ < std::chrono::hours{10}) os << '0'; os << tod.h_.count() << ':'; if (tod.m_ < std::chrono::minutes{10}) os << '0'; os << tod.m_.count() << ':' << tod.s_; return os; } template friend std::basic_ostream& date::to_stream(std::basic_ostream& os, const CharT* fmt, const fields& fds, const std::string* abbrev, const std::chrono::seconds* offset_sec); template friend std::basic_istream& date::from_stream(std::basic_istream& is, const CharT* fmt, fields& fds, std::basic_string* abbrev, std::chrono::minutes* offset); }; inline CONSTCD14 bool is_am(std::chrono::hours const& h) NOEXCEPT { using std::chrono::hours; return hours{0} <= h && h < hours{12}; } inline CONSTCD14 bool is_pm(std::chrono::hours const& h) NOEXCEPT { using std::chrono::hours; return hours{12} <= h && h < hours{24}; } inline CONSTCD14 std::chrono::hours make12(std::chrono::hours h) NOEXCEPT { using std::chrono::hours; if (h < hours{12}) { if (h == hours{0}) h = hours{12}; } else { if (h != hours{12}) h = h - hours{12}; } return h; } inline CONSTCD14 std::chrono::hours make24(std::chrono::hours h, bool is_pm) NOEXCEPT { using std::chrono::hours; if (is_pm) { if (h != hours{12}) h = h + hours{12}; } else if (h == hours{12}) h = hours{0}; return h; } template using time_of_day = hh_mm_ss; template CONSTCD11 inline hh_mm_ss> make_time(const std::chrono::duration& d) { return hh_mm_ss>(d); } template inline typename std::enable_if < !std::is_convertible::value, std::basic_ostream& >::type operator<<(std::basic_ostream& os, const sys_time& tp) { auto const dp = date::floor(tp); return os << year_month_day(dp) << ' ' << make_time(tp-dp); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const sys_days& dp) { return os << year_month_day(dp); } template inline std::basic_ostream& operator<<(std::basic_ostream& os, const local_time& ut) { return (os << sys_time{ut.time_since_epoch()}); } namespace detail { template class string_literal; template inline CONSTCD14 string_literal::type, N1 + N2 - 1> operator+(const string_literal& x, const string_literal& y) NOEXCEPT; template class string_literal { CharT p_[N]; CONSTCD11 string_literal() NOEXCEPT : p_{} {} public: using const_iterator = const CharT*; string_literal(string_literal const&) = default; string_literal& operator=(string_literal const&) = delete; template ::type> CONSTCD11 string_literal(CharT c) NOEXCEPT : p_{c} { } template ::type> CONSTCD11 string_literal(CharT c1, CharT c2) NOEXCEPT : p_{c1, c2} { } template ::type> CONSTCD11 string_literal(CharT c1, CharT c2, CharT c3) NOEXCEPT : p_{c1, c2, c3} { } CONSTCD14 string_literal(const CharT(&a)[N]) NOEXCEPT : p_{} { for (std::size_t i = 0; i < N; ++i) p_[i] = a[i]; } template ::type> CONSTCD14 string_literal(const char(&a)[N]) NOEXCEPT : p_{} { for (std::size_t i = 0; i < N; ++i) p_[i] = a[i]; } template ::value>::type> CONSTCD14 string_literal(string_literal const& a) NOEXCEPT : p_{} { for (std::size_t i = 0; i < N; ++i) p_[i] = a[i]; } CONSTCD11 const CharT* data() const NOEXCEPT {return p_;} CONSTCD11 std::size_t size() const NOEXCEPT {return N-1;} CONSTCD11 const_iterator begin() const NOEXCEPT {return p_;} CONSTCD11 const_iterator end() const NOEXCEPT {return p_ + N-1;} CONSTCD11 CharT const& operator[](std::size_t n) const NOEXCEPT { return p_[n]; } template friend std::basic_ostream& operator<<(std::basic_ostream& os, const string_literal& s) { return os << s.p_; } template friend CONSTCD14 string_literal::type, N1 + N2 - 1> operator+(const string_literal& x, const string_literal& y) NOEXCEPT; }; template CONSTCD11 inline string_literal operator+(const string_literal& x, const string_literal& y) NOEXCEPT { return string_literal(x[0], y[0]); } template CONSTCD11 inline string_literal operator+(const string_literal& x, const string_literal& y) NOEXCEPT { return string_literal(x[0], x[1], y[0]); } template CONSTCD14 inline string_literal::type, N1 + N2 - 1> operator+(const string_literal& x, const string_literal& y) NOEXCEPT { using CT = typename std::conditional::type; string_literal r; std::size_t i = 0; for (; i < N1-1; ++i) r.p_[i] = CT(x.p_[i]); for (std::size_t j = 0; j < N2; ++j, ++i) r.p_[i] = CT(y.p_[j]); return r; } template inline std::basic_string operator+(std::basic_string x, const string_literal& y) { x.append(y.data(), y.size()); return x; } #if __cplusplus >= 201402 && (!defined(__EDG_VERSION__) || __EDG_VERSION__ > 411) \ && (!defined(__SUNPRO_CC) || __SUNPRO_CC > 0x5150) template ::value || std::is_same::value || std::is_same::value || std::is_same::value>> CONSTCD14 inline string_literal msl(CharT c) NOEXCEPT { return string_literal{c}; } CONSTCD14 inline std::size_t to_string_len(std::intmax_t i) { std::size_t r = 0; do { i /= 10; ++r; } while (i > 0); return r; } template CONSTCD14 inline std::enable_if_t < N < 10, string_literal > msl() NOEXCEPT { return msl(char(N % 10 + '0')); } template CONSTCD14 inline std::enable_if_t < 10 <= N, string_literal > msl() NOEXCEPT { return msl() + msl(char(N % 10 + '0')); } template CONSTCD14 inline std::enable_if_t < std::ratio::type::den != 1, string_literal::type::num) + to_string_len(std::ratio::type::den) + 4> > msl(std::ratio) NOEXCEPT { using R = typename std::ratio::type; return msl(CharT{'['}) + msl() + msl(CharT{'/'}) + msl() + msl(CharT{']'}); } template CONSTCD14 inline std::enable_if_t < std::ratio::type::den == 1, string_literal::type::num) + 3> > msl(std::ratio) NOEXCEPT { using R = typename std::ratio::type; return msl(CharT{'['}) + msl() + msl(CharT{']'}); } #else // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= 411) inline std::string to_string(std::uint64_t x) { return std::to_string(x); } template inline std::basic_string to_string(std::uint64_t x) { auto y = std::to_string(x); return std::basic_string(y.begin(), y.end()); } template inline typename std::enable_if < std::ratio::type::den != 1, std::basic_string >::type msl(std::ratio) { using R = typename std::ratio::type; return std::basic_string(1, '[') + to_string(R::num) + CharT{'/'} + to_string(R::den) + CharT{']'}; } template inline typename std::enable_if < std::ratio::type::den == 1, std::basic_string >::type msl(std::ratio) { using R = typename std::ratio::type; return std::basic_string(1, '[') + to_string(R::num) + CharT{']'}; } #endif // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= 411) template CONSTCD11 inline string_literal msl(std::atto) NOEXCEPT { return string_literal{'a'}; } template CONSTCD11 inline string_literal msl(std::femto) NOEXCEPT { return string_literal{'f'}; } template CONSTCD11 inline string_literal msl(std::pico) NOEXCEPT { return string_literal{'p'}; } template CONSTCD11 inline string_literal msl(std::nano) NOEXCEPT { return string_literal{'n'}; } template CONSTCD11 inline typename std::enable_if < std::is_same::value, string_literal >::type msl(std::micro) NOEXCEPT { return string_literal{'\xC2', '\xB5'}; } template CONSTCD11 inline typename std::enable_if < !std::is_same::value, string_literal >::type msl(std::micro) NOEXCEPT { return string_literal{CharT{static_cast('\xB5')}}; } template CONSTCD11 inline string_literal msl(std::milli) NOEXCEPT { return string_literal{'m'}; } template CONSTCD11 inline string_literal msl(std::centi) NOEXCEPT { return string_literal{'c'}; } template CONSTCD11 inline string_literal msl(std::deca) NOEXCEPT { return string_literal{'d', 'a'}; } template CONSTCD11 inline string_literal msl(std::deci) NOEXCEPT { return string_literal{'d'}; } template CONSTCD11 inline string_literal msl(std::hecto) NOEXCEPT { return string_literal{'h'}; } template CONSTCD11 inline string_literal msl(std::kilo) NOEXCEPT { return string_literal{'k'}; } template CONSTCD11 inline string_literal msl(std::mega) NOEXCEPT { return string_literal{'M'}; } template CONSTCD11 inline string_literal msl(std::giga) NOEXCEPT { return string_literal{'G'}; } template CONSTCD11 inline string_literal msl(std::tera) NOEXCEPT { return string_literal{'T'}; } template CONSTCD11 inline string_literal msl(std::peta) NOEXCEPT { return string_literal{'P'}; } template CONSTCD11 inline string_literal msl(std::exa) NOEXCEPT { return string_literal{'E'}; } template CONSTCD11 inline auto get_units(Period p) -> decltype(msl(p) + string_literal{'s'}) { return msl(p) + string_literal{'s'}; } template CONSTCD11 inline string_literal get_units(std::ratio<1>) { return string_literal{'s'}; } template CONSTCD11 inline string_literal get_units(std::ratio<3600>) { return string_literal{'h'}; } template CONSTCD11 inline string_literal get_units(std::ratio<60>) { return string_literal{'m', 'i', 'n'}; } template CONSTCD11 inline string_literal get_units(std::ratio<86400>) { return string_literal{'d'}; } template > struct make_string; template <> struct make_string { template static std::string from(Rep n) { return std::to_string(n); } }; template struct make_string { template static std::basic_string from(Rep n) { auto s = std::to_string(n); return std::basic_string(s.begin(), s.end()); } }; template <> struct make_string { template static std::wstring from(Rep n) { return std::to_wstring(n); } }; template struct make_string { template static std::basic_string from(Rep n) { auto s = std::to_wstring(n); return std::basic_string(s.begin(), s.end()); } }; } // namespace detail // to_stream CONSTDATA year nanyear{-32768}; template struct fields { year_month_day ymd{nanyear/0/0}; weekday wd{8u}; hh_mm_ss tod{}; bool has_tod = false; #if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ <= 409) fields() : ymd{nanyear/0/0}, wd{8u}, tod{}, has_tod{false} {} #else fields() = default; #endif fields(year_month_day ymd_) : ymd(ymd_) {} fields(weekday wd_) : wd(wd_) {} fields(hh_mm_ss tod_) : tod(tod_), has_tod(true) {} fields(year_month_day ymd_, weekday wd_) : ymd(ymd_), wd(wd_) {} fields(year_month_day ymd_, hh_mm_ss tod_) : ymd(ymd_), tod(tod_), has_tod(true) {} fields(weekday wd_, hh_mm_ss tod_) : wd(wd_), tod(tod_), has_tod(true) {} fields(year_month_day ymd_, weekday wd_, hh_mm_ss tod_) : ymd(ymd_) , wd(wd_) , tod(tod_) , has_tod(true) {} }; namespace detail { template unsigned extract_weekday(std::basic_ostream& os, const fields& fds) { if (!fds.ymd.ok() && !fds.wd.ok()) { // fds does not contain a valid weekday os.setstate(std::ios::failbit); return 8; } weekday wd; if (fds.ymd.ok()) { wd = weekday{sys_days(fds.ymd)}; if (fds.wd.ok() && wd != fds.wd) { // fds.ymd and fds.wd are inconsistent os.setstate(std::ios::failbit); return 8; } } else wd = fds.wd; return static_cast((wd - Sunday).count()); } template unsigned extract_month(std::basic_ostream& os, const fields& fds) { if (!fds.ymd.month().ok()) { // fds does not contain a valid month os.setstate(std::ios::failbit); return 0; } return static_cast(fds.ymd.month()); } } // namespace detail #if ONLY_C_LOCALE namespace detail { inline std::pair weekday_names() { static const std::string nm[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); } inline std::pair month_names() { static const std::string nm[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); } inline std::pair ampm_names() { static const std::string nm[] = { "AM", "PM" }; return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); } template FwdIter scan_keyword(std::basic_istream& is, FwdIter kb, FwdIter ke) { size_t nkw = static_cast(std::distance(kb, ke)); const unsigned char doesnt_match = '\0'; const unsigned char might_match = '\1'; const unsigned char does_match = '\2'; unsigned char statbuf[100]; unsigned char* status = statbuf; std::unique_ptr stat_hold(0, free); if (nkw > sizeof(statbuf)) { status = (unsigned char*)std::malloc(nkw); if (status == nullptr) throw std::bad_alloc(); stat_hold.reset(status); } size_t n_might_match = nkw; // At this point, any keyword might match size_t n_does_match = 0; // but none of them definitely do // Initialize all statuses to might_match, except for "" keywords are does_match unsigned char* st = status; for (auto ky = kb; ky != ke; ++ky, ++st) { if (!ky->empty()) *st = might_match; else { *st = does_match; --n_might_match; ++n_does_match; } } // While there might be a match, test keywords against the next CharT for (size_t indx = 0; is && n_might_match > 0; ++indx) { // Peek at the next CharT but don't consume it auto ic = is.peek(); if (ic == EOF) { is.setstate(std::ios::eofbit); break; } auto c = static_cast(toupper(static_cast(ic))); bool consume = false; // For each keyword which might match, see if the indx character is c // If a match if found, consume c // If a match is found, and that is the last character in the keyword, // then that keyword matches. // If the keyword doesn't match this character, then change the keyword // to doesn't match st = status; for (auto ky = kb; ky != ke; ++ky, ++st) { if (*st == might_match) { if (c == static_cast(toupper(static_cast((*ky)[indx])))) { consume = true; if (ky->size() == indx+1) { *st = does_match; --n_might_match; ++n_does_match; } } else { *st = doesnt_match; --n_might_match; } } } // consume if we matched a character if (consume) { (void)is.get(); // If we consumed a character and there might be a matched keyword that // was marked matched on a previous iteration, then such keywords // are now marked as not matching. if (n_might_match + n_does_match > 1) { st = status; for (auto ky = kb; ky != ke; ++ky, ++st) { if (*st == does_match && ky->size() != indx+1) { *st = doesnt_match; --n_does_match; } } } } } // We've exited the loop because we hit eof and/or we have no more "might matches". // Return the first matching result for (st = status; kb != ke; ++kb, ++st) if (*st == does_match) break; if (kb == ke) is.setstate(std::ios::failbit); return kb; } } // namespace detail #endif // ONLY_C_LOCALE template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const fields& fds, const std::string* abbrev, const std::chrono::seconds* offset_sec) { #if ONLY_C_LOCALE using detail::weekday_names; using detail::month_names; using detail::ampm_names; #endif using detail::save_ostream; using detail::get_units; using detail::extract_weekday; using detail::extract_month; using std::ios; using std::chrono::duration_cast; using std::chrono::seconds; using std::chrono::minutes; using std::chrono::hours; date::detail::save_ostream ss(os); os.fill(' '); os.flags(std::ios::skipws | std::ios::dec); os.width(0); tm tm{}; bool insert_negative = fds.has_tod && fds.tod.to_duration() < Duration::zero(); #if !ONLY_C_LOCALE auto& facet = std::use_facet>(os.getloc()); #endif const CharT* command = nullptr; CharT modified = CharT{}; for (; *fmt; ++fmt) { switch (*fmt) { case 'a': case 'A': if (command) { if (modified == CharT{}) { tm.tm_wday = static_cast(extract_weekday(os, fds)); if (os.fail()) return os; #if !ONLY_C_LOCALE const CharT f[] = {'%', *fmt}; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); #else // ONLY_C_LOCALE os << weekday_names().first[tm.tm_wday+7*(*fmt == 'a')]; #endif // ONLY_C_LOCALE } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'b': case 'B': case 'h': if (command) { if (modified == CharT{}) { tm.tm_mon = static_cast(extract_month(os, fds)) - 1; #if !ONLY_C_LOCALE const CharT f[] = {'%', *fmt}; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); #else // ONLY_C_LOCALE os << month_names().first[tm.tm_mon+12*(*fmt != 'B')]; #endif // ONLY_C_LOCALE } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'c': case 'x': if (command) { if (modified == CharT{'O'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.ok()) os.setstate(std::ios::failbit); if (*fmt == 'c' && !fds.has_tod) os.setstate(std::ios::failbit); #if !ONLY_C_LOCALE tm = std::tm{}; auto const& ymd = fds.ymd; auto ld = local_days(ymd); if (*fmt == 'c') { tm.tm_sec = static_cast(fds.tod.seconds().count()); tm.tm_min = static_cast(fds.tod.minutes().count()); tm.tm_hour = static_cast(fds.tod.hours().count()); } tm.tm_mday = static_cast(static_cast(ymd.day())); tm.tm_mon = static_cast(extract_month(os, fds) - 1); tm.tm_year = static_cast(ymd.year()) - 1900; tm.tm_wday = static_cast(extract_weekday(os, fds)); if (os.fail()) return os; tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); CharT f[3] = {'%'}; auto fe = std::begin(f) + 1; if (modified == CharT{'E'}) *fe++ = modified; *fe++ = *fmt; facet.put(os, os, os.fill(), &tm, std::begin(f), fe); #else // ONLY_C_LOCALE if (*fmt == 'c') { auto wd = static_cast(extract_weekday(os, fds)); os << weekday_names().first[static_cast(wd)+7] << ' '; os << month_names().first[extract_month(os, fds)-1+12] << ' '; auto d = static_cast(static_cast(fds.ymd.day())); if (d < 10) os << ' '; os << d << ' ' << make_time(duration_cast(fds.tod.to_duration())) << ' ' << fds.ymd.year(); } else // *fmt == 'x' { auto const& ymd = fds.ymd; save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << static_cast(ymd.month()) << CharT{'/'}; os.width(2); os << static_cast(ymd.day()) << CharT{'/'}; os.width(2); os << static_cast(ymd.year()) % 100; } #endif // ONLY_C_LOCALE } command = nullptr; modified = CharT{}; } else os << *fmt; break; case 'C': if (command) { if (modified == CharT{'O'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.year().ok()) os.setstate(std::ios::failbit); auto y = static_cast(fds.ymd.year()); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); if (y >= 0) { os.width(2); os << y/100; } else { os << CharT{'-'}; os.width(2); os << -(y-99)/100; } } #if !ONLY_C_LOCALE else if (modified == CharT{'E'}) { tm.tm_year = y - 1900; CharT f[3] = {'%', 'E', 'C'}; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } command = nullptr; modified = CharT{}; } else os << *fmt; break; case 'd': case 'e': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.day().ok()) os.setstate(std::ios::failbit); auto d = static_cast(static_cast(fds.ymd.day())); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { save_ostream _(os); if (*fmt == CharT{'d'}) os.fill('0'); else os.fill(' '); os.flags(std::ios::dec | std::ios::right); os.width(2); os << d; } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { tm.tm_mday = d; CharT f[3] = {'%', 'O', *fmt}; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } command = nullptr; modified = CharT{}; } else os << *fmt; break; case 'D': if (command) { if (modified == CharT{}) { if (!fds.ymd.ok()) os.setstate(std::ios::failbit); auto const& ymd = fds.ymd; save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << static_cast(ymd.month()) << CharT{'/'}; os.width(2); os << static_cast(ymd.day()) << CharT{'/'}; os.width(2); os << static_cast(ymd.year()) % 100; } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'F': if (command) { if (modified == CharT{}) { if (!fds.ymd.ok()) os.setstate(std::ios::failbit); auto const& ymd = fds.ymd; save_ostream _(os); os.imbue(std::locale::classic()); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(4); os << static_cast(ymd.year()) << CharT{'-'}; os.width(2); os << static_cast(ymd.month()) << CharT{'-'}; os.width(2); os << static_cast(ymd.day()); } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'g': case 'G': if (command) { if (modified == CharT{}) { if (!fds.ymd.ok()) os.setstate(std::ios::failbit); auto ld = local_days(fds.ymd); auto y = year_month_day{ld + days{3}}.year(); auto start = local_days((y-years{1})/December/Thursday[last]) + (Monday-Thursday); if (ld < start) --y; if (*fmt == CharT{'G'}) os << y; else { save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(2); os << std::abs(static_cast(y)) % 100; } } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'H': case 'I': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.has_tod) os.setstate(std::ios::failbit); if (insert_negative) { os << '-'; insert_negative = false; } auto hms = fds.tod; #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { auto h = *fmt == CharT{'I'} ? date::make12(hms.hours()) : hms.hours(); if (h < hours{10}) os << CharT{'0'}; os << h.count(); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_hour = static_cast(hms.hours().count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'j': if (command) { if (modified == CharT{}) { if (fds.ymd.ok() || fds.has_tod) { days doy; if (fds.ymd.ok()) { auto ld = local_days(fds.ymd); auto y = fds.ymd.year(); doy = ld - local_days(y/January/1) + days{1}; } else { doy = duration_cast(fds.tod.to_duration()); } save_ostream _(os); os.fill('0'); os.flags(std::ios::dec | std::ios::right); os.width(3); os << doy.count(); } else { os.setstate(std::ios::failbit); } } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'm': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.month().ok()) os.setstate(std::ios::failbit); auto m = static_cast(fds.ymd.month()); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { if (m < 10) os << CharT{'0'}; os << m; } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_mon = static_cast(m-1); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'M': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.has_tod) os.setstate(std::ios::failbit); if (insert_negative) { os << '-'; insert_negative = false; } #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { if (fds.tod.minutes() < minutes{10}) os << CharT{'0'}; os << fds.tod.minutes().count(); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_min = static_cast(fds.tod.minutes().count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'n': if (command) { if (modified == CharT{}) os << CharT{'\n'}; else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'p': if (command) { if (modified == CharT{}) { if (!fds.has_tod) os.setstate(std::ios::failbit); #if !ONLY_C_LOCALE const CharT f[] = {'%', *fmt}; tm.tm_hour = static_cast(fds.tod.hours().count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); #else if (date::is_am(fds.tod.hours())) os << ampm_names().first[0]; else os << ampm_names().first[1]; #endif } else { os << CharT{'%'} << modified << *fmt; } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'Q': case 'q': if (command) { if (modified == CharT{}) { if (!fds.has_tod) os.setstate(std::ios::failbit); auto d = fds.tod.to_duration(); if (*fmt == 'q') os << get_units(typename decltype(d)::period::type{}); else os << d.count(); } else { os << CharT{'%'} << modified << *fmt; } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'r': if (command) { if (modified == CharT{}) { if (!fds.has_tod) os.setstate(std::ios::failbit); #if !ONLY_C_LOCALE const CharT f[] = {'%', *fmt}; tm.tm_hour = static_cast(fds.tod.hours().count()); tm.tm_min = static_cast(fds.tod.minutes().count()); tm.tm_sec = static_cast(fds.tod.seconds().count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); #else hh_mm_ss tod(duration_cast(fds.tod.to_duration())); save_ostream _(os); os.fill('0'); os.width(2); os << date::make12(tod.hours()).count() << CharT{':'}; os.width(2); os << tod.minutes().count() << CharT{':'}; os.width(2); os << tod.seconds().count() << CharT{' '}; if (date::is_am(tod.hours())) os << ampm_names().first[0]; else os << ampm_names().first[1]; #endif } else { os << CharT{'%'} << modified << *fmt; } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'R': if (command) { if (modified == CharT{}) { if (!fds.has_tod) os.setstate(std::ios::failbit); if (fds.tod.hours() < hours{10}) os << CharT{'0'}; os << fds.tod.hours().count() << CharT{':'}; if (fds.tod.minutes() < minutes{10}) os << CharT{'0'}; os << fds.tod.minutes().count(); } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'S': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.has_tod) os.setstate(std::ios::failbit); if (insert_negative) { os << '-'; insert_negative = false; } #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { os << fds.tod.s_; } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_sec = static_cast(fds.tod.s_.seconds().count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 't': if (command) { if (modified == CharT{}) os << CharT{'\t'}; else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'T': if (command) { if (modified == CharT{}) { if (!fds.has_tod) os.setstate(std::ios::failbit); os << fds.tod; } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'u': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { auto wd = extract_weekday(os, fds); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { os << (wd != 0 ? wd : 7u); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_wday = static_cast(wd); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'U': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { auto const& ymd = fds.ymd; if (!ymd.ok()) os.setstate(std::ios::failbit); auto ld = local_days(ymd); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { auto st = local_days(Sunday[1]/January/ymd.year()); if (ld < st) os << CharT{'0'} << CharT{'0'}; else { auto wn = duration_cast(ld - st).count() + 1; if (wn < 10) os << CharT{'0'}; os << wn; } } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_year = static_cast(ymd.year()) - 1900; tm.tm_wday = static_cast(extract_weekday(os, fds)); if (os.fail()) return os; tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'V': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.ok()) os.setstate(std::ios::failbit); auto ld = local_days(fds.ymd); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { auto y = year_month_day{ld + days{3}}.year(); auto st = local_days((y-years{1})/12/Thursday[last]) + (Monday-Thursday); if (ld < st) { --y; st = local_days((y - years{1})/12/Thursday[last]) + (Monday-Thursday); } auto wn = duration_cast(ld - st).count() + 1; if (wn < 10) os << CharT{'0'}; os << wn; } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; auto const& ymd = fds.ymd; tm.tm_year = static_cast(ymd.year()) - 1900; tm.tm_wday = static_cast(extract_weekday(os, fds)); if (os.fail()) return os; tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'w': if (command) { auto wd = extract_weekday(os, fds); if (os.fail()) return os; #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { os << wd; } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_wday = static_cast(wd); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif else { os << CharT{'%'} << modified << *fmt; } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'W': if (command) { if (modified == CharT{'E'}) os << CharT{'%'} << modified << *fmt; else { auto const& ymd = fds.ymd; if (!ymd.ok()) os.setstate(std::ios::failbit); auto ld = local_days(ymd); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { auto st = local_days(Monday[1]/January/ymd.year()); if (ld < st) os << CharT{'0'} << CharT{'0'}; else { auto wn = duration_cast(ld - st).count() + 1; if (wn < 10) os << CharT{'0'}; os << wn; } } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_year = static_cast(ymd.year()) - 1900; tm.tm_wday = static_cast(extract_weekday(os, fds)); if (os.fail()) return os; tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'X': if (command) { if (modified == CharT{'O'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.has_tod) os.setstate(std::ios::failbit); #if !ONLY_C_LOCALE tm = std::tm{}; tm.tm_sec = static_cast(fds.tod.seconds().count()); tm.tm_min = static_cast(fds.tod.minutes().count()); tm.tm_hour = static_cast(fds.tod.hours().count()); CharT f[3] = {'%'}; auto fe = std::begin(f) + 1; if (modified == CharT{'E'}) *fe++ = modified; *fe++ = *fmt; facet.put(os, os, os.fill(), &tm, std::begin(f), fe); #else os << fds.tod; #endif } command = nullptr; modified = CharT{}; } else os << *fmt; break; case 'y': if (command) { if (!fds.ymd.year().ok()) os.setstate(std::ios::failbit); auto y = static_cast(fds.ymd.year()); #if !ONLY_C_LOCALE if (modified == CharT{}) { #endif y = std::abs(y) % 100; if (y < 10) os << CharT{'0'}; os << y; #if !ONLY_C_LOCALE } else { const CharT f[] = {'%', modified, *fmt}; tm.tm_year = y - 1900; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'Y': if (command) { if (modified == CharT{'O'}) os << CharT{'%'} << modified << *fmt; else { if (!fds.ymd.year().ok()) os.setstate(std::ios::failbit); auto y = fds.ymd.year(); #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { save_ostream _(os); os.imbue(std::locale::classic()); os << y; } #if !ONLY_C_LOCALE else if (modified == CharT{'E'}) { const CharT f[] = {'%', modified, *fmt}; tm.tm_year = static_cast(y) - 1900; facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); } #endif } modified = CharT{}; command = nullptr; } else os << *fmt; break; case 'z': if (command) { if (offset_sec == nullptr) { // Can not format %z with unknown offset os.setstate(ios::failbit); return os; } auto m = duration_cast(*offset_sec); auto neg = m < minutes{0}; m = date::abs(m); auto h = duration_cast(m); m -= h; if (neg) os << CharT{'-'}; else os << CharT{'+'}; if (h < hours{10}) os << CharT{'0'}; os << h.count(); if (modified != CharT{}) os << CharT{':'}; if (m < minutes{10}) os << CharT{'0'}; os << m.count(); command = nullptr; modified = CharT{}; } else os << *fmt; break; case 'Z': if (command) { if (modified == CharT{}) { if (abbrev == nullptr) { // Can not format %Z with unknown time_zone os.setstate(ios::failbit); return os; } for (auto c : *abbrev) os << CharT(c); } else { os << CharT{'%'} << modified << *fmt; modified = CharT{}; } command = nullptr; } else os << *fmt; break; case 'E': case 'O': if (command) { if (modified == CharT{}) { modified = *fmt; } else { os << CharT{'%'} << modified << *fmt; command = nullptr; modified = CharT{}; } } else os << *fmt; break; case '%': if (command) { if (modified == CharT{}) { os << CharT{'%'}; command = nullptr; } else { os << CharT{'%'} << modified << CharT{'%'}; command = nullptr; modified = CharT{}; } } else command = fmt; break; default: if (command) { os << CharT{'%'}; command = nullptr; } if (modified != CharT{}) { os << modified; modified = CharT{}; } os << *fmt; break; } } if (command) os << CharT{'%'}; if (modified != CharT{}) os << modified; return os; } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const year& y) { using CT = std::chrono::seconds; fields fds{y/0/0}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const month& m) { using CT = std::chrono::seconds; fields fds{m/0/nanyear}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const day& d) { using CT = std::chrono::seconds; fields fds{d/0/nanyear}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const weekday& wd) { using CT = std::chrono::seconds; fields fds{wd}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const year_month& ym) { using CT = std::chrono::seconds; fields fds{ym/0}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const month_day& md) { using CT = std::chrono::seconds; fields fds{md/nanyear}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const year_month_day& ymd) { using CT = std::chrono::seconds; fields fds{ymd}; return to_stream(os, fmt, fds); } template inline std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const std::chrono::duration& d) { using Duration = std::chrono::duration; using CT = typename std::common_type::type; fields fds{hh_mm_ss{d}}; return to_stream(os, fmt, fds); } template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const local_time& tp, const std::string* abbrev = nullptr, const std::chrono::seconds* offset_sec = nullptr) { using CT = typename std::common_type::type; auto ld = std::chrono::time_point_cast(tp); fields fds; if (ld <= tp) fds = fields{year_month_day{ld}, hh_mm_ss{tp-local_seconds{ld}}}; else fds = fields{year_month_day{ld - days{1}}, hh_mm_ss{days{1} - (local_seconds{ld} - tp)}}; return to_stream(os, fmt, fds, abbrev, offset_sec); } template std::basic_ostream& to_stream(std::basic_ostream& os, const CharT* fmt, const sys_time& tp) { using std::chrono::seconds; using CT = typename std::common_type::type; const std::string abbrev("UTC"); CONSTDATA seconds offset{0}; auto sd = std::chrono::time_point_cast(tp); fields fds; if (sd <= tp) fds = fields{year_month_day{sd}, hh_mm_ss{tp-sys_seconds{sd}}}; else fds = fields{year_month_day{sd - days{1}}, hh_mm_ss{days{1} - (sys_seconds{sd} - tp)}}; return to_stream(os, fmt, fds, &abbrev, &offset); } // format template auto format(const std::locale& loc, const CharT* fmt, const Streamable& tp) -> decltype(to_stream(std::declval&>(), fmt, tp), std::basic_string{}) { std::basic_ostringstream os; os.exceptions(std::ios::failbit | std::ios::badbit); os.imbue(loc); to_stream(os, fmt, tp); return os.str(); } template auto format(const CharT* fmt, const Streamable& tp) -> decltype(to_stream(std::declval&>(), fmt, tp), std::basic_string{}) { std::basic_ostringstream os; os.exceptions(std::ios::failbit | std::ios::badbit); to_stream(os, fmt, tp); return os.str(); } template auto format(const std::locale& loc, const std::basic_string& fmt, const Streamable& tp) -> decltype(to_stream(std::declval&>(), fmt.c_str(), tp), std::basic_string{}) { std::basic_ostringstream os; os.exceptions(std::ios::failbit | std::ios::badbit); os.imbue(loc); to_stream(os, fmt.c_str(), tp); return os.str(); } template auto format(const std::basic_string& fmt, const Streamable& tp) -> decltype(to_stream(std::declval&>(), fmt.c_str(), tp), std::basic_string{}) { std::basic_ostringstream os; os.exceptions(std::ios::failbit | std::ios::badbit); to_stream(os, fmt.c_str(), tp); return os.str(); } // parse namespace detail { template bool read_char(std::basic_istream& is, CharT fmt, std::ios::iostate& err) { auto ic = is.get(); if (Traits::eq_int_type(ic, Traits::eof()) || !Traits::eq(Traits::to_char_type(ic), fmt)) { err |= std::ios::failbit; is.setstate(std::ios::failbit); return false; } return true; } template unsigned read_unsigned(std::basic_istream& is, unsigned m = 1, unsigned M = 10) { unsigned x = 0; unsigned count = 0; while (true) { auto ic = is.peek(); if (Traits::eq_int_type(ic, Traits::eof())) break; auto c = static_cast(Traits::to_char_type(ic)); if (!('0' <= c && c <= '9')) break; (void)is.get(); ++count; x = 10*x + static_cast(c - '0'); if (count == M) break; } if (count < m) is.setstate(std::ios::failbit); return x; } template int read_signed(std::basic_istream& is, unsigned m = 1, unsigned M = 10) { auto ic = is.peek(); if (!Traits::eq_int_type(ic, Traits::eof())) { auto c = static_cast(Traits::to_char_type(ic)); if (('0' <= c && c <= '9') || c == '-' || c == '+') { if (c == '-' || c == '+') (void)is.get(); auto x = static_cast(read_unsigned(is, std::max(m, 1u), M)); if (!is.fail()) { if (c == '-') x = -x; return x; } } } if (m > 0) is.setstate(std::ios::failbit); return 0; } template long double read_long_double(std::basic_istream& is, unsigned m = 1, unsigned M = 10) { unsigned count = 0; unsigned fcount = 0; unsigned long long i = 0; unsigned long long f = 0; bool parsing_fraction = false; #if ONLY_C_LOCALE typename Traits::int_type decimal_point = '.'; #else auto decimal_point = Traits::to_int_type( std::use_facet>(is.getloc()).decimal_point()); #endif while (true) { auto ic = is.peek(); if (Traits::eq_int_type(ic, Traits::eof())) break; if (Traits::eq_int_type(ic, decimal_point)) { decimal_point = Traits::eof(); parsing_fraction = true; } else { auto c = static_cast(Traits::to_char_type(ic)); if (!('0' <= c && c <= '9')) break; if (!parsing_fraction) { i = 10*i + static_cast(c - '0'); } else { f = 10*f + static_cast(c - '0'); ++fcount; } } (void)is.get(); if (++count == M) break; } if (count < m) { is.setstate(std::ios::failbit); return 0; } return static_cast(i) + static_cast(f)/std::pow(10.L, fcount); } struct rs { int& i; unsigned m; unsigned M; }; struct ru { int& i; unsigned m; unsigned M; }; struct rld { long double& i; unsigned m; unsigned M; }; template void read(std::basic_istream&) { } template void read(std::basic_istream& is, CharT a0, Args&& ...args); template void read(std::basic_istream& is, rs a0, Args&& ...args); template void read(std::basic_istream& is, ru a0, Args&& ...args); template void read(std::basic_istream& is, int a0, Args&& ...args); template void read(std::basic_istream& is, rld a0, Args&& ...args); template void read(std::basic_istream& is, CharT a0, Args&& ...args) { // No-op if a0 == CharT{} if (a0 != CharT{}) { auto ic = is.peek(); if (Traits::eq_int_type(ic, Traits::eof())) { is.setstate(std::ios::failbit | std::ios::eofbit); return; } if (!Traits::eq(Traits::to_char_type(ic), a0)) { is.setstate(std::ios::failbit); return; } (void)is.get(); } read(is, std::forward(args)...); } template void read(std::basic_istream& is, rs a0, Args&& ...args) { auto x = read_signed(is, a0.m, a0.M); if (is.fail()) return; a0.i = x; read(is, std::forward(args)...); } template void read(std::basic_istream& is, ru a0, Args&& ...args) { auto x = read_unsigned(is, a0.m, a0.M); if (is.fail()) return; a0.i = static_cast(x); read(is, std::forward(args)...); } template void read(std::basic_istream& is, int a0, Args&& ...args) { if (a0 != -1) { auto u = static_cast(a0); CharT buf[std::numeric_limits::digits10+2u] = {}; auto e = buf; do { *e++ = static_cast(CharT(u % 10) + CharT{'0'}); u /= 10; } while (u > 0); std::reverse(buf, e); for (auto p = buf; p != e && is.rdstate() == std::ios::goodbit; ++p) read(is, *p); } if (is.rdstate() == std::ios::goodbit) read(is, std::forward(args)...); } template void read(std::basic_istream& is, rld a0, Args&& ...args) { auto x = read_long_double(is, a0.m, a0.M); if (is.fail()) return; a0.i = x; read(is, std::forward(args)...); } template inline void checked_set(T& value, T from, T not_a_value, std::basic_ios& is) { if (!is.fail()) { if (value == not_a_value) value = std::move(from); else if (value != from) is.setstate(std::ios::failbit); } } } // namespace detail; template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, fields& fds, std::basic_string* abbrev, std::chrono::minutes* offset) { using std::numeric_limits; using std::ios; using std::chrono::duration; using std::chrono::duration_cast; using std::chrono::seconds; using std::chrono::minutes; using std::chrono::hours; using detail::round_i; typename std::basic_istream::sentry ok{is, true}; if (ok) { date::detail::save_istream ss(is); is.fill(' '); is.flags(std::ios::skipws | std::ios::dec); is.width(0); #if !ONLY_C_LOCALE auto& f = std::use_facet>(is.getloc()); std::tm tm{}; #endif const CharT* command = nullptr; auto modified = CharT{}; auto width = -1; CONSTDATA int not_a_year = numeric_limits::min(); CONSTDATA int not_a_2digit_year = 100; CONSTDATA int not_a_century = not_a_year / 100; CONSTDATA int not_a_month = 0; CONSTDATA int not_a_day = 0; CONSTDATA int not_a_hour = numeric_limits::min(); CONSTDATA int not_a_hour_12_value = 0; CONSTDATA int not_a_minute = not_a_hour; CONSTDATA Duration not_a_second = Duration::min(); CONSTDATA int not_a_doy = -1; CONSTDATA int not_a_weekday = 8; CONSTDATA int not_a_week_num = 100; CONSTDATA int not_a_ampm = -1; CONSTDATA minutes not_a_offset = minutes::min(); int Y = not_a_year; // c, F, Y * int y = not_a_2digit_year; // D, x, y * int g = not_a_2digit_year; // g * int G = not_a_year; // G * int C = not_a_century; // C * int m = not_a_month; // b, B, h, m, c, D, F, x * int d = not_a_day; // c, d, D, e, F, x * int j = not_a_doy; // j * int wd = not_a_weekday; // a, A, u, w * int H = not_a_hour; // c, H, R, T, X * int I = not_a_hour_12_value; // I, r * int p = not_a_ampm; // p, r * int M = not_a_minute; // c, M, r, R, T, X * Duration s = not_a_second; // c, r, S, T, X * int U = not_a_week_num; // U * int V = not_a_week_num; // V * int W = not_a_week_num; // W * std::basic_string temp_abbrev; // Z * minutes temp_offset = not_a_offset; // z * using detail::read; using detail::rs; using detail::ru; using detail::rld; using detail::checked_set; for (; *fmt != CharT{} && !is.fail(); ++fmt) { switch (*fmt) { case 'a': case 'A': case 'u': case 'w': // wd: a, A, u, w if (command) { int trial_wd = not_a_weekday; if (*fmt == 'a' || *fmt == 'A') { if (modified == CharT{}) { #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); is.setstate(err); if (!is.fail()) trial_wd = tm.tm_wday; #else auto nm = detail::weekday_names(); auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; if (!is.fail()) trial_wd = i % 7; #endif } else read(is, CharT{'%'}, width, modified, *fmt); } else // *fmt == 'u' || *fmt == 'w' { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { read(is, ru{trial_wd, 1, width == -1 ? 1u : static_cast(width)}); if (!is.fail()) { if (*fmt == 'u') { if (!(1 <= trial_wd && trial_wd <= 7)) { trial_wd = not_a_weekday; is.setstate(ios::failbit); } else if (trial_wd == 7) trial_wd = 0; } else // *fmt == 'w' { if (!(0 <= trial_wd && trial_wd <= 6)) { trial_wd = not_a_weekday; is.setstate(ios::failbit); } } } } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); is.setstate(err); if (!is.fail()) trial_wd = tm.tm_wday; } #endif else read(is, CharT{'%'}, width, modified, *fmt); } if (trial_wd != not_a_weekday) checked_set(wd, trial_wd, not_a_weekday, is); } else // !command read(is, *fmt); command = nullptr; width = -1; modified = CharT{}; break; case 'b': case 'B': case 'h': if (command) { if (modified == CharT{}) { int ttm = not_a_month; #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) ttm = tm.tm_mon + 1; is.setstate(err); #else auto nm = detail::month_names(); auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; if (!is.fail()) ttm = i % 12 + 1; #endif checked_set(m, ttm, not_a_month, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'c': if (command) { if (modified != CharT{'O'}) { #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) { checked_set(Y, tm.tm_year + 1900, not_a_year, is); checked_set(m, tm.tm_mon + 1, not_a_month, is); checked_set(d, tm.tm_mday, not_a_day, is); checked_set(H, tm.tm_hour, not_a_hour, is); checked_set(M, tm.tm_min, not_a_minute, is); checked_set(s, duration_cast(seconds{tm.tm_sec}), not_a_second, is); } is.setstate(err); #else // "%a %b %e %T %Y" auto nm = detail::weekday_names(); auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; checked_set(wd, static_cast(i % 7), not_a_weekday, is); ws(is); nm = detail::month_names(); i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; checked_set(m, static_cast(i % 12 + 1), not_a_month, is); ws(is); int td = not_a_day; read(is, rs{td, 1, 2}); checked_set(d, td, not_a_day, is); ws(is); using dfs = detail::decimal_format_seconds; CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; int tH; int tM; long double S{}; read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, CharT{':'}, rld{S, 1, w}); checked_set(H, tH, not_a_hour, is); checked_set(M, tM, not_a_minute, is); checked_set(s, round_i(duration{S}), not_a_second, is); ws(is); int tY = not_a_year; read(is, rs{tY, 1, 4u}); checked_set(Y, tY, not_a_year, is); #endif } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'x': if (command) { if (modified != CharT{'O'}) { #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) { checked_set(Y, tm.tm_year + 1900, not_a_year, is); checked_set(m, tm.tm_mon + 1, not_a_month, is); checked_set(d, tm.tm_mday, not_a_day, is); } is.setstate(err); #else // "%m/%d/%y" int ty = not_a_2digit_year; int tm = not_a_month; int td = not_a_day; read(is, ru{tm, 1, 2}, CharT{'/'}, ru{td, 1, 2}, CharT{'/'}, rs{ty, 1, 2}); checked_set(y, ty, not_a_2digit_year, is); checked_set(m, tm, not_a_month, is); checked_set(d, td, not_a_day, is); #endif } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'X': if (command) { if (modified != CharT{'O'}) { #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) { checked_set(H, tm.tm_hour, not_a_hour, is); checked_set(M, tm.tm_min, not_a_minute, is); checked_set(s, duration_cast(seconds{tm.tm_sec}), not_a_second, is); } is.setstate(err); #else // "%T" using dfs = detail::decimal_format_seconds; CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; int tH = not_a_hour; int tM = not_a_minute; long double S{}; read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, CharT{':'}, rld{S, 1, w}); checked_set(H, tH, not_a_hour, is); checked_set(M, tM, not_a_minute, is); checked_set(s, round_i(duration{S}), not_a_second, is); #endif } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'C': if (command) { int tC = not_a_century; #if !ONLY_C_LOCALE if (modified == CharT{}) { #endif read(is, rs{tC, 1, width == -1 ? 2u : static_cast(width)}); #if !ONLY_C_LOCALE } else { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) { auto tY = tm.tm_year + 1900; tC = (tY >= 0 ? tY : tY-99) / 100; } is.setstate(err); } #endif checked_set(C, tC, not_a_century, is); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'D': if (command) { if (modified == CharT{}) { int tn = not_a_month; int td = not_a_day; int ty = not_a_2digit_year; read(is, ru{tn, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, ru{td, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, rs{ty, 1, 2}); checked_set(y, ty, not_a_2digit_year, is); checked_set(m, tn, not_a_month, is); checked_set(d, td, not_a_day, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'F': if (command) { if (modified == CharT{}) { int tY = not_a_year; int tn = not_a_month; int td = not_a_day; read(is, rs{tY, 1, width == -1 ? 4u : static_cast(width)}, CharT{'-'}, ru{tn, 1, 2}, CharT{'-'}, ru{td, 1, 2}); checked_set(Y, tY, not_a_year, is); checked_set(m, tn, not_a_month, is); checked_set(d, td, not_a_day, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'd': case 'e': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { int td = not_a_day; read(is, rs{td, 1, width == -1 ? 2u : static_cast(width)}); checked_set(d, td, not_a_day, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); command = nullptr; width = -1; modified = CharT{}; if ((err & ios::failbit) == 0) checked_set(d, tm.tm_mday, not_a_day, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'H': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { int tH = not_a_hour; read(is, ru{tH, 1, width == -1 ? 2u : static_cast(width)}); checked_set(H, tH, not_a_hour, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(H, tm.tm_hour, not_a_hour, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'I': if (command) { if (modified == CharT{}) { int tI = not_a_hour_12_value; // reads in an hour into I, but most be in [1, 12] read(is, rs{tI, 1, width == -1 ? 2u : static_cast(width)}); if (!(1 <= tI && tI <= 12)) is.setstate(ios::failbit); checked_set(I, tI, not_a_hour_12_value, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'j': if (command) { if (modified == CharT{}) { int tj = not_a_doy; read(is, ru{tj, 1, width == -1 ? 3u : static_cast(width)}); checked_set(j, tj, not_a_doy, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'M': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { int tM = not_a_minute; read(is, ru{tM, 1, width == -1 ? 2u : static_cast(width)}); checked_set(M, tM, not_a_minute, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(M, tm.tm_min, not_a_minute, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'm': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { int tn = not_a_month; read(is, rs{tn, 1, width == -1 ? 2u : static_cast(width)}); checked_set(m, tn, not_a_month, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(m, tm.tm_mon + 1, not_a_month, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'n': case 't': if (command) { if (modified == CharT{}) { // %n matches a single white space character // %t matches 0 or 1 white space characters auto ic = is.peek(); if (Traits::eq_int_type(ic, Traits::eof())) { ios::iostate err = ios::eofbit; if (*fmt == 'n') err |= ios::failbit; is.setstate(err); break; } if (isspace(ic)) { (void)is.get(); } else if (*fmt == 'n') is.setstate(ios::failbit); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'p': if (command) { if (modified == CharT{}) { int tp = not_a_ampm; #if !ONLY_C_LOCALE tm = std::tm{}; tm.tm_hour = 1; ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); is.setstate(err); if (tm.tm_hour == 1) tp = 0; else if (tm.tm_hour == 13) tp = 1; else is.setstate(err); #else auto nm = detail::ampm_names(); auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; tp = static_cast(i); #endif checked_set(p, tp, not_a_ampm, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'r': if (command) { if (modified == CharT{}) { #if !ONLY_C_LOCALE ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) { checked_set(H, tm.tm_hour, not_a_hour, is); checked_set(M, tm.tm_min, not_a_hour, is); checked_set(s, duration_cast(seconds{tm.tm_sec}), not_a_second, is); } is.setstate(err); #else // "%I:%M:%S %p" using dfs = detail::decimal_format_seconds; CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; long double S{}; int tI = not_a_hour_12_value; int tM = not_a_minute; read(is, ru{tI, 1, 2}, CharT{':'}, ru{tM, 1, 2}, CharT{':'}, rld{S, 1, w}); checked_set(I, tI, not_a_hour_12_value, is); checked_set(M, tM, not_a_minute, is); checked_set(s, round_i(duration{S}), not_a_second, is); ws(is); auto nm = detail::ampm_names(); auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; checked_set(p, static_cast(i), not_a_ampm, is); #endif } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'R': if (command) { if (modified == CharT{}) { int tH = not_a_hour; int tM = not_a_minute; read(is, ru{tH, 1, 2}, CharT{'\0'}, CharT{':'}, CharT{'\0'}, ru{tM, 1, 2}, CharT{'\0'}); checked_set(H, tH, not_a_hour, is); checked_set(M, tM, not_a_minute, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'S': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'E'}) #endif { using dfs = detail::decimal_format_seconds; CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; long double S{}; read(is, rld{S, 1, width == -1 ? w : static_cast(width)}); checked_set(s, round_i(duration{S}), not_a_second, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'O'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(s, duration_cast(seconds{tm.tm_sec}), not_a_second, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'T': if (command) { if (modified == CharT{}) { using dfs = detail::decimal_format_seconds; CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; int tH = not_a_hour; int tM = not_a_minute; long double S{}; read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, CharT{':'}, rld{S, 1, w}); checked_set(H, tH, not_a_hour, is); checked_set(M, tM, not_a_minute, is); checked_set(s, round_i(duration{S}), not_a_second, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'Y': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #else if (modified != CharT{'O'}) #endif { int tY = not_a_year; read(is, rs{tY, 1, width == -1 ? 4u : static_cast(width)}); checked_set(Y, tY, not_a_year, is); } #if !ONLY_C_LOCALE else if (modified == CharT{'E'}) { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(Y, tm.tm_year + 1900, not_a_year, is); is.setstate(err); } #endif else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'y': if (command) { #if !ONLY_C_LOCALE if (modified == CharT{}) #endif { int ty = not_a_2digit_year; read(is, ru{ty, 1, width == -1 ? 2u : static_cast(width)}); checked_set(y, ty, not_a_2digit_year, is); } #if !ONLY_C_LOCALE else { ios::iostate err = ios::goodbit; f.get(is, nullptr, is, err, &tm, command, fmt+1); if ((err & ios::failbit) == 0) checked_set(Y, tm.tm_year + 1900, not_a_year, is); is.setstate(err); } #endif command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'g': if (command) { if (modified == CharT{}) { int tg = not_a_2digit_year; read(is, ru{tg, 1, width == -1 ? 2u : static_cast(width)}); checked_set(g, tg, not_a_2digit_year, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'G': if (command) { if (modified == CharT{}) { int tG = not_a_year; read(is, rs{tG, 1, width == -1 ? 4u : static_cast(width)}); checked_set(G, tG, not_a_year, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'U': if (command) { if (modified == CharT{}) { int tU = not_a_week_num; read(is, ru{tU, 1, width == -1 ? 2u : static_cast(width)}); checked_set(U, tU, not_a_week_num, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'V': if (command) { if (modified == CharT{}) { int tV = not_a_week_num; read(is, ru{tV, 1, width == -1 ? 2u : static_cast(width)}); checked_set(V, tV, not_a_week_num, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'W': if (command) { if (modified == CharT{}) { int tW = not_a_week_num; read(is, ru{tW, 1, width == -1 ? 2u : static_cast(width)}); checked_set(W, tW, not_a_week_num, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'E': case 'O': if (command) { if (modified == CharT{}) { modified = *fmt; } else { read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } } else read(is, *fmt); break; case '%': if (command) { if (modified == CharT{}) read(is, *fmt); else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else command = fmt; break; case 'z': if (command) { int tH, tM; minutes toff = not_a_offset; bool neg = false; auto ic = is.peek(); if (!Traits::eq_int_type(ic, Traits::eof())) { auto c = static_cast(Traits::to_char_type(ic)); if (c == '-') neg = true; } if (modified == CharT{}) { read(is, rs{tH, 2, 2}); if (!is.fail()) toff = hours{std::abs(tH)}; if (is.good()) { ic = is.peek(); if (!Traits::eq_int_type(ic, Traits::eof())) { auto c = static_cast(Traits::to_char_type(ic)); if ('0' <= c && c <= '9') { read(is, ru{tM, 2, 2}); if (!is.fail()) toff += minutes{tM}; } } } } else { read(is, rs{tH, 1, 2}); if (!is.fail()) toff = hours{std::abs(tH)}; if (is.good()) { ic = is.peek(); if (!Traits::eq_int_type(ic, Traits::eof())) { auto c = static_cast(Traits::to_char_type(ic)); if (c == ':') { (void)is.get(); read(is, ru{tM, 2, 2}); if (!is.fail()) toff += minutes{tM}; } } } } if (neg) toff = -toff; checked_set(temp_offset, toff, not_a_offset, is); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; case 'Z': if (command) { if (modified == CharT{}) { std::basic_string buf; while (is.rdstate() == std::ios::goodbit) { auto i = is.rdbuf()->sgetc(); if (Traits::eq_int_type(i, Traits::eof())) { is.setstate(ios::eofbit); break; } auto wc = Traits::to_char_type(i); auto c = static_cast(wc); // is c a valid time zone name or abbreviation character? if (!(CharT{1} < wc && wc < CharT{127}) || !(isalnum(c) || c == '_' || c == '/' || c == '-' || c == '+')) break; buf.push_back(c); is.rdbuf()->sbumpc(); } if (buf.empty()) is.setstate(ios::failbit); checked_set(temp_abbrev, buf, {}, is); } else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } else read(is, *fmt); break; default: if (command) { if (width == -1 && modified == CharT{} && '0' <= *fmt && *fmt <= '9') { width = static_cast(*fmt) - '0'; while ('0' <= fmt[1] && fmt[1] <= '9') width = 10*width + static_cast(*++fmt) - '0'; } else { if (modified == CharT{}) read(is, CharT{'%'}, width, *fmt); else read(is, CharT{'%'}, width, modified, *fmt); command = nullptr; width = -1; modified = CharT{}; } } else // !command { if (isspace(static_cast(*fmt))) { // space matches 0 or more white space characters if (is.good()) ws(is); } else read(is, *fmt); } break; } } // is.fail() || *fmt == CharT{} if (is.rdstate() == ios::goodbit && command) { if (modified == CharT{}) read(is, CharT{'%'}, width); else read(is, CharT{'%'}, width, modified); } if (!is.fail()) { if (y != not_a_2digit_year) { // Convert y and an optional C to Y if (!(0 <= y && y <= 99)) goto broken; if (C == not_a_century) { if (Y == not_a_year) { if (y >= 69) C = 19; else C = 20; } else { C = (Y >= 0 ? Y : Y-100) / 100; } } int tY; if (C >= 0) tY = 100*C + y; else tY = 100*(C+1) - (y == 0 ? 100 : y); if (Y != not_a_year && Y != tY) goto broken; Y = tY; } if (g != not_a_2digit_year) { // Convert g and an optional C to G if (!(0 <= g && g <= 99)) goto broken; if (C == not_a_century) { if (G == not_a_year) { if (g >= 69) C = 19; else C = 20; } else { C = (G >= 0 ? G : G-100) / 100; } } int tG; if (C >= 0) tG = 100*C + g; else tG = 100*(C+1) - (g == 0 ? 100 : g); if (G != not_a_year && G != tG) goto broken; G = tG; } if (Y < static_cast(year::min()) || Y > static_cast(year::max())) Y = not_a_year; bool computed = false; if (G != not_a_year && V != not_a_week_num && wd != not_a_weekday) { year_month_day ymd_trial = sys_days(year{G-1}/December/Thursday[last]) + (Monday-Thursday) + weeks{V-1} + (weekday{static_cast(wd)}-Monday); if (Y == not_a_year) Y = static_cast(ymd_trial.year()); else if (year{Y} != ymd_trial.year()) goto broken; if (m == not_a_month) m = static_cast(static_cast(ymd_trial.month())); else if (month(static_cast(m)) != ymd_trial.month()) goto broken; if (d == not_a_day) d = static_cast(static_cast(ymd_trial.day())); else if (day(static_cast(d)) != ymd_trial.day()) goto broken; computed = true; } if (Y != not_a_year && U != not_a_week_num && wd != not_a_weekday) { year_month_day ymd_trial = sys_days(year{Y}/January/Sunday[1]) + weeks{U-1} + (weekday{static_cast(wd)} - Sunday); if (Y == not_a_year) Y = static_cast(ymd_trial.year()); else if (year{Y} != ymd_trial.year()) goto broken; if (m == not_a_month) m = static_cast(static_cast(ymd_trial.month())); else if (month(static_cast(m)) != ymd_trial.month()) goto broken; if (d == not_a_day) d = static_cast(static_cast(ymd_trial.day())); else if (day(static_cast(d)) != ymd_trial.day()) goto broken; computed = true; } if (Y != not_a_year && W != not_a_week_num && wd != not_a_weekday) { year_month_day ymd_trial = sys_days(year{Y}/January/Monday[1]) + weeks{W-1} + (weekday{static_cast(wd)} - Monday); if (Y == not_a_year) Y = static_cast(ymd_trial.year()); else if (year{Y} != ymd_trial.year()) goto broken; if (m == not_a_month) m = static_cast(static_cast(ymd_trial.month())); else if (month(static_cast(m)) != ymd_trial.month()) goto broken; if (d == not_a_day) d = static_cast(static_cast(ymd_trial.day())); else if (day(static_cast(d)) != ymd_trial.day()) goto broken; computed = true; } if (j != not_a_doy && Y != not_a_year) { auto ymd_trial = year_month_day{local_days(year{Y}/1/1) + days{j-1}}; if (m == not_a_month) m = static_cast(static_cast(ymd_trial.month())); else if (month(static_cast(m)) != ymd_trial.month()) goto broken; if (d == not_a_day) d = static_cast(static_cast(ymd_trial.day())); else if (day(static_cast(d)) != ymd_trial.day()) goto broken; j = not_a_doy; } auto ymd = year{Y}/m/d; if (ymd.ok()) { if (wd == not_a_weekday) wd = static_cast((weekday(sys_days(ymd)) - Sunday).count()); else if (wd != static_cast((weekday(sys_days(ymd)) - Sunday).count())) goto broken; if (!computed) { if (G != not_a_year || V != not_a_week_num) { sys_days sd = ymd; auto G_trial = year_month_day{sd + days{3}}.year(); auto start = sys_days((G_trial - years{1})/December/Thursday[last]) + (Monday - Thursday); if (sd < start) { --G_trial; if (V != not_a_week_num) start = sys_days((G_trial - years{1})/December/Thursday[last]) + (Monday - Thursday); } if (G != not_a_year && G != static_cast(G_trial)) goto broken; if (V != not_a_week_num) { auto V_trial = duration_cast(sd - start).count() + 1; if (V != V_trial) goto broken; } } if (U != not_a_week_num) { auto start = sys_days(Sunday[1]/January/ymd.year()); auto U_trial = floor(sys_days(ymd) - start).count() + 1; if (U != U_trial) goto broken; } if (W != not_a_week_num) { auto start = sys_days(Monday[1]/January/ymd.year()); auto W_trial = floor(sys_days(ymd) - start).count() + 1; if (W != W_trial) goto broken; } } } fds.ymd = ymd; if (I != not_a_hour_12_value) { if (!(1 <= I && I <= 12)) goto broken; if (p != not_a_ampm) { // p is in [0, 1] == [AM, PM] // Store trial H in I if (I == 12) --p; I += p*12; // Either set H from I or make sure H and I are consistent if (H == not_a_hour) H = I; else if (I != H) goto broken; } else // p == not_a_ampm { // if H, make sure H and I could be consistent if (H != not_a_hour) { if (I == 12) { if (H != 0 && H != 12) goto broken; } else if (!(I == H || I == H+12)) { goto broken; } } else // I is ambiguous, AM or PM? goto broken; } } if (H != not_a_hour) { fds.has_tod = true; fds.tod = hh_mm_ss{hours{H}}; } if (M != not_a_minute) { fds.has_tod = true; fds.tod.m_ = minutes{M}; } if (s != not_a_second) { fds.has_tod = true; fds.tod.s_ = detail::decimal_format_seconds{s}; } if (j != not_a_doy) { fds.has_tod = true; fds.tod.h_ += hours{days{j}}; } if (wd != not_a_weekday) fds.wd = weekday{static_cast(wd)}; if (abbrev != nullptr) *abbrev = std::move(temp_abbrev); if (offset != nullptr && temp_offset != not_a_offset) *offset = temp_offset; } return is; } broken: is.setstate(ios::failbit); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, year& y, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.year().ok()) is.setstate(std::ios::failbit); if (!is.fail()) y = fds.ymd.year(); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, month& m, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.month().ok()) is.setstate(std::ios::failbit); if (!is.fail()) m = fds.ymd.month(); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, day& d, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.day().ok()) is.setstate(std::ios::failbit); if (!is.fail()) d = fds.ymd.day(); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, weekday& wd, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.wd.ok()) is.setstate(std::ios::failbit); if (!is.fail()) wd = fds.wd; return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, year_month& ym, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.month().ok()) is.setstate(std::ios::failbit); if (!is.fail()) ym = fds.ymd.year()/fds.ymd.month(); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, month_day& md, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.month().ok() || !fds.ymd.day().ok()) is.setstate(std::ios::failbit); if (!is.fail()) md = fds.ymd.month()/fds.ymd.day(); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, year_month_day& ymd, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = std::chrono::seconds; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.ok()) is.setstate(std::ios::failbit); if (!is.fail()) ymd = fds.ymd; return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, sys_time& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = typename std::common_type::type; using detail::round_i; std::chrono::minutes offset_local{}; auto offptr = offset ? offset : &offset_local; fields fds{}; fds.has_tod = true; date::from_stream(is, fmt, fds, abbrev, offptr); if (!fds.ymd.ok() || !fds.tod.in_conventional_range()) is.setstate(std::ios::failbit); if (!is.fail()) tp = round_i(sys_days(fds.ymd) - *offptr + fds.tod.to_duration()); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, local_time& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using CT = typename std::common_type::type; using detail::round_i; fields fds{}; fds.has_tod = true; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.ymd.ok() || !fds.tod.in_conventional_range()) is.setstate(std::ios::failbit); if (!is.fail()) tp = round_i(local_seconds{local_days(fds.ymd)} + fds.tod.to_duration()); return is; } template > std::basic_istream& from_stream(std::basic_istream& is, const CharT* fmt, std::chrono::duration& d, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) { using Duration = std::chrono::duration; using CT = typename std::common_type::type; using detail::round_i; fields fds{}; date::from_stream(is, fmt, fds, abbrev, offset); if (!fds.has_tod) is.setstate(std::ios::failbit); if (!is.fail()) d = round_i(fds.tod.to_duration()); return is; } template , class Alloc = std::allocator> struct parse_manip { const std::basic_string format_; Parsable& tp_; std::basic_string* abbrev_; std::chrono::minutes* offset_; public: parse_manip(std::basic_string format, Parsable& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) : format_(std::move(format)) , tp_(tp) , abbrev_(abbrev) , offset_(offset) {} #if HAS_STRING_VIEW parse_manip(const CharT* format, Parsable& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) : format_(format) , tp_(tp) , abbrev_(abbrev) , offset_(offset) {} parse_manip(std::basic_string_view format, Parsable& tp, std::basic_string* abbrev = nullptr, std::chrono::minutes* offset = nullptr) : format_(format) , tp_(tp) , abbrev_(abbrev) , offset_(offset) {} #endif // HAS_STRING_VIEW }; template std::basic_istream& operator>>(std::basic_istream& is, const parse_manip& x) { return date::from_stream(is, x.format_.c_str(), x.tp_, x.abbrev_, x.offset_); } template inline auto parse(const std::basic_string& format, Parsable& tp) -> decltype(date::from_stream(std::declval&>(), format.c_str(), tp), parse_manip{format, tp}) { return {format, tp}; } template inline auto parse(const std::basic_string& format, Parsable& tp, std::basic_string& abbrev) -> decltype(date::from_stream(std::declval&>(), format.c_str(), tp, &abbrev), parse_manip{format, tp, &abbrev}) { return {format, tp, &abbrev}; } template inline auto parse(const std::basic_string& format, Parsable& tp, std::chrono::minutes& offset) -> decltype(date::from_stream(std::declval&>(), format.c_str(), tp, std::declval*>(), &offset), parse_manip{format, tp, nullptr, &offset}) { return {format, tp, nullptr, &offset}; } template inline auto parse(const std::basic_string& format, Parsable& tp, std::basic_string& abbrev, std::chrono::minutes& offset) -> decltype(date::from_stream(std::declval&>(), format.c_str(), tp, &abbrev, &offset), parse_manip{format, tp, &abbrev, &offset}) { return {format, tp, &abbrev, &offset}; } // const CharT* formats template inline auto parse(const CharT* format, Parsable& tp) -> decltype(date::from_stream(std::declval&>(), format, tp), parse_manip{format, tp}) { return {format, tp}; } template inline auto parse(const CharT* format, Parsable& tp, std::basic_string& abbrev) -> decltype(date::from_stream(std::declval&>(), format, tp, &abbrev), parse_manip{format, tp, &abbrev}) { return {format, tp, &abbrev}; } template inline auto parse(const CharT* format, Parsable& tp, std::chrono::minutes& offset) -> decltype(date::from_stream(std::declval&>(), format, tp, std::declval*>(), &offset), parse_manip{format, tp, nullptr, &offset}) { return {format, tp, nullptr, &offset}; } template inline auto parse(const CharT* format, Parsable& tp, std::basic_string& abbrev, std::chrono::minutes& offset) -> decltype(date::from_stream(std::declval&>(), format, tp, &abbrev, &offset), parse_manip{format, tp, &abbrev, &offset}) { return {format, tp, &abbrev, &offset}; } // duration streaming template inline std::basic_ostream& operator<<(std::basic_ostream& os, const std::chrono::duration& d) { return os << detail::make_string::from(d.count()) + detail::get_units(typename Period::type{}); } } // namespace date #ifdef _MSC_VER # pragma warning(pop) #endif #ifdef __GNUC__ # pragma GCC diagnostic pop #endif #endif // DATE_H ================================================ FILE: src/Common/unix/fast_float.h ================================================ // fast_float by Daniel Lemire // fast_float by João Paulo Magalhaes // // with contributions from Eugene Golushkov // with contributions from Maksim Kita // with contributions from Marcin Wojdyr // with contributions from Neal Richardson // with contributions from Tim Paine // with contributions from Fabio Pellacini // // Licensed under the Apache License, Version 2.0, or the // MIT License at your option. This file may not be copied, // modified, or distributed except according to those terms. // // MIT License Notice // // MIT License // // Copyright (c) 2021 The fast_float authors // // 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. // // Apache License (Version 2.0) Notice // // Copyright 2021 The fast_float authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // #ifndef FASTFLOAT_FAST_FLOAT_H #define FASTFLOAT_FAST_FLOAT_H #include namespace fast_float { enum chars_format { scientific = 1<<0, fixed = 1<<2, hex = 1<<3, general = fixed | scientific }; struct from_chars_result { const char *ptr; std::errc ec; }; struct parse_options { constexpr explicit parse_options(chars_format fmt = chars_format::general, char dot = '.') : format(fmt), decimal_point(dot) {} /** Which number formats are accepted */ chars_format format; /** The character used as decimal point */ char decimal_point; }; /** * This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting * a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale. * The resulting floating-point value is the closest floating-point values (using either float or double), * using the "round to even" convention for values that would otherwise fall right in-between two values. * That is, we provide exact parsing according to the IEEE standard. * * Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the * parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned * `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. * * The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). * * Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of * the type `fast_float::chars_format`. It is a bitset value: we check whether * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set * to determine whether we allowe the fixed point and scientific notation respectively. * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. */ template from_chars_result from_chars(const char *first, const char *last, T &value, chars_format fmt = chars_format::general) noexcept; /** * Like from_chars, but accepts an `options` argument to govern number parsing. */ template from_chars_result from_chars_advanced(const char *first, const char *last, T &value, parse_options options) noexcept; } #endif // FASTFLOAT_FAST_FLOAT_H #ifndef FASTFLOAT_FLOAT_COMMON_H #define FASTFLOAT_FLOAT_COMMON_H #include #include #include #include #include #if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ || defined(__MINGW64__) \ || defined(__s390x__) \ || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \ || defined(__EMSCRIPTEN__)) #define FASTFLOAT_64BIT #elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \ || defined(__arm__) || defined(_M_ARM) \ || defined(__MINGW32__)) #define FASTFLOAT_32BIT #else // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. // We can never tell the register width, but the SIZE_MAX is a good approximation. // UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max portability. #if SIZE_MAX == 0xffff #error Unknown platform (16-bit, unsupported) #elif SIZE_MAX == 0xffffffff #define FASTFLOAT_32BIT #elif SIZE_MAX == 0xffffffffffffffff #define FASTFLOAT_64BIT #else #error Unknown platform (not 32-bit, not 64-bit?) #endif #endif #if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) #include #endif #if defined(_MSC_VER) && !defined(__clang__) #define FASTFLOAT_VISUAL_STUDIO 1 #endif #ifdef _WIN32 #define FASTFLOAT_IS_BIG_ENDIAN 0 #else #if defined(__APPLE__) || defined(__FreeBSD__) #include #elif defined(sun) || defined(__sun) #include #else #include #endif # #ifndef __BYTE_ORDER__ // safe choice #define FASTFLOAT_IS_BIG_ENDIAN 0 #endif # #ifndef __ORDER_LITTLE_ENDIAN__ // safe choice #define FASTFLOAT_IS_BIG_ENDIAN 0 #endif # #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define FASTFLOAT_IS_BIG_ENDIAN 0 #else #define FASTFLOAT_IS_BIG_ENDIAN 1 #endif #endif #ifdef FASTFLOAT_VISUAL_STUDIO #define fastfloat_really_inline __forceinline #else #define fastfloat_really_inline inline __attribute__((always_inline)) #endif #ifndef FASTFLOAT_ASSERT #define FASTFLOAT_ASSERT(x) { if (!(x)) abort(); } #endif #ifndef FASTFLOAT_DEBUG_ASSERT #include #define FASTFLOAT_DEBUG_ASSERT(x) assert(x) #endif // rust style `try!()` macro, or `?` operator #define FASTFLOAT_TRY(x) { if (!(x)) return false; } namespace fast_float { // Compares two ASCII strings in a case insensitive manner. inline bool fastfloat_strncasecmp(const char *input1, const char *input2, size_t length) { char running_diff{0}; for (size_t i = 0; i < length; i++) { running_diff |= (input1[i] ^ input2[i]); } return (running_diff == 0) || (running_diff == 32); } #ifndef FLT_EVAL_METHOD #error "FLT_EVAL_METHOD should be defined, please include cfloat." #endif // a pointer and a length to a contiguous block of memory template struct span { const T* ptr; size_t length; span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {} span() : ptr(nullptr), length(0) {} constexpr size_t len() const noexcept { return length; } const T& operator[](size_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return ptr[index]; } }; struct value128 { uint64_t low; uint64_t high; value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} value128() : low(0), high(0) {} }; /* result might be undefined when input_num is zero */ fastfloat_really_inline int leading_zeroes(uint64_t input_num) { assert(input_num > 0); #ifdef FASTFLOAT_VISUAL_STUDIO #if defined(_M_X64) || defined(_M_ARM64) unsigned long leading_zero = 0; // Search the mask data from most significant bit (MSB) // to least significant bit (LSB) for a set bit (1). _BitScanReverse64(&leading_zero, input_num); return (int)(63 - leading_zero); #else int last_bit = 0; if(input_num & uint64_t(0xffffffff00000000)) input_num >>= 32, last_bit |= 32; if(input_num & uint64_t( 0xffff0000)) input_num >>= 16, last_bit |= 16; if(input_num & uint64_t( 0xff00)) input_num >>= 8, last_bit |= 8; if(input_num & uint64_t( 0xf0)) input_num >>= 4, last_bit |= 4; if(input_num & uint64_t( 0xc)) input_num >>= 2, last_bit |= 2; if(input_num & uint64_t( 0x2)) input_num >>= 1, last_bit |= 1; return 63 - last_bit; #endif #else return __builtin_clzll(input_num); #endif } #ifdef FASTFLOAT_32BIT // slow emulation routine for 32-bit fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) { return x * (uint64_t)y; } // slow emulation routine for 32-bit #if !defined(__MINGW64__) fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); uint64_t adbc_carry = !!(adbc < ad); uint64_t lo = bd + (adbc << 32); *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + (adbc_carry << 32) + !!(lo < bd); return lo; } #endif // !__MINGW64__ #endif // FASTFLOAT_32BIT // compute 64-bit a*b fastfloat_really_inline value128 full_multiplication(uint64_t a, uint64_t b) { value128 answer; #ifdef _M_ARM64 // ARM64 has native support for 64-bit multiplications, no need to emulate answer.high = __umulh(a, b); answer.low = a * b; #elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 #elif defined(FASTFLOAT_64BIT) __uint128_t r = ((__uint128_t)a) * b; answer.low = uint64_t(r); answer.high = uint64_t(r >> 64); #else #error Not implemented #endif return answer; } struct adjusted_mantissa { uint64_t mantissa{0}; int32_t power2{0}; // a negative value indicates an invalid result adjusted_mantissa() = default; bool operator==(const adjusted_mantissa &o) const { return mantissa == o.mantissa && power2 == o.power2; } bool operator!=(const adjusted_mantissa &o) const { return mantissa != o.mantissa || power2 != o.power2; } }; // Bias so we can get the real exponent with an invalid adjusted_mantissa. constexpr static int32_t invalid_am_bias = -0x8000; constexpr static double powers_of_ten_double[] = { 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10}; template struct binary_format { using equiv_uint = typename std::conditional::type; static inline constexpr int mantissa_explicit_bits(); static inline constexpr int minimum_exponent(); static inline constexpr int infinite_power(); static inline constexpr int sign_index(); static inline constexpr int min_exponent_fast_path(); static inline constexpr int max_exponent_fast_path(); static inline constexpr int max_exponent_round_to_even(); static inline constexpr int min_exponent_round_to_even(); static inline constexpr uint64_t max_mantissa_fast_path(); static inline constexpr int largest_power_of_ten(); static inline constexpr int smallest_power_of_ten(); static inline constexpr T exact_power_of_ten(int64_t power); static inline constexpr size_t max_digits(); static inline constexpr equiv_uint exponent_mask(); static inline constexpr equiv_uint mantissa_mask(); static inline constexpr equiv_uint hidden_bit_mask(); }; template <> inline constexpr int binary_format::mantissa_explicit_bits() { return 52; } template <> inline constexpr int binary_format::mantissa_explicit_bits() { return 23; } template <> inline constexpr int binary_format::max_exponent_round_to_even() { return 23; } template <> inline constexpr int binary_format::max_exponent_round_to_even() { return 10; } template <> inline constexpr int binary_format::min_exponent_round_to_even() { return -4; } template <> inline constexpr int binary_format::min_exponent_round_to_even() { return -17; } template <> inline constexpr int binary_format::minimum_exponent() { return -1023; } template <> inline constexpr int binary_format::minimum_exponent() { return -127; } template <> inline constexpr int binary_format::infinite_power() { return 0x7FF; } template <> inline constexpr int binary_format::infinite_power() { return 0xFF; } template <> inline constexpr int binary_format::sign_index() { return 63; } template <> inline constexpr int binary_format::sign_index() { return 31; } template <> inline constexpr int binary_format::min_exponent_fast_path() { #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) return 0; #else return -22; #endif } template <> inline constexpr int binary_format::min_exponent_fast_path() { #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) return 0; #else return -10; #endif } template <> inline constexpr int binary_format::max_exponent_fast_path() { return 22; } template <> inline constexpr int binary_format::max_exponent_fast_path() { return 10; } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { return uint64_t(2) << mantissa_explicit_bits(); } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { return uint64_t(2) << mantissa_explicit_bits(); } template <> inline constexpr double binary_format::exact_power_of_ten(int64_t power) { return powers_of_ten_double[power]; } template <> inline constexpr float binary_format::exact_power_of_ten(int64_t power) { return powers_of_ten_float[power]; } template <> inline constexpr int binary_format::largest_power_of_ten() { return 308; } template <> inline constexpr int binary_format::largest_power_of_ten() { return 38; } template <> inline constexpr int binary_format::smallest_power_of_ten() { return -342; } template <> inline constexpr int binary_format::smallest_power_of_ten() { return -65; } template <> inline constexpr size_t binary_format::max_digits() { return 769; } template <> inline constexpr size_t binary_format::max_digits() { return 114; } template <> inline constexpr binary_format::equiv_uint binary_format::exponent_mask() { return 0x7F800000; } template <> inline constexpr binary_format::equiv_uint binary_format::exponent_mask() { return 0x7FF0000000000000; } template <> inline constexpr binary_format::equiv_uint binary_format::mantissa_mask() { return 0x007FFFFF; } template <> inline constexpr binary_format::equiv_uint binary_format::mantissa_mask() { return 0x000FFFFFFFFFFFFF; } template <> inline constexpr binary_format::equiv_uint binary_format::hidden_bit_mask() { return 0x00800000; } template <> inline constexpr binary_format::equiv_uint binary_format::hidden_bit_mask() { return 0x0010000000000000; } template fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) { uint64_t word = am.mantissa; word |= uint64_t(am.power2) << binary_format::mantissa_explicit_bits(); word = negative ? word | (uint64_t(1) << binary_format::sign_index()) : word; #if FASTFLOAT_IS_BIG_ENDIAN == 1 if (std::is_same::value) { ::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian } else { ::memcpy(&value, &word, sizeof(T)); } #else // For little-endian systems: ::memcpy(&value, &word, sizeof(T)); #endif } } // namespace fast_float #endif #ifndef FASTFLOAT_ASCII_NUMBER_H #define FASTFLOAT_ASCII_NUMBER_H #include #include #include #include namespace fast_float { // Next function can be micro-optimized, but compilers are entirely // able to optimize it well. fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } fastfloat_really_inline uint64_t byteswap(uint64_t val) { return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; } fastfloat_really_inline uint64_t read_u64(const char *chars) { uint64_t val; ::memcpy(&val, chars, sizeof(uint64_t)); #if FASTFLOAT_IS_BIG_ENDIAN == 1 // Need to read as-if the number was in little-endian order. val = byteswap(val); #endif return val; } fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { #if FASTFLOAT_IS_BIG_ENDIAN == 1 // Need to read as-if the number was in little-endian order. val = byteswap(val); #endif ::memcpy(chars, &val, sizeof(uint64_t)); } // credit @aqrit fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { const uint64_t mask = 0x000000FF000000FF; const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) val -= 0x3030303030303030; val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; return uint32_t(val); } fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { return parse_eight_digits_unrolled(read_u64(chars)); } // credit @aqrit fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & 0x8080808080808080)); } fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { return is_made_of_eight_digits_fast(read_u64(chars)); } typedef span byte_span; struct parsed_number_string { int64_t exponent{0}; uint64_t mantissa{0}; const char *lastmatch{nullptr}; bool negative{false}; bool valid{false}; bool too_many_digits{false}; // contains the range of the significant digits byte_span integer{}; // non-nullable byte_span fraction{}; // nullable }; // Assuming that you use no more than 19 digits, this will // parse an ASCII string. fastfloat_really_inline parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { const chars_format fmt = options.format; const char decimal_point = options.decimal_point; parsed_number_string answer; answer.valid = false; answer.too_many_digits = false; answer.negative = (*p == '-'); if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here ++p; if (p == pend) { return answer; } if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot return answer; } } const char *const start_digits = p; uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok p += 8; } while ((p != pend) && is_integer(*p)) { // a multiplication by 10 is cheaper than an arbitrary integer // multiplication i = 10 * i + uint64_t(*p - '0'); // might overflow, we will handle the overflow later ++p; } const char *const end_of_integer_part = p; int64_t digit_count = int64_t(end_of_integer_part - start_digits); answer.integer = byte_span(start_digits, size_t(digit_count)); int64_t exponent = 0; if ((p != pend) && (*p == decimal_point)) { ++p; const char* before = p; // can occur at most twice without overflowing, but let it occur more, since // for integers with many digits, digit parsing is the primary bottleneck. while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok p += 8; } while ((p != pend) && is_integer(*p)) { uint8_t digit = uint8_t(*p - '0'); ++p; i = i * 10 + digit; // in rare cases, this will overflow, but that's ok } exponent = before - p; answer.fraction = byte_span(before, size_t(p - before)); digit_count -= exponent; } // we must have encountered at least one integer! if (digit_count == 0) { return answer; } int64_t exp_number = 0; // explicit exponential part if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { const char * location_of_e = p; ++p; bool neg_exp = false; if ((p != pend) && ('-' == *p)) { neg_exp = true; ++p; } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) ++p; } if ((p == pend) || !is_integer(*p)) { if(!(fmt & chars_format::fixed)) { // We are in error. return answer; } // Otherwise, we will be ignoring the 'e'. p = location_of_e; } else { while ((p != pend) && is_integer(*p)) { uint8_t digit = uint8_t(*p - '0'); if (exp_number < 0x10000000) { exp_number = 10 * exp_number + digit; } ++p; } if(neg_exp) { exp_number = - exp_number; } exponent += exp_number; } } else { // If it scientific and not fixed, we have to bail out. if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } } answer.lastmatch = p; answer.valid = true; // If we frequently had to deal with long strings of digits, // we could extend our code by using a 128-bit integer instead // of a 64-bit integer. However, this is uncommon. // // We can deal with up to 19 digits. if (digit_count > 19) { // this is uncommon // It is possible that the integer had an overflow. // We have to handle the case where we have 0.0000somenumber. // We need to be mindful of the case where we only have zeroes... // E.g., 0.000000000...000. const char *start = start_digits; while ((start != pend) && (*start == '0' || *start == decimal_point)) { if(*start == '0') { digit_count --; } start++; } if (digit_count > 19) { answer.too_many_digits = true; // Let us start again, this time, avoiding overflows. // We don't need to check if is_integer, since we use the // pre-tokenized spans from above. i = 0; p = answer.integer.ptr; const char* int_end = p + answer.integer.len(); const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; while((i < minimal_nineteen_digit_integer) && (p != int_end)) { i = i * 10 + uint64_t(*p - '0'); ++p; } if (i >= minimal_nineteen_digit_integer) { // We have a big integers exponent = end_of_integer_part - p + exp_number; } else { // We have a value with a fractional component. p = answer.fraction.ptr; const char* frac_end = p + answer.fraction.len(); while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { i = i * 10 + uint64_t(*p - '0'); ++p; } exponent = answer.fraction.ptr - p + exp_number; } // We have now corrected both exponent and i, to a truncated value } } answer.exponent = exponent; answer.mantissa = i; return answer; } } // namespace fast_float #endif #ifndef FASTFLOAT_FAST_TABLE_H #define FASTFLOAT_FAST_TABLE_H #include namespace fast_float { /** * When mapping numbers from decimal to binary, * we go from w * 10^q to m * 2^p but we have * 10^q = 5^q * 2^q, so effectively * we are trying to match * w * 2^q * 5^q to m * 2^p. Thus the powers of two * are not a concern since they can be represented * exactly using the binary notation, only the powers of five * affect the binary significand. */ /** * The smallest non-zero float (binary64) is 2^−1074. * We take as input numbers of the form w x 10^q where w < 2^64. * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. * However, we have that * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^−1074. * Thus it is possible for a number of the form w * 10^-342 where * w is a 64-bit value to be a non-zero floating-point number. ********* * Any number of form w * 10^309 where w>= 1 is going to be * infinite in binary64 so we never need to worry about powers * of 5 greater than 308. */ template struct powers_template { constexpr static int smallest_power_of_five = binary_format::smallest_power_of_ten(); constexpr static int largest_power_of_five = binary_format::largest_power_of_ten(); constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); // Powers of five from 5^-342 all the way to 5^308 rounded toward one. static const uint64_t power_of_five_128[number_of_entries]; }; template const uint64_t powers_template::power_of_five_128[number_of_entries] = { 0xeef453d6923bd65a,0x113faa2906a13b3f, 0x9558b4661b6565f8,0x4ac7ca59a424c507, 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, 0xe95a99df8ace6f53,0xf4d82c2c107973dc, 0x91d8a02bb6c10594,0x79071b9b8a4be869, 0xb64ec836a47146f9,0x9748e2826cdee284, 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, 0xb208ef855c969f4f,0xbdbd2d335e51a935, 0xde8b2b66b3bc4723,0xad2c788035e61382, 0x8b16fb203055ac76,0x4c3bcb5021afcc31, 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, 0xd953e8624b85dd78,0xd71d6dad34a2af0d, 0x87d4713d6f33aa6b,0x8672648c40e5ad68, 0xa9c98d8ccb009506,0x680efdaf511f18c2, 0xd43bf0effdc0ba48,0x212bd1b2566def2, 0x84a57695fe98746d,0x14bb630f7604b57, 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, 0xcf42894a5dce35ea,0x52064cac828675b9, 0x818995ce7aa0e1b2,0x7343efebd1940993, 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, 0xca66fa129f9b60a6,0xd41a26e077774ef6, 0xfd00b897478238d0,0x8920b098955522b4, 0x9e20735e8cb16382,0x55b46e5f5d5535b0, 0xc5a890362fddbc62,0xeb2189f734aa831d, 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, 0x9a6bb0aa55653b2d,0x47b233c92125366e, 0xc1069cd4eabe89f8,0x999ec0bb696e840a, 0xf148440a256e2c76,0xc00670ea43ca250d, 0x96cd2a865764dbca,0x380406926a5e5728, 0xbc807527ed3e12bc,0xc605083704f5ecf2, 0xeba09271e88d976b,0xf7864a44c633682e, 0x93445b8731587ea3,0x7ab3ee6afbe0211d, 0xb8157268fdae9e4c,0x5960ea05bad82964, 0xe61acf033d1a45df,0x6fb92487298e33bd, 0x8fd0c16206306bab,0xa5d3b6d479f8e056, 0xb3c4f1ba87bc8696,0x8f48a4899877186c, 0xe0b62e2929aba83c,0x331acdabfe94de87, 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, 0x892731ac9faf056e,0xbe311c083a225cd2, 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, 0xd64d3d9db981787d,0x92cbbccdad5b108, 0x85f0468293f0eb4e,0x25bbf56008c58ea5, 0xa76c582338ed2621,0xaf2af2b80af6f24e, 0xd1476e2c07286faa,0x1af5af660db4aee1, 0x82cca4db847945ca,0x50d98d9fc890ed4d, 0xa37fce126597973c,0xe50ff107bab528a0, 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, 0x9faacf3df73609b1,0x77b191618c54e9ac, 0xc795830d75038c1d,0xd59df5b9ef6a2417, 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, 0x9becce62836ac577,0x4ee367f9430aec32, 0xc2e801fb244576d5,0x229c41f793cda73f, 0xf3a20279ed56d48a,0x6b43527578c1110f, 0x9845418c345644d6,0x830a13896b78aaa9, 0xbe5691ef416bd60c,0x23cc986bc656d553, 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, 0x91376c36d99995be,0x23100809b9c21fa1, 0xb58547448ffffb2d,0xabd40a0c2832a78a, 0xe2e69915b3fff9f9,0x16c90c8f323f516c, 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, 0xb1442798f49ffb4a,0x99cd11cfdf41779c, 0xdd95317f31c7fa1d,0x40405643d711d583, 0x8a7d3eef7f1cfc52,0x482835ea666b2572, 0xad1c8eab5ee43b66,0xda3243650005eecf, 0xd863b256369d4a40,0x90bed43e40076a82, 0x873e4f75e2224e68,0x5a7744a6e804a291, 0xa90de3535aaae202,0x711515d0a205cb36, 0xd3515c2831559a83,0xd5a5b44ca873e03, 0x8412d9991ed58091,0xe858790afe9486c2, 0xa5178fff668ae0b6,0x626e974dbe39a872, 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, 0xa139029f6a239f72,0x1c1fffc1ebc44e80, 0xc987434744ac874e,0xa327ffb266b56220, 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, 0xc4ce17b399107c22,0xcb550fb4384d21d3, 0xf6019da07f549b2b,0x7e2a53a146606a48, 0x99c102844f94e0fb,0x2eda7444cbfc426d, 0xc0314325637a1939,0xfa911155fefb5308, 0xf03d93eebc589f88,0x793555ab7eba27ca, 0x96267c7535b763b5,0x4bc1558b2f3458de, 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, 0xea9c227723ee8bcb,0x465e15a979c1cadc, 0x92a1958a7675175f,0xbfacd89ec191ec9, 0xb749faed14125d36,0xcef980ec671f667b, 0xe51c79a85916f484,0x82b7e12780e7401a, 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, 0xdfbdcece67006ac9,0x67a791e093e1d49a, 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, 0xaecc49914078536d,0x58fae9f773886e18, 0xda7f5bf590966848,0xaf39a475506a899e, 0x888f99797a5e012d,0x6d8406c952429603, 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, 0x855c3be0a17fcd26,0x5cf2eea09a55067f, 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, 0xd0601d8efc57b08b,0xf13b94daf124da26, 0x823c12795db6ce57,0x76c53d08d6b70858, 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, 0xfe5d54150b090b02,0xd3f93b35435d7c4c, 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, 0xc6b8e9b0709f109a,0x359ab6419ca1091b, 0xf867241c8cc6d4c0,0xc30163d203c94b62, 0x9b407691d7fc44f8,0x79e0de63425dcf1d, 0xc21094364dfb5636,0x985915fc12f542e4, 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, 0xbd8430bd08277231,0x50c6ff782a838353, 0xece53cec4a314ebd,0xa4f8bf5635246428, 0x940f4613ae5ed136,0x871b7795e136be99, 0xb913179899f68584,0x28e2557b59846e3f, 0xe757dd7ec07426e5,0x331aeada2fe589cf, 0x9096ea6f3848984f,0x3ff0d2c85def7621, 0xb4bca50b065abe63,0xfed077a756b53a9, 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, 0xb080392cc4349dec,0xbd8d794d96aacfb3, 0xdca04777f541c567,0xecf0d7a0fc5583a0, 0x89e42caaf9491b60,0xf41686c49db57244, 0xac5d37d5b79b6239,0x311c2875c522ced5, 0xd77485cb25823ac7,0x7d633293366b828b, 0x86a8d39ef77164bc,0xae5dff9c02033197, 0xa8530886b54dbdeb,0xd9f57f830283fdfc, 0xd267caa862a12d66,0xd072df63c324fd7b, 0x8380dea93da4bc60,0x4247cb9e59f71e6d, 0xa46116538d0deb78,0x52d9be85f074e608, 0xcd795be870516656,0x67902e276c921f8b, 0x806bd9714632dff6,0xba1cd8a3db53b6, 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, 0xfad2a4b13d1b5d6c,0x796b805720085f81, 0x9cc3a6eec6311a63,0xcbe3303674053bb0, 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, 0xf4f1b4d515acb93b,0xee92fb5515482d44, 0x991711052d8bf3c5,0x751bdd152d4d1c4a, 0xbf5cd54678eef0b6,0xd262d45a78a0635d, 0xef340a98172aace4,0x86fb897116c87c34, 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, 0xbae0a846d2195712,0x8974836059cca109, 0xe998d258869facd7,0x2bd1a438703fc94b, 0x91ff83775423cc06,0x7b6306a34627ddcf, 0xb67f6455292cbf08,0x1a3bc84c17b1d542, 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, 0x8e938662882af53e,0x547eb47b7282ee9c, 0xb23867fb2a35b28d,0xe99e619a4f23aa43, 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, 0xae0b158b4738705e,0x9624ab50b148d445, 0xd98ddaee19068c76,0x3badd624dd9b0957, 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, 0xd47487cc8470652b,0x7647c3200069671f, 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, 0xa5fb0a17c777cf09,0xf468107100525890, 0xcf79cc9db955c2cc,0x7182148d4066eeb4, 0x81ac1fe293d599bf,0xc6f14cd848405530, 0xa21727db38cb002f,0xb8ada00e5a506a7c, 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, 0xfd442e4688bd304a,0x908f4a166d1da663, 0x9e4a9cec15763e2e,0x9a598e4e043287fe, 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, 0xf7549530e188c128,0xd12bee59e68ef47c, 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, 0xc13a148e3032d6e7,0xe36a52363c1faf01, 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, 0xebdf661791d60f56,0x111b495b3464ad21, 0x936b9fcebb25c995,0xcab10dd900beec34, 0xb84687c269ef3bfb,0x3d5d514f40eea742, 0xe65829b3046b0afa,0xcb4a5a3112a5112, 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, 0xb3f4e093db73a093,0x59ed216765690f56, 0xe0f218b8d25088b8,0x306869c13ec3532c, 0x8c974f7383725573,0x1e414218c73a13fb, 0xafbd2350644eeacf,0xe5d1929ef90898fa, 0xdbac6c247d62a583,0xdf45f746b74abf39, 0x894bc396ce5da772,0x6b8bba8c328eb783, 0xab9eb47c81f5114f,0x66ea92f3f326564, 0xd686619ba27255a2,0xc80a537b0efefebd, 0x8613fd0145877585,0xbd06742ce95f5f36, 0xa798fc4196e952e7,0x2c48113823b73704, 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, 0x82ef85133de648c4,0x9a984d73dbe722fb, 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, 0xcc963fee10b7d1b3,0x318df905079926a8, 0xffbbcfe994e5c61f,0xfdf17746497f7052, 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, 0x9c1661a651213e2d,0x6bea10ca65c084e, 0xc31bfa0fe5698db8,0x486e494fcff30a62, 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, 0x986ddb5c6b3a76b7,0xf89629465a75e01c, 0xbe89523386091465,0xf6bbb397f1135823, 0xee2ba6c0678b597f,0x746aa07ded582e2c, 0x94db483840b717ef,0xa8c2a44eb4571cdc, 0xba121a4650e4ddeb,0x92f34d62616ce413, 0xe896a0d7e51e1566,0x77b020baf9c81d17, 0x915e2486ef32cd60,0xace1474dc1d122e, 0xb5b5ada8aaff80b8,0xd819992132456ba, 0xe3231912d5bf60e6,0x10e1fff697ed6c69, 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, 0xad4ab7112eb3929d,0x86c16c98d2c953c6, 0xd89d64d57a607744,0xe871c7bf077ba8b7, 0x87625f056c7c4a8b,0x11471cd764ad4972, 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, 0xd389b47879823479,0x4aff1d108d4ec2c3, 0x843610cb4bf160cb,0xcedf722a585139ba, 0xa54394fe1eedb8fe,0xc2974eb4ee658828, 0xce947a3da6a9273e,0x733d226229feea32, 0x811ccc668829b887,0x806357d5a3f525f, 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, 0xc9bcff6034c13052,0xfc89b393dd02f0b5, 0xfc2c3f3841f17c67,0xbbac2078d443ace2, 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, 0xc5029163f384a931,0xa9e795e65d4df11, 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, 0x99ea0196163fa42e,0x504bced1bf8e4e45, 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, 0xf07da27a82c37088,0x5d767327bb4e5a4c, 0x964e858c91ba2655,0x3a6a07f8d510f86f, 0xbbe226efb628afea,0x890489f70a55368b, 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, 0xb77ada0617e3bbcb,0x9ce6ebb40173744, 0xe55990879ddcaabd,0xcc420a6a101d0515, 0x8f57fa54c2a9eab6,0x9fa946824a12232d, 0xb32df8e9f3546564,0x47939822dc96abf9, 0xdff9772470297ebd,0x59787e2b93bc56f7, 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, 0xaefae51477a06b03,0xede622920b6b23f1, 0xdab99e59958885c4,0xe95fab368e45eced, 0x88b402f7fd75539b,0x11dbcb0218ebb414, 0xaae103b5fcd2a881,0xd652bdc29f26a119, 0xd59944a37c0752a2,0x4be76d3346f0495f, 0x857fcae62d8493a5,0x6f70a4400c562ddb, 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, 0xd097ad07a71f26b2,0x7e2000a41346a7a7, 0x825ecc24c873782f,0x8ed400668c0c28c8, 0xa2f67f2dfa90563b,0x728900802f0f32fa, 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, 0xfea126b7d78186bc,0xe2f610c84987bfa8, 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, 0xc6ede63fa05d3143,0x91503d1c79720dbb, 0xf8a95fcf88747d94,0x75a44c6397ce912a, 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, 0xc24452da229b021b,0xfbe85badce996168, 0xf2d56790ab41c2a2,0xfae27299423fb9c3, 0x97c560ba6b0919a5,0xdccd879fc967d41a, 0xbdb6b8e905cb600f,0x5400e987bbc1c920, 0xed246723473e3813,0x290123e9aab23b68, 0x9436c0760c86e30b,0xf9a0b6720aaf6521, 0xb94470938fa89bce,0xf808e40e8d5b3e69, 0xe7958cb87392c2c2,0xb60b1d1230b20e04, 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, 0xe2280b6c20dd5232,0x25c6da63c38de1b0, 0x8d590723948a535f,0x579c487e5a38ad0e, 0xb0af48ec79ace837,0x2d835a9df0c6d851, 0xdcdb1b2798182244,0xf8e431456cf88e65, 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, 0xac8b2d36eed2dac5,0xe272467e3d222f3f, 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, 0x86ccbb52ea94baea,0x98e947129fc2b4e9, 0xa87fea27a539e9a5,0x3f2398d747b36224, 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, 0x83a3eeeef9153e89,0x1953cf68300424ac, 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, 0xcdb02555653131b6,0x3792f412cb06794d, 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, 0xc8de047564d20a8b,0xf245825a5a445275, 0xfb158592be068d2e,0xeed6e2f0f0d56712, 0x9ced737bb6c4183d,0x55464dd69685606b, 0xc428d05aa4751e4c,0xaa97e14c3c26b886, 0xf53304714d9265df,0xd53dd99f4b3066a8, 0x993fe2c6d07b7fab,0xe546a8038efe4029, 0xbf8fdb78849a5f96,0xde98520472bdd033, 0xef73d256a5c0f77c,0x963e66858f6d4440, 0x95a8637627989aad,0xdde7001379a44aa8, 0xbb127c53b17ec159,0x5560c018580d5d52, 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, 0x9226712162ab070d,0xcab3961304ca70e8, 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, 0xb267ed1940f1c61c,0x55f038b237591ed3, 0xdf01e85f912e37a3,0x6b6c46dec52f6688, 0x8b61313bbabce2c6,0x2323ac4b3b3da015, 0xae397d8aa96c1b77,0xabec975e0a0d081a, 0xd9c7dced53c72255,0x96e7bd358c904a21, 0x881cea14545c7575,0x7e50d64177da2e54, 0xaa242499697392d2,0xdde50bd1d5d0b9e9, 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, 0x84ec3c97da624ab4,0xbd5af13bef0b113e, 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, 0xcfb11ead453994ba,0x67de18eda5814af2, 0x81ceb32c4b43fcf4,0x80eacf948770ced7, 0xa2425ff75e14fc31,0xa1258379a94d028d, 0xcad2f7f5359a3b3e,0x96ee45813a04330, 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, 0x9e74d1b791e07e48,0x775ea264cf55347e, 0xc612062576589dda,0x95364afe032a819e, 0xf79687aed3eec551,0x3a83ddbd83f52205, 0x9abe14cd44753b52,0xc4926a9672793543, 0xc16d9a0095928a27,0x75b7053c0f178294, 0xf1c90080baf72cb1,0x5324c68b12dd6339, 0x971da05074da7bee,0xd3f6fc16ebca5e04, 0xbce5086492111aea,0x88f4bb1ca6bcf585, 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6, 0x9392ee8e921d5d07,0x3aff322e62439fd0, 0xb877aa3236a4b449,0x9befeb9fad487c3, 0xe69594bec44de15b,0x4c2ebe687989a9b4, 0x901d7cf73ab0acd9,0xf9d37014bf60a11, 0xb424dc35095cd80f,0x538484c19ef38c95, 0xe12e13424bb40e13,0x2865a5f206b06fba, 0x8cbccc096f5088cb,0xf93f87b7442e45d4, 0xafebff0bcb24aafe,0xf78f69a51539d749, 0xdbe6fecebdedd5be,0xb573440e5a884d1c, 0x89705f4136b4a597,0x31680a88f8953031, 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, 0xd6bf94d5e57a42bc,0x3d32907604691b4d, 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, 0xa7c5ac471b478423,0xfcf80dc33721d54, 0xd1b71758e219652b,0xd3c36113404ea4a9, 0x83126e978d4fdf3b,0x645a1cac083126ea, 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, 0xcccccccccccccccc,0xcccccccccccccccd, 0x8000000000000000,0x0, 0xa000000000000000,0x0, 0xc800000000000000,0x0, 0xfa00000000000000,0x0, 0x9c40000000000000,0x0, 0xc350000000000000,0x0, 0xf424000000000000,0x0, 0x9896800000000000,0x0, 0xbebc200000000000,0x0, 0xee6b280000000000,0x0, 0x9502f90000000000,0x0, 0xba43b74000000000,0x0, 0xe8d4a51000000000,0x0, 0x9184e72a00000000,0x0, 0xb5e620f480000000,0x0, 0xe35fa931a0000000,0x0, 0x8e1bc9bf04000000,0x0, 0xb1a2bc2ec5000000,0x0, 0xde0b6b3a76400000,0x0, 0x8ac7230489e80000,0x0, 0xad78ebc5ac620000,0x0, 0xd8d726b7177a8000,0x0, 0x878678326eac9000,0x0, 0xa968163f0a57b400,0x0, 0xd3c21bcecceda100,0x0, 0x84595161401484a0,0x0, 0xa56fa5b99019a5c8,0x0, 0xcecb8f27f4200f3a,0x0, 0x813f3978f8940984,0x4000000000000000, 0xa18f07d736b90be5,0x5000000000000000, 0xc9f2c9cd04674ede,0xa400000000000000, 0xfc6f7c4045812296,0x4d00000000000000, 0x9dc5ada82b70b59d,0xf020000000000000, 0xc5371912364ce305,0x6c28000000000000, 0xf684df56c3e01bc6,0xc732000000000000, 0x9a130b963a6c115c,0x3c7f400000000000, 0xc097ce7bc90715b3,0x4b9f100000000000, 0xf0bdc21abb48db20,0x1e86d40000000000, 0x96769950b50d88f4,0x1314448000000000, 0xbc143fa4e250eb31,0x17d955a000000000, 0xeb194f8e1ae525fd,0x5dcfab0800000000, 0x92efd1b8d0cf37be,0x5aa1cae500000000, 0xb7abc627050305ad,0xf14a3d9e40000000, 0xe596b7b0c643c719,0x6d9ccd05d0000000, 0x8f7e32ce7bea5c6f,0xe4820023a2000000, 0xb35dbf821ae4f38b,0xdda2802c8a800000, 0xe0352f62a19e306e,0xd50b2037ad200000, 0x8c213d9da502de45,0x4526f422cc340000, 0xaf298d050e4395d6,0x9670b12b7f410000, 0xdaf3f04651d47b4c,0x3c0cdd765f114000, 0x88d8762bf324cd0f,0xa5880a69fb6ac800, 0xab0e93b6efee0053,0x8eea0d047a457a00, 0xd5d238a4abe98068,0x72a4904598d6d880, 0x85a36366eb71f041,0x47a6da2b7f864750, 0xa70c3c40a64e6c51,0x999090b65f67d924, 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, 0x82818f1281ed449f,0xbff8f10e7a8921a4, 0xa321f2d7226895c7,0xaff72d52192b6a0d, 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, 0xfee50b7025c36a08,0x2f236d04753d5b4, 0x9f4f2726179a2245,0x1d762422c946590, 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, 0x9b934c3b330c8577,0x63cc55f49f88eb2f, 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, 0xf316271c7fc3908a,0x8bef464e3945ef7a, 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, 0xb975d6b6ee39e436,0xb3e2fd538e122b44, 0xe7d34c64a9c85d44,0x60dbbca87196b616, 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, 0xb51d13aea4a488dd,0x6babab6398bdbe41, 0xe264589a4dcdab14,0xc696963c7eed2dd1, 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, 0xb0de65388cc8ada8,0x3b25a55f43294bcb, 0xdd15fe86affad912,0x49ef0eb713f39ebe, 0x8a2dbf142dfcc7ab,0x6e3569326c784337, 0xacb92ed9397bf996,0x49c2c37f07965404, 0xd7e77a8f87daf7fb,0xdc33745ec97be906, 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, 0xd2d80db02aabd62b,0xf50a3fa490c30190, 0x83c7088e1aab65db,0x792667c6da79e0fa, 0xa4b8cab1a1563f52,0x577001b891185938, 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, 0x80b05e5ac60b6178,0x544f8158315b05b4, 0xa0dc75f1778e39d6,0x696361ae3db1c721, 0xc913936dd571c84c,0x3bc3a19cd1e38e9, 0xfb5878494ace3a5f,0x4ab48a04065c723, 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, 0xc45d1df942711d9a,0x3ba5d0bd324f8394, 0xf5746577930d6500,0xca8f44ec7ee36479, 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, 0x95d04aee3b80ece5,0xbba1f1d158724a12, 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, 0xea1575143cf97226,0xf52d09d71a3293bd, 0x924d692ca61be758,0x593c2626705f9c56, 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, 0xe498f455c38b997a,0xb6dfb9c0f956447, 0x8edf98b59a373fec,0x4724bd4189bd5eac, 0xb2977ee300c50fe7,0x58edec91ec2cb657, 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, 0x8b865b215899f46c,0xbd79e0d20082ee74, 0xae67f1e9aec07187,0xecd8590680a3aa11, 0xda01ee641a708de9,0xe80e6f4820cc9495, 0x884134fe908658b2,0x3109058d147fdcdd, 0xaa51823e34a7eede,0xbd4b46f0599fd415, 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, 0x850fadc09923329e,0x3e2cf6bc604ddb0, 0xa6539930bf6bff45,0x84db8346b786151c, 0xcfe87f7cef46ff16,0xe612641865679a63, 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, 0xa26da3999aef7749,0xe3be5e330f38f09d, 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, 0xc646d63501a1511d,0xb281e1fd541501b8, 0xf7d88bc24209a565,0x1f225a7ca91a4226, 0x9ae757596946075f,0x3375788de9b06958, 0xc1a12d2fc3978937,0x52d6b1641c83ae, 0xf209787bb47d6b84,0xc0678c5dbd23a49a, 0x9745eb4d50ce6332,0xf840b7ba963646e0, 0xbd176620a501fbff,0xb650e5a93bc3d898, 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, 0x93ba47c980e98cdf,0xc66f336c36b10137, 0xb8a8d9bbe123f017,0xb80b0047445d4184, 0xe6d3102ad96cec1d,0xa60dc059157491e5, 0x9043ea1ac7e41392,0x87c89837ad68db2f, 0xb454e4a179dd1877,0x29babe4598c311fb, 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, 0x8ce2529e2734bb1d,0x1899e4a65f58660c, 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, 0xdc21a1171d42645d,0x76707543f4fa1f73, 0x899504ae72497eba,0x6a06494a791c53a8, 0xabfa45da0edbde69,0x487db9d17636892, 0xd6f8d7509292d603,0x45a9d2845d3c42b6, 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, 0xa7f26836f282b732,0x8e6cac7768d7141e, 0xd1ef0244af2364ff,0x3207d795430cd926, 0x8335616aed761f1f,0x7f44e6bd49e807b8, 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, 0xcd036837130890a1,0x36dba887c37a8c0f, 0x802221226be55a64,0xc2494954da2c9789, 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, 0xc83553c5c8965d3d,0x6f92829494e5acc7, 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, 0x9c69a97284b578d7,0xff2a760414536efb, 0xc38413cf25e2d70d,0xfef5138519684aba, 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, 0x98bf2f79d5993802,0xef2f773ffbd97a61, 0xbeeefb584aff8603,0xaafb550ffacfd8fa, 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, 0x952ab45cfa97a0b2,0xdd945a747bf26183, 0xba756174393d88df,0x94f971119aeef9e4, 0xe912b9d1478ceb17,0x7a37cd5601aab85d, 0x91abb422ccb812ee,0xac62e055c10ab33a, 0xb616a12b7fe617aa,0x577b986b314d6009, 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, 0x8e41ade9fbebc27d,0x14588f13be847307, 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, 0x8aec23d680043bee,0x25de7bb9480d5854, 0xada72ccc20054ae9,0xaf561aa79a10ae6a, 0xd910f7ff28069da4,0x1b2ba1518094da04, 0x87aa9aff79042286,0x90fb44d2f05d0842, 0xa99541bf57452b28,0x353a1607ac744a53, 0xd3fa922f2d1675f2,0x42889b8997915ce8, 0x847c9b5d7c2e09b7,0x69956135febada11, 0xa59bc234db398c25,0x43fab9837e699095, 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, 0x8161afb94b44f57d,0x1d1be0eebac278f5, 0xa1ba1ba79e1632dc,0x6462d92a69731732, 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, 0xfcb2cb35e702af78,0x5cda735244c3d43e, 0x9defbf01b061adab,0x3a0888136afa64a7, 0xc56baec21c7a1916,0x88aaa1845b8fdd0, 0xf6c69a72a3989f5b,0x8aad549e57273d45, 0x9a3c2087a63f6399,0x36ac54e2f678864b, 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, 0x969eb7c47859e743,0x9f644ae5a4b1b325, 0xbc4665b596706114,0x873d5d9f0dde1fee, 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, 0x9316ff75dd87cbd8,0x9a7f12442d588f2, 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, 0x8fa475791a569d10,0xf96e017d694487bc, 0xb38d92d760ec4455,0x37c981dcc395a9ac, 0xe070f78d3927556a,0x85bbe253f47b1417, 0x8c469ab843b89562,0x93956d7478ccec8e, 0xaf58416654a6babb,0x387ac8d1970027b2, 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, 0x88fcf317f22241e2,0x441fece3bdf81f03, 0xab3c2fddeeaad25a,0xd527e81cad7626c3, 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, 0x85c7056562757456,0xf6872d5667844e49, 0xa738c6bebb12d16c,0xb428f8ac016561db, 0xd106f86e69d785c7,0xe13336d701beba52, 0x82a45b450226b39c,0xecc0024661173473, 0xa34d721642b06084,0x27f002d7f95d0190, 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, 0xff290242c83396ce,0x7e67047175a15271, 0x9f79a169bd203e41,0xf0062c6e984d386, 0xc75809c42c684dd1,0x52c07b78a3e60868, 0xf92e0c3537826145,0xa7709a56ccdf8a82, 0x9bbcc7a142b17ccb,0x88a66076400bb691, 0xc2abf989935ddbfe,0x6acff893d00ea435, 0xf356f7ebf83552fe,0x583f6b8c4124d43, 0x98165af37b2153de,0xc3727a337a8b704a, 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, 0xeda2ee1c7064130c,0x1162def06f79df73, 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, 0x910ab1d4db9914a0,0x1d9c9892400a22a2, 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, 0x8da471a9de737e24,0x5ceaecfed289e5d2, 0xb10d8e1456105dad,0x7425a83e872c5f47, 0xdd50f1996b947518,0xd12f124e28f77719, 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, 0xace73cbfdc0bfb7b,0x636cc64d1001550b, 0xd8210befd30efa5a,0x3c47f7e05401aa4e, 0x8714a775e3e95c78,0x65acfaec34810a71, 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, 0xd31045a8341ca07c,0x1ede48111209a050, 0x83ea2b892091e44d,0x934aed0aab460432, 0xa4e4b66b68b65d60,0xf81da84d5617853f, 0xce1de40642e3f4b9,0x36251260ab9d668e, 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, 0xa1075a24e4421730,0xb24cf65b8612f81f, 0xc94930ae1d529cfc,0xdee033f26797b627, 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, 0x9d412e0806e88aa5,0x8e1f289560ee864e, 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, 0xf5b5d7ec8acb58a2,0xae10af696774b1db, 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, 0xbff610b0cc6edd3f,0x17fd090a58d32af3, 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, 0xea53df5fd18d5513,0x84c86189216dc5ed, 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, 0xe4d5e82392a40515,0xfabaf3feaa5334a, 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, 0xb2c71d5bca9023f8,0x743e20e9ef511012, 0xdf78e4b2bd342cf6,0x914da9246b255416, 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, 0xae9672aba3d0c320,0xa184ac2473b529b1, 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, 0x8865899617fb1871,0x7e2fa67c7a658892, 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, 0xd51ea6fa85785631,0x552a74227f3ea565, 0x8533285c936b35de,0xd53a88958f87275f, 0xa67ff273b8460356,0x8a892abaf368f137, 0xd01fef10a657842c,0x2d2b7569b0432d85, 0x8213f56a67f6b29b,0x9c3b29620e29fc73, 0xa298f2c501f45f42,0x8349f3ba91b47b8f, 0xcb3f2f7642717713,0x241c70a936219a73, 0xfe0efb53d30dd4d7,0xed238cd383aa0110, 0x9ec95d1463e8a506,0xf4363804324a40aa, 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, 0xf81aa16fdc1b81da,0xdd94b7868e94050a, 0x9b10a4e5e9913128,0xca7cf2b4191c8326, 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, 0xf24a01a73cf2dccf,0xbc633b39673c8cec, 0x976e41088617ca01,0xd5be0503e085d813, 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, 0xec9c459d51852ba2,0xddf8e7d60ed1219e, 0x93e1ab8252f33b45,0xcabb90e5c942b503, 0xb8da1662e7b00a17,0x3d6a751f3b936243, 0xe7109bfba19c0c9d,0xcc512670a783ad4, 0x906a617d450187e2,0x27fb2b80668b24c5, 0xb484f9dc9641e9da,0xb1f9f660802dedf6, 0xe1a63853bbd26451,0x5e7873f8a0396973, 0x8d07e33455637eb2,0xdb0b487b6423e1e8, 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, 0xdc5c5301c56b75f7,0x7641a140cc7810fb, 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, 0xac2820d9623bf429,0x546345fa9fbdcd44, 0xd732290fbacaf133,0xa97c177947ad4095, 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, 0xa81f301449ee8c70,0x5c68f256bfff5a74, 0xd226fc195c6a2f8c,0x73832eec6fff3111, 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, 0xa0555e361951c366,0xd7e105bcc332621f, 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, 0xfa856334878fc150,0xb14f98f6f0feb951, 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, 0xc3b8358109e84f07,0xa862f80ec4700c8, 0xf4a642e14c6262c8,0xcd27bb612758c0fa, 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, 0xeeea5d5004981478,0x1858ccfce06cac74, 0x95527a5202df0ccb,0xf37801e0c43ebc8, 0xbaa718e68396cffd,0xd30560258f54e6ba, 0xe950df20247c83fd,0x47c6b82ef32a2069, 0x91d28b7416cdd27e,0x4cdc331d57fa5441, 0xb6472e511c81471d,0xe0133fe4adf8e952, 0xe3d8f9e563a198e5,0x58180fddd97723a6, 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; using powers = powers_template<>; } #endif #ifndef FASTFLOAT_DECIMAL_TO_BINARY_H #define FASTFLOAT_DECIMAL_TO_BINARY_H #include #include #include #include #include #include namespace fast_float { // This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating // the result, with the "high" part corresponding to the most significant bits and the // low part corresponding to the least significant bits. // template fastfloat_really_inline value128 compute_product_approximation(int64_t q, uint64_t w) { const int index = 2 * int(q - powers::smallest_power_of_five); // For small values of q, e.g., q in [0,27], the answer is always exact because // The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]); // gives the exact answer. value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); constexpr uint64_t precision_mask = (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) : uint64_t(0xFFFFFFFFFFFFFFFF); if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) // regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); firstproduct.low += secondproduct.high; if(secondproduct.high > firstproduct.low) { firstproduct.high++; } } return firstproduct; } namespace detail { /** * For q in (0,350), we have that * f = (((152170 + 65536) * q ) >> 16); * is equal to * floor(p) + q * where * p = log(5**q)/log(2) = q * log(5)/log(2) * * For negative values of q in (-400,0), we have that * f = (((152170 + 65536) * q ) >> 16); * is equal to * -ceil(p) + q * where * p = log(5**-q)/log(2) = -q * log(5)/log(2) */ constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { return (((152170 + 65536) * q) >> 16) + 63; } } // namespace detail // create an adjusted mantissa, biased by the invalid power2 // for significant digits already multiplied by 10 ** q. template fastfloat_really_inline adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { int hilz = int(w >> 63) ^ 1; adjusted_mantissa answer; answer.mantissa = w << hilz; int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias); return answer; } // w * 10 ** q, without rounding the representation up. // the power2 in the exponent will be adjusted by invalid_am_bias. template fastfloat_really_inline adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { int lz = leading_zeroes(w); w <<= lz; value128 product = compute_product_approximation(q, w); return compute_error_scaled(q, product.high, lz); } // w * 10 ** q // The returned value should be a valid ieee64 number that simply need to be packed. // However, in some very rare cases, the computation will fail. In such cases, we // return an adjusted_mantissa with a negative power of 2: the caller should recompute // in such cases. template fastfloat_really_inline adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { adjusted_mantissa answer; if ((w == 0) || (q < binary::smallest_power_of_ten())) { answer.power2 = 0; answer.mantissa = 0; // result should be zero return answer; } if (q > binary::largest_power_of_ten()) { // we want to get infinity: answer.power2 = binary::infinite_power(); answer.mantissa = 0; return answer; } // At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five]. // We want the most significant bit of i to be 1. Shift if needed. int lz = leading_zeroes(w); w <<= lz; // The required precision is binary::mantissa_explicit_bits() + 3 because // 1. We need the implicit bit // 2. We need an extra bit for rounding purposes // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) value128 product = compute_product_approximation(q, w); if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further // In some very rare cases, this could happen, in which case we might need a more accurate // computation that what we can provide cheaply. This is very, very unlikely. // const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0, // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation. if(!inside_safe_exponent) { return compute_error_scaled(q, product.high, lz); } } // The "compute_product_approximation" function can be slightly slower than a branchless approach: // value128 product = compute_product(q, w); // but in practice, we can win big with the compute_product_approximation if its additional branch // is easily predicted. Which is best is data specific. int upperbit = int(product.high >> 63); answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent()); if (answer.power2 <= 0) { // we have a subnormal? // Here have that answer.power2 <= 0 so -answer.power2 >= 0 if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. answer.power2 = 0; answer.mantissa = 0; // result should be zero return answer; } // next line is safe because -answer.power2 + 1 < 64 answer.mantissa >>= -answer.power2 + 1; // Thankfully, we can't have both "round-to-even" and subnormals because // "round-to-even" only occurs for powers close to 0. answer.mantissa += (answer.mantissa & 1); // round up answer.mantissa >>= 1; // There is a weird scenario where we don't have a subnormal but just. // Suppose we start with 2.2250738585072013e-308, we end up // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer // subnormal, but we can only know this after rounding. // So we only declare a subnormal if we are smaller than the threshold. answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; return answer; } // usually, we round *up*, but if we fall right in between and and we have an // even basis, we need to round down // We are only concerned with the cases where 5**q fits in single 64-bit word. if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && ((answer.mantissa & 3) == 1) ) { // we may fall between two floats! // To be in-between two floats we need that in doing // answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); // ... we dropped out only zeroes. But if this happened, then we can go back!!! if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) { answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up } } answer.mantissa += (answer.mantissa & 1); // round up answer.mantissa >>= 1; if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); answer.power2++; // undo previous addition } answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); if (answer.power2 >= binary::infinite_power()) { // infinity answer.power2 = binary::infinite_power(); answer.mantissa = 0; } return answer; } } // namespace fast_float #endif #ifndef FASTFLOAT_BIGINT_H #define FASTFLOAT_BIGINT_H #include #include #include #include namespace fast_float { // the limb width: we want efficient multiplication of double the bits in // limb, or for 64-bit limbs, at least 64-bit multiplication where we can // extract the high and low parts efficiently. this is every 64-bit // architecture except for sparc, which emulates 128-bit multiplication. // we might have platforms where `CHAR_BIT` is not 8, so let's avoid // doing `8 * sizeof(limb)`. #if defined(FASTFLOAT_64BIT) && !defined(__sparc) #define FASTFLOAT_64BIT_LIMB typedef uint64_t limb; constexpr size_t limb_bits = 64; #else #define FASTFLOAT_32BIT_LIMB typedef uint32_t limb; constexpr size_t limb_bits = 32; #endif typedef span limb_span; // number of bits in a bigint. this needs to be at least the number // of bits required to store the largest bigint, which is // `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or // ~3600 bits, so we round to 4000. constexpr size_t bigint_bits = 4000; constexpr size_t bigint_limbs = bigint_bits / limb_bits; // vector-like type that is allocated on the stack. the entire // buffer is pre-allocated, and only the length changes. template struct stackvec { limb data[size]; // we never need more than 150 limbs uint16_t length{0}; stackvec() = default; stackvec(const stackvec &) = delete; stackvec &operator=(const stackvec &) = delete; stackvec(stackvec &&) = delete; stackvec &operator=(stackvec &&other) = delete; // create stack vector from existing limb span. stackvec(limb_span s) { FASTFLOAT_ASSERT(try_extend(s)); } limb& operator[](size_t index) noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return data[index]; } const limb& operator[](size_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return data[index]; } // index from the end of the container const limb& rindex(size_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); size_t rindex = length - index - 1; return data[rindex]; } // set the length, without bounds checking. void set_len(size_t len) noexcept { length = uint16_t(len); } constexpr size_t len() const noexcept { return length; } constexpr bool is_empty() const noexcept { return length == 0; } constexpr size_t capacity() const noexcept { return size; } // append item to vector, without bounds checking void push_unchecked(limb value) noexcept { data[length] = value; length++; } // append item to vector, returning if item was added bool try_push(limb value) noexcept { if (len() < capacity()) { push_unchecked(value); return true; } else { return false; } } // add items to the vector, from a span, without bounds checking void extend_unchecked(limb_span s) noexcept { limb* ptr = data + length; ::memcpy((void*)ptr, (const void*)s.ptr, sizeof(limb) * s.len()); set_len(len() + s.len()); } // try to add items to the vector, returning if items were added bool try_extend(limb_span s) noexcept { if (len() + s.len() <= capacity()) { extend_unchecked(s); return true; } else { return false; } } // resize the vector, without bounds checking // if the new size is longer than the vector, assign value to each // appended item. void resize_unchecked(size_t new_len, limb value) noexcept { if (new_len > len()) { size_t count = new_len - len(); limb* first = data + len(); limb* last = first + count; ::std::fill(first, last, value); set_len(new_len); } else { set_len(new_len); } } // try to resize the vector, returning if the vector was resized. bool try_resize(size_t new_len, limb value) noexcept { if (new_len > capacity()) { return false; } else { resize_unchecked(new_len, value); return true; } } // check if any limbs are non-zero after the given index. // this needs to be done in reverse order, since the index // is relative to the most significant limbs. bool nonzero(size_t index) const noexcept { while (index < len()) { if (rindex(index) != 0) { return true; } index++; } return false; } // normalize the big integer, so most-significant zero limbs are removed. void normalize() noexcept { while (len() > 0 && rindex(0) == 0) { length--; } } }; fastfloat_really_inline uint64_t empty_hi64(bool& truncated) noexcept { truncated = false; return 0; } fastfloat_really_inline uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept { truncated = false; int shl = leading_zeroes(r0); return r0 << shl; } fastfloat_really_inline uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept { int shl = leading_zeroes(r0); if (shl == 0) { truncated = r1 != 0; return r0; } else { int shr = 64 - shl; truncated = (r1 << shl) != 0; return (r0 << shl) | (r1 >> shr); } } fastfloat_really_inline uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept { return uint64_hi64(r0, truncated); } fastfloat_really_inline uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept { uint64_t x0 = r0; uint64_t x1 = r1; return uint64_hi64((x0 << 32) | x1, truncated); } fastfloat_really_inline uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept { uint64_t x0 = r0; uint64_t x1 = r1; uint64_t x2 = r2; return uint64_hi64(x0, (x1 << 32) | x2, truncated); } // add two small integers, checking for overflow. // we want an efficient operation. for msvc, where // we don't have built-in intrinsics, this is still // pretty fast. fastfloat_really_inline limb scalar_add(limb x, limb y, bool& overflow) noexcept { limb z; // gcc and clang #if defined(__has_builtin) #if __has_builtin(__builtin_add_overflow) overflow = __builtin_add_overflow(x, y, &z); return z; #endif #endif // generic, this still optimizes correctly on MSVC. z = x + y; overflow = z < x; return z; } // multiply two small integers, getting both the high and low bits. fastfloat_really_inline limb scalar_mul(limb x, limb y, limb& carry) noexcept { #ifdef FASTFLOAT_64BIT_LIMB #if defined(__SIZEOF_INT128__) // GCC and clang both define it as an extension. __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); carry = limb(z >> limb_bits); return limb(z); #else // fallback, no native 128-bit integer multiplication with carry. // on msvc, this optimizes identically, somehow. value128 z = full_multiplication(x, y); bool overflow; z.low = scalar_add(z.low, carry, overflow); z.high += uint64_t(overflow); // cannot overflow carry = z.high; return z.low; #endif #else uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); carry = limb(z >> limb_bits); return limb(z); #endif } // add scalar value to bigint starting from offset. // used in grade school multiplication template inline bool small_add_from(stackvec& vec, limb y, size_t start) noexcept { size_t index = start; limb carry = y; bool overflow; while (carry != 0 && index < vec.len()) { vec[index] = scalar_add(vec[index], carry, overflow); carry = limb(overflow); index += 1; } if (carry != 0) { FASTFLOAT_TRY(vec.try_push(carry)); } return true; } // add scalar value to bigint. template fastfloat_really_inline bool small_add(stackvec& vec, limb y) noexcept { return small_add_from(vec, y, 0); } // multiply bigint by scalar value. template inline bool small_mul(stackvec& vec, limb y) noexcept { limb carry = 0; for (size_t index = 0; index < vec.len(); index++) { vec[index] = scalar_mul(vec[index], y, carry); } if (carry != 0) { FASTFLOAT_TRY(vec.try_push(carry)); } return true; } // add bigint to bigint starting from index. // used in grade school multiplication template bool large_add_from(stackvec& x, limb_span y, size_t start) noexcept { // the effective x buffer is from `xstart..x.len()`, so exit early // if we can't get that current range. if (x.len() < start || y.len() > x.len() - start) { FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); } bool carry = false; for (size_t index = 0; index < y.len(); index++) { limb xi = x[index + start]; limb yi = y[index]; bool c1 = false; bool c2 = false; xi = scalar_add(xi, yi, c1); if (carry) { xi = scalar_add(xi, 1, c2); } x[index + start] = xi; carry = c1 | c2; } // handle overflow if (carry) { FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); } return true; } // add bigint to bigint. template fastfloat_really_inline bool large_add_from(stackvec& x, limb_span y) noexcept { return large_add_from(x, y, 0); } // grade-school multiplication algorithm template bool long_mul(stackvec& x, limb_span y) noexcept { limb_span xs = limb_span(x.data, x.len()); stackvec z(xs); limb_span zs = limb_span(z.data, z.len()); if (y.len() != 0) { limb y0 = y[0]; FASTFLOAT_TRY(small_mul(x, y0)); for (size_t index = 1; index < y.len(); index++) { limb yi = y[index]; stackvec zi; if (yi != 0) { // re-use the same buffer throughout zi.set_len(0); FASTFLOAT_TRY(zi.try_extend(zs)); FASTFLOAT_TRY(small_mul(zi, yi)); limb_span zis = limb_span(zi.data, zi.len()); FASTFLOAT_TRY(large_add_from(x, zis, index)); } } } x.normalize(); return true; } // grade-school multiplication algorithm template bool large_mul(stackvec& x, limb_span y) noexcept { if (y.len() == 1) { FASTFLOAT_TRY(small_mul(x, y[0])); } else { FASTFLOAT_TRY(long_mul(x, y)); } return true; } // big integer type. implements a small subset of big integer // arithmetic, using simple algorithms since asymptotically // faster algorithms are slower for a small number of limbs. // all operations assume the big-integer is normalized. struct bigint { // storage of the limbs, in little-endian order. stackvec vec; bigint(): vec() {} bigint(const bigint &) = delete; bigint &operator=(const bigint &) = delete; bigint(bigint &&) = delete; bigint &operator=(bigint &&other) = delete; bigint(uint64_t value): vec() { #ifdef FASTFLOAT_64BIT_LIMB vec.push_unchecked(value); #else vec.push_unchecked(uint32_t(value)); vec.push_unchecked(uint32_t(value >> 32)); #endif vec.normalize(); } // get the high 64 bits from the vector, and if bits were truncated. // this is to get the significant digits for the float. uint64_t hi64(bool& truncated) const noexcept { #ifdef FASTFLOAT_64BIT_LIMB if (vec.len() == 0) { return empty_hi64(truncated); } else if (vec.len() == 1) { return uint64_hi64(vec.rindex(0), truncated); } else { uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); truncated |= vec.nonzero(2); return result; } #else if (vec.len() == 0) { return empty_hi64(truncated); } else if (vec.len() == 1) { return uint32_hi64(vec.rindex(0), truncated); } else if (vec.len() == 2) { return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); } else { uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); truncated |= vec.nonzero(3); return result; } #endif } // compare two big integers, returning the large value. // assumes both are normalized. if the return value is // negative, other is larger, if the return value is // positive, this is larger, otherwise they are equal. // the limbs are stored in little-endian order, so we // must compare the limbs in ever order. int compare(const bigint& other) const noexcept { if (vec.len() > other.vec.len()) { return 1; } else if (vec.len() < other.vec.len()) { return -1; } else { for (size_t index = vec.len(); index > 0; index--) { limb xi = vec[index - 1]; limb yi = other.vec[index - 1]; if (xi > yi) { return 1; } else if (xi < yi) { return -1; } } return 0; } } // shift left each limb n bits, carrying over to the new limb // returns true if we were able to shift all the digits. bool shl_bits(size_t n) noexcept { // Internally, for each item, we shift left by n, and add the previous // right shifted limb-bits. // For example, we transform (for u8) shifted left 2, to: // b10100100 b01000010 // b10 b10010001 b00001000 FASTFLOAT_DEBUG_ASSERT(n != 0); FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); size_t shl = n; size_t shr = limb_bits - shl; limb prev = 0; for (size_t index = 0; index < vec.len(); index++) { limb xi = vec[index]; vec[index] = (xi << shl) | (prev >> shr); prev = xi; } limb carry = prev >> shr; if (carry != 0) { return vec.try_push(carry); } return true; } // move the limbs left by `n` limbs. bool shl_limbs(size_t n) noexcept { FASTFLOAT_DEBUG_ASSERT(n != 0); if (n + vec.len() > vec.capacity()) { return false; } else if (!vec.is_empty()) { // move limbs limb* dst = vec.data + n; const limb* src = vec.data; ::memmove(dst, src, sizeof(limb) * vec.len()); // fill in empty limbs limb* first = vec.data; limb* last = first + n; ::std::fill(first, last, 0); vec.set_len(n + vec.len()); return true; } else { return true; } } // move the limbs left by `n` bits. bool shl(size_t n) noexcept { size_t rem = n % limb_bits; size_t div = n / limb_bits; if (rem != 0) { FASTFLOAT_TRY(shl_bits(rem)); } if (div != 0) { FASTFLOAT_TRY(shl_limbs(div)); } return true; } // get the number of leading zeros in the bigint. int ctlz() const noexcept { if (vec.is_empty()) { return 0; } else { #ifdef FASTFLOAT_64BIT_LIMB return leading_zeroes(vec.rindex(0)); #else // no use defining a specialized leading_zeroes for a 32-bit type. uint64_t r0 = vec.rindex(0); return leading_zeroes(r0 << 32); #endif } } // get the number of bits in the bigint. int bit_length() const noexcept { int lz = ctlz(); return int(limb_bits * vec.len()) - lz; } bool mul(limb y) noexcept { return small_mul(vec, y); } bool add(limb y) noexcept { return small_add(vec, y); } // multiply as if by 2 raised to a power. bool pow2(uint32_t exp) noexcept { return shl(exp); } // multiply as if by 5 raised to a power. bool pow5(uint32_t exp) noexcept { // multiply by a power of 5 static constexpr uint32_t large_step = 135; static constexpr uint64_t small_power_of_5[] = { 1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL, 1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL, 6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL, 3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL, 2384185791015625UL, 11920928955078125UL, 59604644775390625UL, 298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL, }; #ifdef FASTFLOAT_64BIT_LIMB constexpr static limb large_power_of_5[] = { 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, 10482974169319127550UL, 198276706040285095UL}; #else constexpr static limb large_power_of_5[] = { 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; #endif size_t large_length = sizeof(large_power_of_5) / sizeof(limb); limb_span large = limb_span(large_power_of_5, large_length); while (exp >= large_step) { FASTFLOAT_TRY(large_mul(vec, large)); exp -= large_step; } #ifdef FASTFLOAT_64BIT_LIMB uint32_t small_step = 27; limb max_native = 7450580596923828125UL; #else uint32_t small_step = 13; limb max_native = 1220703125U; #endif while (exp >= small_step) { FASTFLOAT_TRY(small_mul(vec, max_native)); exp -= small_step; } if (exp != 0) { FASTFLOAT_TRY(small_mul(vec, limb(small_power_of_5[exp]))); } return true; } // multiply as if by 10 raised to a power. bool pow10(uint32_t exp) noexcept { FASTFLOAT_TRY(pow5(exp)); return pow2(exp); } }; } // namespace fast_float #endif #ifndef FASTFLOAT_ASCII_NUMBER_H #define FASTFLOAT_ASCII_NUMBER_H #include #include #include #include namespace fast_float { // Next function can be micro-optimized, but compilers are entirely // able to optimize it well. fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } fastfloat_really_inline uint64_t byteswap(uint64_t val) { return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; } fastfloat_really_inline uint64_t read_u64(const char *chars) { uint64_t val; ::memcpy(&val, chars, sizeof(uint64_t)); #if FASTFLOAT_IS_BIG_ENDIAN == 1 // Need to read as-if the number was in little-endian order. val = byteswap(val); #endif return val; } fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { #if FASTFLOAT_IS_BIG_ENDIAN == 1 // Need to read as-if the number was in little-endian order. val = byteswap(val); #endif ::memcpy(chars, &val, sizeof(uint64_t)); } // credit @aqrit fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { const uint64_t mask = 0x000000FF000000FF; const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) val -= 0x3030303030303030; val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; return uint32_t(val); } fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { return parse_eight_digits_unrolled(read_u64(chars)); } // credit @aqrit fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & 0x8080808080808080)); } fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { return is_made_of_eight_digits_fast(read_u64(chars)); } typedef span byte_span; struct parsed_number_string { int64_t exponent{0}; uint64_t mantissa{0}; const char *lastmatch{nullptr}; bool negative{false}; bool valid{false}; bool too_many_digits{false}; // contains the range of the significant digits byte_span integer{}; // non-nullable byte_span fraction{}; // nullable }; // Assuming that you use no more than 19 digits, this will // parse an ASCII string. fastfloat_really_inline parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { const chars_format fmt = options.format; const char decimal_point = options.decimal_point; parsed_number_string answer; answer.valid = false; answer.too_many_digits = false; answer.negative = (*p == '-'); if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here ++p; if (p == pend) { return answer; } if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot return answer; } } const char *const start_digits = p; uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok p += 8; } while ((p != pend) && is_integer(*p)) { // a multiplication by 10 is cheaper than an arbitrary integer // multiplication i = 10 * i + uint64_t(*p - '0'); // might overflow, we will handle the overflow later ++p; } const char *const end_of_integer_part = p; int64_t digit_count = int64_t(end_of_integer_part - start_digits); answer.integer = byte_span(start_digits, size_t(digit_count)); int64_t exponent = 0; if ((p != pend) && (*p == decimal_point)) { ++p; const char* before = p; // can occur at most twice without overflowing, but let it occur more, since // for integers with many digits, digit parsing is the primary bottleneck. while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok p += 8; } while ((p != pend) && is_integer(*p)) { uint8_t digit = uint8_t(*p - '0'); ++p; i = i * 10 + digit; // in rare cases, this will overflow, but that's ok } exponent = before - p; answer.fraction = byte_span(before, size_t(p - before)); digit_count -= exponent; } // we must have encountered at least one integer! if (digit_count == 0) { return answer; } int64_t exp_number = 0; // explicit exponential part if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { const char * location_of_e = p; ++p; bool neg_exp = false; if ((p != pend) && ('-' == *p)) { neg_exp = true; ++p; } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) ++p; } if ((p == pend) || !is_integer(*p)) { if(!(fmt & chars_format::fixed)) { // We are in error. return answer; } // Otherwise, we will be ignoring the 'e'. p = location_of_e; } else { while ((p != pend) && is_integer(*p)) { uint8_t digit = uint8_t(*p - '0'); if (exp_number < 0x10000000) { exp_number = 10 * exp_number + digit; } ++p; } if(neg_exp) { exp_number = - exp_number; } exponent += exp_number; } } else { // If it scientific and not fixed, we have to bail out. if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } } answer.lastmatch = p; answer.valid = true; // If we frequently had to deal with long strings of digits, // we could extend our code by using a 128-bit integer instead // of a 64-bit integer. However, this is uncommon. // // We can deal with up to 19 digits. if (digit_count > 19) { // this is uncommon // It is possible that the integer had an overflow. // We have to handle the case where we have 0.0000somenumber. // We need to be mindful of the case where we only have zeroes... // E.g., 0.000000000...000. const char *start = start_digits; while ((start != pend) && (*start == '0' || *start == decimal_point)) { if(*start == '0') { digit_count --; } start++; } if (digit_count > 19) { answer.too_many_digits = true; // Let us start again, this time, avoiding overflows. // We don't need to check if is_integer, since we use the // pre-tokenized spans from above. i = 0; p = answer.integer.ptr; const char* int_end = p + answer.integer.len(); const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; while((i < minimal_nineteen_digit_integer) && (p != int_end)) { i = i * 10 + uint64_t(*p - '0'); ++p; } if (i >= minimal_nineteen_digit_integer) { // We have a big integers exponent = end_of_integer_part - p + exp_number; } else { // We have a value with a fractional component. p = answer.fraction.ptr; const char* frac_end = p + answer.fraction.len(); while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { i = i * 10 + uint64_t(*p - '0'); ++p; } exponent = answer.fraction.ptr - p + exp_number; } // We have now corrected both exponent and i, to a truncated value } } answer.exponent = exponent; answer.mantissa = i; return answer; } } // namespace fast_float #endif #ifndef FASTFLOAT_DIGIT_COMPARISON_H #define FASTFLOAT_DIGIT_COMPARISON_H #include #include #include #include namespace fast_float { // 1e0 to 1e19 constexpr static uint64_t powers_of_ten_uint64[] = { 1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL, 1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL, 100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL, 1000000000000000000UL, 10000000000000000000UL}; // calculate the exponent, in scientific notation, of the number. // this algorithm is not even close to optimized, but it has no practical // effect on performance: in order to have a faster algorithm, we'd need // to slow down performance for faster algorithms, and this is still fast. fastfloat_really_inline int32_t scientific_exponent(parsed_number_string& num) noexcept { uint64_t mantissa = num.mantissa; int32_t exponent = int32_t(num.exponent); while (mantissa >= 10000) { mantissa /= 10000; exponent += 4; } while (mantissa >= 100) { mantissa /= 100; exponent += 2; } while (mantissa >= 10) { mantissa /= 10; exponent += 1; } return exponent; } // this converts a native floating-point number to an extended-precision float. template fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept { using equiv_uint = typename binary_format::equiv_uint; constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); adjusted_mantissa am; int32_t bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); equiv_uint bits; ::memcpy(&bits, &value, sizeof(T)); if ((bits & exponent_mask) == 0) { // denormal am.power2 = 1 - bias; am.mantissa = bits & mantissa_mask; } else { // normal am.power2 = int32_t((bits & exponent_mask) >> binary_format::mantissa_explicit_bits()); am.power2 -= bias; am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; } return am; } // get the extended precision value of the halfway point between b and b+u. // we are given a native float that represents b, so we need to adjust it // halfway between b and b+u. template fastfloat_really_inline adjusted_mantissa to_extended_halfway(T value) noexcept { adjusted_mantissa am = to_extended(value); am.mantissa <<= 1; am.mantissa += 1; am.power2 -= 1; return am; } // round an extended-precision float to the nearest machine float. template fastfloat_really_inline void round(adjusted_mantissa& am, callback cb) noexcept { int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; if (-am.power2 >= mantissa_shift) { // have a denormal float int32_t shift = -am.power2 + 1; cb(am, std::min(shift, 64)); // check for round-up: if rounding-nearest carried us to the hidden bit. am.power2 = (am.mantissa < (uint64_t(1) << binary_format::mantissa_explicit_bits())) ? 0 : 1; return; } // have a normal float, use the default shift. cb(am, mantissa_shift); // check for carry if (am.mantissa >= (uint64_t(2) << binary_format::mantissa_explicit_bits())) { am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); am.power2++; } // check for infinite: we could have carried to an infinite power am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); if (am.power2 >= binary_format::infinite_power()) { am.power2 = binary_format::infinite_power(); am.mantissa = 0; } } template fastfloat_really_inline void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept { uint64_t mask; uint64_t halfway; if (shift == 64) { mask = UINT64_MAX; } else { mask = (uint64_t(1) << shift) - 1; } if (shift == 0) { halfway = 0; } else { halfway = uint64_t(1) << (shift - 1); } uint64_t truncated_bits = am.mantissa & mask; uint64_t is_above = truncated_bits > halfway; uint64_t is_halfway = truncated_bits == halfway; // shift digits into position if (shift == 64) { am.mantissa = 0; } else { am.mantissa >>= shift; } am.power2 += shift; bool is_odd = (am.mantissa & 1) == 1; am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); } fastfloat_really_inline void round_down(adjusted_mantissa& am, int32_t shift) noexcept { if (shift == 64) { am.mantissa = 0; } else { am.mantissa >>= shift; } am.power2 += shift; } fastfloat_really_inline void skip_zeros(const char*& first, const char* last) noexcept { uint64_t val; while (std::distance(first, last) >= 8) { ::memcpy(&val, first, sizeof(uint64_t)); if (val != 0x3030303030303030) { break; } first += 8; } while (first != last) { if (*first != '0') { break; } first++; } } // determine if any non-zero digits were truncated. // all characters must be valid digits. fastfloat_really_inline bool is_truncated(const char* first, const char* last) noexcept { // do 8-bit optimizations, can just compare to 8 literal 0s. uint64_t val; while (std::distance(first, last) >= 8) { ::memcpy(&val, first, sizeof(uint64_t)); if (val != 0x3030303030303030) { return true; } first += 8; } while (first != last) { if (*first != '0') { return true; } first++; } return false; } fastfloat_really_inline bool is_truncated(byte_span s) noexcept { return is_truncated(s.ptr, s.ptr + s.len()); } fastfloat_really_inline void parse_eight_digits(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { value = value * 100000000 + parse_eight_digits_unrolled(p); p += 8; counter += 8; count += 8; } fastfloat_really_inline void parse_one_digit(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { value = value * 10 + limb(*p - '0'); p++; counter++; count++; } fastfloat_really_inline void add_native(bigint& big, limb power, limb value) noexcept { big.mul(power); big.add(value); } fastfloat_really_inline void round_up_bigint(bigint& big, size_t& count) noexcept { // need to round-up the digits, but need to avoid rounding // ....9999 to ...10000, which could cause a false halfway point. add_native(big, 10, 1); count++; } // parse the significant digits into a big integer inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max_digits, size_t& digits) noexcept { // try to minimize the number of big integer and scalar multiplication. // therefore, try to parse 8 digits at a time, and multiply by the largest // scalar value (9 or 19 digits) for each step. size_t counter = 0; digits = 0; limb value = 0; #ifdef FASTFLOAT_64BIT_LIMB size_t step = 19; #else size_t step = 9; #endif // process all integer digits. const char* p = num.integer.ptr; const char* pend = p + num.integer.len(); skip_zeros(p, pend); // process all digits, in increments of step per loop while (p != pend) { while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { parse_eight_digits(p, value, counter, digits); } while (counter < step && p != pend && digits < max_digits) { parse_one_digit(p, value, counter, digits); } if (digits == max_digits) { // add the temporary value, then check if we've truncated any digits add_native(result, limb(powers_of_ten_uint64[counter]), value); bool truncated = is_truncated(p, pend); if (num.fraction.ptr != nullptr) { truncated |= is_truncated(num.fraction); } if (truncated) { round_up_bigint(result, digits); } return; } else { add_native(result, limb(powers_of_ten_uint64[counter]), value); counter = 0; value = 0; } } // add our fraction digits, if they're available. if (num.fraction.ptr != nullptr) { p = num.fraction.ptr; pend = p + num.fraction.len(); if (digits == 0) { skip_zeros(p, pend); } // process all digits, in increments of step per loop while (p != pend) { while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { parse_eight_digits(p, value, counter, digits); } while (counter < step && p != pend && digits < max_digits) { parse_one_digit(p, value, counter, digits); } if (digits == max_digits) { // add the temporary value, then check if we've truncated any digits add_native(result, limb(powers_of_ten_uint64[counter]), value); bool truncated = is_truncated(p, pend); if (truncated) { round_up_bigint(result, digits); } return; } else { add_native(result, limb(powers_of_ten_uint64[counter]), value); counter = 0; value = 0; } } } if (counter != 0) { add_native(result, limb(powers_of_ten_uint64[counter]), value); } } template inline adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); adjusted_mantissa answer; bool truncated; answer.mantissa = bigmant.hi64(truncated); int bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); answer.power2 = bigmant.bit_length() - 64 + bias; round(answer, [truncated](adjusted_mantissa& a, int32_t shift) { round_nearest_tie_even(a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { return is_above || (is_halfway && truncated) || (is_odd && is_halfway); }); }); return answer; } // the scaling here is quite simple: we have, for the real digits `m * 10^e`, // and for the theoretical digits `n * 2^f`. Since `e` is always negative, // to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. // we then need to scale by `2^(f- e)`, and then the two significant digits // are of the same magnitude. template inline adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { bigint& real_digits = bigmant; int32_t real_exp = exponent; // get the value of `b`, rounded down, and get a bigint representation of b+h adjusted_mantissa am_b = am; // gcc7 buf: use a lambda to remove the noexcept qualifier bug with -Wnoexcept-type. round(am_b, [](adjusted_mantissa&a, int32_t shift) { round_down(a, shift); }); T b; to_float(false, am_b, b); adjusted_mantissa theor = to_extended_halfway(b); bigint theor_digits(theor.mantissa); int32_t theor_exp = theor.power2; // scale real digits and theor digits to be same power. int32_t pow2_exp = theor_exp - real_exp; uint32_t pow5_exp = uint32_t(-real_exp); if (pow5_exp != 0) { FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); } if (pow2_exp > 0) { FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); } else if (pow2_exp < 0) { FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); } // compare digits, and use it to director rounding int ord = real_digits.compare(theor_digits); adjusted_mantissa answer = am; round(answer, [ord](adjusted_mantissa& a, int32_t shift) { round_nearest_tie_even(a, shift, [ord](bool is_odd, bool _, bool __) -> bool { (void)_; // not needed, since we've done our comparison (void)__; // not needed, since we've done our comparison if (ord > 0) { return true; } else if (ord < 0) { return false; } else { return is_odd; } }); }); return answer; } // parse the significant digits as a big integer to unambiguously round the // the significant digits. here, we are trying to determine how to round // an extended float representation close to `b+h`, halfway between `b` // (the float rounded-down) and `b+u`, the next positive float. this // algorithm is always correct, and uses one of two approaches. when // the exponent is positive relative to the significant digits (such as // 1234), we create a big-integer representation, get the high 64-bits, // determine if any lower bits are truncated, and use that to direct // rounding. in case of a negative exponent relative to the significant // digits (such as 1.2345), we create a theoretical representation of // `b` as a big-integer type, scaled to the same binary exponent as // the actual digits. we then compare the big integer representations // of both, and use that to direct rounding. template inline adjusted_mantissa digit_comp(parsed_number_string& num, adjusted_mantissa am) noexcept { // remove the invalid exponent bias am.power2 -= invalid_am_bias; int32_t sci_exp = scientific_exponent(num); size_t max_digits = binary_format::max_digits(); size_t digits = 0; bigint bigmant; parse_mantissa(bigmant, num, max_digits, digits); // can't underflow, since digits is at most max_digits. int32_t exponent = sci_exp + 1 - int32_t(digits); if (exponent >= 0) { return positive_digit_comp(bigmant, exponent); } else { return negative_digit_comp(bigmant, am, exponent); } } } // namespace fast_float #endif #ifndef FASTFLOAT_PARSE_NUMBER_H #define FASTFLOAT_PARSE_NUMBER_H #include #include #include #include namespace fast_float { namespace detail { /** * Special case +inf, -inf, nan, infinity, -infinity. * The case comparisons could be made much faster given that we know that the * strings a null-free and fixed. **/ template from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { from_chars_result answer; answer.ptr = first; answer.ec = std::errc(); // be optimistic bool minusSign = false; if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here minusSign = true; ++first; } if (last - first >= 3) { if (fastfloat_strncasecmp(first, "nan", 3)) { answer.ptr = (first += 3); value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). if(first != last && *first == '(') { for(const char* ptr = first + 1; ptr != last; ++ptr) { if (*ptr == ')') { answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) break; } else if(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_')) break; // forbidden char, not nan(n-char-seq-opt) } } return answer; } if (fastfloat_strncasecmp(first, "inf", 3)) { if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 5)) { answer.ptr = first + 8; } else { answer.ptr = first + 3; } value = minusSign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); return answer; } } answer.ec = std::errc::invalid_argument; return answer; } } // namespace detail template from_chars_result from_chars(const char *first, const char *last, T &value, chars_format fmt /*= chars_format::general*/) noexcept { return from_chars_advanced(first, last, value, parse_options{fmt}); } template from_chars_result from_chars_advanced(const char *first, const char *last, T &value, parse_options options) noexcept { static_assert (std::is_same::value || std::is_same::value, "only float and double are supported"); from_chars_result answer; if (first == last) { answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; } parsed_number_string pns = parse_number_string(first, last, options); if (!pns.valid) { return detail::parse_infnan(first, last, value); } answer.ec = std::errc(); // be optimistic answer.ptr = pns.lastmatch; // Next is Clinger's fast path. if (binary_format::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format::max_exponent_fast_path() && pns.mantissa <=binary_format::max_mantissa_fast_path() && !pns.too_many_digits) { value = T(pns.mantissa); if (pns.exponent < 0) { value = value / binary_format::exact_power_of_ten(-pns.exponent); } else { value = value * binary_format::exact_power_of_ten(pns.exponent); } if (pns.negative) { value = -value; } return answer; } adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); if(pns.too_many_digits && am.power2 >= 0) { if(am != compute_float>(pns.exponent, pns.mantissa + 1)) { am = compute_error>(pns.exponent, pns.mantissa); } } // If we called compute_float>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), // then we need to go the long way around again. This is very uncommon. if(am.power2 < 0) { am = digit_comp(pns, am); } to_float(pns.negative, am, value); return answer; } } // namespace fast_float #endif ================================================ FILE: src/Common/unix/platform.cpp ================================================ #include #include uint32_t GetTickCount() { #if BOOST_OS_LINUX struct timespec ts; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); return (1000 * ts.tv_sec + ts.tv_nsec / 1000000); #elif BOOST_OS_MACOS return clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1000000; #elif BOOST_OS_BSD struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (1000 * ts.tv_sec + ts.tv_nsec / 1000000); #endif } ================================================ FILE: src/Common/unix/platform.h ================================================ #include class SlimRWLock { public: void LockRead() { m_sm.lock_shared(); } void UnlockRead() { m_sm.unlock_shared(); } void LockWrite() { m_sm.lock(); } void UnlockWrite() { m_sm.unlock(); } private: std::shared_mutex m_sm; }; inline uint32_t GetExceptionError() { return errno; } #undef False #undef True #undef None #undef Bool #undef Status #undef Success #undef ClientMessage // placeholder uint32_t GetTickCount(); // strcpy_s and strcat_s implementations template void strcpy_s(char (&dst)[N], const char* src) { if(N == 0) return; char* dstP = dst; const char* end = src + N - 1; while(src < end) { char c = *src; *dstP = c; if(c == '\0') return; dstP++; src++; c++; } *dstP = '\0'; return; } template void strcat_s(char (&dst)[N], const char* src) { if(N == 0) return; char* dstP = dst; const char* end = dstP + N - 1; while(dstP < end && *dstP != '\0') dstP++; while(dstP < end) { char c = *src; *dstP = c; if(c == '\0') return; dstP++; src++; c++; } *dstP = '\0'; return; } ================================================ FILE: src/Common/version.h ================================================ #ifndef EMULATOR_NAME #define EMULATOR_NAME "Cemu" #define EMULATOR_VERSION_SUFFIX "" #define _XSTRINGFY(s) _STRINGFY(s) #define _STRINGFY(s) #s #if EMULATOR_VERSION_MAJOR != 0 #define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_MAJOR) "." _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) #define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_MAJOR) "." _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) #else // no version provided. Only show commit hash #define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX) #define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX) #endif #endif ================================================ FILE: src/Common/windows/FileStream_win32.cpp ================================================ #include "Common/windows/FileStream_win32.h" FileStream* FileStream::openFile(std::string_view path) { HANDLE hFile = CreateFileW(boost::nowide::widen(path.data(), path.size()).c_str(), FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); if (hFile == INVALID_HANDLE_VALUE) return nullptr; return new FileStream(hFile); } FileStream* FileStream::openFile(const wchar_t* path, bool allowWrite) { HANDLE hFile = CreateFileW(path, allowWrite ? (FILE_GENERIC_READ | FILE_GENERIC_WRITE) : FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); if (hFile == INVALID_HANDLE_VALUE) return nullptr; return new FileStream(hFile); } FileStream* FileStream::openFile2(const fs::path& path, bool allowWrite) { return openFile(path.generic_wstring().c_str(), allowWrite); } FileStream* FileStream::createFile(const wchar_t* path) { HANDLE hFile = CreateFileW(path, FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0); if (hFile == INVALID_HANDLE_VALUE) return nullptr; return new FileStream(hFile); } FileStream* FileStream::createFile(std::string_view path) { auto w = boost::nowide::widen(path.data(), path.size()); HANDLE hFile = CreateFileW(w.c_str(), FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0); if (hFile == INVALID_HANDLE_VALUE) return nullptr; return new FileStream(hFile); } FileStream* FileStream::createFile2(const fs::path& path) { return createFile(path.generic_wstring().c_str()); } std::optional> FileStream::LoadIntoMemory(const fs::path& path) { FileStream* fs = openFile2(path); if (!fs) return std::nullopt; uint64 fileSize = fs->GetSize(); if(fileSize > 0xFFFFFFFFull) { delete fs; return std::nullopt; } std::optional> v(fileSize); if (fs->readData(v->data(), (uint32)fileSize) != (uint32)fileSize) { delete fs; return std::nullopt; } delete fs; return v; } void FileStream::SetPosition(uint64 pos) { LONG posHigh = (LONG)(pos >> 32); LONG posLow = (LONG)(pos); SetFilePointer(m_hFile, posLow, &posHigh, FILE_BEGIN); } uint64 FileStream::GetSize() { DWORD fileSizeHigh = 0; DWORD fileSizeLow = 0; fileSizeLow = GetFileSize(m_hFile, &fileSizeHigh); return ((uint64)fileSizeHigh << 32) | (uint64)fileSizeLow; } bool FileStream::SetEndOfFile() { return ::SetEndOfFile(m_hFile) != 0; } void FileStream::extract(std::vector& data) { DWORD fileSize = GetFileSize(m_hFile, nullptr); data.resize(fileSize); SetFilePointer(m_hFile, 0, 0, FILE_BEGIN); DWORD bt; ReadFile(m_hFile, data.data(), fileSize, &bt, nullptr); } uint32 FileStream::readData(void* data, uint32 length) { DWORD bytesRead = 0; ReadFile(m_hFile, data, length, &bytesRead, NULL); return bytesRead; } bool FileStream::readU64(uint64& v) { return readData(&v, sizeof(uint64)) == sizeof(uint64); } bool FileStream::readU32(uint32& v) { return readData(&v, sizeof(uint32)) == sizeof(uint32); } bool FileStream::readU8(uint8& v) { return readData(&v, sizeof(uint8)) == sizeof(uint8); } bool FileStream::readLine(std::string& line) { line.clear(); uint8 c; bool isEOF = true; while (readU8(c)) { isEOF = false; if(c == '\r') continue; if (c == '\n') break; line.push_back((char)c); } return !isEOF; } sint32 FileStream::writeData(const void* data, sint32 length) { DWORD bytesWritten = 0; WriteFile(m_hFile, data, length, &bytesWritten, NULL); return bytesWritten; } void FileStream::writeU64(uint64 v) { writeData(&v, sizeof(uint64)); } void FileStream::writeU32(uint32 v) { writeData(&v, sizeof(uint32)); } void FileStream::writeU8(uint8 v) { writeData(&v, sizeof(uint8)); } void FileStream::writeStringFmt(const char* format, ...) { char buffer[2048]; va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); writeData(buffer, (sint32)strlen(buffer)); } void FileStream::writeString(const char* str) { writeData(str, (sint32)strlen(str)); } void FileStream::writeLine(const char* str) { writeData(str, (sint32)strlen(str)); writeData("\r\n", 2); } FileStream::~FileStream() { if(m_isValid) CloseHandle(m_hFile); } FileStream::FileStream(HANDLE hFile) { m_hFile = hFile; m_isValid = true; } ================================================ FILE: src/Common/windows/FileStream_win32.h ================================================ #pragma once #include "Common/precompiled.h" class FileStream { public: static FileStream* openFile(std::string_view path); static FileStream* openFile(const wchar_t* path, bool allowWrite = false); static FileStream* openFile2(const fs::path& path, bool allowWrite = false); static FileStream* createFile(const wchar_t* path); static FileStream* createFile(std::string_view path); static FileStream* createFile2(const fs::path& path); // helper function to load a file into memory static std::optional> LoadIntoMemory(const fs::path& path); // size and seek void SetPosition(uint64 pos); uint64 GetSize(); bool SetEndOfFile(); void extract(std::vector& data); // reading uint32 readData(void* data, uint32 length); bool readU64(uint64& v); bool readU32(uint32& v); bool readU16(uint16& v); bool readU8(uint8& v); bool readLine(std::string& line); // writing (binary) sint32 writeData(const void* data, sint32 length); void writeU64(uint64 v); void writeU32(uint32 v); void writeU16(uint16 v); void writeU8(uint8 v); // writing (strings) void writeStringFmt(const char* format, ...); void writeString(const char* str); void writeLine(const char* str); ~FileStream(); FileStream() = default; private: FileStream(HANDLE hFile); bool m_isValid{}; HANDLE m_hFile; }; ================================================ FILE: src/Common/windows/platform.cpp ================================================ #include SlimRWLock::SlimRWLock() { static_assert(sizeof(m_lock) == sizeof(SRWLOCK)); RTL_SRWLOCK* srwLock = (RTL_SRWLOCK*)&m_lock; *srwLock = SRWLOCK_INIT; //m_lock = { SRWLOCK_INIT }; } void SlimRWLock::LockRead() { AcquireSRWLockShared((SRWLOCK*)&m_lock); } void SlimRWLock::UnlockRead() { ReleaseSRWLockShared((SRWLOCK*)&m_lock); } void SlimRWLock::LockWrite() { AcquireSRWLockExclusive((SRWLOCK*)&m_lock); } void SlimRWLock::UnlockWrite() { ReleaseSRWLockExclusive((SRWLOCK*)&m_lock); } uint32_t GetExceptionError() { return (uint32_t)GetLastError(); } ================================================ FILE: src/Common/windows/platform.h ================================================ #pragma once #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #define AF_BLUETOOTH AF_BTH #define BTPROTO_RFCOMM BT_PORT_ANY class SlimRWLock { public: SlimRWLock(); void LockRead(); void UnlockRead(); void LockWrite(); void UnlockWrite(); private: /*SRWLOCK*/ void* m_lock; }; uint32_t GetExceptionError(); ================================================ FILE: src/audio/CMakeLists.txt ================================================ add_library(CemuAudio IAudioAPI.cpp IAudioAPI.h IAudioInputAPI.cpp IAudioInputAPI.h ) set_property(TARGET CemuAudio PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") if(WIN32) target_sources(CemuAudio PRIVATE DirectSoundAPI.cpp DirectSoundAPI.h XAudio2API.cpp XAudio2API.h XAudio27API.cpp XAudio27API.h ) endif() if(ENABLE_CUBEB) target_sources(CemuAudio PRIVATE CubebAPI.cpp CubebAPI.h CubebInputAPI.cpp CubebInputAPI.h ) #add_compile_definitions(HAS_CUBEB) endif() target_include_directories(CemuAudio PUBLIC "../") target_link_libraries(CemuAudio PRIVATE CemuCommon CemuGui ) if(ENABLE_CUBEB) # PUBLIC because cubeb.h/cubeb.h is included in CubebAPI.h target_link_libraries(CemuAudio PUBLIC cubeb::cubeb) endif() ================================================ FILE: src/audio/CubebAPI.cpp ================================================ #include "CubebAPI.h" #if BOOST_OS_WINDOWS #include #include #include #pragma comment(lib, "Avrt.lib") #pragma comment(lib, "ksuser.lib") #endif static void state_cb(cubeb_stream* stream, void* user, cubeb_state state) { if (!stream) return; /*switch (state) { case CUBEB_STATE_STARTED: fprintf(stderr, "stream started\n"); break; case CUBEB_STATE_STOPPED: fprintf(stderr, "stream stopped\n"); break; case CUBEB_STATE_DRAINED: fprintf(stderr, "stream drained\n"); break; default: fprintf(stderr, "unknown stream state %d\n", state); }*/ } long CubebAPI::data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes) { auto* thisptr = (CubebAPI*)user; //const auto size = (size_t)thisptr->m_bytesPerBlock; // (size_t)nframes* thisptr->m_channels; // m_bytesPerBlock = samples_per_block * channels * (bits_per_sample / 8); const auto size = (size_t)nframes * thisptr->m_channels * (thisptr->m_bitsPerSample/8); std::unique_lock lock(thisptr->m_mutex); if (thisptr->m_buffer.empty()) { // we got no data, just write silence memset(outputbuffer, 0x00, size); } else { const auto copied = std::min(thisptr->m_buffer.size(), size); memcpy(outputbuffer, thisptr->m_buffer.data(), copied); thisptr->m_buffer.erase(thisptr->m_buffer.begin(), std::next(thisptr->m_buffer.begin(), copied)); lock.unlock(); // fill rest with silence if (copied != size) memset((uint8*)outputbuffer + copied, 0x00, size - copied); } return nframes; } CubebAPI::CubebAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) : IAudioAPI(samplerate, channels, samples_per_block, bits_per_sample) { cubeb_stream_params output_params; output_params.format = CUBEB_SAMPLE_S16LE; output_params.rate = samplerate; output_params.channels = channels; output_params.prefs = CUBEB_STREAM_PREF_NONE; switch (channels) { case 8: output_params.layout = CUBEB_LAYOUT_3F4_LFE; break; case 6: output_params.layout = CUBEB_LAYOUT_3F2_LFE_BACK; break; case 4: output_params.layout = CUBEB_LAYOUT_QUAD; break; case 2: output_params.layout = CUBEB_LAYOUT_STEREO; break; default: output_params.layout = CUBEB_LAYOUT_MONO; break; } uint32 latency = 1; cubeb_get_min_latency(s_context, &output_params, &latency); m_buffer.reserve((size_t)m_bytesPerBlock * kBlockCount); if (cubeb_stream_init(s_context, &m_stream, "Cemu Cubeb output", nullptr, nullptr, devid, &output_params, latency, data_cb, state_cb, this) != CUBEB_OK) { throw std::runtime_error("can't initialize cubeb device"); } } CubebAPI::~CubebAPI() { if (m_stream) { Stop(); cubeb_stream_destroy(m_stream); } } bool CubebAPI::NeedAdditionalBlocks() const { std::shared_lock lock(m_mutex); return m_buffer.size() < GetAudioDelay() * m_bytesPerBlock; } bool CubebAPI::FeedBlock(sint16* data) { std::unique_lock lock(m_mutex); if (m_buffer.capacity() <= m_buffer.size() + m_bytesPerBlock) { cemuLog_logDebug(LogType::Force, "dropped direct sound block since too many buffers are queued"); return false; } m_buffer.insert(m_buffer.end(), (uint8*)data, (uint8*)data + m_bytesPerBlock); return true; } bool CubebAPI::Play() { if (m_is_playing) return true; if (cubeb_stream_start(m_stream) == CUBEB_OK) { m_is_playing = true; return true; } return false; } bool CubebAPI::Stop() { if (!m_is_playing) return true; if (cubeb_stream_stop(m_stream) == CUBEB_OK) { m_is_playing = false; return true; } return false; } void CubebAPI::SetVolume(sint32 volume) { IAudioAPI::SetVolume(volume); cubeb_stream_set_volume(m_stream, (float)volume / 100.0f); } bool CubebAPI::InitializeStatic() { if (cubeb_init(&s_context, "Cemu Cubeb", nullptr)) { cemuLog_log(LogType::Force, "can't create cubeb audio api"); return false; } return true; } void CubebAPI::Destroy() { if (s_context) cubeb_destroy(s_context); } std::vector CubebAPI::GetDevices() { std::vector result; // Add the default device to the list auto defaultDevice = std::make_shared(nullptr, "default", L"Default Device"); result.emplace_back(defaultDevice); cubeb_device_collection devices; if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices) != CUBEB_OK) return result; result.reserve(devices.count + 1); // The default device already occupies one element for (size_t i = 0; i < devices.count; ++i) { // const auto& device = devices.device[i]; if (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED) { auto device = std::make_shared(devices.device[i].devid, devices.device[i].device_id, boost::nowide::widen( devices.device[i].friendly_name)); result.emplace_back(device); } } cubeb_device_collection_destroy(s_context, &devices); return result; } ================================================ FILE: src/audio/CubebAPI.h ================================================ #pragma once #include "IAudioAPI.h" #include #include class CubebAPI : public IAudioAPI { public: class CubebDeviceDescription : public DeviceDescription { public: CubebDeviceDescription(cubeb_devid devid, std::string device_id, const std::wstring& name) : DeviceDescription(name), m_devid(devid), m_device_id(std::move(device_id)) { } std::wstring GetIdentifier() const override { return boost::nowide::widen(m_device_id); } cubeb_devid GetDeviceId() const { return m_devid; } private: cubeb_devid m_devid; std::string m_device_id; }; using CubebDeviceDescriptionPtr = std::shared_ptr; CubebAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); ~CubebAPI(); AudioAPI GetType() const override { return Cubeb; } bool NeedAdditionalBlocks() const override; bool FeedBlock(sint16* data) override; bool Play() override; bool Stop() override; void SetVolume(sint32 volume) override; static std::vector GetDevices(); static bool InitializeStatic(); static void Destroy(); private: inline static cubeb* s_context = nullptr; cubeb_stream* m_stream = nullptr; bool m_is_playing = false; mutable std::shared_mutex m_mutex; std::vector m_buffer; static long data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes); }; ================================================ FILE: src/audio/CubebInputAPI.cpp ================================================ #include "CubebInputAPI.h" #if BOOST_OS_WINDOWS #include #include #include #pragma comment(lib, "Avrt.lib") #pragma comment(lib, "ksuser.lib") #endif static void state_cb(cubeb_stream* stream, void* user, cubeb_state state) { if (!stream) return; /*switch (state) { case CUBEB_STATE_STARTED: fprintf(stderr, "stream started\n"); break; case CUBEB_STATE_STOPPED: fprintf(stderr, "stream stopped\n"); break; case CUBEB_STATE_DRAINED: fprintf(stderr, "stream drained\n"); break; default: fprintf(stderr, "unknown stream state %d\n", state); }*/ } long CubebInputAPI::data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes) { auto* thisptr = (CubebInputAPI*)user; const auto size = (size_t)nframes * thisptr->m_channels * (thisptr->m_bitsPerSample / 8); std::unique_lock lock(thisptr->m_mutex); if (thisptr->m_buffer.capacity() <= thisptr->m_buffer.size() + size) { cemuLog_logDebug(LogType::Force, "dropped input sound block since too many buffers are queued"); return nframes; } thisptr->m_buffer.insert(thisptr->m_buffer.end(), (uint8*)inputbuffer, (uint8*)inputbuffer + size); return nframes; } CubebInputAPI::CubebInputAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) : IAudioInputAPI(samplerate, channels, samples_per_block, bits_per_sample) { cubeb_stream_params input_params; input_params.format = CUBEB_SAMPLE_S16LE; input_params.rate = samplerate; input_params.channels = channels; input_params.prefs = CUBEB_STREAM_PREF_NONE; switch (channels) { case 8: input_params.layout = CUBEB_LAYOUT_3F4_LFE; break; case 6: input_params.layout = CUBEB_LAYOUT_QUAD_LFE | CHANNEL_FRONT_CENTER; break; case 4: input_params.layout = CUBEB_LAYOUT_QUAD; break; case 2: input_params.layout = CUBEB_LAYOUT_STEREO; break; default: input_params.layout = CUBEB_LAYOUT_MONO; break; } uint32 latency = 1; cubeb_get_min_latency(s_context, &input_params, &latency); m_buffer.reserve((size_t)m_bytesPerBlock * kBlockCount); if (cubeb_stream_init(s_context, &m_stream, "Cemu Cubeb input", devid, &input_params, nullptr, nullptr, latency, data_cb, state_cb, this) != CUBEB_OK) { throw std::runtime_error("can't initialize cubeb device"); } } CubebInputAPI::~CubebInputAPI() { if (m_stream) { Stop(); cubeb_stream_destroy(m_stream); } } bool CubebInputAPI::ConsumeBlock(sint16* data) { std::unique_lock lock(m_mutex); if (m_buffer.empty()) { // we got no data, just write silence memset(data, 0x00, m_bytesPerBlock); } else { const auto copied = std::min(m_buffer.size(), (size_t)m_bytesPerBlock); memcpy(data, m_buffer.data(), copied); m_buffer.erase(m_buffer.begin(), std::next(m_buffer.begin(), copied)); lock.unlock(); // fill rest with silence if (copied != m_bytesPerBlock) memset((uint8*)data + copied, 0x00, m_bytesPerBlock - copied); } return true; } bool CubebInputAPI::Play() { if (m_is_playing) return true; if (cubeb_stream_start(m_stream) == CUBEB_OK) { m_is_playing = true; return true; } return false; } bool CubebInputAPI::Stop() { if (!m_is_playing) return true; if (cubeb_stream_stop(m_stream) == CUBEB_OK) { m_is_playing = false; return true; } return false; } void CubebInputAPI::SetVolume(sint32 volume) { IAudioInputAPI::SetVolume(volume); cubeb_stream_set_volume(m_stream, (float)volume / 100.0f); } bool CubebInputAPI::InitializeStatic() { if (cubeb_init(&s_context, "Cemu Input Cubeb", nullptr)) { cemuLog_log(LogType::Force, "can't create cubeb audio api"); return false; } return true; } void CubebInputAPI::Destroy() { if (s_context) cubeb_destroy(s_context); } std::vector CubebInputAPI::GetDevices() { std::vector result; // Add the default device to the list auto defaultDevice = std::make_shared(nullptr, "default", L"Default Device"); result.emplace_back(defaultDevice); cubeb_device_collection devices; if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK) return result; result.reserve(devices.count + 1); // The default device already occupies one element for (size_t i = 0; i < devices.count; ++i) { // const auto& device = devices.device[i]; if (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED) { auto device = std::make_shared(devices.device[i].devid, devices.device[i].device_id, boost::nowide::widen( devices.device[i].friendly_name)); result.emplace_back(device); } } cubeb_device_collection_destroy(s_context, &devices); return result; } ================================================ FILE: src/audio/CubebInputAPI.h ================================================ #pragma once #include "IAudioInputAPI.h" #include class CubebInputAPI : public IAudioInputAPI { public: class CubebDeviceDescription : public DeviceDescription { public: CubebDeviceDescription(cubeb_devid devid, std::string device_id, const std::wstring& name) : DeviceDescription(name), m_devid(devid), m_device_id(std::move(device_id)) { } std::wstring GetIdentifier() const override { return boost::nowide::widen(m_device_id); } cubeb_devid GetDeviceId() const { return m_devid; } private: cubeb_devid m_devid; std::string m_device_id; }; using CubebDeviceDescriptionPtr = std::shared_ptr; CubebInputAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); ~CubebInputAPI(); AudioInputAPI GetType() const override { return Cubeb; } bool ConsumeBlock(sint16* data) override; bool Play() override; bool Stop() override; bool IsPlaying() const override { return m_is_playing; }; void SetVolume(sint32 volume) override; static std::vector GetDevices(); static bool InitializeStatic(); static void Destroy(); private: inline static cubeb* s_context = nullptr; cubeb_stream* m_stream = nullptr; bool m_is_playing = false; mutable std::shared_mutex m_mutex; std::vector m_buffer; static long data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes); }; ================================================ FILE: src/audio/DirectSoundAPI.cpp ================================================ #include "DirectSoundAPI.h" #include "util/helpers/helpers.h" #include "WindowSystem.h" #include #pragma comment(lib, "Dsound.lib") std::wstring DirectSoundAPI::DirectSoundDeviceDescription::GetIdentifier() const { return m_guid ? WStringFromGUID(*m_guid) : L"default"; } DirectSoundAPI::DirectSoundAPI(GUID* guid, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) : IAudioAPI(samplerate, channels, samples_per_block, bits_per_sample) { if (DirectSoundCreate8(guid, &m_direct_sound, nullptr) != DS_OK) throw std::runtime_error("can't create directsound device"); if (FAILED(m_direct_sound->SetCooperativeLevel(static_cast(WindowSystem::GetWindowInfo().window_main.surface), DSSCL_PRIORITY))) throw std::runtime_error("can't set directsound priority"); DSBUFFERDESC bd{}; bd.dwSize = sizeof(DSBUFFERDESC); bd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY; bd.dwBufferBytes = kBufferCount * m_bytesPerBlock; // kBlockCount * (samples_per_block * channels * (bits_per_sample / 8)); bd.lpwfxFormat = (LPWAVEFORMATEX)&m_wfx; Microsoft::WRL::ComPtr sound_buffer; if (FAILED(m_direct_sound->CreateSoundBuffer(&bd, &sound_buffer, nullptr))) throw std::runtime_error("can't create directsound soundbuffer"); DSBCAPS caps{}; caps.dwSize = sizeof(DSBCAPS); if (FAILED(sound_buffer->GetCaps(&caps))) throw std::runtime_error("can't get directsound soundbuffer size"); m_sound_buffer_size = caps.dwBufferBytes; Microsoft::WRL::ComPtr notify8; if (FAILED(sound_buffer->QueryInterface(IID_IDirectSoundBuffer8, &m_sound_buffer))) { throw std::runtime_error("can't get directsound buffer interface"); } if (FAILED(sound_buffer->QueryInterface(IID_IDirectSoundNotify8, &m_notify))) { throw std::runtime_error("can't get directsound notify interface"); } { // initialize sound buffer void *ptr1, *ptr2; DWORD bytes1, bytes2; m_sound_buffer->Lock(0, m_sound_buffer_size, &ptr1, &bytes1, &ptr2, &bytes2, 0); memset(ptr1, 0x00, bytes1); if (ptr2 && bytes2 > 0) memset(ptr2, 0x00, bytes2); m_sound_buffer->Unlock(ptr1, bytes1, ptr2, bytes2); } m_sound_buffer->SetCurrentPosition(0); DSBPOSITIONNOTIFY notify[kBufferCount]{}; for (size_t i = 0; i < kBufferCount; ++i) { m_notify_event[i] = CreateEvent(nullptr, FALSE, FALSE, nullptr); notify[i].hEventNotify = m_notify_event[i]; //notify[i].dwOffset = ((i*2) + 1) * (m_bytes_per_block / 2); notify[i].dwOffset = (i * m_bytesPerBlock); } if (FAILED(m_notify->SetNotificationPositions(kBufferCount, notify))) throw std::runtime_error("can't set directsound notify positions"); m_running = true; m_thread = std::thread(&DirectSoundAPI::AudioThread, this); #if BOOST_OS_WINDOWS SetThreadPriority(m_thread.native_handle(), THREAD_PRIORITY_TIME_CRITICAL); #endif } void DirectSoundAPI::AudioThread() { while (m_running) { HRESULT hr = WaitForMultipleObjects(m_notify_event.size(), m_notify_event.data(), FALSE, 10); if (WAIT_OBJECT_0 <= hr && hr <= WAIT_OBJECT_0 + kBufferCount) { // write to the following buffer const sint32 position = (hr - WAIT_OBJECT_0 + 1) % kBufferCount; void *ptr1, *ptr2; DWORD bytes1, bytes2; hr = m_sound_buffer->Lock(position * m_bytesPerBlock, m_bytesPerBlock, &ptr1, &bytes1, &ptr2, &bytes2, 0); if (hr == DSERR_BUFFERLOST) { m_sound_buffer->Restore(); hr = m_sound_buffer->Lock(position * m_bytesPerBlock, m_bytesPerBlock, &ptr1, &bytes1, &ptr2, &bytes2, 0); } if (FAILED(hr)) { cemuLog_log(LogType::Force, "DirectSound: Dropped audio block due to locking failure"); continue; } { std::unique_lock lock(m_mutex); if (m_buffer.empty()) { //cemuLog_logDebug(LogType::Force, "DirectSound: writing silence"); // we got no data, just write silence memset(ptr1, 0x00, bytes1); if (ptr2) memset(ptr2, 0x00, bytes2); } else { const auto& buffer = m_buffer.front(); memcpy(ptr1, buffer.get(), bytes1); if (ptr2) memcpy(ptr2, buffer.get() + bytes1, bytes2); m_buffer.pop(); } } m_sound_buffer->Unlock(ptr1, bytes1, ptr2, bytes2); } } } DirectSoundAPI::~DirectSoundAPI() { m_running = false; DirectSoundAPI::Stop(); if(m_thread.joinable()) m_thread.join(); for(auto entry : m_notify_event) { if (entry) CloseHandle(entry); } } bool DirectSoundAPI::Play() { if (m_playing) return true; m_playing = SUCCEEDED(m_sound_buffer->Play(0, 0, DSBPLAY_LOOPING)); return m_playing; } bool DirectSoundAPI::Stop() { if (!m_playing) return true; m_playing = FAILED(m_sound_buffer->Stop()); return m_playing; } bool DirectSoundAPI::FeedBlock(sint16* data) { std::lock_guard lock(m_mutex); if (m_buffer.size() > kBlockCount) { cemuLog_logDebug(LogType::Force, "dropped direct sound block since too many buffers are queued"); return false; } auto tmp = std::make_unique(m_bytesPerBlock); memcpy(tmp.get(), data, m_bytesPerBlock); m_buffer.emplace(std::move(tmp)); return true; } void DirectSoundAPI::SetVolume(sint32 volume) { IAudioAPI::SetVolume(volume); const LONG value = pow((float)volume / 100.0f, 0.20f) * (DSBVOLUME_MAX - DSBVOLUME_MIN) + DSBVOLUME_MIN; m_sound_buffer->SetVolume(value); } bool DirectSoundAPI::NeedAdditionalBlocks() const { std::shared_lock lock(m_mutex); return m_buffer.size() < GetAudioDelay(); } std::vector DirectSoundAPI::GetDevices() { std::vector result; DirectSoundEnumerateW( [](LPGUID lpGuid, LPCWSTR lpcstrDescription, LPCWSTR lpcstrModule, LPVOID lpContext) -> BOOL { auto results = (std::vector*)lpContext; auto obj = std::make_shared(lpcstrDescription, lpGuid); results->emplace_back(obj); return TRUE; }, &result); //Exclude default primary sound device if no other sound devices are available if (result.size() == 1 && result.at(0).get()->GetIdentifier() == L"default") { result.clear(); } return result; } std::vector DirectSoundAPI::GetInputDevices() { std::vector result; DirectSoundCaptureEnumerateW( [](LPGUID lpGuid, LPCWSTR lpcstrDescription, LPCWSTR lpcstrModule, LPVOID lpContext) -> BOOL { auto results = (std::vector*)lpContext; auto obj = std::make_shared(lpcstrDescription, lpGuid); results->emplace_back(obj); return TRUE; }, &result); return result; } ================================================ FILE: src/audio/DirectSoundAPI.h ================================================ #pragma once #define DIRECTSOUND_VERSION 0x0800 #include #include #include #include "IAudioAPI.h" class DirectSoundAPI : public IAudioAPI { public: class DirectSoundDeviceDescription : public DeviceDescription { public: DirectSoundDeviceDescription(const std::wstring& name, GUID* guid) : DeviceDescription(name), m_guid(guid) { } std::wstring GetIdentifier() const override; GUID* GetGUID() const { return m_guid; } private: GUID* m_guid; }; using DirectSoundDeviceDescriptionPtr = std::shared_ptr; // output DirectSoundAPI(GUID* guid, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); ~DirectSoundAPI(); AudioAPI GetType() const override { return DirectSound; } bool Play() override; bool Stop() override; bool FeedBlock(sint16* data) override; void SetVolume(sint32 volume) override; bool NeedAdditionalBlocks() const override; static std::vector GetDevices(); static std::vector GetInputDevices(); private: Microsoft::WRL::ComPtr m_direct_sound; //Microsoft::WRL::ComPtr m_direct_sound_capture; Microsoft::WRL::ComPtr m_sound_buffer; Microsoft::WRL::ComPtr m_notify; DWORD m_sound_buffer_size = 0; uint32_t m_offset = 0; bool m_data_written = false; static const uint32 kBufferCount = 4; std::array m_notify_event{}; mutable std::shared_mutex m_mutex; std::queue> m_buffer; std::thread m_thread; std::atomic_bool m_running = false; void AudioThread(); }; ================================================ FILE: src/audio/IAudioAPI.cpp ================================================ #include "IAudioAPI.h" #if BOOST_OS_WINDOWS #include "XAudio2API.h" #include "XAudio27API.h" #include "DirectSoundAPI.h" #endif #include "config/CemuConfig.h" #if HAS_CUBEB #include "CubebAPI.h" #endif std::shared_mutex g_audioMutex; AudioAPIPtr g_tvAudio; AudioAPIPtr g_padAudio; AudioAPIPtr g_portalAudio; std::atomic_int32_t g_padVolume = 0; uint32 IAudioAPI::s_audioDelay = 2; std::array IAudioAPI::s_availableApis{}; IAudioAPI::IAudioAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) : m_samplerate(samplerate), m_channels(channels), m_samplesPerBlock(samples_per_block), m_bitsPerSample(bits_per_sample) { m_bytesPerBlock = samples_per_block * channels * (bits_per_sample / 8); InitWFX(m_samplerate, m_channels, m_bitsPerSample); } void IAudioAPI::PrintLogging() { cemuLog_log(LogType::Force, "------- Init Audio backend -------"); cemuLog_log(LogType::Force, "DirectSound: {}", s_availableApis[DirectSound] ? "available" : "not supported"); cemuLog_log(LogType::Force, "XAudio 2.8: {}", s_availableApis[XAudio2] ? "available" : "not supported"); if (!s_availableApis[XAudio2]) { cemuLog_log(LogType::Force, "XAudio 2.7: {}", s_availableApis[XAudio27] ? "available" : "not supported"); } cemuLog_log(LogType::Force, "Cubeb: {}", s_availableApis[Cubeb] ? "available" : "not supported"); } void IAudioAPI::InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample) { #if BOOST_OS_WINDOWS // move this to Windows-specific audio API implementations and use a cross-platform format here m_wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; m_wfx.Format.nChannels = channels; m_wfx.Format.nSamplesPerSec = samplerate; m_wfx.Format.wBitsPerSample = bits_per_sample; m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels � wBitsPerSample) / 8 m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec � nBlockAlign. m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; m_wfx.Samples.wValidBitsPerSample = bits_per_sample; switch (channels) { case 8: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER); break; case 6: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); break; case 4: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); break; case 2: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT); break; default: m_wfx.dwChannelMask = 0; break; } #endif } void IAudioAPI::InitializeStatic() { s_audioDelay = GetConfig().audio_delay; #if BOOST_OS_WINDOWS s_availableApis[DirectSound] = true; s_availableApis[XAudio2] = XAudio2API::InitializeStatic(); if (!s_availableApis[XAudio2]) // don't try to initialize the older lib if the newer version is available s_availableApis[XAudio27] = XAudio27API::InitializeStatic(); #endif #if HAS_CUBEB s_availableApis[Cubeb] = CubebAPI::InitializeStatic(); #endif } bool IAudioAPI::IsAudioAPIAvailable(AudioAPI api) { if ((size_t)api < s_availableApis.size()) return s_availableApis[api]; cemu_assert_debug(false); return false; } AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample) { sint32 channels = CemuConfig::AudioChannelsToNChannels(AudioTypeToChannels(type)); return CreateDeviceFromConfig(type, rate, channels, samples_per_block, bits_per_sample); } AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) { AudioAPIPtr audioAPIDev; auto& config = GetConfig(); const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api; auto selectedDevice = GetDeviceFromType(type); if (selectedDevice.empty()) return {}; IAudioAPI::DeviceDescriptionPtr device_description; if (IAudioAPI::IsAudioAPIAvailable(audio_api)) { auto devices = IAudioAPI::GetDevices(audio_api); const auto it = std::find_if(devices.begin(), devices.end(), [&selectedDevice](const auto& d) { return d->GetIdentifier() == selectedDevice; }); if (it != devices.end()) device_description = *it; } if (!device_description) throw std::runtime_error("failed to find selected device while trying to create audio device"); audioAPIDev = CreateDevice(audio_api, device_description, rate, channels, samples_per_block, bits_per_sample); audioAPIDev->SetVolume(GetVolumeFromType(type)); return audioAPIDev; } AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) { if (!IsAudioAPIAvailable(api)) return {}; switch (api) { #if BOOST_OS_WINDOWS case DirectSound: { const auto tmp = std::dynamic_pointer_cast(device); return std::make_unique(tmp->GetGUID(), samplerate, channels, samples_per_block, bits_per_sample); } case XAudio27: { const auto tmp = std::dynamic_pointer_cast(device); return std::make_unique(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample); } case XAudio2: { const auto tmp = std::dynamic_pointer_cast(device); return std::make_unique(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample); } #endif #if HAS_CUBEB case Cubeb: { const auto tmp = std::dynamic_pointer_cast(device); return std::make_unique(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample); } #endif default: throw std::runtime_error(fmt::format("invalid audio api: {}", api)); } } std::vector IAudioAPI::GetDevices(AudioAPI api) { if (!IsAudioAPIAvailable(api)) return {}; switch (api) { #if BOOST_OS_WINDOWS case DirectSound: { return DirectSoundAPI::GetDevices(); } case XAudio27: { return XAudio27API::GetDevices(); } case XAudio2: { return XAudio2API::GetDevices(); } #endif #if HAS_CUBEB case Cubeb: { return CubebAPI::GetDevices(); } #endif default: throw std::runtime_error(fmt::format("invalid audio api: {}", api)); } } void IAudioAPI::SetAudioDelayOverride(uint32 delay) { m_audioDelayOverride = delay; } uint32 IAudioAPI::GetAudioDelay() const { return m_audioDelayOverride > 0 ? m_audioDelayOverride : s_audioDelay; } AudioChannels IAudioAPI::AudioTypeToChannels(AudioType type) { auto& config = GetConfig(); switch (type) { case TV: return config.tv_channels; case Gamepad: return config.pad_channels; case Portal: return kMono; default: return kMono; } } std::wstring IAudioAPI::GetDeviceFromType(AudioType type) { auto& config = GetConfig(); switch (type) { case TV: return config.tv_device; case Gamepad: return config.pad_device; case Portal: return config.portal_device; default: return L""; } } sint32 IAudioAPI::GetVolumeFromType(AudioType type) { auto& config = GetConfig(); switch (type) { case TV: return config.tv_volume; case Gamepad: return config.pad_volume; case Portal: return config.portal_volume; default: return 0; } } ================================================ FILE: src/audio/IAudioAPI.h ================================================ #pragma once #if BOOST_OS_WINDOWS #include #endif #include "config/CemuConfig.h" class IAudioAPI { friend class GeneralSettings2; public: class DeviceDescription { public: explicit DeviceDescription(std::wstring name) : m_name(std::move(name)) { } virtual ~DeviceDescription() = default; virtual std::wstring GetIdentifier() const = 0; const std::wstring& GetName() const { return m_name; } bool operator==(const DeviceDescription& o) const { return GetIdentifier() == o.GetIdentifier(); } private: std::wstring m_name; }; using DeviceDescriptionPtr = std::shared_ptr; enum AudioType { TV = 0, Gamepad, Portal }; enum AudioAPI { DirectSound = 0, XAudio27, XAudio2, Cubeb, AudioAPIEnd, }; static constexpr uint32 kBlockCount = 24; IAudioAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); virtual ~IAudioAPI() = default; virtual AudioAPI GetType() const = 0; sint32 GetChannels() const { return m_channels; } virtual sint32 GetVolume() const { return m_volume; } virtual void SetVolume(sint32 volume) { m_volume = volume; } virtual void SetInputVolume(sint32 volume) { m_inputVolume = volume; } virtual bool NeedAdditionalBlocks() const = 0; virtual bool FeedBlock(sint16* data) = 0; virtual bool Play() = 0; virtual bool Stop() = 0; void SetAudioDelayOverride(uint32 delay); uint32 GetAudioDelay() const; static void PrintLogging(); static void InitializeStatic(); static bool IsAudioAPIAvailable(AudioAPI api); static std::unique_ptr CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample); static std::unique_ptr CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); static std::unique_ptr CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); static std::vector GetDevices(AudioAPI api); protected: #if BOOST_OS_WINDOWS WAVEFORMATEXTENSIBLE m_wfx{}; #endif uint32 m_samplerate, m_channels, m_samplesPerBlock, m_bitsPerSample; uint32 m_bytesPerBlock; sint32 m_volume = 0, m_inputVolume = 0; bool m_playing = false; static std::array s_availableApis; uint32 m_audioDelayOverride = 0; private: static uint32 s_audioDelay; void InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample); static AudioChannels AudioTypeToChannels(AudioType type); static std::wstring GetDeviceFromType(AudioType type); static sint32 GetVolumeFromType(AudioType type); }; using AudioAPIPtr = std::unique_ptr; extern std::shared_mutex g_audioMutex; extern AudioAPIPtr g_tvAudio; extern AudioAPIPtr g_padAudio; extern std::atomic_int32_t g_padVolume; extern AudioAPIPtr g_portalAudio; ================================================ FILE: src/audio/IAudioInputAPI.cpp ================================================ #include "IAudioInputAPI.h" #if HAS_CUBEB #include "CubebInputAPI.h" #endif std::shared_mutex g_audioInputMutex; AudioInputAPIPtr g_inputAudio; std::array IAudioInputAPI::s_availableApis{}; IAudioInputAPI::IAudioInputAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) : m_samplerate(samplerate), m_channels(channels), m_samplesPerBlock(samples_per_block), m_bitsPerSample(bits_per_sample) { m_bytesPerBlock = samples_per_block * channels * (bits_per_sample / 8); } void IAudioInputAPI::PrintLogging() { cemuLog_log(LogType::Force, "------- Init Audio input backend -------"); cemuLog_log(LogType::Force, "Cubeb: {}", s_availableApis[Cubeb] ? "available" : "not supported"); } void IAudioInputAPI::InitializeStatic() { #if HAS_CUBEB s_availableApis[Cubeb] = CubebInputAPI::InitializeStatic(); #endif } bool IAudioInputAPI::IsAudioInputAPIAvailable(AudioInputAPI api) { if ((size_t)api < s_availableApis.size()) return s_availableApis[api]; cemu_assert_debug(false); return false; } AudioInputAPIPtr IAudioInputAPI::CreateDevice(AudioInputAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) { if (!IsAudioInputAPIAvailable(api)) return {}; switch(api) { #if HAS_CUBEB case Cubeb: { const auto tmp = std::dynamic_pointer_cast(device); return std::make_unique(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample); } #endif default: throw std::runtime_error(fmt::format("invalid audio api: {}", api)); } } std::vector IAudioInputAPI::GetDevices(AudioInputAPI api) { if (!IsAudioInputAPIAvailable(api)) return {}; switch(api) { #if HAS_CUBEB case Cubeb: { return CubebInputAPI::GetDevices(); } #endif default: throw std::runtime_error(fmt::format("invalid audio api: {}", api)); } } ================================================ FILE: src/audio/IAudioInputAPI.h ================================================ #pragma once class IAudioInputAPI { friend class GeneralSettings2; public: class DeviceDescription { public: explicit DeviceDescription(std::wstring name) : m_name(std::move(name)) { } virtual ~DeviceDescription() = default; virtual std::wstring GetIdentifier() const = 0; const std::wstring& GetName() const { return m_name; } bool operator==(const DeviceDescription& o) const { return GetIdentifier() == o.GetIdentifier(); } private: std::wstring m_name; }; using DeviceDescriptionPtr = std::shared_ptr; enum AudioInputAPI { Cubeb, AudioInputAPIEnd, }; static constexpr uint32 kBlockCount = 24; IAudioInputAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); virtual ~IAudioInputAPI() = default; virtual AudioInputAPI GetType() const = 0; sint32 GetChannels() const { return m_channels; } virtual sint32 GetVolume() const { return m_volume; } virtual void SetVolume(sint32 volume) { m_volume = volume; } virtual bool ConsumeBlock(sint16* data) = 0; virtual bool Play() = 0; virtual bool Stop() = 0; virtual bool IsPlaying() const = 0; static void PrintLogging(); static void InitializeStatic(); static bool IsAudioInputAPIAvailable(AudioInputAPI api); static std::unique_ptr CreateDevice(AudioInputAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); static std::vector GetDevices(AudioInputAPI api); protected: uint32 m_samplerate, m_channels, m_samplesPerBlock, m_bitsPerSample; uint32 m_bytesPerBlock; sint32 m_volume = 0; static std::array s_availableApis; private: }; using AudioInputAPIPtr = std::unique_ptr; extern std::shared_mutex g_audioInputMutex; extern AudioInputAPIPtr g_inputAudio; ================================================ FILE: src/audio/XAudio27API.cpp ================================================ #include "XAudio27API.h" #include "../dependencies/DirectX_2010/XAudio2.h" static_assert(IAudioAPI::kBlockCount < XAUDIO2_MAX_QUEUED_BUFFERS, "too many xaudio2 buffers"); HMODULE XAudio27API::s_xaudio_dll = nullptr; std::unique_ptr XAudio27API::s_xaudio; XAudio27API::XAudio27API(uint32 device_id, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) : IAudioAPI(samplerate, channels, samples_per_block, bits_per_sample) { if (!s_xaudio) throw std::runtime_error("xaudio 2.7 not initialized!"); // we use -1 for always selecting the primary device, which is the first one if (device_id == -1) device_id = 0; HRESULT hres; IXAudio2* xaudio; if (FAILED((hres = XAudio2Create(&xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR)))) throw std::runtime_error(fmt::format("can't create xaudio device (hres: {:#x})", hres)); m_xaudio = decltype(m_xaudio)(xaudio); IXAudio2MasteringVoice* mastering_voice; if (FAILED((hres = m_xaudio->CreateMasteringVoice(&mastering_voice, channels, samplerate, 0, device_id)))) throw std::runtime_error(fmt::format("can't create xaudio mastering voice (hres: {:#x})", hres)); m_mastering_voice = decltype(m_mastering_voice)(mastering_voice); m_wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; m_wfx.Format.nChannels = channels; m_wfx.Format.nSamplesPerSec = samplerate; m_wfx.Format.wBitsPerSample = bits_per_sample; m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8 m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign. m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; m_wfx.Samples.wValidBitsPerSample = bits_per_sample; switch (channels) { case 8: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER); break; case 6: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); break; case 4: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); break; case 2: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT); break; default: m_wfx.dwChannelMask = 0; break; } IXAudio2SourceVoice* source_voice; if (FAILED((hres = m_xaudio->CreateSourceVoice(&source_voice, &m_wfx.Format, 0, 1.0f)))) throw std::runtime_error(fmt::format("can't create xaudio source voice (hres: {:#x})", hres)); m_source_voice = decltype(m_source_voice)(source_voice); m_sound_buffer_size = kBlockCount * (samples_per_block * channels * (bits_per_sample / 8)); for (uint32 i = 0; i < kBlockCount; ++i) m_audio_buffer[i] = std::make_unique(m_bytesPerBlock); m_xaudio->StartEngine(); } XAudio27API::~XAudio27API() { if(m_xaudio) m_xaudio->StopEngine(); XAudio27API::Stop(); m_source_voice.reset(); m_mastering_voice.reset(); m_xaudio.reset(); } void XAudio27API::SetVolume(sint32 volume) { IAudioAPI::SetVolume(volume); m_mastering_voice->SetVolume((float)volume / 100.0f); } bool XAudio27API::Play() { if (m_playing) return true; m_playing = SUCCEEDED(m_source_voice->Start()); return m_playing; } bool XAudio27API::Stop() { if (!m_playing) return true; m_playing = FAILED(m_source_voice->Stop()); m_source_voice->FlushSourceBuffers(); return m_playing; } bool XAudio27API::InitializeStatic() { if (s_xaudio) return true; #ifdef _DEBUG s_xaudio_dll = LoadLibraryExW(L"XAudioD2_7.DLL", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if(!s_xaudio_dll) s_xaudio_dll = LoadLibraryExW(L"XAudio2_7.DLL", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); #else s_xaudio_dll = LoadLibraryExW(L"XAudio2_7.DLL", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); #endif try { if (!s_xaudio_dll) throw std::exception(); IXAudio2* xaudio; if (FAILED(XAudio2Create(&xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR))) throw std::exception(); s_xaudio = decltype(s_xaudio)(xaudio); return true; } catch (const std::exception&) { if (s_xaudio_dll) FreeLibrary(s_xaudio_dll); return false; } } void XAudio27API::Destroy() { s_xaudio.reset(); if (s_xaudio_dll) FreeLibrary(s_xaudio_dll); } std::vector XAudio27API::GetDevices() { std::vector result; // always add the default device auto default_device = std::make_shared(L"Primary Sound Driver", L"", -1); result.emplace_back(default_device); uint32 count = 0; if (FAILED(s_xaudio->GetDeviceCount(&count))) return result; if (!count) return result; result.reserve(count + 1); // first device is always the primary device for (uint32 id = 0; id < count; ++id) { XAUDIO2_DEVICE_DETAILS details; if (SUCCEEDED(s_xaudio->GetDeviceDetails(id, &details))) { auto device = std::make_shared(details.DisplayName, details.DeviceID, id); result.emplace_back(device); } } return result; } void XAudio27API::XAudioDeleter::operator()(IXAudio2* ptr) const { if (ptr) ptr->Release(); } void XAudio27API::VoiceDeleter::operator()(IXAudio2Voice* ptr) const { if (ptr) ptr->DestroyVoice(); } bool XAudio27API::FeedBlock(sint16* data) { // check if we queued too many blocks if(m_blocks_queued >= kBlockCount) { m_blocks_queued = GetQueuedBuffers(); if (m_blocks_queued >= kBlockCount) { cemuLog_logDebug(LogType::Force, "dropped xaudio2 block since too many buffers are queued"); return false; } } memcpy(m_audio_buffer[m_offset].get(), data, m_bytesPerBlock); XAUDIO2_BUFFER buffer{}; buffer.AudioBytes = m_bytesPerBlock; buffer.pAudioData = m_audio_buffer[m_offset].get(); m_source_voice->SubmitSourceBuffer(&buffer); m_offset = (m_offset + 1) % kBlockCount; m_blocks_queued++; return true; } uint32 XAudio27API::GetQueuedBuffers() const { XAUDIO2_VOICE_STATE state{}; m_source_voice->GetState(&state); return state.BuffersQueued; } bool XAudio27API::NeedAdditionalBlocks() const { return GetQueuedBuffers() < GetAudioDelay(); } ================================================ FILE: src/audio/XAudio27API.h ================================================ #pragma once #define DIRECTSOUND_VERSION 0x0800 #include #include #include #include "IAudioAPI.h" struct IXAudio2; struct IXAudio2Voice; struct IXAudio2MasteringVoice; struct IXAudio2SourceVoice; class XAudio27API : public IAudioAPI { public: class XAudio27DeviceDescription : public DeviceDescription { public: XAudio27DeviceDescription(const std::wstring& name, std::wstring device_id, uint32 id) : DeviceDescription(name), m_device_id(std::move(device_id)), m_id(id) { } std::wstring GetIdentifier() const override { return m_device_id.empty() ? L"default" : m_device_id; } uint32 GetDeviceId() const { return m_id; } private: std::wstring m_device_id; const uint32 m_id; }; using XAudio2DeviceDescriptionPtr = std::shared_ptr; AudioAPI GetType() const override { return XAudio27; } XAudio27API(uint32 device_id, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); ~XAudio27API(); void SetVolume(sint32 volume) override; bool Play() override; bool Stop() override; bool FeedBlock(sint16* data) override; bool NeedAdditionalBlocks() const override; static bool InitializeStatic(); static void Destroy(); static std::vector GetDevices(); private: uint32 GetQueuedBuffers() const; struct XAudioDeleter { void operator()(IXAudio2* ptr) const; }; struct VoiceDeleter { void operator()(IXAudio2Voice* ptr) const; }; static HMODULE s_xaudio_dll; static std::unique_ptr s_xaudio; std::unique_ptr m_xaudio; std::wstring m_device_id; std::unique_ptr m_mastering_voice; std::unique_ptr m_source_voice; std::unique_ptr m_audio_buffer[kBlockCount]; DWORD m_sound_buffer_size = 0; uint32_t m_offset = 0; uint32_t m_blocks_queued = 0; }; ================================================ FILE: src/audio/XAudio2API.cpp ================================================ //#if (_WIN32_WINNT >= 0x0602 /*_WIN32_WINNT_WIN8*/) #include #include #ifndef XAUDIO2_DLL #error wrong included! #endif #include "XAudio2API.h" #include "util/helpers/helpers.h" #include #include #include // guid from mmdeviceapi.h static const GUID DEVINTERFACE_AUDIO_RENDER_GUID = { 0xe6327cad, 0xdcec, 0x4949, 0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2 }; #pragma comment(lib, "wbemuuid.lib") static_assert(IAudioAPI::kBlockCount < XAUDIO2_MAX_QUEUED_BUFFERS, "too many xaudio2 buffers"); HMODULE XAudio2API::s_xaudio_dll = nullptr; std::vector XAudio2API::s_devices; XAudio2API::XAudio2API(std::wstring device_id, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) : IAudioAPI(samplerate, channels, samples_per_block, bits_per_sample), m_device_id(std::move(device_id)) { const auto _XAudio2Create = (decltype(&XAudio2Create))GetProcAddress(s_xaudio_dll, "XAudio2Create"); if (!_XAudio2Create) throw std::runtime_error("can't find XAudio2Create import"); HRESULT hres; if (FAILED((hres = _XAudio2Create(&m_xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR)))) throw std::runtime_error(fmt::format("can't create xaudio device (hres: {:#x})", hres)); IXAudio2MasteringVoice* mastering_voice; if (FAILED((hres = m_xaudio->CreateMasteringVoice(&mastering_voice, channels, samplerate, 0, m_device_id.empty() ? nullptr : m_device_id.c_str())))) throw std::runtime_error(fmt::format("can't create xaudio mastering voice (hres: {:#x})", hres)); m_mastering_voice.reset(mastering_voice); m_wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; m_wfx.Format.nChannels = channels; m_wfx.Format.nSamplesPerSec = samplerate; m_wfx.Format.wBitsPerSample = bits_per_sample; m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8 m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign. m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; m_wfx.Samples.wValidBitsPerSample = bits_per_sample; switch(channels) { case 8: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER); break; case 6: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); break; case 4: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); break; case 2: m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT); break; default: m_wfx.dwChannelMask = 0; break; } IXAudio2SourceVoice* source_voice; if (FAILED((hres = m_xaudio->CreateSourceVoice(&source_voice, &m_wfx.Format, 0, 1.0f)))) throw std::runtime_error(fmt::format("can't create xaudio source voice (hres: {:#x})", hres)); m_source_voice = decltype(m_source_voice)(source_voice); m_sound_buffer_size = kBlockCount * (samples_per_block * channels * (bits_per_sample / 8)); for (uint32 i = 0; i < kBlockCount; ++i) m_audio_buffer[i] = std::make_unique(m_bytesPerBlock); m_xaudio->StartEngine(); } void XAudio2API::VoiceDeleter::operator()(IXAudio2Voice* ptr) const { if (ptr) ptr->DestroyVoice(); } XAudio2API::~XAudio2API() { if(m_xaudio) m_xaudio->StopEngine(); XAudio2API::Stop(); } void XAudio2API::SetVolume(sint32 volume) { IAudioAPI::SetVolume(volume); m_mastering_voice->SetVolume((float)volume / 100.0f); } bool XAudio2API::Play() { if (m_playing) return true; m_playing = SUCCEEDED(m_source_voice->Start()); return m_playing; } bool XAudio2API::Stop() { if (!m_playing) return true; m_playing = FAILED(m_source_voice->Stop()); m_source_voice->FlushSourceBuffers(); return m_playing; } bool XAudio2API::InitializeStatic() { if (s_xaudio_dll) return true; // win 10 s_xaudio_dll = LoadLibraryEx(XAUDIO2_DLL, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); try { if (!s_xaudio_dll) throw std::exception(); const auto _XAudio2Create = (decltype(&XAudio2Create))GetProcAddress(s_xaudio_dll, "XAudio2Create"); if (!_XAudio2Create) throw std::exception(); RefreshDevices(); return true; } catch (const std::exception&) { if (s_xaudio_dll) FreeLibrary(s_xaudio_dll); return false; } } void XAudio2API::Destroy() { if (s_xaudio_dll) FreeLibrary(s_xaudio_dll); } const std::vector& XAudio2API::RefreshDevices() { s_devices.clear(); try { Microsoft::WRL::ComPtr wbem_locator; HRESULT hres = CoCreateInstance(__uuidof(WbemLocator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&wbem_locator)); if (FAILED(hres)) throw std::system_error(hres, std::system_category()); std::shared_ptr path(SysAllocString(LR"(\\.\root\cimv2)"), SysFreeString); std::shared_ptr language(SysAllocString(L"WQL"), SysFreeString); std::shared_ptr query(SysAllocString(LR"(SELECT Name,DeviceID FROM Win32_PNPEntity WHERE PNPClass = "AudioEndpoint")"), SysFreeString); std::shared_ptr name_row(SysAllocString(L"Name"), SysFreeString); std::shared_ptr device_id_row(SysAllocString(L"DeviceID"), SysFreeString); Microsoft::WRL::ComPtr wbem_services; hres = wbem_locator->ConnectServer(path.get(), nullptr, nullptr, nullptr, 0, nullptr, nullptr, &wbem_services); if (FAILED(hres)) throw std::system_error(hres, std::system_category()); hres = CoSetProxyBlanket(wbem_services.Get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE); if (FAILED(hres)) throw std::system_error(hres, std::system_category()); Microsoft::WRL::ComPtr wbem_enum; hres = wbem_services->ExecQuery(language.get(), query.get(), WBEM_FLAG_RETURN_WBEM_COMPLETE | WBEM_FLAG_FORWARD_ONLY, nullptr, &wbem_enum); if (FAILED(hres)) throw std::system_error(hres, std::system_category()); ULONG returned; IWbemClassObject* object[20]; // WBEM_S_TIMEDOUT while (SUCCEEDED(hres = wbem_enum->Next(100, 20, object, &returned)) && returned > 0) { for (ULONG i = 0; i < returned; ++i) { std::wstring name, device_id; VARIANT var; if (SUCCEEDED(object[i]->Get(name_row.get(), 0L, &var, NULL, NULL)) && var.vt == VT_BSTR && var.bstrVal) { name = var.bstrVal; if (SUCCEEDED(object[i]->Get(device_id_row.get(), 0L, &var, NULL, NULL)) && var.vt == VT_BSTR && var.bstrVal) { std::wstring id = var.bstrVal; if(id.find(L"{0.0.0.00000000}") == std::wstring::npos) { object[i]->Release(); continue; } std::replace(id.begin(), id.end(), L'\\', L'#'); // xaudio devices have "#" instead of backslashes std::wstringstream tmp; tmp << L"\\\\?\\" << id << L"#{" << WStringFromGUID(DEVINTERFACE_AUDIO_RENDER_GUID) << L"}"; device_id = tmp.str(); } } auto device = std::make_shared(name,device_id); s_devices.emplace_back(device); object[i]->Release(); } } // Only add default device if audio devices exist if (s_devices.size() > 0) { auto default_device = std::make_shared(L"Primary Sound Driver", L""); s_devices.insert(s_devices.begin(), default_device); } } catch (const std::system_error& ex) { cemuLog_log(LogType::Force, "XAudio2API::RefreshDevices: error while refreshing device list ({} - code: 0x{:08x})", ex.what(), ex.code().value()); } CoUninitialize(); return s_devices; } bool XAudio2API::FeedBlock(sint16* data) { // check if we queued too many blocks if (m_blocks_queued >= kBlockCount) { m_blocks_queued = GetQueuedBuffers(); if (m_blocks_queued >= kBlockCount) { cemuLog_logDebug(LogType::Force, "dropped xaudio2 block since too many buffers are queued"); return false; } } memcpy(m_audio_buffer[m_offset].get(), data, m_bytesPerBlock); XAUDIO2_BUFFER buffer{}; buffer.AudioBytes = m_bytesPerBlock; buffer.pAudioData = m_audio_buffer[m_offset].get(); m_source_voice->SubmitSourceBuffer(&buffer); m_offset = (m_offset + 1) % kBlockCount; m_blocks_queued++; return true; } uint32 XAudio2API::GetQueuedBuffers() const { XAUDIO2_VOICE_STATE state{}; m_source_voice->GetState(&state); return state.BuffersQueued; } bool XAudio2API::NeedAdditionalBlocks() const { return GetQueuedBuffers() < GetAudioDelay(); } ================================================ FILE: src/audio/XAudio2API.h ================================================ #pragma once #define DIRECTSOUND_VERSION 0x0800 #include #include #include #include #include "IAudioAPI.h" struct IXAudio2; struct IXAudio2Voice; struct IXAudio2MasteringVoice; struct IXAudio2SourceVoice; class XAudio2API : public IAudioAPI { public: class XAudio2DeviceDescription : public DeviceDescription { public: XAudio2DeviceDescription(const std::wstring& name, std::wstring device_id) : DeviceDescription(name), m_device_id(std::move(device_id)) { } std::wstring GetIdentifier() const override { return m_device_id.empty() ? L"default" : m_device_id; } const std::wstring& GetDeviceId() const { return m_device_id; } private: std::wstring m_device_id; }; using XAudio2DeviceDescriptionPtr = std::shared_ptr; AudioAPI GetType() const override { return XAudio27; } XAudio2API(std::wstring device_id, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); ~XAudio2API(); void SetVolume(sint32 volume) override; bool Play() override; bool Stop() override; bool FeedBlock(sint16* data) override; bool NeedAdditionalBlocks() const override; static bool InitializeStatic(); static void Destroy(); static const std::vector& GetDevices() { return s_devices; } private: uint32 GetQueuedBuffers() const; static const std::vector& RefreshDevices(); struct VoiceDeleter { void operator()(IXAudio2Voice* ptr) const; }; static HMODULE s_xaudio_dll; static std::vector s_devices; Microsoft::WRL::ComPtr m_xaudio; std::wstring m_device_id; std::unique_ptr m_mastering_voice; std::unique_ptr m_source_voice; std::unique_ptr m_audio_buffer[kBlockCount]; DWORD m_sound_buffer_size = 0; uint32_t m_offset = 0; uint32_t m_blocks_queued = 0; }; ================================================ FILE: src/audio/xaudio2_7/audiodefs.h ================================================ /*************************************************************************** * * Copyright (c) Microsoft Corporation. All rights reserved. * * File: audiodefs.h * Content: Basic constants and data types for audio work. * * Remarks: This header file defines all of the audio format constants and * structures required for XAudio2 and XACT work. Providing these * in a single location avoids certain dependency problems in the * legacy audio headers (mmreg.h, mmsystem.h, ksmedia.h). * * NOTE: Including the legacy headers after this one may cause a * compilation error, because they define some of the same types * defined here without preprocessor guards to avoid multiple * definitions. If a source file needs one of the old headers, * it must include it before including audiodefs.h. * ***************************************************************************/ #ifndef __AUDIODEFS_INCLUDED__ #define __AUDIODEFS_INCLUDED__ #include // For WORD, DWORD, etc. #pragma pack(push, 1) // Pack structures to 1-byte boundaries /************************************************************************** * * WAVEFORMATEX: Base structure for many audio formats. Format-specific * extensions can be defined for particular formats by using a non-zero * cbSize value and adding extra fields to the end of this structure. * ***************************************************************************/ #ifndef _WAVEFORMATEX_ #define _WAVEFORMATEX_ typedef struct tWAVEFORMATEX { WORD wFormatTag; // Integer identifier of the format WORD nChannels; // Number of audio channels DWORD nSamplesPerSec; // Audio sample rate DWORD nAvgBytesPerSec; // Bytes per second (possibly approximate) WORD nBlockAlign; // Size in bytes of a sample block (all channels) WORD wBitsPerSample; // Size in bits of a single per-channel sample WORD cbSize; // Bytes of extra data appended to this struct } WAVEFORMATEX; #endif // Defining pointer types outside of the #if block to make sure they are // defined even if mmreg.h or mmsystem.h is #included before this file typedef WAVEFORMATEX *PWAVEFORMATEX, *NPWAVEFORMATEX, *LPWAVEFORMATEX; typedef const WAVEFORMATEX *PCWAVEFORMATEX, *LPCWAVEFORMATEX; /************************************************************************** * * WAVEFORMATEXTENSIBLE: Extended version of WAVEFORMATEX that should be * used as a basis for all new audio formats. The format tag is replaced * with a GUID, allowing new formats to be defined without registering a * format tag with Microsoft. There are also new fields that can be used * to specify the spatial positions for each channel and the bit packing * used for wide samples (e.g. 24-bit PCM samples in 32-bit containers). * ***************************************************************************/ #ifndef _WAVEFORMATEXTENSIBLE_ #define _WAVEFORMATEXTENSIBLE_ typedef struct { WAVEFORMATEX Format; // Base WAVEFORMATEX data union { WORD wValidBitsPerSample; // Valid bits in each sample container WORD wSamplesPerBlock; // Samples per block of audio data; valid // if wBitsPerSample=0 (but rarely used). WORD wReserved; // Zero if neither case above applies. } Samples; DWORD dwChannelMask; // Positions of the audio channels GUID SubFormat; // Format identifier GUID } WAVEFORMATEXTENSIBLE; #endif typedef WAVEFORMATEXTENSIBLE *PWAVEFORMATEXTENSIBLE, *LPWAVEFORMATEXTENSIBLE; typedef const WAVEFORMATEXTENSIBLE *PCWAVEFORMATEXTENSIBLE, *LPCWAVEFORMATEXTENSIBLE; /************************************************************************** * * Define the most common wave format tags used in WAVEFORMATEX formats. * ***************************************************************************/ #ifndef WAVE_FORMAT_PCM // Pulse Code Modulation // If WAVE_FORMAT_PCM is not defined, we need to define some legacy types // for compatibility with the Windows mmreg.h / mmsystem.h header files. // Old general format structure (information common to all formats) typedef struct waveformat_tag { WORD wFormatTag; WORD nChannels; DWORD nSamplesPerSec; DWORD nAvgBytesPerSec; WORD nBlockAlign; } WAVEFORMAT, *PWAVEFORMAT, NEAR *NPWAVEFORMAT, FAR *LPWAVEFORMAT; // Specific format structure for PCM data typedef struct pcmwaveformat_tag { WAVEFORMAT wf; WORD wBitsPerSample; } PCMWAVEFORMAT, *PPCMWAVEFORMAT, NEAR *NPPCMWAVEFORMAT, FAR *LPPCMWAVEFORMAT; #define WAVE_FORMAT_PCM 0x0001 #endif #ifndef WAVE_FORMAT_ADPCM // Microsoft Adaptive Differental PCM // Replicate the Microsoft ADPCM type definitions from mmreg.h. typedef struct adpcmcoef_tag { short iCoef1; short iCoef2; } ADPCMCOEFSET; #pragma warning(push) #pragma warning(disable:4200) // Disable zero-sized array warnings typedef struct adpcmwaveformat_tag { WAVEFORMATEX wfx; WORD wSamplesPerBlock; WORD wNumCoef; ADPCMCOEFSET aCoef[]; // Always 7 coefficient pairs for MS ADPCM } ADPCMWAVEFORMAT; #pragma warning(pop) #define WAVE_FORMAT_ADPCM 0x0002 #endif // Other frequently used format tags #ifndef WAVE_FORMAT_UNKNOWN #define WAVE_FORMAT_UNKNOWN 0x0000 // Unknown or invalid format tag #endif #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 // 32-bit floating-point #endif #ifndef WAVE_FORMAT_MPEGLAYER3 #define WAVE_FORMAT_MPEGLAYER3 0x0055 // ISO/MPEG Layer3 #endif #ifndef WAVE_FORMAT_DOLBY_AC3_SPDIF #define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092 // Dolby Audio Codec 3 over S/PDIF #endif #ifndef WAVE_FORMAT_WMAUDIO2 #define WAVE_FORMAT_WMAUDIO2 0x0161 // Windows Media Audio #endif #ifndef WAVE_FORMAT_WMAUDIO3 #define WAVE_FORMAT_WMAUDIO3 0x0162 // Windows Media Audio Pro #endif #ifndef WAVE_FORMAT_WMASPDIF #define WAVE_FORMAT_WMASPDIF 0x0164 // Windows Media Audio over S/PDIF #endif #ifndef WAVE_FORMAT_EXTENSIBLE #define WAVE_FORMAT_EXTENSIBLE 0xFFFE // All WAVEFORMATEXTENSIBLE formats #endif /************************************************************************** * * Define the most common wave format GUIDs used in WAVEFORMATEXTENSIBLE * formats. Note that including the Windows ksmedia.h header after this * one will cause build problems; this cannot be avoided, since ksmedia.h * defines these macros without preprocessor guards. * ***************************************************************************/ #ifdef __cplusplus // uuid() and __uuidof() are only available in C++ #ifndef KSDATAFORMAT_SUBTYPE_PCM struct __declspec(uuid("00000001-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_PCM_STRUCT; #define KSDATAFORMAT_SUBTYPE_PCM __uuidof(KSDATAFORMAT_SUBTYPE_PCM_STRUCT) #endif #ifndef KSDATAFORMAT_SUBTYPE_ADPCM struct __declspec(uuid("00000002-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_ADPCM_STRUCT; #define KSDATAFORMAT_SUBTYPE_ADPCM __uuidof(KSDATAFORMAT_SUBTYPE_ADPCM_STRUCT) #endif #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT struct __declspec(uuid("00000003-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_STRUCT; #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT __uuidof(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_STRUCT) #endif #endif /************************************************************************** * * Speaker positions used in the WAVEFORMATEXTENSIBLE dwChannelMask field. * ***************************************************************************/ #ifndef SPEAKER_FRONT_LEFT #define SPEAKER_FRONT_LEFT 0x00000001 #define SPEAKER_FRONT_RIGHT 0x00000002 #define SPEAKER_FRONT_CENTER 0x00000004 #define SPEAKER_LOW_FREQUENCY 0x00000008 #define SPEAKER_BACK_LEFT 0x00000010 #define SPEAKER_BACK_RIGHT 0x00000020 #define SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040 #define SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080 #define SPEAKER_BACK_CENTER 0x00000100 #define SPEAKER_SIDE_LEFT 0x00000200 #define SPEAKER_SIDE_RIGHT 0x00000400 #define SPEAKER_TOP_CENTER 0x00000800 #define SPEAKER_TOP_FRONT_LEFT 0x00001000 #define SPEAKER_TOP_FRONT_CENTER 0x00002000 #define SPEAKER_TOP_FRONT_RIGHT 0x00004000 #define SPEAKER_TOP_BACK_LEFT 0x00008000 #define SPEAKER_TOP_BACK_CENTER 0x00010000 #define SPEAKER_TOP_BACK_RIGHT 0x00020000 #define SPEAKER_RESERVED 0x7FFC0000 #define SPEAKER_ALL 0x80000000 #define _SPEAKER_POSITIONS_ #endif #ifndef SPEAKER_STEREO #define SPEAKER_MONO (SPEAKER_FRONT_CENTER) #define SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT) #define SPEAKER_2POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY) #define SPEAKER_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER) #define SPEAKER_QUAD (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) #define SPEAKER_4POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) #define SPEAKER_5POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) #define SPEAKER_7POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER) #define SPEAKER_5POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) #define SPEAKER_7POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) #endif #pragma pack(pop) #endif // #ifndef __AUDIODEFS_INCLUDED__ ================================================ FILE: src/audio/xaudio2_7/comdecl.h ================================================ // comdecl.h: Macros to facilitate COM interface and GUID declarations. // Copyright (c) Microsoft Corporation. All rights reserved. #ifndef _COMDECL_H_ #define _COMDECL_H_ #ifndef _XBOX #include // For standard COM interface macros #else #pragma warning(push) #pragma warning(disable:4061) #include // Required by xobjbase.h #include // Special definitions for Xbox build #pragma warning(pop) #endif // The DEFINE_CLSID() and DEFINE_IID() macros defined below allow COM GUIDs to // be declared and defined in such a way that clients can obtain the GUIDs using // either the __uuidof() extension or the old-style CLSID_Foo / IID_IFoo names. // If using the latter approach, the client can also choose whether to get the // GUID definitions by defining the INITGUID preprocessor constant or by linking // to a GUID library. This works in either C or C++. #ifdef __cplusplus #define DECLSPEC_UUID_WRAPPER(x) __declspec(uuid(#x)) #ifdef INITGUID #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ class DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) className; \ EXTERN_C const GUID DECLSPEC_SELECTANY CLSID_##className = __uuidof(className) #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ interface DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) interfaceName; \ EXTERN_C const GUID DECLSPEC_SELECTANY IID_##interfaceName = __uuidof(interfaceName) #else // INITGUID #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ class DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) className; \ EXTERN_C const GUID CLSID_##className #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ interface DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) interfaceName; \ EXTERN_C const GUID IID_##interfaceName #endif // INITGUID #else // __cplusplus #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ DEFINE_GUID(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ DEFINE_GUID(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) #endif // __cplusplus #endif // #ifndef _COMDECL_H_ ================================================ FILE: src/audio/xaudio2_7/dxsdkver.h ================================================ /*==========================================================================; * * * File: dxsdkver.h * Content: DirectX SDK Version Include File * ****************************************************************************/ #ifndef _DXSDKVER_H_ #define _DXSDKVER_H_ #define _DXSDK_PRODUCT_MAJOR 9 #define _DXSDK_PRODUCT_MINOR 29 #define _DXSDK_BUILD_MAJOR 1962 #define _DXSDK_BUILD_MINOR 0 #endif // _DXSDKVER_H_ ================================================ FILE: src/audio/xaudio2_7/xma2defs.h ================================================ /*************************************************************************** * * Copyright (c) Microsoft Corporation. All rights reserved. * * File: xma2defs.h * Content: Constants, data types and functions for XMA2 compressed audio. * ***************************************************************************/ #ifndef __XMA2DEFS_INCLUDED__ #define __XMA2DEFS_INCLUDED__ #include // Markers for documenting API semantics #include // For S_OK, E_FAIL #include "audiodefs.h" // Basic data types and constants for audio work /*************************************************************************** * Overview ***************************************************************************/ // A typical XMA2 file contains these RIFF chunks: // // 'fmt' or 'XMA2' chunk (or both): A description of the XMA data's structure // and characteristics (length, channels, sample rate, loops, block size, etc). // // 'seek' chunk: A seek table to help navigate the XMA data. // // 'data' chunk: The encoded XMA2 data. // // The encoded XMA2 data is structured as a set of BLOCKS, which contain PACKETS, // which contain FRAMES, which contain SUBFRAMES (roughly speaking). The frames // in a file may also be divided into several subsets, called STREAMS. // // FRAME: A variable-sized segment of XMA data that decodes to exactly 512 mono // or stereo PCM samples. This is the smallest unit of XMA data that can // be decoded in isolation. Frames are an arbitrary number of bits in // length, and need not be byte-aligned. See "XMA frame structure" below. // // SUBFRAME: A region of bits in an XMA frame that decodes to 128 mono or stereo // samples. The XMA decoder cannot decode a subframe in isolation; it needs // a whole frame to work with. However, it can begin emitting the frame's // decoded samples at any one of the four subframe boundaries. Subframes // can be addressed for seeking and looping purposes. // // PACKET: A 2Kb region containing a 32-bit header and some XMA frames. Frames // can (and usually do) span packets. A packet's header includes the offset // in bits of the first frame that begins within that packet. All of the // frames that begin in a given packet belong to the same "stream" (see the // Multichannel Audio section below). // // STREAM: A set of packets within an XMA file that all contain data for the // same mono or stereo component of a PCM file with more than two channels. // The packets comprising a given stream may be interleaved with each other // more or less arbitrarily; see Multichannel Audio. // // BLOCK: An array of XMA packets; or, to break it down differently, a series of // consecutive XMA frames, padded at the end with reserved data. A block // must contain at least one 2Kb packet per stream, and it can hold up to // 4095 packets (8190Kb), but its size is typically in the 32Kb-128Kb range. // (The size chosen involves a trade-off between memory use and efficiency // of reading from permanent storage.) // // XMA frames do not span blocks, so a block is guaranteed to begin with a // set of complete frames, one per stream. Also, a block in a multi-stream // XMA2 file always contains the same number of samples for each stream; // see Multichannel Audio. // // The 'data' chunk in an XMA2 file is an array of XMA2WAVEFORMAT.BlockCount XMA // blocks, all the same size (as specified in XMA2WAVEFORMAT.BlockSizeInBytes) // except for the last one, which may be shorter. // MULTICHANNEL AUDIO: the XMA decoder can only decode raw XMA data into either // mono or stereo PCM data. In order to encode a 6-channel file (say), the file // must be deinterleaved into 3 stereo streams that are encoded independently, // producing 3 encoded XMA data streams. Then the packets in these 3 streams // are interleaved to produce a single XMA2 file, and some information is added // to the file so that the original 6-channel audio can be reconstructed at // decode time. This works using the concept of an XMA stream (see above). // // The frames for all the streams in an XMA file are interleaved in an arbitrary // order. To locate a frame that belongs to a given stream in a given XMA block, // you must examine the first few packets in the block. Here (and only here) the // packets are guaranteed to be presented in stream order, so that all frames // beginning in packet 0 belong to stream 0 (the first stereo pair), etc. // // (This means that when decoding multi-stream XMA files, only entire XMA blocks // should be submitted to the decoder; otherwise it cannot know which frames // belong to which stream.) // // Once you have one frame that belongs to a given stream, you can find the next // one by looking at the frame's 'NextFrameOffsetBits' value (which is stored in // its first 15 bits; see XMAFRAME below). The GetXmaFrameBitPosition function // uses this technique. // SEEKING IN XMA2 FILES: Here is some pseudocode to find the byte position and // subframe in an XMA2 file which will contain sample S when decoded. // // 1. Traverse the seek table to find the XMA2 block containing sample S. The // seek table is an array of big-endian DWORDs, one per block in the file. // The Nth DWORD is the total number of PCM samples that would be obtained // by decoding the entire XMA file up to the end of block N. Hence, the // block we want is the first one whose seek table entry is greater than S. // (See the GetXmaBlockContainingSample helper function.) // // 2. Calculate which frame F within the block found above contains sample S. // Since each frame decodes to 512 samples, this is straightforward. The // first frame in the block produces samples X to X + 512, where X is the // seek table entry for the prior block. So F is (S - X) / 512. // // 3. Find the bit offset within the block where frame F starts. Since frames // are variable-sized, this can only be done by traversing all the frames in // the block until we reach frame F. (See GetXmaFrameBitPosition.) // // 4. Frame F has four 128-sample subframes. To find the subframe containing S, // we can use the formula (S % 512) / 128. // // In the case of multi-stream XMA files, sample S is a multichannel sample with // parts coming from several frames, one per stream. To find all these frames, // steps 2-4 need to be repeated for each stream N, using the knowledge that the // first packets in a block are presented in stream order. The frame traversal // in step 3 must be started at the first frame in the Nth packet of the block, // which will be the first frame for stream N. (And the packet header will tell // you the first frame's start position within the packet.) // // Step 1 can be performed using the GetXmaBlockContainingSample function below, // and steps 2-4 by calling GetXmaDecodePositionForSample once for each stream. /*************************************************************************** * XMA constants ***************************************************************************/ // Size of the PCM samples produced by the XMA decoder #define XMA_OUTPUT_SAMPLE_BYTES 2u #define XMA_OUTPUT_SAMPLE_BITS (XMA_OUTPUT_SAMPLE_BYTES * 8u) // Size of an XMA packet #define XMA_BYTES_PER_PACKET 2048u #define XMA_BITS_PER_PACKET (XMA_BYTES_PER_PACKET * 8u) // Size of an XMA packet header #define XMA_PACKET_HEADER_BYTES 4u #define XMA_PACKET_HEADER_BITS (XMA_PACKET_HEADER_BYTES * 8u) // Sample blocks in a decoded XMA frame #define XMA_SAMPLES_PER_FRAME 512u // Sample blocks in a decoded XMA subframe #define XMA_SAMPLES_PER_SUBFRAME 128u // Maximum encoded data that can be submitted to the XMA decoder at a time #define XMA_READBUFFER_MAX_PACKETS 4095u #define XMA_READBUFFER_MAX_BYTES (XMA_READBUFFER_MAX_PACKETS * XMA_BYTES_PER_PACKET) // Maximum size allowed for the XMA decoder's output buffers #define XMA_WRITEBUFFER_MAX_BYTES (31u * 256u) // Required byte alignment of the XMA decoder's output buffers #define XMA_WRITEBUFFER_BYTE_ALIGNMENT 256u // Decode chunk sizes for the XMA_PLAYBACK_INIT.subframesToDecode field #define XMA_MIN_SUBFRAMES_TO_DECODE 1u #define XMA_MAX_SUBFRAMES_TO_DECODE 8u #define XMA_OPTIMAL_SUBFRAMES_TO_DECODE 4u // LoopCount<255 means finite repetitions; LoopCount=255 means infinite looping #define XMA_MAX_LOOPCOUNT 254u #define XMA_INFINITE_LOOP 255u /*************************************************************************** * XMA format structures ***************************************************************************/ // The currently recommended way to express format information for XMA2 files // is the XMA2WAVEFORMATEX structure. This structure is fully compliant with // the WAVEFORMATEX standard and contains all the information needed to parse // and manage XMA2 files in a compact way. #define WAVE_FORMAT_XMA2 0x166 typedef struct XMA2WAVEFORMATEX { WAVEFORMATEX wfx; // Meaning of the WAVEFORMATEX fields here: // wFormatTag; // Audio format type; always WAVE_FORMAT_XMA2 // nChannels; // Channel count of the decoded audio // nSamplesPerSec; // Sample rate of the decoded audio // nAvgBytesPerSec; // Used internally by the XMA encoder // nBlockAlign; // Decoded sample size; channels * wBitsPerSample / 8 // wBitsPerSample; // Bits per decoded mono sample; always 16 for XMA // cbSize; // Size in bytes of the rest of this structure (34) WORD NumStreams; // Number of audio streams (1 or 2 channels each) DWORD ChannelMask; // Spatial positions of the channels in this file, // stored as SPEAKER_xxx values (see audiodefs.h) DWORD SamplesEncoded; // Total number of PCM samples the file decodes to DWORD BytesPerBlock; // XMA block size (but the last one may be shorter) DWORD PlayBegin; // First valid sample in the decoded audio DWORD PlayLength; // Length of the valid part of the decoded audio DWORD LoopBegin; // Beginning of the loop region in decoded sample terms DWORD LoopLength; // Length of the loop region in decoded sample terms BYTE LoopCount; // Number of loop repetitions; 255 = infinite BYTE EncoderVersion; // Version of XMA encoder that generated the file WORD BlockCount; // XMA blocks in file (and entries in its seek table) } XMA2WAVEFORMATEX, *PXMA2WAVEFORMATEX; // The legacy XMA format structures are described here for reference, but they // should not be used in new content. XMAWAVEFORMAT was the structure used in // XMA version 1 files. XMA2WAVEFORMAT was used in early XMA2 files; it is not // placed in the usual 'fmt' RIFF chunk but in its own 'XMA2' chunk. #ifndef WAVE_FORMAT_XMA #define WAVE_FORMAT_XMA 0x0165 // Values used in the ChannelMask fields below. Similar to the SPEAKER_xxx // values defined in audiodefs.h, but modified to fit in a single byte. #ifndef XMA_SPEAKER_LEFT #define XMA_SPEAKER_LEFT 0x01 #define XMA_SPEAKER_RIGHT 0x02 #define XMA_SPEAKER_CENTER 0x04 #define XMA_SPEAKER_LFE 0x08 #define XMA_SPEAKER_LEFT_SURROUND 0x10 #define XMA_SPEAKER_RIGHT_SURROUND 0x20 #define XMA_SPEAKER_LEFT_BACK 0x40 #define XMA_SPEAKER_RIGHT_BACK 0x80 #endif // Used in XMAWAVEFORMAT for per-stream data typedef struct XMASTREAMFORMAT { DWORD PsuedoBytesPerSec; // Used by the XMA encoder (typo preserved for legacy reasons) DWORD SampleRate; // The stream's decoded sample rate (in XMA2 files, // this is the same for all streams in the file). DWORD LoopStart; // Bit offset of the frame containing the loop start // point, relative to the beginning of the stream. DWORD LoopEnd; // Bit offset of the frame containing the loop end. BYTE SubframeData; // Two 4-bit numbers specifying the exact location of // the loop points within the frames that contain them. // SubframeEnd: Subframe of the loop end frame where // the loop ends. Ranges from 0 to 3. // SubframeSkip: Subframes to skip in the start frame to // reach the loop. Ranges from 0 to 4. BYTE Channels; // Number of channels in the stream (1 or 2) WORD ChannelMask; // Spatial positions of the channels in the stream } XMASTREAMFORMAT; // Legacy XMA1 format structure typedef struct XMAWAVEFORMAT { WORD FormatTag; // Audio format type (always WAVE_FORMAT_XMA) WORD BitsPerSample; // Bit depth (currently required to be 16) WORD EncodeOptions; // Options for XMA encoder/decoder WORD LargestSkip; // Largest skip used in interleaving streams WORD NumStreams; // Number of interleaved audio streams BYTE LoopCount; // Number of loop repetitions; 255 = infinite BYTE Version; // XMA encoder version that generated the file. // Always 3 or higher for XMA2 files. XMASTREAMFORMAT XmaStreams[1]; // Per-stream format information; the actual // array length is in the NumStreams field. } XMAWAVEFORMAT; // Used in XMA2WAVEFORMAT for per-stream data typedef struct XMA2STREAMFORMAT { BYTE Channels; // Number of channels in the stream (1 or 2) BYTE RESERVED; // Reserved for future use WORD ChannelMask; // Spatial positions of the channels in the stream } XMA2STREAMFORMAT; // Legacy XMA2 format structure (big-endian byte ordering) typedef struct XMA2WAVEFORMAT { BYTE Version; // XMA encoder version that generated the file. // Always 3 or higher for XMA2 files. BYTE NumStreams; // Number of interleaved audio streams BYTE RESERVED; // Reserved for future use BYTE LoopCount; // Number of loop repetitions; 255 = infinite DWORD LoopBegin; // Loop begin point, in samples DWORD LoopEnd; // Loop end point, in samples DWORD SampleRate; // The file's decoded sample rate DWORD EncodeOptions; // Options for the XMA encoder/decoder DWORD PsuedoBytesPerSec; // Used internally by the XMA encoder DWORD BlockSizeInBytes; // Size in bytes of this file's XMA blocks (except // possibly the last one). Always a multiple of // 2Kb, since XMA blocks are arrays of 2Kb packets. DWORD SamplesEncoded; // Total number of PCM samples encoded in this file DWORD SamplesInSource; // Actual number of PCM samples in the source // material used to generate this file DWORD BlockCount; // Number of XMA blocks in this file (and hence // also the number of entries in its seek table) XMA2STREAMFORMAT Streams[1]; // Per-stream format information; the actual // array length is in the NumStreams field. } XMA2WAVEFORMAT; #endif // #ifndef WAVE_FORMAT_XMA /*************************************************************************** * XMA packet structure (in big-endian form) ***************************************************************************/ typedef struct XMA2PACKET { int FrameCount : 6; // Number of XMA frames that begin in this packet int FrameOffsetInBits : 15; // Bit of XmaData where the first complete frame begins int PacketMetaData : 3; // Metadata stored in the packet (always 1 for XMA2) int PacketSkipCount : 8; // How many packets belonging to other streams must be // skipped to find the next packet belonging to this one BYTE XmaData[XMA_BYTES_PER_PACKET - sizeof(DWORD)]; // XMA encoded data } XMA2PACKET; // E.g. if the first DWORD of a packet is 0x30107902: // // 001100 000001000001111 001 00000010 // | | | |____ Skip 2 packets to find the next one for this stream // | | |___________ XMA2 signature (always 001) // | |_____________________ First frame starts 527 bits into packet // |________________________________ Packet contains 12 frames // Helper functions to extract the fields above from an XMA packet. (Note that // the bitfields cannot be read directly on little-endian architectures such as // the Intel x86, as they are laid out in big-endian form.) __inline DWORD GetXmaPacketFrameCount(__in_bcount(1) const BYTE* pPacket) { return (DWORD)(pPacket[0] >> 2); } __inline DWORD GetXmaPacketFirstFrameOffsetInBits(__in_bcount(3) const BYTE* pPacket) { return ((DWORD)(pPacket[0] & 0x3) << 13) | ((DWORD)(pPacket[1]) << 5) | ((DWORD)(pPacket[2]) >> 3); } __inline DWORD GetXmaPacketMetadata(__in_bcount(3) const BYTE* pPacket) { return (DWORD)(pPacket[2] & 0x7); } __inline DWORD GetXmaPacketSkipCount(__in_bcount(4) const BYTE* pPacket) { return (DWORD)(pPacket[3]); } /*************************************************************************** * XMA frame structure ***************************************************************************/ // There is no way to represent the XMA frame as a C struct, since it is a // variable-sized string of bits that need not be stored at a byte-aligned // position in memory. This is the layout: // // XMAFRAME // { // LengthInBits: A 15-bit number representing the length of this frame. // XmaData: Encoded XMA data; its size in bits is (LengthInBits - 15). // } // Size in bits of the frame's initial LengthInBits field #define XMA_BITS_IN_FRAME_LENGTH_FIELD 15 // Special LengthInBits value that marks an invalid final frame #define XMA_FINAL_FRAME_MARKER 0x7FFF /*************************************************************************** * XMA helper functions ***************************************************************************/ // We define a local ASSERT macro to equal the global one if it exists. // You can define XMA2DEFS_ASSERT in advance to override this default. #ifndef XMA2DEFS_ASSERT #ifdef ASSERT #define XMA2DEFS_ASSERT ASSERT #else #define XMA2DEFS_ASSERT(a) /* No-op by default */ #endif #endif // GetXmaBlockContainingSample: Use a given seek table to find the XMA block // containing a given decoded sample. Note that the seek table entries in an // XMA file are stored in big-endian form and may need to be converted prior // to calling this function. __inline HRESULT GetXmaBlockContainingSample ( DWORD nBlockCount, // Blocks in the file (= seek table entries) __in_ecount(nBlockCount) const DWORD* pSeekTable, // Pointer to the seek table data DWORD nDesiredSample, // Decoded sample to locate __out DWORD* pnBlockContainingSample, // Index of the block containing the sample __out DWORD* pnSampleOffsetWithinBlock // Position of the sample in this block ) { DWORD nPreviousTotalSamples = 0; DWORD nBlock; DWORD nTotalSamplesSoFar; XMA2DEFS_ASSERT(pSeekTable); XMA2DEFS_ASSERT(pnBlockContainingSample); XMA2DEFS_ASSERT(pnSampleOffsetWithinBlock); for (nBlock = 0; nBlock < nBlockCount; ++nBlock) { nTotalSamplesSoFar = pSeekTable[nBlock]; if (nTotalSamplesSoFar > nDesiredSample) { *pnBlockContainingSample = nBlock; *pnSampleOffsetWithinBlock = nDesiredSample - nPreviousTotalSamples; return S_OK; } nPreviousTotalSamples = nTotalSamplesSoFar; } return E_FAIL; } // GetXmaFrameLengthInBits: Reads a given frame's LengthInBits field. __inline DWORD GetXmaFrameLengthInBits ( __in_bcount(nBitPosition / 8 + 3) __in const BYTE* pPacket, // Pointer to XMA packet[s] containing the frame DWORD nBitPosition // Bit offset of the frame within this packet ) { DWORD nRegion; DWORD nBytePosition = nBitPosition / 8; DWORD nBitOffset = nBitPosition % 8; if (nBitOffset < 2) // Only need to read 2 bytes (and might not be safe to read more) { nRegion = (DWORD)(pPacket[nBytePosition+0]) << 8 | (DWORD)(pPacket[nBytePosition+1]); return (nRegion >> (1 - nBitOffset)) & 0x7FFF; // Last 15 bits } else // Need to read 3 bytes { nRegion = (DWORD)(pPacket[nBytePosition+0]) << 16 | (DWORD)(pPacket[nBytePosition+1]) << 8 | (DWORD)(pPacket[nBytePosition+2]); return (nRegion >> (9 - nBitOffset)) & 0x7FFF; // Last 15 bits } } // GetXmaFrameBitPosition: Calculates the bit offset of a given frame within // an XMA block or set of blocks. Returns 0 on failure. __inline DWORD GetXmaFrameBitPosition ( __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] DWORD nXmaDataBytes, // Size of pXmaData in bytes DWORD nStreamIndex, // Stream within which to seek DWORD nDesiredFrame // Frame sought ) { const BYTE* pCurrentPacket; DWORD nPacketsExamined = 0; DWORD nFrameCountSoFar = 0; DWORD nFramesToSkip; DWORD nFrameBitOffset; XMA2DEFS_ASSERT(pXmaData); XMA2DEFS_ASSERT(nXmaDataBytes % XMA_BYTES_PER_PACKET == 0); // Get the first XMA packet belonging to the desired stream, relying on the // fact that the first packets for each stream are in consecutive order at // the beginning of an XMA block. pCurrentPacket = pXmaData + nStreamIndex * XMA_BYTES_PER_PACKET; for (;;) { // If we have exceeded the size of the XMA data, return failure if (pCurrentPacket + XMA_BYTES_PER_PACKET > pXmaData + nXmaDataBytes) { return 0; } // If the current packet contains the frame we are looking for... if (nFrameCountSoFar + GetXmaPacketFrameCount(pCurrentPacket) > nDesiredFrame) { // See how many frames in this packet we need to skip to get to it XMA2DEFS_ASSERT(nDesiredFrame >= nFrameCountSoFar); nFramesToSkip = nDesiredFrame - nFrameCountSoFar; // Get the bit offset of the first frame in this packet nFrameBitOffset = XMA_PACKET_HEADER_BITS + GetXmaPacketFirstFrameOffsetInBits(pCurrentPacket); // Advance nFrameBitOffset to the frame of interest while (nFramesToSkip--) { nFrameBitOffset += GetXmaFrameLengthInBits(pCurrentPacket, nFrameBitOffset); } // The bit offset to return is the number of bits from pXmaData to // pCurrentPacket plus the bit offset of the frame of interest return (DWORD)(pCurrentPacket - pXmaData) * 8 + nFrameBitOffset; } // If we haven't found the right packet yet, advance our counters ++nPacketsExamined; nFrameCountSoFar += GetXmaPacketFrameCount(pCurrentPacket); // And skip to the next packet belonging to the same stream pCurrentPacket += XMA_BYTES_PER_PACKET * (GetXmaPacketSkipCount(pCurrentPacket) + 1); } } // GetLastXmaFrameBitPosition: Calculates the bit offset of the last complete // frame in an XMA block or set of blocks. __inline DWORD GetLastXmaFrameBitPosition ( __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] DWORD nXmaDataBytes, // Size of pXmaData in bytes DWORD nStreamIndex // Stream within which to seek ) { const BYTE* pLastPacket; DWORD nBytesToNextPacket; DWORD nFrameBitOffset; DWORD nFramesInLastPacket; XMA2DEFS_ASSERT(pXmaData); XMA2DEFS_ASSERT(nXmaDataBytes % XMA_BYTES_PER_PACKET == 0); XMA2DEFS_ASSERT(nXmaDataBytes >= XMA_BYTES_PER_PACKET * (nStreamIndex + 1)); // Get the first XMA packet belonging to the desired stream, relying on the // fact that the first packets for each stream are in consecutive order at // the beginning of an XMA block. pLastPacket = pXmaData + nStreamIndex * XMA_BYTES_PER_PACKET; // Search for the last packet belonging to the desired stream for (;;) { nBytesToNextPacket = XMA_BYTES_PER_PACKET * (GetXmaPacketSkipCount(pLastPacket) + 1); XMA2DEFS_ASSERT(nBytesToNextPacket); if (pLastPacket + nBytesToNextPacket + XMA_BYTES_PER_PACKET > pXmaData + nXmaDataBytes) { break; // The next packet would extend beyond the end of pXmaData } pLastPacket += nBytesToNextPacket; } // The last packet can sometimes have no seekable frames, in which case we // have to use the previous one if (GetXmaPacketFrameCount(pLastPacket) == 0) { pLastPacket -= nBytesToNextPacket; } // Found the last packet. Get the bit offset of its first frame. nFrameBitOffset = XMA_PACKET_HEADER_BITS + GetXmaPacketFirstFrameOffsetInBits(pLastPacket); // Traverse frames until we reach the last one nFramesInLastPacket = GetXmaPacketFrameCount(pLastPacket); while (--nFramesInLastPacket) { nFrameBitOffset += GetXmaFrameLengthInBits(pLastPacket, nFrameBitOffset); } // The bit offset to return is the number of bits from pXmaData to // pLastPacket plus the offset of the last frame in this packet. return (DWORD)(pLastPacket - pXmaData) * 8 + nFrameBitOffset; } // GetXmaDecodePositionForSample: Obtains the information needed to make the // decoder generate audio starting at a given sample position relative to the // beginning of the given XMA block: the bit offset of the appropriate frame, // and the right subframe within that frame. This data can be passed directly // to the XMAPlaybackSetDecodePosition function. __inline HRESULT GetXmaDecodePositionForSample ( __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] DWORD nXmaDataBytes, // Size of pXmaData in bytes DWORD nStreamIndex, // Stream within which to seek DWORD nDesiredSample, // Sample sought __out DWORD* pnBitOffset, // Returns the bit offset within pXmaData of // the frame containing the sample sought __out DWORD* pnSubFrame // Returns the subframe containing the sample ) { DWORD nDesiredFrame = nDesiredSample / XMA_SAMPLES_PER_FRAME; DWORD nSubFrame = (nDesiredSample % XMA_SAMPLES_PER_FRAME) / XMA_SAMPLES_PER_SUBFRAME; DWORD nBitOffset = GetXmaFrameBitPosition(pXmaData, nXmaDataBytes, nStreamIndex, nDesiredFrame); XMA2DEFS_ASSERT(pnBitOffset); XMA2DEFS_ASSERT(pnSubFrame); if (nBitOffset) { *pnBitOffset = nBitOffset; *pnSubFrame = nSubFrame; return S_OK; } else { return E_FAIL; } } // GetXmaSampleRate: Obtains the legal XMA sample rate (24, 32, 44.1 or 48Khz) // corresponding to a generic sample rate. __inline DWORD GetXmaSampleRate(DWORD dwGeneralRate) { DWORD dwXmaRate = 48000; // Default XMA rate for all rates above 44100Hz if (dwGeneralRate <= 24000) dwXmaRate = 24000; else if (dwGeneralRate <= 32000) dwXmaRate = 32000; else if (dwGeneralRate <= 44100) dwXmaRate = 44100; return dwXmaRate; } // Functions to convert between WAVEFORMATEXTENSIBLE channel masks (combinations // of the SPEAKER_xxx flags defined in audiodefs.h) and XMA channel masks (which // are limited to eight possible speaker positions: left, right, center, low // frequency, side left, side right, back left and back right). __inline DWORD GetStandardChannelMaskFromXmaMask(BYTE bXmaMask) { DWORD dwStandardMask = 0; if (bXmaMask & XMA_SPEAKER_LEFT) dwStandardMask |= SPEAKER_FRONT_LEFT; if (bXmaMask & XMA_SPEAKER_RIGHT) dwStandardMask |= SPEAKER_FRONT_RIGHT; if (bXmaMask & XMA_SPEAKER_CENTER) dwStandardMask |= SPEAKER_FRONT_CENTER; if (bXmaMask & XMA_SPEAKER_LFE) dwStandardMask |= SPEAKER_LOW_FREQUENCY; if (bXmaMask & XMA_SPEAKER_LEFT_SURROUND) dwStandardMask |= SPEAKER_SIDE_LEFT; if (bXmaMask & XMA_SPEAKER_RIGHT_SURROUND) dwStandardMask |= SPEAKER_SIDE_RIGHT; if (bXmaMask & XMA_SPEAKER_LEFT_BACK) dwStandardMask |= SPEAKER_BACK_LEFT; if (bXmaMask & XMA_SPEAKER_RIGHT_BACK) dwStandardMask |= SPEAKER_BACK_RIGHT; return dwStandardMask; } __inline BYTE GetXmaChannelMaskFromStandardMask(DWORD dwStandardMask) { BYTE bXmaMask = 0; if (dwStandardMask & SPEAKER_FRONT_LEFT) bXmaMask |= XMA_SPEAKER_LEFT; if (dwStandardMask & SPEAKER_FRONT_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT; if (dwStandardMask & SPEAKER_FRONT_CENTER) bXmaMask |= XMA_SPEAKER_CENTER; if (dwStandardMask & SPEAKER_LOW_FREQUENCY) bXmaMask |= XMA_SPEAKER_LFE; if (dwStandardMask & SPEAKER_SIDE_LEFT) bXmaMask |= XMA_SPEAKER_LEFT_SURROUND; if (dwStandardMask & SPEAKER_SIDE_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT_SURROUND; if (dwStandardMask & SPEAKER_BACK_LEFT) bXmaMask |= XMA_SPEAKER_LEFT_BACK; if (dwStandardMask & SPEAKER_BACK_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT_BACK; return bXmaMask; } // LocalizeXma2Format: Modifies a XMA2WAVEFORMATEX structure in place to comply // with the current platform's byte-ordering rules (little- or big-endian). __inline HRESULT LocalizeXma2Format(__inout XMA2WAVEFORMATEX* pXma2Format) { #define XMASWAP2BYTES(n) ((WORD)(((n) >> 8) | (((n) & 0xff) << 8))) #define XMASWAP4BYTES(n) ((DWORD)((n) >> 24 | (n) << 24 | ((n) & 0xff00) << 8 | ((n) & 0xff0000) >> 8)) if (pXma2Format->wfx.wFormatTag == WAVE_FORMAT_XMA2) { return S_OK; } else if (XMASWAP2BYTES(pXma2Format->wfx.wFormatTag) == WAVE_FORMAT_XMA2) { pXma2Format->wfx.wFormatTag = XMASWAP2BYTES(pXma2Format->wfx.wFormatTag); pXma2Format->wfx.nChannels = XMASWAP2BYTES(pXma2Format->wfx.nChannels); pXma2Format->wfx.nSamplesPerSec = XMASWAP4BYTES(pXma2Format->wfx.nSamplesPerSec); pXma2Format->wfx.nAvgBytesPerSec = XMASWAP4BYTES(pXma2Format->wfx.nAvgBytesPerSec); pXma2Format->wfx.nBlockAlign = XMASWAP2BYTES(pXma2Format->wfx.nBlockAlign); pXma2Format->wfx.wBitsPerSample = XMASWAP2BYTES(pXma2Format->wfx.wBitsPerSample); pXma2Format->wfx.cbSize = XMASWAP2BYTES(pXma2Format->wfx.cbSize); pXma2Format->NumStreams = XMASWAP2BYTES(pXma2Format->NumStreams); pXma2Format->ChannelMask = XMASWAP4BYTES(pXma2Format->ChannelMask); pXma2Format->SamplesEncoded = XMASWAP4BYTES(pXma2Format->SamplesEncoded); pXma2Format->BytesPerBlock = XMASWAP4BYTES(pXma2Format->BytesPerBlock); pXma2Format->PlayBegin = XMASWAP4BYTES(pXma2Format->PlayBegin); pXma2Format->PlayLength = XMASWAP4BYTES(pXma2Format->PlayLength); pXma2Format->LoopBegin = XMASWAP4BYTES(pXma2Format->LoopBegin); pXma2Format->LoopLength = XMASWAP4BYTES(pXma2Format->LoopLength); pXma2Format->BlockCount = XMASWAP2BYTES(pXma2Format->BlockCount); return S_OK; } else { return E_FAIL; // Not a recognizable XMA2 format } #undef XMASWAP2BYTES #undef XMASWAP4BYTES } #endif // #ifndef __XMA2DEFS_INCLUDED__ ================================================ FILE: src/config/ActiveSettings.cpp ================================================ #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/CafeSystem.h" #include "Cemu/Logging/CemuLogging.h" #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" #include "util/helpers/helpers.h" #include "Cafe/HW/Latte/Core/Latte.h" void ActiveSettings::SetPaths(bool isPortableMode, const fs::path& executablePath, const fs::path& userDataPath, const fs::path& configPath, const fs::path& cachePath, const fs::path& dataPath, std::set& failedWriteAccess) { cemu_assert_debug(!s_setPathsCalled); // can only change paths before loading s_isPortableMode = isPortableMode; s_executable_path = executablePath; s_user_data_path = userDataPath; s_config_path = configPath; s_cache_path = cachePath; s_data_path = dataPath; failedWriteAccess.clear(); for (auto&& path : {userDataPath, configPath, cachePath}) { std::error_code ec; if (!fs::exists(path, ec)) fs::create_directories(path, ec); if (!TestWriteAccess(path)) { cemuLog_log(LogType::Force, "Failed to write to {}", _pathToUtf8(path)); failedWriteAccess.insert(path); } } s_executable_filename = s_executable_path.filename(); s_setPathsCalled = true; } [[nodiscard]] bool ActiveSettings::IsPortableMode() { return s_isPortableMode; } void ActiveSettings::Init() { cemu_assert_debug(s_setPathsCalled); std::string additionalErrorInfo; s_has_required_online_files = iosuCrypt_checkRequirementsForOnlineMode(additionalErrorInfo) == IOS_CRYPTO_ONLINE_REQ_OK; } bool ActiveSettings::LoadSharedLibrariesEnabled() { return g_current_game_profile->ShouldLoadSharedLibraries().value_or(true); } bool ActiveSettings::DisplayDRCEnabled() { return g_current_game_profile->StartWithGamepadView(); } CPUMode ActiveSettings::GetCPUMode() { auto mode = g_current_game_profile->GetCPUMode().value_or(CPUMode::Auto); if (mode == CPUMode::Auto) { if (GetPhysicalCoreCount() >= 4) mode = CPUMode::MulticoreRecompiler; else mode = CPUMode::SinglecoreRecompiler; } else if (mode == CPUMode::DualcoreRecompiler) // dualcore is disabled now mode = CPUMode::MulticoreRecompiler; return mode; } uint8 ActiveSettings::GetTimerShiftFactor() { return s_timer_shift; } void ActiveSettings::SetTimerShiftFactor(uint8 shiftFactor) { s_timer_shift = shiftFactor; } PrecompiledShaderOption ActiveSettings::GetPrecompiledShadersOption() { return PrecompiledShaderOption::Auto; // g_current_game_profile->GetPrecompiledShadersState().value_or(GetConfig().precompiled_shaders); } bool ActiveSettings::RenderUpsideDownEnabled() { return LaunchSettings::RenderUpsideDownEnabled().value_or(GetConfig().render_upside_down); } bool ActiveSettings::WaitForGX2DrawDoneEnabled() { return GetConfig().gx2drawdone_sync; } GraphicAPI ActiveSettings::GetGraphicsAPI() { GraphicAPI api = g_current_game_profile->GetGraphicsAPI().value_or(GetConfig().graphic_api); // check if vulkan even available if (api == kVulkan && !g_vulkan_available) api = kOpenGL; return api; } float ActiveSettings::GetTVGamma() { const auto& config = GetConfig(); return config.overrideGammaValue.GetValue() + LatteGPUState.tvGamma * !config.overrideAppGammaPreference.GetValue(); } float ActiveSettings::GetDRCGamma() { const auto& config = GetConfig(); return config.overrideGammaValue.GetValue() + LatteGPUState.drcGamma * !config.overrideAppGammaPreference.GetValue(); } bool ActiveSettings::AudioOutputOnlyAux() { return s_audio_aux_only; } void ActiveSettings::EnableAudioOnlyAux(bool state) { s_audio_aux_only = state; } uint32 ActiveSettings::GetPersistentId() { return LaunchSettings::GetPersistentId().value_or(GetConfig().account.m_persistent_id); } bool ActiveSettings::IsOnlineEnabled() { if(!Account::GetAccount(GetPersistentId()).IsValidOnlineAccount()) return false; if(!HasRequiredOnlineFiles()) return false; NetworkService networkService = static_cast(GetConfig().GetAccountNetworkService(GetPersistentId())); return networkService == NetworkService::Nintendo || networkService == NetworkService::Pretendo || networkService == NetworkService::Custom; } bool ActiveSettings::HasRequiredOnlineFiles() { return s_has_required_online_files; } NetworkService ActiveSettings::GetNetworkService() { return GetConfig().GetAccountNetworkService(GetPersistentId()); } bool ActiveSettings::DumpShadersEnabled() { return s_dump_shaders; } bool ActiveSettings::DumpTexturesEnabled() { return s_dump_textures; } bool ActiveSettings::DumpRecompilerFunctionsEnabled() { return s_dump_recompiler_functions; } bool ActiveSettings::DumpLibcurlRequestsEnabled() { return s_dump_libcurl_requests; } void ActiveSettings::EnableDumpShaders(bool state) { s_dump_shaders = state; } void ActiveSettings::EnableDumpTextures(bool state) { s_dump_textures = state; } void ActiveSettings::EnableDumpRecompilerFunctions(bool state) { s_dump_recompiler_functions = state; } void ActiveSettings::EnableDumpLibcurlRequests(bool state) { s_dump_libcurl_requests = state; } bool ActiveSettings::VPADDelayEnabled() { const uint64 titleId = CafeSystem::GetForegroundTitleId(); // workaround for Art Academy spamming VPADRead return /* Art Academy: Home Studio (US) */ titleId == 0x000500001017BF00 || /* Art Academy: Home Studio (JP) */ titleId == 0x000500001017BE00 || /* Art Academy: Atelier (EU) */ titleId == 0x000500001017B500; } bool ActiveSettings::ShaderPreventInfiniteLoopsEnabled() { const uint64 titleId = CafeSystem::GetForegroundTitleId(); // workaround for NSMBU (and variants) having a bug where shaders can get stuck in infinite loops // Fatal Frame has an actual infinite loop in shader 0xb6a67c19f6472e00 encountered during a cutscene for the second drop (eShop version only?) // update: As of Cemu 1.20.0 this should no longer be required for NSMBU/NSLU due to fixes with uniform handling. But we leave it here for good measure // todo - Once we add support for loop config registers this workaround should become unnecessary return /* NSMBU JP */ titleId == 0x0005000010101C00 || /* NSMBU US */ titleId == 0x0005000010101D00 || /* NSMBU EU */ titleId == 0x0005000010101E00 || /* NSMBU+L US */ titleId == 0x000500001014B700 || /* NSMBU+L EU */ titleId == 0x000500001014B800 || /* NSLU US */ titleId == 0x0005000010142300 || /* NSLU EU */ titleId == 0x0005000010142400 || /* Project Zero: Maiden of Black Water (EU) */ titleId == 0x00050000101D0300 || /* Fatal Frame: Maiden of Black Water (US) */ titleId == 0x00050000101D0600 || /* Project Zero: Maiden of Black Water (JP) */ titleId == 0x000500001014D200 || /* Project Zero: Maiden of Black Water (Trial, EU) */ titleId == 0x00050000101D3F00; } bool ActiveSettings::FlushGPUCacheOnSwap() { const uint64 titleId = CafeSystem::GetForegroundTitleId(); // games that require flushing the cache between frames return /* PAC-MAN and the Ghostly Adventures (EU) */ titleId == 0x0005000010147900 || /* PAC-MAN and the Ghostly Adventures (US) */ titleId == 0x0005000010146300 || /* PAC-MAN and the Ghostly Adventures 2 (EU) */ titleId == 0x000500001017E500 || /* PAC-MAN and the Ghostly Adventures 2 (US) */ titleId == 0x000500001017E600; } bool ActiveSettings::ForceSamplerRoundToPrecision() { // some Wayforward games (Duck Tales, Adventure Time Explore The Dungeon) sample textures exactly on the texel edge. On Wii U this is fine because the rounding mode can be controlled // on OpenGL/Vulkan when uv coordinates are converted from (un)normalized to integer the implementation always truncates which causes an off-by-one error in edge cases // In the future we should look into optionally correctly emulating sampler behavior (its quite complex, Latte supports flexible precision levels) const uint64 titleId = CafeSystem::GetForegroundTitleId(); return /* Adventure Time ETDBIDK (EU) */ titleId == 0x000500001014E100 || /* Adventure Time ETDBIDK (US) */ titleId == 0x0005000010144000 || /* DuckTales Remastered (EU) */ titleId == 0x0005000010129200 || /* DuckTales Remastered (US) */ titleId == 0x0005000010129000; return false; } fs::path ActiveSettings::GetMlcPath() { cemu_assert_debug(s_setPathsCalled); if(const auto launch_mlc = LaunchSettings::GetMLCPath(); launch_mlc.has_value()) return launch_mlc.value(); if(const auto config_mlc = GetConfig().mlc_path.GetValue(); !config_mlc.empty()) return _utf8ToPath(config_mlc); return GetDefaultMLCPath(); } bool ActiveSettings::IsCustomMlcPath() { cemu_assert_debug(s_setPathsCalled); return !GetConfig().mlc_path.GetValue().empty(); } bool ActiveSettings::IsCommandLineMlcPath() { return LaunchSettings::GetMLCPath().has_value(); } fs::path ActiveSettings::GetDefaultMLCPath() { return GetUserDataPath("mlc01"); } ================================================ FILE: src/config/ActiveSettings.h ================================================ #pragma once #include #include "config/CemuConfig.h" #include "config/NetworkSettings.h" // global active settings for fast access (reflects settings from command line and game profile) class ActiveSettings { private: template static fs::path GetPath(const fs::path& path, std::string_view format, TArgs&&... args) { cemu_assert_debug(format.empty() || (format[0] != '/' && format[0] != '\\')); std::string tmpPathStr = fmt::format(fmt::runtime(format), std::forward(args)...); return path / _utf8ToPath(tmpPathStr); } template static fs::path GetPath(const fs::path& path, std::wstring_view format, TArgs&&... args) { cemu_assert_debug(format.empty() || (format[0] != L'/' && format[0] != L'\\')); return path / fmt::format(fmt::runtime(format), std::forward(args)...); } static fs::path GetPath(const fs::path& path, std::string_view p) { std::basic_string_view s((const char8_t*)p.data(), p.size()); return path / fs::path(s); } static fs::path GetPath(const fs::path& path) { return path; } public: // Set directories and return all directories that failed write access test static void SetPaths(bool isPortableMode, const fs::path& executablePath, const fs::path& userDataPath, const fs::path& configPath, const fs::path& cachePath, const fs::path& dataPath, std::set& failedWriteAccess); static void Init(); [[nodiscard]] static fs::path GetExecutablePath() { return s_executable_path; } [[nodiscard]] static fs::path GetExecutableFilename() { return s_executable_filename; } template [[nodiscard]] static fs::path GetUserDataPath(TArgs&&... args){ return GetPath(s_user_data_path, std::forward(args)...); }; template [[nodiscard]] static fs::path GetConfigPath(TArgs&&... args){ return GetPath(s_config_path, std::forward(args)...); }; template [[nodiscard]] static fs::path GetCachePath(TArgs&&... args){ return GetPath(s_cache_path, std::forward(args)...); }; template [[nodiscard]] static fs::path GetDataPath(TArgs&&... args){ return GetPath(s_data_path, std::forward(args)...); }; [[nodiscard]] static fs::path GetMlcPath(); template [[nodiscard]] static fs::path GetMlcPath(TArgs&&... args){ return GetPath(GetMlcPath(), std::forward(args)...); }; static bool IsCustomMlcPath(); static bool IsCommandLineMlcPath(); // get mlc path to default cemu root dir/mlc01 [[nodiscard]] static fs::path GetDefaultMLCPath(); private: inline static bool s_isPortableMode{false}; inline static fs::path s_executable_path; inline static fs::path s_user_data_path; inline static fs::path s_config_path; inline static fs::path s_cache_path; inline static fs::path s_data_path; inline static fs::path s_executable_filename; // cemu.exe inline static fs::path s_mlc_path; public: // can be called before Init [[nodiscard]] static bool IsPortableMode(); // general [[nodiscard]] static bool LoadSharedLibrariesEnabled(); [[nodiscard]] static bool DisplayDRCEnabled(); // cpu [[nodiscard]] static CPUMode GetCPUMode(); [[nodiscard]] static uint8 GetTimerShiftFactor(); static void SetTimerShiftFactor(uint8 shiftFactor); // gpu [[nodiscard]] static PrecompiledShaderOption GetPrecompiledShadersOption(); [[nodiscard]] static bool RenderUpsideDownEnabled(); [[nodiscard]] static bool WaitForGX2DrawDoneEnabled(); [[nodiscard]] static GraphicAPI GetGraphicsAPI(); // gamma [[nodiscard]] static float GetTVGamma(); [[nodiscard]] static float GetDRCGamma(); // audio [[nodiscard]] static bool AudioOutputOnlyAux(); static void EnableAudioOnlyAux(bool state); // account [[nodiscard]] static uint32 GetPersistentId(); [[nodiscard]] static bool IsOnlineEnabled(); [[nodiscard]] static bool HasRequiredOnlineFiles(); [[nodiscard]] static NetworkService GetNetworkService(); // dump options [[nodiscard]] static bool DumpShadersEnabled(); [[nodiscard]] static bool DumpTexturesEnabled(); [[nodiscard]] static bool DumpRecompilerFunctionsEnabled(); [[nodiscard]] static bool DumpLibcurlRequestsEnabled(); static void EnableDumpShaders(bool state); static void EnableDumpTextures(bool state); static void EnableDumpRecompilerFunctions(bool state); static void EnableDumpLibcurlRequests(bool state); // hacks [[nodiscard]] static bool VPADDelayEnabled(); [[nodiscard]] static bool ShaderPreventInfiniteLoopsEnabled(); [[nodiscard]] static bool FlushGPUCacheOnSwap(); [[nodiscard]] static bool ForceSamplerRoundToPrecision(); private: inline static bool s_setPathsCalled = false; // dump options inline static bool s_dump_shaders = false; inline static bool s_dump_textures = false; inline static bool s_dump_recompiler_functions = false; inline static bool s_dump_libcurl_requests = false; // timer speed inline static uint8 s_timer_shift = 3; // right shift factor, 0 -> 8x, 3 -> 1x, 4 -> 0.5x // debug inline static bool s_audio_aux_only = false; inline static bool s_has_required_online_files = false; }; ================================================ FILE: src/config/CMakeLists.txt ================================================ add_library(CemuConfig ActiveSettings.cpp ActiveSettings.h CemuConfig.cpp CemuConfig.h ConfigValue.h LaunchSettings.cpp LaunchSettings.h NetworkSettings.cpp NetworkSettings.h XMLConfig.h ) set_property(TARGET CemuConfig PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") target_include_directories(CemuConfig PUBLIC "../") target_link_libraries(CemuConfig PRIVATE CemuCommon CemuGui Boost::program_options ) ================================================ FILE: src/config/CemuConfig.cpp ================================================ #include "config/CemuConfig.h" #include "WindowSystem.h" #include "util/helpers/helpers.h" #include "config/ActiveSettings.h" #include "ActiveSettings.h" void CemuConfig::SetMLCPath(fs::path path, bool save) { mlc_path.SetValue(_pathToUtf8(path)); if(save) GetConfigHandle().Save(); Account::RefreshAccounts(); } XMLConfigParser CemuConfig::Load(XMLConfigParser& parser) { auto new_parser = parser.get("content"); if (new_parser.valid()) parser = new_parser; // general settings log_flag = parser.get("logflag", log_flag.GetInitValue()); cemuLog_setActiveLoggingFlags(GetConfig().log_flag.GetValue()); advanced_ppc_logging = parser.get("advanced_ppc_logging", advanced_ppc_logging.GetInitValue()); const char* mlc = parser.get("mlc_path", ""); mlc_path = mlc; permanent_storage = parser.get("permanent_storage", permanent_storage); proxy_server = parser.get("proxy_server", ""); disable_screensaver = parser.get("disable_screensaver", disable_screensaver); play_boot_sound = parser.get("play_boot_sound", play_boot_sound); console_language = parser.get("console_language", console_language.GetInitValue()); game_paths.clear(); auto game_path_parser = parser.get("GamePaths"); for (auto element = game_path_parser.get("Entry"); element.valid(); element = game_path_parser.get("Entry", element)) { const std::string path = element.value(""); if (path.empty()) continue; try { game_paths.emplace_back(path); } catch (const std::exception&) { cemuLog_log(LogType::Force, "config load error: can't load game path: {}", path); } } std::unique_lock _lock(game_cache_entries_mutex); game_cache_entries.clear(); auto game_cache_parser = parser.get("GameCache"); for (auto element = game_cache_parser.get("Entry"); element.valid(); element = game_cache_parser.get("Entry", element)) { const char* rpx = element.get("path", ""); try { GameEntry entry{}; entry.rpx_file = boost::nowide::widen(rpx); entry.title_id = element.get("title_id"); entry.legacy_name = boost::nowide::widen(element.get("name", "")); entry.custom_name = element.get("custom_name", ""); entry.legacy_region = element.get("region", 0); entry.legacy_version = element.get("version", 0); entry.legacy_update_version = element.get("version", 0); entry.legacy_dlc_version = element.get("dlc_version", 0); entry.legacy_time_played = element.get("time_played"); entry.legacy_last_played = element.get("last_played"); entry.favorite = element.get("favorite", false); game_cache_entries.emplace_back(entry); if (entry.title_id != 0) { if (entry.favorite) game_cache_favorites.emplace(entry.title_id); else game_cache_favorites.erase(entry.title_id); } } catch (const std::exception&) { cemuLog_log(LogType::Force, "config load error: can't load game cache entry: {}", rpx); } } _lock.unlock(); graphic_pack_entries.clear(); auto graphic_pack_parser = parser.get("GraphicPack"); for (auto element = graphic_pack_parser.get("Entry"); element.valid(); element = graphic_pack_parser.get("Entry", element)) { std::string filename = element.get_attribute("filename", ""); if(filename.empty()) // legacy loading { filename = element.get("filename", ""); fs::path path = fs::path(filename).lexically_normal(); graphic_pack_entries.try_emplace(path); const std::string category = element.get("category", ""); const std::string preset = element.get("preset", ""); graphic_pack_entries[filename].try_emplace(category, preset); } else { fs::path path = fs::path(filename).lexically_normal(); graphic_pack_entries.try_emplace(path); const bool disabled = element.get_attribute("disabled", false); if (disabled) { graphic_pack_entries[path].try_emplace("_disabled", "true"); } for (auto preset = element.get("Preset"); preset.valid(); preset = element.get("Preset", preset)) { const std::string category = preset.get("category", ""); const std::string active_preset = preset.get("preset", ""); graphic_pack_entries[path].try_emplace(category, active_preset); } } } // graphics auto graphic = parser.get("Graphic"); graphic_api = graphic.get("api", kOpenGL); graphic.get("device", legacy_graphic_device_uuid); if (graphic.get("vkDevice").valid()) graphic.get("vkDevice", vk_graphic_device_uuid); else vk_graphic_device_uuid = legacy_graphic_device_uuid; mtl_graphic_device_uuid = graphic.get("mtlDevice", 0); vsync = graphic.get("VSync", 0); overrideAppGammaPreference = graphic.get("OverrideAppGammaPreference", false); overrideGammaValue = graphic.get("OverrideGammaValue", 2.2f); if(overrideGammaValue < 0) overrideGammaValue = 2.2f; userDisplayGamma = graphic.get("UserDisplayGamma", 2.2f); if(userDisplayGamma < 0) userDisplayGamma = 2.2f; gx2drawdone_sync = graphic.get("GX2DrawdoneSync", true); upscale_filter = graphic.get("UpscaleFilter", kBicubicHermiteFilter); downscale_filter = graphic.get("DownscaleFilter", kLinearFilter); fullscreen_scaling = graphic.get("FullscreenScaling", kKeepAspectRatio); async_compile = graphic.get("AsyncCompile", async_compile); vk_accurate_barriers = graphic.get("vkAccurateBarriers", true); // this used to be "VulkanAccurateBarriers" but because we changed the default to true in 1.27.1 the option name had to be changed #if ENABLE_METAL force_mesh_shaders = graphic.get("ForceMeshShaders", false); #endif auto overlay_node = graphic.get("Overlay"); if(overlay_node.valid()) { overlay.position = overlay_node.get("Position", ScreenPosition::kDisabled); overlay.text_color = overlay_node.get("TextColor", 0xFFFFFFFF); overlay.text_scale = overlay_node.get("TextScale", 100); overlay.fps = overlay_node.get("FPS", true); overlay.drawcalls = overlay_node.get("DrawCalls", false); overlay.cpu_usage = overlay_node.get("CPUUsage", false); overlay.cpu_per_core_usage = overlay_node.get("CPUPerCoreUsage", false); overlay.ram_usage = overlay_node.get("RAMUsage", false); overlay.vram_usage = overlay_node.get("VRAMUsage", false); overlay.debug = overlay_node.get("Debug", false); notification.controller_profiles = overlay_node.get("ControllerProfiles", true); notification.controller_battery = overlay_node.get("ControllerBattery", true); notification.shader_compiling = overlay_node.get("ShaderCompiling", true); } else { // legacy support overlay.position = graphic.get("OverlayPosition", ScreenPosition::kDisabled); overlay.text_color = graphic.get("OverlayTextColor", 0xFFFFFFFF); overlay.fps = graphic.get("OverlayFPS", true); overlay.drawcalls = graphic.get("OverlayDrawCalls", false); overlay.cpu_usage = graphic.get("OverlayCPUUsage", false); overlay.cpu_per_core_usage = graphic.get("OverlayCPUPerCoreUsage", false); overlay.ram_usage = graphic.get("OverlayRAMUsage", false); notification.controller_profiles = graphic.get("OverlayControllerProfiles", true); notification.controller_battery = graphic.get("OverlayControllerBattery", true); notification.shader_compiling = graphic.get("ShaderCompiling", true); } auto notification_node = graphic.get("Notification"); if (notification_node.valid()) { notification.position = notification_node.get("Position", ScreenPosition::kTopLeft); notification.text_color = notification_node.get("TextColor", 0xFFFFFFFF); notification.text_scale = notification_node.get("TextScale", 100); notification.controller_profiles = notification_node.get("ControllerProfiles", true); notification.controller_battery = notification_node.get("ControllerBattery", false); notification.shader_compiling = notification_node.get("ShaderCompiling", true); notification.friends = notification_node.get("FriendService", true); } // audio auto audio = parser.get("Audio"); audio_api = audio.get("api", 0); audio_delay = audio.get("delay", 2); tv_channels = audio.get("TVChannels", kStereo); pad_channels = audio.get("PadChannels", kStereo); input_channels = audio.get("InputChannels", kMono); tv_volume = audio.get("TVVolume", 20); pad_volume = audio.get("PadVolume", 0); input_volume = audio.get("InputVolume", 20); portal_volume = audio.get("PortalVolume", 20); const auto tv = audio.get("TVDevice", ""); try { tv_device = boost::nowide::widen(tv); } catch (const std::exception&) { cemuLog_log(LogType::Force, "config load error: can't load tv device: {}", tv); } const auto pad = audio.get("PadDevice", ""); try { pad_device = boost::nowide::widen(pad); } catch (const std::exception&) { cemuLog_log(LogType::Force, "config load error: can't load pad device: {}", pad); } const auto input_device_name = audio.get("InputDevice", ""); try { input_device = boost::nowide::widen(input_device_name); } catch (const std::exception&) { cemuLog_log(LogType::Force, "config load error: can't load input device: {}", input_device_name); } const auto portal_device_name = audio.get("PortalDevice", ""); try { portal_device = boost::nowide::widen(portal_device_name); } catch (const std::exception&) { cemuLog_log(LogType::Force, "config load error: can't load input device: {}", portal_device_name); } // account auto acc = parser.get("Account"); account.m_persistent_id = acc.get("PersistentId", account.m_persistent_id); // legacy online settings, we only parse these for upgrading purposes account.legacy_online_enabled = acc.get("OnlineEnabled", account.legacy_online_enabled); account.legacy_active_service = acc.get("ActiveService",account.legacy_active_service); // per-account online setting auto accService = parser.get("AccountService"); account.service_select.clear(); for (auto element = accService.get("SelectedService"); element.valid(); element = accService.get("SelectedService", element)) { uint32 persistentId = element.get_attribute("PersistentId", 0); sint32 serviceIndex = element.get_attribute("Service", 0); NetworkService networkService = static_cast(serviceIndex); if (persistentId < Account::kMinPersistendId) continue; if(networkService == NetworkService::Offline || networkService == NetworkService::Nintendo || networkService == NetworkService::Pretendo || networkService == NetworkService::Custom) account.service_select.emplace(persistentId, networkService); } // debug auto debug = parser.get("Debug"); #if BOOST_OS_WINDOWS crash_dump = debug.get("CrashDumpWindows", crash_dump); #elif BOOST_OS_UNIX crash_dump = debug.get("CrashDumpUnix", crash_dump); #endif gdb_port = debug.get("GDBPort", 1337); #if ENABLE_METAL gpu_capture_dir = debug.get("GPUCaptureDir", ""); framebuffer_fetch = debug.get("FramebufferFetch", true); #endif // input auto input = parser.get("Input"); auto dsuc = input.get("DSUC"); dsu_client.host = dsuc.get_attribute("host", dsu_client.host); dsu_client.port = dsuc.get_attribute("port", dsu_client.port); // emulatedusbdevices auto usbdevices = parser.get("EmulatedUsbDevices"); emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal); emulated_usb_devices.emulate_infinity_base = usbdevices.get("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base); emulated_usb_devices.emulate_dimensions_toypad = usbdevices.get("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad); return parser; } XMLConfigParser CemuConfig::Save(XMLConfigParser& parser) { auto config = parser.set("content"); // general settings config.set("logflag", log_flag.GetValue()); config.set("advanced_ppc_logging", advanced_ppc_logging.GetValue()); config.set("mlc_path", mlc_path.GetValue().c_str()); config.set("permanent_storage", permanent_storage); config.set("proxy_server", proxy_server.GetValue().c_str()); config.set("play_boot_sound", play_boot_sound); // config.set("cpu_mode", cpu_mode.GetValue()); //config.set("console_region", console_region.GetValue()); config.set("console_language", console_language.GetValue()); // game paths auto game_path_parser = config.set("GamePaths"); for (const auto& entry : game_paths) { game_path_parser.set("Entry", entry.c_str()); } // game list cache std::unique_lock _lock(game_cache_entries_mutex); auto game_cache_parser = config.set("GameCache"); for (const auto& game : game_cache_entries) { auto entry = game_cache_parser.set("Entry"); entry.set("title_id", (sint64)game.title_id); entry.set("name", boost::nowide::narrow(game.legacy_name).c_str()); entry.set("custom_name", game.custom_name.c_str()); entry.set("region", (sint32)game.legacy_region); entry.set("version", (sint32)game.legacy_update_version); entry.set("dlc_version", (sint32)game.legacy_dlc_version); entry.set("path", boost::nowide::narrow(game.rpx_file).c_str()); entry.set("time_played", game.legacy_time_played); entry.set("last_played", game.legacy_last_played); entry.set("favorite", game.favorite); } _lock.unlock(); auto graphic_pack_parser = config.set("GraphicPack"); for (const auto& game : graphic_pack_entries) { auto entry = graphic_pack_parser.set("Entry"); entry.set_attribute("filename",_pathToUtf8(game.first).c_str()); for(const auto& kv : game.second) { // TODO: less hacky pls if(boost::iequals(kv.first, "_disabled")) { entry.set_attribute("disabled", true); continue; } auto preset = entry.set("Preset"); if(!kv.first.empty()) preset.set("category", kv.first.c_str()); preset.set("preset", kv.second.c_str()); } } // graphics auto graphic = config.set("Graphic"); graphic.set("api", graphic_api); graphic.set("device", legacy_graphic_device_uuid); graphic.set("vkDevice", vk_graphic_device_uuid); graphic.set("mtlDevice", mtl_graphic_device_uuid); graphic.set("VSync", vsync); graphic.set("OverrideAppGammaPreference", overrideAppGammaPreference); graphic.set("OverrideGammaValue", overrideGammaValue); graphic.set("UserDisplayGamma", userDisplayGamma); graphic.set("GX2DrawdoneSync", gx2drawdone_sync); #if ENABLE_METAL graphic.set("ForceMeshShaders", force_mesh_shaders); #endif //graphic.set("PrecompiledShaders", precompiled_shaders.GetValue()); graphic.set("UpscaleFilter", upscale_filter); graphic.set("DownscaleFilter", downscale_filter); graphic.set("FullscreenScaling", fullscreen_scaling); graphic.set("AsyncCompile", async_compile.GetValue()); graphic.set("vkAccurateBarriers", vk_accurate_barriers); auto overlay_node = graphic.set("Overlay"); overlay_node.set("Position", overlay.position); overlay_node.set("TextColor", overlay.text_color); overlay_node.set("TextScale", overlay.text_scale); overlay_node.set("FPS", overlay.fps); overlay_node.set("DrawCalls", overlay.drawcalls); overlay_node.set("CPUUsage", overlay.cpu_usage); overlay_node.set("CPUPerCoreUsage", overlay.cpu_per_core_usage); overlay_node.set("RAMUsage", overlay.ram_usage); overlay_node.set("VRAMUsage", overlay.vram_usage); overlay_node.set("Debug", overlay.debug); auto notification_node = graphic.set("Notification"); notification_node.set("Position", notification.position); notification_node.set("TextColor", notification.text_color); notification_node.set("TextScale", notification.text_scale); notification_node.set("ControllerProfiles", notification.controller_profiles); notification_node.set("ControllerBattery", notification.controller_battery); notification_node.set("ShaderCompiling", notification.shader_compiling); notification_node.set("FriendService", notification.friends); // audio auto audio = config.set("Audio"); audio.set("api", audio_api); audio.set("delay", audio_delay); audio.set("TVChannels", tv_channels); audio.set("PadChannels", pad_channels); audio.set("InputChannels", input_channels); audio.set("TVVolume", tv_volume); audio.set("PadVolume", pad_volume); audio.set("InputVolume", input_volume); audio.set("PortalVolume", portal_volume); audio.set("TVDevice", boost::nowide::narrow(tv_device).c_str()); audio.set("PadDevice", boost::nowide::narrow(pad_device).c_str()); audio.set("InputDevice", boost::nowide::narrow(input_device).c_str()); audio.set("PortalDevice", boost::nowide::narrow(portal_device).c_str()); // account auto acc = config.set("Account"); acc.set("PersistentId", account.m_persistent_id.GetValue()); // legacy online mode setting acc.set("OnlineEnabled", account.legacy_online_enabled.GetValue()); acc.set("ActiveService",account.legacy_active_service.GetValue()); // per-account online setting auto accService = config.set("AccountService"); for(auto& it : account.service_select) { auto entry = accService.set("SelectedService"); entry.set_attribute("PersistentId", it.first); entry.set_attribute("Service", static_cast(it.second)); } // debug auto debug = config.set("Debug"); #if BOOST_OS_WINDOWS debug.set("CrashDumpWindows", crash_dump.GetValue()); #elif BOOST_OS_UNIX debug.set("CrashDumpUnix", crash_dump.GetValue()); #endif debug.set("GDBPort", gdb_port); #if ENABLE_METAL debug.set("GPUCaptureDir", gpu_capture_dir); debug.set("FramebufferFetch", framebuffer_fetch); #endif // input auto input = config.set("Input"); auto dsuc = input.set("DSUC"); dsuc.set_attribute("host", dsu_client.host); dsuc.set_attribute("port", dsu_client.port); // emulated usb devices auto usbdevices = config.set("EmulatedUsbDevices"); usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue()); usbdevices.set("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base.GetValue()); usbdevices.set("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad.GetValue()); return config; } GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId) { // assumes game_cache_entries_mutex is already held for (auto& it : game_cache_entries) { if (it.title_id == titleId) return ⁢ } return nullptr; } GameEntry* CemuConfig::CreateGameEntry(uint64 titleId) { // assumes game_cache_entries_mutex is already held GameEntry gameEntry; gameEntry.title_id = titleId; game_cache_entries.emplace_back(gameEntry); return &game_cache_entries.back(); } bool CemuConfig::IsGameListFavorite(uint64 titleId) { std::unique_lock _lock(game_cache_entries_mutex); return game_cache_favorites.find(titleId) != game_cache_favorites.end(); } void CemuConfig::SetGameListFavorite(uint64 titleId, bool isFavorite) { std::unique_lock _lock(game_cache_entries_mutex); GameEntry* gameEntry = GetGameEntryByTitleId(titleId); if (!gameEntry) gameEntry = CreateGameEntry(titleId); gameEntry->favorite = isFavorite; if (isFavorite) game_cache_favorites.emplace(titleId); else game_cache_favorites.erase(titleId); } bool CemuConfig::GetGameListCustomName(uint64 titleId, std::string& customName) { std::unique_lock _lock(game_cache_entries_mutex); GameEntry* gameEntry = GetGameEntryByTitleId(titleId); if (gameEntry && !gameEntry->custom_name.empty()) { customName = gameEntry->custom_name; return true; } return false; } void CemuConfig::SetGameListCustomName(uint64 titleId, std::string customName) { std::unique_lock _lock(game_cache_entries_mutex); GameEntry* gameEntry = GetGameEntryByTitleId(titleId); if (!gameEntry) { if (customName.empty()) return; gameEntry = CreateGameEntry(titleId); } gameEntry->custom_name = std::move(customName); } NetworkService CemuConfig::GetAccountNetworkService(uint32 persistentId) { auto it = account.service_select.find(persistentId); if (it != account.service_select.end()) { NetworkService serviceIndex = it->second; // make sure the returned service is valid if (serviceIndex != NetworkService::Offline && serviceIndex != NetworkService::Nintendo && serviceIndex != NetworkService::Pretendo && serviceIndex != NetworkService::Custom) return NetworkService::Offline; if( static_cast(serviceIndex) == NetworkService::Custom && !NetworkConfig::XMLExists() ) return NetworkService::Offline; // custom is selected but no custom config exists return serviceIndex; } // if not found, return the legacy value if(!account.legacy_online_enabled) return NetworkService::Offline; return static_cast(account.legacy_active_service.GetValue() + 1); // +1 because "Offline" now takes index 0 } void CemuConfig::SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex) { account.service_select[persistentId] = serviceIndex; } ================================================ FILE: src/config/CemuConfig.h ================================================ #pragma once #include "ConfigValue.h" #include "XMLConfig.h" #include "util/math/vector2.h" #include "Cafe/Account/Account.h" enum class NetworkService; struct GameEntry { GameEntry() = default; std::wstring rpx_file; std::wstring legacy_name; std::string product_code; std::string company_code; std::string custom_name; sint32 icon = -1; sint32 icon_small = -1; sint32 legacy_version = 0; sint32 legacy_update_version = -1; sint32 legacy_dlc_version = -1; uint64 title_id = 0; uint32 legacy_region = 0; std::wstring save_folder; std::wstring update_folder; std::wstring dlc_folder; uint64 legacy_time_played = 0; uint64 legacy_last_played = 0; bool favorite = false; bool is_update = false; }; struct GraphicPackEntry { GraphicPackEntry(std::wstring_view filename,std::string_view category, std::string_view preset) : filename(filename) { presets[std::string{category}] = preset; } GraphicPackEntry(std::wstring_view filename, std::string_view preset) : filename(filename) { presets[""] = preset; } GraphicPackEntry(std::wstring filename) : filename(std::move(filename)) {} struct Preset { std::string name; std::string category; }; std::unordered_map presets; // category, active_preset std::wstring filename; bool enabled = true; }; enum GraphicAPI { kOpenGL = 0, kVulkan, kMetal, }; enum AudioChannels { kMono = 0, kStereo, kSurround, }; enum UpscalingFilter { kLinearFilter, kBicubicFilter, kBicubicHermiteFilter, kNearestNeighborFilter, }; enum FullscreenScaling { kKeepAspectRatio, kStretch, }; enum class ScreenPosition { kDisabled = 0, kTopLeft, kTopCenter, kTopRight, kBottomLeft, kBottomCenter, kBottomRight, }; enum class PrecompiledShaderOption { Auto, Enable, Disable, }; ENABLE_ENUM_ITERATORS(PrecompiledShaderOption, PrecompiledShaderOption::Auto, PrecompiledShaderOption::Disable); enum class AccurateShaderMulOption { False = 0, // always use standard multiplication True = 1 // fully emulate non-ieee MUL special cases (0*anything = 0) }; ENABLE_ENUM_ITERATORS(AccurateShaderMulOption, AccurateShaderMulOption::False, AccurateShaderMulOption::True); enum class MetalBufferCacheMode { Auto, DevicePrivate, DeviceShared, Host, }; ENABLE_ENUM_ITERATORS(MetalBufferCacheMode, MetalBufferCacheMode::Auto, MetalBufferCacheMode::Host); enum class PositionInvariance { Auto, False, True, }; ENABLE_ENUM_ITERATORS(PositionInvariance, PositionInvariance::False, PositionInvariance::True); enum class CPUMode { SinglecoreInterpreter = 0, SinglecoreRecompiler = 1, DualcoreRecompiler = 2, // deprecated and not used anymore MulticoreRecompiler = 3, Auto = 4, }; ENABLE_ENUM_ITERATORS(CPUMode, CPUMode::SinglecoreInterpreter, CPUMode::Auto); enum class CPUModeLegacy { SinglecoreInterpreter = 0, SinglecoreRecompiler = 1, DualcoreRecompiler = 2, TriplecoreRecompiler = 3, Auto = 4 }; ENABLE_ENUM_ITERATORS(CPUModeLegacy, CPUModeLegacy::SinglecoreInterpreter, CPUModeLegacy::Auto); enum class CafeConsoleRegion { JPN = 0x1, USA = 0x2, EUR = 0x4, AUS_DEPR = 0x8, CHN = 0x10, KOR = 0x20, TWN = 0x40, Auto = 0xFF, }; ENABLE_BITMASK_OPERATORS(CafeConsoleRegion); enum class CafeConsoleLanguage { JA = 0, EN = 1, FR = 2, DE = 3, IT = 4, ES = 5, ZH = 6, KO = 7, NL = 8, PT = 9, RU = 10, TW = 11, }; ENABLE_ENUM_ITERATORS(CafeConsoleLanguage, CafeConsoleLanguage::JA, CafeConsoleLanguage::TW); #if BOOST_OS_WINDOWS enum class CrashDump { Disabled, Lite, Full }; ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Full); #elif BOOST_OS_UNIX enum class CrashDump { Disabled, Enabled }; ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Enabled); #endif template <> struct fmt::formatter : formatter { template auto format(const PrecompiledShaderOption c, FormatContext &ctx) const { string_view name; switch (c) { case PrecompiledShaderOption::Auto: name = "auto"; break; case PrecompiledShaderOption::Enable: name = "true"; break; case PrecompiledShaderOption::Disable: name = "false"; break; default: name = "unknown"; break; } return formatter::format(name, ctx); } }; template <> struct fmt::formatter : formatter { template auto format(const AccurateShaderMulOption c, FormatContext &ctx) const { string_view name; switch (c) { case AccurateShaderMulOption::True: name = "true"; break; case AccurateShaderMulOption::False: name = "false"; break; default: name = "unknown"; break; } return formatter::format(name, ctx); } }; template <> struct fmt::formatter : formatter { template auto format(const MetalBufferCacheMode c, FormatContext &ctx) const { string_view name; switch (c) { case MetalBufferCacheMode::Auto: name = "auto"; break; case MetalBufferCacheMode::DevicePrivate: name = "device private"; break; case MetalBufferCacheMode::DeviceShared: name = "device shared"; break; case MetalBufferCacheMode::Host: name = "host"; break; default: name = "unknown"; break; } return formatter::format(name, ctx); } }; template <> struct fmt::formatter : formatter { template auto format(const PositionInvariance c, FormatContext &ctx) const { string_view name; switch (c) { case PositionInvariance::Auto: name = "auto"; break; case PositionInvariance::False: name = "false"; break; case PositionInvariance::True: name = "true"; break; default: name = "unknown"; break; } return formatter::format(name, ctx); } }; template <> struct fmt::formatter : formatter { template auto format(const CPUMode c, FormatContext &ctx) const { string_view name; switch (c) { case CPUMode::SinglecoreInterpreter: name = "Single-core interpreter"; break; case CPUMode::SinglecoreRecompiler: name = "Single-core recompiler"; break; case CPUMode::DualcoreRecompiler: name = "Dual-core recompiler"; break; case CPUMode::MulticoreRecompiler: name = "Multi-core recompiler"; break; case CPUMode::Auto: name = "Auto"; break; default: name = "unknown"; break; } return formatter::format(name, ctx); } }; template <> struct fmt::formatter : formatter { template auto format(const CPUModeLegacy c, FormatContext &ctx) const { string_view name; switch (c) { case CPUModeLegacy::SinglecoreInterpreter: name = "Singlecore-Interpreter"; break; case CPUModeLegacy::SinglecoreRecompiler: name = "Singlecore-Recompiler"; break; case CPUModeLegacy::DualcoreRecompiler: name = "Dualcore-Recompiler"; break; case CPUModeLegacy::TriplecoreRecompiler: name = "Triplecore-Recompiler"; break; case CPUModeLegacy::Auto: name = "Auto"; break; default: name = "unknown"; break; } return formatter::format(name, ctx); } }; template <> struct fmt::formatter : formatter { template auto format(const CafeConsoleRegion v, FormatContext &ctx) const { string_view name; switch (v) { case CafeConsoleRegion::JPN: name = TR_NOOP("Japan"); break; case CafeConsoleRegion::USA: name = TR_NOOP("USA"); break; case CafeConsoleRegion::EUR: name = TR_NOOP("Europe"); break; case CafeConsoleRegion::AUS_DEPR: name = TR_NOOP("Australia"); break; case CafeConsoleRegion::CHN: name = TR_NOOP("China"); break; case CafeConsoleRegion::KOR: name = TR_NOOP("Korea"); break; case CafeConsoleRegion::TWN: name = TR_NOOP("Taiwan"); break; case CafeConsoleRegion::Auto: name = TR_NOOP("Auto"); break; default: name = TR_NOOP("many"); break; } return formatter::format(name, ctx); } }; template <> struct fmt::formatter : formatter { template auto format(const CafeConsoleLanguage v, FormatContext &ctx) { string_view name; switch (v) { case CafeConsoleLanguage::JA: name = "Japanese"; break; case CafeConsoleLanguage::EN: name = "English"; break; case CafeConsoleLanguage::FR: name = "French"; break; case CafeConsoleLanguage::DE: name = "German"; break; case CafeConsoleLanguage::IT: name = "Italian"; break; case CafeConsoleLanguage::ES: name = "Spanish"; break; case CafeConsoleLanguage::ZH: name = "Chinese"; break; case CafeConsoleLanguage::KO: name = "Korean"; break; case CafeConsoleLanguage::NL: name = "Dutch"; break; case CafeConsoleLanguage::PT: name = "Portugese"; break; case CafeConsoleLanguage::RU: name = "Russian"; break; case CafeConsoleLanguage::TW: name = "Taiwanese"; break; default: name = "unknown"; break; } return formatter::format(name, ctx); } }; #if BOOST_OS_WINDOWS template <> struct fmt::formatter : formatter { template auto format(const CrashDump v, FormatContext &ctx) { string_view name; switch (v) { case CrashDump::Disabled: name = "Disabled"; break; case CrashDump::Lite: name = "Lite"; break; case CrashDump::Full: name = "Full"; break; default: name = "unknown"; break; } return formatter::format(name, ctx); } }; #elif BOOST_OS_UNIX template <> struct fmt::formatter : formatter { template auto format(const CrashDump v, FormatContext &ctx) { string_view name; switch (v) { case CrashDump::Disabled: name = "Disabled"; break; case CrashDump::Enabled: name = "Enabled"; break; default: name = "unknown"; break; } return formatter::format(name, ctx); } }; #endif struct CemuConfig { CemuConfig() { }; CemuConfig(const CemuConfig&) = delete; // sets mlc path, updates permanent config value, saves config void SetMLCPath(fs::path path, bool save = true); ConfigValue log_flag{ 0 }; ConfigValue advanced_ppc_logging{ false }; ConfigValue permanent_storage{ true }; ConfigValue mlc_path{}; ConfigValue proxy_server{}; // temporary workaround because feature crashes on macOS #if BOOST_OS_MACOS #define DISABLE_SCREENSAVER_DEFAULT false #else #define DISABLE_SCREENSAVER_DEFAULT true #endif ConfigValue disable_screensaver{DISABLE_SCREENSAVER_DEFAULT}; #undef DISABLE_SCREENSAVER_DEFAULT ConfigValue play_boot_sound{false}; std::vector game_paths; std::mutex game_cache_entries_mutex; std::vector game_cache_entries; // optimized access std::set game_cache_favorites; // per titleId struct _path_hash { std::size_t operator()(const fs::path& path) const { return fs::hash_value(path); } }; // > std::unordered_map, _path_hash> graphic_pack_entries; ConfigValueBounds cpu_mode{ CPUMode::Auto }; ConfigValueBounds console_language{ CafeConsoleLanguage::EN }; // graphics ConfigValue graphic_api{ kVulkan }; std::array legacy_graphic_device_uuid{}; // placeholder option for backwards compatibility with settings from 2.6 and before (renamed to "vkDevice") std::array vk_graphic_device_uuid; uint64 mtl_graphic_device_uuid{ 0 }; ConfigValue vsync{ 0 }; // 0 = off, 1+ = depending on render backend ConfigValue gx2drawdone_sync { true }; ConfigValue render_upside_down{ false }; ConfigValue async_compile{ true }; #if ENABLE_METAL ConfigValue force_mesh_shaders{ false }; #endif // Gamma ConfigValue overrideAppGammaPreference{ false }; ConfigValue overrideGammaValue{ 2.2f }; ConfigValue userDisplayGamma { 2.2f }; // 0 = sRGB, >0 gamma ConfigValue vk_accurate_barriers{ true }; struct { ScreenPosition position = ScreenPosition::kDisabled; uint32 text_color = 0xFFFFFFFF; sint32 text_scale = 100; bool fps = true; bool drawcalls = false; bool cpu_usage = false; bool cpu_per_core_usage = false; bool ram_usage = false; bool vram_usage = false; bool debug = false; } overlay{}; struct { ScreenPosition position = ScreenPosition::kTopLeft; uint32 text_color = 0xFFFFFFFF; sint32 text_scale = 100; bool controller_profiles = true; bool controller_battery = false; bool shader_compiling = true; bool friends = true; } notification{}; ConfigValue upscale_filter{kBicubicFilter}; ConfigValue downscale_filter{kLinearFilter}; ConfigValue fullscreen_scaling{kKeepAspectRatio}; // audio sint32 audio_api = 0; sint32 audio_delay = 2; AudioChannels tv_channels = kStereo, pad_channels = kStereo, input_channels = kMono; sint32 tv_volume = 50, pad_volume = 0, input_volume = 50, portal_volume = 50; std::wstring tv_device{ L"default" }, pad_device, input_device, portal_device; // account struct { ConfigValueBounds m_persistent_id{ Account::kMinPersistendId, Account::kMinPersistendId, 0xFFFFFFFF }; ConfigValue legacy_online_enabled{false}; ConfigValue legacy_active_service{0}; std::unordered_map service_select; // per-account service index. Key is persistentId }account{}; // input struct { ConfigValue host{"127.0.0.1"}; ConfigValue port{ 26760 }; }dsu_client{}; // debug ConfigValueBounds crash_dump{ CrashDump::Disabled }; ConfigValue gdb_port{ 1337 }; #if ENABLE_METAL ConfigValue gpu_capture_dir{ "" }; ConfigValue framebuffer_fetch{ true }; #endif XMLConfigParser Load(XMLConfigParser& parser); XMLConfigParser Save(XMLConfigParser& parser); bool IsGameListFavorite(uint64 titleId); void SetGameListFavorite(uint64 titleId, bool isFavorite); bool GetGameListCustomName(uint64 titleId, std::string& customName); void SetGameListCustomName(uint64 titleId, std::string customName); NetworkService GetAccountNetworkService(uint32 persistentId); void SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex); // emulated usb devices struct { ConfigValue emulate_skylander_portal{false}; ConfigValue emulate_infinity_base{false}; ConfigValue emulate_dimensions_toypad{false}; }emulated_usb_devices{}; static int AudioChannelsToNChannels(AudioChannels kStereo) { switch (kStereo) { case 0: return 1; // will mix mono sound on both output channels case 2: return 6; default: // stereo return 2; } } private: GameEntry* GetGameEntryByTitleId(uint64 titleId); GameEntry* CreateGameEntry(uint64 titleId); }; typedef XMLDataConfig XMLCemuConfig_t; inline XMLCemuConfig_t& GetConfigHandle() { static XMLCemuConfig_t config; return config; } inline CemuConfig& GetConfig() { return GetConfigHandle().data(); } ================================================ FILE: src/config/ConfigValue.h ================================================ #pragma once #include "Common/enumFlags.h" #include #include #include #include #include template class ConfigValueAtomic { public: constexpr ConfigValueAtomic() : m_value(TType()), m_init_value(TType()) {} constexpr ConfigValueAtomic(const TType& init_value) : m_value(init_value), m_init_value(init_value) {} constexpr ConfigValueAtomic(TType&& init_value) : m_value(init_value), m_init_value(init_value) {} ConfigValueAtomic(const ConfigValueAtomic& v) : m_value(v.GetValue()), m_init_value(v.m_init_value) {} ConfigValueAtomic& operator=(const ConfigValueAtomic& v) { SetValue(v.GetValue()); return *this; } ConfigValueAtomic& operator=(const TType& v) { SetValue(v); return *this; } ConfigValueAtomic& operator=(TType&& v) { SetValue(v); return *this; } [[nodiscard]] inline TType GetValue() const { return m_value.load(); } void SetValue(const TType& v) { m_value = v; } [[nodiscard]] const TType& GetInitValue() const { return m_init_value; } operator TType() const { return m_value.load(); } private: std::atomic m_value; const TType m_init_value; }; template class ConfigValueNoneAtomic { public: constexpr ConfigValueNoneAtomic() : m_value({}), m_init_value({}) {} constexpr ConfigValueNoneAtomic(const TType& init_value) : m_value(init_value), m_init_value(init_value) {} constexpr ConfigValueNoneAtomic(TType&& init_value) : m_value(init_value), m_init_value(init_value) {} ConfigValueNoneAtomic(const ConfigValueNoneAtomic& v) : m_value(v.GetValue()), m_init_value(v.GetInitValue()) {} ConfigValueNoneAtomic& operator=(const ConfigValueNoneAtomic& v) { SetValue(v.GetValue()); return *this; } ConfigValueNoneAtomic& operator=(const TType& v) { SetValue(v); return *this; } ConfigValueNoneAtomic& operator=(TType&& v) { SetValue(v); return *this; } [[nodiscard]] TType GetValue() const { std::shared_lock lock(m_mutex); return m_value; } void SetValue(const TType& v) { std::lock_guard lock(m_mutex); m_value = v; } void SetValue(std::string_view v) requires std::is_same_v { std::lock_guard lock(m_mutex); m_value = v; } void SetValue(std::wstring_view v) requires std::is_same_v { std::lock_guard lock(m_mutex); m_value = v; } [[nodiscard]] const TType& GetInitValue() const { return m_init_value; } [[nodiscard]] operator TType() const { return GetValue(); } protected: mutable std::shared_mutex m_mutex; TType m_value; const TType m_init_value; }; template constexpr bool is_atomic_type_v = std::is_trivially_copyable_v && std::is_copy_constructible_v && std::is_move_constructible_v && std::is_copy_assignable_v && std::is_move_assignable_v; template class ConfigValue : public std::conditional, ConfigValueAtomic, ConfigValueNoneAtomic>::type { public: using base_type = typename std::conditional, ConfigValueAtomic, ConfigValueNoneAtomic>::type; using base_type::base_type; ConfigValue& operator=(const ConfigValue& v) { base_type::operator=(v.GetValue()); return *this; } ConfigValue& operator=(const TType& v) { base_type::operator=(v); return *this; } ConfigValue& operator=(TType&& v) { base_type::operator=(v); return *this; } }; template class ConfigValueBounds : public ConfigValue { public: using base_type = ConfigValue; constexpr ConfigValueBounds(const TType& min, const TType& init_value, const TType& max) :base_type(init_value), m_min_value(min), m_max_value(max) { assert(m_min_value <= init_value && init_value <= m_max_value); } constexpr ConfigValueBounds(TType&& min, TType&& init_value, TType&& max) : base_type(std::forward(init_value)), m_min_value(min), m_max_value(max) { assert(m_min_value <= init_value && init_value <= m_max_value); } // init from enum with iterators constexpr ConfigValueBounds() requires std::is_enum_v && EnableEnumIterators::enable : base_type(), m_min_value(begin(TType{})), m_max_value(rbegin(TType{})) { assert(m_min_value <= this->GetInitValue() && this->GetInitValue() <= m_max_value); } constexpr ConfigValueBounds(const TType& init_value) requires std::is_enum_v && EnableEnumIterators::enable : base_type(std::forward(init_value)), m_min_value(begin(init_value)), m_max_value(rbegin(init_value)) { assert(m_min_value <= init_value && init_value <= m_max_value); } constexpr ConfigValueBounds(TType&& init_value) requires std::is_enum_v && EnableEnumIterators::enable : base_type(std::forward(init_value)), m_min_value(begin(init_value)), m_max_value(rbegin(init_value)) { assert(m_min_value <= init_value && init_value <= m_max_value); } ConfigValueBounds& operator=(const ConfigValueBounds& v) { base_type::operator=(v.GetValue()); const auto& value = this->GetValue(); if (value < GetMin() || value > GetMax()) base_type::SetValue(this->GetInitValue()); return *this; }; ConfigValueBounds& operator=(const TType& v) { base_type::operator=(v); const auto& value = this->GetValue(); if (value < GetMin() || value > GetMax()) base_type::SetValue(this->GetInitValue()); return *this; }; ConfigValueBounds& operator=(TType&& v) { base_type::operator=(v); const auto& value = this->GetValue(); if (value < GetMin() || value > GetMax()) base_type::SetValue(this->GetInitValue()); return *this; }; [[nodiscard]] const TType& GetMax() const { return m_max_value; } [[nodiscard]] const TType& GetMin() const { return m_min_value; } private: const TType m_min_value; const TType m_max_value; }; ================================================ FILE: src/config/LaunchSettings.cpp ================================================ #include "LaunchSettings.h" #include "util/helpers/helpers.h" #include "Cafe/Account/Account.h" #include "Cafe/OS/libs/coreinit/coreinit.h" #include "boost/program_options.hpp" #include "config/ActiveSettings.h" #include "config/NetworkSettings.h" #include "util/crypto/aes128.h" #include "Cafe/Filesystem/FST/FST.h" #include "util/helpers/StringHelpers.h" void requireConsole(); bool LaunchSettings::HandleCommandline(const wchar_t* lpCmdLine) { #if BOOST_OS_WINDOWS const std::vector args = boost::program_options::split_winmain(lpCmdLine); return HandleCommandline(args); #else cemu_assert_unimplemented(); return false; #endif } bool LaunchSettings::HandleCommandline(int argc, wchar_t* argv[]) { std::vector args; args.reserve(argc); for(int i = 0; i < argc; ++i) { args.emplace_back(argv[i]); } return HandleCommandline(args); } bool LaunchSettings::HandleCommandline(int argc, char* argv[]) { std::vector args; args.reserve(argc); for(int i = 0; i < argc; ++i) { args.emplace_back(boost::nowide::widen(argv[i])); } return HandleCommandline(args); } bool LaunchSettings::HandleCommandline(const std::vector& args) { namespace po = boost::program_options; po::options_description desc{ "Launch options" }; desc.add_options() ("help,h", "This help screen") ("version,v", "Displays the version of Cemu") #if !BOOST_OS_WINDOWS ("verbose", "Log to stdout") #endif ("game,g", po::wvalue(), "Path of game to launch") ("title-id,t", po::value(), "Title ID of the title to be launched (overridden by --game)") ("mlc,m", po::wvalue(), "Custom mlc folder location") ("fullscreen,f", po::value()->implicit_value(true), "Launch games in fullscreen mode") ("ud,u", po::value()->implicit_value(true), "Render output upside-down") ("account,a", po::value(), "Persistent id of account") ("force-interpreter", po::value()->implicit_value(true), "Force interpreter CPU emulation, disables recompiler. Useful for debugging purposes where you want to get accurate memory accesses and stack traces.") ("force-multicore-interpreter", po::value()->implicit_value(true), "Force multi-core interpreter CPU emulation, disables recompiler. Only useful for getting stack traces, but slightly faster than the single-core interpreter mode.") ("enable-gdbstub", po::value()->implicit_value(true), "Enable GDB stub to debug executables inside Cemu using an external debugger"); po::options_description hidden{ "Hidden options" }; hidden.add_options() ("nsight", po::value()->implicit_value(true), "NSight debugging options") ("legacy", po::value()->implicit_value(true), "Intel legacy graphic mode") ("ppcrec-lower-addr", po::value(), "For debugging: Lower address allowed for PPC recompilation") ("ppcrec-upper-addr", po::value(), "For debugging: Upper address allowed for PPC recompilation"); po::options_description extractor{ "Extractor tool" }; extractor.add_options() ("extract,e", po::wvalue(), "Path to WUD or WUX file for extraction") ("path,p", po::value(), "Path of file to extract (for example meta/meta.xml)") ("output,o", po::wvalue(), "Output path for extracted file."); po::options_description all; all.add(desc).add(hidden).add(extractor); po::options_description visible; visible.add(desc).add(extractor); try { po::wcommand_line_parser parser{ args }; parser.allow_unregistered().options(all).style(po::command_line_style::allow_long | po::command_line_style::allow_short | po::command_line_style::allow_dash_for_short | po::command_line_style::case_insensitive | po::command_line_style::long_allow_next | po::command_line_style::short_allow_next | po::command_line_style::allow_long_disguise); const auto parsed_options = parser.run(); po::variables_map vm; po::store(parsed_options, vm); notify(vm); if (vm.count("help")) { requireConsole(); std::cout << visible << std::endl; return false; // exit in main } if (vm.count("version")) { requireConsole(); std::string versionStr; #if EMULATOR_VERSION_PATCH == 0 versionStr = fmt::format("{}.{}{}", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_SUFFIX); #else versionStr = fmt::format("{}.{}-{}{}", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, EMULATOR_VERSION_SUFFIX); #endif std::cout << versionStr << std::endl; return false; // exit in main } if (vm.count("verbose")) s_verbose = true; if (vm.count("game")) { std::wstring tmp = vm["game"].as(); // workaround for boost command_line_parser not trimming token for short name parameters despite short_allow_adjacent if (tmp.size() > 0 && tmp.front() == '=') tmp.erase(tmp.begin()+0); s_load_game_file = tmp; } if (vm.count("title-id")) { auto title_param = vm["title-id"].as(); try { if (title_param.starts_with('=')){ title_param.erase(title_param.begin()); } s_load_title_id = std::stoull(title_param, nullptr, 16); } catch (std::invalid_argument const& e) { std::cerr << "Expected title_param ID as an unsigned 64-bit hexadecimal string\n"; } } if (vm.count("mlc")) { std::wstring tmp = vm["mlc"].as(); // workaround for boost command_line_parser not trimming token for short name parameters despite short_allow_adjacent if (tmp.size() > 0 && tmp.front() == '=') tmp.erase(tmp.begin() + 0); s_mlc_path = tmp; } if (vm.count("account")) { const auto id = ConvertString(vm["account"].as(), 16); if (id >= Account::kMinPersistendId) s_persistent_id = id; } if (vm.count("fullscreen")) s_fullscreen = vm["fullscreen"].as(); if (vm.count("ud")) s_render_upside_down = vm["ud"].as(); if (vm.count("nsight")) s_nsight_mode = vm["nsight"].as(); if(vm.count("force-interpreter")) s_force_interpreter = vm["force-interpreter"].as(); if(vm.count("force-multicore-interpreter")) s_force_multicore_interpreter = vm["force-multicore-interpreter"].as(); if (vm.count("enable-gdbstub")) s_enable_gdbstub = vm["enable-gdbstub"].as(); std::wstring extract_path, log_path; std::string output_path; if (vm.count("extract")) extract_path = vm["extract"].as(); if (vm.count("path")) output_path = vm["path"].as(); if (vm.count("output")) log_path = vm["output"].as(); // recompiler range limit for debugging if (vm.count("ppcrec-lower-addr")) { uint32 addr = (uint32)StringHelpers::ToInt64(vm["ppcrec-lower-addr"].as()); ppcRec_limitLowerAddr = addr; } if (vm.count("ppcrec-upper-addr")) { uint32 addr = (uint32)StringHelpers::ToInt64(vm["ppcrec-upper-addr"].as()); ppcRec_limitUpperAddr = addr; } if(ppcRec_limitLowerAddr != 0 && ppcRec_limitUpperAddr != 0) cemuLog_log(LogType::Force, "PPCRec range limited to 0x{:08x}-0x{:08x}", ppcRec_limitLowerAddr, ppcRec_limitUpperAddr); if(!extract_path.empty()) { ExtractorTool(extract_path, output_path, log_path); return false; } return true; } catch (const std::exception& ex) { std::string errorMsg; errorMsg.append("Error while trying to parse command line parameter:\n"); errorMsg.append(ex.what()); std::cout << errorMsg << std::endl; return false; } } bool LaunchSettings::ExtractorTool(std::wstring_view wud_path, std::string_view output_path, std::wstring_view log_path) { // extracting requires path of file if (output_path.empty()) { requireConsole(); puts("Cannot extract files because no source path was specified (-p)\n"); return false; } // mount wud AES128_init(); FSTVolume* srcVolume = FSTVolume::OpenFromDiscImage(fs::path(wud_path)); if (!srcVolume) { requireConsole(); puts(fmt::format("Unable to open \"%s\"\n", fs::path(wud_path).generic_string()).c_str()); return false; } bool fileFound = false; std::vector fileData = srcVolume->ExtractFile(output_path, &fileFound); delete srcVolume; if (!fileFound) { requireConsole(); puts(fmt::format("Unable to read file \"%s\"\n", output_path).c_str()); return false; } // output on console (if no output path was specified) if (!log_path.empty()) { try { fs::path filename(std::wstring{ log_path }); filename /= boost::nowide::widen(std::string(output_path)); fs::path p = filename; p.remove_filename(); fs::create_directories(p); std::ofstream file(filename, std::ios::out | std::ios::binary); file.write((const char*)fileData.data(), fileData.size()); file.flush(); file.close(); } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't write file: {}", ex.what()); puts(fmt::format("can't write file: %s\n", ex.what()).c_str()); } } else { // output to console requireConsole(); printf("%.*s", (int)fileData.size(), fileData.data()); fflush(stdout); } return true; } ================================================ FILE: src/config/LaunchSettings.h ================================================ #pragma once #include #include class LaunchSettings { public: // winmain static bool HandleCommandline(const wchar_t* lpCmdLine); // wmain static bool HandleCommandline(int argc, wchar_t* argv[]); // main (unix) static bool HandleCommandline(int argc, char* argv[]); static bool HandleCommandline(const std::vector& args); static std::optional GetLoadFile() { return s_load_game_file; } static std::optional GetLoadTitleID() {return s_load_title_id;} static std::optional GetMLCPath() { return s_mlc_path; } static std::optional RenderUpsideDownEnabled() { return s_render_upside_down; } static std::optional FullscreenEnabled() { return s_fullscreen; } static bool Verbose() { return s_verbose; } static bool GDBStubEnabled() { return s_enable_gdbstub; } static bool NSightModeEnabled() { return s_nsight_mode; } static bool ForceInterpreter() { return s_force_interpreter; }; static bool ForceMultiCoreInterpreter() { return s_force_multicore_interpreter; } static std::optional GetPersistentId() { return s_persistent_id; } static uint32 GetPPCRecLowerAddr() { return ppcRec_limitLowerAddr; }; static uint32 GetPPCRecUpperAddr() { return ppcRec_limitUpperAddr; }; private: inline static std::optional s_load_game_file{}; inline static std::optional s_load_title_id{}; inline static std::optional s_mlc_path{}; inline static std::optional s_render_upside_down{}; inline static std::optional s_fullscreen{}; inline static bool s_verbose = false; inline static bool s_enable_gdbstub = false; inline static bool s_nsight_mode = false; inline static bool s_force_interpreter = false; inline static bool s_force_multicore_interpreter = false; inline static std::optional s_persistent_id{}; // for recompiler debugging inline static uint32 ppcRec_limitLowerAddr{}; inline static uint32 ppcRec_limitUpperAddr{}; static bool ExtractorTool(std::wstring_view wud_path, std::string_view output_path, std::wstring_view log_path); }; ================================================ FILE: src/config/NetworkSettings.cpp ================================================ #include "NetworkSettings.h" #include "ActiveSettings.h" #include "LaunchSettings.h" #include "CemuConfig.h" #include "Common/FileStream.h" XMLNetworkConfig_t n_config(L"network_services.xml"); void NetworkConfig::LoadOnce() { n_config.SetFilename(ActiveSettings::GetConfigPath("network_services.xml").generic_wstring()); if (XMLExists()) n_config.Load(); } void NetworkConfig::Load(XMLConfigParser& parser) { auto config = parser.get("content"); networkname = config.get("networkname", "Custom"); disablesslver = config.get("disablesslverification", disablesslver); auto u = config.get("urls"); urls.ACT = u.get("act", NintendoURLs::ACTURL); urls.ECS = u.get("ecs", NintendoURLs::ECSURL); urls.NUS = u.get("nus", NintendoURLs::NUSURL); urls.IAS = u.get("ias", NintendoURLs::IASURL); urls.CCSU = u.get("ccsu", NintendoURLs::CCSUURL); urls.CCS = u.get("ccs", NintendoURLs::CCSURL); urls.IDBE = u.get("idbe", NintendoURLs::IDBEURL); urls.BOSS = u.get("boss", NintendoURLs::BOSSURL); urls.TAGAYA = u.get("tagaya", NintendoURLs::TAGAYAURL); urls.OLV = u.get("olv", NintendoURLs::OLVURL); } bool NetworkConfig::XMLExists() { static std::optional s_exists; // caches result of fs::exists if(s_exists.has_value()) return *s_exists; std::error_code ec; if (!fs::exists(ActiveSettings::GetConfigPath("network_services.xml"), ec)) { s_exists = false; return false; } s_exists = true; return true; } ================================================ FILE: src/config/NetworkSettings.h ================================================ #pragma once #include "ConfigValue.h" #include "XMLConfig.h" enum class NetworkService { Offline, Nintendo, Pretendo, Custom, COUNT = Custom }; struct NetworkConfig { NetworkConfig() { }; NetworkConfig(const NetworkConfig&) = delete; ConfigValue networkname; ConfigValue disablesslver = false; struct { ConfigValue ACT; ConfigValue ECS; ConfigValue NUS; ConfigValue IAS; ConfigValue CCSU; ConfigValue CCS; ConfigValue IDBE; ConfigValue BOSS; ConfigValue TAGAYA; ConfigValue OLV; }urls{}; public: static void LoadOnce(); void Load(XMLConfigParser& parser); void Save(XMLConfigParser& parser); static bool XMLExists(); }; struct NintendoURLs { inline static std::string ACTURL = "https://account.nintendo.net"; inline static std::string ECSURL = "https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP"; inline static std::string NUSURL = "https://nus.wup.shop.nintendo.net/nus/services/NetUpdateSOAP"; inline static std::string IASURL = "https://ias.wup.shop.nintendo.net/ias/services/IdentityAuthenticationSOAP"; inline static std::string CCSUURL = "https://ccs.wup.shop.nintendo.net/ccs/download"; inline static std::string CCSURL = "http://ccs.cdn.wup.shop.nintendo.net/ccs/download"; inline static std::string IDBEURL = "https://idbe-wup.cdn.nintendo.net/icondata"; inline static std::string BOSSURL = "https://npts.app.nintendo.net/p01/tasksheet"; inline static std::string TAGAYAURL = "https://tagaya.wup.shop.nintendo.net/tagaya/versionlist"; inline static std::string OLVURL = "https://discovery.olv.nintendo.net/v1/endpoint"; }; struct PretendoURLs { inline static std::string ACTURL = "https://account.pretendo.cc"; inline static std::string ECSURL = "https://ecs.wup.shop.pretendo.cc/ecs/services/ECommerceSOAP"; inline static std::string NUSURL = "https://nus.c.shop.pretendo.cc/nus/services/NetUpdateSOAP"; inline static std::string IASURL = "https://ias.c.shop.pretendo.cc/ias/services/IdentityAuthenticationSOAP"; inline static std::string CCSUURL = "https://ccs.c.shop.pretendo.cc/ccs/download"; inline static std::string CCSURL = "http://ccs.cdn.c.shop.pretendo.cc/ccs/download"; inline static std::string IDBEURL = "https://idbe-wup.cdn.pretendo.cc/icondata"; inline static std::string BOSSURL = "https://npts.app.pretendo.cc/p01/tasksheet"; inline static std::string TAGAYAURL = "https://tagaya.wup.shop.pretendo.cc/tagaya/versionlist"; inline static std::string OLVURL = "https://discovery.olv.pretendo.cc/v1/endpoint"; }; typedef XMLDataConfig XMLNetworkConfig_t; extern XMLNetworkConfig_t n_config; inline NetworkConfig& GetNetworkConfig() { return n_config.data();}; inline bool IsNetworkServiceSSLDisabled(NetworkService service) { if(service == NetworkService::Nintendo) return false; else if(service == NetworkService::Pretendo) return true; else if(service == NetworkService::Custom) return GetNetworkConfig().disablesslver.GetValue(); return false; } ================================================ FILE: src/config/XMLConfig.h ================================================ #pragma once #include "util/tinyxml2/tinyxml2.h" #include "Common/FileStream.h" #include "config/ConfigValue.h" #include #include template concept HasConstCharConstructor = requires { T(std::declval()); }; class XMLConfigParser { public: XMLConfigParser(tinyxml2::XMLDocument* document) : m_document(document), m_current_element(nullptr), m_is_root(true) {} private: XMLConfigParser(tinyxml2::XMLDocument* document, tinyxml2::XMLElement* element) : m_document(document), m_current_element(element), m_is_root(false) {} public: template auto get(const char* name, T default_value = {}) { if constexpr(std::is_enum_v) return static_cast(get(name, static_cast>(default_value))); const auto* element = m_current_element ? m_current_element->FirstChildElement(name) : m_document->FirstChildElement(name); if (element == nullptr) return default_value; if constexpr (std::is_same_v) return element->BoolText(default_value); else if constexpr (std::is_same_v) return element->FloatText(default_value); else if constexpr (std::is_same_v) return element->IntText(default_value); else if constexpr (std::is_same_v) return element->UnsignedText(default_value); else if constexpr (std::is_same_v) return element->Int64Text(default_value); else if constexpr (std::is_same_v) // doesnt support real uint64... return (uint64)element->Int64Text((sint64)default_value); else if constexpr (std::is_same_v || std::is_same_v || HasConstCharConstructor) { const char* text = element->GetText(); return text ? text : default_value; } return default_value; } template T get(const char* name, const ConfigValue& default_value) { return get(name, default_value.GetInitValue()); } template T get(const char* name, const ConfigValueBounds& default_value) { return get(name, default_value.GetInitValue()); } template auto get_attribute(const char* name, T default_value = {}) { if constexpr (std::is_enum_v) return static_cast(get_attribute(name, static_cast>(default_value))); if (m_current_element == nullptr) return default_value; if constexpr (std::is_same_v) return m_current_element->BoolAttribute(name, default_value); else if constexpr (std::is_same_v) return m_current_element->FloatAttribute(name, default_value); else if constexpr (std::is_same_v) return m_current_element->IntAttribute(name, default_value); else if constexpr (std::is_same_v) return m_current_element->UnsignedAttribute(name, default_value); else if constexpr (std::is_same_v) return m_current_element->Int64Attribute(name, default_value); else if constexpr (std::is_same_v) // doesnt support real uint64... return (uint64)m_current_element->Int64Attribute(name, (sint64)default_value); else if constexpr (std::is_same_v || std::is_same_v) { const char* text = m_current_element->Attribute(name); return text ? text : default_value; } return default_value; } template T get_attribute(const char* name, const ConfigValue& default_value) { return get_attribute(name, default_value.GetInitValue()); } template T get_attribute(const char* name, const ConfigValueBounds& default_value) { return get_attribute(name, default_value.GetInitValue()); } int char2int(char input) { if(input >= '0' && input <= '9') return input - '0'; if(input >= 'A' && input <= 'F') return input - 'A' + 10; if(input >= 'a' && input <= 'f') return input - 'a' + 10; throw std::invalid_argument("Invalid input string"); } template std::array& get(const char* name, std::array& arr) { arr = {}; const auto element = m_current_element ? m_current_element->FirstChildElement(name) : m_document->FirstChildElement(name); if (element == nullptr) return arr; const char* text = element->GetText(); if(text) { assert(strlen(text) == (arr.size() * 2)); std::istringstream iss(text); for(int i = 0; i < arr.size(); ++i) { arr[i] = (char2int(text[i*2]) << 4) + char2int(text[i * 2 + 1]); } } return arr; } template T value(T default_value = T()) { //peterBreak(); return default_value; } bool value(bool default_value) { return m_current_element ? m_current_element->BoolText(default_value) : default_value; } float value(float default_value) { return m_current_element ? m_current_element->FloatText(default_value) : default_value; } double value(double default_value) { return m_current_element ? m_current_element->DoubleText(default_value) : default_value; } uint32 value(uint32 default_value) { return m_current_element ? m_current_element->UnsignedText(default_value) : default_value; } sint32 value(sint32 default_value) { return m_current_element ? m_current_element->IntText(default_value) : default_value; } uint64 value(uint64 default_value) { return m_current_element ? (uint64)m_current_element->Int64Text(default_value) : default_value; } sint64 value(sint64 default_value) { return m_current_element ? m_current_element->Int64Text(default_value) : default_value; } const char* value(const char* default_value) { if (m_current_element) { if (m_current_element->GetText()) return m_current_element->GetText(); } return default_value; } template void set(const char* name, const std::array& value) { auto element = m_document->NewElement(name); std::stringstream str; for(const auto& v : value) { str << fmt::format("{:02x}", v); } element->SetText(str.str().c_str()); if (m_current_element) m_current_element->InsertEndChild(element); else m_document->InsertEndChild(element); } template void set(const char* name, T value) { auto* element = m_document->NewElement(name); if constexpr (std::is_enum_v) element->SetText(fmt::format("{}", static_cast::type>(value)).c_str()); else element->SetText(fmt::format("{}", value).c_str()); if (m_current_element) m_current_element->InsertEndChild(element); else m_document->InsertEndChild(element); } template void set(const char* name, const std::atomic& value) { set(name, value.load()); } template void set(const char* name, const ConfigValue& value) { set(name, value.GetValue()); } void set(const char* name, uint64 value) { set(name, (sint64)value); } tinyxml2::XMLElement* GetCurrentElement() const { return m_current_element; } XMLConfigParser get(const char* name) const { const auto element = m_current_element ? m_current_element->FirstChildElement(name) : m_document->FirstChildElement(name); return {m_document, element}; } XMLConfigParser get(const char* name, const XMLConfigParser& parser) { const auto element = parser.m_current_element ? parser.m_current_element->NextSiblingElement(name) : parser.m_document->NextSiblingElement(name); return { m_document, element }; } XMLConfigParser set(const char* name) const { const auto element = m_document->NewElement(name); if (m_current_element) m_current_element->InsertEndChild(element); else m_document->InsertEndChild(element); return {m_document, element}; } template XMLConfigParser& set_attribute(const char* name, const T& value) { cemu_assert_debug(m_current_element != nullptr); if (m_current_element) m_current_element->SetAttribute(name, value); return *this; } template XMLConfigParser& set_attribute(const char* name, const ConfigValue& value) { return set_attribute(name, value.GetValue()); } template XMLConfigParser& set_attribute(const char* name, const ConfigValueBounds& value) { return set_attribute(name, value.GetValue()); } XMLConfigParser& set_attribute(const char* name, const std::string& value) { return set_attribute(name, value.c_str()); } bool valid() const { if (m_is_root) return m_document != nullptr; return m_document != nullptr && m_current_element != nullptr; } private: tinyxml2::XMLDocument* m_document; tinyxml2::XMLElement* m_current_element; bool m_is_root; }; using ChildXMLConfigParser = std::pair, std::function>; template concept XMLConfigurable = requires(T t, XMLConfigParser& configParser) { { t.Save(configParser) } -> std::same_as; { t.Load(configParser) } -> std::same_as; }; template class XMLConfig { public: XMLConfig() = delete; XMLConfig(T& instance) : m_instance(instance) {} XMLConfig(T& instance, std::wstring_view filename) : m_instance(instance), m_filename(filename) {} XMLConfig(const XMLConfig&) = delete; virtual ~XMLConfig() = default; bool Load() { if (m_filename.empty()) return false; return Load(m_filename); } bool Load(const std::wstring& filename) { FileStream* fs = FileStream::openFile(filename.c_str()); if (!fs) { cemuLog_logDebug(LogType::Force, "XMLConfig::Load > failed \"{}\" with error {}", boost::nowide::narrow(filename), errno); return false; } std::vector xmlData; xmlData.resize(fs->GetSize()); fs->readData(xmlData.data(), xmlData.size()); delete fs; tinyxml2::XMLDocument doc; const tinyxml2::XMLError error = doc.Parse((const char*)xmlData.data(), xmlData.size()); const bool success = error == tinyxml2::XML_SUCCESS; if (error != 0) { cemuLog_logDebug(LogType::Force, "XMLConfig::Load > LoadFile {}", error); } if (!success) { return false; } auto parser = XMLConfigParser(&doc); auto parentParser = m_instance.Load(parser); for (auto [save, load] : m_childConfigParsers) load(parentParser); return true; } bool Save() { if (m_filename.empty()) return false; return Save(m_filename); } bool Save(const std::wstring& filename) { std::wstring tmp_name = fmt::format(L"{}_{}.tmp", filename,rand() % 1000); std::error_code err; fs::create_directories(fs::path(filename).parent_path(), err); if (err) { cemuLog_log(LogType::Force, "can't create parent path for save file: {}", err.message()); return false; } FILE* file = nullptr; #if BOOST_OS_WINDOWS file = _wfopen(tmp_name.c_str(), L"wb"); #else file = fopen(boost::nowide::narrow(tmp_name).c_str(), "wb"); #endif if (!file) { cemuLog_logDebug(LogType::Force, "XMLConfig::Save > failed \"{}\" with error {}", boost::nowide::narrow(filename), errno); return false; } tinyxml2::XMLDocument doc; const auto declaration = doc.NewDeclaration(); doc.InsertFirstChild(declaration); auto parser = XMLConfigParser(&doc); auto parentParser = m_instance.Save(parser); for (auto [save, load] : m_childConfigParsers) save(parentParser); const tinyxml2::XMLError error = doc.SaveFile(file); const bool success = error == tinyxml2::XML_SUCCESS; if(error != 0) cemuLog_logDebug(LogType::Force, "XMLConfig::Save > SaveFile {}", error); fflush(file); fclose(file); fs::rename(tmp_name, filename, err); if(err) { cemuLog_log(LogType::Force, "Unable to save settings to file: {}", err.message().c_str()); fs::remove(tmp_name, err); } return success; } [[nodiscard]] const std::wstring& GetFilename() const { return m_filename; } void SetFilename(const std::wstring& filename) { m_filename = filename; } std::unique_lock Lock() { return std::unique_lock(m_mutex); } void AddChildConfig(ChildXMLConfigParser childConfigParser) { m_childConfigParsers.push_back(childConfigParser); } private: std::vector m_childConfigParsers; T& m_instance; std::wstring m_filename; std::mutex m_mutex; }; template concept XMLConfigProvider = requires(T t, ChildXMLConfigParser& configParser) { { t().Save() } -> std::same_as; { t().Load() } -> std::same_as; { t().Lock() } -> std::same_as>; { t().AddChildConfig(configParser) } -> std::same_as; }; template class XMLChildConfig { public: XMLChildConfig(XMLConfigProvider auto getParentConfig) { m_parentConfig = { .lock = [getParentConfig]() { return getParentConfig().Lock(); }, .save = [getParentConfig]() { return getParentConfig().Save(); }, .load = [getParentConfig]() { return getParentConfig().Load(); }, }; auto configParser = std::make_pair( [this](XMLConfigParser& parser) { (m_data.*S)(parser); }, [this](XMLConfigParser& parser) { (m_data.*L)(parser); }); getParentConfig().AddChildConfig(configParser); } bool Save() { return m_parentConfig.save(); } bool Load() { return m_parentConfig.load(); } T& Data() { return m_data; } std::unique_lock Lock() { return m_parentConfig.lock(); } virtual ~XMLChildConfig() = default; private: T m_data; struct { std::function()> lock; std::function save; std::function load; } m_parentConfig; }; template struct XMLDataConfig {}; template class XMLDataConfig : public XMLConfig { public: XMLDataConfig() : XMLConfig::XMLConfig(m_data), m_data() {} XMLDataConfig(std::wstring_view filename) : XMLConfig::XMLConfig(m_data, filename), m_data() {} XMLDataConfig(std::wstring_view filename, T init_data) : XMLConfig::XMLConfig(m_data, filename), m_data(std::move(init_data)) {} XMLDataConfig(const XMLDataConfig& o) = delete; T& data() { return m_data; } private: T m_data; }; template concept XMLStandaloneConfigurable = requires(T t, XMLConfigParser& configParser) { { t.Save(configParser) } -> std::same_as; { t.Load(configParser) } -> std::same_as; }; template struct XMLConfigWrapper { XMLConfigParser Save(XMLConfigParser& configParser) { data.Save(configParser); return configParser; } XMLConfigParser Load(XMLConfigParser& configParser) { data.Load(configParser); return configParser; } T data; }; template class XMLDataConfig : public XMLConfig> { public: XMLDataConfig() : XMLConfig>::XMLConfig(m_configWrapper), m_configWrapper() {} XMLDataConfig(std::wstring_view filename) : XMLConfig>::XMLConfig(m_configWrapper, filename), m_configWrapper() {} XMLDataConfig(std::wstring_view filename, T init_data) : XMLConfig>::XMLConfig(m_configWrapper, filename), m_configWrapper{.data = std::move(init_data)} {} XMLDataConfig(const XMLDataConfig& o) = delete; T& data() { return m_configWrapper.data; } private: XMLConfigWrapper m_configWrapper; }; ================================================ FILE: src/gui/CMakeLists.txt ================================================ add_library(CemuGui INTERFACE) target_include_directories(CemuGui INTERFACE "interface") if(ENABLE_WXWIDGETS) add_subdirectory(wxgui) target_link_libraries(CemuGui INTERFACE CemuWxGui) endif() ================================================ FILE: src/gui/interface/WindowSystem.h ================================================ #pragma once #include "config/CemuConfig.h" #include "input/api/ControllerState.h" namespace WindowSystem { struct WindowHandleInfo { enum class Backend { X11, Wayland, Cocoa, Windows, } backend; void* display = nullptr; void* surface = nullptr; }; enum struct PlatformKeyCodes : uint32 { LCONTROL, RCONTROL, TAB, ESCAPE, }; struct WindowInfo { std::atomic_bool app_active; // our app is active/has focus std::atomic_int32_t width, height; // client size of main window std::atomic_int32_t phys_width, phys_height; // client size of main window in physical pixels std::atomic dpi_scale; std::atomic_bool pad_open; // if separate pad view is open std::atomic_int32_t pad_width, pad_height; // client size of pad window std::atomic_int32_t phys_pad_width, phys_pad_height; // client size of pad window in physical pixels std::atomic pad_dpi_scale; std::atomic_bool pad_maximized = false; std::atomic_int32_t restored_pad_x = -1, restored_pad_y = -1; std::atomic_int32_t restored_pad_width = -1, restored_pad_height = -1; std::atomic_bool is_fullscreen; std::atomic_bool debugger_focused; void set_keystate(uint32 keycode, bool state) { const std::lock_guard lock(keycode_mutex); m_keydown[keycode] = state; } bool get_keystate(uint32 keycode) { const std::lock_guard lock(keycode_mutex); auto result = m_keydown.find(keycode); if (result == m_keydown.end()) return false; return result->second; } void set_keystatesup() { const std::lock_guard lock(keycode_mutex); std::for_each(m_keydown.begin(), m_keydown.end(), [](std::pair& el) { el.second = false; }); } template void iter_keystates(fn f) { const std::lock_guard lock(keycode_mutex); std::for_each(m_keydown.cbegin(), m_keydown.cend(), f); } WindowHandleInfo window_main; WindowHandleInfo window_pad; // canvas WindowHandleInfo canvas_main; WindowHandleInfo canvas_pad; private: std::mutex keycode_mutex; std::unordered_map m_keydown; }; enum class ErrorCategory { KEYS_TXT_CREATION = 0, GRAPHIC_PACKS = 1, }; void ShowErrorDialog(std::string_view message, std::string_view title, std::optional errorCategory = {}); inline void ShowErrorDialog(std::string_view message, std::optional errorCategory = {}) { ShowErrorDialog(message, "", errorCategory); } void Create(); WindowInfo& GetWindowInfo(); void UpdateWindowTitles(bool isIdle, bool isLoading, double fps); void GetWindowSize(int& w, int& h); void GetPadWindowSize(int& w, int& h); void GetWindowPhysSize(int& w, int& h); void GetPadWindowPhysSize(int& w, int& h); double GetWindowDPIScale(); double GetPadDPIScale(); bool IsPadWindowOpen(); bool IsKeyDown(uint32 key); bool IsKeyDown(PlatformKeyCodes key); std::string GetKeyCodeName(uint32 key); bool InputConfigWindowHasFocus(); void NotifyGameLoaded(); void NotifyGameExited(); void RefreshGameList(); bool IsFullScreen(); void CaptureInput(const ControllerState& currentState, const ControllerState& lastState); }; // namespace WindowSystem ================================================ FILE: src/gui/wxgui/AudioDebuggerWindow.cpp ================================================ #include "wxgui.h" #include "AudioDebuggerWindow.h" #include "Cafe/OS/libs/snd_core/ax.h" #include "Cafe/OS/libs/snd_core/ax_internal.h" enum { // options REFRESH_ID, CLOSE_ID, VOICELIST_ID, REFRESH_TIMER_ID, }; wxBEGIN_EVENT_TABLE(AudioDebuggerWindow, wxFrame) EVT_BUTTON(CLOSE_ID, AudioDebuggerWindow::OnCloseButton) EVT_BUTTON(REFRESH_ID, AudioDebuggerWindow::OnRefreshButton) EVT_TIMER(REFRESH_TIMER_ID, AudioDebuggerWindow::OnRefreshTimer) EVT_CLOSE(AudioDebuggerWindow::OnClose) wxEND_EVENT_TABLE() AudioDebuggerWindow::AudioDebuggerWindow(wxFrame& parent) : wxFrame(&parent, wxID_ANY, _("AX voice viewer"), wxDefaultPosition, wxSize(1126, 580), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER) { wxPanel* mainPane = new wxPanel(this); wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); voiceListbox = new wxListCtrl(mainPane, VOICELIST_ID, wxPoint(0, 0), wxSize(1126, 570), wxLC_REPORT); voiceListbox->SetFont(wxFont(8, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Courier New")); // add columns wxListItem col0; col0.SetId(0); col0.SetText("idx"); col0.SetWidth(40); voiceListbox->InsertColumn(0, col0); wxListItem col1; col1.SetId(1); col1.SetText("state"); col1.SetWidth(48); voiceListbox->InsertColumn(1, col1); //wxListItem col2; // format col1.SetId(2); col1.SetText("fmt"); col1.SetWidth(52); voiceListbox->InsertColumn(2, col1); // sample base addr col1.SetId(3); col1.SetText("base"); col1.SetWidth(70); voiceListbox->InsertColumn(3, col1); // current offset col1.SetId(4); col1.SetText("current"); col1.SetWidth(70); voiceListbox->InsertColumn(4, col1); // loop offset col1.SetId(5); col1.SetText("loop"); col1.SetWidth(70); voiceListbox->InsertColumn(5, col1); // end offset col1.SetId(6); col1.SetText("end"); col1.SetWidth(70); voiceListbox->InsertColumn(6, col1); // volume col1.SetId(7); col1.SetText("vol"); col1.SetWidth(46); voiceListbox->InsertColumn(7, col1); // volume delta col1.SetId(8); col1.SetText("volD"); col1.SetWidth(46); voiceListbox->InsertColumn(8, col1); // src col1.SetId(9); col1.SetText("src"); col1.SetWidth(70); voiceListbox->InsertColumn(9, col1); // low-pass filter coef a0 col1.SetId(10); col1.SetText("lpa0"); col1.SetWidth(46); voiceListbox->InsertColumn(10, col1); // low-pass filter coef b0 col1.SetId(11); col1.SetText("lpb0"); col1.SetWidth(46); voiceListbox->InsertColumn(11, col1); // biquad filter coef b0 col1.SetId(12); col1.SetText("bqb0"); col1.SetWidth(46); voiceListbox->InsertColumn(12, col1); // biquad filter coef b0 col1.SetId(13); col1.SetText("bqb1"); col1.SetWidth(46); voiceListbox->InsertColumn(13, col1); // biquad filter coef b0 col1.SetId(14); col1.SetText("bqb2"); col1.SetWidth(46); voiceListbox->InsertColumn(14, col1); // biquad filter coef a0 col1.SetId(15); col1.SetText("bqa1"); col1.SetWidth(46); voiceListbox->InsertColumn(15, col1); // biquad filter coef a1 col1.SetId(16); col1.SetText("bqa2"); col1.SetWidth(46); voiceListbox->InsertColumn(16, col1); // device mix col1.SetId(17); col1.SetText("deviceMix"); col1.SetWidth(186); voiceListbox->InsertColumn(17, col1); sizer->Add(voiceListbox, 1, wxEXPAND | wxBOTTOM, 0); voiceListbox->Bind(wxEVT_RIGHT_DOWN, &AudioDebuggerWindow::OnVoiceListRightClick, this); mainPane->SetSizer(sizer); // add empty entries for (sint32 i = 0; i < snd_core::AX_MAX_VOICES; i++) { wxListItem item; item.SetId(i); item.SetText(wxString::Format("%d", snd_core::AX_MAX_VOICES-i-1)); voiceListbox->InsertItem(item); } RefreshVoiceList(); // start refresh timer static const int INTERVAL = 100; // milliseconds refreshTimer = new wxTimer(this, REFRESH_TIMER_ID); refreshTimer->Start(INTERVAL); } void AudioDebuggerWindow::OnRefreshTimer(wxTimerEvent& event) { RefreshVoiceList(); } void AudioDebuggerWindow::OnCloseButton(wxCommandEvent& event) { Close(); } void AudioDebuggerWindow::OnRefreshButton(wxCommandEvent& event) { RefreshVoiceList(); } void AudioDebuggerWindow::OnClose(wxCloseEvent& event) { Close(); } #define _r(__idx) _swapEndianU32(threadItrBE->context.gpr[__idx]) void AudioDebuggerWindow::RefreshVoiceList_sndgeneric() { if (snd_core::__AXVPBInternalVoiceArray == nullptr || snd_core::__AXVPBArrayPtr == nullptr) return; snd_core::AXVPB tempVoiceArray[snd_core::AX_MAX_VOICES]; memcpy(tempVoiceArray, snd_core::__AXVPBArrayPtr, sizeof(snd_core::AXVPB)*snd_core::AX_MAX_VOICES); voiceListbox->Freeze(); for (sint32 i = 0; i < snd_core::AX_MAX_VOICES; i++) { sint32 voiceIndex = snd_core::AX_MAX_VOICES - 1 - i; snd_core::AXVPBInternal_t* internal = snd_core::__AXVPBInternalVoiceArray + voiceIndex; // index voiceListbox->SetItem(i, 0, wxString::Format("%d", (sint32)tempVoiceArray[voiceIndex].index)); // state uint16 playbackState = _swapEndianU16(internal->playbackState); voiceListbox->SetItem(i, 1, playbackState ? "on" : "off"); // if voice index is invalid then stop updating here to prevent crashes if (voiceIndex < 0 || playbackState == 0) { voiceListbox->SetItem(i, 0, ""); voiceListbox->SetItem(i, 1, ""); voiceListbox->SetItem(i, 2, ""); voiceListbox->SetItem(i, 3, ""); voiceListbox->SetItem(i, 4, ""); voiceListbox->SetItem(i, 5, ""); voiceListbox->SetItem(i, 6, ""); voiceListbox->SetItem(i, 7, ""); voiceListbox->SetItem(i, 8, ""); voiceListbox->SetItem(i, 9, ""); voiceListbox->SetItem(i, 10, ""); voiceListbox->SetItem(i, 11, ""); voiceListbox->SetItem(i, 12, ""); voiceListbox->SetItem(i, 13, ""); voiceListbox->SetItem(i, 14, ""); voiceListbox->SetItem(i, 15, ""); voiceListbox->SetItem(i, 16, ""); voiceListbox->SetItem(i, 17, ""); continue; } // format wxString formatLabel; if (tempVoiceArray[voiceIndex].offsets.format == _swapEndianU16(snd_core::AX_FORMAT_ADPCM)) formatLabel = "adpcm"; else if (tempVoiceArray[voiceIndex].offsets.format == _swapEndianU16(snd_core::AX_FORMAT_PCM16)) formatLabel = "pcm16"; else if (tempVoiceArray[voiceIndex].offsets.format == _swapEndianU16(snd_core::AX_FORMAT_PCM8)) formatLabel = "pcm8"; else formatLabel = "ukn"; voiceListbox->SetItem(i, 2, formatLabel); // update offsets snd_core::AXPBOFFSET_t tempOffsets; snd_core::AXGetVoiceOffsets(tempVoiceArray + voiceIndex, &tempOffsets); // sample base voiceListbox->SetItem(i, 3, wxString::Format("%08x", _swapEndianU32(tempOffsets.samples))); // current offset voiceListbox->SetItem(i, 4, wxString::Format("%08x", _swapEndianU32(tempOffsets.currentOffset))); // loop offset voiceListbox->SetItem(i, 5, tempOffsets.loopFlag ? wxString::Format("%08x", _swapEndianU32(tempOffsets.loopOffset)) : ""); // end offset voiceListbox->SetItem(i, 6, wxString::Format("%08x", _swapEndianU32(tempOffsets.endOffset))); // volume voiceListbox->SetItem(i, 7, wxString::Format("%04x", (uint16)internal->veVolume)); // volume delta voiceListbox->SetItem(i, 8, wxString::Format("%04x", (uint16)internal->veDelta)); // src voiceListbox->SetItem(i, 9, wxString::Format("%04x%04x", _swapEndianU16(internal->src.ratioHigh), _swapEndianU16(internal->src.ratioLow))); // lpf if (internal->lpf.on) { voiceListbox->SetItem(i, 10, wxString::Format("%04x", _swapEndianU16(internal->lpf.a0))); voiceListbox->SetItem(i, 11, wxString::Format("%04x", _swapEndianU16(internal->lpf.b0))); } else { voiceListbox->SetItem(i, 10, ""); voiceListbox->SetItem(i, 11, ""); } // biquad if (internal->biquad.on) { voiceListbox->SetItem(i, 12, wxString::Format("%04x", _swapEndianU16(internal->biquad.b0))); voiceListbox->SetItem(i, 13, wxString::Format("%04x", _swapEndianU16(internal->biquad.b1))); voiceListbox->SetItem(i, 14, wxString::Format("%04x", _swapEndianU16(internal->biquad.b2))); voiceListbox->SetItem(i, 15, wxString::Format("%04x", _swapEndianU16(internal->biquad.a1))); voiceListbox->SetItem(i, 16, wxString::Format("%04x", _swapEndianU16(internal->biquad.a2))); } else { voiceListbox->SetItem(i, 12, ""); voiceListbox->SetItem(i, 13, ""); voiceListbox->SetItem(i, 14, ""); voiceListbox->SetItem(i, 15, ""); voiceListbox->SetItem(i, 16, ""); } wxString label; // device mix for (uint32 f = 0; f < snd_core::AX_TV_CHANNEL_COUNT*snd_core::AX_MAX_NUM_BUS; f++) { sint32 busIndex = f% snd_core::AX_MAX_NUM_BUS; sint32 channelIndex = f / snd_core::AX_MAX_NUM_BUS; //debug_printf("DeviceMix TV Voice %08x b%02d/c%02d vol %04x delta %04x\n", hCPU->gpr[3], busIndex, channelIndex, _swapEndianU16(mixArrayBE[f].vol), _swapEndianU16(mixArrayBE[f].volDelta)); uint32 mixVol = internal->deviceMixTV[channelIndex * 4 + busIndex].vol; mixVol = (mixVol + 0x0FFF) >> (12); label += wxString::Format("%x", mixVol); //ax.voiceInternal[voiceIndex].deviceMixTVChannel[channelIndex].bus[busIndex].vol = _swapEndianU16(mixArrayBE[f].vol); } voiceListbox->SetItem(i, 17, label); } voiceListbox->Thaw(); } void AudioDebuggerWindow::RefreshVoiceList() { RefreshVoiceList_sndgeneric(); } void AudioDebuggerWindow::OnVoiceListPopupClick(wxCommandEvent &evt) { } void AudioDebuggerWindow::OnVoiceListRightClick(wxMouseEvent& event) { } void AudioDebuggerWindow::Close() { // this->MakeModal(false); refreshTimer->Stop(); this->Destroy(); } ================================================ FILE: src/gui/wxgui/AudioDebuggerWindow.h ================================================ #pragma once #include class wxListCtrl; class AudioDebuggerWindow : public wxFrame { public: AudioDebuggerWindow(wxFrame& parent); void OnCloseButton(wxCommandEvent& event); void OnRefreshButton(wxCommandEvent& event); void OnClose(wxCloseEvent& event); void RefreshVoiceList_sndgeneric(); void RefreshVoiceList(); void OnRefreshTimer(wxTimerEvent& event); void OnVoiceListPopupClick(wxCommandEvent &evt); void OnVoiceListRightClick(wxMouseEvent& event); void Close(); private: wxListCtrl* voiceListbox; wxTimer* refreshTimer; wxDECLARE_EVENT_TABLE(); }; ================================================ FILE: src/gui/wxgui/CMakeLists.txt ================================================ add_library(CemuWxGui STATIC canvas/IRenderCanvas.h canvas/OpenGLCanvas.cpp canvas/OpenGLCanvas.h canvas/VulkanCanvas.cpp canvas/VulkanCanvas.h CemuApp.cpp CemuApp.h CemuUpdateWindow.cpp CemuUpdateWindow.h ChecksumTool.cpp ChecksumTool.h components/TextList.cpp components/TextList.h components/wxDownloadManagerList.cpp components/wxDownloadManagerList.h components/wxGameList.cpp components/wxGameList.h components/wxInputDraw.cpp components/wxInputDraw.h components/wxLogCtrl.cpp components/wxLogCtrl.h components/wxTitleManagerList.cpp components/wxTitleManagerList.h debugger/BreakpointWindow.cpp debugger/BreakpointWindow.h debugger/DebuggerWindow2.cpp debugger/DebuggerWindow2.h debugger/DisasmCtrl.cpp debugger/DisasmCtrl.h debugger/DumpCtrl.cpp debugger/DumpCtrl.h debugger/DumpWindow.cpp debugger/DumpWindow.h debugger/ModuleWindow.cpp debugger/ModuleWindow.h debugger/RegisterWindow.cpp debugger/RegisterWindow.h debugger/SymbolCtrl.cpp debugger/SymbolCtrl.h debugger/SymbolWindow.cpp debugger/SymbolWindow.h dialogs/CreateAccount/wxCreateAccountDialog.cpp dialogs/CreateAccount/wxCreateAccountDialog.h dialogs/SaveImport/SaveImportWindow.cpp dialogs/SaveImport/SaveImportWindow.h dialogs/SaveImport/SaveTransfer.cpp dialogs/SaveImport/SaveTransfer.h AudioDebuggerWindow.cpp AudioDebuggerWindow.h DownloadGraphicPacksWindow.cpp DownloadGraphicPacksWindow.h GameProfileWindow.cpp GameProfileWindow.h GameUpdateWindow.cpp GameUpdateWindow.h GeneralSettings2.cpp GeneralSettings2.h GettingStartedDialog.cpp GettingStartedDialog.h GraphicPacksWindow2.cpp GraphicPacksWindow2.h wxWindowSystem.cpp helpers/wxControlObject.h helpers/wxCustomData.h helpers/wxCustomEvents.cpp helpers/wxCustomEvents.h helpers/wxHelpers.cpp helpers/wxHelpers.h helpers/wxLogEvent.h helpers/wxWayland.cpp helpers/wxWayland.h input/HotkeySettings.cpp input/HotkeySettings.h input/InputAPIAddWindow.cpp input/InputAPIAddWindow.h input/InputSettings2.cpp input/InputSettings2.h input/panels/ClassicControllerInputPanel.cpp input/panels/ClassicControllerInputPanel.h input/panels/InputPanel.cpp input/panels/InputPanel.h input/panels/ProControllerInputPanel.cpp input/panels/ProControllerInputPanel.h input/panels/VPADInputPanel.cpp input/panels/VPADInputPanel.h input/panels/WiimoteInputPanel.cpp input/panels/WiimoteInputPanel.h input/settings/DefaultControllerSettings.cpp input/settings/DefaultControllerSettings.h input/settings/WiimoteControllerSettings.cpp input/settings/WiimoteControllerSettings.h LoggingWindow.cpp LoggingWindow.h MainWindow.cpp MainWindow.h MemorySearcherTool.cpp MemorySearcherTool.h PadViewFrame.cpp PadViewFrame.h TitleManager.cpp TitleManager.h wxCemuConfig.cpp wxCemuConfig.h EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp EmulatedUSBDevices/EmulatedUSBDeviceFrame.h windows/PPCThreadsViewer windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp windows/PPCThreadsViewer/DebugPPCThreadsWindow.h windows/TextureRelationViewer windows/TextureRelationViewer/TextureRelationWindow.cpp windows/TextureRelationViewer/TextureRelationWindow.h wxcomponents/checktree.cpp wxcomponents/checktree.h wxgui.h wxHelper.h ) if (ENABLE_METAL) target_sources(CemuWxGui PRIVATE canvas/MetalCanvas.cpp canvas/MetalCanvas.h ) endif() if (ENABLE_BLUEZ) target_sources(CemuWxGui PRIVATE input/PairingDialog.cpp input/PairingDialog.h ) endif() set_property(TARGET CemuWxGui PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") target_include_directories(CemuWxGui PUBLIC "../") # PUBLIC because rapidjson/document.h is included in ChecksumTool.h target_include_directories(CemuWxGui PUBLIC ${RAPIDJSON_INCLUDE_DIRS}) target_link_libraries(CemuWxGui PRIVATE CemuCommon CemuResource libzip::zip ZArchive::zarchive CemuComponents SDL2::SDL2 pugixml::pugixml CemuCafe PUBLIC CURL::libcurl ) if(ENABLE_WXWIDGETS AND UNIX AND NOT APPLE) # PUBLIC because gdk/gdkkeysyms.h is included in guiWrapper.h target_link_libraries(CemuWxGui PUBLIC GTK3::gtk) if (ENABLE_WAYLAND) target_link_libraries(CemuWxGui PRIVATE Wayland::Client CemuWaylandProtocols) endif() endif() if(ENABLE_CUBEB) target_link_libraries(CemuWxGui PRIVATE cubeb::cubeb) endif() if(UNIX AND NOT APPLE) if(ENABLE_FERAL_GAMEMODE) target_link_libraries(CemuWxGui PRIVATE gamemode) endif() endif() if (ENABLE_WXWIDGETS) # PUBLIC because wx/app.h is included in CemuApp.h target_link_libraries(CemuWxGui PUBLIC wxWidgets::wxWidgets) endif() if(WIN32) target_link_libraries(CemuWxGui PRIVATE bthprops) endif() if(ALLOW_PORTABLE) target_compile_definitions(CemuWxGui PRIVATE CEMU_ALLOW_PORTABLE) endif () ================================================ FILE: src/gui/wxgui/CemuApp.cpp ================================================ #include "wxgui/CemuApp.h" #include "wxCemuConfig.h" #include "wxgui/MainWindow.h" #include "wxgui/wxgui.h" #include "config/CemuConfig.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" #include "wxgui/GettingStartedDialog.h" #include "input/InputManager.h" #include "wxgui/helpers/wxHelpers.h" #include "Cemu/ncrypto/ncrypto.h" #include "wxgui/input/HotkeySettings.h" #include "wxgui/debugger/DebuggerWindow2.h" #include #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND #include "wxgui/helpers/wxWayland.h" #endif #if __WXGTK__ #include #endif #include #include #include #include #include "wxHelper.h" #include "Cafe/TitleList/TitleList.h" #include "Cafe/TitleList/SaveList.h" wxIMPLEMENT_APP_NO_MAIN(CemuApp); // defined in wxWindowSystem.cpp extern WindowSystem::WindowInfo g_window_info; extern std::shared_mutex g_mutex; // forward declarations from main.cpp void UnitTests(); void CemuCommonInit(); void HandlePostUpdate(); // Translation strings to extract for gettext: void unused_translation_dummy() { void(_("Browse")); void(_("Select a file")); void(_("Select a directory")); void(_("Japanese")); void(_("English")); void(_("French")); void(_("German")); void(_("Italian")); void(_("Spanish")); void(_("Chinese")); void(_("Korean")); void(_("Dutch")); void(_("Portugese")); void(_("Russian")); void(_("Taiwanese")); void(_("unknown")); } #if BOOST_OS_WINDOWS #include fs::path GetAppDataRoamingPath() { PWSTR path = nullptr; HRESULT result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &path); if (FAILED(result)) { CoTaskMemFree(path); return {}; } std::string appDataPath = boost::nowide::narrow(path); CoTaskMemFree(path); return _utf8ToPath(appDataPath); } #endif #if BOOST_OS_WINDOWS void CemuApp::DeterminePaths(std::set& failedWriteAccess) // for Windows { std::error_code ec; bool isPortable = false; fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); fs::path portablePath = exePath.parent_path() / "portable"; data_path = exePath.parent_path(); // the data path is always the same as the exe path #ifdef CEMU_ALLOW_PORTABLE if (fs::is_directory(portablePath, ec)) { isPortable = true; user_data_path = config_path = cache_path = portablePath; } else #endif { fs::path roamingPath = GetAppDataRoamingPath() / "Cemu"; user_data_path = config_path = cache_path = roamingPath; } // on Windows Cemu used to be portable by default prior to 2.0-89 // to remain backwards compatible with old installations we check for settings.xml in the Cemu directory // if it exists, we use the exe path as the portable directory if(!isPortable) // lower priority than portable directory { if (fs::exists(exePath.parent_path() / "settings.xml", ec)) { isPortable = true; user_data_path = config_path = cache_path = exePath.parent_path(); } } ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess); } #endif #if BOOST_OS_LINUX || BOOST_OS_BSD void CemuApp::DeterminePaths(std::set& failedWriteAccess) // for Linux { std::error_code ec; bool isPortable = false; fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); fs::path portablePath = exePath.parent_path() / "portable"; // GetExecutablePath returns the AppImage's temporary mount location wxString appImagePath; if (wxGetEnv("APPIMAGE", &appImagePath)) { exePath = wxHelper::MakeFSPath(appImagePath); portablePath = exePath.parent_path() / "portable"; } #ifdef CEMU_ALLOW_PORTABLE if (fs::is_directory(portablePath, ec)) { isPortable = true; user_data_path = config_path = cache_path = portablePath; // in portable mode assume the data directories (resources, gameProfiles/default/) are next to the executable data_path = exePath.parent_path(); } else #endif { SetAppName("Cemu"); wxString appName = GetAppName(); standardPaths.SetFileLayout(wxStandardPaths::FileLayout::FileLayout_XDG); auto getEnvDir = [&](const wxString& varName, const wxString& defaultValue) { wxString dir; if (!wxGetEnv(varName, &dir) || dir.empty()) return defaultValue; return dir; }; wxString homeDir = wxFileName::GetHomeDir(); user_data_path = (getEnvDir(wxS("XDG_DATA_HOME"), homeDir + wxS("/.local/share")) + "/" + appName).ToStdString(); config_path = (getEnvDir(wxS("XDG_CONFIG_HOME"), homeDir + wxS("/.config")) + "/" + appName).ToStdString(); data_path = standardPaths.GetDataDir().ToStdString(); cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString(); cache_path /= appName.ToStdString(); } ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess); } #endif #if BOOST_OS_MACOS void CemuApp::DeterminePaths(std::set& failedWriteAccess) // for MacOS { std::error_code ec; bool isPortable = false; fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); // If run from an app bundle, use its parent directory fs::path appPath = exePath.parent_path().parent_path().parent_path(); fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable"; #ifdef CEMU_ALLOW_PORTABLE if (fs::is_directory(portablePath, ec)) { isPortable = true; user_data_path = config_path = cache_path = portablePath; data_path = exePath.parent_path(); } else #endif { SetAppName("Cemu"); wxString appName = GetAppName(); user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString(); data_path = standardPaths.GetDataDir().ToStdString(); cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString(); cache_path /= appName.ToStdString(); } ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess); } #endif // create default MLC files or quit if it fails void CemuApp::InitializeNewMLCOrFail(fs::path mlc) { if( CemuApp::CreateDefaultMLCFiles(mlc) ) return; // all good cemu_assert_debug(!ActiveSettings::IsCustomMlcPath()); // should not be possible? if(ActiveSettings::IsCommandLineMlcPath() || ActiveSettings::IsCustomMlcPath()) { // tell user that the custom path is not writable wxMessageBox(formatWxString(_("Cemu failed to write to the custom mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); exit(0); } wxMessageBox(formatWxString(_("Cemu failed to write to the mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); exit(0); } void CemuApp::InitializeExistingMLCOrFail(fs::path mlc) { if(CreateDefaultMLCFiles(mlc)) return; // all good // failed to write mlc files if(ActiveSettings::IsCommandLineMlcPath() || ActiveSettings::IsCustomMlcPath()) { // tell user that the custom path is not writable // if it's a command line path then just quit. Otherwise ask if user wants to reset the path if(ActiveSettings::IsCommandLineMlcPath()) { wxMessageBox(formatWxString(_("Cemu failed to write to the custom mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); exit(0); } // ask user if they want to reset the path const wxString message = formatWxString(_("Cemu failed to write to the custom mlc directory.\n\nThe path is:\n{}\n\nCemu cannot start without a valid mlc path. Do you want to reset the path? You can later change it again in the General Settings."), _pathToUtf8(mlc)); wxMessageDialog dialog(nullptr, message, _("Error"), wxCENTRE | wxYES_NO | wxICON_WARNING); dialog.SetYesNoLabels(_("Reset path"), _("Exit")); const auto dialogResult = dialog.ShowModal(); if (dialogResult == wxID_NO) exit(0); else // reset path { GetConfig().mlc_path = ""; GetConfigHandle().Save(); } } else { // default path is not writeable. Just let the user know and quit. Unsure if it would be a good idea to ask the user to choose an alternative path instead wxMessageBox(formatWxString(_("Cemu failed to write to the default mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); exit(0); } } std::string TranslationCallback(std::string_view msgId) { return wxGetTranslation(wxString::FromUTF8(msgId)).utf8_string(); } bool CemuApp::OnInit() { #if __WXGTK__ GTKSuppressDiagnostics(G_LOG_LEVEL_MASK & ~G_LOG_FLAG_FATAL); #endif std::set failedWriteAccess; DeterminePaths(failedWriteAccess); // make sure default cemu directories exist CreateDefaultCemuFiles(); GetConfigHandle().SetFilename(ActiveSettings::GetConfigPath("settings.xml").generic_wstring()); auto& config = GetWxGUIConfig(); std::error_code ec; bool isFirstStart = !fs::exists(ActiveSettings::GetConfigPath("settings.xml"), ec); NetworkConfig::LoadOnce(); if (!isFirstStart) { GetConfigHandle().Load(); sint32 language = config.language.GetValue(); LocalizeUI(static_cast(language == wxLANGUAGE_DEFAULT ? wxLocale::GetSystemLanguage() : language)); } else { LocalizeUI(static_cast(wxLocale::GetSystemLanguage())); } SetTranslationCallback(TranslationCallback); #if __WXMSW__ auto& wxGuiConfig = GetWxGUIConfig(); if (wxGuiConfig.msw_theme.GetValue() == static_cast(MSWThemeOption::kAuto)) { MSWEnableDarkMode(DarkMode_Auto); } else if (wxGuiConfig.msw_theme.GetValue() == static_cast(MSWThemeOption::kDark)) { MSWEnableDarkMode(DarkMode_Always); } // extend tooltip duration to the maximum possible value wxToolTip::SetDelay(-1); wxToolTip::SetAutoPop(MAKELPARAM(std::numeric_limits::max(),0)); #endif for (auto&& path : failedWriteAccess) { wxMessageBox(formatWxString(_("Cemu can't write to {}!"), wxHelper::FromPath(path)), _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr); } if (isFirstStart) { // show the getting started dialog GettingStartedDialog dia(nullptr); dia.ShowModal(); // make sure config is created. Gfx pack UI and input UI may create it earlier already, but we still want to update it GetConfigHandle().Save(); // create mlc, on failure the user can quit here. So do this after the Getting Started dialog InitializeNewMLCOrFail(ActiveSettings::GetMlcPath()); } else { // check if mlc is valid and recreate default files InitializeExistingMLCOrFail(ActiveSettings::GetMlcPath()); } ActiveSettings::Init(); // this is a bit of a misnomer, right now this call only loads certs for online play. In the future we should move the logic to a more appropriate place HandlePostUpdate(); LatteOverlay_init(); // run a couple of tests if in non-release mode #ifdef CEMU_DEBUG_ASSERT UnitTests(); #endif CemuCommonInit(); wxInitAllImageHandlers(); // fill colour db wxTheColourDatabase->AddColour("ERROR", wxColour(0xCC, 0, 0)); wxTheColourDatabase->AddColour("SUCCESS", wxColour(0, 0xbb, 0)); #if BOOST_OS_WINDOWS const auto parent_path = GetParentProcess(); if(parent_path.has_filename()) { const auto filename = parent_path.filename().generic_string(); if (boost::icontains(filename, "WiiU_USB_Helper")) __fastfail(0); } #endif InitializeGlobalVulkan(); Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this); m_mainFrame = new MainWindow(); std::unique_lock lock(g_mutex); g_window_info.app_active = true; HotkeySettings::Init(m_mainFrame); SetTopWindow(m_mainFrame); m_mainFrame->Show(); #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND if (wxWlIsWaylandWindow(m_mainFrame)) wxWlSetAppId(m_mainFrame, "info.cemu.Cemu"); #endif // show warning on macOS about state of builds #if BOOST_OS_MACOS if (!config.did_show_macos_disclaimer) { const auto message = _( "Thank you for testing the in-development build of Cemu for macOS.\n \n" "The macOS port is currently purely experimental and should not be considered stable or ready for issue-free gameplay. " "There are also known issues with degraded performance due to the use of MoltenVk and Rosetta for ARM Macs. We appreciate your patience while we improve Cemu for macOS."); wxMessageDialog dialog(nullptr, message, _("Preview version"), wxCENTRE | wxOK | wxICON_WARNING); dialog.SetOKLabel(_("I understand")); dialog.ShowModal(); config.did_show_macos_disclaimer = true; GetConfigHandle().Save(); } #endif return true; } int CemuApp::OnExit() { wxApp::OnExit(); wxTheClipboard->Flush(); #if BOOST_OS_WINDOWS ExitProcess(0); #else _Exit(0); #endif } #if BOOST_OS_WINDOWS void DumpThreadStackTrace(); #endif void CemuApp::OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg) { cemuLog_createLogFile(false); cemuLog_log(LogType::Force, "Encountered wxWidgets assert!"); cemuLog_log(LogType::Force, "File: {0} Line: {1}", wxString(file).utf8_string(), line); cemuLog_log(LogType::Force, "Func: {0} Cond: {1}", wxString(func).utf8_string(), wxString(cond).utf8_string()); cemuLog_log(LogType::Force, "Message: {}", wxString(msg).utf8_string()); #if BOOST_OS_WINDOWS DumpThreadStackTrace(); #endif cemu_assert_debug(false); } int CemuApp::FilterEvent(wxEvent& event) { if(event.GetEventType() == wxEVT_KEY_DOWN) { const auto& key_event = (wxKeyEvent&)event; g_window_info.set_keystate(fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags()), true); } else if(event.GetEventType() == wxEVT_KEY_UP) { const auto& key_event = (wxKeyEvent&)event; g_window_info.set_keystate(fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags()), false); } else if(event.GetEventType() == wxEVT_ACTIVATE_APP) { const auto& activate_event = (wxActivateEvent&)event; if(!activate_event.GetActive()) g_window_info.set_keystatesup(); } // track if debugger window or its child windows are focused if (g_debugger_window && (event.GetEventType() == wxEVT_SET_FOCUS || event.GetEventType() == wxEVT_ACTIVATE)) { wxWindow* target_window = wxDynamicCast(event.GetEventObject(), wxWindow); if (target_window && event.GetEventType() == wxEVT_ACTIVATE && !((wxActivateEvent&)event).GetActive()) target_window = nullptr; if (target_window) { g_window_info.debugger_focused = false; wxWindow* window_it = target_window; while (window_it) { if (window_it == g_debugger_window) g_window_info.debugger_focused = true; window_it = window_it->GetParent(); } } } else if (!g_debugger_window) { g_window_info.debugger_focused = false; } return wxApp::FilterEvent(event); } std::vector CemuApp::GetLanguages() const { std::vector availableLanguages(m_availableTranslations); availableLanguages.insert(availableLanguages.begin(), wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); return availableLanguages; } void CemuApp::LocalizeUI(wxLanguage languageToUse) { std::unique_ptr translationsMgr(new wxTranslations()); m_availableTranslations = GetAvailableTranslationLanguages(translationsMgr.get()); bool isTranslationAvailable = std::any_of(m_availableTranslations.begin(), m_availableTranslations.end(), [languageToUse](const wxLanguageInfo* info) { return info->Language == languageToUse; }); if (languageToUse == wxLANGUAGE_DEFAULT || isTranslationAvailable) { translationsMgr->SetLanguage(static_cast(languageToUse)); translationsMgr->AddCatalog("cemu"); if (translationsMgr->IsLoaded("cemu") && wxLocale::IsAvailable(languageToUse)) { m_locale.Init(languageToUse); } // This must be run after wxLocale::Init, as the latter sets up its own wxTranslations instance which we want to override wxTranslations::Set(translationsMgr.release()); } } std::vector CemuApp::GetAvailableTranslationLanguages(wxTranslations* translationsMgr) { wxFileTranslationsLoader::AddCatalogLookupPathPrefix(wxHelper::FromPath(ActiveSettings::GetDataPath("resources"))); std::vector languages; for (const auto& langName : translationsMgr->GetAvailableTranslations("cemu")) { const auto* langInfo = wxLocale::FindLanguageInfo(langName); if (langInfo) languages.emplace_back(langInfo); } return languages; } bool CemuApp::CheckMLCPath(const fs::path& mlc) { std::error_code ec; if (!fs::exists(mlc, ec)) return false; if (!fs::exists(mlc / "usr", ec) || !fs::exists(mlc / "sys", ec)) return false; return true; } bool CemuApp::CreateDefaultMLCFiles(const fs::path& mlc) { auto CreateDirectoriesIfNotExist = [](const fs::path& path) { std::error_code ec; if (!fs::exists(path, ec)) return fs::create_directories(path, ec); return true; }; // list of directories to create const fs::path directories[] = { mlc, mlc / "sys", mlc / "usr", mlc / "usr/title/00050000", // base mlc / "usr/title/0005000c", // dlc mlc / "usr/title/0005000e", // update mlc / "usr/save/00050010/1004a000/user/common/db", // Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200} mlc / "usr/save/00050010/1004a100/user/common/db", mlc / "usr/save/00050010/1004a200/user/common/db", mlc / "sys/title/0005001b/1005c000/content" // lang files }; for(auto& path : directories) { if(!CreateDirectoriesIfNotExist(path)) return false; } // create sys/usr folder in mlc01 try { const auto langDir = fs::path(mlc).append("sys/title/0005001b/1005c000/content"); auto langFile = fs::path(langDir).append("language.txt"); if (!fs::exists(langFile)) { std::ofstream file(langFile); if (file.is_open()) { const char* langStrings[] = { "ja","en","fr","de","it","es","zh","ko","nl","pt","ru","zh" }; for (const char* lang : langStrings) file << fmt::format(R"("{}",)", lang) << std::endl; file.flush(); file.close(); } } auto countryFile = fs::path(langDir).append("country.txt"); if (!fs::exists(countryFile)) { std::ofstream file(countryFile); for (sint32 i = 0; i < NCrypto::GetCountryCount(); i++) { const char* countryCode = NCrypto::GetCountryAsString(i); if (boost::iequals(countryCode, "NN")) file << "NULL," << std::endl; else file << fmt::format(R"("{}",)", countryCode) << std::endl; } file.flush(); file.close(); } // create a dummy file in the mlc folder to check if it's writable const auto dummyFile = fs::path(mlc).append("writetestdummy"); std::ofstream file(dummyFile); if (!file.is_open()) return false; file.close(); fs::remove(dummyFile); } catch (const std::exception& ex) { return false; } return true; } void CemuApp::CreateDefaultCemuFiles() { // cemu directories try { const auto controllerProfileFolder = ActiveSettings::GetConfigPath("controllerProfiles"); if (!fs::exists(controllerProfileFolder)) fs::create_directories(controllerProfileFolder); const auto memorySearcherFolder = ActiveSettings::GetUserDataPath("memorySearcher"); if (!fs::exists(memorySearcherFolder)) fs::create_directories(memorySearcherFolder); } catch (const std::exception& ex) { wxString errorMsg = formatWxString(_("Couldn't create a required cemu directory or file!\n\nError: {0}"), ex.what()); #if BOOST_OS_WINDOWS const DWORD lastError = GetLastError(); if (lastError != ERROR_SUCCESS) errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError)); #endif wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); exit(0); } } void CemuApp::ActivateApp(wxActivateEvent& event) { g_window_info.app_active = event.GetActive(); event.Skip(); } ================================================ FILE: src/gui/wxgui/CemuApp.h ================================================ #pragma once #include class MainWindow; class CemuApp : public wxApp { public: bool OnInit() override; int OnExit() override; void OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg) override; int FilterEvent(wxEvent& event) override; std::vector GetLanguages() const; static bool CheckMLCPath(const fs::path& mlc); static bool CreateDefaultMLCFiles(const fs::path& mlc); static void CreateDefaultCemuFiles(); static void InitializeNewMLCOrFail(fs::path mlc); static void InitializeExistingMLCOrFail(fs::path mlc); private: void LocalizeUI(wxLanguage languageToUse); void DeterminePaths(std::set& failedWriteAccess); void ActivateApp(wxActivateEvent& event); static std::vector GetAvailableTranslationLanguages(wxTranslations* translationsMgr); MainWindow* m_mainFrame = nullptr; wxLocale m_locale; std::vector m_availableTranslations; }; wxDECLARE_APP(CemuApp); ================================================ FILE: src/gui/wxgui/CemuUpdateWindow.cpp ================================================ #include "wxgui/CemuUpdateWindow.h" #include "Common/version.h" #include "util/helpers/helpers.h" #include "util/helpers/SystemException.h" #include "config/ActiveSettings.h" #include "Common/FileStream.h" #include "wxCemuConfig.h" #include #include #include #include #include #ifndef BOOST_OS_WINDOWS #include #include #endif #include #include #include wxDECLARE_EVENT(wxEVT_RESULT, wxCommandEvent); wxDEFINE_EVENT(wxEVT_RESULT, wxCommandEvent); wxDECLARE_EVENT(wxEVT_PROGRESS, wxCommandEvent); wxDEFINE_EVENT(wxEVT_PROGRESS, wxCommandEvent); CemuUpdateWindow::CemuUpdateWindow(wxWindow* parent) : wxDialog(parent, wxID_ANY, _("Cemu update"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX) { auto* sizer = new wxBoxSizer(wxVERTICAL); m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL); m_gauge->SetValue(0); sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5); auto* rows = new wxFlexGridSizer(0, 2, 0, 0); rows->AddGrowableCol(1); m_text = new wxStaticText(this, wxID_ANY, _("Checking for latest version...")); rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); { auto* right_side = new wxBoxSizer(wxHORIZONTAL); m_updateButton = new wxButton(this, wxID_ANY, _("Update")); m_updateButton->Bind(wxEVT_BUTTON, &CemuUpdateWindow::OnUpdateButton, this); right_side->Add(m_updateButton, 0, wxALL, 5); m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); m_cancelButton->Bind(wxEVT_BUTTON, &CemuUpdateWindow::OnCancelButton, this); right_side->Add(m_cancelButton, 0, wxALL, 5); rows->Add(right_side, 1, wxALIGN_RIGHT, 5); } m_changelog = new wxHyperlinkCtrl(this, wxID_ANY, _("Changelog"), wxEmptyString); rows->Add(m_changelog, 0, wxLEFT | wxBOTTOM | wxRIGHT | wxEXPAND, 5); sizer->Add(rows, 0, wxALL | wxEXPAND, 5); SetSizerAndFit(sizer); Centre(wxBOTH); Bind(wxEVT_CLOSE_WINDOW, &CemuUpdateWindow::OnClose, this); Bind(wxEVT_RESULT, &CemuUpdateWindow::OnResult, this); Bind(wxEVT_PROGRESS, &CemuUpdateWindow::OnGaugeUpdate, this); m_thread = std::thread(&CemuUpdateWindow::WorkerThread, this); m_updateButton->Hide(); m_changelog->Hide(); } CemuUpdateWindow::~CemuUpdateWindow() { m_order = WorkerOrder::Exit; if (m_thread.joinable()) m_thread.join(); } size_t CemuUpdateWindow::WriteStringCallback(char* ptr, size_t size, size_t nmemb, void* userdata) { ((std::string*)userdata)->append(ptr, size * nmemb); return size * nmemb; }; std::string _curlUrlEscape(CURL* curl, const std::string& input) { char* escapedStr = curl_easy_escape(curl, input.c_str(), input.size()); std::string r(escapedStr); curl_free(escapedStr); return r; } std::string _curlUrlUnescape(CURL* curl, std::string_view input) { int decodedLen = 0; const char* decoded = curl_easy_unescape(curl, input.data(), input.size(), &decodedLen); return std::string(decoded, decodedLen); } // returns true if update is available and sets output parameters bool CemuUpdateWindow::QueryUpdateInfo(std::string& downloadUrlOut, std::string& changelogUrlOut) { std::string buffer; std::string urlStr("https://cemu.info/api2/version.php?v="); auto* curl = curl_easy_init(); urlStr.append(_curlUrlEscape(curl, BUILD_VERSION_STRING)); #if BOOST_OS_LINUX || BOOST_OS_BSD // dummy placeholder on BSD for now urlStr.append("&platform=linux_appimage"); #elif BOOST_OS_WINDOWS urlStr.append("&platform=windows"); #elif BOOST_OS_MACOS urlStr.append("&platform=macos_bundle"); #else #error Name for current platform is missing #endif #if defined(__aarch64__) urlStr.append("_aarch64"); #elif defined(ARCH_X86_64) urlStr.append("_x86_64"); #else urlStr.append("_unknown"); #endif #if BOOST_OS_BSD return false; // BSD users must update from source; no binary available #endif const auto& config = GetWxGUIConfig(); if(config.receive_untested_updates) urlStr.append("&allowNewUpdates=1"); curl_easy_setopt(curl, CURLOPT_URL, urlStr.c_str()); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteStringCallback); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); bool result = false; CURLcode cr = curl_easy_perform(curl); if (cr == CURLE_OK) { long http_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); if (http_code != 0 && http_code != 200) { cemuLog_log(LogType::Force, "Update check failed (http code: {})", http_code); cemu_assert_debug(false); return false; } std::vector tokens; const boost::char_separator sep{ "|" }; for (const auto& token : boost::tokenizer(buffer, sep)) tokens.emplace_back(token); if (tokens.size() >= 3 && tokens[0] == "UPDATE") { // first token: "UPDATE" // second token: Download URL // third token: Changelog URL // we allow more tokens in case we ever want to add extra information for future releases downloadUrlOut = _curlUrlUnescape(curl, tokens[1]); changelogUrlOut = _curlUrlUnescape(curl, tokens[2]); if (!downloadUrlOut.empty() && !changelogUrlOut.empty()) result = true; } } else { cemuLog_log(LogType::Force, "Update check failed with CURL error {}", (int)cr); cemu_assert_debug(false); } curl_easy_cleanup(curl); return result; } std::future CemuUpdateWindow::IsUpdateAvailableAsync() { return std::async(std::launch::async, CheckVersion); } bool CemuUpdateWindow::CheckVersion() { std::string downloadUrl, changelogUrl; return QueryUpdateInfo(downloadUrl, changelogUrl); } int CemuUpdateWindow::ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { auto* thisptr = (CemuUpdateWindow*)clientp; auto* event = new wxCommandEvent(wxEVT_PROGRESS); event->SetInt((int)dlnow); wxQueueEvent(thisptr, event); return 0; } bool CemuUpdateWindow::DownloadCemuZip(const std::string& url, const fs::path& filename) { FileStream* fsUpdateFile = FileStream::createFile2(filename); if (!fsUpdateFile) return false; bool result = false; auto* curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_NOBODY, 1); curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); auto r = curl_easy_perform(curl); if (r == CURLE_OK) { long http_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); if (http_code != 0 && http_code != 200) { cemuLog_log(LogType::Force, "Unable to download cemu update zip file from {} (http error: {})", url, http_code); curl_easy_cleanup(curl); return false; } curl_off_t update_size; if (curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &update_size) == CURLE_OK) m_gaugeMaxValue = (int)update_size; auto _curlWriteData = +[](void* ptr, size_t size, size_t nmemb, void* ctx) -> size_t { FileStream* fs = (FileStream*)ctx; const size_t writeSize = size * nmemb; fs->writeData(ptr, writeSize); return writeSize; }; curl_easy_setopt(curl, CURLOPT_NOBODY, 0); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _curlWriteData); curl_easy_setopt(curl, CURLOPT_WRITEDATA, fsUpdateFile); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback); curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this); auto curl_result = std::async(std::launch::async, [](CURL* curl, long* http_code) { const auto r = curl_easy_perform(curl); curl_easy_cleanup(curl); return r; }, curl, &http_code); while (!curl_result.valid()) { if (m_order == WorkerOrder::Exit) return false; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } result = curl_result.get() == CURLE_OK; delete fsUpdateFile; } else { cemuLog_log(LogType::Force, "Cemu zip download failed with error {}", r); curl_easy_cleanup(curl); } if (!result && fs::exists(filename)) { try { fs::remove(filename); } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't remove update.zip on error: {}", ex.what()); } } return result; } bool CemuUpdateWindow::ExtractUpdate(const fs::path& zipname, const fs::path& targetpath, std::string& cemuFolderName) { cemuFolderName.clear(); // open downloaded zip int err; auto* za = zip_open(zipname.string().c_str(), ZIP_RDONLY, &err); if (za == nullptr) { cemuLog_log(LogType::Force, "Cannot open zip file: {}", zipname.string()); return false; } const auto count = zip_get_num_entries(za, 0); m_gaugeMaxValue = count; for (auto i = 0; i < count; i++) { if (m_order == WorkerOrder::Exit) return false; zip_stat_t sb{}; if (zip_stat_index(za, i, 0, &sb) == 0) { fs::path fname = targetpath; fname /= sb.name; const auto len = strlen(sb.name); if (strcmp(sb.name, ".") == 0 || strcmp(sb.name, "..") == 0) { // protection continue; } if (sb.name[len - 1] == '/' || sb.name[len - 1] == '\\') { // directory try { if (!exists(fname)) create_directory(fname); } catch (const std::exception& ex) { SystemException sys(ex); cemuLog_log(LogType::Force, "can't create folder \"{}\" for update: {}", sb.name, sys.what()); } // the root should have only one Cemu_... directory, we track it here if ((std::count(sb.name, sb.name + len, '/') + std::count(sb.name, sb.name + len, '\\')) == 1) { if (!cemuFolderName.empty()) cemuLog_log(LogType::Force, "update zip has multiple folders in root"); cemuFolderName.assign(sb.name, len - 1); } continue; } // file auto* zf = zip_fopen_index(za, i, 0); if (!zf) { cemuLog_log(LogType::Force, "can't open zip file \"{}\"", sb.name); zip_close(za); return false; } std::vector buffer(sb.size); const auto read = zip_fread(zf, buffer.data(), sb.size); if (read != (sint64)sb.size) { cemuLog_log(LogType::Force, "could only read 0x{:x} of 0x{:x} bytes from zip file \"{}\"", read, sb.size, sb.name); zip_close(za); return false; } auto* file = fopen(fname.string().c_str(), "wb"); if (file == nullptr) { cemuLog_log(LogType::Force, "can't create update file \"{}\"", sb.name); zip_close(za); return false; } fwrite(buffer.data(), 1, buffer.size(), file); fflush(file); fclose(file); zip_fclose(zf); if ((i / 10) * 10 == i) { auto* event = new wxCommandEvent(wxEVT_PROGRESS); event->SetInt(i); wxQueueEvent(this, event); } } } auto* event = new wxCommandEvent(wxEVT_PROGRESS); event->SetInt(m_gaugeMaxValue); wxQueueEvent(this, event); zip_close(za); return true; } void CemuUpdateWindow::WorkerThread() { const auto tmppath = fs::temp_directory_path() / L"cemu_update"; std::error_code ec; // clean leftovers if (exists(tmppath)) remove_all(tmppath, ec); while (true) { std::unique_lock lock(m_mutex); while (m_order == WorkerOrder::Idle) m_condition.wait_for(lock, std::chrono::milliseconds(125)); if (m_order == WorkerOrder::Exit) break; try { if (m_order == WorkerOrder::CheckVersion) { auto* event = new wxCommandEvent(wxEVT_RESULT); if (QueryUpdateInfo(m_downloadUrl, m_changelogUrl)) event->SetInt((int)Result::UpdateAvailable); else event->SetInt((int)Result::NoUpdateAvailable); wxQueueEvent(this, event); } else if (m_order == WorkerOrder::UpdateVersion) { // download update const std::string url = m_downloadUrl; if (!exists(tmppath)) create_directory(tmppath); #if BOOST_OS_WINDOWS const auto update_file = tmppath / L"update.zip"; #elif BOOST_OS_LINUX || BOOST_OS_BSD // dummy placeholder on BSD for now const auto update_file = tmppath / L"Cemu.AppImage"; #elif BOOST_OS_MACOS const auto update_file = tmppath / L"cemu.dmg"; #endif if (DownloadCemuZip(url, update_file)) { auto* event = new wxCommandEvent(wxEVT_RESULT); event->SetInt((int)Result::UpdateDownloaded); wxQueueEvent(this, event); } else { auto* event = new wxCommandEvent(wxEVT_RESULT); event->SetInt((int)Result::UpdateDownloadError); wxQueueEvent(this, event); m_order = WorkerOrder::Idle; continue; } if (m_order == WorkerOrder::Exit) break; // extract std::string cemuFolderName; #if BOOST_OS_WINDOWS if (!ExtractUpdate(update_file, tmppath, cemuFolderName)) { cemuLog_log(LogType::Force, "Extracting Cemu zip failed"); break; } if (cemuFolderName.empty()) { cemuLog_log(LogType::Force, "Cemu folder not found in zip"); break; } #endif const auto expected_path = tmppath / cemuFolderName; if (exists(expected_path)) { auto* event = new wxCommandEvent(wxEVT_RESULT); event->SetInt((int)Result::ExtractSuccess); wxQueueEvent(this, event); } else { auto* event = new wxCommandEvent(wxEVT_RESULT); event->SetInt((int)Result::ExtractError); wxQueueEvent(this, event); if (exists(tmppath)) { try { fs::remove(tmppath); } catch (const std::exception& ex) { SystemException sys(ex); cemuLog_log(LogType::Force, "can't remove extracted tmp files: {}", sys.what()); } } continue; } if (m_order == WorkerOrder::Exit) break; // apply update fs::path exePath = ActiveSettings::GetExecutablePath(); #if BOOST_OS_WINDOWS std::wstring target_directory = exePath.parent_path().generic_wstring(); if (target_directory[target_directory.size() - 1] == '/') target_directory = target_directory.substr(0, target_directory.size() - 1); // remove trailing / // get exe name const auto exec = ActiveSettings::GetExecutablePath(); const auto target_exe = fs::path(exec).replace_extension("exe.backup"); fs::rename(exec, target_exe); m_restartFile = exec; #elif BOOST_OS_LINUX const char* appimage_path = std::getenv("APPIMAGE"); const auto target_exe = fs::path(appimage_path).replace_extension("AppImage.backup"); const char* filePath = update_file.c_str(); mode_t permissions = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; fs::rename(appimage_path, target_exe); m_restartFile = appimage_path; chmod(filePath, permissions); wxString wxAppPath = wxString::FromUTF8(appimage_path); wxCopyFile (wxT("/tmp/cemu_update/Cemu.AppImage"), wxAppPath); #endif #if BOOST_OS_WINDOWS const auto index = expected_path.wstring().size(); int counter = 0; for (const auto& it : fs::recursive_directory_iterator(expected_path)) { const auto filename = it.path().wstring().substr(index); auto target_file = target_directory + filename; try { if (is_directory(it)) { if (!fs::exists(target_file)) fs::create_directory(target_file); } else { if (it.path().filename() == L"Cemu.exe") fs::rename(it.path(), fs::path(target_file).replace_filename(exec.filename())); else fs::rename(it.path(), target_file); } } catch (const std::exception& ex) { SystemException sys(ex); cemuLog_log(LogType::Force, "applying update error: {}", sys.what()); } if ((counter++ % 10) == 0) { auto* event = new wxCommandEvent(wxEVT_PROGRESS); event->SetInt(counter); wxQueueEvent(this, event); } } #endif auto* event = new wxCommandEvent(wxEVT_PROGRESS); event->SetInt(m_gaugeMaxValue); wxQueueEvent(this, event); auto* result_event = new wxCommandEvent(wxEVT_RESULT); result_event->SetInt((int)Result::Success); wxQueueEvent(this, result_event); } } catch (const std::exception& ex) { SystemException sys(ex); cemuLog_log(LogType::Force, "update error: {}", sys.what()); // clean leftovers if (exists(tmppath)) remove_all(tmppath, ec); auto* result_event = new wxCommandEvent(wxEVT_RESULT); result_event->SetInt((int)Result::Error); wxQueueEvent(this, result_event); } m_order = WorkerOrder::Idle; } } void CemuUpdateWindow::OnClose(wxCloseEvent& event) { event.Skip(); #if BOOST_OS_WINDOWS if (m_restartRequired && !m_restartFile.empty() && fs::exists(m_restartFile)) { PROCESS_INFORMATION pi{}; STARTUPINFOW si{}; si.cb = sizeof(si); std::wstring cmdline = GetCommandLineW(); const auto index = cmdline.find('"', 1); cemu_assert_debug(index != std::wstring::npos); cmdline = L"\"" + boost::nowide::widen(_pathToUtf8(m_restartFile)) + L"\"" + cmdline.substr(index + 1); HANDLE lock = CreateMutexW(nullptr, TRUE, L"Global\\cemu_update_lock"); CreateProcessW(nullptr, (wchar_t*)cmdline.c_str(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); exit(0); } #elif BOOST_OS_LINUX if (m_restartRequired && !m_restartFile.empty() && fs::exists(m_restartFile)) { const char* appimage_path = std::getenv("APPIMAGE"); execlp(appimage_path, appimage_path, (char *)NULL); exit(0); } #elif BOOST_OS_MACOS if (m_restartRequired) { const auto tmppath = fs::temp_directory_path() / L"cemu_update/Cemu.dmg"; fs::path exePath = ActiveSettings::GetExecutablePath().parent_path(); const auto apppath = exePath / L"update.sh"; execlp("sh", "sh", apppath.c_str(), NULL); exit(0); } #endif } void CemuUpdateWindow::OnResult(wxCommandEvent& event) { switch ((Result)event.GetInt()) { case Result::NoUpdateAvailable: m_cancelButton->SetLabel(_("Exit")); m_text->SetLabel(_("No update available!")); m_gauge->SetValue(100); break; case Result::UpdateAvailable: { if (!m_changelogUrl.empty()) { m_changelog->SetURL(m_changelogUrl); m_changelog->Show(); } else m_changelog->Hide(); m_updateButton->Show(); m_text->SetLabel(_("Update available!")); m_cancelButton->SetLabel(_("Exit")); break; } case Result::UpdateDownloaded: m_text->SetLabel(_("Extracting update...")); m_gauge->SetValue(0); break; case Result::UpdateDownloadError: m_updateButton->Enable(); m_text->SetLabel(_("Couldn't download the update!")); break; case Result::ExtractSuccess: m_text->SetLabel(_("Applying update...")); m_gauge->SetValue(0); m_cancelButton->Disable(); break; case Result::ExtractError: m_updateButton->Enable(); m_cancelButton->Enable(); m_text->SetLabel(_("Extracting failed!")); break; case Result::Success: m_cancelButton->Enable(); m_updateButton->Hide(); m_text->SetLabel(_("Success")); m_cancelButton->SetLabel(_("Restart")); m_restartRequired = true; break; default:; } } void CemuUpdateWindow::OnGaugeUpdate(wxCommandEvent& event) { const int total_size = m_gaugeMaxValue > 0 ? m_gaugeMaxValue : 10000000; m_gauge->SetValue((event.GetInt() * 100) / total_size); } void CemuUpdateWindow::OnUpdateButton(const wxCommandEvent& event) { std::unique_lock lock(m_mutex); m_order = WorkerOrder::UpdateVersion; m_condition.notify_all(); m_updateButton->Disable(); m_text->SetLabel(_("Downloading update...")); } void CemuUpdateWindow::OnCancelButton(const wxCommandEvent& event) { Close(); } ================================================ FILE: src/gui/wxgui/CemuUpdateWindow.h ================================================ #pragma once #include #include #include #include #include #include #include class CemuUpdateWindow : public wxDialog { public: CemuUpdateWindow(wxWindow* parent); ~CemuUpdateWindow(); static std::future IsUpdateAvailableAsync(); private: wxStaticText* m_text; wxGauge* m_gauge; wxButton* m_cancelButton, *m_updateButton; wxHyperlinkCtrl* m_changelog; void OnUpdateButton(const wxCommandEvent& event); void OnCancelButton(const wxCommandEvent& event); void OnClose(wxCloseEvent& event); void OnResult(wxCommandEvent& event); void OnGaugeUpdate(wxCommandEvent& event); static size_t WriteStringCallback(char* ptr, size_t size, size_t nmemb, void* userdata); static bool QueryUpdateInfo(std::string& downloadUrlOut, std::string& changelogUrlOut); static bool CheckVersion(); static int ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); bool DownloadCemuZip(const std::string& url, const fs::path& filename); bool ExtractUpdate(const fs::path& zipname, const fs::path& targetpath, std::string& cemuFolderName); enum class WorkerOrder { Idle, Exit, CheckVersion, UpdateVersion, }; enum class Result { NoUpdateAvailable, UpdateAvailable, UpdateDownloaded, UpdateDownloadError, ExtractSuccess, ExtractError, Success, Error }; std::mutex m_mutex; std::condition_variable m_condition; WorkerOrder m_order = WorkerOrder::CheckVersion; void WorkerThread(); std::string m_downloadUrl, m_changelogUrl; int m_gaugeMaxValue = 0; std::thread m_thread; fs::path m_restartFile; bool m_restartRequired = false; }; ================================================ FILE: src/gui/wxgui/ChecksumTool.cpp ================================================ #include "wxgui/ChecksumTool.h" #include "Cafe/TitleList/GameInfo.h" #include "wxgui/helpers/wxCustomEvents.h" #include "util/helpers/helpers.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/wxHelper.h" #include "Cafe/Filesystem/WUD/wud.h" #include #include #include /* EVP_Digest */ #include /* SHA256_DIGEST_LENGTH */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config/ActiveSettings.h" #include "Cafe/TitleList/TitleList.h" const char kSchema[] = R"( { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { "title_id": { "type": "string" }, "region": { "type": "integer" }, "version": { "type": "integer" }, "wud_hash": { "type": "string" }, "files": { "type": "array", "items": { "type": "object", "properties": { "file": { "type": "string" }, "hash": { "type": "string" } }, "required": [ "file", "hash" ] } } }, "required": [ "title_id", "region", "version", "files" ] })"; ChecksumTool::ChecksumTool(wxWindow* parent, wxTitleManagerList::TitleEntry& entry) : wxDialog(parent, wxID_ANY, formatWxString(_("Title checksum of {:08x}-{:08x}"), (uint32) (entry.title_id >> 32), (uint32) (entry.title_id & 0xFFFFFFFF)), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), m_entry(entry) { m_info = CafeTitleList::GetTitleInfoByUID(m_entry.location_uid); if (!m_info.IsValid()) throw std::runtime_error("Invalid title"); // only request online update once static bool s_once = false; if (!s_once) { s_once = true; m_online_ready = std::async(std::launch::async, &ChecksumTool::LoadOnlineData, this); } else m_enable_verify_button = 1; auto* sizer = new wxBoxSizer(wxVERTICAL); { auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Verifying integrity of game files...")), wxVERTICAL); auto* box = box_sizer->GetStaticBox(); m_progress = new wxGauge(box, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH); m_progress->SetMinSize({ 400, -1 }); m_progress->SetValue(0); box_sizer->Add(m_progress, 0, wxALL | wxEXPAND, 5); m_status = new wxStaticText(box, wxID_ANY, wxEmptyString); m_status->Wrap(-1); box_sizer->Add(m_status, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5); sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Control")), wxHORIZONTAL); auto* box = box_sizer->GetStaticBox(); m_verify_online = new wxButton(box, wxID_ANY, _("Verify online")); m_verify_online->SetToolTip(_("Verifies the checksum online")); m_verify_online->Disable(); m_verify_online->Bind(wxEVT_BUTTON, &ChecksumTool::OnVerifyOnline, this); m_verify_online->Bind(wxEVT_ENABLE, [this](wxCommandEvent&) { ++m_enable_verify_button; if (m_enable_verify_button >= 2) { // only enable if we have a file for it const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id); const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion()); const auto checksum_path = ActiveSettings::GetUserDataPath("resources/checksums/{}", default_file); if (exists(checksum_path)) m_verify_online->Enable(); } }); box_sizer->Add(m_verify_online, 0, wxALL | wxEXPAND, 5); m_verify_local = new wxButton(box, wxID_ANY, _("Verify with local file")); m_verify_online->SetToolTip(_("Verifies the checksum with a local JSON file you can select")); m_verify_local->Disable(); m_verify_local->Bind(wxEVT_BUTTON, &ChecksumTool::OnVerifyLocal, this); box_sizer->Add(m_verify_local, 0, wxALL | wxEXPAND, 5); m_export_button = new wxButton(box, wxID_ANY, _("Export")); m_verify_online->SetToolTip(_("Export the title checksum data to a local JSON file")); m_export_button->Disable(); m_export_button->Bind(wxEVT_BUTTON, &ChecksumTool::OnExportChecksums, this); box_sizer->Add(m_export_button, 0, wxALL | wxEXPAND, 5); sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } this->Bind(wxEVT_SET_GAUGE_VALUE, &ChecksumTool::OnSetGaugevalue, this); m_worker = std::thread(&ChecksumTool::DoWork, this); this->SetSizerAndFit(sizer); this->Centre(wxBOTH); } ChecksumTool::~ChecksumTool() { m_running = false; if (m_worker.joinable()) m_worker.join(); } std::size_t WriteCallback(const char* in, std::size_t size, std::size_t num, std::string* out) { const std::size_t totalBytes(size * num); out->append(in, totalBytes); return totalBytes; } void ChecksumTool::LoadOnlineData() const { try { bool updated_required = true; std::string latest_commit; const auto checksum_path = ActiveSettings::GetUserDataPath("resources/checksums"); if (exists(checksum_path)) { std::string current_commit; // check for current version std::ifstream file(checksum_path / "commit.txt"); if (file.is_open()) { std::getline(file, current_commit); file.close(); } // check latest version /* https://api.github.com/repos/teamcemu/title-checksums/branches/master https://api.github.com/repos/teamcemu/title-checksums/commits?per_page=1 */ std::string data; auto* curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/repos/teamcemu/title-checksums/commits/master"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING); curl_easy_perform(curl); long http_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); curl_easy_cleanup(curl); if (http_code == 200 && !data.empty()) { rapidjson::Document doc; doc.Parse(data.c_str(), data.size()); if (!doc.HasParseError() && doc.HasMember("sha")) { latest_commit = doc["sha"].GetString(); if (boost::iequals(current_commit, latest_commit)) { updated_required = false; } } } } else { // create directory since not available yet fs::create_directories(checksum_path); } if (updated_required) { std::string data; auto* curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, "https://github.com/TeamCemu/title-checksums/archive/master.zip"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING); curl_easy_perform(curl); long http_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); curl_easy_cleanup(curl); if (http_code == 200 && !data.empty()) { // init zip source zip_error_t error; zip_error_init(&error); zip_source_t* src; if ((src = zip_source_buffer_create(data.data(), data.size(), 1, &error)) == nullptr) { zip_error_fini(&error); return; } auto* za = zip_open_from_source(src, ZIP_RDONLY, &error); if (!za) { wxQueueEvent(m_verify_online, new wxCommandEvent(wxEVT_ENABLE)); return; } const auto numEntries = zip_get_num_entries(za, 0); for (sint64 i = 0; i < numEntries; i++) { zip_stat_t sb = { 0 }; if (zip_stat_index(za, i, 0, &sb) != 0) continue; if (std::strstr(sb.name, "../") != nullptr || std::strstr(sb.name, "..\\") != nullptr) continue; // bad path if (boost::equals(sb.name, "title-checksums-master/")) continue; // title-checksums-master/ const auto path = checksum_path / &sb.name[sizeof("title-checksums-master")]; size_t sbNameLen = strlen(sb.name); if (sbNameLen == 0) continue; if (sb.name[sbNameLen - 1] == '/') { std::error_code ec; fs::create_directories(path, ec); continue; } if (sb.size == 0) continue; if (sb.size > (1024 * 1024 * 128)) continue; // skip unusually huge files zip_file_t* zipFile = zip_fopen_index(za, i, 0); if (zipFile == nullptr) continue; std::vector buffer(sb.size); if (zip_fread(zipFile, buffer.data(), sb.size) == sb.size) { std::ofstream file(path); if (file.is_open()) { file.write(buffer.data(), sb.size); file.flush(); file.close(); } } zip_fclose(zipFile); } std::ofstream file(checksum_path / "commit.txt"); if (file.is_open()) { file << latest_commit; file.close(); } } } } catch(const std::exception& ex) { cemuLog_log(LogType::Force, "error on updating json checksum data: {}", ex.what()); } wxQueueEvent(m_verify_online, new wxCommandEvent(wxEVT_ENABLE)); } void ChecksumTool::OnSetGaugevalue(wxSetGaugeValue& event) { event.GetGauge()->SetValue(event.GetValue()); event.GetTextCtrl()->SetLabelText(event.GetText()); // no error if(event.GetInt() == 0 && event.GetValue() == 100) { m_export_button->Enable(); m_verify_local->Enable(); wxPostEvent(m_verify_online, wxCommandEvent(wxEVT_ENABLE)); } } void ChecksumTool::OnExportChecksums(wxCommandEvent& event) { // TODO: merge if json already exists wxDirDialog dialog(this, _("Export checksum entry"), "", wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); if (dialog.ShowModal() != wxID_OK || dialog.GetPath().IsEmpty()) return; rapidjson::Document doc; doc.SetObject(); auto& a = doc.GetAllocator(); /* title_id region version wud_hash files: [ {file, hash} ] */ auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id); doc.AddMember("title_id", rapidjson::StringRef(title_id_str.c_str(), title_id_str.size()), a); doc.AddMember("region", (int)m_info.GetMetaRegion(), a); doc.AddMember("version", m_info.GetAppTitleVersion(), a); if (!m_json_entry.wud_hash.empty()) doc.AddMember("wud_hash", rapidjson::StringRef(m_json_entry.wud_hash.c_str(), m_json_entry.wud_hash.size()), a); rapidjson::Value entry_array(rapidjson::kArrayType); rapidjson::Value file_array(rapidjson::kArrayType); for(const auto& file : m_json_entry.file_hashes) { rapidjson::Value file_entry; file_entry.SetObject(); file_entry.AddMember("file", rapidjson::StringRef(file.first.c_str(), file.first.size()), a); file_entry.AddMember("hash", rapidjson::StringRef(file.second.c_str(), file.second.size()), a); file_array.PushBack(file_entry, a); } doc.AddMember("files", file_array, a); std::filesystem::path target_file{ dialog.GetPath().c_str().AsInternal() }; target_file /= fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion()); std::ofstream file(target_file); if(file.is_open()) { rapidjson::OStreamWrapper osw(file); rapidjson::PrettyWriter writer(osw); //rapidjson::GenericSchemaValidator > validator(schema, writer); doc.Accept(writer); wxMessageBox(_("Export successful"), wxMessageBoxCaptionStr, wxOK | wxCENTRE, this); } else { wxMessageBox(formatWxString(_("Can't write to file: {}"), target_file.string()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); } } void ChecksumTool::VerifyJsonEntry(const rapidjson::Document& doc) { rapidjson::Document sdoc; sdoc.Parse(kSchema, std::size(kSchema)); wxASSERT(!sdoc.HasParseError()); rapidjson::SchemaDocument schema(sdoc); rapidjson::SchemaValidator validator(schema); if (!doc.Accept(validator)) { //// validation error: //rapidjson::StringBuffer sb; //validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); //printf("Invalid schema: %s\n", sb.GetString()); //printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); //sb.Clear(); //validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); //printf("Invalid document: %s\n", sb.GetString()); ///* //Invalid schema: # //Invalid keyword: required //Invalid document: # // */ wxMessageBox(_("JSON file doesn't satisfy needed schema"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } try { JsonEntry test_entry{}; test_entry.title_id = ConvertString(doc["title_id"].GetString(), 16); test_entry.region = (CafeConsoleRegion)doc["region"].GetInt(); test_entry.version = doc["version"].GetInt(); if (doc.HasMember("wud_hash")) test_entry.wud_hash = doc["wud_hash"].GetString(); for (const auto& v : doc["files"].GetArray()) { std::filesystem::path genericFilePath(v["file"].GetString(), std::filesystem::path::generic_format); // convert path to generic form (forward slashes) test_entry.file_hashes[genericFilePath.generic_string()] = v["hash"].GetString(); } if (m_json_entry.title_id != test_entry.title_id) { wxMessageBox(formatWxString(_("The file you are comparing with is for a different title.")), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } if (m_json_entry.version != test_entry.version) { wxMessageBox(formatWxString(_("Wrong version: {}"), test_entry.version), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } if (m_json_entry.region != test_entry.region) { wxMessageBox(formatWxString(_("Wrong region: {}"), test_entry.region), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } if (!m_json_entry.wud_hash.empty()) { if (test_entry.wud_hash.empty()) { wxMessageBox(_("The verification data doesn't include a WUD hash!"), _("Error"), wxOK | wxCENTRE | wxICON_WARNING, this); return; } if(!boost::iequals(test_entry.wud_hash, m_json_entry.wud_hash)) { wxMessageBox(formatWxString(_("Your game image is invalid!\n\nYour hash:\n{}\n\nExpected hash:\n{}"), m_json_entry.wud_hash, test_entry.wud_hash), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } } else { std::map> invalid_hashes; std::vector missing_files; const auto writeMismatchInfoToLog = [this, &missing_files, &invalid_hashes]() { wxFileDialog dialog(this, _("Select a file to export the errors"), wxEmptyString, wxEmptyString, "Error list (*.txt)|*.txt", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dialog.ShowModal() != wxID_OK || dialog.GetPath().IsEmpty()) return; const std::string path = dialog.GetPath().utf8_string(); std::ofstream file(path); if (file.is_open()) { if (!missing_files.empty()) { file << "The following files are missing:\n"; for (const auto& f : missing_files) file << "\t" << f << "\n"; file << "\n"; } if (!invalid_hashes.empty()) { file << "The following files have an invalid hash (name | current hash | expected hash):\n"; for (const auto& f : invalid_hashes) file << "\t" << f.first << " | " << f.second.first << " | " << f.second.second << "\n"; } file.flush(); file.close(); wxLaunchDefaultBrowser(formatWxString("file:{}", path)); } else wxMessageBox(_("Can't open file to write!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); }; for (const auto& f : test_entry.file_hashes) { const auto it = m_json_entry.file_hashes.find(f.first); if (it == m_json_entry.file_hashes.cend()) { missing_files.emplace_back(f.first); } else if (!boost::iequals(f.second, it->second)) { invalid_hashes[f.first] = std::make_pair(it->second, f.second); } } // files are missing but rest is okay if ((!missing_files.empty() || !invalid_hashes.empty()) && (missing_files.size() + invalid_hashes.size()) < 30) { // the list of missing + invalid hashes is short enough that we can print it to the message box std::stringstream str; if (missing_files.size() > 0) { str << _("The following files are missing:").ToUTF8().data() << "\n"; for (const auto& v : missing_files) str << v << "\n"; if(invalid_hashes.size() > 0) str << "\n"; } if (invalid_hashes.size() > 0) { str << _("The following files are damaged:").ToUTF8().data() << "\n"; for (const auto& v : invalid_hashes) str << v.first << "\n"; } wxMessageBox(str.str(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } else if (missing_files.empty() && !invalid_hashes.empty()) { const int result = wxMessageBox(formatWxString( _("{} files have an invalid hash!\nDo you want to export a list of them to a file?"), invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this); if (result == wxYES) { writeMismatchInfoToLog(); } return; } else if (!missing_files.empty() && !invalid_hashes.empty()) { const int result = wxMessageBox(formatWxString( _("Multiple issues with your game files have been found!\nDo you want to export them to a file?"), invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this); if (result == wxYES) { writeMismatchInfoToLog(); } return; } } wxMessageBox(_("Your game files are valid"), _("Success"), wxOK | wxCENTRE, this); } catch (const std::exception& ex) { wxMessageBox(formatWxString(_("JSON parse error: {}"), ex.what()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); } } void ChecksumTool::OnVerifyOnline(wxCommandEvent& event) { const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id); const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion()); const auto checksum_path = ActiveSettings::GetUserDataPath("resources/checksums/{}", default_file); if(!exists(checksum_path)) return; std::ifstream file(checksum_path); if (!file.is_open()) { wxMessageBox(_("Can't open file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } rapidjson::IStreamWrapper str(file); rapidjson::Document d; d.ParseStream(str); if (d.HasParseError()) { wxMessageBox(_("Can't parse JSON file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } VerifyJsonEntry(d); } void ChecksumTool::OnVerifyLocal(wxCommandEvent& event) { const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id); const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion()); wxFileDialog file_dialog(this, _("Open checksum entry"), "", default_file.c_str(),"JSON files (*.json)|*.json", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (file_dialog.ShowModal() != wxID_OK || file_dialog.GetPath().IsEmpty()) return; std::filesystem::path filename{ file_dialog.GetPath().c_str().AsInternal() }; std::ifstream file(filename); if(!file.is_open()) { wxMessageBox(_("Can't open file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } rapidjson::IStreamWrapper str(file); rapidjson::Document d; d.ParseStream(str); if (d.HasParseError()) { wxMessageBox(_("Can't parse JSON file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } VerifyJsonEntry(d); } static void _fscGetAllFiles(std::set& allFilesOut, const std::string& fscBasePath, const std::string& relativePath) { sint32 fscStatus; FSCVirtualFile* fsc = fsc_openDirIterator((fscBasePath + relativePath).c_str(), &fscStatus); cemu_assert(fsc); FSCDirEntry dirEntry; while (fsc_nextDir(fsc, &dirEntry)) { if (dirEntry.isDirectory) { _fscGetAllFiles(allFilesOut, fscBasePath, std::string(relativePath).append(dirEntry.GetPath()).append("/")); } else { allFilesOut.emplace(std::string(relativePath).append(dirEntry.GetPath())); } } delete fsc; } void ChecksumTool::DoWork() { m_json_entry.title_id = m_info.GetAppTitleId(); m_json_entry.region = m_info.GetMetaRegion(); m_json_entry.version = m_info.GetAppTitleVersion(); static_assert(SHA256_DIGEST_LENGTH == 32); std::array checksum{}; switch (m_info.GetFormat()) { case TitleInfo::TitleDataFormat::WUD: { const auto path = m_entry.path.string(); wxQueueEvent(this, new wxSetGaugeValue(1, m_progress, m_status, formatWxString(_("Reading game image: {}"), path))); wud_t* wud = wud_open(m_info.GetPath()); if (!wud) throw std::runtime_error("can't open game image"); const auto wud_size = wud_getWUDSize(wud); std::vector buffer(1024 * 1024 * 8); EVP_MD_CTX *sha256 = EVP_MD_CTX_new(); EVP_DigestInit(sha256, EVP_sha256()); uint32 read = 0; size_t offset = 0; auto size = wud_size; do { if (!m_running.load(std::memory_order_relaxed)) { wud_close(wud); return; } read = wud_readData(wud, buffer.data(), std::min(buffer.size(), (size_t)wud_size - offset), offset); offset += read; size -= read; EVP_DigestUpdate(sha256, buffer.data(), read); wxQueueEvent(this, new wxSetGaugeValue((int)((offset * 90) / wud_size), m_progress, m_status, formatWxString(_("Reading game image: {0}/{1} kB"), offset / 1024, wud_size / 1024))); } while (read != 0 && size > 0); wud_close(wud); wxQueueEvent(this, new wxSetGaugeValue(90, m_progress, m_status, formatWxString(_("Generating checksum of game image: {}"), path))); if (!m_running.load(std::memory_order_relaxed)) return; EVP_DigestFinal_ex(sha256, checksum.data(), NULL); EVP_MD_CTX_free(sha256); std::stringstream str; for (const auto& b : checksum) { str << fmt::format("{:02X}", b); } m_json_entry.wud_hash = str.str(); wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, formatWxString(_("Generated checksum of game image: {}"), path))); break; } default: // we hash the individual files for all formats except WUD/WUX std::string temporaryMountPath = TitleInfo::GetUniqueTempMountingPath(); m_info.Mount(temporaryMountPath.c_str(), "", FSC_PRIORITY_BASE); wxQueueEvent(this, new wxSetGaugeValue(1, m_progress, m_status, _("Grabbing game files"))); // get list of all files std::set files; _fscGetAllFiles(files, temporaryMountPath, ""); const size_t file_count = files.size(); size_t counter = 0; for (const auto& filename : files) { auto fileData = fsc_extractFile((temporaryMountPath + "/" + filename).c_str()); if (!fileData) { cemuLog_log(LogType::Force, "Failed to open {}", filename); continue; } SHA256(fileData->data(), fileData->size(), checksum.data()); std::stringstream str; for (const auto& b : checksum) { str << fmt::format("{:02X}", b); } // store relative path and hash m_json_entry.file_hashes[filename] = str.str(); ++counter; wxQueueEvent(this, new wxSetGaugeValue((int)((counter * 100) / file_count), m_progress, m_status, formatWxString(_("Hashing game file: {}/{}"), counter, file_count))); if (!m_running.load(std::memory_order_relaxed)) { m_info.Unmount(temporaryMountPath.c_str()); return; } } m_info.Unmount(temporaryMountPath.c_str()); wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, formatWxString(_("Generated checksum of {} game files"), file_count))); break; } } ================================================ FILE: src/gui/wxgui/ChecksumTool.h ================================================ #pragma once #include #include "wxgui/components/wxTitleManagerList.h" #include "Cafe/TitleList/TitleInfo.h" #include class wxSetGaugeValue; class ChecksumTool : public wxDialog { public: ChecksumTool(wxWindow* parent, wxTitleManagerList::TitleEntry& entry); ~ChecksumTool(); private: std::future m_online_ready; void LoadOnlineData() const; void VerifyJsonEntry(const rapidjson::Document& doc); void OnSetGaugevalue(wxSetGaugeValue& event); void OnExportChecksums(wxCommandEvent& event); void OnVerifyOnline(wxCommandEvent& event); void OnVerifyLocal(wxCommandEvent& event); void DoWork(); std::atomic_bool m_running = true; std::thread m_worker; class wxGauge* m_progress; class wxStaticText* m_status; class wxButton *m_verify_online, *m_verify_local, *m_export_button; int m_enable_verify_button = 0; TitleInfo m_info; wxTitleManagerList::TitleEntry m_entry; wxColour m_default_color; struct JsonEntry { uint64 title_id; uint32 version; CafeConsoleRegion region; std::string wud_hash; std::map file_hashes; }; JsonEntry m_json_entry; inline static std::mutex s_mutex{}; inline static std::vector s_entries{}; }; ================================================ FILE: src/gui/wxgui/DownloadGraphicPacksWindow.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/DownloadGraphicPacksWindow.h" #include #include #include #include #include #include "config/ActiveSettings.h" #include "Common/FileStream.h" #include "Cafe/CafeSystem.h" struct DownloadGraphicPacksWindow::curlDownloadFileState_t { std::vector fileData; double progress; }; size_t DownloadGraphicPacksWindow::curlDownloadFile_writeData(void *ptr, size_t size, size_t nmemb, curlDownloadFileState_t* downloadState) { const size_t writeSize = size * nmemb; const size_t currentSize = downloadState->fileData.size(); const size_t newSize = currentSize + writeSize; downloadState->fileData.resize(newSize); memcpy(downloadState->fileData.data() + currentSize, ptr, writeSize); return writeSize; } int DownloadGraphicPacksWindow::progress_callback(curlDownloadFileState_t* downloadState, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { if (dltotal > 1.0) downloadState->progress = dlnow / dltotal; else downloadState->progress = 0.0; return 0; } bool DownloadGraphicPacksWindow::curlDownloadFile(const char *url, curlDownloadFileState_t* downloadState) { CURL* curl = curl_easy_init(); if (curl == nullptr) return false; downloadState->progress = 0.0; curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlDownloadFile_writeData); curl_easy_setopt(curl, CURLOPT_WRITEDATA, downloadState); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback); curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, downloadState); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING); downloadState->fileData.resize(0); const CURLcode res = curl_easy_perform(curl); curl_easy_cleanup(curl); return res == CURLE_OK; } // returns true if the version matches bool checkGraphicPackDownloadedVersion(const char* nameVersion, bool& hasVersionFile) { hasVersionFile = false; const auto path = ActiveSettings::GetUserDataPath("graphicPacks/downloadedGraphicPacks/version.txt"); std::unique_ptr file(FileStream::openFile2(path)); std::string versionInFile; if (file && file->readLine(versionInFile)) { return boost::iequals(versionInFile, nameVersion); } return false; } void createGraphicPackDownloadedVersionFile(const char* nameVersion) { const auto path = ActiveSettings::GetUserDataPath("graphicPacks/downloadedGraphicPacks/version.txt"); std::unique_ptr file(FileStream::createFile2(path)); if (file) file->writeString(nameVersion); else { cemuLog_log(LogType::Force, "Failed to write graphic pack version.txt"); } } void deleteDownloadedGraphicPacks() { const auto path = ActiveSettings::GetUserDataPath("graphicPacks/downloadedGraphicPacks"); std::error_code er; if (!fs::exists(path, er)) return; try { for (auto& p : fs::directory_iterator(path)) { fs::remove_all(p.path(), er); } } catch (std::filesystem::filesystem_error& e) { cemuLog_log(LogType::Force, "Error in deleteDownloadedGraphicPacks():"); cemuLog_log(LogType::Force, e.what()); } } void DownloadGraphicPacksWindow::UpdateThread() { // get github url std::string githubAPIUrl; curlDownloadFileState_t tempDownloadState; std::string queryUrl("https://cemu.info/api2/query_graphicpack_url.php?"); char temp[64]; sprintf(temp, "version=%d.%d.%d", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH); queryUrl.append(temp); queryUrl.append("&"); sprintf(temp, "t=%u", (uint32)std::chrono::seconds(std::time(NULL)).count()); // add a dynamic part to the url to bypass overly aggressive caching (like some proxies do) queryUrl.append(temp); if (curlDownloadFile(queryUrl.c_str(), &tempDownloadState) && boost::starts_with((const char*)tempDownloadState.fileData.data(), "http")) { // convert downloaded data to url string githubAPIUrl.assign(tempDownloadState.fileData.cbegin(), tempDownloadState.fileData.cend()); } else { // cemu api request failed, use hardcoded github url cemuLog_log(LogType::Force, "Graphic pack update request failed or returned invalid URL. Using default repository URL instead"); githubAPIUrl = "https://api.github.com/repos/cemu-project/cemu_graphic_packs/releases/latest"; } // github API request if (curlDownloadFile(githubAPIUrl.c_str(), &tempDownloadState) == false) { wxMessageBox( _("Error"), _(L"Failed to connect to server"), wxOK | wxCENTRE | wxICON_ERROR, this); m_threadState = ThreadError; return; } // parse json result rapidjson::Document d; d.Parse((const char*)tempDownloadState.fileData.data(), tempDownloadState.fileData.size()); if (d.HasParseError()) { m_threadState = ThreadError; return; } auto& jsonName = d["name"]; if (jsonName.IsString() == false) { m_threadState = ThreadError; return; } const char* assetName = jsonName.GetString(); // name includes version if( d.IsObject() == false) { m_threadState = ThreadError; return; } auto& jsonAssets = d["assets"]; if (jsonAssets.IsArray() == false || jsonAssets.GetArray().Size() == 0) { m_threadState = ThreadError; return; } auto& jsonAsset0 = jsonAssets.GetArray()[0]; if (jsonAsset0.IsObject() == false) { m_threadState = ThreadError; return; } auto& jsonDownloadUrl = jsonAsset0["browser_download_url"]; if (jsonDownloadUrl.IsString() == false) { m_threadState = ThreadError; return; } const char* browserDownloadUrl = jsonDownloadUrl.GetString(); // check version bool hasVersionFile = false; if (checkGraphicPackDownloadedVersion(assetName, hasVersionFile)) { // already up to date wxMessageBox(_("No updates available."), _("Graphic packs"), wxOK | wxCENTRE, this); m_threadState = ThreadFinished; return; } if (hasVersionFile) { // if a version file already exists (and graphic packs are installed) ask the user if he really wants to update if (wxMessageBox(_("Updated graphic packs are available. Do you want to download and install them?"), _("Graphic packs"), wxYES_NO, this) != wxYES) { // cancel update m_threadState = ThreadFinished; return; } } // download zip m_stage = StageDownloading; if (curlDownloadFile(browserDownloadUrl, m_downloadState.get()) == false) { wxMessageBox(_("Error"), _(L"Failed to connect to server"), wxOK | wxCENTRE | wxICON_ERROR, this); m_threadState = ThreadError; return; } m_extractionProgress = 0.0; m_stage = StageExtracting; zip_source_t *src; zip_t *za; zip_error_t error; // init zip source zip_error_init(&error); if ((src = zip_source_buffer_create(m_downloadState->fileData.data(), m_downloadState->fileData.size(), 1, &error)) == NULL) { zip_error_fini(&error); m_threadState = ThreadError; return; } // open zip from source if ((za = zip_open_from_source(src, 0, &error)) == NULL) { zip_source_free(src); zip_error_fini(&error); m_threadState = ThreadError; return; } auto path = ActiveSettings::GetUserDataPath("graphicPacks/downloadedGraphicPacks"); std::error_code er; //fs::remove_all(path, er); -> Don't delete the whole folder and recreate it immediately afterwards because sometimes it just fails deleteDownloadedGraphicPacks(); fs::create_directories(path, er); // make sure downloadedGraphicPacks folder exists sint32 numEntries = zip_get_num_entries(za, 0); for (sint32 i = 0; i < numEntries; i++) { m_extractionProgress = (double)i / (double)numEntries; zip_stat_t sb = { 0 }; if (zip_stat_index(za, i, 0, &sb) != 0) { assert_dbg(); } if(std::strstr(sb.name, "../") != nullptr || std::strstr(sb.name, "..\\") != nullptr) continue; // bad path path = ActiveSettings::GetUserDataPath("graphicPacks/downloadedGraphicPacks/{}", sb.name); size_t sbNameLen = strlen(sb.name); if(sbNameLen == 0) continue; if (sb.name[sbNameLen - 1] == '/') { fs::create_directories(path, er); continue; } if(sb.size == 0) continue; if (sb.size > (1024 * 1024 * 128)) continue; // skip unusually huge files zip_file_t* zipFile = zip_fopen_index(za, i, 0); if (zipFile == nullptr) continue; uint8* fileBuffer = new uint8[sb.size]; if (zip_fread(zipFile, fileBuffer, sb.size) == sb.size) { FileStream* fs = FileStream::createFile2(path); if (fs) { fs->writeData(fileBuffer, sb.size); delete fs; } } delete [] fileBuffer; zip_fclose(zipFile); } zip_error_fini(&error); createGraphicPackDownloadedVersionFile(assetName); m_threadState = ThreadFinished; } DownloadGraphicPacksWindow::DownloadGraphicPacksWindow(wxWindow* parent) : wxDialog(parent, wxID_ANY, _("Checking version..."), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), m_threadState(ThreadRunning), m_stage(StageCheckVersion), m_currentStage(StageCheckVersion) { auto* sizer = new wxBoxSizer(wxVERTICAL); m_processBar = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL); m_processBar->SetValue(0); m_processBar->SetRange(100); sizer->Add(m_processBar, 0, wxALL | wxEXPAND, 5); auto* m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); m_cancelButton->Bind(wxEVT_BUTTON, &DownloadGraphicPacksWindow::OnCancelButton, this); sizer->Add(m_cancelButton, 0, wxALIGN_RIGHT | wxALL, 5); this->SetSizer(sizer); this->Centre(wxBOTH); wxWindowBase::Layout(); wxWindowBase::Fit(); m_timer = new wxTimer(this); this->Bind(wxEVT_TIMER, &DownloadGraphicPacksWindow::OnUpdate, this); this->Bind(wxEVT_CLOSE_WINDOW, &DownloadGraphicPacksWindow::OnClose, this); m_timer->Start(250); m_downloadState = std::make_unique(); } DownloadGraphicPacksWindow::~DownloadGraphicPacksWindow() { m_timer->Stop(); if (m_thread.joinable()) m_thread.join(); } const std::string& DownloadGraphicPacksWindow::GetException() const { return m_threadException; } int DownloadGraphicPacksWindow::ShowModal() { if(CafeSystem::IsTitleRunning()) { wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this); return wxID_CANCEL; } m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this); wxDialog::ShowModal(); return m_threadState == ThreadCanceled ? wxID_CANCEL : wxID_OK; } void DownloadGraphicPacksWindow::OnClose(wxCloseEvent& event) { if (m_threadState == ThreadRunning) { //wxMessageDialog dialog(this, _("Do you really want to cancel the update process?\n\nCanceling the process will delete the applied update."), _("Info"), wxCENTRE | wxYES_NO); //if (dialog.ShowModal() != wxID_YES) // return; m_threadState = ThreadCanceled; } m_timer->Stop(); if (m_thread.joinable()) m_thread.join(); event.Skip(); } void DownloadGraphicPacksWindow::OnUpdate(const wxTimerEvent& event) { if (m_threadState != ThreadRunning) { Close(); return; } if (m_currentStage != m_stage) { if (m_stage == StageDownloading) { this->SetTitle(_("Downloading graphic packs...")); } else if (m_stage == StageExtracting) { this->SetTitle(_("Extracting...")); } m_currentStage = m_stage; } if (m_currentStage == StageDownloading) { const sint32 processedSize = (sint32)(m_downloadState->progress * 100.0f); if (m_processBar->GetValue() != processedSize) m_processBar->SetValue(processedSize); } else if (m_currentStage == StageExtracting) { const sint32 processedSize = (sint32)(m_extractionProgress * 100.0f); if (m_processBar->GetValue() != processedSize) m_processBar->SetValue(processedSize); } } void DownloadGraphicPacksWindow::OnCancelButton(const wxCommandEvent& event) { Close(); } ================================================ FILE: src/gui/wxgui/DownloadGraphicPacksWindow.h ================================================ #pragma once #include #include #include #include #include #include #include #include class DownloadGraphicPacksWindow : public wxDialog { public: DownloadGraphicPacksWindow(wxWindow* parent); ~DownloadGraphicPacksWindow(); const std::string& GetException() const; int ShowModal() override; void OnClose(wxCloseEvent& event); void OnUpdate(const wxTimerEvent& event); void OnCancelButton(const wxCommandEvent& event); private: void UpdateThread(); enum ThreadState_t { ThreadRunning, ThreadCanceled, ThreadError, ThreadFinished, }; enum DownloadStage_t { StageCheckVersion, StageDownloading, StageExtracting }; std::atomic m_threadState; std::atomic m_stage; std::atomic m_extractionProgress; std::string m_threadException; std::thread m_thread; DownloadStage_t m_currentStage; wxGauge* m_processBar; wxTimer* m_timer; struct curlDownloadFileState_t; std::unique_ptr m_downloadState; static size_t curlDownloadFile_writeData(void* ptr, size_t size, size_t nmemb, curlDownloadFileState_t* downloadState); static int progress_callback(curlDownloadFileState_t* downloadState, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); static bool curlDownloadFile(const char* url, curlDownloadFileState_t* downloadState); }; ================================================ FILE: src/gui/wxgui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp ================================================ #include "EmulatedUSBDeviceFrame.h" #include #include "config/CemuConfig.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/wxHelper.h" #include "util/helpers/helpers.h" #include "Cafe/OS/libs/nsyshid/nsyshid.h" #include "Cafe/OS/libs/nsyshid/Dimensions.h" #include "Common/FileStream.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "resource/embedded/resources.h" EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent) : wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL) { SetIcon(wxICON(X_BOX)); auto& config = GetConfig(); auto* sizer = new wxBoxSizer(wxVERTICAL); auto* notebook = new wxNotebook(this, wxID_ANY); notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal")); notebook->AddPage(AddInfinityPage(notebook), _("Infinity Base")); notebook->AddPage(AddDimensionsPage(notebook), _("Dimensions Toypad")); sizer->Add(notebook, 1, wxEXPAND | wxALL, 2); SetSizerAndFit(sizer); Layout(); Centre(wxBOTH); } EmulatedUSBDeviceFrame::~EmulatedUSBDeviceFrame() {} wxPanel* EmulatedUSBDeviceFrame::AddSkylanderPage(wxNotebook* notebook) { auto* panel = new wxPanel(notebook); auto* panelSizer = new wxBoxSizer(wxVERTICAL); auto* box = new wxStaticBox(panel, wxID_ANY, _("Skylanders Manager")); auto* boxSizer = new wxStaticBoxSizer(box, wxVERTICAL); auto* row = new wxBoxSizer(wxHORIZONTAL); m_emulatePortal = new wxCheckBox(box, wxID_ANY, _("Emulate Skylander Portal")); m_emulatePortal->SetValue( GetConfig().emulated_usb_devices.emulate_skylander_portal); m_emulatePortal->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { GetConfig().emulated_usb_devices.emulate_skylander_portal = m_emulatePortal->IsChecked(); GetConfigHandle().Save(); }); row->Add(m_emulatePortal, 1, wxEXPAND | wxALL, 2); boxSizer->Add(row, 1, wxEXPAND | wxALL, 2); for (int i = 0; i < nsyshid::MAX_SKYLANDERS; i++) { boxSizer->Add(AddSkylanderRow(i, box), 1, wxEXPAND | wxALL, 2); } panelSizer->Add(boxSizer, 1, wxEXPAND | wxALL, 2); panel->SetSizerAndFit(panelSizer); return panel; } wxPanel* EmulatedUSBDeviceFrame::AddInfinityPage(wxNotebook* notebook) { auto* panel = new wxPanel(notebook); auto* panelSizer = new wxBoxSizer(wxVERTICAL); auto* box = new wxStaticBox(panel, wxID_ANY, _("Infinity Manager")); auto* boxSizer = new wxStaticBoxSizer(box, wxVERTICAL); auto* row = new wxBoxSizer(wxHORIZONTAL); m_emulateBase = new wxCheckBox(box, wxID_ANY, _("Emulate Infinity Base")); m_emulateBase->SetValue( GetConfig().emulated_usb_devices.emulate_infinity_base); m_emulateBase->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { GetConfig().emulated_usb_devices.emulate_infinity_base = m_emulateBase->IsChecked(); GetConfigHandle().Save(); }); row->Add(m_emulateBase, 1, wxEXPAND | wxALL, 2); boxSizer->Add(row, 1, wxEXPAND | wxALL, 2); boxSizer->Add(AddInfinityRow("Play Set/Power Disc", 0, box), 1, wxEXPAND | wxALL, 2); boxSizer->Add(AddInfinityRow("Power Disc Two", 1, box), 1, wxEXPAND | wxALL, 2); boxSizer->Add(AddInfinityRow("Power Disc Three", 2, box), 1, wxEXPAND | wxALL, 2); boxSizer->Add(AddInfinityRow("Player One", 3, box), 1, wxEXPAND | wxALL, 2); boxSizer->Add(AddInfinityRow("Player One Ability One", 4, box), 1, wxEXPAND | wxALL, 2); boxSizer->Add(AddInfinityRow("Player One Ability Two", 5, box), 1, wxEXPAND | wxALL, 2); boxSizer->Add(AddInfinityRow("Player Two", 6, box), 1, wxEXPAND | wxALL, 2); boxSizer->Add(AddInfinityRow("Player Two Ability One", 7, box), 1, wxEXPAND | wxALL, 2); boxSizer->Add(AddInfinityRow("Player Two Ability Two", 8, box), 1, wxEXPAND | wxALL, 2); panelSizer->Add(boxSizer, 1, wxEXPAND | wxALL, 2); panel->SetSizerAndFit(panelSizer); return panel; } wxPanel* EmulatedUSBDeviceFrame::AddDimensionsPage(wxNotebook* notebook) { auto* panel = new wxPanel(notebook); auto* panel_sizer = new wxBoxSizer(wxVERTICAL); auto* box = new wxStaticBox(panel, wxID_ANY, _("Dimensions Manager")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto* row = new wxBoxSizer(wxHORIZONTAL); m_emulateToypad = new wxCheckBox(box, wxID_ANY, _("Emulate Dimensions Toypad")); m_emulateToypad->SetValue( GetConfig().emulated_usb_devices.emulate_dimensions_toypad); m_emulateToypad->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { GetConfig().emulated_usb_devices.emulate_dimensions_toypad = m_emulateToypad->IsChecked(); GetConfigHandle().Save(); }); row->Add(m_emulateToypad, 1, wxEXPAND | wxALL, 2); box_sizer->Add(row, 1, wxEXPAND | wxALL, 2); auto* top_row = new wxBoxSizer(wxHORIZONTAL); auto* bottom_row = new wxBoxSizer(wxHORIZONTAL); top_row->Add(AddDimensionPanel(2, 0, box), 1, wxEXPAND | wxALL, 2); top_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); top_row->Add(AddDimensionPanel(1, 1, box), 1, wxEXPAND | wxALL, 2); top_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); top_row->Add(AddDimensionPanel(3, 2, box), 1, wxEXPAND | wxALL, 2); bottom_row->Add(AddDimensionPanel(2, 3, box), 1, wxEXPAND | wxALL, 2); bottom_row->Add(AddDimensionPanel(2, 4, box), 1, wxEXPAND | wxALL, 2); bottom_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 0); bottom_row->Add(AddDimensionPanel(3, 5, box), 1, wxEXPAND | wxALL, 2); bottom_row->Add(AddDimensionPanel(3, 6, box), 1, wxEXPAND | wxALL, 2); box_sizer->Add(top_row, 1, wxEXPAND | wxALL, 2); box_sizer->Add(bottom_row, 1, wxEXPAND | wxALL, 2); panel_sizer->Add(box_sizer, 1, wxEXPAND | wxALL, 2); panel->SetSizerAndFit(panel_sizer); return panel; } wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, wxStaticBox* box) { auto* row = new wxBoxSizer(wxHORIZONTAL); row->Add(new wxStaticText(box, wxID_ANY, fmt::format("{} {}", _("Skylander").ToStdString(), rowNumber + 1)), 1, wxEXPAND | wxALL, 2); m_skylanderSlots[rowNumber] = new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY); m_skylanderSlots[rowNumber]->SetMinSize(wxSize(150, -1)); m_skylanderSlots[rowNumber]->Disable(); row->Add(m_skylanderSlots[rowNumber], 1, wxEXPAND | wxALL, 2); auto* loadButton = new wxButton(box, wxID_ANY, _("Load")); loadButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { LoadSkylander(rowNumber); }); auto* createButton = new wxButton(box, wxID_ANY, _("Create")); createButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { CreateSkylander(rowNumber); }); auto* clearButton = new wxButton(box, wxID_ANY, _("Clear")); clearButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { ClearSkylander(rowNumber); }); row->Add(loadButton, 1, wxEXPAND | wxALL, 2); row->Add(createButton, 1, wxEXPAND | wxALL, 2); row->Add(clearButton, 1, wxEXPAND | wxALL, 2); return row; } wxBoxSizer* EmulatedUSBDeviceFrame::AddInfinityRow(wxString name, uint8 rowNumber, wxStaticBox* box) { auto* row = new wxBoxSizer(wxHORIZONTAL); row->Add(new wxStaticText(box, wxID_ANY, name), 1, wxEXPAND | wxALL, 2); m_infinitySlots[rowNumber] = new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY); m_infinitySlots[rowNumber]->SetMinSize(wxSize(150, -1)); m_infinitySlots[rowNumber]->Disable(); row->Add(m_infinitySlots[rowNumber], 1, wxALL | wxEXPAND, 5); auto* loadButton = new wxButton(box, wxID_ANY, _("Load")); loadButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { LoadFigure(rowNumber); }); auto* createButton = new wxButton(box, wxID_ANY, _("Create")); createButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { CreateFigure(rowNumber); }); auto* clearButton = new wxButton(box, wxID_ANY, _("Clear")); clearButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { ClearFigure(rowNumber); }); row->Add(loadButton, 1, wxEXPAND | wxALL, 2); row->Add(createButton, 1, wxEXPAND | wxALL, 2); row->Add(clearButton, 1, wxEXPAND | wxALL, 2); return row; } wxBoxSizer* EmulatedUSBDeviceFrame::AddDimensionPanel(uint8 pad, uint8 index, wxStaticBox* box) { auto* panel = new wxBoxSizer(wxVERTICAL); auto* combo_row = new wxBoxSizer(wxHORIZONTAL); m_dimensionSlots[index] = new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY); combo_row->Add(m_dimensionSlots[index], 1, wxEXPAND | wxALL, 2); auto* move_button = new wxButton(box, wxID_ANY, _("Move")); move_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { MoveMinifig(pad, index); }); combo_row->Add(move_button, 1, wxEXPAND | wxALL, 2); auto* button_row = new wxBoxSizer(wxHORIZONTAL); auto* load_button = new wxButton(box, wxID_ANY, _("Load")); load_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { LoadMinifig(pad, index); }); auto* clear_button = new wxButton(box, wxID_ANY, _("Clear")); clear_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { ClearMinifig(pad, index); }); auto* create_button = new wxButton(box, wxID_ANY, _("Create")); create_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { CreateMinifig(pad, index); }); button_row->Add(clear_button, 1, wxEXPAND | wxALL, 2); button_row->Add(create_button, 1, wxEXPAND | wxALL, 2); button_row->Add(load_button, 1, wxEXPAND | wxALL, 2); panel->Add(combo_row, 1, wxEXPAND | wxALL, 2); panel->Add(button_row, 1, wxEXPAND | wxALL, 2); return panel; } void EmulatedUSBDeviceFrame::LoadSkylander(uint8 slot) { wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "", "Skylander files (*.sky;*.bin;*.dump;*.dmp)|*.sky;*.bin;*.dump;*.dmp", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) return; LoadSkylanderPath(slot, openFileDialog.GetPath()); } void EmulatedUSBDeviceFrame::LoadSkylanderPath(uint8 slot, wxString path) { std::unique_ptr skyFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); if (!skyFile) { wxMessageDialog open_error(this, "Error Opening File: " + path); open_error.ShowModal(); return; } std::array fileData; if (skyFile->readData(fileData.data(), fileData.size()) != fileData.size()) { wxMessageDialog open_error(this, "Failed to read file! File was too small"); open_error.ShowModal(); return; } ClearSkylander(slot); uint16 skyId = uint16(fileData[0x11]) << 8 | uint16(fileData[0x10]); uint16 skyVar = uint16(fileData[0x1D]) << 8 | uint16(fileData[0x1C]); uint8 portalSlot = nsyshid::g_skyportal.LoadSkylander(fileData.data(), std::move(skyFile)); m_skySlots[slot] = std::tuple(portalSlot, skyId, skyVar); UpdateSkylanderEdits(); } void EmulatedUSBDeviceFrame::CreateSkylander(uint8 slot) { CreateSkylanderDialog create_dlg(this, slot); create_dlg.ShowModal(); if (create_dlg.GetReturnCode() == 1) { LoadSkylanderPath(slot, create_dlg.GetFilePath()); } } void EmulatedUSBDeviceFrame::ClearSkylander(uint8 slot) { if (auto slotInfos = m_skySlots[slot]) { auto [curSlot, id, var] = slotInfos.value(); nsyshid::g_skyportal.RemoveSkylander(curSlot); m_skySlots[slot] = {}; UpdateSkylanderEdits(); } } CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) : wxDialog(parent, wxID_ANY, _("Skylander Figure Creator"), wxDefaultPosition, wxSize(500, 150)) { auto* sizer = new wxBoxSizer(wxVERTICAL); auto* comboRow = new wxBoxSizer(wxHORIZONTAL); auto* comboBox = new wxComboBox(this, wxID_ANY); comboBox->Append("---Select---", reinterpret_cast(0xFFFFFFFF)); wxArrayString filterlist; for (const auto& it : nsyshid::g_skyportal.GetListSkylanders()) { const uint32 variant = uint32(uint32(it.first.first) << 16) | uint32(it.first.second); comboBox->Append(it.second, reinterpret_cast(variant)); filterlist.Add(it.second); } comboBox->SetSelection(0); bool enabled = comboBox->AutoComplete(filterlist); comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2); auto* idVarRow = new wxBoxSizer(wxHORIZONTAL); wxIntegerValidator validator; auto* labelId = new wxStaticText(this, wxID_ANY, "ID:"); auto* labelVar = new wxStaticText(this, wxID_ANY, "Variant:"); auto* editId = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); auto* editVar = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); idVarRow->Add(labelId, 1, wxALL, 5); idVarRow->Add(editId, 1, wxALL, 5); idVarRow->Add(labelVar, 1, wxALL, 5); idVarRow->Add(editVar, 1, wxALL, 5); auto* buttonRow = new wxBoxSizer(wxHORIZONTAL); auto* createButton = new wxButton(this, wxID_ANY, _("Create")); createButton->Bind(wxEVT_BUTTON, [editId, editVar, this](wxCommandEvent&) { long longSkyId; if (!editId->GetValue().ToLong(&longSkyId) || longSkyId > 0xFFFF) { wxMessageDialog idError(this, "Error Converting ID!", "ID Entered is Invalid"); idError.ShowModal(); return; } long longSkyVar; if (!editVar->GetValue().ToLong(&longSkyVar) || longSkyVar > 0xFFFF) { wxMessageDialog idError(this, "Error Converting Variant!", "Variant Entered is Invalid"); idError.ShowModal(); return; } uint16 skyId = longSkyId & 0xFFFF; uint16 skyVar = longSkyVar & 0xFFFF; wxString predefName = nsyshid::g_skyportal.FindSkylander(skyId, skyVar) + ".sky"; wxFileDialog saveFileDialog(this, _("Create Skylander file"), "", predefName, "SKY files (*.sky)|*.sky", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (saveFileDialog.ShowModal() == wxID_CANCEL) return; m_filePath = saveFileDialog.GetPath(); if (!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar)) { wxMessageDialog errorMessage(this, "Failed to create file"); errorMessage.ShowModal(); this->EndModal(0); return; } this->EndModal(1); }); auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { this->EndModal(0); }); comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editId, editVar, this](wxCommandEvent&) { const uint64 sky_info = reinterpret_cast(comboBox->GetClientData(comboBox->GetSelection())); if (sky_info != 0xFFFFFFFF) { const uint16 skyId = sky_info >> 16; const uint16 skyVar = sky_info & 0xFFFF; editId->SetValue(wxString::Format(wxT("%i"), skyId)); editVar->SetValue(wxString::Format(wxT("%i"), skyVar)); } }); buttonRow->Add(createButton, 1, wxALL, 5); buttonRow->Add(cancelButton, 1, wxALL, 5); sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2); sizer->Add(idVarRow, 1, wxEXPAND | wxALL, 2); sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2); this->SetSizer(sizer); this->Centre(wxBOTH); } wxString CreateSkylanderDialog::GetFilePath() const { return m_filePath; } void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() { for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++) { std::string displayString; if (auto sd = m_skySlots[i]) { auto [portalSlot, skyId, skyVar] = sd.value(); displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar); } else { displayString = "None"; } m_skylanderSlots[i]->ChangeValue(displayString); } } void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot) { wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "", "BIN files (*.bin)|*.bin", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) { wxMessageDialog errorMessage(this, "File Okay Error"); errorMessage.ShowModal(); return; } LoadFigurePath(slot, openFileDialog.GetPath()); } void EmulatedUSBDeviceFrame::LoadFigurePath(uint8 slot, wxString path) { std::unique_ptr infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); if (!infFile) { wxMessageDialog errorMessage(this, "File Open Error"); errorMessage.ShowModal(); return; } std::array fileData; if (infFile->readData(fileData.data(), fileData.size()) != fileData.size()) { wxMessageDialog open_error(this, "Failed to read file! File was too small"); open_error.ShowModal(); return; } ClearFigure(slot); uint32 number = nsyshid::g_infinitybase.LoadFigure(fileData, std::move(infFile), slot); m_infinitySlots[slot]->ChangeValue(nsyshid::g_infinitybase.FindFigure(number).second); } void EmulatedUSBDeviceFrame::CreateFigure(uint8 slot) { cemuLog_log(LogType::Force, "Create Figure: {}", slot); CreateInfinityFigureDialog create_dlg(this, slot); create_dlg.ShowModal(); if (create_dlg.GetReturnCode() == 1) { LoadFigurePath(slot, create_dlg.GetFilePath()); } } void EmulatedUSBDeviceFrame::ClearFigure(uint8 slot) { m_infinitySlots[slot]->ChangeValue("None"); nsyshid::g_infinitybase.RemoveFigure(slot); } CreateInfinityFigureDialog::CreateInfinityFigureDialog(wxWindow* parent, uint8 slot) : wxDialog(parent, wxID_ANY, _("Infinity Figure Creator"), wxDefaultPosition, wxSize(500, 150)) { auto* sizer = new wxBoxSizer(wxVERTICAL); auto* comboRow = new wxBoxSizer(wxHORIZONTAL); auto* comboBox = new wxComboBox(this, wxID_ANY); comboBox->Append("---Select---", reinterpret_cast(0xFFFFFF)); wxArrayString filterlist; for (const auto& it : nsyshid::g_infinitybase.GetFigureList()) { const uint32 figure = it.first; if ((slot == 0 && ((figure > 0x1E8480 && figure < 0x2DC6BF) || (figure > 0x3D0900 && figure < 0x4C4B3F))) || ((slot == 1 || slot == 2) && (figure > 0x3D0900 && figure < 0x4C4B3F)) || ((slot == 3 || slot == 6) && figure < 0x1E847F) || ((slot == 4 || slot == 5 || slot == 7 || slot == 8) && (figure > 0x2DC6C0 && figure < 0x3D08FF))) { comboBox->Append(it.second.second, reinterpret_cast(figure)); filterlist.Add(it.second.second); } } comboBox->SetSelection(0); bool enabled = comboBox->AutoComplete(filterlist); comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2); auto* figNumRow = new wxBoxSizer(wxHORIZONTAL); wxIntegerValidator validator; auto* labelFigNum = new wxStaticText(this, wxID_ANY, "Figure Number:"); auto* editFigNum = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); figNumRow->Add(labelFigNum, 1, wxALL, 5); figNumRow->Add(editFigNum, 1, wxALL, 5); auto* buttonRow = new wxBoxSizer(wxHORIZONTAL); auto* createButton = new wxButton(this, wxID_ANY, _("Create")); createButton->Bind(wxEVT_BUTTON, [editFigNum, this](wxCommandEvent&) { long longFigNum; if (!editFigNum->GetValue().ToLong(&longFigNum)) { wxMessageDialog idError(this, "Error Converting Figure Number!", "Number Entered is Invalid"); idError.ShowModal(); this->EndModal(0); } uint32 figNum = longFigNum & 0xFFFFFFFF; auto figure = nsyshid::g_infinitybase.FindFigure(figNum); wxString predefName = figure.second + ".bin"; wxFileDialog saveFileDialog(this, _("Create Infinity Figure file"), "", predefName, "BIN files (*.bin)|*.bin", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (saveFileDialog.ShowModal() == wxID_CANCEL) this->EndModal(0); m_filePath = saveFileDialog.GetPath(); nsyshid::g_infinitybase.CreateFigure(_utf8ToPath(m_filePath.utf8_string()), figNum, figure.first); this->EndModal(1); }); auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { this->EndModal(0); }); comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editFigNum, this](wxCommandEvent&) { const uint64 fig_info = reinterpret_cast(comboBox->GetClientData(comboBox->GetSelection())); if (fig_info != 0xFFFFFF) { const uint32 figNum = fig_info & 0xFFFFFFFF; editFigNum->SetValue(wxString::Format(wxT("%i"), figNum)); } }); buttonRow->Add(createButton, 1, wxALL, 5); buttonRow->Add(cancelButton, 1, wxALL, 5); sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2); sizer->Add(figNumRow, 1, wxEXPAND | wxALL, 2); sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2); this->SetSizer(sizer); this->Centre(wxBOTH); } wxString CreateInfinityFigureDialog::GetFilePath() const { return m_filePath; } void EmulatedUSBDeviceFrame::LoadMinifig(uint8 pad, uint8 index) { wxFileDialog openFileDialog(this, _("Load Dimensions Figure"), "", "", "Dimensions files (*.bin)|*.bin", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) return; LoadMinifigPath(openFileDialog.GetPath(), pad, index); } void EmulatedUSBDeviceFrame::LoadMinifigPath(wxString path_name, uint8 pad, uint8 index) { std::unique_ptr dim_file(FileStream::openFile2(_utf8ToPath(path_name.utf8_string()), true)); if (!dim_file) { wxMessageDialog errorMessage(this, "Failed to open minifig file"); errorMessage.ShowModal(); return; } std::array file_data; if (dim_file->readData(file_data.data(), file_data.size()) != file_data.size()) { wxMessageDialog errorMessage(this, "Failed to read minifig file data"); errorMessage.ShowModal(); return; } ClearMinifig(pad, index); uint32 id = nsyshid::g_dimensionstoypad.LoadFigure(file_data, std::move(dim_file), pad, index); m_dimensionSlots[index]->ChangeValue(nsyshid::g_dimensionstoypad.FindFigure(id)); m_dimSlots[index] = id; } void EmulatedUSBDeviceFrame::ClearMinifig(uint8 pad, uint8 index) { nsyshid::g_dimensionstoypad.RemoveFigure(pad, index, true); m_dimensionSlots[index]->ChangeValue("None"); m_dimSlots[index] = std::nullopt; } void EmulatedUSBDeviceFrame::CreateMinifig(uint8 pad, uint8 index) { CreateDimensionFigureDialog create_dlg(this); create_dlg.ShowModal(); if (create_dlg.GetReturnCode() == 1) { LoadMinifigPath(create_dlg.GetFilePath(), pad, index); } } void EmulatedUSBDeviceFrame::MoveMinifig(uint8 pad, uint8 index) { if (!m_dimSlots[index]) return; MoveDimensionFigureDialog move_dlg(this, index); nsyshid::g_dimensionstoypad.TempRemove(index); move_dlg.ShowModal(); if (move_dlg.GetReturnCode() == 1) { nsyshid::g_dimensionstoypad.MoveFigure(move_dlg.GetNewPad(), move_dlg.GetNewIndex(), pad, index); if (index != move_dlg.GetNewIndex()) { m_dimSlots[move_dlg.GetNewIndex()] = m_dimSlots[index]; m_dimensionSlots[move_dlg.GetNewIndex()]->ChangeValue(m_dimensionSlots[index]->GetValue()); m_dimSlots[index] = std::nullopt; m_dimensionSlots[index]->ChangeValue("None"); } } else { nsyshid::g_dimensionstoypad.CancelRemove(index); } } CreateDimensionFigureDialog::CreateDimensionFigureDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _("Dimensions Figure Creator"), wxDefaultPosition, wxSize(500, 200)) { auto* sizer = new wxBoxSizer(wxVERTICAL); auto* comboRow = new wxBoxSizer(wxHORIZONTAL); auto* comboBox = new wxComboBox(this, wxID_ANY); comboBox->Append("---Select---", reinterpret_cast(0xFFFFFFFF)); wxArrayString filterlist; for (const auto& it : nsyshid::g_dimensionstoypad.GetListMinifigs()) { const uint32 figure = it.first; comboBox->Append(it.second, reinterpret_cast(figure)); filterlist.Add(it.second); } comboBox->SetSelection(0); bool enabled = comboBox->AutoComplete(filterlist); comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2); auto* figNumRow = new wxBoxSizer(wxHORIZONTAL); wxIntegerValidator validator; auto* labelFigNum = new wxStaticText(this, wxID_ANY, "Figure Number:"); auto* editFigNum = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); figNumRow->Add(labelFigNum, 1, wxALL, 5); figNumRow->Add(editFigNum, 1, wxALL, 5); auto* buttonRow = new wxBoxSizer(wxHORIZONTAL); auto* createButton = new wxButton(this, wxID_ANY, _("Create")); createButton->Bind(wxEVT_BUTTON, [editFigNum, this](wxCommandEvent&) { long longFigNum; if (!editFigNum->GetValue().ToLong(&longFigNum) || longFigNum > 0xFFFF) { wxMessageDialog idError(this, "Error Converting Figure Number!", "Number Entered is Invalid"); idError.ShowModal(); this->EndModal(0); } uint16 figNum = longFigNum & 0xFFFF; auto figure = nsyshid::g_dimensionstoypad.FindFigure(figNum); wxString predefName = figure + ".bin"; wxFileDialog saveFileDialog(this, _("Create Dimensions Figure file"), "", predefName, "BIN files (*.bin)|*.bin", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (saveFileDialog.ShowModal() == wxID_CANCEL) this->EndModal(0); m_filePath = saveFileDialog.GetPath(); nsyshid::g_dimensionstoypad.CreateFigure(_utf8ToPath(m_filePath.utf8_string()), figNum); this->EndModal(1); }); auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { this->EndModal(0); }); comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editFigNum, this](wxCommandEvent&) { const uint64 fig_info = reinterpret_cast(comboBox->GetClientData(comboBox->GetSelection())); if (fig_info != 0xFFFF) { const uint16 figNum = fig_info & 0xFFFF; editFigNum->SetValue(wxString::Format(wxT("%i"), figNum)); } }); buttonRow->Add(createButton, 1, wxALL, 5); buttonRow->Add(cancelButton, 1, wxALL, 5); sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2); sizer->Add(figNumRow, 1, wxEXPAND | wxALL, 2); sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2); this->SetSizer(sizer); this->Centre(wxBOTH); } wxString CreateDimensionFigureDialog::GetFilePath() const { return m_filePath; } MoveDimensionFigureDialog::MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* parent, uint8 currentIndex) : wxDialog(parent, wxID_ANY, _("Dimensions Figure Mover"), wxDefaultPosition, wxSize(700, 300)) { auto* sizer = new wxGridSizer(2, 5, 10, 10); std::array, 7> ids = parent->GetCurrentMinifigs(); sizer->Add(AddMinifigSlot(2, 0, currentIndex, ids[0]), 1, wxALL, 5); sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); sizer->Add(AddMinifigSlot(1, 1, currentIndex, ids[1]), 1, wxALL, 5); sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); sizer->Add(AddMinifigSlot(3, 2, currentIndex, ids[2]), 1, wxALL, 5); sizer->Add(AddMinifigSlot(2, 3, currentIndex, ids[3]), 1, wxALL, 5); sizer->Add(AddMinifigSlot(2, 4, currentIndex, ids[4]), 1, wxALL, 5); sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); sizer->Add(AddMinifigSlot(3, 5, currentIndex, ids[5]), 1, wxALL, 5); sizer->Add(AddMinifigSlot(3, 6, currentIndex, ids[6]), 1, wxALL, 5); this->SetSizer(sizer); this->Centre(wxBOTH); } wxBoxSizer* MoveDimensionFigureDialog::AddMinifigSlot(uint8 pad, uint8 index, uint8 currentIndex, std::optional currentId) { auto* panel = new wxBoxSizer(wxVERTICAL); auto* label = new wxStaticText(this, wxID_ANY, "None"); if (currentId) label->SetLabel(nsyshid::g_dimensionstoypad.FindFigure(currentId.value())); auto* moveButton = new wxButton(this, wxID_ANY, _("Move Here")); if (index == currentIndex) moveButton->SetLabelText("Pick up and Place"); moveButton->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { m_newPad = pad; m_newIndex = index; this->EndModal(1); }); panel->Add(label, 1, wxALL, 5); panel->Add(moveButton, 1, wxALL, 5); return panel; } uint8 MoveDimensionFigureDialog::GetNewPad() const { return m_newPad; } uint8 MoveDimensionFigureDialog::GetNewIndex() const { return m_newIndex; } std::array, 7> EmulatedUSBDeviceFrame::GetCurrentMinifigs() { return m_dimSlots; } ================================================ FILE: src/gui/wxgui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h ================================================ #pragma once #include #include #include #include "Cafe/OS/libs/nsyshid/Infinity.h" #include "Cafe/OS/libs/nsyshid/Skylander.h" class wxBoxSizer; class wxCheckBox; class wxFlexGridSizer; class wxNotebook; class wxPanel; class wxStaticBox; class wxString; class wxTextCtrl; class EmulatedUSBDeviceFrame : public wxFrame { public: EmulatedUSBDeviceFrame(wxWindow* parent); ~EmulatedUSBDeviceFrame(); std::array, 7> GetCurrentMinifigs(); private: wxCheckBox* m_emulatePortal; wxCheckBox* m_emulateBase; wxCheckBox* m_emulateToypad; std::array m_skylanderSlots; std::array m_infinitySlots; std::array m_dimensionSlots; std::array>, nsyshid::MAX_SKYLANDERS> m_skySlots; std::array, 7> m_dimSlots; wxPanel* AddSkylanderPage(wxNotebook* notebook); wxPanel* AddInfinityPage(wxNotebook* notebook); wxPanel* AddDimensionsPage(wxNotebook* notebook); wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box); wxBoxSizer* AddInfinityRow(wxString name, uint8 row_number, wxStaticBox* box); wxBoxSizer* AddDimensionPanel(uint8 pad, uint8 index, wxStaticBox* box); void LoadSkylander(uint8 slot); void LoadSkylanderPath(uint8 slot, wxString path); void CreateSkylander(uint8 slot); void ClearSkylander(uint8 slot); void UpdateSkylanderEdits(); void LoadFigure(uint8 slot); void LoadFigurePath(uint8 slot, wxString path); void CreateFigure(uint8 slot); void ClearFigure(uint8 slot); void LoadMinifig(uint8 pad, uint8 index); void LoadMinifigPath(wxString path_name, uint8 pad, uint8 index); void CreateMinifig(uint8 pad, uint8 index); void ClearMinifig(uint8 pad, uint8 index); void MoveMinifig(uint8 pad, uint8 index); }; class CreateSkylanderDialog : public wxDialog { public: explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot); wxString GetFilePath() const; protected: wxString m_filePath; }; class CreateInfinityFigureDialog : public wxDialog { public: explicit CreateInfinityFigureDialog(wxWindow* parent, uint8 slot); wxString GetFilePath() const; protected: wxString m_filePath; }; class CreateDimensionFigureDialog : public wxDialog { public: explicit CreateDimensionFigureDialog(wxWindow* parent); wxString GetFilePath() const; protected: wxString m_filePath; }; class MoveDimensionFigureDialog : public wxDialog { public: explicit MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* parent, uint8 currentIndex); uint8 GetNewPad() const; uint8 GetNewIndex() const; protected: uint8 m_newIndex = 0; uint8 m_newPad = 0; private: wxBoxSizer* AddMinifigSlot(uint8 pad, uint8 index, uint8 oldIndex, std::optional currentId); }; ================================================ FILE: src/gui/wxgui/GameProfileWindow.cpp ================================================ #include "wxgui/GameProfileWindow.h" #include #include #include #include #include #include #include #include "wxgui/helpers/wxHelpers.h" #include "input/InputManager.h" #if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD #include "resource/embedded/resources.h" #endif GameProfileWindow::GameProfileWindow(wxWindow* parent, uint64_t title_id) : wxFrame(parent, wxID_ANY, _("Edit game profile"), wxDefaultPosition, wxSize{ 390, 350 }, wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER | wxTAB_TRAVERSAL | wxSYSTEM_MENU), m_title_id(title_id) { SetIcon(wxICON(X_GAME_PROFILE)); m_game_profile.Reset(); m_game_profile.Load(title_id); this->SetSizeHints(wxDefaultSize, wxDefaultSize); auto* main_sizer = new wxBoxSizer(wxVERTICAL); auto* m_notebook = new wxNotebook(this, wxID_ANY); // general { auto* panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto* sizer = new wxBoxSizer(wxVERTICAL); { auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(panel, wxID_ANY, _("General")), wxVERTICAL); auto* box = box_sizer->GetStaticBox(); m_load_libs = new wxCheckBox(box, wxID_ANY, _("Load shared libraries")); m_load_libs->SetToolTip(_("EXPERT OPTION\nThis option will load libraries from the cafeLibs directory")); box_sizer->Add(m_load_libs, 0, wxALL, 5); m_start_with_padview = new wxCheckBox(box, wxID_ANY, _("Launch with gamepad view")); m_start_with_padview->SetToolTip(_("Games will be launched with gamepad view toggled as default. The view can be toggled with CTRL + TAB")); box_sizer->Add(m_start_with_padview, 0, wxALL, 5); sizer->Add(box_sizer, 0, wxEXPAND, 5); } // cpu { auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(panel, wxID_ANY, _("CPU")), wxVERTICAL); auto* box = box_sizer->GetStaticBox(); auto* first_row = new wxFlexGridSizer(0, 2, 0, 0); first_row->SetFlexibleDirection(wxBOTH); first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); first_row->Add(new wxStaticText(box, wxID_ANY, _("Mode")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString cpu_modes[] = { _("Single-core interpreter"), _("Single-core recompiler"), _("Multi-core recompiler"), _("Auto (recommended)") }; const sint32 m_cpu_modeNChoices = std::size(cpu_modes); m_cpu_mode = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_cpu_modeNChoices, cpu_modes, 0); m_cpu_mode->SetToolTip(_("Set the CPU emulation mode")); first_row->Add(m_cpu_mode, 0, wxALL, 5); first_row->Add(new wxStaticText(box, wxID_ANY, _("Thread quantum")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* quantum_sizer = new wxFlexGridSizer(0, 2, 0, 0); quantum_sizer->SetFlexibleDirection(wxBOTH); quantum_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); wxString quantum_values[] = { "20000", "45000", "60000", "80000" ,"100000" }; m_thread_quantum = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(quantum_values), quantum_values); m_thread_quantum->SetMinSize(wxSize(85, -1)); m_thread_quantum->SetToolTip(_("EXPERT OPTION\nSet the maximum thread slice runtime (in virtual cycles)")); quantum_sizer->Add(m_thread_quantum, 0, wxALL, 5); quantum_sizer->Add(new wxStaticText(box, wxID_ANY, _("cycles")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); first_row->Add(quantum_sizer, 0, wxEXPAND, 5); box_sizer->Add(first_row, 0, wxEXPAND, 5); sizer->Add(box_sizer, 0, wxEXPAND, 5); } panel->SetSizer(sizer); panel->Layout(); sizer->Fit(panel); m_notebook->AddPage(panel, _("General"), true); } // graphic { auto* panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto* sizer = new wxBoxSizer(wxVERTICAL); //m_extended_texture_readback = new wxCheckBox(panel, wxID_ANY, _("Extended texture readback")); //m_extended_texture_readback->SetToolTip(_("Improves emulation accuracy of CPU to GPU memory access at the cost of performance. Required for some games.")); //sizer->Add(m_extended_texture_readback, 0, wxALL, 5); auto* first_row = new wxFlexGridSizer(0, 2, 0, 0); first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); /*first_row->Add(new wxStaticText(panel, wxID_ANY, _("Precompiled shaders")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString precompiled_modes[] = { _("auto"), _("enable"), _("disable") }; const sint32 precompiled_count = std::size(precompiled_modes); m_precompiled = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, precompiled_count, precompiled_modes, 0); m_precompiled->SetToolTip(_("Precompiled shaders can speed up the load time on the shader loading screen.\nAuto will enable it for AMD/Intel but disable it for NVIDIA GPUs as a workaround for a driver bug.\n\nRecommended: Auto")); first_row->Add(m_precompiled, 0, wxALL, 5);*/ first_row->Add(new wxStaticText(panel, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString gapi_values[] = { "", "OpenGL", "Vulkan", #if ENABLE_METAL "Metal" #endif }; m_graphic_api = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(gapi_values), gapi_values); first_row->Add(m_graphic_api, 0, wxALL, 5); first_row->Add(new wxStaticText(panel, wxID_ANY, _("Shader multiplication accuracy")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString mul_values[] = { _("false"), _("true")}; m_shader_mul_accuracy = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(mul_values), mul_values); m_shader_mul_accuracy->SetToolTip(_("EXPERT OPTION\nControls the accuracy of floating point multiplication in shaders.\n\nRecommended: true")); first_row->Add(m_shader_mul_accuracy, 0, wxALL, 5); #if ENABLE_METAL first_row->Add(new wxStaticText(panel, wxID_ANY, _("Shader fast math")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString math_values[] = { _("false"), _("true") }; m_shader_fast_math = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(math_values), math_values); m_shader_fast_math->SetToolTip(_("EXPERT OPTION\nEnables fast math for all shaders. May (rarely) cause graphical bugs.\n\nMetal only\n\nRecommended: true")); first_row->Add(m_shader_fast_math, 0, wxALL, 5); first_row->Add(new wxStaticText(panel, wxID_ANY, _("Metal buffer cache mode")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString cache_values[] = { _("auto"), _("device private"), _("device shared"), _("host") }; m_metal_buffer_cache_mode = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(cache_values), cache_values); m_metal_buffer_cache_mode->SetToolTip(_("EXPERT OPTION\nDecides how the buffer cache memory will be managed.\n\nMetal only\n\nRecommended: auto")); first_row->Add(m_metal_buffer_cache_mode, 0, wxALL, 5); first_row->Add(new wxStaticText(panel, wxID_ANY, _("Position invariance")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString pos_values[] = { _("auto"), _("false"), _("true") }; m_position_invariance = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(pos_values), pos_values); m_position_invariance->SetToolTip(_("EXPERT OPTION\nDisables most optimizations for vertex positions. May fix polygon cutouts or flickering in some games.\n\nMetal only\n\nRecommended: auto")); first_row->Add(m_position_invariance, 0, wxALL, 5); #endif /*first_row->Add(new wxStaticText(panel, wxID_ANY, _("GPU buffer cache accuracy")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString accuarcy_values[] = { _("high"), _("medium"), _("low") }; m_cache_accuracy = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(accuarcy_values), accuarcy_values); m_cache_accuracy->SetToolTip(_("Lower value results in higher performance, but may cause graphical issues")); first_row->Add(m_cache_accuracy, 0, wxALL, 5);*/ sizer->Add(first_row, 0, wxEXPAND, 5); panel->SetSizer(sizer); panel->Layout(); sizer->Fit(panel); m_notebook->AddPage(panel, _("Graphic"), false); } //// audio //{ // auto panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); // wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); // m_disable_audio = new wxCheckBox(panel, wxID_ANY, _("Disable Audio"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE | wxCHK_ALLOW_3RD_STATE_FOR_USER); // sizer->Add(m_disable_audio, 0, wxALL, 5); // panel->SetSizer(sizer); // panel->Layout(); // sizer->Fit(panel); // m_notebook->AddPage(panel, _("Audio"), false); //} // controller { auto panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); wxBoxSizer* sizer; sizer = new wxBoxSizer(wxVERTICAL); wxFlexGridSizer* profile_sizer; profile_sizer = new wxFlexGridSizer(0, 2, 0, 0); profile_sizer->SetFlexibleDirection(wxBOTH); profile_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); for (int i = 0; i < 8; ++i) { profile_sizer->Add(new wxStaticText(panel, wxID_ANY, formatWxString(_("Controller {}"), i + 1)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_controller_profile[i] = new wxComboBox(panel, wxID_ANY,"", wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_DROPDOWN| wxCB_READONLY); m_controller_profile[i]->SetMinSize(wxSize(250, -1)); m_controller_profile[i]->Bind(wxEVT_COMBOBOX_DROPDOWN, &GameProfileWindow::OnControllerProfileDropdown, this); m_controller_profile[i]->SetToolTip(_("Forces a given controller profile")); profile_sizer->Add(m_controller_profile[i], 0, wxALL, 5); } sizer->Add(profile_sizer, 0, wxEXPAND, 5); panel->SetSizer(sizer); panel->Layout(); sizer->Fit(panel); m_notebook->AddPage(panel, _("Controller"), false); } main_sizer->Add(m_notebook, 1, wxEXPAND | wxALL, 5); this->SetSizer(main_sizer); this->Layout(); this->Centre(wxBOTH); ApplyProfile(); } GameProfileWindow::~GameProfileWindow() { SaveProfile(); } void GameProfileWindow::OnStreamoutSizeChange(wxCommandEvent& event) { wxSlider* slider = wxDynamicCast(event.GetEventObject(), wxSlider); wxASSERT(slider); wxStaticText* text = wxDynamicCast(slider->GetClientData(), wxStaticText); wxASSERT(text); text->SetLabelText(fmt::format("{} MB", slider->GetValue())); event.Skip(); } void GameProfileWindow::OnControllerProfileDropdown(wxCommandEvent& event) { wxComboBox* cb = wxDynamicCast(event.GetEventObject(), wxComboBox); wxASSERT(cb); wxWindowUpdateLocker lock(cb); const auto selected_value = cb->GetStringSelection(); cb->Clear(); cb->Append(wxEmptyString); auto profiles = InputManager::get_profiles(); for (const auto& profile : profiles) { cb->Append(wxString::FromUTF8(profile)); } cb->SetStringSelection(selected_value); } void GameProfileWindow::SetProfileInt(gameProfileIntegerOption_t& option, wxCheckBox* checkbox, sint32 value) const { const auto state = checkbox->GetValue(); if (state) { option.isPresent = true; option.value = value; } else option.isPresent = false; } void GameProfileWindow::ApplyProfile() { if(m_game_profile.m_gameName) this->SetTitle(_("Edit game profile") + " - " + m_game_profile.m_gameName.value()); // general m_load_libs->SetValue(m_game_profile.m_loadSharedLibraries.value()); m_start_with_padview->SetValue(m_game_profile.m_startWithPadView); // cpu // wxString cpu_modes[] = { _("Singlecore-Interpreter"), _("Singlecore-Recompiler"), _("Triplecore-Recompiler"), _("Auto (recommended)") }; switch(m_game_profile.m_cpuMode.value()) { case CPUMode::SinglecoreInterpreter: m_cpu_mode->SetSelection(0); break; case CPUMode::SinglecoreRecompiler: m_cpu_mode->SetSelection(1); break; case CPUMode::DualcoreRecompiler: m_cpu_mode->SetSelection(2); break; case CPUMode::MulticoreRecompiler: m_cpu_mode->SetSelection(2); break; default: m_cpu_mode->SetSelection(3); } m_thread_quantum->SetStringSelection(fmt::format("{}", m_game_profile.m_threadQuantum)); // gpu if (!m_game_profile.m_graphics_api.has_value()) m_graphic_api->SetSelection(0); // selecting "" else m_graphic_api->SetSelection(1 + m_game_profile.m_graphics_api.value()); // "", OpenGL, Vulkan, Metal m_shader_mul_accuracy->SetSelection((int)m_game_profile.m_accurateShaderMul); #if ENABLE_METAL m_shader_fast_math->SetSelection((int)m_game_profile.m_shaderFastMath); m_metal_buffer_cache_mode->SetSelection((int)m_game_profile.m_metalBufferCacheMode); m_position_invariance->SetSelection((int)m_game_profile.m_positionInvariance); #endif //// audio //m_disable_audio->Set3StateValue(GetCheckboxState(m_game_profile.disableAudio)); // controller auto profiles = InputManager::get_profiles(); for (const auto& cb : m_controller_profile) { cb->Clear(); for (const auto& profile : profiles) { cb->Append(wxString::FromUTF8(profile)); } } for (int i = 0; i < InputManager::kMaxController; ++i) { const bool has_value = m_game_profile.m_controllerProfile[i].has_value(); if (has_value) { const auto& v = m_game_profile.m_controllerProfile[i].value(); m_controller_profile[i]->SetStringSelection(wxString::FromUTF8(v)); } else m_controller_profile[i]->SetSelection(wxNOT_FOUND); } } void GameProfileWindow::SaveProfile() { // update game profile struct m_game_profile.Reset(); // general m_game_profile.m_loadSharedLibraries = m_load_libs->GetValue(); m_game_profile.m_startWithPadView = m_start_with_padview->GetValue(); // cpu switch(m_cpu_mode->GetSelection()) { case 0: m_game_profile.m_cpuMode = CPUMode::SinglecoreInterpreter; break; case 1: m_game_profile.m_cpuMode = CPUMode::SinglecoreRecompiler; break; case 2: m_game_profile.m_cpuMode = CPUMode::MulticoreRecompiler; break; default: m_game_profile.m_cpuMode = CPUMode::Auto; } const wxString thread_quantum = m_thread_quantum->GetStringSelection(); if (!thread_quantum.empty()) { m_game_profile.m_threadQuantum = ConvertString(thread_quantum.ToStdString()); m_game_profile.m_threadQuantum = std::min(m_game_profile.m_threadQuantum, 536870912); m_game_profile.m_threadQuantum = std::max(m_game_profile.m_threadQuantum, 5000); } // gpu m_game_profile.m_accurateShaderMul = (AccurateShaderMulOption)m_shader_mul_accuracy->GetSelection(); if (m_game_profile.m_accurateShaderMul != AccurateShaderMulOption::False && m_game_profile.m_accurateShaderMul != AccurateShaderMulOption::True) m_game_profile.m_accurateShaderMul = AccurateShaderMulOption::True; // force a legal value #if ENABLE_METAL m_game_profile.m_shaderFastMath = (bool)m_shader_fast_math->GetSelection(); m_game_profile.m_metalBufferCacheMode = (MetalBufferCacheMode)m_metal_buffer_cache_mode->GetSelection(); m_game_profile.m_positionInvariance = (PositionInvariance)m_position_invariance->GetSelection(); #endif if (m_graphic_api->GetSelection() == 0) m_game_profile.m_graphics_api = {}; else m_game_profile.m_graphics_api = (GraphicAPI)(m_graphic_api->GetSelection() - 1); // "", OpenGL, Vulkan, Metal // controller for (int i = 0; i < 8; ++i) { if(m_controller_profile[i]->GetSelection() == wxNOT_FOUND) { m_game_profile.m_controllerProfile[i].reset(); continue; } const wxString profile_name = m_controller_profile[i]->GetStringSelection(); if (profile_name.empty()) m_game_profile.m_controllerProfile[i].reset(); else m_game_profile.m_controllerProfile[i] = profile_name.ToUTF8(); } // update game profile file m_game_profile.Save(m_title_id); } void GameProfileWindow::SetSliderValue(wxSlider* slider, sint32 new_value) const { wxASSERT(slider); slider->SetValue(new_value); wxCommandEvent slider_event(wxEVT_SLIDER, slider->GetId()); slider_event.SetEventObject(slider); slider_event.SetClientData((void*)IsFrozen()); wxPostEvent(slider->GetEventHandler(), slider_event); } ================================================ FILE: src/gui/wxgui/GameProfileWindow.h ================================================ #pragma once #include #include #include #include #include #include "Cafe/GameProfile/GameProfile.h" class GameProfileWindow : public wxFrame { public: GameProfileWindow(wxWindow* parent, uint64_t title_id); ~GameProfileWindow(); private: uint64_t m_title_id; GameProfile m_game_profile; void OnStreamoutSizeChange(wxCommandEvent& event); void OnControllerProfileDropdown(wxCommandEvent& event); void SetSliderValue(wxSlider* slider, sint32 new_value) const; void SetProfileInt(gameProfileIntegerOption_t& option, wxCheckBox* checkbox, sint32 value) const; void ApplyProfile(); void SaveProfile(); // general wxCheckBox* m_load_libs, *m_start_with_padview; // cpu wxChoice *m_cpu_mode; wxChoice* m_thread_quantum; // gpu //wxCheckBox* m_extended_texture_readback; //wxChoice* m_precompiled; wxChoice* m_graphic_api; wxChoice* m_shader_mul_accuracy; #if ENABLE_METAL wxChoice* m_shader_fast_math; wxChoice* m_metal_buffer_cache_mode; wxChoice* m_position_invariance; #endif //wxChoice* m_cache_accuracy; // audio //wxCheckBox* m_disable_audio; // controller wxComboBox* m_controller_profile[8]; }; ================================================ FILE: src/gui/wxgui/GameUpdateWindow.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/GameUpdateWindow.h" #include "util/helpers/helpers.h" #include #include #include "util/helpers/SystemException.h" #include "wxgui/CemuApp.h" #include "Cafe/TitleList/GameInfo.h" #include "wxgui/helpers/wxHelpers.h" #include "wxHelper.h" wxString _GetTitleIdTypeStr(TitleId titleId) { TitleIdParser tip(titleId); switch (tip.GetType()) { case TitleIdParser::TITLE_TYPE::AOC: return _("DLC"); case TitleIdParser::TITLE_TYPE::BASE_TITLE: return _("Base game"); case TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO: return _("Demo"); case TitleIdParser::TITLE_TYPE::SYSTEM_TITLE: case TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE: return _("System title"); case TitleIdParser::TITLE_TYPE::SYSTEM_DATA: return _("System data title"); case TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE: return _("Update"); default: break; } return "Unknown"; } bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath) { m_title_info = TitleInfo(metaPath); if (!m_title_info.IsValid()) return false; fs::path target_location = ActiveSettings::GetMlcPath(m_title_info.GetInstallPath()); std::error_code ec; if (fs::exists(target_location, ec)) { try { const TitleInfo tmp(target_location); if (!tmp.IsValid()) { // does not exist / is not valid. We allow to overwrite it } else { TitleIdParser tip(m_title_info.GetAppTitleId()); TitleIdParser tipOther(tmp.GetAppTitleId()); if (tip.GetType() != tipOther.GetType()) { auto typeStrToInstall = _GetTitleIdTypeStr(m_title_info.GetAppTitleId()); auto typeStrCurrentlyInstalled = _GetTitleIdTypeStr(tmp.GetAppTitleId()); auto wxMsg = _("It seems that there is already a title installed at the target location but it has a different type.\nCurrently installed: \'{}\' Installing: \'{}\'\n\nThis can happen for titles which were installed with very old Cemu versions.\nDo you still want to continue with the installation? It will replace the currently installed title."); wxMessageDialog dialog(this, formatWxString(wxMsg, typeStrCurrentlyInstalled, typeStrToInstall), _("Warning"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION); if (dialog.ShowModal() != wxID_YES) return false; } else if (tmp.GetAppTitleVersion() == m_title_info.GetAppTitleVersion()) { wxMessageDialog dialog(this, _("It seems that the selected title is already installed, do you want to reinstall it?"), _("Warning"), wxCENTRE | wxYES_NO); if (dialog.ShowModal() != wxID_YES) return false; } else if (tmp.GetAppTitleVersion() > m_title_info.GetAppTitleVersion()) { wxMessageDialog dialog(this, _("It seems that a newer version is already installed, do you still want to install the older version?"), _("Warning"), wxCENTRE | wxYES_NO); if (dialog.ShowModal() != wxID_YES) return false; } } // temp rename until done m_backup_folder = target_location; m_backup_folder.replace_extension(".backup"); std::error_code ec; while (fs::exists(m_backup_folder, ec) || ec) { fs::remove_all(m_backup_folder, ec); if (ec) { const auto error_msg = formatWxString(_("Error when trying to move former title installation:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return false; } // wait so filesystem doesnt std::this_thread::sleep_for(std::chrono::milliseconds(100)); } fs::rename(target_location, m_backup_folder); } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "GameUpdateWindow::ParseUpdate exist-error: {} at {}", ex.what(), _pathToUtf8(target_location)); } } m_target_path = target_location; fs::path source(metaPath); m_source_paths = { fs::path(source).append("content"), fs::path(source).append("code"), fs::path(source).append("meta") }; m_required_size = 0; for (auto& path : m_source_paths) { for (const fs::directory_entry& f : fs::recursive_directory_iterator(path)) { if (is_regular_file(f.path())) m_required_size += file_size(f.path()); } } const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath()); if (targetSpace.free <= m_required_size) { auto string = formatWxString(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024)); throw std::runtime_error(string.utf8_string()); } return true; } GameUpdateWindow::GameUpdateWindow(wxWindow& parent, const fs::path& filePath) : wxDialog(&parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), m_thread_state(ThreadRunning) { try { #if BOOST_OS_WINDOWS SetLastError(0); #endif if(!ParseUpdate(filePath)) throw AbortException(); } catch (const std::runtime_error& ex) { throw SystemException(ex); } auto sizer = new wxBoxSizer(wxVERTICAL); TitleIdParser tip(GetTitleId()); if (tip.GetType() == TitleIdParser::TITLE_TYPE::AOC) SetTitle(_("Installing DLC...")); else if (tip.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) SetTitle(_("Installing update...")); else if (tip.IsSystemTitle()) SetTitle(_("Installing system title...")); else SetTitle(_("Installing title...")); m_processBar = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL); m_processBar->SetValue(0); m_processBar->SetRange((sint32)(m_required_size / 1000)); sizer->Add(m_processBar, 0, wxALL | wxEXPAND, 5); wxButton* m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); m_cancelButton->Bind(wxEVT_BUTTON, &GameUpdateWindow::OnCancelButton, this); sizer->Add(m_cancelButton, 0, wxALIGN_RIGHT | wxALL, 5); this->SetSizer(sizer); this->Centre(wxBOTH); wxWindowBase::Layout(); wxWindowBase::Fit(); m_timer = new wxTimer(this); this->Bind(wxEVT_TIMER, &GameUpdateWindow::OnUpdate, this); this->Bind(wxEVT_CLOSE_WINDOW, &GameUpdateWindow::OnClose, this); m_timer->Start(250); m_thread_state = ThreadRunning; m_thread = std::thread(&GameUpdateWindow::ThreadWork, this); } void GameUpdateWindow::ThreadWork() { fs::directory_entry currentDirEntry; try { // create base directories for (auto& path : m_source_paths) { if (!path.has_stem()) continue; fs::path targetDir = fs::path(m_target_path) / path.stem(); create_directories(targetDir); } for (auto& path : m_source_paths) { if (m_thread_state == ThreadCanceled) break; if (!path.has_parent_path()) continue; const auto len = path.parent_path().string().size() + 1; for (const fs::directory_entry& f : fs::recursive_directory_iterator {path}) { if (m_thread_state == ThreadCanceled) break; currentDirEntry = f; fs::path relative(f.path().string().substr(len)); fs::path target = fs::path(m_target_path) / relative; if (is_directory(f)) { create_directories(target); continue; } copy(f, target, fs::copy_options::overwrite_existing); if (is_regular_file(f.path())) { m_processed_size += file_size(f.path()); } } } } catch (const std::exception& ex) { std::stringstream error_msg; error_msg << GetSystemErrorMessage(ex); if(currentDirEntry != fs::directory_entry{}) error_msg << fmt::format("\n{}\n{}",_("Current file:").utf8_string(), _pathToUtf8(currentDirEntry.path())); m_thread_exception = error_msg.str(); m_thread_state = ThreadCanceled; } if (m_thread_state == ThreadCanceled) { if(fs::exists(m_target_path)) fs::remove_all(m_target_path); } else m_thread_state = ThreadFinished; } GameUpdateWindow::~GameUpdateWindow() { m_timer->Stop(); if (m_thread.joinable()) m_thread.join(); } int GameUpdateWindow::ShowModal() { wxDialog::ShowModal(); return m_thread_state == ThreadCanceled ? wxID_CANCEL : wxID_OK; } void GameUpdateWindow::OnClose(wxCloseEvent& event) { if (m_thread_state == ThreadRunning) { wxMessageDialog dialog(this, _("Do you really want to cancel the installation process?\n\nCanceling the process will delete the applied files."), _("Info"), wxCENTRE | wxYES_NO); if (dialog.ShowModal() != wxID_YES) return; m_thread_state = ThreadCanceled; } m_timer->Stop(); if (m_thread.joinable()) m_thread.join(); if(!m_backup_folder.empty()) { if(m_thread_state == ThreadCanceled) { // restore backup try { if(fs::exists(m_target_path)) fs::remove_all(m_target_path); fs::rename(m_backup_folder, m_target_path); } catch (const std::exception& ex) { cemuLog_logDebug(LogType::Force, "can't restore update backup: {}",ex.what()); } } else { // delete backup try { if(fs::exists(m_backup_folder)) fs::remove_all(m_backup_folder); } catch (const std::exception& ex) { cemuLog_logDebug(LogType::Force, "can't delete update backup: {}",ex.what()); } } m_backup_folder.clear(); } event.Skip(); } void GameUpdateWindow::OnUpdate(const wxTimerEvent& event) { if (m_thread_state != ThreadRunning) { Close(); return; } const auto processedSize = (sint32)(m_processed_size / 1000); if (m_processBar->GetValue() != processedSize) m_processBar->SetValue(processedSize); } void GameUpdateWindow::OnCancelButton(const wxCommandEvent& event) { Close(); } ================================================ FILE: src/gui/wxgui/GameUpdateWindow.h ================================================ #pragma once #include "Cafe/TitleList/GameInfo.h" #include #include #include #include #include #include #include // thrown if users doesn't wish to reinstall update/dlc class AbortException : public std::exception {}; class GameUpdateWindow : public wxDialog { public: GameUpdateWindow(wxWindow& parent, const fs::path& metaPath); ~GameUpdateWindow(); uint64 GetTitleId() const { return m_title_info.GetAppTitleId(); } bool HasException() const { return !m_thread_exception.empty(); } //bool IsDLC() const { return m_game_info->IsDLC(); } //bool IsUpdate() const { return m_game_info->IsUpdate(); } const std::string& GetExceptionMessage() const { return m_thread_exception; } const std::string GetGameName() const { return m_title_info.GetMetaTitleName(); } uint32 GetTargetVersion() const { return m_title_info.GetAppTitleVersion(); } fs::path GetTargetPath() const { return fs::path(m_target_path); } int ShowModal() override; void OnClose(wxCloseEvent& event); void OnUpdate(const wxTimerEvent& event); void OnCancelButton(const wxCommandEvent& event); //uint64 GetUpdateTitleId() const { return m_title_info->GetUpdateTitleId(); } //uint64 GetDLCTitleId() const { return m_game_info->GetDLCTitleId(); } private: //std::unique_ptr m_game_info; TitleInfo m_title_info; enum ThreadState_t { ThreadRunning, ThreadCanceled, ThreadFinished, }; uint64_t m_required_size; fs::path m_target_path; std::array m_source_paths; bool ParseUpdate(const fs::path& metaPath); std::atomic m_processed_size = 0; std::atomic m_thread_state; std::string m_thread_exception; std::thread m_thread; void ThreadWork(); fs::path m_backup_folder; // for prev update data wxGauge* m_processBar; wxTimer* m_timer; }; ================================================ FILE: src/gui/wxgui/GeneralSettings2.cpp ================================================ #include "wxCemuConfig.h" #include "wxgui/wxgui.h" #include "wxgui/GeneralSettings2.h" #include "wxgui/CemuApp.h" #include "wxgui/helpers/wxControlObject.h" #include "util/helpers/helpers.h" #include "Cafe/OS/libs/snd_core/ax.h" #include #include #include #include #include #include #include "config/CemuConfig.h" #include "config/NetworkSettings.h" #include "audio/IAudioAPI.h" #if BOOST_OS_WINDOWS #include "audio/DirectSoundAPI.h" #include "audio/XAudio27API.h" #endif #include "audio/CubebAPI.h" #include "audio/IAudioInputAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #if ENABLE_METAL #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #endif #include "Cafe/Account/Account.h" #include #include "util/helpers/SystemException.h" #include "wxgui/dialogs/CreateAccount/wxCreateAccountDialog.h" #if BOOST_OS_WINDOWS #include #endif #include "config/LaunchSettings.h" #include "config/ActiveSettings.h" #include "wxgui/helpers/wxHelpers.h" #include "resource/embedded/resources.h" #include "Cafe/CafeSystem.h" #include "Cemu/ncrypto/ncrypto.h" #include "Cafe/TitleList/TitleList.h" #include "wxHelper.h" #include "util/ScreenSaver/ScreenSaver.h" const wxString kDirectSound("DirectSound"); const wxString kXAudio27("XAudio2.7"); const wxString kXAudio2("XAudio2"); const wxString kCubeb("Cubeb"); const wxString kPropertyPersistentId("PersistentId"); const wxString kPropertyMiiName("MiiName"); const wxString kPropertyBirthday("Birthday"); const wxString kPropertyGender("Gender"); const wxString kPropertyEmail("Email"); const wxString kPropertyCountry("Country"); wxDEFINE_EVENT(wxEVT_ACCOUNTLIST_REFRESH, wxCommandEvent); class wxDeviceDescription : public wxClientData { public: wxDeviceDescription(const IAudioAPI::DeviceDescriptionPtr& description) : m_description(description) {} const IAudioAPI::DeviceDescriptionPtr& GetDescription() const { return m_description; } private: IAudioAPI::DeviceDescriptionPtr m_description; }; class wxInputDeviceDescription : public wxClientData { public: wxInputDeviceDescription(const IAudioInputAPI::DeviceDescriptionPtr& description) : m_description(description) {} const IAudioInputAPI::DeviceDescriptionPtr& GetDescription() const { return m_description; } private: IAudioInputAPI::DeviceDescriptionPtr m_description; }; class wxVulkanUUID : public wxClientData { public: wxVulkanUUID(const VulkanRenderer::DeviceInfo& info) : m_device_info(info) {} const VulkanRenderer::DeviceInfo& GetDeviceInfo() const { return m_device_info; } private: VulkanRenderer::DeviceInfo m_device_info; }; #if ENABLE_METAL class wxMetalUUID : public wxClientData { public: wxMetalUUID(const MetalRenderer::DeviceInfo& info) : m_device_info(info) {} const MetalRenderer::DeviceInfo& GetDeviceInfo() const { return m_device_info; } private: MetalRenderer::DeviceInfo m_device_info; }; #endif class wxAccountData : public wxClientData { public: wxAccountData(const Account& account) : m_account(account) {} Account& GetAccount() { return m_account; } const Account& GetAccount() const { return m_account; } private: Account m_account; }; wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) { auto* panel = new wxPanel(notebook); auto* general_panel_sizer = new wxBoxSizer(wxVERTICAL); { auto* box = new wxStaticBox(panel, wxID_ANY, _("Interface")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); { auto* first_row = new wxFlexGridSizer(0, 2, 0, 0); first_row->SetFlexibleDirection(wxBOTH); first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); first_row->Add(new wxStaticText(box, wxID_ANY, _("Language")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxString language_choices[] = { _("Default") }; m_language = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(language_choices), language_choices); m_language->SetSelection(0); m_language->SetToolTip(_("Changes the interface language of Cemu\nAvailable languages are stored in the translation directory\nA restart will be required after changing the language")); for (const auto& language : wxGetApp().GetLanguages()) { m_language->Append(language->DescriptionNative); } first_row->Add(m_language, 0, wxALL | wxEXPAND, 5); box_sizer->Add(first_row, 1, wxEXPAND, 5); } #if BOOST_OS_WINDOWS { auto* second_row = new wxFlexGridSizer(0, 2, 0, 0); second_row->SetFlexibleDirection(wxBOTH); second_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); second_row->Add(new wxStaticText(box, wxID_ANY, _("Theme")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_msw_theme = new wxChoice(box, wxID_ANY); m_msw_theme->SetToolTip(_("Changes the Windows theme used by Cemu\nThis only works on Windows 10 and later\nA restart will be required for any changes to take effect")); m_msw_theme->AppendString(_("Follow Windows theme")); m_msw_theme->AppendString(_("Light Theme")); m_msw_theme->AppendString(_("Dark Theme")); m_msw_theme->SetSelection(0); second_row->Add(m_msw_theme, 0, wxALL, 5); box_sizer->Add(second_row, 0, wxEXPAND, 5); } #endif { auto* third_row = new wxFlexGridSizer(0, 3, 0, 0); third_row->SetFlexibleDirection(wxBOTH); third_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); sint32 checkboxCount = 0; auto CountRowElement = [&]() { checkboxCount++; if(checkboxCount != 2) return; third_row->AddSpacer(10); checkboxCount = 0; }; auto InsertEmptyRow = [&]() { while(checkboxCount != 0) CountRowElement(); third_row->AddSpacer(10); third_row->AddSpacer(10); third_row->AddSpacer(10); }; const int topflag = wxALIGN_CENTER_VERTICAL | wxALL; m_save_window_position_size = new wxCheckBox(box, wxID_ANY, _("Remember main window position")); m_save_window_position_size->SetToolTip(_("Restores the last known window position and size when starting Cemu")); third_row->Add(m_save_window_position_size, 0, topflag, 5); CountRowElement(); //third_row->AddSpacer(10); m_save_padwindow_position_size = new wxCheckBox(box, wxID_ANY, _("Remember pad window position")); m_save_padwindow_position_size->SetToolTip(_("Restores the last known pad window position and size when opening it")); third_row->Add(m_save_padwindow_position_size, 0, topflag, 5); CountRowElement(); const int botflag = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxBOTTOM; m_discord_presence = new wxCheckBox(box, wxID_ANY, _("Discord Presence")); m_discord_presence->SetToolTip(_("Enables the Discord Rich Presence feature\nYou will also need to enable it in the Discord settings itself!")); third_row->Add(m_discord_presence, 0, botflag, 5); CountRowElement(); #ifndef ENABLE_DISCORD_RPC m_discord_presence->Disable(); #endif // third_row->AddSpacer(10); m_fullscreen_menubar = new wxCheckBox(box, wxID_ANY, _("Fullscreen menu bar")); m_fullscreen_menubar->SetToolTip(_("Displays the menu bar when Cemu is running in fullscreen mode and the mouse cursor is moved to the top")); third_row->Add(m_fullscreen_menubar, 0, botflag, 5); CountRowElement(); m_save_screenshot = new wxCheckBox(box, wxID_ANY, _("Save screenshot")); m_save_screenshot->SetToolTip(_("Pressing the screenshot key will save a screenshot directly to the screenshots folder instead of to the clipboard")); third_row->Add(m_save_screenshot, 0, botflag, 5); CountRowElement(); m_disable_screensaver = new wxCheckBox(box, wxID_ANY, _("Disable screen saver")); m_disable_screensaver->SetToolTip(_("Prevents the system from activating the screen saver or going to sleep while running a game.")); third_row->Add(m_disable_screensaver, 0, botflag, 5); CountRowElement(); // enable/disable feral interactive gamemode #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) m_feral_gamemode = new wxCheckBox(box, wxID_ANY, _("Enable Feral GameMode")); m_feral_gamemode->SetToolTip(_("Use FeralInteractive GameMode if installed.")); third_row->Add(m_feral_gamemode, 0, botflag, 5); CountRowElement(); #endif // temporary workaround because feature crashes on macOS #if BOOST_OS_MACOS m_disable_screensaver->Enable(false); #endif m_play_boot_sound = new wxCheckBox(box, wxID_ANY, _("Enable intro sound")); m_play_boot_sound->SetToolTip(_("Play bootSound file while compiling shaders/pipelines.")); third_row->Add(m_play_boot_sound, 0, botflag, 5); CountRowElement(); m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates")); m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup")); third_row->Add(m_auto_update, 0, botflag, 5); CountRowElement(); m_receive_untested_releases = new wxCheckBox(box, wxID_ANY, _("Receive untested updates")); m_receive_untested_releases->SetToolTip(_("When checking for updates, include brand new and untested releases. These may contain bugs!")); third_row->Add(m_receive_untested_releases, 0, botflag, 5); #if BOOST_OS_LINUX if (!std::getenv("APPIMAGE")) { m_auto_update->Disable(); } #elif BOOST_OS_BSD // BSD users must update from source so disable auto updates m_auto_update->Disable(); #endif box_sizer->Add(third_row, 0, wxEXPAND, 5); } general_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto* outerMlcBox = new wxStaticBox(panel, wxID_ANY, _("Custom MLC path")); auto* box_sizer_mlc = new wxStaticBoxSizer(outerMlcBox, wxVERTICAL); box_sizer_mlc->Add(new wxStaticText(box_sizer_mlc->GetStaticBox(), wxID_ANY, _("You can configure a custom path for the emulated internal Wii U storage (MLC).\nThis is where Cemu stores saves, accounts and other Wii U system files.")), 0, wxALL, 5); auto* mlcPathLineSizer = new wxBoxSizer(wxHORIZONTAL); m_mlc_path = new wxTextCtrl(outerMlcBox, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY); m_mlc_path->SetMinSize(wxSize(150, -1)); m_mlc_path->SetToolTip(_("The mlc directory contains your save games and installed game update/dlc data")); mlcPathLineSizer->Add(m_mlc_path, 1, wxALL | wxEXPAND, 5); auto* changePath = new wxButton(outerMlcBox, wxID_ANY, "Change"); changePath->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathSelect, this); mlcPathLineSizer->Add(changePath, 0, wxALL, 5); if (LaunchSettings::GetMLCPath().has_value()) changePath->Disable(); auto* clearPath = new wxButton(outerMlcBox, wxID_ANY, "Clear custom path"); clearPath->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathClear, this); mlcPathLineSizer->Add(clearPath, 0, wxALL, 5); if (LaunchSettings::GetMLCPath().has_value() || !ActiveSettings::IsCustomMlcPath()) clearPath->Disable(); box_sizer_mlc->Add(mlcPathLineSizer, 0, wxEXPAND, 5); general_panel_sizer->Add(box_sizer_mlc, 0, wxEXPAND | wxALL, 5); } { auto* general_gamepath_box = new wxStaticBox(panel, wxID_ANY, _("Game Paths")); auto* general_gamepath_sizer = new wxStaticBoxSizer(general_gamepath_box, wxVERTICAL); m_game_paths = new wxListBox(general_gamepath_box, wxID_ANY); m_game_paths->SetMinSize(wxSize(150, 70)); m_game_paths->SetToolTip(_("Add the root directory of your game(s). It will scan all directories in it for games")); general_gamepath_sizer->Add(m_game_paths, 1, wxALL | wxEXPAND, 5); auto* general_gamepath_buttons = new wxFlexGridSizer(0, 2, 0, 0); general_gamepath_buttons->SetFlexibleDirection(wxBOTH); general_gamepath_buttons->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); auto* general_gamepath_add_button = new wxButton(general_gamepath_box, wxID_ANY, _("Add")); general_gamepath_add_button->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAddPathClicked, this); general_gamepath_add_button->SetToolTip(_("Adds a game path to scan for games displayed in the game list\nIf you have unpacked games, make sure to select the root folder of a game")); general_gamepath_buttons->Add(general_gamepath_add_button, 0, wxALL, 5); auto* general_gamepath_remove_button = new wxButton(general_gamepath_box, wxID_ANY, _("Remove")); general_gamepath_remove_button->Bind(wxEVT_BUTTON, &GeneralSettings2::OnRemovePathClicked, this); general_gamepath_remove_button->SetToolTip(_("Removes the currently selected game path from the game list")); general_gamepath_buttons->Add(general_gamepath_remove_button, 0, wxALL, 5); general_gamepath_sizer->Add(general_gamepath_buttons, 0, wxEXPAND, 5); general_panel_sizer->Add(general_gamepath_sizer, 1, wxEXPAND | wxALL, 5); } panel->SetSizerAndFit(general_panel_sizer); return panel; } wxPanel* GeneralSettings2::AddGraphicsPage(wxNotebook* notebook) { // Graphics page auto graphics_panel = new wxPanel(notebook); auto graphics_panel_sizer = new wxBoxSizer(wxVERTICAL); { auto box = new wxStaticBox(graphics_panel, wxID_ANY, _("General")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto row = new wxFlexGridSizer(0, 2, 0, 0); row->SetFlexibleDirection(wxBOTH); row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); row->Add(new wxStaticText(box, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); sint32 api_size = 1; wxString choices[3] = { "OpenGL" }; if (g_vulkan_available) { choices[api_size++] = "Vulkan"; } #if ENABLE_METAL choices[api_size++] = "Metal"; #endif m_graphic_api = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, api_size, choices); m_graphic_api->SetSelection(0); if (api_size > 1) m_graphic_api->SetToolTip(_("Select one of the available graphic back ends")); if (CafeSystem::IsTitleRunning()) { m_graphic_api->Disable(); m_graphic_api->SetToolTip(_("Graphics API cannot be changed while a title is running")); } row->Add(m_graphic_api, 0, wxALL, 5); m_graphic_api->Bind(wxEVT_CHOICE, &GeneralSettings2::OnGraphicAPISelected, this); row->Add(new wxStaticText(box, wxID_ANY, _("Graphics Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_graphic_device = new wxChoice(box, wxID_ANY, wxDefaultPosition, { 230, -1 }, api_size, choices); m_graphic_device->SetSelection(0); m_graphic_device->SetToolTip(_("Select the used graphic device")); row->Add(m_graphic_device, 0, wxALL, 5); row->Add(new wxStaticText(box, wxID_ANY, _("VSync")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_vsync = new wxChoice(box, wxID_ANY, wxDefaultPosition, { 230, -1 }); m_vsync->SetToolTip(_("Controls the vsync state")); m_vsync->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { GetConfig().vsync = event.GetSelection(); }); row->Add(m_vsync, 0, wxALL, 5); box_sizer->Add(row, 0, wxEXPAND, 5); auto* graphic_misc_row = new wxFlexGridSizer(0, 2, 0, 0); m_async_compile = new wxCheckBox(box, wxID_ANY, _("Async shader compile")); m_async_compile->SetToolTip(_("Enables async shader and pipeline compilation. Reduces stutter at the cost of objects not rendering for a short time.\nVulkan only")); graphic_misc_row->Add(m_async_compile, 0, wxALL, 5); m_gx2drawdone_sync = new wxCheckBox(box, wxID_ANY, _("Full sync at GX2DrawDone()")); m_gx2drawdone_sync->SetToolTip(_("If synchronization is requested by the game, the emulated CPU will wait for the GPU to finish all operations.\nThis is more accurate behavior, but may cause lower performance")); graphic_misc_row->Add(m_gx2drawdone_sync, 0, wxALL, 5); #if ENABLE_METAL m_force_mesh_shaders = new wxCheckBox(box, wxID_ANY, _("Force mesh shaders")); m_force_mesh_shaders->SetToolTip(_("Force mesh shaders on all GPUs that support them. Mesh shaders are disabled by default on Intel GPUs due to potential stability issues.\nMetal only")); graphic_misc_row->Add(m_force_mesh_shaders, 0, wxALL, 5); #endif box_sizer->Add(graphic_misc_row, 1, wxEXPAND, 5); graphics_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto box = new wxStaticBox(graphics_panel, wxID_ANY, _("Gamma settings")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto row = new wxFlexGridSizer(0, 2, 0, 0); row->SetFlexibleDirection(wxBOTH); row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); auto targetGammaLabel = new wxStaticText(box, wxID_ANY, _("Target Gamma")); row->Add(targetGammaLabel, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_overrideGammaValue = new wxSpinCtrlDouble(box, wxID_ANY, "2.2f", wxDefaultPosition, {230, -1}, wxSP_ARROW_KEYS, 0.1f, 4.0f, 2.2f, 0.1f); row->Add(m_overrideGammaValue, 0, wxALL, 5); auto targetGammaTooltip = _("The display gamma to reproduce\nIf you are unsure, set this to 2.2"); targetGammaLabel->SetToolTip(targetGammaTooltip); m_overrideGammaValue->SetToolTip(targetGammaTooltip); m_overrideGammaValue->Bind(wxEVT_SPINCTRLDOUBLE, [](wxSpinDoubleEvent& event) { GetConfig().overrideGammaValue = event.GetValue(); }); auto displayGammaLabel = new wxStaticText(box, wxID_ANY, _("Display Gamma")); row->Add(displayGammaLabel, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); wxBoxSizer* srgbCheckBoxSizer = new wxBoxSizer(wxHORIZONTAL); row->Add(srgbCheckBoxSizer); m_userDisplayGamma = new wxSpinCtrlDouble(box, wxID_ANY, "2.2f", wxDefaultPosition, {230, -1}, wxSP_ARROW_KEYS, 0.1f, 4.0f, 2.2f, 0.1f); auto displayGammaTooltip = _("The gamma of your monitor\nIf you are unsure, set this to 2.2"); m_userDisplayGamma->SetToolTip(displayGammaTooltip); displayGammaLabel->SetToolTip(displayGammaTooltip); m_userDisplayGamma->Bind(wxEVT_SPINCTRLDOUBLE, [](wxSpinDoubleEvent& event) { GetConfig().userDisplayGamma = event.GetValue(); }); m_userDisplayisSRGB = new wxCheckBox(box, wxID_ANY, "sRGB"); m_userDisplayisSRGB->SetToolTip(_("Select this if Cemu is being displayed using a piecewise sRGB gamma curve.\n" "This is typically not the case so you can probably leave this unchecked.\n" "Exceptions include HDR displays (with HDR enabled), calibrated SDR displays with Windows 11's Auto Color Management enabled, " "or when using a display profile with a VCGT tag that targets piecewise sRGB.\n" "When this box is selected Cemu will compensate for the piecewise curve to approximate the pure gamma curve of a TV.\n" "Colors will be more accurate, especially in dark scenes, but this may result in banding or crushed shadows, " "so it is best if you display Cemu with pure gamma and do not use this setting.")); m_userDisplayisSRGB->Bind(wxEVT_CHECKBOX, &GeneralSettings2::OnUserDisplaySRGBSelected, this); srgbCheckBoxSizer->Add(m_userDisplayGamma, 0, wxALL, 5); srgbCheckBoxSizer->Add(m_userDisplayisSRGB, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); row->Add(new wxStaticText(box, wxID_ANY, _("Override Gamma")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_overrideGamma = new wxCheckBox(box, wxID_ANY, "", wxDefaultPosition, {230, -1}); m_overrideGamma->SetToolTip(_("Ignore title's gamma preference")); m_overrideGamma->Bind(wxEVT_CHECKBOX, [](wxCommandEvent& event) { GetConfig().overrideAppGammaPreference = event.IsChecked(); }); row->Add(m_overrideGamma, 0, wxALL, 5); box_sizer->Add(row, 0, wxEXPAND, 5); graphics_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { wxString choices[] = { _("Bilinear"), _("Bicubic"), _("Hermite"), _("Nearest Neighbor") }; m_upscale_filter = new wxRadioBox(graphics_panel, wxID_ANY, _("Upscale filter"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS); m_upscale_filter->SetToolTip(_("Upscaling filters are used when the game resolution is smaller than the window size")); m_upscale_filter->Bind(wxEVT_RADIOBOX, [](wxCommandEvent& event) { GetConfig().upscale_filter = event.GetSelection(); }); graphics_panel_sizer->Add(m_upscale_filter, 0, wxALL | wxEXPAND, 5); m_downscale_filter = new wxRadioBox(graphics_panel, wxID_ANY, _("Downscale filter"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS); m_downscale_filter->SetToolTip(_("Downscaling filters are used when the game resolution is bigger than the window size")); m_downscale_filter->Bind(wxEVT_RADIOBOX, [](wxCommandEvent& event) { GetConfig().downscale_filter = event.GetSelection(); }); graphics_panel_sizer->Add(m_downscale_filter, 0, wxALL | wxEXPAND, 5); } { wxString choices[] = { _("Keep aspect ratio"), _("Stretch") }; m_fullscreen_scaling = new wxRadioBox(graphics_panel, wxID_ANY, _("Fullscreen scaling"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS); m_fullscreen_scaling->SetToolTip(_("Controls the output aspect ratio when it doesn't match the ratio of the game")); m_fullscreen_scaling->Bind(wxEVT_RADIOBOX, [](wxCommandEvent& event) { GetConfig().fullscreen_scaling = event.GetSelection(); }); graphics_panel_sizer->Add(m_fullscreen_scaling, 0, wxALL | wxEXPAND, 5); } graphics_panel->SetSizerAndFit(graphics_panel_sizer); return graphics_panel; } wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook) { auto audio_panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto audio_panel_sizer = new wxBoxSizer(wxVERTICAL); { auto box = new wxStaticBox(audio_panel, wxID_ANY, _("General")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto audio_general_row = new wxFlexGridSizer(0, 3, 0, 0); audio_general_row->SetFlexibleDirection(wxBOTH); audio_general_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); audio_general_row->Add(new wxStaticText(box, wxID_ANY, _("API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_audio_api = new wxChoice(box, wxID_ANY); if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::DirectSound)) m_audio_api->Append(kDirectSound); if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::XAudio27)) m_audio_api->Append(kXAudio27); if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::XAudio2)) m_audio_api->Append(kXAudio2); if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::Cubeb)) m_audio_api->Append(kCubeb); m_audio_api->SetSelection(0); m_audio_api->SetToolTip(_("Select one of the available audio back ends")); audio_general_row->Add(m_audio_api, 0, wxALL, 5); m_audio_api->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioAPISelected, this); audio_general_row->AddSpacer(0); audio_general_row->Add(new wxStaticText(box, wxID_ANY, _("Latency")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_audio_latency = new wxSlider(box, wxID_ANY, 2, 0, IAudioAPI::kBlockCount - 1, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); m_audio_latency->SetToolTip(_("Controls the amount of buffered audio data\nHigher values will create a delay in audio playback, but may avoid audio problems when emulation is too slow")); audio_general_row->Add(m_audio_latency, 0, wxEXPAND | wxALL, 5); auto latency_text = new wxStaticText(box, wxID_ANY, "24ms"); audio_general_row->Add(latency_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_audio_latency->Bind(wxEVT_SLIDER, &GeneralSettings2::OnLatencySliderChanged, this, wxID_ANY, wxID_ANY, new wxControlObject(latency_text)); m_audio_latency->Bind(wxEVT_SLIDER, &GeneralSettings2::OnAudioLatencyChanged, this); box_sizer->Add(audio_general_row, 1, wxEXPAND, 5); audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } const wxString audio_channel_choices[] = { _("Mono"), _("Stereo") , _("Surround") }; { auto box = new wxStaticBox(audio_panel, wxID_ANY, _("TV")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto audio_tv_row = new wxFlexGridSizer(0, 3, 0, 0); audio_tv_row->SetFlexibleDirection(wxBOTH); audio_tv_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_tv_device = new wxChoice(box, wxID_ANY); m_tv_device->SetMinSize(wxSize(300, -1)); m_tv_device->SetToolTip(_("Select the active audio output device for Wii U TV")); audio_tv_row->Add(m_tv_device, 0, wxEXPAND | wxALL, 5); audio_tv_row->AddSpacer(0); m_tv_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_tv_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_choices), audio_channel_choices); m_tv_channels->SetSelection(1); // set default to stereo m_tv_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this); audio_tv_row->Add(m_tv_channels, 0, wxEXPAND | wxALL, 5); audio_tv_row->AddSpacer(0); audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_tv_volume = new wxSlider(box, wxID_ANY, 100, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); audio_tv_row->Add(m_tv_volume, 0, wxEXPAND | wxALL, 5); auto audio_tv_volume_text = new wxStaticText(box, wxID_ANY, "100%"); audio_tv_row->Add(audio_tv_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_tv_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_tv_volume_text)); m_tv_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); box_sizer->Add(audio_tv_row, 1, wxEXPAND, 5); audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Gamepad")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto audio_pad_row = new wxFlexGridSizer(0, 3, 0, 0); audio_pad_row->SetFlexibleDirection(wxBOTH); audio_pad_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_pad_device = new wxChoice(box, wxID_ANY); m_pad_device->SetMinSize(wxSize(300, -1)); m_pad_device->SetToolTip(_("Select the active audio output device for Wii U GamePad")); audio_pad_row->Add(m_pad_device, 0, wxEXPAND | wxALL, 5); audio_pad_row->AddSpacer(0); m_pad_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); const wxString audio_channel_drc_choices[] = { _("Stereo") }; // stereo for now only audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_pad_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices); m_pad_channels->SetSelection(0); // set default to stereo m_pad_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this); audio_pad_row->Add(m_pad_channels, 0, wxEXPAND | wxALL, 5); audio_pad_row->AddSpacer(0); audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_pad_volume = new wxSlider(box, wxID_ANY, 100, 0, 100); audio_pad_row->Add(m_pad_volume, 0, wxEXPAND | wxALL, 5); auto audio_pad_volume_text = new wxStaticText(box, wxID_ANY, "100%"); audio_pad_row->Add(audio_pad_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_pad_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_pad_volume_text)); m_pad_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); box_sizer->Add(audio_pad_row, 1, wxEXPAND, 5); audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Microphone (Experimental)")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto audio_input_row = new wxFlexGridSizer(0, 3, 0, 0); audio_input_row->SetFlexibleDirection(wxBOTH); audio_input_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_input_device = new wxChoice(box, wxID_ANY); m_input_device->SetMinSize(wxSize(300, -1)); m_input_device->SetToolTip(_("Select the active audio input device for Wii U GamePad")); audio_input_row->Add(m_input_device, 0, wxEXPAND | wxALL, 5); audio_input_row->AddSpacer(0); m_input_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); const wxString audio_channel_drc_choices[] = { _("Mono") }; // mono for now only audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_input_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices); m_input_channels->SetSelection(0); // set default to stereo m_input_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this); audio_input_row->Add(m_input_channels, 0, wxEXPAND | wxALL, 5); audio_input_row->AddSpacer(0); audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_input_volume = new wxSlider(box, wxID_ANY, 100, 0, 100); audio_input_row->Add(m_input_volume, 0, wxEXPAND | wxALL, 5); auto audio_input_volume_text = new wxStaticText(box, wxID_ANY, "100%"); audio_input_row->Add(audio_input_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_input_volume_text)); m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); box_sizer->Add(audio_input_row, 1, wxEXPAND, 5); audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Trap Team Portal")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto portal_audio_row = new wxFlexGridSizer(0, 3, 0, 0); portal_audio_row->SetFlexibleDirection(wxBOTH); portal_audio_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); portal_audio_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_portal_device = new wxChoice(box, wxID_ANY, wxDefaultPosition); m_portal_device->SetMinSize(wxSize(300, -1)); m_portal_device->SetToolTip(_("Select the active audio output device for Wii U GamePad")); portal_audio_row->Add(m_portal_device, 0, wxEXPAND | wxALL, 5); portal_audio_row->AddSpacer(0); m_portal_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); portal_audio_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_portal_volume = new wxSlider(box, wxID_ANY, 100, 0, 100); portal_audio_row->Add(m_portal_volume, 0, wxEXPAND | wxALL, 5); auto audio_pad_volume_text = new wxStaticText(box, wxID_ANY, "100%"); portal_audio_row->Add(audio_pad_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); m_portal_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_pad_volume_text)); m_portal_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); box_sizer->Add(portal_audio_row, 1, wxEXPAND, 5); audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } audio_panel->SetSizerAndFit(audio_panel_sizer); return audio_panel; } wxPanel* GeneralSettings2::AddOverlayPage(wxNotebook* notebook) { auto* panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto* panel_sizer = new wxBoxSizer(wxVERTICAL); const wxString positions[]{ _("Disabled"), _("Top left"), _("Top center"), _("Top right"), _("Bottom left"), _("Bottom center"), _("Bottom right") }; const wxString text_scale[]{ "50%", "75%", "100%", "125%", "150%", "175%", "200%", "225%", "250%", "275%", "300%" }; { auto box = new wxStaticBox(panel, wxID_ANY, _("Overlay")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto position_row = new wxFlexGridSizer(1, 0, 0, 0); position_row->SetFlexibleDirection(wxBOTH); position_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); { position_row->Add(new wxStaticText(box, wxID_ANY, _("Position")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_overlay_position = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(positions), positions); m_overlay_position->SetSelection(0); m_overlay_position->SetToolTip(_("Controls the overlay which displays technical information while playing")); position_row->Add(m_overlay_position, 0, wxALL, 5); position_row->AddSpacer(25); position_row->Add(new wxStaticText(box, wxID_ANY, _("Text Color")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); m_overlay_font_color = new wxColourPickerCtrl(box, wxID_ANY, *wxWHITE, wxDefaultPosition, wxDefaultSize, wxCLRP_SHOW_ALPHA); m_overlay_font_color->SetToolTip(_("Sets the text color of the overlay")); position_row->Add(m_overlay_font_color, 0, wxEXPAND | wxALL, 5); position_row->AddSpacer(25); position_row->Add(new wxStaticText(box, wxID_ANY, _("Scale")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); m_overlay_scale = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(text_scale), text_scale); m_overlay_scale->SetToolTip(_("Sets the scale of the overlay text")); position_row->Add(m_overlay_scale, 0, wxEXPAND | wxALL, 5); } box_sizer->Add(position_row, 0, wxEXPAND, 5); auto settings2_row = new wxFlexGridSizer(0, 4, 2, 0); { m_overlay_fps = new wxCheckBox(box, wxID_ANY, _("FPS")); m_overlay_fps->SetToolTip(_("The number of frames per second. Average over last 5 seconds")); settings2_row->Add(m_overlay_fps, 0, wxALL, 5); m_overlay_drawcalls = new wxCheckBox(box, wxID_ANY, _("Draw calls per frame")); m_overlay_drawcalls->SetToolTip(_("The number of draw calls per frame. Average over last 5 seconds")); settings2_row->Add(m_overlay_drawcalls, 0, wxALL, 5); m_overlay_cpu = new wxCheckBox(box, wxID_ANY, _("CPU usage")); m_overlay_cpu->SetToolTip(_("CPU usage of Cemu in percent")); settings2_row->Add(m_overlay_cpu, 0, wxALL, 5); m_overlay_cpu_per_core = new wxCheckBox(box, wxID_ANY, _("CPU per core usage")); m_overlay_cpu_per_core->SetToolTip(_("Total cpu usage in percent for each core")); settings2_row->Add(m_overlay_cpu_per_core, 0, wxALL, 5); m_overlay_ram = new wxCheckBox(box, wxID_ANY, _("RAM usage")); m_overlay_ram->SetToolTip(_("Cemu RAM usage in MB")); settings2_row->Add(m_overlay_ram, 0, wxALL, 5); m_overlay_vram = new wxCheckBox(box, wxID_ANY, _("VRAM usage")); #if BOOST_OS_WINDOWS using RtlGetVersion_t = LONG(WINAPI*)(PRTL_OSVERSIONINFOW lpVersionInformation); const auto pRtlGetVersion = (RtlGetVersion_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion"); //if(IsWindows8Point1OrGreater()) requires manifest RTL_OSVERSIONINFOW info{}; // Windows 8.1 6.3* if (pRtlGetVersion && pRtlGetVersion(&info) == 0 && ((info.dwMajorVersion == 6 && info.dwMinorVersion >= 3) || info.dwMajorVersion > 6)) m_overlay_vram->SetToolTip(_("The VRAM usage of Cemu in MB")); else { m_overlay_vram->SetToolTip(_("This option requires Win8.1+")); m_overlay_vram->Disable(); } #else m_overlay_vram->SetToolTip(_("The VRAM usage of Cemu in MB")); #endif settings2_row->Add(m_overlay_vram, 0, wxALL, 5); m_overlay_debug = new wxCheckBox(box, wxID_ANY, _("Debug")); m_overlay_debug->SetToolTip(_("Displays internal debug information (Vulkan only)")); settings2_row->Add(m_overlay_debug, 0, wxALL, 5); } box_sizer->Add(settings2_row, 0, wxEXPAND, 5); panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto box = new wxStaticBox(panel, wxID_ANY, _("Notifications")); auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto position_row = new wxFlexGridSizer(1, 0, 0, 0); position_row->SetFlexibleDirection(wxBOTH); position_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); { position_row->Add(new wxStaticText(box, wxID_ANY, _("Position")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_notification_position = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(positions), positions); m_notification_position->SetSelection(0); m_notification_position->SetToolTip(_("Controls the notification position while playing")); position_row->Add(m_notification_position, 0, wxALL, 5); position_row->AddSpacer(25); position_row->Add(new wxStaticText(box, wxID_ANY, _("Text Color")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); m_notification_font_color = new wxColourPickerCtrl(box, wxID_ANY, *wxWHITE, wxDefaultPosition, wxDefaultSize, wxCLRP_SHOW_ALPHA); m_notification_font_color->SetToolTip(_("Sets the text color of notifications")); position_row->Add(m_notification_font_color, 0, wxEXPAND | wxALL, 5); position_row->Add(new wxStaticText(box, wxID_ANY, _("Scale")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); m_notification_scale = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(text_scale), text_scale); m_notification_scale->SetToolTip(_("Sets the scale of the notification text")); position_row->Add(m_notification_scale, 0, wxEXPAND | wxALL, 5); } box_sizer->Add(position_row, 0, wxEXPAND, 5); auto settings1_row = new wxFlexGridSizer(1, 0, 2, 0); { m_controller_profile_name = new wxCheckBox(box, wxID_ANY, _("Controller profiles")); m_controller_profile_name->SetToolTip(_("Displays the active controller profile when starting a game")); settings1_row->Add(m_controller_profile_name, 0, wxALL, 5); m_controller_low_battery = new wxCheckBox(box, wxID_ANY, _("Low battery")); m_controller_low_battery->SetToolTip(_("Shows a notification when a low controller battery has been detected")); settings1_row->Add(m_controller_low_battery, 0, wxALL, 5); m_shader_compiling = new wxCheckBox(box, wxID_ANY, _("Shader compiler")); m_shader_compiling->SetToolTip(_("Shows a notification after shaders have been compiled")); settings1_row->Add(m_shader_compiling, 0, wxALL, 5); m_friends_data = new wxCheckBox(box, wxID_ANY, _("Friend list")); m_friends_data->SetToolTip(_("Shows friend list related data if online")); settings1_row->Add(m_friends_data, 0, wxALL, 5); } box_sizer->Add(settings1_row, 0, wxEXPAND, 5); panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } panel->SetSizerAndFit(panel_sizer); return panel; } wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) { auto* online_panel = new wxPanel(notebook); auto* online_panel_sizer = new wxBoxSizer(wxVERTICAL); { auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Account settings")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto* content = new wxFlexGridSizer(0, 4, 0, 0); content->SetFlexibleDirection(wxBOTH); content->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); content->AddGrowableCol(1, 1); content->AddGrowableCol(2, 0); content->AddGrowableCol(3, 0); content->Add(new wxStaticText(box, wxID_ANY, _("Active account")), 1, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_active_account = new wxChoice(box, wxID_ANY); m_active_account->SetMinSize({ 250, -1 }); content->Add(m_active_account, 0, wxEXPAND | wxALL, 5); m_active_account->Bind(wxEVT_CHOICE, &GeneralSettings2::OnActiveAccountChanged, this); m_create_account = new wxButton(box, wxID_ANY, _("Create")); content->Add(m_create_account, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5); m_create_account->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAccountCreate, this); m_delete_account = new wxButton(box, wxID_ANY, _("Delete")); content->Add(m_delete_account, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5); m_delete_account->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAccountDelete, this); box_sizer->Add(content, 1, wxEXPAND, 5); online_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); if (CafeSystem::IsTitleRunning()) { m_active_account->Enable(false); m_create_account->Enable(false); m_delete_account->Enable(false); } } { wxString choices[] = { _("Offline"), _("Nintendo"), _("Pretendo"), _("Custom") }; m_active_service = new wxRadioBox(online_panel, wxID_ANY, _("Network Service"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 4, wxRA_SPECIFY_COLS); if (!NetworkConfig::XMLExists()) m_active_service->Enable(3, false); m_active_service->SetItemToolTip(0, _("Online functionality disabled for this account")); m_active_service->SetItemToolTip(1, _("Connect to the official Nintendo Network Service")); m_active_service->SetItemToolTip(2, _("Connect to the Pretendo Network Service")); m_active_service->SetItemToolTip(3, _("Connect to a custom Network Service (configured via network_services.xml)")); m_active_service->Bind(wxEVT_RADIOBOX, &GeneralSettings2::OnAccountServiceChanged,this); online_panel_sizer->Add(m_active_service, 0, wxEXPAND | wxALL, 5); if (CafeSystem::IsTitleRunning()) { m_active_service->Enable(false); } } { auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Online play requirements")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto* row = new wxFlexGridSizer(0, 2, 0, 0); row->SetFlexibleDirection(wxBOTH); row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); const wxImage tmp = wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage(); m_validate_online = new wxBitmapButton(box, wxID_ANY, tmp.Scale(16, 16)); m_validate_online->Bind(wxEVT_BUTTON, &GeneralSettings2::OnShowOnlineValidator, this); row->Add(m_validate_online, 0, wxEXPAND | wxALL, 5); m_online_status = new wxStaticText(box, wxID_ANY, _("No account selected")); row->Add(m_online_status, 1, wxALL | wxALIGN_CENTRE_VERTICAL, 5); box_sizer->Add(row, 1, wxEXPAND, 5); auto* tutorial_link = new wxHyperlinkCtrl(box, wxID_ANY, _("Online play tutorial"), "https://cemu.info/online-guide"); box_sizer->Add(tutorial_link, 0, wxALL, 5); online_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { m_account_information = new wxCollapsiblePane(online_panel, wxID_ANY, _("Account information")); auto win = m_account_information->GetPane(); auto content = new wxBoxSizer(wxVERTICAL); m_account_grid = new wxPropertyGrid(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_HIDE_MARGIN | wxPG_STATIC_SPLITTER); m_account_grid->SetExtraStyle(wxPG_EX_HELP_AS_TOOLTIPS); m_account_grid->SetMinSize({ 300, -1 }); //m_account_grid->Append(new wxPropertyCategory("Main")); auto* persistent_id_gprop = m_account_grid->Append(new wxStringProperty("PersistentId", kPropertyPersistentId)); persistent_id_gprop->SetHelpString(_("The persistent id is the internal folder name used for your saves")); m_account_grid->SetPropertyReadOnly(persistent_id_gprop); m_account_grid->Append(new wxStringProperty(_("Mii name"), kPropertyMiiName))->SetHelpString(_("The mii name is the profile name")); m_account_grid->Append(new wxStringProperty(_("Birthday"), kPropertyBirthday)); wxPGChoices gender; gender.Add(_("Female"), 0); gender.Add(_("Male"), 1); m_account_grid->Append(new wxEnumProperty(_("Gender"), kPropertyGender, gender)); m_account_grid->Append(new wxStringProperty(_("Email"), kPropertyEmail)); wxPGChoices countries; for (int i = 0; i < NCrypto::GetCountryCount(); ++i) { const auto country = NCrypto::GetCountryAsString(i); if (country && (i == 0 || !boost::equals(country, "NN"))) { countries.Add(country, i); } } m_account_grid->Append(new wxEnumProperty(_("Country"), kPropertyCountry, countries)); m_account_grid->Bind(wxEVT_PG_CHANGED, &GeneralSettings2::OnAccountSettingsChanged, this); content->Add(m_account_grid, 1, wxEXPAND | wxALL, 5); win->SetSizer(content); content->SetSizeHints(win); online_panel_sizer->Add(m_account_information, 0, wxEXPAND | wxALL, 5); } online_panel->SetSizerAndFit(online_panel_sizer); return online_panel; } wxPanel* GeneralSettings2::AddDebugPage(wxNotebook* notebook) { auto* panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto* debug_panel_sizer = new wxBoxSizer(wxVERTICAL); { auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0); debug_row->SetFlexibleDirection(wxBOTH); debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); debug_row->Add(new wxStaticText(panel, wxID_ANY, _("Crash dump")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); #if BOOST_OS_WINDOWS wxString dump_choices[] = {_("Disabled"), _("Lite"), _("Full")}; #elif BOOST_OS_UNIX wxString dump_choices[] = {_("Disabled"), _("Enabled")}; #endif m_crash_dump = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(dump_choices), dump_choices); m_crash_dump->SetSelection(0); #if BOOST_OS_WINDOWS m_crash_dump->SetToolTip(_("Creates a dump when Cemu crashes\nOnly enable when requested by a developer!\nThe Full option will create a very large dump file (includes a full RAM dump of the Cemu process)")); #elif BOOST_OS_UNIX m_crash_dump->SetToolTip(_("Creates a core dump when Cemu crashes\nOnly enable when requested by a developer!")); #endif debug_row->Add(m_crash_dump, 0, wxALL | wxEXPAND, 5); debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); } { auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0); debug_row->SetFlexibleDirection(wxBOTH); debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); debug_row->Add(new wxStaticText(panel, wxID_ANY, _("GDB Stub port")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_gdb_port = new wxSpinCtrl(panel, wxID_ANY, "1337", wxDefaultPosition, wxDefaultSize, 0, 1000, 65535); m_gdb_port->SetToolTip(_("Changes the port that the GDB stub will use, which you can use by either starting Cemu with the --enable-gdbstub option or by enabling it the Debug tab.")); debug_row->Add(m_gdb_port, 0, wxALL | wxEXPAND, 5); debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); } #if ENABLE_METAL { auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0); debug_row->SetFlexibleDirection(wxBOTH); debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); debug_row->Add(new wxStaticText(panel, wxID_ANY, _("GPU capture save directory"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_gpu_capture_dir = new wxTextCtrl(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_DONTWRAP); m_gpu_capture_dir->SetMinSize(wxSize(150, -1)); m_gpu_capture_dir->SetToolTip(_("Cemu will save the GPU captures done by selecting Debug -> GPU capture (Metal) in the menu bar in this directory. If a debugger with support for GPU captures (like Xcode) is attached, the capture will be opened in that debugger instead. If such debugger is not attached, METAL_CAPTURE_ENABLED must be set to 1 as an environment variable.")); debug_row->Add(m_gpu_capture_dir, 0, wxALL | wxEXPAND, 5); debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); } { auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0); debug_row->SetFlexibleDirection(wxBOTH); debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); m_framebuffer_fetch = new wxCheckBox(panel, wxID_ANY, _("Framebuffer fetch")); m_framebuffer_fetch->SetToolTip(_("Enable framebuffer fetch for eligible textures on supported devices.\nMetal only")); debug_row->Add(m_framebuffer_fetch, 0, wxALL | wxEXPAND, 5); debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); } #endif panel->SetSizerAndFit(debug_panel_sizer); return panel; } GeneralSettings2::GeneralSettings2(wxWindow* parent, bool game_launched) : wxDialog(parent, wxID_ANY, _("General settings"), wxDefaultPosition, wxDefaultSize, wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER), m_game_launched(game_launched) { SetIcon(wxICON(X_SETTINGS)); auto* sizer = new wxBoxSizer(wxVERTICAL); auto* notebook = new wxNotebook(this, wxID_ANY); notebook->AddPage(AddGeneralPage(notebook), _("General")); notebook->AddPage(AddGraphicsPage(notebook), _("Graphics")); notebook->AddPage(AddAudioPage(notebook), _("Audio")); notebook->AddPage(AddOverlayPage(notebook), _("Overlay")); notebook->AddPage(AddAccountPage(notebook), _("Account")); notebook->AddPage(AddDebugPage(notebook), _("Debug")); Bind(wxEVT_CLOSE_WINDOW, &GeneralSettings2::OnClose, this); // sizer->Add(notebook, 1, wxEXPAND | wxALL, 5); SetSizerAndFit(sizer); Layout(); Centre(wxBOTH); // UpdateOnlineAccounts(); UpdateAudioDeviceList(); ApplyConfig(); HandleGraphicsApiSelection(); DisableSettings(game_launched); } uint32 GeneralSettings2::GetSelectedAccountPersistentId() { const auto active_account = m_active_account->GetSelection(); if (active_account == wxNOT_FOUND) return GetConfig().account.m_persistent_id.GetInitValue(); return dynamic_cast(m_active_account->GetClientObject(active_account))->GetAccount().GetPersistentId(); } void GeneralSettings2::StoreConfig() { auto& app = wxGetApp(); auto& config = GetConfig(); auto& wxGuiConfig = GetWxGUIConfig(); wxGuiConfig.use_discord_presence = m_discord_presence->IsChecked(); wxGuiConfig.fullscreen_menubar = m_fullscreen_menubar->IsChecked(); wxGuiConfig.check_update = m_auto_update->IsChecked(); wxGuiConfig.save_screenshot = m_save_screenshot->IsChecked(); wxGuiConfig.receive_untested_updates = m_receive_untested_releases->IsChecked(); #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) wxGuiConfig.feral_gamemode = m_feral_gamemode->IsChecked(); #endif #if BOOST_OS_WINDOWS wxGuiConfig.msw_theme = m_msw_theme->GetSelection(); #endif config.play_boot_sound = m_play_boot_sound->IsChecked(); config.disable_screensaver = m_disable_screensaver->IsChecked(); // toggle while a game is running if (CafeSystem::IsTitleRunning()) { ScreenSaver::SetInhibit(config.disable_screensaver); } // -1 is default wx widget value -> set to dummy 0 so mainwindow and padwindow will update it wxGuiConfig.window_position = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; wxGuiConfig.window_size = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; wxGuiConfig.pad_position = m_save_padwindow_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; wxGuiConfig.pad_size = m_save_padwindow_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; config.game_paths.clear(); for (auto& path : m_game_paths->GetStrings()) config.game_paths.emplace_back(path.utf8_string()); auto selection = m_language->GetSelection(); if (selection == 0) wxGuiConfig.language = wxLANGUAGE_DEFAULT; else { const auto language = m_language->GetStringSelection(); for (const auto& lang : app.GetLanguages()) { if (lang->DescriptionNative == language) { wxGuiConfig.language = lang->Language; break; } } } // audio if (m_audio_api->GetStringSelection() == kDirectSound) config.audio_api = IAudioAPI::DirectSound; else if (m_audio_api->GetStringSelection() == kXAudio27) config.audio_api = IAudioAPI::XAudio27; else if (m_audio_api->GetStringSelection() == kXAudio2) config.audio_api = IAudioAPI::XAudio2; else if (m_audio_api->GetStringSelection() == kCubeb) config.audio_api = IAudioAPI::Cubeb; config.audio_delay = m_audio_latency->GetValue(); config.tv_channels = (AudioChannels)m_tv_channels->GetSelection(); //config.pad_channels = (AudioChannels)m_pad_channels->GetSelection(); config.pad_channels = kStereo; // (AudioChannels)m_pad_channels->GetSelection(); //config.input_channels = (AudioChannels)m_input_channels->GetSelection(); config.input_channels = kMono; // (AudioChannels)m_input_channels->GetSelection(); config.tv_volume = m_tv_volume->GetValue(); config.pad_volume = m_pad_volume->GetValue(); config.input_volume = m_input_volume->GetValue(); config.portal_volume = m_portal_volume->GetValue(); config.tv_device.clear(); const auto tv_device = m_tv_device->GetSelection(); if (tv_device != wxNOT_FOUND && tv_device != 0 && m_tv_device->HasClientObjectData()) { const auto* device_description = (wxDeviceDescription*)m_tv_device->GetClientObject(tv_device); if(device_description) config.tv_device = device_description->GetDescription()->GetIdentifier(); } config.pad_device.clear(); const auto pad_device = m_pad_device->GetSelection(); if (pad_device != wxNOT_FOUND && pad_device != 0 && m_pad_device->HasClientObjectData()) { const auto* device_description = (wxDeviceDescription*)m_pad_device->GetClientObject(pad_device); if (device_description) config.pad_device = device_description->GetDescription()->GetIdentifier(); } config.input_device = L""; const auto input_device = m_input_device->GetSelection(); if (input_device != wxNOT_FOUND && input_device != 0 && m_input_device->HasClientObjectData()) { const auto* device_description = (wxDeviceDescription*)m_input_device->GetClientObject(input_device); if (device_description) config.input_device = device_description->GetDescription()->GetIdentifier(); } config.portal_device.clear(); const auto portal_device = m_portal_device->GetSelection(); if (portal_device != wxNOT_FOUND && portal_device != 0 && m_portal_device->HasClientObjectData()) { const auto* device_description = (wxDeviceDescription*)m_portal_device->GetClientObject(portal_device); if (device_description) config.portal_device = device_description->GetDescription()->GetIdentifier(); } // graphics config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection(); selection = m_graphic_device->GetSelection(); if (config.graphic_api == GraphicAPI::kVulkan) { if (selection != wxNOT_FOUND) { const auto* info = (wxVulkanUUID*)m_graphic_device->GetClientObject(selection); if (info) config.vk_graphic_device_uuid = info->GetDeviceInfo().uuid; else config.vk_graphic_device_uuid = {}; } else config.vk_graphic_device_uuid = {}; } #if ENABLE_METAL else if (config.graphic_api == GraphicAPI::kMetal) { if (selection != wxNOT_FOUND) { const auto* info = (wxMetalUUID*)m_graphic_device->GetClientObject(selection); if (info) config.mtl_graphic_device_uuid = info->GetDeviceInfo().uuid; else config.mtl_graphic_device_uuid = {}; } else config.mtl_graphic_device_uuid = {}; } #endif config.gx2drawdone_sync = m_gx2drawdone_sync->IsChecked(); #if ENABLE_METAL config.force_mesh_shaders = m_force_mesh_shaders->IsChecked(); #endif config.async_compile = m_async_compile->IsChecked(); config.overlay.position = (ScreenPosition)m_overlay_position->GetSelection(); wxASSERT((int)config.overlay.position <= (int)ScreenPosition::kBottomRight); config.overlay.text_color = m_overlay_font_color->GetColour().GetRGBA(); config.overlay.text_scale = m_overlay_scale->GetSelection() * 25 + 50; config.overlay.fps = m_overlay_fps->GetValue(); config.overlay.drawcalls = m_overlay_drawcalls->GetValue(); config.overlay.cpu_usage = m_overlay_cpu->GetValue(); config.overlay.cpu_per_core_usage = m_overlay_cpu_per_core->GetValue(); config.overlay.ram_usage = m_overlay_ram->GetValue(); config.overlay.vram_usage = m_overlay_vram->GetValue(); config.overlay.debug = m_overlay_debug->GetValue(); config.notification.position = (ScreenPosition)m_notification_position->GetSelection(); wxASSERT((int)config.notification.position <= (int)ScreenPosition::kBottomRight); config.notification.text_color = m_notification_font_color->GetColour().GetRGBA(); config.notification.text_scale = m_notification_scale->GetSelection() * 25 + 50; config.notification.controller_profiles = m_controller_profile_name->GetValue(); config.notification.controller_battery = m_controller_low_battery->GetValue(); config.notification.shader_compiling = m_shader_compiling->GetValue(); config.notification.friends = m_friends_data->GetValue(); // account config.account.m_persistent_id = GetSelectedAccountPersistentId(); // debug config.crash_dump = (CrashDump)m_crash_dump->GetSelection(); config.gdb_port = m_gdb_port->GetValue(); #if ENABLE_METAL config.gpu_capture_dir = m_gpu_capture_dir->GetValue().utf8_string(); config.framebuffer_fetch = m_framebuffer_fetch->IsChecked(); #endif GetConfigHandle().Save(); } GeneralSettings2::~GeneralSettings2() { Unbind(wxEVT_CLOSE_WINDOW, &GeneralSettings2::OnClose, this); } void GeneralSettings2::OnClose(wxCloseEvent& event) { StoreConfig(); if (m_has_account_change) { wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH); GetParent()->ProcessWindowEvent(refresh_event); } event.Skip(); } void GeneralSettings2::ValidateConfig() { GetConfigHandle().Load(); auto& data = GetConfigHandle().data(); // todo //data.fullscreen_scaling = min(max(data.fullscreen_scaling,)) } void GeneralSettings2::DisableSettings(bool game_launched) { } void GeneralSettings2::OnAudioLatencyChanged(wxCommandEvent& event) { IAudioAPI::s_audioDelay = event.GetInt(); event.Skip(); } void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event) { if(event.GetEventObject() == m_input_volume) { std::shared_lock lock(g_audioInputMutex); if (g_inputAudio) g_inputAudio->SetVolume(event.GetInt()); } else { std::shared_lock lock(g_audioMutex); if(event.GetEventObject() == m_pad_volume) { if (g_padAudio) g_padAudio->SetVolume(event.GetInt()); g_padVolume = event.GetInt(); } else if (event.GetEventObject() == m_tv_volume) { if (g_tvAudio) g_tvAudio->SetVolume(event.GetInt()); } else { if(g_portalAudio) g_portalAudio->SetVolume(event.GetInt()); } } event.Skip(); } void GeneralSettings2::OnInputVolumeChanged(wxCommandEvent& event) { std::shared_lock lock(g_audioMutex); if (g_padAudio) g_padAudio->SetInputVolume(event.GetInt()); event.Skip(); } void GeneralSettings2::OnSliderChangedPercent(wxCommandEvent& event) { const auto slider = dynamic_cast(event.GetEventObject()); wxASSERT(slider); const auto control = dynamic_cast(event.GetEventUserData()); wxASSERT(control); auto slider_text = control->GetControl(); wxASSERT(slider_text); const auto value = event.GetInt(); slider->SetValue(value); slider_text->SetLabel(wxString::Format("%d%%", value)); event.Skip(); } void GeneralSettings2::OnLatencySliderChanged(wxCommandEvent& event) { const auto slider = dynamic_cast(event.GetEventObject()); wxASSERT(slider); const auto control = dynamic_cast(event.GetEventUserData()); wxASSERT(control); auto slider_text = control->GetControl(); wxASSERT(slider_text); const auto value = event.GetInt(); slider->SetValue(value); slider_text->SetLabel(wxString::Format("%dms", value * 12)); event.Skip(); } void GeneralSettings2::UpdateAudioDeviceList() { m_tv_device->Clear(); m_pad_device->Clear(); m_input_device->Clear(); m_portal_device->Clear(); m_tv_device->Append(_("Disabled")); m_pad_device->Append(_("Disabled")); m_input_device->Append(_("Disabled")); m_portal_device->Append(_("Disabled")); const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().audio_api; const auto devices = IAudioAPI::GetDevices(audio_api); for (auto& device : devices) { m_tv_device->Append(device->GetName(), new wxDeviceDescription(device)); m_pad_device->Append(device->GetName(), new wxDeviceDescription(device)); m_portal_device->Append(device->GetName(), new wxDeviceDescription(device)); } const auto input_audio_api = IAudioInputAPI::Cubeb; //(IAudioAPI::AudioAPI)GetConfig().input_audio_api; const auto input_devices = IAudioInputAPI::GetDevices(input_audio_api); for (auto& device : input_devices) { m_input_device->Append(device->GetName(), new wxInputDeviceDescription(device)); } if(m_tv_device->GetCount() > 1) m_tv_device->SetSelection(1); else m_tv_device->SetSelection(0); m_pad_device->SetSelection(0); m_input_device->SetSelection(0); m_portal_device->SetSelection(0); // todo reset global instance of audio device } void GeneralSettings2::ResetAccountInformation() { m_account_grid->SetSplitterPosition(100); m_active_account->SetSelection(0); for(auto it = m_account_grid->GetIterator(); !it.AtEnd(); ++it) { (*it)->SetValueToUnspecified(); } // refresh pane size m_account_information->InvalidateBestSize(); #if BOOST_OS_WINDOWS m_account_information->OnStateChange(GetBestSize()); #endif } void GeneralSettings2::OnAccountCreate(wxCommandEvent& event) { wxASSERT(Account::HasFreeAccountSlots()); wxCreateAccountDialog dialog(this); if (dialog.ShowModal() == wxID_CANCEL) return; Account account(dialog.GetPersistentId(), dialog.GetMiiName().ToStdWstring()); account.Save(); Account::RefreshAccounts(); const int index = m_active_account->Append(account.ToString(), new wxAccountData(account)); // update ui m_active_account->SetSelection(index); UpdateAccountInformation(); m_create_account->Enable(m_active_account->GetCount() < 0xC); m_delete_account->Enable(m_active_account->GetCount() > 1); // send main window event wxASSERT(GetParent()); wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH); GetParent()->ProcessWindowEvent(refresh_event); } void GeneralSettings2::OnAccountDelete(wxCommandEvent& event) { if(m_active_account->GetCount() == 1) { wxMessageBox(_("Can't delete the only account!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } const auto selection = m_active_account->GetSelection(); wxASSERT(selection != wxNOT_FOUND); auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); wxASSERT(obj); auto& account = obj->GetAccount(); const std::wstring format_str = _("Are you sure you want to delete the account {} with id {:x}?").ToStdWstring(); const std::wstring msg = fmt::format(fmt::runtime(format_str), std::wstring{ account.GetMiiName() }, account.GetPersistentId()); const int answer = wxMessageBox(msg, _("Confirmation"), wxYES_NO | wxCENTRE | wxICON_QUESTION, this); if (answer == wxNO) return; // todo: ask if saves should be deleted too? const fs::path path = account.GetFileName(); try { fs::remove_all(path.parent_path()); m_active_account->Delete(selection); m_active_account->SetSelection(0); Account::RefreshAccounts(); UpdateAccountInformation(); m_create_account->Enable(m_active_account->GetCount() < 0xC); m_delete_account->Enable(m_active_account->GetCount() > 1); } catch(const std::exception& ex) { SystemException sys(ex); cemuLog_log(LogType::Force, sys.what()); } } void GeneralSettings2::OnAccountSettingsChanged(wxPropertyGridEvent& event) { wxPGProperty* property = event.GetProperty(); if (!property) return; const wxAny value = property->GetValue(); if (value.IsNull()) return; const auto selection = m_active_account->GetSelection(); wxASSERT(selection != wxNOT_FOUND); auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); wxASSERT(obj); auto& account = obj->GetAccount(); // TODO make id changeable to free ids + current it? bool refresh_accounts = false; if (property->GetName() == kPropertyMiiName) { std::wstring new_name = value.As().ToStdWstring(); if (new_name.empty()) new_name = L"default"; account.SetMiiName(new_name); refresh_accounts = true; } else if (property->GetName() == kPropertyBirthday) { const std::string birthday = value.As().ToStdString(); const boost::char_separator sep{ "-" }; std::vector tokens; for (const auto& token : boost::tokenizer(birthday, sep)) { tokens.emplace_back(token); } if (tokens.size() == 3) { account.SetBirthYear(ConvertString(tokens[0])); account.SetBirthMonth(ConvertString(tokens[1])); account.SetBirthDay(ConvertString(tokens[2])); } } else if (property->GetName() == kPropertyGender) { account.SetGender(value.As()); } else if (property->GetName() == kPropertyEmail) { account.SetEmail(value.As().ToStdString()); } else if (property->GetName() == kPropertyCountry) { account.SetCountry(value.As()); } else cemu_assert_debug(false); account.Save(); Account::RefreshAccounts(); // refresh internal account list UpdateAccountInformation(); // refresh on invalid values if(refresh_accounts) { wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH); GetParent()->ProcessWindowEvent(refresh_event); } } void GeneralSettings2::UpdateAccountInformation() { m_account_grid->SetSplitterPosition(100); const auto selection = m_active_account->GetSelection(); if(selection == wxNOT_FOUND) { m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE); ResetAccountInformation(); m_online_status->SetLabel(_("No account selected")); return; } const auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); wxASSERT(obj); const auto& account = obj->GetAccount(); m_active_account->SetString(selection, account.ToString()); m_account_grid->GetProperty(kPropertyPersistentId)->SetValueFromString(fmt::format("{:x}", account.GetPersistentId())); m_account_grid->GetProperty(kPropertyMiiName)->SetValueFromString(std::wstring{ account.GetMiiName() }); m_account_grid->GetProperty(kPropertyBirthday)->SetValueFromString(fmt::format("{:04d}-{:02d}-{:02d}", account.GetBirthYear(), account.GetBirthMonth(), account.GetBirthDay())); const auto gender_property = m_account_grid->GetProperty(kPropertyGender); // gender 2 can be also female? gender_property->SetChoiceSelection(std::min(gender_property->GetChoices().GetCount() - 1, (uint32)account.GetGender())); m_account_grid->GetProperty(kPropertyEmail)->SetValueFromString(std::string{ account.GetEmail() }); auto* country_property = dynamic_cast(m_account_grid->GetProperty(kPropertyCountry)); wxASSERT(country_property); int index = (country_property)->GetIndexForValue(account.GetCountry()); if (index == wxNOT_FOUND) index = 0; country_property->SetChoiceSelection(index); const bool online_fully_valid = account.IsValidOnlineAccount() && ActiveSettings::HasRequiredOnlineFiles(); if (ActiveSettings::HasRequiredOnlineFiles()) { if(account.IsValidOnlineAccount()) m_online_status->SetLabel(_("Selected account is a valid online account")); else m_online_status->SetLabel(_("Selected account is not linked to a NNID or PNID")); } else { if(NCrypto::OTP_IsPresent() != NCrypto::SEEPROM_IsPresent()) m_online_status->SetLabel(_("OTP.bin or SEEPROM.bin is missing")); else if(NCrypto::OTP_IsPresent() && NCrypto::SEEPROM_IsPresent()) m_online_status->SetLabel(_("OTP and SEEPROM present but no certificate files were found")); else m_online_status->SetLabel(_("Online play is not set up. Follow the guide below to get started")); } if(online_fully_valid) { m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_CHECK_YES).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() | wxBORDER_NONE); } else { m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE); } // enable/disable network service field depending on online requirements m_active_service->Enable(online_fully_valid && !CafeSystem::IsTitleRunning()); if(online_fully_valid) { NetworkService service = GetConfig().GetAccountNetworkService(account.GetPersistentId()); m_active_service->SetSelection(static_cast(service)); // set the config option here for the selected service // this will guarantee that it's actually written to settings.xml // allowing us to eventually get rid of the legacy option in the (far) future GetConfig().SetAccountSelectedService(account.GetPersistentId(), service); } else { m_active_service->SetSelection(0); // force offline } wxString tmp = _("Network service"); tmp.append(" ("); tmp.append(wxString::FromUTF8(boost::nowide::narrow(account.GetMiiName()))); tmp.append(")"); m_active_service->SetLabel(tmp); // refresh pane size m_account_grid->InvalidateBestSize(); //m_account_grid->GetParent()->FitInside(); //m_account_information->OnStateChange(GetBestSize()); idk.. } void GeneralSettings2::UpdateOnlineAccounts() { m_active_account->Clear(); for(const auto& account : Account::GetAccounts()) { m_active_account->Append(fmt::format(L"{} ({:x})", std::wstring{ account.GetMiiName() }, account.GetPersistentId()), new wxAccountData(account)); } m_active_account->SetSelection(0); m_create_account->Enable(m_active_account->GetCount() < 0xC); m_delete_account->Enable(m_active_account->GetCount() > 1); UpdateAccountInformation(); } void GeneralSettings2::HandleGraphicsApiSelection() { int selection = m_vsync->GetSelection(); if(selection == wxNOT_FOUND) selection = GetConfig().vsync; m_vsync->Clear(); if (m_graphic_api->GetSelection() == 0) { // OpenGL m_vsync->AppendString(_("Off")); m_vsync->AppendString(_("On")); if (selection == 0) m_vsync->Select(0); else m_vsync->Select(1); m_graphic_device->Clear(); m_graphic_device->Disable(); m_gx2drawdone_sync->Enable(); m_async_compile->Disable(); #if ENABLE_METAL m_force_mesh_shaders->Disable(); #endif } else if (m_graphic_api->GetSelection() == 1) { // Vulkan m_gx2drawdone_sync->Disable(); m_async_compile->Enable(); #if ENABLE_METAL m_force_mesh_shaders->Disable(); #endif m_vsync->AppendString(_("Off")); m_vsync->AppendString(_("Double buffering")); m_vsync->AppendString(_("Triple buffering")); #if BOOST_OS_WINDOWS m_vsync->AppendString(_("Match emulated display (Experimental)")); #endif m_vsync->Select(selection); m_graphic_device->Enable(); auto devices = VulkanRenderer::GetDevices(); m_graphic_device->Clear(); if(!devices.empty()) { for(const auto& device : devices) { m_graphic_device->Append(device.name, new wxVulkanUUID(device)); } m_graphic_device->SetSelection(0); const auto& config = GetConfig(); for(size_t i = 0; i < devices.size(); ++i) { if(config.vk_graphic_device_uuid == devices[i].uuid) { m_graphic_device->SetSelection(i); break; } } } } #if ENABLE_METAL else { // Metal m_gx2drawdone_sync->Disable(); m_async_compile->Enable(); m_force_mesh_shaders->Enable(); m_vsync->AppendString(_("Off")); m_vsync->AppendString(_("On")); m_vsync->Select(selection); m_graphic_device->Enable(); m_graphic_device->Clear(); auto devices = MetalRenderer::GetDevices(); if(!devices.empty()) { for (const auto& device : devices) { m_graphic_device->Append(device.name, new wxMetalUUID(device)); } m_graphic_device->SetSelection(0); const auto& config = GetConfig(); for (size_t i = 0; i < devices.size(); ++i) { if (config.mtl_graphic_device_uuid == devices[i].uuid) { m_graphic_device->SetSelection(i); break; } } } } #endif } void GeneralSettings2::ApplyConfig() { ValidateConfig(); auto& wxGUIconfig = GetWxGUIConfig(); auto& config = GetConfig(); if (LaunchSettings::GetMLCPath().has_value()) m_mlc_path->SetValue(wxHelper::FromPath(LaunchSettings::GetMLCPath().value())); else m_mlc_path->SetValue(wxString::FromUTF8(config.mlc_path.GetValue())); m_save_window_position_size->SetValue(wxGUIconfig.window_position != Vector2i{-1,-1}); m_save_padwindow_position_size->SetValue(wxGUIconfig.pad_position != Vector2i{-1,-1}); m_discord_presence->SetValue(wxGUIconfig.use_discord_presence); m_fullscreen_menubar->SetValue(wxGUIconfig.fullscreen_menubar); m_auto_update->SetValue(wxGUIconfig.check_update); m_receive_untested_releases->SetValue(wxGUIconfig.receive_untested_updates); m_save_screenshot->SetValue(wxGUIconfig.save_screenshot); m_disable_screensaver->SetValue(config.disable_screensaver); m_play_boot_sound->SetValue(config.play_boot_sound); #if BOOST_OS_WINDOWS m_msw_theme->SetSelection(wxGUIconfig.msw_theme); #endif #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) m_feral_gamemode->SetValue(wxGUIconfig.feral_gamemode); #endif // temporary workaround because feature crashes on macOS #if BOOST_OS_MACOS m_disable_screensaver->SetValue(false); #endif m_game_paths->Clear(); for (auto& path : config.game_paths) { m_game_paths->Append(wxString::FromUTF8(path)); } const auto& app = wxGetApp(); for (const auto& language : app.GetLanguages()) { if (wxGUIconfig.language == language->Language) { m_language->SetStringSelection(language->DescriptionNative); break; } } // graphics m_graphic_api->SetSelection(config.graphic_api); m_vsync->SetSelection(config.vsync); m_overrideGamma->SetValue(config.overrideAppGammaPreference); m_overrideGammaValue->SetValue(config.overrideGammaValue); m_userDisplayisSRGB->SetValue(config.userDisplayGamma == 0.0f); m_userDisplayGamma->SetValue(config.userDisplayGamma); if(m_userDisplayisSRGB->GetValue()) { m_userDisplayGamma->Disable(); m_userDisplayGamma->SetValue(2.2f); } m_async_compile->SetValue(config.async_compile); m_gx2drawdone_sync->SetValue(config.gx2drawdone_sync); #if ENABLE_METAL m_force_mesh_shaders->SetValue(config.force_mesh_shaders); #endif m_upscale_filter->SetSelection(config.upscale_filter); m_downscale_filter->SetSelection(config.downscale_filter); m_fullscreen_scaling->SetSelection(config.fullscreen_scaling); wxASSERT((uint32)config.overlay.position < m_overlay_position->GetCount()); m_overlay_position->SetSelection((int)config.overlay.position); m_overlay_font_color->SetColour(wxColour((unsigned long)config.overlay.text_color)); uint32 selection = (config.overlay.text_scale - 50) / 25; wxASSERT(selection < m_overlay_scale->GetCount()); m_overlay_scale->SetSelection(selection); m_overlay_fps->SetValue(config.overlay.fps); m_overlay_drawcalls->SetValue(config.overlay.drawcalls); m_overlay_cpu->SetValue(config.overlay.cpu_usage); m_overlay_cpu_per_core->SetValue(config.overlay.cpu_per_core_usage); m_overlay_ram->SetValue(config.overlay.ram_usage); m_overlay_vram->SetValue(config.overlay.vram_usage); m_overlay_debug->SetValue(config.overlay.debug); wxASSERT((uint32)config.notification.position < m_notification_position->GetCount()); m_notification_position->SetSelection((int)config.notification.position); m_notification_font_color->SetColour(wxColour((unsigned long)config.notification.text_color)); selection = (config.notification.text_scale - 50) / 25; wxASSERT(selection < m_notification_scale->GetCount()); m_notification_scale->SetSelection(selection); m_controller_profile_name->SetValue(config.notification.controller_profiles); m_controller_low_battery->SetValue(config.notification.controller_battery); m_shader_compiling->SetValue(config.notification.shader_compiling); m_friends_data->SetValue(config.notification.friends); // audio if(config.audio_api == IAudioAPI::DirectSound) m_audio_api->SetStringSelection(kDirectSound); else if(config.audio_api == IAudioAPI::XAudio27) m_audio_api->SetStringSelection(kXAudio27); else if(config.audio_api == IAudioAPI::XAudio2) m_audio_api->SetStringSelection(kXAudio2); else if(config.audio_api == IAudioAPI::Cubeb) m_audio_api->SetStringSelection(kCubeb); SendSliderEvent(m_audio_latency, config.audio_delay); m_tv_channels->SetSelection(config.tv_channels); //m_pad_channels->SetSelection(config.pad_channels); m_pad_channels->SetSelection(0); //m_input_channels->SetSelection(config.pad_channels); m_input_channels->SetSelection(0); SendSliderEvent(m_tv_volume, config.tv_volume); if (!config.tv_device.empty() && m_tv_device->HasClientObjectData()) { for(uint32 i = 0; i < m_tv_device->GetCount(); ++i) { const auto device_description = (wxDeviceDescription*)m_tv_device->GetClientObject(i); if (device_description && config.tv_device == device_description->GetDescription()->GetIdentifier()) { m_tv_device->SetSelection(i); break; } } } else m_tv_device->SetSelection(0); SendSliderEvent(m_pad_volume, config.pad_volume); if (!config.pad_device.empty() && m_pad_device->HasClientObjectData()) { for (uint32 i = 0; i < m_pad_device->GetCount(); ++i) { const auto device_description = (wxDeviceDescription*)m_pad_device->GetClientObject(i); if (device_description && config.pad_device == device_description->GetDescription()->GetIdentifier()) { m_pad_device->SetSelection(i); break; } } } else m_pad_device->SetSelection(0); SendSliderEvent(m_input_volume, config.input_volume); if (!config.input_device.empty() && m_input_device->HasClientObjectData()) { for (uint32 i = 0; i < m_input_device->GetCount(); ++i) { const auto device_description = (wxInputDeviceDescription*)m_input_device->GetClientObject(i); if (device_description && config.input_device == device_description->GetDescription()->GetIdentifier()) { m_input_device->SetSelection(i); break; } } } else m_input_device->SetSelection(0); SendSliderEvent(m_portal_volume, config.portal_volume); if (!config.portal_device.empty() && m_portal_device->HasClientObjectData()) { for (uint32 i = 0; i < m_portal_device->GetCount(); ++i) { const auto device_description = (wxDeviceDescription*)m_portal_device->GetClientObject(i); if (device_description && config.portal_device == device_description->GetDescription()->GetIdentifier()) { m_portal_device->SetSelection(i); break; } } } else m_portal_device->SetSelection(0); // account UpdateOnlineAccounts(); m_active_account->SetSelection(0); for(uint32 i = 0; i < m_active_account->GetCount(); ++i) { const auto* obj = dynamic_cast(m_active_account->GetClientObject(i)); wxASSERT(obj); if(obj->GetAccount().GetPersistentId() == ActiveSettings::GetPersistentId()) { m_active_account->SetSelection(i); break; } } m_active_service->SetSelection((int)config.GetAccountNetworkService(ActiveSettings::GetPersistentId())); UpdateAccountInformation(); // debug m_crash_dump->SetSelection((int)config.crash_dump.GetValue()); m_gdb_port->SetValue(config.gdb_port.GetValue()); #if ENABLE_METAL m_gpu_capture_dir->SetValue(wxString::FromUTF8(config.gpu_capture_dir.GetValue())); m_framebuffer_fetch->SetValue(config.framebuffer_fetch); #endif } void GeneralSettings2::OnAudioAPISelected(wxCommandEvent& event) { IAudioAPI::AudioAPI api; if (m_audio_api->GetStringSelection() == kDirectSound) api = IAudioAPI::DirectSound; else if (m_audio_api->GetStringSelection() == kXAudio27) api = IAudioAPI::XAudio27; else if (m_audio_api->GetStringSelection() == kXAudio2) api = IAudioAPI::XAudio2; else if (m_audio_api->GetStringSelection() == kCubeb) api = IAudioAPI::Cubeb; else { wxFAIL_MSG("invalid audio api selected!"); return; } GetConfig().audio_api = api; UpdateAudioDeviceList(); OnAudioDeviceSelected(event); } #define AX_FRAMES_PER_GROUP 4 void GeneralSettings2::UpdateAudioDevice() { auto& config = GetConfig(); std::unique_lock lock(g_audioMutex); std::unique_lock inputLock(g_audioInputMutex); // tv audio device { const auto selection = m_tv_device->GetSelection(); if (selection == wxNOT_FOUND) { cemu_assert_debug(false); return; } g_tvAudio.reset(); if (m_tv_device->HasClientObjectData()) { const auto description = (wxDeviceDescription*)m_tv_device->GetClientObject(selection); if (description) { sint32 channels; if (m_game_launched && g_tvAudio) channels = g_tvAudio->GetChannels(); else channels = CemuConfig::AudioChannelsToNChannels(config.tv_channels); try { g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); g_tvAudio->SetVolume(m_tv_volume->GetValue()); } catch (std::runtime_error& ex) { cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what()); } } } } // pad audio device { const auto selection = m_pad_device->GetSelection(); if (selection == wxNOT_FOUND) { cemu_assert_debug(false); return; } g_padAudio.reset(); if (m_pad_device->HasClientObjectData()) { const auto description = (wxDeviceDescription*)m_pad_device->GetClientObject(selection); if (description) { sint32 channels; if (m_game_launched && g_padAudio) channels = g_padAudio->GetChannels(); else channels = CemuConfig::AudioChannelsToNChannels(config.pad_channels); g_padVolume = m_pad_volume->GetValue(); try { g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); g_padAudio->SetVolume(m_pad_volume->GetValue()); } catch (std::runtime_error& ex) { cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what()); } } } } // input audio device { const auto selection = m_input_device->GetSelection(); if (selection == wxNOT_FOUND) { cemu_assert_debug(false); return; } g_inputAudio.reset(); if (m_input_device->HasClientObjectData()) { const auto description = (wxInputDeviceDescription*)m_input_device->GetClientObject(selection); if (description) { sint32 channels; if (m_game_launched && g_inputAudio) channels = g_inputAudio->GetChannels(); else channels = CemuConfig::AudioChannelsToNChannels(config.input_channels); try { g_inputAudio = IAudioInputAPI::CreateDevice(IAudioInputAPI::AudioInputAPI::Cubeb, description->GetDescription(), 32000, channels, snd_core::AX_SAMPLES_PER_3MS_32KHZ, 16); g_inputAudio->SetVolume(m_input_volume->GetValue()); } catch (std::runtime_error& ex) { cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what()); } } } } // skylander portal audio device { const auto selection = m_portal_device->GetSelection(); if (selection == wxNOT_FOUND) { cemu_assert_debug(false); return; } g_portalAudio.reset(); if (m_portal_device->HasClientObjectData()) { const auto description = (wxDeviceDescription*)m_portal_device->GetClientObject(selection); if (description) { sint32 channels; if (m_game_launched && g_portalAudio) channels = g_portalAudio->GetChannels(); else channels = 1; try { g_portalAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 8000, 1, 32, 16); g_portalAudio->SetVolume(m_portal_volume->GetValue()); } catch (std::runtime_error& ex) { cemuLog_log(LogType::Force, "can't initialize portal audio: {}", ex.what()); } } } } } void GeneralSettings2::OnAudioDeviceSelected(wxCommandEvent& event) { UpdateAudioDevice(); } void GeneralSettings2::OnAudioChannelsSelected(wxCommandEvent& event) { const auto obj = wxDynamicCast(event.GetEventObject(), wxChoice); wxASSERT(obj); if (obj->GetSelection() == wxNOT_FOUND) return; auto& config = GetConfig(); if (obj == m_tv_channels) { if (config.tv_channels == (AudioChannels)obj->GetSelection()) return; config.tv_channels = (AudioChannels)obj->GetSelection(); } else if (obj == m_pad_channels) { if (config.pad_channels == (AudioChannels)obj->GetSelection()) return; config.pad_channels = (AudioChannels)obj->GetSelection(); } else cemu_assert_debug(false); if(m_game_launched) wxMessageBox(_("You have to restart the game in order to apply the new settings."), _("Information"), wxOK | wxCENTRE, this); else UpdateAudioDevice(); } void GeneralSettings2::OnGraphicAPISelected(wxCommandEvent& event) { HandleGraphicsApiSelection(); } void GeneralSettings2::OnUserDisplaySRGBSelected(wxCommandEvent& event) { m_userDisplayGamma->SetValue(2.2f); if(event.IsChecked()) m_userDisplayGamma->Disable(); else m_userDisplayGamma->Enable(); auto& config = GetConfig(); config.userDisplayGamma = m_userDisplayGamma->GetValue() * !event.IsChecked(); } void GeneralSettings2::OnAddPathClicked(wxCommandEvent& event) { wxDirDialog path_dialog(this, _("Select a directory containing games."), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty()) return; const auto path = path_dialog.GetPath(); // test if already included for (auto& s : m_game_paths->GetStrings()) { if (s == path) return; } m_game_paths->Append(path); m_reload_gamelist = true; // trigger title list rescan with new path configuration CafeTitleList::ClearScanPaths(); for (auto& it : m_game_paths->GetStrings()) CafeTitleList::AddScanPath(wxHelper::MakeFSPath(it)); CafeTitleList::Refresh(); } void GeneralSettings2::OnRemovePathClicked(wxCommandEvent& event) { const auto selection = m_game_paths->GetSelection(); if (selection == wxNOT_FOUND) return; m_game_paths->Delete(selection); m_reload_gamelist = true; // trigger title list rescan with new path configuration CafeTitleList::ClearScanPaths(); for (auto& it : m_game_paths->GetStrings()) CafeTitleList::AddScanPath(wxHelper::MakeFSPath(it)); CafeTitleList::Refresh(); } void GeneralSettings2::OnActiveAccountChanged(wxCommandEvent& event) { UpdateAccountInformation(); m_has_account_change = true; } void GeneralSettings2::OnAccountServiceChanged(wxCommandEvent& event) { auto& config = GetConfig(); uint32 peristentId = GetSelectedAccountPersistentId(); config.SetAccountSelectedService(peristentId, static_cast(m_active_service->GetSelection())); UpdateAccountInformation(); } void GeneralSettings2::OnMLCPathSelect(wxCommandEvent& event) { if(CafeSystem::IsTitleRunning()) { wxMessageBox(_("Can't change MLC path while a game is running!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } // show directory dialog wxDirDialog path_dialog(this, _("Select MLC directory"), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty()) return; // check if the choosen MLC path is an already initialized MLC location fs::path newMlc = wxHelper::MakeFSPath(path_dialog.GetPath()); if(CemuApp::CheckMLCPath(newMlc)) { // ask user if they are sure they want to use this folder and let them know that accounts and saves wont transfer wxString message = _("Note that changing the MLC location will not transfer any accounts or save files. Are you sure you want to change the path?"); wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); if(dialog.ShowModal() == wxID_NO) return; if( !CemuApp::CreateDefaultMLCFiles(newMlc) ) // creating also acts as a check for read+write access { wxMessageBox(_("Failed to create default MLC files in the selected directory. The MLC path has not been changed"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } } else { // ask user if they want to create a new mlc structure at the choosen location wxString message = _("The selected directory does not contain the expected MLC structure. Do you want to create a new MLC structure in this directory?\nNote that changing the MLC location will not transfer any accounts or save files."); wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); if( !CemuApp::CreateDefaultMLCFiles(newMlc) ) { wxMessageBox(_("Failed to create default MLC files in the selected directory. The MLC path has not been changed"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } } // update MLC path and store any other modified settings GetConfig().SetMLCPath(newMlc); StoreConfig(); wxMessageBox(_("Cemu needs to be restarted for the changes to take effect."), _("Information"), wxOK | wxCENTRE | wxICON_INFORMATION, this); // close settings and then cemu wxCloseEvent closeEvent(wxEVT_CLOSE_WINDOW); wxPostEvent(this, closeEvent); wxPostEvent(GetParent(), closeEvent); } void GeneralSettings2::OnMLCPathClear(wxCommandEvent& event) { if(CafeSystem::IsTitleRunning()) { wxMessageBox(_("Can't change MLC path while a game is running!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } wxString message = _("Note that changing the MLC location will not transfer any accounts or save files. Are you sure you want to change the path?"); wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); if(dialog.ShowModal() == wxID_NO) return; GetConfig().SetMLCPath(""); StoreConfig(); GetConfigHandle().Save(); wxMessageBox(_("Cemu needs to be restarted for the changes to take effect."), _("Information"), wxOK | wxCENTRE | wxICON_INFORMATION, this); // close settings and then cemu wxCloseEvent closeEvent(wxEVT_CLOSE_WINDOW); wxPostEvent(this, closeEvent); wxPostEvent(GetParent(), closeEvent); } void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) { const auto selection = m_active_account->GetSelection(); if (selection == wxNOT_FOUND) return; const auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); wxASSERT(obj); const auto& account = obj->GetAccount(); const auto validator = account.ValidateOnlineFiles(); if (validator) // everything valid? shouldn't happen return; wxString err; err << _("The following error(s) have been found:") << '\n'; if (validator.otp == OnlineValidator::FileState::Missing) err << _("otp.bin missing in Cemu directory") << '\n'; else if(validator.otp == OnlineValidator::FileState::Corrupted) err << _("otp.bin is invalid") << '\n'; if (validator.seeprom == OnlineValidator::FileState::Missing) err << _("seeprom.bin missing in Cemu directory") << '\n'; else if(validator.seeprom == OnlineValidator::FileState::Corrupted) err << _("seeprom.bin is invalid") << '\n'; if(!validator.missing_files.empty()) { err << _("Missing certificate and key files:") << '\n'; int counter = 0; for (const auto& f : validator.missing_files) { err << f << '\n'; ++counter; if(counter > 10) { err << "..." << '\n'; break; } } err << '\n'; } if (!validator.valid_account) { err << _("The currently selected account is not a valid or dumped online account:") << '\n'; err << GetOnlineAccountErrorMessage(validator.account_error); } wxMessageBox(err, _("Online Status"), wxOK | wxCENTRE | wxICON_INFORMATION); } wxString GeneralSettings2::GetOnlineAccountErrorMessage(OnlineAccountError error) { switch (error) { case OnlineAccountError::kNoAccountId: return _("AccountId missing (The account is not connected to a NNID/PNID)"); case OnlineAccountError::kNoPasswordCached: return _("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)"); case OnlineAccountError::kPasswordCacheEmpty: return _("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)"); case OnlineAccountError::kNoPrincipalId: return _("PrincipalId missing"); default: return "no error"; } } ================================================ FILE: src/gui/wxgui/GeneralSettings2.h ================================================ #pragma once #include #include #include class wxCheckBox; class wxChoice; class wxColourPickerCtrl; class wxListBox; class wxNotebook; class wxRadioBox; class wxSlider; class wxSpinCtrl; class wxSpinCtrlDouble; class wxStaticText; wxDECLARE_EVENT(wxEVT_ACCOUNTLIST_REFRESH, wxCommandEvent); class GeneralSettings2 : public wxDialog { public: GeneralSettings2(wxWindow* parent, bool game_launched); ~GeneralSettings2(); [[nodiscard]] bool ShouldReloadGamelist() const { return m_reload_gamelist; } [[nodiscard]] bool MLCModified() const { return m_mlc_modified; } void OnClose(wxCloseEvent& event); private: void ValidateConfig(); void StoreConfig(); void DisableSettings(bool game_launched); bool m_reload_gamelist = false; bool m_mlc_modified = false; bool m_game_launched; bool m_has_account_change = false; // keep track of dirty state of accounts wxPanel* AddGeneralPage(wxNotebook* notebook); wxPanel* AddGraphicsPage(wxNotebook* notebook); wxPanel* AddAudioPage(wxNotebook* notebook); wxPanel* AddOverlayPage(wxNotebook* notebook); wxPanel* AddAccountPage(wxNotebook* notebook); wxPanel* AddDebugPage(wxNotebook* notebook); // General wxChoice * m_language; wxCheckBox* m_save_window_position_size; wxCheckBox* m_save_padwindow_position_size; wxCheckBox* m_discord_presence, *m_fullscreen_menubar; wxCheckBox* m_auto_update, *m_receive_untested_releases, *m_save_screenshot; wxCheckBox* m_disable_screensaver; wxCheckBox* m_play_boot_sound; #if BOOST_OS_WINDOWS wxChoice* m_msw_theme; #endif #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) wxCheckBox* m_feral_gamemode; #endif wxListBox* m_game_paths; wxTextCtrl* m_mlc_path; // Graphics wxChoice* m_graphic_api, * m_graphic_device; wxChoice* m_vsync; wxCheckBox* m_overrideGamma; wxSpinCtrlDouble* m_overrideGammaValue; wxSpinCtrlDouble* m_userDisplayGamma; wxCheckBox* m_userDisplayisSRGB; wxCheckBox *m_async_compile, *m_gx2drawdone_sync; #if ENABLE_METAL wxCheckBox *m_force_mesh_shaders; #endif wxRadioBox* m_upscale_filter, *m_downscale_filter, *m_fullscreen_scaling; wxChoice* m_overlay_position, *m_notification_position, *m_overlay_scale, *m_notification_scale; wxCheckBox* m_controller_profile_name, *m_controller_low_battery, *m_shader_compiling, *m_friends_data; wxCheckBox *m_overlay_fps, *m_overlay_drawcalls, *m_overlay_cpu, *m_overlay_cpu_per_core,*m_overlay_ram, *m_overlay_vram, *m_overlay_debug; wxColourPickerCtrl *m_overlay_font_color, *m_notification_font_color; // Audio wxChoice* m_audio_api; wxSlider *m_audio_latency; wxSlider *m_tv_volume, *m_pad_volume, *m_input_volume, *m_portal_volume; wxChoice *m_tv_channels, *m_pad_channels, *m_input_channels; wxChoice *m_tv_device, *m_pad_device, *m_input_device, *m_portal_device; // Account wxButton* m_create_account, * m_delete_account; wxChoice* m_active_account; wxRadioBox* m_active_service; wxCollapsiblePane* m_account_information; wxPropertyGrid* m_account_grid; wxBitmapButton* m_validate_online; wxStaticText* m_online_status; // Debug wxChoice* m_crash_dump; wxSpinCtrl* m_gdb_port; #if ENABLE_METAL wxTextCtrl* m_gpu_capture_dir; wxCheckBox* m_framebuffer_fetch; #endif void OnAccountCreate(wxCommandEvent& event); void OnAccountDelete(wxCommandEvent& event); void OnAccountSettingsChanged(wxPropertyGridEvent& event); void OnAudioLatencyChanged(wxCommandEvent& event); void OnVolumeChanged(wxCommandEvent& event); void OnInputVolumeChanged(wxCommandEvent& event); void OnSliderChangedPercent(wxCommandEvent& event); void OnLatencySliderChanged(wxCommandEvent& event); void OnAudioAPISelected(wxCommandEvent& event); void OnAudioDeviceSelected(wxCommandEvent& event); void OnAudioChannelsSelected(wxCommandEvent& event); void OnGraphicAPISelected(wxCommandEvent& event); void OnUserDisplaySRGBSelected(wxCommandEvent& event); void OnAddPathClicked(wxCommandEvent& event); void OnRemovePathClicked(wxCommandEvent& event); void OnActiveAccountChanged(wxCommandEvent& event); void OnMLCPathSelect(wxCommandEvent& event); void OnMLCPathClear(wxCommandEvent& event); void OnShowOnlineValidator(wxCommandEvent& event); void OnAccountServiceChanged(wxCommandEvent& event); static wxString GetOnlineAccountErrorMessage(OnlineAccountError error); uint32 GetSelectedAccountPersistentId(); // updates cemu audio devices void UpdateAudioDevice(); // refreshes audio device list for dropdown void UpdateAudioDeviceList(); void ResetAccountInformation(); void UpdateAccountInformation(); void UpdateOnlineAccounts(); void HandleGraphicsApiSelection(); void ApplyConfig(); }; ================================================ FILE: src/gui/wxgui/GettingStartedDialog.cpp ================================================ #include "wxgui/GettingStartedDialog.h" #include #include #include #include #include #include #include "config/ActiveSettings.h" #include "wxCemuConfig.h" #include "wxgui/CemuApp.h" #include "wxgui/DownloadGraphicPacksWindow.h" #include "wxgui/GraphicPacksWindow2.h" #include "wxgui/input/InputSettings2.h" #include "config/CemuConfig.h" #include "Cafe/TitleList/TitleList.h" #if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD #include "resource/embedded/resources.h" #endif #include "wxHelper.h" wxDEFINE_EVENT(EVT_REFRESH_FIRST_PAGE, wxCommandEvent); // used to refresh the first page after the language change wxPanel* GettingStartedDialog::CreatePage1() { auto* mainPanel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto* page1_sizer = new wxBoxSizer(wxVERTICAL); { auto* sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(new wxStaticBitmap(mainPanel, wxID_ANY, wxICON(M_WND_ICON128)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_page1.staticText11 = new wxStaticText(mainPanel, wxID_ANY, _("It looks like you're starting Cemu for the first time.\nThis quick setup assistant will help you get the best experience")); m_page1.staticText11->Wrap(-1); sizer->Add(m_page1.staticText11, 0, wxALL, 5); page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); } if(ActiveSettings::IsPortableMode()) { m_page1.portableModeInfoText = new wxStaticText(mainPanel, wxID_ANY, _("Cemu is running in portable mode")); m_page1.portableModeInfoText->Show(true); page1_sizer->Add(m_page1.portableModeInfoText, 0, wxALL, 5); } // language selection #if 0 { m_page1.languageBoxSizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Language")); m_page1.languageText = new wxStaticText(m_page1.languageBoxSizer->GetStaticBox(), wxID_ANY, _("Select the language you want to use in Cemu")); m_page1.languageBoxSizer->Add(m_page1.languageText, 0, wxALL, 5); wxString language_choices[] = { _("Default") }; wxChoice* m_language = new wxChoice(m_page1.languageBoxSizer->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(language_choices), language_choices); m_language->SetSelection(0); for (const auto& language : wxGetApp().GetLanguages()) { m_language->Append(language->DescriptionNative); } m_language->SetSelection(0); m_page1.languageBoxSizer->Add(m_language, 0, wxALL | wxEXPAND, 5); page1_sizer->Add(m_page1.languageBoxSizer, 0, wxALL | wxEXPAND, 5); m_language->Bind(wxEVT_CHOICE, [this, m_language](const auto&) { const auto language = m_language->GetStringSelection(); auto selection = m_language->GetSelection(); if (selection == 0) GetConfig().language = wxLANGUAGE_DEFAULT; else { auto& app = wxGetApp(); const auto language = m_language->GetStringSelection(); for (const auto& lang : app.GetLanguages()) { if (lang->DescriptionNative == language) { app.LocalizeUI(static_cast(lang->Language)); wxCommandEvent event(EVT_REFRESH_FIRST_PAGE); wxPostEvent(this, event); break; } } } }); } #endif { m_page1.gamePathBoxSizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Game paths")); m_page1.gamePathText = new wxStaticText(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, _("The game path is scanned by Cemu to automatically locate your games, game updates and DLCs. We recommend creating a dedicated directory in which\nyou place all your Wii U game files. Additional paths can be set later in Cemu's general settings. All common Wii U game formats are supported by Cemu.")); m_page1.gamePathBoxSizer->Add(m_page1.gamePathText, 0, wxALL, 5); auto* game_path_sizer = new wxBoxSizer(wxHORIZONTAL); m_page1.gamePathText2 = new wxStaticText(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, _("Game path")); game_path_sizer->Add(m_page1.gamePathText2, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_page1.gamePathPicker = new wxDirPickerCtrl(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select a folder")); game_path_sizer->Add(m_page1.gamePathPicker, 1, wxALL, 5); m_page1.gamePathBoxSizer->Add(game_path_sizer, 0, wxEXPAND, 5); page1_sizer->Add(m_page1.gamePathBoxSizer, 0, wxALL | wxEXPAND, 5); } { auto* sizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Graphic packs && mods")); sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Graphic packs improve games by offering the ability to change resolution, increase FPS, tweak visuals or add gameplay modifications.\nGet started by opening the graphic packs configuration window.\n")), 0, wxALL, 5); auto* download_gp = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Download and configure graphic packs")); download_gp->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnConfigureGPs, this); sizer->Add(download_gp, 0, wxALIGN_CENTER | wxALL, 5); page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); } { auto* sizer = new wxFlexGridSizer(0, 1, 0, 0); sizer->AddGrowableCol(0); sizer->AddGrowableRow(0); sizer->SetFlexibleDirection(wxBOTH); sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); auto* next = new wxButton(mainPanel, wxID_ANY, _("Next")); next->Bind(wxEVT_BUTTON, [this](const auto&){m_notebook->SetSelection(1); }); sizer->Add(next, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5); page1_sizer->Add(sizer, 1, wxEXPAND, 5); } mainPanel->SetSizer(page1_sizer); return mainPanel; } wxPanel* GettingStartedDialog::CreatePage2() { auto* result = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto* page2_sizer = new wxBoxSizer(wxVERTICAL); { auto* sizer = new wxStaticBoxSizer(new wxStaticBox(result, wxID_ANY, _("Input settings")), wxVERTICAL); sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("You can configure one controller for each player.\nWe advise you to always use GamePad as emulated input for the first player, since many games expect the GamePad to be present.\nIt is also required for touch functionality.\nThe default global hotkeys are:\nCTRL - show pad screen\nCTRL + TAB - toggle pad screen\nALT + ENTER - toggle fullscreen\nESC - leave fullscreen\n\nIf you're having trouble configuring your controller, make sure to have it in idle state and press calibrate.\nAlso don't set the axis deadzone too low.")), 0, wxALL, 5); auto* input_button = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Configure input")); input_button->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnInputSettings, this); sizer->Add(input_button, 0, wxALIGN_CENTER | wxALL, 5); page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); } { auto* sizer = new wxStaticBoxSizer(new wxStaticBox(result, wxID_ANY, _("Additional options")), wxVERTICAL); auto* option_sizer = new wxFlexGridSizer(0, 2, 0, 0); option_sizer->SetFlexibleDirection(wxBOTH); option_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); m_page2.fullscreenCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Start games with fullscreen")); option_sizer->Add(m_page2.fullscreenCheckbox, 0, wxALL, 5); m_page2.separateCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Open separate pad screen")); option_sizer->Add(m_page2.separateCheckbox, 0, wxALL, 5); m_page2.updateCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Automatically check for updates")); option_sizer->Add(m_page2.updateCheckbox, 0, wxALL, 5); #if BOOST_OS_LINUX if (!std::getenv("APPIMAGE")) { m_page2.updateCheckbox->Disable(); } #endif sizer->Add(option_sizer, 1, wxEXPAND, 5); page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); } { auto* sizer = new wxFlexGridSizer(0, 3, 0, 0); sizer->AddGrowableCol(1); sizer->AddGrowableRow(0); sizer->SetFlexibleDirection(wxBOTH); sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); auto* previous = new wxButton(result, wxID_ANY, _("Previous")); previous->Bind(wxEVT_BUTTON, [this](const auto&) {m_notebook->SetSelection(0); }); sizer->Add(previous, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5); auto* close = new wxButton(result, wxID_ANY, _("Close")); close->Bind(wxEVT_BUTTON, [this](const auto&){ Close(); }); sizer->Add(close, 1, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5); page2_sizer->Add(sizer, 1, wxEXPAND | wxLEFT, 5); } result->SetSizer(page2_sizer); return result; } void GettingStartedDialog::ApplySettings() { auto& config = GetWxGUIConfig(); m_page2.fullscreenCheckbox->SetValue(config.fullscreen.GetValue()); m_page2.updateCheckbox->SetValue(config.check_update.GetValue()); m_page2.separateCheckbox->SetValue(config.pad_open.GetValue()); } void GettingStartedDialog::UpdateWindowSize() { for (auto it = m_notebook->GetChildren().GetFirst(); it; it = it->GetNext()) { it->GetData()->Layout(); } m_notebook->Layout(); Layout(); Fit(); } void GettingStartedDialog::OnClose(wxCloseEvent& event) { event.Skip(); auto& wxGUIConfig = GetWxGUIConfig(); wxGUIConfig.fullscreen = m_page2.fullscreenCheckbox->GetValue(); wxGUIConfig.check_update = m_page2.updateCheckbox->GetValue(); wxGUIConfig.pad_open = m_page2.separateCheckbox->GetValue(); auto& config = GetConfig(); const fs::path gamePath = wxHelper::MakeFSPath(m_page1.gamePathPicker->GetPath()); std::error_code ec; if (!gamePath.empty() && fs::exists(gamePath, ec)) { const auto it = std::find(config.game_paths.cbegin(), config.game_paths.cend(), gamePath); if (it == config.game_paths.cend()) { config.game_paths.emplace_back(_pathToUtf8(gamePath)); } } } GettingStartedDialog::GettingStartedDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _("Getting started"), wxDefaultPosition, { 740,530 }, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { auto* sizer = new wxBoxSizer(wxVERTICAL); m_notebook = new wxSimplebook(this, wxID_ANY); auto* m_page1 = CreatePage1(); m_notebook->AddPage(m_page1, wxEmptyString); auto* m_page2 = CreatePage2(); m_notebook->AddPage(m_page2, wxEmptyString); sizer->Add(m_notebook, 1, wxEXPAND | wxALL, 5); this->SetSizer(sizer); this->Centre(wxBOTH); this->Bind(wxEVT_CLOSE_WINDOW, &GettingStartedDialog::OnClose, this); ApplySettings(); UpdateWindowSize(); } void GettingStartedDialog::OnConfigureGPs(wxCommandEvent& event) { DownloadGraphicPacksWindow dialog(this); dialog.ShowModal(); GraphicPacksWindow2::RefreshGraphicPacks(); GraphicPacksWindow2 window(this, 0); window.ShowModal(); } void GettingStartedDialog::OnInputSettings(wxCommandEvent& event) { InputSettings2 dialog(this); dialog.ShowModal(); } ================================================ FILE: src/gui/wxgui/GettingStartedDialog.h ================================================ #pragma once #include #include #include #include #include #include #include class GettingStartedDialog : public wxDialog { public: GettingStartedDialog(wxWindow* parent = nullptr); private: wxPanel* CreatePage1(); wxPanel* CreatePage2(); void ApplySettings(); void UpdateWindowSize(); void OnClose(wxCloseEvent& event); void OnConfigureGPs(wxCommandEvent& event); void OnInputSettings(wxCommandEvent& event); wxSimplebook* m_notebook; struct { // header wxStaticText* staticText11{}; wxStaticText* portableModeInfoText{}; // game path box wxStaticBoxSizer* gamePathBoxSizer{}; wxStaticText* gamePathText{}; wxStaticText* gamePathText2{}; wxDirPickerCtrl* gamePathPicker{}; }m_page1; struct { wxCheckBox* fullscreenCheckbox; wxCheckBox* separateCheckbox; wxCheckBox* updateCheckbox; }m_page2; }; ================================================ FILE: src/gui/wxgui/GraphicPacksWindow2.cpp ================================================ #include "helpers/wxHelpers.h" #include "wxgui/wxgui.h" #include "wxgui/GraphicPacksWindow2.h" #include "wxgui/DownloadGraphicPacksWindow.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "config/CemuConfig.h" #include "config/ActiveSettings.h" #include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" #include "Cafe/CafeSystem.h" #include "Cafe/TitleList/TitleList.h" #include "wxHelper.h" #if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD #include "resource/embedded/resources.h" #endif // main.cpp class wxGraphicPackData : public wxTreeItemData { public: wxGraphicPackData(GraphicPackPtr pack) : m_pack(std::move(pack)) { } const GraphicPackPtr& GetGraphicPack() const { return m_pack; } private: GraphicPackPtr m_pack; }; void GraphicPacksWindow2::FillGraphicPackList() const { wxWindowUpdateLocker lock(m_graphic_pack_tree); m_graphic_pack_tree->DeleteAllItems(); auto graphic_packs = GraphicPack2::GetGraphicPacks(); const auto root = m_graphic_pack_tree->AddRoot("Root"); const bool has_filter = !m_filter.empty(); for(auto& p : graphic_packs) { if (!p->IsUniversal()) { // filter graphic packs by given title id if (m_filter_installed_games && !m_installed_games.empty()) { bool found = false; for (uint64 titleId : p->GetTitleIds()) { if (std::find(m_installed_games.cbegin(), m_installed_games.cend(), titleId) != m_installed_games.cend()) { found = true; break; } } if (!found) continue; } // filter graphic packs by given title id if(has_filter) { bool found = false; if (boost::icontains(p->GetVirtualPath(), m_filter)) found = true; else { for (uint64 titleId : p->GetTitleIds()) { if (boost::icontains(fmt::format("{:x}", titleId), m_filter)) { found = true; break; } } } if (!found) continue; } } const auto& path = p->GetVirtualPath(); auto tokens = TokenizeView(path, '/'); auto node = root; for(size_t i=0; iAppendItem(parent_node, wxString::FromUTF8(token)); } } else { // last token // if a node with same name already exists, add a number suffix for (sint32 s = 0; s < 999; s++) { wxString nodeName = wxString::FromUTF8(token); if (s > 0) nodeName.append(formatWxString(" #{}", s + 1)); node = FindTreeItem(parent_node, nodeName); if (!node.IsOk()) { node = m_graphic_pack_tree->AppendItem(parent_node, nodeName); break; } } } } if(node.IsOk() && node != root) { m_graphic_pack_tree->SetItemData(node, new wxGraphicPackData(p)); bool canEnable = true; if (p->GetVersion() == 3) { auto tmp_text = m_graphic_pack_tree->GetItemText(node); m_graphic_pack_tree->SetItemText(node, tmp_text + " (may not be compatible with Vulkan)"); } else if (p->GetVersion() != 3 && p->GetVersion() != 4 && p->GetVersion() != 5 && p->GetVersion() != 6 && p->GetVersion() != GraphicPack2::GFXPACK_VERSION_7) { auto tmp_text = m_graphic_pack_tree->GetItemText(node); m_graphic_pack_tree->SetItemText(node, tmp_text + " (Unsupported version)"); m_graphic_pack_tree->SetItemTextColour(node, m_incompatible_colour); canEnable = false; } else if (p->IsActivated()) m_graphic_pack_tree->SetItemTextColour(node, m_activated_colour); m_graphic_pack_tree->MakeCheckable(node, p->IsEnabled()); if (!canEnable) m_graphic_pack_tree->DisableCheckBox(node); } } m_graphic_pack_tree->Sort(root, true); if (!m_filter.empty()) { size_t counter = 0; ExpandChildren({ root }, counter); } } void GraphicPacksWindow2::GetChildren(const wxTreeItemId& id, std::vector& children) const { wxTreeItemIdValue cookie; wxTreeItemId child = m_graphic_pack_tree->GetFirstChild(id, cookie); while (child.IsOk()) { children.emplace_back(child); child = m_graphic_pack_tree->GetNextChild(child, cookie); } } void GraphicPacksWindow2::ExpandChildren(const std::vector& ids, size_t& counter) const { std::vector children; for (const auto& id : ids) GetChildren(id, children); counter += children.size(); if (counter >= 30 || children.empty()) return; for (const auto& id : ids) { if(id != m_graphic_pack_tree->GetRootItem() && m_graphic_pack_tree->HasChildren(id)) m_graphic_pack_tree->Expand(id); } ExpandChildren(children, counter); } void GraphicPacksWindow2::RefreshGraphicPacks() { GraphicPack2::ClearGraphicPacks(); GraphicPack2::LoadAll(); } GraphicPacksWindow2::GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_filter) : wxDialog(parent, wxID_ANY, _("Graphic packs"), wxDefaultPosition, wxSize(1000,670), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER), m_installed_games(CafeTitleList::GetAllTitleIds()) { if (title_id_filter != 0) m_filter = fmt::format("{:x}", title_id_filter); m_filter_installed_games = !m_installed_games.empty(); SetIcon(wxICON(X_BOX)); SetMinSize(wxSize(500, 400)); auto main_sizer = new wxBoxSizer(wxVERTICAL); m_splitter_window = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_3D); m_splitter_window->Bind(wxEVT_SIZE, &GraphicPacksWindow2::OnSizeChanged, this); m_splitter_window->Bind(wxEVT_SPLITTER_SASH_POS_CHANGED, &GraphicPacksWindow2::SashPositionChanged, this); // left side auto left_panel = new wxPanel(m_splitter_window); { auto sizer = new wxBoxSizer(wxVERTICAL); wxFlexGridSizer* filter_row = new wxFlexGridSizer(0, 3, 0, 0); filter_row->AddGrowableCol(1); filter_row->SetFlexibleDirection(wxBOTH); filter_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); const auto text = new wxStaticText(left_panel, wxID_ANY, _("Filter")); text->Wrap(-1); filter_row->Add(text, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_filter_text = new wxTextCtrl(left_panel, wxID_ANY, wxString::FromUTF8(m_filter)); filter_row->Add(m_filter_text, 0, wxALL | wxEXPAND, 5); m_filter_text->Bind(wxEVT_COMMAND_TEXT_UPDATED, &GraphicPacksWindow2::OnFilterUpdate, this); m_installed_games_only = new wxCheckBox(left_panel, wxID_ANY, _("Installed games")); m_installed_games_only->SetValue(m_filter_installed_games); filter_row->Add(m_installed_games_only, 0, wxALL | wxEXPAND, 5); m_installed_games_only->Bind(wxEVT_CHECKBOX, &GraphicPacksWindow2::OnInstalledGamesChanged, this); if (m_installed_games.empty()) m_installed_games_only->Disable(); sizer->Add(filter_row, 0, wxEXPAND, 5); m_graphic_pack_tree = new wxCheckTree(left_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT); m_graphic_pack_tree->Bind(wxEVT_TREE_SEL_CHANGED, &GraphicPacksWindow2::OnTreeSelectionChanged, this); m_graphic_pack_tree->Bind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeChoiceChanged, this); //m_graphic_pack_tree->SetMinSize(wxSize(600, 400)); sizer->Add(m_graphic_pack_tree, 1, wxEXPAND | wxALL, 5); left_panel->SetSizerAndFit(sizer); } // right side m_right_panel = new wxPanel(m_splitter_window, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxFULL_REPAINT_ON_RESIZE); { auto* sizer = new wxBoxSizer(wxVERTICAL); { m_gp_options = new wxScrolled(m_right_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxVSCROLL); m_gp_options->SetScrollRate(-1, 10); auto* inner_sizer = new wxBoxSizer(wxVERTICAL); { auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Graphic pack")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); m_graphic_pack_name = new wxStaticText(box, wxID_ANY, wxEmptyString); box_sizer->Add(m_graphic_pack_name, 1, wxEXPAND | wxALL, 5); inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } { auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Description")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); m_graphic_pack_description = new wxStaticText(box, wxID_ANY, wxEmptyString); box_sizer->Add(m_graphic_pack_description, 1, wxEXPAND | wxALL, 5); inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } m_preset_sizer = new wxBoxSizer(wxVERTICAL); inner_sizer->Add(m_preset_sizer, 0, wxEXPAND, 0); { auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Control")); auto* box_sizer = new wxStaticBoxSizer(box, wxHORIZONTAL); m_reload_shaders = new wxButton(box, wxID_ANY, _("Reload edited shaders")); m_reload_shaders->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnReloadShaders, this); m_reload_shaders->Disable(); box_sizer->Add(m_reload_shaders, 0, wxEXPAND | wxALL, 5); inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } inner_sizer->Add(new wxStaticText(m_gp_options, wxID_ANY, wxEmptyString), 1, wxALL | wxEXPAND, 5); m_gp_options->SetSizerAndFit(inner_sizer); m_gp_options->Hide(); sizer->Add(m_gp_options, 1, wxEXPAND | wxRESERVE_SPACE_EVEN_IF_HIDDEN); } sizer->Add(new wxStaticLine(m_right_panel, wxID_ANY), 0, wxLEFT|wxRIGHT | wxEXPAND, 3); auto* row = new wxBoxSizer(wxHORIZONTAL); m_update_graphicPacks = new wxButton(m_right_panel, wxID_ANY, _("Download latest community graphic packs")); m_update_graphicPacks->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnCheckForUpdates, this); row->Add(m_update_graphicPacks, 0, wxALL, 5); sizer->Add(row, 0, wxALL | wxEXPAND, 5); m_right_panel->SetSizerAndFit(sizer); } m_splitter_window->SetMinimumPaneSize(50); m_splitter_window->SplitVertically(left_panel, m_right_panel, (sint32)(m_ratio * m_splitter_window->GetParent()->GetSize().GetWidth())); main_sizer->Add(m_splitter_window, 1, wxEXPAND, 5); m_info_bar = new wxInfoBar(this); m_info_bar->SetShowHideEffects(wxSHOW_EFFECT_BLEND, wxSHOW_EFFECT_BLEND); m_info_bar->SetEffectDuration(500); main_sizer->Add(m_info_bar, 0, wxALL | wxEXPAND, 5); SetSizer(main_sizer); UpdateTitleRunning(CafeSystem::IsTitleRunning()); FillGraphicPackList(); } void GraphicPacksWindow2::SaveStateToConfig() { auto& data = GetConfigHandle().data(); data.graphic_pack_entries.clear(); for (const auto& gp : GraphicPack2::GetGraphicPacks()) { auto filename = _utf8ToPath(gp->GetNormalizedPathString()); if (gp->IsEnabled()) { data.graphic_pack_entries.try_emplace(filename); auto& it = data.graphic_pack_entries[filename]; // otherwise store all selected presets for (const auto& preset : gp->GetActivePresets()) it.try_emplace(preset->category, preset->name); } else if(gp->IsDefaultEnabled()) { // save that its disabled data.graphic_pack_entries.try_emplace(filename); auto& it = data.graphic_pack_entries[filename]; it.try_emplace("_disabled", "false"); } } GetConfigHandle().Save(); } GraphicPacksWindow2::~GraphicPacksWindow2() { m_graphic_pack_tree->Unbind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeSelectionChanged, this); m_graphic_pack_tree->Unbind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeChoiceChanged, this); // m_active_preset->Unbind(wxEVT_CHOICE, &GraphicPacksWindow2::OnActivePresetChanged, this); m_reload_shaders->Unbind(wxEVT_BUTTON, &GraphicPacksWindow2::OnReloadShaders, this); SaveStateToConfig(); } wxTreeItemId GraphicPacksWindow2::FindTreeItem(const wxTreeItemId& root, const wxString& text) const { wxTreeItemIdValue cookie; for(auto item = m_graphic_pack_tree->GetFirstChild(root, cookie); item.IsOk(); item = m_graphic_pack_tree->GetNextSibling(item)) { if (m_graphic_pack_tree->GetItemText(item) == text) return item; } return {}; } void GraphicPacksWindow2::LoadPresetSelections(const GraphicPackPtr& gp) { std::vector order; auto presets = gp->GetCategorizedPresets(order); for(const auto& category : order) { const auto& entry = presets[category]; // test if any preset is visible and update its status if (std::none_of(entry.cbegin(), entry.cend(), [gp](const auto& p) { return p->visible; })) { continue; } wxString categoryWxStr = wxString::FromUTF8(category); wxString label(category.empty() ? _("Active preset") : categoryWxStr); auto* box = new wxStaticBox(m_preset_sizer->GetContainingWindow(), wxID_ANY, label); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); auto* preset = new wxChoice(box, wxID_ANY); preset->SetClientObject(new wxStringClientData(categoryWxStr)); preset->Bind(wxEVT_CHOICE, &GraphicPacksWindow2::OnActivePresetChanged, this); std::optional active_preset; for (auto& pentry : entry) { if (!pentry->visible) continue; preset->AppendString(wxString::FromUTF8(pentry->name)); if (pentry->active) active_preset = pentry->name; } if (active_preset) preset->SetStringSelection(wxString::FromUTF8(active_preset.value())); else if (preset->GetCount() > 0) preset->SetSelection(0); box_sizer->Add(preset, 1, wxEXPAND | wxALL, 5); m_preset_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } } void GraphicPacksWindow2::OnTreeSelectionChanged(wxTreeEvent& event) { wxWindowUpdateLocker lock(this); bool item_deselected = m_graphic_pack_tree->GetSelection() == m_graphic_pack_tree->GetRootItem(); if (item_deselected) return; const auto selection = m_graphic_pack_tree->GetSelection(); if (selection.IsOk()) { const auto data = dynamic_cast(m_graphic_pack_tree->GetItemData(selection)); if (data) { if(!m_gp_options->IsShown()) m_gp_options->Show(); const auto& gp = data->GetGraphicPack(); if (gp != m_shown_graphic_pack) { m_preset_sizer->Clear(true); m_gp_name = gp->GetName(); m_graphic_pack_name->SetLabel(wxString::FromUTF8(m_gp_name)); if (gp->GetDescription().empty()) m_gp_description = _("This graphic pack has no description").utf8_string(); else m_gp_description = gp->GetDescription(); m_graphic_pack_description->SetLabel(wxString::FromUTF8(m_gp_description)); LoadPresetSelections(gp); m_reload_shaders->Enable(gp->HasShaders()); m_shown_graphic_pack = gp; m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 20); m_graphic_pack_name->GetGrandParent()->Layout(); m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 20); m_graphic_pack_description->GetGrandParent()->Layout(); m_right_panel->FitInside(); m_right_panel->Layout(); } return; } } m_preset_sizer->Clear(true); m_graphic_pack_name->SetLabel(wxEmptyString); m_graphic_pack_description->SetLabel(wxEmptyString); m_reload_shaders->Disable(); m_shown_graphic_pack.reset(); m_gp_options->Hide(); m_right_panel->FitInside(); m_right_panel->Layout(); } void GraphicPacksWindow2::OnTreeChoiceChanged(wxTreeEvent& event) { auto item = event.GetItem(); if (!item.IsOk()) return; const bool state = event.GetExtraLong() != 0; const auto data = dynamic_cast(m_graphic_pack_tree->GetItemData(item)); if (!data) return; auto& graphic_pack = data->GetGraphicPack(); graphic_pack->SetEnabled(state); bool requiresRestart = graphic_pack->RequiresRestart(true, false); bool isRunning = CafeSystem::IsTitleRunning() && graphic_pack->ContainsTitleId(CafeSystem::GetForegroundTitleId()); if (isRunning) { if (state) { GraphicPack2::ActivateGraphicPack(graphic_pack); if (!requiresRestart) { ReloadPack(graphic_pack); m_graphic_pack_tree->SetItemTextColour(item, m_activated_colour); } } else { if (!requiresRestart) { DeleteShadersFromRuntimeCache(graphic_pack); m_graphic_pack_tree->SetItemTextColour(item, m_default_colour); } GraphicPack2::DeactivateGraphicPack(graphic_pack); } } if (!m_info_bar->IsShown() && (isRunning && requiresRestart)) m_info_bar->ShowMessage(_("Restart of Cemu required for changes to take effect")); // also change selection to activated gp m_graphic_pack_tree->SelectItem(item); } // In some environments with GTK (e.g. a flatpak app with org.freedesktop.Platform 22.08 runtime), // destroying the event source inside the handler crashes the app. // As a workaround to that, the wxWindow that needs to be destroyed is hidden and then // destroyed at a later time, outside the handler. void GraphicPacksWindow2::ClearPresets() { size_t item_count = m_preset_sizer->GetItemCount(); std::vector sizers; sizers.reserve(item_count); for (size_t i = 0; i < item_count; i++) sizers.push_back(m_preset_sizer->GetItem(i)->GetSizer()); for (auto&& sizer : sizers) { auto static_box_sizer = dynamic_cast(sizer); if (static_box_sizer) { wxStaticBox* parent_window = static_box_sizer->GetStaticBox(); if (parent_window) { m_preset_sizer->Detach(sizer); parent_window->Hide(); CallAfter([=]() { parent_window->DestroyChildren(); delete static_box_sizer; }); } } } } void GraphicPacksWindow2::OnActivePresetChanged(wxCommandEvent& event) { if (!m_shown_graphic_pack) return; const auto obj = wxDynamicCast(event.GetEventObject(), wxChoice); wxASSERT(obj); const auto string_data = dynamic_cast(obj->GetClientObject()); wxASSERT(string_data); const auto preset = obj->GetStringSelection().utf8_string(); if(m_shown_graphic_pack->SetActivePreset(string_data->GetData().utf8_string(), preset)) { wxWindowUpdateLocker lock(this); ClearPresets(); LoadPresetSelections(m_shown_graphic_pack); //m_preset_sizer->GetContainingWindow()->Layout(); //m_right_panel->FitInside(); m_right_panel->FitInside(); m_right_panel->Layout(); } if (!m_shown_graphic_pack->RequiresRestart(false, true)) ReloadPack(m_shown_graphic_pack); else if (!m_info_bar->IsShown()) m_info_bar->ShowMessage(_("Restart of Cemu required for changes to take effect")); } void GraphicPacksWindow2::OnReloadShaders(wxCommandEvent& event) { if (m_shown_graphic_pack) ReloadPack(m_shown_graphic_pack); } void GraphicPacksWindow2::OnCheckForUpdates(wxCommandEvent& event) { DownloadGraphicPacksWindow frame(this); SaveStateToConfig(); const int updateResult = frame.ShowModal(); if (updateResult == wxID_OK) { if (!CafeSystem::IsTitleRunning()) { // remember virtual paths of all the enabled packs std::map previouslyEnabledPacks; for(auto& it : GraphicPack2::GetGraphicPacks()) { if(it->IsEnabled()) previouslyEnabledPacks.emplace(it->GetNormalizedPathString(), it->GetVirtualPath()); } // reload graphic packs RefreshGraphicPacks(); FillGraphicPackList(); // remove packs which are still present for(auto& it : GraphicPack2::GetGraphicPacks()) previouslyEnabledPacks.erase(it->GetNormalizedPathString()); if(!previouslyEnabledPacks.empty()) { std::string lost_packs; for(auto& it : previouslyEnabledPacks) { lost_packs.append(it.second); lost_packs.push_back('\n'); } wxString message = _("This update removed or renamed the following graphic packs:"); message << "\n \n" << wxString::FromUTF8(lost_packs) << " \n" << _("You may need to set them up again."); wxMessageBox(message, _("Warning"), wxOK | wxCENTRE | wxICON_INFORMATION, this); } } } } void GraphicPacksWindow2::OnSizeChanged(wxSizeEvent& event) { const auto obj = (wxSplitterWindow*)event.GetEventObject(); wxASSERT(obj); const auto width = std::max(obj->GetMinimumPaneSize(), obj->GetParent()->GetClientSize().GetWidth()); obj->SetSashPosition((sint32)(m_ratio * width)); if (!m_gp_name.empty()) m_graphic_pack_name->SetLabel(wxString::FromUTF8(m_gp_name)); if (!m_gp_description.empty()) m_graphic_pack_description->SetLabel(wxString::FromUTF8(m_gp_description)); m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 10); m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 10); event.Skip(); } void GraphicPacksWindow2::SashPositionChanged(wxEvent& event) { const auto obj = (wxSplitterWindow*)event.GetEventObject(); wxASSERT(obj); const auto width = std::max(obj->GetMinimumPaneSize(), obj->GetParent()->GetClientSize().GetWidth()); m_ratio = (float)obj->GetSashPosition() / width; event.Skip(); } void GraphicPacksWindow2::OnFilterUpdate(wxEvent& event) { m_filter = m_filter_text->GetValue().utf8_string(); FillGraphicPackList(); event.Skip(); } void GraphicPacksWindow2::OnInstalledGamesChanged(wxCommandEvent& event) { m_filter_installed_games = m_installed_games_only->GetValue(); FillGraphicPackList(); event.Skip(); } void GraphicPacksWindow2::UpdateTitleRunning(bool running) { m_update_graphicPacks->Enable(!running); if(running) m_update_graphicPacks->SetToolTip(_("Graphic packs cannot be updated while a game is running.")); else m_update_graphicPacks->SetToolTip(nullptr); } void GraphicPacksWindow2::ReloadPack(const GraphicPackPtr& graphic_pack) const { if (graphic_pack->HasShaders() || graphic_pack->HasPatches() || graphic_pack->HasCustomVSyncFrequency()) { if (graphic_pack->Reload()) { DeleteShadersFromRuntimeCache(graphic_pack); } } } void GraphicPacksWindow2::DeleteShadersFromRuntimeCache(const GraphicPackPtr& graphic_pack) const { for (const auto& shader : graphic_pack->GetCustomShaders()) { LatteConst::ShaderType shaderType; if (shader.type == GraphicPack2::GP_SHADER_TYPE::VERTEX) shaderType = LatteConst::ShaderType::Vertex; else if (shader.type == GraphicPack2::GP_SHADER_TYPE::GEOMETRY) shaderType = LatteConst::ShaderType::Geometry; else if (shader.type == GraphicPack2::GP_SHADER_TYPE::PIXEL) shaderType = LatteConst::ShaderType::Pixel; LatteAsyncCommands_queueDeleteShader(shader.shader_base_hash, shader.shader_aux_hash, shaderType); } } ================================================ FILE: src/gui/wxgui/GraphicPacksWindow2.h ================================================ #pragma once #include #include #include #include #include "wxcomponents/checktree.h" #include "Cafe/GraphicPack/GraphicPack2.h" class wxSplitterWindow; class wxPanel; class wxButton; class wxChoice; class GraphicPacksWindow2 : public wxDialog { public: GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_filter); ~GraphicPacksWindow2(); static void RefreshGraphicPacks(); void UpdateTitleRunning(bool running); private: std::string m_filter; bool m_filter_installed_games; std::vector m_installed_games; void ClearPresets(); void FillGraphicPackList() const; void GetChildren(const wxTreeItemId& id, std::vector& children) const; void ExpandChildren(const std::vector& ids, size_t& counter) const; wxSplitterWindow * m_splitter_window; wxPanel* m_right_panel; wxScrolled* m_gp_options; wxCheckTree * m_graphic_pack_tree; wxTextCtrl* m_filter_text; wxCheckBox* m_installed_games_only; wxStaticText* m_graphic_pack_name, *m_graphic_pack_description; wxBoxSizer* m_preset_sizer; std::vector m_active_preset; wxButton* m_reload_shaders; wxButton* m_update_graphicPacks; wxInfoBar* m_info_bar; GraphicPackPtr m_shown_graphic_pack; std::string m_gp_name, m_gp_description; float m_ratio = 0.55f; wxColour m_default_colour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); wxColour m_activated_colour = wxSystemSettings::SelectLightDark(wxColour(0x00, 0xCC, 0x00), wxColour(0x42, 0xB3, 0x42)); wxColour m_incompatible_colour = wxSystemSettings::SelectLightDark(wxColour(0xCC, 0x00, 0x00), wxColour(0xDE, 0x49, 0x49)); wxTreeItemId FindTreeItem(const wxTreeItemId& root, const wxString& text) const; void LoadPresetSelections(const GraphicPackPtr& gp); void OnTreeSelectionChanged(wxTreeEvent& event); void OnTreeChoiceChanged(wxTreeEvent& event); void OnActivePresetChanged(wxCommandEvent& event); void OnReloadShaders(wxCommandEvent& event); void OnCheckForUpdates(wxCommandEvent& event); void OnSizeChanged(wxSizeEvent& event); void SashPositionChanged(wxEvent& event); void OnFilterUpdate(wxEvent& event); void OnInstalledGamesChanged(wxCommandEvent& event); void SaveStateToConfig(); void ReloadPack(const GraphicPackPtr& graphic_pack) const; void DeleteShadersFromRuntimeCache(const GraphicPackPtr& graphic_pack) const; }; ================================================ FILE: src/gui/wxgui/LoggingWindow.cpp ================================================ #include "wxgui/LoggingWindow.h" #include "Cemu/Logging/CemuLogging.h" #include "wxgui/helpers/wxLogEvent.h" #include #include #include wxDEFINE_EVENT(EVT_LOG, wxLogEvent); LoggingWindow::LoggingWindow(wxFrame* parent) : wxFrame(parent, wxID_ANY, _("Logging window"), wxDefaultPosition, wxSize(800, 600), wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL) { auto* sizer = new wxBoxSizer( wxVERTICAL ); { auto filter_row = new wxBoxSizer( wxHORIZONTAL ); filter_row->Add(new wxStaticText( this, wxID_ANY, _("Filter")), 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); wxString choices[] = {"Unsupported APIs calls", "Coreinit Logging", "Coreinit File-Access", "Coreinit Thread-Synchronization", "Coreinit Memory", "Coreinit MP", "Coreinit Thread", "nn::nfp", "GX2", "Audio", "Input", "Socket", "Save", "H264", "Graphic pack patches", "Texture cache", "Texture readback", "OpenGL debug output", "Vulkan validation layer", "Metal debug output"}; m_filter = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, std::size(choices), choices); m_filter->Bind(wxEVT_COMBOBOX, &LoggingWindow::OnFilterChange, this); m_filter->Bind(wxEVT_TEXT, &LoggingWindow::OnFilterChange, this); filter_row->Add(m_filter, 1, wxALL, 5 ); m_filter_message = new wxCheckBox(this, wxID_ANY, _("Filter messages")); m_filter_message->Bind(wxEVT_CHECKBOX, &LoggingWindow::OnFilterMessageChange, this); filter_row->Add(m_filter_message, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); sizer->Add( filter_row, 0, wxEXPAND, 5 ); } m_log_list = new wxLogCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle, true); sizer->Add( m_log_list, 1, wxALL | wxEXPAND, 5 ); this->SetSizer( sizer ); this->Layout(); this->Bind(EVT_LOG, &LoggingWindow::OnLogMessage, this); cemuLog_setCallbacks(this); } LoggingWindow::~LoggingWindow() { this->Unbind(EVT_LOG, &LoggingWindow::OnLogMessage, this); cemuLog_clearCallbacks(); } void LoggingWindow::Log(std::string_view filter, std::string_view message) { wxLogEvent event(std::string {filter}, std::string{ message }); OnLogMessage(event); //const auto log_event = new wxLogEvent(filter, message); //wxQueueEvent(s_instance, log_event); } void LoggingWindow::Log(std::string_view filter, std::wstring_view message) { wxLogEvent event(std::string {filter}, std::wstring{ message }); OnLogMessage(event); //const auto log_event = new wxLogEvent(filter, message); //wxQueueEvent(s_instance, log_event); } void LoggingWindow::OnLogMessage(wxLogEvent& event) { m_log_list->PushEntry(event.GetFilter(), event.GetMessage()); } void LoggingWindow::OnFilterChange(wxCommandEvent& event) { m_log_list->SetActiveFilter(m_filter->GetValue().utf8_string()); event.Skip(); } void LoggingWindow::OnFilterMessageChange(wxCommandEvent& event) { m_log_list->SetFilterMessage(m_filter_message->GetValue()); event.Skip(); } ================================================ FILE: src/gui/wxgui/LoggingWindow.h ================================================ #pragma once #include "Cemu/Logging/CemuLogging.h" #include #include #include #include "wxgui/components/wxLogCtrl.h" class wxLogEvent; class LoggingWindow : public wxFrame, public LoggingCallbacks { public: LoggingWindow(wxFrame* parent); ~LoggingWindow(); void Log(std::string_view filter, std::string_view message) override; void Log(std::string_view filter, std::wstring_view message) override; private: void OnLogMessage(wxLogEvent& event); void OnFilterChange(wxCommandEvent& event); void OnFilterMessageChange(wxCommandEvent& event); wxComboBox* m_filter; wxLogCtrl* m_log_list; wxCheckBox* m_filter_message; }; ================================================ FILE: src/gui/wxgui/MainWindow.cpp ================================================ #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "interface/WindowSystem.h" #include "wxCemuConfig.h" #include "wxgui/wxgui.h" #include "wxgui/MainWindow.h" #include #include "wxgui/GameUpdateWindow.h" #include "wxgui/PadViewFrame.h" #include "wxgui/windows/TextureRelationViewer/TextureRelationWindow.h" #include "wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h" #include "AudioDebuggerWindow.h" #include "wxgui/canvas/OpenGLCanvas.h" #include "wxgui/canvas/VulkanCanvas.h" #if ENABLE_METAL #include "wxgui/canvas/MetalCanvas.h" #endif #include "Cafe/OS/libs/nfc/nfc.h" #include "Cafe/OS/libs/swkbd/swkbd.h" #include "wxgui/debugger/DebuggerWindow2.h" #include "util/helpers/helpers.h" #include "config/CemuConfig.h" #include "Cemu/DiscordPresence/DiscordPresence.h" #include "util/ScreenSaver/ScreenSaver.h" #include "wxgui/GeneralSettings2.h" #include "wxgui/GraphicPacksWindow2.h" #include "wxgui/CemuApp.h" #include "wxgui/CemuUpdateWindow.h" #include "wxgui/LoggingWindow.h" #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" #include "Cafe/Filesystem/FST/FST.h" #include "wxgui/TitleManager.h" #include "wxgui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" #include "Cafe/CafeSystem.h" #include "util/helpers/SystemException.h" #include "wxgui/DownloadGraphicPacksWindow.h" #include "wxgui/GettingStartedDialog.h" #include "wxgui/helpers/wxHelpers.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h" #include "wxgui/input/InputSettings2.h" #include "wxgui/input/HotkeySettings.h" #include "input/InputManager.h" #if BOOST_OS_WINDOWS #define exit(__c) ExitProcess(__c) #else #define exit(__c) _Exit(__c) #endif #if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD #include "resource/embedded/resources.h" #endif #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND #include "wxgui/helpers/wxWayland.h" #endif //GameMode support #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) #include "gamemode_client.h" #endif #if ENABLE_METAL #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #endif #include "Cafe/TitleList/TitleInfo.h" #include "Cafe/TitleList/TitleList.h" #include "wxHelper.h" extern WindowSystem::WindowInfo g_window_info; extern std::shared_mutex g_mutex; enum { // ui elements MAINFRAME_GAMELIST_ID = 20000, //wxID_HIGHEST + 1, // file MAINFRAME_MENU_ID_FILE_LOAD = 20100, MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, MAINFRAME_MENU_ID_FILE_EXIT, MAINFRAME_MENU_ID_FILE_END_EMULATION, MAINFRAME_MENU_ID_FILE_RECENT_0, MAINFRAME_MENU_ID_FILE_RECENT_LAST = MAINFRAME_MENU_ID_FILE_RECENT_0 + 15, // options MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN = 20200, MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, MAINFRAME_MENU_ID_OPTIONS_GRAPHIC, MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2, MAINFRAME_MENU_ID_OPTIONS_GENERAL, MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MAINFRAME_MENU_ID_OPTIONS_AUDIO, MAINFRAME_MENU_ID_OPTIONS_INPUT, MAINFRAME_MENU_ID_OPTIONS_HOTKEY, MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, // options -> account MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 = 20350, MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12 = 20350 + 11, // options -> system language MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE = 20500, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_FRENCH, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_GERMAN, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ITALIAN, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_SPANISH, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_CHINESE, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_KOREAN, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_DUTCH, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, // tools MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER = 20600, MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, // cpu // cpu->timer speed MAINFRAME_MENU_ID_TIMER_SPEED_1X = 20700, MAINFRAME_MENU_ID_TIMER_SPEED_2X = 20701, MAINFRAME_MENU_ID_TIMER_SPEED_4X = 20702, MAINFRAME_MENU_ID_TIMER_SPEED_8X = 20703, MAINFRAME_MENU_ID_TIMER_SPEED_05X = 20704, MAINFRAME_MENU_ID_TIMER_SPEED_025X = 20705, MAINFRAME_MENU_ID_TIMER_SPEED_0125X = 20706, // nfc->Touch NFC file MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE = 21000, MAINFRAME_MENU_ID_NFC_RECENT_0, MAINFRAME_MENU_ID_NFC_RECENT_LAST = MAINFRAME_MENU_ID_NFC_RECENT_0 + 15, // debug MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN = 21100, MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW, MAINFRAME_MENU_ID_DEBUG_TOGGLE_GDB_STUB, MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY, MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, MAINFRAME_MENU_ID_DEBUG_GPU_CAPTURE, // debug->logging MAINFRAME_MENU_ID_DEBUG_LOGGING_MESSAGE = 21499, MAINFRAME_MENU_ID_DEBUG_LOGGING0 = 21500, MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO = 21599, // debug->dump MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES = 21600, MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, MAINFRAME_MENU_ID_DEBUG_DUMP_FST, MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, // help MAINFRAME_MENU_ID_HELP_ABOUT = 21700, MAINFRAME_MENU_ID_HELP_UPDATE, // custom MAINFRAME_ID_TIMER1 = 21800, }; wxDEFINE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent); wxDEFINE_EVENT(wxEVT_REQUEST_GAMELIST_REFRESH, wxCommandEvent); wxDEFINE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); wxDEFINE_EVENT(wxEVT_REQUEST_RECREATE_CANVAS, wxCommandEvent); wxBEGIN_EVENT_TABLE(MainWindow, wxFrame) EVT_TIMER(MAINFRAME_ID_TIMER1, MainWindow::OnTimer) EVT_CLOSE(MainWindow::OnClose) EVT_SIZE(MainWindow::OnSizeEvent) EVT_DPI_CHANGED(MainWindow::OnDPIChangedEvent) EVT_MOVE(MainWindow::OnMove) // file menu EVT_MENU(MAINFRAME_MENU_ID_FILE_LOAD, MainWindow::OnFileMenu) EVT_MENU(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MainWindow::OnInstallUpdate) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, MainWindow::OnClearSpotPassCache) EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit) EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu) EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu) // options -> region menu EVT_MENU_RANGE(MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1, MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12, MainWindow::OnAccountSelect) EVT_MENU_RANGE(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, MainWindow::OnConsoleLanguage) // options menu EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_AUDIO, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_HOTKEY, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, MainWindow::OnOptionsInput) // tools menu EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, MainWindow::OnToolsInput) // cpu menu EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_4X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_2X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_1X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_05X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_025X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_0125X, MainWindow::OnDebugSetting) // nfc menu EVT_MENU(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, MainWindow::OnNFCMenu) EVT_MENU_RANGE(MAINFRAME_MENU_ID_NFC_RECENT_0 + 0, MAINFRAME_MENU_ID_NFC_RECENT_LAST, MainWindow::OnNFCMenu) // debug -> logging menu EVT_MENU_RANGE(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 0, MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 98, MainWindow::OnDebugLoggingToggleFlagGeneric) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO, MainWindow::OnPPCInfoToggle) // debug -> dump menu EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, MainWindow::OnDebugDumpGeneric) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, MainWindow::OnDebugDumpGeneric) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, MainWindow::OnDebugDumpGeneric) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, MainWindow::OnDebugSetting) // debug -> Other options EVT_MENU(MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_GPU_CAPTURE, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_FST, MainWindow::OnDebugSetting) // debug -> View ... EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW, MainWindow::OnLoggingWindow) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_TOGGLE_GDB_STUB, MainWindow::OnGDBStubToggle) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, MainWindow::OnDebugViewPPCThreads) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, MainWindow::OnDebugViewPPCDebugger) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, MainWindow::OnDebugViewAudioDebugger) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, MainWindow::OnDebugViewTextureRelations) // help menu EVT_MENU(MAINFRAME_MENU_ID_HELP_ABOUT, MainWindow::OnHelpAbout) EVT_MENU(MAINFRAME_MENU_ID_HELP_UPDATE, MainWindow::OnHelpUpdate) // misc EVT_COMMAND(wxID_ANY, wxEVT_REQUEST_GAMELIST_REFRESH, MainWindow::OnRequestGameListRefresh) EVT_COMMAND(wxID_ANY, wxEVT_GAMELIST_BEGIN_UPDATE, MainWindow::OnGameListBeginUpdate) EVT_COMMAND(wxID_ANY, wxEVT_GAMELIST_END_UPDATE, MainWindow::OnGameListEndUpdate) EVT_COMMAND(wxID_ANY, wxEVT_ACCOUNTLIST_REFRESH, MainWindow::OnAccountListRefresh) EVT_COMMAND(wxID_ANY, wxEVT_SET_WINDOW_TITLE, MainWindow::OnSetWindowTitle) EVT_COMMAND(wxID_ANY, wxEVT_REQUEST_RECREATE_CANVAS, MainWindow::OnRequestRecreateCanvas) wxEND_EVENT_TABLE() class wxGameDropTarget : public wxFileDropTarget { public: wxGameDropTarget(MainWindow* window) : m_window(window) {} bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override { if(!m_window->IsGameLaunched() && filenames.GetCount() == 1) return m_window->FileLoad(_utf8ToPath(filenames[0].utf8_string()), wxLaunchGameEvent::INITIATED_BY::DRAG_AND_DROP); return false; } private: MainWindow* m_window; }; class wxAmiiboDropTarget : public wxFileDropTarget { public: wxAmiiboDropTarget(MainWindow* window) : m_window(window) {} bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override { if (!m_window->IsGameLaunched() || filenames.GetCount() != 1) return false; uint32 nfcError; std::string path = filenames[0].utf8_string(); if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError)) { GetWxGUIConfig().AddRecentNfcFile(path); m_window->UpdateNFCMenu(); return true; } else { if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) wxMessageBox(_("Not a valid NFC file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } } private: MainWindow* m_window; }; MainWindow::MainWindow() : wxFrame(nullptr, wxID_ANY, GetInitialWindowTitle(), wxDefaultPosition, wxSize(1280, 720), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN | wxRESIZE_BORDER) { #ifdef __WXMAC__ // Not necessary to set wxApp::s_macExitMenuItemId as automatically handled wxApp::s_macAboutMenuItemId = MAINFRAME_MENU_ID_HELP_ABOUT; wxApp::s_macPreferencesMenuItemId = MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS; #endif g_window_info.window_main = initHandleContextFromWxWidgetsWindow(this); g_mainFrame = this; CafeSystem::SetImplementation(this); RecreateMenu(); SetClientSize(1280, 720); SetIcon(wxICON(M_WND_ICON128)); #if BOOST_OS_MACOS this->EnableFullScreenView(true); #endif #if BOOST_OS_WINDOWS HICON hWindowIcon = (HICON)LoadImageA(NULL, "M_WND_ICON16", IMAGE_ICON, 16, 16, LR_LOADFROMFILE); SendMessage(this->GetHWND(), WM_SETICON, ICON_SMALL, (LPARAM)hWindowIcon); #endif auto* main_sizer = new wxBoxSizer(wxVERTICAL); auto load_file = LaunchSettings::GetLoadFile(); auto load_title_id = LaunchSettings::GetLoadTitleID(); bool quick_launch = false; if (load_file) { MainWindow::RequestLaunchGame(load_file.value(), wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE); quick_launch = true; } else if (load_title_id) { TitleInfo info; TitleId baseId; if (CafeTitleList::FindBaseTitleId(load_title_id.value(), baseId) && CafeTitleList::GetFirstByTitleId(baseId, info)) { MainWindow::RequestLaunchGame(info.GetPath(), wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE); quick_launch = true; } else { wxString errorMsg = fmt::format("Title ID {:016x} not found", load_title_id.value()); wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); } } SetSizer(main_sizer); if (!quick_launch) { CreateGameListAndStatusBar(); } else { // launching game via -g or -t option. Don't set up or load game list m_game_list = nullptr; m_info_bar = nullptr; } SetSizer(main_sizer); m_last_mouse_move_time = std::chrono::steady_clock::now(); m_timer = new wxTimer(this, MAINFRAME_ID_TIMER1); m_timer->Start(500); LoadSettings(); #ifdef ENABLE_DISCORD_RPC if (GetWxGUIConfig().use_discord_presence) m_discord = std::make_unique(); #endif Bind(wxEVT_OPEN_GRAPHIC_PACK, &MainWindow::OnGraphicWindowOpen, this); Bind(wxEVT_LAUNCH_GAME, &MainWindow::OnLaunchFromFile, this); if (LaunchSettings::GDBStubEnabled()) { g_gdbstub = std::make_unique(GetConfig().gdb_port); } } MainWindow::~MainWindow() { if (m_padView) { m_padView->Destroy(); m_padView = nullptr; } m_timer->Stop(); std::unique_lock lock(g_mutex); g_mainFrame = nullptr; } void MainWindow::CreateGameListAndStatusBar() { if(m_main_panel) return; // already displayed m_main_panel = new wxPanel(this); auto* sizer = new wxBoxSizer(wxVERTICAL); // game list m_game_list = new wxGameList(m_main_panel, MAINFRAME_GAMELIST_ID); m_game_list->Bind(wxEVT_OPEN_SETTINGS, [this](auto&) {OpenSettings(); }); m_game_list->SetDropTarget(new wxGameDropTarget(this)); sizer->Add(m_game_list, 1, wxEXPAND); // info, warning bar m_info_bar = new wxInfoBar(m_main_panel); m_info_bar->SetShowHideEffects(wxSHOW_EFFECT_BLEND, wxSHOW_EFFECT_BLEND); m_info_bar->SetEffectDuration(500); sizer->Add(m_info_bar, 0, wxALL | wxEXPAND, 5); m_main_panel->SetSizer(sizer); auto* main_sizer = this->GetSizer(); main_sizer->Add(m_main_panel, 1, wxEXPAND, 0, nullptr); } void MainWindow::DestroyGameListAndStatusBar() { if(!m_main_panel) return; m_main_panel->Destroy(); m_main_panel = nullptr; m_game_list = nullptr; m_info_bar = nullptr; } wxString MainWindow::GetInitialWindowTitle() { return BUILD_VERSION_WITH_NAME_STRING; } void MainWindow::OnClose(wxCloseEvent& event) { if(m_game_list) m_game_list->OnClose(event); if (!IsMaximized() && !WindowSystem::IsFullScreen()) m_restored_size = GetSize(); SaveSettings(); m_timer->Stop(); event.Skip(); CafeSystem::Shutdown(); DestroyCanvas(); } bool MainWindow::InstallUpdate(const fs::path& metaFilePath) { try { GameUpdateWindow frame(*this, metaFilePath); const int updateResult = frame.ShowModal(); if (updateResult == wxID_OK) { CafeTitleList::AddTitleFromPath(frame.GetTargetPath()); // this will also send a notification to the game list which will update the entry wxMessageBox(_("Title installed!"), _("Success")); return true; } else { if (frame.GetExceptionMessage().empty()) wxMessageBox(_("Title installation has been canceled!")); else { throw std::runtime_error(frame.GetExceptionMessage()); } } } catch(const AbortException&) { // ignored } catch (const std::exception& ex) { wxMessageBox(ex.what(), _("Update error")); } return false; } bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATED_BY initiatedBy) { TitleInfo launchTitle{ launchPath }; if (launchTitle.IsValid()) { // the title might not be in the TitleList, so we add it as a temporary entry CafeTitleList::AddTitleFromPath(launchPath); // title is valid, launch from TitleId TitleId baseTitleId; if (!CafeTitleList::FindBaseTitleId(launchTitle.GetAppTitleId(), baseTitleId)) { wxString t = _("Unable to launch game because the base files were not found."); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } CafeSystem::PREPARE_STATUS_CODE r = CafeSystem::PrepareForegroundTitle(baseTitleId); if (r == CafeSystem::PREPARE_STATUS_CODE::INVALID_RPX) { cemu_assert_debug(false); return false; } else if (r == CafeSystem::PREPARE_STATUS_CODE::UNABLE_TO_MOUNT) { wxString t = _("Unable to mount title.\nMake sure the configured game paths are still valid and refresh the game list.\n\nFile which failed to load:\n"); t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } else if (r != CafeSystem::PREPARE_STATUS_CODE::SUCCESS) { wxString t = _("Failed to launch game."); t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } } else //if (launchTitle.GetFormat() == TitleInfo::TitleDataFormat::INVALID_STRUCTURE ) { // title is invalid, if it's an RPX/ELF we can launch it directly // otherwise it's an error CafeTitleFileType fileType = DetermineCafeSystemFileType(launchPath); if (fileType == CafeTitleFileType::RPX || fileType == CafeTitleFileType::ELF) { CafeSystem::PREPARE_STATUS_CODE r = CafeSystem::PrepareForegroundTitleFromStandaloneRPX(launchPath); if (r != CafeSystem::PREPARE_STATUS_CODE::SUCCESS) { cemu_assert_debug(false); // todo wxString t = _("Failed to launch executable. Path: "); t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } } else if (initiatedBy == wxLaunchGameEvent::INITIATED_BY::GAME_LIST) { wxString t = _("Unable to launch title.\nMake sure the configured game paths are still valid and refresh the game list.\n\nPath which failed to load:\n"); t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } else if (initiatedBy == wxLaunchGameEvent::INITIATED_BY::MENU || initiatedBy == wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE) { wxString t = _("Unable to launch game\nPath:\n"); t.append(_pathToUtf8(launchPath)); if(launchTitle.GetInvalidReason() == TitleInfo::InvalidReason::NO_DISC_KEY) { t.append("\n\n"); t.append(_("Could not decrypt title. Make sure that keys.txt contains the correct disc key for this title.")); } if(launchTitle.GetInvalidReason() == TitleInfo::InvalidReason::NO_TITLE_TIK) { t.append("\n\n"); t.append(_("Could not decrypt title because title.tik is missing.")); } wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } else { wxString t = _("Unable to launch game\nPath:\n"); t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } } if(launchTitle.IsValid()) GetWxGUIConfig().AddRecentlyLaunchedFile(_pathToUtf8(launchTitle.GetPath())); else GetWxGUIConfig().AddRecentlyLaunchedFile(_pathToUtf8(launchPath)); wxWindowUpdateLocker lock(this); DestroyGameListAndStatusBar(); m_game_launched = true; m_loadMenuItem->Enable(false); m_installUpdateMenuItem->Enable(false); m_memorySearcherMenuItem->Enable(true); m_launched_game_name = CafeSystem::GetForegroundTitleName(); #ifdef ENABLE_DISCORD_RPC if (m_discord) m_discord->UpdatePresence(DiscordPresence::Playing, m_launched_game_name); #endif if (GetConfig().disable_screensaver) ScreenSaver::SetInhibit(true); if (FullscreenEnabled()) SetFullScreen(true); //GameMode support #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) if(GetWxGUIConfig().feral_gamemode) { // attempt to start gamemode if(gamemode_request_start() < 0) { // GameMode failed to start cemuLog_log(LogType::Force, "Could not start GameMode"); } else { cemuLog_log(LogType::Force, "GameMode has been started."); } } #endif CreateCanvas(); CafeSystem::LaunchForegroundTitle(); RecreateMenu(); UpdateChildWindowTitleRunningState(); return true; } void MainWindow::OnLaunchFromFile(wxLaunchGameEvent& event) { if (event.GetPath().empty()) return; FileLoad(event.GetPath(), event.GetInitiatedBy()); } void MainWindow::OnFileMenu(wxCommandEvent& event) { const auto menuId = event.GetId(); if (menuId == MAINFRAME_MENU_ID_FILE_LOAD) { const auto wildcard = formatWxString( "{}|*.wud;*.wux;*.wua;*.wuhb;*.iso;*.rpx;*.elf;title.tmd" "|{}|*.wud;*.wux;*.iso" "|{}|title.tmd" "|{}|*.wua" "|{}|*.wuhb" "|{}|*.rpx;*.elf" "|{}|*", _("All Wii U files (*.wud, *.wux, *.wua, *.wuhb, *.iso, *.rpx, *.elf)"), _("Wii U image (*.wud, *.wux, *.iso, *.wad)"), _("Wii U NUS content"), _("Wii U archive (*.wua)"), _("Wii U homebrew bundle (*.wuhb)"), _("Wii U executable (*.rpx, *.elf)"), _("All files (*.*)") ); wxFileDialog openFileDialog(this, _("Open file to launch"), wxEmptyString, wxEmptyString, wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDialog.ShowModal() == wxID_CANCEL || openFileDialog.GetPath().IsEmpty()) return; const wxString wxStrFilePath = openFileDialog.GetPath(); FileLoad(_utf8ToPath(wxStrFilePath.utf8_string()), wxLaunchGameEvent::INITIATED_BY::MENU); } else if (menuId >= MAINFRAME_MENU_ID_FILE_RECENT_0 && menuId <= MAINFRAME_MENU_ID_FILE_RECENT_LAST) { const auto& config = GetWxGUIConfig(); const size_t index = menuId - MAINFRAME_MENU_ID_FILE_RECENT_0; if (index < config.recent_launch_files.size()) { fs::path path = _utf8ToPath(config.recent_launch_files[index]); if (!path.empty()) FileLoad(path, wxLaunchGameEvent::INITIATED_BY::MENU); } } else if (menuId == MAINFRAME_MENU_ID_FILE_END_EMULATION) { EndEmulation(); } } void MainWindow::OnOpenFolder(wxCommandEvent& event) { const auto id = event.GetId(); if(id == MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER) wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetUserDataPath())); else if(id == MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER) wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetMlcPath())); else if (id == MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER) wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetCachePath("shaderCache"))); } void MainWindow::OnClearSpotPassCache(wxCommandEvent& event) { fs::path bossPath = ActiveSettings::GetMlcPath("usr/boss"); fs::remove_all(bossPath); // recreate usr/boss/ fs::create_directory(bossPath); wxMessageBox(_("SpotPass cache cleared")); } void MainWindow::OnInstallUpdate(wxCommandEvent& event) { while (true) { wxDirDialog openDirDialog(nullptr, _("Select folder of title to install"), "", wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST, wxDefaultPosition, wxDefaultSize, _("Select the folder that stores your update, DLC or base game files")); int modalChoice = openDirDialog.ShowModal(); if (modalChoice == wxID_CANCEL || openDirDialog.GetPath().IsEmpty()) break; if (modalChoice == wxID_OK) { #if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD fs::path dirPath((const char*)(openDirDialog.GetPath().fn_str())); #else fs::path dirPath(openDirDialog.GetPath().fn_str()); #endif if ((dirPath.filename() == "code" || dirPath.filename() == "content" || dirPath.filename() == "meta") && dirPath.has_parent_path()) { if (!fs::exists(dirPath.parent_path() / "code") || !fs::exists(dirPath.parent_path() / "content") || !fs::exists(dirPath.parent_path() / "meta")) { wxMessageBox(formatWxString(_("The (parent) folder of the title you selected is missing at least one of the required subfolders (\"code\", \"content\" and \"meta\")\nMake sure that the files are complete."), dirPath.filename().string())); continue; } else dirPath = dirPath.parent_path(); } if (!fs::exists(dirPath)) wxMessageBox(_("The folder you have selected cannot be found on your system.")); else if (!fs::exists(dirPath / "meta" / "meta.xml")) wxMessageBox(_("Unable to find the /meta/meta.xml file inside the selected folder.")); else { InstallUpdate(dirPath); return; } } } } void MainWindow::OnNFCMenu(wxCommandEvent& event) { if (event.GetId() == MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE) { wxFileDialog openFileDialog(this, _("Open file to load"), "", "", "All NFC files (bin, dat, nfc)|*.bin;*.dat;*.nfc|All files (*.*)|*", wxFD_OPEN | wxFD_FILE_MUST_EXIST); // TRANSLATE if (openFileDialog.ShowModal() == wxID_CANCEL || openFileDialog.GetPath().IsEmpty()) return; wxString wxStrFilePath = openFileDialog.GetPath(); uint32 nfcError; if (nfc::TouchTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) { if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) wxMessageBox(_("Not a valid NFC file")); } else { GetWxGUIConfig().AddRecentNfcFile(wxStrFilePath.utf8_string()); UpdateNFCMenu(); } } else if (event.GetId() >= MAINFRAME_MENU_ID_NFC_RECENT_0 && event.GetId() <= MAINFRAME_MENU_ID_NFC_RECENT_LAST) { const size_t index = event.GetId() - MAINFRAME_MENU_ID_NFC_RECENT_0; auto& config = GetWxGUIConfig(); if (index < config.recent_nfc_files.size()) { const auto& path = config.recent_nfc_files[index]; if (!path.empty()) { uint32 nfcError = 0; if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError) == false) { if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) wxMessageBox(_("Not a valid NFC file")); } else { config.AddRecentNfcFile(path); UpdateNFCMenu(); } } } } } void MainWindow::OnFileExit(wxCommandEvent& event) { Close(); } void MainWindow::TogglePadView() { const auto& config = GetWxGUIConfig(); if (config.pad_open) { if (m_padView) return; m_padView = new PadViewFrame(this); m_padView->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnPadClose, this); m_padView->Show(true); #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND if (wxWlIsWaylandWindow(m_padView)) wxWlSetAppId(m_padView, "info.cemu.Cemu"); #endif m_padView->Initialize(); if (m_game_launched) m_padView->InitializeRenderCanvas(); } else if (m_padView) { m_padView->Destroy(); m_padView = nullptr; } } #if BOOST_OS_WINDOWS #ifndef DBT_DEVNODES_CHANGED #define DBT_DEVNODES_CHANGED (0x0007) #endif WXLRESULT MainWindow::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) { if (nMsg == WM_DEVICECHANGE) { if (wParam == DBT_DEVNODES_CHANGED) { InputManager::instance().on_device_changed(); } } return wxFrame::MSWWindowProc(nMsg, wParam, lParam); } #endif void MainWindow::OpenSettings() { auto& config = GetWxGUIConfig(); const auto language = config.language; GeneralSettings2 frame(this, m_game_launched); frame.ShowModal(); const bool paths_modified = frame.ShouldReloadGamelist(); const bool mlc_modified = frame.MLCModified(); if (paths_modified) m_game_list->ReloadGameEntries(); else SaveSettings(); #ifdef ENABLE_DISCORD_RPC if (config.use_discord_presence) { if (!m_discord) { m_discord = std::make_unique(); if (!m_launched_game_name.empty()) m_discord->UpdatePresence(DiscordPresence::Playing, m_launched_game_name); } } else m_discord.reset(); #endif if(config.check_update && !m_game_launched) m_update_available = CemuUpdateWindow::IsUpdateAvailableAsync(); if (mlc_modified) RecreateMenu(); if (!config.fullscreen_menubar && IsFullScreen()) SetMenuVisible(false); if (language != config.language) wxMessageBox(_("Cemu must be restarted to apply the selected UI language."), _("Information"), wxOK | wxCENTRE, this); // TODO: change language to newly selected one } void MainWindow::OnOptionsInput(wxCommandEvent& event) { switch (event.GetId()) { case MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN: { const bool state = m_fullscreenMenuItem->IsChecked(); SetFullScreen(state); break; } case MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW: { GetWxGUIConfig().pad_open = !GetWxGUIConfig().pad_open; g_wxConfig.Save(); TogglePadView(); break; } case MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2: { if (m_graphic_pack_window) return; uint64 titleId = 0; if (CafeSystem::IsTitleRunning()) titleId = CafeSystem::GetForegroundTitleId(); m_graphic_pack_window = new GraphicPacksWindow2(this, titleId); m_graphic_pack_window->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnGraphicWindowClose, this); m_graphic_pack_window->Show(true); break; } case MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS: case MAINFRAME_MENU_ID_OPTIONS_GENERAL2: { OpenSettings(); break; } case MAINFRAME_MENU_ID_OPTIONS_INPUT: { auto* frame = new InputSettings2(this); frame->ShowModal(); frame->Destroy(); break; } case MAINFRAME_MENU_ID_OPTIONS_HOTKEY: { auto* frame = new HotkeySettings(this); frame->Show(); break; } } } void MainWindow::OnAccountSelect(wxCommandEvent& event) { const int index = event.GetId() - MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1; const auto& accounts = Account::GetAccounts(); wxASSERT(index >= 0 && index < (int)accounts.size()); auto& config = GetConfig(); config.account.m_persistent_id = accounts[index].GetPersistentId(); // config.account.online_enabled.value = false; // reset online for safety GetConfigHandle().Save(); } void MainWindow::OnConsoleLanguage(wxCommandEvent& event) { switch (event.GetId()) { case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE: GetConfig().console_language = CafeConsoleLanguage::JA; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH: GetConfig().console_language = CafeConsoleLanguage::EN; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_FRENCH: GetConfig().console_language = CafeConsoleLanguage::FR; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_GERMAN: GetConfig().console_language = CafeConsoleLanguage::DE; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ITALIAN: GetConfig().console_language = CafeConsoleLanguage::IT; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_SPANISH: GetConfig().console_language = CafeConsoleLanguage::ES; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_CHINESE: GetConfig().console_language = CafeConsoleLanguage::ZH; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_KOREAN: GetConfig().console_language = CafeConsoleLanguage::KO; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_DUTCH: GetConfig().console_language = CafeConsoleLanguage::NL; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE: GetConfig().console_language = CafeConsoleLanguage::PT; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN: GetConfig().console_language = CafeConsoleLanguage::RU; break; case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE: GetConfig().console_language = CafeConsoleLanguage::TW; break; default: cemu_assert_debug(false); } if (m_game_list) { m_game_list->DeleteCachedStrings(); m_game_list->ReloadGameEntries(); } GetConfigHandle().Save(); } //void MainWindow::OnCPUMode(wxCommandEvent& event) //{ // if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_SINGLECORE_INTERPRETER) // GetConfig().cpu_mode = CPUMode::SinglecoreInterpreter; // else if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_SINGLECORE_RECOMPILER) // GetConfig().cpu_mode = CPUMode::SinglecoreRecompiler; // else if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_DUALCORE_RECOMPILER) // GetConfig().cpu_mode = CPUMode::DualcoreRecompiler; // else if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_TRIPLECORE_RECOMPILER) // GetConfig().cpu_mode = CPUMode::TriplecoreRecompiler; // else // cemu_assert_debug(false); // // GetConfigHandle().Save(); //} void MainWindow::OnDebugSetting(wxCommandEvent& event) { if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN) GetConfig().render_upside_down = event.IsChecked(); else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS) { GetConfig().vk_accurate_barriers = event.IsChecked(); if(!GetConfig().vk_accurate_barriers) wxMessageBox(_("Warning: Disabling the accurate barriers option will lead to flickering graphics but may improve performance. It is highly recommended to leave it turned on."), _("Accurate barriers are off"), wxOK); } #if ENABLE_METAL else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_GPU_CAPTURE) { cemu_assert_debug(g_renderer->GetType() == RendererAPI::Metal); static_cast(g_renderer.get())->CaptureFrame(); } #endif else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY) ActiveSettings::EnableAudioOnlyAux(event.IsChecked()); else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_RAM) memory_createDump(); else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_FST) { /* int msgBoxAnswer = wxMessageBox(_("All files from the currently running game will be dumped to /dump/. This process can take a few minutes."), _("Dump WUD"), wxOK | wxCANCEL | wxICON_WARNING); if (msgBoxAnswer == wxOK) { volumeFST_dump(bootGame_getMountedWUD()); wxMessageBox(_("Dump complete")); }*/ } else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS) { // toggle debug -> dump -> curl requests const bool state = event.IsChecked(); ActiveSettings::EnableDumpLibcurlRequests(state); if (state) { try { const fs::path path(ActiveSettings::GetUserDataPath()); fs::create_directories(path / "dump" / "curl"); } catch (const std::exception& ex) { SystemException sys(ex); cemuLog_log(LogType::Force, "error when creating dump curl folder: {}", sys.what()); ActiveSettings::EnableDumpLibcurlRequests(false); } } } else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_8X) ActiveSettings::SetTimerShiftFactor(0); else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_4X) ActiveSettings::SetTimerShiftFactor(1); else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_2X) ActiveSettings::SetTimerShiftFactor(2); else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_1X) ActiveSettings::SetTimerShiftFactor(3); else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_05X) ActiveSettings::SetTimerShiftFactor(4); else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_025X) ActiveSettings::SetTimerShiftFactor(5); else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_0125X) ActiveSettings::SetTimerShiftFactor(6); else cemu_assert_debug(false); GetConfigHandle().Save(); } void MainWindow::OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event) { sint32 loggingIdBase = MAINFRAME_MENU_ID_DEBUG_LOGGING0; sint32 id = event.GetId(); if (id >= loggingIdBase && id < (MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 64)) { bool isEnable = event.IsChecked(); LogType loggingType = static_cast(id - loggingIdBase); if (isEnable) GetConfig().log_flag = GetConfig().log_flag.GetValue() | cemuLog_getFlag(loggingType); else GetConfig().log_flag = GetConfig().log_flag.GetValue() & ~cemuLog_getFlag(loggingType); cemuLog_setActiveLoggingFlags(GetConfig().log_flag.GetValue()); GetConfigHandle().Save(); } } void MainWindow::OnPPCInfoToggle(wxCommandEvent& event) { GetConfig().advanced_ppc_logging = !GetConfig().advanced_ppc_logging.GetValue(); GetConfigHandle().Save(); } void MainWindow::OnDebugDumpGeneric(wxCommandEvent& event) { std::string dumpSubpath; std::function setDumpState; switch(event.GetId()) { case MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES: dumpSubpath = "dump/textures"; setDumpState = ActiveSettings::EnableDumpTextures; break; case MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS: dumpSubpath = "dump/shaders"; setDumpState = ActiveSettings::EnableDumpShaders; break; case MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS: dumpSubpath = "dump/recompiler"; setDumpState = ActiveSettings::EnableDumpRecompilerFunctions; break; default: UNREACHABLE; } const bool value = event.IsChecked(); setDumpState(value); if (value) { try { fs::create_directories(ActiveSettings::GetUserDataPath(dumpSubpath)); } catch (const std::exception & ex) { SystemException sys(ex); cemuLog_log(LogType::Force, "can't create folder {} in user data folder: {}", dumpSubpath, ex.what()); setDumpState(false); } } } void MainWindow::OnLoggingWindow(wxCommandEvent& event) { if(m_logging_window) return; m_logging_window = new LoggingWindow(this); m_logging_window->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { m_logging_window = nullptr; event.Skip(); }); m_logging_window->Show(true); } void MainWindow::OnGDBStubToggle(wxCommandEvent& event) { if (g_gdbstub) { g_gdbstub.release(); return; } const auto& config = GetConfig(); g_gdbstub = std::make_unique(config.gdb_port); } void MainWindow::OnDebugViewPPCThreads(wxCommandEvent& event) { auto frame = new DebugPPCThreadsWindow(*this); frame->Show(true); } void MainWindow::OnDebugViewPPCDebugger(wxCommandEvent& event) { if (m_debugger_window && m_debugger_window->IsShown()) { m_debugger_window->Close(); m_debugger_window = nullptr; return; } auto rect = GetDesktopRect(); /* sint32 new_width = max(rect.GetWidth() * 0.70, rect.GetWidth() - 850); this->SetSize(new_width, 480);*/ this->SetSize(800, 450 + 50); this->CenterOnScreen(); auto pos = this->GetPosition(); pos.y = std::min(pos.y + 200, rect.GetHeight() - 400); this->SetPosition(pos); m_debugger_window = new DebuggerWindow2(*this, rect); m_debugger_window->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnDebuggerClose, this); m_debugger_window->Show(true); } void MainWindow::OnDebugViewAudioDebugger(wxCommandEvent& event) { auto frame = new AudioDebuggerWindow(*this); frame->Show(true); } void MainWindow::OnDebugViewTextureRelations(wxCommandEvent& event) { openTextureViewer(*this); } void MainWindow::ShowCursor(bool state) { #if BOOST_OS_WINDOWS CURSORINFO info{}; info.cbSize = sizeof(CURSORINFO); GetCursorInfo(&info); const bool visible = info.flags == CURSOR_SHOWING; if (state == visible) return; int counter = 0; if(state) { do { counter = ::ShowCursor(TRUE); } while (counter < 0); } else { do { counter = ::ShowCursor(FALSE); } while (counter >= 0); } #else if (state) { wxSetCursor(wxNullCursor); // restore system default cursor } else { wxSetCursor(wxCursor(wxCURSOR_BLANK)); } #endif } uintptr_t MainWindow::GetRenderCanvasHWND() { // deprecated. We can use the global cross-platform window info structs now #if BOOST_OS_WINDOWS if (!m_render_canvas) return 0; return (uintptr_t)m_render_canvas->GetHWND(); #else return 0; #endif } wxRect MainWindow::GetDesktopRect() { const auto pos = GetPosition(); const auto middle = pos.x + GetSize().GetWidth() / 2; const auto displayCount = wxDisplay::GetCount(); for (uint32 i = 0; i < displayCount; ++i) { wxDisplay display(i); if (!display.IsOk()) continue; const auto geo = display.GetGeometry(); if (geo.x <= middle && middle <= geo.x + geo.width) return geo; } return { 0,0,800,600 }; } void MainWindow::LoadSettings() { GetConfigHandle().Load(); const auto& config = GetWxGUIConfig(); if(config.check_update) m_update_available = CemuUpdateWindow::IsUpdateAvailableAsync(); if (config.window_position != Vector2i{ -1,-1 }) this->SetPosition({ config.window_position.x, config.window_position.y }); if (config.window_size.x > 0 && config.window_size.y > 0) { this->SetSize({ config.window_size.x, config.window_size.y }); if (config.window_maximized) this->Maximize(); } if (config.pad_position != Vector2i{ -1,-1 }) { g_window_info.restored_pad_x = config.pad_position.x; g_window_info.restored_pad_y = config.pad_position.y; } if (config.pad_size != Vector2i{ -1,-1 }) { g_window_info.restored_pad_width = config.pad_size.x; g_window_info.restored_pad_height = config.pad_size.y; g_window_info.pad_maximized = config.pad_maximized; } this->TogglePadView(); if(m_game_list) m_game_list->LoadConfig(); } void MainWindow::SaveSettings() { auto lock = GetConfigHandle().Lock(); auto& config = GetWxGUIConfig(); if (config.window_position != Vector2i{ -1,-1 }) { config.window_position.x = m_restored_position.x; config.window_position.y = m_restored_position.y; } if (config.window_size != Vector2i{ -1,-1 }) { config.window_size.x = m_restored_size.x; config.window_size.y = m_restored_size.y; config.window_maximized = IsMaximized(); } else { config.window_maximized = false; } config.pad_open = m_padView != nullptr; if (config.pad_position != Vector2i{ -1,-1 } && g_window_info.restored_pad_x != -1) { config.pad_position.x = g_window_info.restored_pad_x; config.pad_position.y = g_window_info.restored_pad_y; } if (config.pad_size != Vector2i{ -1,-1 } && g_window_info.restored_pad_width != -1) { config.pad_size.x = g_window_info.restored_pad_width; config.pad_size.y = g_window_info.restored_pad_height; config.pad_maximized = g_window_info.pad_maximized; } else { config.pad_maximized = false; } if(m_game_list) m_game_list->SaveConfig(); g_wxConfig.Save(); } void MainWindow::OnMouseMove(wxMouseEvent& event) { event.Skip(); m_last_mouse_move_time = std::chrono::steady_clock::now(); m_mouse_position = wxGetMousePosition(); ShowCursor(true); auto& instance = InputManager::instance(); std::unique_lock lock(instance.m_main_mouse.m_mutex); auto physPos = ToPhys(event.GetPosition()); instance.m_main_mouse.position = { physPos.x, physPos.y }; lock.unlock(); if (!IsFullScreen()) return; const auto& config = GetWxGUIConfig(); // if mouse goes to upper screen then show our menu in fullscreen mode if (config.fullscreen_menubar) SetMenuVisible(event.GetPosition().y < 50); } void MainWindow::OnMouseLeft(wxMouseEvent& event) { auto& instance = InputManager::instance(); std::scoped_lock lock(instance.m_main_mouse.m_mutex); instance.m_main_mouse.left_down = event.ButtonDown(wxMOUSE_BTN_LEFT); auto physPos = ToPhys(event.GetPosition()); instance.m_main_mouse.position = { physPos.x, physPos.y }; if (event.ButtonDown(wxMOUSE_BTN_LEFT)) instance.m_main_mouse.left_down_toggle = true; event.Skip(); } void MainWindow::OnMouseRight(wxMouseEvent& event) { auto& instance = InputManager::instance(); std::scoped_lock lock(instance.m_main_mouse.m_mutex); instance.m_main_mouse.right_down = event.ButtonDown(wxMOUSE_BTN_RIGHT); auto physPos = ToPhys(event.GetPosition()); instance.m_main_mouse.position = { physPos.x, physPos.y }; if(event.ButtonDown(wxMOUSE_BTN_RIGHT)) instance.m_main_mouse.right_down_toggle = true; event.Skip(); } void MainWindow::OnGameListBeginUpdate(wxCommandEvent& event) { if (m_game_list->IsShown()) m_info_bar->ShowMessage(_("Updating game list...")); } void MainWindow::OnGameListEndUpdate(wxCommandEvent& event) { m_info_bar->Dismiss(); } void MainWindow::OnAccountListRefresh(wxCommandEvent& event) { RecreateMenu(); } void MainWindow::OnRequestGameListRefresh(wxCommandEvent& event) { m_game_list->ReloadGameEntries(); } void MainWindow::OnSetWindowTitle(wxCommandEvent& event) { this->SetTitle(event.GetString()); } void MainWindow::OnKeyUp(wxKeyEvent& event) { event.Skip(); if (swkbd_hasKeyboardInputHook()) return; HotkeySettings::CaptureInput(event); } void MainWindow::OnKeyDown(wxKeyEvent& event) { #if defined(__APPLE__) // On macOS, allow Cmd+Q to quit the application if (event.CmdDown() && event.GetKeyCode() == 'Q') { Close(true); } #else // On Windows/Linux, only Alt+F4 is allowed for quitting if (event.AltDown() && event.GetKeyCode() == WXK_F4) { Close(true); } #endif else { event.Skip(); } } void MainWindow::OnChar(wxKeyEvent& event) { if (swkbd_hasKeyboardInputHook()) swkbd_keyInput(event.GetUnicodeKey()); // event.Skip(); } void MainWindow::OnToolsInput(wxCommandEvent& event) { const auto id = event.GetId(); switch (id) { case MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER: { if (m_toolWindow) m_toolWindow->SetFocus(); else { m_toolWindow = new MemorySearcherTool(this); m_toolWindow->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnMemorySearcherClose, this); m_toolWindow->Show(true); } break; } case MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER: case MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER: { const auto default_tab = id == MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER ? TitleManagerPage::TitleManager : TitleManagerPage::DownloadManager; if (m_title_manager) m_title_manager->SetFocusAndTab(default_tab); else { m_title_manager = new TitleManager(this, default_tab); m_title_manager->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { m_title_manager = nullptr; event.Skip(); }); m_title_manager->Show(); } break; } case MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES: { if (m_usb_devices) { m_usb_devices->Show(true); m_usb_devices->Raise(); m_usb_devices->SetFocus(); } else { m_usb_devices = new EmulatedUSBDeviceFrame(this); m_usb_devices->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { if (event.CanVeto()) { m_usb_devices->Show(false); event.Veto(); } }); m_usb_devices->Show(true); } break; } break; } } void MainWindow::OnGesturePan(wxPanGestureEvent& event) { auto& instance = InputManager::instance(); std::scoped_lock lock(instance.m_main_touch.m_mutex); auto physPos = ToPhys(event.GetPosition()); instance.m_main_touch.position = { physPos.x, physPos.y }; instance.m_main_touch.left_down = event.IsGestureStart() || !event.IsGestureEnd(); if (event.IsGestureStart() || !event.IsGestureEnd()) instance.m_main_touch.left_down_toggle = true; event.Skip(); } void MainWindow::OnGameLoaded() { if (m_debugger_window) m_debugger_window->OnGameLoaded(); } void MainWindow::AsyncSetTitle(std::string_view windowTitle) { wxCommandEvent set_title_event(wxEVT_SET_WINDOW_TITLE); set_title_event.SetString(wxString::FromUTF8(windowTitle)); g_mainFrame->QueueEvent(set_title_event.Clone()); } void MainWindow::CreateCanvas() { // create panel for canvas m_game_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxWANTS_CHARS); auto* sizer = new wxBoxSizer(wxVERTICAL); // shouldn't be needed, but who knows m_game_panel->Bind(wxEVT_KEY_UP, &MainWindow::OnKeyUp, this); m_game_panel->Bind(wxEVT_CHAR, &MainWindow::OnChar, this); m_game_panel->SetSizer(sizer); this->GetSizer()->Add(m_game_panel, 1, wxEXPAND); // create canvas if (ActiveSettings::GetGraphicsAPI() == kVulkan) m_render_canvas = new VulkanCanvas(m_game_panel, wxSize(1280, 720), true); else if (ActiveSettings::GetGraphicsAPI() == kOpenGL) m_render_canvas = GLCanvas_Create(m_game_panel, wxSize(1280, 720), true); #if ENABLE_METAL else m_render_canvas = new MetalCanvas(m_game_panel, wxSize(1280, 720), true); #endif // mouse events m_render_canvas->Bind(wxEVT_MOTION, &MainWindow::OnMouseMove, this); m_render_canvas->Bind(wxEVT_MOUSEWHEEL, &MainWindow::OnMouseWheel, this); m_render_canvas->Bind(wxEVT_LEFT_DOWN, &MainWindow::OnMouseLeft, this); m_render_canvas->Bind(wxEVT_LEFT_UP, &MainWindow::OnMouseLeft, this); m_render_canvas->Bind(wxEVT_RIGHT_DOWN, &MainWindow::OnMouseRight, this); m_render_canvas->Bind(wxEVT_RIGHT_UP, &MainWindow::OnMouseRight, this); m_render_canvas->Bind(wxEVT_GESTURE_PAN, &MainWindow::OnGesturePan, this); // key events m_render_canvas->Bind(wxEVT_KEY_UP, &MainWindow::OnKeyUp, this); m_render_canvas->Bind(wxEVT_KEY_DOWN, &MainWindow::OnKeyDown, this); m_render_canvas->Bind(wxEVT_CHAR, &MainWindow::OnChar, this); m_render_canvas->SetDropTarget(new wxAmiiboDropTarget(this)); m_game_panel->GetSizer()->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr); GetSizer()->Layout(); m_render_canvas->SetFocus(); if (m_padView) m_padView->InitializeRenderCanvas(); } void MainWindow::DestroyCanvas() { if (m_padView) { m_padView->DestroyCanvas(); } if (m_render_canvas) { m_render_canvas->Destroy(); m_render_canvas = nullptr; } if(m_game_panel) { m_game_panel->Destroy(); m_game_panel = nullptr; } } void MainWindow::OnSizeEvent(wxSizeEvent& event) { if (!IsMaximized() && !WindowSystem::IsFullScreen()) m_restored_size = GetSize(); const wxSize client_size = GetClientSize(); g_window_info.width = client_size.GetWidth(); g_window_info.height = client_size.GetHeight(); g_window_info.phys_width = ToPhys(client_size.GetWidth()); g_window_info.phys_height = ToPhys(client_size.GetHeight()); g_window_info.dpi_scale = GetDPIScaleFactor(); if (m_debugger_window && m_debugger_window->IsShown()) m_debugger_window->OnParentMove(GetPosition(), event.GetSize()); event.Skip(); VsyncDriver_notifyWindowPosChanged(); } void MainWindow::OnDPIChangedEvent(wxDPIChangedEvent& event) { event.Skip(); const wxSize client_size = GetClientSize(); g_window_info.width = client_size.GetWidth(); g_window_info.height = client_size.GetHeight(); g_window_info.phys_width = ToPhys(client_size.GetWidth()); g_window_info.phys_height = ToPhys(client_size.GetHeight()); g_window_info.dpi_scale = GetDPIScaleFactor(); } void MainWindow::OnMove(wxMoveEvent& event) { if (!IsMaximized() && !WindowSystem::IsFullScreen()) m_restored_position = GetPosition(); if (m_debugger_window && m_debugger_window->IsShown()) m_debugger_window->OnParentMove(GetPosition(), GetSize()); VsyncDriver_notifyWindowPosChanged(); } void MainWindow::OnDebuggerClose(wxCloseEvent& event) { m_debugger_window = nullptr; event.Skip(); } void MainWindow::OnPadClose(wxCloseEvent& event) { auto& config = GetWxGUIConfig(); config.pad_open = false; if (config.pad_position != Vector2i{ -1,-1 }) m_padView->GetPosition(&config.pad_position.x, &config.pad_position.y); if (config.pad_size != Vector2i{ -1,-1 }) m_padView->GetSize(&config.pad_size.x, &config.pad_size.y); g_wxConfig.Save(); // already deleted by wxwidget m_padView = nullptr; if (m_padViewMenuItem) m_padViewMenuItem->Check(false); event.Skip(); } void MainWindow::OnMemorySearcherClose(wxCloseEvent& event) { m_toolWindow = nullptr; event.Skip(); } void MainWindow::OnMouseWheel(wxMouseEvent& event) { const float delta = event.GetWheelRotation(); // in 120 steps -> max reached ~480 (?) auto& instance = InputManager::instance(); instance.m_mouse_wheel = (delta / 120.0f); event.Skip(); } void MainWindow::SetFullScreen(bool state) { // only update config entry if we dont't have launch parameters if (!LaunchSettings::FullscreenEnabled().has_value()) { GetWxGUIConfig().fullscreen = state; g_wxConfig.Save(); } if (state && !m_game_launched) return; g_window_info.is_fullscreen = state; m_fullscreenMenuItem->Check(state); this->ShowFullScreen(state); if (state) m_menu_visible = false; // menu gets always disabled by wxFULLSCREEN_NOMENUBAR else SetMenuVisible(true); } void MainWindow::EndEmulation() // unfinished - memory leaks and crashes after repeated use (after 3x usually) { CafeSystem::ShutdownTitle(); DestroyCanvas(); m_game_launched = false; m_launched_game_name.clear(); #ifdef ENABLE_DISCORD_RPC if (m_discord) m_discord->UpdatePresence(DiscordPresence::Idling, ""); #endif if (GetConfig().disable_screensaver) ScreenSaver::SetInhibit(false); // close memory searcher if created if (m_toolWindow) { m_toolWindow->Close(); m_toolWindow = nullptr; m_memorySearcherMenuItem->Enable(false); } RecreateMenu(); CreateGameListAndStatusBar(); DoLayout(); UpdateChildWindowTitleRunningState(); } void MainWindow::SetMenuVisible(bool state) { if (m_menu_visible == state) return; SetMenuBar(state ? m_menuBar : nullptr); m_menu_visible = state; } void MainWindow::UpdateNFCMenu() { if (m_nfcMenuSeparator0) { m_nfcMenu->Remove(m_nfcMenuSeparator0->GetId()); m_nfcMenuSeparator0 = nullptr; } // remove recent files list for (sint32 i = 0; i < wxCemuConfig::kMaxRecentEntries; i++) { if (m_nfcMenu->FindChildItem(MAINFRAME_MENU_ID_NFC_RECENT_0 + i) == nullptr) continue; m_nfcMenu->Remove(MAINFRAME_MENU_ID_NFC_RECENT_0 + i); } // add entries const auto& config = GetWxGUIConfig(); sint32 recentFileIndex = 0; for (size_t i = 0; i < config.recent_nfc_files.size(); i++) { const auto& entry = config.recent_nfc_files[i]; if (entry.empty()) continue; if (!fs::exists(_utf8ToPath(entry))) continue; if (recentFileIndex == 0) m_nfcMenuSeparator0 = m_nfcMenu->AppendSeparator(); m_nfcMenu->Append(MAINFRAME_MENU_ID_NFC_RECENT_0 + i, formatWxString("{}. {}", recentFileIndex, entry)); recentFileIndex++; if (recentFileIndex >= 12) break; } } bool MainWindow::IsMenuHidden() const { return m_menu_visible; } void MainWindow::OnTimer(wxTimerEvent& event) { if(m_update_available.valid() && future_is_ready(m_update_available)) { if(m_update_available.get()) { wxMessageDialog dialog(this, _("There's a new update available.\nDo you want to update?"), _("Update notification"), wxCENTRE | wxYES_NO); if(dialog.ShowModal() == wxID_YES) { CemuUpdateWindow update_window(this); update_window.ShowModal(); } } m_update_available = {}; } if (!IsFullScreen() || m_menu_visible) return; const auto mouse_position = wxGetMousePosition(); if(m_mouse_position != mouse_position) { m_last_mouse_move_time = std::chrono::steady_clock::now(); m_mouse_position = mouse_position; ShowCursor(true); return; } auto diff = std::chrono::duration_cast(std::chrono::steady_clock::now() - m_last_mouse_move_time); if (diff.count() > 3000) { ShowCursor(false); } } #define BUILD_DATE __DATE__ " " __TIME__ class CemuAboutDialog : public wxDialog { public: CemuAboutDialog(wxWindow* parent = NULL) : wxDialog(NULL, wxID_ANY, _("About Cemu"), wxDefaultPosition, wxSize(500, 700)) { Create(parent); } void Create(wxWindow* parent = NULL) { SetIcon(wxICON(M_WND_ICON128)); wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this); wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); m_scrolledSizer = new wxBoxSizer(wxVERTICAL); AddHeaderInfo(scrolledWindow, m_scrolledSizer); m_scrolledSizer->AddSpacer(5); AddLibInfo(scrolledWindow, m_scrolledSizer); m_scrolledSizer->AddSpacer(5); AddThanks(scrolledWindow, m_scrolledSizer); scrolledWindow->SetSizer(m_scrolledSizer); scrolledWindow->FitInside(); scrolledWindow->SetScrollRate(25, 25); mainSizer->Add(scrolledWindow, wxSizerFlags(1).Expand().Border(wxLEFT, 10)); SetSizer(mainSizer); CentreOnParent(); } void AddHeaderInfo(wxWindow* parent, wxSizer* sizer) { auto versionString = formatWxString(_("Cemu\nVersion {0}\nCompiled on {1}\nOriginal authors: {2}"), BUILD_VERSION_STRING, BUILD_DATE, "Exzap, Petergov"); sizer->Add(new wxStaticText(parent, wxID_ANY, versionString), wxSizerFlags().Border(wxALL, 3).Border(wxTOP, 10)); sizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://cemu.info", "https://cemu.info", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 3)); sizer->AddSpacer(3); sizer->Add(new wxStaticLine(parent), wxSizerFlags().Expand().Border(wxRIGHT, 4)); sizer->AddSpacer(5); wxString extraInfo(_("Cemu is a Wii U emulator.\n\nWii and Wii U are trademarks of Nintendo.\nCemu is not affiliated with Nintendo.")); sizer->Add(new wxStaticText(parent, wxID_ANY, extraInfo), wxSizerFlags()); } void AddLibInfo(wxWindow* parent, wxSizer* sizer) { sizer->AddSpacer(3); sizer->Add(new wxStaticLine(parent), wxSizerFlags().Expand().Border(wxRIGHT, 4)); sizer->AddSpacer(3); sizer->Add(new wxStaticText(parent, wxID_ANY, _("Used libraries and utilities:")), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 2)); // zLib { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "zLib (")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.zlib.net", "https://www.zlib.net", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")")); sizer->Add(lineSizer); } // wxWidgets { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "wxWidgets (")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.wxwidgets.org/", "https://www.wxwidgets.org/", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")")); sizer->Add(lineSizer); } // OpenSSL { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "OpenSSL (")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.openssl.org/", "https://www.openssl.org/", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")")); sizer->Add(lineSizer); } // libcurl { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "libcurl (")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://curl.haxx.se/libcurl/", "https://curl.haxx.se/libcurl/", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")")); sizer->Add(lineSizer); } // imgui { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "imgui (")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://github.com/ocornut/imgui", "https://github.com/ocornut/imgui", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")")); sizer->Add(lineSizer); } // fontawesome { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "fontawesome (")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://github.com/FortAwesome/Font-Awesome", "https://github.com/FortAwesome/Font-Awesome", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")")); sizer->Add(lineSizer); } // boost { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "boost (")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.boost.org", "https://www.boost.org", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")")); sizer->Add(lineSizer); } // libusb { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "libusb (")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://libusb.info", "https://libusb.info", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")")); sizer->Add(lineSizer); } #if BOOST_OS_MACOS // MoltenVK { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, -1, "MoltenVK (")); lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://github.com/KhronosGroup/MoltenVK", "https://github.com/KhronosGroup/MoltenVK", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, -1, ")")); sizer->Add(lineSizer); } #endif // icons { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "icons from ")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://icons8.com", "https://icons8.com", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); sizer->Add(lineSizer); } // Lato font (are we still using it?) { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "\"Lato\" font by tyPoland Lukasz Dziedzic (OFL, V1.1)", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); sizer->Add(lineSizer); } // SDL { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "SDL (")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://github.com/libsdl-org/SDL", "https://github.com/libsdl-org/SDL", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")")); sizer->Add(lineSizer); } // IH264 { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); lineSizer->Add(new wxStaticText(parent, wxID_ANY, "Modified ih264 from Android project (")); lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "Source", "https://cemu.info/oss/ih264d.zip", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT))); lineSizer->Add(new wxStaticText(parent, wxID_ANY, " ")); wxHyperlinkCtrl* noticeLink = new wxHyperlinkCtrl(parent, wxID_ANY, "NOTICE", "", wxDefaultPosition, wxDefaultSize, (wxHL_CONTEXTMENU|wxNO_BORDER|wxHL_ALIGN_LEFT)); noticeLink->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& event) { fs::path tempPath = fs::temp_directory_path(); tempPath.append("NOTICE_IH264.txt"); FileStream* fs = FileStream::createFile2(tempPath); if (!fs) return; fs->writeString( "/******************************************************************************\r\n" " *\r\n" " * Copyright (C) 2015 The Android Open Source Project\r\n" " *\r\n" " * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n" " * you may not use this file except in compliance with the License.\r\n" " * You may obtain a copy of the License at:" " *\r\n" " * http://www.apache.org/licenses/LICENSE-2.0\r\n" " *\r\n" " * Unless required by applicable law or agreed to in writing, software\r\n" " * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n" " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n" " * See the License for the specific language governing permissions and\r\n" " * limitations under the License.\r\n" " *\r\n" " *****************************************************************************\r\n" " * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore\r\n" "*/\r\n" "/*****************************************************************************/\r\n" ); delete fs; wxLaunchDefaultBrowser(formatWxString("file:{}", _pathToUtf8(tempPath))); }); lineSizer->Add(noticeLink); lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")")); sizer->Add(lineSizer); } } void AddThanks(wxWindow* parent, wxSizer* sizer) { sizer->AddSpacer(3); sizer->Add(new wxStaticLine(parent), wxSizerFlags().Expand().Border(wxRIGHT, 4)); sizer->AddSpacer(3); wxGridSizer* gridSizer = new wxGridSizer(1, 2, 0, 0); sizer->AddSpacer(2); sizer->Add(new wxStaticText(parent, wxID_ANY, _("Thanks to our Patreon supporters:")), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 2)); std::vector patreonSupporterNames{ "Maufeat", "lvlv", "F34R", "John Godgames", "Jameel Lewis", "skooks", "Cheesy", "Barrowsx", "Mored1984", "madmat007" , "Kuhnnl", "Owen M", "lucianobugalu", "KimoMaka", "nick palma aka renaissance18", "TheGiantBros", "SpiGAndromeda" , "Chimech0", "Nicolás Pino", "Pezzatti", "Barry Wallace", "REGNR8 Productions", "Lagia", "Freestyler316", "Dentora" , "tactics", "Merola.C", "Ceigyx", "Mata", "BobSchneeder45", "fenixDG", "jjalapeno55", "FissionMetroid101", "Jetta88" , "nesxdie", "Mikah", "PornfoxVR.com", "Hunter4everosa", "Bbzx", "Salim Sanehi", "FalloutpunkX", "NashOH-CL", "RaheemWala" , "Faris Leonhart", "MahvZero", "PlaguedGuardian", "Stuffie", "CaptainLester", "Qtech", "Zaurexus", "Leonidas", "Artifesto" , "Alca259", "SirWestofAsh", "Loli Co.", "The Technical Revolutionary", "MegaYama", "mitori", "Seymordius", "Adrian Josh Cruz", "Manuel Hoenings", "Just A Jabb" , "pgantonio", "CannonXIII", "Lonewolf00708", "AlexsDesign.com", "NoskLo", "MrSirHaku", "xElite_V AKA William H. Johnson", "Zalnor", "Pig", "James \"SE4LS\"", "DairyOrange", "Horoko Lawrence", "bloodmc", "Officer Jenny", "Quasar", "Postposterous", "Jake Jackson", "Kaydax", "CthePredatorG" , "Hengi", "Pyrochaser", "luma.x3"}; wxString nameListLeft, nameListRight; for (size_t i = 0; i < patreonSupporterNames.size(); i++) { const char* name = patreonSupporterNames[i]; wxString& nameList = ((i % 2) == 0) ? nameListLeft : nameListRight; if (i >= 2) nameList.append("\n"); nameList.append(wxString::FromUTF8(name)); } gridSizer->Add(new wxStaticText(parent, wxID_ANY, nameListLeft), wxSizerFlags()); gridSizer->Add(new wxStaticText(parent, wxID_ANY, nameListRight), wxSizerFlags()); sizer->AddSpacer(4); sizer->Add(gridSizer, 1, wxEXPAND); sizer->AddSpacer(2); sizer->Add(new wxStaticText(parent, wxID_ANY, _("Special thanks:")), wxSizerFlags().Expand().Border(wxTOP, 2)); sizer->Add(new wxStaticText(parent, wxID_ANY, "espes - Also try XQEMU!\nWaltzz92"), wxSizerFlags().Expand().Border(wxTOP, 1)); } protected: wxSizer* m_scrolledSizer; }; void MainWindow::OnHelpAbout(wxCommandEvent& event) { CemuAboutDialog dlgAbout(this); dlgAbout.ShowModal(); } void MainWindow::OnHelpUpdate(wxCommandEvent& event) { CemuUpdateWindow test(this); test.ShowModal(); } void MainWindow::RecreateMenu() { if (m_menuBar) { SetMenuBar(nullptr); m_menuBar->Destroy(); m_menuBar = nullptr; } auto& guiConfig = GetWxGUIConfig(); m_menuBar = new wxMenuBar(); // file submenu m_fileMenu = new wxMenu(); if (!m_game_launched) { m_loadMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_LOAD, _("&Load...")); m_installUpdateMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, _("&Install game title, update or DLC...")); wxMenu* recentMenu = new wxMenu(); sint32 recentFileIndex = 1; m_fileMenuSeparator0 = nullptr; m_fileMenuSeparator1 = nullptr; for (size_t i = 0; i < guiConfig.recent_launch_files.size(); i++) { const std::string& pathStr = guiConfig.recent_launch_files[i]; if (pathStr.empty()) continue; recentMenu->Append(MAINFRAME_MENU_ID_FILE_RECENT_0 + i, formatWxString("{}. {}", recentFileIndex, pathStr)); recentFileIndex++; if (recentFileIndex >= 10) break; } if (recentFileIndex == 0) { wxMenuItem* placeholder = recentMenu->Append(wxID_NONE, _("(No recent files)")); placeholder->Enable(false); } m_fileMenu->AppendSeparator(); m_fileMenu->AppendSubMenu(recentMenu, _("Recent files")); m_fileMenu->AppendSeparator(); } else { #ifdef CEMU_DEBUG_ASSERT m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_END_EMULATION, _("Close game")); m_fileMenuSeparator1 = m_fileMenu->AppendSeparator(); #endif } m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("Open Cemu folder")); m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("Open MLC folder")); m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, _("Open &shader cache folder")); m_fileMenu->AppendSeparator(); m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, _("Clear Spot&Pass cache")); if (m_game_launched) m_fileMenu->Enable(MAINFRAME_MENU_ID_FILE_CLEAR_SPOTPASS_CACHE, false); m_fileMenu->AppendSeparator(); m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit")); m_menuBar->Append(m_fileMenu, _("&File")); // options->account submenu m_optionsAccountMenu = new wxMenu(); const auto account_id = ActiveSettings::GetPersistentId(); int index = 0; for(const auto& account : Account::GetAccounts()) { wxMenuItem* item = m_optionsAccountMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 + index, account.ToString()); item->Check(account_id == account.GetPersistentId()); if (m_game_launched || LaunchSettings::GetPersistentId().has_value()) item->Enable(false); ++index; } auto& config = GetConfig(); auto& wxConfig = GetWxGUIConfig(); // options->console language submenu wxMenu* optionsConsoleLanguageMenu = new wxMenu(); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH, _("&English"))->Check(config.console_language == CafeConsoleLanguage::EN); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE, _("&Japanese"))->Check(config.console_language == CafeConsoleLanguage::JA); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_FRENCH, _("&French"))->Check(config.console_language == CafeConsoleLanguage::FR); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_GERMAN, _("&German"))->Check(config.console_language == CafeConsoleLanguage::DE); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ITALIAN, _("&Italian"))->Check(config.console_language == CafeConsoleLanguage::IT); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_SPANISH, _("&Spanish"))->Check(config.console_language == CafeConsoleLanguage::ES); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_CHINESE, _("&Chinese"))->Check(config.console_language == CafeConsoleLanguage::ZH); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_KOREAN, _("&Korean"))->Check(config.console_language == CafeConsoleLanguage::KO); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_DUTCH, _("&Dutch"))->Check(config.console_language == CafeConsoleLanguage::NL); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE, _("&Portuguese"))->Check(config.console_language == CafeConsoleLanguage::PT); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN, _("&Russian"))->Check(config.console_language == CafeConsoleLanguage::RU); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, _("&Taiwanese"))->Check(config.console_language == CafeConsoleLanguage::TW); if(IsGameLaunched()) { auto items = optionsConsoleLanguageMenu->GetMenuItems(); for (auto& item : items) { item->Enable(false); } } // options submenu wxMenu* optionsMenu = new wxMenu(); m_fullscreenMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN, _("&Fullscreen")); m_fullscreenMenuItem->Check(FullscreenEnabled()); optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2, _("&Graphic packs")); m_padViewMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, _("&Separate GamePad view")); m_padViewMenuItem->Check(wxConfig.pad_open); optionsMenu->AppendSeparator(); #if BOOST_OS_MACOS optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, _("&Settings..." "\tCtrl-,")); #endif optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, _("&General settings")); optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_INPUT, _("&Input settings")); optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_HOTKEY, _("&Hotkey settings")); optionsMenu->AppendSeparator(); optionsMenu->AppendSubMenu(m_optionsAccountMenu, _("&Active account")); optionsMenu->AppendSubMenu(optionsConsoleLanguageMenu, _("&Console language")); m_menuBar->Append(optionsMenu, _("&Options")); // tools submenu wxMenu* toolsMenu = new wxMenu(); m_memorySearcherMenuItem = toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, _("&Memory searcher")); m_memorySearcherMenuItem->Enable(false); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager")); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, _("&Download Manager")); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, _("&Emulated USB Devices")); m_menuBar->Append(toolsMenu, _("&Tools")); // cpu timer speed menu wxMenu* timerSpeedMenu = new wxMenu(); timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_1X, _("&1x speed"))->Check(ActiveSettings::GetTimerShiftFactor() == 3); timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_2X, _("&2x speed"))->Check(ActiveSettings::GetTimerShiftFactor() == 2); timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_4X, _("&4x speed"))->Check(ActiveSettings::GetTimerShiftFactor() == 1); timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_8X, _("&8x speed"))->Check(ActiveSettings::GetTimerShiftFactor() == 0); timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_05X, _("&0.5x speed"))->Check(ActiveSettings::GetTimerShiftFactor() == 4); timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_025X, _("&0.25x speed"))->Check(ActiveSettings::GetTimerShiftFactor() == 5); timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_0125X, _("&0.125x speed"))->Check(ActiveSettings::GetTimerShiftFactor() == 6); // cpu submenu wxMenu* cpuMenu = new wxMenu(); cpuMenu->AppendSubMenu(timerSpeedMenu, _("&Timer speed")); m_menuBar->Append(cpuMenu, _("&CPU")); // nfc submenu wxMenu* nfcMenu = new wxMenu(); m_nfcMenu = nfcMenu; nfcMenu->Append(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, _("&Scan NFC tag/amiibo from file"))->Enable(false); m_menuBar->Append(nfcMenu, _("&NFC")); m_nfcMenuSeparator0 = nullptr; // debug->logging submenu wxMenu* debugLoggingMenu = new wxMenu(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::UnsupportedAPI), _("&Unsupported API calls"))->Check(cemuLog_isLoggingEnabled(LogType::UnsupportedAPI)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::APIErrors), _("&Invalid API usage"))->Check(cemuLog_isLoggingEnabled(LogType::APIErrors)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitLogging), _("&Coreinit Logging (OSReport/OSConsole)"))->Check(cemuLog_isLoggingEnabled(LogType::CoreinitLogging)); debugLoggingMenu->AppendSeparator(); wxMenu* logCosModulesMenu = new wxMenu(); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING_MESSAGE, _("&Options below are for experts. Leave off if unsure"))->Enable(false); logCosModulesMenu->AppendSeparator(); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitFile), _("coreinit File-Access API"))->Check(cemuLog_isLoggingEnabled(LogType::CoreinitFile)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThreadSync), _("coreinit Thread-Synchronization API"))->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThreadSync)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMem), _("coreinit Memory API"))->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMem)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMP), _("coreinit MP API"))->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMP)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("coreinit Thread API"))->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("nn_save API"))->Check(cemuLog_isLoggingEnabled(LogType::Save)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("nn_nfp API"))->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("nn_fp API"))->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::PRUDP), _("nn_fp PRUDP"))->Check(cemuLog_isLoggingEnabled(LogType::PRUDP)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("nn_boss API"))->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NFC), _("nfc API"))->Check(cemuLog_isLoggingEnabled(LogType::NFC)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NTAG), _("ntag API"))->Check(cemuLog_isLoggingEnabled(LogType::NTAG)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("nsysnet API"))->Check(cemuLog_isLoggingEnabled(LogType::Socket)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("h264 API"))->Check(cemuLog_isLoggingEnabled(LogType::H264)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("gx2 API"))->Check(cemuLog_isLoggingEnabled(LogType::GX2)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("Audio API"))->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("Input API"))->Check(cemuLog_isLoggingEnabled(LogType::InputAPI)); debugLoggingMenu->AppendSubMenu(logCosModulesMenu, _("&CafeOS modules logging")); debugLoggingMenu->AppendSeparator(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Patches), _("&Graphic pack patches"))->Check(cemuLog_isLoggingEnabled(LogType::Patches)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureCache), _("&Texture cache warnings"))->Check(cemuLog_isLoggingEnabled(LogType::TextureCache)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureReadback), _("&Texture readback"))->Check(cemuLog_isLoggingEnabled(LogType::TextureReadback)); debugLoggingMenu->AppendSeparator(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::OpenGLLogging), _("&OpenGL debug output"))->Check(cemuLog_isLoggingEnabled(LogType::OpenGLLogging)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::VulkanValidation), _("&Vulkan validation layer (slow)"))->Check(cemuLog_isLoggingEnabled(LogType::VulkanValidation)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO, _("&Log PPC context for API"))->Check(cemuLog_advancedPPCLoggingEnabled()); m_loggingSubmenu = debugLoggingMenu; // debug->dump submenu wxMenu* debugDumpMenu = new wxMenu; debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, _("&Textures"))->Check(ActiveSettings::DumpTexturesEnabled()); debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, _("&Shaders"))->Check(ActiveSettings::DumpShadersEnabled()); debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, _("&Recompiled functions"))->Check(ActiveSettings::DumpRecompilerFunctionsEnabled()); debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, _("&nlibcurl HTTP/HTTPS requests")); // debug submenu wxMenu* debugMenu = new wxMenu(); m_debugMenu = debugMenu; debugMenu->AppendSubMenu(debugLoggingMenu, _("&Logging")); debugMenu->AppendSubMenu(debugDumpMenu, _("&Dump")); debugMenu->AppendSeparator(); auto upsidedownItem = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, _("&Render upside-down")); upsidedownItem->Check(ActiveSettings::RenderUpsideDownEnabled()); if(LaunchSettings::RenderUpsideDownEnabled().has_value()) upsidedownItem->Enable(false); auto accurateBarriers = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, _("&Accurate barriers (Vulkan)")); accurateBarriers->Check(GetConfig().vk_accurate_barriers); #if ENABLE_METAL auto gpuCapture = debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_GPU_CAPTURE, _("&GPU capture (Metal)")); gpuCapture->Enable(m_game_launched && g_renderer->GetType() == RendererAPI::Metal); #endif debugMenu->AppendSeparator(); #ifdef CEMU_DEBUG_ASSERT auto audioAuxOnly = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY, _("&Audio AUX only")); audioAuxOnly->Check(ActiveSettings::AudioOutputOnlyAux()); #endif debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW, _("&Open logging window")); m_gdbstub_toggle = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_TOGGLE_GDB_STUB, _("&Launch with GDB stub")); m_gdbstub_toggle->Check(g_gdbstub != nullptr); m_gdbstub_toggle->Enable(!m_game_launched); debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, _("&View PPC threads")); debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, _("&View PPC debugger")); debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, _("&View audio debugger")); debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, _("&View texture cache info")); debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, _("&Dump current RAM")); // debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_DUMP_FST, _("&Dump WUD filesystem"))->Enable(false); m_menuBar->Append(debugMenu, _("&Debug")); // help menu wxMenu* helpMenu = new wxMenu(); m_check_update_menu = helpMenu->Append(MAINFRAME_MENU_ID_HELP_UPDATE, _("&Check for updates")); #if BOOST_OS_LINUX if (!std::getenv("APPIMAGE")) { m_check_update_menu->Enable(false); } #elif BOOST_OS_BSD // BSD users must update from source so disable update checks m_check_update_menu->Enable(false); #endif helpMenu->AppendSeparator(); helpMenu->Append(MAINFRAME_MENU_ID_HELP_ABOUT, _("&About Cemu")); m_menuBar->Append(helpMenu, _("&Help")); SetMenuBar(m_menuBar); m_menu_visible = true; if (m_game_launched) { if (m_check_update_menu) m_check_update_menu->Enable(false); m_memorySearcherMenuItem->Enable(true); m_nfcMenu->Enable(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, true); // these options cant be toggled after the renderer backend is initialized: m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::OpenGLLogging), false); m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::VulkanValidation), false); UpdateNFCMenu(); } // hide new menu in fullscreen if (IsFullScreen()) SetMenuVisible(false); } void MainWindow::UpdateChildWindowTitleRunningState() { const bool running = CafeSystem::IsTitleRunning(); if(m_graphic_pack_window) m_graphic_pack_window->UpdateTitleRunning(running); } void MainWindow::RestoreSettingsAfterGameExited() { RecreateMenu(); } void MainWindow::UpdateSettingsAfterGameLaunch() { m_update_available = {}; RecreateMenu(); } void MainWindow::OnGraphicWindowClose(wxCloseEvent& event) { m_graphic_pack_window->Destroy(); m_graphic_pack_window = nullptr; } void MainWindow::OnGraphicWindowOpen(wxTitleIdEvent& event) { if (m_graphic_pack_window) return; m_graphic_pack_window = new GraphicPacksWindow2(this, event.GetTitleId()); m_graphic_pack_window->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnGraphicWindowClose, this); m_graphic_pack_window->Show(true); } void MainWindow::RequestGameListRefresh() { auto* evt = new wxCommandEvent(wxEVT_REQUEST_GAMELIST_REFRESH); wxQueueEvent(g_mainFrame, evt); } void MainWindow::RequestLaunchGame(fs::path filePath, wxLaunchGameEvent::INITIATED_BY initiatedBy) { wxLaunchGameEvent evt(filePath, initiatedBy); wxPostEvent(g_mainFrame, evt); } void MainWindow::OnRequestRecreateCanvas(wxCommandEvent& event) { CounterSemaphore* sem = (CounterSemaphore*)event.GetClientData(); DestroyCanvas(); CreateCanvas(); sem->increment(); } void MainWindow::CafeRecreateCanvas() { CounterSemaphore sem; auto* evt = new wxCommandEvent(wxEVT_REQUEST_RECREATE_CANVAS); evt->SetClientData((void*)&sem); wxQueueEvent(g_mainFrame, evt); sem.decrementWithWait(); } bool MainWindow::FullscreenEnabled() const { return LaunchSettings::FullscreenEnabled().value_or(GetWxGUIConfig().fullscreen); } ================================================ FILE: src/gui/wxgui/MainWindow.h ================================================ #pragma once #include #include #include #include "wxgui/PadViewFrame.h" #include "wxgui/MemorySearcherTool.h" #include "config/XMLConfig.h" #include "wxgui/LoggingWindow.h" #include "wxgui/components/wxGameList.h" #include #include "Cafe/HW/Espresso/Debugger/GDBStub.h" #include "Cafe/CafeSystem.h" class DebuggerWindow2; struct GameEntry; class DiscordPresence; class TitleManager; class GraphicPacksWindow2; class EmulatedUSBDeviceFrame; class wxLaunchGameEvent; wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); wxDECLARE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent); class wxLaunchGameEvent : public wxCommandEvent { public: enum class INITIATED_BY { MENU, // via file menu DRAG_AND_DROP, GAME_LIST, TITLE_MANAGER, COMMAND_LINE, // -g parameter }; wxLaunchGameEvent(fs::path path, INITIATED_BY initiatedBy) : wxCommandEvent(wxEVT_LAUNCH_GAME), m_launchPath(path), m_initiatedBy(initiatedBy) {} [[nodiscard]] fs::path GetPath() const { return m_launchPath; } [[nodiscard]] INITIATED_BY GetInitiatedBy() const { return m_initiatedBy; } wxEvent* Clone() const { return new wxLaunchGameEvent(*this); } private: fs::path m_launchPath; INITIATED_BY m_initiatedBy; }; class MainWindow : public wxFrame, public CafeSystem::SystemImplementation { friend class CemuApp; public: MainWindow(); ~MainWindow(); void CreateGameListAndStatusBar(); void DestroyGameListAndStatusBar(); void UpdateSettingsAfterGameLaunch(); void RestoreSettingsAfterGameExited(); bool FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATED_BY initiatedBy); [[nodiscard]] bool IsGameLaunched() const { return m_game_launched; } void SetFullScreen(bool state); void EndEmulation(); void SetMenuVisible(bool state); void UpdateNFCMenu(); bool IsMenuHidden() const; void TogglePadView(); #if BOOST_OS_WINDOWS WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override; #endif void OpenSettings(); PadViewFrame* GetPadView() const { return m_padView; } void OnSizeEvent(wxSizeEvent& event); void OnDPIChangedEvent(wxDPIChangedEvent& event); void OnMove(wxMoveEvent& event); void OnDebuggerClose(wxCloseEvent& event); void OnPadClose(wxCloseEvent& event); void OnMemorySearcherClose(wxCloseEvent& event); void OnMouseWheel(wxMouseEvent& event); void OnClose(wxCloseEvent& event); void OnFileMenu(wxCommandEvent& event); void OnOpenFolder(wxCommandEvent& event); void OnClearSpotPassCache(wxCommandEvent& event); void OnLaunchFromFile(wxLaunchGameEvent& event); void OnInstallUpdate(wxCommandEvent& event); void OnFileExit(wxCommandEvent& event); void OnNFCMenu(wxCommandEvent& event); void OnOptionsInput(wxCommandEvent& event); void OnAccountSelect(wxCommandEvent& event); void OnConsoleLanguage(wxCommandEvent& event); void OnHelpAbout(wxCommandEvent& event); void OnHelpUpdate(wxCommandEvent& event); void OnDebugSetting(wxCommandEvent& event); void OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event); void OnPPCInfoToggle(wxCommandEvent& event); void OnDebugDumpGeneric(wxCommandEvent& event); void OnLoggingWindow(wxCommandEvent& event); void OnGDBStubToggle(wxCommandEvent& event); void OnDebugViewPPCThreads(wxCommandEvent& event); void OnDebugViewPPCDebugger(wxCommandEvent& event); void OnDebugViewAudioDebugger(wxCommandEvent& event); void OnDebugViewTextureRelations(wxCommandEvent& event); void OnMouseMove(wxMouseEvent& event); void OnMouseLeft(wxMouseEvent& event); void OnMouseRight(wxMouseEvent& event); void OnGameListBeginUpdate(wxCommandEvent& event); void OnGameListEndUpdate(wxCommandEvent& event); void OnAccountListRefresh(wxCommandEvent& event); void OnRequestGameListRefresh(wxCommandEvent& event); void OnSetWindowTitle(wxCommandEvent& event); void OnKeyUp(wxKeyEvent& event); void OnKeyDown(wxKeyEvent& event); void OnChar(wxKeyEvent& event); void OnToolsInput(wxCommandEvent& event); void OnGesturePan(wxPanGestureEvent& event); void OnGameLoaded(); void AsyncSetTitle(std::string_view windowTitle); void CreateCanvas(); void DestroyCanvas(); static void ShowCursor(bool state); uintptr_t GetRenderCanvasHWND(); static void RequestGameListRefresh(); static void RequestLaunchGame(fs::path filePath, wxLaunchGameEvent::INITIATED_BY initiatedBy); private: bool FullscreenEnabled() const; void RecreateMenu(); void UpdateChildWindowTitleRunningState(); static wxString GetInitialWindowTitle(); bool InstallUpdate(const fs::path& metaFilePath); void OnTimer(wxTimerEvent& event); // CafeSystem implementation void CafeRecreateCanvas() override; void OnRequestRecreateCanvas(wxCommandEvent& event); wxRect GetDesktopRect(); MemorySearcherTool* m_toolWindow = nullptr; TitleManager* m_title_manager = nullptr; EmulatedUSBDeviceFrame* m_usb_devices = nullptr; PadViewFrame* m_padView = nullptr; GraphicPacksWindow2* m_graphic_pack_window = nullptr; wxTimer* m_timer; wxPoint m_mouse_position{}; std::chrono::steady_clock::time_point m_last_mouse_move_time; wxSize m_restored_size; wxPoint m_restored_position; bool m_menu_visible = false; bool m_game_launched = false; #ifdef ENABLE_DISCORD_RPC std::unique_ptr m_discord; #endif std::string m_launched_game_name; wxMenuItem* m_gdbstub_toggle{}; DebuggerWindow2* m_debugger_window = nullptr; LoggingWindow* m_logging_window = nullptr; std::future m_update_available; wxMenuItem* m_check_update_menu{}; void LoadSettings(); void SaveSettings(); void OnGraphicWindowClose(wxCloseEvent& event); void OnGraphicWindowOpen(wxTitleIdEvent& event); // panels wxPanel* m_main_panel{}, * m_game_panel{}; // rendering wxWindow* m_render_canvas{}; // gamelist wxGameList* m_game_list{}; wxInfoBar* m_info_bar{}; // menu wxMenuBar* m_menuBar{}; // file wxMenu* m_fileMenu{}; wxMenuItem* m_fileMenuSeparator0{}; wxMenuItem* m_fileMenuSeparator1{}; wxMenuItem* m_loadMenuItem{}; wxMenuItem* m_installUpdateMenuItem{}; wxMenuItem* m_exitMenuItem{}; // options wxMenu* m_optionsAccountMenu{}; wxMenuItem* m_fullscreenMenuItem{}; wxMenuItem* m_padViewMenuItem{}; // tools wxMenuItem* m_memorySearcherMenuItem{}; // cpu wxMenu* m_cpuTimerSubmenu{}; // nfc wxMenu* m_nfcMenu{}; wxMenuItem* m_nfcMenuSeparator0{}; // debug wxMenu* m_debugMenu{}; wxMenu* m_loggingSubmenu{}; wxMenuItem* m_asyncCompile{}; wxDECLARE_EVENT_TABLE(); }; extern MainWindow* g_mainFrame; ================================================ FILE: src/gui/wxgui/MemorySearcherTool.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/MemorySearcherTool.h" #include #include #include #include #include "config/ActiveSettings.h" #include "wxgui/helpers/wxHelpers.h" #include "Common/FileStream.h" #include "util/IniParser/IniParser.h" #include "util/helpers/StringHelpers.h" #include "Cafe/CafeSystem.h" enum { COMBOBOX_DATATYPE = wxID_HIGHEST + 1, TEXT_VALUE, BUTTON_START, BUTTON_FILTER, LIST_RESULTS, LIST_ENTRYTABLE, TIMER_REFRESH, LIST_ENTRY_ADD, LIST_ENTRY_REMOVE, }; wxDEFINE_EVENT(wxEVT_SEARCH_FINISHED, wxCommandEvent); wxBEGIN_EVENT_TABLE(MemorySearcherTool, wxFrame) EVT_CLOSE(MemorySearcherTool::OnClose) EVT_BUTTON(BUTTON_START, MemorySearcherTool::OnSearch) EVT_BUTTON(BUTTON_FILTER, MemorySearcherTool::OnFilter) EVT_TIMER(TIMER_REFRESH, MemorySearcherTool::OnTimerTick) wxEND_EVENT_TABLE() constexpr auto kMaxResultCount = 5000; const wxString kDatatypeFloat = "float"; const wxString kDatatypeDouble = "double"; const wxString kDatatypeString = "string"; const wxString kDatatypeInt8 = "int8"; const wxString kDatatypeInt16 = "int16"; const wxString kDatatypeInt32 = "int32"; const wxString kDatatypeInt64 = "int64"; const wxString kDataTypeNames[] = {kDatatypeFloat,kDatatypeDouble,/*DATATYPE_STRING,*/kDatatypeInt8,kDatatypeInt16,kDatatypeInt32,kDatatypeInt64}; MemorySearcherTool::MemorySearcherTool(wxFrame* parent) : wxFrame(parent, wxID_ANY, _("Memory Searcher"), wxDefaultPosition, wxSize(600, 540), wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL) { this->SetSizeHints(wxDefaultSize, wxDefaultSize); this->wxTopLevelWindowBase::SetMinSize(wxSize(600, 540)); auto* sizer = new wxBoxSizer(wxVERTICAL); auto* row1 = new wxFlexGridSizer(0, 4, 0, 0); row1->AddGrowableCol(1); m_cbDataType = new wxComboBox(this, COMBOBOX_DATATYPE, kDatatypeFloat, wxDefaultPosition, wxDefaultSize, std::size(kDataTypeNames), kDataTypeNames, wxCB_READONLY); m_textValue = new wxTextCtrl(this, TEXT_VALUE); m_buttonStart = new wxButton(this, BUTTON_START, _("Search")); m_buttonFilter = new wxButton(this, BUTTON_FILTER, _("Filter")); m_buttonFilter->Disable(); row1->Add(m_cbDataType, 0, wxALL, 5); row1->Add(m_textValue, 0, wxALL | wxEXPAND, 5); row1->Add(m_buttonStart, 0, wxALL, 5); row1->Add(m_buttonFilter, 0, wxALL, 5); sizer->Add(row1, 0, wxEXPAND, 5); auto* row2 = new wxFlexGridSizer(0, 1, 0, 0); row2->AddGrowableCol(0); m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL); m_gauge->SetValue(0); m_gauge->Enable(false); m_textEntryTable = new wxStaticText(this, wxID_ANY, _("Results")); m_listResults = new wxListView(this, LIST_RESULTS, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SORT_ASCENDING); m_listResults->Bind(wxEVT_LEFT_DCLICK, &MemorySearcherTool::OnResultListClick, this); { wxListItem col0; col0.SetId(0); col0.SetText(_("Address")); col0.SetWidth(100); m_listResults->InsertColumn(0, col0); wxListItem col1; col1.SetId(1); col1.SetText(_("Value")); col1.SetWidth(250); m_listResults->InsertColumn(1, col1); } auto textEntryTable = new wxStaticText(this, wxID_ANY, _("Stored Entries")); m_listEntryTable = new wxDataViewListCtrl(this, LIST_ENTRYTABLE, wxDefaultPosition, wxSize(420, 200), wxDV_HORIZ_RULES); m_listEntryTable->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &MemorySearcherTool::OnEntryListRightClick, this); m_listEntryTable->Bind(wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_DONE, &MemorySearcherTool::OnItemEdited, this); { m_listEntryTable->AppendTextColumn(_("Description"), wxDATAVIEW_CELL_EDITABLE, 150, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE); m_listEntryTable->AppendTextColumn(_("Address"), wxDATAVIEW_CELL_INERT, 100, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE); m_listEntryTable->AppendTextColumn(_("Type")); m_listEntryTable->AppendTextColumn(_("Value"), wxDATAVIEW_CELL_EDITABLE); m_listEntryTable->AppendToggleColumn(_("Freeze"), wxDATAVIEW_CELL_ACTIVATABLE, 50, wxALIGN_LEFT, 0); } row2->AddGrowableRow(3); row2->AddGrowableRow(5); row2->Add(m_gauge, 0, wxALL | wxEXPAND, 5); row2->Add(0, 10, 1, wxEXPAND, 5); row2->Add(m_textEntryTable, 0, wxALL, 5); row2->Add(m_listResults, 1, wxALL | wxEXPAND, 5); row2->Add(textEntryTable, 0, wxALL, 5); row2->Add(m_listEntryTable, 1, wxALL | wxEXPAND, 5); sizer->Add(row2, 1, wxEXPAND, 5); // load stored entries Load(); this->Bind(wxEVT_SEARCH_FINISHED, &MemorySearcherTool::OnSearchFinished, this); this->Bind(wxEVT_SET_GAUGE_VALUE, &MemorySearcherTool::OnUpdateGauge, this); m_refresh_timer = new wxTimer(this, TIMER_REFRESH); m_refresh_timer->Start(250); this->SetSizer(sizer); this->wxWindowBase::Layout(); this->Centre(wxBOTH); } MemorySearcherTool::~MemorySearcherTool() { m_refresh_timer->Stop(); m_running = false; if (m_worker.joinable()) m_worker.join(); } void MemorySearcherTool::OnTimerTick(wxTimerEvent& event) { RefreshResultList(); RefreshStashList(); } void MemorySearcherTool::OnClose(wxCloseEvent& event) { Save(); event.Skip(); } void MemorySearcherTool::OnSearchFinished(wxCommandEvent&) { FillResultList(); m_search_running = false; m_buttonStart->Enable(); m_buttonFilter->Enable(); } void MemorySearcherTool::OnUpdateGauge(wxSetGaugeValue& event) { auto* gauge = event.GetGauge(); const auto value = event.GetValue(); if (event.GetRange() != 0) { gauge->SetRange(event.GetRange()); gauge->SetValue(value); return; } debug_printf("update gauge: %d + %d = %d (/%d)\n", gauge->GetValue(), value, gauge->GetValue() + value, gauge->GetRange()); gauge->SetValue(gauge->GetValue() + value); } void MemorySearcherTool::OnSearch(wxCommandEvent&) { if (m_clear_state) { Reset(); return; } if (m_search_running) return; if (m_textValue->IsEmpty()) return; SetSearchDataType(); if (!VerifySearchValue()) { wxMessageBox(_("Your entered value is not valid for the selected datatype."), _("Error"), wxICON_ERROR); return; } m_buttonStart->Disable(); m_buttonFilter->Disable(); m_cbDataType->Disable(); m_buttonStart->SetLabelText(_("Clear")); m_clear_state = true; if (m_worker.joinable()) m_worker.join(); m_worker = std::thread([this]() { m_search_jobs.clear(); uint32 total_size = 0; for (const auto& itr : memory_getMMURanges()) { if (!m_running) return; if (!itr->isMapped()) continue; void* ptr = itr->getPtr(); const uint32 size = itr->getSize(); total_size += (size / kGaugeStep); m_search_jobs.emplace_back(std::async(std::launch::async, [this, ptr, size]() { return SearchValues(m_searchDataType, ptr, size); })); } wxQueueEvent(this, new wxSetGaugeValue(0, total_size, m_gauge)); ListType_t tmp; for (auto& it : m_search_jobs) { const auto result = it.get(); tmp.insert(tmp.end(), result.cbegin(), result.cend()); } std::unique_lock lock(m_mutex); m_searchBuffer.swap(tmp); lock.unlock(); wxQueueEvent(this, new wxCommandEvent(wxEVT_SEARCH_FINISHED)); }); } void MemorySearcherTool::OnFilter(wxCommandEvent& event) { m_buttonStart->Disable(); m_buttonFilter->Disable(); if (m_worker.joinable()) m_worker.join(); m_worker = std::thread([this]() { const auto count = (uint32)m_searchBuffer.size(); wxQueueEvent(this, new wxSetGaugeValue(0, count, m_gauge)); auto tmp = FilterValues(m_searchDataType); std::unique_lock lock(m_mutex); m_searchBuffer.swap(tmp); lock.unlock(); wxQueueEvent(this, new wxCommandEvent(wxEVT_SEARCH_FINISHED)); }); m_gauge->SetValue(0); } void MemorySearcherTool::Load() { const auto memorySearcherPath = ActiveSettings::GetUserDataPath("memorySearcher/{:016x}.ini", CafeSystem::GetForegroundTitleId()); auto memSearcherIniContents = FileStream::LoadIntoMemory(memorySearcherPath); if (!memSearcherIniContents) return; IniParser iniParser(*memSearcherIniContents, _pathToUtf8(memorySearcherPath)); while (iniParser.NextSection()) { auto option_description = iniParser.FindOption("description"); auto option_address = iniParser.FindOption("address"); auto option_type = iniParser.FindOption("type"); auto option_value = iniParser.FindOption("value"); if (!option_description || !option_address || !option_type || !option_value) continue; try { const auto addr = StringHelpers::ToInt64(*option_address); if (!IsAddressValid(addr)) continue; } catch (const std::invalid_argument&) { continue; } bool found = false; for (const auto& entry : kDataTypeNames) { if (boost::iequals(entry.ToStdString(), *option_type)) { found = true; break; } } if (!found && !boost::iequals(kDatatypeString.ToStdString(), *option_type)) continue; wxVector data; data.push_back(std::string(*option_description).c_str()); data.push_back(std::string(*option_address).c_str()); data.push_back(std::string(*option_type).c_str()); data.push_back(std::string(*option_value).c_str()); data.push_back(!option_value->empty()); m_listEntryTable->AppendItem(data); } } void MemorySearcherTool::Save() { const auto memorySearcherPath = ActiveSettings::GetUserDataPath("memorySearcher/{:016x}.ini", CafeSystem::GetForegroundTitleId()); FileStream* fs = FileStream::createFile2(memorySearcherPath); if (fs) { for (int i = 0; i < m_listEntryTable->GetItemCount(); ++i) { fs->writeLine("[Entry]"); std::string tmp = "description=" + m_listEntryTable->GetTextValue(i, 0).ToStdString(); fs->writeLine(tmp.c_str()); tmp = "address=" + m_listEntryTable->GetTextValue(i, 1).ToStdString(); fs->writeLine(tmp.c_str()); tmp = "type=" + m_listEntryTable->GetTextValue(i, 2).ToStdString(); fs->writeLine(tmp.c_str()); // only save value when FREEZE is active if (m_listEntryTable->GetToggleValue(i, 4)) { tmp = "value=" + m_listEntryTable->GetTextValue(i, 3).ToStdString(); } else { tmp = "value="; } fs->writeLine(tmp.c_str()); fs->writeLine(""); } delete fs; } } bool MemorySearcherTool::IsAddressValid(uint32 addr) { for (const auto& itr : memory_getMMURanges()) { if (!itr->isMapped()) continue; void* ptr = itr->getPtr(); const uint32 size = itr->getSize(); MEMPTR start((uint8*)ptr); MEMPTR end((uint8*)ptr + size); if (start.GetMPTR() <= addr && addr < end.GetMPTR()) return true; } return false; } void MemorySearcherTool::OnEntryListRightClick(wxDataViewEvent& event) { //void *data = reinterpret_cast(event.GetItem().GetData()); wxMenu mnu; //mnu.SetClientData(data); mnu.Append(LIST_ENTRY_ADD, _("&Add new entry"))->Enable(false); mnu.Append(LIST_ENTRY_REMOVE, _("&Remove entry")); mnu.Bind(wxEVT_COMMAND_MENU_SELECTED, &MemorySearcherTool::OnPopupClick, this); PopupMenu(&mnu); } void MemorySearcherTool::OnResultListClick(wxMouseEvent& event) { for (long selectedIndex = m_listResults->GetFirstSelected(); selectedIndex != wxNOT_FOUND; selectedIndex = m_listResults->GetNextSelected(selectedIndex)) { long address = m_listResults->GetItemData(selectedIndex); auto currValue = m_listResults->GetItemText(selectedIndex, 1); wxString addressString = wxString::Format("0x%08lx", address); // description, address, type, value, freeze wxVector data; data.push_back(""); data.push_back(addressString); data.push_back(m_cbDataType->GetValue()); data.push_back(currValue); data.push_back(false); m_listEntryTable->AppendItem(data); } } void MemorySearcherTool::Reset() { m_searchBuffer.clear(); m_buttonStart->SetLabelText(_("Search")); m_textEntryTable->SetLabelText(_("Results")); m_buttonFilter->Disable(); m_cbDataType->Enable(); m_textValue->SetValue(""); m_listResults->DeleteAllItems(); m_clear_state = false; } bool MemorySearcherTool::VerifySearchValue() const { const auto inputString = m_textValue->GetValue().ToStdString(); switch (m_searchDataType) { case SearchDataType_String: return true; case SearchDataType_Float: { float value; return ConvertStringToType(inputString, value); } case SearchDataType_Double: { double value; return ConvertStringToType(inputString, value); } case SearchDataType_Int8: { sint8 value; return ConvertStringToType(inputString, value); } case SearchDataType_Int16: { sint16 value; return ConvertStringToType(inputString, value); } case SearchDataType_Int32: { sint32 value; return ConvertStringToType(inputString, value); } case SearchDataType_Int64: { sint64 value; return ConvertStringToType(inputString, value); } default: return false; } } void MemorySearcherTool::FillResultList() { auto text = formatWxString(_("Results ({0})"), m_searchBuffer.size()); m_textEntryTable->SetLabelText(text); m_listResults->DeleteAllItems(); if (m_searchBuffer.empty() || m_searchBuffer.size() > kMaxResultCount) return; for (const auto& address : m_searchBuffer) { const auto index = m_listResults->InsertItem(0, fmt::format("{:#08x}", address.GetMPTR())); m_listResults->SetItemData(index, address.GetMPTR()); m_listResults->SetItem(index, 1, m_textValue->GetValue()); } } void MemorySearcherTool::RefreshResultList() { std::unique_lock lock(m_mutex); if (m_searchBuffer.empty() || m_searchBuffer.size() > kMaxResultCount) return; for (int i = 0; i < m_listResults->GetItemCount(); ++i) { const auto addr = m_listResults->GetItemData(i); switch (m_searchDataType) { case SearchDataType_String: { // TODO Peter break; } case SearchDataType_Float: { const auto value = memory_read(addr); m_listResults->SetItem(i, 1, fmt::format("{}", value)); break; } case SearchDataType_Double: { const auto value = memory_read(addr); m_listResults->SetItem(i, 1, fmt::format("{}", value)); break; } case SearchDataType_Int8: { const auto value = memory_read(addr); m_listResults->SetItem(i, 1, fmt::format("{}", value)); break; } case SearchDataType_Int16: { const auto value = memory_read(addr); m_listResults->SetItem(i, 1, fmt::format("{}", value)); break; } case SearchDataType_Int32: { const auto value = memory_read(addr); m_listResults->SetItem(i, 1, fmt::format("{}", value)); break; } case SearchDataType_Int64: { const auto value = memory_read(addr); m_listResults->SetItem(i, 1, fmt::format("{}", value)); break; } } } } void MemorySearcherTool::RefreshStashList() { for (int i = 0; i < m_listEntryTable->GetItemCount(); ++i) { auto freeze = m_listEntryTable->GetToggleValue(i, 4); auto addressText = m_listEntryTable->GetTextValue(i, 1).ToStdString(); auto type = m_listEntryTable->GetTextValue(i, 2); auto address = std::stol(addressText, nullptr, 16); // if freeze is activated, set the value instead of refreshing it if (freeze) { wxVariant var; m_listEntryTable->GetValue(var, i, 3); if (type == kDatatypeFloat) { auto value = (float)var.GetDouble(); memory_writeFloat(address, value); } else if (type == kDatatypeDouble) { auto value = var.GetDouble(); memory_writeDouble(address, value); } else if (type == kDatatypeInt8) { auto value = var.GetInteger(); memory_writeU8(address, value); } else if (type == kDatatypeInt16) { auto value = var.GetInteger(); memory_writeU16(address, value); } else if (type == kDatatypeInt32) { auto value = var.GetInteger(); memory_writeU32(address, value); } else if (type == kDatatypeInt64) { auto valueText = var.GetString().ToStdString(); auto value = std::stoull(valueText); memory_writeU64(address, value); } else if (type == kDatatypeString) { auto valueText = var.GetString().ToStdString(); for (int i = 0; i < valueText.size(); ++i) { memory_writeU8(address + i, valueText[i]); } memory_writeU8(address + valueText.size(), 0x00); } } else { if (type == kDatatypeFloat) { auto value = memory_readFloat(address); m_listEntryTable->SetValue(fmt::format("{}", value), i, 3); } else if (type == kDatatypeDouble) { auto value = memory_readDouble(address); m_listEntryTable->SetValue(fmt::format("{}", value), i, 3); } else if (type == kDatatypeInt8) { auto value = (sint8)memory_readU8(address); m_listEntryTable->SetValue(fmt::format("{}", (int)value), i, 3); } else if (type == kDatatypeInt16) { auto value = (sint16)memory_readU16(address); m_listEntryTable->SetValue(fmt::format("{}", value), i, 3); } else if (type == kDatatypeInt32) { auto value = (sint32)memory_readU32(address); m_listEntryTable->SetValue(fmt::format("{}", value), i, 3); } else if (type == kDatatypeInt64) { auto value = (sint64)memory_readU64(address); m_listEntryTable->SetValue(fmt::format("{}", value), i, 3); } else if (type == kDatatypeString) { // TODO Peter } } } } void MemorySearcherTool::SetSearchDataType() { const auto type = m_cbDataType->GetStringSelection(); if (type == kDatatypeFloat) m_searchDataType = SearchDataType_Float; else if (type == kDatatypeDouble) m_searchDataType = SearchDataType_Double; else if (type == kDatatypeInt8) m_searchDataType = SearchDataType_Int8; else if (type == kDatatypeInt16) m_searchDataType = SearchDataType_Int16; else if (type == kDatatypeInt32) m_searchDataType = SearchDataType_Int32; else if (type == kDatatypeInt64) m_searchDataType = SearchDataType_Int64; else if (type == kDatatypeString) m_searchDataType = SearchDataType_String; else m_searchDataType = SearchDataType_None; } template<> bool MemorySearcherTool::ConvertStringToType(const std::string& inValue, sint8& outValue) const { sint16 tmp; std::istringstream iss(inValue); iss >> std::noskipws >> tmp; if (iss && iss.eof()) { if (SCHAR_MIN <= tmp && tmp <= SCHAR_MAX) { outValue = tmp; return true; } } return false; } void MemorySearcherTool::OnPopupClick(wxCommandEvent& event) { if (event.GetId() == LIST_ENTRY_REMOVE) { const int row = m_listEntryTable->GetSelectedRow(); if (row == wxNOT_FOUND) return; m_listEntryTable->DeleteItem(row); } } void MemorySearcherTool::OnItemEdited(wxDataViewEvent& event) { auto column = event.GetColumn(); // Edit description if (column == 0) { } // Edit value else if (column == 3) { auto row = m_listEntryTable->GetSelectedRow(); if (row == wxNOT_FOUND) return; auto addressText = m_listEntryTable->GetTextValue(row, 1).ToStdString(); uint32 address = std::stoul(addressText, nullptr, 16); auto type = m_listEntryTable->GetTextValue(row, 2); if (type == kDatatypeFloat) { auto value = (float)event.GetValue().GetDouble(); memory_writeFloat(address, value); } else if (type == kDatatypeDouble) { auto value = event.GetValue().GetDouble(); memory_writeDouble(address, value); } else if (type == kDatatypeInt8) { auto value = event.GetValue().GetInteger(); memory_writeU8(address, value); } else if (type == kDatatypeInt16) { auto value = event.GetValue().GetInteger(); memory_writeU16(address, value); } else if (type == kDatatypeInt32) { auto value = event.GetValue().GetInteger(); memory_writeU32(address, value); } else if (type == kDatatypeInt64) { auto valueText = event.GetValue().GetString().ToStdString(); auto value = std::stoull(valueText); memory_writeU64(address, value); } else if (type == kDatatypeString) { auto valueText = event.GetValue().GetString().ToStdString(); for (int i = 0; i < valueText.size(); ++i) { memory_writeU8(address + i, valueText[i]); } memory_writeU8(address + valueText.size(), 0x00); } } } ================================================ FILE: src/gui/wxgui/MemorySearcherTool.h ================================================ #pragma once #include #include #include #include #include #include #include "Cafe/HW/MMU/MMU.h" #include "util/helpers/helpers.h" #include "wxgui/helpers/wxCustomEvents.h" class wxComboBox; class wxDataViewEvent; class wxDataViewListCtrl; class wxStaticText; class wxTimer; class wxTimerEvent; enum SearchDataType { SearchDataType_None, SearchDataType_String, SearchDataType_Float, SearchDataType_Double, SearchDataType_Int8, SearchDataType_Int16, SearchDataType_Int32, SearchDataType_Int64, }; struct TableEntry_t { uint32 address; const char description[32]; SearchDataType type; bool freeze; uint16 dataLen; uint8* data; }; class MemorySearcherTool : public wxFrame { public: MemorySearcherTool(wxFrame* parent); virtual ~MemorySearcherTool(); void OnTimerTick(wxTimerEvent& event); void OnClose(wxCloseEvent&); void OnSearch(wxCommandEvent&); void OnSearchFinished(wxCommandEvent& event); void OnUpdateGauge(wxSetGaugeValue& event); void OnFilter(wxCommandEvent& event); void OnResultListClick(wxMouseEvent& event); void OnEntryListRightClick(wxDataViewEvent& event); //void OnEntryListRightClick2(wxContextMenuEvent& event); void OnPopupClick(wxCommandEvent& event); void OnItemEdited(wxDataViewEvent& event); private: void Reset(); bool VerifySearchValue() const; void FillResultList(); void RefreshResultList(); void RefreshStashList(); void SetSearchDataType(); void Load(); void Save(); static bool IsAddressValid(uint32 addr); template bool ConvertStringToType(const std::string& inValue, T& outValue) const { std::istringstream iss(inValue); iss >> std::noskipws >> outValue; return iss && iss.eof(); } using ListType_t = std::vector>; ListType_t SearchValues(SearchDataType type, void* ptr, uint32 size) { /*if (type == SearchDataType_String) return SearchValues((const char* )ptr, size); else */if (type == SearchDataType_Float) return SearchValues((float*)ptr, size); else if (type == SearchDataType_Double) return SearchValues((double*)ptr, size); else if (type == SearchDataType_Int8) return SearchValues((sint8*)ptr, size); else if (type == SearchDataType_Int16) return SearchValues((sint16*)ptr, size); else if (type == SearchDataType_Int32) return SearchValues((sint32*)ptr, size); else if (type == SearchDataType_Int64) return SearchValues((sint64*)ptr, size); return {}; } ListType_t FilterValues(SearchDataType type) { /*if (type == SearchDataType_String) return FilterValues(); else */if (type == SearchDataType_Float) return FilterValues(); else if (type == SearchDataType_Double) return FilterValues(); else if (type == SearchDataType_Int8) return FilterValues(); else if (type == SearchDataType_Int16) return FilterValues(); else if (type == SearchDataType_Int32) return FilterValues(); else if (type == SearchDataType_Int64) return FilterValues(); return {}; } constexpr static int kGaugeStep = 0x10000; template ListType_t SearchValues(T* ptr, uint32 size) { const auto search_value = ConvertString(m_textValue->GetValue().ToStdString()); const auto* end = (T*)((uint8*)ptr + size - sizeof(T)); uint32 counter = 0; std::vector> result; for (; ptr < end; ++ptr) { if (!m_running) return result; const auto tmp = (betype*)ptr; if (equals(search_value, tmp->value())) result.emplace_back(ptr); counter += sizeof(T); if(counter >= kGaugeStep) { wxQueueEvent(this, new wxSetGaugeValue(1, m_gauge)); counter -= kGaugeStep; } } return result; } template ListType_t FilterValues() { const auto search_value = ConvertString(m_textValue->GetValue().ToStdString()); ListType_t newSearchBuffer; newSearchBuffer.reserve(m_searchBuffer.size()); for (const auto& it : m_searchBuffer) { if (!m_running) return newSearchBuffer; const auto tmp = (betype*)it.GetPtr(); if (equals(search_value , tmp->value())) newSearchBuffer.emplace_back(it); wxQueueEvent(this, new wxSetGaugeValue(1, m_gauge)); } return newSearchBuffer; } wxDECLARE_EVENT_TABLE(); SearchDataType m_searchDataType = SearchDataType_None; wxComboBox* m_cbDataType; wxTextCtrl* m_textValue; wxButton *m_buttonStart, *m_buttonFilter; wxListView* m_listResults; wxDataViewListCtrl* m_listEntryTable; wxStaticText* m_textEntryTable; wxGauge* m_gauge; wxTimer* m_refresh_timer; ListType_t m_searchBuffer; std::vector m_tableEntries; std::mutex m_mutex; std::thread m_worker; std::atomic_bool m_running = true; std::atomic_bool m_search_running = false; std::vector> m_search_jobs; bool m_clear_state = false; }; template<> bool MemorySearcherTool::ConvertStringToType(const std::string& inValue, sint8& outValue) const; // //template //void MemorySearcherTool::FilterValues() //{ // auto s1 = m_textValue->GetValue(); // auto s2 = s1.c_str(); // auto stringValue = s2.AsChar(); // // T filterValue; // ConvertStringToType(stringValue, filterValue); // // std::vector newSearchBuffer; // newSearchBuffer.reserve(m_searchBuffer.size()); // // uint32 gaugeValue = 0; // uint32 count = m_searchBuffer.size(); // uint32 counter = 0; // // for (auto it = m_searchBuffer.begin(); it != m_searchBuffer.end(); ++it) // { // if (m_running) // return; // // T value = memory_read(*it); // if constexpr (std::is_same::value) // { // //float value1 = filterValue * 0.95f; // float diff = filterValue - value; // diff = fabs(diff); // if(diff < value*0.05f) // { // newSearchBuffer.push_back(*it); // } // // } // else // { // if (value == filterValue) // { // newSearchBuffer.push_back(*it); // } // } // // ++counter; // uint32 newValue = counter * 100 / count; // if (newValue > gaugeValue + GAUGE_STEP_SIZE) // { // gaugeValue = newValue; // m_gaugeValue = newValue; // } // } // // m_mutex.lock(); // m_searchBuffer = newSearchBuffer; // m_gaugeValue = 100; // m_mutex.unlock(); // m_isSearchFinished = true; //} // ================================================ FILE: src/gui/wxgui/PadViewFrame.cpp ================================================ #include "interface/WindowSystem.h" #include "wxgui/wxgui.h" #include "wxgui/PadViewFrame.h" #include #include "config/ActiveSettings.h" #include "Cafe/OS/libs/swkbd/swkbd.h" #include "wxgui/canvas/OpenGLCanvas.h" #include "wxgui/canvas/VulkanCanvas.h" #if ENABLE_METAL #include "wxgui/canvas/MetalCanvas.h" #endif #include "config/CemuConfig.h" #include "wxgui/MainWindow.h" #include "wxgui/helpers/wxHelpers.h" #include "input/InputManager.h" #if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD #include "resource/embedded/resources.h" #endif #include "wxHelper.h" extern WindowSystem::WindowInfo g_window_info; #define PAD_MIN_WIDTH 320 #define PAD_MIN_HEIGHT 180 PadViewFrame::PadViewFrame(wxFrame* parent) : wxFrame(nullptr, wxID_ANY, _("GamePad View"), wxDefaultPosition, wxDefaultSize, wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxCLOSE_BOX | wxWANTS_CHARS) { g_window_info.window_pad = initHandleContextFromWxWidgetsWindow(this); SetIcon(wxICON(M_WND_ICON128)); wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES); SetMinClientSize({ PAD_MIN_WIDTH, PAD_MIN_HEIGHT }); SetPosition({ g_window_info.restored_pad_x, g_window_info.restored_pad_y }); if (g_window_info.restored_pad_width >= PAD_MIN_WIDTH && g_window_info.restored_pad_height >= PAD_MIN_HEIGHT) SetClientSize({ g_window_info.restored_pad_width, g_window_info.restored_pad_height }); else SetClientSize(wxSize(854, 480)); if (g_window_info.pad_maximized) Maximize(); Bind(wxEVT_SIZE, &PadViewFrame::OnSizeEvent, this); Bind(wxEVT_DPI_CHANGED, &PadViewFrame::OnDPIChangedEvent, this); Bind(wxEVT_MOVE, &PadViewFrame::OnMoveEvent, this); Bind(wxEVT_MOTION, &PadViewFrame::OnMouseMove, this); Bind(wxEVT_SET_WINDOW_TITLE, &PadViewFrame::OnSetWindowTitle, this); g_window_info.pad_open = true; } PadViewFrame::~PadViewFrame() { g_window_info.pad_open = false; } bool PadViewFrame::Initialize() { const wxSize client_size = GetClientSize(); g_window_info.pad_width = client_size.GetWidth(); g_window_info.pad_height = client_size.GetHeight(); g_window_info.phys_pad_width = ToPhys(client_size.GetWidth()); g_window_info.phys_pad_height = ToPhys(client_size.GetHeight()); return true; } void PadViewFrame::InitializeRenderCanvas() { auto sizer = new wxBoxSizer(wxVERTICAL); { if (ActiveSettings::GetGraphicsAPI() == kVulkan) m_render_canvas = new VulkanCanvas(this, wxSize(854, 480), false); else if (ActiveSettings::GetGraphicsAPI() == kOpenGL) m_render_canvas = GLCanvas_Create(this, wxSize(854, 480), false); #if ENABLE_METAL else m_render_canvas = new MetalCanvas(this, wxSize(854, 480), false); #endif sizer->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr); } SetSizer(sizer); Layout(); m_render_canvas->Bind(wxEVT_KEY_UP, &PadViewFrame::OnKeyUp, this); m_render_canvas->Bind(wxEVT_CHAR, &PadViewFrame::OnChar, this); m_render_canvas->Bind(wxEVT_MOTION, &PadViewFrame::OnMouseMove, this); m_render_canvas->Bind(wxEVT_LEFT_DOWN, &PadViewFrame::OnMouseLeft, this); m_render_canvas->Bind(wxEVT_LEFT_UP, &PadViewFrame::OnMouseLeft, this); m_render_canvas->Bind(wxEVT_RIGHT_DOWN, &PadViewFrame::OnMouseRight, this); m_render_canvas->Bind(wxEVT_RIGHT_UP, &PadViewFrame::OnMouseRight, this); m_render_canvas->Bind(wxEVT_GESTURE_PAN, &PadViewFrame::OnGesturePan, this); m_render_canvas->SetFocus(); SendSizeEvent(); } void PadViewFrame::DestroyCanvas() { if(!m_render_canvas) return; m_render_canvas->Destroy(); m_render_canvas = nullptr; } void PadViewFrame::OnSizeEvent(wxSizeEvent& event) { if (!IsMaximized() && !IsFullScreen()) { g_window_info.restored_pad_width = GetSize().x; g_window_info.restored_pad_height = GetSize().y; } g_window_info.pad_maximized = IsMaximized() && !IsFullScreen(); const wxSize client_size = GetClientSize(); g_window_info.pad_width = client_size.GetWidth(); g_window_info.pad_height = client_size.GetHeight(); g_window_info.phys_pad_width = ToPhys(client_size.GetWidth()); g_window_info.phys_pad_height = ToPhys(client_size.GetHeight()); g_window_info.pad_dpi_scale = GetDPIScaleFactor(); event.Skip(); } void PadViewFrame::OnDPIChangedEvent(wxDPIChangedEvent& event) { event.Skip(); const wxSize client_size = GetClientSize(); g_window_info.pad_width = client_size.GetWidth(); g_window_info.pad_height = client_size.GetHeight(); g_window_info.phys_pad_width = ToPhys(client_size.GetWidth()); g_window_info.phys_pad_height = ToPhys(client_size.GetHeight()); g_window_info.pad_dpi_scale = GetDPIScaleFactor(); } void PadViewFrame::OnMoveEvent(wxMoveEvent& event) { if (!IsMaximized() && !IsFullScreen()) { g_window_info.restored_pad_x = GetPosition().x; g_window_info.restored_pad_y = GetPosition().y; } } void PadViewFrame::OnKeyUp(wxKeyEvent& event) { event.Skip(); if (swkbd_hasKeyboardInputHook()) return; const auto code = event.GetKeyCode(); if (code == WXK_ESCAPE) ShowFullScreen(false); else if (code == WXK_RETURN && event.AltDown() || code == WXK_F11) ShowFullScreen(!IsFullScreen()); } void PadViewFrame::OnGesturePan(wxPanGestureEvent& event) { auto& instance = InputManager::instance(); std::scoped_lock lock(instance.m_pad_touch.m_mutex); auto physPos = ToPhys(event.GetPosition()); instance.m_pad_touch.position = { physPos.x, physPos.y }; instance.m_pad_touch.left_down = event.IsGestureStart() || !event.IsGestureEnd(); if (event.IsGestureStart() || !event.IsGestureEnd()) instance.m_pad_touch.left_down_toggle = true; } void PadViewFrame::OnChar(wxKeyEvent& event) { if (swkbd_hasKeyboardInputHook()) swkbd_keyInput(event.GetUnicodeKey()); event.Skip(); } void PadViewFrame::OnMouseMove(wxMouseEvent& event) { auto& instance = InputManager::instance(); std::scoped_lock lock(instance.m_pad_touch.m_mutex); auto physPos = ToPhys(event.GetPosition()); instance.m_pad_mouse.position = { physPos.x, physPos.y }; event.Skip(); } void PadViewFrame::OnMouseLeft(wxMouseEvent& event) { auto& instance = InputManager::instance(); std::scoped_lock lock(instance.m_pad_mouse.m_mutex); instance.m_pad_mouse.left_down = event.ButtonDown(wxMOUSE_BTN_LEFT); auto physPos = ToPhys(event.GetPosition()); instance.m_pad_mouse.position = { physPos.x, physPos.y }; if (event.ButtonDown(wxMOUSE_BTN_LEFT)) instance.m_pad_mouse.left_down_toggle = true; } void PadViewFrame::OnMouseRight(wxMouseEvent& event) { auto& instance = InputManager::instance(); std::scoped_lock lock(instance.m_pad_mouse.m_mutex); instance.m_pad_mouse.right_down = event.ButtonDown(wxMOUSE_BTN_LEFT); auto physPos = ToPhys(event.GetPosition()); instance.m_pad_mouse.position = { physPos.x, physPos.y }; if (event.ButtonDown(wxMOUSE_BTN_RIGHT)) instance.m_pad_mouse.right_down_toggle = true; } void PadViewFrame::OnSetWindowTitle(wxCommandEvent& event) { this->SetTitle(event.GetString()); } void PadViewFrame::AsyncSetTitle(std::string_view windowTitle) { wxCommandEvent set_title_event(wxEVT_SET_WINDOW_TITLE); set_title_event.SetString(wxString::FromUTF8(windowTitle)); QueueEvent(set_title_event.Clone()); } ================================================ FILE: src/gui/wxgui/PadViewFrame.h ================================================ #pragma once #include #define WM_CREATE_PAD (WM_USER+1) #define WM_DESTROY_PAD (WM_USER+2) wxDECLARE_EVENT(EVT_PAD_CLOSE, wxCommandEvent); wxDECLARE_EVENT(EVT_SET_WINDOW_TITLE, wxCommandEvent); class PadViewFrame : public wxFrame { public: PadViewFrame(wxFrame* parent); ~PadViewFrame(); bool Initialize(); void InitializeRenderCanvas(); void DestroyCanvas(); void OnKeyUp(wxKeyEvent& event); void OnChar(wxKeyEvent& event); void AsyncSetTitle(std::string_view windowTitle); private: void OnMouseMove(wxMouseEvent& event); void OnMouseLeft(wxMouseEvent& event); void OnMouseRight(wxMouseEvent& event); void OnSizeEvent(wxSizeEvent& event); void OnDPIChangedEvent(wxDPIChangedEvent& event); void OnMoveEvent(wxMoveEvent& event); void OnGesturePan(wxPanGestureEvent& event); void OnSetWindowTitle(wxCommandEvent& event); wxWindow* m_render_canvas = nullptr; }; ================================================ FILE: src/gui/wxgui/TitleManager.cpp ================================================ #include "wxgui/TitleManager.h" #include "wxgui/helpers/wxCustomEvents.h" #include "wxgui/helpers/wxCustomData.h" #include "Cafe/TitleList/GameInfo.h" #include "util/helpers/helpers.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/wxHelper.h" #include "wxgui/components/wxTitleManagerList.h" #include "wxgui/components/wxDownloadManagerList.h" #include "wxgui/GameUpdateWindow.h" #include "wxgui/dialogs/SaveImport/SaveTransfer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "config/ActiveSettings.h" #include "wxgui/dialogs/SaveImport/SaveImportWindow.h" #include "Cafe/Account/Account.h" #include "Cemu/Tools/DownloadManager/DownloadManager.h" #include "wxgui/CemuApp.h" #include "Cafe/TitleList/TitleList.h" #include "Cafe/TitleList/SaveList.h" #include "resource/embedded/resources.h" wxDEFINE_EVENT(wxEVT_TITLE_FOUND, wxCommandEvent); wxDEFINE_EVENT(wxEVT_TITLE_SEARCH_COMPLETE, wxCommandEvent); wxDEFINE_EVENT(wxEVT_DL_TITLE_UPDATE, wxCommandEvent); wxDEFINE_EVENT(wxEVT_DL_DISCONNECT_COMPLETE, wxCommandEvent); wxPanel* TitleManager::CreateTitleManagerPage() { auto* panel = new wxPanel(m_notebook); auto* sizer = new wxBoxSizer(wxVERTICAL); { auto* row = new wxFlexGridSizer(0, 4, 0, 0); row->AddGrowableCol(1); row->Add(new wxStaticText(panel, wxID_ANY, _("Filter")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_filter = new wxTextCtrl(panel, wxID_ANY); m_filter->Bind(wxEVT_TEXT, &TitleManager::OnFilterChanged, this); row->Add(m_filter, 1, wxALL | wxEXPAND, 5); const wxImage refresh = wxHelper::LoadThemedBitmapFromPNG(PNG_REFRESH_png, sizeof(PNG_REFRESH_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)).ConvertToImage(); m_refresh_button = new wxBitmapButton(panel, wxID_ANY, refresh.Scale(16, 16)); m_refresh_button->Disable(); m_refresh_button->Bind(wxEVT_BUTTON, &TitleManager::OnRefreshButton, this); m_refresh_button->SetToolTip(_("Refresh")); row->Add(m_refresh_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); const wxBitmap help_bitmap = wxHelper::LoadThemedBitmapFromPNG(PNG_HELP_png, sizeof(PNG_HELP_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); auto* help_button = new wxStaticBitmap(panel, wxID_ANY, help_bitmap); help_button->SetToolTip(formatWxString(_("The following prefixes are supported:\n{0}\n{1}\n{2}\n{3}\n{4}"), "titleid:", "name:", "type:", "version:", "region:")); row->Add(help_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); sizer->Add(row, 0, wxEXPAND, 5); } sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5); m_title_list = new wxTitleManagerList(panel); m_title_list->SetSizeHints(800, 600); m_title_list->Bind(wxEVT_LIST_ITEM_SELECTED, &TitleManager::OnTitleSelected, this); sizer->Add(m_title_list, 1, wxALL | wxEXPAND, 5); sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5); { auto* row = new wxFlexGridSizer(0, 3, 0, 0); row->AddGrowableCol(2); auto* install_button = new wxButton(panel, wxID_ANY, _("Install title")); install_button->Bind(wxEVT_BUTTON, &TitleManager::OnInstallTitle, this); row->Add(install_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); row->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL), 0, wxEXPAND | wxALL, 5); { m_save_panel = new wxPanel(panel); auto* save_sizer = new wxFlexGridSizer(0, 7, 0, 0); save_sizer->Add(new wxStaticText(m_save_panel, wxID_ANY, _("Account")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_save_account_list = new wxChoice(m_save_panel, wxID_ANY); m_save_account_list->SetMinSize({ 170, -1 }); m_save_account_list->Bind(wxEVT_CHOICE, &TitleManager::OnSaveAccountSelected, this); save_sizer->Add(m_save_account_list, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* save_open = new wxButton(m_save_panel, wxID_ANY, _("Open directory")); save_open->Bind(wxEVT_BUTTON, &TitleManager::OnSaveOpenDirectory, this); save_open->SetToolTip(_("Open the directory of the save entry")); save_open->Disable(); save_sizer->Add(save_open, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* save_transfer = new wxButton(m_save_panel, wxID_ANY, _("Transfer")); save_transfer->Bind(wxEVT_BUTTON, &TitleManager::OnSaveTransfer, this); save_transfer->SetToolTip(_("Transfers the save entry to another persistent account id")); save_transfer->Disable(); save_sizer->Add(save_transfer, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* save_delete = new wxButton(m_save_panel, wxID_ANY, _("Delete")); save_delete->Bind(wxEVT_BUTTON, &TitleManager::OnSaveDelete, this); save_delete->SetToolTip(_("Permanently delete the save entry")); save_delete->Disable(); save_sizer->Add(save_delete, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_save_import = new wxButton(m_save_panel, wxID_ANY, _("Import")); m_save_import->Bind(wxEVT_BUTTON, &TitleManager::OnSaveImport, this); m_save_import->SetToolTip(_("Imports a zipped save entry")); save_sizer->Add(m_save_import, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxTOP | wxBOTTOM, 5); auto* export_bttn = new wxButton(m_save_panel, wxID_ANY, _("Export")); export_bttn->Bind(wxEVT_BUTTON, &TitleManager::OnSaveExport, this); export_bttn->SetToolTip(_("Exports the selected save entry as zip file")); export_bttn->Disable(); save_sizer->Add(export_bttn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxTOP | wxBOTTOM, 5); m_save_panel->SetSizerAndFit(save_sizer); row->Add(m_save_panel, 1, wxRESERVE_SPACE_EVEN_IF_HIDDEN | wxALIGN_CENTER_VERTICAL, 0); m_save_panel->Hide(); // hide by default } sizer->Add(row, 0, wxEXPAND, 5); } panel->SetSizerAndFit(sizer); return panel; } wxPanel* TitleManager::CreateDownloadManagerPage() { auto* panel = new wxPanel(m_notebook); auto* sizer = new wxBoxSizer(wxVERTICAL); { auto* row = new wxBoxSizer(wxHORIZONTAL); #if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN m_account = new wxChoice(panel, wxID_ANY); m_account->SetMinSize({ 250,-1 }); auto accounts = Account::GetAccounts(); if (!accounts.empty()) { const auto id = GetConfig().account.m_persistent_id.GetValue(); for (const auto& a : accounts) { m_account->Append(a.ToString(), (void*)static_cast(a.GetPersistentId())); if(a.GetPersistentId() == id) { m_account->SetSelection(m_account->GetCount() - 1); } } } row->Add(m_account, 0, wxALL, 5); #endif m_connect = new wxButton(panel, wxID_ANY, _("Connect")); m_connect->Bind(wxEVT_BUTTON, &TitleManager::OnConnect, this); row->Add(m_connect, 0, wxALL, 5); sizer->Add(row, 0, wxEXPAND, 5); } #if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN m_status_text = new wxStaticText(panel, wxID_ANY, _("Select an account and press Connect")); #else if(!NCrypto::HasDataForConsoleCert()) { m_status_text = new wxStaticText(panel, wxID_ANY, _("Valid online files are required to download eShop titles. For more information, go to the Account tab in the General Settings.")); m_connect->Enable(false); } else m_status_text = new wxStaticText(panel, wxID_ANY, _("Click on Connect to load the list of downloadable titles")); #endif this->Bind(wxEVT_SET_TEXT, &TitleManager::OnSetStatusText, this); sizer->Add(m_status_text, 0, wxALL, 5); sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5); { auto* row = new wxFlexGridSizer(0, 3, 0, 0); m_show_titles = new wxCheckBox(panel, wxID_ANY, _("Show available titles")); m_show_titles->SetValue(true); m_show_titles->Enable(false); row->Add(m_show_titles, 0, wxALL, 5); m_show_titles->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this); m_show_updates = new wxCheckBox(panel, wxID_ANY, _("Show available updates")); m_show_updates->SetValue(true); m_show_updates->Enable(false); row->Add(m_show_updates, 0, wxALL, 5); m_show_updates->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this); m_show_installed = new wxCheckBox(panel, wxID_ANY, _("Show installed")); m_show_installed->SetValue(true); m_show_installed->Enable(false); row->Add(m_show_installed, 0, wxALL, 5); m_show_installed->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this); sizer->Add(row, 0, wxEXPAND, 5); } m_download_list = new wxDownloadManagerList(panel); m_download_list->SetSizeHints(800, 600); m_download_list->Bind(wxEVT_LIST_ITEM_SELECTED, &TitleManager::OnTitleSelected, this); sizer->Add(m_download_list, 1, wxALL | wxEXPAND, 5); panel->SetSizerAndFit(sizer); return panel; } TitleManager::TitleManager(wxWindow* parent, TitleManagerPage default_page) : wxFrame(parent, wxID_ANY, _("Title Manager"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL) { SetIcon(wxICON(X_BOX)); auto* sizer = new wxBoxSizer(wxVERTICAL); m_notebook = new wxNotebook(this, wxID_ANY); m_notebook->AddPage(CreateTitleManagerPage(), _("Title Manager"), default_page == TitleManagerPage::TitleManager); m_notebook->AddPage(CreateDownloadManagerPage(), _("Download Manager"), default_page == TitleManagerPage::DownloadManager); sizer->Add(m_notebook, 1, wxEXPAND); m_status_bar = CreateStatusBar(2, wxSTB_SIZEGRIP); m_status_bar->SetStatusText(_("Searching for titles...")); this->SetSizerAndFit(sizer); this->Centre(wxBOTH); this->Bind(wxEVT_SET_STATUS_BAR_TEXT, &TitleManager::OnSetStatusBarText, this); this->Bind(wxEVT_TITLE_FOUND, &TitleManager::OnTitleFound, this); this->Bind(wxEVT_TITLE_SEARCH_COMPLETE, &TitleManager::OnTitleSearchComplete, this); this->Bind(wxEVT_DL_TITLE_UPDATE, &TitleManager::OnDownloadableTitleUpdate, this); this->Bind(wxEVT_DL_DISCONNECT_COMPLETE, &TitleManager::OnDisconnect, this); // TODO typing on title list should change filter text and filter! // if download manager is already active then restore state DownloadManager* dlMgr = DownloadManager::GetInstance(false); if (dlMgr && dlMgr->IsConnected()) { dlMgr->setUserData(this); dlMgr->registerCallbacks( TitleManager::Callback_ConnectStatusUpdate, TitleManager::Callback_AddDownloadableTitle, TitleManager::Callback_RemoveDownloadableTitle); SetConnected(true); } m_callbackId = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((TitleManager*)ctx)->HandleTitleListCallback(evt); }, this); } TitleManager::~TitleManager() { CafeTitleList::UnregisterCallback(m_callbackId); // unregister callbacks for download manager DownloadManager* dlMgr = DownloadManager::GetInstance(false); if (dlMgr) { dlMgr->setUserData(nullptr); dlMgr->registerCallbacks( nullptr, nullptr, nullptr); // if download manager is still downloading / installing then show a warning if (dlMgr->hasActiveDownloads()) { static bool s_showedBGDownloadWarning = false; if (!s_showedBGDownloadWarning) { wxMessageBox(_("Currently active downloads will continue in the background."), _("Information"), wxOK | wxCENTRE, this); s_showedBGDownloadWarning = true; } } } m_running = false; } void TitleManager::SetFocusAndTab(TitleManagerPage page) { m_notebook->SetSelection((int)page); this->SetFocus(); } void TitleManager::SetDownloadStatusText(const wxString& text) { auto* evt = new wxCommandEvent(wxEVT_SET_TEXT); evt->SetEventObject(m_status_text); evt->SetString(text); wxQueueEvent(this, evt); } void TitleManager::HandleTitleListCallback(CafeTitleListCallbackEvent* evt) { if (evt->eventType == CafeTitleListCallbackEvent::TYPE::SCAN_FINISHED) { auto* evt = new wxCommandEvent(wxEVT_TITLE_SEARCH_COMPLETE); wxQueueEvent(this, evt); } } void TitleManager::OnTitleFound(wxCommandEvent& event) { auto* obj = dynamic_cast(event.GetClientObject()); wxASSERT(obj); m_title_list->AddTitle(obj); } void TitleManager::OnTitleSearchComplete(wxCommandEvent& event) { m_isScanning = false; if (m_connectRequested) { InitiateConnect(); m_connectRequested = false; } // update status bar text m_title_list->SortEntries(-1); m_status_bar->SetStatusText(formatWxString(_("Found {0} games, {1} updates, {2} DLCs and {3} save entries"), m_title_list->GetCountByType(wxTitleManagerList::EntryType::Base) + m_title_list->GetCountByType(wxTitleManagerList::EntryType::System), m_title_list->GetCountByType(wxTitleManagerList::EntryType::Update), m_title_list->GetCountByType(wxTitleManagerList::EntryType::Dlc), m_title_list->GetCountByType(wxTitleManagerList::EntryType::Save) )); // re-filter if any filter is set const auto filter = m_filter->GetValue(); if (!filter.IsEmpty()) m_title_list->Filter(m_filter->GetValue()); m_title_list->AutosizeColumns(); m_refresh_button->Enable(); } void TitleManager::OnSetStatusBarText(wxSetStatusBarTextEvent& event) { m_status_bar->SetStatusText(event.GetText(), event.GetNumber()); } void TitleManager::OnFilterChanged(wxCommandEvent& event) { event.Skip(); m_title_list->Filter(m_filter->GetValue()); } void TitleManager::OnRefreshButton(wxCommandEvent& event) { m_refresh_button->Disable(); m_isScanning = true; // m_title_list->ClearItems(); -> Dont clear. Refresh() triggers incremental updates via notifications m_status_bar->SetStatusText(_("Searching for titles...")); CafeTitleList::Refresh(); } void TitleManager::OnInstallTitle(wxCommandEvent& event) { wxFileDialog openFileDialog(this, _("Select title to install"), "", "", "meta.xml|meta.xml", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDialog.ShowModal() == wxID_CANCEL || openFileDialog.GetPath().IsEmpty()) return; fs::path filePath(openFileDialog.GetPath().wc_string()); try { filePath = filePath.parent_path(); filePath = filePath.parent_path(); GameUpdateWindow frame(*this, filePath); const int updateResult = frame.ShowModal(); if (updateResult == wxID_OK) { CafeTitleList::AddTitleFromPath(frame.GetTargetPath()); } else { if (frame.GetExceptionMessage().empty()) wxMessageBox(_("Update installation has been canceled!")); else { throw std::runtime_error(frame.GetExceptionMessage()); } } } catch (const AbortException&) { // ignored } catch (const std::exception& ex) { wxMessageBox(ex.what(), _("Update error")); } } static void PopulateSavePersistentIds(wxTitleManagerList::TitleEntry& entry) { if (!entry.persistent_ids.empty()) return; cemu_assert(entry.type == wxTitleManagerList::EntryType::Save); SaveInfo saveInfo = CafeSaveList::GetSaveByTitleId(entry.title_id); if (!saveInfo.IsValid()) return; fs::path savePath = saveInfo.GetPath(); savePath /= "user"; std::error_code ec; for (auto it : fs::directory_iterator(savePath, ec)) { if(!it.is_directory(ec)) continue; if(fs::is_empty(it.path())) continue; std::string dirName = it.path().filename().string(); if(!std::regex_match(dirName, std::regex("[0-9a-fA-F]{8}"))) continue; entry.persistent_ids.emplace_back(ConvertString(dirName, 16)); } } void TitleManager::OnTitleSelected(wxListEvent& event) { event.Skip(); const auto entry = m_title_list->GetSelectedTitleEntry(); if(entry.has_value() && entry->type == wxTitleManagerList::EntryType::Save) { m_save_panel->Show(); m_save_account_list->Clear(); PopulateSavePersistentIds(*entry); // an account must be selected before any of the control buttons can be used for(auto&& v : m_save_panel->GetChildren()) { if (dynamic_cast(v) && v->GetId() != m_save_import->GetId()) // import is always enabled v->Disable(); } const auto& accounts = Account::GetAccounts(); for (const auto& id : entry->persistent_ids) { const auto it = std::find_if(accounts.cbegin(), accounts.cend(), [id](const auto& acc) { return acc.GetPersistentId() == id; }); if(it != accounts.cend()) { m_save_account_list->Append(fmt::format("{:x} ({})", id, boost::nowide::narrow(it->GetMiiName().data(), it->GetMiiName().size())), (void*)(uintptr_t)id); } else m_save_account_list->Append(fmt::format("{:x}", id), (void*)(uintptr_t)id); } } else { m_save_panel->Hide(); } } void TitleManager::OnSaveOpenDirectory(wxCommandEvent& event) { const auto selection= m_save_account_list->GetSelection(); if (selection == wxNOT_FOUND) return; const auto entry = m_title_list->GetSelectedTitleEntry(); if (!entry.has_value()) return; const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection); const fs::path target = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF), persistent_id); if (!fs::exists(target) || !fs::is_directory(target)) return; wxLaunchDefaultApplication(wxHelper::FromPath(target)); } void TitleManager::OnSaveDelete(wxCommandEvent& event) { const auto selection_index = m_save_account_list->GetSelection(); if (selection_index == wxNOT_FOUND) return; const auto selection = m_save_account_list->GetStringSelection(); if (selection.IsEmpty()) return; const auto msg = formatWxString(_("Are you really sure that you want to delete the save entry for {}"), selection); const auto result = wxMessageBox(msg, _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this); if (result == wxNO) return; auto entry = m_title_list->GetSelectedTitleEntry(); if (!entry.has_value()) return; const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index); const fs::path target = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF), persistent_id); if (!fs::exists(target) || !fs::is_directory(target)) return; // edit meta saveinfo.xml bool meta_file_edited = false; const fs::path saveinfo = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF)); if (fs::exists(saveinfo) || fs::is_regular_file(saveinfo)) { pugi::xml_document doc; if (doc.load_file(saveinfo.c_str())) { auto info_node = doc.child("info"); if(info_node) { auto persistend_id_string = fmt::format(L"{:08x}", persistent_id); const auto delete_entry = info_node.find_child([&persistend_id_string](const pugi::xml_node& node) { return boost::iequals(node.attribute("persistentId").as_string(), persistend_id_string); }); if (delete_entry) { info_node.remove_child(delete_entry); meta_file_edited = doc.save_file(saveinfo.c_str()); } } } } if (!meta_file_edited) cemuLog_log(LogType::Force, "TitleManager::OnSaveDelete: couldn't delete save entry in saveinfo.xml: {}", _pathToUtf8(saveinfo)); // remove from title entry auto& persistent_ids = entry->persistent_ids; persistent_ids.erase(std::remove(persistent_ids.begin(), persistent_ids.end(), persistent_id), persistent_ids.end()); std::error_code ec; fs::remove_all(target, ec); if (ec) { const auto error_msg = formatWxString(_("Error when trying to delete the save directory:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return; } m_save_account_list->Delete(selection_index); } void TitleManager::OnSaveTransfer(wxCommandEvent& event) { const auto selection_index = m_save_account_list->GetSelection(); if (selection_index == wxNOT_FOUND) return; const auto selection = m_save_account_list->GetStringSelection(); if (selection.IsEmpty()) return; auto entry = m_title_list->GetSelectedTitleEntry(); if (!entry.has_value()) return; const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index); SaveTransfer transfer(this, entry->title_id, selection, persistent_id); if (transfer.ShowModal() == wxCANCEL) return; // remove old id entry auto& persistent_ids = entry->persistent_ids; persistent_ids.erase(std::remove(persistent_ids.begin(), persistent_ids.end(), persistent_id), persistent_ids.end()); // add new id if not added yet const auto new_id = transfer.GetTargetPersistentId(); if (new_id != 0 && std::find(persistent_ids.cbegin(), persistent_ids.cend(), new_id) == persistent_ids.cend()) { persistent_ids.emplace_back(new_id); const auto& account = Account::GetAccount(new_id); if(account.GetPersistentId() == new_id) m_save_account_list->Append(fmt::format("{:x} ({})", new_id, boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())), (void*)(uintptr_t)new_id); else m_save_account_list->Append(fmt::format("{:x}", new_id), (void*)(uintptr_t)new_id); } m_save_account_list->Delete(selection_index); } void TitleManager::OnSaveAccountSelected(wxCommandEvent& event) { event.Skip(); for (auto&& v : m_save_panel->GetChildren()) { if (!v->IsEnabled()) v->Enable(); } } void TitleManager::OnSaveExport(wxCommandEvent& event) { const auto selection_index = m_save_account_list->GetSelection(); if (selection_index == wxNOT_FOUND) return; const auto selection = m_save_account_list->GetStringSelection(); if (selection.IsEmpty()) return; const auto entry = m_title_list->GetSelectedTitleEntry(); if (!entry.has_value()) return; const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index); wxFileDialog path_dialog(this, _("Select a target file to export the save entry"), wxHelper::FromPath(entry->path), wxEmptyString, fmt::format("{}|*.zip", _("Exported save entry (*.zip)")), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().IsEmpty()) return; const auto path = path_dialog.GetPath(); int ze; auto* zip = zip_open(path.ToUTF8().data(), ZIP_CREATE | ZIP_TRUNCATE, &ze); if (!zip) { zip_error_t ziperror; zip_error_init_with_code(&ziperror, ze); const auto error_msg = formatWxString(_("Error when creating the zip for the save entry:\n{}"), zip_error_strerror(&ziperror)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } // grab all files to zip const auto savedir = fs::path(entry->path).append(fmt::format("user/{:08x}", persistent_id)); const auto savedir_str = savedir.generic_u8string(); for(const auto& it : fs::recursive_directory_iterator(savedir)) { if (it.path() == "." || it.path() == "..") continue; const auto entryname = it.path().generic_u8string(); if(fs::is_directory(it.path())) { if(zip_dir_add(zip, (const char*)entryname.substr(savedir_str.size() + 1).c_str(), ZIP_FL_ENC_UTF_8) < 0 ) { const auto error_msg = formatWxString(_("Error when trying to add a directory to the zip:\n{}"), zip_strerror(zip)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); } } else { auto* source = zip_source_file(zip, (const char*)entryname.c_str(), 0, 0); if(!source) { const auto error_msg = formatWxString(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); } if (zip_file_add(zip, (const char*)entryname.substr(savedir_str.size() + 1).c_str(), source, ZIP_FL_ENC_UTF_8) < 0) { const auto error_msg = formatWxString(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); zip_source_free(source); } } } // add own metainfo like file and store title id for verification later std::string metacontent = fmt::format("titleId = {:#016x}", entry->title_id); auto* metabuff = zip_source_buffer(zip, metacontent.data(), metacontent.size(), 0); if(zip_file_add(zip, "cemu_meta", metabuff, ZIP_FL_ENC_UTF_8) < 0) { const auto error_msg = formatWxString(_("Error when trying to add cemu_meta file to the zip:\n{}"), zip_strerror(zip)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); zip_source_free(metabuff); } zip_close(zip); } void TitleManager::OnSaveImport(wxCommandEvent& event) { auto entry = m_title_list->GetSelectedTitleEntry(); if (!entry.has_value()) return; SaveImportWindow save_import(this, entry->title_id); if (save_import.ShowModal() == wxCANCEL) return; // add new id if not added yet auto& persistent_ids = entry->persistent_ids; const auto new_id = save_import.GetTargetPersistentId(); if (new_id != 0 && std::find(persistent_ids.cbegin(), persistent_ids.cend(), new_id) == persistent_ids.cend()) { persistent_ids.emplace_back(new_id); const auto& account = Account::GetAccount(new_id); if (account.GetPersistentId() == new_id) m_save_account_list->Append(fmt::format("{:x} ({})", new_id, boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())), (void*)(uintptr_t)new_id); else m_save_account_list->Append(fmt::format("{:x}", new_id), (void*)(uintptr_t)new_id); } } void TitleManager::InitiateConnect() { // init connection to download manager if queued #if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN uint32 persistentId = (uint32)(uintptr_t)m_account->GetClientData(m_account->GetSelection()); auto& account = Account::GetAccount(persistentId); #endif DownloadManager* dlMgr = DownloadManager::GetInstance(); dlMgr->reset(); m_download_list->SetCurrentDownloadMgr(dlMgr); std::string deviceCertBase64 = NCrypto::CertECC::GetDeviceCertificate().encodeToBase64(); if (!NCrypto::SEEPROM_IsPresent()) { SetDownloadStatusText(_("Dumped online files not found")); return; } SetDownloadStatusText(_("Connecting...")); // begin async connect dlMgr->setUserData(this); dlMgr->registerCallbacks( TitleManager::Callback_ConnectStatusUpdate, TitleManager::Callback_AddDownloadableTitle, TitleManager::Callback_RemoveDownloadableTitle); std::string accountName; std::array accountPassword; std::string accountCountry; #if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN accountName = account.GetAccountId(); accountPassword = account.GetAccountPasswordCache(); accountCountry.assign(NCrypto::GetCountryAsString(account.GetCountry())); #endif dlMgr->connect(accountName, accountPassword, NCrypto::SEEPROM_GetRegion(), accountCountry, NCrypto::GetDeviceId(), NCrypto::GetSerial(), deviceCertBase64); } void TitleManager::OnConnect(wxCommandEvent& event) { if (!m_isScanning) { InitiateConnect(); SetConnected(true); } else { SetDownloadStatusText(_("Getting installed title information...")); SetConnected(true); m_connectRequested = true; } } void TitleManager::OnDlFilterCheckbox(wxCommandEvent& event) { m_download_list->Filter2(m_show_titles->GetValue(), m_show_updates->GetValue(), m_show_installed->GetValue()); m_download_list->SortEntries(); } void TitleManager::OnSetStatusText(wxCommandEvent& event) { auto* text = wxDynamicCast(event.GetEventObject(), wxControl); wxASSERT(text); text->SetLabel(event.GetString()); } void TitleManager::OnDownloadableTitleUpdate(wxCommandEvent& event) { auto* obj = dynamic_cast(event.GetClientObject()); auto entry = obj->GetData(); m_download_list->AddOrUpdateTitle(obj); } void TitleManager::OnDisconnect(wxCommandEvent& event) { SetConnected(false); } void TitleManager::SetConnected(bool state) { #if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN m_account->Enable(!state); #endif m_connect->Enable(!state); m_show_titles->Enable(state); m_show_updates->Enable(state); m_show_installed->Enable(state); m_download_list->Enable(state); } void TitleManager::Callback_ConnectStatusUpdate(std::string statusText, DLMGR_STATUS_CODE statusCode) { TitleManager* titleManager = static_cast(DownloadManager::GetInstance()->getUserData()); titleManager->SetDownloadStatusText(wxString::FromUTF8(statusText)); if (statusCode == DLMGR_STATUS_CODE::FAILED) { auto* evt = new wxCommandEvent(wxEVT_DL_DISCONNECT_COMPLETE); wxQueueEvent(titleManager, evt); // this will set SetConnected() to false return; } } void TitleManager::Callback_AddDownloadableTitle(const DlMgrTitleReport& titleInfo) { TitleManager* titleManager = static_cast(DownloadManager::GetInstance()->getUserData()); wxDownloadManagerList::EntryType type = wxDownloadManagerList::EntryType::Base; if (((titleInfo.titleId >> 32) & 0xF) == 0xE) type = wxDownloadManagerList::EntryType::Update; else if (((titleInfo.titleId >> 32) & 0xF) == 0xC) type = wxDownloadManagerList::EntryType::DLC; if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE || titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UNFINISHED || titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UPDATE) { // installable title wxDownloadManagerList::TitleEntry titleEntry(type, false, titleInfo.titleId, titleInfo.version, titleInfo.isPaused); titleEntry.name = wxString::FromUTF8(titleInfo.name); titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Available; titleEntry.progress = 0; if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UNFINISHED) titleEntry.progress = 1; if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UPDATE) titleEntry.progress = 2; auto* evt = new wxCommandEvent(wxEVT_DL_TITLE_UPDATE); evt->SetClientObject(new wxCustomData(titleEntry)); wxQueueEvent(titleManager, evt); } else { // package wxDownloadManagerList::TitleEntry titleEntry(type, true, titleInfo.titleId, titleInfo.version, titleInfo.isPaused); titleEntry.name = wxString::FromUTF8(titleInfo.name); titleEntry.progress = titleInfo.progress; titleEntry.progressMax = titleInfo.progressMax; switch (titleInfo.status) { case DlMgrTitleReport::STATUS::INITIALIZING: titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Initializing; break; case DlMgrTitleReport::STATUS::QUEUED: titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Queued; break; case DlMgrTitleReport::STATUS::CHECKING: titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Checking; break; case DlMgrTitleReport::STATUS::DOWNLOADING: titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Downloading; titleEntry.progress = titleInfo.progress; break; case DlMgrTitleReport::STATUS::VERIFYING: titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Verifying; break; case DlMgrTitleReport::STATUS::INSTALLING: titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Installing; break; case DlMgrTitleReport::STATUS::INSTALLED: titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Installed; break; case DlMgrTitleReport::STATUS::HAS_ERROR: titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Error; titleEntry.errorMsg = titleInfo.errorMsg; break; default: titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::None; break; } auto* evt = new wxCommandEvent(wxEVT_DL_TITLE_UPDATE); evt->SetClientObject(new wxCustomData(titleEntry)); wxQueueEvent(titleManager, evt); } } void TitleManager::Callback_RemoveDownloadableTitle(uint64 titleId, uint16 version) { TitleManager* titleManager = static_cast(DownloadManager::GetInstance()->getUserData()); assert_dbg(); } ================================================ FILE: src/gui/wxgui/TitleManager.h ================================================ #pragma once #include #include #include #include #include "Cemu/Tools/DownloadManager/DownloadManager.h" #define DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN 0 class wxCheckBox; class wxStaticText; class wxListEvent; class wxSetStatusBarTextEvent; class wxTitleManagerList; class wxDownloadManagerList; class wxTextCtrl; class wxStatusBar; class wxImageList; class wxBitmapButton; class wxPanel; class wxChoice; class wxNotebook; enum class TitleManagerPage { TitleManager = 0, DownloadManager = 1 }; enum class DLMGR_STATUS_CODE; class TitleManager : public wxFrame { public: TitleManager(wxWindow* parent, TitleManagerPage default_page = TitleManagerPage::TitleManager); ~TitleManager(); void SetFocusAndTab(TitleManagerPage page); void SetDownloadStatusText(const wxString& text); private: wxPanel* CreateTitleManagerPage(); wxPanel* CreateDownloadManagerPage(); // title manager void OnTitleFound(wxCommandEvent& event); void OnTitleSearchComplete(wxCommandEvent& event); void OnSetStatusBarText(wxSetStatusBarTextEvent& event); void OnFilterChanged(wxCommandEvent& event); void OnRefreshButton(wxCommandEvent& event); void OnInstallTitle(wxCommandEvent& event); void OnTitleSelected(wxListEvent& event); void OnSaveOpenDirectory(wxCommandEvent& event); void OnSaveDelete(wxCommandEvent& event); void OnSaveTransfer(wxCommandEvent& event); void OnSaveAccountSelected(wxCommandEvent& event); void OnSaveExport(wxCommandEvent& event); void OnSaveImport(wxCommandEvent& event); void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt); wxNotebook* m_notebook; uint32 m_callbackId; // title manager wxTextCtrl* m_filter; wxTitleManagerList* m_title_list; wxStatusBar* m_status_bar; wxBitmapButton* m_refresh_button; wxPanel* m_save_panel; wxChoice* m_save_account_list; wxButton* m_save_import; bool m_isScanning{ true }; // set when CafeTitleList is scanning std::atomic_bool m_running = true; // download manager void InitiateConnect(); void OnConnect(wxCommandEvent& event); void OnSetStatusText(wxCommandEvent& event); void OnDownloadableTitleUpdate(wxCommandEvent& event); void OnDisconnect(wxCommandEvent& event); void OnDlFilterCheckbox(wxCommandEvent& event); void SetConnected(bool state); static void Callback_ConnectStatusUpdate(std::string statusText, DLMGR_STATUS_CODE statusCode); static void Callback_AddDownloadableTitle(const struct DlMgrTitleReport& titleInfo); static void Callback_RemoveDownloadableTitle(uint64 titleId, uint16 version); wxChoice* m_account; wxButton* m_connect; wxStaticText* m_status_text; wxCheckBox *m_show_titles, *m_show_updates, *m_show_installed; wxDownloadManagerList* m_download_list; bool m_connectRequested{false}; // connect was clicked before m_foundTitles was available }; ================================================ FILE: src/gui/wxgui/canvas/IRenderCanvas.h ================================================ #pragma once #include // base class for all render interfaces class IRenderCanvas { public: IRenderCanvas(bool is_main_window) : m_is_main_window(is_main_window) {} protected: bool m_is_main_window; }; ================================================ FILE: src/gui/wxgui/canvas/MetalCanvas.cpp ================================================ #include "wxgui/canvas/MetalCanvas.h" #include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h" #include #include MetalCanvas::MetalCanvas(wxWindow* parent, const wxSize& size, bool is_main_window) : IRenderCanvas(is_main_window), wxWindow(parent, wxID_ANY, wxDefaultPosition, size, wxNO_FULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) { Bind(wxEVT_PAINT, &MetalCanvas::OnPaint, this); Bind(wxEVT_SIZE, &MetalCanvas::OnResize, this); auto& canvas = is_main_window ? WindowSystem::GetWindowInfo().canvas_main : WindowSystem::GetWindowInfo().canvas_pad; canvas = initHandleContextFromWxWidgetsWindow(this); try { if (is_main_window) g_renderer = std::make_unique(); auto metal_renderer = MetalRenderer::GetInstance(); metal_renderer->InitializeLayer({size.x, size.y}, is_main_window); } catch(const std::exception& ex) { cemuLog_log(LogType::Force, "Error when initializing Metal renderer: {}", ex.what()); auto msg = formatWxString(_("Error when initializing Metal renderer:\n{}"), ex.what()); wxMessageDialog dialog(this, msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); dialog.ShowModal(); exit(0); } wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES); } MetalCanvas::~MetalCanvas() { Unbind(wxEVT_PAINT, &MetalCanvas::OnPaint, this); Unbind(wxEVT_SIZE, &MetalCanvas::OnResize, this); MetalRenderer* mtlr = (MetalRenderer*)g_renderer.get(); if (mtlr) mtlr->ShutdownLayer(m_is_main_window); } void MetalCanvas::OnPaint(wxPaintEvent& event) { } void MetalCanvas::OnResize(wxSizeEvent& event) { const wxSize size = GetSize(); if (size.GetWidth() == 0 || size.GetHeight() == 0) return; const wxRect refreshRect(size); RefreshRect(refreshRect, false); auto metal_renderer = MetalRenderer::GetInstance(); metal_renderer->ResizeLayer({size.x, size.y}, m_is_main_window); } ================================================ FILE: src/gui/wxgui/canvas/MetalCanvas.h ================================================ #pragma once #include "wxgui/canvas/IRenderCanvas.h" #include #include class MetalCanvas : public IRenderCanvas, public wxWindow { public: MetalCanvas(wxWindow* parent, const wxSize& size, bool is_main_window); ~MetalCanvas(); private: void OnPaint(wxPaintEvent& event); void OnResize(wxSizeEvent& event); }; ================================================ FILE: src/gui/wxgui/canvas/OpenGLCanvas.cpp ================================================ #include "wxgui/canvas/OpenGLCanvas.h" #include "wxgui/canvas/IRenderCanvas.h" #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" #include "config/CemuConfig.h" #include "Common/GLInclude/GLInclude.h" #include // this includes GL/gl.h, avoid using this in a header because it would contaminate our own OpenGL definitions (GLInclude) static const int g_gl_attribute_list[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, WX_GL_MIN_RED, 8, WX_GL_MIN_GREEN, 8, WX_GL_MIN_BLUE, 8, WX_GL_MIN_ALPHA, 8, WX_GL_STENCIL_SIZE, 8, //WX_GL_MAJOR_VERSION, 4, //WX_GL_MINOR_VERSION, 1, //wx_GL_COMPAT_PROFILE, 0, // end of list }; class OpenGLCanvas; class GLCanvasManager : public OpenGLCanvasCallbacks { public: GLCanvasManager() { SetOpenGLCanvasCallbacks(this); } ~GLCanvasManager() { ClearOpenGLCanvasCallbacks(); } void SetTVView(OpenGLCanvas* canvas) { m_tvView = canvas; } void SetPadView(OpenGLCanvas* canvas) { m_padView = canvas; } void SetGLContext(wxGLContext* context) { m_glContext = context; } void DeleteGLContext() { if (m_tvView == nullptr && m_padView == nullptr && m_glContext) { delete m_glContext; m_glContext = nullptr; } } bool HasPadViewOpen() const { return m_padView != nullptr; } bool MakeCurrent(bool padView); void SwapBuffers(bool swapTV, bool swapDRC); private: wxGLContext* m_glContext = nullptr; OpenGLCanvas* m_tvView = nullptr; OpenGLCanvas* m_padView = nullptr; } s_glCanvasManager; class OpenGLCanvas : public IRenderCanvas, public wxGLCanvas { public: OpenGLCanvas(wxWindow* parent, const wxSize& size, bool is_main_window) : IRenderCanvas(is_main_window), wxGLCanvas(parent, wxID_ANY, g_gl_attribute_list, wxDefaultPosition, size, wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) { if (m_is_main_window) { s_glCanvasManager.SetTVView(this); s_glCanvasManager.SetGLContext(new wxGLContext(this)); g_renderer = std::make_unique(); } else { s_glCanvasManager.SetPadView(this); } wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES); } ~OpenGLCanvas() override { // todo - if this is the main window, make sure the renderer has been shut down if (m_is_main_window) s_glCanvasManager.SetTVView(nullptr); else s_glCanvasManager.SetPadView(nullptr); s_glCanvasManager.DeleteGLContext(); } void UpdateVSyncState() { int configValue = GetConfig().vsync.GetValue(); if(m_activeVSyncState != configValue) { #if BOOST_OS_WINDOWS if(wglSwapIntervalEXT) wglSwapIntervalEXT(configValue); // 1 = enabled, 0 = disabled #elif BOOST_OS_LINUX || BOOST_OS_BSD if (eglSwapInterval) { if (eglSwapInterval(eglGetCurrentDisplay(), configValue) == EGL_FALSE) { cemuLog_log(LogType::Force, "Failed to set vsync using EGL"); } } #else cemuLog_log(LogType::Force, "OpenGL vsync not implemented"); #endif m_activeVSyncState = configValue; } } private: int m_activeVSyncState = -1; //wxGLContext* m_context = nullptr; }; wxWindow* GLCanvas_Create(wxWindow* parent, const wxSize& size, bool is_main_window) { return new OpenGLCanvas(parent, size, is_main_window); } void GLCanvasManager::SwapBuffers(bool swapTV, bool swapDRC) { if (swapTV && m_tvView) { MakeCurrent(false); m_tvView->SwapBuffers(); m_tvView->UpdateVSyncState(); } if (swapDRC && m_padView) { MakeCurrent(true); m_padView->SwapBuffers(); m_padView->UpdateVSyncState(); } MakeCurrent(false); } bool GLCanvasManager::MakeCurrent(bool padView) { OpenGLCanvas* canvas = padView ? m_padView : m_tvView; if (!canvas) return false; m_glContext->SetCurrent(*canvas); return true; } ================================================ FILE: src/gui/wxgui/canvas/OpenGLCanvas.h ================================================ #pragma once #include wxWindow* GLCanvas_Create(wxWindow* parent, const wxSize& size, bool is_main_window); ================================================ FILE: src/gui/wxgui/canvas/VulkanCanvas.cpp ================================================ #include "wxgui/canvas/VulkanCanvas.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND #include "wxgui/helpers/wxWayland.h" #endif #include #include VulkanCanvas::VulkanCanvas(wxWindow* parent, const wxSize& size, bool is_main_window) : IRenderCanvas(is_main_window), wxWindow(parent, wxID_ANY, wxDefaultPosition, size, wxNO_FULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) { Bind(wxEVT_PAINT, &VulkanCanvas::OnPaint, this); Bind(wxEVT_SIZE, &VulkanCanvas::OnResize, this); #if __WXMSW__ MSWDisableComposited(); #endif auto& canvas = is_main_window ? WindowSystem::GetWindowInfo().canvas_main : WindowSystem::GetWindowInfo().canvas_pad; canvas = initHandleContextFromWxWidgetsWindow(this); #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND if (canvas.backend == WindowSystem::WindowHandleInfo::Backend::Wayland) { m_subsurface = std::make_unique(this); canvas.surface = m_subsurface->getSurface(); } #endif cemu_assert(g_vulkan_available); try { if (is_main_window) g_renderer = std::make_unique(); auto vulkan_renderer = VulkanRenderer::GetInstance(); vulkan_renderer->InitializeSurface({size.x, size.y}, is_main_window); } catch(const std::exception& ex) { cemuLog_log(LogType::Force, "Error when initializing Vulkan renderer: {}", ex.what()); auto msg = formatWxString(_("Error when initializing Vulkan renderer:\n{}"), ex.what()); wxMessageDialog dialog(this, msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); dialog.ShowModal(); exit(0); } wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES); } VulkanCanvas::~VulkanCanvas() { Unbind(wxEVT_PAINT, &VulkanCanvas::OnPaint, this); Unbind(wxEVT_SIZE, &VulkanCanvas::OnResize, this); if(!m_is_main_window) { VulkanRenderer* vkr = (VulkanRenderer*)g_renderer.get(); if(vkr) vkr->StopUsingPadAndWait(); } } void VulkanCanvas::OnPaint(wxPaintEvent& event) { } void VulkanCanvas::OnResize(wxSizeEvent& event) { const wxSize size = GetSize(); if (size.GetWidth() == 0 || size.GetHeight() == 0) return; #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND if(m_subsurface) { auto sRect = GetScreenRect(); m_subsurface->setSize(sRect.GetX(), sRect.GetY(), sRect.GetWidth(), sRect.GetHeight()); } #endif const wxRect refreshRect(size); RefreshRect(refreshRect, false); } ================================================ FILE: src/gui/wxgui/canvas/VulkanCanvas.h ================================================ #pragma once #include "wxgui/canvas/IRenderCanvas.h" #include #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include class VulkanCanvas : public IRenderCanvas, public wxWindow { #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND std::unique_ptr m_subsurface; #endif public: VulkanCanvas(wxWindow* parent, const wxSize& size, bool is_main_window); ~VulkanCanvas(); private: void OnPaint(wxPaintEvent& event); void OnResize(wxSizeEvent& event); }; ================================================ FILE: src/gui/wxgui/components/TextList.cpp ================================================ #include "wxgui/wxgui.h" #include "TextList.h" #include "debugger/DisasmCtrl.h" #include #include TextList::~TextList() { m_tooltip_timer->Stop(); this->Unbind(wxEVT_MOTION, &TextList::OnMouseMoveEvent, this); this->Unbind(wxEVT_KEY_DOWN, &TextList::OnKeyDownEvent, this); this->Unbind(wxEVT_KEY_UP, &TextList::OnKeyUpEvent, this); this->Unbind(wxEVT_PAINT, &TextList::OnPaintEvent, this); this->Unbind(wxEVT_LEFT_DOWN, &TextList::OnMouseDownEvent, this); this->Unbind(wxEVT_LEFT_UP, &TextList::OnMouseUpEvent, this); this->Unbind(wxEVT_LEFT_DCLICK, &TextList::OnMouseDClickEvent, this); this->Unbind(wxEVT_CONTEXT_MENU, &TextList::OnContextMenu, this); this->Unbind(wxEVT_ERASE_BACKGROUND, &TextList::OnEraseBackground, this); } TextList::TextList(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) : wxControl(parent, id, pos, size, style), wxScrollHelper(this) { m_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); this->wxWindowBase::SetBackgroundStyle(wxBG_STYLE_PAINT); wxInfoDC dc(this); this->DoPrepareReadOnlyDC(dc); dc.SetFont(m_font); m_line_height = dc.GetCharHeight(); m_char_width = dc.GetCharWidth(); m_yScrollPixelsPerLine = m_line_height; this->ShowScrollbars(wxSHOW_SB_DEFAULT, wxSHOW_SB_DEFAULT); m_tooltip = new wxToolTip(wxEmptyString); this->Bind(wxEVT_MOTION, &TextList::OnMouseMoveEvent, this); this->Bind(wxEVT_KEY_DOWN, &TextList::OnKeyDownEvent, this); this->Bind(wxEVT_KEY_UP, &TextList::OnKeyUpEvent, this); this->Bind(wxEVT_PAINT, &TextList::OnPaintEvent, this); this->Bind(wxEVT_LEFT_DOWN, &TextList::OnMouseDownEvent, this); this->Bind(wxEVT_LEFT_UP, &TextList::OnMouseUpEvent, this); this->Bind(wxEVT_LEFT_DCLICK, &TextList::OnMouseDClickEvent, this); this->Bind(wxEVT_CONTEXT_MENU, &TextList::OnContextMenu, this); this->Bind(wxEVT_ERASE_BACKGROUND, &TextList::OnEraseBackground, this); m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); m_tooltip_window->Hide(); m_tooltip_timer = new wxTimer(this); this->Bind(wxEVT_TIMER, &TextList::OnTooltipTimer, this); } void TextList::RefreshControl(const wxRect* update_region) { if(update_region) Refresh(true, update_region); else { wxRect region = GetClientRect(); update_region = ®ion; Refresh(true, update_region); } } void TextList::RefreshLine(uint32 line) { wxRect update_region = GetClientRect(); update_region.y = (sint32)line * m_line_height; update_region.height = m_line_height; CalcScrolledPosition(0, update_region.y, nullptr, &update_region.y); // debug_printf("update: <%x, %x>\n", update_region.y, update_region.height); Refresh(true, &update_region); } wxSize TextList::DoGetVirtualSize() const { return {wxDefaultCoord, (int)m_element_count * m_line_height}; } void TextList::DoSetSize(int x, int y, int width, int height, int sizeFlags) { wxControl::DoSetSize(x, y, width, height, sizeFlags); m_elements_visible = (height + m_line_height - 1) / m_line_height; Refresh(); } void TextList::SetScrollPos(int orient, int pos, bool refresh) { wxControl::SetScrollPos(orient, pos, refresh); } void TextList::DrawLineBackground(wxDC& dc, uint32 line, const wxColour& colour, uint32 lines) const { wxRect rect; rect.x = GetPosition().x; rect.y = line * m_line_height; rect.width = GetSize().x; rect.height = m_line_height * lines; dc.SetBrush(colour); dc.DrawRectangle(rect); } void TextList::DrawLineBackground(wxDC& dc, const wxPoint& position, const wxColour& colour, uint32 lines) const { wxRect rect; rect.x = position.x; rect.y = position.y; rect.width = this->GetSize().x; rect.height = m_line_height * lines; dc.SetBrush(colour); dc.DrawRectangle(rect); } bool TextList::SetElementCount(size_t element_count) { if (m_element_count == element_count) return false; if (element_count > 0x7FFFFFFF) element_count = 0x7FFFFFFF; m_element_count = element_count; this->AdjustScrollbars(); return true; } uint32 TextList::GetElementCount() const { return m_element_count; } bool TextList::IsKeyDown(sint32 keycode) { return m_key_states[keycode]; } void TextList::WriteText(wxDC& dc, const wxString& text, wxPoint& position) const { dc.ResetBoundingBox(); dc.DrawText(text, position); position.x += dc.MaxX() - dc.MinX(); } void TextList::WriteText(wxDC& dc, const wxString& text, wxPoint& position, const wxColour& color) const { dc.SetTextForeground(color); WriteText(dc, text, position); } void TextList::NextLine(wxPoint& position, const wxPoint* start_position) const { position.y += m_line_height; if (start_position) position.x = start_position->x; } void TextList::OnMouseDown() {} void TextList::OnMouseUp() {} bool TextList::OnShowTooltip(const wxPoint& position, uint32 line) { return false; } void TextList::OnMouseMoveEvent(wxMouseEvent& event) { m_tooltip_timer->Stop(); m_tooltip_timer->StartOnce(250); wxPoint position = event.GetPosition(); CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y); m_mouse_position = position; if(m_mouse_down) m_selection.SetBottomRight(position); const sint32 line = position.y / m_line_height; OnMouseMove(position, line); } void TextList::OnKeyDownEvent(wxKeyEvent& event) { const auto key_code = event.GetKeyCode(); const auto it = m_key_states.find(key_code); if(it == m_key_states.end() || !it->second) { m_key_states[key_code] = true; OnKeyPressed(key_code, event.GetPosition()); } event.Skip(); } void TextList::OnKeyUpEvent(wxKeyEvent& event) { m_key_states[event.GetKeyCode()] = false; } void TextList::OnMouseDownEvent(wxMouseEvent& event) { m_mouse_down = true; wxPoint position = event.GetPosition(); CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y); m_selection.SetPosition(position); m_selection.SetBottomRight(position); OnMouseDown(); event.Skip(); } void TextList::OnMouseUpEvent(wxMouseEvent& event) { m_mouse_down = false; OnMouseUp(); event.Skip(); } void TextList::OnMouseDClickEvent(wxMouseEvent& event) { wxPoint position = event.GetPosition(); CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y); m_selection.SetPosition(position); m_selection.SetBottomRight(position); const uint32 line = position.y / m_line_height; OnMouseDClick(position, line); } void TextList::OnContextMenu(wxContextMenuEvent& event) { wxPoint position = event.GetPosition(); if (position == wxDefaultPosition) return; wxPoint clientPosition = ScreenToClient(position); CalcUnscrolledPosition(clientPosition.x, clientPosition.y, &clientPosition.x, &clientPosition.y); m_selection.SetPosition(clientPosition); m_selection.SetBottomRight(clientPosition); const uint32 line = clientPosition.y / m_line_height; OnContextMenu(clientPosition, line); } void TextList::OnTooltipTimer(wxTimerEvent& event) { m_tooltip_window->Hide(); const auto cursor_position = wxGetMousePosition(); const auto position = GetScreenPosition(); if (cursor_position.x < position.x || cursor_position.y < position.y) return; const auto size = GetSize(); if (position.x + size.x < cursor_position.x || position.y + size.y < cursor_position.y) return; const sint32 line = position.y / m_line_height; if(OnShowTooltip(m_mouse_position, line)) { m_tooltip_window->SetPosition(wxPoint(m_mouse_position.x + 15, m_mouse_position.y + 15)); m_tooltip_window->SendSizeEvent(); m_tooltip_window->Show(); } } void TextList::OnPaintEvent(wxPaintEvent& event) { wxAutoBufferedPaintDC dc(m_targetWindow); // get window position auto position = GetPosition(); // get current real position wxRect rect_update = GetUpdateRegion().GetBox(); const auto count = (uint32)std::ceil((float)rect_update.GetHeight() / m_line_height); position.y = (rect_update.y / m_line_height) * m_line_height; // paint background dc.SetFont(m_font); const wxColour window_colour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); dc.SetBrush(GetBackgroundColour()); dc.SetPen(*wxTRANSPARENT_PEN); dc.DrawRectangle(rect_update); //// paint selection //if (!m_selected_text.eof()) //{ // dc.SetBrush(*wxBLUE_BRUSH); // dc.SetPen(*wxBLUE_PEN); // dc.DrawRectangle(m_selection); //} sint32 start; CalcUnscrolledPosition(rect_update.x, rect_update.y, nullptr, &start); start /= m_line_height; m_scrolled_to_end = (start + count) >= m_element_count; OnDraw(dc, start, count, position); // removed Update() here since all text is white } ================================================ FILE: src/gui/wxgui/components/TextList.h ================================================ #pragma once #include #include #include class TextList : public wxControl, public wxScrollHelper { public: virtual ~TextList(); TextList(wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0); void RefreshControl(const wxRect* update_region = nullptr); void RefreshLine(uint32 line); wxSize DoGetVirtualSize() const override; void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; void SetScrollPos(int orient, int pos, bool refresh) override; void DrawLineBackground(wxDC& dc, uint32 line, const wxColour& colour, uint32 lines = 1) const; void DrawLineBackground(wxDC& dc, const wxPoint& position, const wxColour& colour, uint32 lines = 1) const; bool SetElementCount(size_t element_count); uint32 GetElementCount() const; bool IsKeyDown(sint32 keycode); protected: void WriteText(wxDC& dc, const wxString& text, wxPoint& position) const; void WriteText(wxDC& dc, const wxString& text, wxPoint& position, const wxColour& color) const; void NextLine(wxPoint& position, const wxPoint* start_position = nullptr) const; virtual void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) {}; virtual void OnMouseMove(const wxPoint& position, uint32 line) {} virtual void OnKeyPressed(sint32 key_code, const wxPoint& position) {} virtual void OnMouseDown(); virtual void OnMouseUp(); virtual void OnMouseDClick(const wxPoint& position, uint32 line) {} virtual void OnContextMenu(const wxPoint& position, uint32 line) {} virtual bool OnShowTooltip(const wxPoint& position, uint32 line); void OnEraseBackground(wxEraseEvent&) {} sint32 m_line_height; sint32 m_char_width; size_t m_elements_visible = 0; size_t m_element_count = 0; bool m_scrolled_to_end = true; std::wstringstream m_selected_text; bool m_mouse_down = false; wxRect m_selection; wxPanel* m_tooltip_window; private: void OnPaintEvent(wxPaintEvent& event); void OnMouseMoveEvent(wxMouseEvent& event); void OnKeyDownEvent(wxKeyEvent& event); void OnKeyUpEvent(wxKeyEvent& event); void OnMouseDownEvent(wxMouseEvent& event); void OnMouseUpEvent(wxMouseEvent& event); void OnMouseDClickEvent(wxMouseEvent& event); void OnContextMenu(wxContextMenuEvent& event); void OnTooltipTimer(wxTimerEvent& event); std::unordered_map m_key_states; wxFont m_font; wxTimer* m_tooltip_timer; wxPoint m_mouse_position; }; ================================================ FILE: src/gui/wxgui/components/wxDownloadManagerList.cpp ================================================ #include "wxgui/components/wxDownloadManagerList.h" #include "wxHelper.h" #include "wxgui/helpers/wxHelpers.h" #include "util/helpers/SystemException.h" #include "Cafe/TitleList/GameInfo.h" #include "wxgui/components/wxGameList.h" #include "wxgui/helpers/wxCustomEvents.h" #include #include #include #include #include #include #include #include #include "config/ActiveSettings.h" #include "wxgui/ChecksumTool.h" #include "Cemu/Tools/DownloadManager/DownloadManager.h" #include "Cafe/TitleList/TitleId.h" #include "wxgui/MainWindow.h" wxDEFINE_EVENT(wxEVT_REMOVE_ENTRY, wxCommandEvent); wxDownloadManagerList::wxDownloadManagerList(wxWindow* parent, wxWindowID id) : wxListView(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL) { AddColumns(); // tooltip TODO: extract class mb wxPanelTooltip m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); auto* tooltip_sizer = new wxBoxSizer(wxVERTICAL); m_tooltip_text = new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString); tooltip_sizer->Add(m_tooltip_text , 0, wxALL, 5); m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); m_tooltip_window->SetSizerAndFit(tooltip_sizer); m_tooltip_window->Hide(); m_tooltip_timer = new wxTimer(this); Bind(wxEVT_LIST_COL_CLICK, &wxDownloadManagerList::OnColumnClick, this); Bind(wxEVT_CONTEXT_MENU, &wxDownloadManagerList::OnContextMenu, this); Bind(wxEVT_LIST_ITEM_SELECTED, &wxDownloadManagerList::OnItemSelected, this); Bind(wxEVT_TIMER, &wxDownloadManagerList::OnTimer, this); Bind(wxEVT_REMOVE_ITEM, &wxDownloadManagerList::OnRemoveItem, this); Bind(wxEVT_REMOVE_ENTRY, &wxDownloadManagerList::OnRemoveEntry, this); Bind(wxEVT_CLOSE_WINDOW, &wxDownloadManagerList::OnClose, this); ShowSortIndicator(ColumnName); } boost::optional wxDownloadManagerList::GetSelectedTitleEntry() const { const auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { const auto tmp = GetTitleEntry(selection); if (tmp.has_value()) return tmp.value(); } return {}; } boost::optional wxDownloadManagerList::GetSelectedTitleEntry() { const auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { const auto tmp = GetTitleEntry(selection); if (tmp.has_value()) return tmp.value(); } return {}; } boost::optional wxDownloadManagerList::GetTitleEntry(uint64 titleId, uint16 titleVersion) { for(const auto& v : m_data) { if (v->entry.titleId == titleId && v->entry.version == titleVersion) return v->entry; } return {}; } void wxDownloadManagerList::AddColumns() { wxListItem col0; col0.SetId(ColumnTitleId); col0.SetText(_("Title ID")); col0.SetWidth(120); InsertColumn(ColumnTitleId, col0); wxListItem col1; col1.SetId(ColumnName); col1.SetText(_("Name")); col1.SetWidth(260); InsertColumn(ColumnName, col1); wxListItem col2; col2.SetId(ColumnVersion); col2.SetText(_("Version")); col2.SetWidth(55); InsertColumn(ColumnVersion, col2); wxListItem col3; col3.SetId(ColumnType); col3.SetText(_("Type")); col3.SetWidth(60); InsertColumn(ColumnType, col3); wxListItem col4; col4.SetId(ColumnProgress); col4.SetText(_("Progress")); col4.SetWidth(wxLIST_AUTOSIZE_USEHEADER); InsertColumn(ColumnProgress, col4); wxListItem col5; col5.SetId(ColumnStatus); col5.SetText(_("Status")); col5.SetWidth(240); InsertColumn(ColumnStatus, col5); } wxString wxDownloadManagerList::OnGetItemText(long item, long column) const { if (item >= GetItemCount()) return wxEmptyString; const auto entry = GetTitleEntry(item); if (entry.has_value()) return GetTitleEntryText(entry.value(), (ItemColumn)column); return wxEmptyString; } wxItemAttr* wxDownloadManagerList::OnGetItemAttr(long item) const { const auto entry = GetTitleEntry(item); const wxColour bgColour = GetBackgroundColour(); const bool isDarkTheme = wxSystemSettings::GetAppearance().IsDark(); if (entry.has_value()) { auto& entryData = entry.value(); if (entryData.status == TitleDownloadStatus::Downloading || entryData.status == TitleDownloadStatus::Verifying || entryData.status == TitleDownloadStatus::Installing) { const wxColour kActiveColor = isDarkTheme ? wxColour(80, 40, 40) : wxColour(0xFFE0E0); static wxListItemAttr s_active_attr; s_active_attr.SetBackgroundColour(kActiveColor); s_active_attr.SetTextColour(GetTextColour()); s_active_attr.SetFont(GetFont()); return &s_active_attr; } else if (entryData.status == TitleDownloadStatus::Installed && entryData.isPackage) { const wxColour kActiveColor = isDarkTheme ? wxColour(40, 80, 40) : wxColour(0xE0FFE0); static wxListItemAttr s_installed_attr; s_installed_attr.SetBackgroundColour(kActiveColor); s_installed_attr.SetTextColour(GetTextColour()); s_installed_attr.SetFont(GetFont()); return &s_installed_attr; } else if (entryData.status == TitleDownloadStatus::Error) { const wxColour kActiveColor = isDarkTheme ? wxColour(40, 40, 80) : wxColour(0xCCCCF2); static wxListItemAttr s_error_attr; s_error_attr.SetBackgroundColour(kActiveColor); s_error_attr.SetTextColour(GetTextColour()); s_error_attr.SetFont(GetFont()); return &s_error_attr; } } wxColour bgColourSecondary = wxHelper::CalculateAccentColour(bgColour); static wxListItemAttr s_coloured_attr; s_coloured_attr.SetBackgroundColour(bgColourSecondary); s_coloured_attr.SetTextColour(GetTextColour()); s_coloured_attr.SetFont(GetFont()); return item % 2 == 0 ? nullptr : &s_coloured_attr; } boost::optional wxDownloadManagerList::GetTitleEntry(long item) { long counter = 0; for (const auto& data : m_sorted_data) { if (!data.get().visible) continue; if (item != counter++) continue; return data.get().entry; } return {}; } boost::optional wxDownloadManagerList::GetTitleEntry(long item) const { long counter = 0; for (const auto& data : m_sorted_data) { if (!data.get().visible) continue; if (item != counter++) continue; return data.get().entry; } return {}; } void wxDownloadManagerList::OnClose(wxCloseEvent& event) { event.Skip(); // wait until all tasks are complete if (m_context_worker.valid()) m_context_worker.get(); g_mainFrame->RequestGameListRefresh(); // todo: add games instead of fully refreshing game list if a game is downloaded } void wxDownloadManagerList::OnColumnClick(wxListEvent& event) { const int column = event.GetColumn(); SortEntries(column); event.Skip(); } void wxDownloadManagerList::RemoveItem(long item) { const int item_count = GetItemCount(); const ItemData* ref = nullptr; long counter = 0; for(auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it) { if (!it->get().visible) continue; if (item != counter++) continue; ref = &(it->get()); m_sorted_data.erase(it); break; } // shouldn't happen if (ref == nullptr) return; for(auto it = m_data.begin(); it != m_data.end(); ++it) { if (ref != (*it).get()) continue; m_data.erase(it); break; } SetItemCount(std::max(0, item_count - 1)); RefreshPage(); } void wxDownloadManagerList::RemoveItem(const TitleEntry& entry) { const int item_count = GetItemCount(); const TitleEntry* ref = &entry; for (auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it) { if (ref != &it->get().entry) continue; m_sorted_data.erase(it); break; } for (auto it = m_data.begin(); it != m_data.end(); ++it) { if (ref != &(*it).get()->entry) continue; m_data.erase(it); break; } SetItemCount(std::max(0, item_count - 1)); RefreshPage(); } void wxDownloadManagerList::OnItemSelected(wxListEvent& event) { event.Skip(); m_tooltip_timer->Stop(); const auto selection = event.GetIndex(); if (selection == wxNOT_FOUND) { m_tooltip_window->Hide(); return; } } enum ContextMenuEntries { kContextMenuRetry = wxID_HIGHEST + 1, kContextMenuDownload, kContextMenuPause, kContextMenuResume, }; void wxDownloadManagerList::OnContextMenu(wxContextMenuEvent& event) { // still doing work if (m_context_worker.valid() && !future_is_ready(m_context_worker)) return; wxMenu menu; menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxDownloadManagerList::OnContextMenuSelected, this); const auto selection = GetFirstSelected(); if (selection == wxNOT_FOUND) return; const auto entry = GetTitleEntry(selection); if (!entry.has_value()) return; if (entry->isPaused) menu.Append(kContextMenuResume, _("&Resume")); else if (entry->status == TitleDownloadStatus::Error) menu.Append(kContextMenuRetry, _("&Retry")); else if(entry->status == TitleDownloadStatus::Available) menu.Append(kContextMenuDownload, _("&Download")); //else if(entry->status == TitleDownloadStatus::Downloading || entry->status == TitleDownloadStatus::Initializing) // menu.Append(kContextMenuPause, _("&Pause")); buggy! PopupMenu(&menu); // TODO: fix tooltip position } void wxDownloadManagerList::SetCurrentDownloadMgr(DownloadManager* dlMgr) { std::unique_lock _l(m_dlMgrMutex); m_dlMgr = dlMgr; } bool wxDownloadManagerList::StartDownloadEntry(const TitleEntry& entry) { std::unique_lock _l(m_dlMgrMutex); if (m_dlMgr) m_dlMgr->initiateDownload(entry.titleId, entry.version); return true; } bool wxDownloadManagerList::RetryDownloadEntry(const TitleEntry& entry) { StartDownloadEntry(entry); return true; } bool wxDownloadManagerList::PauseDownloadEntry(const TitleEntry& entry) { std::unique_lock _l(m_dlMgrMutex); if (m_dlMgr) m_dlMgr->pauseDownload(entry.titleId, entry.version); return true; } void wxDownloadManagerList::OnContextMenuSelected(wxCommandEvent& event) { // still doing work if (m_context_worker.valid() && !future_is_ready(m_context_worker)) return; const auto selection = GetFirstSelected(); if (selection == wxNOT_FOUND) return; const auto entry = GetTitleEntry(selection); if (!entry.has_value()) return; switch (event.GetId()) { case kContextMenuDownload: StartDownloadEntry(entry.value()); break; case kContextMenuRetry: RetryDownloadEntry(entry.value()); break; case kContextMenuResume: StartDownloadEntry(entry.value()); break; case kContextMenuPause: PauseDownloadEntry(entry.value()); break; } } void wxDownloadManagerList::OnTimer(wxTimerEvent& event) { if(event.GetTimer().GetId() != m_tooltip_timer->GetId()) { event.Skip(); return; } m_tooltip_window->Show(); } void wxDownloadManagerList::OnRemoveItem(wxCommandEvent& event) { RemoveItem(event.GetInt()); } void wxDownloadManagerList::OnRemoveEntry(wxCommandEvent& event) { wxASSERT(event.GetClientData() != nullptr); RemoveItem(*(TitleEntry*)event.GetClientData()); } wxString wxDownloadManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColumn column) { switch (column) { case ColumnTitleId: return formatWxString("{:08x}-{:08x}", (uint32) (entry.titleId >> 32), (uint32) (entry.titleId & 0xFFFFFFFF)); case ColumnName: return entry.name; case ColumnType: return GetTranslatedTitleEntryType(entry.type); case ColumnVersion: { // dont show version for base game unless it is not v0 if (entry.type == EntryType::Base && entry.version == 0) return ""; if (entry.type == EntryType::DLC && entry.version == 0) return ""; return formatWxString("v{}", entry.version); } case ColumnProgress: { if (entry.status == TitleDownloadStatus::Downloading) { if (entry.progress >= 1000) return "100%"; return formatWxString("{:.1f}%", (float) entry.progress / 10.0f); // one decimal } else if (entry.status == TitleDownloadStatus::Installing || entry.status == TitleDownloadStatus::Checking || entry.status == TitleDownloadStatus::Verifying) { return formatWxString("{0}/{1}", entry.progress, entry.progressMax); // number of processed files/content files } return ""; } case ColumnStatus: { if (entry.isPaused) return _("Paused"); else if (entry.status == TitleDownloadStatus::Available) { if (entry.progress == 1) return _("Not installed (Partially downloaded)"); if (entry.progress == 2) return _("Update available"); return _("Not installed"); } else if (entry.status == TitleDownloadStatus::Initializing) return _("Initializing"); else if (entry.status == TitleDownloadStatus::Checking) return _("Checking"); else if (entry.status == TitleDownloadStatus::Queued) return _("Queued"); else if (entry.status == TitleDownloadStatus::Downloading) return _("Downloading"); else if (entry.status == TitleDownloadStatus::Verifying) return _("Verifying"); else if (entry.status == TitleDownloadStatus::Installing) return _("Installing"); else if (entry.status == TitleDownloadStatus::Installed) return _("Installed"); else if (entry.status == TitleDownloadStatus::Error) { wxString errorStatusMsg = _("Error:"); errorStatusMsg.append(" "); errorStatusMsg.append(entry.errorMsg); return errorStatusMsg; } else return "Unknown status"; } } return wxEmptyString; } wxString wxDownloadManagerList::GetTranslatedTitleEntryType(EntryType type) { switch (type) { case EntryType::Base: return _("base"); case EntryType::Update: return _("update"); case EntryType::DLC: return _("DLC"); default: return std::to_string(static_cast>(type)); } } void wxDownloadManagerList::AddOrUpdateTitle(TitleEntryData_t* obj) { const auto& data = obj->GetData(); // if already in list only update auto entry = GetTitleEntry(data.titleId, data.version); if (entry.has_value()) { // update item entry.value() = data; RefreshPage(); // more efficient way to do this? return; } m_data.emplace_back(std::make_unique(true, data)); m_sorted_data.emplace_back(*m_data[m_data.size() - 1]); SetItemCount(m_data.size()); // reapply sort Filter2(m_filterShowTitles, m_filterShowUpdates, m_filterShowInstalled); SortEntries(); } void wxDownloadManagerList::UpdateTitleStatusDepr(TitleEntryData_t* obj, const wxString& text) { const auto& data = obj->GetData(); const auto entry = GetTitleEntry(data.titleId, data.version); // check if already added to list if (!entry.has_value()) return; // update gamelist text for(size_t i = 0; i < m_sorted_data.size(); ++i) { if (m_sorted_data[i].get().entry == data) { SetItem(i, ColumnStatus, text); return; } } cemuLog_logDebug(LogType::Force, "cant update title status of {:x}", data.titleId); } int wxDownloadManagerList::AddImage(const wxImage& image) const { return -1; // m_image_list->Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC)); } // return < bool wxDownloadManagerList::SortFunc(std::span sortColumnOrder, const Type_t& v1, const Type_t& v2) { cemu_assert_debug(sortColumnOrder.size() == 5); // visible have always priority if (!v1.get().visible && v2.get().visible) return false; else if (v1.get().visible && !v2.get().visible) return true; const auto& entry1 = v1.get().entry; const auto& entry2 = v2.get().entry; for (size_t i = 0; i < sortColumnOrder.size(); i++) { int sortByColumn = sortColumnOrder[i]; if (sortByColumn == ColumnTitleId) { // ensure strong ordering -> use type since only one entry should be now (should be changed if every save for every user is displayed spearately?) if (entry1.titleId != entry2.titleId) return entry1.titleId < entry2.titleId; } else if (sortByColumn == ColumnName) { const int tmp = entry1.name.CmpNoCase(entry2.name); if (tmp != 0) return tmp < 0; } else if (sortByColumn == ColumnType) { if (std::underlying_type_t(entry1.type) != std::underlying_type_t(entry2.type)) return std::underlying_type_t(entry1.type) < std::underlying_type_t(entry2.type); } else if (sortByColumn == ColumnVersion) { if (entry1.version != entry2.version) return entry1.version < entry2.version; } else if (sortByColumn == ColumnProgress) { if (entry1.progress != entry2.progress) return entry1.progress < entry2.progress; } else { cemu_assert_debug(false); return (uintptr_t)&entry1 < (uintptr_t)&entry2; } } return false; } #include void wxDownloadManagerList::SortEntries(int column) { boost::container::small_vector s_SortColumnOrder{ ColumnName, ColumnType, ColumnVersion, ColumnTitleId, ColumnProgress }; bool ascending; if (column == -1) { column = GetSortIndicator(); if (column == -1) column = ColumnName; ascending = IsAscendingSortIndicator(); } else ascending = GetUpdatedAscendingSortIndicator(column); // prioritize column by moving it to first position in the column sort order list s_SortColumnOrder.erase(std::remove(s_SortColumnOrder.begin(), s_SortColumnOrder.end(), column), s_SortColumnOrder.end()); s_SortColumnOrder.insert(s_SortColumnOrder.begin(), column); std::sort(m_sorted_data.begin(), m_sorted_data.end(), [this, &s_SortColumnOrder, ascending](const Type_t& v1, const Type_t& v2) -> bool { return ascending ? SortFunc(s_SortColumnOrder, v1, v2) : SortFunc(s_SortColumnOrder, v2, v1); }); ShowSortIndicator(column, ascending); RefreshPage(); } void wxDownloadManagerList::RefreshPage() { long item_count = GetItemCount(); if (item_count > 0) RefreshItems(GetTopItem(), std::min(item_count - 1, GetTopItem() + GetCountPerPage() + 1)); } int wxDownloadManagerList::Filter(const wxString& filter, const wxString& prefix, ItemColumn column) { if (prefix.empty()) return -1; if (!filter.StartsWith(prefix)) return -1; int counter = 0; const auto tmp_filter = filter.substr(prefix.size() - 1).Trim(false); for (auto&& data : m_data) { if (GetTitleEntryText(data->entry, column).Upper().Contains(tmp_filter)) { data->visible = true; ++counter; } else data->visible = false; } return counter; } void wxDownloadManagerList::Filter(const wxString& filter) { if(filter.empty()) { std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; }); SetItemCount(m_data.size()); RefreshPage(); return; } const auto filter_upper = filter.Upper().Trim(false).Trim(true); int counter = 0; if (const auto result = Filter(filter_upper, "TITLEID:", ColumnTitleId) != -1) counter = result; else if (const auto result = Filter(filter_upper, "NAME:", ColumnName) != -1) counter = result; else if (const auto result = Filter(filter_upper, "TYPE:", ColumnType) != -1) counter = result; //else if (const auto result = Filter(filter_upper, "REGION:", ColumnRegion) != -1) // counter = result; else if (const auto result = Filter(filter_upper, "VERSION:", ColumnVersion) != -1) counter = result; else if(filter_upper == "ERROR") { for (auto&& data : m_data) { bool visible = false; //if (data->entry.error != TitleError::None) // visible = true; data->visible = visible; if (visible) ++counter; } } else { for (auto&& data : m_data) { bool visible = false; if (data->entry.name.Upper().Contains(filter_upper)) visible = true; else if (GetTitleEntryText(data->entry, ColumnTitleId).Upper().Contains(filter_upper)) visible = true; else if (GetTitleEntryText(data->entry, ColumnType).Upper().Contains(filter_upper)) visible = true; data->visible = visible; if (visible) ++counter; } } SetItemCount(counter); RefreshPage(); } void wxDownloadManagerList::Filter2(bool showTitles, bool showUpdates, bool showInstalled) { m_filterShowTitles = showTitles; m_filterShowUpdates = showUpdates; m_filterShowInstalled = showInstalled; if (showTitles && showUpdates && showInstalled) { std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; }); SetItemCount(m_data.size()); RefreshPage(); return; } size_t counter = 0; for (auto&& data : m_data) { bool visible = false; TitleIdParser tParser(data->entry.titleId); bool isInstalled = data->entry.status == TitleDownloadStatus::Installed; if (tParser.IsBaseTitleUpdate()) { if (showUpdates && (showInstalled || !isInstalled)) visible = true; } else { if (showTitles && (showInstalled || !isInstalled)) visible = true; } data->visible = visible; if (visible) ++counter; } SetItemCount(counter); RefreshPage(); } size_t wxDownloadManagerList::GetCountByType(EntryType type) const { size_t result = 0; for(const auto& data : m_data) { if (data->entry.type == type) ++result; } return result; } void wxDownloadManagerList::ClearItems() { m_sorted_data.clear(); m_data.clear(); SetItemCount(0); RefreshPage(); } void wxDownloadManagerList::AutosizeColumns() { wxAutosizeColumns(this, ColumnTitleId, ColumnMAX - 1); } ================================================ FILE: src/gui/wxgui/components/wxDownloadManagerList.h ================================================ #pragma once #include "wxgui/helpers/wxCustomData.h" #include "config/CemuConfig.h" #include #include // std::optional doesn't support optional reference inner types yet #include #include class wxDownloadManagerList : public wxListView { friend class TitleManager; public: wxDownloadManagerList(wxWindow* parent, wxWindowID id = wxID_ANY); enum ItemColumn { ColumnTitleId = 0, ColumnName, ColumnVersion, ColumnType, ColumnProgress, ColumnStatus, ColumnMAX, }; enum class EntryType { Base, Update, DLC, }; enum class TitleDownloadStatus { None, Available, // available for download Error, // error state Queued, // queued for download Initializing, // downloading/parsing TMD Checking, // checking for previously downloaded files Downloading, Verifying, // verifying downloaded files Installing, Installed, // error state? }; void SortEntries(int column = -1); void RefreshPage(); void Filter(const wxString& filter); void Filter2(bool showTitles, bool showUpdates, bool showInstalled); [[nodiscard]] size_t GetCountByType(EntryType type) const; void ClearItems(); void AutosizeColumns(); struct TitleEntry { TitleEntry(const EntryType& type, bool isPackage, uint64 titleId, uint16 version, bool isPaused) : type(type), isPackage(isPackage), titleId(titleId), version(version), isPaused(isPaused) {} EntryType type; bool isPaused{}; int icon = -1; bool isPackage; uint64 titleId; wxString name; uint32_t version{ 0 }; uint32 progress; // downloading: in 1/10th of a percent, installing: number of files uint32 progressMax{ 0 }; CafeConsoleRegion region; TitleDownloadStatus status = TitleDownloadStatus::None; std::string errorMsg; bool operator==(const TitleEntry& e) const { return type == e.type && titleId == e.titleId && version == e.version; } bool operator!=(const TitleEntry& e) const { return !(*this == e); } }; boost::optional GetSelectedTitleEntry() const; private: void AddColumns(); int Filter(const wxString& filter, const wxString& prefix, ItemColumn column); boost::optional GetSelectedTitleEntry(); boost::optional GetTitleEntry(uint64 titleId, uint16 titleVersion); class wxPanel* m_tooltip_window; class wxStaticText* m_tooltip_text; class wxTimer* m_tooltip_timer; void OnClose(wxCloseEvent& event); void OnColumnClick(wxListEvent& event); void OnContextMenu(wxContextMenuEvent& event); void OnItemSelected(wxListEvent& event); void OnContextMenuSelected(wxCommandEvent& event); void OnTimer(class wxTimerEvent& event); void OnRemoveItem(wxCommandEvent& event); void OnRemoveEntry(wxCommandEvent& event); using TitleEntryData_t = wxCustomData; void AddOrUpdateTitle(TitleEntryData_t* obj); void UpdateTitleStatusDepr(TitleEntryData_t* obj, const wxString& text); int AddImage(const wxImage& image) const; wxString OnGetItemText(long item, long column) const override; wxItemAttr* OnGetItemAttr(long item) const override; [[nodiscard]] boost::optional GetTitleEntry(long item) const; [[nodiscard]] boost::optional GetTitleEntry(long item); //[[nodiscard]] boost::optional GetTitleEntry(const fs::path& path) const; //[[nodiscard]] boost::optional GetTitleEntry(const fs::path& path); void SetCurrentDownloadMgr(class DownloadManager* dlMgr); //bool FixEntry(TitleEntry& entry); //bool VerifyEntryFiles(TitleEntry& entry); bool StartDownloadEntry(const TitleEntry& entry); bool RetryDownloadEntry(const TitleEntry& entry); bool PauseDownloadEntry(const TitleEntry& entry); void RemoveItem(long item); void RemoveItem(const TitleEntry& entry); struct ItemData { ItemData(bool visible, const TitleEntry& entry) : visible(visible), entry(entry) {} bool visible; TitleEntry entry; }; using ItemDataPtr = std::unique_ptr; std::vector m_data; std::vector> m_sorted_data; bool m_filterShowTitles = true; bool m_filterShowUpdates = true; bool m_filterShowInstalled = true; DownloadManager* m_dlMgr{}; std::mutex m_dlMgrMutex; using Type_t = std::reference_wrapper; bool SortFunc(std::span sortColumnOrder, const Type_t& v1, const Type_t& v2); static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column); static wxString GetTranslatedTitleEntryType(EntryType entryType); std::future m_context_worker; }; ================================================ FILE: src/gui/wxgui/components/wxGameList.cpp ================================================ #include "wxgui/components/wxGameList.h" #include "wxgui/helpers/wxCustomData.h" #include "wxCemuConfig.h" #include "util/helpers/helpers.h" #include "wxgui/GameProfileWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" #include "Cafe/TitleList/GameInfo.h" #include "Cafe/TitleList/TitleList.h" #include "wxgui/CemuApp.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/MainWindow.h" #include "../wxHelper.h" #include "Cafe/IOSU/PDM/iosu_pdm.h" // for last played and play time #if BOOST_OS_WINDOWS // for shortcut creation #include #include #include #include #include #include #include #include #endif // public events wxDEFINE_EVENT(wxEVT_OPEN_SETTINGS, wxCommandEvent); wxDEFINE_EVENT(wxEVT_GAMELIST_BEGIN_UPDATE, wxCommandEvent); wxDEFINE_EVENT(wxEVT_GAMELIST_END_UPDATE, wxCommandEvent); wxDEFINE_EVENT(wxEVT_OPEN_GRAPHIC_PACK, wxTitleIdEvent); // internal events wxDEFINE_EVENT(wxEVT_GAME_ENTRY_ADDED_OR_REMOVED, wxTitleIdEvent); constexpr uint64 kDefaultEntryData = 0x1337; void _stripPathFilename(fs::path& path) { if (path.has_extension()) path = path.parent_path(); } std::vector _getCachesPaths(const TitleId& titleId) { std::vector cachePaths{ ActiveSettings::GetCachePath(L"shaderCache/driver/vk/{:016x}.bin", titleId), ActiveSettings::GetCachePath(L"shaderCache/precompiled/{:016x}_spirv.bin", titleId), ActiveSettings::GetCachePath(L"shaderCache/precompiled/{:016x}_gl.bin", titleId), ActiveSettings::GetCachePath(L"shaderCache/precompiled/{:016x}_air.bin", titleId), ActiveSettings::GetCachePath(L"shaderCache/transferable/{:016x}_shaders.bin", titleId), ActiveSettings::GetCachePath(L"shaderCache/transferable/{:016x}_mtlshaders.bin", titleId), ActiveSettings::GetCachePath(L"shaderCache/transferable/{:016x}_vkpipeline.bin", titleId), ActiveSettings::GetCachePath(L"shaderCache/transferable/{:016x}_mtlpipeline.bin", titleId)}; cachePaths.erase(std::remove_if(cachePaths.begin(), cachePaths.end(), [](const fs::path& cachePath) { std::error_code ec; return !fs::exists(cachePath, ec); }), cachePaths.end()); return cachePaths; } // Convert PNG to Apple icon image format bool writeICNS(const fs::path& pngPath, const fs::path& icnsPath) { // Read PNG file std::ifstream pngFile(pngPath, std::ios::binary); if (!pngFile) return false; // Get PNG size pngFile.seekg(0, std::ios::end); uint32 pngSize = static_cast(pngFile.tellg()); pngFile.seekg(0, std::ios::beg); // Calculate total file size (header + size + type + data) uint32 totalSize = 8 + 8 + pngSize; // Create output file std::ofstream icnsFile(icnsPath, std::ios::binary); if (!icnsFile) return false; // Write ICNS header icnsFile.put(0x69); // 'i' icnsFile.put(0x63); // 'c' icnsFile.put(0x6e); // 'n' icnsFile.put(0x73); // 's' // Write total file size (big endian) icnsFile.put((totalSize >> 24) & 0xFF); icnsFile.put((totalSize >> 16) & 0xFF); icnsFile.put((totalSize >> 8) & 0xFF); icnsFile.put(totalSize & 0xFF); // Write icon type (ic07 = 128x128 PNG) icnsFile.put(0x69); // 'i' icnsFile.put(0x63); // 'c' icnsFile.put(0x30); // '0' icnsFile.put(0x37); // '7' // Write PNG size (big endian) icnsFile.put((pngSize >> 24) & 0xFF); icnsFile.put((pngSize >> 16) & 0xFF); icnsFile.put((pngSize >> 8) & 0xFF); icnsFile.put(pngSize & 0xFF); // Copy PNG data icnsFile << pngFile.rdbuf(); return true; } wxGameList::wxGameList(wxWindow* parent, wxWindowID id) : wxListView(parent, id, wxDefaultPosition, wxDefaultSize, GetStyleFlags(Style::kList)), m_style(Style::kList) { const auto& config = GetWxGUIConfig(); char transparent_bitmap[kIconWidth * kIconWidth * 4] = {}; memset(transparent_bitmap, wxSystemSettings::GetAppearance().IsDark() ? 0xFF : 0x00, sizeof(transparent_bitmap)); wxBitmap blank(transparent_bitmap, kIconWidth, kIconWidth); m_image_list_data.Add(blank); wxListCtrl::SetImageList(&m_image_list_data, wxIMAGE_LIST_NORMAL); wxBitmap::Rescale(blank, {kListIconWidth, kListIconWidth}); m_image_list_small_data.Add(blank); wxListCtrl::SetImageList(&m_image_list_small_data, wxIMAGE_LIST_SMALL); InsertColumn(ColumnHiddenName, "", wxLIST_FORMAT_LEFT, 0); if(config.show_icon_column) InsertColumn(ColumnIcon, _("Icon"), wxLIST_FORMAT_LEFT, GetColumnDefaultWidth(ColumnIcon)); else InsertColumn(ColumnIcon, _("Icon"), wxLIST_FORMAT_LEFT, 0); InsertColumn(ColumnName, _("Game"), wxLIST_FORMAT_LEFT, config.column_width.name); InsertColumn(ColumnVersion, _("Version"), wxLIST_FORMAT_LEFT, config.column_width.version); InsertColumn(ColumnDLC, _("DLC"), wxLIST_FORMAT_LEFT, config.column_width.dlc); InsertColumn(ColumnGameTime, _("You've played"), wxLIST_FORMAT_LEFT, config.column_width.game_time); InsertColumn(ColumnGameStarted, _("Last played"), wxLIST_FORMAT_LEFT, config.column_width.game_started); InsertColumn(ColumnRegion, _("Region"), wxLIST_FORMAT_LEFT, config.column_width.region); InsertColumn(ColumnTitleID, _("Title ID"), wxLIST_FORMAT_LEFT, config.column_width.title_id); m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); auto* tooltip_sizer = new wxBoxSizer(wxVERTICAL); tooltip_sizer->Add(new wxStaticText(m_tooltip_window, wxID_ANY, _("This game entry seems to be either an update or the base game was merged with update data\nBroken game dumps cause various problems during emulation and may even stop working at all in future Cemu versions\nPlease make sure the base game is intact and install updates only with the File->Install Update/DLC option")), 0, wxALL, 5); m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); m_tooltip_window->SetSizerAndFit(tooltip_sizer); m_tooltip_window->Hide(); m_tooltip_timer = new wxTimer(this); Bind(wxEVT_CLOSE_WINDOW, &wxGameList::OnClose, this); Bind(wxEVT_MOTION, &wxGameList::OnMouseMove, this); Bind(wxEVT_LIST_KEY_DOWN, &wxGameList::OnKeyDown, this); Bind(wxEVT_CONTEXT_MENU, &wxGameList::OnContextMenu, this); Bind(wxEVT_LIST_ITEM_ACTIVATED, &wxGameList::OnItemActivated, this); Bind(wxEVT_GAME_ENTRY_ADDED_OR_REMOVED, &wxGameList::OnGameEntryUpdatedByTitleId, this); Bind(wxEVT_TIMER, &wxGameList::OnTimer, this); Bind(wxEVT_LEAVE_WINDOW, &wxGameList::OnLeaveWindow, this); Bind(wxEVT_LIST_COL_CLICK, &wxGameList::OnColumnClick, this); Bind(wxEVT_LIST_COL_BEGIN_DRAG, &wxGameList::OnColumnBeginResize, this); Bind(wxEVT_LIST_COL_END_DRAG, &wxGameList::OnColumnResize, this); Bind(wxEVT_LIST_COL_RIGHT_CLICK, &wxGameList::OnColumnRightClick, this); Bind(wxEVT_SIZE, &wxGameList::OnGameListSize, this); m_bulkUpdateTimer.Bind(wxEVT_TIMER, &wxGameList::OnTimerBulkAddEntriesToGameList, this); m_callbackIdTitleList = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((wxGameList*)ctx)->HandleTitleListCallback(evt); }, this); // start async worker (for icon loading) m_async_worker_active = true; m_async_worker_thread = std::thread(&wxGameList::AsyncWorkerThread, this); ShowSortIndicator(ColumnName); } wxGameList::~wxGameList() { CafeTitleList::UnregisterCallback(m_callbackIdTitleList); m_tooltip_timer->Stop(); // shut down async worker m_async_worker_active.store(false); m_async_task_count.increment(); m_async_worker_thread.join(); // clear image cache m_icon_cache_mtx.lock(); m_icon_cache.clear(); m_icon_cache_mtx.unlock(); } void wxGameList::LoadConfig() { const auto& config = GetWxGUIConfig(); SetStyle((Style)config.game_list_style, false); if (!config.game_list_column_order.empty()) { wxArrayInt order; order.reserve(ColumnCounts); const auto order_string = std::string_view(config.game_list_column_order).substr(1); const boost::char_separator sep(","); boost::tokenizer tokens(order_string.begin(), order_string.end(), sep); for(const auto& token : tokens) { order.push_back(ConvertString(token, 10)); } #ifdef wxHAS_LISTCTRL_COLUMN_ORDER if(order.GetCount() == ColumnCounts) SetColumnsOrder(order); #endif } } void wxGameList::OnGameListSize(wxSizeEvent &event) { event.Skip(); // when using a sizer-based layout, do not change the size of the wxComponent in its own wxSizeEvent handler to avoid some UI issues. int last_col_index = 0; for(int i = GetColumnCount() - 1; i > 0; i--) { #ifdef wxHAS_LISTCTRL_COLUMN_ORDER if(GetColumnWidth(GetColumnIndexFromOrder(i)) > 0) { last_col_index = GetColumnIndexFromOrder(i); break; } #else if(GetColumnWidth(i) > 0) { last_col_index = i; break; } #endif } wxListEvent column_resize_event(wxEVT_LIST_COL_END_DRAG); column_resize_event.SetColumn(last_col_index); wxPostEvent(this, column_resize_event); } void wxGameList::AdjustLastColumnWidth() { wxWindowUpdateLocker windowlock(this); int last_col_index = 0; int last_col_width = GetClientSize().GetWidth(); for (int i = 1; i < GetColumnCount(); i++) { #ifdef wxHAS_LISTCTRL_COLUMN_ORDER if (GetColumnWidth(GetColumnIndexFromOrder(i)) > 0) { last_col_index = GetColumnIndexFromOrder(i); last_col_width -= GetColumnWidth(last_col_index); } #else if (GetColumnWidth(i) > 0) { last_col_index = i; last_col_width -= GetColumnWidth(i); } #endif } last_col_width += GetColumnWidth(last_col_index); if (last_col_width < GetColumnDefaultWidth(last_col_index)) // keep a minimum width last_col_width = GetColumnDefaultWidth(last_col_index); SetColumnWidth(last_col_index, last_col_width); } // todo: scale all columns using a ratio instead of hardcoding exact widths int wxGameList::GetColumnDefaultWidth(int column) { switch (column) { case ColumnIcon: return kListIconWidth+2; case ColumnName: return DefaultColumnSize::name; case ColumnVersion: return DefaultColumnSize::version; case ColumnDLC: return DefaultColumnSize::dlc; case ColumnGameTime: return DefaultColumnSize::game_time; case ColumnGameStarted: return DefaultColumnSize::game_started; case ColumnRegion: return DefaultColumnSize::region; case ColumnTitleID: return DefaultColumnSize::title_id; default: return 80; } } void wxGameList::SaveConfig(bool flush) { auto& config = GetWxGUIConfig(); config.game_list_style = (int)m_style; #ifdef wxHAS_LISTCTRL_COLUMN_ORDER config.game_list_column_order = fmt::format("{}", GetColumnsOrder()); #endif if (flush) GetConfigHandle().Save(); } bool wxGameList::IsVisible(long item) const { wxRect itemRect; GetItemRect(item, itemRect); const wxRect clientRect = GetClientRect(); bool visible = clientRect.Intersects(itemRect); return visible; } void wxGameList::ReloadGameEntries() { wxWindowUpdateLocker windowlock(this); DeleteAllItems(); // tell the game list to rescan CafeTitleList::Refresh(); // resend notifications for all known titles by re-registering the callback CafeTitleList::UnregisterCallback(m_callbackIdTitleList); m_callbackIdTitleList = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((wxGameList*)ctx)->HandleTitleListCallback(evt); }, this); } long wxGameList::FindListItemByTitleId(uint64 title_id) const { for (int i = 0; i < GetItemCount(); ++i) { const auto id = (uint64)GetItemData(i); if (id == title_id) return i; } return wxNOT_FOUND; } // get title name with cache std::string wxGameList::GetNameByTitleId(uint64 titleId) { auto it = m_name_cache.find(titleId); if (it != m_name_cache.end()) return it->second; TitleInfo titleInfo; if (!CafeTitleList::GetFirstByTitleId(titleId, titleInfo)) return "Unknown title"; std::string name; if (!GetConfig().GetGameListCustomName(titleId, name)) name = titleInfo.GetMetaTitleName(); m_name_cache.emplace(titleId, name); return name; } void wxGameList::SetStyle(Style style, bool save) { if (m_style == style) return; wxWindowUpdateLocker updatelock(this); m_style = style; SetWindowStyleFlag(GetStyleFlags(m_style)); uint64 selected_title_id = 0; auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { selected_title_id = (uint64)GetItemData(selection); selection = wxNOT_FOUND; } switch(style) { case Style::kIcons: wxListCtrl::SetImageList(&m_image_list_data, wxIMAGE_LIST_NORMAL); break; case Style::kSmallIcons: wxListCtrl::SetImageList(&m_image_list_small_data, wxIMAGE_LIST_NORMAL); break; case Style::kList: wxListCtrl::SetImageList(&m_image_list_small_data, wxIMAGE_LIST_SMALL); break; } ReloadGameEntries(); SortEntries(); UpdateItemColors(); if(selection != wxNOT_FOUND) { Select(selection); Focus(selection); } if(save) { GetWxGUIConfig().game_list_style = (int)m_style; GetConfigHandle().Save(); } if (style == Style::kList) ApplyGameListColumnWidths(); } long wxGameList::GetStyleFlags(Style style) const { switch (style) { case Style::kList: return (wxLC_SINGLE_SEL | wxLC_VRULES | wxLC_REPORT); case Style::kIcons: return (wxLC_SINGLE_SEL | wxLC_ICON); case Style::kSmallIcons: return (wxLC_SINGLE_SEL | wxLC_ICON); default: wxASSERT(false); return (wxLC_SINGLE_SEL | wxLC_REPORT); } } void wxGameList::UpdateItemColors(sint32 startIndex) { wxWindowUpdateLocker lock(this); for (int i = startIndex; i < GetItemCount(); ++i) { const uint64 titleId = GetItemData(i); if (GetConfig().IsGameListFavorite(titleId)) { SetItemBackgroundColour(i, kFavoriteColor); SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); } else if ((i % 2) != 0) { SetItemBackgroundColour(i, kPrimaryColor); SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); } else { SetItemBackgroundColour(i, kAlternateColor); SetItemTextColour(i, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); } } } static inline int order_to_int(const std::weak_ordering &wo) { // no easy conversion seems to exist in C++20 if (wo == std::weak_ordering::less) return -1; else if (wo == std::weak_ordering::greater) return 1; return 0; } std::weak_ordering wxGameList::SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData) { auto titleLastPlayed = [](uint64_t id) { iosu::pdm::GameListStat playTimeStat{}; iosu::pdm::GetStatForGamelist(id, playTimeStat); return playTimeStat; }; auto titlePlayMinutes = [](uint64_t id) { iosu::pdm::GameListStat playTimeStat; if (!iosu::pdm::GetStatForGamelist(id, playTimeStat)) return 0u; return playTimeStat.numMinutesPlayed; }; auto titleRegion = [](uint64_t id) { return CafeTitleList::GetGameInfo(id).GetRegion(); }; switch(sortData->column) { default: case ColumnName: { const auto isFavoriteA = GetConfig().IsGameListFavorite(titleId1); const auto isFavoriteB = GetConfig().IsGameListFavorite(titleId2); const auto nameA = GetNameByTitleId(titleId1); const auto nameB = GetNameByTitleId(titleId2); return std::tie(isFavoriteB, nameA) <=> std::tie(isFavoriteA, nameB); } case ColumnGameStarted: return titleLastPlayed(titleId1).last_played <=> titleLastPlayed(titleId2).last_played; case ColumnGameTime: return titlePlayMinutes(titleId1) <=> titlePlayMinutes(titleId2); case ColumnRegion: return titleRegion(titleId1) <=> titleRegion(titleId2); case ColumnTitleID: return titleId1 <=> titleId2; } // unreachable cemu_assert_debug(false); return std::weak_ordering::less; } int wxGameList::SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) { const auto sort_data = (SortData*)sortData; return sort_data->dir * order_to_int(sort_data->thisptr->SortComparator((uint64)item1, (uint64)item2, sort_data)); } void wxGameList::SortEntries(int column) { bool ascending; if (column == -1) { column = GetSortIndicator(); if (column == -1) column = ColumnName; ascending = IsAscendingSortIndicator(); } else ascending = GetUpdatedAscendingSortIndicator(column); switch (column) { case ColumnName: case ColumnGameTime: case ColumnGameStarted: case ColumnRegion: case ColumnTitleID: { SortData data{this, ItemColumns{column}, ascending ? 1 : -1}; SortItems(SortFunction, (wxIntPtr)&data); ShowSortIndicator(column, ascending); break; } } } void wxGameList::OnKeyDown(wxListEvent& event) { event.Skip(); if (m_style != Style::kList) return; const auto keycode = event.GetKeyCode(); if (keycode == WXK_LEFT) { const auto item_count = GetItemCount(); if (item_count > 0) { auto selection = (int)GetFirstSelected(); if (selection == wxNOT_FOUND) selection = 0; else selection = std::max(0, selection - GetCountPerPage()); Select(selection); Focus(selection); } } else if (keycode == WXK_RIGHT) { const auto item_count = GetItemCount(); if (item_count > 0) { auto selection = (int)GetFirstSelected(); if (selection == wxNOT_FOUND) selection = 0; selection = std::min(item_count - 1, selection + GetCountPerPage()); Select(selection); Focus(selection); } } } enum ContextMenuEntries { kContextMenuRefreshGames = wxID_HIGHEST + 1, kContextMenuStart, kWikiPage, kContextMenuFavorite, kContextMenuEditName, kContextMenuGameFolder, kContextMenuSaveFolder, kContextMenuUpdateFolder, kContextMenuDLCFolder, kContextMenuEditGraphicPacks, kContextMenuEditGameProfile, kContextMenuRemoveCache, kContextMenuStyleList, kContextMenuStyleIcon, kContextMenuStyleIconSmall, kContextMenuCreateShortcut, kContextMenuCopyTitleName, kContextMenuCopyTitleId, kContextMenuCopyTitleImage }; void wxGameList::OnContextMenu(wxContextMenuEvent& event) { auto& config = GetConfig(); wxMenu menu; menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxGameList::OnContextMenuSelected, this); const auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { const auto title_id = (uint64)GetItemData(selection); GameInfo2 gameInfo = CafeTitleList::GetGameInfo(title_id); if (gameInfo.IsValid()) { menu.SetClientData((void*)title_id); menu.Append(kContextMenuStart, _("&Start")); bool isFavorite = GetConfig().IsGameListFavorite(title_id); std::error_code ec; menu.AppendSeparator(); menu.AppendCheckItem(kContextMenuFavorite, _("&Favorite"))->Check(isFavorite); menu.Append(kContextMenuEditName, _("&Edit name")); menu.AppendSeparator(); menu.Append(kWikiPage, _("&Wiki page")); menu.Append(kContextMenuGameFolder, _("&Game directory")); menu.Append(kContextMenuSaveFolder, _("&Save directory"))->Enable(fs::is_directory(gameInfo.GetSaveFolder(), ec)); menu.Append(kContextMenuUpdateFolder, _("&Update directory"))->Enable(gameInfo.HasUpdate()); menu.Append(kContextMenuDLCFolder, _("&DLC directory"))->Enable(gameInfo.HasAOC()); menu.AppendSeparator(); menu.Append(kContextMenuRemoveCache, _("&Remove shader caches"))->Enable(!_getCachesPaths(gameInfo.GetBaseTitleId()).empty()); menu.AppendSeparator(); menu.Append(kContextMenuEditGraphicPacks, _("&Edit graphic packs")); menu.Append(kContextMenuEditGameProfile, _("&Edit game profile")); menu.AppendSeparator(); menu.Append(kContextMenuCreateShortcut, _("&Create shortcut")); menu.AppendSeparator(); menu.Append(kContextMenuCopyTitleName, _("&Copy Title Name")); menu.Append(kContextMenuCopyTitleId, _("&Copy Title ID")); menu.Append(kContextMenuCopyTitleImage, _("&Copy Title Image")); menu.AppendSeparator(); } } menu.Append(kContextMenuRefreshGames, _("&Refresh game list"))->Enable(!CafeTitleList::IsScanning()); menu.AppendSeparator(); menu.AppendRadioItem(kContextMenuStyleList, _("Style: &List"))->Check(m_style == Style::kList); menu.AppendRadioItem(kContextMenuStyleIcon, _("Style: &Icons"))->Check(m_style == Style::kIcons); menu.AppendRadioItem(kContextMenuStyleIconSmall, _("Style: &Small Icons"))->Check(m_style == Style::kSmallIcons); PopupMenu(&menu); } void wxGameList::OnContextMenuSelected(wxCommandEvent& event) { const auto title_id = (uint64)((wxMenu*)event.GetEventObject())->GetClientData(); if (title_id) { GameInfo2 gameInfo = CafeTitleList::GetGameInfo(title_id); if (gameInfo.IsValid()) { switch (event.GetId()) { case kContextMenuStart: { MainWindow::RequestLaunchGame(gameInfo.GetBase().GetPath(), wxLaunchGameEvent::INITIATED_BY::GAME_LIST); break; } case kContextMenuFavorite: GetConfig().SetGameListFavorite(title_id, !GetConfig().IsGameListFavorite(title_id)); SortEntries(); UpdateItemColors(); SaveConfig(true); break; case kContextMenuEditName: { std::string customName = ""; if (!GetConfig().GetGameListCustomName(title_id, customName)) customName.clear(); wxTextEntryDialog dialog(this, wxEmptyString, _("Enter a custom game title"), wxString::FromUTF8(customName)); if(dialog.ShowModal() == wxID_OK) { const auto custom_name = dialog.GetValue(); GetConfig().SetGameListCustomName(title_id, custom_name.utf8_string()); m_name_cache.clear(); GetConfigHandle().Save(); // update list entry for (int i = 0; i < GetItemCount(); ++i) { const auto id = (uint64)GetItemData(i); if (id == title_id) { if (m_style == Style::kList) SetItem(i, ColumnName, wxString::FromUTF8(GetNameByTitleId(title_id))); break; } } SortEntries(); UpdateItemColors(); } break; } case kContextMenuGameFolder: { fs::path path(gameInfo.GetBase().GetPath()); _stripPathFilename(path); wxLaunchDefaultApplication(wxHelper::FromPath(path)); break; } case kWikiPage: { // https://wiki.cemu.info/wiki/GameIDs // WUP-P-ALZP if (gameInfo.GetBase().ParseXmlInfo()) { std::string productCode = gameInfo.GetBase().GetMetaInfo()->GetProductCode(); const auto tokens = TokenizeView(productCode, '-'); wxASSERT(!tokens.empty()); const std::string company_code = gameInfo.GetBase().GetMetaInfo()->GetCompanyCode(); wxASSERT(company_code.size() >= 2); wxLaunchDefaultBrowser(formatWxString("https://wiki.cemu.info/wiki/{}{}", *tokens.rbegin(), company_code.substr(company_code.size() - 2))); } break; } case kContextMenuSaveFolder: { wxLaunchDefaultApplication(wxHelper::FromPath(gameInfo.GetSaveFolder())); break; } case kContextMenuUpdateFolder: { fs::path path(gameInfo.GetUpdate().GetPath()); _stripPathFilename(path); wxLaunchDefaultApplication(wxHelper::FromPath(path)); break; } case kContextMenuDLCFolder: { fs::path path(gameInfo.GetAOC().front().GetPath()); _stripPathFilename(path); wxLaunchDefaultApplication(wxHelper::FromPath(path)); break; } case kContextMenuRemoveCache: { RemoveCache(_getCachesPaths(gameInfo.GetBaseTitleId()), gameInfo.GetTitleName()); break; } case kContextMenuEditGraphicPacks: { wxTitleIdEvent open_event(wxEVT_OPEN_GRAPHIC_PACK, title_id); ProcessWindowEvent(open_event); break; } case kContextMenuEditGameProfile: { (new GameProfileWindow(GetParent(), title_id))->Show(); break; } case kContextMenuCreateShortcut: { CreateShortcut(gameInfo); break; } case kContextMenuCopyTitleName: { if (wxClipboard::Get()->Open()) { wxClipboard::Get()->SetData(new wxTextDataObject(wxString::FromUTF8(gameInfo.GetTitleName()))); wxClipboard::Get()->Close(); } break; } case kContextMenuCopyTitleId: { if (wxClipboard::Get()->Open()) { wxClipboard::Get()->SetData(new wxTextDataObject(fmt::format("{:016x}", gameInfo.GetBaseTitleId()))); wxClipboard::Get()->Close(); } break; } case kContextMenuCopyTitleImage: { if (wxClipboard::Get()->Open()) { int icon_large; int icon_small; if (!QueryIconForTitle(title_id, icon_large, icon_small)) break; auto icon = m_image_list_data.GetIcon(icon_large); auto newClipboardData = wxBitmapDataObject(icon); wxClipboard::Get()->SetData(&newClipboardData); wxClipboard::Get()->Close(); } break; } } } } switch (event.GetId()) { case kContextMenuRefreshGames: ReloadGameEntries(); break; case kContextMenuStyleList: SetStyle(Style::kList); break; case kContextMenuStyleIcon: SetStyle(Style::kIcons); break; case kContextMenuStyleIconSmall: SetStyle(Style::kSmallIcons); break; } } void wxGameList::OnColumnClick(wxListEvent& event) { const int column = event.GetColumn(); SortEntries(column); UpdateItemColors(); event.Skip(); } void wxGameList::OnColumnRightClick(wxListEvent& event) { enum ItemIds { ResetWidth = wxID_HIGHEST + 1, ResetOrder, ShowIcon, ShowName, ShowVersion, ShowDlc, ShowGameTime, ShowLastPlayed, ShowRegion, ShowTitleId }; const int column = event.GetColumn(); wxMenu menu; menu.SetClientObject(new wxCustomData(column)); menu.Append(ResetWidth, _("Reset &width")); menu.Append(ResetOrder, _("Reset &order")) ; menu.AppendSeparator(); menu.AppendCheckItem(ShowIcon, _("Show &icon"))->Check(GetColumnWidth(ColumnIcon) > 0); menu.AppendCheckItem(ShowName, _("Show &name"))->Check(GetColumnWidth(ColumnName) > 0); menu.AppendCheckItem(ShowVersion, _("Show &version"))->Check(GetColumnWidth(ColumnVersion) > 0); menu.AppendCheckItem(ShowDlc, _("Show &dlc"))->Check(GetColumnWidth(ColumnDLC) > 0); menu.AppendCheckItem(ShowGameTime, _("Show &game time"))->Check(GetColumnWidth(ColumnGameTime) > 0); menu.AppendCheckItem(ShowLastPlayed, _("Show &last played"))->Check(GetColumnWidth(ColumnGameStarted) > 0); menu.AppendCheckItem(ShowRegion, _("Show ®ion"))->Check(GetColumnWidth(ColumnRegion) > 0); menu.AppendCheckItem(ShowTitleId, _("Show &title ID"))->Check(GetColumnWidth(ColumnTitleID) > 0); menu.Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& event) { event.Skip(); const auto menu = dynamic_cast(event.GetEventObject()); const int column = dynamic_cast*>(menu->GetClientObject())->GetData(); auto& config = GetWxGUIConfig(); switch (event.GetId()) { case ShowIcon: config.show_icon_column = menu->IsChecked(ShowIcon); break; case ShowName: config.column_width.name = menu->IsChecked(ShowName) ? DefaultColumnSize::name : 0; break; case ShowVersion: config.column_width.version = menu->IsChecked(ShowVersion) ? DefaultColumnSize::version : 0; break; case ShowDlc: config.column_width.dlc = menu->IsChecked(ShowDlc) ? DefaultColumnSize::dlc : 0; break; case ShowGameTime: config.column_width.game_time = menu->IsChecked(ShowGameTime) ? DefaultColumnSize::game_time : 0; break; case ShowLastPlayed: config.column_width.game_started = menu->IsChecked(ShowLastPlayed) ? DefaultColumnSize::game_started : 0; break; case ShowRegion: config.column_width.region = menu->IsChecked(ShowRegion) ? DefaultColumnSize::region : 0; break; case ShowTitleId: config.column_width.title_id = menu->IsChecked(ShowTitleId) ? DefaultColumnSize::title_id : 0; break; case ResetWidth: { switch (column) { case ColumnIcon: break; case ColumnName: config.column_width.name = DefaultColumnSize::name; break; case ColumnVersion: config.column_width.version = DefaultColumnSize::version; break; case ColumnDLC: config.column_width.dlc = DefaultColumnSize::dlc; break; case ColumnGameTime: config.column_width.game_time = DefaultColumnSize::game_time; break; case ColumnGameStarted: config.column_width.game_started = DefaultColumnSize::game_started; break; case ColumnRegion: config.column_width.region = DefaultColumnSize::region; break; case ColumnTitleID: config.column_width.title_id = DefaultColumnSize::title_id; default: return; } break; } case ResetOrder: { config.game_list_column_order.clear(); wxArrayInt order(ColumnCounts); std::iota(order.begin(), order.end(), 0); #ifdef wxHAS_LISTCTRL_COLUMN_ORDER SetColumnsOrder(order); #endif //ApplyGameListColumnWidths(); //Refresh(); //return; } } GetConfigHandle().Save(); ApplyGameListColumnWidths(); }); PopupMenu(&menu); event.Skip(); } void wxGameList::ApplyGameListColumnWidths() { const auto& config = GetWxGUIConfig(); wxWindowUpdateLocker lock(this); if(config.show_icon_column) SetColumnWidth(ColumnIcon, kListIconWidth+2); else SetColumnWidth(ColumnIcon, 0); SetColumnWidth(ColumnName, config.column_width.name); SetColumnWidth(ColumnVersion, config.column_width.version); SetColumnWidth(ColumnDLC, config.column_width.dlc); SetColumnWidth(ColumnGameTime, config.column_width.game_time); SetColumnWidth(ColumnGameStarted, config.column_width.game_started); SetColumnWidth(ColumnRegion, config.column_width.region); SetColumnWidth(ColumnTitleID, config.column_width.title_id); AdjustLastColumnWidth(); } void wxGameList::OnColumnBeginResize(wxListEvent& event) { const int column = event.GetColumn(); const int width = GetColumnWidth(column); int last_col_index = 0; for(int i = GetColumnCount() - 1; i > 0; i--) { #ifdef wxHAS_LISTCTRL_COLUMN_ORDER if(GetColumnWidth(GetColumnIndexFromOrder(i)) > 0) { last_col_index = GetColumnIndexFromOrder(i); break; } #else if(GetColumnWidth(i) > 0) { last_col_index = i; break; } #endif } if (width == 0 || column == ColumnIcon || column == last_col_index) // dont resize hidden name, icon, and last column event.Veto(); else event.Skip(); } void wxGameList::OnColumnResize(wxListEvent& event) { event.Skip(); if(m_style != Style::kList) return; const int column = event.GetColumn(); const int width = GetColumnWidth(column); auto& config = GetWxGUIConfig(); switch (column) { case ColumnName: config.column_width.name = width; break; case ColumnVersion: config.column_width.version = width; break; case ColumnDLC: config.column_width.dlc = width; break; case ColumnGameTime: config.column_width.game_time = width; break; case ColumnGameStarted: config.column_width.game_started = width; break; case ColumnRegion: config.column_width.region = width; break; default: break; } GetConfigHandle().Save(); AdjustLastColumnWidth(); } void wxGameList::OnClose(wxCloseEvent& event) { event.Skip(); m_exit = true; } int wxGameList::FindInsertPosition(TitleId titleId, bool& entryAlreadyExists) { entryAlreadyExists = false; SortData data{this, ItemColumns(GetSortIndicator()), IsAscendingSortIndicator()}; const auto itemCount = GetItemCount(); if (itemCount == 0) return 0; sint32 low = 0; sint32 high = itemCount; while (low < high) { sint32 mid = low + (high - low) / 2; auto cmp = SortComparator(titleId, (uint64)GetItemData(mid), &data); if (cmp <= 0) { if (cmp == 0) { entryAlreadyExists = true; return mid; } high = mid; } else { low = mid + 1; } } return low; } void wxGameList::OnTimerBulkAddEntriesToGameList(wxTimerEvent& event) { std::vector titleIdsToUpdate; std::swap(titleIdsToUpdate, m_bulkTitlesToAdd); wxWindowUpdateLocker lock(this); bool hasAnyNewEntry = false; for (auto& titleId : titleIdsToUpdate) { GameInfo2 gameInfo = CafeTitleList::GetGameInfo(titleId); if (!gameInfo.IsValid() || gameInfo.IsSystemDataTitle()) { // entry no longer exists or is not a valid game // we dont need to remove list entries here because all delete operations should trigger a full list refresh continue; } TitleId baseTitleId = gameInfo.GetBaseTitleId(); bool isNewEntry = false; int icon = -1; /* 0 is the default empty icon */ int icon_small = -1; /* 0 is the default empty icon */ QueryIconForTitle(baseTitleId, icon, icon_small); bool entryAlreadyExists = false; auto index = FindInsertPosition(baseTitleId, entryAlreadyExists); if(!entryAlreadyExists) { // entry doesn't exist index = InsertItem(index, wxString::FromUTF8(GetNameByTitleId(baseTitleId))); SetItemPtrData(index, baseTitleId); isNewEntry = true; hasAnyNewEntry = true; } if (m_style == Style::kList) { SetItemColumnImage(index, ColumnIcon, icon_small); SetItem(index, ColumnName, wxString::FromUTF8(GetNameByTitleId(baseTitleId))); SetItem(index, ColumnVersion, fmt::format("{}", gameInfo.GetVersion())); if(gameInfo.HasAOC()) SetItem(index, ColumnDLC, fmt::format("{}", gameInfo.GetAOCVersion())); else SetItem(index, ColumnDLC, wxString()); if (isNewEntry) { iosu::pdm::GameListStat playTimeStat; if (iosu::pdm::GetStatForGamelist(baseTitleId, playTimeStat)) { // time played uint32 minutesPlayed = playTimeStat.numMinutesPlayed; if (minutesPlayed == 0) SetItem(index, ColumnGameTime, wxEmptyString); else if (minutesPlayed < 60) SetItem(index, ColumnGameTime, formatWxString(wxPLURAL("{} minute", "{} minutes", minutesPlayed), minutesPlayed)); else { uint32 hours = minutesPlayed / 60; uint32 minutes = minutesPlayed % 60; wxString hoursText = formatWxString(wxPLURAL("{} hour", "{} hours", hours), hours); wxString minutesText = formatWxString(wxPLURAL("{} minute", "{} minutes", minutes), minutes); SetItem(index, ColumnGameTime, hoursText + " " + minutesText); } // last played if (playTimeStat.last_played.year != 0) { const wxDateTime tmp((wxDateTime::wxDateTime_t)playTimeStat.last_played.day, (wxDateTime::Month)playTimeStat.last_played.month, (wxDateTime::wxDateTime_t)playTimeStat.last_played.year, 0, 0, 0, 0); SetItem(index, ColumnGameStarted, tmp.FormatDate()); } else SetItem(index, ColumnGameStarted, _("never")); } else { SetItem(index, ColumnGameTime, wxEmptyString); SetItem(index, ColumnGameStarted, _("never")); } } const auto region_text = fmt::format("{}", gameInfo.GetRegion()); SetItem(index, ColumnRegion, wxGetTranslation(region_text)); SetItem(index, ColumnTitleID, fmt::format("{:016x}", baseTitleId)); } else if (m_style == Style::kIcons) { SetItemImage(index, icon); } else if (m_style == Style::kSmallIcons) { SetItemImage(index, icon_small); } } if (hasAnyNewEntry) UpdateItemColors(); } void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event) { if (m_bulkTitlesToAdd.size() < 100) m_bulkUpdateTimer.StartOnce(100); // if timer is started already this will delay it const auto titleId = event.GetTitleId(); m_bulkTitlesToAdd.emplace_back(titleId); } void wxGameList::OnItemActivated(wxListEvent& event) { event.Skip(); const auto selection = event.GetIndex(); if (selection == wxNOT_FOUND) return; const auto item_data = (uint64)GetItemData(selection); if(item_data == kDefaultEntryData) { const wxCommandEvent open_settings_event(wxEVT_OPEN_SETTINGS); wxPostEvent(this, open_settings_event); return; } TitleInfo titleInfo; if (!CafeTitleList::GetFirstByTitleId(item_data, titleInfo)) return; if (!titleInfo.IsValid()) return; MainWindow::RequestLaunchGame(titleInfo.GetPath(), wxLaunchGameEvent::INITIATED_BY::GAME_LIST); } void wxGameList::OnTimer(wxTimerEvent& event) { const auto& obj = event.GetTimer().GetId(); if(obj == m_tooltip_timer->GetId()) { m_tooltip_window->Hide(); auto flag = wxLIST_HITTEST_ONITEM; const auto item = this->HitTest(m_mouse_position, flag); if(item != wxNOT_FOUND ) { //const auto title_id = (uint64_t)GetItemData(item); //auto entry = GetGameEntry(title_id); //if (entry && entry->is_update) //{ // m_tooltip_window->SetPosition(wxPoint(m_mouse_position.x + 15, m_mouse_position.y + 15)); // m_tooltip_window->SendSizeEvent(); // m_tooltip_window->Show(); //} } } } void wxGameList::OnMouseMove(wxMouseEvent& event) { m_tooltip_timer->Stop(); m_tooltip_timer->StartOnce(250); m_mouse_position = event.GetPosition(); } void wxGameList::OnLeaveWindow(wxMouseEvent& event) { m_tooltip_timer->Stop(); m_tooltip_window->Hide(); } void wxGameList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt) { if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED || evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_REMOVED) { wxQueueEvent(this, new wxTitleIdEvent(wxEVT_GAME_ENTRY_ADDED_OR_REMOVED, evt->titleInfo->GetAppTitleId())); } } void wxGameList::RemoveCache(const std::vector& cachePaths, const std::string& titleName) { wxMessageDialog dialog(this, formatWxString(_("Remove the shader caches for {}?"), titleName), _("Remove shader caches"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION); dialog.SetYesNoLabels(_("Yes"), _("No")); const auto dialogResult = dialog.ShowModal(); if (dialogResult != wxID_YES) return; std::vector errs; for (const fs::path& cachePath : cachePaths) { if (std::error_code ec; !fs::remove(cachePath, ec)) errs.emplace_back(fmt::format("{} : {}", cachePath.string(), ec.message())); } if (errs.empty()) wxMessageDialog(this, _("The shader caches were removed!"), _("Shader caches removed"), wxCENTRE | wxOK | wxICON_INFORMATION).ShowModal(); else wxMessageDialog(this, formatWxString(_("Failed to remove the shader caches:\n{}"), fmt::join(errs, "\n")), _("Error"), wxCENTRE | wxOK | wxICON_ERROR).ShowModal(); } void wxGameList::AsyncWorkerThread() { SetThreadName("GameListWorker"); while (m_async_worker_active) { m_async_task_count.decrementWithWait(); // get next titleId to load (if any) m_async_worker_mutex.lock(); bool hasJob = !m_icon_load_queue.empty(); TitleId titleId = 0; if (hasJob) { titleId = m_icon_load_queue.front(); m_icon_load_queue.erase(m_icon_load_queue.begin()); } m_async_worker_mutex.unlock(); if (!hasJob) continue; if(m_icon_loaded.find(titleId) != m_icon_loaded.end()) continue; m_icon_loaded.emplace(titleId); // load and process icon TitleInfo titleInfo; if( !CafeTitleList::GetFirstByTitleId(titleId, titleInfo) ) continue; std::string tempMountPath = TitleInfo::GetUniqueTempMountingPath(); if(!titleInfo.Mount(tempMountPath, "", FSC_PRIORITY_BASE)) continue; auto tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga").c_str()); // try iconTex.tga.gz if (!tgaData) { tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga.gz").c_str()); if (tgaData) { auto decompressed = zlibDecompress(*tgaData, 70*1024); std::swap(tgaData, decompressed); } } bool iconSuccessfullyLoaded = false; if (tgaData && tgaData->size() > 16) { wxMemoryInputStream tmp_stream(tgaData->data(), tgaData->size()); const wxImage image(tmp_stream); // todo - is wxImageList thread safe? int icon = m_image_list_data.Add(image.Scale(kIconWidth, kIconWidth, wxIMAGE_QUALITY_BICUBIC)); int icon_small = m_image_list_small_data.Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC)); // store in cache m_icon_cache_mtx.lock(); m_icon_cache.try_emplace(titleId, icon, icon_small); m_icon_cache_mtx.unlock(); iconSuccessfullyLoaded = true; } else { cemuLog_log(LogType::Force, "Failed to load icon for title {:016x}", titleId); } titleInfo.Unmount(tempMountPath); // notify UI about loaded icon if(iconSuccessfullyLoaded) wxQueueEvent(this, new wxTitleIdEvent(wxEVT_GAME_ENTRY_ADDED_OR_REMOVED, titleId)); } } void wxGameList::RequestLoadIconAsync(TitleId titleId) { m_async_worker_mutex.lock(); m_icon_load_queue.push_back(titleId); m_async_worker_mutex.unlock(); m_async_task_count.increment(); } // returns icons if cached, otherwise an async request to load them is made bool wxGameList::QueryIconForTitle(TitleId titleId, int& icon, int& iconSmall) { m_icon_cache_mtx.lock(); auto it = m_icon_cache.find(titleId); if (it == m_icon_cache.end()) { m_icon_cache_mtx.unlock(); RequestLoadIconAsync(titleId); return false; } icon = it->second.first; iconSmall = it->second.second; m_icon_cache_mtx.unlock(); return true; } void wxGameList::DeleteCachedStrings() { m_name_cache.clear(); } #if BOOST_OS_LINUX || BOOST_OS_BSD void wxGameList::CreateShortcut(GameInfo2& gameInfo) { const auto titleId = gameInfo.GetBaseTitleId(); const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName()); auto exePath = ActiveSettings::GetExecutablePath(); const char* flatpakId = getenv("FLATPAK_ID"); const wxString desktopEntryName = wxString::Format("%s.desktop", titleName); wxFileDialog entryDialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktopEntryName, "Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); const auto result = entryDialog.ShowModal(); if (result == wxID_CANCEL) return; const auto output_path = entryDialog.GetPath(); std::optional iconPath; // Obtain and convert icon [&]() { int iconIdx, smallIconIdx; if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx)) { cemuLog_log(LogType::Force, "Icon hasn't loaded"); return; } const fs::path outIconDir = ActiveSettings::GetUserDataPath("icons"); if (!fs::exists(outIconDir) && !fs::create_directories(outIconDir)) { cemuLog_log(LogType::Force, "Failed to create icon directory"); return; } iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value())); const auto icon = m_image_list_data.GetIcon(iconIdx); wxBitmap bitmap{icon}; wxImage image = bitmap.ConvertToImage(); wxPNGHandler pngHandler; if (!pngHandler.SaveFile(&image, pngFileStream, false)) { iconPath = std::nullopt; cemuLog_log(LogType::Force, "Icon failed to save"); } }(); std::string desktopExecEntry = flatpakId ? fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpakId, titleId) : fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exePath), titleId); // 'Icon' accepts spaces in file name, does not accept quoted file paths // 'Exec' does not accept non-escaped spaces, and can accept quoted file paths auto desktopEntryString = fmt::format( "[Desktop Entry]\n" "Name={0}\n" "Comment=Play {0} on Cemu\n" "Exec={1}\n" "Icon={2}\n" "Terminal=false\n" "Type=Application\n" "Categories=Game;\n", titleName.utf8_string(), desktopExecEntry, _pathToUtf8(iconPath.value_or(""))); if (flatpakId) desktopEntryString += fmt::format("X-Flatpak={}\n", flatpakId); std::ofstream outputStream(output_path.utf8_string()); if (!outputStream.good()) { auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string()); wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return; } outputStream << desktopEntryString; } #elif BOOST_OS_MACOS void wxGameList::CreateShortcut(GameInfo2& gameInfo) { const auto titleId = gameInfo.GetBaseTitleId(); const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName()); auto exePath = ActiveSettings::GetExecutablePath(); const wxString appName = wxString::Format("%s.app", titleName); wxFileDialog entryDialog(this, _("Choose shortcut location"), "~/Applications", appName, "Application (*.app)|*.app", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); const auto result = entryDialog.ShowModal(); if (result == wxID_CANCEL) return; const auto output_path = entryDialog.GetPath(); // Create .app folder const fs::path appPath = output_path.utf8_string(); if (!fs::create_directories(appPath)) { cemuLog_log(LogType::Force, "Failed to create app directory"); return; } const fs::path infoPath = appPath / "Contents/Info.plist"; const fs::path scriptPath = appPath / "Contents/MacOS/run.sh"; const fs::path icnsPath = appPath / "Contents/Resources/shortcut.icns"; if (!(fs::create_directories(scriptPath.parent_path()) && fs::create_directories(icnsPath.parent_path()))) { cemuLog_log(LogType::Force, "Failed to create app shortcut directories"); return; } std::optional iconPath; // Obtain and convert icon [&]() { int iconIdx, smallIconIdx; if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx)) { cemuLog_log(LogType::Force, "Icon hasn't loaded"); return; } const fs::path outIconDir = fs::temp_directory_path(); if (!fs::exists(outIconDir) && !fs::create_directories(outIconDir)) { cemuLog_log(LogType::Force, "Failed to create icon directory"); return; } iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value())); const auto icon = m_image_list_data.GetIcon(iconIdx); wxBitmap bitmap{icon}; wxImage image = bitmap.ConvertToImage(); wxPNGHandler pngHandler; if (!pngHandler.SaveFile(&image, pngFileStream, false)) { iconPath = std::nullopt; cemuLog_log(LogType::Force, "Icon failed to save"); } }(); std::string runCommand = fmt::format("#!/bin/zsh\n\n{0:?} --title-id {1:016x}", _pathToUtf8(exePath), titleId); const std::string infoPlist = fmt::format( "\n" "\n" "\n" "\n" " CFBundleDisplayName\n" " {0}\n" " CFBundleExecutable\n" " run.sh\n" " CFBundleIconFile\n" " shortcut.icns\n" " CFBundleName\n" " {0}\n" " CFBundlePackageType\n" " APPL\n" " CFBundleSignature\n" " \?\?\?\?\n" " LSApplicationCategoryType\n" " public.app-category.games\n" " CFBundleShortVersionString\n" " {1}\n" " CFBundleVersion\n" " {1}\n" "\n" "\n", gameInfo.GetTitleName(), std::to_string(gameInfo.GetVersion()) ); // write Info.plist to infoPath std::ofstream infoStream(infoPath); std::ofstream scriptStream(scriptPath); if (!infoStream.good() || !scriptStream.good()) { auto errorMsg = formatWxString(_("Failed to save app shortcut to {}"), output_path.utf8_string()); wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return; } infoStream << infoPlist; scriptStream << runCommand; scriptStream.close(); // Set execute permissions for script fs::permissions( scriptPath, fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec, fs::perm_options::add ); // Return if iconPath is empty if (!iconPath) { cemuLog_log(LogType::Force, "Icon not found"); return; } // Convert icon to icns, only works for 128x128 PNG // Alternatively, can run the command "sips -s format icns {iconPath} --out '{icnsPath}'" // using std::system() to handle images of any size if (!writeICNS(*iconPath, icnsPath)) { cemuLog_log(LogType::Force, "Failed to convert icon to icns"); return; } // Remove temp file fs::remove(*iconPath); } #elif BOOST_OS_WINDOWS void wxGameList::CreateShortcut(GameInfo2& gameInfo) { const auto titleId = gameInfo.GetBaseTitleId(); const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName()); auto exePath = ActiveSettings::GetExecutablePath(); // Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path PWSTR userShortcutFolder; SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &userShortcutFolder); const wxString shortcutName = wxString::Format("%s.lnk", titleName); wxFileDialog shortcutDialog(this, _("Choose shortcut location"), userShortcutFolder, shortcutName, "Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); CoTaskMemFree(userShortcutFolder); const auto result = shortcutDialog.ShowModal(); if (result == wxID_CANCEL) return; const auto outputPath = shortcutDialog.GetPath(); std::optional icon_path = std::nullopt; { int iconIdx; int smallIconIdx; if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx)) { cemuLog_log(LogType::Force, "Icon hasn't loaded"); return; } const auto icon = m_image_list_data.GetIcon(iconIdx); const auto folder = ActiveSettings::GetUserDataPath("icons"); if (!fs::exists(folder) && !fs::create_directories(folder)) { cemuLog_log(LogType::Force, "Failed to create icon directory"); return; } wxBitmap bitmap{icon}; icon_path = folder / fmt::format("{:016x}.ico", titleId); auto stream = wxFileOutputStream(icon_path->wstring()); auto image = bitmap.ConvertToImage(); wxICOHandler icohandler{}; if (!icohandler.SaveFile(&image, stream, false)) { icon_path = std::nullopt; cemuLog_log(LogType::Force, "Icon failed to save"); } } Microsoft::WRL::ComPtr shellLink; HRESULT hres = CoCreateInstance(__uuidof(ShellLink), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); if (SUCCEEDED(hres)) { const auto description = wxString::Format("Play %s on Cemu", titleName); const auto args = wxString::Format("-t %016llx", titleId); shellLink->SetPath(exePath.wstring().c_str()); shellLink->SetDescription(description.wc_str()); shellLink->SetArguments(args.wc_str()); shellLink->SetWorkingDirectory(exePath.parent_path().wstring().c_str()); if (icon_path) shellLink->SetIconLocation(icon_path->wstring().c_str(), 0); else shellLink->SetIconLocation(exePath.wstring().c_str(), 0); Microsoft::WRL::ComPtr shellLinkFile; // save the shortcut hres = shellLink.As(&shellLinkFile); if (SUCCEEDED(hres)) { hres = shellLinkFile->Save(outputPath.wc_str(), TRUE); } } if (FAILED(hres)) { auto errorMsg = formatWxString(_("Failed to save shortcut to {}"), outputPath); wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); } } #endif ================================================ FILE: src/gui/wxgui/components/wxGameList.h ================================================ #pragma once #include "config/CemuConfig.h" #include "Cafe/TitleList/TitleId.h" #include #include #include #include #include #include "wxHelper.h" #include "util/helpers/Semaphore.h" class wxTitleIdEvent : public wxCommandEvent { public: wxTitleIdEvent(int type, uint64 title_id) : wxCommandEvent(type), m_title_id(title_id) {} [[nodiscard]] uint64 GetTitleId() const { return m_title_id; } private: uint64 m_title_id; }; wxDECLARE_EVENT(wxEVT_OPEN_SETTINGS, wxCommandEvent); wxDECLARE_EVENT(wxEVT_OPEN_GRAPHIC_PACK, wxTitleIdEvent); wxDECLARE_EVENT(wxEVT_GAMELIST_BEGIN_UPDATE, wxCommandEvent); wxDECLARE_EVENT(wxEVT_GAMELIST_END_UPDATE, wxCommandEvent); class wxGameList : public wxListView { friend class MainWindow; public: enum class Style { kList, kIcons, kSmallIcons }; wxGameList(wxWindow* parent, wxWindowID id = wxID_ANY); ~wxGameList(); void SetStyle(Style style, bool save = true); void LoadConfig(); void SaveConfig(bool flush = false); bool IsVisible(long item) const; // only available in wxwidgets 3.1.3 void ReloadGameEntries(); void DeleteCachedStrings(); void CreateShortcut(GameInfo2& gameInfo); long FindListItemByTitleId(uint64 title_id) const; void OnClose(wxCloseEvent& event); private: std::atomic_bool m_exit = false; Style m_style; long GetStyleFlags(Style style) const; const wxColour kUpdateColor{ wxSystemSettings::SelectLightDark(wxColour(195, 57, 57), wxColour(84, 29, 29)) }; const wxColour kFavoriteColor{ wxSystemSettings::SelectLightDark(wxColour(253, 246, 211), wxColour(82, 84, 48)) }; const wxColour kPrimaryColor = GetBackgroundColour(); const wxColour kAlternateColor = wxHelper::CalculateAccentColour(kPrimaryColor); void UpdateItemColors(sint32 startIndex = 0); enum ItemColumns : int { ColumnHiddenName = 0, ColumnIcon, ColumnName, ColumnVersion, ColumnDLC, ColumnGameTime, ColumnGameStarted, ColumnRegion, ColumnTitleID, //ColumnFavorite, ColumnCounts, }; void SortEntries(int column = -1); struct SortData { wxGameList* thisptr; ItemColumns column; int dir; }; int FindInsertPosition(TitleId titleId, bool& entryAlreadyExists); std::weak_ordering SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData); static int SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData); wxTimer* m_tooltip_timer; wxToolTip* m_tool_tip; wxPanel* m_tooltip_window; wxPoint m_mouse_position; std::mutex m_async_worker_mutex; std::thread m_async_worker_thread; CounterSemaphore m_async_task_count; std::atomic_bool m_async_worker_active{false}; std::vector m_icon_load_queue; uint64 m_callbackIdTitleList; std::string GetNameByTitleId(uint64 titleId); void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt); void RemoveCache(const std::vector& cachePath, const std::string& titleName); void AsyncWorkerThread(); void RequestLoadIconAsync(TitleId titleId); bool QueryIconForTitle(TitleId titleId, int& icon, int& iconSmall); inline static constexpr int kListIconWidth = 64; inline static constexpr int kIconWidth = 128; wxImageList m_image_list_data = wxImageList(kIconWidth, kIconWidth, false, 1); wxImageList m_image_list_small_data = wxImageList(kListIconWidth, kListIconWidth, false, 1); std::mutex m_icon_cache_mtx; std::set m_icon_loaded; std::map> m_icon_cache; // pair contains icon and small icon std::map m_name_cache; // bulk update handling std::vector m_bulkTitlesToAdd; wxTimer m_bulkUpdateTimer; // list mode void CreateListColumns(); void OnColumnClick(wxListEvent& event); void OnColumnRightClick(wxListEvent& event); void ApplyGameListColumnWidths(); void OnColumnBeginResize(wxListEvent& event); void OnColumnResize(wxListEvent& event); void OnColumnDrag(wxListEvent& event); // generic events void OnKeyDown(wxListEvent& event); void OnContextMenu(wxContextMenuEvent& event); void OnContextMenuSelected(wxCommandEvent& event); void OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event); void OnItemActivated(wxListEvent& event); void OnTimer(wxTimerEvent& event); void OnMouseMove(wxMouseEvent& event); void OnLeaveWindow(wxMouseEvent& event); void OnGameListSize(wxSizeEvent& event); void AdjustLastColumnWidth(); int GetColumnDefaultWidth(int column); void OnTimerBulkAddEntriesToGameList(wxTimerEvent& event); static inline std::once_flag s_launch_file_once; }; ================================================ FILE: src/gui/wxgui/components/wxInputDraw.cpp ================================================ #include "wxgui/components/wxInputDraw.h" #include #include wxInputDraw::wxInputDraw(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size) : wxWindow(parent, id, pos, size, 0, wxPanelNameStr) { SetBackgroundStyle(wxBG_STYLE_PAINT); Bind(wxEVT_PAINT, &wxInputDraw::OnPaintEvent, this); } wxInputDraw::~wxInputDraw() { Unbind(wxEVT_PAINT, &wxInputDraw::OnPaintEvent, this); } void wxInputDraw::OnPaintEvent(wxPaintEvent& event) { wxAutoBufferedPaintDC dc(this); OnRender(dc); } void wxInputDraw::OnRender(wxDC& dc) { dc.Clear(); glm::vec2 position = m_position; wxPen black = wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); wxPen red = *wxRED_PEN; wxPen grey = wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); wxPen green = wxSystemSettings::SelectLightDark(0x336600, 0x99FF99); wxBrush black_brush = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); wxBrush red_brush = *wxRED_BRUSH; wxBrush grey_brush = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT); wxBrush green_brush = wxSystemSettings::SelectLightDark(0x336600, 0x99FF99); if(!IsEnabled()) { position = {}; black.SetColour(black.GetColour()); red.SetColour(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); grey.SetColour(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT).MakeDisabled()); black_brush = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT); red_brush = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT).MakeDisabled(); grey_brush = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT).MakeDisabled(); } dc.SetBackgroundMode(wxSOLID); dc.SetBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); dc.Clear(); const auto size = GetSize(); const auto min_size = (float)std::min(size.GetWidth(), size.GetHeight()) - 1.0f; const Vector2f middle{min_size / 2.0f, min_size / 2.0f}; // border const wxRect border{0, 0, (int)min_size, (int)min_size}; dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); dc.DrawRectangle(border); dc.SetPen(IsEnabled() ? green.GetColour() : grey.GetColour()); dc.DrawCircle((int)middle.x, (int)middle.y, (int)middle.x); if (m_deadzone > 0) { dc.SetPen(grey); dc.SetBrush(grey_brush); const auto deadzone_size = m_deadzone * min_size / 2.0f; dc.DrawCircle( static_cast(middle.x), static_cast(middle.x), (int)deadzone_size); if (length(position) >= m_deadzone) { dc.SetPen(red); dc.SetBrush(red_brush); if (std::abs(1.0f - length(position)) < 0.05f) { dc.SetPen(green); dc.SetBrush(green_brush); } } else { dc.SetPen(black); dc.SetBrush(black_brush); } } else { dc.SetPen(red); dc.SetBrush(red_brush); } // draw axis const wxPoint pos { static_cast(middle.x + position.x * middle.x), static_cast(middle.y - position.y * middle.y) }; dc.DrawCircle(pos.x, pos.y, 2); dc.SetBrush(*wxTRANSPARENT_BRUSH); } void wxInputDraw::SetAxisValue(const glm::vec2& position) { m_position = position; Refresh(); } void wxInputDraw::SetDeadzone(float deadzone) { m_deadzone = deadzone; Refresh(); } ================================================ FILE: src/gui/wxgui/components/wxInputDraw.h ================================================ #pragma once #include "input/api/ControllerState.h" #include "util/math/vector2.h" #include class wxInputDraw : public wxWindow { public: wxInputDraw(wxWindow *parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); ~wxInputDraw(); virtual void OnRender(wxDC& dc); void SetAxisValue(const glm::vec2& position); void SetDeadzone(float deadzone); private: void OnPaintEvent(wxPaintEvent& event); glm::vec2 m_position{}; float m_deadzone{0}; }; ================================================ FILE: src/gui/wxgui/components/wxLogCtrl.cpp ================================================ #include "wxgui/components/wxLogCtrl.h" #include wxDEFINE_EVENT(EVT_ON_LIST_UPDATED, wxEvent); wxLogCtrl::wxLogCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, bool alternateRowColours) : TextList(parent, id, pos, size, style) { m_timer = new wxTimer(this); this->Bind(wxEVT_TIMER, &wxLogCtrl::OnTimer, this); this->Bind(EVT_ON_LIST_UPDATED, &wxLogCtrl::OnActiveListUpdated, this); m_timer->Start(250); m_alternateRowColoursEnabled = alternateRowColours; } wxLogCtrl::~wxLogCtrl() { this->Unbind(wxEVT_TIMER, &wxLogCtrl::OnTimer, this); this->Unbind(EVT_ON_LIST_UPDATED, &wxLogCtrl::OnActiveListUpdated, this); if(m_timer) m_timer->Stop(); if(m_update_worker.joinable()) m_update_worker.join(); } void wxLogCtrl::SetActiveFilter(const std::string& active_filter) { if(m_active_filter == active_filter) return; m_active_filter = active_filter; if(m_update_worker.joinable()) m_update_worker.join(); m_update_worker = std::thread(&wxLogCtrl::UpdateActiveEntries, this); } const std::string& wxLogCtrl::GetActiveFilter() const { return m_active_filter; } void wxLogCtrl::SetFilterMessage(bool state) { if(m_filter_messages == state) return; m_filter_messages = state; if(m_update_worker.joinable()) m_update_worker.join(); m_update_worker = std::thread(&wxLogCtrl::UpdateActiveEntries, this); } bool wxLogCtrl::GetFilterMessage() const { return m_filter_messages; } void wxLogCtrl::PushEntry(const wxString& filter, const wxString& message) { std::unique_lock lock(m_mutex); m_log_entries.emplace_back(filter, message); ListIt_t it = m_log_entries.back(); lock.unlock(); if(m_active_filter.empty() || filter == m_active_filter || (m_filter_messages && boost::icontains(message.ToStdString(), m_active_filter))) { std::unique_lock active_lock(m_active_mutex); m_active_entries.emplace_back(std::cref(it)); const auto entry_count = m_active_entries.size(); active_lock.unlock(); if(entry_count <= m_elements_visible) { RefreshLine(entry_count - 1); } } } void wxLogCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) { wxPoint position = start_position; std::scoped_lock lock(m_active_mutex); auto it = std::next(m_active_entries.cbegin(), start); if(it == m_active_entries.cend()) return; for (sint32 i = 0; i <= count && it != m_active_entries.cend(); ++i, ++it) { wxColour background_colour = GetBackgroundColour(); if((start + i) % 2 != 0 && m_alternateRowColoursEnabled) background_colour = GetAlternateRowColour(); DrawLineBackground(dc, position, background_colour); dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); dc.DrawText(it->get().second, position); NextLine(position, &start_position); } } void wxLogCtrl::OnTimer(wxTimerEvent& event) { std::unique_lock lock(m_active_mutex); const auto count = m_active_entries.size(); if(count == m_element_count) return; lock.unlock(); SetElementCount(count); if(m_scrolled_to_end) { Scroll(0, count); RefreshControl(); } } void wxLogCtrl::OnActiveListUpdated(wxEvent& event) { std::unique_lock lock(m_active_mutex); const auto count = m_active_entries.size(); lock.unlock(); SetElementCount(count); RefreshControl(); } void wxLogCtrl::UpdateActiveEntries() { { std::scoped_lock lock(m_mutex, m_active_mutex); m_active_entries.clear(); if(m_active_filter.empty()) { for (const auto& it : m_log_entries) m_active_entries.emplace_back(it); } else { for (const auto& it : m_log_entries) { if(it.first == m_active_filter || (m_filter_messages && boost::icontains(it.second.ToStdString(), m_active_filter)) ) m_active_entries.emplace_back(it); } } } wxQueueEvent(this, new wxCommandEvent(EVT_ON_LIST_UPDATED)); } ================================================ FILE: src/gui/wxgui/components/wxLogCtrl.h ================================================ #pragma once #include "TextList.h" class wxLogCtrl : public TextList { public: wxLogCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, bool alternateRowColours = false); ~wxLogCtrl(); void SetActiveFilter(const std::string& active_filter); [[nodiscard]] const std::string& GetActiveFilter() const; void SetFilterMessage(bool state); [[nodiscard]] bool GetFilterMessage() const; void PushEntry(const wxString& filter, const wxString& message); protected: void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position); private: void OnTimer(wxTimerEvent& event); void OnActiveListUpdated(wxEvent& event); wxTimer* m_timer; bool m_alternateRowColoursEnabled = false; wxColour m_alternateRowColour; wxColour GetAlternateRowColour() { if (m_alternateRowColour.IsOk()) return m_alternateRowColour; // Depending on the background, alternate row colour should be a bit // darker or brighter. const wxColour bgColour = GetBackgroundColour(); int alpha = bgColour.GetRGB() > 0x808080 ? 97 : 110; m_alternateRowColour = bgColour.ChangeLightness(alpha); return m_alternateRowColour; } std::string m_active_filter; std::thread m_update_worker; bool m_filter_messages = false; std::mutex m_mutex, m_active_mutex; using ListEntry_t = std::pair; using ListIt_t = std::list::const_reference; std::list m_log_entries; std::list> m_active_entries; void UpdateActiveEntries(); }; ================================================ FILE: src/gui/wxgui/components/wxProgressDialogManager.h ================================================ #pragma once #include #include #include #include "util/helpers/Semaphore.h" wxDEFINE_EVENT(wxEVT_CREATE_PROGRESS_DIALOG, wxThreadEvent); wxDEFINE_EVENT(wxEVT_DESTROY_PROGRESS_DIALOG, wxThreadEvent); wxDEFINE_EVENT(wxEVT_UPDATE_PROGRESS_DIALOG, wxThreadEvent); // wrapper for wxGenericProgressDialog which can be used from any thread class wxProgressDialogManager : public wxEvtHandler { public: wxProgressDialogManager(wxWindow* parent) : m_parent(parent), m_dialog(nullptr) { Bind(wxEVT_CREATE_PROGRESS_DIALOG, &wxProgressDialogManager::OnCreateProgressDialog, this); Bind(wxEVT_DESTROY_PROGRESS_DIALOG, &wxProgressDialogManager::OnDestroyProgressDialog, this); Bind(wxEVT_UPDATE_PROGRESS_DIALOG, &wxProgressDialogManager::OnUpdateProgressDialog, this); } ~wxProgressDialogManager() { if (m_dialog) Destroy(); } void Create(const wxString& title, const wxString& message, int maximum, int style = wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME) { m_instanceSemaphore.increment(); m_isCancelled = false; m_isSkipped = false; wxThreadEvent event(wxEVT_CREATE_PROGRESS_DIALOG); event.SetString(title); event.SetInt(maximum); event.SetExtraLong(style); wxQueueEvent(this, event.Clone()); } void Destroy() { wxThreadEvent event(wxEVT_DESTROY_PROGRESS_DIALOG); wxQueueEvent(this, event.Clone()); m_instanceSemaphore.waitUntilZero(); // wait until destruction is complete } // this also updates the cancel and skip state void Update(int value, const wxString& newmsg = wxEmptyString) { wxThreadEvent event(wxEVT_UPDATE_PROGRESS_DIALOG); event.SetInt(value); event.SetString(newmsg); wxQueueEvent(this, event.Clone()); } bool IsCancelled() const { return m_isCancelled; } bool IsSkipped() const { return m_isSkipped; } bool IsCancelledOrSkipped() const { return m_isCancelled || m_isSkipped; } private: void OnCreateProgressDialog(wxThreadEvent& event) { if (m_dialog) { m_dialog->Destroy(); m_instanceSemaphore.waitUntilZero(); } m_maximum = event.GetInt(); m_dialog = new wxGenericProgressDialog(event.GetString(), "Please wait...", m_maximum, m_parent, event.GetExtraLong()); } void OnDestroyProgressDialog(wxThreadEvent& event) { if (m_dialog) { m_dialog->Destroy(); m_dialog = nullptr; m_instanceSemaphore.decrement(); } } void OnUpdateProgressDialog(wxThreadEvent& event) { if (m_dialog) { // make sure that progress is never >= maximum // because wxGenericProgressDialog seems to become crashy on destruction otherwise int progress = event.GetInt(); if(progress >= m_maximum) progress = m_maximum - 1; bool wasSkipped = false; bool r = m_dialog->Update(progress, event.GetString(), &wasSkipped); if(!r) m_isCancelled = true; if(wasSkipped) m_isSkipped = true; } } wxWindow* m_parent; wxGenericProgressDialog* m_dialog; bool m_isCancelled{false}; bool m_isSkipped{false}; int m_maximum{0}; CounterSemaphore m_instanceSemaphore; // used to synchronize destruction of the dialog }; ================================================ FILE: src/gui/wxgui/components/wxTitleManagerList.cpp ================================================ #include "wxgui/components/wxTitleManagerList.h" #include "wxgui/helpers/wxHelpers.h" #include "util/helpers/SystemException.h" #include "Cafe/TitleList/GameInfo.h" #include "Cafe/TitleList/TitleInfo.h" #include "Cafe/TitleList/TitleList.h" #include "wxgui/components/wxGameList.h" #include "wxgui/helpers/wxCustomEvents.h" #include "wxgui/helpers/wxHelpers.h" #include #include #include #include #include #include #include #include #include #include "../wxHelper.h" #include #include "config/ActiveSettings.h" #include "wxgui/ChecksumTool.h" #include "wxgui/MainWindow.h" #include "Cafe/TitleList/TitleId.h" #include "Cafe/TitleList/SaveList.h" #include "Cafe/TitleList/TitleList.h" #include #include #include "Common/FileStream.h" wxDEFINE_EVENT(wxEVT_TITLE_FOUND, wxCommandEvent); wxDEFINE_EVENT(wxEVT_TITLE_REMOVED, wxCommandEvent); wxDEFINE_EVENT(wxEVT_REMOVE_ENTRY, wxCommandEvent); wxTitleManagerList::wxTitleManagerList(wxWindow* parent, wxWindowID id) : wxListView(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL) { AddColumns(); // tooltip TODO: extract class mb wxPanelTooltip m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); auto* tooltip_sizer = new wxBoxSizer(wxVERTICAL); m_tooltip_text = new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString); tooltip_sizer->Add(m_tooltip_text , 0, wxALL, 5); m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); m_tooltip_window->SetSizerAndFit(tooltip_sizer); m_tooltip_window->Hide(); m_tooltip_timer = new wxTimer(this); Bind(wxEVT_LIST_COL_CLICK, &wxTitleManagerList::OnColumnClick, this); Bind(wxEVT_CONTEXT_MENU, &wxTitleManagerList::OnContextMenu, this); Bind(wxEVT_LIST_ITEM_SELECTED, &wxTitleManagerList::OnItemSelected, this); Bind(wxEVT_TIMER, &wxTitleManagerList::OnTimer, this); Bind(wxEVT_REMOVE_ITEM, &wxTitleManagerList::OnRemoveItem, this); Bind(wxEVT_REMOVE_ENTRY, &wxTitleManagerList::OnRemoveEntry, this); Bind(wxEVT_TITLE_FOUND, &wxTitleManagerList::OnTitleDiscovered, this); Bind(wxEVT_TITLE_REMOVED, &wxTitleManagerList::OnTitleRemoved, this); Bind(wxEVT_CLOSE_WINDOW, &wxTitleManagerList::OnClose, this); m_callbackIdTitleList = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((wxTitleManagerList*)ctx)->HandleTitleListCallback(evt); }, this); m_callbackIdSaveList = CafeSaveList::RegisterCallback([](CafeSaveListCallbackEvent* evt, void* ctx) { ((wxTitleManagerList*)ctx)->HandleSaveListCallback(evt); }, this); ShowSortIndicator(ColumnTitleId); } wxTitleManagerList::~wxTitleManagerList() { CafeSaveList::UnregisterCallback(m_callbackIdSaveList); CafeTitleList::UnregisterCallback(m_callbackIdTitleList); } boost::optional wxTitleManagerList::GetSelectedTitleEntry() const { const auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { const auto tmp = GetTitleEntry(selection); if (tmp.has_value()) return tmp.value(); } return {}; } boost::optional wxTitleManagerList::GetSelectedTitleEntry() { const auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { const auto tmp = GetTitleEntry(selection); if (tmp.has_value()) return tmp.value(); } return {}; } //boost::optional wxTitleManagerList::GetTitleEntry(EntryType type, uint64 titleid) //{ // for(const auto& v : m_data) // { // if (v->entry.title_id == titleid && v->entry.type == type) // return v->entry; // } // // return {}; //} boost::optional wxTitleManagerList::GetTitleEntryByUID(uint64 uid) { for (const auto& v : m_data) { if (v->entry.location_uid == uid) return v->entry; } return {}; } void wxTitleManagerList::AddColumns() { wxListItem col0; col0.SetId(ColumnTitleId); col0.SetText(_("Title ID")); col0.SetWidth(120); InsertColumn(ColumnTitleId, col0); wxListItem col1; col1.SetId(ColumnName); col1.SetText(_("Name")); col1.SetWidth(435); InsertColumn(ColumnName, col1); wxListItem col2; col2.SetId(ColumnType); col2.SetText(_("Type")); col2.SetWidth(65); InsertColumn(ColumnType, col2); wxListItem col3; col3.SetId(ColumnVersion); col3.SetText(_("Version")); col3.SetWidth(40); InsertColumn(ColumnVersion, col3); wxListItem col4; col4.SetId(ColumnRegion); col4.SetText(_("Region")); col4.SetWidth(60); InsertColumn(ColumnRegion, col4); wxListItem col5; col5.SetId(ColumnFormat); col5.SetText(_("Format")); col5.SetWidth(63); InsertColumn(ColumnFormat, col5); wxListItem col6; col6.SetId(ColumnLocation); col6.SetText(_("Location")); col6.SetWidth(63); InsertColumn(ColumnLocation, col6); } wxString wxTitleManagerList::OnGetItemText(long item, long column) const { if (item >= GetItemCount()) return wxEmptyString; const auto entry = GetTitleEntry(item); if (entry.has_value()) return GetTitleEntryText(entry.value(), (ItemColumn)column); return wxEmptyString; } wxItemAttr* wxTitleManagerList::OnGetItemAttr(long item) const { static wxColour bgColourPrimary = GetBackgroundColour(); static wxColour bgColourSecondary = wxHelper::CalculateAccentColour(bgColourPrimary); static wxListItemAttr s_primary_attr(GetTextColour(), bgColourPrimary, GetFont()); static wxListItemAttr s_secondary_attr(GetTextColour(), bgColourSecondary, GetFont()); return item % 2 == 0 ? &s_primary_attr : &s_secondary_attr; } boost::optional wxTitleManagerList::GetTitleEntry(long item) { long counter = 0; for (const auto& data : m_sorted_data) { if (!data.get().visible) continue; if (item != counter++) continue; return data.get().entry; } return {}; } boost::optional wxTitleManagerList::GetTitleEntry(const fs::path& path) const { const auto tmp = _pathToUtf8(path); for (const auto& data : m_data) { if (boost::iequals(_pathToUtf8(data->entry.path), tmp)) return data->entry; } return {}; } boost::optional wxTitleManagerList::GetTitleEntry(const fs::path& path) { const auto tmp = _pathToUtf8(path); for (const auto& data : m_data) { if (boost::iequals(_pathToUtf8(data->entry.path), tmp)) return data->entry; } return {}; } boost::optional wxTitleManagerList::GetTitleEntry(long item) const { long counter = 0; for (const auto& data : m_sorted_data) { if (!data.get().visible) continue; if (item != counter++) continue; return data.get().entry; } return {}; } void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId, uint64 rightClickedUID) { TitleInfo titleInfo_base; TitleInfo titleInfo_update; TitleInfo titleInfo_aoc; titleId = TitleIdParser::MakeBaseTitleId(titleId); // if the titleId of a separate update is selected, this converts it back to the base titleId TitleIdParser titleIdParser(titleId); bool hasBaseTitleId = titleIdParser.GetType() != TitleIdParser::TITLE_TYPE::AOC; bool hasUpdateTitleId = titleIdParser.CanHaveSeparateUpdateTitleId(); TitleId updateTitleId = hasUpdateTitleId ? titleIdParser.GetSeparateUpdateTitleId() : 0; // todo - AOC titleIds might differ from the base/update game in other bits than the type. We have to use the meta data from the base/update to match aoc to the base title id // for now we just assume they match TitleId aocTitleId; if (hasBaseTitleId) aocTitleId = (titleId & (uint64)~0xFF00000000) | (uint64)0xC00000000; else aocTitleId = titleId; // find base and update for (const auto& data : m_data) { if (hasBaseTitleId && data->entry.title_id == titleId) { if (!titleInfo_base.IsValid()) { titleInfo_base = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); if(data->entry.location_uid == rightClickedUID) break; // prefer the users selection } } } for (const auto& data : m_data) { if (hasUpdateTitleId && data->entry.title_id == updateTitleId) { if (!titleInfo_update.IsValid()) { titleInfo_update = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); if(data->entry.location_uid == rightClickedUID) break; } else { // if multiple updates are present use the newest one if (titleInfo_update.GetAppTitleVersion() < data->entry.version) titleInfo_update = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); if(data->entry.location_uid == rightClickedUID) break; } } } // find AOC for (const auto& data : m_data) { if (data->entry.title_id == aocTitleId) { titleInfo_aoc = CafeTitleList::GetTitleInfoByUID(data->entry.location_uid); if(data->entry.location_uid == rightClickedUID) break; } } wxString msg = _("The following content will be converted to a compressed Wii U archive file (.wua):"); msg.append("\n \n"); if (titleInfo_base.IsValid()) msg.append(formatWxString(_("Base game:\n{}"), titleInfo_base.GetPrintPath())); else msg.append(_("Base game:\nNot installed")); msg.append("\n\n"); if (titleInfo_update.IsValid()) msg.append(formatWxString(_("Update:\n{}"), titleInfo_update.GetPrintPath())); else msg.append(_("Update:\nNot installed")); msg.append("\n\n"); if (titleInfo_aoc.IsValid()) msg.append(formatWxString(_("DLC:\n{}"), titleInfo_aoc.GetPrintPath())); else msg.append(_("DLC:\nNot installed")); const int answer = wxMessageBox(msg, _("Confirmation"), wxOK | wxCANCEL | wxCENTRE | wxICON_QUESTION, this); if (answer != wxOK) return; std::vector titlesToConvert; if (titleInfo_base.IsValid()) titlesToConvert.emplace_back(&titleInfo_base); if (titleInfo_update.IsValid()) titlesToConvert.emplace_back(&titleInfo_update); if (titleInfo_aoc.IsValid()) titlesToConvert.emplace_back(&titleInfo_aoc); if (titlesToConvert.empty()) return; // get short name CafeConsoleLanguage languageId = CafeConsoleLanguage::EN; // todo - use user's locale std::string shortName; if (titleInfo_base.IsValid()) shortName = titleInfo_base.GetMetaInfo()->GetShortName(languageId); else if (titleInfo_update.IsValid()) shortName = titleInfo_update.GetMetaInfo()->GetShortName(languageId); else if (titleInfo_aoc.IsValid()) shortName = titleInfo_aoc.GetMetaInfo()->GetShortName(languageId); if (!shortName.empty()) { boost::replace_all(shortName, ":", ""); } // for the default output directory we use the first game path configured by the user std::string defaultDir = ""; if (!GetConfig().game_paths.empty()) defaultDir = GetConfig().game_paths.front(); // get the short name, which we will use as a suggested default file name std::string defaultFileName = std::move(shortName); boost::replace_all(defaultFileName, "/", ""); boost::replace_all(defaultFileName, "\\", ""); CafeConsoleRegion region = CafeConsoleRegion::Auto; if (titleInfo_base.IsValid() && titleInfo_base.HasValidXmlInfo()) region = titleInfo_base.GetMetaInfo()->GetRegion(); else if (titleInfo_update.IsValid() && titleInfo_update.HasValidXmlInfo()) region = titleInfo_update.GetMetaInfo()->GetRegion(); if (region == CafeConsoleRegion::JPN) defaultFileName.append(" (JP)"); else if (region == CafeConsoleRegion::EUR) defaultFileName.append(" (EU)"); else if (region == CafeConsoleRegion::USA) defaultFileName.append(" (US)"); if (titleInfo_update.IsValid()) { defaultFileName.append(fmt::format(" (v{})", titleInfo_update.GetAppTitleVersion())); } defaultFileName.append(".wua"); // ask the user to provide a path for the output file wxFileDialog saveFileDialog(this, _("Save Wii U game archive file"), defaultDir, wxString::FromUTF8(defaultFileName), "WUA files (*.wua)|*.wua", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (saveFileDialog.ShowModal() == wxID_CANCEL || saveFileDialog.GetPath().IsEmpty()) return; fs::path outputPath(wxHelper::MakeFSPath(saveFileDialog.GetPath())); fs::path outputPathTmp(wxHelper::MakeFSPath(saveFileDialog.GetPath().append("__tmp"))); struct ZArchiveWriterContext { static void NewOutputFile(const int32_t partIndex, void* _ctx) { ZArchiveWriterContext* ctx = (ZArchiveWriterContext*)_ctx; ctx->fs = FileStream::createFile2(ctx->outputPath); if (!ctx->fs) ctx->isValid = false; } static void WriteOutputData(const void* data, size_t length, void* _ctx) { ZArchiveWriterContext* ctx = (ZArchiveWriterContext*)_ctx; if (ctx->fs) ctx->fs->writeData(data, length); } bool RecursivelyCountFiles(const std::string& fscPath) { sint32 fscStatus; std::unique_ptr vfDir(fsc_openDirIterator(fscPath.c_str(), &fscStatus)); if (!vfDir) return false; if (cancelled) return false; FSCDirEntry dirEntry; while (fsc_nextDir(vfDir.get(), &dirEntry)) { if (dirEntry.isFile) { totalInputFileSize += (uint64)dirEntry.fileSize; totalFileCount++; } else if (dirEntry.isDirectory) { if (!RecursivelyCountFiles(fmt::format("{}{}/", fscPath, dirEntry.path))) { return false; } } else cemu_assert_unimplemented(); } return true; } bool RecursivelyAddFiles(std::string archivePath, std::string fscPath) { sint32 fscStatus; std::unique_ptr vfDir(fsc_openDirIterator(fscPath.c_str(), &fscStatus)); if (!vfDir) return false; if (cancelled) return false; zaWriter->MakeDir(archivePath.c_str(), false); FSCDirEntry dirEntry; while (fsc_nextDir(vfDir.get(), &dirEntry)) { if (dirEntry.isFile) { zaWriter->StartNewFile((archivePath + dirEntry.path).c_str()); std::unique_ptr vFile(fsc_open((fscPath + dirEntry.path).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus)); if (!vFile) return false; transferBuffer.resize(32 * 1024); // 32KB uint32 readBytes; while (true) { readBytes = vFile->fscReadData(transferBuffer.data(), transferBuffer.size()); if(readBytes == 0) break; zaWriter->AppendData(transferBuffer.data(), readBytes); if (cancelled) return false; transferredInputBytes += readBytes; } currentFileIndex++; } else if (dirEntry.isDirectory) { if (!RecursivelyAddFiles(fmt::format("{}{}/", archivePath, dirEntry.path), fmt::format("{}{}/", fscPath, dirEntry.path))) return false; } else cemu_assert_unimplemented(); } return true; } bool LoadTitleMetaAndCountFiles(TitleInfo* titleInfo) { std::string temporaryMountPath = TitleInfo::GetUniqueTempMountingPath(); titleInfo->Mount(temporaryMountPath.c_str(), "", FSC_PRIORITY_BASE); bool r = RecursivelyCountFiles(temporaryMountPath); titleInfo->Unmount(temporaryMountPath.c_str()); return r; } bool StoreTitle(TitleInfo* titleInfo) { std::string temporaryMountPath = TitleInfo::GetUniqueTempMountingPath(); titleInfo->Mount(temporaryMountPath.c_str(), "", FSC_PRIORITY_BASE); bool r = RecursivelyAddFiles(fmt::format("{:016x}_v{}/", titleInfo->GetAppTitleId(), titleInfo->GetAppTitleVersion()), temporaryMountPath); titleInfo->Unmount(temporaryMountPath.c_str()); return r; } bool AddTitles(TitleInfo** titles, size_t count) { currentFileIndex = 0; totalFileCount = 0; // count files for (size_t i = 0; i < count; i++) { if (!LoadTitleMetaAndCountFiles(titles[i])) return false; if (cancelled) return false; } // store files for (size_t i = 0; i < count; i++) { if (!StoreTitle(titles[i])) return false; } return true; } ~ZArchiveWriterContext() { delete fs; delete zaWriter; }; fs::path outputPath; FileStream* fs; ZArchiveWriter* zaWriter{}; bool isValid{false}; std::vector transferBuffer; std::atomic_bool cancelled{false}; // progress std::atomic_uint32_t totalFileCount{}; std::atomic_uint32_t currentFileIndex{}; std::atomic_uint64_t totalInputFileSize{}; std::atomic_uint64_t transferredInputBytes{}; }writerContext; // mount and store writerContext.isValid = true; writerContext.outputPath = outputPathTmp; writerContext.zaWriter = new ZArchiveWriter(&ZArchiveWriterContext::NewOutputFile, &ZArchiveWriterContext::WriteOutputData, &writerContext); if (!writerContext.isValid) { // failed to create file wxMessageBox(_("Unable to create file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } // open progress dialog wxGenericProgressDialog progressDialog("Converting to .wua", _("Counting files..."), 100, // range this, // parent wxPD_CAN_ABORT ); progressDialog.Show(); auto asyncWorker = std::async(std::launch::async, &ZArchiveWriterContext::AddTitles, &writerContext, titlesToConvert.data(), titlesToConvert.size()); while (!future_is_ready(asyncWorker)) { if (writerContext.cancelled) { progressDialog.Update(0, _("Stopping...")); } else if (writerContext.currentFileIndex != 0) { uint64 numSizeCompleted = writerContext.transferredInputBytes; uint64 numSizeTotal = writerContext.totalInputFileSize; uint32 pct = (uint32)(numSizeCompleted * (uint64)100 / numSizeTotal); pct = std::min(pct, (uint32)100); if (pct >= 100) pct = 99; // never set it to 100 as progress == total will make .Update() call ShowModal() and lock up this loop std::string textSuffix = fmt::format(" ({}MiB/{}MiB)", numSizeCompleted / 1024 / 1024, numSizeTotal / 1024 / 1024); progressDialog.Update(pct, _("Converting files...") + textSuffix); } else { progressDialog.Update(0, _("Collecting list of files...") + fmt::format(" ({})", writerContext.totalFileCount.load())); } if (progressDialog.WasCancelled()) writerContext.cancelled.store(true); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } progressDialog.Update(100-1, _("Finalizing...")); bool r = asyncWorker.get(); if (!r) { delete writerContext.fs; writerContext.fs = nullptr; std::error_code ec; fs::remove(outputPathTmp, ec); return; } writerContext.zaWriter->Finalize(); delete writerContext.fs; writerContext.fs = nullptr; // verify the created WUA file ZArchiveReader* zreader = ZArchiveReader::OpenFromFile(outputPathTmp); if (!zreader) { wxMessageBox(_("Conversion failed\n"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); std::error_code ec; fs::remove(outputPathTmp, ec); return; } // todo - do a quick verification here delete zreader; // finish progressDialog.Hide(); fs::rename(outputPathTmp, outputPath); // ask user if they want to delete the original titles // todo CafeTitleList::Refresh(); wxMessageBox(_("Conversion finished\n"), _("Complete"), wxOK | wxCENTRE | wxICON_INFORMATION, this); } void wxTitleManagerList::OnClose(wxCloseEvent& event) { event.Skip(); // wait until all tasks are complete if (m_context_worker.valid()) m_context_worker.get(); } void wxTitleManagerList::OnColumnClick(wxListEvent& event) { const int column = event.GetColumn(); SortEntries(column); event.Skip(); } void wxTitleManagerList::RemoveItem(long item) { const int item_count = GetItemCount(); const ItemData* ref = nullptr; long counter = 0; for(auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it) { if (!it->get().visible) continue; if (item != counter++) continue; ref = &(it->get()); m_sorted_data.erase(it); break; } // shouldn't happen if (ref == nullptr) return; for(auto it = m_data.begin(); it != m_data.end(); ++it) { if (ref != (*it).get()) continue; m_data.erase(it); break; } SetItemCount(std::max(0, item_count - 1)); RefreshPage(); } void wxTitleManagerList::RemoveItem(const TitleEntry& entry) { const int item_count = GetItemCount(); const TitleEntry* ref = &entry; for (auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it) { if (ref != &it->get().entry) continue; m_sorted_data.erase(it); break; } for (auto it = m_data.begin(); it != m_data.end(); ++it) { if (ref != &(*it).get()->entry) continue; m_data.erase(it); break; } SetItemCount(std::max(0, item_count - 1)); RefreshPage(); } void wxTitleManagerList::OnItemSelected(wxListEvent& event) { event.Skip(); m_tooltip_timer->Stop(); const auto selection = event.GetIndex(); if (selection == wxNOT_FOUND) { m_tooltip_window->Hide(); return; } const auto entry = GetTitleEntry(selection); if (!entry.has_value()) { m_tooltip_window->Hide(); return; } m_tooltip_window->Hide(); return; //const auto mouse_position = wxGetMousePosition() - GetScreenPosition(); //m_tooltip_window->SetPosition(wxPoint(mouse_position.x + 15, mouse_position.y + 15)); //wxString msg; //switch(entry->error) //{ //case TitleError::WrongBaseLocation: // msg = _("This base game is installed at the wrong location."); // break; //case TitleError::WrongUpdateLocation: // msg = _("This update is installed at the wrong location."); // break; //case TitleError::WrongDlcLocation: // msg = _("This DLC is installed at the wrong location."); // break; //default: // return;; //} //m_tooltip_text->SetLabel(formatWxString("{}\n{}", msg, _("You can use the context menu to fix it."))); //m_tooltip_window->Fit(); //m_tooltip_timer->StartOnce(250); } enum ContextMenuEntries { kContextMenuOpenDirectory = wxID_HIGHEST + 1, kContextMenuDelete, kContextMenuLaunch, kContextMenuVerifyGameFiles, kContextMenuConvertToWUA, }; void wxTitleManagerList::OnContextMenu(wxContextMenuEvent& event) { // still doing work if (m_context_worker.valid() && !future_is_ready(m_context_worker)) return; wxMenu menu; menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxTitleManagerList::OnContextMenuSelected, this); const auto selection = GetFirstSelected(); if (selection == wxNOT_FOUND) return; const auto entry = GetTitleEntry(selection); if (!entry.has_value()) return; if(entry->type == EntryType::Base) menu.Append(kContextMenuLaunch, _("&Launch title")); menu.Append(kContextMenuOpenDirectory, _("&Open directory")); if (entry->type != EntryType::Save) menu.Append(kContextMenuVerifyGameFiles, _("&Verify integrity of game files")); menu.AppendSeparator(); if (entry->type != EntryType::Save && entry->format != EntryFormat::WUA) { menu.Append(kContextMenuConvertToWUA, _("Convert to compressed Wii U archive (.wua)")); menu.AppendSeparator(); } menu.Append(kContextMenuDelete, _("&Delete")); PopupMenu(&menu); // TODO: fix tooltip position } bool wxTitleManagerList::DeleteEntry(long index, const TitleEntry& entry) { wxDTorFunc reset_text(wxQueueEvent, this, new wxSetStatusBarTextEvent(wxEmptyString, 1)); wxQueueEvent(this, new wxSetStatusBarTextEvent("Deleting entry...", 1)); wxString msg; const bool is_directory = fs::is_directory(entry.path); if(is_directory) msg = formatWxString(_("Are you really sure that you want to delete the following folder:\n{}"), _pathToUtf8(entry.path)); else msg = formatWxString(_("Are you really sure that you want to delete the following file:\n{}"), _pathToUtf8(entry.path)); const auto result = wxMessageBox(msg, _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this); if (result == wxNO) return false; std::error_code ec; if (is_directory) { if (entry.type != EntryType::Save) { // delete content, meta, code folders first const auto content = entry.path / "content"; fs::remove_all(content, ec); const auto meta = entry.path / "meta"; fs::remove_all(meta, ec); const auto code = entry.path / "code"; fs::remove_all(code, ec); } else { // delete meta, user folders first const auto meta = entry.path / "meta"; fs::remove_all(meta, ec); const auto user = entry.path / "user"; fs::remove_all(user, ec); } // check if folder is empty if(fs::is_empty(entry.path, ec)) fs::remove_all(entry.path, ec); } else // simply remove file fs::remove(entry.path, ec); if(ec) { const auto error_msg = formatWxString(_("Error when trying to delete the entry:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK|wxCENTRE, this); return false; } // thread safe request to delete entry const auto evt = new wxCommandEvent(wxEVT_REMOVE_ITEM); evt->SetInt(index); wxQueueEvent(this, evt); return true; } void wxTitleManagerList::OnContextMenuSelected(wxCommandEvent& event) { // still doing work if (m_context_worker.valid() && !future_is_ready(m_context_worker)) return; const auto selection = GetFirstSelected(); if (selection == wxNOT_FOUND) return; const auto entry = GetTitleEntry(selection); if (!entry.has_value()) return; switch (event.GetId()) { case kContextMenuOpenDirectory: { const auto path = fs::is_directory(entry->path) ? entry->path : entry->path.parent_path(); wxLaunchDefaultApplication(wxHelper::FromPath(path)); } break; case kContextMenuDelete: m_context_worker = std::async(std::launch::async, &wxTitleManagerList::DeleteEntry, this, selection, entry.value()); break; case kContextMenuLaunch: { try { MainWindow::RequestLaunchGame(entry->path, wxLaunchGameEvent::INITIATED_BY::TITLE_MANAGER); Close(); } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "wxTitleManagerList::OnContextMenuSelected: can't launch title: {}", ex.what()); } } break; case kContextMenuVerifyGameFiles: (new ChecksumTool(this, entry.value()))->Show(); break; case kContextMenuConvertToWUA: OnConvertToCompressedFormat(entry.value().title_id, entry.value().location_uid); break; } } void wxTitleManagerList::OnTimer(wxTimerEvent& event) { if(event.GetTimer().GetId() != m_tooltip_timer->GetId()) { event.Skip(); return; } m_tooltip_window->Show(); } void wxTitleManagerList::OnRemoveItem(wxCommandEvent& event) { RemoveItem(event.GetInt()); } void wxTitleManagerList::OnRemoveEntry(wxCommandEvent& event) { wxASSERT(event.GetClientData() != nullptr); RemoveItem(*(TitleEntry*)event.GetClientData()); } wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColumn column) { switch (column) { case ColumnTitleId: return formatWxString("{:08x}-{:08x}", (uint32) (entry.title_id >> 32), (uint32) (entry.title_id & 0xFFFFFFFF)); case ColumnName: return entry.name; case ColumnType: return GetTranslatedTitleEntryType(entry.type); case ColumnVersion: return formatWxString("{}", entry.version); case ColumnRegion: return wxGetTranslation(fmt::format("{}", entry.region)); case ColumnFormat: { if (entry.type == EntryType::Save) return _("Save folder"); switch (entry.format) { case wxTitleManagerList::EntryFormat::Folder: return _("Folder"); case wxTitleManagerList::EntryFormat::WUD: return _("WUD"); case wxTitleManagerList::EntryFormat::NUS: return _("NUS"); case wxTitleManagerList::EntryFormat::WUA: return _("WUA"); case wxTitleManagerList::EntryFormat::WUHB: return _("WUHB"); } return ""; } case ColumnLocation: { const auto relative_mlc_path = _pathToUtf8(entry.path.lexically_relative(ActiveSettings::GetMlcPath())); if (relative_mlc_path.starts_with("usr") || relative_mlc_path.starts_with("sys")) return _("MLC"); else return _("Game Paths"); } default: UNREACHABLE; } return wxEmptyString; } wxString wxTitleManagerList::GetTranslatedTitleEntryType(EntryType type) { switch (type) { case EntryType::Base: return _("base"); case EntryType::Update: return _("update"); case EntryType::Dlc: return _("DLC"); case EntryType::Save: return _("save"); case EntryType::System: return _("system"); default: return std::to_string(static_cast>(type)); } } void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt) { if (evt->eventType != CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED && evt->eventType != CafeTitleListCallbackEvent::TYPE::TITLE_REMOVED) return; auto& titleInfo = *evt->titleInfo; wxTitleManagerList::EntryType entryType; switch (titleInfo.GetTitleType()) { case TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE: entryType = EntryType::Update; break; case TitleIdParser::TITLE_TYPE::AOC: entryType = EntryType::Dlc; break; case TitleIdParser::TITLE_TYPE::SYSTEM_DATA: case TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE: case TitleIdParser::TITLE_TYPE::SYSTEM_TITLE: entryType = EntryType::System; break; default: entryType = EntryType::Base; } wxTitleManagerList::EntryFormat entryFormat; switch (titleInfo.GetFormat()) { case TitleInfo::TitleDataFormat::WUD: entryFormat = EntryFormat::WUD; break; case TitleInfo::TitleDataFormat::NUS: entryFormat = EntryFormat::NUS; break; case TitleInfo::TitleDataFormat::WIIU_ARCHIVE: entryFormat = EntryFormat::WUA; break; case TitleInfo::TitleDataFormat::WUHB: entryFormat = EntryFormat::WUHB; break; case TitleInfo::TitleDataFormat::HOST_FS: default: entryFormat = EntryFormat::Folder; break; } if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED) { if (titleInfo.IsCached()) return; // the title list only displays non-cached entries wxTitleManagerList::TitleEntry entry(entryType, entryFormat, titleInfo.GetPath()); ParsedMetaXml* metaInfo = titleInfo.GetMetaInfo(); if(titleInfo.IsSystemDataTitle()) return; // dont show system data titles for now entry.location_uid = titleInfo.GetUID(); entry.title_id = titleInfo.GetAppTitleId(); std::string name = metaInfo->GetLongName(GetConfig().console_language.GetValue()); const auto nl = name.find(L'\n'); if (nl != std::string::npos) name.replace(nl, 1, " - "); entry.name = wxString::FromUTF8(name); entry.version = titleInfo.GetAppTitleVersion(); entry.region = metaInfo->GetRegion(); auto* cmdEvt = new wxCommandEvent(wxEVT_TITLE_FOUND); cmdEvt->SetClientObject(new wxCustomData(entry)); wxQueueEvent(this, cmdEvt); } else if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_REMOVED) { wxTitleManagerList::TitleEntry entry(entryType, entryFormat, titleInfo.GetPath()); entry.location_uid = titleInfo.GetUID(); entry.title_id = titleInfo.GetAppTitleId(); auto* cmdEvt = new wxCommandEvent(wxEVT_TITLE_REMOVED); cmdEvt->SetClientObject(new wxCustomData(entry)); wxQueueEvent(this, cmdEvt); } } void wxTitleManagerList::HandleSaveListCallback(struct CafeSaveListCallbackEvent* evt) { if (evt->eventType == CafeSaveListCallbackEvent::TYPE::SAVE_DISCOVERED) { ParsedMetaXml* metaInfo = evt->saveInfo->GetMetaInfo(); if (!metaInfo) return; auto& saveInfo = *evt->saveInfo; wxTitleManagerList::TitleEntry entry(EntryType::Save, EntryFormat::Folder, saveInfo.GetPath()); entry.location_uid = std::hash() ( metaInfo->GetTitleId() ); entry.title_id = metaInfo->GetTitleId(); std::string name = metaInfo->GetLongName(GetConfig().console_language.GetValue()); const auto nl = name.find(L'\n'); if (nl != std::string::npos) name.replace(nl, 1, " - "); entry.name = wxString::FromUTF8(name); entry.version = metaInfo->GetTitleVersion(); entry.region = metaInfo->GetRegion(); auto* cmdEvt = new wxCommandEvent(wxEVT_TITLE_FOUND); cmdEvt->SetClientObject(new wxCustomData(entry)); wxQueueEvent(this, cmdEvt); } } void wxTitleManagerList::OnTitleDiscovered(wxCommandEvent& event) { auto* obj = dynamic_cast(event.GetClientObject()); wxASSERT(obj); AddTitle(obj); } void wxTitleManagerList::OnTitleRemoved(wxCommandEvent& event) { auto* obj = dynamic_cast(event.GetClientObject()); wxASSERT(obj); for (auto& itr : m_data) { if (itr.get()->entry.location_uid == obj->get().location_uid) { RemoveItem(itr.get()->entry); break; } } } void wxTitleManagerList::AddTitle(TitleEntryData_t* obj) { const auto& data = obj->GetData(); if (GetTitleEntryByUID(data.location_uid).has_value()) return; // already in list m_data.emplace_back(std::make_unique(true, data)); m_sorted_data.emplace_back(*m_data[m_data.size() - 1]); SetItemCount(m_data.size()); } int wxTitleManagerList::AddImage(const wxImage& image) const { return -1; // m_image_list->Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC)); } // return < bool wxTitleManagerList::SortFunc(int column, const Type_t& v1, const Type_t& v2) { // last sort option if (column == -1) return v1.get().entry.path.compare(v2.get().entry.path) < 0; // visible have always priority if (!v1.get().visible && v2.get().visible) return false; else if (v1.get().visible && !v2.get().visible) return true; const auto& entry1 = v1.get().entry; const auto& entry2 = v2.get().entry; // check column: title id -> type -> path if (column == ColumnTitleId) { // ensure strong ordering -> use type since only one entry should be now (should be changed if every save for every user is displayed separately?) if (entry1.title_id == entry2.title_id) return SortFunc(ColumnType, v1, v2); return entry1.title_id < entry2.title_id; } else if (column == ColumnName) { const int tmp = entry1.name.CmpNoCase(entry2.name); if(tmp == 0) return SortFunc(ColumnTitleId, v1, v2); return tmp < 0; } else if (column == ColumnType) { if(entry1.type == entry2.type) return SortFunc(-1, v1, v2); return std::underlying_type_t(entry1.type) < std::underlying_type_t(entry2.type); } else if (column == ColumnVersion) { if(entry1.version == entry2.version) return SortFunc(ColumnTitleId, v1, v2); return entry1.version < entry2.version; } else if (column == ColumnRegion) { if(entry1.region == entry2.region) return SortFunc(ColumnTitleId, v1, v2); return std::underlying_type_t(entry1.region) < std::underlying_type_t(entry2.region); } else if (column == ColumnFormat) { if(entry1.format == entry2.format) return SortFunc(ColumnType, v1, v2); return std::underlying_type_t(entry1.format) < std::underlying_type_t(entry2.format); } return false; } void wxTitleManagerList::SortEntries(int column) { bool ascending; if (column == -1) { column = GetSortIndicator(); if (column == -1) column = ColumnTitleId; ascending = IsAscendingSortIndicator(); } else ascending = GetUpdatedAscendingSortIndicator(column); if (column != ColumnTitleId && column != ColumnName && column != ColumnType && column != ColumnVersion && column != ColumnRegion && column != ColumnFormat) return; std::sort(m_sorted_data.begin(), m_sorted_data.end(), [this, column, ascending](const Type_t& v1, const Type_t& v2) -> bool { return ascending ? SortFunc(column, v1, v2) : SortFunc(column, v2, v1); }); ShowSortIndicator(column, ascending); RefreshPage(); } void wxTitleManagerList::RefreshPage() { long item_count = GetItemCount(); if (item_count > 0) RefreshItems(GetTopItem(), std::min(item_count - 1, GetTopItem() + GetCountPerPage() + 1)); } int wxTitleManagerList::Filter(const wxString& filter, const wxString& prefix, ItemColumn column) { if (prefix.empty()) return -1; if (!filter.StartsWith(prefix)) return -1; int counter = 0; const auto tmp_filter = filter.substr(prefix.size() - 1).Trim(false); for (auto&& data : m_data) { if (GetTitleEntryText(data->entry, column).Upper().Contains(tmp_filter)) { data->visible = true; ++counter; } else data->visible = false; } return counter; } void wxTitleManagerList::Filter(const wxString& filter) { if(filter.empty()) { std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; }); SetItemCount(m_data.size()); RefreshPage(); return; } const auto filter_upper = filter.Upper().Trim(false).Trim(true); int counter = 0; if (const auto result = Filter(filter_upper, "TITLEID:", ColumnTitleId) != -1) counter = result; else if (const auto result = Filter(filter_upper, "NAME:", ColumnName) != -1) counter = result; else if (const auto result = Filter(filter_upper, "TYPE:", ColumnType) != -1) counter = result; else if (const auto result = Filter(filter_upper, "REGION:", ColumnRegion) != -1) counter = result; else if (const auto result = Filter(filter_upper, "VERSION:", ColumnVersion) != -1) counter = result; else if (const auto result = Filter(filter_upper, "FORMAT:", ColumnFormat) != -1) counter = result; else if(filter_upper == "ERROR") { for (auto&& data : m_data) { bool visible = true; data->visible = visible; if (visible) ++counter; } } else { for (auto&& data : m_data) { bool visible = false; if (data->entry.name.Upper().Contains(filter_upper)) visible = true; else if (GetTitleEntryText(data->entry, ColumnTitleId).Upper().Contains(filter_upper)) visible = true; else if (GetTitleEntryText(data->entry, ColumnType).Upper().Contains(filter_upper)) visible = true; data->visible = visible; if (visible) ++counter; } } SetItemCount(counter); RefreshPage(); } size_t wxTitleManagerList::GetCountByType(EntryType type) const { size_t result = 0; for(const auto& data : m_data) { if (data->entry.type == type) ++result; } return result; } void wxTitleManagerList::ClearItems() { m_sorted_data.clear(); m_data.clear(); SetItemCount(0); RefreshPage(); } void wxTitleManagerList::AutosizeColumns() { wxAutosizeColumns(this, ColumnTitleId, ColumnMAX - 1); } ================================================ FILE: src/gui/wxgui/components/wxTitleManagerList.h ================================================ #pragma once #include "wxgui/helpers/wxCustomData.h" #include "config/CemuConfig.h" #include #include // std::optional doesn't support optional reference inner types yet #include #include class wxTitleManagerList : public wxListView { friend class TitleManager; public: wxTitleManagerList(wxWindow* parent, wxWindowID id = wxID_ANY); ~wxTitleManagerList(); enum ItemColumn { ColumnTitleId = 0, ColumnName, ColumnType, ColumnVersion, ColumnRegion, ColumnFormat, ColumnLocation, ColumnMAX, }; enum class EntryType { Base, Update, Dlc, Save, System, }; enum class EntryFormat { Folder, WUD, NUS, WUA, WUHB, }; // sort by column, if -1 will sort by last column or default (=titleid) void SortEntries(int column); void RefreshPage(); void Filter(const wxString& filter); [[nodiscard]] size_t GetCountByType(EntryType type) const; void ClearItems(); void AutosizeColumns(); struct TitleEntry { TitleEntry(const EntryType& type, const EntryFormat& format, fs::path path) : type(type), format(format), path(std::move(path)) {} EntryType type; EntryFormat format; fs::path path; int icon = -1; uint64 location_uid; uint64 title_id; wxString name; uint32_t version = 0; CafeConsoleRegion region; std::vector persistent_ids; // only used for save }; boost::optional GetSelectedTitleEntry() const; private: void AddColumns(); int Filter(const wxString& filter, const wxString& prefix, ItemColumn column); boost::optional GetSelectedTitleEntry(); boost::optional GetTitleEntryByUID(uint64 uid); class wxPanel* m_tooltip_window; class wxStaticText* m_tooltip_text; class wxTimer* m_tooltip_timer; void OnClose(wxCloseEvent& event); void OnColumnClick(wxListEvent& event); void OnContextMenu(wxContextMenuEvent& event); void OnItemSelected(wxListEvent& event); void OnContextMenuSelected(wxCommandEvent& event); void OnTimer(class wxTimerEvent& event); void OnRemoveItem(wxCommandEvent& event); void OnRemoveEntry(wxCommandEvent& event); void OnTitleDiscovered(wxCommandEvent& event); void OnTitleRemoved(wxCommandEvent& event); void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt); void HandleSaveListCallback(struct CafeSaveListCallbackEvent* evt); using TitleEntryData_t = wxCustomData; void AddTitle(TitleEntryData_t* obj); int AddImage(const wxImage& image) const; wxString OnGetItemText(long item, long column) const override; wxItemAttr* OnGetItemAttr(long item) const override; [[nodiscard]] boost::optional GetTitleEntry(long item) const; [[nodiscard]] boost::optional GetTitleEntry(long item); [[nodiscard]] boost::optional GetTitleEntry(const fs::path& path) const; [[nodiscard]] boost::optional GetTitleEntry(const fs::path& path); void OnConvertToCompressedFormat(uint64 titleId, uint64 rightClickedUID); bool DeleteEntry(long index, const TitleEntry& entry); void RemoveItem(long item); void RemoveItem(const TitleEntry& entry); struct ItemData { ItemData(bool visible, const TitleEntry& entry) : visible(visible), entry(entry) {} bool visible; TitleEntry entry; }; using ItemDataPtr = std::unique_ptr; std::vector m_data; std::vector> m_sorted_data; using Type_t = std::reference_wrapper; bool SortFunc(int column, const Type_t& v1, const Type_t& v2); static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column); static wxString GetTranslatedTitleEntryType(EntryType entryType); std::future m_context_worker; uint64 m_callbackIdTitleList; uint64 m_callbackIdSaveList; }; ================================================ FILE: src/gui/wxgui/debugger/BreakpointWindow.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/debugger/BreakpointWindow.h" #include #include "wxgui/debugger/DebuggerWindow2.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cemu/ExpressionParser/ExpressionParser.h" enum { MENU_ID_CREATE_CODE_BP_EXECUTION = 1, MENU_ID_CREATE_CODE_BP_LOGGING, MENU_ID_CREATE_MEM_BP_READ, MENU_ID_CREATE_MEM_BP_WRITE, MENU_ID_DELETE_BP, }; enum ItemColumns { ColumnEnabled = 0, ColumnAddress, ColumnType, ColumnComment, }; BreakpointWindow::BreakpointWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size) : wxFrame(&parent, wxID_ANY, _("Breakpoints"), wxDefaultPosition, wxSize(420, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT) { this->SetSizeHints(wxDefaultSize, wxDefaultSize); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); m_breakpoints = new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL); m_breakpoints->EnableCheckBoxes(true); wxListItem col0; col0.SetId(ColumnEnabled); col0.SetText(_("On")); col0.SetWidth(32); m_breakpoints->InsertColumn(ColumnEnabled, col0); wxListItem col1; col1.SetId(ColumnAddress); col1.SetText(_("Address")); col1.SetWidth(75); m_breakpoints->InsertColumn(ColumnAddress, col1); wxListItem col2; col2.SetId(ColumnType); col2.SetText(_("Type")); col2.SetWidth(42); m_breakpoints->InsertColumn(ColumnType, col2); wxListItem col3; col3.SetId(ColumnComment); col3.SetText(_("Comment")); col3.SetWidth(250); m_breakpoints->InsertColumn(ColumnComment, col3); main_sizer->Add(m_breakpoints, 1, wxEXPAND); this->SetSizer(main_sizer); this->wxWindowBase::Layout(); this->Centre(wxBOTH); if (parent.GetConfig().data().pin_to_main) OnMainMove(main_position, main_size); m_breakpoints->Bind(wxEVT_LIST_ITEM_CHECKED, &BreakpointWindow::OnBreakpointToggled, this); m_breakpoints->Bind(wxEVT_LIST_ITEM_UNCHECKED, &BreakpointWindow::OnBreakpointToggled, this); m_breakpoints->Bind(wxEVT_LEFT_DCLICK, &BreakpointWindow::OnLeftDClick, this); m_breakpoints->Bind(wxEVT_RIGHT_DOWN, &BreakpointWindow::OnRightDown, this); OnUpdateView(); } BreakpointWindow::~BreakpointWindow() { m_breakpoints->Unbind(wxEVT_LIST_ITEM_CHECKED, &BreakpointWindow::OnBreakpointToggled, this); m_breakpoints->Unbind(wxEVT_LIST_ITEM_UNCHECKED, &BreakpointWindow::OnBreakpointToggled, this); m_breakpoints->Unbind(wxEVT_LEFT_DCLICK, &BreakpointWindow::OnLeftDClick, this); m_breakpoints->Unbind(wxEVT_RIGHT_DOWN, &BreakpointWindow::OnRightDown, this); } void BreakpointWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size) { wxSize size(420, 250); this->SetSize(size); wxPoint position = main_position; position.x -= 420; position.y += main_size.y - 250; this->SetPosition(position); } void BreakpointWindow::OnUpdateView() { Freeze(); m_breakpoints->DeleteAllItems(); if (!debuggerState.breakpoints.empty()) { uint32_t i = 0; for (const auto bpBase : debuggerState.breakpoints) { DebuggerBreakpoint* bp = bpBase; while (bp) { wxListItem item = {}; item.SetId(i++); const auto index = m_breakpoints->InsertItem(item); m_breakpoints->SetItem(index, ColumnAddress, wxString::Format("%08x", bp->address)); const char* typeName = "UKN"; if (bp->bpType == DEBUGGER_BP_T_NORMAL) typeName = "X"; else if (bp->bpType == DEBUGGER_BP_T_LOGGING) typeName = "LOG"; else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT) typeName = "XS"; else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ) typeName = "R"; else if (bp->bpType == DEBUGGER_BP_T_MEMORY_WRITE) typeName = "W"; m_breakpoints->SetItem(index, ColumnType, typeName); m_breakpoints->SetItem(index, ColumnComment, bp->comment); m_breakpoints->CheckItem(index, bp->enabled); m_breakpoints->SetItemPtrData(index, (wxUIntPtr)bp); bp = bp->next; } } } Thaw(); } void BreakpointWindow::OnGameLoaded() { OnUpdateView(); } void BreakpointWindow::OnBreakpointToggled(wxListEvent& event) { const int32_t index = event.GetIndex(); if (0 <= index && index < m_breakpoints->GetItemCount()) { const bool state = m_breakpoints->IsItemChecked(index); wxString line = m_breakpoints->GetItemText(index, ColumnAddress); DebuggerBreakpoint* bp = (DebuggerBreakpoint*)m_breakpoints->GetItemData(index); const uint32 address = std::stoul(line.ToStdString(), nullptr, 16); debugger_toggleBreakpoint(address, state, bp); m_breakpoints->CheckItem(index, state); } } void BreakpointWindow::OnLeftDClick(wxMouseEvent& event) { const auto position = event.GetPosition(); int flags = 0; const long index = m_breakpoints->HitTest(position, flags); if (index == wxNOT_FOUND) return; sint32 x = position.x; const auto enabled_width = m_breakpoints->GetColumnWidth(ColumnEnabled); if (x <= enabled_width) return; x -= enabled_width; const auto address_width = m_breakpoints->GetColumnWidth(ColumnAddress); if (x <= address_width) { const auto item = m_breakpoints->GetItemText(index, ColumnAddress); const auto address = std::stoul(item.ToStdString(), nullptr, 16); debuggerState.debugSession.instructionPointer = address; g_debuggerDispatcher.MoveIP(); return; } x -= address_width; const auto type_width = m_breakpoints->GetColumnWidth(ColumnType); if (x <= type_width) return; x -= type_width; const auto comment_width = m_breakpoints->GetColumnWidth(ColumnComment); if (x <= comment_width) { if (index >= debuggerState.breakpoints.size()) return; const auto item = m_breakpoints->GetItemText(index, ColumnAddress); const auto address = std::stoul(item.ToStdString(), nullptr, 16); auto it = debuggerState.breakpoints.begin(); std::advance(it, index); const wxString dialogTitle = (*it)->bpType == DEBUGGER_BP_T_LOGGING ? _("Enter a new logging message") : _("Enter a new comment"); const wxString dialogMessage = (*it)->bpType == DEBUGGER_BP_T_LOGGING ? _("Set logging message when code at address %08x is ran.\nUse placeholders like {r3} or {f3} to log register values") : _("Set comment for breakpoint at address %08x"); wxTextEntryDialog set_comment_dialog(this, dialogMessage, dialogTitle, (*it)->comment); if (set_comment_dialog.ShowModal() == wxID_OK) { (*it)->comment = set_comment_dialog.GetValue().ToStdWstring(); m_breakpoints->SetItem(index, ColumnComment, set_comment_dialog.GetValue()); } } } void BreakpointWindow::OnRightDown(wxMouseEvent& event) { int flags = 0; const long index = m_breakpoints->HitTest(event.GetPosition(), flags); if (index == wxNOT_FOUND || index < 0 || index >= m_breakpoints->GetItemCount()) { wxMenu menu; menu.Append(MENU_ID_CREATE_CODE_BP_EXECUTION, _("Create execution breakpoint")); menu.Append(MENU_ID_CREATE_CODE_BP_LOGGING, _("Create logging breakpoint")); menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)")); menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)")); menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &BreakpointWindow::OnContextMenuClick, this); PopupMenu(&menu); } else { m_breakpoints->Focus(index); m_breakpoints->Select(index); wxMenu menu; menu.Append(MENU_ID_DELETE_BP, _("Delete breakpoint")); menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &BreakpointWindow::OnContextMenuClickSelected, this); PopupMenu(&menu); } } void BreakpointWindow::OnContextMenuClickSelected(wxCommandEvent& evt) { if (evt.GetId() == MENU_ID_DELETE_BP) { long sel = m_breakpoints->GetFirstSelected(); if (sel == wxNOT_FOUND || sel < 0 || sel >= m_breakpoints->GetItemCount()) return; auto it = debuggerState.breakpoints.begin(); std::advance(it, sel); debugger_deleteBreakpoint(*it); wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); wxPostEvent(this->m_parent, evt); } } void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt) { wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Set breakpoint"), wxEmptyString); if (goto_dialog.ShowModal() == wxID_OK) { ExpressionParser parser; auto value = goto_dialog.GetValue().ToStdString(); std::transform(value.begin(), value.end(), value.begin(), tolower); uint32_t newBreakpointAddress = 0; try { debugger_addParserSymbols(parser); newBreakpointAddress = parser.IsConstantExpression("0x"+value) ? (uint32)parser.Evaluate("0x"+value) : (uint32)parser.Evaluate(value); } catch (const std::exception& ex) { wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } switch (evt.GetId()) { case MENU_ID_CREATE_CODE_BP_EXECUTION: debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_NORMAL); break; case MENU_ID_CREATE_CODE_BP_LOGGING: debugger_createCodeBreakpoint(newBreakpointAddress, DEBUGGER_BP_T_LOGGING); break; case MENU_ID_CREATE_MEM_BP_READ: debugger_createMemoryBreakpoint(newBreakpointAddress, true, false); break; case MENU_ID_CREATE_MEM_BP_WRITE: debugger_createMemoryBreakpoint(newBreakpointAddress, false, true); break; } this->OnUpdateView(); } } ================================================ FILE: src/gui/wxgui/debugger/BreakpointWindow.h ================================================ #pragma once #include class DebuggerWindow2; class wxListEvent; class wxListView; class BreakpointWindow : public wxFrame { public: BreakpointWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size); virtual ~BreakpointWindow(); void OnMainMove(const wxPoint& position, const wxSize& main_size); void OnUpdateView(); void OnGameLoaded(); private: void OnBreakpointToggled(wxListEvent& event); void OnLeftDClick(wxMouseEvent& event); void OnRightDown(wxMouseEvent& event); void OnContextMenuClick(wxCommandEvent& evt); void OnContextMenuClickSelected(wxCommandEvent& evt); wxListView* m_breakpoints; }; ================================================ FILE: src/gui/wxgui/debugger/DebuggerWindow2.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/debugger/DebuggerWindow2.h" #include "wxHelper.h" #include #include "config/ActiveSettings.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cafe/OS/RPL/rpl_debug_symbols.h" #include "wxgui/debugger/RegisterWindow.h" #include "wxgui/debugger/DumpWindow.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/OS/RPL/rpl.h" #include "wxgui/debugger/DisasmCtrl.h" #include "wxgui/debugger/SymbolWindow.h" #include "wxgui/debugger/BreakpointWindow.h" #include "wxgui/debugger/ModuleWindow.h" #include "util/helpers/helpers.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" #include "Cemu/Logging/CemuLogging.h" #include "resource/embedded/resources.h" enum { // file MENU_ID_FILE_EXIT = wxID_HIGHEST + 8000, // settings MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, MENU_ID_OPTIONS_BREAK_ON_START, MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS, MENU_ID_OPTIONS_SWITCH_CPU_MODE, // window MENU_ID_WINDOW_REGISTERS, MENU_ID_WINDOW_DUMP, MENU_ID_WINDOW_STACK, MENU_ID_WINDOW_BREAKPOINTS, MENU_ID_WINDOW_MODULE, MENU_ID_WINDOW_SYMBOL, // tool TOOL_ID_GOTO, TOOL_ID_BP, TOOL_ID_PAUSE, TOOL_ID_STEP_OVER, TOOL_ID_STEP_INTO, }; wxDEFINE_EVENT(wxEVT_DEBUGGER_CLOSE, wxCloseEvent); wxDEFINE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent); wxDEFINE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent); wxDEFINE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent); wxDEFINE_EVENT(wxEVT_MOVE_IP, wxCommandEvent); wxDEFINE_EVENT(wxEVT_RUN, wxCommandEvent); wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent); wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent); wxDEFINE_EVENT(wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED, wxCommandEvent); wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame) EVT_SHOW(DebuggerWindow2::OnShow) EVT_CLOSE(DebuggerWindow2::OnClose) EVT_COMMAND(wxID_ANY, wxEVT_UPDATE_VIEW, DebuggerWindow2::OnUpdateView) EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_CHANGE, DebuggerWindow2::OnBreakpointChange) EVT_COMMAND(wxID_ANY, wxEVT_MOVE_IP, DebuggerWindow2::OnMoveIP) EVT_COMMAND(wxID_ANY, wxEVT_COMMAND_TOOL_CLICKED, DebuggerWindow2::OnToolClicked) EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_HIT, DebuggerWindow2::OnBreakpointHit) EVT_COMMAND(wxID_ANY, wxEVT_RUN, DebuggerWindow2::OnRunProgram) EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_LOADED, DebuggerWindow2::OnNotifyModuleLoaded) EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_UNLOADED, DebuggerWindow2::OnNotifyModuleUnloaded) EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED, DebuggerWindow2::OnNotifyGraphicPacksModified) EVT_COMMAND(wxID_ANY, wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, DebuggerWindow2::OnDisasmCtrlGotoAddress) // file menu EVT_MENU(MENU_ID_FILE_EXIT, DebuggerWindow2::OnExit) // window EVT_MENU_RANGE(MENU_ID_WINDOW_REGISTERS, MENU_ID_WINDOW_MODULE, DebuggerWindow2::OnWindowMenu) wxEND_EVENT_TABLE() DebuggerWindow2* g_debugger_window; void DebuggerConfig::Load(XMLConfigParser& parser) { pin_to_main = parser.get("PinToMainWindow", true); break_on_start = parser.get("break_on_start", true); log_memory_breakpoints = parser.get("log_memory_breakpoints", false); auto window_parser = parser.get("Windows"); show_register = window_parser.get("Registers", true); show_dump = window_parser.get("MemoryDump", true); show_stack = window_parser.get("Stack", true); show_breakpoints = window_parser.get("Breakpoints", true); show_modules = window_parser.get("Modules", true); show_symbols = window_parser.get("Symbols", true); } void DebuggerConfig::Save(XMLConfigParser& parser) { parser.set("PinToMainWindow", pin_to_main); parser.set("break_on_start", break_on_start); parser.set("log_memory_breakpoints", log_memory_breakpoints); auto window_parser = parser.set("Windows"); window_parser.set("Registers", show_register); window_parser.set("MemoryDump", show_dump); window_parser.set("Stack", show_stack); window_parser.set("Breakpoints", show_breakpoints); window_parser.set("Modules", show_modules); window_parser.set("Symbols", show_symbols); } void DebuggerModuleStorage::Load(XMLConfigParser& parser) { auto breakpoints_parser = parser.get("Breakpoints"); for (auto element = breakpoints_parser.get("Entry"); element.valid(); element = breakpoints_parser.get("Entry", element)) { const auto address_string = element.get("Address", ""); if (*address_string == '\0') continue; uint32 relative_address = std::strtoul(address_string, nullptr, 16); if (relative_address == 0) continue; auto type = element.get("Type", 0); auto enabled = element.get("Enabled", true); const auto comment = element.get("Comment", ""); // calculate absolute address uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL || type == DEBUGGER_BP_T_LOGGING) ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data; uint32 address = module_base_address + relative_address; // don't change anything if there's already a breakpoint if (debugger_getFirstBP(address) != nullptr) continue; // register breakpoints in debugger if (type == DEBUGGER_BP_T_NORMAL) debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); else if (type == DEBUGGER_BP_T_LOGGING) debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING); else if (type == DEBUGGER_BP_T_MEMORY_READ) debugger_createMemoryBreakpoint(address, true, false); else if (type == DEBUGGER_BP_T_MEMORY_WRITE) debugger_createMemoryBreakpoint(address, false, true); else continue; DebuggerBreakpoint* debugBreakpoint = debugger_getFirstBP(address); if (!enabled) debugger_toggleBreakpoint(address, false, debugBreakpoint); debugBreakpoint->comment = boost::nowide::widen(comment); } auto comments_parser = parser.get("Comments"); for (XMLConfigParser element = comments_parser.get("Entry"); element.valid(); element = comments_parser.get("Entry", element)) { const auto address_string = element.get("Address", ""); if (*address_string == '\0') continue; uint32 address = std::strtoul(address_string, nullptr, 16); if (address == 0) continue; const auto comment = element.get("Comment", ""); if (*comment == '\0') continue; rplDebugSymbol_createComment(address, boost::nowide::widen(comment).c_str()); } } void DebuggerModuleStorage::Save(XMLConfigParser& parser) { auto breakpoints_parser = parser.set("Breakpoints"); for (const auto& bp : debuggerState.breakpoints) { // check breakpoint type if (bp->dbType != DEBUGGER_BP_T_DEBUGGER) continue; // check whether the breakpoint is part of the current module being saved RPLModule* address_module; if (bp->bpType == DEBUGGER_BP_T_NORMAL || bp->bpType == DEBUGGER_BP_T_LOGGING) address_module = RPLLoader_FindModuleByCodeAddr(bp->address); else if (bp->isMemBP()) address_module = RPLLoader_FindModuleByDataAddr(bp->address); else continue; if (!address_module || !(address_module->moduleName2 == this->module_name && address_module->patchCRC == this->crc_hash)) continue; uint32_t relative_address = bp->address - (bp->isMemBP() ? address_module->regionMappingBase_data : address_module->regionMappingBase_text.GetMPTR()); auto entry = breakpoints_parser.set("Entry"); entry.set("Address", fmt::format("{:#10x}", relative_address)); entry.set("Comment", boost::nowide::narrow(bp->comment).c_str()); entry.set("Type", bp->bpType); entry.set("Enabled", bp->enabled); if (this->delete_breakpoints_after_saving) debugger_deleteBreakpoint(bp); this->delete_breakpoints_after_saving = false; } auto comments_parser = parser.set("Comments"); for (const auto& comment_entry : rplDebugSymbol_getSymbols()) { // check comment type const auto comment_address = comment_entry.first; const auto comment = static_cast(comment_entry.second); if (!comment || comment->type != RplDebugSymbolComment) continue; // check whether it's part of the current module being saved RPLModule* address_module = RPLLoader_FindModuleByCodeAddr(comment_entry.first); if (!address_module || !(address_module->moduleName2 == module_name && address_module->patchCRC == this->crc_hash)) continue; uint32_t relative_address = comment_address - address_module->regionMappingBase_text.GetMPTR(); auto entry = comments_parser.set("Entry"); entry.set("Address", fmt::format("{:#10x}", relative_address)); entry.set("Comment", boost::nowide::narrow(comment->comment).c_str()); } } void DebuggerWindow2::CreateToolBar() { m_toolbar = wxFrame::CreateToolBar(wxTB_HORIZONTAL, wxID_ANY); m_toolbar->SetToolBitmapSize(wxSize(16, 16)); wxBitmap goto_bitmap = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_GOTO_png, sizeof(DEBUGGER_GOTO_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); m_toolbar->AddTool(TOOL_ID_GOTO, wxEmptyString, goto_bitmap, wxNullBitmap, wxITEM_NORMAL, _("GoTo (CTRL + G)"), "test", NULL); m_toolbar->AddSeparator(); wxBitmap bp_bitmap = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_BP_RED_png, sizeof(DEBUGGER_BP_RED_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); m_toolbar->AddTool(TOOL_ID_BP, wxEmptyString, bp_bitmap, wxNullBitmap, wxITEM_NORMAL, _("Toggle Breakpoint (F9)"), wxEmptyString, NULL); m_toolbar->AddSeparator(); m_pause = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_PAUSE_png, sizeof(DEBUGGER_PAUSE_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); m_run = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_PLAY_png, sizeof(DEBUGGER_PLAY_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); m_toolbar->AddTool(TOOL_ID_PAUSE, wxEmptyString, m_pause, wxNullBitmap, wxITEM_NORMAL, _("Break (F5)"), wxEmptyString, NULL); wxBitmap step_into_bitmap = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_STEP_INTO_png, sizeof(DEBUGGER_STEP_INTO_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); wxBitmap step_over_bitmap = wxHelper::LoadThemedBitmapFromPNG(DEBUGGER_STEP_OVER_png, sizeof(DEBUGGER_STEP_OVER_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); m_toolbar->AddTool(TOOL_ID_STEP_INTO, wxEmptyString, step_into_bitmap, wxNullBitmap, wxITEM_NORMAL, _("Step Into (F11)"), wxEmptyString, NULL); m_toolbar->AddTool(TOOL_ID_STEP_OVER, wxEmptyString, step_over_bitmap, wxNullBitmap, wxITEM_NORMAL, _("Step Over (F10)"), wxEmptyString, NULL); m_toolbar->AddSeparator(); m_toolbar->Realize(); m_toolbar->EnableTool(TOOL_ID_STEP_INTO, false); m_toolbar->EnableTool(TOOL_ID_STEP_OVER, false); } void DebuggerWindow2::SaveModuleStorage(const RPLModule* module, bool delete_breakpoints) { auto path = GetModuleStoragePath(module->moduleName2, module->patchCRC); for (auto& module_storage : m_modules_storage) { if (module_storage->data().module_name == module->moduleName2 && module_storage->data().crc_hash == module->patchCRC) { module_storage->data().delete_breakpoints_after_saving = delete_breakpoints; module_storage->Save(path); if (delete_breakpoints) m_modules_storage.erase(std::find(m_modules_storage.begin(), m_modules_storage.end(), module_storage)); } } } void DebuggerWindow2::LoadModuleStorage(const RPLModule* module) { auto path = GetModuleStoragePath(module->moduleName2, module->patchCRC); bool already_loaded = std::any_of(m_modules_storage.begin(), m_modules_storage.end(), [path](const std::unique_ptr& debug) { return debug->GetFilename() == path; }); if (!path.empty() && !already_loaded) { m_modules_storage.emplace_back(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false }))->Load(); } } DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size) : wxFrame(&parent, wxID_ANY, _("PPC Debugger"), wxDefaultPosition, wxSize(1280, 300), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT), m_module_address(0) { g_debuggerDispatcher.SetDebuggerCallbacks(this); const auto file = ActiveSettings::GetConfigPath("debugger/config.xml"); m_config.SetFilename(file.generic_wstring()); m_config.Load(); debuggerState.breakOnEntry = m_config.data().break_on_start; debuggerState.logOnlyMemoryBreakpoints = m_config.data().log_memory_breakpoints; m_main_position = parent.GetPosition(); m_main_size = parent.GetSize(); auto y = std::max(300, (display_size.GetHeight() - 500 - 300) * 0.8); this->SetSize(1280, y); CreateMenuBar(); CreateToolBar(); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); // load configs for already loaded modules const auto module_count = RPLLoader_GetModuleCount(); const auto module_list = RPLLoader_GetModuleList(); for (sint32 i = 0; i < module_count; i++) { const auto module = module_list[i]; LoadModuleStorage(module); } wxString label_text = _("> no modules loaded"); if (module_count != 0) { RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR); if (current_rpl_module) label_text = wxString::Format("> %s", current_rpl_module->moduleName2.c_str()); else label_text = _("> unknown module"); } m_module_label = new wxStaticText(this, wxID_ANY, label_text); m_module_label->SetForegroundColour(wxColour(0xFFbf52fe)); main_sizer->Add(m_module_label, 0, wxEXPAND | wxALL, 5); m_disasm_ctrl = new DisasmCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle); main_sizer->Add(m_disasm_ctrl, 1, wxEXPAND); this->SetSizer(main_sizer); this->wxWindowBase::Layout(); m_register_window = new RegisterWindow(*this, m_main_position, m_main_size); m_dump_window = new DumpWindow(*this, m_main_position, m_main_size); m_breakpoint_window = new BreakpointWindow(*this, m_main_position, m_main_size); m_module_window = new ModuleWindow(*this, m_main_position, m_main_size); m_symbol_window = new SymbolWindow(*this, m_main_position, m_main_size); const bool value = m_config.data().pin_to_main; m_config.data().pin_to_main = true; OnParentMove(m_main_position, m_main_size); m_config.data().pin_to_main = value; g_debugger_window = this; } DebuggerWindow2::~DebuggerWindow2() { g_debuggerDispatcher.ClearDebuggerCallbacks(); debuggerState.breakOnEntry = false; g_debugger_window = nullptr; // save configs for all modules that are still loaded // doesn't delete breakpoints since that should (in the future) be done by unloading the rpl modules when exiting the current game const auto module_count = RPLLoader_GetModuleCount(); const auto module_list = RPLLoader_GetModuleList(); for (sint32 i = 0; i < module_count; i++) { const auto module = module_list[i]; if (module) SaveModuleStorage(module, false); } if (m_register_window && m_register_window->IsShown()) m_register_window->Close(true); if (m_dump_window && m_dump_window->IsShown()) m_dump_window->Close(true); if (m_breakpoint_window && m_breakpoint_window->IsShown()) m_breakpoint_window->Close(true); if (m_module_window && m_module_window->IsShown()) m_module_window->Close(true); if (m_symbol_window && m_symbol_window->IsShown()) m_symbol_window->Close(true); m_config.Save(); } void DebuggerWindow2::OnClose(wxCloseEvent& event) { debuggerState.breakOnEntry = false; const wxCloseEvent parentEvent(wxEVT_DEBUGGER_CLOSE); wxPostEvent(m_parent, parentEvent); event.Skip(); } void DebuggerWindow2::OnMoveIP(wxCommandEvent& event) { const auto ip = debuggerState.debugSession.instructionPointer; UpdateModuleLabel(ip); m_disasm_ctrl->CenterOffset(ip); } void DebuggerWindow2::OnDisasmCtrlGotoAddress(wxCommandEvent& event) { uint32 address = static_cast(event.GetExtraLong()); UpdateModuleLabel(address); } void DebuggerWindow2::OnParentMove(const wxPoint& main_position, const wxSize& main_size) { m_main_position = main_position; m_main_size = main_size; if (!m_config.data().pin_to_main) return; wxSize size(m_main_size.x, GetSize().GetHeight()); SetSize(size); wxPoint position = m_main_position; position.y -= size.y; SetPosition(position); m_register_window->OnMainMove(main_position, main_size); m_dump_window->OnMainMove(main_position, main_size); m_breakpoint_window->OnMainMove(main_position, main_size); m_module_window->OnMainMove(main_position, main_size); m_symbol_window->OnMainMove(main_position, main_size); } void DebuggerWindow2::OnNotifyModuleLoaded(wxCommandEvent& event) { RPLModule* module = (RPLModule*)event.GetClientData(); LoadModuleStorage(module); m_module_window->OnGameLoaded(); m_symbol_window->OnGameLoaded(); m_disasm_ctrl->Init(); } void DebuggerWindow2::OnNotifyModuleUnloaded(wxCommandEvent& event) { RPLModule* module = (RPLModule*)event.GetClientData(); // todo - the RPL module is already unloaded at this point. Find a better way to handle this SaveModuleStorage(module, true); m_module_window->OnGameLoaded(); m_symbol_window->OnGameLoaded(); m_disasm_ctrl->Init(); } void DebuggerWindow2::OnNotifyGraphicPacksModified(wxCommandEvent& event) { m_module_window->OnGameLoaded(); m_symbol_window->OnGameLoaded(); m_disasm_ctrl->Init(); } void DebuggerWindow2::OnGameLoaded() { m_disasm_ctrl->Init(); m_dump_window->OnGameLoaded(); m_module_window->OnGameLoaded(); m_breakpoint_window->OnGameLoaded(); m_symbol_window->OnGameLoaded(); RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR); if(current_rpl_module) m_module_label->SetLabel(wxString::Format("> %s", current_rpl_module->moduleName2.c_str())); this->SendSizeEvent(); } XMLDebuggerConfig& DebuggerWindow2::GetConfig() { return m_config; } bool DebuggerWindow2::Show(bool show) { const bool result = wxFrame::Show(show); if (show) { m_register_window->Show(m_config.data().show_register); m_dump_window->Show(m_config.data().show_dump); m_breakpoint_window->Show(m_config.data().show_breakpoints); m_module_window->Show(m_config.data().show_modules); m_symbol_window->Show(m_config.data().show_symbols); } else { m_register_window->Show(false); m_dump_window->Show(false); m_breakpoint_window->Show(false); m_module_window->Show(false); m_symbol_window->Show(false); } return result; } std::wstring DebuggerWindow2::GetModuleStoragePath(std::string module_name, uint32_t crc_hash) const { if (module_name.empty() || crc_hash == 0) return {}; return ActiveSettings::GetConfigPath("debugger/{}_{:#10x}.xml", module_name, crc_hash).generic_wstring(); } void DebuggerWindow2::OnBreakpointHit(wxCommandEvent& event) { const auto ip = debuggerState.debugSession.instructionPointer; UpdateModuleLabel(ip); m_toolbar->SetToolShortHelp(TOOL_ID_PAUSE, _("Run (F5)")); m_toolbar->SetToolNormalBitmap(TOOL_ID_PAUSE, m_run); m_toolbar->EnableTool(TOOL_ID_STEP_INTO, true); m_toolbar->EnableTool(TOOL_ID_STEP_OVER, true); m_disasm_ctrl->CenterOffset(ip); } void DebuggerWindow2::OnRunProgram(wxCommandEvent& event) { m_toolbar->SetToolShortHelp(TOOL_ID_PAUSE, _("Break (F5)")); m_toolbar->SetToolNormalBitmap(TOOL_ID_PAUSE, m_pause); m_toolbar->EnableTool(TOOL_ID_STEP_INTO, false); m_toolbar->EnableTool(TOOL_ID_STEP_OVER, false); } void DebuggerWindow2::OnToolClicked(wxCommandEvent& event) { switch(event.GetId()) { case TOOL_ID_GOTO: m_disasm_ctrl->GoToAddressDialog(); break; case TOOL_ID_PAUSE: if (debugger_isTrapped()) debugger_resume(); else debugger_forceBreak(); break; case TOOL_ID_STEP_INTO: if (debugger_isTrapped()) debuggerState.debugSession.stepInto = true; break; case TOOL_ID_STEP_OVER: if (debugger_isTrapped()) debuggerState.debugSession.stepOver = true; break; } } void DebuggerWindow2::OnBreakpointChange(wxCommandEvent& event) { m_breakpoint_window->OnUpdateView(); m_disasm_ctrl->RefreshControl(); UpdateModuleLabel(); } void DebuggerWindow2::OnOptionsInput(wxCommandEvent& event) { switch (event.GetId()) { case MENU_ID_OPTIONS_PIN_TO_MAINWINDOW: { const bool value = !m_config.data().pin_to_main; m_config.data().pin_to_main = value; if (value) OnParentMove(m_main_position, m_main_size); break; } case MENU_ID_OPTIONS_BREAK_ON_START: { const bool value = !m_config.data().break_on_start; m_config.data().break_on_start = value; debuggerState.breakOnEntry = value; break; } case MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS: { const bool value = !m_config.data().log_memory_breakpoints; m_config.data().log_memory_breakpoints = value; debuggerState.logOnlyMemoryBreakpoints = value; break; } case MENU_ID_OPTIONS_SWITCH_CPU_MODE: { if (ppcRecompilerEnabled) { ppcRecompilerEnabled = false; cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to interpreter"); } else { ppcRecompilerEnabled = true; cemuLog_log(LogType::Force, "Debugger: Switched CPU mode to recompiler"); } break; } default: return; } m_config.Save(); } void DebuggerWindow2::OnWindowMenu(wxCommandEvent& event) { switch (event.GetId()) { case MENU_ID_WINDOW_REGISTERS: { const bool value = !m_config.data().show_register; m_config.data().show_register = value; m_register_window->Show(value); break; } case MENU_ID_WINDOW_DUMP: { const bool value = !m_config.data().show_dump; m_config.data().show_dump = value; m_dump_window->Show(value); break; } case MENU_ID_WINDOW_BREAKPOINTS: { const bool value = !m_config.data().show_breakpoints; m_config.data().show_breakpoints = value; m_breakpoint_window->Show(value); break; } case MENU_ID_WINDOW_MODULE: { const bool value = !m_config.data().show_modules; m_config.data().show_modules = value; m_module_window->Show(value); break; } case MENU_ID_WINDOW_SYMBOL: { const bool value = !m_config.data().show_symbols; m_config.data().show_symbols = value; m_symbol_window->Show(value); break; } default: return; } m_config.Save(); } void DebuggerWindow2::OnUpdateView(wxCommandEvent& event) { UpdateModuleLabel(); m_disasm_ctrl->OnUpdateView(); m_register_window->OnUpdateView(); m_breakpoint_window->OnUpdateView(); } void DebuggerWindow2::OnExit(wxCommandEvent& event) { this->Close(); } void DebuggerWindow2::OnShow(wxShowEvent& event) { m_register_window->Show(); m_dump_window->Show(); m_breakpoint_window->Show(); m_module_window->Show(); m_symbol_window->Show(); event.Skip(); } void DebuggerWindow2::CreateMenuBar() { auto menu_bar = new wxMenuBar; // file wxMenu* file_menu = new wxMenu; file_menu->Append(MENU_ID_FILE_EXIT, _("&Exit")); file_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnExit, this); menu_bar->Append(file_menu, _("&File")); // options wxMenu* options_menu = new wxMenu; options_menu->Append(MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, _("&Pin to main window"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().pin_to_main); options_menu->Append(MENU_ID_OPTIONS_BREAK_ON_START, _("Break on &entry point"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().break_on_start); options_menu->Append(MENU_ID_OPTIONS_LOG_MEMORY_BREAKPOINTS, _("Log only memory breakpoints"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().log_memory_breakpoints); options_menu->Append(MENU_ID_OPTIONS_SWITCH_CPU_MODE, _("Switch to &interpreter CPU mode"), wxEmptyString, wxITEM_CHECK); menu_bar->Append(options_menu, _("&Options")); // window wxMenu* window_menu = new wxMenu; window_menu->Append(MENU_ID_WINDOW_REGISTERS, _("&Registers"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_register); window_menu->Append(MENU_ID_WINDOW_DUMP, _("&Memory Dump"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_dump); window_menu->Append(MENU_ID_WINDOW_BREAKPOINTS, _("&Breakpoints"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_breakpoints); window_menu->Append(MENU_ID_WINDOW_MODULE, _("Module&list"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_modules); window_menu->Append(MENU_ID_WINDOW_SYMBOL, _("&Symbols"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_symbols); menu_bar->Append(window_menu, _("&Window")); SetMenuBar(menu_bar); options_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnOptionsInput, this); window_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnWindowMenu, this); } void DebuggerWindow2::UpdateModuleLabel(uint32 address) { if (address == 0) address = m_disasm_ctrl->GetViewBaseAddress(); RPLModule* module = RPLLoader_FindModuleByCodeAddr(address); if (module && m_module_address != module->regionMappingBase_text.GetMPTR()) { m_module_label->SetLabel(wxString::Format("> %s", module->moduleName2.c_str())); m_module_address = module->regionMappingBase_text.GetMPTR(); } else if (address >= mmuRange_CODECAVE.getBase() && address < mmuRange_CODECAVE.getEnd()) { m_module_label->SetLabel(wxString::Format("> %s", "Cemu codecave")); m_module_address = mmuRange_CODECAVE.getBase(); } } void DebuggerWindow2::UpdateViewThreadsafe() { auto* evt = new wxCommandEvent(wxEVT_UPDATE_VIEW); wxQueueEvent(this, evt); } void DebuggerWindow2::NotifyDebugBreakpointHit() { auto* evt = new wxCommandEvent(wxEVT_BREAKPOINT_HIT); wxQueueEvent(this, evt); } void DebuggerWindow2::NotifyRun() { auto* evt = new wxCommandEvent(wxEVT_RUN); wxQueueEvent(this, evt); } void DebuggerWindow2::MoveIP() { auto* evt = new wxCommandEvent(wxEVT_MOVE_IP); wxQueueEvent(this, evt); } void DebuggerWindow2::NotifyModuleLoaded(void* module) { auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_LOADED); evt->SetClientData(module); wxQueueEvent(this, evt); } void DebuggerWindow2::NotifyGraphicPacksModified() { auto* evt = new wxCommandEvent(wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED); wxQueueEvent(this, evt); } void DebuggerWindow2::NotifyModuleUnloaded(void* module) { auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_UNLOADED); evt->SetClientData(module); wxQueueEvent(this, evt); } ================================================ FILE: src/gui/wxgui/debugger/DebuggerWindow2.h ================================================ #pragma once #include "wxgui/debugger/DisasmCtrl.h" #include "config/XMLConfig.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/HW/Espresso/Debugger/GDBStub.h" #include #include #include class BreakpointWindow; class RegisterWindow; class DumpWindow; class ModuleWindow; class SymbolWindow; class wxStaticText; wxDECLARE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent); wxDECLARE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent); wxDECLARE_EVENT(wxEVT_RUN, wxCommandEvent); wxDECLARE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent); wxDECLARE_EVENT(wxEVT_MOVE_IP, wxCommandEvent); wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent); wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent); wxDECLARE_EVENT(wxEVT_NOTIFY_GRAPHIC_PACKS_MODIFIED, wxCommandEvent); extern class DebuggerWindow2* g_debugger_window; struct DebuggerConfig { DebuggerConfig() : pin_to_main(true), break_on_start(true), log_memory_breakpoints(false), show_register(true), show_dump(true), show_stack(true), show_breakpoints(true), show_modules(true), show_symbols(true) {} bool pin_to_main; bool break_on_start; bool log_memory_breakpoints; bool show_register; bool show_dump; bool show_stack; bool show_breakpoints; bool show_modules; bool show_symbols; void Load(XMLConfigParser& parser); void Save(XMLConfigParser& parser); }; typedef XMLDataConfig XMLDebuggerConfig; struct DebuggerModuleStorage { std::string module_name; uint32_t crc_hash; const RPLModule* rpl_module; bool delete_breakpoints_after_saving; void Load(XMLConfigParser& parser); void Save(XMLConfigParser& parser); }; typedef XMLDataConfig XMLDebuggerModuleConfig; static wxBitmap LoadThemedBitmapFromPNG(const uint8* data, size_t size, const wxColour& tint) { wxMemoryInputStream strm(data, size); wxImage img(strm, wxBITMAP_TYPE_PNG); img.Replace(0x00, 0x00, 0x00, tint.Red(), tint.Green(), tint.Blue()); return wxBitmap(img); } class DebuggerWindow2 : public wxFrame, public DebuggerCallbacks { public: void CreateToolBar(); void LoadModuleStorage(const RPLModule* module); void SaveModuleStorage(const RPLModule* module, bool delete_breakpoints); DebuggerWindow2(wxFrame& parent, const wxRect& display_size); ~DebuggerWindow2(); void OnParentMove(const wxPoint& position, const wxSize& size); void OnGameLoaded(); XMLDebuggerConfig& GetConfig(); bool Show(bool show = true) override; std::wstring GetModuleStoragePath(std::string module_name, uint32_t crc_hash) const; private: void OnBreakpointHit(wxCommandEvent& event); void OnRunProgram(wxCommandEvent& event); void OnToolClicked(wxCommandEvent& event); void OnBreakpointChange(wxCommandEvent& event); void OnOptionsInput(wxCommandEvent& event); void OnWindowMenu(wxCommandEvent& event); void OnUpdateView(wxCommandEvent& event); void OnExit(wxCommandEvent& event); void OnShow(wxShowEvent& event); void OnClose(wxCloseEvent& event); void OnMoveIP(wxCommandEvent& event); void OnNotifyModuleLoaded(wxCommandEvent& event); void OnNotifyModuleUnloaded(wxCommandEvent& event); void OnNotifyGraphicPacksModified(wxCommandEvent& event); // events from DisasmCtrl void OnDisasmCtrlGotoAddress(wxCommandEvent& event); void CreateMenuBar(); void UpdateModuleLabel(uint32 address = 0); void UpdateViewThreadsafe() override; void NotifyDebugBreakpointHit() override; void NotifyRun() override; void MoveIP() override; void NotifyModuleLoaded(void* module) override; void NotifyGraphicPacksModified() override; void NotifyModuleUnloaded(void* module) override; XMLDebuggerConfig m_config; std::vector> m_modules_storage; wxPoint m_main_position; wxSize m_main_size; RegisterWindow* m_register_window; DumpWindow* m_dump_window; BreakpointWindow* m_breakpoint_window; ModuleWindow* m_module_window; SymbolWindow* m_symbol_window; DisasmCtrl* m_disasm_ctrl; wxToolBar* m_toolbar; wxBitmap m_run, m_pause; uint32 m_module_address; wxStaticText* m_module_label; wxDECLARE_EVENT_TABLE(); }; ================================================ FILE: src/gui/wxgui/debugger/DisasmCtrl.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/debugger/DisasmCtrl.h" #include "wxHelper.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/OS/RPL/rpl_debug_symbols.h" #include "Cemu/PPCAssembler/ppcAssembler.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "wxgui/debugger/DebuggerWindow2.h" #include "util/helpers/helpers.h" #include "Cemu/ExpressionParser/ExpressionParser.h" #include "Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h" wxDEFINE_EVENT(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, wxCommandEvent); #define MAX_SYMBOL_LEN (120) constexpr uint8 arrowRightPNG[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0B, 0x08, 0x03, 0x00, 0x00, 0x00, 0x41, 0x3C, 0xFD, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x06, 0x50, 0x4C, 0x54, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA5, 0x67, 0xB9, 0xCF, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4E, 0x53, 0xFF, 0x00, 0xE5, 0xB7, 0x30, 0x4A, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, 0xC7, 0x6F, 0xA8, 0x64, 0x00, 0x00, 0x00, 0x2C, 0x49, 0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0x60, 0x84, 0x03, 0x08, 0x13, 0x59, 0x00, 0xCC, 0x46, 0x11, 0x00, 0x71, 0x80, 0x24, 0x32, 0xC0, 0x10, 0x60, 0xC0, 0x10, 0xC0, 0x00, 0x58, 0xCC, 0x80, 0xD8, 0x00, 0x02, 0x60, 0x3E, 0x7E, 0x77, 0x00, 0x31, 0x23, 0x23, 0x00, 0x21, 0x95, 0x00, 0x5B, 0x20, 0x73, 0x8D, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; std::optional g_ipArrowBitmap; static wxColour theme_textForeground; static wxColour theme_textForegroundMuted; static wxColour theme_background; // background colors for lines in the disassembly view static wxColour theme_lineBreakpointAndCurrentInstruction; static wxColour theme_lineCurrentInstruction; static wxColour theme_lineBreakpointSet; static wxColour theme_lineLoggingBreakpointSet; static wxColour theme_lineLastGotoAddress; // text colors for addresses in the disassembly view static wxColour theme_syntaxGPR; static wxColour theme_syntaxFPR; static wxColour theme_syntaxSPR; static wxColour theme_syntaxCR; static wxColour theme_syntaxIMM; static wxColour theme_syntaxIMMOffset; static wxColour theme_syntaxCallIMM; static wxColour theme_syntaxPseudoOrUnknown; static wxColour theme_syntaxSymbol; static wxColour theme_opCode; static wxColour theme_typeData; static wxColour theme_patchedOpCode; static wxColour theme_patchedData; static void InitSyntaxColors() { theme_textForeground = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); theme_textForegroundMuted = wxSystemSettings::SelectLightDark(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT), wxColour(140, 142, 145)); theme_background = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); // line background highlights theme_lineBreakpointSet = wxSystemSettings::SelectLightDark(wxColour(0xFF,0x80,0x80), wxColour(0x84,0x21,0x21)); // red for a non-current breakpoint theme_lineBreakpointAndCurrentInstruction = wxSystemSettings::SelectLightDark(wxColour(0xD2,0x7E,0xD2), wxColour(0x91,0x44,0xA4)); // pink for a current breakpoint theme_lineCurrentInstruction = wxSystemSettings::SelectLightDark(wxColour(0xFF,0xA0,0x80), wxColour(0x87,0x53,0x1A)); // light orange theme_lineLoggingBreakpointSet = wxSystemSettings::SelectLightDark(wxColour(0xAB,0xED,0xEE), wxColour(0x25,0x5D,0x6D)); // light blue theme_lineLastGotoAddress = wxSystemSettings::SelectLightDark(wxColour(0xE0,0xE0,0xE0), wxColour(0x40,0x40,0x40)); // very light gray to indicate the line that was last jumped to // disassembly syntax colors theme_syntaxGPR = wxSystemSettings::SelectLightDark(wxColour(0x66,0x00,0x00), wxColour(0xE0,0x6C,0x75)); theme_syntaxFPR = wxSystemSettings::SelectLightDark(wxColour(0x66,0x66,0x00), wxColour(0xD1,0x9A,0x66)); theme_syntaxSPR = wxSystemSettings::SelectLightDark(wxColour(0x00,0x66,0x66), wxColour(0x56,0xB6,0xC2)); theme_syntaxCR = wxSystemSettings::SelectLightDark(wxColour(0x00,0x66,0x66), wxColour(0x56,0xB6,0xC2)); theme_syntaxIMM = wxSystemSettings::SelectLightDark(wxColour(0x00,0x66,0x00), wxColour(0x9D,0xDE,0x6F)); theme_syntaxIMMOffset = wxSystemSettings::SelectLightDark(wxColour(0x00,0x66,0x00), wxColour(0x9D,0xDE,0x6F)); theme_syntaxCallIMM = wxSystemSettings::SelectLightDark(wxColour(0x00,0x00,0x88), wxColour(0x61,0xAF,0xEF)); theme_syntaxPseudoOrUnknown = wxSystemSettings::SelectLightDark(wxColour(0xA0,0xA0,0xA0), wxColour(0x5C,0x63,0x70)); theme_syntaxSymbol = wxSystemSettings::SelectLightDark(wxColour(0xA0,0x00,0x00), wxColour(0xE0,0x6C,0x75)); // opcode & data highlighting theme_opCode = wxSystemSettings::SelectLightDark(wxColour(0xFF,0x00,0x40), wxColour(0xFF,0x70,0x7B)); theme_patchedOpCode = wxSystemSettings::SelectLightDark(wxColour(0xFF,0x20,0x20), wxColour(0xCC,0x52,0xF5)); theme_typeData = wxSystemSettings::SelectLightDark(wxColour(0xFF,0x20,0x20), wxColour(0xCE,0x91,0x78)); theme_patchedData = wxSystemSettings::SelectLightDark(wxColour(0xFF,0x20,0x20), wxColour(0xF4,0x43,0x36)); // theme the current instruction pointer arrow g_ipArrowBitmap = wxHelper::LoadThemedBitmapFromPNG(arrowRightPNG, sizeof(arrowRightPNG), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); } #define OFFSET_ADDRESS (60) #define OFFSET_ADDRESS_RELATIVE (90) #define OFFSET_DISASSEMBLY (300) #define OFFSET_DISASSEMBLY_OPERAND (80) DisasmCtrl::DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style) : TextList(parent, id, pos, size, style), m_mouse_line(-1), m_mouse_line_drawn(-1), m_active_line(-1) { Init(); if (!g_ipArrowBitmap.has_value()) { InitSyntaxColors(); } auto tooltip_sizer = new wxBoxSizer(wxVERTICAL); tooltip_sizer->Add(new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString), 0, wxALL, 5); m_tooltip_window->SetSizer(tooltip_sizer); Bind(wxEVT_MENU, &DisasmCtrl::OnContextMenuEntryClicked, this, IDContextMenu_ToggleBreakpoint, IDContextMenu_Last); } void DisasmCtrl::Init() { SelectCodeRegion(mmuRange_TEXT_AREA.getBase()); } void DisasmCtrl::SelectCodeRegion(uint32 newAddress) { if (newAddress >= mmuRange_TEXT_AREA.getBase() && newAddress < mmuRange_TEXT_AREA.getEnd()) { currentCodeRegionStart = MEMORY_CODEAREA_ADDR; currentCodeRegionEnd = RPLLoader_GetMaxCodeOffset(); currentCodeRegionEnd = std::max(currentCodeRegionEnd, currentCodeRegionStart + 0x1000); } MMURange* mmuRange = memory_getMMURangeByAddress(newAddress); if (mmuRange) { currentCodeRegionStart = mmuRange->getBase(); currentCodeRegionEnd = mmuRange->getEnd(); } else { currentCodeRegionStart = 0; currentCodeRegionEnd = 0; } // update line tracking sint32 element_count = currentCodeRegionEnd - currentCodeRegionStart; // if (element_count <= 0x00010000) // element_count = 0x00010000; if (this->SetElementCount(element_count / 4)) { Scroll(0, 0); RefreshControl(); } } void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, RPLModule* rplModule) { wxPoint position = linePosition; bool hasPatch = debugger_hasPatch(virtualAddress); PPCDisassembledInstruction disasmInstr; const DebuggerBreakpoint* bp = debugger_getFirstBP(virtualAddress); while (bp) { if (bp->isExecuteBP() && bp->enabled) break; bp = bp->next; } uint32 opcode; if (bp) opcode = bp->originalOpcodeValue; else opcode = memory_readU32(virtualAddress); ppcAssembler_disassemble(virtualAddress, opcode, &disasmInstr); const bool is_active_bp = debuggerState.debugSession.isTrapped && debuggerState.debugSession.instructionPointer == virtualAddress; // write virtual address wxColour background_colour; if (is_active_bp && bp != nullptr) background_colour = theme_lineBreakpointAndCurrentInstruction; else if (is_active_bp) background_colour = theme_lineCurrentInstruction; else if (bp != nullptr) background_colour = bp->bpType == DEBUGGER_BP_T_NORMAL ? theme_lineBreakpointSet : theme_lineLoggingBreakpointSet; else if(virtualAddress == m_lastGotoTarget) background_colour = theme_lineLastGotoAddress; else background_colour = theme_background; DrawLineBackground(dc, position, background_colour); dc.SetTextForeground(theme_textForeground); dc.DrawText(wxString::Format("%08x", virtualAddress), position); position.x += OFFSET_ADDRESS; dc.SetTextForeground(theme_textForegroundMuted); if (rplModule) dc.DrawText(wxString::Format("+0x%-8x", virtualAddress - rplModule->regionMappingBase_text.GetMPTR()), position); else dc.DrawText("???", position); position.x += OFFSET_ADDRESS_RELATIVE; // draw arrow to clearly indicate instruction pointer if(is_active_bp) dc.DrawBitmap(*g_ipArrowBitmap, wxPoint(position.x - 24, position.y + 2), false); // handle data symbols auto debugSymbolDataType = DebugSymbolStorage::GetDataType(virtualAddress); if (debugSymbolDataType == DEBUG_SYMBOL_TYPE::FLOAT) { dc.SetTextForeground(hasPatch ? theme_patchedData : theme_typeData); dc.DrawText(fmt::format(".float"), position); position.x += OFFSET_DISASSEMBLY_OPERAND; dc.SetTextForeground(hasPatch ? theme_patchedData : theme_syntaxIMM); dc.DrawText(fmt::format("{}", memory_readFloat(virtualAddress)), position); return; } else if (debugSymbolDataType == DEBUG_SYMBOL_TYPE::U32) { dc.SetTextForeground(hasPatch ? theme_patchedData : theme_typeData); dc.DrawText(fmt::format(".uint"), position); position.x += OFFSET_DISASSEMBLY_OPERAND; dc.SetTextForeground(hasPatch ? theme_patchedData : theme_syntaxIMM); dc.DrawText(fmt::format("{}", memory_readU32(virtualAddress)), position); return; } sint32 start_width = position.x; dc.SetTextForeground(hasPatch ? theme_patchedOpCode : theme_opCode); std::string opName = ppcAssembler_getInstructionName(disasmInstr.ppcAsmCode); std::transform(opName.begin(), opName.end(), opName.begin(), tolower); dc.DrawText(wxString::Format("%-12s", opName.c_str()), position); position.x += OFFSET_DISASSEMBLY_OPERAND; bool isRLWINM = disasmInstr.ppcAsmCode == PPCASM_OP_RLWINM || disasmInstr.ppcAsmCode == PPCASM_OP_RLWINM_ || disasmInstr.ppcAsmCode == PPCASM_OP_CLRLWI || disasmInstr.ppcAsmCode == PPCASM_OP_CLRLWI_ || disasmInstr.ppcAsmCode == PPCASM_OP_CLRRWI || disasmInstr.ppcAsmCode == PPCASM_OP_CLRRWI_ || disasmInstr.ppcAsmCode == PPCASM_OP_EXTLWI || disasmInstr.ppcAsmCode == PPCASM_OP_EXTLWI_ || disasmInstr.ppcAsmCode == PPCASM_OP_EXTRWI || disasmInstr.ppcAsmCode == PPCASM_OP_EXTRWI_ || disasmInstr.ppcAsmCode == PPCASM_OP_SLWI || disasmInstr.ppcAsmCode == PPCASM_OP_SLWI_ || disasmInstr.ppcAsmCode == PPCASM_OP_SRWI || disasmInstr.ppcAsmCode == PPCASM_OP_SRWI_ || disasmInstr.ppcAsmCode == PPCASM_OP_ROTRWI || disasmInstr.ppcAsmCode == PPCASM_OP_ROTRWI_ || disasmInstr.ppcAsmCode == PPCASM_OP_ROTLWI || disasmInstr.ppcAsmCode == PPCASM_OP_ROTLWI_; bool forceDecDisplay = isRLWINM; if (disasmInstr.ppcAsmCode == PPCASM_OP_UKN) { // show raw bytes WriteText(dc, wxString::Format("%02x %02x %02x %02x", (opcode >> 24) & 0xFF, (opcode >> 16) & 0xFF, (opcode >> 8) & 0xFF, (opcode >> 0) & 0xFF), position, theme_syntaxPseudoOrUnknown); } bool is_first_operand = true; for (sint32 o = 0; o < PPCASM_OPERAND_COUNT; o++) { if (((disasmInstr.operandMask >> o) & 1) == 0) continue; if (!is_first_operand) WriteText(dc, ", ", position, theme_textForeground); is_first_operand = false; switch (disasmInstr.operand[o].type) { case PPCASM_OPERAND_TYPE_GPR: WriteText(dc, wxString::Format("r%d", disasmInstr.operand[o].registerIndex), position, theme_syntaxGPR); break; case PPCASM_OPERAND_TYPE_FPR: WriteText(dc, wxString::Format("f%d", disasmInstr.operand[o].registerIndex), position, theme_syntaxFPR); break; case PPCASM_OPERAND_TYPE_SPR: WriteText(dc, wxString::Format("spr%d", disasmInstr.operand[o].registerIndex), position, theme_syntaxSPR); break; case PPCASM_OPERAND_TYPE_CR: WriteText(dc, wxString::Format("cr%d", disasmInstr.operand[o].registerIndex), position, theme_syntaxCR); break; case PPCASM_OPERAND_TYPE_IMM: { wxString string; if (disasmInstr.operand[o].isSignedImm) { sint32 sImm = disasmInstr.operand[o].immS32; if (disasmInstr.operand[o].immWidth == 16 && (sImm & 0x8000)) sImm |= (sint32)0xFFFF0000; if ((sImm > -10 && sImm < 10) || forceDecDisplay) string = wxString::Format("%d", sImm); else { if (sImm < 0) string = wxString::Format("-0x%x", -sImm); else string = wxString::Format("0x%x", sImm); } } else { uint32 uImm = disasmInstr.operand[o].immS32; if ((uImm >= 0 && uImm < 10) || forceDecDisplay) string = wxString::Format("%u", uImm); else string = wxString::Format("0x%x", uImm); } WriteText(dc, string, position, theme_syntaxIMM); break; } case PPCASM_OPERAND_TYPE_PSQMODE: { if (disasmInstr.operand[o].immS32) WriteText(dc, "single", position, theme_syntaxIMM); else WriteText(dc, "paired", position, theme_syntaxIMM); break; } case PPCASM_OPERAND_TYPE_CIMM: { wxString string; // use symbol for function calls if available uint32 callDest = disasmInstr.operand[o].immU32; RPLStoredSymbol* storedSymbol = nullptr; if (disasmInstr.ppcAsmCode == PPCASM_OP_BL || disasmInstr.ppcAsmCode == PPCASM_OP_BLA) storedSymbol = rplSymbolStorage_getByAddress(callDest); if (storedSymbol) { // if symbol is within same module then don't display libname prefix RPLModule* rplModuleCurrent = RPLLoader_FindModuleByCodeAddr(virtualAddress); // cache this if (rplModuleCurrent && callDest >= rplModuleCurrent->regionMappingBase_text.GetMPTR() && callDest < (rplModuleCurrent->regionMappingBase_text.GetMPTR() + rplModuleCurrent->regionSize_text)) string = wxString((char*)storedSymbol->symbolName); else string = wxString::Format("%s.%s", (char*)storedSymbol->libName, (char*)storedSymbol->symbolName); // truncate name after 36 characters if (string.Length() >= 36) { string.Truncate(34); string.Append(".."); } } else { string = wxString::Format("0x%08x", disasmInstr.operand[o].immU32); } WriteText(dc, string, position, theme_syntaxCallIMM); if (disasmInstr.ppcAsmCode != PPCASM_OP_BL) { wxString x = wxString(" "); if (callDest <= virtualAddress) x.Append(wxUniChar(0x2191)); // arrow up else x.Append(wxUniChar(0x2193)); // arrow down WriteText(dc, x, position, theme_textForeground); } break; } case PPCASM_OPERAND_TYPE_MEM: { // offset wxString string; if (disasmInstr.operand[o].isSignedImm && disasmInstr.operand[o].immS32 >= 0) string = wxString::Format("+0x%x", disasmInstr.operand[o].immS32); else string = wxString::Format("-0x%x", -disasmInstr.operand[o].immS32); WriteText(dc, string, position, theme_syntaxIMMOffset); WriteText(dc, "(", position, theme_textForeground); // register WriteText(dc, wxString::Format("r%d", disasmInstr.operand[o].registerIndex), position, theme_syntaxGPR); WriteText(dc, ")", position, theme_textForeground); break; } default: // TODO WriteText(dc, "", position, wxColour(0xFF444444)); } } position.x = start_width + OFFSET_DISASSEMBLY; const auto comment = static_cast(rplDebugSymbol_getForAddress(virtualAddress)); if (comment && comment->type == RplDebugSymbolComment) WriteText(dc, comment->comment, position, theme_textForeground); else if (isRLWINM) { sint32 rS, rA, SH, MB, ME; rS = (opcode >> 21) & 0x1f; rA = (opcode >> 16) & 0x1f; SH = (opcode >> 11) & 0x1f; MB = (opcode >> 6) & 0x1f; ME = (opcode >> 1) & 0x1f; uint32 mask = ppcAssembler_generateMaskRLW(MB, ME); wxString string; if (SH == 0) string = wxString::Format("r%d=r%d&0x%x", rA, rS, mask); else if ((0xFFFFFFFF << (uint32)disasmInstr.operand[2].immS32) == mask) string = wxString::Format("r%d=r%d<<%d", rA, rS, SH); else if ((0xFFFFFFFF >> (32 - SH) == mask)) string = wxString::Format("r%d=r%d>>%d", rA, rS, 32 - SH); else string = wxString::Format("r%d=(r%d<<<%d)&0x%x", rA, rS, SH, mask); WriteText(dc, string, position, theme_textForegroundMuted); } else if (disasmInstr.ppcAsmCode == PPCASM_OP_SUBF || disasmInstr.ppcAsmCode == PPCASM_OP_SUBF_) { sint32 rD, rA, rB; rD = (opcode >> 21) & 0x1f; rA = (opcode >> 16) & 0x1f; rB = (opcode >> 11) & 0x1f; wxString string; string = wxString::Format("r%d=r%d-r%d", rD, rB, rA); WriteText(dc, string, position, theme_textForegroundMuted); } } void DisasmCtrl::DrawLabelName(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, RPLStoredSymbol* storedSymbol) { wxString symbol_string = wxString::Format("%s:", (char*)storedSymbol->symbolName); if (symbol_string.Length() > MAX_SYMBOL_LEN) { symbol_string.Truncate(MAX_SYMBOL_LEN - 3); symbol_string.Append("..:"); } wxPoint tmpPos(linePosition); WriteText(dc, symbol_string, tmpPos, theme_syntaxSymbol); } void DisasmCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) { wxPoint position(0, 0); RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(GetViewBaseAddress()); if (currentCodeRegionStart == currentCodeRegionEnd) return; sint32 viewFirstLine = GetViewStart().y; sint32 lineOffset = start - viewFirstLine; cemu_assert_debug(lineOffset >= 0); sint32 instructionIndex = 0; sint32 numLinesToUpdate = lineOffset + count; numLinesToUpdate = std::min(numLinesToUpdate, (sint32)m_elements_visible); if(m_lineToAddress.size() != m_elements_visible) m_lineToAddress.resize(m_elements_visible); sint32 lineIndex = 0; while(lineIndex < numLinesToUpdate) { const uint32 virtualAddress = GetViewBaseAddress() + instructionIndex * 4; instructionIndex++; if (virtualAddress < currentCodeRegionStart || virtualAddress >= currentCodeRegionEnd) { NextLine(position, &start_position); m_lineToAddress[lineIndex] = std::nullopt; lineIndex++; continue; } // check if valid memory address if (!memory_isAddressRangeAccessible(virtualAddress, 4)) { NextLine(position, &start_position); m_lineToAddress[lineIndex] = std::nullopt; lineIndex++; continue; } // draw symbol as label RPLStoredSymbol* storedSymbol = rplSymbolStorage_getByAddress(virtualAddress); if (storedSymbol) { if(lineIndex >= lineOffset) DrawLabelName(dc, position, virtualAddress, storedSymbol); m_lineToAddress[lineIndex] = virtualAddress; lineIndex++; if (lineIndex >= numLinesToUpdate) break; NextLine(position, &start_position); } m_lineToAddress[lineIndex] = virtualAddress; if (lineIndex >= lineOffset) DrawDisassemblyLine(dc, position, virtualAddress, current_rpl_module); NextLine(position, &start_position); lineIndex++; } // draw vertical separator lines: offset | disassembly | comment dc.SetPen(*wxLIGHT_GREY_PEN); wxPoint line_from = start_position; line_from.x = OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE - 5; wxPoint line_to = line_from; line_to.y += (count + 1) * m_line_height; dc.DrawLine(line_from, line_to); line_from.x += OFFSET_DISASSEMBLY; line_to.x += OFFSET_DISASSEMBLY; dc.DrawLine(line_from, line_to); } void DisasmCtrl::OnMouseMove(const wxPoint& start_position, uint32 line) { /*m_mouse_line = line; if (m_mouse_line_drawn != -1) RefreshLine(m_mouse_line_drawn); if (m_mouse_line != -1) RefreshLine(m_mouse_line);*/ wxPoint position = start_position; // address if (position.x <= OFFSET_ADDRESS) { return; } position.x -= OFFSET_ADDRESS; // relative offset if (position.x <= OFFSET_ADDRESS_RELATIVE) { return; } position.x -= OFFSET_ADDRESS_RELATIVE; // disassembly code if (position.x <= OFFSET_DISASSEMBLY) { if(m_mouse_down) { } if (position.x <= OFFSET_DISASSEMBLY_OPERAND) { return; } position.x -= OFFSET_DISASSEMBLY_OPERAND; } } void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position) { auto optVirtualAddress = LinePixelPosToAddress(position.y); switch (key_code) { case WXK_F9: { if (optVirtualAddress) { debugger_toggleExecuteBreakpoint(*optVirtualAddress); wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); wxPostEvent(this->m_parent, evt); } return; } case 'G': { if(IsKeyDown(WXK_CONTROL)) { GoToAddressDialog(); return; } } } // debugger currently in break state if (debuggerState.debugSession.isTrapped) { switch (key_code) { case WXK_F5: { debuggerState.debugSession.run = true; return; } case WXK_F10: { debuggerState.debugSession.stepOver = true; return; } case WXK_F11: { debuggerState.debugSession.stepInto = true; } } } else { switch (key_code) { case WXK_F5: { debuggerState.debugSession.shouldBreak = true; } } } } void DisasmCtrl::OnMouseDClick(const wxPoint& position, uint32 line) { wxPoint pos = position; auto optVirtualAddress = LinePixelPosToAddress(position.y - GetViewStart().y * m_line_height); if (!optVirtualAddress) return; MPTR virtualAddress = *optVirtualAddress; // address if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE) { // address + address relative debugger_toggleExecuteBreakpoint(virtualAddress); RefreshLine(line); wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); wxPostEvent(this->m_parent, evt); return; } else if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + OFFSET_DISASSEMBLY) { // double-clicked on disassembly (operation and operand data) wxString currentInstruction = wxEmptyString; wxTextEntryDialog set_value_dialog(this, _("Enter a new instruction."), wxString::Format(_("Overwrite instruction at address %08x"), virtualAddress), currentInstruction); if (set_value_dialog.ShowModal() == wxID_OK) { PPCAssemblerInOut ctx = { 0 }; ctx.virtualAddress = virtualAddress; if (ppcAssembler_assembleSingleInstruction(set_value_dialog.GetValue().c_str(), &ctx)) { debugger_createPatch(virtualAddress, { ctx.outputData.data(), ctx.outputData.size() }); RefreshLine(line); } } return; } else { // comment const auto comment = static_cast(rplDebugSymbol_getForAddress(virtualAddress)); wxString old_comment = wxEmptyString; if (comment && comment->type == RplDebugSymbolComment) old_comment = comment->comment; wxTextEntryDialog set_value_dialog(this, _("Enter a new comment."), wxString::Format(_("Create comment at address %08x"), virtualAddress), old_comment); if (set_value_dialog.ShowModal() == wxID_OK) { rplDebugSymbol_createComment(virtualAddress, set_value_dialog.GetValue().wc_str()); RefreshLine(line); } return; } } void DisasmCtrl::CopyToClipboard(std::string text) { #if BOOST_OS_WINDOWS if (OpenClipboard(nullptr)) { EmptyClipboard(); const HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, text.size() + 1); if (hGlobal) { memcpy(GlobalLock(hGlobal), text.c_str(), text.size() + 1); GlobalUnlock(hGlobal); SetClipboardData(CF_TEXT, hGlobal); } CloseClipboard(); } #endif } static uint32 GetUnrelocatedAddress(MPTR address) { RPLModule* rplModule = RPLLoader_FindModuleByCodeAddr(address); if (!rplModule) return 0; if (address >= rplModule->regionMappingBase_text.GetMPTR() && address < (rplModule->regionMappingBase_text.GetMPTR() + rplModule->regionSize_text)) return 0x02000000 + (address - rplModule->regionMappingBase_text.GetMPTR()); return 0; } void DisasmCtrl::OnContextMenu(const wxPoint& position, uint32 line) { auto optVirtualAddress = LinePixelPosToAddress(position.y - GetViewStart().y * m_line_height); if (!optVirtualAddress) return; MPTR virtualAddress = *optVirtualAddress; m_contextMenuAddress = virtualAddress; // show dialog wxMenu menu; menu.Append(IDContextMenu_ToggleBreakpoint, _("Toggle breakpoint")); menu.Append(IDContextMenu_ToggleLoggingBreakpoint, _("Toggle logging point")); if(debugger_hasPatch(virtualAddress)) menu.Append(IDContextMenu_RestoreOriginalInstructions, _("Restore original instructions")); menu.AppendSeparator(); menu.Append(IDContextMenu_CopyAddress, _("Copy address")); uint32 unrelocatedAddress = GetUnrelocatedAddress(virtualAddress); if (unrelocatedAddress && unrelocatedAddress != virtualAddress) menu.Append(IDContextMenu_CopyUnrelocatedAddress, _("Copy virtual address (for IDA/Ghidra)")); PopupMenu(&menu); } void DisasmCtrl::OnContextMenuEntryClicked(wxCommandEvent& event) { switch(event.GetId()) { case IDContextMenu_ToggleBreakpoint: { debugger_toggleExecuteBreakpoint(m_contextMenuAddress); wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); wxPostEvent(this->m_parent, evt); break; } case IDContextMenu_ToggleLoggingBreakpoint: { debugger_toggleLoggingBreakpoint(m_contextMenuAddress); wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); wxPostEvent(this->m_parent, evt); break; } case IDContextMenu_RestoreOriginalInstructions: { debugger_removePatch(m_contextMenuAddress); wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); // This also refreshes the disassembly view wxPostEvent(this->m_parent, evt); break; } case IDContextMenu_CopyAddress: { CopyToClipboard(fmt::format("{:#10x}", m_contextMenuAddress)); break; } case IDContextMenu_CopyUnrelocatedAddress: { uint32 unrelocatedAddress = GetUnrelocatedAddress(m_contextMenuAddress); CopyToClipboard(fmt::format("{:#10x}", unrelocatedAddress)); break; } default: UNREACHABLE; } } bool DisasmCtrl::OnShowTooltip(const wxPoint& position, uint32 line) { return false; } void DisasmCtrl::ScrollWindow(int dx, int dy, const wxRect* prect) { // scroll and repaint everything RefreshControl(nullptr); TextList::ScrollWindow(dx, dy, nullptr); } wxSize DisasmCtrl::DoGetBestSize() const { return TextList::DoGetBestSize(); } void DisasmCtrl::OnUpdateView() { RefreshControl(); } uint32 DisasmCtrl::GetViewBaseAddress() const { if (GetViewStart().y < 0) return MPTR_NULL; return currentCodeRegionStart + GetViewStart().y * 4; } std::optional DisasmCtrl::LinePixelPosToAddress(sint32 posY) { if (posY < 0) return std::nullopt; sint32 lineIndex = posY / m_line_height; if (lineIndex >= m_lineToAddress.size()) return std::nullopt; return m_lineToAddress[lineIndex]; } uint32 DisasmCtrl::AddressToScrollPos(uint32 offset) const { return (offset - currentCodeRegionStart) / 4; } void DisasmCtrl::CenterOffset(uint32 offset) { if (offset < currentCodeRegionStart || offset >= currentCodeRegionEnd) SelectCodeRegion(offset); const sint32 line = AddressToScrollPos(offset); if (line < 0 || line >= (sint32)m_element_count) return; if (m_active_line != -1) RefreshLine(m_active_line); DoScroll(0, std::max(0, line - (sint32)(m_elements_visible / 2))); m_active_line = line; RefreshLine(m_active_line); } void DisasmCtrl::GoToAddressDialog() { wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString); if (goto_dialog.ShowModal() == wxID_OK) { ExpressionParser parser; auto value = goto_dialog.GetValue().ToStdString(); std::transform(value.begin(), value.end(), value.begin(), tolower); // trim any leading spaces while(!value.empty() && value[0] == ' ') value.erase(value.begin()); debugger_addParserSymbols(parser); // try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234) if (parser.IsConstantExpression("0x"+value)) { const auto result = (uint32)parser.Evaluate("0x"+value); m_lastGotoTarget = result; CenterOffset(result); wxCommandEvent evt(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS); evt.SetExtraLong(static_cast(result)); wxPostEvent(GetParent(), evt); } else if (parser.IsConstantExpression(value)) { const auto result = (uint32)parser.Evaluate(value); m_lastGotoTarget = result; CenterOffset(result); wxCommandEvent evt(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS); evt.SetExtraLong(static_cast(result)); wxPostEvent(GetParent(), evt); } else { try { // if not a constant expression (i.e. relying on unknown variables), then evaluating will throw an exception with a detailed error message const auto _ = (uint32)parser.Evaluate(value); } catch (const std::exception& ex) { wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); } } } } ================================================ FILE: src/gui/wxgui/debugger/DisasmCtrl.h ================================================ #pragma once #include "wxgui/components/TextList.h" wxDECLARE_EVENT(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, wxCommandEvent); // Notify parent that goto address operation completed. Event contains the address that was jumped to. class DisasmCtrl : public TextList { enum { IDContextMenu_ToggleBreakpoint = wxID_HIGHEST + 1, IDContextMenu_ToggleLoggingBreakpoint, IDContextMenu_RestoreOriginalInstructions, IDContextMenu_CopyAddress, IDContextMenu_CopyUnrelocatedAddress, IDContextMenu_Last }; public: DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style); void Init(); wxSize DoGetBestSize() const override; void OnUpdateView(); uint32 GetViewBaseAddress() const; std::optional LinePixelPosToAddress(sint32 posY); uint32 AddressToScrollPos(uint32 offset) const; void CenterOffset(uint32 offset); void GoToAddressDialog(); protected: void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override; void OnMouseMove(const wxPoint& position, uint32 line) override; void OnKeyPressed(sint32 key_code, const wxPoint& position) override; void OnMouseDClick(const wxPoint& position, uint32 line) override; void OnContextMenu(const wxPoint& position, uint32 line) override; void OnContextMenuEntryClicked(wxCommandEvent& event); bool OnShowTooltip(const wxPoint& position, uint32 line) override; void ScrollWindow(int dx, int dy, const wxRect* prect) override; void DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, struct RPLModule* rplModule); void DrawLabelName(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, struct RPLStoredSymbol* storedSymbol); void SelectCodeRegion(uint32 newAddress); private: void CopyToClipboard(std::string text); sint32 m_mouse_line, m_mouse_line_drawn; sint32 m_active_line; uint32 m_lastGotoTarget{}; uint32 m_contextMenuAddress{}; // code region info uint32 currentCodeRegionStart; uint32 currentCodeRegionEnd; // line to address mapping std::vector> m_lineToAddress; }; ================================================ FILE: src/gui/wxgui/debugger/DumpCtrl.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/debugger/DumpCtrl.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "util/helpers/helpers.h" #include "Cemu/ExpressionParser/ExpressionParser.h" #define OFFSET_ADDRESS (60) #define OFFSET_ADDRESS_RELATIVE (90) #define OFFSET_MEMORY (450) DumpCtrl::DumpCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style) : TextList(parent, id, pos, size, style) { MMURange* range = memory_getMMURangeByAddress(0x10000000); if (range) { m_memoryRegion.baseAddress = range->getBase(); m_memoryRegion.size = range->getSize(); Init(); } else { m_memoryRegion.baseAddress = 0x10000000; m_memoryRegion.size = 0x1000; Init(); } } void DumpCtrl::Init() { uint32 element_count = m_memoryRegion.size; this->SetElementCount(element_count / 0x10); Scroll(0, 0); RefreshControl(); } void DumpCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) { wxPoint position = start_position; uint32 endAddr = m_memoryRegion.baseAddress + m_memoryRegion.size; RPLModule* currentCodeRPL = RPLLoader_FindModuleByCodeAddr(LineToOffset(start)); RPLModule* currentDataRPL = RPLLoader_FindModuleByDataAddr(LineToOffset(start)); for (sint32 i = 0; i <= count; i++) { const uint32 virtual_address = LineToOffset(start + i); dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); dc.DrawText(wxString::Format("%08x", virtual_address), position); position.x += OFFSET_ADDRESS; dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); if (currentCodeRPL) { dc.DrawText(wxString::Format("+0x%-8x", virtual_address - currentCodeRPL->regionMappingBase_text.GetMPTR()), position); } else if (currentDataRPL) { dc.DrawText(wxString::Format("+0x%-8x", virtual_address - currentDataRPL->regionMappingBase_data), position); } else { dc.DrawText("???", position); } position.x += OFFSET_ADDRESS_RELATIVE; sint32 start_width = position.x; if (!memory_isAddressRangeAccessible(virtual_address, 0x10)) { for (sint32 f=0; f<0x10; f++) { wxPoint p(position); WriteText(dc, wxString::Format("?? "), p); position.x += (m_char_width * 3); } } else { std::array data; memory_readBytes(virtual_address, data); for (auto b : data) { wxPoint p(position); WriteText(dc, wxString::Format("%02x ", b), p); position.x += (m_char_width * 3); } position.x = start_width = OFFSET_MEMORY; dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); for (auto b : data) { if (isprint(b)) dc.DrawText(wxString::Format("%c ", b), position); else dc.DrawText(".", position); position.x += m_char_width; } } // display goto indicator if (m_lastGotoOffset >= virtual_address && m_lastGotoOffset < (virtual_address + 16)) { sint32 indicatorX = start_position.x + OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + (m_lastGotoOffset - virtual_address) * m_char_width * 3; wxPoint line1(start_position.x + indicatorX - 2, position.y); wxPoint line2(line1.x, line1.y + m_line_height); dc.SetPen(*wxRED_PEN); dc.DrawLine(line1, line2); dc.DrawLine(line1, wxPoint(line1.x + 3, line1.y)); dc.DrawLine(line2, wxPoint(line2.x + 3, line2.y)); } NextLine(position, &start_position); } // draw vertical separator lines for 4 byte blocks sint32 cursorOffsetHexBytes = start_position.x + OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE; wxPoint line_from( cursorOffsetHexBytes + m_char_width * (3 * 4 - 1) + m_char_width / 2, start_position.y ); wxPoint line_to(line_from.x, line_from.y + m_line_height * (count + 1)); dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_INACTIVECAPTIONTEXT)); for (sint32 i = 0; i < 3; i++) { dc.DrawLine(line_from, line_to); line_from.x += m_char_width * (3 * 4); line_to.x += m_char_width * (3 * 4); } } void DumpCtrl::OnMouseMove(const wxPoint& start_position, uint32 line) { wxPoint position = start_position; // address if (position.x <= OFFSET_ADDRESS) { return; } position.x -= OFFSET_ADDRESS; // relative offset if (position.x <= OFFSET_ADDRESS_RELATIVE) { return; } position.x -= OFFSET_ADDRESS_RELATIVE; // byte code if (position.x <= OFFSET_MEMORY) { } // string view position.x -= OFFSET_MEMORY; } void DumpCtrl::OnMouseDClick(const wxPoint& position, uint32 line) { wxPoint pos = position; if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE) return; pos.x -= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE; if(pos.x <= OFFSET_MEMORY) { const uint32 byte_index = (pos.x / m_char_width) / 3; const uint32 offset = LineToOffset(line) + byte_index; if (!memory_isAddressRangeAccessible(offset, 1)) return; const uint8 value = memory_readU8(offset); wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set byte at address %08x"), offset), wxString::Format("%02x", value)); if (set_value_dialog.ShowModal() == wxID_OK) { const uint8 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16); memory_writeU8(offset, new_value); wxRect update_rect(0, line * m_line_height, GetSize().x, m_line_height); RefreshControl(&update_rect); } return; } } void DumpCtrl::GoToAddressDialog() { wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString); if (goto_dialog.ShowModal() == wxID_OK) { ExpressionParser parser; auto value = goto_dialog.GetValue().ToStdString(); std::transform(value.begin(), value.end(), value.begin(), tolower); debugger_addParserSymbols(parser); // try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234) if (parser.IsConstantExpression("0x"+value)) { const auto result = (uint32)parser.Evaluate("0x"+value); m_lastGotoOffset = result; CenterOffset(result); } else if (parser.IsConstantExpression(value)) { const auto result = (uint32)parser.Evaluate(value); m_lastGotoOffset = result; CenterOffset(result); } else { try { const auto _ = (uint32)parser.Evaluate(value); } catch (const std::exception& ex) { wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); } } } } void DumpCtrl::CenterOffset(uint32 offset) { // check if the offset is valid if (!memory_isAddressRangeAccessible(offset, 1)) return; // set region and line MMURange* range = memory_getMMURangeByAddress(offset); if (m_memoryRegion.baseAddress != range->getBase() || m_memoryRegion.size != range->getSize()) { m_memoryRegion.baseAddress = range->getBase(); m_memoryRegion.size = range->getSize(); Init(); } const sint32 line = OffsetToLine(offset); if (line < 0 || line >= (sint32)m_element_count) return; DoScroll(0, std::max(0, line - ((sint32)m_elements_visible / 2))); RefreshControl(); //RefreshLine(line); debug_printf("scroll to %x\n", debuggerState.debugSession.instructionPointer); } uint32 DumpCtrl::LineToOffset(uint32 line) { return m_memoryRegion.baseAddress + line * 0x10; } uint32 DumpCtrl::OffsetToLine(uint32 offset) { return (offset - m_memoryRegion.baseAddress) / 0x10; } void DumpCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position) { switch (key_code) { case 'G': { if (IsKeyDown(WXK_CONTROL)) { GoToAddressDialog(); return; } } } } wxSize DumpCtrl::DoGetBestSize() const { return TextList::DoGetBestSize(); } ================================================ FILE: src/gui/wxgui/debugger/DumpCtrl.h ================================================ #pragma once #include "wxgui/components/TextList.h" class DumpCtrl : public TextList { public: DumpCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style); void Init(); wxSize DoGetBestSize() const override; protected: void GoToAddressDialog(); void CenterOffset(uint32 offset); uint32 LineToOffset(uint32 line); uint32 OffsetToLine(uint32 offset); void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override; void OnMouseMove(const wxPoint& position, uint32 line) override; void OnMouseDClick(const wxPoint& position, uint32 line) override; void OnKeyPressed(sint32 key_code, const wxPoint& position) override; private: struct { uint32 baseAddress; uint32 size; }m_memoryRegion; uint32 m_lastGotoOffset{0}; }; ================================================ FILE: src/gui/wxgui/debugger/DumpWindow.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/debugger/DumpWindow.h" #include "wxgui/debugger/DebuggerWindow2.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "wxgui/debugger/DumpCtrl.h" enum { // REGISTER REGISTER_ADDRESS_R0 = wxID_HIGHEST + 8200, REGISTER_LABEL_R0 = REGISTER_ADDRESS_R0 + 32, REGISTER_LABEL_FPR0_0 = REGISTER_LABEL_R0 + 32, REGISTER_LABEL_FPR1_0 = REGISTER_LABEL_R0 + 32, }; DumpWindow::DumpWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size) : wxFrame(&parent, wxID_ANY, _("Memory Dump"), wxDefaultPosition, wxSize(600, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT) { wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); m_dump_ctrl = new DumpCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle); main_sizer->Add(m_dump_ctrl, 1, wxEXPAND); this->SetSizer(main_sizer); this->wxWindowBase::Layout(); this->Centre(wxBOTH); if (parent.GetConfig().data().pin_to_main) OnMainMove(main_position, main_size); } void DumpWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size) { wxSize size(600, 250); this->SetSize(size); wxPoint position = main_position; position.y += main_size.GetHeight(); this->SetPosition(position); } void DumpWindow::OnGameLoaded() { m_dump_ctrl->Init(); } ================================================ FILE: src/gui/wxgui/debugger/DumpWindow.h ================================================ #pragma once #include "wxgui/debugger/DumpCtrl.h" class DebuggerWindow2; class DumpWindow : public wxFrame { public: DumpWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size); void OnMainMove(const wxPoint& position, const wxSize& main_size); void OnGameLoaded(); private: wxScrolledWindow* m_scrolled_window; DumpCtrl* m_dump_ctrl; }; ================================================ FILE: src/gui/wxgui/debugger/ModuleWindow.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/debugger/ModuleWindow.h" #include #include "wxgui/debugger/DebuggerWindow2.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cafe/GraphicPack/GraphicPack2.h" enum ItemColumns { ColumnName = 0, ColumnAddress, ColumnSize, }; ModuleWindow::ModuleWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size) : wxFrame(&parent, wxID_ANY, _("Modules"), wxDefaultPosition, wxSize(420, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT) { this->SetSizeHints(wxDefaultSize, wxDefaultSize); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); m_modules = new wxListView(this, wxID_ANY); wxListItem col0; col0.SetId(ColumnName); col0.SetText(_("Name")); col0.SetWidth(125); m_modules->InsertColumn(ColumnName, col0); wxListItem col1; col1.SetId(ColumnAddress); col1.SetText(_("Address")); col1.SetWidth(75); col1.SetAlign(wxLIST_FORMAT_RIGHT); m_modules->InsertColumn(ColumnAddress, col1); wxListItem col2; col2.SetId(ColumnSize); col2.SetText(_("Size")); col2.SetWidth(75); col2.SetAlign(wxLIST_FORMAT_RIGHT); m_modules->InsertColumn(ColumnSize, col2); main_sizer->Add(m_modules, 1, wxEXPAND); this->SetSizer(main_sizer); this->wxWindowBase::Layout(); this->Centre(wxBOTH); if (parent.GetConfig().data().pin_to_main) OnMainMove(main_position, main_size); m_modules->Bind(wxEVT_LEFT_DCLICK, &ModuleWindow::OnLeftDClick, this); OnGameLoaded(); } void ModuleWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size) { wxSize size(420, 250); this->SetSize(size); wxPoint position = main_position; position.x -= 420; position.y += main_size.y; this->SetPosition(position); } void ModuleWindow::OnGameLoaded() { Freeze(); m_modules->DeleteAllItems(); const auto module_count = RPLLoader_GetModuleCount(); const auto module_list = RPLLoader_GetModuleList(); for (int i = 0; i < module_count; i++) { const auto module = module_list[i]; if (module) { wxListItem item; item.SetId(i); item.SetText(module->moduleName2); const auto index = m_modules->InsertItem(item); m_modules->SetItem(index, ColumnAddress, wxString::Format("%08x", module->regionMappingBase_text.GetMPTR())); m_modules->SetItem(index, ColumnSize, wxString::Format("%x", module->regionSize_text)); } } sint32 patch_count = 0; for (auto& gfx_pack : GraphicPack2::GetGraphicPacks()) { for (auto& patch_group : gfx_pack->GetPatchGroups()) { if (patch_group->isApplied()) { wxListItem item; item.SetId(module_count + patch_count); item.SetText(std::string(patch_group->getName())); const auto index = m_modules->InsertItem(item); m_modules->SetItem(index, ColumnAddress, wxString::Format("%08x", patch_group->getCodeCaveBase())); m_modules->SetItem(index, ColumnSize, wxString::Format("%x", patch_group->getCodeCaveSize())); patch_count++; } } } Thaw(); } void ModuleWindow::OnLeftDClick(wxMouseEvent& event) { long selected = m_modules->GetFirstSelected(); if (selected == wxNOT_FOUND) return; const auto text = m_modules->GetItemText(selected, ColumnAddress); const auto address = std::stoul(text.ToStdString(), nullptr, 16); if (address == 0) return; debuggerState.debugSession.instructionPointer = address; g_debuggerDispatcher.MoveIP(); } ================================================ FILE: src/gui/wxgui/debugger/ModuleWindow.h ================================================ #pragma once #include class DebuggerWindow2; class wxListView; class ModuleWindow : public wxFrame { public: ModuleWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size); void OnMainMove(const wxPoint& position, const wxSize& main_size); void OnGameLoaded(); private: void OnLeftDClick(wxMouseEvent& event); wxListView* m_modules; }; ================================================ FILE: src/gui/wxgui/debugger/RegisterWindow.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/debugger/RegisterWindow.h" #include #include "wxgui/debugger/DebuggerWindow2.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_structs.h" #include "Cafe/HW/Espresso/EspressoISA.h" enum { // REGISTER kRegisterValueR0 = wxID_HIGHEST + 8400, kRegisterLabelR0 = kRegisterValueR0 + 32, kRegisterLabelLR = kRegisterLabelR0 + 32, kRegisterValueLR = kRegisterLabelLR + 1, kRegisterValueFPR0_0 = kRegisterValueLR + 32, kRegisterValueFPR1_0 = kRegisterValueFPR0_0 + 32, kRegisterValueCR0 = kRegisterValueFPR1_0 + 32, kContextMenuZero, kContextMenuInc, kContextMenuDec, kContextMenuCopy, kContextMenuGotoDisasm, kContextMenuGotoDump, }; RegisterWindow::RegisterWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size) : wxFrame(&parent, wxID_ANY, _("Registers"), wxDefaultPosition, wxSize(400, 975), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT), m_prev_snapshot({}), m_show_double_values(true), m_context_ctrl(nullptr) { SetSizeHints(wxDefaultSize, wxDefaultSize); SetMaxSize({ 400, 975 }); this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); auto scrolled_win = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL); wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); auto gpr_sizer = new wxFlexGridSizer(0, 3, 0, 0); // GPRs for(sint32 i = 0; i < 32; ++i) { gpr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("R%d", i)), 0, wxLEFT, 5); auto value = new wxTextCtrl(scrolled_win, kRegisterValueR0 + i, wxString::Format("%08x", 0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2); value->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); value->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this); //value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this); gpr_sizer->Add(value, 0, wxLEFT|wxRIGHT, 5); auto label = new wxTextCtrl(scrolled_win, kRegisterLabelR0 + i, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2); label->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); label->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); label->SetMinSize(wxSize(500, -1)); gpr_sizer->Add(label, 0, wxEXPAND); } { // LR gpr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("LR")), 0, wxLEFT, 5); auto value = new wxTextCtrl(scrolled_win, kRegisterValueLR, wxString::Format("%08x", 0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2); value->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); value->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this); //value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this); gpr_sizer->Add(value, 0, wxLEFT | wxRIGHT, 5); auto label = new wxTextCtrl(scrolled_win, kRegisterLabelLR, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2); label->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); label->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); label->SetMinSize(wxSize(500, -1)); gpr_sizer->Add(label, 0, wxEXPAND); } sizer->Add(gpr_sizer, 1, wxEXPAND); auto button = new wxButton(scrolled_win, wxID_ANY, _("FP view mode")); button->Bind(wxEVT_BUTTON, &RegisterWindow::OnFPViewModePress, this); sizer->Add(button, 0, wxALL, 5); auto fp_sizer = new wxFlexGridSizer(0, 3, 0, 0); for (sint32 i = 0; i < 32; ++i) { fp_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("FP%d", i)), 0, wxLEFT, 5); auto value0 = new wxTextCtrl(scrolled_win, kRegisterValueFPR0_0 + i, wxString::Format("%lf", 0.0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2); value0->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); value0->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); value0->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this); fp_sizer->Add(value0, 0, wxLEFT | wxRIGHT, 5); auto value1 = new wxTextCtrl(scrolled_win, kRegisterValueFPR1_0 + i, wxString::Format("%lf", 0.0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2); value1->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); value1->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); value1->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this); fp_sizer->Add(value1, 0, wxLEFT | wxRIGHT, 5); } sizer->Add(fp_sizer, 0, wxEXPAND); auto cr_sizer = new wxFlexGridSizer(0, 2, 0, 0); // CRs for (sint32 i = 0; i < 8; ++i) { cr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("CR%d", i)), 0, wxLEFT, 5); auto value = new wxTextCtrl(scrolled_win, kRegisterValueCR0 + i, "-", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER | wxTE_RICH2); value->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this); cr_sizer->Add(value, 0, wxRIGHT, 5); } sizer->Add(cr_sizer, 0, wxEXPAND); scrolled_win->SetSizerAndFit(sizer); scrolled_win->SetScrollRate(-1, 10); main_sizer->Add(scrolled_win, 1, wxEXPAND); SetSizer(main_sizer); Layout(); if (parent.GetConfig().data().pin_to_main) OnMainMove(main_position, main_size); //Bind(wxEVT_COMMAND_MENU_SELECTED, &RegisterWindow::OnValueContextMenuSelected, this, kContextMenuZero, kContextMenuGotoDump); } void RegisterWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size) { wxSize size(400, 255 + main_size.y + 250); this->SetSize(size); wxPoint position = main_position; position.x += main_size.x; position.y -= 255; this->SetPosition(position); } void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, uint32 registerValue, bool hasChanged) { //const auto value = dynamic_cast(GetWindowChild(kRegisterValueR0 + i)); //wxASSERT(value); //const bool has_changed = register_value != m_prev_snapshot.gpr[i]; if (hasChanged) value->SetForegroundColour(m_changed_color); else if (value->GetForegroundColour() != wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)) value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); value->ChangeValue(wxString::Format("%08x", registerValue)); //const auto label = dynamic_cast(GetWindowChild(kRegisterLabelR0 + i)); //wxASSERT(label); label->SetForegroundColour(hasChanged ? m_changed_color : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); // check if address is a string if (registerValue >= MEMORY_DATA_AREA_ADDR && registerValue < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE)) { bool is_valid_string = true; std::stringstream buffer; uint32 string_offset = registerValue; while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE)) { const uint8 c = memory_readU8(string_offset++); if (isprint(c)) buffer << c; else if (c == '\0') { buffer << c; break; } else { is_valid_string = false; break; } } if (is_valid_string && buffer.tellp() > 1) { label->ChangeValue(wxString::Format("\"%s\"", buffer.str().c_str())); return; } // check for widestring is_valid_string = true; string_offset = registerValue; std::wstringstream wbuffer; while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE - 1)) { const uint16 c = memory_readU16(string_offset); string_offset += 2; if (iswprint(c)) wbuffer << c; else if (c == L'\0') { wbuffer << c; break; } else { is_valid_string = false; break; } } if (is_valid_string && buffer.tellp() > 1) { label->ChangeValue(wxString::Format(L"ws\"%s\"", wbuffer.str().c_str())); return; } } // check if address is a code offset RPLModule* code_module = RPLLoader_FindModuleByCodeAddr(registerValue); if (code_module) { label->ChangeValue(wxString::Format("<%s> + %x", code_module->moduleName2.c_str(), registerValue - code_module->regionMappingBase_text.GetMPTR())); return; } label->ChangeValue(wxEmptyString); } void RegisterWindow::OnUpdateView() { // m_register_ctrl->RefreshControl(); for (int i = 0; i < 32; ++i) { const uint32 registerValue = debuggerState.debugSession.ppcSnapshot.gpr[i]; const bool hasChanged = registerValue != m_prev_snapshot.gpr[i]; const auto value = dynamic_cast(FindWindow(kRegisterValueR0 + i)); wxASSERT(value); const auto label = dynamic_cast(FindWindow(kRegisterLabelR0 + i)); wxASSERT(label); UpdateIntegerRegister(label, value, registerValue, hasChanged); } // update LR { const uint32 registerValue = debuggerState.debugSession.ppcSnapshot.spr_lr; const bool hasChanged = registerValue != m_prev_snapshot.spr_lr; const auto value = dynamic_cast(FindWindow(kRegisterValueLR)); wxASSERT(value); const auto label = dynamic_cast(FindWindow(kRegisterLabelLR)); wxASSERT(label); UpdateIntegerRegister(label, value, registerValue, hasChanged); } for (int i = 0; i < 32; ++i) { const uint64_t register_value = debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int; const auto value = dynamic_cast(FindWindow(kRegisterValueFPR0_0 + i)); wxASSERT(value); const bool has_changed = register_value != m_prev_snapshot.fpr[i].fp0int; if (has_changed) value->SetForegroundColour(m_changed_color); else if (value->GetForegroundColour() != wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)) value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); else continue; if(m_show_double_values) value->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0)); else value->ChangeValue(wxString::Format("%016llx", register_value)); } for (int i = 0; i < 32; ++i) { const uint64_t register_value = debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int; const auto value = dynamic_cast(FindWindow(kRegisterValueFPR1_0 + i)); wxASSERT(value); const bool has_changed = register_value != m_prev_snapshot.fpr[i].fp1int; if (has_changed) value->SetForegroundColour(m_changed_color); else if (value->GetForegroundColour() != wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)) value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); else continue; if (m_show_double_values) value->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1)); else value->ChangeValue(wxString::Format("%016llx", register_value)); } // update CRs for (int i = 0; i < 8; ++i) { const auto value = dynamic_cast(FindWindow(kRegisterValueCR0 + i)); wxASSERT(value); auto cr_bits_ptr = debuggerState.debugSession.ppcSnapshot.cr + i * 4; auto cr_bits_ptr_cmp = m_prev_snapshot.cr + i * 4; const bool has_changed = !std::equal(cr_bits_ptr, cr_bits_ptr + 4, cr_bits_ptr_cmp); if (has_changed) value->SetForegroundColour(m_changed_color); else if (value->GetForegroundColour() != wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)) value->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); else continue; std::vector joinArray = {}; if (cr_bits_ptr[Espresso::CR_BIT_INDEX_LT] != 0) joinArray.emplace_back("LT"); if (cr_bits_ptr[Espresso::CR_BIT_INDEX_GT] != 0) joinArray.emplace_back("GT"); if (cr_bits_ptr[Espresso::CR_BIT_INDEX_EQ] != 0) joinArray.emplace_back("EQ"); if (cr_bits_ptr[Espresso::CR_BIT_INDEX_SO] != 0) joinArray.emplace_back("SO"); if (joinArray.empty()) value->ChangeValue("-"); else value->ChangeValue(fmt::format("{}", fmt::join(joinArray, ", "))); } memcpy(&m_prev_snapshot, &debuggerState.debugSession.ppcSnapshot, sizeof(m_prev_snapshot)); } void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event) { if (!debuggerState.debugSession.isTrapped) { event.Skip(); return; } const auto id = event.GetId(); if(kRegisterValueR0 <= id && id < kRegisterValueR0 + 32) { const uint32 register_index = id - kRegisterValueR0; const uint32 register_value = debuggerState.debugSession.ppcSnapshot.gpr[register_index]; wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set R%d value"), register_index), wxString::Format("%08x", register_value)); if (set_value_dialog.ShowModal() == wxID_OK) { const uint32 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16); debuggerState.debugSession.hCPU->gpr[register_index] = new_value; debuggerState.debugSession.ppcSnapshot.gpr[register_index] = new_value; OnUpdateView(); } return; } if (kRegisterValueFPR0_0 <= id && id < kRegisterValueFPR0_0 + 32) { const uint32 register_index = id - kRegisterValueFPR0_0; const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0; wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set FP0_%d value"), register_index), wxString::Format("%lf", register_value)); if (set_value_dialog.ShowModal() == wxID_OK) { const double new_value = std::stod(set_value_dialog.GetValue().ToStdString()); debuggerState.debugSession.hCPU->fpr[register_index].fp0 = new_value; debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0 = new_value; OnUpdateView(); } return; } if (kRegisterValueFPR1_0 <= id && id < kRegisterValueFPR1_0 + 32) { const uint32 register_index = id - kRegisterValueFPR1_0; const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1; wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), wxString::Format(_("Set FP1_%d value"), register_index), wxString::Format("%lf", register_value)); if (set_value_dialog.ShowModal() == wxID_OK) { const double new_value = std::stod(set_value_dialog.GetValue().ToStdString()); debuggerState.debugSession.hCPU->fpr[register_index].fp1 = new_value; debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1 = new_value; OnUpdateView(); } return; } event.Skip(); } void RegisterWindow::OnFPViewModePress(wxCommandEvent& event) { m_show_double_values = !m_show_double_values; for (int i = 0; i < 32; ++i) { const auto value0 = dynamic_cast(FindWindow(kRegisterValueFPR0_0 + i)); const auto value1 = dynamic_cast(FindWindow(kRegisterValueFPR1_0 + i)); wxASSERT(value0); wxASSERT(value1); if (m_show_double_values) { value0->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0)); value1->ChangeValue(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1)); } else { value0->ChangeValue(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int)); value1->ChangeValue(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int)); } } } void RegisterWindow::OnValueContextMenu(wxContextMenuEvent& event) { wxMenu menu; menu.Append(kContextMenuZero, _("&Zero")); menu.Append(kContextMenuInc, _("&Increment")); menu.Append(kContextMenuDec, _("&Decrement")); menu.AppendSeparator(); menu.Append(kContextMenuCopy, _("&Copy")); menu.AppendSeparator(); menu.Append(kContextMenuGotoDisasm, _("&Goto Disasm")); menu.Append(kContextMenuGotoDump, _("G&oto Dump")); m_context_ctrl = dynamic_cast(event.GetEventObject()); PopupMenu(&menu); } void RegisterWindow::OnValueContextMenuSelected(wxCommandEvent& event) { wxASSERT(m_context_ctrl); switch (event.GetId()) { case kContextMenuZero: break; case kContextMenuInc: break; case kContextMenuDec: break; case kContextMenuCopy: break; case kContextMenuGotoDisasm: break; case kContextMenuGotoDump: break; } } ================================================ FILE: src/gui/wxgui/debugger/RegisterWindow.h ================================================ #pragma once #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include class DebuggerWindow2; class wxTextCtrl; class RegisterWindow : public wxFrame { public: RegisterWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size); void OnMainMove(const wxPoint& position, const wxSize& main_size); void OnUpdateView(); private: void OnMouseDClickEvent(wxMouseEvent& event); void OnFPViewModePress(wxCommandEvent& event); void OnValueContextMenu(wxContextMenuEvent& event); void OnValueContextMenuSelected(wxCommandEvent& event); void UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, uint32 registerValue, bool hasChanged); PPCSnapshot m_prev_snapshot; bool m_show_double_values; wxColour m_changed_color = {0xFF0000FF}; wxTextCtrl* m_context_ctrl; }; ================================================ FILE: src/gui/wxgui/debugger/SymbolCtrl.cpp ================================================ #include "wxgui/debugger/SymbolCtrl.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include enum ItemColumns { ColumnName = 0, ColumnAddress, ColumnModule, }; SymbolListCtrl::SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size) : wxListView(parent, id, pos, size, wxLC_REPORT | wxLC_VIRTUAL) { wxListItem col0; col0.SetId(ColumnName); col0.SetText(_("Name")); col0.SetWidth(200); InsertColumn(ColumnName, col0); wxListItem col1; col1.SetId(ColumnAddress); col1.SetText(_("Address")); col1.SetWidth(75); col1.SetAlign(wxLIST_FORMAT_RIGHT); InsertColumn(ColumnAddress, col1); wxListItem col2; col2.SetId(ColumnModule); col2.SetText(_("Module")); col2.SetWidth(75); col2.SetAlign(wxLIST_FORMAT_RIGHT); InsertColumn(ColumnModule, col2); Bind(wxEVT_LIST_ITEM_ACTIVATED, &SymbolListCtrl::OnLeftDClick, this); Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &SymbolListCtrl::OnRightClick, this); m_list_filter.Clear(); OnGameLoaded(); SetItemCount(m_data.size()); } void SymbolListCtrl::OnGameLoaded() { m_data.clear(); const auto symbol_map = rplSymbolStorage_lockSymbolMap(); for (auto const& [address, symbol_info] : symbol_map) { if (symbol_info == nullptr || symbol_info->symbolName == nullptr) continue; wxString libNameWX = wxString::FromAscii((const char*)symbol_info->libName); wxString symbolNameWX = wxString::FromAscii((const char*)symbol_info->symbolName); wxString searchNameWX = libNameWX + symbolNameWX; searchNameWX.MakeLower(); auto new_entry = m_data.try_emplace( symbol_info->address, symbolNameWX, libNameWX, searchNameWX, false ); if (m_list_filter.IsEmpty()) new_entry.first->second.visible = true; else if (new_entry.first->second.searchName.Contains(m_list_filter)) new_entry.first->second.visible = true; } rplSymbolStorage_unlockSymbolMap(); SetItemCount(m_data.size()); if (m_data.size() > 0) RefreshItems(GetTopItem(), std::min(m_data.size() - 1, GetTopItem() + GetCountPerPage() + 1)); } wxString SymbolListCtrl::OnGetItemText(long item, long column) const { if (item >= GetItemCount()) return wxEmptyString; long visible_idx = 0; for (const auto& [address, symbol] : m_data) { if (!symbol.visible) continue; if (item == visible_idx) { if (column == ColumnName) return wxString(symbol.name); if (column == ColumnAddress) return wxString::Format("%08x", address); if (column == ColumnModule) return wxString(symbol.libName); } visible_idx++; } return wxEmptyString; } void SymbolListCtrl::OnLeftDClick(wxListEvent& event) { long selected = GetFirstSelected(); if (selected == wxNOT_FOUND) return; const auto text = GetItemText(selected, ColumnAddress); const auto address = std::stoul(text.ToStdString(), nullptr, 16); if (address == 0) return; debuggerState.debugSession.instructionPointer = address; g_debuggerDispatcher.MoveIP(); } void SymbolListCtrl::OnRightClick(wxListEvent& event) { long selected = GetFirstSelected(); if (selected == wxNOT_FOUND) return; auto text = GetItemText(selected, ColumnAddress); text = "0x" + text; #if BOOST_OS_WINDOWS if (OpenClipboard(nullptr)) { EmptyClipboard(); const HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, text.size()+1); if (hGlobal) { memcpy(GlobalLock(hGlobal), text.c_str(), text.size() + 1); GlobalUnlock(hGlobal); SetClipboardData(CF_TEXT, hGlobal); GlobalFree(hGlobal); } CloseClipboard(); } #endif } void SymbolListCtrl::ChangeListFilter(wxString filter) { m_list_filter = filter.MakeLower(); size_t visible_entries = m_data.size(); for (auto& [address, symbol] : m_data) { if (m_list_filter.IsEmpty()) symbol.visible = true; else if (symbol.searchName.Contains(m_list_filter)) symbol.visible = true; else { visible_entries--; symbol.visible = false; } } SetItemCount(visible_entries); if (visible_entries > 0) RefreshItems(GetTopItem(), std::min(visible_entries - 1, GetTopItem() + GetCountPerPage() + 1)); } ================================================ FILE: src/gui/wxgui/debugger/SymbolCtrl.h ================================================ #pragma once #include class SymbolListCtrl : public wxListView { public: SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size); void OnGameLoaded(); void ChangeListFilter(wxString filter); private: struct SymbolItem { SymbolItem() = default; SymbolItem(wxString name, wxString libName, wxString searchName, bool visible) : name(name), libName(libName), searchName(searchName), visible(visible) {} wxString name; wxString libName; wxString searchName; bool visible; }; std::map m_data; wxString m_list_filter; wxString OnGetItemText(long item, long column) const; void OnLeftDClick(wxListEvent& event); void OnRightClick(wxListEvent& event); }; ================================================ FILE: src/gui/wxgui/debugger/SymbolWindow.cpp ================================================ #include "wxgui/wxgui.h" #include "wxgui/debugger/SymbolWindow.h" #include "wxgui/debugger/DebuggerWindow2.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" enum ItemColumns { ColumnName = 0, ColumnAddress, ColumnModule, }; SymbolWindow::SymbolWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size) : wxFrame(&parent, wxID_ANY, _("Symbols"), wxDefaultPosition, wxSize(600, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT) { wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); m_symbol_ctrl = new SymbolListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); main_sizer->Add(m_symbol_ctrl, 1, wxEXPAND); m_filter = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_NO_VSCROLL); m_filter->Bind(wxEVT_TEXT, &SymbolWindow::OnFilterChanged, this); main_sizer->Add(m_filter, 0, wxALL | wxEXPAND, 5); this->SetSizer(main_sizer); this->wxWindowBase::Layout(); this->Centre(wxHORIZONTAL); if (parent.GetConfig().data().pin_to_main) OnMainMove(main_position, main_size); } void SymbolWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size) { wxSize size(420, 250); this->SetSize(size); wxPoint position = main_position; position.x -= 420; this->SetPosition(position); } void SymbolWindow::OnGameLoaded() { m_symbol_ctrl->OnGameLoaded(); } void SymbolWindow::OnFilterChanged(wxCommandEvent& event) { m_symbol_ctrl->ChangeListFilter(m_filter->GetValue()); } ================================================ FILE: src/gui/wxgui/debugger/SymbolWindow.h ================================================ #pragma once #include "wxgui/debugger/SymbolCtrl.h" #include class DebuggerWindow2; class SymbolWindow : public wxFrame { public: SymbolWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size); void OnMainMove(const wxPoint& position, const wxSize& main_size); void OnGameLoaded(); void OnLeftDClick(wxListEvent& event); private: wxTextCtrl* m_filter; SymbolListCtrl* m_symbol_ctrl; void OnFilterChanged(wxCommandEvent& event); }; ================================================ FILE: src/gui/wxgui/dialogs/CreateAccount/wxCreateAccountDialog.cpp ================================================ #include "wxgui/dialogs/CreateAccount/wxCreateAccountDialog.h" #include "Cafe/Account/Account.h" #include #include #include #include #include #include #include #include "util/helpers/helpers.h" wxCreateAccountDialog::wxCreateAccountDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _("Create new account")) { auto* main_sizer = new wxFlexGridSizer(0, 2, 0, 0); main_sizer->AddGrowableCol(1); main_sizer->SetFlexibleDirection(wxBOTH); main_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); main_sizer->Add(new wxStaticText(this, wxID_ANY, "PersistentId"), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_persistent_id = new wxTextCtrl(this, wxID_ANY, fmt::format("{:x}", Account::GetNextPersistentId())); m_persistent_id->SetToolTip(_("The persistent id is the internal folder name used for your saves. Only change this if you are importing saves from a Wii U with a specific id")); main_sizer->Add(m_persistent_id, 1, wxALL | wxEXPAND, 5); main_sizer->Add(new wxStaticText(this, wxID_ANY, _("Mii name")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_mii_name = new wxTextCtrl(this, wxID_ANY); m_mii_name->SetFocus(); m_mii_name->SetMaxLength(10); main_sizer->Add(m_mii_name, 1, wxALL | wxEXPAND, 5); main_sizer->Add(0, 0, 1, wxEXPAND, 5); { auto* button_sizer = new wxBoxSizer(wxHORIZONTAL); m_ok_button = new wxButton(this, wxID_ANY, _("OK")); m_ok_button->Bind(wxEVT_BUTTON, &wxCreateAccountDialog::OnOK, this); button_sizer->Add(m_ok_button, 0, wxALL, 5); m_cancel_buton = new wxButton(this, wxID_ANY, _("Cancel")); m_cancel_buton->Bind(wxEVT_BUTTON, &wxCreateAccountDialog::OnCancel, this); button_sizer->Add(m_cancel_buton, 0, wxALL, 5); main_sizer->Add(button_sizer, 1, wxALIGN_RIGHT, 5); } this->SetSizerAndFit(main_sizer); this->wxWindowBase::Layout(); } uint32 wxCreateAccountDialog::GetPersistentId() const { const std::string id_string = m_persistent_id->GetValue().ToStdString(); return ConvertString(id_string, 16); } wxString wxCreateAccountDialog::GetMiiName() const { return m_mii_name->GetValue(); } void wxCreateAccountDialog::OnOK(wxCommandEvent& event) { if(m_persistent_id->IsEmpty()) { wxMessageBox(_("No persistent id entered!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } const auto id = GetPersistentId(); if(id < Account::kMinPersistendId) { wxMessageBox(formatWxString(_("The persistent id must be greater than {:x}!"), Account::kMinPersistendId), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } const auto& account = Account::GetAccount(id); if(account.GetPersistentId() == id) { const std::wstring msg = fmt::format(fmt::runtime(_("The persistent id {:x} is already in use by account {}!").ToStdWstring()), account.GetPersistentId(), std::wstring{ account.GetMiiName() }); wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } if(m_mii_name->IsEmpty()) { wxMessageBox(_("Account name may not be empty!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } EndModal(wxID_OK); } void wxCreateAccountDialog::OnCancel(wxCommandEvent& event) { EndModal(wxID_CANCEL); } ================================================ FILE: src/gui/wxgui/dialogs/CreateAccount/wxCreateAccountDialog.h ================================================ #pragma once #include class wxCreateAccountDialog : public wxDialog { public: wxCreateAccountDialog(wxWindow* parent); [[nodiscard]] uint32 GetPersistentId() const; [[nodiscard]] wxString GetMiiName() const; private: class wxTextCtrl* m_persistent_id; class wxTextCtrl* m_mii_name; class wxButton* m_ok_button, * m_cancel_buton; void OnOK(wxCommandEvent& event); void OnCancel(wxCommandEvent& event); }; ================================================ FILE: src/gui/wxgui/dialogs/SaveImport/SaveImportWindow.cpp ================================================ #include "SaveImportWindow.h" #include "pugixml.hpp" #include #include #include #include #include #include #include #include #include "zip.h" #include "Cafe/Account/Account.h" #include "config/ActiveSettings.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "util/helpers/helpers.h" #include "Cafe/HW/Espresso/PPCState.h" #include "wxgui/helpers/wxHelpers.h" SaveImportWindow::SaveImportWindow(wxWindow* parent, uint64 title_id) : wxDialog(parent, wxID_ANY, _("Import save entry"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), m_title_id(title_id) { auto* sizer = new wxBoxSizer(wxVERTICAL); { auto* row1 = new wxFlexGridSizer(0, 2, 0, 0); row1->AddGrowableCol(1); row1->Add(new wxStaticText(this, wxID_ANY, _("Source")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_source_selection = new wxFilePickerCtrl(this, wxID_ANY, wxEmptyString, _("Select a zipped save file"), formatWxString("{}|*.zip", _("Save entry (*.zip)"))); m_source_selection->SetMinSize({ 270, -1 }); row1->Add(m_source_selection, 1, wxALL | wxEXPAND, 5); row1->Add(new wxStaticText(this, wxID_ANY, _("Target")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); const auto& accounts = Account::GetAccounts(); m_target_selection = new wxComboBox(this, wxID_ANY); for (const auto& account : accounts) { m_target_selection->Append(fmt::format("{:x} ({})", account.GetPersistentId(), boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())), (void*)(uintptr_t)account.GetPersistentId()); } row1->Add(m_target_selection, 1, wxALL | wxEXPAND, 5); sizer->Add(row1, 0, wxEXPAND, 5); } { auto* row2 = new wxFlexGridSizer(0, 2, 0, 0); row2->AddGrowableCol(1); row2->SetFlexibleDirection(wxBOTH); row2->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); auto* ok_button = new wxButton(this, wxID_ANY, _("OK")); ok_button->Bind(wxEVT_BUTTON, &SaveImportWindow::OnImport, this); row2->Add(ok_button, 0, wxALL, 5); auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel")); cancel_button->Bind(wxEVT_BUTTON, [this](auto&) { m_return_code = wxCANCEL; Close(); }); row2->Add(cancel_button, 0, wxALIGN_RIGHT | wxALL, 5); sizer->Add(row2, 1, wxEXPAND, 5); } this->SetSizerAndFit(sizer); this->Centre(wxBOTH); } void SaveImportWindow::EndModal(int retCode) { wxDialog::EndModal(retCode); SetReturnCode(m_return_code); } void SaveImportWindow::OnImport(wxCommandEvent& event) { const auto source_path = m_source_selection->GetPath(); if (source_path.empty()) return; // try to find cemu_meta file to verify targetid const std::string zipfile = source_path.ToUTF8().data(); int ziperr; auto* zip = zip_open(zipfile.c_str(), ZIP_RDONLY, &ziperr); if (zip) { const sint32 numEntries = zip_get_num_entries(zip, 0); for (sint32 i = 0; i < numEntries; i++) { zip_stat_t sb{}; if (zip_stat_index(zip, i, 0, &sb) != 0) continue; if (!boost::equals(sb.name, "cemu_meta")) continue; auto* zip_file = zip_fopen_index(zip, i, 0); if (zip_file == nullptr) continue; auto buffer = std::make_unique(sb.size); if (zip_fread(zip_file, buffer.get(), sb.size) == sb.size) { std::string_view str{ buffer.get(), sb.size }; // titleId = {:#016x} if(boost::starts_with(str, "titleId = ")) { const uint64_t titleId = ConvertString(str.substr(sizeof("titleId = ") + 1), 16); if(titleId != 0 && titleId != m_title_id) { const auto msg = formatWxString(_("You are trying to import a savegame for a different title than your currently selected one: {:016x} vs {:016x}\nAre you sure that you want to continue?"), titleId, m_title_id); const auto res = wxMessageBox(msg, _("Error"), wxYES_NO | wxCENTRE | wxICON_WARNING, this); if(res == wxNO) { m_return_code = wxCANCEL; Close(); return; } } } } zip_fclose(zip_file); break; } zip_close(zip); } // unzip to tmp folder -> using target path directly now std::error_code ec; //auto tmp_source = fs::temp_directory_path(ec); //if(ec) //{ // const auto error_msg = formatWxString(_("Error when getting the temp directory path:\n{}"), GetSystemErrorMessage(ec)); // wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); // return; //} uint32 target_id = 0; const auto selection = m_target_selection->GetCurrentSelection(); if (selection != wxNOT_FOUND) target_id = (uint32)(uintptr_t)m_target_selection->GetClientData(selection); if (target_id == 0) { target_id = ConvertString(m_target_selection->GetValue().ToStdString(), 16); if (target_id < Account::kMinPersistendId) { const auto msg = formatWxString(_("The given account id is not valid!\nIt must be a hex number bigger or equal than {:08x}"), Account::kMinPersistendId); wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } } fs::path target_path = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), target_id); if (fs::exists(target_path)) { if (!fs::is_directory(target_path)) { const auto msg = formatWxString(_("There's already a file at the target directory:\n{}"), _pathToUtf8(target_path)); wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); m_return_code = wxCANCEL; Close(); return; } const auto msg = _("There's already a save game available for the target account, do you want to overwrite it?\nThis will delete the existing save files for the account and replace them."); const auto result = wxMessageBox(msg, _("Error"), wxYES_NO | wxCENTRE | wxICON_WARNING, this); if (result == wxNO) { m_return_code = wxCANCEL; Close(); return; } std::error_code ec; while (fs::exists(target_path, ec) || ec) { fs::remove_all(target_path, ec); if (ec) { const auto error_msg = formatWxString(_("Error when trying to delete the former save game:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } // extract zip file //tmp_source.append("Cemu").append(fmt::format("{}", rand())); //tmp_source = getMlcPath(L"usr/save"); //tmp_source /= fmt::format("{:08x}/{:08x}/user/{:08x}_tmp", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), target_id); auto tmp_source = target_path; fs::create_directories(tmp_source, ec); if (ec) { const auto error_msg = formatWxString(_("Error when creating the extraction path:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } zip = zip_open(zipfile.c_str(), ZIP_RDONLY, &ziperr); if (!zip) { const auto error_msg = formatWxString(_("Error when opening the import zip file:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } sint32 numEntries = zip_get_num_entries(zip, 0); for (sint32 i = 0; i < numEntries; i++) { zip_stat_t sb{}; if (zip_stat_index(zip, i, 0, &sb) != 0) { cemuLog_log(LogType::Force, "zip stat index failed on {} entry", i); continue; } if (boost::starts_with(sb.name, "../") || boost::starts_with(sb.name, "..\\")) continue; // bad path if (boost::equals(sb.name, "cemu_meta")) continue; size_t sb_name_len = strlen(sb.name); if (sb_name_len == 0) continue; const auto path = fs::path(tmp_source).append(sb.name); if (sb.name[sb_name_len - 1] == '/') { fs::create_directories(path, ec); if (ec) cemuLog_log(LogType::Force, "can't create directory {}: {}", sb.name, ec.message()); continue; } if (sb.size == 0) continue; if (sb.size > (1024 * 1024 * 128)) continue; // skip unusually huge files auto* zip_file = zip_fopen_index(zip, i, 0); if (zip_file == nullptr) continue; auto buffer = std::make_unique(sb.size); if (zip_fread(zip_file, buffer.get(), sb.size) == sb.size) { std::ofstream file(path, std::ios::out | std::ios::binary); if (file.is_open()) file.write(buffer.get(), sb.size); } zip_fclose(zip_file); } zip_close(zip); // extracted all files to tmp_source // edit meta saveinfo.xml fs::path saveinfo = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF)); if (fs::exists(saveinfo) || fs::is_regular_file(saveinfo)) { pugi::xml_document doc; if (doc.load_file(saveinfo.c_str())) { auto info_node = doc.child("info"); if (info_node) { // delete old node if available auto old_persistend_id_string = fmt::format("{:08x}", target_id); const auto node_entry = info_node.find_child([&old_persistend_id_string](const pugi::xml_node& node) { return boost::iequals(node.attribute("persistentId").as_string(), old_persistend_id_string); }); // add save-entry node if not available yet if (!node_entry) { // add new node auto new_persistend_id_string = fmt::format("{:08x}", target_id); auto new_node = info_node.append_child("account"); new_node.append_attribute("persistentId").set_value(new_persistend_id_string.c_str()); auto timestamp = new_node.append_child("timestamp"); timestamp.text().set(fmt::format("{:016x}", coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK).c_str()); // TODO time not initialized yet? if(!doc.save_file(saveinfo.c_str())) cemuLog_log(LogType::Force, "couldn't insert save entry in saveinfo.xml: {}", _pathToUtf8(saveinfo)); } } } } // todo create saveinfo if doesnt exist /*wxMessageBox(fmt::format("{}\n{}", tmp_source.generic_u8string(), target_path.generic_u8string()), _("Error"), wxOK | wxCENTRE, this); fs::rename(tmp_source, target_path, ec); if (ec) { const auto error_msg = formatWxString(_("Error when trying to move the extracted save game:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return; }*/ m_target_id = target_id; m_return_code = wxOK; Close(); } ================================================ FILE: src/gui/wxgui/dialogs/SaveImport/SaveImportWindow.h ================================================ #pragma once #include class SaveImportWindow : public wxDialog { public: SaveImportWindow(wxWindow* parent, uint64 title_id); void EndModal(int retCode) override; uint32 GetTargetPersistentId() const { return m_target_id; } private: void OnImport(wxCommandEvent& event); class wxFilePickerCtrl* m_source_selection; class wxComboBox* m_target_selection; uint32 m_target_id = 0; const uint64 m_title_id; const fs::path m_source_file; int m_return_code = wxCANCEL; }; ================================================ FILE: src/gui/wxgui/dialogs/SaveImport/SaveTransfer.cpp ================================================ #include "SaveTransfer.h" #include "pugixml.hpp" #include #include #include #include #include #include #include #include "Cafe/Account/Account.h" #include "config/ActiveSettings.h" #include "util/helpers/helpers.h" #include "wxgui/helpers/wxHelpers.h" SaveTransfer::SaveTransfer(wxWindow* parent, uint64 title_id, const wxString& source_account, uint32 source_id) : wxDialog(parent, wxID_ANY, _("Save transfer"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), m_title_id(title_id), m_source_id(source_id) { auto* sizer = new wxBoxSizer(wxVERTICAL); { auto* row1 = new wxFlexGridSizer(0, 2, 0, 0); row1->AddGrowableCol(1); row1->Add(new wxStaticText(this, wxID_ANY, _("Source")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* source_choice = new wxChoice(this, wxID_ANY); source_choice->SetMinSize({170, -1}); source_choice->Append(source_account); source_choice->SetSelection(0); row1->Add(source_choice, 1, wxALL | wxEXPAND, 5); row1->Add(new wxStaticText(this, wxID_ANY, _("Target")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); const auto& accounts = Account::GetAccounts(); m_target_selection = new wxComboBox(this, wxID_ANY); for (const auto& account : accounts) { if (account.GetPersistentId() == m_source_id) continue; m_target_selection->Append(fmt::format("{:x} ({})", account.GetPersistentId(), boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())), (void*)(uintptr_t)account.GetPersistentId()); } row1->Add(m_target_selection, 1, wxALL | wxEXPAND, 5); sizer->Add(row1, 0, wxEXPAND, 5); } { auto* row2 = new wxFlexGridSizer(0, 2, 0, 0); row2->AddGrowableCol(1); row2->SetFlexibleDirection(wxBOTH); row2->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); auto* ok_button = new wxButton(this, wxID_ANY, _("OK")); ok_button->Bind(wxEVT_BUTTON, &SaveTransfer::OnTransfer, this); row2->Add(ok_button, 0, wxALL, 5); auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel")); cancel_button->Bind(wxEVT_BUTTON, [this](auto&) { m_return_code = wxCANCEL; Close(); }); row2->Add(cancel_button, 0, wxALIGN_RIGHT | wxALL, 5); sizer->Add(row2, 1, wxEXPAND, 5); } this->SetSizerAndFit(sizer); this->Centre(wxBOTH); } void SaveTransfer::EndModal(int retCode) { wxDialog::EndModal(retCode); SetReturnCode(m_return_code); } void SaveTransfer::OnTransfer(wxCommandEvent& event) { uint32 target_id = 0; const auto selection = m_target_selection->GetCurrentSelection(); if(selection != wxNOT_FOUND) target_id = (uint32)(uintptr_t)m_target_selection->GetClientData(selection); if (target_id == 0) { target_id = ConvertString(m_target_selection->GetValue().ToStdString(), 16); if(target_id < Account::kMinPersistendId) { const auto msg = formatWxString(_("The given account id is not valid!\nIt must be a hex number bigger or equal than {:08x}"), Account::kMinPersistendId); wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } } const fs::path source_path = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), m_source_id); if (!fs::exists(source_path) || !fs::is_directory(source_path)) return; const fs::path target_path = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), target_id); if (fs::exists(target_path)) { if(!fs::is_directory(target_path)) { const auto msg = formatWxString(_("There's already a file at the target directory:\n{}"), _pathToUtf8(target_path)); wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); m_return_code = wxCANCEL; Close(); return; } const auto msg = _("There's already a save game available for the target account, do you want to overwrite it?\nThis will delete the existing save files for the account and replace them."); const auto result = wxMessageBox(msg, _("Error"), wxYES_NO | wxCENTRE | wxICON_WARNING, this); if(result == wxNO) { m_return_code = wxCANCEL; Close(); return; } std::error_code ec; while (fs::exists(target_path, ec) || ec) { fs::remove_all(target_path, ec); if (ec) { const auto error_msg = formatWxString(_("Error when trying to delete the former save game:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return; } // wait so filesystem doesnt std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } // edit meta saveinfo.xml bool meta_file_edited = false; const fs::path saveinfo = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF)); if (fs::exists(saveinfo) || fs::is_regular_file(saveinfo)) { pugi::xml_document doc; if (doc.load_file(saveinfo.c_str())) { auto info_node = doc.child("info"); if (info_node) { // delete old node if available auto old_persistend_id_string = fmt::format("{:08x}", target_id); const auto delete_entry = info_node.find_child([&old_persistend_id_string](const pugi::xml_node& node) { return boost::iequals(node.attribute("persistentId").as_string(), old_persistend_id_string); }); if (delete_entry) info_node.remove_child(delete_entry); // move new node auto new_persistend_id_string = fmt::format("{:08x}", m_source_id); auto move_entry = info_node.find_child([&new_persistend_id_string](const pugi::xml_node& node) { return boost::iequals(node.attribute("persistentId").as_string(), new_persistend_id_string); }); if(move_entry) move_entry.attribute("persistentId").set_value(old_persistend_id_string.c_str()); else { // TODO: create if not found! (shouldnt happen though) } meta_file_edited = doc.save_file(saveinfo.c_str()); } } } if (!meta_file_edited) cemuLog_log(LogType::Force, "SaveTransfer::OnTransfer: couldn't update save entry in saveinfo.xml: {}", _pathToUtf8(saveinfo)); std::error_code ec; fs::rename(source_path, target_path, ec); if (ec) { const auto error_msg = formatWxString(_("Error when trying to move the save game:\n{}"), GetSystemErrorMessage(ec)); wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); return; } m_target_id = target_id; m_return_code = wxOK; Close(); } ================================================ FILE: src/gui/wxgui/dialogs/SaveImport/SaveTransfer.h ================================================ #pragma once #include class wxComboBox; class SaveTransfer : public wxDialog { public: SaveTransfer(wxWindow* parent, uint64 title_id, const wxString& source_account, uint32 source_id); void EndModal(int retCode) override; uint32 GetTargetPersistentId() const { return m_target_id; } private: void OnTransfer(wxCommandEvent& event); wxComboBox* m_target_selection; uint32 m_target_id = 0; const uint64 m_title_id; const uint32 m_source_id; int m_return_code = wxCANCEL; }; ================================================ FILE: src/gui/wxgui/helpers/wxControlObject.h ================================================ #pragma once #include class wxControlObject : public wxObject { public: wxControlObject(wxControl* control) : m_control(control) {} template T* GetControl() const { static_assert(std::is_base_of_v, "T must inherit from wxControl"); return dynamic_cast(m_control); } wxControl* GetControl() const { return m_control; } private: wxControl* m_control; }; class wxControlObjects : public wxObject { public: wxControlObjects(std::vector controls) : m_controls(std::move(controls)) {} template T* GetControl(int index) const { static_assert(std::is_base_of_v, "T must inherit from wxControl"); if (index < 0 || index >= m_controls.size()) return nullptr; return dynamic_cast(m_controls[index]); } const std::vector& GetControls() const { return m_controls; } private: std::vector m_controls; }; ================================================ FILE: src/gui/wxgui/helpers/wxCustomData.h ================================================ #pragma once #include template class wxCustomData : public wxClientData { public: wxCustomData() : m_data({}) {} wxCustomData(T data) : m_data(std::move(data)) { } const T& GetData() const { return m_data; } /// /// obtains ownership of the data /// /// returns the stored data T get() { return std::move(m_data); } /// /// access to the data without obtaining ownership /// /// returns a reference to the stored data T& ref() { return m_data; } bool operator==(const T& o) const { return m_data == o.m_data; } bool operator!=(const T& o) const { return !(*this == o); } private: T m_data; }; //template //class wxCustomObject : public wxObject //{ //public: // wxCustomObject(T data) // : m_data(std::move(data)) { } // // const T& GetData() const { return m_data; } // // bool operator==(const T& o) const { return m_data == o.m_data; } // bool operator!=(const T& o) const { return !(*this == o); } //private: // T m_data; //}; ================================================ FILE: src/gui/wxgui/helpers/wxCustomEvents.cpp ================================================ #include "wxgui/helpers/wxCustomEvents.h" wxDEFINE_EVENT(wxEVT_SET_STATUS_BAR_TEXT, wxSetStatusBarTextEvent); wxDEFINE_EVENT(wxEVT_SET_GAUGE_VALUE, wxSetGaugeValue); wxDEFINE_EVENT(wxEVT_REMOVE_ITEM, wxCommandEvent); wxDEFINE_EVENT(wxEVT_SET_TEXT, wxCommandEvent); wxDEFINE_EVENT(wxEVT_ENABLE, wxCommandEvent); ================================================ FILE: src/gui/wxgui/helpers/wxCustomEvents.h ================================================ #pragma once #include #include class wxStatusBar; class wxControl; wxDECLARE_EVENT(wxEVT_REMOVE_ITEM, wxCommandEvent); wxDECLARE_EVENT(wxEVT_SET_TEXT, wxCommandEvent); wxDECLARE_EVENT(wxEVT_ENABLE, wxCommandEvent); class wxSetStatusBarTextEvent; wxDECLARE_EVENT(wxEVT_SET_STATUS_BAR_TEXT, wxSetStatusBarTextEvent); class wxSetStatusBarTextEvent : public wxCommandEvent { public: wxSetStatusBarTextEvent(const wxString& text, int number = 0, wxEventType type = wxEVT_SET_STATUS_BAR_TEXT, int id = 0) : wxCommandEvent(type, id), m_text(text), m_number(number) {} wxSetStatusBarTextEvent(const wxSetStatusBarTextEvent& event) : wxCommandEvent(event), m_text(event.GetText()), m_number(event.GetNumber()) {} [[nodiscard]] const wxString& GetText() const { return m_text; } [[nodiscard]] int GetNumber() const { return m_number; } wxEvent* Clone() const override { return new wxSetStatusBarTextEvent(*this); } private: const wxString m_text; const int m_number; }; class wxSetGaugeValue; wxDECLARE_EVENT(wxEVT_SET_GAUGE_VALUE, wxSetGaugeValue); class wxSetGaugeValue : public wxCommandEvent { public: wxSetGaugeValue(int value, wxGauge* gauge, wxControl* text_ctrl = nullptr, const wxString& text = wxEmptyString, wxEventType type = wxEVT_SET_GAUGE_VALUE, int id = 0) : wxCommandEvent(type, id), m_gauge_value(value), m_gauge_range(0), m_text_ctrl(text_ctrl), m_text(text) { SetEventObject(gauge); } wxSetGaugeValue(int value, int range, wxGauge* gauge, wxControl* text_ctrl = nullptr, const wxString& text = wxEmptyString, wxEventType type = wxEVT_SET_GAUGE_VALUE, int id = 0) : wxCommandEvent(type, id), m_gauge_value(value), m_gauge_range(range), m_text_ctrl(text_ctrl), m_text(text) { SetEventObject(gauge); } wxSetGaugeValue(const wxSetGaugeValue& event) : wxCommandEvent(event), m_gauge_value(event.GetValue()), m_gauge_range(event.GetRange()), m_text_ctrl(event.GetTextCtrl()), m_text(event.GetText()) {} [[nodiscard]] int GetValue() const { return m_gauge_value; } [[nodiscard]] int GetRange() const { return m_gauge_range; } [[nodiscard]] wxGauge* GetGauge() const { return (wxGauge*)GetEventObject(); } [[nodiscard]] const wxString& GetText() const { return m_text; } [[nodiscard]] wxControl* GetTextCtrl() const { return m_text_ctrl; } wxEvent* Clone() const override { return new wxSetGaugeValue(*this); } private: const int m_gauge_value, m_gauge_range; wxControl* m_text_ctrl; const wxString m_text; }; ================================================ FILE: src/gui/wxgui/helpers/wxHelpers.cpp ================================================ #include "wxgui/helpers/wxHelpers.h" #include #include #include #include #if BOOST_OS_LINUX || BOOST_OS_BSD #include #include #include #include #ifdef HAS_WAYLAND #include #endif #endif #include "wxgui/helpers/wxControlObject.h" void wxAutosizeColumn(wxListCtrlBase* ctrl, int col) { ctrl->SetColumnWidth(col, wxLIST_AUTOSIZE_USEHEADER); int wh = ctrl->GetColumnWidth(col); ctrl->SetColumnWidth(col, wxLIST_AUTOSIZE); int wc = ctrl->GetColumnWidth(col); if (wh > wc) ctrl->SetColumnWidth(col, wxLIST_AUTOSIZE_USEHEADER); } void wxAutosizeColumns(wxListCtrlBase* ctrl, int col_start, int col_end) { wxWindowUpdateLocker lock(ctrl); for (int i = col_start; i <= col_end; ++i) wxAutosizeColumn(ctrl, i); } void update_slider_text(wxCommandEvent& event, const wxFormatString& format /*= "%d%%"*/) { const auto slider = dynamic_cast(event.GetEventObject()); wxASSERT(slider); auto slider_text = dynamic_cast(event.GetEventUserData())->GetControl(); wxASSERT(slider_text); slider_text->SetLabel(wxString::Format(format, slider->GetValue())); } uint32 fix_raw_keycode(uint32 keycode, uint32 raw_flags) { #if BOOST_OS_WINDOWS const auto flags = (HIWORD(raw_flags) & 0xFFF); if(keycode == VK_SHIFT) { if(flags == 0x2A) return 160; else if (flags == 0x36) return 161; } else if (keycode == VK_CONTROL) { if (flags == 0x1d) return 162; else if (flags == 0x11d) return 163; } else if (keycode == VK_MENU) { if ((flags & 0xFF) == 0x38) return 164; else if ((flags & 0xFF) == 0x38) return 165; } #endif return keycode; } WindowSystem::WindowHandleInfo initHandleContextFromWxWidgetsWindow(wxWindow* wxw) { WindowSystem::WindowHandleInfo handleInfo; #if BOOST_OS_WINDOWS handleInfo.backend = WindowSystem::WindowHandleInfo::Backend::Windows; handleInfo.surface = reinterpret_cast(wxw->GetHWND()); #elif BOOST_OS_LINUX || BOOST_OS_BSD GtkWidget* gtkWidget = (GtkWidget*)wxw->GetHandle(); // returns GtkWidget gtk_widget_realize(gtkWidget); GdkWindow* gdkWindow = gtk_widget_get_window(gtkWidget); GdkDisplay* gdkDisplay = gdk_window_get_display(gdkWindow); if (GDK_IS_X11_WINDOW(gdkWindow)) { handleInfo.backend = WindowSystem::WindowHandleInfo::Backend::X11; handleInfo.surface = reinterpret_cast(gdk_x11_window_get_xid(gdkWindow)); handleInfo.display = gdk_x11_display_get_xdisplay(gdkDisplay); if (!handleInfo.display) { cemuLog_log(LogType::Force, "Unable to get xlib display"); } } #ifdef HAS_WAYLAND else if (GDK_IS_WAYLAND_WINDOW(gdkWindow)) { handleInfo.backend = WindowSystem::WindowHandleInfo::Backend::Wayland; handleInfo.surface = gdk_wayland_window_get_wl_surface(gdkWindow); handleInfo.display = gdk_wayland_display_get_wl_display(gdkDisplay); } #endif else { cemuLog_log(LogType::Force, "Unsuported GTK backend"); } #elif BOOST_OS_MACOS handleInfo.backend = WindowSystem::WindowHandleInfo::Backend::Cocoa; handleInfo.surface = reinterpret_cast(wxw->GetHandle()); #endif return handleInfo; } ================================================ FILE: src/gui/wxgui/helpers/wxHelpers.h ================================================ #pragma once #include "interface/WindowSystem.h" #include #include #include template <> struct fmt::formatter : formatter { template auto format(const wxString& str, FormatContext& ctx) const { return formatter::format(str.ToStdString(), ctx); } }; class wxTempEnable { public: wxTempEnable(wxControl* control, bool state = true) : m_control(control), m_state(state) { m_control->Enable(m_state); } wxTempEnable(const wxTempEnable&) = delete; wxTempEnable(wxTempEnable&&) noexcept = default; ~wxTempEnable() { if(m_control) m_control->Enable(!m_state); } private: wxControl* m_control; const bool m_state; }; class wxTempDisable : wxTempEnable { public: wxTempDisable(wxControl* control) : wxTempEnable(control, false) {} }; template wxString formatWxString(const wxString& format, TArgs&&...args) { return wxString::FromUTF8(fmt::format(fmt::runtime(format.utf8_string()), std::forward(args)...)); } // executes a function when destroying the obj template class wxDTorFunc { public: wxDTorFunc(TFunction&& func, TArgs&&... args) { auto bound = std::bind(std::forward(func), std::forward(args)...); // m_function = [bound] { bound(); }; m_function = [bound{ std::move(bound) }]{ bound(); }; } ~wxDTorFunc() { m_function(); } private: std::function m_function; }; void wxAutosizeColumn(wxListCtrlBase* ctrl, int col); void wxAutosizeColumns(wxListCtrlBase* ctrl, int col_start, int col_end); template T get_next_sibling(const T element) { static_assert(std::is_pointer_v && std::is_base_of_v>, "element must be a pointer and inherit from wxControl"); for (auto sibling = element->GetNextSibling(); sibling; sibling = sibling->GetNextSibling()) { if (auto result = dynamic_cast(sibling)) return result; } return nullptr; } template T get_prev_sibling(const T element) { static_assert(std::is_pointer_v && std::is_base_of_v>, "element must be a pointer and inherit from wxControl"); for (auto sibling = element->GetPrevSibling(); sibling; sibling = sibling->GetPrevSibling()) { auto result = dynamic_cast(sibling); if (result) return result; } return nullptr; } void update_slider_text(wxCommandEvent& event, const wxFormatString& format = "%d%%"); uint32 fix_raw_keycode(uint32 keycode, uint32 raw_flags); WindowSystem::WindowHandleInfo initHandleContextFromWxWidgetsWindow(wxWindow* wxw); ================================================ FILE: src/gui/wxgui/helpers/wxLogEvent.h ================================================ #pragma once #include class wxLogEvent; wxDECLARE_EVENT(EVT_LOG, wxLogEvent); class wxLogEvent : public wxCommandEvent { public: wxLogEvent(const wxString& filter, const wxString& message) : wxCommandEvent(EVT_LOG), m_filter(filter), m_message(message) { } wxLogEvent(const wxLogEvent& event) : wxCommandEvent(event), m_filter(event.GetFilter()), m_message(event.GetMessage()) { } wxEvent* Clone() const { return new wxLogEvent(*this); } [[nodiscard]] const wxString& GetFilter() const { return m_filter; } [[nodiscard]] const wxString& GetMessage() const { return m_message; } private: wxString m_filter; wxString m_message; }; ================================================ FILE: src/gui/wxgui/helpers/wxWayland.cpp ================================================ #include "wxgui/helpers/wxWayland.h" #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND #include bool wxWlIsWaylandWindow(wxWindow* window) { GtkWidget* gtkWindow = static_cast(window->GetHandle()); GdkWindow* gdkWindow = gtk_widget_get_window(gtkWindow); return GDK_IS_WAYLAND_WINDOW(gdkWindow); } void wxWlSetAppId(wxFrame* frame, const char* applicationId) { GtkWidget* gtkWindow = static_cast(frame->GetHandle()); gtk_widget_realize(gtkWindow); GdkWindow* gdkWindow = gtk_widget_get_window(gtkWindow); static auto gdk_wl_set_app_id = reinterpret_cast(dlsym(nullptr, "gdk_wayland_window_set_application_id")); if (gdk_wl_set_app_id) gdk_wl_set_app_id(gdkWindow, applicationId); } #endif // BOOST_OS_LINUX && HAS_WAYLAND ================================================ FILE: src/gui/wxgui/helpers/wxWayland.h ================================================ #pragma once #if ( BOOST_OS_LINUX || BOOST_OS_BSD ) && HAS_WAYLAND #include #include #include #include #include #include "wayland-viewporter-client-protocol.h" class wxWlSubsurface { static void registry_add_object(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { auto wlSubsurface = static_cast(data); if (!strcmp(interface, wl_subcompositor_interface.name)) { wlSubsurface->m_subcompositor = static_cast(wl_registry_bind(registry, name, &wl_subcompositor_interface, 1)); } else if (!strcmp(interface, wp_viewporter_interface.name)) { wlSubsurface->m_viewporter = static_cast(wl_registry_bind(registry, name, &wp_viewporter_interface, 1)); } } static void registry_remove_object(void* /*data*/, struct wl_registry* /*registry*/, uint32_t /*name*/) {} wl_registry_listener m_registry_listener = {®istry_add_object, ®istry_remove_object}; wl_subcompositor* m_subcompositor; wl_surface* m_surface; wl_subsurface* m_subsurface; wp_viewporter* m_viewporter = NULL; wp_viewport* m_viewport = NULL; int32_t m_xPos = 0; int32_t m_yPos = 0; int32_t m_width = 0; int32_t m_height = 0; public: wxWlSubsurface(wxWindow* window) { GtkWidget* widget = static_cast(window->GetHandle()); gtk_widget_realize(widget); GdkWindow* gdkWindow = gtk_widget_get_window(widget); GdkDisplay* gdkDisplay = gdk_window_get_display(gdkWindow); wl_display* display = gdk_wayland_display_get_wl_display(gdkDisplay); wl_surface* surface = gdk_wayland_window_get_wl_surface(gdkWindow); wl_compositor* compositor = gdk_wayland_display_get_wl_compositor(gdkDisplay); wl_registry* registry = wl_display_get_registry(display); wl_registry_add_listener(registry, &m_registry_listener, this); wl_display_roundtrip(display); m_surface = wl_compositor_create_surface(compositor); if (m_viewporter) m_viewport = wp_viewporter_get_viewport(m_viewporter, m_surface); wl_region* region = wl_compositor_create_region(compositor); wl_surface_set_input_region(m_surface, region); m_subsurface = wl_subcompositor_get_subsurface(m_subcompositor, m_surface, surface); wl_subsurface_set_desync(m_subsurface); auto sRect = window->GetScreenRect(); setSize(sRect.x, sRect.y, sRect.width, sRect.height); wl_region_destroy(region); } wl_surface* getSurface() const { return m_surface; } void setSize(int32_t xPos, int32_t yPos, int32_t width, int32_t height) { if (xPos != m_xPos || m_yPos != yPos || m_width != width || m_height != height) { m_xPos = xPos; m_yPos = yPos; m_height = height; m_width = width; wl_subsurface_set_position(m_subsurface, m_xPos, m_yPos); if (m_viewport) wp_viewport_set_destination(m_viewport, m_width, m_height); wl_surface_commit(m_surface); } } ~wxWlSubsurface() { wl_subsurface_destroy(m_subsurface); wl_surface_destroy(m_surface); wl_subcompositor_destroy(m_subcompositor); } }; bool wxWlIsWaylandWindow(wxWindow* window); void wxWlSetAppId(wxFrame* frame, const char* application_id); #endif // BOOST_OS_LINUX && HAS_WAYLAND ================================================ FILE: src/gui/wxgui/input/HotkeySettings.cpp ================================================ #include "wxgui/input/HotkeySettings.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "interface/WindowSystem.h" #include #include "input/InputManager.h" #include "HotkeySettings.h" #include "MainWindow.h" #include #include #if BOOST_OS_WINDOWS #include #endif #if BOOST_OS_LINUX || BOOST_OS_MACOS || BOOST_OS_BSD #include "resource/embedded/resources.h" #endif std::optional GenerateScreenshotFilename(bool isDRC) { fs::path screendir = ActiveSettings::GetUserDataPath("screenshots"); // build screenshot name with format Screenshot_YYYY-MM-DD_HH-MM-SS[_GamePad].png // if the file already exists add a suffix counter (_2.png, _3.png etc) std::time_t time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); std::tm* tm = std::localtime(&time_t); std::string screenshotFileName = fmt::format("Screenshot_{:04}-{:02}-{:02}_{:02}-{:02}-{:02}", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); if (isDRC) screenshotFileName.append("_GamePad"); fs::path screenshotPath; for (sint32 i = 0; i < 999; i++) { screenshotPath = screendir; if (i == 0) screenshotPath.append(fmt::format("{}.png", screenshotFileName)); else screenshotPath.append(fmt::format("{}_{}.png", screenshotFileName, i + 1)); std::error_code ec; bool exists = fs::exists(screenshotPath, ec); if (!ec && !exists) return screenshotPath; } return std::nullopt; } bool SaveScreenshotToFile(const fs::path& imagePath, const wxImage& image) { std::error_code ec; fs::create_directories(imagePath.parent_path(), ec); if (ec) return false; // suspend wxWidgets logging for the lifetime this object, to prevent a message box if wxImage::SaveFile fails wxLogNull _logNo; return image.SaveFile(imagePath.wstring()); } bool SaveScreenshotToClipboard(const wxImage& image) { static std::mutex s_clipboardMutex; bool success = false; s_clipboardMutex.lock(); if (wxTheClipboard->Open()) { wxTheClipboard->SetData(new wxImageDataObject(image)); wxTheClipboard->Close(); success = true; } s_clipboardMutex.unlock(); return success; } std::optional SaveScreenshot(std::vector data, int width, int height, bool mainWindow) { #if BOOST_OS_WINDOWS // on Windows wxWidgets uses OLE API for the clipboard // to make this work we need to call OleInitialize() on the same thread OleInitialize(nullptr); #endif bool save_screenshot = GetWxGUIConfig().save_screenshot; wxImage image(width, height, data.data(), true); if (mainWindow) { if (SaveScreenshotToClipboard(image)) { if (!save_screenshot) return _tr("Screenshot saved to clipboard"); } else { return _tr("Failed to open clipboard"); } } if (save_screenshot) { auto imagePath = GenerateScreenshotFilename(mainWindow); if (imagePath.has_value() && SaveScreenshotToFile(imagePath.value(), image)) { if (mainWindow) return _tr("Screenshot saved"); } else { return _tr("Failed to save screenshot to file"); } } return std::nullopt; } extern WindowSystem::WindowInfo g_window_info; std::unordered_map> HotkeySettings::s_cfgHotkeyToFuncMap; struct HotkeyEntry { std::unique_ptr name; std::unique_ptr keyInput; std::unique_ptr controllerInput; sHotkeyCfg& hotkey; HotkeyEntry(wxStaticText* name, wxButton* keyInput, wxButton* controllerInput, sHotkeyCfg& hotkey) : name(name), keyInput(keyInput), controllerInput(controllerInput), hotkey(hotkey) { keyInput->SetClientData(&hotkey); controllerInput->SetClientData(&hotkey); } }; HotkeySettings::HotkeySettings(wxWindow* parent) : wxFrame(parent, wxID_ANY, _("Hotkey Settings")) { SetIcon(wxICON(X_HOTKEY_SETTINGS)); m_sizer = new wxFlexGridSizer(0, 3, 5, 5); m_sizer->AddGrowableCol(1); m_sizer->AddGrowableCol(2); m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_THEME); m_panel->SetSizer(m_sizer); Center(); SetActiveController(); CreateColumnHeaders(); /* global modifier */ CreateHotkeyRow(_tr("Hotkey modifier"), s_cfgHotkeys.modifiers); m_hotkeys.at(0).keyInput->Hide(); /* hotkeys */ CreateHotkeyRow(_tr("Toggle fullscreen"), s_cfgHotkeys.toggleFullscreen); CreateHotkeyRow(_tr("Take screenshot"), s_cfgHotkeys.takeScreenshot); CreateHotkeyRow(_tr("Toggle fast-forward"), s_cfgHotkeys.toggleFastForward); #ifdef CEMU_DEBUG_ASSERT CreateHotkeyRow(_tr("End emulation"), s_cfgHotkeys.endEmulation); #endif CreateHotkeyRow(_tr("Exit application"), s_cfgHotkeys.exitApplication); m_controllerTimer = new wxTimer(this); Bind(wxEVT_TIMER, &HotkeySettings::OnControllerTimer, this); m_sizer->SetSizeHints(this); } HotkeySettings::~HotkeySettings() { m_controllerTimer->Stop(); if (m_needToSave) { GetConfigHandle().Save(); } } void HotkeySettings::Init(MainWindow* mainWindowFrame) { s_cfgHotkeyToFuncMap.insert({ {&s_cfgHotkeys.toggleFullscreen, [](void) { s_mainWindow->SetFullScreen(!s_mainWindow->IsFullScreen()); }}, {&s_cfgHotkeys.toggleFullscreenAlt, [](void) { s_mainWindow->SetFullScreen(!s_mainWindow->IsFullScreen()); }}, {&s_cfgHotkeys.exitFullscreen, [](void) { s_mainWindow->SetFullScreen(false); }}, {&s_cfgHotkeys.takeScreenshot, [](void) { if (g_renderer) g_renderer->RequestScreenshot(SaveScreenshot); }}, {&s_cfgHotkeys.toggleFastForward, [](void) { ActiveSettings::SetTimerShiftFactor((ActiveSettings::GetTimerShiftFactor() < 3) ? 3 : 1); }}, {&s_cfgHotkeys.exitApplication, [](void) { auto closeEvent = new wxCloseEvent{wxEVT_CLOSE_WINDOW, s_mainWindow->GetId()}; closeEvent->SetCanVeto(false); wxQueueEvent(s_mainWindow, closeEvent); }}, #ifdef CEMU_DEBUG_ASSERT {&s_cfgHotkeys.endEmulation, [](void) { wxTheApp->CallAfter([]() { s_mainWindow->EndEmulation(); }); }}, #endif }); s_keyboardHotkeyToFuncMap.reserve(s_cfgHotkeyToFuncMap.size()); for (const auto& [cfgHotkey, func] : s_cfgHotkeyToFuncMap) { auto keyboardHotkey = cfgHotkey->keyboard.raw; if (keyboardHotkey > sHotkeyCfg::keyboardNone) { s_keyboardHotkeyToFuncMap[keyboardHotkey] = func; } auto controllerHotkey = cfgHotkey->controller; if (controllerHotkey > sHotkeyCfg::controllerNone) { s_controllerHotkeyToFuncMap[controllerHotkey] = func; } } s_mainWindow = mainWindowFrame; } void HotkeySettings::CreateColumnHeaders(void) { auto* emptySpace = new wxStaticText(m_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL); auto* keyboard = new wxStaticText(m_panel, wxID_ANY, _tr("Keyboard"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL); auto* controller = new wxStaticText(m_panel, wxID_ANY, _tr("Controller"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL); keyboard->SetFont(keyboard->GetFont().Bold().Larger()); controller->SetFont(controller->GetFont().Bold().Larger()); keyboard->SetMinSize(m_minButtonSize); controller->SetMinSize(m_minButtonSize); auto flags = wxSizerFlags().Expand().Border(wxTOP, 10); m_sizer->Add(emptySpace, flags); m_sizer->Add(keyboard, flags); m_sizer->Add(controller, flags); } void HotkeySettings::CreateHotkeyRow(const wxString& label, sHotkeyCfg& cfgHotkey) { auto* name = new wxStaticText(m_panel, wxID_ANY, label); auto* keyInput = new wxButton(m_panel, wxID_ANY, To_wxString(cfgHotkey.keyboard), wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBU_EXACTFIT); auto* controllerInput = new wxButton(m_panel, wxID_ANY, To_wxString(cfgHotkey.controller), wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBU_EXACTFIT); /* for starting input */ keyInput->Bind(wxEVT_BUTTON, &HotkeySettings::OnKeyboardHotkeyInputLeftClick, this); controllerInput->Bind(wxEVT_BUTTON, &HotkeySettings::OnControllerHotkeyInputLeftClick, this); /* for cancelling and clearing input */ keyInput->Bind(wxEVT_RIGHT_UP, &HotkeySettings::OnKeyboardHotkeyInputRightClick, this); controllerInput->Bind(wxEVT_RIGHT_UP, &HotkeySettings::OnControllerHotkeyInputRightClick, this); keyInput->SetMinSize(m_minButtonSize); controllerInput->SetMinSize(m_minButtonSize); const wxColour inputButtonColor = GetBackgroundColour(); keyInput->SetBackgroundColour(inputButtonColor); controllerInput->SetBackgroundColour(inputButtonColor); auto flags = wxSizerFlags(1).Expand().Border(wxALL, 5).CenterVertical(); m_sizer->Add(name, flags); m_sizer->Add(keyInput, flags); m_sizer->Add(controllerInput, flags); m_hotkeys.emplace_back(name, keyInput, controllerInput, cfgHotkey); } void HotkeySettings::OnControllerTimer(wxTimerEvent& event) { if (m_activeController.expired()) { m_controllerTimer->Stop(); return; } auto& controller = *m_activeController.lock(); auto buttons = controller.update_state().buttons; if (!buttons.IsIdle()) { for (const auto& newHotkey : buttons.GetButtonList()) { m_controllerTimer->Stop(); auto* inputButton = static_cast(m_controllerTimer->GetClientData()); auto& cfgHotkey = *static_cast(inputButton->GetClientData()); const auto oldHotkey = cfgHotkey.controller; const bool isModifier = (&cfgHotkey == &s_cfgHotkeys.modifiers); /* ignore same hotkeys and block duplicate hotkeys */ if ((newHotkey != oldHotkey) && (isModifier || (newHotkey != s_cfgHotkeys.modifiers.controller)) && (s_controllerHotkeyToFuncMap.find(newHotkey) == s_controllerHotkeyToFuncMap.end())) { m_needToSave |= true; cfgHotkey.controller = newHotkey; /* don't bind modifier to map */ if (!isModifier) { s_controllerHotkeyToFuncMap.erase(oldHotkey); s_controllerHotkeyToFuncMap[newHotkey] = s_cfgHotkeyToFuncMap.at(&cfgHotkey); } } FinalizeInput(inputButton); return; } } } void HotkeySettings::OnKeyboardHotkeyInputLeftClick(wxCommandEvent& event) { auto* inputButton = static_cast(event.GetEventObject()); if (m_activeInputButton) { /* ignore multiple clicks of the same button */ if (inputButton == m_activeInputButton) return; RestoreInputButton(); } inputButton->Bind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this); inputButton->SetLabelText(m_editModeHotkeyText); m_activeInputButton = inputButton; } void HotkeySettings::OnControllerHotkeyInputLeftClick(wxCommandEvent& event) { auto* inputButton = static_cast(event.GetEventObject()); if (m_activeInputButton) { /* ignore multiple clicks of the same button */ if (inputButton == m_activeInputButton) return; RestoreInputButton(); } m_controllerTimer->Stop(); if (!SetActiveController()) { return; } inputButton->SetLabelText(m_editModeHotkeyText); m_controllerTimer->SetClientData(inputButton); m_controllerTimer->Start(25); m_activeInputButton = inputButton; } void HotkeySettings::OnKeyboardHotkeyInputRightClick(wxMouseEvent& event) { if (m_activeInputButton) { RestoreInputButton(); return; } auto* inputButton = static_cast(event.GetEventObject()); auto& cfgHotkey = *static_cast(inputButton->GetClientData()); uKeyboardHotkey newHotkey{sHotkeyCfg::keyboardNone}; if (cfgHotkey.keyboard.raw != newHotkey.raw) { m_needToSave |= true; s_keyboardHotkeyToFuncMap.erase(cfgHotkey.keyboard.raw); cfgHotkey.keyboard = newHotkey; FinalizeInput(inputButton); } } void HotkeySettings::OnControllerHotkeyInputRightClick(wxMouseEvent& event) { if (m_activeInputButton) { RestoreInputButton(); return; } auto* inputButton = static_cast(event.GetEventObject()); auto& cfgHotkey = *static_cast(inputButton->GetClientData()); ControllerHotkey_t newHotkey{sHotkeyCfg::controllerNone}; if (cfgHotkey.controller != newHotkey) { m_needToSave |= true; s_controllerHotkeyToFuncMap.erase(cfgHotkey.controller); cfgHotkey.controller = newHotkey; FinalizeInput(inputButton); } } bool HotkeySettings::SetActiveController(void) { auto emulatedController = InputManager::instance().get_controller(0); if (emulatedController.use_count() <= 1) { return false; } const auto& controllers = emulatedController->get_controllers(); if (controllers.empty()) { return false; } m_activeController = controllers.at(0); return true; } void HotkeySettings::OnKeyUp(wxKeyEvent& event) { auto* inputButton = static_cast(event.GetEventObject()); auto& cfgHotkey = *static_cast(inputButton->GetClientData()); if (auto keycode = event.GetKeyCode(); IsValidKeycodeUp(keycode)) { auto oldHotkey = cfgHotkey.keyboard; uKeyboardHotkey newHotkey{}; newHotkey.key = keycode; newHotkey.alt = event.AltDown(); newHotkey.ctrl = event.ControlDown(); newHotkey.shift = event.ShiftDown(); if ((newHotkey.raw != oldHotkey.raw) && (s_keyboardHotkeyToFuncMap.find(newHotkey.raw) == s_keyboardHotkeyToFuncMap.end())) { m_needToSave |= true; cfgHotkey.keyboard = newHotkey; s_keyboardHotkeyToFuncMap.erase(oldHotkey.raw); s_keyboardHotkeyToFuncMap[newHotkey.raw] = s_cfgHotkeyToFuncMap.at(&cfgHotkey); } } FinalizeInput(inputButton); } template void HotkeySettings::FinalizeInput(wxButton* inputButton) { auto& cfgHotkey = *static_cast(inputButton->GetClientData()); if constexpr (std::is_same_v) { inputButton->Unbind(wxEVT_KEY_UP, &HotkeySettings::OnKeyUp, this); inputButton->SetLabelText(To_wxString(cfgHotkey.keyboard)); } else if constexpr (std::is_same_v) { inputButton->SetLabelText(To_wxString(cfgHotkey.controller)); } m_activeInputButton = nullptr; } template void HotkeySettings::RestoreInputButton(void) { FinalizeInput(m_activeInputButton); } bool HotkeySettings::IsValidKeycodeUp(int keycode) { switch (keycode) { case WXK_NONE: case WXK_ESCAPE: case WXK_ALT: case WXK_CONTROL: case WXK_SHIFT: return false; default: return true; } } wxString HotkeySettings::To_wxString(uKeyboardHotkey hotkey) { if (hotkey.raw == sHotkeyCfg::keyboardNone) { return m_disabledHotkeyText; } wxString ret{}; if (hotkey.alt) { ret.append(_("Alt + ")); } if (hotkey.ctrl) { ret.append(_("Ctrl + ")); } if (hotkey.shift) { ret.append(_("Shift + ")); } ret.append(wxAcceleratorEntry(0, hotkey.key).ToString()); return ret; } wxString HotkeySettings::To_wxString(ControllerHotkey_t hotkey) { if ((hotkey == sHotkeyCfg::controllerNone) || m_activeController.expired()) { return m_disabledHotkeyText; } return m_activeController.lock()->get_button_name(hotkey); } void HotkeySettings::CaptureInput(wxKeyEvent& event) { uKeyboardHotkey hotkey{}; hotkey.key = event.GetKeyCode(); hotkey.alt = event.AltDown(); hotkey.ctrl = event.ControlDown(); hotkey.shift = event.ShiftDown(); const auto it = s_keyboardHotkeyToFuncMap.find(hotkey.raw); if (it != s_keyboardHotkeyToFuncMap.end()) it->second(); } void HotkeySettings::CaptureInput(const ControllerState& currentState, const ControllerState& lastState) { const auto& modifier = s_cfgHotkeys.modifiers.controller; if ((modifier >= 0) && currentState.buttons.GetButtonState(modifier)) { for (const auto& buttonId : currentState.buttons.GetButtonList()) { const auto it = s_controllerHotkeyToFuncMap.find(buttonId); if (it == s_controllerHotkeyToFuncMap.end()) continue; /* only capture clicks */ if (lastState.buttons.GetButtonState(buttonId)) break; it->second(); break; } } } ================================================ FILE: src/gui/wxgui/input/HotkeySettings.h ================================================ #pragma once #include #include "wxCemuConfig.h" #include "input/api/Controller.h" class HotkeyEntry; class HotkeySettings : public wxFrame { public: static void Init(class MainWindow* mainWindow); static void CaptureInput(wxKeyEvent& event); static void CaptureInput(const ControllerState& currentState, const ControllerState& lastState); HotkeySettings(wxWindow* parent); ~HotkeySettings(); private: inline static class MainWindow* s_mainWindow = nullptr; static std::unordered_map> s_cfgHotkeyToFuncMap; inline static std::unordered_map> s_keyboardHotkeyToFuncMap{}; inline static std::unordered_map> s_controllerHotkeyToFuncMap{}; inline static auto& s_cfgHotkeys = GetWxGUIConfig().hotkeys; wxPanel* m_panel; wxFlexGridSizer* m_sizer; wxButton* m_activeInputButton{ nullptr }; wxTimer* m_controllerTimer{ nullptr }; const wxSize m_minButtonSize{ 250, 45 }; const wxString m_disabledHotkeyText{ _("----") }; const wxString m_editModeHotkeyText{ "" }; std::vector m_hotkeys; std::weak_ptr m_activeController{}; bool m_needToSave = false; /* helpers */ void CreateColumnHeaders(void); void CreateHotkeyRow(const wxString& label, sHotkeyCfg& cfgHotkey); wxString To_wxString(uKeyboardHotkey hotkey); wxString To_wxString(ControllerHotkey_t hotkey); bool IsValidKeycodeUp(int keycode); bool SetActiveController(void); template void FinalizeInput(wxButton* inputButton); template void RestoreInputButton(void); /* events */ void OnKeyboardHotkeyInputLeftClick(wxCommandEvent& event); void OnControllerHotkeyInputLeftClick(wxCommandEvent& event); void OnKeyboardHotkeyInputRightClick(wxMouseEvent& event); void OnControllerHotkeyInputRightClick(wxMouseEvent& event); void OnKeyUp(wxKeyEvent& event); void OnControllerTimer(wxTimerEvent& event); }; ================================================ FILE: src/gui/wxgui/input/InputAPIAddWindow.cpp ================================================ #include "wxgui/input/InputAPIAddWindow.h" #include "input/InputManager.h" #include "wxgui/helpers/wxCustomData.h" #include "wxgui/helpers/wxHelpers.h" #include "input/api/Controller.h" #include #include #include #include #include #include #include #include #include "input/ControllerFactory.h" wxDEFINE_EVENT(wxControllersRefreshed, wxCommandEvent); using wxTypeData = wxCustomData; using wxControllerData = wxCustomData; InputAPIAddWindow::InputAPIAddWindow(wxWindow* parent, const wxPoint& position, const std::vector& controllers) : wxDialog(parent, wxID_ANY, "Add input API", position, wxDefaultSize, wxCAPTION), m_controllers(controllers) { this->SetSizeHints(wxDefaultSize, wxDefaultSize); auto* sizer = new wxBoxSizer(wxVERTICAL); { auto* api_row = new wxFlexGridSizer(2); // API api_row->Add(new wxStaticText(this, wxID_ANY, _("API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_input_api = new wxChoice(this, wxID_ANY); auto& providers = InputManager::instance().get_api_providers(); for (const auto& p : providers) { if (p.empty()) continue; const auto provider = *p.begin(); m_input_api->Append(wxString::FromUTF8(provider->api_name()), new wxTypeData(provider->api())); } m_input_api->Bind(wxEVT_CHOICE, &InputAPIAddWindow::on_api_selected, this); api_row->Add(m_input_api, 1, wxALL | wxEXPAND, 5); // Controller api_row->Add(new wxStaticText(this, wxID_ANY, _("Controller")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_controller_list = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY); m_controller_list->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputAPIAddWindow::on_controller_dropdown, this); m_controller_list->Bind(wxEVT_COMBOBOX, &InputAPIAddWindow::on_controller_selected, this); m_controller_list->SetMinSize(wxSize(240, -1)); m_controller_list->Disable(); api_row->Add(m_controller_list, 1, wxALL | wxEXPAND, 5); sizer->Add(api_row, 0, wxEXPAND, 5); } sizer->Add(new wxStaticLine(this), 0, wxEXPAND); { auto* end_row = new wxBoxSizer(wxHORIZONTAL); m_ok_button = new wxButton(this, wxID_ANY, _("Add")); m_ok_button->Bind(wxEVT_BUTTON, &InputAPIAddWindow::on_add_button, this); m_ok_button->Disable(); end_row->Add(m_ok_button, 0, wxALL, 5); auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel")); cancel_button->Bind(wxEVT_BUTTON, &InputAPIAddWindow::on_close_button, this); end_row->Add(cancel_button, 0, wxALL, 5); sizer->Add(end_row, 0, wxEXPAND, 5); } { // optional settings m_settings_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); auto* panel_sizer = new wxBoxSizer(wxVERTICAL); panel_sizer->Add(new wxStaticLine(m_settings_panel), 0, wxEXPAND, 0); { auto* row = new wxBoxSizer(wxHORIZONTAL); // we only have dsu settings atm, so add elements now row->Add(new wxStaticText(m_settings_panel, wxID_ANY, _("IP")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_ip = new wxTextCtrl(m_settings_panel, wxID_ANY, "127.0.0.1"); row->Add(m_ip, 0, wxALL, 5); row->Add(new wxStaticText(m_settings_panel, wxID_ANY, _("Port")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); m_port = new wxTextCtrl(m_settings_panel, wxID_ANY, "26760"); row->Add(m_port, 0, wxALL, 5); panel_sizer->Add(row, 0, wxEXPAND); } m_settings_panel->SetSizer(panel_sizer); m_settings_panel->Layout(); m_settings_panel->Hide(); sizer->Add(m_settings_panel, 1, wxEXPAND); } this->SetSizer(sizer); this->Layout(); sizer->Fit(this); this->Bind(wxControllersRefreshed, &InputAPIAddWindow::on_controllers_refreshed, this); } InputAPIAddWindow::~InputAPIAddWindow() { discard_thread_result(); } void InputAPIAddWindow::on_add_button(wxCommandEvent& event) { const auto selection = m_input_api->GetSelection(); if (selection == wxNOT_FOUND) { cemu_assert_debug(false); EndModal(wxID_CANCEL); return; } for (const auto& c : m_controllers) { if (*c == *m_controller) { wxMessageBox(_("The controller is already added!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; } } m_type = static_cast(m_input_api->GetClientObject(selection))->get(); EndModal(wxID_OK); } void InputAPIAddWindow::on_close_button(wxCommandEvent& event) { EndModal(wxID_CANCEL); } bool InputAPIAddWindow::has_custom_settings() const { const auto selection = m_input_api->GetStringSelection(); return selection == wxString::FromUTF8(to_string(InputAPI::DSUClient)); } std::unique_ptr InputAPIAddWindow::get_settings() const { if (!has_custom_settings()) return {}; return std::make_unique(m_ip->GetValue().ToStdString(), ConvertString(m_port->GetValue().ToStdString())); } void InputAPIAddWindow::on_api_selected(wxCommandEvent& event) { discard_thread_result(); if (m_input_api->GetSelection() == wxNOT_FOUND) return; m_controller_list->Enable(); m_controller_list->SetSelection(wxNOT_FOUND); const auto selection = m_input_api->GetStringSelection(); // keyboard is a special case, as theres only one device supported atm if (selection == wxString::FromUTF8(to_string(InputAPI::Keyboard))) { const auto controllers = InputManager::instance().get_api_provider(InputAPI::Keyboard)->get_controllers(); if (!controllers.empty()) { m_controller = controllers[0]; m_ok_button->Enable(); m_controller_list->Clear(); const auto display_name = controllers[0]->display_name(); const auto index = m_controller_list->Append(display_name, new wxCustomData(controllers[0])); m_controller_list->SetSelection(index); } } else { #if BOOST_OS_LINUX || BOOST_OS_BSD // We rely on the wxEVT_COMBOBOX_DROPDOWN event to trigger filling the controller list, // but on wxGTK the dropdown button cannot be clicked if the list is empty // so as a quick and dirty workaround we fill the list here wxCommandEvent tmpCmdEvt; on_controller_dropdown(tmpCmdEvt); #endif } const auto show_settings = has_custom_settings(); // dsu has special settings for ip/port if (show_settings != m_settings_panel->IsShown()) { wxWindowUpdateLocker locker(this); m_settings_panel->Show(show_settings); Layout(); Fit(); } } void InputAPIAddWindow::on_controller_dropdown(wxCommandEvent& event) { if (m_search_running) return; int selection = m_input_api->GetSelection(); if (selection == wxNOT_FOUND) return; const auto type = static_cast(m_input_api->GetClientObject(selection))->get(); auto settings = get_settings(); ControllerProviderPtr provider; if (settings) provider = InputManager::instance().get_api_provider(type, *settings); else provider = InputManager::instance().get_api_provider(type); if (!provider) return; std::string selected_uuid; selection = m_controller_list->GetSelection(); if (selection != wxNOT_FOUND) { // TODO selected_uuid } m_search_running = true; wxWindowUpdateLocker lock(m_controller_list); m_controller_list->Clear(); m_controller_list->Append(_("Searching for controllers..."), (wxClientData*)nullptr); m_controller_list->SetSelection(wxNOT_FOUND); m_search_thread_data = std::make_unique(); std::thread([this, provider, selected_uuid](std::shared_ptr data) { auto available_controllers = provider->get_controllers(); { std::lock_guard lock{data->mutex}; if(!data->discardResult) { wxCommandEvent event(wxControllersRefreshed); event.SetEventObject(m_controller_list); event.SetClientObject(new wxCustomData(std::move(available_controllers))); event.SetInt(provider->api()); event.SetString(selected_uuid); wxPostEvent(this, event); m_search_running = false; } } }, m_search_thread_data).detach(); } void InputAPIAddWindow::on_controller_selected(wxCommandEvent& event) { if (m_search_running) { return; } const auto selection = m_controller_list->GetSelection(); if (selection == wxNOT_FOUND) { return; } if (auto* controller = (wxControllerData*)m_controller_list->GetClientObject(selection)) { m_controller = controller->ref(); m_ok_button->Enable(); } } void InputAPIAddWindow::on_controllers_refreshed(wxCommandEvent& event) { const auto type = event.GetInt(); wxASSERT(0 <= type && type < InputAPI::MAX); auto* controllers = dynamic_cast(event.GetEventObject()); wxASSERT(controllers); const auto available_controllers = static_cast>>*>(event. GetClientObject())->get(); const auto selected_uuid = event.GetString().ToStdString(); bool item_selected = false; wxWindowUpdateLocker lock(controllers); controllers->Clear(); for (const auto& c : available_controllers) { const auto display_name = c->display_name(); const auto uuid = c->uuid(); const auto index = controllers->Append(display_name, new wxCustomData(c)); if (!item_selected && selected_uuid == uuid) { controllers->SetSelection(index); item_selected = true; } } } void InputAPIAddWindow::discard_thread_result() { m_search_running = false; if(m_search_thread_data) { std::lock_guard lock{m_search_thread_data->mutex}; m_search_thread_data->discardResult = true; } } ================================================ FILE: src/gui/wxgui/input/InputAPIAddWindow.h ================================================ #pragma once #include "input/api/InputAPI.h" #include #include #include #include "wxgui/helpers/wxCustomData.h" #include "input/api/Controller.h" class wxComboBox; class wxChoice; class wxTextCtrl; using wxAPIType = wxCustomData; class InputAPIAddWindow : public wxDialog { public: InputAPIAddWindow(wxWindow* parent, const wxPoint& position, const std::vector& controllers); ~InputAPIAddWindow(); bool is_valid() const { return m_type.has_value() && m_controller != nullptr; } InputAPI::Type get_type() const { return m_type.value(); } std::shared_ptr get_controller() const { return m_controller; } bool has_custom_settings() const; std::unique_ptr get_settings() const; private: void on_add_button(wxCommandEvent& event); void on_close_button(wxCommandEvent& event); void on_api_selected(wxCommandEvent& event); void on_controller_dropdown(wxCommandEvent& event); void on_controller_selected(wxCommandEvent& event); void on_controllers_refreshed(wxCommandEvent& event); void discard_thread_result(); wxChoice* m_input_api; wxComboBox* m_controller_list; wxButton* m_ok_button; wxPanel* m_settings_panel; wxTextCtrl* m_ip, * m_port; std::optional m_type; std::shared_ptr m_controller; std::vector m_controllers; std::atomic_bool m_search_running = false; struct AsyncThreadData { std::atomic_bool discardResult = false; std::mutex mutex; }; std::shared_ptr m_search_thread_data; }; ================================================ FILE: src/gui/wxgui/input/InputSettings2.cpp ================================================ #include "wxgui/input/InputSettings2.h" #include #include "input/InputManager.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/helpers/wxControlObject.h" #include "wxgui/helpers/wxCustomData.h" #include #include #include #include #include #include #include #include #include #include "config/ActiveSettings.h" #include "wxgui/input/InputAPIAddWindow.h" #include "input/ControllerFactory.h" #ifdef HAS_BLUEZ #include "wxgui/input/PairingDialog.h" #endif #include "wxgui/input/panels/VPADInputPanel.h" #include "wxgui/input/panels/ProControllerInputPanel.h" #include "wxgui/input/settings/DefaultControllerSettings.h" #include "wxgui/input/panels/ClassicControllerInputPanel.h" #include "wxgui/input/panels/WiimoteInputPanel.h" #include "wxgui/input/settings/WiimoteControllerSettings.h" #include "util/EventService.h" #include "resource/embedded/resources.h" bool g_inputConfigWindowHasFocus = false; using wxTypeData = wxCustomData; using wxControllerData = wxCustomData; struct ControllerPage { EmulatedControllerPtr m_controller; // profiles wxComboBox* m_profiles; wxButton* m_profile_load, * m_profile_save, * m_profile_delete; wxStaticText* m_profile_status; // emulated controller wxComboBox* m_emulated_controller; // controller api wxComboBox* m_controllers; wxButton* m_controller_api_add, *m_controller_api_remove; wxButton* m_controller_settings, * m_controller_calibrate, *m_controller_clear; wxBitmapButton* m_controller_connected; // panel std::array m_panels{}; }; using wxControllerPageData = wxCustomData; InputSettings2::InputSettings2(wxWindow* parent) : wxDialog(parent, wxID_ANY, _("Input settings")) { this->SetSizeHints(wxDefaultSize, wxDefaultSize); g_inputConfigWindowHasFocus = true; m_connected = wxHelper::LoadThemedBitmapFromPNG(INPUT_CONNECTED_png, sizeof(INPUT_CONNECTED_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); m_disconnected = wxHelper::LoadThemedBitmapFromPNG(INPUT_DISCONNECTED_png, sizeof(INPUT_DISCONNECTED_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); m_low_battery = wxHelper::LoadThemedBitmapFromPNG(INPUT_LOW_BATTERY_png, sizeof(INPUT_LOW_BATTERY_png), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); auto* sizer = new wxBoxSizer(wxVERTICAL); m_notebook = new wxNotebook(this, wxID_ANY); for(size_t i = 0; i < InputManager::kMaxController; ++i) { auto* page = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); page->SetClientObject(nullptr); // force internal type to client object m_notebook->AddPage(page, formatWxString(_("Controller {}"), i + 1)); } m_notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &InputSettings2::on_controller_page_changed, this); sizer->Add(m_notebook, 1, wxEXPAND); m_notebook->SetSelection(0); auto* first_page = initialize_page(0); // init first/default page for fitting size auto* page_data = (wxControllerPageData*)first_page->GetClientObject(); auto* panel = new VPADInputPanel(first_page); page_data->ref().m_panels[EmulatedController::Type::VPAD] = panel; auto* first_page_sizer = dynamic_cast(first_page->GetSizer()); auto* panel_sizer = first_page_sizer->FindItemAtPosition(wxGBPosition(7, 0))->GetSizer(); panel_sizer->Add(panel, 0, wxEXPAND); panel->Show(); first_page->Layout(); SetSizer(sizer); Layout(); Fit(); panel->Hide(); update_state(); Bind(wxEVT_TIMER, &InputSettings2::on_timer, this); m_timer = new wxTimer(this); m_timer->Start(25); m_controller_changed = EventService::instance().connect(&InputSettings2::on_controller_changed, this); } InputSettings2::~InputSettings2() { m_controller_changed.disconnect(); g_inputConfigWindowHasFocus = false; m_timer->Stop(); InputManager::instance().save(); } wxWindow* InputSettings2::initialize_page(size_t index) { auto* page = m_notebook->GetPage(index); if (page->GetClientObject()) // already initialized return page; page->Bind(wxEVT_LEFT_UP, &InputSettings2::on_left_click, this); ControllerPage page_data{}; const auto emulated_controller = InputManager::instance().get_controller(index); page_data.m_controller = emulated_controller; wxWindowUpdateLocker lock(page); auto* sizer = new wxGridBagSizer(); { // profile sizer->Add(new wxStaticText(page, wxID_ANY, _("Profile")), wxGBPosition(0, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* profiles = new wxComboBox(page, wxID_ANY, kDefaultProfileName); sizer->Add(profiles, wxGBPosition(0, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 5); #if BOOST_OS_LINUX || BOOST_OS_BSD // We rely on the wxEVT_COMBOBOX_DROPDOWN event to trigger filling the profile list, // but on wxGTK the dropdown button cannot be clicked if the list is empty // so as a quick and dirty workaround we fill the list here wxCommandEvent tmpCmdEvt; tmpCmdEvt.SetEventObject(profiles); on_profile_dropdown(tmpCmdEvt); #endif if (emulated_controller && emulated_controller->has_profile_name()) { profiles->SetValue(emulated_controller->get_profile_name()); } auto* load_bttn = new wxButton(page, wxID_ANY, _("Load")); load_bttn->Disable(); sizer->Add(load_bttn, wxGBPosition(0, 2), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* save_bttn = new wxButton(page, wxID_ANY, _("Save")); save_bttn->Disable(); sizer->Add(save_bttn, wxGBPosition(0, 3), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* delete_bttn = new wxButton(page, wxID_ANY, _("Delete")); delete_bttn->Disable(); sizer->Add(delete_bttn, wxGBPosition(0, 4), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* profile_status = new wxStaticText(page, wxID_ANY, _("controller set by gameprofile. changes won't be saved permanently!")); profile_status->SetMinSize(wxSize(200, -1)); profile_status->Wrap(200); sizer->Add(profile_status, wxGBPosition(0, 5), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxRESERVE_SPACE_EVEN_IF_HIDDEN, 5); if(InputManager::instance().is_gameprofile_set(index)) { profile_status->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); } else { profile_status->Hide(); } load_bttn->Bind(wxEVT_BUTTON, &InputSettings2::on_profile_load, this); save_bttn->Bind(wxEVT_BUTTON, &InputSettings2::on_profile_save, this); delete_bttn->Bind(wxEVT_BUTTON, &InputSettings2::on_profile_delete, this); profiles->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputSettings2::on_profile_dropdown, this); profiles->Bind(wxEVT_TEXT, &InputSettings2::on_profile_text_changed, this); page_data.m_profiles = profiles; page_data.m_profile_load = load_bttn; page_data.m_profile_save = save_bttn; page_data.m_profile_delete = delete_bttn; page_data.m_profile_status = profile_status; } sizer->Add(new wxStaticLine(page), wxGBPosition(1, 0), wxGBSpan(1, 6), wxEXPAND); { // emulated controller sizer->Add(new wxStaticText(page, wxID_ANY, _("Emulated controller")), wxGBPosition(2, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* econtroller_box = new wxComboBox(page, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY); econtroller_box->SetMinSize(wxSize(200, -1)); econtroller_box->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputSettings2::on_emulated_controller_dropdown, this); econtroller_box->Bind(wxEVT_COMBOBOX, &InputSettings2::on_emulated_controller_selected, this); econtroller_box->AppendString(_("Disabled")); econtroller_box->SetSelection(0); sizer->Add(econtroller_box, wxGBPosition(2, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 5); page_data.m_emulated_controller = econtroller_box; } sizer->Add(new wxStaticLine(page), wxGBPosition(3, 0), wxGBSpan(1, 6), wxEXPAND); { // controller api sizer->Add(new wxStaticText(page, wxID_ANY, _("Controller")), wxGBPosition(4, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); auto* controllers = new wxComboBox(page, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY); controllers->Bind(wxEVT_COMBOBOX, &InputSettings2::on_controller_selected, this); controllers->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputSettings2::on_controller_dropdown, this); controllers->SetMinSize(wxSize(300, -1)); page_data.m_controllers = controllers; sizer->Add(controllers, wxGBPosition(4, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 5); { // add/remove buttons auto* bttn_sizer = new wxBoxSizer(wxHORIZONTAL); auto* add_api = new wxButton(page, wxID_ANY, " + ", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); add_api->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_add, this); bttn_sizer->Add(add_api, 0, wxALL, 5); auto* remove_api = new wxButton(page, wxID_ANY, " - ", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); remove_api->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_remove, this); bttn_sizer->Add(remove_api, 0, wxALL, 5); sizer->Add(bttn_sizer, wxGBPosition(4, 2), wxDefaultSpan, wxEXPAND, 5); page_data.m_controller_api_add = add_api; page_data.m_controller_api_remove = remove_api; } #ifdef HAS_BLUEZ auto* pairingDialog = new wxButton(page, wxID_ANY, _("Pair Wii/Wii U Controller")); pairingDialog->Bind(wxEVT_BUTTON, [this](wxEvent&) { PairingDialog pairing_dialog(this); pairing_dialog.ShowModal(); }); sizer->Add(pairingDialog, wxGBPosition(5, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); #endif // controller auto* controller_bttns = new wxBoxSizer(wxHORIZONTAL); auto* settings = new wxButton(page, wxID_ANY, _("Settings")); settings->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_settings, this); settings->Disable(); controller_bttns->Add(settings, 0, wxALL, 5); auto* calibrate = new wxButton(page, wxID_ANY, _("Calibrate")); calibrate->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_calibrate, this); calibrate->Disable(); controller_bttns->Add(calibrate, 0, wxALL, 5); auto* clear = new wxButton(page, wxID_ANY, _("Clear")); clear->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_clear, this); controller_bttns->Add(clear, 0, wxALL, 5); sizer->Add(controller_bttns, wxGBPosition(5, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); auto* connected_button = new wxBitmapButton(page, wxID_ANY, m_disconnected); connected_button->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_connect, this); connected_button->SetToolTip(_("Test if the controller is connected")); sizer->Add(connected_button, wxGBPosition(5, 2), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); // TODO replace with icon page_data.m_controller_settings = settings; page_data.m_controller_calibrate = calibrate; page_data.m_controller_clear = clear; page_data.m_controller_connected = connected_button; } sizer->Add(new wxStaticLine(page), wxGBPosition(6, 0), wxGBSpan(1, 6), wxEXPAND); auto* panel_sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(panel_sizer, wxGBPosition(7, 0), wxGBSpan(1, 6), wxEXPAND | wxALL, 5); page->SetSizer(sizer); page->Layout(); page->SetClientObject(new wxCustomData(page_data)); return page; } std::shared_ptr InputSettings2::get_active_controller() const { auto& page_data = get_current_page_data(); const auto selection = page_data.m_controllers->GetSelection(); if(selection != wxNOT_FOUND) { if(auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(selection)) return controller->ref(); } return {}; } bool InputSettings2::has_settings(InputAPI::Type type) { switch(type) { case InputAPI::Keyboard: return false; default: return true; } } void InputSettings2::update_state() { auto* page = m_notebook->GetCurrentPage(); wxWindowUpdateLocker lock(page); auto* page_data_ptr = (wxControllerPageData*)page->GetClientObject(); wxASSERT(page_data_ptr); auto& page_data = page_data_ptr->ref(); page_data.m_profile_status->Hide(); EmulatedControllerPtr emulated_controller = page_data.m_controller; auto has_controllers = false; // update emulated if(emulated_controller) { has_controllers = !emulated_controller->get_controllers().empty(); const auto emulated_type = emulated_controller->type(); int index = page_data.m_emulated_controller->Append(wxString::FromUTF8(emulated_controller->type_to_string(emulated_type))); page_data.m_emulated_controller->SetSelection(index); const auto controller_selection = page_data.m_controllers->GetStringSelection(); page_data.m_controllers->Clear(); if (has_controllers) { for (const auto& c : emulated_controller->get_controllers()) { page_data.m_controllers->Append(fmt::format("{} [{}]", c->display_name(), c->api_name()), new wxCustomData(c)); } if (page_data.m_controllers->GetCount() > 0) { page_data.m_controllers->SetSelection(0); if (!controller_selection.empty()) page_data.m_controllers->SetStringSelection(controller_selection); } } } else { page_data.m_emulated_controller->SetValue(_("Disabled")); } ControllerPtr controller; if (page_data.m_controllers->GetSelection() != wxNOT_FOUND) { if (const auto data = (wxControllerData*)page_data.m_controllers->GetClientObject(page_data.m_controllers->GetSelection())) controller = data->ref(); } if (controller && controller->is_connected()) page_data.m_controller_connected->SetBitmap(m_connected); else page_data.m_controller_connected->SetBitmap(m_disconnected); // update controller page_data.m_controller_calibrate->Enable(has_controllers); page_data.m_controller_api_remove->Enable(has_controllers); page_data.m_controller_settings->Enable(controller && has_settings(controller->api())); // update settings // update panel // test if we need to update to correct panel std::optional active_api{}; for(auto i = 0; i < EmulatedController::Type::MAX; ++i) { if(page_data.m_panels[i] && page_data.m_panels[i]->IsShown()) { active_api = (EmulatedController::Type)i; break; } } // disabled and no emulated controller if (!active_api && !emulated_controller) return; // enabled correct panel for active controller if (active_api && emulated_controller && emulated_controller->type() == active_api.value()) { // same controller type panel already shown, refresh content of panels for (auto* panel : page_data.m_panels) { if (panel) panel->load_controller(page_data.m_controller); } return; } // hide all panels for (auto* panel : page_data.m_panels) { if (panel) panel->Hide(); } // show required panel if (emulated_controller) { auto* sizer = dynamic_cast(page->GetSizer()); wxASSERT(sizer); const auto type = page_data.m_controller->type(); InputPanel* panel = page_data.m_panels[type]; if (!panel) { switch (type) { case EmulatedController::Type::VPAD: panel = new VPADInputPanel(page); break; case EmulatedController::Pro: panel = new ProControllerInputPanel(page); break; case EmulatedController::Classic: panel = new ClassicControllerInputPanel(page); break; case EmulatedController::Wiimote: panel = new WiimoteInputPanel(page); break; default: cemu_assert_debug(false); return; } page_data.m_panels[type] = panel; auto* panel_sizer = sizer->FindItemAtPosition(wxGBPosition(7, 0))->GetSizer(); wxASSERT(panel_sizer); panel_sizer->Add(panel, 0, wxEXPAND); } panel->load_controller(page_data.m_controller); if (has_controllers) panel->set_selected_controller(emulated_controller, emulated_controller->get_controllers()[0]); panel->Show(); page->wxWindowBase::Layout(); page->wxWindow::Update(); } } void InputSettings2::on_controller_changed() { for(auto i = 0 ; i < m_notebook->GetPageCount(); ++i) { auto* page = m_notebook->GetPage(i); if (!page) continue; auto* page_data_ptr = (wxControllerPageData*)page->GetClientObject(); if (!page_data_ptr) continue; const auto& page_data = page_data_ptr->ref(); if (page_data.m_controllers->GetSelection() != wxNOT_FOUND) { if (const auto data = (wxControllerData*)page_data.m_controllers->GetClientObject(page_data.m_controllers->GetSelection())) { if (const auto controller = data->ref()) { if (controller->connect()) page_data.m_controller_connected->SetBitmap(m_connected); else page_data.m_controller_connected->SetBitmap(m_disconnected); } } } } } void InputSettings2::on_notebook_page_changed(wxBookCtrlEvent& event) { initialize_page(event.GetSelection()); update_state(); event.Skip(); } void InputSettings2::on_timer(wxTimerEvent&) { auto& page_data = get_current_page_data(); if (!page_data.m_controller) { return; } auto* panel = page_data.m_panels[page_data.m_controller->type()]; if (!panel) return; auto controller = get_active_controller(); if (controller) { panel->on_timer(page_data.m_controller, controller); } } void InputSettings2::on_left_click(wxMouseEvent& event) { event.Skip(); auto& page_data = get_current_page_data(); if (!page_data.m_controller) { return; } auto* panel = page_data.m_panels[page_data.m_controller->type()]; if (!panel) return; panel->on_left_click(event); } void InputSettings2::on_profile_dropdown(wxCommandEvent& event) { auto* profile_names = dynamic_cast(event.GetEventObject()); wxASSERT(profile_names); wxWindowUpdateLocker lock(profile_names); const auto selected_value = profile_names->GetStringSelection(); profile_names->Clear(); for(const auto& profile : InputManager::get_profiles()) { profile_names->Append(wxString::FromUTF8(profile)); } profile_names->SetStringSelection(selected_value); } void InputSettings2::on_profile_text_changed(wxCommandEvent& event) { auto* profile_names = dynamic_cast(event.GetEventObject()); wxASSERT(profile_names); auto& page_data = get_current_page_data(); // load_bttn, save_bttn, delete_bttn, profile_status const auto text = event.GetString(); const bool valid_name = InputManager::is_valid_profilename(text.utf8_string()); const bool name_exists = profile_names->FindString(text) != wxNOT_FOUND; page_data.m_profile_load->Enable(name_exists); page_data.m_profile_save->Enable(valid_name); page_data.m_profile_delete->Enable(name_exists); page_data.m_profile_status->Hide(); } void InputSettings2::on_profile_load(wxCommandEvent& event) { auto& page_data = get_current_page_data(); auto* profile_names = page_data.m_profiles; auto* text = page_data.m_profile_status; const auto selection = profile_names->GetValue().utf8_string(); text->Show(); if (selection.empty() || !InputManager::is_valid_profilename(selection)) { text->SetLabelText(_("invalid profile name")); text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); text->Refresh(); return; } const auto page_index = m_notebook->GetSelection(); if (InputManager::instance().load(page_index, selection)) { text->SetLabelText(_("profile loaded")); text->SetForegroundColour(wxTheColourDatabase->Find("SUCCESS")); } else { text->SetLabelText(_("couldn't load profile")); text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); } text->Refresh(); // update controller info page_data.m_controller = InputManager::instance().get_controller(page_index); update_state(); } void InputSettings2::on_profile_save(wxCommandEvent& event) { auto& page_data = get_current_page_data(); auto* profile_names = page_data.m_profiles; auto* text = page_data.m_profile_status; const auto selection = profile_names->GetValue().utf8_string(); text->Show(); if (selection.empty() || !InputManager::is_valid_profilename(selection)) { text->SetLabelText(_("invalid profile name")); text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); text->Refresh(); return; } if(InputManager::instance().save(m_notebook->GetSelection(), selection)) { text->SetLabelText(_("profile saved")); text->SetForegroundColour(wxTheColourDatabase->Find("SUCCESS")); } else { text->SetLabelText(_("couldn't save profile")); text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); } text->Refresh(); } void InputSettings2::on_profile_delete(wxCommandEvent& event) { auto& page_data = get_current_page_data(); auto* profile_names = page_data.m_profiles; auto* text = page_data.m_profile_status; const auto selection = profile_names->GetStringSelection().utf8_string(); text->Show(); if (selection.empty() || !InputManager::is_valid_profilename(selection)) { text->SetLabelText(_("invalid profile name")); text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); text->Refresh(); return; } try { const fs::path old_path = ActiveSettings::GetConfigPath("controllerProfiles/{}.txt", selection); fs::remove(old_path); const fs::path path = ActiveSettings::GetConfigPath("controllerProfiles/{}.xml", selection); fs::remove(path); profile_names->ChangeValue(kDefaultProfileName); text->SetLabelText(_("profile deleted")); text->SetForegroundColour(wxTheColourDatabase->Find("SUCCESS")); page_data.m_profile_load->Disable(); page_data.m_profile_save->Disable(); page_data.m_profile_delete->Disable(); } catch (const std::exception&) { text->SetLabelText(_("can't delete profile")); text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); } text->Refresh(); } void InputSettings2::on_controller_page_changed(wxBookCtrlEvent& event) { initialize_page(event.GetSelection()); update_state(); event.Skip(); } void InputSettings2::on_emulated_controller_selected(wxCommandEvent& event) { const auto page_index = m_notebook->GetSelection(); auto& page_data = get_current_page_data(); const auto selection = event.GetSelection(); if(selection == 0) // disabled selected { page_data.m_controller = {}; InputManager::instance().delete_controller(page_index, true); } else { try { const auto type = EmulatedController::type_from_string(event.GetString().utf8_string()); // same has already been selected if (page_data.m_controller && page_data.m_controller->type() == type) return; // set new controller const auto new_controller = InputManager::instance().set_controller(page_index, type); page_data.m_controller = new_controller; // append controllers if some were already added before if (new_controller->get_controllers().empty()) { // test if we had no emulated controller before but still assigned controllers we want to transfer now for (uint32 i = 0; i < page_data.m_controllers->GetCount(); ++i) { if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(i)) { new_controller->add_controller(controller->ref()); } } } // set default mappings if any controllers available for(const auto& c: new_controller->get_controllers()) { new_controller->set_default_mapping(c); } } catch (const std::exception&) { cemu_assert_debug(false); } } update_state(); } void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event) { auto* emulated_controllers = dynamic_cast(event.GetEventObject()); wxASSERT(emulated_controllers); wxWindowUpdateLocker lock(emulated_controllers); bool is_gamepad_selected = false; bool is_wpad_selected = false; const auto selected = emulated_controllers->GetSelection(); const auto selected_value = emulated_controllers->GetStringSelection(); if(selected != wxNOT_FOUND) { is_gamepad_selected = selected_value == wxString::FromUTF8(EmulatedController::type_to_string(EmulatedController::Type::VPAD)); is_wpad_selected = !is_gamepad_selected && selected != 0; } const auto [vpad_count, wpad_count] = InputManager::instance().get_controller_count(); emulated_controllers->Clear(); emulated_controllers->AppendString(_("Disabled")); if (vpad_count < InputManager::kMaxVPADControllers || is_gamepad_selected) emulated_controllers->Append(wxString::FromUTF8(EmulatedController::type_to_string(EmulatedController::Type::VPAD))); if (wpad_count < InputManager::kMaxWPADControllers || is_wpad_selected) { emulated_controllers->AppendString(wxString::FromUTF8(EmulatedController::type_to_string(EmulatedController::Type::Pro))); emulated_controllers->AppendString(wxString::FromUTF8(EmulatedController::type_to_string(EmulatedController::Type::Classic))); emulated_controllers->AppendString(wxString::FromUTF8(EmulatedController::type_to_string(EmulatedController::Type::Wiimote))); } emulated_controllers->SetStringSelection(selected_value); } void InputSettings2::on_controller_selected(wxCommandEvent& event) { auto& page_data = get_current_page_data(); const auto enabled = event.GetSelection() != wxNOT_FOUND; page_data.m_controller_api_remove->Enable(enabled); // page_data->ref().m_controller_list->Clear(); if(enabled) { // get selected controller if any todo if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(event.GetSelection())) { page_data.m_controller_settings->Enable(has_settings(controller->ref()->api())); if(page_data.m_controller) { page_data.m_panels[page_data.m_controller->type()]->set_selected_controller(page_data.m_controller, controller->ref()); } } } } void InputSettings2::on_controller_dropdown(wxCommandEvent& event) { if(auto* controllers = dynamic_cast(event.GetEventObject())) { if(controllers->GetCount()== 0) { on_controller_add(event); controllers->SetSelection(0); } } } ControllerPage& InputSettings2::get_current_page_data() const { auto* page = m_notebook->GetCurrentPage(); auto* page_data_ptr = (wxControllerPageData*)page->GetClientObject(); wxASSERT(page_data_ptr); return page_data_ptr->ref(); } void InputSettings2::on_controller_connect(wxCommandEvent& event) { auto& page_data = get_current_page_data(); if (page_data.m_controllers->GetSelection() != wxNOT_FOUND) { if (const auto data = (wxControllerData*)page_data.m_controllers->GetClientObject(page_data.m_controllers->GetSelection())) { if(const auto controller = data->ref()) { if(controller->connect()) page_data.m_controller_connected->SetBitmap(m_connected); else page_data.m_controller_connected->SetBitmap(m_disconnected); } } } } void InputSettings2::on_controller_add(wxCommandEvent& event) { auto& page_data = get_current_page_data(); std::vector controllers; controllers.reserve(page_data.m_controllers->GetCount()); for(uint32 i = 0; i < page_data.m_controllers->GetCount(); ++i) { if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(i)) controllers.emplace_back(controller->ref()); } InputAPIAddWindow wnd(this, wxGetMousePosition() + wxSize(5, 5), controllers); if (wnd.ShowModal() != wxID_OK) return; wxASSERT(wnd.is_valid()); const auto controller = wnd.get_controller(); const auto api_type = wnd.get_type(); controller->connect(); const int index = page_data.m_controllers->Append(fmt::format("{} [{}]", controller->display_name(), to_string(api_type)), new wxCustomData(controller)); page_data.m_controllers->Select(index); if(page_data.m_controller) { page_data.m_controller->add_controller(controller); const auto type = page_data.m_controller->type(); // if first controller and we got no mappings, add default mappings if(page_data.m_controller->set_default_mapping(controller)) page_data.m_panels[type]->load_controller(page_data.m_controller); page_data.m_panels[type]->set_selected_controller(page_data.m_controller, controller); } update_state(); } void InputSettings2::on_controller_remove(wxCommandEvent& event) { auto& page_data = get_current_page_data(); auto* api_box = page_data.m_controllers; int selection = api_box->GetSelection(); if (selection == wxNOT_FOUND) return; if (page_data.m_controller) { if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(selection)) { page_data.m_controller->remove_controller(controller->ref()); page_data.m_panels[page_data.m_controller->type()]->load_controller(page_data.m_controller); } } page_data.m_panels[page_data.m_controller->type()]->reset_colours(); api_box->Delete(selection); api_box->Refresh(); update_state(); if (api_box->GetCount() > 0) { selection = selection > 0 ? (selection - 1) : 0; api_box->SetSelection(selection); } update_state(); } void InputSettings2::on_controller_calibrate(wxCommandEvent& event) { if(const auto controller = get_active_controller()) controller->calibrate(); } void InputSettings2::on_controller_clear(wxCommandEvent& event) { auto& page_data = get_current_page_data(); if (page_data.m_controller) { const auto type = page_data.m_controller->type(); page_data.m_panels[type]->reset_configuration(); page_data.m_controller->clear_mappings(); } } void InputSettings2::on_controller_settings(wxCommandEvent& event) { auto controller = get_active_controller(); if (!controller) return; switch(controller->api()) { case InputAPI::DirectInput: case InputAPI::XInput: case InputAPI::GameCube: case InputAPI::WGIGamepad: case InputAPI::WGIRawController: case InputAPI::SDLController: case InputAPI::DSUClient: { DefaultControllerSettings wnd(this, wxGetMousePosition() + wxSize(5, 5), controller); wnd.ShowModal(); break; } case InputAPI::Keyboard: break; #ifdef SUPPORTS_WIIMOTE case InputAPI::Wiimote: { const auto wiimote = std::dynamic_pointer_cast(controller); wxASSERT(wiimote); WiimoteControllerSettings wnd(this, wxGetMousePosition() + wxSize(5, 5), wiimote); wnd.ShowModal(); break; } #endif } } ================================================ FILE: src/gui/wxgui/input/InputSettings2.h ================================================ #pragma once #include #include #include #include "input/api/InputAPI.h" #include struct ControllerPage; class ControllerBase; class InputSettings2 : public wxDialog { public: InputSettings2(wxWindow* parent); ~InputSettings2(); private: const wxString kDefaultProfileName = _(""); wxNotebook* m_notebook; wxTimer* m_timer; wxBitmap m_connected, m_disconnected, m_low_battery; wxWindow* initialize_page(size_t index); // currently selected controller from active tab std::shared_ptr get_active_controller() const; bool has_settings(InputAPI::Type type); ControllerPage& get_current_page_data() const; void update_state(); boost::signals2::connection m_controller_changed; void on_controller_changed(); // events void on_notebook_page_changed(wxBookCtrlEvent& event); void on_timer(wxTimerEvent& event); void on_left_click(wxMouseEvent& event); void on_controller_page_changed(wxBookCtrlEvent& event); void on_profile_dropdown(wxCommandEvent& event); void on_profile_text_changed(wxCommandEvent& event); void on_profile_load(wxCommandEvent& event); void on_profile_save(wxCommandEvent& event); void on_profile_delete(wxCommandEvent& event); void on_emulated_controller_selected(wxCommandEvent& event); void on_emulated_controller_dropdown(wxCommandEvent& event); void on_controller_selected(wxCommandEvent& event); void on_controller_dropdown(wxCommandEvent& event); void on_controller_connect(wxCommandEvent& event); void on_controller_add(wxCommandEvent& event); void on_controller_remove(wxCommandEvent& event); void on_controller_calibrate(wxCommandEvent& event); void on_controller_clear(wxCommandEvent& event); void on_controller_settings(wxCommandEvent& event); // void on_controller_dropdown(wxCommandEvent& event); // void on_controllers_refreshed(wxCommandEvent& event); }; ================================================ FILE: src/gui/wxgui/input/PairingDialog.cpp ================================================ #include "wxgui/wxgui.h" #include "PairingDialog.h" #if BOOST_OS_WINDOWS #include #endif #if BOOST_OS_LINUX #include #include #include #include #endif wxDECLARE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent); wxDEFINE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent); PairingDialog::PairingDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _("Pairing..."), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX) { auto* sizer = new wxBoxSizer(wxVERTICAL); m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(350, 20), wxGA_HORIZONTAL); m_gauge->SetValue(0); sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5); auto* rows = new wxFlexGridSizer(0, 2, 0, 0); rows->AddGrowableCol(1); m_text = new wxStaticText(this, wxID_ANY, _("Searching for controllers...")); rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); { auto* right_side = new wxBoxSizer(wxHORIZONTAL); m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); m_cancelButton->Bind(wxEVT_BUTTON, &PairingDialog::OnCancelButton, this); right_side->Add(m_cancelButton, 0, wxALL, 5); rows->Add(right_side, 1, wxALIGN_RIGHT, 5); } sizer->Add(rows, 0, wxALL | wxEXPAND, 5); SetSizerAndFit(sizer); Centre(wxBOTH); Bind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); Bind(wxEVT_PROGRESS_PAIR, &PairingDialog::OnGaugeUpdate, this); m_thread = std::thread(&PairingDialog::WorkerThread, this); } PairingDialog::~PairingDialog() { Unbind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); } void PairingDialog::OnClose(wxCloseEvent& event) { event.Skip(); m_threadShouldQuit = true; if (m_thread.joinable()) m_thread.join(); } void PairingDialog::OnCancelButton(const wxCommandEvent& event) { Close(); } void PairingDialog::OnGaugeUpdate(wxCommandEvent& event) { PairingState state = (PairingState)event.GetInt(); switch (state) { case PairingState::Pairing: { m_text->SetLabel(_("Found controller. Pairing...")); m_gauge->SetValue(50); break; } case PairingState::Finished: { m_text->SetLabel(_("Successfully paired the controller.")); m_gauge->SetValue(100); m_cancelButton->SetLabel(_("Close")); break; } case PairingState::NoBluetoothAvailable: { m_text->SetLabel(_("Failed to find a suitable Bluetooth radio.")); m_gauge->SetValue(0); m_cancelButton->SetLabel(_("Close")); break; } case PairingState::SearchFailed: { m_text->SetLabel(_("Failed to find controllers.")); m_gauge->SetValue(0); m_cancelButton->SetLabel(_("Close")); break; } case PairingState::PairingFailed: { m_text->SetLabel(_("Failed to pair with the found controller.")); m_gauge->SetValue(0); m_cancelButton->SetLabel(_("Close")); break; } case PairingState::BluetoothUnusable: { m_text->SetLabel(_("Please use your system's Bluetooth manager instead.")); m_gauge->SetValue(0); m_cancelButton->SetLabel(_("Close")); break; } default: { break; } } } #if BOOST_OS_WINDOWS void PairingDialog::WorkerThread() { const std::wstring wiimoteName = L"Nintendo RVL-CNT-01"; const std::wstring wiiUProControllerName = L"Nintendo RVL-CNT-01-UC"; const GUID bthHidGuid = {0x00001124, 0x0000, 0x1000, {0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}}; const BLUETOOTH_FIND_RADIO_PARAMS radioFindParams = { .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS)}; HANDLE radio = INVALID_HANDLE_VALUE; HBLUETOOTH_RADIO_FIND radioFind = BluetoothFindFirstRadio(&radioFindParams, &radio); if (radioFind == nullptr) { UpdateCallback(PairingState::NoBluetoothAvailable); return; } BluetoothFindRadioClose(radioFind); BLUETOOTH_RADIO_INFO radioInfo = { .dwSize = sizeof(BLUETOOTH_RADIO_INFO)}; DWORD result = BluetoothGetRadioInfo(radio, &radioInfo); if (result != ERROR_SUCCESS) { UpdateCallback(PairingState::NoBluetoothAvailable); return; } const BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = { .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS), .fReturnAuthenticated = FALSE, .fReturnRemembered = FALSE, .fReturnUnknown = TRUE, .fReturnConnected = FALSE, .fIssueInquiry = TRUE, .cTimeoutMultiplier = 5, .hRadio = radio}; BLUETOOTH_DEVICE_INFO info = { .dwSize = sizeof(BLUETOOTH_DEVICE_INFO)}; while (!m_threadShouldQuit) { HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info); if (deviceFind == nullptr) { UpdateCallback(PairingState::SearchFailed); return; } while (!m_threadShouldQuit) { if (info.szName == wiimoteName || info.szName == wiiUProControllerName) { BluetoothFindDeviceClose(deviceFind); UpdateCallback(PairingState::Pairing); wchar_t passwd[6] = {radioInfo.address.rgBytes[0], radioInfo.address.rgBytes[1], radioInfo.address.rgBytes[2], radioInfo.address.rgBytes[3], radioInfo.address.rgBytes[4], radioInfo.address.rgBytes[5]}; DWORD bthResult = BluetoothAuthenticateDevice(nullptr, radio, &info, passwd, 6); if (bthResult != ERROR_SUCCESS) { UpdateCallback(PairingState::PairingFailed); return; } bthResult = BluetoothSetServiceState(radio, &info, &bthHidGuid, BLUETOOTH_SERVICE_ENABLE); if (bthResult != ERROR_SUCCESS) { UpdateCallback(PairingState::PairingFailed); return; } UpdateCallback(PairingState::Finished); return; } BOOL nextDevResult = BluetoothFindNextDevice(deviceFind, &info); if (nextDevResult == FALSE) { break; } } BluetoothFindDeviceClose(deviceFind); } } #elif defined(HAS_BLUEZ) void PairingDialog::WorkerThread() { constexpr static uint8_t LIAC_LAP[] = {0x00, 0x8b, 0x9e}; constexpr static auto isWiimoteName = [](std::string_view name) { return name == "Nintendo RVL-CNT-01" || name == "Nintendo RVL-CNT-01-TR"; }; // Get default BT device const auto hostId = hci_get_route(nullptr); if (hostId < 0) { UpdateCallback(PairingState::NoBluetoothAvailable); return; } // Search for device inquiry_info* infos = nullptr; m_cancelButton->Disable(); const auto respCount = hci_inquiry(hostId, 7, 4, LIAC_LAP, &infos, IREQ_CACHE_FLUSH); m_cancelButton->Enable(); if (respCount <= 0) { UpdateCallback(PairingState::SearchFailed); return; } stdx::scope_exit infoFree([&]() { bt_free(infos);}); if (m_threadShouldQuit) return; // Open dev to read name const auto hostDev = hci_open_dev(hostId); stdx::scope_exit devClose([&]() { hci_close_dev(hostDev);}); char nameBuffer[HCI_MAX_NAME_LENGTH] = {}; bool foundADevice = false; // Get device name and compare. Would use product and vendor id from SDP, but many third-party Wiimotes don't store them for (const auto& devInfo : std::span(infos, respCount)) { const auto& addr = devInfo.bdaddr; const auto err = hci_read_remote_name(hostDev, &addr, HCI_MAX_NAME_LENGTH, nameBuffer, 2000); if (m_threadShouldQuit) return; if (err || !isWiimoteName(nameBuffer)) continue; L2CapWiimote::AddCandidateAddress(addr); foundADevice = true; const auto& b = addr.b; cemuLog_log(LogType::Force, "Pairing Dialog: Found '{}' with address '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}'", nameBuffer, b[5], b[4], b[3], b[2], b[1], b[0]); } if (foundADevice) UpdateCallback(PairingState::Finished); else UpdateCallback(PairingState::SearchFailed); } #else void PairingDialog::WorkerThread() { UpdateCallback(PairingState::BluetoothUnusable); } #endif void PairingDialog::UpdateCallback(PairingState state) { auto* event = new wxCommandEvent(wxEVT_PROGRESS_PAIR); event->SetInt((int)state); wxQueueEvent(this, event); } ================================================ FILE: src/gui/wxgui/input/PairingDialog.h ================================================ #pragma once #include #include #include #include class PairingDialog : public wxDialog { public: PairingDialog(wxWindow* parent); ~PairingDialog(); private: enum class PairingState { Pairing, Finished, NoBluetoothAvailable, SearchFailed, PairingFailed, BluetoothUnusable }; void OnClose(wxCloseEvent& event); void OnCancelButton(const wxCommandEvent& event); void OnGaugeUpdate(wxCommandEvent& event); void WorkerThread(); void UpdateCallback(PairingState state); wxStaticText* m_text; wxGauge* m_gauge; wxButton* m_cancelButton; std::thread m_thread; bool m_threadShouldQuit = false; }; ================================================ FILE: src/gui/wxgui/input/panels/ClassicControllerInputPanel.cpp ================================================ #include "wxgui/input/panels/ClassicControllerInputPanel.h" #include #include #include #include #include #include "wxgui/helpers/wxControlObject.h" #include "input/emulated/ClassicController.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/components/wxInputDraw.h" constexpr ClassicController::ButtonId g_kFirstColumnItems[] = { ClassicController::kButtonId_A, ClassicController::kButtonId_B, ClassicController::kButtonId_X, ClassicController::kButtonId_Y, ClassicController::kButtonId_L, ClassicController::kButtonId_R, ClassicController::kButtonId_ZL, ClassicController::kButtonId_ZR, ClassicController::kButtonId_Plus, ClassicController::kButtonId_Minus }; constexpr ClassicController::ButtonId g_kSecondColumnItems[] = { ClassicController::kButtonId_StickL_Up, ClassicController::kButtonId_StickL_Down, ClassicController::kButtonId_StickL_Left, ClassicController::kButtonId_StickL_Right }; constexpr ClassicController::ButtonId g_kThirdColumnItems[] = { ClassicController::kButtonId_StickR_Up, ClassicController::kButtonId_StickR_Down, ClassicController::kButtonId_StickR_Left, ClassicController::kButtonId_StickR_Right }; constexpr ClassicController::ButtonId g_kFourthRowItems[] = { ClassicController::kButtonId_Up, ClassicController::kButtonId_Down, ClassicController::kButtonId_Left, ClassicController::kButtonId_Right }; ClassicControllerInputPanel::ClassicControllerInputPanel(wxWindow* parent) : InputPanel(parent) { auto bold_font = GetFont(); bold_font.MakeBold(); auto* main_sizer = new wxGridBagSizer(); sint32 row = 0; sint32 column = 0; for (const auto& id : g_kFirstColumnItems) { row++; add_button_row(main_sizer, row, column, id); } ////////////////////////////////////////////////////////////////// main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); row = 0; column += 3; auto text = new wxStaticText(this, wxID_ANY, _("Left Axis")); text->SetFont(bold_font); main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (const auto& id : g_kSecondColumnItems) { row++; add_button_row(main_sizer, row, column, id); } row++; // input drawer m_left_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); main_sizer->Add(m_left_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); ////////////////////////////////////////////////////////////////// row = 0; column += 4; text = new wxStaticText(this, wxID_ANY, _("Right Axis")); text->SetFont(bold_font); main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (const auto& id : g_kThirdColumnItems) { row++; add_button_row(main_sizer, row, column, id); } row++; // input drawer m_right_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); main_sizer->Add(m_right_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); ////////////////////////////////////////////////////////////////// row = 0; column += 4; text = new wxStaticText(this, wxID_ANY, _("D-pad")); text->SetFont(bold_font); main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (auto id : g_kFourthRowItems) { row++; add_button_row(main_sizer, row, column, id); } ////////////////////////////////////////////////////////////////// SetSizer(main_sizer); Layout(); } void ClassicControllerInputPanel::add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const ClassicController::ButtonId &button_id) { sizer->Add( new wxStaticText(this, wxID_ANY, wxGetTranslation(wxString::FromUTF8(ClassicController::get_button_name(button_id)))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); text_ctrl->SetClientData(reinterpret_cast(button_id)); text_ctrl->SetMinSize(wxSize(150, -1)); text_ctrl->SetEditable(false); text_ctrl->SetBackgroundColour(kKeyColourNormalMode); bind_hotkey_events(text_ctrl); sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); } ================================================ FILE: src/gui/wxgui/input/panels/ClassicControllerInputPanel.h ================================================ #pragma once #include #include "input/emulated/ClassicController.h" #include "wxgui/input/panels/InputPanel.h" class wxInputDraw; class ClassicControllerInputPanel : public InputPanel { public: ClassicControllerInputPanel(wxWindow* parent); private: wxInputDraw* m_left_draw, * m_right_draw; void add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const ClassicController::ButtonId &button_id); }; ================================================ FILE: src/gui/wxgui/input/panels/InputPanel.cpp ================================================ #include "wxgui/input/panels/InputPanel.h" #include #include #include "wxgui/helpers/wxHelpers.h" InputPanel::InputPanel(wxWindow* parent) : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxWANTS_CHARS) { Bind(wxEVT_LEFT_UP, &InputPanel::on_left_click, this); } void InputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) { const auto& state = controller->update_state(); if(m_focused_element == wxID_NONE) { return; } const auto element = dynamic_cast(FindWindow(m_focused_element)); wxASSERT(element); const auto mapping = reinterpret_cast(element->GetClientData()); // reset mapping if(std::exchange(m_right_down, false) || WindowSystem::IsKeyDown(WindowSystem::PlatformKeyCodes::ESCAPE)) { element->SetBackgroundColour(kKeyColourNormalMode); m_color_backup[element->GetId()] = kKeyColourNormalMode; emulated_controller->delete_mapping(mapping); if(element->IsEmpty()) reset_focused_element(); else element->SetValue(wxEmptyString); return; } static bool s_was_idle = true; if (state.buttons.IsIdle()) { s_was_idle = true; return; } if (!s_was_idle) { return; } s_was_idle = false; for(const auto& id : state.buttons.GetButtonList()) { if (controller->has_axis()) { // test if one axis direction is pressed more than the other if ((id == kAxisXP || id == kAxisXN) && (state.buttons.GetButtonState(kAxisYP) || state.buttons.GetButtonState(kAxisYN))) { if (std::abs(state.axis.y) > std::abs(state.axis.x)) continue; } else if ((id == kAxisYP || id == kAxisYN) && (state.buttons.GetButtonState(kAxisXP) || state.buttons.GetButtonState(kAxisXN))) { if (std::abs(state.axis.x) > std::abs(state.axis.y)) continue; } else if ((id == kRotationXP || id == kRotationXN) && (state.buttons.GetButtonState(kRotationYP) || state.buttons.GetButtonState(kRotationYN))) { if (std::abs(state.rotation.y) > std::abs(state.rotation.x)) continue; } else if ((id == kRotationYP || id == kRotationYN) && (state.buttons.GetButtonState(kRotationXP) || state.buttons.GetButtonState(kRotationXN))) { if (std::abs(state.rotation.x) > std::abs(state.rotation.y)) continue; } else if ((id == kTriggerXP || id == kTriggerXN) && (state.buttons.GetButtonState(kTriggerYP) || state.buttons.GetButtonState(kTriggerYN))) { if (std::abs(state.trigger.y) > std::abs(state.trigger.x)) continue; } else if ((id == kTriggerYP || id == kTriggerYN) && (state.buttons.GetButtonState(kTriggerXP) || state.buttons.GetButtonState(kTriggerXN))) { if (std::abs(state.trigger.x) > std::abs(state.trigger.y)) continue; } // ignore too low button values on configuration if (id >= kButtonAxisStart) { if (controller->get_axis_value(id) < 0.33f) { cemuLog_logDebug(LogType::Force, "skipping since value too low {}", controller->get_axis_value(id)); s_was_idle = true; return; } } } emulated_controller->set_mapping(mapping, controller, id); element->SetValue(controller->get_button_name(id)); element->SetBackgroundColour(kKeyColourNormalMode); m_color_backup[element->GetId()] = kKeyColourNormalMode; break; } if (const auto sibling = get_next_sibling(element)) sibling->SetFocus(); else // last element reached { reset_focused_element(); this->SetFocusIgnoringChildren(); } } void InputPanel::reset_configuration() { m_color_backup.clear(); wxWindowUpdateLocker lock(this); for (const auto& c : GetChildren()) { if (auto* text = dynamic_cast(c)) { text->SetValue(wxEmptyString); text->SetBackgroundColour(kKeyColourNormalMode); text->Refresh(); } } } void InputPanel::reset_colours() { m_color_backup.clear(); wxWindowUpdateLocker lock(this); for (const auto& c : GetChildren()) { if (auto* text = dynamic_cast(c)) { text->SetBackgroundColour(kKeyColourNormalMode); text->Refresh(); } } } void InputPanel::load_controller(const EmulatedControllerPtr& controller) { reset_configuration(); if(!controller) { return; } if(controller->get_controllers().empty()) { return; } wxWindowUpdateLocker lock(this); for (auto* child : this->GetChildren()) { const auto text = dynamic_cast(child); if (text == nullptr) continue; const auto mapping = reinterpret_cast(text->GetClientData()); if (mapping <= 0) continue; auto button_name = controller->get_mapping_name(mapping); text->ChangeValue(button_name); } } void InputPanel::set_selected_controller(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) { wxWindowUpdateLocker lock(this); for (auto* child : this->GetChildren()) { const auto text = dynamic_cast(child); if (text == nullptr) continue; if (text->GetId() == m_focused_element) continue; const auto mapping = reinterpret_cast(text->GetClientData()); if (mapping <= 0) continue; const auto mapping_controller = emulated_controller->get_mapping_controller(mapping); if (!mapping_controller) continue; text->SetBackgroundColour(*mapping_controller == *controller ? kKeyColourNormalMode : kKeyColourActiveMode); text->Refresh(); } } void InputPanel::bind_hotkey_events(wxTextCtrl* text_ctrl) { text_ctrl->Bind(wxEVT_SET_FOCUS, &InputPanel::on_edit_key_focus, this); text_ctrl->Bind(wxEVT_KILL_FOCUS, &InputPanel::on_edit_key_kill_focus, this); text_ctrl->Bind(wxEVT_RIGHT_DOWN, &InputPanel::on_right_click, this); #if BOOST_OS_LINUX // Bind to a no-op lambda to disable arrow keys navigation text_ctrl->Bind(wxEVT_KEY_DOWN, [](wxKeyEvent &) {}); #endif } void InputPanel::on_left_click(wxMouseEvent& event) { if (m_focused_element == wxID_NONE) return; const auto focuses_element = FindWindow(m_focused_element); wxASSERT(focuses_element); wxFocusEvent focus(wxEVT_KILL_FOCUS, m_focused_element); focus.SetWindow(focuses_element); focuses_element->GetEventHandler()->ProcessEvent(focus); this->SetFocusIgnoringChildren(); } void InputPanel::on_edit_key_focus(wxFocusEvent& event) { auto* text = dynamic_cast(event.GetEventObject()); wxASSERT(text); m_color_backup[text->GetId()] = text->GetBackgroundColour(); text->SetBackgroundColour(kKeyColourEditMode); #if BOOST_OS_WINDOWS text->HideNativeCaret(); #endif text->Refresh(); m_focused_element = text->GetId(); event.Skip(); } void InputPanel::on_edit_key_kill_focus(wxFocusEvent& event) { reset_focused_element(); event.Skip(); } void InputPanel::on_right_click(wxMouseEvent& event) { m_right_down = true; if(m_focused_element == wxID_NONE) { auto* text = dynamic_cast(event.GetEventObject()); wxASSERT(text); text->SetFocus(); } } bool InputPanel::reset_focused_element() { if (m_focused_element == wxID_NONE) return false; auto* prev_element = dynamic_cast(FindWindow(m_focused_element)); wxASSERT(prev_element); if(m_color_backup.find(prev_element->GetId()) != m_color_backup.cend()) prev_element->SetBackgroundColour(m_color_backup[prev_element->GetId()]); else prev_element->SetBackgroundColour(kKeyColourNormalMode); #if BOOST_OS_WINDOWS prev_element->HideNativeCaret(); #endif prev_element->Refresh(); m_focused_element = wxID_NONE; return true; } ================================================ FILE: src/gui/wxgui/input/panels/InputPanel.h ================================================ #pragma once #include #include #include "input/emulated/EmulatedController.h" #include "input/api/Controller.h" #include "wxgui/wxHelper.h" class ControllerBase; class wxTextCtrl; class wxComboBox; class InputPanel : public wxPanel { public: const wxColour kKeyColourNormalMode = GetBackgroundColour(); const wxColour kKeyColourEditMode = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); const wxColour kKeyColourActiveMode = wxHelper::CalculateAccentColour(GetBackgroundColour()); InputPanel(wxWindow* parent); ControllerPtr get_active_controller() const; virtual void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller); void on_left_click(wxMouseEvent& event); void reset_configuration(); virtual void load_controller(const EmulatedControllerPtr& controller); void set_selected_controller(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller_base); void reset_colours(); protected: void bind_hotkey_events(wxTextCtrl* text_ctrl); void on_edit_key_focus(wxFocusEvent& event); void on_edit_key_kill_focus(wxFocusEvent& event); void on_right_click(wxMouseEvent& event); bool reset_focused_element(); bool m_right_down = false; int m_focused_element = wxID_NONE; std::unordered_map m_color_backup; }; ================================================ FILE: src/gui/wxgui/input/panels/ProControllerInputPanel.cpp ================================================ #include "wxgui/input/panels/ProControllerInputPanel.h" #include #include #include #include #include "input/emulated/ProController.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/components/wxInputDraw.h" const ProController::ButtonId g_kFirstColumnItems[] = { ProController::kButtonId_A, ProController::kButtonId_B, ProController::kButtonId_X, ProController::kButtonId_Y, ProController::kButtonId_L, ProController::kButtonId_R, ProController::kButtonId_ZL, ProController::kButtonId_ZR, ProController::kButtonId_Plus, ProController::kButtonId_Minus }; const ProController::ButtonId g_kSecondColumnItems[] = { ProController::kButtonId_StickL, ProController::kButtonId_StickL_Up, ProController::kButtonId_StickL_Down, ProController::kButtonId_StickL_Left, ProController::kButtonId_StickL_Right }; const ProController::ButtonId g_kThirdColumnItems[] = { ProController::kButtonId_StickR, ProController::kButtonId_StickR_Up, ProController::kButtonId_StickR_Down, ProController::kButtonId_StickR_Left, ProController::kButtonId_StickR_Right }; const ProController::ButtonId g_kFourthRowItems[] = { ProController::kButtonId_Up, ProController::kButtonId_Down, ProController::kButtonId_Left, ProController::kButtonId_Right }; ProControllerInputPanel::ProControllerInputPanel(wxWindow* parent) : InputPanel(parent) { auto bold_font = GetFont(); bold_font.MakeBold(); auto main_sizer = new wxGridBagSizer(); sint32 row = 0; sint32 column = 0; for (auto id : g_kFirstColumnItems) { row++; add_button_row(main_sizer, row, column, id); } ////////////////////////////////////////////////////////////////// main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); row = 0; column += 3; auto text = new wxStaticText(this, wxID_ANY, _("Left Axis")); text->SetFont(bold_font); main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (auto id : g_kSecondColumnItems) { row++; add_button_row(main_sizer, row, column, id); } row++; // input drawer m_left_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); main_sizer->Add(m_left_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); ////////////////////////////////////////////////////////////////// row = 0; column += 4; text = new wxStaticText(this, wxID_ANY, _("Right Axis")); text->SetFont(bold_font); main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (auto id : g_kThirdColumnItems) { row++; add_button_row(main_sizer, row, column, id); } row++; // input drawer m_right_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); main_sizer->Add(m_right_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); ////////////////////////////////////////////////////////////////// row = 0; column += 4; text = new wxStaticText(this, wxID_ANY, _("D-pad")); text->SetFont(bold_font); main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (auto id : g_kFourthRowItems) { row++; add_button_row(main_sizer, row, column, id); } ////////////////////////////////////////////////////////////////// SetSizerAndFit(main_sizer); } void ProControllerInputPanel::add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const ProController::ButtonId &button_id) { sizer->Add( new wxStaticText(this, wxID_ANY, wxGetTranslation(wxString::FromUTF8(ProController::get_button_name(button_id)))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); text_ctrl->SetClientData((void*)button_id); text_ctrl->SetMinSize(wxSize(150, -1)); text_ctrl->SetEditable(false); text_ctrl->SetBackgroundColour(kKeyColourNormalMode); bind_hotkey_events(text_ctrl); sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); } void ProControllerInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller_base) { InputPanel::on_timer(emulated_controller, controller_base); if (emulated_controller) { const auto axis = emulated_controller->get_axis(); const auto rotation = emulated_controller->get_rotation(); m_left_draw->SetAxisValue(axis); m_right_draw->SetAxisValue(rotation); } } ================================================ FILE: src/gui/wxgui/input/panels/ProControllerInputPanel.h ================================================ #pragma once #include #include "input/emulated/ProController.h" #include "wxgui/input/panels/InputPanel.h" #include "wxgui/components/wxInputDraw.h" class ProControllerInputPanel : public InputPanel { public: ProControllerInputPanel(wxWindow* parent); void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override; private: wxInputDraw* m_left_draw, * m_right_draw; void add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const ProController::ButtonId &button_id); }; ================================================ FILE: src/gui/wxgui/input/panels/VPADInputPanel.cpp ================================================ #include "wxgui/input/panels/VPADInputPanel.h" #include #include #include #include #include #include #include "wxgui/helpers/wxControlObject.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/components/wxInputDraw.h" #include "input/emulated/VPADController.h" constexpr VPADController::ButtonId g_kFirstColumnItems[] = { VPADController::kButtonId_A, VPADController::kButtonId_B, VPADController::kButtonId_X, VPADController::kButtonId_Y, VPADController::kButtonId_L, VPADController::kButtonId_R, VPADController::kButtonId_ZL, VPADController::kButtonId_ZR, VPADController::kButtonId_Plus, VPADController::kButtonId_Minus }; constexpr VPADController::ButtonId g_kSecondColumnItems[] = { VPADController::kButtonId_StickL, VPADController::kButtonId_StickL_Up, VPADController::kButtonId_StickL_Down, VPADController::kButtonId_StickL_Left, VPADController::kButtonId_StickL_Right }; constexpr VPADController::ButtonId g_kThirdColumnItems[] = { VPADController::kButtonId_StickR, VPADController::kButtonId_StickR_Up, VPADController::kButtonId_StickR_Down, VPADController::kButtonId_StickR_Left, VPADController::kButtonId_StickR_Right }; constexpr VPADController::ButtonId g_kFourthRowItems[] = { VPADController::kButtonId_Up, VPADController::kButtonId_Down, VPADController::kButtonId_Left, VPADController::kButtonId_Right }; VPADInputPanel::VPADInputPanel(wxWindow* parent) : InputPanel(parent) { auto bold_font = GetFont(); bold_font.MakeBold(); auto* main_sizer = new wxGridBagSizer(); sint32 row = 0; sint32 column = 0; for (const auto& id : g_kFirstColumnItems) { row++; add_button_row(main_sizer, row, column, id); } ////////////////////////////////////////////////////////////////// main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); row = 0; column += 3; auto text = new wxStaticText(this, wxID_ANY, _("Left Axis")); text->SetFont(bold_font); main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (const auto& id : g_kSecondColumnItems) { row++; add_button_row(main_sizer, row, column, id); } row++; // input drawer m_left_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); main_sizer->Add(m_left_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); ////////////////////////////////////////////////////////////////// row = 0; column += 4; text = new wxStaticText(this, wxID_ANY, _("Right Axis")); text->SetFont(bold_font); main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (const auto& id : g_kThirdColumnItems) { row++; add_button_row(main_sizer, row, column, id); } row++; // input drawer m_right_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); main_sizer->Add(m_right_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); // Volume row = 10; text = new wxStaticText(this, wxID_ANY, _("Volume")); text->Disable(); main_sizer->Add(text, wxGBPosition(row, column), wxDefaultSpan, wxALL, 5); auto*m_volume = new wxSlider(this, wxID_ANY, 0, 0, 100); m_volume->Disable(); main_sizer->Add(m_volume, wxGBPosition(row, column + 1), wxDefaultSpan, wxTOP | wxBOTTOM | wxEXPAND, 5); const auto volume_text = new wxStaticText(this, wxID_ANY, wxString::Format("%d%%", 0)); volume_text->Disable(); main_sizer->Add(volume_text, wxGBPosition(row, column + 2), wxDefaultSpan, wxALL, 5); m_volume->Bind(wxEVT_SLIDER, &VPADInputPanel::OnVolumeChange, this, wxID_ANY, wxID_ANY, new wxControlObject(volume_text)); main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); ////////////////////////////////////////////////////////////////// row = 0; column += 4; text = new wxStaticText(this, wxID_ANY, _("D-pad")); text->SetFont(bold_font); main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (const auto& id : g_kFourthRowItems) { row++; add_button_row(main_sizer, row, column, id); } // Blow Mic row = 8; add_button_row(main_sizer, row, column, VPADController::kButtonId_Mic, _("blow mic")); row++; add_button_row(main_sizer, row, column, VPADController::kButtonId_Screen, _("show screen")); row++; auto toggleScreenText = new wxStaticText(this, wxID_ANY, _("toggle screen")); main_sizer->Add(toggleScreenText, wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_togglePadViewCheckBox = new wxCheckBox(this, wxID_ANY, {}, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); wxString toggleScreenTT = _("Makes the \"show screen\" button toggle between the TV and gamepad screens"); m_togglePadViewCheckBox->SetToolTip(toggleScreenTT); toggleScreenText->SetToolTip(toggleScreenTT); main_sizer->Add(m_togglePadViewCheckBox, wxGBPosition(row,column+1), wxDefaultSpan, wxALL | wxEXPAND, 5); ////////////////////////////////////////////////////////////////// SetSizer(main_sizer); Layout(); } void VPADInputPanel::add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const VPADController::ButtonId &button_id) { add_button_row(sizer, row, column, button_id, wxGetTranslation(wxString::FromUTF8(VPADController::get_button_name(button_id)))); } void VPADInputPanel::add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const VPADController::ButtonId &button_id, const wxString &label) { sizer->Add( new wxStaticText(this, wxID_ANY, label), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); text_ctrl->SetClientData((void*)button_id); text_ctrl->SetMinSize(wxSize(150, -1)); text_ctrl->SetEditable(false); text_ctrl->SetBackgroundColour(kKeyColourNormalMode); bind_hotkey_events(text_ctrl); sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); } void VPADInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller_base) { InputPanel::on_timer(emulated_controller, controller_base); static_cast(emulated_controller.get())->set_screen_toggle(m_togglePadViewCheckBox->GetValue()); if(emulated_controller) { const auto axis = emulated_controller->get_axis(); const auto rotation = emulated_controller->get_rotation(); m_left_draw->SetAxisValue(axis); m_right_draw->SetAxisValue(rotation); } } void VPADInputPanel::OnVolumeChange(wxCommandEvent& event) { } void VPADInputPanel::load_controller(const EmulatedControllerPtr& controller) { InputPanel::load_controller(controller); const bool isToggle = static_cast(controller.get())->is_screen_active_toggle(); m_togglePadViewCheckBox->SetValue(isToggle); } ================================================ FILE: src/gui/wxgui/input/panels/VPADInputPanel.h ================================================ #pragma once #include #include "wxgui/input/panels/InputPanel.h" class wxInputDraw; class wxCheckBox; class VPADInputPanel : public InputPanel { public: VPADInputPanel(wxWindow* parent); void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override; virtual void load_controller(const EmulatedControllerPtr& controller) override; private: void OnVolumeChange(wxCommandEvent& event); wxInputDraw* m_left_draw, * m_right_draw; wxCheckBox* m_togglePadViewCheckBox; void add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const VPADController::ButtonId &button_id); void add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const VPADController::ButtonId &button_id, const wxString &label); }; ================================================ FILE: src/gui/wxgui/input/panels/WiimoteInputPanel.cpp ================================================ #include "wxgui/input/panels/WiimoteInputPanel.h" #include #include #include #include #include #include #include #include "wxgui/helpers/wxControlObject.h" #include "input/emulated/WiimoteController.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/components/wxInputDraw.h" constexpr WiimoteController::ButtonId g_kFirstColumnItems[] = { WiimoteController::kButtonId_A, WiimoteController::kButtonId_B, WiimoteController::kButtonId_1, WiimoteController::kButtonId_2, WiimoteController::kButtonId_Plus, WiimoteController::kButtonId_Minus, WiimoteController::kButtonId_Home }; constexpr WiimoteController::ButtonId g_kSecondColumnItems[] = { WiimoteController::kButtonId_Up, WiimoteController::kButtonId_Down, WiimoteController::kButtonId_Left, WiimoteController::kButtonId_Right }; constexpr WiimoteController::ButtonId g_kThirdColumnItems[] = { WiimoteController::kButtonId_Nunchuck_C, WiimoteController::kButtonId_Nunchuck_Z, WiimoteController::kButtonId_None, WiimoteController::kButtonId_Nunchuck_Up,WiimoteController::kButtonId_Nunchuck_Down,WiimoteController::kButtonId_Nunchuck_Left,WiimoteController::kButtonId_Nunchuck_Right }; WiimoteInputPanel::WiimoteInputPanel(wxWindow* parent) : InputPanel(parent) { auto bold_font = GetFont(); bold_font.MakeBold(); auto* main_sizer = new wxBoxSizer(wxVERTICAL); auto* horiz_main_sizer = new wxBoxSizer(wxHORIZONTAL); auto* extensions_sizer = new wxBoxSizer(wxHORIZONTAL); horiz_main_sizer->Add(extensions_sizer, wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL)); extensions_sizer->Add(new wxStaticText(this, wxID_ANY, _("Extensions:"))); extensions_sizer->AddSpacer(10); m_motion_plus = new wxCheckBox(this, wxID_ANY, _("MotionPlus")); m_motion_plus->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this); extensions_sizer->Add(m_motion_plus); m_nunchuck = new wxCheckBox(this, wxID_ANY, _("Nunchuck")); m_nunchuck->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this); extensions_sizer->Add(m_nunchuck); m_classic = new wxCheckBox(this, wxID_ANY, _("Classic")); m_classic->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this); m_classic->Hide(); extensions_sizer->Add(m_classic); main_sizer->Add(horiz_main_sizer, 0, wxEXPAND | wxALL, 5); main_sizer->Add(new wxStaticLine(this), 0, wxLEFT | wxRIGHT | wxTOP | wxEXPAND, 5); m_item_sizer = new wxGridBagSizer(); sint32 row = 0; sint32 column = 0; for (const auto& id : g_kFirstColumnItems) { row++; add_button_row(row, column, id); } m_item_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5); ////////////////////////////////////////////////////////////////// row = 0; column += 3; auto text = new wxStaticText(this, wxID_ANY, _("D-pad")); text->SetFont(bold_font); m_item_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (const auto& id : g_kSecondColumnItems) { row++; add_button_row(row, column, id); } row = 8; // Volume text = new wxStaticText(this, wxID_ANY, _("Volume")); text->Disable(); m_item_sizer->Add(text, wxGBPosition(row, column), wxDefaultSpan, wxALL, 5); m_volume = new wxSlider(this, wxID_ANY, 0, 0, 100); m_volume->Disable(); m_item_sizer->Add(m_volume, wxGBPosition(row, column + 1), wxDefaultSpan, wxTOP | wxBOTTOM | wxEXPAND, 5); const auto volume_text = new wxStaticText(this, wxID_ANY, wxString::Format("%d%%", 0)); volume_text->Disable(); m_item_sizer->Add(volume_text, wxGBPosition(row, column + 2), wxDefaultSpan, wxALL, 5); m_volume->Bind(wxEVT_SLIDER, &WiimoteInputPanel::on_volume_change, this, wxID_ANY, wxID_ANY, new wxControlObject(volume_text)); row++; m_item_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5); ////////////////////////////////////////////////////////////////// row = 0; column += 4; text = new wxStaticText(this, wxID_ANY, _("Nunchuck")); text->SetFont(bold_font); m_item_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); for (const auto& id : g_kThirdColumnItems) { row++; if (id == WiimoteController::kButtonId_None) continue; m_item_sizer->Add( new wxStaticText(this, wxID_ANY, wxGetTranslation(wxString::FromUTF8(WiimoteController::get_button_name(id)))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); text_ctrl->SetClientData((void*)id); text_ctrl->SetMinSize(wxSize(150, -1)); text_ctrl->SetEditable(false); text_ctrl->SetBackgroundColour(kKeyColourNormalMode); bind_hotkey_events(text_ctrl); text_ctrl->Enable(m_nunchuck->GetValue()); m_item_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); m_nunchuck_items.push_back(text_ctrl); } // input drawer m_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); m_draw->Enable(m_nunchuck->GetValue()); m_item_sizer->Add(5, 0, wxGBPosition(3, column + 3), wxDefaultSpan, wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); m_item_sizer->Add(m_draw, wxGBPosition(3, column + 4), wxGBSpan(4, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); m_nunchuck_items.push_back(m_draw); ////////////////////////////////////////////////////////////////// main_sizer->Add(m_item_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5); SetSizer(main_sizer); Layout(); } void WiimoteInputPanel::add_button_row(sint32 row, sint32 column, const WiimoteController::ButtonId &button_id) { m_item_sizer->Add( new wxStaticText(this, wxID_ANY, wxGetTranslation(wxString::FromUTF8(WiimoteController::get_button_name(button_id)))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); text_ctrl->SetClientData((void*)button_id); text_ctrl->SetMinSize(wxSize(150, -1)); text_ctrl->SetEditable(false); text_ctrl->SetBackgroundColour(kKeyColourNormalMode); bind_hotkey_events(text_ctrl); m_item_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); } void WiimoteInputPanel::set_active_device_type(WPADDeviceType type) { m_device_type = type; m_motion_plus->SetValue(type == kWAPDevMPLS || type == kWAPDevMPLSFreeStyle || type == kWAPDevMPLSClassic); switch(type) { case kWAPDevFreestyle: case kWAPDevMPLSFreeStyle: m_nunchuck->SetValue(true); m_classic->SetValue(false); for (const auto& item : m_nunchuck_items) { item->Enable(true); } break; case kWAPDevClassic: case kWAPDevMPLSClassic: m_nunchuck->SetValue(false); m_classic->SetValue(true); for (const auto& item : m_nunchuck_items) { item->Enable(false); } break; default: m_nunchuck->SetValue(false); m_classic->SetValue(false); for (const auto& item : m_nunchuck_items) { item->Enable(false); } } } void WiimoteInputPanel::on_volume_change(wxCommandEvent& event) { } void WiimoteInputPanel::on_extension_change(wxCommandEvent& event) { if(m_motion_plus->GetValue() && m_nunchuck->GetValue()) set_active_device_type(kWAPDevMPLSFreeStyle); else if(m_motion_plus->GetValue() && m_classic->GetValue()) set_active_device_type(kWAPDevMPLSClassic); else if (m_motion_plus->GetValue()) set_active_device_type(kWAPDevMPLS); else if (m_nunchuck->GetValue()) set_active_device_type(kWAPDevFreestyle); else if (m_classic->GetValue()) set_active_device_type(kWAPDevClassic); else set_active_device_type(kWAPDevCore); } void WiimoteInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) { if (emulated_controller) { const auto wiimote = std::dynamic_pointer_cast(emulated_controller); wxASSERT(wiimote); wiimote->set_device_type(m_device_type); } InputPanel::on_timer(emulated_controller, controller); if (emulated_controller) { const auto axis = emulated_controller->get_axis(); m_draw->SetAxisValue(axis); } } void WiimoteInputPanel::load_controller(const EmulatedControllerPtr& emulated_controller) { InputPanel::load_controller(emulated_controller); if (emulated_controller) { const auto wiimote = std::dynamic_pointer_cast(emulated_controller); wxASSERT(wiimote); set_active_device_type(wiimote->get_device_type()); } } ================================================ FILE: src/gui/wxgui/input/panels/WiimoteInputPanel.h ================================================ #pragma once #include "wxgui/input/panels/InputPanel.h" #include "input/emulated/WiimoteController.h" #include class wxCheckBox; class wxGridBagSizer; class wxInputDraw; class WiimoteInputPanel : public InputPanel { public: WiimoteInputPanel(wxWindow* parent); void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override; void load_controller(const EmulatedControllerPtr& emulated_controller) override; private: wxInputDraw* m_draw; WPADDeviceType m_device_type = kWAPDevCore; void set_active_device_type(WPADDeviceType type); void on_volume_change(wxCommandEvent& event); void on_extension_change(wxCommandEvent& event); void on_pair_button(wxCommandEvent& event); wxGridBagSizer* m_item_sizer; wxCheckBox* m_nunchuck, * m_classic; wxCheckBox* m_motion_plus; wxSlider* m_volume; std::vector m_nunchuck_items; void add_button_row(sint32 row, sint32 column, const WiimoteController::ButtonId &button_id); }; ================================================ FILE: src/gui/wxgui/input/settings/DefaultControllerSettings.cpp ================================================ #include "wxgui/input/settings/DefaultControllerSettings.h" #include #include #include #include #include #include #include #include #include "wxgui/helpers/wxControlObject.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/components/wxInputDraw.h" #include "wxgui/input/InputAPIAddWindow.h" DefaultControllerSettings::DefaultControllerSettings(wxWindow* parent, const wxPoint& position, std::shared_ptr controller) : wxDialog(parent, wxID_ANY, _("Controller settings"), position, wxDefaultSize, wxDEFAULT_DIALOG_STYLE), m_controller(std::move(controller)) { m_settings = m_controller->get_settings(); m_rumble_backup = m_settings.rumble; this->SetSizeHints(wxDefaultSize, wxDefaultSize); auto* sizer = new wxBoxSizer(wxVERTICAL); // options { auto* box = new wxStaticBox(this, wxID_ANY, _("Options")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); // Motion m_use_motion = new wxCheckBox(box, wxID_ANY, _("Use motion")); m_use_motion->SetValue(m_settings.motion); m_use_motion->Enable(m_controller->has_motion()); box_sizer->Add(m_use_motion, 0, wxEXPAND | wxALL, 5); // Vibration auto* rumbleSizer = new wxBoxSizer(wxHORIZONTAL); const auto rumble = (int)(m_settings.rumble * 100); rumbleSizer->Add(new wxStaticText(box, wxID_ANY, _("Rumble")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_rumble = new wxSlider(box, wxID_ANY, rumble, 0, 100); rumbleSizer->Add(m_rumble, 1, wxALL | wxEXPAND, 5); const auto text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", rumble)); rumbleSizer->Add(text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_rumble->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_rumble_change, this, wxID_ANY, wxID_ANY, new wxControlObject(text)); box_sizer->Add(rumbleSizer); sizer->Add(box_sizer, 1, wxALL|wxEXPAND, 5); } auto* row_sizer = new wxBoxSizer(wxHORIZONTAL); // Axis { auto* box = new wxStaticBox(this, wxID_ANY, _("Axis")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); { auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0); // Deadzone const auto deadzone = (int)(m_settings.axis.deadzone * 100); content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_axis_deadzone = new wxSlider(box, wxID_ANY, deadzone, 0, 100); content_sizer->Add(m_axis_deadzone, 1, wxALL | wxEXPAND, 5); const auto deadzone_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", deadzone)); content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_axis_deadzone->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_deadzone_change, this, wxID_ANY, wxID_ANY, new wxControlObject(deadzone_text)); // Range const auto range = (int)(m_settings.axis.range * 100); content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_axis_range = new wxSlider(box, wxID_ANY, range, 50, 200); content_sizer->Add(m_axis_range, 1, wxALL | wxEXPAND, 5); const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", range)); content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_axis_range->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_range_change, this, wxID_ANY, wxID_ANY, new wxControlObject(range_text)); content_sizer->AddSpacer(1); m_axis_draw = new wxInputDraw(box, wxID_ANY, wxDefaultPosition, { 60, 60 }); content_sizer->Add(m_axis_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); box_sizer->Add(content_sizer, 1, wxEXPAND, 0); } row_sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); } // Rotation { auto* box = new wxStaticBox(this, wxID_ANY, _("Rotation")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); { auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0); // Deadzone const auto deadzone = (int)(m_settings.rotation.deadzone * 100); content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_rotation_deadzone = new wxSlider(box, wxID_ANY, deadzone, 0, 100); content_sizer->Add(m_rotation_deadzone, 1, wxALL | wxEXPAND, 5); const auto deadzone_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", deadzone)); content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_rotation_deadzone->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_deadzone_change, this, wxID_ANY, wxID_ANY, new wxControlObject(deadzone_text)); // Range const auto range = (int)(m_settings.rotation.range * 100); content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_rotation_range = new wxSlider(box, wxID_ANY, range, 50, 200); content_sizer->Add(m_rotation_range, 1, wxALL | wxEXPAND, 5); const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", range)); content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_rotation_range->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_range_change, this, wxID_ANY, wxID_ANY, new wxControlObject(range_text)); content_sizer->AddSpacer(1); m_rotation_draw = new wxInputDraw(box, wxID_ANY, wxDefaultPosition, { 60, 60 }); content_sizer->Add(m_rotation_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); box_sizer->Add(content_sizer, 1, wxEXPAND, 0); } row_sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); } // Trigger { auto* box = new wxStaticBox(this, wxID_ANY, _("Trigger")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); { auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0); // Deadzone const auto deadzone = (int)(m_settings.trigger.deadzone * 100); content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_trigger_deadzone = new wxSlider(box, wxID_ANY, deadzone, 0, 100); content_sizer->Add(m_trigger_deadzone, 1, wxALL | wxEXPAND, 5); const auto deadzone_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", deadzone)); content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_trigger_deadzone->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_deadzone_change, this, wxID_ANY, wxID_ANY, new wxControlObject(deadzone_text)); // Range const auto range = (int)(m_settings.trigger.range * 100); content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_trigger_range = new wxSlider(box, wxID_ANY, range, 50, 200); content_sizer->Add(m_trigger_range, 1, wxALL | wxEXPAND, 5); const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", range)); content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_trigger_range->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_range_change, this, wxID_ANY, wxID_ANY, new wxControlObject(range_text)); content_sizer->AddSpacer(1); m_trigger_draw = new wxInputDraw(box, wxID_ANY, wxDefaultPosition, { 60, 60 }); content_sizer->Add(m_trigger_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); box_sizer->Add(content_sizer, 1, wxEXPAND, 0); } row_sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); } sizer->Add(row_sizer); { auto* control_sizer = new wxFlexGridSizer(0, 4, 0, 0); control_sizer->AddGrowableCol(3); auto* ok_button = new wxButton(this, wxID_ANY, _("OK")); ok_button->Bind(wxEVT_BUTTON, [this](auto&) { update_settings(); EndModal(wxID_OK); }); control_sizer->Add(ok_button, 0, wxALL, 5); control_sizer->Add(0, 0, 0, wxEXPAND, 5); auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel")); cancel_button->Bind(wxEVT_BUTTON, [this](auto&) { EndModal(wxID_CANCEL); }); control_sizer->Add(cancel_button, 0, wxALL, 5); auto* calibrate_button = new wxButton(this, wxID_ANY, _("Calibrate")); calibrate_button->Bind(wxEVT_BUTTON, [this](auto&) { m_controller->calibrate(); }); control_sizer->Add(calibrate_button, 0, wxALL | wxALIGN_RIGHT, 5); sizer->Add(control_sizer, 0, wxEXPAND, 5); } this->SetSizer(sizer); this->Layout(); this->Fit(); this->Bind(wxEVT_CLOSE_WINDOW, &DefaultControllerSettings::on_close, this); m_timer = new wxTimer(this); Bind(wxEVT_TIMER, &DefaultControllerSettings::on_timer, this); m_timer->Start(100); } DefaultControllerSettings::~DefaultControllerSettings() { m_controller->stop_rumble(); m_timer->Stop(); } void DefaultControllerSettings::update_settings() { // update settings m_controller->set_settings(m_settings); if (m_use_motion) m_controller->set_use_motion(m_use_motion->GetValue()); } void DefaultControllerSettings::on_timer(wxTimerEvent& event) { if (m_rumble_time.has_value() && std::chrono::duration_cast(std::chrono::steady_clock::now() - m_rumble_time.value()).count() > 500 ) { m_controller->stop_rumble(); m_rumble_time = {}; } const auto& default_state = m_controller->get_default_state(); auto state = m_controller->raw_state(); m_controller->apply_axis_setting(state.axis, default_state.axis, m_settings.axis); m_controller->apply_axis_setting(state.rotation, default_state.rotation, m_settings.rotation); m_controller->apply_axis_setting(state.trigger, default_state.trigger, m_settings.trigger); if (m_controller->api() == InputAPI::SDLController) { state.axis.y *= -1; state.rotation.y *= -1; } m_axis_draw->SetDeadzone(m_settings.axis.deadzone); m_axis_draw->SetAxisValue(state.axis); m_rotation_draw->SetDeadzone(m_settings.rotation.deadzone); m_rotation_draw->SetAxisValue(state.rotation); m_trigger_draw->SetDeadzone(m_settings.trigger.deadzone); m_trigger_draw->SetAxisValue(state.trigger); } void DefaultControllerSettings::on_close(wxCloseEvent& event) { if (this->GetReturnCode() == 0 || this->GetReturnCode() == wxID_OK) update_settings(); event.Skip(); } void DefaultControllerSettings::on_deadzone_change(wxCommandEvent& event) { update_slider_text(event); const auto new_value = (float)event.GetInt() / 100.0f; if (event.GetEventObject() == m_axis_deadzone) m_settings.axis.deadzone = new_value; else if (event.GetEventObject() == m_rotation_deadzone) m_settings.rotation.deadzone = new_value; else if (event.GetEventObject() == m_trigger_deadzone) m_settings.trigger.deadzone = new_value; } void DefaultControllerSettings::on_range_change(wxCommandEvent& event) { update_slider_text(event); const auto new_value = (float)event.GetInt() / 100.0f; if (event.GetEventObject() == m_axis_range) m_settings.axis.range = new_value; else if (event.GetEventObject() == m_rotation_range) m_settings.rotation.range = new_value; else if (event.GetEventObject() == m_trigger_range) m_settings.trigger.range = new_value; } void DefaultControllerSettings::on_rumble_change(wxCommandEvent& event) { update_slider_text(event); const auto rumble_value = event.GetInt(); m_settings.rumble = (float)rumble_value / 100.0f; m_controller->set_rumble(m_settings.rumble); if (rumble_value != 0) m_controller->start_rumble(); else m_controller->stop_rumble(); m_controller->set_rumble(m_rumble_backup); m_rumble_time = std::chrono::steady_clock::now(); } ================================================ FILE: src/gui/wxgui/input/settings/DefaultControllerSettings.h ================================================ #pragma once #include #include #include #include "input/api/Controller.h" class wxCheckBox; class wxInputDraw; class DefaultControllerSettings : public wxDialog { public: DefaultControllerSettings(wxWindow* parent, const wxPoint& position, ControllerPtr controller); ~DefaultControllerSettings(); private: void update_settings(); ControllerPtr m_controller; ControllerBase::Settings m_settings; float m_rumble_backup; wxTimer* m_timer; std::optional m_rumble_time{}; wxSlider* m_axis_deadzone, *m_axis_range; wxSlider* m_rotation_deadzone, *m_rotation_range; wxSlider* m_trigger_deadzone, *m_trigger_range; wxSlider* m_rumble; wxCheckBox* m_use_motion = nullptr; wxInputDraw* m_axis_draw, * m_rotation_draw, *m_trigger_draw; void on_timer(wxTimerEvent& event); void on_close(wxCloseEvent& event); void on_deadzone_change(wxCommandEvent& event); void on_range_change(wxCommandEvent& event); void on_rumble_change(wxCommandEvent& event); }; ================================================ FILE: src/gui/wxgui/input/settings/WiimoteControllerSettings.cpp ================================================ #include "wxgui/input/settings/WiimoteControllerSettings.h" #include #include #include #include #include #include #include #include #include "wxgui/helpers/wxControlObject.h" #include "wxgui/helpers/wxHelpers.h" #include "wxgui/components/wxInputDraw.h" #include "wxgui/input/InputAPIAddWindow.h" #ifdef SUPPORTS_WIIMOTE WiimoteControllerSettings::WiimoteControllerSettings(wxWindow* parent, const wxPoint& position, std::shared_ptr controller) : wxDialog(parent, wxID_ANY, _("Controller settings"), position, wxDefaultSize, wxDEFAULT_DIALOG_STYLE), m_controller(std::move(controller)) { m_settings = m_controller->get_settings(); m_rumble_backup = m_settings.rumble; m_packet_delay_backup = m_controller->get_packet_delay(); this->SetSizeHints(wxDefaultSize, wxDefaultSize); auto* sizer = new wxBoxSizer(wxVERTICAL); // extension info { auto* box = new wxStaticBox(this, wxID_ANY, _("Connected extension")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); m_extension_text = new wxStaticText(box, wxID_ANY, _("None")); box_sizer->Add(m_extension_text, 0, wxALL, 5); sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); } // options { auto* box = new wxStaticBox(this, wxID_ANY, _("Options")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); { auto* row_sizer = new wxBoxSizer(wxHORIZONTAL); // Rumble m_rumble = new wxCheckBox(box, wxID_ANY, _("Rumble")); m_rumble->SetValue(m_settings.rumble > 0); row_sizer->Add(m_rumble, 0, wxALL, 5); m_rumble->Bind(wxEVT_CHECKBOX, &WiimoteControllerSettings::on_rumble_change, this); // Motion m_use_motion = new wxCheckBox(box, wxID_ANY, _("Use motion")); m_use_motion->SetValue(m_settings.motion); m_use_motion->Enable(m_controller->has_motion()); row_sizer->Add(m_use_motion, 0, wxALL, 5); box_sizer->Add(row_sizer); } { auto* row_sizer = new wxBoxSizer(wxHORIZONTAL); // Delay row_sizer->Add(new wxStaticText(box, wxID_ANY, _("Packet delay")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_package_delay = new wxSlider(box, wxID_ANY, m_packet_delay_backup, 1, 100); row_sizer->Add(m_package_delay, 1, wxALL | wxEXPAND, 5); const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%dms", m_packet_delay_backup)); row_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_package_delay->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_delay_change, this, wxID_ANY, wxID_ANY, new wxControlObject(range_text)); box_sizer->Add(row_sizer); } sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); } // Nunchuck { m_nunchuck_settings = new wxStaticBox(this, wxID_ANY, _("Nunchuck")); auto* box_sizer = new wxStaticBoxSizer(m_nunchuck_settings, wxVERTICAL); { auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0); // Deadzone const auto deadzone = (int)(m_settings.axis.deadzone * 100); content_sizer->Add(new wxStaticText(m_nunchuck_settings, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_nunchuck_deadzone = new wxSlider(m_nunchuck_settings, wxID_ANY, deadzone, 0, 100); content_sizer->Add(m_nunchuck_deadzone, 1, wxALL | wxEXPAND, 5); const auto deadzone_text = new wxStaticText(m_nunchuck_settings, wxID_ANY, wxString::Format("%d%%", deadzone)); content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_nunchuck_deadzone->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, new wxControlObject(deadzone_text)); // Range const auto range = (int)(m_settings.axis.range * 100); content_sizer->Add(new wxStaticText(m_nunchuck_settings, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_nunchuck_range = new wxSlider(m_nunchuck_settings, wxID_ANY, range, 50, 200); content_sizer->Add(m_nunchuck_range, 1, wxALL | wxEXPAND, 5); const auto range_text = new wxStaticText(m_nunchuck_settings, wxID_ANY, wxString::Format("%d%%", range)); content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_nunchuck_range->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, new wxControlObject(range_text)); content_sizer->AddSpacer(1); m_nunchuck_draw = new wxInputDraw(m_nunchuck_settings, wxID_ANY, wxDefaultPosition, { 60, 60 }); content_sizer->Add(m_nunchuck_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); box_sizer->Add(content_sizer, 1, wxEXPAND, 0); } sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); } // Classic { m_classic_settings = new wxStaticBox(this, wxID_ANY, _("Classic")); auto* box_sizer = new wxStaticBoxSizer(m_classic_settings, wxVERTICAL); { auto* content_sizer = new wxFlexGridSizer(0, 6, 0, 0); // Deadzone { // Axis const auto deadzone = (int)(m_settings.axis.deadzone * 100); content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_classic_axis_deadzone = new wxSlider(m_classic_settings, wxID_ANY, deadzone, 0, 100); content_sizer->Add(m_classic_axis_deadzone, 1, wxALL | wxEXPAND, 5); const auto deadzone_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", deadzone)); content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_classic_axis_deadzone->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, new wxControlObject(deadzone_text)); } { // Range const auto deadzone = (int)(m_settings.rotation.deadzone * 100); content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_classic_rotation_deadzone = new wxSlider(m_classic_settings, wxID_ANY, deadzone, 0, 100); content_sizer->Add(m_classic_rotation_deadzone, 1, wxALL | wxEXPAND, 5); const auto deadzone_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", deadzone)); content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_classic_rotation_deadzone->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, new wxControlObject(deadzone_text)); } // Range { // Axis const auto range = (int)(m_settings.axis.range * 100); content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_classic_axis_range = new wxSlider(m_classic_settings, wxID_ANY, range, 50, 200); content_sizer->Add(m_classic_axis_range, 1, wxALL | wxEXPAND, 5); const auto range_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", range)); content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_classic_axis_range->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, new wxControlObject(range_text)); } { // Rotation const auto range = (int)(m_settings.rotation.range * 100); content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_classic_rotation_range = new wxSlider(m_classic_settings, wxID_ANY, range, 50, 200); content_sizer->Add(m_classic_rotation_range, 1, wxALL | wxEXPAND, 5); const auto range_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", range)); content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); m_classic_rotation_range->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, new wxControlObject(range_text)); } content_sizer->AddSpacer(1); m_classic_axis_draw = new wxInputDraw(m_classic_settings, wxID_ANY, wxDefaultPosition, { 60, 60 }); content_sizer->Add(m_classic_axis_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); content_sizer->AddSpacer(1); content_sizer->AddSpacer(1); m_classic_rotation_draw = new wxInputDraw(m_classic_settings, wxID_ANY, wxDefaultPosition, { 60, 60 }); content_sizer->Add(m_classic_rotation_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); box_sizer->Add(content_sizer, 1, wxEXPAND, 0); } sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); } { auto* control_sizer = new wxFlexGridSizer(0, 4, 0, 0); control_sizer->AddGrowableCol(3); auto* ok_button = new wxButton(this, wxID_ANY, _("OK")); ok_button->Bind(wxEVT_BUTTON, [this](auto&) { update_settings(); EndModal(wxID_OK); }); control_sizer->Add(ok_button, 0, wxALL, 5); control_sizer->Add(0, 0, 0, wxEXPAND, 5); auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel")); cancel_button->Bind(wxEVT_BUTTON, [this](auto&) { EndModal(wxID_CANCEL); }); control_sizer->Add(cancel_button, 0, wxALL, 5); auto* calibrate_button = new wxButton(this, wxID_ANY, _("Calibrate")); calibrate_button->Bind(wxEVT_BUTTON, [this](auto&) { m_controller->calibrate(); }); control_sizer->Add(calibrate_button, 0, wxALL | wxALIGN_RIGHT, 5); sizer->Add(control_sizer, 0, wxEXPAND, 5); } this->SetSizer(sizer); this->Layout(); this->Fit(); this->Bind(wxEVT_CLOSE_WINDOW, &WiimoteControllerSettings::on_close, this); m_timer = new wxTimer(this); Bind(wxEVT_TIMER, &WiimoteControllerSettings::on_timer, this); m_timer->Start(100); } WiimoteControllerSettings::~WiimoteControllerSettings() { m_controller->stop_rumble(); m_timer->Stop(); } void WiimoteControllerSettings::update_settings() { // update settings m_controller->set_settings(m_settings); if (m_use_motion) m_controller->set_use_motion(m_use_motion->GetValue()); m_controller->set_packet_delay(m_package_delay->GetValue()); } void WiimoteControllerSettings::on_timer(wxTimerEvent& event) { if (m_rumble_time.has_value() && std::chrono::duration_cast(std::chrono::steady_clock::now() - m_rumble_time.value()).count() > 500) { m_controller->stop_rumble(); m_rumble_time = {}; } const auto& default_state = m_controller->get_default_state(); auto state = m_controller->raw_state(); m_controller->apply_axis_setting(state.axis, default_state.axis, m_settings.axis); m_controller->apply_axis_setting(state.rotation, default_state.rotation, m_settings.rotation); m_controller->apply_axis_setting(state.trigger, default_state.trigger, m_settings.trigger); if (m_nunchuck_settings->IsEnabled()) { m_nunchuck_draw->SetDeadzone(m_settings.axis.deadzone); m_nunchuck_draw->SetAxisValue(state.axis); } if (m_classic_settings->IsEnabled()) { m_classic_axis_draw->SetDeadzone(m_settings.axis.deadzone); m_classic_axis_draw->SetAxisValue(state.axis); m_classic_rotation_draw->SetDeadzone(m_settings.rotation.deadzone); m_classic_rotation_draw->SetAxisValue(state.rotation); } wxString label; switch (m_controller->get_extension()) { case NativeWiimoteController::Nunchuck: label = _("Nunchuck"); m_nunchuck_settings->Enable(); m_classic_settings->Disable(); break; case NativeWiimoteController::Classic: label = _("Classic"); m_nunchuck_settings->Disable(); m_classic_settings->Enable(); break; default: m_nunchuck_settings->Disable(); m_classic_settings->Disable(); } if(m_controller->is_mpls_attached()) { const bool empty = label.empty(); if (!empty) label.Append(" ("); label.Append(_("MotionPlus")); if (!empty) label.Append(")"); } if(label.empty()) { label = _("None"); } m_extension_text->SetLabelText(label); } void WiimoteControllerSettings::on_close(wxCloseEvent& event) { if (this->GetReturnCode() == 0 || this->GetReturnCode() == wxID_OK) update_settings(); event.Skip(); } void WiimoteControllerSettings::on_slider_change(wxCommandEvent& event) { update_slider_text(event); const auto new_value = (float)event.GetInt() / 100.0f; auto* obj = event.GetEventObject(); if (obj == m_nunchuck_deadzone || obj == m_classic_axis_deadzone) m_settings.axis.deadzone = new_value; else if (obj == m_nunchuck_range || obj == m_classic_axis_range) m_settings.axis.range = new_value; else if (obj == m_classic_rotation_deadzone) m_settings.rotation.deadzone = new_value; else if (obj == m_classic_rotation_range) m_settings.rotation.range = new_value; } void WiimoteControllerSettings::on_rumble_change(wxCommandEvent& event) { const auto rumble_value = m_rumble->GetValue(); m_settings.rumble = rumble_value ? 1.0f : 0.0f; m_controller->set_rumble(m_settings.rumble); if (rumble_value) m_controller->start_rumble(); else m_controller->stop_rumble(); m_controller->set_rumble(m_rumble_backup); m_rumble_time = std::chrono::steady_clock::now(); } void WiimoteControllerSettings::on_delay_change(wxCommandEvent& event) { update_slider_text(event, "%dms"); } #endif ================================================ FILE: src/gui/wxgui/input/settings/WiimoteControllerSettings.h ================================================ #pragma once #ifdef SUPPORTS_WIIMOTE #include #include #include #include "input/api/Controller.h" #include "input/api/Wiimote/NativeWiimoteController.h" class wxStaticBox; class wxStaticText; class wxCheckBox; class wxInputDraw; class WiimoteControllerSettings : public wxDialog { public: WiimoteControllerSettings(wxWindow* parent, const wxPoint& position, std::shared_ptr controller); ~WiimoteControllerSettings(); private: void update_settings(); std::shared_ptr m_controller; ControllerBase::Settings m_settings; float m_rumble_backup; uint32 m_packet_delay_backup; wxTimer* m_timer; std::optional m_rumble_time{}; wxStaticText* m_extension_text; wxSlider* m_package_delay; wxCheckBox* m_rumble = nullptr; wxCheckBox* m_use_motion = nullptr; wxStaticBox* m_nunchuck_settings; wxSlider* m_nunchuck_deadzone, * m_nunchuck_range; wxInputDraw* m_nunchuck_draw; wxStaticBox* m_classic_settings; wxSlider* m_classic_axis_deadzone, * m_classic_axis_range; wxInputDraw* m_classic_axis_draw; wxSlider* m_classic_rotation_deadzone, * m_classic_rotation_range; wxInputDraw* m_classic_rotation_draw; void on_timer(wxTimerEvent& event); void on_close(wxCloseEvent& event); void on_slider_change(wxCommandEvent& event); void on_rumble_change(wxCommandEvent& event); void on_delay_change(wxCommandEvent& event); }; #endif ================================================ FILE: src/gui/wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp ================================================ #include "wxgui/wxgui.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h" #include "DebugPPCThreadsWindow.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "wxgui/components/wxProgressDialogManager.h" #include #include #include enum { // options REFRESH_ID = wxID_HIGHEST + 1, AUTO_REFRESH_ID, CLOSE_ID, GPLIST_ID, // list context menu options THREADLIST_MENU_BOOST_PRIO_1, THREADLIST_MENU_BOOST_PRIO_5, THREADLIST_MENU_DECREASE_PRIO_1, THREADLIST_MENU_DECREASE_PRIO_5, THREADLIST_MENU_SUSPEND, THREADLIST_MENU_RESUME, THREADLIST_MENU_DUMP_STACK_TRACE, THREADLIST_MENU_PROFILE_THREAD, }; wxBEGIN_EVENT_TABLE(DebugPPCThreadsWindow, wxFrame) EVT_BUTTON(CLOSE_ID, DebugPPCThreadsWindow::OnCloseButton) EVT_BUTTON(REFRESH_ID, DebugPPCThreadsWindow::OnRefreshButton) EVT_CLOSE(DebugPPCThreadsWindow::OnClose) wxEND_EVENT_TABLE() DebugPPCThreadsWindow::DebugPPCThreadsWindow(wxFrame& parent) : wxFrame(&parent, wxID_ANY, _("PPC threads"), wxDefaultPosition, wxSize(930, 280), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER) { auto* sizer = new wxBoxSizer(wxVERTICAL); m_thread_list = new wxListView(this, GPLIST_ID, wxPoint(0, 0), wxSize(930, 240), wxLC_REPORT); m_thread_list->SetFont(wxFont(8, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Courier New")); // wxSystemSettings::GetFont(wxSYS_OEM_FIXED_FONT)); // add columns wxListItem col0; col0.SetId(0); col0.SetText("Address"); col0.SetWidth(75); m_thread_list->InsertColumn(0, col0); wxListItem col1; col1.SetId(1); col1.SetText("Entry"); col1.SetWidth(75); m_thread_list->InsertColumn(1, col1); wxListItem col2; col2.SetId(2); col2.SetText("Stack"); col2.SetWidth(145); m_thread_list->InsertColumn(2, col2); wxListItem col3; col3.SetId(3); col3.SetText("PC"); col3.SetWidth(120); m_thread_list->InsertColumn(3, col3); wxListItem colLR; colLR.SetId(4); colLR.SetText("LR"); colLR.SetWidth(75); m_thread_list->InsertColumn(4, colLR); wxListItem col4; col4.SetId(5); col4.SetText("State"); col4.SetWidth(90); m_thread_list->InsertColumn(5, col4); wxListItem col5; col5.SetId(6); col5.SetText("Affinity"); col5.SetWidth(70); m_thread_list->InsertColumn(6, col5); wxListItem colPriority; colPriority.SetId(7); colPriority.SetText("Priority"); colPriority.SetWidth(80); m_thread_list->InsertColumn(7, colPriority); wxListItem col6; col6.SetId(8); col6.SetText("SliceStart"); col6.SetWidth(110); m_thread_list->InsertColumn(8, col6); wxListItem col7; col7.SetId(9); col7.SetText("SumWakeTime"); col7.SetWidth(110); m_thread_list->InsertColumn(9, col7); wxListItem col8; col8.SetId(10); col8.SetText("ThreadName"); col8.SetWidth(180); m_thread_list->InsertColumn(10, col8); wxListItem col9; col9.SetId(11); col9.SetText("GPR"); col9.SetWidth(180); m_thread_list->InsertColumn(11, col9); wxListItem col10; col10.SetId(12); col10.SetText("Extra info"); col10.SetWidth(180); m_thread_list->InsertColumn(12, col10); sizer->Add(m_thread_list, 1, wxEXPAND | wxALL, 5); auto* row = new wxBoxSizer(wxHORIZONTAL); wxButton* button = new wxButton(this, REFRESH_ID, _("Refresh"), wxPoint(0, 0), wxSize(80, 26)); row->Add(button, 0, wxALL, 5); m_auto_refresh = new wxCheckBox(this, AUTO_REFRESH_ID, _("Auto refresh")); m_auto_refresh->SetValue(true); row->Add(m_auto_refresh, 0, wxEXPAND | wxALL, 5); sizer->Add(row, 0, wxEXPAND | wxALL, 5); m_thread_list->Bind(wxEVT_RIGHT_DOWN, &DebugPPCThreadsWindow::OnThreadListRightClick, this); SetSizer(sizer); RefreshThreadList(); m_timer = new wxTimer(this); this->Bind(wxEVT_TIMER, &DebugPPCThreadsWindow::OnTimer, this); m_timer->Start(250); } DebugPPCThreadsWindow::~DebugPPCThreadsWindow() { m_timer->Stop(); } void DebugPPCThreadsWindow::OnCloseButton(wxCommandEvent& event) { Close(); } void DebugPPCThreadsWindow::OnRefreshButton(wxCommandEvent& event) { RefreshThreadList(); } void DebugPPCThreadsWindow::OnClose(wxCloseEvent& event) { Close(); } void DebugPPCThreadsWindow::OnTimer(wxTimerEvent& event) { if (m_auto_refresh->IsChecked()) RefreshThreadList(); } #define _r(__idx) _swapEndianU32(cafeThread->context.gpr[__idx]) void DebugPPCThreadsWindow::RefreshThreadList() { wxWindowUpdateLocker lock(m_thread_list); long selected_thread = 0; const int selection = m_thread_list->GetFirstSelected(); if (selection != wxNOT_FOUND) selected_thread = m_thread_list->GetItemData(selection); const int scrollPos = m_thread_list->GetScrollPos(0); m_thread_list->DeleteAllItems(); if (activeThreadCount > 0) { __OSLockScheduler(); srwlock_activeThreadList.LockWrite(); for (sint32 i = 0; i < activeThreadCount; i++) { MPTR threadItrMPTR = activeThread[i]; OSThread_t* cafeThread = (OSThread_t*)memory_getPointerFromVirtualOffset(threadItrMPTR); wxListItem item; item.SetId(i); item.SetText(wxString::Format("%08X", threadItrMPTR)); m_thread_list->InsertItem(item); m_thread_list->SetItemData(item, (long)threadItrMPTR); // entry point m_thread_list->SetItem(i, 1, wxString::Format("%08X", cafeThread->entrypoint.GetMPTR())); // stack base (low) m_thread_list->SetItem(i, 2, wxString::Format("%08X - %08X", cafeThread->stackEnd.GetMPTR(), cafeThread->stackBase.GetMPTR())); // pc RPLStoredSymbol* symbol = rplSymbolStorage_getByAddress(cafeThread->context.srr0); wxString pcLabel; if (symbol) pcLabel = wxString::Format("%s (0x%08x)", (const char*)symbol->symbolName, cafeThread->context.srr0); else pcLabel = wxString::Format("%08X", cafeThread->context.srr0); m_thread_list->SetItem(i, 3, pcLabel); // lr m_thread_list->SetItem(i, 4, wxString::Format("%08X", _swapEndianU32(cafeThread->context.lr))); // state OSThread_t::THREAD_STATE threadState = cafeThread->state; wxString threadStateStr = "UNDEFINED"; if (cafeThread->suspendCounter != 0) threadStateStr = "SUSPENDED"; else if (threadState == OSThread_t::THREAD_STATE::STATE_NONE) threadStateStr = "NONE"; else if (threadState == OSThread_t::THREAD_STATE::STATE_READY) threadStateStr = "READY"; else if (threadState == OSThread_t::THREAD_STATE::STATE_RUNNING) threadStateStr = "RUNNING"; else if (threadState == OSThread_t::THREAD_STATE::STATE_WAITING) threadStateStr = "WAITING"; else if (threadState == OSThread_t::THREAD_STATE::STATE_MORIBUND) threadStateStr = "MORIBUND"; m_thread_list->SetItem(i, 5, threadStateStr); // affinity uint8 affinity = cafeThread->attr & 7; uint8 affinityReal = cafeThread->context.affinity; wxString affinityLabel; if (affinity != affinityReal) affinityLabel = wxString::Format("(!) %d%d%d real: %d%d%d", (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, (affinityReal >> 0) & 1, (affinityReal >> 1) & 1, (affinityReal >> 2) & 1); else affinityLabel = wxString::Format("%d%d%d", (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1); m_thread_list->SetItem(i, 6, affinityLabel); // priority sint32 effectivePriority = cafeThread->effectivePriority; m_thread_list->SetItem(i, 7, wxString::Format("%d", effectivePriority)); // last awake in cycles uint64 lastWakeUpTime = cafeThread->wakeUpTime; m_thread_list->SetItem(i, 8, wxString::Format("%" PRIu64, lastWakeUpTime)); // awake time in cycles uint64 awakeTime = cafeThread->totalCycles; m_thread_list->SetItem(i, 9, wxString::Format("%" PRIu64, awakeTime)); // thread name const char* threadName = "NULL"; if (!cafeThread->threadName.IsNull()) threadName = cafeThread->threadName.GetPtr(); m_thread_list->SetItem(i, 10, threadName); // GPR m_thread_list->SetItem(i, 11, wxString::Format("r3 %08x r4 %08x r5 %08x r6 %08x r7 %08x", _r(3), _r(4), _r(5), _r(6), _r(7))); // waiting condition / extra info coreinit::OSMutex* mutex = cafeThread->waitingForMutex; wxString extraInfoLabel; if (mutex) extraInfoLabel = wxString::Format("Mutex 0x%08x (Held by thread 0x%08X Lock-Count: %d)", memory_getVirtualOffsetFromPointer(mutex), mutex->owner.GetMPTR(), (uint32)mutex->lockCount); // OSSetThreadCancelState if (cafeThread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL) extraInfoLabel += "[Cancel requested]"; m_thread_list->SetItem(i, 12, extraInfoLabel); if (selected_thread != 0 && selected_thread == (long)threadItrMPTR) { m_thread_list->Select(i); m_thread_list->Focus(i); } } srwlock_activeThreadList.UnlockWrite(); __OSUnlockScheduler(); } m_thread_list->SetScrollPos(0, scrollPos, true); } void DebugPPCThreadsWindow::DumpStackTrace(OSThread_t* thread) { cemuLog_log(LogType::Force, "Dumping stack trace for thread {0:08x} LR: {1:08x}", memory_getVirtualOffsetFromPointer(thread), _swapEndianU32(thread->context.lr)); DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1]), true); } void DebugPPCThreadsWindow::PresentProfileResults(OSThread_t* thread, const std::unordered_map& samples) { std::vector> sortedSamples; // count samples uint32 totalSampleCount = 0; for (auto& sample : samples) totalSampleCount += sample.second; cemuLog_log(LogType::Force, "--- Thread {:08x} profile results with {:} samples captured ---", MEMPTR(thread).GetMPTR(), totalSampleCount); cemuLog_log(LogType::Force, "Exclusive time, grouped by function:"); // print samples grouped by function sortedSamples.clear(); for (auto& sample : samples) { RPLStoredSymbol* symbol = rplSymbolStorage_getByClosestAddress(sample.first); VAddr sampleAddr = sample.first; if (symbol) sampleAddr = symbol->address; auto it = std::find_if(sortedSamples.begin(), sortedSamples.end(), [sampleAddr](const std::pair& a) { return a.first == sampleAddr; }); if (it != sortedSamples.end()) it->second += sample.second; else sortedSamples.push_back(std::make_pair(sampleAddr, sample.second)); } std::sort(sortedSamples.begin(), sortedSamples.end(), [](const std::pair& a, const std::pair& b) { return a.second > b.second; }); for (auto& sample : sortedSamples) { if (sample.second < 3) continue; VAddr sampleAddr = sample.first; RPLStoredSymbol* symbol = rplSymbolStorage_getByClosestAddress(sample.first); std::string strName; if (symbol) { strName = fmt::format("{}.{}+0x{:x}", (const char*)symbol->libName, (const char*)symbol->symbolName, sampleAddr - symbol->address); } else strName = "Unknown"; cemuLog_log(LogType::Force, "[{:08x}] {:8.2f}% (Samples: {:5}) Symbol: {}", sample.first, (double)(sample.second * 100) / (double)totalSampleCount, sample.second, strName); } } void DebugPPCThreadsWindow::ProfileThreadWorker(OSThread_t* thread) { wxProgressDialogManager progressDialog(this); progressDialog.Create(_("Profiling thread"), _("Capturing samples..."), 1000, // range wxPD_CAN_SKIP); std::unordered_map samples; // loop for one minute uint64 startTime = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); uint32 totalSampleCount = 0; while (true) { // suspend thread coreinit::OSSuspendThread(thread); // wait until thread is not running anymore __OSLockScheduler(); while (coreinit::OSIsThreadRunningNoLock(thread)) { __OSUnlockScheduler(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); __OSLockScheduler(); } uint32 sampleIP = thread->context.srr0; __OSUnlockScheduler(); coreinit::OSResumeThread(thread); // count sample samples[sampleIP]++; totalSampleCount++; if ((totalSampleCount % 50) == 0) { wxString msg = formatWxString(_("Capturing samples... ({:})\nResults will be written to log.txt\n"), totalSampleCount); if (totalSampleCount < 30000) msg.Append(_("Click Skip button for early results with lower accuracy")); else msg.Append(_("Click Skip button to finish")); progressDialog.Update(totalSampleCount * 1000 / 30000, msg); if (progressDialog.IsCancelledOrSkipped()) break; } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } PresentProfileResults(thread, samples); progressDialog.Destroy(); } void DebugPPCThreadsWindow::ProfileThread(OSThread_t* thread) { std::thread profileThread(&DebugPPCThreadsWindow::ProfileThreadWorker, this, thread); profileThread.detach(); } void DebugPPCThreadsWindow::OnThreadListPopupClick(wxCommandEvent& evt) { MPTR threadMPTR = (MPTR)(size_t)static_cast(evt.GetEventObject())->GetClientData(); OSThread_t* osThread = (OSThread_t*)memory_getPointerFromVirtualOffset(threadMPTR); __OSLockScheduler(); if (!coreinit::__OSIsThreadActive(osThread)) { __OSUnlockScheduler(); return; } __OSUnlockScheduler(); // handle command switch (evt.GetId()) { case THREADLIST_MENU_BOOST_PRIO_5: osThread->basePriority = osThread->basePriority - 5; break; case THREADLIST_MENU_BOOST_PRIO_1: osThread->basePriority = osThread->basePriority - 1; break; case THREADLIST_MENU_DECREASE_PRIO_5: osThread->basePriority = osThread->basePriority + 5; break; case THREADLIST_MENU_DECREASE_PRIO_1: osThread->basePriority = osThread->basePriority + 1; break; case THREADLIST_MENU_SUSPEND: coreinit::OSSuspendThread(osThread); break; case THREADLIST_MENU_RESUME: coreinit::OSResumeThread(osThread); break; case THREADLIST_MENU_DUMP_STACK_TRACE: DumpStackTrace(osThread); break; case THREADLIST_MENU_PROFILE_THREAD: ProfileThread(osThread); break; } coreinit::__OSUpdateThreadEffectivePriority(osThread); // update thread list RefreshThreadList(); } void DebugPPCThreadsWindow::OnThreadListRightClick(wxMouseEvent& event) { // Get the item index int hitTestFlag; int itemIndex = m_thread_list->HitTest(event.GetPosition(), hitTestFlag); if (itemIndex == wxNOT_FOUND) return; // select item m_thread_list->Focus(itemIndex); long sel = m_thread_list->GetFirstSelected(); if (sel != wxNOT_FOUND) m_thread_list->Select(sel, false); m_thread_list->Select(itemIndex); // check if thread is still on the list of active threads MPTR threadMPTR = (MPTR)m_thread_list->GetItemData(itemIndex); __OSLockScheduler(); if (!coreinit::__OSIsThreadActive(MEMPTR(threadMPTR))) { __OSUnlockScheduler(); return; } __OSUnlockScheduler(); // create menu entry wxMenu menu; menu.SetClientData((void*)(size_t)threadMPTR); menu.Append(THREADLIST_MENU_BOOST_PRIO_5, _("Boost priority (-5)")); menu.Append(THREADLIST_MENU_BOOST_PRIO_1, _("Boost priority (-1)")); menu.AppendSeparator(); menu.Append(THREADLIST_MENU_DECREASE_PRIO_5, _("Decrease priority (+5)")); menu.Append(THREADLIST_MENU_DECREASE_PRIO_1, _("Decrease priority (+1)")); menu.AppendSeparator(); menu.Append(THREADLIST_MENU_RESUME, _("Resume")); menu.Append(THREADLIST_MENU_SUSPEND, _("Suspend")); menu.AppendSeparator(); menu.Append(THREADLIST_MENU_DUMP_STACK_TRACE, _("Write stack trace to log")); menu.Append(THREADLIST_MENU_PROFILE_THREAD, _("Profile thread")); menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &DebugPPCThreadsWindow::OnThreadListPopupClick, this); PopupMenu(&menu); } void DebugPPCThreadsWindow::Close() { this->Destroy(); } ================================================ FILE: src/gui/wxgui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h ================================================ #pragma once #include class wxListView; class DebugPPCThreadsWindow: public wxFrame { public: DebugPPCThreadsWindow(wxFrame& parent); ~DebugPPCThreadsWindow(); void OnCloseButton(wxCommandEvent& event); void OnRefreshButton(wxCommandEvent& event); void OnClose(wxCloseEvent& event); void RefreshThreadList(); void OnThreadListPopupClick(wxCommandEvent &evt); void OnThreadListRightClick(wxMouseEvent& event); void Close(); private: void ProfileThread(struct OSThread_t* thread); void ProfileThreadWorker(OSThread_t* thread); void PresentProfileResults(OSThread_t* thread, const std::unordered_map& samples); void DumpStackTrace(struct OSThread_t* thread); wxListView* m_thread_list; wxCheckBox* m_auto_refresh; wxTimer* m_timer; void OnTimer(wxTimerEvent& event); wxDECLARE_EVENT_TABLE(); }; ================================================ FILE: src/gui/wxgui/windows/TextureRelationViewer/TextureRelationWindow.cpp ================================================ #include "wxgui/wxgui.h" #include "wxHelper.h" #include "TextureRelationWindow.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" enum { // options REFRESH_ID, CLOSE_ID, TEX_LIST_A_ID, TEX_LIST_B_ID, CHECKBOX_SHOW_ONLY_ACTIVE, CHECKBOX_SHOW_VIEWS, }; wxBEGIN_EVENT_TABLE(TextureRelationViewerWindow, wxFrame) EVT_BUTTON(CLOSE_ID, TextureRelationViewerWindow::OnCloseButton) EVT_BUTTON(REFRESH_ID, TextureRelationViewerWindow::OnRefreshButton) EVT_CHECKBOX(CHECKBOX_SHOW_ONLY_ACTIVE, TextureRelationViewerWindow::OnCheckbox) EVT_CHECKBOX(CHECKBOX_SHOW_VIEWS, TextureRelationViewerWindow::OnCheckbox) EVT_CLOSE(TextureRelationViewerWindow::OnClose) wxEND_EVENT_TABLE() wxListCtrl* textureRelationListA; bool isTextureViewerOpen = false; void openTextureViewer(wxFrame& parentFrame) { if (isTextureViewerOpen) return; auto frame = new TextureRelationViewerWindow(parentFrame); frame->Show(true); } TextureRelationViewerWindow::TextureRelationViewerWindow(wxFrame& parent) : wxFrame(&parent, wxID_ANY, _("Texture cache"), wxDefaultPosition, wxSize(1000, 480), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER) { isTextureViewerOpen = true; this->showOnlyActive = false; this->showTextureViews = true; wxPanel* mainPane = new wxPanel(this); wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); textureRelationListA = new wxListCtrl(mainPane, TEX_LIST_A_ID, wxPoint(0, 0), wxSize(1008, 440), wxLC_REPORT); textureRelationListA->SetFont(wxFont(8, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Courier New"));//wxSystemSettings::GetFont(wxSYS_OEM_FIXED_FONT)); // add columns wxListItem colType; sint32 columnIndex = 0; colType.SetId(columnIndex); columnIndex++; colType.SetText("Type"); colType.SetWidth(85); textureRelationListA->InsertColumn(columnIndex-1, colType); wxListItem colPhysAddr; colPhysAddr.SetId(columnIndex); columnIndex++; colPhysAddr.SetText("PhysAddr"); colPhysAddr.SetWidth(80); textureRelationListA->InsertColumn(columnIndex-1, colPhysAddr); wxListItem colPhysMipAddr; colPhysMipAddr.SetId(columnIndex); columnIndex++; colPhysMipAddr.SetText("MipPAddr"); colPhysMipAddr.SetWidth(80); textureRelationListA->InsertColumn(columnIndex-1, colPhysMipAddr); wxListItem colDim; colDim.SetId(columnIndex); columnIndex++; colDim.SetText("Dim"); colDim.SetWidth(80); textureRelationListA->InsertColumn(columnIndex-1, colDim); wxListItem colResolution; colResolution.SetId(columnIndex); columnIndex++; colResolution.SetText("Resolution"); colResolution.SetWidth(110); textureRelationListA->InsertColumn(columnIndex-1, colResolution); wxListItem colFormat; colFormat.SetId(columnIndex); columnIndex++; colFormat.SetText("Format"); colFormat.SetWidth(70); textureRelationListA->InsertColumn(columnIndex-1, colFormat); wxListItem colPitch; colPitch.SetId(columnIndex); columnIndex++; colPitch.SetText("Pitch"); colPitch.SetWidth(80); textureRelationListA->InsertColumn(columnIndex-1, colPitch); wxListItem colTilemode; colTilemode.SetId(columnIndex); columnIndex++; colTilemode.SetText("Tilemode"); colTilemode.SetWidth(80); textureRelationListA->InsertColumn(columnIndex-1, colTilemode); wxListItem colSliceRange; colSliceRange.SetId(columnIndex); columnIndex++; colSliceRange.SetText("SliceRange"); colSliceRange.SetWidth(90); textureRelationListA->InsertColumn(columnIndex-1, colSliceRange); wxListItem colMipRange; colMipRange.SetId(columnIndex); columnIndex++; colMipRange.SetText("MipRange"); colMipRange.SetWidth(90); textureRelationListA->InsertColumn(columnIndex-1, colMipRange); wxListItem colAge; colAge.SetId(columnIndex); columnIndex++; colAge.SetText("Last access"); colAge.SetWidth(90); textureRelationListA->InsertColumn(columnIndex - 1, colAge); wxListItem colOverwriteRes; colOverwriteRes.SetId(columnIndex); columnIndex++; colOverwriteRes.SetText("OverwriteRes"); colOverwriteRes.SetWidth(110); textureRelationListA->InsertColumn(columnIndex - 1, colOverwriteRes); wxBoxSizer* sizerBottom = new wxBoxSizer(wxHORIZONTAL); sizer->Add(textureRelationListA, 1, wxEXPAND | wxBOTTOM, 0); wxButton* button = new wxButton(mainPane, REFRESH_ID, _("Refresh")); sizerBottom->Add(button, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM | wxTOP | wxLEFT, 10); wxCheckBox* checkboxShowOnlyActive = new wxCheckBox(mainPane, CHECKBOX_SHOW_ONLY_ACTIVE, _("Show only active")); sizerBottom->Add(checkboxShowOnlyActive, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM | wxTOP | wxLEFT, 10); wxCheckBox* checkboxShowViews = new wxCheckBox(mainPane, CHECKBOX_SHOW_VIEWS, _("Show views")); sizerBottom->Add(checkboxShowViews, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM | wxTOP | wxLEFT, 10); checkboxShowViews->SetValue(true); textureRelationListA->Bind(wxEVT_RIGHT_DOWN, &TextureRelationViewerWindow::OnTextureListRightClick, this); sizer->Add( sizerBottom, 0, // vertically unstretchable wxALIGN_LEFT); mainPane->SetSizer(sizer); RefreshTextureList(); } TextureRelationViewerWindow::~TextureRelationViewerWindow() { isTextureViewerOpen = false; } void TextureRelationViewerWindow::OnCloseButton(wxCommandEvent& event) { Close(); } void TextureRelationViewerWindow::OnRefreshButton(wxCommandEvent& event) { RefreshTextureList(); } void TextureRelationViewerWindow::OnCheckbox(wxCommandEvent& event) { if (event.GetId() == CHECKBOX_SHOW_ONLY_ACTIVE) { showOnlyActive = event.IsChecked(); RefreshTextureList(); } else if (event.GetId() == CHECKBOX_SHOW_VIEWS) { showTextureViews = event.IsChecked(); RefreshTextureList(); } } void TextureRelationViewerWindow::OnClose(wxCloseEvent& event) { Close(); } void TextureRelationViewerWindow::_setTextureRelationListItemTexture(wxListCtrl* uiList, sint32 rowIndex, struct LatteTextureInformation* texInfo) { wxString typeLabel; // count number of alternative views for base view sint32 alternativeViewCount = texInfo->alternativeViewCount; if (texInfo->isUpdatedOnGPU) typeLabel = "TEXTURE*"; else typeLabel = "TEXTURE"; if (alternativeViewCount > 0) { typeLabel += wxString::Format("(%d)", alternativeViewCount + 1); } wxListItem item; item.SetId(rowIndex); item.SetText(typeLabel); item.SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); uiList->InsertItem(item); sint32 columnIndex = 1; // phys address uiList->SetItem(rowIndex, columnIndex, wxString::Format("%08X", texInfo->physAddress)); columnIndex++; // phys mip address uiList->SetItem(rowIndex, columnIndex, wxString::Format("%08X", texInfo->physMipAddress)); columnIndex++; // dim wxString dimLabel; if (texInfo->dim == Latte::E_DIM::DIM_2D) dimLabel = "2D"; else if (texInfo->dim == Latte::E_DIM::DIM_2D_ARRAY) dimLabel = "2D_ARRAY"; else if (texInfo->dim == Latte::E_DIM::DIM_3D) dimLabel = "3D"; else if (texInfo->dim == Latte::E_DIM::DIM_CUBEMAP) dimLabel = "CUBEMAP"; else if (texInfo->dim == Latte::E_DIM::DIM_1D) dimLabel = "1D"; else if (texInfo->dim == Latte::E_DIM::DIM_2D_MSAA) dimLabel = "2D_MSAA"; else if (texInfo->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) dimLabel = "2D_MS_ARRAY"; else dimLabel = "UKN"; uiList->SetItem(rowIndex, columnIndex, dimLabel); columnIndex++; // resolution wxString resolutionLabel; if (texInfo->depth == 1) resolutionLabel = wxString::Format("%dx%d", texInfo->width, texInfo->height); else resolutionLabel = wxString::Format("%dx%dx%d", texInfo->width, texInfo->height, texInfo->depth); uiList->SetItem(rowIndex, columnIndex, resolutionLabel); columnIndex++; // format wxString formatLabel; if(texInfo->isDepth) formatLabel = wxString::Format("%04x(d)", (uint32)texInfo->format); else formatLabel = wxString::Format("%04x", (uint32)texInfo->format); uiList->SetItem(rowIndex, columnIndex, formatLabel); columnIndex++; // pitch uiList->SetItem(rowIndex, columnIndex, wxString::Format("%d", texInfo->pitch)); columnIndex++; // tilemode uiList->SetItem(rowIndex, columnIndex, wxString::Format("%d", (int)texInfo->tileMode)); columnIndex++; // sliceRange uiList->SetItem(rowIndex, columnIndex, ""); columnIndex++; // mipRange uiList->SetItem(rowIndex, columnIndex, texInfo->mipLevels == 1 ? "1 mip" : wxString::Format("%d mips", texInfo->mipLevels)); columnIndex++; // last access uiList->SetItem(rowIndex, columnIndex, wxString::Format("%lus", (GetTickCount() - texInfo->lastAccessTick + 499) / 1000)); columnIndex++; // overwrite resolution wxString overwriteResLabel; if (texInfo->overwriteInfo.hasResolutionOverwrite) { if(texInfo->overwriteInfo.depth != 1 || texInfo->depth != 1) overwriteResLabel = wxString::Format("%dx%dx%d", texInfo->overwriteInfo.width, texInfo->overwriteInfo.height, texInfo->overwriteInfo.depth); else overwriteResLabel = wxString::Format("%dx%d", texInfo->overwriteInfo.width, texInfo->overwriteInfo.height); } uiList->SetItem(rowIndex, columnIndex, overwriteResLabel); columnIndex++; } void TextureRelationViewerWindow::_setTextureRelationListItemView(wxListCtrl* uiList, sint32 rowIndex, struct LatteTextureInformation* texInfo, struct LatteTextureViewInformation* viewInfo) { // count number of alternative views sint32 alternativeViewCount = 0; // todo // set type string // find and handle highlight entry wxListItem item; item.SetId(rowIndex); item.SetText(alternativeViewCount == 0 ? "> VIEW" : wxString::Format("> VIEW(%d)", alternativeViewCount+1)); item.SetBackgroundColour(wxHelper::CalculateAccentColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW))); uiList->InsertItem(item); //uiList->SetItemPtrData(item, (wxUIntPtr)viewInfo); sint32 columnIndex = 1; // phys address uiList->SetItem(rowIndex, columnIndex, ""); columnIndex++; // phys mip address uiList->SetItem(rowIndex, columnIndex, ""); columnIndex++; // dim wxString dimLabel; if (viewInfo->dim == Latte::E_DIM::DIM_2D) dimLabel = "2D"; else if (viewInfo->dim == Latte::E_DIM::DIM_2D_ARRAY) dimLabel = "2D_ARRAY"; else if (viewInfo->dim == Latte::E_DIM::DIM_3D) dimLabel = "3D"; else if (viewInfo->dim == Latte::E_DIM::DIM_CUBEMAP) dimLabel = "CUBEMAP"; else if (viewInfo->dim == Latte::E_DIM::DIM_1D) dimLabel = "1D"; else if (viewInfo->dim == Latte::E_DIM::DIM_2D_MSAA) dimLabel = "2D_MSAA"; else if (viewInfo->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) dimLabel = "2D_ARRAY_MSAA"; else dimLabel = "UKN"; uiList->SetItem(rowIndex, columnIndex, dimLabel); columnIndex++; // resolution uiList->SetItem(rowIndex, columnIndex, ""); columnIndex++; // format uiList->SetItem(rowIndex, columnIndex, wxString::Format("%04x", (uint32)viewInfo->format)); columnIndex++; // pitch uiList->SetItem(rowIndex, columnIndex, ""); columnIndex++; // tilemode uiList->SetItem(rowIndex, columnIndex, ""); columnIndex++; // sliceRange uiList->SetItem(rowIndex, columnIndex, wxString::Format("%d-%d", viewInfo->firstSlice, viewInfo->firstSlice+ viewInfo->numSlice-1)); columnIndex++; // mipRange uiList->SetItem(rowIndex, columnIndex, wxString::Format("%d-%d", viewInfo->firstMip, viewInfo->firstMip + viewInfo->numMip - 1)); columnIndex++; // last access uiList->SetItem(rowIndex, columnIndex, ""); columnIndex++; } void TextureRelationViewerWindow::RefreshTextureList() { int scrollPos = textureRelationListA->GetScrollPos(wxVERTICAL); textureRelationListA->DeleteAllItems(); std::vector texCache = LatteTexture_QueryCacheInfo(); // sort by physAddr in ascending order for (sint32 i1 = 0; i1 < texCache.size(); i1++) { for (sint32 i2 = i1+1; i2 < texCache.size(); i2++) { if (texCache[i1].physAddress > texCache[i2].physAddress) { std::swap(texCache[i1], texCache[i2]); } } } textureRelationListA->Freeze(); sint32 rowIndex = 0; uint32 currentTick = GetTickCount(); for (auto& tex : texCache) { uint32 timeSinceLastAccess = currentTick - tex.lastAccessTick; if (showOnlyActive && timeSinceLastAccess > 3000) continue; // hide textures which haven't been updated in more than 3 seconds _setTextureRelationListItemTexture(textureRelationListA, rowIndex, &tex); rowIndex++; if (showTextureViews) { for (auto& view : tex.views) { _setTextureRelationListItemView(textureRelationListA, rowIndex, &tex, &view); rowIndex++; } } } textureRelationListA->Thaw(); long itemCount = textureRelationListA->GetItemCount(); if (itemCount > 0) textureRelationListA->EnsureVisible(std::min(itemCount - 1, scrollPos + textureRelationListA->GetCountPerPage() - 1)); } void TextureRelationViewerWindow::OnTextureListRightClick(wxMouseEvent& event) { } void TextureRelationViewerWindow::Close() { this->Destroy(); } ================================================ FILE: src/gui/wxgui/windows/TextureRelationViewer/TextureRelationWindow.h ================================================ #pragma once #include class wxListCtrl; class TextureRelationViewerWindow : public wxFrame { public: TextureRelationViewerWindow(wxFrame& parent); ~TextureRelationViewerWindow(); void OnCloseButton(wxCommandEvent& event); void OnRefreshButton(wxCommandEvent& event); void OnCheckbox(wxCommandEvent& event); void OnClose(wxCloseEvent& event); void RefreshTextureList(); void OnTextureListRightClick(wxMouseEvent& event); void Close(); private: wxDECLARE_EVENT_TABLE(); void _setTextureRelationListItemTexture(wxListCtrl* uiList, sint32 rowIndex, struct LatteTextureInformation* texInfo); void _setTextureRelationListItemView(wxListCtrl* uiList, sint32 rowIndex, struct LatteTextureInformation* texInfo, struct LatteTextureViewInformation* viewInfo); bool showOnlyActive; bool showTextureViews; }; void openTextureViewer(wxFrame& parentFrame); ================================================ FILE: src/gui/wxgui/wxCemuConfig.cpp ================================================ #include "wxCemuConfig.h" #include "Common/precompiled.h" #include "config/CemuConfig.h" #include "config/XMLConfig.h" #include "util/helpers/helpers.h" #include XMLWxCemuConfig_t g_wxConfig(&GetConfigHandle); void wxCemuConfig::AddRecentlyLaunchedFile(std::string_view file) { recent_launch_files.insert(recent_launch_files.begin(), std::string(file)); RemoveDuplicatesKeepOrder(recent_launch_files); while (recent_launch_files.size() > kMaxRecentEntries) recent_launch_files.pop_back(); } void wxCemuConfig::AddRecentNfcFile(std::string_view file) { recent_nfc_files.insert(recent_nfc_files.begin(), std::string(file)); RemoveDuplicatesKeepOrder(recent_nfc_files); while (recent_nfc_files.size() > kMaxRecentEntries) recent_nfc_files.pop_back(); } void wxCemuConfig::Load(XMLConfigParser& parser) { language = parser.get("language", wxLANGUAGE_DEFAULT); msw_theme = parser.get("msw_theme", msw_theme); use_discord_presence = parser.get("use_discord_presence", true); fullscreen_menubar = parser.get("fullscreen_menubar", false); feral_gamemode = parser.get("feral_gamemode", false); check_update = parser.get("check_update", check_update); receive_untested_updates = parser.get("receive_untested_updates", receive_untested_updates); save_screenshot = parser.get("save_screenshot", save_screenshot); did_show_vulkan_warning = parser.get("vk_warning", did_show_vulkan_warning); did_show_graphic_pack_download = parser.get("gp_download", did_show_graphic_pack_download); did_show_macos_disclaimer = parser.get("macos_disclaimer", did_show_macos_disclaimer); fullscreen = parser.get("fullscreen", fullscreen); window_position.x = parser.get("window_position").get("x", -1); window_position.y = parser.get("window_position").get("y", -1); window_size.x = parser.get("window_size").get("x", -1); window_size.y = parser.get("window_size").get("y", -1); window_maximized = parser.get("window_maximized", false); pad_open = parser.get("open_pad", false); pad_position.x = parser.get("pad_position").get("x", -1); pad_position.y = parser.get("pad_position").get("y", -1); pad_size.x = parser.get("pad_size").get("x", -1); pad_size.y = parser.get("pad_size").get("y", -1); pad_maximized = parser.get("pad_maximized", false); auto gamelist = parser.get("GameList"); game_list_style = gamelist.get("style", 0); game_list_column_order = gamelist.get("order", ""); show_icon_column = parser.get("show_icon_column", true); // return default width if value in config file out of range auto loadColumnSize = [&gamelist](const char* name, uint32 defaultWidth) { sint64 val = gamelist.get(name, DefaultColumnSize::name); if (val < 0 || val > (sint64)std::numeric_limits::max) return defaultWidth; return static_cast(val); }; column_width.name = loadColumnSize("name_width", DefaultColumnSize::name); column_width.version = loadColumnSize("version_width", DefaultColumnSize::version); column_width.dlc = loadColumnSize("dlc_width", DefaultColumnSize::dlc); column_width.game_time = loadColumnSize("game_time_width", DefaultColumnSize::game_time); column_width.game_started = loadColumnSize("game_started_width", DefaultColumnSize::game_started); column_width.region = loadColumnSize("region_width", DefaultColumnSize::region); column_width.title_id = loadColumnSize("title_id", DefaultColumnSize::title_id); recent_launch_files.clear(); auto launch_parser = parser.get("RecentLaunchFiles"); for (auto element = launch_parser.get("Entry"); element.valid(); element = launch_parser.get("Entry", element)) { const std::string path = element.value(""); if (path.empty()) continue; try { recent_launch_files.emplace_back(path); } catch (const std::exception&) { cemuLog_log(LogType::Force, "config load error: can't load recently launched game file: {}", path); } } recent_nfc_files.clear(); auto nfc_parser = parser.get("RecentNFCFiles"); for (auto element = nfc_parser.get("Entry"); element.valid(); element = nfc_parser.get("Entry", element)) { const std::string path = element.value(""); if (path.empty()) continue; try { recent_nfc_files.emplace_back(path); } catch (const std::exception&) { cemuLog_log(LogType::Force, "config load error: can't load recently launched nfc file: {}", path); } } // hotkeys auto xml_hotkeys = parser.get("Hotkeys"); hotkeys.modifiers = xml_hotkeys.get("modifiers", sHotkeyCfg{}); hotkeys.exitFullscreen = xml_hotkeys.get("ExitFullscreen", sHotkeyCfg{uKeyboardHotkey{WXK_ESCAPE}}); hotkeys.toggleFullscreen = xml_hotkeys.get("ToggleFullscreen", sHotkeyCfg{uKeyboardHotkey{WXK_F11}}); hotkeys.toggleFullscreenAlt = xml_hotkeys.get("ToggleFullscreenAlt", sHotkeyCfg{uKeyboardHotkey{WXK_CONTROL_M, true}}); // ALT+ENTER hotkeys.takeScreenshot = xml_hotkeys.get("TakeScreenshot", sHotkeyCfg{uKeyboardHotkey{WXK_F12}}); hotkeys.toggleFastForward = xml_hotkeys.get("ToggleFastForward", sHotkeyCfg{}); hotkeys.exitApplication = xml_hotkeys.get("ExitApplication", sHotkeyCfg{}); #ifdef CEMU_DEBUG_ASSERT hotkeys.endEmulation = xml_hotkeys.get("EndEmulation", sHotkeyCfg{uKeyboardHotkey{WXK_F5}}); #endif } void wxCemuConfig::Save(XMLConfigParser& config) { // general settings config.set("language", language); config.set("msw_theme", msw_theme); config.set("use_discord_presence", use_discord_presence); config.set("fullscreen_menubar", fullscreen_menubar); config.set("feral_gamemode", feral_gamemode); config.set("check_update", check_update); config.set("receive_untested_updates", receive_untested_updates); config.set("save_screenshot", save_screenshot); config.set("vk_warning", did_show_vulkan_warning); config.set("gp_download", did_show_graphic_pack_download); config.set("macos_disclaimer", did_show_macos_disclaimer); config.set("fullscreen", fullscreen); auto wpos = config.set("window_position"); wpos.set("x", window_position.x); wpos.set("y", window_position.y); auto wsize = config.set("window_size"); wsize.set("x", window_size.x); wsize.set("y", window_size.y); config.set("window_maximized", window_maximized); config.set("open_pad", pad_open); auto ppos = config.set("pad_position"); ppos.set("x", pad_position.x); ppos.set("y", pad_position.y); auto psize = config.set("pad_size"); psize.set("x", pad_size.x); psize.set("y", pad_size.y); config.set("pad_maximized", pad_maximized); config.set("show_icon_column", show_icon_column); auto gamelist = config.set("GameList"); gamelist.set("style", game_list_style); gamelist.set("order", game_list_column_order); gamelist.set("name_width", column_width.name); gamelist.set("version_width", column_width.version); gamelist.set("dlc_width", column_width.dlc); gamelist.set("game_time_width", column_width.game_time); gamelist.set("game_started_width", column_width.game_started); gamelist.set("region_width", column_width.region); gamelist.set("title_id", column_width.title_id); auto launch_files_parser = config.set("RecentLaunchFiles"); for (const auto& entry : recent_launch_files) { launch_files_parser.set("Entry", entry.c_str()); } auto nfc_files_parser = config.set("RecentNFCFiles"); for (const auto& entry : recent_nfc_files) { nfc_files_parser.set("Entry", entry.c_str()); } // hotkeys auto xml_hotkeys = config.set("Hotkeys"); xml_hotkeys.set("modifiers", hotkeys.modifiers); xml_hotkeys.set("ExitFullscreen", hotkeys.exitFullscreen); xml_hotkeys.set("ToggleFullscreen", hotkeys.toggleFullscreen); xml_hotkeys.set("ToggleFullscreenAlt", hotkeys.toggleFullscreenAlt); xml_hotkeys.set("TakeScreenshot", hotkeys.takeScreenshot); xml_hotkeys.set("ToggleFastForward", hotkeys.toggleFastForward); xml_hotkeys.set("ExitApplication", hotkeys.exitApplication); } ================================================ FILE: src/gui/wxgui/wxCemuConfig.h ================================================ #pragma once #include "config/CemuConfig.h" #include "config/XMLConfig.h" #include "util/math/vector2.h" #include "wxgui.h" namespace DefaultColumnSize { enum : uint32 { name = 500u, version = 60u, dlc = 50u, game_time = 140u, game_started = 160u, region = 80u, title_id = 160u }; }; enum class MSWThemeOption : int { kAuto = 0, kLight = 1, kDark = 2, }; ENABLE_ENUM_ITERATORS(MSWThemeOption, MSWThemeOption::kAuto, MSWThemeOption::kDark); typedef union { struct { uint16 key : 13; // enough bits for all keycodes uint16 alt : 1; uint16 ctrl : 1; uint16 shift : 1; }; uint16 raw; } uKeyboardHotkey; typedef sint16 ControllerHotkey_t; struct sHotkeyCfg { static constexpr uint8 keyboardNone{WXK_NONE}; static constexpr sint8 controllerNone{-1}; // no enums to work with, but buttons start from 0 uKeyboardHotkey keyboard{keyboardNone}; ControllerHotkey_t controller{controllerNone}; /* for defaults */ sHotkeyCfg(const uKeyboardHotkey& keyboard = {keyboardNone}, const ControllerHotkey_t& controller = {controllerNone}) : keyboard(keyboard), controller(controller) {}; /* for reading from xml */ sHotkeyCfg(const char* xml_values) { std::istringstream iss(xml_values); iss >> keyboard.raw >> controller; } }; template<> struct fmt::formatter : formatter { template auto format(const sHotkeyCfg c, FormatContext& ctx) const { std::string xml_values = fmt::format("{} {}", c.keyboard.raw, c.controller); return formatter::format(xml_values, ctx); } }; struct wxCemuConfig { ConfigValue language{wxLANGUAGE_DEFAULT}; ConfigValue msw_theme { static_cast(MSWThemeOption::kAuto) }; ConfigValue use_discord_presence{true}; ConfigValue fullscreen{ false }; ConfigValue fullscreen_menubar{false}; ConfigValue feral_gamemode{false}; // max 15 entries static constexpr size_t kMaxRecentEntries = 15; std::vector recent_launch_files; std::vector recent_nfc_files; Vector2i window_position{-1, -1}; Vector2i window_size{-1, -1}; ConfigValue window_maximized; ConfigValue pad_open; Vector2i pad_position{-1, -1}; Vector2i pad_size{-1, -1}; ConfigValue pad_maximized; ConfigValue check_update{true}; ConfigValue receive_untested_updates{false}; ConfigValue save_screenshot{true}; ConfigValue did_show_vulkan_warning{false}; ConfigValue did_show_graphic_pack_download{false}; // no longer used but we keep the config value around in case people downgrade Cemu. Despite the name this was used for the Getting Started dialog ConfigValue did_show_macos_disclaimer{false}; ConfigValue show_icon_column{true}; int game_list_style = 0; std::string game_list_column_order; struct { uint32 name = DefaultColumnSize::name; uint32 version = DefaultColumnSize::version; uint32 dlc = DefaultColumnSize::dlc; uint32 game_time = DefaultColumnSize::game_time; uint32 game_started = DefaultColumnSize::game_started; uint32 region = DefaultColumnSize::region; uint32 title_id = 0; } column_width{}; // hotkeys struct { sHotkeyCfg modifiers; sHotkeyCfg toggleFullscreen; sHotkeyCfg toggleFullscreenAlt; sHotkeyCfg exitFullscreen; sHotkeyCfg takeScreenshot; sHotkeyCfg toggleFastForward; sHotkeyCfg exitApplication; #ifdef CEMU_DEBUG_ASSERT sHotkeyCfg endEmulation; #endif } hotkeys{}; void AddRecentlyLaunchedFile(std::string_view file); void AddRecentNfcFile(std::string_view file); void Load(XMLConfigParser& parser); void Save(XMLConfigParser& parser); }; typedef XMLChildConfig XMLWxCemuConfig_t; extern XMLWxCemuConfig_t g_wxConfig; inline XMLWxCemuConfig_t& GetWxGuiConfigHandle() { return g_wxConfig; } inline wxCemuConfig& GetWxGUIConfig() { return GetWxGuiConfigHandle().Data(); } ================================================ FILE: src/gui/wxgui/wxHelper.h ================================================ #pragma once #include #include #include namespace wxHelper { inline fs::path MakeFSPath(const wxString& str) { auto tmpUtf8 = str.ToUTF8(); auto sv = std::basic_string_view((const char8_t*)tmpUtf8.data(), tmpUtf8.length()); return fs::path(sv); } inline wxString FromPath(const fs::path& path) { std::string str = _pathToUtf8(path); return wxString::FromUTF8(str); } inline wxColour CalculateAccentColour(const wxColour& bgColour) { const uint32 bgLightness = (bgColour.GetRed() + bgColour.GetGreen() + bgColour.GetBlue()) / 3; const bool isDarkTheme = bgLightness < 128; wxColour bgColourSecondary = bgColour.ChangeLightness(isDarkTheme ? 110 : 90); // color for even rows // for very light themes we'll use a blue tint to match the older Windows Cemu look if (bgLightness > 250) bgColourSecondary = wxColour(bgColour.Red() - 13, bgColour.Green() - 6, bgColour.Blue() - 2); return bgColourSecondary; } static wxBitmap LoadThemedBitmapFromPNG(const uint8* data, size_t size, const wxColour& tint) { wxMemoryInputStream strm(data, size); wxImage img(strm, wxBITMAP_TYPE_PNG); img.Replace(0x00, 0x00, 0x00, tint.Red(), tint.Green(), tint.Blue()); return wxBitmap(img); } }; ================================================ FILE: src/gui/wxgui/wxWindowSystem.cpp ================================================ #include "input/HotkeySettings.h" #include "interface/WindowSystem.h" #include "helpers/wxHelpers.h" #if BOOST_OS_LINUX || BOOST_OS_BSD #include #include #include #include #include #ifdef HAS_WAYLAND #include #endif #endif #if BOOST_OS_MACOS #include #endif #include "wxgui/wxgui.h" #include "wxgui/CemuApp.h" #include "wxgui/MainWindow.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "config/ActiveSettings.h" #include "config/NetworkSettings.h" #include "config/CemuConfig.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/CafeSystem.h" WindowSystem::WindowInfo g_window_info{}; std::shared_mutex g_mutex; MainWindow* g_mainFrame = nullptr; #if BOOST_OS_WINDOWS void _wxLaunch() { SetThreadName("MainThread_UI"); wxEntry(); } #endif void WindowSystem::Create() { SetThreadName("cemu"); #if BOOST_OS_WINDOWS // on Windows wxWidgets there is a bug where wxDirDialog->ShowModal will deadlock in Windows internals somehow // moving the UI thread off the main thread fixes this std::thread t = std::thread(_wxLaunch); t.join(); #else int argc = 0; char* argv[1]{}; wxEntry(argc, argv); #endif } void WindowSystem::ShowErrorDialog(std::string_view message, std::string_view title, std::optional /*errorId*/) { wxString caption; if (title.empty()) caption = wxASCII_STR(wxMessageBoxCaptionStr); else caption = wxString::FromUTF8(title); wxMessageBox(wxString::FromUTF8(message), caption, wxOK | wxCENTRE | wxICON_ERROR); } WindowSystem::WindowInfo& WindowSystem::GetWindowInfo() { return g_window_info; } void WindowSystem::UpdateWindowTitles(bool isIdle, bool isLoading, double fps) { std::string windowText; windowText = BUILD_VERSION_WITH_NAME_STRING; if (isIdle) { if (g_mainFrame) g_mainFrame->AsyncSetTitle(windowText); return; } if (isLoading) { windowText.append(" - Loading..."); if (g_mainFrame) g_mainFrame->AsyncSetTitle(windowText); return; } const char* renderer = ""; if (g_renderer) { switch (g_renderer->GetType()) { case RendererAPI::OpenGL: renderer = "[OpenGL]"; break; case RendererAPI::Vulkan: renderer = "[Vulkan]"; break; #if ENABLE_METAL case RendererAPI::Metal: renderer = "[Metal]"; break; #endif default:; } } // get GPU vendor/mode const char* graphicMode = "[Generic]"; if (LatteGPUState.glVendor == GLVENDOR_AMD) graphicMode = "[AMD GPU]"; else if (LatteGPUState.glVendor == GLVENDOR_INTEL) graphicMode = "[Intel GPU]"; else if (LatteGPUState.glVendor == GLVENDOR_NVIDIA) graphicMode = "[NVIDIA GPU]"; else if (LatteGPUState.glVendor == GLVENDOR_APPLE) graphicMode = "[Apple GPU]"; const uint64 titleId = CafeSystem::GetForegroundTitleId(); windowText.append(fmt::format(" - FPS: {:.2f} {} {} [TitleId: {:08x}-{:08x}]", (double)fps, renderer, graphicMode, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF))); if (ActiveSettings::IsOnlineEnabled()) { if (ActiveSettings::GetNetworkService() == NetworkService::Nintendo) windowText.append(" [Online]"); else if (ActiveSettings::GetNetworkService() == NetworkService::Pretendo) windowText.append(" [Online-Pretendo]"); else if (ActiveSettings::GetNetworkService() == NetworkService::Custom) windowText.append(" [Online-" + GetNetworkConfig().networkname.GetValue() + "]"); } windowText.append(" "); windowText.append(CafeSystem::GetForegroundTitleName()); // append region CafeConsoleRegion region = CafeSystem::GetForegroundTitleRegion(); uint16 titleVersion = CafeSystem::GetForegroundTitleVersion(); if (region == CafeConsoleRegion::JPN) windowText.append(fmt::format(" [JP v{}]", titleVersion)); else if (region == CafeConsoleRegion::USA) windowText.append(fmt::format(" [US v{}]", titleVersion)); else if (region == CafeConsoleRegion::EUR) windowText.append(fmt::format(" [EU v{}]", titleVersion)); else windowText.append(fmt::format(" [v{}]", titleVersion)); std::shared_lock lock(g_mutex); if (g_mainFrame) { g_mainFrame->AsyncSetTitle(windowText); auto* pad = g_mainFrame->GetPadView(); if (pad) pad->AsyncSetTitle(fmt::format("{} - FPS: {:.02f}", _("GamePad View").utf8_string(), fps)); } } void WindowSystem::GetWindowSize(int& w, int& h) { w = g_window_info.width; h = g_window_info.height; } void WindowSystem::GetPadWindowSize(int& w, int& h) { if (g_window_info.pad_open) { w = g_window_info.pad_width; h = g_window_info.pad_height; } else { w = 0; h = 0; } } void WindowSystem::GetWindowPhysSize(int& w, int& h) { w = g_window_info.phys_width; h = g_window_info.phys_height; } void WindowSystem::GetPadWindowPhysSize(int& w, int& h) { if (g_window_info.pad_open) { w = g_window_info.phys_pad_width; h = g_window_info.phys_pad_height; } else { w = 0; h = 0; } } double WindowSystem::GetWindowDPIScale() { return g_window_info.dpi_scale; } double WindowSystem::GetPadDPIScale() { return g_window_info.pad_open ? g_window_info.pad_dpi_scale.load() : 1.0; } bool WindowSystem::IsPadWindowOpen() { return g_window_info.pad_open; } bool WindowSystem::IsKeyDown(uint32 key) { return g_window_info.get_keystate(key); } bool WindowSystem::IsKeyDown(PlatformKeyCodes platformKey) { uint32 key = 0; switch (platformKey) { #if BOOST_OS_WINDOWS case PlatformKeyCodes::LCONTROL: key = VK_LCONTROL; break; case PlatformKeyCodes::RCONTROL: key = VK_RCONTROL; break; case PlatformKeyCodes::TAB: key = VK_TAB; break; case PlatformKeyCodes::ESCAPE: key = VK_ESCAPE; break; #elif BOOST_OS_LINUX || BOOST_OS_BSD case PlatformKeyCodes::LCONTROL: key = GDK_KEY_Control_L; break; case PlatformKeyCodes::RCONTROL: key = GDK_KEY_Control_R; break; case PlatformKeyCodes::TAB: key = GDK_KEY_Tab; break; case PlatformKeyCodes::ESCAPE: key = GDK_KEY_Escape; break; #elif BOOST_OS_MACOS case PlatformKeyCodes::LCONTROL: key = kVK_Control; break; case PlatformKeyCodes::RCONTROL: key = kVK_RightControl; break; case PlatformKeyCodes::TAB: key = kVK_Tab; break; case PlatformKeyCodes::ESCAPE: key = kVK_Escape; break; #endif default: return false; } return WindowSystem::IsKeyDown(key); } std::string WindowSystem::GetKeyCodeName(uint32 button) { #if BOOST_OS_WINDOWS LONG scan_code = MapVirtualKeyA((UINT)button, MAPVK_VK_TO_VSC_EX); if (HIBYTE(scan_code)) scan_code |= 0x100; // because MapVirtualKey strips the extended bit for some keys switch (button) { case VK_LEFT: case VK_UP: case VK_RIGHT: case VK_DOWN: // arrow keys case VK_PRIOR: case VK_NEXT: // page up and page down case VK_END: case VK_HOME: case VK_INSERT: case VK_DELETE: case VK_DIVIDE: // numpad slash case VK_NUMLOCK: { scan_code |= 0x100; // set extended bit break; } } scan_code <<= 16; char key_name[128]; if (GetKeyNameTextA(scan_code, key_name, std::size(key_name)) != 0) return key_name; else return fmt::format("key_{}", button); #elif BOOST_OS_LINUX || BOOST_OS_BSD return gdk_keyval_name(button); #else return fmt::format("key_{}", button); #endif } bool WindowSystem::InputConfigWindowHasFocus() { return g_inputConfigWindowHasFocus; } void WindowSystem::NotifyGameLoaded() { std::shared_lock lock(g_mutex); if (g_mainFrame) { g_mainFrame->OnGameLoaded(); g_mainFrame->UpdateSettingsAfterGameLaunch(); } } void WindowSystem::NotifyGameExited() { std::shared_lock lock(g_mutex); if (g_mainFrame) g_mainFrame->RestoreSettingsAfterGameExited(); } void WindowSystem::RefreshGameList() { std::shared_lock lock(g_mutex); if (g_mainFrame) { g_mainFrame->RequestGameListRefresh(); } } void WindowSystem::CaptureInput(const ControllerState& currentState, const ControllerState& lastState) { HotkeySettings::CaptureInput(currentState, lastState); } bool WindowSystem::IsFullScreen() { return g_window_info.is_fullscreen; } ================================================ FILE: src/gui/wxgui/wxcomponents/checktree.cpp ================================================ #include "wxgui/wxcomponents/checktree.h" #include #include #include #include wxDEFINE_EVENT(wxEVT_CHECKTREE_FOCUS, wxTreeEvent); wxDEFINE_EVENT(wxEVT_CHECKTREE_CHOICE, wxTreeEvent); //IMPLEMENT_DYNAMIC_CLASS(wxCheckTree, wxTreeCtrl) bool on_check_or_label(int flags) { return flags & (wxTREE_HITTEST_ONITEMSTATEICON | wxTREE_HITTEST_ONITEMLABEL) ? true : false; } bool on_check(int flags) { return flags & (wxTREE_HITTEST_ONITEMSTATEICON) ? true : false; } bool on_label(int flags) { return flags & (wxTREE_HITTEST_ONITEMLABEL) ? true : false; } void unhighlight(wxTreeCtrl* m_treeCtrl1, wxTreeItemId& id) { if (!id.IsOk()) return; const int state = m_treeCtrl1->GetItemState(id); if (wxCheckTree::UNCHECKED <= state && state < wxCheckTree::UNCHECKED_DISABLED) { m_treeCtrl1->SetItemState(id, wxCheckTree::UNCHECKED); } else if (wxCheckTree::CHECKED <= state && state < wxCheckTree::CHECKED_DISABLED) { m_treeCtrl1->SetItemState(id, wxCheckTree::CHECKED); } } void mohighlight(wxTreeCtrl* m_treeCtrl1, wxTreeItemId& id, bool toggle) { if (!id.IsOk()) return; const int i = m_treeCtrl1->GetItemState(id); if (i < 0) return; bool is_checked = false; if (wxCheckTree::UNCHECKED <= i && i < wxCheckTree::UNCHECKED_DISABLED) { m_treeCtrl1->SetItemState(id, toggle ? wxCheckTree::CHECKED_MOUSE_OVER : wxCheckTree::UNCHECKED_MOUSE_OVER); is_checked = true; } else if (wxCheckTree::CHECKED <= i && i < wxCheckTree::CHECKED_DISABLED) { m_treeCtrl1->SetItemState(id, toggle ? wxCheckTree::UNCHECKED_MOUSE_OVER : wxCheckTree::CHECKED_MOUSE_OVER); is_checked = false; } if (toggle) { wxTreeEvent event2(wxEVT_CHECKTREE_CHOICE, m_treeCtrl1, id); event2.SetExtraLong(is_checked ? 1 : 0); m_treeCtrl1->ProcessWindowEvent(event2); } } void ldhighlight(wxTreeCtrl* m_treeCtrl1, wxTreeItemId& id) { if (!id.IsOk()) return; const int i = m_treeCtrl1->GetItemState(id); if (wxCheckTree::UNCHECKED <= i && i < wxCheckTree::UNCHECKED_DISABLED) { m_treeCtrl1->SetItemState(id, wxCheckTree::UNCHECKED_LEFT_DOWN); } else if (wxCheckTree::CHECKED <= i && i < wxCheckTree::CHECKED_DISABLED) { m_treeCtrl1->SetItemState(id, wxCheckTree::CHECKED_LEFT_DOWN); } } wxIMPLEMENT_CLASS(wxCheckTree, wxTreeCtrl); wxCheckTree::wxCheckTree() { Init(); } wxCheckTree::wxCheckTree(wxWindow* parent, const wxWindowID id, const wxPoint& pos, const wxSize& size, long style) : wxTreeCtrl(parent, id, pos, size, style) { Init(); } void wxCheckTree::Init() { int width = wxRendererNative::Get().GetCheckBoxSize(this).GetWidth(); int height = wxRendererNative::Get().GetCheckBoxSize(this).GetHeight(); auto states = new wxImageList(width, height, true); wxBitmap unchecked_bmp(width, height); wxBitmap unchecked_mouse_over_bmp(width, height); wxBitmap unchecked_left_down_bmp(width, height); wxBitmap unchecked_disabled_bmp(width, height); wxBitmap checked_bmp(width, height); wxBitmap checked_mouse_over_bmp(width, height); wxBitmap checked_left_down_bmp(width, height); wxBitmap checked_disabled_bmp(width, height); wxMemoryDC renderer_dc; // Unchecked renderer_dc.SelectObject(unchecked_bmp); renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_NONE); // Unchecked Mouse Over renderer_dc.SelectObject(unchecked_mouse_over_bmp); renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CURRENT); // Unchecked and Disabled renderer_dc.SelectObject(unchecked_disabled_bmp); renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_DISABLED); // Unchecked Left Down renderer_dc.SelectObject(unchecked_left_down_bmp); renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CURRENT | wxCONTROL_PRESSED); // Checked renderer_dc.SelectObject(checked_bmp); renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CHECKED); // Checked Mouse Over renderer_dc.SelectObject(checked_mouse_over_bmp); renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CHECKED | wxCONTROL_CURRENT); // Checked Left Down renderer_dc.SelectObject(checked_left_down_bmp); renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CHECKED | wxCONTROL_CURRENT | wxCONTROL_PRESSED); // Checked and Disabled renderer_dc.SelectObject(checked_disabled_bmp); renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour())); renderer_dc.Clear(); wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CHECKED | wxCONTROL_DISABLED); renderer_dc.SelectObject(wxNullBitmap); states->Add(unchecked_bmp); states->Add(unchecked_mouse_over_bmp); states->Add(unchecked_left_down_bmp); states->Add(unchecked_disabled_bmp); states->Add(checked_bmp); states->Add(checked_mouse_over_bmp); states->Add(checked_left_down_bmp); states->Add(checked_disabled_bmp); AssignStateImageList(states); Connect(wxEVT_TREE_SEL_CHANGING, wxTreeEventHandler( wxCheckTree::On_Tree_Sel_Changed ), nullptr, this); Connect(wxEVT_CHAR, wxKeyEventHandler( wxCheckTree::On_Char ), nullptr, this); Connect(wxEVT_KEY_DOWN, wxKeyEventHandler( wxCheckTree::On_KeyDown ), nullptr, this); Connect(wxEVT_KEY_UP, wxKeyEventHandler( wxCheckTree::On_KeyUp ), nullptr, this); Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler( wxCheckTree::On_Mouse_Enter_Tree ), nullptr, this); Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler( wxCheckTree::On_Mouse_Leave_Tree ), nullptr, this); Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler( wxCheckTree::On_Left_DClick ), nullptr, this); Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler( wxCheckTree::On_Left_Down ), nullptr, this); Connect(wxEVT_LEFT_UP, wxMouseEventHandler( wxCheckTree::On_Left_Up ), nullptr, this); Connect(wxEVT_MOTION, wxMouseEventHandler( wxCheckTree::On_Mouse_Motion ), nullptr, this); Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler( wxCheckTree::On_Mouse_Wheel ), nullptr, this); Connect(wxEVT_SET_FOCUS, wxFocusEventHandler( wxCheckTree::On_Tree_Focus_Set ), nullptr, this); Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler( wxCheckTree::On_Tree_Focus_Lost ), nullptr, this); } void wxCheckTree::Sort(const wxTreeItemId& node, bool recursive) { if (recursive) { wxTreeItemIdValue cookie; for(auto it = GetFirstChild(node, cookie); it.IsOk(); it = GetNextChild(it, cookie)) { Sort(it, true); } } if(GetChildrenCount(node, false) > 0) this->SortChildren(node); } int wxCheckTree::OnCompareItems(const wxTreeItemId& item1, const wxTreeItemId& item2) { const bool check1 = GetChildrenCount(item1, false) == 0; const bool check2 = GetChildrenCount(item2, false) == 0; if (!check1 && check2) return -1; if (check1 && !check2) return 1; return GetItemText(item1).Lower().compare(GetItemText(item2).Lower()); } void wxCheckTree::SetItemTextColour(const wxTreeItemId& item, const wxColour& col) { const auto it = m_colors.find(item); if (it == m_colors.end()) m_colors.emplace(std::pair(item, col)); else m_colors[item] = col; wxTreeCtrl::SetItemTextColour(item, col); } bool wxCheckTree::EnableCheckBox(const wxTreeItemId& item, bool enable) { if (!item.IsOk()) return false; const int state = GetItemState(item); if (state < 0 || state > CHECKED_DISABLED) return false; if (enable) { if (state == UNCHECKED_DISABLED) SetItemState(item, UNCHECKED); else if (state == CHECKED_DISABLED) SetItemState(item, CHECKED); const auto it = m_colors.find(item); if (it != m_colors.end()) { SetItemTextColour(item, it->second); m_colors.erase(it); } return true; } if (state == UNCHECKED_DISABLED || state == CHECKED_DISABLED) { //don't disable a second time or we'll lose the //text color information. return true; } if (state == UNCHECKED || state == UNCHECKED_MOUSE_OVER || state == UNCHECKED_LEFT_DOWN) SetItemState(item, UNCHECKED_DISABLED); else if (state == CHECKED || state == CHECKED_MOUSE_OVER || state == CHECKED_LEFT_DOWN) SetItemState(item, CHECKED_DISABLED); const wxColour col = GetItemTextColour(item); SetItemTextColour(item, wxColour(161, 161, 146)); m_colors[item] = col; return true; } bool wxCheckTree::DisableCheckBox(const wxTreeItemId& item) { return EnableCheckBox(item, false); } void wxCheckTree::MakeCheckable(const wxTreeItemId& item, bool state) { if (!item.IsOk()) return; const int i = GetItemState(item); if (i < 0 || i > CHECKED_DISABLED) SetItemState(item, state ? CHECKED : UNCHECKED); } bool wxCheckTree::IsCheckable(const wxTreeItemId& item) { if (!item.IsOk()) return false; const int i = GetItemState(item); return i >= 0 && i <= CHECKED_DISABLED; } void wxCheckTree::Check(const wxTreeItemId& item, bool state) { if (!item.IsOk()) return; const int old_state = GetItemState(item); if (UNCHECKED <= old_state && old_state <= CHECKED_DISABLED) { const bool enable = !(old_state == UNCHECKED_DISABLED || old_state == CHECKED_DISABLED); int check = enable ? CHECKED : CHECKED_DISABLED; int uncheck = enable ? UNCHECKED : UNCHECKED_DISABLED; const int new_state = state ? check : uncheck; if (new_state != old_state) { SetItemState(item, new_state); } } } void wxCheckTree::Uncheck(const wxTreeItemId& item) { Check(item, false); } void wxCheckTree::On_Tree_Sel_Changed(wxTreeEvent& event) { wxTreeItemId id = event.GetItem(); unhighlight(this, last_kf); mohighlight(this, id, false); last_kf = id; event.Skip(); } void wxCheckTree::On_Char(wxKeyEvent& event) { if (!GetSelection().IsOk() && GetCount() > 0) { //If there is no selection, any keypress should just select the first item wxTreeItemIdValue cookie; const auto new_item = HasFlag(wxTR_HIDE_ROOT) ? GetFirstChild(GetRootItem(), cookie) : GetRootItem(); SelectItem(new_item); last_kf = new_item; return; } event.Skip(); } void wxCheckTree::On_KeyDown(wxKeyEvent& event) { if (event.GetKeyCode() == WXK_SPACE) { last_kf = this->GetSelection(); ldhighlight(this, last_kf); } event.Skip(); } void wxCheckTree::On_KeyUp(wxKeyEvent& event) { if (event.GetKeyCode() == WXK_SPACE) { //last_kf = this->GetSelection(); mohighlight(this, last_kf, true); } else if (event.GetKeyCode() == WXK_ESCAPE) { unhighlight(this, last_kf); last_kf = {}; Unselect(); } event.Skip(); } void wxCheckTree::On_Mouse_Enter_Tree(wxMouseEvent& event) { if (event.LeftIsDown()) { mouse_entered_tree_with_left_down = true; } } void wxCheckTree::On_Mouse_Leave_Tree(wxMouseEvent& event) { unhighlight(this, last_mouse_over); unhighlight(this, last_left_down); last_mouse_over = {}; last_left_down = {}; } void wxCheckTree::On_Left_DClick(wxMouseEvent& event) { int flags; HitTest(event.GetPosition(), flags); //double clicks on buttons can be annoying, so we'll ignore those //but all other double clicks will just have 1 more click added to them //without this, the check boxes are not as responsive as they should be if (!(flags & wxTREE_HITTEST_ONITEMBUTTON)) { On_Left_Down(event); On_Left_Up(event); } } void wxCheckTree::On_Left_Down(wxMouseEvent& event) { int flags; wxTreeItemId id = HitTest(event.GetPosition(), flags); if (!id.IsOk()) return; int i = GetItemState(id); if (id.IsOk() && i >= 0 && on_check(flags)) { last_left_down = id; ldhighlight(this, id); } else if (on_label(flags)) event.Skip(); } void wxCheckTree::On_Left_Up(wxMouseEvent& event) { SetFocus(); int flags; wxTreeItemId id = HitTest(event.GetPosition(), flags); if (!id.IsOk()) return; int i = GetItemState(id); if (mouse_entered_tree_with_left_down) { mouse_entered_tree_with_left_down = false; if (i >= 0 && on_check(flags)) { mohighlight(this, id, false); last_mouse_over = id; } } else if (id.IsOk()) { if (flags & wxTREE_HITTEST_ONITEMBUTTON && ItemHasChildren(id)) { if (IsExpanded(id)) { Collapse(id); } else { Expand(id); } } else if (i >= 0 && on_check(flags)) { if (id != last_left_down) { unhighlight(this, last_left_down); mohighlight(this, id, false); } else { mohighlight(this, id, true); } last_left_down = wxTreeItemId(); last_mouse_over = id; } else if (on_label(flags)) { event.Skip(); } else { unhighlight(this, last_left_down); unhighlight(this, last_mouse_over); last_left_down = wxTreeItemId(); last_mouse_over = wxTreeItemId(); } } else { //id is not ok unhighlight(this, last_left_down); unhighlight(this, last_mouse_over); last_left_down = wxTreeItemId(); last_mouse_over = wxTreeItemId(); } } void wxCheckTree::On_Mouse_Motion(wxMouseEvent& event) { if (mouse_entered_tree_with_left_down) { //just ignore everything until the left button is released return; } int flags; wxTreeItemId id = HitTest(event.GetPosition(), flags); if (!id.IsOk()) { unhighlight(this, last_mouse_over); last_mouse_over = {}; } else if (event.LeftIsDown() && last_left_down.IsOk()) { //to match the behavior of ordinary check boxes, //if we've moved to a new item while holding the mouse button down //we want to set the item where the left down click occured to have //mouse over highlight. And if we return to the box where the //left down occured, we want to return it to the having the left down highlight //I don't understand why this is the behavior //of ordinary check boxes, but I'm goin to match it anyway. if (id == last_left_down) { const auto state = GetItemState(last_left_down); if (state != UNCHECKED_LEFT_DOWN && state != CHECKED_LEFT_DOWN) { ldhighlight(this, last_left_down); } } else mohighlight(this, last_left_down, false); } else { //4 cases 1 we're still on the same item, but we've moved off the state icon or label // 2 we're still on the same item and on the state icon or label - do nothing // 3 we're on a new item but not on its state icon or label (or the new item has no state) // 4 we're on a new item, it has a state icon, and we're on the state icon or label const int state = GetItemState(id); if (id == last_mouse_over) { if (state < 0 || !on_check(flags)) { unhighlight(this, last_mouse_over); last_mouse_over = {}; } } else { if (state < 0 || !on_check(flags)) { unhighlight(this, last_mouse_over); last_mouse_over = {}; } else { unhighlight(this, last_mouse_over); mohighlight(this, id, false); last_mouse_over = id; } } } } void wxCheckTree::On_Mouse_Wheel(wxMouseEvent& event) { event.Skip(); } void wxCheckTree::On_Tree_Focus_Set(wxFocusEvent& event) { //event.Skip(); //skipping this event will set the last selected item //to be highlighted and I want the tree items to only be //highlighted by keyboard actions. } void wxCheckTree::On_Tree_Focus_Lost(wxFocusEvent& event) { unhighlight(this, last_kf); Unselect(); event.Skip(); } void wxCheckTree::SetFocusFromKbd() { if (last_kf.IsOk()) SelectItem(last_kf); wxTreeEvent event2(wxEVT_CHECKTREE_FOCUS, this, wxTreeItemId()); ProcessWindowEvent(event2); wxWindow::SetFocusFromKbd(); } ================================================ FILE: src/gui/wxgui/wxcomponents/checktree.h ================================================ #ifndef checktree_H_INCLUDED #define checktree_H_INCLUDED // credits to: https://forums.wxwidgets.org/viewtopic.php?t=39582 #include "wx/treectrl.h" #include #define WXMAKINGDLL_CHECKTREE // dll export macros #ifdef WXMAKINGDLL_CHECKTREE #define WXDLLIMPEXP_CHECKTREE WXEXPORT #elif defined(WXUSING_CHECKTREE_SOURCE) #define WXDLLIMPEXP_CHECKTREE #elif defined(WXUSINGDLL) #define WXDLLIMPEXP_CHECKTREE WXIMPORT #else // not making nor using DLL #define WXDLLIMPEXP_CHECKTREE #endif #include // wxDECLARE_CLASS, wxIMPLEMENT_CLASS for sorting? class WXDLLIMPEXP_CHECKTREE wxCheckTree : public wxTreeCtrl { wxDECLARE_ABSTRACT_CLASS(wxCheckTree); public: wxCheckTree(); wxCheckTree(wxWindow *parent, const wxWindowID id, const wxPoint& pos, const wxSize& size, long style); void Init(); void Sort(const wxTreeItemId& node, bool recursive); int OnCompareItems(const wxTreeItemId& item1, const wxTreeItemId& item2) override; //methods overriden from base class: void SetFocusFromKbd() override; void SetItemTextColour(const wxTreeItemId &item, const wxColour &col) override; //interaction with the check boxes: bool EnableCheckBox(const wxTreeItemId &item, bool enable = true ); bool DisableCheckBox(const wxTreeItemId &item); void Check(const wxTreeItemId &item,bool state=true); void Uncheck(const wxTreeItemId &item); void MakeCheckable(const wxTreeItemId &item, bool state = false); bool IsCheckable(const wxTreeItemId& item); enum { UNCHECKED, UNCHECKED_MOUSE_OVER, UNCHECKED_LEFT_DOWN, UNCHECKED_DISABLED, CHECKED, CHECKED_MOUSE_OVER, CHECKED_LEFT_DOWN, CHECKED_DISABLED }; private: //event handlers void On_Tree_Sel_Changed( wxTreeEvent& event ); void On_Char( wxKeyEvent& event ); void On_KeyDown( wxKeyEvent& event ); void On_KeyUp( wxKeyEvent& event ); void On_Mouse_Enter_Tree( wxMouseEvent& event ); void On_Mouse_Leave_Tree( wxMouseEvent& event ); void On_Left_DClick( wxMouseEvent& event ); void On_Left_Down( wxMouseEvent& event ); void On_Left_Up( wxMouseEvent& event ); void On_Mouse_Motion( wxMouseEvent& event ); void On_Mouse_Wheel( wxMouseEvent& event ); void On_Tree_Focus_Set( wxFocusEvent& event ); void On_Tree_Focus_Lost( wxFocusEvent& event ); //private data: std::map m_colors; bool mouse_entered_tree_with_left_down = false; wxTreeItemId last_mouse_over{}; wxTreeItemId last_left_down{}; wxTreeItemId last_kf{}; // NB: due to an ugly wxMSW hack you _must_ use DECLARE_DYNAMIC_CLASS() // if you want your overloaded OnCompareItems() to be called. // OTOH, if you don't want it you may omit the next line - this will // make default (alphabetical) sorting much faster under wxMSW. //DECLARE_DYNAMIC_CLASS(wxCheckTree) }; wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CHECKTREE, wxEVT_CHECKTREE_CHOICE, wxTreeEvent); wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CHECKTREE, wxEVT_CHECKTREE_FOCUS, wxTreeEvent); #endif // checktree_H_INCLUDED ================================================ FILE: src/gui/wxgui/wxgui.h ================================================ #pragma once #define wxNO_UNSAFE_WXSTRING_CONV 1 #include #ifndef WX_PRECOMP #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern bool g_inputConfigWindowHasFocus; inline bool SendSliderEvent(wxSlider* slider, int new_value) { wxCommandEvent cevent(wxEVT_SLIDER, slider->GetId()); cevent.SetInt(new_value); cevent.SetEventObject(slider); return slider->HandleWindowEvent(cevent); } ================================================ FILE: src/imgui/CMakeLists.txt ================================================ add_library(imguiImpl imgui_impl_opengl3.cpp imgui_impl_opengl3.h imgui_impl_vulkan.cpp imgui_impl_vulkan.h imgui_extension.cpp imgui_extension.h ) if (ENABLE_METAL) target_sources(imguiImpl PRIVATE imgui_impl_metal.mm imgui_impl_metal.h ) target_compile_definitions(imguiImpl PRIVATE IMGUI_IMPL_METAL_CPP) endif () set_property(TARGET imguiImpl PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") target_include_directories(imguiImpl PUBLIC "../") # imgui source files target_sources(imguiImpl PRIVATE "../../dependencies/imgui/imgui.cpp" "../../dependencies/imgui/imgui_draw.cpp" "../../dependencies/imgui/imgui_tables.cpp" "../../dependencies/imgui/imgui_widgets.cpp" ) target_include_directories(imguiImpl PUBLIC "../../dependencies/imgui/") target_link_libraries(imguiImpl PRIVATE CemuCommon CemuGui ) ================================================ FILE: src/imgui/imgui_extension.cpp ================================================ #include "imgui_extension.h" #include "WindowSystem.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "resource/IconsFontAwesome5.h" #include "imgui_impl_opengl3.h" #include "resource/resource.h" #include "imgui_impl_vulkan.h" #include "input/InputManager.h" // template static T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } template static T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } static ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } int rotation_start_index; void ImRotateStart() { rotation_start_index = ImGui::GetWindowDrawList()->VtxBuffer.Size; } ImVec2 ImRotationCenter() { ImVec2 l(FLT_MAX, FLT_MAX), u(-FLT_MAX, -FLT_MAX); // bounds const auto& buf = ImGui::GetWindowDrawList()->VtxBuffer; for (int i = rotation_start_index; i < buf.Size; i++) l = ImMin(l, buf[i].pos), u = ImMax(u, buf[i].pos); return ImVec2((l.x + u.x) / 2, (l.y + u.y) / 2); // or use _ClipRectStack? } void ImRotateEnd(float rad, ImVec2 center) { const float s = sin(rad); const float c = cos(rad); center = ImRotate(center, s, c) - center; auto& buf = ImGui::GetWindowDrawList()->VtxBuffer; for (int i = rotation_start_index; i < buf.Size; i++) buf[i].pos = ImRotate(buf[i].pos, s, c) - center; } uint8* extractCafeDefaultFont(sint32* size); sint32 g_font_size = 0; uint8* g_font_data = nullptr; #if !BOOST_OS_WINDOWS extern int const g_fontawesome_size; extern char const g_fontawesome_data[]; #endif std::unordered_map g_imgui_fonts; std::stack g_font_requests; void ImGui_PrecacheFonts() { while (!g_font_requests.empty()) { const int size = g_font_requests.top(); g_font_requests.pop(); auto& io = ImGui::GetIO(); cemu_assert(io.Fonts->Locked == false); if (g_font_size == 0) g_font_data = extractCafeDefaultFont(&g_font_size); ImFontConfig cfg{}; cfg.FontDataOwnedByAtlas = false; //cfg.FontData = g_font_data; //cfg.FontDataSize = g_font_size; //cfg.SizePixels = size; ImFont* font = io.Fonts->AddFontFromMemoryTTF(g_font_data, g_font_size, (float)size, &cfg); ImFontConfig cfgmerge{}; cfgmerge.FontDataOwnedByAtlas = false; cfgmerge.MergeMode = true; cfgmerge.GlyphMinAdvanceX = 20.0f; //cfgmerge.GlyphOffset = { 2,2 }; static const ImWchar icon_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; #if BOOST_OS_WINDOWS const auto hinstance = GetModuleHandle(nullptr); const HRSRC res = FindResource(hinstance, MAKEINTRESOURCE(IDR_FONTAWESOME), RT_RCDATA); if (res) { const HGLOBAL mem = ::LoadResource(hinstance, res); if (mem) { void* data = LockResource(mem); const size_t len = SizeofResource(hinstance, res); io.Fonts->AddFontFromMemoryTTF(data, (int)len, (float)size, &cfgmerge, icon_ranges); } } #else io.Fonts->AddFontFromMemoryTTF((void*)g_fontawesome_data, (int)g_fontawesome_size, (float)size, &cfgmerge, icon_ranges); #endif g_imgui_fonts[(int)size] = font; // Vulkan doesn't let us destroy resources that are still being used, so we flush here g_renderer->Flush(true); g_renderer->DeleteFontTextures(); } } void ImGui_ClearFonts() { g_imgui_fonts.clear(); } ImFont* ImGui_GetFont(float size) { const auto it = g_imgui_fonts.find((int)size); if (it != g_imgui_fonts.cend()) return it->second; g_font_requests.emplace((int)size); return nullptr; // will create the font in next precache call } void ImGui_UpdateWindowInformation(bool mainWindow) { static std::map keyboard_mapping; auto& windowInfo = WindowSystem::GetWindowInfo(); static uint32 current_key = 0; ImGuiIO& io = ImGui::GetIO(); io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; #if BOOST_OS_WINDOWS io.ImeWindowHandle = mainWindow ? windowInfo.window_main.surface : windowInfo.window_pad.surface; #else io.ImeWindowHandle = nullptr; #endif io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); auto& instance = InputManager::instance(); const auto mousePos = instance.get_mouse_position(!mainWindow); io.MousePos = { (float)mousePos.x, (float)mousePos.y }; bool padDown; const auto pos = instance.get_left_down_mouse_info(&padDown); io.MouseDown[0] = padDown != mainWindow && pos.has_value(); auto get_mapping = [&](uint32 key_code) { auto key = keyboard_mapping.find(key_code); if (key != keyboard_mapping.end()) return key->second; ImGuiKey mapped_key = (ImGuiKey)((uint32)current_key + ImGuiKey_NamedKey_BEGIN); current_key = (current_key + 1) % (uint32)ImGuiKey_NamedKey_COUNT; keyboard_mapping[key_code] = mapped_key; return mapped_key; }; windowInfo.iter_keystates([&](auto&& el){ io.AddKeyEvent(get_mapping(el.first), el.second); }); // printf("%f %f %d\n", io.MousePos.x, io.MousePos.y, io.MouseDown[0]); for (auto i = 0; i < InputManager::kMaxController; ++i) { const auto controller = instance.get_controller(i); if (!controller) continue; if (controller->is_start_down()) io.NavInputs[ImGuiNavInput_Input] = 1.0f; if (controller->is_a_down()) io.NavInputs[ImGuiNavInput_Activate] = 1.0f; if (controller->is_b_down()) io.NavInputs[ImGuiNavInput_Cancel] = 1.0f; if (controller->is_left_down()) io.NavInputs[ImGuiNavInput_DpadLeft] = 1.0f; if (controller->is_right_down()) io.NavInputs[ImGuiNavInput_DpadRight] = 1.0f; if (controller->is_up_down()) io.NavInputs[ImGuiNavInput_DpadUp] = 1.0f; if (controller->is_down_down()) io.NavInputs[ImGuiNavInput_DpadDown] = 1.0f; } } ================================================ FILE: src/imgui/imgui_extension.h ================================================ #pragma once #include "imgui.h" void ImRotateStart(); ImVec2 ImRotationCenter(); void ImRotateEnd(float rad, ImVec2 center = ImRotationCenter()); inline ImVec2 operator-(const ImVec2& l, const ImVec2& r) { return{ l.x - r.x, l.y - r.y }; } inline bool operator<(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x < rhs.x; } inline bool operator>=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x >= rhs.x; } bool ImGui_BeginPadDistinct(const char* name, bool* p_open, ImGuiWindowFlags flags, bool pad); void ImGui_PrecacheFonts(); void ImGui_ClearFonts(); ImFont* ImGui_GetFont(float size); void ImGui_UpdateWindowInformation(bool mainWindow); ================================================ FILE: src/imgui/imgui_impl_metal.h ================================================ // dear imgui: Renderer Backend for Metal // This needs to be used along with a Platform Backend (e.g. OSX) // Implemented features: // [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs #include "imgui.h" // IMGUI_IMPL_API //----------------------------------------------------------------------------- // ObjC API //----------------------------------------------------------------------------- #ifdef __OBJC__ @class MTLRenderPassDescriptor; @protocol MTLDevice, MTLCommandBuffer, MTLRenderCommandEncoder; IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id device); IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor); IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id commandBuffer, id commandEncoder); // Called by Init/NewFrame/Shutdown IMGUI_IMPL_API bool ImGui_ImplMetal_CreateFontsTexture(id device); IMGUI_IMPL_API void ImGui_ImplMetal_DestroyFontsTexture(); IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id device); IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); #endif //----------------------------------------------------------------------------- // C++ API //----------------------------------------------------------------------------- // Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file // More info about using Metal from C++: https://developer.apple.com/metal/cpp/ #ifdef IMGUI_IMPL_METAL_CPP #include #ifndef __OBJC__ IMGUI_IMPL_API bool ImGui_ImplMetal_Init(MTL::Device* device); IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor); IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, MTL::CommandBuffer* commandBuffer, MTL::RenderCommandEncoder* commandEncoder); // Called by Init/NewFrame/Shutdown IMGUI_IMPL_API bool ImGui_ImplMetal_CreateFontsTexture(MTL::Device* device); IMGUI_IMPL_API void ImGui_ImplMetal_DestroyFontsTexture(); IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device); IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); #endif #endif ================================================ FILE: src/imgui/imgui_impl_metal.mm ================================================ // dear imgui: Renderer Backend for Metal // This needs to be used along with a Platform Backend (e.g. OSX) // Implemented features: // [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'. // 2022-07-05: Metal: Add dispatch synchronization. // 2022-06-30: Metal: Use __bridge for ARC based systems. // 2022-06-01: Metal: Fixed null dereference on exit inside command buffer completion handler. // 2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts. // 2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code). // 2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file. // 2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464) // 2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) // 2021-02-18: Metal: Change blending equation to preserve alpha in output buffer. // 2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst. // 2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. // 2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. // 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. // 2018-07-05: Metal: Added new Metal backend implementation. #include "imgui.h" #include "imgui_impl_metal.h" #import #import #pragma mark - Support classes // A wrapper around a MTLBuffer object that knows the last time it was reused @interface MetalBuffer : NSObject @property (nonatomic, strong) id buffer; @property (nonatomic, assign) double lastReuseTime; - (instancetype)initWithBuffer:(id)buffer; @end // An object that encapsulates the data necessary to uniquely identify a // render pipeline state. These are used as cache keys. @interface FramebufferDescriptor : NSObject @property (nonatomic, assign) unsigned long sampleCount; @property (nonatomic, assign) MTLPixelFormat colorPixelFormat; @property (nonatomic, assign) MTLPixelFormat depthPixelFormat; @property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor; @end // A singleton that stores long-lived objects that are needed by the Metal // renderer backend. Stores the render pipeline state cache and the default // font texture, and manages the reusable buffer cache. @interface MetalContext : NSObject @property (nonatomic, strong) id device; @property (nonatomic, strong) id depthStencilState; @property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient @property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors @property (nonatomic, strong, nullable) id fontTexture; @property (nonatomic, strong) NSMutableArray* bufferCache; @property (nonatomic, assign) double lastBufferCachePurge; - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device; @end struct ImGui_ImplMetal_Data { MetalContext* SharedMetalContext; ImGui_ImplMetal_Data() { memset(this, 0, sizeof(*this)); } }; static ImGui_ImplMetal_Data* ImGui_ImplMetal_CreateBackendData() { return IM_NEW(ImGui_ImplMetal_Data)(); } static ImGui_ImplMetal_Data* ImGui_ImplMetal_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } static void ImGui_ImplMetal_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal_GetBackendData()); } static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); } #ifdef IMGUI_IMPL_METAL_CPP #pragma mark - Dear ImGui Metal C++ Backend API bool ImGui_ImplMetal_Init(MTL::Device* device) { return ImGui_ImplMetal_Init((__bridge id)(device)); } void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor) { ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor)); } void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, MTL::CommandBuffer* commandBuffer, MTL::RenderCommandEncoder* commandEncoder) { ImGui_ImplMetal_RenderDrawData(draw_data, (__bridge id)(commandBuffer), (__bridge id)(commandEncoder)); } bool ImGui_ImplMetal_CreateFontsTexture(MTL::Device* device) { return ImGui_ImplMetal_CreateFontsTexture((__bridge id)(device)); } bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) { return ImGui_ImplMetal_CreateDeviceObjects((__bridge id)(device)); } #endif // #ifdef IMGUI_IMPL_METAL_CPP #pragma mark - Dear ImGui Metal Backend API bool ImGui_ImplMetal_Init(id device) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_CreateBackendData(); ImGuiIO& io = ImGui::GetIO(); io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_metal"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. bd->SharedMetalContext = [[MetalContext alloc] init]; bd->SharedMetalContext.device = device; return true; } void ImGui_ImplMetal_Shutdown() { ImGui_ImplMetal_DestroyDeviceObjects(); ImGui_ImplMetal_DestroyBackendData(); } void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); IM_ASSERT(bd->SharedMetalContext != nil && "No Metal context. Did you call ImGui_ImplMetal_Init() ?"); bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]; if (bd->SharedMetalContext.depthStencilState == nil) ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device); } static void ImGui_ImplMetal_SetupRenderState(ImDrawData* drawData, id commandBuffer, id commandEncoder, id renderPipelineState, MetalBuffer* vertexBuffer, size_t vertexBufferOffset) { IM_UNUSED(commandBuffer); ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [commandEncoder setCullMode:MTLCullModeNone]; [commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState]; // Setup viewport, orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPos (top left) to // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. MTLViewport viewport = { .originX = 0.0, .originY = 0.0, .width = (double)(drawData->DisplaySize.x * drawData->FramebufferScale.x), .height = (double)(drawData->DisplaySize.y * drawData->FramebufferScale.y), .znear = 0.0, .zfar = 1.0 }; [commandEncoder setViewport:viewport]; float L = drawData->DisplayPos.x; float R = drawData->DisplayPos.x + drawData->DisplaySize.x; float T = drawData->DisplayPos.y; float B = drawData->DisplayPos.y + drawData->DisplaySize.y; float N = (float)viewport.znear; float F = (float)viewport.zfar; const float ortho_projection[4][4] = { { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, { 0.0f, 0.0f, 1/(F-N), 0.0f }, { (R+L)/(L-R), (T+B)/(B-T), N/(F-N), 1.0f }, }; [commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:1]; [commandEncoder setRenderPipelineState:renderPipelineState]; [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:0 atIndex:0]; [commandEncoder setVertexBufferOffset:vertexBufferOffset atIndex:0]; } // Metal Render function. void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id commandBuffer, id commandEncoder) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); MetalContext* ctx = bd->SharedMetalContext; // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) int fb_width = (int)(drawData->DisplaySize.x * drawData->FramebufferScale.x); int fb_height = (int)(drawData->DisplaySize.y * drawData->FramebufferScale.y); if (fb_width <= 0 || fb_height <= 0 || drawData->CmdListsCount == 0) return; // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame // The hit rate for this cache should be very near 100%. id renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor]; if (renderPipelineState == nil) { // No luck; make a new render pipeline state renderPipelineState = [ctx renderPipelineStateForFramebufferDescriptor:ctx.framebufferDescriptor device:commandBuffer.device]; // Cache render pipeline state for later reuse ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState; } size_t vertexBufferLength = (size_t)drawData->TotalVtxCount * sizeof(ImDrawVert); size_t indexBufferLength = (size_t)drawData->TotalIdxCount * sizeof(ImDrawIdx); MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device]; MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device]; ImGui_ImplMetal_SetupRenderState(drawData, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = drawData->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = drawData->FramebufferScale; // (1,1) unless using retina display which are often (2,2) // Render command lists size_t vertexBufferOffset = 0; size_t indexBufferOffset = 0; for (int n = 0; n < drawData->CmdListsCount; n++) { const ImDrawList* cmd_list = drawData->CmdLists[n]; memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, cmd_list->VtxBuffer.Data, (size_t)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, cmd_list->IdxBuffer.Data, (size_t)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; if (pcmd->UserCallback) { // User callback, registered via ImDrawList::AddCallback() // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) ImGui_ImplMetal_SetupRenderState(drawData, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); else pcmd->UserCallback(cmd_list, pcmd); } else { // Project scissor/clipping rectangles into framebuffer space ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); // Clamp to viewport as setScissorRect() won't accept values that are off bounds if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; } if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; } if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) continue; if (pcmd->ElemCount == 0) // drawIndexedPrimitives() validation doesn't accept this continue; // Apply scissor/clipping rectangle MTLScissorRect scissorRect = { .x = NSUInteger(clip_min.x), .y = NSUInteger(clip_min.y), .width = NSUInteger(clip_max.x - clip_min.x), .height = NSUInteger(clip_max.y - clip_min.y) }; [commandEncoder setScissorRect:scissorRect]; // Bind texture, Draw if (ImTextureID tex_id = pcmd->GetTexID()) [commandEncoder setFragmentTexture:(__bridge id)(tex_id) atIndex:0]; [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0]; [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:pcmd->ElemCount indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32 indexBuffer:indexBuffer.buffer indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)]; } } vertexBufferOffset += (size_t)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); indexBufferOffset += (size_t)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx); } [commandBuffer addCompletedHandler:^(id) { dispatch_async(dispatch_get_main_queue(), ^{ ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); if (bd != nullptr) { @synchronized(bd->SharedMetalContext.bufferCache) { [bd->SharedMetalContext.bufferCache addObject:vertexBuffer]; [bd->SharedMetalContext.bufferCache addObject:indexBuffer]; } } }); }]; } bool ImGui_ImplMetal_CreateFontsTexture(id device) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); ImGuiIO& io = ImGui::GetIO(); // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here. // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth. // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures. // You can make that change in your implementation. unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:(NSUInteger)width height:(NSUInteger)height mipmapped:NO]; textureDescriptor.usage = MTLTextureUsageShaderRead; #if TARGET_OS_OSX || TARGET_OS_MACCATALYST textureDescriptor.storageMode = MTLStorageModeManaged; #else textureDescriptor.storageMode = MTLStorageModeShared; #endif id texture = [device newTextureWithDescriptor:textureDescriptor]; [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)width, (NSUInteger)height) mipmapLevel:0 withBytes:pixels bytesPerRow:(NSUInteger)width * 4]; bd->SharedMetalContext.fontTexture = texture; io.Fonts->SetTexID((__bridge void*)bd->SharedMetalContext.fontTexture); // ImTextureID == void* return (bd->SharedMetalContext.fontTexture != nil); } void ImGui_ImplMetal_DestroyFontsTexture() { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); ImGuiIO& io = ImGui::GetIO(); bd->SharedMetalContext.fontTexture = nil; io.Fonts->SetTexID(nullptr); } bool ImGui_ImplMetal_CreateDeviceObjects(id device) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; depthStencilDescriptor.depthWriteEnabled = NO; depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; ImGui_ImplMetal_CreateFontsTexture(device); return true; } void ImGui_ImplMetal_DestroyDeviceObjects() { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); ImGui_ImplMetal_DestroyFontsTexture(); [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects]; } #pragma mark - MetalBuffer implementation @implementation MetalBuffer - (instancetype)initWithBuffer:(id)buffer { if ((self = [super init])) { _buffer = buffer; _lastReuseTime = GetMachAbsoluteTimeInSeconds(); } return self; } @end #pragma mark - FramebufferDescriptor implementation @implementation FramebufferDescriptor - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor { if ((self = [super init])) { _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount; _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat; _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat; _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat; } return self; } - (nonnull id)copyWithZone:(nullable NSZone*)zone { FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init]; copy.sampleCount = self.sampleCount; copy.colorPixelFormat = self.colorPixelFormat; copy.depthPixelFormat = self.depthPixelFormat; copy.stencilPixelFormat = self.stencilPixelFormat; return copy; } - (NSUInteger)hash { NSUInteger sc = _sampleCount & 0x3; NSUInteger cf = _colorPixelFormat & 0x3FF; NSUInteger df = _depthPixelFormat & 0x3FF; NSUInteger sf = _stencilPixelFormat & 0x3FF; NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc; return hash; } - (BOOL)isEqual:(id)object { FramebufferDescriptor* other = object; if (![other isKindOfClass:[FramebufferDescriptor class]]) return NO; return other.sampleCount == self.sampleCount && other.colorPixelFormat == self.colorPixelFormat && other.depthPixelFormat == self.depthPixelFormat && other.stencilPixelFormat == self.stencilPixelFormat; } @end #pragma mark - MetalContext implementation @implementation MetalContext - (instancetype)init { if ((self = [super init])) { self.renderPipelineStateCache = [NSMutableDictionary dictionary]; self.bufferCache = [NSMutableArray array]; _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); } return self; } - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device { uint64_t now = GetMachAbsoluteTimeInSeconds(); @synchronized(self.bufferCache) { // Purge old buffers that haven't been useful for a while if (now - self.lastBufferCachePurge > 1.0) { NSMutableArray* survivors = [NSMutableArray array]; for (MetalBuffer* candidate in self.bufferCache) if (candidate.lastReuseTime > self.lastBufferCachePurge) [survivors addObject:candidate]; self.bufferCache = [survivors mutableCopy]; self.lastBufferCachePurge = now; } // See if we have a buffer we can reuse MetalBuffer* bestCandidate = nil; for (MetalBuffer* candidate in self.bufferCache) if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime)) bestCandidate = candidate; if (bestCandidate != nil) { [self.bufferCache removeObject:bestCandidate]; bestCandidate.lastReuseTime = now; return bestCandidate; } } // No luck; make a new buffer id backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared]; return [[MetalBuffer alloc] initWithBuffer:backing]; } // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device { NSError* error = nil; NSString* shaderSource = @"" "#include \n" "using namespace metal;\n" "\n" "struct Uniforms {\n" " float4x4 projectionMatrix;\n" "};\n" "\n" "struct VertexIn {\n" " float2 position [[attribute(0)]];\n" " float2 texCoords [[attribute(1)]];\n" " uchar4 color [[attribute(2)]];\n" "};\n" "\n" "struct VertexOut {\n" " float4 position [[position]];\n" " float2 texCoords;\n" " float4 color;\n" "};\n" "\n" "vertex VertexOut vertex_main(VertexIn in [[stage_in]],\n" " constant Uniforms &uniforms [[buffer(1)]]) {\n" " VertexOut out;\n" " out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n" " out.texCoords = in.texCoords;\n" " out.color = float4(in.color) / float4(255.0);\n" " return out;\n" "}\n" "\n" "fragment half4 fragment_main(VertexOut in [[stage_in]],\n" " texture2d texture [[texture(0)]]) {\n" " constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear);\n" " half4 texColor = texture.sample(linearSampler, in.texCoords);\n" " return half4(in.color) * texColor;\n" "}\n"; id library = [device newLibraryWithSource:shaderSource options:nil error:&error]; if (library == nil) { NSLog(@"Error: failed to create Metal library: %@", error); return nil; } id vertexFunction = [library newFunctionWithName:@"vertex_main"]; id fragmentFunction = [library newFunctionWithName:@"fragment_main"]; if (vertexFunction == nil || fragmentFunction == nil) { NSLog(@"Error: failed to find Metal shader functions in library: %@", error); return nil; } MTLVertexDescriptor* vertexDescriptor = [MTLVertexDescriptor vertexDescriptor]; vertexDescriptor.attributes[0].offset = IM_OFFSETOF(ImDrawVert, pos); vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position vertexDescriptor.attributes[0].bufferIndex = 0; vertexDescriptor.attributes[1].offset = IM_OFFSETOF(ImDrawVert, uv); vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoords vertexDescriptor.attributes[1].bufferIndex = 0; vertexDescriptor.attributes[2].offset = IM_OFFSETOF(ImDrawVert, col); vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; // color vertexDescriptor.attributes[2].bufferIndex = 0; vertexDescriptor.layouts[0].stepRate = 1; vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert); MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; pipelineDescriptor.vertexFunction = vertexFunction; pipelineDescriptor.fragmentFunction = fragmentFunction; pipelineDescriptor.vertexDescriptor = vertexDescriptor; pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount; pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat; pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat; pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat; id renderPipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error]; if (error != nil) NSLog(@"Error: failed to create Metal pipeline state: %@", error); return renderPipelineState; } @end ================================================ FILE: src/imgui/imgui_impl_opengl3.cpp ================================================ // dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline // - Desktop GL: 2.x 3.x 4.x // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) // Implemented features: // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. // [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bits indices. // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility. // 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call. // 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. // 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop. // 2019-03-15: OpenGL: Added a dummy GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early. // 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0). // 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader. // 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. // 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450). // 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. // 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN. // 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used. // 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES". // 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation. // 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link. // 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples. // 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. // 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state. // 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer. // 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150". // 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context. // 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself. // 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. // 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. // 2017-05-01: OpenGL: Fixed save and restore of current blend func state. // 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. // 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. // 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752) //---------------------------------------- // OpenGL GLSL GLSL // version version string //---------------------------------------- // 2.0 110 "#version 110" // 2.1 120 "#version 120" // 3.0 130 "#version 130" // 3.1 140 "#version 140" // 3.2 150 "#version 150" // 3.3 330 "#version 330 core" // 4.0 400 "#version 400 core" // 4.1 410 "#version 410 core" // 4.2 420 "#version 410 core" // 4.3 430 "#version 430 core" // ES 2.0 100 "#version 100" = WebGL 1.0 // ES 3.0 300 "#version 300 es" = WebGL 2.0 //---------------------------------------- #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif #include "imgui.h" #include "imgui_impl_opengl3.h" #include #include "Common/GLInclude/GLInclude.h" #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier #include // intptr_t #else #include // intptr_t #endif #if defined(__APPLE__) #include "TargetConditionals.h" #endif // Auto-detect GL version #if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) #define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" #undef IMGUI_IMPL_OPENGL_LOADER_GL3W #undef IMGUI_IMPL_OPENGL_LOADER_GLEW #undef IMGUI_IMPL_OPENGL_LOADER_GLAD #undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM #elif defined(__EMSCRIPTEN__) #define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" #undef IMGUI_IMPL_OPENGL_LOADER_GL3W #undef IMGUI_IMPL_OPENGL_LOADER_GLEW #undef IMGUI_IMPL_OPENGL_LOADER_GLAD #undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM #endif #endif // GL includes #if defined(IMGUI_IMPL_OPENGL_ES2) #include #elif defined(IMGUI_IMPL_OPENGL_ES3) #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) #include // Use GL ES 3 #else #include // Use GL ES 3 #endif #else // About Desktop OpenGL function loaders: // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. #if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) #include // Needs to be initialized with gl3wInit() in user's code #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) #include // Needs to be initialized with glewInit() in user's code #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) #include // Needs to be initialized with gladLoadGL() in user's code #else //#include IMGUI_IMPL_OPENGL_LOADER_CUSTOM"glext.h""glext.h" #endif #endif // Desktop GL has glDrawElementsBaseVertex() which GL ES and WebGL don't have. #if defined(IMGUI_IMPL_OPENGL_ES2) || defined(IMGUI_IMPL_OPENGL_ES3) #define IMGUI_IMPL_OPENGL_HAS_DRAW_WITH_BASE_VERTEX 0 #else #define IMGUI_IMPL_OPENGL_HAS_DRAW_WITH_BASE_VERTEX 1 #endif // OpenGL Data static char g_GlslVersionString[32] = ""; static GLuint g_FontTexture = 0; static GLuint g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; // Uniforms location static int g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0, g_AttribLocationVtxColor = 0; // Vertex attributes location static unsigned int g_VboHandle = 0, g_ElementsHandle = 0; // Functions bool ImGui_ImplOpenGL3_Init(const char* glsl_version) { // Setup back-end capabilities flags ImGuiIO& io = ImGui::GetIO(); io.BackendRendererName = "imgui_impl_opengl3"; #if IMGUI_IMPL_OPENGL_HAS_DRAW_WITH_BASE_VERTEX io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. #endif // Store GLSL version string so we can refer to it later in case we recreate shaders. Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure. #if defined(IMGUI_IMPL_OPENGL_ES2) if (glsl_version == NULL) glsl_version = "#version 100"; #elif defined(IMGUI_IMPL_OPENGL_ES3) if (glsl_version == NULL) glsl_version = "#version 300 es"; #else if (glsl_version == NULL) glsl_version = "#version 130"; #endif IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString)); strcpy(g_GlslVersionString, glsl_version); strcat(g_GlslVersionString, "\n"); // Dummy construct to make it easily visible in the IDE and debugger which GL loader has been selected. // The code actually never uses the 'gl_loader' variable! It is only here so you can read it! // If auto-detection fails or doesn't select the same GL loader file as used by your application, // you are likely to get a crash below. // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. const char* gl_loader = "Unknown"; IM_UNUSED(gl_loader); #if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) gl_loader = "GL3W"; #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) gl_loader = "GLEW"; #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) gl_loader = "GLAD"; #else // IMGUI_IMPL_OPENGL_LOADER_CUSTOM gl_loader = "Custom"; #endif // Make a dummy GL call (we don't actually need the result) // IF YOU GET A CRASH HERE: it probably means that you haven't initialized the OpenGL function loader used by this code. // Desktop OpenGL 3/4 need a function loader. See the IMGUI_IMPL_OPENGL_LOADER_xxx explanation above. GLint current_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); return true; } void ImGui_ImplOpenGL3_Shutdown() { ImGui_ImplOpenGL3_DestroyDeviceObjects(); } void ImGui_ImplOpenGL3_NewFrame() { if (!g_ShaderHandle) ImGui_ImplOpenGL3_CreateDeviceObjects(); if(g_FontTexture == 0) ImGui_ImplOpenGL3_CreateFontsTexture(); } static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) { // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill glEnablei(GL_BLEND, 0); glBlendEquationSeparatei(0, GL_FUNC_ADD, GL_FUNC_ADD); glBlendFuncSeparatei(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glEnable(GL_SCISSOR_TEST); #ifdef GL_POLYGON_MODE glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif // Setup viewport, orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); float L = draw_data->DisplayPos.x; float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; float T = draw_data->DisplayPos.y; float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; const float ortho_projection[4][4] = { { 2.0f / (R - L), 0.0f, 0.0f, 0.0f }, { 0.0f, 2.0f / (T - B), 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f, 0.0f }, { (R + L) / (L - R), (T + B) / (B - T), 0.0f, 1.0f }, }; glUseProgram(g_ShaderHandle); glUniform1i(g_AttribLocationTex, 0); glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); #ifdef GL_SAMPLER_BINDING glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise. #endif (void)vertex_array_object; #ifndef IMGUI_IMPL_OPENGL_ES2 glBindVertexArray(vertex_array_object); #endif // Bind vertex/index buffers and setup attributes for ImDrawVert glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); glEnableVertexAttribArray(g_AttribLocationVtxPos); glEnableVertexAttribArray(g_AttribLocationVtxUV); glEnableVertexAttribArray(g_AttribLocationVtxColor); glVertexAttribPointer(g_AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); glVertexAttribPointer(g_AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); glVertexAttribPointer(g_AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); } // OpenGL3 Render function. // (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); if (fb_width <= 0 || fb_height <= 0) return; // Backup GL state GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); glActiveTexture(GL_TEXTURE0); GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); #ifdef GL_SAMPLER_BINDING GLint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, &last_sampler); #endif GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 GLint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array_object); #endif #ifdef GL_POLYGON_MODE GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); #endif GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); //GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); //GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); //GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); //GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); //GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); //GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); //GLboolean last_enable_blend = false; glIsEnabled(GL_BLEND); GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); bool clip_origin_lower_left = true; #if defined(GL_CLIP_ORIGIN) && !defined(__APPLE__) GLenum last_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&last_clip_origin); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT) if (last_clip_origin == GL_UPPER_LEFT) clip_origin_lower_left = false; #endif // Setup desired GL state // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. GLuint vertex_array_object = 0; #ifndef IMGUI_IMPL_OPENGL_ES2 glGenVertexArrays(1, &vertex_array_object); #endif ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) // Render command lists for (int n = 0; n < draw_data->CmdListsCount; n++) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; // Upload vertex/index buffers glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; if (pcmd->UserCallback != NULL) { // User callback, registered via ImDrawList::AddCallback() // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); else pcmd->UserCallback(cmd_list, pcmd); } else { // Project scissor/clipping rectangles into framebuffer space ImVec4 clip_rect; clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) { // Apply scissor/clipping rectangle if (clip_origin_lower_left) glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); else glScissor((int)clip_rect.x, (int)clip_rect.y, (int)clip_rect.z, (int)clip_rect.w); // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) // Bind texture, Draw glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); #if IMGUI_IMPL_OPENGL_HAS_DRAW_WITH_BASE_VERTEX glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset); #else glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))); #endif } } } } // Destroy the temporary VAO #ifndef IMGUI_IMPL_OPENGL_ES2 glDeleteVertexArrays(1, &vertex_array_object); #endif // Restore modified GL state glUseProgram(last_program); glBindTexture(GL_TEXTURE_2D, last_texture); #ifdef GL_SAMPLER_BINDING glBindSampler(0, last_sampler); #endif glActiveTexture(last_active_texture); #ifndef IMGUI_IMPL_OPENGL_ES2 glBindVertexArray(last_vertex_array_object); #endif glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); //glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); //glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); //if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); glDisablei(GL_BLEND, 0); if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); #ifdef GL_POLYGON_MODE glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); #endif glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); } bool ImGui_ImplOpenGL3_CreateFontsTexture() { if (g_FontTexture != 0) // already created return false; // Build texture atlas ImGuiIO& io = ImGui::GetIO(); unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. // Upload texture to graphics system GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGenTextures(1, &g_FontTexture); glBindTexture(GL_TEXTURE_2D, g_FontTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); #ifdef GL_UNPACK_ROW_LENGTH glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); #endif glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Store our identifier io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontTexture; // Restore state glBindTexture(GL_TEXTURE_2D, last_texture); return true; } void ImGui_ImplOpenGL3_DestroyFontsTexture() { if (g_FontTexture) { ImGuiIO& io = ImGui::GetIO(); glDeleteTextures(1, &g_FontTexture); io.Fonts->TexID = 0; g_FontTexture = 0; } } // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. static bool CheckShader(GLuint handle, const char* desc) { GLint status = 0, log_length = 0; glGetShaderiv(handle, GL_COMPILE_STATUS, &status); glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); if ((GLboolean)status == GL_FALSE) fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s!\n", desc); if (log_length > 1) { ImVector buf; buf.resize((int)(log_length + 1)); glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); fprintf(stderr, "%s\n", buf.begin()); } return (GLboolean)status == GL_TRUE; } // If you get an error please report on GitHub. You may try different GL context version or GLSL version. static bool CheckProgram(GLuint handle, const char* desc) { GLint status = 0, log_length = 0; glGetProgramiv(handle, GL_LINK_STATUS, &status); glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); if ((GLboolean)status == GL_FALSE) fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')\n", desc, g_GlslVersionString); if (log_length > 1) { ImVector buf; buf.resize((int)(log_length + 1)); glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); fprintf(stderr, "%s\n", buf.begin()); } return (GLboolean)status == GL_TRUE; } bool ImGui_ImplOpenGL3_CreateDeviceObjects() { // Backup GL state GLint last_texture, last_array_buffer; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); #endif // Parse GLSL version string int glsl_version = 130; sscanf(g_GlslVersionString, "#version %d", &glsl_version); const GLchar* vertex_shader_glsl_120 = "uniform mat4 ProjMtx;\n" "attribute vec2 Position;\n" "attribute vec2 UV;\n" "attribute vec4 Color;\n" "varying vec2 Frag_UV;\n" "varying vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* vertex_shader_glsl_130 = "uniform mat4 ProjMtx;\n" "in vec2 Position;\n" "in vec2 UV;\n" "in vec4 Color;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* vertex_shader_glsl_300_es = "precision mediump float;\n" "layout (location = 0) in vec2 Position;\n" "layout (location = 1) in vec2 UV;\n" "layout (location = 2) in vec4 Color;\n" "uniform mat4 ProjMtx;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* vertex_shader_glsl_410_core = "layout (location = 0) in vec2 Position;\n" "layout (location = 1) in vec2 UV;\n" "layout (location = 2) in vec4 Color;\n" "uniform mat4 ProjMtx;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* fragment_shader_glsl_120 = "#ifdef GL_ES\n" " precision mediump float;\n" "#endif\n" "uniform sampler2D Texture;\n" "varying vec2 Frag_UV;\n" "varying vec4 Frag_Color;\n" "void main()\n" "{\n" " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" "}\n"; const GLchar* fragment_shader_glsl_130 = "uniform sampler2D Texture;\n" "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" "out vec4 Out_Color;\n" "void main()\n" "{\n" " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" "}\n"; const GLchar* fragment_shader_glsl_300_es = "precision mediump float;\n" "uniform sampler2D Texture;\n" "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" "layout (location = 0) out vec4 Out_Color;\n" "void main()\n" "{\n" " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" "}\n"; const GLchar* fragment_shader_glsl_410_core = "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" "uniform sampler2D Texture;\n" "layout (location = 0) out vec4 Out_Color;\n" "void main()\n" "{\n" " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" "}\n"; // Select shaders matching our GLSL versions const GLchar* vertex_shader = NULL; const GLchar* fragment_shader = NULL; if (glsl_version < 130) { vertex_shader = vertex_shader_glsl_120; fragment_shader = fragment_shader_glsl_120; } else if (glsl_version >= 410) { vertex_shader = vertex_shader_glsl_410_core; fragment_shader = fragment_shader_glsl_410_core; } else if (glsl_version == 300) { vertex_shader = vertex_shader_glsl_300_es; fragment_shader = fragment_shader_glsl_300_es; } else { vertex_shader = vertex_shader_glsl_130; fragment_shader = fragment_shader_glsl_130; } // Create shaders const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader }; g_VertHandle = glCreateShader(GL_VERTEX_SHADER); glShaderSource(g_VertHandle, 2, vertex_shader_with_version, NULL); glCompileShader(g_VertHandle); CheckShader(g_VertHandle, "vertex shader"); const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader }; g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(g_FragHandle, 2, fragment_shader_with_version, NULL); glCompileShader(g_FragHandle); CheckShader(g_FragHandle, "fragment shader"); g_ShaderHandle = glCreateProgram(); glAttachShader(g_ShaderHandle, g_VertHandle); glAttachShader(g_ShaderHandle, g_FragHandle); glLinkProgram(g_ShaderHandle); CheckProgram(g_ShaderHandle, "shader program"); g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); g_AttribLocationVtxPos = glGetAttribLocation(g_ShaderHandle, "Position"); g_AttribLocationVtxUV = glGetAttribLocation(g_ShaderHandle, "UV"); g_AttribLocationVtxColor = glGetAttribLocation(g_ShaderHandle, "Color"); // Create buffers glGenBuffers(1, &g_VboHandle); glGenBuffers(1, &g_ElementsHandle); ImGui_ImplOpenGL3_CreateFontsTexture(); // Restore modified GL state glBindTexture(GL_TEXTURE_2D, last_texture); glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 glBindVertexArray(last_vertex_array); #endif return true; } void ImGui_ImplOpenGL3_DestroyDeviceObjects() { if (g_VboHandle) { glDeleteBuffers(1, &g_VboHandle); g_VboHandle = 0; } if (g_ElementsHandle) { glDeleteBuffers(1, &g_ElementsHandle); g_ElementsHandle = 0; } if (g_ShaderHandle && g_VertHandle) { glDetachShader(g_ShaderHandle, g_VertHandle); } if (g_ShaderHandle && g_FragHandle) { glDetachShader(g_ShaderHandle, g_FragHandle); } if (g_VertHandle) { glDeleteShader(g_VertHandle); g_VertHandle = 0; } if (g_FragHandle) { glDeleteShader(g_FragHandle); g_FragHandle = 0; } if (g_ShaderHandle) { glDeleteProgram(g_ShaderHandle); g_ShaderHandle = 0; } ImGui_ImplOpenGL3_DestroyFontsTexture(); } ================================================ FILE: src/imgui/imgui_impl_opengl3.h ================================================ // dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline // - Desktop GL: 3.x 4.x // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) // Implemented features: // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui // About Desktop OpenGL function loaders: // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. // About GLSL version: // The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string. // On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" // Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. #pragma once // Specific OpenGL versions //#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten //#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android #include "imgui.h" #define IMGUI_IMPL_OPENGL_LOADER_CUSTOM "glext.h" #include "Common/GLInclude/GLInclude.h" // Set default OpenGL3 loader to be gl3w #if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \ && !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \ && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \ && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) #define IMGUI_IMPL_OPENGL_LOADER_GL3W #endif #undef IMGUI_IMPL_OPENGL_LOADER_GL3W IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL); IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); // Called by Init/NewFrame/Shutdown IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); ================================================ FILE: src/imgui/imgui_impl_vulkan.cpp ================================================ // dear imgui: Renderer for Vulkan // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) // Implemented features: // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. // In this binding, ImTextureID is used to store a 'VkDescriptorSet' texture identifier. Read the FAQ about ImTextureID in imgui.cpp. // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui // The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ // Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app. // - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h. // You will use those if you want to use this rendering back-end in your engine/app. // - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by // the back-end itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code. // Read comments in imgui_impl_vulkan.h. // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2019-05-29: Vulkan: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-04-30: Vulkan: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. // 2019-04-04: *BREAKING CHANGE*: Vulkan: Added ImageCount/MinImageCount fields in ImGui_ImplVulkan_InitInfo, required for initialization (was previously a hard #define IMGUI_VK_QUEUED_FRAMES 2). Added ImGui_ImplVulkan_SetMinImageCount(). // 2019-04-04: Vulkan: Added VkInstance argument to ImGui_ImplVulkanH_CreateWindow() optional helper. // 2019-04-04: Vulkan: Avoid passing negative coordinates to vkCmdSetScissor, which debug validation layers do not like. // 2019-04-01: Vulkan: Support for 32-bit index buffer (#define ImDrawIdx unsigned int). // 2019-02-16: Vulkan: Viewport and clipping rectangles correctly using draw_data->FramebufferScale to allow retina display. // 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. // 2018-08-25: Vulkan: Fixed mishandled VkSurfaceCapabilitiesKHR::maxImageCount=0 case. // 2018-06-22: Inverted the parameters to ImGui_ImplVulkan_RenderDrawData() to be consistent with other bindings. // 2018-06-08: Misc: Extracted imgui_impl_vulkan.cpp/.h away from the old combined GLFW+Vulkan example. // 2018-06-08: Vulkan: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. // 2018-03-03: Vulkan: Various refactor, created a couple of ImGui_ImplVulkanH_XXX helper that the example can use and that viewport support will use. // 2018-03-01: Vulkan: Renamed ImGui_ImplVulkan_Init_Info to ImGui_ImplVulkan_InitInfo and fields to match more closely Vulkan terminology. // 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback, ImGui_ImplVulkan_Render() calls ImGui_ImplVulkan_RenderDrawData() itself. // 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. // 2017-05-15: Vulkan: Fix scissor offset being negative. Fix new Vulkan validation warnings. Set required depth member for buffer image copy. // 2016-11-13: Vulkan: Fix validation layer warnings and errors and redeclare gl_PerVertex. // 2016-10-18: Vulkan: Add location decorators & change to use structs as in/out in glsl, update embedded spv (produced with glslangValidator -x). Null the released resources. // 2016-08-27: Vulkan: Fix Vulkan example for use when a depth buffer is active. #include "imgui.h" #include "imgui_impl_vulkan.h" #include #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" // Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData() // [Please zero-clear before use!] struct ImGui_ImplVulkanH_FrameRenderBuffers { VkDeviceMemory VertexBufferMemory; VkDeviceMemory IndexBufferMemory; VkDeviceSize VertexBufferSize; VkDeviceSize IndexBufferSize; VkBuffer VertexBuffer; VkBuffer IndexBuffer; }; // Each viewport will hold 1 ImGui_ImplVulkanH_WindowRenderBuffers // [Please zero-clear before use!] struct ImGui_ImplVulkanH_WindowRenderBuffers { uint32_t Index; uint32_t Count; ImGui_ImplVulkanH_FrameRenderBuffers* FrameRenderBuffers; }; // Vulkan data static ImGui_ImplVulkan_InitInfo g_VulkanInitInfo = {}; static VkRenderPass g_RenderPass = VK_NULL_HANDLE; static VkDeviceSize g_BufferMemoryAlignment = 256; static VkPipelineCreateFlags g_PipelineCreateFlags = 0x00; static VkDescriptorSetLayout g_DescriptorSetLayout = VK_NULL_HANDLE; static VkPipelineLayout g_PipelineLayout = VK_NULL_HANDLE; // static VkDescriptorSet g_DescriptorSet = VK_NULL_HANDLE; static VkPipeline g_Pipeline = VK_NULL_HANDLE; // Font data static VkSampler g_FontSampler = VK_NULL_HANDLE; static VkDeviceMemory g_FontMemory = VK_NULL_HANDLE; static VkImage g_FontImage = VK_NULL_HANDLE; static VkImageView g_FontView = VK_NULL_HANDLE; static VkDeviceMemory g_UploadBufferMemory = VK_NULL_HANDLE; static VkBuffer g_UploadBuffer = VK_NULL_HANDLE; // Render buffers static ImGui_ImplVulkanH_WindowRenderBuffers g_MainWindowRenderBuffers; // Forward Declarations bool ImGui_ImplVulkan_CreateDeviceObjects(); void ImGui_ImplVulkan_DestroyDeviceObjects(); void ImGui_ImplVulkanH_DestroyFrame(VkDevice device, ImGui_ImplVulkanH_Frame* fd, const VkAllocationCallbacks* allocator); void ImGui_ImplVulkanH_DestroyFrameSemaphores(VkDevice device, ImGui_ImplVulkanH_FrameSemaphores* fsd, const VkAllocationCallbacks* allocator); void ImGui_ImplVulkanH_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkanH_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator); void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkanH_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator); void ImGui_ImplVulkanH_CreateWindowSwapchain(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count); void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator); struct ImGuiTexture { VkImage image; VkDeviceMemory memory; VkImageView view; VkSampler sampler; VkDescriptorSet descriptor_set; VkBuffer uploadBuffer; VkDeviceMemory uploadBufferMemory; }; //----------------------------------------------------------------------------- // SHADERS //----------------------------------------------------------------------------- // glsl_shader.vert, compiled with: // # glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert /* #version 450 core layout(location = 0) in vec2 aPos; layout(location = 1) in vec2 aUV; layout(location = 2) in vec4 aColor; layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; } pc; out gl_PerVertex { vec4 gl_Position; }; layout(location = 0) out struct { vec4 Color; vec2 UV; } Out; void main() { Out.Color = aColor; Out.UV = aUV; gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1); } */ static uint32_t __glsl_shader_vert_spv[] = { 0x07230203,0x00010000,0x00080001,0x0000002e,0x00000000,0x00020011,0x00000001,0x0006000b, 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, 0x000a000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x0000000f,0x00000015, 0x0000001b,0x0000001c,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, 0x00000000,0x00030005,0x00000009,0x00000000,0x00050006,0x00000009,0x00000000,0x6f6c6f43, 0x00000072,0x00040006,0x00000009,0x00000001,0x00005655,0x00030005,0x0000000b,0x0074754f, 0x00040005,0x0000000f,0x6c6f4361,0x0000726f,0x00030005,0x00000015,0x00565561,0x00060005, 0x00000019,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,0x00000019,0x00000000, 0x505f6c67,0x7469736f,0x006e6f69,0x00030005,0x0000001b,0x00000000,0x00040005,0x0000001c, 0x736f5061,0x00000000,0x00060005,0x0000001e,0x73755075,0x6e6f4368,0x6e617473,0x00000074, 0x00050006,0x0000001e,0x00000000,0x61635375,0x0000656c,0x00060006,0x0000001e,0x00000001, 0x61725475,0x616c736e,0x00006574,0x00030005,0x00000020,0x00006370,0x00040047,0x0000000b, 0x0000001e,0x00000000,0x00040047,0x0000000f,0x0000001e,0x00000002,0x00040047,0x00000015, 0x0000001e,0x00000001,0x00050048,0x00000019,0x00000000,0x0000000b,0x00000000,0x00030047, 0x00000019,0x00000002,0x00040047,0x0000001c,0x0000001e,0x00000000,0x00050048,0x0000001e, 0x00000000,0x00000023,0x00000000,0x00050048,0x0000001e,0x00000001,0x00000023,0x00000008, 0x00030047,0x0000001e,0x00000002,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, 0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040017, 0x00000008,0x00000006,0x00000002,0x0004001e,0x00000009,0x00000007,0x00000008,0x00040020, 0x0000000a,0x00000003,0x00000009,0x0004003b,0x0000000a,0x0000000b,0x00000003,0x00040015, 0x0000000c,0x00000020,0x00000001,0x0004002b,0x0000000c,0x0000000d,0x00000000,0x00040020, 0x0000000e,0x00000001,0x00000007,0x0004003b,0x0000000e,0x0000000f,0x00000001,0x00040020, 0x00000011,0x00000003,0x00000007,0x0004002b,0x0000000c,0x00000013,0x00000001,0x00040020, 0x00000014,0x00000001,0x00000008,0x0004003b,0x00000014,0x00000015,0x00000001,0x00040020, 0x00000017,0x00000003,0x00000008,0x0003001e,0x00000019,0x00000007,0x00040020,0x0000001a, 0x00000003,0x00000019,0x0004003b,0x0000001a,0x0000001b,0x00000003,0x0004003b,0x00000014, 0x0000001c,0x00000001,0x0004001e,0x0000001e,0x00000008,0x00000008,0x00040020,0x0000001f, 0x00000009,0x0000001e,0x0004003b,0x0000001f,0x00000020,0x00000009,0x00040020,0x00000021, 0x00000009,0x00000008,0x0004002b,0x00000006,0x00000028,0x00000000,0x0004002b,0x00000006, 0x00000029,0x3f800000,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8, 0x00000005,0x0004003d,0x00000007,0x00000010,0x0000000f,0x00050041,0x00000011,0x00000012, 0x0000000b,0x0000000d,0x0003003e,0x00000012,0x00000010,0x0004003d,0x00000008,0x00000016, 0x00000015,0x00050041,0x00000017,0x00000018,0x0000000b,0x00000013,0x0003003e,0x00000018, 0x00000016,0x0004003d,0x00000008,0x0000001d,0x0000001c,0x00050041,0x00000021,0x00000022, 0x00000020,0x0000000d,0x0004003d,0x00000008,0x00000023,0x00000022,0x00050085,0x00000008, 0x00000024,0x0000001d,0x00000023,0x00050041,0x00000021,0x00000025,0x00000020,0x00000013, 0x0004003d,0x00000008,0x00000026,0x00000025,0x00050081,0x00000008,0x00000027,0x00000024, 0x00000026,0x00050051,0x00000006,0x0000002a,0x00000027,0x00000000,0x00050051,0x00000006, 0x0000002b,0x00000027,0x00000001,0x00070050,0x00000007,0x0000002c,0x0000002a,0x0000002b, 0x00000028,0x00000029,0x00050041,0x00000011,0x0000002d,0x0000001b,0x0000000d,0x0003003e, 0x0000002d,0x0000002c,0x000100fd,0x00010038 }; // glsl_shader.frag, compiled with: // # glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag /* #version 450 core layout(location = 0) out vec4 fColor; layout(set=0, binding=0) uniform sampler2D sTexture; layout(location = 0) in struct { vec4 Color; vec2 UV; } In; void main() { fColor = In.Color * texture(sTexture, In.UV.st); } */ static uint32_t __glsl_shader_frag_spv[] = { 0x07230203,0x00010000,0x00080001,0x0000001e,0x00000000,0x00020011,0x00000001,0x0006000b, 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000009,0x0000000d,0x00030010, 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, 0x00000000,0x00040005,0x00000009,0x6c6f4366,0x0000726f,0x00030005,0x0000000b,0x00000000, 0x00050006,0x0000000b,0x00000000,0x6f6c6f43,0x00000072,0x00040006,0x0000000b,0x00000001, 0x00005655,0x00030005,0x0000000d,0x00006e49,0x00050005,0x00000016,0x78655473,0x65727574, 0x00000000,0x00040047,0x00000009,0x0000001e,0x00000000,0x00040047,0x0000000d,0x0000001e, 0x00000000,0x00040047,0x00000016,0x00000022,0x00000000,0x00040047,0x00000016,0x00000021, 0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006, 0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,0x00000003, 0x00000007,0x0004003b,0x00000008,0x00000009,0x00000003,0x00040017,0x0000000a,0x00000006, 0x00000002,0x0004001e,0x0000000b,0x00000007,0x0000000a,0x00040020,0x0000000c,0x00000001, 0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000001,0x00040015,0x0000000e,0x00000020, 0x00000001,0x0004002b,0x0000000e,0x0000000f,0x00000000,0x00040020,0x00000010,0x00000001, 0x00000007,0x00090019,0x00000013,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000, 0x00000001,0x00000000,0x0003001b,0x00000014,0x00000013,0x00040020,0x00000015,0x00000000, 0x00000014,0x0004003b,0x00000015,0x00000016,0x00000000,0x0004002b,0x0000000e,0x00000018, 0x00000001,0x00040020,0x00000019,0x00000001,0x0000000a,0x00050036,0x00000002,0x00000004, 0x00000000,0x00000003,0x000200f8,0x00000005,0x00050041,0x00000010,0x00000011,0x0000000d, 0x0000000f,0x0004003d,0x00000007,0x00000012,0x00000011,0x0004003d,0x00000014,0x00000017, 0x00000016,0x00050041,0x00000019,0x0000001a,0x0000000d,0x00000018,0x0004003d,0x0000000a, 0x0000001b,0x0000001a,0x00050057,0x00000007,0x0000001c,0x00000017,0x0000001b,0x00050085, 0x00000007,0x0000001d,0x00000012,0x0000001c,0x0003003e,0x00000009,0x0000001d,0x000100fd, 0x00010038 }; //----------------------------------------------------------------------------- // FUNCTIONS //----------------------------------------------------------------------------- static uint32_t ImGui_ImplVulkan_MemoryType(VkMemoryPropertyFlags properties, uint32_t type_bits) { ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; VkPhysicalDeviceMemoryProperties prop; vkGetPhysicalDeviceMemoryProperties(v->PhysicalDevice, &prop); for (uint32_t i = 0; i < prop.memoryTypeCount; i++) if ((prop.memoryTypes[i].propertyFlags & properties) == properties && type_bits & (1<CheckVkResultFn) v->CheckVkResultFn(err); } static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory, VkDeviceSize& p_buffer_size, size_t new_size, VkBufferUsageFlagBits usage) { ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; vkDeviceWaitIdle(v->Device); // make sure previously created buffer is not in use anymore VkResult err; if (buffer != VK_NULL_HANDLE) vkDestroyBuffer(v->Device, buffer, v->Allocator); if (buffer_memory != VK_NULL_HANDLE) vkFreeMemory(v->Device, buffer_memory, v->Allocator); VkDeviceSize vertex_buffer_size_aligned = ((new_size - 1) / g_BufferMemoryAlignment + 1) * g_BufferMemoryAlignment; VkBufferCreateInfo buffer_info = {}; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; buffer_info.size = vertex_buffer_size_aligned; buffer_info.usage = usage; buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &buffer); check_vk_result(err); VkMemoryRequirements req; vkGetBufferMemoryRequirements(v->Device, buffer, &req); g_BufferMemoryAlignment = (g_BufferMemoryAlignment > req.alignment) ? g_BufferMemoryAlignment : req.alignment; VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.allocationSize = req.size; alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &buffer_memory); check_vk_result(err); err = vkBindBufferMemory(v->Device, buffer, buffer_memory, 0); check_vk_result(err); p_buffer_size = new_size; } static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkCommandBuffer command_buffer, ImGui_ImplVulkanH_FrameRenderBuffers* rb, int fb_width, int fb_height) { // Bind pipeline { vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_Pipeline); //VkDescriptorSet desc_set[1] = { g_DescriptorSet }; //vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_PipelineLayout, 0, 1, desc_set, 0, NULL); } // Bind Vertex And Index Buffer: { VkBuffer vertex_buffers[1] = { rb->VertexBuffer }; VkDeviceSize vertex_offset[1] = { 0 }; vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffers, vertex_offset); vkCmdBindIndexBuffer(command_buffer, rb->IndexBuffer, 0, sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32); } // Setup viewport: { VkViewport viewport; viewport.x = 0; viewport.y = 0; viewport.width = (float)fb_width; viewport.height = (float)fb_height; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vkCmdSetViewport(command_buffer, 0, 1, &viewport); } // Setup scale and translation: // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. { float scale[2]; scale[0] = 2.0f / draw_data->DisplaySize.x; scale[1] = 2.0f / draw_data->DisplaySize.y; float translate[2]; translate[0] = -1.0f - draw_data->DisplayPos.x * scale[0]; translate[1] = -1.0f - draw_data->DisplayPos.y * scale[1]; vkCmdPushConstants(command_buffer, g_PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 0, sizeof(float) * 2, scale); vkCmdPushConstants(command_buffer, g_PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 2, sizeof(float) * 2, translate); } } // Render function // (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); if (fb_width <= 0 || fb_height <= 0 || draw_data->TotalVtxCount == 0) return; ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; // Allocate array to store enough vertex/index buffers ImGui_ImplVulkanH_WindowRenderBuffers* wrb = &g_MainWindowRenderBuffers; if (wrb->FrameRenderBuffers == NULL) { wrb->Index = 0; wrb->Count = v->ImageCount; wrb->FrameRenderBuffers = (ImGui_ImplVulkanH_FrameRenderBuffers*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameRenderBuffers) * wrb->Count); memset(wrb->FrameRenderBuffers, 0, sizeof(ImGui_ImplVulkanH_FrameRenderBuffers) * wrb->Count); } IM_ASSERT(wrb->Count == v->ImageCount); wrb->Index = (wrb->Index + 1) % wrb->Count; ImGui_ImplVulkanH_FrameRenderBuffers* rb = &wrb->FrameRenderBuffers[wrb->Index]; VkResult err; // Create or resize the vertex/index buffers size_t vertex_size = draw_data->TotalVtxCount * sizeof(ImDrawVert); size_t index_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx); if (rb->VertexBuffer == VK_NULL_HANDLE || rb->VertexBufferSize < vertex_size) CreateOrResizeBuffer(rb->VertexBuffer, rb->VertexBufferMemory, rb->VertexBufferSize, vertex_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); if (rb->IndexBuffer == VK_NULL_HANDLE || rb->IndexBufferSize < index_size) CreateOrResizeBuffer(rb->IndexBuffer, rb->IndexBufferMemory, rb->IndexBufferSize, index_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); // Upload vertex/index data into a single contiguous GPU buffer { cemu_assert_debug(vertex_size != 0); cemu_assert_debug(index_size != 0); // todo - read and use actual nonCoherentAtomSize instead of assuming 0x80 uint32 aligned_vertex_size = (vertex_size + 0x7F) & ~0x7F; uint32 aligned_index_size = (index_size + 0x7F) & ~0x7F; ImDrawVert* vtx_dst = NULL; ImDrawIdx* idx_dst = NULL; err = vkMapMemory(v->Device, rb->VertexBufferMemory, 0, aligned_vertex_size, 0, (void**)(&vtx_dst)); check_vk_result(err); err = vkMapMemory(v->Device, rb->IndexBufferMemory, 0, aligned_index_size, 0, (void**)(&idx_dst)); check_vk_result(err); for (int n = 0; n < draw_data->CmdListsCount; n++) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); vtx_dst += cmd_list->VtxBuffer.Size; idx_dst += cmd_list->IdxBuffer.Size; } VkMappedMemoryRange range[2] = {}; range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; range[0].memory = rb->VertexBufferMemory; range[0].size = VK_WHOLE_SIZE; range[1].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; range[1].memory = rb->IndexBufferMemory; range[1].size = VK_WHOLE_SIZE; err = vkFlushMappedMemoryRanges(v->Device, 2, range); check_vk_result(err); vkUnmapMemory(v->Device, rb->VertexBufferMemory); vkUnmapMemory(v->Device, rb->IndexBufferMemory); } // Setup desired Vulkan state ImGui_ImplVulkan_SetupRenderState(draw_data, command_buffer, rb, fb_width, fb_height); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) // Render command lists // (Because we merged all buffers into a single one, we maintain our own offset into them) int vtx_offset = 0; int idx_offset = 0; for (int n = 0; n < draw_data->CmdListsCount; n++) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; if (pcmd->UserCallback != NULL) { // User callback, registered via ImDrawList::AddCallback() // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) ImGui_ImplVulkan_SetupRenderState(draw_data, command_buffer, rb, fb_width, fb_height); else pcmd->UserCallback(cmd_list, pcmd); } else { // Project scissor/clipping rectangles into framebuffer space ImVec4 clip_rect; clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) { // Negative offsets are illegal for vkCmdSetScissor if (clip_rect.x < 0.0f) clip_rect.x = 0.0f; if (clip_rect.y < 0.0f) clip_rect.y = 0.0f; // Apply scissor/clipping rectangle VkRect2D scissor; scissor.offset.x = (int32_t)(clip_rect.x); scissor.offset.y = (int32_t)(clip_rect.y); scissor.extent.width = (uint32_t)(clip_rect.z - clip_rect.x); scissor.extent.height = (uint32_t)(clip_rect.w - clip_rect.y); vkCmdSetScissor(command_buffer, 0, 1, &scissor); VkDescriptorSet desc_set[1] = { ((ImGuiTexture*)pcmd->TextureId)->descriptor_set }; vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_PipelineLayout, 0, 1, desc_set, 0, NULL); // Draw //vkCmdDrawIndexed(command_buffer, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); vkCmdDrawIndexed(command_buffer, pcmd->ElemCount, 1, idx_offset, vtx_offset, 0); } } idx_offset += pcmd->ElemCount; } vtx_offset += cmd_list->VtxBuffer.Size; // global_idx_offset += cmd_list->IdxBuffer.Size; // global_vtx_offset += cmd_list->VtxBuffer.Size; } } void ImGui_ImplVulkan_DestroyFontsTexture() { ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; ImGui_ImplVulkan_DestroyFontUploadObjects(); if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; } if (g_FontImage) { vkDestroyImage(v->Device, g_FontImage, v->Allocator); g_FontImage = VK_NULL_HANDLE; } if (g_FontMemory) { vkFreeMemory(v->Device, g_FontMemory, v->Allocator); g_FontMemory = VK_NULL_HANDLE; } ImGuiIO& io = ImGui::GetIO(); auto texture = io.Fonts->TexID; if(texture != (ImTextureID)nullptr) { ImGui_ImplVulkan_DeleteTexture(texture); delete (ImGuiTexture*)texture; io.Fonts->TexID = nullptr; } } bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) { if(g_FontView) return true; ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; ImGuiIO& io = ImGui::GetIO(); unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); size_t upload_size = width*height*4*sizeof(char); VkResult err; // Create the Image: { VkImageCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; info.imageType = VK_IMAGE_TYPE_2D; info.format = VK_FORMAT_R8G8B8A8_UNORM; info.extent.width = width; info.extent.height = height; info.extent.depth = 1; info.mipLevels = 1; info.arrayLayers = 1; info.samples = VK_SAMPLE_COUNT_1_BIT; info.tiling = VK_IMAGE_TILING_OPTIMAL; info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; err = vkCreateImage(v->Device, &info, v->Allocator, &g_FontImage); check_vk_result(err); VkMemoryRequirements req; vkGetImageMemoryRequirements(v->Device, g_FontImage, &req); VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.allocationSize = req.size; alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits); err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &g_FontMemory); check_vk_result(err); err = vkBindImageMemory(v->Device, g_FontImage, g_FontMemory, 0); check_vk_result(err); } // Create the Image View: { VkImageViewCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; info.image = g_FontImage; info.viewType = VK_IMAGE_VIEW_TYPE_2D; info.format = VK_FORMAT_R8G8B8A8_UNORM; info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; info.subresourceRange.levelCount = 1; info.subresourceRange.layerCount = 1; err = vkCreateImageView(v->Device, &info, v->Allocator, &g_FontView); check_vk_result(err); } //// Update the Descriptor Set: //{ // VkDescriptorImageInfo desc_image[1] = {}; // desc_image[0].sampler = g_FontSampler; // desc_image[0].imageView = g_FontView; // desc_image[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; // VkWriteDescriptorSet write_desc[1] = {}; // write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; // write_desc[0].dstSet = g_DescriptorSet; // write_desc[0].descriptorCount = 1; // write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; // write_desc[0].pImageInfo = desc_image; // vkUpdateDescriptorSets(v->Device, 1, write_desc, 0, NULL); //} VkDescriptorSet font_descriptor_set = (VkDescriptorSet)ImGui_ImplVulkan_AddTexture(g_FontSampler, g_FontView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); // Create the Upload Buffer: { VkBufferCreateInfo buffer_info = {}; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; buffer_info.size = upload_size; buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &g_UploadBuffer); check_vk_result(err); VkMemoryRequirements req; vkGetBufferMemoryRequirements(v->Device, g_UploadBuffer, &req); g_BufferMemoryAlignment = (g_BufferMemoryAlignment > req.alignment) ? g_BufferMemoryAlignment : req.alignment; VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.allocationSize = req.size; alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &g_UploadBufferMemory); check_vk_result(err); err = vkBindBufferMemory(v->Device, g_UploadBuffer, g_UploadBufferMemory, 0); check_vk_result(err); } // Upload to Buffer: { cemu_assert_debug(upload_size != 0); char* map = NULL; err = vkMapMemory(v->Device, g_UploadBufferMemory, 0, upload_size, 0, (void**)(&map)); check_vk_result(err); memcpy(map, pixels, upload_size); VkMappedMemoryRange range[1] = {}; range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; range[0].memory = g_UploadBufferMemory; range[0].size = upload_size; err = vkFlushMappedMemoryRanges(v->Device, 1, range); check_vk_result(err); vkUnmapMemory(v->Device, g_UploadBufferMemory); } // Copy to Image: { VkImageMemoryBarrier copy_barrier[1] = {}; copy_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; copy_barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; copy_barrier[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; copy_barrier[0].image = g_FontImage; copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; copy_barrier[0].subresourceRange.levelCount = 1; copy_barrier[0].subresourceRange.layerCount = 1; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, copy_barrier); VkBufferImageCopy region = {}; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.layerCount = 1; region.imageExtent.width = width; region.imageExtent.height = height; region.imageExtent.depth = 1; vkCmdCopyBufferToImage(command_buffer, g_UploadBuffer, g_FontImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); VkImageMemoryBarrier use_barrier[1] = {}; use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; use_barrier[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; use_barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; use_barrier[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; use_barrier[0].image = g_FontImage; use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; use_barrier[0].subresourceRange.levelCount = 1; use_barrier[0].subresourceRange.layerCount = 1; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, use_barrier); } // Store our identifier //io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontImage; auto texture = new ImGuiTexture(); texture->descriptor_set = font_descriptor_set; io.Fonts->TexID = (ImTextureID)texture; return true; } bool ImGui_ImplVulkan_CreateDeviceObjects() { ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; VkResult err; VkShaderModule vert_module; VkShaderModule frag_module; // Create The Shader Modules: { VkShaderModuleCreateInfo vert_info = {}; vert_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; vert_info.codeSize = sizeof(__glsl_shader_vert_spv); vert_info.pCode = (uint32_t*)__glsl_shader_vert_spv; err = vkCreateShaderModule(v->Device, &vert_info, v->Allocator, &vert_module); check_vk_result(err); VkShaderModuleCreateInfo frag_info = {}; frag_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; frag_info.codeSize = sizeof(__glsl_shader_frag_spv); frag_info.pCode = (uint32_t*)__glsl_shader_frag_spv; err = vkCreateShaderModule(v->Device, &frag_info, v->Allocator, &frag_module); check_vk_result(err); } if (!g_FontSampler) { VkSamplerCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; info.magFilter = VK_FILTER_LINEAR; info.minFilter = VK_FILTER_LINEAR; info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; info.minLod = -1000; info.maxLod = 1000; info.maxAnisotropy = 1.0f; err = vkCreateSampler(v->Device, &info, v->Allocator, &g_FontSampler); check_vk_result(err); } if (!g_DescriptorSetLayout) { VkSampler sampler[1] = {g_FontSampler}; VkDescriptorSetLayoutBinding binding[1] = {}; binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; binding[0].descriptorCount = 1; binding[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; //binding[0].pImmutableSamplers = sampler; VkDescriptorSetLayoutCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; info.bindingCount = 1; info.pBindings = binding; err = vkCreateDescriptorSetLayout(v->Device, &info, v->Allocator, &g_DescriptorSetLayout); check_vk_result(err); } //// Create Descriptor Set: //{ // VkDescriptorSetAllocateInfo alloc_info = {}; // alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; // alloc_info.descriptorPool = v->DescriptorPool; // alloc_info.descriptorSetCount = 1; // alloc_info.pSetLayouts = &g_DescriptorSetLayout; // err = vkAllocateDescriptorSets(v->Device, &alloc_info, &g_DescriptorSet); // check_vk_result(err); //} if (!g_PipelineLayout) { // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix VkPushConstantRange push_constants[1] = {}; push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; push_constants[0].offset = sizeof(float) * 0; push_constants[0].size = sizeof(float) * 4; VkDescriptorSetLayout set_layout[1] = { g_DescriptorSetLayout }; VkPipelineLayoutCreateInfo layout_info = {}; layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; layout_info.setLayoutCount = 1; layout_info.pSetLayouts = set_layout; layout_info.pushConstantRangeCount = 1; layout_info.pPushConstantRanges = push_constants; err = vkCreatePipelineLayout(v->Device, &layout_info, v->Allocator, &g_PipelineLayout); check_vk_result(err); } VkPipelineShaderStageCreateInfo stage[2] = {}; stage[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stage[0].stage = VK_SHADER_STAGE_VERTEX_BIT; stage[0].module = vert_module; stage[0].pName = "main"; stage[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stage[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; stage[1].module = frag_module; stage[1].pName = "main"; VkVertexInputBindingDescription binding_desc[1] = {}; binding_desc[0].stride = sizeof(ImDrawVert); binding_desc[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; VkVertexInputAttributeDescription attribute_desc[3] = {}; attribute_desc[0].location = 0; attribute_desc[0].binding = binding_desc[0].binding; attribute_desc[0].format = VK_FORMAT_R32G32_SFLOAT; attribute_desc[0].offset = IM_OFFSETOF(ImDrawVert, pos); attribute_desc[1].location = 1; attribute_desc[1].binding = binding_desc[0].binding; attribute_desc[1].format = VK_FORMAT_R32G32_SFLOAT; attribute_desc[1].offset = IM_OFFSETOF(ImDrawVert, uv); attribute_desc[2].location = 2; attribute_desc[2].binding = binding_desc[0].binding; attribute_desc[2].format = VK_FORMAT_R8G8B8A8_UNORM; attribute_desc[2].offset = IM_OFFSETOF(ImDrawVert, col); VkPipelineVertexInputStateCreateInfo vertex_info = {}; vertex_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertex_info.vertexBindingDescriptionCount = 1; vertex_info.pVertexBindingDescriptions = binding_desc; vertex_info.vertexAttributeDescriptionCount = 3; vertex_info.pVertexAttributeDescriptions = attribute_desc; VkPipelineInputAssemblyStateCreateInfo ia_info = {}; ia_info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; ia_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; VkPipelineViewportStateCreateInfo viewport_info = {}; viewport_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewport_info.viewportCount = 1; viewport_info.scissorCount = 1; VkPipelineRasterizationStateCreateInfo raster_info = {}; raster_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; raster_info.polygonMode = VK_POLYGON_MODE_FILL; raster_info.cullMode = VK_CULL_MODE_NONE; raster_info.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; raster_info.lineWidth = 1.0f; VkPipelineMultisampleStateCreateInfo ms_info = {}; ms_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; ms_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; VkPipelineColorBlendAttachmentState color_attachment[1] = {}; color_attachment[0].blendEnable = VK_TRUE; color_attachment[0].srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; color_attachment[0].dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; color_attachment[0].colorBlendOp = VK_BLEND_OP_ADD; color_attachment[0].srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; color_attachment[0].dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; color_attachment[0].alphaBlendOp = VK_BLEND_OP_ADD; color_attachment[0].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; VkPipelineDepthStencilStateCreateInfo depth_info = {}; depth_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; VkPipelineColorBlendStateCreateInfo blend_info = {}; blend_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; blend_info.attachmentCount = 1; blend_info.pAttachments = color_attachment; VkDynamicState dynamic_states[2] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamic_state = {}; dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamic_state.dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states); dynamic_state.pDynamicStates = dynamic_states; VkGraphicsPipelineCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; info.flags = g_PipelineCreateFlags; info.stageCount = 2; info.pStages = stage; info.pVertexInputState = &vertex_info; info.pInputAssemblyState = &ia_info; info.pViewportState = &viewport_info; info.pRasterizationState = &raster_info; info.pMultisampleState = &ms_info; info.pDepthStencilState = &depth_info; info.pColorBlendState = &blend_info; info.pDynamicState = &dynamic_state; info.layout = g_PipelineLayout; info.renderPass = g_RenderPass; err = vkCreateGraphicsPipelines(v->Device, v->PipelineCache, 1, &info, v->Allocator, &g_Pipeline); check_vk_result(err); vkDestroyShaderModule(v->Device, vert_module, v->Allocator); vkDestroyShaderModule(v->Device, frag_module, v->Allocator); return true; } void ImGui_ImplVulkan_DestroyFontUploadObjects() { ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; if (g_UploadBuffer) { vkDestroyBuffer(v->Device, g_UploadBuffer, v->Allocator); g_UploadBuffer = VK_NULL_HANDLE; } if (g_UploadBufferMemory) { vkFreeMemory(v->Device, g_UploadBufferMemory, v->Allocator); g_UploadBufferMemory = VK_NULL_HANDLE; } } void ImGui_ImplVulkan_DestroyDeviceObjects() { ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &g_MainWindowRenderBuffers, v->Allocator); ImGui_ImplVulkan_DestroyFontUploadObjects(); if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; } if (g_FontImage) { vkDestroyImage(v->Device, g_FontImage, v->Allocator); g_FontImage = VK_NULL_HANDLE; } if (g_FontMemory) { vkFreeMemory(v->Device, g_FontMemory, v->Allocator); g_FontMemory = VK_NULL_HANDLE; } if (g_FontSampler) { vkDestroySampler(v->Device, g_FontSampler, v->Allocator); g_FontSampler = VK_NULL_HANDLE; } if (g_DescriptorSetLayout) { vkDestroyDescriptorSetLayout(v->Device, g_DescriptorSetLayout, v->Allocator); g_DescriptorSetLayout = VK_NULL_HANDLE; } if (g_PipelineLayout) { vkDestroyPipelineLayout(v->Device, g_PipelineLayout, v->Allocator); g_PipelineLayout = VK_NULL_HANDLE; } if (g_Pipeline) { vkDestroyPipeline(v->Device, g_Pipeline, v->Allocator); g_Pipeline = VK_NULL_HANDLE; } } bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass) { // Setup back-end capabilities flags ImGuiIO& io = ImGui::GetIO(); io.BackendRendererName = "imgui_impl_vulkan"; // io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. IM_ASSERT(info->Instance != VK_NULL_HANDLE); IM_ASSERT(info->PhysicalDevice != VK_NULL_HANDLE); IM_ASSERT(info->Device != VK_NULL_HANDLE); IM_ASSERT(info->Queue != VK_NULL_HANDLE); IM_ASSERT(info->DescriptorPool != VK_NULL_HANDLE); IM_ASSERT(info->MinImageCount >= 2); IM_ASSERT(info->ImageCount >= info->MinImageCount); IM_ASSERT(render_pass != VK_NULL_HANDLE); g_VulkanInitInfo = *info; g_RenderPass = render_pass; ImGui_ImplVulkan_CreateDeviceObjects(); return true; } ImTextureID ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout) { VkResult err; VkDescriptorSet descriptor_set; // Create Descriptor Set: { VkDescriptorSetAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; alloc_info.descriptorPool = g_VulkanInitInfo.DescriptorPool; alloc_info.descriptorSetCount = 1; alloc_info.pSetLayouts = &g_DescriptorSetLayout; err = vkAllocateDescriptorSets(g_VulkanInitInfo.Device, &alloc_info, &descriptor_set); check_vk_result(err); } // Update the Descriptor Set: { VkDescriptorImageInfo desc_image[1] = {}; desc_image[0].sampler = sampler; desc_image[0].imageView = image_view; desc_image[0].imageLayout = image_layout; VkWriteDescriptorSet write_desc[1] = {}; write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write_desc[0].dstSet = descriptor_set; write_desc[0].descriptorCount = 1; write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write_desc[0].pImageInfo = desc_image; vkUpdateDescriptorSets(g_VulkanInitInfo.Device, 1, write_desc, 0, NULL); } return (ImTextureID)descriptor_set; } void ImGui_ImplVulkan_Shutdown() { ImGui_ImplVulkan_DestroyDeviceObjects(); } void ImGui_ImplVulkan_NewFrame(VkCommandBuffer command_buffer, VkFramebuffer framebuffer, VkExtent2D extent) { auto& io = ImGui::GetIO(); VkRenderPassBeginInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = g_RenderPass; renderPassInfo.framebuffer = framebuffer; renderPassInfo.renderArea.offset = {0, 0}; renderPassInfo.renderArea.extent = extent; renderPassInfo.clearValueCount = 0; vkCmdBeginRenderPass(command_buffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); } void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count) { IM_ASSERT(min_image_count >= 2); if (g_VulkanInitInfo.MinImageCount == min_image_count) return; ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; VkResult err = vkDeviceWaitIdle(v->Device); check_vk_result(err); ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &g_MainWindowRenderBuffers, v->Allocator); g_VulkanInitInfo.MinImageCount = min_image_count; } //------------------------------------------------------------------------- // Internal / Miscellaneous Vulkan Helpers // (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own app.) //------------------------------------------------------------------------- // You probably do NOT need to use or care about those functions. // Those functions only exist because: // 1) they facilitate the readability and maintenance of the multiple main.cpp examples files. // 2) the upcoming multi-viewport feature will need them internally. // Generally we avoid exposing any kind of superfluous high-level helpers in the bindings, // but it is too much code to duplicate everywhere so we exceptionally expose them. // // Your engine/app will likely _already_ have code to setup all that stuff (swap chain, render pass, frame buffers, etc.). // You may read this code to learn about Vulkan, but it is recommended you use you own custom tailored code to do equivalent work. // (The ImGui_ImplVulkanH_XXX functions do not interact with any of the state used by the regular ImGui_ImplVulkan_XXX functions) //------------------------------------------------------------------------- VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkFormat* request_formats, int request_formats_count, VkColorSpaceKHR request_color_space) { IM_ASSERT(request_formats != NULL); IM_ASSERT(request_formats_count > 0); // Per Spec Format and View Format are expected to be the same unless VK_IMAGE_CREATE_MUTABLE_BIT was set at image creation // Assuming that the default behavior is without setting this bit, there is no need for separate Swapchain image and image view format // Additionally several new color spaces were introduced with Vulkan Spec v1.0.40, // hence we must make sure that a format with the mostly available color space, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, is found and used. uint32_t avail_count; vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &avail_count, NULL); ImVector avail_format; avail_format.resize((int)avail_count); vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &avail_count, avail_format.Data); // First check if only one format, VK_FORMAT_UNDEFINED, is available, which would imply that any format is available if (avail_count == 1) { if (avail_format[0].format == VK_FORMAT_UNDEFINED) { VkSurfaceFormatKHR ret; ret.format = request_formats[0]; ret.colorSpace = request_color_space; return ret; } else { // No point in searching another format return avail_format[0]; } } else { // Request several formats, the first found will be used for (int request_i = 0; request_i < request_formats_count; request_i++) for (uint32_t avail_i = 0; avail_i < avail_count; avail_i++) if (avail_format[avail_i].format == request_formats[request_i] && avail_format[avail_i].colorSpace == request_color_space) return avail_format[avail_i]; // If none of the requested image formats could be found, use the first available return avail_format[0]; } } VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkPresentModeKHR* request_modes, int request_modes_count) { IM_ASSERT(request_modes != NULL); IM_ASSERT(request_modes_count > 0); // Request a certain mode and confirm that it is available. If not use VK_PRESENT_MODE_FIFO_KHR which is mandatory uint32_t avail_count = 0; vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &avail_count, NULL); ImVector avail_modes; avail_modes.resize((int)avail_count); vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &avail_count, avail_modes.Data); //for (uint32_t avail_i = 0; avail_i < avail_count; avail_i++) // printf("[vulkan] avail_modes[%d] = %d\n", avail_i, avail_modes[avail_i]); for (int request_i = 0; request_i < request_modes_count; request_i++) for (uint32_t avail_i = 0; avail_i < avail_count; avail_i++) if (request_modes[request_i] == avail_modes[avail_i]) return request_modes[request_i]; return VK_PRESENT_MODE_FIFO_KHR; // Always available } void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator) { IM_ASSERT(physical_device != VK_NULL_HANDLE && device != VK_NULL_HANDLE); (void)physical_device; (void)allocator; // Create Command Buffers VkResult err; for (uint32_t i = 0; i < wd->ImageCount; i++) { ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[i]; { VkCommandPoolCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; info.queueFamilyIndex = queue_family; err = vkCreateCommandPool(device, &info, allocator, &fd->CommandPool); check_vk_result(err); } { VkCommandBufferAllocateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; info.commandPool = fd->CommandPool; info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; info.commandBufferCount = 1; err = vkAllocateCommandBuffers(device, &info, &fd->CommandBuffer); check_vk_result(err); } { VkFenceCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; info.flags = VK_FENCE_CREATE_SIGNALED_BIT; err = vkCreateFence(device, &info, allocator, &fd->Fence); check_vk_result(err); } { VkSemaphoreCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; err = vkCreateSemaphore(device, &info, allocator, &fsd->ImageAcquiredSemaphore); check_vk_result(err); err = vkCreateSemaphore(device, &info, allocator, &fsd->RenderCompleteSemaphore); check_vk_result(err); } } } int ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(VkPresentModeKHR present_mode) { if (present_mode == VK_PRESENT_MODE_MAILBOX_KHR) return 3; if (present_mode == VK_PRESENT_MODE_FIFO_KHR || present_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) return 2; if (present_mode == VK_PRESENT_MODE_IMMEDIATE_KHR) return 1; IM_ASSERT(0); return 1; } // Also destroy old swap chain and in-flight frames data, if any. void ImGui_ImplVulkanH_CreateWindowSwapchain(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count) { VkResult err; VkSwapchainKHR old_swapchain = wd->Swapchain; err = vkDeviceWaitIdle(device); check_vk_result(err); // We don't use ImGui_ImplVulkanH_DestroyWindow() because we want to preserve the old swapchain to create the new one. // Destroy old Framebuffer for (uint32_t i = 0; i < wd->ImageCount; i++) { ImGui_ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator); ImGui_ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator); } IM_FREE(wd->Frames); IM_FREE(wd->FrameSemaphores); wd->Frames = NULL; wd->FrameSemaphores = NULL; wd->ImageCount = 0; if (wd->RenderPass) vkDestroyRenderPass(device, wd->RenderPass, allocator); // If min image count was not specified, request different count of images dependent on selected present mode if (min_image_count == 0) min_image_count = ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(wd->PresentMode); // Create Swapchain { VkSwapchainCreateInfoKHR info = {}; info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; info.surface = wd->Surface; info.minImageCount = min_image_count; info.imageFormat = wd->SurfaceFormat.format; info.imageColorSpace = wd->SurfaceFormat.colorSpace; info.imageArrayLayers = 1; info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; // Assume that graphics family == present family info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; info.presentMode = wd->PresentMode; info.clipped = VK_TRUE; info.oldSwapchain = old_swapchain; VkSurfaceCapabilitiesKHR cap; err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, wd->Surface, &cap); check_vk_result(err); if (info.minImageCount < cap.minImageCount) info.minImageCount = cap.minImageCount; else if (cap.maxImageCount != 0 && info.minImageCount > cap.maxImageCount) info.minImageCount = cap.maxImageCount; if (cap.currentExtent.width == 0xffffffff) { info.imageExtent.width = wd->Width = w; info.imageExtent.height = wd->Height = h; } else { info.imageExtent.width = wd->Width = cap.currentExtent.width; info.imageExtent.height = wd->Height = cap.currentExtent.height; } err = vkCreateSwapchainKHR(device, &info, allocator, &wd->Swapchain); check_vk_result(err); err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, NULL); check_vk_result(err); VkImage backbuffers[16] = {}; IM_ASSERT(wd->ImageCount >= min_image_count); IM_ASSERT(wd->ImageCount < IM_ARRAYSIZE(backbuffers)); err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, backbuffers); check_vk_result(err); IM_ASSERT(wd->Frames == NULL); wd->Frames = (ImGui_ImplVulkanH_Frame*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_Frame) * wd->ImageCount); wd->FrameSemaphores = (ImGui_ImplVulkanH_FrameSemaphores*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameSemaphores) * wd->ImageCount); memset(wd->Frames, 0, sizeof(wd->Frames[0]) * wd->ImageCount); memset(wd->FrameSemaphores, 0, sizeof(wd->FrameSemaphores[0]) * wd->ImageCount); for (uint32_t i = 0; i < wd->ImageCount; i++) wd->Frames[i].Backbuffer = backbuffers[i]; } if (old_swapchain) vkDestroySwapchainKHR(device, old_swapchain, allocator); // Create the Render Pass { VkAttachmentDescription attachment = {}; attachment.format = wd->SurfaceFormat.format; attachment.samples = VK_SAMPLE_COUNT_1_BIT; attachment.loadOp = wd->ClearEnable ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD;// VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference color_attachment = {}; color_attachment.attachment = 0; color_attachment.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &color_attachment; VkSubpassDependency dependency = {}; dependency.srcSubpass = VK_SUBPASS_EXTERNAL; dependency.dstSubpass = 0; dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.srcAccessMask = 0; dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; VkRenderPassCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; info.attachmentCount = 1; info.pAttachments = &attachment; info.subpassCount = 1; info.pSubpasses = &subpass; info.dependencyCount = 1; info.pDependencies = &dependency; err = vkCreateRenderPass(device, &info, allocator, &wd->RenderPass); check_vk_result(err); } // Create The Image Views { VkImageViewCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; info.viewType = VK_IMAGE_VIEW_TYPE_2D; info.format = wd->SurfaceFormat.format; info.components.r = VK_COMPONENT_SWIZZLE_R; info.components.g = VK_COMPONENT_SWIZZLE_G; info.components.b = VK_COMPONENT_SWIZZLE_B; info.components.a = VK_COMPONENT_SWIZZLE_A; VkImageSubresourceRange image_range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; info.subresourceRange = image_range; for (uint32_t i = 0; i < wd->ImageCount; i++) { ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; info.image = fd->Backbuffer; err = vkCreateImageView(device, &info, allocator, &fd->BackbufferView); check_vk_result(err); } } // Create Framebuffer { VkImageView attachment[1]; VkFramebufferCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; info.renderPass = wd->RenderPass; info.attachmentCount = 1; info.pAttachments = attachment; info.width = wd->Width; info.height = wd->Height; info.layers = 1; for (uint32_t i = 0; i < wd->ImageCount; i++) { ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; attachment[0] = fd->BackbufferView; err = vkCreateFramebuffer(device, &info, allocator, &fd->Framebuffer); check_vk_result(err); } } } void ImGui_ImplVulkanH_CreateWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int width, int height, uint32_t min_image_count) { (void)instance; ImGui_ImplVulkanH_CreateWindowSwapchain(physical_device, device, wd, allocator, width, height, min_image_count); ImGui_ImplVulkanH_CreateWindowCommandBuffers(physical_device, device, wd, queue_family, allocator); } void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator) { vkDeviceWaitIdle(device); // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals) //vkQueueWaitIdle(g_Queue); for (uint32_t i = 0; i < wd->ImageCount; i++) { ImGui_ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator); ImGui_ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator); } IM_FREE(wd->Frames); IM_FREE(wd->FrameSemaphores); wd->Frames = NULL; wd->FrameSemaphores = NULL; vkDestroyRenderPass(device, wd->RenderPass, allocator); vkDestroySwapchainKHR(device, wd->Swapchain, allocator); vkDestroySurfaceKHR(instance, wd->Surface, allocator); *wd = ImGui_ImplVulkanH_Window(); } void ImGui_ImplVulkanH_DestroyFrame(VkDevice device, ImGui_ImplVulkanH_Frame* fd, const VkAllocationCallbacks* allocator) { vkDestroyFence(device, fd->Fence, allocator); vkFreeCommandBuffers(device, fd->CommandPool, 1, &fd->CommandBuffer); vkDestroyCommandPool(device, fd->CommandPool, allocator); fd->Fence = VK_NULL_HANDLE; fd->CommandBuffer = VK_NULL_HANDLE; fd->CommandPool = VK_NULL_HANDLE; vkDestroyImageView(device, fd->BackbufferView, allocator); vkDestroyFramebuffer(device, fd->Framebuffer, allocator); } void ImGui_ImplVulkanH_DestroyFrameSemaphores(VkDevice device, ImGui_ImplVulkanH_FrameSemaphores* fsd, const VkAllocationCallbacks* allocator) { vkDestroySemaphore(device, fsd->ImageAcquiredSemaphore, allocator); vkDestroySemaphore(device, fsd->RenderCompleteSemaphore, allocator); fsd->ImageAcquiredSemaphore = fsd->RenderCompleteSemaphore = VK_NULL_HANDLE; } void ImGui_ImplVulkanH_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkanH_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator) { if (buffers->VertexBuffer) { vkDestroyBuffer(device, buffers->VertexBuffer, allocator); buffers->VertexBuffer = VK_NULL_HANDLE; } if (buffers->VertexBufferMemory) { vkFreeMemory(device, buffers->VertexBufferMemory, allocator); buffers->VertexBufferMemory = VK_NULL_HANDLE; } if (buffers->IndexBuffer) { vkDestroyBuffer(device, buffers->IndexBuffer, allocator); buffers->IndexBuffer = VK_NULL_HANDLE; } if (buffers->IndexBufferMemory) { vkFreeMemory(device, buffers->IndexBufferMemory, allocator); buffers->IndexBufferMemory = VK_NULL_HANDLE; } buffers->VertexBufferSize = 0; buffers->IndexBufferSize = 0; } void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkanH_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator) { for (uint32_t n = 0; n < buffers->Count; n++) ImGui_ImplVulkanH_DestroyFrameRenderBuffers(device, &buffers->FrameRenderBuffers[n], allocator); IM_FREE(buffers->FrameRenderBuffers); buffers->FrameRenderBuffers = NULL; buffers->Index = 0; buffers->Count = 0; } ImTextureID ImGui_ImplVulkan_GenerateTexture(VkCommandBuffer commandBuffer, const std::vector& data, const Vector2i& size) { try { auto* texture = new ImGuiTexture(); VkResult err; // Create the Image View: { VkImageCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; info.imageType = VK_IMAGE_TYPE_2D; info.format = VK_FORMAT_R8G8B8A8_UNORM; info.extent.width = size.x; info.extent.height = size.y; info.extent.depth = 1; info.mipLevels = 1; info.arrayLayers = 1; info.samples = VK_SAMPLE_COUNT_1_BIT; info.tiling = VK_IMAGE_TILING_OPTIMAL; info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; err = vkCreateImage(g_VulkanInitInfo.Device, &info, g_VulkanInitInfo.Allocator, &texture->image); check_vk_result(err); VkMemoryRequirements req; vkGetImageMemoryRequirements(g_VulkanInitInfo.Device, texture->image, &req); VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.allocationSize = req.size; alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits); err = vkAllocateMemory(g_VulkanInitInfo.Device, &alloc_info, g_VulkanInitInfo.Allocator, &texture->memory); check_vk_result(err); err = vkBindImageMemory(g_VulkanInitInfo.Device, texture->image, texture->memory, 0); check_vk_result(err); } // Create the Image View: { VkImageViewCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; info.image = texture->image; info.viewType = VK_IMAGE_VIEW_TYPE_2D; info.format = VK_FORMAT_R8G8B8A8_UNORM; info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; info.subresourceRange.levelCount = 1; info.subresourceRange.layerCount = 1; err = vkCreateImageView(g_VulkanInitInfo.Device, &info, g_VulkanInitInfo.Allocator, &texture->view); check_vk_result(err); } // create sampler { VkSamplerCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; info.magFilter = VK_FILTER_LINEAR; info.minFilter = VK_FILTER_LINEAR; info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; info.minLod = -1000; info.maxLod = 1000; info.maxAnisotropy = 1.0f; err = vkCreateSampler(g_VulkanInitInfo.Device, &info, g_VulkanInitInfo.Allocator, &texture->sampler); check_vk_result(err); } texture->descriptor_set = (VkDescriptorSet)ImGui_ImplVulkan_AddTexture(texture->sampler, texture->view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); // Create the Upload Buffer: { VkBufferCreateInfo buffer_info = {}; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; buffer_info.size = data.size(); buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; err = vkCreateBuffer(g_VulkanInitInfo.Device, &buffer_info, g_VulkanInitInfo.Allocator, &texture->uploadBuffer); check_vk_result(err); VkMemoryRequirements req; vkGetBufferMemoryRequirements(g_VulkanInitInfo.Device, texture->uploadBuffer, &req); VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.allocationSize = req.size; alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); err = vkAllocateMemory(g_VulkanInitInfo.Device, &alloc_info, g_VulkanInitInfo.Allocator, &texture->uploadBufferMemory); check_vk_result(err); err = vkBindBufferMemory(g_VulkanInitInfo.Device, texture->uploadBuffer, texture->uploadBufferMemory, 0); check_vk_result(err); } // Upload to Buffer: { cemu_assert_debug(data.size() != 0); char* map = NULL; err = vkMapMemory(g_VulkanInitInfo.Device, texture->uploadBufferMemory, 0, data.size(), 0, (void**)(&map)); check_vk_result(err); memcpy(map, data.data(), data.size()); VkMappedMemoryRange range[1] = {}; range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; range[0].memory = texture->uploadBufferMemory; range[0].size = data.size(); err = vkFlushMappedMemoryRanges(g_VulkanInitInfo.Device, 1, range); check_vk_result(err); vkUnmapMemory(g_VulkanInitInfo.Device, texture->uploadBufferMemory); } // Copy to Image: { VkImageMemoryBarrier copy_barrier[1] = {}; copy_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; copy_barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; copy_barrier[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; copy_barrier[0].image = texture->image; copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; copy_barrier[0].subresourceRange.levelCount = 1; copy_barrier[0].subresourceRange.layerCount = 1; vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, copy_barrier); VkBufferImageCopy region = {}; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.layerCount = 1; region.imageExtent.width = size.x; region.imageExtent.height = size.y; region.imageExtent.depth = 1; vkCmdCopyBufferToImage(commandBuffer, texture->uploadBuffer, texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); VkImageMemoryBarrier use_barrier[1] = {}; use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; use_barrier[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; use_barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; use_barrier[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; use_barrier[0].image = texture->image; use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; use_barrier[0].subresourceRange.levelCount = 1; use_barrier[0].subresourceRange.layerCount = 1; vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, use_barrier); } return (ImTextureID)texture; } catch (const std::exception & ex) { cemuLog_log(LogType::Force, "can't generate imgui texture: {}", ex.what()); return nullptr; } } void ImGui_ImplVulkan_DeleteTexture(ImTextureID id) { auto textureInfo = (ImGuiTexture*)id; vkFreeDescriptorSets(g_VulkanInitInfo.Device, g_VulkanInitInfo.DescriptorPool, 1, &textureInfo->descriptor_set); vkDestroySampler(g_VulkanInitInfo.Device, textureInfo->sampler, nullptr); vkDestroyImageView(g_VulkanInitInfo.Device, textureInfo->view, nullptr); vkDestroyImage(g_VulkanInitInfo.Device, textureInfo->image, nullptr); vkDestroyBuffer(g_VulkanInitInfo.Device, textureInfo->uploadBuffer, nullptr); vkFreeMemory(g_VulkanInitInfo.Device, textureInfo->memory, nullptr); vkFreeMemory(g_VulkanInitInfo.Device, textureInfo->uploadBufferMemory, nullptr); *textureInfo = {}; } ================================================ FILE: src/imgui/imgui_impl_vulkan.h ================================================ // dear imgui: Renderer for Vulkan // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) // Implemented features: // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. // Missing features: // [ ] Renderer: User texture binding. Changes of ImTextureID aren't supported by this binding! See https://github.com/ocornut/imgui/pull/914 // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui // The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ // Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app. // - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h. // You will use those if you want to use this rendering back-end in your engine/app. // - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by // the back-end itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code. // Read comments in imgui_impl_vulkan.h. #pragma once #include #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "util/math/vector2.h" // Initialization data, for ImGui_ImplVulkan_Init() // [Please zero-clear before use!] struct ImGui_ImplVulkan_InitInfo { VkInstance Instance; VkPhysicalDevice PhysicalDevice; VkDevice Device; uint32_t QueueFamily; VkQueue Queue; VkPipelineCache PipelineCache; VkDescriptorPool DescriptorPool; uint32_t MinImageCount; // >= 2 uint32_t ImageCount; // >= MinImageCount const VkAllocationCallbacks* Allocator; void (*CheckVkResultFn)(VkResult err); }; // Called by user code IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass); IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown(); IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame(VkCommandBuffer command_buffer, VkFramebuffer framebuffer, VkExtent2D extent); IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer); IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer); IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontsTexture(); IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontUploadObjects(); IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated) // //IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass); //IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown(); //IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame(); //IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer); //IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer); //IMGUI_IMPL_API void ImGui_ImplVulkan_InvalidateFontUploadObjects(); IMGUI_IMPL_API ImTextureID ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout); IMGUI_IMPL_API ImTextureID ImGui_ImplVulkan_GenerateTexture(VkCommandBuffer commandBuffer, const std::vector& data, const Vector2i& size); IMGUI_IMPL_API void ImGui_ImplVulkan_DeleteTexture(ImTextureID id); //------------------------------------------------------------------------- // Internal / Miscellaneous Vulkan Helpers // (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own engine/app.) //------------------------------------------------------------------------- // You probably do NOT need to use or care about those functions. // Those functions only exist because: // 1) they facilitate the readability and maintenance of the multiple main.cpp examples files. // 2) the upcoming multi-viewport feature will need them internally. // Generally we avoid exposing any kind of superfluous high-level helpers in the bindings, // but it is too much code to duplicate everywhere so we exceptionally expose them. // // Your engine/app will likely _already_ have code to setup all that stuff (swap chain, render pass, frame buffers, etc.). // You may read this code to learn about Vulkan, but it is recommended you use you own custom tailored code to do equivalent work. // (The ImGui_ImplVulkanH_XXX functions do not interact with any of the state used by the regular ImGui_ImplVulkan_XXX functions) //------------------------------------------------------------------------- struct ImGui_ImplVulkanH_Frame; struct ImGui_ImplVulkanH_Window; // Helpers IMGUI_IMPL_API void ImGui_ImplVulkanH_CreateWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wnd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count); IMGUI_IMPL_API void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wnd, const VkAllocationCallbacks* allocator); IMGUI_IMPL_API VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkFormat* request_formats, int request_formats_count, VkColorSpaceKHR request_color_space); IMGUI_IMPL_API VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkPresentModeKHR* request_modes, int request_modes_count); IMGUI_IMPL_API int ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(VkPresentModeKHR present_mode); // Helper structure to hold the data needed by one rendering frame // (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.) // [Please zero-clear before use!] struct ImGui_ImplVulkanH_Frame { VkCommandPool CommandPool; VkCommandBuffer CommandBuffer; VkFence Fence; VkImage Backbuffer; VkImageView BackbufferView; VkFramebuffer Framebuffer; }; struct ImGui_ImplVulkanH_FrameSemaphores { VkSemaphore ImageAcquiredSemaphore; VkSemaphore RenderCompleteSemaphore; }; // Helper structure to hold the data needed by one rendering context into one OS window // (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.) struct ImGui_ImplVulkanH_Window { int Width; int Height; VkSwapchainKHR Swapchain; VkSurfaceKHR Surface; VkSurfaceFormatKHR SurfaceFormat; VkPresentModeKHR PresentMode; VkRenderPass RenderPass; bool ClearEnable; VkClearValue ClearValue; uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount) uint32_t ImageCount; // Number of simultaneous in-flight frames (returned by vkGetSwapchainImagesKHR, usually derived from min_image_count) uint32_t SemaphoreIndex; // Current set of swapchain wait semaphores we're using (needs to be distinct from per frame data) ImGui_ImplVulkanH_Frame* Frames; ImGui_ImplVulkanH_FrameSemaphores* FrameSemaphores; ImGui_ImplVulkanH_Window() { memset(this, 0, sizeof(*this)); PresentMode = VK_PRESENT_MODE_MAX_ENUM_KHR; ClearEnable = true; } }; ================================================ FILE: src/input/CMakeLists.txt ================================================ add_library(CemuInput InputManager.cpp InputManager.h ControllerFactory.cpp ControllerFactory.h api/ControllerState.h api/Controller.cpp api/Controller.h api/ControllerState.cpp api/InputAPI.h api/ControllerProvider.h api/DSU/DSUController.h api/DSU/DSUControllerProvider.cpp api/DSU/DSUController.cpp api/DSU/DSUControllerProvider.h api/DSU/DSUMessages.h api/DSU/DSUMessages.cpp api/SDL/SDLController.cpp api/SDL/SDLControllerProvider.cpp api/SDL/SDLController.h api/SDL/SDLControllerProvider.h api/Keyboard/KeyboardControllerProvider.h api/Keyboard/KeyboardControllerProvider.cpp api/Keyboard/KeyboardController.cpp api/Keyboard/KeyboardController.h api/GameCube/GameCubeController.cpp api/GameCube/GameCubeControllerProvider.h api/GameCube/GameCubeControllerProvider.cpp api/GameCube/GameCubeController.h emulated/ProController.cpp emulated/EmulatedController.h emulated/EmulatedController.cpp emulated/ProController.h emulated/WPADController.cpp emulated/WPADController.h emulated/WiimoteController.h emulated/VPADController.cpp emulated/WiimoteController.cpp emulated/VPADController.h emulated/ClassicController.cpp emulated/ClassicController.h ) set_property(TARGET CemuInput PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") if(WIN32) # XInput target_sources(CemuInput PRIVATE api/XInput/XInputControllerProvider.cpp api/XInput/XInputControllerProvider.h api/XInput/XInputController.cpp api/XInput/XInputController.h ) # DirectInput target_sources(CemuInput PRIVATE api/DirectInput/DirectInputControllerProvider.cpp api/DirectInput/DirectInputController.h api/DirectInput/DirectInputControllerProvider.h api/DirectInput/DirectInputController.cpp ) endif() if (SUPPORTS_WIIMOTE) target_compile_definitions(CemuInput PUBLIC SUPPORTS_WIIMOTE) target_sources(CemuInput PRIVATE api/Wiimote/WiimoteControllerProvider.h api/Wiimote/WiimoteControllerProvider.cpp api/Wiimote/WiimoteMessages.h api/Wiimote/NativeWiimoteController.h api/Wiimote/NativeWiimoteController.cpp api/Wiimote/WiimoteDevice.h ) if (ENABLE_HIDAPI) target_sources(CemuInput PRIVATE api/Wiimote/hidapi/HidapiWiimote.cpp api/Wiimote/hidapi/HidapiWiimote.h) endif () if (ENABLE_BLUEZ) target_sources(CemuInput PRIVATE api/Wiimote/l2cap/L2CapWiimote.cpp api/Wiimote/l2cap/L2CapWiimote.h) endif() endif () target_include_directories(CemuInput PUBLIC "../") target_link_libraries(CemuInput PRIVATE CemuCommon CemuGui ) if (ENABLE_HIDAPI) target_link_libraries(CemuInput PRIVATE hidapi::hidapi) endif() if (ENABLE_BLUEZ) target_link_libraries(CemuInput PRIVATE bluez::bluez) endif () ================================================ FILE: src/input/ControllerFactory.cpp ================================================ #include "input/ControllerFactory.h" #include "input/emulated/VPADController.h" #include "input/emulated/ProController.h" #include "input/emulated/ClassicController.h" #include "input/emulated/WiimoteController.h" #include "input/api/SDL/SDLController.h" #include "input/api/Keyboard/KeyboardController.h" #include "input/api/DSU/DSUController.h" #include "input/api/GameCube/GameCubeController.h" #if BOOST_OS_WINDOWS #include "input/api/XInput/XInputController.h" #include "input/api/DirectInput/DirectInputController.h" #endif #if HAS_WIIMOTE #include "input/api/Wiimote/NativeWiimoteController.h" #endif ControllerPtr ControllerFactory::CreateController(InputAPI::Type api, std::string_view uuid, std::string_view display_name) { switch (api) { #if HAS_KEYBOARD case InputAPI::Keyboard: return std::make_shared(); #endif #if HAS_DIRECTINPUT case InputAPI::DirectInput: { GUID guid; // Workaround for mouse2joystick users, which has 0 as it's uuid in it's profile and counts on Cemu applying it to the first directinput controller. GUIDFromString also doesn't allow for invalid uuids either. if (uuid == "0") { const auto provider = InputManager::instance().get_api_provider(InputAPI::DirectInput); const auto controllers = provider->get_controllers(); if (controllers.empty()) throw std::invalid_argument(fmt::format( "can't apply non-uuid-specific directinput profile when no controllers are available")); if (!GUIDFromString(controllers.front()->uuid().c_str(), guid)) throw std::invalid_argument(fmt::format("invalid guid format: {}", uuid)); } else { if (!GUIDFromString(uuid.data(), guid)) throw std::invalid_argument(fmt::format("invalid guid format: {}", uuid)); } return std::make_shared(guid); } #endif #if HAS_XINPUT case InputAPI::XInput: { const auto index = ConvertString(uuid); return std::make_shared(index); } #endif #if HAS_SDL case InputAPI::SDLController: { // diid_guid const auto index = uuid.find_first_of('_'); if (index == std::string_view::npos) throw std::invalid_argument(fmt::format("invalid sdl uuid format: {}", uuid)); const auto guid_index = ConvertString(uuid.substr(0, index)); const auto guid = SDL_JoystickGetGUIDFromString(std::string{uuid.substr(index + 1)}.c_str()); if (display_name.empty()) return std::make_shared(guid, guid_index); else return std::make_shared(guid, guid_index, display_name); } #endif #if HAS_DSU case InputAPI::DSUClient: { const auto index = ConvertString(uuid); return std::make_shared(index); } #endif #if HAS_GAMECUBE case InputAPI::GameCube: { const auto index = uuid.find_first_of('_'); if (index == std::string_view::npos) throw std::invalid_argument(fmt::format("invalid gamecube uuid format: {}", uuid)); const auto adapter = ConvertString(uuid.substr(0, index)); const auto controller_index = ConvertString(uuid.substr(index + 1)); return std::make_shared(adapter, controller_index); } #endif #if HAS_WIIMOTE case InputAPI::Wiimote: { const auto index = ConvertString(uuid); return std::make_shared(index); } #endif default: throw std::invalid_argument(fmt::format("unhandled controller api: {}", api)); } /* case InputAPI::WGIGamepad: break; case InputAPI::WGIRawController: break; */ } EmulatedControllerPtr ControllerFactory::CreateEmulatedController(size_t player_index, EmulatedController::Type type) { switch (type) { case EmulatedController::Type::VPAD: return std::make_shared(player_index); case EmulatedController::Type::Pro: return std::make_shared(player_index); case EmulatedController::Type::Classic: return std::make_shared(player_index); case EmulatedController::Type::Wiimote: return std::make_shared(player_index); default: throw std::runtime_error(fmt::format("unknown emulated controller type: {}", type)); } } ControllerProviderPtr ControllerFactory::CreateControllerProvider(InputAPI::Type api, const ControllerProviderSettings& settings) { switch (api) { #if HAS_KEYBOARD case InputAPI::Keyboard: return std::make_shared(); #endif #if HAS_SDL case InputAPI::SDLController: return std::make_shared(); #endif #if HAS_XINPUT case InputAPI::XInput: return std::make_shared(); #endif #if HAS_DIRECTINPUT case InputAPI::DirectInput: return std::make_shared(); #endif #if HAS_DSU case InputAPI::DSUClient: { try { const auto& dsu_settings = dynamic_cast(settings); return std::make_shared(dsu_settings); } catch (const std::bad_cast&) { cemuLog_log(LogType::Force, "failing to cast ControllerProviderSettings class to DSUControllerProvider"); return std::make_shared(); } } #endif #if HAS_GAMECUBE case InputAPI::GameCube: return std::make_shared(); #endif #if HAS_WIIMOTE case InputAPI::Wiimote: return std::make_shared(); #endif default: cemu_assert_debug(false); return {}; } } ================================================ FILE: src/input/ControllerFactory.h ================================================ #pragma once #include "input/api/InputAPI.h" #include "input/api/Controller.h" #include "input/emulated/EmulatedController.h" class ControllerFactory { public: static ControllerPtr CreateController(InputAPI::Type api, std::string_view uuid, std::string_view display_name); static EmulatedControllerPtr CreateEmulatedController(size_t player_index, EmulatedController::Type type); static ControllerProviderPtr CreateControllerProvider(InputAPI::Type api, const ControllerProviderSettings& settings); }; ================================================ FILE: src/input/InputManager.cpp ================================================ #include "input/InputManager.h" #include "config/ActiveSettings.h" #include "input/ControllerFactory.h" #include #include #include "Cafe/GameProfile/GameProfile.h" #include "util/EventService.h" InputManager::InputManager() { /* auto create_provider = [] template () { static_assert(std::is_base_of_v); try { auto controller = std::make_shared(); m_api_available[controller->api()] = controller; } catch (const std::exception& ex) { cemuLog_log(LogType::Force, ex.what()); } } */ #if HAS_KEYBOARD create_provider(); #endif #if HAS_SDL create_provider(); #endif #if HAS_XINPUT create_provider(); #endif #if HAS_DIRECTINPUT create_provider(); #endif #if HAS_DSU create_provider(); #endif #if HAS_GAMECUBE create_provider(); #endif #if HAS_WIIMOTE create_provider(); #endif m_update_thread_shutdown.store(false); m_update_thread = std::thread(&InputManager::update_thread, this); } InputManager::~InputManager() { m_update_thread_shutdown.store(true); m_update_thread.join(); } void InputManager::load() noexcept { for (size_t i = 0; i < kMaxController; ++i) { try { load(i); } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't load controller profile: {}", ex.what()); } } } bool InputManager::load(size_t player_index, std::string_view filename) { fs::path file_path; if (filename.empty()) file_path = ActiveSettings::GetConfigPath("controllerProfiles/controller{}", player_index); else file_path = ActiveSettings::GetConfigPath("controllerProfiles/{}", filename); auto old_file = file_path; old_file.replace_extension(".txt"); // test .txt extension file_path.replace_extension(".xml"); // force .xml extension if (fs::exists(old_file) && !fs::exists(file_path)) migrate_config(old_file); if (!fs::exists(file_path)) return false; try { auto xmlData = FileStream::LoadIntoMemory(file_path); if (!xmlData || xmlData->empty()) return false; pugi::xml_document doc; if (!doc.load_buffer(xmlData->data(), xmlData->size())) return false; const pugi::xml_node root = doc.document_element(); const auto type_node = root.child("type"); if (!type_node) return false; const auto emulate = EmulatedController::type_from_string(type_node.child_value()); auto emulated_controller = ControllerFactory::CreateEmulatedController(player_index, emulate); if (const auto profile_name_node = root.child("profile")) emulated_controller->m_profile_name = profile_name_node.child_value(); // custom settings emulated_controller->load(root); for (const auto controller_node : root.select_nodes("controller")) { const auto cnode = controller_node.node(); const auto api_node = cnode.child("api"); if (!api_node) continue; const auto uuid_node = cnode.child("uuid"); if (!uuid_node) continue; const auto* display_name = cnode.child_value("display_name"); try { const auto api = InputAPI::from_string(api_node.child_value()); auto controller = ControllerFactory::CreateController(api, uuid_node.child_value(), display_name); emulated_controller->add_controller(controller); // load optional settings auto settings = controller->get_settings(); if (const auto axis_node = cnode.child("axis")) { if (const auto value = axis_node.child("deadzone")) settings.axis.deadzone = ConvertString(value.child_value()); if (const auto value = axis_node.child("range")) settings.axis.range = ConvertString(value.child_value()); } if (const auto rotation_node = cnode.child("rotation")) { if (const auto value = rotation_node.child("deadzone")) settings.rotation.deadzone = ConvertString(value.child_value()); if (const auto value = rotation_node.child("range")) settings.rotation.range = ConvertString(value.child_value()); } if (const auto trigger_node = cnode.child("trigger")) { if (const auto value = trigger_node.child("deadzone")) settings.trigger.deadzone = ConvertString(value.child_value()); if (const auto value = trigger_node.child("range")) settings.trigger.range = ConvertString(value.child_value()); } if (const auto value = cnode.child("rumble")) settings.rumble = ConvertString(value.child_value()); if (const auto value = cnode.child("motion")) settings.motion = ConvertString(value.child_value()); controller->set_settings(settings); // custom settings controller->load(cnode); // mappings if (const auto mappings_node = cnode.child("mappings")) { for (const auto& entry : mappings_node.select_nodes("entry")) { const auto enode = entry.node(); const auto mapping_node = enode.child("mapping"); if (!mapping_node) continue; const auto button_node = enode.child("button"); if (!button_node) continue; const auto mapping = ConvertString(mapping_node.child_value()); const auto button = ConvertString(button_node.child_value()); emulated_controller->set_mapping(mapping, controller, button); } } } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't load controller: {}", ex.what()); } } set_controller(emulated_controller); return true; } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't load config file: {}", ex.what()); return false; } } bool InputManager::migrate_config(const fs::path& file_path) { try { auto xmlData = FileStream::LoadIntoMemory(file_path); if (!xmlData || xmlData->empty()) return false; std::string iniDataStr((const char*)xmlData->data(), xmlData->size()); std::stringstream iniData(iniDataStr); boost::property_tree::ptree m_data; read_ini(iniData, m_data); const auto emulate_string = m_data.get("General.emulate"); const auto api_string = m_data.get("General.api"); auto uuid_opt = m_data.get_optional("General.controller"); const auto display_name = m_data.get_optional("General.display"); std::string uuid; if (api_string == to_string(InputAPI::Keyboard)) uuid = to_string(InputAPI::Keyboard); else { if (!uuid_opt) return false; uuid = uuid_opt.value(); if (api_string == to_string(InputAPI::SDLController)) { uuid += "_0"; } } fs::path out_file = file_path; out_file.replace_extension(".xml"); pugi::xml_document doc; auto declaration_node = doc.append_child(pugi::node_declaration); declaration_node.append_attribute("version") = "1.0"; declaration_node.append_attribute("encoding") = "UTF-8"; auto emulated_controller = doc.append_child("emulated_controller"); emulated_controller.append_child("type").append_child(pugi::node_pcdata).set_value(emulate_string.c_str()); bool has_keyboard = api_string == to_string(InputAPI::Keyboard); if (!has_keyboard) // test if only keyboard configured { auto controller = emulated_controller.append_child("controller"); controller.append_child("api").append_child(pugi::node_pcdata).set_value(api_string.c_str()); controller.append_child("uuid").append_child(pugi::node_pcdata).set_value(uuid.c_str()); if (display_name.has_value() && !display_name->empty()) controller.append_child("display_name").append_child(pugi::node_pcdata).set_value( display_name.value().c_str()); controller.append_child("rumble").append_child(pugi::node_pcdata).set_value( m_data.get("Controller.rumble").c_str()); auto axis_node = controller.append_child("axis"); axis_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value( m_data.get("Controller.leftDeadzone").c_str()); axis_node.append_child("range").append_child(pugi::node_pcdata).set_value( m_data.get("Controller.leftRange").c_str()); auto rotation_node = controller.append_child("rotation"); rotation_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value( m_data.get("Controller.rightDeadzone").c_str()); rotation_node.append_child("range").append_child(pugi::node_pcdata).set_value( m_data.get("Controller.rightRange").c_str()); auto mappings_node = controller.append_child("mappings"); for (int i = 1; i < 28; ++i) // test all possible mappings (max is 27 for vpad controller) { auto mapping = m_data.get_optional(fmt::format("Controller.{}", i)); if (!mapping || mapping->empty()) continue; if (!boost::starts_with(mapping.value(), "button_")) { if (boost::starts_with(mapping.value(), "key_")) has_keyboard = true; continue; } const auto button = ConvertString(mapping.value().substr(7), 16); uint64 flag_bit = 0; for (auto b = 0; b < 64; ++b) { if (HAS_BIT(button, b)) { flag_bit = b; break; } } // fix old flag layout to new one for all kind of axis stuff if (flag_bit >= 24 && flag_bit <= 31) flag_bit += 8; else if (flag_bit == 32) flag_bit = kTriggerXP; else if (flag_bit == 33) flag_bit = kRotationXP; else if (flag_bit == 34) flag_bit = kRotationYP; else if (flag_bit == 35) flag_bit = kTriggerYP; else if (flag_bit == 36) flag_bit = kAxisXN; else if (flag_bit == 37) flag_bit = kAxisYN; else if (flag_bit == 38) flag_bit = kTriggerXN; else if (flag_bit == 39) flag_bit = kRotationXN; else if (flag_bit == 40) flag_bit = kRotationYN; else if (flag_bit == 41) flag_bit = kTriggerYN; // fix old api mappings if (api_string == to_string(InputAPI::XInput)) { const std::unordered_map xinput = { {kButton0, 12}, // XINPUT_GAMEPAD_A {kButton1, 13}, // XINPUT_GAMEPAD_B {kButton2, 14}, // XINPUT_GAMEPAD_X {kButton3, 15}, // XINPUT_GAMEPAD_Y {kButton4, 8}, // XINPUT_GAMEPAD_LEFT_SHOULDER {kButton5, 9}, // XINPUT_GAMEPAD_LEFT_SHOULDER {kButton6, 4}, // XINPUT_GAMEPAD_START {kButton7, 5}, // XINPUT_GAMEPAD_BACK {kButton8, 6}, // XINPUT_GAMEPAD_LEFT_THUMB {kButton9, 7}, // XINPUT_GAMEPAD_RIGHT_THUMB {kButton10, 0}, // XINPUT_GAMEPAD_DPAD_UP {kButton11, 1}, // XINPUT_GAMEPAD_DPAD_DOWN {kButton12, 2}, // XINPUT_GAMEPAD_DPAD_LEFT {kButton13, 3}, // XINPUT_GAMEPAD_DPAD_RIGHT }; const auto it = xinput.find(flag_bit); if (it != xinput.cend()) flag_bit = it->second; } else if (api_string == "DSU") { const std::unordered_map dsu = { {7, kButton0}, // ButtonSelect {8, kButton1}, // ButtonLStick {9, kButton2}, // ButtonRStick {6, kButton3}, // ButtonStart {4, kButton10}, // ButtonL {5, kButton11}, // ButtonR {0, kButton14}, // ButtonA {1, kButton13}, // ButtonB {2, kButton15}, // ButtonX {3, kButton12}, // ButtonY }; const auto it = dsu.find(flag_bit); if (it != dsu.cend()) flag_bit = it->second; } auto entry_node = mappings_node.append_child("entry"); entry_node.append_child("mapping").append_child(pugi::node_pcdata).set_value( fmt::format("{}", i).c_str()); entry_node.append_child("button").append_child(pugi::node_pcdata).set_value( fmt::format("{}", flag_bit).c_str()); } } if (has_keyboard) { auto controller = emulated_controller.append_child("controller"); controller.append_child("api").append_child(pugi::node_pcdata).set_value("Keyboard"); controller.append_child("uuid").append_child(pugi::node_pcdata).set_value("Keyboard"); auto mappings_node = controller.append_child("mappings"); for (int i = 1; i < 28; ++i) // test all possible mappings (max is 27 for vpad controller) { auto mapping = m_data.get_optional(fmt::format("Controller.{}", i)); if (!mapping || mapping->empty()) continue; if (!boost::starts_with(mapping.value(), "key_")) continue; const auto button = ConvertString(mapping.value().substr(4)); auto entry_node = mappings_node.append_child("entry"); entry_node.append_child("mapping").append_child(pugi::node_pcdata).set_value( fmt::format("{}", i).c_str()); entry_node.append_child("button").append_child(pugi::node_pcdata).set_value( fmt::format("{}", button).c_str()); } } std::ofstream write_file(out_file, std::ios::out | std::ios::trunc); if (write_file.is_open()) { doc.save(write_file); return true; } } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't migrate config file {}: {}", file_path.string(), ex.what()); } return false; } void InputManager::save() noexcept { for (size_t i = 0; i < kMaxController; ++i) { try { save(i); } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "can't save controller profile: {}", ex.what()); } } } bool InputManager::save(size_t player_index, std::string_view filename) { // dont overwrite files if set by gameprofile if (m_is_gameprofile_set[player_index]) return true; auto emulated_controller = get_controller(player_index); if (!emulated_controller) return false; fs::path file_path = ActiveSettings::GetConfigPath("controllerProfiles"); fs::create_directories(file_path); const auto is_default_file = filename.empty(); if (is_default_file) file_path /= fmt::format("controller{}", player_index); else file_path /= _utf8ToPath(filename); file_path.replace_extension(".xml"); // force .xml extension pugi::xml_document doc; auto declaration_node = doc.append_child(pugi::node_declaration); declaration_node.append_attribute("version") = "1.0"; declaration_node.append_attribute("encoding") = "UTF-8"; auto emulated_controller_node = doc.append_child("emulated_controller"); emulated_controller_node.append_child("type").append_child(pugi::node_pcdata).set_value(std::string{ emulated_controller->type_string() }.c_str()); if(!is_default_file) emulated_controller->m_profile_name = std::string{filename}; if (emulated_controller->has_profile_name()) emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value( emulated_controller->get_profile_name().c_str()); // custom settings emulated_controller->save(emulated_controller_node); for (const auto& controller : emulated_controller->get_controllers()) { auto controller_node = emulated_controller_node.append_child("controller"); // general controller_node.append_child("api").append_child(pugi::node_pcdata).set_value(std::string{ controller->api_name() }.c_str()); controller_node.append_child("uuid").append_child(pugi::node_pcdata).set_value(controller->uuid().c_str()); controller_node.append_child("display_name").append_child(pugi::node_pcdata).set_value( controller->display_name().c_str()); // settings const auto& settings = controller->get_settings(); if (controller->has_motion()) controller_node.append_child("motion").append_child(pugi::node_pcdata).set_value( fmt::format("{}", settings.motion).c_str()); if (controller->has_rumble()) controller_node.append_child("rumble").append_child(pugi::node_pcdata).set_value( fmt::format("{}", settings.rumble).c_str()); auto axis_node = controller_node.append_child("axis"); axis_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value( fmt::format("{}", settings.axis.deadzone).c_str()); axis_node.append_child("range").append_child(pugi::node_pcdata).set_value( fmt::format("{}", settings.axis.range).c_str()); auto rotation_node = controller_node.append_child("rotation"); rotation_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value( fmt::format("{}", settings.rotation.deadzone).c_str()); rotation_node.append_child("range").append_child(pugi::node_pcdata).set_value( fmt::format("{}", settings.rotation.range).c_str()); auto trigger_node = controller_node.append_child("trigger"); trigger_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value( fmt::format("{}", settings.trigger.deadzone).c_str()); trigger_node.append_child("range").append_child(pugi::node_pcdata).set_value( fmt::format("{}", settings.trigger.range).c_str()); // custom settings controller->save(controller_node); // mappings for current controller auto mappings_node = controller_node.append_child("mappings"); for (const auto& mapping : emulated_controller->m_mappings) { if (!mapping.second.controller.expired() && *controller == *mapping.second.controller.lock()) { auto entry_node = mappings_node.append_child("entry"); entry_node.append_child("mapping").append_child(pugi::node_pcdata).set_value( fmt::format("{}", mapping.first).c_str()); entry_node.append_child("button").append_child(pugi::node_pcdata).set_value( fmt::format("{}", mapping.second.button).c_str()); } } } FileStream* fs = FileStream::createFile2(file_path); if (!fs) return false; std::stringstream xmlData; doc.save(xmlData); std::string xmlStr = xmlData.str(); fs->writeData(xmlStr.data(), xmlStr.size()); delete fs; return true; } bool InputManager::is_gameprofile_set(size_t player_index) const { return m_is_gameprofile_set[player_index]; } EmulatedControllerPtr InputManager::set_controller(EmulatedControllerPtr controller) { auto prev_controller = delete_controller(controller->player_index()); // assign controllers to new emulated controller if empty if (prev_controller && controller->get_controllers().empty()) { for (const auto& c : prev_controller->get_controllers()) { controller->add_controller(c); } } // try to connect all controllers /*for (auto& c : controller->get_controllers()) { c->connect(); }*/ std::scoped_lock lock(m_mutex); switch (controller->type()) { case EmulatedController::Type::VPAD: for (auto& pad : m_vpad) { if (!pad) { pad.swap(controller); return prev_controller; } } break; default: for (auto& pad : m_wpad) { if (!pad) { pad.swap(controller); return prev_controller; } } break; } cemu_assert_debug(false); return prev_controller; } EmulatedControllerPtr InputManager::set_controller(size_t player_index, EmulatedController::Type type) { try { auto emulated_controller = ControllerFactory::CreateEmulatedController(player_index, type); set_controller(emulated_controller); return emulated_controller; } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "Unable to set controller type {} on player index {}: {}", type, player_index, ex.what()); } return {}; } EmulatedControllerPtr InputManager::set_controller(size_t player_index, EmulatedController::Type type, const std::shared_ptr& controller) { auto result = set_controller(player_index, type); if (result) result->add_controller(controller); return result; } EmulatedControllerPtr InputManager::get_controller(size_t player_index) const { std::shared_lock lock(m_mutex); for (const auto& pad : m_vpad) { if (pad && pad->player_index() == player_index) return pad; } for (const auto& pad : m_wpad) { if (pad && pad->player_index() == player_index) return pad; } return {}; } EmulatedControllerPtr InputManager::delete_controller(size_t player_index, bool delete_profile) { std::scoped_lock lock(m_mutex); for (auto& controller : m_vpad) { auto result = controller; if (result && result->player_index() == player_index) { controller = {}; if(delete_profile) { std::error_code ec{}; fs::remove(ActiveSettings::GetConfigPath("controllerProfiles/controller{}.xml", player_index), ec); fs::remove(ActiveSettings::GetConfigPath("controllerProfiles/controller{}.txt", player_index), ec); } return result; } } for (auto& controller : m_wpad) { auto result = controller; if (result && result->player_index() == player_index) { controller = {}; std::error_code ec{}; fs::remove(ActiveSettings::GetConfigPath("controllerProfiles/controller{}.xml", player_index), ec); fs::remove(ActiveSettings::GetConfigPath("controllerProfiles/controller{}.txt", player_index), ec); return result; } } return {}; } std::shared_ptr InputManager::get_vpad_controller(size_t index) const { if (index >= m_vpad.size()) return {}; std::shared_lock lock(m_mutex); return std::static_pointer_cast(m_vpad[index]); } std::shared_ptr InputManager::get_wpad_controller(size_t index) const { if (index >= m_wpad.size()) return {}; std::shared_lock lock(m_mutex); return std::static_pointer_cast(m_wpad[index]); } std::pair InputManager::get_controller_count() const { std::shared_lock lock(m_mutex); const size_t vpad = std::count_if(m_vpad.cbegin(), m_vpad.cend(), [](const auto& v) { return v != nullptr; }); const size_t wpad = std::count_if(m_wpad.cbegin(), m_wpad.cend(), [](const auto& v) { return v != nullptr; }); return std::make_pair(vpad, wpad); } void InputManager::on_device_changed() { std::shared_lock lock(m_mutex); for (auto& pad : m_vpad) { if (pad) pad->connect(); } for (auto& pad : m_wpad) { if (pad) pad->connect(); } lock.unlock(); EventService::instance().signal(); } ControllerProviderPtr InputManager::get_api_provider(InputAPI::Type api) const { if(!m_api_available[api].empty()) return *(m_api_available[api].begin()); cemu_assert_debug(false); return {}; } ControllerProviderPtr InputManager::get_api_provider(InputAPI::Type api, const ControllerProviderSettings& settings) { for(const auto& p : m_api_available[api]) { if(*p == settings) { return p; } } const auto result = ControllerFactory::CreateControllerProvider(api, settings); m_api_available[api].emplace_back(result); return result; } void InputManager::apply_game_profile() { const auto& profiles = g_current_game_profile->GetControllerProfile(); for (int i = 0; i < kMaxController; ++i) { if (profiles[i] && !profiles[i]->empty()) { if (load(i, profiles[i].value())) { m_is_gameprofile_set[i] = true; if (const auto controller = get_controller(i)) { if (!controller->has_profile_name()) controller->m_profile_name = profiles[i].value(); } } } } } std::vector InputManager::get_profiles() { const auto path = ActiveSettings::GetConfigPath("controllerProfiles"); if (!exists(path)) return {}; std::set tmp; for (const auto& entry : fs::directory_iterator(path)) { const auto& p = entry.path(); if (p.has_extension() && (p.extension() == ".xml" || p.extension() == ".txt")) { auto stem = _pathToUtf8(p.filename().stem()); if (is_valid_profilename(stem)) { tmp.emplace(stem); } } } std::vector result; result.reserve(tmp.size()); result.insert(result.end(), tmp.begin(), tmp.end()); return result; } bool InputManager::is_valid_profilename(const std::string& name) { if (!IsValidFilename(name)) return false; // dont allow default profile names for (size_t i = 0; i < kMaxController; i++) { if (name == fmt::format("controller{}", i)) return false; } return true; } glm::ivec2 InputManager::get_mouse_position(bool pad_window) const { if (pad_window) { std::shared_lock lock(m_pad_mouse.m_mutex); return m_pad_mouse.position; } else { std::shared_lock lock(m_main_mouse.m_mutex); return m_main_mouse.position; } } std::optional InputManager::get_left_down_mouse_info(bool* is_pad) { if (is_pad) *is_pad = false; { std::shared_lock lock(m_main_mouse.m_mutex); if (std::exchange(m_main_mouse.left_down_toggle, false)) return m_main_mouse.position; if (m_main_mouse.left_down) return m_main_mouse.position; } { std::shared_lock lock(m_main_touch.m_mutex); if (std::exchange(m_main_touch.left_down_toggle, false)) return m_main_touch.position; if (m_main_touch.left_down) return m_main_touch.position; } if (is_pad) *is_pad = true; { std::shared_lock lock(m_pad_mouse.m_mutex); if (std::exchange(m_pad_mouse.left_down_toggle, false)) return m_pad_mouse.position; if (m_pad_mouse.left_down) return m_pad_mouse.position; } { std::shared_lock lock(m_pad_touch.m_mutex); if (std::exchange(m_pad_touch.left_down_toggle, false)) return m_pad_touch.position; if (m_pad_touch.left_down) return m_pad_touch.position; } return {}; } std::optional InputManager::get_right_down_mouse_info(bool* is_pad) { if (is_pad) *is_pad = false; { std::shared_lock lock(m_main_mouse.m_mutex); if (std::exchange(m_main_mouse.right_down_toggle, false)) return m_main_mouse.position; if (m_main_mouse.right_down) return m_main_mouse.position; } { std::shared_lock lock(m_main_touch.m_mutex); if (std::exchange(m_main_touch.right_down_toggle, false)) return m_main_touch.position; if (m_main_touch.right_down) return m_main_touch.position; } if (is_pad) *is_pad = true; { std::shared_lock lock(m_pad_mouse.m_mutex); if (std::exchange(m_pad_mouse.right_down_toggle, false)) return m_pad_mouse.position; if (m_pad_mouse.right_down) return m_pad_mouse.position; } { std::shared_lock lock(m_pad_touch.m_mutex); if (std::exchange(m_pad_touch.right_down_toggle, false)) return m_pad_touch.position; if (m_pad_touch.right_down) return m_pad_touch.position; } return {}; } void InputManager::update_thread() { SetThreadName("Input_update"); while (!m_update_thread_shutdown.load(std::memory_order::relaxed)) { std::shared_lock lock(m_mutex); for (auto& pad : m_vpad) { if (pad) pad->update(); } for (auto& pad : m_wpad) { if (pad) pad->update(); } lock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::yield(); } } ================================================ FILE: src/input/InputManager.h ================================================ #pragma once #if BOOST_OS_WINDOWS #include "input/api/DirectInput/DirectInputControllerProvider.h" #include "input/api/XInput/XInputControllerProvider.h" #endif #ifdef SUPPORTS_WIIMOTE #include "input/api/Wiimote/WiimoteControllerProvider.h" #endif #include "util/helpers/Singleton.h" #include "input/api/SDL/SDLControllerProvider.h" #include "input/api/Keyboard/KeyboardControllerProvider.h" #include "input/api/DSU/DSUControllerProvider.h" #include "input/api/GameCube/GameCubeControllerProvider.h" #include "input/emulated/VPADController.h" #include "input/emulated/WPADController.h" #include #include class InputManager : public Singleton { friend class Singleton; InputManager(); ~InputManager(); friend class MainWindow; friend class PadViewFrame; public: constexpr static size_t kMaxController = 8; constexpr static size_t kMaxVPADControllers = 2; constexpr static size_t kMaxWPADControllers = 7; void load() noexcept; bool load(size_t player_index, std::string_view filename = {}); bool migrate_config(const fs::path& file_path); void save() noexcept; bool save(size_t player_index, std::string_view filename = {}); bool is_gameprofile_set(size_t player_index) const; EmulatedControllerPtr set_controller(EmulatedControllerPtr controller); EmulatedControllerPtr set_controller(size_t player_index, EmulatedController::Type type); EmulatedControllerPtr set_controller(size_t player_index, EmulatedController::Type type, const std::shared_ptr& controller); EmulatedControllerPtr delete_controller(size_t player_index, bool delete_profile = false); EmulatedControllerPtr get_controller(size_t player_index) const; std::shared_ptr get_vpad_controller(size_t index) const; std::shared_ptr get_wpad_controller(size_t index) const; std::pair get_controller_count() const; bool is_api_available(InputAPI::Type api) const { return !m_api_available[api].empty(); } ControllerProviderPtr get_api_provider(std::string_view api_name) const; ControllerProviderPtr get_api_provider(InputAPI::Type api) const; // will create the provider with the given settings if it doesn't exist yet ControllerProviderPtr get_api_provider(InputAPI::Type api, const ControllerProviderSettings& settings); const auto& get_api_providers() const { return m_api_available; } void apply_game_profile(); void on_device_changed(); static std::vector get_profiles(); static bool is_valid_profilename(const std::string& name); struct MouseInfo { mutable std::shared_mutex m_mutex; glm::ivec2 position{}; bool left_down = false; bool right_down = false; bool left_down_toggle = false; bool right_down_toggle = false; } m_main_mouse{}, m_pad_mouse{}, m_main_touch{}, m_pad_touch{}; glm::ivec2 get_mouse_position(bool pad_window) const; std::optional get_left_down_mouse_info(bool* is_pad); std::optional get_right_down_mouse_info(bool* is_pad); std::atomic m_mouse_wheel; private: void update_thread(); std::thread m_update_thread; std::atomic m_update_thread_shutdown{false}; std::array, InputAPI::MAX> m_api_available{ }; mutable std::shared_mutex m_mutex; std::array m_vpad; std::array m_wpad; std::array m_is_gameprofile_set{}; template void create_provider() // lambda templates only work in c++20 -> define locally in ctor { static_assert(std::is_base_of_v); try { auto controller = std::make_shared(); m_api_available[controller->api()] = std::vector{ controller }; } catch (const std::exception& ex) { cemuLog_log(LogType::Force, ex.what()); } } }; ================================================ FILE: src/input/api/Controller.cpp ================================================ #include "input/api/Controller.h" #include "config/CemuConfig.h" #include "WindowSystem.h" ControllerBase::ControllerBase(std::string_view uuid, std::string_view display_name) : m_uuid{uuid}, m_display_name{display_name} { } const ControllerState& ControllerBase::update_state() { if (!m_is_calibrated) calibrate(); ControllerState result = raw_state(); // ignore default buttons result.buttons.UnsetButtons(m_default_state.buttons); // apply deadzone and range and ignore default axis values apply_axis_setting(result.axis, m_default_state.axis, m_settings.axis); apply_axis_setting(result.rotation, m_default_state.rotation, m_settings.rotation); apply_axis_setting(result.trigger, m_default_state.trigger, m_settings.trigger); #define APPLY_AXIS_BUTTON(_axis_, _flag_) \ if (result._axis_.x < -ControllerState::kAxisThreshold) \ result.buttons.SetButtonState((_flag_) + (kAxisXN - kAxisXP), true); \ else if (result._axis_.x > ControllerState::kAxisThreshold) \ result.buttons.SetButtonState((_flag_), true); \ if (result._axis_.y < -ControllerState::kAxisThreshold) \ result.buttons.SetButtonState((_flag_) + 1 + (kAxisXN - kAxisXP), true); \ else if (result._axis_.y > ControllerState::kAxisThreshold) \ result.buttons.SetButtonState((_flag_) + 1, true); if (result.axis.x < -ControllerState::kAxisThreshold) result.buttons.SetButtonState((kAxisXP) + (kAxisXN - kAxisXP), true); else if (result.axis.x > ControllerState::kAxisThreshold) result.buttons.SetButtonState((kAxisXP), true); if (result.axis.y < -ControllerState::kAxisThreshold) result.buttons.SetButtonState((kAxisXP) + 1 + (kAxisXN - kAxisXP), true); else if (result.axis.y > ControllerState::kAxisThreshold) result.buttons.SetButtonState((kAxisXP) + 1, true); APPLY_AXIS_BUTTON(rotation, kRotationXP); APPLY_AXIS_BUTTON(trigger, kTriggerXP); /* // positive values kAxisXP, kAxisYP, kRotationXP, kRotationYP, kTriggerXP, kTriggerYP, // negative values kAxisXN, kAxisYN, kRotationXN, kRotationYN, kTriggerXN, kTriggerYN, */ #undef APPLY_AXIS_BUTTON WindowSystem::CaptureInput(result, m_last_state); m_last_state = std::move(result); return m_last_state; } void ControllerBase::apply_axis_setting(glm::vec2& axis, const glm::vec2& default_value, const AxisSetting& setting) const { constexpr float kMaxValue = 1.0f + ControllerState::kMinAxisValue; if (setting.deadzone < 1.0f) { if (axis.x < default_value.x) axis.x = (axis.x - default_value.x) / (kMaxValue + default_value.x); else axis.x = (axis.x - default_value.x) / (kMaxValue - default_value.x); if (axis.y < default_value.y) axis.y = (axis.y - default_value.y) / (kMaxValue + default_value.y); else axis.y = (axis.y - default_value.y) / (kMaxValue - default_value.y); auto len = length(axis); if (len >= setting.deadzone) { axis *= setting.range; len = length(axis); // Scaled Radial Dead Zone: stickInput = stickInput.normalized * ((stickInput.magnitude - deadzone) / (1 - deadzone)); if (len > 0) { axis = normalize(axis); axis *= ((len - setting.deadzone) / (kMaxValue - setting.deadzone)); if (length(axis) > 1.0f) axis = normalize(axis); } if (axis.x != 0 || axis.y != 0) { if (std::abs(axis.x) < ControllerState::kMinAxisValue) axis.x = ControllerState::kMinAxisValue; if (std::abs(axis.y) < ControllerState::kMinAxisValue) axis.y = ControllerState::kMinAxisValue; } return; } } axis = {0, 0}; } bool ControllerBase::operator==(const ControllerBase& c) const { return api() == c.api() && uuid() == c.uuid(); } float ControllerBase::get_axis_value(uint64 button) const { if (m_last_state.buttons.GetButtonState(button)) { if (button <= kButtonNoneAxisMAX || !has_axis()) return 1.0f; switch (button) { case kAxisXP: case kAxisXN: return std::abs(m_last_state.axis.x); case kAxisYP: case kAxisYN: return std::abs(m_last_state.axis.y); case kRotationXP: case kRotationXN: return std::abs(m_last_state.rotation.x); case kRotationYP: case kRotationYN: return std::abs(m_last_state.rotation.y); case kTriggerXP: case kTriggerXN: return std::abs(m_last_state.trigger.x); case kTriggerYP: case kTriggerYN: return std::abs(m_last_state.trigger.y); } } return 0; } const ControllerState& ControllerBase::calibrate() { m_default_state = raw_state(); m_is_calibrated = is_connected(); return m_default_state; } std::string ControllerBase::get_button_name(uint64 button) const { switch (button) { case kButtonZL: return "ZL"; case kButtonZR: return "ZR"; case kButtonUp: return "DPAD-Up"; case kButtonDown: return "DPAD-Down"; case kButtonLeft: return "DPAD-Left"; case kButtonRight: return "DPAD-Right"; case kAxisXP: return "X-Axis+"; case kAxisYP: return "Y-Axis+"; case kAxisXN: return "X-Axis-"; case kAxisYN: return "Y-Axis-"; case kRotationXP: return "X-Rotation+"; case kRotationYP: return "Y-Rotation+"; case kRotationXN: return "X-Rotation-"; case kRotationYN: return "Y-Rotation-"; case kTriggerXP: return "X-Trigger+"; case kTriggerYP: return "Y-Trigger+"; case kTriggerXN: return "X-Trigger-"; case kTriggerYN: return "y-Trigger-"; } return fmt::format("Button {}", (uint64)button); } ControllerBase::Settings ControllerBase::get_settings() const { std::scoped_lock lock(m_settings_mutex); return m_settings; } void ControllerBase::set_settings(const Settings& settings) { std::scoped_lock lock(m_settings_mutex); m_settings = settings; } void ControllerBase::set_axis_settings(const AxisSetting& settings) { std::scoped_lock lock(m_settings_mutex); m_settings.axis = settings; } void ControllerBase::set_rotation_settings(const AxisSetting& settings) { std::scoped_lock lock(m_settings_mutex); m_settings.rotation = settings; } void ControllerBase::set_trigger_settings(const AxisSetting& settings) { std::scoped_lock lock(m_settings_mutex); m_settings.trigger = settings; } void ControllerBase::set_rumble(float rumble) { std::scoped_lock lock(m_settings_mutex); m_settings.rumble = rumble; } void ControllerBase::set_use_motion(bool state) { std::scoped_lock lock(m_settings_mutex); m_settings.motion = state; } ================================================ FILE: src/input/api/Controller.h ================================================ #pragma once #include "input/InputManager.h" #include "input/motion/MotionSample.h" #include "input/api/ControllerState.h" namespace pugi { class xml_node; } enum Buttons2 : uint64 { // General kButton0, kButton1, kButton2, kButton3, kButton4, kButton5, kButton6, kButton7, kButton8, kButton9, kButton10, kButton11, kButton12, kButton13, kButton14, kButton15, kButton16, kButton17, kButton18, kButton19, kButton20, kButton21, kButton22, kButton23, kButton24, kButton25, kButton26, kButton27, kButton28, kButton29, kButton30, kButton31, // Trigger kButtonZL, kButtonZR, // DPAD kButtonUp, kButtonDown, kButtonLeft, kButtonRight, // positive values kAxisXP, kAxisYP, kRotationXP, kRotationYP, kTriggerXP, kTriggerYP, // negative values kAxisXN, kAxisYN, kRotationXN, kRotationYN, kTriggerXN, kTriggerYN, kButtonMAX, kButtonNoneAxisMAX = kButtonRight, kButtonAxisStart = kAxisXP, }; class ControllerBase { public: ControllerBase(std::string_view uuid, std::string_view display_name); virtual ~ControllerBase() = default; const std::string& uuid() const { return m_uuid; } const std::string& display_name() const { return m_display_name; } virtual std::string_view api_name() const = 0; virtual InputAPI::Type api() const = 0; virtual void update() {} virtual bool connect() { return is_connected(); } virtual bool is_connected() = 0; virtual bool has_battery() { return false; } virtual bool has_low_battery() { return false; } const ControllerState& calibrate(); const ControllerState& update_state(); const ControllerState& get_state() const { return m_last_state; } const ControllerState& get_default_state() { return is_calibrated() ? m_default_state : calibrate(); } virtual ControllerState raw_state() = 0; bool is_calibrated() const { return m_is_calibrated; } float get_axis_value(uint64 button) const; virtual bool has_axis() const { return true; } bool use_motion() { return has_motion() && m_settings.motion; } virtual bool has_motion() { return false; } virtual MotionSample get_motion_sample() { return {}; } virtual bool has_position() { return false; } virtual glm::vec2 get_position() { return {}; } virtual glm::vec2 get_prev_position() { return {}; } virtual PositionVisibility GetPositionVisibility() {return PositionVisibility::NONE;}; virtual bool has_rumble() { return false; } virtual void start_rumble() {} virtual void stop_rumble() {} virtual std::string get_button_name(uint64 button) const; virtual void save(pugi::xml_node& node){} virtual void load(const pugi::xml_node& node){} struct AxisSetting { AxisSetting(float deadzone = 0.25f) : deadzone(deadzone) {} float deadzone; float range = 1.0f; }; struct Settings { AxisSetting axis{}, rotation{}, trigger{}; float rumble = 0; bool motion = false; // only valid when has_motion is true }; Settings get_settings() const; void set_settings(const Settings& settings); void set_axis_settings(const AxisSetting& settings); void set_rotation_settings(const AxisSetting& settings); void set_trigger_settings(const AxisSetting& settings); void set_rumble(float rumble); void set_use_motion(bool state); void apply_axis_setting(glm::vec2& axis, const glm::vec2& default_value, const AxisSetting& setting) const; bool operator==(const ControllerBase& c) const; bool operator!=(const ControllerBase& c) const { return !(*this == c); } protected: std::string m_uuid; std::string m_display_name; ControllerState m_last_state{}; bool m_is_calibrated = false; ControllerState m_default_state{}; mutable std::mutex m_settings_mutex; Settings m_settings{}; }; template class Controller : public ControllerBase { public: Controller(std::string_view uuid, std::string_view display_name) : ControllerBase(uuid, display_name) { static_assert(std::is_base_of_v); m_provider = std::dynamic_pointer_cast(InputManager::instance().get_api_provider(TProvider::kAPIType)); cemu_assert_debug(m_provider != nullptr); } Controller(std::string_view uuid, std::string_view display_name, const ControllerProviderSettings& settings) : ControllerBase(uuid, display_name) { static_assert(std::is_base_of_v); m_provider = std::dynamic_pointer_cast(InputManager::instance().get_api_provider(TProvider::kAPIType, settings)); cemu_assert_debug(m_provider != nullptr); } // update provider if settings are different from default provider void update_provider(std::shared_ptr provider) { m_provider = std::move(provider); } protected: using base_type = Controller; std::shared_ptr m_provider; }; using ControllerPtr = std::shared_ptr; ================================================ FILE: src/input/api/ControllerProvider.h ================================================ #pragma once #include "input/api/InputAPI.h" class ControllerBase; struct ControllerProviderSettings { virtual ~ControllerProviderSettings() = default; virtual bool operator==(const ControllerProviderSettings&) const = 0; }; class ControllerProviderBase { public: ControllerProviderBase() = default; virtual ~ControllerProviderBase() = default; virtual InputAPI::Type api() const = 0; std::string_view api_name() const { return to_string(api()); } virtual std::vector> get_controllers() = 0; virtual bool has_settings() const { return false; } virtual bool operator==(const ControllerProviderBase& p) const { return api() == p.api(); } virtual bool operator==(const ControllerProviderSettings& p) const { return false; } }; using ControllerProviderPtr = std::shared_ptr; template class ControllerProvider : public ControllerProviderBase { using base_type = ControllerProviderBase; public: ControllerProvider() = default; ControllerProvider(const TSettings& settings) : m_settings(settings) {} bool has_settings() const override { return true; } const TSettings& get_settings() const { return m_settings; } bool operator==(const ControllerProviderBase& p) const override { if (!base_type::operator==(p)) return false; if (!p.has_settings()) return false; auto* ptr = dynamic_cast*>(&p); if (!ptr) return false; return base_type::operator==(p) && m_settings == ptr->m_settings; } bool operator==(const ControllerProviderSettings& p) const override { auto* ptr = dynamic_cast(&p); if (!ptr) return false; return m_settings == *ptr; } protected: TSettings m_settings{}; }; ================================================ FILE: src/input/api/ControllerState.cpp ================================================ #include "input/api/ControllerState.h" bool ControllerState::operator==(const ControllerState& other) const { return buttons == other.buttons; /*&& (std::signbit(axis.x) == std::signbit(other.axis.x) && std::abs(axis.x - other.axis.x) <= kAxisThreshold) && (std::signbit(axis.y) == std::signbit(other.axis.y) && std::abs(axis.y - other.axis.y) <= kAxisThreshold) && (std::signbit(rotation.x) == std::signbit(other.rotation.x) && std::abs(rotation.x - other.rotation.x) <= kAxisThreshold) && (std::signbit(rotation.y) == std::signbit(other.rotation.y) && std::abs(rotation.y - other.rotation.y) <= kAxisThreshold) && (std::signbit(trigger.x) == std::signbit(other.trigger.x) && std::abs(trigger.x - other.trigger.x) <= kAxisThreshold) && (std::signbit(trigger.y) == std::signbit(other.trigger.y) && std::abs(trigger.y - other.trigger.y) <= kAxisThreshold);*/ } ================================================ FILE: src/input/api/ControllerState.h ================================================ #pragma once #include #include "util/helpers/fspinlock.h" enum class PositionVisibility { NONE = 0, FULL = 1, PARTIAL = 2 }; // helper class for storing and managing button press states in a thread-safe manner struct ControllerButtonState { ControllerButtonState() = default; ControllerButtonState(const ControllerButtonState& other) { this->m_pressedButtons = other.m_pressedButtons; } ControllerButtonState(ControllerButtonState&& other) { this->m_pressedButtons = std::move(other.m_pressedButtons); } void SetButtonState(uint32 buttonId, bool isPressed) { std::lock_guard _l(this->m_spinlock); if (isPressed) { if (std::find(m_pressedButtons.cbegin(), m_pressedButtons.cend(), buttonId) != m_pressedButtons.end()) return; m_pressedButtons.emplace_back(buttonId); } else { std::erase(m_pressedButtons, buttonId); } } // set multiple buttons at once within a single lock interval void SetPressedButtons(std::span buttonList) { std::lock_guard _l(this->m_spinlock); for (auto& buttonId : buttonList) { if (std::find(m_pressedButtons.cbegin(), m_pressedButtons.cend(), buttonId) == m_pressedButtons.end()) m_pressedButtons.emplace_back(buttonId); } } // returns true if pressed bool GetButtonState(uint32 buttonId) const { std::lock_guard _l(this->m_spinlock); bool r = std::find(m_pressedButtons.cbegin(), m_pressedButtons.cend(), buttonId) != m_pressedButtons.cend(); return r; } // remove pressed state for all pressed buttons in buttonsToUnset void UnsetButtons(const ControllerButtonState& buttonsToUnset) { std::scoped_lock _l(this->m_spinlock, buttonsToUnset.m_spinlock); for (auto it = m_pressedButtons.begin(); it != m_pressedButtons.end();) { if (std::find(buttonsToUnset.m_pressedButtons.cbegin(), buttonsToUnset.m_pressedButtons.cend(), *it) == buttonsToUnset.m_pressedButtons.cend()) { ++it; continue; } it = m_pressedButtons.erase(it); } } // returns true if no buttons are pressed bool IsIdle() const { std::lock_guard _l(this->m_spinlock); const bool r = m_pressedButtons.empty(); return r; } std::vector GetButtonList() const { std::lock_guard _l(this->m_spinlock); std::vector copy = m_pressedButtons; return copy; } bool operator==(const ControllerButtonState& other) const { std::scoped_lock _l(this->m_spinlock, other.m_spinlock); auto& otherButtons = other.m_pressedButtons; if (m_pressedButtons.size() != otherButtons.size()) { return false; } for (auto& buttonId : m_pressedButtons) { if (std::find(otherButtons.cbegin(), otherButtons.cend(), buttonId) == otherButtons.cend()) { return false; } } return true; } ControllerButtonState& operator=(ControllerButtonState&& other) { cemu_assert_debug(!other.m_spinlock.is_locked()); std::scoped_lock _l(this->m_spinlock, other.m_spinlock); this->m_pressedButtons = std::move(other.m_pressedButtons); return *this; } private: std::vector m_pressedButtons; // since only very few buttons are pressed at a time, using a vector with linear scan is more efficient than a set/map mutable FSpinlock m_spinlock; }; struct ControllerState { // when does a axis counts as pressed constexpr static float kAxisThreshold = 0.1f; // on the real console the stick x or y values never really reach 0.0 if one of the axis is moved // some games rely on this due to incorrectly checking if the stick is tilted via if (vstick.x != 0 && vstick.y != 0) // here we simulate a slight bias if the axis is almost perfectly centered constexpr static float kMinAxisValue = 0.0000001f; // [-1; 1] glm::vec2 axis{ }; glm::vec2 rotation{ }; glm::vec2 trigger{ }; ControllerButtonState buttons{}; uint64 last_state = 0; bool operator==(const ControllerState& other) const; bool operator!=(const ControllerState& other) const { return !(*this == other); } }; ================================================ FILE: src/input/api/DSU/DSUController.cpp ================================================ #include "input/api/DSU/DSUController.h" #include DSUController::DSUController(uint32 index) : base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1)), m_index(index) { if (index >= DSUControllerProvider::kMaxClients) throw std::runtime_error(fmt::format("max {} dsu controllers are supported! given index: {}", DSUControllerProvider::kMaxClients, index)); } DSUController::DSUController(uint32 index, const DSUProviderSettings& settings) : base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1), settings), m_index(index) { if (index >= DSUControllerProvider::kMaxClients) throw std::runtime_error(fmt::format("max {} dsu controllers are supported! given index: {}", DSUControllerProvider::kMaxClients, index)); } void DSUController::save(pugi::xml_node& node) { base_type::save(node); node.append_child("ip").append_child(pugi::node_pcdata).set_value( fmt::format("{}", m_provider->get_settings().ip).c_str()); node.append_child("port").append_child(pugi::node_pcdata).set_value( fmt::format("{}", m_provider->get_settings().port).c_str()); } void DSUController::load(const pugi::xml_node& node) { base_type::load(node); DSUProviderSettings settings; if (const auto value = node.child("ip")) settings.ip = value.child_value(); if (const auto value = node.child("port")) settings.port = ConvertString(value.child_value()); const auto provider = InputManager::instance().get_api_provider(api(), settings); update_provider(std::dynamic_pointer_cast(provider)); connect(); } bool DSUController::connect() { if (is_connected()) return true; m_provider->request_pad_data(m_index); return is_connected(); } bool DSUController::is_connected() { return m_provider->is_connected(m_index); } MotionSample DSUController::get_motion_sample() { return m_provider->get_motion_sample(m_index); } bool DSUController::has_position() { const auto state = m_provider->get_state(m_index); return state.data.tpad1.active || state.data.tpad2.active; } glm::vec2 DSUController::get_position() { // touchpad resolution is 1920x942 const auto state = m_provider->get_state(m_index); if (state.data.tpad1.active) return glm::vec2{(float)state.data.tpad1.x / 1920.0f, (float)state.data.tpad1.y / 942.0f}; if (state.data.tpad2.active) return glm::vec2{(float)state.data.tpad2.x / 1920.0f, (float)state.data.tpad2.y / 942.0f}; return {}; } glm::vec2 DSUController::get_prev_position() { const auto state = m_provider->get_prev_state(m_index); if (state.data.tpad1.active) return glm::vec2{(float)state.data.tpad1.x / 1920.0f, (float)state.data.tpad1.y / 942.0f}; if (state.data.tpad2.active) return glm::vec2{(float)state.data.tpad2.x / 1920.0f, (float)state.data.tpad2.y / 942.0f}; return {}; } PositionVisibility DSUController::GetPositionVisibility() { const auto state = m_provider->get_prev_state(m_index); return (state.data.tpad1.active || state.data.tpad2.active) ? PositionVisibility::FULL : PositionVisibility::NONE; } std::string DSUController::get_button_name(uint64 button) const { switch (button) { case kButton0: return "Share"; case kButton1: return "Stick L"; case kButton2: return "Stick R"; case kButton3: return "Options"; case kButton4: return "Up"; case kButton5: return "Right"; case kButton6: return "Down"; case kButton7: return "Left"; case kButton8: return "ZL"; case kButton9: return "ZR"; case kButton10: return "L"; case kButton11: return "R"; case kButton12: return "Triangle"; case kButton13: return "Circle"; case kButton14: return "Cross"; case kButton15: return "Square"; case kButton16: return "Touch"; } return base_type::get_button_name(button); } ControllerState DSUController::raw_state() { ControllerState result{}; if (!is_connected()) return result; const auto state = m_provider->get_state(m_index); // didn't read any data from the controller yet if (state.info.state != DsState::Connected) return result; int bitindex = 0; for (int i = 0; i < 8; ++i, ++bitindex) { if (HAS_BIT(state.data.state1, i)) { result.buttons.SetButtonState(bitindex, true); } } for (int i = 0; i < 8; ++i, ++bitindex) { if (HAS_BIT(state.data.state2, i)) { result.buttons.SetButtonState(bitindex, true); } } if (state.data.touch) result.buttons.SetButtonState(kButton16, true); result.axis.x = (float)state.data.lx / std::numeric_limits::max(); result.axis.x = (result.axis.x * 2.0f) - 1.0f; result.axis.y = (float)state.data.ly / std::numeric_limits::max(); result.axis.y = (result.axis.y * 2.0f) - 1.0f; result.rotation.x = (float)state.data.rx / std::numeric_limits::max(); result.rotation.x = (result.rotation.x * 2.0f) - 1.0f; result.rotation.y = (float)state.data.ry / std::numeric_limits::max(); result.rotation.y = (result.rotation.y * 2.0f) - 1.0f; result.trigger.x = (float)state.data.l2 / std::numeric_limits::max(); result.trigger.y = (float)state.data.r2 / std::numeric_limits::max(); return result; } ================================================ FILE: src/input/api/DSU/DSUController.h ================================================ #pragma once #include "input/api/Controller.h" #include "input/api/DSU/DSUControllerProvider.h" #include "Cafe/HW/AI/AI.h" #include "Cafe/HW/AI/AI.h" #include "Cafe/HW/AI/AI.h" #include "Cafe/HW/AI/AI.h" class DSUController : public Controller { public: DSUController(uint32 index); DSUController(uint32 index, const DSUProviderSettings& settings); std::string_view api_name() const override { static_assert(to_string(InputAPI::DSUClient) == "DSUController"); return to_string(InputAPI::DSUClient); } InputAPI::Type api() const override { return InputAPI::DSUClient; } void save(pugi::xml_node& node) override; void load(const pugi::xml_node& node) override; bool connect() override; bool is_connected() override; bool has_motion() override { return true; } MotionSample get_motion_sample() override; bool has_position() override; glm::vec2 get_position() override; glm::vec2 get_prev_position() override; PositionVisibility GetPositionVisibility() override; std::string get_button_name(uint64 button) const override; protected: ControllerState raw_state() override; private: uint32 m_index; }; ================================================ FILE: src/input/api/DSU/DSUControllerProvider.cpp ================================================ #include "input/api/DSU/DSUControllerProvider.h" #include "input/api/DSU/DSUController.h" #if BOOST_OS_WINDOWS #include #include #elif BOOST_OS_LINUX || BOOST_OS_MACOS #include #include #endif DSUControllerProvider::DSUControllerProvider() : base_type(), m_uid(rand()), m_socket(m_io_service) { if (!connect()) { throw std::runtime_error("dsu client can't open the udp connection"); } m_running = true; m_reader_thread = std::thread(&DSUControllerProvider::reader_thread, this); m_writer_thread = std::thread(&DSUControllerProvider::writer_thread, this); request_version(); } DSUControllerProvider::DSUControllerProvider(const DSUProviderSettings& settings) : base_type(settings), m_uid(rand()), m_socket(m_io_service) { if (!connect()) { throw std::runtime_error("dsu client can't open the udp connection"); } m_running = true; m_reader_thread = std::thread(&DSUControllerProvider::reader_thread, this); m_writer_thread = std::thread(&DSUControllerProvider::writer_thread, this); request_version(); } DSUControllerProvider::~DSUControllerProvider() { if (m_running) { m_running = false; m_writer_thread.join(); m_reader_thread.join(); } } std::vector> DSUControllerProvider::get_controllers() { std::vector result; std::array indices; for (auto i = 0; i < kMaxClients; ++i) indices[i] = get_packet_index(i); request_pad_info(); const auto controller_result = wait_update(indices, 3000); for (auto i = 0; i < kMaxClients; ++i) { if (controller_result[i] && is_connected(i)) result.emplace_back(std::make_shared(i, m_settings)); } return result; } bool DSUControllerProvider::connect() { // already connected? if (m_receiver_endpoint.address().to_string() == get_settings().ip && m_receiver_endpoint.port() == get_settings().port) return true; try { using namespace boost::asio; ip::udp::resolver resolver(m_io_service); m_receiver_endpoint = *resolver.resolve(get_settings().ip, fmt::format("{}", get_settings().port)).cbegin(); if (m_socket.is_open()) m_socket.close(); m_socket.open(ip::udp::v4()); // set timeout for our threads to give a chance to exit #if BOOST_OS_WINDOWS m_socket.set_option(boost::asio::detail::socket_option::integer{200}); #elif BOOST_OS_LINUX || BOOST_OS_MACOS timeval timeout{.tv_usec = 200 * 1000}; setsockopt(m_socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeval)); #endif // reset data m_state = {}; m_prev_state = {}; // restart threads return true; } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "dsu client connect error: {}", ex.what()); return false; } } bool DSUControllerProvider::is_connected(uint8_t index) const { if (index >= kMaxClients) return false; std::scoped_lock lock(m_mutex[index]); return m_state[index].info.state == DsState::Connected; } DSUControllerProvider::ControllerState DSUControllerProvider::get_state(uint8_t index) const { if (index >= kMaxClients) return {}; std::scoped_lock lock(m_mutex[index]); return m_state[index]; } DSUControllerProvider::ControllerState DSUControllerProvider::get_prev_state(uint8_t index) const { if (index >= kMaxClients) return {}; std::scoped_lock lock(m_mutex[index]); return m_prev_state[index]; } std::array DSUControllerProvider::wait_update( const std::array& indices, size_t timeout) const { std::array result{false, false, false, false}; const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout); do { for (int i = 0; i < kMaxClients; ++i) { if (result[i]) continue; std::unique_lock lock(m_mutex[i]); result[i] = indices[i] < m_state[i].packet_index; } if (std::all_of(result.cbegin(), result.cend(), [](const bool& v) { return v == true; })) break; //std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::yield(); } while (std::chrono::steady_clock::now() < end); return result; } bool DSUControllerProvider::wait_update(uint8_t index, uint32_t packet_index, size_t timeout) const { if (index >= kMaxClients) return false; std::unique_lock lock(m_mutex[index]); if (packet_index < m_state[index].packet_index) return true; const auto result = m_wait_cond[index].wait_for(lock, std::chrono::milliseconds(timeout), [this, index, packet_index]() { return packet_index < m_state[index].packet_index; }); return result; } uint32_t DSUControllerProvider::get_packet_index(uint8_t index) const { std::scoped_lock lock(m_mutex[index]); return m_state[index].packet_index; } void DSUControllerProvider::request_version() { auto msg = std::make_unique(m_uid); std::scoped_lock lock(m_writer_mutex); m_writer_jobs.push(std::move(msg)); m_writer_cond.notify_one(); } void DSUControllerProvider::request_pad_info() { auto msg = std::make_unique(m_uid, 4, std::array{0, 1, 2, 3}); std::scoped_lock lock(m_writer_mutex); m_writer_jobs.push(std::move(msg)); m_writer_cond.notify_one(); } void DSUControllerProvider::request_pad_info(uint8_t index) { if (index >= kMaxClients) return; auto msg = std::make_unique(m_uid, 1, std::array{index}); std::scoped_lock lock(m_writer_mutex); m_writer_jobs.push(std::move(msg)); m_writer_cond.notify_one(); } void DSUControllerProvider::request_pad_data() { auto msg = std::make_unique(m_uid); std::scoped_lock lock(m_writer_mutex); m_writer_jobs.push(std::move(msg)); m_writer_cond.notify_one(); } void DSUControllerProvider::request_pad_data(uint8_t index) { if (index >= kMaxClients) return; auto msg = std::make_unique(m_uid, index); std::scoped_lock lock(m_writer_mutex); m_writer_jobs.push(std::move(msg)); m_writer_cond.notify_one(); } MotionSample DSUControllerProvider::get_motion_sample(uint8_t index) const { if (index >= kMaxClients) return MotionSample(); std::scoped_lock lock(m_mutex[index]); return m_state[index].motion_sample; } void DSUControllerProvider::reader_thread() { SetThreadName("DSU-reader"); bool first_read = true; while (m_running.load(std::memory_order_relaxed)) { ServerMessage* msg; //try //{ std::array recv_buf; // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init) boost::asio::ip::udp::endpoint sender_endpoint; boost::system::error_code ec{}; const size_t len = m_socket.receive_from(boost::asio::buffer(recv_buf), sender_endpoint, 0, ec); if (ec) { #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::ReaderThread: exception %s\n", ec.what()); #endif // there's probably no server listening on the given address:port if (first_read) // workaroud: first read always fails? first_read = false; else { std::this_thread::sleep_for(std::chrono::milliseconds(250)); std::this_thread::yield(); } continue; } #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::ReaderThread: received message with len: 0x%llx\n", len); #endif if (len < sizeof(ServerMessage)) // cant be a valid message continue; msg = (ServerMessage*)recv_buf.data(); // } // catch (const std::exception&) // { //#ifdef DEBUG_DSU_CLIENT // printf(" DSUControllerProvider::ReaderThread: exception %s\n", ex.what()); //#endif // // // there's probably no server listening on the given address:port // if (first_read) // workaroud: first read always fails? // first_read = false; // else // { // std::this_thread::sleep_for(std::chrono::milliseconds(250)); // std::this_thread::yield(); // } // continue; // } uint8_t index = 0xFF; switch (msg->GetMessageType()) { case MessageType::Version: { const auto rsp = (VersionResponse*)msg; if (!rsp->IsValid()) { #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::ReaderThread: VersionResponse is invalid!\n"); #endif continue; } #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::ReaderThread: server version is: 0x%x\n", rsp->GetVersion()); #endif m_server_version = rsp->GetVersion(); // wdc break; } case MessageType::Information: { const auto info = (PortInfo*)msg; if (!info->IsValid()) { #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::ReaderThread: PortInfo is invalid!\n"); #endif continue; } index = info->GetIndex(); #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::ReaderThread: received PortInfo for index %d\n", index); #endif auto& mutex = m_mutex[index]; std::scoped_lock lock(mutex); m_prev_state[index] = m_state[index]; m_state[index] = *info; m_wait_cond[index].notify_all(); break; } case MessageType::Data: { const auto rsp = (DataResponse*)msg; if (!rsp->IsValid()) { #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::ReaderThread: DataResponse is invalid!\n"); #endif continue; } index = rsp->GetIndex(); #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::ReaderThread: received DataResponse for index %d\n", index); #endif auto& mutex = m_mutex[index]; std::scoped_lock lock(mutex); m_prev_state[index] = m_state[index]; m_state[index] = *rsp; m_wait_cond[index].notify_all(); // update motion info immediately, guaranteeing that we dont drop packets integrate_motion(index, *rsp); break; } } if (index != 0xFF) request_pad_data(index); } } void DSUControllerProvider::writer_thread() { SetThreadName("DSU-writer"); while (m_running.load(std::memory_order_relaxed)) { std::unique_lock lock(m_writer_mutex); while (m_writer_jobs.empty()) { if (m_writer_cond.wait_for(lock, std::chrono::milliseconds(250)) == std::cv_status::timeout) { if (!m_running.load(std::memory_order_relaxed)) return; } } const auto msg = std::move(m_writer_jobs.front()); m_writer_jobs.pop(); lock.unlock(); #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::WriterThread: sending message: 0x%x (len: 0x%x)\n", (int)msg->GetMessageType(), msg->GetSize()); #endif try { m_socket.send_to(boost::asio::buffer(msg.get(), msg->GetSize()), m_receiver_endpoint); } catch (const std::exception& ec) { #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::WriterThread: exception %s\n", ec.what()); #endif std::this_thread::sleep_for(std::chrono::milliseconds(250)); } } } void DSUControllerProvider::integrate_motion(uint8_t index, const DataResponse& data_response) { const uint64 ts = data_response.GetMotionTimestamp(); if (ts <= m_last_motion_timestamp[index]) { const uint64 dif = m_last_motion_timestamp[index] - ts; if (dif >= 10000000) // timestamp more than 10 seconds in the past, a controller reset probably happened m_last_motion_timestamp[index] = 0; return; } const uint64 elapsedTime = ts - m_last_motion_timestamp[index]; m_last_motion_timestamp[index] = ts; const double elapsedTimeD = (double)elapsedTime / 1000000.0; const auto& acc = data_response.GetAcceleration(); const auto& gyro = data_response.GetGyro(); m_motion_handler[index].processMotionSample((float)elapsedTimeD, gyro.x * 0.0174533f, gyro.y * 0.0174533f, gyro.z * 0.0174533f, acc.x, -acc.y, -acc.z); m_state[index].motion_sample = m_motion_handler[index].getMotionSample(); } DSUControllerProvider::ControllerState& DSUControllerProvider::ControllerState::operator=(const PortInfo& port_info) { info = port_info.GetInfo(); last_update = std::chrono::steady_clock::now(); packet_index++; // increase packet index for every packet we assign/recv return *this; } DSUControllerProvider::ControllerState& DSUControllerProvider::ControllerState::operator=( const DataResponse& data_response) { this->operator=(static_cast(data_response)); data = data_response.GetData(); return *this; } ================================================ FILE: src/input/api/DSU/DSUControllerProvider.h ================================================ #pragma once #include "input/motion/MotionHandler.h" #include "input/api/DSU/DSUMessages.h" #include "input/api/ControllerProvider.h" #include #ifndef HAS_DSU #define HAS_DSU 1 #endif // #define DEBUG_DSU_CLIENT struct DSUProviderSettings : public ControllerProviderSettings { std::string ip; uint16 port; DSUProviderSettings() : ip("127.0.0.1"), port(26760) {} DSUProviderSettings(std::string ip, uint16 port) : ip(std::move(ip)), port(port) { } bool operator==(const DSUProviderSettings& s) const { return port == s.port && ip == s.ip; } bool operator==(const ControllerProviderSettings& s) const override { const auto* ptr = dynamic_cast(&s); return ptr && *this == *ptr; } }; class DSUControllerProvider : public ControllerProvider { friend class DSUController; using base_type = ControllerProvider; public: constexpr static int kMaxClients = 8; struct ControllerState { // when was info updated last time std::chrono::steady_clock::time_point last_update{}; uint64_t packet_index = 0; // our packet index count PortInfoData info{}; DataResponseData data{}; MotionSample motion_sample{}; ControllerState& operator=(const PortInfo& port_info); ControllerState& operator=(const DataResponse& data_response); }; DSUControllerProvider(); DSUControllerProvider(const DSUProviderSettings& settings); ~DSUControllerProvider(); inline static InputAPI::Type kAPIType = InputAPI::DSUClient; InputAPI::Type api() const override { return kAPIType; } std::vector> get_controllers() override; bool connect(); bool is_connected(uint8_t index) const; ControllerState get_state(uint8_t index) const; ControllerState get_prev_state(uint8_t index) const; MotionSample get_motion_sample(uint8_t index) const; std::array wait_update(const std::array& indices, size_t timeout) const; bool wait_update(uint8_t index, uint32_t packet_index, size_t timeout) const; uint32_t get_packet_index(uint8_t index) const; // refresh pad info for all pads void request_pad_info(); // refresh pad info for pad with given index void request_pad_info(uint8_t index); void request_version(); void request_pad_data(); void request_pad_data(uint8_t index); private: uint16 m_server_version = 0; std::atomic_bool m_running = false; std::thread m_reader_thread, m_writer_thread; void reader_thread(); void writer_thread(); void integrate_motion(uint8_t index, const DataResponse& data_response); std::mutex m_writer_mutex; std::condition_variable m_writer_cond; uint32 m_uid; boost::asio::io_context m_io_service; boost::asio::ip::udp::endpoint m_receiver_endpoint; boost::asio::ip::udp::socket m_socket; std::array m_state{}; std::array m_prev_state{}; mutable std::array m_mutex; mutable std::array m_wait_cond; std::queue> m_writer_jobs; std::array m_motion_handler; std::array m_last_motion_timestamp{}; }; ================================================ FILE: src/input/api/DSU/DSUMessages.cpp ================================================ #include "input/api/DSU/DSUMessages.h" #include "util/crypto/crc32.h" constexpr uint32_t kMagicClient = 'CUSD'; constexpr uint32_t kMagicServer = 'SUSD'; constexpr uint16_t kProtocolVersion = 1001; MessageHeader::MessageHeader(uint32_t magic, uint32_t uid) : m_magic(magic), m_protocol_version(kProtocolVersion), m_uid(uid) { } void MessageHeader::Finalize(size_t size) { m_packet_size = (uint16_t)(size - sizeof(MessageHeader)); m_crc32 = CRC32(size); } uint32_t MessageHeader::CRC32(size_t size) const { uint32_t tmp, tmp2; tmp = m_crc32; m_crc32 = 0; tmp2 = crc32_calc(this, size); m_crc32 = tmp; return tmp2; } bool MessageHeader::IsClientMessage() const { return m_magic == kMagicClient; } bool MessageHeader::IsServerMessage() const { return m_magic == kMagicServer; } Message::Message(uint32_t magic, uint32_t uid, MessageType type) : MessageHeader(magic, uid), m_message_type(type) { } ClientMessage::ClientMessage(uint32_t uid, MessageType message_type) : Message(kMagicClient, uid, message_type) { } VersionRequest::VersionRequest(uint32_t uid) : ClientMessage(uid, MessageType::Version) { Finalize(sizeof(VersionRequest)); } ListPorts::ListPorts(uint32_t uid, uint32_t num_pads_requests, const std::array& request_indices) : ClientMessage(uid, MessageType::Information), m_count(num_pads_requests), m_indices(request_indices) { Finalize(sizeof(ListPorts)); } DataRequest::DataRequest(uint32_t uid) : ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::AllPads), m_index(0), m_mac_address({}) { Finalize(sizeof(DataRequest)); } DataRequest::DataRequest(uint32_t uid, uint8_t index) : ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::Index), m_index(index), m_mac_address({}) { Finalize(sizeof(DataRequest)); } DataRequest::DataRequest(uint32_t uid, const MACAddress_t& mac_address) : ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::MACAddress), m_index(0), m_mac_address(mac_address) { Finalize(sizeof(DataRequest)); } DataRequest::DataRequest(uint32_t uid, uint8_t index, const MACAddress_t& mac_address) : ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::Index | RegisterFlag::MACAddress), m_index(index), m_mac_address(mac_address) { Finalize(sizeof(DataRequest)); } bool ServerMessage::ValidateCRC32(size_t size) const { return GetCRC32() == CRC32(size); } bool VersionResponse::IsValid() const { return ValidateCRC32(sizeof(VersionResponse)); } bool PortInfo::IsValid() const { return ValidateCRC32(sizeof(PortInfo)); } bool DataResponse::IsValid() const { return ValidateCRC32(sizeof(DataResponse)); } ================================================ FILE: src/input/api/DSU/DSUMessages.h ================================================ #pragma once // https://v1993.github.io/cemuhook-protocol/ #include "Common/enumFlags.h" #include "util/math/vector3.h" #include #include enum class DsState : uint8_t { Disconnected = 0x00, Reserved = 0x01, Connected = 0x02 }; enum class DsConnection : uint8_t { None = 0x00, Usb = 0x01, Bluetooth = 0x02 }; enum class DsModel : uint8_t { None = 0, DS3 = 1, DS4 = 2, Generic = 3 }; enum class DsBattery : uint8_t { None = 0x00, Dying = 0x01, Low = 0x02, Medium = 0x03, High = 0x04, Full = 0x05, Charging = 0xEE, Charged = 0xEF }; enum class RegisterFlag : uint8_t { AllPads = 0x00, Index = 0x01, MACAddress = 0x02 }; ENABLE_BITMASK_OPERATORS(RegisterFlag); enum class MessageType : uint32_t { Version = 0x100000, Information = 0x100001, Data = 0x100002, Rumble = 0x100003, // TODO }; using MACAddress_t = std::array; #pragma pack(push,1) class MessageHeader { public: MessageHeader(uint32_t magic, uint32_t uid); [[nodiscard]] uint16_t GetSize() const { return sizeof(MessageHeader) + m_packet_size; } [[nodiscard]] bool IsClientMessage() const; [[nodiscard]] bool IsServerMessage() const; [[nodiscard]] uint32_t GetCRC32() const { return m_crc32; } protected: void Finalize(size_t size); [[nodiscard]] uint32_t CRC32(size_t size) const; private: uint32_t m_magic; uint16_t m_protocol_version; uint16_t m_packet_size = 0; mutable uint32_t m_crc32 = 0; uint32_t m_uid; }; static_assert(sizeof(MessageHeader) == 0x10); class Message : public MessageHeader { public: Message(uint32_t magic, uint32_t uid, MessageType type); [[nodiscard]] MessageType GetMessageType() const { return m_message_type; } private: MessageType m_message_type; }; static_assert(sizeof(Message) == 0x14); // client messages class ClientMessage : public Message { public: ClientMessage(uint32_t uid, MessageType message_type); }; static_assert(sizeof(ClientMessage) == sizeof(Message)); class VersionRequest : public ClientMessage { public: VersionRequest(uint32_t uid); }; static_assert(sizeof(VersionRequest) == sizeof(ClientMessage)); class ListPorts : public ClientMessage { public: ListPorts(uint32_t uid, uint32_t num_pads_requests, const std::array& request_indices); private: uint32_t m_count; std::array m_indices; }; class DataRequest : public ClientMessage { public: DataRequest(uint32_t uid); DataRequest(uint32_t uid, uint8_t index); DataRequest(uint32_t uid, const MACAddress_t& mac_address); DataRequest(uint32_t uid, uint8_t index, const MACAddress_t& mac_address); private: RegisterFlag m_reg_flags; uint8_t m_index; MACAddress_t m_mac_address; }; // server messages class ServerMessage : public Message { public: ServerMessage() = delete; protected: [[nodiscard]] bool ValidateCRC32(size_t size) const; }; class VersionResponse : public ServerMessage { public: [[nodiscard]] bool IsValid() const; [[nodiscard]] uint16_t GetVersion() const { return m_version; } private: uint16_t m_version; uint8_t padding[2]; }; static_assert(sizeof(VersionResponse) == 0x18); struct PortInfoData { uint8_t index; DsState state; DsModel model; DsConnection connection; MACAddress_t mac_address; DsBattery battery; uint8_t is_active; }; class PortInfo : public ServerMessage { public: [[nodiscard]] bool IsValid() const; [[nodiscard]] const PortInfoData& GetInfo() const { return m_info; } [[nodiscard]] uint8_t GetIndex() const { return m_info.index; } [[nodiscard]] DsState GetState() const { return m_info.state; } [[nodiscard]] DsModel GetModel() const { return m_info.model; } [[nodiscard]] DsConnection GetConnection() const { return m_info.connection; } [[nodiscard]] MACAddress_t GetMacAddress() const { return m_info.mac_address; } [[nodiscard]] DsBattery GetBattery() const { return m_info.battery; } [[nodiscard]] bool IsActive() const { return m_info.is_active != 0; } protected: PortInfoData m_info; }; static_assert(sizeof(PortInfo) == 0x20); struct TouchPoint { uint8_t active; uint8_t index; int16_t x, y; }; static_assert(sizeof(TouchPoint) == 0x6); struct DataResponseData { uint32_t m_packet_index; uint8_t state1; uint8_t state2; uint8_t ps; uint8_t touch; // y values are inverted by convention uint8_t lx, ly; uint8_t rx, ry; uint8_t dpad_left; uint8_t dpad_down; uint8_t dpad_right; uint8_t dpad_up; uint8_t square; uint8_t cross; uint8_t circle; uint8_t triangle; uint8_t r1; uint8_t l1; uint8_t r2; uint8_t l2; TouchPoint tpad1, tpad2; uint64_t motion_timestamp; Vector3f accel; Vector3f gyro; }; class DataResponse : public PortInfo { public: [[nodiscard]] bool IsValid() const; [[nodiscard]] const DataResponseData& GetData() const { return m_data; } uint32_t GetPacketIndex() const { return m_data.m_packet_index; } uint8_t GetState1() const { return m_data.state1; } uint8_t GetState2() const { return m_data.state2; } uint8_t GetPs() const { return m_data.ps; } uint8_t GetTouch() const { return m_data.touch; } uint8_t GetLx() const { return m_data.lx; } uint8_t GetLy() const { return m_data.ly; } uint8_t GetRx() const { return m_data.rx; } uint8_t GetRy() const { return m_data.ry; } uint8_t GetDpadLeft() const { return m_data.dpad_left; } uint8_t GetDpadDown() const { return m_data.dpad_down; } uint8_t GetDpadRight() const { return m_data.dpad_right; } uint8_t GetDpadUp() const { return m_data.dpad_up; } uint8_t GetSquare() const { return m_data.square; } uint8_t GetCross() const { return m_data.cross; } uint8_t GetCircle() const { return m_data.circle; } uint8_t GetTriangle() const { return m_data.triangle; } uint8_t GetR1() const { return m_data.r1; } uint8_t GetL1() const { return m_data.l1; } uint8_t GetR2() const { return m_data.r2; } uint8_t GetL2() const { return m_data.l2; } const TouchPoint& GetTpad1() const { return m_data.tpad1; } const TouchPoint& GetTpad2() const { return m_data.tpad2; } uint64_t GetMotionTimestamp() const { return m_data.motion_timestamp; } const Vector3f& GetAcceleration() const { return m_data.accel; } const Vector3f& GetGyro() const { return m_data.gyro; } private: DataResponseData m_data; }; //static_assert(sizeof(DataResponse) == 0x20); #pragma pack(pop) ================================================ FILE: src/input/api/DirectInput/DirectInputController.cpp ================================================ #include "input/api/DirectInput/DirectInputController.h" #include "WindowSystem.h" DirectInputController::DirectInputController(const GUID& guid) : base_type(StringFromGUID(guid), fmt::format("[{}]", StringFromGUID(guid))), m_guid{ guid } { } DirectInputController::DirectInputController(const GUID& guid, std::string_view display_name) : base_type(StringFromGUID(guid), display_name), m_guid(guid) { } DirectInputController::~DirectInputController() { if (m_device) { m_device->Unacquire(); // TODO: test if really needed // workaround for gamecube controllers crash on release? bool should_release_device = true; if (m_product_guid == GUID{}) { DIDEVICEINSTANCEW info{}; info.dwSize = sizeof(DIDEVICEINSTANCEW); if (SUCCEEDED(m_device->GetDeviceInfo(&info))) { m_product_guid = info.guidProduct; } } // info.guidProduct = {18440079-0000-0000-0000-504944564944} constexpr GUID kGameCubeController = { 0x18440079, 0, 0, {0,0,0x50,0x49,0x44,0x56,0x49,0x44} }; if (kGameCubeController == m_product_guid) should_release_device = false; if (!should_release_device) m_device.Detach(); } } void DirectInputController::save(pugi::xml_node& node) { base_type::save(node); node.append_child("product_guid").append_child(pugi::node_pcdata).set_value( fmt::format("{}", StringFromGUID(m_product_guid)).c_str()); } void DirectInputController::load(const pugi::xml_node& node) { base_type::load(node); if (const auto value = node.child("product_guid")) { if (GUIDFromString(value.child_value(), m_product_guid) && m_product_guid != GUID{} && !is_connected()) { // test if another controller with the same product guid is connectable and replace for(const auto& c : m_provider->get_controllers()) { if(const auto ptr = std::dynamic_pointer_cast(c)) { if (ptr->is_connected() && ptr->get_product_guid() == m_product_guid) { const auto tmp_guid = m_guid; m_guid = ptr->get_guid(); if (connect()) break; // couldn't connect m_guid = tmp_guid; } } } } } } bool DirectInputController::connect() { if (is_connected()) return true; m_effect = nullptr; std::scoped_lock lock(m_mutex); HRESULT hr = m_provider->get_dinput()->CreateDevice(m_guid, &m_device, nullptr); if (FAILED(hr) || m_device == nullptr) return false; DIDEVICEINSTANCEW idi{}; idi.dwSize = sizeof(DIDEVICEINSTANCEW); if (SUCCEEDED(m_device->GetDeviceInfo(&idi))) { // overwrite guid name with "real" display name m_display_name = boost::nowide::narrow(idi.tszProductName); } // set data format if (FAILED(m_device->SetDataFormat(m_provider->get_data_format()))) { return false; } HWND hwndMainWindow = static_cast(WindowSystem::GetWindowInfo().window_main.surface); // set access if (FAILED(m_device->SetCooperativeLevel(hwndMainWindow, DISCL_BACKGROUND | DISCL_EXCLUSIVE))) { if (FAILED(m_device->SetCooperativeLevel(hwndMainWindow, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) { return false; } // rumble can only be used with exclusive access } else { GUID guid_effect = GUID_NULL; // check if constant force is supported HRESULT result = m_device->EnumEffects([](LPCDIEFFECTINFOW eff, LPVOID guid) -> BOOL { *(GUID*)guid = eff->guid; return DIENUM_STOP; }, &guid_effect, DIEFT_CONSTANTFORCE); if (SUCCEEDED(result) && guid_effect != GUID_NULL) { DWORD dwAxes[2] = { DIJOFS_X, DIJOFS_Y }; LONG lDirection[2] = { 1, 0 }; DICONSTANTFORCE constant_force = { DI_FFNOMINALMAX }; // DI_FFNOMINALMAX -> should be max normally?! DIEFFECT effect{}; effect.dwSize = sizeof(DIEFFECT); effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; effect.dwDuration = INFINITE; // DI_SECONDS; effect.dwGain = DI_FFNOMINALMAX; // No scaling effect.dwTriggerButton = DIEB_NOTRIGGER; // Not a button response DIEB_NOTRIGGER DIJOFS_BUTTON0 effect.cAxes = 2; effect.rgdwAxes = dwAxes; effect.rglDirection = lDirection; effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE); effect.lpvTypeSpecificParams = &constant_force; m_device->CreateEffect(guid_effect, &effect, &m_effect, nullptr); } } DIDEVICEINSTANCEW info{}; info.dwSize = sizeof(DIDEVICEINSTANCEW); if (SUCCEEDED(m_device->GetDeviceInfo(&info))) { m_product_guid = info.guidProduct; } std::fill(m_min_axis.begin(), m_min_axis.end(), 0); std::fill(m_max_axis.begin(), m_max_axis.end(), std::numeric_limits::max()); m_device->EnumObjects( [](LPCDIDEVICEOBJECTINSTANCEW lpddoi, LPVOID pvRef) -> BOOL { auto* thisptr = (DirectInputController*)pvRef; const auto instance = DIDFT_GETINSTANCE(lpddoi->dwType); // some tools may use state.rglSlider properties, so they have 8 instead of 6 axis if(instance >= thisptr->m_min_axis.size()) { return DIENUM_CONTINUE; } DIPROPRANGE range{}; range.diph.dwSize = sizeof(range); range.diph.dwHeaderSize = sizeof(range.diph); range.diph.dwHow = DIPH_BYID; range.diph.dwObj = lpddoi->dwType; if (thisptr->m_device->GetProperty(DIPROP_RANGE, &range.diph) == DI_OK) { thisptr->m_min_axis[instance] = range.lMin; thisptr->m_max_axis[instance] = range.lMax; } return DIENUM_CONTINUE; }, this, DIDFT_AXIS); m_device->Acquire(); return true; } bool DirectInputController::is_connected() { std::shared_lock lock(m_mutex); return m_device != nullptr; } bool DirectInputController::has_rumble() { return m_effect != nullptr; } void DirectInputController::start_rumble() { if (!has_rumble()) return; } void DirectInputController::stop_rumble() { if (!has_rumble()) return; } std::string DirectInputController::get_button_name(uint64 button) const { switch(button) { case kAxisXP: return "X+"; case kAxisYP: return "Y+"; case kAxisXN: return "X-"; case kAxisYN: return "Y-"; case kRotationXP: return "RX+"; case kRotationYP: return "RY+"; case kRotationXN: return "RX-"; case kRotationYN: return "RY-"; case kTriggerXP: return "Z+"; case kTriggerYP: return "RZ+"; case kTriggerXN: return "Z-"; case kTriggerYN: return "RZ-"; } return base_type::get_button_name(button); } ControllerState DirectInputController::raw_state() { ControllerState result{}; if (!is_connected()) return result; HRESULT hr = m_device->Poll(); if (FAILED(hr)) { if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) { result.last_state = hr; m_device->Acquire(); } return result; } DIJOYSTATE state{}; hr = m_device->GetDeviceState(sizeof(state), &state); if (FAILED(hr)) { if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) { result.last_state = hr; m_device->Acquire(); } return result; } result.last_state = hr; // buttons for (size_t i = 0; i < std::size(state.rgbButtons); ++i) { if (HAS_BIT(state.rgbButtons[i], 7)) result.buttons.SetButtonState(i, true); } // axis constexpr float kThreshold = 0.001f; float v = (float(state.lX - m_min_axis[0]) / float(m_max_axis[0] - m_min_axis[0])) * 2.0f - 1.0f; if (std::abs(v) >= kThreshold) result.axis.x = v; v = (float(state.lY - m_min_axis[1]) / float(m_max_axis[1] - m_min_axis[1])) * 2.0f - 1.0f; if (std::abs(v) >= kThreshold) result.axis.y = -v; // Right Stick v = (float(state.lRx - m_min_axis[3]) / float(m_max_axis[3] - m_min_axis[3])) * 2.0f - 1.0f; if (std::abs(v) >= kThreshold) result.rotation.x = v; v = (float(state.lRy - m_min_axis[4]) / float(m_max_axis[4] - m_min_axis[4])) * 2.0f - 1.0f; if (std::abs(v) >= kThreshold) result.rotation.y = -v; // Trigger v = (float(state.lZ - m_min_axis[2]) / float(m_max_axis[2] - m_min_axis[2])) * 2.0f - 1.0f; if (std::abs(v) >= kThreshold) result.trigger.x = v; v = (float(state.lRz - m_min_axis[5]) / float(m_max_axis[5] - m_min_axis[5])) * 2.0f - 1.0f; if (std::abs(v) >= kThreshold) result.trigger.y = -v; // dpad const auto pov = state.rgdwPOV[0]; if (pov != static_cast(-1)) { switch (pov) { case 0: result.buttons.SetButtonState(kButtonUp, true); break; case 4500: result.buttons.SetButtonState(kButtonUp, true); // up + right case 9000: result.buttons.SetButtonState(kButtonRight, true); break; case 13500: result.buttons.SetButtonState(kButtonRight, true); // right + down case 18000: result.buttons.SetButtonState(kButtonDown, true); break; case 22500: result.buttons.SetButtonState(kButtonDown, true); // down + left case 27000: result.buttons.SetButtonState(kButtonLeft, true); break; case 31500: result.buttons.SetButtonState(kButtonLeft, true); // left + up result.buttons.SetButtonState(kButtonUp, true); // left + up break; } } return result; } ================================================ FILE: src/input/api/DirectInput/DirectInputController.h ================================================ #pragma once #include "input/api/DirectInput/DirectInputControllerProvider.h" #include "input/api/Controller.h" #include class DirectInputController : public Controller { public: DirectInputController(const GUID& guid); DirectInputController(const GUID& guid, std::string_view display_name); ~DirectInputController() override; std::string_view api_name() const override { static_assert(to_string(InputAPI::DirectInput) == "DirectInput"); return to_string(InputAPI::DirectInput); } InputAPI::Type api() const override { return InputAPI::DirectInput; } void save(pugi::xml_node& node) override; void load(const pugi::xml_node& node) override; bool connect() override; bool is_connected() override; bool has_rumble() override; void start_rumble() override; void stop_rumble() override; std::string get_button_name(uint64 button) const override; const GUID& get_guid() const { return m_guid; } const GUID& get_product_guid() const { return m_product_guid; } protected: ControllerState raw_state() override; private: GUID m_guid; GUID m_product_guid{}; std::shared_mutex m_mutex; Microsoft::WRL::ComPtr m_device; Microsoft::WRL::ComPtr m_effect; std::array m_min_axis{}; std::array m_max_axis{}; }; ================================================ FILE: src/input/api/DirectInput/DirectInputControllerProvider.cpp ================================================ #include "input/api/DirectInput/DirectInputControllerProvider.h" #include "input/api/DirectInput/DirectInputController.h" DirectInputControllerProvider::DirectInputControllerProvider() { const auto r = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8W, (void**)&m_dinput8, nullptr); if (FAILED(r)) { const auto error = GetLastError(); throw std::runtime_error(fmt::format("can't create direct input object (error: {:#x})", error)); } } DirectInputControllerProvider::~DirectInputControllerProvider() { } std::vector> DirectInputControllerProvider::get_controllers() { std::vector> result; m_dinput8->EnumDevices(DI8DEVCLASS_GAMECTRL, [](LPCDIDEVICEINSTANCEW lpddi, LPVOID pvRef) -> BOOL { auto* controllers = (decltype(&result))pvRef; std::string display_name = boost::nowide::narrow(lpddi->tszProductName); controllers->emplace_back(std::make_shared(lpddi->guidInstance, display_name)); return DIENUM_CONTINUE; }, &result, DIEDFL_ALLDEVICES); return result; } LPCDIDATAFORMAT DirectInputControllerProvider::get_data_format() const { return GetdfDIJoystick(); } ================================================ FILE: src/input/api/DirectInput/DirectInputControllerProvider.h ================================================ #pragma once #if BOOST_OS_WINDOWS #define DIRECTINPUT_VERSION 0x0800 #include #include #include "input/api/ControllerProvider.h" #ifndef HAS_DIRECTINPUT #define HAS_DIRECTINPUT 1 #endif class DirectInputControllerProvider : public ControllerProviderBase { public: DirectInputControllerProvider(); ~DirectInputControllerProvider() override; inline static InputAPI::Type kAPIType = InputAPI::DirectInput; InputAPI::Type api() const override { return kAPIType; } std::vector> get_controllers() override; IDirectInput8W* get_dinput() const { return m_dinput8.Get(); } LPCDIDATAFORMAT get_data_format() const; private: HMODULE m_module = nullptr; decltype(&DirectInput8Create) m_DirectInput8Create; decltype(&GetdfDIJoystick) m_GetdfDIJoystick = nullptr; Microsoft::WRL::ComPtr m_dinput8; }; #endif ================================================ FILE: src/input/api/GameCube/GameCubeController.cpp ================================================ #include "input/api/GameCube/GameCubeController.h" #ifdef HAS_GAMECUBE GameCubeController::GameCubeController(uint32 adapter, uint32 index) : base_type(fmt::format("{}_{}", adapter, index), fmt::format("Controller {}", index + 1)), m_adapter(adapter), m_index(index) { // update names if multiple adapters are connected if (adapter > 0) m_display_name = fmt::format("Controller {} ({})", index + 1, adapter); m_settings.axis.range = 1.20f; m_settings.rotation.range = 1.25f; m_settings.trigger.range = 1.07f; } bool GameCubeController::is_connected() { return m_provider->is_connected(m_adapter); } bool GameCubeController::has_rumble() { return m_provider->has_rumble_connected(m_adapter); } void GameCubeController::start_rumble() { if (m_settings.rumble <= 0) return; m_provider->set_rumble_state(m_adapter, m_index, true); } void GameCubeController::stop_rumble() { m_provider->set_rumble_state(m_adapter, m_index, false); } std::string GameCubeController::get_button_name(uint64 button) const { switch (button) { case kButton0: return "A"; case kButton1: return "B"; case kButton2: return "X"; case kButton3: return "Y"; case kButton4: return "Left"; case kButton5: return "Right"; case kButton6: return "Down"; case kButton7: return "Up"; case kButton8: return "Start"; case kButton9: return "Z"; case kButton10: return "Trigger R"; case kButton11: return "Trigger L"; } return base_type::get_button_name(button); } ControllerState GameCubeController::raw_state() { ControllerState result{}; if (!is_connected()) return result; const auto state = m_provider->get_state(m_adapter, m_index); if (state.valid) { for (auto i = 0; i <= kButton11; ++i) { if (HAS_BIT(state.button, i)) { result.buttons.set(i); } } // printf("(%d, %d) - (%d, %d) - (%d, %d)\n", state.lstick_x, state.lstick_y, state.rstick_x, state.rstick_y, state.lstick, state.rstick); result.axis.x = (float)state.lstick_x / std::numeric_limits::max(); result.axis.x = (result.axis.x * 2.0f) - 1.0f; result.axis.y = (float)state.lstick_y / std::numeric_limits::max(); result.axis.y = (result.axis.y * 2.0f) - 1.0f; result.rotation.x = (float)state.rstick_x / std::numeric_limits::max(); result.rotation.x = (result.rotation.x * 2.0f) - 1.0f; result.rotation.y = (float)state.rstick_y / std::numeric_limits::max(); result.rotation.y = (result.rotation.y * 2.0f) - 1.0f; result.trigger.x = (float)state.lstick / std::numeric_limits::max(); result.trigger.y = (float)state.rstick / std::numeric_limits::max(); } return result; } #endif ================================================ FILE: src/input/api/GameCube/GameCubeController.h ================================================ #pragma once #include "input/api/Controller.h" #include "input/api/GameCube/GameCubeControllerProvider.h" #ifdef HAS_GAMECUBE class GameCubeController : public Controller { public: GameCubeController(uint32 adapter, uint32 index); std::string_view api_name() const override { static_assert(to_string(InputAPI::GameCube) == "GameCube"); return to_string(InputAPI::GameCube); } InputAPI::Type api() const override { return InputAPI::GameCube; } bool is_connected() override; bool has_rumble() override; void start_rumble() override; void stop_rumble() override; std::string get_button_name(uint64 button) const override; protected: ControllerState raw_state() override; uint32 m_adapter; uint32 m_index; }; #endif ================================================ FILE: src/input/api/GameCube/GameCubeControllerProvider.cpp ================================================ #include "input/api/GameCube/GameCubeControllerProvider.h" #include "input/api/GameCube/GameCubeController.h" #include "util/libusbWrapper/libusbWrapper.h" #if HAS_GAMECUBE constexpr uint16_t kVendorId = 0x57e, kProductId = 0x337; GameCubeControllerProvider::GameCubeControllerProvider() { m_libusb = libusbWrapper::getInstance(); m_libusb->init(); if(!m_libusb->isAvailable()) throw std::runtime_error("libusbWrapper not available"); m_libusb->p_libusb_init(&m_context); for(auto i = 0; i < kMaxAdapters; ++i) { auto device = fetch_usb_device(i); if(std::get<0>(device)) { m_adapters[i].m_device_handle = std::get<0>(device); m_adapters[i].m_endpoint_reader = std::get<1>(device); m_adapters[i].m_endpoint_writer = std::get<2>(device); } } if (m_libusb->p_libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { m_libusb->p_libusb_hotplug_register_callback(m_context, static_cast(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), LIBUSB_HOTPLUG_NO_FLAGS, kVendorId, kProductId, LIBUSB_HOTPLUG_MATCH_ANY, &GameCubeControllerProvider::hotplug_event, this, &m_callback_handle); } m_running = true; m_reader_thread = std::thread(&GameCubeControllerProvider::reader_thread, this); m_writer_thread = std::thread(&GameCubeControllerProvider::writer_thread, this); } GameCubeControllerProvider::~GameCubeControllerProvider() { if (m_running) { m_running = false; m_writer_thread.join(); m_reader_thread.join(); } if (m_callback_handle) { m_libusb->p_libusb_hotplug_deregister_callback(m_context, m_callback_handle); m_callback_handle = 0; } for (auto& a : m_adapters) { m_libusb->p_libusb_close(a.m_device_handle); } if (m_context) { m_libusb->p_libusb_exit(m_context); m_context = nullptr; } } std::vector GameCubeControllerProvider::get_controllers() { std::vector result; const auto adapter_count = get_adapter_count(); for (uint32 adapter_index = 0; adapter_index < adapter_count && adapter_index < kMaxAdapters; ++adapter_index) { // adapter doesn't tell us which one is actually connected, so we return all of them for (int index = 0; index < 4; ++index) result.emplace_back(std::make_shared(adapter_index, index)); } return result; } uint32 GameCubeControllerProvider::get_adapter_count() const { uint32 adapter_count = 0; libusb_device** devices; const auto count = m_libusb->p_libusb_get_device_list(nullptr, &devices); if (count < 0) { cemuLog_log(LogType::Force, "libusb error {} at libusb_get_device_list: {}", static_cast(count), m_libusb->p_libusb_error_name(static_cast(count))); return adapter_count; } for (ssize_t i = 0; i < count; ++i) { if (!devices[i]) continue; libusb_device_descriptor desc; int ret = m_libusb->p_libusb_get_device_descriptor(devices[i], &desc); if (ret != 0) { cemuLog_log(LogType::Force, "libusb error {} at libusb_get_device_descriptor: {}", ret, m_libusb->p_libusb_error_name(ret)); continue; } if (desc.idVendor != kVendorId || desc.idProduct != kProductId) continue; ++adapter_count; } m_libusb->p_libusb_free_device_list(devices, 1); return adapter_count; } bool GameCubeControllerProvider::has_rumble_connected(uint32 adapter_index) const { if (adapter_index >= m_adapters.size()) return false; std::scoped_lock lock(m_adapters[adapter_index].m_state_mutex); return m_adapters[adapter_index].m_rumble_connected; } bool GameCubeControllerProvider::is_connected(uint32 adapter_index) const { if (adapter_index >= m_adapters.size()) return false; return m_adapters[adapter_index].m_device_handle != nullptr; } void GameCubeControllerProvider::set_rumble_state(uint32 adapter_index, uint32 index, bool state) { if (adapter_index >= m_adapters.size()) return; if (index >= kMaxIndex) return; std::scoped_lock lock(m_writer_mutex); m_adapters[adapter_index].rumble_states[index] = state; m_rumble_changed = true; m_writer_cond.notify_all(); } GameCubeControllerProvider::GCState GameCubeControllerProvider::get_state(uint32 adapter_index, uint32 index) { if (adapter_index >= m_adapters.size()) return {}; if (index >= kMaxIndex) return {}; std::scoped_lock lock(m_adapters[adapter_index].m_state_mutex); return m_adapters[adapter_index].m_states[index]; } #ifdef interface #undef interface #endif std::tuple GameCubeControllerProvider::fetch_usb_device(uint32 adapter) const { std::tuple result{nullptr, 0xFF, 0xFF}; libusb_device** devices; const auto count = m_libusb->p_libusb_get_device_list(nullptr, &devices); if (count < 0) { cemuLog_log(LogType::Force, "libusb error {} at libusb_get_device_list: {}", static_cast(count), m_libusb->p_libusb_error_name(static_cast(count))); return result; } int adapter_index = 0; for (ssize_t i = 0; i < count; ++i) { if (!devices[i]) continue; libusb_device_descriptor desc; int ret = m_libusb->p_libusb_get_device_descriptor(devices[i], &desc); if (ret != 0) { cemuLog_log(LogType::Force, "libusb error {} at libusb_get_device_descriptor: {}", ret, m_libusb->p_libusb_error_name(ret)); continue; } if (desc.idVendor != kVendorId || desc.idProduct != kProductId) continue; if (adapter != adapter_index++) continue; libusb_device_handle* device_handle; ret = m_libusb->p_libusb_open(devices[i], &device_handle); if (ret != 0) { cemuLog_log(LogType::Force, "libusb error {} at libusb_open: {}", ret, m_libusb->p_libusb_error_name(ret)); continue; } if (m_libusb->p_libusb_kernel_driver_active(device_handle, 0) == 1) { ret = m_libusb->p_libusb_detach_kernel_driver(device_handle, 0); if (ret != 0) { cemuLog_log(LogType::Force, "libusb error {} at libusb_detach_kernel_driver: {}", ret, m_libusb->p_libusb_error_name(ret)); m_libusb->p_libusb_close(device_handle); continue; } } ret = m_libusb->p_libusb_claim_interface(device_handle, 0); if (ret != 0) { cemuLog_log(LogType::Force, "libusb error {} at libusb_claim_interface: {}", ret, m_libusb->p_libusb_error_name(ret)); m_libusb->p_libusb_close(device_handle); continue; } libusb_config_descriptor* config = nullptr; m_libusb->p_libusb_get_config_descriptor(devices[i], 0, &config); for (auto ic = 0; ic < config->bNumInterfaces; ic++) { const auto& interface = config->interface[ic]; for (auto j = 0; j < interface.num_altsetting; j++) { const auto& interface_desc = interface.altsetting[j]; for (auto k = 0; k < interface_desc.bNumEndpoints; k++) { const auto& endpoint = interface_desc.endpoint[k]; if (endpoint.bEndpointAddress & LIBUSB_ENDPOINT_IN) std::get<1>(result) = endpoint.bEndpointAddress; else std::get<2>(result) = endpoint.bEndpointAddress; } } } m_libusb->p_libusb_free_config_descriptor(config); // start polling int size = 0; uint8_t payload = 0x13; m_libusb->p_libusb_interrupt_transfer(device_handle, std::get<2>(result), &payload, sizeof(payload), &size, 25); std::get<0>(result) = device_handle; break; } m_libusb->p_libusb_free_device_list(devices, 1); return result; } void GameCubeControllerProvider::reader_thread() { SetThreadName("GCControllerAdapter::reader_thread"); while (m_running.load(std::memory_order_relaxed)) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::yield(); for(auto& adapter : m_adapters) { if (!adapter.m_device_handle) continue; std::array data{}; int read; const int result = m_libusb->p_libusb_interrupt_transfer(adapter.m_device_handle, adapter.m_endpoint_reader, data.data(), static_cast(data.size()), &read, 25); if (result == 0) { /* byte 1 0x10 NORMAL STATE 0x20 WAVEBIRD STATE 0x04 RUMBLE POWER */ for (int i = 0; i < 4; ++i) { GCState state; state.valid = true; state.button = *(uint16*)&data[1 + (i * 9) + 1]; state.lstick_x = data[1 + (i * 9) + 3]; state.lstick_y = data[1 + (i * 9) + 4]; state.rstick_x = data[1 + (i * 9) + 5]; state.rstick_y = data[1 + (i * 9) + 6]; state.lstick = data[1 + (i * 9) + 7]; state.rstick = data[1 + (i * 9) + 8]; std::scoped_lock lock(adapter.m_state_mutex); adapter.m_rumble_connected = HAS_FLAG(data[1], 4); adapter.m_states[i] = state; } } else if (result == LIBUSB_ERROR_NO_DEVICE || result == LIBUSB_ERROR_IO) { cemuLog_log(LogType::Force, "libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)); if (const auto handle = adapter.m_device_handle.exchange(nullptr)) m_libusb->p_libusb_close(handle); } else { cemuLog_log(LogType::Force, "libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)); } } } } void GameCubeControllerProvider::writer_thread() { SetThreadName("GCControllerAdapter::writer_thread"); std::array, kMaxAdapters> rumble_states{}; while (m_running.load(std::memory_order_relaxed)) { std::unique_lock lock(m_writer_mutex); if (!m_rumble_changed && m_writer_cond.wait_for(lock, std::chrono::milliseconds(250)) == std::cv_status::timeout) { if (!m_running) return; continue; } bool cmd_sent = false; for (size_t i = 0; i < kMaxAdapters; ++i) { auto& adapter = m_adapters[i]; if (!adapter.m_device_handle) continue; if (adapter.rumble_states == rumble_states[i]) continue; rumble_states[i] = adapter.rumble_states; m_rumble_changed = false; lock.unlock(); std::array rumble{ 0x11, rumble_states[i][0],rumble_states[i][1],rumble_states[i][2], rumble_states[i][3] }; int written; const int result = m_libusb->p_libusb_interrupt_transfer(adapter.m_device_handle, adapter.m_endpoint_writer, rumble.data(), static_cast(rumble.size()), &written, 25); if (result != 0) { cemuLog_log(LogType::Force, "libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)); } cmd_sent = true; lock.lock(); } if(cmd_sent) { lock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } } int GameCubeControllerProvider::hotplug_event(libusb_context* ctx, libusb_device* dev, libusb_hotplug_event event, void* user_data) { auto* thisptr = static_cast(user_data); if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) { for (auto i = 0; i < kMaxAdapters; ++i) { if (thisptr->m_adapters[i].m_device_handle) continue; auto device = thisptr->fetch_usb_device(i); if (std::get<0>(device)) { thisptr->m_adapters[i].m_endpoint_reader = std::get<1>(device); thisptr->m_adapters[i].m_endpoint_writer = std::get<2>(device); thisptr->m_adapters[i].m_device_handle = std::get<0>(device); } } } /*else { const auto device_handle = thisptr->m_device_handle.exchange(nullptr); if (device_handle) thisptr->m_libusb->p_libusb_close(device_handle); }*/ return 0; } #endif ================================================ FILE: src/input/api/GameCube/GameCubeControllerProvider.h ================================================ #pragma once #include "util/libusbWrapper/libusbWrapper.h" #include "input/api/ControllerProvider.h" #ifdef HAS_GAMECUBE class GameCubeControllerProvider : public ControllerProviderBase { friend class DSUController; public: constexpr static size_t kMaxAdapters = 4; constexpr static size_t kMaxIndex = 4; GameCubeControllerProvider(); ~GameCubeControllerProvider(); inline static InputAPI::Type kAPIType = InputAPI::GameCube; InputAPI::Type api() const override { return kAPIType; } std::vector> get_controllers() override; uint32 get_adapter_count() const; bool has_rumble_connected(uint32 adapter_index) const; bool is_connected(uint32 adapter_index) const; void set_rumble_state(uint32 adapter_index, uint32 index, bool state); struct GCState { bool valid = false; uint16 button = 0; uint8 lstick_x = 0; uint8 lstick_y = 0; uint8 rstick_x = 0; uint8 rstick_y = 0; uint8 lstick = 0; uint8 rstick = 0; }; GCState get_state(uint32 adapter_index, uint32 index); private: std::shared_ptr m_libusb; libusb_context* m_context = nullptr; std::atomic_bool m_running = false; std::thread m_reader_thread, m_writer_thread; void reader_thread(); void writer_thread(); // handle, endpoint_reader, endpoint_writer std::tuple fetch_usb_device(uint32 adapter) const; std::mutex m_writer_mutex; std::condition_variable m_writer_cond; bool m_rumble_changed = false; struct Adapter { std::atomic m_device_handle{}; uint8 m_endpoint_reader = 0xFF, m_endpoint_writer = 0xFF; mutable std::mutex m_state_mutex; std::array m_states{}; bool m_rumble_connected = false; std::array rumble_states{}; }; std::array m_adapters; libusb_hotplug_callback_handle m_callback_handle = 0; static int hotplug_event(struct libusb_context* ctx, struct libusb_device* dev, libusb_hotplug_event event, void* user_data); }; #endif ================================================ FILE: src/input/api/InputAPI.h ================================================ #pragma once #include "util/helpers/helpers.h" namespace InputAPI { enum Type { Keyboard, SDLController, XInput, DirectInput, DSUClient, GameCube, Wiimote, WGIGamepad, WGIRawController, MAX }; constexpr std::string_view to_string(Type type) { switch (type) { case Keyboard: return "Keyboard"; case DirectInput: return "DirectInput"; case XInput: return "XInput"; case Wiimote: return "Wiimote"; case GameCube: return "GameCube"; case DSUClient: return "DSUController"; case WGIGamepad: return "WGIGamepad"; case WGIRawController: return "WGIRawController"; case SDLController: return "SDLController"; default: break; } throw std::runtime_error(fmt::format("unknown input api: {}", to_underlying(type))); } constexpr Type from_string(std::string_view str) { if (str == to_string(Keyboard)) return Keyboard; else if (str == to_string(DirectInput)) return DirectInput; else if (str == to_string(XInput)) return XInput; else if (str == to_string(Wiimote)) return Wiimote; else if (str == to_string(GameCube)) return GameCube; else if (str == to_string(DSUClient)) return DSUClient; else if (str == to_string(SDLController)) return SDLController; else if (str == "DSU") // legacy return DSUClient; //else if (str == "WGIGamepad") // return WGIGamepad; // //else if (str == "WGIRawController") // return WGIRawController; throw std::runtime_error(fmt::format("unknown input api: {}", str)); } } ================================================ FILE: src/input/api/Keyboard/KeyboardController.cpp ================================================ #include #include "input/api/Keyboard/KeyboardController.h" #include "WindowSystem.h" KeyboardController::KeyboardController() : base_type("keyboard", "Keyboard") { } std::string KeyboardController::get_button_name(uint64 button) const { return WindowSystem::GetKeyCodeName(button); } ControllerState KeyboardController::raw_state() { ControllerState result{}; if (WindowSystem::GetWindowInfo().debugger_focused) return result; boost::container::small_vector pressedKeys; WindowSystem::GetWindowInfo().iter_keystates([&pressedKeys](const std::pair& keyState) { if (keyState.second) pressedKeys.emplace_back(keyState.first); }); result.buttons.SetPressedButtons(pressedKeys); return result; } ================================================ FILE: src/input/api/Keyboard/KeyboardController.h ================================================ #pragma once #include "input/api/Keyboard/KeyboardControllerProvider.h" #include "input/api/Controller.h" class KeyboardController : public Controller { public: KeyboardController(); std::string_view api_name() const override // TODO: use constexpr virtual function with c++20 { static_assert(to_string(InputAPI::Keyboard) == "Keyboard"); return to_string(InputAPI::Keyboard); } InputAPI::Type api() const override { return InputAPI::Keyboard; } bool is_connected() override { return true; } bool has_axis() const override { return false; } std::string get_button_name(uint64 button) const override; protected: ControllerState raw_state() override; }; ================================================ FILE: src/input/api/Keyboard/KeyboardControllerProvider.cpp ================================================ #include "input/api/Keyboard/KeyboardControllerProvider.h" #include "input/api/Keyboard/KeyboardController.h" std::vector> KeyboardControllerProvider::get_controllers() { std::vector> result; result.emplace_back(std::make_shared()); return result; } ================================================ FILE: src/input/api/Keyboard/KeyboardControllerProvider.h ================================================ #pragma once #include "input/api/ControllerProvider.h" #ifndef HAS_KEYBOARD #define HAS_KEYBOARD 1 #endif class KeyboardControllerProvider : public ControllerProviderBase { friend class KeyboardController; public: inline static InputAPI::Type kAPIType = InputAPI::Keyboard; InputAPI::Type api() const override { return kAPIType; } std::vector> get_controllers() override; }; ================================================ FILE: src/input/api/SDL/SDLController.cpp ================================================ #include "input/api/SDL/SDLController.h" #include "input/api/SDL/SDLControllerProvider.h" SDLController::SDLController(const SDL_JoystickGUID& guid, size_t guid_index) : base_type(fmt::format("{}_", guid_index), fmt::format("Controller {}", guid_index + 1)), m_guid_index(guid_index), m_guid(guid) { char tmp[64]; SDL_JoystickGetGUIDString(m_guid, tmp, std::size(tmp)); m_uuid += tmp; } SDLController::SDLController(const SDL_JoystickGUID& guid, size_t guid_index, std::string_view display_name) : base_type(fmt::format("{}_", guid_index), display_name), m_guid_index(guid_index), m_guid(guid) { char tmp[64]; SDL_JoystickGetGUIDString(m_guid, tmp, std::size(tmp)); m_uuid += tmp; } SDLController::~SDLController() { if (m_controller) SDL_GameControllerClose(m_controller); } bool SDLController::is_connected() { std::scoped_lock lock(m_controller_mutex); if (!m_controller) { return false; } if (!SDL_GameControllerGetAttached(m_controller)) { m_controller = nullptr; return false; } return true; } bool SDLController::connect() { if (is_connected()) { return true; } m_has_rumble = false; const auto index = m_provider->get_index(m_guid_index, m_guid); std::scoped_lock lock(m_controller_mutex); m_diid = SDL_JoystickGetDeviceInstanceID(index); if (m_diid == -1) return false; m_controller = SDL_GameControllerOpen(index); if (!m_controller) return false; if (const char* name = SDL_GameControllerName(m_controller)) m_display_name = name; for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i) { m_buttons[i] = SDL_GameControllerHasButton(m_controller, (SDL_GameControllerButton)i); } for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; ++i) { m_axis[i] = SDL_GameControllerHasAxis(m_controller, (SDL_GameControllerAxis)i); } if (SDL_GameControllerHasSensor(m_controller, SDL_SENSOR_ACCEL)) { m_has_accel = true; SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_ACCEL, SDL_TRUE); } if (SDL_GameControllerHasSensor(m_controller, SDL_SENSOR_GYRO)) { m_has_gyro = true; SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_GYRO, SDL_TRUE); } m_has_rumble = SDL_GameControllerRumble(m_controller, 0, 0, 0) == 0; return true; } void SDLController::start_rumble() { std::scoped_lock lock(m_controller_mutex); if (is_connected() && !m_has_rumble) return; if (m_settings.rumble <= 0) return; SDL_GameControllerRumble(m_controller, (Uint16)(m_settings.rumble * 0xFFFF), (Uint16)(m_settings.rumble * 0xFFFF), 5 * 1000); } void SDLController::stop_rumble() { std::scoped_lock lock(m_controller_mutex); if (is_connected() && !m_has_rumble) return; SDL_GameControllerRumble(m_controller, 0, 0, 0); } MotionSample SDLController::get_motion_sample() { if (is_connected() && has_motion()) { return m_provider->motion_sample(m_diid); } return {}; } std::string SDLController::get_button_name(uint64 button) const { if (const char* name = SDL_GameControllerGetStringForButton((SDL_GameControllerButton)button)) return name; return base_type::get_button_name(button); } ControllerState SDLController::raw_state() { ControllerState result{}; std::scoped_lock lock(m_controller_mutex); if (!is_connected()) return result; for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i) { if (m_buttons[i] && SDL_GameControllerGetButton(m_controller, (SDL_GameControllerButton)i)) result.buttons.SetButtonState(i, true); } if (m_axis[SDL_CONTROLLER_AXIS_LEFTX]) result.axis.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_LEFTX) / 32767.0f; if (m_axis[SDL_CONTROLLER_AXIS_LEFTY]) result.axis.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_LEFTY) / 32767.0f; if (m_axis[SDL_CONTROLLER_AXIS_RIGHTX]) result.rotation.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_RIGHTX) / 32767.0f; if (m_axis[SDL_CONTROLLER_AXIS_RIGHTY]) result.rotation.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_RIGHTY) / 32767.0f; if (m_axis[SDL_CONTROLLER_AXIS_TRIGGERLEFT]) result.trigger.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) / 32767.0f; if (m_axis[SDL_CONTROLLER_AXIS_TRIGGERRIGHT]) result.trigger.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) / 32767.0f; return result; } ================================================ FILE: src/input/api/SDL/SDLController.h ================================================ #pragma once #include "input/api/Controller.h" #include "input/api/SDL/SDLControllerProvider.h" #include class SDLController : public Controller { public: SDLController(const SDL_JoystickGUID& guid, size_t guid_index); SDLController(const SDL_JoystickGUID& guid, size_t guid_index, std::string_view display_name); ~SDLController() override; std::string_view api_name() const override { static_assert(to_string(InputAPI::SDLController) == "SDLController"); return to_string(InputAPI::SDLController); } InputAPI::Type api() const override { return InputAPI::SDLController; } bool is_connected() override; bool connect() override; bool has_motion() override { return m_has_gyro && m_has_accel; } bool has_rumble() override { return m_has_rumble; } void start_rumble() override; void stop_rumble() override; MotionSample get_motion_sample() override; std::string get_button_name(uint64 button) const override; const SDL_JoystickGUID& get_guid() const { return m_guid; } constexpr static SDL_JoystickGUID kLeftJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00,0x68 ,0x00 }; constexpr static SDL_JoystickGUID kRightJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 }; constexpr static SDL_JoystickGUID kSwitchProController{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 }; protected: ControllerState raw_state() override; private: inline static SDL_JoystickGUID kEmptyGUID{}; size_t m_guid_index; SDL_JoystickGUID m_guid; std::recursive_mutex m_controller_mutex; SDL_GameController* m_controller = nullptr; SDL_JoystickID m_diid = -1; bool m_has_gyro = false; bool m_has_accel = false; bool m_has_rumble = false; std::array m_buttons{}; std::array m_axis{}; }; ================================================ FILE: src/input/api/SDL/SDLControllerProvider.cpp ================================================ #include "input/api/SDL/SDLControllerProvider.h" #include "input/api/SDL/SDLController.h" #include "util/helpers/TempState.h" #include #include struct SDL_JoystickGUIDHash { std::size_t operator()(const SDL_JoystickGUID& guid) const { return boost::hash_value(guid.data); } }; SDLControllerProvider::SDLControllerProvider() { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STADIA, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_LUNA, "1"); if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS) < 0) throw std::runtime_error(fmt::format("couldn't initialize SDL: {}", SDL_GetError())); if (SDL_GameControllerEventState(SDL_ENABLE) < 0) { cemuLog_log(LogType::Force, "Couldn't enable SDL gamecontroller event polling: {}", SDL_GetError()); } m_running = true; m_thread = std::thread(&SDLControllerProvider::event_thread, this); } SDLControllerProvider::~SDLControllerProvider() { if (m_running) { m_running = false; // wake the thread with a quit event if it's currently waiting for events SDL_Event evt; evt.type = SDL_QUIT; SDL_PushEvent(&evt); // wait until thread exited m_thread.join(); } SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS); } std::vector> SDLControllerProvider::get_controllers() { std::vector> result; std::unordered_map guid_counter; TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks); for (int i = 0; i < SDL_NumJoysticks(); ++i) { if (SDL_JoystickGetDeviceType(i) == SDL_JOYSTICK_TYPE_GAMECONTROLLER) { const auto guid = SDL_JoystickGetDeviceGUID(i); const auto it = guid_counter.try_emplace(guid, 0); if (auto* controller = SDL_GameControllerOpen(i)) { const char* name = SDL_GameControllerName(controller); result.emplace_back(std::make_shared(guid, it.first->second, name)); SDL_GameControllerClose(controller); } else result.emplace_back(std::make_shared(guid, it.first->second)); ++it.first->second; } } return result; } int SDLControllerProvider::get_index(size_t guid_index, const SDL_JoystickGUID& guid) const { size_t index = 0; TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks); for (int i = 0; i < SDL_NumJoysticks(); ++i) { if (SDL_JoystickGetDeviceType(i) == SDL_JOYSTICK_TYPE_GAMECONTROLLER) { if(guid == SDL_JoystickGetDeviceGUID(i)) { if (index == guid_index) { return i; } ++index; } } } return -1; } MotionSample SDLControllerProvider::motion_sample(int diid) { std::scoped_lock lock(m_motion_data_mtx[diid]); return m_motion_data[diid]; } void SDLControllerProvider::event_thread() { SetThreadName("SDL_events"); while (m_running.load(std::memory_order_relaxed)) { SDL_Event event{}; SDL_WaitEvent(&event); switch (event.type) { case SDL_QUIT: m_running = false; return; case SDL_CONTROLLERAXISMOTION: /**< Game controller axis motion */ { break; } case SDL_CONTROLLERBUTTONDOWN: /**< Game controller button pressed */ { break; } case SDL_CONTROLLERBUTTONUP: /**< Game controller button released */ { break; } case SDL_CONTROLLERDEVICEADDED: /**< A new Game controller has been inserted into the system */ { InputManager::instance().on_device_changed(); break; } case SDL_CONTROLLERDEVICEREMOVED: /**< An opened Game controller has been removed */ { InputManager::instance().on_device_changed(); break; } case SDL_CONTROLLERDEVICEREMAPPED: /**< The controller mapping was updated */ { break; } case SDL_CONTROLLERTOUCHPADDOWN: /**< Game controller touchpad was touched */ { break; } case SDL_CONTROLLERTOUCHPADMOTION: /**< Game controller touchpad finger was moved */ { break; } case SDL_CONTROLLERTOUCHPADUP: /**< Game controller touchpad finger was lifted */ { break; } case SDL_CONTROLLERSENSORUPDATE: /**< Game controller sensor was updated */ { const auto index = event.csensor.which; const auto ts = event.csensor.timestamp; auto& motionTracking = m_motion_tracking[index]; if (event.csensor.sensor == SDL_SENSOR_ACCEL) { const auto dif = ts - motionTracking.lastTimestampAccel; if (dif <= 0) break; if (dif >= 10000) { motionTracking.hasAcc = false; motionTracking.hasGyro = false; motionTracking.lastTimestampAccel = ts; break; } motionTracking.lastTimestampAccel = ts; motionTracking.acc[0] = -event.csensor.data[0] / 9.81f; motionTracking.acc[1] = -event.csensor.data[1] / 9.81f; motionTracking.acc[2] = -event.csensor.data[2] / 9.81f; motionTracking.hasAcc = true; } if (event.csensor.sensor == SDL_SENSOR_GYRO) { const auto dif = ts - motionTracking.lastTimestampGyro; if (dif <= 0) break; if (dif >= 10000) { motionTracking.hasAcc = false; motionTracking.hasGyro = false; motionTracking.lastTimestampGyro = ts; break; } motionTracking.lastTimestampGyro = ts; motionTracking.gyro[0] = event.csensor.data[0]; motionTracking.gyro[1] = -event.csensor.data[1]; motionTracking.gyro[2] = -event.csensor.data[2]; motionTracking.hasGyro = true; } if (motionTracking.hasAcc && motionTracking.hasGyro) { auto ts = std::max(motionTracking.lastTimestampGyro, motionTracking.lastTimestampAccel); if (ts > motionTracking.lastTimestampIntegrate) { const auto tsDif = ts - motionTracking.lastTimestampIntegrate; motionTracking.lastTimestampIntegrate = ts; float tsDifD = (float)tsDif / 1000.0f; if (tsDifD >= 1.0f) tsDifD = 1.0f; m_motion_handler[index].processMotionSample(tsDifD, motionTracking.gyro.x, motionTracking.gyro.y, motionTracking.gyro.z, motionTracking.acc.x, -motionTracking.acc.y, -motionTracking.acc.z); std::scoped_lock lock(m_motion_data_mtx[index]); m_motion_data[index] = m_motion_handler[index].getMotionSample(); } motionTracking.hasAcc = false; motionTracking.hasGyro = false; } break; } } } } ================================================ FILE: src/input/api/SDL/SDLControllerProvider.h ================================================ #pragma once #include #include "input/motion/MotionHandler.h" #include "input/api/ControllerProvider.h" #ifndef HAS_SDL #define HAS_SDL 1 #endif static bool operator==(const SDL_JoystickGUID& g1, const SDL_JoystickGUID& g2) { return memcmp(&g1, &g2, sizeof(SDL_JoystickGUID)) == 0; } class SDLControllerProvider : public ControllerProviderBase { friend class SDLController; public: SDLControllerProvider(); ~SDLControllerProvider(); inline static InputAPI::Type kAPIType = InputAPI::SDLController; InputAPI::Type api() const override { return kAPIType; } std::vector> get_controllers() override; int get_index(size_t guid_index, const SDL_JoystickGUID& guid) const; MotionSample motion_sample(int diid); private: void event_thread(); std::atomic_bool m_running = false; std::thread m_thread; std::array m_motion_handler{}; std::array m_motion_data{}; std::array m_motion_data_mtx{}; struct MotionInfoTracking { uint64 lastTimestampGyro{}; uint64 lastTimestampAccel{}; uint64 lastTimestampIntegrate{}; bool hasGyro{}; bool hasAcc{}; glm::vec3 gyro{}; glm::vec3 acc{}; }; std::array m_motion_tracking{}; }; ================================================ FILE: src/input/api/Wiimote/NativeWiimoteController.cpp ================================================ #include "input/api/Wiimote/NativeWiimoteController.h" #include "input/api/Wiimote/WiimoteControllerProvider.h" #include NativeWiimoteController::NativeWiimoteController(size_t index) : base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1)), m_index(index) { m_settings.motion = true; } void NativeWiimoteController::save(pugi::xml_node& node) { base_type::save(node); node.append_child("packet_delay").append_child(pugi::node_pcdata).set_value( fmt::format("{}", m_packet_delay).c_str()); } void NativeWiimoteController::load(const pugi::xml_node& node) { base_type::load(node); if (const auto value = node.child("packet_delay")) m_packet_delay = ConvertString(value.child_value()); } bool NativeWiimoteController::connect() { if (is_connected()) return true; if (!m_provider->is_registered_device(m_index)) { m_provider->get_controllers(); } if (m_provider->is_connected(m_index)) { m_provider->set_packet_delay(m_index, m_packet_delay); m_provider->set_led(m_index, m_player_index); return true; } return false; } bool NativeWiimoteController::is_connected() { if (m_provider->is_connected(m_index)) { m_provider->set_packet_delay(m_index, m_packet_delay); return true; } return false; } void NativeWiimoteController::set_player_index(size_t player_index) { m_player_index = player_index; m_provider->set_led(m_index, m_player_index); } NativeWiimoteController::Extension NativeWiimoteController::get_extension() const { Extension result = None; const auto ext = m_provider->get_state(m_index).m_extension; if (std::holds_alternative(ext)) result = Nunchuck; else if (std::holds_alternative(ext)) result = Classic; return result; } bool NativeWiimoteController::is_mpls_attached() const { return m_provider->get_state(m_index).m_motion_plus.has_value(); } bool NativeWiimoteController::has_position() { const auto state = m_provider->get_state(m_index); return std::any_of(state.ir_camera.dots.cbegin(), state.ir_camera.dots.cend(), [](const IRDot& v) { return v.visible; }); } glm::vec2 NativeWiimoteController::get_position() { const auto state = m_provider->get_state(m_index); return state.ir_camera.position; } glm::vec2 NativeWiimoteController::get_prev_position() { const auto state = m_provider->get_state(m_index); return state.ir_camera.m_prev_position; } PositionVisibility NativeWiimoteController::GetPositionVisibility() { const auto state = m_provider->get_state(m_index); return state.ir_camera.m_positionVisibility; } bool NativeWiimoteController::has_low_battery() { const auto state = m_provider->get_state(m_index); return HAS_FLAG(state.flags, kBatteryEmpty); } void NativeWiimoteController::start_rumble() { if (m_settings.rumble < 1.0f) { return; } m_provider->set_rumble(m_index, true); } void NativeWiimoteController::stop_rumble() { m_provider->set_rumble(m_index, false); } MotionSample NativeWiimoteController::get_motion_sample() { const auto state = m_provider->get_state(m_index); return state.motion_sample; } MotionSample NativeWiimoteController::get_nunchuck_motion_sample() const { const auto state = m_provider->get_state(m_index); if (std::holds_alternative(state.m_extension)) { return std::get(state.m_extension).motion_sample; } return {}; } std::string NativeWiimoteController::get_button_name(uint64 button) const { switch (button) { case kWiimoteButton_A: return "A"; case kWiimoteButton_B: return "B"; case kWiimoteButton_One: return "1"; case kWiimoteButton_Two: return "2"; case kWiimoteButton_Plus: return "+"; case kWiimoteButton_Minus: return "-"; case kWiimoteButton_Home: return "HOME"; case kWiimoteButton_Up: return "UP"; case kWiimoteButton_Down: return "DOWN"; case kWiimoteButton_Left: return "LEFT"; case kWiimoteButton_Right: return "RIGHT"; // nunchuck case kWiimoteButton_C: return "C"; case kWiimoteButton_Z: return "Z"; // classic case kHighestWiimote + kClassicButton_A: return "A"; case kHighestWiimote + kClassicButton_B: return "B"; case kHighestWiimote + kClassicButton_Y: return "Y"; case kHighestWiimote + kClassicButton_X: return "X"; case kHighestWiimote + kClassicButton_Plus: return "+"; case kHighestWiimote + kClassicButton_Minus: return "-"; case kHighestWiimote + kClassicButton_Home: return "HOME"; case kHighestWiimote + kClassicButton_Up: return "UP"; case kHighestWiimote + kClassicButton_Down: return "DOWN"; case kHighestWiimote + kClassicButton_Left: return "LEFT"; case kHighestWiimote + kClassicButton_Right: return "RIGHT"; case kHighestWiimote + kClassicButton_L: return "L"; case kHighestWiimote + kClassicButton_R: return "R"; case kHighestWiimote + kClassicButton_ZL: return "ZL"; case kHighestWiimote + kClassicButton_ZR: return "ZR"; } return base_type::get_button_name(button); } uint32 NativeWiimoteController::get_packet_delay() { m_packet_delay = m_provider->get_packet_delay(m_index); return m_packet_delay; } void NativeWiimoteController::set_packet_delay(uint32 delay) { m_packet_delay = delay; m_provider->set_packet_delay(m_index, delay); } ControllerState NativeWiimoteController::raw_state() { ControllerState result{}; if (!is_connected()) return result; const auto state = m_provider->get_state(m_index); for (int i = 0; i < std::numeric_limits::digits; i++) result.buttons.SetButtonState(i, (state.buttons & (1 << i)) != 0); if (std::holds_alternative(state.m_extension)) { const auto nunchuck = std::get(state.m_extension); if (nunchuck.c) result.buttons.SetButtonState(kWiimoteButton_C, true); if (nunchuck.z) result.buttons.SetButtonState(kWiimoteButton_Z, true); result.axis = nunchuck.axis; } else if (std::holds_alternative(state.m_extension)) { const auto classic = std::get(state.m_extension); uint64 buttons = (uint64)classic.buttons << kHighestWiimote; for (int i = 0; i < std::numeric_limits::digits; i++) { // OR with base buttons if((buttons & (1 << i))) result.buttons.SetButtonState(i, true); } result.axis = classic.left_axis; result.rotation = classic.right_axis; result.trigger = classic.trigger; } return result; } ================================================ FILE: src/input/api/Wiimote/NativeWiimoteController.h ================================================ #pragma once #include "input/api/Controller.h" #include "input/api/Wiimote/WiimoteControllerProvider.h" // todo: find better name because of emulated nameclash class NativeWiimoteController : public Controller { public: NativeWiimoteController(size_t index); enum Extension { None, Nunchuck, Classic, MotionPlus, }; std::string_view api_name() const override { static_assert(to_string(InputAPI::Wiimote) == "Wiimote"); return to_string(InputAPI::Wiimote); } InputAPI::Type api() const override { return InputAPI::Wiimote; } void save(pugi::xml_node& node) override; void load(const pugi::xml_node& node) override; bool connect() override; bool is_connected() override; void set_player_index(size_t player_index); Extension get_extension() const; bool is_mpls_attached() const; ControllerState raw_state() override; bool has_position() override; glm::vec2 get_position() override; glm::vec2 get_prev_position() override; PositionVisibility GetPositionVisibility() override; bool has_motion() override { return true; } bool has_rumble() override { return true; } bool has_battery() override { return true; } bool has_low_battery() override; void start_rumble() override; void stop_rumble() override; MotionSample get_motion_sample() override; MotionSample get_nunchuck_motion_sample() const; std::string get_button_name(uint64 button) const override; uint32 get_packet_delay(); void set_packet_delay(uint32 delay); private: size_t m_index; size_t m_player_index = 0; uint32 m_packet_delay = WiimoteControllerProvider::kDefaultPacketDelay; }; ================================================ FILE: src/input/api/Wiimote/WiimoteControllerProvider.cpp ================================================ #include "input/api/Wiimote/WiimoteControllerProvider.h" #include "input/api/Wiimote/NativeWiimoteController.h" #include "input/api/Wiimote/WiimoteMessages.h" #ifdef HAS_HIDAPI #include "input/api/Wiimote/hidapi/HidapiWiimote.h" #endif #ifdef HAS_BLUEZ #include "input/api/Wiimote/l2cap/L2CapWiimote.h" #endif #include #include WiimoteControllerProvider::WiimoteControllerProvider() : m_running(true) { m_reader_thread = std::thread(&WiimoteControllerProvider::reader_thread, this); m_writer_thread = std::thread(&WiimoteControllerProvider::writer_thread, this); m_connectionThread = std::thread(&WiimoteControllerProvider::connectionThread, this); } WiimoteControllerProvider::~WiimoteControllerProvider() { if (m_running) { m_running = false; m_writer_thread.join(); m_reader_thread.join(); m_connectionThread.join(); } } std::vector> WiimoteControllerProvider::get_controllers() { m_connectedDeviceMutex.lock(); auto devices = m_connectedDevices; m_connectedDeviceMutex.unlock(); std::scoped_lock lock(m_device_mutex); for (auto& device : devices) { const auto writeable = device->write_data({kStatusRequest, 0x00}); if (!writeable) continue; bool isDuplicate = false; ssize_t lowestReplaceableIndex = -1; for (ssize_t i = m_wiimotes.size() - 1; i >= 0; --i) { const auto& wiimoteDevice = m_wiimotes[i].device; if (wiimoteDevice) { if (*wiimoteDevice == *device) { isDuplicate = true; break; } continue; } lowestReplaceableIndex = i; } if (isDuplicate) continue; if (lowestReplaceableIndex != -1) m_wiimotes.replace(lowestReplaceableIndex, std::make_unique(device)); else m_wiimotes.push_back(std::make_unique(device)); } std::vector> result; result.reserve(m_wiimotes.size()); for (size_t i = 0; i < m_wiimotes.size(); ++i) { result.emplace_back(std::make_shared(i)); } return result; } bool WiimoteControllerProvider::is_connected(size_t index) { std::shared_lock lock(m_device_mutex); return index < m_wiimotes.size() && m_wiimotes[index].device; } bool WiimoteControllerProvider::is_registered_device(size_t index) { std::shared_lock lock(m_device_mutex); return index < m_wiimotes.size(); } void WiimoteControllerProvider::set_rumble(size_t index, bool state) { std::shared_lock lock(m_device_mutex); if (index >= m_wiimotes.size()) return; m_wiimotes[index].rumble = state; lock.unlock(); send_packet(index, { kStatusRequest, 0x00 }); } void WiimoteControllerProvider::request_status(size_t index) { send_packet(index, {kStatusRequest, 0x00}); } void WiimoteControllerProvider::set_led(size_t index, size_t player_index) { uint8 mask = 0; mask |= 1 << (4 + (player_index % 4)); if (player_index >= 4) mask |= 1 << (4 + ((player_index - 3) % 4)); send_packet(index, {kLED, mask}); } uint32 WiimoteControllerProvider::get_packet_delay(size_t index) { std::shared_lock lock(m_device_mutex); if (index < m_wiimotes.size()) { return m_wiimotes[index].data_delay; } return kDefaultPacketDelay; } void WiimoteControllerProvider::set_packet_delay(size_t index, uint32 delay) { std::shared_lock lock(m_device_mutex); if (index < m_wiimotes.size()) { m_wiimotes[index].data_delay = delay; } } WiimoteControllerProvider::WiimoteState WiimoteControllerProvider::get_state(size_t index) { std::shared_lock lock(m_device_mutex); if (index < m_wiimotes.size()) { std::shared_lock data_lock(m_wiimotes[index].mutex); return m_wiimotes[index].state; } return {}; } void WiimoteControllerProvider::connectionThread() { SetThreadName("Wiimote-connect"); while (m_running.load(std::memory_order_relaxed)) { std::vector devices; #ifdef HAS_HIDAPI const auto& hidDevices = HidapiWiimote::get_devices(); std::ranges::move(hidDevices, std::back_inserter(devices)); #endif #ifdef HAS_BLUEZ const auto& l2capDevices = L2CapWiimote::get_devices(); std::ranges::move(l2capDevices, std::back_inserter(devices)); #endif { std::scoped_lock lock(m_connectedDeviceMutex); m_connectedDevices.clear(); std::ranges::move(devices, std::back_inserter(m_connectedDevices)); } std::this_thread::sleep_for(std::chrono::seconds(2)); } } void WiimoteControllerProvider::reader_thread() { SetThreadName("Wiimote-reader"); std::chrono::steady_clock::time_point lastCheck = {}; while (m_running.load(std::memory_order_relaxed)) { const auto now = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(now - lastCheck) > std::chrono::milliseconds(500)) { // check for new connected wiimotes get_controllers(); lastCheck = std::chrono::steady_clock::now(); } bool receivedAnyPacket = false; std::shared_lock lock(m_device_mutex); for (size_t index = 0; index < m_wiimotes.size(); ++index) { auto& wiimote = m_wiimotes[index]; if (!wiimote.device) continue; const auto read_data = wiimote.device->read_data(); if (!read_data) { wiimote.device.reset(); continue; } if (read_data->empty()) continue; receivedAnyPacket = true; std::shared_lock read_lock(wiimote.mutex); WiimoteState new_state = wiimote.state; read_lock.unlock(); bool update_report = false; const uint8* data = read_data->data(); const auto id = (InputReportId)*data; ++data; switch (id) { case kStatus: { cemuLog_logDebug(LogType::Force,"WiimoteControllerProvider::read_thread: kStatus"); new_state.buttons = (*(uint16*)data) & (~0x60E0); data += 2; new_state.flags = *data; ++data; data += 2; // skip zeroes new_state.battery_level = *data; ++data; new_state.ir_camera.mode = set_ir_camera(index, true); if(!new_state.m_calibrated) calibrate(index); if(!new_state.m_motion_plus) detect_motion_plus(index); if (HAS_FLAG(new_state.flags, kExtensionConnected)) { cemuLog_logDebug(LogType::Force,"Extension flag is set"); if(new_state.m_extension.index() == 0) request_extension(index); } else { new_state.m_extension = {}; } update_report = true; } break; case kRead: { cemuLog_logDebug(LogType::Force,"WiimoteControllerProvider::read_thread: kRead"); new_state.buttons = (*(uint16*)data) & (~0x60E0); data += 2; const uint8 error_flag = *data & 0xF, size = (*data >> 4) + 1; ++data; if (error_flag) { // 7 means that wiimote is already enabled or not available cemuLog_logDebug(LogType::Force,"Received error on data read {:#x}", error_flag); continue; } auto address = *(betype*)data; data += 2; if (address == (kRegisterCalibration & 0xFFFF)) { cemuLog_logDebug(LogType::Force,"Calibration received"); cemu_assert(size == 8); new_state.m_calib_acceleration.zero.x = (uint16)*data << 2; ++data; new_state.m_calib_acceleration.zero.y = (uint16)*data << 2; ++data; new_state.m_calib_acceleration.zero.z = (uint16)*data << 2; ++data; // --XXYYZZ new_state.m_calib_acceleration.zero.x |= (*data >> 4) & 0x3; // 5|4 -> 1|0 new_state.m_calib_acceleration.zero.y |= (*data >> 2) & 0x3; // 3|4 -> 1|0 new_state.m_calib_acceleration.zero.z |= *data & 0x3; ++data; new_state.m_calib_acceleration.gravity.x = (uint16)*data << 2; ++data; new_state.m_calib_acceleration.gravity.y = (uint16)*data << 2; ++data; new_state.m_calib_acceleration.gravity.z = (uint16)*data << 2; ++data; new_state.m_calib_acceleration.gravity.x |= (*data >> 4) & 0x3; // 5|4 -> 1|0 new_state.m_calib_acceleration.gravity.y |= (*data >> 2) & 0x3; // 3|4 -> 1|0 new_state.m_calib_acceleration.gravity.z |= *data & 0x3; ++data; new_state.m_calibrated = true; } else if (address == (kRegisterExtensionType & 0xFFFF)) { if (size == 0xf) { cemuLog_logDebug(LogType::Force,"Extension type received but no extension connected"); continue; } cemu_assert(size == 6); auto be_type = *(betype*)data; data += 6; // 48 be_type >>= 16; be_type &= 0xFFFFFFFFFFFF; switch (be_type.value()) { case kExtensionNunchuck: cemuLog_logDebug(LogType::Force,"Extension Type Received: Nunchuck"); new_state.m_extension = NunchuckData{}; break; case kExtensionClassic: cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic"); new_state.m_extension = ClassicData{}; break; case kExtensionClassicPro: cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic Pro"); new_state.m_extension = ClassicData{}; break; case kExtensionGuitar: cemuLog_logDebug(LogType::Force,"Extension Type Received: Guitar"); break; case kExtensionDrums: cemuLog_logDebug(LogType::Force,"Extension Type Received: Drums"); break; case kExtensionBalanceBoard: cemuLog_logDebug(LogType::Force,"Extension Type Received: Balance Board"); break; case kExtensionMotionPlus: cemuLog_logDebug(LogType::Force,"Extension Type Received: MotionPlus"); set_motion_plus(index, true); new_state.m_motion_plus = MotionPlusData{}; break; case kExtensionPartialyInserted: cemuLog_logDebug(LogType::Force,"Extension only partially inserted"); new_state.m_extension = {}; request_status(index); break; case kExtensionMotionPlusInactive: cemuLog_logDebug(LogType::Force,"Extension Type Received: Inactive MotionPlus"); break; default: cemuLog_logDebug(LogType::Force,"Unknown extension: {:#x}", be_type.value()); new_state.m_extension = {}; break; } if (new_state.m_extension.index() != 0) send_read_packet(index, kRegisterMemory, kRegisterExtensionCalibration, 0x10); } else if (address == (kRegisterExtensionCalibration & 0xFFFF)) { cemu_assert(size == 0x10); cemuLog_logDebug(LogType::Force,"Extension calibration received"); std::visit( overloaded { [](auto) { }, [data](MotionPlusData& mp) { // TODO fix }, [data](NunchuckData& nunchuck) { std::array zero{}; if (memcmp(zero.data(), data, zero.size()) == 0) { cemuLog_logDebug(LogType::Force,"Extension calibration data is zero"); return; } nunchuck.calibration.zero.x = (uint16)data[0] << 2; nunchuck.calibration.zero.y = (uint16)data[1] << 2; nunchuck.calibration.zero.z = (uint16)data[2] << 2; // --XXYYZZ nunchuck.calibration.zero.x |= (data[3] >> 4) & 0x3; // 5|4 -> 1|0 nunchuck.calibration.zero.y |= (data[3] >> 2) & 0x3; // 3|4 -> 1|0 nunchuck.calibration.zero.z |= data[3] & 0x3; nunchuck.calibration.gravity.x = (uint16)data[4] << 2;; nunchuck.calibration.gravity.y = (uint16)data[5] << 2;; nunchuck.calibration.gravity.z = (uint16)data[6] << 2;; // --XXYYZZ nunchuck.calibration.gravity.x |= (data[7] >> 4) & 0x3; // 5|4 -> 1|0 nunchuck.calibration.gravity.y |= (data[7] >> 2) & 0x3; // 3|4 -> 1|0 nunchuck.calibration.gravity.z |= data[7] & 0x3; nunchuck.calibration.max.x = data[8]; nunchuck.calibration.max.y = data[11]; nunchuck.calibration.min.x = data[9]; nunchuck.calibration.min.y = data[12]; nunchuck.calibration.center.x = data[10]; nunchuck.calibration.center.y = data[13]; } }, new_state.m_extension); } else { cemuLog_logDebug(LogType::Force,"Unhandled read data received"); continue; } update_report = true; } break; case kAcknowledge: { new_state.buttons = *(uint16*)data & (~0x60E0); data += 2; const auto report_id = *data++; const auto error = *data++; if (error) cemuLog_logDebug(LogType::Force, "Error {:#x} from output report {:#x}", error, report_id); break; } case kDataCore: { // 30 BB BB new_state.buttons = *(uint16*)data & (~0x60E0); data += 2; break; } case kDataCoreAcc: { // 31 BB BB AA AA AA new_state.buttons = *(uint16*)data & (~0x60E0); parse_acceleration(new_state, data); break; } case kDataCoreExt8: { // 32 BB BB EE EE EE EE EE EE EE EE new_state.buttons = *(uint16*)data & (~0x60E0); data += 2; break; } case kDataCoreAccIR: { // 33 BB BB AA AA AA II II II II II II II II II II II II new_state.buttons = *(uint16*)data & (~0x60E0); parse_acceleration(new_state, data); data += parse_ir(new_state, data); break; } case kDataCoreExt19: { // 34 BB BB EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE new_state.buttons = *(uint16*)data & (~0x60E0); data += 2; break; } case kDataCoreAccExt: { // 35 BB BB AA AA AA EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE new_state.buttons = *(uint16*)data & (~0x60E0); parse_acceleration(new_state, data); break; } case kDataCoreIRExt: { // 36 BB BB II II II II II II II II II II EE EE EE EE EE EE EE EE EE new_state.buttons = *(uint16*)data & (~0x60E0); data += 2; break; } case kDataCoreAccIRExt: { // 37 BB BB AA AA AA II II II II II II II II II II EE EE EE EE EE EE new_state.buttons = *(uint16*)data & (~0x60E0); parse_acceleration(new_state, data); data += parse_ir(new_state, data); // 10 std::visit( overloaded { [](auto) { }, [data](MotionPlusData& mp) mutable { glm::vec<3, uint16> raw; raw.x = *data; ++data; raw.y = *data; ++data; raw.z = *data; ++data; raw.x |= (uint16)*data << 6; // 7|2 -> 13|8 mp.slow_yaw = *data & 2; mp.slow_pitch = *data & 1; ++data; raw.y |= (uint16)*data << 6; // 7|2 -> 13|8 mp.slow_roll = *data & 2; mp.extension_connected = *data & 1; ++data; raw.z |= (uint16)*data << 6; // 7|2 -> 13|8 auto& calib = mp.calibration; glm::vec3 orientation = raw; /*orientation -= calib.zero; Vector3 tmp = calib.gravity; tmp -= calib.zero; orientation /= tmp;*/ mp.orientation = orientation; cemuLog_logDebug(LogType::Force,"MotionPlus: {:.2f}, {:.2f} {:.2f}", mp.orientation.x, mp.orientation.y, mp.orientation.z); }, [data](NunchuckData& nunchuck) mutable { nunchuck.raw_axis.x = *data; ++data; nunchuck.raw_axis.y = *data; ++data; glm::vec<3, uint16> raw_acc; raw_acc.x = (uint16)*data << 2; ++data; raw_acc.y = (uint16)*data << 2; ++data; raw_acc.z = (uint16)*data << 2; ++data; nunchuck.z = (*data & 1) == 0; nunchuck.c = (*data & 2) == 0; raw_acc.x |= (*data >> 2) & 0x3; // 3|2 -> 1|0 raw_acc.y |= (*data >> 4) & 0x3; // 5|4 -> 1|0 raw_acc.z |= (*data >> 6) & 0x3; // 7|6 -> 1|0 auto& calib = nunchuck.calibration; if (nunchuck.raw_axis.x < nunchuck.calibration.center.x) // [-1, 0] nunchuck.axis.x = ((float)nunchuck.raw_axis.x - calib.min.x) / ((float)nunchuck. calibration.center.x - calib.min.x + 0.012f) - 1.0f; else // [0, 1] nunchuck.axis.x = (float)(nunchuck.raw_axis.x - nunchuck.calibration.center.x) / ( nunchuck.calibration.max.x - nunchuck.calibration.center.x + 0.012f); if (nunchuck.raw_axis.y <= nunchuck.calibration.center.y) // [-1, 0] nunchuck.axis.y = ((float)nunchuck.raw_axis.y - calib.min.y) / ((float)nunchuck. calibration.center.y - calib.min.y + 0.012f) - 1.0f; else // [0, 1] nunchuck.axis.y = (float)(nunchuck.raw_axis.y - nunchuck.calibration.center.y) / ( nunchuck.calibration.max.y - nunchuck.calibration.center.y); glm::vec3 acceleration = raw_acc; nunchuck.prev_acceleration = nunchuck.acceleration; nunchuck.acceleration = acceleration - glm::vec3(calib.zero); float acc[3]{ -nunchuck.acceleration.x, -nunchuck.acceleration.z, nunchuck.acceleration.y }; const auto grav = nunchuck.calibration.gravity - nunchuck.calibration.zero; auto tacc = nunchuck.acceleration; auto pacc = nunchuck.prev_acceleration; if (grav != glm::vec<3, uint16>{}) { acc[0] /= (float)grav.x; acc[1] /= (float)grav.y; acc[2] /= (float)grav.z; tacc.x /= (float)grav.x; pacc.x /= (float)grav.x; tacc.y /= (float)grav.y; pacc.y /= (float)grav.y; tacc.z /= (float)grav.z; pacc.z /= (float)grav.z; } float zero3[3]{}; float zero4[4]{}; nunchuck.motion_sample = MotionSample( acc, glm::length(tacc - pacc), zero3, zero3, zero4 ); cemuLog_logDebug(LogType::Force,"Nunchuck: Z={}, C={} | {}, {} | {:.2f}, {:.2f}, {:.2f}", nunchuck.z, nunchuck.c, nunchuck.axis.x, nunchuck.axis.y, RadToDeg(nunchuck.acceleration.x), RadToDeg(nunchuck.acceleration.y), RadToDeg(nunchuck.acceleration.z)); }, [data](ClassicData& classic) mutable { classic.left_raw_axis.x = *data & 0x3F; classic.right_raw_axis.x = (*data & 0xC0) >> 3; // 7|6 -> 4|3 ++data; classic.left_raw_axis.y = *data & 0x3F; classic.right_raw_axis.x |= (*data & 0xC0) >> 5; // 7|6 -> 2|1 ++data; classic.right_raw_axis.y = *data & 0x1F; classic.raw_trigger.x = (*data & 0x60) >> 2; // 6|5 -> 4|3 classic.right_raw_axis.x |= (*data & 0x80) >> 7; // 7 -> 0 ++data; classic.raw_trigger.x |= (*data & 0xE0) >> 5; // 7|5 -> 2|0 classic.raw_trigger.y = (*data & 0x1F); ++data; classic.buttons = ~(*(uint16*)data); data += 2; classic.left_axis = classic.left_raw_axis; classic.left_axis /= 63.0f; classic.left_axis = classic.left_axis * 2.0f - 1.0f; classic.right_axis = classic.right_raw_axis; classic.right_axis /= 31.0f; classic.right_axis = classic.right_axis * 2.0f - 1.0f; classic.trigger = classic.raw_trigger; classic.trigger /= 31.0f; cemuLog_logDebug(LogType::Force,"Classic Controller: Buttons={:b} | {}, {} | {}, {} | {}, {}", classic.buttons, classic.left_axis.x, classic.left_axis.y, classic.right_axis.x, classic.right_axis.y, classic.trigger.x, classic.trigger.y); } }, new_state.m_extension); break; } case kDataExt: { // 3d EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE break; } default: cemuLog_logDebug(LogType::Force,"unhandled input packet id {} for wiimote {}", id, index); } // update motion data //const auto motionnow = std::chrono::high_resolution_clock::now(); //const auto delta_time = (float)std::chrono::duration_cast(motionnow - new_state.m_last_motion_timestamp).count() / 1000.0f; //new_state.m_last_motion_timestamp = motionnow; float acc[3]{-new_state.m_acceleration.x, -new_state.m_acceleration.z, new_state.m_acceleration.y}; const auto grav = new_state.m_calib_acceleration.gravity - new_state.m_calib_acceleration.zero; auto tacc = new_state.m_acceleration; auto pacc = new_state.m_prev_acceleration; if (grav != glm::vec<3, uint16>{}) { acc[0] /= (float)grav.x; acc[1] /= (float)grav.y; acc[2] /= (float)grav.z; tacc.x /= (float)grav.x; pacc.x /= (float)grav.x; tacc.y /= (float)grav.y; pacc.y /= (float)grav.y; tacc.z /= (float)grav.z; pacc.z /= (float)grav.z; } float zero3[3]{}; float zero4[4]{}; new_state.motion_sample = MotionSample( acc, glm::length(tacc - pacc), zero3, zero3, zero4 ); std::unique_lock data_lock(wiimote.mutex); wiimote.state = new_state; data_lock.unlock(); if (update_report) update_report_type(index); } lock.unlock(); if (!receivedAnyPacket) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } } void WiimoteControllerProvider::parse_acceleration(WiimoteState& wiimote_state, const uint8*& data) { glm::vec<3, uint16> raw_acc; // acc encoded in BB BB raw_acc.x = (*data >> 5) & 3; // 6|5 -> 1|0 ++data; raw_acc.y = (*data >> 4) & 2; // 5 -> 1 raw_acc.z = (*data >> 5) & 2; // 6 -> 1 ++data; raw_acc.x |= (uint16)*data << 2; ++data; raw_acc.y |= (uint16)*data << 2; ++data; raw_acc.z |= (uint16)*data << 2; ++data; wiimote_state.m_prev_acceleration = wiimote_state.m_acceleration; glm::vec3 acceleration = raw_acc; const auto& calib = wiimote_state.m_calib_acceleration; wiimote_state.m_acceleration = acceleration - glm::vec3(calib.zero); glm::vec3 tmp = calib.gravity; tmp -= calib.zero; acceleration = (wiimote_state.m_acceleration / tmp); const float pi_2 = (float)std::numbers::pi / 2.0f; wiimote_state.m_roll = std::atan2(acceleration.z, acceleration.x) - pi_2; } void WiimoteControllerProvider::rotate_ir(WiimoteState& wiimote_state) { const float rot = wiimote_state.m_roll; if (rot == 0.0f) return; const float sin = std::sin(rot); const float cos = std::cos(rot); int i = -1; for (auto& dot : wiimote_state.ir_camera.dots) { i++; if (!dot.visible) continue; // move to center, rotate and move back dot.pos -= 0.5f; dot.pos.x = (dot.pos.x * cos) + (dot.pos.y * (-sin)); dot.pos.y = (dot.pos.x * sin) + (dot.pos.y * cos); dot.pos += 0.5f; } } void WiimoteControllerProvider::calculate_ir_position(WiimoteState& wiimote_state) { auto& ir = wiimote_state.ir_camera; ir.m_prev_position = ir.position; std::pair indices = ir.indices; if (ir.middle.x != 0) { const float last_angle = std::atan(ir.middle.y / ir.middle.x); float best_distance = std::numeric_limits::max(); for (size_t i = 0; i < ir.dots.size(); ++i) { if (!ir.dots[i].visible) continue; for (size_t j = i + 1; j < ir.dots.size(); ++j) { if (!ir.dots[j].visible) continue; const auto mid = (ir.dots[i].pos + ir.dots[j].pos) / 2.0f; if (mid.x == 0) continue; // check if angle is close enough to the last known one float angle = std::atan(mid.y / mid.x); if (std::abs(last_angle - angle) > DegToRad(10.0f)) continue; // check if distance between points is similar to last known distance const float distance = std::abs(ir.distance - glm::length(ir.dots[i].pos - ir.dots[j].pos)); if (distance > 0.1f && distance > best_distance) continue; // found a new pair best_distance = distance; indices = {(sint32)i, (sint32)j}; } } } if (ir.dots[indices.first].visible && ir.dots[indices.second].visible) { ir.prev_dots[indices.first] = ir.dots[indices.first]; ir.prev_dots[indices.second] = ir.dots[indices.second]; ir.position = (ir.dots[indices.first].pos + ir.dots[indices.second].pos) / 2.0f; ir.middle = ir.position; ir.distance = glm::length(ir.dots[indices.first].pos - ir.dots[indices.second].pos); ir.indices = indices; ir.m_positionVisibility = PositionVisibility::FULL; } else if (ir.dots[indices.first].visible) { ir.position = ir.middle + (ir.dots[indices.first].pos - ir.prev_dots[indices.first].pos); ir.m_positionVisibility = PositionVisibility::PARTIAL; } else if (ir.dots[indices.second].visible) { ir.position = ir.middle + (ir.dots[indices.second].pos - ir.prev_dots[indices.second].pos); ir.m_positionVisibility = PositionVisibility::PARTIAL; } else { ir.m_positionVisibility = PositionVisibility::NONE; } } sint32 WiimoteControllerProvider::parse_ir(WiimoteState& wiimote_state, const uint8* data) { switch (wiimote_state.ir_camera.mode) { case kIRDisabled: wiimote_state.ir_camera.dots = {}; return 0; case kBasicIR: { const auto ir = (BasicIR*)data; for (int i = 0; i < 2; ++i) { auto& dot1 = wiimote_state.ir_camera.dots[i * 2]; auto& dot2 = wiimote_state.ir_camera.dots[i * 2 + 1]; dot1.raw.x = ir[i].x1 | (ir[i].bits.x1 << 8); // 9|8 dot1.raw.y = ir[i].y1 | (ir[i].bits.y1 << 8); dot1.size = 0; dot2.raw.x = ir[i].x2 | (ir[i].bits.x2 << 8); dot2.raw.y = ir[i].y2 | (ir[i].bits.y2 << 8); dot2.size = 0; dot1.visible = dot1.raw != glm::vec<2, uint16>(0x3ff, 0x3ff); if (dot1.visible) dot1.pos = glm::vec2(1.0f - dot1.raw.x / 1023.0f, (float)dot1.raw.y / 768.0f); else dot1.pos = {}; dot2.visible = dot2.raw != glm::vec<2, uint16>(0x3ff, 0x3ff); if (dot2.visible) dot2.pos = glm::vec2(1.0f - dot2.raw.x / 1023.0f, (float)dot2.raw.y / 768.0f); else dot2.pos = {}; } rotate_ir(wiimote_state); calculate_ir_position(wiimote_state); return sizeof(BasicIR) * 2; } case kExtendedIR: { const auto ir = (ExtendedIR*)data; for (int i = 0; i < 4; ++i) { auto& dot = wiimote_state.ir_camera.dots[i]; dot.raw.x = ir[i].x; dot.raw.y = ir[i].y; dot.raw.x |= (uint16)ir[i].bits.x << 8; // 9|8 dot.raw.y |= (uint16)ir[i].bits.y << 8; // 9|8 dot.size = ir[i].bits.size; dot.visible = dot.raw != glm::vec<2, uint16>(0x3ff, 0x3ff); if (dot.visible) dot.pos = glm::vec2(1.0f - dot.raw.x / 1023.0f, (float)dot.raw.y / 768.0f); else dot.pos = {}; } rotate_ir(wiimote_state); calculate_ir_position(wiimote_state); return sizeof(ExtendedIR) * 4; } default: cemu_assert(false); break; } return 0; } void WiimoteControllerProvider::request_extension(size_t index) { // send_write_packet(index, kRegisterMemory, kRegisterExtensionEncrypted, { 0x00 }); send_write_packet(index, kRegisterMemory, kRegisterExtension1, {0x55}); send_write_packet(index, kRegisterMemory, kRegisterExtension2, {0x00}); send_read_packet(index, kRegisterMemory, kRegisterExtensionType, 6); } void WiimoteControllerProvider::detect_motion_plus(size_t index) { send_read_packet(index, kRegisterMemory, kRegisterMotionPlusDetect, 6); } void WiimoteControllerProvider::set_motion_plus(size_t index, bool state) { if (state) { send_write_packet(index, kRegisterMemory, kRegisterMotionPlusInit, { 0x55 }); send_write_packet(index, kRegisterMemory, kRegisterMotionPlusEnable, { 0x04 }); } else { send_write_packet(index, kRegisterMemory, kRegisterExtension1, { 0x55 }); } } void WiimoteControllerProvider::writer_thread() { SetThreadName("Wiimote-writer"); while (m_running.load(std::memory_order_relaxed)) { std::unique_lock writer_lock(m_writer_mutex); while (m_write_queue.empty()) { if (m_writer_cond.wait_for(writer_lock, std::chrono::milliseconds(250)) == std::cv_status::timeout) { if (!m_running.load(std::memory_order_relaxed)) return; } } auto index = (size_t)-1; std::vector data; std::shared_lock device_lock(m_device_mutex); // get first packet of device which is ready to be sent const auto now = std::chrono::high_resolution_clock::now(); for (auto it = m_write_queue.begin(); it != m_write_queue.end(); ++it) { if (it->first >= m_wiimotes.size()) continue; const auto delay = m_wiimotes[it->first].data_delay.load(std::memory_order_relaxed); if (now >= m_wiimotes[it->first].data_ts + std::chrono::milliseconds(delay)) { index = it->first; data = it->second; m_write_queue.erase(it); break; } } writer_lock.unlock(); if (index != (size_t)-1 && !data.empty()) { auto& wiimote = m_wiimotes[index]; if (!wiimote.device) continue; if (wiimote.rumble) data[1] |= 1; if (!wiimote.device->write_data(data)) { wiimote.device.reset(); wiimote.rumble = false; } else wiimote.data_ts = std::chrono::high_resolution_clock::now(); } device_lock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::yield(); } } void WiimoteControllerProvider::calibrate(size_t index) { send_read_packet(index, kEEPROMMemory, kRegisterCalibration, 8); } void WiimoteControllerProvider::update_report_type(size_t index) { std::shared_lock read_lock(m_wiimotes[index].mutex); auto& state = m_wiimotes[index].state; const bool extension = state.m_extension.index() != 0; // TODO || HasMotionPlus(); const bool ir = state.ir_camera.mode != kIRDisabled; const bool motion = true; // UseMotion(); InputReportId report_type; if (extension && ir && motion) report_type = kDataCoreAccIRExt; else if (extension && ir) report_type = kDataCoreIRExt; else if (extension && motion) report_type = kDataCoreAccExt; else if (ir && motion) report_type = kDataCoreAccIR; else if (extension) report_type = kDataCoreExt19; else if (ir) report_type = kDataCoreAccIR; else if (motion) report_type = kDataCoreAcc; else report_type = kDataCore; cemuLog_logDebug(LogType::Force,"Setting report type to {}", report_type); send_packet(index, {kType, 0x04, report_type}); state.ir_camera.mode = set_ir_camera(index, true); } IRMode WiimoteControllerProvider::set_ir_camera(size_t index, bool state) { std::shared_lock read_lock(m_wiimotes[index].mutex); auto& wiimote_state = m_wiimotes[index].state; IRMode mode; if (!state) mode = kIRDisabled; else { mode = wiimote_state.m_extension.index() == 0 ? kExtendedIR : kBasicIR; } if (wiimote_state.ir_camera.mode == mode) return mode; wiimote_state.ir_camera.mode = mode; const uint8_t data = state ? 0x04 : 0x00; send_packet(index, {kIR, data}); send_packet(index, {kIR2, data}); if (state) { send_write_packet(index, kRegisterMemory, kRegisterIR, {0x08}); send_write_packet(index, kRegisterMemory, kRegisterIRSensitivity1, {0x02, 0x00, 0x00, 0x71, 0x01, 0x00, 0xaa, 0x00, 0x64}); send_write_packet(index, kRegisterMemory, kRegisterIRSensitivity2, {0x63, 0x03}); send_write_packet(index, kRegisterMemory, kRegisterIRMode, {(uint8)mode}); send_write_packet(index, kRegisterMemory, kRegisterIR, {0x08}); } update_report_type(index); return mode; } void WiimoteControllerProvider::send_packet(size_t index, std::vector data) { cemu_assert(data.size() > 1); std::shared_lock device_lock(m_device_mutex); if (index >= m_wiimotes.size()) return; device_lock.unlock(); std::unique_lock lock(m_writer_mutex); m_write_queue.emplace_back(index, data); m_writer_cond.notify_one(); } void WiimoteControllerProvider::send_read_packet(size_t index, MemoryType type, RegisterAddress address, uint16 size) { std::vector data(7); data[0] = kReadMemory; data[1] = type; *(betype*)(data.data() + 2) = (address & 0xFFFFFF) << 8; // only uint24 *(betype*)(data.data() + 2 + 3) = size; send_packet(index, std::move(data)); } void WiimoteControllerProvider::send_write_packet(size_t index, MemoryType type, RegisterAddress address, const std::vector& data) { cemu_assert(data.size() <= 16); std::vector packet(6 + 16); packet[0] = kWriteMemory; packet[1] = type; *(betype*)(packet.data() + 2) = (address & 0xFFFFFF) << 8; // only uint24 *(packet.data() + 2 + 3) = (uint8)data.size(); std::copy(data.begin(), data.end(), packet.data() + 2 + 3 + 1); send_packet(index, std::move(packet)); } ================================================ FILE: src/input/api/Wiimote/WiimoteControllerProvider.h ================================================ #pragma once #include "input/motion/MotionHandler.h" #include "input/api/Wiimote/WiimoteDevice.h" #include "input/api/Wiimote/WiimoteMessages.h" #include "input/api/ControllerProvider.h" #include "input/api/ControllerState.h" #include #include #include #ifndef HAS_WIIMOTE #define HAS_WIIMOTE 1 #endif #define WIIMOTE_DEBUG 1 class WiimoteControllerProvider : public ControllerProviderBase { friend class WiimoteController; public: constexpr static uint32 kDefaultPacketDelay = 25; WiimoteControllerProvider(); ~WiimoteControllerProvider(); inline static InputAPI::Type kAPIType = InputAPI::Wiimote; InputAPI::Type api() const override { return kAPIType; } std::vector> get_controllers() override; bool is_connected(size_t index); bool is_registered_device(size_t index); void set_rumble(size_t index, bool state); void request_status(size_t index); void set_led(size_t index, size_t player_index); uint32 get_packet_delay(size_t index); void set_packet_delay(size_t index, uint32 delay); struct WiimoteState { uint16 buttons = 0; uint8 flags = 0; uint8 battery_level = 0; glm::vec3 m_acceleration{}, m_prev_acceleration{}; float m_roll = 0; std::chrono::high_resolution_clock::time_point m_last_motion_timestamp{}; MotionSample motion_sample{}; WiiUMotionHandler motion_handler{}; bool m_calibrated = false; Calibration m_calib_acceleration{}; struct IRCamera { IRMode mode = kIRDisabled; std::array dots{}, prev_dots{}; glm::vec2 position{}, m_prev_position{}; PositionVisibility m_positionVisibility; glm::vec2 middle {}; float distance = 0; std::pair indices{ 0,1 }; }ir_camera{}; std::optional m_motion_plus; std::variant m_extension{}; }; WiimoteState get_state(size_t index); private: std::atomic_bool m_running = false; std::thread m_reader_thread, m_writer_thread; std::shared_mutex m_device_mutex; std::thread m_connectionThread; std::vector m_connectedDevices; std::mutex m_connectedDeviceMutex; struct Wiimote { Wiimote(WiimoteDevicePtr device) : device(std::move(device)) {} WiimoteDevicePtr device; std::atomic_bool rumble = false; std::shared_mutex mutex; WiimoteState state{}; std::atomic_uint32_t data_delay = kDefaultPacketDelay; std::chrono::high_resolution_clock::time_point data_ts{}; }; boost::ptr_vector m_wiimotes; std::list>> m_write_queue; std::mutex m_writer_mutex; std::condition_variable m_writer_cond; void reader_thread(); void writer_thread(); void connectionThread(); void calibrate(size_t index); IRMode set_ir_camera(size_t index, bool state); void send_packet(size_t index, std::vector data); void send_read_packet(size_t index, MemoryType type, RegisterAddress address, uint16 size); void send_write_packet(size_t index, MemoryType type, RegisterAddress address, const std::vector& data); void parse_acceleration(WiimoteState& wiimote_state, const uint8*& data); void rotate_ir(WiimoteState& wiimote_state); void calculate_ir_position(WiimoteState& wiimote_state); int parse_ir(WiimoteState& wiimote_state, const uint8* data); void request_extension(size_t index); void detect_motion_plus(size_t index); void set_motion_plus(size_t index, bool state); void update_report_type(size_t index); }; ================================================ FILE: src/input/api/Wiimote/WiimoteDevice.h ================================================ #pragma once class WiimoteDevice { friend class WiimoteInfo; public: virtual ~WiimoteDevice() = default; virtual bool write_data(const std::vector& data) = 0; virtual std::optional> read_data() = 0; virtual bool operator==(const WiimoteDevice& o) const = 0; }; using WiimoteDevicePtr = std::shared_ptr; ================================================ FILE: src/input/api/Wiimote/WiimoteMessages.h ================================================ #pragma once // https://wiibrew.org/wiki/Wiimote enum InputReportId : uint8 { kNone = 0, kStatus = 0x20, kRead = 0x21, kAcknowledge = 0x22, kDataCore = 0x30, kDataCoreAcc = 0x31, kDataCoreExt8 = 0x32, kDataCoreAccIR = 0x33, kDataCoreExt19 = 0x34, kDataCoreAccExt = 0x35, kDataCoreIRExt = 0x36, kDataCoreAccIRExt = 0x37, kDataExt = 0x3d, }; enum RegisterAddress : uint32 { kRegisterCalibration = 0x16, kRegisterCalibration2 = 0x20, // backup calibration data kRegisterIR = 0x4b00030, kRegisterIRSensitivity1 = 0x4b00000, kRegisterIRSensitivity2 = 0x4b0001a, kRegisterIRMode = 0x4b00033, kRegisterExtensionEncrypted = 0x4a40040, kRegisterExtension1 = 0x4a400f0, kRegisterExtension2 = 0x4a400fb, kRegisterExtensionType = 0x4a400fa, kRegisterExtensionCalibration = 0x4a40020, kRegisterMotionPlusDetect = 0x4a600fa, kRegisterMotionPlusInit = 0x4a600f0, kRegisterMotionPlusEnable = 0x4a600fe, }; enum ExtensionType : uint64 { kExtensionNunchuck = 0x0000A4200000, kExtensionClassic = 0x0000A4200101, kExtensionClassicPro = 0x0100A4200101, kExtensionDrawsome = 0xFF00A4200013, kExtensionGuitar = 0x0000A4200103, kExtensionDrums = 0x0100A4200103, kExtensionBalanceBoard = 0x2A2C, kExtensionMotionPlusInactive = 0xa4200005, kExtensionMotionPlus = 0xa6200005, kExtensionPartialyInserted = 0xffffffffffff, }; enum MemoryType : uint8 { kEEPROMMemory = 0, kRegisterMemory = 0x4, }; enum StatusBitmask : uint8 { kBatteryEmpty = 0x1, kExtensionConnected = 0x2, kSpeakerEnabled = 0x4, kIREnabled = 0x8, kLed1 = 0x10, kLed2 = 0x20, kLed3 = 0x40, kLed4 = 0x80 }; enum OutputReportId : uint8 { kLED = 0x11, kType = 0x12, kIR = 0x13, kSpeakerState = 0x14, kStatusRequest = 0x15, kWriteMemory = 0x16, kReadMemory = 0x17, kSpeakerData = 0x18, kSpeakerMute = 0x19, kIR2 = 0x1A, }; enum IRMode : uint8 { kIRDisabled, kBasicIR = 1, kExtendedIR = 3, kFullIR = 5, }; enum WiimoteButtons { kWiimoteButton_Left = 0, kWiimoteButton_Right = 1, kWiimoteButton_Down = 2, kWiimoteButton_Up = 3, kWiimoteButton_Plus = 4, kWiimoteButton_Two = 8, kWiimoteButton_One = 9, kWiimoteButton_B = 10, kWiimoteButton_A = 11, kWiimoteButton_Minus = 12, kWiimoteButton_Home = 15, // self defined kWiimoteButton_C = 16, kWiimoteButton_Z = 17, kHighestWiimote = 20, }; enum ClassicButtons { kClassicButton_R = 1, kClassicButton_Plus = 2, kClassicButton_Home = 3, kClassicButton_Minus = 4, kClassicButton_L = 5, kClassicButton_Down = 6, kClassicButton_Right = 7, kClassicButton_Up = 8, kClassicButton_Left = 9, kClassicButton_ZR = 10, kClassicButton_X = 11, kClassicButton_A = 12, kClassicButton_Y = 13, kClassicButton_B = 14, kClassicButton_ZL = 15, }; struct Calibration { glm::vec<3, uint16> zero{ 0x200, 0x200, 0x200 }; glm::vec<3, uint16> gravity{ 0x240, 0x240, 0x240 }; }; struct BasicIR { uint8 x1; uint8 y1; struct { uint8 x2 : 2; uint8 y2 : 2; uint8 x1 : 2; uint8 y1 : 2; } bits; static_assert(sizeof(bits) == 1); uint8 x2; uint8 y2; }; static_assert(sizeof(BasicIR) == 5); struct ExtendedIR { uint8 x; uint8 y; struct { uint8 size : 4; uint8 x : 2; uint8 y : 2; } bits; static_assert(sizeof(bits) == 1); }; static_assert(sizeof(ExtendedIR) == 3); struct IRDot { bool visible = false; glm::vec2 pos; glm::vec<2, uint16> raw; uint32 size; }; struct IRCamera { IRMode mode; std::array dots{}, prev_dots{}; glm::vec2 position, m_prev_position; glm::vec2 middle; float distance; std::pair indices{ 0,1 }; }; struct NunchuchCalibration : Calibration { glm::vec<2, uint8> min{}; glm::vec<2, uint8> center{ 0x7f, 0x7f }; glm::vec<2, uint8> max{ 0xff, 0xff }; }; struct MotionPlusData { Calibration calibration{}; glm::vec3 orientation; // yaw, roll, pitch bool slow_roll = false; bool slow_pitch = false; bool slow_yaw = false; bool extension_connected = false; }; struct NunchuckData { glm::vec3 acceleration{}, prev_acceleration{}; NunchuchCalibration calibration{}; bool c = false; bool z = false; glm::vec2 axis{}; glm::vec<2, uint8> raw_axis{}; MotionSample motion_sample{}; }; struct ClassicData { glm::vec2 left_axis{}; glm::vec<2, uint8> left_raw_axis{}; glm::vec2 right_axis{}; glm::vec<2, uint8> right_raw_axis{}; glm::vec2 trigger{}; glm::vec<2, uint8> raw_trigger{}; uint16 buttons = 0; }; ================================================ FILE: src/input/api/Wiimote/hidapi/HidapiWiimote.cpp ================================================ #include "HidapiWiimote.h" #include static constexpr uint16 WIIMOTE_VENDOR_ID = 0x057e; static constexpr uint16 WIIMOTE_PRODUCT_ID = 0x0306; static constexpr uint16 WIIMOTE_MP_PRODUCT_ID = 0x0330; static constexpr uint16 WIIMOTE_MAX_INPUT_REPORT_LENGTH = 22; static constexpr auto PRO_CONTROLLER_NAME = L"Nintendo RVL-CNT-01-UC"; HidapiWiimote::HidapiWiimote(hid_device* dev, std::string_view path) : m_handle(dev), m_path(path) { } bool HidapiWiimote::write_data(const std::vector &data) { return hid_write(m_handle, data.data(), data.size()) >= 0; } std::optional> HidapiWiimote::read_data() { std::array read_data{}; const auto result = hid_read(m_handle, read_data.data(), WIIMOTE_MAX_INPUT_REPORT_LENGTH); if (result < 0) return {}; return {{read_data.cbegin(), read_data.cbegin() + result}}; } std::vector HidapiWiimote::get_devices() { std::vector wiimote_devices; hid_init(); const auto device_enumeration = hid_enumerate(WIIMOTE_VENDOR_ID, 0x0); for (auto it = device_enumeration; it != nullptr; it = it->next){ if (it->product_id != WIIMOTE_PRODUCT_ID && it->product_id != WIIMOTE_MP_PRODUCT_ID) continue; if (std::wcscmp(it->product_string, PRO_CONTROLLER_NAME) == 0) continue; auto dev = hid_open_path(it->path); if (!dev){ cemuLog_logDebug(LogType::Force, "Unable to open Wiimote device at {}: {}", it->path, boost::nowide::narrow(hid_error(nullptr))); } else { hid_set_nonblocking(dev, true); wiimote_devices.push_back(std::make_shared(dev, it->path)); } } hid_free_enumeration(device_enumeration); return wiimote_devices; } bool HidapiWiimote::operator==(const WiimoteDevice& rhs) const { auto other = dynamic_cast(&rhs); if (!other) return false; return m_path == other->m_path; } HidapiWiimote::~HidapiWiimote() { hid_close(m_handle); } ================================================ FILE: src/input/api/Wiimote/hidapi/HidapiWiimote.h ================================================ #pragma once #include #include class HidapiWiimote : public WiimoteDevice { public: HidapiWiimote(hid_device* dev, std::string_view path); ~HidapiWiimote() override; bool write_data(const std::vector &data) override; std::optional> read_data() override; bool operator==(const WiimoteDevice& o) const override; static std::vector get_devices(); private: hid_device* m_handle; const std::string m_path; }; ================================================ FILE: src/input/api/Wiimote/l2cap/L2CapWiimote.cpp ================================================ #include "L2CapWiimote.h" #include constexpr auto comparator = [](const bdaddr_t& a, const bdaddr_t& b) { return bacmp(&a, &b); }; static auto s_addresses = std::map(comparator); static std::mutex s_addressMutex; static bool AttemptConnect(int sockFd, const sockaddr_l2& addr) { auto res = connect(sockFd, reinterpret_cast(&addr), sizeof(sockaddr_l2)); if (res == 0) return true; return connect(sockFd, reinterpret_cast(&addr), sizeof(sockaddr_l2)) == 0; } static bool AttemptSetNonBlock(int sockFd) { return fcntl(sockFd, F_SETFL, fcntl(sockFd, F_GETFL) | O_NONBLOCK) == 0; } L2CapWiimote::L2CapWiimote(int controlFd, int dataFd, bdaddr_t addr) : m_controlFd(controlFd), m_dataFd(dataFd), m_addr(addr) { } L2CapWiimote::~L2CapWiimote() { close(m_dataFd); close(m_controlFd); const auto& b = m_addr.b; cemuLog_logDebug(LogType::Force, "Wiimote at {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} disconnected", b[5], b[4], b[3], b[2], b[1], b[0]); // Re-add to candidate vec s_addressMutex.lock(); s_addresses[m_addr] = false; s_addressMutex.unlock(); } void L2CapWiimote::AddCandidateAddress(bdaddr_t addr) { std::scoped_lock lock(s_addressMutex); s_addresses.try_emplace(addr, false); } std::vector L2CapWiimote::get_devices() { s_addressMutex.lock(); std::vector unconnected; for (const auto& [addr, connected] : s_addresses) { if (!connected) unconnected.push_back(addr); } s_addressMutex.unlock(); std::vector outDevices; for (const auto& addr : unconnected) { // Control socket, PSM 0x11, needs to be open for the data socket to be opened auto controlFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (controlFd < 0) { cemuLog_logDebug(LogType::Force, "Failed to open control socket: {}", strerror(errno)); continue; } sockaddr_l2 controlAddr{}; controlAddr.l2_family = AF_BLUETOOTH; controlAddr.l2_psm = htobs(0x11); controlAddr.l2_bdaddr = addr; if (!AttemptConnect(controlFd, controlAddr) || !AttemptSetNonBlock(controlFd)) { const auto& b = addr.b; cemuLog_logDebug(LogType::Force, "Failed to connect control socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); close(controlFd); continue; } // Socket for sending and receiving data from controller, PSM 0x13 auto dataFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (dataFd < 0) { cemuLog_logDebug(LogType::Force, "Failed to open data socket: {}", strerror(errno)); close(controlFd); continue; } sockaddr_l2 dataAddr{}; dataAddr.l2_family = AF_BLUETOOTH; dataAddr.l2_psm = htobs(0x13); dataAddr.l2_bdaddr = addr; if (!AttemptConnect(dataFd, dataAddr) || !AttemptSetNonBlock(dataFd)) { const auto& b = addr.b; cemuLog_logDebug(LogType::Force, "Failed to connect data socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); close(dataFd); close(controlFd); continue; } outDevices.emplace_back(std::make_shared(controlFd, dataFd, addr)); s_addressMutex.lock(); s_addresses[addr] = true; s_addressMutex.unlock(); } return outDevices; } bool L2CapWiimote::write_data(const std::vector& data) { const auto size = data.size(); cemu_assert_debug(size < 23); uint8 buffer[23]; // All outgoing messages must be prefixed with 0xA2 buffer[0] = 0xA2; std::memcpy(buffer + 1, data.data(), size); const auto outSize = size + 1; return send(m_dataFd, buffer, outSize, 0) == outSize; } std::optional> L2CapWiimote::read_data() { uint8 buffer[23]; const auto nBytes = recv(m_dataFd, buffer, 23, 0); if (nBytes < 0 && errno == EWOULDBLOCK) return std::vector{}; // All incoming messages must be prefixed with 0xA1 if (nBytes < 2 || buffer[0] != 0xA1) return std::nullopt; return std::vector(buffer + 1, buffer + 1 + nBytes - 1); } bool L2CapWiimote::operator==(const WiimoteDevice& rhs) const { auto mote = dynamic_cast(&rhs); if (!mote) return false; return bacmp(&m_addr, &mote->m_addr) == 0; } ================================================ FILE: src/input/api/Wiimote/l2cap/L2CapWiimote.h ================================================ #pragma once #include #include class L2CapWiimote : public WiimoteDevice { public: L2CapWiimote(int controlFd, int dataFd, bdaddr_t addr); ~L2CapWiimote() override; bool write_data(const std::vector& data) override; std::optional> read_data() override; bool operator==(const WiimoteDevice& o) const override; static void AddCandidateAddress(bdaddr_t addr); static std::vector get_devices(); private: int m_controlFd; int m_dataFd; bdaddr_t m_addr; }; ================================================ FILE: src/input/api/XInput/XInputController.cpp ================================================ #include "input/api/XInput/XInputController.h" XInputController::XInputController(uint32 index) : base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1)) { if (index >= XUSER_MAX_COUNT) throw std::runtime_error(fmt::format("invalid xinput index {} (must be smaller than {})", index, XUSER_MAX_COUNT)); m_index = index; m_settings.axis.deadzone = m_settings.rotation.deadzone = m_settings.trigger.deadzone = 0.15f; } bool XInputController::connect() { if (m_connected) return true; m_has_battery = false; XINPUT_CAPABILITIES caps{}; m_connected = m_provider->m_XInputGetCapabilities(m_index, XINPUT_FLAG_GAMEPAD, &caps) != ERROR_DEVICE_NOT_CONNECTED; if (!m_connected) return false; m_has_rumble = (caps.Vibration.wLeftMotorSpeed > 0) || (caps.Vibration.wRightMotorSpeed > 0); if (m_provider->m_XInputGetBatteryInformation) { XINPUT_BATTERY_INFORMATION battery{}; if (m_provider->m_XInputGetBatteryInformation(m_index, BATTERY_DEVTYPE_GAMEPAD, &battery) == ERROR_SUCCESS) { m_has_battery = (battery.BatteryType == BATTERY_TYPE_ALKALINE || battery.BatteryType == BATTERY_TYPE_NIMH); } } return m_connected; } bool XInputController::is_connected() { return m_connected; } void XInputController::start_rumble() { if (!has_rumble() || m_settings.rumble <= 0) return; XINPUT_VIBRATION vibration; vibration.wLeftMotorSpeed = static_cast(m_settings.rumble * std::numeric_limits::max()); vibration.wRightMotorSpeed = static_cast(m_settings.rumble * std::numeric_limits::max()); m_provider->m_XInputSetState(m_index, &vibration); } void XInputController::stop_rumble() { if (!has_rumble()) return; XINPUT_VIBRATION vibration{}; m_provider->m_XInputSetState(m_index, &vibration); } bool XInputController::has_low_battery() { if (!has_battery()) return false; XINPUT_BATTERY_INFORMATION battery{}; if (m_provider->m_XInputGetBatteryInformation(m_index, BATTERY_DEVTYPE_GAMEPAD, &battery) == ERROR_SUCCESS) { return (battery.BatteryType == BATTERY_TYPE_ALKALINE || battery.BatteryType == BATTERY_TYPE_NIMH) && battery .BatteryLevel <= BATTERY_LEVEL_LOW; } return false; } std::string XInputController::get_button_name(uint64 button) const { switch (1ULL << button) { case XINPUT_GAMEPAD_A: return "A"; case XINPUT_GAMEPAD_B: return "B"; case XINPUT_GAMEPAD_X: return "X"; case XINPUT_GAMEPAD_Y: return "Y"; case XINPUT_GAMEPAD_LEFT_SHOULDER: return "L"; case XINPUT_GAMEPAD_RIGHT_SHOULDER: return "R"; case XINPUT_GAMEPAD_START: return "Start"; case XINPUT_GAMEPAD_BACK: return "Select"; case XINPUT_GAMEPAD_LEFT_THUMB: return "L-Stick"; case XINPUT_GAMEPAD_RIGHT_THUMB: return "R-Stick"; case XINPUT_GAMEPAD_DPAD_UP: return "DPAD-Up"; case XINPUT_GAMEPAD_DPAD_DOWN: return "DPAD-Down"; case XINPUT_GAMEPAD_DPAD_LEFT: return "DPAD-Left"; case XINPUT_GAMEPAD_DPAD_RIGHT: return "DPAD-Right"; } return Controller::get_button_name(button); } ControllerState XInputController::raw_state() { ControllerState result{}; if (!m_connected) return result; XINPUT_STATE state; if (m_provider->m_XInputGetState(m_index, &state) != ERROR_SUCCESS) { m_connected = false; return result; } // Buttons for(int i=0;i::digits;i++) result.buttons.SetButtonState(i, (state.Gamepad.wButtons & (1 << i)) != 0); if (state.Gamepad.sThumbLX > 0) result.axis.x = (float)state.Gamepad.sThumbLX / std::numeric_limits::max(); else if (state.Gamepad.sThumbLX < 0) result.axis.x = (float)-state.Gamepad.sThumbLX / std::numeric_limits::min(); if (state.Gamepad.sThumbLY > 0) result.axis.y = (float)state.Gamepad.sThumbLY / std::numeric_limits::max(); else if (state.Gamepad.sThumbLY < 0) result.axis.y = (float)-state.Gamepad.sThumbLY / std::numeric_limits::min(); // Right Stick if (state.Gamepad.sThumbRX > 0) result.rotation.x = (float)state.Gamepad.sThumbRX / std::numeric_limits::max(); else if (state.Gamepad.sThumbRX < 0) result.rotation.x = (float)-state.Gamepad.sThumbRX / std::numeric_limits::min(); if (state.Gamepad.sThumbRY > 0) result.rotation.y = (float)state.Gamepad.sThumbRY / std::numeric_limits::max(); else if (state.Gamepad.sThumbRY < 0) result.rotation.y = (float)-state.Gamepad.sThumbRY / std::numeric_limits::min(); // Trigger result.trigger.x = (float)state.Gamepad.bLeftTrigger / std::numeric_limits::max(); result.trigger.y = (float)state.Gamepad.bRightTrigger / std::numeric_limits::max(); return result; } ================================================ FILE: src/input/api/XInput/XInputController.h ================================================ #pragma once #include "input/api/XInput/XInputControllerProvider.h" #include "input/api/Controller.h" class XInputController : public Controller { public: XInputController(uint32 index); std::string_view api_name() const override { static_assert(to_string(InputAPI::XInput) == "XInput"); return to_string(InputAPI::XInput); } InputAPI::Type api() const override { return InputAPI::XInput; } bool connect() override; bool is_connected() override; bool has_rumble() override { return m_has_rumble; } bool has_battery() override { return m_has_battery; } bool has_low_battery() override; void start_rumble() override; void stop_rumble() override; std::string get_button_name(uint64 button) const override; protected: ControllerState raw_state() override; private: uint32 m_index; bool m_connected = false; bool m_has_battery = false; bool m_has_rumble = false; }; ================================================ FILE: src/input/api/XInput/XInputControllerProvider.cpp ================================================ #include #include "input/api/XInput/XInputControllerProvider.h" #include "input/api/XInput/XInputController.h" XInputControllerProvider::XInputControllerProvider() { // try to load newest to oldest m_module = LoadLibraryA("XInput1_4.DLL"); if (!m_module) { m_module = LoadLibraryA("XInput1_3.DLL"); if (!m_module) { m_module = LoadLibraryA("XInput9_1_0.dll"); if (!m_module) throw std::runtime_error("can't load any xinput dll"); } } #define GET_XINPUT_PROC(__FUNC__) m_##__FUNC__ = (decltype(m_##__FUNC__))GetProcAddress(m_module, #__FUNC__) GET_XINPUT_PROC(XInputGetCapabilities); GET_XINPUT_PROC(XInputGetState); GET_XINPUT_PROC(XInputSetState); if (!m_XInputGetCapabilities || !m_XInputGetState || !m_XInputSetState) { FreeLibrary(m_module); throw std::runtime_error("can't find necessary xinput functions"); } // only available in XInput1_4 and XInput1_3 GET_XINPUT_PROC(XInputGetBatteryInformation); #undef GET_XINPUT_PROC } XInputControllerProvider::~XInputControllerProvider() { if (m_module) FreeLibrary(m_module); } std::vector> XInputControllerProvider::get_controllers() { std::vector> result; for(DWORD i = 0; i < XUSER_MAX_COUNT; ++i) { XINPUT_CAPABILITIES caps; if (m_XInputGetCapabilities(i, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS) { result.emplace_back(std::make_shared(i)); } } return result; } ================================================ FILE: src/input/api/XInput/XInputControllerProvider.h ================================================ #pragma once #if BOOST_OS_WINDOWS #include "input/api/ControllerProvider.h" #include #ifndef HAS_XINPUT #define HAS_XINPUT 1 #endif class XInputControllerProvider : public ControllerProviderBase { friend class XInputController; public: XInputControllerProvider(); ~XInputControllerProvider() override; inline static InputAPI::Type kAPIType = InputAPI::XInput; InputAPI::Type api() const override { return kAPIType; } std::vector> get_controllers() override; private: HMODULE m_module = nullptr; decltype(&XInputGetBatteryInformation) m_XInputGetBatteryInformation; decltype(&XInputGetCapabilities) m_XInputGetCapabilities; decltype(&XInputSetState) m_XInputSetState; decltype(&XInputGetState) m_XInputGetState; }; #endif ================================================ FILE: src/input/emulated/ClassicController.cpp ================================================ #include "input/emulated/ClassicController.h" #include "input/api/Controller.h" #include "input/api/SDL/SDLController.h" ClassicController::ClassicController(size_t player_index) : WPADController(player_index, kDataFormat_CLASSIC) { } uint32 ClassicController::get_emulated_button_flag(uint32 id) const { return s_get_emulated_button_flag(id); } uint32 ClassicController::s_get_emulated_button_flag(uint32 id) { switch (id) { case kButtonId_A: return kCLButton_A; case kButtonId_B: return kCLButton_B; case kButtonId_X: return kCLButton_X; case kButtonId_Y: return kCLButton_Y; case kButtonId_Plus: return kCLButton_Plus; case kButtonId_Minus: return kCLButton_Minus; case kButtonId_Up: return kCLButton_Up; case kButtonId_Down: return kCLButton_Down; case kButtonId_Left: return kCLButton_Left; case kButtonId_Right: return kCLButton_Right; case kButtonId_L: return kCLButton_L; case kButtonId_ZL: return kCLButton_ZL; case kButtonId_R: return kCLButton_R; case kButtonId_ZR: return kCLButton_ZR; } return 0; } std::string_view ClassicController::get_button_name(ButtonId id) { switch (id) { case kButtonId_A: return "A"; case kButtonId_B: return "B"; case kButtonId_X: return "X"; case kButtonId_Y: return "Y"; case kButtonId_L: return "L"; case kButtonId_R: return "R"; case kButtonId_ZL: return "ZL"; case kButtonId_ZR: return "ZR"; case kButtonId_Plus: return "+"; case kButtonId_Minus: return "-"; case kButtonId_Home: return TR_NOOP("home"); case kButtonId_Up: return TR_NOOP("up"); case kButtonId_Down: return TR_NOOP("down"); case kButtonId_Left: return TR_NOOP("left"); case kButtonId_Right: return TR_NOOP("right"); case kButtonId_StickL_Up: return TR_NOOP("up"); case kButtonId_StickL_Down: return TR_NOOP("down"); case kButtonId_StickL_Left: return TR_NOOP("left"); case kButtonId_StickL_Right: return TR_NOOP("right"); case kButtonId_StickR_Up: return TR_NOOP("up"); case kButtonId_StickR_Down: return TR_NOOP("down"); case kButtonId_StickR_Left: return TR_NOOP("left"); case kButtonId_StickR_Right: return TR_NOOP("right"); default: return ""; } } glm::vec2 ClassicController::get_axis() const { const auto left = get_axis_value(kButtonId_StickL_Left); const auto right = get_axis_value(kButtonId_StickL_Right); const auto up = get_axis_value(kButtonId_StickL_Up); const auto down = get_axis_value(kButtonId_StickL_Down); glm::vec2 result; result.x = (left > right) ? -left : right; result.y = (up > down) ? up : -down; return length(result) > 1.0f ? normalize(result) : result; } glm::vec2 ClassicController::get_rotation() const { const auto left = get_axis_value(kButtonId_StickR_Left); const auto right = get_axis_value(kButtonId_StickR_Right); const auto up = get_axis_value(kButtonId_StickR_Up); const auto down = get_axis_value(kButtonId_StickR_Down); glm::vec2 result; result.x = (left > right) ? -left : right; result.y = (up > down) ? up : -down; return length(result) > 1.0f ? normalize(result) : result; } glm::vec2 ClassicController::get_trigger() const { const auto left = get_axis_value(kButtonId_ZL); const auto right = get_axis_value(kButtonId_ZR); return { left, right }; } bool ClassicController::set_default_mapping(const std::shared_ptr& controller) { std::vector> mapping; switch (controller->api()) { case InputAPI::SDLController: { const auto sdl_controller = std::static_pointer_cast(controller); if (sdl_controller->get_guid() == SDLController::kLeftJoyCon) { mapping = { {kButtonId_L, kButton9}, {kButtonId_ZL, kTriggerXP}, {kButtonId_Minus, kButton4}, {kButtonId_Up, kButton11}, {kButtonId_Down, kButton12}, {kButtonId_Left, kButton13}, {kButtonId_Right, kButton14}, {kButtonId_StickL_Up, kAxisYN}, {kButtonId_StickL_Down, kAxisYP}, {kButtonId_StickL_Left, kAxisXN}, {kButtonId_StickL_Right, kAxisXP}, }; } else if (sdl_controller->get_guid() == SDLController::kRightJoyCon) { mapping = { {kButtonId_A, kButton0}, {kButtonId_B, kButton1}, {kButtonId_X, kButton2}, {kButtonId_Y, kButton3}, {kButtonId_R, kButton10}, {kButtonId_ZR, kTriggerYP}, {kButtonId_Plus, kButton6}, {kButtonId_StickR_Up, kRotationYN}, {kButtonId_StickR_Down, kRotationYP}, {kButtonId_StickR_Left, kRotationXN}, {kButtonId_StickR_Right, kRotationXP}, }; } else { mapping = { {kButtonId_A, kButton1}, {kButtonId_B, kButton0}, {kButtonId_X, kButton3}, {kButtonId_Y, kButton2}, {kButtonId_L, kButton9}, {kButtonId_R, kButton10}, {kButtonId_ZL, kTriggerXP}, {kButtonId_ZR, kTriggerYP}, {kButtonId_Plus, kButton6}, {kButtonId_Minus, kButton4}, {kButtonId_Up, kButton11}, {kButtonId_Down, kButton12}, {kButtonId_Left, kButton13}, {kButtonId_Right, kButton14}, {kButtonId_StickL_Up, kAxisYN}, {kButtonId_StickL_Down, kAxisYP}, {kButtonId_StickL_Left, kAxisXN}, {kButtonId_StickL_Right, kAxisXP}, {kButtonId_StickR_Up, kRotationYN}, {kButtonId_StickR_Down, kRotationYP}, {kButtonId_StickR_Left, kRotationXN}, {kButtonId_StickR_Right, kRotationXP}, }; } } case InputAPI::XInput: { mapping = { {kButtonId_A, kButton13}, {kButtonId_B, kButton12}, {kButtonId_X, kButton15}, {kButtonId_Y, kButton14}, {kButtonId_L, kButton8}, {kButtonId_R, kButton9}, {kButtonId_ZL, kTriggerXP}, {kButtonId_ZR, kTriggerYP}, {kButtonId_Plus, kButton4}, {kButtonId_Minus, kButton5}, {kButtonId_Up, kButton0}, {kButtonId_Down, kButton1}, {kButtonId_Left, kButton2}, {kButtonId_Right, kButton3}, {kButtonId_StickL_Up, kAxisYP}, {kButtonId_StickL_Down, kAxisYN}, {kButtonId_StickL_Left, kAxisXN}, {kButtonId_StickL_Right, kAxisXP}, {kButtonId_StickR_Up, kRotationYP}, {kButtonId_StickR_Down, kRotationYN}, {kButtonId_StickR_Left, kRotationXN}, {kButtonId_StickR_Right, kRotationXP}, }; break; } } bool mapping_updated = false; std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m) { if (m_mappings.find(m.first) == m_mappings.cend()) { set_mapping(m.first, controller, m.second); mapping_updated = true; } }); return mapping_updated; } ================================================ FILE: src/input/emulated/ClassicController.h ================================================ #pragma once #include "input/emulated/WPADController.h" class ClassicController : public WPADController { public: enum ButtonId { kButtonId_None, kButtonId_A, kButtonId_B, kButtonId_X, kButtonId_Y, kButtonId_L, kButtonId_R, kButtonId_ZL, kButtonId_ZR, kButtonId_Plus, kButtonId_Minus, kButtonId_Home, kButtonId_Up, kButtonId_Down, kButtonId_Left, kButtonId_Right, kButtonId_StickL_Up, kButtonId_StickL_Down, kButtonId_StickL_Left, kButtonId_StickL_Right, kButtonId_StickR_Up, kButtonId_StickR_Down, kButtonId_StickR_Left, kButtonId_StickR_Right, kButtonId_Max, }; ClassicController(size_t player_index); Type type() const override { return Type::Classic; } WPADDeviceType get_device_type() const override { return kWAPDevClassic; } uint32 get_emulated_button_flag(uint32 id) const override; size_t get_highest_mapping_id() const override { return kButtonId_Max; } bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_StickL_Up && mapping <= kButtonId_StickR_Right; } glm::vec2 get_axis() const override; glm::vec2 get_rotation() const override; glm::vec2 get_trigger() const override; static uint32 s_get_emulated_button_flag(uint32 id); static std::string_view get_button_name(ButtonId id); bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); } bool is_left_down() const override { return is_mapping_down(kButtonId_Left); } bool is_right_down() const override { return is_mapping_down(kButtonId_Right); } bool is_up_down() const override { return is_mapping_down(kButtonId_Up); } bool is_down_down() const override { return is_mapping_down(kButtonId_Down); } bool is_a_down() const override { return is_mapping_down(kButtonId_A); } bool is_b_down() const override { return is_mapping_down(kButtonId_B); } bool is_home_down() const override { return is_mapping_down(kButtonId_Home); } bool set_default_mapping(const std::shared_ptr& controller) override; }; ================================================ FILE: src/input/emulated/EmulatedController.cpp ================================================ #include "input/emulated/EmulatedController.h" #include "input/api/Controller.h" #ifdef SUPPORTS_WIIMOTE #include "input/api/Wiimote/NativeWiimoteController.h" #endif std::string_view EmulatedController::type_to_string(Type type) { switch (type) { case VPAD: return "Wii U GamePad"; case Pro: return "Wii U Pro Controller"; case Classic: return "Wii U Classic Controller"; case Wiimote: return "Wiimote"; } throw std::runtime_error(fmt::format("unknown emulated controller: {}", to_underlying(type))); } EmulatedController::Type EmulatedController::type_from_string(std::string_view str) { if (str == "Wii U GamePad") return VPAD; else if (str == "Wii U Pro Controller") return Pro; else if (str == "Wii U Classic Controller") return Classic; else if (str == "Wiimote") return Wiimote; throw std::runtime_error(fmt::format("unknown emulated controller: {}", str)); } EmulatedController::EmulatedController(size_t player_index) : m_player_index(player_index) { } void EmulatedController::calibrate() { std::shared_lock lock(m_mutex); for (const auto& controller : m_controllers) { controller->calibrate(); } } void EmulatedController::connect() { std::shared_lock lock(m_mutex); for (const auto& controller : m_controllers) { controller->connect(); } } void EmulatedController::update() { std::shared_lock lock(m_mutex); for(const auto& controller : m_controllers) { controller->update(); } } void EmulatedController::controllers_update_states() { std::shared_lock lock(m_mutex); for (const auto& controller : m_controllers) { controller->update_state(); } } void EmulatedController::start_rumble() { m_rumble = true; std::shared_lock lock(m_mutex); for (const auto& controller : m_controllers) { controller->start_rumble(); } } void EmulatedController::stop_rumble() { if (!m_rumble) return; m_rumble = false; std::shared_lock lock(m_mutex); for (const auto& controller : m_controllers) { controller->stop_rumble(); } } bool EmulatedController::is_battery_low() const { std::shared_lock lock(m_mutex); return std::any_of(m_controllers.cbegin(), m_controllers.cend(), [](const auto& c) {return c->has_low_battery(); }); } bool EmulatedController::has_motion() const { std::shared_lock lock(m_mutex); return std::any_of(m_controllers.cbegin(), m_controllers.cend(), [](const auto& c) {return c->use_motion(); }); } MotionSample EmulatedController::get_motion_data() const { std::shared_lock lock(m_mutex); for (const auto& controller : m_controllers) { if (controller->use_motion()) return controller->get_motion_sample(); } return {}; } bool EmulatedController::has_second_motion() const { int motion = 0; std::shared_lock lock(m_mutex); for(const auto& controller : m_controllers) { if(controller->use_motion()) { // if wiimote has nunchuck connected, we use its acceleration #if SUPPORTS_WIIMOTE if(controller->api() == InputAPI::Wiimote) { if(((NativeWiimoteController*)controller.get())->get_extension() == NativeWiimoteController::Nunchuck) { return true; } } #endif motion++; } } return motion >= 2; } MotionSample EmulatedController::get_second_motion_data() const { int motion = 0; std::shared_lock lock(m_mutex); for (const auto& controller : m_controllers) { if (controller->use_motion()) { // if wiimote has nunchuck connected, we use its acceleration #ifdef SUPPORTS_WIIMOTE if (controller->api() == InputAPI::Wiimote) { if (((NativeWiimoteController*)controller.get())->get_extension() == NativeWiimoteController::Nunchuck) { return ((NativeWiimoteController*)controller.get())->get_nunchuck_motion_sample(); } } #endif motion++; if(motion == 2) { return controller->get_motion_sample(); } } } return {}; } bool EmulatedController::has_position() const { std::shared_lock lock(m_mutex); return std::any_of(m_controllers.cbegin(), m_controllers.cend(), [](const auto& c) {return c->has_position(); }); } glm::vec2 EmulatedController::get_position() const { std::shared_lock lock(m_mutex); for (const auto& controller : m_controllers) { if (controller->has_position()) return controller->get_position(); } return {}; } glm::vec2 EmulatedController::get_prev_position() const { std::shared_lock lock(m_mutex); for (const auto& controller : m_controllers) { if (controller->has_position()) return controller->get_prev_position(); } return {}; } PositionVisibility EmulatedController::GetPositionVisibility() const { std::shared_lock lock(m_mutex); for (const auto& controller : m_controllers) { if (controller->has_position()) return controller->GetPositionVisibility(); } return PositionVisibility::NONE; } void EmulatedController::add_controller(std::shared_ptr controller) { controller->connect(); #ifdef SUPPORTS_WIIMOTE if (const auto wiimote = std::dynamic_pointer_cast(controller)) { wiimote->set_player_index(m_player_index); } #endif std::scoped_lock lock(m_mutex); m_controllers.emplace_back(std::move(controller)); } void EmulatedController::remove_controller(const std::shared_ptr& controller) { std::scoped_lock lock(m_mutex); const auto it = std::find(m_controllers.cbegin(), m_controllers.cend(), controller); if (it != m_controllers.cend()) { m_controllers.erase(it); for(auto m = m_mappings.begin(); m != m_mappings.end();) { if(auto mc = m->second.controller.lock()) { if(*mc == *controller) { m = m_mappings.erase(m); continue; } } ++m; } } } void EmulatedController::clear_controllers() { std::scoped_lock lock(m_mutex); m_controllers.clear(); } float EmulatedController::get_axis_value(uint64 mapping) const { const auto it = m_mappings.find(mapping); if (it != m_mappings.cend()) { if (const auto controller = it->second.controller.lock()) { return controller->get_axis_value(it->second.button); } } return 0; } bool EmulatedController::is_mapping_down(uint64 mapping) const { const auto it = m_mappings.find(mapping); if (it != m_mappings.cend()) { if (const auto controller = it->second.controller.lock()) return controller->get_state().buttons.GetButtonState(it->second.button); } return false; } std::string EmulatedController::get_mapping_name(uint64 mapping) const { const auto it = m_mappings.find(mapping); if (it != m_mappings.cend()) { if (const auto controller = it->second.controller.lock()) { return controller->get_button_name(it->second.button); } } return {}; } std::shared_ptr EmulatedController::get_mapping_controller(uint64 mapping) const { const auto it = m_mappings.find(mapping); if (it != m_mappings.cend()) { if (const auto controller = it->second.controller.lock()) { return controller; } } return {}; } void EmulatedController::delete_mapping(uint64 mapping) { m_mappings.erase(mapping); } void EmulatedController::clear_mappings() { m_mappings.clear(); } void EmulatedController::set_mapping(uint64 mapping, const std::shared_ptr& controller, uint64 button) { m_mappings[mapping] = { controller, button }; } bool EmulatedController::operator==(const EmulatedController& o) const { return type() == o.type() && m_player_index == o.m_player_index; } bool EmulatedController::operator!=(const EmulatedController& o) const { return !(*this == o); } ================================================ FILE: src/input/emulated/EmulatedController.h ================================================ #pragma once #include class ControllerBase; #include "input/motion/MotionSample.h" #include "input/api/ControllerState.h" #include "input/api/InputAPI.h" #include "util/helpers/helpers.h" // mapping = wii u controller button id // button = api button id class EmulatedController { friend class InputManager; public: EmulatedController(size_t player_index); virtual ~EmulatedController() = default; virtual void load(const pugi::xml_node& node){}; virtual void save(pugi::xml_node& node){}; enum Type { VPAD, Pro, Classic, Wiimote, MAX }; virtual Type type() const = 0; std::string_view type_string() const { return type_to_string(type()); } static std::string_view type_to_string(Type type); static Type type_from_string(std::string_view str); size_t player_index() const { return m_player_index; } const std::string& get_profile_name() const { return m_profile_name; } bool has_profile_name() const { return !m_profile_name.empty() && m_profile_name != "default"; } void calibrate(); void connect(); virtual void update(); void controllers_update_states(); virtual glm::vec2 get_axis() const = 0; virtual glm::vec2 get_rotation() const = 0; virtual glm::vec2 get_trigger() const = 0; void start_rumble(); void stop_rumble(); bool is_battery_low() const; bool has_motion() const; MotionSample get_motion_data() const; // some controllers (nunchuck) provide extra motion data bool has_second_motion() const; MotionSample get_second_motion_data() const; bool has_position() const; glm::vec2 get_position() const; glm::vec2 get_prev_position() const; PositionVisibility GetPositionVisibility() const; void add_controller(std::shared_ptr controller); void remove_controller(const std::shared_ptr& controller); void clear_controllers(); const std::vector>& get_controllers() const { return m_controllers; } bool is_mapping_down(uint64 mapping) const; std::string get_mapping_name(uint64 mapping) const; std::shared_ptr get_mapping_controller(uint64 mapping) const; void delete_mapping(uint64 mapping); void clear_mappings(); void set_mapping(uint64 mapping, const std::shared_ptr& controller_base, uint64 button); virtual uint32 get_emulated_button_flag(uint32 mapping) const = 0; bool operator==(const EmulatedController& o) const; bool operator!=(const EmulatedController& o) const; virtual size_t get_highest_mapping_id() const = 0; virtual bool is_axis_mapping(uint64 mapping) const = 0; virtual bool is_start_down() const = 0; virtual bool is_left_down() const = 0; virtual bool is_right_down() const = 0; virtual bool is_up_down() const = 0; virtual bool is_down_down() const = 0; virtual bool is_a_down() const = 0; virtual bool is_b_down() const = 0; virtual bool is_home_down() const = 0; bool was_home_button_down() { return std::exchange(m_homebutton_down, false); } virtual bool set_default_mapping(const std::shared_ptr& controller) { return false; } protected: size_t m_player_index; std::string m_profile_name = "default"; mutable std::shared_mutex m_mutex; std::vector> m_controllers; float get_axis_value(uint64 mapping) const; bool m_rumble = false; struct Mapping { std::weak_ptr controller; uint64 button; }; std::unordered_map m_mappings; bool m_homebutton_down = false; }; using EmulatedControllerPtr = std::shared_ptr; template <> struct fmt::formatter : formatter { template auto format(EmulatedController::Type v, FormatContext& ctx) const { switch (v) { case EmulatedController::Type::VPAD: return formatter::format("Wii U Gamepad", ctx); case EmulatedController::Type::Pro: return formatter::format("Wii U Pro Controller", ctx); case EmulatedController::Type::Classic: return formatter::format("Wii U Classic Controller Pro", ctx); case EmulatedController::Type::Wiimote: return formatter::format("Wiimote", ctx); } throw std::invalid_argument(fmt::format("invalid emulated controller type with value {}", to_underlying(v))); } }; ================================================ FILE: src/input/emulated/ProController.cpp ================================================ #include "input/emulated/ProController.h" #include "input/api/Controller.h" #include "input/api/SDL/SDLController.h" ProController::ProController(size_t player_index) : WPADController(player_index, kDataFormat_URCC) { } uint32 ProController::get_emulated_button_flag(uint32 id) const { return s_get_emulated_button_flag(id); } uint32 ProController::s_get_emulated_button_flag(uint32 id) { switch (id) { case kButtonId_A: return kProButton_A; case kButtonId_B: return kProButton_B; case kButtonId_X: return kProButton_X; case kButtonId_Y: return kProButton_Y; case kButtonId_Plus: return kProButton_Plus; case kButtonId_Minus: return kProButton_Minus; case kButtonId_Up: return kProButton_Up; case kButtonId_Down: return kProButton_Down; case kButtonId_Left: return kProButton_Left; case kButtonId_Right: return kProButton_Right; case kButtonId_StickL: return kProButton_StickL; case kButtonId_StickR: return kProButton_StickR; case kButtonId_L: return kProButton_L; case kButtonId_ZL: return kProButton_ZL; case kButtonId_R: return kProButton_R; case kButtonId_ZR: return kProButton_ZR; default: return 0; } } std::string_view ProController::get_button_name(ButtonId id) { switch (id) { case kButtonId_A: return "A"; case kButtonId_B: return "B"; case kButtonId_X: return "X"; case kButtonId_Y: return "Y"; case kButtonId_L: return "L"; case kButtonId_R: return "R"; case kButtonId_ZL: return "ZL"; case kButtonId_ZR: return "ZR"; case kButtonId_Plus: return "+"; case kButtonId_Minus: return "-"; case kButtonId_Up: return TR_NOOP("up"); case kButtonId_Down: return TR_NOOP("down"); case kButtonId_Left: return TR_NOOP("left"); case kButtonId_Right: return TR_NOOP("right"); case kButtonId_StickL: return TR_NOOP("click"); case kButtonId_StickR: return TR_NOOP("click"); case kButtonId_StickL_Up: return TR_NOOP("up"); case kButtonId_StickL_Down: return TR_NOOP("down"); case kButtonId_StickL_Left: return TR_NOOP("left"); case kButtonId_StickL_Right: return TR_NOOP("right"); case kButtonId_StickR_Up: return TR_NOOP("up"); case kButtonId_StickR_Down: return TR_NOOP("down"); case kButtonId_StickR_Left: return TR_NOOP("left"); case kButtonId_StickR_Right: return TR_NOOP("right"); case kButtonId_Home: return TR_NOOP("home"); default: cemu_assert_debug(false); return ""; } } glm::vec2 ProController::get_axis() const { const auto left = get_axis_value(kButtonId_StickL_Left); const auto right = get_axis_value(kButtonId_StickL_Right); const auto up = get_axis_value(kButtonId_StickL_Up); const auto down = get_axis_value(kButtonId_StickL_Down); glm::vec2 result; result.x = (left > right) ? -left : right; result.y = (up > down) ? up : -down; return result; } glm::vec2 ProController::get_rotation() const { const auto left = get_axis_value(kButtonId_StickR_Left); const auto right = get_axis_value(kButtonId_StickR_Right); const auto up = get_axis_value(kButtonId_StickR_Up); const auto down = get_axis_value(kButtonId_StickR_Down); glm::vec2 result; result.x = (left > right) ? -left : right; result.y = (up > down) ? up : -down; return result; } glm::vec2 ProController::get_trigger() const { const auto left = get_axis_value(kButtonId_ZL); const auto right = get_axis_value(kButtonId_ZR); return { left, right }; } bool ProController::set_default_mapping(const std::shared_ptr& controller) { std::vector> mapping; switch (controller->api()) { case InputAPI::SDLController: { const auto sdl_controller = std::static_pointer_cast(controller); if (sdl_controller->get_guid() == SDLController::kLeftJoyCon) { mapping = { {kButtonId_L, kButton9}, {kButtonId_ZL, kTriggerXP}, {kButtonId_Minus, kButton4}, {kButtonId_Up, kButton11}, {kButtonId_Down, kButton12}, {kButtonId_Left, kButton13}, {kButtonId_Right, kButton14}, {kButtonId_StickL, kButton7}, {kButtonId_StickL_Up, kAxisYN}, {kButtonId_StickL_Down, kAxisYP}, {kButtonId_StickL_Left, kAxisXN}, {kButtonId_StickL_Right, kAxisXP}, }; } else if (sdl_controller->get_guid() == SDLController::kRightJoyCon) { mapping = { {kButtonId_A, kButton0}, {kButtonId_B, kButton1}, {kButtonId_X, kButton2}, {kButtonId_Y, kButton3}, {kButtonId_R, kButton10}, {kButtonId_ZR, kTriggerYP}, {kButtonId_Plus, kButton6}, {kButtonId_StickR, kButton8}, {kButtonId_StickR_Up, kRotationYN}, {kButtonId_StickR_Down, kRotationYP}, {kButtonId_StickR_Left, kRotationXN}, {kButtonId_StickR_Right, kRotationXP}, }; } else { mapping = { {kButtonId_A, kButton1}, {kButtonId_B, kButton0}, {kButtonId_X, kButton3}, {kButtonId_Y, kButton2}, {kButtonId_L, kButton9}, {kButtonId_R, kButton10}, {kButtonId_ZL, kTriggerXP}, {kButtonId_ZR, kTriggerYP}, {kButtonId_Plus, kButton6}, {kButtonId_Minus, kButton4}, {kButtonId_Up, kButton11}, {kButtonId_Down, kButton12}, {kButtonId_Left, kButton13}, {kButtonId_Right, kButton14}, {kButtonId_StickL, kButton7}, {kButtonId_StickR, kButton8}, {kButtonId_StickL_Up, kAxisYN}, {kButtonId_StickL_Down, kAxisYP}, {kButtonId_StickL_Left, kAxisXN}, {kButtonId_StickL_Right, kAxisXP}, {kButtonId_StickR_Up, kRotationYN}, {kButtonId_StickR_Down, kRotationYP}, {kButtonId_StickR_Left, kRotationXN}, {kButtonId_StickR_Right, kRotationXP}, }; } break; } case InputAPI::XInput: { mapping = { {kButtonId_A, kButton13}, {kButtonId_B, kButton12}, {kButtonId_X, kButton15}, {kButtonId_Y, kButton14}, {kButtonId_L, kButton8}, {kButtonId_R, kButton9}, {kButtonId_ZL, kTriggerXP}, {kButtonId_ZR, kTriggerYP}, {kButtonId_Plus, kButton4}, {kButtonId_Minus, kButton5}, {kButtonId_Up, kButton0}, {kButtonId_Down, kButton1}, {kButtonId_Left, kButton2}, {kButtonId_Right, kButton3}, {kButtonId_StickL, kButton6}, {kButtonId_StickR, kButton7}, {kButtonId_StickL_Up, kAxisYP}, {kButtonId_StickL_Down, kAxisYN}, {kButtonId_StickL_Left, kAxisXN}, {kButtonId_StickL_Right, kAxisXP}, {kButtonId_StickR_Up, kRotationYP}, {kButtonId_StickR_Down, kRotationYN}, {kButtonId_StickR_Left, kRotationXN}, {kButtonId_StickR_Right, kRotationXP}, }; break; } } bool mapping_updated = false; std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m) { if (m_mappings.find(m.first) == m_mappings.cend()) { set_mapping(m.first, controller, m.second); mapping_updated = true; } }); return mapping_updated; } ================================================ FILE: src/input/emulated/ProController.h ================================================ #pragma once #include "input/emulated/WPADController.h" class ProController : public WPADController { public: enum ButtonId { kButtonId_None, kButtonId_A, kButtonId_B, kButtonId_X, kButtonId_Y, kButtonId_L, kButtonId_R, kButtonId_ZL, kButtonId_ZR, kButtonId_Plus, kButtonId_Minus, kButtonId_Home, kButtonId_Up, kButtonId_Down, kButtonId_Left, kButtonId_Right, kButtonId_StickL, kButtonId_StickR, kButtonId_StickL_Up, kButtonId_StickL_Down, kButtonId_StickL_Left, kButtonId_StickL_Right, kButtonId_StickR_Up, kButtonId_StickR_Down, kButtonId_StickR_Left, kButtonId_StickR_Right, kButtonId_Max, }; ProController(size_t player_index); Type type() const override { return Type::Pro; } WPADDeviceType get_device_type() const override { return kWAPDevURCC; } uint32 get_emulated_button_flag(uint32 id) const override; size_t get_highest_mapping_id() const override { return kButtonId_Max; } bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_StickL_Up && mapping <= kButtonId_StickR_Right; } glm::vec2 get_axis() const override; glm::vec2 get_rotation() const override; glm::vec2 get_trigger() const override; static uint32 s_get_emulated_button_flag(uint32 id); static std::string_view get_button_name(ButtonId id); bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); } bool is_left_down() const override { return is_mapping_down(kButtonId_Left); } bool is_right_down() const override { return is_mapping_down(kButtonId_Right); } bool is_up_down() const override { return is_mapping_down(kButtonId_Up); } bool is_down_down() const override { return is_mapping_down(kButtonId_Down); } bool is_a_down() const override { return is_mapping_down(kButtonId_A); } bool is_b_down() const override { return is_mapping_down(kButtonId_B); } bool is_home_down() const override { return is_mapping_down(kButtonId_Home); } bool set_default_mapping(const std::shared_ptr& controller) override; }; ================================================ FILE: src/input/emulated/VPADController.cpp ================================================ #include "input/emulated/VPADController.h" #include "input/api/Controller.h" #include "input/api/SDL/SDLController.h" #include "WindowSystem.h" #include "input/InputManager.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/CafeSystem.h" enum ControllerVPADMapping2 : uint32 { VPAD_A = 0x8000, VPAD_B = 0x4000, VPAD_X = 0x2000, VPAD_Y = 0x1000, VPAD_L = 0x0020, VPAD_R = 0x0010, VPAD_ZL = 0x0080, VPAD_ZR = 0x0040, VPAD_PLUS = 0x0008, VPAD_MINUS = 0x0004, VPAD_HOME = 0x0002, VPAD_UP = 0x0200, VPAD_DOWN = 0x0100, VPAD_LEFT = 0x0800, VPAD_RIGHT = 0x0400, VPAD_STICK_R = 0x00020000, VPAD_STICK_L = 0x00040000, VPAD_STICK_L_UP = 0x10000000, VPAD_STICK_L_DOWN = 0x08000000, VPAD_STICK_L_LEFT = 0x40000000, VPAD_STICK_L_RIGHT = 0x20000000, VPAD_STICK_R_UP = 0x01000000, VPAD_STICK_R_DOWN = 0x00800000, VPAD_STICK_R_LEFT = 0x04000000, VPAD_STICK_R_RIGHT = 0x02000000, // special flag VPAD_REPEAT = 0x80000000, }; void VPADController::VPADRead(VPADStatus_t& status, const BtnRepeat& repeat) { controllers_update_states(); m_mic_active = false; m_screen_active = false; for (uint32 i = kButtonId_A; i < kButtonId_Max; ++i) { // axis will be aplied later if (is_axis_mapping(i)) continue; if (is_mapping_down(i)) { const uint32 value = get_emulated_button_flag(i); if (value == 0) { // special buttons if (i == kButtonId_Mic) m_mic_active = true; else if (i == kButtonId_Screen) m_screen_active = true; continue; } status.hold |= value; } } m_homebutton_down |= is_home_down(); const auto axis = get_axis(); status.leftStick.x = axis.x; status.leftStick.y = axis.y; constexpr float kAxisThreshold = 0.5f; constexpr float kHoldAxisThreshold = 0.1f; const uint32 last_hold = m_last_holdvalue; if (axis.x <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_LEFT) && axis.x <= -kHoldAxisThreshold)) status.hold |= VPAD_STICK_L_LEFT; else if (axis.x >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_RIGHT) && axis.x >= kHoldAxisThreshold)) status.hold |= VPAD_STICK_L_RIGHT; if (axis.y <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_DOWN) && axis.y <= -kHoldAxisThreshold)) status.hold |= VPAD_STICK_L_DOWN; else if (axis.y >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_UP) && axis.y >= kHoldAxisThreshold)) status.hold |= VPAD_STICK_L_UP; const auto rotation = get_rotation(); status.rightStick.x = rotation.x; status.rightStick.y = rotation.y; if (rotation.x <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_LEFT) && rotation.x <= -kHoldAxisThreshold)) status.hold |= VPAD_STICK_R_LEFT; else if (rotation.x >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_RIGHT) && rotation.x >= kHoldAxisThreshold)) status.hold |= VPAD_STICK_R_RIGHT; if (rotation.y <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_DOWN) && rotation.y <= -kHoldAxisThreshold)) status.hold |= VPAD_STICK_R_DOWN; else if (rotation.y >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_UP) && rotation.y >= kHoldAxisThreshold)) status.hold |= VPAD_STICK_R_UP; // button repeat const auto now = std::chrono::high_resolution_clock::now(); if (status.hold != m_last_holdvalue) { m_last_hold_change = m_last_pulse = now; } if (repeat.pulse > 0) { if (m_last_hold_change + std::chrono::milliseconds(repeat.delay) >= now) { if ((m_last_pulse + std::chrono::milliseconds(repeat.pulse)) < now) { m_last_pulse = now; status.hold |= VPAD_REPEAT; } } } // general status.release = m_last_holdvalue & ~status.hold; status.trig = ~m_last_holdvalue & status.hold; m_last_holdvalue = status.hold; // touch update_touch(status); // motion status.dir.x = {1, 0, 0}; status.dir.y = {0, 1, 0}; status.dir.z = {0, 0, 1}; status.accXY = {1.0f, 0.0f}; update_motion(status); } void VPADController::update() { EmulatedController::update(); if (!CafeSystem::IsTitleRunning()) return; std::unique_lock lock(m_rumble_mutex); if (m_rumble_queue.empty()) { m_parser = 0; lock.unlock(); stop_rumble(); return; } const auto tick = now_cached(); if (std::chrono::duration_cast(tick - m_last_rumble_check).count() < 1000 / 60) return; m_last_rumble_check = tick; const auto& it = m_rumble_queue.front(); if (it[m_parser]) start_rumble(); else stop_rumble(); ++m_parser; if (m_parser >= it.size()) { m_rumble_queue.pop(); m_parser = 0; } } void VPADController::update_touch(VPADStatus_t& status) { status.tpData.touch = kTpTouchOff; status.tpData.validity = kTpInvalid; // keep x,y from previous update // NGDK (Neko Game Development Kit 2) games (e.g. Mysterios Cities of Gold) rely on x/y remaining intact after touch is released status.tpData.x = (uint16)m_last_touch_position.x; status.tpData.y = (uint16)m_last_touch_position.y; auto& instance = InputManager::instance(); bool pad_view; if (has_position()) { const auto mouse = get_position(); status.tpData.touch = kTpTouchOn; status.tpData.validity = kTpValid; status.tpData.x = (uint16)(mouse.x * 3883.0f + 92.0f); status.tpData.y = (uint16)(4095.0f - mouse.y * 3694.0f - 254.0f); m_last_touch_position = glm::ivec2{status.tpData.x, status.tpData.y}; } else if (const auto left_mouse = instance.get_left_down_mouse_info(&pad_view)) { glm::ivec2 image_pos, image_size; LatteRenderTarget_getScreenImageArea(&image_pos.x, &image_pos.y, &image_size.x, &image_size.y, nullptr, nullptr, pad_view); glm::vec2 relative_mouse_pos = left_mouse.value() - image_pos; relative_mouse_pos = { std::min(relative_mouse_pos.x, (float)image_size.x), std::min(relative_mouse_pos.y, (float)image_size.y) }; relative_mouse_pos = { std::max(relative_mouse_pos.x, 0.0f), std::max(relative_mouse_pos.y, 0.0f) }; relative_mouse_pos /= image_size; status.tpData.touch = kTpTouchOn; status.tpData.validity = kTpValid; status.tpData.x = (uint16)((relative_mouse_pos.x * 3883.0f) + 92.0f); status.tpData.y = (uint16)(4095.0f - (relative_mouse_pos.y * 3694.0f) - 254.0f); m_last_touch_position = glm::ivec2{ status.tpData.x, status.tpData.y }; /*cemuLog_log(LogType::Force, "TDATA: {},{} -> {},{} -> {},{} -> {},{} -> {},{} -> {},{}", left_mouse->x, left_mouse->y, (left_mouse.value() - image_pos).x, (left_mouse.value() - image_pos).y, relative_mouse_pos.x, relative_mouse_pos.y, (uint16)(relative_mouse_pos.x * 3883.0 + 92.0), (uint16)(4095.0 - relative_mouse_pos.y * 3694.0 - 254.0), status.tpData.x.value(), status.tpData.y.value(), status.tpData.x.bevalue(), status.tpData.y.bevalue() );*/ } status.tpProcessed1 = status.tpData; status.tpProcessed2 = status.tpData; } void VPADController::update_motion(VPADStatus_t& status) { if (has_motion()) { auto motionSample = get_motion_data(); glm::vec3 acc; motionSample.getVPADAccelerometer(&acc[0]); //const auto& acc = motionSample.getVPADAccelerometer(); status.acc.x = acc.x; status.acc.y = acc.y; status.acc.z = acc.z; status.accMagnitude = motionSample.getVPADAccMagnitude(); status.accAcceleration = motionSample.getVPADAccAcceleration(); glm::vec3 gyroChange; motionSample.getVPADGyroChange(&gyroChange[0]); //const auto& gyroChange = motionSample.getVPADGyroChange(); status.gyroChange.x = gyroChange.x; status.gyroChange.y = gyroChange.y; status.gyroChange.z = gyroChange.z; //debug_printf("GyroChange %7.2lf %7.2lf %7.2lf\n", (float)status.gyroChange.x, (float)status.gyroChange.y, (float)status.gyroChange.z); glm::vec3 gyroOrientation; motionSample.getVPADOrientation(&gyroOrientation[0]); //const auto& gyroOrientation = motionSample.getVPADOrientation(); status.gyroOrientation.x = gyroOrientation.x; status.gyroOrientation.y = gyroOrientation.y; status.gyroOrientation.z = gyroOrientation.z; float attitude[9]; motionSample.getVPADAttitudeMatrix(attitude); status.dir.x.x = attitude[0]; status.dir.x.y = attitude[1]; status.dir.x.z = attitude[2]; status.dir.y.x = attitude[3]; status.dir.y.y = attitude[4]; status.dir.y.z = attitude[5]; status.dir.z.x = attitude[6]; status.dir.z.y = attitude[7]; status.dir.z.z = attitude[8]; return; } bool pad_view; auto& input_manager = InputManager::instance(); if (const auto right_mouse = input_manager.get_right_down_mouse_info(&pad_view)) { const Vector2 mousePos(right_mouse->x, right_mouse->y); int w, h; if (pad_view) WindowSystem::GetPadWindowPhysSize(w, h); else WindowSystem::GetWindowPhysSize(w, h); float wx = mousePos.x / w; float wy = mousePos.y / h; static glm::vec3 m_lastGyroRotation{}, m_startGyroRotation{}; static bool m_startGyroRotationSet{}; float rotX = (wy * 2 - 1.0f) * 135.0f; // up/down best float rotY = (wx * 2 - 1.0f) * -180.0f; // left/right float rotZ = input_manager.m_mouse_wheel * 14.0f + m_lastGyroRotation.z; input_manager.m_mouse_wheel = 0.0f; if (!m_startGyroRotationSet) { m_startGyroRotation = {rotX, rotY, rotZ}; m_startGyroRotationSet = true; } /* debug_printf("\n\ngyro:\n<%.02f, %.02f, %.02f>\n\n", rotX, rotY, rotZ);*/ Quaternion q(rotX, rotY, rotZ); auto rot = q.GetTransposedRotationMatrix(); /*m_forward = std::get<0>(rot); m_right = std::get<1>(rot); m_up = std::get<2>(rot);*/ status.dir.x = std::get<0>(rot); status.dir.y = std::get<1>(rot); status.dir.z = std::get<2>(rot); /*debug_printf("rot:\n<%.02f, %.02f, %.02f>\n<%.02f, %.02f, %.02f>\n<%.02f, %.02f, %.02f>\n\n", (float)status.dir.x.x, (float)status.dir.x.y, (float)status.dir.x.z, (float)status.dir.y.x, (float)status.dir.y.y, (float)status.dir.y.z, (float)status.dir.z.x, (float)status.dir.z.y, (float)status.dir.z.z);*/ glm::vec3 rotation(rotX - m_lastGyroRotation.x, (rotY - m_lastGyroRotation.y) * 15.0f, rotZ - m_lastGyroRotation.z); rotation.x = std::min(1.0f, std::max(-1.0f, rotation.x / 360.0f)); rotation.y = std::min(1.0f, std::max(-1.0f, rotation.y / 360.0f)); rotation.z = std::min(1.0f, std::max(-1.0f, rotation.z / 360.0f)); /*debug_printf("\n\ngyro:\n<%.02f, %.02f, %.02f>\n\n", rotation.x, rotation.y, rotation.z);*/ constexpr float pi2 = (float)(M_PI * 2); status.gyroChange = {rotation.x, rotation.y, rotation.z}; status.gyroOrientation = {rotation.x, rotation.y, rotation.z}; //status.angle = { rotation.x / pi2, rotation.y / pi2, rotation.z / pi2 }; status.acc = {rotation.x, rotation.y, rotation.z}; status.accAcceleration = 1.0f; status.accMagnitude = 1.0f; status.accXY = {1.0f, 0.0f}; m_lastGyroRotation = {rotX, rotY, rotZ}; } } std::string_view VPADController::get_button_name(ButtonId id) { switch (id) { case kButtonId_A: return "A"; case kButtonId_B: return "B"; case kButtonId_X: return "X"; case kButtonId_Y: return "Y"; case kButtonId_L: return "L"; case kButtonId_R: return "R"; case kButtonId_ZL: return "ZL"; case kButtonId_ZR: return "ZR"; case kButtonId_Plus: return "+"; case kButtonId_Minus: return "-"; case kButtonId_Up: return TR_NOOP("up"); case kButtonId_Down: return TR_NOOP("down"); case kButtonId_Left: return TR_NOOP("left"); case kButtonId_Right: return TR_NOOP("right"); case kButtonId_StickL: return TR_NOOP("click"); case kButtonId_StickR: return TR_NOOP("click"); case kButtonId_StickL_Up: return TR_NOOP("up"); case kButtonId_StickL_Down: return TR_NOOP("down"); case kButtonId_StickL_Left: return TR_NOOP("left"); case kButtonId_StickL_Right: return TR_NOOP("right"); case kButtonId_StickR_Up: return TR_NOOP("up"); case kButtonId_StickR_Down: return TR_NOOP("down"); case kButtonId_StickR_Left: return TR_NOOP("left"); case kButtonId_StickR_Right: return TR_NOOP("right"); case kButtonId_Home: return TR_NOOP("home"); default: cemu_assert_debug(false); return ""; } } void VPADController::clear_rumble() { std::scoped_lock lock(m_rumble_mutex); while (!m_rumble_queue.empty()) m_rumble_queue.pop(); m_parser = 0; } bool VPADController::push_rumble(uint8* pattern, uint8 length) { if (pattern == nullptr || length == 0) { clear_rumble(); return true; } std::scoped_lock lock(m_rumble_mutex); if (m_rumble_queue.size() >= 5) { cemuLog_logDebugOnce(LogType::Force, "VPADControlMotor(): Pattern too long"); return false; } // len = max 15 bytes of data = 120 bits = 1 seconds // we will use 60 hz for 1 second std::vector bitset; int byte = 0; int len = (int)length; while (len > 0) { const uint8 p = pattern[byte]; for (int j = 0; j < 8 && j < len; j += 2) { const bool set = (p & (3 << j)) != 0; bitset.push_back(set); } ++byte; len -= 8; } m_rumble_queue.emplace(std::move(bitset)); m_last_rumble_check = {}; return true; } uint32 VPADController::get_emulated_button_flag(uint32 id) const { switch (id) { case kButtonId_A: return VPAD_A; case kButtonId_B: return VPAD_B; case kButtonId_X: return VPAD_X; case kButtonId_Y: return VPAD_Y; case kButtonId_L: return VPAD_L; case kButtonId_R: return VPAD_R; case kButtonId_ZL: return VPAD_ZL; case kButtonId_ZR: return VPAD_ZR; case kButtonId_Plus: return VPAD_PLUS; case kButtonId_Minus: return VPAD_MINUS; case kButtonId_Up: return VPAD_UP; case kButtonId_Down: return VPAD_DOWN; case kButtonId_Left: return VPAD_LEFT; case kButtonId_Right: return VPAD_RIGHT; case kButtonId_StickL: return VPAD_STICK_L; case kButtonId_StickR: return VPAD_STICK_R; case kButtonId_StickL_Up: return VPAD_STICK_L_UP; case kButtonId_StickL_Down: return VPAD_STICK_L_DOWN; case kButtonId_StickL_Left: return VPAD_STICK_L_LEFT; case kButtonId_StickL_Right: return VPAD_STICK_L_RIGHT; case kButtonId_StickR_Up: return VPAD_STICK_R_UP; case kButtonId_StickR_Down: return VPAD_STICK_R_DOWN; case kButtonId_StickR_Left: return VPAD_STICK_R_LEFT; case kButtonId_StickR_Right: return VPAD_STICK_R_RIGHT; } return 0; } glm::vec2 VPADController::get_axis() const { const auto left = get_axis_value(kButtonId_StickL_Left); const auto right = get_axis_value(kButtonId_StickL_Right); const auto up = get_axis_value(kButtonId_StickL_Up); const auto down = get_axis_value(kButtonId_StickL_Down); glm::vec2 result; result.x = (left > right) ? -left : right; result.y = (up > down) ? up : -down; return length(result) > 1.0f ? normalize(result) : result; } glm::vec2 VPADController::get_rotation() const { const auto left = get_axis_value(kButtonId_StickR_Left); const auto right = get_axis_value(kButtonId_StickR_Right); const auto up = get_axis_value(kButtonId_StickR_Up); const auto down = get_axis_value(kButtonId_StickR_Down); glm::vec2 result; result.x = (left > right) ? -left : right; result.y = (up > down) ? up : -down; return length(result) > 1.0f ? normalize(result) : result; } glm::vec2 VPADController::get_trigger() const { const auto left = get_axis_value(kButtonId_ZL); const auto right = get_axis_value(kButtonId_ZR); return {left, right}; } bool VPADController::set_default_mapping(const std::shared_ptr& controller) { std::vector> mapping; switch (controller->api()) { case InputAPI::SDLController: { const auto sdl_controller = std::static_pointer_cast(controller); if (sdl_controller->get_guid() == SDLController::kLeftJoyCon) { mapping = { {kButtonId_L, kButton9}, {kButtonId_ZL, kTriggerXP}, {kButtonId_Minus, kButton4}, {kButtonId_Up, kButton11}, {kButtonId_Down, kButton12}, {kButtonId_Left, kButton13}, {kButtonId_Right, kButton14}, {kButtonId_StickL, kButton7}, {kButtonId_StickL_Up, kAxisYN}, {kButtonId_StickL_Down, kAxisYP}, {kButtonId_StickL_Left, kAxisXN}, {kButtonId_StickL_Right, kAxisXP}, {kButtonId_Mic, kButton15}, }; } else if (sdl_controller->get_guid() == SDLController::kRightJoyCon) { mapping = { {kButtonId_A, kButton0}, {kButtonId_B, kButton1}, {kButtonId_X, kButton2}, {kButtonId_Y, kButton3}, {kButtonId_R, kButton10}, {kButtonId_ZR, kTriggerYP}, {kButtonId_Plus, kButton6}, {kButtonId_StickR, kButton8}, {kButtonId_StickR_Up, kRotationYN}, {kButtonId_StickR_Down, kRotationYP}, {kButtonId_StickR_Left, kRotationXN}, {kButtonId_StickR_Right, kRotationXP}, }; } else if (sdl_controller->get_guid() == SDLController::kSwitchProController) { // Switch Pro Controller is similar to default mapping, but with a/b and x/y swapped mapping = { {kButtonId_A, kButton0}, {kButtonId_B, kButton1}, {kButtonId_X, kButton2}, {kButtonId_Y, kButton3}, {kButtonId_L, kButton9}, {kButtonId_R, kButton10}, {kButtonId_ZL, kTriggerXP}, {kButtonId_ZR, kTriggerYP}, {kButtonId_Plus, kButton6}, {kButtonId_Minus, kButton4}, {kButtonId_Up, kButton11}, {kButtonId_Down, kButton12}, {kButtonId_Left, kButton13}, {kButtonId_Right, kButton14}, {kButtonId_StickL, kButton7}, {kButtonId_StickR, kButton8}, {kButtonId_StickL_Up, kAxisYN}, {kButtonId_StickL_Down, kAxisYP}, {kButtonId_StickL_Left, kAxisXN}, {kButtonId_StickL_Right, kAxisXP}, {kButtonId_StickR_Up, kRotationYN}, {kButtonId_StickR_Down, kRotationYP}, {kButtonId_StickR_Left, kRotationXN}, {kButtonId_StickR_Right, kRotationXP}, }; } else { mapping = { {kButtonId_A, kButton1}, {kButtonId_B, kButton0}, {kButtonId_X, kButton3}, {kButtonId_Y, kButton2}, {kButtonId_L, kButton9}, {kButtonId_R, kButton10}, {kButtonId_ZL, kTriggerXP}, {kButtonId_ZR, kTriggerYP}, {kButtonId_Plus, kButton6}, {kButtonId_Minus, kButton4}, {kButtonId_Up, kButton11}, {kButtonId_Down, kButton12}, {kButtonId_Left, kButton13}, {kButtonId_Right, kButton14}, {kButtonId_StickL, kButton7}, {kButtonId_StickR, kButton8}, {kButtonId_StickL_Up, kAxisYN}, {kButtonId_StickL_Down, kAxisYP}, {kButtonId_StickL_Left, kAxisXN}, {kButtonId_StickL_Right, kAxisXP}, {kButtonId_StickR_Up, kRotationYN}, {kButtonId_StickR_Down, kRotationYP}, {kButtonId_StickR_Left, kRotationXN}, {kButtonId_StickR_Right, kRotationXP}, }; } break; } case InputAPI::XInput: { mapping = { {kButtonId_A, kButton13}, {kButtonId_B, kButton12}, {kButtonId_X, kButton15}, {kButtonId_Y, kButton14}, {kButtonId_L, kButton8}, {kButtonId_R, kButton9}, {kButtonId_ZL, kTriggerXP}, {kButtonId_ZR, kTriggerYP}, {kButtonId_Plus, kButton4}, {kButtonId_Minus, kButton5}, {kButtonId_Up, kButton0}, {kButtonId_Down, kButton1}, {kButtonId_Left, kButton2}, {kButtonId_Right, kButton3}, {kButtonId_StickL, kButton6}, {kButtonId_StickR, kButton7}, {kButtonId_StickL_Up, kAxisYP}, {kButtonId_StickL_Down, kAxisYN}, {kButtonId_StickL_Left, kAxisXN}, {kButtonId_StickL_Right, kAxisXP}, {kButtonId_StickR_Up, kRotationYP}, {kButtonId_StickR_Down, kRotationYN}, {kButtonId_StickR_Left, kRotationXN}, {kButtonId_StickR_Right, kRotationXP}, }; break; } } bool mapping_updated = false; std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m) { if (m_mappings.find(m.first) == m_mappings.cend()) { set_mapping(m.first, controller, m.second); mapping_updated = true; } }); return mapping_updated; } void VPADController::load(const pugi::xml_node& node) { if (const auto value = node.child("toggle_display")) m_screen_active_toggle = ConvertString(value.child_value()); } void VPADController::save(pugi::xml_node& node) { node.append_child("toggle_display").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (int)m_screen_active_toggle).c_str()); } ================================================ FILE: src/input/emulated/VPADController.h ================================================ #pragma once #include "input/emulated/EmulatedController.h" #include "Cafe/OS/libs/vpad/vpad.h" class VPADController : public EmulatedController { public: enum ButtonId { kButtonId_None, kButtonId_A, kButtonId_B, kButtonId_X, kButtonId_Y, kButtonId_L, kButtonId_R, kButtonId_ZL, kButtonId_ZR, kButtonId_Plus, kButtonId_Minus, kButtonId_Up, kButtonId_Down, kButtonId_Left, kButtonId_Right, kButtonId_StickL, kButtonId_StickR, kButtonId_StickL_Up, kButtonId_StickL_Down, kButtonId_StickL_Left, kButtonId_StickL_Right, kButtonId_StickR_Up, kButtonId_StickR_Down, kButtonId_StickR_Left, kButtonId_StickR_Right, kButtonId_Mic, kButtonId_Screen, kButtonId_Home, kButtonId_Max, }; using EmulatedController::EmulatedController; Type type() const override { return VPAD; } void VPADRead(VPADStatus_t& status, const BtnRepeat& repeat); void update() override; uint32 get_emulated_button_flag(uint32 id) const override; glm::vec2 get_axis() const override; glm::vec2 get_rotation() const override; glm::vec2 get_trigger() const override; bool is_mic_active() { return m_mic_active; } bool is_screen_active() { return m_screen_active; } bool is_screen_active_toggle() { return m_screen_active_toggle; } void set_screen_toggle(bool toggle) {m_screen_active_toggle = toggle;} static std::string_view get_button_name(ButtonId id); void clear_rumble(); bool push_rumble(uint8* pattern, uint8 length); size_t get_highest_mapping_id() const override { return kButtonId_Max; } bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_StickL_Up && mapping <= kButtonId_StickR_Right; } bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); } bool is_left_down() const override { return is_mapping_down(kButtonId_Left); } bool is_right_down() const override { return is_mapping_down(kButtonId_Right); } bool is_up_down() const override { return is_mapping_down(kButtonId_Up); } bool is_down_down() const override { return is_mapping_down(kButtonId_Down); } bool is_a_down() const override { return is_mapping_down(kButtonId_A); } bool is_b_down() const override { return is_mapping_down(kButtonId_B); } bool is_home_down() const override { return is_mapping_down(kButtonId_Home); } bool set_default_mapping(const std::shared_ptr& controller) override; void load(const pugi::xml_node& node) override; void save(pugi::xml_node& node) override; private: bool m_mic_active = false; bool m_screen_active = false; bool m_screen_active_toggle = false; uint32be m_last_holdvalue = 0; std::chrono::high_resolution_clock::time_point m_last_hold_change{}, m_last_pulse{}; std::mutex m_rumble_mutex; std::chrono::high_resolution_clock::time_point m_last_rumble_check{}; std::queue> m_rumble_queue; uint8 m_parser = 0; void update_touch(VPADStatus_t& status); void update_motion(VPADStatus_t& status); glm::ivec2 m_last_touch_position{}; }; ================================================ FILE: src/input/emulated/WPADController.cpp ================================================ #include #include "input/emulated/WPADController.h" #include "input/emulated/ClassicController.h" #include "input/emulated/ProController.h" #include "input/emulated/WiimoteController.h" WPADController::WPADController(size_t player_index, WPADDataFormat data_format) : EmulatedController(player_index), m_data_format(data_format) { } WPADDataFormat WPADController::get_default_data_format() const { switch (get_device_type()) { case kWAPDevCore: return kDataFormat_CORE_ACC_DPD; case kWAPDevFreestyle: return kDataFormat_FREESTYLE_ACC; case kWAPDevClassic: return kDataFormat_CLASSIC; case kWAPDevMPLS: return kDataFormat_MPLS; case kWAPDevMPLSFreeStyle: return kDataFormat_FREESTYLE_ACC_DPD; case kWAPDevMPLSClassic: return kDataFormat_CLASSIC_ACC_DPD; case kWAPDevURCC: return kDataFormat_URCC; default: return kDataFormat_CORE; } } uint32 WPADController::get_emulated_button_flag(WPADDataFormat format, uint32 id) const { switch(format) { case kDataFormat_CORE: case kDataFormat_CORE_ACC: case kDataFormat_CORE_ACC_DPD: case kDataFormat_CORE_ACC_DPD_FULL: case kDataFormat_FREESTYLE: case kDataFormat_FREESTYLE_ACC: case kDataFormat_FREESTYLE_ACC_DPD: case kDataFormat_MPLS: return WiimoteController::s_get_emulated_button_flag(id); case kDataFormat_CLASSIC: case kDataFormat_CLASSIC_ACC: case kDataFormat_CLASSIC_ACC_DPD: return ClassicController::s_get_emulated_button_flag(id); case kDataFormat_TRAIN: break; case kDataFormat_GUITAR: break; case kDataFormat_BALANCE_CHECKER: break; case kDataFormat_DRUM: break; case kDataFormat_TAIKO: break; case kDataFormat_URCC: return ProController::s_get_emulated_button_flag(id); } return 0; } void WPADController::WPADRead(WPADStatus_t* status) { controllers_update_states(); uint32 button = 0; for (uint32 i = 1; i < get_highest_mapping_id(); ++i) { if (is_mapping_down(i)) { const uint32 value = get_emulated_button_flag(m_data_format, i); button |= value; } } m_homebutton_down |= is_home_down(); // todo fill position api from wiimote switch (m_data_format) { case kDataFormat_CORE: case kDataFormat_CORE_ACC: case kDataFormat_CORE_ACC_DPD: case kDataFormat_CORE_ACC_DPD_FULL: { memset(status, 0x00, sizeof(*status)); status->button = button; break; } case kDataFormat_FREESTYLE: case kDataFormat_FREESTYLE_ACC: case kDataFormat_FREESTYLE_ACC_DPD: { WPADFSStatus_t* ex_status = (WPADFSStatus_t*)status; memset(ex_status, 0x00, sizeof(*ex_status)); ex_status->button = button; auto axis = get_axis(); axis *= 127.0f; ex_status->fsStickX = (sint8)axis.x; ex_status->fsStickY = (sint8)axis.y; break; } case kDataFormat_CLASSIC: case kDataFormat_CLASSIC_ACC: case kDataFormat_CLASSIC_ACC_DPD: case kDataFormat_GUITAR: case kDataFormat_DRUM: case kDataFormat_TAIKO: { WPADCLStatus_t* ex_status = (WPADCLStatus_t*)status; memset(ex_status, 0x00, sizeof(*ex_status)); ex_status->clButton = button; auto axis = get_axis(); axis *= 2048.0f; ex_status->clLStickX = (uint16)axis.x; ex_status->clLStickY = (uint16)axis.y; auto rotation = get_rotation(); rotation *= 2048.0f; ex_status->clRStickX = (uint16)rotation.x; ex_status->clRStickY = (uint16)rotation.y; break; } case kDataFormat_TRAIN: { WPADTRStatus_t* ex_status = (WPADTRStatus_t*)status; // TODO break; } case kDataFormat_BALANCE_CHECKER: { WPADBLStatus_t* ex_status = (WPADBLStatus_t*)status; // TODO break; } case kDataFormat_MPLS: { WPADMPStatus_t* ex_status = (WPADMPStatus_t*)status; ex_status->stat = 1; // attached // TODO break; } case kDataFormat_URCC: { WPADUCStatus_t* ex_status = (WPADUCStatus_t*)status; memset(ex_status, 0x00, sizeof(*ex_status)); ex_status->ucButton = button; ex_status->cable = TRUE; ex_status->charge = TRUE; auto axis = get_axis(); axis *= 2048.0f; ex_status->ucLStickX = (uint16)axis.x; ex_status->ucLStickY = (uint16)axis.y; auto rotation = get_rotation(); rotation *= 2048.0f; ex_status->ucRStickX = (uint16)rotation.x; ex_status->ucRStickY = (uint16)rotation.y; break; } default: cemu_assert(false); } status->dev = get_device_type(); status->err = WPAD_ERR_NONE; } void WPADController::KPADRead(KPADStatus_t& status, const BtnRepeat& repeat) { uint32be* hold, *release, *trigger; switch (type()) { case Pro: hold = &status.ex_status.uc.hold; release = &status.ex_status.uc.release; trigger = &status.ex_status.uc.trig; break; case Classic: hold = &status.ex_status.cl.hold; release = &status.ex_status.cl.release; trigger = &status.ex_status.cl.trig; break; default: hold = &status.hold; release = &status.release; trigger = &status.trig; } controllers_update_states(); for (uint32 i = 1; i < get_highest_mapping_id(); ++i) { if (is_mapping_down(i)) { const uint32 value = get_emulated_button_flag(m_data_format, i); *hold |= value; } } m_homebutton_down |= is_home_down(); // button repeat const auto now = std::chrono::steady_clock::now(); if (*hold != m_last_holdvalue) { m_last_hold_change = m_last_pulse = now; } if (repeat.pulse > 0) { if (m_last_hold_change + std::chrono::milliseconds(repeat.delay) >= now) { if ((m_last_pulse + std::chrono::milliseconds(repeat.pulse)) < now) { m_last_pulse = now; *hold |= kWPADButtonRepeat; } } } // axis const auto axis = get_axis(); const auto rotation = get_rotation(); *release = m_last_holdvalue & ~*hold; //status.release = m_last_holdvalue & ~*hold; *trigger = ~m_last_holdvalue & *hold; //status.trig = ~m_last_holdvalue & *hold; m_last_holdvalue = *hold; if (is_mpls_attached()) { status.mpls.dir.X.x = 1; status.mpls.dir.X.y = 0; status.mpls.dir.X.z = 0; status.mpls.dir.Y.x = 0; status.mpls.dir.Y.y = 1; status.mpls.dir.Y.z = 0; status.mpls.dir.Z.x = 0; status.mpls.dir.Z.y = 0; status.mpls.dir.Z.z = 1; } if (has_motion()) { auto motion_sample = get_motion_data(); glm::vec3 acc; motion_sample.getAccelerometer(&acc[0]); status.acc.x = acc.x; status.acc.y = acc.y; status.acc.z = acc.z; status.acc_value = motion_sample.getVPADAccMagnitude(); status.acc_speed = motion_sample.getVPADAccAcceleration(); //glm::vec2 acc_vert; //motion_sample.getVPADAccXY(&acc_vert[0]); //status.acc_vertical.x = acc_vert.x; //status.acc_vertical.y = acc_vert.y; status.accVertical.x = std::min(1.0f, std::abs(acc.x + acc.y)); status.accVertical.y = std::min(std::max(-1.0f, -acc.z), 1.0f); if (is_mpls_attached()) { // todo glm::vec3 gyroChange; motion_sample.getVPADGyroChange(&gyroChange[0]); //const auto& gyroChange = motionSample.getVPADGyroChange(); status.mpls.mpls.x = gyroChange.x; status.mpls.mpls.y = gyroChange.y; status.mpls.mpls.z = gyroChange.z; //debug_printf("GyroChange %7.2lf %7.2lf %7.2lf\n", (float)status.gyroChange.x, (float)status.gyroChange.y, (float)status.gyroChange.z); glm::vec3 gyroOrientation; motion_sample.getVPADOrientation(&gyroOrientation[0]); //const auto& gyroOrientation = motionSample.getVPADOrientation(); status.mpls.angle.x = gyroOrientation.x; status.mpls.angle.y = gyroOrientation.y; status.mpls.angle.z = gyroOrientation.z; float attitude[9]; motion_sample.getVPADAttitudeMatrix(attitude); status.mpls.dir.X.x = attitude[0]; status.mpls.dir.X.y = attitude[1]; status.mpls.dir.X.z = attitude[2]; status.mpls.dir.Y.x = attitude[3]; status.mpls.dir.Y.y = attitude[4]; status.mpls.dir.Y.z = attitude[5]; status.mpls.dir.Z.x = attitude[6]; status.mpls.dir.Z.y = attitude[7]; status.mpls.dir.Z.z = attitude[8]; } } auto visibility = GetPositionVisibility(); if (has_position() && visibility != PositionVisibility::NONE) { if (visibility == PositionVisibility::FULL) status.dpd_valid_fg = 2; else status.dpd_valid_fg = -1; const auto position = get_position(); const auto pos = (position * 2.0f) - 1.0f; status.pos.x = pos.x; status.pos.y = pos.y; const auto delta = position - get_prev_position(); status.vec.x = delta.x; status.vec.y = delta.y; status.speed = glm::length(delta); } else status.dpd_valid_fg = 0; switch (type()) { case Wiimote: status.ex_status.fs.stick.x = axis.x; status.ex_status.fs.stick.y = axis.y; if(has_second_motion()) { auto motion_sample = get_second_motion_data(); glm::vec3 acc; motion_sample.getAccelerometer(&acc[0]); status.ex_status.fs.acc.x = acc.x; status.ex_status.fs.acc.y = acc.y; status.ex_status.fs.acc.z = acc.z; status.ex_status.fs.accValue = motion_sample.getVPADAccMagnitude(); status.ex_status.fs.accSpeed = motion_sample.getVPADAccAcceleration(); } break; case Pro: status.ex_status.uc.lstick.x = axis.x; status.ex_status.uc.lstick.y = axis.y; status.ex_status.uc.rstick.x = rotation.x; status.ex_status.uc.rstick.y = rotation.y; status.ex_status.uc.charge = FALSE; status.ex_status.uc.cable = TRUE; break; case Classic: status.ex_status.cl.lstick.x = axis.x; status.ex_status.cl.lstick.y = axis.y; status.ex_status.cl.rstick.x = rotation.x; status.ex_status.cl.rstick.y = rotation.y; if (HAS_FLAG((uint32)status.ex_status.cl.hold, kCLButton_ZL)) status.ex_status.cl.ltrigger = 1.0f; if (HAS_FLAG((uint32)status.ex_status.cl.hold, kCLButton_ZR)) status.ex_status.cl.rtrigger = 1.0f; break; default: cemu_assert(false); } } ================================================ FILE: src/input/emulated/WPADController.h ================================================ #pragma once #include "input/emulated/EmulatedController.h" #include "Cafe/OS/libs/padscore/padscore.h" #include "Cafe/OS/libs/vpad/vpad.h" constexpr uint32 kWPADButtonRepeat = 0x80000000; enum WPADDeviceType { kWAPDevCore = 0, kWAPDevFreestyle = 1, kWAPDevClassic = 2, kWAPDevMPLS = 5, kWAPDevMPLSFreeStyle = 6, kWAPDevMPLSClassic = 7, kWAPDevURCC = 31, kWAPDevNotFound = 253, kWAPDevUnknown = 255, }; // core, balanceboard enum WPADCoreButtons { kWPADButton_Left = 0x1, kWPADButton_Right = 0x2, kWPADButton_Down = 0x4, kWPADButton_Up = 0x8, kWPADButton_Plus = 0x10, kWPADButton_2 = 0x100, kWPADButton_1 = 0x200, kWPADButton_B = 0x400, kWPADButton_A = 0x800, kWPADButton_Minus = 0x1000, kWPADButton_Home = 0x8000, }; // Nunchuck aka Freestyle enum WPADNunchuckButtons { kWPADButton_Z = 0x2000, kWPADButton_C = 0x4000, }; // Classic Controller enum WPADClassicButtons { kCLButton_Up = 0x1, kCLButton_Left = 0x2, kCLButton_ZR = 0x4, kCLButton_X = 0x8, kCLButton_A = 0x10, kCLButton_Y = 0x20, kCLButton_B = 0x40, kCLButton_ZL = 0x80, kCLButton_R = 0x200, kCLButton_Plus = 0x400, kCLButton_Home = 0x800, kCLButton_Minus = 0x1000, kCLButton_L = 0x2000, kCLButton_Down = 0x4000, kCLButton_Right = 0x8000 }; // Pro Controller aka URCC enum WPADProButtons { kProButton_Up = 0x1, kProButton_Left = 0x2, kProButton_ZR = 0x4, kProButton_X = 0x8, kProButton_A = 0x10, kProButton_Y = 0x20, kProButton_B = 0x40, kProButton_ZL = 0x80, kProButton_R = 0x200, kProButton_Plus = 0x400, kProButton_Home = 0x800, kProButton_Minus = 0x1000, kProButton_L = 0x2000, kProButton_Down = 0x4000, kProButton_Right = 0x8000, kProButton_StickR = 0x10000, kProButton_StickL = 0x20000 }; enum WPADDataFormat { kDataFormat_CORE = 0, kDataFormat_CORE_ACC = 1, kDataFormat_CORE_ACC_DPD = 2, kDataFormat_FREESTYLE = 3, kDataFormat_FREESTYLE_ACC = 4, kDataFormat_FREESTYLE_ACC_DPD = 5, kDataFormat_CLASSIC = 6, kDataFormat_CLASSIC_ACC = 7, kDataFormat_CLASSIC_ACC_DPD = 8, kDataFormat_CORE_ACC_DPD_FULL = 9, // buttons, motion, pointing kDataFormat_TRAIN = 10, kDataFormat_GUITAR = 11, kDataFormat_BALANCE_CHECKER = 12, kDataFormat_DRUM = 15, kDataFormat_MPLS = 16, // buttons, motion, pointing, motion plus kDataFormat_TAIKO = 17, kDataFormat_URCC = 22, // buttons, URCC aka pro }; class WPADController : public EmulatedController { using base_type = EmulatedController; public: WPADController(size_t player_index, WPADDataFormat data_format); uint32 get_emulated_button_flag(WPADDataFormat format, uint32 id) const; virtual WPADDeviceType get_device_type() const = 0; WPADDataFormat get_data_format() const { return m_data_format; } void set_data_format(WPADDataFormat data_format) { m_data_format = data_format; } void WPADRead(WPADStatus_t* status); void KPADRead(KPADStatus_t& status, const BtnRepeat& repeat); virtual bool is_mpls_attached() { return false; } enum class ConnectCallbackStatus { None, // do nothing ReportDisconnect, // call disconnect ReportConnect, // call connect }; ConnectCallbackStatus m_status = ConnectCallbackStatus::ReportConnect; ConnectCallbackStatus m_extension_status = ConnectCallbackStatus::ReportConnect; WPADDataFormat get_default_data_format() const; protected: WPADDataFormat m_data_format; private: uint32be m_last_holdvalue = 0; std::chrono::steady_clock::time_point m_last_hold_change{}, m_last_pulse{}; }; ================================================ FILE: src/input/emulated/WiimoteController.cpp ================================================ #include "input/emulated/WiimoteController.h" #include "input/api/Controller.h" #include "input/api/Wiimote/NativeWiimoteController.h" WiimoteController::WiimoteController(size_t player_index) : WPADController(player_index, kDataFormat_CORE_ACC_DPD) { } void WiimoteController::set_device_type(WPADDeviceType device_type) { m_device_type = device_type; m_data_format = get_default_data_format(); } bool WiimoteController::is_mpls_attached() { return m_device_type == kWAPDevMPLS || m_device_type == kWAPDevMPLSClassic || m_device_type == kWAPDevMPLSFreeStyle; } uint32 WiimoteController::get_emulated_button_flag(uint32 id) const { return s_get_emulated_button_flag(id); } bool WiimoteController::set_default_mapping(const std::shared_ptr& controller) { std::vector> mapping; switch (controller->api()) { case InputAPI::Wiimote: { const auto sdl_controller = std::static_pointer_cast(controller); mapping = { {kButtonId_A, kWiimoteButton_A}, {kButtonId_B, kWiimoteButton_B}, {kButtonId_1, kWiimoteButton_One}, {kButtonId_2, kWiimoteButton_Two}, {kButtonId_Home, kWiimoteButton_Home}, {kButtonId_Plus, kWiimoteButton_Plus}, {kButtonId_Minus, kWiimoteButton_Minus}, {kButtonId_Up, kWiimoteButton_Up}, {kButtonId_Down, kWiimoteButton_Down}, {kButtonId_Left, kWiimoteButton_Left}, {kButtonId_Right, kWiimoteButton_Right}, {kButtonId_Nunchuck_Z, kWiimoteButton_Z}, {kButtonId_Nunchuck_C, kWiimoteButton_C}, {kButtonId_Nunchuck_Up, kAxisYP}, {kButtonId_Nunchuck_Down, kAxisYN}, {kButtonId_Nunchuck_Left, kAxisXN}, {kButtonId_Nunchuck_Right, kAxisXP}, }; } } bool mapping_updated = false; std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m) { if (m_mappings.find(m.first) == m_mappings.cend()) { set_mapping(m.first, controller, m.second); mapping_updated = true; } }); return mapping_updated; } glm::vec2 WiimoteController::get_axis() const { const auto left = get_axis_value(kButtonId_Nunchuck_Left); const auto right = get_axis_value(kButtonId_Nunchuck_Right); const auto up = get_axis_value(kButtonId_Nunchuck_Up); const auto down = get_axis_value(kButtonId_Nunchuck_Down); glm::vec2 result; result.x = (left > right) ? -left : right; result.y = (up > down) ? up : -down; return result; } glm::vec2 WiimoteController::get_rotation() const { return {}; } glm::vec2 WiimoteController::get_trigger() const { return {}; } void WiimoteController::load(const pugi::xml_node& node) { base_type::load(node); if (const auto value = node.child("device_type")) m_device_type = ConvertString(value.child_value()); } void WiimoteController::save(pugi::xml_node& node) { base_type::save(node); node.append_child("device_type").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (int)m_device_type).c_str()); } uint32 WiimoteController::s_get_emulated_button_flag(uint32 id) { switch (id) { case kButtonId_A: return kWPADButton_A; case kButtonId_B: return kWPADButton_B; case kButtonId_1: return kWPADButton_1; case kButtonId_2: return kWPADButton_2; case kButtonId_Plus: return kWPADButton_Plus; case kButtonId_Minus: return kWPADButton_Minus; case kButtonId_Home: return kWPADButton_Home; case kButtonId_Up: return kWPADButton_Up; case kButtonId_Down: return kWPADButton_Down; case kButtonId_Left: return kWPADButton_Left; case kButtonId_Right: return kWPADButton_Right; case kButtonId_Nunchuck_Z: return kWPADButton_Z; case kButtonId_Nunchuck_C: return kWPADButton_C; } return 0; } std::string_view WiimoteController::get_button_name(ButtonId id) { switch (id) { case kButtonId_A: return "A"; case kButtonId_B: return "B"; case kButtonId_1: return "1"; case kButtonId_2: return "2"; case kButtonId_Home: return TR_NOOP("home"); case kButtonId_Plus: return "+"; case kButtonId_Minus: return "-"; case kButtonId_Up: return TR_NOOP("up"); case kButtonId_Down: return TR_NOOP("down"); case kButtonId_Left: return TR_NOOP("left"); case kButtonId_Right: return TR_NOOP("right"); case kButtonId_Nunchuck_Z: return "Z"; case kButtonId_Nunchuck_C: return "C"; case kButtonId_Nunchuck_Up: return TR_NOOP("up"); case kButtonId_Nunchuck_Down: return TR_NOOP("down"); case kButtonId_Nunchuck_Left: return TR_NOOP("left"); case kButtonId_Nunchuck_Right: return TR_NOOP("right"); default: return ""; } } ================================================ FILE: src/input/emulated/WiimoteController.h ================================================ #pragma once #include "input/emulated/WPADController.h" class WiimoteController : public WPADController { using base_type = WPADController; public: enum ButtonId { kButtonId_None, kButtonId_A, kButtonId_B, kButtonId_1, kButtonId_2, kButtonId_Nunchuck_Z, kButtonId_Nunchuck_C, kButtonId_Plus, kButtonId_Minus, kButtonId_Up, kButtonId_Down, kButtonId_Left, kButtonId_Right, kButtonId_Nunchuck_Up, kButtonId_Nunchuck_Down, kButtonId_Nunchuck_Left, kButtonId_Nunchuck_Right, kButtonId_Home, kButtonId_Max, }; WiimoteController(size_t player_index); Type type() const override { return Type::Wiimote; } WPADDeviceType get_device_type() const override { return m_device_type; } void set_device_type(WPADDeviceType device_type); bool is_mpls_attached() override; uint32 get_emulated_button_flag(uint32 id) const override; size_t get_highest_mapping_id() const override { return kButtonId_Max; } bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_Nunchuck_Up && mapping <= kButtonId_Nunchuck_Right; } bool set_default_mapping(const std::shared_ptr& controller) override; glm::vec2 get_axis() const override; glm::vec2 get_rotation() const override; glm::vec2 get_trigger() const override; void load(const pugi::xml_node& node) override; void save(pugi::xml_node& node) override; static uint32 s_get_emulated_button_flag(uint32 id); static std::string_view get_button_name(ButtonId id); bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); } bool is_left_down() const override { return is_mapping_down(kButtonId_Left); } bool is_right_down() const override { return is_mapping_down(kButtonId_Right); } bool is_up_down() const override { return is_mapping_down(kButtonId_Up); } bool is_down_down() const override { return is_mapping_down(kButtonId_Down); } bool is_a_down() const override { return is_mapping_down(kButtonId_A); } bool is_b_down() const override { return is_mapping_down(kButtonId_B); } bool is_home_down() const override { return is_mapping_down(kButtonId_Home); } private: WPADDeviceType m_device_type = kWAPDevCore; }; ================================================ FILE: src/input/motion/Mahony.h ================================================ #pragma once #include #include #include "util/math/quaternion.h" class MahonySensorFusion { public: MahonySensorFusion() { // assume default forward pose (holding controller in hand, tilted forward so the sticks/buttons face upward) m_imuQ.Assign(sqrtf(0.5), sqrtf(0.5), 0.0f, 0.0f); } // gx, gy, gz are in radians/sec void updateIMU(float deltaTime, float gx, float gy, float gz, float ax, float ay, float az) { Vector3f av(ax, ay, az); Vector3f gv(gx, gy, gz); if (deltaTime > 0.2f) deltaTime = 0.2f; // dont let stutter mess up the internal state updateGyroBias(gx, gy, gz); gv.x -= m_gyroBias[0]; gv.y -= m_gyroBias[1]; gv.z -= m_gyroBias[2]; // ignore small angles to avoid drift due to bias (especially on yaw) if (fabs(gv.x) < 0.015f) gv.x = 0.0f; if (fabs(gv.y) < 0.015f) gv.y = 0.0f; if (fabs(gv.z) < 0.015f) gv.z = 0.0f; // cemuLog_logDebug(LogType::Force, "[IMU Quat] time {:7.4} | {:7.2} {:7.2} {:7.2} {:7.2} | gyro( - bias) {:7.4} {:7.4} {:7.4} | acc {:7.2} {:7.2} {:7.2} | GyroBias {:7.4} {:7.4} {:7.4}", deltaTime, m_imuQ.x, m_imuQ.y, m_imuQ.z, m_imuQ.w, gv.x, gv.y, gv.z, ax, ay, az, m_gyroBias[0], m_gyroBias[1], m_gyroBias[2]); if (fabs(av.x) > 0.000001f || fabs(av.y) > 0.000001f || fabs(av.z) > 0.000001f) { av.Normalize(); Vector3f grav = m_imuQ.GetVectorZ(); grav.Scale(0.5f); Vector3f errorFeedback = grav.Cross(av); // apply scaled feedback gv -= errorFeedback; } gv.Scale(0.5f * deltaTime); m_imuQ += (m_imuQ * Quaternionf(0.0f, gv.x, gv.y, gv.z)); m_imuQ.NormalizeXYZW(); updateOrientationAngles(); } float getRollRadians() { return m_roll + (float)m_rollWinding * 2.0f * 3.14159265f; } float getPitchRadians() { return m_pitch + (float)m_pitchWinding * 2.0f * 3.14159265f; } float getYawRadians() { return m_yaw + (float)m_yawWinding * 2.0f * 3.14159265f; } void getQuaternion(float q[4]) const { q[0] = m_imuQ.w; q[1] = m_imuQ.x; q[2] = m_imuQ.y; q[3] = m_imuQ.z; } void getGyroBias(float gBias[3]) const { gBias[0] = m_gyroBias[0]; gBias[1] = m_gyroBias[1]; gBias[2] = m_gyroBias[2]; } private: // calculate roll, yaw and pitch in radians. (-0.5 to 0.5) void calcOrientation() { float sinr_cosp = 2.0f * (m_imuQ.z * m_imuQ.w + m_imuQ.x * m_imuQ.y); float cosr_cosp = 1.0f - 2.0f * (m_imuQ.w * m_imuQ.w + m_imuQ.x * m_imuQ.x); m_roll = std::atan2(sinr_cosp, cosr_cosp); // pitch (y-axis rotation) float sinp = 2.0f * (m_imuQ.z * m_imuQ.x - m_imuQ.y * m_imuQ.w); if (std::abs(sinp) >= 1.0) m_pitch = std::copysign(3.14159265359f / 2.0f, sinp); else m_pitch = std::asin(sinp); // yaw (z-axis rotation) float siny_cosp = 2.0f * (m_imuQ.z * m_imuQ.y + m_imuQ.w * m_imuQ.x); float cosy_cosp = 1.0f - 2.0f * (m_imuQ.x * m_imuQ.x + m_imuQ.y * m_imuQ.y); m_yaw = std::atan2(siny_cosp, cosy_cosp); } void updateOrientationAngles() { auto calcWindingCountChange = [](float prevAngle, float newAngle) -> int { if (newAngle > prevAngle) { float angleDif = newAngle - prevAngle; if (angleDif > 3.14159265f) return -1; } else if (newAngle < prevAngle) { float angleDif = prevAngle - newAngle; if (angleDif > 3.14159265f) return 1; } return 0; }; float prevRoll = m_roll; float prevPitch = m_pitch; float prevYaw = m_yaw; calcOrientation(); // calculate roll, yaw and pitch including winding count to match what VPAD API returns m_rollWinding += calcWindingCountChange(prevRoll, m_roll); m_pitchWinding += calcWindingCountChange(prevPitch, m_pitch); m_yawWinding += calcWindingCountChange(prevYaw, m_yaw); } void updateGyroBias(float gx, float gy, float gz) { // dont let actual movement influence the bias // but be careful about setting this too low, there are controllers out there with really bad bias (my Switch Pro had -0.0933 0.0619 0.0179 in resting state) if (fabs(gx) >= 0.35f || fabs(gy) >= 0.35f || fabs(gz) >= 0.35f) return; m_gyroTotalSum[0] += gx; m_gyroTotalSum[1] += gy; m_gyroTotalSum[2] += gz; m_gyroTotalSampleCount++; if (m_gyroTotalSampleCount >= 200) { m_gyroBias[0] = (float)(m_gyroTotalSum[0] / (double)m_gyroTotalSampleCount); m_gyroBias[1] = (float)(m_gyroTotalSum[1] / (double)m_gyroTotalSampleCount); m_gyroBias[2] = (float)(m_gyroTotalSum[2] / (double)m_gyroTotalSampleCount); } } private: Quaternionf m_imuQ; // current orientation // angle data float m_roll{}; float m_pitch{}; float m_yaw{}; int m_rollWinding{}; int m_pitchWinding{}; int m_yawWinding{}; // gyro bias float m_gyroBias[3]{}; double m_gyroTotalSum[3]{}; uint64 m_gyroTotalSampleCount{}; }; ================================================ FILE: src/input/motion/MotionHandler.h ================================================ #pragma once #include "Mahony.h" #include "MotionSample.h" // utility class to translate external motion input (DSU, SDL GamePad sensors) into the values expected by VPAD API (and maybe others in the future) class WiiUMotionHandler { public: // gyro is in radians/sec void processMotionSample( float deltaTime, float gx, float gy, float gz, float accx, float accy, float accz) { m_gyro[0] = gx; m_gyro[1] = gy; m_gyro[2] = gz; m_prevAcc[0] = m_acc[0]; m_prevAcc[1] = m_acc[1]; m_prevAcc[2] = m_acc[2]; m_acc[0] = accx; m_acc[1] = accy; m_acc[2] = accz; // integrate acc and gyro samples into IMU m_imu.updateIMU(deltaTime, gx, gy, gz, accx, accy, accz); // get orientation from IMU m_orientation[0] = _radToOrientation(-m_imu.getYawRadians()) - 0.50f; m_orientation[1] = _radToOrientation(-m_imu.getPitchRadians()) - 0.50f; m_orientation[2] = _radToOrientation(m_imu.getRollRadians()); } MotionSample getMotionSample() { float q[4]; m_imu.getQuaternion(q); float gBias[3]; m_imu.getGyroBias(gBias); float gyroDebiased[3]; gyroDebiased[0] = m_gyro[0] - gBias[0]; gyroDebiased[1] = m_gyro[1] - gBias[1]; gyroDebiased[2] = m_gyro[2] - gBias[2]; return MotionSample(m_acc, MotionSample::calculateAccAcceleration(m_prevAcc, m_acc), gyroDebiased, m_orientation, q); } private: // VPAD orientation unit is 1.0 = one revolution around the axis float _radToOrientation(float rad) { return rad / (2.0f * 3.14159265f); } MahonySensorFusion m_imu; // state float m_gyro[3]{}; float m_acc[3]{}; float m_prevAcc[3]{}; // calculated values float m_orientation[3]{}; }; ================================================ FILE: src/input/motion/MotionSample.h ================================================ #pragma once #include "util/math/vector3.h" #include "util/math/quaternion.h" struct Quat { float w; float x; float y; float z; Quat() { w = 1.0f; x = 0.0f; y = 0.0f; z = 0.0f; } Quat(float inW, float inX, float inY, float inZ) { w = inW; x = inX; y = inY; z = inZ; } static Quat AngleAxis(float inAngle, float inX, float inY, float inZ) { Quat result = Quat(cosf(inAngle * 0.5f), inX, inY, inZ); result.Normalize(); return result; } void Set(float inW, float inX, float inY, float inZ) { w = inW; x = inX; y = inY; z = inZ; } Quat& operator*=(const Quat& rhs) { Set(w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z, w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y, w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x, w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w); return *this; } friend Quat operator*(Quat lhs, const Quat& rhs) { lhs *= rhs; return lhs; } void Normalize() { //printf("Normalizing: %.4f, %.4f, %.4f, %.4f\n", w, x, y, z); const float length = sqrtf(x * x + y * y + z * z); float targetLength = 1.0f - w * w; if (targetLength <= 0.0f || length <= 0.0f) { Set(1.0f, 0.0f, 0.0f, 0.0f); return; } targetLength = sqrtf(targetLength); const float fixFactor = targetLength / length; x *= fixFactor; y *= fixFactor; z *= fixFactor; //printf("Normalized: %.4f, %.4f, %.4f, %.4f\n", w, x, y, z); return; } Quat Normalized() const { Quat result = *this; result.Normalize(); return result; } void Conjugate() { x = -x; y = -y; z = -z; return; } Quat Conjugated() const { Quat result = *this; result.Conjugate(); return result; } }; // helper class to store unified motion data // supports retrieving values in their API-specific (VPAD, KPAD etc.) format class MotionSample { public: MotionSample() = default; MotionSample(float acc[3], float accAcceleration, float gyro[3], float orientation[3], float quaternion[4] ) { m_acc[0] = acc[0]; m_acc[1] = acc[1]; m_acc[2] = acc[2]; m_accAcceleration = accAcceleration; m_gyro[0] = gyro[0]; m_gyro[1] = gyro[1]; m_gyro[2] = gyro[2]; m_orientation[0] = orientation[0]; m_orientation[1] = orientation[1]; m_orientation[2] = orientation[2]; m_q[0] = quaternion[0]; m_q[1] = quaternion[1]; m_q[2] = quaternion[2]; m_q[3] = quaternion[3]; m_accMagnitude = sqrtf(m_acc[0] * m_acc[0] + m_acc[1] * m_acc[1] + m_acc[2] * m_acc[2]); } void getVPADOrientation(float orientation[3]) { orientation[0] = m_orientation[0]; orientation[1] = m_orientation[1]; orientation[2] = m_orientation[2]; } void getVPADGyroChange(float gyro[3]) { // filter noise if (fabs(gyro[0]) < 0.012f) gyro[0] = 0.0f; if (fabs(gyro[1]) < 0.012f) gyro[1] = 0.0f; if (fabs(gyro[2]) < 0.012f) gyro[2] = 0.0f; // convert gyro[0] = _radToOrientation(-m_gyro[0]); gyro[1] = _radToOrientation(-m_gyro[1]); gyro[2] = _radToOrientation(m_gyro[2]); } void getVPADAccelerometer(float acc[3]) { acc[0] = -m_acc[0]; acc[1] = -m_acc[1]; acc[2] = m_acc[2]; } float getVPADAccMagnitude() { return m_accMagnitude; } float getVPADAccAcceleration() // Possibly not entirely correct. Our results are smaller than VPAD API ones { return m_accAcceleration; } void getVPADAccXY(float accXY[2]) { float invMag = 1.0f / m_accMagnitude; float normAcc[3]; normAcc[0] = m_acc[0] * invMag; normAcc[1] = m_acc[1] * invMag; normAcc[2] = m_acc[2] * invMag; accXY[0] = sqrtf(normAcc[0] * normAcc[0] + normAcc[1] * normAcc[1]); accXY[1] = -sin(getAtanPitch(-normAcc[2], normAcc[0], -normAcc[1])); } void getXVector(float vOut[3], Quaternionf& q) { float X = q.x; float Y = q.y; float Z = q.z; float W = q.w; float xy = X * Y; float xz = X * Z; float yy = Y * Y; float yw = Y * W; float zz = Z * Z; float zw = Z * W; vOut[0] = 1.0f - 2.0f * (yy + zz); // x.x vOut[2] = 2.0f * (xy + zw); // x.y vOut[1] = 2.0f * (xz - yw); // x.z } void getVPADAttitudeMatrix(float mtx[9]) { // VPADs attitude matrix has mixed axis handedness, the most sane way to replicate it is by generating Y and Z by rotating the X vector Quaternionf qImu(m_q[0], m_q[1], m_q[2], m_q[3]); Quaternionf qY = qImu * Quaternionf::FromAngleAxis(1.5708f * 1.0f, 0.0f, 0.0f, 1.0f); Quaternionf qZ = qImu * Quaternionf::FromAngleAxis(1.5708f * 1.0f, 0.0f, 1.0f, 0.0f); getXVector(mtx + 0, qImu); getXVector(mtx + 3, qY); getXVector(mtx + 6, qZ); } static float calculateAccAcceleration(float prevAcc[3], float currentAcc[3]) { float ax = currentAcc[0] - prevAcc[0]; float ay = currentAcc[1] - prevAcc[1]; float az = currentAcc[2] - prevAcc[2]; return sqrtf(ax * ax + ay * ay + az * az); } void getAccelerometer(float acc[3]) { acc[0] = m_acc[0]; acc[1] = m_acc[1]; acc[2] = m_acc[2]; } void getGyrometer(float gyro[3]) { gyro[0] = m_gyro[0]; gyro[1] = m_gyro[1]; gyro[2] = m_gyro[2]; } private: static float _radToOrientation(float rad) { return rad / (2.0f * 3.14159265f); } static float getAtanPitch(float X, float Y, float Z) { return atan2f(-X, sqrtf(Y * Y + Z * Z)); } // provided values float m_gyro[3]{}; float m_acc[3]{}; float m_accAcceleration{}; float m_orientation[3]{}; float m_q[4]{}; // calculated values float m_accMagnitude{}; }; /* Captured VPAD attitude values Assuming GamePad is in a direct line between player (holder) and the monitor DRC flat on table, screen facing up. Top pointing away (away from person, pointing towards monitor): 1.00 -0.03 -0.00 0.03 0.99 -0.13 0.01 0.13 0.99 Turned 45° to the right: 0.71 -0.03 0.71 0.12 0.99 -0.08 -0.70 0.14 0.70 Turned 45° to the right (top of GamePad pointing right now): 0.08 -0.03 1.00 -> Z points towards person 0.15 0.99 0.01 -0.99 0.15 0.09 -> DRC Z-Axis now points towards X-minus Turned 90° to the right (top of gamepad now pointing towards holder, away from monitor): -1.00 -0.01 0.06 0.00 0.99 0.15 -0.06 0.15 -0.99 Turned 90° to the right (pointing left): -0.17 -0.01 -0.99 -0.13 0.99 0.02 0.98 0.13 -0.17 After another 90° we end up in the initial position: 0.99 -0.03 -0.11 0.01 0.99 -0.13 0.12 0.12 0.99 ------ From initial position, lean the GamePad on its left side. 45° up. So the screen is pointing to the top left 0.66 -0.75 -0.03 0.74 0.66 -0.11 0.10 0.05 0.99 Further 45°, GamePad now on its left, screen pointing left: -0.03 -1.00 -0.00 0.99 -0.03 -0.15 0.15 -0.01 0.99 From initial position, lean the GamePad on its right side. 45° up. So the screen is pointing to the top right 0.75 0.65 -0.11 -0.65 0.76 0.07 0.12 0.02 0.99 From initial position, tilt the GamePad up 90° (bottom side remains in touch with surface): 0.99 -0.05 -0.10 -0.10 0.01 -0.99 0.05 1.00 0.01 From initial position, stand the GamePad on its top side: 1.00 -0.01 -0.09 0.09 -0.01 1.00 -0.01 -1.00 -0.01 Rotate GamePad 180° around x axis, so it now lies on its screen (top of GamePad pointing to holder): 0.99 -0.03 -0.15 -0.04 -1.00 -0.08 -0.15 0.09 -0.99 */ ================================================ FILE: src/main.cpp ================================================ #include "WindowSystem.h" #include "util/crypto/aes128.h" #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "config/CemuConfig.h" #include "config/NetworkSettings.h" #include "config/LaunchSettings.h" #include "input/InputManager.h" #include "Cafe/CafeSystem.h" #include "Cafe/TitleList/TitleList.h" #include "Cafe/TitleList/SaveList.h" #include "Common/ExceptionHandler/ExceptionHandler.h" #include "Common/cpu_features.h" #include "util/helpers/helpers.h" #include "config/ActiveSettings.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cafe/OS/libs/vpad/vpad.h" #include "audio/IAudioAPI.h" #include "audio/IAudioInputAPI.h" #if BOOST_OS_WINDOWS #pragma comment(lib,"Dbghelp.lib") #endif #define SDL_MAIN_HANDLED #include #if BOOST_OS_LINUX #define _putenv(__s) putenv((char*)(__s)) #include #elif BOOST_OS_MACOS || BOOST_OS_BSD #define _putenv(__s) putenv((char*)(__s)) #include #include #endif #if BOOST_OS_WINDOWS extern "C" { __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; } #endif std::atomic_bool g_isGPUInitFinished = false; std::wstring executablePath; // some implementations of _putenv dont copy the string and instead only store a pointer // thus we use a helper to keep a permanent copy std::vector sPutEnvMap; void _putenvSafe(const char* c) { auto s = new std::string(c); sPutEnvMap.emplace_back(s); _putenv(s->c_str()); } void reconfigureGLDrivers() { // reconfigure GL drivers to store const fs::path nvCacheDir = ActiveSettings::GetCachePath("shaderCache/driver/nvidia/"); std::error_code err; fs::create_directories(nvCacheDir, err); std::string nvCacheDirEnvOption("__GL_SHADER_DISK_CACHE_PATH="); nvCacheDirEnvOption.append(_pathToUtf8(nvCacheDir)); #if BOOST_OS_WINDOWS std::wstring tmpW = boost::nowide::widen(nvCacheDirEnvOption); _wputenv(tmpW.c_str()); #else _putenvSafe(nvCacheDirEnvOption.c_str()); #endif _putenvSafe("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1"); } void reconfigureVkDrivers() { _putenvSafe("DISABLE_LAYER_AMD_SWITCHABLE_GRAPHICS_1=1"); _putenvSafe("DISABLE_VK_LAYER_VALVE_steam_fossilize_1=1"); } void WindowsInitCwd() { #if BOOST_OS_WINDOWS executablePath.resize(4096); int i = GetModuleFileNameW(NULL, executablePath.data(), executablePath.size()); if(i >= 0) executablePath.resize(i); else executablePath.clear(); SetCurrentDirectoryW(executablePath.c_str()); // set high priority SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); #endif } void CemuCommonInit() { reconfigureGLDrivers(); reconfigureVkDrivers(); // crypto init AES128_init(); // init PPC timer // call this as early as possible because it measures frequency of RDTSC using an asynchronous thread over 3 seconds PPCTimer_init(); WindowsInitCwd(); ExceptionHandler_Init(); // read config GetConfigHandle().Load(); if (NetworkConfig::XMLExists()) n_config.Load(); // parallelize expensive init code std::future futureInitAudioAPI = std::async(std::launch::async, []{ IAudioAPI::InitializeStatic(); IAudioInputAPI::InitializeStatic(); return 0; }); std::future futureInitGraphicPacks = std::async(std::launch::async, []{ GraphicPack2::LoadAll(); return 0; }); InputManager::instance().load(); futureInitAudioAPI.wait(); futureInitGraphicPacks.wait(); // init Cafe system CafeSystem::Initialize(); // init title list CafeTitleList::Initialize(ActiveSettings::GetUserDataPath("title_list_cache.xml")); for (auto& it : GetConfig().game_paths) CafeTitleList::AddScanPath(_utf8ToPath(it)); fs::path mlcPath = ActiveSettings::GetMlcPath(); if (!mlcPath.empty()) CafeTitleList::SetMLCPath(mlcPath); CafeTitleList::Refresh(); // init save list CafeSaveList::Initialize(); if (!mlcPath.empty()) { CafeSaveList::SetMLCPath(mlcPath); CafeSaveList::Refresh(); } } void mainEmulatorLLE(); void ppcAsmTest(); void gx2CopySurfaceTest(); void ExpressionParser_test(); void FSTVolumeTest(); void CRCTest(); void UnitTests() { ExpressionParser_test(); gx2CopySurfaceTest(); ppcAsmTest(); FSTVolumeTest(); CRCTest(); } bool isConsoleConnected = false; void requireConsole() { #if BOOST_OS_WINDOWS if (isConsoleConnected) return; HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); DWORD dwFileType = GetFileType(hOut); if (dwFileType == FILE_TYPE_UNKNOWN || dwFileType == FILE_TYPE_CHAR) { if (AttachConsole(ATTACH_PARENT_PROCESS) != FALSE) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); freopen("CONIN$", "r", stdin); isConsoleConnected = true; } } else { isConsoleConnected = true; } #endif } void HandlePostUpdate() { // finalize update process // delete update cemu.exe.backup if available const auto filename = ActiveSettings::GetExecutablePath().replace_extension("exe.backup"); if (fs::exists(filename)) { #if BOOST_OS_WINDOWS HANDLE lock; do { lock = CreateMutexW(nullptr, TRUE, L"Global\\cemu_update_lock"); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } while (lock == nullptr); const DWORD wait_result = WaitForSingleObject(lock, 2000); CloseHandle(lock); if (wait_result == WAIT_OBJECT_0) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::error_code ec; fs::remove(filename, ec); } #else while (fs::exists(filename)) { std::error_code ec; fs::remove(filename, ec); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } #endif } } void ToolShaderCacheMerger(); #if BOOST_OS_WINDOWS // entrypoint for release builds int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) { if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE))) cemuLog_log(LogType::Force, "CoInitializeEx() failed"); SDL_SetMainReady(); if (!LaunchSettings::HandleCommandline(lpCmdLine)) return 0; WindowSystem::Create(); return 0; } // entrypoint for debug builds with console int main(int argc, char* argv[]) { if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE))) cemuLog_log(LogType::Force, "CoInitializeEx() failed"); SDL_SetMainReady(); if (!LaunchSettings::HandleCommandline(argc, argv)) return 0; WindowSystem::Create(); return 0; } #else int main(int argc, char *argv[]) { #if BOOST_OS_LINUX || BOOST_OS_BSD XInitThreads(); #endif if (!LaunchSettings::HandleCommandline(argc, argv)) return 0; WindowSystem::Create(); return 0; } #endif extern "C" DLLEXPORT uint64 gameMeta_getTitleId() { return CafeSystem::GetForegroundTitleId(); } ================================================ FILE: src/mainLLE.cpp ================================================ #include "util/crypto/aes128.h" #include "WindowSystem.h" #include "Common/FileStream.h" void CemuCommonInit(); typedef struct { /* +0x000 */ uint32be magic; }ppcAncastHeader_t; void loadEncryptedPPCAncastKernel() { auto kernelData = FileStream::LoadIntoMemory("kernel.img"); if (!kernelData) exit(-1); // check header ppcAncastHeader_t* ancastHeader = (ppcAncastHeader_t*)kernelData->data(); if(ancastHeader->magic != (uint32be)0xEFA282D9) assert_dbg(); // invalid magic memcpy(memory_getPointerFromPhysicalOffset(0x08000000), kernelData->data(), kernelData->size()); } void loadPPCBootrom() { auto bootromData = FileStream::LoadIntoMemory("bootrom.bin"); if (!bootromData) exit(-1); memcpy(memory_getPointerFromPhysicalOffset(0x00000000), bootromData->data(), bootromData->size()); } void mainEmulatorLLE() { CemuCommonInit(); // memory init memory_initPhysicalLayout(); // start GUI thread WindowSystem::Create(); // load kernel ancast image loadPPCBootrom(); loadEncryptedPPCAncastKernel(); PPCTimer_waitForInit(); // begin execution PPCCoreLLE_startSingleCoreScheduler(0x00000100); } ================================================ FILE: src/resource/CMakeLists.txt ================================================ add_library(CemuResource) set_property(TARGET CemuResource PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") enable_language(ASM) # icon resources target_sources(CemuResource PRIVATE embedded/resources.cpp embedded/resources.h ) if(UNIX) if(NOT APPLE) target_sources(CemuResource PRIVATE embedded/fontawesome.S) else() target_sources(CemuResource PRIVATE embedded/fontawesome_macos.S) endif() endif() target_sources(CemuResource PRIVATE CafeDefaultFont.cpp) target_include_directories(CemuResource PUBLIC "../") target_link_libraries(CemuResource PRIVATE CemuCommon) ================================================ FILE: src/resource/CafeDefaultFont.cpp ================================================ unsigned char cafeDefaultFontZLIB[] = { 0x78, 0xda, 0xdc, 0xbc, 0x07, 0x78, 0x54, 0xd5, 0xda, 0xf7, 0x7d, 0xef, 0x36, 0xbb, 0x0c, 0xbd, 0x43, 0x80, 0x4c, 0x08, 0x20, 0x25, 0xf4, 0x16, 0x3a, 0x24, 0xf4, 0x1a, 0x7a, 0x82, 0x20, 0x09, 0x84, 0xde, 0x7b, 0x91, 0x2e, 0x20, 0x20, 0x45, 0x04, 0xe9, 0x4d, 0x40, 0x40, 0x40, 0x98, 0x50, 0x94, 0x5e, 0x04, 0x15, 0x15, 0x05, 0x15, 0x15, 0x05, 0x01, 0x15, 0x05, 0x14, 0x14, 0xa5, 0x2a, 0x65, 0xde, 0xdf, 0x9e, 0x4c, 0x10, 0x50, 0xcf, 0x79, 0xce, 0xf3, 0x3d, 0xcf, 0x77, 0x5d, 0xef, 0x8b, 0xe7, 0xcf, 0x6a, 0xf7, 0xba, 0xfb, 0x5e, 0x7b, 0xad, 0xd9, 0xeb, 0x20, 0x8a, 0x88, 0x64, 0xe2, 0x2f, 0x5d, 0xa4, 0x7e, 0x5c, 0xf3, 0x56, 0x67, 0xf7, 0x1e, 0xec, 0x20, 0x9e, 0x0e, 0xe3, 0x45, 0x2b, 0xff, 0xa0, 0x7e, 0xab, 0x36, 0x31, 0x7d, 0xd6, 0x9f, 0x18, 0x21, 0xf6, 0x9c, 0xf1, 0x22, 0x05, 0xfb, 0x34, 0x6f, 0x55, 0xaa, 0x6c, 0x6f, 0xcf, 0xa5, 0x3b, 0x22, 0xca, 0x54, 0x66, 0x25, 0x76, 0xe9, 0x9b, 0x34, 0x20, 0x6e, 0xc3, 0x97, 0x7f, 0x88, 0x94, 0x29, 0x28, 0x92, 0x6d, 0x6d, 0x97, 0x61, 0x43, 0x7c, 0x55, 0xce, 0x6f, 0x6f, 0x27, 0xd2, 0xd2, 0xc7, 0xf8, 0x0f, 0xdd, 0x06, 0x74, 0xef, 0x7b, 0x21, 0xdb, 0xe9, 0x6b, 0x22, 0x75, 0x8f, 0x89, 0x78, 0x17, 0x74, 0x4f, 0x1a, 0x3c, 0xc0, 0x95, 0x06, 0xff, 0x11, 0x94, 0x56, 0xf7, 0x3e, 0x23, 0xbb, 0xbd, 0x3a, 0xae, 0x50, 0x37, 0x91, 0x89, 0x0d, 0xc4, 0x78, 0xbb, 0x40, 0x8f, 0xae, 0x49, 0xc9, 0xde, 0x97, 0x9f, 0x3b, 0x0d, 0xff, 0x74, 0x8c, 0x57, 0xec, 0x41, 0x47, 0xfa, 0xcf, 0xd3, 0xad, 0xa3, 0x5d, 0x87, 0x76, 0xc1, 0x1e, 0x7d, 0x87, 0x8c, 0x38, 0x32, 0xe3, 0x62, 0x79, 0xda, 0x97, 0x44, 0x4a, 0x55, 0xeb, 0xd3, 0xbf, 0x4b, 0x52, 0xd1, 0xcd, 0x1b, 0x1c, 0x91, 0x56, 0x16, 0xed, 0x5a, 0x7d, 0x93, 0x46, 0x0c, 0x48, 0x3f, 0x3f, 0xfd, 0x30, 0xc6, 0x7b, 0x40, 0xef, 0xeb, 0x97, 0xd4, 0xb7, 0xeb, 0x9e, 0x37, 0x93, 0x96, 0x8a, 0xa7, 0x7e, 0x1f, 0xf4, 0xfb, 0x71, 0x40, 0xff, 0xc1, 0x43, 0x1e, 0x94, 0x13, 0xd7, 0xbe, 0x78, 0x77, 0x7c, 0xc0, 0xa0, 0xae, 0x03, 0xfa, 0x3b, 0x3d, 0xab, 0x88, 0xb4, 0x48, 0xa6, 0xbd, 0x4b, 0x5c, 0x5f, 0xa8, 0xda, 0x17, 0x49, 0x53, 0x53, 0xee, 0x76, 0xca, 0x58, 0xed, 0xa6, 0xe4, 0x36, 0x4f, 0xd0, 0x23, 0x1f, 0x5f, 0x5a, 0x3f, 0xd2, 0x2d, 0x4f, 0x26, 0xcd, 0x48, 0xbe, 0xbb, 0xfc, 0xfe, 0x79, 0x67, 0x9c, 0xe5, 0xea, 0x6f, 0x8b, 0x1a, 0x9c, 0x21, 0xee, 0xdf, 0xe6, 0x1b, 0x0f, 0xda, 0x8a, 0x38, 0xfb, 0xee, 0x2e, 0xbf, 0x7d, 0xc4, 0x19, 0x17, 0xea, 0x7f, 0xf8, 0xc7, 0x7b, 0xdc, 0xed, 0xe1, 0xef, 0xab, 0x58, 0x3f, 0x5b, 0x3c, 0xcc, 0x8c, 0x93, 0x44, 0x19, 0xee, 0x7a, 0xc9, 0xbb, 0x40, 0x34, 0x46, 0x75, 0x3d, 0x5e, 0xb9, 0x21, 0x86, 0x88, 0x31, 0xc2, 0xc8, 0x06, 0xcb, 0x5c, 0xa9, 0xa5, 0xb6, 0x5b, 0x3a, 0xa8, 0xc5, 0x50, 0x2b, 0xa3, 0xa1, 0xaa, 0xaa, 0xae, 0xa9, 0xda, 0x37, 0xa2, 0x06, 0xe2, 0xe4, 0x4a, 0x80, 0xb9, 0x11, 0x2e, 0xef, 0x21, 0x23, 0xe3, 0x9a, 0xc8, 0x41, 0x91, 0x07, 0x81, 0x54, 0x1d, 0xac, 0x5d, 0x6a, 0x3b, 0x9f, 0x28, 0x2b, 0xdc, 0x31, 0xed, 0x8e, 0xb1, 0xd4, 0xb5, 0x54, 0xbc, 0x7a, 0x29, 0x89, 0x0a, 0xaa, 0xfa, 0x3b, 0x12, 0x5a, 0x89, 0x6a, 0xb4, 0x10, 0xc5, 0xac, 0x17, 0xb8, 0x65, 0x64, 0x95, 0x14, 0xa3, 0x82, 0xb4, 0x0f, 0xe2, 0x57, 0x49, 0xf1, 0xa8, 0xa2, 0xea, 0x13, 0x25, 0x25, 0x88, 0xde, 0x8c, 0x6d, 0x93, 0x04, 0xe3, 0x2e, 0xfd, 0x5e, 0xc9, 0xad, 0x46, 0x4b, 0x8a, 0x5a, 0x59, 0xba, 0x68, 0x2d, 0x24, 0xa7, 0x51, 0x99, 0x71, 0x0f, 0x88, 0x94, 0x1c, 0x6e, 0x69, 0x36, 0x84, 0xf6, 0x6e, 0x2a, 0x3c, 0x75, 0x25, 0xc1, 0x85, 0x35, 0x5e, 0x3a, 0xe8, 0xbb, 0xa1, 0xd9, 0x11, 0xec, 0x6b, 0xaf, 0x5f, 0xa5, 0x5e, 0x4b, 0xaa, 0x07, 0xb1, 0x4a, 0xf2, 0xeb, 0xfb, 0x25, 0xca, 0x38, 0x2c, 0x2b, 0x82, 0x40, 0x27, 0x2b, 0x5c, 0xc2, 0x8c, 0xbc, 0x92, 0x51, 0xbf, 0x12, 0x78, 0xa0, 0xbf, 0x21, 0x65, 0xb5, 0x8b, 0x52, 0xdd, 0x85, 0xa7, 0x89, 0xd4, 0xd2, 0x93, 0x65, 0x9a, 0xb6, 0x4f, 0x9a, 0x6b, 0xfb, 0xa5, 0xb9, 0xf5, 0x94, 0x34, 0x37, 0x8f, 0x49, 0x73, 0xcf, 0x64, 0x30, 0x45, 0x9a, 0x33, 0xd6, 0x32, 0x88, 0xae, 0xd2, 0x4a, 0xef, 0x27, 0xcd, 0xd0, 0xa9, 0xb9, 0x0b, 0xf5, 0x90, 0x94, 0x34, 0x4b, 0x48, 0x49, 0x7b, 0xbd, 0x94, 0x34, 0xaa, 0x52, 0x2f, 0x42, 0x7d, 0x95, 0x94, 0xd4, 0x9f, 0x97, 0x52, 0x41, 0x4c, 0xf9, 0x9b, 0x32, 0x54, 0x37, 0x15, 0x20, 0xcc, 0x51, 0x42, 0xf5, 0xbf, 0x41, 0x1a, 0x9d, 0xe7, 0x23, 0xf8, 0x36, 0xa2, 0xbd, 0x96, 0x76, 0x38, 0xb2, 0x92, 0x99, 0x77, 0x9e, 0x76, 0x36, 0xa9, 0x1a, 0xd4, 0xab, 0x93, 0x4c, 0xd3, 0x3b, 0x05, 0x56, 0x81, 0x5f, 0x95, 0x8b, 0xd2, 0x17, 0xd4, 0x24, 0x83, 0x5f, 0xd2, 0xf3, 0x4a, 0x27, 0xe5, 0x62, 0x60, 0xa7, 0x72, 0x29, 0xb0, 0x4b, 0x37, 0xa0, 0x01, 0xea, 0x60, 0x29, 0xa6, 0x9c, 0x95, 0x99, 0x9e, 0xa1, 0xee, 0x9c, 0x10, 0x06, 0x61, 0xcf, 0x00, 0xa9, 0xeb, 0x39, 0x29, 0x71, 0xd8, 0x38, 0x2d, 0x88, 0x2e, 0xd2, 0x52, 0xfd, 0x1a, 0x9f, 0x74, 0x95, 0x1a, 0x2e, 0x3c, 0x31, 0x20, 0x56, 0x6a, 0xe8, 0xf3, 0x64, 0xaa, 0x5a, 0x5b, 0x4a, 0xa9, 0x27, 0xa5, 0xa8, 0xfa, 0x89, 0x3c, 0xa5, 0x9e, 0xc6, 0xc7, 0xdd, 0xa4, 0xa8, 0xfe, 0xb5, 0x14, 0xd1, 0xcf, 0x4a, 0x51, 0x23, 0x51, 0x8a, 0x9a, 0xd7, 0x41, 0x5b, 0x29, 0x62, 0xb6, 0xa3, 0xff, 0x19, 0x19, 0x1b, 0x44, 0x06, 0xc9, 0xea, 0x29, 0x2e, 0xf9, 0xb4, 0x6b, 0x92, 0x55, 0x4f, 0x2f, 0x99, 0xb5, 0x19, 0x12, 0xad, 0x9e, 0x93, 0x4e, 0x5a, 0x41, 0x49, 0xd0, 0xa2, 0xa4, 0xb7, 0x1a, 0x25, 0x45, 0xd4, 0xee, 0x12, 0xa7, 0x66, 0x96, 0xd2, 0x94, 0x65, 0xd5, 0x58, 0x29, 0xad, 0xbc, 0x28, 0x05, 0xd5, 0x5a, 0x52, 0xc8, 0xed, 0x57, 0x0a, 0x63, 0xd3, 0x53, 0x81, 0xcb, 0x6a, 0x0b, 0xea, 0xc5, 0x24, 0x4e, 0x1b, 0x0b, 0x6d, 0x37, 0xd0, 0x13, 0xfa, 0x1e, 0x94, 0xdd, 0xa5, 0x8c, 0xb2, 0x8c, 0xb1, 0xef, 0xa5, 0xa8, 0xe2, 0x97, 0x08, 0xc6, 0x1a, 0xa9, 0x05, 0xc5, 0xd4, 0x5a, 0x93, 0x57, 0x75, 0x25, 0x87, 0x5a, 0x48, 0x0c, 0xe5, 0x96, 0x14, 0xd7, 0x7c, 0x32, 0x0c, 0x84, 0x6b, 0x3e, 0x25, 0x2f, 0x70, 0xa8, 0xb7, 0x06, 0x4d, 0x42, 0x65, 0x3d, 0x32, 0xb8, 0x08, 0x65, 0x8f, 0x50, 0x5f, 0x3c, 0x68, 0x00, 0xfa, 0x81, 0x21, 0x60, 0x38, 0xe8, 0x08, 0xde, 0x02, 0x1f, 0x85, 0xca, 0x2f, 0xc0, 0x56, 0xb0, 0x34, 0x95, 0x9f, 0x92, 0x91, 0xfa, 0x2f, 0xe0, 0x3c, 0xf5, 0x82, 0x20, 0x5a, 0x77, 0x73, 0xb7, 0xad, 0xe4, 0x76, 0x73, 0xd6, 0x88, 0x63, 0x29, 0x3c, 0x25, 0xad, 0xdc, 0x3c, 0xd6, 0x0f, 0x4b, 0x6d, 0xcf, 0x0b, 0x12, 0xeb, 0xe9, 0x21, 0x89, 0x9e, 0xd6, 0x92, 0xac, 0xbf, 0x8a, 0xff, 0x13, 0x65, 0x2a, 0xf1, 0xe8, 0xab, 0xd7, 0xc7, 0x77, 0x2d, 0xd1, 0x61, 0xa6, 0xb4, 0xd4, 0x72, 0x48, 0x53, 0xfc, 0x9e, 0x45, 0x7f, 0x5d, 0xfa, 0x18, 0xd9, 0xf1, 0x7b, 0x1e, 0x99, 0xa0, 0x57, 0x95, 0xa7, 0xf4, 0x2d, 0xd0, 0x5a, 0x92, 0x55, 0xcb, 0x23, 0xcd, 0xb4, 0xfc, 0xd2, 0x58, 0xff, 0x42, 0x1a, 0xea, 0x3d, 0xe4, 0x59, 0x7d, 0x3b, 0x79, 0xbc, 0x86, 0xbe, 0x8d, 0x92, 0x4b, 0x6f, 0x23, 0x63, 0x8d, 0x93, 0xd2, 0xd4, 0xf8, 0x8c, 0xf6, 0xdb, 0x81, 0x07, 0x1e, 0x43, 0x46, 0x7a, 0x3c, 0xae, 0xad, 0x8a, 0x6b, 0x6f, 0x5b, 0x65, 0x8b, 0xb4, 0x74, 0x41, 0xfc, 0x52, 0xf4, 0x0a, 0xa9, 0xf0, 0x94, 0x92, 0x0c, 0x56, 0x94, 0x64, 0xe0, 0x19, 0x2d, 0x1c, 0x7c, 0xe6, 0x1a, 0x81, 0xed, 0x81, 0x3f, 0xac, 0x18, 0x30, 0x2d, 0xf5, 0xf9, 0x33, 0x8e, 0x40, 0x7b, 0x85, 0xe7, 0xfc, 0xd9, 0x40, 0xc0, 0x53, 0x44, 0x1a, 0x7a, 0xc2, 0xa1, 0xa1, 0xcf, 0x28, 0x2b, 0xf1, 0x56, 0x5f, 0x49, 0xb1, 0x8e, 0x52, 0x3f, 0x26, 0xf9, 0xcc, 0x4c, 0xd0, 0xbd, 0x4d, 0x3d, 0x56, 0xca, 0x1b, 0x75, 0xa4, 0xbd, 0x55, 0x8e, 0x7a, 0xe5, 0xc0, 0x1f, 0xc6, 0x87, 0x3c, 0x8b, 0xa7, 0xc1, 0x16, 0x29, 0xaf, 0xbf, 0x8c, 0xbd, 0xad, 0xa4, 0xa9, 0x76, 0x81, 0x7c, 0xab, 0x91, 0x0a, 0xfd, 0x8c, 0x64, 0xf7, 0x54, 0x94, 0x9c, 0xda, 0x44, 0x29, 0xa3, 0x3f, 0x47, 0x3b, 0x17, 0x58, 0x24, 0xe2, 0x99, 0xc5, 0xfa, 0x32, 0x8d, 0x7a, 0x4f, 0x99, 0xe6, 0x49, 0xcb, 0x5d, 0xea, 0xda, 0x2d, 0xc9, 0x83, 0x4f, 0x32, 0x1b, 0xb3, 0xf1, 0xd5, 0xc1, 0xd4, 0x71, 0x3d, 0x9d, 0x34, 0xf3, 0x0c, 0x82, 0xee, 0x3d, 0xea, 0x2f, 0x4b, 0x5e, 0xe3, 0x75, 0xe8, 0x0e, 0x81, 0x77, 0xa5, 0x12, 0x68, 0xee, 0x79, 0x8a, 0xfa, 0x37, 0xac, 0x5d, 0xad, 0x03, 0x5f, 0x40, 0x9f, 0x0f, 0x7f, 0x24, 0x83, 0x8d, 0x6e, 0xa9, 0xfa, 0x64, 0xba, 0xc6, 0xf3, 0xaf, 0x9d, 0x92, 0x78, 0xe5, 0x7d, 0xe9, 0xa6, 0x66, 0x92, 0xe7, 0x94, 0x3b, 0x32, 0x52, 0x39, 0x25, 0x3d, 0x8d, 0x8b, 0xd2, 0x13, 0x9f, 0x75, 0x54, 0x36, 0x4b, 0x67, 0xf5, 0x07, 0xe9, 0xa8, 0x7e, 0x2f, 0x9d, 0xc9, 0xb9, 0xd6, 0xea, 0x45, 0x50, 0x89, 0xbe, 0x68, 0x19, 0xa5, 0x75, 0x97, 0xce, 0x5a, 0x0f, 0xca, 0xd9, 0x52, 0x5b, 0xf9, 0x83, 0x75, 0x4d, 0x64, 0xbb, 0x5b, 0x57, 0x2f, 0xc9, 0x28, 0x7d, 0xaa, 0x6c, 0xf3, 0x54, 0x90, 0xb1, 0xda, 0x62, 0x89, 0xa5, 0x7e, 0x48, 0xcd, 0x28, 0x3d, 0x89, 0x69, 0x5b, 0x10, 0x4d, 0x2e, 0x6f, 0x05, 0x55, 0xd5, 0x8e, 0xd0, 0x76, 0x94, 0x5a, 0x5a, 0x4b, 0x19, 0x06, 0x4d, 0x5f, 0x17, 0xda, 0x77, 0xd2, 0xd9, 0x78, 0x41, 0xea, 0x52, 0x1f, 0x0d, 0x06, 0x2b, 0x4b, 0x65, 0xa4, 0xfa, 0x9b, 0x8c, 0x34, 0xe6, 0x4b, 0x1b, 0xcf, 0x4c, 0xe9, 0xea, 0x19, 0x2d, 0x49, 0x69, 0x30, 0xce, 0x4b, 0x3b, 0xe3, 0x73, 0x79, 0x16, 0x5f, 0xb6, 0x86, 0x76, 0x1c, 0xc8, 0xa5, 0x47, 0xb3, 0xf6, 0x4d, 0x65, 0x9d, 0x9c, 0x2a, 0x53, 0x40, 0x09, 0x50, 0x24, 0x88, 0x68, 0x29, 0xe6, 0x99, 0x26, 0xd1, 0x76, 0x76, 0x79, 0x8f, 0x76, 0x29, 0xfd, 0x77, 0xe9, 0xae, 0xa5, 0x97, 0x02, 0x7a, 0x35, 0x69, 0xa7, 0x3f, 0xc5, 0x7a, 0xd7, 0x5b, 0x0a, 0x69, 0x97, 0xa4, 0xb2, 0x5e, 0x4f, 0x9e, 0xd1, 0x4e, 0x32, 0xde, 0x4c, 0x7a, 0xe0, 0xdb, 0xba, 0xcc, 0x2b, 0x0f, 0x26, 0x81, 0x56, 0x21, 0xd8, 0xa0, 0x13, 0x88, 0x0f, 0x95, 0x1d, 0x41, 0x7b, 0xa3, 0x88, 0x74, 0xc3, 0xa7, 0xf9, 0x58, 0x63, 0x9a, 0x91, 0x53, 0xcd, 0xf5, 0x4d, 0x52, 0x48, 0xaf, 0x2d, 0xf5, 0xf5, 0x22, 0x52, 0x49, 0x6f, 0x10, 0xf4, 0x41, 0x15, 0xb0, 0x34, 0xa4, 0x5b, 0x0b, 0x90, 0x15, 0xf4, 0x02, 0x3d, 0x40, 0x17, 0x75, 0xa9, 0x54, 0x01, 0x6e, 0x59, 0x0f, 0xc4, 0x00, 0x1f, 0x68, 0x0e, 0x9a, 0x81, 0x36, 0xa0, 0x29, 0x88, 0x03, 0xed, 0x40, 0x1f, 0x17, 0x4a, 0x33, 0xa9, 0x0a, 0xca, 0x85, 0xe6, 0x26, 0x82, 0x3a, 0xa0, 0x36, 0xc8, 0x17, 0x9a, 0xeb, 0xce, 0x69, 0x19, 0x2a, 0x5b, 0x28, 0xb9, 0x1e, 0xcc, 0x35, 0x3e, 0x92, 0x67, 0x8c, 0xad, 0xbc, 0x53, 0x90, 0x09, 0xe6, 0x82, 0x31, 0xa0, 0x73, 0xa8, 0x3e, 0x0f, 0x6c, 0x00, 0x2b, 0xc0, 0xb9, 0x10, 0xcd, 0x1b, 0xe0, 0x28, 0x78, 0xd1, 0xd3, 0x5f, 0xba, 0xeb, 0x75, 0xa5, 0x86, 0x71, 0x50, 0x7a, 0xea, 0x93, 0xa5, 0x85, 0xf6, 0x2b, 0x6f, 0xdc, 0xf3, 0xac, 0x87, 0xfd, 0xa4, 0x1e, 0xcf, 0x7e, 0x43, 0x68, 0x92, 0x79, 0xdb, 0xf7, 0x04, 0x43, 0x8c, 0x33, 0xb2, 0x19, 0x6c, 0x01, 0x25, 0xc0, 0x94, 0xb4, 0xd2, 0x1d, 0x77, 0xc7, 0xf0, 0xd5, 0x87, 0xe0, 0xe8, 0x9f, 0x6b, 0x53, 0x20, 0x7f, 0x2a, 0x1e, 0x5b, 0xab, 0xdc, 0xb2, 0x3e, 0x68, 0x9a, 0x5a, 0x06, 0x6e, 0x3d, 0xde, 0x76, 0xc7, 0x95, 0x3c, 0x9e, 0xf5, 0xbc, 0x2b, 0x57, 0xa0, 0xf3, 0x47, 0xf8, 0xfd, 0x01, 0xeb, 0xf0, 0x11, 0xd6, 0x9e, 0x92, 0x32, 0x08, 0x8c, 0x34, 0x56, 0xf1, 0x2c, 0xee, 0x02, 0xc3, 0x24, 0x51, 0x1f, 0x28, 0x1b, 0x79, 0xce, 0x36, 0x12, 0x8f, 0xce, 0xaa, 0x8e, 0x5f, 0x77, 0x4a, 0x45, 0xb5, 0x0d, 0xcf, 0x43, 0x47, 0xca, 0x76, 0x52, 0x4c, 0xbd, 0x26, 0xe9, 0xd5, 0x0b, 0xac, 0xa5, 0xf3, 0x03, 0xb7, 0xd4, 0x79, 0x52, 0x5c, 0xe4, 0x3e, 0x7a, 0x2a, 0x8d, 0x90, 0xb3, 0x0b, 0xd9, 0x9f, 0x83, 0xb5, 0x8f, 0xc8, 0x1f, 0x1e, 0x5a, 0xfb, 0x32, 0x68, 0x8e, 0x2c, 0x7c, 0x04, 0x2f, 0x3d, 0xd1, 0xee, 0x07, 0x3e, 0x48, 0x5d, 0x47, 0xe1, 0xe5, 0xf0, 0x4e, 0xfe, 0x13, 0x2e, 0x6d, 0x45, 0xb0, 0x92, 0xb1, 0x88, 0x54, 0x48, 0x03, 0xd6, 0xc7, 0x7c, 0x7a, 0xa2, 0x32, 0x3e, 0xb8, 0xfb, 0x70, 0xe4, 0x0e, 0x7d, 0xbf, 0x81, 0xda, 0xd4, 0xcf, 0x6a, 0x0e, 0xeb, 0xaf, 0xc3, 0x7a, 0x08, 0x2f, 0xb5, 0xa6, 0xf4, 0xd2, 0x56, 0xca, 0x2a, 0xc6, 0x3e, 0xb0, 0x66, 0x49, 0xcb, 0x10, 0x7a, 0x84, 0x30, 0x30, 0xad, 0xcd, 0xb3, 0x1a, 0xaf, 0xc6, 0x48, 0x2b, 0xa5, 0x39, 0x76, 0x77, 0x93, 0x56, 0x6a, 0x27, 0xe9, 0xce, 0x3b, 0xa8, 0x95, 0xda, 0x39, 0x58, 0x1f, 0xa0, 0xb5, 0x95, 0xfe, 0xca, 0x76, 0xe9, 0xad, 0xbc, 0x21, 0xb5, 0x78, 0x27, 0x55, 0x57, 0x9b, 0x21, 0xcb, 0x27, 0x25, 0x5d, 0x88, 0x04, 0x86, 0x07, 0x6d, 0x0d, 0xca, 0xe6, 0xfd, 0x56, 0x57, 0x8a, 0x1b, 0x2d, 0xa5, 0x8c, 0x51, 0x43, 0x4a, 0x19, 0xd5, 0xa5, 0x83, 0x19, 0x29, 0xc5, 0x59, 0x57, 0x9b, 0xe9, 0x63, 0xa4, 0x85, 0xf1, 0x1a, 0xcf, 0xc7, 0x6a, 0x49, 0xd2, 0x7b, 0x07, 0x06, 0xe2, 0xf3, 0x04, 0xa3, 0x5e, 0xe0, 0xac, 0x3a, 0x4d, 0x36, 0xf0, 0xee, 0x4c, 0xb1, 0x86, 0xca, 0x6a, 0xe3, 0x6e, 0x60, 0xa0, 0xbb, 0xae, 0x9a, 0x67, 0xa5, 0x83, 0x71, 0x95, 0x79, 0xdb, 0x58, 0x37, 0x2b, 0x49, 0x23, 0xa3, 0x96, 0x44, 0xb9, 0x7b, 0x19, 0xf7, 0xfd, 0x60, 0xf8, 0xe5, 0x55, 0x63, 0x63, 0xe0, 0xbe, 0xbe, 0x4f, 0x2a, 0xea, 0x0b, 0xa5, 0x34, 0xeb, 0x61, 0x3c, 0x31, 0x8b, 0x86, 0xbe, 0xbd, 0xd5, 0x8c, 0x38, 0xb2, 0xa7, 0x32, 0xd8, 0x0f, 0x19, 0x1a, 0xb9, 0xf9, 0xb6, 0x94, 0x35, 0x2e, 0x88, 0x87, 0xb5, 0xbe, 0xa3, 0x7e, 0x47, 0xb2, 0x18, 0x73, 0x25, 0xd2, 0xdd, 0x07, 0xb1, 0x66, 0xa7, 0x18, 0x2f, 0x07, 0xd7, 0xe4, 0xb2, 0x46, 0x6d, 0x29, 0xab, 0xf7, 0x95, 0x18, 0x78, 0xa5, 0xe8, 0x5d, 0x03, 0x37, 0xdd, 0x3d, 0x90, 0x3e, 0x39, 0x70, 0x4e, 0x3b, 0xc2, 0x7b, 0xc8, 0xdd, 0x57, 0x64, 0x21, 0xee, 0x1b, 0x02, 0xc3, 0x3c, 0x9b, 0xe8, 0x6f, 0x9f, 0xba, 0x7f, 0xd0, 0x2b, 0x8a, 0xed, 0xa9, 0x4a, 0x2e, 0xb1, 0x47, 0xd0, 0xba, 0xf1, 0x4c, 0x5d, 0xe6, 0x7d, 0xc4, 0x5e, 0x40, 0xdf, 0x25, 0x99, 0xb4, 0x79, 0x52, 0x55, 0x1b, 0x22, 0x31, 0xda, 0x00, 0x89, 0xd4, 0x46, 0xb1, 0x5e, 0x44, 0x4a, 0x23, 0xde, 0x61, 0xa2, 0x0d, 0x0e, 0xfc, 0x68, 0x78, 0xe1, 0x57, 0x4c, 0x1a, 0x6a, 0xab, 0x58, 0xe3, 0xa2, 0xa5, 0xab, 0xfe, 0x16, 0x7b, 0x91, 0x9f, 0xd8, 0xdb, 0xdc, 0x90, 0x96, 0x2e, 0x82, 0xfb, 0x8c, 0x04, 0xa9, 0xcc, 0x9a, 0x68, 0xba, 0x7b, 0x0f, 0xf6, 0x49, 0x85, 0xd5, 0x2d, 0x81, 0x39, 0xfa, 0x24, 0x89, 0xd5, 0x46, 0xb2, 0x46, 0x15, 0x93, 0xa7, 0xf5, 0x14, 0xe4, 0x8f, 0x42, 0x2e, 0x3c, 0xf4, 0x8c, 0xac, 0xe1, 0x55, 0xa4, 0x0c, 0x7b, 0xc5, 0x69, 0xee, 0xfb, 0xed, 0xe1, 0xde, 0xea, 0x11, 0xfc, 0xed, 0x9e, 0x8a, 0x7d, 0x8a, 0xbb, 0x37, 0x61, 0xdf, 0x37, 0x06, 0x1b, 0x5b, 0x04, 0x6d, 0x76, 0xf7, 0x7e, 0x87, 0xa5, 0x9c, 0x76, 0x2e, 0x64, 0xb7, 0x0b, 0x57, 0x77, 0xc0, 0xda, 0x5a, 0xcf, 0x7d, 0x07, 0xeb, 0x27, 0xf0, 0x77, 0xef, 0xe0, 0x5e, 0xb0, 0x15, 0x7b, 0xdd, 0xa6, 0xda, 0x99, 0xc0, 0x35, 0xdd, 0x2b, 0x1e, 0xf6, 0x4f, 0x63, 0xd3, 0xf6, 0x51, 0xac, 0x99, 0xf9, 0x94, 0x93, 0xbc, 0x93, 0x97, 0xf2, 0xce, 0x19, 0x2a, 0x63, 0xc1, 0xb8, 0xd4, 0x7d, 0x56, 0x60, 0x42, 0x90, 0xa6, 0x67, 0xea, 0xfb, 0x8a, 0x77, 0x65, 0x33, 0xe3, 0x0e, 0xeb, 0xce, 0x19, 0x9e, 0xa9, 0x34, 0xb0, 0xd7, 0x0a, 0x62, 0x8a, 0x1c, 0x06, 0x2a, 0x31, 0x70, 0x75, 0x9a, 0xaa, 0xf5, 0x96, 0x3a, 0xca, 0xc9, 0x40, 0x7d, 0x50, 0x4f, 0x2d, 0x1b, 0x68, 0x0b, 0x6d, 0x56, 0xf5, 0x7d, 0xec, 0x78, 0x8f, 0x1c, 0xd8, 0x28, 0x6d, 0x78, 0x5f, 0x0f, 0x64, 0x2d, 0xce, 0xe2, 0xee, 0xad, 0x28, 0xd3, 0x69, 0x07, 0x88, 0xc3, 0x4c, 0x7c, 0x3f, 0x1a, 0x5f, 0xe2, 0x77, 0x6d, 0x3c, 0x6b, 0xe5, 0xb1, 0x40, 0x01, 0xf5, 0x18, 0x39, 0x7b, 0x9c, 0xfd, 0xd0, 0x4a, 0xf6, 0x10, 0x87, 0xc8, 0x9b, 0xde, 0xd8, 0x51, 0x11, 0x1d, 0xca, 0x05, 0xda, 0xea, 0xba, 0x98, 0xea, 0xcf, 0x41, 0x3f, 0xb4, 0x34, 0x23, 0x40, 0x47, 0x9e, 0x8d, 0x4c, 0xd2, 0x92, 0x75, 0xab, 0x90, 0x6e, 0x4a, 0x11, 0xe3, 0x13, 0x29, 0xe2, 0x59, 0xc2, 0xbe, 0x33, 0x9b, 0x8c, 0x37, 0xf2, 0xc8, 0x44, 0xbd, 0xa8, 0xf4, 0xc6, 0xa6, 0xe1, 0x41, 0x68, 0x92, 0xd5, 0x88, 0x65, 0x5f, 0xd1, 0x48, 0x9a, 0x50, 0x2f, 0xaf, 0xb9, 0xf9, 0xfe, 0x34, 0xbc, 0xba, 0x4b, 0x76, 0xad, 0x1d, 0x36, 0xcc, 0x64, 0x5f, 0xf6, 0x19, 0xef, 0xd3, 0x01, 0x92, 0x41, 0xbb, 0x4e, 0x3b, 0x4a, 0x6a, 0xeb, 0x2b, 0xf0, 0xdf, 0xd7, 0x52, 0x81, 0x7d, 0x7b, 0x73, 0x72, 0xb9, 0x15, 0x7b, 0xfd, 0x96, 0x9e, 0x4f, 0xe9, 0x3b, 0xc4, 0xbb, 0x6a, 0x31, 0xb9, 0x0b, 0x98, 0xe7, 0xda, 0x1f, 0x19, 0x44, 0x63, 0xc9, 0xce, 0xfb, 0x62, 0xb4, 0x7e, 0x94, 0x77, 0x5c, 0x75, 0xe6, 0x1c, 0x92, 0x04, 0x64, 0x65, 0x31, 0x2e, 0xb3, 0x8f, 0x9b, 0x46, 0x1e, 0x0c, 0x44, 0x6e, 0x17, 0xde, 0x23, 0x4d, 0x88, 0x6b, 0x6e, 0x64, 0x2b, 0x92, 0x39, 0xe8, 0xeb, 0x9d, 0xc4, 0xb3, 0xa1, 0xe4, 0x51, 0xaf, 0x4b, 0x46, 0xed, 0x53, 0xce, 0x38, 0xef, 0xa2, 0xc7, 0x57, 0xe2, 0xd3, 0x8a, 0x52, 0x0e, 0x0a, 0xa2, 0xba, 0xa6, 0x4b, 0x05, 0xad, 0xb2, 0x24, 0x2b, 0x1d, 0xa5, 0x0b, 0xef, 0xf6, 0xdc, 0xe4, 0x73, 0x32, 0xef, 0xe3, 0x64, 0xed, 0x2a, 0xa0, 0x5f, 0xcb, 0xc2, 0x5e, 0x33, 0x5e, 0x2a, 0xa8, 0xcb, 0xa4, 0x84, 0xba, 0x8a, 0xb1, 0xb7, 0xc1, 0x30, 0xc9, 0xa3, 0x85, 0xf1, 0xae, 0x4a, 0xe0, 0x5d, 0x12, 0xc5, 0x3e, 0x93, 0xbd, 0x27, 0xb9, 0x5b, 0x96, 0x75, 0xa4, 0x8c, 0xf2, 0x93, 0x94, 0x53, 0xae, 0x8b, 0x8f, 0x3d, 0x6a, 0xcd, 0xe0, 0x7e, 0x36, 0x0b, 0x7b, 0xd8, 0xde, 0x52, 0x46, 0xed, 0xc3, 0xf8, 0x0b, 0xac, 0x1d, 0x63, 0xc4, 0x5d, 0x9b, 0x72, 0xa9, 0xbd, 0x18, 0x8b, 0x91, 0x0c, 0x6a, 0x7b, 0xe2, 0xc2, 0x7a, 0xa1, 0xda, 0x62, 0xf3, 0xde, 0x6f, 0xe0, 0xd6, 0xd9, 0xaf, 0x8c, 0x35, 0x16, 0xe0, 0xfb, 0x11, 0x12, 0xa9, 0xe7, 0x23, 0x7e, 0x27, 0xd0, 0xe1, 0x04, 0xfb, 0xbd, 0x63, 0x3c, 0xaf, 0xe7, 0xa5, 0xbc, 0x3a, 0x81, 0x18, 0xbf, 0xca, 0x73, 0xe4, 0xf2, 0x0f, 0x67, 0xff, 0xdb, 0x53, 0x4a, 0x21, 0xd7, 0x87, 0x0d, 0x86, 0xbb, 0xef, 0x55, 0x9e, 0x61, 0x8f, 0xdf, 0x5a, 0x9a, 0xb0, 0x67, 0x6e, 0xa1, 0xf4, 0x95, 0xca, 0xca, 0xc8, 0xc0, 0xe9, 0x60, 0x3b, 0x5e, 0x62, 0x39, 0xb7, 0x05, 0xf7, 0xce, 0xa0, 0x91, 0xda, 0x23, 0xb0, 0x3d, 0xb8, 0x77, 0xee, 0x27, 0x2d, 0xdc, 0x7d, 0xb4, 0x2b, 0x5b, 0xf9, 0x51, 0x4a, 0x28, 0xc5, 0x02, 0x3b, 0x94, 0x3d, 0x52, 0x40, 0x9d, 0xcd, 0x9e, 0x7c, 0x2a, 0xef, 0xd0, 0x91, 0xd2, 0x5e, 0x6d, 0x2e, 0xb1, 0xee, 0x7e, 0x9a, 0x33, 0x64, 0x71, 0x75, 0x84, 0x14, 0x57, 0x2e, 0x04, 0x7e, 0x57, 0x67, 0xf0, 0x3e, 0x1d, 0xcf, 0x5e, 0x68, 0x83, 0xe4, 0x51, 0xf6, 0x4b, 0x49, 0xb5, 0x1e, 0xef, 0xe8, 0x79, 0x72, 0x58, 0x0f, 0x97, 0xc3, 0xec, 0x8f, 0x7a, 0xb2, 0x7f, 0x4f, 0x02, 0x89, 0x6a, 0x11, 0x89, 0x04, 0x55, 0x35, 0x3f, 0xfb, 0x69, 0xa0, 0xfc, 0xca, 0x1e, 0xe7, 0x33, 0xd9, 0xeb, 0x82, 0x75, 0x73, 0xd6, 0xa3, 0x60, 0x59, 0x9f, 0xa5, 0x26, 0xc2, 0xeb, 0x07, 0x79, 0xe5, 0x49, 0xb0, 0x07, 0x99, 0xe1, 0x82, 0xf3, 0x47, 0x69, 0x77, 0xdd, 0xd5, 0x6f, 0x4b, 0x4e, 0xf6, 0x14, 0x29, 0xc6, 0x58, 0x9e, 0xe3, 0xd1, 0xac, 0xbf, 0xaf, 0x51, 0x7f, 0x95, 0x5c, 0x99, 0x4d, 0x7e, 0xb3, 0x87, 0xd6, 0xd6, 0xb3, 0x7f, 0xdb, 0xc0, 0xd9, 0xee, 0x1e, 0xeb, 0xd3, 0x4a, 0xce, 0x79, 0x1f, 0x48, 0x38, 0xfb, 0xe3, 0x51, 0x7a, 0xcf, 0xc0, 0xe5, 0xe0, 0x7e, 0x78, 0x26, 0x75, 0x9e, 0x47, 0xed, 0xae, 0x64, 0x37, 0xca, 0x91, 0x2f, 0x1b, 0xc5, 0xab, 0x2d, 0x0f, 0xae, 0x6d, 0x51, 0x3a, 0xe7, 0x42, 0xf6, 0x6d, 0xab, 0x3c, 0x55, 0xe0, 0x19, 0x25, 0x85, 0xc9, 0xd9, 0xc2, 0xfa, 0x6f, 0xac, 0xd5, 0xb3, 0x24, 0xbd, 0xb1, 0x82, 0xbe, 0xb9, 0x20, 0x45, 0x0a, 0x7b, 0xe2, 0x24, 0x9f, 0xa7, 0x2d, 0xe7, 0xd3, 0x3b, 0x8c, 0x8d, 0x60, 0x6f, 0x3c, 0x9a, 0xbc, 0xd8, 0x20, 0xf9, 0x8d, 0x0c, 0x92, 0xd1, 0x78, 0x46, 0x32, 0x98, 0x85, 0x24, 0x9f, 0x31, 0x53, 0xe2, 0xdd, 0x7d, 0x31, 0xef, 0xdf, 0x14, 0xcf, 0x06, 0xce, 0x01, 0xc0, 0x98, 0x44, 0x5d, 0x61, 0x1f, 0xec, 0x9e, 0x67, 0x1b, 0xa4, 0xee, 0x9b, 0xcd, 0xf9, 0x94, 0xee, 0x7b, 0x82, 0xb5, 0xdd, 0x7d, 0x17, 0xb0, 0x77, 0x4a, 0x41, 0x6e, 0x21, 0xa3, 0x8f, 0x64, 0xe0, 0x1d, 0x5d, 0x9d, 0x7d, 0x7d, 0x25, 0x4f, 0x49, 0x90, 0x51, 0x72, 0xf3, 0x1e, 0xcf, 0x65, 0xfa, 0x02, 0x7f, 0x98, 0xdd, 0x99, 0x73, 0x13, 0x99, 0xf7, 0x25, 0xaf, 0xfb, 0x7e, 0xe1, 0x3d, 0xf2, 0x87, 0xfe, 0x61, 0x70, 0xbf, 0x1c, 0xa5, 0x2d, 0x90, 0x12, 0xda, 0x17, 0xac, 0x45, 0xcb, 0xf0, 0xd3, 0xd8, 0xe0, 0x33, 0x39, 0x4d, 0xef, 0x8f, 0x6d, 0x57, 0xa0, 0x65, 0x8f, 0x6c, 0x7c, 0xca, 0x58, 0x17, 0xfa, 0x4e, 0xe2, 0x83, 0x00, 0xfb, 0x67, 0x4b, 0x32, 0xe9, 0x99, 0x24, 0x1b, 0xcf, 0x51, 0x16, 0xde, 0x11, 0x79, 0x59, 0x87, 0x9b, 0xb1, 0xc6, 0x35, 0x65, 0x7d, 0x98, 0x66, 0xe4, 0xe3, 0xbd, 0x06, 0xd8, 0x13, 0x4e, 0xc3, 0x0f, 0x12, 0x7c, 0xbe, 0x58, 0xc3, 0x75, 0x87, 0xb3, 0xc5, 0x37, 0x94, 0xac, 0x95, 0xda, 0xe8, 0xc0, 0x6f, 0x41, 0x7f, 0xd6, 0x66, 0xdd, 0x5a, 0xc8, 0xfe, 0xb2, 0x31, 0x67, 0x96, 0xb2, 0x52, 0x1e, 0x7d, 0x5b, 0x18, 0x45, 0x39, 0xf3, 0x7d, 0x42, 0x8c, 0xea, 0xb0, 0x87, 0xcf, 0x0e, 0x7d, 0x3c, 0x7c, 0x17, 0xf2, 0x1c, 0xb9, 0x6b, 0xeb, 0xf7, 0xec, 0xe9, 0xf7, 0x41, 0x93, 0x24, 0x1d, 0x8d, 0x51, 0x9c, 0x83, 0xa6, 0x4a, 0x3d, 0x50, 0x81, 0xf7, 0xcc, 0xd3, 0xee, 0x3e, 0xd5, 0x1c, 0x2f, 0x1f, 0x5a, 0xcd, 0xa5, 0xa3, 0x67, 0xad, 0x1c, 0x45, 0x56, 0x53, 0xde, 0x67, 0x4d, 0xed, 0x59, 0x9c, 0x45, 0xbf, 0x09, 0xee, 0x1b, 0xc3, 0x99, 0xdb, 0x9e, 0x78, 0x76, 0xd3, 0xf7, 0xc8, 0xd3, 0x46, 0x77, 0x69, 0xef, 0x19, 0x8c, 0x0d, 0xab, 0x64, 0x13, 0x71, 0x70, 0xf7, 0xcd, 0x73, 0xf1, 0xcf, 0x70, 0xca, 0x45, 0x5a, 0x11, 0xa5, 0x92, 0x56, 0x24, 0xf0, 0x93, 0x71, 0x26, 0xf0, 0x91, 0x71, 0x46, 0xdd, 0x9c, 0x8a, 0xb4, 0x7a, 0xe0, 0x23, 0x6b, 0x18, 0xef, 0xe1, 0x82, 0xf0, 0x24, 0x4e, 0x9c, 0x25, 0x12, 0x8d, 0xe6, 0x81, 0x0b, 0xc6, 0x52, 0xde, 0x55, 0x13, 0xe5, 0x29, 0xcf, 0x10, 0xf6, 0xd0, 0xcd, 0x59, 0x4f, 0xe2, 0xb1, 0x63, 0x8a, 0xd4, 0xd1, 0x7b, 0x05, 0x6e, 0xe9, 0x33, 0x65, 0xa0, 0xb6, 0x57, 0xf2, 0x98, 0x5d, 0xa5, 0x85, 0xd5, 0x43, 0xba, 0x98, 0x83, 0x88, 0xc5, 0x38, 0x62, 0xf2, 0x39, 0x31, 0x7d, 0x56, 0x72, 0x58, 0x94, 0xac, 0xfb, 0xb9, 0x9d, 0xba, 0xe8, 0xb4, 0x4a, 0x12, 0xac, 0x9e, 0xd8, 0x76, 0x57, 0x9e, 0xc6, 0xfe, 0x81, 0xc6, 0xc0, 0xe0, 0x7a, 0x6b, 0x7a, 0xca, 0xe0, 0xdb, 0xcc, 0xf8, 0xe5, 0x3b, 0xd6, 0xbb, 0x41, 0x92, 0x0b, 0x1d, 0x9a, 0xeb, 0x37, 0x79, 0x47, 0xe4, 0x96, 0x36, 0xbc, 0x0b, 0x9b, 0xf3, 0x3c, 0xe4, 0x08, 0x9d, 0x43, 0xdd, 0x3d, 0x5f, 0x3c, 0x7b, 0x8e, 0xf7, 0xc1, 0x41, 0xfa, 0x97, 0xbb, 0x60, 0xaf, 0xd9, 0xdc, 0x7a, 0x4d, 0x1a, 0xfc, 0x4f, 0xc3, 0xfd, 0x8d, 0x00, 0x9f, 0x75, 0x03, 0x1d, 0x3c, 0x4d, 0xd8, 0x63, 0x2b, 0xf2, 0x6c, 0x68, 0xff, 0x1d, 0x0b, 0x3a, 0xb3, 0xcf, 0x6d, 0xcd, 0x7e, 0xa4, 0xad, 0x71, 0x1c, 0x3d, 0x47, 0x48, 0x0b, 0xbb, 0x80, 0xb4, 0x41, 0xaf, 0x07, 0x5a, 0x32, 0xfb, 0xa7, 0xcb, 0x9c, 0x41, 0xdd, 0x33, 0x4e, 0x45, 0x19, 0xa7, 0xb6, 0x64, 0x3f, 0xde, 0x52, 0x5e, 0xe0, 0x1c, 0xd7, 0x07, 0xbc, 0xc0, 0x3a, 0xe0, 0x9e, 0x67, 0x0e, 0x85, 0x30, 0x42, 0xad, 0x22, 0x4b, 0x38, 0x67, 0xb5, 0x55, 0x4b, 0x73, 0x8e, 0x2a, 0x2d, 0xf5, 0xd5, 0xe7, 0x38, 0x0f, 0x3d, 0x27, 0x0d, 0xd4, 0x81, 0xd2, 0x10, 0x34, 0xd1, 0x8e, 0x05, 0xae, 0xba, 0x67, 0x1b, 0x9e, 0x93, 0xee, 0xe4, 0x6e, 0x7b, 0x7d, 0x81, 0x74, 0x31, 0xb2, 0x48, 0x5b, 0x9e, 0xfb, 0xe6, 0x7a, 0x75, 0xf6, 0x53, 0xe4, 0x87, 0x51, 0x9e, 0x3d, 0xcc, 0xdb, 0xc4, 0xe6, 0x7b, 0xf6, 0x48, 0x4d, 0xd9, 0x5b, 0x1d, 0xe6, 0xdc, 0x12, 0xcd, 0xb3, 0x1e, 0x2d, 0x43, 0xc0, 0x08, 0xd0, 0x04, 0xd4, 0x04, 0x2d, 0x40, 0x25, 0xfd, 0x2b, 0xfc, 0x79, 0x0f, 0x1e, 0x19, 0x78, 0xd7, 0xf4, 0x20, 0x26, 0xe3, 0xc8, 0xa9, 0xce, 0xbc, 0x7b, 0xdc, 0xf1, 0x05, 0xd8, 0x7c, 0x1c, 0x39, 0xd1, 0x8c, 0x47, 0xcb, 0x73, 0x60, 0x61, 0xa8, 0x9c, 0x03, 0x56, 0x83, 0x37, 0xc1, 0x7b, 0xa1, 0x33, 0x8e, 0xdb, 0x3f, 0x11, 0xf4, 0x06, 0xee, 0xd9, 0xa3, 0xdc, 0x13, 0x67, 0x90, 0x78, 0xd0, 0x36, 0xd4, 0x7e, 0x3a, 0x74, 0x06, 0x71, 0xcf, 0x31, 0x35, 0x41, 0xb1, 0xd0, 0x39, 0x24, 0x26, 0xd4, 0x1f, 0x05, 0x6d, 0x34, 0xa8, 0xf0, 0x37, 0x7c, 0x12, 0x40, 0x9b, 0xd0, 0x59, 0x26, 0x22, 0x74, 0xee, 0x79, 0xf2, 0xec, 0xd3, 0x21, 0x14, 0x9b, 0x47, 0xf9, 0x37, 0x0e, 0xf1, 0xaf, 0x05, 0x8a, 0xc2, 0xa3, 0x4a, 0xe8, 0x4c, 0x54, 0x2e, 0xc4, 0xb3, 0xad, 0xba, 0x83, 0x18, 0xa5, 0xce, 0x75, 0x75, 0x48, 0x7e, 0x64, 0x6e, 0xc3, 0xd0, 0x5c, 0xb7, 0x5d, 0x04, 0x54, 0xf8, 0x1b, 0xdb, 0x5c, 0xf9, 0x7d, 0x43, 0x7a, 0x75, 0x02, 0x0d, 0x42, 0xb2, 0xb2, 0x3f, 0x72, 0xc6, 0x7a, 0xf4, 0xac, 0x95, 0x26, 0xa7, 0x73, 0x88, 0xce, 0x95, 0xd3, 0x22, 0x54, 0x77, 0xfb, 0x8b, 0xc1, 0xb3, 0x22, 0xa8, 0xa4, 0xfe, 0x2a, 0x33, 0x25, 0x99, 0xb5, 0xa2, 0x98, 0xc4, 0x79, 0xb2, 0xb1, 0x66, 0x02, 0xf7, 0x3d, 0x2c, 0x72, 0xcf, 0xeb, 0x42, 0xed, 0xc4, 0xfb, 0xc7, 0x3d, 0x9b, 0x24, 0x07, 0x1e, 0xa8, 0xe5, 0x28, 0x0b, 0xb2, 0x17, 0x4a, 0x0e, 0xee, 0x31, 0x9b, 0x6b, 0xf3, 0x59, 0x43, 0xf7, 0xca, 0xd3, 0xc1, 0x3d, 0xd4, 0x76, 0xce, 0xee, 0xeb, 0xa4, 0x98, 0x56, 0x5f, 0x3a, 0x6a, 0xad, 0x38, 0x87, 0x26, 0x4a, 0x27, 0xf6, 0x20, 0x6d, 0xb5, 0xe6, 0xd2, 0x41, 0x2b, 0x01, 0x06, 0x49, 0x15, 0x2d, 0x47, 0x30, 0x77, 0x2f, 0x82, 0x9a, 0xee, 0xb9, 0x82, 0x58, 0x2e, 0xe5, 0xfd, 0xd5, 0x86, 0x77, 0x4e, 0x1b, 0x75, 0x2f, 0x7b, 0x80, 0xd4, 0x33, 0x48, 0x91, 0x10, 0x9a, 0xa4, 0xfe, 0xb6, 0x13, 0xfc, 0x8d, 0xc8, 0x3d, 0xf3, 0xcc, 0x01, 0x53, 0xc0, 0xca, 0x50, 0x39, 0x0b, 0xa4, 0x0b, 0x21, 0x3e, 0x75, 0x4e, 0xf0, 0xf7, 0xa3, 0xe1, 0x9c, 0x5a, 0xba, 0x70, 0xbe, 0x13, 0xde, 0x61, 0x92, 0x56, 0xaa, 0x8b, 0x45, 0x78, 0xa7, 0x8a, 0xd2, 0x14, 0xb9, 0x53, 0x29, 0xb7, 0xa0, 0xc0, 0x1f, 0x60, 0x0c, 0xe3, 0xbe, 0xc0, 0x35, 0xe6, 0x19, 0x69, 0xb2, 0x79, 0xde, 0x4a, 0x5a, 0x6f, 0x4b, 0x47, 0x6b, 0xb4, 0xd4, 0x77, 0xcf, 0x89, 0x50, 0xdd, 0x36, 0x4a, 0xcb, 0xb1, 0x54, 0xdd, 0x1f, 0x64, 0x74, 0xf9, 0xa8, 0x19, 0x64, 0x12, 0xef, 0x5c, 0xb1, 0xd6, 0x4a, 0xbd, 0x20, 0xfe, 0x17, 0xd6, 0x06, 0xdd, 0x8f, 0xdf, 0x4a, 0xcb, 0x24, 0x17, 0xd4, 0xfb, 0x52, 0x8e, 0x4d, 0x8d, 0x8b, 0xfb, 0xe7, 0x5e, 0xb1, 0xb4, 0x5f, 0xa5, 0x1f, 0xf9, 0x1d, 0xcd, 0xb5, 0xbf, 0x7b, 0xe8, 0x37, 0x34, 0x6f, 0xe8, 0x77, 0x96, 0xb4, 0xdf, 0xd6, 0x5a, 0xa5, 0xfd, 0xf6, 0x86, 0xbd, 0xe7, 0xf1, 0xf7, 0xc4, 0x90, 0x7f, 0xdd, 0xb1, 0x0e, 0x9a, 0xef, 0xc1, 0x0f, 0xf4, 0xbf, 0x84, 0x8d, 0xf7, 0x42, 0x7d, 0x0d, 0x42, 0x73, 0x5d, 0xfa, 0x8b, 0x21, 0x9e, 0xf0, 0x0f, 0xac, 0x0c, 0xfd, 0x36, 0x97, 0x2a, 0xf3, 0x92, 0xb8, 0xe7, 0xc9, 0x57, 0x42, 0x48, 0x3d, 0x1b, 0xba, 0xe7, 0xcc, 0xc7, 0xcf, 0x9f, 0xf3, 0xf4, 0x6f, 0xd9, 0xe7, 0xbb, 0x48, 0x94, 0x0b, 0xa1, 0xf8, 0xbd, 0x0d, 0x66, 0x30, 0x56, 0xc3, 0x68, 0x23, 0x0b, 0x5d, 0x84, 0xf4, 0x7f, 0x14, 0xc3, 0xd3, 0x60, 0x54, 0xe1, 0xac, 0x16, 0xfa, 0x9d, 0x96, 0xf6, 0x28, 0xf7, 0x37, 0x40, 0xf7, 0x37, 0x57, 0x6d, 0x10, 0x67, 0x9e, 0x39, 0xb2, 0xd5, 0x30, 0x39, 0x5b, 0x4f, 0x96, 0x41, 0xc4, 0xd6, 0xcd, 0xb1, 0xdd, 0xb8, 0x65, 0x58, 0x2a, 0x02, 0xef, 0x07, 0x7f, 0x13, 0x5e, 0x9b, 0x0a, 0xf7, 0x37, 0x5d, 0x17, 0x5a, 0x7c, 0xe0, 0xae, 0xb1, 0x55, 0xda, 0x19, 0x8b, 0xc1, 0x46, 0xb0, 0x2e, 0x58, 0xb6, 0xe1, 0xcc, 0xd8, 0x86, 0x7d, 0x78, 0x1d, 0xde, 0x73, 0x6d, 0x8d, 0x8f, 0x41, 0x02, 0xeb, 0x72, 0x1d, 0x49, 0x50, 0x73, 0x07, 0x36, 0xe9, 0x3e, 0xf1, 0xe9, 0x85, 0x38, 0x37, 0x0d, 0x24, 0xf7, 0x53, 0xf5, 0xc3, 0x67, 0x72, 0xc4, 0xdd, 0x67, 0xb9, 0xbf, 0xb9, 0xbb, 0xcf, 0x85, 0xeb, 0x73, 0x3d, 0x39, 0x30, 0x3f, 0xf8, 0x8c, 0x94, 0xa6, 0xef, 0x19, 0x19, 0xab, 0x3d, 0xc7, 0xbe, 0xd5, 0xfd, 0xcd, 0x7b, 0x34, 0x6d, 0x72, 0x5e, 0xe3, 0x19, 0x34, 0xd6, 0xc8, 0x61, 0xf6, 0xf9, 0xd1, 0xda, 0x26, 0x99, 0x1e, 0xfc, 0xfd, 0xaf, 0x82, 0xb4, 0x0f, 0xd2, 0xec, 0x63, 0x9d, 0xad, 0xcc, 0x7b, 0x16, 0x7b, 0xd5, 0x32, 0xf2, 0x32, 0xfc, 0x22, 0xdc, 0xbd, 0x9d, 0xbb, 0xc7, 0xd3, 0xde, 0xe3, 0xac, 0x51, 0x5f, 0x2a, 0x98, 0x9b, 0xd8, 0xaf, 0x26, 0x07, 0x16, 0x68, 0xdd, 0x02, 0xbf, 0x6b, 0xcf, 0x06, 0x3e, 0xd2, 0xfa, 0xc0, 0xff, 0xa2, 0xf4, 0x52, 0x9e, 0x92, 0x9a, 0x0a, 0xfb, 0xaa, 0xe0, 0x6f, 0xb2, 0x85, 0xc4, 0x62, 0xaf, 0x19, 0xa7, 0xd6, 0x97, 0xcc, 0x2a, 0xe7, 0x15, 0xf6, 0xb7, 0x25, 0xd3, 0xf8, 0x90, 0xc3, 0xfd, 0x5d, 0x84, 0x7e, 0xa3, 0x6d, 0x1d, 0x82, 0xeb, 0xe7, 0x1e, 0xf8, 0x6b, 0x4a, 0x5a, 0x3b, 0xf8, 0xdb, 0x83, 0xc3, 0x5e, 0x78, 0x6a, 0x30, 0xc7, 0x76, 0x85, 0x62, 0xb9, 0xd2, 0x3d, 0xa7, 0xf3, 0x9e, 0x18, 0xcc, 0x39, 0x6a, 0xb0, 0x7b, 0xde, 0x52, 0xef, 0xb2, 0x9f, 0x3f, 0x29, 0xe1, 0xd0, 0xb7, 0x25, 0x2f, 0x03, 0x94, 0xf7, 0xc0, 0x8f, 0xa0, 0x3c, 0x88, 0x0a, 0x95, 0x85, 0x41, 0x83, 0x50, 0x3b, 0x1a, 0x14, 0x01, 0xcd, 0x40, 0x6b, 0xd0, 0x0e, 0x54, 0x07, 0xab, 0x5c, 0x99, 0x80, 0x3c, 0xbc, 0x3f, 0x3a, 0xc4, 0xe3, 0x17, 0x70, 0x1c, 0xf0, 0x2e, 0xbe, 0x5f, 0x11, 0xd4, 0x49, 0xc5, 0x03, 0xb4, 0xba, 0xff, 0x0e, 0x58, 0x43, 0xbd, 0x4f, 0x48, 0x46, 0xf9, 0x10, 0xaf, 0x06, 0xa9, 0xbc, 0x1f, 0x58, 0x21, 0x44, 0x85, 0xe6, 0xbb, 0xfd, 0x53, 0xc0, 0x40, 0xd0, 0x0d, 0x8c, 0x07, 0xfd, 0x52, 0xeb, 0xf7, 0xae, 0xa4, 0xc2, 0xd5, 0xed, 0xc1, 0x07, 0x7a, 0x5b, 0x79, 0xe5, 0x51, 0xb0, 0x46, 0xf5, 0xa1, 0xfc, 0x0b, 0xe8, 0xef, 0x40, 0xf9, 0x17, 0xd0, 0xdf, 0x8e, 0xf2, 0x2f, 0x70, 0xd7, 0x3a, 0xca, 0xbf, 0x80, 0xfe, 0xbe, 0x94, 0x7f, 0x87, 0xff, 0x2d, 0x3d, 0xfe, 0x89, 0xfe, 0xff, 0x15, 0x3d, 0xfe, 0xc9, 0xcf, 0xff, 0x2b, 0x7a, 0x84, 0xf2, 0xf1, 0xe7, 0x50, 0x2e, 0xfe, 0xfc, 0xe4, 0xb3, 0x90, 0xfa, 0x1d, 0x24, 0xf8, 0x3d, 0x24, 0x1f, 0x34, 0x7a, 0x28, 0x0f, 0x1b, 0xa4, 0xe6, 0xa5, 0x9b, 0x9f, 0x69, 0xb9, 0x7a, 0xdf, 0x0e, 0xee, 0xed, 0x79, 0xee, 0x58, 0xbf, 0xca, 0xfe, 0x17, 0x72, 0xd5, 0x9d, 0xf7, 0x01, 0xf5, 0xaa, 0x8f, 0x3c, 0x5b, 0xaf, 0x83, 0xf5, 0x80, 0x37, 0xdc, 0xbd, 0x4f, 0xfe, 0x7c, 0x8e, 0x1e, 0xe4, 0x4a, 0xc5, 0xbd, 0xcc, 0xe0, 0x23, 0x00, 0xcf, 0xbb, 0xec, 0x6f, 0xef, 0xa1, 0xd3, 0xbd, 0xdb, 0x21, 0x1e, 0xe8, 0x74, 0x8f, 0x77, 0xdd, 0xbd, 0x32, 0x80, 0x13, 0xc3, 0xbd, 0x15, 0x80, 0xfe, 0x7b, 0x2d, 0x42, 0xcf, 0xeb, 0xa3, 0x68, 0xf7, 0x38, 0xd2, 0xf4, 0x4a, 0x45, 0xf0, 0xfb, 0xe4, 0x7f, 0x15, 0xcd, 0x02, 0x81, 0xff, 0x88, 0xfe, 0x5f, 0xa1, 0x43, 0x5a, 0x3d, 0xd0, 0x0c, 0x54, 0x06, 0xcb, 0x43, 0xed, 0x06, 0xa0, 0xe2, 0x23, 0xed, 0x3b, 0x7f, 0x9d, 0xff, 0x2f, 0xe6, 0x60, 0x71, 0x10, 0xf4, 0xbb, 0xdf, 0x5c, 0x83, 0xa8, 0x13, 0xb8, 0x11, 0xfc, 0xf6, 0xea, 0xa2, 0xa0, 0xd8, 0x0f, 0xeb, 0x2e, 0xca, 0xb3, 0x47, 0x7e, 0x14, 0xee, 0xb7, 0xd9, 0xa6, 0xe2, 0x0b, 0xae, 0xef, 0x6e, 0x9b, 0x77, 0xc6, 0x93, 0x70, 0xf6, 0x49, 0x8a, 0x3d, 0x31, 0xf5, 0xfb, 0xed, 0x63, 0xdf, 0x70, 0xff, 0x05, 0x8c, 0x02, 0xe2, 0xf9, 0x37, 0x34, 0xf6, 0x13, 0xed, 0x17, 0x1e, 0xd6, 0xb7, 0x05, 0xde, 0xf9, 0xfb, 0x39, 0x81, 0xfc, 0xfa, 0xc4, 0x07, 0xe5, 0x00, 0x65, 0xa0, 0x10, 0x65, 0x09, 0x10, 0xa6, 0x4f, 0x09, 0x34, 0xa3, 0x5d, 0xf9, 0xb1, 0xef, 0xca, 0xff, 0x84, 0xb4, 0xef, 0xc8, 0x12, 0xe8, 0x95, 0x56, 0xff, 0x2b, 0x02, 0xf9, 0x8d, 0xbb, 0x0f, 0xca, 0x01, 0xb7, 0x7c, 0x8e, 0x76, 0x21, 0xca, 0x12, 0x20, 0xcc, 0x6d, 0x33, 0xb7, 0x19, 0x7d, 0x65, 0xfe, 0x2b, 0xb4, 0x6a, 0x74, 0x60, 0x95, 0x1a, 0x2d, 0x33, 0x29, 0x4f, 0x87, 0xf0, 0x05, 0x6d, 0x8d, 0xf2, 0x0a, 0xe5, 0xf2, 0xe0, 0x77, 0xef, 0xe8, 0x40, 0x9b, 0x50, 0x7f, 0x4a, 0xa8, 0x4c, 0x00, 0xaf, 0x52, 0xff, 0x0c, 0x94, 0x4a, 0xa5, 0x0d, 0xfc, 0x96, 0x3a, 0x16, 0xc8, 0xa0, 0x46, 0x3f, 0x88, 0x04, 0x6e, 0x39, 0x98, 0x76, 0x76, 0xca, 0x7c, 0xc0, 0x1b, 0x6a, 0x57, 0x06, 0xe1, 0x8f, 0xc8, 0x48, 0x71, 0x61, 0xbc, 0x25, 0x29, 0xee, 0xb7, 0xf5, 0x20, 0xba, 0xa4, 0x7e, 0x63, 0xff, 0x3b, 0xe8, 0x1e, 0xce, 0x3c, 0xee, 0xb7, 0xf7, 0x5c, 0x92, 0x35, 0x58, 0x7a, 0x24, 0x43, 0xa8, 0x4c, 0xe1, 0x2c, 0x9e, 0xe2, 0x31, 0x52, 0xbf, 0xc7, 0x3f, 0x89, 0x7f, 0xf6, 0xe5, 0xe3, 0xb0, 0x3b, 0x43, 0x7f, 0xf1, 0xcf, 0xef, 0xf8, 0xff, 0x12, 0xb1, 0x81, 0xe7, 0xff, 0x4b, 0x74, 0xff, 0x11, 0x02, 0xb5, 0x3d, 0x75, 0x1f, 0xb4, 0x05, 0x94, 0x81, 0x86, 0x94, 0x71, 0xa0, 0x06, 0xf5, 0xbe, 0xa9, 0x6d, 0x68, 0x62, 0x24, 0x21, 0x78, 0xb7, 0xe0, 0x51, 0x1c, 0x63, 0x8f, 0x95, 0x56, 0xdf, 0x01, 0xae, 0x3e, 0x86, 0x9e, 0x4f, 0xb4, 0x53, 0x1e, 0xbb, 0x8f, 0xf0, 0xef, 0xe0, 0xde, 0x57, 0x78, 0x12, 0x47, 0x02, 0x1f, 0x3c, 0xd9, 0xf7, 0xf0, 0x3e, 0xc3, 0xff, 0x34, 0x42, 0xeb, 0x8c, 0x7b, 0x47, 0xe2, 0xef, 0xe0, 0xde, 0x9b, 0x78, 0x14, 0xc1, 0x3b, 0x14, 0x4f, 0xe2, 0xa7, 0x07, 0x3f, 0xfc, 0x6d, 0x7f, 0xea, 0xd8, 0x11, 0xfd, 0xa7, 0xfb, 0xdb, 0xc0, 0x2e, 0xea, 0x05, 0xc0, 0x09, 0x70, 0xf2, 0xe1, 0xf8, 0xc5, 0xc0, 0x3d, 0xf7, 0x4e, 0xc6, 0x3f, 0x21, 0xed, 0xae, 0xc6, 0x7f, 0x84, 0x4b, 0x81, 0xd1, 0xff, 0xf9, 0x1c, 0xf6, 0xe3, 0x25, 0xfe, 0xd2, 0x5f, 0x36, 0xed, 0x9e, 0x48, 0x1a, 0xd2, 0xbe, 0x1b, 0x04, 0xd1, 0x2d, 0x74, 0x77, 0xe4, 0x49, 0xbc, 0x15, 0xf8, 0x36, 0xed, 0x4e, 0xc9, 0xc3, 0xef, 0x0a, 0x69, 0x78, 0x3d, 0xb5, 0xb4, 0xec, 0xe0, 0xb7, 0x86, 0xe6, 0xff, 0x7d, 0x04, 0x0e, 0xfe, 0xa5, 0xaf, 0x98, 0xe4, 0xfa, 0x77, 0xf3, 0xdc, 0xbb, 0x2b, 0xea, 0x17, 0x8f, 0xdc, 0x55, 0x79, 0xfd, 0x91, 0x7b, 0x2a, 0x8f, 0xf4, 0x3d, 0xfc, 0x76, 0x52, 0x3c, 0x74, 0x17, 0xe5, 0xf6, 0xe3, 0x7d, 0xc1, 0xf3, 0x8b, 0x7b, 0x0f, 0xe5, 0x9f, 0xf0, 0xa4, 0xdd, 0x21, 0x18, 0x0b, 0x9f, 0xe8, 0xeb, 0x14, 0x78, 0xf5, 0xe1, 0x7d, 0x94, 0x4e, 0x81, 0xd7, 0x28, 0xeb, 0x85, 0xea, 0xbf, 0x6a, 0x3f, 0x06, 0xef, 0xb4, 0x84, 0x10, 0x78, 0x4f, 0xcd, 0x21, 0x5d, 0x28, 0xcf, 0xd1, 0x9e, 0x4c, 0xb9, 0x0f, 0x7c, 0x0c, 0xbe, 0x03, 0x5f, 0x80, 0xf3, 0xca, 0xc9, 0xc0, 0x87, 0xa9, 0x6d, 0xa9, 0xe4, 0x8e, 0xab, 0x7a, 0xb0, 0xff, 0x90, 0xaa, 0x8b, 0xa3, 0x7c, 0xc6, 0xb9, 0x40, 0x97, 0x17, 0x82, 0x74, 0x17, 0x03, 0x35, 0xdd, 0xb3, 0x9a, 0x5a, 0x3a, 0xf0, 0x15, 0x73, 0x36, 0x07, 0xef, 0xca, 0xdc, 0x09, 0xbc, 0x9b, 0x7a, 0x57, 0x26, 0xb0, 0x2a, 0xed, 0xce, 0x4c, 0x10, 0xee, 0x9d, 0x18, 0x8f, 0x4c, 0x53, 0xce, 0x4a, 0x27, 0x30, 0x59, 0x8d, 0x92, 0x7c, 0x94, 0xcf, 0x2a, 0x67, 0x03, 0x9f, 0x82, 0xe3, 0x60, 0x87, 0x3a, 0x4c, 0x6a, 0x6a, 0x3f, 0xc8, 0x4c, 0xcf, 0xf0, 0xe0, 0xf7, 0x9f, 0x3f, 0xf1, 0x36, 0x18, 0xc3, 0xbb, 0xad, 0xd3, 0x23, 0x77, 0x6e, 0xfe, 0x09, 0x5b, 0xa0, 0x6d, 0x1b, 0xba, 0xd7, 0xf0, 0x9f, 0x62, 0xa0, 0x5b, 0x72, 0xae, 0xfc, 0x6f, 0xcf, 0x0d, 0x95, 0x03, 0xff, 0x25, 0x6d, 0xdd, 0xc7, 0x72, 0x3f, 0x39, 0x70, 0xcb, 0xcd, 0x7f, 0xb7, 0x1e, 0xfc, 0x7e, 0x75, 0x46, 0x7a, 0x50, 0x4e, 0x50, 0xef, 0x49, 0xce, 0xd0, 0x37, 0xad, 0xe1, 0x41, 0x5c, 0xe0, 0xcc, 0x77, 0x46, 0xa6, 0xaa, 0x67, 0x02, 0x3f, 0xa6, 0xdd, 0x29, 0xfa, 0xb7, 0xd8, 0x26, 0x35, 0x82, 0x77, 0x8d, 0xdc, 0x7b, 0x46, 0x27, 0xa5, 0x98, 0xfa, 0x69, 0xea, 0xb7, 0xc6, 0x87, 0xf8, 0x35, 0xb0, 0xc1, 0xbd, 0x73, 0x14, 0xc4, 0x19, 0x79, 0xea, 0x61, 0xfd, 0xc9, 0xb6, 0x5b, 0xff, 0x1a, 0x50, 0x1a, 0x19, 0xa5, 0xa8, 0x8b, 0xb4, 0x31, 0xb3, 0x6d, 0x08, 0x6d, 0xe4, 0xa9, 0x87, 0xf5, 0x27, 0xdb, 0x6e, 0x3d, 0x0d, 0x93, 0x68, 0x4f, 0xfa, 0x93, 0xee, 0xe1, 0xfd, 0xa6, 0xbf, 0x43, 0xc7, 0xe0, 0xb7, 0xc1, 0xb1, 0xff, 0x92, 0xe6, 0xef, 0xb0, 0xf2, 0x61, 0x7d, 0x98, 0x7b, 0x6f, 0x2a, 0x88, 0x1c, 0xa9, 0xdf, 0xf8, 0xfe, 0x09, 0xee, 0xbd, 0xaa, 0x34, 0xe8, 0xf5, 0x25, 0x8b, 0x7b, 0xc7, 0xea, 0xbf, 0x0b, 0xf7, 0xfb, 0xa1, 0x7b, 0x3f, 0xeb, 0x2f, 0x38, 0x2d, 0xd1, 0xc1, 0x7b, 0x5a, 0x7f, 0x73, 0x57, 0x4b, 0x8d, 0x79, 0xe4, 0xbe, 0x96, 0x8b, 0x81, 0x92, 0xa0, 0x2c, 0x91, 0x91, 0x4a, 0xcd, 0xc0, 0xdd, 0xe0, 0x9d, 0xad, 0x68, 0x69, 0x17, 0xbc, 0xb3, 0xd5, 0x33, 0xf8, 0xbd, 0x2a, 0x78, 0x67, 0x4b, 0xd9, 0x00, 0x7e, 0x96, 0x32, 0x8f, 0xdd, 0xd7, 0x62, 0x6f, 0xa2, 0xb6, 0x93, 0xf0, 0xe0, 0x9d, 0xad, 0x46, 0x92, 0xc3, 0xbd, 0xb3, 0x15, 0xbc, 0x33, 0xe5, 0xde, 0x95, 0x72, 0xef, 0x49, 0xb5, 0x08, 0xec, 0xf5, 0x74, 0x91, 0xde, 0x9e, 0xd6, 0x81, 0x11, 0x9e, 0xd6, 0x0f, 0x66, 0x02, 0xb7, 0xfc, 0x9a, 0xf6, 0x38, 0xca, 0xe7, 0xc1, 0xd0, 0x50, 0x7b, 0xb5, 0xa7, 0x55, 0x60, 0x94, 0xfb, 0xdd, 0xfc, 0xdf, 0xd1, 0x06, 0xef, 0x5d, 0xfd, 0xff, 0x80, 0xe0, 0x5d, 0xae, 0x7f, 0x83, 0xe0, 0x3d, 0xaf, 0x0d, 0x52, 0x24, 0x58, 0xfe, 0x2f, 0xc1, 0xbd, 0x3f, 0xa6, 0xe6, 0x09, 0x6c, 0x50, 0xcb, 0x52, 0x4e, 0x91, 0xda, 0xb4, 0xc7, 0xa8, 0xb9, 0x03, 0x7f, 0x80, 0xef, 0xe9, 0x3f, 0x1d, 0x6a, 0xdf, 0x00, 0xdf, 0xa6, 0xb6, 0xa1, 0xcd, 0x1d, 0x48, 0x74, 0x7f, 0x6b, 0x4a, 0x9d, 0x2b, 0x15, 0xa8, 0xef, 0x05, 0x6f, 0x81, 0x0f, 0x8c, 0x5c, 0xe2, 0x0f, 0xde, 0x3b, 0x73, 0xd1, 0x86, 0xbc, 0xfd, 0x1f, 0x82, 0xfb, 0x5d, 0x3f, 0xed, 0xbe, 0xda, 0xff, 0x26, 0xd2, 0xee, 0xbe, 0xb9, 0x77, 0xde, 0x1e, 0xbb, 0xf7, 0xf6, 0x28, 0x1a, 0xa5, 0xee, 0x57, 0xdd, 0x3b, 0x6f, 0x8f, 0x22, 0xf8, 0xdd, 0x2e, 0x53, 0xe8, 0x7e, 0xdb, 0xb3, 0x81, 0x40, 0xda, 0x1d, 0x36, 0xf7, 0xee, 0x5a, 0xf0, 0xfe, 0xda, 0x04, 0x29, 0xa3, 0x4d, 0xe7, 0x79, 0x4a, 0xbb, 0xc7, 0xf6, 0x24, 0x72, 0xca, 0xb4, 0xb4, 0xfb, 0x6c, 0xee, 0x7d, 0xb5, 0xe0, 0x1d, 0xb5, 0x77, 0xa5, 0x92, 0x7b, 0x97, 0x4d, 0x59, 0xc1, 0x33, 0xe1, 0xde, 0x3b, 0xdb, 0x22, 0x83, 0xf5, 0x23, 0x3c, 0x0b, 0x5b, 0xa4, 0x2b, 0xe7, 0xfe, 0xae, 0xca, 0x6b, 0xc1, 0x6f, 0xdc, 0x53, 0xdd, 0x3b, 0x64, 0xec, 0x45, 0xa6, 0x6b, 0xee, 0x3d, 0xde, 0x6c, 0xf2, 0x97, 0x3f, 0xc1, 0x7b, 0x65, 0x85, 0x65, 0x88, 0x67, 0x95, 0xd4, 0xd1, 0xa7, 0xca, 0x4b, 0xe0, 0x8a, 0x67, 0x2d, 0x7b, 0xdb, 0x47, 0xb1, 0x2e, 0xf5, 0x7e, 0xd0, 0x93, 0x08, 0xde, 0x17, 0x7a, 0x12, 0xee, 0xfd, 0xa1, 0x27, 0x10, 0xfa, 0x5d, 0x6c, 0x73, 0xda, 0x7d, 0x20, 0x91, 0x54, 0xb8, 0xbf, 0xcb, 0x05, 0xef, 0x3c, 0x3f, 0x44, 0xe0, 0x73, 0xb0, 0x96, 0x7a, 0x7d, 0xd0, 0x34, 0xf5, 0x77, 0xbc, 0x3f, 0x11, 0xba, 0x2f, 0x54, 0x90, 0xb9, 0x0f, 0xe1, 0xde, 0x1f, 0xa2, 0x0c, 0x41, 0x16, 0xba, 0x70, 0x7f, 0xeb, 0x4b, 0xab, 0x87, 0xf0, 0xd2, 0x13, 0xed, 0x7e, 0xe0, 0x03, 0xf0, 0x56, 0xaa, 0x2e, 0x69, 0xbf, 0x57, 0x3c, 0x84, 0x4b, 0x1f, 0xbc, 0x57, 0x44, 0xf9, 0x08, 0x94, 0x08, 0x17, 0xee, 0xef, 0xcb, 0xb4, 0xd3, 0x90, 0x8f, 0xbe, 0xf1, 0xee, 0x3d, 0x23, 0xea, 0x41, 0xb8, 0xf7, 0x8d, 0x28, 0x83, 0x70, 0xef, 0x1d, 0x51, 0xa6, 0xe1, 0x2c, 0xb4, 0x0e, 0x65, 0xf0, 0xfe, 0x91, 0xb2, 0x55, 0x46, 0xa4, 0xf9, 0x82, 0xbe, 0x5e, 0x20, 0x78, 0x17, 0x29, 0xa4, 0x9b, 0x8b, 0xe0, 0x9d, 0x24, 0xb7, 0x0c, 0xd5, 0xdd, 0x3b, 0xe5, 0x0f, 0xe1, 0xde, 0x51, 0xa2, 0x0c, 0x22, 0x44, 0x17, 0xbc, 0xab, 0x44, 0x19, 0x84, 0x7b, 0x67, 0x89, 0x32, 0x08, 0xf7, 0xee, 0x12, 0x65, 0x10, 0xee, 0x1d, 0xa6, 0xb4, 0x7e, 0xf7, 0x2e, 0x13, 0x65, 0x10, 0xee, 0x9d, 0xa6, 0x87, 0xfd, 0x9d, 0x1f, 0xa3, 0x71, 0xef, 0xcc, 0x07, 0xe1, 0xde, 0x75, 0xa2, 0x0c, 0xc2, 0xfd, 0x2d, 0x95, 0xf2, 0x21, 0xdc, 0xfb, 0x4f, 0x94, 0x41, 0xb8, 0xf7, 0xa0, 0x28, 0x83, 0x70, 0xef, 0x43, 0xa5, 0xf9, 0x20, 0xf5, 0x4e, 0x54, 0x2a, 0x9e, 0xa8, 0xa7, 0xc6, 0xd8, 0x71, 0x7d, 0xf4, 0x10, 0x59, 0x83, 0x77, 0x98, 0x5c, 0xac, 0x65, 0xed, 0x73, 0xef, 0xdf, 0xec, 0x91, 0x12, 0x6a, 0xa7, 0xc0, 0x0c, 0x2d, 0x73, 0xa0, 0xbe, 0xb6, 0x3f, 0x70, 0x5f, 0x2b, 0x14, 0x98, 0xae, 0xe5, 0x95, 0x70, 0xf7, 0x2e, 0x88, 0x7b, 0x87, 0xc3, 0xbd, 0xbf, 0x91, 0x76, 0x77, 0xe3, 0xdf, 0xdd, 0xdb, 0xf8, 0xdb, 0xbb, 0x1a, 0xa1, 0xbb, 0x09, 0xc8, 0x4e, 0x72, 0xe1, 0xde, 0x49, 0xd0, 0x6f, 0x07, 0x24, 0x78, 0x8f, 0xe0, 0xf6, 0xfd, 0x43, 0x60, 0x87, 0x7e, 0xfb, 0x41, 0x69, 0xf0, 0x23, 0x38, 0x99, 0xda, 0xef, 0xe2, 0xc1, 0x27, 0x9c, 0x07, 0x83, 0xdf, 0xe6, 0x83, 0x6b, 0x40, 0xb1, 0xd4, 0xbb, 0x41, 0xee, 0x7e, 0x88, 0x9c, 0x0e, 0x7e, 0xa3, 0x81, 0x5f, 0x10, 0xee, 0x77, 0x1a, 0xf7, 0x0e, 0x71, 0xea, 0x3d, 0xe2, 0x3f, 0xeb, 0xee, 0xf7, 0x61, 0xf7, 0xbb, 0xaf, 0xfb, 0x3b, 0x8a, 0xbb, 0xe7, 0x77, 0x7f, 0x4b, 0x31, 0x1b, 0x89, 0xcf, 0xa8, 0x25, 0xa5, 0xdc, 0x4f, 0x25, 0x21, 0xe4, 0x4d, 0xbd, 0xd9, 0x9f, 0x65, 0x69, 0xf0, 0xce, 0xbe, 0xdb, 0xd6, 0xb3, 0xb8, 0x77, 0xdd, 0x6e, 0xc9, 0x78, 0xf1, 0x04, 0xbf, 0x1c, 0x8c, 0x55, 0x9b, 0xab, 0x4f, 0xab, 0xbd, 0xd5, 0xe1, 0xea, 0x08, 0xf5, 0x39, 0x75, 0x92, 0xba, 0x50, 0x5d, 0xa5, 0x6e, 0xd4, 0xfa, 0x6b, 0x43, 0xb5, 0xb1, 0xda, 0x74, 0x6d, 0x86, 0xf6, 0x8a, 0xf6, 0x91, 0x3e, 0xc5, 0xc8, 0x9a, 0xa1, 0x66, 0xbe, 0x74, 0xf9, 0x32, 0xe4, 0x2b, 0x92, 0xaf, 0x68, 0xbe, 0x98, 0x7c, 0xab, 0xf2, 0xed, 0xce, 0xf7, 0x7e, 0xfe, 0x25, 0xf9, 0x97, 0xe7, 0xff, 0x3d, 0x3c, 0x7b, 0x78, 0xbe, 0xf0, 0xba, 0xe1, 0x4d, 0xc3, 0xdb, 0x85, 0x27, 0x84, 0x3f, 0x1d, 0xde, 0x31, 0x7c, 0x4c, 0xf8, 0xf6, 0xf0, 0x23, 0xe1, 0x9f, 0x86, 0x9f, 0x0e, 0xff, 0x25, 0xfc, 0x46, 0xf8, 0x03, 0x5f, 0x26, 0x5f, 0x01, 0x5f, 0x61, 0x5f, 0x69, 0x5f, 0x79, 0x5f, 0x15, 0x5f, 0x0d, 0x5f, 0x1d, 0x5f, 0x27, 0xdf, 0x40, 0xdf, 0x48, 0xdf, 0x38, 0xdf, 0x2c, 0xdf, 0x3c, 0xdf, 0x1a, 0xdf, 0x26, 0xdf, 0x0e, 0xdf, 0x6e, 0xdf, 0xd7, 0x11, 0x46, 0x44, 0xd6, 0x88, 0x9c, 0x11, 0xbe, 0x88, 0x02, 0x11, 0x85, 0x23, 0x4a, 0x46, 0x94, 0x8d, 0x68, 0x16, 0xd1, 0x29, 0x62, 0x52, 0xc4, 0xa2, 0x88, 0xf5, 0x05, 0xd4, 0x02, 0x19, 0x0b, 0x64, 0x29, 0x90, 0xbd, 0x40, 0x9e, 0x02, 0xe1, 0x05, 0x8a, 0x16, 0x28, 0x5e, 0xa0, 0x41, 0x81, 0xa4, 0x02, 0x5d, 0x23, 0xd5, 0xc8, 0x4c, 0x91, 0x11, 0x05, 0x07, 0x17, 0xfc, 0xb1, 0xe0, 0x8d, 0x42, 0x6c, 0x0e, 0x0a, 0xa5, 0x2b, 0x94, 0xa9, 0x50, 0xb6, 0x42, 0xb9, 0x0a, 0xbd, 0x52, 0x68, 0x63, 0xa1, 0x63, 0x85, 0x8e, 0x17, 0xfa, 0xa1, 0xb0, 0x14, 0xae, 0x56, 0xb8, 0xe7, 0x53, 0xe3, 0xa2, 0xfa, 0x44, 0x0d, 0x2f, 0x99, 0x73, 0x5d, 0x9e, 0x75, 0x11, 0x77, 0x02, 0x77, 0xf5, 0x07, 0x91, 0x0f, 0x02, 0x81, 0x40, 0x70, 0x31, 0xf3, 0xc9, 0x4a, 0x35, 0x4e, 0xed, 0xa0, 0xf6, 0xc1, 0xee, 0x91, 0xd8, 0x3d, 0x59, 0x5d, 0xa4, 0xae, 0x56, 0x37, 0x69, 0x43, 0xb4, 0x51, 0xda, 0x24, 0xec, 0x9e, 0xa5, 0xad, 0xd6, 0x4e, 0xe8, 0x53, 0xb1, 0x5b, 0xf2, 0xa5, 0xcf, 0x97, 0x11, 0xbb, 0x8b, 0xe5, 0x8b, 0xcd, 0xb7, 0x3a, 0xdf, 0x9e, 0x7c, 0x0f, 0xb0, 0x7b, 0x65, 0xb8, 0x84, 0xe7, 0x0a, 0xf7, 0x85, 0x37, 0x08, 0x8f, 0x0b, 0xd9, 0xdd, 0x29, 0x7c, 0x7c, 0xf8, 0x1b, 0xe1, 0xef, 0x84, 0x7f, 0x1e, 0xfe, 0x75, 0xf8, 0x6f, 0xe1, 0xb7, 0x7c, 0xe2, 0xcb, 0x82, 0xdd, 0xa5, 0x7c, 0x65, 0x7d, 0x95, 0x7d, 0xd5, 0xb0, 0xfb, 0x19, 0xdf, 0x00, 0xdf, 0x10, 0xdf, 0x28, 0xdf, 0x78, 0xdf, 0x6c, 0xdf, 0x4a, 0xdf, 0xab, 0xbe, 0xd7, 0x7d, 0x3b, 0x43, 0x76, 0xe7, 0x78, 0xc4, 0xee, 0xa6, 0x11, 0xad, 0x22, 0x9e, 0x8b, 0x78, 0x31, 0x64, 0x77, 0x66, 0xec, 0xce, 0x5d, 0x20, 0x7f, 0xc8, 0xee, 0xc4, 0x02, 0xc9, 0x41, 0xbb, 0x7d, 0x05, 0x13, 0xb1, 0xfb, 0xa7, 0x82, 0x81, 0xc7, 0xec, 0xde, 0x50, 0xe8, 0xfd, 0x47, 0xec, 0x1e, 0x16, 0x95, 0x18, 0x35, 0x04, 0xbb, 0x73, 0xae, 0xf3, 0x61, 0xb7, 0x3c, 0xf0, 0x05, 0xed, 0x4e, 0xfd, 0x7f, 0x79, 0x74, 0x00, 0x6d, 0xac, 0x7c, 0xd2, 0xda, 0xce, 0x10, 0x7c, 0x66, 0xc9, 0xb9, 0xfb, 0xa3, 0x82, 0xdf, 0x29, 0xa7, 0xa7, 0xad, 0xf4, 0xb7, 0xc3, 0xbf, 0x7f, 0x20, 0xf2, 0xfd, 0xbd, 0x9f, 0xfb, 0x7d, 0x7f, 0x97, 0xf2, 0x80, 0xdb, 0x77, 0xf1, 0xa9, 0x47, 0xdf, 0x05, 0x97, 0x4b, 0xf1, 0x5f, 0x94, 0xfc, 0xcd, 0x9f, 0x6f, 0xb2, 0x07, 0xff, 0xb6, 0x41, 0x9f, 0xef, 0x75, 0x91, 0x0b, 0xdd, 0xa8, 0xe5, 0x14, 0xf9, 0x8e, 0x35, 0xe1, 0xbb, 0xbe, 0xdf, 0x67, 0xf8, 0xae, 0xf3, 0xf9, 0xde, 0xe7, 0x59, 0xdf, 0xcf, 0xd7, 0x3d, 0x1f, 0x73, 0xe9, 0xe6, 0xf9, 0x97, 0xbe, 0xa9, 0x96, 0x36, 0xf3, 0xbb, 0xed, 0xe7, 0x0e, 0x9d, 0x7b, 0x53, 0xe4, 0xdb, 0x09, 0x41, 0x0e, 0xf7, 0xce, 0x2d, 0x3a, 0xc7, 0x7a, 0x75, 0x2e, 0xe1, 0x9b, 0x42, 0xdf, 0xe4, 0xfa, 0xc6, 0x3e, 0x2f, 0xe7, 0x86, 0x9e, 0x9f, 0x42, 0xbb, 0xd8, 0xb9, 0x88, 0x73, 0xf9, 0xcf, 0x85, 0x9d, 0xcb, 0x75, 0xee, 0xab, 0x73, 0x9f, 0xd0, 0xde, 0x02, 0x5d, 0xae, 0xb3, 0xa7, 0xcf, 0xe6, 0xfa, 0xfa, 0x57, 0x77, 0xde, 0x17, 0xe1, 0xcf, 0x2c, 0x7e, 0xe6, 0xe5, 0x74, 0x48, 0x34, 0x13, 0x3d, 0x93, 0x83, 0x36, 0xdf, 0x60, 0x47, 0x28, 0x46, 0xba, 0x3f, 0x75, 0x34, 0x7c, 0xa0, 0xb4, 0x88, 0x67, 0xbb, 0x67, 0x2f, 0x7f, 0xbf, 0xe3, 0xb9, 0x62, 0xc6, 0x58, 0xee, 0xf7, 0x4f, 0xb1, 0x0f, 0xa6, 0x52, 0xd8, 0xf4, 0x3b, 0x23, 0x9c, 0xf9, 0x22, 0xde, 0x8f, 0xd2, 0x8d, 0x4f, 0xb7, 0x21, 0xdd, 0xf6, 0x74, 0x0f, 0xd2, 0x5b, 0xa9, 0x63, 0xe9, 0x33, 0xa5, 0x6f, 0x92, 0x7e, 0x7b, 0xfa, 0x13, 0xe9, 0x7f, 0x4c, 0x7f, 0x23, 0x43, 0xae, 0x0c, 0x79, 0x33, 0x44, 0x65, 0x68, 0x22, 0x92, 0xa1, 0x57, 0x86, 0x3e, 0xfc, 0xed, 0xbe, 0x2d, 0x82, 0x7f, 0x07, 0xc1, 0x13, 0x96, 0x61, 0xec, 0x9f, 0x72, 0x33, 0x4c, 0x7c, 0xa4, 0x3e, 0x2a, 0xc3, 0xe8, 0x0c, 0xf3, 0x32, 0xcc, 0x0f, 0xb5, 0x56, 0xb9, 0xc8, 0xb0, 0x26, 0xd4, 0x5a, 0xf9, 0xb8, 0x4f, 0x33, 0xcc, 0x7b, 0xa2, 0x3d, 0xf5, 0xb1, 0xd6, 0xf8, 0x0c, 0x0b, 0x1f, 0xd6, 0xe7, 0xa7, 0xbd, 0xac, 0x95, 0xf2, 0xca, 0x33, 0xca, 0x20, 0xa5, 0x97, 0x3e, 0x5c, 0xe9, 0xa4, 0x74, 0x54, 0xe2, 0x94, 0x16, 0x4a, 0x5b, 0x65, 0x9b, 0x52, 0x89, 0x77, 0x49, 0x15, 0xa5, 0x9e, 0x32, 0x5b, 0x79, 0x51, 0x99, 0xa3, 0xbc, 0xa4, 0xcc, 0x55, 0xe6, 0xe9, 0x43, 0x95, 0x97, 0xf5, 0x61, 0xca, 0x7c, 0xa5, 0xb2, 0x12, 0xad, 0xec, 0x56, 0xde, 0x54, 0xf6, 0x28, 0x15, 0x94, 0x44, 0x56, 0x0c, 0x8f, 0x98, 0x62, 0x8b, 0x57, 0xd2, 0x4b, 0x06, 0xc9, 0x28, 0x99, 0x25, 0xab, 0x64, 0x93, 0xec, 0x92, 0x4b, 0x72, 0x4b, 0x98, 0xe4, 0xe7, 0x09, 0x8a, 0x90, 0x02, 0x52, 0x50, 0x0a, 0x4b, 0x11, 0x29, 0x2a, 0xc5, 0xa4, 0xb8, 0x44, 0x29, 0x2d, 0x95, 0x86, 0x4a, 0x2b, 0xe5, 0x69, 0xa5, 0xa9, 0x6c, 0x90, 0x12, 0x52, 0x5a, 0xca, 0x48, 0x25, 0xa9, 0xca, 0xca, 0xde, 0x51, 0x3a, 0xf1, 0x3e, 0xe9, 0x26, 0x3d, 0xf4, 0x21, 0xd2, 0x47, 0xfa, 0xf2, 0x4e, 0x18, 0x24, 0x43, 0x64, 0x28, 0x2b, 0xd0, 0x68, 0x99, 0x22, 0x53, 0x65, 0x9a, 0x4c, 0x97, 0x17, 0x64, 0x86, 0xd2, 0x5a, 0xa9, 0xaf, 0xb4, 0x51, 0x3a, 0xb0, 0x17, 0x1a, 0xa1, 0xf4, 0x56, 0xfa, 0x28, 0xdd, 0x95, 0x7e, 0x4a, 0x03, 0x25, 0x41, 0x76, 0x28, 0x49, 0x32, 0x53, 0xa9, 0xa3, 0x1c, 0x40, 0xfb, 0x2e, 0xf2, 0xa6, 0xd2, 0x4d, 0xd9, 0xab, 0x8f, 0xd4, 0x47, 0xc9, 0x26, 0x67, 0x9c, 0xd2, 0x9e, 0xf7, 0xd5, 0x61, 0xfd, 0x59, 0x99, 0xa5, 0xd4, 0xd5, 0x47, 0xeb, 0x63, 0xf4, 0xb1, 0xfa, 0x38, 0xb3, 0xb8, 0x19, 0x65, 0x96, 0x30, 0x4b, 0x9a, 0xa5, 0xcc, 0xb2, 0xac, 0xa3, 0x96, 0x39, 0xda, 0x1c, 0x63, 0x8e, 0x35, 0xc7, 0x9b, 0xef, 0x9a, 0x47, 0xcd, 0xf7, 0xcc, 0x0f, 0xc4, 0xb1, 0x34, 0x2b, 0xbd, 0x95, 0xc1, 0xca, 0x68, 0x65, 0xb2, 0x32, 0x2b, 0x3b, 0x24, 0xaf, 0xd5, 0xde, 0x7a, 0xda, 0xea, 0x60, 0x3d, 0x63, 0x8d, 0x91, 0x70, 0x19, 0x65, 0x2d, 0xb4, 0x16, 0x59, 0x8b, 0xad, 0x25, 0xd6, 0x52, 0x6b, 0x05, 0xeb, 0x69, 0x59, 0xeb, 0xb8, 0x75, 0xc2, 0xfa, 0xd8, 0xfa, 0xd4, 0xce, 0x6b, 0xe7, 0xb3, 0xf3, 0xdb, 0x3e, 0xa9, 0x62, 0x37, 0xb6, 0x5b, 0xdb, 0x6d, 0xec, 0xb6, 0x76, 0x3b, 0x3b, 0x9e, 0x77, 0xd9, 0x00, 0x7b, 0xbf, 0x7d, 0xc0, 0x3e, 0x68, 0x1f, 0xb2, 0xbf, 0x97, 0xc1, 0xf6, 0x45, 0xb3, 0xb4, 0xb5, 0xcc, 0x2c, 0x63, 0x2d, 0x17, 0x5d, 0x4a, 0x9a, 0xbd, 0xad, 0x83, 0x66, 0x1f, 0xeb, 0x90, 0xd9, 0xcf, 0x3a, 0x6c, 0xf6, 0xb5, 0xde, 0x32, 0x07, 0x4b, 0xb4, 0x39, 0x4a, 0x2a, 0x9b, 0x13, 0xac, 0x93, 0xe6, 0x44, 0xeb, 0x33, 0xf3, 0x39, 0xeb, 0x73, 0x49, 0x27, 0xd5, 0xcc, 0x71, 0xd6, 0x27, 0xe6, 0x1a, 0x2b, 0x60, 0xbe, 0x6a, 0x8b, 0xb9, 0xd6, 0x56, 0xcc, 0x75, 0xb6, 0x6a, 0x6e, 0x94, 0x44, 0xc9, 0x24, 0x49, 0xe6, 0xfb, 0x76, 0xb8, 0x79, 0xcc, 0x8e, 0x30, 0x3f, 0xb4, 0x0b, 0x48, 0x16, 0xe9, 0x62, 0x7e, 0x24, 0xc9, 0xe6, 0x65, 0x3b, 0xa7, 0xf9, 0xa3, 0x5d, 0xc6, 0xbc, 0x6a, 0x97, 0x93, 0x9e, 0xe6, 0x75, 0xbb, 0x8a, 0x79, 0xc3, 0xae, 0x2a, 0x39, 0xa5, 0xb7, 0xf1, 0x93, 0x91, 0x4d, 0x72, 0x48, 0x2f, 0x4b, 0xb1, 0x1b, 0x5a, 0xba, 0xdd, 0xc4, 0x52, 0xed, 0x46, 0xce, 0x29, 0xc9, 0x23, 0xfd, 0xac, 0x2c, 0x76, 0x82, 0x95, 0xd5, 0x6e, 0x6f, 0x65, 0xb3, 0x9f, 0x66, 0x2f, 0x32, 0xd0, 0xaa, 0x6e, 0x4f, 0xb0, 0x6a, 0xda, 0xcf, 0x59, 0x35, 0xec, 0x89, 0x56, 0x3d, 0xfb, 0x05, 0xab, 0xbe, 0x3d, 0x43, 0x22, 0x65, 0xb8, 0xd5, 0xc0, 0x9e, 0x69, 0xb5, 0xb6, 0x17, 0x5a, 0x71, 0x32, 0x46, 0x0a, 0xc9, 0x58, 0xab, 0xa3, 0x71, 0xc5, 0xea, 0x64, 0xbf, 0x65, 0x25, 0xda, 0x87, 0xad, 0x24, 0xfb, 0x88, 0xd5, 0xd9, 0x7e, 0x5b, 0x9e, 0x92, 0xe7, 0xad, 0x21, 0xf6, 0x69, 0x6b, 0xac, 0xfd, 0x83, 0x35, 0xce, 0x9a, 0x63, 0x5f, 0xb7, 0xe6, 0xda, 0x37, 0xad, 0x97, 0xec, 0x1b, 0xf2, 0xac, 0x3a, 0x40, 0xad, 0xed, 0x6c, 0x51, 0x07, 0xaa, 0x31, 0xea, 0x20, 0x35, 0x56, 0xad, 0xa3, 0x0e, 0x36, 0x9f, 0x55, 0xeb, 0xaa, 0xf5, 0xd4, 0x21, 0xea, 0x50, 0xb5, 0xbe, 0xda, 0x40, 0x6d, 0xa8, 0x36, 0x52, 0x87, 0xa9, 0x8d, 0xd5, 0x26, 0xbc, 0x77, 0x9a, 0x1a, 0xf9, 0xd5, 0x66, 0xea, 0x48, 0xbb, 0x9a, 0x3a, 0x8a, 0x77, 0x51, 0x9c, 0xd3, 0x4a, 0x6d, 0xa1, 0xfc, 0xa0, 0x5c, 0x52, 0x5b, 0xaa, 0xcf, 0xaa, 0xad, 0xd4, 0xd1, 0x6a, 0x6b, 0xb5, 0x8d, 0x3a, 0xc6, 0xc9, 0xa5, 0xbe, 0xac, 0x8e, 0x55, 0xdb, 0xaa, 0xe3, 0xd4, 0x76, 0xca, 0x45, 0xe5, 0xb2, 0x1a, 0xaf, 0x26, 0xa8, 0xed, 0xd5, 0xf1, 0xbc, 0xbb, 0x26, 0xb0, 0x8a, 0x77, 0x54, 0x27, 0xf2, 0xf6, 0x9a, 0xaf, 0x3e, 0xc3, 0x4a, 0xbe, 0x40, 0x1d, 0xe1, 0x9c, 0x76, 0xce, 0xa8, 0x0b, 0x9d, 0xcf, 0xbc, 0xef, 0x7b, 0x8f, 0x79, 0x3f, 0x30, 0xff, 0x30, 0xef, 0xda, 0x31, 0x96, 0xd7, 0x4a, 0x67, 0xb7, 0x32, 0x63, 0xad, 0xcd, 0xe6, 0xe7, 0x76, 0x61, 0x2b, 0xdc, 0xee, 0x66, 0x75, 0xb5, 0xdf, 0xb3, 0xba, 0xd9, 0xef, 0x5b, 0xdd, 0xed, 0x0f, 0xac, 0x1e, 0xf6, 0x31, 0xab, 0xa7, 0xfd, 0xa1, 0x3a, 0xd9, 0xac, 0x63, 0x6d, 0x31, 0xeb, 0x5a, 0x7e, 0xb3, 0x8b, 0xb5, 0x5b, 0xed, 0xa4, 0x4e, 0x31, 0xd7, 0xdb, 0x9a, 0xf9, 0x8b, 0x5d, 0xde, 0xf8, 0xda, 0xf8, 0xc6, 0xf2, 0xd9, 0xdd, 0xbd, 0x33, 0x8d, 0x23, 0xc6, 0x61, 0x73, 0x84, 0x39, 0xd2, 0xfa, 0xc8, 0x7c, 0xcd, 0xd6, 0xd5, 0x44, 0x35, 0xc9, 0xf2, 0xd8, 0x4d, 0xcd, 0x72, 0xd6, 0x4a, 0xb3, 0xb3, 0xb5, 0xcb, 0x2a, 0x6f, 0x8f, 0x32, 0xeb, 0x59, 0x6f, 0x9a, 0xf5, 0xad, 0x37, 0xcc, 0x39, 0xd6, 0x4f, 0xe6, 0x4b, 0xd6, 0x8f, 0xe6, 0x17, 0x76, 0x09, 0xf3, 0x94, 0x1d, 0x65, 0x45, 0xd8, 0x23, 0xad, 0x02, 0xf6, 0x08, 0xab, 0x96, 0x3d, 0xdd, 0xaa, 0x6d, 0x4f, 0xb3, 0x7a, 0xd9, 0x9f, 0x59, 0xbd, 0xed, 0x93, 0x56, 0x43, 0x7b, 0x96, 0xd5, 0xc2, 0x5e, 0xe0, 0x9d, 0xa5, 0x3e, 0x6f, 0x6e, 0xb1, 0x33, 0xa9, 0x9d, 0xd5, 0xa9, 0x6a, 0x17, 0x75, 0x9a, 0x9a, 0xac, 0x4e, 0x37, 0x1b, 0x58, 0x29, 0xe6, 0x5c, 0xeb, 0x5b, 0x2b, 0xd2, 0xee, 0x61, 0x15, 0xf4, 0xfc, 0x6e, 0x15, 0xb2, 0x7b, 0x5a, 0x85, 0xed, 0x5e, 0xd6, 0x14, 0xfb, 0xaa, 0xfa, 0x82, 0x3a, 0x43, 0x9d, 0xc9, 0x6e, 0x6c, 0x96, 0x3a, 0xdb, 0x6c, 0x68, 0x0e, 0xb0, 0xde, 0x36, 0x6f, 0x5a, 0x6d, 0xd4, 0x17, 0xd5, 0x39, 0xde, 0x79, 0xea, 0x4b, 0x66, 0x77, 0xb5, 0xab, 0x93, 0xd3, 0x9c, 0x67, 0x7d, 0x67, 0x5e, 0xb1, 0x4b, 0xa9, 0xdd, 0xd4, 0xb9, 0x6a, 0x77, 0x7b, 0x92, 0xda, 0x43, 0x9d, 0xe7, 0xbc, 0xa3, 0xee, 0x54, 0x77, 0x19, 0x79, 0xd5, 0xdd, 0xb6, 0x57, 0xdd, 0xa3, 0xee, 0x55, 0xf7, 0xa9, 0xfb, 0xd5, 0x03, 0xea, 0x41, 0x23, 0x9f, 0xd3, 0xd7, 0x2e, 0xe6, 0x29, 0xe4, 0xbc, 0xa5, 0x5e, 0x51, 0xaf, 0x3a, 0x27, 0xd4, 0x9f, 0xd5, 0x5f, 0xd4, 0x6b, 0xea, 0xaf, 0xde, 0x97, 0xbd, 0xf3, 0x3d, 0x19, 0xd4, 0xbb, 0xea, 0x3d, 0xf5, 0xbe, 0xa3, 0x6b, 0x63, 0x1d, 0x8f, 0x93, 0x5e, 0x1b, 0xa7, 0x8d, 0xd7, 0x26, 0x38, 0x59, 0x9c, 0xec, 0xde, 0xd7, 0xbc, 0x1b, 0x9c, 0x2f, 0x9c, 0x2f, 0x79, 0x6f, 0x4e, 0xd1, 0x26, 0x6b, 0xcf, 0x6b, 0x53, 0x9d, 0x4b, 0xce, 0x65, 0x6d, 0x1a, 0xe7, 0x94, 0xcd, 0xe2, 0xd7, 0x5e, 0x90, 0x37, 0x3d, 0x3f, 0x7b, 0xb2, 0x1a, 0xdf, 0x1b, 0xdf, 0x1a, 0xdf, 0x19, 0x17, 0x78, 0xb7, 0xce, 0x34, 0xee, 0x1b, 0x0f, 0x78, 0xc3, 0xce, 0xd6, 0x5e, 0xd4, 0xe6, 0xb0, 0x97, 0x3f, 0x24, 0x7b, 0xe4, 0x88, 0x6c, 0x93, 0x03, 0xda, 0x4b, 0x46, 0x25, 0x6d, 0xae, 0x63, 0x39, 0x19, 0x9c, 0xac, 0xde, 0xd7, 0xb5, 0x79, 0xda, 0xcb, 0xda, 0x7c, 0x6d, 0x81, 0xb6, 0x50, 0x5b, 0xa4, 0x2d, 0xf6, 0x64, 0x33, 0xd6, 0x38, 0x5f, 0x19, 0xaf, 0x1a, 0x6b, 0x8d, 0x75, 0xc6, 0x7a, 0xe3, 0x35, 0xcf, 0x2f, 0xc6, 0x3d, 0xcf, 0x35, 0xe3, 0x92, 0x71, 0xd7, 0xd8, 0x60, 0x6c, 0x34, 0x36, 0x19, 0xaf, 0x1b, 0x9b, 0x8d, 0x2d, 0x86, 0xdf, 0xf3, 0x94, 0xa7, 0xa8, 0xa7, 0xb8, 0xa7, 0x94, 0xa7, 0x9c, 0x13, 0xf0, 0x54, 0xf0, 0x54, 0xf5, 0x94, 0x31, 0x6d, 0x4f, 0x25, 0x4f, 0x65, 0x4f, 0x09, 0xaf, 0xea, 0x35, 0xbc, 0x96, 0xd7, 0xf1, 0xa6, 0x37, 0x33, 0x7a, 0xb3, 0x78, 0x73, 0xc8, 0x56, 0x4f, 0xac, 0xa7, 0x8e, 0x37, 0x97, 0x37, 0xb7, 0x37, 0xcc, 0xe8, 0xe8, 0xdd, 0xe8, 0xcd, 0xef, 0x0d, 0xf7, 0xfa, 0xbc, 0x11, 0xde, 0x02, 0xde, 0x48, 0xd3, 0x6b, 0x5a, 0xde, 0x82, 0x9e, 0x5f, 0x3d, 0xd5, 0x3d, 0x35, 0xbc, 0x85, 0xbc, 0x85, 0xbd, 0x45, 0x3c, 0xf5, 0x3c, 0xf5, 0x3d, 0x0d, 0xbc, 0x19, 0x3d, 0x0d, 0x3d, 0x8d, 0x9c, 0x6f, 0x9d, 0xef, 0x3c, 0x77, 0x3c, 0x31, 0x9e, 0xcc, 0x9e, 0xba, 0x9e, 0x8b, 0xde, 0x4d, 0xde, 0xa2, 0xde, 0x62, 0xde, 0xe2, 0xde, 0x12, 0xde, 0xd2, 0x9e, 0x30, 0xcf, 0x8f, 0x9e, 0x2b, 0x9e, 0xe6, 0xde, 0x6c, 0x9e, 0x9a, 0x9e, 0x04, 0xef, 0x53, 0xce, 0x05, 0xaf, 0xe9, 0xcd, 0xe7, 0x2d, 0x67, 0xe6, 0x34, 0x73, 0x79, 0x2b, 0x78, 0x2b, 0xaa, 0x79, 0xbd, 0xd1, 0xde, 0xaa, 0xde, 0x1a, 0xde, 0x52, 0xde, 0x5a, 0xde, 0xda, 0xde, 0x18, 0x6f, 0x5d, 0x33, 0xbd, 0xb7, 0xbe, 0xb7, 0x81, 0xb7, 0x91, 0xb7, 0x89, 0xb7, 0x99, 0xb7, 0x85, 0xb7, 0x95, 0xb7, 0x8d, 0x91, 0xc7, 0x1b, 0xef, 0x6d, 0xef, 0xed, 0xe8, 0x4d, 0xf4, 0x76, 0xf1, 0x76, 0xf3, 0xf6, 0xf4, 0xf6, 0xf1, 0xf6, 0xf7, 0x0e, 0xf2, 0x0e, 0xf5, 0x8e, 0xf4, 0x8e, 0xf6, 0x8e, 0x33, 0x1a, 0x7a, 0xe7, 0x18, 0xb7, 0x8d, 0x3b, 0xce, 0xe7, 0x72, 0x45, 0xae, 0x9a, 0x15, 0x9c, 0xef, 0xcd, 0xc9, 0xe6, 0xeb, 0xe6, 0x09, 0x6b, 0x82, 0x93, 0xc7, 0xe9, 0x63, 0x96, 0x37, 0xbb, 0xca, 0xbb, 0x72, 0xd4, 0x9c, 0x64, 0xcd, 0x33, 0x37, 0xc9, 0x7b, 0xe6, 0x71, 0xf3, 0x67, 0x79, 0xdf, 0xbc, 0x67, 0x19, 0xf2, 0x81, 0x95, 0x5d, 0x8e, 0x59, 0x15, 0xe4, 0x43, 0xab, 0xa5, 0x35, 0x5e, 0x3e, 0xb2, 0x46, 0xc8, 0x71, 0x39, 0x61, 0x7e, 0x6c, 0x4d, 0x74, 0x4a, 0x39, 0x0d, 0x9d, 0x96, 0x4e, 0x6f, 0x67, 0x92, 0x7c, 0x2c, 0x9f, 0xc8, 0x49, 0xf9, 0x4c, 0x3e, 0x97, 0x2f, 0xe4, 0x94, 0x7c, 0x29, 0xa7, 0xed, 0xca, 0x72, 0x46, 0xbe, 0x66, 0x7f, 0x7d, 0xce, 0xee, 0x20, 0xdf, 0xc8, 0xb7, 0x72, 0x41, 0xbe, 0x93, 0xef, 0xe5, 0x07, 0xb9, 0x28, 0x97, 0xe5, 0x47, 0xf9, 0xc9, 0x38, 0xeb, 0x3c, 0x67, 0x77, 0x74, 0x26, 0x3a, 0x0b, 0xb4, 0x35, 0xda, 0x3a, 0x6d, 0x99, 0x77, 0xb3, 0x77, 0x8b, 0xb6, 0x49, 0x7b, 0x4d, 0xdb, 0xae, 0xad, 0xd0, 0x36, 0xb3, 0x87, 0xda, 0xaa, 0x2d, 0xd7, 0x5e, 0xd7, 0x56, 0x6a, 0x5b, 0xb4, 0x55, 0x5a, 0x8a, 0x5e, 0x46, 0x8f, 0xd6, 0xcb, 0xea, 0x95, 0xf5, 0x72, 0x7a, 0x15, 0xbd, 0xbc, 0x5e, 0x55, 0xaf, 0xa0, 0x57, 0xd3, 0x2b, 0xea, 0xd5, 0xf5, 0x4a, 0x7a, 0x0d, 0x6d, 0xbd, 0xb6, 0xc1, 0x7a, 0xc7, 0x2e, 0xed, 0xec, 0xd2, 0x5e, 0xd5, 0xd6, 0x5a, 0xd5, 0xb4, 0x6d, 0xe6, 0x40, 0x76, 0x9f, 0x7e, 0x6d, 0xa3, 0xb1, 0xdf, 0xf8, 0xdd, 0xf8, 0xc3, 0x7c, 0xde, 0x9c, 0xaa, 0xa4, 0x73, 0xfc, 0x8a, 0x6d, 0x35, 0x32, 0x3f, 0x31, 0x3f, 0x35, 0x7f, 0x92, 0xdf, 0xe5, 0x0f, 0x25, 0xbd, 0xb3, 0xd3, 0xd9, 0xe1, 0xec, 0x56, 0x14, 0xb3, 0xa2, 0x5c, 0x33, 0xbb, 0xc9, 0xaf, 0x72, 0xdd, 0x9c, 0x22, 0x37, 0xe4, 0xa6, 0xdc, 0x72, 0xb6, 0xcb, 0x6d, 0xb9, 0x63, 0xde, 0x37, 0x37, 0x5b, 0x39, 0xe5, 0x9e, 0x55, 0xd1, 0xec, 0x6f, 0xb5, 0x92, 0xfb, 0x12, 0xb0, 0x46, 0x2a, 0xa2, 0xa8, 0x8a, 0xa6, 0xe8, 0x8a, 0xa1, 0x78, 0x14, 0x53, 0xb1, 0x14, 0x47, 0xf1, 0x5a, 0xaf, 0x28, 0x19, 0x95, 0xcc, 0x4a, 0x16, 0x25, 0x9b, 0xf5, 0x85, 0x92, 0x5d, 0xc9, 0xa1, 0xe4, 0x74, 0xde, 0x53, 0x72, 0x29, 0xb9, 0x95, 0x30, 0x25, 0xaf, 0xfd, 0x8c, 0x12, 0x6e, 0x3f, 0x6b, 0x1d, 0x51, 0x7c, 0xf6, 0x25, 0xa5, 0x80, 0x7d, 0x5e, 0x89, 0x54, 0x0a, 0x29, 0x85, 0x95, 0xa7, 0x94, 0x22, 0x4a, 0x51, 0xa5, 0x98, 0x52, 0x5c, 0x29, 0xa1, 0x94, 0xb4, 0x4e, 0x59, 0x5f, 0x2a, 0xa5, 0x9c, 0xc3, 0x4a, 0x94, 0x3d, 0xdb, 0xce, 0x65, 0x17, 0xb4, 0x4b, 0x2a, 0x79, 0x94, 0x7c, 0x76, 0x0e, 0xe7, 0x23, 0xe7, 0x7d, 0xfb, 0xb2, 0x52, 0x50, 0xaf, 0xa5, 0x37, 0x56, 0x32, 0x28, 0xa5, 0xf5, 0xda, 0x7a, 0x13, 0x3d, 0x46, 0x6f, 0xaa, 0xc7, 0xea, 0xcd, 0xf4, 0x3a, 0x7a, 0x73, 0xbd, 0xae, 0x1e, 0xa7, 0xd7, 0xd3, 0x5b, 0x38, 0xb9, 0xd9, 0x6d, 0xdc, 0x55, 0xf2, 0xcb, 0x03, 0x25, 0xc2, 0x39, 0xe4, 0x9c, 0xd4, 0xeb, 0xeb, 0x2d, 0xf5, 0x06, 0x7a, 0x2b, 0xbd, 0xa1, 0xde, 0xda, 0x7b, 0xd4, 0xfb, 0x9e, 0xde, 0x48, 0x6f, 0x63, 0x24, 0x78, 0xf7, 0x7b, 0x0f, 0x7a, 0x0f, 0x7b, 0xdf, 0x31, 0xda, 0x1b, 0x4f, 0x6b, 0x6f, 0x68, 0xdf, 0x69, 0x6f, 0x6a, 0x17, 0xac, 0xaa, 0xf6, 0x78, 0xf9, 0x4d, 0xc9, 0xaa, 0xed, 0xd4, 0xbe, 0xd7, 0x76, 0x69, 0x3f, 0x68, 0xbb, 0xb5, 0x8b, 0xc6, 0x4d, 0xe3, 0xaa, 0xb6, 0x47, 0xbb, 0xa4, 0xed, 0xd5, 0x2e, 0x6b, 0xfb, 0xb4, 0x1f, 0xb5, 0xfd, 0xda, 0x4f, 0xda, 0x01, 0xed, 0x8a, 0x76, 0x50, 0xbb, 0xaa, 0x1d, 0xd2, 0x7e, 0xd6, 0xde, 0xd2, 0x7e, 0x31, 0x6e, 0x19, 0x3f, 0x6b, 0x87, 0xb5, 0x6b, 0xd6, 0x8b, 0xda, 0xaf, 0xda, 0x11, 0xed, 0x37, 0xed, 0x6d, 0xed, 0xba, 0xf6, 0x8e, 0x76, 0x43, 0x7b, 0x57, 0xbb, 0xa9, 0x1d, 0xd5, 0x6e, 0x69, 0xef, 0x69, 0xb7, 0xb5, 0xf7, 0xb5, 0x3b, 0xda, 0x07, 0xda, 0xef, 0xe6, 0x45, 0x27, 0xc5, 0x39, 0xa2, 0x1d, 0xd3, 0xfe, 0xd0, 0x3e, 0xd4, 0xee, 0x6a, 0x1f, 0x69, 0xf7, 0xb4, 0xe3, 0xda, 0x7d, 0xed, 0x84, 0xf6, 0x40, 0xfb, 0x58, 0x0b, 0x98, 0x97, 0xcc, 0x4e, 0xd6, 0x76, 0x33, 0xd1, 0xda, 0x61, 0x26, 0x5b, 0x7b, 0xcc, 0x55, 0xd6, 0x65, 0xed, 0x13, 0x5d, 0xbc, 0xbb, 0xbc, 0x7b, 0x9d, 0xad, 0xce, 0xdb, 0xce, 0x36, 0xe7, 0xa8, 0xf6, 0xa9, 0xae, 0x38, 0x6f, 0x38, 0xc7, 0x9c, 0x37, 0x9d, 0x0f, 0xad, 0x72, 0xf6, 0x70, 0xed, 0xa4, 0xae, 0x7a, 0x77, 0x7b, 0xf7, 0x39, 0x07, 0x9d, 0x4f, 0x9d, 0x3d, 0xf6, 0x35, 0x67, 0xaf, 0xfd, 0xab, 0xb3, 0xcf, 0xfe, 0xcd, 0xd9, 0xef, 0x7c, 0xac, 0x7d, 0xa6, 0x6b, 0xce, 0x01, 0xe7, 0x13, 0xed, 0x73, 0x5d, 0xd7, 0xbe, 0xd0, 0x0d, 0xed, 0x94, 0xee, 0xd1, 0xbe, 0xb4, 0x3e, 0xd4, 0xbe, 0xd2, 0x4d, 0xed, 0xb4, 0x6e, 0x69, 0x67, 0x74, 0x5b, 0xfb, 0x5a, 0x77, 0xb4, 0xb3, 0xba, 0x57, 0x3b, 0xa7, 0xa7, 0xd3, 0xce, 0xeb, 0xe9, 0xb5, 0x6f, 0xf4, 0x0c, 0xda, 0xb7, 0x7a, 0x46, 0xe7, 0xb8, 0xf3, 0x81, 0xfd, 0x87, 0x56, 0x43, 0xab, 0xa9, 0xd5, 0xb2, 0xef, 0xd9, 0x0f, 0xec, 0x80, 0x56, 0x5b, 0x8b, 0xd1, 0x62, 0x1d, 0x55, 0xab, 0xe3, 0x98, 0x8e, 0xad, 0xd5, 0x75, 0x1c, 0xad, 0x9e, 0x56, 0x5f, 0x6b, 0xe0, 0xa4, 0x73, 0x32, 0x3a, 0x99, 0xb4, 0x86, 0x5a, 0x23, 0x27, 0xb3, 0xd6, 0x58, 0x6b, 0xa2, 0x35, 0xd5, 0x9a, 0x69, 0xcd, 0xb5, 0x38, 0x8f, 0xe9, 0x61, 0x39, 0xf7, 0x38, 0x1e, 0xaf, 0x27, 0x9d, 0xd6, 0x42, 0x6b, 0xe9, 0x49, 0xaf, 0xb5, 0x32, 0x2e, 0x1b, 0x3f, 0x7a, 0xc4, 0xa3, 0x68, 0xad, 0x3d, 0xaa, 0x47, 0xf3, 0xe8, 0x5a, 0x1b, 0x8f, 0xe1, 0xf1, 0x68, 0x6d, 0xb5, 0x76, 0x5a, 0xbc, 0xf7, 0x43, 0x23, 0xdc, 0xe9, 0x67, 0x8f, 0xd5, 0x12, 0xb4, 0xf6, 0xd6, 0x01, 0xeb, 0x98, 0x75, 0xd5, 0x4e, 0x67, 0x47, 0x1b, 0x3e, 0xbb, 0xbe, 0xdd, 0xd2, 0x1e, 0x67, 0x4f, 0xb5, 0x5f, 0xf6, 0xce, 0xb5, 0x4f, 0xd9, 0xdf, 0xd9, 0xbf, 0x5b, 0xdb, 0xbc, 0xb3, 0x8d, 0x08, 0xa3, 0x80, 0x11, 0xe9, 0xbc, 0xeb, 0xdd, 0x63, 0x87, 0x19, 0x01, 0xed, 0x69, 0xfb, 0x53, 0xad, 0x83, 0xd6, 0xd1, 0xbe, 0xaf, 0x3d, 0xa3, 0x75, 0xd2, 0x12, 0x1d, 0x45, 0x4b, 0x72, 0x34, 0x27, 0x07, 0x67, 0x90, 0x67, 0xb5, 0xd1, 0xda, 0x18, 0xef, 0x44, 0xef, 0x64, 0xb5, 0xba, 0x77, 0xaa, 0x99, 0xdb, 0xcc, 0x63, 0x86, 0x99, 0x79, 0xcd, 0x7c, 0x66, 0x7e, 0xef, 0x70, 0xef, 0x74, 0xef, 0x0c, 0x33, 0xc9, 0xda, 0x69, 0xf6, 0xb0, 0xf6, 0x9a, 0x3d, 0xad, 0x7d, 0x66, 0x2f, 0x6b, 0xbf, 0x39, 0xc8, 0x7a, 0xd7, 0x1c, 0x62, 0x1d, 0x35, 0x87, 0x5a, 0xef, 0x99, 0xc3, 0xac, 0xf7, 0x8d, 0x1f, 0x8c, 0x8b, 0xe6, 0x70, 0xeb, 0x03, 0xf3, 0x65, 0xeb, 0x82, 0x39, 0xdf, 0xfa, 0xde, 0x5c, 0x60, 0xfd, 0x60, 0x2e, 0xb4, 0x2e, 0x9a, 0x8b, 0xac, 0x4b, 0xe6, 0x6a, 0xeb, 0x8a, 0xb9, 0xc1, 0x36, 0x4c, 0xbf, 0x9d, 0xd9, 0x4c, 0xb1, 0xb3, 0x98, 0x5b, 0xed, 0xac, 0x9e, 0x02, 0x76, 0x36, 0x73, 0x9b, 0xa7, 0xa0, 0xf9, 0xa5, 0x9d, 0xc7, 0xfc, 0xca, 0x2e, 0x62, 0x5e, 0xb3, 0x2b, 0x98, 0xbf, 0xda, 0x15, 0xcd, 0xdf, 0xec, 0x4a, 0xe6, 0x2d, 0xbb, 0xba, 0x79, 0xdb, 0xae, 0x61, 0xde, 0xb1, 0x6b, 0x9a, 0xbf, 0xdb, 0xb5, 0xcc, 0x07, 0x76, 0x1d, 0x33, 0x60, 0xd7, 0xb5, 0xc4, 0xae, 0x67, 0x99, 0x76, 0x33, 0xcb, 0xb2, 0x9b, 0x5b, 0xb6, 0x1d, 0x67, 0x39, 0x76, 0x0b, 0xeb, 0x29, 0xbb, 0xb7, 0x55, 0xc4, 0xee, 0x63, 0x15, 0xb5, 0xfb, 0x5a, 0xc5, 0xec, 0x7e, 0x56, 0x25, 0x7b, 0xb4, 0x15, 0x6d, 0x8f, 0xb1, 0x62, 0xec, 0xc9, 0x56, 0xac, 0x3d, 0xc5, 0xaa, 0x63, 0x3f, 0x6f, 0xd5, 0xf5, 0x44, 0x5a, 0x8d, 0xed, 0x17, 0xad, 0x26, 0xf6, 0x1c, 0xab, 0xa9, 0xfd, 0x92, 0xd5, 0xcc, 0x9e, 0x6b, 0x35, 0xb7, 0xe7, 0x59, 0x6d, 0xed, 0x45, 0x56, 0x3b, 0x7b, 0xb1, 0x15, 0x6f, 0x2f, 0xb1, 0x12, 0xec, 0xa5, 0x6a, 0x4f, 0xfb, 0x23, 0xab, 0x8f, 0x7d, 0xdc, 0xea, 0x6b, 0x9f, 0xb0, 0xfa, 0xd9, 0x1f, 0x5b, 0xfd, 0xed, 0x4f, 0xac, 0x01, 0xf6, 0xe7, 0xd6, 0x40, 0xfb, 0x0b, 0x6b, 0x90, 0xfd, 0xa5, 0x35, 0xd8, 0xfe, 0xca, 0x1a, 0x6a, 0x9f, 0xb1, 0x86, 0xd9, 0x5f, 0x5b, 0xc3, 0xed, 0xb3, 0xd6, 0x28, 0xfb, 0x1b, 0xeb, 0x59, 0xfb, 0x5b, 0xeb, 0x79, 0xfb, 0x67, 0xeb, 0x65, 0xfb, 0x96, 0x35, 0xdf, 0xbe, 0x6d, 0x2d, 0xb0, 0xef, 0xd8, 0xd9, 0x3d, 0x85, 0xed, 0x73, 0xf6, 0x2f, 0xde, 0x3a, 0xf6, 0x7c, 0xb3, 0x92, 0xb5, 0xca, 0x8c, 0xb6, 0x56, 0x9b, 0x95, 0xad, 0x35, 0x66, 0x15, 0xeb, 0x55, 0xb3, 0xaa, 0xb5, 0xd6, 0xac, 0x66, 0xad, 0x33, 0xab, 0x5b, 0xeb, 0xcd, 0x1a, 0xd6, 0x6b, 0x66, 0x4d, 0x6b, 0x83, 0x59, 0xcb, 0xda, 0x68, 0xd6, 0xb6, 0x36, 0x71, 0x26, 0x78, 0xdd, 0x9c, 0x66, 0x7d, 0x65, 0x4e, 0xb7, 0x4e, 0x9b, 0x2f, 0x58, 0x67, 0xcc, 0x19, 0xd6, 0xd7, 0xe6, 0x4c, 0xeb, 0xac, 0x39, 0xcb, 0x3a, 0x67, 0xce, 0xb6, 0xce, 0x9b, 0x2f, 0x5a, 0xdf, 0x98, 0x27, 0xed, 0x42, 0xe6, 0x67, 0x76, 0x6e, 0x2b, 0x97, 0xdd, 0xc9, 0xca, 0x6d, 0x27, 0x5a, 0x79, 0xec, 0x24, 0x2b, 0xcc, 0xee, 0x6c, 0xe5, 0xb5, 0xbb, 0x58, 0xf9, 0xec, 0x64, 0x2b, 0xbf, 0xdd, 0xd5, 0xf9, 0xd1, 0xb9, 0xe1, 0xfc, 0xe4, 0xdc, 0x74, 0xae, 0x38, 0xb7, 0x9c, 0xab, 0xce, 0x6d, 0xe7, 0x67, 0xe7, 0x8e, 0xd5, 0xc5, 0x7e, 0xd7, 0x4a, 0xb6, 0x8f, 0x3a, 0xbf, 0x38, 0xbf, 0x3b, 0xd7, 0x9c, 0x3f, 0x9c, 0x5f, 0x9d, 0xbb, 0xce, 0x6f, 0xce, 0x3d, 0xe7, 0xba, 0x73, 0xdf, 0x1a, 0x6d, 0x5f, 0xb0, 0x9e, 0xb3, 0x7f, 0xb4, 0x26, 0xdb, 0x57, 0xac, 0x49, 0xf6, 0x4f, 0x4e, 0x69, 0xa7, 0xbc, 0x53, 0xd6, 0xa9, 0xe8, 0x94, 0x71, 0x2a, 0x38, 0xe5, 0x9c, 0x4a, 0x66, 0x23, 0xb3, 0x99, 0xd9, 0xc4, 0x8c, 0x33, 0x1b, 0x9b, 0xcd, 0xcd, 0xa6, 0x66, 0x0b, 0xa7, 0x91, 0xd3, 0xd4, 0x69, 0xe2, 0x34, 0x77, 0x1a, 0x3b, 0xcd, 0xcc, 0xc5, 0xe6, 0x32, 0x73, 0xa9, 0xb9, 0xc2, 0x5c, 0x62, 0x2e, 0x77, 0x5a, 0x3b, 0xf1, 0x4e, 0x5b, 0xa7, 0xbd, 0xd3, 0xc6, 0x49, 0x70, 0xda, 0x39, 0x4f, 0x9b, 0xdb, 0xcd, 0x9d, 0xe6, 0x1b, 0xe6, 0x6e, 0x73, 0x87, 0xb9, 0xcb, 0x7c, 0xd3, 0xdc, 0xe3, 0xf4, 0x77, 0x06, 0x3b, 0x03, 0x9d, 0xa1, 0xce, 0x00, 0x67, 0x88, 0x33, 0xc8, 0x19, 0x66, 0x9e, 0x36, 0xcf, 0x99, 0x5f, 0x9b, 0xdf, 0x98, 0x67, 0xcc, 0xf3, 0xe6, 0x59, 0xf3, 0x5b, 0xbb, 0xbf, 0x3d, 0xc8, 0x1e, 0x68, 0x0f, 0xb1, 0x07, 0xd8, 0x83, 0xad, 0xe2, 0x56, 0x49, 0xab, 0x84, 0x55, 0xda, 0x8a, 0xb2, 0x4a, 0x39, 0x93, 0x9d, 0x69, 0xce, 0xf3, 0xce, 0x0b, 0xce, 0x14, 0x67, 0xba, 0x33, 0xd5, 0x99, 0x61, 0xbd, 0xe0, 0x2c, 0x74, 0x96, 0x3a, 0x8b, 0x9d, 0xe5, 0xce, 0x22, 0x67, 0x99, 0xb3, 0xc4, 0x59, 0xe1, 0xe4, 0x75, 0x7c, 0x4e, 0x7e, 0xa7, 0x80, 0x93, 0xcf, 0x89, 0x70, 0xc2, 0x9d, 0x48, 0xa7, 0xb2, 0x13, 0xed, 0xb4, 0x70, 0xe2, 0x9c, 0x8e, 0x4e, 0x07, 0xe7, 0x59, 0x67, 0x94, 0x3d, 0xcc, 0x1e, 0xea, 0xcc, 0x71, 0x5e, 0x74, 0x5e, 0x71, 0x56, 0x3a, 0x35, 0x9c, 0x18, 0xa7, 0x96, 0x53, 0xc7, 0xa9, 0xe9, 0xc4, 0x3a, 0xb5, 0x9d, 0xba, 0x66, 0x3b, 0xf3, 0x69, 0x33, 0xc1, 0xec, 0x68, 0xc6, 0x9b, 0x1d, 0xcc, 0xf6, 0xe6, 0x33, 0x4e, 0xa2, 0x93, 0xec, 0x74, 0x76, 0xba, 0x39, 0x49, 0x4e, 0x57, 0xa7, 0x8b, 0xd3, 0xdd, 0x3c, 0x60, 0x1e, 0x36, 0x0f, 0x99, 0x6f, 0x9b, 0x07, 0xcd, 0x23, 0xe6, 0x5b, 0xe6, 0x3b, 0xce, 0x1a, 0x67, 0xbd, 0xb3, 0xd6, 0xd9, 0xe0, 0xbc, 0xea, 0xbc, 0xe6, 0xac, 0x73, 0x36, 0x3a, 0x4f, 0x39, 0xc5, 0x9d, 0xa2, 0x4e, 0x09, 0xa7, 0x88, 0x13, 0xe5, 0x14, 0x73, 0x4a, 0x3a, 0xd5, 0x9c, 0xaa, 0x4e, 0x7d, 0xa7, 0xba, 0x53, 0xcf, 0xa9, 0xe2, 0x34, 0x30, 0xdb, 0x98, 0xad, 0xcd, 0x56, 0x66, 0x4b, 0xb3, 0xad, 0x51, 0xcf, 0x68, 0x68, 0x44, 0x1b, 0xd5, 0x8c, 0xba, 0x4e, 0x4f, 0xa7, 0x93, 0xd3, 0xc3, 0x79, 0xc6, 0xe9, 0x65, 0xbe, 0x62, 0xae, 0x34, 0xf7, 0x99, 0x7b, 0xcd, 0xfd, 0x46, 0x0d, 0xa3, 0xba, 0x51, 0xd3, 0x19, 0xeb, 0x8c, 0xe1, 0x14, 0x38, 0xdc, 0x19, 0xed, 0x8c, 0x34, 0x7f, 0x30, 0xbf, 0x37, 0x2f, 0x98, 0xdf, 0x19, 0x31, 0x46, 0x2d, 0xa3, 0xb6, 0x33, 0xcf, 0x99, 0xeb, 0xcc, 0x72, 0x66, 0x3a, 0xe3, 0x9d, 0x09, 0xce, 0x4b, 0xce, 0x6c, 0x6b, 0xb6, 0x35, 0xcb, 0x9a, 0x69, 0xcd, 0xb0, 0xaa, 0x18, 0x75, 0x8c, 0x58, 0xa3, 0xaa, 0xf3, 0xba, 0xb3, 0xda, 0xd9, 0xe4, 0xac, 0x72, 0x36, 0x5b, 0x65, 0xad, 0x32, 0x4e, 0x21, 0xa7, 0xa0, 0x53, 0xd8, 0xa8, 0x62, 0x54, 0x36, 0x1a, 0x1b, 0x4d, 0x8c, 0xa6, 0x46, 0x23, 0xa3, 0x99, 0xd1, 0xdc, 0x88, 0x33, 0x5a, 0x18, 0x2d, 0x8d, 0x56, 0x46, 0x6b, 0xa3, 0xac, 0x51, 0xda, 0x28, 0x63, 0x74, 0x32, 0x12, 0x95, 0x46, 0x46, 0x39, 0xa5, 0xb1, 0xd2, 0xc4, 0xf9, 0xda, 0xf8, 0xc5, 0xf8, 0x55, 0xa9, 0xa6, 0x54, 0x57, 0x6a, 0x19, 0xe5, 0x95, 0x1a, 0x4a, 0x4d, 0xa5, 0xb6, 0x51, 0x41, 0x69, 0xa7, 0xc4, 0x2b, 0xcd, 0x8c, 0x24, 0x7d, 0xbc, 0xbb, 0xe7, 0x32, 0x7e, 0x33, 0x6e, 0x28, 0x15, 0x8d, 0xeb, 0xfa, 0x97, 0x8e, 0xa1, 0x9f, 0xd4, 0x3f, 0xd3, 0x3f, 0xd7, 0x2f, 0xeb, 0x3f, 0xca, 0x32, 0xfd, 0x67, 0xfb, 0xae, 0x23, 0x8e, 0xd7, 0xc9, 0xa6, 0x2d, 0xb1, 0x1b, 0xe8, 0xd9, 0xf5, 0x1c, 0x7a, 0x4e, 0x3d, 0x97, 0x9e, 0x5b, 0xf9, 0x51, 0xe9, 0xa1, 0x0c, 0x54, 0x3a, 0xeb, 0x91, 0xea, 0x32, 0xe3, 0x53, 0x25, 0xc5, 0x38, 0xa9, 0x6c, 0x35, 0xbe, 0x30, 0xbe, 0x34, 0x3e, 0x33, 0xbe, 0x32, 0x3e, 0x37, 0x4e, 0x19, 0xa7, 0x8d, 0x33, 0xc6, 0x27, 0x6a, 0x5f, 0xef, 0x8b, 0xca, 0x7b, 0xca, 0x07, 0xca, 0xfb, 0xca, 0x31, 0xe5, 0x63, 0xe5, 0x13, 0xe5, 0x23, 0xe5, 0x43, 0xe5, 0xb8, 0x72, 0x42, 0x79, 0x57, 0xd9, 0xae, 0x34, 0x57, 0xde, 0x56, 0xde, 0xd2, 0x0b, 0x2a, 0x3b, 0xf5, 0xc2, 0x46, 0x37, 0xbd, 0xb8, 0x72, 0x54, 0xf9, 0x49, 0xb9, 0x62, 0x9c, 0xb3, 0x6b, 0x1b, 0xe7, 0xad, 0xca, 0x6a, 0x2f, 0x6b, 0xab, 0xbd, 0xcc, 0xbb, 0x53, 0x5d, 0xa4, 0xf6, 0x56, 0x17, 0xab, 0x7d, 0xd4, 0x25, 0xea, 0x52, 0xb5, 0xbf, 0xba, 0x5c, 0x5d, 0x21, 0xb5, 0x24, 0x56, 0x12, 0xa4, 0x89, 0xb4, 0x17, 0x8f, 0xea, 0x84, 0xfe, 0x09, 0x0a, 0x45, 0xfe, 0xbf, 0xfd, 0xf9, 0xbf, 0xfb, 0x84, 0x2b, 0xc1, 0xb3, 0xa9, 0x65, 0x8e, 0xe1, 0x24, 0x9a, 0x99, 0x73, 0xe7, 0x22, 0xce, 0x9c, 0x8b, 0x39, 0x6f, 0x2e, 0xe1, 0xc4, 0x59, 0x96, 0xd3, 0xe6, 0xf1, 0xe0, 0x79, 0x33, 0x1f, 0x27, 0x4e, 0xce, 0x9b, 0x9c, 0x36, 0xdb, 0x70, 0xde, 0x6c, 0x6b, 0xc7, 0x73, 0xde, 0x3c, 0xc0, 0x59, 0x93, 0x93, 0x26, 0xb9, 0xd0, 0x2d, 0x78, 0x46, 0x4e, 0x50, 0x9a, 0x29, 0xed, 0x65, 0x94, 0xd2, 0x45, 0x49, 0x52, 0x92, 0x65, 0x13, 0x67, 0xe5, 0x9d, 0x9c, 0x79, 0xf3, 0x2a, 0x6f, 0x29, 0x7b, 0x95, 0x7d, 0xca, 0x7e, 0xa5, 0x1f, 0x67, 0xe4, 0xc3, 0x44, 0xef, 0x1d, 0xf9, 0x46, 0x39, 0x22, 0x33, 0x65, 0x96, 0x13, 0xc6, 0x99, 0x76, 0x80, 0x3e, 0x8e, 0x33, 0xf6, 0x01, 0xe5, 0x6d, 0x75, 0x98, 0xb2, 0xcb, 0xfd, 0x17, 0x4b, 0x94, 0xba, 0x4a, 0x55, 0x51, 0x39, 0x39, 0x97, 0xb4, 0x32, 0xb9, 0x67, 0x43, 0x37, 0x0b, 0x83, 0x79, 0xe7, 0xe6, 0xe0, 0x1b, 0xca, 0x51, 0xfb, 0xa2, 0x35, 0x4e, 0xf1, 0x93, 0x41, 0x31, 0x4a, 0xac, 0x1b, 0x4d, 0x32, 0xb1, 0xb9, 0x52, 0x4b, 0xa9, 0xad, 0x8f, 0xe7, 0xa4, 0x3d, 0x96, 0xf3, 0xf6, 0x78, 0x4e, 0xd9, 0xc1, 0xd3, 0xb5, 0xf9, 0xae, 0x7b, 0xb2, 0x36, 0x72, 0x70, 0xc2, 0x76, 0xcf, 0xd4, 0xed, 0x25, 0x99, 0x73, 0xc0, 0x36, 0x79, 0x33, 0xb4, 0xd3, 0x3f, 0x2c, 0x07, 0xd8, 0xed, 0xfb, 0x85, 0xff, 0x45, 0xf9, 0xfc, 0xd2, 0x26, 0xbe, 0x6e, 0x82, 0xcf, 0xd7, 0x78, 0xa7, 0x64, 0x68, 0xd1, 0xd8, 0xef, 0x69, 0xd5, 0x3e, 0xde, 0x5f, 0x3e, 0xcc, 0x5f, 0x24, 0x21, 0xb1, 0x9b, 0x6f, 0x7a, 0x9b, 0x78, 0xbf, 0x5a, 0x28, 0x69, 0x97, 0x25, 0x96, 0x74, 0xe9, 0x12, 0xd9, 0x39, 0x2c, 0x22, 0xc2, 0x2f, 0x09, 0x7e, 0x89, 0x8d, 0xac, 0xb3, 0x95, 0xec, 0x89, 0x4d, 0x8c, 0x29, 0xe1, 0x57, 0xa2, 0xfc, 0xbe, 0xc4, 0x6e, 0x25, 0xfc, 0x6a, 0x94, 0x2f, 0xd9, 0xe7, 0x3f, 0x18, 0xe7, 0xd7, 0x0b, 0xb7, 0xdf, 0x5a, 0x44, 0x71, 0x62, 0xeb, 0x76, 0xa9, 0xdb, 0xf2, 0xe9, 0xf8, 0x88, 0xc8, 0x88, 0xb0, 0xe9, 0xf1, 0x3e, 0x7f, 0x5c, 0x5c, 0x7c, 0x84, 0xbf, 0x56, 0x42, 0x98, 0xcf, 0x1f, 0xed, 0xd6, 0xa2, 0x13, 0x12, 0x7c, 0x29, 0xa9, 0x44, 0x49, 0xc9, 0xfe, 0x22, 0x74, 0x85, 0x5a, 0x3e, 0x7f, 0x69, 0x77, 0xbc, 0xb4, 0x4b, 0x79, 0x30, 0x2e, 0xde, 0x87, 0x12, 0xd3, 0x93, 0x7c, 0x7e, 0x27, 0x2e, 0x3e, 0x91, 0x1e, 0x9f, 0x3b, 0xe6, 0xb8, 0xb5, 0x8a, 0x6e, 0xad, 0x62, 0x62, 0x58, 0x62, 0x42, 0x42, 0x42, 0x98, 0x5f, 0x29, 0x9e, 0x90, 0x10, 0xe9, 0x97, 0xb8, 0xf8, 0xae, 0x09, 0x09, 0x25, 0xfc, 0x5a, 0x94, 0x0f, 0x3e, 0x7a, 0xa1, 0x24, 0x14, 0x32, 0x62, 0xe3, 0xe2, 0xfd, 0x46, 0x64, 0x8c, 0xdf, 0x13, 0x19, 0x83, 0xfa, 0x09, 0x7e, 0x25, 0xb1, 0x84, 0x5f, 0x8f, 0x8a, 0x44, 0x2f, 0x5f, 0x72, 0x8a, 0xd1, 0x39, 0xc6, 0xe7, 0x8e, 0xa4, 0x0a, 0x77, 0xff, 0xf6, 0x1b, 0x89, 0x75, 0xbb, 0xf8, 0xb5, 0x62, 0x11, 0xf4, 0xc7, 0xfa, 0xa6, 0xfb, 0xa6, 0xc3, 0x3b, 0xa5, 0xb4, 0x51, 0x08, 0xb3, 0x5a, 0xc4, 0x27, 0xc6, 0x85, 0x25, 0xb5, 0x4c, 0x88, 0x8f, 0x4c, 0x60, 0xb4, 0x56, 0xab, 0x78, 0x86, 0xc2, 0x5c, 0xa3, 0x42, 0x92, 0x4b, 0xf8, 0x8d, 0x28, 0xbf, 0x19, 0x5b, 0x7c, 0xab, 0xa8, 0xa9, 0xae, 0xf1, 0xd0, 0x8c, 0x8c, 0x89, 0xc4, 0xc5, 0x91, 0x31, 0x49, 0x7e, 0xb5, 0x73, 0x37, 0xbf, 0xd2, 0x05, 0xf9, 0x7e, 0xa3, 0x58, 0x09, 0xbf, 0x19, 0xe5, 0x73, 0x95, 0xf4, 0xc6, 0x76, 0xd9, 0xa9, 0x4b, 0x67, 0x9f, 0xcb, 0xc1, 0x5f, 0x2b, 0x31, 0xc1, 0x25, 0x49, 0xac, 0x13, 0x54, 0xd2, 0x8a, 0xda, 0x6a, 0x7a, 0x25, 0xb6, 0x6e, 0x4c, 0xb1, 0x88, 0x87, 0xce, 0xb6, 0xa3, 0x1e, 0x77, 0xbe, 0x93, 0xca, 0x45, 0x29, 0x8e, 0x0a, 0xb1, 0x58, 0x9c, 0xe8, 0xab, 0x3b, 0x3d, 0x32, 0xc9, 0x0d, 0x44, 0xd0, 0x53, 0x12, 0xe6, 0x7a, 0xd3, 0xef, 0x0b, 0x43, 0xc9, 0x34, 0x2d, 0xfd, 0x5a, 0xa1, 0xc8, 0xa4, 0x3a, 0xa9, 0x22, 0xbc, 0xff, 0x30, 0xdd, 0x5f, 0x90, 0x59, 0x12, 0xf6, 0xa7, 0x69, 0x8f, 0x4e, 0x4a, 0x17, 0x15, 0x34, 0x68, 0xab, 0xd7, 0xd1, 0xea, 0xc6, 0x47, 0x84, 0x45, 0x46, 0x24, 0x14, 0x8b, 0x28, 0xe1, 0x4f, 0x1f, 0x95, 0xa2, 0xaa, 0x75, 0xfd, 0xc9, 0x49, 0x75, 0x4a, 0xf8, 0x33, 0x44, 0x41, 0xe8, 0xf3, 0xf9, 0xd3, 0xc5, 0x36, 0x72, 0xa7, 0x53, 0x89, 0x8c, 0x49, 0xf0, 0xa7, 0x77, 0x5b, 0x2d, 0x69, 0xa5, 0xa7, 0x55, 0xc2, 0x9f, 0x11, 0x36, 0x99, 0x82, 0x2e, 0xf1, 0xe1, 0x81, 0x2e, 0xc8, 0xf5, 0x67, 0x88, 0x4d, 0xf4, 0x4d, 0x4f, 0xf4, 0xf9, 0x33, 0xe0, 0xb4, 0x12, 0xfe, 0x4c, 0x51, 0x8d, 0x5b, 0xc7, 0xa7, 0xe8, 0xc9, 0x75, 0x12, 0x0a, 0xfa, 0xd3, 0x77, 0x8d, 0x1c, 0x51, 0xc2, 0x9f, 0x39, 0xaa, 0x71, 0x8b, 0xf8, 0xc6, 0xad, 0x52, 0x3b, 0xc3, 0x22, 0xe8, 0xcf, 0x1a, 0xec, 0xcf, 0x12, 0x95, 0x22, 0x19, 0x63, 0xdb, 0xc4, 0xa7, 0x64, 0xcc, 0x18, 0xeb, 0x57, 0x92, 0x62, 0xfc, 0x19, 0x8b, 0xbb, 0x49, 0x4a, 0xea, 0xc6, 0xa4, 0xa4, 0x73, 0xff, 0x4a, 0xcf, 0x5f, 0x7e, 0x25, 0x07, 0x91, 0xd0, 0x0a, 0xc5, 0xc5, 0xa7, 0xb8, 0xce, 0xc3, 0xda, 0x98, 0xe9, 0x84, 0xd7, 0x15, 0x5b, 0x2c, 0x22, 0x92, 0x69, 0x69, 0xf5, 0xb0, 0xd4, 0x71, 0x77, 0x0a, 0xb9, 0xef, 0xf6, 0x24, 0x60, 0x49, 0x7d, 0xf4, 0xaf, 0x4f, 0xef, 0xe3, 0xa1, 0xfa, 0x87, 0x00, 0xa6, 0x88, 0x64, 0x8d, 0xc4, 0x5b, 0xb1, 0x7e, 0xa9, 0xb1, 0x55, 0x51, 0x94, 0x60, 0xac, 0xb2, 0x46, 0x49, 0x8a, 0xa8, 0x75, 0x5b, 0xc7, 0xfb, 0x33, 0x46, 0xc6, 0xf8, 0xea, 0xfa, 0xbd, 0x24, 0xa5, 0x13, 0x49, 0xbe, 0xc5, 0xf8, 0x12, 0x11, 0xbf, 0x23, 0x53, 0x26, 0x85, 0x35, 0x30, 0x26, 0x66, 0x7a, 0x62, 0x4a, 0x16, 0x4f, 0x71, 0xff, 0xd0, 0xe2, 0x61, 0x05, 0x70, 0x53, 0x36, 0x6c, 0xcb, 0x5a, 0xbc, 0x84, 0x3f, 0x7b, 0x54, 0x8a, 0xe2, 0x96, 0x39, 0xf0, 0xb3, 0x5b, 0xe6, 0x8c, 0x4a, 0xd1, 0xdc, 0x32, 0x57, 0x54, 0x8a, 0xee, 0x96, 0xb9, 0xa3, 0x52, 0x0c, 0xb7, 0xcc, 0x13, 0x95, 0xe2, 0x71, 0xcb, 0xb0, 0xa8, 0x14, 0xd3, 0x2d, 0xf3, 0x46, 0xa5, 0x58, 0x6e, 0x99, 0x2f, 0x2a, 0xc5, 0x76, 0xcb, 0xa2, 0x51, 0xbe, 0x92, 0x7e, 0xa5, 0x63, 0x09, 0x7f, 0xb1, 0x60, 0x65, 0x60, 0x09, 0x7f, 0xf1, 0x60, 0x65, 0x50, 0x09, 0x7f, 0xfe, 0x28, 0xf1, 0xa7, 0x2f, 0xfe, 0xdf, 0xd0, 0x31, 0x1c, 0x1d, 0xf3, 0xc3, 0xdb, 0x87, 0x8e, 0x6e, 0x19, 0x81, 0x8e, 0x6e, 0x59, 0x00, 0x1d, 0xdd, 0x32, 0x12, 0x1d, 0xdd, 0xb2, 0x20, 0x3a, 0xba, 0x65, 0x21, 0x74, 0x74, 0xcb, 0xc2, 0xe8, 0xe8, 0x96, 0x4f, 0xa1, 0xa3, 0x5b, 0x16, 0x41, 0x47, 0xb7, 0x8c, 0x8a, 0xf2, 0x55, 0x0b, 0xa6, 0x5a, 0x89, 0x28, 0xc4, 0x66, 0x4a, 0xf4, 0xc5, 0x12, 0x9f, 0xc4, 0xd8, 0x60, 0x38, 0x78, 0x7c, 0xa2, 0xdc, 0x7c, 0x2b, 0x19, 0xe5, 0x2f, 0x51, 0xdc, 0x5f, 0x82, 0x27, 0xa9, 0x14, 0x49, 0x5c, 0xdf, 0xf7, 0x0f, 0x91, 0x88, 0x4c, 0x8a, 0x8e, 0x74, 0x97, 0xb1, 0x7f, 0x49, 0x41, 0x2a, 0x95, 0xf0, 0x97, 0x7e, 0x18, 0x1e, 0x25, 0x87, 0xbf, 0x54, 0xb1, 0x14, 0x43, 0xc9, 0x5e, 0x37, 0x9e, 0x65, 0xc8, 0x35, 0xb0, 0xcc, 0xa3, 0x9e, 0xf9, 0xeb, 0x70, 0xd9, 0x28, 0x5f, 0x85, 0xa0, 0xbe, 0xe5, 0xa0, 0x53, 0xea, 0xfe, 0x55, 0x08, 0x4f, 0xd8, 0xdf, 0x0a, 0x77, 0xfb, 0x25, 0xc7, 0xf6, 0xe0, 0xcb, 0xb4, 0x4e, 0x8d, 0xc8, 0xe8, 0x94, 0xb2, 0x4a, 0x76, 0x2c, 0x2a, 0x8f, 0xfd, 0x28, 0xfc, 0xf7, 0xfa, 0x92, 0xd8, 0x49, 0xd1, 0x25, 0xfc, 0x15, 0xa2, 0x4a, 0xe6, 0xac, 0x56, 0xc2, 0x5f, 0xf1, 0xdf, 0x91, 0x92, 0x84, 0x5d, 0x20, 0xaf, 0x44, 0x48, 0x24, 0x47, 0x21, 0x5f, 0x49, 0x5f, 0x7d, 0xf7, 0xe1, 0xc5, 0x95, 0x0d, 0xa7, 0x4f, 0xaf, 0x1f, 0x59, 0x9f, 0xa7, 0x3d, 0x9e, 0x65, 0x9d, 0x65, 0x91, 0x27, 0xba, 0xa2, 0xa2, 0x64, 0xcf, 0x86, 0xfc, 0x68, 0x56, 0x99, 0x1c, 0x3c, 0x20, 0xfc, 0x2f, 0x48, 0xe2, 0xb7, 0x63, 0x8b, 0x77, 0x9d, 0x5e, 0x32, 0xd2, 0xe7, 0xab, 0x36, 0x1d, 0x5e, 0x95, 0xff, 0x1c, 0xf6, 0x95, 0x4c, 0xe5, 0xe1, 0xd7, 0xe1, 0x09, 0x95, 0xcf, 0x9f, 0xe8, 0x3e, 0xef, 0xb5, 0x5a, 0xc4, 0x6f, 0x53, 0x7d, 0x9a, 0x2f, 0x6c, 0x9b, 0x5a, 0x58, 0xcb, 0x93, 0x10, 0xe3, 0xae, 0x81, 0x16, 0xab, 0x69, 0x64, 0x90, 0x3a, 0xb2, 0x1e, 0x4f, 0x5f, 0xec, 0x93, 0x8f, 0x52, 0xa2, 0xbb, 0x0e, 0xa5, 0x2e, 0xf6, 0x6a, 0x6c, 0x62, 0x72, 0xa4, 0x5f, 0x8b, 0x4d, 0x4a, 0x66, 0x58, 0x8d, 0x4d, 0x0a, 0xa3, 0x9e, 0xe8, 0xae, 0x41, 0x4f, 0xce, 0x49, 0x42, 0x25, 0x16, 0xe6, 0xc8, 0x7a, 0xc4, 0x30, 0x12, 0x09, 0xf5, 0xb0, 0x8b, 0x22, 0x28, 0x05, 0x7e, 0x7f, 0x23, 0x24, 0x32, 0x75, 0xb5, 0xd3, 0x79, 0xc0, 0xf1, 0xbd, 0x41, 0x42, 0x19, 0x7f, 0xe1, 0x0a, 0x47, 0xd7, 0xa2, 0x42, 0x41, 0x25, 0xf8, 0x3b, 0x2e, 0x75, 0x95, 0xfb, 0x53, 0x16, 0x21, 0xaf, 0xe2, 0xfa, 0xc0, 0x47, 0x8f, 0x51, 0x38, 0xe4, 0x83, 0xc8, 0x6a, 0xb8, 0xa6, 0x6a, 0xb0, 0xdb, 0x6f, 0xf1, 0xf0, 0xf8, 0x7c, 0xf5, 0x22, 0xeb, 0xbb, 0xc2, 0xdc, 0x68, 0x55, 0x0b, 0xba, 0xcc, 0x35, 0x20, 0xe4, 0x51, 0x69, 0x1d, 0x5f, 0xd2, 0x57, 0x8d, 0x77, 0xa3, 0xab, 0x71, 0xa8, 0xd3, 0xe7, 0xea, 0x92, 0xe6, 0x72, 0x4f, 0x21, 0x5a, 0x0d, 0x1f, 0x7d, 0xfb, 0xa6, 0x06, 0xea, 0xef, 0x32, 0x38, 0x14, 0x99, 0x48, 0x37, 0x8d, 0xab, 0x87, 0x34, 0x88, 0x4d, 0x0b, 0x4d, 0xa2, 0xfb, 0x7a, 0x7e, 0xd2, 0xc4, 0xb4, 0x50, 0xd6, 0x88, 0x8a, 0xf4, 0x95, 0x74, 0xbd, 0x56, 0x8f, 0x85, 0xb9, 0x5a, 0x42, 0xc9, 0x94, 0x92, 0x4a, 0x36, 0x1e, 0xc0, 0x9a, 0x0f, 0xbb, 0xe3, 0x1e, 0xed, 0xae, 0xf5, 0x38, 0xf5, 0xdf, 0xd2, 0xd4, 0x8e, 0xf2, 0x47, 0x17, 0xff, 0x5b, 0xa6, 0x31, 0x51, 0xfe, 0xca, 0xc5, 0xa7, 0x23, 0xd8, 0x4d, 0x16, 0xb4, 0xfd, 0x2b, 0x0d, 0x61, 0x29, 0xe9, 0x2f, 0x09, 0x69, 0xec, 0xc3, 0x0c, 0x4b, 0xf3, 0xae, 0x9b, 0x5c, 0x91, 0xa4, 0x7a, 0x49, 0x1e, 0x92, 0x54, 0x76, 0x75, 0x58, 0x34, 0x58, 0xc3, 0xff, 0x1b, 0xa9, 0x58, 0xff, 0x7f, 0x2a, 0xfb, 0x5c, 0xf5, 0xdd, 0xf5, 0xa5, 0x5a, 0x24, 0x4b, 0xc8, 0x23, 0xf1, 0x8e, 0x48, 0x08, 0xe9, 0x58, 0xd7, 0x75, 0x46, 0x9a, 0xfd, 0xf5, 0x5c, 0xfb, 0x23, 0x22, 0x43, 0x0e, 0x08, 0xd9, 0xf1, 0xd0, 0xe4, 0xfa, 0x98, 0x9c, 0x3d, 0xf5, 0xe1, 0xe4, 0xed, 0xce, 0x73, 0x98, 0xb5, 0xa4, 0xbf, 0x3c, 0xcf, 0x62, 0x83, 0x7f, 0xe8, 0x6f, 0xc8, 0x9a, 0xab, 0x64, 0xcb, 0xea, 0xaf, 0x40, 0xbd, 0x51, 0x94, 0xbf, 0x12, 0x45, 0x63, 0xd7, 0x6b, 0x75, 0xf1, 0xab, 0xaf, 0x1e, 0xaf, 0xb2, 0x34, 0x3f, 0x35, 0x89, 0x72, 0xd3, 0xd1, 0xdf, 0x98, 0x6a, 0xd3, 0xa8, 0xad, 0xac, 0x33, 0x54, 0x9a, 0x51, 0x51, 0xdc, 0x4a, 0xf3, 0xa8, 0xad, 0x4a, 0xb0, 0x27, 0x8e, 0x4a, 0xb0, 0xa7, 0x85, 0x4b, 0x53, 0x97, 0x4a, 0x4b, 0x97, 0xc6, 0xad, 0xb4, 0x72, 0x69, 0xdc, 0x4a, 0x6b, 0x97, 0xc6, 0xad, 0xb4, 0x71, 0x69, 0x6a, 0x53, 0x69, 0xeb, 0xd2, 0xb8, 0x95, 0x76, 0x2e, 0x8d, 0x5b, 0x89, 0x77, 0x69, 0xdc, 0x4a, 0x82, 0x4b, 0x13, 0x4b, 0xa5, 0xbd, 0x4b, 0xe3, 0x56, 0x9e, 0x76, 0x69, 0xdc, 0x4a, 0x07, 0x97, 0xc6, 0xad, 0x74, 0x74, 0x69, 0xea, 0x51, 0x79, 0xc6, 0xa5, 0x71, 0x2b, 0x9d, 0x5c, 0x1a, 0xb7, 0x92, 0xe8, 0xd2, 0xb8, 0x95, 0x24, 0x97, 0x26, 0x86, 0x4a, 0x67, 0x97, 0xc6, 0xad, 0x74, 0x71, 0x69, 0xdc, 0x4a, 0xb2, 0x4b, 0xe3, 0x56, 0xba, 0x46, 0xf9, 0xab, 0x3c, 0x74, 0x73, 0x37, 0xb7, 0xe1, 0xaf, 0x41, 0xad, 0x7b, 0xb0, 0x56, 0x93, 0x5a, 0x8f, 0x60, 0x3e, 0xd1, 0xa8, 0x45, 0xa3, 0x67, 0x94, 0xbf, 0xea, 0x43, 0xea, 0x5e, 0x6e, 0x23, 0x48, 0xdd, 0x3b, 0x58, 0x73, 0xa9, 0xfb, 0x04, 0x6b, 0x2e, 0x69, 0xdf, 0x28, 0x7f, 0xb5, 0x87, 0xa4, 0xfd, 0xdc, 0x46, 0x90, 0xb4, 0x7f, 0xb0, 0xe6, 0x92, 0x0e, 0x08, 0xd6, 0x5c, 0xd2, 0x81, 0x51, 0xfe, 0xea, 0x0f, 0x49, 0x07, 0xb9, 0x8d, 0x20, 0xe9, 0xe0, 0x60, 0xcd, 0x25, 0x1d, 0x12, 0xac, 0xb9, 0xa4, 0x43, 0xa3, 0xb6, 0xd9, 0xba, 0x9a, 0xb6, 0x79, 0x8a, 0x29, 0xee, 0xb7, 0xba, 0xfa, 0xb5, 0x82, 0x71, 0x23, 0xd2, 0xde, 0x29, 0x25, 0x44, 0xd8, 0x8c, 0x1f, 0x6d, 0xc9, 0x46, 0x45, 0x99, 0xc5, 0xca, 0x9c, 0xba, 0x00, 0x0c, 0x48, 0x11, 0x4f, 0xcc, 0x1b, 0xfd, 0xda, 0xd7, 0xab, 0x58, 0xc0, 0x90, 0x62, 0x6e, 0xa3, 0x56, 0xba, 0x24, 0xab, 0xa9, 0x55, 0xdd, 0x53, 0xd4, 0xca, 0x69, 0x1a, 0x56, 0xa8, 0xab, 0xb7, 0xa7, 0x9d, 0xa7, 0xae, 0x5e, 0xce, 0xe3, 0x33, 0x82, 0x5d, 0xde, 0x98, 0xfd, 0xb9, 0xc6, 0x67, 0x1d, 0x9f, 0x71, 0xbc, 0x33, 0xde, 0xe3, 0x5e, 0x68, 0xb0, 0xe9, 0xcb, 0x14, 0xb3, 0x9f, 0xe3, 0x60, 0xda, 0x7f, 0xc1, 0x3e, 0x2d, 0xb9, 0x4e, 0x4a, 0x41, 0x65, 0x6a, 0x0b, 0x72, 0x7b, 0x6a, 0x7c, 0xb0, 0x55, 0xd8, 0x6d, 0xed, 0xb6, 0xc6, 0x8b, 0xa2, 0xd7, 0x9a, 0xda, 0xa5, 0x75, 0xb0, 0x93, 0xad, 0x76, 0x42, 0xad, 0x74, 0x5d, 0xac, 0xe6, 0x56, 0x4d, 0x4f, 0x71, 0x2b, 0xb7, 0x69, 0xa4, 0x2b, 0xb6, 0x53, 0x09, 0x4c, 0xf6, 0xeb, 0x33, 0x79, 0xb9, 0xd4, 0x49, 0x31, 0x92, 0xeb, 0xfc, 0x67, 0xe7, 0xc7, 0xf7, 0xf9, 0x6f, 0xa1, 0x2c, 0x74, 0xff, 0xc5, 0x41, 0xed, 0x8e, 0xc8, 0x83, 0x8d, 0xee, 0xbf, 0x47, 0x68, 0x6c, 0x0a, 0xfc, 0xaa, 0x5b, 0x81, 0xdf, 0x52, 0x5b, 0x8f, 0xd2, 0x18, 0x4b, 0xb5, 0x1b, 0x81, 0xeb, 0x0f, 0x36, 0xa5, 0x51, 0x19, 0x1b, 0xa1, 0xbb, 0x1e, 0xa2, 0x9b, 0xc5, 0x7f, 0xbd, 0xa5, 0xb7, 0x67, 0x12, 0x54, 0x73, 0xd5, 0x2b, 0x6e, 0x9f, 0x67, 0xb2, 0xb1, 0x46, 0x7b, 0x41, 0x3d, 0xfb, 0xd8, 0xf8, 0x00, 0x6d, 0xee, 0x63, 0x34, 0x23, 0xb4, 0x17, 0x1e, 0xa3, 0x7b, 0x54, 0xde, 0xfe, 0xc7, 0xb4, 0xba, 0x66, 0xec, 0x0f, 0xea, 0xf5, 0x72, 0xea, 0xbf, 0x9a, 0x98, 0xfa, 0x47, 0xd1, 0x1f, 0xc1, 0x0c, 0x8e, 0xc4, 0xae, 0x2d, 0x4d, 0x44, 0x74, 0x2b, 0xf8, 0xaf, 0xc2, 0x89, 0x87, 0x3e, 0x93, 0xba, 0xb9, 0x53, 0xc4, 0xca, 0xe5, 0xfe, 0x13, 0x90, 0xa0, 0xb5, 0x88, 0x53, 0x04, 0x8c, 0x17, 0xf1, 0x32, 0xe6, 0x2d, 0x0f, 0xce, 0x88, 0xa4, 0x1b, 0xe1, 0x7e, 0xd9, 0x06, 0x71, 0x60, 0x2f, 0x38, 0x29, 0x92, 0x61, 0x08, 0x38, 0x2f, 0x92, 0xf1, 0xa8, 0x48, 0xa6, 0x1e, 0x22, 0x99, 0xa7, 0xb2, 0xb5, 0x8c, 0x06, 0x07, 0x45, 0xb2, 0x41, 0x97, 0x9d, 0x73, 0x6c, 0x0e, 0x64, 0xe7, 0xb8, 0x26, 0x92, 0x6b, 0x1e, 0x40, 0x66, 0x6e, 0xc2, 0x90, 0x1b, 0x79, 0x79, 0x38, 0xe1, 0x85, 0xf9, 0x00, 0x7c, 0xf2, 0x42, 0x97, 0xf7, 0x98, 0x48, 0xbe, 0xd2, 0x22, 0xf9, 0x1b, 0x88, 0x84, 0xc3, 0x3f, 0x02, 0x1e, 0x05, 0xe0, 0x1b, 0xc9, 0x06, 0xa4, 0x10, 0xba, 0x15, 0xa6, 0xaf, 0x28, 0x63, 0xc5, 0x99, 0x53, 0xe2, 0x8a, 0x48, 0x49, 0xc6, 0x4a, 0x22, 0xbb, 0x14, 0xba, 0x95, 0xc6, 0x13, 0x65, 0xe8, 0x2b, 0x07, 0xdf, 0xf2, 0xc8, 0xad, 0x78, 0x42, 0xa4, 0x0a, 0x72, 0xab, 0x4e, 0x12, 0xa9, 0x8e, 0x2d, 0x35, 0xb1, 0xa9, 0x26, 0x3a, 0xd4, 0xc2, 0xfe, 0xda, 0xd8, 0x51, 0xfb, 0x45, 0x9e, 0x5a, 0xe4, 0xc6, 0x30, 0x27, 0x16, 0xba, 0x3a, 0xd8, 0x58, 0xe7, 0x08, 0x2b, 0x0b, 0x32, 0xea, 0x57, 0x03, 0xd0, 0xbb, 0xff, 0xc6, 0x66, 0x43, 0xec, 0x6b, 0x88, 0x8f, 0x1a, 0xae, 0x21, 0xe9, 0xe9, 0x6f, 0xc2, 0xdc, 0xa6, 0xf8, 0xaf, 0x19, 0x65, 0x1c, 0xed, 0x96, 0xd8, 0xdc, 0x2a, 0x4a, 0xa4, 0x35, 0xb6, 0xb5, 0x81, 0x57, 0xbb, 0xf5, 0x22, 0x09, 0xb5, 0x44, 0xda, 0xd3, 0x7e, 0x1a, 0xdf, 0x3d, 0x8d, 0x3e, 0x1d, 0xa0, 0xef, 0x00, 0xef, 0x8e, 0x2e, 0xd0, 0xb3, 0x13, 0x73, 0x12, 0xf1, 0x63, 0xe2, 0x68, 0x91, 0x24, 0xec, 0xe2, 0x6d, 0x2b, 0x9d, 0xa1, 0xed, 0x8c, 0x5d, 0x9d, 0x19, 0xef, 0x82, 0x6e, 0xc9, 0xf4, 0x75, 0x4d, 0x14, 0xe9, 0x06, 0x7a, 0x40, 0xd3, 0x13, 0xfa, 0x9e, 0xd8, 0xda, 0xeb, 0x9e, 0x48, 0xef, 0x0b, 0x22, 0x7d, 0xb0, 0xbd, 0x1f, 0x63, 0x03, 0xe8, 0x43, 0x45, 0x19, 0x8a, 0x2e, 0x43, 0xd1, 0x7d, 0x58, 0x32, 0x80, 0xef, 0x70, 0x62, 0x30, 0x02, 0x3e, 0xa3, 0xb0, 0xe1, 0x59, 0xc6, 0x46, 0x83, 0x31, 0xf8, 0x64, 0x3c, 0x7c, 0x26, 0x50, 0x9f, 0x80, 0xcf, 0x26, 0xd2, 0x7e, 0x8e, 0x3a, 0x2e, 0x92, 0x49, 0xc4, 0x6d, 0x32, 0x7e, 0x98, 0x8c, 0x6e, 0x53, 0xd0, 0x7f, 0x0a, 0xfc, 0x9f, 0x27, 0x4e, 0x53, 0x91, 0x3d, 0x15, 0xbe, 0xd3, 0x06, 0x88, 0x4c, 0xcf, 0x0b, 0x90, 0xfd, 0x02, 0x34, 0x33, 0x98, 0x3f, 0x13, 0x3f, 0xcf, 0x62, 0xce, 0x2c, 0x74, 0x9e, 0x4d, 0xae, 0xcc, 0x26, 0x4e, 0x2f, 0x62, 0xeb, 0x8b, 0xc8, 0x7c, 0x11, 0x5f, 0xcf, 0x21, 0x7e, 0x2f, 0xd1, 0x37, 0x37, 0x5e, 0x64, 0x5e, 0x41, 0x80, 0x4d, 0x2f, 0xe3, 0xab, 0x97, 0xf1, 0xcd, 0x7c, 0xe4, 0xce, 0x87, 0xef, 0x02, 0xfc, 0xbe, 0x60, 0x21, 0xc0, 0x8e, 0x85, 0xc4, 0x69, 0x21, 0x72, 0x16, 0xd2, 0xbf, 0x08, 0xb9, 0x8b, 0xe0, 0xb3, 0x08, 0x1d, 0x17, 0x93, 0x13, 0x8b, 0xe1, 0xbf, 0x04, 0x3d, 0x97, 0x30, 0x77, 0x29, 0xfa, 0x2d, 0x25, 0x76, 0xcb, 0xf0, 0xd9, 0x32, 0xf8, 0x2c, 0x23, 0x36, 0xcb, 0xa9, 0x2f, 0xc7, 0xf7, 0x2b, 0xe0, 0xbf, 0x92, 0xfa, 0x4a, 0x64, 0xbf, 0x02, 0xef, 0x57, 0xe8, 0x7b, 0x05, 0x9f, 0xad, 0x42, 0x87, 0xd5, 0xe8, 0xb0, 0x1a, 0x7e, 0x6b, 0x98, 0xb3, 0x06, 0xde, 0x6b, 0x18, 0x7b, 0x35, 0x1b, 0x20, 0xef, 0xd6, 0x22, 0x63, 0xed, 0x4a, 0x80, 0x1e, 0xeb, 0xd0, 0x67, 0x3d, 0xf9, 0xb8, 0x1e, 0xbb, 0x5e, 0x03, 0x1b, 0xd1, 0x6b, 0x13, 0xbe, 0x7e, 0x1d, 0x47, 0x6f, 0xc6, 0x7f, 0x5b, 0xe0, 0xeb, 0xa7, 0x9d, 0x42, 0xbc, 0x52, 0xb0, 0x69, 0x2b, 0xfe, 0xd8, 0x86, 0xdf, 0xb7, 0x11, 0xeb, 0x1d, 0xf4, 0xbd, 0x01, 0xef, 0x37, 0xf0, 0xd3, 0x9b, 0xf8, 0x70, 0x27, 0xba, 0xed, 0x86, 0xd7, 0x6e, 0x74, 0xd8, 0xb3, 0x49, 0x64, 0x2f, 0xf3, 0xf6, 0x61, 0xdb, 0x3e, 0xea, 0xfb, 0xb1, 0xe9, 0x00, 0xf6, 0x1e, 0x64, 0xde, 0x21, 0x78, 0xbf, 0xc5, 0xbc, 0xb7, 0x98, 0x73, 0x98, 0x3c, 0x39, 0xcc, 0xfc, 0x23, 0xe8, 0x79, 0x84, 0xe7, 0xe5, 0x6d, 0x74, 0x7c, 0x1b, 0x1e, 0xef, 0x20, 0xfb, 0x1d, 0xf8, 0x1c, 0x45, 0xd7, 0xa3, 0xcc, 0x79, 0x0f, 0x5f, 0xbf, 0x87, 0xbe, 0xef, 0x5f, 0x12, 0xf9, 0x80, 0x39, 0xc7, 0xc8, 0xd3, 0x63, 0xd8, 0xf2, 0x21, 0x7c, 0x3f, 0x84, 0xee, 0x23, 0x68, 0x8e, 0x23, 0xeb, 0x38, 0xf9, 0x70, 0x9c, 0x78, 0x9c, 0xe0, 0xd9, 0x39, 0x41, 0xdf, 0xc7, 0xe4, 0xf6, 0xc7, 0xc8, 0xfa, 0x18, 0xdb, 0x3e, 0x81, 0xfe, 0x53, 0x62, 0xf4, 0x59, 0x3a, 0x00, 0x8f, 0xcf, 0xb1, 0xe3, 0x73, 0x64, 0x7f, 0x81, 0x0f, 0x4e, 0x61, 0xdb, 0x97, 0x37, 0x44, 0xbe, 0x22, 0x1e, 0x5f, 0x41, 0x7b, 0x1a, 0x3b, 0xcf, 0xa0, 0xcb, 0x19, 0xea, 0x67, 0xf1, 0xc9, 0x59, 0x6c, 0x38, 0x87, 0x8e, 0xe7, 0xe0, 0x71, 0x1e, 0xbd, 0xbf, 0xc1, 0x86, 0x6f, 0xa8, 0x7f, 0x8b, 0xcd, 0xdf, 0x21, 0xf3, 0x3b, 0xe6, 0xc1, 0x4a, 0x2e, 0xd0, 0xfe, 0x01, 0x9f, 0xfd, 0x40, 0xec, 0x7e, 0x60, 0xee, 0x45, 0xf8, 0x5c, 0x24, 0x3e, 0x97, 0xb0, 0xe3, 0x12, 0x32, 0x2f, 0x23, 0xf3, 0x32, 0xcf, 0xce, 0x8f, 0xe0, 0x27, 0xf4, 0xbc, 0x4a, 0xff, 0x55, 0x6c, 0xfa, 0x19, 0x5d, 0x7f, 0x81, 0xf6, 0x17, 0x72, 0xed, 0x1a, 0xf6, 0x5f, 0xa3, 0xfc, 0x15, 0xbd, 0x7f, 0x23, 0x2f, 0xaf, 0x93, 0x03, 0xd7, 0xe1, 0x7f, 0x1d, 0xfb, 0x6f, 0x90, 0x83, 0x37, 0x88, 0xef, 0x0d, 0x9e, 0xc9, 0x1b, 0xf0, 0xbb, 0x81, 0xd0, 0x1b, 0xd8, 0x7f, 0x93, 0x5c, 0xb8, 0x49, 0xdf, 0x4d, 0x9e, 0x83, 0x9b, 0xcc, 0xbd, 0x85, 0xce, 0xb7, 0x98, 0x77, 0x0b, 0xda, 0x5b, 0xd8, 0x77, 0x0b, 0xda, 0xdb, 0xe4, 0xca, 0x6d, 0xfc, 0x72, 0x1b, 0x7d, 0x6f, 0x63, 0xef, 0x6d, 0xe6, 0xde, 0x21, 0xcf, 0xef, 0xe0, 0xcb, 0x3b, 0xd4, 0x7f, 0x87, 0xfe, 0x77, 0xd6, 0x8c, 0x3f, 0x78, 0x9e, 0xfe, 0xc0, 0xb6, 0xbb, 0xee, 0xff, 0x87, 0xb5, 0x60, 0xe8, 0xff, 0x6b, 0x8a, 0xdf, 0x02, 0x2b, 0x45, 0x91, 0x93, 0x6c, 0x7c, 0xaa, 0x01, 0xea, 0x6a, 0x69, 0xc0, 0x4b, 0x49, 0x5d, 0x28, 0x8a, 0x76, 0x4a, 0x14, 0xa3, 0x88, 0x28, 0x9e, 0xed, 0xa2, 0x58, 0x94, 0xf6, 0x41, 0x51, 0x9c, 0x49, 0xa2, 0xa4, 0xeb, 0x01, 0xee, 0x88, 0x92, 0xfe, 0xbc, 0x28, 0x19, 0x59, 0x96, 0x33, 0xe5, 0x15, 0x25, 0xf3, 0x26, 0x51, 0xb2, 0xbc, 0x28, 0x4a, 0xd6, 0x3e, 0xec, 0x9a, 0xb2, 0x81, 0x23, 0xa2, 0x64, 0x1f, 0x20, 0x4a, 0xce, 0x64, 0x51, 0x72, 0xcd, 0x10, 0x25, 0x37, 0xc8, 0x13, 0x0f, 0xfc, 0x00, 0x3e, 0x79, 0x4e, 0x00, 0xe6, 0xe7, 0xb9, 0x26, 0x4a, 0x18, 0x3c, 0xc2, 0x32, 0x01, 0x1f, 0x40, 0x7e, 0xd8, 0x3d, 0x51, 0xf2, 0x32, 0x8f, 0xb5, 0x54, 0xe1, 0xcc, 0xac, 0xe4, 0x87, 0x57, 0xf8, 0x54, 0x51, 0x7c, 0x71, 0x60, 0xa7, 0x28, 0x11, 0x7b, 0x45, 0x29, 0x80, 0x8e, 0x91, 0xc8, 0x8d, 0x44, 0x9f, 0xc8, 0x79, 0x60, 0x0d, 0x40, 0xcf, 0x48, 0x68, 0x23, 0xb1, 0x27, 0xf2, 0x82, 0x28, 0x05, 0xa1, 0x29, 0x78, 0x45, 0x94, 0x42, 0xc8, 0x2c, 0x8c, 0x6d, 0x4f, 0xc1, 0xef, 0xa9, 0x1b, 0xa2, 0x14, 0xe9, 0x20, 0x4a, 0xd1, 0x5c, 0x20, 0x1a, 0xa0, 0x53, 0xd1, 0x11, 0x00, 0x7b, 0x8b, 0xc2, 0xbb, 0x28, 0x36, 0x17, 0xc5, 0xb6, 0x62, 0x8c, 0x17, 0x63, 0xbc, 0x18, 0xe3, 0xc5, 0x18, 0x2f, 0xc6, 0x78, 0x31, 0xc6, 0x8b, 0x31, 0x5e, 0x8c, 0xf1, 0xe2, 0xe8, 0x18, 0x95, 0x28, 0x4a, 0x09, 0xf4, 0x2e, 0x39, 0x04, 0xd0, 0x2e, 0x8d, 0xed, 0x65, 0x90, 0x55, 0x16, 0xbd, 0xca, 0xad, 0x17, 0x85, 0x75, 0x5c, 0x29, 0x8f, 0x4f, 0xca, 0x63, 0x6f, 0x79, 0xec, 0xac, 0xd0, 0x00, 0x8c, 0x06, 0x47, 0x45, 0xa9, 0x08, 0xcf, 0x8a, 0xe8, 0x5a, 0x89, 0x76, 0x34, 0x73, 0xa2, 0x99, 0x5f, 0xb9, 0x0e, 0x40, 0x46, 0x15, 0x6c, 0xae, 0x82, 0xbe, 0x55, 0xf1, 0x4b, 0x55, 0x6c, 0xad, 0x46, 0xbd, 0xfa, 0x31, 0x51, 0x6a, 0xa0, 0x7b, 0x4d, 0x6c, 0xad, 0x85, 0x7f, 0x6b, 0xe1, 0x9f, 0x5a, 0x67, 0xd8, 0x1c, 0xb6, 0x06, 0x94, 0x31, 0xb4, 0x63, 0xd0, 0x2d, 0x16, 0x99, 0xb1, 0xd8, 0x5f, 0xa7, 0x09, 0x60, 0x4e, 0x5d, 0xda, 0xf5, 0xa0, 0xaf, 0x07, 0xdf, 0xfa, 0xc8, 0xaf, 0x8f, 0xcf, 0x1b, 0x40, 0xdb, 0x30, 0x1d, 0x40, 0xb7, 0x46, 0x94, 0x8d, 0x2e, 0x89, 0xd2, 0x98, 0x39, 0x4d, 0xb0, 0xb1, 0x29, 0x7d, 0xcd, 0x90, 0xdf, 0x0c, 0x1d, 0x9b, 0x43, 0xd7, 0x1c, 0xbd, 0xe2, 0xd0, 0xbd, 0x05, 0x68, 0x49, 0xcc, 0x5a, 0x31, 0xbf, 0x75, 0x79, 0x51, 0xda, 0x60, 0x63, 0x1b, 0xda, 0x6d, 0xa1, 0x6b, 0x87, 0x7d, 0xf1, 0xe8, 0x90, 0x80, 0x1f, 0x3a, 0x60, 0x57, 0x47, 0x62, 0xf2, 0x0c, 0xed, 0xc4, 0x82, 0xa2, 0x24, 0xd1, 0xd7, 0x19, 0x5d, 0xba, 0xc0, 0x3b, 0xb9, 0x96, 0x28, 0x5d, 0x91, 0xc7, 0x7b, 0x41, 0xe9, 0x86, 0x8c, 0x6e, 0xf0, 0xee, 0x4e, 0x2e, 0x75, 0x27, 0x46, 0x3d, 0xf0, 0x73, 0x0f, 0xfc, 0xd0, 0x83, 0xb8, 0xf5, 0xc4, 0xf7, 0x3d, 0x89, 0x51, 0x4f, 0xf8, 0xf7, 0xc2, 0x27, 0xbd, 0x18, 0xef, 0x8d, 0xdd, 0x3c, 0x3a, 0x4a, 0x1f, 0x72, 0xaa, 0x0f, 0x34, 0x7d, 0x99, 0xdf, 0x17, 0x7f, 0xf7, 0x25, 0xef, 0xfa, 0x12, 0x8f, 0x7e, 0xe4, 0x6f, 0x3f, 0x72, 0xa4, 0x1f, 0x39, 0xd0, 0x1f, 0xdd, 0xfa, 0x93, 0x6f, 0x03, 0xa0, 0x19, 0x80, 0xfc, 0x01, 0xf4, 0x0d, 0x64, 0x6c, 0x20, 0x7c, 0x06, 0xa1, 0xfb, 0x20, 0x6c, 0x18, 0x0c, 0xcd, 0x60, 0x78, 0x0e, 0x41, 0x9f, 0x21, 0x8c, 0x0f, 0x85, 0x76, 0xe8, 0xd2, 0x10, 0x88, 0xcb, 0x30, 0x74, 0x1f, 0x86, 0xfc, 0x61, 0xf0, 0x1e, 0x4e, 0x5e, 0x0e, 0x47, 0xe7, 0xe1, 0xd8, 0x39, 0x02, 0x3b, 0x46, 0x92, 0x57, 0xa3, 0xd0, 0x65, 0x14, 0x76, 0x8e, 0x22, 0xef, 0x46, 0x41, 0xff, 0x2c, 0x7c, 0x9e, 0xc5, 0x87, 0xa3, 0xb1, 0x63, 0x34, 0x31, 0x1b, 0x83, 0xbc, 0xb1, 0xd0, 0x8d, 0xc5, 0x6f, 0xe3, 0x88, 0xf1, 0x78, 0xf7, 0x5a, 0x34, 0xf6, 0x4d, 0x80, 0xcf, 0x04, 0xe2, 0x34, 0x11, 0x3f, 0x4d, 0x44, 0x9f, 0xe7, 0x28, 0x9f, 0xc3, 0x57, 0x93, 0xe8, 0x9f, 0x4c, 0x9c, 0x26, 0xe3, 0xcf, 0x29, 0xe8, 0xf5, 0x3c, 0x3e, 0x78, 0x1e, 0x7f, 0x3d, 0x0f, 0x8f, 0xa9, 0xe4, 0xd2, 0x34, 0xfc, 0x30, 0x9d, 0xf1, 0x17, 0xe8, 0x9f, 0xa1, 0x03, 0xf4, 0x9c, 0x41, 0x3e, 0xcc, 0x24, 0xc6, 0xb3, 0xe0, 0x37, 0x1b, 0x5f, 0xbc, 0x88, 0x9c, 0x39, 0xe8, 0x31, 0x87, 0xf9, 0x2f, 0x81, 0xb9, 0xb4, 0xe7, 0xe1, 0xdf, 0x97, 0xc9, 0xfb, 0xf9, 0xc4, 0x9e, 0x77, 0x95, 0xb2, 0x80, 0x58, 0x2f, 0x44, 0xdf, 0x45, 0xf0, 0x5e, 0x8c, 0x0f, 0x16, 0x13, 0x83, 0x25, 0xc8, 0x5f, 0x8a, 0x8c, 0x65, 0x8c, 0x2f, 0x87, 0xf7, 0x72, 0x74, 0x58, 0x41, 0xdf, 0x4a, 0xec, 0xe1, 0xdd, 0xa3, 0xac, 0x22, 0xff, 0x56, 0xe3, 0x1f, 0xde, 0x3b, 0xca, 0xab, 0xf0, 0x7a, 0x15, 0xbf, 0xac, 0x83, 0xff, 0x7a, 0xe2, 0xba, 0x1e, 0x1d, 0x5e, 0x43, 0xa7, 0x0d, 0x3c, 0x2b, 0x1b, 0xf0, 0xd5, 0x46, 0xe2, 0xb3, 0x09, 0xde, 0x9b, 0xf0, 0xd3, 0x26, 0x78, 0x6c, 0x46, 0xc6, 0x16, 0xe6, 0xf9, 0x89, 0xe7, 0x56, 0xfc, 0xb8, 0x0d, 0xfe, 0xdb, 0x29, 0x77, 0xe0, 0xbb, 0x37, 0x88, 0xcd, 0x9b, 0xd4, 0x77, 0xc2, 0x73, 0x17, 0x79, 0xbc, 0x1b, 0x1d, 0xf7, 0xe0, 0xcf, 0xbd, 0xf8, 0x6e, 0x1f, 0x3a, 0xee, 0x27, 0xce, 0x07, 0xe0, 0x79, 0x10, 0x1f, 0x1c, 0x82, 0xe7, 0x5b, 0x96, 0x28, 0x87, 0x91, 0x75, 0x04, 0x1b, 0xdf, 0x41, 0x9f, 0x77, 0xf0, 0xdf, 0x51, 0xf4, 0x7e, 0x8f, 0xdc, 0x7e, 0x9f, 0x79, 0x1f, 0x60, 0xeb, 0x31, 0x7c, 0xfc, 0x21, 0x38, 0xce, 0x73, 0x79, 0x02, 0x9f, 0xf0, 0x8e, 0x50, 0x3e, 0xc1, 0x3f, 0x9f, 0x32, 0x76, 0x12, 0xfa, 0xcf, 0xd0, 0xe9, 0x0b, 0x62, 0xf1, 0x05, 0x39, 0x70, 0x8a, 0x79, 0x5f, 0xa2, 0x17, 0xef, 0x08, 0xe5, 0x2b, 0xf2, 0xe8, 0x34, 0x31, 0x3f, 0xe3, 0x02, 0xbf, 0x9d, 0x21, 0xd6, 0x5f, 0xe3, 0xe3, 0xb3, 0xc8, 0x3e, 0x47, 0x6c, 0xce, 0x13, 0xa7, 0x6f, 0xe0, 0xfb, 0x1d, 0x7c, 0xbe, 0xe7, 0x99, 0xfa, 0x01, 0x3e, 0x97, 0xe1, 0xf3, 0x13, 0x7a, 0x5c, 0xc5, 0xe6, 0x9f, 0xe1, 0x77, 0x0d, 0x7d, 0x7f, 0xc5, 0x5f, 0xd7, 0x89, 0xd3, 0x0d, 0xd6, 0xcb, 0x9b, 0xcc, 0xbd, 0x05, 0xfd, 0x6d, 0xe4, 0xdc, 0x41, 0x8f, 0x3f, 0xe8, 0xbb, 0x0b, 0xee, 0xe1, 0x93, 0xfb, 0xf0, 0x67, 0xfd, 0x55, 0x59, 0x7b, 0x55, 0xe5, 0x84, 0xa8, 0x5a, 0xbc, 0xa8, 0x7a, 0x13, 0x51, 0x8d, 0xbc, 0xa2, 0x7a, 0x7c, 0x60, 0xa9, 0xa8, 0xe6, 0x8b, 0xa2, 0xda, 0x45, 0xc0, 0x29, 0x51, 0x1d, 0xc6, 0xd9, 0xc7, 0xaa, 0xe9, 0x2c, 0xb0, 0x57, 0xd4, 0x0c, 0xb9, 0x44, 0xcd, 0x18, 0x2d, 0x6a, 0xa6, 0x5a, 0x60, 0x93, 0xa8, 0x99, 0x27, 0x89, 0x9a, 0x85, 0xfe, 0xac, 0x2b, 0x45, 0xcd, 0x36, 0x5e, 0xd4, 0xec, 0x89, 0xa2, 0xe6, 0xa0, 0xcc, 0xd9, 0x43, 0xd4, 0x5c, 0xf0, 0xcf, 0x9d, 0x0c, 0xae, 0x88, 0x9a, 0x07, 0x9e, 0x61, 0xf0, 0xc9, 0x0b, 0xff, 0x7c, 0xa3, 0x45, 0xcd, 0x5f, 0x47, 0xd4, 0x70, 0xfa, 0x7c, 0xc8, 0x8e, 0x80, 0x67, 0xc4, 0x35, 0x51, 0x0b, 0xdc, 0x10, 0xb5, 0x60, 0x41, 0x30, 0x55, 0xd4, 0x42, 0xc8, 0x28, 0xb4, 0x5e, 0xd4, 0xc2, 0x99, 0xc0, 0x05, 0x51, 0x9f, 0x3a, 0x26, 0x6a, 0x91, 0x19, 0xa2, 0x16, 0xa5, 0x2c, 0x86, 0x0c, 0xd6, 0x42, 0xb5, 0xb8, 0x5f, 0xd4, 0x28, 0xe4, 0x94, 0x80, 0x57, 0x49, 0xf8, 0x94, 0x82, 0x6f, 0x69, 0x68, 0xca, 0x44, 0x81, 0x3e, 0xa2, 0x96, 0x3d, 0x2a, 0x6a, 0xf9, 0x23, 0xa2, 0x56, 0xaa, 0x26, 0x6a, 0x95, 0xd2, 0xa2, 0x56, 0x45, 0x8f, 0x1a, 0xba, 0xa8, 0xb5, 0xe0, 0x1b, 0x33, 0x42, 0xd4, 0x58, 0x74, 0x8b, 0x3d, 0x2f, 0x2a, 0x6b, 0x96, 0x5a, 0x17, 0x79, 0xf5, 0xd0, 0xa7, 0x3e, 0xba, 0xd4, 0x67, 0x5e, 0x43, 0x6c, 0x68, 0x84, 0xed, 0x8d, 0xe3, 0x00, 0x3c, 0x9a, 0x20, 0xa7, 0x09, 0xfd, 0x4d, 0x0f, 0x8a, 0xda, 0x0c, 0x3b, 0x9a, 0x5d, 0x12, 0xb5, 0xf9, 0x00, 0x70, 0x46, 0xd4, 0x38, 0xe6, 0xb5, 0x40, 0xe7, 0x96, 0xc8, 0x68, 0x89, 0x7e, 0xad, 0xa0, 0x6f, 0x83, 0x2f, 0xdb, 0x32, 0xb7, 0x2d, 0x3e, 0x6a, 0x87, 0x2f, 0xd9, 0xdf, 0xaa, 0xf1, 0xb4, 0xe3, 0xd1, 0x3b, 0x01, 0x7d, 0x12, 0x98, 0xdf, 0x1e, 0xf9, 0x4f, 0xc3, 0xb7, 0x03, 0x32, 0x3b, 0xe0, 0xc7, 0x8e, 0xe8, 0xd0, 0x11, 0x5e, 0xcf, 0x50, 0xef, 0x34, 0x44, 0xd4, 0x44, 0xec, 0x48, 0x64, 0x3c, 0x11, 0x9e, 0x49, 0xe9, 0x00, 0x3c, 0x3a, 0x33, 0xb7, 0x33, 0xed, 0x2e, 0xf0, 0x4d, 0xc6, 0x0f, 0x5d, 0x69, 0x77, 0xc5, 0x0f, 0xdd, 0x98, 0xdb, 0x6d, 0xa7, 0xa8, 0xdd, 0xd1, 0xa9, 0x3b, 0x7e, 0xef, 0x81, 0x9d, 0x3d, 0x98, 0xdb, 0x03, 0x9f, 0xf6, 0xc4, 0x8e, 0x5e, 0x1c, 0x61, 0x7a, 0x61, 0x73, 0x2f, 0xfc, 0xdc, 0x9b, 0xb2, 0x0f, 0x32, 0xfa, 0x12, 0xd3, 0xbe, 0xf0, 0xec, 0x8b, 0x5f, 0xfa, 0x41, 0xd3, 0x9f, 0x39, 0x03, 0xe8, 0x1b, 0x70, 0x4f, 0xd4, 0x41, 0xe8, 0x37, 0x78, 0x8d, 0xa8, 0x43, 0x90, 0xc3, 0x9a, 0xa4, 0x0e, 0x5b, 0x28, 0xea, 0x70, 0xe2, 0x3c, 0x02, 0x9e, 0x23, 0xb3, 0x01, 0xe4, 0x8c, 0xc2, 0x0f, 0xa3, 0xb0, 0x9f, 0xf5, 0x47, 0x1d, 0x0d, 0xed, 0x18, 0xf4, 0x1d, 0x0b, 0xdf, 0x71, 0x0d, 0x44, 0x1d, 0xcf, 0xd8, 0x78, 0xf8, 0x4e, 0x40, 0xef, 0x89, 0xf8, 0xe6, 0x39, 0xc6, 0x27, 0xc1, 0x77, 0x0a, 0xf1, 0x99, 0x82, 0xcf, 0x9f, 0x67, 0xce, 0x54, 0x7c, 0x39, 0x0d, 0x5d, 0xa6, 0x63, 0xcf, 0x0c, 0x62, 0x37, 0x13, 0xde, 0xb3, 0xb0, 0x7b, 0x36, 0xba, 0xcc, 0x41, 0x8f, 0x39, 0xc4, 0x60, 0x0e, 0x73, 0x58, 0x53, 0xd4, 0x97, 0xd0, 0x7b, 0x2e, 0x39, 0x3a, 0x17, 0x9a, 0x79, 0x1d, 0x00, 0x39, 0xcc, 0xfa, 0xa2, 0xbe, 0x8c, 0x1e, 0xf3, 0x89, 0xcb, 0x02, 0xfa, 0x17, 0x42, 0xbf, 0x08, 0x1d, 0x16, 0x91, 0x63, 0x8b, 0xc9, 0xcf, 0xc5, 0xc8, 0x5f, 0x82, 0xfd, 0x4b, 0xf1, 0xe9, 0x32, 0xf4, 0x5f, 0x4e, 0xdf, 0x72, 0xf8, 0xad, 0x80, 0x76, 0x25, 0x72, 0x56, 0xa2, 0xfb, 0x2b, 0xcc, 0x5f, 0xd5, 0x5a, 0xd4, 0xd5, 0xc4, 0x68, 0x35, 0xf9, 0xbe, 0x86, 0x5c, 0x7b, 0x15, 0x7d, 0xd7, 0x62, 0xf3, 0x5a, 0xf4, 0x5b, 0x87, 0x1e, 0xeb, 0xd0, 0x71, 0x1d, 0x31, 0x5d, 0xcf, 0xfc, 0xf5, 0xf8, 0xf8, 0x35, 0xf2, 0xed, 0x35, 0x68, 0x37, 0xe0, 0xfb, 0x0d, 0xf8, 0x6f, 0x03, 0x7c, 0x36, 0x32, 0xb6, 0x11, 0xf9, 0x1b, 0x91, 0xb7, 0x09, 0xfd, 0x36, 0xe1, 0xb7, 0x4d, 0xd8, 0xf9, 0x3a, 0xba, 0x6f, 0xc6, 0x9e, 0xcd, 0xe8, 0xb4, 0x19, 0x9f, 0xb2, 0x07, 0x56, 0xb7, 0x40, 0xb7, 0x05, 0xde, 0x7e, 0x78, 0xfb, 0xc9, 0x09, 0x3f, 0x3a, 0xf8, 0xa1, 0x4f, 0xa1, 0x6f, 0x2b, 0xbe, 0xdb, 0xc6, 0x9c, 0x6d, 0xe8, 0xbe, 0x9d, 0x39, 0x3b, 0xd0, 0xfb, 0x0d, 0xf8, 0xbf, 0x41, 0x7b, 0x27, 0x39, 0xc5, 0x7a, 0xa5, 0xee, 0x22, 0x6f, 0x76, 0x13, 0xef, 0x3d, 0xc8, 0xdf, 0x4b, 0xac, 0xf7, 0xe1, 0xd3, 0x7d, 0xd0, 0xee, 0x67, 0xde, 0x01, 0xec, 0x3f, 0x80, 0x6e, 0x07, 0xe9, 0x3b, 0x84, 0x1d, 0x6f, 0x21, 0xef, 0x6d, 0xe6, 0xbd, 0x0b, 0xdd, 0x51, 0x6c, 0x7c, 0x8f, 0xf1, 0xf7, 0xe0, 0xf7, 0x3e, 0xfa, 0x7e, 0x80, 0xcf, 0x8f, 0xe1, 0x9b, 0x0f, 0xc9, 0xff, 0x0f, 0xc9, 0xcb, 0x8f, 0xd0, 0xe1, 0x38, 0xf5, 0xe3, 0xe8, 0x71, 0x02, 0xfb, 0x3f, 0xc6, 0x47, 0x9f, 0xa2, 0xeb, 0xa7, 0xf8, 0xe4, 0x24, 0x3e, 0xf8, 0x8c, 0xf9, 0x9f, 0x21, 0xe7, 0x73, 0x78, 0x7c, 0x0e, 0xcd, 0x17, 0x94, 0xa7, 0xd0, 0xe3, 0x4b, 0xe6, 0x7c, 0x85, 0x8d, 0xa7, 0x91, 0x73, 0x06, 0x9a, 0x33, 0xe8, 0xfc, 0x35, 0xe5, 0xd7, 0xd8, 0x7f, 0x16, 0x7f, 0x9d, 0x43, 0x8f, 0xf3, 0xd8, 0x79, 0x1e, 0xfe, 0xdf, 0xd0, 0xf7, 0x1d, 0x71, 0xfb, 0x8e, 0xf8, 0x5f, 0x20, 0x7f, 0x7f, 0x40, 0xc6, 0x45, 0x72, 0xe6, 0x12, 0x3e, 0xbb, 0x4c, 0xfb, 0x47, 0x7c, 0xfb, 0x13, 0xcf, 0xfc, 0x15, 0xfa, 0xaf, 0x22, 0xfb, 0x17, 0xf2, 0xf6, 0x1a, 0x79, 0x79, 0x8d, 0xb9, 0xbf, 0x92, 0x4b, 0xbf, 0x21, 0xeb, 0x3a, 0xb2, 0xd8, 0xbb, 0xaa, 0x37, 0xc8, 0xfb, 0x9b, 0xcc, 0xbb, 0x89, 0xcf, 0x59, 0xef, 0xd4, 0x5b, 0xe4, 0xc1, 0x1d, 0xe6, 0xfe, 0x0e, 0xff, 0xdf, 0xf1, 0xc5, 0xef, 0xc4, 0xfa, 0x0f, 0x78, 0xdf, 0xc5, 0x07, 0x77, 0x89, 0xd3, 0x3d, 0xca, 0x7b, 0xd0, 0xdc, 0x67, 0xfc, 0x01, 0xf4, 0x81, 0xf1, 0xa2, 0xc9, 0x11, 0xd1, 0x94, 0xa9, 0xa2, 0xa9, 0xf1, 0xe0, 0xa8, 0x68, 0x5a, 0x6b, 0xd1, 0xf4, 0x5c, 0xa2, 0x19, 0xe9, 0x40, 0x5e, 0x70, 0x43, 0x34, 0x4f, 0x13, 0xd1, 0x4c, 0x68, 0xad, 0x13, 0xa2, 0xd9, 0x03, 0x44, 0xf3, 0x96, 0x16, 0x8d, 0xbd, 0xa8, 0x96, 0x3e, 0x5a, 0xb4, 0x0c, 0x9b, 0x44, 0x63, 0x1f, 0xaa, 0x65, 0x5e, 0x2a, 0x5a, 0x96, 0xed, 0x00, 0x7e, 0xd9, 0xe8, 0xcf, 0x3e, 0x5a, 0xb4, 0x9c, 0x1d, 0x44, 0xcb, 0x6d, 0x01, 0xe6, 0xe6, 0x01, 0x61, 0xf3, 0x44, 0xcb, 0x0b, 0xaf, 0xfc, 0xf4, 0x85, 0x9f, 0x11, 0xcd, 0x37, 0x44, 0x34, 0xd6, 0x3e, 0x2d, 0x82, 0x79, 0x91, 0x7e, 0xd1, 0x0a, 0xc2, 0xbf, 0xd0, 0x49, 0xd1, 0x0a, 0x5f, 0x12, 0xed, 0x29, 0xf8, 0x14, 0xb9, 0x23, 0x5a, 0x31, 0x5d, 0x34, 0xf6, 0x7c, 0x5a, 0x14, 0xfa, 0x45, 0x1d, 0x14, 0xad, 0x84, 0x0f, 0x8c, 0x00, 0xa7, 0x44, 0x2b, 0x09, 0x7f, 0xce, 0xf1, 0x5a, 0xa9, 0x6c, 0xa0, 0x01, 0xc0, 0x8e, 0x52, 0xd8, 0x50, 0x3a, 0x13, 0x80, 0xbe, 0x0c, 0xbc, 0xca, 0xc5, 0x89, 0x56, 0x1e, 0x39, 0x15, 0xa0, 0xad, 0x08, 0xaf, 0x4a, 0xc8, 0xaf, 0x74, 0x4c, 0xb4, 0xca, 0xf4, 0x57, 0xa9, 0x25, 0x1a, 0xc7, 0x10, 0xad, 0x1a, 0x3c, 0xaa, 0xa3, 0x5b, 0x0d, 0xf8, 0xd7, 0xa4, 0xaf, 0x26, 0x72, 0x6b, 0x63, 0x53, 0x0c, 0x34, 0x31, 0xe8, 0x12, 0xbb, 0x46, 0xb4, 0x3a, 0x2f, 0x8a, 0x56, 0x77, 0x86, 0x68, 0x0d, 0x56, 0x82, 0x9d, 0xa2, 0x35, 0xa4, 0x6c, 0x4c, 0xd9, 0x74, 0xbd, 0x68, 0xcd, 0x90, 0xdb, 0x02, 0xde, 0x2d, 0xf0, 0x41, 0x4b, 0xfc, 0xd7, 0xaa, 0xbc, 0x68, 0xad, 0xa3, 0x44, 0x6b, 0x87, 0xde, 0xf1, 0xe8, 0x16, 0x0f, 0x0f, 0xd6, 0x37, 0xad, 0x23, 0x36, 0x3f, 0x83, 0xbd, 0xcf, 0x5c, 0x10, 0xad, 0x13, 0x7e, 0x4d, 0xa4, 0x9d, 0x84, 0x5f, 0x3a, 0x17, 0x04, 0xf0, 0xe9, 0xc2, 0xbc, 0x64, 0xf8, 0x26, 0x63, 0x7b, 0x32, 0x73, 0xd8, 0xb3, 0x69, 0x5d, 0xf1, 0x75, 0x57, 0x78, 0x76, 0xc5, 0xe6, 0xae, 0xf0, 0xef, 0x8a, 0x8e, 0x5d, 0x99, 0xdf, 0xbd, 0x1a, 0x20, 0x16, 0xdd, 0xd1, 0xad, 0x3b, 0xbe, 0xe8, 0x01, 0x6d, 0x0f, 0x74, 0xef, 0x81, 0xad, 0x3d, 0xe0, 0xd5, 0x03, 0xde, 0x9c, 0xfd, 0x35, 0xd6, 0x3b, 0xad, 0x37, 0xfc, 0x7b, 0xe3, 0x8f, 0x3e, 0xf8, 0xae, 0x6f, 0x11, 0xd1, 0xfa, 0x61, 0x7f, 0x7f, 0xfc, 0xd1, 0x9f, 0x18, 0xf5, 0x87, 0x67, 0xff, 0xbd, 0xe0, 0x8a, 0x68, 0x03, 0xb0, 0x79, 0x00, 0xfe, 0x19, 0x08, 0xfd, 0xe0, 0x3a, 0x20, 0x59, 0xb4, 0x21, 0xd8, 0xcd, 0x1a, 0xa8, 0x0d, 0xc1, 0xaf, 0x43, 0x90, 0x3b, 0x14, 0x7f, 0x0d, 0x85, 0x6e, 0x28, 0xfe, 0x1e, 0x8a, 0xac, 0xa1, 0x8c, 0x0f, 0x65, 0x7c, 0x28, 0xe3, 0x43, 0x19, 0x1f, 0x46, 0x5e, 0x0c, 0x47, 0xb7, 0xe1, 0xc8, 0x1b, 0xce, 0xf8, 0x70, 0xc6, 0x87, 0x33, 0x3e, 0x9c, 0xf1, 0xe1, 0x8c, 0x0f, 0xc7, 0xb7, 0x23, 0xf0, 0xc9, 0x08, 0x7c, 0x3e, 0x02, 0xdd, 0x46, 0xc2, 0x6f, 0x14, 0xbe, 0x1b, 0x45, 0xdf, 0x28, 0xe4, 0xb2, 0xa6, 0x6a, 0xa3, 0xa0, 0x7b, 0x16, 0xfd, 0x9e, 0xed, 0x23, 0xda, 0x68, 0x64, 0x8d, 0xc1, 0xae, 0xb1, 0xf0, 0x65, 0x3f, 0xa7, 0x8d, 0xc7, 0x8f, 0x13, 0xd0, 0x71, 0x02, 0x7e, 0x98, 0x48, 0xce, 0x3c, 0x97, 0x28, 0xda, 0x24, 0xfc, 0x3f, 0x69, 0x21, 0xc0, 0xee, 0x49, 0xc4, 0x64, 0x12, 0xb2, 0x26, 0xe1, 0x93, 0x49, 0xf8, 0x70, 0x12, 0xf2, 0x26, 0x93, 0x6f, 0x93, 0xf1, 0xf3, 0x64, 0xec, 0x9e, 0x02, 0x9e, 0x47, 0xaf, 0xe7, 0x99, 0x3b, 0x15, 0x99, 0x53, 0xf1, 0xeb, 0x54, 0xf2, 0x6f, 0x1a, 0xb2, 0xa7, 0xa1, 0xf3, 0x34, 0xe6, 0x4f, 0x47, 0x9f, 0xe9, 0xe8, 0xfc, 0x02, 0x7c, 0x66, 0x10, 0xaf, 0x19, 0xc8, 0x9b, 0x01, 0x7f, 0xf6, 0x7d, 0xda, 0x4c, 0xf4, 0x9b, 0x89, 0xcf, 0x67, 0x31, 0x6f, 0x36, 0xf1, 0x7d, 0x91, 0x1c, 0x63, 0xdf, 0xa7, 0xbd, 0x44, 0x9c, 0x5e, 0xc2, 0x97, 0x73, 0x91, 0x33, 0x0f, 0x3f, 0xb3, 0x36, 0x6b, 0x2f, 0x53, 0x7f, 0x99, 0x9c, 0x7e, 0x19, 0x3b, 0xe7, 0x13, 0x8b, 0xf9, 0xf4, 0xcd, 0x27, 0xa7, 0xe6, 0x13, 0xb7, 0xf9, 0xf8, 0x65, 0x3e, 0x3a, 0xcc, 0x67, 0x7c, 0x3e, 0xe3, 0x0b, 0x18, 0x5f, 0xc0, 0xf8, 0x02, 0xc6, 0x17, 0x30, 0xbe, 0x80, 0xf1, 0x05, 0x8c, 0x2f, 0x60, 0x7c, 0x01, 0xe3, 0x0b, 0x19, 0x5f, 0xc8, 0xf8, 0x42, 0xc6, 0x17, 0x32, 0xbe, 0x90, 0xf1, 0x85, 0x8c, 0x2f, 0x64, 0x7c, 0x21, 0xe3, 0x8b, 0x18, 0x5f, 0xc4, 0xf8, 0x22, 0xc6, 0x17, 0x31, 0xbe, 0x88, 0xf1, 0x45, 0x8c, 0x2f, 0x62, 0x7c, 0x11, 0xe3, 0x8b, 0x19, 0x5f, 0xcc, 0xf8, 0x62, 0xc6, 0x17, 0x33, 0xbe, 0x98, 0xf1, 0xc5, 0x8c, 0x2f, 0x66, 0x7c, 0x31, 0xe3, 0x4b, 0x18, 0x5f, 0xc2, 0xf8, 0x12, 0xc6, 0x97, 0x30, 0xbe, 0x84, 0xf1, 0x25, 0x8c, 0x2f, 0x61, 0x7c, 0x09, 0xe3, 0x4b, 0x19, 0x5f, 0xca, 0xf8, 0x52, 0xc6, 0x97, 0x32, 0xce, 0x3b, 0x45, 0x5b, 0xca, 0xf8, 0x52, 0xc6, 0x97, 0x32, 0xbe, 0x8c, 0xf1, 0x65, 0x8c, 0x2f, 0x63, 0x7c, 0x19, 0xe3, 0xcb, 0x18, 0x5f, 0xc6, 0xf8, 0x32, 0xc6, 0x97, 0x31, 0xbe, 0x3c, 0x5b, 0x2a, 0x56, 0xe0, 0xbf, 0x57, 0xa0, 0xe1, 0x7d, 0xa3, 0xad, 0xc6, 0xef, 0xab, 0xf1, 0xf1, 0x6a, 0xe2, 0xb4, 0x86, 0xf8, 0xbe, 0x8a, 0xdf, 0x5f, 0x25, 0xd7, 0x5f, 0xc5, 0xbf, 0x6b, 0x89, 0xe5, 0x7a, 0x72, 0xe0, 0x35, 0xc6, 0x36, 0x12, 0xaf, 0x4d, 0xf8, 0xfb, 0x75, 0x72, 0xe1, 0x75, 0xf2, 0x74, 0x33, 0xfc, 0xb7, 0x90, 0x1b, 0x7e, 0xe6, 0xa7, 0x20, 0x67, 0x2b, 0xf9, 0xbb, 0x8d, 0x9c, 0xd9, 0x4e, 0xcc, 0x76, 0x30, 0x67, 0x07, 0xb9, 0xff, 0x06, 0xb4, 0x6f, 0x4c, 0x0a, 0x81, 0x98, 0xbe, 0x09, 0xef, 0x37, 0xe9, 0x7b, 0x13, 0xd9, 0x6f, 0x92, 0x33, 0x6f, 0xc2, 0x77, 0x27, 0x63, 0x3b, 0x79, 0xd6, 0x76, 0x31, 0xb6, 0x8b, 0xfa, 0x6e, 0x62, 0xbc, 0x87, 0xe7, 0x6f, 0x2f, 0xd8, 0x87, 0x8c, 0xfd, 0xc4, 0xf3, 0x00, 0xfc, 0x0e, 0xb2, 0x9e, 0x1d, 0x42, 0xf7, 0xb7, 0xc8, 0xc3, 0xb7, 0xd0, 0xf7, 0x2d, 0xe2, 0x7e, 0x98, 0x67, 0xe3, 0xf0, 0x99, 0xff, 0x0c, 0x47, 0xd0, 0xf5, 0x6d, 0xf2, 0xfd, 0x6d, 0xf2, 0xe2, 0x1d, 0x72, 0xf2, 0x1d, 0x64, 0x1c, 0x45, 0xfe, 0x87, 0xf0, 0xfc, 0x08, 0x99, 0xc7, 0x79, 0x86, 0x4f, 0x90, 0x3b, 0x27, 0xc8, 0xa7, 0x8f, 0xd3, 0xa5, 0xe2, 0x93, 0x5c, 0x21, 0x90, 0xcf, 0x9f, 0xe2, 0x9b, 0x93, 0xe4, 0xe2, 0x67, 0xe8, 0xf2, 0x39, 0x3a, 0x7d, 0xc1, 0xf3, 0x70, 0x0a, 0x3f, 0x7d, 0x09, 0xef, 0xaf, 0xd0, 0xe9, 0x34, 0xbe, 0xfe, 0x9a, 0x75, 0xe7, 0x2c, 0xcf, 0xee, 0x79, 0xe6, 0x7e, 0x43, 0x4c, 0xbe, 0xc5, 0x47, 0x17, 0xe8, 0xff, 0x81, 0xf6, 0x45, 0xf2, 0xf7, 0x32, 0x36, 0xfd, 0x48, 0xdf, 0x15, 0x62, 0xf2, 0x33, 0x72, 0xae, 0xe1, 0xdb, 0x6b, 0xf8, 0xef, 0x1a, 0xed, 0x5f, 0x79, 0x6e, 0x7f, 0xa5, 0xef, 0x37, 0x78, 0xfc, 0xc6, 0x5a, 0xf7, 0x1b, 0xbe, 0xbe, 0x8e, 0x8e, 0xd7, 0x4f, 0xa4, 0xe2, 0x06, 0x3e, 0xba, 0x89, 0xff, 0x6e, 0xe2, 0xcf, 0x5b, 0xe8, 0x79, 0x0b, 0x39, 0xb7, 0xe0, 0x75, 0x9b, 0x38, 0xdf, 0xc1, 0x4f, 0xbf, 0xf3, 0x0c, 0xfd, 0xce, 0x73, 0xfd, 0x07, 0x71, 0xb8, 0xcb, 0xfc, 0x7b, 0xc8, 0xbf, 0x87, 0x9e, 0xf7, 0xe0, 0x7f, 0x8f, 0xfe, 0xfb, 0xf8, 0xf9, 0x3e, 0xcf, 0xd8, 0x7d, 0xfc, 0x7c, 0x9f, 0xe7, 0xe6, 0x3e, 0xcf, 0xe6, 0x03, 0x62, 0xf2, 0x00, 0xbf, 0x3e, 0xc0, 0x9f, 0x0f, 0x78, 0x8e, 0x1f, 0x30, 0xf7, 0x01, 0xb2, 0x02, 0xf0, 0x0e, 0xac, 0x11, 0x5d, 0x6a, 0x81, 0x64, 0x70, 0x0c, 0xdc, 0x10, 0x5d, 0xc9, 0x0b, 0xea, 0x80, 0x3e, 0xe0, 0x9a, 0xe8, 0x6a, 0x69, 0x30, 0x00, 0xf8, 0x01, 0x6d, 0x6d, 0x08, 0xa0, 0xd4, 0x5b, 0x83, 0x2b, 0xa2, 0x1b, 0xcc, 0x35, 0xce, 0x8b, 0xee, 0x61, 0x9e, 0x87, 0xba, 0x67, 0x13, 0xb8, 0x24, 0xba, 0x59, 0x10, 0x24, 0x82, 0xa5, 0xe0, 0x94, 0xe8, 0x56, 0x36, 0x10, 0x07, 0xe8, 0x63, 0xef, 0xaf, 0x3b, 0xc8, 0xf6, 0xd2, 0xf6, 0xba, 0xe5, 0x5e, 0x70, 0x52, 0xf4, 0x74, 0xf0, 0x4d, 0x4f, 0x5f, 0xfa, 0x7b, 0xa2, 0x67, 0xd8, 0x2e, 0x7a, 0xc6, 0x78, 0x40, 0x5f, 0xa6, 0x85, 0xa2, 0x67, 0xae, 0x06, 0x90, 0x99, 0x19, 0xbd, 0x38, 0x13, 0xe8, 0xbc, 0x0f, 0xf5, 0xcc, 0xd0, 0x64, 0x3e, 0x23, 0x7a, 0x96, 0x74, 0x80, 0xf1, 0x2c, 0x8c, 0x65, 0xa1, 0x3f, 0x0b, 0xb6, 0x64, 0x81, 0x47, 0xd6, 0x28, 0xd0, 0x01, 0xcc, 0x00, 0xc8, 0xc8, 0x76, 0x47, 0xf4, 0xec, 0x99, 0xc0, 0x8b, 0xa2, 0xe7, 0xc0, 0x2e, 0xde, 0x9f, 0x7a, 0x4e, 0xea, 0x39, 0xd1, 0x2f, 0x17, 0xfa, 0xe7, 0x6a, 0x00, 0xe0, 0x91, 0x8b, 0xbe, 0x5c, 0xd8, 0x9b, 0x87, 0xf1, 0x30, 0x64, 0x84, 0x31, 0x1e, 0x86, 0x1e, 0xf9, 0xa6, 0x8a, 0x9e, 0xff, 0x04, 0xc0, 0xbe, 0xfc, 0xf0, 0x0a, 0xc7, 0xc6, 0x70, 0xe4, 0x86, 0xa3, 0x73, 0x78, 0x0f, 0xd1, 0x23, 0x44, 0x74, 0xce, 0x18, 0x7a, 0x64, 0x2e, 0x40, 0x7f, 0x24, 0xb6, 0x46, 0xa2, 0x6b, 0x24, 0x3c, 0x22, 0xb1, 0x2f, 0x92, 0xb1, 0x82, 0xf8, 0xa1, 0x10, 0x7c, 0x0a, 0xa3, 0x53, 0x11, 0x7c, 0x59, 0x8c, 0x76, 0x71, 0x74, 0xe6, 0xdd, 0xab, 0x97, 0x38, 0x22, 0x7a, 0x49, 0xe4, 0x96, 0x44, 0x46, 0x49, 0xe4, 0x95, 0x42, 0xd7, 0x52, 0xe8, 0x59, 0x0a, 0xfe, 0xa5, 0x88, 0x41, 0x29, 0xf4, 0x2a, 0x8d, 0xcf, 0xcb, 0x60, 0x2f, 0x67, 0x10, 0xbd, 0x0c, 0xb1, 0x2b, 0x83, 0x4f, 0xca, 0x30, 0xbf, 0x2c, 0xb2, 0xcb, 0xa2, 0x2f, 0x67, 0x12, 0xbd, 0x1c, 0x76, 0x94, 0x83, 0x57, 0x79, 0x74, 0x28, 0x8f, 0x7f, 0xcb, 0xd3, 0x57, 0x1e, 0x9d, 0x2b, 0xc0, 0xab, 0x02, 0xbc, 0x2a, 0xc0, 0xab, 0x02, 0xbc, 0x2a, 0x20, 0xab, 0x22, 0xba, 0x55, 0xc4, 0x5f, 0x15, 0x2f, 0x88, 0x5e, 0x09, 0x5d, 0x2b, 0xad, 0x07, 0xf8, 0xaa, 0x12, 0x3a, 0x54, 0x72, 0xfb, 0xb0, 0x33, 0x1a, 0x79, 0xd1, 0x3e, 0x50, 0x1e, 0xc0, 0x3b, 0x1a, 0x39, 0xd1, 0xf0, 0x88, 0x86, 0x3e, 0x9a, 0xd8, 0x44, 0x13, 0xf3, 0x68, 0xf4, 0x8f, 0xc6, 0xc6, 0x68, 0xe4, 0x44, 0xe3, 0xfb, 0xca, 0xe8, 0x5e, 0x19, 0xff, 0x54, 0x8e, 0x06, 0x4d, 0x00, 0xbe, 0xa8, 0x8c, 0xbd, 0x95, 0xb1, 0xbd, 0x32, 0xfa, 0x56, 0x46, 0x76, 0x65, 0x74, 0xac, 0x8c, 0x6f, 0x2b, 0x63, 0x53, 0x15, 0xf4, 0xaf, 0x82, 0x6e, 0x55, 0xc8, 0xa1, 0x2a, 0x23, 0x00, 0xfe, 0xa9, 0xb2, 0x12, 0xa0, 0x5f, 0x15, 0xf4, 0xaf, 0x42, 0x9c, 0xab, 0xe0, 0x93, 0xaa, 0x3a, 0xc0, 0xbf, 0x55, 0xb1, 0xbf, 0x2a, 0xf6, 0x57, 0xc5, 0xfe, 0xaa, 0xf8, 0xbe, 0xea, 0x68, 0x80, 0x4d, 0x55, 0xb1, 0xb7, 0xea, 0x4e, 0x80, 0x4d, 0x55, 0xc9, 0xcb, 0x6a, 0xf8, 0xa0, 0x1a, 0xf2, 0xaa, 0xa1, 0x53, 0x75, 0xf4, 0xae, 0x8e, 0x7d, 0x35, 0xf0, 0x79, 0x0d, 0x74, 0xa9, 0x81, 0x8d, 0x35, 0xe1, 0x53, 0x13, 0x59, 0x35, 0xd1, 0xa3, 0x16, 0xb6, 0xd5, 0x42, 0x5e, 0x6d, 0xf8, 0xd7, 0x66, 0xbc, 0x36, 0x7d, 0x31, 0xd8, 0x10, 0x33, 0x4f, 0xf4, 0x58, 0xe4, 0xc6, 0x62, 0x73, 0x2c, 0x36, 0xd6, 0xc1, 0x8f, 0x75, 0x90, 0x57, 0x07, 0xff, 0xd4, 0x45, 0xe7, 0xba, 0xf8, 0xab, 0x1e, 0xfe, 0xa9, 0x47, 0x5f, 0x3d, 0x74, 0xad, 0xcf, 0x9c, 0xfa, 0xf8, 0xa6, 0x3e, 0x76, 0x35, 0x40, 0x3f, 0xf6, 0x33, 0x7a, 0x43, 0xf2, 0xab, 0x21, 0xe3, 0x0d, 0xd1, 0xa9, 0x11, 0x7a, 0x37, 0x42, 0x66, 0x23, 0x7c, 0xdb, 0x18, 0xbf, 0x34, 0xc6, 0x77, 0x4d, 0x18, 0x6f, 0x82, 0xdd, 0x4d, 0xf0, 0x49, 0x53, 0xfc, 0xdc, 0x14, 0x3b, 0x9a, 0xe1, 0xc3, 0x66, 0xe8, 0xd1, 0x0c, 0x3d, 0x9b, 0x33, 0xde, 0x1c, 0x9e, 0xcd, 0xf1, 0x41, 0x1c, 0x76, 0xc4, 0x31, 0xa7, 0x05, 0x7d, 0x2d, 0xb0, 0xbd, 0x05, 0x32, 0x5b, 0x62, 0x47, 0x4b, 0x62, 0xd1, 0x0a, 0x3d, 0x5b, 0x91, 0xc3, 0xad, 0x90, 0xd9, 0x1a, 0x3b, 0x5b, 0x33, 0xbf, 0x35, 0xbe, 0x6b, 0x43, 0x0c, 0xda, 0xd0, 0xd7, 0x16, 0xdb, 0xda, 0x22, 0xa7, 0x2d, 0xb6, 0xb5, 0x23, 0x36, 0xed, 0xe0, 0xd9, 0x0e, 0x9e, 0xf1, 0xe8, 0x11, 0x0f, 0x9f, 0x04, 0x9e, 0xcd, 0x04, 0xf4, 0x4c, 0xa0, 0xde, 0x1e, 0x3b, 0xdb, 0xe3, 0xbb, 0xa7, 0x2d, 0x00, 0xcf, 0xa7, 0xe9, 0xeb, 0x00, 0xcf, 0x0e, 0xf0, 0xec, 0xc0, 0xfc, 0x8e, 0xac, 0x13, 0x1d, 0x89, 0x23, 0x7b, 0x2e, 0xfd, 0x19, 0xe2, 0xf6, 0x0c, 0x3a, 0x75, 0xc2, 0xf6, 0x4e, 0xc4, 0x21, 0x91, 0x78, 0x26, 0xe2, 0xaf, 0x44, 0x7c, 0x93, 0xc4, 0x78, 0x12, 0x32, 0x93, 0x98, 0xd3, 0x19, 0xdb, 0x3b, 0x93, 0x2f, 0x5d, 0xc8, 0xa9, 0x2e, 0x8c, 0x77, 0xc1, 0xb6, 0x64, 0xe4, 0x24, 0x23, 0x33, 0x19, 0xdf, 0x74, 0x25, 0x06, 0x9c, 0x37, 0xf5, 0x6e, 0xd8, 0xde, 0x0d, 0xdb, 0xba, 0x11, 0x8f, 0xee, 0xd4, 0xbb, 0x23, 0xb3, 0x3b, 0x76, 0xf4, 0x40, 0x66, 0x0f, 0xe6, 0xf7, 0xc0, 0x77, 0x3d, 0xf1, 0x6d, 0x4f, 0x6c, 0xea, 0x85, 0xcc, 0x5e, 0xc4, 0xa8, 0x37, 0xb6, 0xf7, 0x26, 0x1e, 0xbd, 0x99, 0xd3, 0x87, 0x39, 0x7d, 0x98, 0xd3, 0x07, 0x9e, 0x7d, 0xe9, 0xeb, 0x8b, 0x1d, 0x7d, 0xc9, 0x01, 0xce, 0xa1, 0x7a, 0x3f, 0x74, 0xea, 0x8f, 0xef, 0xfa, 0x93, 0x8b, 0xfd, 0xc9, 0xd7, 0x01, 0xc8, 0x1c, 0x40, 0x5e, 0x0c, 0x64, 0xfe, 0x40, 0xe2, 0x38, 0x10, 0x5f, 0x0c, 0x44, 0xb7, 0x81, 0xe3, 0x01, 0x7c, 0x07, 0xba, 0x63, 0xcc, 0x19, 0x88, 0xae, 0x03, 0xe1, 0x37, 0x08, 0xdb, 0x06, 0x41, 0x37, 0x88, 0x38, 0x0d, 0x62, 0xee, 0x20, 0xe2, 0x31, 0x08, 0xfa, 0x41, 0xf8, 0x72, 0x10, 0x31, 0x18, 0x84, 0x7e, 0x83, 0x19, 0x1b, 0x8c, 0xcf, 0x07, 0x33, 0x77, 0x08, 0x7c, 0x87, 0xe0, 0x83, 0x21, 0xc4, 0x68, 0x08, 0xf6, 0x0f, 0x41, 0xef, 0x21, 0xd8, 0x36, 0x04, 0x9b, 0xd9, 0xff, 0xe9, 0x43, 0x88, 0xf5, 0x10, 0xec, 0x18, 0x42, 0xce, 0x0e, 0x85, 0x8e, 0xbd, 0x9f, 0x3e, 0x94, 0xf1, 0xa1, 0x8c, 0xb3, 0xff, 0xd3, 0x87, 0x32, 0x3e, 0x0c, 0x1e, 0xc3, 0x18, 0x1b, 0x06, 0x8f, 0x61, 0xf0, 0x18, 0x86, 0x8e, 0xc3, 0x98, 0x33, 0x1c, 0x3f, 0x0e, 0xa7, 0x3e, 0x1c, 0xdf, 0x8c, 0x40, 0x8f, 0x91, 0xe4, 0xfb, 0x48, 0xec, 0x1a, 0xc9, 0xd8, 0x28, 0xc6, 0x46, 0x61, 0xef, 0x28, 0x9e, 0xa3, 0x51, 0xe4, 0xe2, 0xb3, 0xc4, 0xe8, 0x59, 0x72, 0x76, 0x34, 0xfe, 0x1a, 0x4d, 0x9d, 0x33, 0xb5, 0x3e, 0x9a, 0xf6, 0x18, 0xda, 0x63, 0x68, 0x8f, 0xa1, 0x3d, 0x86, 0xf6, 0x58, 0x62, 0x3c, 0x16, 0x3f, 0x8f, 0xc5, 0x7f, 0x63, 0xe9, 0x1b, 0x8b, 0xed, 0x63, 0xc9, 0xfb, 0xb1, 0xf0, 0x18, 0x87, 0x1e, 0xe3, 0xf0, 0xdd, 0x38, 0xec, 0x1b, 0x87, 0x7d, 0xe3, 0x98, 0xc7, 0xbe, 0x51, 0x1f, 0x4f, 0xff, 0x78, 0x72, 0x67, 0x3c, 0xbe, 0x1f, 0x8f, 0x1d, 0xe3, 0xe1, 0x33, 0x1e, 0x7f, 0x4f, 0x80, 0xd7, 0x04, 0x74, 0x9e, 0x00, 0xbf, 0x09, 0xd0, 0x4e, 0x80, 0x76, 0x02, 0x3e, 0x9d, 0x80, 0x8f, 0x26, 0x90, 0xeb, 0x13, 0xa0, 0x9b, 0x00, 0xdd, 0x44, 0xe6, 0x4e, 0xc4, 0x2f, 0x13, 0xc9, 0xb3, 0x89, 0xf8, 0x71, 0x22, 0x7e, 0x7b, 0x0e, 0xbd, 0x9f, 0xc3, 0x8e, 0xe7, 0xf0, 0xfb, 0x73, 0xc4, 0x69, 0x12, 0x7e, 0x9f, 0x84, 0x0f, 0x26, 0x91, 0x2f, 0x93, 0xf0, 0xd1, 0x24, 0xec, 0x9e, 0x44, 0x5e, 0x4d, 0xc2, 0x47, 0xec, 0x4d, 0xf5, 0x49, 0xe4, 0x1f, 0xfb, 0x52, 0x9d, 0x3d, 0xa9, 0x3e, 0x19, 0xba, 0xc9, 0xe4, 0xf7, 0x64, 0xec, 0x9f, 0x0c, 0xcd, 0x64, 0xe2, 0x32, 0x19, 0x5e, 0x93, 0x19, 0x9f, 0x4c, 0xbe, 0x4f, 0x21, 0xa7, 0xa7, 0x10, 0xc3, 0x29, 0xd0, 0x4c, 0x21, 0x3f, 0x9e, 0x47, 0xff, 0xe7, 0x99, 0x3f, 0x15, 0xfd, 0xd8, 0xa7, 0xea, 0xd3, 0xdc, 0x12, 0x3d, 0xa7, 0xa1, 0xe7, 0x34, 0xf4, 0x9c, 0x86, 0x7f, 0xa7, 0xb1, 0x7e, 0x4f, 0x87, 0xef, 0x74, 0x74, 0x99, 0x81, 0x9f, 0x67, 0xe2, 0xdf, 0xd9, 0xcc, 0x9b, 0x8d, 0x3f, 0x66, 0x13, 0x93, 0xd9, 0xd8, 0x37, 0x1b, 0xdf, 0xcf, 0x66, 0xce, 0x6c, 0xe6, 0xcc, 0x66, 0xce, 0x8b, 0x3c, 0xe7, 0x73, 0xa0, 0x9d, 0x83, 0x6d, 0x73, 0xb0, 0x6d, 0x0e, 0xb6, 0xcd, 0x21, 0x17, 0xe7, 0xe0, 0xa7, 0x39, 0xf8, 0x69, 0x0e, 0xf6, 0xbf, 0x84, 0x2e, 0x2f, 0x11, 0xef, 0x97, 0xb0, 0xe3, 0x25, 0x74, 0x9b, 0x4b, 0x7b, 0x2e, 0xba, 0xcd, 0x45, 0xb7, 0xb9, 0xe8, 0x36, 0x97, 0x31, 0xf6, 0xbd, 0xfa, 0x5c, 0x6c, 0x98, 0xcb, 0xb3, 0x31, 0x97, 0xe7, 0x70, 0x2e, 0x3a, 0xcc, 0xc3, 0x1f, 0xf3, 0xd0, 0xe7, 0x65, 0xfc, 0x3b, 0x1f, 0x3f, 0xcf, 0x77, 0x4b, 0x74, 0x67, 0xff, 0xab, 0xcf, 0x47, 0xf7, 0x85, 0xf0, 0x59, 0x08, 0x9f, 0x85, 0xf0, 0x61, 0x5f, 0xab, 0x2f, 0x84, 0xcf, 0x42, 0xf8, 0x2c, 0x84, 0x0f, 0x7b, 0x5b, 0x7d, 0x21, 0x7c, 0x16, 0x92, 0xcf, 0x8b, 0xb0, 0x61, 0x11, 0x36, 0x2c, 0x22, 0xa6, 0x8b, 0x98, 0xcf, 0xfe, 0x56, 0x5f, 0x84, 0x1d, 0x8b, 0xf0, 0xff, 0x22, 0x7c, 0xc6, 0x1e, 0x57, 0x5f, 0x4c, 0x7c, 0x16, 0x13, 0xef, 0xc5, 0xe4, 0xc3, 0x62, 0xf2, 0x61, 0x31, 0xf3, 0x16, 0xe3, 0x67, 0xf6, 0xb5, 0xfa, 0x12, 0xc6, 0x96, 0x60, 0xff, 0x12, 0xe6, 0xb2, 0xb7, 0xd5, 0x97, 0x30, 0x77, 0x09, 0x73, 0x97, 0x30, 0x97, 0xfd, 0xad, 0xbe, 0x04, 0x5d, 0x97, 0xa2, 0xeb, 0x52, 0x74, 0x65, 0x8f, 0xab, 0xb3, 0xc7, 0xd5, 0xd9, 0xe3, 0xea, 0xec, 0x71, 0x75, 0xf6, 0xb8, 0x3a, 0x7b, 0x5c, 0x9d, 0x3d, 0xae, 0xce, 0x1e, 0x57, 0x67, 0x8f, 0xab, 0x2f, 0x23, 0xb6, 0xcb, 0x88, 0xed, 0x32, 0xe2, 0xc6, 0x3e, 0x57, 0x5f, 0x46, 0x6c, 0x97, 0x11, 0x9b, 0x65, 0xc4, 0x6e, 0x19, 0xb9, 0xb2, 0x1c, 0x79, 0xcb, 0xd1, 0x65, 0x39, 0xfe, 0x5e, 0xce, 0xf8, 0x72, 0xc6, 0x97, 0x33, 0xbe, 0x9c, 0xf1, 0xe5, 0xf0, 0x59, 0x81, 0xdd, 0x2b, 0xb0, 0x7b, 0x05, 0x39, 0xb7, 0x02, 0x39, 0x2b, 0xd0, 0x67, 0x05, 0xfa, 0xac, 0x40, 0x9f, 0x15, 0xc8, 0x5a, 0x81, 0x3e, 0x2b, 0xd1, 0x67, 0x25, 0xfa, 0xac, 0x44, 0xde, 0x4a, 0xe4, 0xad, 0x44, 0xde, 0x4a, 0xe4, 0xad, 0x84, 0xdf, 0x4a, 0xec, 0x5b, 0x09, 0xcd, 0x2b, 0xd0, 0xbc, 0x02, 0xcd, 0x2b, 0xc4, 0x6a, 0x15, 0x63, 0xab, 0x18, 0x5b, 0xcd, 0xb3, 0xb2, 0x9a, 0x78, 0xae, 0x26, 0x9e, 0xab, 0x89, 0xe7, 0x6a, 0xe2, 0xb9, 0x9a, 0x78, 0xb2, 0xd7, 0xd6, 0xd7, 0x10, 0x83, 0x35, 0xc4, 0x60, 0x0d, 0xba, 0xad, 0x41, 0xb7, 0x35, 0xe4, 0xd0, 0x1a, 0xe2, 0xb0, 0x86, 0x7c, 0x78, 0x95, 0xbe, 0x57, 0x79, 0x7e, 0xd6, 0xe2, 0xeb, 0xb5, 0xe8, 0xbf, 0x16, 0x7f, 0xad, 0x85, 0x66, 0x2d, 0xba, 0xad, 0x65, 0x7c, 0x2d, 0xf9, 0xb2, 0x0e, 0x5d, 0xd6, 0xc1, 0x77, 0x1d, 0xb2, 0xd6, 0x21, 0x6b, 0x1d, 0x76, 0xad, 0xc3, 0xae, 0x75, 0xd8, 0xb5, 0x0e, 0xbb, 0xd6, 0x63, 0xd7, 0x7a, 0xec, 0x5a, 0x4f, 0x3c, 0xd7, 0xe3, 0xc3, 0xf5, 0xc4, 0x73, 0x3d, 0xf1, 0x5c, 0x4f, 0x3c, 0xd7, 0xe3, 0xc7, 0xd7, 0xe8, 0x7b, 0x8d, 0x67, 0x76, 0x03, 0x7a, 0x6c, 0x40, 0x8f, 0x0d, 0xd8, 0xbf, 0x81, 0x35, 0x61, 0x83, 0xdb, 0x47, 0x1e, 0x6e, 0x20, 0x76, 0x1b, 0x90, 0xb3, 0x81, 0xbc, 0xdc, 0x44, 0xfc, 0x36, 0x11, 0xbf, 0xd7, 0xf1, 0xf9, 0xeb, 0xd8, 0xf3, 0x3a, 0x7e, 0x7a, 0x1d, 0x5d, 0x5f, 0x47, 0xde, 0xeb, 0xe4, 0xe4, 0x66, 0xfa, 0x37, 0xa3, 0xe7, 0x66, 0xf4, 0xd9, 0x8c, 0x9d, 0x9b, 0xb1, 0x73, 0x33, 0x76, 0x6e, 0xc6, 0xce, 0xcd, 0xe4, 0xed, 0x66, 0x97, 0x86, 0x58, 0x6c, 0x41, 0xd6, 0x16, 0x64, 0x6d, 0x41, 0xd6, 0x16, 0xfa, 0xfd, 0xd0, 0xfb, 0xa1, 0xf7, 0xa3, 0x7f, 0x0a, 0xfa, 0xa5, 0x20, 0x77, 0x2b, 0x76, 0x6d, 0x85, 0xef, 0x56, 0xec, 0xd8, 0x8a, 0x1d, 0xdb, 0xb0, 0x63, 0x1b, 0x76, 0x6c, 0xc3, 0x8e, 0x6d, 0xe8, 0xb7, 0x0d, 0xfd, 0xb6, 0x21, 0x7b, 0x1b, 0x7e, 0xd8, 0x86, 0x7e, 0xdb, 0xd0, 0x6f, 0x1b, 0xcf, 0xda, 0x76, 0x9e, 0xaf, 0xed, 0xe8, 0xb8, 0x83, 0xdc, 0xdc, 0x41, 0x6e, 0xee, 0xc0, 0x87, 0x3b, 0xd0, 0x73, 0x07, 0x7a, 0xec, 0x40, 0x8f, 0x1d, 0xc8, 0x7b, 0x03, 0x3d, 0xdf, 0x40, 0xe6, 0x9b, 0xc8, 0xdf, 0x89, 0x2e, 0x3b, 0xe1, 0xb9, 0x13, 0x3f, 0xec, 0x44, 0xf6, 0x4e, 0x7c, 0xb3, 0x13, 0xdf, 0xec, 0xc4, 0x37, 0x3b, 0xc9, 0xf5, 0x9d, 0xd8, 0xbc, 0x13, 0x7e, 0xbb, 0x98, 0xb3, 0x0b, 0xdb, 0x76, 0x11, 0x83, 0x5d, 0xe4, 0xec, 0x2e, 0x78, 0xee, 0x22, 0x0e, 0xbb, 0xc8, 0x91, 0x5d, 0xf0, 0xdc, 0x05, 0xcd, 0x6e, 0xe2, 0xbc, 0x1b, 0x3e, 0xbb, 0xe1, 0xb3, 0x1b, 0x3e, 0xbb, 0xf1, 0xdd, 0x6e, 0xf2, 0x62, 0x37, 0x3c, 0x76, 0x33, 0xbe, 0x07, 0x1e, 0x7b, 0xe0, 0xb1, 0x07, 0x1e, 0x7b, 0xe0, 0xb1, 0x07, 0x1e, 0x7b, 0xe0, 0xb1, 0x07, 0x1b, 0xf6, 0x60, 0xc3, 0x5e, 0x74, 0xdd, 0xcb, 0x9a, 0xb9, 0x17, 0xda, 0x7d, 0xd0, 0xee, 0x83, 0x76, 0x1f, 0xb4, 0xfb, 0xa0, 0xe5, 0xec, 0xa3, 0xef, 0x83, 0xcf, 0x7e, 0x72, 0x61, 0x3f, 0xb6, 0xed, 0xc7, 0xb6, 0xfd, 0xd0, 0xef, 0x27, 0x1f, 0xf6, 0x93, 0x33, 0xfb, 0xd1, 0x63, 0x3f, 0xb9, 0xba, 0x9f, 0x5c, 0xdd, 0x4f, 0x1e, 0x1e, 0x20, 0x0f, 0x0f, 0x90, 0x87, 0x07, 0xf0, 0xe3, 0x01, 0x72, 0xf5, 0x00, 0xb9, 0x7a, 0x80, 0xb1, 0x03, 0x8c, 0x1d, 0x64, 0xec, 0x20, 0xba, 0x1e, 0x44, 0xf6, 0x41, 0xe6, 0x1d, 0x64, 0xde, 0x41, 0xc6, 0x0e, 0x32, 0x76, 0x88, 0xb1, 0x43, 0xcc, 0x3b, 0xc4, 0xbc, 0x43, 0xcc, 0x3b, 0xc4, 0xbc, 0x43, 0xe4, 0xd6, 0x21, 0x62, 0x73, 0x88, 0xdc, 0x7a, 0x8b, 0x98, 0xbc, 0x45, 0x4c, 0xde, 0x22, 0x26, 0x87, 0x89, 0xc9, 0x61, 0x62, 0x72, 0x18, 0xff, 0x1d, 0xc6, 0xee, 0xc3, 0xc4, 0xf0, 0x30, 0x31, 0x3f, 0x8c, 0xaf, 0x0f, 0xe3, 0xeb, 0xc3, 0xf8, 0xe5, 0x30, 0x3c, 0x8f, 0xc0, 0xf3, 0x08, 0x3c, 0x8f, 0xe0, 0xef, 0x23, 0xc4, 0xee, 0x6d, 0xe6, 0xbf, 0x4b, 0xac, 0x8e, 0x12, 0xab, 0xa3, 0xe8, 0x71, 0x94, 0x5c, 0x3a, 0x4a, 0x3c, 0x8e, 0x32, 0xff, 0x28, 0xf3, 0x8f, 0x32, 0xff, 0x7d, 0xf4, 0x7a, 0x1f, 0xbd, 0xde, 0x47, 0xaf, 0xf7, 0xe1, 0x71, 0x8c, 0x79, 0xc7, 0x88, 0xf9, 0x31, 0x72, 0xe3, 0x18, 0x7e, 0x3d, 0x86, 0x5f, 0x3f, 0x44, 0x9f, 0x8f, 0x18, 0x3b, 0x4e, 0xfb, 0x38, 0xed, 0xe3, 0xf8, 0xee, 0x38, 0xcf, 0xcb, 0x09, 0x7c, 0x74, 0x02, 0x1f, 0x7d, 0x0c, 0xbf, 0x4f, 0x98, 0xf3, 0x09, 0x73, 0x3e, 0x81, 0xe6, 0x13, 0x68, 0x3e, 0x81, 0xe6, 0x13, 0x68, 0x3e, 0x85, 0xe6, 0x53, 0x68, 0x3e, 0xc5, 0x8f, 0x9f, 0xe2, 0xc7, 0x93, 0xf8, 0xf9, 0x24, 0x7e, 0x3e, 0x89, 0x9f, 0x4f, 0xe2, 0x97, 0x93, 0xc8, 0x3f, 0x89, 0xfc, 0x93, 0xc8, 0x3f, 0x89, 0x8c, 0xcf, 0xb0, 0xe1, 0x33, 0x6c, 0xf8, 0x0c, 0x5b, 0x3f, 0x27, 0x3e, 0xa7, 0x88, 0xf1, 0x29, 0x62, 0x7c, 0x8a, 0x5c, 0x39, 0x45, 0xae, 0x9c, 0x82, 0xef, 0x69, 0x78, 0x9e, 0x86, 0xe7, 0x69, 0x78, 0x9e, 0x86, 0xe7, 0x69, 0x78, 0x9d, 0x21, 0xb6, 0x67, 0x88, 0xed, 0x19, 0x78, 0x7c, 0x8d, 0xbe, 0x5f, 0xe3, 0xbf, 0xaf, 0xb1, 0xff, 0x6b, 0xfc, 0x77, 0x96, 0xdc, 0x3b, 0x4b, 0x8e, 0x9d, 0x25, 0xae, 0xe7, 0x98, 0x7b, 0x1e, 0x5f, 0x9c, 0xc7, 0x17, 0xe7, 0xf1, 0xc5, 0x79, 0x74, 0x3f, 0x8f, 0xcf, 0xbf, 0xe1, 0x79, 0xf9, 0x16, 0xba, 0x6f, 0x79, 0x5e, 0xbe, 0xc5, 0x7f, 0xdf, 0xe2, 0x87, 0x6f, 0xb1, 0xe9, 0x5b, 0x6c, 0xfa, 0x16, 0x9b, 0xbe, 0xc5, 0xa6, 0x6f, 0x91, 0xfd, 0x2d, 0x36, 0x7d, 0x07, 0x8f, 0xef, 0x90, 0x7f, 0x01, 0xb9, 0xdf, 0x23, 0xf3, 0x12, 0xcf, 0xdb, 0x25, 0xe6, 0x5f, 0x66, 0xfe, 0x65, 0xe6, 0x5f, 0x66, 0xfe, 0x65, 0xe6, 0x5f, 0x66, 0xfe, 0x65, 0xe6, 0x73, 0xe6, 0xd4, 0x7f, 0x24, 0x46, 0x3f, 0x51, 0xff, 0x09, 0x5e, 0x57, 0x98, 0x7b, 0x05, 0xdd, 0xaf, 0xe0, 0x87, 0xab, 0xd0, 0x5d, 0x85, 0xee, 0x2a, 0x63, 0x57, 0x19, 0xbb, 0x8a, 0x9c, 0xab, 0xc8, 0xb9, 0x8a, 0x9c, 0x9f, 0x91, 0xf3, 0x33, 0xb4, 0x3f, 0x43, 0xfb, 0x33, 0x76, 0xfe, 0x4c, 0x0e, 0xfe, 0x8c, 0xcf, 0x7e, 0x86, 0xdf, 0x2f, 0x8c, 0xfd, 0x82, 0x8f, 0x7e, 0x21, 0x1f, 0x7e, 0xc1, 0x47, 0xbf, 0xe0, 0xa3, 0x6b, 0xd0, 0xfc, 0x8a, 0xef, 0x7e, 0xc3, 0xf6, 0xdf, 0xb0, 0xfd, 0x3a, 0xbc, 0xaf, 0xc3, 0xfb, 0x3a, 0xbc, 0xaf, 0xc3, 0xfb, 0x3a, 0xbc, 0xaf, 0xc3, 0xfb, 0x26, 0x3e, 0xbe, 0x49, 0xde, 0xdf, 0x24, 0xef, 0x6f, 0x12, 0x8f, 0x9b, 0xd8, 0x7f, 0x13, 0x9f, 0xdd, 0xc4, 0x47, 0x37, 0xf1, 0xd1, 0x4d, 0xfc, 0x7e, 0x0b, 0xbf, 0xdc, 0x22, 0x47, 0x6e, 0xc3, 0xe3, 0x36, 0x3c, 0x6e, 0xc3, 0xe3, 0x36, 0x3c, 0x38, 0xe7, 0xea, 0x7f, 0x90, 0x37, 0x7f, 0xa0, 0xc3, 0x1f, 0xd8, 0xf4, 0x07, 0xb6, 0xff, 0x81, 0xed, 0x77, 0xb1, 0xfd, 0x2e, 0xb6, 0xdf, 0xc5, 0xf6, 0xbb, 0xcc, 0xb9, 0xcb, 0x1c, 0xce, 0xbe, 0xfa, 0x3d, 0xe4, 0xdd, 0xc3, 0x16, 0xce, 0xbf, 0xfa, 0x7d, 0x6c, 0xb9, 0x8f, 0x2d, 0xf7, 0xd1, 0xf3, 0x3e, 0xb6, 0xdc, 0xe7, 0x79, 0xba, 0xcf, 0xda, 0xf2, 0x00, 0x9e, 0x0f, 0x88, 0xc7, 0x03, 0xe8, 0x02, 0xd0, 0x05, 0xa0, 0x0b, 0x40, 0x17, 0x80, 0x2e, 0x00, 0x5d, 0x00, 0xba, 0x00, 0x32, 0x03, 0xdb, 0xc5, 0x90, 0x4b, 0x62, 0x28, 0x02, 0x72, 0x81, 0x3a, 0xa0, 0x03, 0x18, 0x02, 0x66, 0x80, 0x35, 0x60, 0x2f, 0x38, 0x09, 0xae, 0x88, 0xa1, 0xea, 0x20, 0x2f, 0x28, 0x0f, 0x9a, 0x80, 0x64, 0x30, 0x1a, 0xcc, 0x03, 0x9b, 0xc0, 0x11, 0x31, 0x34, 0xe8, 0x35, 0xe8, 0x35, 0xe8, 0x0d, 0x78, 0x7a, 0xce, 0x8b, 0x61, 0xde, 0x13, 0x83, 0x33, 0xb1, 0x61, 0x45, 0x01, 0x64, 0xd8, 0x89, 0x60, 0x04, 0x78, 0x11, 0xf8, 0xc5, 0xf0, 0xd2, 0xf6, 0xd2, 0xf6, 0xd2, 0xf6, 0xae, 0x07, 0x07, 0xc1, 0x29, 0x70, 0x4d, 0x8c, 0x74, 0xf0, 0x4a, 0x07, 0xaf, 0xf4, 0xc8, 0x4e, 0xcf, 0xfc, 0xf4, 0xcc, 0x4f, 0x0f, 0x7d, 0xc6, 0x49, 0x00, 0xda, 0x8c, 0xd0, 0x66, 0xb6, 0x80, 0x0f, 0x44, 0x83, 0x38, 0xd0, 0x43, 0x8c, 0x2c, 0xb5, 0x40, 0x3c, 0x18, 0x00, 0xa6, 0x82, 0x95, 0x60, 0x27, 0x38, 0x01, 0xb0, 0x39, 0x2b, 0x36, 0x67, 0x45, 0xbf, 0xac, 0xf0, 0xcb, 0x0a, 0x7d, 0x56, 0x6c, 0xc8, 0x76, 0x43, 0x8c, 0xec, 0x20, 0x47, 0x3a, 0x50, 0x10, 0x54, 0x03, 0xad, 0x41, 0x1f, 0x80, 0xbc, 0x1c, 0x4b, 0x01, 0x3e, 0xcb, 0x71, 0x0c, 0x5c, 0x00, 0xd8, 0x95, 0xb3, 0x08, 0xc0, 0x17, 0x39, 0xd1, 0x3f, 0x37, 0xf2, 0xf3, 0xc0, 0x27, 0x0f, 0xbe, 0xc8, 0x83, 0x2f, 0xf2, 0x9c, 0x01, 0xf0, 0x0b, 0x83, 0x5f, 0x18, 0xfc, 0xc2, 0xe0, 0x17, 0x06, 0xbf, 0x30, 0xf8, 0x85, 0xc1, 0x2f, 0x0c, 0x7e, 0x9c, 0xb9, 0x8d, 0x30, 0xf8, 0x85, 0xc1, 0x2f, 0x0c, 0x7e, 0x79, 0xe1, 0x97, 0x17, 0xdd, 0xf3, 0xa2, 0x7b, 0x5e, 0x7c, 0x9b, 0x17, 0xbd, 0xf3, 0xa2, 0x77, 0x5e, 0xf4, 0xce, 0x8b, 0xde, 0xf9, 0xd1, 0x3b, 0x3f, 0x7a, 0xe7, 0x2f, 0x0d, 0x1a, 0x00, 0x7c, 0x11, 0x0e, 0x6d, 0x38, 0x76, 0x86, 0x63, 0x67, 0x38, 0xf4, 0xe1, 0xd0, 0x87, 0x43, 0x1f, 0x7e, 0x29, 0xf5, 0x9f, 0x90, 0xf1, 0xd1, 0xef, 0xa3, 0xdf, 0x47, 0x7f, 0x04, 0x3a, 0x45, 0xa0, 0x53, 0x01, 0x74, 0x2a, 0x80, 0x4e, 0x05, 0xd0, 0xa9, 0x00, 0xfd, 0x05, 0xa0, 0x2f, 0x00, 0x7d, 0x24, 0xf4, 0x9c, 0xe9, 0x8d, 0x48, 0xf8, 0x47, 0xc2, 0x9f, 0x73, 0xbd, 0x11, 0x89, 0x6d, 0x91, 0xff, 0x87, 0xb6, 0xf7, 0x80, 0x8f, 0xeb, 0xaa, 0x12, 0xc6, 0xef, 0x7d, 0x75, 0x7a, 0xd1, 0x34, 0x4d, 0x93, 0x34, 0x9a, 0xae, 0x2e, 0x8d, 0x34, 0xea, 0xd2, 0x93, 0xac, 0x2e, 0xd9, 0xb2, 0xdc, 0x65, 0x49, 0x6e, 0x92, 0xe5, 0x6e, 0xcb, 0x25, 0x71, 0x1c, 0x3b, 0x4e, 0xec, 0x38, 0xcd, 0x49, 0x9c, 0x4a, 0x28, 0x49, 0x20, 0x90, 0x02, 0xa9, 0x98, 0x84, 0x34, 0x12, 0x20, 0xc4, 0xde, 0xc0, 0x42, 0x60, 0xd9, 0xef, 0x4b, 0xb2, 0xbb, 0xf9, 0x01, 0x7f, 0xb2, 0xcb, 0x02, 0x4b, 0x59, 0x42, 0x58, 0x20, 0x10, 0x6b, 0xfc, 0x9d, 0x7b, 0xdf, 0x7b, 0x53, 0x64, 0xc9, 0x76, 0x58, 0xfe, 0xce, 0x4f, 0x99, 0x99, 0xf7, 0xee, 0xbd, 0xef, 0xdc, 0x73, 0xce, 0x3d, 0xed, 0x9e, 0x7b, 0x1e, 0xd0, 0x26, 0x08, 0xf8, 0x0e, 0x02, 0xbe, 0x83, 0x40, 0x9b, 0x20, 0xd0, 0x26, 0x04, 0x78, 0x0f, 0x01, 0xde, 0x43, 0x30, 0xef, 0x30, 0x8c, 0x15, 0xfe, 0x10, 0xf1, 0x51, 0xf8, 0x8c, 0xc2, 0x7c, 0x62, 0x40, 0xd7, 0x38, 0xf0, 0x52, 0x09, 0xcc, 0xb7, 0x14, 0xc6, 0x2b, 0xfd, 0x14, 0xe2, 0xcb, 0x01, 0x37, 0xe0, 0xd7, 0x93, 0x92, 0x36, 0x7c, 0x15, 0xfc, 0xae, 0x06, 0xbc, 0x57, 0xc3, 0x9c, 0x6b, 0xa0, 0x7f, 0x0d, 0x8c, 0x99, 0x80, 0xf6, 0x09, 0xc0, 0x43, 0x2d, 0xc0, 0x53, 0x07, 0xbc, 0x95, 0x04, 0x7a, 0x27, 0xbf, 0x83, 0xf8, 0x7a, 0xc0, 0x6f, 0x3d, 0xe0, 0xa9, 0x01, 0xf0, 0xd6, 0x00, 0xbc, 0xd0, 0x08, 0x73, 0x6d, 0x84, 0x76, 0x4d, 0x00, 0x5f, 0x13, 0xe0, 0xba, 0x19, 0xda, 0xb6, 0xc0, 0x98, 0x2d, 0xf0, 0x6c, 0xf0, 0x7d, 0xf9, 0x56, 0xe0, 0xb5, 0x56, 0xe8, 0xdb, 0x0a, 0xb4, 0x05, 0x3f, 0x98, 0x6f, 0x05, 0x7e, 0x6e, 0x05, 0x7e, 0x6e, 0x05, 0x7e, 0x6e, 0x05, 0xfe, 0x6c, 0x05, 0x9e, 0x6a, 0x85, 0x71, 0xda, 0x80, 0xa7, 0xda, 0xa0, 0x4f, 0x1b, 0xb4, 0x6b, 0x83, 0x31, 0xda, 0x80, 0x26, 0x6d, 0xd0, 0xa6, 0x0d, 0x68, 0xd7, 0x06, 0xe3, 0xb7, 0xc3, 0xfd, 0x76, 0xe0, 0xf7, 0x76, 0xe0, 0xf7, 0x76, 0x80, 0xa1, 0x1d, 0xda, 0xb4, 0x03, 0x4d, 0xda, 0x61, 0x1e, 0xed, 0xf0, 0xdc, 0x76, 0x68, 0xd7, 0x0e, 0xf8, 0x6c, 0x07, 0x7c, 0x4a, 0x80, 0x4f, 0x09, 0xc6, 0x92, 0x00, 0x9f, 0x12, 0xcc, 0x59, 0x02, 0x58, 0x25, 0x78, 0xa6, 0x04, 0xed, 0x24, 0xc0, 0x87, 0x04, 0xcf, 0xeb, 0x80, 0x36, 0x1d, 0x00, 0x57, 0x07, 0x8c, 0xd5, 0x01, 0xf7, 0x3b, 0xe0, 0x7e, 0x07, 0xdc, 0xef, 0x80, 0xfb, 0x1d, 0xf0, 0xbc, 0x0e, 0x80, 0xbd, 0x13, 0x60, 0xef, 0x84, 0x36, 0x9d, 0x00, 0x53, 0x27, 0xc0, 0xde, 0x09, 0xb0, 0x77, 0x42, 0xbb, 0x4e, 0x80, 0xab, 0x13, 0x60, 0x5f, 0x04, 0x7d, 0x17, 0x01, 0x1c, 0x8b, 0x00, 0x8e, 0x45, 0x00, 0xc7, 0x22, 0xe8, 0xbf, 0x08, 0xe0, 0x58, 0x04, 0x70, 0x2c, 0x02, 0x38, 0xba, 0xe0, 0x19, 0x5d, 0x00, 0x47, 0x17, 0xc0, 0xd1, 0x05, 0x70, 0x74, 0xc1, 0x73, 0xba, 0xa0, 0x7f, 0x17, 0xb4, 0xeb, 0x82, 0xe7, 0x74, 0x01, 0x1c, 0xdd, 0xd0, 0xa6, 0x1b, 0x9e, 0xd1, 0x0d, 0x63, 0x75, 0xc3, 0xfd, 0x6e, 0xe0, 0x8f, 0x6e, 0xa0, 0x6d, 0x37, 0xe0, 0xba, 0x1b, 0xd6, 0x6b, 0x37, 0xd0, 0xb1, 0xe7, 0x3a, 0xc4, 0xf7, 0x02, 0x0c, 0xbd, 0x00, 0x43, 0x2f, 0xc0, 0xd0, 0x0b, 0x63, 0xf4, 0x02, 0x0c, 0xbd, 0x00, 0x43, 0x2f, 0xe0, 0x0f, 0xfc, 0x7d, 0xbe, 0x0f, 0xf0, 0xd3, 0x07, 0xf8, 0xe9, 0x03, 0xfc, 0xf4, 0xc1, 0x58, 0x7d, 0x00, 0x57, 0x1f, 0xc0, 0xd5, 0x07, 0x70, 0xf5, 0xc1, 0xf3, 0xfa, 0x00, 0xae, 0x7e, 0x98, 0x57, 0x3f, 0xcc, 0x6b, 0x00, 0xe6, 0x35, 0x00, 0xcf, 0x1c, 0x80, 0x31, 0x07, 0x60, 0xcc, 0x01, 0x18, 0x73, 0x00, 0xc6, 0x1c, 0x80, 0x31, 0x07, 0x60, 0xcc, 0x01, 0x18, 0x73, 0x00, 0xc6, 0x1c, 0x84, 0x31, 0x07, 0x61, 0xcc, 0x41, 0x18, 0x73, 0x10, 0xc6, 0x1c, 0x84, 0x31, 0x07, 0x61, 0xcc, 0x41, 0x18, 0x73, 0x10, 0xc6, 0x1c, 0x84, 0x31, 0x07, 0x61, 0xae, 0x43, 0xc0, 0x2f, 0x8b, 0xe1, 0xda, 0x62, 0xb8, 0xb6, 0x18, 0xae, 0x2d, 0x86, 0x6b, 0x8b, 0x61, 0xfe, 0x4b, 0x60, 0x6e, 0x4b, 0x60, 0xfe, 0x4b, 0x60, 0xfe, 0x4b, 0x60, 0xfe, 0x4b, 0x60, 0x7e, 0x4b, 0x80, 0xae, 0x4b, 0x60, 0xad, 0x2d, 0x01, 0x1e, 0x5a, 0x02, 0x38, 0x58, 0x02, 0xcf, 0x19, 0x86, 0x76, 0xc3, 0x00, 0xcf, 0x30, 0x3c, 0x63, 0x18, 0xda, 0x0c, 0x03, 0x2c, 0xc3, 0x30, 0xd6, 0x30, 0xdc, 0x1f, 0x86, 0xfb, 0x4b, 0xe1, 0xfe, 0x52, 0xb8, 0xbf, 0x14, 0x70, 0xb2, 0x14, 0x70, 0xb2, 0x14, 0x70, 0x32, 0x62, 0x81, 0x3f, 0x58, 0x9f, 0x23, 0xd0, 0x67, 0x04, 0x78, 0x70, 0x04, 0x9e, 0x3f, 0x02, 0xbc, 0x3a, 0x02, 0x6b, 0x67, 0x19, 0xf0, 0xf3, 0x32, 0xe0, 0xf9, 0x65, 0xd0, 0x7e, 0x19, 0xb4, 0x5f, 0x06, 0xed, 0x97, 0x43, 0xfb, 0xe5, 0xb0, 0x8e, 0x96, 0x03, 0x1c, 0xcb, 0xe1, 0x19, 0x2b, 0x60, 0x4e, 0x2b, 0xa0, 0xef, 0x0a, 0x98, 0xd3, 0x0a, 0x98, 0xd3, 0x4a, 0x78, 0xde, 0x6a, 0x58, 0x5b, 0xab, 0x61, 0x4d, 0xac, 0x06, 0xfc, 0xaf, 0x86, 0xb5, 0xb5, 0x06, 0xf0, 0xb3, 0x06, 0xf0, 0xb3, 0x06, 0xf0, 0xb3, 0x06, 0x60, 0x1a, 0x85, 0xb5, 0x31, 0x0a, 0xf7, 0x47, 0xe1, 0xfe, 0x28, 0xdc, 0x1f, 0x85, 0x79, 0x8f, 0xc2, 0xda, 0x5b, 0x0b, 0xf4, 0x59, 0x0b, 0xb8, 0x5b, 0x0b, 0xdf, 0xc7, 0x00, 0xb7, 0x63, 0x30, 0xf6, 0x18, 0x8c, 0x3b, 0x06, 0x6d, 0xc7, 0xa0, 0xed, 0x18, 0xe0, 0x63, 0x1c, 0xf0, 0x30, 0x0e, 0x63, 0x8d, 0x03, 0x9c, 0xe3, 0x30, 0xaf, 0x09, 0xc0, 0xed, 0x04, 0xe0, 0x65, 0x02, 0xc6, 0x9e, 0x80, 0xdf, 0xeb, 0xe0, 0xfe, 0x06, 0x58, 0x9b, 0x1b, 0xa0, 0xdf, 0x06, 0xe8, 0xb3, 0x01, 0xae, 0x6d, 0x80, 0xf1, 0x36, 0xc2, 0x78, 0x1b, 0xe1, 0xb9, 0x1b, 0x41, 0x96, 0x6e, 0x84, 0xb6, 0x1b, 0x61, 0x6e, 0x1b, 0x81, 0x46, 0x1b, 0x61, 0x5e, 0x1b, 0x61, 0x5e, 0x9b, 0x60, 0x5e, 0x9b, 0x00, 0x0f, 0x9b, 0x40, 0x4e, 0x6d, 0x02, 0xd9, 0xb3, 0x09, 0x70, 0xb1, 0x09, 0x9e, 0xb1, 0x09, 0x70, 0xb1, 0x09, 0x9e, 0x3b, 0x09, 0x6b, 0x71, 0x12, 0xc6, 0x9d, 0x84, 0xb9, 0x4e, 0x02, 0x9c, 0x93, 0x80, 0xf7, 0x49, 0xe0, 0x81, 0x29, 0xc0, 0xc5, 0x14, 0xcc, 0x77, 0x0a, 0x68, 0xbd, 0x19, 0xee, 0x6f, 0x86, 0xb1, 0x37, 0x43, 0x9f, 0x69, 0xc0, 0xf3, 0x34, 0xc8, 0xa7, 0x2d, 0xb0, 0x9e, 0xb7, 0xc0, 0xf3, 0xb7, 0x82, 0xcc, 0xd9, 0x0a, 0xb0, 0x6c, 0x03, 0x58, 0xb7, 0x01, 0x5c, 0xdb, 0xe1, 0xfe, 0x76, 0xa0, 0xdf, 0x0e, 0xa0, 0xc9, 0x0e, 0x98, 0xdf, 0x4e, 0x18, 0x7f, 0x27, 0xd0, 0x74, 0x27, 0xc0, 0xb2, 0x0b, 0xf0, 0xb8, 0x0b, 0xe8, 0xbf, 0x1b, 0xe6, 0xb6, 0x1b, 0xe0, 0xd9, 0x0d, 0xcf, 0xdb, 0x0d, 0xb0, 0xee, 0x86, 0xe7, 0xed, 0x81, 0xf1, 0xf6, 0x00, 0x0e, 0xf7, 0x00, 0xce, 0x66, 0xe0, 0xfe, 0x0c, 0xd0, 0x60, 0x06, 0xe0, 0xdb, 0x0b, 0x7d, 0xf6, 0x42, 0x9f, 0x7d, 0x70, 0x6d, 0x1f, 0xe0, 0x7b, 0x3f, 0xc0, 0xb2, 0x1f, 0xae, 0xed, 0x87, 0x6b, 0x07, 0x60, 0x2e, 0x07, 0x80, 0x2e, 0x07, 0x40, 0x16, 0x5e, 0x01, 0xf8, 0xb9, 0x02, 0x60, 0xb9, 0x02, 0xfa, 0x5f, 0x09, 0xb8, 0x38, 0x08, 0xcf, 0x3d, 0x08, 0xf0, 0x5c, 0x05, 0x73, 0xbe, 0x0a, 0x68, 0x7e, 0x08, 0x9e, 0x77, 0x08, 0x64, 0xd8, 0x21, 0x80, 0xe9, 0x10, 0xcc, 0xe3, 0x6a, 0xc0, 0xcb, 0xd5, 0xd0, 0xff, 0x6a, 0xe0, 0x81, 0xc3, 0x00, 0xef, 0x11, 0x78, 0xfe, 0x35, 0xc0, 0xcb, 0xd7, 0xc0, 0x33, 0xae, 0x81, 0x67, 0x5f, 0x03, 0xe3, 0x1e, 0x05, 0xbc, 0x1d, 0x05, 0xd8, 0x8f, 0xc2, 0xba, 0xb8, 0x16, 0xc6, 0xbe, 0x0e, 0xf0, 0x7c, 0x0c, 0x64, 0xe8, 0x31, 0xb8, 0x77, 0x1c, 0xe6, 0x79, 0x1c, 0xc6, 0xb9, 0x1e, 0xe6, 0x7d, 0x3d, 0xd0, 0xed, 0x7a, 0x80, 0xe9, 0x7a, 0x18, 0xe7, 0x7a, 0x98, 0xcf, 0xf5, 0x00, 0xf7, 0xf5, 0x30, 0xa7, 0x13, 0x30, 0x16, 0xf8, 0xfc, 0xfc, 0x0d, 0xc0, 0x0b, 0x37, 0x02, 0xfe, 0x6e, 0x04, 0x9a, 0xdc, 0x04, 0xd7, 0x6e, 0x06, 0xdc, 0xdc, 0x0c, 0x70, 0xde, 0x02, 0x78, 0x3c, 0x09, 0xe3, 0xde, 0x0a, 0x30, 0xde, 0x06, 0xf3, 0xba, 0x0d, 0xe0, 0xbd, 0x1d, 0x3e, 0x6f, 0x87, 0xcf, 0x53, 0x80, 0xff, 0x53, 0x30, 0xd6, 0x1d, 0xd0, 0x1e, 0x7c, 0x76, 0xfe, 0x4e, 0x80, 0xef, 0x4e, 0x80, 0xf5, 0x2e, 0x80, 0xf9, 0x6e, 0x80, 0xe1, 0x6e, 0xf8, 0x7e, 0x0f, 0x7c, 0xbf, 0x17, 0xbe, 0xdf, 0x0b, 0xdf, 0x3f, 0x01, 0xb0, 0x7d, 0x02, 0x78, 0xfa, 0x3e, 0xf8, 0x7d, 0x1f, 0xd0, 0xe3, 0x93, 0x30, 0x87, 0x4f, 0xc1, 0x58, 0x9f, 0x82, 0xf9, 0x82, 0x1f, 0xcd, 0x7f, 0x1a, 0xe0, 0xfb, 0x34, 0xd0, 0xeb, 0x33, 0x20, 0x3f, 0xee, 0x07, 0x9e, 0xb9, 0x1f, 0x60, 0x7d, 0x00, 0xae, 0x3f, 0x00, 0x38, 0x01, 0x3f, 0x97, 0x7f, 0x10, 0xe8, 0xff, 0x20, 0x7c, 0x82, 0xff, 0xca, 0x7f, 0x16, 0x60, 0xff, 0x1c, 0x8c, 0x05, 0xbe, 0x27, 0xff, 0x79, 0xc0, 0xd3, 0xe7, 0xe1, 0xf9, 0x5f, 0x00, 0xf8, 0xbf, 0x00, 0x73, 0x79, 0x18, 0xc6, 0x79, 0x18, 0x78, 0xf9, 0x61, 0x78, 0xc6, 0xc3, 0xb0, 0x86, 0x1f, 0x01, 0x5e, 0x06, 0xbf, 0x92, 0x7f, 0x14, 0x70, 0xf7, 0x18, 0xd0, 0xef, 0x8b, 0xd0, 0xee, 0x4b, 0x80, 0x9b, 0xc7, 0x01, 0x9e, 0x27, 0xa0, 0xed, 0x93, 0xa0, 0x77, 0x9e, 0x84, 0xfe, 0x4f, 0xc1, 0xf7, 0xa7, 0x60, 0xdc, 0xa7, 0x01, 0x9f, 0xcf, 0xc0, 0xbd, 0x67, 0x00, 0x87, 0x5f, 0x86, 0x7b, 0x5f, 0x86, 0x7b, 0xa7, 0xa1, 0xef, 0x69, 0x98, 0xdf, 0x69, 0xb8, 0xf6, 0x15, 0x18, 0xef, 0x2b, 0x30, 0xc6, 0xb3, 0x00, 0xe3, 0xb3, 0xd0, 0xf6, 0x39, 0x80, 0xf1, 0x39, 0xc0, 0xd3, 0x73, 0x80, 0xbb, 0xaf, 0xc2, 0x73, 0xbe, 0x0a, 0x63, 0x3c, 0x0f, 0x38, 0x01, 0x3f, 0x8b, 0x7f, 0x01, 0x70, 0xf4, 0x02, 0xf4, 0x79, 0x11, 0x68, 0xfb, 0x22, 0xac, 0x8b, 0x17, 0xe1, 0x19, 0x2f, 0x02, 0x6f, 0xbf, 0x48, 0xae, 0xc1, 0xb8, 0x2f, 0x02, 0x1d, 0x5e, 0x04, 0x18, 0x5f, 0x02, 0x7c, 0xbf, 0x04, 0xfc, 0xf1, 0x12, 0xd0, 0x0a, 0x7c, 0x2e, 0x1e, 0x7c, 0x2e, 0x1e, 0x7c, 0x2e, 0xfe, 0x6b, 0xf0, 0x9c, 0xaf, 0x01, 0xcc, 0xe0, 0x43, 0xf1, 0xe0, 0x2b, 0xf1, 0xaf, 0xc0, 0xfc, 0x5f, 0x85, 0xb1, 0x5e, 0x05, 0xba, 0x82, 0x4f, 0xc4, 0x7f, 0x1d, 0xfa, 0x83, 0xef, 0xc3, 0x7f, 0x03, 0xc6, 0xf8, 0x26, 0x8c, 0xfd, 0x1a, 0x8c, 0xf3, 0x1a, 0x3c, 0xf3, 0x35, 0x80, 0xf5, 0x35, 0xc0, 0x1b, 0xf8, 0x36, 0xfc, 0x6b, 0x40, 0xab, 0xd7, 0x61, 0xbc, 0x7f, 0x28, 0x46, 0x1c, 0x2a, 0x03, 0xe3, 0x92, 0xe5, 0x1f, 0x40, 0x61, 0xd4, 0x88, 0x5a, 0x51, 0x07, 0xea, 0x96, 0x3a, 0x11, 0xcb, 0x60, 0x86, 0xc5, 0x33, 0x08, 0x73, 0x0c, 0xe6, 0x48, 0x29, 0x37, 0x86, 0x45, 0xcc, 0x24, 0x34, 0xe6, 0x35, 0x1c, 0xac, 0x3e, 0xad, 0x56, 0x18, 0x45, 0x82, 0x30, 0x39, 0x88, 0x34, 0x1a, 0x71, 0x14, 0x89, 0xe2, 0x94, 0x38, 0xd4, 0x21, 0x59, 0xad, 0xf6, 0x58, 0x24, 0x58, 0xe2, 0x0b, 0xe9, 0x74, 0xfe, 0x52, 0x5b, 0x1b, 0x9b, 0xa8, 0x29, 0x60, 0x1c, 0x76, 0x13, 0x67, 0xc6, 0xc1, 0x68, 0xa4, 0x8d, 0xab, 0xab, 0x8d, 0x04, 0x8b, 0x4d, 0x4c, 0xb0, 0x38, 0x6a, 0xab, 0x6d, 0x63, 0x94, 0x9b, 0xf0, 0xb3, 0x82, 0xc1, 0x01, 0x6b, 0xa0, 0x1e, 0xfe, 0xfe, 0xec, 0x69, 0xee, 0x59, 0x5a, 0x29, 0x6d, 0x1f, 0x88, 0xfb, 0xcb, 0xeb, 0xaa, 0x82, 0x5c, 0xde, 0x0d, 0x26, 0xde, 0x53, 0x96, 0xac, 0x2c, 0x5a, 0x94, 0x8c, 0xd6, 0x47, 0x7d, 0x06, 0xaf, 0x71, 0x5d, 0x5e, 0x61, 0x89, 0x3b, 0xbf, 0xa4, 0x30, 0x0f, 0x3e, 0xf3, 0xdd, 0xf0, 0x99, 0x7a, 0x98, 0xfd, 0x97, 0x73, 0xf1, 0x24, 0xbb, 0xf9, 0xdc, 0xfd, 0xdc, 0x41, 0x67, 0xc4, 0x6f, 0x0d, 0x75, 0x4c, 0x34, 0xd6, 0x0f, 0x37, 0x97, 0x46, 0x42, 0xae, 0x2d, 0x57, 0x04, 0x13, 0xa5, 0xd1, 0xf2, 0x86, 0x68, 0x65, 0xbd, 0xd5, 0x6e, 0xcd, 0x9f, 0x15, 0xf3, 0x4b, 0x8b, 0xf2, 0xf2, 0x8a, 0x4a, 0xf3, 0xdd, 0xa5, 0x64, 0x80, 0x52, 0xee, 0xd6, 0xbf, 0x4e, 0x35, 0xf1, 0x31, 0x98, 0x1c, 0xfc, 0x07, 0x42, 0x16, 0x70, 0x20, 0x22, 0x07, 0xaa, 0x94, 0xca, 0x58, 0x8c, 0x38, 0x3c, 0x40, 0xcc, 0x56, 0x06, 0x23, 0x66, 0x13, 0xe2, 0xb8, 0xc9, 0x41, 0x16, 0x63, 0x3c, 0x85, 0x87, 0x10, 0x72, 0xd8, 0x6d, 0x56, 0x93, 0x51, 0xa7, 0x81, 0xc6, 0xa2, 0xa0, 0xb3, 0x97, 0x62, 0x36, 0x12, 0x35, 0x61, 0x91, 0xc5, 0xc1, 0xe2, 0x48, 0x94, 0x0d, 0xb0, 0x26, 0x5c, 0x8a, 0x71, 0x82, 0x7d, 0xea, 0x6d, 0x97, 0x4b, 0xef, 0xd2, 0xbf, 0xc5, 0x3e, 0xfe, 0x88, 0x2f, 0xa4, 0xb9, 0x76, 0xf6, 0xa6, 0x6b, 0xc5, 0x88, 0xf7, 0x11, 0xa6, 0xe5, 0x5f, 0x18, 0x17, 0x53, 0xde, 0xb8, 0xba, 0x74, 0xc0, 0x3f, 0xfb, 0xf6, 0xec, 0x7f, 0xf9, 0x9c, 0x78, 0x71, 0xea, 0x39, 0x5b, 0x21, 0x39, 0xe9, 0x00, 0x50, 0xe0, 0xd4, 0x13, 0xfc, 0x3d, 0x14, 0x8e, 0x0a, 0x74, 0xf0, 0x79, 0x00, 0x83, 0xc7, 0x03, 0x83, 0xa7, 0xf3, 0x97, 0xae, 0x91, 0x02, 0x5a, 0x80, 0x86, 0x65, 0x10, 0xbb, 0x09, 0xcc, 0xde, 0xc9, 0x41, 0x81, 0x63, 0x18, 0x66, 0x6a, 0x50, 0x83, 0x45, 0x11, 0x8f, 0x21, 0x8c, 0xb7, 0xe2, 0x21, 0xaf, 0x14, 0x43, 0x1a, 0x46, 0x64, 0x34, 0xe2, 0xcc, 0xbc, 0x6d, 0x51, 0xa6, 0xe9, 0xa8, 0xe4, 0x46, 0xa8, 0xbc, 0x34, 0x16, 0x29, 0x2c, 0xf0, 0xfb, 0xbc, 0x6e, 0xa7, 0x23, 0xcf, 0x6a, 0xd0, 0x91, 0xf9, 0xe8, 0xb2, 0xe7, 0x63, 0x22, 0x33, 0xaa, 0xab, 0x4d, 0xd6, 0x67, 0xcf, 0x0b, 0x07, 0x4d, 0xac, 0xc3, 0xee, 0x4c, 0xd4, 0x10, 0x12, 0xba, 0xd8, 0x27, 0xd5, 0x59, 0xfe, 0xa6, 0x70, 0x5d, 0xed, 0xd8, 0xe6, 0xa1, 0xb6, 0x39, 0x73, 0x6d, 0x89, 0xdb, 0xe3, 0x01, 0x47, 0x65, 0xd4, 0xe9, 0xb7, 0x19, 0x78, 0x33, 0x9f, 0x35, 0xf7, 0x73, 0x6f, 0xb8, 0x7c, 0xc3, 0x9d, 0x8d, 0x9b, 0xa3, 0x19, 0x14, 0xfc, 0x75, 0x4a, 0x5f, 0x10, 0x29, 0x77, 0x07, 0x4b, 0x44, 0x51, 0x34, 0x12, 0x7c, 0x9c, 0xff, 0x13, 0x42, 0xc2, 0xef, 0x01, 0x1f, 0x1c, 0x72, 0xa1, 0x25, 0xd2, 0x20, 0xc6, 0x3c, 0xd0, 0x85, 0x56, 0x2b, 0x10, 0xc4, 0x8d, 0x64, 0x96, 0x8c, 0x06, 0x31, 0x1b, 0x61, 0x96, 0xdc, 0x28, 0x25, 0x92, 0x0e, 0x6b, 0x34, 0x78, 0x4c, 0x21, 0x15, 0xcf, 0x03, 0x55, 0x5d, 0xbc, 0xcb, 0x09, 0x14, 0xcb, 0xb3, 0x5a, 0xcc, 0x26, 0x83, 0x56, 0x23, 0x0a, 0x30, 0x16, 0xa7, 0xa7, 0xd3, 0x14, 0x44, 0x2d, 0x16, 0x03, 0xea, 0xcc, 0x02, 0x8e, 0x80, 0x8d, 0xfc, 0xb1, 0x0f, 0x8f, 0x7b, 0x02, 0xa9, 0xb7, 0x79, 0x0b, 0x3e, 0xf7, 0xc7, 0xca, 0xd9, 0x1f, 0x3f, 0xa1, 0x8f, 0xfb, 0xbf, 0xc0, 0xfc, 0x8e, 0xd3, 0xcf, 0x06, 0x1a, 0x99, 0xe2, 0xd9, 0x3f, 0x34, 0xc1, 0x14, 0x7e, 0xb6, 0xa2, 0x6b, 0xf6, 0xab, 0xa9, 0xfb, 0x9e, 0xc6, 0xd7, 0xa6, 0x9e, 0x75, 0x7a, 0xf9, 0x07, 0x9e, 0x4e, 0x35, 0x7e, 0x91, 0xbc, 0xd2, 0x87, 0x45, 0x5f, 0x81, 0xb5, 0xf4, 0x1c, 0xc0, 0xab, 0x43, 0x36, 0x14, 0x45, 0x5d, 0x52, 0x47, 0x01, 0xc6, 0x0c, 0x40, 0x0c, 0x94, 0x05, 0x56, 0x9a, 0x14, 0x31, 0xcb, 0x72, 0x63, 0x1a, 0x4c, 0x40, 0x15, 0x88, 0x53, 0x34, 0x06, 0xa0, 0x4f, 0xf1, 0x43, 0x0e, 0xbb, 0x5e, 0x8f, 0x51, 0x24, 0x64, 0x8f, 0x3a, 0xa2, 0x7a, 0x9b, 0x3e, 0x0f, 0x20, 0x15, 0x91, 0x0e, 0xeb, 0xb4, 0x00, 0x67, 0x18, 0x30, 0x4d, 0x96, 0x4e, 0xc0, 0x0d, 0x08, 0x4f, 0x92, 0x6f, 0x75, 0xca, 0xd2, 0x11, 0x39, 0x2b, 0x59, 0x40, 0x01, 0x2b, 0xf3, 0xec, 0xc8, 0xfe, 0xfe, 0xe2, 0x7b, 0xef, 0x4f, 0xbd, 0xf5, 0xc7, 0x07, 0x3f, 0x7d, 0xf7, 0x67, 0x6e, 0xfa, 0xd6, 0x91, 0xe6, 0xa2, 0xce, 0xa9, 0x2e, 0xdc, 0xd8, 0x75, 0xfc, 0x1b, 0x87, 0x66, 0x3f, 0x7a, 0x3c, 0xd2, 0x39, 0x96, 0xdc, 0x71, 0x38, 0x15, 0xe0, 0x86, 0x52, 0x25, 0xd7, 0xee, 0x3c, 0x3c, 0xf3, 0xc5, 0xda, 0x4d, 0xb7, 0xae, 0xa9, 0xdd, 0xb0, 0xbc, 0xd3, 0xd9, 0x96, 0xfa, 0xf3, 0xf8, 0x27, 0x77, 0xb7, 0x00, 0xc4, 0x18, 0xad, 0x3d, 0xff, 0x3e, 0x50, 0xe8, 0x29, 0x54, 0x4e, 0x24, 0x80, 0x0d, 0x56, 0x3e, 0x8b, 0x79, 0xc4, 0x0c, 0x20, 0x16, 0x10, 0xc6, 0xa2, 0x19, 0x80, 0x97, 0x27, 0x2a, 0x10, 0xf0, 0xcc, 0x8c, 0x21, 0x86, 0xd9, 0x31, 0x08, 0xb0, 0x53, 0x86, 0xda, 0x89, 0x87, 0x30, 0x8a, 0x86, 0xfd, 0xde, 0x7c, 0xa7, 0xd5, 0x2c, 0xf2, 0xa8, 0x1c, 0x97, 0x8b, 0x04, 0xc1, 0x35, 0x85, 0x94, 0x85, 0x2a, 0x30, 0xc0, 0x9f, 0x97, 0x0c, 0x25, 0x6a, 0x9c, 0x2e, 0x91, 0x5e, 0x60, 0x88, 0x1c, 0x70, 0xd8, 0x0b, 0x18, 0xc2, 0x45, 0xf5, 0x6d, 0x98, 0x7b, 0x20, 0xcf, 0xb8, 0x62, 0xcd, 0xec, 0xb3, 0x0f, 0xcc, 0x3e, 0xb7, 0x69, 0xcb, 0xd7, 0x30, 0xfb, 0xe0, 0xa7, 0xff, 0x7b, 0x4d, 0x8f, 0xe8, 0xb0, 0xda, 0x12, 0xcb, 0xaf, 0x1e, 0xdb, 0xf1, 0xec, 0xb1, 0xde, 0x81, 0xe3, 0xa7, 0xa7, 0xdb, 0x26, 0x97, 0x0f, 0x84, 0x34, 0x79, 0xb8, 0xd6, 0x34, 0xbe, 0x75, 0xdf, 0xce, 0xd7, 0xb1, 0xe9, 0x0b, 0x5f, 0xc0, 0xa6, 0xd7, 0x77, 0x4e, 0x2d, 0x9f, 0xd4, 0x9b, 0x22, 0x95, 0x91, 0xc1, 0xdb, 0xbf, 0x7f, 0xec, 0xfa, 0x37, 0x4f, 0x0d, 0x98, 0x0b, 0x2a, 0x03, 0xa2, 0x96, 0x54, 0xd5, 0x58, 0x9b, 0xba, 0x9f, 0xce, 0xa7, 0x0f, 0xfd, 0xa7, 0xbc, 0x84, 0xcc, 0xa5, 0x58, 0xe4, 0xfd, 0xb0, 0x18, 0x90, 0x19, 0xa8, 0xc2, 0x0e, 0x78, 0x73, 0xaf, 0xb0, 0xec, 0xc0, 0xe8, 0xe0, 0x69, 0x2d, 0x34, 0xac, 0x02, 0xee, 0xd2, 0x8a, 0x82, 0x76, 0x06, 0x01, 0xc3, 0x68, 0x35, 0xbb, 0x61, 0xe5, 0x50, 0x4a, 0xed, 0x20, 0xf2, 0x0e, 0x8d, 0x71, 0x40, 0xb9, 0x9d, 0x20, 0x0f, 0x18, 0x86, 0x4e, 0x7f, 0x06, 0x96, 0x9e, 0xfc, 0x80, 0xcb, 0xe8, 0xb7, 0x27, 0xb7, 0x9f, 0x54, 0x3f, 0xb7, 0x0b, 0xac, 0x5a, 0x10, 0xb6, 0x33, 0xf3, 0x76, 0x45, 0x2c, 0xab, 0xf6, 0x1c, 0x1d, 0x95, 0x4c, 0xbd, 0xdd, 0xb1, 0x60, 0x38, 0xe6, 0x0c, 0xd9, 0x63, 0x79, 0x44, 0xe2, 0x86, 0x4d, 0xd8, 0x8c, 0x0b, 0xb0, 0x2c, 0x54, 0x23, 0xd1, 0xfa, 0x24, 0x11, 0xb1, 0x32, 0xcb, 0x44, 0xeb, 0x2b, 0x98, 0x4b, 0x11, 0x01, 0x56, 0x33, 0x6f, 0x1e, 0xf9, 0x8f, 0x07, 0x2c, 0x13, 0x23, 0x81, 0xe6, 0xe5, 0xd5, 0xf1, 0xfe, 0xa2, 0x3c, 0xc1, 0x26, 0x58, 0x1c, 0x05, 0xf6, 0x68, 0x7c, 0x64, 0x71, 0xfc, 0x8e, 0x5f, 0x3e, 0x32, 0x7a, 0x29, 0xda, 0xe8, 0xf2, 0x8c, 0xdf, 0x5d, 0xbf, 0x57, 0xdb, 0x60, 0xef, 0x4d, 0x06, 0x6a, 0x43, 0x76, 0xb3, 0xa9, 0xd5, 0x06, 0x2b, 0xb9, 0x28, 0x3f, 0xe4, 0xd7, 0x1d, 0xd1, 0x1d, 0x78, 0x0d, 0xb3, 0xf7, 0x5f, 0x84, 0x64, 0x26, 0x22, 0xff, 0x60, 0xfd, 0xf0, 0xf7, 0x01, 0x37, 0x1a, 0x90, 0x1b, 0x15, 0x4a, 0x3e, 0xa4, 0xa0, 0x6a, 0x92, 0x4c, 0x1d, 0x8d, 0x81, 0x48, 0x98, 0x42, 0x43, 0x81, 0x58, 0x20, 0xc4, 0xe9, 0xf2, 0x4b, 0xb1, 0x9d, 0x11, 0x38, 0xe0, 0xfe, 0x40, 0x0d, 0xe7, 0xcc, 0x13, 0x6b, 0x2b, 0x18, 0xfa, 0xbd, 0x8d, 0xe1, 0xef, 0xdb, 0xf2, 0x6a, 0xea, 0xaf, 0x0f, 0xcd, 0xfe, 0x8c, 0xf1, 0x3d, 0x84, 0x85, 0x57, 0xb7, 0xbc, 0xdb, 0x73, 0xec, 0xc5, 0xbd, 0xa9, 0xaf, 0xe1, 0x9e, 0xbd, 0x2f, 0x1e, 0xeb, 0x61, 0x7e, 0xf0, 0xf9, 0xd4, 0x87, 0x5f, 0xdf, 0x0a, 0x16, 0xc4, 0xd7, 0x53, 0x7f, 0x7e, 0xe8, 0xba, 0xef, 0xdd, 0x3e, 0xf0, 0xd1, 0xab, 0x03, 0xb7, 0x7f, 0x57, 0xd5, 0x01, 0x5f, 0x84, 0x67, 0x5b, 0x50, 0x00, 0xb5, 0x4b, 0x2d, 0x22, 0x66, 0x04, 0x4c, 0x68, 0xb1, 0x11, 0x00, 0xe0, 0xc6, 0xb4, 0x74, 0xd1, 0x6a, 0xb2, 0x16, 0x6d, 0x9e, 0x15, 0xa3, 0x22, 0x22, 0x40, 0x3d, 0xd6, 0x40, 0x5e, 0xc0, 0x6c, 0x32, 0x1a, 0xf4, 0x5a, 0x64, 0xc1, 0x16, 0x5d, 0x7a, 0xd1, 0xca, 0x20, 0x39, 0x02, 0x56, 0x4c, 0x40, 0x74, 0x28, 0xf0, 0x06, 0xeb, 0x12, 0x56, 0xe6, 0x27, 0x04, 0x18, 0x05, 0x30, 0x5f, 0xea, 0x67, 0xb8, 0x87, 0x82, 0xaa, 0x00, 0xfe, 0xda, 0x6b, 0x4f, 0x01, 0x4c, 0xc7, 0x08, 0x74, 0xa9, 0xe2, 0x23, 0x29, 0x27, 0xf7, 0x3e, 0x85, 0x96, 0x42, 0xce, 0xdc, 0x77, 0x84, 0x39, 0x49, 0x0b, 0xc7, 0x10, 0x59, 0x53, 0x06, 0xf0, 0xea, 0x89, 0xc6, 0x02, 0xae, 0xe1, 0x78, 0x96, 0xdb, 0x48, 0x30, 0x36, 0x4a, 0x31, 0x26, 0x60, 0x90, 0x7f, 0xa3, 0x32, 0xca, 0xe0, 0xff, 0x7a, 0xa4, 0xb7, 0x92, 0x7f, 0xa2, 0xce, 0x5d, 0x1a, 0x16, 0x65, 0xb0, 0xc8, 0x1f, 0x57, 0x86, 0xcf, 0xdd, 0xc8, 0x5e, 0x3d, 0x3b, 0xce, 0x14, 0xcd, 0xfe, 0xf4, 0xe9, 0xa7, 0xd3, 0xc2, 0x0c, 0xc6, 0x4f, 0x3d, 0xc1, 0xb5, 0x50, 0xbb, 0xe0, 0x0a, 0x99, 0xbd, 0xe3, 0x44, 0xff, 0x0b, 0x1c, 0xbf, 0x11, 0x88, 0xc1, 0x8c, 0xc2, 0x93, 0x80, 0x2c, 0x82, 0x80, 0x47, 0x45, 0x22, 0x6e, 0x07, 0xb5, 0x98, 0x30, 0x2a, 0xad, 0xb3, 0x0d, 0x7c, 0x5d, 0x85, 0xe0, 0x37, 0xd6, 0x6a, 0x66, 0x2e, 0xda, 0x07, 0x65, 0xba, 0x00, 0x3f, 0x87, 0x8a, 0xa3, 0x14, 0x44, 0xab, 0xa3, 0x98, 0xf0, 0x73, 0x46, 0xff, 0x64, 0xa0, 0x0d, 0x88, 0x39, 0xea, 0x87, 0x6b, 0x51, 0xb4, 0xce, 0xec, 0xb8, 0x3a, 0x05, 0xe6, 0x51, 0x3c, 0x96, 0xa3, 0x72, 0x52, 0x3f, 0x56, 0xf4, 0x8c, 0x3a, 0xb5, 0xa7, 0x73, 0x94, 0x8c, 0x8c, 0xc7, 0x52, 0x98, 0xa7, 0x16, 0xc5, 0xa5, 0x08, 0xc1, 0x2c, 0x08, 0xeb, 0x8d, 0x3c, 0x90, 0x9b, 0x1d, 0x05, 0xa0, 0x01, 0x5e, 0x50, 0x9e, 0x0c, 0x41, 0xa1, 0x16, 0x69, 0x09, 0x74, 0x02, 0xe1, 0x3d, 0x0a, 0x4d, 0x10, 0xfe, 0x8f, 0x6f, 0x62, 0xa4, 0xd9, 0xd7, 0xde, 0x61, 0xaf, 0xe6, 0x7e, 0x97, 0x2a, 0x7c, 0x7a, 0xf6, 0x73, 0xf0, 0x14, 0x18, 0x73, 0x14, 0x64, 0x69, 0x37, 0xc8, 0x9e, 0x6a, 0xd4, 0x2a, 0x35, 0xd9, 0x40, 0x05, 0x80, 0xba, 0xe1, 0x30, 0x86, 0xf1, 0x06, 0x80, 0x2e, 0xc4, 0xde, 0xe2, 0x26, 0x55, 0xe6, 0xde, 0x91, 0x66, 0xee, 0x9d, 0x94, 0x52, 0xd5, 0xa8, 0x3a, 0x1c, 0x2b, 0x8b, 0x05, 0x09, 0xa5, 0xb0, 0x95, 0x20, 0x21, 0x14, 0x51, 0x17, 0x6f, 0x01, 0x43, 0x56, 0x6f, 0xb4, 0x82, 0xcd, 0x59, 0xbc, 0x56, 0xc0, 0x53, 0x2b, 0xe6, 0xbb, 0x77, 0xfd, 0xe9, 0xae, 0x67, 0x52, 0xbf, 0x78, 0x63, 0xc7, 0x96, 0xd7, 0xb0, 0xf5, 0x89, 0x55, 0xf7, 0x1c, 0xdd, 0x51, 0xd1, 0x6a, 0xaa, 0xf0, 0x7a, 0xda, 0x57, 0x1f, 0x18, 0xbe, 0xf2, 0x1b, 0x37, 0xf6, 0x0d, 0xdd, 0xfa, 0xed, 0xc3, 0x55, 0xcb, 0x87, 0x7a, 0x83, 0x6f, 0xd9, 0xdd, 0xcc, 0x2b, 0xb3, 0x3f, 0x5c, 0xb2, 0x98, 0x8a, 0xd1, 0x87, 0xb1, 0xf9, 0x5b, 0x3b, 0x3c, 0x55, 0x8b, 0x4a, 0x36, 0xb8, 0x0c, 0x56, 0x73, 0xac, 0x38, 0x7f, 0xf1, 0x9d, 0xdf, 0x3b, 0x7a, 0xe2, 0x9f, 0xef, 0x1a, 0xd2, 0xdb, 0x0b, 0xf2, 0x70, 0x79, 0x7e, 0xde, 0x21, 0xa4, 0xf2, 0x1b, 0xdf, 0x4f, 0xf9, 0x2d, 0x2a, 0x85, 0x80, 0xac, 0xc4, 0x46, 0xda, 0x28, 0xf0, 0x0c, 0xc1, 0x11, 0x98, 0x49, 0xf3, 0xf0, 0x19, 0x26, 0x18, 0x82, 0xbf, 0x04, 0xfc, 0x9f, 0xef, 0x7f, 0x7b, 0xd6, 0xf6, 0xce, 0x3b, 0xcc, 0x6f, 0x89, 0x9a, 0x67, 0x6e, 0x9a, 0x3d, 0xc4, 0x3f, 0x30, 0x7b, 0x23, 0x73, 0x35, 0xac, 0x3a, 0x30, 0x45, 0x78, 0xa2, 0xe3, 0x59, 0xd0, 0xf1, 0x49, 0x29, 0xa1, 0xd7, 0x32, 0xa2, 0x00, 0x12, 0x19, 0x8b, 0x68, 0x23, 0x79, 0x06, 0x8b, 0xb9, 0x8d, 0x44, 0xad, 0x4f, 0x0e, 0xf2, 0x20, 0xa2, 0xa7, 0xd8, 0x21, 0x45, 0x9f, 0x2b, 0xff, 0x2c, 0x06, 0xc2, 0x2a, 0x40, 0x8d, 0x70, 0x50, 0x79, 0x1a, 0x59, 0x5f, 0xe4, 0x89, 0xf0, 0x7f, 0xee, 0xc8, 0xac, 0x8d, 0xf9, 0x2d, 0xbe, 0xe2, 0x21, 0xf2, 0xec, 0x87, 0x1e, 0x82, 0xa7, 0xbf, 0xfd, 0x10, 0x67, 0xfc, 0xf5, 0xaf, 0x3f, 0xfa, 0x03, 0x81, 0x80, 0x33, 0x5e, 0x8b, 0x2d, 0xa9, 0xf7, 0xc9, 0x9f, 0x32, 0x3f, 0x7c, 0x13, 0x85, 0xc3, 0x26, 0x59, 0x64, 0xa1, 0x43, 0x27, 0x65, 0xb5, 0x30, 0x3a, 0x67, 0x69, 0x00, 0xa6, 0x81, 0x6f, 0x7a, 0xe7, 0x1d, 0x62, 0xa8, 0x61, 0x34, 0x99, 0x7a, 0x02, 0xbf, 0x05, 0x5f, 0xfd, 0x68, 0x48, 0x5e, 0x1b, 0x2e, 0xca, 0xda, 0xd4, 0xf0, 0x18, 0x24, 0xbc, 0x93, 0x59, 0x09, 0x05, 0x60, 0xc3, 0x71, 0x60, 0xb7, 0xcc, 0x64, 0xb5, 0x40, 0x99, 0x06, 0xa3, 0x92, 0x36, 0x18, 0x25, 0x2c, 0x2f, 0xe8, 0x3c, 0x59, 0x2c, 0x4f, 0x90, 0x96, 0xc3, 0xea, 0xf8, 0x2d, 0x85, 0xd5, 0x9b, 0xdf, 0x29, 0x5a, 0x88, 0xbf, 0xe7, 0xda, 0x4f, 0x18, 0xb9, 0xce, 0xbf, 0xcf, 0x3c, 0x03, 0x70, 0x3a, 0xd0, 0xb2, 0xc1, 0xd3, 0xbe, 0xa5, 0x6b, 0x9e, 0x17, 0x31, 0xcc, 0x6c, 0xc0, 0x4b, 0x3e, 0xc1, 0x2e, 0x19, 0xa5, 0x17, 0x25, 0x2b, 0x92, 0x69, 0x08, 0x0c, 0xaa, 0xb0, 0xa1, 0x57, 0xca, 0x47, 0x98, 0x05, 0x0b, 0x80, 0x99, 0x51, 0xd7, 0x80, 0x7a, 0x6b, 0xf4, 0x05, 0x47, 0xb1, 0x5d, 0x11, 0xc0, 0xd4, 0x9a, 0xa7, 0xba, 0x83, 0x82, 0x59, 0x67, 0x4d, 0x30, 0xcf, 0xb4, 0xec, 0x7f, 0x72, 0xcf, 0xd4, 0x76, 0x03, 0xe3, 0x72, 0x59, 0x9b, 0xab, 0xa4, 0xc9, 0xae, 0xe0, 0xdb, 0xf8, 0x7f, 0xae, 0x7a, 0xed, 0x86, 0x5e, 0xdf, 0xd1, 0x3c, 0x97, 0x35, 0x32, 0x72, 0x74, 0x0d, 0xfb, 0x74, 0x86, 0x9f, 0xda, 0x00, 0xb6, 0x00, 0xe1, 0x27, 0x23, 0x88, 0x5a, 0x5a, 0xd1, 0x9f, 0x98, 0xad, 0x93, 0x3c, 0x26, 0xe8, 0xe7, 0x30, 0x5d, 0x79, 0x56, 0x6b, 0x3c, 0x1a, 0x2e, 0xa2, 0xe8, 0x01, 0x6d, 0x56, 0x8f, 0xdb, 0x70, 0x33, 0x06, 0x44, 0x89, 0x4e, 0x17, 0x55, 0x63, 0x51, 0x5c, 0xc1, 0x52, 0x4e, 0x4b, 0xe0, 0x1b, 0x7b, 0x22, 0x95, 0x36, 0xfc, 0x8f, 0xce, 0x8a, 0xe2, 0x17, 0x66, 0x7f, 0x03, 0xb6, 0x54, 0x7d, 0x9c, 0x89, 0xbf, 0x6c, 0xf7, 0x3b, 0xac, 0x62, 0x2a, 0xaa, 0xb3, 0x7b, 0x8a, 0x5d, 0xab, 0xdf, 0x7e, 0x9b, 0xad, 0xb4, 0xb9, 0x18, 0x9b, 0xd7, 0x31, 0xfb, 0x80, 0xa7, 0x48, 0x6f, 0x6b, 0x9a, 0x5d, 0x2b, 0xe8, 0x2d, 0x7a, 0xa6, 0xca, 0x98, 0xa7, 0xe7, 0x67, 0xaf, 0xa0, 0x76, 0x10, 0xc0, 0xc5, 0xfe, 0x02, 0xbe, 0xf1, 0x54, 0x03, 0x11, 0x40, 0x88, 0xca, 0x46, 0x6b, 0xd3, 0xa2, 0x94, 0x47, 0xbc, 0xd5, 0xca, 0x01, 0x4f, 0x84, 0x1d, 0x84, 0xcb, 0xd8, 0x5f, 0x9c, 0xfb, 0xee, 0xdb, 0xcf, 0x81, 0xd5, 0xf7, 0x91, 0x8b, 0xce, 0xcb, 0x09, 0xf2, 0x84, 0x83, 0xfe, 0x56, 0x54, 0x22, 0x45, 0x2d, 0x06, 0xbd, 0x86, 0x63, 0x05, 0xc0, 0xed, 0xc0, 0x7c, 0x43, 0x59, 0x91, 0xd5, 0x95, 0x1e, 0x4a, 0xac, 0xab, 0x6d, 0xc7, 0x30, 0x60, 0xd8, 0x61, 0x17, 0x79, 0x2b, 0xc7, 0x9d, 0xfb, 0xee, 0x4b, 0x9e, 0xcf, 0xbe, 0x8d, 0xaf, 0xf6, 0xa7, 0x1e, 0x7e, 0xee, 0x39, 0xa6, 0x78, 0xdd, 0xbd, 0x7e, 0xe3, 0x72, 0xe6, 0xdb, 0xb3, 0x9f, 0x79, 0xed, 0xce, 0x90, 0xfe, 0x85, 0x54, 0x0d, 0x3a, 0x7f, 0x5e, 0x81, 0xf5, 0x0d, 0x26, 0x82, 0x48, 0xb5, 0x71, 0x11, 0x3d, 0x4b, 0x8e, 0xda, 0xc9, 0x73, 0x10, 0x3e, 0x09, 0x30, 0x14, 0xa1, 0x72, 0xa9, 0xc4, 0x63, 0x24, 0x45, 0x3e, 0xe1, 0xf9, 0x2c, 0x02, 0x8f, 0x62, 0x86, 0xe7, 0xe6, 0x2c, 0xd9, 0x22, 0x54, 0x18, 0x70, 0x24, 0x15, 0xb9, 0x16, 0xb4, 0x02, 0xf7, 0x61, 0xa1, 0x14, 0x17, 0x47, 0xb1, 0xd3, 0x41, 0xd0, 0x0a, 0x16, 0x03, 0x98, 0x15, 0x2e, 0x8a, 0xf9, 0x84, 0xf0, 0xc9, 0xaf, 0xb1, 0x4c, 0xaa, 0xd8, 0x5d, 0x5b, 0x50, 0x9b, 0x9f, 0x72, 0x0b, 0xaf, 0x7c, 0xd6, 0x57, 0x68, 0xc2, 0x3f, 0x35, 0xe5, 0xeb, 0x1d, 0x3e, 0xfc, 0x2f, 0xc6, 0x42, 0x1f, 0x61, 0x42, 0x36, 0xe5, 0xaa, 0xf5, 0x9e, 0xbb, 0xa7, 0xba, 0x9a, 0x9d, 0x69, 0xa8, 0x3e, 0xc7, 0xf0, 0x0f, 0xe8, 0xdd, 0xe7, 0x1e, 0xf1, 0x34, 0xf9, 0x9b, 0xeb, 0xd9, 0x31, 0xb7, 0x3e, 0x47, 0x96, 0x38, 0x90, 0x5f, 0xf2, 0x70, 0x2c, 0x43, 0x96, 0x06, 0x4e, 0x03, 0xe4, 0x40, 0x8e, 0x80, 0x2b, 0xc0, 0xeb, 0x5c, 0x04, 0x1e, 0x30, 0x65, 0x88, 0x55, 0xa3, 0x42, 0x52, 0x81, 0xeb, 0x40, 0x96, 0xec, 0x74, 0x07, 0xac, 0xe7, 0xde, 0x66, 0xd8, 0x57, 0x76, 0xfb, 0x7c, 0x56, 0xb6, 0x9c, 0x65, 0xc9, 0x73, 0xf3, 0xec, 0x9c, 0xdb, 0x5b, 0xeb, 0x3a, 0x07, 0x2b, 0xc1, 0xe8, 0xfa, 0xe8, 0xe7, 0xfe, 0xfa, 0x7c, 0xf6, 0xff, 0x50, 0x3d, 0xf6, 0x19, 0xfa, 0xac, 0x10, 0xda, 0x37, 0x78, 0xba, 0x18, 0xd8, 0x5d, 0x67, 0x07, 0x61, 0xac, 0x25, 0xb5, 0x4f, 0xc1, 0x04, 0x54, 0x7f, 0x30, 0x0c, 0x59, 0x0d, 0xe4, 0x76, 0x3e, 0xc8, 0x34, 0xa2, 0xeb, 0x81, 0xf1, 0xa7, 0x32, 0x8b, 0x62, 0x86, 0x2c, 0x8a, 0x42, 0x75, 0x51, 0x64, 0x37, 0x51, 0xd7, 0xc6, 0x0c, 0x55, 0x62, 0xf0, 0x25, 0x84, 0x42, 0x81, 0xfc, 0x40, 0x38, 0x2c, 0x8b, 0x40, 0xd5, 0x1c, 0x53, 0x56, 0x49, 0xb2, 0x15, 0x5f, 0x38, 0x9d, 0xc8, 0xc8, 0xb5, 0xa3, 0xd5, 0x4b, 0xcb, 0x1d, 0xa2, 0xcb, 0x64, 0x8a, 0x85, 0x47, 0x56, 0x9e, 0xfb, 0xb7, 0xdc, 0xa9, 0x1d, 0x58, 0x7e, 0x72, 0x32, 0x99, 0x67, 0x5b, 0x95, 0x6f, 0x36, 0x4d, 0x6d, 0xa9, 0xe1, 0xec, 0x17, 0xcc, 0x92, 0xd8, 0x2f, 0xa3, 0xe7, 0x7f, 0xcf, 0xff, 0x1a, 0x74, 0x0e, 0xf1, 0x3c, 0xb2, 0x6c, 0xa7, 0x1c, 0xf5, 0x12, 0x8b, 0xc5, 0xb2, 0x6c, 0xa7, 0x2c, 0xc5, 0xa2, 0x9a, 0x50, 0x59, 0x6a, 0x85, 0xff, 0x35, 0x35, 0x47, 0x3e, 0x97, 0xfa, 0xcb, 0x2b, 0x5b, 0xb7, 0xbe, 0x82, 0xc5, 0xcf, 0x65, 0xac, 0xa9, 0xbd, 0x2f, 0x1e, 0xef, 0xe9, 0x39, 0x4e, 0x3e, 0x65, 0x8b, 0xea, 0x0f, 0x67, 0x76, 0xee, 0x3c, 0x83, 0x8d, 0x9f, 0x7f, 0x08, 0x9b, 0xce, 0xec, 0xda, 0x75, 0x26, 0xf5, 0xc1, 0x43, 0xd7, 0xbd, 0x79, 0x6a, 0x68, 0xe8, 0xd4, 0x9b, 0xd7, 0x1d, 0xfb, 0xde, 0xa9, 0xc1, 0xc1, 0x53, 0xdf, 0x23, 0xbe, 0xd1, 0xe8, 0xf9, 0x7b, 0x00, 0xbe, 0x1f, 0x83, 0x86, 0x70, 0xa1, 0x66, 0xb4, 0x45, 0xb2, 0xd4, 0x82, 0x5d, 0xe5, 0xd2, 0x83, 0xa4, 0x8f, 0x07, 0x01, 0x85, 0x2c, 0x38, 0xba, 0x45, 0x80, 0xfe, 0x42, 0x10, 0x01, 0xec, 0x7e, 0x30, 0xb1, 0x36, 0x0f, 0x52, 0xc3, 0x0b, 0x16, 0xd3, 0x0e, 0x22, 0x89, 0xa9, 0xd3, 0xb1, 0x93, 0xa1, 0x54, 0xe0, 0x39, 0x7e, 0x9f, 0xdc, 0x6c, 0xbe, 0x16, 0x20, 0x52, 0xa3, 0xb1, 0x70, 0x65, 0x09, 0x15, 0xa9, 0xe1, 0x02, 0x9c, 0x90, 0x3d, 0xa8, 0x4a, 0x40, 0xb6, 0x3c, 0x3f, 0xc0, 0xbb, 0x6a, 0x8e, 0x01, 0x65, 0x44, 0xe2, 0xfe, 0xd9, 0x22, 0x19, 0xfb, 0xb8, 0xbe, 0x8d, 0x6d, 0xc6, 0x4c, 0x20, 0x79, 0x6c, 0x80, 0xcc, 0xaf, 0xa5, 0x61, 0x63, 0xd3, 0xed, 0xcb, 0xc9, 0x8c, 0x7b, 0x3b, 0xd9, 0xd1, 0x2d, 0x33, 0x14, 0x29, 0xdb, 0x5f, 0x1e, 0x5e, 0xe7, 0xea, 0x28, 0x5c, 0xf9, 0xda, 0xcc, 0x35, 0x14, 0x35, 0xfb, 0xdf, 0x58, 0x3e, 0xa8, 0xcf, 0xb3, 0xe7, 0xe7, 0xed, 0xfe, 0x65, 0x61, 0x11, 0xcc, 0xf7, 0xd8, 0x55, 0xaf, 0xf5, 0x8c, 0x46, 0xe2, 0x04, 0x03, 0xd7, 0x7f, 0xaf, 0x97, 0x7d, 0x71, 0x32, 0xf5, 0xab, 0xc7, 0x29, 0x9a, 0xaa, 0xca, 0xaf, 0x2b, 0xf2, 0x62, 0x61, 0x12, 0x17, 0x3d, 0x4b, 0x91, 0xd5, 0x50, 0xbb, 0xc5, 0xec, 0x30, 0x08, 0x84, 0x7e, 0xe3, 0xe7, 0x3f, 0xd0, 0x94, 0x52, 0xfa, 0x55, 0xe1, 0x82, 0xc1, 0xd3, 0x65, 0x84, 0x53, 0xa3, 0x58, 0xc4, 0x2e, 0x0c, 0x6e, 0x3d, 0xe1, 0x54, 0xf8, 0x21, 0xc8, 0x3f, 0x80, 0x53, 0x0b, 0xe0, 0x76, 0x10, 0x56, 0xbc, 0x06, 0x89, 0x9a, 0x8d, 0x02, 0x26, 0x78, 0x20, 0x7a, 0x67, 0xc7, 0xa0, 0x5e, 0xab, 0x63, 0x09, 0x2e, 0x58, 0x59, 0x7a, 0x7a, 0x65, 0xa6, 0x2e, 0xcf, 0x6d, 0x4a, 0x51, 0x46, 0xbe, 0xf2, 0xa3, 0x34, 0x44, 0x70, 0x61, 0x27, 0xf2, 0xf8, 0xc6, 0xcb, 0xea, 0x44, 0xac, 0xba, 0x29, 0xe5, 0x07, 0x3b, 0x06, 0xcc, 0xb6, 0x87, 0xa5, 0x8e, 0x8d, 0xd2, 0x37, 0x8b, 0x44, 0x82, 0x70, 0xe9, 0x9e, 0xa4, 0xb8, 0x45, 0xa1, 0xdd, 0x8e, 0x91, 0xbd, 0xca, 0x5e, 0x55, 0x59, 0x51, 0x5e, 0x56, 0x5a, 0x12, 0x8f, 0x85, 0x83, 0x24, 0x48, 0x01, 0xe6, 0xb5, 0x0d, 0xdb, 0x0c, 0xd9, 0xe6, 0xb5, 0xc2, 0xae, 0x60, 0xd0, 0xd6, 0x65, 0x5c, 0x4d, 0xba, 0xd2, 0x5c, 0x75, 0xaa, 0xcd, 0xc8, 0xfc, 0xcf, 0xf4, 0x23, 0x07, 0xda, 0xda, 0x0e, 0x3c, 0x32, 0x3d, 0xfd, 0xd8, 0x81, 0xf6, 0xf6, 0x03, 0x8f, 0xf1, 0x0f, 0xe2, 0x73, 0x9f, 0x5b, 0xf9, 0x97, 0x07, 0xef, 0xfc, 0xcd, 0xe3, 0x13, 0x13, 0x8f, 0xff, 0xe6, 0xce, 0x07, 0xff, 0xb2, 0x92, 0xdd, 0x28, 0x5b, 0x8f, 0x8f, 0x2c, 0xbe, 0xe3, 0xfb, 0xc7, 0xae, 0xfb, 0xc1, 0x9d, 0x4b, 0x96, 0xdc, 0xf9, 0x83, 0xeb, 0x8e, 0x7d, 0xff, 0x8e, 0xc5, 0xda, 0xa7, 0x7f, 0x3a, 0x73, 0x14, 0xcc, 0x24, 0x85, 0xb7, 0xaf, 0xdd, 0xfb, 0x53, 0xc5, 0x3e, 0xa6, 0xbe, 0x0a, 0x77, 0x8c, 0xfa, 0xfa, 0x6e, 0x54, 0x23, 0x55, 0x92, 0x68, 0x11, 0xb8, 0xca, 0xec, 0x24, 0x4c, 0x98, 0x19, 0x13, 0x30, 0x35, 0x75, 0x39, 0x6e, 0x8a, 0x1b, 0x32, 0x80, 0x6f, 0x9f, 0xef, 0x72, 0xda, 0xf5, 0x6e, 0x83, 0x5b, 0xf6, 0xeb, 0x65, 0xf7, 0x98, 0x72, 0x25, 0x48, 0x08, 0x9b, 0xe2, 0xcf, 0x53, 0xe9, 0xcb, 0x4c, 0x8d, 0x1c, 0x5e, 0x5e, 0xf2, 0xf0, 0x63, 0x3f, 0xf9, 0xc9, 0xc9, 0x37, 0x6f, 0xec, 0xe8, 0xb9, 0xf9, 0x7b, 0x27, 0x7e, 0xf2, 0x36, 0xf3, 0x72, 0xc9, 0xe2, 0x1d, 0x1d, 0xd7, 0xdf, 0x37, 0x7b, 0x96, 0xf9, 0x0f, 0xe9, 0x8a, 0xc7, 0x36, 0x6f, 0x7e, 0xfc, 0xea, 0x45, 0xb3, 0xbf, 0xa3, 0x21, 0xa3, 0x1c, 0x38, 0xbc, 0xa8, 0x5e, 0xaa, 0x15, 0xb1, 0x12, 0x6c, 0x10, 0xd2, 0x00, 0x11, 0x94, 0x67, 0x40, 0xf1, 0xb8, 0x29, 0x30, 0x5e, 0x83, 0x57, 0x06, 0x46, 0xb3, 0x30, 0x30, 0x89, 0x79, 0xc1, 0x79, 0x1b, 0xbf, 0x50, 0x32, 0xb4, 0xa3, 0xe3, 0xc4, 0x27, 0x66, 0xff, 0x01, 0xe0, 0xb9, 0x92, 0xc0, 0x73, 0xa8, 0x2b, 0xf5, 0x07, 0xfe, 0x81, 0xd4, 0x2c, 0x81, 0x67, 0x6d, 0xea, 0x47, 0x42, 0x35, 0xe5, 0xe3, 0x4a, 0x74, 0xe0, 0x85, 0x4a, 0x37, 0x43, 0x34, 0xbb, 0xec, 0x58, 0x7b, 0xc0, 0x9d, 0x62, 0x72, 0x17, 0x2d, 0xa5, 0xfb, 0x4e, 0x96, 0xb0, 0x9c, 0x83, 0xb6, 0xb8, 0xf0, 0x26, 0x91, 0xb0, 0x5b, 0xc9, 0xc2, 0xbf, 0x48, 0xf7, 0xd1, 0xd1, 0x17, 0x63, 0xa1, 0x50, 0x2c, 0xc4, 0x13, 0x99, 0xab, 0x08, 0x30, 0x95, 0x43, 0xe4, 0xc0, 0x9c, 0x30, 0x47, 0xdc, 0xd1, 0x08, 0x0a, 0x4e, 0xa8, 0x62, 0x4c, 0x15, 0x6b, 0x5c, 0xea, 0x74, 0xb4, 0xc5, 0xf3, 0xc1, 0xee, 0x63, 0x73, 0xe4, 0x5e, 0x7c, 0x68, 0x0f, 0x88, 0x3a, 0x45, 0x9c, 0x1d, 0x53, 0xc4, 0xdb, 0x47, 0xa7, 0x40, 0xed, 0x69, 0x42, 0x73, 0x64, 0xdf, 0xd4, 0xf3, 0x0f, 0x5c, 0x57, 0x95, 0xa1, 0xcd, 0xcf, 0x29, 0x6d, 0x82, 0xc4, 0xb6, 0x0d, 0x50, 0x2b, 0x47, 0x50, 0xa2, 0x73, 0x84, 0x53, 0xa8, 0x33, 0x99, 0x36, 0x75, 0x80, 0x4f, 0x88, 0x71, 0x6b, 0x35, 0xea, 0xe7, 0xe3, 0x13, 0x98, 0x07, 0x18, 0x3b, 0x58, 0x36, 0xab, 0xd5, 0x18, 0x90, 0xd3, 0xc5, 0xf4, 0x2d, 0xbb, 0x66, 0x45, 0xc9, 0xc3, 0x0f, 0xff, 0x23, 0xbb, 0xe1, 0x1b, 0x8d, 0x9e, 0xd4, 0x83, 0xd6, 0x58, 0xec, 0xd6, 0xb7, 0xf1, 0x67, 0x6f, 0xfd, 0xc7, 0xeb, 0xdb, 0x2b, 0x46, 0xaf, 0x1b, 0x89, 0xf9, 0x99, 0x5f, 0xc6, 0xfa, 0x37, 0xb7, 0x1e, 0x39, 0x3a, 0xfb, 0x87, 0xd9, 0xfc, 0x72, 0xfc, 0x81, 0xdb, 0x31, 0x0b, 0xc6, 0x4a, 0xe3, 0xb6, 0x7b, 0xc7, 0x07, 0x4f, 0x6c, 0x1b, 0xb2, 0xe7, 0x97, 0x02, 0xbb, 0xb4, 0x9e, 0x7f, 0x9f, 0xfd, 0x15, 0xd0, 0xac, 0x1d, 0xb5, 0x49, 0xcd, 0xed, 0x00, 0x56, 0x11, 0x06, 0xad, 0x4c, 0xde, 0xb2, 0x04, 0x16, 0x03, 0x0b, 0x36, 0x20, 0x07, 0x5a, 0x72, 0xb7, 0x0a, 0xef, 0x8e, 0x41, 0x35, 0x08, 0x04, 0xd2, 0xb6, 0xb5, 0xb9, 0x04, 0xd6, 0xa7, 0xcf, 0x13, 0x04, 0x71, 0xeb, 0x94, 0x2d, 0xd8, 0xf4, 0xc2, 0x14, 0xd4, 0x08, 0xaf, 0xac, 0xfe, 0xd2, 0x64, 0x11, 0xea, 0x68, 0x04, 0xd8, 0xe9, 0x62, 0x3f, 0xa5, 0x73, 0x5a, 0xf3, 0x2a, 0xba, 0x37, 0x2c, 0x6a, 0x5f, 0x2f, 0x05, 0x9a, 0x37, 0x5c, 0x7d, 0xdd, 0xd5, 0x1b, 0x9a, 0x3b, 0x8e, 0x3c, 0xbf, 0xbf, 0xfb, 0xf8, 0x81, 0xcd, 0xb1, 0x56, 0xad, 0xdb, 0xe2, 0xa8, 0x1f, 0xd9, 0x3b, 0xd2, 0xb9, 0xb5, 0x37, 0xd2, 0x44, 0x6f, 0x36, 0xb5, 0xec, 0xff, 0xd2, 0xce, 0x43, 0xdf, 0x1b, 0xe4, 0x9e, 0xb5, 0x5a, 0x02, 0xf1, 0x40, 0xa0, 0x71, 0x71, 0x79, 0xd3, 0x50, 0x32, 0x56, 0xda, 0xb0, 0xf2, 0xf0, 0xe8, 0xfa, 0xc7, 0x8e, 0xf4, 0x7a, 0xab, 0xbb, 0x4b, 0xa7, 0x0d, 0x96, 0xb2, 0xba, 0xb2, 0x60, 0xc7, 0x78, 0x43, 0xfb, 0xd2, 0x64, 0x24, 0x9e, 0x5c, 0x7d, 0xfd, 0x86, 0xc5, 0x77, 0xee, 0x59, 0xb4, 0xb8, 0x4f, 0xb6, 0x3b, 0x5b, 0x53, 0xf7, 0xd3, 0xb9, 0xae, 0x41, 0x2f, 0xca, 0xb6, 0xbb, 0x6d, 0x0d, 0xc6, 0x9a, 0x46, 0x10, 0xae, 0x75, 0x3e, 0x86, 0xe5, 0xa3, 0x98, 0x61, 0x39, 0x10, 0xb7, 0x17, 0x5c, 0xe5, 0x38, 0x22, 0x77, 0x49, 0x87, 0x4a, 0x18, 0x06, 0x88, 0x28, 0xcc, 0x20, 0x41, 0xc4, 0x82, 0x48, 0xf0, 0xa2, 0x19, 0x03, 0x5f, 0x76, 0xc7, 0xa0, 0x1a, 0xb6, 0xd9, 0x03, 0x14, 0x05, 0x87, 0x97, 0xa0, 0x68, 0x86, 0x70, 0x6e, 0x72, 0x6e, 0x0f, 0x70, 0x00, 0xc1, 0x5d, 0x9f, 0x99, 0xaf, 0xa7, 0x8a, 0xdb, 0x19, 0xa2, 0xc9, 0x6c, 0xab, 0x56, 0x2c, 0xea, 0x68, 0x6b, 0xa9, 0x4f, 0x56, 0x55, 0x94, 0xc4, 0xc3, 0xc1, 0x60, 0xb1, 0x76, 0x7e, 0x24, 0x5f, 0x24, 0xf2, 0x83, 0x3f, 0x16, 0xf2, 0xeb, 0xa6, 0xee, 0x5d, 0x67, 0x9d, 0x37, 0x04, 0x14, 0x3b, 0xf1, 0xe3, 0x25, 0x1f, 0x93, 0x22, 0x2b, 0x3f, 0x75, 0x70, 0xa9, 0xb1, 0x6d, 0x9e, 0x70, 0xd0, 0x61, 0x7e, 0x6a, 0xd9, 0xc2, 0x64, 0xc2, 0xa8, 0x80, 0x24, 0xa7, 0xd2, 0x38, 0x38, 0x78, 0x22, 0xc4, 0x52, 0x55, 0x7d, 0x71, 0x1a, 0xd8, 0x25, 0x6e, 0x1f, 0xb5, 0x62, 0xa8, 0x0b, 0x5e, 0x17, 0xe0, 0x4e, 0xa4, 0x1a, 0xde, 0x4a, 0x25, 0xb9, 0x69, 0xee, 0x57, 0x1f, 0x39, 0xb9, 0x5f, 0x3d, 0x2b, 0xd3, 0x99, 0xec, 0x6b, 0xdc, 0x0d, 0x63, 0x98, 0x49, 0x7c, 0x84, 0x03, 0x36, 0xc6, 0xd4, 0xb3, 0xd7, 0x88, 0x0c, 0xcf, 0xab, 0xdb, 0x17, 0xaa, 0x6b, 0x6f, 0x46, 0x66, 0xc5, 0x9f, 0xd4, 0xea, 0xbc, 0x19, 0xef, 0xbe, 0x2e, 0x40, 0xfe, 0x1c, 0xcc, 0x77, 0x71, 0x67, 0xea, 0x1b, 0x6f, 0xa5, 0x5e, 0xc5, 0x5d, 0xf2, 0x53, 0x52, 0xdb, 0x8f, 0xcc, 0xc6, 0x98, 0x7f, 0x3d, 0x82, 0xef, 0x7b, 0xf6, 0x59, 0xfa, 0xac, 0xcf, 0x9d, 0xff, 0x1d, 0x1f, 0x84, 0x67, 0xb9, 0x51, 0x48, 0x0a, 0xb0, 0x8a, 0xf7, 0x92, 0xb1, 0x48, 0x40, 0x9b, 0xd9, 0x4c, 0x06, 0x51, 0x40, 0x6e, 0xec, 0xe6, 0x55, 0xfd, 0x05, 0xd2, 0x55, 0x59, 0x1d, 0xf4, 0x6b, 0x01, 0xc3, 0xbc, 0xb7, 0xea, 0xe6, 0x75, 0x35, 0x6f, 0x2d, 0xbd, 0xef, 0x9d, 0x1b, 0x6f, 0x7c, 0xe7, 0xbe, 0xa5, 0x6f, 0x57, 0xaf, 0xbb, 0xf9, 0xc9, 0xa6, 0x6d, 0x77, 0xad, 0x21, 0x9b, 0x1b, 0x47, 0xdf, 0x79, 0x60, 0xcd, 0x9a, 0x07, 0xde, 0x39, 0x0a, 0xdf, 0x1f, 0x58, 0x73, 0xf7, 0xb6, 0x26, 0xf2, 0xcc, 0xd4, 0x63, 0xf4, 0x99, 0x35, 0x68, 0xf3, 0xf3, 0x4e, 0x32, 0x35, 0x65, 0xa7, 0xc0, 0x0f, 0xee, 0x33, 0x35, 0x4e, 0x79, 0x5e, 0x56, 0xaa, 0xd4, 0xb2, 0xa6, 0xa0, 0x50, 0x29, 0x1a, 0x52, 0x1d, 0x81, 0x0b, 0xda, 0xa1, 0x4c, 0xb3, 0x51, 0xc9, 0x4c, 0x5f, 0xeb, 0x56, 0x13, 0x76, 0x38, 0x42, 0xc1, 0x72, 0x0d, 0xf1, 0xbe, 0x08, 0xb8, 0x62, 0x96, 0x4f, 0x5a, 0x98, 0x71, 0x57, 0x2b, 0x58, 0x65, 0x0e, 0xb2, 0x1b, 0xc8, 0x07, 0x3b, 0x8f, 0x3c, 0xb7, 0xd7, 0x16, 0xf6, 0xe7, 0xa5, 0xdd, 0xd4, 0x02, 0xea, 0xbe, 0x76, 0x26, 0xaf, 0xfd, 0xee, 0xed, 0x83, 0x64, 0x66, 0xab, 0xc8, 0x5c, 0xf9, 0x07, 0xce, 0xdd, 0xbf, 0xef, 0x8d, 0x4f, 0x4d, 0x9a, 0xf5, 0x05, 0xa1, 0x12, 0x97, 0xec, 0xb7, 0xf6, 0x13, 0x77, 0xb6, 0x76, 0x7d, 0x44, 0x18, 0xbf, 0xff, 0x87, 0x57, 0xab, 0xd3, 0x95, 0xd1, 0x20, 0xd3, 0x55, 0xdd, 0xab, 0xc9, 0x43, 0x01, 0xa9, 0x40, 0x4f, 0xfd, 0x59, 0x70, 0x20, 0x32, 0xfe, 0x3a, 0x79, 0xcf, 0x17, 0x02, 0xff, 0x9f, 0xb8, 0x55, 0x5a, 0x1c, 0xc4, 0xaa, 0x03, 0xd3, 0x86, 0xa9, 0x07, 0xc3, 0x8f, 0xcc, 0xbe, 0xf4, 0xcd, 0xd9, 0x97, 0x9e, 0xf0, 0x05, 0xb5, 0xf8, 0x84, 0xc9, 0xab, 0xd7, 0xe5, 0x9b, 0xf0, 0xf5, 0x62, 0xd8, 0x4b, 0x3c, 0x08, 0xfe, 0x01, 0x9f, 0xf3, 0x5c, 0x55, 0x68, 0x55, 0x65, 0xe5, 0xaa, 0x10, 0xfb, 0x43, 0x7b, 0x01, 0x7d, 0x9e, 0x17, 0x21, 0x0d, 0xf1, 0xe7, 0x22, 0xc4, 0x4f, 0x2d, 0xca, 0xe3, 0x59, 0xe2, 0x39, 0xf3, 0x1c, 0x2c, 0xef, 0x1c, 0xa7, 0x25, 0x82, 0xc2, 0x2d, 0x01, 0x27, 0xf5, 0xa2, 0xc8, 0x53, 0x23, 0x51, 0x58, 0xe9, 0x73, 0x1f, 0xde, 0x8c, 0xc9, 0x4f, 0xa7, 0xe2, 0x4b, 0x69, 0xb8, 0x94, 0xf4, 0x5a, 0xea, 0x0b, 0x06, 0xad, 0x5e, 0x97, 0xfa, 0xec, 0x37, 0x53, 0xd2, 0xb3, 0xbe, 0x88, 0x80, 0xbd, 0x1a, 0xa3, 0x28, 0x9a, 0x75, 0xb8, 0x4b, 0x88, 0x7a, 0x5b, 0x7d, 0x21, 0x0d, 0x5e, 0x94, 0x67, 0x32, 0x98, 0xb0, 0x8f, 0x8f, 0x51, 0x08, 0xb9, 0x78, 0x51, 0x79, 0x45, 0xe1, 0x47, 0xff, 0xc2, 0x3f, 0xe0, 0x75, 0x9d, 0x6b, 0xf7, 0x75, 0x86, 0x42, 0x9d, 0x3e, 0xf6, 0x5b, 0xf6, 0x02, 0x80, 0xbb, 0xbd, 0xbe, 0xa7, 0xbb, 0x81, 0x7c, 0x07, 0x98, 0x81, 0x78, 0xbc, 0x1e, 0x60, 0xf6, 0xa2, 0x22, 0xc9, 0xef, 0xb5, 0x19, 0x79, 0x8e, 0x00, 0xcd, 0xe2, 0x2c, 0x90, 0x63, 0xc5, 0xb1, 0x22, 0xb2, 0x88, 0x02, 0xd4, 0xc3, 0x02, 0x60, 0x89, 0x19, 0xac, 0x95, 0x01, 0x56, 0xbd, 0x6c, 0x5e, 0xff, 0x7f, 0x5d, 0x3e, 0x4d, 0xea, 0x76, 0x8d, 0x3e, 0xf5, 0x49, 0x9d, 0xc7, 0xfe, 0x6d, 0xfc, 0xeb, 0x54, 0xc9, 0xff, 0x75, 0x39, 0x35, 0x78, 0x87, 0xc6, 0x84, 0x57, 0x68, 0x9d, 0xd6, 0xef, 0xa5, 0xa2, 0x6e, 0x13, 0x33, 0xec, 0xce, 0x9b, 0xfd, 0x91, 0xc9, 0xcd, 0xfc, 0x07, 0xf3, 0xb2, 0xc9, 0x34, 0xfb, 0x7d, 0x8f, 0x13, 0x9f, 0xb7, 0x58, 0x66, 0x97, 0x22, 0x7c, 0x3e, 0x05, 0x6b, 0xf0, 0xbf, 0x01, 0x0e, 0x3b, 0xf1, 0xf3, 0x0c, 0x2c, 0x79, 0xfb, 0x1a, 0x1e, 0x60, 0x70, 0x86, 0x58, 0x65, 0xc5, 0x56, 0x56, 0xf6, 0xf3, 0xac, 0x17, 0xe0, 0x8a, 0xf9, 0xe9, 0x5b, 0xb3, 0x1f, 0xbc, 0xe2, 0x2b, 0xd6, 0xe3, 0x06, 0x5b, 0xa1, 0x41, 0xef, 0xb7, 0xe1, 0x7a, 0x5d, 0xd0, 0xfb, 0x2a, 0xd3, 0x30, 0xfb, 0x1d, 0xa6, 0x81, 0x9d, 0xf2, 0x3a, 0x67, 0x7f, 0x11, 0xee, 0x2c, 0x2a, 0xea, 0x0c, 0x33, 0xee, 0xbc, 0x42, 0x99, 0x37, 0xc8, 0xc1, 0x9d, 0xab, 0xe0, 0x79, 0x26, 0x14, 0x96, 0x8a, 0x45, 0x98, 0x29, 0xd9, 0x7b, 0x61, 0x54, 0xe1, 0x01, 0x2d, 0x46, 0x15, 0x09, 0xe2, 0xb0, 0xda, 0x64, 0x3f, 0x48, 0x24, 0xb1, 0x23, 0xc2, 0xc2, 0x98, 0x08, 0x92, 0xab, 0x9c, 0xe7, 0x7e, 0xc1, 0x06, 0x3e, 0xfa, 0x8e, 0x5e, 0xcb, 0x7a, 0xcf, 0xfd, 0x85, 0x2b, 0xe0, 0x17, 0x87, 0x02, 0x1f, 0x3d, 0xfa, 0xf4, 0x0a, 0x6b, 0x81, 0x89, 0x5b, 0xf7, 0xb4, 0xac, 0xd3, 0x89, 0xae, 0xdc, 0xce, 0xe9, 0x01, 0xc7, 0x8d, 0x68, 0xef, 0x0b, 0x05, 0x66, 0x06, 0xd3, 0xa5, 0x47, 0x0c, 0xf2, 0x08, 0x12, 0x05, 0x5e, 0x10, 0xf9, 0x19, 0xf2, 0xd2, 0x3a, 0xcc, 0xa3, 0x49, 0x90, 0x32, 0x74, 0x45, 0xed, 0x26, 0x92, 0x8b, 0x1d, 0xe3, 0xe4, 0x08, 0x96, 0x57, 0x2a, 0xb9, 0x48, 0x43, 0xea, 0x53, 0x2a, 0x36, 0xce, 0x4e, 0x0e, 0x56, 0xa2, 0x3e, 0x18, 0xcc, 0x8f, 0x86, 0xa3, 0x31, 0x8b, 0x46, 0xe7, 0x03, 0xd9, 0xcf, 0xa6, 0xe3, 0x7e, 0x58, 0x96, 0xeb, 0x0e, 0xe2, 0x24, 0x57, 0xc2, 0x0d, 0x2a, 0x4b, 0x40, 0xb8, 0xd7, 0xa7, 0xb7, 0x5a, 0x23, 0x51, 0xe6, 0xc9, 0x83, 0x8f, 0x6d, 0xad, 0x70, 0x97, 0x36, 0x15, 0x97, 0x0f, 0x76, 0xb6, 0xf8, 0x67, 0xcf, 0xae, 0xfc, 0xf6, 0xc1, 0xd5, 0x37, 0x8e, 0x57, 0xae, 0xf6, 0x17, 0x68, 0x1d, 0xd1, 0xa1, 0xe5, 0xeb, 0x1a, 0x5b, 0xb6, 0x2d, 0x29, 0xef, 0xb8, 0xe7, 0xb7, 0x4f, 0x6f, 0x5a, 0x2f, 0xad, 0x6e, 0xaf, 0x74, 0xdb, 0xfd, 0x5a, 0x7c, 0x86, 0xf3, 0x57, 0x75, 0xc4, 0x22, 0xf5, 0x41, 0x4b, 0x5e, 0xa0, 0xaa, 0x90, 0xd9, 0x33, 0xb2, 0xb4, 0x71, 0xfa, 0xd4, 0xaa, 0xd9, 0xab, 0x6c, 0x9e, 0xd1, 0x50, 0x7d, 0xd4, 0x1e, 0xe8, 0x9a, 0xee, 0x6a, 0xd9, 0xb3, 0xa6, 0x81, 0x1b, 0xd8, 0xbf, 0xaf, 0x28, 0x5a, 0xe4, 0x34, 0xcb, 0x78, 0x49, 0x3d, 0xc1, 0xbe, 0x43, 0xf1, 0xb2, 0x14, 0x7d, 0x42, 0xb2, 0x34, 0x60, 0x81, 0xf7, 0x11, 0xe4, 0x74, 0x80, 0x05, 0x88, 0xd9, 0x81, 0xf4, 0x16, 0x09, 0x68, 0x3e, 0x81, 0x23, 0x71, 0x61, 0x52, 0xae, 0x8d, 0x18, 0xa7, 0xe2, 0x18, 0x28, 0x3f, 0x05, 0x43, 0xd4, 0xde, 0x23, 0xa1, 0xe4, 0x8c, 0xbc, 0xaa, 0x4f, 0x77, 0xd1, 0xb2, 0x1a, 0x56, 0x0e, 0x29, 0x2f, 0xd8, 0x15, 0x65, 0x7a, 0x82, 0x33, 0x3e, 0xbc, 0xb8, 0x0c, 0x74, 0x66, 0x7e, 0x19, 0xe0, 0x4e, 0x8e, 0x28, 0x67, 0xe1, 0x2e, 0x2d, 0xbb, 0x16, 0x42, 0x5d, 0x65, 0x1a, 0xbd, 0xd9, 0x71, 0xb8, 0x39, 0xf8, 0xc4, 0xa5, 0x54, 0xae, 0x0d, 0x77, 0x78, 0xf5, 0x0b, 0x61, 0x33, 0xa8, 0x60, 0x3c, 0x27, 0x6a, 0x37, 0x07, 0xc5, 0xb3, 0xff, 0x4e, 0x83, 0x78, 0x5b, 0xa3, 0x9a, 0xe2, 0xf9, 0x31, 0xdc, 0xae, 0x92, 0x20, 0x3b, 0xc2, 0xc7, 0x22, 0xe9, 0xfc, 0xfb, 0x02, 0xe2, 0x48, 0xdd, 0xf6, 0x7c, 0x34, 0x8e, 0xc5, 0xc1, 0xd3, 0x1e, 0xc0, 0xb1, 0x61, 0x64, 0x90, 0xd1, 0x31, 0x95, 0x51, 0x06, 0xf3, 0xc4, 0x6f, 0x4c, 0xff, 0xe4, 0x78, 0x46, 0xdd, 0xe1, 0xaa, 0x43, 0x3a, 0x30, 0xa5, 0x75, 0x2c, 0xe1, 0x3f, 0xb2, 0xc9, 0x3c, 0xa3, 0x31, 0x31, 0xac, 0xd9, 0xc8, 0x20, 0x90, 0x6e, 0x93, 0x5a, 0x10, 0x13, 0xfa, 0x31, 0x03, 0xd6, 0xeb, 0x77, 0x83, 0x16, 0xc3, 0x58, 0x18, 0x03, 0x9b, 0x45, 0xd8, 0x29, 0x10, 0x1b, 0xdd, 0xf3, 0xb7, 0x75, 0xde, 0x03, 0x9d, 0xa5, 0xf6, 0xac, 0x7e, 0x1c, 0xf8, 0xf0, 0x0b, 0xf6, 0x03, 0xc6, 0x57, 0xba, 0x11, 0xca, 0xa6, 0x47, 0x00, 0xcf, 0x30, 0xe4, 0x76, 0x9b, 0x4c, 0xee, 0x71, 0xf7, 0xd8, 0xca, 0xe5, 0xc3, 0x8b, 0xfb, 0x7a, 0xc0, 0x34, 0x6a, 0xad, 0x4b, 0x94, 0x97, 0x86, 0x8a, 0x0b, 0xfd, 0xa6, 0x7c, 0x53, 0x7e, 0x3c, 0x62, 0xb1, 0x10, 0x69, 0x92, 0x26, 0x58, 0x5d, 0x58, 0xf1, 0x01, 0xc0, 0x3e, 0x62, 0x65, 0x0a, 0x3a, 0x0b, 0x29, 0xb9, 0x23, 0xd1, 0x8b, 0x10, 0xdd, 0xe9, 0x52, 0x48, 0x6f, 0x17, 0x82, 0xcc, 0xd3, 0x07, 0x1f, 0xdb, 0x52, 0xb1, 0x7e, 0x49, 0xfd, 0xfa, 0x9e, 0x38, 0xf3, 0x68, 0x41, 0xd3, 0xca, 0x64, 0xf3, 0x58, 0x67, 0x1c, 0x8c, 0x88, 0xf2, 0xc1, 0xe9, 0xb6, 0xee, 0xb5, 0x2d, 0x11, 0xab, 0xd9, 0x26, 0xd4, 0x06, 0x47, 0x37, 0xef, 0xa8, 0xdd, 0xff, 0x8d, 0x46, 0xdf, 0xea, 0xed, 0x07, 0x5b, 0x7a, 0xf7, 0x2f, 0x2f, 0x97, 0xe6, 0xf0, 0xc0, 0x4b, 0xfb, 0x0e, 0xde, 0x1d, 0x4c, 0x3e, 0x73, 0x70, 0xf9, 0x9d, 0xdb, 0x5b, 0xf2, 0xbc, 0xf8, 0xff, 0xe3, 0x03, 0x75, 0xdd, 0xb1, 0xe5, 0x83, 0xc5, 0xbd, 0x3b, 0xfa, 0xcf, 0xb4, 0x6e, 0x5b, 0x52, 0x16, 0x1b, 0xd8, 0xb1, 0xe8, 0xd0, 0xaa, 0xeb, 0x56, 0x94, 0xf8, 0x8a, 0x7d, 0x3a, 0xcd, 0xa2, 0x92, 0xd6, 0x48, 0xde, 0xae, 0xbd, 0x2d, 0x2b, 0x93, 0xee, 0x50, 0xef, 0x8e, 0xde, 0x96, 0xfd, 0x13, 0xad, 0x5c, 0xc5, 0xfe, 0xab, 0x8a, 0xe3, 0xc5, 0xc0, 0x0a, 0x4b, 0x47, 0x26, 0x57, 0xaf, 0xdc, 0xd4, 0x71, 0xf8, 0xf4, 0x4c, 0x79, 0x21, 0xac, 0xbb, 0x5b, 0xce, 0x7f, 0xc0, 0xd9, 0xf8, 0xb3, 0xc8, 0x80, 0xc2, 0xe8, 0x53, 0xb2, 0xb7, 0x6f, 0x30, 0x1a, 0x88, 0xa8, 0x2d, 0x24, 0xfb, 0x57, 0x84, 0x03, 0xd4, 0x9f, 0xa0, 0x05, 0x54, 0x0e, 0xf0, 0xaa, 0x11, 0x16, 0x55, 0xec, 0x10, 0x5f, 0x8a, 0xc8, 0x27, 0xd9, 0x17, 0x2b, 0xbe, 0x58, 0x13, 0xea, 0xe0, 0x17, 0xce, 0xbd, 0x0b, 0x0e, 0x66, 0xda, 0x23, 0xa3, 0x8e, 0xbc, 0xa4, 0xb5, 0x46, 0x62, 0xe1, 0x50, 0x26, 0x0c, 0x43, 0x77, 0xaf, 0x8b, 0x05, 0xb1, 0x4e, 0xc5, 0xae, 0xe2, 0x1c, 0x88, 0x02, 0x28, 0x80, 0x04, 0x1e, 0xaa, 0x9a, 0xe9, 0xbc, 0xf9, 0x9e, 0xe3, 0x07, 0x57, 0x1c, 0xae, 0x6b, 0x7f, 0x68, 0x7a, 0xf4, 0x8e, 0xa9, 0x64, 0xeb, 0xde, 0x2f, 0x6c, 0xae, 0x5e, 0xd6, 0xdd, 0xe4, 0xd1, 0x6a, 0x4a, 0x8e, 0x9e, 0xf9, 0x6b, 0x7b, 0xcd, 0xeb, 0x67, 0x9f, 0x7f, 0x7c, 0xa8, 0xe7, 0xae, 0x9e, 0x55, 0xdd, 0xc7, 0x5e, 0x3a, 0x70, 0xf8, 0xbb, 0x77, 0x0e, 0xdb, 0x42, 0xb5, 0x81, 0x95, 0x71, 0x98, 0x3d, 0x46, 0xc3, 0xe7, 0x3f, 0x60, 0xef, 0xe4, 0x34, 0x60, 0xf7, 0xed, 0x90, 0x74, 0x65, 0xa0, 0xbc, 0x3c, 0x60, 0xc9, 0x33, 0x8a, 0xe7, 0x19, 0xca, 0xb8, 0x32, 0x0c, 0x66, 0x99, 0xb4, 0x2b, 0xb3, 0x9b, 0xd8, 0x80, 0xaa, 0xa8, 0xf5, 0x5e, 0x46, 0xb3, 0x3d, 0x44, 0x22, 0x6b, 0x63, 0x91, 0x60, 0x2c, 0x98, 0x09, 0xd9, 0xe7, 0x06, 0xce, 0xa8, 0x65, 0x94, 0x66, 0x2e, 0x6a, 0x69, 0x8f, 0x68, 0xad, 0x16, 0x4b, 0xb0, 0x6e, 0xb0, 0xbe, 0x6f, 0x77, 0x7f, 0x24, 0x36, 0xb0, 0xbd, 0xa3, 0x7d, 0x59, 0x32, 0x64, 0xcb, 0x37, 0x36, 0x04, 0x57, 0x6f, 0xda, 0x51, 0xbf, 0xfe, 0xd1, 0x43, 0x3d, 0xed, 0x87, 0xbf, 0x7a, 0x60, 0xd7, 0x63, 0x1d, 0x6c, 0x93, 0xde, 0xe8, 0xf6, 0xbb, 0x6b, 0x37, 0x9c, 0x5c, 0x35, 0x7a, 0xeb, 0xfa, 0xea, 0xc2, 0x50, 0x61, 0x5e, 0x5f, 0x69, 0x4b, 0x24, 0xaf, 0xe7, 0xc6, 0x33, 0x47, 0xf6, 0xbe, 0x76, 0xeb, 0x70, 0x8f, 0x44, 0xf5, 0xdc, 0x30, 0xf8, 0x30, 0x64, 0xae, 0xdd, 0xe8, 0x0b, 0xb2, 0x5c, 0xb5, 0x94, 0x62, 0x9e, 0x2d, 0xf0, 0x73, 0x2c, 0x16, 0xcc, 0xa0, 0x8f, 0xc8, 0xe6, 0x76, 0xee, 0x25, 0x86, 0x55, 0xbd, 0x97, 0x32, 0xc4, 0xf1, 0x22, 0xcf, 0x89, 0x33, 0x44, 0x07, 0x89, 0xc2, 0x6e, 0x24, 0x60, 0x5e, 0xc0, 0xbb, 0x55, 0x33, 0x70, 0xb7, 0x1c, 0xf3, 0x1c, 0x57, 0x63, 0x9e, 0x35, 0x0b, 0x34, 0xc7, 0x0c, 0x8f, 0x99, 0xac, 0x5e, 0x24, 0x0c, 0x3a, 0xae, 0x86, 0x41, 0xf5, 0x80, 0x9e, 0x70, 0x65, 0xc8, 0x5e, 0x4a, 0x54, 0xd6, 0xc5, 0x37, 0xa6, 0x59, 0x15, 0x49, 0xe2, 0x7c, 0x98, 0x74, 0xb1, 0x77, 0xb6, 0x7c, 0x71, 0xc2, 0x3a, 0xb1, 0x34, 0xd0, 0x94, 0x76, 0x48, 0xcc, 0x4e, 0xbf, 0x3d, 0x16, 0x1b, 0x19, 0x2a, 0x59, 0x75, 0xf7, 0x8e, 0x56, 0x05, 0x6f, 0xb5, 0x0b, 0x20, 0xf8, 0xae, 0x81, 0x01, 0x4d, 0xcb, 0x3c, 0x0e, 0xc8, 0xb5, 0xda, 0x25, 0x37, 0xbc, 0x74, 0x40, 0xc6, 0x68, 0xdf, 0x05, 0x28, 0x97, 0x71, 0x7c, 0xfe, 0x03, 0xf1, 0x4b, 0x82, 0x0f, 0xad, 0x43, 0x8f, 0x49, 0x24, 0x20, 0xc5, 0x8d, 0x84, 0x88, 0xdb, 0x6e, 0xc5, 0x1a, 0xbe, 0x1d, 0x8b, 0x1a, 0x6e, 0x40, 0x5e, 0x25, 0x75, 0x88, 0x67, 0x34, 0x0c, 0x0f, 0x6a, 0x49, 0x43, 0x72, 0x6f, 0x76, 0x23, 0x3d, 0x08, 0x37, 0x3d, 0x37, 0x89, 0x0c, 0x86, 0xcd, 0x83, 0x24, 0x0c, 0x84, 0xc6, 0x74, 0x60, 0x71, 0xed, 0xa6, 0xf9, 0x37, 0xa3, 0x5a, 0xe0, 0x2b, 0x62, 0x80, 0x78, 0xa5, 0xc6, 0xcb, 0xed, 0xa7, 0x6c, 0x19, 0x89, 0xa2, 0x76, 0x0c, 0x69, 0xb5, 0x7b, 0xb4, 0x80, 0x5e, 0xfb, 0xba, 0x89, 0xb5, 0x6b, 0x86, 0x17, 0x77, 0x2f, 0x02, 0x34, 0x97, 0x87, 0x6d, 0x41, 0x47, 0xb1, 0x91, 0xa8, 0xb7, 0xb4, 0x39, 0x60, 0x95, 0xd7, 0x19, 0x7c, 0x51, 0x77, 0x0a, 0x9d, 0x2e, 0x30, 0xac, 0xc4, 0xcb, 0xe1, 0xd5, 0x0a, 0x5c, 0xa7, 0xa4, 0x67, 0x25, 0x78, 0x9b, 0x74, 0xff, 0xe6, 0x15, 0xd7, 0xae, 0x2a, 0x3b, 0x3b, 0xb5, 0x65, 0xf0, 0xfa, 0x86, 0x33, 0xba, 0x48, 0x4d, 0x4b, 0x51, 0xf3, 0x9a, 0xe6, 0x82, 0x96, 0x64, 0x41, 0xd2, 0x5d, 0x7b, 0x59, 0x3c, 0x6d, 0x8f, 0xda, 0xbc, 0xfe, 0xc4, 0x86, 0xdb, 0xd7, 0x76, 0x6d, 0x19, 0x6e, 0xf5, 0xb2, 0x07, 0x3b, 0x96, 0x36, 0x6c, 0x06, 0x3d, 0x76, 0x88, 0xb9, 0x69, 0xd7, 0xa1, 0xde, 0xd6, 0x59, 0x17, 0x3f, 0x69, 0x2f, 0x74, 0xe8, 0x43, 0x6d, 0xcb, 0x2a, 0xba, 0x4f, 0x34, 0x58, 0xe2, 0xf9, 0x7d, 0x17, 0xe7, 0x7f, 0x96, 0xa9, 0xda, 0x9a, 0xec, 0x39, 0x34, 0x9a, 0xf0, 0x05, 0xbc, 0x0a, 0x8d, 0xce, 0x02, 0x8d, 0xb6, 0xe0, 0xed, 0xb2, 0x05, 0x66, 0x5b, 0x8d, 0x79, 0xe3, 0x96, 0x32, 0x50, 0x7d, 0xbd, 0x58, 0xaf, 0xf1, 0x28, 0xbe, 0xbc, 0x93, 0x5c, 0x2d, 0xc3, 0x5a, 0x7e, 0x0b, 0xc6, 0x5a, 0xf5, 0x06, 0xaf, 0x8a, 0xc2, 0x56, 0xa0, 0xb6, 0x06, 0x23, 0x0d, 0x29, 0x16, 0xc3, 0xe8, 0x59, 0x06, 0x9c, 0x74, 0x64, 0xe4, 0x05, 0xe3, 0x24, 0xd2, 0x20, 0x3d, 0xab, 0xd1, 0x4f, 0x02, 0xee, 0xa9, 0xa4, 0xe3, 0xc7, 0xb4, 0x38, 0x2d, 0x0b, 0x0c, 0x3a, 0x46, 0x16, 0x1a, 0xf2, 0x83, 0xff, 0x17, 0x83, 0xec, 0xe1, 0x54, 0x89, 0xdb, 0x71, 0x79, 0x83, 0x68, 0xb5, 0xd3, 0x83, 0x73, 0x24, 0x53, 0x7a, 0x20, 0xa9, 0xfb, 0x6f, 0x19, 0xc3, 0x80, 0x75, 0xba, 0xa9, 0x1c, 0x19, 0x07, 0xe2, 0xdb, 0x39, 0x31, 0xb6, 0x6a, 0xe5, 0x8a, 0x65, 0x8b, 0x07, 0xbb, 0x3a, 0x8b, 0x6d, 0x71, 0x59, 0xe4, 0x99, 0xd4, 0x8d, 0xf9, 0xcb, 0x67, 0x23, 0x57, 0xc2, 0x9a, 0xd9, 0xb7, 0x13, 0x44, 0xe0, 0x43, 0x1a, 0x90, 0x53, 0x23, 0x59, 0x39, 0x1c, 0x7a, 0x99, 0x82, 0x32, 0xcd, 0x54, 0x9d, 0x9b, 0x97, 0xb4, 0x7a, 0x84, 0x0a, 0x87, 0xcf, 0x8c, 0xab, 0x4c, 0xc5, 0x9e, 0x67, 0x53, 0x9f, 0xf6, 0x07, 0x6c, 0x7e, 0x13, 0x3e, 0xfa, 0xe5, 0xfc, 0xa0, 0x29, 0xf5, 0x86, 0xb9, 0xa0, 0xa0, 0x16, 0xb8, 0x36, 0xd1, 0x52, 0x98, 0xe6, 0xda, 0x4b, 0x09, 0xd8, 0x34, 0x83, 0x79, 0x02, 0x3e, 0x7c, 0xee, 0xf3, 0x86, 0x3c, 0x3c, 0x6c, 0xb3, 0xa5, 0x0e, 0x86, 0xed, 0xc6, 0x62, 0x7b, 0xca, 0x68, 0x71, 0xe0, 0x4f, 0xb9, 0x8c, 0xa9, 0x42, 0x7e, 0x93, 0xbd, 0x28, 0x9b, 0x6b, 0x65, 0x79, 0xf1, 0xbe, 0x70, 0x00, 0x78, 0x71, 0x3b, 0x7a, 0x4d, 0x8e, 0x65, 0x9a, 0x97, 0x60, 0x9d, 0xa6, 0xd8, 0xc0, 0x60, 0x5d, 0x2b, 0x88, 0x4c, 0x9a, 0x6f, 0xa4, 0x5c, 0xe1, 0x94, 0x2b, 0xa3, 0x72, 0xc3, 0x16, 0x70, 0xc7, 0x05, 0xcc, 0x92, 0xe8, 0x10, 0x0f, 0x56, 0xf3, 0x6e, 0xa4, 0x45, 0x8c, 0x46, 0xcb, 0x4c, 0xea, 0x39, 0x46, 0x89, 0x0f, 0xed, 0x1e, 0xa4, 0xdf, 0x75, 0xa3, 0x48, 0xa7, 0x9b, 0x06, 0x6a, 0xf1, 0x3c, 0x02, 0xab, 0x48, 0xd9, 0xaa, 0x6d, 0x5c, 0xa0, 0x3b, 0x50, 0x52, 0xed, 0x8c, 0xe7, 0xef, 0x0b, 0x92, 0x64, 0x72, 0xe3, 0xba, 0x89, 0xf1, 0xb1, 0x55, 0x2b, 0x28, 0x79, 0xcb, 0x2b, 0x6c, 0x54, 0x92, 0x84, 0x65, 0xe3, 0x58, 0xce, 0x2b, 0xa8, 0x6f, 0x63, 0x33, 0xb1, 0xf6, 0xcb, 0x95, 0x23, 0xae, 0x88, 0x4a, 0x7e, 0x3f, 0x71, 0xb0, 0xac, 0xaa, 0xfd, 0xec, 0x74, 0x09, 0x07, 0xaa, 0x6e, 0xe8, 0x3d, 0x7c, 0xc3, 0x15, 0xd6, 0xfc, 0x67, 0x34, 0x56, 0xa3, 0x26, 0x3f, 0xb9, 0xb2, 0xb5, 0x6f, 0xd7, 0x40, 0xb8, 0x36, 0x51, 0x50, 0x77, 0x99, 0x22, 0xa5, 0x24, 0x5a, 0x59, 0xbd, 0xf1, 0xae, 0x0d, 0xeb, 0x6e, 0x5a, 0x5d, 0x82, 0x8b, 0x52, 0x3f, 0x6d, 0xaa, 0xf4, 0x46, 0x7d, 0x76, 0x8d, 0xd6, 0x20, 0x0c, 0x95, 0x55, 0x9d, 0xbc, 0x8e, 0xd9, 0xe0, 0x70, 0x0c, 0xe7, 0x17, 0x74, 0x76, 0x2d, 0x2a, 0xa8, 0x18, 0x6e, 0x0e, 0x94, 0x0d, 0x6c, 0xac, 0x6d, 0xbe, 0xba, 0xde, 0x12, 0xbb, 0x94, 0x64, 0x11, 0xc6, 0x36, 0x74, 0x5d, 0xbb, 0xb1, 0xb9, 0x61, 0xfc, 0xca, 0xb6, 0xdf, 0x9c, 0x9a, 0x7d, 0xba, 0xa5, 0xd9, 0x64, 0x35, 0x69, 0x75, 0xb2, 0xae, 0xa5, 0x74, 0x3d, 0x81, 0x77, 0xc9, 0xe4, 0xf2, 0x1f, 0xc4, 0xc8, 0x58, 0xe3, 0x03, 0xd7, 0x66, 0x31, 0x66, 0xb4, 0x3b, 0x2c, 0x60, 0x65, 0x6c, 0x1f, 0x81, 0xa5, 0x36, 0x8e, 0x75, 0x7a, 0x61, 0x40, 0xc9, 0x05, 0x53, 0x1b, 0x89, 0xf3, 0x37, 0xba, 0xd8, 0x7d, 0x83, 0x30, 0x30, 0xaa, 0x70, 0x46, 0x0f, 0x68, 0x5b, 0x2d, 0xa6, 0xe9, 0x61, 0x0c, 0xd6, 0x82, 0xba, 0x35, 0x21, 0xde, 0x68, 0xe2, 0x27, 0x45, 0xc2, 0x0d, 0xc6, 0x31, 0x64, 0x34, 0xee, 0x1e, 0xa4, 0xdf, 0x69, 0xa6, 0xcf, 0xf4, 0xa0, 0x06, 0xf4, 0x86, 0xe2, 0x09, 0x11, 0x53, 0x5a, 0x37, 0x0e, 0x34, 0x9f, 0xd1, 0xa9, 0xf9, 0x69, 0x8b, 0x16, 0x18, 0x0d, 0x24, 0x82, 0x32, 0x16, 0xb1, 0xd8, 0x2f, 0x31, 0x94, 0x34, 0x30, 0x77, 0x14, 0x3d, 0x6b, 0x60, 0xf5, 0x86, 0x99, 0xcb, 0x1d, 0x0d, 0x14, 0x9c, 0x3a, 0x18, 0x08, 0x99, 0xfc, 0xeb, 0x8e, 0x5e, 0x79, 0x60, 0xf3, 0xe4, 0xfa, 0x75, 0x6b, 0xd7, 0x0c, 0xf6, 0x53, 0x0e, 0x2c, 0x09, 0x5a, 0x2d, 0x66, 0x25, 0xa9, 0x23, 0xc3, 0x37, 0x6a, 0xb0, 0xe9, 0xe3, 0xf2, 0xe4, 0x65, 0x98, 0x1e, 0x59, 0xb2, 0xca, 0xc1, 0xb7, 0xe4, 0xb2, 0x54, 0xd3, 0xbc, 0xcc, 0x9a, 0x48, 0x5c, 0x4c, 0xff, 0xb5, 0x7c, 0x69, 0x7c, 0xfe, 0x08, 0x6a, 0xda, 0x60, 0x29, 0x89, 0xba, 0xf2, 0x55, 0x16, 0x66, 0x7f, 0x99, 0xe1, 0xb6, 0x75, 0x0b, 0x32, 0x70, 0x72, 0x7e, 0xd5, 0xb8, 0xa0, 0x75, 0xb3, 0xf8, 0x86, 0x97, 0xa9, 0x75, 0x23, 0x94, 0xaf, 0xab, 0x52, 0xd9, 0x1a, 0x7c, 0x85, 0x65, 0x60, 0x23, 0xff, 0x08, 0xac, 0x65, 0x13, 0xf8, 0x0a, 0xd3, 0x92, 0x21, 0x04, 0x56, 0x8d, 0xdd, 0x44, 0x82, 0x81, 0x8c, 0x62, 0xcd, 0xf8, 0x48, 0xda, 0xd8, 0x66, 0x62, 0xc7, 0x53, 0x4d, 0xb0, 0x5b, 0x36, 0xf0, 0xd5, 0x6d, 0x41, 0xa9, 0xe8, 0x82, 0xdb, 0x34, 0xb3, 0x58, 0x71, 0xbf, 0x89, 0xd5, 0x2f, 0x99, 0xcc, 0x66, 0x73, 0xd8, 0x1c, 0x8e, 0x85, 0x42, 0x91, 0x62, 0xba, 0x03, 0x4e, 0x76, 0x5d, 0x05, 0x99, 0x26, 0xc9, 0x7a, 0x1b, 0xd0, 0xaf, 0x52, 0x51, 0x20, 0xaa, 0xaa, 0xb0, 0xb2, 0xf9, 0x55, 0x7b, 0xba, 0x6f, 0xbc, 0x27, 0xd0, 0x3e, 0xde, 0xb4, 0xf2, 0x48, 0xf2, 0xec, 0x95, 0x71, 0x9d, 0xb5, 0xe3, 0xd1, 0x9d, 0xab, 0x6f, 0x9f, 0x4a, 0xb6, 0xec, 0xfb, 0xc2, 0xf4, 0xf8, 0xb1, 0x04, 0xcb, 0xb4, 0xd7, 0x7c, 0xeb, 0xe5, 0x8d, 0xb7, 0xac, 0x8e, 0x0f, 0x75, 0x73, 0x8f, 0xfc, 0xb5, 0x25, 0x7a, 0xc5, 0xe2, 0x89, 0xae, 0xeb, 0x5f, 0x3f, 0xba, 0xf7, 0xf5, 0x53, 0x23, 0x2d, 0xf5, 0x4c, 0x19, 0x22, 0xb9, 0x10, 0x30, 0x37, 0xde, 0x4b, 0x73, 0x21, 0xea, 0xe5, 0x5c, 0x08, 0xd6, 0x4e, 0xe3, 0x12, 0xcb, 0xc1, 0x3f, 0x9a, 0xa1, 0x73, 0x2e, 0x47, 0x57, 0x49, 0x86, 0x30, 0xb8, 0xac, 0x8e, 0x9c, 0x39, 0x87, 0xb5, 0x58, 0x04, 0x83, 0x8b, 0x17, 0x99, 0x8d, 0x20, 0x2d, 0xe9, 0xec, 0xa8, 0x5a, 0x9c, 0x9e, 0x13, 0xb4, 0x89, 0x2f, 0xdc, 0x2e, 0x3b, 0x66, 0x43, 0x3d, 0x04, 0x53, 0x79, 0x99, 0xd5, 0x6a, 0x8b, 0x85, 0x02, 0xb6, 0x88, 0x9c, 0xcc, 0x96, 0x83, 0x05, 0x1c, 0x9c, 0x07, 0x0d, 0x75, 0xc1, 0xba, 0x44, 0x5d, 0xc2, 0x91, 0x60, 0xdd, 0x95, 0x33, 0x5d, 0x37, 0xde, 0xab, 0x60, 0x03, 0xaf, 0xbe, 0x87, 0xe0, 0x43, 0x7a, 0x2c, 0x1b, 0x1f, 0xbf, 0xfa, 0xd5, 0xd9, 0x7b, 0x98, 0x5f, 0xb5, 0x25, 0x5e, 0x78, 0x69, 0xe3, 0x0d, 0xcb, 0xc3, 0x43, 0xdd, 0xec, 0x8a, 0x8f, 0x6e, 0x23, 0x38, 0x91, 0xae, 0xfd, 0xc6, 0xd1, 0xbd, 0xdf, 0xb8, 0x65, 0x71, 0x4b, 0xf2, 0x7f, 0x8e, 0xbc, 0xf5, 0x16, 0xcc, 0x7d, 0x09, 0xf5, 0x0d, 0x6f, 0x45, 0x36, 0xd4, 0x89, 0x56, 0x83, 0x47, 0xc0, 0x33, 0x24, 0x00, 0xd7, 0xd6, 0xda, 0x54, 0x5b, 0x59, 0x21, 0xb0, 0x7d, 0xb2, 0xfd, 0x63, 0x27, 0xce, 0x21, 0x20, 0x64, 0x32, 0x3b, 0x64, 0xed, 0x9d, 0xe7, 0xf2, 0x1e, 0x1a, 0x17, 0xc6, 0x28, 0x1e, 0x2d, 0xf4, 0xcb, 0x9b, 0xb1, 0x6a, 0x30, 0xbb, 0x3e, 0x93, 0x91, 0x06, 0x8e, 0xb6, 0xea, 0xd8, 0x65, 0x6c, 0x81, 0xa8, 0x18, 0x8d, 0x24, 0xdb, 0x71, 0x24, 0x5a, 0x81, 0xdb, 0x49, 0x88, 0xaf, 0xde, 0xe5, 0x14, 0x44, 0x26, 0xde, 0xb5, 0x67, 0xb8, 0x9c, 0xb5, 0x24, 0xfa, 0xd6, 0xb7, 0x90, 0x35, 0x54, 0xb6, 0x78, 0x1a, 0xfb, 0x9b, 0x37, 0x76, 0x47, 0xa4, 0x23, 0x5f, 0x3b, 0x34, 0xf9, 0xd4, 0xd1, 0xfe, 0xf6, 0x03, 0x8f, 0x6d, 0x9d, 0x7a, 0x4a, 0x72, 0xcc, 0x4c, 0xbc, 0x12, 0x36, 0x68, 0xec, 0x1b, 0x56, 0x6c, 0xf5, 0xf8, 0x4c, 0xce, 0x8d, 0x2f, 0x8e, 0x3c, 0x1e, 0xd1, 0x6b, 0xad, 0x27, 0x13, 0xdb, 0x1e, 0xda, 0x5d, 0x32, 0xd4, 0x1c, 0xac, 0x5a, 0xbe, 0xb7, 0x63, 0xe9, 0xd5, 0x2b, 0x4a, 0xb9, 0xb6, 0xea, 0x6b, 0x3e, 0xfd, 0xd2, 0xce, 0x3b, 0xff, 0xf3, 0xf3, 0x2b, 0x49, 0xf8, 0x79, 0xe2, 0x8b, 0x47, 0x07, 0x96, 0x8d, 0x5c, 0xf7, 0xd5, 0xf6, 0x1b, 0xdb, 0xec, 0x21, 0x83, 0x14, 0x76, 0x8b, 0x15, 0xb1, 0x2e, 0x4b, 0x6b, 0xd5, 0x55, 0xed, 0xb6, 0xa0, 0x01, 0x70, 0x43, 0xfc, 0xc5, 0x77, 0x39, 0xf2, 0x56, 0x87, 0x72, 0xb0, 0xb2, 0x29, 0x26, 0x22, 0x48, 0xe0, 0x78, 0x4e, 0xe0, 0x67, 0x48, 0xe8, 0x90, 0x13, 0x30, 0x58, 0xe3, 0x8a, 0x43, 0xbb, 0x9b, 0x84, 0xc9, 0x33, 0xb8, 0xb9, 0x9c, 0x86, 0x14, 0x5b, 0xc0, 0x82, 0x65, 0x25, 0xd1, 0x70, 0xa0, 0xd0, 0xed, 0x32, 0x1b, 0xe1, 0x51, 0x9a, 0x90, 0x46, 0xe7, 0x28, 0xc5, 0x54, 0x76, 0x91, 0x98, 0x6b, 0x96, 0xca, 0x94, 0xc3, 0x0d, 0x01, 0x35, 0x4e, 0xe1, 0x62, 0x4b, 0x02, 0xd2, 0x78, 0xcb, 0xe1, 0xdb, 0x6d, 0xcc, 0xf3, 0xa1, 0x75, 0xdb, 0xf7, 0xd6, 0x6f, 0x7b, 0xee, 0xfa, 0xfe, 0xce, 0xc3, 0xcf, 0xee, 0x99, 0x78, 0x70, 0x5f, 0x87, 0xdd, 0x3b, 0x3b, 0xcd, 0x56, 0x8c, 0xcc, 0x74, 0x77, 0xac, 0xef, 0xae, 0xcc, 0xcb, 0x37, 0x32, 0xbb, 0x9a, 0x36, 0xf5, 0xc5, 0x4e, 0x1d, 0x4d, 0x85, 0x48, 0xe0, 0xa0, 0xf7, 0x96, 0xef, 0x9d, 0xd8, 0xfe, 0xe2, 0xcd, 0x4b, 0x5a, 0x0f, 0x3c, 0xb1, 0xab, 0xbc, 0x70, 0xec, 0xa6, 0xb5, 0x65, 0x05, 0xc1, 0x82, 0x3c, 0xb2, 0x16, 0x86, 0x53, 0x4f, 0x28, 0x73, 0xee, 0xc2, 0x86, 0xe7, 0x6b, 0x48, 0xd8, 0x57, 0x71, 0x90, 0xab, 0x91, 0xc8, 0x13, 0xff, 0x6e, 0x46, 0x07, 0x33, 0xe2, 0x45, 0x4c, 0xce, 0x58, 0xa5, 0x17, 0xb9, 0x20, 0x28, 0x3b, 0x04, 0x39, 0x91, 0xb9, 0xc1, 0xd3, 0x86, 0xcb, 0xec, 0xb8, 0x73, 0x6e, 0xc7, 0xfc, 0xbf, 0xed, 0x89, 0x52, 0x83, 0xda, 0x47, 0x8d, 0x05, 0x5e, 0xb4, 0x6f, 0x76, 0x30, 0x10, 0x54, 0x8d, 0x07, 0x21, 0xb2, 0x97, 0x56, 0x55, 0x51, 0x1a, 0x0f, 0x07, 0xfd, 0x5e, 0x5b, 0x9e, 0x51, 0x4f, 0x29, 0xa2, 0xcf, 0xa1, 0x48, 0xd6, 0x96, 0xc6, 0x7c, 0x64, 0x21, 0x2a, 0x45, 0xcc, 0x39, 0x02, 0x91, 0x26, 0x93, 0x48, 0x43, 0x81, 0x52, 0x72, 0xf3, 0x53, 0x47, 0x7a, 0x16, 0x22, 0x54, 0x43, 0xf2, 0x53, 0x2b, 0x6c, 0x91, 0x42, 0x5b, 0xe6, 0x44, 0x84, 0x42, 0xb9, 0x73, 0x33, 0x24, 0x02, 0x98, 0x58, 0x17, 0xe1, 0x06, 0x6e, 0xf9, 0xf6, 0xb1, 0x0b, 0xa9, 0xd7, 0xd7, 0xde, 0x6f, 0xd2, 0x17, 0x84, 0x4a, 0xf3, 0x95, 0xd8, 0x1f, 0x46, 0x15, 0x40, 0xd0, 0x2f, 0xf3, 0xcf, 0xa1, 0x42, 0xd4, 0x20, 0xd5, 0x59, 0xcc, 0x64, 0x0b, 0x04, 0xf3, 0x1c, 0x33, 0x90, 0x95, 0xf8, 0xc3, 0x71, 0x68, 0x14, 0x04, 0x1d, 0xe8, 0x5f, 0x22, 0xb7, 0xa7, 0x78, 0xb2, 0x4f, 0x51, 0x88, 0x0a, 0xad, 0x8e, 0xba, 0xb0, 0x93, 0xc8, 0xe6, 0x84, 0x15, 0x16, 0x64, 0x2b, 0x4e, 0x28, 0x7a, 0x50, 0x34, 0xd1, 0x55, 0x2c, 0x5b, 0x6c, 0x6f, 0x6e, 0x77, 0xf9, 0x3f, 0x5b, 0x33, 0x71, 0xfd, 0xb2, 0xee, 0x26, 0x0e, 0x9b, 0x5c, 0x01, 0x5b, 0xf9, 0x40, 0xad, 0x0f, 0xfb, 0x53, 0xff, 0xc5, 0xae, 0x31, 0xf2, 0x2e, 0xe7, 0xc8, 0xf8, 0xea, 0x9b, 0xd7, 0x57, 0xdb, 0x37, 0x19, 0x8d, 0x02, 0x76, 0xd6, 0xae, 0x6c, 0x5f, 0x75, 0xea, 0xdc, 0x63, 0x72, 0x7c, 0xbc, 0xe2, 0xfc, 0x07, 0xc2, 0x8f, 0xf8, 0x77, 0x51, 0x03, 0xea, 0xc6, 0xdf, 0x90, 0x23, 0x86, 0x76, 0x17, 0x26, 0xc2, 0x84, 0xe7, 0xba, 0x1b, 0x23, 0xac, 0x8e, 0xcf, 0x03, 0x6b, 0x94, 0x1b, 0x50, 0x7c, 0x1e, 0xf5, 0x9e, 0x3e, 0xe7, 0xde, 0x85, 0x97, 0x19, 0xb8, 0x3c, 0xaa, 0x78, 0x6c, 0x95, 0x40, 0x5e, 0xcd, 0x18, 0x61, 0x8d, 0xcd, 0x60, 0xdb, 0x12, 0x83, 0x16, 0x11, 0x4f, 0x99, 0x7c, 0xe5, 0x46, 0x45, 0xcc, 0x71, 0x20, 0x92, 0x75, 0x3a, 0x3c, 0x26, 0x10, 0xa4, 0xec, 0xa4, 0xf9, 0xf9, 0xae, 0x8f, 0xdb, 0x6d, 0x0f, 0x56, 0xe3, 0x9d, 0x09, 0x70, 0x96, 0x36, 0x0f, 0xd2, 0xbe, 0xe8, 0x63, 0x74, 0x2d, 0xa6, 0x3c, 0x9e, 0xdb, 0x15, 0xe9, 0xf5, 0x4a, 0x4f, 0xb4, 0x70, 0xc7, 0x10, 0x8d, 0x2a, 0x5c, 0x46, 0x47, 0x12, 0x25, 0x98, 0x92, 0xbb, 0x23, 0xb9, 0x33, 0xc0, 0xaa, 0x01, 0xb4, 0x69, 0xd8, 0x49, 0xb9, 0xff, 0x65, 0xf5, 0x1a, 0xa5, 0x6f, 0x94, 0xc9, 0x5b, 0xd4, 0xd1, 0xda, 0xdc, 0x90, 0x0c, 0x3b, 0xad, 0x56, 0x47, 0x30, 0x12, 0x2b, 0x36, 0x64, 0x45, 0x14, 0x72, 0x02, 0x75, 0x72, 0x16, 0x95, 0xcc, 0x24, 0xc1, 0x1c, 0x06, 0xa2, 0xbe, 0x5e, 0x76, 0xa0, 0x8f, 0x73, 0xcc, 0x17, 0xc8, 0x2b, 0x59, 0x1b, 0xef, 0xdf, 0xd1, 0x5d, 0x4c, 0x78, 0xe9, 0x2c, 0x61, 0xb2, 0xba, 0x2b, 0x5e, 0xbc, 0xbe, 0x74, 0xf1, 0xf0, 0xca, 0x84, 0x33, 0x13, 0x00, 0x64, 0x77, 0x5e, 0x10, 0xe3, 0x6b, 0x35, 0x58, 0xca, 0x16, 0xef, 0x68, 0x5f, 0x02, 0x8c, 0xa6, 0xb0, 0xe0, 0x92, 0xb1, 0xcf, 0xcc, 0xb4, 0x0b, 0x1a, 0x11, 0x7f, 0x94, 0x54, 0x43, 0x83, 0x32, 0xff, 0x69, 0x57, 0x03, 0xff, 0x8d, 0xa0, 0x75, 0x4c, 0x8d, 0x4c, 0x41, 0xb7, 0x19, 0x2c, 0x56, 0xc9, 0xc2, 0x30, 0xda, 0x38, 0xd6, 0xa3, 0x75, 0xcb, 0x30, 0x6b, 0xd6, 0x37, 0x63, 0xd1, 0xcc, 0xab, 0x4c, 0x98, 0xd3, 0xc0, 0x92, 0xd3, 0x60, 0x81, 0x7b, 0x1a, 0xb8, 0xa7, 0xb2, 0x63, 0x1b, 0x20, 0x5b, 0x4e, 0xbf, 0xda, 0x3c, 0x98, 0x39, 0xb2, 0x60, 0x31, 0x32, 0x7a, 0xbd, 0x30, 0x06, 0xe2, 0x68, 0xb7, 0xfc, 0x1d, 0x8d, 0xea, 0x58, 0x86, 0x2c, 0x46, 0x64, 0x36, 0x8b, 0x63, 0x56, 0x93, 0x41, 0xc3, 0x89, 0xe2, 0x4e, 0x51, 0xe5, 0xce, 0xff, 0xcd, 0x28, 0x7b, 0x44, 0x95, 0x59, 0x3b, 0xe5, 0xb0, 0x2c, 0x19, 0x0a, 0xfd, 0xed, 0x23, 0x11, 0x9c, 0xb4, 0x2f, 0x38, 0x12, 0xb2, 0x58, 0x94, 0x71, 0x8c, 0xf8, 0xa2, 0xc3, 0x84, 0xa8, 0xd3, 0xf1, 0x31, 0x87, 0x81, 0x11, 0x58, 0x8d, 0x66, 0x4a, 0x1e, 0x0e, 0xc9, 0x43, 0x49, 0x1d, 0x88, 0x03, 0x77, 0x83, 0xd3, 0x4e, 0xca, 0xa3, 0xfd, 0x0d, 0x63, 0xc8, 0x5c, 0x5e, 0x84, 0xd0, 0xf8, 0xda, 0x55, 0x2b, 0x80, 0x39, 0x46, 0x96, 0x0e, 0x2f, 0x59, 0x3c, 0x34, 0x38, 0xd0, 0xdf, 0xd5, 0x19, 0x89, 0x05, 0x83, 0x61, 0x22, 0x13, 0xf3, 0x80, 0xe7, 0x17, 0x94, 0x89, 0x59, 0x3c, 0x7e, 0xe9, 0x65, 0x11, 0xb0, 0xe6, 0x44, 0xbc, 0xe7, 0x17, 0xa6, 0xaf, 0x52, 0xce, 0x5f, 0x42, 0x38, 0x7f, 0xfe, 0x75, 0x52, 0x92, 0x5e, 0x27, 0xa9, 0x01, 0x1e, 0x65, 0xad, 0x0e, 0x99, 0xff, 0x87, 0xa8, 0x08, 0x9e, 0x34, 0x28, 0x22, 0xb8, 0x27, 0xbd, 0x1e, 0x66, 0xbf, 0x74, 0xd1, 0xd5, 0xb3, 0xf6, 0xdc, 0x63, 0xe9, 0x58, 0xba, 0xbc, 0x6f, 0x0a, 0xba, 0x84, 0x2f, 0xe1, 0xbf, 0x09, 0x32, 0xfb, 0xdf, 0x65, 0x96, 0xb6, 0x84, 0xc0, 0x9a, 0x0d, 0x5b, 0xcc, 0x2c, 0x49, 0xc5, 0xd6, 0x88, 0x2c, 0x59, 0x28, 0x36, 0x72, 0x9d, 0x5c, 0x63, 0xe8, 0x35, 0x66, 0x00, 0xda, 0xf4, 0x79, 0x2f, 0x6c, 0xaa, 0x2e, 0x8b, 0x38, 0x11, 0x3a, 0x73, 0x89, 0x0e, 0x36, 0xb4, 0x88, 0x46, 0x05, 0x99, 0x4c, 0x7a, 0x1d, 0x03, 0x54, 0xd2, 0x0c, 0x29, 0x83, 0x07, 0x78, 0x7a, 0x36, 0x8a, 0xc3, 0x0b, 0x37, 0xfc, 0x18, 0x83, 0x8e, 0x52, 0xeb, 0x0b, 0xa6, 0xd4, 0x50, 0x9f, 0xb4, 0x12, 0x95, 0x47, 0x28, 0x6c, 0xb8, 0x34, 0x85, 0x73, 0xd5, 0x20, 0x21, 0xe5, 0x02, 0xd4, 0xab, 0x99, 0x38, 0xb1, 0x6c, 0x51, 0x0b, 0x8f, 0x8d, 0xf9, 0x45, 0x76, 0x55, 0x39, 0x02, 0xa1, 0x16, 0x20, 0xce, 0xd6, 0x5c, 0x7d, 0xb9, 0x49, 0x21, 0x44, 0x1a, 0xff, 0xc2, 0x13, 0x20, 0xb3, 0x6a, 0xb1, 0x59, 0x5e, 0xc6, 0xe6, 0x28, 0x66, 0x45, 0x1b, 0xe6, 0x51, 0x08, 0x33, 0x1c, 0x4f, 0x63, 0x47, 0xd9, 0x57, 0xb4, 0x34, 0x76, 0x44, 0xa4, 0x46, 0x29, 0x3d, 0xd6, 0x49, 0x28, 0xb2, 0x79, 0x90, 0x04, 0x16, 0x15, 0x23, 0x54, 0x4b, 0x4f, 0xff, 0x68, 0xb0, 0x8c, 0x10, 0x81, 0xa5, 0xd9, 0x7f, 0x8a, 0x88, 0xa8, 0x04, 0xc4, 0x6d, 0x4e, 0x1f, 0x07, 0xbd, 0xac, 0x6e, 0x21, 0x9a, 0x42, 0x9a, 0xdb, 0x2d, 0x2b, 0xf4, 0xaf, 0xd5, 0x5e, 0xd8, 0x49, 0x22, 0x79, 0x65, 0x22, 0x8b, 0xc5, 0x49, 0xb9, 0xdf, 0x45, 0x5b, 0x8f, 0x2a, 0xc4, 0xaa, 0x45, 0xb5, 0x89, 0x1a, 0x47, 0xd0, 0x16, 0x84, 0xbf, 0xf9, 0x88, 0x05, 0xee, 0x93, 0x75, 0xbe, 0x28, 0x37, 0x9e, 0x63, 0xb7, 0x90, 0x85, 0x55, 0x3e, 0xb2, 0x62, 0xac, 0x21, 0x37, 0x80, 0x9d, 0xa5, 0x79, 0xb2, 0x55, 0x88, 0xa8, 0x11, 0x67, 0x9f, 0xbb, 0x20, 0x30, 0x9d, 0xb5, 0x62, 0x54, 0x1a, 0x69, 0xc3, 0x40, 0xa3, 0xc5, 0x78, 0x54, 0xb1, 0x6a, 0xcc, 0x58, 0x87, 0x5b, 0x40, 0x31, 0xe8, 0x62, 0x58, 0x83, 0x1a, 0x31, 0x43, 0x36, 0x05, 0xbc, 0xf3, 0x5c, 0x36, 0x70, 0x2a, 0xb5, 0x1a, 0x41, 0xed, 0x5e, 0x28, 0xe3, 0x4d, 0x06, 0xe0, 0x58, 0x45, 0x8c, 0xd1, 0xef, 0x68, 0x54, 0x2f, 0xf3, 0xb2, 0xc5, 0x6c, 0xd4, 0x72, 0xf4, 0x78, 0xb5, 0x42, 0xbb, 0x56, 0x59, 0xfe, 0x91, 0x51, 0xd0, 0xdf, 0x34, 0x08, 0xa1, 0x64, 0xd3, 0x82, 0x83, 0xa0, 0xcc, 0x10, 0xd8, 0x60, 0x98, 0x6f, 0x04, 0x30, 0xc1, 0x39, 0xa4, 0x63, 0x38, 0xdd, 0x05, 0x92, 0xf8, 0x52, 0x5d, 0x09, 0x8d, 0x49, 0x46, 0xcd, 0x62, 0x44, 0xc5, 0x6e, 0x5f, 0x6f, 0x4f, 0xb7, 0xd4, 0xd6, 0xdc, 0x44, 0x49, 0x4d, 0x96, 0xa6, 0xf5, 0x72, 0x84, 0xef, 0x25, 0xc9, 0x7f, 0x91, 0xf5, 0x4a, 0x99, 0x62, 0xd9, 0xf2, 0xf1, 0x0b, 0x98, 0x22, 0x5b, 0xcc, 0x5e, 0x4c, 0xb4, 0x5e, 0x92, 0x4f, 0x94, 0x05, 0x8d, 0x51, 0x25, 0x78, 0x83, 0x7f, 0xe4, 0xcf, 0xa2, 0x22, 0xd4, 0x24, 0xd5, 0x5b, 0x2d, 0xd4, 0x36, 0x27, 0xdb, 0xef, 0x2a, 0xba, 0xe8, 0x7a, 0x50, 0x6d, 0x73, 0x9a, 0x87, 0x05, 0x8b, 0x80, 0x1e, 0xc5, 0x28, 0x02, 0x51, 0x55, 0x1b, 0x76, 0x92, 0x84, 0xab, 0x5c, 0x64, 0x28, 0x1b, 0x64, 0x74, 0x9e, 0x74, 0x96, 0xdf, 0x27, 0xb3, 0x6c, 0x3e, 0xf8, 0x95, 0x83, 0x91, 0x15, 0x7e, 0x91, 0xa9, 0x88, 0x8f, 0x5d, 0xb7, 0x2c, 0xc2, 0x6c, 0x3f, 0x9b, 0xda, 0xa0, 0x4c, 0xa1, 0x1a, 0xfc, 0x8f, 0x76, 0x9d, 0x6e, 0xda, 0xce, 0x84, 0xfa, 0x76, 0xf5, 0x95, 0x9e, 0xcb, 0xa7, 0xd0, 0x65, 0xc3, 0xd6, 0x2d, 0x75, 0x3a, 0x00, 0x2c, 0x72, 0x34, 0x84, 0x21, 0xfb, 0x9d, 0x33, 0xf4, 0x28, 0x03, 0x59, 0x9a, 0xf2, 0x7a, 0x1d, 0x25, 0xe7, 0x95, 0x89, 0xc1, 0x48, 0xc1, 0xc3, 0xc8, 0xeb, 0x51, 0x0e, 0x52, 0x1b, 0x75, 0x5a, 0x81, 0x43, 0x45, 0xb8, 0x48, 0x4b, 0x13, 0x54, 0x9d, 0x85, 0x58, 0xce, 0x40, 0xc1, 0x2a, 0x6c, 0xd9, 0x76, 0x21, 0xf3, 0x0f, 0x2d, 0x7d, 0xd6, 0x7b, 0xf6, 0xb8, 0xfc, 0xa6, 0x6b, 0x3f, 0x7f, 0x17, 0x05, 0x90, 0x5a, 0x7d, 0x8d, 0x57, 0x7f, 0x93, 0x3f, 0x1b, 0x29, 0xfa, 0xc0, 0x68, 0x3d, 0xc9, 0xdf, 0x79, 0x9b, 0x02, 0x5e, 0x36, 0xe8, 0xc4, 0x97, 0x00, 0x58, 0xb9, 0xb7, 0xf8, 0x57, 0x90, 0x0f, 0x7c, 0xf4, 0x65, 0xd2, 0xb0, 0x09, 0x2c, 0xb1, 0x10, 0x78, 0x3a, 0x5a, 0x82, 0x4d, 0x76, 0x40, 0x83, 0x45, 0x06, 0x31, 0x22, 0x02, 0x6f, 0x50, 0x3b, 0x39, 0x38, 0x2f, 0x6e, 0x75, 0x69, 0xdc, 0x96, 0x97, 0x95, 0xc6, 0x0b, 0xfc, 0x30, 0x92, 0xcf, 0x6a, 0xb5, 0x85, 0x83, 0x4e, 0x7d, 0x0e, 0x86, 0x93, 0x04, 0xc1, 0xc4, 0xd1, 0x2b, 0x16, 0xd4, 0x39, 0xc8, 0x73, 0x8a, 0x44, 0x05, 0x33, 0x4d, 0x8d, 0xb2, 0x25, 0x28, 0xc2, 0x3f, 0x73, 0x9b, 0x3b, 0x6a, 0x2b, 0xf0, 0x74, 0xb5, 0xcb, 0xa8, 0x5e, 0x74, 0xbd, 0xc1, 0x65, 0xbc, 0x42, 0xa3, 0xd5, 0x6a, 0x0e, 0xea, 0xdc, 0x86, 0x9b, 0xdf, 0x7b, 0x41, 0x99, 0xc1, 0xb1, 0x47, 0x9f, 0xe6, 0x85, 0x1d, 0x56, 0xdd, 0xda, 0x2d, 0xc7, 0xe4, 0x99, 0x71, 0x77, 0x69, 0x0c, 0xd7, 0x68, 0x0c, 0x06, 0xcd, 0x35, 0x60, 0x78, 0xeb, 0x49, 0xbc, 0x8a, 0xcc, 0xed, 0x9b, 0x30, 0xb7, 0x28, 0x7a, 0x0e, 0xd0, 0x0e, 0x0a, 0x00, 0xd1, 0xed, 0x73, 0x7a, 0xfd, 0x2b, 0x70, 0x9d, 0x9e, 0x20, 0x47, 0xc7, 0x25, 0x53, 0x12, 0xfc, 0xba, 0x7a, 0x2c, 0xe0, 0x38, 0xd6, 0x68, 0x49, 0x1e, 0x4d, 0x8c, 0xea, 0x56, 0x41, 0xa0, 0xc1, 0xa7, 0xcd, 0xf4, 0x20, 0x8f, 0x7c, 0xe6, 0x7d, 0x92, 0x9a, 0x59, 0x8a, 0x99, 0x9f, 0x51, 0x99, 0x51, 0x92, 0xf9, 0x48, 0x4e, 0xeb, 0xd1, 0x34, 0x23, 0xa5, 0xd7, 0x3c, 0x2d, 0x41, 0x04, 0xc7, 0x62, 0xb1, 0xae, 0x58, 0xd7, 0xa2, 0x4e, 0x82, 0x21, 0x0f, 0x39, 0x2c, 0x42, 0xbd, 0x80, 0x9c, 0x70, 0x4e, 0x01, 0xc3, 0xcf, 0xf9, 0x8d, 0x2f, 0x86, 0x45, 0x76, 0x93, 0x3d, 0x50, 0x96, 0xef, 0x2a, 0x2d, 0xb2, 0xd9, 0x8a, 0x4a, 0x5d, 0xf9, 0x65, 0x01, 0x3b, 0x1e, 0xb6, 0xc1, 0x95, 0x7c, 0xf9, 0x4a, 0x3e, 0x5c, 0xb1, 0x9d, 0xf3, 0x5d, 0x88, 0x59, 0x5e, 0x97, 0xae, 0xd6, 0x00, 0x2d, 0xa0, 0x87, 0x7b, 0xce, 0xef, 0xbf, 0xfe, 0x78, 0x21, 0x54, 0x2b, 0xb8, 0xfd, 0x2a, 0xc5, 0xed, 0x8b, 0x19, 0xdc, 0x2a, 0xd7, 0x9f, 0xa4, 0xd7, 0x5f, 0xce, 0xc2, 0x39, 0xac, 0x89, 0xd4, 0x13, 0xdc, 0x00, 0xac, 0x89, 0x26, 0x74, 0x9b, 0x64, 0x56, 0xd7, 0xab, 0xb6, 0x1c, 0x8b, 0xbc, 0x9a, 0xb8, 0x14, 0xcf, 0x62, 0x2f, 0xa2, 0xc0, 0x58, 0x45, 0x81, 0x69, 0x88, 0x02, 0xa3, 0xee, 0x35, 0x35, 0x34, 0xb7, 0x8a, 0xf4, 0xf0, 0xab, 0x12, 0x1d, 0xba, 0x68, 0x1f, 0x94, 0xe9, 0x22, 0x2b, 0xbf, 0x26, 0xd4, 0xd4, 0xd8, 0xe0, 0x08, 0x05, 0xa3, 0x54, 0x02, 0x5c, 0xa8, 0xfc, 0x72, 0x25, 0xc0, 0xfc, 0x89, 0x98, 0x0b, 0x0b, 0x06, 0x57, 0x4e, 0xfe, 0x51, 0x81, 0x72, 0x9a, 0xb0, 0x66, 0x61, 0x71, 0x91, 0x95, 0x69, 0xd4, 0xaf, 0x1c, 0x31, 0x9c, 0x23, 0xdf, 0xc8, 0xba, 0x04, 0xf9, 0x66, 0x36, 0x5d, 0xb6, 0x7c, 0x4b, 0xaf, 0x3e, 0xcd, 0xa5, 0x56, 0xdf, 0x7c, 0x8b, 0x6d, 0x01, 0x7a, 0xcb, 0xf0, 0x08, 0x7b, 0x80, 0x7e, 0x8d, 0x68, 0x95, 0xb4, 0x3c, 0x0c, 0xb0, 0x44, 0xcc, 0x0c, 0x8f, 0x2d, 0x24, 0x47, 0x81, 0x6e, 0xd5, 0xcf, 0x67, 0x34, 0x92, 0x43, 0xa5, 0x69, 0xa3, 0xd1, 0xa0, 0xd7, 0xb2, 0x54, 0x4b, 0x01, 0x90, 0x8d, 0xa8, 0x11, 0x4c, 0xc6, 0x3a, 0x2a, 0x88, 0x89, 0x62, 0x32, 0x5e, 0x5a, 0x31, 0x5d, 0x20, 0x9c, 0xd3, 0x53, 0x50, 0x8d, 0xc4, 0xb4, 0x1a, 0xfa, 0x7a, 0xf3, 0x95, 0xcf, 0x1e, 0x8c, 0x2c, 0x27, 0x94, 0x89, 0x8d, 0x1d, 0x23, 0x94, 0x39, 0x93, 0x5a, 0x7f, 0x26, 0xd5, 0xbf, 0x80, 0xe2, 0x99, 0x57, 0x8a, 0xa7, 0xe9, 0x40, 0xec, 0xc6, 0xdd, 0x60, 0xb7, 0x37, 0xa3, 0x7f, 0x91, 0x4c, 0x2e, 0xac, 0xe3, 0xf3, 0xb1, 0xa0, 0xd3, 0x92, 0x83, 0xc6, 0x4a, 0x58, 0xaf, 0x06, 0x81, 0xa4, 0x16, 0xb4, 0xdc, 0x0c, 0x68, 0x7b, 0x35, 0x94, 0xa0, 0xc8, 0x00, 0xf9, 0x08, 0x20, 0x37, 0xaa, 0x17, 0x19, 0x22, 0x05, 0x8c, 0x06, 0x96, 0x95, 0x53, 0x13, 0x64, 0x63, 0xbc, 0x36, 0xab, 0x27, 0x0f, 0x4a, 0x81, 0x1e, 0x22, 0x21, 0x5d, 0xe5, 0xaf, 0x0b, 0xf4, 0xfd, 0x5b, 0x1e, 0x48, 0x76, 0x81, 0x60, 0x36, 0xcd, 0xa8, 0xb9, 0xa9, 0xb1, 0xb6, 0xaa, 0xbc, 0x34, 0x48, 0xad, 0x01, 0xf8, 0x47, 0x36, 0x9b, 0x03, 0xd6, 0x0b, 0x35, 0xca, 0x1c, 0x02, 0x00, 0xf7, 0xb8, 0x6c, 0xb2, 0xb0, 0x2e, 0x56, 0x69, 0xc0, 0x3f, 0x01, 0x6a, 0x26, 0x35, 0x78, 0x76, 0xa7, 0xcb, 0xf7, 0xb9, 0x0b, 0x48, 0xf0, 0xca, 0xbf, 0xff, 0xee, 0xd3, 0xbe, 0x07, 0x41, 0x23, 0x99, 0x8f, 0x69, 0xac, 0x9a, 0x9e, 0x2d, 0x8b, 0x02, 0xcc, 0x76, 0x15, 0xad, 0x0b, 0x59, 0xef, 0x5f, 0x7e, 0xad, 0x22, 0xf5, 0x91, 0xd1, 0x7a, 0x3b, 0xf6, 0x34, 0xae, 0xed, 0xd8, 0x74, 0x8e, 0x00, 0x2c, 0xcb, 0x13, 0x71, 0x31, 0xff, 0x0a, 0x13, 0x41, 0x2b, 0xe8, 0x9e, 0xc3, 0x33, 0xdc, 0xb1, 0xf4, 0xf5, 0xe6, 0xac, 0xeb, 0xcf, 0x71, 0x47, 0x55, 0xd9, 0x2e, 0x46, 0x15, 0xd9, 0x3e, 0x81, 0x35, 0xf2, 0x26, 0x9f, 0xad, 0x1d, 0x63, 0xed, 0x0a, 0x89, 0x31, 0xe2, 0x6a, 0xcc, 0x73, 0xec, 0xc0, 0x72, 0xac, 0x05, 0xb7, 0xca, 0x06, 0x1f, 0x86, 0x9c, 0x3b, 0x9c, 0xba, 0x5f, 0xdc, 0x80, 0x8c, 0x46, 0xed, 0x98, 0x01, 0x53, 0xad, 0xa7, 0xd3, 0xa9, 0x5f, 0xb3, 0x15, 0x01, 0xd1, 0xd9, 0x9a, 0x51, 0x93, 0x1e, 0x24, 0x3b, 0xb1, 0xc1, 0x44, 0x96, 0x86, 0xff, 0xbc, 0xb2, 0xf2, 0x48, 0x42, 0x7f, 0xc3, 0x18, 0x4d, 0x34, 0xa1, 0xdd, 0xd1, 0x65, 0xf6, 0x96, 0x6a, 0x65, 0x4d, 0xa2, 0x21, 0x9a, 0x64, 0x9e, 0x21, 0xe6, 0xef, 0x46, 0xcc, 0x3e, 0x50, 0x25, 0x13, 0xb1, 0x89, 0xf1, 0xb1, 0x35, 0xab, 0x46, 0x86, 0x87, 0x06, 0x7b, 0x16, 0xb5, 0xb5, 0x10, 0x02, 0x3b, 0x88, 0x86, 0xb1, 0x5e, 0x96, 0x86, 0xb9, 0x1c, 0x26, 0xc8, 0x16, 0x21, 0xfc, 0xbf, 0x52, 0x25, 0x53, 0x42, 0x54, 0x45, 0x09, 0x55, 0x32, 0xa0, 0x76, 0x8a, 0xe0, 0x4a, 0x19, 0x51, 0x3b, 0xf2, 0x67, 0xea, 0x06, 0xba, 0xf0, 0xce, 0xce, 0xbf, 0x4c, 0x3f, 0x73, 0x5b, 0x7e, 0x2c, 0xcf, 0xef, 0xe9, 0x06, 0xd9, 0x73, 0x39, 0xba, 0xe8, 0xe2, 0xec, 0x93, 0x23, 0xb3, 0x14, 0xfe, 0x48, 0x64, 0xf1, 0xc7, 0x8b, 0x59, 0x7c, 0x53, 0x92, 0x75, 0xfd, 0x65, 0x72, 0x5d, 0xd6, 0x4f, 0xc2, 0x77, 0x40, 0xbe, 0x2d, 0x47, 0x2f, 0x49, 0xae, 0xb9, 0xf2, 0xcd, 0xd0, 0x89, 0x75, 0x1a, 0x5e, 0xd1, 0x53, 0xc9, 0xf9, 0x44, 0x9d, 0x6c, 0x91, 0x0b, 0x69, 0x8b, 0x5c, 0x4f, 0x2c, 0x72, 0x1a, 0x5a, 0xd7, 0x8d, 0x01, 0xf9, 0xb6, 0x92, 0x6d, 0xdd, 0x56, 0xa4, 0x15, 0x49, 0x09, 0x92, 0x99, 0xcb, 0x1f, 0x00, 0x65, 0xfa, 0xcb, 0xa6, 0xfd, 0x72, 0xb4, 0x7c, 0x99, 0x12, 0x57, 0xe9, 0xe9, 0x92, 0xda, 0xa2, 0xaa, 0x04, 0xb5, 0x7e, 0x5c, 0x09, 0xba, 0xb0, 0x72, 0xfb, 0x38, 0x82, 0xd5, 0x36, 0xaf, 0xca, 0xab, 0xfb, 0x58, 0xe2, 0x76, 0x01, 0x2d, 0x98, 0xa3, 0x77, 0x5e, 0x41, 0x75, 0xe8, 0x51, 0x79, 0x69, 0x5a, 0xc0, 0xd6, 0x61, 0xb5, 0x3c, 0x11, 0xc1, 0xcc, 0x40, 0x29, 0xe6, 0xfb, 0xe4, 0x3c, 0x3c, 0x4d, 0xe6, 0x72, 0x3a, 0xeb, 0xa3, 0x0c, 0xd6, 0xcc, 0x82, 0x82, 0x55, 0x27, 0xcb, 0x48, 0x83, 0x3e, 0x2d, 0x58, 0x4b, 0xa0, 0xf9, 0x02, 0x02, 0x75, 0x6e, 0xe3, 0x51, 0xc9, 0x09, 0xc4, 0xa8, 0x43, 0x75, 0xb5, 0x89, 0xaa, 0x8a, 0xb4, 0x34, 0x35, 0x5e, 0xb6, 0x34, 0xcd, 0x59, 0x48, 0x4f, 0x5d, 0xe6, 0x22, 0xf9, 0x38, 0x2b, 0x40, 0xe1, 0xe9, 0x4f, 0x50, 0x3f, 0xa4, 0x0a, 0xf5, 0x4b, 0x3d, 0x8a, 0xcd, 0x25, 0x0a, 0xe1, 0x10, 0x8d, 0x77, 0x64, 0x19, 0x12, 0x82, 0xa0, 0x1a, 0x12, 0xa4, 0xda, 0x15, 0xb5, 0x9b, 0x28, 0x7f, 0xce, 0x50, 0x8b, 0xa2, 0x0a, 0x55, 0xd9, 0x92, 0x61, 0x62, 0x30, 0x91, 0x93, 0x3b, 0x17, 0x31, 0x98, 0xd2, 0xe7, 0xb0, 0x99, 0x1a, 0x12, 0x97, 0x5b, 0xd0, 0x4e, 0x0a, 0xf4, 0xed, 0x1d, 0x0e, 0xd4, 0xfb, 0x44, 0x46, 0x63, 0x76, 0xdb, 0x16, 0x4b, 0x0b, 0xd9, 0x47, 0x1f, 0xfd, 0x6b, 0xfb, 0xae, 0x91, 0x0a, 0x9d, 0x76, 0xbb, 0x4e, 0xcb, 0xf5, 0x2e, 0x65, 0x3f, 0x43, 0x14, 0x32, 0x9d, 0x13, 0xb5, 0x43, 0x7a, 0xd1, 0x66, 0x69, 0xd3, 0x9c, 0x75, 0xaa, 0xd3, 0x36, 0x82, 0x2d, 0xc2, 0xcf, 0x6b, 0x8b, 0xe8, 0x31, 0xb1, 0x0f, 0x95, 0xf5, 0x65, 0x34, 0x30, 0x34, 0x1b, 0x4b, 0xa3, 0xa1, 0xf6, 0xe1, 0x0c, 0x35, 0x4a, 0x7a, 0x11, 0xf8, 0xcb, 0x60, 0x99, 0x77, 0x84, 0xc3, 0xca, 0xa2, 0x32, 0x7d, 0xcc, 0x45, 0x95, 0x7d, 0x10, 0x9d, 0xce, 0xff, 0xe3, 0x2c, 0xa5, 0x40, 0xef, 0xde, 0xa5, 0x80, 0x15, 0x0d, 0xe7, 0x8d, 0x00, 0x4e, 0x2e, 0x7f, 0x01, 0xa5, 0xd1, 0x64, 0xe3, 0x7a, 0x46, 0x08, 0x96, 0x72, 0x63, 0x5e, 0x6f, 0xf0, 0xef, 0xa2, 0x16, 0x12, 0xf3, 0x22, 0x29, 0x74, 0xa6, 0x4a, 0xa2, 0x5a, 0x30, 0xab, 0x2b, 0xc4, 0x02, 0x4b, 0x42, 0x5e, 0x70, 0x81, 0xcb, 0xba, 0xa0, 0xc4, 0x50, 0x62, 0xa0, 0x47, 0x05, 0x9e, 0x15, 0x26, 0x11, 0xa9, 0xf5, 0x25, 0x47, 0xbe, 0x40, 0x08, 0xa1, 0x51, 0x2d, 0xa7, 0x98, 0x72, 0x19, 0x6d, 0x57, 0x40, 0xd7, 0x99, 0xda, 0x9e, 0xc6, 0xa1, 0x48, 0x27, 0x74, 0xb1, 0x3e, 0x21, 0x7a, 0x4c, 0x61, 0x81, 0x3e, 0xca, 0x7e, 0x0d, 0x74, 0xc4, 0xb9, 0xfd, 0xa4, 0x0a, 0x98, 0x92, 0x86, 0xc3, 0x9a, 0xc9, 0xdc, 0xae, 0x0b, 0x75, 0xa0, 0x09, 0x76, 0x80, 0x87, 0x16, 0xd4, 0x02, 0x66, 0x4f, 0x43, 0xd0, 0x1a, 0x8e, 0x86, 0x03, 0xb6, 0xf9, 0x8c, 0x4d, 0x12, 0xf4, 0xb8, 0x68, 0x02, 0xdd, 0x82, 0xc1, 0xaf, 0x8b, 0xe7, 0xc5, 0x2d, 0x1c, 0x0c, 0x5b, 0x38, 0xed, 0x6d, 0x6e, 0x4c, 0x0c, 0xe4, 0x9f, 0xb6, 0x13, 0x68, 0xb8, 0x12, 0x37, 0x4b, 0x36, 0x33, 0xd6, 0xe3, 0x2e, 0x58, 0xc7, 0x7a, 0x80, 0xd2, 0x58, 0x8b, 0xc1, 0x0e, 0x1c, 0x90, 0x29, 0x56, 0x07, 0x7e, 0x96, 0x56, 0x23, 0x68, 0x27, 0x91, 0x5e, 0x7f, 0x61, 0xf4, 0xcb, 0x8c, 0x8d, 0x46, 0x34, 0x6a, 0x10, 0x59, 0x96, 0x46, 0xee, 0x2d, 0x26, 0x1d, 0xa7, 0x06, 0x85, 0x0b, 0x68, 0xc8, 0x4c, 0xed, 0x4c, 0xdd, 0x56, 0x32, 0x02, 0xba, 0xec, 0x01, 0x42, 0x34, 0x64, 0x76, 0xa9, 0x01, 0x60, 0x91, 0x4d, 0xab, 0xa3, 0x28, 0xfb, 0x07, 0x99, 0x41, 0xa4, 0x66, 0x92, 0x97, 0xcb, 0x70, 0xfa, 0xc9, 0xdc, 0x71, 0x2e, 0xab, 0xb7, 0xb2, 0xe5, 0x80, 0x56, 0xa2, 0x95, 0x2b, 0x14, 0xd5, 0xd8, 0xd7, 0x03, 0x8b, 0xb8, 0x9d, 0x12, 0xfb, 0xb2, 0xb7, 0x1c, 0x2e, 0x9f, 0x01, 0x2e, 0xb2, 0xa6, 0xeb, 0x0e, 0xbc, 0x74, 0x9c, 0x86, 0xbf, 0x2e, 0xc5, 0x16, 0x72, 0x38, 0xac, 0x20, 0xf5, 0xcb, 0x85, 0x57, 0xf9, 0xc7, 0xe2, 0x94, 0x8c, 0xae, 0xe4, 0xdc, 0xfc, 0x4b, 0xa8, 0x10, 0x6d, 0x7e, 0x1e, 0x16, 0xb5, 0x40, 0x32, 0x0f, 0x22, 0x40, 0x9f, 0x02, 0xa0, 0x46, 0x7a, 0x1d, 0x13, 0x61, 0xcf, 0xcb, 0xc2, 0x5e, 0x64, 0x94, 0xbc, 0x9b, 0x62, 0xb9, 0xda, 0x1c, 0x69, 0x85, 0x16, 0x68, 0x34, 0x2a, 0x59, 0x94, 0xad, 0x6d, 0x5b, 0x87, 0x35, 0x30, 0xaf, 0x2e, 0x68, 0x56, 0x51, 0x45, 0xd4, 0x9a, 0x23, 0x28, 0x63, 0xa9, 0x76, 0xeb, 0x83, 0xdb, 0x57, 0xdd, 0x3f, 0xb8, 0xeb, 0x4c, 0x72, 0x47, 0xf4, 0x9a, 0x1b, 0x31, 0x73, 0x4e, 0x99, 0x6f, 0xcd, 0x8a, 0xfb, 0xf6, 0x75, 0x1a, 0xff, 0xba, 0x92, 0x2f, 0x66, 0x84, 0x3b, 0x8e, 0xd5, 0x9c, 0xca, 0x96, 0x59, 0x77, 0xc3, 0x1c, 0x1a, 0xd0, 0x9b, 0xb2, 0x97, 0xa4, 0x57, 0x36, 0xde, 0x05, 0x2d, 0x39, 0x68, 0x41, 0x7e, 0x89, 0xca, 0xaf, 0x51, 0xb9, 0x41, 0x50, 0xa4, 0x7b, 0x1a, 0x3c, 0xc7, 0x64, 0xe4, 0xbc, 0x46, 0x95, 0xf3, 0x3a, 0x56, 0x10, 0xa6, 0x04, 0x35, 0x21, 0xa2, 0x82, 0xc6, 0xd6, 0x95, 0x96, 0xe8, 0xd2, 0xbd, 0x68, 0xec, 0x3f, 0x9b, 0x99, 0xe5, 0xb2, 0x39, 0xf3, 0xb7, 0x56, 0x9c, 0x2c, 0xba, 0x1b, 0x02, 0x76, 0x41, 0x0d, 0xc5, 0xd3, 0x65, 0xab, 0x91, 0x44, 0x36, 0xee, 0x2e, 0x1a, 0x64, 0xad, 0xdd, 0xf6, 0xe0, 0xf6, 0x25, 0x77, 0x75, 0x9f, 0x78, 0xbd, 0x6e, 0x57, 0xe4, 0x9a, 0x9b, 0x2e, 0x1e, 0x54, 0xa5, 0x58, 0x36, 0xe0, 0x6c, 0x34, 0x2b, 0xbc, 0x52, 0x71, 0xfe, 0x7d, 0x7e, 0x39, 0xff, 0x1c, 0xf8, 0x84, 0x5f, 0x56, 0xf2, 0xba, 0x23, 0x58, 0x03, 0xce, 0x2d, 0x02, 0x57, 0x88, 0x98, 0x50, 0x51, 0xac, 0xc1, 0xfd, 0xde, 0xec, 0xab, 0xac, 0x7a, 0x75, 0x54, 0x95, 0xfa, 0x1a, 0x5e, 0xe0, 0x35, 0x24, 0x29, 0x56, 0x50, 0x0e, 0xad, 0xeb, 0x75, 0x0c, 0xc3, 0x70, 0xa3, 0x5a, 0xd9, 0x5a, 0x22, 0xf4, 0x1c, 0x27, 0xee, 0x0e, 0xc9, 0x0f, 0x00, 0x81, 0x7f, 0xa9, 0xe6, 0x34, 0xc5, 0x8b, 0xf4, 0x91, 0x2b, 0x1e, 0xca, 0x21, 0x1b, 0xea, 0xb3, 0xca, 0x16, 0x96, 0xad, 0x24, 0x48, 0x83, 0x65, 0xf3, 0xa4, 0x2c, 0x5e, 0xc4, 0xde, 0xb2, 0x91, 0x35, 0x6d, 0x0d, 0x38, 0x38, 0xef, 0xdc, 0x9c, 0xc3, 0x54, 0xe3, 0xd9, 0x79, 0x31, 0xfc, 0x56, 0x39, 0xeb, 0xca, 0x9b, 0xc2, 0x45, 0xec, 0xda, 0xb9, 0x69, 0x83, 0x69, 0xdb, 0x6b, 0x6e, 0x6a, 0x06, 0xae, 0xb6, 0xda, 0x52, 0x4f, 0x9e, 0x22, 0x6b, 0xf0, 0xfc, 0xfb, 0xe2, 0x3f, 0x01, 0x5e, 0x57, 0xa0, 0xb7, 0x65, 0x34, 0xe5, 0x87, 0x08, 0xc7, 0x82, 0x81, 0x22, 0x12, 0x03, 0xa5, 0x17, 0xeb, 0x75, 0xdc, 0x40, 0x18, 0x8b, 0x04, 0xb9, 0xea, 0x2d, 0x5e, 0xb9, 0x65, 0x54, 0x6f, 0x29, 0x18, 0xae, 0x46, 0xc0, 0xda, 0x58, 0x64, 0x66, 0xb2, 0x77, 0xed, 0x4c, 0x06, 0x86, 0x72, 0x9f, 0x20, 0x0b, 0x63, 0xd0, 0x7c, 0xfa, 0xb5, 0xe0, 0x2f, 0xb0, 0x7a, 0xfd, 0x94, 0x9e, 0x3a, 0x19, 0x22, 0xe6, 0x31, 0x3d, 0xf7, 0xc8, 0xab, 0x45, 0x1c, 0xb2, 0x77, 0x30, 0xb2, 0xfb, 0xc2, 0x5a, 0x32, 0x1a, 0x69, 0xa2, 0x82, 0x7e, 0x1c, 0xa4, 0xf6, 0x4e, 0xbd, 0xe2, 0x64, 0xac, 0x40, 0x8a, 0x28, 0x1d, 0xec, 0x6f, 0x6f, 0x6d, 0x6a, 0xb4, 0x06, 0x95, 0xad, 0x3d, 0xeb, 0xc7, 0xda, 0xda, 0x53, 0x11, 0x1f, 0x58, 0x80, 0x5e, 0x0b, 0x8a, 0x51, 0x42, 0x90, 0x45, 0x2d, 0x40, 0x10, 0x75, 0xd7, 0xef, 0xed, 0x32, 0x8e, 0x10, 0xa4, 0x30, 0xf5, 0x5e, 0x53, 0x05, 0x25, 0xa1, 0x2e, 0x4d, 0xc2, 0x86, 0x85, 0x25, 0xe8, 0x02, 0x34, 0x9a, 0x87, 0xa6, 0xb2, 0xc1, 0xc4, 0xa2, 0xe6, 0xd4, 0xbd, 0xec, 0xaf, 0x39, 0x2d, 0x32, 0xa3, 0x20, 0x9a, 0x40, 0x9f, 0x94, 0xcc, 0x31, 0x8c, 0xf0, 0xf2, 0x1a, 0x86, 0x43, 0x83, 0x60, 0x5f, 0x92, 0x80, 0x0f, 0xd9, 0x20, 0x2a, 0x23, 0x76, 0x06, 0xc7, 0x23, 0x0e, 0xd4, 0x14, 0x62, 0x58, 0x9e, 0x99, 0x24, 0x71, 0x2e, 0x9a, 0xca, 0xa8, 0x1e, 0x96, 0x64, 0x59, 0xba, 0x51, 0x33, 0x43, 0x44, 0x48, 0x25, 0xdc, 0x15, 0xc1, 0x2c, 0xc5, 0x33, 0x17, 0xf4, 0x9b, 0xb7, 0xc7, 0xa8, 0xe4, 0x0f, 0x85, 0x30, 0x0a, 0x4d, 0x84, 0x26, 0xc6, 0xd7, 0x76, 0x75, 0x36, 0x35, 0x54, 0x57, 0x16, 0x15, 0x38, 0xed, 0x5a, 0x11, 0x99, 0xb1, 0x59, 0x9f, 0x29, 0x57, 0xa1, 0x24, 0x3b, 0xe2, 0xda, 0x0a, 0x2e, 0x6a, 0xc2, 0x6a, 0x46, 0xae, 0xcd, 0x61, 0xcf, 0xa4, 0xe3, 0xca, 0xd7, 0x85, 0x4c, 0xb2, 0x60, 0xb2, 0x5e, 0x4e, 0xc4, 0xad, 0x57, 0xf3, 0x71, 0x5d, 0xf8, 0xbf, 0x6b, 0x87, 0xeb, 0xbc, 0x3b, 0xb6, 0x6e, 0xd9, 0xe1, 0xad, 0x1b, 0xc6, 0xdb, 0x63, 0xdd, 0xe3, 0x9b, 0xb7, 0x57, 0xd7, 0x2e, 0x0a, 0x74, 0x6f, 0xeb, 0xef, 0xdb, 0x3d, 0x18, 0x7d, 0xa3, 0x7c, 0x97, 0xc9, 0xd2, 0x34, 0x7d, 0xfb, 0x8a, 0xee, 0x96, 0x82, 0xc2, 0xe6, 0xf5, 0x87, 0xaf, 0x3d, 0xbc, 0xbe, 0xb9, 0xe3, 0xc8, 0xb3, 0x7b, 0xf7, 0x7e, 0x71, 0x4f, 0xfd, 0xf8, 0x48, 0x59, 0xad, 0x25, 0x50, 0x5d, 0x34, 0xb8, 0xaa, 0x69, 0xcb, 0x1d, 0x2b, 0x4f, 0xac, 0xc7, 0x1f, 0xf9, 0x12, 0x7d, 0xa5, 0x6b, 0xb6, 0x6d, 0x5b, 0x53, 0xda, 0x9b, 0xf0, 0xcf, 0xfe, 0x30, 0x54, 0xea, 0xd6, 0x0b, 0xbc, 0xe8, 0x19, 0xa8, 0x29, 0x6a, 0x88, 0xbb, 0xdc, 0xe5, 0x6d, 0x5c, 0x59, 0x7f, 0x48, 0x6b, 0x29, 0xec, 0x8e, 0x0c, 0x1e, 0x5a, 0x55, 0x61, 0xce, 0x2b, 0x75, 0x04, 0x8b, 0xcd, 0x9c, 0xd6, 0x95, 0x18, 0xed, 0xed, 0x3c, 0xb2, 0xa1, 0xa9, 0x6c, 0x60, 0x53, 0xdd, 0xc0, 0x16, 0xbf, 0xbd, 0xbb, 0xd1, 0x59, 0x59, 0x59, 0x62, 0x2d, 0xb9, 0x75, 0x62, 0xf0, 0xaa, 0x55, 0x15, 0xf5, 0x64, 0x8f, 0x62, 0x59, 0xea, 0x53, 0xec, 0x8f, 0x38, 0x1d, 0x32, 0x81, 0x2d, 0xf7, 0xa8, 0x4c, 0x04, 0x5b, 0x23, 0x46, 0x82, 0x89, 0x14, 0x0f, 0x8e, 0x02, 0xbb, 0xe7, 0xd3, 0xda, 0x11, 0x5e, 0x7a, 0x55, 0x9c, 0x73, 0x55, 0xd9, 0x4a, 0x8c, 0xa7, 0x0b, 0x42, 0x80, 0x82, 0x19, 0x03, 0xad, 0x46, 0xf6, 0xd1, 0xd4, 0xe3, 0x19, 0x53, 0xd9, 0x25, 0x21, 0xbc, 0x52, 0xb9, 0xda, 0x96, 0xb8, 0x43, 0x54, 0x8e, 0x29, 0xfb, 0x6e, 0xf3, 0xb6, 0x07, 0xcd, 0x68, 0x36, 0x9b, 0x5b, 0xcc, 0xcd, 0xb1, 0x78, 0x30, 0x1c, 0x8b, 0x14, 0xd3, 0xfa, 0x06, 0x73, 0x52, 0x72, 0xb3, 0x3d, 0x03, 0x99, 0x50, 0xad, 0x73, 0x4e, 0x73, 0xd4, 0x27, 0x13, 0x6c, 0x7e, 0xc5, 0xde, 0x2e, 0x92, 0xa4, 0x3b, 0xd1, 0xbc, 0xec, 0x48, 0xf2, 0x6c, 0xc7, 0xe1, 0x67, 0xf7, 0x5d, 0xfb, 0x7c, 0x4b, 0xb9, 0xc6, 0x6d, 0xb3, 0x95, 0x34, 0xaf, 0x68, 0xef, 0xdb, 0xd9, 0x17, 0x0a, 0x2c, 0x1a, 0x5c, 0x99, 0x98, 0xb8, 0x77, 0x4b, 0x3d, 0x49, 0x53, 0xdd, 0x74, 0x73, 0x9d, 0x41, 0x17, 0x67, 0xb9, 0x96, 0xc4, 0xd7, 0x4e, 0x8f, 0xdf, 0xb0, 0x2a, 0x3e, 0xd4, 0xcd, 0x7c, 0xe3, 0x5c, 0xcf, 0xf8, 0xfd, 0x33, 0xed, 0xfd, 0xc9, 0x41, 0x93, 0xd9, 0x17, 0xf0, 0xc5, 0xfb, 0x27, 0x1b, 0xb7, 0x87, 0xeb, 0x43, 0x79, 0x5d, 0xc7, 0xbf, 0x7e, 0xf5, 0xce, 0x57, 0x4f, 0x0e, 0x77, 0xb5, 0xf6, 0x45, 0x65, 0x3d, 0x7b, 0x0b, 0x30, 0xfc, 0x7b, 0xe0, 0x47, 0x39, 0x50, 0x29, 0xd8, 0xfb, 0x98, 0x9c, 0xba, 0x02, 0x21, 0x33, 0xa0, 0xaa, 0x3a, 0x25, 0xa9, 0x49, 0xa9, 0xf3, 0x6b, 0x25, 0x07, 0xa0, 0x68, 0xe1, 0x97, 0x79, 0xf6, 0x06, 0x13, 0x78, 0x28, 0x77, 0xbb, 0xef, 0xcc, 0x3c, 0x67, 0x90, 0xce, 0x92, 0x5a, 0xc3, 0x5f, 0x20, 0xcf, 0x14, 0xbd, 0x40, 0xc7, 0x10, 0xa9, 0xda, 0x28, 0x80, 0x5a, 0x31, 0x63, 0x9e, 0x2d, 0xc2, 0xb2, 0x3f, 0x8a, 0x98, 0x7d, 0xa0, 0x7a, 0x44, 0xb4, 0x4f, 0x3e, 0x3f, 0x93, 0xde, 0xce, 0x56, 0x76, 0xc7, 0x08, 0x10, 0x41, 0x6b, 0x5c, 0x3e, 0xa3, 0x4d, 0x82, 0x91, 0x8c, 0xba, 0x6d, 0x64, 0x9b, 0x0f, 0x2a, 0xa6, 0xea, 0x94, 0x31, 0x94, 0xb7, 0xcb, 0x60, 0xd8, 0x95, 0x17, 0x32, 0xde, 0xfc, 0xbb, 0xd3, 0xfa, 0x39, 0x40, 0x0a, 0x3a, 0xad, 0xeb, 0x44, 0x5e, 0xde, 0x09, 0x97, 0x16, 0x5b, 0xcf, 0x8d, 0xcf, 0x03, 0x30, 0x3e, 0xff, 0x7b, 0x80, 0xf7, 0xa7, 0xf0, 0xcd, 0x8f, 0x1a, 0xa5, 0x64, 0x06, 0x47, 0x20, 0x60, 0x35, 0x98, 0x1c, 0xaf, 0xdd, 0x08, 0x4c, 0x91, 0xc1, 0xd6, 0x74, 0x1a, 0x5b, 0xe4, 0x1f, 0xc1, 0x98, 0x76, 0x81, 0x23, 0x63, 0x72, 0x1a, 0x32, 0x48, 0x46, 0xc0, 0xdc, 0x67, 0x14, 0xa0, 0xa6, 0x01, 0xa8, 0xb3, 0x9f, 0xfc, 0xe4, 0x59, 0x5c, 0x9c, 0xfa, 0x09, 0xbb, 0xa1, 0x73, 0x09, 0x05, 0xe7, 0x2e, 0x66, 0xcf, 0xae, 0x43, 0x3d, 0x6d, 0xb3, 0x95, 0xdc, 0x75, 0x47, 0xbe, 0xf3, 0x9d, 0x23, 0x64, 0x2d, 0xec, 0x02, 0xe2, 0x5d, 0xcd, 0xbf, 0x4c, 0x6b, 0x39, 0x92, 0x2a, 0x91, 0x4a, 0xd2, 0xff, 0x66, 0x7a, 0x14, 0x60, 0x3a, 0x5d, 0xa0, 0xc1, 0x9e, 0xa7, 0x23, 0x69, 0x6a, 0xac, 0x95, 0x27, 0xa9, 0x97, 0xf0, 0x4c, 0xdb, 0xdc, 0x80, 0xdf, 0xca, 0x33, 0xbf, 0x74, 0x86, 0x6b, 0xbc, 0xde, 0xaa, 0xb0, 0xc3, 0x11, 0xae, 0xf2, 0x7a, 0x6b, 0xc2, 0x4e, 0xf6, 0xc3, 0x73, 0x1a, 0xf6, 0x43, 0xdc, 0xe4, 0xad, 0x26, 0xd7, 0xaa, 0xbd, 0x3e, 0xf9, 0x93, 0xe6, 0x1c, 0xb6, 0xa7, 0x9e, 0xc0, 0xa7, 0xe1, 0xb9, 0x36, 0x54, 0x8d, 0xf6, 0x7d, 0x35, 0x80, 0x59, 0xdc, 0xaf, 0x94, 0xcd, 0x16, 0x69, 0x58, 0x33, 0xcb, 0x79, 0x23, 0x7a, 0x9c, 0x6c, 0xd7, 0x64, 0x92, 0x4a, 0x63, 0x48, 0x60, 0xc1, 0x61, 0x23, 0xc9, 0xbc, 0xf3, 0xb4, 0x45, 0x99, 0xa6, 0xa3, 0x92, 0x03, 0x23, 0x92, 0x3e, 0x4a, 0xca, 0xbe, 0xe6, 0x3b, 0xe5, 0x14, 0x68, 0x52, 0x3e, 0xc9, 0x96, 0x93, 0xc4, 0x6b, 0x62, 0x6c, 0x0b, 0xd7, 0x71, 0xfc, 0x20, 0x7b, 0x4a, 0x30, 0xc5, 0x87, 0x95, 0xa8, 0x54, 0xfd, 0x19, 0x6b, 0x4e, 0xb4, 0x8a, 0x7b, 0x2a, 0x6b, 0x8e, 0x3e, 0x32, 0xe7, 0xbf, 0x56, 0xab, 0xf1, 0x27, 0x40, 0xc4, 0x9c, 0x1a, 0xa6, 0x77, 0xc3, 0xff, 0x56, 0xb1, 0x1f, 0x02, 0x4e, 0xdd, 0x92, 0x93, 0xc9, 0xc1, 0x35, 0xc5, 0x33, 0xab, 0xe0, 0x19, 0xaf, 0x3a, 0x2b, 0xa3, 0x11, 0xae, 0xae, 0x4f, 0xdd, 0xc7, 0x7e, 0x9d, 0xe2, 0xac, 0x15, 0x8d, 0xa0, 0x3b, 0x24, 0x53, 0x2b, 0x28, 0xf3, 0x18, 0xa8, 0xfb, 0x02, 0xcc, 0x72, 0xea, 0xfe, 0x41, 0x5c, 0xab, 0x63, 0x08, 0x52, 0xf4, 0x1a, 0x86, 0xa0, 0x85, 0xe4, 0x0e, 0x4e, 0xa7, 0x6b, 0xd5, 0xec, 0x24, 0x68, 0xa2, 0x04, 0x9e, 0x91, 0xf3, 0xe3, 0x2f, 0xd2, 0x76, 0x4f, 0x76, 0xdb, 0x51, 0xa9, 0xb0, 0xbd, 0x0d, 0xa3, 0xfe, 0xde, 0xb6, 0x91, 0xf6, 0x91, 0xc6, 0xfa, 0xda, 0x44, 0x45, 0x59, 0xb8, 0xd8, 0xe7, 0x71, 0xd8, 0x33, 0x25, 0xbe, 0xe6, 0xa2, 0x94, 0x4f, 0xcc, 0x89, 0xe0, 0xa4, 0x45, 0x14, 0x29, 0x3c, 0x42, 0xb1, 0x5b, 0x6f, 0xcb, 0x4a, 0xca, 0x22, 0x7d, 0x7f, 0xa6, 0xe2, 0xda, 0x57, 0x1d, 0x71, 0x3a, 0x23, 0xd5, 0xf8, 0x9f, 0xcf, 0x94, 0x4f, 0xdc, 0x39, 0x19, 0xe8, 0xf6, 0x99, 0x18, 0xad, 0xd5, 0xe7, 0x98, 0xd9, 0xeb, 0x49, 0xb6, 0xf5, 0x97, 0xae, 0xbc, 0x76, 0x45, 0xfc, 0xec, 0xe4, 0x74, 0xb0, 0xbe, 0x22, 0xee, 0x98, 0x54, 0x9a, 0xfa, 0xbc, 0x55, 0x21, 0x87, 0x23, 0x54, 0x75, 0x21, 0x19, 0xbe, 0x7a, 0xce, 0xba, 0xfc, 0xe6, 0x4d, 0x49, 0x9d, 0x8e, 0x44, 0x7e, 0xb6, 0x1c, 0x9c, 0xf0, 0x97, 0xfa, 0xcd, 0xf5, 0x53, 0xa7, 0x56, 0x91, 0x8a, 0xa2, 0x3b, 0x0f, 0xe9, 0x3d, 0x25, 0x85, 0xdc, 0x37, 0xd5, 0xb6, 0x6a, 0x5f, 0x5a, 0xcb, 0xfc, 0xa5, 0xd4, 0x27, 0xe8, 0xda, 0xb0, 0xa2, 0x30, 0x4a, 0x48, 0x55, 0x5a, 0x5a, 0xb9, 0x04, 0x91, 0x82, 0xe0, 0x54, 0xac, 0xf1, 0x32, 0xba, 0x32, 0xe5, 0x06, 0x11, 0x2a, 0x2e, 0xca, 0x77, 0x92, 0x12, 0x91, 0xe1, 0x30, 0x2d, 0xad, 0x34, 0x37, 0x82, 0x73, 0xe1, 0xa2, 0xc9, 0x89, 0xd2, 0xfc, 0x52, 0x9e, 0x46, 0x06, 0x03, 0xec, 0x87, 0x1f, 0x7d, 0x27, 0x2b, 0x1a, 0xc3, 0x35, 0xcc, 0xb7, 0x96, 0xf0, 0xf9, 0x97, 0x01, 0x4e, 0xc2, 0x4b, 0x36, 0x14, 0x91, 0x82, 0x3a, 0x2d, 0x3d, 0x31, 0xc0, 0x5e, 0x08, 0x1c, 0xdc, 0xb7, 0x25, 0xc3, 0x5c, 0x2e, 0x60, 0x4a, 0x6c, 0x0d, 0xaf, 0x9a, 0x13, 0x45, 0xcb, 0x7a, 0x34, 0x8d, 0x97, 0x71, 0x0d, 0x19, 0x39, 0x3f, 0x4b, 0x65, 0x58, 0xb5, 0x54, 0x91, 0x5b, 0x03, 0x54, 0x39, 0x69, 0x42, 0x58, 0x59, 0xa9, 0x8e, 0x05, 0x77, 0xfc, 0xc8, 0x4f, 0x03, 0x25, 0x72, 0x69, 0xc5, 0x8b, 0x39, 0xc5, 0x78, 0xe8, 0xe2, 0x6e, 0x2e, 0x7f, 0x76, 0x61, 0xdf, 0xf5, 0x6c, 0x06, 0x36, 0xc0, 0x83, 0x9f, 0xd4, 0xd0, 0x9c, 0x03, 0x1b, 0xcf, 0xfc, 0xff, 0x05, 0x17, 0xfb, 0x61, 0x6a, 0xd7, 0x02, 0x70, 0x01, 0x2c, 0x04, 0xae, 0x12, 0x60, 0xa6, 0x35, 0x00, 0xa3, 0x19, 0x15, 0x48, 0x5e, 0xb3, 0x41, 0x23, 0x72, 0x48, 0x40, 0x72, 0x7d, 0xd1, 0xcd, 0xca, 0xaa, 0x77, 0x39, 0x48, 0xc9, 0x5a, 0x4c, 0xac, 0x2d, 0x78, 0xa4, 0x5a, 0x57, 0x94, 0x59, 0xe3, 0x7b, 0xf0, 0xec, 0x57, 0x3c, 0x9f, 0x3c, 0xcb, 0xfe, 0x79, 0x6f, 0x50, 0xdf, 0x7d, 0xee, 0xb7, 0xcc, 0x9b, 0x83, 0x07, 0xfc, 0xc6, 0x45, 0xcc, 0xa6, 0xd9, 0x9f, 0x9f, 0x3f, 0x8f, 0x6e, 0x87, 0x71, 0x3f, 0x4b, 0xce, 0xcf, 0x70, 0x07, 0xe4, 0xf3, 0x33, 0xe8, 0x93, 0x2a, 0x1e, 0xf8, 0x3f, 0x73, 0x1a, 0x78, 0xee, 0x98, 0xa4, 0xe7, 0x41, 0xb5, 0xc4, 0xf5, 0x0c, 0xcb, 0x31, 0x4a, 0xc9, 0x13, 0xa7, 0x40, 0xab, 0xdd, 0x32, 0x98, 0x91, 0xfd, 0x71, 0x9e, 0x55, 0xbc, 0x76, 0x0f, 0x92, 0xf3, 0xce, 0x55, 0xe7, 0x7e, 0x77, 0xe6, 0xa6, 0xec, 0xad, 0x97, 0xa0, 0x12, 0x50, 0x99, 0x8e, 0x48, 0x28, 0x20, 0x7b, 0xeb, 0x24, 0xd4, 0xaf, 0x14, 0x5f, 0x60, 0xb3, 0x95, 0x92, 0x29, 0x5b, 0x3d, 0xdd, 0x72, 0x45, 0x5c, 0x67, 0x6a, 0xb8, 0x65, 0x7c, 0xf3, 0x51, 0xbf, 0xb3, 0x67, 0xd5, 0xc6, 0x9a, 0x91, 0x6b, 0xd7, 0x54, 0x9c, 0x9d, 0x5c, 0x5f, 0x32, 0xd4, 0x18, 0x38, 0xbb, 0x6e, 0xac, 0x7d, 0x67, 0x39, 0xfb, 0x61, 0x74, 0xe3, 0xa2, 0x95, 0x7b, 0xd6, 0x27, 0x97, 0xd4, 0xe4, 0x27, 0x36, 0xdd, 0x39, 0x4e, 0x94, 0xe6, 0xbe, 0x03, 0xfe, 0xa6, 0x55, 0x8d, 0xe4, 0xdb, 0x95, 0x33, 0x9d, 0x8d, 0xb3, 0xa6, 0x2c, 0x1b, 0x03, 0xe6, 0xe5, 0x46, 0x5d, 0xcf, 0xdb, 0x68, 0x99, 0x7e, 0x79, 0x46, 0x79, 0x54, 0x65, 0x72, 0x38, 0xcb, 0xc2, 0xf0, 0x4a, 0x4e, 0x59, 0x21, 0xcc, 0x35, 0x3d, 0x46, 0x5f, 0x0c, 0x80, 0x2a, 0x95, 0xab, 0xce, 0xe5, 0x42, 0x2d, 0xab, 0x79, 0x30, 0x61, 0xa5, 0x40, 0xdf, 0xc8, 0x44, 0x5d, 0x96, 0x6e, 0x87, 0x19, 0xb0, 0x3b, 0x82, 0x4d, 0x71, 0x57, 0xae, 0x4e, 0x07, 0xc0, 0x15, 0xb8, 0xc0, 0xce, 0x7f, 0x8f, 0xe2, 0xfb, 0x61, 0x39, 0x40, 0xa2, 0x0b, 0x61, 0x9e, 0xb3, 0x51, 0xe5, 0xee, 0x4d, 0xff, 0x60, 0xd3, 0x55, 0x20, 0x7d, 0xb4, 0x9a, 0xb0, 0x80, 0x95, 0x34, 0xef, 0xa9, 0xb9, 0x95, 0xe9, 0xc9, 0x10, 0x45, 0x60, 0x0b, 0x4c, 0xa7, 0xcb, 0x0e, 0xcf, 0xd7, 0x4e, 0x8a, 0x22, 0x70, 0x22, 0x78, 0x52, 0x7a, 0x7e, 0x9e, 0xa6, 0xb9, 0x95, 0xe7, 0x75, 0x01, 0x5b, 0x38, 0x10, 0x8e, 0x87, 0x44, 0x62, 0x4b, 0xaa, 0xd3, 0x9e, 0x5b, 0xe7, 0xb4, 0x6e, 0x21, 0x34, 0xc4, 0x57, 0x1c, 0xcf, 0x29, 0x76, 0x3a, 0x1f, 0x56, 0x8e, 0x2f, 0xbf, 0x65, 0x32, 0x69, 0xb5, 0xaf, 0x74, 0x9b, 0x8c, 0x53, 0x5b, 0x98, 0x7b, 0xb3, 0x30, 0x44, 0xcf, 0xed, 0x70, 0xc5, 0x80, 0x1f, 0x1b, 0x2a, 0x44, 0xc3, 0xb2, 0x96, 0x72, 0xd1, 0x12, 0xa7, 0x32, 0xc9, 0x78, 0x75, 0x56, 0xd4, 0xaf, 0x9f, 0xf7, 0x0e, 0xc9, 0x79, 0x97, 0xc0, 0x17, 0xc1, 0xc8, 0xeb, 0xb6, 0x17, 0x3a, 0x0a, 0x65, 0xad, 0x43, 0xdf, 0xe5, 0x31, 0x47, 0x83, 0xd8, 0xd4, 0x0c, 0x5e, 0x50, 0xe0, 0x4c, 0xd3, 0xcc, 0xf3, 0xd7, 0x76, 0x75, 0x5d, 0xfb, 0xfc, 0xcc, 0xde, 0xe7, 0xaf, 0xeb, 0xee, 0xbe, 0xee, 0xf9, 0xbd, 0x77, 0x9e, 0x3a, 0x75, 0xe7, 0xdd, 0x77, 0xdc, 0xc1, 0x69, 0xfa, 0x6f, 0xf9, 0xd6, 0x55, 0x57, 0xbd, 0x7e, 0x73, 0x7f, 0xff, 0xcd, 0xaf, 0x5f, 0x75, 0xd5, 0xb7, 0x6e, 0xe9, 0x3f, 0x77, 0xfc, 0xf5, 0xd3, 0xa7, 0xcf, 0x9c, 0x39, 0x7d, 0xfa, 0x75, 0xd0, 0xb3, 0xdd, 0xe7, 0xbf, 0xca, 0x75, 0x70, 0x4b, 0x11, 0x3c, 0x04, 0x25, 0x48, 0xdd, 0xd3, 0x0a, 0x8c, 0x39, 0x1b, 0x91, 0xab, 0xa1, 0x02, 0xa5, 0xee, 0xa9, 0x96, 0xd6, 0x3d, 0xbd, 0xa0, 0xaa, 0xe9, 0xee, 0x4b, 0xd6, 0x3d, 0xcd, 0xb4, 0xa0, 0xa7, 0x8d, 0xb4, 0xa1, 0x58, 0xa8, 0x22, 0x16, 0xa0, 0x05, 0x37, 0xe4, 0xfa, 0x27, 0xb5, 0x95, 0x58, 0x5e, 0x42, 0x76, 0x57, 0xc6, 0xf1, 0xa2, 0x15, 0x4f, 0x33, 0x5e, 0x56, 0x3d, 0x15, 0x55, 0xf7, 0x74, 0x4d, 0xf6, 0xed, 0x19, 0x8a, 0x56, 0x0e, 0x77, 0x6d, 0xed, 0x9f, 0x59, 0x1c, 0xad, 0x65, 0x36, 0xb4, 0x77, 0x92, 0xc9, 0x7e, 0x62, 0x4f, 0xb5, 0xb3, 0xb3, 0xa8, 0xef, 0x9e, 0xce, 0x5e, 0x32, 0xe5, 0xcf, 0xec, 0x95, 0x6c, 0xe1, 0xe0, 0xc6, 0xd3, 0x65, 0x89, 0x8d, 0xb7, 0xad, 0xb9, 0x63, 0x6c, 0xa2, 0xa6, 0x16, 0x3e, 0xef, 0x9b, 0x60, 0x76, 0x77, 0x7d, 0xf3, 0x20, 0xc1, 0x80, 0xb4, 0xa8, 0xd8, 0xfd, 0xed, 0xee, 0xd7, 0x0f, 0x13, 0x2c, 0x74, 0xaf, 0x2e, 0x04, 0xf4, 0xa3, 0xa5, 0xe7, 0x3f, 0x10, 0x6e, 0x02, 0x7a, 0xe9, 0x90, 0x13, 0xf5, 0xa2, 0x5f, 0x0d, 0x9e, 0xae, 0x22, 0x71, 0xb3, 0x16, 0xd0, 0x25, 0x65, 0x21, 0x86, 0xa1, 0x25, 0x6a, 0xc8, 0x2f, 0x56, 0xf9, 0xa5, 0x24, 0x0f, 0x97, 0x21, 0x5e, 0xc7, 0xe8, 0x78, 0x66, 0x46, 0x8f, 0xb1, 0x11, 0xeb, 0x78, 0xac, 0x9b, 0x24, 0x9b, 0x40, 0xf2, 0xf1, 0x8d, 0xdd, 0x83, 0x9c, 0x81, 0x61, 0x98, 0xec, 0xc2, 0x34, 0x55, 0x1f, 0xab, 0x0f, 0xad, 0x47, 0x93, 0xbc, 0xb0, 0x39, 0xca, 0xb4, 0x4e, 0x9f, 0x15, 0x99, 0xdb, 0x91, 0x94, 0xa1, 0x71, 0xb9, 0x0c, 0x7a, 0x84, 0x5c, 0xbd, 0xae, 0x1e, 0xa9, 0xad, 0xa9, 0xa1, 0xaa, 0xa2, 0x24, 0x56, 0x5c, 0xe4, 0xf7, 0xe6, 0x59, 0xf4, 0x4e, 0x83, 0x13, 0x26, 0xaa, 0x0b, 0x99, 0xd2, 0x67, 0x8a, 0x48, 0x95, 0xc9, 0x2c, 0x2e, 0xc2, 0x59, 0x07, 0x89, 0xe4, 0x32, 0x34, 0x39, 0x87, 0x00, 0x33, 0x75, 0x67, 0xf8, 0x7f, 0x2b, 0x68, 0x5c, 0x21, 0xd7, 0x9a, 0x49, 0xd5, 0xde, 0x78, 0xfd, 0xf5, 0x37, 0xde, 0x74, 0xe2, 0x04, 0x33, 0x42, 0xab, 0xce, 0xb4, 0xac, 0x92, 0xca, 0x6d, 0x2e, 0x63, 0x03, 0x2d, 0x3a, 0x73, 0xe0, 0x9b, 0x8d, 0xc9, 0x33, 0x37, 0x4e, 0x3d, 0x76, 0x65, 0x67, 0xe7, 0x55, 0x5f, 0xda, 0x72, 0xfc, 0x1b, 0xc9, 0xda, 0x97, 0x4f, 0x2c, 0xbf, 0x63, 0x5b, 0x4b, 0x9e, 0x97, 0x19, 0x55, 0x0b, 0xcb, 0xa4, 0xaa, 0x55, 0x7e, 0xc4, 0xab, 0x48, 0x8d, 0x19, 0x7a, 0xa0, 0x88, 0x9c, 0x14, 0xdb, 0xb3, 0x7f, 0xd7, 0x15, 0x2a, 0xd3, 0xee, 0xdf, 0xbe, 0xf9, 0x2a, 0xa5, 0xa8, 0x0c, 0xb5, 0xa9, 0x6f, 0x49, 0x3d, 0xc9, 0xd9, 0x38, 0x3d, 0xf8, 0x43, 0x51, 0x74, 0x5c, 0xd2, 0x9b, 0xc8, 0x4b, 0x4c, 0x02, 0xb4, 0xa2, 0x8c, 0x5a, 0x41, 0x86, 0x1c, 0x0d, 0x25, 0x8b, 0x6a, 0x77, 0x76, 0xad, 0x4e, 0x8e, 0xdb, 0x9a, 0xce, 0x34, 0x9e, 0xaf, 0xc9, 0x1e, 0xb5, 0x09, 0xad, 0x20, 0x33, 0x3d, 0xb7, 0xbe, 0x4c, 0x56, 0x0b, 0x52, 0x41, 0x26, 0x60, 0x8b, 0x45, 0x23, 0x39, 0x15, 0x64, 0xe4, 0x73, 0x5a, 0xd1, 0xf4, 0xeb, 0x85, 0xa8, 0xd4, 0x51, 0x84, 0xcc, 0x50, 0xe6, 0x1c, 0xe9, 0x35, 0x75, 0xba, 0xa2, 0xfe, 0x15, 0xeb, 0x93, 0xab, 0x6f, 0x9f, 0xaa, 0x6f, 0xdd, 0xfb, 0xf0, 0xf4, 0xd8, 0xf1, 0x04, 0x91, 0x31, 0x7f, 0x4d, 0x1f, 0x24, 0xed, 0xb9, 0x21, 0xd2, 0x5e, 0xee, 0xee, 0x3a, 0xf1, 0xfa, 0xd1, 0xbd, 0xdf, 0x3a, 0x35, 0xd2, 0x92, 0x4c, 0xf5, 0xf2, 0x8b, 0xa3, 0xe9, 0x79, 0xd3, 0xf3, 0xa2, 0x41, 0x34, 0x29, 0x19, 0xcc, 0xa6, 0x4c, 0x29, 0x9d, 0xf4, 0x4a, 0xbd, 0xb0, 0x2e, 0x4e, 0xf6, 0xe4, 0x17, 0x6c, 0x91, 0x9e, 0x99, 0xa4, 0xb5, 0xda, 0x62, 0xe1, 0xf9, 0x26, 0x96, 0x53, 0x1b, 0x47, 0x9e, 0x58, 0xee, 0xb4, 0xda, 0x3f, 0xbf, 0x55, 0x9d, 0xd3, 0xc4, 0x75, 0x89, 0x33, 0x67, 0xb2, 0x66, 0x74, 0x67, 0xef, 0xaa, 0x45, 0xc7, 0xe5, 0xf9, 0x74, 0xb5, 0xa4, 0x1a, 0x45, 0x37, 0x92, 0xe3, 0x13, 0x4f, 0xb2, 0x3f, 0x52, 0xe8, 0xf8, 0x90, 0x52, 0x21, 0x8a, 0x1c, 0xfc, 0xcd, 0x53, 0x0e, 0xc1, 0x7a, 0xe9, 0x4f, 0x5e, 0xfd, 0xa9, 0xac, 0x3f, 0x1f, 0x3d, 0x50, 0x47, 0xeb, 0x5c, 0xe7, 0xd4, 0xd7, 0x52, 0x4f, 0xf0, 0x79, 0x16, 0x6a, 0xb3, 0x27, 0x5d, 0xae, 0x95, 0x1e, 0x16, 0x9e, 0xce, 0x3d, 0x4b, 0x9c, 0xd3, 0x64, 0x54, 0x3e, 0x2d, 0x1c, 0x35, 0x47, 0x62, 0x41, 0xfb, 0x02, 0xa7, 0x85, 0xad, 0xd9, 0xeb, 0xa3, 0x80, 0x91, 0x23, 0x11, 0xe5, 0xfb, 0xbb, 0x32, 0xc7, 0x85, 0xcf, 0xb6, 0x3f, 0xb4, 0x4d, 0x3d, 0x1b, 0x5b, 0xbd, 0xac, 0xa7, 0xd9, 0x63, 0xd4, 0xc5, 0x59, 0xa6, 0xb5, 0x56, 0x3d, 0x30, 0xcc, 0xfc, 0xeb, 0x5f, 0x9f, 0xc3, 0x3f, 0xef, 0x5b, 0xad, 0x1e, 0x18, 0xce, 0x2b, 0xae, 0x2a, 0xec, 0xcf, 0xc4, 0x19, 0x98, 0x7f, 0x02, 0xdc, 0xe4, 0xa3, 0x4f, 0xc8, 0x53, 0xd2, 0x09, 0x20, 0x97, 0xac, 0xe0, 0x3f, 0x32, 0x03, 0x4a, 0x11, 0x64, 0x72, 0x85, 0x53, 0xae, 0x90, 0x1f, 0x8c, 0xf2, 0x63, 0x54, 0x09, 0xdd, 0xe4, 0xc9, 0x25, 0xc2, 0x65, 0x15, 0x83, 0x64, 0xe3, 0x47, 0xee, 0x29, 0xdb, 0x0c, 0xe9, 0x0a, 0xe2, 0xe9, 0xbb, 0x92, 0x3f, 0x9d, 0x6f, 0x9b, 0x6b, 0x54, 0xc8, 0xf7, 0x47, 0xc1, 0xa8, 0x70, 0x14, 0xdb, 0x33, 0x11, 0x0d, 0xa7, 0x4b, 0x14, 0x80, 0x17, 0xa8, 0x28, 0x51, 0xeb, 0x86, 0x77, 0x54, 0x9f, 0xdc, 0x18, 0x68, 0x76, 0x1a, 0x45, 0x8f, 0xa9, 0xa1, 0xa8, 0xb2, 0xaf, 0xb1, 0xcc, 0x76, 0x66, 0xa6, 0xc8, 0xc5, 0xb3, 0x75, 0x53, 0xfb, 0xcc, 0xe6, 0x7b, 0xbc, 0x7a, 0x6f, 0xdd, 0xe2, 0xea, 0xd9, 0x13, 0xec, 0x87, 0x2e, 0x3f, 0x9d, 0x67, 0xdb, 0xf9, 0xf7, 0x59, 0x2f, 0xc8, 0xe0, 0x56, 0x52, 0xdf, 0xb5, 0x15, 0x63, 0xbe, 0x00, 0xe6, 0xb1, 0x60, 0x7d, 0xd7, 0xdd, 0xd9, 0xf5, 0x5d, 0x9b, 0x1b, 0x63, 0x91, 0x60, 0xe0, 0xa2, 0xf5, 0x5d, 0x23, 0x0b, 0x55, 0x18, 0x65, 0x7e, 0xaa, 0xc9, 0x33, 0x59, 0xa2, 0xf5, 0x03, 0x89, 0x92, 0xee, 0x2a, 0x4f, 0x79, 0xff, 0xc4, 0xc6, 0x89, 0xfe, 0xf2, 0xc4, 0xe4, 0xdd, 0xeb, 0xb6, 0x3d, 0xdb, 0x52, 0xa1, 0xc9, 0xb7, 0xdb, 0x62, 0x0d, 0x4b, 0x5b, 0x6a, 0x06, 0xaa, 0xf3, 0xcb, 0xfb, 0xd7, 0x6d, 0x5a, 0xd7, 0x5f, 0x5e, 0x3d, 0x71, 0xcb, 0x9a, 0xcd, 0x0f, 0x37, 0xb1, 0xfd, 0x46, 0xbd, 0xd3, 0xe3, 0x74, 0x84, 0x13, 0xfe, 0x48, 0x4d, 0xc8, 0x5f, 0x14, 0x6f, 0x5d, 0xd5, 0xd6, 0x7b, 0x68, 0x4d, 0x4d, 0x8f, 0x1c, 0x38, 0xf2, 0xba, 0x4b, 0x1b, 0x0a, 0x63, 0x75, 0x11, 0x5f, 0x51, 0x89, 0x34, 0xde, 0xd9, 0xbc, 0x73, 0x59, 0xb5, 0xd4, 0x40, 0xe6, 0x98, 0xba, 0x9f, 0xce, 0x71, 0x0d, 0x7a, 0x42, 0xf6, 0xfd, 0xad, 0x6b, 0x30, 0x16, 0x1b, 0x93, 0x75, 0x3e, 0x8e, 0x15, 0x48, 0x55, 0x57, 0xb2, 0xa5, 0x3c, 0xe7, 0x1a, 0x97, 0x55, 0x15, 0x09, 0x61, 0x1e, 0x96, 0xc1, 0x0c, 0xe2, 0x49, 0x81, 0xa3, 0xdd, 0x48, 0x60, 0xb1, 0xc0, 0xee, 0xce, 0x04, 0x41, 0x69, 0x41, 0xd7, 0x71, 0x35, 0x08, 0x57, 0xb3, 0x40, 0xf3, 0x34, 0x22, 0xd5, 0xd0, 0x29, 0x20, 0x72, 0x5c, 0x8d, 0xc4, 0x59, 0xe5, 0x62, 0xae, 0x6a, 0x29, 0x57, 0xcd, 0xdf, 0xaf, 0x94, 0xeb, 0xbc, 0x88, 0x2e, 0x5d, 0x7e, 0x78, 0x78, 0x81, 0xb7, 0xf9, 0xac, 0xfd, 0x44, 0xdd, 0xc7, 0xc4, 0x7f, 0xc7, 0xcc, 0xaa, 0x06, 0x4d, 0xd3, 0x7c, 0x6f, 0xf6, 0xd1, 0x74, 0x56, 0x2d, 0x4c, 0x16, 0x99, 0xf7, 0x84, 0x6a, 0xc1, 0x87, 0x0e, 0xe3, 0x56, 0xc9, 0xdc, 0x82, 0x45, 0x7e, 0x33, 0x60, 0x67, 0x14, 0x4c, 0x1f, 0x1a, 0x64, 0xd0, 0xd3, 0x2d, 0x5e, 0xa4, 0x61, 0x35, 0x32, 0x1f, 0x6a, 0x08, 0xfa, 0x04, 0x64, 0xe0, 0x05, 0xc3, 0x64, 0x86, 0x1f, 0xf5, 0x58, 0x79, 0xd1, 0xdc, 0xf4, 0x20, 0x79, 0x11, 0x0f, 0x33, 0xa6, 0xc3, 0xb2, 0x51, 0x34, 0x78, 0xda, 0x4a, 0x0b, 0xa8, 0xcc, 0x61, 0x64, 0x65, 0x00, 0x1a, 0x89, 0x50, 0x87, 0x20, 0xdf, 0x17, 0x18, 0x23, 0xff, 0x7f, 0x07, 0x84, 0xb4, 0x28, 0xd3, 0x57, 0xab, 0x61, 0xb5, 0x17, 0xef, 0x8b, 0xb4, 0x5a, 0xa5, 0xab, 0xbc, 0xcc, 0x94, 0x51, 0xc8, 0x56, 0xb1, 0x77, 0xef, 0x9e, 0x8d, 0x1b, 0x26, 0xc6, 0x48, 0x8e, 0x1c, 0x39, 0x97, 0x96, 0x59, 0x7a, 0xc6, 0x8f, 0xb9, 0xf4, 0x16, 0x2c, 0xbf, 0x72, 0xa9, 0x32, 0x1a, 0x7f, 0xcb, 0x8a, 0xad, 0xaf, 0x2f, 0xab, 0x58, 0x7f, 0xc7, 0xa6, 0xf5, 0xb7, 0xae, 0x2d, 0x5b, 0xb0, 0x78, 0x86, 0xc5, 0xa8, 0xf1, 0x34, 0xae, 0x69, 0xeb, 0xdb, 0xd9, 0x17, 0xec, 0x68, 0xd3, 0x5a, 0x35, 0x1f, 0x7b, 0x89, 0xeb, 0x57, 0x6d, 0xec, 0x3f, 0x3e, 0xd9, 0xd4, 0x38, 0x71, 0x55, 0xdb, 0xfc, 0x05, 0x33, 0xfc, 0x1d, 0x9d, 0x8b, 0x0a, 0x2a, 0x46, 0x5a, 0x8a, 0xcb, 0x86, 0xa6, 0x1a, 0x7a, 0x6f, 0x6a, 0x12, 0x4d, 0x1a, 0x59, 0xc6, 0x83, 0x5c, 0xa0, 0xfc, 0x77, 0x1f, 0xfe, 0xb4, 0x4c, 0x69, 0xf7, 0x6a, 0xac, 0x15, 0xaf, 0xc3, 0x58, 0x7b, 0xa0, 0x31, 0xc9, 0x1a, 0x68, 0x7d, 0x67, 0x43, 0x94, 0x16, 0x84, 0xf2, 0x2e, 0x74, 0x8f, 0xa3, 0xc5, 0xa2, 0xf4, 0xf4, 0x38, 0x23, 0xd2, 0xf1, 0x3a, 0x79, 0xe1, 0xeb, 0xc8, 0xc2, 0xd7, 0x20, 0x93, 0xa8, 0x31, 0x65, 0xed, 0x96, 0x80, 0x29, 0xa9, 0xd3, 0x8e, 0xd2, 0x02, 0x4b, 0x7a, 0xb0, 0x18, 0x69, 0x96, 0xe4, 0xce, 0x5c, 0xf1, 0x21, 0x73, 0x6d, 0xcf, 0x5c, 0x19, 0xa2, 0x0c, 0x65, 0xd4, 0x31, 0x99, 0xc1, 0xc8, 0xf7, 0x4b, 0x8e, 0x96, 0xff, 0xf7, 0x02, 0x4c, 0x1a, 0xc9, 0x8c, 0xa2, 0xd7, 0xf1, 0x7a, 0xe0, 0x64, 0xbd, 0x41, 0x2f, 0x18, 0x80, 0xb1, 0x0d, 0x9c, 0x81, 0xec, 0x4f, 0x5c, 0x6c, 0x54, 0xa4, 0xd7, 0x2b, 0x83, 0x66, 0x8b, 0x3d, 0x52, 0xd0, 0xff, 0xb6, 0x93, 0x47, 0x0e, 0x5f, 0x75, 0xe5, 0xce, 0xed, 0x5b, 0xa6, 0x37, 0x4f, 0xad, 0x9f, 0xc8, 0x15, 0x83, 0xe6, 0xbf, 0x97, 0x18, 0xfc, 0xfb, 0x32, 0xfd, 0xdf, 0x26, 0x3d, 0xff, 0x5e, 0x6b, 0xa1, 0x63, 0x66, 0x75, 0xfd, 0xbc, 0xe2, 0xf6, 0x1a, 0x4d, 0x47, 0xd5, 0xdf, 0x73, 0x89, 0x60, 0x74, 0xf3, 0xf9, 0xf7, 0xb9, 0x55, 0xfc, 0x73, 0x68, 0x00, 0x5d, 0x45, 0x03, 0x0a, 0xcf, 0x7b, 0x31, 0x79, 0x5d, 0x8c, 0x17, 0x3e, 0x79, 0x16, 0xab, 0x51, 0x86, 0x42, 0x04, 0xf2, 0x9a, 0x63, 0xa8, 0xcd, 0x22, 0x47, 0x1b, 0x76, 0xc8, 0x51, 0x01, 0x5e, 0xdd, 0x42, 0x0f, 0xce, 0xd7, 0x22, 0x3b, 0x78, 0x40, 0x77, 0xcd, 0x6d, 0x18, 0x75, 0x77, 0xb5, 0xb7, 0x86, 0x83, 0x45, 0x05, 0x6e, 0x17, 0x1a, 0xc0, 0x03, 0x62, 0xc6, 0xed, 0xe6, 0x80, 0xf6, 0xdc, 0xc2, 0x72, 0x8d, 0x4b, 0x93, 0x8d, 0xc4, 0x58, 0xda, 0x18, 0xe6, 0xaa, 0xed, 0xf7, 0x4f, 0x56, 0x95, 0xb4, 0xf7, 0xb7, 0x97, 0xd4, 0x0e, 0x8f, 0x0d, 0xd7, 0x36, 0x4d, 0xdf, 0x32, 0xbc, 0xe1, 0xf3, 0x2d, 0xe5, 0xda, 0x7c, 0x9b, 0x2d, 0x5e, 0xd7, 0x9f, 0x88, 0x75, 0x56, 0xb8, 0x8b, 0xea, 0x16, 0xf5, 0x2e, 0xaa, 0x2b, 0x2a, 0xeb, 0x5c, 0xd2, 0x59, 0xe6, 0x4e, 0xae, 0x6e, 0x5d, 0xb4, 0x73, 0x28, 0x7e, 0xb6, 0xe7, 0xf8, 0x8b, 0xfc, 0x73, 0x6d, 0xeb, 0xf7, 0x26, 0x9a, 0x57, 0x76, 0xd4, 0x54, 0x54, 0x17, 0x46, 0x12, 0xe5, 0x55, 0xbd, 0x5b, 0x17, 0x2f, 0x3d, 0xb2, 0xaa, 0x3c, 0x2d, 0x7f, 0xca, 0x5a, 0x82, 0xb1, 0xa6, 0xb2, 0xe2, 0x60, 0xac, 0xae, 0xaf, 0xa6, 0x7e, 0xb8, 0xbd, 0xae, 0xa9, 0xbb, 0xb2, 0xa8, 0xbd, 0xa6, 0xb0, 0x64, 0xd9, 0x35, 0xcb, 0xce, 0xc5, 0xd9, 0x1f, 0x6f, 0x7b, 0xfa, 0x48, 0x97, 0x9c, 0xd3, 0xc2, 0x3c, 0x0b, 0xb8, 0xf3, 0x92, 0x77, 0xfd, 0x68, 0x44, 0x06, 0x23, 0x07, 0x2d, 0x3a, 0xa9, 0x46, 0x4a, 0x29, 0x8e, 0xa6, 0x95, 0x3a, 0xe3, 0x4e, 0x47, 0x1d, 0x7d, 0xf9, 0x8a, 0x4d, 0xdd, 0x8b, 0x66, 0xd2, 0xd1, 0x2d, 0x79, 0xd7, 0xfa, 0xfb, 0x64, 0xb7, 0xb9, 0xab, 0x45, 0x24, 0x7b, 0xcb, 0x79, 0xe5, 0x7d, 0x35, 0x1e, 0x9a, 0x01, 0xc0, 0x75, 0xd1, 0xad, 0xe2, 0x69, 0xbd, 0x96, 0xc3, 0xf9, 0xc9, 0xd5, 0xed, 0x1f, 0xbd, 0xae, 0x6c, 0x29, 0xc3, 0xf3, 0xe3, 0xe7, 0xdf, 0x67, 0x5e, 0xe7, 0x5b, 0x51, 0x31, 0x6a, 0x91, 0x1a, 0xb5, 0x40, 0xb7, 0x22, 0x90, 0x27, 0xe4, 0x18, 0x06, 0xcb, 0xec, 0x23, 0x5b, 0x2e, 0xb4, 0xf2, 0xf1, 0x34, 0x3d, 0x8c, 0x31, 0x2e, 0xc8, 0x35, 0xbf, 0xc8, 0x3b, 0x06, 0xe8, 0x29, 0x2e, 0x52, 0x14, 0xba, 0x18, 0x17, 0x13, 0xfc, 0xf3, 0x99, 0xc5, 0xb1, 0xc0, 0xbe, 0x38, 0xfe, 0xf6, 0xe1, 0x1b, 0xae, 0xb4, 0xba, 0x1e, 0x25, 0x7b, 0xdd, 0x93, 0x84, 0xb3, 0x9b, 0x95, 0xbd, 0x6e, 0x3d, 0x70, 0xf6, 0x09, 0x8b, 0xc2, 0x61, 0x2e, 0x5c, 0x79, 0xc1, 0x2e, 0x36, 0xcd, 0x6b, 0x8e, 0x82, 0xfd, 0x59, 0xcb, 0xbf, 0x81, 0x22, 0xe8, 0x48, 0x4a, 0x7e, 0x4f, 0x51, 0xb9, 0x5c, 0xa3, 0x1d, 0xe0, 0xff, 0x07, 0x80, 0xbf, 0x04, 0x49, 0x52, 0xab, 0x08, 0xf0, 0x16, 0xd3, 0x83, 0xc5, 0x14, 0x7e, 0x10, 0x8d, 0x5a, 0x2c, 0x88, 0x48, 0xd8, 0x98, 0x3d, 0x13, 0x51, 0xd4, 0x8c, 0x23, 0x8d, 0x66, 0xa7, 0x66, 0xa8, 0x24, 0x5e, 0x12, 0x24, 0x7b, 0x59, 0x31, 0x2b, 0x2d, 0xee, 0x6b, 0x4b, 0xd4, 0xcd, 0x99, 0x81, 0x63, 0x01, 0x21, 0xd0, 0x7e, 0x6b, 0xce, 0x4c, 0x70, 0x28, 0xf5, 0xa3, 0x9c, 0xd9, 0xc0, 0x3a, 0xbd, 0x95, 0x79, 0xfe, 0xd1, 0xac, 0xf9, 0x3c, 0x7a, 0x24, 0xf5, 0x83, 0xec, 0xe5, 0x84, 0xdb, 0x65, 0xdc, 0xf3, 0x2b, 0x72, 0x72, 0x54, 0x4a, 0x31, 0x27, 0x78, 0x81, 0x00, 0x66, 0x0c, 0xc6, 0xfd, 0x40, 0x19, 0xe6, 0x68, 0x8e, 0x4a, 0xe6, 0xaa, 0x4e, 0xbd, 0x9a, 0xce, 0x51, 0xe1, 0x04, 0x51, 0xa0, 0xa5, 0x35, 0x45, 0xba, 0x87, 0xbb, 0x63, 0x50, 0x4b, 0xd2, 0x76, 0xf8, 0x51, 0x0d, 0x56, 0x5d, 0xe3, 0x71, 0x9a, 0x86, 0x42, 0xad, 0x8c, 0xaa, 0x4b, 0x34, 0xd7, 0xcb, 0x45, 0xfc, 0x48, 0x27, 0xf5, 0x45, 0x38, 0xe9, 0x1c, 0x15, 0x47, 0x5d, 0xd8, 0x1a, 0x23, 0xa5, 0xb5, 0x0c, 0x4a, 0x16, 0xf0, 0x42, 0xc2, 0x51, 0xc1, 0xde, 0x7c, 0x47, 0xa1, 0xd9, 0x99, 0x94, 0x67, 0x2e, 0xa2, 0x32, 0x98, 0xfc, 0xf9, 0x7c, 0xc7, 0xa1, 0x2f, 0x4c, 0x52, 0xc9, 0x60, 0x75, 0xfe, 0x02, 0x22, 0x0c, 0x8a, 0xa5, 0x9e, 0xe0, 0xbe, 0x48, 0xf7, 0xa5, 0x96, 0xa1, 0x0f, 0x25, 0x73, 0x23, 0x3d, 0x88, 0xc5, 0x02, 0x57, 0x77, 0x66, 0xb6, 0xf5, 0xea, 0xc8, 0x19, 0x04, 0xac, 0x41, 0x33, 0x86, 0xcc, 0x96, 0x1d, 0x39, 0xcc, 0xa6, 0x2a, 0x22, 0x9e, 0x67, 0x81, 0xdf, 0xc9, 0xf6, 0x1d, 0x41, 0x4a, 0x56, 0xe1, 0x99, 0x02, 0x6a, 0x2d, 0x2a, 0x9d, 0x91, 0x9e, 0xd5, 0xb1, 0x7a, 0xdd, 0xa5, 0x07, 0x41, 0x59, 0x63, 0x48, 0x52, 0xa6, 0xbb, 0xa0, 0x13, 0x16, 0xee, 0xae, 0x6c, 0x1e, 0x0a, 0xe3, 0x40, 0xa9, 0x9d, 0xd9, 0x43, 0x10, 0x85, 0x88, 0x11, 0x18, 0x79, 0x03, 0x1d, 0x52, 0x7b, 0x6b, 0x5d, 0x6d, 0x75, 0x25, 0x48, 0xc3, 0xac, 0x1d, 0x45, 0x23, 0x91, 0x88, 0x17, 0xd9, 0x51, 0xbc, 0x24, 0xf1, 0x72, 0x77, 0x1c, 0xd9, 0x5f, 0x66, 0x36, 0x81, 0xc8, 0x96, 0x63, 0x35, 0xdd, 0x72, 0x9c, 0x5e, 0xdc, 0xd6, 0x90, 0x62, 0xe6, 0xd1, 0x4f, 0x8f, 0x95, 0x91, 0xfc, 0x22, 0xe6, 0x75, 0x75, 0x3b, 0xd2, 0x67, 0xbf, 0xd4, 0x76, 0xe4, 0x45, 0x68, 0x9c, 0x93, 0x3f, 0x2f, 0xaf, 0x19, 0x6e, 0x08, 0xd6, 0x7b, 0x08, 0x6d, 0x90, 0x74, 0x3e, 0x8c, 0x05, 0x0b, 0x7d, 0xf3, 0x96, 0x1a, 0xc8, 0xe6, 0x05, 0x9e, 0x8a, 0x2d, 0xfa, 0x7a, 0x06, 0xd9, 0x33, 0x1e, 0x67, 0xe5, 0x72, 0xf5, 0x5e, 0xa9, 0xe8, 0x82, 0xdb, 0x70, 0x83, 0xb4, 0x61, 0xc7, 0x95, 0x77, 0xc0, 0x48, 0x3a, 0x99, 0xc7, 0x2d, 0x24, 0x30, 0x1d, 0xb8, 0x0c, 0x44, 0x71, 0x43, 0x67, 0x53, 0xcc, 0x3c, 0x2c, 0xfd, 0x58, 0x29, 0x61, 0x69, 0xe6, 0xf5, 0x8b, 0x4d, 0x8d, 0xca, 0x2f, 0x22, 0x03, 0x86, 0x61, 0x3e, 0x6d, 0x68, 0x48, 0xea, 0x27, 0xf2, 0xb7, 0x25, 0xca, 0x20, 0x10, 0x60, 0x1c, 0xd1, 0x02, 0xfb, 0xb4, 0xe4, 0xbd, 0x6f, 0xec, 0x28, 0x4f, 0x4f, 0xd1, 0xea, 0x48, 0x8d, 0x82, 0x71, 0x8d, 0x9e, 0x51, 0xa4, 0x70, 0x6d, 0x4d, 0x65, 0x45, 0x69, 0xbc, 0xd0, 0x2f, 0xbf, 0x8e, 0x47, 0x91, 0xc7, 0x6d, 0xb8, 0xcd, 0x30, 0xaf, 0x3c, 0xbe, 0x78, 0xba, 0xd2, 0x45, 0x44, 0xf5, 0x0f, 0x95, 0x74, 0xb1, 0x0b, 0x6d, 0x91, 0x54, 0xfd, 0xc5, 0x64, 0xf8, 0x02, 0xf9, 0x48, 0x39, 0x92, 0x9d, 0xce, 0x5f, 0xfc, 0x29, 0xc8, 0xc0, 0xd5, 0xe8, 0x0b, 0x92, 0x7d, 0x35, 0xd6, 0x9b, 0x25, 0x10, 0x7d, 0xb5, 0x85, 0x60, 0x4b, 0xb3, 0xa0, 0x03, 0xf5, 0x64, 0xb9, 0x12, 0xca, 0x56, 0x19, 0xe1, 0x16, 0x32, 0xeb, 0x11, 0xc9, 0x6f, 0x31, 0x8f, 0x21, 0xb3, 0x79, 0xc7, 0xa0, 0x4e, 0x24, 0xef, 0x7d, 0xc3, 0xa3, 0x26, 0x03, 0xdd, 0x4b, 0x23, 0x22, 0x8c, 0x1d, 0x07, 0x8c, 0x29, 0x31, 0x92, 0xda, 0x4b, 0x77, 0xa1, 0x2b, 0x8c, 0xf4, 0x13, 0xc6, 0xb5, 0x72, 0x1c, 0x78, 0x54, 0xf2, 0xac, 0x5a, 0x31, 0xbc, 0x64, 0xf1, 0x60, 0x5f, 0x6f, 0x4f, 0x57, 0x4b, 0x73, 0x43, 0x32, 0x68, 0xa3, 0x3a, 0xc2, 0x91, 0xb0, 0xc8, 0xe5, 0xdf, 0x73, 0x13, 0x65, 0x2f, 0x89, 0xca, 0xe0, 0x42, 0x74, 0x50, 0xc5, 0xe3, 0x8f, 0x72, 0x2a, 0x23, 0x9d, 0x4d, 0x79, 0x2e, 0x81, 0xe4, 0x6c, 0xf6, 0x52, 0x49, 0xf3, 0x73, 0x39, 0xc5, 0x8f, 0x6f, 0xcd, 0x96, 0x84, 0x17, 0x72, 0xde, 0xc2, 0x9c, 0x28, 0x93, 0x4a, 0x16, 0xa7, 0xb2, 0xfc, 0x14, 0x03, 0x54, 0x7e, 0xee, 0xc6, 0x79, 0x92, 0x65, 0x09, 0xe6, 0x0c, 0x9d, 0xe5, 0x24, 0xa3, 0x6b, 0x60, 0x5d, 0x46, 0x80, 0x36, 0x01, 0x7a, 0x41, 0x8a, 0x01, 0x7a, 0xad, 0x19, 0xe9, 0x45, 0xcf, 0x91, 0x18, 0x46, 0xc9, 0x61, 0x9f, 0x5c, 0x82, 0x0c, 0x5a, 0xb0, 0xd9, 0x3c, 0x47, 0x8a, 0x76, 0xa4, 0x47, 0x40, 0x16, 0xd6, 0xcc, 0x5a, 0xcc, 0x97, 0x37, 0x12, 0xca, 0x1a, 0x48, 0xea, 0xce, 0x1a, 0x43, 0x30, 0x0b, 0x17, 0x19, 0x63, 0x2e, 0xad, 0xb3, 0xc7, 0x01, 0x79, 0x1a, 0xc7, 0x68, 0xd7, 0x8e, 0xad, 0xd3, 0x13, 0xe3, 0x63, 0xa3, 0xa4, 0x64, 0xed, 0x40, 0x7f, 0x4f, 0x57, 0x53, 0xc3, 0x85, 0x92, 0x35, 0xef, 0x6f, 0x90, 0xac, 0x97, 0xcb, 0x12, 0xb9, 0x42, 0x57, 0xd8, 0x38, 0x27, 0xcf, 0xe3, 0x51, 0x25, 0xcf, 0xa3, 0x31, 0xe5, 0xbc, 0x20, 0x4d, 0xb0, 0x7e, 0x41, 0x31, 0x8c, 0x7f, 0x48, 0x93, 0x0b, 0x99, 0xa7, 0xcd, 0x97, 0x9d, 0x1c, 0xf2, 0xb1, 0x19, 0x67, 0x1e, 0x19, 0x2d, 0x10, 0x9b, 0x4c, 0x42, 0xfb, 0x25, 0x7d, 0x35, 0x78, 0x9f, 0x61, 0x0b, 0x93, 0x11, 0xd2, 0x01, 0x1d, 0xd6, 0x68, 0x35, 0xfb, 0x68, 0xd1, 0x51, 0xed, 0x28, 0xc9, 0x35, 0x9e, 0xa6, 0x1b, 0x6b, 0x60, 0x5f, 0x12, 0x5b, 0x84, 0x4a, 0xea, 0xf0, 0x3c, 0x6d, 0xa8, 0xb0, 0x26, 0x0d, 0x65, 0x9d, 0x4a, 0xc5, 0x75, 0x9e, 0xd4, 0xde, 0xd6, 0xd2, 0xd8, 0x40, 0x64, 0x36, 0x15, 0xdb, 0xd4, 0x36, 0xf9, 0xdf, 0x51, 0x41, 0xf8, 0x87, 0xb3, 0x80, 0xe0, 0x8f, 0xbd, 0x08, 0x99, 0x67, 0x3e, 0x3e, 0xde, 0x88, 0xfc, 0x3f, 0x0a, 0x76, 0xed, 0x3b, 0xec, 0x87, 0xc8, 0x8d, 0x16, 0xbf, 0x90, 0xcf, 0x93, 0xa0, 0xae, 0x82, 0x26, 0x1b, 0xcf, 0x31, 0x34, 0x65, 0x82, 0x6c, 0x3f, 0x62, 0x55, 0x85, 0xb9, 0xd4, 0xab, 0x54, 0x73, 0x65, 0xbb, 0x45, 0xe4, 0x75, 0xad, 0x6e, 0xe4, 0x0e, 0x3a, 0x22, 0x01, 0xe5, 0x3d, 0x9c, 0xc5, 0x95, 0x73, 0x12, 0x5c, 0x92, 0xf5, 0x56, 0xf6, 0x9d, 0x2b, 0x62, 0x06, 0x53, 0xa0, 0x6f, 0xd9, 0x78, 0x7a, 0x43, 0xf9, 0x78, 0x23, 0x49, 0xe8, 0x89, 0x4e, 0xab, 0x5b, 0xa7, 0x24, 0x0d, 0x65, 0xd7, 0xa1, 0xde, 0x36, 0xe6, 0x97, 0x14, 0x3e, 0xb0, 0xa5, 0x3a, 0x01, 0xbe, 0x6a, 0xb4, 0x59, 0x32, 0x14, 0x1a, 0x00, 0xc0, 0x62, 0x2d, 0x29, 0x6c, 0xaf, 0x1c, 0x16, 0xf4, 0x71, 0x58, 0x01, 0x52, 0x86, 0x44, 0x7e, 0x69, 0xf4, 0xb8, 0xfa, 0xea, 0xda, 0x20, 0xa9, 0xf5, 0x8e, 0x05, 0x7e, 0x66, 0x6e, 0x33, 0x94, 0x69, 0x05, 0x4a, 0x37, 0x18, 0xb3, 0x05, 0x1d, 0x65, 0xc5, 0x74, 0x37, 0x78, 0x9e, 0xb7, 0x6a, 0x64, 0xc0, 0x4f, 0xe4, 0xbc, 0x49, 0x83, 0xeb, 0x94, 0x97, 0x44, 0x7b, 0x58, 0x37, 0x77, 0x4a, 0x67, 0xe6, 0x7f, 0xc3, 0x2d, 0x1b, 0x9a, 0x67, 0x9a, 0x17, 0xe4, 0x42, 0xd9, 0x10, 0x62, 0x3f, 0x82, 0x39, 0xcf, 0x7d, 0x3f, 0xd0, 0xf4, 0x45, 0xde, 0x0f, 0x64, 0x93, 0x5f, 0x3b, 0x43, 0x52, 0xf5, 0xd8, 0x8f, 0x52, 0x53, 0xa7, 0x53, 0x53, 0x9f, 0x77, 0x15, 0xf0, 0x7f, 0x30, 0xda, 0x34, 0x1a, 0xbb, 0xe9, 0x4f, 0x42, 0x81, 0x43, 0xce, 0x9a, 0x72, 0x5a, 0x67, 0xef, 0x8c, 0x2e, 0x8e, 0x44, 0x86, 0x62, 0xcc, 0x8c, 0xd5, 0x09, 0xcf, 0xf2, 0x23, 0x24, 0x98, 0xd8, 0x8f, 0x50, 0x39, 0xc9, 0x76, 0x8b, 0xb8, 0x34, 0x59, 0xef, 0x06, 0x9a, 0xce, 0x7a, 0x37, 0x50, 0x39, 0x2a, 0x6b, 0x09, 0xc4, 0x03, 0xca, 0xbb, 0x81, 0x8a, 0xa3, 0x6c, 0x05, 0x8e, 0x0a, 0x22, 0x2b, 0x94, 0xe2, 0x39, 0x4f, 0xa7, 0x2f, 0x08, 0xca, 0x02, 0x46, 0x30, 0xa5, 0x5e, 0x7e, 0xc0, 0xa7, 0x7b, 0x47, 0x63, 0xe0, 0xb5, 0x96, 0x7f, 0xd3, 0x05, 0xee, 0x4d, 0xbd, 0xf4, 0x19, 0x57, 0x21, 0xff, 0xa2, 0xd6, 0x24, 0x68, 0x1d, 0xfa, 0xef, 0xf1, 0x3e, 0xfb, 0x4a, 0xa7, 0x9f, 0x7f, 0x43, 0x6f, 0xd7, 0x08, 0x16, 0xdd, 0xd7, 0xf8, 0x42, 0x3b, 0x81, 0x33, 0xcc, 0xdc, 0xe7, 0xae, 0x76, 0xd7, 0x56, 0xcf, 0xee, 0x0c, 0xcb, 0x20, 0x47, 0xfa, 0x42, 0xa1, 0xbe, 0x08, 0x73, 0xc0, 0xea, 0x70, 0x58, 0x67, 0x6f, 0x8b, 0xf4, 0x87, 0x43, 0xfd, 0x51, 0x3a, 0x01, 0xa4, 0xe2, 0xeb, 0xc7, 0x80, 0x2f, 0x1f, 0x79, 0x57, 0x90, 0xcf, 0x6e, 0xca, 0xbc, 0x2b, 0x68, 0x3a, 0xf3, 0xae, 0xa0, 0x78, 0xe6, 0x5d, 0x41, 0xac, 0x09, 0x8b, 0x6c, 0xe6, 0x65, 0x41, 0x36, 0x02, 0x6a, 0xb2, 0x19, 0x0c, 0x9d, 0x1f, 0xbf, 0xea, 0xf2, 0x6a, 0xfe, 0xc8, 0xeb, 0x34, 0xef, 0xe9, 0x3c, 0x8e, 0x67, 0xf0, 0xa6, 0xd4, 0x53, 0xaf, 0xba, 0x1c, 0x9a, 0xdf, 0xf3, 0x06, 0xdd, 0x3f, 0xe5, 0x15, 0x3c, 0x97, 0x7a, 0xca, 0x6d, 0xc2, 0xd7, 0x9b, 0x7d, 0x86, 0xd4, 0x57, 0x4d, 0x6e, 0x46, 0x8f, 0x7f, 0x6b, 0x32, 0xa5, 0x6e, 0x37, 0xfb, 0x2d, 0x38, 0x51, 0x94, 0xf2, 0x11, 0x38, 0xac, 0xa9, 0x27, 0x39, 0x0c, 0x70, 0x38, 0x51, 0xb1, 0x54, 0x68, 0x16, 0x98, 0x5c, 0xc2, 0x6d, 0xa5, 0x88, 0x74, 0x22, 0x47, 0x71, 0x50, 0x26, 0x1c, 0x4d, 0xae, 0xcc, 0x7e, 0x67, 0x10, 0xc1, 0x17, 0x87, 0x67, 0xdf, 0xd4, 0x16, 0xf8, 0xef, 0xfa, 0x5a, 0x6a, 0xfd, 0x13, 0x6e, 0x9f, 0x80, 0x19, 0x8d, 0x99, 0x17, 0x2d, 0xda, 0xbf, 0x88, 0x05, 0x36, 0xf6, 0xc3, 0xbf, 0xbe, 0xe7, 0xcc, 0xc7, 0x9f, 0x60, 0x5f, 0xca, 0x37, 0xcf, 0x7e, 0xd6, 0x55, 0xe9, 0xca, 0xaf, 0x74, 0x31, 0x1b, 0x29, 0x12, 0x30, 0x6a, 0x00, 0x1c, 0x2c, 0x86, 0x67, 0x9b, 0xc9, 0x7b, 0x83, 0x34, 0x39, 0xef, 0x0d, 0x9a, 0x9e, 0xf3, 0xde, 0x20, 0xbb, 0xfc, 0xde, 0x20, 0x21, 0xe7, 0xc5, 0x41, 0xec, 0x62, 0xb3, 0x61, 0xf6, 0xd7, 0x4c, 0xc1, 0xb9, 0x59, 0x93, 0x91, 0xc9, 0x9f, 0xfd, 0x0d, 0xf3, 0x27, 0xf6, 0x31, 0x47, 0xcc, 0x34, 0xdb, 0xf6, 0xe9, 0x65, 0xa6, 0xa8, 0x95, 0xf9, 0xf6, 0xa7, 0x41, 0x2f, 0xaf, 0x67, 0x25, 0x66, 0x23, 0xff, 0x0c, 0xd2, 0xa3, 0x3a, 0xd4, 0x2c, 0x35, 0xe8, 0xe1, 0x29, 0x1e, 0xba, 0x93, 0x74, 0xc1, 0xbb, 0x80, 0xc0, 0x45, 0x64, 0x31, 0xbb, 0x5e, 0x7d, 0x27, 0xd0, 0x0e, 0x61, 0x88, 0xbe, 0x01, 0x28, 0x1c, 0xc9, 0xbc, 0x01, 0xc8, 0xa9, 0xe4, 0x18, 0x5d, 0xf2, 0xfd, 0x3f, 0xf8, 0x9d, 0x45, 0xe3, 0x0d, 0xf9, 0xd5, 0xd1, 0xaa, 0xae, 0x60, 0xea, 0xf1, 0xba, 0xc3, 0x83, 0xc9, 0x55, 0x2d, 0x85, 0x9d, 0x76, 0xbb, 0xc6, 0x98, 0x5f, 0x5e, 0x5d, 0x5f, 0x1c, 0xed, 0xac, 0xf2, 0x86, 0xd7, 0xdc, 0xb1, 0xa3, 0x3e, 0x19, 0xae, 0x0e, 0xf9, 0xad, 0x66, 0xbb, 0xc8, 0x0d, 0x32, 0x16, 0x4f, 0xc0, 0x1e, 0x2d, 0x0a, 0x16, 0xe3, 0xe9, 0x9a, 0x8a, 0xe2, 0xb6, 0x95, 0x89, 0xd4, 0x71, 0x83, 0xad, 0xc2, 0x51, 0xe4, 0xd0, 0x3b, 0x62, 0xad, 0xf1, 0xe2, 0xae, 0x64, 0x90, 0x29, 0x6e, 0x6d, 0xb0, 0xda, 0xad, 0x79, 0x5a, 0xf9, 0xbd, 0xb3, 0x6c, 0x0b, 0xf3, 0x3d, 0xfe, 0x69, 0xe0, 0x23, 0x3f, 0x89, 0x82, 0xf0, 0xe4, 0x55, 0x64, 0x0c, 0x5e, 0x9f, 0x4e, 0xbc, 0xd8, 0xc1, 0x0e, 0x91, 0x54, 0x0b, 0xb7, 0xcb, 0xee, 0x77, 0xf8, 0x2f, 0x96, 0x6a, 0x51, 0x57, 0xab, 0x6c, 0x91, 0xdf, 0xd3, 0x73, 0x60, 0xa4, 0xbc, 0x7c, 0xe4, 0x40, 0x4f, 0xef, 0x81, 0x65, 0xe5, 0xe5, 0xcb, 0x0e, 0xf4, 0x7e, 0xee, 0x73, 0xcb, 0x97, 0x2c, 0xe1, 0x9f, 0x2e, 0x1d, 0xd9, 0xdf, 0xd7, 0x7f, 0x60, 0xa4, 0xac, 0x6c, 0xe4, 0x40, 0x7f, 0xdf, 0xfe, 0x91, 0xd2, 0x14, 0x77, 0xf6, 0xec, 0xe4, 0xc6, 0x8d, 0x93, 0xf2, 0x7e, 0xf5, 0x0e, 0xb6, 0x9f, 0xf9, 0x16, 0x7d, 0x67, 0x9c, 0x83, 0xbc, 0xc9, 0x9d, 0x1e, 0x55, 0xdf, 0x4f, 0x8e, 0x09, 0x31, 0xe0, 0x63, 0x6c, 0x42, 0x1c, 0x37, 0x49, 0x0e, 0x8c, 0x92, 0xd7, 0x2b, 0x9a, 0x8c, 0x5a, 0x0d, 0x34, 0x13, 0x29, 0x18, 0x51, 0xb2, 0x16, 0xcd, 0x98, 0xa7, 0x38, 0x0c, 0xca, 0x2f, 0x83, 0x4d, 0xe0, 0x9f, 0xb6, 0xf0, 0x02, 0x2b, 0x68, 0x5b, 0xf0, 0x95, 0xbb, 0x2d, 0x76, 0x3e, 0xf4, 0x5e, 0x88, 0x75, 0x9a, 0x77, 0xff, 0xe6, 0x28, 0x57, 0xf1, 0x88, 0x3e, 0xdf, 0xe8, 0x71, 0x3d, 0xfc, 0x9f, 0x66, 0xe3, 0xe4, 0xa4, 0xde, 0xca, 0x2c, 0x97, 0x9f, 0x1d, 0x63, 0x6f, 0xc5, 0xff, 0xc1, 0x3f, 0x06, 0xb4, 0xad, 0x21, 0x3b, 0x6c, 0x84, 0xb6, 0xee, 0xf9, 0x69, 0xab, 0xd0, 0xb4, 0x27, 0x53, 0x5b, 0xb8, 0x83, 0x05, 0xf2, 0x3a, 0xa3, 0xe1, 0x10, 0x90, 0x57, 0x93, 0x43, 0xde, 0xe4, 0x1c, 0xea, 0x46, 0x2e, 0x20, 0xee, 0x78, 0xa2, 0x2b, 0x66, 0x0d, 0xfa, 0x83, 0x09, 0x4f, 0xea, 0x67, 0x13, 0xc7, 0x2a, 0xfa, 0x13, 0xde, 0x76, 0xbb, 0x5d, 0xd4, 0xdb, 0xc3, 0xd1, 0x72, 0x5f, 0xf7, 0x0a, 0x7f, 0xef, 0xc1, 0xd1, 0x92, 0x88, 0x3f, 0xea, 0x77, 0x5b, 0x4c, 0x40, 0xd9, 0x1d, 0x8c, 0xd1, 0xe6, 0x36, 0xfb, 0xdd, 0xf9, 0x1e, 0x5c, 0xbf, 0xac, 0x20, 0x39, 0x58, 0x9e, 0x7a, 0xd9, 0x90, 0x57, 0x64, 0xf5, 0xe4, 0x69, 0xfb, 0x7a, 0x7d, 0x4d, 0x95, 0x45, 0x8c, 0xbb, 0xaa, 0xdc, 0x68, 0x31, 0x9a, 0xf5, 0xf2, 0x9c, 0x96, 0xb2, 0xb7, 0x32, 0x49, 0xe1, 0x04, 0x32, 0x80, 0xb7, 0x96, 0x94, 0x12, 0x46, 0xc0, 0x27, 0x29, 0x9b, 0x8b, 0xfd, 0xf4, 0xac, 0x0d, 0xa8, 0x80, 0xf6, 0xf4, 0x0e, 0x77, 0x4f, 0xe6, 0xdd, 0x30, 0xbd, 0xec, 0x90, 0x35, 0x4c, 0xdf, 0xfa, 0xa2, 0xf1, 0x94, 0xda, 0x16, 0x7c, 0xeb, 0x8b, 0xf2, 0xda, 0x5d, 0x6b, 0xe2, 0x5d, 0x7b, 0x85, 0x27, 0xd1, 0x56, 0x57, 0x51, 0x54, 0x93, 0x5f, 0x30, 0x94, 0x28, 0xe9, 0x49, 0xf8, 0x8a, 0x9a, 0x97, 0xd5, 0x54, 0xb5, 0x79, 0x58, 0xd6, 0x62, 0x1a, 0x3b, 0xc9, 0x15, 0xb8, 0xac, 0x8b, 0xd7, 0xac, 0xea, 0xf1, 0xfa, 0x36, 0x16, 0x86, 0x82, 0x5d, 0x9b, 0xa4, 0xa6, 0xa9, 0xfe, 0x78, 0xb8, 0xa0, 0xc8, 0x6c, 0x60, 0x7e, 0x0f, 0xeb, 0xb6, 0x0a, 0xf0, 0xfe, 0x5b, 0xfe, 0x11, 0x14, 0x27, 0x75, 0x48, 0xe2, 0x18, 0xf3, 0x2e, 0xcc, 0x31, 0xd9, 0xfb, 0x9a, 0x39, 0x6f, 0x71, 0xe9, 0x49, 0xbf, 0xe1, 0xa0, 0x97, 0x1b, 0x8a, 0x45, 0x83, 0xa1, 0x20, 0x85, 0x32, 0x1d, 0x7c, 0x16, 0x1c, 0xd9, 0x46, 0x46, 0x4e, 0x02, 0x3c, 0x3e, 0x2b, 0xea, 0xf5, 0x3a, 0x5b, 0x7e, 0xc0, 0xdd, 0xde, 0x22, 0x35, 0xf8, 0x83, 0xf9, 0x79, 0x46, 0x8d, 0x91, 0x8b, 0x39, 0x13, 0xc9, 0x7a, 0x6f, 0xd5, 0xaa, 0x8e, 0x70, 0x40, 0xda, 0xd0, 0xd6, 0xb2, 0x36, 0xc8, 0x47, 0x34, 0x5a, 0xbd, 0x49, 0xbf, 0x72, 0x68, 0xc9, 0x0a, 0x30, 0x1d, 0x04, 0x31, 0xe1, 0x29, 0xb6, 0x6b, 0x42, 0xdd, 0x53, 0x52, 0xe3, 0xa6, 0xbe, 0x78, 0x24, 0x4c, 0xf1, 0x5a, 0x03, 0x78, 0xcd, 0xa7, 0x78, 0x0d, 0x92, 0xa8, 0x5d, 0x31, 0x46, 0x9c, 0x81, 0xf0, 0x68, 0x1e, 0x8d, 0x1a, 0x11, 0x0d, 0xdc, 0x9e, 0xde, 0x33, 0xef, 0xc9, 0xd4, 0xdf, 0xee, 0x60, 0x86, 0x8c, 0x46, 0x63, 0xd0, 0x18, 0x8c, 0x85, 0x42, 0xa1, 0x62, 0x51, 0x93, 0xd9, 0x2b, 0x77, 0x2e, 0x58, 0x58, 0x1b, 0xdf, 0x6e, 0x2f, 0xf7, 0x24, 0xda, 0x93, 0x35, 0x45, 0x55, 0x8e, 0x5b, 0x57, 0xfa, 0x44, 0xb1, 0x68, 0x69, 0x03, 0x45, 0x71, 0xd3, 0xb2, 0x44, 0x49, 0xb3, 0x8b, 0xb7, 0x3b, 0x65, 0xe4, 0x7a, 0x98, 0x95, 0xb3, 0x66, 0x77, 0x75, 0xa8, 0x22, 0xd4, 0x35, 0xd5, 0x4e, 0x30, 0xec, 0x71, 0x60, 0x23, 0x81, 0x15, 0xf0, 0xcb, 0x58, 0x01, 0xbf, 0x1a, 0x54, 0x8a, 0xfa, 0xa4, 0x6e, 0x3b, 0x60, 0x97, 0xe4, 0xac, 0x0a, 0x0c, 0x18, 0x7b, 0xa4, 0xf6, 0xb1, 0x06, 0x93, 0x7d, 0xcf, 0x49, 0xb5, 0x12, 0x76, 0x0e, 0x82, 0xb5, 0x20, 0x1e, 0xb4, 0xa5, 0xda, 0x92, 0x50, 0xb1, 0x27, 0x3f, 0xcf, 0xa2, 0xd4, 0xda, 0xd5, 0x6a, 0xd2, 0xb5, 0x76, 0x49, 0x80, 0x7f, 0xbe, 0x57, 0x07, 0xc8, 0xe9, 0x2f, 0xf8, 0x3e, 0x83, 0x3b, 0xea, 0xad, 0x69, 0xd4, 0x30, 0x62, 0x77, 0xa3, 0xbf, 0xa4, 0xc8, 0x63, 0xc8, 0x13, 0x63, 0xce, 0x64, 0x63, 0x93, 0x37, 0xb9, 0xbe, 0x27, 0x16, 0xec, 0x98, 0x68, 0xae, 0x59, 0xd1, 0x5a, 0x6c, 0xb0, 0x71, 0x6f, 0x3b, 0xa2, 0xfe, 0xbc, 0x86, 0xc4, 0x8a, 0xbe, 0x0e, 0xa3, 0xd5, 0x68, 0x94, 0x11, 0xde, 0xb3, 0xad, 0xbb, 0x76, 0x5d, 0x4f, 0xbc, 0xa8, 0x75, 0x4d, 0x63, 0x91, 0x83, 0xc8, 0xf7, 0x10, 0x7b, 0x0f, 0x3e, 0x22, 0x5c, 0x03, 0x26, 0x56, 0x42, 0xaa, 0x72, 0xc3, 0xc4, 0x78, 0x3c, 0x60, 0x04, 0x7c, 0xf7, 0xab, 0x90, 0xb7, 0xd3, 0x73, 0x48, 0xa3, 0x24, 0xba, 0x20, 0x11, 0x43, 0x87, 0x2c, 0x4a, 0xa7, 0xd5, 0x6a, 0xcb, 0x30, 0x88, 0xe0, 0xc7, 0x09, 0x47, 0x30, 0xeb, 0x24, 0x17, 0x29, 0x3b, 0x82, 0x0f, 0x61, 0x97, 0x2b, 0x5a, 0x71, 0xfc, 0x9a, 0x93, 0x71, 0xb3, 0x7d, 0x68, 0xfb, 0xba, 0x60, 0x40, 0x28, 0x37, 0xeb, 0x62, 0x49, 0xdf, 0x54, 0x6a, 0x02, 0x3f, 0x2c, 0x72, 0x16, 0x73, 0x53, 0x60, 0xed, 0x56, 0x2d, 0xcd, 0xc2, 0x0e, 0x33, 0x3f, 0x61, 0x5c, 0xfc, 0xa3, 0x20, 0x23, 0xdc, 0x68, 0x09, 0xba, 0x4f, 0x32, 0x17, 0x80, 0x9a, 0xe9, 0xc2, 0xe0, 0xf4, 0x5b, 0x19, 0xf9, 0xfc, 0x0e, 0xa9, 0xcd, 0x51, 0x91, 0x3e, 0x87, 0x43, 0xce, 0xed, 0x48, 0xe9, 0x73, 0x3b, 0x3d, 0xe9, 0x44, 0xf4, 0x8e, 0xf4, 0x79, 0x9c, 0x3e, 0x92, 0xc5, 0x34, 0xff, 0x81, 0x9f, 0x9c, 0x8e, 0x99, 0xe6, 0xa3, 0x92, 0xdf, 0xe3, 0x01, 0xbd, 0xb3, 0xc4, 0x03, 0x4e, 0x72, 0x7d, 0x5d, 0x45, 0x59, 0x38, 0x94, 0xef, 0xb4, 0x98, 0x45, 0x1e, 0xe9, 0xb1, 0x5e, 0xaf, 0x21, 0x52, 0x39, 0x93, 0xaa, 0x64, 0x03, 0x3a, 0xd1, 0x43, 0x3a, 0x84, 0xc1, 0xe8, 0xd1, 0x1d, 0xa7, 0xba, 0x78, 0x85, 0x4c, 0xa4, 0x3e, 0x73, 0x72, 0x27, 0x99, 0x75, 0x70, 0x47, 0x57, 0x1a, 0x8f, 0x97, 0xc2, 0xdf, 0x97, 0x23, 0x4d, 0xfd, 0xc5, 0x56, 0x47, 0xcd, 0xa2, 0xf6, 0xd6, 0x5b, 0xfc, 0x25, 0x9a, 0xc2, 0x86, 0xc1, 0x32, 0x93, 0xc7, 0xe8, 0xa8, 0x1e, 0x5c, 0x35, 0x58, 0x5d, 0xdc, 0xb1, 0xbe, 0xb5, 0x65, 0x4d, 0x93, 0x2f, 0x1e, 0xb6, 0x39, 0x03, 0xc1, 0x58, 0x45, 0x51, 0xc3, 0xe2, 0xf2, 0x45, 0x35, 0xdc, 0xd3, 0xf1, 0xe2, 0x60, 0x3c, 0x1e, 0x0c, 0x94, 0xa4, 0xde, 0xb2, 0x5b, 0x05, 0xcc, 0xe9, 0x3d, 0x16, 0x4f, 0x24, 0xce, 0x44, 0xea, 0x5c, 0xa2, 0x36, 0xdf, 0x1b, 0x6d, 0x2b, 0xcb, 0x67, 0x19, 0xa3, 0xd9, 0x66, 0x60, 0xf9, 0xfc, 0xda, 0x9a, 0x60, 0x67, 0x6d, 0x91, 0x2b, 0x52, 0xed, 0x0d, 0x56, 0x99, 0xf4, 0x61, 0x9f, 0xab, 0xd6, 0xea, 0xec, 0xad, 0x8c, 0xb6, 0x97, 0xb9, 0x3c, 0xb2, 0x6d, 0xb3, 0x94, 0xbd, 0x87, 0xf1, 0xc3, 0x5a, 0xbb, 0xf0, 0x1c, 0x49, 0x7b, 0x3a, 0xef, 0xa2, 0x87, 0x26, 0x73, 0x76, 0xa8, 0xe7, 0x48, 0x60, 0x61, 0xcd, 0x7b, 0x62, 0xe3, 0x5d, 0x7f, 0x4f, 0x55, 0x69, 0x47, 0xb9, 0xeb, 0x64, 0x2c, 0xe6, 0xaf, 0x73, 0x9d, 0x3c, 0xc9, 0x6f, 0xf3, 0x04, 0x0a, 0x6a, 0xfb, 0x4b, 0x53, 0xcf, 0xe3, 0xc1, 0x92, 0x4a, 0xb7, 0x33, 0x75, 0x2b, 0x48, 0x24, 0x7a, 0x16, 0x81, 0xbd, 0x07, 0xfd, 0x5a, 0xb8, 0x81, 0x9e, 0x81, 0xd8, 0x32, 0x78, 0xda, 0x42, 0x33, 0x41, 0x94, 0x83, 0x10, 0xed, 0x83, 0x0a, 0x87, 0x21, 0xd4, 0x81, 0x88, 0x27, 0x1d, 0xcf, 0xbe, 0xdb, 0x9d, 0x7b, 0x77, 0xee, 0x0d, 0xfa, 0x2a, 0x05, 0xb8, 0x31, 0x4a, 0x5f, 0x33, 0x9f, 0x39, 0x43, 0x01, 0x4b, 0x8a, 0x24, 0xca, 0xd4, 0xcf, 0x49, 0x07, 0xff, 0xd7, 0x93, 0x0f, 0x9a, 0xf2, 0x0b, 0xf3, 0xac, 0x85, 0x2e, 0x93, 0xc9, 0x55, 0x68, 0xcd, 0x2b, 0xcc, 0x37, 0xf1, 0x27, 0x52, 0x66, 0xfc, 0xfb, 0x27, 0xf3, 0xe4, 0x4b, 0x79, 0x70, 0xc9, 0x68, 0xcc, 0x2f, 0x44, 0xcc, 0xf9, 0xff, 0x62, 0x7e, 0x42, 0x61, 0xb6, 0x82, 0x4c, 0xba, 0x49, 0xd2, 0xe7, 0x61, 0x96, 0xa3, 0x99, 0xdf, 0xc4, 0x51, 0xb0, 0xd0, 0x44, 0x2e, 0x25, 0x4f, 0xbd, 0x9d, 0xfa, 0x70, 0x94, 0xbb, 0xa8, 0x07, 0xd0, 0x97, 0x9e, 0x45, 0xba, 0x49, 0xf7, 0x3c, 0x4d, 0xa4, 0x02, 0xb8, 0x06, 0x36, 0x0d, 0x83, 0x67, 0x32, 0xcd, 0xb2, 0x1a, 0x8c, 0xd2, 0xf7, 0x8d, 0x66, 0xe5, 0xbb, 0x6b, 0x9c, 0xa5, 0xb6, 0xb9, 0xf9, 0xee, 0xb9, 0x13, 0x74, 0xfe, 0x5b, 0x5e, 0xbc, 0xb3, 0xca, 0x56, 0x68, 0xe5, 0x19, 0x83, 0x2d, 0xec, 0x79, 0xd0, 0xe4, 0x2a, 0x4a, 0xcf, 0x35, 0x94, 0x80, 0x99, 0x8a, 0xc1, 0xf6, 0x4a, 0x1f, 0xcf, 0x0f, 0xea, 0x35, 0xfe, 0x18, 0xfe, 0x4b, 0xce, 0xa4, 0x6b, 0x54, 0xde, 0x68, 0x03, 0xde, 0xf0, 0x11, 0xde, 0xf0, 0x60, 0x30, 0xba, 0x80, 0x2f, 0x10, 0xa9, 0x93, 0x36, 0x49, 0x29, 0x05, 0xeb, 0x4d, 0xa2, 0x05, 0x36, 0x00, 0xe5, 0x11, 0xab, 0xd5, 0x1a, 0xb1, 0x50, 0x59, 0x40, 0xb5, 0x31, 0x49, 0xe6, 0x76, 0x90, 0xb3, 0x1d, 0x00, 0x58, 0x3b, 0x4d, 0xf5, 0x16, 0x05, 0xf2, 0x2a, 0xe6, 0xb6, 0x6b, 0x2c, 0x6e, 0xfe, 0x4a, 0xc1, 0x6a, 0xf6, 0x9f, 0x3c, 0xe9, 0x32, 0x59, 0x84, 0x03, 0x42, 0xbe, 0xf5, 0xe8, 0x7d, 0x66, 0x4f, 0xbe, 0x91, 0xbd, 0x47, 0x67, 0xfe, 0xba, 0x49, 0xf8, 0x15, 0xf3, 0xfb, 0xd4, 0x2a, 0x51, 0xff, 0x29, 0xa3, 0xfe, 0x55, 0xaf, 0xd9, 0xe0, 0x53, 0xe0, 0x40, 0xef, 0x02, 0x1c, 0xe9, 0xb3, 0x1b, 0xed, 0x0a, 0x03, 0xa8, 0x67, 0x37, 0x14, 0xfa, 0xbe, 0x7b, 0x52, 0x38, 0x31, 0x6b, 0x26, 0x4c, 0x46, 0xfb, 0xb0, 0xb5, 0x20, 0x97, 0x83, 0x24, 0x37, 0x1d, 0xfc, 0x69, 0xe4, 0xa7, 0xdb, 0x4e, 0x24, 0xd2, 0x48, 0x5e, 0xee, 0xc2, 0x10, 0xae, 0x16, 0x88, 0xaf, 0x09, 0x5a, 0x23, 0x60, 0x0b, 0xda, 0x8a, 0x6d, 0xa1, 0x62, 0x62, 0x5c, 0xd8, 0x32, 0x6f, 0xb7, 0x73, 0x52, 0xf6, 0x26, 0x2b, 0x7d, 0x4e, 0xc6, 0xf2, 0x3f, 0xfa, 0x17, 0x55, 0x55, 0xb5, 0x5b, 0x3c, 0x63, 0x35, 0x6b, 0x26, 0x4e, 0x0e, 0x07, 0xab, 0x4e, 0x86, 0x63, 0xb0, 0x24, 0x4e, 0xae, 0xf4, 0x08, 0xfc, 0x58, 0x51, 0x3c, 0x51, 0x52, 0x53, 0xba, 0x65, 0x3d, 0x61, 0xfa, 0xd1, 0xf2, 0x32, 0xf2, 0x51, 0x5a, 0xe1, 0xb4, 0xa7, 0x6e, 0x64, 0xb4, 0x1e, 0xb2, 0xcf, 0x43, 0x70, 0xea, 0xe3, 0x1f, 0xc1, 0x22, 0xf7, 0xed, 0xf3, 0x3f, 0x83, 0x79, 0x69, 0xbf, 0x82, 0xf0, 0x4b, 0xec, 0x3d, 0xa7, 0x13, 0xa5, 0xb2, 0x4d, 0x41, 0xf4, 0x49, 0x23, 0xc0, 0x6d, 0x43, 0x5e, 0x02, 0x79, 0xda, 0x46, 0xec, 0xc9, 0x24, 0xe7, 0xf6, 0x62, 0xd5, 0x5c, 0xf4, 0x3a, 0xbc, 0x8a, 0xb9, 0xa8, 0x59, 0xc8, 0x5c, 0x84, 0x96, 0x75, 0x4d, 0x1b, 0xba, 0xc3, 0xe1, 0xee, 0x0d, 0x4d, 0xcd, 0x1b, 0xba, 0x22, 0x91, 0xae, 0x0d, 0xcd, 0x57, 0x5e, 0x79, 0x90, 0x7f, 0x24, 0xdc, 0x33, 0xd9, 0xd6, 0x36, 0xd9, 0x13, 0x89, 0xc8, 0x9f, 0xe1, 0xd4, 0xf2, 0xcf, 0xc3, 0x3f, 0xd9, 0xae, 0x61, 0x7e, 0xc3, 0x24, 0xc1, 0x56, 0x23, 0xef, 0xc0, 0x58, 0xa7, 0xe4, 0x35, 0xaa, 0xe9, 0x7d, 0x55, 0x39, 0x49, 0x8b, 0x3d, 0xe9, 0xc4, 0xbd, 0x5e, 0xb2, 0xa3, 0xdd, 0xa9, 0xa6, 0xf6, 0x49, 0x73, 0x0d, 0x9f, 0xac, 0x16, 0x72, 0xce, 0x22, 0x49, 0xed, 0xcb, 0xb2, 0x7f, 0xd4, 0x9c, 0xc5, 0x1c, 0x03, 0xc8, 0x41, 0xb1, 0xfd, 0xae, 0xbd, 0x12, 0xec, 0x1f, 0x93, 0x3b, 0xe8, 0x8c, 0x54, 0xbb, 0x0c, 0x45, 0x23, 0x8d, 0x25, 0xdd, 0xb2, 0x09, 0x54, 0xda, 0x94, 0x7f, 0x72, 0xa5, 0x5f, 0x90, 0x0d, 0xa0, 0xd2, 0x96, 0xb0, 0xc5, 0xef, 0x5d, 0x11, 0x06, 0x05, 0x3d, 0x49, 0x4d, 0x20, 0xb7, 0xf3, 0x07, 0xcc, 0xe9, 0x7c, 0x3a, 0x9f, 0x6a, 0x98, 0x8f, 0x4b, 0x99, 0xcf, 0x31, 0x79, 0x0a, 0x66, 0x62, 0x54, 0x98, 0x64, 0xa3, 0x82, 0xa5, 0x2f, 0x83, 0x23, 0x57, 0xf8, 0xac, 0x2b, 0xa3, 0x72, 0xc3, 0xac, 0x0c, 0xbe, 0x9e, 0x74, 0x7a, 0x5e, 0x2f, 0x11, 0x8d, 0x9d, 0xe9, 0x0c, 0x3e, 0x29, 0xd7, 0x1a, 0xc9, 0x69, 0xa2, 0xbe, 0xee, 0x23, 0x24, 0x27, 0xf0, 0x69, 0xe6, 0x49, 0xe0, 0xab, 0xcb, 0xb5, 0x49, 0xea, 0x93, 0x09, 0xb0, 0x4a, 0x2a, 0x3c, 0xc9, 0x16, 0xb3, 0x27, 0xec, 0x0c, 0xd4, 0x80, 0x24, 0x2d, 0x1c, 0x54, 0xed, 0xbe, 0x44, 0x95, 0xe4, 0xe1, 0x45, 0x2f, 0x6f, 0x73, 0x59, 0x97, 0xae, 0x94, 0xe7, 0x8c, 0xb7, 0xcf, 0x2e, 0xfd, 0xcf, 0xa2, 0x88, 0x6a, 0x98, 0x04, 0xbc, 0x9e, 0x7c, 0x99, 0xff, 0xf1, 0x9d, 0x30, 0x67, 0x07, 0x1a, 0x97, 0x27, 0xa2, 0xe3, 0x41, 0x98, 0x98, 0xd4, 0x2c, 0x3d, 0xf8, 0xc1, 0xc9, 0x3f, 0x94, 0x79, 0x66, 0x25, 0xe9, 0xf5, 0x28, 0xea, 0x5d, 0xce, 0xe9, 0x97, 0x32, 0xf9, 0x79, 0xea, 0x0d, 0x92, 0xd3, 0x0f, 0x8e, 0x6e, 0x5a, 0x11, 0xa8, 0xd9, 0x77, 0x82, 0x9a, 0x7c, 0xf7, 0x03, 0x4f, 0x67, 0x79, 0x20, 0x5f, 0xcb, 0xe5, 0x69, 0x0a, 0x4c, 0xd1, 0x32, 0xcb, 0xc9, 0x65, 0x0e, 0x13, 0xcf, 0x77, 0x54, 0x26, 0x2d, 0xdb, 0x6d, 0x7c, 0x49, 0x38, 0x75, 0x96, 0xd1, 0x9a, 0xed, 0x24, 0x8e, 0x06, 0xb6, 0xe9, 0x57, 0x81, 0xd7, 0x6b, 0x89, 0x6d, 0x5a, 0x0b, 0x26, 0xa8, 0xfb, 0x62, 0x39, 0x77, 0x3d, 0xe9, 0x9c, 0xbb, 0x5e, 0x66, 0xa8, 0x3c, 0x18, 0x2e, 0x07, 0x53, 0x82, 0x88, 0x1b, 0x21, 0x6d, 0x9a, 0xce, 0xb3, 0x35, 0x4e, 0x58, 0x8b, 0x4b, 0x9b, 0xa8, 0x77, 0xe9, 0xac, 0x3a, 0xa7, 0xa7, 0xd8, 0x59, 0x1c, 0x09, 0xd7, 0x4b, 0xf5, 0x61, 0x6f, 0x72, 0xb8, 0x36, 0x39, 0x59, 0x50, 0x24, 0x58, 0x8c, 0x46, 0x87, 0x27, 0xe4, 0x2d, 0x29, 0x0e, 0x37, 0x48, 0x0d, 0x61, 0x77, 0x62, 0xa0, 0xa6, 0x7e, 0xcc, 0xcf, 0x57, 0x5a, 0x74, 0x7a, 0x9d, 0xc7, 0xe5, 0x70, 0xd9, 0xec, 0x81, 0xda, 0x92, 0x48, 0x47, 0xa5, 0x27, 0xe4, 0x4b, 0x6a, 0xf5, 0x46, 0xb3, 0x21, 0x60, 0x77, 0xe6, 0xdb, 0x6c, 0xc1, 0xa6, 0xca, 0x40, 0x53, 0xb9, 0x27, 0xec, 0x27, 0xf2, 0x26, 0x00, 0x73, 0x39, 0x29, 0xd4, 0xa3, 0x42, 0xb2, 0x6a, 0x0d, 0x34, 0x3f, 0x20, 0x77, 0x8f, 0x59, 0x4a, 0xc7, 0x84, 0x7a, 0xd1, 0x50, 0xd0, 0x6a, 0x8d, 0x66, 0x59, 0x4e, 0x6a, 0xa8, 0xae, 0x1d, 0x27, 0x9b, 0xb1, 0x83, 0x18, 0x51, 0xd4, 0xa2, 0xa0, 0x30, 0x9f, 0xf4, 0xf5, 0x47, 0x7b, 0x06, 0x6b, 0xf4, 0xe6, 0x15, 0x5e, 0xde, 0xd3, 0x7b, 0xd3, 0x4d, 0x4e, 0x1b, 0xe8, 0x35, 0x70, 0x98, 0xd8, 0x97, 0x5d, 0xae, 0xa5, 0xdd, 0xd8, 0x69, 0x36, 0x35, 0x58, 0x6e, 0x70, 0x3d, 0x38, 0x95, 0xfa, 0x1f, 0x87, 0x1b, 0xec, 0x69, 0x02, 0xcb, 0x00, 0xc8, 0x10, 0x27, 0xf8, 0x79, 0x2e, 0x02, 0x8b, 0x13, 0x74, 0x0a, 0xf8, 0x78, 0x44, 0xf6, 0x11, 0x71, 0x27, 0x65, 0x02, 0x6e, 0xb2, 0x00, 0x75, 0x21, 0x57, 0xd0, 0x11, 0x02, 0x54, 0xa6, 0x63, 0x6a, 0x78, 0x4e, 0x50, 0x8a, 0x71, 0xae, 0x74, 0x6b, 0xc5, 0xa2, 0x81, 0xba, 0x92, 0xce, 0x32, 0xd7, 0x2d, 0xf1, 0x98, 0xbf, 0xd6, 0xc5, 0x3f, 0x30, 0xfb, 0x67, 0x77, 0xa4, 0x28, 0x4a, 0xd4, 0x3b, 0x1e, 0x4c, 0x3d, 0x5f, 0x52, 0xe9, 0x71, 0xe2, 0x03, 0x88, 0xd6, 0x33, 0x06, 0x7b, 0x62, 0x9e, 0x77, 0x4f, 0x4b, 0x19, 0x91, 0x9d, 0x87, 0x40, 0x57, 0x70, 0xa0, 0xbf, 0x30, 0x1b, 0x64, 0xa9, 0x27, 0x41, 0xc3, 0x13, 0x44, 0x4b, 0x24, 0x18, 0xff, 0xf7, 0xaf, 0x79, 0x73, 0x9f, 0xd9, 0xc9, 0x4c, 0x08, 0x7a, 0x5e, 0x30, 0x88, 0xeb, 0x58, 0xbb, 0x89, 0x3c, 0x8c, 0xd1, 0x9a, 0x0c, 0xb3, 0xfe, 0x5a, 0x8f, 0xa7, 0xce, 0x7f, 0xce, 0x60, 0x82, 0xe7, 0xb8, 0x41, 0xbe, 0xb7, 0xf2, 0x0f, 0x82, 0x5f, 0x03, 0xcf, 0x09, 0x3a, 0x34, 0x2c, 0x39, 0x7d, 0x44, 0xcf, 0xd8, 0x48, 0x83, 0x8a, 0xcf, 0x18, 0x0f, 0xc4, 0x5b, 0x2c, 0x94, 0x49, 0xb3, 0x1f, 0x02, 0x18, 0xae, 0xa1, 0x3f, 0x64, 0xad, 0xc4, 0xe6, 0x04, 0x9b, 0xdc, 0x7b, 0xcd, 0x2e, 0xa6, 0x8b, 0xd3, 0x70, 0xbc, 0x41, 0x5c, 0xa2, 0xf5, 0xd7, 0x17, 0x69, 0x16, 0x0b, 0x3a, 0x2e, 0xaf, 0x97, 0x75, 0x98, 0x76, 0x7e, 0x71, 0xa7, 0x4d, 0x5c, 0x0d, 0x3e, 0x2f, 0xa7, 0x1b, 0x15, 0x5c, 0xdb, 0xf9, 0x07, 0xcc, 0xd6, 0xff, 0xf1, 0x54, 0xb8, 0x5c, 0x15, 0x9e, 0x3f, 0x17, 0x14, 0xfe, 0xc9, 0x5d, 0xee, 0x1a, 0xff, 0x93, 0xc5, 0x0c, 0x58, 0xc1, 0x0e, 0x4b, 0x81, 0xa5, 0xa0, 0x38, 0xf5, 0x5b, 0x37, 0xb5, 0xaf, 0x9c, 0x80, 0x8f, 0x8a, 0xf4, 0x7b, 0xa6, 0xb3, 0x63, 0x47, 0xd2, 0xa0, 0x42, 0x84, 0x58, 0x5e, 0x89, 0x85, 0x23, 0xe8, 0x57, 0x4a, 0xc4, 0x2a, 0xc7, 0x62, 0xa3, 0x14, 0xea, 0x24, 0x85, 0x58, 0x64, 0x2a, 0x6e, 0x30, 0xd9, 0xf8, 0x2d, 0xac, 0xc0, 0xaf, 0xe1, 0xf3, 0x4c, 0x87, 0x4f, 0x3f, 0x7e, 0x83, 0xd9, 0xc4, 0x6f, 0xe5, 0x75, 0x4b, 0x74, 0xce, 0xa3, 0x8f, 0xb3, 0xf7, 0x98, 0xf5, 0xcf, 0xe8, 0xf2, 0x34, 0x27, 0x75, 0x16, 0x6c, 0x3c, 0xa7, 0xd1, 0x7e, 0x29, 0xcf, 0x79, 0x8d, 0xfd, 0x8f, 0xb2, 0x7d, 0xc7, 0x83, 0xec, 0x2b, 0x82, 0xe7, 0x5f, 0x10, 0x33, 0xa2, 0x04, 0xe9, 0x54, 0x63, 0x46, 0x79, 0x41, 0x42, 0x10, 0x39, 0x66, 0x54, 0x7f, 0x01, 0x51, 0x8a, 0x52, 0xbf, 0xd6, 0xda, 0xed, 0x5b, 0x26, 0xff, 0xf9, 0x10, 0x10, 0x6e, 0x8a, 0xd7, 0x72, 0xbc, 0x8e, 0xdf, 0xc4, 0xd9, 0x8d, 0x40, 0x99, 0x2b, 0x1c, 0x96, 0xff, 0x83, 0x7f, 0x62, 0xd6, 0xff, 0xc1, 0x5c, 0x68, 0xb6, 0x16, 0x5a, 0xde, 0xd7, 0x9b, 0xe5, 0xe7, 0x96, 0x82, 0xfc, 0xf9, 0x36, 0x7d, 0xb7, 0x7c, 0x5c, 0x8a, 0x90, 0x6c, 0x5e, 0x0d, 0x2d, 0xf8, 0xa5, 0xc6, 0x8c, 0xa4, 0x74, 0xcc, 0xa8, 0x43, 0x89, 0x19, 0x69, 0xe4, 0x98, 0x11, 0x9b, 0x90, 0x63, 0x46, 0xb6, 0x60, 0x5d, 0x00, 0x7f, 0x5b, 0xa7, 0xff, 0xc5, 0x9f, 0x52, 0xdb, 0x75, 0x86, 0x5f, 0xbe, 0x8f, 0x4f, 0xf1, 0x9b, 0xcc, 0x05, 0x86, 0xd4, 0x3b, 0x3b, 0x9b, 0xb5, 0x7e, 0x33, 0xae, 0xd8, 0x01, 0xcf, 0xb8, 0x92, 0xbb, 0x8d, 0x71, 0xf0, 0x4f, 0x21, 0x2d, 0xf2, 0x48, 0x2e, 0xf8, 0x8d, 0xf7, 0x13, 0x63, 0x64, 0x07, 0xd9, 0x3b, 0x14, 0x38, 0xa4, 0xc5, 0x5a, 0x36, 0xab, 0xf8, 0x6e, 0x94, 0xc5, 0x95, 0xc1, 0x62, 0x13, 0x29, 0xb3, 0x6b, 0xfe, 0x2d, 0xff, 0x94, 0xcb, 0xf5, 0x81, 0xd1, 0x4a, 0xd8, 0x05, 0xfa, 0x15, 0x0a, 0x3a, 0x26, 0x26, 0xb6, 0xc1, 0x38, 0xf9, 0x92, 0x83, 0xbc, 0xac, 0x1a, 0xed, 0x03, 0xc0, 0xb6, 0x65, 0x8f, 0x62, 0x23, 0x71, 0x3c, 0x1a, 0x50, 0xf8, 0x5d, 0x71, 0x28, 0xef, 0xbd, 0xdb, 0x5c, 0x5e, 0x6b, 0xea, 0x0d, 0xb1, 0x4d, 0xef, 0x4a, 0xfd, 0x37, 0xb8, 0x69, 0xf2, 0xbb, 0xb5, 0xb1, 0x0f, 0xe0, 0xf9, 0x16, 0x85, 0xc7, 0x4d, 0x2a, 0x17, 0x61, 0xb4, 0x9f, 0x88, 0xce, 0x1d, 0x98, 0xe0, 0x58, 0x8b, 0x34, 0x41, 0x7a, 0xc6, 0x94, 0x06, 0x06, 0x89, 0x7c, 0x62, 0xbe, 0xf5, 0x5b, 0xb3, 0xcf, 0x75, 0xe5, 0xbd, 0xa6, 0x40, 0x90, 0x7f, 0x2a, 0x95, 0xb2, 0x1a, 0x3f, 0x70, 0xb9, 0xe4, 0x71, 0x74, 0x82, 0x8e, 0x0d, 0x00, 0x3c, 0x7a, 0xe4, 0x92, 0xec, 0xe4, 0xd5, 0x65, 0x78, 0x1f, 0x5c, 0xdf, 0x06, 0x7f, 0x7a, 0xa4, 0xa3, 0xa3, 0x28, 0xc1, 0xbd, 0x36, 0x36, 0xc1, 0x06, 0x52, 0x5f, 0xb3, 0xfa, 0x5c, 0xb7, 0xff, 0xbb, 0xd6, 0x6a, 0xf7, 0x58, 0xc5, 0xb6, 0xd4, 0xbf, 0x9b, 0x34, 0x38, 0xcf, 0x60, 0x14, 0x19, 0x99, 0x0e, 0x2b, 0xb8, 0xbb, 0x98, 0x37, 0x81, 0x0e, 0x16, 0xe4, 0x95, 0xf2, 0x45, 0x4c, 0x98, 0x8f, 0x10, 0x02, 0xed, 0x07, 0x4a, 0x4c, 0x32, 0x43, 0xd6, 0x52, 0x0b, 0x7d, 0xbb, 0xf8, 0xdc, 0x4a, 0xbe, 0xcc, 0x9b, 0x73, 0xcb, 0xf6, 0xce, 0x29, 0xcf, 0x4b, 0xc6, 0x1e, 0x02, 0xbc, 0xbd, 0x4b, 0xcf, 0x83, 0xbb, 0xc9, 0x29, 0x37, 0x32, 0xb6, 0x7a, 0x0a, 0x1c, 0x6d, 0xb3, 0xc6, 0x33, 0x23, 0xa7, 0x4f, 0x7a, 0x33, 0xef, 0xe6, 0x1e, 0xea, 0xce, 0x3e, 0xc4, 0x3d, 0x17, 0x5e, 0x58, 0xd7, 0x3a, 0xf9, 0x04, 0x28, 0xc1, 0xa4, 0xfc, 0x02, 0x7b, 0x8a, 0x4a, 0x0b, 0x32, 0x5b, 0x69, 0x6c, 0x3a, 0x2d, 0x3f, 0x9c, 0xae, 0xa4, 0xcc, 0xa8, 0x6f, 0xbe, 0xf7, 0xc2, 0x7b, 0x37, 0x1b, 0x5c, 0xe2, 0xd5, 0x5a, 0xf8, 0x77, 0x58, 0x74, 0x19, 0xf8, 0x07, 0x52, 0x7f, 0xc4, 0x7a, 0xad, 0x70, 0x5c, 0x6b, 0x30, 0x68, 0x8f, 0xf3, 0x3a, 0xfa, 0x8c, 0x1e, 0x05, 0xee, 0x3c, 0x02, 0x37, 0x19, 0x38, 0x83, 0x60, 0x59, 0x36, 0xe9, 0xe6, 0x97, 0x4d, 0xef, 0xfe, 0xee, 0x85, 0xdf, 0x9d, 0x32, 0x06, 0x4d, 0xfb, 0x58, 0x01, 0x1c, 0x46, 0x76, 0x9f, 0x29, 0x68, 0x14, 0xbd, 0xa9, 0xdf, 0x61, 0xab, 0x60, 0x3b, 0x05, 0x8b, 0x82, 0xd3, 0xf2, 0xb7, 0xdb, 0xc9, 0x41, 0xc3, 0x18, 0x77, 0x35, 0x23, 0xf2, 0x6f, 0x20, 0xa3, 0xcc, 0x97, 0x98, 0x1c, 0xfe, 0x43, 0x68, 0x33, 0x06, 0xbe, 0x34, 0xe8, 0x90, 0x11, 0x1b, 0x15, 0xbe, 0x24, 0xda, 0x4a, 0x0e, 0xa7, 0xe1, 0x9f, 0xb9, 0x0c, 0xa2, 0x2d, 0xe0, 0xd1, 0x17, 0x79, 0xbb, 0x9a, 0xf8, 0x37, 0x9c, 0x06, 0x6d, 0xa0, 0x79, 0xac, 0xcb, 0x65, 0x26, 0xf3, 0xc5, 0x68, 0x2b, 0xf7, 0x24, 0xf3, 0x75, 0xfe, 0x0e, 0x90, 0x21, 0x60, 0x87, 0x0b, 0xc4, 0xfe, 0x06, 0x2c, 0xaf, 0x4f, 0xe7, 0x96, 0x4d, 0xb2, 0x34, 0x65, 0x29, 0xcf, 0x6a, 0x36, 0xea, 0x34, 0x1c, 0x8b, 0xbc, 0xd8, 0x2b, 0xa7, 0x8c, 0x25, 0xeb, 0x13, 0x8a, 0x3d, 0x48, 0x74, 0x73, 0x50, 0x3d, 0x04, 0x9b, 0x2a, 0x0e, 0xe3, 0x83, 0xde, 0xaa, 0xae, 0x78, 0xb0, 0x35, 0x59, 0xed, 0x0c, 0x84, 0xf1, 0x21, 0x6f, 0xcd, 0xa2, 0x78, 0xa8, 0x35, 0x59, 0xc5, 0x27, 0x63, 0x65, 0x35, 0xcb, 0x9a, 0x8b, 0x7c, 0xc5, 0xbe, 0x92, 0xd2, 0xea, 0xe5, 0x4d, 0x01, 0x5f, 0xc0, 0x47, 0xe9, 0x2c, 0x32, 0xef, 0x09, 0xbf, 0x07, 0xdd, 0x96, 0x94, 0x12, 0xf0, 0x7c, 0xd6, 0x49, 0x7d, 0x44, 0x8e, 0x50, 0x1c, 0xad, 0x46, 0x24, 0xa4, 0xcf, 0x32, 0xf4, 0x25, 0x58, 0xc4, 0x3e, 0x45, 0x78, 0x18, 0x23, 0x9f, 0xc7, 0x6a, 0x86, 0xb9, 0x16, 0xe2, 0x42, 0xfe, 0xff, 0x11, 0xf7, 0x1e, 0xe0, 0x51, 0x5c, 0x67, 0xa3, 0xf0, 0x9c, 0x29, 0xdb, 0x7b, 0xd5, 0xf6, 0xde, 0x57, 0x75, 0xb5, 0xbb, 0xea, 0xbb, 0xea, 0x42, 0x15, 0x15, 0x10, 0x42, 0x20, 0x10, 0x42, 0xa2, 0x09, 0x90, 0x05, 0xc6, 0x14, 0xd3, 0x3b, 0xb6, 0x01, 0x57, 0x8a, 0x71, 0xdc, 0x8d, 0x0b, 0x8e, 0x71, 0x83, 0xb8, 0x24, 0xb1, 0x0d, 0x8e, 0xed, 0x24, 0x4e, 0xc7, 0xce, 0xcd, 0xe7, 0xe4, 0x4b, 0xfb, 0x92, 0x38, 0x71, 0x12, 0xc7, 0x8e, 0x73, 0x1d, 0x83, 0x46, 0xf7, 0x9c, 0x33, 0xbb, 0xab, 0x82, 0xb0, 0xc9, 0xfd, 0xff, 0xe7, 0xb9, 0x8f, 0x8d, 0x76, 0x67, 0xe7, 0xcc, 0x99, 0x53, 0xde, 0xf3, 0xf6, 0x92, 0x4e, 0x79, 0x31, 0x31, 0x16, 0x19, 0xa9, 0x4d, 0x0f, 0x86, 0x94, 0xe9, 0x9d, 0x7a, 0xc9, 0x4d, 0x86, 0xbc, 0xba, 0x9c, 0x9c, 0x86, 0xf2, 0x98, 0x49, 0xef, 0xd4, 0x49, 0x36, 0x19, 0xf2, 0xeb, 0xb2, 0x73, 0x1b, 0xca, 0xa3, 0xbc, 0x83, 0x4a, 0xb3, 0x4b, 0x95, 0xdd, 0x91, 0x80, 0x94, 0xc6, 0xa1, 0xb2, 0xb8, 0x95, 0xd9, 0x9d, 0x49, 0xaf, 0xc3, 0x67, 0x47, 0x3c, 0xdd, 0x3c, 0xfa, 0x6e, 0xf2, 0x6d, 0x66, 0x37, 0xce, 0x13, 0x5a, 0xc8, 0x29, 0x06, 0xa4, 0x93, 0x12, 0x74, 0x22, 0x89, 0x4a, 0x4d, 0x21, 0x40, 0x04, 0xdd, 0x14, 0x48, 0x6d, 0x01, 0x20, 0xda, 0x7a, 0xce, 0xa1, 0x8c, 0x9a, 0x9c, 0x82, 0xfa, 0x2b, 0x32, 0x6a, 0xd6, 0x69, 0x70, 0x7e, 0x4c, 0x94, 0x0d, 0x13, 0xe7, 0xc7, 0xd4, 0x80, 0x5d, 0xd3, 0xf3, 0x63, 0x7e, 0x65, 0xda, 0x4b, 0x8e, 0xa7, 0x6f, 0xe0, 0x29, 0xc8, 0x9f, 0xf3, 0x3e, 0xc7, 0x63, 0xb5, 0x24, 0x8d, 0x33, 0x0c, 0xeb, 0xba, 0x07, 0x95, 0x87, 0x32, 0x42, 0xeb, 0x03, 0x36, 0xb5, 0xda, 0x16, 0xd0, 0xa3, 0x0c, 0xd1, 0xe0, 0x71, 0x38, 0x3c, 0x3d, 0x1c, 0x26, 0x7c, 0x6d, 0x30, 0x4b, 0x0f, 0x87, 0xcb, 0xdb, 0x95, 0x15, 0x40, 0x42, 0x5c, 0x60, 0x22, 0x83, 0xf4, 0xd4, 0x6b, 0x7c, 0x06, 0xd6, 0xd0, 0xdf, 0x23, 0x9f, 0x63, 0x1a, 0xe1, 0xe6, 0x41, 0x9c, 0x87, 0x90, 0x0e, 0x31, 0x27, 0x85, 0x91, 0x09, 0xd0, 0xa2, 0x54, 0xa0, 0xe8, 0x58, 0xb5, 0x43, 0xeb, 0x58, 0x43, 0xd6, 0x8c, 0xbd, 0xca, 0x34, 0x6e, 0xc7, 0xcf, 0xac, 0xe7, 0xe5, 0x91, 0x67, 0x78, 0x0f, 0x7f, 0xd5, 0x33, 0xeb, 0xc9, 0xa2, 0xb1, 0xb7, 0x79, 0x0f, 0xaf, 0xc3, 0xcf, 0xdc, 0x04, 0x71, 0xe2, 0x13, 0xf0, 0x3c, 0x6b, 0xd1, 0x79, 0x26, 0x48, 0x04, 0x33, 0xc4, 0x22, 0xbc, 0x45, 0xe8, 0x38, 0x20, 0x8b, 0x26, 0x8f, 0x21, 0xb4, 0x40, 0xcb, 0xa4, 0x0d, 0xcf, 0x91, 0x54, 0x26, 0x14, 0x0c, 0x2f, 0xe0, 0x68, 0xdb, 0xaa, 0x2a, 0xcb, 0x21, 0xa9, 0x23, 0x1e, 0x0c, 0xc6, 0x1d, 0xd2, 0x43, 0x96, 0xaa, 0x55, 0xf4, 0xad, 0xa1, 0xa6, 0x25, 0x45, 0xf6, 0x98, 0x4f, 0xab, 0xf5, 0xc5, 0xec, 0x45, 0x4b, 0x9a, 0xb8, 0xf9, 0x2c, 0xe4, 0x09, 0xc9, 0x37, 0xf8, 0x6a, 0x28, 0x1c, 0xa7, 0x35, 0xdb, 0xa3, 0xf0, 0x4d, 0x04, 0xd9, 0x9d, 0x8e, 0x55, 0x25, 0x09, 0x08, 0x96, 0x72, 0x19, 0x7e, 0x9f, 0x12, 0x28, 0xa7, 0xbd, 0x2f, 0x06, 0x3f, 0x79, 0xe0, 0x68, 0xc3, 0x68, 0x5b, 0xf0, 0x1e, 0x85, 0xa7, 0x2c, 0xdc, 0x5d, 0x7b, 0xcf, 0x43, 0x3c, 0xa1, 0xbd, 0x6a, 0x69, 0x8d, 0x2b, 0xe6, 0xd1, 0xb4, 0xd4, 0x6e, 0xbf, 0x05, 0x76, 0xf0, 0x06, 0xbd, 0x82, 0x5c, 0xc4, 0xfc, 0x01, 0xc7, 0x55, 0x56, 0x70, 0xf6, 0x41, 0x15, 0x31, 0x49, 0x97, 0x0f, 0x07, 0x32, 0x88, 0xd8, 0xe6, 0xac, 0x74, 0x14, 0x34, 0x3c, 0x18, 0x00, 0x8f, 0x00, 0x9d, 0x0b, 0x12, 0x40, 0xe8, 0x73, 0x7b, 0xfd, 0x93, 0x3c, 0x1c, 0x27, 0x04, 0xb6, 0x38, 0xe6, 0xb5, 0x38, 0xc5, 0xd2, 0x1b, 0x8e, 0xa2, 0xa6, 0x40, 0xb0, 0xa9, 0xd8, 0xe9, 0x2c, 0x6e, 0x0a, 0x06, 0x9a, 0x8a, 0x1c, 0xab, 0x0b, 0x73, 0xc2, 0xd1, 0x68, 0x38, 0xa7, 0x90, 0xf1, 0x86, 0xa0, 0x18, 0x60, 0x8e, 0xd4, 0x85, 0x82, 0x75, 0xf9, 0x66, 0x73, 0x7e, 0x5d, 0x30, 0x14, 0x8b, 0x85, 0xc2, 0xb1, 0x18, 0x07, 0x67, 0x3f, 0x60, 0x3e, 0x21, 0x3b, 0xf8, 0x3d, 0x78, 0x7c, 0x90, 0xda, 0x5f, 0x6b, 0x14, 0xd7, 0x33, 0x86, 0x1f, 0xd8, 0xa2, 0x75, 0x5e, 0x7f, 0x7d, 0xcc, 0x66, 0x8b, 0xd5, 0xfb, 0xbd, 0x75, 0x51, 0xdb, 0xe2, 0xc2, 0x5c, 0xf4, 0xae, 0xdc, 0x42, 0xde, 0xc3, 0xfe, 0xea, 0x3c, 0x93, 0x29, 0xaf, 0xda, 0xef, 0xab, 0xca, 0x31, 0x1a, 0x73, 0xaa, 0x7c, 0xc1, 0x78, 0x3c, 0x98, 0xcd, 0x8d, 0x81, 0x82, 0x6b, 0x34, 0x48, 0x7e, 0xce, 0xaf, 0xc2, 0x63, 0x08, 0x20, 0xd9, 0xc3, 0x8c, 0x4a, 0x7d, 0x42, 0x29, 0x09, 0x05, 0x66, 0xd2, 0x04, 0x3d, 0x3a, 0xd3, 0x82, 0xb9, 0x26, 0xdf, 0xbf, 0xd6, 0xda, 0x25, 0x65, 0x76, 0xbb, 0x3d, 0x60, 0xf7, 0x7b, 0xd0, 0xf8, 0x51, 0x24, 0xd4, 0x97, 0x8d, 0x7f, 0x82, 0xcc, 0x5e, 0x7b, 0x35, 0xc1, 0x3b, 0x7f, 0x95, 0x5b, 0xf4, 0x37, 0xde, 0x29, 0x73, 0xba, 0x18, 0x67, 0xa8, 0xbe, 0xc0, 0x6c, 0x2e, 0xa8, 0xcf, 0x2c, 0x2c, 0x9e, 0x54, 0x34, 0x0a, 0x3e, 0x65, 0xc7, 0x14, 0x1c, 0x65, 0xa6, 0x88, 0xf7, 0x99, 0x9f, 0x52, 0x41, 0x41, 0x12, 0xcf, 0x2d, 0x44, 0x14, 0x26, 0xf3, 0xaf, 0x63, 0xd8, 0x70, 0xcc, 0x21, 0x7b, 0xf0, 0xfa, 0xc6, 0xcc, 0x70, 0x63, 0xa6, 0x22, 0xef, 0x5b, 0x0b, 0xaa, 0x3c, 0x9e, 0x9a, 0x02, 0xab, 0xb5, 0xa0, 0xc6, 0xe3, 0xa9, 0x2a, 0xb0, 0xce, 0x2f, 0x08, 0x07, 0xa3, 0xd1, 0x60, 0xb8, 0x00, 0xfc, 0x9b, 0xfd, 0x85, 0xd2, 0xa2, 0xbf, 0xf5, 0x82, 0x50, 0x01, 0xc9, 0x3d, 0xef, 0x66, 0x6f, 0x32, 0xdb, 0x60, 0xc8, 0x4e, 0x7a, 0xbd, 0x15, 0x61, 0x83, 0x21, 0x5c, 0xe1, 0x0d, 0x44, 0xa3, 0x81, 0x50, 0x24, 0x02, 0x4e, 0xfc, 0x54, 0x26, 0x78, 0x8b, 0xe3, 0x02, 0x48, 0xe2, 0x39, 0xfa, 0x56, 0xaa, 0x0d, 0xf3, 0x25, 0x5a, 0xc2, 0x9d, 0x74, 0x70, 0x68, 0x92, 0xa1, 0x68, 0x78, 0x4c, 0x20, 0xb3, 0x24, 0x82, 0x44, 0x51, 0xa4, 0x15, 0x69, 0xe4, 0x52, 0xcc, 0xa4, 0xf0, 0xa6, 0x30, 0x29, 0x0e, 0x2a, 0x3d, 0x26, 0x52, 0xf2, 0x96, 0xd4, 0xaa, 0x6f, 0xdf, 0x2f, 0x75, 0xb9, 0xc0, 0x85, 0xcf, 0x20, 0xe7, 0xd2, 0xf7, 0x82, 0x40, 0xae, 0xca, 0x52, 0x66, 0x78, 0x17, 0xfc, 0x21, 0x56, 0x4a, 0x78, 0x18, 0x16, 0x4f, 0x41, 0x1e, 0x66, 0x37, 0xa4, 0xb1, 0x28, 0xa6, 0x37, 0x9c, 0x0c, 0xe0, 0xb7, 0x21, 0xac, 0xc7, 0xbd, 0x36, 0x8d, 0xf7, 0xf0, 0xbb, 0xd5, 0x22, 0xd5, 0xe4, 0x77, 0xf3, 0x33, 0xef, 0xce, 0x7c, 0x23, 0x85, 0x3f, 0x50, 0x98, 0xf4, 0x0b, 0x6f, 0x97, 0x85, 0x5c, 0xe0, 0x8b, 0xcb, 0x4a, 0xbb, 0x7e, 0xe5, 0x4b, 0xaa, 0xb0, 0x8b, 0x6f, 0xfa, 0x54, 0xa6, 0xf8, 0x9d, 0x5e, 0xff, 0xa9, 0x5c, 0xfe, 0x3b, 0x5d, 0x16, 0xe2, 0x9b, 0xe8, 0xe7, 0xa0, 0x9c, 0xff, 0x1a, 0xca, 0xbb, 0x80, 0xe3, 0xe7, 0x39, 0x37, 0x19, 0xc8, 0x60, 0xbb, 0x71, 0xb0, 0xff, 0x34, 0x5c, 0x4a, 0xea, 0xa7, 0xe7, 0x4f, 0x61, 0x8a, 0x32, 0xd9, 0x1e, 0x0a, 0xd0, 0x67, 0x01, 0x7c, 0x5c, 0xce, 0x33, 0x92, 0x4a, 0xbe, 0x05, 0xf6, 0x89, 0x70, 0x1e, 0x9e, 0x42, 0xca, 0x75, 0x19, 0xa1, 0xed, 0x99, 0x3b, 0x56, 0x6a, 0xdd, 0xb9, 0x26, 0x63, 0x8e, 0x53, 0xab, 0x75, 0xe6, 0x18, 0x4d, 0xb9, 0x6e, 0x2d, 0xef, 0xa4, 0x31, 0xc7, 0xa5, 0xd5, 0xba, 0xb8, 0x2b, 0x78, 0x97, 0xe3, 0x75, 0xfe, 0xce, 0xde, 0x4b, 0xf6, 0x13, 0x66, 0x48, 0xd7, 0xd7, 0x73, 0xd8, 0x44, 0x64, 0x80, 0xc2, 0x22, 0x12, 0x9a, 0xb0, 0x8c, 0x9e, 0xba, 0x20, 0x33, 0x11, 0x87, 0x2a, 0x02, 0x2e, 0x1d, 0x35, 0x34, 0xa5, 0xfa, 0x7c, 0x2a, 0x90, 0x0b, 0xde, 0xa1, 0x46, 0xa7, 0x96, 0xa5, 0xd7, 0xe2, 0xdf, 0x08, 0x0e, 0x13, 0x4e, 0x54, 0x9e, 0xef, 0x39, 0xa7, 0x73, 0x6b, 0x7c, 0xe8, 0xfc, 0x33, 0xd7, 0x0a, 0x1f, 0x88, 0x80, 0xe7, 0x67, 0x74, 0xeb, 0x8f, 0xaf, 0xad, 0x9f, 0xc1, 0xdf, 0x9e, 0xcb, 0x73, 0xf9, 0x6b, 0xf6, 0x49, 0xb2, 0x1d, 0x72, 0x3c, 0x06, 0x62, 0x3e, 0x37, 0x24, 0x31, 0x85, 0x8d, 0xc4, 0x52, 0x09, 0xa4, 0x7f, 0x26, 0x7c, 0x45, 0xa6, 0xae, 0x52, 0xf1, 0x64, 0x52, 0x92, 0x22, 0x53, 0x7b, 0xb4, 0x1c, 0x79, 0xc7, 0x2b, 0x51, 0x70, 0x25, 0x31, 0x9a, 0xce, 0x76, 0xb6, 0x1c, 0x3b, 0x7d, 0x40, 0x01, 0x4a, 0xc3, 0x67, 0x08, 0x03, 0x30, 0xd0, 0xc8, 0xeb, 0x6f, 0x72, 0xae, 0x63, 0x7d, 0xc6, 0x55, 0x82, 0xb4, 0xa5, 0x73, 0x17, 0xf7, 0xd4, 0x0f, 0x4e, 0x38, 0x46, 0xbc, 0xcb, 0xe5, 0x23, 0x2e, 0x5f, 0xed, 0x97, 0x4e, 0x38, 0x40, 0x20, 0xb2, 0xe5, 0x66, 0xb7, 0x80, 0xbf, 0x8f, 0x3f, 0x04, 0x47, 0x1b, 0xe7, 0x82, 0xff, 0x25, 0x64, 0xda, 0xa9, 0x0a, 0x07, 0xfa, 0xab, 0x10, 0x70, 0x42, 0xce, 0x86, 0x9c, 0xe0, 0x68, 0x7a, 0x92, 0xe2, 0xc9, 0xcc, 0x1b, 0x33, 0x85, 0x79, 0xfb, 0x3e, 0xe4, 0x33, 0x65, 0x46, 0x8d, 0xd8, 0x6e, 0xac, 0x2d, 0xe8, 0xd3, 0x49, 0x44, 0x8e, 0x68, 0x53, 0x21, 0xe4, 0xdd, 0x7c, 0xe8, 0x5d, 0x45, 0xf4, 0x01, 0x28, 0xa3, 0x20, 0x9a, 0x99, 0x82, 0x49, 0x24, 0xf3, 0x40, 0xf6, 0x84, 0x23, 0x97, 0xc0, 0xa5, 0x8e, 0x80, 0xdf, 0x9f, 0x59, 0xb2, 0x85, 0x3e, 0x00, 0x4c, 0x78, 0x1d, 0x9f, 0x25, 0x08, 0x3a, 0x04, 0xe9, 0x25, 0xc3, 0xc9, 0x10, 0x04, 0xd9, 0x03, 0x17, 0x24, 0x84, 0xd4, 0x4a, 0x61, 0x94, 0x99, 0x48, 0x41, 0xf1, 0x21, 0x4f, 0x8d, 0x9d, 0xf3, 0xe8, 0xd0, 0xd8, 0x82, 0xf7, 0xa8, 0xcd, 0xf4, 0x67, 0x97, 0xc5, 0xb0, 0x3d, 0x3a, 0x73, 0x50, 0xe2, 0x64, 0xca, 0xe1, 0x77, 0x1c, 0xf9, 0x9f, 0x34, 0x33, 0x29, 0x8e, 0x1c, 0x3d, 0x0e, 0xbb, 0x9e, 0xcf, 0xf5, 0xa1, 0x35, 0xe1, 0x3e, 0xe2, 0x0e, 0x00, 0xa5, 0x5e, 0x19, 0xe0, 0x03, 0x1d, 0x97, 0xd4, 0x38, 0x0e, 0x22, 0x9f, 0x51, 0xdd, 0xec, 0x41, 0xb1, 0x51, 0x2c, 0x36, 0x4a, 0xc9, 0xb3, 0xc6, 0x2f, 0x3e, 0x90, 0x48, 0x49, 0xf9, 0xcf, 0x9f, 0xa2, 0x4e, 0x58, 0x67, 0xe7, 0xe6, 0xb5, 0xd9, 0xaf, 0xdc, 0x9c, 0x4c, 0x24, 0xca, 0x2d, 0x49, 0x07, 0xfd, 0x1b, 0x38, 0x9b, 0x9e, 0xf1, 0x7f, 0x30, 0x7f, 0x81, 0x38, 0x05, 0xe9, 0xf0, 0x43, 0x58, 0xc6, 0xc3, 0xe6, 0x5b, 0xd0, 0x9f, 0x4e, 0x62, 0x13, 0xc9, 0x98, 0x63, 0x0a, 0xc9, 0x66, 0xc8, 0xdf, 0x78, 0x94, 0x0a, 0x1e, 0xdf, 0x88, 0x6b, 0x16, 0x7b, 0x34, 0x24, 0x8f, 0x76, 0x39, 0xdd, 0x5e, 0x78, 0x64, 0x54, 0x31, 0x77, 0xa4, 0x80, 0xd6, 0xa9, 0xf8, 0x85, 0x53, 0x4b, 0x13, 0x93, 0x12, 0x32, 0x34, 0x76, 0x89, 0xfa, 0xef, 0x65, 0xaf, 0xb2, 0x5f, 0x3c, 0x70, 0x3f, 0xfb, 0xef, 0x57, 0x96, 0x2f, 0x7f, 0x05, 0xf0, 0xef, 0x7f, 0x00, 0xf0, 0x5e, 0x5d, 0xf6, 0x8b, 0xba, 0x9d, 0xe7, 0x6f, 0xb8, 0xe1, 0xfc, 0xae, 0xba, 0xba, 0x5d, 0xe8, 0x73, 0x67, 0x1d, 0x15, 0x7a, 0x7a, 0xf6, 0x83, 0xec, 0xa7, 0x17, 0x86, 0x87, 0x2f, 0x00, 0xe9, 0x83, 0x0f, 0x00, 0xd9, 0x85, 0xd5, 0xab, 0x2f, 0xb0, 0x9f, 0x3c, 0xb0, 0xe3, 0xfb, 0x87, 0x9b, 0x9b, 0x0f, 0x7f, 0x7f, 0xc7, 0xce, 0xef, 0x1d, 0x6e, 0x6a, 0x3a, 0xfc, 0x3d, 0xb4, 0xbe, 0xc8, 0x9c, 0xd5, 0x86, 0xf5, 0x13, 0x90, 0x4e, 0x0b, 0x26, 0x56, 0x08, 0xf1, 0x64, 0x61, 0xb8, 0x2d, 0x61, 0x3b, 0x5a, 0x1d, 0x47, 0x2a, 0x21, 0x09, 0x5e, 0x20, 0xb4, 0xc9, 0x20, 0xc2, 0xb4, 0x9d, 0x33, 0xbb, 0x84, 0xec, 0xcd, 0x32, 0x93, 0x58, 0x94, 0x25, 0x63, 0xb7, 0xf2, 0x3d, 0xa6, 0xfb, 0xc9, 0x86, 0x6f, 0x9b, 0x75, 0xd4, 0x8f, 0xdc, 0x73, 0x73, 0x73, 0xe7, 0xba, 0xaf, 0xe4, 0xa9, 0x6d, 0x68, 0x23, 0x28, 0xa2, 0x13, 0xee, 0xe1, 0x21, 0xf8, 0x95, 0x22, 0xf8, 0x50, 0x62, 0x2b, 0x4a, 0x46, 0x21, 0x85, 0x80, 0x78, 0x8f, 0xe9, 0xc7, 0xfb, 0xcf, 0x83, 0xa7, 0x34, 0xd4, 0x84, 0x0a, 0xc7, 0xe0, 0xbd, 0x0d, 0x93, 0xcd, 0x34, 0x0d, 0x25, 0x3b, 0x91, 0x50, 0x40, 0xf3, 0x69, 0x3e, 0xe4, 0x4e, 0x90, 0x4a, 0x5b, 0xc0, 0x87, 0x98, 0x10, 0x4a, 0xc2, 0xe8, 0x1f, 0xe2, 0xa9, 0xe8, 0x43, 0x97, 0xdf, 0xa1, 0xe3, 0x97, 0xdf, 0xf9, 0x1c, 0xf3, 0x63, 0xa7, 0x9e, 0x7f, 0xfe, 0xb2, 0xfe, 0xf9, 0xe7, 0xc9, 0x6d, 0x2f, 0xa5, 0x61, 0x86, 0x99, 0x85, 0xed, 0xfa, 0x88, 0xf7, 0x48, 0xe9, 0x9b, 0x42, 0x58, 0x33, 0x8f, 0xa6, 0x04, 0xa1, 0x86, 0xe6, 0x67, 0x71, 0x7e, 0x63, 0xf0, 0x1f, 0x33, 0xeb, 0xd2, 0x98, 0xfa, 0x3d, 0xfa, 0x78, 0x1a, 0x70, 0x00, 0x51, 0x09, 0xc7, 0xbb, 0x11, 0x7e, 0xf5, 0x20, 0xfe, 0x1e, 0xa9, 0xde, 0xac, 0x70, 0x35, 0xd4, 0xd8, 0xd8, 0x8a, 0x98, 0xfa, 0x54, 0x77, 0x48, 0xa3, 0x9b, 0x1a, 0x30, 0x20, 0xdc, 0x2e, 0x43, 0x96, 0x5e, 0x4b, 0x78, 0x80, 0x87, 0xe1, 0x23, 0x46, 0xca, 0xeb, 0x43, 0x69, 0x5c, 0xac, 0x94, 0x56, 0x87, 0x2b, 0x84, 0xf2, 0x11, 0xfa, 0xe6, 0x04, 0x79, 0x21, 0x40, 0xd6, 0x40, 0x87, 0x16, 0xbc, 0x9d, 0xdf, 0xea, 0x0b, 0xd5, 0x58, 0x80, 0x5c, 0xa9, 0x95, 0xd0, 0x40, 0xeb, 0x62, 0xbf, 0x6b, 0x0d, 0x6a, 0xbd, 0xd5, 0x16, 0xf2, 0x9d, 0xcb, 0xef, 0xe8, 0x94, 0xe0, 0x47, 0xac, 0x53, 0x2c, 0xa3, 0xe3, 0xf4, 0x67, 0x7c, 0x81, 0xbe, 0xd0, 0xc9, 0x3e, 0xa3, 0xb1, 0x64, 0x69, 0x84, 0xfa, 0x02, 0x3f, 0xbb, 0xcf, 0xe5, 0x97, 0x32, 0xcc, 0xe9, 0xd9, 0x59, 0xb9, 0x2a, 0xd2, 0x45, 0xc6, 0x25, 0x76, 0xe5, 0x82, 0xd3, 0x1c, 0xaf, 0x51, 0x3d, 0x7e, 0x98, 0xd7, 0xc0, 0x93, 0x11, 0x22, 0x48, 0xd7, 0x72, 0xf1, 0x3a, 0x0b, 0x69, 0xe1, 0x28, 0xc1, 0x63, 0x78, 0xeb, 0x04, 0x00, 0xd7, 0xfc, 0xa6, 0x01, 0xca, 0x6f, 0xc0, 0x30, 0x44, 0x37, 0xa2, 0x3e, 0x4c, 0x2f, 0x1f, 0x82, 0x28, 0xd3, 0x96, 0x9b, 0x93, 0xa5, 0x54, 0x66, 0xa1, 0xff, 0x14, 0x22, 0xbe, 0x25, 0xe4, 0xe1, 0xc2, 0x9d, 0x7c, 0xd8, 0x3d, 0xd1, 0x4a, 0xea, 0xd5, 0x5c, 0x7d, 0x5e, 0xbe, 0xd6, 0x15, 0xe5, 0x4a, 0x9c, 0xc6, 0xa3, 0x11, 0xea, 0xc9, 0x95, 0x67, 0x36, 0x55, 0x56, 0x6e, 0x3a, 0xb3, 0xf2, 0x9d, 0xcc, 0x97, 0x7b, 0x3f, 0xff, 0xfa, 0xc2, 0x85, 0x5f, 0xff, 0xfc, 0x5e, 0xfc, 0xa5, 0xef, 0xeb, 0x9f, 0x9f, 0x7c, 0xe7, 0x33, 0xd0, 0xbc, 0xf3, 0xcc, 0x92, 0xfe, 0xa7, 0x77, 0x36, 0x93, 0x64, 0xf3, 0xce, 0xa7, 0xfb, 0x97, 0x9c, 0xd9, 0xd9, 0x0c, 0xe8, 0x45, 0x60, 0xd9, 0xd7, 0x3f, 0xdc, 0xb3, 0xe7, 0x4f, 0xcf, 0x0c, 0x81, 0xd7, 0x5f, 0x07, 0x43, 0xcf, 0xfc, 0x69, 0xcf, 0x9e, 0x0f, 0xbf, 0xbe, 0x0c, 0xbc, 0x8c, 0xf7, 0x6e, 0x31, 0x41, 0xf0, 0xf8, 0x78, 0xed, 0xb1, 0x6c, 0x47, 0x23, 0xd4, 0x43, 0x13, 0x03, 0x5c, 0x26, 0x98, 0xf4, 0xb9, 0x87, 0x2d, 0x3d, 0x84, 0x5b, 0xa9, 0xd4, 0x7b, 0x94, 0x4a, 0xad, 0x90, 0x6f, 0xe2, 0x12, 0x0a, 0xe1, 0xc2, 0x4e, 0x54, 0x54, 0x89, 0x2d, 0x15, 0x68, 0xe0, 0xca, 0x88, 0x12, 0x4e, 0x45, 0x59, 0x18, 0x8b, 0xf0, 0xf8, 0x6d, 0xa7, 0xfe, 0x78, 0xfc, 0xd2, 0xf1, 0x3f, 0xdc, 0xdb, 0x76, 0xd8, 0xe9, 0xc9, 0xee, 0x3f, 0x31, 0x7c, 0x69, 0xf8, 0xe4, 0x92, 0x6c, 0x8f, 0x93, 0x39, 0x35, 0x36, 0xbe, 0xe3, 0x07, 0x77, 0xcf, 0xe1, 0xb1, 0x0f, 0x83, 0x85, 0xbc, 0xb9, 0x77, 0xfd, 0x70, 0x3b, 0x09, 0x82, 0x36, 0xb6, 0x61, 0xe1, 0xed, 0x43, 0xc5, 0x0c, 0x75, 0xe7, 0x95, 0xd5, 0x4c, 0xf1, 0xd0, 0xed, 0x0b, 0xc1, 0x37, 0x6c, 0x41, 0x38, 0xb6, 0xa5, 0x70, 0xc9, 0x3e, 0x66, 0x9e, 0x22, 0x4a, 0x89, 0xf9, 0x49, 0x89, 0x91, 0x47, 0x52, 0xa0, 0xc8, 0x4d, 0x12, 0xd8, 0x37, 0x0e, 0x45, 0xe7, 0x65, 0xa5, 0x73, 0x6c, 0x44, 0x9a, 0x78, 0x0c, 0x89, 0xf8, 0xb9, 0x14, 0xc4, 0x99, 0x26, 0x6e, 0x45, 0xa7, 0xdd, 0xc2, 0x86, 0x39, 0xd8, 0x61, 0x49, 0xc8, 0x15, 0x0c, 0xba, 0xf8, 0x7c, 0x43, 0xc8, 0x83, 0x8b, 0xc7, 0x46, 0x27, 0x87, 0x9c, 0xc1, 0xa9, 0x68, 0x1d, 0x48, 0x1f, 0xea, 0x40, 0xa5, 0x91, 0xa9, 0xc9, 0xbe, 0xa8, 0x08, 0xb0, 0x98, 0x8f, 0x7d, 0x8e, 0xb1, 0xcf, 0x5a, 0xf7, 0x0f, 0x14, 0xb7, 0xee, 0x3d, 0xdb, 0xdf, 0x7f, 0x76, 0x6f, 0x5b, 0xf1, 0xd2, 0xfd, 0xad, 0x63, 0x9f, 0x39, 0x7c, 0x40, 0xe1, 0x6b, 0xf7, 0xf7, 0x9e, 0x58, 0x9b, 0x58, 0xfd, 0xcd, 0xb1, 0xdb, 0x6f, 0x1f, 0xfb, 0xe6, 0xea, 0xc4, 0xda, 0x13, 0xbd, 0xfe, 0x76, 0xdf, 0xf3, 0x6b, 0xac, 0xbe, 0xcf, 0x64, 0x7d, 0x47, 0xce, 0x2f, 0xdb, 0x74, 0x6e, 0x73, 0x59, 0xd9, 0xe6, 0x73, 0x9b, 0x96, 0x9d, 0x3f, 0xd2, 0x27, 0xfb, 0xcc, 0x67, 0x5d, 0x23, 0xe0, 0xd9, 0x36, 0x3c, 0xf0, 0x9d, 0x91, 0xbb, 0xfe, 0x85, 0x7a, 0xfa, 0xd7, 0x5d, 0x23, 0xdf, 0x79, 0x60, 0x83, 0x8d, 0x27, 0x48, 0xf9, 0x77, 0x8c, 0x7f, 0x42, 0x9f, 0xa4, 0xc5, 0x84, 0x86, 0x28, 0x27, 0xb6, 0x9d, 0x8f, 0xe6, 0x59, 0x29, 0x44, 0xd1, 0x38, 0x5a, 0x6c, 0xc0, 0x2a, 0x7f, 0x94, 0xa9, 0x39, 0x86, 0xca, 0xb7, 0xa0, 0x90, 0x12, 0xa4, 0xd4, 0x2b, 0xcc, 0x84, 0xfe, 0x5f, 0xa3, 0x41, 0x2e, 0x89, 0xc3, 0xdb, 0x19, 0x26, 0x27, 0x63, 0x34, 0x98, 0x7e, 0x1f, 0x99, 0xfa, 0xb4, 0x5a, 0x6d, 0xb9, 0xb6, 0xcc, 0xed, 0x47, 0x82, 0xa1, 0x00, 0xa1, 0xcd, 0x89, 0xf2, 0xd1, 0x38, 0x91, 0x17, 0xc0, 0xf9, 0xe7, 0x91, 0xf5, 0x79, 0x5a, 0x16, 0xc2, 0x52, 0xc4, 0xbb, 0xe1, 0x66, 0x54, 0x8e, 0x3c, 0x27, 0xd1, 0x9a, 0x5b, 0xbc, 0xac, 0x35, 0xc7, 0x5f, 0x33, 0xbf, 0x20, 0xbf, 0xbb, 0x36, 0x47, 0x00, 0xf6, 0x55, 0x57, 0xf8, 0x1a, 0xca, 0x73, 0x65, 0xf6, 0xb6, 0xb9, 0xbd, 0xd9, 0x73, 0xee, 0x5c, 0x5d, 0x51, 0xbb, 0xf9, 0x91, 0xc5, 0x2b, 0xef, 0xcb, 0x57, 0x49, 0x7c, 0x1b, 0x62, 0xa4, 0x30, 0xcb, 0x1f, 0xb7, 0x93, 0xdf, 0x75, 0x94, 0xe7, 0x9a, 0x73, 0x16, 0xdd, 0x31, 0xb8, 0x70, 0xdf, 0x1c, 0xbf, 0xa7, 0x65, 0x63, 0x27, 0xfb, 0xad, 0x36, 0x63, 0x50, 0x1b, 0xae, 0xce, 0xcd, 0xaf, 0x0b, 0x6b, 0x6b, 0x77, 0xbd, 0xb2, 0xe1, 0xe6, 0xef, 0x1e, 0x6e, 0xe9, 0xae, 0x5a, 0xe4, 0x1b, 0xfb, 0x43, 0x7e, 0x5f, 0x20, 0x58, 0xea, 0x51, 0x71, 0xeb, 0x75, 0x80, 0x3d, 0x46, 0xeb, 0x20, 0xbc, 0x58, 0x88, 0x62, 0xe2, 0x44, 0x52, 0x06, 0xb1, 0x26, 0x89, 0x88, 0x67, 0x1c, 0x1e, 0x3a, 0x2a, 0x05, 0x33, 0x4e, 0xc4, 0xfe, 0x43, 0xa4, 0x3b, 0x90, 0x36, 0x72, 0x46, 0x32, 0x45, 0xc2, 0x0b, 0x51, 0xda, 0xe2, 0xbc, 0x4c, 0xaa, 0x92, 0xab, 0x5a, 0x46, 0xaf, 0x6e, 0x09, 0x1b, 0xa1, 0xc3, 0xfe, 0x25, 0x2d, 0x71, 0x71, 0x0d, 0x49, 0x71, 0x51, 0xac, 0x30, 0xe0, 0x75, 0xf8, 0x9d, 0x08, 0xe0, 0x52, 0x4e, 0x15, 0x08, 0xdc, 0x42, 0x20, 0x1a, 0x49, 0x67, 0xe3, 0x84, 0x8b, 0xeb, 0x9b, 0xf0, 0xae, 0xe0, 0xa7, 0x6b, 0x74, 0xfb, 0x94, 0x2e, 0xd0, 0x14, 0x5c, 0xd6, 0x3a, 0x6b, 0x4d, 0xa3, 0x27, 0xbf, 0x73, 0x6d, 0x45, 0x85, 0xaf, 0x68, 0x41, 0x95, 0x7b, 0x64, 0xa0, 0x74, 0x6e, 0x91, 0xe9, 0x42, 0xe1, 0x8a, 0x07, 0x87, 0x87, 0xee, 0x5a, 0x9c, 0x3b, 0x3a, 0x34, 0x6b, 0xb4, 0x2d, 0x90, 0x58, 0xff, 0xc8, 0xe0, 0x53, 0x4b, 0x2f, 0x00, 0xa2, 0xac, 0x24, 0xb7, 0x63, 0x75, 0xa2, 0x79, 0x79, 0xc2, 0x7c, 0xaf, 0xbe, 0xb0, 0xa3, 0x6c, 0xe4, 0x86, 0xd0, 0xe2, 0x63, 0xc3, 0xbc, 0x1d, 0x6f, 0x1d, 0x6c, 0x28, 0x5d, 0xb2, 0xbd, 0x66, 0xe4, 0x7e, 0x97, 0xaa, 0xa6, 0x7f, 0x53, 0x5d, 0xf7, 0xd7, 0x36, 0xd4, 0xad, 0x61, 0xe3, 0x10, 0x8f, 0xad, 0x86, 0x70, 0xe6, 0x61, 0x9e, 0x26, 0xe4, 0x78, 0xe5, 0x56, 0x25, 0x45, 0x16, 0x80, 0x92, 0x69, 0x71, 0xc6, 0x65, 0x04, 0x6c, 0x6e, 0xe4, 0xa6, 0x03, 0x8f, 0xdf, 0x08, 0x9e, 0x27, 0xb1, 0x06, 0x62, 0xe1, 0x54, 0x0a, 0xde, 0x34, 0x0c, 0x0d, 0x23, 0xcb, 0xdb, 0x57, 0x37, 0xc3, 0x95, 0xd7, 0x85, 0x7e, 0xbf, 0x36, 0xa0, 0x77, 0xa3, 0xcc, 0x1a, 0xa9, 0x5c, 0x2b, 0x70, 0x2d, 0x80, 0x93, 0x8f, 0xc3, 0x3e, 0xb9, 0x24, 0x0a, 0x9e, 0xc9, 0x25, 0xca, 0x55, 0x99, 0x94, 0x2b, 0x20, 0x44, 0xf9, 0xea, 0x06, 0x2b, 0x93, 0xc3, 0xad, 0xe1, 0xdc, 0x9e, 0x1d, 0xb3, 0x79, 0xcd, 0x43, 0xe5, 0xc6, 0xd6, 0xd6, 0xca, 0x75, 0xbd, 0x65, 0x42, 0xf0, 0xc3, 0xba, 0x35, 0xcd, 0xfe, 0x96, 0xbd, 0xcf, 0x0f, 0x6d, 0xf8, 0xfa, 0x68, 0xbc, 0xf3, 0xae, 0xef, 0x6d, 0x9c, 0xb7, 0xa5, 0xc5, 0xb5, 0x9c, 0x1c, 0xdc, 0xfa, 0xfc, 0xba, 0x58, 0x64, 0xc1, 0xce, 0xd6, 0xd6, 0x91, 0x12, 0xe7, 0x09, 0x5b, 0x7d, 0x73, 0x9b, 0xaf, 0xb6, 0xb5, 0x0c, 0x12, 0xe4, 0x01, 0xaa, 0x64, 0xe9, 0x81, 0xd9, 0x37, 0xbe, 0xbc, 0xab, 0x76, 0xc1, 0x53, 0xff, 0x3c, 0xfe, 0x1d, 0x90, 0xf3, 0xfd, 0xe5, 0x91, 0x8e, 0x65, 0x85, 0xf3, 0xee, 0xe0, 0x78, 0xe3, 0x00, 0x7b, 0x86, 0xce, 0x86, 0x74, 0x11, 0xdb, 0x6f, 0x91, 0x4f, 0x82, 0x49, 0xc0, 0x50, 0x68, 0x45, 0xb0, 0x61, 0x11, 0x45, 0x39, 0x90, 0xb1, 0x26, 0x5c, 0xdd, 0x00, 0x61, 0x47, 0x17, 0xe1, 0xf4, 0xf8, 0x74, 0x0c, 0xe2, 0x8d, 0x64, 0x14, 0xc6, 0x8b, 0x4c, 0x28, 0xe5, 0x69, 0x61, 0x05, 0x2a, 0x7d, 0x05, 0x85, 0xa4, 0x08, 0x3a, 0xdb, 0x3c, 0x77, 0x78, 0x7b, 0xcd, 0x05, 0x5d, 0x5e, 0xbc, 0xbc, 0xb6, 0x2d, 0xec, 0x15, 0xe8, 0x7c, 0xb6, 0xbe, 0x23, 0xc3, 0x2d, 0xa6, 0x9c, 0xd6, 0x44, 0xbe, 0x8a, 0xe7, 0xd4, 0x50, 0x9f, 0x6f, 0xf9, 0xd7, 0x47, 0xbf, 0x1e, 0x64, 0xe7, 0x82, 0xaf, 0xaf, 0x7c, 0xfb, 0x99, 0xdb, 0x97, 0x17, 0xcf, 0x92, 0x9b, 0x34, 0x92, 0xe5, 0x6f, 0xb2, 0xbf, 0x3d, 0xdf, 0xfc, 0xe4, 0xf3, 0xe7, 0x7b, 0xbc, 0x26, 0x0e, 0xce, 0x9b, 0xc7, 0x3f, 0xa5, 0x79, 0xcc, 0x63, 0x90, 0xcb, 0xaa, 0x21, 0x96, 0x27, 0xc5, 0x35, 0x80, 0xa4, 0xa3, 0x22, 0x72, 0x62, 0xbb, 0xac, 0xc8, 0x8f, 0x02, 0x6e, 0xc0, 0x48, 0xda, 0xd7, 0x27, 0xd4, 0x84, 0xbc, 0xec, 0x30, 0x5b, 0x54, 0x88, 0xe4, 0xdd, 0x2f, 0x6b, 0x90, 0x8b, 0xfc, 0xbe, 0x95, 0x90, 0x28, 0x57, 0xe4, 0x84, 0x2d, 0x26, 0xbd, 0x0e, 0x9b, 0x77, 0xf9, 0x5c, 0x11, 0x79, 0x4c, 0xa6, 0x38, 0x2a, 0x35, 0xd9, 0x25, 0x6b, 0x5a, 0x8a, 0xd8, 0x0c, 0xc2, 0xd4, 0x93, 0xa1, 0x8a, 0x65, 0x8d, 0x81, 0x9c, 0xe6, 0x81, 0x58, 0xd1, 0x50, 0x6b, 0xae, 0xb3, 0x76, 0x39, 0xb8, 0x8f, 0x32, 0x18, 0x64, 0xc9, 0xce, 0x05, 0x81, 0xae, 0xba, 0xd8, 0xfc, 0xd1, 0x0d, 0xa3, 0xf3, 0x63, 0x55, 0x37, 0x9f, 0x5d, 0x3d, 0xfa, 0xdc, 0x96, 0xe4, 0x73, 0xcf, 0x65, 0xcf, 0x2e, 0x75, 0xf9, 0xe7, 0xee, 0x5e, 0xe0, 0xea, 0xe9, 0x5b, 0xe8, 0x3b, 0x18, 0x6a, 0x5f, 0x3f, 0xab, 0x6d, 0xd3, 0xa2, 0x26, 0xbb, 0x36, 0xb1, 0x70, 0x5b, 0x47, 0xf5, 0xc6, 0xf9, 0x51, 0x7a, 0xbb, 0x51, 0x29, 0x57, 0xca, 0x23, 0x6e, 0x67, 0x69, 0x49, 0xa2, 0x69, 0xe1, 0xc6, 0x63, 0x8b, 0x97, 0x9f, 0xb9, 0xb9, 0xb6, 0x61, 0xc7, 0xd3, 0x83, 0xa7, 0xff, 0x92, 0x30, 0x15, 0x55, 0xb5, 0x15, 0x14, 0xce, 0xab, 0xf4, 0x30, 0x22, 0x99, 0x10, 0xee, 0x5f, 0xc7, 0xf8, 0xa7, 0xd4, 0x02, 0x9c, 0x4f, 0x63, 0x5f, 0x52, 0x84, 0xbc, 0x38, 0xa5, 0xb8, 0xf4, 0x36, 0xb7, 0x40, 0xd9, 0x04, 0x09, 0xc5, 0x40, 0x92, 0x1e, 0xc9, 0x14, 0x2b, 0x87, 0x0c, 0x12, 0x9f, 0x66, 0x06, 0xd2, 0x0e, 0xd4, 0x31, 0x9c, 0xc2, 0xbc, 0x17, 0xd7, 0xbb, 0xc4, 0x09, 0x5d, 0xaf, 0xbb, 0x7d, 0x2e, 0xa2, 0x31, 0xaa, 0xb2, 0x92, 0x78, 0x34, 0x37, 0x3b, 0x1c, 0x74, 0x3b, 0x6d, 0x96, 0x2c, 0x9d, 0x1b, 0x71, 0x53, 0x9e, 0x34, 0xa6, 0x9c, 0x58, 0xa1, 0x54, 0xf1, 0x87, 0x4c, 0xa4, 0x73, 0x29, 0xe4, 0x4a, 0xd2, 0x96, 0x59, 0x2e, 0x30, 0x6c, 0x81, 0x6b, 0x5e, 0xff, 0x60, 0x7e, 0xe7, 0x5d, 0x23, 0x95, 0x43, 0x73, 0xb2, 0x2b, 0xfc, 0xaa, 0x82, 0x81, 0xbb, 0x07, 0x46, 0x1f, 0x29, 0x0e, 0x8b, 0xd5, 0x32, 0x99, 0x27, 0xde, 0x5e, 0x1c, 0x6d, 0x8b, 0x1a, 0x37, 0xae, 0x7e, 0xe4, 0x91, 0x2d, 0x37, 0x59, 0x8b, 0x3b, 0x0a, 0x93, 0x73, 0x4b, 0x7c, 0xea, 0x2c, 0xe9, 0xed, 0xa1, 0x32, 0xaf, 0x32, 0xd8, 0x76, 0x43, 0x7d, 0xf7, 0x66, 0xbb, 0x2a, 0x52, 0x56, 0x69, 0x8b, 0x0d, 0x34, 0x67, 0xd7, 0xc4, 0x9a, 0x94, 0x62, 0xbd, 0x59, 0xaf, 0xf3, 0x46, 0x2c, 0xc9, 0xf6, 0x5d, 0x73, 0xeb, 0x3d, 0x89, 0x1c, 0xa3, 0xcd, 0x6d, 0x53, 0xc1, 0xb5, 0x6a, 0x61, 0xcf, 0x53, 0xc5, 0x90, 0x07, 0x88, 0x23, 0x1b, 0x51, 0x18, 0xeb, 0xbc, 0x8d, 0x06, 0x92, 0xa8, 0x4f, 0x8b, 0x0b, 0x21, 0xa2, 0x39, 0x1e, 0xcb, 0xcf, 0x26, 0x11, 0xa7, 0xa5, 0x43, 0x52, 0x07, 0x83, 0x69, 0x24, 0x3a, 0xa3, 0xd8, 0x66, 0x8e, 0xc1, 0x22, 0xc5, 0xaa, 0x20, 0x66, 0x15, 0xce, 0x08, 0xd2, 0x4e, 0x2a, 0x44, 0x32, 0x59, 0x61, 0x4f, 0x78, 0x69, 0x7f, 0x4f, 0xb5, 0xdf, 0x59, 0xb3, 0xa4, 0x72, 0xe1, 0xba, 0xa4, 0x2e, 0xdb, 0x7f, 0x5c, 0xed, 0x96, 0x85, 0x3b, 0x6a, 0xe6, 0xee, 0xeb, 0xcd, 0x6d, 0xbd, 0xe5, 0x5b, 0x23, 0x89, 0x83, 0x0d, 0x37, 0xe8, 0xac, 0x6a, 0x21, 0x79, 0x0f, 0xd3, 0x28, 0xce, 0x72, 0x16, 0xb8, 0x83, 0xfd, 0xab, 0x37, 0xee, 0x3c, 0x34, 0xab, 0x72, 0x61, 0x55, 0xae, 0xc6, 0x5c, 0x51, 0xdd, 0xe8, 0xaf, 0xde, 0x9e, 0x2c, 0xb5, 0x77, 0x05, 0xd5, 0x5e, 0xaf, 0x57, 0x63, 0xac, 0xe9, 0xdf, 0x35, 0x6f, 0xdd, 0xa5, 0x77, 0xbf, 0xbf, 0x52, 0x20, 0x94, 0xa8, 0xf4, 0xe2, 0xc5, 0xf8, 0xac, 0xaa, 0xd8, 0xa7, 0x68, 0x1d, 0xdc, 0x6b, 0x3b, 0x91, 0x83, 0x6a, 0x9a, 0x52, 0x84, 0x41, 0x40, 0xd2, 0x19, 0x67, 0x0b, 0xc4, 0x59, 0xc7, 0x90, 0xed, 0x36, 0x8c, 0x4c, 0xe2, 0x79, 0x64, 0x33, 0x2a, 0x5e, 0xea, 0x76, 0x23, 0x61, 0x40, 0x3d, 0x35, 0xa7, 0xd3, 0x64, 0x37, 0x8b, 0x42, 0x6f, 0x08, 0xa8, 0xc4, 0x06, 0x9f, 0xb9, 0x63, 0xb3, 0xb1, 0xf6, 0xec, 0xea, 0x49, 0x9e, 0xef, 0xc9, 0x64, 0x98, 0xfa, 0xb6, 0x34, 0x4b, 0x2d, 0x6e, 0x6e, 0x9f, 0x35, 0x1f, 0x79, 0xba, 0x5f, 0x71, 0x53, 0x1f, 0x70, 0xf9, 0xcf, 0xc8, 0x5f, 0x35, 0xd5, 0x43, 0x7c, 0x3a, 0x0c, 0xf1, 0x69, 0x25, 0xc4, 0xa7, 0x3c, 0x28, 0xe1, 0x39, 0x50, 0xa6, 0x5a, 0x3e, 0x4a, 0xa6, 0x0f, 0xf9, 0x5a, 0x28, 0x9d, 0xa0, 0x03, 0x07, 0x99, 0xc3, 0xc9, 0x74, 0x87, 0x69, 0x96, 0xcb, 0x90, 0x60, 0x68, 0xd0, 0xcb, 0x1c, 0x72, 0x07, 0x5f, 0xca, 0x97, 0x8a, 0x85, 0xf0, 0x59, 0x9e, 0x53, 0x88, 0x98, 0x70, 0x15, 0xf6, 0xed, 0xf2, 0xa1, 0x48, 0x64, 0x1e, 0x05, 0x10, 0x3a, 0xd1, 0x6a, 0x48, 0x0c, 0x36, 0x80, 0xc3, 0x8d, 0x20, 0xcc, 0x3f, 0x71, 0xeb, 0xa1, 0x13, 0x7c, 0x20, 0x3a, 0xbe, 0xff, 0xc0, 0x09, 0x11, 0x08, 0x8d, 0xbc, 0xb8, 0xbd, 0xb6, 0x16, 0x25, 0x41, 0x3b, 0x87, 0x92, 0xa0, 0x9d, 0x23, 0x8f, 0xb1, 0xff, 0xb8, 0x72, 0x05, 0xc8, 0x0f, 0x00, 0xf2, 0xd3, 0x4f, 0x59, 0x96, 0x3c, 0xbc, 0xe8, 0x25, 0xf6, 0x6f, 0xcf, 0xbf, 0xc0, 0xfe, 0xf5, 0xa5, 0xbe, 0xbe, 0x97, 0x80, 0xfa, 0x85, 0xe7, 0x81, 0xea, 0xa5, 0x45, 0x18, 0xaf, 0xa0, 0xd8, 0xbf, 0x4f, 0x98, 0x27, 0xa1, 0xfc, 0x56, 0x43, 0x24, 0x92, 0x65, 0x50, 0x06, 0x84, 0x68, 0x86, 0x46, 0x99, 0x04, 0x70, 0x59, 0x9b, 0x55, 0x4d, 0x28, 0x53, 0x3e, 0xd1, 0x8b, 0xfc, 0x8e, 0x50, 0xe6, 0x70, 0x1e, 0x8f, 0x4b, 0x39, 0x44, 0x35, 0x0b, 0x85, 0xc2, 0x1a, 0x61, 0x8d, 0x5f, 0x17, 0x70, 0xf9, 0xbc, 0x1e, 0xa7, 0x4a, 0x8c, 0x0b, 0x46, 0x41, 0xd6, 0x09, 0x39, 0x1a, 0x40, 0x58, 0xe1, 0x85, 0x90, 0x5f, 0x1a, 0x37, 0x68, 0x08, 0x19, 0x68, 0x69, 0xb1, 0x73, 0x1e, 0x4a, 0x03, 0x90, 0xd6, 0xb6, 0x16, 0xfa, 0x26, 0xe1, 0x0d, 0xf0, 0xe7, 0x1f, 0xbd, 0x6d, 0xdd, 0xb1, 0xac, 0xa1, 0x89, 0x12, 0xd8, 0x1c, 0x03, 0xbc, 0xf2, 0xad, 0xaf, 0x6e, 0x1b, 0x3a, 0x3e, 0x98, 0x1f, 0x2a, 0xad, 0xa8, 0x49, 0xe6, 0x77, 0x96, 0xbb, 0x4c, 0xf1, 0xae, 0xe2, 0xea, 0xb5, 0x6d, 0x61, 0xde, 0xa1, 0xdf, 0x7c, 0x6d, 0x6e, 0x70, 0xf6, 0xba, 0xc6, 0xee, 0x3b, 0x6f, 0x9c, 0xa3, 0xa7, 0xef, 0x9f, 0xd7, 0x44, 0x1f, 0x38, 0x59, 0xf3, 0xab, 0xb6, 0x2c, 0x7f, 0x6d, 0xdb, 0x53, 0xec, 0x5f, 0x5f, 0x5b, 0x96, 0x58, 0xff, 0xd0, 0x92, 0x97, 0x12, 0x25, 0x6d, 0xc6, 0x80, 0xa9, 0x62, 0xa8, 0xf9, 0xa5, 0xc4, 0x8a, 0xa6, 0x40, 0xe2, 0x96, 0xff, 0x7e, 0xb0, 0xad, 0x16, 0xf8, 0x5a, 0x37, 0xcc, 0x8e, 0x2f, 0xae, 0xf3, 0x47, 0x57, 0x3d, 0xbc, 0x16, 0xc1, 0xd0, 0xce, 0xf1, 0x7f, 0x92, 0x28, 0x7f, 0xa3, 0x32, 0x25, 0xf7, 0xe6, 0x64, 0x3c, 0x48, 0x20, 0x47, 0xe4, 0x71, 0x61, 0x99, 0x55, 0x8d, 0x4c, 0xd2, 0xa5, 0x20, 0x83, 0xec, 0x76, 0x5e, 0x4c, 0xd4, 0xcd, 0x31, 0x86, 0x4b, 0xed, 0x15, 0x8b, 0x92, 0x76, 0xea, 0xf3, 0x31, 0xf7, 0xac, 0x96, 0x32, 0xad, 0x5d, 0x27, 0x0e, 0x36, 0xad, 0x48, 0xa0, 0x3e, 0x7d, 0xe3, 0x9f, 0xd1, 0x15, 0x70, 0x3d, 0xc3, 0xc4, 0x2f, 0x92, 0x62, 0x2b, 0xa4, 0x8c, 0x3a, 0x29, 0x49, 0x93, 0x69, 0x24, 0xa4, 0x4f, 0xfb, 0x8e, 0x45, 0x9a, 0x18, 0x9c, 0xa1, 0x11, 0x01, 0x6a, 0x2e, 0x56, 0xa9, 0x48, 0x26, 0xdf, 0x8e, 0x4e, 0xba, 0x5d, 0x48, 0xa4, 0x53, 0x53, 0xcc, 0x78, 0x3b, 0x37, 0xa3, 0x90, 0xf9, 0x92, 0xce, 0xc3, 0x5f, 0xf6, 0x74, 0xd2, 0x34, 0x71, 0x07, 0xf1, 0xfe, 0xa9, 0xdb, 0x29, 0xc4, 0x86, 0x0b, 0x74, 0xa3, 0x40, 0x98, 0x30, 0x11, 0xf2, 0x78, 0x73, 0x5c, 0x3c, 0x28, 0xd6, 0x79, 0xd2, 0x9c, 0x20, 0xc5, 0x89, 0xab, 0x58, 0x61, 0x07, 0xa8, 0x1c, 0x2a, 0x43, 0x00, 0x49, 0xe4, 0xff, 0x1f, 0xa1, 0x2b, 0x0a, 0x72, 0x03, 0x6d, 0xf5, 0x65, 0xda, 0x27, 0x05, 0x22, 0x92, 0x91, 0x0a, 0x3e, 0xe2, 0x9b, 0x0d, 0x8f, 0x81, 0x43, 0x4b, 0xc4, 0x06, 0x77, 0xbe, 0x6b, 0x96, 0xd8, 0x94, 0xe3, 0x0e, 0x75, 0xd4, 0x97, 0xa8, 0xc1, 0x2e, 0x4d, 0xa2, 0xf8, 0x40, 0x5b, 0xc0, 0xa4, 0x08, 0x37, 0x97, 0x81, 0xf3, 0x7a, 0x9f, 0x5a, 0xed, 0xd3, 0x8c, 0xbd, 0x20, 0xcf, 0xa2, 0xbe, 0x06, 0x14, 0xd6, 0x40, 0x96, 0x08, 0xd3, 0x44, 0x45, 0xa0, 0xa6, 0xf0, 0xf2, 0x17, 0xc5, 0x05, 0x1c, 0xad, 0x3e, 0x00, 0xcf, 0xff, 0x93, 0x70, 0xef, 0x3c, 0xc4, 0x2f, 0x38, 0xde, 0x57, 0x6c, 0x13, 0x42, 0x04, 0x20, 0x03, 0x04, 0x0f, 0xe7, 0xae, 0x4b, 0x5d, 0x01, 0x5e, 0x46, 0x93, 0xe5, 0x42, 0xa8, 0x21, 0x87, 0xcb, 0xa1, 0x88, 0xd7, 0xb5, 0x09, 0xa5, 0x8e, 0x4b, 0x7f, 0xe7, 0xd0, 0x44, 0x6a, 0xa1, 0xa7, 0x37, 0xcd, 0x9d, 0xd4, 0x34, 0x77, 0xa2, 0xa9, 0xf1, 0x7a, 0x9a, 0x16, 0x21, 0xe6, 0xdb, 0x91, 0x69, 0x85, 0x49, 0x47, 0x38, 0xd5, 0x88, 0x98, 0x68, 0xd3, 0xc3, 0x39, 0xdb, 0x21, 0x99, 0x4b, 0xed, 0x52, 0x7b, 0xbc, 0x2e, 0xc4, 0x81, 0x67, 0xd6, 0x19, 0x1d, 0x23, 0x28, 0xed, 0x62, 0xaf, 0xf3, 0x29, 0xa1, 0x3b, 0xf0, 0x0b, 0xfd, 0x64, 0x75, 0x45, 0xb0, 0x25, 0x99, 0x23, 0x2e, 0xbd, 0xbb, 0xef, 0xd6, 0x26, 0x3e, 0xd3, 0x1f, 0xcc, 0xbe, 0x38, 0x38, 0x34, 0x6f, 0x77, 0xe8, 0x62, 0x73, 0xe2, 0x40, 0xab, 0x31, 0x98, 0x15, 0x69, 0x89, 0xf6, 0x2c, 0xe9, 0x0a, 0xce, 0x75, 0xb3, 0x9f, 0x86, 0x02, 0xf4, 0xbf, 0xc7, 0xee, 0x1b, 0xde, 0xd1, 0x3f, 0x97, 0x3c, 0x3e, 0xe6, 0x6c, 0xaa, 0x9f, 0x88, 0x35, 0xf9, 0x08, 0xae, 0xa5, 0x1e, 0xd9, 0x4b, 0xe4, 0x13, 0xfe, 0x13, 0x58, 0x5c, 0x08, 0xa7, 0x3c, 0x36, 0x74, 0x0e, 0x2d, 0xcd, 0xd7, 0x85, 0x00, 0x4f, 0x04, 0x78, 0xfc, 0x49, 0xf1, 0x39, 0x98, 0xcd, 0xa1, 0x3e, 0xb2, 0xf5, 0xae, 0x3b, 0xd4, 0xba, 0x89, 0x1d, 0x7c, 0x40, 0x6f, 0x63, 0x3e, 0x91, 0xaa, 0x04, 0x91, 0xd9, 0x89, 0x3c, 0x0d, 0x83, 0xf8, 0x9b, 0x43, 0xec, 0xc7, 0x2c, 0xfb, 0x8f, 0x55, 0x99, 0xa0, 0x97, 0xd6, 0x73, 0x6f, 0xbc, 0xbd, 0xd8, 0x61, 0x84, 0xef, 0x6d, 0x85, 0x34, 0x08, 0xa9, 0x2f, 0xaa, 0x90, 0x1c, 0x5a, 0x08, 0x65, 0x7f, 0xd0, 0xe8, 0x74, 0x90, 0x64, 0x3d, 0x66, 0x25, 0xc9, 0x45, 0x14, 0x2e, 0xa3, 0x85, 0x38, 0x15, 0xa2, 0xb9, 0xaa, 0xb2, 0xa2, 0xcc, 0xe1, 0x2d, 0xc0, 0xdc, 0x16, 0xc2, 0x2d, 0x93, 0x88, 0x67, 0x4a, 0x7a, 0x9b, 0x91, 0x32, 0xa5, 0x52, 0xd7, 0x63, 0x47, 0x17, 0x07, 0x25, 0xc8, 0xb3, 0xf4, 0x54, 0x0e, 0x37, 0x07, 0x8f, 0xec, 0xef, 0x3d, 0xd8, 0xb0, 0xa2, 0xd6, 0xe1, 0x6d, 0x59, 0xdf, 0x9a, 0xec, 0x2a, 0xd0, 0x20, 0x02, 0xe5, 0x91, 0xd8, 0x8a, 0xb2, 0x5b, 0x8f, 0xac, 0xae, 0xca, 0x8a, 0x75, 0x27, 0x3b, 0xe7, 0xda, 0xe2, 0x8d, 0xa1, 0x33, 0x61, 0x37, 0xf9, 0x0c, 0xd3, 0xe8, 0x0a, 0xf8, 0x6a, 0x16, 0xc4, 0x7a, 0x97, 0xdc, 0xeb, 0xac, 0x98, 0x5b, 0x18, 0x6b, 0x2d, 0x2f, 0x34, 0xeb, 0x0b, 0x8a, 0xab, 0x7c, 0xb3, 0x76, 0x27, 0xcb, 0xec, 0x9d, 0x01, 0xad, 0xc7, 0x61, 0x91, 0xea, 0x12, 0x8b, 0xf7, 0x2d, 0xcc, 0x5d, 0xb2, 0xa0, 0xc5, 0xe8, 0xde, 0x3c, 0x27, 0xb7, 0xa3, 0xb6, 0x58, 0x1b, 0x70, 0xcd, 0xe2, 0xd6, 0xb6, 0x09, 0xce, 0xd1, 0x0c, 0xe7, 0x58, 0x4e, 0xe4, 0x25, 0xb3, 0x3d, 0x88, 0x1e, 0x34, 0xaa, 0x55, 0x88, 0xce, 0x52, 0x34, 0x40, 0xe9, 0x9e, 0x33, 0x85, 0x16, 0x90, 0x86, 0xa3, 0xac, 0xa4, 0x30, 0x3f, 0x27, 0x4c, 0x94, 0x83, 0x72, 0xce, 0x54, 0xf4, 0xd5, 0x53, 0x43, 0x98, 0x69, 0xf2, 0x72, 0x90, 0xee, 0xe9, 0x13, 0x73, 0x8b, 0xed, 0x71, 0x3c, 0x31, 0x43, 0x49, 0x5f, 0x6d, 0xef, 0xa2, 0xc4, 0x8d, 0x8f, 0xaf, 0x78, 0x29, 0xcf, 0xb2, 0xac, 0x76, 0x79, 0x9d, 0xeb, 0xe8, 0xbe, 0x05, 0xe4, 0x13, 0x5f, 0x31, 0xab, 0xbc, 0xa1, 0xbe, 0x56, 0x83, 0xe7, 0xf0, 0xca, 0x9e, 0x5b, 0x17, 0x47, 0xda, 0x5d, 0x01, 0x47, 0x49, 0x7b, 0xfe, 0xaa, 0x75, 0xf7, 0x72, 0x73, 0x9b, 0x35, 0xfe, 0x39, 0x7d, 0x82, 0xfa, 0x98, 0x88, 0x03, 0xcb, 0x8b, 0xf9, 0x80, 0xe0, 0xa7, 0xe5, 0x54, 0x33, 0x83, 0xcd, 0xfd, 0x80, 0x1c, 0xc1, 0xd1, 0x87, 0xd8, 0xe1, 0x39, 0x07, 0xe7, 0xe5, 0xcd, 0xc5, 0xe7, 0x46, 0xfc, 0xa5, 0x6d, 0xc2, 0x64, 0x1a, 0x1d, 0x7e, 0x55, 0x3f, 0xca, 0xeb, 0xe8, 0x47, 0x7d, 0x1d, 0xfd, 0x68, 0xb1, 0xe8, 0xc7, 0x20, 0xcf, 0x15, 0x24, 0xc7, 0xa0, 0x96, 0x6b, 0x66, 0xea, 0xed, 0x2b, 0x1b, 0x61, 0x41, 0x1a, 0x23, 0x4c, 0xb1, 0x47, 0xe3, 0xd5, 0xe9, 0x5d, 0x01, 0xa7, 0x80, 0x6f, 0xe6, 0x20, 0x16, 0x4b, 0xc8, 0x74, 0x29, 0x27, 0x44, 0x73, 0x31, 0x23, 0x0e, 0x1e, 0xc9, 0x31, 0x88, 0xe5, 0x48, 0x3a, 0x04, 0xaa, 0xb8, 0x0b, 0x63, 0x4f, 0x07, 0x7d, 0xc2, 0x6e, 0x3b, 0x0c, 0x00, 0x5f, 0xa1, 0xb3, 0xfb, 0xb2, 0xfc, 0x0d, 0xb1, 0x2d, 0x1b, 0x04, 0x22, 0x31, 0xc5, 0x9e, 0xa2, 0xf5, 0xde, 0x48, 0x62, 0x56, 0x40, 0x6a, 0xc9, 0x92, 0x6b, 0xac, 0xe5, 0x11, 0x37, 0xef, 0xe7, 0x22, 0xb5, 0x59, 0x45, 0x33, 0xd4, 0x9d, 0x46, 0x77, 0x76, 0xbb, 0xff, 0xe6, 0x67, 0x37, 0x37, 0xfa, 0xa4, 0xad, 0x0e, 0xe7, 0xe3, 0xe7, 0xb3, 0x97, 0x6d, 0xb9, 0x6b, 0xfe, 0x19, 0xf6, 0x8b, 0x6f, 0xef, 0x68, 0xb6, 0xd3, 0x42, 0xb9, 0x68, 0x5f, 0xe3, 0xc3, 0x80, 0xff, 0xcd, 0x4e, 0x89, 0x41, 0x8b, 0x1c, 0x2b, 0xb7, 0x40, 0xdc, 0x69, 0xa0, 0x85, 0x84, 0x02, 0x62, 0x9b, 0xdc, 0x64, 0x18, 0x49, 0x7d, 0x2e, 0xec, 0x6a, 0x99, 0x26, 0x7f, 0xb1, 0x8c, 0x03, 0x65, 0x21, 0xc2, 0x59, 0x79, 0x74, 0xb3, 0xda, 0x1f, 0xf0, 0x3a, 0x99, 0xb4, 0x24, 0x1b, 0xcb, 0xe8, 0x4d, 0x5c, 0x53, 0x6c, 0x4b, 0x28, 0x33, 0x22, 0x48, 0x14, 0xec, 0xac, 0xd9, 0x77, 0xa7, 0xaf, 0x7e, 0x49, 0x69, 0x6c, 0x71, 0x5d, 0xe0, 0x62, 0xc5, 0x86, 0x27, 0x57, 0xf5, 0x3f, 0xb9, 0xa3, 0x11, 0x25, 0x02, 0x9c, 0x77, 0x67, 0xf1, 0x45, 0x20, 0xa9, 0xad, 0xe0, 0x92, 0x00, 0xfa, 0x9a, 0xd7, 0xd6, 0x2f, 0x7b, 0x7c, 0x43, 0xb2, 0x76, 0xcf, 0xeb, 0x37, 0xa3, 0xec, 0x88, 0x15, 0xc5, 0x6c, 0x15, 0x91, 0xce, 0x25, 0x7b, 0x96, 0xfa, 0x27, 0x1c, 0x9f, 0x1f, 0x69, 0x07, 0x55, 0x48, 0xa7, 0xd6, 0x48, 0x32, 0x24, 0xf6, 0x8a, 0x84, 0xb4, 0x29, 0x27, 0xe3, 0xf1, 0x08, 0x25, 0x1b, 0x25, 0x94, 0x5b, 0x94, 0x7e, 0xa5, 0xcf, 0x66, 0xc1, 0x05, 0x2f, 0x14, 0x40, 0xc1, 0xe7, 0xa7, 0xcf, 0x8e, 0x2e, 0x35, 0x54, 0x9f, 0x07, 0x9f, 0x12, 0x2b, 0x98, 0xec, 0xab, 0x1a, 0x21, 0x77, 0x2e, 0x3e, 0xd8, 0xed, 0xbf, 0xf3, 0xf0, 0xed, 0xb7, 0x69, 0xfc, 0x89, 0x20, 0x18, 0x73, 0x5a, 0xff, 0x3c, 0xab, 0xbd, 0x7a, 0xdb, 0x0b, 0x23, 0x6b, 0xce, 0xed, 0xac, 0x6f, 0x3a, 0xf2, 0x93, 0xfd, 0xd4, 0xc2, 0xbc, 0x85, 0x07, 0xe7, 0x3f, 0xf3, 0xc2, 0x2b, 0x2f, 0x24, 0xd6, 0x2f, 0xaa, 0x96, 0x1f, 0x5b, 0xa8, 0xf2, 0x17, 0x9d, 0xdb, 0x36, 0xf2, 0xea, 0xfe, 0xe6, 0xba, 0xdd, 0xdf, 0xda, 0xb8, 0xf1, 0xdb, 0x7b, 0x53, 0xf8, 0x13, 0xe1, 0xb1, 0xdd, 0x90, 0x17, 0xcd, 0x43, 0x71, 0x6a, 0x79, 0xd8, 0xe8, 0x60, 0x32, 0x62, 0x5e, 0x1a, 0x10, 0xeb, 0x32, 0x6b, 0x0a, 0x9a, 0x73, 0xc2, 0x2e, 0x27, 0xc5, 0x9f, 0x9e, 0x57, 0x69, 0xe6, 0xf3, 0x8d, 0xf0, 0x2b, 0x35, 0x5b, 0xa8, 0x54, 0x28, 0xdc, 0x85, 0x4d, 0xf1, 0x86, 0x35, 0x8d, 0xde, 0xfc, 0xbe, 0x83, 0x3d, 0xc9, 0xce, 0x49, 0xd8, 0x6a, 0xd1, 0xe3, 0x5b, 0x67, 0x25, 0xb6, 0x3c, 0x7f, 0xe3, 0xea, 0xc7, 0x92, 0x54, 0x89, 0x58, 0x6a, 0xb0, 0x18, 0x0a, 0xe6, 0x6d, 0xa8, 0x59, 0xb4, 0xb9, 0xbd, 0xc8, 0x72, 0xd5, 0x59, 0x36, 0xd7, 0xdf, 0x78, 0x76, 0xef, 0xd2, 0x67, 0xf7, 0xb6, 0xd4, 0x25, 0xb9, 0x31, 0x67, 0x8d, 0x7f, 0x4e, 0xdd, 0x0a, 0xcf, 0xae, 0x89, 0x58, 0xcc, 0x1d, 0x5a, 0x1d, 0x46, 0xb7, 0x48, 0x30, 0x9c, 0xc2, 0x06, 0xa5, 0x8e, 0xeb, 0x4c, 0x77, 0xf1, 0x91, 0xb8, 0xc6, 0x63, 0x3d, 0x3d, 0xe7, 0x5d, 0x5a, 0x8f, 0x8b, 0x03, 0x19, 0xa4, 0x6c, 0x9b, 0xca, 0x45, 0x29, 0x71, 0x49, 0x43, 0x07, 0x75, 0xab, 0xdb, 0xc1, 0x7e, 0x77, 0x12, 0x43, 0xc5, 0x7e, 0xcf, 0xe1, 0xa0, 0x0a, 0xa8, 0x87, 0x9c, 0x9e, 0xb1, 0x37, 0x1b, 0x9b, 0x33, 0x8c, 0x15, 0x59, 0x37, 0xd7, 0xe0, 0xe5, 0xc6, 0x8d, 0x62, 0x7b, 0xff, 0x0c, 0x69, 0x95, 0x17, 0xd1, 0x2a, 0xc8, 0xf0, 0x63, 0x2a, 0x95, 0x8a, 0x19, 0x28, 0xc4, 0xa4, 0xca, 0x4b, 0x78, 0x3c, 0xda, 0x00, 0xa6, 0x14, 0x93, 0x34, 0x0a, 0x88, 0x56, 0x66, 0xf2, 0x16, 0x91, 0x3e, 0x2f, 0x5c, 0x61, 0xea, 0x05, 0x91, 0x46, 0x2e, 0x6c, 0xdd, 0xf9, 0x78, 0x6f, 0xff, 0x89, 0x15, 0xf1, 0x0b, 0x8e, 0xb2, 0xce, 0x48, 0x5e, 0x5f, 0x53, 0xae, 0x58, 0xa1, 0x12, 0x90, 0x22, 0x85, 0x56, 0x44, 0x7d, 0x9e, 0x58, 0x3f, 0xba, 0x3e, 0x79, 0xc7, 0xcf, 0x0e, 0x54, 0x46, 0x97, 0x1e, 0xe9, 0x41, 0xc1, 0xad, 0xb1, 0x39, 0x65, 0x36, 0xff, 0xc2, 0x7b, 0xd7, 0x47, 0x86, 0x86, 0x06, 0x23, 0x2a, 0xa3, 0x9c, 0x8f, 0xe1, 0xb5, 0x85, 0x3d, 0xc3, 0xec, 0x83, 0xfb, 0x2f, 0x25, 0x8a, 0x10, 0x96, 0x0f, 0x62, 0x08, 0x98, 0x38, 0x4b, 0x3c, 0x2c, 0x66, 0x23, 0x0e, 0x20, 0x7d, 0x9a, 0x8a, 0xe2, 0x4a, 0x65, 0x96, 0xcf, 0x0f, 0x21, 0xd5, 0x98, 0xd6, 0x04, 0x23, 0x21, 0x70, 0xf2, 0x61, 0x4a, 0x29, 0x5a, 0x11, 0x9a, 0xd0, 0x5b, 0x91, 0x76, 0x9b, 0xd3, 0xb0, 0xe9, 0xa9, 0xca, 0xbe, 0xa3, 0x4b, 0x0a, 0x42, 0x75, 0xf3, 0xf3, 0x12, 0x0d, 0xe7, 0x43, 0x1d, 0x1b, 0x9a, 0x06, 0x1e, 0xbd, 0xb1, 0xb2, 0xf3, 0xd8, 0xfb, 0x7b, 0xce, 0xef, 0xfe, 0xd9, 0xdd, 0xed, 0x91, 0x45, 0x7b, 0xdb, 0x0b, 0xf8, 0x50, 0x5a, 0x13, 0xf4, 0x2d, 0xcd, 0x5f, 0x74, 0xdb, 0x82, 0x3d, 0xa0, 0x64, 0xc5, 0xdd, 0x0b, 0x5b, 0xb7, 0x2f, 0x28, 0x98, 0xdd, 0xd7, 0xb5, 0xa9, 0xcd, 0xd3, 0xbc, 0xf7, 0xc5, 0xe5, 0x5b, 0xbf, 0x7b, 0x74, 0x36, 0x60, 0x5f, 0x06, 0x75, 0xa0, 0xed, 0x96, 0x6f, 0xdf, 0xd0, 0x7b, 0xff, 0x9e, 0x21, 0x47, 0x9b, 0xc4, 0xa8, 0x57, 0x51, 0x05, 0x2f, 0x6f, 0x6a, 0x3f, 0x38, 0x50, 0x02, 0xb0, 0x9e, 0xbb, 0x15, 0xce, 0x69, 0x3f, 0xa4, 0x5b, 0x9c, 0x9e, 0x7b, 0x49, 0x8a, 0x85, 0x4a, 0xd5, 0xa4, 0x45, 0x8a, 0x56, 0x24, 0xd0, 0xa0, 0x8c, 0xdb, 0x29, 0x15, 0x37, 0x2a, 0x3b, 0xc0, 0xe3, 0x2d, 0xe7, 0xe1, 0x00, 0xe5, 0x6b, 0xb7, 0x5a, 0x9b, 0x6a, 0xd5, 0x93, 0x94, 0x4d, 0xd6, 0x88, 0x8b, 0xae, 0xad, 0x11, 0x9f, 0x58, 0x07, 0x65, 0x84, 0xaa, 0x5b, 0x70, 0xb8, 0xbf, 0xa0, 0xa0, 0xff, 0xf0, 0x82, 0xf3, 0x0b, 0xb9, 0x2f, 0x0b, 0xcf, 0xef, 0xb9, 0x74, 0xac, 0xbd, 0xfd, 0xd8, 0xa5, 0x3d, 0x93, 0xbe, 0x80, 0xc2, 0xfe, 0x5b, 0xba, 0xbb, 0x6f, 0xe9, 0x8f, 0x02, 0x10, 0xe5, 0xbe, 0x15, 0x02, 0x6a, 0x0f, 0x68, 0x3c, 0xf0, 0xfa, 0x4d, 0x1b, 0x5f, 0x3b, 0xd0, 0xc8, 0x4d, 0xbe, 0xf1, 0xc0, 0x6b, 0x1b, 0x6f, 0x7a, 0x1d, 0x5e, 0x81, 0xbb, 0x50, 0x7e, 0x7d, 0x96, 0x3d, 0x45, 0xbd, 0x0f, 0x99, 0xb8, 0x08, 0xf1, 0x68, 0x52, 0x98, 0xed, 0x56, 0x8b, 0x68, 0x06, 0x87, 0x8d, 0xcb, 0xb0, 0xd2, 0x19, 0xf1, 0xc7, 0xc8, 0xc3, 0x33, 0x96, 0x8a, 0xc5, 0x46, 0xdc, 0x5d, 0x1e, 0x91, 0xe6, 0x01, 0x67, 0xbe, 0x5f, 0x94, 0xe1, 0xbc, 0xcd, 0x93, 0xee, 0x43, 0xb0, 0xc8, 0x9b, 0x08, 0xe8, 0x2e, 0xe2, 0x52, 0xa8, 0xe0, 0xc3, 0x93, 0xe2, 0xc1, 0x67, 0x68, 0xd2, 0xc3, 0xc5, 0x96, 0xc0, 0xb1, 0x15, 0x04, 0x3c, 0xde, 0x10, 0x56, 0x61, 0x33, 0x19, 0x16, 0x7c, 0x26, 0xfe, 0x3b, 0x15, 0x00, 0x8c, 0x53, 0x48, 0xc3, 0x85, 0x8c, 0x50, 0xef, 0xdb, 0x73, 0x83, 0x2d, 0xb5, 0x65, 0xc6, 0x27, 0x18, 0xb9, 0xe0, 0x7b, 0x8c, 0xd9, 0x70, 0x0b, 0x98, 0xf3, 0x9c, 0xcc, 0xec, 0xc9, 0x77, 0xba, 0x49, 0x99, 0xc3, 0xbe, 0xfc, 0x40, 0xf0, 0x11, 0x5a, 0x26, 0x7c, 0x99, 0x67, 0x32, 0xdc, 0xc2, 0xbe, 0xf2, 0xba, 0xc4, 0xe4, 0x08, 0x59, 0x7e, 0xd2, 0x16, 0x30, 0xca, 0x43, 0x2d, 0xe5, 0x60, 0x87, 0xad, 0x44, 0xcf, 0x0e, 0x2a, 0xf4, 0xe4, 0x3b, 0xe0, 0x76, 0xab, 0xdf, 0x20, 0x6e, 0x40, 0xcc, 0xf8, 0xec, 0x7e, 0xf6, 0x36, 0x6b, 0xb1, 0x0e, 0xec, 0x51, 0x64, 0x8d, 0x75, 0xb2, 0x2d, 0xa6, 0x80, 0x51, 0xca, 0x9d, 0xcf, 0x4d, 0x10, 0x6e, 0x4e, 0x42, 0xb8, 0xc9, 0x46, 0x31, 0x5d, 0x41, 0xbb, 0x85, 0x02, 0xa4, 0x08, 0x6b, 0x15, 0xb1, 0x3d, 0x07, 0xbb, 0x03, 0xe7, 0x70, 0x52, 0x46, 0x46, 0x91, 0x96, 0x4d, 0x84, 0xcd, 0x6a, 0xa5, 0x09, 0x09, 0x16, 0x60, 0x62, 0xd3, 0xdd, 0x04, 0xb2, 0x32, 0xe0, 0x1c, 0xe6, 0x19, 0x33, 0x03, 0x82, 0x0f, 0x88, 0x37, 0x98, 0x01, 0x89, 0x56, 0x26, 0xe8, 0xbd, 0xf7, 0xdd, 0xd1, 0x0b, 0xec, 0x5f, 0xd8, 0x8f, 0xf6, 0x4b, 0xf4, 0x0a, 0x51, 0xfe, 0x82, 0x3d, 0x5d, 0x17, 0xda, 0xf6, 0x0f, 0x14, 0x49, 0x55, 0x2a, 0xc6, 0xa5, 0x49, 0x9d, 0xdd, 0xbb, 0xbf, 0xbf, 0x33, 0x29, 0x64, 0xbf, 0x01, 0xea, 0x55, 0x80, 0xfc, 0x2f, 0x52, 0x27, 0xd7, 0x99, 0x24, 0x6c, 0xac, 0xe9, 0xc6, 0xae, 0x42, 0x11, 0x23, 0xf9, 0xe2, 0x53, 0x7e, 0xde, 0xc2, 0x3b, 0x56, 0xce, 0x5a, 0xbb, 0xb0, 0x2b, 0x87, 0xf3, 0xa7, 0xee, 0x1a, 0xff, 0x98, 0x79, 0x0e, 0xe2, 0x96, 0x3a, 0xe4, 0x67, 0x21, 0x05, 0x14, 0x8e, 0x3d, 0x21, 0x29, 0x28, 0x48, 0x8c, 0xf0, 0x79, 0x78, 0xe0, 0x13, 0xda, 0x05, 0x0e, 0xd7, 0xd4, 0x11, 0xb5, 0x1e, 0x6d, 0x8e, 0xde, 0xef, 0xc3, 0xfa, 0x72, 0x9d, 0x2a, 0x8d, 0x6e, 0x32, 0x2a, 0x72, 0x55, 0x1c, 0xcd, 0x81, 0xcf, 0xc1, 0x34, 0xc6, 0x3e, 0x33, 0x20, 0x22, 0x26, 0x58, 0xd6, 0x5c, 0xb2, 0xfc, 0xe8, 0xdc, 0xf8, 0xfc, 0xc6, 0x22, 0xad, 0xb6, 0x78, 0xd6, 0xfc, 0xe8, 0xdc, 0xa3, 0xcb, 0x4b, 0x9a, 0xcb, 0x6e, 0x11, 0x19, 0x94, 0xb4, 0x48, 0x11, 0xf2, 0x99, 0x43, 0x95, 0xad, 0xae, 0x70, 0x65, 0x48, 0x7b, 0x51, 0x1b, 0xaa, 0xcc, 0x76, 0xb5, 0x54, 0x86, 0xcc, 0xbe, 0x90, 0x42, 0xc4, 0x28, 0xb3, 0x20, 0x92, 0x5a, 0xcf, 0x8e, 0xef, 0xbf, 0xe9, 0x8d, 0x83, 0x8d, 0xc6, 0xdc, 0x4a, 0x9f, 0xaf, 0x32, 0xd7, 0xd8, 0x78, 0xf0, 0x8d, 0x9b, 0xf6, 0x03, 0xb0, 0x5e, 0xa8, 0x75, 0x64, 0x99, 0xfa, 0xd6, 0x6d, 0x2b, 0x5d, 0x7b, 0xa4, 0xd3, 0x1e, 0x68, 0x5c, 0x56, 0x01, 0x78, 0xec, 0x17, 0x48, 0x37, 0x67, 0xef, 0x3c, 0xb2, 0xb6, 0x74, 0xdb, 0xba, 0x3e, 0x53, 0x96, 0x43, 0x2b, 0x44, 0xf6, 0xe2, 0x2e, 0x7a, 0x0b, 0x59, 0xc9, 0xfc, 0x82, 0xe0, 0x11, 0x51, 0xae, 0x34, 0xa4, 0x24, 0xe3, 0x7e, 0x1f, 0xc1, 0x56, 0x72, 0xec, 0x77, 0xd5, 0x8d, 0x55, 0x43, 0xd8, 0x35, 0xb4, 0xa5, 0x27, 0x89, 0xbc, 0x1a, 0x79, 0x04, 0xcf, 0x41, 0x21, 0x7d, 0x05, 0x72, 0x76, 0xe6, 0x53, 0xa0, 0xab, 0x6a, 0x93, 0x45, 0x2b, 0x5e, 0x43, 0x6f, 0x01, 0x8b, 0x3c, 0x36, 0xf6, 0x7f, 0x30, 0x1e, 0x99, 0x4b, 0xef, 0xa4, 0xac, 0xcc, 0xaf, 0x61, 0xdb, 0x2c, 0x28, 0x4a, 0xac, 0x4f, 0x27, 0xa9, 0x46, 0x59, 0x8d, 0xbb, 0x19, 0xb4, 0xc4, 0xd8, 0x8f, 0x2f, 0x6d, 0xa4, 0x84, 0x5d, 0xa7, 0xf8, 0x31, 0x2d, 0xec, 0x1e, 0x5d, 0x2f, 0x44, 0x7c, 0x15, 0x87, 0x3d, 0x43, 0x24, 0x96, 0x55, 0xaf, 0xfd, 0x28, 0x3c, 0x26, 0x2a, 0x82, 0x08, 0x40, 0x56, 0x40, 0xab, 0x96, 0x8a, 0xf1, 0xf0, 0xf8, 0x13, 0xc3, 0xf3, 0x70, 0x2c, 0xca, 0x35, 0xdd, 0xdc, 0x3a, 0x2a, 0xe1, 0xe8, 0x45, 0x6b, 0x41, 0x8f, 0xc2, 0xe4, 0xd1, 0x04, 0x8b, 0xe5, 0x26, 0xb7, 0x56, 0xe3, 0x31, 0x29, 0xc6, 0xc6, 0x15, 0x66, 0x8f, 0x46, 0xe3, 0x32, 0x29, 0x14, 0x26, 0x97, 0x46, 0xe3, 0x31, 0x2b, 0xe8, 0x9d, 0xdc, 0xfc, 0xf6, 0x6a, 0xdc, 0x26, 0x79, 0x71, 0x40, 0xeb, 0x36, 0x2b, 0x14, 0x66, 0xd8, 0x18, 0x5e, 0xc1, 0x87, 0x34, 0xa9, 0xeb, 0xf1, 0x71, 0xa2, 0x83, 0xfa, 0x16, 0x58, 0xc8, 0xfc, 0x99, 0xe2, 0x13, 0x7f, 0x87, 0x34, 0xf2, 0x6e, 0x2e, 0xee, 0x86, 0xbe, 0x3b, 0x15, 0x77, 0x83, 0xf4, 0x95, 0x01, 0xb0, 0x10, 0xcc, 0x81, 0x7c, 0x8e, 0x2e, 0xa9, 0x46, 0x55, 0x2a, 0xeb, 0xb1, 0x17, 0x2b, 0xe7, 0x6e, 0xca, 0x69, 0xde, 0x48, 0xbe, 0x2f, 0x07, 0xc4, 0x2b, 0x48, 0xaf, 0x0f, 0x11, 0x4c, 0x90, 0xb0, 0x36, 0x8e, 0x94, 0x2b, 0x29, 0x81, 0x29, 0xa0, 0x16, 0x66, 0x19, 0xcc, 0x60, 0xce, 0x31, 0xf6, 0xf7, 0x9b, 0x7c, 0x62, 0xbb, 0xc4, 0x53, 0x3f, 0xab, 0x25, 0x34, 0xac, 0xb7, 0x62, 0x7a, 0x84, 0xec, 0xf9, 0x47, 0xe0, 0x19, 0x94, 0x70, 0x55, 0x88, 0xf8, 0x20, 0x6d, 0xab, 0x60, 0x18, 0x2c, 0x4f, 0x19, 0x52, 0x24, 0x09, 0x1e, 0x49, 0x23, 0xd5, 0x2c, 0x93, 0x02, 0xc2, 0x6c, 0x32, 0x1a, 0xb2, 0x74, 0x52, 0x8b, 0x0c, 0x85, 0x82, 0x4b, 0x80, 0x44, 0xc0, 0x9b, 0x50, 0xfe, 0x42, 0xda, 0x9d, 0x09, 0xf3, 0x71, 0xe0, 0x64, 0x47, 0xe4, 0xed, 0xed, 0xa3, 0x0d, 0x0e, 0x47, 0xfd, 0x68, 0x3b, 0xfb, 0x4f, 0x20, 0xd9, 0xfb, 0xda, 0xd6, 0xd2, 0x9a, 0xdd, 0xdf, 0xda, 0xc4, 0xda, 0xa9, 0x33, 0x63, 0x81, 0x27, 0xdc, 0x95, 0xf3, 0x63, 0xc5, 0xf3, 0x2b, 0x1c, 0xac, 0x81, 0x3c, 0x5a, 0xba, 0xfc, 0x48, 0xd7, 0xc0, 0x83, 0xa3, 0xe5, 0xcc, 0xa9, 0xd3, 0x6c, 0x0d, 0x91, 0xf1, 0x35, 0xd0, 0x4f, 0xf7, 0x35, 0x30, 0x20, 0x8c, 0x60, 0xe4, 0x7c, 0x0d, 0x78, 0x19, 0x5f, 0x03, 0xfd, 0xd8, 0xc8, 0x7b, 0xd4, 0x50, 0xc6, 0xd7, 0x20, 0xf5, 0x2c, 0xef, 0xbf, 0x09, 0x3e, 0xf2, 0x34, 0x40, 0x19, 0xf7, 0x47, 0xaf, 0xee, 0x40, 0xa9, 0xa0, 0x79, 0x59, 0xa9, 0x1e, 0x94, 0x11, 0xdc, 0x07, 0xf9, 0xc6, 0x23, 0x5c, 0x27, 0xa0, 0x9a, 0xd3, 0xa5, 0xcb, 0xd8, 0x47, 0x79, 0x04, 0xb6, 0x5d, 0xdb, 0x88, 0xda, 0x24, 0xe2, 0x39, 0x71, 0xee, 0x2a, 0x43, 0x93, 0x90, 0xcf, 0x08, 0xe8, 0x94, 0x2d, 0xdb, 0x98, 0xbe, 0x22, 0x7b, 0x11, 0x08, 0x5a, 0xc8, 0x66, 0x81, 0x80, 0x20, 0x04, 0x36, 0x81, 0xcd, 0x6a, 0xd1, 0x69, 0x35, 0x2a, 0xb9, 0x0c, 0x82, 0x18, 0x9f, 0xe0, 0x2b, 0x45, 0x3c, 0x6d, 0xc8, 0xa3, 0x74, 0x60, 0xb9, 0x18, 0xb2, 0x39, 0x90, 0xcf, 0x54, 0xa6, 0x3e, 0x22, 0x15, 0x94, 0x11, 0xc4, 0x1d, 0x4a, 0xba, 0x8a, 0x2d, 0x91, 0x84, 0x4a, 0x1b, 0x02, 0xf4, 0x32, 0xaf, 0x75, 0xfb, 0xe5, 0xbb, 0xe0, 0x9f, 0x7b, 0xac, 0x65, 0x45, 0xf9, 0x7e, 0x8b, 0x80, 0x5a, 0x7a, 0x9a, 0x6e, 0xfd, 0x88, 0x3d, 0xc1, 0xfe, 0xeb, 0x5c, 0x29, 0x5b, 0x66, 0x76, 0x82, 0x5e, 0xf6, 0x77, 0x66, 0x27, 0x49, 0xca, 0xab, 0xb7, 0xbc, 0x0a, 0x94, 0x60, 0xf5, 0xdf, 0x2e, 0x8b, 0xb9, 0x75, 0x83, 0xf8, 0x5f, 0x60, 0x85, 0x63, 0x2e, 0x46, 0x35, 0x59, 0x62, 0x06, 0x12, 0xf0, 0x40, 0x23, 0x8e, 0xf3, 0x05, 0xf0, 0x9c, 0x0c, 0x08, 0xf9, 0x24, 0xc3, 0x18, 0x9a, 0x20, 0x76, 0x25, 0x08, 0x23, 0xd1, 0x5c, 0x90, 0xe7, 0x40, 0xe1, 0xde, 0xf0, 0x8f, 0x5d, 0xc4, 0xb3, 0x64, 0x3c, 0x0b, 0x70, 0xa9, 0x13, 0xa4, 0xeb, 0xe4, 0xdc, 0x0b, 0x2a, 0x50, 0xb5, 0x01, 0x5f, 0x3a, 0x59, 0x43, 0x29, 0x50, 0x4e, 0x2e, 0x68, 0x0a, 0xa9, 0x0a, 0x94, 0xee, 0x05, 0xd6, 0xdb, 0x72, 0xab, 0x35, 0xec, 0x63, 0xe6, 0x9a, 0xc2, 0x87, 0xdf, 0x79, 0xa8, 0xb0, 0xd6, 0xc4, 0x3e, 0xa6, 0xa9, 0xce, 0x39, 0x0c, 0xee, 0xb3, 0xb7, 0x44, 0x42, 0x65, 0x16, 0x76, 0xe5, 0x63, 0x8e, 0x62, 0x25, 0xe8, 0xb6, 0x55, 0x87, 0x0f, 0xbd, 0x73, 0x4b, 0xb8, 0xda, 0x06, 0xba, 0x95, 0xc5, 0xf6, 0xd3, 0xec, 0x4a, 0x4b, 0x59, 0xa8, 0xa0, 0xd5, 0x1e, 0x70, 0x81, 0x8f, 0x83, 0x9e, 0xb1, 0x1b, 0xc9, 0x5b, 0x3c, 0x41, 0x56, 0xe1, 0x0a, 0x90, 0x2b, 0xe3, 0x95, 0x0a, 0x79, 0x51, 0x90, 0x2c, 0x76, 0x58, 0x58, 0x43, 0xd8, 0x4c, 0xae, 0x1e, 0xbb, 0xd3, 0x1c, 0x06, 0x7f, 0xb4, 0x38, 0xc6, 0xde, 0x0a, 0x16, 0xc9, 0x95, 0xc9, 0x38, 0x37, 0x57, 0x2f, 0xe4, 0xff, 0x5d, 0xcc, 0x53, 0x44, 0x05, 0xd2, 0xe3, 0x8b, 0x01, 0xcd, 0x64, 0xc1, 0x83, 0x9e, 0xb6, 0x00, 0x67, 0x67, 0x82, 0xf7, 0xd3, 0x89, 0x2d, 0x19, 0x88, 0xa4, 0x91, 0xca, 0x32, 0xa5, 0x51, 0xb1, 0x67, 0x52, 0xa2, 0x38, 0x00, 0xd6, 0xe3, 0x5f, 0x77, 0x7b, 0x17, 0x48, 0xeb, 0xf1, 0xb3, 0x43, 0x5c, 0xed, 0x00, 0x9d, 0xc6, 0x29, 0x80, 0xdb, 0xab, 0xce, 0x58, 0xa3, 0x32, 0x36, 0xba, 0x69, 0xf1, 0x3e, 0x24, 0xdf, 0x15, 0x4d, 0xe3, 0xf4, 0x74, 0x1e, 0x80, 0xbe, 0x8e, 0xff, 0x3a, 0x38, 0xf2, 0xc2, 0xb6, 0x9a, 0x65, 0x8b, 0x63, 0xf3, 0x12, 0xae, 0xba, 0xfd, 0x17, 0xb7, 0x9d, 0xbc, 0x32, 0xaf, 0x5c, 0x60, 0x94, 0xeb, 0xca, 0xe6, 0x6f, 0xef, 0x3d, 0xf1, 0x78, 0x68, 0xf6, 0x68, 0xc3, 0x77, 0x5e, 0x41, 0x18, 0xd9, 0x37, 0x6b, 0x55, 0x4d, 0xdb, 0xda, 0xd6, 0x88, 0x4a, 0xa9, 0x13, 0x31, 0xba, 0x86, 0x96, 0xf8, 0x92, 0x03, 0xed, 0x2b, 0x1f, 0xce, 0xcb, 0xaa, 0xea, 0xec, 0x8f, 0x2d, 0x7a, 0x74, 0x4b, 0xfd, 0xd0, 0xdc, 0x21, 0x89, 0x22, 0x1c, 0x0d, 0xdf, 0xb2, 0x23, 0x36, 0x3f, 0xe9, 0x02, 0xcf, 0x58, 0xe2, 0xed, 0x85, 0xb9, 0xad, 0x71, 0x9b, 0x23, 0xe0, 0x50, 0x28, 0x89, 0x29, 0xfe, 0x18, 0x26, 0xe4, 0x6d, 0xa7, 0xe1, 0x21, 0x53, 0x4f, 0x23, 0xb6, 0x58, 0x19, 0xb0, 0x9b, 0x89, 0x11, 0x93, 0x2b, 0x13, 0x61, 0x54, 0x06, 0x95, 0x0c, 0x3a, 0x62, 0x48, 0xdd, 0x1d, 0x8b, 0xa7, 0x5d, 0x4d, 0x10, 0x51, 0xe2, 0xf1, 0x21, 0x40, 0x20, 0x9f, 0x73, 0x66, 0xd6, 0x25, 0x5a, 0xa0, 0xf6, 0x6b, 0xc6, 0xb6, 0xc9, 0xf3, 0x6d, 0xf7, 0xbf, 0xc7, 0xf0, 0x55, 0x7e, 0x0d, 0xb9, 0x5b, 0x9e, 0x67, 0x63, 0x4e, 0x7d, 0xb1, 0x94, 0xfa, 0x20, 0x7b, 0x20, 0x96, 0xd7, 0x61, 0xbf, 0x52, 0x6d, 0x08, 0x32, 0xa7, 0xae, 0xb8, 0xb3, 0x07, 0xe2, 0x79, 0x9d, 0x76, 0xea, 0x9b, 0x86, 0xe0, 0xc4, 0x38, 0x2a, 0xe0, 0x38, 0xec, 0x28, 0xf7, 0x8a, 0x65, 0x4a, 0xfc, 0x26, 0xc2, 0xe1, 0x06, 0xec, 0x21, 0x02, 0x47, 0x03, 0x81, 0x53, 0xe9, 0xb3, 0xf3, 0x78, 0xc6, 0x34, 0x74, 0x52, 0x78, 0x4c, 0x57, 0x83, 0x20, 0x53, 0xf1, 0x6d, 0x7b, 0x48, 0xcd, 0x1e, 0x93, 0x9a, 0xec, 0x3e, 0xe3, 0x13, 0x97, 0x2e, 0xdd, 0x1e, 0x4e, 0x58, 0xc0, 0x0a, 0x45, 0xb6, 0xfd, 0x05, 0xb6, 0xc7, 0x90, 0xf4, 0xc6, 0x9a, 0x5c, 0x4e, 0x3b, 0x49, 0x6a, 0x4c, 0x4a, 0xfe, 0xd8, 0x8d, 0xcc, 0xa9, 0xb1, 0x3b, 0x4d, 0x01, 0xf0, 0x37, 0xa3, 0x63, 0xec, 0x9c, 0x3b, 0x57, 0x26, 0xab, 0x2c, 0xc6, 0xb5, 0x93, 0xbe, 0x18, 0xff, 0x84, 0xa1, 0x70, 0x3c, 0x4a, 0x6d, 0xd3, 0x59, 0x27, 0x8e, 0xed, 0x85, 0x03, 0xe3, 0x30, 0x21, 0x91, 0x76, 0x14, 0x30, 0x12, 0xb8, 0x3c, 0xce, 0xc4, 0x0d, 0xf8, 0x13, 0xba, 0x8b, 0x99, 0x78, 0x17, 0x94, 0x68, 0xce, 0xeb, 0x9c, 0x5e, 0xa5, 0x82, 0xe1, 0x19, 0xb0, 0x57, 0x8b, 0x88, 0x44, 0x9b, 0x8c, 0x05, 0x77, 0xd2, 0x08, 0xd4, 0x0e, 0x86, 0x7a, 0x8f, 0x2d, 0x95, 0xe5, 0xd7, 0xcc, 0x5d, 0xb6, 0xa1, 0x6a, 0x43, 0xbd, 0xb7, 0xa5, 0xbe, 0xb2, 0xd0, 0x23, 0xa2, 0x96, 0xd2, 0x9f, 0xb1, 0x7f, 0x65, 0x87, 0xd9, 0xdf, 0xbd, 0x30, 0xe4, 0x09, 0xee, 0xb7, 0xf4, 0x3e, 0x05, 0x4c, 0xe0, 0x14, 0x40, 0xb1, 0xbd, 0x70, 0x4c, 0x02, 0x09, 0xc6, 0xd1, 0x39, 0xc4, 0x06, 0x6e, 0x54, 0x0e, 0x61, 0x06, 0x51, 0xd3, 0x34, 0x4e, 0x4b, 0x0d, 0x11, 0xb5, 0x80, 0x4c, 0x0b, 0x0f, 0x46, 0x8a, 0x8b, 0x2b, 0xbf, 0x56, 0x1b, 0x8c, 0xb3, 0xf0, 0x77, 0x6c, 0x62, 0x70, 0x31, 0x10, 0x98, 0x4d, 0x08, 0xb9, 0x67, 0x87, 0xbd, 0x6e, 0xa7, 0x1d, 0x23, 0xf8, 0x1c, 0x59, 0x0e, 0x87, 0xe0, 0x45, 0xd7, 0x46, 0xf0, 0xd3, 0x67, 0xa6, 0xe4, 0xc9, 0x66, 0x44, 0xf7, 0xb6, 0x99, 0xe6, 0x3b, 0x23, 0xfa, 0x9f, 0x79, 0x11, 0xc6, 0xee, 0x4b, 0xd1, 0x2a, 0x41, 0x27, 0x5e, 0x07, 0x17, 0x31, 0xcc, 0x91, 0x7e, 0x2b, 0x5f, 0x44, 0x92, 0x34, 0xc0, 0xb3, 0x84, 0x28, 0x0e, 0xe1, 0x65, 0x44, 0xae, 0x16, 0x64, 0x16, 0xc1, 0x25, 0x42, 0x95, 0x51, 0xc9, 0x6e, 0x82, 0x8f, 0x5a, 0xf1, 0x67, 0x6c, 0xd5, 0x93, 0xb4, 0xa0, 0xb9, 0x3b, 0x1d, 0x28, 0xd3, 0x58, 0x8a, 0xbc, 0xb9, 0x64, 0x2e, 0x6e, 0xf6, 0xe2, 0x6b, 0xcf, 0x1e, 0xc3, 0x20, 0xa4, 0x1c, 0x4a, 0xde, 0x1c, 0x3c, 0xeb, 0x86, 0xd4, 0xac, 0x5f, 0x4f, 0xcf, 0x7a, 0x4c, 0xfd, 0xde, 0x7b, 0xe4, 0x5f, 0x2f, 0xcd, 0x38, 0x51, 0x24, 0x22, 0x42, 0x18, 0xdc, 0x47, 0x6e, 0x86, 0x93, 0x9b, 0xf0, 0x57, 0x43, 0x32, 0x54, 0xe0, 0x1a, 0x36, 0x21, 0x7b, 0xc6, 0x26, 0xe4, 0x60, 0x9a, 0xf5, 0x3a, 0x31, 0x44, 0xf7, 0x0e, 0x9b, 0x2e, 0xa0, 0x0f, 0x88, 0xb5, 0x62, 0xad, 0x52, 0x8e, 0xcb, 0x96, 0x08, 0x21, 0x0a, 0x4a, 0x59, 0xcd, 0xe3, 0x3e, 0xce, 0x29, 0x83, 0x0f, 0x90, 0x03, 0x9b, 0x76, 0xaa, 0x53, 0x1b, 0x88, 0x0a, 0x5b, 0x76, 0x7c, 0x7d, 0xc5, 0x8a, 0xaf, 0x6f, 0x6f, 0x11, 0x92, 0x92, 0xb6, 0x6d, 0x4f, 0x2e, 0x1b, 0x7a, 0x72, 0xfb, 0x6c, 0x09, 0xf8, 0x0b, 0xf6, 0x60, 0x9b, 0xea, 0xd5, 0x46, 0x1e, 0xba, 0xe1, 0xf9, 0x9b, 0x2b, 0x2b, 0x6f, 0x7e, 0xfe, 0x86, 0x07, 0x57, 0x3d, 0xb9, 0xa1, 0x02, 0x69, 0x69, 0xc8, 0x9d, 0xd8, 0x81, 0x6d, 0x9a, 0x53, 0x5b, 0xea, 0x4c, 0xab, 0xa6, 0xfb, 0x7a, 0x65, 0x4e, 0x72, 0x9a, 0xe6, 0x66, 0x7c, 0xbd, 0x54, 0x97, 0xc6, 0x1a, 0xdf, 0xa3, 0xfb, 0x27, 0x7c, 0xbd, 0x48, 0x88, 0xcf, 0x3f, 0x81, 0x5f, 0x6d, 0x44, 0xdd, 0xb9, 0x2c, 0x05, 0xc9, 0x15, 0x75, 0x42, 0xb8, 0x5c, 0x95, 0xc2, 0x50, 0xe9, 0xc4, 0xbc, 0x0e, 0x74, 0x06, 0xa7, 0xff, 0xe8, 0x42, 0xd6, 0x55, 0x09, 0x2e, 0x24, 0x6d, 0xf5, 0x3a, 0x3d, 0x08, 0x7b, 0x09, 0x71, 0x6a, 0x0a, 0x0e, 0x44, 0x13, 0x5c, 0xb6, 0x26, 0x80, 0x2c, 0x7a, 0x18, 0x7d, 0xd1, 0x9f, 0x8c, 0xbd, 0x64, 0xae, 0x6c, 0x9a, 0x9b, 0xbf, 0xa1, 0x21, 0x2b, 0x3f, 0x27, 0xa0, 0xaa, 0x1e, 0xfb, 0xf6, 0x5b, 0x66, 0x87, 0x04, 0xb4, 0xf9, 0xf5, 0x62, 0xab, 0x0a, 0xd8, 0x04, 0x1e, 0x13, 0x73, 0xea, 0xb2, 0xb3, 0xac, 0x2d, 0x47, 0x1d, 0xdc, 0x2f, 0x32, 0xe5, 0xb8, 0x0f, 0x51, 0x9f, 0x9b, 0x74, 0x63, 0xf7, 0x34, 0xb5, 0xfa, 0x5a, 0xbc, 0xe4, 0x0a, 0x95, 0x0d, 0xe3, 0x8c, 0x71, 0x38, 0xe7, 0x65, 0xcc, 0xc3, 0x84, 0x01, 0xe9, 0x19, 0x74, 0x58, 0x37, 0x8d, 0x07, 0x84, 0x44, 0x59, 0x03, 0x12, 0xd6, 0x8c, 0x38, 0x86, 0xdb, 0x89, 0xb9, 0x0d, 0x1c, 0xb9, 0x6d, 0x01, 0x98, 0xb4, 0xba, 0x26, 0x67, 0x3f, 0xa2, 0xfe, 0x69, 0x3f, 0xb0, 0xac, 0x38, 0x14, 0xcb, 0xae, 0x52, 0xb3, 0xdd, 0xdf, 0x1e, 0xfb, 0xc6, 0x93, 0x66, 0x97, 0x10, 0xec, 0xe1, 0xdc, 0xf9, 0xe8, 0x0f, 0x86, 0xfa, 0xd5, 0xba, 0xbd, 0x85, 0xb1, 0xcb, 0x10, 0x6f, 0x99, 0x75, 0x57, 0xf2, 0x38, 0x97, 0x3e, 0x08, 0x37, 0xb3, 0xe0, 0xbb, 0xff, 0x9b, 0xf9, 0x20, 0x25, 0x7b, 0xe7, 0x25, 0xb3, 0xa7, 0x78, 0x96, 0x41, 0x31, 0x1a, 0x72, 0x2f, 0x03, 0x50, 0xc8, 0xc6, 0x5c, 0x8b, 0x91, 0xd7, 0x3c, 0x59, 0x92, 0xe6, 0xa1, 0xf2, 0x91, 0xd7, 0xe1, 0x5b, 0xb6, 0xa9, 0xf7, 0xae, 0x15, 0x25, 0x25, 0x2b, 0xee, 0xea, 0xfd, 0x2e, 0xfc, 0x52, 0x5c, 0x8c, 0xbe, 0x1c, 0xfc, 0xed, 0x43, 0x3d, 0x3d, 0x0f, 0xfd, 0xf6, 0x20, 0xfe, 0x32, 0xff, 0xe1, 0xdf, 0xee, 0xff, 0x2e, 0xe8, 0x02, 0x55, 0x23, 0x77, 0xcf, 0xe9, 0xba, 0x7b, 0xa4, 0x8a, 0x24, 0xe1, 0xb7, 0xae, 0x39, 0xf0, 0x1b, 0xa0, 0x7e, 0x4d, 0xf5, 0x9d, 0xfa, 0xd1, 0x86, 0x0d, 0x3f, 0xba, 0xaf, 0x8f, 0x7a, 0xfa, 0x69, 0xaa, 0xef, 0xbe, 0x1f, 0x6d, 0xb8, 0x11, 0x7d, 0x7f, 0x1c, 0xe3, 0x7f, 0xf6, 0x51, 0xe6, 0xcf, 0x70, 0xaf, 0x65, 0xc8, 0x3f, 0x13, 0x31, 0x4e, 0x06, 0xce, 0x1b, 0x0b, 0xbb, 0x82, 0x1a, 0x9b, 0x70, 0x55, 0x36, 0x44, 0x8a, 0x64, 0x84, 0x0c, 0xa5, 0x09, 0x76, 0xf1, 0x21, 0x3e, 0xf5, 0xa4, 0xb9, 0x26, 0xee, 0xe4, 0x31, 0x7f, 0xf6, 0x5a, 0xb7, 0x5d, 0x3e, 0xf8, 0x1e, 0x79, 0xee, 0xd2, 0x69, 0x8e, 0x27, 0x82, 0xfb, 0x25, 0x46, 0xe0, 0x94, 0xea, 0x5f, 0x95, 0xee, 0x1f, 0xf7, 0x6c, 0xc0, 0xc6, 0x97, 0xf9, 0x18, 0x1a, 0x11, 0xd0, 0x58, 0xc0, 0x44, 0xff, 0x2e, 0x25, 0xea, 0x3f, 0xc3, 0x90, 0x71, 0x6f, 0x60, 0x54, 0x6c, 0x3e, 0x64, 0xc1, 0xd8, 0x7c, 0xf8, 0x06, 0x44, 0xd2, 0x10, 0xc7, 0x95, 0x7e, 0x07, 0xa6, 0x61, 0xf3, 0x20, 0x9f, 0xb9, 0x03, 0xbe, 0x43, 0x8d, 0xec, 0x12, 0x2a, 0x8a, 0xe4, 0xd4, 0x39, 0x9c, 0xf7, 0x44, 0x1a, 0x38, 0xd3, 0x24, 0x55, 0x4d, 0xa8, 0xb5, 0xea, 0x0c, 0x49, 0x95, 0x01, 0xec, 0x1c, 0x07, 0x25, 0x3c, 0x10, 0x8b, 0x2b, 0xe9, 0x1d, 0x97, 0xd6, 0xfd, 0xfc, 0x9b, 0xf7, 0x8d, 0x54, 0xbc, 0xd7, 0x78, 0xea, 0x1f, 0x8f, 0xa1, 0x57, 0x91, 0x4b, 0xac, 0x4e, 0x3a, 0x77, 0xe0, 0xbe, 0x1b, 0xc0, 0x7b, 0x6c, 0x60, 0xd1, 0x86, 0x7c, 0x75, 0x90, 0x7c, 0x20, 0x43, 0x37, 0xf9, 0xaf, 0xc2, 0x77, 0x8a, 0x91, 0x0e, 0x91, 0xc7, 0xc0, 0x05, 0x03, 0x06, 0xac, 0x53, 0x26, 0xe6, 0x67, 0xde, 0x25, 0x26, 0xc4, 0xb8, 0x4e, 0x68, 0x7a, 0x4e, 0x91, 0xd4, 0x3f, 0xfe, 0xab, 0x9f, 0x7f, 0xf6, 0x1e, 0x59, 0x7d, 0x89, 0xac, 0x47, 0xef, 0x48, 0x2f, 0x16, 0x9a, 0x0c, 0x5e, 0x2f, 0xc1, 0x5d, 0xd8, 0xf7, 0x14, 0xe5, 0x24, 0xa0, 0xf1, 0xe0, 0x39, 0x47, 0xbf, 0xe9, 0x3b, 0xa2, 0x22, 0x54, 0xb8, 0x77, 0xa5, 0x4b, 0xc8, 0x33, 0x4d, 0xdf, 0x13, 0xf4, 0x4f, 0x80, 0x18, 0xd7, 0xcf, 0x7f, 0xcd, 0xbd, 0xe9, 0xbd, 0x69, 0x5b, 0x83, 0xb7, 0x07, 0xe2, 0x73, 0x28, 0x48, 0x33, 0xf7, 0xcc, 0x24, 0x7b, 0xd0, 0x34, 0xd3, 0x43, 0x20, 0x26, 0xf5, 0xff, 0x83, 0xec, 0x11, 0x75, 0x28, 0xa9, 0xe3, 0x33, 0xcb, 0x1e, 0xec, 0x20, 0x39, 0x32, 0x33, 0xf1, 0xf9, 0xfb, 0xd3, 0x88, 0xd2, 0xa0, 0x3c, 0x08, 0x70, 0x8d, 0x79, 0x8b, 0xe0, 0xd8, 0xbc, 0x80, 0x8f, 0x6b, 0xe9, 0x29, 0xe9, 0x9f, 0x4f, 0x93, 0x97, 0x4c, 0x44, 0x41, 0x32, 0x97, 0x37, 0x69, 0xcc, 0x06, 0xce, 0x49, 0x66, 0xc1, 0xc4, 0x70, 0xf1, 0x60, 0x4d, 0x32, 0x13, 0x37, 0x58, 0xfe, 0x35, 0x07, 0x1b, 0xb9, 0xa6, 0x98, 0x74, 0xe9, 0x1a, 0x32, 0x12, 0x1c, 0x26, 0x20, 0x0a, 0xc7, 0x3f, 0xa6, 0x3f, 0x84, 0x34, 0x23, 0x8f, 0xa8, 0x4c, 0x56, 0x28, 0x50, 0x6e, 0x8e, 0xc6, 0x6b, 0x30, 0xb0, 0xfd, 0x33, 0x32, 0xbc, 0x6a, 0xb7, 0x52, 0xe3, 0x75, 0xfb, 0x21, 0xdb, 0x9a, 0xca, 0x2b, 0xc1, 0x91, 0x09, 0xec, 0xc1, 0x8e, 0x5d, 0xee, 0x75, 0xfa, 0x14, 0x25, 0x81, 0xab, 0xc9, 0xd1, 0x12, 0x74, 0xa7, 0xb1, 0xfb, 0x6f, 0x27, 0x4f, 0x01, 0xea, 0xe5, 0x65, 0xfd, 0x2f, 0x8c, 0x9d, 0x7a, 0x6e, 0x6c, 0x5e, 0x97, 0x54, 0x25, 0x52, 0x09, 0xdc, 0x8d, 0x5d, 0x03, 0x15, 0x7d, 0x8f, 0xed, 0x68, 0x17, 0x8d, 0x95, 0x90, 0x17, 0x25, 0xed, 0xdb, 0x1e, 0xed, 0xef, 0xdd, 0xdc, 0x19, 0x51, 0x2b, 0x35, 0x7c, 0xfa, 0xb3, 0xce, 0xa5, 0xc3, 0x6f, 0xb0, 0x9f, 0x3c, 0xf4, 0x10, 0xfb, 0xc9, 0x1b, 0xc3, 0xa3, 0xcb, 0x17, 0xc8, 0x84, 0x7c, 0x47, 0xae, 0x55, 0x0e, 0x29, 0xca, 0xba, 0x07, 0x87, 0x9f, 0xbc, 0xb1, 0xc2, 0x9b, 0xeb, 0x95, 0xa5, 0x64, 0x97, 0xf9, 0xe3, 0x1f, 0x33, 0x72, 0x3c, 0xa7, 0x8e, 0x64, 0x9b, 0x1a, 0xe2, 0x32, 0x0a, 0x6e, 0x00, 0xd2, 0x08, 0xd1, 0x0c, 0x4d, 0x31, 0x23, 0x02, 0x28, 0xc2, 0xf0, 0x79, 0x04, 0x1f, 0x4d, 0x0c, 0x62, 0x36, 0x5e, 0x7f, 0x3a, 0xe6, 0xde, 0x8e, 0x12, 0x8b, 0xa7, 0x27, 0x06, 0x88, 0xa0, 0xdf, 0xed, 0x72, 0x3a, 0x2c, 0xa6, 0x2c, 0x9d, 0x12, 0xe5, 0xc6, 0xc9, 0x03, 0x79, 0x42, 0x1e, 0x8a, 0xf3, 0xe3, 0xfc, 0xf3, 0x39, 0xd7, 0x08, 0xe4, 0xd9, 0x3d, 0xc5, 0xc5, 0x86, 0x0f, 0x05, 0xd6, 0xd4, 0x84, 0x2b, 0x00, 0x7d, 0x4a, 0x25, 0xed, 0x9a, 0x37, 0xf6, 0xdc, 0xa9, 0xb1, 0xe7, 0x97, 0x2c, 0x7b, 0x19, 0x50, 0xf7, 0x9d, 0xf8, 0xdb, 0xbc, 0x3a, 0xbe, 0x56, 0xa9, 0x8e, 0x74, 0x6e, 0xee, 0xed, 0x7f, 0x74, 0x6b, 0xbb, 0x84, 0x7c, 0x63, 0xac, 0x4c, 0xd4, 0xbe, 0xfd, 0xb1, 0x45, 0x15, 0x03, 0x9d, 0x8d, 0x6e, 0x81, 0x0a, 0x14, 0xca, 0x16, 0x2c, 0x1f, 0x1d, 0x7e, 0x03, 0xc8, 0x1e, 0x7a, 0x08, 0xc8, 0xde, 0x18, 0x5e, 0xda, 0x39, 0x20, 0x96, 0xc1, 0xa9, 0x55, 0xdc, 0xf8, 0xe4, 0xf0, 0x83, 0xeb, 0x5e, 0xd8, 0x52, 0x29, 0xb7, 0xe6, 0x3a, 0xf8, 0x42, 0x4e, 0xae, 0x7c, 0x76, 0xfc, 0x13, 0xfe, 0xfb, 0x70, 0x9e, 0x6a, 0xc8, 0xd3, 0x7d, 0x8f, 0xa3, 0x70, 0x90, 0xa7, 0xe3, 0x43, 0xfe, 0x97, 0x0f, 0x77, 0x0b, 0x49, 0x92, 0xa8, 0xfa, 0xaa, 0xbd, 0x49, 0x90, 0x51, 0x08, 0x3b, 0xa8, 0x74, 0xe2, 0xd6, 0xaf, 0x68, 0xe8, 0xc2, 0x0d, 0x11, 0x97, 0xe8, 0x99, 0x68, 0x88, 0x98, 0x9e, 0xb4, 0x9a, 0x79, 0x7a, 0xe3, 0x64, 0xe0, 0x5a, 0xed, 0xe0, 0x21, 0x34, 0xa6, 0x1a, 0x13, 0xb8, 0x2d, 0xae, 0x71, 0xa2, 0xd1, 0x00, 0x42, 0x93, 0xa3, 0xc9, 0xc9, 0x0e, 0x87, 0x82, 0x01, 0x3f, 0xca, 0x0e, 0xc5, 0x65, 0xf9, 0x98, 0xc2, 0x28, 0xa6, 0x0c, 0x16, 0x5c, 0x75, 0xc1, 0x09, 0xa7, 0x14, 0xd2, 0xc7, 0xb1, 0x4d, 0x4c, 0xd5, 0xd0, 0xa3, 0xeb, 0x2a, 0x2a, 0xd6, 0x3d, 0x3a, 0x94, 0xfa, 0x1c, 0xfb, 0x99, 0x64, 0xd9, 0x53, 0x7f, 0xbc, 0xed, 0x9e, 0xcf, 0xce, 0xf4, 0xf5, 0x9d, 0xf9, 0xec, 0x9e, 0x5b, 0xff, 0xf4, 0xd4, 0x32, 0x09, 0xfb, 0xfe, 0x7b, 0xef, 0x3d, 0xd2, 0x72, 0xe4, 0xdd, 0x9d, 0x3b, 0x7e, 0x70, 0xb4, 0xb5, 0xf5, 0xe8, 0x0f, 0x76, 0xec, 0x7c, 0xf7, 0x48, 0x0b, 0xb9, 0xf5, 0xe8, 0xdf, 0x9f, 0x5e, 0x94, 0x66, 0x3f, 0xde, 0x18, 0xee, 0x7b, 0xea, 0xef, 0x47, 0xc7, 0xf6, 0xc1, 0x23, 0xb1, 0x17, 0xd5, 0x68, 0xfd, 0x02, 0x9e, 0xcf, 0x03, 0xf8, 0x7c, 0xfa, 0x89, 0x68, 0xb2, 0xc0, 0x02, 0x20, 0x72, 0x6c, 0xc4, 0x8c, 0x14, 0x09, 0xa8, 0x4c, 0x9a, 0x2b, 0x0e, 0x5d, 0x1a, 0x51, 0xe5, 0x4f, 0xbf, 0xd7, 0x98, 0xa5, 0x84, 0x02, 0x3a, 0x7c, 0x42, 0xec, 0xe0, 0x23, 0xee, 0x49, 0xe9, 0x48, 0x7b, 0xb2, 0x02, 0x24, 0x02, 0x23, 0x06, 0x0a, 0xcb, 0xc0, 0x19, 0xc7, 0x2c, 0x07, 0xf5, 0x3c, 0xfb, 0xf7, 0xf4, 0x79, 0x05, 0x7f, 0xbc, 0x74, 0xde, 0x98, 0x13, 0x0e, 0xea, 0xd8, 0xf7, 0xa1, 0xb8, 0xfb, 0x00, 0x58, 0x51, 0xda, 0xd5, 0xb5, 0xad, 0x33, 0x50, 0xb3, 0xfb, 0x9b, 0x9b, 0xc1, 0x1f, 0x28, 0x09, 0xf8, 0x93, 0x2b, 0xd9, 0x13, 0x2b, 0x5e, 0x90, 0x70, 0x5e, 0xf9, 0x27, 0xb9, 0x54, 0xa8, 0x0f, 0xd8, 0xd9, 0x76, 0x28, 0xdd, 0xf2, 0xe7, 0x7b, 0x55, 0xb5, 0xfd, 0x9b, 0xeb, 0x17, 0x1c, 0x1f, 0x2e, 0xc3, 0x70, 0x6f, 0x82, 0x23, 0x7b, 0x04, 0x8e, 0xdb, 0x81, 0x72, 0x4d, 0xe0, 0x15, 0x47, 0xd1, 0x5a, 0x48, 0x0d, 0x9e, 0xd2, 0x37, 0x60, 0xe3, 0x2f, 0xca, 0xd6, 0x60, 0x44, 0xf0, 0x3d, 0xc1, 0xa9, 0xca, 0x24, 0x70, 0xf1, 0x1d, 0xc0, 0xc1, 0xc1, 0x37, 0x87, 0x52, 0x32, 0x7e, 0xa3, 0x18, 0xd1, 0xb8, 0x10, 0x32, 0x44, 0xd9, 0xdf, 0x4f, 0xa7, 0xf1, 0x4a, 0x5f, 0x5f, 0x06, 0xdb, 0xfc, 0xec, 0x12, 0x7b, 0x23, 0x6d, 0x61, 0xab, 0xa9, 0xe2, 0x34, 0x72, 0xc9, 0x60, 0x9c, 0xb1, 0x11, 0xfa, 0xe3, 0x33, 0x67, 0xd8, 0x59, 0xdc, 0xf8, 0xc6, 0xc7, 0x99, 0x83, 0xe9, 0x9c, 0x0d, 0x72, 0x44, 0xde, 0x50, 0xe0, 0x33, 0x85, 0xfc, 0x40, 0x47, 0x10, 0x1c, 0xa5, 0x24, 0x21, 0x12, 0xa4, 0x75, 0x2b, 0x7a, 0xad, 0x0a, 0x49, 0x6c, 0x90, 0xdf, 0x81, 0x2c, 0x05, 0x92, 0x20, 0x73, 0x00, 0x76, 0x9c, 0xc2, 0x63, 0x21, 0xb7, 0xdc, 0xf0, 0xee, 0xd3, 0xb7, 0x2e, 0x89, 0x5e, 0x2a, 0xd9, 0xf7, 0x8b, 0xfb, 0xd3, 0x23, 0x28, 0xb0, 0x38, 0x98, 0xdc, 0xa5, 0xf7, 0x8d, 0xb0, 0x85, 0xe0, 0xdd, 0xbe, 0x9b, 0xf2, 0x95, 0xc1, 0xb1, 0x15, 0x68, 0x00, 0xdc, 0x79, 0x29, 0x84, 0xeb, 0xb3, 0x31, 0xc5, 0xfb, 0xd7, 0x24, 0x2b, 0xb9, 0x1a, 0xd9, 0x08, 0xb3, 0x89, 0x32, 0x18, 0x78, 0x82, 0xaf, 0x9f, 0xc0, 0xc0, 0xff, 0x31, 0x4f, 0x1f, 0x75, 0x70, 0xa5, 0xb3, 0xb5, 0xd4, 0xe6, 0x6b, 0x10, 0x8f, 0x93, 0x60, 0xe8, 0x12, 0x38, 0xcf, 0xce, 0x9a, 0x99, 0x82, 0x78, 0x1e, 0x78, 0xff, 0xfd, 0x07, 0x3e, 0xe2, 0xc6, 0x8c, 0xfc, 0x66, 0x6b, 0x98, 0xd3, 0x84, 0x94, 0xc8, 0x27, 0x76, 0x27, 0x45, 0x6a, 0xec, 0x2d, 0x91, 0x0d, 0xe8, 0x06, 0xee, 0xbc, 0x9b, 0x20, 0x6a, 0xa6, 0x47, 0xd3, 0x87, 0xcd, 0x34, 0xe1, 0x62, 0xc9, 0x9d, 0x74, 0xf3, 0x97, 0x35, 0xc1, 0xc7, 0xd6, 0xc0, 0xdd, 0x4d, 0xcd, 0x9f, 0x99, 0x72, 0x4c, 0x91, 0x8b, 0x66, 0x7e, 0x9e, 0xcb, 0x61, 0xd0, 0x43, 0xf4, 0x27, 0x05, 0x52, 0x1e, 0x9e, 0x72, 0x2c, 0xed, 0x4a, 0xac, 0xd1, 0xa9, 0x65, 0x14, 0x7f, 0x72, 0x09, 0xbe, 0xb4, 0xbb, 0x58, 0xdc, 0xa3, 0xd7, 0xf1, 0xf8, 0xe4, 0xbc, 0xdb, 0x6f, 0xbf, 0xed, 0xb6, 0xda, 0xd5, 0x2d, 0xfe, 0xa3, 0x3f, 0x1b, 0x38, 0xb2, 0x30, 0x0f, 0x6e, 0xed, 0xe2, 0xd1, 0xc4, 0xd0, 0x43, 0x37, 0x94, 0xd5, 0xed, 0x3c, 0xb7, 0x76, 0xc5, 0xf3, 0x7b, 0x9a, 0x4a, 0x76, 0xfe, 0xe8, 0x6e, 0xb0, 0x52, 0x4c, 0x9b, 0x1c, 0xbb, 0xcf, 0x3f, 0x7e, 0xfa, 0xa5, 0x50, 0xd7, 0x96, 0xd9, 0xcf, 0x5e, 0xa4, 0xe7, 0x08, 0x6a, 0x37, 0x3c, 0x3c, 0x94, 0xdd, 0x18, 0x35, 0x37, 0xec, 0x3a, 0x3f, 0x32, 0x7a, 0x7e, 0x57, 0x5d, 0xed, 0xd7, 0x00, 0xf1, 0xca, 0xfd, 0x40, 0xf2, 0xf6, 0x66, 0x89, 0x75, 0xc8, 0x91, 0x43, 0x22, 0x1b, 0xd8, 0xf8, 0xa7, 0xb4, 0x00, 0xf3, 0x4c, 0x15, 0xc4, 0xb1, 0x73, 0xb9, 0x22, 0x9c, 0xf4, 0x21, 0xe5, 0xf4, 0x84, 0x02, 0xc6, 0xf1, 0x2c, 0xd0, 0x11, 0x4d, 0x55, 0xc6, 0x74, 0x64, 0x2c, 0xf8, 0x33, 0xde, 0x76, 0x65, 0x2a, 0xe8, 0x5c, 0xf3, 0xe9, 0x6b, 0x3e, 0x88, 0xf0, 0x19, 0xae, 0x23, 0x5a, 0x52, 0xe4, 0xf7, 0x6a, 0x2a, 0xb4, 0xe5, 0xd8, 0x9d, 0x95, 0x77, 0x95, 0x3b, 0x2b, 0x35, 0xa5, 0x66, 0xd3, 0x14, 0x37, 0x4d, 0x07, 0xb2, 0xae, 0x90, 0x79, 0x65, 0x43, 0xb3, 0x82, 0x05, 0x5d, 0xc3, 0x25, 0x85, 0x7d, 0x75, 0x41, 0x47, 0xcd, 0x4a, 0xca, 0x17, 0xb5, 0xc9, 0x4a, 0x17, 0xdf, 0xbc, 0xe3, 0xe6, 0xc5, 0xa5, 0xf5, 0x3b, 0x9f, 0x1b, 0x1e, 0x7d, 0xe6, 0xa6, 0xf2, 0x27, 0x4f, 0xe6, 0x76, 0x25, 0xdc, 0x81, 0xae, 0xed, 0x73, 0xc1, 0xab, 0x0e, 0xcb, 0xc1, 0x60, 0xe7, 0xcd, 0x1d, 0xcd, 0xa3, 0xdd, 0x95, 0x16, 0x6d, 0x62, 0xde, 0x86, 0xe6, 0x9a, 0xcd, 0x0b, 0x62, 0x74, 0x0f, 0xca, 0x6f, 0xe5, 0x2a, 0x8a, 0x15, 0x55, 0x77, 0xad, 0x3d, 0xdc, 0x7b, 0xc3, 0x73, 0x37, 0x57, 0x35, 0x6c, 0x7f, 0x66, 0xd9, 0xc3, 0x3f, 0xaf, 0xc8, 0x8a, 0x56, 0xb5, 0x47, 0xf3, 0x17, 0xd4, 0x85, 0xb6, 0x5b, 0x2d, 0x58, 0x57, 0x8e, 0x6a, 0xdf, 0xde, 0x4d, 0x7d, 0x0e, 0xf9, 0x7e, 0x3d, 0x11, 0x46, 0x27, 0xc2, 0xc9, 0x59, 0x12, 0x53, 0xc0, 0x80, 0xc2, 0x77, 0x38, 0xa1, 0xd1, 0x84, 0x99, 0x28, 0x8c, 0xf5, 0x8c, 0x4c, 0xb3, 0x21, 0x4b, 0x2c, 0x06, 0x44, 0x28, 0x90, 0x15, 0x36, 0x84, 0xc5, 0x7a, 0x31, 0xc4, 0x1e, 0x42, 0x3e, 0x21, 0x02, 0x22, 0x61, 0x1a, 0x3c, 0x90, 0xd3, 0xb9, 0x21, 0x63, 0x72, 0x70, 0xc5, 0xd3, 0x26, 0xc6, 0x94, 0x3d, 0xdc, 0xa1, 0x24, 0xb5, 0x7d, 0x6b, 0x34, 0x79, 0xad, 0xc5, 0x3f, 0x7d, 0x25, 0x58, 0x1f, 0xb1, 0xa8, 0xb3, 0x1b, 0xe3, 0xbc, 0xbe, 0x03, 0xf3, 0x02, 0xaa, 0x70, 0x53, 0xc9, 0xc8, 0xd6, 0xf2, 0xe5, 0xb7, 0xb6, 0xb2, 0xeb, 0x0e, 0xf6, 0x34, 0x3b, 0x8a, 0x83, 0x7a, 0xf6, 0x35, 0xf2, 0xaf, 0xec, 0x0f, 0xb3, 0x42, 0xe5, 0x6e, 0x53, 0x81, 0x3f, 0x6b, 0x97, 0xa7, 0x6a, 0x5e, 0xc4, 0xd1, 0x50, 0x19, 0x93, 0x6b, 0x36, 0x0e, 0x34, 0xad, 0x6d, 0x74, 0xa3, 0x62, 0xc8, 0x5c, 0x0d, 0x5f, 0x17, 0xfc, 0x3a, 0x45, 0xc7, 0x6b, 0x9a, 0x49, 0xc7, 0x4b, 0xb9, 0xd8, 0xc0, 0x05, 0xf2, 0x24, 0xb5, 0xf4, 0xca, 0xa9, 0x54, 0x1d, 0x65, 0xfc, 0x2c, 0xfd, 0x09, 0xe4, 0xce, 0xea, 0x39, 0xcb, 0xb3, 0x9e, 0x46, 0xca, 0x3f, 0x6a, 0x68, 0x7a, 0x3f, 0xa6, 0xa4, 0x0e, 0xdd, 0x99, 0xd0, 0x01, 0xa7, 0x6f, 0x70, 0xc2, 0x22, 0xd2, 0xcc, 0x2a, 0x53, 0x7c, 0x39, 0x87, 0x2e, 0xf1, 0xbb, 0xc0, 0x87, 0xf4, 0x27, 0xec, 0x28, 0x7a, 0xdb, 0x17, 0xdc, 0x79, 0xd6, 0xb2, 0x8f, 0xd2, 0xe7, 0xe0, 0xbb, 0xa7, 0xe9, 0x82, 0x4d, 0xd7, 0xad, 0x0b, 0xd6, 0xcf, 0xa0, 0x0b, 0x86, 0xc7, 0xd0, 0x33, 0x55, 0x13, 0x5c, 0x50, 0x41, 0xa9, 0xe2, 0x10, 0x5d, 0x27, 0xd8, 0x1f, 0x89, 0x9c, 0xd9, 0x31, 0x2b, 0xd5, 0x65, 0x33, 0x6c, 0xba, 0xf2, 0x86, 0xd1, 0x7c, 0xb3, 0xc3, 0x55, 0x93, 0x28, 0xca, 0x71, 0x8a, 0xc8, 0xc3, 0x87, 0xc9, 0x1f, 0xdf, 0xf5, 0xa7, 0x67, 0xb6, 0x06, 0x78, 0x6c, 0x8b, 0xd9, 0x0e, 0x7a, 0xd9, 0x5f, 0xea, 0x9c, 0xe0, 0x43, 0x5d, 0xd5, 0xc6, 0x37, 0x80, 0xfe, 0xf5, 0x2b, 0xa7, 0xe0, 0xda, 0xe8, 0xc7, 0xc7, 0x79, 0x76, 0xea, 0xdf, 0x44, 0x11, 0xd2, 0x01, 0x47, 0x67, 0xd4, 0x01, 0x9b, 0x32, 0x3a, 0xe0, 0xfc, 0xdc, 0xab, 0x75, 0xc0, 0xd4, 0x24, 0x1d, 0x30, 0x35, 0x5d, 0x07, 0xac, 0xce, 0xe8, 0x80, 0x53, 0x86, 0x43, 0x4e, 0xff, 0xc6, 0xb3, 0xef, 0x8f, 0xc6, 0x15, 0x2f, 0xeb, 0x6b, 0xb3, 0x17, 0x7f, 0x6b, 0x71, 0x76, 0xad, 0xfe, 0x25, 0x65, 0xbc, 0x70, 0x3f, 0xa0, 0x8c, 0x95, 0x79, 0x9e, 0xa8, 0x9e, 0x65, 0x4f, 0x9a, 0x73, 0xa4, 0xef, 0x6a, 0xca, 0x82, 0x1d, 0xdf, 0x9a, 0xdd, 0x19, 0x78, 0x57, 0x96, 0x63, 0x3e, 0xc1, 0xb2, 0x59, 0x85, 0x9e, 0xbc, 0x4a, 0xa3, 0x03, 0x4a, 0xc2, 0x5e, 0x2f, 0x5b, 0x06, 0x2e, 0xc2, 0xbf, 0xef, 0xba, 0x1c, 0xe0, 0x91, 0x70, 0x91, 0x4c, 0x9a, 0xeb, 0x05, 0x3b, 0x2c, 0x59, 0xec, 0x77, 0x1c, 0x4e, 0xf0, 0x3c, 0xdb, 0x52, 0x0d, 0x4a, 0xb2, 0x2c, 0xec, 0x0e, 0x6f, 0x9e, 0x54, 0x16, 0x0f, 0xe3, 0xfd, 0xcf, 0x1f, 0xff, 0x84, 0xca, 0x4b, 0xfb, 0x70, 0x4b, 0x01, 0xcd, 0x18, 0xff, 0x13, 0xdd, 0xaf, 0xf3, 0x3f, 0xd4, 0xfd, 0x3a, 0xa7, 0xe9, 0x7e, 0x4b, 0x8b, 0x63, 0x85, 0x39, 0xe1, 0x50, 0xc0, 0xe5, 0xb0, 0x9a, 0xa7, 0xeb, 0x7e, 0x27, 0x0e, 0x0c, 0xe2, 0x78, 0xd2, 0xd1, 0xab, 0x38, 0x92, 0x03, 0xd3, 0xe6, 0xd8, 0x64, 0xdd, 0xef, 0xac, 0xe2, 0xc7, 0xd6, 0x2f, 0x3c, 0xdc, 0x0f, 0x05, 0xbc, 0xda, 0x48, 0xdf, 0x9a, 0xca, 0x35, 0x47, 0x5b, 0xf3, 0x06, 0x97, 0xcc, 0x73, 0x15, 0x49, 0xb3, 0xd4, 0x81, 0xd2, 0xce, 0x8a, 0x68, 0x67, 0xb1, 0x75, 0xc3, 0x96, 0x75, 0xeb, 0x86, 0x37, 0x1a, 0xa3, 0x6d, 0x85, 0x25, 0xb3, 0x8b, 0x3c, 0x52, 0x99, 0x5a, 0x4c, 0x1d, 0x8e, 0xd5, 0x64, 0x37, 0x2d, 0x2e, 0xb4, 0x56, 0x96, 0x45, 0x94, 0x96, 0x6d, 0x7d, 0xf5, 0xa3, 0x6d, 0x41, 0x95, 0xb7, 0x2c, 0xd4, 0xa0, 0xb2, 0xb9, 0x6d, 0xc6, 0x9c, 0x0a, 0x4f, 0xc3, 0xdc, 0x5d, 0xed, 0x49, 0x73, 0xc4, 0xa7, 0xd3, 0x9b, 0xf5, 0x62, 0x25, 0x77, 0x5e, 0xc6, 0xc7, 0x71, 0x5e, 0x6d, 0xe3, 0x74, 0xbd, 0xaf, 0x69, 0x92, 0xde, 0xd7, 0x48, 0x18, 0xbe, 0x4c, 0xef, 0xcb, 0xe5, 0x2c, 0xa6, 0x2e, 0x5f, 0xa4, 0x69, 0x91, 0x5a, 0xc4, 0x66, 0x8b, 0xdd, 0xfa, 0x0d, 0x17, 0x68, 0x5a, 0xa3, 0x06, 0x3f, 0x15, 0x7b, 0xf4, 0x28, 0x79, 0x35, 0x79, 0x5c, 0x93, 0x6f, 0xd0, 0x7a, 0x64, 0x63, 0xbb, 0x55, 0x10, 0x00, 0xc7, 0x96, 0x69, 0xf2, 0xb3, 0xc2, 0x26, 0x72, 0x9b, 0xd2, 0x9e, 0x1e, 0x03, 0xce, 0x11, 0x6d, 0x45, 0x3a, 0x5f, 0x01, 0x20, 0x19, 0x88, 0xb3, 0x18, 0x38, 0x10, 0x86, 0x4b, 0xd0, 0x61, 0xc2, 0xa9, 0x1c, 0xe1, 0xc9, 0x74, 0x28, 0x21, 0x44, 0x3a, 0x53, 0x1c, 0x44, 0x06, 0xc0, 0xc0, 0x64, 0xc8, 0xe4, 0xf4, 0xbf, 0xe4, 0xdf, 0xe5, 0x05, 0x10, 0x90, 0xc6, 0xb3, 0x0a, 0xbd, 0x79, 0x55, 0x10, 0x00, 0xf6, 0x47, 0x8b, 0x35, 0x08, 0x02, 0x57, 0x5c, 0xb8, 0xb0, 0x20, 0x58, 0xaa, 0xa5, 0x2e, 0xe8, 0xad, 0x69, 0xa0, 0x61, 0x17, 0x40, 0xc8, 0xc2, 0x70, 0x46, 0xfd, 0x9b, 0x6d, 0x75, 0x3a, 0x70, 0xbc, 0x25, 0xa4, 0xd1, 0x0a, 0x9c, 0xb3, 0x7a, 0xba, 0xbe, 0xd7, 0x74, 0x2d, 0x7d, 0xaf, 0xe9, 0xab, 0xf4, 0xbd, 0x9c, 0xab, 0x16, 0x56, 0x39, 0xa1, 0x93, 0x4c, 0x2b, 0x2e, 0xb0, 0x3f, 0x96, 0xe4, 0xd5, 0x74, 0x2e, 0x5c, 0x92, 0xdf, 0x1b, 0x73, 0xd6, 0x56, 0x97, 0x15, 0xb8, 0x45, 0xe4, 0x5d, 0xd4, 0xa2, 0xd7, 0xd8, 0x0f, 0x2f, 0x6c, 0xaa, 0xd6, 0x5b, 0xb7, 0xe8, 0xab, 0x37, 0x5e, 0x00, 0x59, 0xaf, 0x21, 0x1c, 0x03, 0xc7, 0xc3, 0x9b, 0x87, 0xf1, 0x7a, 0xf8, 0xda, 0xba, 0x5e, 0xd3, 0x75, 0xe8, 0x7a, 0x4d, 0x5f, 0xaa, 0xeb, 0x95, 0x20, 0x2a, 0x10, 0x74, 0x3b, 0xed, 0x56, 0x54, 0xbb, 0x44, 0x1c, 0x96, 0x84, 0x39, 0x3a, 0x20, 0x9a, 0x4c, 0x07, 0x94, 0xea, 0x49, 0x8c, 0xd1, 0xb4, 0x49, 0x29, 0xe9, 0xdf, 0x0e, 0x0e, 0xe8, 0xf2, 0x5a, 0x8a, 0x7e, 0xf8, 0xc3, 0x15, 0xb7, 0xcf, 0xf3, 0xe5, 0x2f, 0xba, 0x75, 0x3e, 0xbb, 0x6a, 0xa6, 0x69, 0x1e, 0xe9, 0x6f, 0x72, 0x96, 0x67, 0x9b, 0xd8, 0xa7, 0xc1, 0x6f, 0x22, 0x73, 0xd6, 0x96, 0xd5, 0xde, 0xd8, 0x95, 0x7b, 0xf5, 0xcc, 0xd9, 0x8d, 0xe9, 0x3a, 0xf5, 0x04, 0x75, 0x99, 0x70, 0x23, 0x3f, 0x7d, 0x8b, 0x1c, 0x22, 0x2b, 0x94, 0xa5, 0x0c, 0x25, 0x2b, 0x06, 0xc4, 0x08, 0x03, 0xcf, 0x24, 0x65, 0x4a, 0x2b, 0x12, 0xe1, 0x0e, 0xb8, 0x09, 0x97, 0x23, 0xcb, 0xa7, 0xe4, 0xf1, 0xb2, 0xd2, 0x90, 0x8a, 0xc1, 0x14, 0x19, 0x08, 0xb0, 0xf7, 0xab, 0x72, 0x92, 0x6e, 0x0d, 0x9b, 0x2a, 0x88, 0xb3, 0x7c, 0x3e, 0x7b, 0x46, 0x68, 0x56, 0xeb, 0x54, 0x16, 0x21, 0x7b, 0x9a, 0x27, 0x38, 0xfb, 0x35, 0x8d, 0x5f, 0x06, 0x02, 0x52, 0x9d, 0x48, 0xa4, 0x95, 0x02, 0x8f, 0xcc, 0xaf, 0xa1, 0x2e, 0x5f, 0x61, 0xc8, 0x7d, 0xd6, 0x66, 0xfb, 0xd8, 0x63, 0x4a, 0xb5, 0x5a, 0x49, 0x2e, 0xb0, 0xb7, 0xd8, 0xc6, 0x36, 0x53, 0x97, 0x55, 0x46, 0x56, 0x94, 0x15, 0x33, 0x99, 0x62, 0x59, 0xe0, 0x5f, 0x26, 0x65, 0xa6, 0xf6, 0x3c, 0xdc, 0x27, 0x31, 0x82, 0xe3, 0x94, 0xd5, 0xa2, 0x1f, 0xeb, 0x98, 0x26, 0x8f, 0x71, 0xaa, 0x16, 0xc6, 0x95, 0x51, 0x19, 0x53, 0xbf, 0xb9, 0xc8, 0xe6, 0x5e, 0xb8, 0x00, 0x7e, 0x8c, 0x0e, 0x0c, 0xf8, 0x19, 0x1b, 0xa6, 0x3e, 0x67, 0x47, 0xc0, 0x51, 0x54, 0xbb, 0x18, 0x12, 0x95, 0x00, 0xde, 0x7f, 0x07, 0xd2, 0x71, 0x3b, 0x67, 0xd0, 0x71, 0x9b, 0xae, 0xd2, 0x71, 0x13, 0x48, 0xe7, 0xd7, 0x8d, 0x9a, 0xc1, 0x91, 0x90, 0x74, 0xff, 0x0c, 0xad, 0x7a, 0x92, 0x16, 0xb4, 0xe7, 0x19, 0x6e, 0x18, 0xed, 0xba, 0x43, 0xe2, 0xe0, 0x76, 0x5d, 0x7c, 0xcd, 0x5d, 0xcf, 0xa8, 0xb8, 0xe9, 0xe7, 0x06, 0x97, 0xea, 0xf2, 0x5a, 0xe3, 0x3f, 0xfc, 0xc1, 0xca, 0xa3, 0x3d, 0xbe, 0xfc, 0xc5, 0xb7, 0xcc, 0x67, 0x57, 0x72, 0x53, 0xb8, 0x38, 0xc3, 0x0e, 0x4f, 0x4c, 0x09, 0xed, 0x2c, 0xe2, 0x57, 0xd2, 0x35, 0xdf, 0xb9, 0x98, 0x87, 0xe5, 0x1c, 0x8e, 0xb6, 0xcf, 0xa0, 0xe4, 0x76, 0x4e, 0x56, 0x72, 0x9b, 0xbe, 0xa2, 0x09, 0x06, 0x66, 0xe3, 0xb5, 0xc2, 0x23, 0xb0, 0x2a, 0x1c, 0xb9, 0x20, 0xc6, 0x7d, 0x39, 0x9c, 0xc2, 0x75, 0x5a, 0x0a, 0x4a, 0x20, 0x97, 0x1e, 0xdf, 0xb7, 0xf7, 0x1e, 0x29, 0x90, 0xdf, 0xb3, 0x6b, 0xcf, 0x31, 0x39, 0x08, 0x4e, 0xab, 0x0f, 0x0f, 0x5e, 0x3f, 0xf9, 0xf0, 0xc3, 0x27, 0x77, 0xdd, 0x7d, 0xdf, 0x7d, 0x77, 0x83, 0x37, 0xa7, 0x97, 0x88, 0xcf, 0xd4, 0x1f, 0xc6, 0x70, 0x30, 0x45, 0xdf, 0x6d, 0xba, 0x96, 0xbe, 0x9b, 0xdb, 0x77, 0x6a, 0x5e, 0x9a, 0x89, 0x01, 0xb0, 0x11, 0x41, 0x7d, 0x90, 0x7e, 0x9e, 0x4c, 0x47, 0x33, 0x9b, 0x66, 0xb4, 0x51, 0x47, 0x1d, 0xd4, 0x07, 0xec, 0xce, 0x8b, 0xec, 0x4e, 0xea, 0x0d, 0xdc, 0xc1, 0xd2, 0xbb, 0xb8, 0x31, 0x40, 0x80, 0xa4, 0x0d, 0x34, 0x4d, 0x58, 0x88, 0xca, 0x17, 0x8d, 0x58, 0xfd, 0xcc, 0x25, 0x1c, 0x52, 0x66, 0xb4, 0xd0, 0xce, 0x94, 0x16, 0x1a, 0x67, 0x5d, 0xe0, 0x7a, 0x4f, 0xd3, 0x34, 0xfc, 0x7b, 0xcf, 0x39, 0x87, 0xda, 0xe3, 0xe6, 0xf4, 0xd3, 0x14, 0x17, 0x78, 0x31, 0x49, 0x41, 0x9d, 0x2a, 0x67, 0x40, 0xfe, 0x52, 0x95, 0x5b, 0x54, 0xe5, 0x35, 0xb9, 0x35, 0x02, 0x4b, 0xa8, 0x44, 0xc1, 0xfe, 0xf4, 0x2c, 0xbb, 0x0c, 0xb9, 0x70, 0x7f, 0x2c, 0x53, 0x0b, 0x04, 0x5a, 0x09, 0xd5, 0x5c, 0xdc, 0x10, 0x90, 0xf1, 0x84, 0x62, 0x7a, 0xb5, 0x27, 0x78, 0xe5, 0x3d, 0xce, 0x87, 0x3b, 0xd0, 0xe6, 0xf3, 0xb5, 0x06, 0xe1, 0xfe, 0xb7, 0xb0, 0x8f, 0x32, 0x55, 0x90, 0xe7, 0x47, 0xfb, 0xef, 0x03, 0xaa, 0x09, 0x1f, 0x31, 0xc3, 0x24, 0xef, 0x2f, 0xe7, 0x24, 0xef, 0x2f, 0x07, 0xf2, 0xfe, 0xb2, 0xf0, 0xd2, 0xfe, 0xbd, 0x5f, 0xd6, 0xd4, 0x35, 0xd1, 0x54, 0x79, 0xfd, 0xbd, 0x6a, 0xaf, 0xbf, 0x57, 0xfd, 0xf5, 0xf6, 0x7a, 0x5d, 0x1d, 0x72, 0x5e, 0xbf, 0x32, 0x9f, 0x17, 0x6b, 0xe2, 0x35, 0x4a, 0x0d, 0xd6, 0xc4, 0x7b, 0x90, 0x4b, 0xa7, 0xd7, 0xe7, 0xc2, 0xe9, 0x9a, 0x67, 0xf6, 0x68, 0xb3, 0x9c, 0x79, 0xf1, 0xc5, 0x33, 0x4f, 0x3d, 0xfd, 0xe2, 0x8b, 0x4f, 0x3f, 0x7b, 0xf3, 0x3b, 0x28, 0x73, 0xc0, 0x3b, 0x37, 0x9f, 0xdf, 0xfa, 0x36, 0xfa, 0xf2, 0xf6, 0xd6, 0xf3, 0x7b, 0x45, 0x17, 0x9f, 0x78, 0xe2, 0x4d, 0x91, 0xe8, 0xcd, 0x27, 0x9e, 0xb8, 0x28, 0xa2, 0x76, 0xf1, 0x5a, 0xf7, 0xbf, 0xba, 0x6e, 0xfd, 0xab, 0xfb, 0x5b, 0x78, 0xec, 0x73, 0xa0, 0x95, 0xd7, 0xb2, 0xff, 0xd5, 0xf5, 0xeb, 0x5e, 0xdd, 0xdf, 0xca, 0x03, 0x77, 0xa7, 0xe0, 0x16, 0xf2, 0xb2, 0xf7, 0x42, 0xb8, 0xcb, 0xe8, 0xde, 0x4d, 0xff, 0xb1, 0xee, 0x9d, 0xbe, 0xd7, 0x6e, 0xd8, 0x78, 0xe5, 0x7e, 0x84, 0x02, 0xee, 0xe2, 0xd8, 0x50, 0x88, 0xcd, 0x4e, 0x21, 0xc8, 0xe4, 0xfa, 0xc7, 0xe7, 0x62, 0x42, 0xf7, 0x6e, 0xfa, 0x4f, 0x75, 0xef, 0xd4, 0x6f, 0xd8, 0x33, 0x90, 0xf3, 0x65, 0x9f, 0x4e, 0xe1, 0x49, 0xf6, 0x37, 0x13, 0xef, 0xe0, 0x7c, 0x69, 0xe1, 0x69, 0xd9, 0x8d, 0x63, 0x72, 0x42, 0x49, 0xbf, 0x62, 0x06, 0xdd, 0xbb, 0x69, 0x42, 0xf7, 0xae, 0x24, 0x94, 0x1a, 0xd5, 0x64, 0xdd, 0x3b, 0x98, 0xd0, 0xbd, 0x53, 0xbb, 0x2f, 0xec, 0xfe, 0xfd, 0x93, 0xfd, 0x17, 0x63, 0xc3, 0x0f, 0xae, 0xc2, 0x08, 0xf9, 0xb4, 0x53, 0x1e, 0xdf, 0xbf, 0x1e, 0xcc, 0x66, 0x7f, 0x50, 0xd7, 0xe9, 0x90, 0x66, 0x81, 0xb7, 0x32, 0x67, 0x9d, 0xf9, 0x10, 0xe3, 0xfc, 0x8c, 0xde, 0xdd, 0x74, 0xbd, 0x7a, 0x77, 0xe6, 0xc3, 0x2f, 0x1e, 0xb8, 0x00, 0x6e, 0xb9, 0x08, 0x0e, 0x71, 0x85, 0x48, 0xb8, 0x85, 0x42, 0x13, 0xc1, 0x6b, 0xc5, 0x7b, 0x04, 0xd7, 0x3b, 0x49, 0xeb, 0xdd, 0x4d, 0xff, 0xb7, 0x7a, 0x77, 0xde, 0x23, 0x76, 0xc3, 0xa6, 0x2f, 0x8e, 0x71, 0x6f, 0x9a, 0xbe, 0x2d, 0x78, 0x6b, 0x20, 0x8d, 0x31, 0x43, 0x7c, 0xb1, 0x08, 0xd3, 0x18, 0xd3, 0x8c, 0x7a, 0x77, 0xd3, 0x14, 0xbd, 0x3b, 0x22, 0x1b, 0x29, 0x82, 0x61, 0x92, 0x98, 0x38, 0x82, 0x21, 0xb8, 0x26, 0xc1, 0x88, 0x42, 0x09, 0xf1, 0xc3, 0xab, 0x38, 0x83, 0xf5, 0xec, 0xb7, 0xc1, 0xe5, 0x99, 0x38, 0x81, 0x25, 0xf7, 0x60, 0x02, 0x81, 0xe5, 0x59, 0xc6, 0x8d, 0xc7, 0x64, 0x80, 0x73, 0x2d, 0x4f, 0x96, 0x08, 0xd2, 0xa3, 0x12, 0xf2, 0x48, 0xc4, 0x73, 0xf1, 0x27, 0x53, 0x33, 0xa3, 0x11, 0x8f, 0xca, 0x62, 0x84, 0xc4, 0x0c, 0x8f, 0xcc, 0x20, 0x31, 0x7c, 0x05, 0x03, 0x13, 0x51, 0xa2, 0x8d, 0x27, 0xb5, 0x4b, 0x97, 0x4e, 0x1a, 0x1b, 0x22, 0x63, 0xeb, 0x5f, 0xa6, 0x7e, 0x73, 0x61, 0xa6, 0xb1, 0x7d, 0xce, 0x6e, 0x04, 0x07, 0xd0, 0x5e, 0x11, 0x1c, 0x5d, 0xa6, 0x6e, 0x49, 0x8d, 0x6f, 0x9a, 0xde, 0xdf, 0x34, 0x45, 0xef, 0x8f, 0x06, 0x36, 0x75, 0x48, 0xfc, 0x6b, 0x0f, 0x69, 0xc6, 0xe1, 0x5c, 0x63, 0x2c, 0x1c, 0x0c, 0xc6, 0xa1, 0xcc, 0xb3, 0x11, 0xd2, 0xd1, 0x6c, 0x62, 0xf3, 0x8b, 0x2a, 0x40, 0x65, 0xcc, 0xa3, 0xd9, 0x5f, 0xa5, 0xf9, 0xbf, 0x4e, 0x71, 0xa7, 0xff, 0x1a, 0xe2, 0x8e, 0xd8, 0xe5, 0x56, 0xaa, 0x5c, 0xde, 0x49, 0xc6, 0x82, 0x09, 0x52, 0x3a, 0x91, 0x9e, 0x67, 0xaa, 0xa1, 0xa0, 0xaa, 0xf2, 0xb1, 0xd5, 0xeb, 0x5f, 0xd8, 0x92, 0xa8, 0xdb, 0xf4, 0xe8, 0xa2, 0xf8, 0xaa, 0x25, 0xdd, 0xae, 0x28, 0x14, 0x69, 0x3c, 0x45, 0x1d, 0x89, 0x8d, 0xf7, 0xca, 0xd8, 0x10, 0xf8, 0x89, 0x20, 0xb7, 0x71, 0x28, 0x11, 0x6f, 0x8e, 0x7a, 0x14, 0x0a, 0xa5, 0x80, 0xda, 0x95, 0xac, 0x6b, 0xbb, 0xe5, 0xb5, 0x1b, 0x6e, 0xbe, 0xb0, 0xaf, 0x0e, 0x09, 0x32, 0x55, 0x2a, 0x8b, 0xd3, 0xf2, 0xd0, 0xf1, 0x03, 0xb5, 0xab, 0x9b, 0xfd, 0x7a, 0x93, 0x5e, 0x9a, 0xb2, 0x0f, 0x40, 0x3e, 0x82, 0x3a, 0x8a, 0xe7, 0xbf, 0x3d, 0x29, 0xca, 0x06, 0x80, 0x6f, 0x82, 0x72, 0xdf, 0x84, 0xcc, 0x87, 0xd2, 0x09, 0x11, 0xe4, 0x08, 0x4a, 0x67, 0x42, 0x31, 0x34, 0x9c, 0x1c, 0x41, 0x41, 0xdc, 0xd3, 0x9f, 0x4e, 0xa5, 0xef, 0x44, 0x38, 0x08, 0xa7, 0xd2, 0x77, 0xf0, 0xf0, 0x22, 0x5c, 0x77, 0x7b, 0x17, 0xf2, 0x43, 0x16, 0xfb, 0xbd, 0x2e, 0xa7, 0x52, 0xe3, 0xc2, 0x8b, 0x00, 0xa6, 0x19, 0x0e, 0x66, 0x2c, 0x85, 0xa0, 0xd3, 0x53, 0x9d, 0x02, 0xa5, 0x42, 0xe1, 0x89, 0x36, 0xc7, 0x13, 0x43, 0x4d, 0xb9, 0x02, 0xf0, 0x53, 0x36, 0x24, 0x3b, 0xb9, 0x29, 0xd1, 0x51, 0xe4, 0x51, 0x67, 0x49, 0xa3, 0xae, 0xee, 0x25, 0xab, 0xe2, 0x8b, 0x1e, 0xdd, 0x54, 0x97, 0xd8, 0xf2, 0xc2, 0xfa, 0xd5, 0x8f, 0x55, 0x52, 0x49, 0xb1, 0x14, 0xce, 0xd6, 0xdf, 0xbc, 0xba, 0xf6, 0xc0, 0xf1, 0x87, 0xe0, 0x0a, 0xa8, 0xaa, 0x42, 0x65, 0x5e, 0x55, 0xdd, 0xbe, 0x0b, 0x37, 0xdf, 0xf0, 0xda, 0x2d, 0x6d, 0xc8, 0x23, 0x1f, 0xc2, 0xe2, 0xf8, 0x27, 0xcc, 0xfb, 0x70, 0x0d, 0xc4, 0x84, 0xf7, 0x4b, 0x6c, 0x07, 0xce, 0xeb, 0xb5, 0x1d, 0x38, 0xbf, 0xda, 0x76, 0x60, 0xca, 0xd8, 0x04, 0x9c, 0x5f, 0x6a, 0x3b, 0x98, 0xdc, 0x6e, 0x66, 0xdb, 0x81, 0x44, 0x02, 0x08, 0x89, 0x57, 0xe2, 0xf5, 0x20, 0xfb, 0x8c, 0xdd, 0x6c, 0x54, 0x29, 0xb8, 0xd2, 0x05, 0xa2, 0x49, 0xfa, 0x49, 0x5c, 0x42, 0x66, 0x8a, 0x6e, 0x92, 0xf4, 0xb9, 0x30, 0x27, 0x4a, 0x93, 0x07, 0x76, 0xef, 0x3e, 0xb0, 0x7f, 0xf7, 0x6e, 0xf6, 0x55, 0x51, 0xc3, 0x86, 0x07, 0x06, 0x86, 0x9f, 0xde, 0x5c, 0x55, 0xb3, 0xe5, 0xe9, 0x15, 0x43, 0x0f, 0xdd, 0x58, 0x27, 0xf8, 0xcb, 0x85, 0x0b, 0x87, 0xde, 0x38, 0x7b, 0xf6, 0xc2, 0x85, 0xb3, 0x67, 0xdf, 0x00, 0xdf, 0x5e, 0xf6, 0xd8, 0xfa, 0x8a, 0x34, 0xcb, 0x96, 0xb8, 0xe9, 0x89, 0x95, 0x6c, 0x2d, 0x3c, 0x44, 0xed, 0x58, 0x8f, 0x03, 0x41, 0x89, 0xe2, 0x64, 0x2c, 0x3f, 0xd2, 0xb6, 0x5b, 0x67, 0xb0, 0x11, 0x20, 0x7a, 0x6d, 0x4a, 0x1b, 0x09, 0x90, 0xa3, 0x85, 0xd8, 0x2f, 0xf6, 0x99, 0x0c, 0x0a, 0x99, 0x54, 0x02, 0x1f, 0x13, 0x3a, 0x04, 0xd8, 0x50, 0x90, 0x52, 0xa5, 0x46, 0x38, 0x3d, 0x09, 0x84, 0x03, 0x4a, 0x46, 0x4d, 0x36, 0x14, 0x90, 0x5f, 0x7b, 0x30, 0xde, 0x98, 0xad, 0x1e, 0x5d, 0x02, 0x1a, 0x5e, 0xae, 0xb5, 0x78, 0x4d, 0x1a, 0xd1, 0x0f, 0x45, 0x3a, 0xb3, 0xd7, 0xb8, 0x1f, 0x58, 0x3c, 0xc9, 0xe6, 0x15, 0x55, 0xd6, 0xe0, 0x82, 0x3b, 0x96, 0x91, 0x2e, 0xd0, 0x69, 0x2b, 0x28, 0xb3, 0xb6, 0xf5, 0x80, 0x4b, 0x88, 0xf6, 0x08, 0x55, 0x46, 0x05, 0xfb, 0x5b, 0xa5, 0x41, 0x29, 0x04, 0x9b, 0x12, 0x3a, 0x51, 0xb4, 0x7e, 0x4e, 0x38, 0xb6, 0xbc, 0x23, 0x42, 0x80, 0xf1, 0xf7, 0xd9, 0x73, 0xd4, 0x27, 0xcc, 0x45, 0xc2, 0x45, 0x2c, 0x4d, 0xca, 0x11, 0x0b, 0xa7, 0x87, 0xd0, 0x4b, 0x36, 0x8a, 0x84, 0x24, 0x59, 0xcf, 0x29, 0x40, 0x2d, 0x3c, 0x40, 0x43, 0x86, 0x99, 0xa0, 0x29, 0x9c, 0xcb, 0x07, 0xe2, 0xa8, 0x01, 0x6c, 0xed, 0x48, 0x73, 0x7f, 0x33, 0xdf, 0xc7, 0x2a, 0x31, 0xc4, 0x05, 0x26, 0x39, 0xda, 0xa5, 0xce, 0xe5, 0x00, 0x9c, 0x4b, 0x32, 0xaf, 0x21, 0xf9, 0xbe, 0x4c, 0xdc, 0x01, 0x62, 0x48, 0x5c, 0xd1, 0x08, 0xa7, 0x23, 0x07, 0xcd, 0xc9, 0x93, 0x83, 0x2f, 0x3e, 0xb7, 0x6f, 0xe3, 0x7d, 0x91, 0xf6, 0xb8, 0xc5, 0x56, 0xd2, 0x1e, 0x69, 0xda, 0x5d, 0x74, 0xf1, 0xd9, 0x67, 0x2f, 0x82, 0x6e, 0xf6, 0x09, 0xca, 0x51, 0xd5, 0xfa, 0xe1, 0xcf, 0x7f, 0xc5, 0xf6, 0x7f, 0xb3, 0xa2, 0xe7, 0xb9, 0x57, 0xde, 0x5a, 0xdb, 0xbc, 0xb6, 0xc1, 0x55, 0x57, 0x31, 0xb6, 0x86, 0xae, 0x3b, 0xf6, 0xc7, 0x3f, 0x1e, 0x23, 0x52, 0xf6, 0x5f, 0xba, 0x94, 0x91, 0x63, 0x59, 0xa7, 0x2e, 0x59, 0x8d, 0x72, 0x65, 0x8c, 0x66, 0xd4, 0xf9, 0x88, 0x7b, 0x62, 0x7a, 0xb8, 0x7d, 0x10, 0x4e, 0xc3, 0xaf, 0xff, 0xa9, 0x14, 0x13, 0xc5, 0xe4, 0x11, 0x0e, 0x99, 0xbc, 0xc8, 0x49, 0x31, 0xbf, 0xff, 0x5d, 0x46, 0x8a, 0x59, 0xc4, 0xfe, 0x1b, 0xf0, 0x5f, 0x06, 0x55, 0xec, 0xb7, 0x66, 0xc2, 0xbb, 0x8b, 0x8f, 0x81, 0x2c, 0xf6, 0x4f, 0xc7, 0x3e, 0xc6, 0xb8, 0x67, 0x29, 0x7d, 0x80, 0x7c, 0x15, 0xfb, 0xcd, 0xa1, 0x78, 0x12, 0xa4, 0x45, 0x26, 0x48, 0x9c, 0x2b, 0xd2, 0x80, 0x4c, 0x32, 0x7a, 0xad, 0x5c, 0x0a, 0x61, 0xd9, 0x04, 0x4c, 0x0c, 0x2f, 0x9d, 0x52, 0xb4, 0x40, 0x37, 0x49, 0x61, 0x5c, 0x8a, 0x82, 0x99, 0xc1, 0x51, 0x7f, 0xdc, 0x21, 0x5f, 0xa7, 0xd1, 0x05, 0x5a, 0xd7, 0xd6, 0xd5, 0xad, 0x6d, 0x0d, 0xe8, 0x34, 0xeb, 0xe4, 0x8e, 0x38, 0xfd, 0x89, 0xca, 0x5f, 0x99, 0xeb, 0xd6, 0x15, 0xcc, 0x2e, 0xb6, 0xd9, 0x8a, 0x67, 0x17, 0xe8, 0xdc, 0xb9, 0x95, 0x7e, 0x14, 0x7b, 0xff, 0x14, 0xbd, 0x96, 0x5c, 0xcf, 0x94, 0x73, 0xf5, 0xa9, 0x71, 0xca, 0xb2, 0x51, 0x94, 0x2c, 0x92, 0x24, 0xba, 0xd3, 0x6a, 0x6c, 0x82, 0x6c, 0x9b, 0xf2, 0x72, 0xd1, 0x97, 0xbc, 0xfc, 0x88, 0xc9, 0xa5, 0x15, 0x0e, 0xaa, 0x95, 0xf6, 0xb2, 0x39, 0x85, 0x91, 0xae, 0x32, 0x87, 0x4a, 0x35, 0x24, 0xd0, 0xba, 0xe8, 0x77, 0xc5, 0xc6, 0x90, 0xdd, 0xae, 0x76, 0x17, 0xfb, 0x75, 0x3a, 0x7f, 0xb1, 0x5b, 0x6d, 0xb7, 0x87, 0x8c, 0x29, 0x7c, 0xbb, 0x94, 0xf9, 0x37, 0xf9, 0x2a, 0x9f, 0xfa, 0xff, 0xe9, 0xfd, 0x33, 0x4f, 0x9e, 0xb7, 0x50, 0xe9, 0x4b, 0xa0, 0xc9, 0xb7, 0x44, 0xcd, 0xe6, 0x68, 0x0b, 0x9a, 0x7c, 0xc2, 0xa7, 0xe4, 0x74, 0xae, 0x87, 0xc6, 0xff, 0x01, 0xee, 0x80, 0x6b, 0x8e, 0xa4, 0x46, 0x24, 0x69, 0x23, 0x0f, 0x6e, 0x64, 0x05, 0x1b, 0xc8, 0x04, 0x41, 0x0c, 0x53, 0x90, 0x33, 0xb2, 0x9a, 0x35, 0x2a, 0xd8, 0x46, 0xaa, 0x63, 0x50, 0x8a, 0x3e, 0x9c, 0x7d, 0xc8, 0x85, 0xdc, 0x2b, 0xa8, 0x69, 0xf9, 0x0c, 0xc1, 0x1a, 0x9a, 0x2f, 0xe2, 0xed, 0xe1, 0x89, 0xf8, 0x74, 0x50, 0xe3, 0xce, 0x33, 0x9b, 0xf2, 0x50, 0xbe, 0xbb, 0x3c, 0x93, 0x39, 0xcf, 0xad, 0x61, 0x4e, 0x8d, 0x5d, 0xcc, 0x9e, 0xdb, 0xbd, 0x20, 0x12, 0x59, 0xd0, 0x3d, 0x37, 0x9b, 0x2c, 0xfb, 0xe2, 0x7f, 0x4d, 0xbe, 0x0b, 0x5b, 0x73, 0xf1, 0x66, 0xff, 0xa0, 0xf2, 0x98, 0x33, 0x84, 0x8f, 0x28, 0x45, 0xde, 0x36, 0x13, 0x19, 0x4d, 0xa0, 0x04, 0x4e, 0xaf, 0x49, 0x47, 0x17, 0xad, 0x42, 0x74, 0x03, 0x4b, 0xa8, 0xc3, 0x4c, 0x33, 0x24, 0x1a, 0x61, 0x33, 0x4e, 0x67, 0xa2, 0xce, 0x20, 0x35, 0x5a, 0x0e, 0x5c, 0x3e, 0xef, 0x44, 0x41, 0x74, 0x97, 0xd3, 0x37, 0x3d, 0xf5, 0x62, 0x9b, 0xbd, 0x79, 0xfe, 0x8a, 0xf2, 0xf6, 0x83, 0x03, 0x45, 0xd9, 0x35, 0xed, 0xf5, 0x11, 0x46, 0xbd, 0x57, 0xc6, 0x04, 0xab, 0x3b, 0xea, 0xf2, 0xec, 0x45, 0xcd, 0xa1, 0xb2, 0xae, 0x52, 0xaf, 0xd4, 0x22, 0x3d, 0xa2, 0x71, 0x4d, 0x9e, 0x83, 0x4b, 0xc3, 0x98, 0x2d, 0x85, 0x7e, 0x7d, 0xce, 0xdc, 0x4d, 0x4d, 0xcd, 0x6b, 0x3b, 0xca, 0x63, 0x51, 0xc7, 0xb1, 0x47, 0x73, 0x6a, 0x4a, 0x8b, 0x6a, 0xbb, 0xcb, 0x02, 0x55, 0xb9, 0x06, 0x48, 0x4f, 0x0c, 0x97, 0xcf, 0x5c, 0x35, 0x2b, 0x9c, 0xaf, 0x16, 0xce, 0x6b, 0x3f, 0xf3, 0x1c, 0xa1, 0x26, 0x92, 0xc8, 0x9a, 0x10, 0x80, 0xf3, 0xc9, 0x71, 0x91, 0x90, 0x33, 0x68, 0x0c, 0x02, 0xb2, 0x81, 0xa0, 0x68, 0x88, 0x33, 0x88, 0x11, 0x14, 0xed, 0x8d, 0xd1, 0xfb, 0x00, 0x66, 0x11, 0xd3, 0xd5, 0x0f, 0x01, 0x51, 0x51, 0x56, 0x5a, 0x8c, 0x2a, 0xd1, 0x70, 0x86, 0x60, 0x94, 0xde, 0xd7, 0x33, 0x4d, 0xd6, 0xa6, 0x32, 0x3c, 0x03, 0x9a, 0x7b, 0x0e, 0xf0, 0x51, 0x71, 0x2c, 0x57, 0x32, 0x29, 0xcc, 0x82, 0x3d, 0x3b, 0xcf, 0x4c, 0x9e, 0x0b, 0x9c, 0x9b, 0xd8, 0x3e, 0x37, 0xbb, 0x65, 0xe7, 0xc2, 0xc2, 0xe2, 0xb9, 0x43, 0xf3, 0x2b, 0x79, 0x60, 0x93, 0x39, 0xaa, 0x3f, 0xff, 0xe2, 0x01, 0x46, 0x29, 0xc8, 0x2a, 0x4f, 0x56, 0xe4, 0x59, 0x0d, 0x79, 0x35, 0x41, 0x4b, 0x48, 0xa0, 0xd3, 0x52, 0xff, 0x9e, 0x34, 0x21, 0xbc, 0x20, 0xf4, 0x41, 0x89, 0xd4, 0xdf, 0x72, 0x43, 0xe3, 0xec, 0x35, 0x2d, 0x45, 0x85, 0x65, 0xa1, 0xd3, 0xb3, 0x1e, 0x68, 0x25, 0x97, 0xe5, 0x8c, 0xfd, 0x53, 0x5d, 0x17, 0xb4, 0xe5, 0x85, 0x83, 0x79, 0x25, 0x5e, 0x67, 0x49, 0xd8, 0x20, 0x50, 0x40, 0x19, 0x99, 0x18, 0x62, 0xff, 0x06, 0x06, 0x89, 0x7f, 0x23, 0xa7, 0xb1, 0xa4, 0x49, 0x03, 0x88, 0xfa, 0x4c, 0x4e, 0xe4, 0xa5, 0x28, 0x57, 0x37, 0x9c, 0x93, 0x0d, 0xd8, 0x50, 0x5a, 0xbd, 0xc8, 0xb4, 0xca, 0xa3, 0x7c, 0x1f, 0xce, 0xf4, 0x57, 0x41, 0xc5, 0x7f, 0x63, 0xca, 0x86, 0x87, 0xc9, 0x91, 0x63, 0xb6, 0xe7, 0x3b, 0x14, 0xc6, 0x82, 0xc6, 0x5c, 0x87, 0x5c, 0xc8, 0x17, 0xd9, 0x6d, 0x26, 0x1e, 0xda, 0x21, 0x94, 0xd8, 0x56, 0x1f, 0x88, 0xd9, 0xb2, 0x97, 0x2e, 0x59, 0xe0, 0xb3, 0xc9, 0x94, 0x7c, 0x39, 0x5f, 0xec, 0xce, 0x2d, 0xb6, 0x8f, 0x8f, 0x13, 0xbb, 0xe1, 0xbb, 0x0f, 0x51, 0xbf, 0x24, 0xbc, 0x20, 0xee, 0x22, 0x00, 0x1f, 0x94, 0x10, 0x46, 0xf2, 0xf7, 0x84, 0xf0, 0x59, 0x00, 0xbe, 0x41, 0xfe, 0x1e, 0x79, 0xff, 0xc3, 0x36, 0x9b, 0xe0, 0x19, 0xd8, 0x81, 0xdb, 0x94, 0x28, 0xb8, 0x36, 0x8a, 0xa9, 0x6d, 0xe0, 0x1c, 0x56, 0xc0, 0x36, 0x23, 0x70, 0x0e, 0x53, 0x72, 0x4b, 0xc2, 0xad, 0xe1, 0x52, 0x40, 0xc6, 0xa7, 0xc1, 0xd6, 0x8a, 0xe9, 0x70, 0x73, 0xe3, 0x55, 0x40, 0x01, 0xdf, 0x0b, 0xfb, 0x64, 0xb6, 0xc1, 0x3e, 0xe1, 0x7b, 0x61, 0x8f, 0x3e, 0x50, 0x42, 0xa3, 0xc8, 0x22, 0xf8, 0x7e, 0x92, 0xcf, 0xe5, 0xb4, 0xa5, 0xce, 0x82, 0x4e, 0x1e, 0xd2, 0x3f, 0xf0, 0x9e, 0x67, 0x08, 0x90, 0x17, 0x52, 0xe7, 0x20, 0x23, 0x01, 0xb2, 0x1f, 0xca, 0x48, 0x1d, 0xfa, 0x38, 0x1d, 0xb6, 0x66, 0xb5, 0x55, 0x95, 0x2b, 0x68, 0x61, 0x96, 0x47, 0xa2, 0xb1, 0xcb, 0x24, 0x1a, 0xea, 0xec, 0xac, 0x07, 0xbf, 0x76, 0xb4, 0xd8, 0x23, 0xb6, 0x89, 0xac, 0x3d, 0xcb, 0x47, 0x62, 0x7a, 0xb7, 0x00, 0xbf, 0x6b, 0x09, 0xf5, 0x14, 0xe8, 0x61, 0xae, 0xc0, 0xd9, 0x25, 0x09, 0x19, 0xfd, 0x51, 0x2a, 0xfe, 0xe1, 0x23, 0x2e, 0xfe, 0x01, 0xde, 0x87, 0xef, 0x22, 0x0f, 0xc1, 0x77, 0x79, 0x41, 0x19, 0x1e, 0x43, 0x19, 0x88, 0xa7, 0x9f, 0x23, 0x8f, 0x32, 0x57, 0xe0, 0xe8, 0xf0, 0x73, 0xe8, 0x79, 0x50, 0x0b, 0x9f, 0x57, 0x65, 0x9e, 0x47, 0xcb, 0x94, 0xee, 0x08, 0x10, 0x5d, 0x50, 0x12, 0x69, 0x02, 0x8a, 0x89, 0x31, 0x03, 0x1c, 0x31, 0xe7, 0xf3, 0xc6, 0x70, 0xe4, 0x04, 0xfc, 0x00, 0x44, 0xd8, 0x96, 0x35, 0xbb, 0xaa, 0x4c, 0x49, 0x0b, 0x0d, 0xa9, 0x41, 0x03, 0xc5, 0xac, 0x07, 0xef, 0xbb, 0xbd, 0xd8, 0x2b, 0xb6, 0x09, 0xad, 0x3d, 0x2b, 0xb8, 0x41, 0xc3, 0x77, 0xc3, 0xbe, 0xc8, 0x5d, 0xb0, 0x2f, 0x2f, 0x48, 0xe2, 0x31, 0x25, 0xd1, 0x98, 0xe0, 0x3b, 0x96, 0x10, 0x3b, 0xc0, 0xb7, 0xa8, 0xd3, 0xf8, 0x1d, 0x7c, 0x1a, 0xbe, 0xc3, 0xa3, 0xe3, 0xc9, 0x01, 0x15, 0x55, 0xeb, 0xad, 0x40, 0x0e, 0x78, 0x58, 0x91, 0x7d, 0x56, 0x26, 0x30, 0x26, 0x2e, 0x5f, 0x4e, 0x18, 0x05, 0xb2, 0x13, 0x0a, 0x29, 0x5f, 0x26, 0xf8, 0xd8, 0x18, 0x92, 0xd8, 0xc0, 0x7e, 0x23, 0xd8, 0x67, 0x95, 0x06, 0x8d, 0xe3, 0x06, 0x09, 0x5f, 0x85, 0x6a, 0xb9, 0x6e, 0x86, 0x7d, 0xbd, 0x97, 0xea, 0x4b, 0x49, 0xa2, 0xf1, 0x6a, 0x29, 0x5f, 0x0e, 0xaa, 0x13, 0x10, 0xcf, 0x01, 0xb9, 0xc0, 0x1b, 0x4b, 0x00, 0xf0, 0x1e, 0xd7, 0x8d, 0x5c, 0x26, 0x90, 0x0a, 0x4e, 0xa0, 0x7e, 0x49, 0x87, 0x91, 0xdd, 0x64, 0x93, 0x84, 0x8c, 0xac, 0x41, 0xca, 0x53, 0x4b, 0xc6, 0x8d, 0x41, 0xa9, 0x15, 0xad, 0x15, 0xb1, 0x83, 0xfc, 0x1b, 0xec, 0xcb, 0x0b, 0xaa, 0xf0, 0x78, 0xab, 0x00, 0xb7, 0xcf, 0xf0, 0x1d, 0x14, 0x0f, 0xff, 0x5e, 0x8d, 0x7f, 0xaf, 0x46, 0xbf, 0xa3, 0xbc, 0x7b, 0xe3, 0x8f, 0x52, 0xad, 0x90, 0xdf, 0x10, 0x72, 0x30, 0xc5, 0x05, 0xa8, 0x0e, 0x82, 0x66, 0x97, 0x13, 0xc1, 0x94, 0x3e, 0x65, 0x14, 0x2a, 0x05, 0x63, 0xb2, 0x32, 0x7b, 0x07, 0xd9, 0x25, 0x8b, 0xba, 0x3a, 0x15, 0x5e, 0x0f, 0xf3, 0x73, 0xa7, 0x27, 0xe5, 0xa7, 0xc6, 0x3e, 0x02, 0x66, 0xa1, 0x7c, 0xdc, 0xe9, 0xe7, 0x47, 0xb9, 0xbc, 0x99, 0xa9, 0x54, 0xcc, 0x11, 0xa5, 0xeb, 0xd9, 0xe3, 0xc7, 0x79, 0xe0, 0xf3, 0x87, 0x21, 0xfe, 0x79, 0x81, 0x7d, 0x0a, 0x34, 0xe3, 0xb6, 0x7c, 0x94, 0x83, 0x3a, 0xc5, 0x9d, 0xf4, 0xa7, 0xf3, 0xf1, 0x2f, 0xa7, 0x38, 0x5d, 0x18, 0x4a, 0x0b, 0x1c, 0xc1, 0xac, 0xa0, 0xeb, 0x85, 0xe3, 0xc7, 0xd1, 0xd3, 0x57, 0x7e, 0xcb, 0xfe, 0xf1, 0xca, 0x6f, 0x66, 0x18, 0x2f, 0xca, 0xb7, 0x8f, 0x12, 0xf8, 0x12, 0xcd, 0x2e, 0x3b, 0xf7, 0x3e, 0xa4, 0x0f, 0x46, 0x68, 0xa6, 0xb2, 0x13, 0x4a, 0x22, 0xe4, 0x9c, 0x0e, 0x7b, 0x99, 0x8c, 0xb9, 0xe8, 0x71, 0x7e, 0xe1, 0xf7, 0xe0, 0xec, 0x90, 0xc4, 0x66, 0x92, 0x47, 0xae, 0x21, 0xcf, 0x7c, 0x55, 0xfe, 0xe8, 0xcd, 0xe0, 0x9f, 0xac, 0x84, 0x3c, 0xc3, 0xe5, 0x15, 0x7d, 0x9e, 0x14, 0x51, 0x97, 0xc8, 0xe3, 0x5f, 0xf5, 0xcc, 0xf3, 0x94, 0xf3, 0xca, 0xaf, 0xc8, 0xe3, 0x87, 0xb9, 0xb8, 0x54, 0x52, 0xc4, 0x9c, 0xff, 0xea, 0x67, 0xb6, 0x31, 0xc5, 0x5f, 0xbc, 0x95, 0x7e, 0xa6, 0x9a, 0xfd, 0x0d, 0xd5, 0x3d, 0x3e, 0x00, 0x9f, 0xb1, 0x25, 0xcd, 0x24, 0xce, 0xd0, 0x3d, 0x87, 0x4c, 0x95, 0x94, 0x02, 0x44, 0x4b, 0xba, 0xd2, 0x10, 0xa4, 0x7e, 0x8c, 0xd6, 0x11, 0xa5, 0xba, 0xaf, 0x7c, 0x74, 0xdf, 0xae, 0x5d, 0xf0, 0xce, 0xeb, 0xc4, 0x0f, 0xa9, 0xb7, 0xa8, 0x9f, 0x43, 0xbc, 0x90, 0xcd, 0x09, 0x00, 0xe2, 0xb4, 0xee, 0x6f, 0x08, 0x89, 0x81, 0x8a, 0x89, 0x70, 0xad, 0x54, 0x22, 0xd1, 0x17, 0x38, 0x8c, 0x31, 0x9d, 0x1a, 0xbd, 0x5e, 0xbe, 0xe2, 0xd6, 0xd9, 0xb3, 0x0f, 0xaf, 0x28, 0x2f, 0x5f, 0x71, 0x78, 0xf6, 0xec, 0x5b, 0x57, 0x94, 0x93, 0x1d, 0xed, 0x87, 0x57, 0x96, 0x97, 0xaf, 0x3c, 0xdc, 0x3e, 0xfb, 0x56, 0xf4, 0x79, 0x2b, 0xb7, 0xe7, 0x2b, 0xc0, 0xf3, 0xe0, 0x34, 0xf9, 0x69, 0x2a, 0x1f, 0xed, 0xb4, 0xce, 0x67, 0xee, 0x7a, 0x85, 0x21, 0x54, 0x6c, 0x77, 0x94, 0xa2, 0x04, 0xc1, 0xa5, 0x0e, 0x7b, 0x71, 0xc8, 0x40, 0x76, 0xa4, 0xaf, 0x1c, 0xc5, 0xe8, 0xb3, 0x18, 0x3e, 0x3c, 0x97, 0xbd, 0x04, 0x7e, 0xc3, 0xd3, 0x10, 0x7a, 0x74, 0x0e, 0xf8, 0xe8, 0x1c, 0x68, 0x54, 0x7a, 0x74, 0xac, 0x26, 0x02, 0x5f, 0x51, 0x96, 0x3a, 0x62, 0xe5, 0x90, 0x52, 0xa1, 0xec, 0x6e, 0x5a, 0x51, 0x69, 0xb5, 0x56, 0xae, 0x68, 0xea, 0x86, 0x17, 0x43, 0x2b, 0xc9, 0xf5, 0x3f, 0x63, 0xd7, 0xbc, 0xe0, 0x70, 0x8b, 0xa3, 0xeb, 0x7e, 0xf7, 0x3f, 0x7f, 0xde, 0xbc, 0xe5, 0xcf, 0xff, 0xf3, 0xbb, 0x75, 0x51, 0xb1, 0xdb, 0xf1, 0x02, 0xbb, 0x9a, 0xab, 0x1d, 0x0a, 0xfb, 0xbe, 0x38, 0xa5, 0xef, 0x42, 0xd2, 0x87, 0x4e, 0x56, 0x4a, 0x09, 0x87, 0xc2, 0xaa, 0x62, 0x2a, 0x60, 0x5c, 0x35, 0x04, 0x61, 0xb0, 0xbb, 0x69, 0x79, 0x95, 0xd5, 0x5a, 0xb5, 0x1c, 0xf6, 0xad, 0x54, 0x0e, 0xad, 0x82, 0x7d, 0x83, 0x3b, 0x33, 0x7d, 0x6f, 0xd9, 0x9c, 0xe9, 0x1b, 0xdc, 0x81, 0xfb, 0x7e, 0x8e, 0xfd, 0x05, 0x19, 0x86, 0x70, 0x2d, 0x25, 0x56, 0xa4, 0x13, 0xf3, 0x91, 0x34, 0x0a, 0xa0, 0x47, 0x82, 0x46, 0x26, 0x52, 0x8e, 0x98, 0x14, 0xcd, 0xa6, 0x99, 0xd4, 0x80, 0xcb, 0xaf, 0x84, 0xd3, 0xcf, 0x5e, 0xfb, 0xb9, 0x1e, 0xce, 0x8a, 0x2d, 0x25, 0x24, 0x4a, 0xb5, 0x83, 0x41, 0x35, 0x0d, 0x94, 0xd8, 0xc5, 0x18, 0x05, 0x60, 0xa1, 0x3a, 0x7c, 0x73, 0x1f, 0xd4, 0x5b, 0xd9, 0x7d, 0x60, 0xb3, 0x55, 0xcf, 0xec, 0xf8, 0xf7, 0x7f, 0x59, 0x75, 0x2d, 0x7c, 0x5f, 0x8b, 0xce, 0x0a, 0x1f, 0x2d, 0x65, 0x7f, 0x01, 0xce, 0xe1, 0xb1, 0xa5, 0xe2, 0xba, 0xb3, 0xd2, 0xb9, 0xf2, 0x27, 0x12, 0x09, 0x73, 0xe0, 0x95, 0x1a, 0x9a, 0x7a, 0xe2, 0x3e, 0x57, 0xcb, 0x6c, 0x39, 0x97, 0x4c, 0xf1, 0x1a, 0x4f, 0xf5, 0xf4, 0x9c, 0x53, 0x3b, 0x5c, 0xf8, 0xd0, 0xe2, 0x14, 0xae, 0xa5, 0x80, 0x2b, 0x23, 0xe3, 0x00, 0xe7, 0xd8, 0xbd, 0xd6, 0xac, 0x07, 0x1f, 0xcc, 0xb2, 0x82, 0x2d, 0xec, 0x2f, 0xe0, 0x60, 0x78, 0x1e, 0x38, 0x2c, 0xee, 0x0c, 0xc3, 0xf5, 0x82, 0x52, 0x0a, 0x51, 0x46, 0x1c, 0x79, 0x51, 0x8c, 0xc3, 0x2a, 0xb9, 0xa1, 0x39, 0x08, 0x06, 0x55, 0x16, 0x40, 0x05, 0x38, 0x69, 0x82, 0x02, 0x34, 0x5c, 0x06, 0x54, 0x6f, 0xaa, 0x3b, 0x25, 0x6f, 0x02, 0xb2, 0x2d, 0x35, 0x46, 0xf3, 0x0c, 0x0d, 0xc1, 0x44, 0xc1, 0xf2, 0xaf, 0xee, 0x07, 0x79, 0x84, 0x40, 0x98, 0x88, 0x21, 0xdf, 0x36, 0x8b, 0x29, 0x4b, 0x4b, 0x94, 0x81, 0x52, 0x5c, 0xaf, 0x80, 0x4b, 0xbd, 0x85, 0x03, 0x7b, 0xb0, 0x6d, 0x17, 0x67, 0xd1, 0xe0, 0x7c, 0xb9, 0x39, 0xb6, 0x00, 0x09, 0xaf, 0x51, 0x4e, 0x00, 0xc9, 0xe4, 0xdb, 0x70, 0x81, 0x5f, 0x37, 0xcd, 0x91, 0x29, 0x64, 0x73, 0x6a, 0x6a, 0xf0, 0x47, 0x93, 0x4a, 0x62, 0x8b, 0xcd, 0xd9, 0xde, 0x15, 0x94, 0xab, 0xe5, 0xd5, 0x55, 0x55, 0xd5, 0xf0, 0x23, 0xd8, 0xb5, 0x7d, 0x4e, 0xcc, 0x26, 0x61, 0x76, 0xf4, 0x77, 0x15, 0x0d, 0x0d, 0x2c, 0xce, 0x6f, 0x5a, 0xad, 0x56, 0xaf, 0x6e, 0xca, 0xef, 0x1f, 0x58, 0x16, 0xef, 0xea, 0x57, 0xeb, 0xe6, 0xe5, 0xce, 0xb9, 0xa9, 0x3e, 0xb2, 0x64, 0xc9, 0xe2, 0x82, 0x44, 0xdb, 0x8d, 0x6d, 0x89, 0xfc, 0xfe, 0x25, 0x4b, 0x22, 0xf5, 0x37, 0xcd, 0xc9, 0x9d, 0xa7, 0x53, 0xc3, 0xf5, 0x4a, 0xc2, 0xf5, 0x32, 0xe1, 0xf5, 0xda, 0xfb, 0xa2, 0x15, 0xe5, 0x9b, 0x48, 0xad, 0x97, 0x1d, 0x4d, 0x8f, 0x41, 0xd3, 0x63, 0x20, 0x7b, 0x87, 0xa6, 0x3b, 0x2d, 0xf1, 0x77, 0x6a, 0xb9, 0x4c, 0x57, 0xb7, 0x23, 0x27, 0x72, 0x1e, 0x7f, 0x65, 0x2f, 0x08, 0xf6, 0xca, 0x4a, 0xf3, 0x72, 0x42, 0xfe, 0x48, 0x48, 0xc1, 0x43, 0xb0, 0x97, 0x5e, 0x25, 0xec, 0x85, 0x86, 0xf6, 0x3c, 0xbd, 0x20, 0xe9, 0xe2, 0x82, 0xd8, 0x06, 0x8e, 0xc0, 0x20, 0x8d, 0x03, 0x90, 0x04, 0x4d, 0x9a, 0xaa, 0xaa, 0xd0, 0x52, 0xcc, 0xd9, 0x3e, 0x27, 0x6a, 0x93, 0xa8, 0xf0, 0x92, 0x75, 0xd5, 0xd4, 0x74, 0xa5, 0x97, 0x2c, 0x0a, 0x6f, 0xa0, 0x25, 0xab, 0xaa, 0x22, 0x23, 0x6d, 0x89, 0x82, 0xc5, 0x13, 0x6b, 0xd0, 0xdf, 0x15, 0x5f, 0x36, 0xd0, 0x9f, 0x5e, 0xb4, 0xc5, 0x03, 0x43, 0x45, 0x93, 0x17, 0xad, 0x3f, 0x3f, 0xd1, 0x86, 0xe1, 0x6a, 0x03, 0x35, 0x8f, 0xfc, 0x80, 0xf9, 0x1f, 0xa2, 0x80, 0xa8, 0x4a, 0xaa, 0xf2, 0xc2, 0x21, 0x9f, 0xd5, 0x68, 0xd0, 0xa9, 0x94, 0x52, 0x06, 0x48, 0x39, 0xef, 0x23, 0x9c, 0x17, 0x28, 0x7d, 0xe2, 0x06, 0x09, 0x8c, 0x1b, 0x31, 0x96, 0x9d, 0x04, 0xcf, 0x2f, 0xba, 0x9c, 0x59, 0x24, 0xce, 0x01, 0x8e, 0x6a, 0xce, 0xa2, 0x9d, 0x8f, 0x62, 0x03, 0xb9, 0x1c, 0xa0, 0x08, 0x2f, 0x54, 0x31, 0x1b, 0xe1, 0x21, 0x3d, 0x2e, 0x2f, 0xa9, 0xc5, 0x41, 0x52, 0x09, 0x80, 0x1c, 0x4c, 0x51, 0x61, 0xb9, 0x0f, 0x72, 0x9f, 0x50, 0x59, 0xc5, 0xf2, 0x45, 0x8c, 0x40, 0x6c, 0x56, 0x3e, 0x9e, 0xfb, 0xb8, 0xca, 0xa9, 0xf6, 0x3a, 0x9e, 0x80, 0x3f, 0x5a, 0xc4, 0x02, 0xfe, 0x22, 0x86, 0xcf, 0xfd, 0xaa, 0x74, 0xa8, 0x9c, 0x4e, 0xda, 0xd9, 0x31, 0x20, 0xd6, 0x2b, 0x5c, 0xfe, 0x37, 0x5e, 0xd7, 0xb8, 0x94, 0x32, 0x9d, 0x68, 0x49, 0xc7, 0x12, 0x91, 0x94, 0x64, 0xb4, 0x03, 0xf0, 0xf7, 0x2c, 0x85, 0xd2, 0xab, 0xb9, 0xf0, 0xba, 0xc6, 0xa3, 0x94, 0xeb, 0xc4, 0xfd, 0x1d, 0xfd, 0x22, 0x19, 0x49, 0x6b, 0x39, 0xdc, 0xbb, 0x9a, 0x3d, 0x49, 0x9b, 0x98, 0xb7, 0x09, 0x17, 0xd2, 0x6a, 0x18, 0xb4, 0x90, 0x74, 0x1a, 0x35, 0x10, 0xa2, 0x05, 0x50, 0x28, 0x01, 0x14, 0x0a, 0xaa, 0x1e, 0x44, 0x3a, 0xfe, 0xb4, 0x67, 0x0a, 0x2e, 0x8b, 0x3c, 0x92, 0x4a, 0x4f, 0xe8, 0xf2, 0x7a, 0x5d, 0x1a, 0x9c, 0x01, 0x1e, 0x4a, 0xce, 0x9c, 0x03, 0xb0, 0x9a, 0x4b, 0xbc, 0x80, 0xf2, 0x33, 0x51, 0xa8, 0x52, 0x26, 0x85, 0x84, 0x17, 0x38, 0x67, 0xf2, 0xa8, 0x51, 0x5b, 0x68, 0xb1, 0x14, 0x6a, 0x8d, 0xec, 0x8d, 0xd9, 0xb9, 0x8e, 0xc6, 0xd6, 0x36, 0x9f, 0x31, 0x5c, 0x52, 0x12, 0x36, 0xfa, 0xda, 0x5a, 0x1b, 0x1d, 0xb9, 0xd9, 0x54, 0x27, 0x3b, 0x72, 0xe5, 0x1f, 0x12, 0x99, 0x4c, 0x42, 0xc9, 0xc1, 0xd1, 0x2a, 0x47, 0x54, 0x28, 0x91, 0xd0, 0xe0, 0x43, 0x83, 0x81, 0xd5, 0xd3, 0x12, 0x89, 0x30, 0xea, 0xa8, 0x9a, 0x3c, 0xd6, 0x38, 0xaa, 0x4a, 0x1a, 0xf6, 0x93, 0x3c, 0x7e, 0xb6, 0x8f, 0x64, 0x78, 0x2a, 0x11, 0xc4, 0xe1, 0x4a, 0x31, 0x84, 0x2f, 0x1a, 0xe5, 0xf6, 0xa5, 0x00, 0xa7, 0xa3, 0xe0, 0xf3, 0x07, 0x9b, 0x84, 0xc8, 0x17, 0xb2, 0x17, 0x99, 0x36, 0x87, 0x90, 0xec, 0x37, 0x42, 0x36, 0xc7, 0x63, 0x70, 0xd4, 0x4a, 0xb5, 0xcf, 0xe7, 0xc2, 0x59, 0x01, 0x80, 0x23, 0x1d, 0xe7, 0x9e, 0x9e, 0x01, 0x2e, 0xb9, 0x5b, 0x8e, 0x10, 0xe1, 0x8c, 0x33, 0x71, 0x90, 0x47, 0xc1, 0x2d, 0xe9, 0x29, 0xa4, 0xe6, 0x94, 0x9a, 0x04, 0xb8, 0x65, 0xe6, 0xc9, 0xb1, 0x37, 0x02, 0x45, 0x95, 0xb3, 0x50, 0x20, 0x91, 0xd2, 0xac, 0x1e, 0xcd, 0x11, 0x7c, 0x48, 0x4b, 0x25, 0x82, 0x42, 0x67, 0x15, 0x59, 0x3d, 0xf3, 0x54, 0x21, 0x6f, 0xb3, 0x64, 0x7c, 0x0f, 0xb5, 0x8b, 0x79, 0x8a, 0x90, 0x43, 0xac, 0x0d, 0x65, 0xab, 0x6e, 0x00, 0x98, 0xe6, 0x80, 0x14, 0x1e, 0x2b, 0xa2, 0x10, 0x90, 0x34, 0x75, 0x55, 0x41, 0x31, 0x64, 0xc3, 0x25, 0x17, 0xa5, 0xeb, 0x8a, 0xad, 0x02, 0xcd, 0x73, 0x3a, 0x2b, 0xca, 0x8a, 0xe3, 0x05, 0x79, 0x06, 0xbd, 0x46, 0x85, 0x4b, 0x1e, 0xa3, 0x50, 0x8d, 0x54, 0x0a, 0x10, 0x19, 0x98, 0x9a, 0x22, 0x84, 0x37, 0x81, 0xa0, 0xae, 0x8a, 0x3c, 0xe4, 0x4d, 0x2e, 0xf1, 0x09, 0x99, 0x21, 0x79, 0xf5, 0x8a, 0x1d, 0xe5, 0x91, 0x70, 0x62, 0xe9, 0xe6, 0xb2, 0xb2, 0x08, 0x69, 0x14, 0xa8, 0x64, 0x0a, 0x5f, 0xbc, 0x31, 0x92, 0x3b, 0xab, 0xc0, 0x94, 0xd7, 0x36, 0xb8, 0x62, 0xb0, 0x2d, 0xaf, 0xa5, 0x33, 0xaf, 0x2c, 0x32, 0x70, 0x7b, 0xdf, 0x8a, 0xe7, 0xca, 0x72, 0x04, 0x59, 0x6a, 0x75, 0xa0, 0xb8, 0xb3, 0xa2, 0x7f, 0x45, 0x41, 0xfb, 0xb2, 0xd5, 0xcb, 0xda, 0x0b, 0xe6, 0xf4, 0xe6, 0x57, 0xe4, 0x2f, 0x3c, 0x38, 0x6f, 0xf0, 0xe1, 0x12, 0xf2, 0xbd, 0xbc, 0xda, 0x78, 0xc4, 0x6a, 0xa8, 0x0f, 0x47, 0x1a, 0xe2, 0xf9, 0x16, 0x4b, 0x2d, 0xf8, 0x50, 0x26, 0xd2, 0x19, 0x74, 0x7a, 0x5f, 0xa1, 0xd5, 0x1d, 0xcf, 0x0d, 0xe4, 0xc4, 0xea, 0x17, 0x94, 0xb7, 0xef, 0xf4, 0x7a, 0x96, 0xd4, 0xd6, 0x6f, 0x9a, 0x57, 0x50, 0x17, 0x6b, 0x92, 0xc9, 0xcd, 0x0e, 0x53, 0x77, 0x7d, 0x38, 0x51, 0x10, 0x08, 0x45, 0xea, 0xfa, 0x92, 0x6d, 0x5b, 0xdd, 0xde, 0x15, 0x4d, 0x65, 0xc3, 0x1d, 0xf9, 0xc9, 0x22, 0x08, 0x1f, 0xb5, 0xe3, 0x51, 0xa6, 0x06, 0xfb, 0x08, 0x7a, 0x92, 0xce, 0x94, 0x7d, 0x7d, 0x04, 0xe9, 0x5a, 0x6e, 0x20, 0xd3, 0xee, 0x6c, 0x03, 0x44, 0xb3, 0x1f, 0xa9, 0xab, 0x14, 0x38, 0x53, 0xa8, 0x2b, 0x65, 0x5e, 0x4f, 0x8b, 0x68, 0x0e, 0xa6, 0xe6, 0x17, 0x67, 0xd8, 0x7f, 0x9d, 0x59, 0xfe, 0xd8, 0x86, 0x44, 0x62, 0xc3, 0x63, 0xcb, 0xa9, 0x32, 0xfa, 0xb3, 0x2f, 0x92, 0xcc, 0x6b, 0x5f, 0x24, 0xa9, 0xee, 0x8a, 0x55, 0x47, 0x3a, 0xba, 0x8f, 0x0e, 0x21, 0xbe, 0xe2, 0x66, 0xf2, 0x05, 0x5a, 0x32, 0x35, 0xdf, 0xf6, 0x94, 0xfa, 0x43, 0x61, 0x7b, 0xba, 0x4a, 0xd0, 0xd4, 0x04, 0x66, 0x11, 0x5a, 0x72, 0x58, 0x6d, 0x14, 0x7c, 0x20, 0xd3, 0x08, 0x04, 0x6a, 0xc9, 0xcf, 0x78, 0x26, 0xed, 0x3d, 0x60, 0xdf, 0x36, 0xf2, 0x05, 0x8d, 0x02, 0x9c, 0x31, 0x17, 0x18, 0x0c, 0x05, 0x66, 0xb6, 0x4b, 0xae, 0x25, 0xff, 0x84, 0xd9, 0x66, 0x00, 0x61, 0x9d, 0x62, 0x08, 0x13, 0x3a, 0x97, 0x04, 0x60, 0xc0, 0x28, 0x72, 0xdc, 0x5c, 0x87, 0xb8, 0x5d, 0x12, 0x82, 0x04, 0x03, 0x3f, 0x18, 0x6a, 0x80, 0x37, 0x59, 0x9f, 0x72, 0xfd, 0xe5, 0x72, 0xc8, 0xff, 0x9d, 0xa8, 0x03, 0x5f, 0xf3, 0x36, 0x2c, 0xaf, 0x2a, 0x5c, 0xba, 0xb0, 0xcb, 0x35, 0xf9, 0x82, 0x5c, 0xd3, 0x51, 0x5b, 0xb7, 0xae, 0x33, 0xdb, 0xe6, 0xb5, 0xb5, 0xc3, 0x2f, 0x1d, 0x39, 0x76, 0x8f, 0x1d, 0xe7, 0x5d, 0xa0, 0xee, 0x01, 0x2e, 0x9c, 0xaf, 0xc3, 0x9e, 0xb4, 0x60, 0xc7, 0x11, 0xe4, 0xcb, 0xcb, 0x05, 0xec, 0xa3, 0xdc, 0xef, 0xf0, 0x3b, 0x0a, 0x25, 0xc3, 0xe5, 0xa0, 0x94, 0xd8, 0x3f, 0x1e, 0x62, 0x36, 0x25, 0x70, 0xa9, 0x18, 0x8b, 0xd3, 0x62, 0xe3, 0xa9, 0x98, 0x53, 0xec, 0x7f, 0x9f, 0x42, 0x18, 0xe3, 0x14, 0x70, 0x60, 0xf9, 0x94, 0xba, 0x87, 0xdc, 0x84, 0x63, 0x25, 0x16, 0x62, 0xf9, 0x61, 0x21, 0x98, 0x8b, 0xe1, 0x7e, 0xce, 0xf8, 0xc7, 0xcc, 0x7e, 0xe6, 0x0c, 0x21, 0x26, 0x1a, 0x88, 0x95, 0x49, 0x59, 0xc8, 0x4a, 0x32, 0x74, 0x43, 0x85, 0x52, 0x4c, 0x13, 0x58, 0x63, 0x8f, 0xd5, 0xd1, 0xe9, 0x74, 0xec, 0xab, 0x90, 0xbe, 0x18, 0xf4, 0xa2, 0x11, 0x2c, 0xc5, 0xe9, 0xe0, 0xb9, 0xaf, 0x00, 0x17, 0x6c, 0x9c, 0xda, 0x86, 0x48, 0x35, 0xc1, 0x44, 0x7d, 0x18, 0xa9, 0x2d, 0x25, 0xb5, 0xd5, 0x85, 0x05, 0x3e, 0x8f, 0xc7, 0xe5, 0xc3, 0x24, 0x28, 0x95, 0x60, 0x18, 0x05, 0x87, 0xa6, 0x4a, 0x96, 0x65, 0xea, 0xaa, 0xe7, 0x4c, 0x24, 0x9d, 0xe3, 0xf3, 0x7c, 0x13, 0x49, 0x8b, 0xf5, 0x48, 0x70, 0x45, 0xb2, 0x0c, 0xf2, 0x76, 0xfa, 0xf5, 0x40, 0x7f, 0x76, 0x53, 0x7f, 0x64, 0xe5, 0x93, 0x55, 0xa4, 0xf0, 0x5b, 0x76, 0x8f, 0x7e, 0x6f, 0xdf, 0xa7, 0xb7, 0xb7, 0xdf, 0xbb, 0xb9, 0x29, 0xa7, 0xe3, 0x86, 0xaa, 0xd2, 0x78, 0x6c, 0xe9, 0xed, 0xbd, 0xed, 0x7b, 0x17, 0xc7, 0xe9, 0xcd, 0x1e, 0xa5, 0xc0, 0x92, 0x5b, 0x15, 0xca, 0x6b, 0x89, 0x59, 0x14, 0x8e, 0x42, 0x37, 0xb8, 0xdb, 0x13, 0xe6, 0xc3, 0xde, 0x36, 0x81, 0x86, 0x2a, 0xf2, 0xe5, 0xf8, 0x89, 0x79, 0x15, 0xfd, 0x55, 0xce, 0xa6, 0x04, 0xfb, 0x47, 0x79, 0xd6, 0xbe, 0x79, 0x83, 0xf1, 0xe5, 0x77, 0xf7, 0xd5, 0x6e, 0x5d, 0xd1, 0x65, 0x6d, 0xb9, 0xbd, 0x79, 0xd6, 0xae, 0xc5, 0x45, 0x91, 0xb9, 0x23, 0xe5, 0x06, 0x41, 0xc0, 0x18, 0x0b, 0x99, 0xed, 0xf1, 0xc6, 0xa0, 0x23, 0x91, 0xa8, 0xf4, 0xb0, 0xfd, 0x55, 0x77, 0xcc, 0xd2, 0x64, 0x6d, 0xfc, 0x5d, 0x3f, 0x5e, 0xbf, 0xc1, 0x71, 0x05, 0x2f, 0xca, 0xcc, 0x81, 0x78, 0xa3, 0x8f, 0x98, 0x95, 0xac, 0xeb, 0x03, 0x84, 0xb0, 0x15, 0x02, 0x67, 0x1e, 0xa0, 0xf9, 0x10, 0x69, 0xf0, 0x08, 0x4a, 0xc8, 0x83, 0x94, 0x19, 0xa5, 0x65, 0x10, 0x12, 0x03, 0x24, 0xaa, 0xe5, 0x0c, 0xa5, 0x4d, 0x3e, 0x64, 0x6c, 0x18, 0x86, 0xee, 0x4e, 0x29, 0xa0, 0x68, 0xa6, 0x6d, 0xc1, 0x7c, 0x77, 0x30, 0xe8, 0xf7, 0xfb, 0x03, 0x5e, 0x07, 0x46, 0x93, 0x69, 0x0f, 0x60, 0x2e, 0x22, 0x8e, 0x37, 0x91, 0x60, 0x82, 0xfb, 0x5d, 0xc5, 0xb9, 0x4c, 0xc4, 0x74, 0x36, 0x90, 0x09, 0x18, 0xac, 0xa0, 0xe1, 0x92, 0x65, 0xaa, 0x17, 0x4e, 0xad, 0x07, 0x49, 0x67, 0x79, 0x83, 0x0d, 0x9b, 0xe6, 0xe5, 0x27, 0x2a, 0xac, 0xe5, 0xf1, 0x3c, 0xc5, 0x71, 0x35, 0x30, 0x17, 0x58, 0x73, 0xe7, 0x24, 0xbd, 0xdd, 0x27, 0x7e, 0xb4, 0x79, 0xef, 0x9f, 0x9e, 0x1a, 0x58, 0x79, 0xfe, 0xf3, 0xc3, 0xc7, 0xff, 0xd4, 0xae, 0xb3, 0x0b, 0x4c, 0x2b, 0xd9, 0x8f, 0xce, 0x9c, 0x65, 0x7f, 0xf6, 0xcb, 0xad, 0xf1, 0xa5, 0x87, 0xcf, 0xbc, 0xbd, 0x7a, 0xe1, 0x73, 0xf7, 0xed, 0x5d, 0x14, 0xab, 0x18, 0xbe, 0x73, 0x4e, 0xd3, 0xa0, 0xbc, 0x64, 0x7f, 0x6b, 0xd9, 0xfc, 0x32, 0x4b, 0xe5, 0xce, 0xd7, 0xb6, 0xd7, 0x2e, 0xcf, 0xa5, 0xf2, 0x05, 0xe5, 0x03, 0x3b, 0xab, 0xeb, 0x3a, 0xd5, 0x79, 0xb3, 0x2b, 0xd4, 0xb3, 0x7a, 0x0b, 0xd5, 0xf1, 0xc1, 0x5b, 0xbb, 0x0e, 0xfc, 0xe0, 0x40, 0xed, 0xe0, 0x73, 0x9f, 0x1e, 0x7e, 0x7c, 0x9c, 0x78, 0xaa, 0x3b, 0x99, 0x2f, 0x15, 0x69, 0x1a, 0xea, 0xba, 0xd6, 0xfe, 0x18, 0x64, 0x7f, 0x7b, 0xf8, 0xbb, 0x67, 0xef, 0x58, 0x5e, 0x1c, 0x68, 0x5b, 0x77, 0xec, 0xfc, 0xaa, 0xe1, 0x6f, 0xec, 0x6f, 0x69, 0x68, 0x6e, 0xad, 0x0e, 0x36, 0x0e, 0x96, 0xce, 0x79, 0xfa, 0xd6, 0x79, 0x3a, 0x0d, 0xe7, 0x83, 0xd2, 0x3f, 0xfe, 0x09, 0xcf, 0x08, 0xf1, 0x70, 0x88, 0xa8, 0x26, 0x16, 0x11, 0x7b, 0x92, 0x32, 0x0d, 0xc2, 0xbb, 0x80, 0x46, 0x55, 0x7f, 0x68, 0xea, 0xaa, 0xdc, 0xaf, 0x69, 0x7b, 0x00, 0x0d, 0x61, 0x9a, 0x86, 0xac, 0x8f, 0x40, 0x90, 0xce, 0x56, 0xcc, 0xe7, 0x63, 0x9b, 0xd0, 0x30, 0x6f, 0x4a, 0xee, 0xd7, 0xaf, 0x6c, 0xbf, 0x16, 0xd9, 0x90, 0x24, 0xe1, 0xb0, 0x3f, 0xe8, 0x77, 0x79, 0xfc, 0x5e, 0x21, 0xda, 0x0c, 0xb4, 0xcc, 0x53, 0xd7, 0x93, 0x3f, 0x35, 0xe3, 0x13, 0xda, 0x1c, 0x0f, 0xde, 0x80, 0x89, 0x20, 0xce, 0x1c, 0x3a, 0xae, 0xb1, 0xc2, 0x5f, 0xb0, 0xfe, 0x10, 0x5e, 0xcb, 0x68, 0x3a, 0x26, 0x90, 0x33, 0x55, 0xc5, 0x8f, 0xad, 0x1f, 0x78, 0x70, 0x34, 0x51, 0xbb, 0xf9, 0xf4, 0x92, 0xd5, 0x27, 0x8b, 0x22, 0x8c, 0x52, 0x2a, 0xb7, 0xe5, 0x35, 0x14, 0xd7, 0x0c, 0x37, 0xfb, 0x03, 0xb3, 0x96, 0x96, 0x46, 0x6a, 0x22, 0x81, 0x2b, 0xd5, 0x85, 0x8b, 0xf7, 0x3d, 0xf4, 0xca, 0xe0, 0x71, 0x20, 0x7e, 0x6b, 0xdd, 0xba, 0xb7, 0xd8, 0xcf, 0x8e, 0x0f, 0xbe, 0xf2, 0xd0, 0xbe, 0xc5, 0x85, 0xab, 0xc3, 0xb3, 0x47, 0x0f, 0x3d, 0xd0, 0xdd, 0xfd, 0xe0, 0xad, 0xa3, 0xed, 0xe1, 0x70, 0xfb, 0xe8, 0xad, 0x0f, 0x76, 0x77, 0x3f, 0x70, 0x68, 0x74, 0x76, 0x18, 0xfc, 0x1e, 0x08, 0x98, 0xfa, 0xea, 0x59, 0xb5, 0x5b, 0x1e, 0x5f, 0x3a, 0xf8, 0xc4, 0xa6, 0x9a, 0xba, 0xa2, 0x06, 0x9e, 0x54, 0x67, 0xd4, 0x85, 0xdb, 0xd7, 0x35, 0x34, 0xad, 0x9f, 0x1d, 0x50, 0xe8, 0x8d, 0xff, 0x1c, 0x7c, 0x15, 0x75, 0x81, 0x7b, 0x3b, 0xce, 0xfe, 0xeb, 0xad, 0x75, 0x85, 0x8b, 0xf7, 0x3e, 0xfc, 0xca, 0xe0, 0xbc, 0x87, 0x6f, 0x5b, 0xd7, 0x91, 0x9d, 0xdd, 0xb1, 0xee, 0xb6, 0x87, 0x7a, 0x7a, 0x1e, 0x39, 0xbc, 0xbe, 0x33, 0x3b, 0xbb, 0x73, 0xfd, 0xe1, 0x47, 0xd0, 0x5e, 0xd0, 0x99, 0xbd, 0x40, 0x71, 0x54, 0x71, 0xa2, 0x8d, 0xb8, 0x9b, 0x5b, 0xfd, 0x7c, 0xb8, 0x11, 0xa8, 0x14, 0xc1, 0x48, 0xc6, 0x0e, 0x80, 0x75, 0xff, 0x34, 0xaa, 0x5c, 0xc3, 0xe3, 0xf1, 0x7b, 0x25, 0x80, 0xcf, 0x5f, 0xd5, 0x24, 0x86, 0xac, 0x00, 0x4e, 0x39, 0x3a, 0x2c, 0x80, 0x3b, 0xf0, 0x1f, 0x3e, 0xb3, 0x56, 0x00, 0x77, 0xc1, 0x53, 0x5c, 0x64, 0x31, 0xa3, 0x00, 0x81, 0xea, 0xca, 0xa2, 0xb6, 0xe2, 0xb6, 0x50, 0xc0, 0x1c, 0xb7, 0xc4, 0xa7, 0x86, 0x6b, 0x49, 0xd3, 0x8a, 0xec, 0xa9, 0xf6, 0x04, 0xec, 0x3f, 0x00, 0x26, 0x2d, 0x3d, 0x4a, 0xd5, 0xa6, 0x9e, 0xb2, 0x39, 0xe8, 0xbc, 0x50, 0xc9, 0xb2, 0xd6, 0x6c, 0x95, 0x32, 0x58, 0x1f, 0xdb, 0xba, 0xb5, 0x77, 0x4f, 0x77, 0x30, 0x38, 0x6f, 0xdf, 0xc2, 0x8d, 0x4f, 0x01, 0xcd, 0xd5, 0x0b, 0xcc, 0x2d, 0xfc, 0xf4, 0xfd, 0xc0, 0xbb, 0x44, 0x7e, 0xc7, 0x90, 0x5d, 0xe1, 0x76, 0x97, 0x04, 0x74, 0xec, 0x2b, 0xe0, 0x9d, 0x70, 0xeb, 0xca, 0xf2, 0xd2, 0x15, 0xed, 0xb9, 0xec, 0x5b, 0xd4, 0xb6, 0xcb, 0x6f, 0x5e, 0xb5, 0xae, 0x3d, 0xdc, 0x7a, 0x33, 0xdd, 0xd3, 0xf7, 0x81, 0xdb, 0x9d, 0x29, 0x6b, 0xee, 0x24, 0x2a, 0x88, 0xb9, 0xc4, 0x03, 0x2f, 0xaa, 0x01, 0x9d, 0xc9, 0x18, 0x5f, 0x80, 0xf2, 0xa4, 0xf0, 0x68, 0x8a, 0x37, 0x22, 0x4e, 0xaf, 0xa3, 0x08, 0xe5, 0xeb, 0xa2, 0x50, 0x61, 0x72, 0x3e, 0x5f, 0xd0, 0x2b, 0x05, 0x28, 0xc7, 0xab, 0x04, 0x32, 0x60, 0xc2, 0x5e, 0x42, 0x28, 0x1c, 0x16, 0xc2, 0xb5, 0xff, 0x4f, 0x1f, 0x5a, 0x2b, 0x84, 0x8b, 0xef, 0x4b, 0x26, 0xdc, 0x2e, 0xb4, 0xf8, 0x8d, 0x0d, 0x89, 0xb9, 0xc9, 0xb9, 0x05, 0x79, 0xae, 0x0a, 0x77, 0x85, 0xc3, 0x6e, 0xb3, 0x18, 0xf4, 0x52, 0xa7, 0xcc, 0xc9, 0x2d, 0xbf, 0x6c, 0xfa, 0xf2, 0xeb, 0x38, 0xc6, 0x06, 0x95, 0x39, 0xcc, 0xb8, 0x80, 0x5e, 0xc7, 0x3e, 0x38, 0x91, 0x05, 0x4e, 0x19, 0xa8, 0x8e, 0x6c, 0xdd, 0xfa, 0xcc, 0x13, 0xcb, 0x96, 0xab, 0x4c, 0x92, 0x9f, 0x3f, 0xec, 0x56, 0xbc, 0x22, 0x34, 0x18, 0x67, 0x3d, 0x05, 0x0a, 0xfe, 0xb3, 0xed, 0xf8, 0x93, 0xc6, 0x5b, 0xec, 0x76, 0x17, 0xba, 0x94, 0xec, 0x2f, 0xc1, 0xe9, 0x1d, 0x5b, 0x7a, 0xf7, 0x19, 0xc5, 0x76, 0x1d, 0xfb, 0x92, 0x19, 0xb8, 0x64, 0x32, 0xf6, 0xd1, 0xff, 0xab, 0x5d, 0x41, 0x78, 0xbe, 0x9b, 0x6a, 0x4c, 0xd5, 0xc7, 0xf0, 0x22, 0xfe, 0xd0, 0xa6, 0xd5, 0x48, 0xc4, 0x90, 0xd0, 0x21, 0xcf, 0x58, 0x80, 0x3c, 0x63, 0x31, 0xdf, 0x43, 0xf4, 0x08, 0x05, 0x7c, 0x8a, 0x20, 0x06, 0xb8, 0xb2, 0xd0, 0x99, 0x4b, 0xe4, 0x5f, 0x2e, 0xf0, 0x0a, 0x3c, 0x4a, 0x4d, 0x14, 0xf9, 0x86, 0x40, 0x4e, 0xd8, 0x8c, 0xb4, 0x47, 0x88, 0x1d, 0x72, 0x79, 0x50, 0x51, 0x71, 0xcc, 0xaf, 0xc0, 0x25, 0xd3, 0xa7, 0x5c, 0x22, 0xd5, 0x38, 0xfa, 0x19, 0x99, 0xf0, 0xbb, 0xc1, 0x87, 0x67, 0x0f, 0x9e, 0xa5, 0x3f, 0xd9, 0x28, 0x3a, 0xcb, 0x37, 0xc8, 0x35, 0x4e, 0xe9, 0x59, 0xe1, 0xc6, 0xf5, 0x2a, 0xa5, 0xf8, 0x7e, 0xa9, 0x90, 0x11, 0xf3, 0x1f, 0x10, 0x2b, 0x95, 0xcc, 0xa9, 0x51, 0xd6, 0x0e, 0x7e, 0x3d, 0x3a, 0xf6, 0x75, 0xd0, 0xdb, 0xc1, 0xbe, 0x24, 0x97, 0x5b, 0x41, 0x5d, 0x3b, 0xfb, 0x28, 0xd9, 0xc1, 0x97, 0xb1, 0xbf, 0x34, 0xda, 0xe5, 0x16, 0x88, 0xab, 0x64, 0x7c, 0x34, 0x87, 0x2d, 0x54, 0x0d, 0x93, 0x60, 0x9e, 0x24, 0xa2, 0xc4, 0x1c, 0xe2, 0x63, 0xce, 0x82, 0xac, 0x8a, 0x42, 0x0e, 0xb6, 0xb5, 0xba, 0xaa, 0xa4, 0x98, 0x41, 0x5c, 0x0c, 0x9f, 0x46, 0x65, 0x8c, 0xd1, 0x8f, 0xc2, 0xa9, 0x3f, 0xf6, 0x70, 0xcd, 0xc3, 0x19, 0x36, 0x98, 0x0f, 0x31, 0x32, 0x9f, 0x1c, 0x21, 0x68, 0x01, 0x8f, 0x84, 0x9c, 0xf0, 0x42, 0xcc, 0x09, 0x8b, 0x45, 0x42, 0x0a, 0x23, 0x53, 0x8a, 0x1a, 0xc2, 0x46, 0xea, 0x1c, 0xf8, 0x4c, 0xe1, 0xd5, 0xcf, 0xf0, 0x49, 0xba, 0x1b, 0x3d, 0x47, 0x0a, 0xe1, 0x23, 0x24, 0xca, 0x2e, 0x96, 0x61, 0xa4, 0xd3, 0xcf, 0x26, 0x63, 0xd3, 0x39, 0xee, 0xeb, 0x79, 0x1a, 0xa0, 0xa8, 0x1a, 0x4d, 0x2c, 0x16, 0x9b, 0x13, 0xeb, 0xaa, 0xaf, 0xd3, 0xb9, 0x35, 0xd9, 0x2e, 0x6f, 0xb6, 0x5b, 0x29, 0x41, 0x58, 0xfc, 0xea, 0x1a, 0xbf, 0x93, 0xbd, 0x22, 0x26, 0xd7, 0xf8, 0xe5, 0xb6, 0x84, 0x87, 0xfd, 0x54, 0x27, 0xb6, 0x84, 0x73, 0xb0, 0x43, 0x7b, 0x02, 0xfe, 0x25, 0x52, 0x0b, 0xb5, 0x16, 0x9f, 0xc9, 0x93, 0xe3, 0x2f, 0xab, 0x2f, 0xf3, 0x9b, 0x63, 0x6d, 0x05, 0x96, 0xe2, 0xc2, 0x1c, 0x95, 0x5d, 0xa2, 0x94, 0x64, 0x39, 0xc2, 0x36, 0x28, 0x6a, 0xa3, 0xdf, 0x4d, 0x91, 0xe6, 0xbc, 0xd8, 0x42, 0x3b, 0x95, 0x77, 0x93, 0xf0, 0xac, 0xcc, 0xa1, 0x91, 0x1b, 0xf8, 0xcf, 0x08, 0x37, 0xa1, 0xcd, 0x7b, 0x80, 0x27, 0x66, 0xe8, 0xd4, 0xee, 0x65, 0xab, 0x25, 0x72, 0xa9, 0xdb, 0x92, 0x65, 0xd4, 0xea, 0x7d, 0x89, 0x48, 0xb0, 0x21, 0x6a, 0x13, 0x6b, 0x2d, 0xaa, 0x0a, 0xa5, 0x42, 0xa3, 0x08, 0xdb, 0xcd, 0x16, 0x8d, 0xd6, 0x57, 0x19, 0x77, 0x26, 0xf2, 0x21, 0x57, 0x97, 0x8f, 0x76, 0xb8, 0x93, 0xfd, 0x86, 0x55, 0x2e, 0x07, 0xb5, 0xb3, 0xd3, 0x3b, 0x2c, 0xb3, 0x40, 0x2e, 0x3d, 0xb5, 0xc5, 0x70, 0x8f, 0xbb, 0x28, 0x3b, 0xf9, 0x67, 0xe6, 0x69, 0x5c, 0x27, 0xd0, 0x8c, 0x14, 0x52, 0x53, 0x2a, 0xa5, 0xad, 0xa2, 0x9a, 0xd3, 0x65, 0x02, 0xaf, 0xae, 0x22, 0x36, 0x95, 0xc9, 0xe8, 0x8a, 0xf4, 0x6c, 0xac, 0xab, 0xdb, 0x34, 0x3f, 0x12, 0x99, 0xbf, 0xa9, 0xae, 0x6e, 0x63, 0x4f, 0xe4, 0x90, 0xa5, 0xb0, 0x2e, 0x10, 0xa8, 0x47, 0x22, 0x58, 0x7d, 0x20, 0x50, 0x57, 0x68, 0xa1, 0x97, 0xd5, 0xdc, 0x34, 0x2f, 0x12, 0x99, 0x77, 0x53, 0x4d, 0xf5, 0x4d, 0x3d, 0x85, 0x85, 0x3d, 0x37, 0x55, 0xc1, 0xbb, 0x56, 0x2b, 0xba, 0x5b, 0x1f, 0xb5, 0x5a, 0xa3, 0xf5, 0x68, 0x3c, 0xdb, 0x89, 0xff, 0xa2, 0x85, 0xf4, 0x10, 0x1c, 0x4f, 0x1c, 0x79, 0x0c, 0xe5, 0x66, 0xfb, 0x3c, 0x70, 0x3b, 0x63, 0x79, 0x6e, 0x2b, 0xc4, 0x6a, 0x71, 0x8b, 0x49, 0x4b, 0x93, 0x04, 0xd5, 0x98, 0xe3, 0x25, 0xa9, 0x06, 0x33, 0x20, 0xeb, 0x09, 0xce, 0x60, 0x9b, 0xce, 0x07, 0xb9, 0x06, 0x34, 0x87, 0x02, 0x3e, 0x34, 0x60, 0x7d, 0x08, 0x4c, 0x1b, 0x20, 0xf0, 0x71, 0x12, 0x24, 0xdf, 0x87, 0x39, 0xc6, 0x58, 0xdc, 0x17, 0xe7, 0x4a, 0xae, 0xeb, 0xe3, 0x7a, 0x2c, 0x5c, 0xf1, 0xf8, 0x7a, 0xb0, 0xda, 0x59, 0xd2, 0x16, 0xce, 0x6e, 0x2b, 0x75, 0x3a, 0x4b, 0xdb, 0xb2, 0xc3, 0x6d, 0x25, 0x4e, 0xb2, 0xf8, 0x58, 0x78, 0xa8, 0xb8, 0x68, 0x69, 0xe8, 0xc4, 0x92, 0x63, 0x16, 0xbb, 0xcd, 0x7c, 0x7c, 0x60, 0xd2, 0xb5, 0xc3, 0x66, 0x3e, 0x46, 0x3e, 0x10, 0x6e, 0x41, 0xcd, 0x5b, 0xd2, 0x8f, 0xb1, 0xca, 0xe3, 0xe8, 0xc6, 0xf1, 0xa5, 0xc7, 0x42, 0x93, 0x1b, 0x1e, 0x5f, 0x7a, 0x4f, 0x78, 0x59, 0x71, 0x7c, 0x69, 0xe8, 0x78, 0x2a, 0xff, 0xe2, 0x3f, 0xe8, 0xe5, 0xf0, 0x6c, 0xd5, 0x13, 0x0b, 0x92, 0x3d, 0x90, 0x5f, 0xa7, 0xa2, 0x40, 0x20, 0x44, 0xb5, 0x6a, 0x29, 0x24, 0x40, 0x8c, 0x10, 0x42, 0xbe, 0x80, 0x2f, 0x14, 0x8c, 0x40, 0x2e, 0x46, 0xca, 0xc5, 0xfb, 0x43, 0x74, 0x2b, 0x06, 0x7c, 0x21, 0x8f, 0x9f, 0x49, 0x40, 0x0f, 0x61, 0x5f, 0x20, 0x10, 0xf5, 0x12, 0x22, 0xd1, 0xb0, 0xa8, 0xb9, 0xbe, 0x2e, 0x99, 0xa8, 0x28, 0x47, 0x02, 0xa5, 0xd3, 0xe5, 0x71, 0xa9, 0x5c, 0x1e, 0xb7, 0x42, 0x06, 0xa1, 0x58, 0x1d, 0xc1, 0x72, 0xf1, 0x04, 0x03, 0xc2, 0xe3, 0x63, 0x89, 0xda, 0x01, 0xf9, 0x69, 0x33, 0xd0, 0x73, 0xdf, 0xb1, 0x9b, 0x0b, 0x8d, 0x2b, 0x8f, 0xa6, 0xe2, 0x39, 0x5d, 0xd1, 0x88, 0xb7, 0x10, 0xad, 0x91, 0x2b, 0xeb, 0x88, 0x76, 0xf1, 0xe1, 0xf3, 0xc3, 0xfb, 0x5f, 0xab, 0xad, 0x16, 0xa8, 0x14, 0x4a, 0x4f, 0xd9, 0x82, 0x59, 0xb7, 0xbc, 0x64, 0x27, 0xb3, 0xac, 0x46, 0x56, 0x0d, 0x31, 0x18, 0x78, 0x1b, 0x7e, 0xd9, 0x6c, 0xfe, 0xc6, 0xa1, 0x68, 0x6b, 0x49, 0xc0, 0x66, 0x90, 0x8a, 0xa4, 0x82, 0xce, 0xfa, 0x1f, 0x1f, 0x59, 0xff, 0xf2, 0xc1, 0x6e, 0xe5, 0xa1, 0x9d, 0xf0, 0xfe, 0x2e, 0xea, 0x96, 0xad, 0xef, 0xdc, 0xd6, 0x3c, 0x34, 0xaf, 0x49, 0x24, 0xf3, 0x84, 0x3d, 0xdf, 0x3c, 0x5f, 0xae, 0xb6, 0xab, 0xbd, 0x1a, 0x08, 0x82, 0xf0, 0xcb, 0xc5, 0x37, 0x35, 0x36, 0xb7, 0x4d, 0xc3, 0x13, 0xb4, 0x2e, 0x1b, 0x6d, 0xbb, 0xf3, 0xc7, 0x3b, 0x47, 0x64, 0x66, 0xb9, 0xc6, 0xab, 0x46, 0xeb, 0x23, 0x87, 0xeb, 0x63, 0xa4, 0x87, 0x89, 0x5a, 0x6e, 0x7d, 0x28, 0x78, 0x8a, 0xd0, 0xfa, 0xd4, 0x42, 0x49, 0x6b, 0xd6, 0xf4, 0x45, 0x4a, 0x73, 0x69, 0xc8, 0x7a, 0x3d, 0x75, 0xad, 0x96, 0x4e, 0x5a, 0xa1, 0x8a, 0xf2, 0xb2, 0xd2, 0x78, 0x34, 0x2f, 0x27, 0xb3, 0x3e, 0xd2, 0x2f, 0x5b, 0x1f, 0x9e, 0x66, 0xda, 0xfa, 0x90, 0x33, 0xaf, 0x8f, 0xfc, 0x80, 0xba, 0x67, 0xef, 0xb3, 0xab, 0xb6, 0x7c, 0xbd, 0x26, 0x5f, 0x62, 0x53, 0x2a, 0x03, 0xa5, 0xed, 0x45, 0x7b, 0x4f, 0xdb, 0xc0, 0x6f, 0xed, 0x59, 0x6c, 0x3d, 0x64, 0xdf, 0x4f, 0xc2, 0xcf, 0x27, 0x8d, 0x0f, 0xec, 0xac, 0xe9, 0xab, 0x0a, 0xa9, 0x14, 0x7a, 0x59, 0x41, 0xd5, 0x9b, 0xfb, 0x56, 0x3d, 0xbd, 0xbd, 0x43, 0xb9, 0x67, 0x33, 0x5c, 0x9c, 0x2d, 0xe4, 0x77, 0x17, 0xdf, 0xbb, 0xba, 0xa4, 0xa5, 0xa6, 0x44, 0xaa, 0xce, 0xb2, 0x64, 0xed, 0xdb, 0xe6, 0xd1, 0x38, 0x14, 0x76, 0xa5, 0xcc, 0x28, 0x85, 0x5f, 0xf6, 0x1f, 0xb4, 0xfb, 0xec, 0x0a, 0x79, 0x69, 0xc7, 0xfc, 0xf2, 0x91, 0xfb, 0x07, 0xe6, 0x4b, 0x8d, 0x32, 0xa5, 0x5d, 0x81, 0x79, 0xde, 0xf9, 0xe3, 0x3a, 0xda, 0xc5, 0x5b, 0x81, 0x6b, 0x6e, 0x35, 0x13, 0xcf, 0x24, 0x4d, 0x65, 0x10, 0xe1, 0x35, 0xd7, 0x54, 0x42, 0x8e, 0x29, 0x5f, 0x2d, 0x80, 0x82, 0x67, 0x9e, 0x0f, 0x1e, 0x0a, 0x1b, 0xa4, 0x2f, 0x4c, 0xa6, 0x98, 0x07, 0x4d, 0xd1, 0xa3, 0x04, 0x43, 0x91, 0x14, 0x24, 0x3d, 0xc8, 0x23, 0x8a, 0x24, 0xd6, 0x70, 0xd6, 0x14, 0x2c, 0x82, 0x11, 0xc4, 0xda, 0x4c, 0x12, 0xf2, 0xaf, 0x6e, 0x3b, 0x4c, 0xe0, 0xc2, 0x1f, 0xd7, 0xd1, 0x25, 0x4a, 0x6a, 0x0d, 0x21, 0xb3, 0xa2, 0xb4, 0x24, 0x1c, 0x84, 0xeb, 0x68, 0x41, 0x8a, 0x0e, 0x4f, 0x4a, 0xc9, 0x41, 0x71, 0x51, 0x78, 0xb6, 0x94, 0x46, 0x9e, 0x2b, 0xd7, 0x8b, 0xa2, 0x91, 0x52, 0xea, 0x0d, 0xbd, 0x9a, 0x33, 0x03, 0x70, 0x35, 0xb3, 0x11, 0x97, 0xc0, 0xe7, 0x34, 0x41, 0xe4, 0x8a, 0xad, 0x7b, 0x5c, 0x65, 0xed, 0x39, 0x4d, 0x89, 0xde, 0x52, 0xb3, 0xbd, 0xb8, 0x25, 0x7c, 0x53, 0xc3, 0x6d, 0x23, 0xb5, 0xe5, 0xeb, 0x9f, 0x18, 0x96, 0x00, 0xab, 0xbe, 0x41, 0x71, 0xd3, 0x77, 0x1b, 0x4a, 0x85, 0x46, 0x85, 0x26, 0xbf, 0x66, 0x61, 0xa5, 0xa7, 0xee, 0xc0, 0x70, 0x55, 0xc9, 0xda, 0x87, 0x57, 0x88, 0xd0, 0x2d, 0xd9, 0x8a, 0x13, 0x25, 0xb9, 0x5a, 0xb3, 0x2c, 0x50, 0xdc, 0x98, 0x73, 0x5c, 0x7a, 0x70, 0x55, 0x6e, 0x4d, 0xc4, 0x2d, 0x27, 0x8f, 0xf0, 0xbc, 0x45, 0x0d, 0xbe, 0xec, 0x9a, 0x88, 0x57, 0xe1, 0xd3, 0x45, 0x3b, 0xb6, 0x2c, 0x9c, 0xff, 0xc0, 0xe6, 0x66, 0xfe, 0x69, 0x8d, 0xfd, 0x57, 0xd2, 0xfe, 0xc6, 0xf9, 0x12, 0x85, 0x3b, 0x37, 0x5b, 0x08, 0xc6, 0xb4, 0xd1, 0xee, 0x5d, 0x4b, 0x1a, 0x6f, 0x5d, 0x53, 0x47, 0xdf, 0xaa, 0xb1, 0xbf, 0x29, 0xeb, 0x2c, 0x69, 0x70, 0x64, 0xd9, 0xec, 0x02, 0x84, 0x7f, 0xf6, 0x8d, 0x67, 0x41, 0x9c, 0xf5, 0x47, 0xb8, 0x1f, 0x18, 0x67, 0xe5, 0x78, 0x48, 0x0a, 0xca, 0x6d, 0xc8, 0x85, 0x0d, 0x29, 0x9f, 0x47, 0x09, 0x0a, 0x02, 0x2d, 0xc5, 0x8c, 0x40, 0xc1, 0x0d, 0x30, 0x58, 0x15, 0x04, 0x39, 0xb4, 0x35, 0x69, 0x7b, 0xee, 0x1a, 0xa2, 0xd9, 0xa5, 0xf5, 0xb9, 0x8c, 0xb9, 0x58, 0xe3, 0x81, 0xeb, 0xcb, 0xe9, 0x1d, 0xe9, 0x82, 0xdc, 0xf1, 0xab, 0x96, 0x20, 0x05, 0x80, 0x9c, 0xba, 0x4b, 0x4f, 0xae, 0x46, 0x35, 0x62, 0xb6, 0xdf, 0x00, 0x7e, 0x92, 0x78, 0x79, 0xa5, 0x84, 0xb4, 0xe8, 0x67, 0x29, 0x7a, 0x4f, 0xde, 0x90, 0xa8, 0xda, 0xf6, 0xf2, 0x46, 0x29, 0xbc, 0x6a, 0x50, 0xb6, 0xef, 0x8e, 0x45, 0x04, 0x0a, 0x85, 0xc4, 0x1e, 0x2a, 0xf5, 0xc7, 0xca, 0xdb, 0x8b, 0xbc, 0x2a, 0x89, 0x9a, 0x47, 0x8d, 0xf0, 0x8a, 0x16, 0xef, 0xe9, 0x38, 0xf9, 0x92, 0xa1, 0xb1, 0x51, 0xf0, 0x38, 0x9c, 0xa7, 0xb8, 0x6d, 0xcf, 0xb9, 0xd1, 0xb5, 0xaf, 0x1e, 0xea, 0xa0, 0xcf, 0x68, 0xec, 0x7f, 0x94, 0x26, 0xf3, 0x6b, 0xc5, 0x52, 0xa5, 0x2e, 0x8b, 0x3f, 0x66, 0xa1, 0x6d, 0x0e, 0xa3, 0x50, 0x00, 0xb7, 0x35, 0x1b, 0x32, 0x80, 0xf3, 0x21, 0x9f, 0x59, 0x82, 0xbc, 0x4b, 0xe5, 0x90, 0x29, 0x8c, 0x84, 0xfd, 0x14, 0x0f, 0x4e, 0x11, 0x67, 0x24, 0xa1, 0x19, 0xc4, 0x1b, 0xa6, 0xb4, 0xcc, 0x58, 0xa4, 0xc7, 0xc2, 0xd1, 0x52, 0x5e, 0xb3, 0x1e, 0x4a, 0xf0, 0xda, 0xb0, 0xc7, 0x29, 0x40, 0x2c, 0x0c, 0x52, 0x1d, 0x4f, 0x92, 0xd5, 0xbd, 0x29, 0x61, 0xdd, 0xc2, 0x69, 0xf9, 0xd0, 0x1f, 0x7e, 0x0c, 0xc5, 0x5e, 0x22, 0x0f, 0x1f, 0x54, 0x9d, 0x49, 0xe9, 0xca, 0x76, 0xd9, 0xb7, 0xc7, 0xd7, 0x9c, 0x5e, 0xdb, 0xba, 0x65, 0x51, 0x9d, 0xa1, 0xcd, 0x5c, 0xa0, 0x55, 0xfb, 0xf3, 0x13, 0xc1, 0xb2, 0xde, 0x72, 0x1b, 0x78, 0x16, 0x1e, 0xad, 0xfd, 0xb1, 0xbc, 0x8a, 0x72, 0xf2, 0xb0, 0x44, 0x6f, 0x53, 0x5f, 0x29, 0x77, 0x26, 0xf2, 0xcc, 0x6f, 0x90, 0x0f, 0x99, 0xfd, 0x6c, 0xdf, 0x63, 0x5b, 0x1a, 0x7c, 0x35, 0x0b, 0xa2, 0x05, 0x32, 0xda, 0x60, 0x08, 0x3b, 0x54, 0xfe, 0xa6, 0x95, 0x55, 0xe3, 0x6d, 0x1a, 0xfb, 0x1f, 0x1a, 0x56, 0x67, 0xcb, 0x3a, 0xe5, 0xb6, 0x2c, 0xc5, 0x36, 0x91, 0x3d, 0xbf, 0x3a, 0x1b, 0x60, 0x9b, 0x24, 0xe1, 0x67, 0x1f, 0xa2, 0xce, 0x31, 0xcf, 0x13, 0x6e, 0x22, 0x9e, 0x2c, 0x94, 0x03, 0x14, 0x56, 0xc7, 0xa0, 0x0c, 0x43, 0x3c, 0x40, 0xf2, 0xfa, 0x27, 0x29, 0x2a, 0x52, 0x46, 0xb6, 0xe5, 0xa9, 0x50, 0x15, 0xb7, 0xd6, 0xa5, 0x36, 0x38, 0xb5, 0x02, 0xb4, 0x6f, 0x10, 0x98, 0x5d, 0xd1, 0x42, 0x88, 0x4c, 0xe2, 0x6a, 0x1f, 0x52, 0x83, 0x23, 0x18, 0xc6, 0x9a, 0x4b, 0x04, 0xd8, 0x0e, 0x2d, 0xb9, 0xa2, 0x5b, 0x3a, 0x6b, 0xf3, 0x53, 0x6b, 0x93, 0xe1, 0xd6, 0x72, 0xbf, 0x60, 0xde, 0x9d, 0xd9, 0x3f, 0xd1, 0x64, 0xfd, 0xe3, 0xd7, 0x36, 0x47, 0x76, 0x5b, 0x79, 0x50, 0xa8, 0x05, 0x65, 0xe4, 0x17, 0x63, 0xf9, 0xcb, 0xef, 0x5a, 0x9c, 0xbd, 0xac, 0x02, 0x18, 0xe2, 0xf3, 0x2a, 0xc9, 0x37, 0xe5, 0xc2, 0x40, 0xfb, 0x43, 0x6f, 0x5e, 0x1c, 0xb6, 0x1b, 0x0c, 0xf1, 0xf9, 0xc9, 0x53, 0x9c, 0xbd, 0xd0, 0x0f, 0xf7, 0x60, 0x31, 0xe4, 0x29, 0x9d, 0x68, 0x0f, 0x8c, 0x80, 0x10, 0xa2, 0xf0, 0x3f, 0xb8, 0xfa, 0x88, 0x4b, 0x5f, 0xc2, 0x43, 0x71, 0x57, 0xc8, 0xeb, 0xb3, 0x5f, 0x04, 0x30, 0x8f, 0x43, 0xd3, 0x4b, 0xe9, 0x66, 0xa7, 0x5d, 0x93, 0xf2, 0x2e, 0x86, 0x6c, 0xa4, 0x98, 0x53, 0xa8, 0xe2, 0xff, 0xb8, 0xd8, 0x03, 0xf4, 0x1f, 0x98, 0x1a, 0x53, 0x53, 0x0a, 0xc8, 0xef, 0x82, 0x10, 0xfb, 0x2c, 0x68, 0x63, 0x9f, 0xbd, 0xc8, 0x7d, 0xc0, 0xcb, 0x95, 0x0f, 0x9b, 0x5d, 0x22, 0xc0, 0x93, 0x6b, 0x05, 0x7c, 0x8d, 0x0c, 0x30, 0x02, 0x8f, 0xe9, 0x11, 0x72, 0xdd, 0xda, 0x65, 0x23, 0xec, 0x4f, 0x41, 0xf6, 0xc8, 0xb2, 0xb5, 0x54, 0xc0, 0xa8, 0x1f, 0xfb, 0xb5, 0x27, 0x61, 0xb3, 0x25, 0x3c, 0xa4, 0x5d, 0x6d, 0xe5, 0x74, 0xd7, 0x41, 0x38, 0x5e, 0x29, 0xdd, 0x37, 0x79, 0xbc, 0x70, 0x6c, 0x84, 0x70, 0x74, 0xa6, 0x51, 0x7f, 0xc5, 0x78, 0xb5, 0xae, 0xe8, 0xe4, 0xf1, 0x4e, 0x70, 0x57, 0x70, 0xbc, 0x0f, 0x03, 0x86, 0x7d, 0x17, 0x44, 0xd8, 0x77, 0x9f, 0x65, 0xbf, 0x0f, 0x0a, 0xe1, 0x3f, 0x1e, 0x7b, 0xd7, 0x3d, 0xf6, 0xb0, 0xe8, 0xa7, 0x1a, 0x83, 0x40, 0xa0, 0x57, 0xff, 0x44, 0x1c, 0x73, 0xed, 0x27, 0xd5, 0xab, 0xfb, 0x86, 0x3f, 0xf8, 0x60, 0xb8, 0x6f, 0x35, 0xd9, 0xe4, 0xd5, 0xb1, 0x27, 0x83, 0x65, 0x16, 0x6b, 0xb9, 0x1f, 0x0c, 0xea, 0xbd, 0x9c, 0xec, 0x5a, 0x34, 0xfe, 0x31, 0xaf, 0x87, 0x79, 0x96, 0x10, 0xc2, 0x33, 0xdc, 0x04, 0xb7, 0xf9, 0x1f, 0xa8, 0x22, 0x12, 0x0f, 0x12, 0x5f, 0x21, 0x1f, 0xe9, 0xb5, 0x90, 0x23, 0x5e, 0x1c, 0x12, 0x17, 0x12, 0x82, 0xfc, 0x88, 0x04, 0x02, 0x07, 0x8f, 0xe4, 0x43, 0x16, 0x93, 0xa0, 0x44, 0x7c, 0xac, 0xae, 0x11, 0x89, 0x85, 0xa2, 0xa5, 0xd8, 0xcd, 0x16, 0x11, 0x21, 0xb8, 0xfa, 0x62, 0x31, 0xe8, 0x15, 0xa4, 0xb4, 0x5d, 0x9c, 0x5a, 0xac, 0xf4, 0x2b, 0x1f, 0xc7, 0x0f, 0xf2, 0x78, 0x38, 0x9b, 0xf2, 0xaa, 0xa9, 0x5d, 0x24, 0x13, 0xff, 0xe9, 0xd3, 0x58, 0xa1, 0x26, 0x16, 0xa7, 0x45, 0x6a, 0x88, 0x89, 0x2d, 0x62, 0xd1, 0xfc, 0x79, 0x1d, 0xb3, 0x1b, 0xea, 0x2a, 0xca, 0xf2, 0x73, 0x7d, 0x1e, 0xa7, 0xdd, 0x64, 0x50, 0xca, 0x45, 0x71, 0x71, 0xdc, 0xe5, 0x94, 0xa2, 0xba, 0x90, 0xa9, 0x98, 0xbc, 0x10, 0xc8, 0xa4, 0xd4, 0xf8, 0x32, 0xfd, 0x05, 0x84, 0xf0, 0xe9, 0x45, 0x48, 0x27, 0xea, 0x29, 0xa2, 0xc4, 0xdb, 0xf4, 0x0f, 0xc5, 0x66, 0xe3, 0x6d, 0x97, 0x7f, 0x2b, 0xb2, 0x69, 0x6e, 0x07, 0x8f, 0x88, 0x85, 0x65, 0xd9, 0xbb, 0x7b, 0x3b, 0x76, 0x2f, 0xc8, 0x8f, 0x2e, 0xde, 0xdb, 0xde, 0xbe, 0x35, 0x58, 0xc0, 0x48, 0x25, 0x12, 0x83, 0x3b, 0x16, 0xcc, 0x69, 0x89, 0xdb, 0x1c, 0x10, 0x8b, 0x1b, 0x83, 0x6e, 0x9b, 0x82, 0x27, 0xa1, 0x25, 0xb1, 0x81, 0x43, 0x5d, 0x5d, 0x87, 0x06, 0x62, 0xe9, 0xcf, 0x93, 0xb6, 0xe2, 0x8e, 0x48, 0xa4, 0xa3, 0xd8, 0xb6, 0x61, 0xd9, 0xb2, 0x0d, 0xcc, 0xcd, 0x52, 0xf5, 0x17, 0xbb, 0x65, 0x4a, 0x6a, 0xb7, 0xb0, 0xaa, 0xb8, 0xb4, 0x68, 0xe9, 0xc1, 0xf6, 0x8e, 0x43, 0x4b, 0xe3, 0xb1, 0x50, 0x3d, 0x5f, 0xa4, 0xd4, 0x2a, 0x1d, 0x15, 0x0b, 0x4a, 0x4a, 0x7a, 0x2b, 0x1c, 0x8c, 0x44, 0x2d, 0x63, 0x78, 0x63, 0xbf, 0x9b, 0x7d, 0x60, 0xb0, 0xb8, 0x78, 0xf0, 0xc0, 0xec, 0xd9, 0x07, 0xd1, 0xe7, 0xc1, 0xd9, 0xc5, 0xbd, 0x09, 0xa7, 0x33, 0xd1, 0x5b, 0x3c, 0xba, 0x73, 0x27, 0xdc, 0xf3, 0x77, 0x08, 0x42, 0x70, 0x16, 0xf2, 0xbe, 0x5a, 0xc2, 0x01, 0x65, 0x9c, 0x62, 0xe2, 0x26, 0x2e, 0x40, 0xdf, 0x43, 0x30, 0x62, 0xc0, 0x13, 0x30, 0xbc, 0x01, 0x88, 0xd1, 0x85, 0x90, 0xed, 0x80, 0x27, 0x0a, 0x95, 0x77, 0xe0, 0xa3, 0x6c, 0xef, 0x5c, 0x62, 0x63, 0xbc, 0x25, 0x81, 0xab, 0xda, 0x51, 0x78, 0xef, 0x51, 0x6b, 0xec, 0x69, 0xbc, 0x2a, 0xdd, 0xb6, 0x27, 0x69, 0x72, 0x39, 0xf5, 0xba, 0xe2, 0xa2, 0x78, 0x2c, 0x3b, 0xe4, 0x8c, 0xba, 0xa2, 0x16, 0x93, 0xce, 0xa1, 0x77, 0xc0, 0xff, 0x15, 0x12, 0x64, 0x48, 0x8d, 0x28, 0x5d, 0xd3, 0x42, 0xdc, 0x20, 0x87, 0x8a, 0xad, 0x6e, 0xa9, 0x4a, 0xbd, 0xd4, 0x34, 0x27, 0x25, 0x06, 0x95, 0x67, 0x3c, 0xfd, 0xf2, 0xb0, 0xc1, 0xa1, 0xbc, 0x72, 0x89, 0xa4, 0x5e, 0x59, 0x63, 0x36, 0x2b, 0xa9, 0x6c, 0x8a, 0x22, 0xbf, 0xd9, 0xb9, 0x78, 0x71, 0x67, 0xd7, 0xa2, 0x45, 0x9d, 0x8d, 0x9b, 0xbb, 0x73, 0x73, 0xbb, 0x37, 0x37, 0xc2, 0xcf, 0xbc, 0xbc, 0xee, 0xcd, 0xec, 0x1a, 0xf2, 0xb6, 0xb1, 0x75, 0x28, 0x3d, 0x90, 0x4a, 0x43, 0x1b, 0x4c, 0x85, 0xfa, 0x2b, 0x3e, 0xe6, 0x94, 0x54, 0x7f, 0xf9, 0x0f, 0x96, 0x78, 0x16, 0xb5, 0x78, 0x79, 0xdf, 0xc2, 0xe5, 0xcb, 0x17, 0xf6, 0x2d, 0x1f, 0xfb, 0x7b, 0x64, 0x1e, 0x64, 0xde, 0x37, 0xf5, 0x14, 0x70, 0x9f, 0xf3, 0x22, 0x1b, 0x6f, 0x83, 0xf4, 0x2d, 0x17, 0xe3, 0x7e, 0x54, 0x5b, 0xb2, 0x18, 0xe9, 0x2c, 0x85, 0x90, 0xd9, 0x82, 0xf2, 0x14, 0xcd, 0x00, 0xba, 0x5f, 0x06, 0x24, 0x12, 0x38, 0x63, 0x14, 0x73, 0x2c, 0xee, 0x51, 0xc8, 0xa5, 0x94, 0x58, 0x3c, 0xd4, 0x84, 0x1c, 0x3c, 0x97, 0x32, 0xcd, 0x34, 0x4d, 0x17, 0xd3, 0xc5, 0xb1, 0xc2, 0x48, 0x41, 0x7e, 0x6e, 0x38, 0xe8, 0xf3, 0x7a, 0xdc, 0x0e, 0x97, 0x4e, 0xa9, 0x76, 0xb8, 0x95, 0x0a, 0x25, 0x3a, 0xd9, 0x2e, 0x2a, 0xc2, 0x15, 0xde, 0x45, 0xa4, 0x4c, 0x0e, 0x2f, 0xb1, 0x58, 0xa1, 0x76, 0x61, 0xe1, 0x56, 0x0d, 0x57, 0x24, 0x01, 0x22, 0x6a, 0x17, 0xd6, 0xe0, 0xe2, 0x73, 0x4e, 0x45, 0xb8, 0x44, 0xae, 0xe4, 0x4f, 0xff, 0x5a, 0xfe, 0x11, 0x78, 0x53, 0xce, 0xf8, 0x83, 0x3b, 0x2a, 0x9e, 0xb4, 0x99, 0xf8, 0xc2, 0x0f, 0x3b, 0xe6, 0xc9, 0x0c, 0x0a, 0x3e, 0x5d, 0xfd, 0x51, 0xb5, 0xa0, 0xc0, 0x3e, 0xb7, 0xe3, 0x90, 0xd5, 0xcc, 0x17, 0xbe, 0x53, 0xfe, 0x3b, 0x19, 0x6c, 0x72, 0x67, 0x2d, 0x2f, 0xcf, 0x3e, 0xaf, 0xe3, 0xaf, 0xed, 0xdd, 0x76, 0x37, 0xaf, 0x96, 0x5a, 0xc5, 0xbe, 0x0d, 0xde, 0xee, 0xb4, 0x59, 0xd9, 0xb7, 0x3d, 0xe1, 0x04, 0xbb, 0x50, 0xaa, 0xb7, 0xaa, 0x20, 0x8a, 0x3b, 0xed, 0x77, 0x82, 0x87, 0xdd, 0xd9, 0x09, 0x50, 0xd4, 0x61, 0xb7, 0x82, 0x05, 0x76, 0x0f, 0xbb, 0x08, 0x3c, 0xe8, 0xb7, 0xb3, 0xa7, 0xb1, 0x7c, 0xb1, 0x6c, 0xfc, 0x9f, 0xf4, 0x1d, 0x90, 0x70, 0x23, 0x19, 0x69, 0x2e, 0xc7, 0x50, 0x21, 0x33, 0x2d, 0xa4, 0xf2, 0xe4, 0x40, 0x5a, 0x52, 0x9a, 0xe0, 0x9f, 0xae, 0xba, 0x85, 0xd9, 0xa5, 0x99, 0x1e, 0xe8, 0xb9, 0x76, 0x65, 0xf8, 0xe9, 0xa5, 0x49, 0xe9, 0x3b, 0xe6, 0xdc, 0x7e, 0x61, 0xcd, 0x9a, 0x37, 0x8e, 0x76, 0x75, 0x1d, 0x7d, 0x63, 0xcd, 0x9a, 0x0b, 0xb7, 0xcf, 0xb9, 0x58, 0xd2, 0xb7, 0x25, 0x59, 0xb9, 0xa9, 0xaf, 0xb8, 0xb8, 0x6f, 0x53, 0x65, 0x72, 0x4b, 0x5f, 0x09, 0x99, 0xb7, 0xff, 0x67, 0x77, 0xb6, 0xb6, 0xde, 0xf9, 0xb3, 0xfd, 0xfb, 0xdf, 0x43, 0x9f, 0xef, 0xed, 0x5f, 0x73, 0xdf, 0x60, 0x5e, 0xde, 0xe0, 0x7d, 0x6b, 0x56, 0x9f, 0x1a, 0xcc, 0xcd, 0x1d, 0xe4, 0x72, 0xa8, 0x3a, 0x51, 0x7d, 0x72, 0x7a, 0x09, 0x21, 0x41, 0x76, 0x02, 0x31, 0x0f, 0xdb, 0x09, 0xd2, 0x3e, 0x27, 0xc8, 0x35, 0x0e, 0xfe, 0x95, 0x10, 0x12, 0x25, 0x67, 0x27, 0xc0, 0x11, 0x98, 0x90, 0x4e, 0xe7, 0x22, 0x03, 0xf1, 0xb7, 0x48, 0xf2, 0x57, 0xc6, 0x3c, 0x65, 0x15, 0x38, 0x49, 0x2f, 0xb9, 0xfc, 0x35, 0xca, 0xa2, 0x2f, 0x30, 0xdc, 0xaf, 0x61, 0xf4, 0xdd, 0xc0, 0x85, 0xec, 0x05, 0xdd, 0x28, 0xe6, 0x0d, 0xf2, 0xeb, 0x3c, 0xb0, 0x9d, 0x46, 0xd7, 0xad, 0xe3, 0x3e, 0xea, 0x02, 0xbe, 0xde, 0x71, 0x19, 0x5d, 0x7b, 0xc6, 0x63, 0x74, 0x36, 0xf2, 0x27, 0x07, 0x3b, 0xcd, 0xe8, 0xba, 0x78, 0xdc, 0x4b, 0xdd, 0x0e, 0x69, 0x03, 0x0f, 0xec, 0xfa, 0x05, 0xba, 0xee, 0x1b, 0xff, 0x07, 0x75, 0x99, 0x99, 0x05, 0xaf, 0x77, 0xf3, 0xd0, 0x75, 0xee, 0x78, 0x8c, 0xfa, 0x26, 0xbe, 0xbf, 0xe7, 0x47, 0xe8, 0x7a, 0x39, 0xbc, 0xff, 0x07, 0xa6, 0x03, 0x5e, 0xef, 0x15, 0xa3, 0xeb, 0xda, 0xf1, 0x28, 0x75, 0x0e, 0xee, 0x09, 0x0f, 0xec, 0xfb, 0x0b, 0xce, 0x25, 0x02, 0xf7, 0x88, 0xfe, 0x7f, 0xbc, 0x47, 0x74, 0xcb, 0xde, 0xe7, 0x06, 0x07, 0x9f, 0xdd, 0xd3, 0xd2, 0xb2, 0xe7, 0xd9, 0xc1, 0xc1, 0xe7, 0xf6, 0xb6, 0xbc, 0x11, 0x6e, 0x1b, 0x4e, 0x26, 0x87, 0x5b, 0x43, 0xa1, 0x56, 0xf4, 0xd9, 0x16, 0x26, 0xf3, 0x8e, 0xbc, 0x7f, 0xa4, 0xbe, 0x1e, 0xfe, 0x39, 0xfa, 0xfe, 0xd1, 0xfa, 0x7a, 0xf8, 0x67, 0xa6, 0x3d, 0xda, 0x03, 0xd7, 0xf2, 0x7b, 0x70, 0x8f, 0x14, 0x84, 0x3f, 0xe9, 0x91, 0x0a, 0xb8, 0x18, 0x28, 0xc4, 0x49, 0xe2, 0x7c, 0xf7, 0x38, 0x30, 0x89, 0xdb, 0x29, 0x05, 0xa1, 0x30, 0x2a, 0x95, 0xc8, 0x3d, 0xc4, 0xa3, 0x45, 0x0e, 0xd1, 0x99, 0xdd, 0x8a, 0x28, 0xa9, 0xef, 0x5d, 0xb9, 0x02, 0xf2, 0x29, 0xf2, 0x77, 0x86, 0x42, 0x59, 0x29, 0x38, 0x76, 0xe2, 0xe8, 0xd1, 0xa3, 0xe4, 0x56, 0x73, 0x89, 0xed, 0x6b, 0x2a, 0x46, 0x3b, 0x07, 0x38, 0xae, 0xe4, 0xc0, 0xf7, 0x74, 0xc2, 0xf7, 0xfc, 0x1c, 0xee, 0x51, 0x1e, 0x51, 0x9a, 0x2c, 0xb2, 0x00, 0x8a, 0xcc, 0x46, 0xde, 0x36, 0x28, 0x31, 0x13, 0x89, 0x44, 0x2c, 0x54, 0xf1, 0x0c, 0x79, 0x8b, 0xe2, 0x2c, 0x24, 0xd8, 0x84, 0xbe, 0x14, 0xb9, 0x85, 0xba, 0x1c, 0xa6, 0x2c, 0x95, 0x82, 0xa1, 0x89, 0x3c, 0x90, 0x8b, 0xf9, 0x79, 0x6c, 0x76, 0x8d, 0x82, 0x89, 0x6a, 0x9a, 0x69, 0xf3, 0xd2, 0x84, 0xbb, 0x3f, 0x9f, 0x41, 0x01, 0xe6, 0x77, 0x38, 0x3c, 0x57, 0xb6, 0x80, 0x97, 0x51, 0xf0, 0xbb, 0x25, 0xda, 0x9c, 0x53, 0xd4, 0x51, 0xe2, 0x11, 0x0b, 0x34, 0x3a, 0x46, 0xce, 0x5b, 0x23, 0xad, 0x59, 0x73, 0x7c, 0xf1, 0x9c, 0x43, 0x4b, 0x62, 0xde, 0xea, 0xbe, 0x62, 0xf6, 0x07, 0xb9, 0xbd, 0xee, 0x67, 0xfc, 0xe6, 0x55, 0x6b, 0x40, 0x6f, 0xb0, 0xb5, 0xb5, 0x23, 0x3b, 0x50, 0x1b, 0xb1, 0x38, 0x62, 0x75, 0x5e, 0xad, 0x1a, 0x90, 0xea, 0xd6, 0x03, 0x83, 0x25, 0x91, 0x05, 0xdb, 0x9b, 0xcb, 0x57, 0x2d, 0x99, 0x1f, 0xfa, 0xb3, 0x38, 0xe5, 0xd3, 0xdc, 0x09, 0xe1, 0xed, 0x6d, 0x38, 0x97, 0x6a, 0x54, 0xc5, 0x0a, 0xe5, 0xee, 0x36, 0x42, 0x99, 0x1a, 0x4e, 0x85, 0xc7, 0xd0, 0x0c, 0x8f, 0x1e, 0x21, 0x52, 0x92, 0x21, 0xf2, 0x7d, 0x20, 0xe9, 0xab, 0x4b, 0xa6, 0xc7, 0xfc, 0xca, 0xac, 0x70, 0x8e, 0x9b, 0x2f, 0x32, 0x4d, 0x38, 0xf4, 0xf2, 0xa6, 0xc6, 0xfb, 0xa7, 0x13, 0x67, 0xe1, 0x59, 0x46, 0xa7, 0xcf, 0xd6, 0x77, 0x93, 0xb4, 0x6a, 0xf8, 0x9e, 0xc5, 0x73, 0x8f, 0x0c, 0x15, 0xaf, 0x1b, 0xde, 0xb1, 0xa5, 0xfc, 0x86, 0x87, 0x06, 0x97, 0xde, 0xb9, 0xb2, 0x3e, 0x6b, 0xbe, 0x27, 0x47, 0x2c, 0x0b, 0x27, 0xe6, 0x16, 0x95, 0x2c, 0xae, 0xf6, 0x38, 0x5b, 0x6e, 0xee, 0x4d, 0xae, 0x6d, 0xcf, 0x71, 0x96, 0xb4, 0xe5, 0xc4, 0xdb, 0x53, 0xb3, 0x57, 0x30, 0xe4, 0xff, 0x46, 0x73, 0x2a, 0xe8, 0xd9, 0xda, 0xb4, 0xfa, 0x98, 0xd7, 0xf9, 0xe8, 0xce, 0xa6, 0x43, 0xab, 0x92, 0xb9, 0xdd, 0x5b, 0x9a, 0xfd, 0x0a, 0xa5, 0xc1, 0x57, 0x17, 0xb3, 0xbb, 0x12, 0x73, 0x0b, 0x8a, 0xe7, 0x95, 0xd9, 0xf6, 0x93, 0x96, 0xe2, 0x39, 0xc5, 0xf9, 0xcd, 0x11, 0xa3, 0x3d, 0x56, 0xe7, 0x43, 0x8b, 0xc1, 0xf9, 0xf9, 0x0a, 0xe1, 0xd9, 0x52, 0xc2, 0xb3, 0x45, 0x43, 0x4a, 0x56, 0x9b, 0xac, 0x82, 0x54, 0x88, 0x56, 0x01, 0xbc, 0x91, 0xb8, 0x4a, 0x3c, 0x84, 0x9c, 0x75, 0x7c, 0x86, 0x47, 0x61, 0xa7, 0x34, 0xfc, 0x85, 0x00, 0x0b, 0xb0, 0xdb, 0xf7, 0x6c, 0x86, 0x81, 0xeb, 0xa2, 0x65, 0xb4, 0x1a, 0xb5, 0x5c, 0x2a, 0x11, 0x8b, 0x04, 0xe8, 0x01, 0x01, 0x52, 0xd2, 0xfe, 0x1f, 0xd6, 0xde, 0x03, 0x3e, 0xae, 0xe2, 0xda, 0x1f, 0xbf, 0x73, 0xdb, 0x56, 0xed, 0xea, 0x6e, 0xef, 0xbd, 0x6a, 0xb5, 0xea, 0xbb, 0xea, 0xd2, 0xaa, 0x5a, 0x56, 0xb5, 0x5c, 0x54, 0x2c, 0xd9, 0x96, 0x2d, 0xc9, 0xb2, 0x6c, 0xe3, 0xde, 0x1b, 0x2e, 0xd8, 0xd8, 0x60, 0x4c, 0xb1, 0x4d, 0x28, 0x26, 0x06, 0x4c, 0xc7, 0x26, 0x10, 0xc0, 0x24, 0x04, 0x02, 0x31, 0xbc, 0x17, 0x42, 0x09, 0x79, 0x09, 0x10, 0xe0, 0x05, 0xd2, 0xe0, 0x25, 0x79, 0x09, 0x09, 0x25, 0xa6, 0xd9, 0xba, 0xfa, 0xcf, 0xcc, 0xbd, 0xbb, 0x5a, 0xc9, 0x32, 0xf0, 0x3e, 0xff, 0x9f, 0xc1, 0xde, 0xdd, 0x5b, 0x67, 0xce, 0xcc, 0x9c, 0x73, 0xe6, 0x94, 0xef, 0xe1, 0x8a, 0x8a, 0xb1, 0x52, 0x2e, 0x64, 0x38, 0x7b, 0x84, 0x39, 0x45, 0xde, 0xa9, 0xe4, 0xc3, 0x54, 0x85, 0xc5, 0x70, 0xe2, 0xde, 0xb1, 0x27, 0xb4, 0x7e, 0x2a, 0x93, 0x3c, 0xfc, 0x28, 0xb0, 0x82, 0x8f, 0xcb, 0x1b, 0xc6, 0x6e, 0x18, 0x36, 0x98, 0xf9, 0x1f, 0x80, 0x4e, 0xbb, 0xa1, 0x9b, 0x9a, 0x75, 0xf1, 0x69, 0x61, 0x4c, 0x16, 0xc1, 0x35, 0xfe, 0x14, 0x5c, 0xc3, 0x51, 0x34, 0xbf, 0xfc, 0x70, 0x3c, 0x50, 0x4d, 0x45, 0xb2, 0x39, 0x00, 0x77, 0xf3, 0x33, 0x51, 0x35, 0x3d, 0x02, 0x55, 0xd3, 0xbb, 0x4c, 0x0d, 0x7b, 0xaf, 0x2e, 0x94, 0x15, 0x72, 0xe3, 0x3d, 0x51, 0x7c, 0x72, 0x5e, 0x52, 0x15, 0x30, 0x4e, 0x54, 0xd3, 0x44, 0x1b, 0xf6, 0xa0, 0x56, 0xd8, 0x3e, 0x80, 0x05, 0x8b, 0x7b, 0x96, 0x9e, 0x18, 0x8d, 0xcf, 0xdc, 0xf3, 0xe8, 0xd2, 0x8a, 0x95, 0x03, 0x9d, 0xce, 0x62, 0x55, 0x81, 0x29, 0x7f, 0xeb, 0xcc, 0xba, 0x15, 0x2d, 0x21, 0xdf, 0x8c, 0xd1, 0xfa, 0xbc, 0xd1, 0xf8, 0x86, 0x15, 0xe4, 0x21, 0x7f, 0x88, 0x6f, 0x22, 0x57, 0xea, 0x2b, 0x46, 0x6f, 0x9a, 0x37, 0xfa, 0xd0, 0xb6, 0x5a, 0x53, 0xa8, 0xc8, 0xde, 0xae, 0x53, 0x71, 0x9e, 0x82, 0x48, 0xfb, 0xaa, 0xba, 0xb2, 0x85, 0xb5, 0x3e, 0x4d, 0xa6, 0x87, 0xdc, 0xb0, 0xc4, 0x15, 0x49, 0xc5, 0x50, 0x9f, 0x87, 0x7c, 0x49, 0x4b, 0xe4, 0x23, 0x44, 0x7b, 0xa3, 0x80, 0xc8, 0x32, 0x2d, 0x46, 0xcf, 0x4a, 0x0a, 0xae, 0x8e, 0xfc, 0x5c, 0xbf, 0xd7, 0x6a, 0x16, 0x42, 0xa6, 0x85, 0xd5, 0x31, 0x19, 0x3b, 0x8b, 0x12, 0x0a, 0x15, 0x4f, 0x0a, 0xa4, 0x16, 0x01, 0x6f, 0x49, 0x4f, 0xdd, 0x68, 0x73, 0x30, 0xd4, 0x34, 0x5c, 0x59, 0xbf, 0xbc, 0x25, 0x18, 0x98, 0x31, 0x62, 0xc9, 0xb4, 0xab, 0xa2, 0x2b, 0xaa, 0x67, 0xdd, 0xb4, 0xba, 0xa6, 0x7e, 0xc7, 0x99, 0xd1, 0x65, 0x0f, 0x6e, 0x4a, 0xb4, 0xcd, 0x03, 0x4d, 0xba, 0xe2, 0xc0, 0x7d, 0x87, 0xa2, 0x6d, 0x23, 0xe5, 0x89, 0xe1, 0x19, 0xfe, 0x9c, 0xd6, 0x81, 0xc2, 0xaa, 0xd1, 0xb6, 0x6c, 0xf2, 0x65, 0x9d, 0x5f, 0x6b, 0x73, 0xc6, 0x96, 0x7e, 0x6f, 0xc9, 0x82, 0xbb, 0xb7, 0x36, 0xd4, 0x6d, 0x3b, 0x3d, 0xda, 0xf7, 0x8b, 0x35, 0xe0, 0x49, 0xbb, 0x2b, 0x39, 0x16, 0x71, 0xea, 0x23, 0x38, 0x16, 0x1c, 0xf6, 0xbd, 0x23, 0x07, 0x36, 0x85, 0xb2, 0x8a, 0xc9, 0x79, 0xa8, 0x00, 0xac, 0x48, 0x73, 0x21, 0xa5, 0xd2, 0x1b, 0xa4, 0x91, 0x36, 0x88, 0xe6, 0x7f, 0x12, 0x23, 0x0b, 0x17, 0xc6, 0xa5, 0x3e, 0x92, 0xca, 0xc7, 0xfe, 0x24, 0x2f, 0xb2, 0x5d, 0x4f, 0x3a, 0x1d, 0xde, 0xb1, 0xa5, 0x66, 0x0f, 0xbd, 0x70, 0xa1, 0x3e, 0xa8, 0xbb, 0xf0, 0x33, 0xa3, 0x8f, 0x36, 0x97, 0x86, 0x6d, 0xb6, 0x8d, 0x50, 0x43, 0x58, 0x00, 0xe9, 0xf5, 0xdf, 0x90, 0x4f, 0x23, 0x3e, 0x3c, 0x03, 0xed, 0x86, 0x4b, 0x00, 0xce, 0xf8, 0x67, 0x24, 0x00, 0x19, 0x0a, 0x50, 0xb0, 0x23, 0x0a, 0x01, 0xc1, 0x9e, 0xf1, 0x64, 0x15, 0x51, 0x84, 0xd5, 0x53, 0x53, 0x9d, 0x9f, 0xeb, 0xb4, 0xeb, 0x82, 0xfa, 0xa0, 0x40, 0x3b, 0xe9, 0x34, 0xb4, 0xd3, 0xa6, 0x31, 0x5b, 0x74, 0x5c, 0x9b, 0x5e, 0xd0, 0x25, 0x9e, 0xe6, 0x07, 0x86, 0x6d, 0x27, 0xad, 0x15, 0x8b, 0xeb, 0x7c, 0xe1, 0x19, 0x8b, 0x62, 0xb1, 0x05, 0x33, 0xc2, 0xbe, 0xda, 0x81, 0x8a, 0xe2, 0x39, 0xa5, 0x76, 0x73, 0x7c, 0x5e, 0x45, 0x45, 0x57, 0xdc, 0x02, 0xd7, 0xd1, 0xd1, 0xad, 0x3b, 0x6b, 0x36, 0xdd, 0x3b, 0x34, 0x78, 0xcf, 0xa6, 0x9a, 0x1d, 0xdb, 0x86, 0x86, 0xcb, 0x96, 0x1f, 0x9d, 0x3f, 0xff, 0xd8, 0x8a, 0xb2, 0xe1, 0x03, 0xbe, 0x44, 0x6f, 0x2c, 0xb1, 0xb0, 0xc2, 0x6e, 0xaf, 0x58, 0x98, 0x88, 0xf5, 0x26, 0x7c, 0x74, 0x99, 0xa7, 0xac, 0x3d, 0x92, 0x3d, 0x27, 0x11, 0x0c, 0x26, 0xe6, 0x64, 0x47, 0xda, 0xcb, 0x3c, 0x63, 0xe3, 0xfe, 0xfb, 0x77, 0xf5, 0xde, 0x30, 0x1c, 0x8f, 0x0f, 0xdf, 0xd0, 0xbb, 0xeb, 0x7e, 0x7f, 0xe8, 0xc4, 0xca, 0x96, 0xbd, 0x8b, 0x8a, 0x8b, 0x17, 0xed, 0x6d, 0x59, 0x79, 0x02, 0x4e, 0x8b, 0xf9, 0x50, 0x36, 0xbd, 0x02, 0x65, 0x11, 0x07, 0xe7, 0x4c, 0x4e, 0x22, 0xa2, 0x47, 0x3b, 0x1d, 0x3c, 0x67, 0xd6, 0x27, 0x7b, 0x4f, 0xe0, 0x6c, 0x0b, 0x22, 0x15, 0x1c, 0x90, 0x1d, 0x0a, 0x87, 0x3c, 0x0c, 0x8a, 0xd4, 0x11, 0x5c, 0xb8, 0x29, 0x9b, 0x25, 0xdc, 0xe3, 0xa4, 0x6b, 0xe2, 0x22, 0x37, 0x15, 0xc6, 0xc5, 0xb0, 0x7f, 0x53, 0xf5, 0x92, 0x3a, 0x6f, 0xa0, 0x7e, 0x61, 0x71, 0xf9, 0x40, 0xbd, 0x0f, 0xac, 0x30, 0x06, 0xb8, 0xfc, 0x3d, 0x6d, 0x33, 0xaf, 0x5e, 0x56, 0x55, 0xbb, 0xf9, 0xde, 0xc1, 0x25, 0x77, 0xae, 0xad, 0x34, 0x46, 0x12, 0x61, 0xfe, 0xb7, 0x5c, 0xbe, 0xe7, 0x7e, 0xf2, 0xb7, 0x5b, 0xf7, 0x84, 0x1b, 0xe7, 0x17, 0x94, 0x2f, 0x48, 0xb8, 0x03, 0x75, 0xfd, 0x7c, 0x9e, 0x3d, 0x6e, 0x8d, 0x16, 0x14, 0x0c, 0x1c, 0x59, 0xd0, 0x73, 0xdb, 0xba, 0xda, 0xca, 0x35, 0x27, 0x16, 0x95, 0xaf, 0x5e, 0x79, 0x45, 0x29, 0xbf, 0xc8, 0x68, 0xc7, 0xf3, 0x7e, 0xe9, 0xf8, 0x27, 0xf4, 0x39, 0x8c, 0x65, 0x17, 0xc4, 0x68, 0xeb, 0x13, 0x7b, 0xfb, 0x09, 0x89, 0x39, 0x21, 0x18, 0x27, 0x15, 0x27, 0xc4, 0xd8, 0x70, 0x97, 0x08, 0xc7, 0x73, 0xfd, 0xb7, 0xbf, 0xbc, 0x6a, 0xd5, 0xcb, 0xb7, 0xf5, 0xf5, 0xdd, 0x86, 0x3e, 0x6f, 0xef, 0x7f, 0xb9, 0x6c, 0xf1, 0xde, 0xa6, 0xa6, 0xbd, 0x8b, 0x4b, 0x4b, 0x85, 0xcf, 0x32, 0xf2, 0x97, 0xcf, 0xf0, 0xff, 0x78, 0xb2, 0xa7, 0xe7, 0x49, 0xa0, 0x7b, 0xe6, 0x19, 0xa0, 0x47, 0xdf, 0xf8, 0xbf, 0x3f, 0x73, 0xf7, 0xbb, 0xfb, 0x4b, 0x4a, 0xf6, 0xbf, 0x7b, 0xf7, 0xdd, 0xef, 0x5e, 0x55, 0x52, 0x72, 0xd5, 0xbb, 0x68, 0xea, 0xc2, 0x16, 0x92, 0x3d, 0xcc, 0x49, 0x21, 0x0f, 0x39, 0x83, 0x9d, 0x94, 0x23, 0x8c, 0xa6, 0xf3, 0x60, 0x4b, 0x4a, 0x42, 0xaa, 0x08, 0x95, 0x91, 0xc3, 0xf3, 0x59, 0x54, 0x66, 0x50, 0x96, 0x30, 0x52, 0x67, 0x7a, 0x5e, 0x24, 0xc9, 0x37, 0x94, 0x0e, 0x87, 0x4d, 0x56, 0x06, 0x4e, 0x30, 0x27, 0xbf, 0x5e, 0x4c, 0xd7, 0x9a, 0x0a, 0x2c, 0x4f, 0xc9, 0xe5, 0x40, 0xa1, 0x9c, 0x01, 0x86, 0x71, 0x0c, 0x75, 0x3b, 0x9c, 0xa2, 0x1a, 0x48, 0x03, 0x16, 0xdc, 0xf8, 0x6f, 0xf4, 0xbb, 0x63, 0xfc, 0x63, 0xda, 0x8a, 0x7f, 0xdf, 0xc4, 0x63, 0x9d, 0x06, 0x9e, 0x1f, 0x64, 0xee, 0x80, 0xbf, 0x8f, 0x6a, 0xd0, 0xef, 0x19, 0xe3, 0x1f, 0x53, 0x1f, 0xa2, 0x7c, 0x78, 0x70, 0xec, 0x3d, 0xf4, 0xbb, 0x18, 0x9e, 0x57, 0xe0, 0xdf, 0xc7, 0x5f, 0x47, 0xbf, 0xeb, 0x20, 0xa3, 0xf8, 0x04, 0xdf, 0x7f, 0xf3, 0x1f, 0x10, 0x8d, 0xcb, 0x20, 0x8d, 0xdb, 0xff, 0x5f, 0xd2, 0xb8, 0xbd, 0xf3, 0xba, 0xe7, 0x56, 0xad, 0x7a, 0xee, 0xf0, 0xac, 0x59, 0x87, 0xd1, 0xe7, 0x75, 0x9d, 0x2f, 0xc7, 0x7a, 0x37, 0x54, 0x57, 0x6f, 0x40, 0xd6, 0x76, 0xfc, 0x19, 0xfb, 0xce, 0x34, 0x46, 0x45, 0xe5, 0x79, 0x48, 0x63, 0x0e, 0xe9, 0x20, 0x2a, 0x89, 0xa8, 0x83, 0x40, 0xd2, 0x4e, 0xd5, 0x41, 0x20, 0xc7, 0xb0, 0x4e, 0xd2, 0x41, 0x52, 0x34, 0x86, 0x4a, 0x08, 0x7f, 0xf1, 0x4b, 0x90, 0x47, 0xfd, 0x5b, 0x65, 0x33, 0x6b, 0xd9, 0x4a, 0xf0, 0xc8, 0xc9, 0xe3, 0xc7, 0x8f, 0x53, 0x67, 0xa2, 0x79, 0xaf, 0x2b, 0x58, 0x3a, 0x43, 0x32, 0x0f, 0xac, 0xff, 0xfa, 0x73, 0x8c, 0x0f, 0x09, 0xe9, 0xe4, 0xc0, 0xf8, 0x90, 0x05, 0x89, 0x5c, 0x3b, 0x64, 0xaa, 0x51, 0x41, 0x7a, 0x89, 0xc9, 0x40, 0x2b, 0x30, 0xba, 0x57, 0xba, 0x02, 0xa2, 0xd7, 0x0a, 0xea, 0x07, 0x33, 0xa1, 0x7e, 0x60, 0x26, 0x9a, 0x2e, 0x93, 0x27, 0xa9, 0x1f, 0x18, 0xdf, 0xe6, 0x5d, 0x97, 0xef, 0x62, 0x45, 0xa6, 0x16, 0xfc, 0x67, 0x69, 0x7f, 0xc2, 0x1b, 0xa8, 0x9d, 0x5f, 0x54, 0xd4, 0x5d, 0x9f, 0x23, 0x57, 0x45, 0xc2, 0x4b, 0x55, 0xb3, 0x77, 0xdc, 0xb5, 0x60, 0xe1, 0xc9, 0x0d, 0xb5, 0xb9, 0x9d, 0x2b, 0x2a, 0xf8, 0x63, 0x59, 0xf3, 0x02, 0x3f, 0xf6, 0x3b, 0x36, 0xd5, 0xe9, 0xa2, 0x1a, 0xf0, 0xf3, 0xd2, 0xe5, 0xa3, 0xab, 0x2a, 0x2b, 0xfa, 0x2a, 0x1c, 0x8e, 0x78, 0x73, 0x24, 0x60, 0x91, 0x68, 0x86, 0x6e, 0x19, 0x29, 0x42, 0x81, 0x1f, 0xb3, 0x0e, 0xed, 0xd8, 0x58, 0xc1, 0x1f, 0x56, 0xa8, 0x30, 0x7f, 0xed, 0x84, 0x73, 0xc3, 0x0e, 0xfb, 0x50, 0x8b, 0x6a, 0xc2, 0xab, 0xa1, 0xfe, 0x61, 0x13, 0xf4, 0x8f, 0xa4, 0xbe, 0x01, 0x25, 0xad, 0x98, 0x44, 0x24, 0x8e, 0xf2, 0x4a, 0xd0, 0x5a, 0x5e, 0x9a, 0x9f, 0x97, 0x9b, 0x13, 0xf0, 0x99, 0x0c, 0x3e, 0x56, 0xae, 0x4f, 0xcb, 0x24, 0x22, 0xbf, 0xb3, 0xe6, 0x11, 0xdc, 0xa1, 0x46, 0x6d, 0x5f, 0x70, 0x62, 0x75, 0x95, 0xa7, 0x7a, 0x7e, 0xc9, 0xa1, 0x6b, 0x5a, 0xaf, 0xfa, 0xc1, 0xe0, 0xda, 0xbb, 0xd7, 0xb5, 0x58, 0xe6, 0xb9, 0xab, 0x54, 0xea, 0x60, 0xed, 0x60, 0x4b, 0xd3, 0xca, 0x26, 0x9f, 0xad, 0xe3, 0xf0, 0x9a, 0xfe, 0x2b, 0xdb, 0xbd, 0xbe, 0x1a, 0x38, 0x15, 0x7a, 0x70, 0xaf, 0x43, 0xb4, 0x1e, 0xf5, 0x24, 0xb6, 0xf8, 0xe0, 0xec, 0x86, 0xe5, 0xb3, 0x8a, 0x35, 0x81, 0x87, 0xf7, 0x0d, 0xdf, 0xb5, 0xba, 0xbc, 0x78, 0xf0, 0xda, 0x39, 0x3e, 0x95, 0xd2, 0xee, 0x68, 0xa9, 0xcb, 0xc9, 0x6d, 0xe9, 0x8f, 0x96, 0x2c, 0xac, 0x0b, 0x1c, 0x01, 0xbe, 0x44, 0x4f, 0xbc, 0xbc, 0xa7, 0xdc, 0x2e, 0xd2, 0x00, 0xce, 0x5d, 0x2d, 0x1c, 0xb3, 0x4e, 0x38, 0xf7, 0x19, 0xc2, 0x40, 0x54, 0x24, 0x4a, 0x51, 0xb4, 0x85, 0x56, 0x82, 0xb5, 0x47, 0xac, 0x61, 0x08, 0x41, 0x7e, 0x43, 0x90, 0xbf, 0xe3, 0xa4, 0xae, 0x21, 0xb2, 0x95, 0x65, 0x09, 0x82, 0x35, 0xb0, 0x06, 0xbd, 0x0e, 0x25, 0x5c, 0x42, 0x26, 0xcf, 0x10, 0x8c, 0xa8, 0x6a, 0xc4, 0xa7, 0xd3, 0x35, 0xde, 0xa2, 0x18, 0x5e, 0x46, 0x2d, 0x37, 0xe8, 0xee, 0x7b, 0x78, 0xec, 0x1a, 0x83, 0x83, 0xd1, 0x91, 0x67, 0x7f, 0x44, 0x2a, 0xc9, 0x3c, 0x7b, 0x8d, 0x77, 0xac, 0x63, 0x2d, 0x67, 0xe2, 0x8f, 0x80, 0x75, 0x66, 0xed, 0x10, 0x75, 0xfb, 0x45, 0x5c, 0xcb, 0x73, 0x39, 0xa4, 0xbf, 0x14, 0xd7, 0x9d, 0x81, 0x32, 0x27, 0xe0, 0x27, 0x49, 0x1a, 0x2a, 0x1b, 0x68, 0x00, 0x92, 0xa9, 0xd0, 0xb0, 0xbd, 0x24, 0x81, 0x91, 0x9f, 0x70, 0x3e, 0xfc, 0x60, 0x2a, 0x15, 0x1a, 0x0e, 0x84, 0x57, 0x1b, 0xf2, 0x06, 0x42, 0x6e, 0xa4, 0xfd, 0x4d, 0xd5, 0x36, 0xd2, 0x60, 0x9e, 0x10, 0xd1, 0x27, 0x94, 0x8d, 0x8d, 0xab, 0x7a, 0xd7, 0x9c, 0xde, 0x58, 0x8e, 0x56, 0x5a, 0xcd, 0xc6, 0xa5, 0xdd, 0x9e, 0x0a, 0xad, 0x4b, 0x5f, 0xd8, 0x38, 0x50, 0x3b, 0x73, 0x63, 0x67, 0x24, 0xd0, 0xba, 0xae, 0x75, 0xf9, 0x96, 0xed, 0x1b, 0xc9, 0x47, 0x63, 0x55, 0x7c, 0x0b, 0x75, 0x95, 0xb1, 0x6e, 0xe3, 0xc9, 0x05, 0xeb, 0x9e, 0xbe, 0x6a, 0x86, 0x25, 0x52, 0xe2, 0x9c, 0x6b, 0x73, 0x06, 0x9c, 0xb9, 0x5d, 0x5b, 0x66, 0xd6, 0x2c, 0x6b, 0x0e, 0x06, 0x7c, 0xe4, 0x7d, 0xf3, 0xb2, 0x62, 0x82, 0x8c, 0x5e, 0x06, 0xdb, 0x58, 0x81, 0xe3, 0xd6, 0x10, 0x37, 0x80, 0x14, 0xec, 0x9d, 0xc2, 0xcd, 0x34, 0x84, 0xc6, 0x37, 0x55, 0x3a, 0x43, 0x09, 0x81, 0xc5, 0x33, 0x5d, 0xc1, 0x49, 0xc7, 0x9e, 0x56, 0x15, 0x64, 0x1d, 0x26, 0x3b, 0xd4, 0x0e, 0xcd, 0x98, 0x47, 0x6b, 0x63, 0x4e, 0x74, 0x07, 0x72, 0xb9, 0x0b, 0x4b, 0xed, 0x21, 0xfa, 0x90, 0x2d, 0x57, 0x67, 0xd3, 0xee, 0x17, 0xf4, 0x45, 0x28, 0x7e, 0x68, 0x0e, 0xce, 0x55, 0x0e, 0xae, 0x38, 0xc8, 0x41, 0x75, 0x82, 0x7c, 0x82, 0x67, 0xa0, 0xbe, 0x3f, 0x98, 0x74, 0xa5, 0xad, 0x10, 0x2d, 0x88, 0xd9, 0x90, 0x03, 0x4d, 0x2b, 0x9a, 0xe2, 0x97, 0x97, 0x4c, 0xc1, 0x1b, 0x76, 0x37, 0x2c, 0x9f, 0x19, 0x08, 0x37, 0x0f, 0x57, 0x56, 0x2f, 0x6b, 0x0e, 0x81, 0x6b, 0x82, 0x9e, 0xf8, 0xfe, 0x96, 0x8e, 0x1b, 0x57, 0xd7, 0x36, 0x5e, 0xf9, 0xd8, 0xf2, 0xd1, 0x87, 0xb7, 0xd4, 0x1a, 0x23, 0x95, 0x41, 0xfe, 0x7e, 0x55, 0xcc, 0xf3, 0x38, 0xf5, 0xef, 0xfd, 0x37, 0x44, 0x5b, 0x06, 0xe3, 0x35, 0xa3, 0x33, 0x83, 0xe1, 0xe6, 0xa5, 0xbc, 0xa4, 0x30, 0x3f, 0x9c, 0x15, 0x1f, 0xbd, 0x6d, 0x68, 0xe1, 0x7d, 0xdb, 0x67, 0xd4, 0x6f, 0x3f, 0x3d, 0x52, 0xbd, 0x6e, 0xe5, 0x68, 0x31, 0xcf, 0xe9, 0x2c, 0x98, 0x27, 0x97, 0x8d, 0x1b, 0xc8, 0x25, 0x90, 0x31, 0x48, 0xc0, 0x7d, 0xf0, 0xb7, 0x03, 0xe5, 0x14, 0x91, 0x3f, 0x1a, 0x77, 0xa4, 0x72, 0x8a, 0x06, 0xc7, 0x1d, 0x64, 0x09, 0xb8, 0x08, 0xcf, 0xdf, 0x4f, 0x50, 0xc2, 0x79, 0x90, 0x7e, 0xbe, 0x11, 0x9e, 0x6f, 0xc3, 0xf7, 0x3f, 0x40, 0x90, 0xd3, 0x9c, 0xaf, 0x85, 0xcf, 0x9f, 0x81, 0xcf, 0x3f, 0x48, 0x80, 0x69, 0xce, 0xbb, 0xe0, 0xfd, 0xc3, 0xe0, 0x6b, 0x78, 0xfe, 0xa1, 0xe4, 0xf3, 0x27, 0xbd, 0xbf, 0x03, 0xde, 0x5f, 0x89, 0xcf, 0x3f, 0x9c, 0x6c, 0xdf, 0xa4, 0xfb, 0xdb, 0xe1, 0xf9, 0x79, 0xf8, 0xfc, 0xe9, 0xe4, 0xf3, 0x27, 0xdd, 0x3f, 0x0f, 0x3e, 0x7f, 0x0e, 0x3e, 0x7f, 0x66, 0xda, 0xf6, 0xb7, 0xc1, 0xfb, 0x3b, 0x70, 0xfb, 0x1e, 0x49, 0x3e, 0x9f, 0x4a, 0x3f, 0x3f, 0x0b, 0xde, 0x5f, 0x87, 0xcf, 0xff, 0x60, 0x7c, 0x7c, 0x9a, 0xe7, 0x77, 0x8f, 0xef, 0x20, 0x1b, 0xc0, 0x76, 0x4a, 0x42, 0x9f, 0x9b, 0xb6, 0x7d, 0x2b, 0x89, 0x26, 0x32, 0x8f, 0x78, 0x0c, 0x9e, 0x7f, 0x61, 0xfa, 0xf3, 0xe3, 0xff, 0x24, 0xf3, 0x80, 0x02, 0x9e, 0x7f, 0x71, 0x5a, 0xfa, 0x97, 0xf3, 0x77, 0x01, 0x2b, 0xb9, 0x04, 0x9e, 0xff, 0xaf, 0x69, 0xdb, 0x5f, 0x08, 0xcf, 0xeb, 0xf0, 0xf9, 0x5f, 0x8f, 0xf3, 0x53, 0xcf, 0x63, 0x99, 0xf8, 0x25, 0x1c, 0xdf, 0x0f, 0xb0, 0x4c, 0x3c, 0xf2, 0x8d, 0xfb, 0x78, 0xc5, 0x74, 0xa7, 0x86, 0x2e, 0xbf, 0xc5, 0x17, 0xee, 0x52, 0x5d, 0xee, 0xae, 0xcb, 0xec, 0xfe, 0xff, 0x0f, 0xfb, 0x7f, 0x72, 0x49, 0x76, 0xcb, 0x70, 0x59, 0xe9, 0x50, 0x73, 0x24, 0xd2, 0x3c, 0x54, 0x5a, 0x36, 0xdc, 0x92, 0x7d, 0xbd, 0xce, 0x57, 0x60, 0xb7, 0xe7, 0x79, 0x75, 0x28, 0x0b, 0xcf, 0x9a, 0xef, 0x37, 0x80, 0x55, 0x03, 0xd7, 0xf5, 0x47, 0xa3, 0xfd, 0xd7, 0x0d, 0x24, 0x3f, 0xdb, 0x87, 0xca, 0x0d, 0x86, 0xf2, 0xa1, 0xf6, 0xb6, 0xe1, 0x32, 0xa3, 0xb1, 0x6c, 0x18, 0xf3, 0x81, 0x01, 0x48, 0x88, 0x18, 0xf9, 0xbe, 0xa0, 0xdf, 0x28, 0x58, 0x86, 0xba, 0xac, 0x01, 0x00, 0xaa, 0x37, 0xa6, 0x09, 0xe1, 0x5b, 0xc8, 0x09, 0xdb, 0xff, 0x62, 0xc8, 0x3e, 0x63, 0xfc, 0xe0, 0x4d, 0xcc, 0xb0, 0xd6, 0x27, 0x8d, 0xfc, 0x71, 0xd3, 0x82, 0x05, 0x0b, 0xc0, 0xdd, 0x39, 0x9d, 0x6a, 0x85, 0xbc, 0xe2, 0xf9, 0xb1, 0x2b, 0x05, 0x5e, 0x53, 0x0f, 0xdf, 0xd1, 0x02, 0xe9, 0x9c, 0x85, 0xb0, 0x04, 0x8d, 0xb0, 0xe7, 0xc1, 0xe4, 0xde, 0x1f, 0xbb, 0xf2, 0x06, 0x27, 0x8b, 0x5c, 0xbb, 0x95, 0x53, 0x43, 0x91, 0x9b, 0x05, 0xc2, 0x93, 0x44, 0x2e, 0xf2, 0x4b, 0x09, 0xd9, 0xfd, 0x53, 0x04, 0xae, 0x1a, 0x09, 0x5c, 0xd2, 0x66, 0x34, 0x8d, 0x7d, 0xa5, 0xc8, 0x78, 0xb7, 0xa0, 0xae, 0x38, 0x2b, 0x12, 0xcb, 0x50, 0x58, 0xed, 0xed, 0x92, 0x68, 0x7d, 0x6f, 0x51, 0xbc, 0xab, 0xc2, 0x6d, 0xce, 0x8a, 0xdb, 0x6f, 0x31, 0xe5, 0xe9, 0xf7, 0x9a, 0xf5, 0x73, 0x73, 0x55, 0x0e, 0xd5, 0x6f, 0xf3, 0x96, 0x17, 0xe6, 0x15, 0x45, 0x83, 0x5a, 0x35, 0x93, 0x11, 0xef, 0x8c, 0x59, 0x9d, 0xc5, 0xed, 0x39, 0xa1, 0x86, 0xaa, 0x72, 0xc7, 0xed, 0x12, 0xa9, 0x90, 0x07, 0xf6, 0x25, 0xd9, 0x08, 0xdb, 0x5b, 0x42, 0xfc, 0x47, 0x42, 0xae, 0x84, 0xf2, 0xd5, 0x84, 0xe5, 0x6b, 0xaa, 0x9e, 0x14, 0x01, 0x9b, 0x97, 0xbe, 0xbd, 0x17, 0x84, 0xec, 0x6a, 0x90, 0x9c, 0x29, 0x97, 0xbd, 0x44, 0x30, 0xd0, 0x2b, 0xbf, 0xfd, 0x29, 0xaa, 0x6f, 0x7b, 0xca, 0x37, 0x3e, 0x00, 0xe3, 0x03, 0xc9, 0x7c, 0x9c, 0x29, 0x3b, 0x7b, 0x72, 0xda, 0xb0, 0xc4, 0x70, 0x79, 0x49, 0x3f, 0x41, 0xdc, 0x60, 0x97, 0x24, 0x5a, 0xdb, 0x95, 0x5f, 0x34, 0xb7, 0xc2, 0xb5, 0x7e, 0x6f, 0xa4, 0xb1, 0x3f, 0xaf, 0x64, 0x6e, 0x75, 0x34, 0xb3, 0xd2, 0x94, 0x25, 0x65, 0x32, 0x6d, 0x59, 0x4e, 0x5b, 0xb6, 0x43, 0x9d, 0x19, 0xa8, 0xca, 0xe9, 0xaa, 0x8f, 0x47, 0xa3, 0x31, 0xa5, 0xc2, 0x64, 0x23, 0x31, 0x15, 0x1d, 0x85, 0x0d, 0xc1, 0xdd, 0xd1, 0xc0, 0xe6, 0xd2, 0xae, 0x52, 0xbb, 0x29, 0x6f, 0x66, 0x81, 0x45, 0x2e, 0xd7, 0xc8, 0xed, 0x56, 0xad, 0xc6, 0x1e, 0xd0, 0xd9, 0xc3, 0x56, 0xe5, 0x22, 0x50, 0x52, 0x90, 0x9b, 0x0f, 0x27, 0x8f, 0x8a, 0x11, 0x72, 0xf7, 0xe1, 0xda, 0xfb, 0x1d, 0xb6, 0x23, 0x40, 0x99, 0x8e, 0xed, 0x08, 0xe2, 0xac, 0x40, 0x86, 0x84, 0x49, 0x82, 0x1d, 0xcb, 0xf4, 0xcb, 0x9a, 0x0f, 0x62, 0x45, 0xc5, 0x94, 0x20, 0xd2, 0x91, 0xdf, 0x46, 0x14, 0xe9, 0x60, 0x1f, 0xfb, 0x57, 0xf0, 0x33, 0x95, 0xaa, 0x6d, 0x13, 0xff, 0x6b, 0x9d, 0x81, 0x52, 0x80, 0x79, 0x57, 0x03, 0xf5, 0xfb, 0x91, 0x42, 0xfe, 0xc5, 0x26, 0x45, 0xe6, 0xe9, 0xd3, 0x5c, 0x46, 0x15, 0x78, 0x83, 0x2f, 0xc4, 0x78, 0x20, 0x5f, 0x92, 0x95, 0xb0, 0x1d, 0x59, 0xc4, 0xa7, 0x09, 0x95, 0x17, 0xd0, 0x84, 0x07, 0x90, 0xb4, 0x1a, 0xca, 0xa5, 0x64, 0x3c, 0xa7, 0x37, 0x0d, 0xe0, 0x04, 0x21, 0x86, 0x0f, 0xa6, 0xa4, 0xbb, 0x28, 0xd3, 0x27, 0xc6, 0xfd, 0x5b, 0x2f, 0x9d, 0x18, 0xff, 0xef, 0xf8, 0x54, 0xd5, 0x77, 0x7d, 0xea, 0x77, 0x7a, 0x20, 0x9e, 0x17, 0x72, 0xaf, 0x2e, 0xe0, 0xd3, 0x09, 0x0a, 0x88, 0x36, 0x9e, 0x5e, 0x4f, 0x75, 0x12, 0xcc, 0xe4, 0x84, 0xb1, 0x23, 0x02, 0xc6, 0x22, 0x59, 0x65, 0x3d, 0xe5, 0x8e, 0x70, 0xf3, 0x50, 0x45, 0xc3, 0x9a, 0x40, 0x96, 0x5a, 0x9f, 0x61, 0x75, 0x67, 0xbb, 0xcc, 0x51, 0xaf, 0xbe, 0xba, 0xc2, 0x12, 0xb3, 0x74, 0x55, 0x83, 0xd5, 0xfe, 0xec, 0x9f, 0x82, 0xd7, 0x65, 0xae, 0xb2, 0x8e, 0xbc, 0xf2, 0x81, 0xc6, 0x40, 0xdc, 0x57, 0xa1, 0xcd, 0xd4, 0x64, 0xaa, 0x5d, 0x79, 0xee, 0xd2, 0x2a, 0xa5, 0xd4, 0x04, 0xd6, 0x95, 0xdb, 0x83, 0x02, 0xcf, 0x6d, 0x83, 0xf4, 0x9e, 0x0b, 0xe9, 0xad, 0x26, 0xb2, 0x89, 0xe7, 0x9e, 0xd4, 0x02, 0xa1, 0x40, 0x07, 0x22, 0xb4, 0x05, 0xb6, 0x97, 0xa4, 0x97, 0x8a, 0xa6, 0x8e, 0xa1, 0x94, 0xa9, 0x63, 0x35, 0x95, 0xa4, 0xef, 0x24, 0xbc, 0xe2, 0x89, 0x0b, 0x86, 0xa8, 0x24, 0x55, 0x2f, 0x73, 0x81, 0xf0, 0x04, 0xd5, 0x37, 0x3f, 0xe1, 0x1b, 0x6e, 0xc6, 0x74, 0xe3, 0x00, 0x91, 0x9d, 0xe5, 0x76, 0x1a, 0xf5, 0x32, 0x09, 0xa1, 0x06, 0xea, 0x09, 0x7b, 0xcb, 0x04, 0x20, 0xf2, 0xa5, 0xd6, 0x16, 0xc1, 0x99, 0x0e, 0x66, 0xdb, 0x72, 0x3c, 0xda, 0x44, 0xa9, 0x23, 0xcf, 0xcd, 0xd5, 0x94, 0xe9, 0x72, 0x0d, 0xb6, 0x86, 0x50, 0xa0, 0xa5, 0xdc, 0x1f, 0x68, 0x18, 0x28, 0xaf, 0x58, 0x54, 0xe7, 0xed, 0x7a, 0x48, 0x16, 0x34, 0x6f, 0x1f, 0xd6, 0xba, 0xa3, 0xd6, 0x8a, 0x5a, 0xad, 0x27, 0xc7, 0x52, 0xdc, 0x08, 0x6e, 0x34, 0xe7, 0x98, 0x0d, 0x56, 0x73, 0x51, 0x67, 0x49, 0x61, 0x5f, 0x5d, 0x28, 0xd0, 0xb0, 0xa8, 0x6c, 0xc5, 0xda, 0x0f, 0x54, 0x1a, 0xa1, 0x96, 0x1b, 0x24, 0xe4, 0x2c, 0x48, 0x43, 0x2d, 0xe1, 0x4d, 0xb8, 0x00, 0x26, 0x21, 0x25, 0x46, 0x4f, 0x2e, 0x4e, 0xb1, 0x6b, 0x2d, 0xa1, 0xd5, 0x86, 0xd3, 0xf4, 0x37, 0x6a, 0xc2, 0xba, 0x42, 0xce, 0x92, 0x30, 0xfc, 0xbf, 0x68, 0x99, 0xda, 0xc0, 0xad, 0x04, 0x5a, 0x9d, 0x9e, 0xff, 0xa1, 0x52, 0x47, 0xfe, 0xae, 0x45, 0x65, 0x52, 0x8c, 0xbd, 0x2e, 0xe5, 0x54, 0x52, 0xd2, 0x14, 0xb0, 0xea, 0x95, 0x4b, 0x70, 0xfc, 0x07, 0x1a, 0xaf, 0x0e, 0xc8, 0x0b, 0x33, 0x10, 0xaa, 0x12, 0xf1, 0x5e, 0x42, 0x6e, 0x86, 0x9a, 0x6e, 0x0e, 0x56, 0x77, 0x85, 0x61, 0xb3, 0x5f, 0xde, 0xdc, 0xb2, 0x9a, 0x4e, 0x8e, 0xdc, 0x37, 0x5c, 0x34, 0x44, 0x27, 0x47, 0xef, 0x5b, 0x9f, 0xa4, 0xfa, 0xf6, 0x27, 0x7d, 0xcb, 0x43, 0xf0, 0x48, 0x1a, 0xd5, 0x2a, 0x40, 0x14, 0xc7, 0x82, 0x7e, 0xa3, 0x5e, 0x65, 0x57, 0xdb, 0xe1, 0x78, 0x66, 0x80, 0x0c, 0xc1, 0x06, 0x14, 0x4f, 0x96, 0x47, 0x85, 0xc3, 0x39, 0xa9, 0xaa, 0xaf, 0xf1, 0x1b, 0xcc, 0x3f, 0xa0, 0xbd, 0xbc, 0x84, 0x73, 0x66, 0x99, 0xf3, 0x6b, 0x4a, 0xca, 0x2c, 0x01, 0xb3, 0x32, 0x2f, 0x16, 0x2b, 0xcc, 0xb0, 0x04, 0x47, 0x6b, 0x2b, 0xfc, 0xf5, 0x8b, 0x4a, 0x4b, 0x17, 0xd5, 0x05, 0x2a, 0x6a, 0x0b, 0x0b, 0x3c, 0x55, 0xf3, 0x0a, 0x0b, 0xbb, 0x2a, 0x3d, 0x05, 0xc3, 0x55, 0x79, 0xce, 0x2c, 0xab, 0xb2, 0xb0, 0x32, 0xaf, 0x8a, 0x34, 0x64, 0x5a, 0xbc, 0x9a, 0x82, 0xd2, 0xd2, 0x02, 0x8d, 0xd7, 0x92, 0x39, 0x66, 0xec, 0xad, 0xca, 0x9f, 0x55, 0xe2, 0x74, 0x96, 0xcc, 0xca, 0xaf, 0xea, 0x35, 0x1a, 0x3b, 0x4b, 0xb2, 0x66, 0xa2, 0x58, 0xb5, 0x99, 0x59, 0x25, 0x02, 0xee, 0x0c, 0xfc, 0x97, 0xac, 0x87, 0x63, 0xa1, 0x80, 0xdc, 0x2a, 0x98, 0xf0, 0xa9, 0x05, 0x5d, 0x3a, 0x65, 0xe6, 0x11, 0x75, 0x6a, 0x51, 0x93, 0x0e, 0xf8, 0xb0, 0x26, 0xad, 0x4d, 0xba, 0xf5, 0x60, 0xaf, 0x50, 0x9d, 0x4e, 0x76, 0xaa, 0x1a, 0x2d, 0x04, 0x7f, 0xbc, 0x5d, 0x51, 0x52, 0x92, 0xa8, 0x2d, 0x28, 0x49, 0x6c, 0x51, 0x3b, 0x55, 0xb9, 0xe5, 0xc1, 0x96, 0x72, 0x9f, 0xaf, 0x6e, 0x41, 0x69, 0x69, 0x5f, 0xc2, 0x93, 0x61, 0xf6, 0x19, 0x8f, 0xca, 0x03, 0xa6, 0x5d, 0x24, 0x53, 0x56, 0x96, 0x28, 0x8c, 0x57, 0x55, 0xf1, 0x2f, 0xe8, 0x3c, 0x9c, 0xc9, 0x16, 0x6f, 0x2b, 0xc8, 0x9e, 0x53, 0x13, 0x74, 0x57, 0x75, 0xc5, 0x03, 0x75, 0xd5, 0x95, 0xee, 0xdf, 0xe0, 0xe9, 0x49, 0x00, 0xfe, 0x28, 0x6c, 0xca, 0x7e, 0xb8, 0xb7, 0x10, 0xf3, 0xa8, 0x41, 0x72, 0x57, 0x21, 0xe4, 0x7d, 0x07, 0x53, 0x79, 0xdf, 0x67, 0x75, 0x79, 0xae, 0x39, 0x54, 0x5c, 0x97, 0xef, 0x9d, 0x5b, 0xe6, 0x31, 0x31, 0xf3, 0x5d, 0x1e, 0x41, 0x6f, 0x5b, 0x38, 0xfe, 0x25, 0xb3, 0x91, 0xb9, 0x87, 0x08, 0x82, 0x47, 0xc9, 0x7d, 0x18, 0x39, 0xe0, 0x01, 0xc2, 0x42, 0x3e, 0x0f, 0xa0, 0x9e, 0x4c, 0xe5, 0xc2, 0xdf, 0x02, 0x72, 0xc1, 0xf3, 0x29, 0x3d, 0xaf, 0x1f, 0x5e, 0xdf, 0x29, 0x5e, 0xbf, 0x47, 0xbc, 0xde, 0x84, 0xaf, 0x7f, 0x98, 0xd2, 0x4d, 0xbd, 0x1e, 0x52, 0x6b, 0x90, 0x38, 0x4d, 0x07, 0xe8, 0x1b, 0x10, 0xfe, 0x11, 0xe4, 0x9b, 0x34, 0x05, 0xe8, 0x79, 0x0c, 0x82, 0xc2, 0x42, 0x50, 0xa1, 0x28, 0x36, 0x00, 0xe9, 0x22, 0x34, 0x68, 0xe3, 0x26, 0x92, 0x83, 0xc4, 0x10, 0x06, 0x84, 0x02, 0x43, 0x07, 0xf8, 0x2b, 0x4e, 0xf2, 0xab, 0xc1, 0x8d, 0x27, 0xc1, 0x51, 0x72, 0x1e, 0xdf, 0x0d, 0x1e, 0x3c, 0x06, 0xee, 0xe7, 0x71, 0x82, 0x1f, 0xfc, 0x4d, 0x9d, 0x27, 0xff, 0xf6, 0x6d, 0x79, 0xd9, 0x47, 0xa9, 0x25, 0x17, 0xbf, 0x4f, 0xfe, 0xed, 0x18, 0x6e, 0xcb, 0x4e, 0xe2, 0x2d, 0x5a, 0x49, 0xf7, 0xc3, 0xb6, 0xb0, 0x8f, 0xb3, 0x28, 0x3f, 0xdf, 0x2f, 0x03, 0x41, 0x19, 0x28, 0x96, 0x01, 0xa3, 0x0c, 0x50, 0xf7, 0xf0, 0x77, 0xf3, 0x27, 0x97, 0x81, 0x01, 0xfe, 0xf4, 0x08, 0x98, 0x07, 0xe6, 0x2c, 0xe3, 0x1f, 0x01, 0x0b, 0xdf, 0x06, 0x8b, 0xf8, 0x93, 0xa3, 0x60, 0x00, 0xcc, 0x1d, 0xe5, 0x1f, 0x06, 0x73, 0x46, 0xf9, 0x47, 0xf8, 0xbb, 0x85, 0xfc, 0x84, 0x25, 0xc4, 0x53, 0xb4, 0x9f, 0xbe, 0x1a, 0x7e, 0x33, 0x12, 0x11, 0x22, 0x3b, 0x11, 0x46, 0xea, 0x55, 0x32, 0x11, 0x98, 0x46, 0xd9, 0xf0, 0x53, 0x32, 0x3b, 0xa0, 0xb6, 0xe9, 0x17, 0x7a, 0x88, 0xda, 0x05, 0xa6, 0x44, 0x5d, 0x72, 0x53, 0x7e, 0x2f, 0xa1, 0x5e, 0xb8, 0x58, 0x09, 0x16, 0x4c, 0x85, 0x3d, 0x99, 0xfa, 0x1b, 0xf6, 0x0c, 0x9c, 0xb1, 0x09, 0x07, 0x6c, 0x22, 0x0e, 0xc7, 0xd8, 0xdc, 0x29, 0x07, 0xf0, 0x7c, 0x3e, 0x0a, 0xce, 0x50, 0xe7, 0xa9, 0xe3, 0x38, 0x67, 0xdf, 0x93, 0x70, 0x8a, 0xad, 0x5d, 0x7c, 0x09, 0xe5, 0x92, 0x89, 0xfb, 0x28, 0x94, 0x24, 0x49, 0x3f, 0x4c, 0xc3, 0x9a, 0x63, 0xe0, 0xaf, 0x22, 0x86, 0xe5, 0x31, 0xe2, 0x2c, 0xf5, 0x39, 0xbd, 0x07, 0xb2, 0xbd, 0x75, 0x02, 0xcb, 0xc9, 0x22, 0x24, 0x2c, 0x2b, 0x59, 0x8a, 0x6d, 0xfd, 0x23, 0x52, 0xc0, 0xca, 0x65, 0x24, 0x94, 0x8c, 0xcc, 0x12, 0x1a, 0xe0, 0xc7, 0x0b, 0x3a, 0x27, 0x25, 0x94, 0x01, 0x4d, 0x84, 0xe0, 0xc5, 0x12, 0xc1, 0x7b, 0xf1, 0xcd, 0x97, 0xe2, 0xaa, 0x9f, 0x88, 0xb7, 0x26, 0x03, 0x47, 0x38, 0x1c, 0x38, 0x82, 0x00, 0xbc, 0x24, 0x5e, 0x84, 0x5e, 0x86, 0xff, 0x16, 0x4a, 0x0a, 0xf5, 0xe4, 0xcf, 0xfb, 0xc1, 0xb5, 0x7c, 0xce, 0xa6, 0xeb, 0x36, 0xf1, 0x63, 0xa0, 0xb9, 0x9f, 0x9f, 0x0f, 0xfe, 0xb1, 0xf6, 0xba, 0xb5, 0x7f, 0xa3, 0xc0, 0xf3, 0xc7, 0xbe, 0xf8, 0xe2, 0xd8, 0xf3, 0xc7, 0xfe, 0xf1, 0x8f, 0x63, 0x88, 0x06, 0x0f, 0x81, 0xcd, 0xd4, 0xef, 0xa8, 0xd7, 0x70, 0x16, 0xc3, 0x91, 0x04, 0xaa, 0x8c, 0x41, 0x69, 0x91, 0x95, 0x11, 0x25, 0xe9, 0xe4, 0x7a, 0x48, 0x86, 0xa5, 0x9b, 0x05, 0xfc, 0x48, 0x1f, 0x82, 0xc2, 0x02, 0x12, 0x9c, 0xcd, 0x08, 0x1b, 0x27, 0x61, 0x07, 0x65, 0x70, 0x4c, 0xe1, 0x28, 0x2e, 0x4a, 0xda, 0xae, 0x96, 0xa2, 0xfc, 0xe4, 0x48, 0x32, 0x99, 0x3a, 0x75, 0x39, 0xbc, 0x8c, 0x45, 0xa3, 0x3e, 0x71, 0x57, 0x72, 0xf8, 0x7b, 0x13, 0x56, 0xbb, 0x0d, 0x10, 0x45, 0x05, 0x91, 0x70, 0xc0, 0x87, 0x12, 0x18, 0x4c, 0x06, 0x94, 0x33, 0x86, 0x32, 0xc6, 0xe4, 0x62, 0xf4, 0x7c, 0xb1, 0x31, 0x0d, 0xa5, 0x2c, 0x28, 0x88, 0x37, 0xed, 0x65, 0x8e, 0x53, 0x5a, 0x47, 0x49, 0x8e, 0x57, 0x6a, 0xf1, 0x6e, 0xa8, 0x2a, 0x5a, 0x36, 0x34, 0x10, 0x75, 0x94, 0xe6, 0x7a, 0x64, 0x4e, 0xdf, 0xa6, 0xda, 0xd8, 0xe8, 0xd0, 0xa2, 0xec, 0x6f, 0x38, 0x47, 0x0d, 0xa9, 0xf4, 0x26, 0xf9, 0xce, 0xbc, 0x1c, 0x57, 0xd0, 0xad, 0xd2, 0x9b, 0x15, 0x5b, 0x8a, 0x0a, 0xdc, 0x41, 0x37, 0xff, 0xe0, 0x74, 0x47, 0xd1, 0x58, 0x9f, 0x24, 0x3e, 0xa0, 0x1e, 0xa4, 0x17, 0x12, 0x99, 0x70, 0xcd, 0x28, 0x65, 0x28, 0xdf, 0x9e, 0x95, 0x18, 0x8c, 0x8c, 0x03, 0xd8, 0x01, 0x88, 0x09, 0xe2, 0xee, 0xed, 0x70, 0x51, 0x51, 0x18, 0x9c, 0xd6, 0x71, 0x17, 0x3f, 0xa7, 0xe4, 0x9c, 0x8e, 0x3c, 0xa7, 0x57, 0x2a, 0xf5, 0x6f, 0x2b, 0x6c, 0xdc, 0x61, 0x70, 0x7c, 0x16, 0x38, 0x7e, 0x98, 0xb3, 0x89, 0xbe, 0xbc, 0xdf, 0xc3, 0x67, 0xfd, 0x4d, 0x7c, 0x56, 0xa6, 0x80, 0x8f, 0x81, 0x03, 0x76, 0xfd, 0xf1, 0xe2, 0x40, 0xd0, 0x8f, 0x32, 0x71, 0xa9, 0xbf, 0x5d, 0xfc, 0x9c, 0xd3, 0x81, 0xd3, 0xa1, 0x58, 0x2c, 0xc4, 0xcf, 0xd5, 0x71, 0xe4, 0x2f, 0x66, 0xf1, 0x2b, 0xd0, 0x03, 0xde, 0x4e, 0x3d, 0x13, 0xdb, 0x39, 0x66, 0xc1, 0xb5, 0xf7, 0x2c, 0xa4, 0xbe, 0x1a, 0xd5, 0x91, 0x97, 0x4a, 0x48, 0x86, 0xc6, 0x72, 0x68, 0x31, 0x4a, 0xb4, 0x22, 0xd8, 0x4b, 0xa7, 0x35, 0x4d, 0xd3, 0x6a, 0x5a, 0x8d, 0xa6, 0x90, 0x86, 0xcb, 0x94, 0x41, 0x6d, 0xac, 0x18, 0xad, 0x41, 0xfd, 0x04, 0xab, 0x19, 0xa4, 0xce, 0x5d, 0xac, 0xa2, 0xce, 0xf1, 0xab, 0xee, 0x44, 0x40, 0xb8, 0x77, 0x7e, 0x70, 0x8c, 0xfc, 0xfa, 0x18, 0xff, 0x3d, 0x30, 0x7a, 0x0c, 0xac, 0xe7, 0xaf, 0x43, 0x73, 0xe7, 0x2c, 0x31, 0x8b, 0x7a, 0x9e, 0x46, 0xd5, 0xdf, 0xf4, 0x08, 0x51, 0x0a, 0x3e, 0x46, 0xad, 0x62, 0x59, 0xa2, 0x69, 0x7a, 0xd6, 0x83, 0x1f, 0x8f, 0x33, 0x20, 0xd2, 0x29, 0x75, 0x96, 0x92, 0x5f, 0xfc, 0x1c, 0x94, 0xe6, 0xc5, 0x6c, 0x05, 0x46, 0x70, 0xca, 0xa6, 0x41, 0xf4, 0xd2, 0xd8, 0xe0, 0xbb, 0x5e, 0xd6, 0x28, 0x59, 0xb5, 0xfc, 0x45, 0xa5, 0x83, 0xbb, 0x1a, 0xec, 0xe8, 0x04, 0x5b, 0x0f, 0x70, 0x4e, 0x05, 0x7c, 0xe7, 0x4b, 0xf0, 0x9d, 0xbf, 0x9e, 0x78, 0xa7, 0x5e, 0x25, 0x81, 0x93, 0xeb, 0x9b, 0xdf, 0x29, 0x12, 0x14, 0x25, 0x38, 0x0b, 0x14, 0x7d, 0x09, 0xbd, 0x13, 0xfd, 0xd5, 0xd8, 0xc0, 0x29, 0x63, 0x81, 0x2d, 0x96, 0xc7, 0x2f, 0xb0, 0x69, 0xe0, 0x3b, 0xdf, 0xe8, 0xe4, 0xaf, 0xba, 0x9a, 0x73, 0x28, 0x5f, 0x94, 0xab, 0x59, 0xa5, 0xe6, 0x9c, 0xc2, 0xc9, 0x1d, 0x40, 0x63, 0x74, 0x03, 0x88, 0x53, 0x9f, 0x41, 0xbe, 0xca, 0x10, 0x55, 0x62, 0xfd, 0x78, 0xfc, 0xb6, 0x11, 0xbc, 0xb6, 0xe7, 0xa1, 0x8d, 0x4f, 0x2f, 0xf2, 0x29, 0xb5, 0x59, 0x31, 0xd7, 0x45, 0xa9, 0x97, 0x93, 0x8f, 0xf7, 0x3e, 0x89, 0x0a, 0x01, 0xe0, 0x34, 0x4c, 0x0c, 0x82, 0xfb, 0xd9, 0x7d, 0x63, 0x75, 0xd4, 0x20, 0x88, 0x03, 0xe7, 0x31, 0x31, 0xe6, 0xe2, 0x9f, 0xac, 0x99, 0x79, 0x88, 0x50, 0x42, 0xae, 0x99, 0x97, 0x88, 0x1a, 0xf4, 0x24, 0x86, 0x15, 0x07, 0x04, 0x43, 0x02, 0x06, 0x69, 0x54, 0xc9, 0x38, 0xa7, 0x09, 0xc4, 0x28, 0x5d, 0x28, 0xe8, 0xf7, 0xe1, 0xcd, 0x15, 0x10, 0x77, 0xe6, 0x70, 0x97, 0x0a, 0xc4, 0xac, 0x41, 0x23, 0xca, 0x1b, 0x4e, 0x16, 0xca, 0xc3, 0x79, 0x57, 0x46, 0x37, 0xa3, 0xaf, 0xde, 0xf4, 0xc0, 0xb2, 0xa5, 0x0f, 0x6d, 0xaf, 0xa7, 0x8e, 0x5d, 0x5c, 0x39, 0x63, 0xff, 0x73, 0x5b, 0x6e, 0xf9, 0x68, 0xfe, 0xf2, 0xbc, 0xed, 0xb7, 0x3d, 0xbb, 0xee, 0x0c, 0xff, 0xb7, 0x17, 0x97, 0x5f, 0xf1, 0x02, 0x50, 0xdd, 0xfd, 0x08, 0x90, 0x9f, 0x1d, 0xa0, 0x2f, 0x2c, 0xa3, 0xd6, 0x0d, 0x9c, 0xda, 0x54, 0xd7, 0xb0, 0xfd, 0x81, 0x41, 0xcd, 0x38, 0xb1, 0xe0, 0xcc, 0xfe, 0x59, 0x1b, 0xb7, 0xb7, 0x35, 0xac, 0x6a, 0x0d, 0xad, 0x79, 0x11, 0x28, 0x6e, 0x3f, 0x01, 0x32, 0x7e, 0xbe, 0x6e, 0xe3, 0xcf, 0xf9, 0xaf, 0x6e, 0x44, 0x6d, 0xaf, 0x1a, 0xff, 0x84, 0x76, 0x31, 0x67, 0x60, 0xdb, 0x2b, 0x91, 0xcf, 0xb0, 0xb2, 0x2c, 0x37, 0xc7, 0xcd, 0xa0, 0x50, 0x8b, 0x66, 0x3a, 0x09, 0xb0, 0x34, 0xa9, 0xe1, 0x80, 0x88, 0x15, 0x66, 0x85, 0x4c, 0x06, 0x94, 0x22, 0x83, 0xa1, 0xb6, 0xd0, 0x02, 0x46, 0x66, 0xde, 0x00, 0xce, 0x60, 0x64, 0x52, 0xd6, 0x35, 0x56, 0x97, 0x8a, 0xbe, 0x32, 0x0a, 0x88, 0x84, 0x95, 0x28, 0xb9, 0x91, 0xd2, 0x94, 0x6d, 0x18, 0x6e, 0xd3, 0x8e, 0xfe, 0x60, 0x57, 0x43, 0xc9, 0xc5, 0x8b, 0x59, 0x9d, 0x41, 0x65, 0xf1, 0xac, 0x65, 0xdb, 0x0f, 0xb7, 0x56, 0xf5, 0x55, 0xd8, 0x17, 0x3e, 0xfc, 0xc9, 0x31, 0xa0, 0xf5, 0xc5, 0x3c, 0xbe, 0x86, 0xea, 0x62, 0xc3, 0x5d, 0xe6, 0xee, 0x0d, 0x87, 0xdb, 0x6b, 0x57, 0xb4, 0x84, 0x5c, 0xd5, 0x6e, 0xc6, 0x38, 0xff, 0xfb, 0x6f, 0x6c, 0x0d, 0x0f, 0xaf, 0xdf, 0x55, 0x57, 0x5d, 0x3f, 0xd6, 0x25, 0xd5, 0x2a, 0x46, 0x7f, 0xfe, 0xe8, 0xb1, 0x15, 0xe5, 0x91, 0x99, 0x83, 0x25, 0x83, 0x8f, 0xdd, 0xb2, 0xbb, 0x40, 0x61, 0x54, 0xac, 0x3e, 0xda, 0xe3, 0xf3, 0x54, 0x75, 0x17, 0x85, 0x5a, 0xf6, 0x2e, 0x29, 0x89, 0x6f, 0xfd, 0x8f, 0xeb, 0x75, 0x52, 0xb5, 0x14, 0xc9, 0xb5, 0x51, 0xe2, 0x4b, 0xa6, 0x8b, 0xba, 0x0f, 0xdb, 0x69, 0x3a, 0x88, 0xb6, 0x44, 0x73, 0x4b, 0xb1, 0x59, 0x4d, 0xa3, 0x68, 0x12, 0x89, 0x1c, 0xee, 0x7a, 0x84, 0x6a, 0x75, 0x32, 0x52, 0x4a, 0x21, 0xcb, 0x77, 0x37, 0xfe, 0x42, 0x30, 0x7d, 0x2c, 0x60, 0x08, 0xa6, 0x23, 0x1c, 0x42, 0x1e, 0xbf, 0x19, 0x0d, 0x95, 0xe5, 0x85, 0xf9, 0xa1, 0x8e, 0x70, 0x47, 0xba, 0xdf, 0x4f, 0x91, 0x96, 0x22, 0x84, 0x19, 0x99, 0x83, 0x72, 0xa3, 0xfe, 0x4e, 0xa4, 0x75, 0xa6, 0x03, 0x3e, 0x07, 0xd3, 0x71, 0x19, 0x51, 0xfe, 0x34, 0x45, 0xeb, 0x10, 0xde, 0x9b, 0x35, 0xa7, 0xc2, 0xe3, 0x2d, 0x8f, 0xe5, 0x99, 0x4c, 0x79, 0xb1, 0xf2, 0xb1, 0x9f, 0xa4, 0xbe, 0x7a, 0x3d, 0x95, 0x39, 0xf0, 0x64, 0x25, 0x79, 0x63, 0xf5, 0xfa, 0x2e, 0xa8, 0xfa, 0xad, 0xaf, 0x2e, 0xee, 0x6b, 0x4b, 0xd8, 0xec, 0x89, 0xd6, 0xfe, 0xe2, 0xea, 0x75, 0xdd, 0x85, 0x85, 0xdd, 0xeb, 0xaa, 0x8b, 0xfb, 0x5b, 0x13, 0x76, 0x5b, 0xa2, 0xad, 0x0f, 0xdc, 0x62, 0xc8, 0xa9, 0xcf, 0xcd, 0x69, 0xc8, 0x31, 0xd8, 0x0b, 0xea, 0xfc, 0x81, 0xda, 0x02, 0xbb, 0xbd, 0xa0, 0x36, 0xe0, 0xaf, 0x2b, 0xb0, 0x1b, 0xe0, 0xc1, 0xdc, 0xfa, 0x1c, 0x03, 0xa9, 0x2a, 0x98, 0xbf, 0xab, 0xad, 0x6d, 0xd7, 0xfc, 0x02, 0x77, 0x49, 0x4b, 0x38, 0xdc, 0x52, 0xe2, 0x9e, 0xfa, 0x5b, 0xd0, 0x11, 0xc7, 0x3f, 0xa5, 0x0b, 0x99, 0x87, 0x09, 0x8e, 0xa8, 0x22, 0x36, 0x9f, 0xcd, 0x61, 0xd2, 0x6a, 0xa3, 0x04, 0x52, 0x01, 0x0a, 0x62, 0x91, 0x54, 0x04, 0xdd, 0x87, 0x72, 0x60, 0x57, 0x08, 0xe0, 0xa8, 0xe2, 0x0c, 0xb1, 0x7e, 0xa7, 0x0b, 0xd1, 0xb6, 0x28, 0xa1, 0x07, 0x04, 0xaa, 0x60, 0x9b, 0x9d, 0xe5, 0x71, 0x61, 0xe8, 0x2e, 0x0e, 0x70, 0x49, 0x5f, 0x6a, 0x8a, 0x88, 0x06, 0x2a, 0x69, 0x7d, 0x20, 0x27, 0xc7, 0xf9, 0xc5, 0x8b, 0xd2, 0x8b, 0x18, 0x80, 0x2f, 0xab, 0xd7, 0x2e, 0x6c, 0xd0, 0xc9, 0xfd, 0x15, 0x73, 0x4a, 0x3b, 0x77, 0xce, 0x8d, 0x6c, 0x6e, 0x08, 0xb6, 0x74, 0xf6, 0xc4, 0xfa, 0x6e, 0x5e, 0x51, 0xd6, 0x7c, 0xec, 0xf7, 0xc7, 0x7b, 0x8e, 0x2e, 0x2f, 0x6f, 0x3b, 0xf4, 0xec, 0xea, 0x91, 0xef, 0xe5, 0x91, 0xbe, 0xda, 0x45, 0xe5, 0xe1, 0xc6, 0xd2, 0x2c, 0x95, 0xc2, 0xa0, 0x38, 0x5c, 0xb4, 0xf2, 0xbe, 0xb5, 0x59, 0x1d, 0x95, 0xfe, 0xf2, 0xd1, 0x1b, 0xe6, 0x5d, 0x79, 0x0d, 0x7d, 0xc6, 0x96, 0x65, 0x57, 0x37, 0x5f, 0xf7, 0xca, 0xee, 0x57, 0xf9, 0x47, 0x3e, 0x3b, 0x50, 0xb6, 0xf2, 0xd6, 0x81, 0x0d, 0xbf, 0x38, 0x3e, 0x6f, 0xce, 0x2c, 0x57, 0x85, 0x71, 0xe0, 0xa6, 0x25, 0x05, 0x3a, 0xb3, 0x2e, 0x43, 0x87, 0xf9, 0x3e, 0xcd, 0xdf, 0x4b, 0x7d, 0xc4, 0x2e, 0x82, 0x33, 0x0a, 0xd7, 0x11, 0x00, 0x84, 0xe8, 0x43, 0x41, 0x99, 0x3d, 0x48, 0xc6, 0x8a, 0xe0, 0xfc, 0xcb, 0xc8, 0xd6, 0x60, 0x38, 0x28, 0x98, 0xe4, 0x92, 0xd1, 0xfb, 0x54, 0x12, 0xaa, 0x51, 0xc8, 0x8f, 0x17, 0x0a, 0x7e, 0x81, 0xdf, 0x65, 0xd4, 0x0e, 0xed, 0x6d, 0xf3, 0xb6, 0xba, 0x14, 0xa4, 0x4c, 0xeb, 0x32, 0x97, 0xcf, 0x2b, 0xf3, 0x4a, 0xf6, 0x2a, 0x6b, 0x87, 0x85, 0x63, 0x14, 0x3a, 0x56, 0x36, 0xaf, 0xdc, 0x23, 0xa5, 0xdf, 0x59, 0x7a, 0x74, 0x20, 0x9a, 0xc1, 0xcd, 0x51, 0xaa, 0x25, 0x94, 0xa5, 0xa4, 0xb7, 0xfa, 0x42, 0xeb, 0x94, 0x03, 0xb8, 0x7d, 0x61, 0xc8, 0x75, 0xde, 0x62, 0x1f, 0x81, 0x9a, 0x5c, 0x38, 0x11, 0xc8, 0xc4, 0x95, 0x7f, 0xc4, 0x12, 0x24, 0x49, 0xe0, 0xd8, 0xe4, 0x46, 0xcf, 0x48, 0x18, 0xbc, 0x6e, 0x6c, 0x97, 0x93, 0xe1, 0xb8, 0x36, 0x04, 0x58, 0x30, 0x11, 0xb5, 0x8a, 0xab, 0x54, 0xbe, 0x35, 0xf6, 0xf6, 0xbd, 0xe7, 0xef, 0x74, 0x44, 0xc1, 0x1c, 0x93, 0x93, 0x39, 0xa4, 0x50, 0x33, 0x4c, 0x86, 0x14, 0xdc, 0xcb, 0x38, 0x75, 0xec, 0x23, 0x5f, 0xcd, 0x26, 0x6f, 0xf4, 0x45, 0xab, 0x8d, 0x1c, 0xbf, 0xdb, 0xdb, 0xe4, 0xb3, 0x97, 0xb9, 0x98, 0x00, 0x67, 0x80, 0x6f, 0x68, 0xe4, 0x1f, 0x66, 0x42, 0xb8, 0x46, 0xa0, 0x33, 0x61, 0xc3, 0x75, 0x3e, 0x44, 0x57, 0x06, 0xca, 0x25, 0x5b, 0x86, 0x91, 0xc5, 0xa7, 0x66, 0xce, 0x7b, 0x63, 0x6e, 0x26, 0x74, 0xf6, 0x65, 0xde, 0xf1, 0xf2, 0x59, 0xfa, 0x4f, 0xf4, 0xf9, 0xaf, 0xce, 0xb2, 0xcd, 0xe8, 0xef, 0xfd, 0xb8, 0x3f, 0x33, 0xf9, 0x87, 0xe9, 0x0b, 0xd8, 0xdf, 0x10, 0x49, 0x84, 0x94, 0xb0, 0x23, 0x80, 0x90, 0x62, 0xf4, 0x49, 0x42, 0x84, 0x2c, 0xc7, 0x99, 0x52, 0x18, 0x49, 0x12, 0xd2, 0xdc, 0xc2, 0x69, 0x45, 0xd8, 0x17, 0x28, 0x3d, 0xa0, 0x0c, 0x41, 0x02, 0x44, 0x06, 0x90, 0xec, 0x70, 0xd3, 0x17, 0x2e, 0xee, 0x22, 0xa3, 0x63, 0xbf, 0xa1, 0xf6, 0x5e, 0x68, 0x97, 0x2b, 0x50, 0x59, 0x67, 0x85, 0x9c, 0xfe, 0x21, 0x7d, 0x7e, 0xac, 0xa5, 0x74, 0xac, 0xe5, 0xfe, 0x46, 0xb5, 0x4b, 0x45, 0x7e, 0x49, 0x7e, 0xa6, 0x72, 0xab, 0x1a, 0xf1, 0x7a, 0x18, 0x1a, 0xbf, 0x81, 0xf6, 0x31, 0xff, 0x26, 0x54, 0x84, 0x0e, 0xe5, 0x1c, 0x69, 0x14, 0x12, 0x1a, 0x73, 0x0e, 0x24, 0xa4, 0x50, 0x39, 0x97, 0x51, 0x9d, 0x16, 0x89, 0x2a, 0xc8, 0x08, 0x50, 0x7a, 0x3e, 0x2b, 0xa1, 0x44, 0xba, 0x41, 0x5a, 0xca, 0x40, 0x21, 0xb5, 0xec, 0x2b, 0xb5, 0x4e, 0x9a, 0x61, 0xff, 0xfc, 0x53, 0x4e, 0x2f, 0x95, 0x1a, 0x39, 0xf0, 0x28, 0x3f, 0x74, 0x90, 0x1f, 0x02, 0x27, 0x0e, 0x92, 0xe7, 0x00, 0xef, 0x4a, 0x78, 0x6a, 0xab, 0x78, 0x82, 0x57, 0xfa, 0x1a, 0xbd, 0xde, 0x46, 0x2f, 0x38, 0x7f, 0xf1, 0x97, 0x54, 0x01, 0x55, 0x20, 0xe0, 0x18, 0x11, 0xb5, 0x92, 0x7e, 0xfa, 0x1c, 0x61, 0x46, 0xf3, 0x8b, 0x93, 0xe2, 0x7a, 0x57, 0x70, 0x96, 0x35, 0x99, 0x01, 0x98, 0x31, 0x15, 0xd6, 0x28, 0xbf, 0x14, 0x6f, 0x0d, 0x00, 0x42, 0x3c, 0x03, 0x08, 0xf5, 0xa3, 0x10, 0x76, 0x19, 0xce, 0x2d, 0x84, 0xf3, 0x85, 0x10, 0xbf, 0x46, 0xc9, 0xaa, 0x3a, 0xbd, 0x54, 0xc7, 0x7f, 0x5e, 0x18, 0x8a, 0x6f, 0xae, 0xa6, 0x3f, 0xbd, 0xa0, 0xaa, 0xde, 0x1c, 0xd7, 0x45, 0x2d, 0x40, 0xa9, 0x93, 0xea, 0xeb, 0xc8, 0xab, 0xc8, 0xaa, 0x46, 0x43, 0x44, 0xc7, 0x7f, 0x51, 0xe8, 0x90, 0x48, 0x7f, 0x28, 0x95, 0x70, 0x41, 0x33, 0x7f, 0x5e, 0x17, 0x31, 0x34, 0xa2, 0x76, 0x6c, 0x20, 0xea, 0x25, 0x23, 0xf4, 0x8b, 0x84, 0x49, 0x6c, 0x07, 0xea, 0xbb, 0x09, 0x80, 0x26, 0x80, 0x61, 0xea, 0x92, 0x78, 0x3b, 0x7d, 0x18, 0x1a, 0x57, 0x68, 0x07, 0xdc, 0x7f, 0x20, 0x14, 0x33, 0xec, 0x72, 0x81, 0xd2, 0x12, 0x4e, 0x25, 0x5c, 0xd0, 0xbb, 0x1a, 0x48, 0x46, 0xc6, 0xce, 0xa1, 0x76, 0x00, 0x79, 0x7e, 0x38, 0xbe, 0xa9, 0xfa, 0x82, 0x8a, 0xfe, 0x14, 0xb6, 0x23, 0x5c, 0xc0, 0xff, 0x1b, 0x37, 0x63, 0xef, 0xd8, 0x39, 0xd4, 0x0c, 0x20, 0xcb, 0x77, 0xa1, 0x66, 0x48, 0xdd, 0xf9, 0x40, 0x21, 0xb4, 0x02, 0x3d, 0xfc, 0x91, 0x71, 0x07, 0x13, 0x65, 0x35, 0x90, 0x1e, 0x9a, 0x84, 0xda, 0x6c, 0xca, 0x94, 0xd0, 0x48, 0x67, 0x20, 0x46, 0xad, 0x00, 0xe3, 0xa1, 0xb0, 0xf0, 0x1d, 0xa8, 0xc3, 0xfa, 0xf4, 0x9a, 0x21, 0xb9, 0x00, 0x30, 0xd1, 0x06, 0xd4, 0xb5, 0xcf, 0x51, 0xd7, 0x1e, 0x93, 0x4a, 0x32, 0x43, 0x66, 0xf8, 0xb6, 0x88, 0xbe, 0x91, 0xac, 0xa1, 0x7e, 0x53, 0x8f, 0xdb, 0x02, 0x69, 0xb2, 0xa5, 0xfa, 0x42, 0x26, 0xfd, 0x71, 0xf5, 0xd6, 0xb8, 0x3e, 0x6a, 0x81, 0x3d, 0x97, 0xea, 0xeb, 0xc9, 0x04, 0x7a, 0xe7, 0x0f, 0xf8, 0x07, 0x98, 0x1c, 0x38, 0xe7, 0xcc, 0x84, 0x2e, 0xc1, 0x69, 0x64, 0x24, 0xd4, 0x97, 0x51, 0x12, 0xd7, 0x20, 0xd1, 0x6a, 0x47, 0x6f, 0x85, 0x3b, 0x2d, 0x64, 0x05, 0x05, 0x06, 0xa3, 0x08, 0xe6, 0x9d, 0xac, 0x4a, 0x6d, 0x64, 0x72, 0xc6, 0x9e, 0x1b, 0x3b, 0x87, 0xde, 0x0c, 0xfb, 0xea, 0x4a, 0xbe, 0x19, 0x92, 0x3b, 0x62, 0xf8, 0xdb, 0xd8, 0x73, 0x64, 0x75, 0x3d, 0x1e, 0x8d, 0x02, 0xf4, 0x66, 0xfa, 0x5f, 0x17, 0xb8, 0xea, 0xad, 0xc5, 0x7a, 0x71, 0x34, 0x70, 0x5f, 0xa3, 0xe3, 0xff, 0x66, 0xba, 0x18, 0x3b, 0xa4, 0x79, 0x56, 0x22, 0xa8, 0x54, 0x90, 0xe8, 0xad, 0x97, 0x0b, 0x82, 0x31, 0x11, 0xa6, 0x76, 0xb7, 0x10, 0x34, 0x10, 0xf4, 0x04, 0x62, 0x5c, 0x51, 0x31, 0x9c, 0x03, 0x41, 0x31, 0x16, 0x03, 0x4e, 0x82, 0x58, 0x90, 0xe9, 0x5a, 0xec, 0x30, 0xb3, 0xb5, 0xbd, 0x17, 0xf7, 0xec, 0xa4, 0xae, 0x8c, 0xf6, 0x45, 0x2c, 0x95, 0x3e, 0xfe, 0x2a, 0x9b, 0x8f, 0x64, 0xec, 0x17, 0x7f, 0x43, 0xfa, 0xad, 0x60, 0x4b, 0x7d, 0xac, 0xb7, 0xf7, 0xe2, 0xbe, 0x9d, 0xd4, 0xae, 0x68, 0x87, 0x87, 0xa6, 0x4d, 0x4e, 0xa4, 0x69, 0x12, 0x07, 0x60, 0x1b, 0x7e, 0x80, 0xdb, 0x00, 0x77, 0x56, 0x78, 0xb0, 0x31, 0x28, 0x05, 0x7a, 0xf7, 0xbc, 0x09, 0xac, 0xa2, 0x3a, 0x2e, 0x8c, 0xf4, 0x22, 0xbf, 0x04, 0x12, 0xdf, 0xa0, 0x87, 0x14, 0xf0, 0x4a, 0x38, 0xb7, 0x04, 0xf9, 0x1c, 0x85, 0xe9, 0xc7, 0xfc, 0x60, 0xe7, 0xc5, 0x3d, 0xbd, 0xb5, 0xac, 0xd9, 0xb1, 0x98, 0x82, 0x8c, 0xd7, 0xc6, 0x5f, 0xe5, 0xab, 0xb4, 0x44, 0xfa, 0xa2, 0x6b, 0x77, 0x52, 0x3b, 0x7b, 0x7b, 0x63, 0xf5, 0xfc, 0x41, 0xab, 0x9f, 0xa4, 0xa2, 0x8b, 0x9d, 0x26, 0x9a, 0xf6, 0x74, 0x44, 0x85, 0xdc, 0x0d, 0xd8, 0xf7, 0x2c, 0xf8, 0x5e, 0x5f, 0xc2, 0xad, 0x56, 0x91, 0x64, 0x13, 0xea, 0x36, 0x89, 0x90, 0xdb, 0xd2, 0x7c, 0x09, 0x26, 0xc2, 0x58, 0x27, 0x86, 0x4a, 0x30, 0xc5, 0x45, 0x71, 0x23, 0x9a, 0x65, 0xa8, 0x36, 0x50, 0x31, 0x1c, 0x0c, 0x44, 0x84, 0x78, 0x31, 0xd3, 0x75, 0xf1, 0x57, 0xf0, 0x85, 0x60, 0x87, 0xf0, 0x42, 0xdc, 0x79, 0xdc, 0x10, 0x2a, 0x7a, 0xf1, 0x0d, 0xb0, 0xc4, 0x61, 0x06, 0x34, 0xe5, 0xee, 0x88, 0x52, 0xbb, 0x76, 0x5d, 0xdc, 0xdb, 0xdb, 0x5b, 0xd4, 0x08, 0x36, 0xdb, 0x7c, 0x00, 0xf7, 0x7b, 0x1c, 0xf6, 0x3b, 0x4c, 0x58, 0x50, 0xec, 0x91, 0x2a, 0x83, 0xc4, 0x9a, 0x29, 0x06, 0x8e, 0x23, 0xe7, 0x23, 0xca, 0x0f, 0x91, 0x42, 0x91, 0x1a, 0x8b, 0x51, 0x0b, 0xc9, 0xae, 0x8f, 0x14, 0x72, 0xc6, 0x02, 0xdc, 0x73, 0x3c, 0xf6, 0x46, 0x80, 0x8d, 0x78, 0xc5, 0xb0, 0x3d, 0x7a, 0xe3, 0x01, 0x48, 0x77, 0xf8, 0x92, 0xf6, 0x28, 0x75, 0x25, 0xa4, 0x42, 0x74, 0x7e, 0x36, 0xa4, 0x3c, 0xd8, 0x01, 0x29, 0x4f, 0x45, 0x49, 0xbf, 0x8d, 0x3f, 0xe4, 0xad, 0xb0, 0x44, 0xe6, 0x47, 0xa9, 0x9d, 0xbb, 0x2e, 0xee, 0x43, 0x94, 0xa7, 0x80, 0xd9, 0xb1, 0x24, 0xb5, 0xf6, 0xd1, 0x9a, 0x0b, 0x21, 0x5e, 0xe7, 0xd6, 0x49, 0x51, 0x34, 0x5c, 0x73, 0x48, 0x4f, 0x82, 0x26, 0x14, 0x46, 0x77, 0xc9, 0xb2, 0x73, 0xd9, 0x4b, 0x11, 0x28, 0xd0, 0xa4, 0x75, 0x67, 0x10, 0x56, 0xbe, 0x4c, 0xe0, 0x09, 0xb0, 0x51, 0x97, 0x5f, 0x87, 0x17, 0x23, 0xee, 0xa0, 0xc0, 0x09, 0xc6, 0x9e, 0x4f, 0x32, 0x0a, 0x9d, 0x97, 0xa3, 0xde, 0xbc, 0xec, 0xe2, 0xb4, 0x06, 0x05, 0xfe, 0x40, 0xd6, 0x26, 0x39, 0x87, 0xce, 0xa5, 0x9a, 0xb4, 0x60, 0xe1, 0x7a, 0x85, 0x6b, 0x27, 0x17, 0xae, 0xd7, 0x2c, 0xb8, 0x2f, 0x32, 0x20, 0x9c, 0x43, 0x71, 0xb1, 0xc0, 0x49, 0x91, 0xbe, 0x40, 0x65, 0xe9, 0x4b, 0x57, 0x58, 0x3d, 0x4e, 0x40, 0xfe, 0x33, 0xb9, 0x78, 0xd0, 0x73, 0x31, 0x3f, 0xfa, 0x4c, 0x1b, 0x31, 0x34, 0x90, 0x35, 0x64, 0x95, 0xb0, 0x9c, 0x0b, 0x9c, 0x12, 0xa9, 0xca, 0xa5, 0xc3, 0x2b, 0xaa, 0x81, 0x7f, 0x20, 0xb9, 0xa2, 0x74, 0x1e, 0x8e, 0x2e, 0xaf, 0xde, 0x02, 0xd7, 0xb1, 0x19, 0xb7, 0xbb, 0x9e, 0xac, 0x19, 0xfb, 0x59, 0x7d, 0xb2, 0xab, 0x9b, 0xab, 0x2f, 0xbc, 0xc8, 0x79, 0x75, 0xb8, 0xaf, 0xf5, 0xa8, 0x8d, 0xaf, 0xd1, 0x03, 0x64, 0x35, 0xf3, 0x22, 0x1c, 0xcd, 0xf2, 0x27, 0x95, 0x69, 0x96, 0x64, 0x0e, 0x4f, 0xf3, 0x91, 0x24, 0x98, 0xea, 0x30, 0x29, 0x80, 0x31, 0x91, 0x60, 0x7d, 0xda, 0xa1, 0xde, 0x27, 0x03, 0x06, 0x0f, 0x9a, 0xfc, 0x5a, 0x11, 0xa6, 0x98, 0x55, 0x83, 0x94, 0x1b, 0x44, 0xd0, 0x49, 0x5e, 0x73, 0x0e, 0x96, 0x2e, 0xec, 0xad, 0x2b, 0xd4, 0x2c, 0x8c, 0x05, 0x5d, 0x01, 0x9d, 0xc3, 0xa4, 0x91, 0xca, 0x14, 0x0c, 0x73, 0xa3, 0xd1, 0xd6, 0x5c, 0x5c, 0xd0, 0xac, 0x9b, 0x73, 0x48, 0x12, 0x34, 0x1a, 0xdd, 0x32, 0xb9, 0x0c, 0xee, 0x99, 0x51, 0x7b, 0x5e, 0x62, 0x3e, 0x21, 0x9b, 0x24, 0x3e, 0xd8, 0x9e, 0x86, 0x54, 0x7b, 0x94, 0x42, 0x49, 0x20, 0xdc, 0x9e, 0x29, 0x81, 0x58, 0x68, 0xbb, 0x82, 0x1b, 0x35, 0xe5, 0xf8, 0x77, 0x68, 0xd9, 0x4b, 0x8e, 0x65, 0xa5, 0x0b, 0x07, 0x67, 0xc6, 0xd5, 0x8b, 0xe2, 0xd1, 0x70, 0x40, 0xe7, 0xb2, 0xe8, 0x24, 0x32, 0x05, 0xcb, 0x7e, 0x6c, 0xb2, 0x35, 0x16, 0x17, 0x94, 0xab, 0xca, 0x57, 0x48, 0xec, 0x3a, 0x83, 0x55, 0xaa, 0x90, 0xc2, 0x96, 0x91, 0xc4, 0x5c, 0xfa, 0x46, 0x9a, 0x64, 0x4b, 0xe1, 0xce, 0xd4, 0x8c, 0x22, 0x6c, 0xf4, 0x62, 0xe5, 0x26, 0xac, 0x4e, 0xa4, 0x83, 0xd5, 0x64, 0x66, 0x66, 0x9a, 0x33, 0x4d, 0x5c, 0xd8, 0xc5, 0x08, 0x90, 0x35, 0xb9, 0x70, 0x5c, 0x59, 0xc1, 0x3e, 0xa8, 0x2d, 0xf4, 0x0b, 0xee, 0x3e, 0xe4, 0x6b, 0x7b, 0x75, 0x9f, 0x49, 0xb3, 0x5d, 0x2a, 0x93, 0x49, 0x77, 0x48, 0x8c, 0xca, 0x83, 0x7f, 0x3c, 0x4b, 0x1a, 0x3f, 0x71, 0x55, 0x9a, 0x2c, 0xab, 0x32, 0x5d, 0x5e, 0xfa, 0x46, 0xf5, 0x5e, 0xa9, 0x52, 0x29, 0xdd, 0xcb, 0xca, 0x80, 0xe2, 0x8e, 0x77, 0xdc, 0x8c, 0xdd, 0x7d, 0xc8, 0x60, 0x82, 0xaf, 0x68, 0x65, 0xe5, 0x34, 0x29, 0xb9, 0x1b, 0xea, 0x13, 0x16, 0x62, 0xe6, 0x59, 0x0d, 0xdc, 0x59, 0x51, 0x88, 0x3c, 0x05, 0x18, 0xb0, 0x4e, 0x30, 0x61, 0xae, 0x41, 0xb1, 0xe1, 0x6b, 0xd1, 0xb6, 0x65, 0x14, 0xc3, 0x67, 0x21, 0xfc, 0x26, 0x02, 0x65, 0xb4, 0x51, 0xeb, 0xd0, 0x99, 0xd1, 0xde, 0x84, 0x9a, 0x10, 0x4a, 0xe2, 0x2a, 0xe1, 0x63, 0xe4, 0x78, 0xf7, 0x22, 0x36, 0x8a, 0x2a, 0x44, 0x8a, 0x4f, 0x2e, 0x99, 0x6c, 0x2c, 0x4d, 0x0a, 0x2d, 0x92, 0xaa, 0x35, 0x66, 0x6e, 0xf4, 0x48, 0x86, 0x4f, 0x73, 0x85, 0x52, 0x79, 0x85, 0xc6, 0x97, 0x71, 0xf0, 0x5f, 0x8f, 0x4a, 0xee, 0x7e, 0xc7, 0xc3, 0xc2, 0x66, 0x29, 0x38, 0x25, 0xcb, 0x2f, 0x93, 0x19, 0xaf, 0xd2, 0x68, 0xae, 0x32, 0xca, 0x80, 0x80, 0x17, 0x3c, 0xee, 0xa0, 0x6f, 0x24, 0x5f, 0x85, 0xb4, 0x52, 0x10, 0x56, 0xc4, 0x39, 0x35, 0x0a, 0xdc, 0x50, 0xb4, 0x0b, 0x41, 0x8d, 0xa0, 0x70, 0xc6, 0xe1, 0x20, 0xd5, 0xca, 0x85, 0x83, 0x3a, 0x6c, 0x93, 0x42, 0x8b, 0x00, 0xa9, 0xb7, 0x4e, 0xc0, 0x4c, 0x21, 0xd8, 0x3e, 0x63, 0xa5, 0xeb, 0x93, 0x9f, 0x72, 0x66, 0x8d, 0x5a, 0xba, 0x9a, 0xcc, 0x99, 0x42, 0x32, 0x66, 0xb1, 0x9d, 0x71, 0xbf, 0xc3, 0x2a, 0x39, 0xc5, 0xa1, 0x4f, 0xd2, 0x28, 0x26, 0xb6, 0x81, 0x95, 0x93, 0xef, 0x4a, 0x4e, 0x41, 0x9d, 0x05, 0x73, 0x51, 0x93, 0x30, 0x5e, 0xc9, 0x4c, 0x54, 0x84, 0xdd, 0xb8, 0x16, 0xb6, 0x62, 0x94, 0x6a, 0xf5, 0x73, 0x61, 0x41, 0x4d, 0x9a, 0xd4, 0x79, 0x46, 0x68, 0x93, 0x01, 0x2e, 0xc4, 0x77, 0x27, 0x77, 0x9d, 0xbf, 0x05, 0xb7, 0xc9, 0xe3, 0xce, 0x5c, 0xcd, 0xca, 0x27, 0x7a, 0x3e, 0xcb, 0xce, 0x7a, 0xde, 0x31, 0x19, 0x0e, 0x11, 0xc2, 0x5c, 0xa1, 0x36, 0xb0, 0x6b, 0xb0, 0x7f, 0x66, 0xee, 0x59, 0x07, 0x4b, 0xd2, 0x78, 0x1a, 0x2b, 0x44, 0x07, 0x0d, 0xc1, 0xd0, 0xcb, 0xc4, 0x06, 0xc0, 0xdd, 0x31, 0xc5, 0x0c, 0x0a, 0x33, 0xc7, 0x8a, 0x2b, 0x4d, 0x22, 0x03, 0xf4, 0x9a, 0x69, 0xce, 0xf6, 0x26, 0x64, 0x06, 0x93, 0x17, 0xce, 0x2b, 0x41, 0x5d, 0x9c, 0x4c, 0xa6, 0xe2, 0xa4, 0xcf, 0xe4, 0xd2, 0x59, 0x7e, 0xc9, 0x44, 0xab, 0x75, 0xb6, 0xe5, 0xf9, 0xab, 0xa3, 0x96, 0xfa, 0x62, 0x55, 0x5b, 0xac, 0xc8, 0xeb, 0xce, 0xb4, 0xe8, 0x39, 0x89, 0x5c, 0xc1, 0x4c, 0x9a, 0x75, 0x77, 0x1a, 0xed, 0x1a, 0xb8, 0x07, 0xcd, 0x9f, 0xa9, 0x89, 0x2c, 0x93, 0x04, 0xcd, 0x7a, 0xbc, 0x40, 0x65, 0x84, 0x30, 0x07, 0xa9, 0x0d, 0x92, 0x8f, 0x09, 0x1b, 0xdc, 0xe7, 0x2f, 0x3d, 0xeb, 0x55, 0xc1, 0xdd, 0x7d, 0xb2, 0x6f, 0x5e, 0xb4, 0xd7, 0x87, 0xbb, 0xfe, 0x35, 0x04, 0x32, 0x04, 0x2f, 0x85, 0x6b, 0x83, 0x5a, 0x7b, 0xe9, 0x7a, 0xf5, 0x4d, 0xba, 0x8a, 0x5a, 0x35, 0xed, 0x65, 0xb0, 0xb7, 0x5c, 0xd8, 0x6c, 0x30, 0x79, 0x70, 0x6f, 0x2f, 0xdf, 0x3b, 0xed, 0xa4, 0x41, 0x23, 0xa3, 0x97, 0xe9, 0xdb, 0xc1, 0x29, 0xb3, 0xf7, 0x19, 0x93, 0x6d, 0x72, 0xf7, 0xf0, 0x2a, 0xe7, 0xf7, 0x4e, 0x9a, 0xc8, 0x78, 0x1c, 0xc9, 0x77, 0xd8, 0x97, 0x08, 0x27, 0x91, 0x8b, 0x32, 0x0d, 0x51, 0x83, 0xd1, 0x2e, 0x22, 0x00, 0x68, 0x96, 0x6a, 0x66, 0xe0, 0xbf, 0xf4, 0xfa, 0xd4, 0x4e, 0x02, 0x6e, 0x97, 0x59, 0x8a, 0x40, 0xe1, 0x4a, 0x83, 0x2c, 0x9c, 0xd9, 0x7e, 0x6f, 0xd8, 0x8b, 0xa3, 0xd3, 0x41, 0x0a, 0x2e, 0x0a, 0x6e, 0xe4, 0x92, 0x78, 0x51, 0xd8, 0x3f, 0xa8, 0x9d, 0x32, 0x88, 0xe4, 0x3b, 0x26, 0xb8, 0x4d, 0xcd, 0x69, 0x2c, 0x2b, 0xb4, 0x1a, 0xdd, 0x7a, 0xf9, 0x56, 0x53, 0x6e, 0x43, 0x34, 0xb7, 0xb1, 0xb4, 0xd0, 0x06, 0x7f, 0x29, 0x96, 0x4f, 0x19, 0x45, 0xf6, 0x85, 0xe8, 0xec, 0x44, 0xc0, 0x15, 0x74, 0x6b, 0xec, 0x5e, 0x0e, 0x7d, 0x75, 0x07, 0x5d, 0x1a, 0x9b, 0x57, 0x33, 0xf6, 0xcb, 0x49, 0x2b, 0x01, 0x8f, 0x17, 0xf9, 0x47, 0x69, 0x23, 0x5c, 0x07, 0x39, 0xc4, 0x15, 0x09, 0x95, 0x1a, 0xb0, 0x8c, 0x13, 0xc1, 0x85, 0xa0, 0xd8, 0x09, 0x31, 0x4f, 0xce, 0x35, 0x79, 0x55, 0x30, 0x08, 0xca, 0x05, 0xfb, 0x3c, 0x71, 0x4e, 0xe8, 0x28, 0xda, 0xcd, 0xfa, 0x27, 0x5f, 0x82, 0xc0, 0x46, 0xba, 0x53, 0x17, 0xb2, 0x2c, 0xda, 0xcd, 0xb2, 0x54, 0x07, 0x72, 0x8c, 0x86, 0xbd, 0x68, 0x41, 0xe1, 0x9e, 0x4f, 0x1a, 0x1b, 0xed, 0x65, 0xe9, 0x30, 0x75, 0x81, 0x7d, 0x6e, 0xce, 0x6b, 0xcc, 0xc9, 0x69, 0xaa, 0x8c, 0x5b, 0x8d, 0x1e, 0x83, 0x72, 0xab, 0x39, 0xbf, 0x31, 0x9a, 0xdb, 0x54, 0x19, 0xb3, 0x19, 0x3d, 0x46, 0x65, 0xfa, 0x8a, 0x03, 0x81, 0xe9, 0x49, 0x20, 0xf8, 0x48, 0x67, 0xd0, 0x87, 0xc9, 0x3f, 0xb2, 0xa7, 0x08, 0x3d, 0x1c, 0x3d, 0x38, 0x76, 0x12, 0x38, 0x4d, 0x99, 0x75, 0x70, 0x1f, 0x0e, 0x15, 0x58, 0x94, 0xfd, 0x8c, 0xfa, 0x4f, 0x60, 0xdc, 0xf8, 0x41, 0xd0, 0x6a, 0x30, 0x00, 0xc2, 0xe0, 0x34, 0x38, 0xac, 0x66, 0x2e, 0x53, 0x29, 0x67, 0x19, 0x42, 0x0f, 0xf4, 0xd8, 0x2d, 0x8e, 0x76, 0xda, 0x85, 0xc9, 0x00, 0x66, 0x04, 0xfc, 0xa5, 0xc5, 0xb9, 0x4e, 0x68, 0xb9, 0x81, 0x1b, 0xfa, 0x76, 0xb6, 0x7b, 0xaf, 0xd1, 0xe7, 0xcc, 0x2c, 0x2c, 0x9c, 0x99, 0xa3, 0xbf, 0xc6, 0xdb, 0xbe, 0xf3, 0xf3, 0x8f, 0xd4, 0x36, 0xe3, 0xa6, 0x63, 0x2a, 0xb7, 0x97, 0x3e, 0x1c, 0x69, 0x59, 0x52, 0xe2, 0x8a, 0x07, 0xf5, 0xfa, 0x60, 0xdc, 0x55, 0xb2, 0xa4, 0x25, 0x42, 0x1a, 0x79, 0x9e, 0xcb, 0xf8, 0xd4, 0x64, 0xc0, 0xfe, 0x5b, 0x96, 0xa1, 0xca, 0xa5, 0x01, 0xc8, 0x1f, 0xed, 0x28, 0xd2, 0x9e, 0x45, 0x3c, 0x71, 0x1d, 0x83, 0x6a, 0xac, 0xad, 0x43, 0xa9, 0x8d, 0xeb, 0xb0, 0x13, 0xb7, 0x1b, 0x57, 0xd5, 0x96, 0x00, 0xc8, 0x3c, 0x3a, 0x20, 0xb3, 0x26, 0x20, 0xcf, 0xd6, 0x69, 0x38, 0xa5, 0x3d, 0xc3, 0x8e, 0x18, 0xb7, 0x57, 0x8a, 0xb5, 0x1d, 0x4c, 0x5b, 0xc4, 0xb9, 0x85, 0x96, 0x62, 0x37, 0x15, 0x6a, 0x27, 0x55, 0xce, 0x3f, 0xc2, 0xd9, 0x8c, 0xab, 0x7f, 0x25, 0xe3, 0x74, 0x16, 0xee, 0xf5, 0xbe, 0xed, 0xad, 0xee, 0x5d, 0x0b, 0x47, 0x46, 0x16, 0xee, 0x72, 0xb7, 0x6e, 0x97, 0x06, 0xf8, 0x3f, 0xaa, 0x24, 0x80, 0x53, 0xaa, 0x24, 0xe4, 0xd8, 0xef, 0xc3, 0x4d, 0x03, 0x25, 0x35, 0xad, 0xad, 0x35, 0x25, 0x03, 0x4d, 0xe1, 0x24, 0xdd, 0xde, 0x49, 0xa7, 0x1b, 0x0d, 0x50, 0xd6, 0xb8, 0x80, 0x81, 0x32, 0x85, 0x6e, 0x46, 0x48, 0x37, 0x9b, 0xc5, 0xe0, 0x34, 0x3a, 0xbf, 0x8d, 0x6e, 0x14, 0xe2, 0xa4, 0x18, 0xbf, 0x6d, 0x1a, 0xba, 0xfd, 0x09, 0xce, 0x89, 0xa3, 0xab, 0x8d, 0x76, 0xf5, 0x47, 0xd3, 0xd1, 0xcd, 0x68, 0xfc, 0x34, 0x23, 0x13, 0xd2, 0x06, 0xf2, 0xf4, 0xcf, 0x59, 0x86, 0xfc, 0x8d, 0xd4, 0x4f, 0xc8, 0xa0, 0x5c, 0xa9, 0x48, 0x94, 0x62, 0xa4, 0x62, 0x44, 0x37, 0x28, 0xdc, 0x90, 0xb3, 0xe9, 0x52, 0xba, 0x29, 0xe4, 0x80, 0x30, 0x42, 0xb2, 0x65, 0xaa, 0xe5, 0x56, 0x85, 0x95, 0xa5, 0x09, 0x19, 0x90, 0xa1, 0x06, 0x6a, 0xd1, 0xde, 0x1e, 0x35, 0x47, 0x06, 0xa6, 0x10, 0xee, 0x3f, 0x3c, 0x7e, 0xcd, 0xeb, 0xcb, 0x8c, 0x0e, 0x15, 0xff, 0x04, 0xb8, 0x6d, 0x32, 0xd9, 0xfc, 0x0a, 0x23, 0xff, 0x4f, 0xb9, 0x02, 0x38, 0xc7, 0xfe, 0x30, 0x89, 0x68, 0x49, 0x9a, 0xfd, 0x19, 0x4a, 0xe5, 0x3c, 0x62, 0x69, 0x42, 0xae, 0x46, 0x3a, 0x34, 0xdc, 0x27, 0x25, 0xbd, 0xbb, 0x0e, 0xc8, 0xd3, 0x19, 0x82, 0x5c, 0x86, 0x74, 0x56, 0x1a, 0x79, 0xce, 0x18, 0x82, 0x62, 0xa8, 0x45, 0x12, 0x54, 0x53, 0x67, 0x90, 0xc6, 0xe0, 0x6a, 0xf0, 0x02, 0x9a, 0x64, 0xd6, 0x5c, 0xe6, 0x0a, 0x28, 0xb4, 0xad, 0x56, 0x6b, 0x9e, 0x35, 0xcf, 0xa7, 0xf3, 0x1b, 0xb2, 0xfc, 0x52, 0x81, 0x27, 0x4e, 0xb0, 0xc1, 0x5c, 0x90, 0xa6, 0xe4, 0xf8, 0x93, 0x2e, 0xd6, 0xd4, 0x00, 0x80, 0xfe, 0x58, 0x08, 0xab, 0x5e, 0x5a, 0x89, 0x5c, 0xce, 0xfa, 0x1c, 0x50, 0x21, 0x9b, 0x0f, 0x15, 0xb2, 0x45, 0x60, 0x27, 0x24, 0x7e, 0xdf, 0xa4, 0xe1, 0x60, 0x25, 0x58, 0x1d, 0xc3, 0xec, 0xb0, 0xc8, 0x64, 0x6b, 0x2e, 0x41, 0x2a, 0x9a, 0x03, 0x0d, 0x44, 0xfa, 0xc0, 0x88, 0x73, 0x18, 0x8e, 0x05, 0xd2, 0xd3, 0xb2, 0x89, 0x6d, 0x93, 0xfb, 0x8c, 0xd4, 0xb5, 0x10, 0x6a, 0x3b, 0xbc, 0x0c, 0x2f, 0x36, 0xd8, 0xf9, 0xa5, 0x13, 0x5d, 0xa3, 0x28, 0x06, 0xf2, 0x0e, 0x06, 0xf1, 0x0c, 0x86, 0x82, 0x02, 0x21, 0x3c, 0xe5, 0x52, 0x66, 0xd5, 0xe5, 0xae, 0x15, 0xe8, 0x90, 0x6d, 0xcd, 0xf6, 0xfc, 0xdf, 0xe8, 0x80, 0x47, 0x17, 0xf4, 0xa5, 0xab, 0x79, 0xbe, 0x94, 0xf2, 0x07, 0xb6, 0xc0, 0xc1, 0xed, 0x4b, 0x0d, 0xb5, 0x64, 0x01, 0x9b, 0x52, 0xfb, 0x62, 0x46, 0x41, 0x15, 0x2c, 0xb3, 0xa1, 0x81, 0x4e, 0x0e, 0xfa, 0x04, 0x7f, 0x41, 0xb2, 0x41, 0x4f, 0xc4, 0x10, 0x7a, 0xa6, 0x0d, 0xd0, 0x92, 0x6c, 0xc0, 0xb0, 0x64, 0xf3, 0x24, 0xcf, 0x12, 0x40, 0x19, 0x33, 0xa4, 0x74, 0xea, 0xa2, 0x29, 0xc8, 0x43, 0xe9, 0xbc, 0x86, 0x98, 0x31, 0x36, 0xb1, 0x70, 0xe4, 0xd3, 0x33, 0x9c, 0xe4, 0x97, 0x4b, 0x78, 0xe7, 0x34, 0x0b, 0xc9, 0x89, 0x24, 0xc5, 0xe6, 0xe9, 0xa5, 0xc8, 0xa5, 0x4b, 0x0b, 0xbc, 0x81, 0xb8, 0xe5, 0xa5, 0x3c, 0x54, 0xe8, 0x5f, 0x2b, 0x1c, 0x5f, 0x24, 0x37, 0x50, 0x4e, 0xc4, 0xfe, 0x84, 0xdc, 0x04, 0xfb, 0x17, 0xc2, 0xfd, 0x13, 0x64, 0x86, 0x1f, 0xf6, 0x86, 0x81, 0x52, 0x1c, 0x8e, 0x99, 0xd8, 0xdf, 0x64, 0x37, 0xe1, 0x70, 0xd1, 0x7d, 0xc8, 0x41, 0x36, 0x8a, 0x4a, 0x07, 0x46, 0x2e, 0xb9, 0x2e, 0x8d, 0x2e, 0x28, 0xb5, 0x11, 0xef, 0x26, 0xf0, 0x9d, 0xc8, 0x95, 0xa6, 0xd5, 0x00, 0x22, 0x27, 0x1b, 0x55, 0x73, 0xd5, 0xe4, 0x6b, 0xf3, 0x33, 0x94, 0x72, 0x29, 0x24, 0x0e, 0x07, 0xb8, 0x34, 0xe2, 0xa4, 0x96, 0xea, 0x37, 0x92, 0x26, 0x7d, 0xe5, 0x7a, 0x91, 0xe4, 0xd8, 0x3c, 0xbd, 0x54, 0x61, 0x99, 0xf4, 0xb5, 0x0c, 0xde, 0xb9, 0x0c, 0x49, 0x00, 0xb0, 0xf0, 0x47, 0x49, 0xd9, 0xf8, 0x3e, 0x11, 0xdb, 0x5f, 0x44, 0x16, 0x58, 0x43, 0x88, 0xd8, 0xfe, 0xcc, 0x94, 0x50, 0x43, 0x52, 0xa6, 0x73, 0x67, 0x9b, 0x50, 0xc9, 0x01, 0xad, 0x2b, 0x62, 0x32, 0x65, 0xbb, 0x75, 0x9f, 0x9b, 0xb2, 0xd1, 0x8f, 0x6c, 0x93, 0x39, 0xdb, 0xad, 0xd5, 0xba, 0xb3, 0x11, 0x8d, 0x7b, 0xc7, 0x3f, 0x61, 0xaf, 0x64, 0xb3, 0xe0, 0x33, 0x4b, 0x91, 0x1c, 0xc8, 0xb5, 0x60, 0xcb, 0x12, 0x0b, 0x30, 0x50, 0x47, 0x2a, 0xac, 0x97, 0x01, 0x13, 0x30, 0x8b, 0xc8, 0xb7, 0xa9, 0x2b, 0xd5, 0x95, 0x44, 0x23, 0x5e, 0x77, 0x7a, 0x99, 0x87, 0xc9, 0x79, 0x37, 0x40, 0x40, 0xc7, 0x41, 0xff, 0x92, 0x29, 0xf8, 0x40, 0xc1, 0x8d, 0x22, 0x22, 0x96, 0xc6, 0x8a, 0xa0, 0xc8, 0x58, 0xfb, 0xd4, 0x9e, 0xc6, 0xc6, 0x3d, 0x4f, 0xad, 0x5d, 0xfb, 0xd4, 0xde, 0xc6, 0xc6, 0xbd, 0x4f, 0x51, 0xcf, 0x49, 0xa4, 0x07, 0xf7, 0x94, 0x56, 0x8c, 0x3c, 0xc3, 0x7f, 0x7d, 0xe7, 0x49, 0xfe, 0xab, 0x9f, 0x2c, 0x5b, 0xf6, 0x13, 0x20, 0x39, 0x79, 0x2f, 0x90, 0x2f, 0x6c, 0x68, 0x51, 0x71, 0xaa, 0x75, 0x0f, 0xb6, 0x1c, 0x79, 0x65, 0xcf, 0xee, 0x57, 0x51, 0xad, 0xbb, 0x57, 0x77, 0xef, 0x79, 0xe5, 0x48, 0x0b, 0xb3, 0xc7, 0x50, 0xe6, 0xd8, 0xfe, 0x90, 0xa3, 0xf7, 0xb7, 0xdb, 0xef, 0xe2, 0x3f, 0x7b, 0x61, 0xe5, 0xca, 0x17, 0x40, 0xc6, 0x5d, 0x77, 0x02, 0xd5, 0x0b, 0x57, 0xac, 0x1d, 0x51, 0xd4, 0x44, 0x75, 0x36, 0xab, 0xcb, 0x88, 0xac, 0x73, 0x27, 0xc7, 0xff, 0xc5, 0x9e, 0x86, 0xfd, 0x8c, 0xa2, 0xd8, 0x45, 0x09, 0x60, 0x49, 0x76, 0x1d, 0xc2, 0x32, 0xa6, 0x18, 0x62, 0x90, 0x16, 0x02, 0xa6, 0x27, 0x42, 0xbb, 0x21, 0x55, 0xa3, 0x44, 0x36, 0xa7, 0xf7, 0xeb, 0xfd, 0x46, 0xbc, 0xc0, 0x85, 0xa0, 0x11, 0x09, 0x97, 0xa4, 0x30, 0xae, 0x18, 0x27, 0x2c, 0x10, 0xae, 0x50, 0x9f, 0xea, 0x0e, 0x7b, 0x87, 0x44, 0x1a, 0x1b, 0xba, 0x69, 0xe1, 0xac, 0x9b, 0xdf, 0x3a, 0x70, 0xe0, 0xad, 0x9b, 0x67, 0xbd, 0x99, 0xbf, 0xf0, 0x60, 0x57, 0xd7, 0xc1, 0x85, 0x05, 0x6f, 0x2c, 0x5c, 0x81, 0x1a, 0xcf, 0x66, 0xc1, 0xb6, 0xb6, 0x6c, 0xef, 0x2d, 0x00, 0x63, 0xaf, 0xed, 0x7a, 0xeb, 0x44, 0x4f, 0xcf, 0x89, 0xb7, 0x76, 0x51, 0xc3, 0x17, 0x4f, 0xf4, 0xdc, 0x34, 0x5a, 0x56, 0x36, 0x7a, 0x63, 0x0f, 0x35, 0xfc, 0x1a, 0xd9, 0x18, 0x17, 0x9b, 0x8c, 0x6b, 0x8c, 0x7c, 0x4a, 0xff, 0x85, 0x3e, 0x0a, 0x65, 0x6d, 0x3e, 0xb1, 0x2c, 0xa1, 0x08, 0xc3, 0xe1, 0x08, 0x68, 0x10, 0xd8, 0x92, 0xc8, 0xd4, 0x6d, 0x93, 0x87, 0x69, 0xd5, 0xa4, 0x61, 0xb2, 0x7e, 0xc3, 0xe9, 0xd5, 0x48, 0x97, 0xd7, 0xe3, 0x12, 0x4c, 0xf9, 0xca, 0x3c, 0x9f, 0xc7, 0x62, 0x12, 0x8a, 0x2f, 0x49, 0xe4, 0x53, 0x8a, 0x2f, 0x09, 0x83, 0x68, 0xd0, 0x4f, 0x9a, 0x5a, 0x86, 0xd4, 0x00, 0xc6, 0x0b, 0xc9, 0xb2, 0x1b, 0x8e, 0x1c, 0xb9, 0xe1, 0xa6, 0xeb, 0xaf, 0xa7, 0xf2, 0x59, 0xe9, 0xae, 0xed, 0x45, 0x69, 0x45, 0x33, 0xd7, 0xee, 0x7e, 0xb1, 0xba, 0xa2, 0x56, 0xa5, 0x51, 0x15, 0xc6, 0x53, 0xe5, 0x98, 0x68, 0x99, 0x2e, 0x6e, 0x5b, 0xff, 0x7d, 0xcb, 0xda, 0x63, 0xe9, 0x15, 0x34, 0x17, 0x74, 0x29, 0x6b, 0x22, 0x99, 0x26, 0x93, 0xc3, 0x10, 0xc4, 0x35, 0x2c, 0x76, 0x8d, 0x7f, 0xcc, 0xd8, 0x61, 0xbf, 0x73, 0x88, 0xd5, 0x09, 0x65, 0xc0, 0x4b, 0x12, 0x94, 0x2b, 0x83, 0x14, 0xf8, 0xba, 0x43, 0xa8, 0x16, 0xca, 0x52, 0x70, 0xf8, 0x08, 0x0a, 0xc1, 0xcb, 0xad, 0x41, 0x15, 0xa8, 0x96, 0xa2, 0xc0, 0xf3, 0x34, 0xa8, 0x50, 0xdf, 0xb4, 0x97, 0x60, 0x78, 0x03, 0xb1, 0x70, 0x09, 0xc6, 0x0b, 0x85, 0xfb, 0x50, 0xf8, 0x92, 0xa8, 0xd6, 0xab, 0x0f, 0xb8, 0x27, 0x8f, 0x34, 0xd2, 0x8b, 0x45, 0x3f, 0x11, 0x1a, 0x6c, 0xb4, 0xe0, 0x8b, 0xd3, 0x86, 0x3a, 0x5e, 0xc8, 0x64, 0xb2, 0x92, 0xac, 0x39, 0xdb, 0x66, 0x6d, 0x0c, 0x29, 0x55, 0xee, 0xa6, 0xd9, 0xfd, 0xb1, 0xb9, 0x57, 0x76, 0x65, 0xbf, 0x38, 0x34, 0xd2, 0xb2, 0xb7, 0xf4, 0x85, 0xe6, 0x4e, 0xdc, 0x6b, 0xfa, 0x28, 0xec, 0x6c, 0xcd, 0xaa, 0xce, 0x7c, 0x7a, 0x4c, 0x1a, 0x5c, 0xea, 0x2d, 0x0b, 0x1b, 0x4b, 0x86, 0x8f, 0x74, 0x91, 0x57, 0x8f, 0x6d, 0xbd, 0x62, 0xeb, 0x8c, 0x2a, 0xf2, 0xaf, 0xf7, 0x4b, 0x1a, 0xf2, 0x52, 0xbd, 0x46, 0x3b, 0xd3, 0x39, 0xe3, 0x32, 0x7a, 0x39, 0xf3, 0x22, 0xfc, 0x96, 0x09, 0xb5, 0xfd, 0x1b, 0x12, 0xca, 0x10, 0x20, 0x25, 0xa6, 0x4c, 0x9a, 0x12, 0xaa, 0x59, 0x23, 0x70, 0xb4, 0xa8, 0x52, 0x41, 0xca, 0xa4, 0x90, 0x11, 0xca, 0x68, 0x5c, 0xb9, 0x89, 0x00, 0x0b, 0x08, 0xb9, 0x7c, 0x18, 0x79, 0x06, 0x31, 0x7c, 0xc0, 0x52, 0x34, 0x8f, 0xb1, 0xcf, 0x0c, 0x65, 0xd3, 0x5b, 0x13, 0xf9, 0xdf, 0x7e, 0x3d, 0xbc, 0xb2, 0x25, 0xa9, 0x68, 0x63, 0x64, 0x4a, 0x03, 0xc7, 0x71, 0xb9, 0x5c, 0x6e, 0x4e, 0x34, 0x3b, 0xa2, 0x0d, 0xf9, 0xdc, 0x3a, 0x9f, 0x97, 0xc3, 0xb8, 0x47, 0x46, 0xe4, 0x04, 0xc6, 0x05, 0x54, 0x85, 0x78, 0xaa, 0x62, 0x80, 0xd0, 0xe5, 0x3c, 0xb9, 0x53, 0x70, 0x1f, 0x62, 0x42, 0x5d, 0xa8, 0x01, 0xea, 0xc3, 0x8b, 0x56, 0xf2, 0xe9, 0xdc, 0x35, 0xb5, 0x87, 0x8e, 0x5e, 0xb5, 0x69, 0xee, 0xb6, 0x18, 0x98, 0x75, 0xdd, 0xc6, 0xb0, 0x5c, 0x53, 0x77, 0x72, 0xb4, 0xf7, 0x86, 0xe1, 0xe2, 0xca, 0xf5, 0xa7, 0x96, 0xf6, 0x5f, 0x59, 0xf8, 0xc9, 0x27, 0x2f, 0x6c, 0xdf, 0x41, 0xbd, 0x5f, 0x55, 0xf8, 0x93, 0x9f, 0x9c, 0xb9, 0xaf, 0xb5, 0x91, 0x7a, 0x7d, 0xc7, 0x85, 0xc3, 0xc1, 0x4d, 0x1d, 0xfd, 0x68, 0xfe, 0x6c, 0x7a, 0xe1, 0x70, 0x5b, 0x65, 0xfc, 0xcb, 0x1d, 0x6f, 0xbc, 0x01, 0xd7, 0xc2, 0x3a, 0x62, 0x29, 0xfd, 0x34, 0xfd, 0x08, 0xf6, 0x1d, 0xa2, 0x18, 0x93, 0xcb, 0xa4, 0x80, 0x7e, 0x57, 0xe4, 0xb1, 0x75, 0x5d, 0xc7, 0x5f, 0x5a, 0xbb, 0xf6, 0xa5, 0xe3, 0x5d, 0xc9, 0xcf, 0x47, 0xca, 0x87, 0x0f, 0xb6, 0xb7, 0x1f, 0x5a, 0x5a, 0x5e, 0xbe, 0xf4, 0x50, 0x7b, 0xfb, 0xc1, 0xe1, 0x72, 0xf2, 0x9a, 0xf4, 0xd3, 0xe8, 0x13, 0x1d, 0x15, 0xae, 0x42, 0x9f, 0x87, 0x08, 0x9c, 0x4c, 0xbd, 0x95, 0x58, 0x4e, 0x3f, 0x45, 0x3f, 0x00, 0xd7, 0xa8, 0x91, 0xf0, 0xc2, 0x51, 0x2b, 0x27, 0x66, 0x10, 0x73, 0xa0, 0x04, 0x5a, 0x41, 0x6c, 0x26, 0xf6, 0x11, 0xd7, 0x13, 0xb7, 0x13, 0xf7, 0x13, 0x8f, 0x13, 0xcf, 0x11, 0x7c, 0xcb, 0xa3, 0x79, 0x70, 0xfc, 0x7a, 0xc4, 0x10, 0x9e, 0x41, 0x39, 0x20, 0x15, 0x00, 0xd5, 0x98, 0x1b, 0x54, 0x01, 0xa8, 0x2a, 0x48, 0xa4, 0xac, 0x64, 0x30, 0x13, 0x48, 0x39, 0x20, 0xd3, 0x48, 0x65, 0x83, 0x3a, 0xa0, 0xd1, 0x03, 0xad, 0x41, 0xa3, 0x1d, 0x34, 0x01, 0x83, 0x19, 0x18, 0x1d, 0x06, 0xe3, 0xa0, 0x0d, 0x58, 0xec, 0xc0, 0xea, 0xb4, 0x58, 0x07, 0x09, 0x07, 0xe1, 0x74, 0x38, 0x17, 0x65, 0x00, 0x24, 0xe6, 0x94, 0x50, 0xcc, 0xad, 0x42, 0x43, 0xbd, 0xf0, 0xd2, 0x47, 0xc3, 0x73, 0x08, 0x09, 0x0c, 0x0a, 0xba, 0xef, 0xfa, 0x12, 0xc2, 0x61, 0x75, 0x3a, 0xba, 0xd3, 0x5f, 0xe5, 0x70, 0xf6, 0xa1, 0xb7, 0x41, 0xc9, 0x58, 0xf6, 0xd3, 0x67, 0x7e, 0x74, 0xf6, 0x87, 0x8f, 0x9e, 0x7e, 0xe8, 0xbe, 0x7b, 0xee, 0xfc, 0xfe, 0x6d, 0xb7, 0x1c, 0xbb, 0xe9, 0xc8, 0xe1, 0x83, 0x07, 0xf6, 0xee, 0xde, 0xb1, 0x6d, 0xd3, 0x86, 0x35, 0xab, 0x96, 0x2f, 0x1b, 0x5a, 0xb2, 0xb0, 0xbf, 0xa7, 0x6b, 0xf6, 0xac, 0xd6, 0xe6, 0xc6, 0xfa, 0x44, 0x55, 0x59, 0x89, 0x4f, 0xfc, 0xe3, 0x71, 0xa1, 0xc8, 0x1a, 0x84, 0xbb, 0x6d, 0xc0, 0xe8, 0x36, 0x01, 0xe3, 0xff, 0xf1, 0x7b, 0xfa, 0xbd, 0x92, 0xef, 0xf0, 0x9d, 0x49, 0xfb, 0xee, 0xff, 0xff, 0x71, 0x3c, 0xbd, 0x0d, 0x64, 0x9f, 0xd7, 0x6c, 0xf1, 0xf9, 0x2c, 0x66, 0xef, 0x59, 0x9f, 0xd9, 0xe2, 0xf5, 0x5a, 0xcc, 0xbe, 0x3b, 0xd0, 0x3f, 0xe8, 0xc7, 0xb0, 0xcf, 0x8c, 0xbe, 0x98, 0x7d, 0x59, 0xc9, 0x2f, 0x63, 0x83, 0xc9, 0x6f, 0xb7, 0x27, 0x6f, 0x1b, 0x49, 0x5e, 0xed, 0x4f, 0xde, 0x4f, 0xdd, 0x96, 0xbc, 0xe8, 0xe2, 0xb5, 0xc9, 0x93, 0x54, 0x66, 0xf2, 0xdb, 0x58, 0x77, 0xf2, 0x4e, 0x52, 0x99, 0xbc, 0x83, 0xbf, 0x3e, 0x79, 0xc7, 0x4f, 0x92, 0x27, 0xe9, 0x15, 0xc2, 0x29, 0x9f, 0x42, 0xb8, 0xcd, 0xdb, 0x26, 0xfe, 0xde, 0x8b, 0x7f, 0xfb, 0x7c, 0x77, 0x89, 0xbf, 0xc1, 0x5b, 0xdf, 0x72, 0xc1, 0xf7, 0xc5, 0x4f, 0xf1, 0x5d, 0xbe, 0x7d, 0x69, 0xdf, 0xd1, 0x67, 0x8b, 0x78, 0xbd, 0x45, 0xfc, 0xcc, 0x14, 0x3f, 0x51, 0x7d, 0x97, 0xf1, 0x4f, 0x18, 0x03, 0xf3, 0x30, 0x9c, 0xfd, 0xbb, 0x5b, 0x1e, 0x75, 0xc1, 0x59, 0xae, 0xce, 0x85, 0x13, 0x31, 0x84, 0xea, 0x34, 0xa3, 0xc0, 0x60, 0x04, 0xf7, 0x88, 0x8e, 0xb0, 0xe2, 0x11, 0xa8, 0x51, 0x23, 0xac, 0x47, 0x17, 0x2e, 0x34, 0x81, 0x40, 0x52, 0x50, 0x64, 0xe7, 0x8a, 0x16, 0xc1, 0x9f, 0xc2, 0x00, 0x2c, 0x82, 0xad, 0x89, 0x60, 0x2a, 0x10, 0x9a, 0x65, 0x31, 0x32, 0x51, 0x32, 0x17, 0x4e, 0xe4, 0x57, 0x49, 0x61, 0x0d, 0x37, 0xfc, 0x9c, 0x3e, 0xe8, 0xf6, 0x87, 0x05, 0x53, 0x87, 0xe8, 0xa4, 0xe6, 0xd2, 0x61, 0xcf, 0xb1, 0xba, 0xc6, 0x25, 0x83, 0x03, 0x92, 0x70, 0x29, 0xfb, 0x2a, 0x56, 0xad, 0xda, 0x50, 0x3b, 0x74, 0xff, 0x96, 0xda, 0xf2, 0x0d, 0x0f, 0xaf, 0xae, 0xe8, 0xc9, 0xb5, 0xb2, 0x66, 0x55, 0x46, 0x3c, 0xb6, 0x73, 0xab, 0x7f, 0xe6, 0xaa, 0xa6, 0x9a, 0xd5, 0x4b, 0x7a, 0xb3, 0xde, 0xba, 0x4d, 0xe3, 0x22, 0xe9, 0x57, 0x6b, 0xbb, 0x0a, 0x75, 0x33, 0x77, 0x3f, 0x32, 0x3c, 0xf6, 0xde, 0xf2, 0x7b, 0xd7, 0x96, 0x6b, 0x74, 0x83, 0x26, 0x95, 0x7a, 0xc7, 0x41, 0xb2, 0xa0, 0x71, 0x4d, 0x7b, 0x96, 0xbb, 0xa2, 0x3b, 0x7e, 0xe1, 0x3c, 0x73, 0xc2, 0x63, 0x49, 0xd6, 0xba, 0x39, 0x8e, 0x69, 0x91, 0x4f, 0xbc, 0x24, 0x48, 0x2b, 0x75, 0x3e, 0xa4, 0x45, 0x16, 0xdc, 0x52, 0x24, 0x69, 0x21, 0x92, 0x28, 0xfd, 0x30, 0x85, 0x49, 0x04, 0x8f, 0xb0, 0x69, 0x47, 0x7a, 0x7b, 0x85, 0x07, 0xd8, 0xd2, 0x68, 0x84, 0x3b, 0x9f, 0x74, 0x3c, 0x81, 0x35, 0x38, 0xb6, 0xdc, 0x25, 0xc4, 0x96, 0x33, 0x68, 0xc5, 0xaf, 0x99, 0x7a, 0x6d, 0x92, 0x50, 0x6b, 0x44, 0x92, 0x8a, 0x57, 0x7d, 0x03, 0x49, 0xd7, 0x60, 0x7c, 0x4b, 0x44, 0x53, 0xef, 0x37, 0xd1, 0x54, 0x00, 0xb4, 0xfc, 0x6e, 0x44, 0x65, 0xb5, 0xf6, 0xa0, 0xf5, 0xf2, 0x64, 0xbd, 0xb8, 0x7b, 0xf9, 0x3d, 0x88, 0xac, 0x4b, 0xcc, 0x2a, 0x86, 0x65, 0x76, 0x1c, 0xa4, 0xae, 0xbf, 0x84, 0xb0, 0x80, 0x18, 0x80, 0xba, 0xd0, 0x17, 0x90, 0xae, 0x25, 0xc4, 0xfd, 0x09, 0x45, 0xd4, 0x0f, 0xe7, 0x86, 0x1c, 0xbb, 0x9d, 0x05, 0x4d, 0x28, 0x2c, 0xa0, 0x37, 0x43, 0x0d, 0x3f, 0x29, 0xfd, 0x06, 0x85, 0xca, 0x6b, 0xe2, 0x57, 0xd1, 0x5d, 0xb0, 0x92, 0x48, 0x12, 0xec, 0x3b, 0xde, 0x80, 0xf2, 0x7f, 0x20, 0xd9, 0x26, 0xae, 0xc5, 0x52, 0x52, 0xa8, 0xea, 0x26, 0x66, 0x04, 0x4e, 0x5c, 0x89, 0xc8, 0x16, 0xe2, 0xf4, 0x61, 0x7f, 0x70, 0x92, 0xd5, 0x4d, 0xa4, 0x17, 0x2d, 0xa8, 0x8b, 0x93, 0x09, 0xe6, 0x4c, 0xd1, 0x96, 0xfe, 0xa2, 0xfb, 0xc4, 0xbb, 0xfb, 0x16, 0x9f, 0x3e, 0xb6, 0x35, 0xda, 0xa0, 0xca, 0x94, 0x67, 0x66, 0x95, 0x36, 0x2f, 0xda, 0xd2, 0xd2, 0x7d, 0xed, 0x40, 0x91, 0x40, 0xb6, 0xc1, 0xde, 0xac, 0x37, 0x6f, 0xd5, 0xb8, 0x01, 0x55, 0x89, 0x08, 0x3c, 0x7c, 0xff, 0xe6, 0x5a, 0x92, 0xdd, 0xf7, 0xf3, 0x03, 0xf5, 0xbe, 0xca, 0x39, 0x79, 0x03, 0x3a, 0xa5, 0x2d, 0x9c, 0x17, 0xb6, 0x65, 0x77, 0xef, 0x9a, 0x03, 0x7e, 0x9b, 0x24, 0x1e, 0xaf, 0xa1, 0xae, 0xf4, 0x58, 0x8e, 0x43, 0x2a, 0xeb, 0x11, 0x95, 0x79, 0xa1, 0x26, 0xd5, 0xc3, 0xe3, 0xe7, 0xa9, 0x3f, 0x41, 0xa5, 0x28, 0x4c, 0xbc, 0xd1, 0xf2, 0x68, 0x36, 0xa4, 0x84, 0x8a, 0x10, 0x93, 0x25, 0xb8, 0xe4, 0xf4, 0xcc, 0x9b, 0x7c, 0x94, 0xc1, 0x0b, 0x18, 0x1d, 0x60, 0x27, 0x0e, 0xa0, 0xc9, 0x29, 0x9b, 0xb4, 0x80, 0x57, 0x4d, 0x59, 0xc0, 0xc2, 0xd3, 0xa7, 0x3f, 0x2f, 0x24, 0x45, 0xe4, 0x4d, 0x9a, 0xdc, 0xab, 0xa6, 0x4c, 0xee, 0xd5, 0x42, 0xdd, 0x11, 0x5c, 0xe5, 0x41, 0x9c, 0xa9, 0xd3, 0x5c, 0x82, 0x33, 0x21, 0x38, 0xad, 0xdf, 0x9d, 0x24, 0x7a, 0x6a, 0xae, 0xb2, 0x53, 0x97, 0x7f, 0x40, 0x2c, 0x5b, 0x89, 0xe7, 0xe9, 0x50, 0xa4, 0xad, 0xa5, 0x35, 0x6b, 0xc6, 0x96, 0xae, 0xbc, 0xc7, 0xee, 0x8b, 0x26, 0xbc, 0x26, 0x46, 0xaf, 0x54, 0x78, 0xdd, 0xed, 0x1d, 0xf3, 0x17, 0xcc, 0xdd, 0x5e, 0xfc, 0xec, 0xc1, 0x4c, 0x3b, 0xa0, 0xd6, 0x47, 0x4b, 0x5c, 0x8a, 0x78, 0xff, 0xce, 0x46, 0xfe, 0xfa, 0x93, 0x0f, 0xaa, 0xd4, 0x3d, 0xba, 0x0c, 0x59, 0x6b, 0x3f, 0xd8, 0xd1, 0x3b, 0xd4, 0xd9, 0x38, 0x56, 0x44, 0x7d, 0x65, 0x37, 0x88, 0xf4, 0xa4, 0x45, 0x7a, 0x2e, 0x4c, 0xa8, 0x9c, 0x50, 0xec, 0x1a, 0xa1, 0xd2, 0xcd, 0xa2, 0x10, 0xe4, 0x66, 0xa1, 0x87, 0x66, 0xa1, 0x48, 0x13, 0xb1, 0x28, 0x35, 0xd3, 0x96, 0xb6, 0xa4, 0x82, 0xdc, 0x53, 0xe7, 0xf0, 0x51, 0x71, 0x7a, 0xad, 0x12, 0xcf, 0xe3, 0xe9, 0x84, 0xba, 0xe6, 0x41, 0x3d, 0xf3, 0x7f, 0x63, 0x8f, 0x52, 0x13, 0x89, 0xfa, 0xd3, 0xe5, 0x3a, 0x44, 0xa2, 0x2e, 0x47, 0x50, 0x97, 0x5b, 0x4e, 0x3e, 0x90, 0xa1, 0xee, 0x36, 0x28, 0x65, 0xad, 0x7d, 0xe4, 0xea, 0x89, 0xfe, 0xcc, 0x49, 0x76, 0x18, 0xd7, 0x2e, 0x1b, 0xff, 0x9c, 0x3a, 0x07, 0xfb, 0x95, 0x4b, 0xfc, 0x2c, 0x6d, 0x46, 0xc0, 0xed, 0x37, 0xad, 0x47, 0xfe, 0xa6, 0xe4, 0x8c, 0x98, 0x38, 0xd0, 0x9b, 0x5c, 0x89, 0x53, 0x39, 0x11, 0x1e, 0xb5, 0xd4, 0xd7, 0x09, 0xc6, 0x2d, 0xce, 0x80, 0xef, 0x78, 0xc3, 0xea, 0xc9, 0x0c, 0x2c, 0x7d, 0x46, 0xb0, 0xbd, 0xc2, 0x2c, 0x49, 0x4f, 0x90, 0xc1, 0x93, 0xc2, 0x1b, 0x9c, 0x8e, 0x81, 0xa5, 0x56, 0x24, 0x39, 0xcd, 0xbc, 0xa8, 0x17, 0x88, 0xb4, 0xb9, 0x2b, 0xaf, 0x6a, 0xed, 0x9d, 0x83, 0x4d, 0xbb, 0x57, 0xf6, 0xba, 0xab, 0x32, 0x54, 0x52, 0x5d, 0x61, 0xdb, 0xaa, 0x59, 0x9b, 0xb7, 0xce, 0x5f, 0x30, 0x67, 0x47, 0xf1, 0xb3, 0x87, 0x32, 0x6d, 0xe9, 0xb3, 0xe3, 0x97, 0x03, 0x37, 0x0e, 0x16, 0x58, 0xa2, 0x95, 0xde, 0x2e, 0x4e, 0x1e, 0xca, 0x0f, 0xad, 0x5d, 0x0e, 0xaa, 0x10, 0x4d, 0xf9, 0x33, 0xe4, 0x6e, 0x71, 0x8e, 0xb4, 0xf3, 0x0f, 0x81, 0xe7, 0xa8, 0x2f, 0x09, 0x3b, 0xd1, 0xfe, 0x44, 0x06, 0x9c, 0xfe, 0x33, 0x05, 0xfb, 0x85, 0x81, 0xc0, 0xc5, 0x57, 0xd0, 0xae, 0x04, 0x39, 0x20, 0x92, 0x38, 0x81, 0x28, 0xf7, 0x01, 0xfe, 0x86, 0xd3, 0x68, 0xcd, 0xc4, 0x05, 0xc4, 0xc4, 0xf9, 0xde, 0xa7, 0xbc, 0x61, 0x4e, 0x2f, 0xa4, 0xd7, 0x62, 0x18, 0x19, 0x8c, 0x29, 0x8a, 0x2a, 0xa1, 0x61, 0x20, 0x48, 0x01, 0x76, 0xd4, 0x08, 0x9e, 0x73, 0x2e, 0x2c, 0xea, 0x1b, 0x6e, 0xad, 0x2a, 0x79, 0x91, 0xd3, 0x85, 0xdd, 0xfa, 0xdc, 0xa0, 0xc1, 0xae, 0x55, 0x32, 0x6a, 0x86, 0x7f, 0xcf, 0x68, 0xeb, 0xa8, 0x2d, 0x1d, 0x0e, 0xa2, 0xfa, 0xe3, 0x0a, 0x47, 0x20, 0x6a, 0xf6, 0x66, 0x49, 0x24, 0x12, 0x54, 0x63, 0x2e, 0x4e, 0x5d, 0x4b, 0x9e, 0x62, 0xb7, 0xc0, 0xcd, 0x6f, 0x43, 0xa2, 0x56, 0x0b, 0x99, 0x1e, 0x36, 0x9c, 0x34, 0x27, 0x8b, 0x3e, 0xc2, 0x01, 0x41, 0x99, 0x6a, 0xab, 0x92, 0x5b, 0xa8, 0xee, 0x54, 0x19, 0x86, 0x1e, 0x94, 0xef, 0x17, 0xf4, 0xdb, 0xad, 0x26, 0x03, 0xa7, 0x86, 0x5b, 0xc9, 0x28, 0x88, 0x4a, 0x64, 0x28, 0x26, 0xef, 0xbb, 0x61, 0xc2, 0x91, 0x73, 0xbe, 0x3b, 0xd2, 0x5b, 0xf3, 0xb7, 0x43, 0xb8, 0x09, 0xf5, 0x4a, 0xa9, 0xa3, 0x64, 0x8c, 0x5d, 0x43, 0xc8, 0x50, 0xdc, 0x55, 0xb2, 0x8a, 0x1c, 0x2a, 0x5b, 0x89, 0x83, 0x94, 0x3a, 0x10, 0xdf, 0x9e, 0x85, 0x43, 0x08, 0x64, 0x84, 0x0c, 0x85, 0x40, 0xb1, 0x32, 0xb8, 0x69, 0x48, 0x82, 0x1c, 0xea, 0x2f, 0x00, 0x29, 0xff, 0xe5, 0xf7, 0xc0, 0x7f, 0x31, 0x5f, 0xfc, 0x7c, 0x1b, 0xff, 0x1e, 0xf9, 0xde, 0x56, 0x6c, 0x8f, 0x8a, 0x53, 0x12, 0xaa, 0x05, 0xd2, 0x47, 0x0d, 0x57, 0x7b, 0xe7, 0xd9, 0xb0, 0x9e, 0xa4, 0x30, 0x68, 0xba, 0x1f, 0xfb, 0xd4, 0x00, 0xc0, 0xd1, 0xa5, 0xdd, 0x28, 0x47, 0x02, 0xef, 0xb7, 0x7b, 0xd0, 0x3b, 0xe6, 0xa2, 0x8d, 0xb8, 0x85, 0x20, 0x29, 0x72, 0xfd, 0x34, 0x57, 0x50, 0x70, 0x30, 0x43, 0x3e, 0x5f, 0xc0, 0xc3, 0xc8, 0x26, 0x32, 0x3c, 0xc4, 0x54, 0x69, 0x6c, 0x4b, 0x9c, 0x92, 0xe5, 0xc1, 0x4a, 0x5e, 0xdf, 0xb4, 0x6c, 0x64, 0xa3, 0xa3, 0x74, 0x76, 0x61, 0xd1, 0xec, 0x52, 0x27, 0xb9, 0x63, 0x87, 0x35, 0xa2, 0x19, 0xcc, 0x2b, 0x9e, 0x7b, 0xed, 0x92, 0x78, 0x7c, 0xc9, 0xb5, 0x73, 0xe7, 0x5e, 0xb3, 0x24, 0xde, 0xda, 0xc0, 0x80, 0xf5, 0xbb, 0x77, 0xaf, 0x17, 0x61, 0xeb, 0xc6, 0x5e, 0x53, 0xeb, 0xae, 0x50, 0xa6, 0xe3, 0xd9, 0x0d, 0x9c, 0x40, 0xf9, 0x72, 0x59, 0x70, 0xac, 0x5d, 0xb0, 0x2f, 0x71, 0x54, 0x8b, 0x24, 0x0e, 0xc7, 0xd9, 0x8a, 0xac, 0xd5, 0x97, 0x94, 0xa0, 0x11, 0x41, 0xaf, 0xbb, 0x53, 0xfb, 0xe6, 0x1e, 0xb2, 0x35, 0xea, 0x0d, 0x46, 0x7d, 0x90, 0x60, 0x96, 0xcb, 0xe1, 0x5d, 0xa7, 0x97, 0x9a, 0x49, 0x01, 0x5e, 0x83, 0x8f, 0x33, 0xf4, 0x0a, 0xb3, 0x27, 0xea, 0x88, 0x15, 0xe5, 0x37, 0x77, 0x35, 0xe7, 0x7b, 0x6a, 0x07, 0x2a, 0xbd, 0x0d, 0x89, 0x12, 0x7d, 0x36, 0xa3, 0x51, 0xa8, 0x1c, 0xc1, 0x22, 0x7f, 0xac, 0x0c, 0x9f, 0x70, 0x55, 0xcf, 0x2f, 0xaf, 0x5d, 0x1b, 0x64, 0xfe, 0x68, 0x50, 0x6b, 0xd5, 0xd1, 0x80, 0xcb, 0x6b, 0xb1, 0xe6, 0x34, 0x57, 0x16, 0xf4, 0xd4, 0x04, 0x54, 0x66, 0x9f, 0xbe, 0x59, 0xaa, 0xd0, 0x99, 0x74, 0x39, 0x79, 0x3e, 0xbf, 0xd9, 0x94, 0xd3, 0x51, 0x1f, 0x6e, 0xaf, 0xf0, 0x85, 0x91, 0x0e, 0x9a, 0x01, 0xc7, 0xfb, 0xcf, 0x70, 0xbc, 0x35, 0x28, 0x9a, 0x59, 0x81, 0xe3, 0xec, 0xe0, 0xae, 0x19, 0x80, 0x0e, 0x44, 0xed, 0x59, 0x62, 0x26, 0x3c, 0xe7, 0xe6, 0x68, 0x19, 0x8a, 0x8c, 0xf2, 0x4e, 0x01, 0x84, 0x2c, 0x24, 0xff, 0xcc, 0xbf, 0x73, 0x90, 0x7f, 0x67, 0xab, 0xce, 0xcc, 0xdc, 0x29, 0xd5, 0x48, 0x25, 0x99, 0xd2, 0x93, 0x8c, 0x59, 0xcb, 0xae, 0x19, 0xf3, 0x92, 0xef, 0x69, 0xd5, 0xfc, 0x62, 0x4b, 0x85, 0xdb, 0x5d, 0x61, 0x01, 0x27, 0x33, 0xf4, 0xf0, 0x5d, 0x66, 0xf8, 0xae, 0x5f, 0xc0, 0x77, 0x59, 0x11, 0x1e, 0x9a, 0x55, 0xa7, 0x82, 0xab, 0x42, 0x48, 0xdd, 0x22, 0x3b, 0x5a, 0x10, 0x43, 0x87, 0xaf, 0x0b, 0x69, 0xb2, 0x50, 0xb1, 0x4b, 0xb1, 0x60, 0x4c, 0x12, 0x9a, 0x55, 0x8b, 0x23, 0x0f, 0xb5, 0xf1, 0x6a, 0x10, 0x2f, 0x87, 0x4c, 0xf3, 0x17, 0x37, 0xab, 0xb5, 0xcc, 0x49, 0xc8, 0xd2, 0x6e, 0x61, 0x34, 0xaa, 0xeb, 0x3e, 0xfe, 0x9f, 0x9b, 0xd5, 0x2a, 0xe6, 0x24, 0x2b, 0xbb, 0x56, 0xae, 0xbf, 0xe1, 0xaf, 0xd4, 0x51, 0xb5, 0x02, 0xa8, 0xd8, 0x0c, 0xe6, 0x73, 0x79, 0x26, 0xd8, 0x02, 0x86, 0xa5, 0x72, 0xfe, 0x82, 0x9e, 0x7b, 0x4f, 0xc7, 0x9f, 0xc2, 0x7a, 0x26, 0xe4, 0xce, 0x47, 0xc9, 0xbb, 0x60, 0x1b, 0x74, 0xa8, 0x4a, 0xa2, 0x92, 0x22, 0xa8, 0x24, 0x28, 0x5b, 0xb2, 0xc3, 0xd9, 0x1e, 0x8e, 0x92, 0x19, 0x05, 0x74, 0x7c, 0x2c, 0x20, 0x70, 0x67, 0xc5, 0x44, 0xb6, 0x9b, 0x6f, 0xe6, 0x3f, 0xdf, 0xaf, 0x33, 0x4a, 0x76, 0xca, 0x39, 0x09, 0x9b, 0x29, 0xdf, 0x05, 0xfb, 0x7a, 0x80, 0x7e, 0x90, 0xff, 0x5f, 0x60, 0x02, 0xff, 0xa1, 0xc9, 0xfc, 0x93, 0x31, 0xa4, 0xd3, 0x85, 0x8c, 0x7f, 0xca, 0x10, 0xeb, 0x49, 0x45, 0xe0, 0xbb, 0x86, 0xe1, 0xbb, 0x32, 0x44, 0xc4, 0x91, 0xe4, 0x12, 0x12, 0xb3, 0x7d, 0x67, 0x81, 0x56, 0x4e, 0xc7, 0x69, 0x71, 0x77, 0x75, 0x12, 0x21, 0xcc, 0xaf, 0x18, 0xb8, 0x63, 0x6e, 0x72, 0x58, 0xcd, 0xdf, 0x0a, 0x56, 0x8e, 0xfd, 0x2a, 0x03, 0x2c, 0xe3, 0xef, 0x21, 0xdd, 0x6c, 0xb1, 0xd1, 0xc8, 0xd3, 0xdb, 0x2a, 0x0c, 0x3a, 0xc0, 0x6f, 0x25, 0xc0, 0xd8, 0x23, 0x2c, 0x4b, 0xce, 0x66, 0x6f, 0xfb, 0xa6, 0x3c, 0x1b, 0xf4, 0xb4, 0xb1, 0x47, 0xe8, 0x7b, 0x2e, 0xf4, 0xb3, 0xb7, 0x1d, 0x23, 0x70, 0xf4, 0xe4, 0x2d, 0xe0, 0x2d, 0xb6, 0xfd, 0x1b, 0xef, 0x81, 0xac, 0x10, 0x34, 0x1f, 0x3e, 0xcc, 0xb6, 0xf3, 0x18, 0xa1, 0x80, 0xf8, 0x31, 0x7d, 0x0b, 0x39, 0x82, 0xef, 0xc1, 0x35, 0x25, 0x49, 0x21, 0x1d, 0x84, 0x4c, 0x4b, 0x07, 0x49, 0xaf, 0x29, 0x89, 0x6e, 0xff, 0xf1, 0x75, 0xd7, 0x01, 0xf7, 0x75, 0xd7, 0xa1, 0x27, 0x80, 0xb3, 0xf0, 0x29, 0xe4, 0xf8, 0x5b, 0xf4, 0x61, 0x72, 0x3d, 0x73, 0x1a, 0xb2, 0x0b, 0x3d, 0x8a, 0x48, 0xc2, 0xf0, 0x77, 0x7d, 0xa8, 0x5a, 0x3c, 0x54, 0xa0, 0x41, 0xab, 0x5c, 0x0e, 0x08, 0xb9, 0x5e, 0xae, 0xd7, 0x64, 0x0a, 0x0e, 0x1e, 0x5c, 0x9c, 0x29, 0xe5, 0x6f, 0xf2, 0xe2, 0xb8, 0x02, 0xc1, 0xf5, 0x74, 0xab, 0xd7, 0x9b, 0x71, 0x75, 0xa7, 0xd1, 0x91, 0xf1, 0xd2, 0x13, 0x38, 0xa4, 0xe0, 0xf1, 0x05, 0x46, 0xbb, 0xea, 0x3c, 0x73, 0x1a, 0xb9, 0x98, 0x38, 0x40, 0xa2, 0x48, 0x02, 0xfc, 0x05, 0xf9, 0x9a, 0xee, 0x63, 0xe5, 0xe4, 0x11, 0x89, 0x15, 0xe5, 0x5f, 0x11, 0xd1, 0x44, 0x16, 0x03, 0x95, 0x5e, 0x1c, 0x71, 0x88, 0xbf, 0x10, 0xa0, 0x0f, 0xb7, 0x1d, 0x3b, 0x98, 0x32, 0x55, 0x72, 0xad, 0x42, 0x2b, 0xbe, 0x9b, 0x9d, 0x78, 0x77, 0x2e, 0xf0, 0xa6, 0xbe, 0x81, 0x3b, 0xbd, 0x11, 0xd5, 0x4d, 0x0b, 0x8d, 0xd6, 0xcc, 0x5f, 0xfe, 0xc5, 0x1b, 0xe5, 0x7e, 0x3c, 0x6a, 0x74, 0x73, 0x17, 0x24, 0x56, 0xa3, 0xf1, 0xcf, 0x99, 0xaa, 0xcf, 0x4c, 0x86, 0x3f, 0xab, 0xd5, 0x9f, 0xa5, 0x7c, 0x0a, 0x37, 0xc2, 0x5e, 0x86, 0xd1, 0x3b, 0x91, 0x83, 0x95, 0x44, 0x0e, 0xd6, 0xc9, 0x5e, 0x37, 0x40, 0x04, 0x7c, 0x36, 0xcb, 0x84, 0xe3, 0x40, 0x32, 0xbd, 0xe3, 0x60, 0x8a, 0x45, 0x7a, 0x1a, 0x77, 0x41, 0x50, 0xe7, 0x8d, 0x22, 0xb8, 0x5d, 0x9d, 0xce, 0x9d, 0x6d, 0x36, 0x47, 0xbd, 0xba, 0x69, 0x7c, 0x04, 0x07, 0xcd, 0xd9, 0x1e, 0x9d, 0xce, 0x03, 0xcf, 0xe7, 0xa0, 0xcf, 0x1c, 0x62, 0xc2, 0xf7, 0x83, 0x30, 0x45, 0xfc, 0x28, 0x4f, 0x83, 0xc1, 0x15, 0x5c, 0x10, 0xfe, 0x5c, 0xd2, 0x6f, 0x33, 0xe1, 0x87, 0x63, 0xb1, 0x1f, 0x4e, 0xaf, 0x73, 0xbb, 0x1c, 0x36, 0x8b, 0x59, 0xe7, 0xd7, 0xfb, 0x43, 0x50, 0x2d, 0x30, 0x5c, 0x92, 0x9b, 0x3f, 0xd5, 0x81, 0x49, 0x6a, 0xa6, 0x36, 0x6e, 0xef, 0x24, 0x9b, 0xbe, 0xe4, 0x80, 0x39, 0x8a, 0xce, 0xc1, 0x6b, 0x72, 0xd0, 0x67, 0x0e, 0xbf, 0x74, 0xb2, 0x27, 0x13, 0xad, 0x9d, 0xcd, 0xf4, 0xed, 0xe4, 0x83, 0x70, 0xdf, 0xa2, 0x47, 0x11, 0x20, 0x28, 0x11, 0x5a, 0x0c, 0x5e, 0x42, 0xe1, 0x31, 0x2b, 0x48, 0x48, 0xc8, 0x09, 0x22, 0x0a, 0x15, 0xb3, 0x1d, 0xc8, 0x87, 0x90, 0xe6, 0x4c, 0x00, 0x37, 0x74, 0xac, 0xa8, 0xb5, 0x5f, 0x93, 0xe1, 0x2e, 0xce, 0xca, 0x2a, 0x76, 0x67, 0x5c, 0x63, 0xaf, 0x5d, 0xc1, 0x3c, 0x1c, 0x69, 0x19, 0x2c, 0x4e, 0xd2, 0xa8, 0x78, 0xb0, 0x45, 0xc8, 0x87, 0x03, 0x3e, 0xba, 0x93, 0xd4, 0xb2, 0x99, 0x84, 0x19, 0x45, 0xc8, 0x9a, 0xa7, 0x09, 0x0c, 0xf5, 0x35, 0xe3, 0xf5, 0x91, 0x2c, 0x56, 0xcc, 0x7a, 0x04, 0xed, 0x41, 0xb0, 0x72, 0x48, 0x40, 0xa7, 0x52, 0xca, 0x48, 0x4d, 0x66, 0x23, 0xfc, 0x97, 0x29, 0x4a, 0x24, 0xe2, 0x15, 0x95, 0xdd, 0xb3, 0xe8, 0x15, 0xb2, 0x4c, 0x26, 0x83, 0x95, 0x59, 0x7d, 0x11, 0x33, 0xa8, 0x8f, 0xe5, 0xd6, 0xcc, 0xad, 0xed, 0xf8, 0x5e, 0x9d, 0xf0, 0x3e, 0x15, 0xdd, 0x09, 0xc6, 0xe0, 0xfb, 0xac, 0x68, 0x55, 0x69, 0xa7, 0x79, 0x9f, 0xc5, 0x54, 0x8f, 0xcb, 0xe4, 0x21, 0x42, 0x67, 0x81, 0xa0, 0x83, 0xc2, 0xe9, 0x97, 0x6a, 0x80, 0xb1, 0xf4, 0xe1, 0x5b, 0xc1, 0x18, 0xe7, 0x8c, 0x98, 0x19, 0x19, 0x23, 0x31, 0x99, 0xcd, 0x52, 0x54, 0xfb, 0x6e, 0x56, 0x57, 0x65, 0x45, 0xac, 0x86, 0xb9, 0xd3, 0x12, 0x75, 0x69, 0x80, 0x39, 0xe2, 0xb3, 0xca, 0x20, 0x0b, 0x54, 0xcb, 0x2d, 0x75, 0xdf, 0xeb, 0xa8, 0x9d, 0x5b, 0x03, 0x9f, 0x79, 0x0b, 0x2f, 0x25, 0xd7, 0x8d, 0xbf, 0x2a, 0x64, 0x9a, 0xa1, 0x42, 0x98, 0x08, 0x3f, 0x4c, 0x4c, 0x11, 0x41, 0xf1, 0xcc, 0xa8, 0xf0, 0x14, 0x21, 0xe1, 0x04, 0x80, 0x03, 0x86, 0x13, 0x10, 0x80, 0xc9, 0x75, 0xd7, 0x22, 0x54, 0xe2, 0x22, 0xbe, 0xe2, 0xd4, 0x2e, 0x81, 0x7f, 0xa6, 0x3d, 0x27, 0x55, 0xc1, 0x60, 0x01, 0x52, 0xb2, 0xc4, 0x6a, 0x96, 0x49, 0x86, 0x00, 0x50, 0x05, 0x8d, 0x42, 0xbd, 0x9b, 0x5c, 0xc7, 0x3f, 0x76, 0xed, 0xb5, 0xa0, 0x83, 0x7f, 0xf8, 0x14, 0x78, 0xf1, 0x14, 0xba, 0x9f, 0x3e, 0x40, 0xae, 0x63, 0x9b, 0xe1, 0xfa, 0x70, 0xa0, 0x1c, 0xfe, 0x69, 0xb2, 0x55, 0x26, 0xe5, 0xaa, 0x08, 0x2d, 0x78, 0x97, 0x3e, 0x00, 0x1a, 0xae, 0xc4, 0xef, 0xbf, 0x09, 0xbe, 0x7f, 0x0b, 0x7e, 0x7f, 0x08, 0x45, 0x36, 0x88, 0x38, 0x0d, 0xa8, 0xce, 0x60, 0x0a, 0xaf, 0x01, 0x20, 0x30, 0xa9, 0xb4, 0xde, 0x00, 0x0c, 0xd7, 0x50, 0xc8, 0x91, 0x5b, 0x78, 0xcb, 0x99, 0xc3, 0xfc, 0x81, 0x5d, 0xbb, 0x40, 0x23, 0xff, 0xf4, 0xa4, 0xbe, 0x40, 0x79, 0x24, 0xc2, 0xa9, 0x43, 0xba, 0xcc, 0xc3, 0xfd, 0x21, 0x91, 0x87, 0x3a, 0xc5, 0xe0, 0x18, 0xaf, 0x10, 0xe0, 0x4d, 0xae, 0xbb, 0xff, 0xf0, 0xfd, 0xe0, 0xdd, 0xc7, 0xf9, 0x9f, 0xe0, 0x06, 0x4d, 0x3c, 0x43, 0x81, 0x9f, 0x81, 0xe0, 0xc6, 0xc1, 0x02, 0x64, 0x36, 0xc7, 0x0f, 0x10, 0x29, 0x92, 0x0a, 0x14, 0xd7, 0x7b, 0x45, 0x23, 0x6f, 0x21, 0x7e, 0x0e, 0xfc, 0x1f, 0xd2, 0x05, 0x92, 0x36, 0x45, 0xdb, 0x87, 0xe1, 0xb3, 0xc2, 0xc9, 0x7a, 0xbf, 0xa9, 0xe2, 0xa5, 0x02, 0x7b, 0x46, 0x66, 0xe4, 0x5b, 0xc0, 0xbb, 0x7c, 0xe0, 0xb7, 0xbb, 0xe0, 0xb5, 0xab, 0xf8, 0x83, 0x64, 0xcd, 0xf8, 0x3f, 0x08, 0x03, 0x92, 0x2d, 0x72, 0x11, 0x55, 0x06, 0x99, 0x7b, 0x27, 0x6c, 0x1c, 0x59, 0x7e, 0x4c, 0x48, 0xa6, 0x70, 0x9a, 0x5a, 0x76, 0xe0, 0x91, 0xa3, 0x8e, 0x8a, 0xfe, 0xca, 0x96, 0x0d, 0xee, 0x7e, 0xb9, 0x8a, 0x96, 0xab, 0xf5, 0x2e, 0x6b, 0x76, 0xa1, 0x6e, 0x34, 0xbf, 0xaf, 0x21, 0xab, 0x75, 0x9e, 0x8f, 0x06, 0x7a, 0x8b, 0xbe, 0xaa, 0x18, 0xb5, 0xe9, 0x15, 0xf8, 0x9e, 0xef, 0xe3, 0xf7, 0x40, 0xee, 0xad, 0xc4, 0xfa, 0x01, 0x21, 0x38, 0xed, 0x85, 0xf8, 0x71, 0xac, 0xdc, 0xac, 0x21, 0x5b, 0xf5, 0xbe, 0x42, 0x0f, 0x26, 0x54, 0x52, 0x93, 0x4e, 0x01, 0xf8, 0x57, 0x82, 0x42, 0xb0, 0xa0, 0x28, 0x62, 0x75, 0xe9, 0x33, 0xe5, 0x74, 0x86, 0x7c, 0x81, 0x7b, 0x63, 0x0b, 0x4a, 0x96, 0xb9, 0x69, 0x67, 0x71, 0x15, 0x7c, 0x0b, 0xa0, 0x7d, 0xf3, 0x5a, 0xb3, 0x1a, 0xfa, 0xf2, 0x47, 0x05, 0xdf, 0xe5, 0x39, 0x3e, 0x9f, 0x5c, 0x38, 0x8e, 0xec, 0xd5, 0x4e, 0xa2, 0x5a, 0xa8, 0x2a, 0xa9, 0x9d, 0x30, 0x5a, 0x0b, 0x7d, 0x5b, 0x0e, 0x70, 0xd5, 0xd4, 0xcb, 0x98, 0xb2, 0x7b, 0xcf, 0xfa, 0x02, 0x97, 0x33, 0x66, 0x4f, 0xe4, 0x02, 0x9f, 0x73, 0x97, 0x34, 0x87, 0xb3, 0x5a, 0x4a, 0x3d, 0x9e, 0xd2, 0x96, 0xac, 0x70, 0x73, 0x89, 0x7b, 0x65, 0x51, 0x6e, 0x24, 0x1e, 0x8f, 0xe4, 0x16, 0xf1, 0xff, 0x8c, 0xcc, 0x28, 0xb0, 0xd9, 0x0a, 0x66, 0x44, 0xb2, 0x1a, 0xf3, 0x6d, 0xb6, 0xfc, 0x46, 0xc8, 0x3f, 0x8a, 0xb3, 0xa2, 0xb1, 0x18, 0x5e, 0xbb, 0xcd, 0x7c, 0x0c, 0xbc, 0x35, 0xfe, 0x77, 0x38, 0x46, 0xa9, 0x7a, 0xc0, 0xe2, 0x40, 0x2d, 0x9f, 0xa8, 0x07, 0x9c, 0x26, 0x5a, 0x7b, 0x9f, 0x10, 0x46, 0x8f, 0x11, 0x85, 0xab, 0x1e, 0xcb, 0x56, 0x40, 0x14, 0xf0, 0xa7, 0xc9, 0xb7, 0xc7, 0x7f, 0x04, 0xe7, 0x9e, 0x19, 0x6e, 0x68, 0x84, 0x00, 0xc0, 0xc5, 0x13, 0x53, 0x46, 0x0c, 0x6b, 0x8b, 0xa1, 0x65, 0xe4, 0x26, 0xdf, 0xde, 0xcf, 0x0f, 0xed, 0x27, 0xef, 0xe3, 0x4f, 0xdf, 0x7a, 0x2b, 0x50, 0x12, 0xe2, 0xfd, 0xaf, 0xe3, 0xfb, 0x5d, 0xc4, 0xfa, 0x27, 0x25, 0xe8, 0x66, 0xd1, 0xb9, 0x6b, 0x90, 0xb0, 0x70, 0x21, 0x50, 0x14, 0xb9, 0x14, 0xcf, 0xc2, 0x7e, 0x20, 0x3c, 0xd2, 0x2a, 0xc0, 0x2d, 0xeb, 0x85, 0xb3, 0xe4, 0xba, 0xc9, 0x27, 0x13, 0xd6, 0xb4, 0xe3, 0x90, 0xac, 0xdd, 0xa9, 0xd3, 0x14, 0x31, 0xab, 0x57, 0xf0, 0xf7, 0xb8, 0x08, 0x97, 0x57, 0xeb, 0xd5, 0xfa, 0xbc, 0xc8, 0xdf, 0xc3, 0x08, 0xd6, 0x5d, 0xa1, 0xca, 0x07, 0x2e, 0xd6, 0x69, 0x80, 0x93, 0x4b, 0xf8, 0x62, 0x34, 0xc4, 0x2b, 0x01, 0xf9, 0xf6, 0xe8, 0xdc, 0xfc, 0x56, 0xb3, 0xb9, 0x35, 0x7f, 0xee, 0xe8, 0xb5, 0x6e, 0x8f, 0xd3, 0x75, 0xd8, 0xe5, 0xf4, 0xb8, 0x9b, 0x0c, 0x2b, 0x47, 0x7c, 0x6e, 0xb7, 0x6f, 0x64, 0xa5, 0xc1, 0x10, 0x8e, 0x02, 0x10, 0x0d, 0x1a, 0x0c, 0x41, 0xf4, 0x19, 0x36, 0x08, 0x7d, 0xba, 0x53, 0xa4, 0x49, 0xb5, 0x08, 0x1e, 0x83, 0xd6, 0x34, 0x1c, 0xd8, 0xa5, 0x88, 0xf5, 0xcf, 0x4f, 0x6e, 0xf3, 0x34, 0xf8, 0x28, 0x58, 0x97, 0x76, 0x50, 0xa8, 0xe0, 0x9b, 0xce, 0xc0, 0xdc, 0x88, 0xfb, 0xc4, 0xc8, 0xb7, 0xc7, 0xe6, 0xef, 0x07, 0x27, 0x9a, 0xf8, 0xbf, 0x02, 0xd3, 0x7d, 0xf7, 0xa1, 0xf9, 0x95, 0xe4, 0x1f, 0xc9, 0x9c, 0x5b, 0x8a, 0x10, 0xb2, 0x9e, 0x53, 0x81, 0xe1, 0xbd, 0x88, 0xa9, 0xa4, 0xad, 0x7b, 0x7d, 0x21, 0x94, 0xf7, 0x6e, 0xf0, 0xd5, 0x5f, 0xff, 0x7a, 0x25, 0x6f, 0x01, 0xff, 0x73, 0xe6, 0x37, 0x5b, 0xc0, 0x8b, 0x68, 0xcf, 0xc7, 0xbf, 0x46, 0xbe, 0x31, 0x7e, 0x96, 0x30, 0x11, 0xbd, 0x89, 0x0c, 0x23, 0xa4, 0x94, 0x86, 0x43, 0x66, 0x57, 0x6c, 0x59, 0x43, 0x94, 0xe6, 0x68, 0x86, 0x4c, 0x95, 0xdf, 0x14, 0x6b, 0xe7, 0x5a, 0x70, 0x82, 0x2d, 0x8d, 0x73, 0x92, 0x88, 0x6e, 0xf4, 0x99, 0x52, 0x46, 0x30, 0xbe, 0x82, 0x41, 0x97, 0xa9, 0x52, 0xc8, 0xe1, 0x3e, 0xd0, 0x04, 0x4c, 0x48, 0x13, 0x62, 0xd0, 0xce, 0x2f, 0xe0, 0x55, 0x01, 0x5c, 0x28, 0x35, 0x10, 0xf4, 0x08, 0x99, 0xa6, 0xc6, 0x38, 0x69, 0x6d, 0x1a, 0x54, 0x1d, 0x64, 0x2b, 0x30, 0x59, 0x2b, 0xd8, 0x83, 0xaa, 0xc1, 0xa6, 0xc1, 0xc2, 0xc2, 0xe6, 0xd6, 0x4e, 0x5f, 0x3d, 0xc8, 0x41, 0x34, 0xcd, 0x01, 0xf5, 0xbe, 0xce, 0xd6, 0x86, 0x06, 0x61, 0xae, 0xdc, 0x49, 0x6f, 0x26, 0x97, 0xb3, 0x35, 0x38, 0xc7, 0x5a, 0x8a, 0x73, 0x3c, 0x8d, 0x12, 0x54, 0x9a, 0x25, 0x58, 0x6c, 0x2c, 0x36, 0x82, 0x37, 0x8f, 0xcf, 0x3a, 0x9e, 0xfc, 0x9f, 0xe9, 0x17, 0xbe, 0x1d, 0xeb, 0x3c, 0x7e, 0xbc, 0x13, 0xcf, 0x77, 0x37, 0xbd, 0x9f, 0xa4, 0xd9, 0xff, 0x16, 0xa2, 0x41, 0x27, 0xb2, 0x83, 0xa0, 0x32, 0xdb, 0x2d, 0xe6, 0x08, 0x51, 0x64, 0x07, 0x67, 0xe5, 0xcc, 0x78, 0xc2, 0x8a, 0x95, 0x7b, 0x0b, 0xf5, 0x42, 0x09, 0x41, 0xbc, 0xf0, 0x62, 0x55, 0x00, 0x6c, 0xb7, 0x79, 0x6c, 0xee, 0xb2, 0xd9, 0x05, 0x91, 0x2c, 0x9b, 0xdb, 0xe6, 0x2a, 0x9b, 0x9d, 0x9f, 0x1d, 0x62, 0x1a, 0x0c, 0xb9, 0xf1, 0x4a, 0x5f, 0xa8, 0x21, 0xdf, 0xb6, 0x0d, 0xf8, 0xdd, 0x86, 0xbc, 0x78, 0x95, 0x37, 0x5c, 0x9f, 0x67, 0xdd, 0x0c, 0xfc, 0xf8, 0xbd, 0xcc, 0x3f, 0x49, 0x5a, 0x9a, 0xf9, 0xff, 0xfe, 0xbd, 0xec, 0x3d, 0x06, 0x7f, 0x56, 0xa1, 0x2f, 0x54, 0x1b, 0x35, 0x6f, 0x03, 0x56, 0xb7, 0x21, 0x90, 0x55, 0xe4, 0x0d, 0xd7, 0x44, 0x8d, 0x9b, 0x01, 0x16, 0x47, 0x4d, 0x74, 0x21, 0xf9, 0x1e, 0xd4, 0x2b, 0x3c, 0x28, 0x4a, 0xc9, 0x6e, 0x81, 0x1a, 0xa3, 0x42, 0x8a, 0x5d, 0xc7, 0x36, 0x2b, 0x49, 0x35, 0xc9, 0x65, 0x28, 0x7e, 0x1d, 0x0e, 0x33, 0xe4, 0xf4, 0x42, 0x21, 0x1c, 0xa8, 0x4e, 0x51, 0xd4, 0x20, 0x96, 0x9f, 0x1e, 0xc2, 0x13, 0xf0, 0x69, 0x82, 0x42, 0x15, 0x58, 0x91, 0xe5, 0xe2, 0x02, 0x38, 0x13, 0xe5, 0x26, 0x71, 0xfd, 0x1b, 0xec, 0x03, 0x24, 0xdf, 0xb3, 0xe6, 0xd5, 0x87, 0x5d, 0xb5, 0xae, 0xc1, 0xfe, 0x45, 0x1a, 0xb7, 0xca, 0xed, 0x07, 0xeb, 0xad, 0x05, 0x75, 0x61, 0x6f, 0x83, 0x7b, 0xa8, 0x7f, 0x91, 0xdb, 0xe4, 0xf1, 0x03, 0xe6, 0x9a, 0x82, 0xd9, 0xe5, 0x2e, 0xbd, 0xea, 0xf6, 0xc2, 0x63, 0x52, 0x59, 0x5e, 0x24, 0x7f, 0x4e, 0x99, 0xdb, 0x88, 0x7e, 0xa8, 0x72, 0xb2, 0x11, 0x2e, 0x23, 0xaf, 0xa7, 0x6b, 0xc6, 0x37, 0xc0, 0x3d, 0x75, 0x69, 0x42, 0xae, 0x56, 0x92, 0xa0, 0x49, 0x42, 0xc3, 0xb6, 0x09, 0x93, 0x4f, 0x91, 0xac, 0x2f, 0xbd, 0x7c, 0xfa, 0x3a, 0xbc, 0x4f, 0x18, 0xf5, 0x58, 0xa1, 0x31, 0xe8, 0x24, 0x0c, 0x64, 0x31, 0xfe, 0x40, 0xbc, 0x18, 0x61, 0x1a, 0xd1, 0x5e, 0x87, 0x83, 0x3f, 0xcf, 0xe7, 0xf0, 0x9f, 0xa3, 0x5d, 0x8f, 0x1c, 0xfc, 0x9a, 0xff, 0x47, 0x66, 0xa1, 0x7a, 0xff, 0xf0, 0xf0, 0x21, 0xf8, 0x71, 0xd5, 0xc8, 0x08, 0x1a, 0x97, 0xdd, 0x63, 0x1f, 0x92, 0xef, 0x8f, 0xff, 0x10, 0xea, 0xd6, 0x75, 0xc2, 0xbb, 0xd4, 0x38, 0x16, 0x84, 0x58, 0x80, 0xc5, 0x2f, 0x2e, 0x7a, 0x6c, 0xc1, 0xeb, 0x13, 0x23, 0x44, 0xa2, 0x7f, 0x84, 0x38, 0x91, 0x5e, 0xac, 0x7c, 0x0a, 0xeb, 0x54, 0x46, 0xc8, 0x34, 0x49, 0xd1, 0x0c, 0x25, 0xa2, 0x36, 0x0e, 0xf7, 0x76, 0xe4, 0xe6, 0x9b, 0x86, 0x0f, 0xa2, 0xd7, 0xf0, 0x07, 0x4f, 0x9d, 0x02, 0x0a, 0xd8, 0x00, 0xfe, 0x3c, 0x6a, 0xec, 0x97, 0xf4, 0x36, 0x32, 0x9b, 0xad, 0x43, 0xa8, 0x2e, 0x8f, 0x33, 0x00, 0xcd, 0x5d, 0x3c, 0x71, 0xc9, 0xec, 0x0f, 0x66, 0xdd, 0x72, 0xcb, 0x2c, 0x66, 0xe1, 0x07, 0xb3, 0x8e, 0x1d, 0xeb, 0x84, 0xd7, 0x7d, 0x4a, 0xdf, 0x04, 0xf7, 0x37, 0xe5, 0x04, 0x87, 0xe7, 0x4b, 0x0a, 0x5d, 0x0a, 0x31, 0x33, 0xd1, 0x06, 0x44, 0x11, 0x1d, 0x3a, 0xce, 0x24, 0x30, 0xd6, 0xa4, 0xf6, 0xaa, 0x17, 0x7c, 0xb2, 0xe0, 0xad, 0xc2, 0xb9, 0x68, 0xa3, 0x3a, 0xb7, 0x30, 0x52, 0x52, 0x12, 0xa1, 0x6f, 0x72, 0x94, 0x74, 0xe4, 0xe5, 0xb5, 0x97, 0x38, 0x46, 0x4a, 0xe2, 0xf1, 0x62, 0x41, 0x2e, 0xd5, 0xd0, 0xc3, 0xe4, 0xfb, 0xac, 0x1d, 0xfb, 0x51, 0xd3, 0xf5, 0x66, 0xa8, 0x24, 0xa3, 0xc8, 0x44, 0xa8, 0xe9, 0xa3, 0x19, 0x9a, 0xa6, 0xc3, 0xe9, 0x75, 0x5e, 0x0f, 0xd6, 0x9b, 0x83, 0xfa, 0xe0, 0x65, 0xf4, 0x66, 0x41, 0x67, 0x0d, 0xa4, 0x54, 0x56, 0x52, 0xaa, 0x85, 0x0a, 0xb3, 0x29, 0x82, 0x82, 0x4c, 0x22, 0x38, 0xd8, 0x64, 0xcb, 0xfc, 0x5d, 0x9d, 0xfe, 0x2b, 0x87, 0x57, 0xc4, 0x3a, 0x0a, 0xcd, 0xbb, 0xfc, 0x9d, 0x57, 0xd2, 0xef, 0x99, 0x84, 0x10, 0x14, 0x93, 0x59, 0xb8, 0x0a, 0x04, 0xa3, 0x9d, 0xab, 0x6b, 0xfb, 0x56, 0xd9, 0x8b, 0x5a, 0xa2, 0xb5, 0xab, 0x3b, 0xa3, 0x78, 0xbd, 0xbf, 0x3f, 0xf6, 0x05, 0xa9, 0x19, 0x7f, 0x1e, 0xd3, 0x8c, 0x4e, 0xae, 0x77, 0xb8, 0xd2, 0xb7, 0xdd, 0x32, 0xeb, 0x83, 0x0f, 0x66, 0xf1, 0xff, 0x7d, 0x6c, 0xd6, 0x07, 0x1f, 0xce, 0x82, 0x2d, 0x95, 0x8f, 0x7d, 0x4e, 0x56, 0x8d, 0x3f, 0x77, 0x09, 0x6d, 0xab, 0x30, 0x6d, 0xf9, 0xff, 0xfe, 0xb0, 0xf3, 0xd8, 0xb1, 0x59, 0x08, 0x7f, 0x70, 0xec, 0x73, 0xca, 0x85, 0xaf, 0x53, 0x10, 0xd2, 0x27, 0xe5, 0x28, 0x1c, 0x3b, 0x2f, 0xe2, 0xc4, 0x17, 0x03, 0x34, 0xc3, 0x8d, 0x83, 0x1f, 0xa0, 0x3b, 0xc8, 0x67, 0xe1, 0xe5, 0x1f, 0x7c, 0x50, 0xf3, 0x21, 0x1a, 0x13, 0xfe, 0x57, 0xb7, 0xde, 0xda, 0xf9, 0xc1, 0x07, 0x82, 0xcc, 0xa4, 0xef, 0x23, 0x73, 0xd8, 0x0e, 0x71, 0x6c, 0x52, 0xf9, 0x7b, 0x70, 0x58, 0xba, 0x45, 0x93, 0x0e, 0x41, 0x75, 0x98, 0x38, 0x9d, 0x30, 0x36, 0x82, 0x84, 0x8e, 0x25, 0xe5, 0x36, 0x68, 0x46, 0x63, 0x92, 0x1c, 0x1f, 0x26, 0xa3, 0x38, 0x1e, 0x2f, 0x19, 0x71, 0x94, 0xb4, 0xe7, 0xe5, 0x75, 0x94, 0x88, 0xb5, 0x2f, 0x96, 0xf3, 0x6b, 0xc1, 0x9b, 0xe3, 0x1f, 0x11, 0xb9, 0x44, 0x5d, 0x42, 0x9b, 0x9b, 0x13, 0x09, 0x79, 0x5d, 0x4e, 0x93, 0x51, 0x9b, 0xa9, 0x90, 0xb0, 0x2a, 0x21, 0xfd, 0xd3, 0xf7, 0x1d, 0x16, 0xc5, 0x93, 0xc1, 0x80, 0x03, 0x69, 0xdd, 0x0c, 0xaa, 0x37, 0xed, 0xfc, 0xa6, 0x12, 0xd5, 0x06, 0x63, 0x40, 0x2c, 0x50, 0x1d, 0xaf, 0x06, 0x46, 0x70, 0x30, 0xc3, 0xa2, 0xb2, 0x9b, 0xda, 0xbc, 0x6d, 0x99, 0x46, 0x99, 0xaa, 0x89, 0x91, 0xc8, 0x0c, 0x2a, 0xf8, 0x43, 0x6d, 0x55, 0x3b, 0xac, 0x33, 0xbd, 0x33, 0xed, 0x1a, 0x74, 0x4c, 0x63, 0x6b, 0xf3, 0xfe, 0x54, 0x2a, 0x27, 0x29, 0x2e, 0xbb, 0x32, 0x5b, 0xa6, 0x53, 0xd9, 0x3c, 0xbd, 0x3d, 0x9c, 0x2d, 0x43, 0xa9, 0x91, 0x46, 0xab, 0xb2, 0xf1, 0xf1, 0x48, 0x65, 0x56, 0xa6, 0xd5, 0xe6, 0x99, 0xdf, 0xcb, 0xd9, 0x55, 0xa6, 0xcc, 0xec, 0x0a, 0x48, 0x94, 0xbb, 0xf9, 0x23, 0x24, 0x3f, 0xfe, 0x1b, 0xec, 0x23, 0x2f, 0x26, 0x1a, 0x13, 0x75, 0x31, 0x00, 0x68, 0xc8, 0x18, 0x10, 0xf0, 0xe3, 0x94, 0xc4, 0x5f, 0x21, 0xdf, 0x97, 0x59, 0xc3, 0xb4, 0x9a, 0x4d, 0x19, 0x50, 0x4d, 0x2b, 0xcc, 0x0f, 0x07, 0xbd, 0x6e, 0x53, 0xb1, 0xb9, 0x58, 0xcb, 0x29, 0x8d, 0x19, 0x46, 0x21, 0x4e, 0x45, 0x48, 0xf8, 0x9d, 0x88, 0x53, 0xf1, 0xa6, 0x7d, 0xf7, 0xa7, 0x21, 0x21, 0x4d, 0x36, 0x3e, 0x56, 0x01, 0xb2, 0xdd, 0xe9, 0xf3, 0x39, 0x5d, 0x3e, 0xdf, 0xef, 0x5c, 0x5e, 0xaf, 0x0b, 0x7e, 0x82, 0x40, 0xa0, 0x2a, 0x62, 0x34, 0x46, 0xaa, 0x02, 0x79, 0xf5, 0x5a, 0x5d, 0x5d, 0x5e, 0xa0, 0x2a, 0xdb, 0x64, 0xca, 0x86, 0xbf, 0xea, 0x74, 0xda, 0x7a, 0xfe, 0x9f, 0x3e, 0xbb, 0xdd, 0x97, 0xfe, 0xf7, 0x43, 0x73, 0x76, 0xb9, 0xc7, 0x53, 0x91, 0x6d, 0x0e, 0xba, 0x5c, 0x41, 0x73, 0x76, 0x85, 0xc7, 0x53, 0x9e, 0x6d, 0x0e, 0xb8, 0x5c, 0x01, 0x61, 0xfc, 0x5e, 0xa1, 0xdb, 0xc9, 0xcd, 0xec, 0x95, 0x44, 0x06, 0xd1, 0x24, 0x44, 0xa3, 0x9b, 0x08, 0xac, 0xc1, 0x88, 0x59, 0xee, 0x90, 0xcd, 0xf6, 0x22, 0x43, 0xef, 0x20, 0x62, 0x2e, 0x06, 0xc4, 0xf8, 0xd7, 0x09, 0x1b, 0x87, 0xb4, 0x13, 0x02, 0x63, 0xc9, 0x20, 0x32, 0xdc, 0x5a, 0x37, 0x66, 0x2c, 0x14, 0xae, 0x69, 0xe0, 0xd5, 0xe2, 0x72, 0x07, 0xe4, 0xe6, 0x2d, 0x4b, 0xf4, 0x2a, 0x69, 0xdd, 0xd7, 0x9b, 0x97, 0x18, 0x32, 0x24, 0x75, 0xcc, 0x29, 0xfe, 0x61, 0x93, 0xf6, 0x31, 0x30, 0xcf, 0xac, 0x79, 0x0c, 0xe3, 0x09, 0xf1, 0x05, 0xd2, 0x8a, 0x71, 0x84, 0x21, 0x5c, 0x7d, 0x36, 0x43, 0xc9, 0x32, 0x34, 0x6a, 0x07, 0xe2, 0x6d, 0x4a, 0x32, 0x99, 0x43, 0x8e, 0x95, 0x3b, 0x8d, 0x60, 0x01, 0xe9, 0x4e, 0x1e, 0xc5, 0x72, 0x5b, 0x01, 0x08, 0xb9, 0x14, 0xc5, 0xb9, 0x51, 0x29, 0xc8, 0x08, 0x96, 0x76, 0xbb, 0x7c, 0xc1, 0x62, 0x07, 0x45, 0xcf, 0x38, 0x3a, 0xf6, 0x97, 0x77, 0xd7, 0x14, 0xbd, 0xce, 0x2b, 0xf8, 0xd7, 0xe1, 0x7f, 0x8a, 0xd7, 0x0b, 0xd7, 0xbc, 0xfb, 0x97, 0x31, 0xfe, 0x45, 0x43, 0x24, 0x11, 0xde, 0xd7, 0x35, 0x38, 0xd8, 0xb5, 0x2f, 0x9c, 0x88, 0x18, 0x10, 0x86, 0xcb, 0x6c, 0xfa, 0x06, 0x69, 0x05, 0xf3, 0x2a, 0x25, 0x21, 0x83, 0x04, 0xc1, 0x2e, 0x14, 0xb0, 0xfa, 0xd8, 0x85, 0x29, 0x8c, 0x97, 0x65, 0xf4, 0x6f, 0xa5, 0x01, 0x66, 0x39, 0x3c, 0x9f, 0x75, 0xe9, 0x79, 0x84, 0x67, 0xc9, 0x6f, 0x95, 0x06, 0xc6, 0x95, 0x82, 0x5e, 0x6a, 0xfb, 0xee, 0x7a, 0x29, 0xda, 0x55, 0x2c, 0x93, 0xfc, 0xf4, 0xcb, 0xda, 0x7f, 0x1f, 0xc5, 0xcf, 0x59, 0x4b, 0x1f, 0x92, 0xda, 0x99, 0xff, 0x21, 0x1c, 0xc4, 0x9a, 0x96, 0x47, 0xb5, 0x38, 0x3b, 0x25, 0x5d, 0xdc, 0x21, 0xc5, 0x70, 0x18, 0xfb, 0x20, 0x90, 0xb2, 0x89, 0x82, 0x5f, 0x51, 0xde, 0x03, 0x4a, 0x94, 0xc6, 0x97, 0x30, 0xcc, 0x70, 0x4b, 0x32, 0x6e, 0x0a, 0x5d, 0x06, 0xb7, 0x44, 0xc9, 0x2b, 0x20, 0x77, 0xa4, 0x11, 0x4c, 0xcf, 0xc4, 0x95, 0x0c, 0xf2, 0x37, 0xc8, 0x34, 0x7e, 0x9f, 0xc6, 0x2f, 0xe4, 0x0a, 0xe0, 0xea, 0xe3, 0x34, 0x9a, 0x8e, 0x62, 0x34, 0x20, 0x8d, 0x4d, 0x0a, 0x52, 0xfb, 0x9f, 0xfe, 0xbe, 0xec, 0xec, 0x83, 0x77, 0xde, 0x73, 0x66, 0xf1, 0xad, 0x47, 0x4e, 0x96, 0xac, 0x3b, 0xb3, 0x09, 0xff, 0x3a, 0xbd, 0xf8, 0xd6, 0xeb, 0x4f, 0x32, 0xaf, 0xdc, 0xf6, 0x80, 0x23, 0x5a, 0x14, 0x75, 0xcc, 0xeb, 0x6b, 0xdb, 0x32, 0x27, 0xe2, 0x88, 0xc6, 0xd0, 0x57, 0x34, 0xa6, 0xf4, 0x83, 0x90, 0x9e, 0x1f, 0xa2, 0x3c, 0x70, 0x71, 0x4c, 0x67, 0x08, 0xbb, 0x89, 0x89, 0x31, 0xc5, 0x45, 0xea, 0xb9, 0xb4, 0xbd, 0x37, 0x1a, 0x5e, 0xe2, 0x92, 0x21, 0xc5, 0x08, 0x1f, 0x71, 0x9f, 0xdb, 0x45, 0x1b, 0x25, 0x39, 0x70, 0x48, 0x8f, 0xa1, 0x21, 0x2d, 0x7c, 0x1d, 0xfc, 0x1b, 0x14, 0x80, 0x7c, 0x70, 0xfe, 0xf5, 0x22, 0x34, 0xa4, 0xcc, 0x6c, 0x43, 0xa4, 0x3a, 0xbc, 0xb7, 0x6b, 0x68, 0xa8, 0x6b, 0x6f, 0xb8, 0x3a, 0x62, 0x10, 0xc7, 0x44, 0x2f, 0xd5, 0x41, 0x19, 0x2d, 0x23, 0xe2, 0x4f, 0xc9, 0x00, 0x68, 0xa2, 0xc1, 0x77, 0x16, 0xd0, 0x9c, 0x28, 0xa0, 0x59, 0x24, 0xa0, 0xe1, 0x7f, 0x92, 0xf3, 0x48, 0x22, 0x7e, 0xfe, 0xd5, 0x4e, 0x76, 0x9f, 0x28, 0x94, 0x8f, 0x8e, 0x08, 0x98, 0xe9, 0x74, 0x2f, 0xf8, 0x03, 0x1b, 0x83, 0x3c, 0x22, 0x97, 0xa8, 0x49, 0x54, 0x29, 0xe0, 0x33, 0xcc, 0x50, 0x36, 0xa1, 0x58, 0x56, 0x96, 0x81, 0x77, 0xa3, 0x6a, 0x61, 0xac, 0x84, 0x64, 0x51, 0x5c, 0x32, 0x01, 0x59, 0x07, 0x92, 0x53, 0x14, 0xee, 0x2f, 0xd5, 0x87, 0xbc, 0x25, 0x54, 0x87, 0xd7, 0x63, 0x08, 0xe8, 0x7d, 0x81, 0x4c, 0x54, 0x35, 0x0a, 0x88, 0xae, 0x90, 0x78, 0x31, 0x25, 0x40, 0xa3, 0x71, 0xa9, 0x02, 0xd0, 0x01, 0x5c, 0xff, 0x39, 0x56, 0x94, 0x4a, 0x58, 0x1f, 0xc9, 0x2e, 0xf7, 0xaa, 0xec, 0x56, 0x7b, 0xb6, 0xf1, 0xab, 0x59, 0xeb, 0xa1, 0x06, 0x65, 0x2a, 0xe1, 0xb4, 0xac, 0xdc, 0x93, 0xc8, 0xa9, 0x69, 0xb1, 0x54, 0x8d, 0xb6, 0x37, 0x58, 0xdc, 0x16, 0x83, 0x3a, 0x43, 0xc3, 0x30, 0x0e, 0x52, 0xc1, 0x19, 0x94, 0x16, 0xbd, 0xde, 0x00, 0x34, 0x33, 0xad, 0x79, 0xb5, 0x41, 0xfe, 0xf7, 0x72, 0xce, 0x6a, 0x77, 0x55, 0x57, 0x9b, 0x0b, 0xb3, 0x6c, 0xa4, 0x61, 0x96, 0x42, 0xa5, 0xc8, 0x90, 0xc1, 0x76, 0xce, 0x81, 0x7d, 0xe1, 0xd9, 0x42, 0x82, 0x25, 0xc2, 0xc4, 0xf6, 0x27, 0x35, 0x40, 0x48, 0xbd, 0x71, 0xe0, 0x3a, 0x69, 0x14, 0x4b, 0xb2, 0x14, 0xb9, 0x26, 0xd5, 0x0b, 0x14, 0x73, 0x4c, 0x2f, 0x42, 0xd5, 0xec, 0x41, 0xaf, 0x04, 0x19, 0xd7, 0x50, 0x2d, 0xe1, 0xac, 0xa9, 0x97, 0x49, 0x50, 0x51, 0x67, 0x96, 0x02, 0x28, 0xc4, 0x95, 0xa6, 0xb1, 0x26, 0x82, 0x7c, 0x09, 0x24, 0x0d, 0x47, 0xd9, 0x24, 0x91, 0xc0, 0x2d, 0x43, 0x58, 0x12, 0x42, 0xc9, 0xfb, 0x50, 0xf1, 0x96, 0xc1, 0xf7, 0xb2, 0x1e, 0x19, 0xb2, 0xbb, 0x60, 0x9e, 0xa8, 0x06, 0xc9, 0x5a, 0x74, 0x4e, 0x30, 0x19, 0xfa, 0x00, 0x6d, 0x7e, 0xc0, 0xf5, 0x91, 0x9c, 0xec, 0x7c, 0x29, 0x29, 0xa9, 0xcc, 0x37, 0xfb, 0x6d, 0x46, 0x85, 0x4a, 0xea, 0xd3, 0xe5, 0x16, 0x14, 0x99, 0xf2, 0xe6, 0x56, 0xf9, 0x9c, 0x65, 0x73, 0x8a, 0xa2, 0xcd, 0x71, 0x87, 0x82, 0x63, 0x76, 0xf8, 0x4a, 0xf3, 0xa2, 0x2d, 0x55, 0xa5, 0x4a, 0xb5, 0x52, 0x99, 0x63, 0x72, 0x70, 0x12, 0x67, 0xd5, 0x82, 0xca, 0x9c, 0x39, 0x55, 0x3e, 0x5b, 0xbc, 0xbd, 0xc0, 0xae, 0x15, 0x74, 0x8b, 0x25, 0xf4, 0x00, 0x71, 0x91, 0xfd, 0x1a, 0x2e, 0x11, 0x0e, 0x45, 0x59, 0x22, 0xdc, 0x14, 0x8c, 0x6b, 0x85, 0x71, 0x6c, 0x68, 0x20, 0x58, 0x37, 0x40, 0x6a, 0x76, 0x10, 0xc8, 0x6c, 0x29, 0x45, 0x42, 0x13, 0xea, 0x50, 0xfa, 0x88, 0x16, 0x55, 0xc4, 0x14, 0x18, 0xb7, 0x60, 0x1d, 0xfa, 0x68, 0xc7, 0xb5, 0x4a, 0xbd, 0x3d, 0xd3, 0x19, 0x8d, 0x3a, 0x5d, 0x39, 0xac, 0x8e, 0x6f, 0x05, 0x4f, 0xdc, 0x9a, 0x69, 0xd3, 0x2b, 0xa3, 0x2e, 0x57, 0x34, 0x4a, 0x08, 0x74, 0x26, 0xdd, 0x90, 0xce, 0x5a, 0xc2, 0x2e, 0xbc, 0x4f, 0xdc, 0x49, 0x33, 0x38, 0x41, 0x17, 0x6e, 0xfe, 0x52, 0x7b, 0x69, 0x84, 0x1d, 0x61, 0x35, 0xeb, 0xec, 0x7a, 0xfb, 0x04, 0xca, 0x3e, 0x98, 0x84, 0xf0, 0x56, 0x45, 0x6a, 0x45, 0x21, 0x02, 0xaf, 0x2d, 0x2f, 0x9a, 0x87, 0xc4, 0xf4, 0xbc, 0xa2, 0xa2, 0x79, 0x95, 0x1e, 0x4f, 0xe5, 0xbc, 0xa2, 0x58, 0x7e, 0x7e, 0x6c, 0x19, 0x5b, 0xe8, 0xae, 0xec, 0x89, 0x17, 0xf7, 0x54, 0xb9, 0xdd, 0x55, 0x3d, 0xc5, 0xf1, 0x9e, 0x4a, 0x37, 0x7f, 0x75, 0x23, 0xfc, 0x73, 0xf8, 0x30, 0xec, 0xcd, 0x08, 0x1c, 0xf3, 0x71, 0x36, 0x1b, 0xee, 0xa3, 0x30, 0xe2, 0x3f, 0x45, 0xe2, 0xaa, 0xee, 0xcd, 0x0c, 0x0d, 0x35, 0x7d, 0x82, 0x9c, 0x37, 0xa1, 0xc1, 0x89, 0xbb, 0x51, 0x21, 0x65, 0xd8, 0xab, 0x0f, 0x70, 0x2c, 0x52, 0x16, 0xb8, 0xf4, 0x02, 0xe5, 0x13, 0x11, 0x87, 0x60, 0xbc, 0x4d, 0xa1, 0x62, 0x25, 0xd6, 0x9a, 0x9c, 0x40, 0x69, 0x48, 0xb7, 0xc3, 0xe3, 0xb5, 0x44, 0x75, 0x6c, 0x36, 0x5f, 0xa4, 0x94, 0x3b, 0xad, 0x6e, 0x4b, 0x4e, 0x4d, 0x00, 0xf8, 0xf8, 0xdf, 0xf9, 0x43, 0x06, 0x1d, 0x40, 0xb6, 0x37, 0x34, 0xef, 0xde, 0x87, 0xf4, 0x08, 0x60, 0x3c, 0x63, 0x00, 0x18, 0x03, 0x9c, 0x7a, 0xe9, 0x3e, 0x1d, 0x34, 0x10, 0xab, 0xe0, 0x9c, 0x02, 0x04, 0x03, 0x2e, 0x99, 0x49, 0x21, 0x9f, 0xd7, 0xe7, 0x15, 0x18, 0x58, 0xaa, 0xb0, 0x61, 0xca, 0x9f, 0x38, 0xd5, 0x91, 0x07, 0x7e, 0xcc, 0x28, 0xe4, 0x32, 0x4e, 0x6f, 0x33, 0x14, 0x17, 0x95, 0xe4, 0x99, 0x1d, 0x7a, 0x4e, 0xa1, 0x96, 0xfa, 0x42, 0x4b, 0xf2, 0xb2, 0x5a, 0x4b, 0x3d, 0xf6, 0xb2, 0xb9, 0x25, 0xb1, 0x0e, 0x17, 0x73, 0x4e, 0x22, 0x95, 0x67, 0xc8, 0x5b, 0x6a, 0x1b, 0x66, 0x2a, 0xd5, 0x19, 0xca, 0x9c, 0x5c, 0xa7, 0xab, 0xb2, 0xa7, 0xb4, 0xa0, 0x2b, 0xe1, 0xf3, 0x08, 0x18, 0x17, 0x68, 0xfc, 0x48, 0xf6, 0x2b, 0xb8, 0xe6, 0xbd, 0x44, 0x22, 0x51, 0xe9, 0x81, 0xec, 0x59, 0x81, 0x34, 0x6d, 0x0d, 0x1c, 0x32, 0xaa, 0x19, 0xea, 0x01, 0x34, 0xc9, 0xcc, 0x43, 0x8c, 0x16, 0xa0, 0x32, 0xda, 0x68, 0x34, 0xbb, 0x71, 0xca, 0x33, 0xce, 0x44, 0xee, 0x50, 0x2a, 0x95, 0x5e, 0xa5, 0x37, 0x14, 0xd0, 0xf9, 0xdc, 0x78, 0xa7, 0x12, 0x98, 0x08, 0x4c, 0xd4, 0x72, 0x53, 0xea, 0xbd, 0xe3, 0xb8, 0x44, 0x70, 0xc0, 0x9d, 0x9b, 0x5d, 0x1c, 0x0d, 0xb8, 0xb2, 0x75, 0x3b, 0x5a, 0x95, 0x2a, 0x56, 0x66, 0x6b, 0xcc, 0xf3, 0x55, 0x45, 0xcd, 0xb6, 0xa2, 0x99, 0xd9, 0x81, 0x42, 0x3d, 0xf3, 0xb8, 0xa7, 0xbe, 0xad, 0xa5, 0xc2, 0x6c, 0x24, 0x95, 0x63, 0x23, 0x4a, 0x79, 0xd0, 0x11, 0x70, 0x55, 0x74, 0x97, 0x14, 0xf5, 0x24, 0xfc, 0x06, 0xed, 0x47, 0x98, 0x0f, 0xae, 0xa1, 0x07, 0x48, 0x29, 0x6c, 0xaf, 0x1e, 0x21, 0xf4, 0x23, 0x8f, 0xa2, 0x50, 0x9b, 0x02, 0x5b, 0xcb, 0x90, 0x48, 0x42, 0x9b, 0x84, 0x41, 0x8c, 0x3b, 0x06, 0x55, 0x1a, 0x04, 0x3c, 0xa6, 0xf5, 0x0a, 0x7e, 0x5a, 0xad, 0x98, 0x3d, 0x86, 0x92, 0xe2, 0x53, 0xee, 0xe6, 0x7f, 0x58, 0x2a, 0xb2, 0x02, 0x65, 0x70, 0x54, 0xbd, 0x5e, 0x0b, 0x6c, 0xd1, 0x0e, 0x96, 0x30, 0xda, 0x2c, 0xb9, 0x89, 0x00, 0xff, 0x3b, 0xe0, 0xf3, 0x87, 0xf4, 0x5a, 0xfe, 0x29, 0xf2, 0x56, 0x41, 0x26, 0xae, 0x86, 0xef, 0x45, 0xfc, 0xc4, 0x87, 0xb0, 0xce, 0x59, 0x94, 0x06, 0x65, 0xc7, 0x20, 0x17, 0x12, 0xd8, 0x04, 0x04, 0x78, 0x06, 0xa7, 0xbc, 0x04, 0x7b, 0x0c, 0x7a, 0x51, 0x49, 0x60, 0x82, 0x44, 0x2f, 0xd6, 0x7a, 0xb4, 0x3e, 0xa1, 0x80, 0x9e, 0x50, 0xaf, 0x54, 0xc8, 0x4a, 0x40, 0x0d, 0x40, 0x64, 0x9a, 0xd4, 0x10, 0x78, 0xf6, 0xcf, 0xc6, 0xe2, 0x40, 0xb4, 0x44, 0x65, 0x9c, 0x13, 0x6d, 0x99, 0xbd, 0xb3, 0xde, 0x91, 0xb5, 0xc3, 0xed, 0x30, 0x06, 0xb9, 0x1d, 0x6d, 0x0a, 0x35, 0xcb, 0x7c, 0x6d, 0x76, 0x45, 0xfd, 0xd9, 0x81, 0xbe, 0xb9, 0xa8, 0x61, 0x6d, 0x41, 0xdc, 0xbe, 0x90, 0x5f, 0xcb, 0xf1, 0x8f, 0x83, 0x57, 0x95, 0x72, 0xd4, 0xbe, 0x15, 0xf4, 0x00, 0xb8, 0x1a, 0xf2, 0x6e, 0x3d, 0xca, 0x93, 0x87, 0x0a, 0x38, 0xa1, 0xc2, 0x9c, 0x9b, 0x46, 0xfa, 0x31, 0xde, 0xef, 0xe2, 0x95, 0xd9, 0x8b, 0x56, 0x7a, 0x1b, 0xd4, 0x6b, 0xbc, 0x13, 0x34, 0xc1, 0xe5, 0x19, 0xd3, 0x1b, 0xf2, 0x07, 0x43, 0x49, 0xc0, 0xa9, 0x93, 0xd2, 0x99, 0x12, 0xab, 0xc2, 0x1d, 0x54, 0xed, 0x9c, 0xa9, 0x53, 0x51, 0xcc, 0x1f, 0x83, 0x51, 0xf5, 0xa2, 0x4c, 0xda, 0xeb, 0xe4, 0xff, 0x02, 0x5e, 0x55, 0x71, 0x98, 0x26, 0x09, 0x38, 0xd7, 0x77, 0xb1, 0x4f, 0x10, 0x4e, 0x84, 0xf4, 0xa3, 0x10, 0xea, 0x45, 0x60, 0x8d, 0x8a, 0x82, 0x1c, 0x86, 0xa1, 0xc0, 0x62, 0xc4, 0x3a, 0x71, 0xe2, 0x06, 0xd1, 0x0f, 0xbf, 0x10, 0xb3, 0xe0, 0x4e, 0x2d, 0xa8, 0x15, 0x8a, 0x83, 0xb0, 0xd8, 0xd8, 0x83, 0xff, 0xc1, 0x9e, 0x38, 0x7d, 0xa1, 0xde, 0xab, 0xd7, 0x89, 0xe6, 0x3e, 0x23, 0xd8, 0x15, 0x6b, 0xa8, 0xae, 0x8d, 0xc8, 0x55, 0xcd, 0x26, 0xd6, 0x90, 0xd8, 0xb4, 0xc9, 0x2f, 0xcb, 0x90, 0xcb, 0x58, 0x56, 0x49, 0xd1, 0xfb, 0xb2, 0x66, 0x54, 0x7e, 0xa6, 0x52, 0x16, 0xaa, 0x36, 0xe8, 0xaf, 0x99, 0xff, 0x97, 0x20, 0xc3, 0x30, 0x2c, 0xc6, 0x99, 0xad, 0x84, 0xe3, 0xa3, 0x87, 0x6b, 0x9f, 0x43, 0x36, 0xcc, 0x09, 0xdf, 0xa3, 0x08, 0x4c, 0x81, 0xf1, 0xee, 0x33, 0x39, 0x21, 0x4d, 0x9e, 0xf2, 0x52, 0x49, 0x67, 0x1c, 0x46, 0xc2, 0x2d, 0x24, 0xf5, 0x4f, 0xac, 0x79, 0x62, 0x44, 0xa5, 0x25, 0x5b, 0xa5, 0x0a, 0x46, 0x96, 0xd9, 0x4e, 0x6a, 0x95, 0x68, 0x81, 0x43, 0xd2, 0x2a, 0xde, 0x75, 0x57, 0x39, 0xcb, 0xeb, 0xde, 0x95, 0x67, 0x60, 0x2c, 0x5b, 0xf8, 0x0e, 0x1b, 0x7c, 0x87, 0x15, 0x61, 0xdd, 0x59, 0xb5, 0x19, 0x29, 0x9f, 0x23, 0x4a, 0x60, 0x40, 0xae, 0x47, 0xa2, 0x0f, 0xa7, 0x00, 0x77, 0x84, 0x35, 0x69, 0xb9, 0xa4, 0x40, 0xcc, 0xec, 0x2b, 0x0e, 0x0a, 0x31, 0x22, 0x42, 0x7f, 0x25, 0xa4, 0x6d, 0xb3, 0x2a, 0x93, 0xe9, 0xa5, 0xa5, 0xad, 0x4c, 0xa6, 0x72, 0xf5, 0xed, 0xc7, 0xb7, 0xa8, 0x94, 0x4c, 0x2f, 0xc5, 0x32, 0xf5, 0x32, 0xed, 0xda, 0x63, 0xf4, 0x80, 0x4a, 0xf6, 0x3d, 0x4e, 0xb5, 0x45, 0xa6, 0xfa, 0xe7, 0xfb, 0x12, 0xd9, 0x4d, 0x32, 0x8d, 0x6c, 0xa5, 0xe6, 0x1d, 0x1c, 0x9f, 0x9c, 0x03, 0x37, 0xc4, 0x9f, 0xb2, 0xf9, 0x22, 0x2e, 0x5d, 0x34, 0x91, 0x85, 0x22, 0x86, 0x60, 0x4f, 0x91, 0x59, 0x21, 0xc9, 0xe6, 0xa7, 0xba, 0x00, 0x42, 0xf0, 0x0f, 0xb6, 0x61, 0x16, 0x03, 0x89, 0x7f, 0xaa, 0x3f, 0x68, 0xca, 0xef, 0x53, 0x83, 0x60, 0xdf, 0x10, 0xff, 0x05, 0x4e, 0x5b, 0xc8, 0x42, 0x99, 0x0a, 0x59, 0x38, 0x6d, 0x01, 0xbc, 0x85, 0x72, 0x16, 0x92, 0xb9, 0x0b, 0xe8, 0x93, 0xb9, 0x77, 0x88, 0xdf, 0x39, 0xd4, 0x65, 0x8a, 0xb8, 0x34, 0x1a, 0x57, 0x24, 0x99, 0xce, 0x00, 0x82, 0xa6, 0x88, 0x53, 0xab, 0x75, 0x46, 0x26, 0xf2, 0x1b, 0xa6, 0x69, 0x33, 0x32, 0xbd, 0x60, 0xef, 0x1d, 0xc2, 0x79, 0x4f, 0xc2, 0xea, 0x4d, 0xc6, 0xf1, 0x48, 0xb5, 0x39, 0x08, 0x8c, 0xd4, 0x14, 0xdb, 0xeb, 0x54, 0x70, 0xbd, 0x0b, 0x43, 0x60, 0xdf, 0xe0, 0xd6, 0xa9, 0x4d, 0xe6, 0xb3, 0xa6, 0x36, 0x99, 0xf6, 0x0c, 0xa1, 0xce, 0xfd, 0x46, 0x6c, 0x9a, 0x59, 0x6c, 0x3b, 0xe8, 0x99, 0x38, 0x80, 0xdb, 0x8e, 0x7c, 0xbb, 0x9b, 0x79, 0x25, 0x71, 0xf3, 0xf8, 0x9b, 0x44, 0x2d, 0x61, 0x4c, 0xe8, 0xaa, 0x9c, 0x24, 0x46, 0x1a, 0xc1, 0xd8, 0x07, 0xa3, 0x65, 0x25, 0x3e, 0xb4, 0xb7, 0x93, 0x24, 0xc1, 0x8b, 0x84, 0x78, 0xf5, 0xa4, 0xad, 0x08, 0x5b, 0x2d, 0x91, 0x54, 0x17, 0x70, 0x2d, 0x63, 0xa2, 0x39, 0x49, 0xcc, 0x4e, 0x0a, 0xe6, 0x50, 0x87, 0x2c, 0x70, 0x53, 0xa4, 0xd4, 0xe8, 0xcc, 0x5a, 0x4f, 0x56, 0x56, 0x41, 0x96, 0x2b, 0xe8, 0x76, 0xf9, 0x5d, 0x3a, 0x0e, 0x52, 0x24, 0x50, 0x60, 0xf7, 0xd8, 0x43, 0xb9, 0x21, 0xf8, 0x3f, 0xd4, 0x21, 0x94, 0x32, 0x85, 0x22, 0x33, 0xe0, 0x36, 0x76, 0xb3, 0xea, 0x0c, 0xb9, 0x44, 0xa9, 0x92, 0x28, 0x7c, 0x36, 0x9b, 0x07, 0xee, 0x39, 0x58, 0x39, 0xe7, 0xb0, 0x5a, 0x6d, 0x9a, 0xcc, 0x0c, 0x45, 0x26, 0x83, 0x22, 0x9d, 0x58, 0xaf, 0x55, 0x6f, 0xc9, 0x94, 0xb1, 0x12, 0x85, 0xc6, 0x69, 0xb5, 0xba, 0x0d, 0x6a, 0xa9, 0x44, 0xc6, 0x30, 0x70, 0x0d, 0x66, 0x68, 0x0d, 0x82, 0xff, 0x6b, 0x3b, 0xf9, 0x20, 0xf3, 0x14, 0x61, 0x20, 0xca, 0x05, 0x5d, 0x5b, 0x33, 0x51, 0x7e, 0x79, 0xb8, 0x45, 0x48, 0xc2, 0x46, 0x09, 0xf4, 0x49, 0xf7, 0xdd, 0xc4, 0x49, 0xb8, 0x19, 0x3a, 0xab, 0xf3, 0xe9, 0x44, 0x18, 0xed, 0x34, 0xb7, 0xa2, 0x90, 0x51, 0x05, 0x80, 0xd6, 0x19, 0xd0, 0xfa, 0x6b, 0xf2, 0x6d, 0xd7, 0xe4, 0x76, 0x6f, 0x6f, 0x8b, 0xcd, 0x2d, 0x77, 0x5d, 0xc3, 0xac, 0xf7, 0x45, 0x4d, 0x32, 0x53, 0x6e, 0x5d, 0x76, 0xcd, 0xfa, 0xae, 0x02, 0x6b, 0xf9, 0xc2, 0x7a, 0xc1, 0x8e, 0x40, 0x6f, 0x23, 0x35, 0xa2, 0xed, 0xe5, 0x52, 0x3b, 0x02, 0xb3, 0x10, 0xd9, 0x11, 0x3a, 0x2f, 0xb1, 0x0f, 0xc8, 0x24, 0x69, 0xf6, 0x01, 0x06, 0xdd, 0x50, 0x2c, 0xd8, 0x07, 0xa8, 0x8c, 0x0f, 0x90, 0x65, 0x40, 0xb0, 0x0f, 0xd4, 0x7c, 0xf8, 0x21, 0x3c, 0x24, 0xc4, 0xef, 0xc3, 0xc1, 0x63, 0x50, 0x2d, 0x0f, 0x39, 0xd4, 0x48, 0xca, 0x88, 0x2b, 0x13, 0xf2, 0x02, 0xb8, 0x3e, 0x1c, 0x58, 0x46, 0x08, 0xb1, 0x4e, 0xee, 0xa4, 0xd1, 0x80, 0x95, 0xc2, 0x85, 0x23, 0xa4, 0x28, 0x0d, 0x62, 0x2c, 0x26, 0x1c, 0x65, 0x81, 0x23, 0xf6, 0x83, 0x28, 0xb3, 0x95, 0x62, 0x97, 0x25, 0x2f, 0x95, 0x42, 0x3d, 0x97, 0xee, 0xbf, 0xf4, 0xca, 0xde, 0x84, 0x5d, 0xaf, 0x53, 0x40, 0x6e, 0x13, 0x0e, 0x06, 0x7c, 0xba, 0x32, 0x7d, 0xa9, 0x42, 0xab, 0xd0, 0xa8, 0x94, 0x32, 0x09, 0x21, 0x07, 0x72, 0x79, 0x1a, 0xaa, 0xad, 0xdb, 0x4c, 0x16, 0x4e, 0xb6, 0x45, 0x48, 0x68, 0x1c, 0x09, 0xe5, 0xe6, 0x04, 0x89, 0x69, 0x34, 0xb0, 0x82, 0x4b, 0x0e, 0xce, 0x29, 0xea, 0x83, 0xce, 0xf5, 0x33, 0x3d, 0xc7, 0x6e, 0xe7, 0xdf, 0xf8, 0xfc, 0x8e, 0x5b, 0x6e, 0xba, 0xed, 0xea, 0x9f, 0xed, 0x28, 0x77, 0xd5, 0x0e, 0xd5, 0x83, 0xd2, 0xfa, 0xbd, 0x3f, 0xdd, 0x3a, 0x76, 0xd1, 0xbc, 0x73, 0x35, 0x1c, 0x5d, 0xb9, 0x73, 0x1b, 0xa7, 0x2d, 0x1e, 0xbd, 0x7d, 0xf8, 0xc1, 0x40, 0x6d, 0x5f, 0x7c, 0xc5, 0x76, 0xde, 0x4d, 0xb7, 0xf2, 0x59, 0x57, 0xae, 0xdc, 0xb6, 0xf6, 0xfe, 0xa2, 0x25, 0xd7, 0xf6, 0x14, 0x0d, 0xcc, 0xa9, 0x35, 0x54, 0xf1, 0x5f, 0xf4, 0x7f, 0x6f, 0x55, 0x05, 0x3d, 0xbe, 0xb8, 0xc7, 0x92, 0xaf, 0xd2, 0x07, 0xa4, 0xb6, 0xbc, 0xae, 0xa2, 0xf6, 0x9d, 0xbd, 0x79, 0xb0, 0x5f, 0xf9, 0xb0, 0x17, 0x2e, 0x48, 0xa7, 0x4c, 0xa8, 0xb7, 0x95, 0x26, 0xe2, 0x19, 0x4a, 0x9c, 0x17, 0x8f, 0xb2, 0x53, 0x50, 0xb6, 0x06, 0x4e, 0x36, 0x6f, 0x61, 0x91, 0x7a, 0xdf, 0x2f, 0xa0, 0x22, 0x6b, 0x38, 0x94, 0xe3, 0xc9, 0xd9, 0x35, 0x76, 0x35, 0x52, 0x6f, 0x33, 0x41, 0xa6, 0x24, 0x5d, 0x7f, 0x73, 0x73, 0x41, 0x84, 0x14, 0x9d, 0x8c, 0xee, 0x77, 0x73, 0x94, 0xec, 0xea, 0x97, 0x0e, 0xd4, 0xd6, 0x5d, 0xf5, 0xc2, 0x95, 0x63, 0x5f, 0xbc, 0x07, 0x0c, 0xb7, 0x83, 0xfc, 0xee, 0x2d, 0x2d, 0x6e, 0x5f, 0xdb, 0xe6, 0x39, 0xfc, 0xaf, 0xa8, 0xe3, 0x33, 0x77, 0xde, 0xdb, 0xb7, 0xe4, 0xa1, 0x1d, 0x33, 0xa8, 0x3b, 0x6f, 0x04, 0x2b, 0xc6, 0xbe, 0x1a, 0xd3, 0xc5, 0xba, 0x96, 0xc7, 0x63, 0xc3, 0xed, 0x79, 0x63, 0xcf, 0x42, 0x8a, 0xe7, 0x8e, 0x7f, 0x4c, 0xff, 0x99, 0x39, 0x2d, 0xc4, 0x40, 0xa1, 0x98, 0x98, 0x08, 0x48, 0x2f, 0x6f, 0x86, 0x22, 0xfb, 0xe0, 0xc0, 0xac, 0x12, 0xd5, 0x26, 0xa1, 0x68, 0x26, 0x1e, 0x92, 0x95, 0x4c, 0x2b, 0x1a, 0x0b, 0xa7, 0xdd, 0x68, 0xd0, 0x72, 0x70, 0x14, 0x50, 0x0c, 0x94, 0x98, 0xe6, 0x99, 0x86, 0xcf, 0x9d, 0x9e, 0x24, 0x48, 0x1b, 0x34, 0x22, 0x98, 0x16, 0xc2, 0x6c, 0xbe, 0x5b, 0xae, 0x91, 0xfa, 0x9a, 0xe7, 0x0c, 0x56, 0x2d, 0x7d, 0x74, 0x6f, 0xf3, 0x8c, 0x3d, 0x8f, 0xaf, 0x98, 0xbf, 0x7d, 0x76, 0xa1, 0x96, 0xd3, 0x4b, 0x1a, 0x7b, 0x3e, 0xba, 0xed, 0x04, 0xa0, 0x9e, 0x1e, 0x59, 0xfc, 0xc4, 0xd8, 0x89, 0x1f, 0x5e, 0xec, 0x99, 0x9b, 0x01, 0x8a, 0x64, 0x12, 0x77, 0xae, 0x43, 0xdd, 0x7c, 0xe4, 0xd5, 0x7d, 0x7b, 0x5e, 0xbb, 0xae, 0x25, 0x90, 0x1b, 0x50, 0x29, 0x06, 0xe7, 0x0c, 0xad, 0x3c, 0xc7, 0x7f, 0x7a, 0xf7, 0xdd, 0xfc, 0xa7, 0xe7, 0x56, 0xae, 0x5b, 0xd6, 0xaf, 0x12, 0x74, 0x87, 0xfe, 0xf1, 0x8f, 0xd9, 0x72, 0xf6, 0x1d, 0xc8, 0x4b, 0x70, 0x7f, 0x28, 0xd2, 0x9f, 0x89, 0x52, 0xe1, 0xa0, 0xd2, 0xc2, 0x48, 0x18, 0x5a, 0xb2, 0x06, 0x6d, 0xaf, 0x24, 0xec, 0x2a, 0xc4, 0x0a, 0x01, 0x89, 0x6b, 0x8e, 0x24, 0xb1, 0xe6, 0x50, 0x61, 0x52, 0x21, 0x66, 0x36, 0x10, 0xf2, 0x06, 0x82, 0xe1, 0x10, 0x56, 0x27, 0xfc, 0x13, 0x15, 0x65, 0x50, 0x32, 0x97, 0xc1, 0x88, 0x6a, 0x5a, 0xa7, 0x82, 0xbb, 0x82, 0x48, 0x96, 0xdb, 0xc1, 0xf4, 0x61, 0x5e, 0x50, 0xdc, 0x33, 0x5c, 0xcf, 0xd8, 0x0f, 0x4f, 0x8c, 0x3d, 0xb1, 0x78, 0xe4, 0x69, 0x40, 0x9d, 0xe8, 0xb9, 0xa5, 0x16, 0x38, 0x1b, 0xd7, 0xcd, 0xa9, 0xe8, 0xcd, 0xb5, 0x4a, 0x03, 0xba, 0x60, 0x76, 0x4b, 0x43, 0xa3, 0x44, 0xc7, 0x69, 0x0b, 0xe7, 0x6c, 0xeb, 0x5b, 0xf1, 0xf8, 0x9e, 0x19, 0x2d, 0x7b, 0x1f, 0x1d, 0xae, 0x1a, 0x9c, 0xdb, 0xec, 0x93, 0x6a, 0xe4, 0x9a, 0x8c, 0x97, 0x97, 0xad, 0x5b, 0x79, 0x0e, 0xa8, 0xee, 0xbe, 0x1b, 0xa8, 0xce, 0xad, 0x74, 0x3b, 0xa5, 0x1a, 0x59, 0xcd, 0x96, 0xbe, 0xb8, 0x8e, 0x5b, 0xe5, 0x94, 0x33, 0x8a, 0xe5, 0x43, 0xcb, 0x07, 0x15, 0x2a, 0x48, 0x88, 0x96, 0xeb, 0x5e, 0xdb, 0xb3, 0xef, 0xd5, 0x23, 0xcd, 0x6a, 0x47, 0xae, 0x5b, 0x22, 0x53, 0x61, 0x7e, 0x02, 0xd7, 0x25, 0xfb, 0x12, 0x9c, 0x6f, 0x4a, 0x22, 0x9b, 0x18, 0x78, 0xd2, 0x07, 0x04, 0x5b, 0x1a, 0x8e, 0x3f, 0x47, 0xdb, 0x11, 0x1c, 0x21, 0x00, 0xa7, 0xdc, 0x64, 0x18, 0x69, 0x07, 0xb2, 0x17, 0x00, 0x7a, 0x19, 0xd2, 0x38, 0xfb, 0x2f, 0xbd, 0xa0, 0x37, 0xa1, 0x57, 0x65, 0xa0, 0x4d, 0x85, 0xc9, 0x90, 0x91, 0xad, 0x8a, 0xc0, 0x69, 0xa9, 0x04, 0xca, 0x49, 0x89, 0x70, 0x6e, 0x38, 0x25, 0x85, 0x21, 0x66, 0xe9, 0xe9, 0xd7, 0xda, 0x78, 0x32, 0xfb, 0x8d, 0x7f, 0x1a, 0x34, 0xde, 0x09, 0xd8, 0x67, 0x46, 0x70, 0xde, 0xdb, 0xd8, 0x87, 0x53, 0x96, 0xd9, 0xe9, 0xe6, 0xeb, 0x5e, 0xde, 0xb3, 0xfb, 0x95, 0xeb, 0x9a, 0x2f, 0x3c, 0x43, 0x7f, 0xbc, 0xec, 0x59, 0xfe, 0x8b, 0x3b, 0xef, 0xe2, 0xbf, 0x7c, 0x76, 0xd9, 0xb4, 0x6b, 0x0b, 0xd5, 0x1f, 0xca, 0xc6, 0x7d, 0xb5, 0xa3, 0xaa, 0x32, 0xa2, 0x5f, 0x68, 0x10, 0x07, 0x2c, 0x4b, 0x70, 0x6c, 0x33, 0x0b, 0x04, 0xd8, 0x20, 0x04, 0xa1, 0x93, 0x01, 0x75, 0x8b, 0x0c, 0x7b, 0x86, 0xdd, 0x66, 0xb5, 0x98, 0x32, 0x55, 0xf0, 0x26, 0x85, 0x1b, 0x25, 0x75, 0xfb, 0x39, 0x77, 0x72, 0xe8, 0x00, 0xfc, 0x2a, 0x4a, 0x68, 0x37, 0xe7, 0x8e, 0x51, 0xdd, 0xfc, 0xbf, 0x3b, 0xd7, 0xcd, 0x70, 0xbb, 0x9b, 0xd6, 0x75, 0x82, 0x3f, 0xf0, 0xae, 0x6d, 0xcf, 0xee, 0xab, 0x2f, 0xdf, 0xf9, 0xfc, 0x7e, 0xa0, 0x1c, 0x0b, 0x3f, 0x08, 0xfe, 0xe2, 0xae, 0x9a, 0x5f, 0x1a, 0x9f, 0x5f, 0xe3, 0xa3, 0x3f, 0xfb, 0x7a, 0xa8, 0x72, 0xdd, 0x5d, 0x83, 0x73, 0xaf, 0x5f, 0x56, 0x0e, 0x9e, 0xb9, 0x1f, 0xd5, 0xba, 0x41, 0xf5, 0x09, 0x31, 0x0e, 0x19, 0x8a, 0xc7, 0x13, 0x94, 0x85, 0xc5, 0x69, 0x61, 0xd4, 0x13, 0xd5, 0x24, 0x27, 0x1c, 0xcd, 0x61, 0x10, 0x73, 0x63, 0x0c, 0x4d, 0x04, 0xf6, 0xf8, 0xca, 0xc5, 0x03, 0xe4, 0xbd, 0x63, 0xbf, 0x27, 0xdd, 0x63, 0x7d, 0xd4, 0xb6, 0x33, 0xe0, 0x8d, 0xfb, 0xc1, 0xcf, 0xcf, 0xa0, 0x3d, 0xc4, 0xf8, 0x27, 0x4c, 0x1b, 0xf3, 0x18, 0xe4, 0xb9, 0x45, 0x68, 0xd7, 0x33, 0x81, 0x78, 0x27, 0xc1, 0x56, 0xc5, 0xc5, 0x13, 0x90, 0x77, 0xc4, 0x04, 0x34, 0x1e, 0x41, 0x14, 0xe4, 0x45, 0xc2, 0xa8, 0xe8, 0x1a, 0xa7, 0x86, 0x77, 0xca, 0x7d, 0x52, 0x1c, 0xa5, 0x20, 0xce, 0x54, 0x21, 0x5c, 0x14, 0x68, 0x26, 0x23, 0xdd, 0xb9, 0x31, 0x10, 0x1e, 0x8d, 0x4d, 0x06, 0xee, 0x92, 0x65, 0x47, 0xfb, 0x16, 0xdf, 0xb6, 0xa6, 0x51, 0x79, 0x61, 0xac, 0x78, 0xdf, 0x8d, 0x27, 0x3a, 0x8e, 0x5d, 0x7c, 0x6a, 0xa4, 0xff, 0x91, 0xaf, 0xee, 0xd8, 0xfb, 0xbb, 0xbb, 0xfa, 0x58, 0xb5, 0x59, 0x4b, 0xbd, 0xcf, 0xcc, 0xd8, 0xf5, 0xd8, 0xea, 0xc4, 0xd2, 0x39, 0xb5, 0x45, 0x11, 0x0d, 0x67, 0x91, 0x92, 0x4d, 0x0b, 0x6f, 0x5f, 0x55, 0x59, 0x71, 0xc5, 0xed, 0x03, 0xe4, 0xa1, 0xca, 0xae, 0x98, 0x69, 0xe8, 0x1c, 0xff, 0xa7, 0x1f, 0x1f, 0xe7, 0xf9, 0x9f, 0xae, 0xea, 0x3a, 0xf5, 0xaf, 0x5b, 0x9d, 0x61, 0x93, 0x7c, 0xff, 0x2f, 0x0e, 0xd4, 0xeb, 0x9d, 0x3e, 0xa7, 0x5e, 0xa5, 0x40, 0xeb, 0x76, 0xf1, 0xf8, 0x27, 0xf4, 0x3a, 0xe6, 0x0c, 0xd1, 0x48, 0xb4, 0x26, 0x66, 0xda, 0xa0, 0x7a, 0xab, 0x46, 0xdb, 0x21, 0xa4, 0xe3, 0x42, 0x85, 0x06, 0xf6, 0x4f, 0xc2, 0xa0, 0x75, 0x8b, 0x0c, 0x56, 0x8c, 0x04, 0x25, 0xb2, 0x8a, 0xc1, 0xaf, 0x93, 0xd6, 0x6d, 0x43, 0x1d, 0x42, 0x4b, 0xcc, 0xcb, 0x09, 0xf8, 0xdc, 0x4e, 0x8b, 0x09, 0x77, 0xd2, 0x3f, 0x61, 0x01, 0x10, 0xeb, 0x84, 0x4f, 0x13, 0x96, 0x09, 0x35, 0xe2, 0x4b, 0x97, 0x2e, 0xbd, 0x2e, 0x77, 0xe3, 0x81, 0x9b, 0x3b, 0x06, 0x9f, 0xbe, 0xbe, 0x2b, 0xd8, 0x34, 0x92, 0xa8, 0x5f, 0x54, 0x66, 0x99, 0x71, 0xf5, 0xb9, 0x1d, 0xb7, 0xbd, 0xdf, 0x91, 0xa7, 0xb2, 0x29, 0x34, 0x05, 0xb3, 0xb7, 0xf6, 0xb6, 0xae, 0x6e, 0xf2, 0x46, 0x67, 0x6f, 0x68, 0xfa, 0xfb, 0x58, 0xd7, 0xae, 0xd9, 0xe1, 0xdc, 0xf9, 0xfb, 0xe6, 0xb6, 0xae, 0x6a, 0xcb, 0x57, 0xe3, 0xd5, 0xfb, 0x7a, 0xc5, 0xdc, 0x98, 0xb9, 0x74, 0xd5, 0xdd, 0xa3, 0x95, 0xab, 0x17, 0xcc, 0x34, 0xe9, 0xaa, 0x5a, 0xbb, 0xa3, 0xad, 0xd7, 0x8e, 0x56, 0x75, 0xcf, 0x9c, 0x63, 0x52, 0xba, 0x02, 0x2e, 0x6f, 0x79, 0x47, 0xa4, 0xb0, 0x23, 0x6e, 0xbd, 0x2d, 0xda, 0xd4, 0x9f, 0x57, 0x36, 0x38, 0x33, 0xcb, 0x19, 0xc9, 0x56, 0x4a, 0xf1, 0xfa, 0x05, 0xe3, 0x6b, 0xf9, 0xe3, 0x74, 0x04, 0xce, 0x1d, 0x1b, 0x11, 0x4b, 0x14, 0xa8, 0x05, 0xab, 0x82, 0xe0, 0x49, 0x5b, 0x8c, 0x8c, 0xc5, 0x74, 0xaf, 0x50, 0x32, 0x48, 0x84, 0xfa, 0x5b, 0x83, 0xc3, 0x3a, 0x6d, 0x84, 0x4d, 0x9b, 0xe5, 0xe7, 0x38, 0xbc, 0x35, 0x44, 0x68, 0xc3, 0xdc, 0xd4, 0x62, 0x80, 0xf1, 0x18, 0x3c, 0x0c, 0xae, 0x26, 0x13, 0x63, 0xcf, 0x07, 0x3b, 0x77, 0xcc, 0x75, 0x17, 0xe7, 0x47, 0x4d, 0x36, 0xc8, 0x86, 0x34, 0xd9, 0xfa, 0x96, 0x7a, 0x6a, 0x1b, 0xfd, 0x2f, 0xde, 0x79, 0x66, 0xec, 0xcc, 0x9c, 0x6b, 0x87, 0x8a, 0x19, 0x99, 0x4a, 0xb6, 0xca, 0x21, 0x87, 0xea, 0xfe, 0xf2, 0x21, 0xe6, 0xc8, 0x19, 0xcc, 0x57, 0x7b, 0x21, 0x5f, 0xdd, 0x0e, 0xf9, 0x6a, 0x03, 0xb2, 0x67, 0x43, 0x95, 0x9a, 0x2d, 0x80, 0x93, 0x91, 0x31, 0xc1, 0xa5, 0xe5, 0x42, 0x70, 0x05, 0xcd, 0x04, 0x8b, 0x86, 0x48, 0x32, 0x98, 0xd2, 0x8b, 0x45, 0xc9, 0x8d, 0x05, 0x46, 0xb2, 0x88, 0x5d, 0x28, 0x08, 0x37, 0x52, 0xfe, 0x50, 0x08, 0x9b, 0xac, 0x10, 0xfb, 0x9c, 0x40, 0xac, 0xc4, 0x84, 0xe7, 0x04, 0x14, 0x40, 0x6e, 0x32, 0x0e, 0x29, 0xdc, 0xca, 0xa6, 0x71, 0x5c, 0x21, 0x21, 0x8f, 0x29, 0xaf, 0x54, 0xe5, 0x58, 0x2d, 0xd5, 0xdd, 0x1b, 0x3a, 0x36, 0xfd, 0xf4, 0x40, 0x53, 0xeb, 0xd5, 0xcf, 0xac, 0x2d, 0x5e, 0xd0, 0x39, 0xd3, 0xf7, 0x86, 0xce, 0x0c, 0xce, 0xc5, 0x37, 0xee, 0xbf, 0xb5, 0xf7, 0x04, 0xff, 0xd5, 0xb3, 0xcb, 0x47, 0x9e, 0x07, 0xdc, 0x43, 0xc3, 0xf7, 0x35, 0x62, 0xae, 0x3b, 0x3f, 0x37, 0xd5, 0x5d, 0xfa, 0xfc, 0x80, 0x51, 0xc9, 0xa9, 0x43, 0x1e, 0x53, 0xdb, 0x0d, 0xaf, 0xec, 0xda, 0xff, 0x5f, 0x37, 0xb4, 0x2a, 0x74, 0x0e, 0x0d, 0x88, 0x9a, 0x34, 0x5b, 0xc7, 0x7e, 0x15, 0x2c, 0x0b, 0x6a, 0x31, 0xe7, 0x3d, 0x05, 0xd4, 0x3f, 0x5b, 0xe1, 0x75, 0x05, 0x26, 0xf8, 0x2e, 0xa2, 0x88, 0x80, 0x75, 0xf3, 0x3e, 0xec, 0xdf, 0x28, 0x73, 0x07, 0x21, 0x83, 0x7a, 0x76, 0x76, 0x22, 0xac, 0xcd, 0xa0, 0xa8, 0x64, 0x50, 0xe4, 0xa0, 0x50, 0x8b, 0x6b, 0xbe, 0x20, 0xcc, 0x09, 0xc2, 0xa8, 0xcf, 0x54, 0x29, 0x15, 0xc8, 0x7b, 0x86, 0x0b, 0x83, 0x88, 0xf1, 0x91, 0xac, 0x44, 0x06, 0xdc, 0x32, 0xbc, 0x57, 0x95, 0xe1, 0xf0, 0xc8, 0x23, 0x1a, 0xd5, 0x2f, 0xa5, 0x7a, 0xf5, 0xcd, 0x32, 0x0d, 0x58, 0x0c, 0x1e, 0xbc, 0x70, 0x06, 0xfc, 0x60, 0xec, 0xc9, 0xd7, 0x8c, 0x46, 0x05, 0x78, 0x05, 0x3c, 0xa7, 0xb5, 0xbf, 0x72, 0x4a, 0xa9, 0x05, 0x2e, 0x63, 0x96, 0xee, 0x37, 0x4a, 0x05, 0x38, 0x33, 0xf6, 0x28, 0xd9, 0x4f, 0xd5, 0x64, 0x72, 0x63, 0x8b, 0xc9, 0x93, 0x2e, 0x3c, 0x3e, 0x0f, 0x8d, 0xff, 0x9b, 0x5c, 0x0e, 0xdb, 0xc3, 0x11, 0xad, 0x42, 0x60, 0x7a, 0xa6, 0x50, 0xec, 0x5a, 0xe4, 0x7d, 0xab, 0xc9, 0x24, 0x5a, 0xfd, 0xe4, 0xe3, 0x2b, 0x05, 0x8c, 0xa9, 0x29, 0x97, 0xf6, 0xf6, 0x3e, 0xe9, 0xf7, 0xe2, 0x20, 0x14, 0x2d, 0x32, 0xcd, 0x94, 0x4f, 0x60, 0xc8, 0x3f, 0xf4, 0x56, 0x7d, 0x5b, 0xaf, 0x35, 0xa7, 0xca, 0x57, 0x3f, 0xd2, 0xe8, 0x63, 0xee, 0xb8, 0x70, 0xdb, 0xec, 0xf6, 0x19, 0x16, 0xaf, 0x41, 0x96, 0x33, 0x67, 0x9d, 0xa0, 0x7b, 0x3e, 0x86, 0x71, 0xd4, 0x1e, 0x82, 0x92, 0x67, 0x75, 0xcb, 0xa3, 0x01, 0xf8, 0x3e, 0xb9, 0x02, 0x90, 0xc0, 0x8c, 0x61, 0x0a, 0xac, 0xf8, 0x07, 0x2b, 0xfc, 0xe8, 0x15, 0x4e, 0xa3, 0x6c, 0x97, 0xa4, 0x16, 0x04, 0xfa, 0x10, 0xaf, 0x46, 0xd9, 0xd7, 0x29, 0x99, 0x94, 0x3c, 0x89, 0x13, 0x78, 0x52, 0xb5, 0xdf, 0xc4, 0x0b, 0x50, 0xd4, 0x3b, 0x17, 0xcc, 0x0a, 0xf9, 0x5d, 0x38, 0xea, 0x1d, 0xa7, 0x68, 0x0a, 0xbb, 0xff, 0xd4, 0x44, 0x91, 0xa0, 0x5a, 0x92, 0x08, 0x8f, 0x10, 0x2f, 0x05, 0x28, 0x89, 0x70, 0x80, 0x68, 0x21, 0x38, 0xd0, 0x18, 0x28, 0x34, 0x02, 0x79, 0xf5, 0x99, 0x4d, 0x55, 0x9d, 0x4e, 0xad, 0xd4, 0xaf, 0x53, 0x67, 0x19, 0x4a, 0x7a, 0xec, 0x9f, 0x69, 0x73, 0xad, 0xae, 0x42, 0x23, 0xe9, 0xf8, 0xb1, 0xce, 0xae, 0xe7, 0x24, 0x7c, 0x81, 0xbe, 0x20, 0x77, 0xc1, 0x9b, 0x6f, 0x52, 0xb9, 0x1a, 0x17, 0x38, 0x3c, 0xd2, 0xa5, 0xc9, 0x58, 0xe9, 0x94, 0x51, 0x34, 0x9b, 0x17, 0xe1, 0xef, 0x74, 0x85, 0x94, 0x8a, 0xa0, 0x6b, 0x6c, 0x01, 0xab, 0xc8, 0x54, 0x90, 0xd9, 0x16, 0xf3, 0xd8, 0x46, 0x48, 0x7d, 0x40, 0x9c, 0x1a, 0xff, 0x44, 0xf2, 0x22, 0x5c, 0xbb, 0x05, 0x44, 0xf7, 0x53, 0xf9, 0x6a, 0x14, 0x30, 0x89, 0x64, 0x2f, 0x32, 0x60, 0x1b, 0xa4, 0x12, 0x86, 0x42, 0x78, 0x91, 0x2c, 0xae, 0x94, 0x8d, 0x5c, 0xad, 0x42, 0x51, 0x1d, 0x4b, 0xf2, 0x04, 0x92, 0x07, 0xf8, 0xac, 0x90, 0xd9, 0x8b, 0x93, 0xbd, 0xd0, 0xd8, 0x14, 0x10, 0x05, 0x7e, 0xbd, 0x5f, 0x1f, 0x0a, 0xb8, 0x11, 0xdc, 0x32, 0x48, 0x4b, 0xd6, 0x0d, 0xa6, 0xe5, 0xec, 0xa6, 0x12, 0xb4, 0x39, 0x11, 0xce, 0x97, 0x93, 0xbc, 0xb8, 0xaa, 0x44, 0x69, 0x74, 0xb5, 0x2f, 0x5c, 0x9d, 0x68, 0x58, 0xdf, 0x57, 0x67, 0x70, 0x2d, 0x5c, 0xb3, 0xa3, 0x72, 0xdd, 0xe9, 0xf5, 0xa5, 0x6f, 0x86, 0x5b, 0x97, 0xd7, 0xd4, 0xac, 0x9e, 0x9d, 0xf3, 0x96, 0xbb, 0x76, 0x69, 0xc3, 0xc8, 0x03, 0x85, 0xcc, 0x89, 0xaf, 0x47, 0xe2, 0x8f, 0x20, 0xd4, 0x7c, 0x7f, 0xfd, 0xe2, 0xca, 0x86, 0xa1, 0x84, 0x63, 0xc6, 0x35, 0xbf, 0xd8, 0x43, 0xdd, 0x70, 0x71, 0x4d, 0xff, 0xe1, 0x85, 0x79, 0xb9, 0x0b, 0x0f, 0x2f, 0x40, 0xdf, 0xbb, 0xae, 0x5d, 0x5c, 0x74, 0xec, 0x26, 0xea, 0x0f, 0x02, 0x8f, 0xda, 0xc8, 0xcc, 0x84, 0xfd, 0x0c, 0x10, 0x57, 0x3c, 0xa9, 0x49, 0xc3, 0x0c, 0x33, 0x42, 0xae, 0x9d, 0xea, 0x0b, 0x71, 0x49, 0xe2, 0xd6, 0xb4, 0xa7, 0xb1, 0xa7, 0x43, 0x27, 0x04, 0xa5, 0x2d, 0x4a, 0xbb, 0x00, 0xa7, 0x18, 0x41, 0x89, 0x0d, 0x5f, 0x12, 0xf0, 0x86, 0x22, 0xee, 0x94, 0xe5, 0x50, 0x28, 0x03, 0x3e, 0x11, 0x1d, 0x95, 0xe4, 0x6f, 0xc9, 0xb2, 0xe0, 0x1c, 0x33, 0x33, 0x59, 0xf6, 0xdb, 0xd3, 0xb2, 0x69, 0xd2, 0xca, 0x4f, 0x56, 0x01, 0x4f, 0x2f, 0xf3, 0xad, 0x47, 0x4c, 0x4f, 0xcb, 0xad, 0x4e, 0xb2, 0xbc, 0x47, 0x92, 0x45, 0xbf, 0x7f, 0x2d, 0xec, 0x73, 0x7a, 0xa1, 0xcc, 0xfd, 0x3b, 0xd4, 0x91, 0xe5, 0x38, 0x4a, 0x14, 0x4a, 0x5d, 0x09, 0xa0, 0x58, 0x80, 0x23, 0x7f, 0xb0, 0x5e, 0x2c, 0x05, 0x58, 0x31, 0x16, 0x39, 0x1e, 0x54, 0x8c, 0x8d, 0x06, 0x54, 0x72, 0xd7, 0xed, 0x34, 0x84, 0x8d, 0x61, 0x85, 0x5e, 0xa1, 0x17, 0xa5, 0x2e, 0xb6, 0x51, 0x0b, 0xca, 0x71, 0xb1, 0xc8, 0xfa, 0x24, 0x69, 0x3a, 0x53, 0x8a, 0xe5, 0x81, 0x98, 0xac, 0x6d, 0xf7, 0x23, 0xa3, 0xa3, 0x8f, 0x5c, 0xd9, 0x26, 0x23, 0x95, 0x1d, 0xbb, 0x1e, 0x1a, 0x59, 0xfa, 0xd0, 0x95, 0xb3, 0x94, 0xe0, 0xef, 0x13, 0x3a, 0x53, 0x0a, 0x2b, 0x80, 0xbc, 0x66, 0xed, 0xe3, 0x3b, 0x6a, 0x6a, 0x76, 0x3c, 0xbe, 0xf6, 0xae, 0x15, 0x0f, 0x6d, 0xaa, 0xaa, 0xda, 0xf4, 0xd0, 0x0a, 0x72, 0xcf, 0x15, 0x2f, 0xf0, 0x9f, 0xde, 0x39, 0x05, 0x1e, 0x00, 0x92, 0xb7, 0x9f, 0xbf, 0x47, 0x12, 0xc3, 0xb5, 0x14, 0xcb, 0x89, 0xd1, 0x84, 0x3c, 0x1f, 0x32, 0x66, 0x1b, 0x98, 0x48, 0xaf, 0x47, 0xd9, 0x39, 0x90, 0x55, 0x33, 0xb8, 0x37, 0x13, 0xc8, 0xba, 0x48, 0xc2, 0x2e, 0x63, 0xc5, 0xec, 0x9d, 0x4b, 0xcf, 0xaf, 0x16, 0xcf, 0x63, 0x54, 0xdd, 0xf2, 0xb2, 0x58, 0x61, 0x6e, 0xd4, 0xef, 0xb5, 0x98, 0x2e, 0x5f, 0xa1, 0x14, 0x88, 0x25, 0x69, 0x31, 0x54, 0xc2, 0xe4, 0x7a, 0x8d, 0x82, 0xd9, 0x0e, 0x1b, 0x0e, 0xc9, 0x4f, 0x87, 0xee, 0xd9, 0x58, 0x5d, 0xbd, 0xf1, 0x9e, 0xa1, 0x61, 0xe1, 0x93, 0xfa, 0xb5, 0x36, 0x58, 0x16, 0x8a, 0x2d, 0x8e, 0xd4, 0x34, 0x2c, 0x38, 0x7d, 0xfe, 0x96, 0x5b, 0xcf, 0x9f, 0xee, 0xef, 0x3f, 0x7d, 0xfe, 0xd6, 0xab, 0xde, 0x99, 0x5b, 0x7f, 0xf7, 0xd2, 0xce, 0x6d, 0xb3, 0xc3, 0x6f, 0xdd, 0xd3, 0x76, 0xfd, 0x6b, 0x7b, 0x76, 0xff, 0xf2, 0x86, 0xf6, 0xf6, 0x1b, 0x7e, 0xb9, 0x7b, 0xcf, 0x6b, 0xd7, 0xb7, 0x51, 0xb7, 0x87, 0x1a, 0x0a, 0xed, 0x79, 0xc1, 0x85, 0x1f, 0x1d, 0x86, 0xc4, 0x38, 0xb7, 0x12, 0x32, 0x75, 0x91, 0x18, 0x9d, 0x2d, 0x0d, 0xf3, 0x0a, 0x17, 0x1d, 0x98, 0xf3, 0xf5, 0xb0, 0xa8, 0x2f, 0x33, 0x4f, 0xe0, 0x7d, 0x6c, 0x36, 0xb1, 0x2c, 0xa5, 0x2f, 0xa3, 0x0d, 0xac, 0x15, 0x25, 0xe5, 0x50, 0x34, 0xb6, 0xb1, 0x23, 0xf6, 0x28, 0x11, 0xa0, 0x13, 0xc4, 0x74, 0x26, 0x37, 0x82, 0xd5, 0x02, 0xcc, 0xb2, 0xd4, 0x45, 0x58, 0x77, 0x4e, 0xbf, 0xa6, 0x37, 0x61, 0x84, 0xec, 0x1a, 0x69, 0xcd, 0x46, 0x83, 0x4e, 0x91, 0xad, 0x8c, 0x08, 0xdb, 0x55, 0x69, 0x0a, 0xc0, 0x39, 0x00, 0x27, 0xb4, 0x56, 0xdc, 0xd8, 0x61, 0xd6, 0x34, 0x8d, 0xda, 0x7c, 0x73, 0xe7, 0xf6, 0x39, 0x59, 0xa7, 0xee, 0x7b, 0xff, 0xfd, 0x6b, 0x5e, 0x3d, 0x50, 0xdb, 0x78, 0xe8, 0xe5, 0xfd, 0xef, 0xbf, 0x31, 0x45, 0x61, 0x26, 0x7f, 0x9c, 0xd5, 0xb6, 0xa2, 0x66, 0xdf, 0xcd, 0x63, 0x2f, 0x92, 0x7f, 0x4e, 0x6c, 0xbc, 0x6f, 0x78, 0xf8, 0xc1, 0x6d, 0x75, 0x63, 0xff, 0x9a, 0x56, 0x5f, 0x7e, 0x6c, 0x3c, 0x8b, 0xfe, 0x1f, 0xe6, 0x61, 0xd8, 0x57, 0x3f, 0xaa, 0x02, 0xed, 0x45, 0x5a, 0x05, 0x2a, 0x00, 0x48, 0xae, 0x87, 0x12, 0x1c, 0x10, 0x2c, 0x18, 0x94, 0xa4, 0x7a, 0x23, 0x26, 0x09, 0xc2, 0x7e, 0x00, 0xc2, 0x62, 0x42, 0x96, 0x16, 0x2d, 0x97, 0xa1, 0x98, 0xae, 0x0b, 0x70, 0x14, 0x27, 0x98, 0xab, 0x3e, 0x85, 0xc5, 0x6d, 0x30, 0x92, 0x4d, 0xb3, 0x77, 0xce, 0xcd, 0x3a, 0x75, 0xea, 0x17, 0xd4, 0xc0, 0x4f, 0x4b, 0x2d, 0xfc, 0x1d, 0x5c, 0x28, 0x74, 0xed, 0x9b, 0x6f, 0xfe, 0xe2, 0xda, 0x5f, 0xec, 0xab, 0xce, 0xe9, 0xdd, 0xdd, 0x19, 0xb2, 0x93, 0x1a, 0x54, 0xcc, 0x77, 0xc7, 0xae, 0xb1, 0x7f, 0x8f, 0x99, 0xb2, 0xc1, 0x67, 0x66, 0x3d, 0x7f, 0x88, 0x79, 0xf8, 0xc3, 0xd2, 0xd1, 0x63, 0xfd, 0x2d, 0x57, 0x8d, 0xb6, 0xea, 0x4c, 0x11, 0xd8, 0xa8, 0xe6, 0xf1, 0x8f, 0xa9, 0x2f, 0xe0, 0xbc, 0xad, 0x44, 0x1a, 0xbe, 0x5d, 0xa8, 0x2e, 0x0c, 0xd9, 0x04, 0x8d, 0xd1, 0x91, 0x90, 0xb7, 0x69, 0x55, 0x5a, 0x6e, 0xa7, 0xe8, 0xc5, 0x84, 0x5a, 0x6f, 0x79, 0x69, 0xbc, 0x28, 0xa4, 0xcd, 0xf5, 0x20, 0xb6, 0xa1, 0x4d, 0x55, 0xb8, 0x99, 0xb0, 0xf1, 0x1b, 0xd2, 0xfc, 0x43, 0x6c, 0x3a, 0xa6, 0xf3, 0x96, 0x96, 0x57, 0xb6, 0xae, 0x7c, 0x60, 0x7d, 0x45, 0xf9, 0xc0, 0xf6, 0xdd, 0xdb, 0x07, 0xca, 0xaf, 0xdf, 0xd1, 0xb9, 0xb6, 0xb3, 0x58, 0x9f, 0x69, 0x96, 0x55, 0x86, 0x87, 0x36, 0xec, 0x6d, 0xd8, 0xf0, 0xc4, 0x8e, 0x9a, 0xf2, 0x81, 0x1d, 0xbb, 0x77, 0x0c, 0x94, 0x3b, 0x2b, 0xfb, 0x2a, 0xea, 0x06, 0x1a, 0x72, 0x34, 0x9c, 0x41, 0xce, 0x18, 0x9b, 0xda, 0x6a, 0xae, 0x38, 0xd2, 0xb6, 0x68, 0xef, 0xfc, 0xf2, 0xdc, 0xec, 0x8a, 0x79, 0x89, 0x95, 0x1b, 0xb2, 0x63, 0xd9, 0x99, 0xca, 0xa5, 0x91, 0x86, 0x7c, 0x6b, 0xd5, 0x9a, 0x13, 0x0b, 0x7b, 0x76, 0xf4, 0x54, 0xe6, 0xe7, 0x54, 0x74, 0x96, 0x67, 0xb7, 0x97, 0xba, 0xdd, 0x61, 0x77, 0x26, 0xf2, 0x61, 0x66, 0xc3, 0xb9, 0xe7, 0x81, 0x73, 0xcf, 0x82, 0x62, 0x2c, 0x38, 0x14, 0x53, 0xd6, 0x4c, 0x26, 0x97, 0x16, 0x96, 0xc9, 0x50, 0x85, 0xb0, 0x98, 0xdc, 0x28, 0xaa, 0xcd, 0x28, 0xd4, 0xf3, 0x10, 0xe1, 0xa9, 0xd3, 0xa7, 0x88, 0x9b, 0xf1, 0xf0, 0x25, 0x6f, 0xfe, 0xa8, 0xb6, 0xaf, 0xd8, 0x2c, 0x17, 0xa6, 0x85, 0xae, 0x78, 0xc5, 0xed, 0x43, 0xd4, 0x3c, 0xfa, 0x7f, 0x2f, 0x18, 0xe8, 0xff, 0xd5, 0x87, 0xab, 0xb3, 0x4c, 0x79, 0x93, 0x26, 0x02, 0x96, 0xd5, 0x0e, 0x7e, 0x23, 0x7d, 0x15, 0x7c, 0xb7, 0x89, 0xb8, 0xee, 0x49, 0x21, 0x14, 0x35, 0xc9, 0xc3, 0x91, 0x0d, 0x46, 0xd0, 0x32, 0x01, 0x98, 0x88, 0x15, 0x4d, 0xf1, 0xf0, 0x69, 0x4e, 0x2f, 0xc7, 0xa7, 0x11, 0x64, 0x83, 0x0e, 0x1f, 0x23, 0xc8, 0x45, 0x13, 0x97, 0x51, 0x18, 0x68, 0x0f, 0x81, 0x6d, 0x81, 0x94, 0xc9, 0x2b, 0xfd, 0x2c, 0xae, 0xfb, 0xc5, 0x71, 0x7a, 0xbf, 0x1b, 0xfb, 0x63, 0x70, 0x2c, 0x6a, 0x3c, 0x2d, 0x14, 0x35, 0x86, 0x76, 0x44, 0x7a, 0x37, 0xf9, 0x4c, 0xf3, 0x6c, 0x4f, 0x54, 0xa5, 0xb0, 0x48, 0xf4, 0xd1, 0x79, 0x85, 0x0b, 0xf6, 0xcd, 0x09, 0xf0, 0x71, 0x7a, 0x29, 0x5f, 0x92, 0xbd, 0xe8, 0x0a, 0x95, 0xda, 0xd4, 0xa5, 0xe5, 0xe2, 0x4b, 0xae, 0x99, 0xc3, 0x10, 0x3f, 0xfc, 0x21, 0xca, 0x87, 0x1a, 0xff, 0x98, 0xf9, 0x18, 0xf6, 0xad, 0x1c, 0xe5, 0xfb, 0x14, 0xc8, 0xa1, 0xb4, 0x0d, 0xe9, 0x50, 0x6c, 0x51, 0xb3, 0xb0, 0x90, 0x53, 0x18, 0x28, 0x42, 0xde, 0x1d, 0xdc, 0x3c, 0x00, 0x22, 0x2f, 0x27, 0x12, 0x76, 0xda, 0xb5, 0x9c, 0x4a, 0x49, 0x94, 0x83, 0x72, 0xb4, 0xb7, 0x65, 0x92, 0x5b, 0x07, 0x0c, 0x29, 0x5c, 0x14, 0x2f, 0x74, 0xeb, 0x91, 0xa3, 0x75, 0x02, 0x5a, 0x21, 0x88, 0x70, 0x86, 0xed, 0x00, 0xa3, 0xce, 0x52, 0x68, 0xca, 0x53, 0x59, 0x37, 0x8e, 0x3d, 0x7b, 0x45, 0xd5, 0xea, 0xdb, 0xfa, 0x42, 0xb3, 0x03, 0xfc, 0xc7, 0x41, 0x37, 0xa9, 0x68, 0xbf, 0x7a, 0xa8, 0xb4, 0x63, 0xff, 0xa3, 0x8b, 0x17, 0x3f, 0xba, 0xbf, 0xbd, 0x74, 0xf0, 0xea, 0x76, 0x52, 0xe1, 0x0e, 0xc2, 0x33, 0x9d, 0xa1, 0xbe, 0x5b, 0x57, 0x57, 0x5f, 0xf1, 0xec, 0x58, 0xe6, 0xe2, 0xc7, 0xce, 0x1f, 0x5f, 0xfd, 0x9f, 0x77, 0x6d, 0x74, 0xb1, 0x92, 0x2b, 0x9c, 0x81, 0xf3, 0xaa, 0x45, 0x47, 0x9e, 0x1a, 0xd9, 0x76, 0x76, 0x5b, 0x45, 0x05, 0xfc, 0x67, 0xe4, 0xa9, 0x23, 0x8b, 0x54, 0xe7, 0x03, 0xce, 0x2b, 0x24, 0xac, 0x6b, 0xe3, 0x5d, 0xff, 0xb9, 0xfa, 0xf8, 0xf9, 0xc7, 0x04, 0x5c, 0x8b, 0x7b, 0xc7, 0xff, 0xc5, 0xcc, 0xc3, 0x76, 0x9b, 0xf9, 0xd8, 0xd5, 0xfa, 0x64, 0x14, 0x61, 0x21, 0x34, 0x5b, 0xd1, 0x27, 0x5c, 0x1f, 0x62, 0x2e, 0xb5, 0x81, 0x10, 0x54, 0x0d, 0xbc, 0x32, 0xd2, 0x52, 0x11, 0x51, 0x48, 0x23, 0x39, 0x29, 0x25, 0x3a, 0x2d, 0xc1, 0xfc, 0xa9, 0x2c, 0xa8, 0x6b, 0x08, 0xf9, 0x76, 0x22, 0xb6, 0x13, 0x39, 0x55, 0xcd, 0xa0, 0x93, 0x75, 0xc5, 0x11, 0x8c, 0x25, 0x55, 0x93, 0x37, 0x2f, 0xf6, 0xf8, 0xfd, 0x89, 0x35, 0xb7, 0x3d, 0xf9, 0x8b, 0xa5, 0xdd, 0xd7, 0x0f, 0x17, 0xbf, 0x61, 0x2b, 0x6a, 0xcb, 0xab, 0x39, 0x78, 0xe5, 0x9a, 0x39, 0x79, 0x0a, 0x57, 0x59, 0x8e, 0x36, 0x4b, 0xab, 0x0b, 0x48, 0xad, 0xcc, 0x41, 0x4e, 0xfb, 0xc7, 0x5f, 0xdf, 0xc8, 0x7f, 0xfe, 0xe1, 0x33, 0xfb, 0x67, 0x96, 0x6c, 0x7c, 0x7c, 0x07, 0xf5, 0x5f, 0x17, 0x73, 0x3b, 0x56, 0xcf, 0x70, 0x97, 0x2c, 0xbf, 0xe5, 0xf9, 0x0f, 0xf7, 0x14, 0xf5, 0xd7, 0x87, 0x58, 0x38, 0x79, 0x1d, 0xab, 0xf1, 0x1e, 0x68, 0x0c, 0xae, 0x8b, 0xef, 0x41, 0x3e, 0xe5, 0x24, 0x16, 0x08, 0xb3, 0x4e, 0x89, 0x24, 0xae, 0x5d, 0xc7, 0xa1, 0x18, 0x35, 0xa8, 0x49, 0xa2, 0x9f, 0x54, 0xf2, 0xa7, 0x98, 0x5d, 0xcf, 0x61, 0x33, 0x3c, 0x05, 0xc4, 0x12, 0xb2, 0x43, 0xa4, 0x60, 0x69, 0x15, 0xd6, 0xd1, 0x44, 0x69, 0x59, 0x78, 0xbc, 0xf7, 0xac, 0x47, 0x17, 0xf2, 0x08, 0xe8, 0xe8, 0x53, 0x14, 0xc6, 0x64, 0x02, 0x11, 0x86, 0x8c, 0x8d, 0x17, 0x53, 0x77, 0x24, 0x6e, 0x1f, 0x8d, 0x77, 0xe6, 0x5a, 0xa5, 0x41, 0x9d, 0x3a, 0x62, 0xcc, 0x6f, 0x72, 0xf0, 0x47, 0xde, 0x18, 0xfb, 0xf4, 0x27, 0x36, 0x8f, 0x02, 0x94, 0x68, 0x9d, 0x0a, 0x43, 0x88, 0x7e, 0x73, 0xfd, 0xa0, 0x96, 0x5b, 0x86, 0x76, 0x07, 0xb2, 0x58, 0xc1, 0xd8, 0xcd, 0x63, 0x2f, 0x91, 0x25, 0xd4, 0x90, 0xd5, 0x30, 0xf6, 0x17, 0x7f, 0xc2, 0x95, 0x68, 0x40, 0x31, 0xa7, 0x50, 0x8b, 0xdd, 0x01, 0xe7, 0xa2, 0x01, 0xf9, 0x4a, 0x15, 0x08, 0x82, 0x11, 0x19, 0xff, 0x68, 0x1c, 0x64, 0xba, 0x18, 0x4a, 0x4f, 0x49, 0x2f, 0x21, 0x91, 0x60, 0xfd, 0x16, 0x67, 0x1a, 0x41, 0xcd, 0x96, 0x83, 0x8b, 0x01, 0xfe, 0xd5, 0xe2, 0x8d, 0x92, 0x90, 0x63, 0x94, 0x84, 0x13, 0x47, 0x89, 0x46, 0xe8, 0x2f, 0xbd, 0xc3, 0xc8, 0xff, 0x10, 0x54, 0xf3, 0x1d, 0xfc, 0x03, 0x94, 0xfb, 0xc2, 0x4b, 0x0a, 0x19, 0x58, 0xc9, 0xbf, 0x08, 0x7e, 0x00, 0x2a, 0x2e, 0x7e, 0x45, 0x3b, 0x98, 0x36, 0xbf, 0x8b, 0xcf, 0xba, 0x8b, 0xb7, 0x9d, 0x99, 0xcb, 0x39, 0x54, 0x24, 0x71, 0x17, 0x78, 0xf6, 0x8c, 0xb0, 0xe6, 0xf3, 0xe0, 0x3e, 0xbb, 0x06, 0xeb, 0xa7, 0x55, 0x89, 0xf2, 0x5c, 0x40, 0x33, 0x41, 0xb8, 0x2c, 0xd1, 0x1e, 0x93, 0x6a, 0x46, 0xba, 0x36, 0x2d, 0xf8, 0x4d, 0x69, 0xe4, 0x37, 0x4d, 0xcf, 0xf9, 0xc6, 0xa6, 0x49, 0x38, 0x39, 0x38, 0x3d, 0x32, 0x98, 0x63, 0xaf, 0x92, 0x30, 0xe1, 0x27, 0x32, 0xe0, 0xd2, 0xed, 0x7e, 0x11, 0x90, 0x6c, 0x2a, 0x8a, 0xb6, 0x22, 0x87, 0xaf, 0x7c, 0x72, 0x7d, 0xac, 0xef, 0xb6, 0x57, 0xd6, 0xce, 0xbc, 0x66, 0xfb, 0x68, 0x78, 0xb6, 0x42, 0xcb, 0x2a, 0x75, 0xda, 0xcc, 0x58, 0xdb, 0x50, 0xf5, 0xdc, 0xdd, 0x3d, 0xd1, 0xc0, 0xdc, 0x43, 0x83, 0x0f, 0xab, 0x38, 0xb0, 0x6d, 0x6c, 0x25, 0xf5, 0xac, 0xc2, 0x40, 0x6d, 0x61, 0x6a, 0x56, 0x1e, 0xe9, 0xdc, 0xf0, 0xf4, 0xde, 0x46, 0x47, 0xe9, 0xec, 0xd8, 0x42, 0x25, 0x2d, 0xcd, 0xd4, 0x64, 0x07, 0x2d, 0x05, 0x5d, 0x6b, 0xaa, 0x8b, 0x17, 0xd4, 0x07, 0xfb, 0x39, 0x1f, 0x07, 0xd6, 0x9e, 0xe9, 0xd0, 0x84, 0x75, 0xb0, 0x3f, 0x3d, 0xb0, 0x3f, 0xa3, 0xb8, 0x3f, 0x79, 0x89, 0x28, 0x64, 0x31, 0xa8, 0x2a, 0xd2, 0x1a, 0x82, 0x86, 0x8a, 0x37, 0xb3, 0x0a, 0xed, 0x90, 0x7b, 0x85, 0x89, 0x2e, 0xb8, 0xc5, 0x70, 0xb1, 0x68, 0x3f, 0xa7, 0x37, 0xe1, 0x0a, 0x18, 0xe9, 0xb6, 0x01, 0x01, 0xa3, 0x1d, 0x52, 0x19, 0x95, 0x18, 0xb9, 0xc4, 0x18, 0x60, 0x30, 0xd2, 0xa3, 0xe1, 0xd1, 0xed, 0xd7, 0xcc, 0x5c, 0xfb, 0xca, 0x6d, 0x7d, 0xb1, 0xf5, 0x4f, 0xec, 0xe6, 0x57, 0x19, 0x14, 0xd4, 0xb3, 0x63, 0x2b, 0xc1, 0x36, 0x2e, 0xe3, 0xa1, 0xc1, 0x43, 0xf3, 0x02, 0xd1, 0x9e, 0x3d, 0x73, 0xab, 0x87, 0xda, 0x62, 0x99, 0x5a, 0x9d, 0x42, 0xa2, 0x51, 0xbc, 0x1e, 0x9b, 0x5d, 0xea, 0x68, 0xdc, 0xfb, 0xf4, 0x86, 0xce, 0xeb, 0x57, 0xd4, 0x30, 0xe0, 0x80, 0x2e, 0xac, 0xe9, 0x38, 0xc3, 0x5f, 0x0f, 0x5b, 0xde, 0x1f, 0xac, 0x5f, 0x50, 0x5c, 0xbd, 0xa6, 0xab, 0xc0, 0x12, 0xcc, 0xd6, 0x64, 0x4a, 0x69, 0x25, 0xec, 0x43, 0xc9, 0xf8, 0xc7, 0x74, 0x0e, 0xe6, 0x55, 0xcb, 0x12, 0x4a, 0xa8, 0x5e, 0x81, 0xdc, 0x1c, 0x3d, 0x64, 0x8e, 0x48, 0x2f, 0x0b, 0x60, 0x2c, 0x3b, 0x80, 0x8b, 0x3d, 0x0d, 0x22, 0x7b, 0x1c, 0x4e, 0xfe, 0x9d, 0xd8, 0xfc, 0xe3, 0xfd, 0x9b, 0x8f, 0x40, 0x49, 0xa0, 0xcc, 0xb2, 0xe4, 0x75, 0xb0, 0xa3, 0xec, 0xfc, 0xa9, 0x97, 0xc1, 0xcd, 0x92, 0xbe, 0x21, 0x14, 0x0d, 0x79, 0x3d, 0x42, 0xb2, 0x3e, 0xeb, 0x75, 0x53, 0xe9, 0x65, 0xdf, 0x02, 0x97, 0x8e, 0xa2, 0x50, 0x4b, 0x8f, 0xf5, 0xe0, 0xb8, 0x4b, 0x04, 0x61, 0xcf, 0xc6, 0xaa, 0xf8, 0xa1, 0xd1, 0x70, 0xab, 0x6f, 0xef, 0x0b, 0x7b, 0x13, 0xdd, 0x37, 0x3e, 0x33, 0xfc, 0xd0, 0x27, 0xb3, 0x67, 0x2a, 0x38, 0xa9, 0x5c, 0x23, 0xd5, 0x16, 0x77, 0x8c, 0x36, 0xf4, 0xec, 0xea, 0x0c, 0xe6, 0x74, 0xed, 0x68, 0xab, 0xdc, 0x5d, 0xb1, 0xed, 0x24, 0xe7, 0xd7, 0x54, 0x1a, 0x1c, 0x5a, 0x19, 0xf5, 0x7d, 0x66, 0x46, 0x56, 0x8c, 0xbf, 0x57, 0x22, 0xa9, 0xdb, 0x7c, 0x6a, 0x60, 0xed, 0x53, 0x7b, 0x1a, 0x36, 0x8e, 0xce, 0xcb, 0x90, 0xca, 0xa4, 0x99, 0xd9, 0x61, 0x7b, 0x51, 0xf7, 0xda, 0xca, 0x9a, 0xa1, 0x7a, 0xaf, 0x46, 0xe7, 0x23, 0xd5, 0xa4, 0x52, 0x63, 0x54, 0x8c, 0x08, 0xf3, 0x74, 0x3e, 0xe4, 0xdf, 0x7f, 0x81, 0xfc, 0xad, 0x0a, 0xd9, 0x1b, 0x0c, 0xb8, 0x76, 0x1b, 0xf2, 0x00, 0x02, 0x1a, 0xce, 0x53, 0x29, 0x21, 0x61, 0xa5, 0x92, 0x41, 0x19, 0x60, 0xd1, 0x32, 0x62, 0x89, 0x54, 0xd5, 0x83, 0x15, 0x02, 0x2c, 0x92, 0x30, 0x55, 0xab, 0x2a, 0xb5, 0x6e, 0xad, 0x3f, 0x94, 0x1d, 0xf2, 0x67, 0xca, 0x11, 0x22, 0x8b, 0x57, 0x3f, 0xd9, 0xac, 0xe0, 0x20, 0x8d, 0xd3, 0x98, 0x20, 0x62, 0x82, 0x09, 0x42, 0xfc, 0xe0, 0x0a, 0x99, 0xbf, 0xdc, 0x2d, 0x9a, 0x13, 0xbe, 0x16, 0xcd, 0x09, 0x5d, 0x47, 0x77, 0xad, 0x88, 0x56, 0xa8, 0x72, 0xac, 0xd6, 0xea, 0xae, 0x8d, 0x1d, 0x1b, 0xd3, 0xad, 0x0f, 0x6f, 0xeb, 0xcd, 0x80, 0x7d, 0xdb, 0x60, 0x02, 0xff, 0x79, 0x37, 0xe8, 0x78, 0x71, 0xb2, 0x1d, 0xc1, 0x92, 0x57, 0x97, 0x35, 0x9d, 0xd1, 0x61, 0xc8, 0xa8, 0x5d, 0xbe, 0xd9, 0xa8, 0xbd, 0x82, 0xff, 0x54, 0xd8, 0x3f, 0x8f, 0x7f, 0x02, 0xb7, 0x83, 0x27, 0xa0, 0x5e, 0x70, 0x4a, 0x60, 0x6a, 0x7e, 0x82, 0x21, 0x48, 0x09, 0x43, 0x2e, 0x66, 0xb1, 0x4b, 0x44, 0x06, 0xa4, 0xd2, 0x64, 0x2a, 0xb8, 0x44, 0x82, 0xc3, 0xd0, 0x27, 0x25, 0x97, 0x5f, 0xf6, 0x62, 0xc8, 0x57, 0x84, 0x3b, 0x92, 0x22, 0x20, 0x8f, 0x90, 0x49, 0x19, 0xa9, 0x8c, 0x59, 0x33, 0xf9, 0x1e, 0xe2, 0xb2, 0xb7, 0x40, 0x21, 0xad, 0x8a, 0x84, 0x03, 0x5e, 0x64, 0xaf, 0xd4, 0x87, 0xdc, 0x98, 0xa8, 0xf1, 0x49, 0x25, 0x32, 0x2a, 0xb1, 0x82, 0x52, 0x88, 0x82, 0xf8, 0xa9, 0x64, 0x25, 0x64, 0x64, 0x21, 0x87, 0xda, 0x21, 0x5b, 0xd1, 0x39, 0x30, 0xe7, 0x8a, 0x84, 0xa5, 0xf7, 0xc6, 0x67, 0x06, 0x3b, 0xaf, 0x5f, 0x59, 0x35, 0xa6, 0x7d, 0xeb, 0x2d, 0xf2, 0xa3, 0x37, 0xc1, 0xe2, 0xde, 0x9a, 0xf5, 0xdd, 0x05, 0x15, 0xcd, 0x46, 0xaf, 0xc2, 0xcd, 0x52, 0xff, 0x32, 0x97, 0x0e, 0xec, 0x6a, 0xf8, 0x01, 0xff, 0xf5, 0x0f, 0xba, 0x73, 0x96, 0xde, 0xb3, 0xe9, 0xaf, 0x63, 0x5b, 0x99, 0x13, 0x63, 0x07, 0xc8, 0x6d, 0x17, 0xa9, 0x6b, 0xf7, 0x55, 0xef, 0xfb, 0xf9, 0xc1, 0x81, 0x41, 0x79, 0x40, 0x88, 0xbd, 0x79, 0x8c, 0x7f, 0x88, 0xc9, 0x83, 0xf2, 0x41, 0x0d, 0x37, 0x87, 0xa1, 0x84, 0x3f, 0x00, 0x15, 0x27, 0x67, 0xa6, 0x1a, 0x83, 0xf9, 0x62, 0x63, 0x19, 0x26, 0x0c, 0x66, 0xf7, 0xcb, 0xc8, 0x56, 0xce, 0x12, 0x0e, 0x23, 0x66, 0xef, 0x8f, 0xfb, 0x04, 0x44, 0x2b, 0xb1, 0xfc, 0x53, 0xb2, 0xc0, 0x07, 0x4b, 0xa7, 0x12, 0xe2, 0x0f, 0x3c, 0x02, 0x54, 0x3f, 0x5a, 0x94, 0xdd, 0xb1, 0xa6, 0xa1, 0x6a, 0xc3, 0xe8, 0xc2, 0x2c, 0xaa, 0x6c, 0xf5, 0xba, 0x4d, 0x35, 0x6b, 0x1f, 0xdb, 0x9a, 0x38, 0x70, 0x9e, 0xbf, 0xef, 0xcd, 0x37, 0x11, 0x92, 0xc7, 0x3d, 0xcd, 0x0f, 0x9c, 0xfb, 0xc3, 0xbe, 0xa6, 0x0d, 0xb3, 0xb3, 0xdd, 0x55, 0x5d, 0xb1, 0xd7, 0xca, 0x3a, 0x0b, 0x0c, 0xb3, 0xaf, 0x7e, 0x64, 0xc1, 0x7d, 0xfc, 0xef, 0xbf, 0xfa, 0x4d, 0x37, 0xff, 0x1a, 0x7b, 0x71, 0x02, 0x1b, 0xe5, 0x21, 0x8c, 0x8d, 0xe2, 0x40, 0x16, 0x22, 0x07, 0xe4, 0xf6, 0x46, 0xe4, 0xb2, 0xc7, 0x45, 0xdb, 0xc5, 0x84, 0x38, 0x91, 0xc6, 0x42, 0xb6, 0x10, 0xdc, 0xe2, 0xfb, 0x04, 0xa9, 0x4b, 0xa5, 0xa2, 0x27, 0xa6, 0xc2, 0x8d, 0x54, 0xae, 0x5a, 0xb5, 0x1e, 0xc3, 0x8d, 0xbc, 0x35, 0x05, 0x5d, 0xe4, 0xb5, 0x1a, 0x11, 0x5d, 0xe4, 0xeb, 0xff, 0x66, 0xec, 0x69, 0x78, 0x22, 0xf7, 0xe1, 0xc6, 0x90, 0xc4, 0x20, 0xe4, 0x31, 0x77, 0xc2, 0x79, 0xa5, 0x25, 0x3a, 0x50, 0x54, 0x45, 0x7d, 0x9e, 0x4d, 0x8e, 0x3d, 0xea, 0x0c, 0x6c, 0x50, 0xd2, 0x62, 0x25, 0x8e, 0x33, 0x06, 0xbc, 0xeb, 0xd0, 0xb5, 0x97, 0x95, 0x04, 0xfd, 0xc9, 0xe0, 0xa1, 0xe4, 0x26, 0x2e, 0x28, 0xe8, 0x3f, 0x49, 0xdc, 0xb0, 0x09, 0x19, 0x90, 0xb2, 0xb0, 0xc6, 0x8b, 0x61, 0x4b, 0x25, 0x42, 0x3c, 0xbf, 0x11, 0x17, 0xe8, 0x09, 0x06, 0xe2, 0x85, 0xe4, 0x9e, 0x99, 0xdb, 0x7b, 0xf3, 0xd1, 0xe0, 0xd6, 0x6c, 0xe8, 0x2e, 0x2a, 0xe8, 0xd9, 0x4e, 0x02, 0xaf, 0xab, 0xa0, 0x6b, 0x5d, 0x75, 0xc3, 0xae, 0xc5, 0xa5, 0x33, 0xaf, 0x7d, 0x79, 0xcf, 0x9e, 0x97, 0xaf, 0x99, 0x59, 0xbe, 0x64, 0x4f, 0x23, 0x3a, 0xed, 0x0c, 0xdd, 0x62, 0xd6, 0x2a, 0xb5, 0xa6, 0xc2, 0xee, 0x0d, 0x89, 0xb2, 0x91, 0xd6, 0xa8, 0x51, 0xa7, 0xd4, 0xf9, 0x6f, 0x73, 0xd5, 0x8d, 0x34, 0x25, 0x16, 0xb7, 0x56, 0xd8, 0x6d, 0x95, 0xad, 0x8b, 0x13, 0x4d, 0x23, 0x75, 0x2e, 0x86, 0xcc, 0x5b, 0x5d, 0xd4, 0xb2, 0x61, 0x7e, 0xa3, 0xc3, 0xd5, 0x36, 0x72, 0xf5, 0xfc, 0x81, 0x3b, 0x56, 0x55, 0x54, 0xac, 0xba, 0x63, 0xa0, 0x7b, 0xdf, 0x60, 0xb3, 0xd3, 0xd1, 0xb4, 0x70, 0x6b, 0x47, 0xc9, 0x50, 0xb1, 0xcf, 0x62, 0x6f, 0xce, 0xa9, 0xe8, 0x6b, 0x2a, 0xb3, 0xfe, 0x7f, 0xe4, 0xbd, 0x07, 0x74, 0x1c, 0xc7, 0x95, 0x2e, 0xdc, 0xd5, 0x61, 0x72, 0xce, 0x98, 0x3c, 0x98, 0x8c, 0x30, 0x18, 0x60, 0x30, 0x83, 0x0c, 0x0c, 0x40, 0xe4, 0x41, 0x20, 0x40, 0x02, 0x04, 0x88, 0xc0, 0x80, 0xc0, 0x4c, 0x82, 0x14, 0x83, 0x28, 0x06, 0x49, 0x4c, 0x22, 0x29, 0x89, 0x0a, 0xa4, 0x6c, 0x2b, 0x38, 0x48, 0xb6, 0x68, 0x99, 0x92, 0x6c, 0x4b, 0x32, 0x25, 0xdb, 0x92, 0x65, 0xcb, 0xa4, 0xed, 0xf5, 0xea, 0x39, 0xae, 0x1c, 0x76, 0xed, 0xf5, 0xda, 0xbb, 0x7e, 0x6b, 0xaf, 0xd3, 0x93, 0xe4, 0x67, 0x3f, 0x05, 0x62, 0xf0, 0xaa, 0xaa, 0xbb, 0x67, 0x7a, 0x06, 0x41, 0xb4, 0xdf, 0xff, 0x9f, 0xf7, 0x9f, 0xf3, 0xeb, 0x1c, 0x0a, 0xe8, 0x9e, 0xaa, 0x41, 0xf7, 0xad, 0xaa, 0x5b, 0xf7, 0xde, 0xfa, 0xee, 0x77, 0xad, 0xb5, 0xdd, 0x13, 0xf5, 0x45, 0x5d, 0x0e, 0x6b, 0x80, 0xcd, 0x59, 0x79, 0x88, 0x3e, 0x0b, 0xe5, 0xe1, 0x25, 0x4e, 0xb3, 0x66, 0xab, 0xcc, 0x02, 0x9d, 0x7a, 0x29, 0x76, 0x87, 0x6d, 0x99, 0x0b, 0x12, 0xdb, 0x16, 0x85, 0xf8, 0x28, 0x33, 0xb3, 0x1d, 0x8a, 0x81, 0x48, 0xc4, 0x6e, 0xd4, 0x29, 0x36, 0x2d, 0x96, 0x65, 0x25, 0x44, 0xd5, 0xd8, 0x10, 0x33, 0x04, 0x22, 0x4e, 0x59, 0xaa, 0x2d, 0xcf, 0x9c, 0x32, 0x47, 0x70, 0x4c, 0x6c, 0x5e, 0xc2, 0xab, 0x35, 0x1a, 0x3d, 0x7e, 0x4c, 0xaa, 0xe9, 0x37, 0xe6, 0x31, 0xf8, 0x34, 0xa2, 0xd5, 0x94, 0xdd, 0x45, 0xc5, 0x80, 0x3e, 0xeb, 0x5f, 0x7d, 0x64, 0x38, 0xba, 0xa6, 0xd4, 0x28, 0xb2, 0x2a, 0x94, 0x91, 0xc2, 0x50, 0x5d, 0x50, 0x77, 0x63, 0x56, 0x2e, 0xa5, 0x6c, 0x68, 0xc7, 0x37, 0xdd, 0xf8, 0xcd, 0xb3, 0xcf, 0xf5, 0x9d, 0xda, 0x90, 0xd0, 0x1a, 0x86, 0x2c, 0x6a, 0x95, 0x3d, 0xba, 0x2a, 0x58, 0x88, 0xf6, 0x7e, 0x7a, 0xf2, 0xd9, 0x7e, 0x9f, 0xe7, 0xfd, 0x27, 0xd1, 0x1c, 0x10, 0x41, 0x9b, 0xf1, 0x17, 0xf8, 0xac, 0xc4, 0x8f, 0x10, 0x3d, 0x52, 0x54, 0xeb, 0x0b, 0xa1, 0x19, 0x18, 0x62, 0x13, 0x02, 0xe4, 0x4d, 0x09, 0x9d, 0x27, 0xbf, 0x0f, 0x2f, 0x73, 0xbf, 0x91, 0xd3, 0x9e, 0x9e, 0x8c, 0xd9, 0xeb, 0xf7, 0xf2, 0x63, 0xde, 0x00, 0xbc, 0x38, 0x41, 0x0f, 0x15, 0xfd, 0x8a, 0xd1, 0x1b, 0xe6, 0xfd, 0x15, 0x2c, 0xff, 0x5f, 0x39, 0xb8, 0xfd, 0x0a, 0xcf, 0x09, 0x78, 0xe5, 0xca, 0x1b, 0xe4, 0xcf, 0x7e, 0x74, 0x85, 0x7c, 0x60, 0x94, 0x67, 0xfd, 0x5b, 0xbf, 0x9e, 0xe7, 0x02, 0x5c, 0xff, 0x38, 0xb9, 0x7d, 0xfe, 0x43, 0xe8, 0x1f, 0xbb, 0xa6, 0x27, 0xd2, 0x97, 0x44, 0xf7, 0xc1, 0xf5, 0xa2, 0x23, 0x1a, 0x80, 0x92, 0xd5, 0x7e, 0xca, 0xb2, 0x08, 0x49, 0xd0, 0x76, 0x1d, 0xf2, 0x1c, 0xd0, 0xc0, 0xe0, 0x6b, 0x86, 0xbf, 0xe6, 0x38, 0x38, 0x3c, 0xb8, 0xd4, 0x15, 0x1b, 0x97, 0xe0, 0x5f, 0x61, 0x0f, 0x16, 0xbd, 0xc0, 0x73, 0x51, 0x2c, 0xd3, 0x70, 0x67, 0x7e, 0x43, 0xf7, 0xcd, 0x7e, 0x63, 0x80, 0xd5, 0xce, 0x0c, 0x0e, 0x56, 0x65, 0x4f, 0x47, 0x97, 0x6a, 0x9c, 0x2c, 0x21, 0xc4, 0x94, 0x88, 0x42, 0x07, 0x18, 0xcb, 0xb5, 0x27, 0xb2, 0xcd, 0x71, 0xb5, 0x4f, 0xb9, 0xc7, 0x1b, 0x0c, 0x05, 0x83, 0xec, 0x29, 0x24, 0xa6, 0xa1, 0x13, 0xec, 0x52, 0x99, 0x14, 0xbd, 0xdc, 0xf8, 0x09, 0x5c, 0x71, 0x55, 0x28, 0xdf, 0x5d, 0xcb, 0x3b, 0x50, 0x66, 0xfa, 0x8e, 0xee, 0x4f, 0xef, 0xdd, 0xf2, 0xfc, 0xc9, 0x54, 0xcb, 0x2d, 0x1f, 0x1d, 0xab, 0x99, 0xdb, 0xb1, 0x31, 0x44, 0xaa, 0x91, 0xef, 0x74, 0x6a, 0x4d, 0x60, 0xfc, 0xad, 0xb3, 0xc7, 0x7e, 0xfa, 0xf1, 0xf5, 0xdb, 0x5e, 0xfc, 0xcb, 0x3d, 0x35, 0x5b, 0x36, 0xad, 0x2f, 0xa1, 0x48, 0xbb, 0xe9, 0x91, 0xee, 0x01, 0x6f, 0xa9, 0x52, 0x61, 0x15, 0xd1, 0xdd, 0x43, 0xeb, 0xba, 0xef, 0x7b, 0xe3, 0xfc, 0x89, 0x6f, 0x9e, 0x69, 0xb5, 0xd5, 0x8d, 0x25, 0xe7, 0x6d, 0x06, 0x6d, 0x62, 0xea, 0xdc, 0x9a, 0x6f, 0xed, 0xd8, 0xbb, 0xe9, 0x25, 0x20, 0xbe, 0xfc, 0x05, 0x60, 0x79, 0x6d, 0x46, 0x17, 0x6e, 0x2d, 0xdf, 0x6a, 0x73, 0xbe, 0xb7, 0x6e, 0xc3, 0x4e, 0xa5, 0xaa, 0x80, 0x1d, 0x43, 0x1d, 0x7c, 0xb5, 0xb7, 0x70, 0x2c, 0x25, 0x88, 0x32, 0x71, 0xfc, 0x38, 0xbe, 0x20, 0x92, 0x64, 0x80, 0x5b, 0xf0, 0x85, 0x31, 0x11, 0x1f, 0x52, 0x39, 0xd8, 0x14, 0x47, 0x47, 0xdd, 0x05, 0x79, 0xc1, 0x05, 0xe9, 0xb2, 0xc1, 0x05, 0x34, 0xe1, 0x3c, 0x82, 0xf0, 0xc2, 0x9a, 0x25, 0xc2, 0x0b, 0x57, 0xae, 0x80, 0x8f, 0x09, 0x02, 0x0c, 0xbf, 0x0d, 0x75, 0xe1, 0x00, 0xc3, 0x9f, 0xe7, 0x2d, 0xa5, 0xe0, 0xed, 0x02, 0xe3, 0xfc, 0x0b, 0x64, 0xef, 0xa3, 0xe4, 0x17, 0x84, 0x21, 0x06, 0xf8, 0xdc, 0x5e, 0x68, 0x40, 0xed, 0x81, 0xcf, 0x2d, 0x21, 0xdc, 0xe8, 0xd4, 0x45, 0x29, 0x63, 0x10, 0x42, 0xa3, 0x1b, 0xee, 0x8b, 0x94, 0x54, 0x82, 0x88, 0x2f, 0x28, 0x62, 0x33, 0xb7, 0x41, 0x62, 0x9f, 0x5c, 0x2a, 0x95, 0xba, 0xa5, 0x6e, 0xad, 0x57, 0xef, 0xd1, 0xea, 0xac, 0x70, 0x89, 0xc0, 0x31, 0xf2, 0x98, 0x59, 0x7e, 0x05, 0x3f, 0x76, 0x21, 0x3c, 0xf1, 0x18, 0x0b, 0x7d, 0xf2, 0xe0, 0x21, 0xa1, 0x62, 0x46, 0x6a, 0x2a, 0xfd, 0x9d, 0xcd, 0x5a, 0x87, 0x5c, 0x6e, 0xd3, 0x82, 0xfa, 0xf4, 0x5f, 0xde, 0x48, 0xff, 0x25, 0xfd, 0xbd, 0x7f, 0x7e, 0x1a, 0x79, 0x12, 0x77, 0x81, 0x9f, 0x9d, 0x91, 0x79, 0x6d, 0xaf, 0x3c, 0xfd, 0xcf, 0xd4, 0x3f, 0x7f, 0x21, 0xd0, 0xe2, 0xf6, 0xb4, 0xf8, 0xbf, 0x90, 0x2e, 0x9b, 0x7f, 0x87, 0x94, 0x80, 0x1f, 0x3c, 0x02, 0xc2, 0x36, 0xd3, 0x3b, 0xef, 0xe8, 0x5c, 0xe9, 0x9f, 0x3c, 0xb2, 0xb0, 0x40, 0x7c, 0x3c, 0xfd, 0x21, 0xc6, 0xcb, 0x3c, 0x46, 0x06, 0x88, 0x00, 0xae, 0x93, 0x78, 0x15, 0x0c, 0xbd, 0x07, 0x50, 0xa5, 0x50, 0xf2, 0x8b, 0xef, 0x01, 0x8c, 0x2a, 0x47, 0x7b, 0xe3, 0x63, 0xf4, 0x7f, 0xe2, 0x31, 0xa8, 0x24, 0xee, 0x40, 0xa8, 0x0c, 0x8a, 0x70, 0x02, 0x11, 0xcd, 0x73, 0x2a, 0xe2, 0x8a, 0x8c, 0x79, 0x83, 0x81, 0xdc, 0xa2, 0x14, 0x9a, 0xef, 0x98, 0x91, 0x67, 0x4e, 0x84, 0x2b, 0x32, 0x32, 0x24, 0xca, 0x85, 0x9f, 0x5b, 0xae, 0x39, 0x91, 0x6d, 0x3d, 0x9a, 0x34, 0x23, 0x30, 0x40, 0xa1, 0x1b, 0x9d, 0x0f, 0x7f, 0xf0, 0x38, 0x2e, 0xe1, 0xdf, 0xaf, 0x1c, 0x32, 0x42, 0x7e, 0x3f, 0x9c, 0x96, 0xac, 0xdf, 0x0f, 0xe7, 0xee, 0xca, 0x03, 0xfc, 0xd9, 0x9c, 0x78, 0xc0, 0xdd, 0x39, 0x23, 0xcd, 0xee, 0xcb, 0x4f, 0x33, 0x0f, 0x30, 0x1f, 0x25, 0xc2, 0x08, 0x8d, 0xe1, 0x07, 0xb8, 0x68, 0x1e, 0x6b, 0x03, 0x4d, 0xb1, 0x43, 0x2c, 0x01, 0xd0, 0xea, 0x19, 0x47, 0x16, 0xc4, 0x74, 0x8a, 0x4d, 0x54, 0x84, 0xff, 0x0f, 0x13, 0x21, 0xbf, 0x47, 0xab, 0x0d, 0x7b, 0xbc, 0x28, 0x0a, 0xef, 0xd7, 0xa2, 0x0a, 0xd3, 0x5a, 0xee, 0x58, 0x81, 0x62, 0x23, 0x5f, 0x5a, 0xee, 0x08, 0xa2, 0x0e, 0xa0, 0xd2, 0x27, 0x70, 0xbf, 0x8e, 0x33, 0x0f, 0x04, 0x9c, 0xb7, 0x77, 0xb1, 0x87, 0x09, 0x41, 0x99, 0xc1, 0x5a, 0x68, 0x5e, 0xf7, 0xa3, 0x1f, 0xb5, 0x07, 0xca, 0xf4, 0xe0, 0xdb, 0xa6, 0x48, 0xe1, 0x8b, 0xf3, 0x7f, 0x30, 0x04, 0x4c, 0x55, 0x61, 0xf0, 0xa9, 0x4f, 0xa7, 0x57, 0x39, 0x0a, 0xc1, 0x10, 0x3e, 0x4c, 0x88, 0x2a, 0x75, 0x72, 0x06, 0x1d, 0x27, 0xcc, 0x3f, 0xa8, 0x37, 0x93, 0x7a, 0x9b, 0x71, 0xfe, 0xb1, 0x02, 0x8f, 0x5c, 0x5f, 0x3b, 0x3f, 0x4f, 0xf3, 0x39, 0xa4, 0x4f, 0xe3, 0xbd, 0x4b, 0x8f, 0x50, 0xc0, 0x12, 0x04, 0xbd, 0xec, 0xe6, 0x7d, 0xc7, 0x29, 0x3c, 0x88, 0x6c, 0x51, 0xfa, 0xec, 0xa3, 0xa3, 0xba, 0x9f, 0xd0, 0x9c, 0x40, 0x81, 0x30, 0xee, 0xb9, 0xf3, 0x76, 0x16, 0xf8, 0x90, 0x37, 0x76, 0x09, 0xb7, 0x92, 0x74, 0x03, 0x7a, 0x9c, 0x9c, 0x0d, 0x04, 0xfe, 0x5d, 0x68, 0x43, 0xfc, 0xe6, 0xff, 0x06, 0x56, 0xe4, 0x49, 0x8c, 0x15, 0x59, 0x3b, 0xd5, 0x38, 0xf3, 0xf9, 0x13, 0x29, 0x84, 0x15, 0x19, 0xbb, 0x6d, 0x4d, 0x4c, 0xaf, 0x35, 0x88, 0xdb, 0xd7, 0xfd, 0x49, 0x80, 0x15, 0x99, 0xff, 0x1b, 0xb1, 0x22, 0x2d, 0x70, 0x5e, 0xbf, 0x82, 0xeb, 0xfd, 0x0a, 0xce, 0xe8, 0x19, 0x7e, 0x91, 0x0b, 0xca, 0xfe, 0xf0, 0x9c, 0x39, 0xf8, 0xfc, 0x81, 0x3b, 0xa0, 0xd7, 0x52, 0xaf, 0xdc, 0x38, 0x43, 0x7e, 0x6a, 0xfe, 0x6b, 0x64, 0x12, 0x9d, 0x25, 0x3c, 0x0b, 0xfe, 0xfd, 0x59, 0x72, 0x23, 0xc1, 0x9f, 0x81, 0xd1, 0xfb, 0xe1, 0xf7, 0x62, 0xf6, 0x8d, 0x4c, 0xa6, 0x2a, 0xf6, 0x43, 0xb1, 0xee, 0xc8, 0x26, 0xdb, 0xb3, 0xd6, 0x32, 0xbd, 0x9f, 0xb5, 0x86, 0x11, 0x55, 0x2a, 0xb4, 0x77, 0x1f, 0x44, 0xe7, 0x8b, 0x7f, 0x59, 0x78, 0x9b, 0xd6, 0x31, 0xd7, 0x09, 0x15, 0x51, 0x46, 0xbc, 0xcc, 0xae, 0x5b, 0x85, 0x5a, 0x85, 0xb2, 0x31, 0x3c, 0xf0, 0xdb, 0x70, 0x04, 0x84, 0xbf, 0xa4, 0xb3, 0x3b, 0xa1, 0x1f, 0xee, 0xe3, 0x52, 0xc0, 0x90, 0x22, 0x66, 0x13, 0x5c, 0xa7, 0x33, 0x99, 0x33, 0xbd, 0x59, 0x16, 0x0d, 0x80, 0x92, 0x6e, 0x76, 0x52, 0x7c, 0xa0, 0xee, 0x26, 0x1a, 0xa3, 0x0a, 0xe4, 0xc9, 0xf0, 0xf2, 0xed, 0x68, 0x7a, 0x3a, 0x83, 0x33, 0xc0, 0x05, 0xc7, 0x93, 0xaa, 0xb2, 0x08, 0x12, 0x55, 0x20, 0xe4, 0x0f, 0x14, 0x22, 0xc3, 0xc1, 0xcf, 0x1e, 0xcd, 0x70, 0xde, 0x64, 0x3c, 0xb7, 0xd0, 0x78, 0x50, 0x8c, 0xd7, 0x10, 0x9b, 0x5a, 0x6f, 0xf4, 0x82, 0x9e, 0xe8, 0x9e, 0xd6, 0xd3, 0x97, 0x3c, 0x4d, 0x63, 0xb5, 0x43, 0x47, 0x13, 0x4d, 0x9f, 0xd8, 0xb6, 0xee, 0xde, 0xe9, 0x44, 0xfd, 0xbe, 0x27, 0x66, 0xcb, 0x07, 0xda, 0x6a, 0x0b, 0x64, 0x92, 0xf0, 0xed, 0x17, 0x2f, 0x5e, 0xff, 0xdd, 0xef, 0xde, 0x6d, 0xac, 0x78, 0xf1, 0x8b, 0x9b, 0x4e, 0xaf, 0xf1, 0xf7, 0xb4, 0xdd, 0xd7, 0x39, 0xd4, 0x74, 0xfc, 0xd5, 0xe3, 0x7b, 0x5f, 0x3d, 0xd7, 0xab, 0xf7, 0x55, 0x7a, 0x86, 0xc2, 0xf4, 0xfe, 0xa3, 0x6f, 0xbc, 0x71, 0x14, 0xea, 0xc0, 0x73, 0x50, 0x7e, 0x7a, 0x2c, 0xbf, 0x20, 0xf1, 0x54, 0x8e, 0xfc, 0x5c, 0xb9, 0xf2, 0x73, 0x09, 0xe5, 0x87, 0xf8, 0xd7, 0xb0, 0xe5, 0x96, 0x7d, 0xcb, 0xdd, 0x4b, 0x89, 0x6e, 0xe5, 0x76, 0x58, 0x6a, 0xbe, 0x25, 0x9b, 0x2c, 0x16, 0x98, 0x4c, 0xab, 0xf5, 0x21, 0x61, 0x61, 0xce, 0xb3, 0xe5, 0x85, 0x15, 0x60, 0x65, 0x85, 0x42, 0x14, 0x50, 0x4a, 0x6d, 0x67, 0x2e, 0x7a, 0x9a, 0xc6, 0x6b, 0x87, 0x8e, 0xc5, 0x9b, 0x1e, 0xdf, 0x0a, 0xa5, 0x54, 0xd5, 0xb0, 0xf7, 0x93, 0xb3, 0x9b, 0x4e, 0x57, 0x4a, 0x25, 0x45, 0xc7, 0xa9, 0xf8, 0xfc, 0x85, 0xf7, 0x9a, 0x2a, 0x5e, 0xfb, 0xd2, 0xa6, 0x73, 0xeb, 0xc2, 0x3d, 0xed, 0xf7, 0x77, 0x0c, 0xaf, 0x3a, 0xf1, 0xf5, 0xe3, 0x7b, 0x5f, 0xbb, 0x30, 0xd0, 0xdd, 0x32, 0x14, 0x66, 0xae, 0x5f, 0xc0, 0xe7, 0xd7, 0xff, 0x06, 0xe5, 0x53, 0x99, 0x2b, 0x1f, 0x03, 0x52, 0x85, 0x3e, 0xf8, 0xac, 0x59, 0x39, 0xd1, 0x50, 0x4e, 0x8b, 0x6e, 0xd3, 0xf0, 0x36, 0x27, 0x2f, 0xdb, 0x07, 0x8b, 0xca, 0xb6, 0xa2, 0x94, 0x5c, 0x1f, 0x2c, 0x20, 0xa9, 0x1e, 0xcb, 0x07, 0x47, 0x6c, 0x3e, 0x50, 0x3e, 0x41, 0x7d, 0x0c, 0xec, 0x5a, 0x59, 0x3e, 0xaf, 0xdf, 0x78, 0x70, 0x79, 0xf1, 0x50, 0x7f, 0x39, 0x89, 0x8e, 0xec, 0x01, 0xb1, 0x1a, 0xca, 0xe7, 0x22, 0x73, 0x84, 0x68, 0x02, 0x06, 0xf6, 0x3d, 0xd4, 0x71, 0x20, 0x16, 0x95, 0x40, 0x1d, 0x81, 0x98, 0xa3, 0x58, 0x02, 0xcb, 0x38, 0x90, 0x08, 0xef, 0x70, 0x32, 0x29, 0xcb, 0xcb, 0x3f, 0x40, 0x9b, 0x09, 0xde, 0x27, 0xa7, 0x32, 0xfa, 0x70, 0x77, 0x8a, 0xd7, 0x91, 0x3b, 0x71, 0x81, 0x78, 0xe7, 0xdf, 0xda, 0x8d, 0xad, 0x2b, 0x8f, 0x09, 0x92, 0xf3, 0xb3, 0x1d, 0x44, 0xb0, 0x23, 0x06, 0x62, 0x2c, 0xd9, 0x29, 0x59, 0x49, 0x88, 0xa1, 0xd7, 0x8f, 0xf2, 0x8b, 0xf2, 0xfb, 0x11, 0x12, 0xb1, 0x48, 0xb2, 0x74, 0x37, 0x64, 0x8f, 0x2a, 0x3c, 0xfe, 0x40, 0x28, 0xe0, 0x0d, 0x79, 0xe1, 0xf6, 0xe7, 0x28, 0x5e, 0x0a, 0xf2, 0x96, 0x9f, 0x70, 0x63, 0xce, 0x81, 0xdb, 0xe0, 0x74, 0x05, 0xea, 0x4c, 0xa5, 0x54, 0xab, 0xd1, 0x78, 0xe3, 0xa9, 0xaa, 0xce, 0xdd, 0x5d, 0x81, 0x50, 0xf7, 0xf6, 0xe6, 0xa6, 0xc1, 0x84, 0x4f, 0x6f, 0x51, 0x56, 0x7b, 0xd7, 0x6d, 0xde, 0x51, 0xb5, 0xe1, 0xc9, 0xc3, 0xed, 0x4d, 0x47, 0xbe, 0xb0, 0x7f, 0xa2, 0xd7, 0x91, 0xdc, 0x92, 0x0a, 0x0f, 0x96, 0x1a, 0xc5, 0x56, 0xb9, 0x2a, 0xe2, 0x4e, 0x46, 0xa9, 0x13, 0x9d, 0x72, 0x65, 0x81, 0xa3, 0xa0, 0x72, 0xe3, 0xf9, 0xe1, 0xd1, 0xbb, 0x37, 0x94, 0xbb, 0x7c, 0x2e, 0x5d, 0x67, 0x71, 0x7d, 0x40, 0xd7, 0x7e, 0xe6, 0xda, 0xd1, 0xbd, 0x5f, 0xbb, 0xbb, 0xdf, 0xb6, 0xba, 0xf1, 0x96, 0x91, 0x38, 0xeb, 0xfd, 0x0c, 0xf5, 0x62, 0x5b, 0x74, 0x70, 0xe1, 0x6d, 0xea, 0xe7, 0xdc, 0x1c, 0xdf, 0x9e, 0x54, 0x78, 0x01, 0x41, 0xeb, 0x55, 0xac, 0xe7, 0xc0, 0x2f, 0x63, 0x8e, 0x4d, 0x6f, 0x26, 0x63, 0x92, 0xef, 0x4e, 0xf1, 0x29, 0x17, 0x6c, 0xfc, 0xd8, 0xb7, 0x64, 0x13, 0x6c, 0xfe, 0xf0, 0xa6, 0x3b, 0x0b, 0x44, 0xd0, 0x87, 0x7c, 0x5e, 0x76, 0x15, 0xf3, 0x9c, 0xd1, 0xec, 0x29, 0xbc, 0x7e, 0x31, 0x63, 0x34, 0xc6, 0x77, 0x59, 0xb2, 0xb3, 0xf4, 0x68, 0xe2, 0xfa, 0xc1, 0xb0, 0x4c, 0xdb, 0xfc, 0xe4, 0x4e, 0x5e, 0xe1, 0x8d, 0xdf, 0x19, 0x9b, 0xbf, 0x8f, 0x8a, 0x53, 0x64, 0x66, 0xa2, 0xb6, 0xa5, 0x7f, 0x19, 0x3c, 0xd0, 0x3b, 0xd1, 0x7a, 0x12, 0xce, 0xd5, 0xaf, 0x5f, 0x18, 0xa8, 0xaf, 0x02, 0x1f, 0xbb, 0x80, 0xde, 0xb1, 0x68, 0xe1, 0xcf, 0xd4, 0xaf, 0x19, 0x54, 0x63, 0xb6, 0x95, 0x98, 0x48, 0xca, 0xe2, 0x70, 0xdf, 0x6a, 0xc5, 0x59, 0x30, 0xdc, 0x99, 0x2e, 0x02, 0xd5, 0x12, 0xc8, 0x25, 0xe5, 0xd6, 0xd4, 0x8e, 0x9c, 0xa9, 0xb7, 0xd2, 0xe7, 0x68, 0xdc, 0x93, 0xd2, 0xe6, 0xc6, 0x32, 0x9f, 0x2d, 0xcc, 0xb0, 0x75, 0x44, 0x73, 0x8e, 0x71, 0x85, 0xac, 0x67, 0x22, 0x21, 0x67, 0x9b, 0x20, 0x30, 0x0c, 0x16, 0xaa, 0x67, 0xfb, 0xca, 0x0a, 0xdb, 0xb6, 0xb6, 0x37, 0x6e, 0xe9, 0x0e, 0x47, 0x7a, 0xa6, 0xd2, 0xbf, 0xa6, 0x2d, 0x05, 0xaa, 0xe6, 0x35, 0xe3, 0xe1, 0xb5, 0x6d, 0x89, 0xd1, 0x7d, 0x87, 0xf6, 0x8d, 0x26, 0x5a, 0x8e, 0x7e, 0x6e, 0xf7, 0xbe, 0xe7, 0x8f, 0x24, 0x9f, 0x7f, 0xa1, 0x74, 0x75, 0xbd, 0x37, 0x34, 0x7c, 0x6a, 0xdc, 0x3b, 0xb2, 0x61, 0x22, 0x48, 0xb6, 0x1b, 0x92, 0x13, 0xc7, 0xd6, 0xac, 0x3a, 0xb4, 0x3e, 0x5e, 0xbc, 0x7a, 0x7f, 0x57, 0xff, 0xe1, 0x0d, 0xa9, 0x74, 0xb1, 0x55, 0xa7, 0xd6, 0xaa, 0x63, 0xbe, 0xc2, 0xda, 0xba, 0xc6, 0x9e, 0x89, 0x5b, 0x3f, 0xbc, 0x71, 0xcb, 0xb3, 0x47, 0xda, 0x3a, 0x6f, 0x7f, 0x76, 0xe6, 0xa9, 0xdf, 0x37, 0x59, 0xab, 0x5b, 0xfa, 0x2b, 0x2a, 0x47, 0x9b, 0xfd, 0x22, 0x99, 0x4a, 0xca, 0x72, 0x86, 0x1d, 0x4c, 0x1f, 0x20, 0x1f, 0x65, 0x5e, 0x20, 0xa2, 0xc4, 0x3f, 0x73, 0x6b, 0x18, 0x9d, 0x80, 0x8a, 0x0b, 0xa1, 0x7c, 0xcc, 0x28, 0x52, 0x8c, 0xd6, 0xb0, 0xf0, 0x0e, 0x83, 0xd7, 0x30, 0xef, 0x28, 0x62, 0x53, 0x17, 0x8b, 0x03, 0x8c, 0x22, 0x5c, 0xca, 0x2c, 0x36, 0xad, 0xa8, 0x71, 0x28, 0xa4, 0x39, 0x4a, 0xe8, 0x28, 0xae, 0xd0, 0x70, 0x7b, 0x46, 0x0d, 0x3a, 0xe1, 0x3d, 0xc4, 0xce, 0xbe, 0x71, 0xa9, 0x0e, 0x24, 0x2e, 0xf7, 0x03, 0x8d, 0x6d, 0x92, 0xc6, 0xe1, 0x38, 0xf8, 0x24, 0xcb, 0xb4, 0xc3, 0x9e, 0xa1, 0xdf, 0xe2, 0xf1, 0xfb, 0xb5, 0xc6, 0x18, 0x97, 0xf9, 0xc7, 0x55, 0x71, 0xe6, 0x21, 0x5f, 0x39, 0x9c, 0x99, 0x41, 0x14, 0xd7, 0x8e, 0x71, 0xeb, 0x91, 0xfc, 0x30, 0x50, 0x99, 0x3d, 0xfa, 0xd2, 0xee, 0x4a, 0x3b, 0x70, 0xa4, 0xff, 0x2b, 0xbc, 0xf6, 0xc4, 0x68, 0xf9, 0x6a, 0xb8, 0xc4, 0xcc, 0x2a, 0x55, 0xc8, 0x3f, 0x30, 0xb4, 0xdd, 0xec, 0xf8, 0x58, 0xc5, 0xc4, 0xc9, 0xc1, 0xb6, 0x5a, 0xa6, 0x41, 0xa9, 0x14, 0x01, 0x53, 0xe5, 0x50, 0xd3, 0xf0, 0x85, 0x1b, 0xf7, 0xaf, 0x39, 0xbf, 0x39, 0xa1, 0xd5, 0x0f, 0xc3, 0x75, 0x35, 0xbd, 0x95, 0x3a, 0xa5, 0x64, 0xcc, 0xa6, 0x81, 0xf1, 0x75, 0x67, 0x37, 0x94, 0x1b, 0x90, 0x9e, 0x3c, 0xb7, 0xf0, 0xa6, 0x28, 0x02, 0xd7, 0x58, 0x80, 0x38, 0xce, 0xea, 0x31, 0x99, 0x19, 0x1a, 0xa4, 0x3a, 0xc0, 0xb0, 0x71, 0x14, 0x78, 0x21, 0x62, 0x2f, 0xb8, 0x13, 0x29, 0x07, 0xca, 0x30, 0x65, 0x31, 0x61, 0xb8, 0xd4, 0x0a, 0x47, 0x7e, 0x49, 0xb2, 0x87, 0x70, 0x48, 0x0c, 0xe8, 0x73, 0xe4, 0x3c, 0xcf, 0x66, 0x90, 0x63, 0xfc, 0x82, 0x43, 0x0d, 0xd9, 0x20, 0x3e, 0x1b, 0x40, 0x81, 0x3e, 0x50, 0xc0, 0xab, 0xd5, 0x07, 0xd9, 0xaa, 0x24, 0xd9, 0x6a, 0x05, 0xd9, 0x54, 0x1f, 0x2d, 0x8f, 0x27, 0x30, 0x60, 0x69, 0x88, 0x22, 0x65, 0x1b, 0x2f, 0xcd, 0xcc, 0x5e, 0xda, 0x58, 0x36, 0xbd, 0x25, 0x75, 0xb2, 0xfa, 0xda, 0xb5, 0xe4, 0xa3, 0x33, 0x88, 0xc7, 0xfe, 0x1b, 0x9b, 0xa6, 0xa9, 0x77, 0xe6, 0x0f, 0x0f, 0x5f, 0x98, 0xa9, 0x46, 0x94, 0xf5, 0xc0, 0xb4, 0xeb, 0x70, 0x47, 0xc3, 0xbc, 0x99, 0xb9, 0x3e, 0xff, 0x42, 0xf3, 0x6a, 0x74, 0x27, 0xfd, 0xbb, 0x3f, 0x1c, 0x3c, 0x48, 0xde, 0x85, 0xec, 0x0a, 0x68, 0x7b, 0xeb, 0x69, 0x39, 0x21, 0x27, 0x3c, 0xc4, 0xaa, 0x97, 0x6c, 0x72, 0x64, 0xfc, 0xf2, 0x08, 0x1f, 0x6d, 0x26, 0xf0, 0xb8, 0x3b, 0xc5, 0x73, 0x87, 0xa0, 0xf3, 0x27, 0x6c, 0x50, 0xe1, 0xe8, 0x1a, 0x7f, 0x7f, 0xf4, 0x45, 0x8f, 0xdd, 0x1f, 0xc4, 0x21, 0x49, 0x1c, 0x22, 0xc3, 0x91, 0xf6, 0xcc, 0x76, 0xc6, 0x45, 0x23, 0xcd, 0xa0, 0x67, 0xdb, 0x97, 0xcf, 0xaf, 0x3e, 0xb2, 0x65, 0xe4, 0x9e, 0x2a, 0x59, 0xcb, 0xf3, 0xb7, 0xf4, 0x9e, 0xde, 0x54, 0xd5, 0xb0, 0xf5, 0x9e, 0x8f, 0x3d, 0xb5, 0xfa, 0xda, 0x81, 0xb0, 0x6c, 0x6b, 0xd1, 0xae, 0xd3, 0x9f, 0x98, 0x3c, 0x70, 0xb0, 0xa7, 0xfd, 0x74, 0xef, 0x64, 0x74, 0xfc, 0xdc, 0xc8, 0xc4, 0x93, 0x17, 0x0e, 0x4f, 0xf7, 0x3b, 0xd2, 0xff, 0xc9, 0xf4, 0x06, 0x59, 0x3b, 0xf4, 0x1c, 0x5c, 0xd3, 0xf3, 0xcc, 0xd3, 0x44, 0x84, 0x38, 0xc6, 0xc5, 0xb8, 0x7c, 0x40, 0xc4, 0x28, 0x38, 0xfb, 0x07, 0x5e, 0x88, 0xb9, 0x0b, 0x2e, 0xc6, 0x95, 0x61, 0x99, 0x47, 0xe2, 0xcf, 0xa2, 0xf7, 0x66, 0x79, 0x57, 0x1d, 0x57, 0x58, 0x16, 0xc3, 0x6d, 0x10, 0xcc, 0x65, 0x48, 0x7c, 0xc4, 0xe2, 0x25, 0x1a, 0xa2, 0xe8, 0x05, 0x9c, 0xa1, 0xfe, 0xa0, 0xdf, 0x8d, 0xe7, 0x68, 0xd6, 0x2d, 0x12, 0x71, 0x60, 0xac, 0x40, 0xc6, 0xa3, 0x5f, 0xb4, 0x47, 0x80, 0x9e, 0x88, 0xd1, 0xae, 0x06, 0x51, 0x55, 0xa1, 0xf5, 0xf9, 0xf4, 0xc3, 0x0e, 0x8f, 0xde, 0xa1, 0x02, 0xc7, 0x3f, 0x67, 0xf1, 0xaa, 0xd2, 0xdf, 0x54, 0x3b, 0x9d, 0x95, 0xd7, 0xc2, 0x6b, 0xef, 0x1c, 0xad, 0xe8, 0xcf, 0x4e, 0x5b, 0x72, 0x46, 0xa1, 0x03, 0xfd, 0x7a, 0x7d, 0xfa, 0x90, 0xdf, 0xa0, 0x2c, 0x34, 0xa4, 0x95, 0x1a, 0x23, 0xf8, 0x88, 0x59, 0x99, 0x76, 0xd1, 0x09, 0xe1, 0xcc, 0x85, 0xf2, 0x90, 0x2e, 0xfc, 0x85, 0xd6, 0x42, 0x79, 0x54, 0x12, 0x77, 0x26, 0x75, 0x85, 0x70, 0xcf, 0x8e, 0x96, 0x45, 0x4a, 0x51, 0xf0, 0x41, 0x29, 0xcd, 0x6a, 0x4c, 0x33, 0x5f, 0x74, 0x7c, 0x07, 0x5e, 0xc6, 0x38, 0xcd, 0x88, 0x23, 0x52, 0x2e, 0x59, 0xee, 0x63, 0x4c, 0x9b, 0x6c, 0xcb, 0x7e, 0x82, 0xbd, 0x2f, 0xf6, 0xe3, 0x0c, 0x55, 0x32, 0xc2, 0x31, 0x55, 0x12, 0x31, 0x7f, 0x51, 0x84, 0xf5, 0xc3, 0x30, 0x13, 0x0f, 0x54, 0x91, 0xa8, 0x7c, 0x2f, 0xaa, 0x4f, 0x8e, 0xd2, 0x96, 0x30, 0x2b, 0x43, 0x10, 0x79, 0x93, 0xbc, 0x3e, 0x75, 0x81, 0x2a, 0xb3, 0x18, 0x25, 0xb4, 0xd2, 0xda, 0x78, 0x95, 0xaf, 0x6b, 0x55, 0xad, 0xf1, 0x29, 0x89, 0x9c, 0x66, 0x94, 0x92, 0x7f, 0x13, 0xd9, 0x0b, 0xae, 0x80, 0x73, 0x85, 0x2f, 0x87, 0xbf, 0x6c, 0x91, 0x5b, 0x7d, 0xe5, 0x85, 0x5d, 0x72, 0x7b, 0x99, 0x3f, 0xdc, 0xdf, 0x56, 0xad, 0x77, 0x7d, 0x35, 0xfc, 0x15, 0x50, 0x61, 0x6c, 0xac, 0x3d, 0xdb, 0x1f, 0xb6, 0x69, 0x4a, 0x7a, 0xea, 0xc1, 0x37, 0xcc, 0x45, 0x06, 0x43, 0x91, 0x71, 0xfe, 0xd3, 0x6a, 0x0b, 0xf5, 0xc8, 0x44, 0xeb, 0x81, 0xd6, 0x56, 0x67, 0xd8, 0x22, 0xeb, 0x52, 0xdb, 0x0c, 0x72, 0x4d, 0xa8, 0xb5, 0x72, 0x6d, 0xfb, 0x81, 0xb6, 0x1b, 0x5f, 0xab, 0xa9, 0xc0, 0x35, 0x38, 0xa0, 0x6f, 0xfc, 0x0a, 0x2d, 0x81, 0x7b, 0x66, 0x25, 0xdc, 0x4f, 0xe4, 0x1e, 0x68, 0x22, 0x54, 0xe2, 0x44, 0xbf, 0x6c, 0x0d, 0x0e, 0xde, 0x80, 0xdb, 0x9d, 0x45, 0x11, 0xa2, 0xb5, 0xb9, 0x95, 0x61, 0x6b, 0x70, 0x2c, 0xfe, 0x78, 0x0f, 0xf7, 0x31, 0xdc, 0x26, 0xfd, 0x46, 0x5f, 0x28, 0xec, 0xcb, 0xd9, 0x26, 0xd9, 0x73, 0x82, 0xaa, 0x45, 0x25, 0x37, 0x72, 0x62, 0xe0, 0x22, 0x31, 0xa5, 0xb3, 0x6f, 0x5d, 0xfb, 0xe0, 0x7d, 0xf7, 0x3d, 0xd8, 0x39, 0x54, 0xaa, 0xba, 0x5e, 0xba, 0xf9, 0xa3, 0x99, 0xba, 0x1b, 0x33, 0x4f, 0x27, 0xab, 0x4f, 0xac, 0x49, 0xed, 0xee, 0xf2, 0x5e, 0x57, 0xba, 0x13, 0xe1, 0xa2, 0xee, 0x42, 0x4a, 0x56, 0x5c, 0x8b, 0xca, 0x6f, 0x94, 0x34, 0x77, 0xd8, 0x40, 0xfd, 0xed, 0xdb, 0x5a, 0xf9, 0xca, 0x1b, 0x8d, 0xf5, 0x75, 0x4d, 0x08, 0xf7, 0x73, 0x43, 0x45, 0xbd, 0x8d, 0x4a, 0x8f, 0xfb, 0x1d, 0x04, 0xbf, 0xa6, 0xe1, 0xbc, 0x40, 0xef, 0x7c, 0x8d, 0x5d, 0x0a, 0x9a, 0x22, 0xb8, 0x52, 0xb2, 0x76, 0x30, 0xda, 0x2a, 0xe0, 0x2d, 0x71, 0xce, 0x2d, 0xde, 0xe7, 0xca, 0x5d, 0x2d, 0x42, 0x33, 0x97, 0x85, 0x4f, 0xd1, 0xf4, 0xd6, 0x8c, 0xb9, 0xb6, 0x72, 0xe3, 0x3d, 0x7c, 0xe3, 0x64, 0x09, 0x4a, 0xfe, 0x86, 0x0a, 0x72, 0x2e, 0x67, 0x81, 0x2d, 0xd3, 0x7c, 0x94, 0x5d, 0x6f, 0x7a, 0x64, 0x26, 0xe3, 0x9c, 0x85, 0x95, 0xcc, 0xe4, 0xa5, 0x97, 0xdc, 0x32, 0x26, 0xf3, 0xc4, 0x1d, 0xb1, 0x45, 0xeb, 0x6d, 0x69, 0xe3, 0xb9, 0xb5, 0x3e, 0x5d, 0xc3, 0xdc, 0x9d, 0xb3, 0xe0, 0xb0, 0x0e, 0x6a, 0x5d, 0x78, 0x93, 0x72, 0xc3, 0xf9, 0xd4, 0x80, 0xd0, 0x46, 0x4e, 0x68, 0x32, 0x35, 0x40, 0x2b, 0x0c, 0x1d, 0x81, 0xa0, 0x4d, 0x01, 0x6d, 0x76, 0x34, 0x60, 0x30, 0xef, 0x2a, 0x57, 0xb3, 0x85, 0x87, 0x24, 0xc0, 0x15, 0x57, 0x53, 0x1d, 0x8f, 0x15, 0xba, 0xed, 0x56, 0xb3, 0x11, 0x1a, 0xff, 0x46, 0x2e, 0x13, 0x23, 0x0f, 0xac, 0x53, 0x06, 0x84, 0xc0, 0x1e, 0xde, 0x0e, 0x35, 0xc1, 0x01, 0x72, 0xd7, 0x7f, 0xf1, 0xc0, 0xe8, 0xf9, 0xc9, 0x8a, 0x48, 0x6a, 0xc3, 0xf4, 0x86, 0x54, 0xc4, 0x5a, 0xde, 0x51, 0x52, 0xd1, 0x99, 0x08, 0x6a, 0x2d, 0xaa, 0x92, 0xda, 0xcb, 0x3b, 0x87, 0x4e, 0x8f, 0x45, 0x23, 0xa9, 0x8d, 0x33, 0x1b, 0x53, 0x11, 0x4b, 0x45, 0x4f, 0xbc, 0xb6, 0xbf, 0x2a, 0x6c, 0xd0, 0x9a, 0x25, 0x91, 0x9d, 0x89, 0xf6, 0xe2, 0xbe, 0x1d, 0xcd, 0xc9, 0xf1, 0xd6, 0x68, 0xc0, 0x5b, 0x54, 0x19, 0xb0, 0x97, 0xfb, 0x0c, 0x26, 0xab, 0xc9, 0xd2, 0x51, 0x9d, 0x2c, 0x4a, 0x6d, 0x6d, 0x6c, 0x1a, 0x6d, 0x8e, 0xf8, 0xbd, 0x25, 0x35, 0x61, 0x47, 0x3c, 0x5c, 0x60, 0xf3, 0xd8, 0xd5, 0xaa, 0x14, 0xaa, 0x79, 0x9e, 0xbe, 0x44, 0x5e, 0x67, 0x1a, 0xe0, 0xec, 0xb9, 0xcc, 0x8e, 0xb2, 0x4a, 0x89, 0xf3, 0x40, 0x09, 0x06, 0x4e, 0x19, 0x09, 0xe6, 0x8e, 0x16, 0xdc, 0x90, 0x52, 0xbc, 0xbe, 0x8d, 0xb0, 0x19, 0x9c, 0x22, 0x28, 0x0a, 0x11, 0x33, 0x87, 0x52, 0x33, 0x70, 0x0c, 0x67, 0x36, 0x7b, 0x5c, 0x8d, 0x4f, 0xf0, 0xb0, 0x19, 0x30, 0x27, 0x86, 0xb3, 0x22, 0x9e, 0xd3, 0x81, 0x90, 0x10, 0x52, 0x42, 0x22, 0x5d, 0xba, 0x23, 0x21, 0x95, 0xf2, 0xfd, 0xa0, 0x53, 0x5e, 0x11, 0xf5, 0xfb, 0xbd, 0x5e, 0xad, 0x36, 0x64, 0xc4, 0xc7, 0x76, 0x7e, 0x96, 0x67, 0x97, 0x85, 0x5b, 0x23, 0x3e, 0xd8, 0x3a, 0x0e, 0x32, 0x8e, 0xd7, 0x22, 0x0f, 0xf7, 0x15, 0x1a, 0x12, 0xa4, 0xb5, 0xac, 0x3a, 0xb1, 0xae, 0xa1, 0xf0, 0x80, 0xd6, 0x72, 0xb9, 0x84, 0x32, 0xeb, 0xa6, 0x81, 0x3b, 0xfd, 0xcb, 0xda, 0x32, 0x5b, 0xd0, 0x66, 0x90, 0x0e, 0x3b, 0x9a, 0xa1, 0xad, 0xde, 0x53, 0xaa, 0x66, 0x6d, 0xf5, 0xfa, 0xb8, 0xc8, 0xe4, 0x6b, 0x9d, 0x6a, 0x22, 0x37, 0x1a, 0x8d, 0xfd, 0x66, 0x50, 0xa6, 0xd5, 0xa7, 0x9f, 0xbe, 0x30, 0xff, 0x6c, 0x7d, 0x9d, 0x4a, 0xab, 0xfa, 0x7d, 0xd3, 0x2d, 0x23, 0x95, 0x5a, 0x43, 0x7f, 0x81, 0x4a, 0x35, 0x30, 0xc8, 0xd6, 0x8b, 0x5f, 0x78, 0x93, 0xfc, 0x3a, 0xf3, 0x0c, 0xdc, 0xa3, 0x3e, 0xcc, 0xc9, 0x4e, 0x0b, 0xdd, 0x11, 0x29, 0xf4, 0x42, 0x4a, 0x00, 0x21, 0xc6, 0xb2, 0xd3, 0x42, 0xeb, 0x2c, 0x7b, 0x83, 0x93, 0x5d, 0x21, 0x52, 0x31, 0xdc, 0xa1, 0x2f, 0xb2, 0x8d, 0xb0, 0xab, 0x3d, 0x8b, 0x8e, 0x2b, 0x71, 0x69, 0x2b, 0xd6, 0x98, 0x08, 0x43, 0x8b, 0x8b, 0xa1, 0x29, 0x66, 0x8e, 0x67, 0xf3, 0x5d, 0xb6, 0x2d, 0x0e, 0x60, 0xfa, 0x0a, 0x5d, 0x4e, 0x87, 0xbd, 0xc0, 0x6c, 0x32, 0xa8, 0x51, 0xbe, 0x69, 0x04, 0x44, 0xa4, 0x18, 0x04, 0x14, 0xe0, 0x24, 0xd5, 0x94, 0x05, 0x8b, 0xb0, 0x0b, 0x87, 0xb3, 0xba, 0x84, 0xcc, 0x79, 0xe0, 0x5b, 0x47, 0x4e, 0x1f, 0xd4, 0x9a, 0x2f, 0x7b, 0x75, 0x27, 0x76, 0x96, 0x75, 0x06, 0xd5, 0xe8, 0xac, 0xc6, 0xef, 0x6d, 0xac, 0x46, 0xf2, 0xaa, 0x8b, 0xd8, 0x82, 0x76, 0x83, 0x44, 0x26, 0x17, 0xd5, 0x46, 0x4f, 0x69, 0xce, 0xdf, 0xc1, 0x4a, 0xe8, 0xe7, 0x07, 0xe7, 0x34, 0x9a, 0x7e, 0xb8, 0x64, 0xd6, 0x0c, 0x5f, 0xe4, 0xe5, 0x24, 0x95, 0x4d, 0x96, 0x44, 0xb1, 0x7c, 0x34, 0x50, 0x37, 0x5d, 0x86, 0xeb, 0xc7, 0x47, 0x7c, 0x84, 0xe3, 0xad, 0x93, 0xc0, 0x1d, 0xc7, 0x63, 0xc1, 0x58, 0x26, 0x4e, 0xa9, 0xa0, 0x5b, 0x34, 0x7f, 0x0b, 0x5d, 0x91, 0xfc, 0x15, 0x5f, 0x53, 0x41, 0xc7, 0x1e, 0x54, 0xd1, 0x3c, 0xc5, 0xf8, 0xd6, 0x8c, 0x5d, 0x8a, 0x10, 0x42, 0xb3, 0xd9, 0x73, 0xac, 0xcc, 0xa7, 0xc8, 0x33, 0x40, 0x89, 0xa3, 0x98, 0x7b, 0x7c, 0x36, 0x73, 0x78, 0xc5, 0x7f, 0x3e, 0x3a, 0xfa, 0x52, 0xa1, 0xdf, 0xe3, 0xcf, 0x9e, 0x57, 0x0a, 0xa4, 0x22, 0x66, 0xb9, 0x7f, 0x33, 0x20, 0x9a, 0x46, 0x50, 0x45, 0xbe, 0xa7, 0x2a, 0x6d, 0x19, 0xa9, 0xc9, 0x0a, 0xc4, 0x1d, 0xd7, 0xa7, 0x9d, 0x52, 0xa7, 0xe3, 0x81, 0x2f, 0xa7, 0x37, 0x5c, 0x29, 0xb0, 0x89, 0x00, 0x29, 0x55, 0x89, 0x24, 0x1a, 0x09, 0xb5, 0x3a, 0xb0, 0xaa, 0xc2, 0xa1, 0xd6, 0x22, 0x79, 0xd8, 0x4b, 0xde, 0x7f, 0xdc, 0x64, 0x01, 0x0f, 0x51, 0x5f, 0xb4, 0xa8, 0xe7, 0x3f, 0x66, 0x8e, 0x5a, 0x2c, 0x51, 0x0b, 0x96, 0x49, 0x1d, 0xd4, 0x7f, 0xab, 0xa9, 0x77, 0x08, 0x33, 0xc2, 0xd3, 0x28, 0x96, 0xc1, 0xd3, 0xcc, 0x2e, 0x81, 0xa7, 0x31, 0xb0, 0x78, 0x1a, 0xb8, 0x99, 0xc4, 0x8c, 0x1e, 0x8a, 0x03, 0xd4, 0xe8, 0xbd, 0x71, 0x8f, 0x1e, 0x79, 0x64, 0xab, 0xd5, 0x8a, 0x6f, 0x7c, 0x2b, 0xfd, 0xc3, 0xff, 0x22, 0x9d, 0x37, 0xe6, 0x55, 0xca, 0xdf, 0xfd, 0x1a, 0x74, 0xff, 0xc3, 0xfc, 0x1f, 0xc8, 0xbf, 0x52, 0x97, 0x8d, 0x21, 0x55, 0xfa, 0xfd, 0xb3, 0xe9, 0xd7, 0x1f, 0x1e, 0x54, 0x05, 0xb5, 0x20, 0x71, 0x16, 0x48, 0x1f, 0x66, 0xe3, 0x87, 0xe9, 0x0f, 0x53, 0xe7, 0xe0, 0x73, 0x94, 0x22, 0x5c, 0xa2, 0x15, 0x65, 0x61, 0x77, 0x43, 0xdd, 0x86, 0xd0, 0x33, 0x73, 0xd0, 0x9d, 0x26, 0x69, 0x11, 0x4a, 0x0f, 0x66, 0xf3, 0x7c, 0xb8, 0x3a, 0x45, 0x04, 0xa6, 0xfa, 0xb1, 0x40, 0x37, 0xda, 0xa7, 0xc5, 0x05, 0x3b, 0xf5, 0x28, 0xa0, 0x63, 0xca, 0x85, 0x9c, 0xe4, 0xfa, 0xd1, 0x08, 0x9a, 0x12, 0x3f, 0x4a, 0xfe, 0x34, 0xad, 0x03, 0x3d, 0xd6, 0x17, 0xba, 0xb6, 0xb7, 0xba, 0xc3, 0xbd, 0xbb, 0x5b, 0xeb, 0xfa, 0x6b, 0xc2, 0x06, 0x8f, 0xb6, 0xd4, 0xd9, 0x31, 0x34, 0xd3, 0x70, 0xe8, 0x8b, 0x47, 0x93, 0xe5, 0xb3, 0x8f, 0x6e, 0x4f, 0x5f, 0xd7, 0x98, 0xa8, 0x77, 0x1e, 0x4e, 0x6f, 0x35, 0x7a, 0x9a, 0x02, 0x6d, 0x9b, 0x6a, 0x5b, 0x77, 0xa6, 0x82, 0x05, 0xae, 0x02, 0x47, 0xca, 0x16, 0xf1, 0xe8, 0xda, 0x6f, 0xfb, 0xd4, 0xe4, 0xea, 0xbb, 0xa6, 0x1a, 0x44, 0xe0, 0x36, 0x6d, 0x31, 0x5b, 0x63, 0xc2, 0x9b, 0x7e, 0x88, 0xf2, 0xc2, 0xe7, 0xaf, 0x24, 0xca, 0x92, 0x25, 0x0e, 0x44, 0x22, 0x81, 0x8e, 0xce, 0x51, 0x54, 0x60, 0x8a, 0x3f, 0xc1, 0x9c, 0xcd, 0x6c, 0x3b, 0x73, 0x64, 0x8f, 0xd6, 0x50, 0x54, 0x14, 0x31, 0x70, 0x94, 0x2f, 0x62, 0x06, 0xf9, 0x79, 0x22, 0xb1, 0x80, 0x84, 0x9d, 0x8b, 0xe7, 0xb3, 0x74, 0xab, 0x00, 0x07, 0xfd, 0x39, 0x66, 0x64, 0x0a, 0x98, 0xb4, 0xe9, 0xf7, 0x46, 0x6f, 0x1f, 0x08, 0x7c, 0xeb, 0xdb, 0xb5, 0xdd, 0xc5, 0x9a, 0xee, 0x4e, 0xb3, 0xc7, 0xa2, 0x15, 0x29, 0xac, 0x62, 0x73, 0xe9, 0x40, 0xd5, 0x43, 0x9f, 0x7a, 0xec, 0x89, 0xb3, 0xb7, 0xed, 0xd8, 0xf3, 0x29, 0x2b, 0xf0, 0xa5, 0x45, 0xe4, 0x4f, 0xa9, 0xcb, 0xd6, 0x88, 0xea, 0x4f, 0xa2, 0x48, 0xf7, 0x74, 0xc3, 0x03, 0x9f, 0x33, 0x8b, 0x0c, 0x36, 0x97, 0x2e, 0x5c, 0x41, 0x8b, 0x68, 0x95, 0x65, 0xd4, 0xa0, 0x3d, 0x7a, 0xcb, 0xae, 0xbb, 0xb4, 0xca, 0x8d, 0xbd, 0x5d, 0x6b, 0x1b, 0x0b, 0x0d, 0x40, 0xfe, 0x30, 0x7c, 0x8f, 0x9a, 0x85, 0xbf, 0x50, 0x65, 0xf0, 0x3d, 0x12, 0xc4, 0x81, 0xa4, 0x2a, 0x08, 0xfd, 0xac, 0x00, 0x5c, 0x01, 0x1a, 0xcc, 0xd2, 0xcf, 0x6e, 0xd1, 0x81, 0x4c, 0x6d, 0x15, 0x02, 0x21, 0x04, 0xe8, 0x29, 0x94, 0x19, 0x96, 0x47, 0xa3, 0x8f, 0x31, 0x11, 0x37, 0xd3, 0x10, 0xf1, 0xed, 0x27, 0xe5, 0x5e, 0xfd, 0xda, 0x80, 0xdf, 0xcf, 0x9d, 0xce, 0x2d, 0x81, 0xa3, 0xc9, 0x04, 0xb6, 0x03, 0x02, 0x14, 0x0d, 0x82, 0x94, 0x20, 0xbb, 0xcf, 0x03, 0xfa, 0xec, 0x55, 0xf6, 0x89, 0xf3, 0x13, 0x91, 0xe4, 0xce, 0xfb, 0xfa, 0xb7, 0x7d, 0xa5, 0xbd, 0x46, 0xa6, 0x91, 0x68, 0xd5, 0x8a, 0x92, 0x86, 0xde, 0x48, 0xd5, 0x40, 0xdc, 0x7a, 0x68, 0x57, 0x68, 0x24, 0x70, 0xcb, 0x01, 0x99, 0x5e, 0xee, 0x0d, 0x7b, 0xc9, 0x17, 0x2b, 0x6a, 0xd3, 0x6f, 0x91, 0x07, 0x69, 0xba, 0xb4, 0x7f, 0x4f, 0x0b, 0xdc, 0x8c, 0x23, 0x03, 0xc3, 0x8d, 0x0a, 0xb1, 0x5a, 0xee, 0x76, 0x1a, 0x5d, 0x95, 0x6d, 0x81, 0xa1, 0x49, 0xa5, 0xd2, 0x0d, 0x1f, 0x3f, 0x64, 0x1c, 0x6d, 0x0d, 0x60, 0x1b, 0xae, 0x7b, 0xe1, 0x6d, 0xea, 0x0d, 0xa8, 0x33, 0x24, 0x70, 0x66, 0xee, 0xe6, 0x25, 0xb0, 0x5c, 0xa2, 0xd8, 0x6e, 0x61, 0xa2, 0x98, 0xed, 0xa6, 0x1a, 0xe2, 0x88, 0x07, 0xf4, 0xfe, 0x4a, 0x8a, 0x82, 0x7e, 0x8f, 0xab, 0xc0, 0xac, 0x56, 0xc2, 0x3f, 0x25, 0xc1, 0xf9, 0x56, 0x7a, 0x2e, 0xea, 0xc1, 0x63, 0x18, 0x32, 0x56, 0x5d, 0x82, 0x4b, 0x28, 0x83, 0x97, 0xc1, 0x77, 0x3d, 0xc9, 0x89, 0xba, 0x23, 0x17, 0x74, 0xf3, 0x29, 0xdf, 0xe4, 0xf6, 0xbd, 0x55, 0xdb, 0x5e, 0x38, 0xd9, 0xd5, 0x72, 0xe4, 0xb9, 0xb9, 0x89, 0x8f, 0xee, 0x4b, 0xea, 0x6d, 0xe4, 0x23, 0x54, 0x64, 0x60, 0xae, 0xad, 0x79, 0xb2, 0x2d, 0xaa, 0xb3, 0x28, 0xc1, 0xbd, 0xb5, 0x9b, 0x3b, 0x43, 0x17, 0x8e, 0x83, 0x9f, 0x17, 0x35, 0x04, 0x74, 0x1d, 0xe7, 0x5f, 0x3f, 0xb5, 0xed, 0x8b, 0x77, 0xf5, 0x35, 0xec, 0xff, 0xcc, 0xae, 0x88, 0x6b, 0xec, 0xec, 0x68, 0x89, 0xd3, 0xeb, 0x44, 0x09, 0xd6, 0x34, 0x41, 0xa4, 0x2f, 0x41, 0x53, 0x08, 0xee, 0x87, 0x84, 0x9e, 0xf0, 0x12, 0x9b, 0x81, 0x3c, 0xa9, 0xee, 0x86, 0x96, 0xc5, 0x50, 0x1d, 0xa9, 0x60, 0xe2, 0x80, 0x54, 0x52, 0x9c, 0x8f, 0x56, 0x03, 0xdd, 0x6b, 0x85, 0x92, 0x51, 0x4c, 0x11, 0x4a, 0xb8, 0x99, 0x2a, 0xc9, 0x29, 0x95, 0x54, 0x43, 0x51, 0x12, 0x91, 0x1a, 0x4d, 0x7b, 0xb0, 0x99, 0x90, 0xcb, 0x67, 0x53, 0x70, 0xc3, 0xa1, 0x65, 0x63, 0x84, 0x4c, 0x86, 0x5f, 0x39, 0xb3, 0xfd, 0xa6, 0x3e, 0x1f, 0x82, 0x5f, 0x50, 0x4f, 0xc8, 0x65, 0xb4, 0x4c, 0x4e, 0xcf, 0xad, 0xfc, 0x45, 0xcb, 0x7c, 0x47, 0xb2, 0x65, 0xa9, 0xee, 0x52, 0xd8, 0x1d, 0xce, 0x57, 0x35, 0x40, 0x9c, 0x37, 0x9b, 0x55, 0x40, 0x2a, 0x01, 0x22, 0x42, 0x2a, 0xda, 0xb4, 0xf4, 0x97, 0x40, 0x73, 0xaf, 0xc4, 0x68, 0x90, 0x4a, 0x09, 0x62, 0xf3, 0xa6, 0xf1, 0xb1, 0x35, 0x03, 0xed, 0x6d, 0xad, 0x2d, 0x15, 0xe5, 0xd1, 0xb2, 0xe2, 0xb0, 0xdf, 0xe7, 0xb0, 0x19, 0xbc, 0x46, 0xaf, 0x54, 0x2f, 0xd5, 0x73, 0x83, 0xa2, 0x43, 0xcc, 0x68, 0x68, 0x2e, 0x26, 0x9a, 0x40, 0x84, 0x41, 0x9e, 0x06, 0x28, 0x14, 0x65, 0x21, 0xac, 0x01, 0xe0, 0xe5, 0x32, 0x49, 0xbc, 0xf1, 0x18, 0x54, 0xd4, 0x98, 0xa9, 0xb8, 0x8a, 0x83, 0x15, 0x57, 0x98, 0x3d, 0x88, 0xbd, 0xd8, 0x09, 0xb2, 0xfc, 0x62, 0xb0, 0xa7, 0xc8, 0x1c, 0x03, 0x06, 0x71, 0xa8, 0x75, 0x53, 0xf2, 0xfc, 0x03, 0x0a, 0x92, 0x0a, 0xb5, 0x0c, 0x6f, 0x9c, 0x29, 0xab, 0xe8, 0x92, 0x03, 0x66, 0xcb, 0x0e, 0x5b, 0xbc, 0x3f, 0x56, 0xd9, 0x1f, 0xb7, 0xed, 0x00, 0xbf, 0xb9, 0xd5, 0x38, 0x78, 0xe0, 0x43, 0xeb, 0xb7, 0x7e, 0x6c, 0x77, 0x93, 0x64, 0xdb, 0x71, 0xf3, 0x9a, 0xda, 0x8a, 0x86, 0xe6, 0xe6, 0xd4, 0x70, 0xed, 0x96, 0xfb, 0x86, 0x4e, 0x6d, 0x00, 0xd6, 0xd2, 0x5d, 0x2a, 0x4d, 0xed, 0xec, 0xbd, 0x6b, 0xbd, 0xcd, 0x4e, 0xa7, 0xb3, 0x7a, 0xfc, 0xe0, 0xb1, 0xc3, 0x53, 0xcd, 0xa2, 0xa9, 0x03, 0x88, 0x60, 0xba, 0xb9, 0x76, 0xbb, 0xcd, 0x6b, 0x90, 0x8b, 0xc4, 0x22, 0x43, 0x8d, 0x97, 0x3a, 0xb7, 0x6d, 0xa4, 0xb8, 0x23, 0xe6, 0xb0, 0xc7, 0x3a, 0x8b, 0x47, 0xb6, 0xdd, 0x00, 0xd1, 0xa1, 0x66, 0xbf, 0xbf, 0x79, 0x28, 0x7a, 0xa2, 0xa8, 0xd9, 0xa0, 0x6f, 0xa9, 0x2a, 0x5d, 0xe7, 0x2e, 0xba, 0x7b, 0x22, 0x75, 0xeb, 0x70, 0xa4, 0xaa, 0xcb, 0x27, 0xd5, 0xb8, 0xda, 0x02, 0xa9, 0xc3, 0xc3, 0x11, 0x89, 0x44, 0x53, 0x68, 0x74, 0x38, 0x54, 0x22, 0x65, 0x41, 0x7c, 0x98, 0xe5, 0x6e, 0x5e, 0xf8, 0x5d, 0xfa, 0x12, 0x55, 0x01, 0x75, 0x44, 0x04, 0xe9, 0x6a, 0x35, 0xc7, 0x9f, 0x91, 0xbb, 0xd6, 0x17, 0x97, 0xd6, 0x98, 0x43, 0x4c, 0xef, 0x21, 0x6f, 0x30, 0xc4, 0x16, 0x57, 0xce, 0x47, 0x3c, 0x66, 0x71, 0xc0, 0x19, 0x45, 0x87, 0x79, 0xa5, 0x3f, 0xb4, 0xfd, 0xd1, 0xd9, 0xf2, 0xe4, 0xb1, 0x97, 0x0e, 0x35, 0xcc, 0x0e, 0x75, 0x38, 0x23, 0x12, 0x8b, 0xce, 0x10, 0xae, 0x5e, 0x5d, 0xd7, 0xba, 0xbb, 0x37, 0xec, 0x6e, 0xdb, 0xde, 0xf5, 0xbc, 0x15, 0xa4, 0xd2, 0x7a, 0xf2, 0xa7, 0x26, 0x0d, 0xf8, 0xb1, 0xa8, 0x61, 0xea, 0xae, 0xd5, 0x93, 0x4f, 0x1e, 0xe9, 0xd0, 0x79, 0x22, 0xb6, 0x94, 0x4a, 0x0d, 0xd5, 0x75, 0xa8, 0x67, 0x57, 0x6b, 0xed, 0xa6, 0xb6, 0x40, 0x93, 0x07, 0xfa, 0xb1, 0x0f, 0x0f, 0x5a, 0x8b, 0xb5, 0x28, 0x07, 0x65, 0x30, 0xfd, 0x61, 0x66, 0x17, 0x73, 0x1d, 0xd7, 0xa1, 0xef, 0x25, 0x7e, 0x92, 0xb4, 0xa6, 0x00, 0x29, 0xae, 0xb3, 0x2a, 0xa0, 0x11, 0xe4, 0x00, 0x22, 0xba, 0x4a, 0x0a, 0xf7, 0xbe, 0xca, 0x52, 0x92, 0x21, 0x98, 0x6e, 0x3e, 0x0c, 0x2d, 0x91, 0x64, 0xeb, 0xde, 0xed, 0xce, 0x54, 0x5b, 0xd9, 0x99, 0xc5, 0xc8, 0x41, 0xeb, 0x92, 0xcb, 0x59, 0x81, 0x9b, 0x12, 0x9c, 0xde, 0xe6, 0x15, 0xba, 0xed, 0x59, 0xae, 0x5b, 0xb2, 0x14, 0xf1, 0xc1, 0x20, 0x42, 0xbb, 0x65, 0x7b, 0x0a, 0xdb, 0xc3, 0xc9, 0x5b, 0x00, 0x88, 0xde, 0x9e, 0xae, 0x8e, 0xa6, 0x86, 0xe2, 0xb0, 0xc7, 0x85, 0x28, 0x9c, 0x58, 0x92, 0x3b, 0x19, 0x66, 0x4e, 0xe5, 0x88, 0xed, 0xc4, 0xd0, 0x55, 0xc8, 0x09, 0xa3, 0x9a, 0x0d, 0xbc, 0x4b, 0x68, 0xe2, 0xe7, 0xa2, 0x18, 0xd5, 0x8b, 0xc7, 0x73, 0x76, 0x51, 0x80, 0x35, 0x46, 0x9f, 0x8c, 0x55, 0x45, 0xe3, 0xe1, 0x76, 0xb7, 0xa9, 0x2d, 0x7d, 0x3d, 0x27, 0xd2, 0x4a, 0x85, 0x47, 0x62, 0x89, 0x81, 0x84, 0x1d, 0x58, 0x6b, 0xc7, 0x9b, 0x7b, 0x77, 0x04, 0xe4, 0x7a, 0x99, 0xda, 0x63, 0xad, 0xb7, 0x26, 0x1c, 0x6a, 0xb3, 0x5c, 0xde, 0xfc, 0x29, 0x61, 0x0c, 0xf6, 0xfa, 0x7f, 0x45, 0x03, 0x9e, 0xb8, 0xcf, 0x55, 0x55, 0x41, 0xfd, 0x3e, 0x1b, 0x89, 0xfd, 0x41, 0xc8, 0x63, 0x77, 0xf9, 0x6a, 0xbb, 0x83, 0xc1, 0xde, 0x86, 0x40, 0xa2, 0x42, 0x17, 0x30, 0x98, 0xa5, 0xba, 0x70, 0xf9, 0x68, 0x30, 0x3c, 0x11, 0xdb, 0xd0, 0x3b, 0x9e, 0x0d, 0xd1, 0x92, 0x25, 0x2c, 0x26, 0xa2, 0x0b, 0xfa, 0x40, 0xff, 0xfe, 0x7f, 0x11, 0x7b, 0x65, 0x8c, 0x6f, 0xe8, 0x28, 0x8a, 0xf4, 0x6d, 0xa9, 0x2e, 0x5f, 0xd7, 0x1c, 0x28, 0xea, 0xd8, 0x00, 0xbe, 0x60, 0xb5, 0x44, 0xfb, 0xb7, 0xd5, 0x34, 0xec, 0x1b, 0x8d, 0x27, 0x6f, 0x7d, 0x7a, 0xfb, 0x8e, 0x2b, 0x87, 0x92, 0xf1, 0x75, 0x7b, 0x1b, 0x6a, 0xb6, 0xf4, 0x97, 0x59, 0x1c, 0xf7, 0x9a, 0x75, 0x12, 0xa5, 0x26, 0xdc, 0x3e, 0x51, 0x19, 0x19, 0x6c, 0xf0, 0xaa, 0x55, 0x12, 0x9d, 0xef, 0xac, 0x2b, 0xb9, 0xa9, 0xb9, 0x73, 0xd7, 0x50, 0xab, 0xcb, 0xd1, 0xba, 0x76, 0x47, 0x6b, 0xdb, 0x74, 0xb3, 0x9b, 0x31, 0x44, 0xf6, 0x56, 0xa6, 0xf6, 0x8d, 0xb4, 0xb9, 0xdc, 0x3d, 0xb3, 0x67, 0xc7, 0x37, 0x7f, 0x74, 0x4f, 0x7d, 0xfd, 0xce, 0x8f, 0x4c, 0xac, 0x3f, 0x35, 0xdd, 0xed, 0x76, 0x77, 0x4d, 0xde, 0x36, 0x90, 0x98, 0x49, 0xf8, 0xac, 0x8e, 0xce, 0x40, 0xd3, 0xc6, 0x9e, 0x7a, 0xbb, 0xad, 0xbe, 0x67, 0x63, 0x63, 0xa0, 0xcb, 0x6e, 0x43, 0x1c, 0xb1, 0x44, 0x75, 0xfa, 0x22, 0x35, 0x03, 0xd7, 0x62, 0x21, 0x8a, 0x19, 0xa2, 0x28, 0xaa, 0xcc, 0x08, 0x28, 0x91, 0x84, 0xc7, 0x5e, 0x71, 0x17, 0x64, 0x06, 0xd7, 0xed, 0x16, 0x58, 0x23, 0xcb, 0x40, 0xaf, 0x82, 0xf9, 0xd0, 0x2b, 0x61, 0xd3, 0x1c, 0xe4, 0x95, 0xcc, 0xa8, 0x35, 0xea, 0xa1, 0xe5, 0x8a, 0x56, 0x34, 0x93, 0xef, 0xe7, 0xe4, 0x40, 0xae, 0xe0, 0x4a, 0xf6, 0x50, 0x33, 0xd0, 0xb9, 0xe9, 0x2e, 0xc2, 0xce, 0x8d, 0x42, 0x89, 0x9c, 0x9b, 0xf9, 0x63, 0x2a, 0x25, 0x69, 0x41, 0x86, 0xa1, 0x5a, 0x31, 0xff, 0x7b, 0xb2, 0xb3, 0x0c, 0x1d, 0x3b, 0xe8, 0xf4, 0xab, 0x2d, 0x6a, 0xf5, 0xc0, 0x60, 0x03, 0xb2, 0x10, 0xc9, 0x6f, 0x3d, 0x3c, 0x08, 0x4d, 0xc6, 0xf9, 0x46, 0x34, 0xf6, 0x33, 0xe9, 0xfb, 0xc9, 0x5f, 0xe1, 0x75, 0x5b, 0x8c, 0x32, 0xc7, 0xa0, 0x83, 0x40, 0x06, 0xd1, 0x5a, 0xf5, 0x39, 0x50, 0x75, 0xf7, 0xee, 0x0c, 0xab, 0x26, 0x4d, 0xcf, 0x64, 0xf3, 0xed, 0xe7, 0xa8, 0x1e, 0x40, 0x14, 0x17, 0xd9, 0xad, 0x3a, 0x0d, 0xbb, 0x36, 0x44, 0x79, 0x6b, 0x83, 0xe2, 0x57, 0x83, 0x19, 0xbf, 0x81, 0x60, 0x31, 0x40, 0x5f, 0x25, 0x06, 0xfe, 0x17, 0x3b, 0xf9, 0x5d, 0xc6, 0xf6, 0xc3, 0x78, 0xba, 0x0f, 0x26, 0x6c, 0x78, 0xba, 0x9f, 0x1f, 0x87, 0x26, 0xb6, 0xda, 0x6d, 0xab, 0x33, 0x96, 0x98, 0xcb, 0xaf, 0xbf, 0x59, 0xee, 0xf7, 0xc4, 0xbd, 0xee, 0xaa, 0x0a, 0x50, 0x2d, 0x9c, 0xd1, 0xeb, 0xd5, 0x2e, 0x0d, 0x9a, 0xd0, 0xe1, 0xee, 0xc2, 0xb9, 0x27, 0x98, 0x41, 0x1c, 0x1f, 0xb9, 0x8f, 0xd9, 0x07, 0xed, 0x09, 0x39, 0xd1, 0x4c, 0x9c, 0x4d, 0x1a, 0xab, 0xa2, 0x24, 0x49, 0xc3, 0x17, 0x61, 0xb2, 0x2f, 0x42, 0x67, 0xf8, 0xde, 0xb2, 0xef, 0x23, 0x1a, 0xe3, 0xa2, 0xb8, 0xa8, 0x86, 0x95, 0x44, 0xf0, 0x6a, 0x08, 0x28, 0xc7, 0x37, 0xe3, 0xca, 0x5d, 0xf1, 0x2a, 0x23, 0xbf, 0x29, 0x76, 0xcb, 0x1a, 0xea, 0xe2, 0xb1, 0x8a, 0xf2, 0x48, 0x49, 0x56, 0x1c, 0xd2, 0xe5, 0xc4, 0xc1, 0x2a, 0x87, 0x5c, 0x71, 0x08, 0x88, 0x9f, 0x32, 0x55, 0xa3, 0xe8, 0xb3, 0x9c, 0x7a, 0x30, 0xb6, 0xdf, 0x4a, 0x85, 0x47, 0x91, 0x42, 0xc0, 0x12, 0x6a, 0xb9, 0x7b, 0x3d, 0x92, 0x10, 0xd4, 0x07, 0xc6, 0x52, 0x73, 0x39, 0x1b, 0x24, 0x3e, 0x10, 0x86, 0x0a, 0xa2, 0x73, 0x60, 0x02, 0x57, 0x3c, 0x7d, 0xab, 0x3c, 0xe0, 0xa9, 0xcc, 0xca, 0xcc, 0x8b, 0x65, 0xd6, 0x28, 0x90, 0x99, 0x77, 0xee, 0x71, 0x92, 0x0b, 0x1f, 0x53, 0xef, 0x64, 0xab, 0xa0, 0x62, 0x3d, 0x50, 0x96, 0xbe, 0x8f, 0x2a, 0x64, 0x1a, 0xa0, 0x1c, 0x6b, 0x88, 0xc1, 0x64, 0x3f, 0x2a, 0x11, 0x98, 0x3b, 0x1f, 0xe8, 0x6e, 0x82, 0x11, 0x31, 0xfb, 0x78, 0xe9, 0x88, 0x11, 0x1e, 0x87, 0xdd, 0x91, 0x24, 0x39, 0x53, 0xa3, 0xa6, 0xba, 0x2a, 0x11, 0x8f, 0x09, 0x27, 0xc8, 0xb2, 0x12, 0x31, 0x2f, 0x21, 0x91, 0x3c, 0xb7, 0x9f, 0xac, 0x86, 0xc2, 0x48, 0x84, 0xdb, 0xdc, 0x78, 0xba, 0x70, 0xc2, 0xc0, 0xd2, 0xc0, 0xf3, 0x45, 0xc5, 0x49, 0x03, 0x79, 0xb5, 0xc5, 0xb4, 0x59, 0x37, 0x85, 0x5c, 0x59, 0xa1, 0x1c, 0x0a, 0x1d, 0x2e, 0x6f, 0x5d, 0xfe, 0xdc, 0x41, 0x72, 0xf8, 0x2f, 0x41, 0x00, 0x00, 0xef, 0x61, 0x0b, 0x6f, 0x8b, 0x9e, 0xc0, 0xe7, 0x71, 0x0e, 0xa2, 0x8d, 0xf8, 0x48, 0x52, 0xb3, 0x0a, 0xfa, 0xad, 0xb1, 0x88, 0xd3, 0xa1, 0x56, 0x11, 0xe2, 0x6c, 0x1e, 0xa2, 0x8b, 0x90, 0x4a, 0x67, 0x52, 0x6c, 0xb2, 0x21, 0x4a, 0xad, 0x44, 0x95, 0xd4, 0xd0, 0x06, 0x22, 0x16, 0x91, 0x18, 0x4a, 0x62, 0xe3, 0xa3, 0xd5, 0x2b, 0x34, 0xdb, 0x83, 0x76, 0x26, 0xef, 0xa2, 0x16, 0x50, 0x96, 0xd3, 0x7c, 0x33, 0x80, 0x5b, 0x21, 0xc8, 0x42, 0x5b, 0x6b, 0xc8, 0xe7, 0x0d, 0xf9, 0x03, 0x4b, 0x42, 0x16, 0x82, 0xb9, 0xa7, 0x79, 0x79, 0x67, 0xce, 0x4b, 0x1c, 0xee, 0xc5, 0xa8, 0x9f, 0x47, 0xf7, 0xb4, 0xa2, 0x0d, 0x07, 0xc3, 0x18, 0x72, 0x77, 0x1f, 0x36, 0xb6, 0x86, 0xf7, 0x97, 0xad, 0xf7, 0xc4, 0xeb, 0x02, 0xfe, 0xfc, 0x63, 0xbf, 0xeb, 0xef, 0x36, 0x72, 0x1b, 0x4d, 0xaa, 0x3d, 0x90, 0xdd, 0x73, 0xc8, 0x9e, 0x8e, 0x21, 0x3e, 0xd4, 0x36, 0xdc, 0xfd, 0x60, 0xee, 0x51, 0x20, 0xbb, 0xcf, 0x20, 0x1b, 0xe1, 0x19, 0xd1, 0x13, 0xb4, 0x18, 0xcb, 0xb7, 0x93, 0xb8, 0x97, 0x55, 0x9e, 0xba, 0x66, 0xf8, 0xfa, 0xe5, 0x48, 0xc4, 0x0c, 0x01, 0xb7, 0x54, 0x06, 0xc7, 0x32, 0xd1, 0x4d, 0x69, 0xee, 0x4d, 0x4e, 0xd7, 0xba, 0x58, 0x71, 0x49, 0x25, 0xa4, 0x40, 0xa2, 0x50, 0x54, 0xd9, 0x34, 0x50, 0x24, 0xd5, 0xd9, 0xa5, 0xe4, 0x2e, 0x68, 0x05, 0x85, 0xda, 0xd1, 0x1a, 0xf2, 0xea, 0xff, 0x2e, 0xa1, 0xc2, 0xa5, 0x9a, 0x2b, 0x52, 0x0c, 0xa0, 0xab, 0x30, 0xaf, 0x20, 0xd7, 0x2f, 0x27, 0x2f, 0xe7, 0x08, 0x51, 0x20, 0xe6, 0x4d, 0xa7, 0x2b, 0x65, 0x8c, 0xd1, 0x50, 0xed, 0x97, 0xfd, 0x65, 0x69, 0xc9, 0x36, 0xf7, 0x4c, 0xb6, 0x9e, 0x62, 0x25, 0x5b, 0x9f, 0x48, 0xf7, 0x80, 0x5f, 0x75, 0x0c, 0xaf, 0x3a, 0xf9, 0x1a, 0x16, 0x6d, 0x77, 0xcb, 0x90, 0x59, 0x1b, 0xc4, 0xbe, 0x72, 0x63, 0xfa, 0x19, 0xea, 0x28, 0xd4, 0x81, 0x23, 0xc4, 0x47, 0xb9, 0xa0, 0x0b, 0x14, 0x1b, 0xd5, 0xad, 0x26, 0x45, 0x24, 0x0e, 0xba, 0x70, 0x57, 0xe2, 0x2c, 0x62, 0xb8, 0x84, 0xa0, 0x19, 0xe8, 0x81, 0x80, 0x39, 0x02, 0x21, 0xcc, 0xc8, 0xdd, 0x04, 0x29, 0x62, 0x48, 0xd1, 0x6e, 0xa8, 0xe3, 0xb8, 0x50, 0x27, 0x52, 0x8f, 0xd9, 0x22, 0x68, 0x15, 0x8b, 0x9a, 0x8b, 0x19, 0x52, 0xbc, 0x9b, 0xe7, 0xab, 0xc9, 0xf4, 0x22, 0xb2, 0x9d, 0x46, 0x93, 0x9a, 0xe1, 0xb5, 0x6b, 0x06, 0xaa, 0x13, 0xb1, 0xf2, 0x92, 0x22, 0x6f, 0x04, 0xfa, 0xa2, 0x66, 0x9e, 0xcb, 0x30, 0x08, 0x6d, 0xfd, 0x5c, 0x1b, 0x55, 0xb4, 0x34, 0xf9, 0x85, 0x88, 0xb5, 0x25, 0x00, 0xf6, 0x57, 0x31, 0x64, 0x94, 0x3a, 0x5a, 0xda, 0x1b, 0xa9, 0x1b, 0x6a, 0x6b, 0x0a, 0x7a, 0x1b, 0xab, 0x13, 0x89, 0x6a, 0x64, 0xc0, 0x1a, 0xf4, 0xa1, 0xea, 0xd5, 0xf5, 0x15, 0xdd, 0xe5, 0x96, 0xd2, 0xae, 0xc9, 0xcd, 0x93, 0x5d, 0xa5, 0xe5, 0x13, 0xe7, 0x46, 0x66, 0x3e, 0x59, 0x5b, 0x2c, 0xd1, 0xa9, 0x34, 0xc1, 0xaa, 0xee, 0x58, 0x51, 0x5b, 0xd4, 0x5a, 0xda, 0x35, 0xb1, 0x69, 0xa2, 0xab, 0xb4, 0x68, 0xe0, 0x50, 0x9f, 0xda, 0xa9, 0xb1, 0x94, 0x94, 0xc5, 0x3d, 0xee, 0x12, 0x94, 0xad, 0x97, 0xfe, 0x81, 0x41, 0x6b, 0x2c, 0xed, 0xac, 0x88, 0xd4, 0x06, 0xad, 0x36, 0xaf, 0x03, 0x9a, 0xb9, 0x76, 0x8f, 0xad, 0xa0, 0xb8, 0xda, 0x15, 0x8a, 0x07, 0xec, 0xee, 0xa2, 0xe4, 0x78, 0x4b, 0xdd, 0xce, 0xc1, 0xf2, 0x64, 0x75, 0x87, 0x52, 0x6e, 0xb2, 0x9a, 0x8c, 0xfe, 0x98, 0x23, 0x50, 0xe1, 0x73, 0xb8, 0xc3, 0x0d, 0xc3, 0x8d, 0xc9, 0x3d, 0xc3, 0xd5, 0x52, 0x85, 0x53, 0x6b, 0x2b, 0xf1, 0xe8, 0x54, 0x6a, 0x0b, 0x1b, 0x53, 0xae, 0x81, 0x63, 0xf1, 0x16, 0xb4, 0x1f, 0xfc, 0xc4, 0xce, 0xab, 0x0a, 0x0c, 0x7f, 0x63, 0x25, 0xee, 0x45, 0x14, 0x36, 0x24, 0x12, 0x21, 0x17, 0xf9, 0xe1, 0x8a, 0xc9, 0x21, 0xd2, 0x8c, 0xac, 0xa0, 0x8b, 0x32, 0xad, 0xf8, 0x38, 0x6a, 0x6e, 0x6b, 0x22, 0xdb, 0x18, 0x5a, 0x0b, 0x1e, 0x3f, 0xb4, 0x17, 0xd8, 0x8a, 0x84, 0xac, 0x68, 0xe9, 0xdc, 0xdc, 0x26, 0xe0, 0x64, 0x78, 0xb9, 0xbd, 0x05, 0xe5, 0x36, 0x30, 0x37, 0xdc, 0xb5, 0x6e, 0xdf, 0xe0, 0x43, 0x4a, 0x55, 0xd6, 0x46, 0x18, 0xbc, 0x75, 0xac, 0xab, 0xa3, 0xbf, 0x32, 0x2b, 0x8b, 0x8a, 0x9e, 0x91, 0x9e, 0x8a, 0x1c, 0x4b, 0x81, 0x4a, 0xf4, 0x8e, 0xa4, 0xca, 0xd9, 0x17, 0x04, 0x50, 0x17, 0xa2, 0x23, 0x14, 0x0a, 0xfa, 0xb1, 0xe1, 0x64, 0x40, 0xa7, 0x26, 0xb1, 0xb3, 0x82, 0x12, 0xdd, 0x28, 0xfc, 0x6a, 0x7c, 0x20, 0x0e, 0x63, 0xc9, 0xbc, 0x45, 0x56, 0xb6, 0x66, 0x46, 0xd6, 0xe1, 0x16, 0x1b, 0xbd, 0xda, 0xec, 0x59, 0x3d, 0x54, 0xfe, 0x62, 0xc7, 0xc8, 0xae, 0xa3, 0xcd, 0x9b, 0x1f, 0xbf, 0xa5, 0x29, 0x36, 0x71, 0x47, 0xcf, 0xb5, 0xbe, 0x3b, 0xc7, 0x63, 0xde, 0x55, 0xd3, 0xcd, 0x2d, 0x1b, 0xdb, 0xcb, 0xa1, 0x8b, 0x4d, 0x4d, 0x15, 0xd6, 0x15, 0x5b, 0xaa, 0xa6, 0xee, 0x5e, 0xd3, 0x73, 0x7a, 0xe7, 0x6a, 0xd3, 0x6f, 0x41, 0xbf, 0xac, 0xbc, 0x67, 0x36, 0x59, 0xb9, 0xae, 0xc9, 0xeb, 0xf4, 0x39, 0xf5, 0x08, 0x47, 0x91, 0x7e, 0x84, 0xfe, 0x0f, 0x5a, 0x0e, 0xf5, 0x4a, 0x35, 0xf1, 0x2b, 0x8e, 0x8d, 0xb7, 0x08, 0x9a, 0xfc, 0x16, 0x15, 0x0f, 0xcb, 0x46, 0x97, 0x0c, 0x7f, 0xb9, 0x08, 0x95, 0xbd, 0x7b, 0x79, 0xb0, 0xb5, 0x75, 0x99, 0x86, 0x8b, 0xc0, 0xd6, 0x85, 0x1c, 0xd8, 0x7a, 0x36, 0x17, 0xa9, 0xf1, 0x81, 0x60, 0xeb, 0xa5, 0xdb, 0xe7, 0x82, 0xad, 0x11, 0xd6, 0x3a, 0x10, 0x0a, 0x72, 0xa7, 0x27, 0xb9, 0x7e, 0x47, 0x2e, 0xd2, 0x3a, 0x43, 0xdd, 0x67, 0xae, 0x4a, 0x08, 0x61, 0xd6, 0x94, 0xa5, 0xf4, 0x96, 0xd6, 0x8c, 0x36, 0x02, 0xaf, 0x97, 0xae, 0xae, 0xec, 0xde, 0xde, 0xe6, 0x11, 0x42, 0xd8, 0x06, 0xdb, 0xeb, 0xac, 0x4a, 0x59, 0xf8, 0x60, 0x65, 0xbd, 0x3b, 0x82, 0x26, 0x02, 0x45, 0x36, 0x54, 0xf2, 0x3a, 0x68, 0xfe, 0xe3, 0x7a, 0x5d, 0x7c, 0xe4, 0x96, 0xa6, 0x2f, 0x76, 0xae, 0xe3, 0xb5, 0xba, 0xae, 0x30, 0xea, 0xea, 0x0a, 0xbe, 0xff, 0xc3, 0xc1, 0x01, 0x95, 0x0a, 0xd5, 0xeb, 0x09, 0xa4, 0x9f, 0xa4, 0x67, 0xe1, 0x9c, 0x97, 0x40, 0xab, 0xf9, 0x00, 0x8e, 0x44, 0xbc, 0xa8, 0xc1, 0xe0, 0xfd, 0x6e, 0x1b, 0xfe, 0x05, 0xe0, 0x9c, 0x4f, 0x14, 0xa0, 0x28, 0x90, 0x88, 0x10, 0xc7, 0xb8, 0x94, 0x42, 0x14, 0xce, 0x9b, 0xc5, 0x6c, 0x01, 0x4e, 0xee, 0x34, 0xda, 0x89, 0x12, 0x1c, 0x68, 0x62, 0x98, 0x6d, 0x82, 0x56, 0x41, 0x4e, 0x03, 0xe8, 0x9b, 0x11, 0x44, 0xa1, 0xc7, 0xed, 0xb4, 0x59, 0xcd, 0x46, 0x83, 0x5e, 0xa7, 0x55, 0x2b, 0x65, 0x28, 0xa4, 0x22, 0x41, 0xbe, 0x19, 0xe0, 0xcb, 0xc7, 0x31, 0x5e, 0xae, 0xb8, 0x9b, 0x37, 0x1f, 0xe2, 0x0c, 0x5e, 0xdf, 0x2d, 0x51, 0x33, 0x12, 0x8d, 0x74, 0x3b, 0x38, 0xf3, 0xfd, 0xf4, 0x41, 0x14, 0xdf, 0xfd, 0xca, 0x9f, 0x7f, 0xf6, 0xc4, 0xc1, 0x2b, 0x05, 0x76, 0xd1, 0x66, 0x70, 0x68, 0x52, 0xec, 0xd2, 0x3d, 0xb6, 0xff, 0x23, 0xe4, 0xfe, 0xf4, 0xfb, 0xe6, 0xa8, 0xd9, 0x12, 0x35, 0x03, 0x5a, 0x71, 0x63, 0x87, 0xc9, 0x02, 0x1e, 0x21, 0xc3, 0x27, 0x80, 0xca, 0xa2, 0xfe, 0xcd, 0x6f, 0xb4, 0x26, 0x9c, 0x07, 0x45, 0x2e, 0x3c, 0x00, 0x7d, 0x84, 0xad, 0xcc, 0x3c, 0xb4, 0x95, 0xca, 0x89, 0x8b, 0x57, 0x8d, 0x80, 0xca, 0x9c, 0xb2, 0xbb, 0x32, 0xfe, 0x26, 0xca, 0xde, 0x15, 0xe3, 0x90, 0x36, 0x5a, 0xe1, 0x79, 0x93, 0xc5, 0xc1, 0x37, 0xc3, 0xf9, 0xec, 0x70, 0x87, 0x9b, 0xca, 0x69, 0x84, 0xf0, 0x79, 0x14, 0x43, 0xa1, 0xe5, 0x2f, 0x6c, 0x27, 0x62, 0xf3, 0xde, 0xb3, 0x93, 0x03, 0xb1, 0x1a, 0x94, 0x95, 0x42, 0xf5, 0xea, 0x29, 0x30, 0xf3, 0x9e, 0xaa, 0x04, 0x63, 0x57, 0xb3, 0x14, 0xec, 0x19, 0x14, 0x73, 0xee, 0x1c, 0x31, 0x66, 0xe7, 0xc7, 0x8f, 0x5b, 0x7a, 0xba, 0x5b, 0x5a, 0x7a, 0x53, 0xe4, 0x1b, 0xa5, 0xab, 0xcb, 0x47, 0x4f, 0xac, 0x0d, 0x17, 0xae, 0x8d, 0xb6, 0xec, 0xee, 0x2f, 0x89, 0x8e, 0x1c, 0xee, 0xee, 0x3e, 0xbc, 0x2e, 0x3a, 0x34, 0xe0, 0x0f, 0xa9, 0x54, 0x66, 0x31, 0x5d, 0x36, 0xd0, 0xdf, 0x37, 0x80, 0xfe, 0xbd, 0x77, 0x4e, 0xaf, 0x4b, 0x4c, 0x9d, 0x5f, 0x43, 0xfd, 0x83, 0xd9, 0x5c, 0xdc, 0xbb, 0xb3, 0xa5, 0x6d, 0xef, 0x40, 0x69, 0x64, 0xdd, 0xf1, 0x81, 0xf7, 0xbb, 0xb6, 0x4c, 0xf3, 0x7a, 0x62, 0x15, 0x9c, 0xd9, 0x8d, 0xcc, 0x33, 0x44, 0x9c, 0xe5, 0xbf, 0xa5, 0x68, 0x1a, 0xe3, 0x71, 0x45, 0x80, 0x92, 0xc0, 0x7d, 0x88, 0xe0, 0x08, 0xa7, 0xb8, 0xd3, 0x43, 0x8e, 0x55, 0x1c, 0x33, 0xd2, 0xc0, 0x1e, 0x71, 0x73, 0x49, 0x10, 0x3a, 0x42, 0x09, 0x29, 0xab, 0xd8, 0xc4, 0x28, 0x86, 0xc9, 0xe6, 0x32, 0xf3, 0xda, 0x4d, 0x90, 0x29, 0x85, 0x4d, 0x47, 0x8a, 0xc7, 0xf7, 0xc4, 0x8c, 0x94, 0x52, 0xeb, 0xd3, 0xa5, 0x2f, 0x16, 0x0d, 0x05, 0xc0, 0x37, 0xdc, 0xfe, 0x1b, 0x75, 0x5a, 0x3d, 0xf8, 0x46, 0xba, 0x9a, 0x1c, 0x8c, 0xd7, 0xf8, 0x5b, 0xd6, 0x57, 0x56, 0x8e, 0xb4, 0x46, 0x64, 0xca, 0x92, 0xd0, 0x16, 0xf5, 0xe0, 0xd1, 0xc7, 0x27, 0x26, 0x3e, 0xb1, 0xbf, 0xa5, 0xb2, 0xe4, 0x39, 0x72, 0x42, 0xe7, 0xd3, 0xa7, 0xef, 0x91, 0xab, 0xfc, 0xce, 0x83, 0xab, 0x0c, 0xa5, 0x3a, 0xf0, 0xad, 0x47, 0xda, 0xce, 0xf5, 0xd7, 0x8f, 0xd5, 0x3b, 0x9d, 0x89, 0xee, 0xe2, 0x80, 0x55, 0xac, 0x9b, 0xfe, 0xc8, 0x96, 0xca, 0xaa, 0xad, 0x0f, 0x4d, 0x0e, 0xdd, 0xdb, 0xfc, 0x08, 0xab, 0xe7, 0x43, 0x0b, 0x7f, 0xa2, 0xa2, 0xd0, 0x5e, 0xae, 0x41, 0x39, 0xdc, 0x65, 0x80, 0x62, 0xf4, 0xb8, 0x04, 0x32, 0xdc, 0x28, 0xe9, 0x7d, 0xbc, 0xbf, 0x87, 0x78, 0xf3, 0x78, 0x87, 0x0f, 0xbe, 0x33, 0x97, 0xe4, 0xa9, 0xd5, 0x86, 0x9c, 0x3e, 0x53, 0x29, 0x9b, 0xe9, 0xc8, 0xee, 0x71, 0x6c, 0xd8, 0x26, 0x21, 0xdc, 0x11, 0x39, 0x4e, 0xd4, 0xac, 0x25, 0x0c, 0xde, 0x70, 0x96, 0x37, 0xb6, 0x34, 0x96, 0x3b, 0x6b, 0xb6, 0xdc, 0xbf, 0xae, 0x7a, 0x6e, 0x76, 0xd8, 0x51, 0xad, 0xb0, 0xe8, 0xca, 0x5b, 0x86, 0xab, 0x63, 0xab, 0xab, 0x9c, 0xce, 0x8a, 0xa6, 0x96, 0xa6, 0x72, 0xe7, 0x01, 0xa1, 0x1d, 0x4c, 0x45, 0x8b, 0xea, 0x4a, 0xfd, 0x81, 0x48, 0xe3, 0x50, 0x6d, 0xff, 0xe9, 0x8d, 0x09, 0x53, 0xb8, 0xba, 0xb0, 0x53, 0xe7, 0xf4, 0x3a, 0xed, 0xb1, 0x8e, 0xe2, 0xd2, 0x64, 0xd4, 0x1f, 0x2c, 0x6d, 0x1a, 0xae, 0x8f, 0xe6, 0xd8, 0xc1, 0xd0, 0x6b, 0x4d, 0xdf, 0x46, 0x7d, 0x0f, 0xda, 0xc1, 0x7a, 0x62, 0x23, 0x4b, 0x2b, 0xee, 0x96, 0x00, 0x91, 0x4c, 0x4a, 0x32, 0x84, 0x88, 0xd9, 0x44, 0x03, 0xae, 0xfc, 0x0c, 0x07, 0xb6, 0x65, 0x53, 0xeb, 0xa1, 0x0f, 0x8c, 0x6a, 0xd0, 0x2c, 0xdb, 0x0e, 0x9d, 0xf2, 0xe9, 0x38, 0xa4, 0x37, 0xff, 0x9f, 0x1c, 0xe1, 0xf4, 0xf8, 0x12, 0x9f, 0x98, 0x8d, 0x1c, 0x25, 0xea, 0x78, 0x8c, 0xe4, 0x21, 0x30, 0x92, 0x7e, 0xea, 0x7a, 0xfa, 0x29, 0xf4, 0x03, 0x8c, 0x5c, 0x07, 0x23, 0xe4, 0x53, 0x7f, 0x78, 0x64, 0x1e, 0xfe, 0xff, 0x91, 0x3f, 0x3c, 0x42, 0x9e, 0x98, 0x3f, 0xfe, 0x08, 0xc2, 0x8e, 0x40, 0x7d, 0xf3, 0x28, 0x7c, 0x46, 0x3b, 0x9a, 0x5b, 0x50, 0xee, 0x8c, 0x0a, 0x88, 0x30, 0xf7, 0x2f, 0x8d, 0x9f, 0x89, 0xc3, 0x08, 0x8a, 0x31, 0x8f, 0x77, 0x1e, 0x50, 0xde, 0x4e, 0xd8, 0xbd, 0x5a, 0xbd, 0x57, 0x8b, 0x2b, 0x0e, 0xf1, 0x38, 0xf9, 0xa5, 0x40, 0x39, 0xf4, 0xa3, 0xee, 0x82, 0x5b, 0xbf, 0x93, 0x0b, 0xc5, 0xb9, 0x94, 0xee, 0xb5, 0xbb, 0xc1, 0x18, 0x79, 0xd7, 0x22, 0x08, 0x8e, 0x92, 0xc5, 0xb4, 0xc0, 0xe7, 0xda, 0x03, 0x9f, 0xcb, 0x87, 0x9e, 0x4b, 0x09, 0x2d, 0x57, 0x7c, 0x66, 0xc1, 0x90, 0x04, 0x33, 0x85, 0x10, 0x93, 0x33, 0x2c, 0x0e, 0x05, 0x61, 0xd8, 0xc6, 0x11, 0x96, 0x7f, 0x9a, 0x3b, 0xaf, 0x82, 0x6d, 0x7c, 0x84, 0x4f, 0xeb, 0xf7, 0x78, 0xcd, 0x7e, 0x8e, 0x45, 0x27, 0x1f, 0x94, 0x12, 0xe3, 0x11, 0xfd, 0x59, 0x6c, 0xca, 0x52, 0x50, 0x14, 0xc5, 0xaf, 0x5d, 0xd6, 0x5b, 0xe7, 0xb2, 0x70, 0x14, 0xe6, 0xfa, 0x8d, 0xc7, 0x73, 0x01, 0x28, 0x8f, 0xb3, 0xaf, 0xc0, 0xc3, 0x50, 0xe0, 0xc3, 0xe2, 0x98, 0xc7, 0x93, 0x38, 0xe6, 0x61, 0x20, 0x8a, 0x93, 0x21, 0x71, 0x0e, 0x66, 0x1f, 0x7b, 0xc0, 0x39, 0x90, 0x7d, 0x68, 0x72, 0x68, 0xd9, 0x04, 0xfc, 0x7c, 0xb0, 0x3e, 0x1b, 0x93, 0x80, 0x62, 0x9b, 0x3f, 0x95, 0x1b, 0x86, 0x48, 0xff, 0x0a, 0xfd, 0xcd, 0xbc, 0xf0, 0x03, 0xdc, 0x37, 0xaa, 0xa0, 0x52, 0xbb, 0x41, 0x4b, 0x09, 0x05, 0xdc, 0xbb, 0xfb, 0x92, 0xd2, 0xf2, 0x52, 0xbd, 0x86, 0x06, 0x58, 0x97, 0xda, 0x79, 0x72, 0x52, 0x68, 0x6a, 0x90, 0x38, 0xbc, 0x8f, 0xb5, 0x83, 0x2d, 0x69, 0x42, 0x47, 0xab, 0x78, 0xdf, 0xe4, 0xe3, 0xfe, 0xf8, 0x83, 0xd1, 0xa4, 0x12, 0xfe, 0x57, 0xad, 0xac, 0xf2, 0x15, 0x9a, 0xfd, 0x0c, 0x22, 0xf7, 0xc5, 0x67, 0x3c, 0x1c, 0x61, 0x89, 0x14, 0x78, 0x11, 0xd5, 0x52, 0x30, 0x80, 0x8e, 0x7e, 0xaa, 0x82, 0x11, 0x10, 0x17, 0x10, 0xc7, 0x8a, 0xcd, 0xd9, 0xca, 0xa3, 0xa0, 0x26, 0x99, 0x8a, 0x96, 0x46, 0x2b, 0x35, 0x72, 0xf2, 0x17, 0xe9, 0x99, 0xe7, 0xfe, 0xdd, 0xd1, 0xe0, 0xb4, 0x88, 0x65, 0x1e, 0xa5, 0x45, 0xa1, 0xd3, 0xe8, 0x3d, 0xc9, 0x0d, 0x4d, 0x8d, 0x63, 0x75, 0x0e, 0x77, 0xdd, 0xea, 0xe8, 0x66, 0x75, 0x81, 0x5c, 0x62, 0x52, 0xff, 0x95, 0x71, 0x19, 0xc8, 0x63, 0xee, 0xb5, 0x89, 0xa2, 0x58, 0x6d, 0xd4, 0x1a, 0x37, 0x83, 0xd6, 0x1b, 0x12, 0xf0, 0x1d, 0x89, 0x4a, 0xd4, 0x62, 0x2a, 0x12, 0x43, 0xeb, 0x3b, 0xe0, 0x1e, 0x2a, 0xaa, 0x99, 0x5c, 0x15, 0xf0, 0x35, 0x0e, 0x44, 0x2a, 0x86, 0x3a, 0x6a, 0xcd, 0xdf, 0x74, 0x76, 0x84, 0x8a, 0xd6, 0x96, 0x92, 0x73, 0x5a, 0x13, 0x94, 0x7b, 0x39, 0x7c, 0x8b, 0xe7, 0xa0, 0xdc, 0x17, 0x61, 0xfc, 0x67, 0x6f, 0x06, 0xe3, 0x4f, 0x3e, 0x37, 0x7f, 0x07, 0xb8, 0x96, 0x3e, 0x09, 0x8e, 0xa1, 0xea, 0xc1, 0x0f, 0x83, 0xa2, 0x87, 0xc1, 0x63, 0x59, 0x6c, 0x95, 0x0f, 0x7e, 0x6f, 0x3e, 0xc6, 0x7f, 0x76, 0x19, 0x8c, 0x3f, 0xe5, 0x4b, 0x17, 0x7e, 0xe5, 0x2b, 0xe0, 0x17, 0xe0, 0x9b, 0xe9, 0x5a, 0xea, 0x9d, 0xf4, 0x26, 0x5c, 0x56, 0x67, 0xe1, 0x6d, 0xe6, 0x0c, 0xf4, 0x29, 0x94, 0x50, 0x1f, 0x27, 0x93, 0xf2, 0x20, 0x1c, 0x97, 0x38, 0x52, 0x62, 0xbc, 0x23, 0xac, 0xc9, 0x81, 0x8d, 0x65, 0xa8, 0xba, 0xb2, 0xb7, 0x10, 0x55, 0xd7, 0xd5, 0x50, 0x30, 0x8c, 0x98, 0xba, 0xfc, 0xbc, 0xc9, 0x97, 0xa9, 0xc2, 0x90, 0xc3, 0xf6, 0x5e, 0x05, 0x87, 0x00, 0x07, 0x01, 0x70, 0x6c, 0x90, 0x6a, 0xde, 0xf6, 0xe0, 0x44, 0x51, 0x71, 0xfb, 0xfa, 0x68, 0x53, 0xe7, 0x4b, 0xc5, 0x83, 0x07, 0x53, 0x53, 0x4f, 0x1e, 0x68, 0xee, 0x7f, 0x72, 0xfe, 0xb3, 0x57, 0xde, 0x7b, 0x72, 0x20, 0xb6, 0xe1, 0xf4, 0x40, 0x85, 0x18, 0x6a, 0x7d, 0xc9, 0xe4, 0x74, 0x78, 0xec, 0xbe, 0xd9, 0x0b, 0xd2, 0xa6, 0x99, 0xf3, 0x43, 0x7d, 0xb7, 0x8f, 0x57, 0xac, 0x9e, 0x5c, 0x7b, 0xb8, 0xdf, 0xdf, 0x73, 0xfa, 0xea, 0xd6, 0x63, 0xff, 0xed, 0xfe, 0xfe, 0xbe, 0xbb, 0xbf, 0xb6, 0x6f, 0xec, 0xe3, 0xa7, 0x66, 0x3d, 0xfd, 0x0a, 0xab, 0x59, 0x47, 0x55, 0x7c, 0xf9, 0x70, 0xf7, 0xf1, 0x0d, 0x0d, 0x32, 0x56, 0x3e, 0x65, 0xe9, 0x2b, 0x74, 0x1f, 0xf3, 0x32, 0x91, 0x40, 0x7c, 0x7e, 0x6a, 0x15, 0x89, 0xfd, 0x45, 0x49, 0x18, 0xda, 0xdb, 0x28, 0xe8, 0xc5, 0xf2, 0x3e, 0xcd, 0xe0, 0x64, 0xe3, 0x51, 0xe4, 0x3e, 0xce, 0xa6, 0x64, 0x52, 0x31, 0x85, 0xd7, 0x2b, 0xc3, 0xe0, 0xb0, 0x31, 0xf4, 0x22, 0xe1, 0x17, 0x25, 0x88, 0x84, 0xd6, 0x18, 0x0e, 0xa2, 0xe2, 0xd6, 0x26, 0xa4, 0xcf, 0x62, 0x19, 0x44, 0x23, 0xde, 0x57, 0x31, 0x88, 0x0a, 0x1d, 0xc3, 0xe7, 0x14, 0x83, 0x76, 0x09, 0xeb, 0x44, 0x7b, 0xb4, 0xdf, 0x41, 0xc0, 0xc6, 0x47, 0xee, 0x29, 0x08, 0xea, 0x9d, 0xd6, 0xd6, 0x26, 0x72, 0xbb, 0x9e, 0xab, 0x0f, 0x6d, 0x37, 0x28, 0x18, 0xa5, 0xd8, 0xe9, 0x9e, 0x88, 0x8f, 0xcd, 0xf4, 0x34, 0x25, 0xd2, 0x1b, 0xa9, 0x11, 0x0c, 0x6e, 0xbc, 0xf3, 0xc9, 0x67, 0x19, 0xd1, 0x0e, 0xad, 0x6c, 0xfd, 0x96, 0x3b, 0x6f, 0x58, 0x04, 0x15, 0xa3, 0xbb, 0xf8, 0x42, 0xd2, 0xeb, 0x6f, 0x5c, 0x66, 0xe3, 0x39, 0x70, 0xff, 0xfd, 0x3a, 0x7c, 0x47, 0x3b, 0x11, 0x21, 0x06, 0x92, 0x7d, 0x2a, 0x20, 0x01, 0x6a, 0x40, 0xa2, 0xc3, 0x78, 0xf4, 0x92, 0x88, 0x4e, 0x57, 0x4c, 0xcc, 0x21, 0x04, 0x47, 0x96, 0xbb, 0x6c, 0x06, 0xbb, 0x17, 0xfc, 0x2b, 0x23, 0x00, 0xe4, 0x34, 0x0d, 0xdf, 0x33, 0x52, 0xe2, 0x76, 0x39, 0x1d, 0x48, 0x73, 0x6a, 0xb9, 0x57, 0xb5, 0xae, 0xf0, 0xaa, 0xf0, 0x9d, 0x40, 0x1e, 0xaf, 0x76, 0x1d, 0xc8, 0x7f, 0xcb, 0xeb, 0xe9, 0x8d, 0xe4, 0xa6, 0xab, 0xdf, 0xbb, 0x4b, 0x59, 0xc0, 0x6c, 0x65, 0xa4, 0xb4, 0x58, 0xbe, 0x55, 0x54, 0xa0, 0x38, 0xb9, 0xd4, 0x2b, 0xa2, 0xf7, 0xa1, 0x1f, 0x80, 0x66, 0x8d, 0xf8, 0x04, 0x34, 0xdd, 0x74, 0xea, 0x13, 0x12, 0x31, 0x91, 0x79, 0xbf, 0x3d, 0xf0, 0xfd, 0x7c, 0xf0, 0xfd, 0xce, 0x5f, 0x2d, 0x80, 0xaf, 0xc5, 0xf3, 0x88, 0x96, 0x62, 0x37, 0x04, 0x1a, 0x42, 0x70, 0xe2, 0xcf, 0xa4, 0xa4, 0x38, 0x14, 0xcb, 0x5f, 0x21, 0x57, 0x75, 0x54, 0x8c, 0x55, 0x08, 0xd6, 0xc0, 0x34, 0x06, 0x62, 0xa0, 0xf6, 0x32, 0xc0, 0xb7, 0x96, 0x8c, 0xc9, 0xa1, 0x58, 0x76, 0x2c, 0xd1, 0x76, 0x34, 0xe9, 0x0a, 0xf8, 0x01, 0x51, 0x1c, 0xf6, 0x47, 0x02, 0x11, 0x97, 0x93, 0x4d, 0x0c, 0xd4, 0x69, 0x15, 0x72, 0x89, 0x88, 0xf0, 0x01, 0x9f, 0x82, 0x23, 0xc2, 0xe5, 0x6a, 0xbc, 0xaa, 0x28, 0x5e, 0x20, 0x5e, 0x01, 0xd4, 0x95, 0xc9, 0x54, 0xa2, 0x25, 0x0d, 0x85, 0xe5, 0x3e, 0xbb, 0x02, 0x17, 0x9d, 0xbd, 0xb5, 0x2a, 0x15, 0x31, 0x60, 0xb9, 0x5c, 0x47, 0xa2, 0x8a, 0x0e, 0x1f, 0x06, 0xdf, 0xce, 0x14, 0xa7, 0x65, 0x5e, 0x16, 0x29, 0x2d, 0xba, 0xb7, 0x95, 0xda, 0xdb, 0x49, 0xe8, 0xa9, 0x56, 0x72, 0x52, 0xe1, 0xa5, 0xb5, 0xe6, 0xf8, 0x70, 0xb1, 0x9a, 0xab, 0x58, 0xcb, 0xc9, 0xe5, 0x39, 0x3c, 0xee, 0x15, 0xc4, 0x67, 0x58, 0x96, 0x67, 0x39, 0x3f, 0xc5, 0x19, 0xe4, 0x0e, 0xa9, 0x90, 0x41, 0x05, 0xa7, 0x82, 0x98, 0x9d, 0x0a, 0xa3, 0x6c, 0x9b, 0xb0, 0x0c, 0x48, 0x60, 0x77, 0x09, 0x9a, 0x13, 0x68, 0x3a, 0x28, 0xc4, 0x64, 0xde, 0x84, 0x90, 0x73, 0x13, 0xc2, 0x96, 0x8c, 0xe5, 0xb5, 0xc5, 0x53, 0x07, 0xfd, 0x2a, 0x1e, 0x53, 0x00, 0x0c, 0x0a, 0x5e, 0xdc, 0x6b, 0x34, 0x69, 0x83, 0x73, 0x88, 0x88, 0x94, 0x86, 0x83, 0xde, 0x42, 0x47, 0x85, 0xb3, 0x22, 0x3b, 0x9f, 0x94, 0x1f, 0x38, 0x9f, 0x16, 0xd5, 0x61, 0x5d, 0x6a, 0x3e, 0x89, 0x51, 0x69, 0xd6, 0x73, 0x2a, 0xae, 0x34, 0xeb, 0x39, 0x7b, 0xcb, 0x8e, 0xe5, 0xe6, 0x13, 0x73, 0x79, 0x51, 0xc9, 0x56, 0x1a, 0xcb, 0xed, 0x5f, 0x44, 0xb5, 0xb8, 0x16, 0x57, 0x39, 0xd1, 0x47, 0xfc, 0x38, 0x29, 0x6f, 0x4e, 0x92, 0xb4, 0x38, 0x06, 0x64, 0x72, 0x5e, 0xe7, 0x95, 0xc1, 0x3d, 0x1d, 0xba, 0x24, 0xb3, 0xb8, 0x74, 0x2c, 0xf4, 0xd0, 0x89, 0x69, 0x14, 0x63, 0xc2, 0xf1, 0x61, 0x28, 0x2a, 0xb9, 0x5c, 0x34, 0x2a, 0xc5, 0x51, 0x65, 0x95, 0x82, 0x94, 0xc9, 0xa6, 0x65, 0xc8, 0xda, 0x37, 0x22, 0x28, 0x15, 0xea, 0xc6, 0xec, 0xbb, 0xe9, 0x5e, 0xc9, 0x38, 0xdb, 0x81, 0xa0, 0xc5, 0x12, 0x31, 0x2d, 0x99, 0xcb, 0x76, 0x24, 0x38, 0xc0, 0xd5, 0x92, 0xfd, 0xa0, 0x3f, 0xe0, 0xae, 0xa8, 0x08, 0x85, 0x2a, 0xfa, 0x2a, 0xfa, 0x7a, 0x7b, 0x52, 0xdd, 0x5d, 0x9d, 0x6d, 0xab, 0x1a, 0xeb, 0x6b, 0xaa, 0x43, 0xe5, 0xa1, 0x68, 0x30, 0x14, 0x0a, 0xf9, 0xd4, 0x99, 0x1a, 0x72, 0x59, 0x3f, 0x80, 0xc9, 0xbb, 0xd6, 0x8b, 0x31, 0x45, 0x3d, 0xda, 0x0b, 0x57, 0x1a, 0x12, 0x6a, 0x52, 0xe7, 0x0c, 0x99, 0x8c, 0x41, 0x87, 0x56, 0xeb, 0x08, 0x1a, 0x4d, 0x21, 0xa7, 0x0e, 0xf4, 0x6a, 0xe1, 0x1d, 0x13, 0x7b, 0xc7, 0x04, 0xef, 0x68, 0xcb, 0x7e, 0xa6, 0xf5, 0x15, 0x6c, 0x3f, 0xa1, 0xf2, 0x78, 0x6f, 0x3c, 0xbf, 0x78, 0xbc, 0xe8, 0x2f, 0x9a, 0x02, 0xa8, 0x6d, 0x00, 0xb5, 0xd5, 0xa1, 0x6f, 0xcb, 0xbb, 0x06, 0x3b, 0x7f, 0xae, 0x56, 0xfd, 0xa3, 0xd9, 0xf2, 0xee, 0x37, 0x97, 0x1b, 0x47, 0x5e, 0x97, 0x8b, 0xbe, 0x01, 0xe7, 0x7b, 0x3f, 0xd0, 0xb3, 0xd2, 0x36, 0xb4, 0x57, 0x94, 0x53, 0x4a, 0xa9, 0x17, 0xba, 0x1c, 0x1a, 0xb8, 0x22, 0xa8, 0xee, 0x36, 0x20, 0xed, 0xb4, 0x25, 0x0d, 0xf0, 0x87, 0x22, 0xf7, 0x23, 0x9c, 0x29, 0x84, 0xba, 0x54, 0x13, 0x4a, 0xa5, 0x14, 0xce, 0x5d, 0xa9, 0x14, 0x2a, 0x43, 0x99, 0x8c, 0xff, 0x15, 0xad, 0x7a, 0xc9, 0xa8, 0x4a, 0x4e, 0x4a, 0x24, 0xb3, 0x29, 0x8d, 0x5a, 0xcc, 0xd0, 0xe8, 0xc0, 0x26, 0xc3, 0x2a, 0xb5, 0x95, 0xe4, 0x4b, 0x16, 0x25, 0x60, 0x7f, 0xc5, 0x18, 0xa1, 0x50, 0xcc, 0xb0, 0xdd, 0x89, 0x9b, 0xec, 0x9d, 0x6c, 0xc8, 0xf0, 0x4e, 0xdd, 0xf4, 0x17, 0x10, 0xd9, 0xfe, 0x70, 0xc0, 0x11, 0x09, 0x65, 0x3f, 0xd1, 0xdf, 0xd7, 0xdb, 0xdd, 0xb9, 0xaa, 0xb9, 0xb1, 0xa1, 0x3a, 0x1e, 0x8d, 0x98, 0x70, 0x6e, 0xbc, 0xcf, 0x1b, 0xd4, 0xc2, 0x2d, 0xc9, 0xb3, 0xfc, 0xce, 0x93, 0xaf, 0x98, 0x50, 0x01, 0x88, 0x0c, 0x5e, 0x7f, 0xd1, 0xa0, 0x8b, 0x46, 0xb9, 0x8d, 0xc9, 0xa1, 0x57, 0x30, 0x6a, 0xc6, 0xe9, 0x9a, 0xac, 0x84, 0x1b, 0x53, 0x63, 0x45, 0x7a, 0xc3, 0xb5, 0x74, 0xd7, 0x75, 0x16, 0x9d, 0x7f, 0x6a, 0x70, 0x55, 0x3d, 0x93, 0x45, 0xf1, 0x7f, 0xe5, 0x91, 0x7b, 0x2c, 0x21, 0x9d, 0xc3, 0xda, 0x06, 0x07, 0x7c, 0x99, 0x9d, 0x4a, 0xa0, 0xc7, 0x7a, 0x30, 0x6a, 0x7f, 0x4a, 0xc1, 0x61, 0xfb, 0xdb, 0x73, 0x46, 0x9b, 0xd5, 0x6b, 0xe2, 0x2a, 0x38, 0xce, 0x71, 0xa2, 0x83, 0x78, 0x8d, 0x1d, 0x36, 0x4d, 0x89, 0xc5, 0x4c, 0x49, 0x19, 0x29, 0x76, 0x2c, 0x8b, 0x01, 0x03, 0x47, 0x59, 0x03, 0x7f, 0x48, 0xb2, 0xb7, 0xa9, 0xec, 0x08, 0x2b, 0x44, 0xb4, 0x48, 0x41, 0xcf, 0x41, 0xc9, 0x32, 0x63, 0x2a, 0xa5, 0x84, 0xc2, 0xa9, 0xee, 0x08, 0xee, 0xca, 0x5f, 0x20, 0x94, 0x3f, 0x3d, 0x2a, 0x13, 0x93, 0xc8, 0xfc, 0x52, 0xcb, 0x29, 0x8a, 0xb3, 0x38, 0x2b, 0x05, 0x5d, 0x25, 0x63, 0xec, 0x39, 0x32, 0xdb, 0x8f, 0x5c, 0xbe, 0xdb, 0x68, 0xd2, 0x4e, 0x10, 0x1d, 0x6d, 0xe8, 0x2c, 0x04, 0xf9, 0xb3, 0x95, 0xb1, 0x68, 0xc4, 0x6b, 0x8c, 0xfb, 0xf1, 0xd8, 0x68, 0xd0, 0xb8, 0xfc, 0xcd, 0x03, 0x40, 0x2d, 0xda, 0x54, 0x99, 0x67, 0x6e, 0x52, 0xf8, 0xe2, 0x17, 0x05, 0x3b, 0xed, 0x36, 0x06, 0xed, 0xb4, 0x7f, 0x83, 0xec, 0x17, 0xed, 0xbe, 0x78, 0x2c, 0x44, 0x7f, 0x86, 0x63, 0x51, 0x47, 0xb4, 0x81, 0x71, 0x56, 0x35, 0xca, 0x2d, 0x66, 0x52, 0xc6, 0x0d, 0x85, 0x8d, 0x2d, 0xf2, 0x85, 0x6e, 0x89, 0xf8, 0x5b, 0x39, 0x0d, 0x46, 0xb9, 0x28, 0x5c, 0x23, 0x9c, 0xec, 0xcc, 0x98, 0x46, 0x38, 0x1a, 0xfc, 0x85, 0x0a, 0xa0, 0x81, 0xd2, 0xe4, 0x8c, 0x8d, 0x9c, 0x13, 0xb2, 0x82, 0x1f, 0x1b, 0xf6, 0xef, 0x54, 0xc8, 0xc0, 0xdf, 0xd1, 0xd3, 0xf8, 0xff, 0xc0, 0xdf, 0x4f, 0xd6, 0xc0, 0x2f, 0x10, 0xce, 0x09, 0x0d, 0x3b, 0x27, 0xf8, 0xce, 0xe4, 0xf2, 0x7d, 0x51, 0x64, 0xcf, 0xdd, 0x50, 0x4f, 0x10, 0x2d, 0xc9, 0xfa, 0xb6, 0x86, 0x36, 0x28, 0xca, 0xba, 0xda, 0x9a, 0x9c, 0x69, 0xa2, 0xbd, 0xe9, 0x69, 0x92, 0x67, 0x71, 0x50, 0x19, 0x0b, 0x63, 0xe5, 0x19, 0x72, 0xe5, 0x49, 0xde, 0xfe, 0xb0, 0xab, 0x6f, 0xad, 0xee, 0xad, 0x30, 0x91, 0xdb, 0x87, 0x33, 0x36, 0xc7, 0x07, 0xcf, 0x10, 0x6c, 0x93, 0xa8, 0x34, 0x19, 0x9b, 0x84, 0x79, 0x86, 0x37, 0x42, 0x78, 0xfb, 0x4c, 0xec, 0xc1, 0xeb, 0x35, 0x05, 0x26, 0xb9, 0xf5, 0xea, 0xd3, 0xa8, 0x29, 0x44, 0x71, 0x27, 0x96, 0x90, 0xdd, 0x5e, 0x00, 0xd0, 0x7a, 0x85, 0x3f, 0xe8, 0xec, 0xed, 0x0c, 0x6e, 0xbd, 0x56, 0x0d, 0x94, 0xd0, 0xc4, 0x50, 0x12, 0x73, 0x2a, 0x80, 0x58, 0xf0, 0xb5, 0x0a, 0x14, 0x12, 0xe4, 0x4c, 0x55, 0xfe, 0x42, 0x86, 0x18, 0x84, 0x46, 0x45, 0x14, 0x89, 0x0c, 0x0d, 0x8d, 0x5c, 0x4a, 0x89, 0xc5, 0xd3, 0x62, 0x7e, 0x56, 0x64, 0xbf, 0x81, 0x10, 0x7e, 0x81, 0x0a, 0xdc, 0xd4, 0x37, 0x18, 0xff, 0x0f, 0x9f, 0x21, 0xb9, 0x2a, 0xa7, 0x33, 0x8e, 0xf1, 0xce, 0xa4, 0xf8, 0xef, 0x21, 0x05, 0x5f, 0x43, 0x2e, 0xff, 0x2d, 0x68, 0x8e, 0x14, 0x42, 0x05, 0x42, 0x74, 0xb4, 0xc3, 0x59, 0x52, 0x97, 0x48, 0x55, 0xa5, 0x58, 0x65, 0x52, 0x51, 0x8e, 0x2d, 0x27, 0x34, 0x53, 0x74, 0x39, 0x7e, 0xc7, 0x07, 0x29, 0x11, 0x5e, 0xef, 0x2f, 0x63, 0x51, 0xad, 0xa8, 0x45, 0xae, 0xe1, 0xb9, 0x44, 0x1f, 0x42, 0x66, 0xd6, 0x79, 0x25, 0x67, 0x66, 0x9d, 0x77, 0x64, 0xcc, 0xac, 0x15, 0x95, 0x08, 0x3f, 0x9d, 0x96, 0x30, 0xbd, 0x58, 0xdb, 0x4b, 0xbc, 0x16, 0xda, 0x5e, 0x71, 0xa2, 0x89, 0x58, 0x43, 0x6c, 0x01, 0x21, 0x6e, 0xbe, 0xc0, 0xc9, 0xa1, 0x14, 0x4e, 0x0e, 0x5b, 0x52, 0x8f, 0xe7, 0x8b, 0x9a, 0x54, 0x00, 0x0d, 0x60, 0x14, 0xe8, 0x6e, 0x26, 0xdd, 0xb7, 0x51, 0x0f, 0x94, 0x40, 0xad, 0x9c, 0xd5, 0x02, 0xb5, 0x06, 0xa8, 0x08, 0xb5, 0x6a, 0x5a, 0x81, 0xc5, 0x8d, 0x10, 0xe8, 0x9c, 0xb4, 0xd9, 0x5f, 0x73, 0x84, 0xad, 0xcb, 0x1b, 0xf4, 0x06, 0xf4, 0x2d, 0xca, 0x7d, 0x7f, 0xef, 0x97, 0x24, 0x5b, 0x60, 0x7f, 0x5a, 0xb9, 0x8f, 0x80, 0xdd, 0x80, 0x82, 0x99, 0xcb, 0x7e, 0x0f, 0x91, 0x99, 0x05, 0xbc, 0xbb, 0xb5, 0xdc, 0x97, 0xc0, 0x51, 0x0f, 0x24, 0xe0, 0xa8, 0x4f, 0x6f, 0x5e, 0x3f, 0xb2, 0xba, 0xaf, 0xbd, 0x35, 0xd1, 0x94, 0x68, 0xac, 0xab, 0x59, 0x34, 0xf2, 0x86, 0xbf, 0x6f, 0xe4, 0x33, 0xf6, 0x9d, 0xfe, 0x03, 0x2c, 0xc1, 0x9b, 0x9d, 0x11, 0xcc, 0xb5, 0x8c, 0x9d, 0x77, 0x7f, 0xbe, 0x09, 0x98, 0x7e, 0x21, 0xdf, 0x4c, 0xfc, 0x5b, 0x26, 0x8b, 0xa8, 0x96, 0xb5, 0x00, 0xd3, 0xc7, 0x3f, 0xc0, 0x54, 0xc4, 0xf1, 0x58, 0xe8, 0xd3, 0x3f, 0xc5, 0x34, 0x10, 0x8d, 0xc4, 0x57, 0x92, 0x6a, 0x3f, 0x74, 0x6f, 0x0a, 0x00, 0xa0, 0xc8, 0xee, 0x08, 0x1c, 0xad, 0x2e, 0xd6, 0x4a, 0x0b, 0x12, 0x22, 0xb1, 0x68, 0x1f, 0x2a, 0x44, 0xc7, 0x63, 0xb0, 0x51, 0x55, 0x13, 0x1a, 0xc7, 0x07, 0x33, 0x64, 0x64, 0x6c, 0x59, 0x73, 0x27, 0x4e, 0x80, 0x40, 0xcd, 0x09, 0xf8, 0x09, 0x90, 0x4a, 0xe6, 0x96, 0xee, 0x46, 0x08, 0x7a, 0x25, 0x2b, 0xb8, 0x0e, 0xb4, 0x84, 0xce, 0xeb, 0x80, 0x9b, 0xa2, 0xca, 0x31, 0x28, 0xb3, 0x55, 0xd8, 0x09, 0x1d, 0xf0, 0x37, 0xd4, 0x85, 0xb5, 0x21, 0x7d, 0x91, 0x57, 0x6b, 0xc4, 0x67, 0xd1, 0xb9, 0x46, 0xda, 0x22, 0xf4, 0x72, 0x2e, 0x3e, 0x22, 0x27, 0xb2, 0x60, 0xa6, 0x9f, 0xe2, 0xac, 0xb2, 0x9a, 0x34, 0x89, 0xd2, 0x1f, 0xec, 0x06, 0x89, 0x54, 0x21, 0xaa, 0x8d, 0x9e, 0xee, 0x38, 0x72, 0x3a, 0x93, 0x1b, 0x41, 0x7e, 0x5d, 0x93, 0x63, 0xc9, 0xa5, 0xff, 0x35, 0x63, 0x8f, 0x09, 0xf1, 0xfd, 0x7c, 0x02, 0x00, 0x1b, 0x19, 0xce, 0x09, 0x35, 0x70, 0xf1, 0xef, 0xf4, 0x15, 0x31, 0xc2, 0x8b, 0xac, 0x07, 0x12, 0x56, 0xbc, 0x86, 0x06, 0x40, 0xc9, 0xcb, 0xed, 0x24, 0x5b, 0x43, 0x96, 0xea, 0xee, 0x85, 0x4e, 0x4b, 0x97, 0x2d, 0xf7, 0xb6, 0x8a, 0xbb, 0xcd, 0x39, 0x9d, 0xa5, 0x0a, 0xe8, 0x5c, 0xc9, 0xf6, 0x49, 0x45, 0x24, 0x45, 0xc9, 0x47, 0x95, 0x00, 0xe1, 0x5b, 0x11, 0xae, 0x84, 0x1c, 0x97, 0x30, 0x24, 0xca, 0xaa, 0x54, 0xa9, 0x49, 0x2e, 0xa4, 0xb4, 0x15, 0xf0, 0xe3, 0x52, 0xc9, 0x76, 0x52, 0x43, 0x43, 0x42, 0x45, 0x12, 0xaa, 0xb9, 0xe5, 0x7b, 0x13, 0xd0, 0xd5, 0xe5, 0xfb, 0x26, 0xab, 0x33, 0xdd, 0x18, 0x15, 0x93, 0xdf, 0x0d, 0xa7, 0x70, 0xa2, 0xbe, 0xcc, 0xb8, 0x84, 0x45, 0x0a, 0x64, 0xbb, 0xc2, 0x31, 0x82, 0x3e, 0xff, 0xba, 0x21, 0xe8, 0x51, 0x75, 0xb6, 0xb5, 0xae, 0x6a, 0x46, 0x99, 0x3f, 0xe1, 0xa0, 0xcf, 0xeb, 0x76, 0x1a, 0x74, 0x6a, 0x95, 0x52, 0x21, 0x66, 0xa0, 0x0c, 0xd6, 0x6b, 0x30, 0xc6, 0xc5, 0xf5, 0x41, 0xa3, 0xf7, 0x81, 0x23, 0xba, 0x78, 0x60, 0x45, 0x6f, 0x66, 0xcd, 0xed, 0xda, 0xb4, 0xa9, 0x96, 0xcb, 0xd3, 0xe0, 0x07, 0x36, 0x5d, 0xb5, 0xec, 0x50, 0x83, 0xef, 0x97, 0xd0, 0x68, 0xc4, 0x9f, 0x55, 0x65, 0x47, 0xfc, 0x3b, 0x02, 0xfb, 0x3b, 0x7f, 0xbc, 0x97, 0x1f, 0x7f, 0x50, 0x9e, 0x3f, 0x0b, 0xf0, 0x1e, 0x7f, 0x6c, 0xe1, 0x4f, 0xd4, 0x15, 0x1c, 0x23, 0xac, 0x22, 0x0e, 0x26, 0xe5, 0x21, 0xb8, 0xe3, 0x01, 0x2b, 0xae, 0x86, 0xc7, 0x0e, 0x55, 0x80, 0xa0, 0xc4, 0xa4, 0x98, 0xc2, 0x09, 0xdc, 0x62, 0x8a, 0xe0, 0xf2, 0xfe, 0x45, 0xfc, 0xa1, 0x5e, 0x36, 0xd3, 0xbb, 0x68, 0x51, 0x43, 0x0e, 0xdd, 0x8f, 0xa3, 0x6d, 0x39, 0x8c, 0x04, 0xa3, 0xd0, 0x92, 0x0c, 0xfa, 0xfd, 0x5e, 0x7f, 0x88, 0x2d, 0x2e, 0x9a, 0x89, 0x2c, 0x8a, 0xf3, 0x45, 0x8b, 0x39, 0x61, 0x71, 0xa5, 0xb5, 0xec, 0xe9, 0x33, 0xa8, 0x3f, 0x74, 0x79, 0x4b, 0x59, 0x41, 0x71, 0x6d, 0xe1, 0xe8, 0x1d, 0xb5, 0x9b, 0x37, 0x24, 0x87, 0x9b, 0xa2, 0x05, 0x06, 0x87, 0xd4, 0x3b, 0xf4, 0xad, 0x43, 0xeb, 0xce, 0x8c, 0x97, 0xad, 0x73, 0x38, 0xa5, 0xc6, 0x60, 0xcf, 0x9a, 0xc9, 0x9a, 0xfa, 0x6d, 0x7d, 0xa5, 0xcd, 0x17, 0xff, 0xf8, 0x2c, 0x19, 0xa7, 0x1d, 0xd1, 0xe6, 0x50, 0xa0, 0xca, 0xab, 0x69, 0x6b, 0x48, 0xdf, 0xb2, 0xef, 0x16, 0x77, 0xd0, 0x6d, 0x52, 0x25, 0x57, 0xaf, 0xae, 0x9e, 0xbd, 0x30, 0x4c, 0x9e, 0xd5, 0xd9, 0x46, 0x7d, 0xd5, 0x01, 0x83, 0xa7, 0x75, 0xa6, 0xad, 0x7e, 0xcf, 0x48, 0x35, 0xcd, 0xda, 0x3e, 0x03, 0x0b, 0x6f, 0x53, 0x3f, 0xc3, 0x67, 0xd2, 0x41, 0xe2, 0x31, 0xee, 0x4c, 0xda, 0x07, 0x08, 0xda, 0x90, 0x3d, 0x93, 0x86, 0x97, 0x8c, 0x21, 0x7b, 0x26, 0x6d, 0xc5, 0x05, 0x8a, 0xd1, 0x61, 0xb0, 0x88, 0x21, 0x17, 0x25, 0xfa, 0xb3, 0x87, 0x86, 0x76, 0x4e, 0x70, 0xcb, 0x90, 0x01, 0xb8, 0x17, 0x7d, 0x9c, 0x4f, 0x04, 0x00, 0x95, 0x8d, 0x5a, 0xad, 0x0e, 0xaa, 0x03, 0x21, 0x9f, 0x0f, 0xb1, 0x01, 0x14, 0xe4, 0xb3, 0x01, 0x68, 0x17, 0x23, 0x86, 0xaa, 0x12, 0x31, 0xca, 0x5c, 0xb6, 0x77, 0x55, 0x16, 0xda, 0x82, 0xd8, 0x00, 0x04, 0x88, 0x96, 0xcd, 0x67, 0xe3, 0x0a, 0x59, 0x98, 0xa2, 0x1a, 0xb3, 0xa0, 0x95, 0x9f, 0xde, 0x90, 0x08, 0x31, 0x40, 0x6d, 0x0d, 0x9d, 0x41, 0x56, 0x2e, 0x98, 0xf7, 0x84, 0x93, 0xcb, 0x23, 0x2f, 0xb9, 0x70, 0xba, 0x22, 0xc8, 0xa4, 0xb6, 0xa2, 0xb7, 0x41, 0x5a, 0x73, 0x11, 0x53, 0x87, 0x75, 0xb9, 0x8f, 0xf7, 0xe4, 0x10, 0x79, 0xcc, 0xae, 0x48, 0xe4, 0x31, 0xbb, 0x22, 0x91, 0xc7, 0x68, 0x52, 0xea, 0xc9, 0x30, 0x79, 0x70, 0x35, 0x5a, 0x17, 0xa5, 0x3e, 0x67, 0x99, 0x3c, 0x70, 0xfe, 0x73, 0xfc, 0xd8, 0x50, 0xed, 0x78, 0x93, 0xe7, 0xe2, 0x99, 0xb6, 0x3d, 0x51, 0x55, 0xfc, 0xae, 0xcd, 0xb3, 0x9f, 0xdc, 0xdb, 0x50, 0x35, 0x7d, 0xef, 0xba, 0x9d, 0x97, 0x93, 0x5a, 0x59, 0xf8, 0xc0, 0x81, 0xb0, 0x0c, 0x78, 0xda, 0x7a, 0xc3, 0xeb, 0xce, 0x6d, 0xfa, 0xd2, 0x6b, 0x15, 0x4d, 0x67, 0x1a, 0xdb, 0x06, 0x2e, 0x7c, 0x7d, 0xef, 0xf1, 0xaf, 0x9f, 0x6c, 0x9d, 0xe8, 0x3d, 0x10, 0xa4, 0xde, 0x11, 0xca, 0x04, 0xe7, 0x77, 0x26, 0x78, 0x2e, 0x1d, 0x4d, 0xc9, 0xe2, 0xfc, 0xce, 0x12, 0x41, 0x7e, 0x27, 0x9d, 0xcd, 0xef, 0xf4, 0x2c, 0x9f, 0xb2, 0x99, 0xcb, 0x74, 0xf2, 0x01, 0x0d, 0xb1, 0x98, 0x56, 0xce, 0xeb, 0x5c, 0x4c, 0x7e, 0x82, 0xf2, 0x3a, 0x7d, 0x37, 0x95, 0xd7, 0x89, 0x85, 0x76, 0xf3, 0x99, 0x9d, 0x1c, 0x19, 0xca, 0xcd, 0x25, 0x77, 0x22, 0x66, 0x94, 0xbc, 0x6c, 0x6a, 0xb4, 0x3f, 0xc5, 0xe0, 0x1a, 0xdc, 0x0f, 0x75, 0x53, 0x09, 0xb1, 0x23, 0x29, 0xb3, 0x42, 0x53, 0xa0, 0x04, 0x67, 0xfa, 0xb3, 0x62, 0xf3, 0xf1, 0xb4, 0x50, 0x44, 0x2e, 0x2b, 0xd4, 0x6e, 0x21, 0x2b, 0x94, 0xed, 0x26, 0x9a, 0x21, 0x90, 0x5e, 0x52, 0x8a, 0xa9, 0x47, 0xf0, 0xd4, 0xc9, 0x83, 0xde, 0x67, 0xf2, 0x40, 0x29, 0xa1, 0x34, 0xf6, 0xdd, 0x14, 0xcd, 0xc8, 0xae, 0xcb, 0xcd, 0x5f, 0x55, 0xb0, 0xac, 0x22, 0xe7, 0x10, 0xab, 0x48, 0x05, 0x66, 0x15, 0x41, 0x99, 0x26, 0xed, 0x77, 0x5d, 0x3b, 0xba, 0xf7, 0xab, 0xf7, 0xf4, 0xb5, 0x27, 0xe1, 0xfc, 0x59, 0xbd, 0x10, 0xa3, 0x3e, 0x8a, 0x75, 0x70, 0x07, 0x71, 0x32, 0xa9, 0xeb, 0x00, 0x94, 0xc8, 0x09, 0x08, 0x50, 0x8b, 0x71, 0x73, 0x55, 0x80, 0xe9, 0xc8, 0x64, 0x19, 0x71, 0xc5, 0x8a, 0xb2, 0x00, 0x05, 0x4e, 0xb9, 0x66, 0x0a, 0xd9, 0xe1, 0x77, 0xbe, 0x99, 0x86, 0xf8, 0xad, 0x8d, 0x80, 0x68, 0x5b, 0x15, 0x29, 0xf1, 0x15, 0x9a, 0x8d, 0x18, 0xe8, 0xa9, 0x64, 0xb1, 0x07, 0x7e, 0x0e, 0x7b, 0x80, 0x81, 0x5b, 0x40, 0x08, 0xe7, 0x32, 0x99, 0x73, 0xc5, 0x20, 0x00, 0xc9, 0x67, 0x8a, 0x1f, 0x91, 0xed, 0x33, 0x1b, 0x23, 0x45, 0xe6, 0x90, 0xbf, 0x10, 0x6e, 0xa8, 0xc0, 0x24, 0x81, 0x82, 0xf2, 0xc5, 0xbb, 0xa1, 0xa0, 0xba, 0x03, 0xcd, 0xf5, 0x96, 0xda, 0x8e, 0x35, 0xe5, 0x0d, 0x23, 0xb5, 0xf6, 0x92, 0xa1, 0xe3, 0x83, 0x86, 0xa0, 0x5e, 0x0c, 0xf7, 0x5e, 0xb7, 0xbb, 0x47, 0xa2, 0x96, 0xae, 0xda, 0xd1, 0x13, 0x6e, 0x3a, 0xf2, 0xc2, 0x81, 0x5d, 0x97, 0x93, 0x27, 0xdb, 0x13, 0x81, 0x98, 0xa1, 0x7c, 0xa0, 0x01, 0xce, 0xdc, 0x97, 0x58, 0x56, 0x96, 0xa9, 0xfb, 0xd6, 0x1f, 0x7b, 0x34, 0x34, 0xb2, 0xbf, 0xcd, 0xee, 0x6b, 0x58, 0x53, 0x56, 0xb3, 0xa9, 0x23, 0x44, 0x91, 0xde, 0x9e, 0x62, 0xab, 0x86, 0x56, 0xd4, 0xef, 0x88, 0xda, 0x46, 0x6f, 0x7b, 0x64, 0x0c, 0x91, 0xb4, 0x40, 0x71, 0x62, 0x3c, 0xd1, 0x45, 0xfa, 0x3f, 0x30, 0x0e, 0xb4, 0x92, 0xb8, 0x9c, 0x54, 0x84, 0x04, 0x50, 0x22, 0x2e, 0xa5, 0x9a, 0x65, 0x05, 0x5a, 0x04, 0xdc, 0x59, 0x92, 0xab, 0x71, 0xb9, 0xc6, 0x2b, 0xa1, 0x82, 0x66, 0x3e, 0x18, 0x15, 0x84, 0x40, 0x41, 0xfa, 0xbf, 0x05, 0x14, 0x84, 0xa0, 0x9f, 0x42, 0x44, 0x90, 0x10, 0x9f, 0xb8, 0x14, 0x22, 0x08, 0xe1, 0x14, 0xb3, 0x60, 0xa0, 0x2c, 0x20, 0x71, 0xfe, 0x23, 0x2c, 0x18, 0xe8, 0xab, 0x59, 0x30, 0x10, 0x82, 0x78, 0xbe, 0xbb, 0x30, 0x38, 0xa0, 0x46, 0xc0, 0x0f, 0xcc, 0x6b, 0xc3, 0x38, 0x39, 0x7d, 0xf6, 0x00, 0xa7, 0xcf, 0x50, 0x09, 0xe6, 0x04, 0x20, 0xc4, 0x01, 0x2c, 0x48, 0xac, 0xcf, 0xcc, 0x80, 0xce, 0xb9, 0xc5, 0x33, 0x99, 0x0a, 0x32, 0x67, 0xf9, 0xac, 0x22, 0x6e, 0xb5, 0xa1, 0xed, 0x0d, 0x17, 0x65, 0x2c, 0x21, 0x28, 0x5a, 0x4c, 0x53, 0x62, 0x41, 0xe2, 0xac, 0xa0, 0x29, 0x42, 0x69, 0xf3, 0x86, 0xc3, 0x1e, 0x04, 0xf6, 0x94, 0x87, 0x7c, 0x5e, 0x8f, 0x3f, 0xb8, 0x94, 0xa8, 0x16, 0xab, 0xa4, 0x25, 0x76, 0xc0, 0x5c, 0x61, 0x71, 0xbc, 0x24, 0x26, 0x91, 0x45, 0xa5, 0x0c, 0xf9, 0x07, 0xd7, 0x2e, 0xe6, 0xc6, 0x11, 0x8a, 0x8b, 0x32, 0x09, 0xf5, 0xd2, 0xfb, 0x5f, 0xc8, 0x85, 0xc6, 0x12, 0xf9, 0xf9, 0x70, 0x47, 0xae, 0xba, 0x11, 0x6d, 0x34, 0x37, 0xd1, 0x82, 0x04, 0x43, 0x8a, 0x48, 0x46, 0x34, 0x87, 0x0b, 0x8e, 0x23, 0xdc, 0xe5, 0x14, 0xaf, 0x7f, 0x31, 0xd2, 0x32, 0x6b, 0x3e, 0xdd, 0x54, 0x4b, 0x4c, 0x99, 0x63, 0x91, 0x49, 0x09, 0x02, 0x1d, 0xc1, 0x20, 0x74, 0xb6, 0xb4, 0x54, 0x56, 0x8a, 0x00, 0x54, 0x6c, 0x7d, 0x72, 0x8e, 0xc5, 0x53, 0xcc, 0x16, 0x65, 0xcf, 0x61, 0x42, 0xcb, 0x22, 0x53, 0xaa, 0x28, 0xa5, 0xfe, 0xde, 0x23, 0x75, 0x13, 0x49, 0xcf, 0x9d, 0x36, 0x7d, 0x72, 0xdf, 0x47, 0x27, 0xe6, 0x9e, 0x3b, 0xd2, 0xd2, 0x75, 0xf2, 0x85, 0x6d, 0x55, 0x7b, 0xb7, 0x4f, 0xfa, 0xaa, 0x95, 0x16, 0x5d, 0xb4, 0x6d, 0xb2, 0xb9, 0x6d, 0x6e, 0x20, 0x42, 0x91, 0xbb, 0x8e, 0x5f, 0x08, 0x75, 0x6e, 0xae, 0x3d, 0xec, 0x2a, 0xdd, 0x75, 0x65, 0x7f, 0x43, 0xdf, 0xd9, 0x97, 0xb6, 0x9f, 0x7a, 0xfd, 0x5c, 0x87, 0x2e, 0xd0, 0x50, 0x84, 0x51, 0x28, 0x25, 0xeb, 0xef, 0x1a, 0xfb, 0xff, 0x65, 0x3e, 0x20, 0x49, 0xb4, 0xc3, 0x35, 0x72, 0x1a, 0xeb, 0xec, 0x3e, 0xe2, 0xdb, 0xec, 0xc4, 0xd7, 0x35, 0x27, 0xa0, 0x87, 0x94, 0x2c, 0xb6, 0x98, 0x68, 0x11, 0x52, 0xbc, 0x80, 0x45, 0x42, 0x67, 0x6e, 0x8a, 0xb9, 0x9b, 0xa3, 0xbc, 0x88, 0x68, 0xa8, 0x2f, 0x68, 0x34, 0xe2, 0x5c, 0x65, 0x14, 0x3e, 0x85, 0x77, 0x77, 0xe6, 0x84, 0x60, 0x67, 0xc6, 0x72, 0xbc, 0x99, 0xd6, 0x7b, 0x58, 0x35, 0x04, 0xd7, 0x15, 0xc5, 0x56, 0xa2, 0xa4, 0x10, 0xb2, 0x77, 0xa5, 0xe6, 0x58, 0x0d, 0x15, 0x07, 0xbc, 0x25, 0xa5, 0x45, 0x3e, 0x5c, 0xdc, 0x3a, 0x93, 0x55, 0x1a, 0x64, 0x78, 0x1d, 0x24, 0xe6, 0xf3, 0xf3, 0x22, 0x34, 0x72, 0x8a, 0xfc, 0xec, 0x24, 0xcb, 0x6e, 0x89, 0xe6, 0x2a, 0xb3, 0xc9, 0x01, 0x32, 0x8a, 0xe9, 0x5d, 0xe0, 0xac, 0x1f, 0x6f, 0xea, 0xdc, 0xd9, 0xe9, 0x87, 0xda, 0xfb, 0x90, 0xa3, 0x2f, 0x56, 0x3f, 0x10, 0x35, 0xf4, 0xd2, 0x74, 0xcb, 0xd1, 0xe7, 0xe6, 0xba, 0x0f, 0x6f, 0x68, 0x8f, 0x98, 0xf4, 0x76, 0xf2, 0xa4, 0xe7, 0xa1, 0xed, 0xcd, 0x1b, 0xb0, 0xc4, 0xab, 0xbc, 0x68, 0x4c, 0xfa, 0xee, 0xd9, 0x3f, 0x6a, 0xfb, 0x81, 0x56, 0x56, 0x52, 0xe5, 0x2e, 0x94, 0x43, 0x85, 0x05, 0x26, 0x7c, 0xbd, 0xcd, 0x45, 0x15, 0x13, 0xa7, 0xd7, 0x28, 0x0b, 0x14, 0x43, 0x2a, 0x73, 0xb0, 0xae, 0xdd, 0xad, 0xb1, 0xd9, 0x6b, 0x5d, 0x68, 0x38, 0xbc, 0xf5, 0xfd, 0xa3, 0x63, 0xc5, 0x76, 0xa3, 0xec, 0x57, 0x3b, 0xb6, 0xa2, 0x01, 0xc1, 0x3b, 0x6a, 0xb0, 0x77, 0x6f, 0x77, 0x97, 0xdf, 0x68, 0xaf, 0x8f, 0xb5, 0xa9, 0x30, 0x59, 0x2c, 0x80, 0x7a, 0xec, 0xcf, 0xd4, 0x38, 0xe6, 0x86, 0xe8, 0x4e, 0x76, 0xc0, 0xcd, 0x44, 0x1c, 0x85, 0x4a, 0xcc, 0xcb, 0x9e, 0x9e, 0xa3, 0x5a, 0x50, 0x04, 0x34, 0x93, 0x50, 0x05, 0x2d, 0x06, 0xce, 0x3e, 0x92, 0xa0, 0x18, 0x32, 0x4b, 0x41, 0xba, 0x3b, 0xc3, 0x3e, 0x0a, 0x15, 0x50, 0x5d, 0x4d, 0xc0, 0x1b, 0xf0, 0x40, 0xe5, 0x22, 0xc9, 0xa0, 0x74, 0xf9, 0xfd, 0x10, 0x97, 0xf6, 0x13, 0x2d, 0x95, 0x59, 0x9d, 0x10, 0x6c, 0x89, 0x94, 0x45, 0xae, 0x57, 0xa9, 0xfc, 0x55, 0x03, 0x35, 0xf1, 0xfe, 0xb8, 0xf5, 0xd6, 0x5d, 0xfb, 0xf6, 0x1d, 0x39, 0xe4, 0xaa, 0x59, 0x13, 0x6b, 0x5a, 0x5b, 0x17, 0xc2, 0x46, 0xc3, 0xc8, 0xe6, 0xd9, 0xf2, 0xc1, 0x4b, 0x7b, 0x9a, 0x67, 0x87, 0x4a, 0x9b, 0x82, 0xba, 0x8a, 0xa9, 0x87, 0xa6, 0xf6, 0x7d, 0xaa, 0x86, 0x5a, 0xa5, 0x95, 0x9b, 0xed, 0x66, 0x53, 0x20, 0xe6, 0x48, 0x0e, 0x9c, 0x18, 0xee, 0xf0, 0x37, 0x45, 0xac, 0x3c, 0x2d, 0x99, 0xb6, 0xa8, 0x7f, 0x6f, 0xc7, 0xba, 0xc3, 0x6e, 0x5d, 0x65, 0x7d, 0xb3, 0x2b, 0x31, 0xd5, 0x53, 0xda, 0x9a, 0xe0, 0xeb, 0x2f, 0xfc, 0x4f, 0xfa, 0xab, 0xb4, 0x94, 0x18, 0x03, 0x45, 0x49, 0x7d, 0x3f, 0x90, 0xc8, 0xc6, 0xcc, 0xa4, 0x58, 0xaa, 0x06, 0x24, 0x34, 0xdf, 0x18, 0x9a, 0xe6, 0xf4, 0x52, 0x82, 0x90, 0x4a, 0xc4, 0x12, 0x29, 0xd2, 0xbf, 0x1c, 0x29, 0x94, 0x98, 0x10, 0x41, 0xbb, 0x71, 0x0a, 0x9f, 0x6d, 0xd1, 0xa8, 0xec, 0xba, 0x44, 0xc2, 0x67, 0x86, 0x72, 0xd0, 0x70, 0xd6, 0x52, 0x57, 0xfc, 0x9d, 0xbd, 0xb3, 0x66, 0xec, 0xdf, 0xfb, 0xb7, 0x93, 0x0d, 0x99, 0x8e, 0x40, 0x4c, 0x88, 0xd1, 0x89, 0x5b, 0xde, 0x17, 0x10, 0x2b, 0xf5, 0x47, 0x0e, 0x82, 0x69, 0xcd, 0x40, 0x4f, 0x77, 0x47, 0x5b, 0x53, 0x43, 0x7d, 0x6d, 0x65, 0x45, 0x59, 0x29, 0xf4, 0x41, 0x3d, 0x41, 0x39, 0x02, 0xb5, 0xa3, 0x09, 0x8c, 0x15, 0x89, 0xc0, 0xf3, 0x6c, 0x40, 0xb6, 0x8d, 0x90, 0x6c, 0x4c, 0x08, 0xee, 0x43, 0xa9, 0xe7, 0x19, 0x5b, 0x59, 0xc0, 0x2b, 0xca, 0x22, 0x18, 0xab, 0xa8, 0x6f, 0xca, 0x65, 0x96, 0x80, 0x0d, 0xcf, 0x65, 0x91, 0xde, 0xd1, 0x1b, 0x6b, 0x80, 0x8b, 0xe0, 0x5f, 0x18, 0x39, 0x53, 0xbe, 0xba, 0xc6, 0xd5, 0xb6, 0xef, 0x52, 0x7f, 0xd9, 0xcc, 0xa6, 0x11, 0x2f, 0xd4, 0xb5, 0xfa, 0x50, 0xed, 0x60, 0x63, 0x7c, 0x6d, 0xad, 0xeb, 0xe0, 0xd1, 0x7d, 0xfb, 0x76, 0xdd, 0x6a, 0x8d, 0xf7, 0x57, 0xd6, 0xae, 0xae, 0xf6, 0xab, 0x54, 0x7a, 0x79, 0x49, 0xcd, 0xe5, 0x43, 0x1b, 0x1f, 0x98, 0xaa, 0x00, 0xe0, 0xc3, 0xe4, 0xbc, 0xa5, 0xa0, 0xce, 0x5a, 0xe2, 0x86, 0x93, 0xa8, 0xa0, 0x16, 0x2e, 0x86, 0xfa, 0x76, 0xb7, 0x6a, 0x35, 0x49, 0x93, 0xea, 0xb2, 0xa6, 0xfe, 0xb2, 0x8e, 0xbd, 0x7d, 0x45, 0xda, 0x40, 0x7d, 0x71, 0x87, 0x1e, 0x4e, 0x0e, 0x6b, 0xa4, 0xd1, 0xdf, 0x39, 0x74, 0x62, 0xa0, 0xd9, 0x1e, 0x0b, 0x98, 0xe0, 0xe4, 0x91, 0x6b, 0x7b, 0x12, 0xad, 0xa5, 0xa9, 0xa9, 0xb8, 0x44, 0x25, 0xa9, 0xc7, 0xfc, 0x3b, 0xd0, 0x57, 0xb1, 0xc0, 0x35, 0xe1, 0x24, 0xaa, 0x51, 0xe5, 0x13, 0x34, 0x19, 0x9c, 0x22, 0x01, 0x01, 0xef, 0x12, 0xfc, 0x81, 0x2b, 0x36, 0xc0, 0x5b, 0x92, 0x1e, 0x15, 0xb7, 0x09, 0xf8, 0x1c, 0x36, 0xcc, 0xbe, 0xea, 0x04, 0x4e, 0xb6, 0x9e, 0x70, 0x42, 0xc8, 0xe7, 0x26, 0x48, 0x65, 0x65, 0x90, 0x46, 0x11, 0xe5, 0x40, 0x59, 0xa1, 0x8c, 0xc9, 0x07, 0xc6, 0x67, 0x2d, 0x55, 0x23, 0x4d, 0x13, 0x77, 0x0e, 0xfa, 0xfd, 0x03, 0x27, 0xc6, 0x93, 0xe3, 0x75, 0x36, 0x73, 0x45, 0x5f, 0xe2, 0x51, 0xb0, 0x6d, 0x68, 0xfa, 0x9f, 0xfe, 0xe1, 0xfc, 0x0f, 0xee, 0xe9, 0x68, 0x87, 0xfa, 0x78, 0xd7, 0xa3, 0x9b, 0x4b, 0xb5, 0xc1, 0x24, 0x39, 0xdc, 0xd3, 0x64, 0x8f, 0x05, 0x2d, 0x15, 0x1b, 0xce, 0x8f, 0x8c, 0xdc, 0xb3, 0xa9, 0xb2, 0xa0, 0xb4, 0x31, 0xe0, 0xad, 0x2f, 0xb5, 0x9e, 0xac, 0x71, 0x9f, 0x9e, 0xb8, 0xff, 0x61, 0x9e, 0x90, 0xa7, 0xac, 0x6f, 0xaa, 0xd2, 0xd9, 0x54, 0x1b, 0xc5, 0x3a, 0x7b, 0x4d, 0xfa, 0x21, 0xfa, 0xd7, 0xcc, 0x33, 0xd0, 0xae, 0xe9, 0x22, 0xbe, 0xcc, 0x71, 0x52, 0x22, 0xbb, 0xa6, 0x0b, 0x10, 0xd2, 0x1a, 0x6c, 0xc4, 0x14, 0x01, 0x46, 0x8c, 0x39, 0x29, 0x91, 0x6d, 0x93, 0x7f, 0x9b, 0xb3, 0x6f, 0x2a, 0x08, 0x11, 0x9c, 0x86, 0x22, 0xf1, 0x5c, 0xae, 0x9d, 0x83, 0x4f, 0x7c, 0xb1, 0x9d, 0x83, 0x4f, 0x93, 0x70, 0x65, 0x83, 0x6c, 0x12, 0x6b, 0x0d, 0x34, 0x78, 0xa4, 0x34, 0x25, 0x9d, 0xe3, 0x3b, 0xe7, 0x18, 0x3e, 0x2b, 0xf5, 0x45, 0xa9, 0x2e, 0x6d, 0x41, 0xaf, 0x3f, 0x88, 0x0d, 0x20, 0x1c, 0x5e, 0xfc, 0x20, 0x03, 0x48, 0x98, 0x75, 0xc8, 0x0a, 0x3e, 0xdf, 0x92, 0xa4, 0x0a, 0x58, 0x3c, 0x39, 0xdc, 0x17, 0x07, 0x8f, 0x26, 0x3c, 0x6d, 0xdb, 0xbb, 0x2b, 0x57, 0x67, 0xf8, 0x55, 0x2a, 0x9b, 0x8f, 0x3c, 0xbf, 0xef, 0x8e, 0xab, 0xf5, 0x11, 0x89, 0x45, 0xaf, 0x2f, 0xaa, 0x5b, 0x0b, 0x95, 0x79, 0x97, 0xaf, 0xe9, 0xe2, 0xec, 0xc4, 0xc5, 0x2d, 0x55, 0x19, 0x03, 0xa9, 0xa1, 0xf2, 0xcb, 0x9f, 0x1f, 0x3f, 0x3d, 0x8c, 0x0c, 0xa4, 0x58, 0x13, 0x26, 0x46, 0x44, 0x94, 0x1a, 0x03, 0x03, 0xef, 0xbf, 0x36, 0xfe, 0xd8, 0xde, 0x64, 0x57, 0x02, 0x27, 0x61, 0xd8, 0x8b, 0xba, 0xa7, 0x6b, 0x0e, 0xf6, 0x0c, 0xb6, 0x9e, 0xf8, 0xca, 0x6d, 0x3b, 0x5f, 0x39, 0xdf, 0x5f, 0x9f, 0xc0, 0x3a, 0xaa, 0x77, 0xe1, 0x2f, 0xd4, 0xf7, 0x68, 0x8a, 0x28, 0x23, 0x76, 0x25, 0x55, 0x0e, 0xa8, 0x96, 0x10, 0x15, 0xb6, 0x1c, 0xbb, 0xc9, 0xdc, 0x8c, 0xe4, 0x32, 0xd4, 0x36, 0xf1, 0x11, 0x77, 0x84, 0xc0, 0x16, 0x5a, 0x0d, 0x2b, 0x35, 0xc0, 0xd6, 0x02, 0x9c, 0x91, 0x41, 0x3f, 0x2a, 0x47, 0x2b, 0x93, 0x32, 0x34, 0x51, 0x06, 0xca, 0xd8, 0x52, 0xc2, 0x09, 0xbe, 0xfe, 0x86, 0x56, 0x48, 0x24, 0x69, 0xca, 0x2b, 0xd4, 0x41, 0x8e, 0xb6, 0x4c, 0x16, 0xdf, 0xa1, 0x35, 0x82, 0x81, 0xc0, 0xda, 0xc9, 0x9d, 0x4d, 0x7b, 0x5e, 0x3e, 0xd7, 0xd3, 0x7d, 0xfe, 0xda, 0xe1, 0x63, 0x57, 0x9b, 0x12, 0xca, 0x90, 0xc5, 0x1c, 0xa9, 0xe9, 0x28, 0xeb, 0xb9, 0x6d, 0x38, 0x52, 0x3e, 0x7a, 0xdb, 0xbd, 0x66, 0xc3, 0x4b, 0x06, 0xcd, 0xce, 0xf4, 0x94, 0x3d, 0x6c, 0x53, 0xf6, 0x9e, 0xff, 0xea, 0xbe, 0x7d, 0x5f, 0x3d, 0xd7, 0xd3, 0x55, 0x3f, 0x6c, 0x90, 0xa9, 0x55, 0x56, 0xbd, 0xa2, 0x62, 0xe2, 0xe4, 0xc0, 0xba, 0x73, 0x1b, 0xca, 0x09, 0x72, 0xe1, 0xcd, 0xf4, 0x25, 0xea, 0xf7, 0xd4, 0x3b, 0x84, 0x1a, 0xda, 0xd4, 0x81, 0xa4, 0x37, 0xe1, 0x57, 0x67, 0xea, 0x79, 0x66, 0xe9, 0xdb, 0xe6, 0x40, 0x4f, 0x3c, 0x16, 0x0a, 0xd8, 0x0a, 0x82, 0x34, 0x4b, 0x48, 0xc4, 0x0d, 0x32, 0xd4, 0x37, 0x65, 0xa0, 0x2a, 0x37, 0x21, 0xb7, 0x09, 0x08, 0xaa, 0x08, 0xb1, 0x38, 0x53, 0xf0, 0xba, 0xd1, 0x22, 0x33, 0xba, 0x8c, 0xde, 0xb0, 0xd5, 0xe0, 0xb9, 0x6c, 0x74, 0xea, 0x24, 0xee, 0x86, 0xb1, 0xba, 0xc6, 0xb1, 0x7a, 0xa7, 0x44, 0xe7, 0x34, 0x35, 0xa6, 0xb7, 0x5d, 0x36, 0x86, 0x10, 0x7b, 0xa7, 0x94, 0x12, 0x2b, 0x80, 0x58, 0x62, 0x37, 0x5c, 0x4e, 0xef, 0x28, 0x2a, 0x19, 0x74, 0x19, 0x83, 0x4e, 0x5d, 0x55, 0x2c, 0xb8, 0xba, 0x68, 0x95, 0xb9, 0x68, 0x55, 0x75, 0xa5, 0x2d, 0x31, 0xd1, 0x1a, 0x0c, 0xb7, 0x4d, 0xc4, 0x6c, 0xb1, 0x9a, 0x96, 0xa2, 0xdb, 0xc8, 0x77, 0x4d, 0xda, 0xf9, 0xbf, 0xca, 0x4d, 0x72, 0xb3, 0x89, 0x94, 0x6a, 0x4d, 0xf3, 0x52, 0x82, 0xe5, 0xfa, 0xff, 0x33, 0x4d, 0xd1, 0x34, 0xb4, 0x7f, 0x52, 0xa8, 0xfa, 0x44, 0x67, 0x85, 0x5b, 0x89, 0x73, 0x8e, 0x69, 0x7c, 0x38, 0xc7, 0xb0, 0x1c, 0x75, 0xd9, 0x11, 0xf1, 0x97, 0x96, 0xfa, 0x8b, 0xfd, 0x38, 0x32, 0x83, 0xde, 0xca, 0xc4, 0xbf, 0x14, 0x97, 0x56, 0xc2, 0x12, 0x94, 0x88, 0x29, 0xb3, 0x13, 0x08, 0x4c, 0xb8, 0x26, 0x40, 0xe5, 0x72, 0x95, 0xb1, 0x28, 0x4f, 0xf0, 0xaf, 0x46, 0x63, 0xb9, 0xd7, 0x59, 0x6e, 0x36, 0x04, 0xaf, 0x98, 0xca, 0x6a, 0x3b, 0x23, 0x81, 0x90, 0x16, 0x78, 0xbd, 0xb1, 0xb0, 0x5b, 0xf3, 0x72, 0x8b, 0xd7, 0xee, 0x69, 0x5a, 0x5f, 0x53, 0xbb, 0xbe, 0xc1, 0xed, 0x2e, 0x6c, 0x7a, 0x45, 0xe3, 0x09, 0x55, 0x16, 0x7a, 0x49, 0x6d, 0x30, 0x10, 0xe9, 0xac, 0x2d, 0x33, 0x9d, 0x95, 0x6b, 0x24, 0x22, 0x8d, 0x1c, 0xb4, 0x7b, 0x62, 0x96, 0x60, 0xac, 0xa8, 0xd8, 0x9e, 0x70, 0xd5, 0x93, 0x94, 0xab, 0xcc, 0xad, 0x55, 0x18, 0x6c, 0xea, 0x4e, 0xb9, 0xc9, 0x67, 0x49, 0xff, 0x71, 0x63, 0x6c, 0x63, 0x51, 0x6c, 0xb4, 0xd9, 0x1f, 0x48, 0x0e, 0x95, 0x95, 0x4d, 0x95, 0x6f, 0x00, 0x26, 0x8b, 0xd7, 0x2c, 0xef, 0x54, 0xdb, 0x0c, 0x0a, 0xad, 0xbb, 0xcc, 0xf5, 0x1d, 0x95, 0x53, 0xaf, 0x77, 0xaa, 0xd0, 0x3c, 0x3e, 0x9e, 0x7e, 0x9a, 0xfa, 0x31, 0x1c, 0x53, 0x23, 0xe2, 0xc9, 0x36, 0x60, 0xc0, 0x38, 0x3a, 0xaf, 0x40, 0x45, 0x0f, 0x48, 0x01, 0xf1, 0x47, 0x16, 0xb9, 0x6c, 0x24, 0x8c, 0x5e, 0xa3, 0x97, 0xc3, 0x80, 0x66, 0xb2, 0xd0, 0x78, 0xb7, 0xae, 0x4a, 0x4b, 0xfd, 0x98, 0x87, 0x29, 0x5f, 0x9f, 0xde, 0x92, 0x3a, 0x51, 0x43, 0xbd, 0xf3, 0xde, 0x0b, 0xe0, 0xc7, 0x2d, 0xfd, 0x08, 0x9f, 0x8c, 0xc8, 0x9b, 0x77, 0x1d, 0xee, 0x68, 0x24, 0x7f, 0x9b, 0xc1, 0x87, 0xfe, 0x0a, 0xfa, 0x68, 0x98, 0x17, 0x11, 0xf3, 0x1a, 0xd9, 0xe1, 0x42, 0x42, 0x31, 0x0e, 0x2d, 0xb4, 0xbb, 0x30, 0xaf, 0x91, 0x1d, 0x88, 0x04, 0x37, 0x46, 0x05, 0x11, 0x48, 0x9e, 0x3e, 0x3b, 0x93, 0xeb, 0x83, 0x11, 0xd6, 0x00, 0x27, 0xdf, 0x31, 0x22, 0x68, 0x91, 0xc3, 0xbd, 0x95, 0x73, 0x39, 0x96, 0x68, 0x85, 0xb3, 0x96, 0xfc, 0x7a, 0xaf, 0x91, 0xcd, 0x5a, 0x12, 0x30, 0xa9, 0xf1, 0x10, 0xec, 0x25, 0xc2, 0x44, 0x82, 0xf7, 0x3a, 0x59, 0xbd, 0x88, 0xf3, 0x8b, 0x3a, 0xc4, 0x82, 0xb0, 0xe7, 0x0f, 0xf3, 0xc0, 0xec, 0x45, 0xe1, 0x20, 0x8c, 0xcd, 0xbe, 0x88, 0xdf, 0xb9, 0x8a, 0xb8, 0xc1, 0xe5, 0x1e, 0x22, 0x2c, 0x82, 0x01, 0x88, 0xa9, 0x04, 0x90, 0x88, 0xfd, 0xd0, 0xb0, 0xa3, 0x79, 0xca, 0xa2, 0xfc, 0x4f, 0x44, 0x48, 0xab, 0xc3, 0x9b, 0x64, 0xde, 0xcd, 0x51, 0x2e, 0x2d, 0x31, 0x98, 0x49, 0xf7, 0xde, 0x81, 0x94, 0x2f, 0xe7, 0x77, 0xa1, 0x23, 0x25, 0x36, 0xbd, 0x0b, 0xcb, 0x62, 0x2e, 0x43, 0xb3, 0x16, 0x21, 0x18, 0x89, 0x48, 0x02, 0xbd, 0xb5, 0x15, 0xba, 0xf1, 0x12, 0x44, 0xbd, 0x92, 0xf1, 0x0c, 0xdb, 0x11, 0xd7, 0x91, 0x97, 0xea, 0x8a, 0xfd, 0xa0, 0xc9, 0x82, 0x38, 0x1f, 0xab, 0x88, 0x2a, 0xbf, 0xc7, 0x1f, 0x86, 0xff, 0xfc, 0xa8, 0x10, 0x40, 0x6c, 0x91, 0x7c, 0x1d, 0x99, 0x41, 0x58, 0xcc, 0xd0, 0xc9, 0x0d, 0xca, 0xb9, 0x7c, 0x91, 0x73, 0x03, 0xe2, 0x69, 0xdb, 0xd1, 0x1d, 0x1b, 0x28, 0xd5, 0xe0, 0x9a, 0x1f, 0xee, 0x86, 0x4a, 0x3c, 0x3e, 0xb9, 0xc2, 0xff, 0x11, 0x37, 0x36, 0xa9, 0x26, 0x41, 0x26, 0x3a, 0x79, 0x9d, 0x1d, 0x27, 0xa4, 0x0b, 0x10, 0xc9, 0xdd, 0x8f, 0x99, 0xeb, 0x84, 0x82, 0xf0, 0x12, 0xab, 0x92, 0x49, 0x17, 0x20, 0xd0, 0xfa, 0x27, 0xc8, 0x6e, 0xb1, 0x84, 0x04, 0x34, 0x4a, 0x2b, 0x23, 0xa7, 0x78, 0x86, 0x5b, 0xde, 0x7c, 0xc0, 0xb4, 0x49, 0x04, 0xe1, 0x2d, 0xf4, 0xb8, 0x91, 0xd9, 0xa0, 0x51, 0xab, 0x94, 0xa8, 0x5c, 0xba, 0x1f, 0xfb, 0xb1, 0x3c, 0xd0, 0x18, 0x2e, 0x0b, 0xc6, 0x8b, 0x4b, 0x12, 0xe6, 0x04, 0x41, 0x62, 0x60, 0x87, 0xc4, 0x50, 0x68, 0x2d, 0x8b, 0xb4, 0x54, 0x83, 0xcb, 0x9f, 0xbe, 0xde, 0x5c, 0x3f, 0xd5, 0x11, 0x74, 0x55, 0x75, 0x97, 0x14, 0x25, 0x2d, 0xd7, 0x3f, 0x4d, 0x11, 0x1d, 0x6a, 0x57, 0x81, 0x26, 0xe2, 0x0e, 0x14, 0xdd, 0x3b, 0x7f, 0x2f, 0x79, 0x8b, 0xa3, 0x66, 0x20, 0x5e, 0x96, 0x8a, 0x3b, 0x0a, 0x8c, 0xe0, 0x27, 0xf3, 0xf5, 0x78, 0x1e, 0x9d, 0x4d, 0x5f, 0x24, 0x2f, 0xe0, 0xfc, 0x88, 0x06, 0x76, 0x38, 0xb5, 0x18, 0xfe, 0x88, 0x74, 0x71, 0x36, 0xe3, 0xdf, 0x02, 0x37, 0x7e, 0x04, 0xe6, 0x9e, 0x63, 0x51, 0xb5, 0x82, 0x14, 0xff, 0x17, 0x3d, 0x85, 0x46, 0xb6, 0xb6, 0x9b, 0x60, 0x01, 0x73, 0xde, 0x0f, 0x79, 0x81, 0x4b, 0xc0, 0xb9, 0x7e, 0xd9, 0x1f, 0x52, 0xaa, 0x2c, 0xa2, 0xf4, 0x17, 0xd9, 0x3c, 0x1b, 0xe6, 0xe5, 0xf7, 0x22, 0xaf, 0xe3, 0xc4, 0x1a, 0x34, 0x8f, 0x2f, 0xd1, 0x93, 0xf0, 0xef, 0xc7, 0x88, 0x8e, 0x64, 0xab, 0x0f, 0x50, 0xb4, 0x1a, 0xb3, 0x11, 0x64, 0xc8, 0xc4, 0x00, 0x83, 0xfc, 0xff, 0x39, 0x56, 0x64, 0x34, 0xcd, 0x1f, 0x41, 0xe2, 0xb0, 0x48, 0xf6, 0x39, 0x70, 0x95, 0x24, 0x6f, 0x30, 0x8c, 0x63, 0x22, 0x2b, 0x53, 0x87, 0x20, 0x74, 0x3f, 0x0b, 0x13, 0xc7, 0xe5, 0xd2, 0x7e, 0xb5, 0xf9, 0xbe, 0xc9, 0xc8, 0xf2, 0xfc, 0x21, 0x67, 0xb4, 0x52, 0xe0, 0x49, 0x6b, 0xaf, 0x5d, 0xc3, 0x14, 0x22, 0x6f, 0x48, 0x1b, 0x37, 0xdd, 0xd9, 0x3b, 0x79, 0xf9, 0x58, 0x57, 0x96, 0x42, 0xa4, 0xa8, 0x6f, 0x77, 0x6b, 0xed, 0x86, 0x56, 0x5f, 0x93, 0xd6, 0xad, 0x04, 0xf7, 0xdf, 0x78, 0x9c, 0xb9, 0x9e, 0xae, 0xc4, 0x44, 0x22, 0x50, 0x27, 0x2e, 0xbc, 0xc9, 0xfc, 0x01, 0xea, 0xc4, 0x10, 0x31, 0x96, 0x94, 0x07, 0xe5, 0xd0, 0xf7, 0x65, 0x30, 0x21, 0x1f, 0x57, 0x2f, 0x90, 0x2d, 0x45, 0xcc, 0xa2, 0x71, 0x89, 0xf1, 0x45, 0x35, 0x8a, 0x67, 0x05, 0x35, 0x8a, 0xc7, 0x73, 0x6a, 0x14, 0x87, 0x88, 0x10, 0x54, 0x9d, 0xfa, 0x80, 0x4f, 0x58, 0xa3, 0x18, 0x43, 0x64, 0xd0, 0x4e, 0x1e, 0xe0, 0x95, 0x68, 0xa3, 0x50, 0x9d, 0x32, 0x7f, 0x38, 0x10, 0x52, 0x28, 0xab, 0xcf, 0x8d, 0xcf, 0x1c, 0x77, 0x98, 0xdb, 0x87, 0x36, 0x97, 0x7f, 0xea, 0xf9, 0xeb, 0x53, 0x1b, 0x8a, 0x7a, 0x6a, 0x3c, 0xd7, 0x27, 0xc7, 0x9a, 0x76, 0x96, 0x22, 0xcc, 0x7d, 0x70, 0x63, 0xcb, 0xd0, 0x9e, 0xc9, 0x44, 0x5f, 0xb9, 0xe5, 0x1f, 0xbe, 0x84, 0x54, 0xec, 0xde, 0x03, 0x8e, 0xda, 0xe1, 0x1a, 0xf4, 0xdb, 0x81, 0xbd, 0x2d, 0x35, 0xe4, 0xdb, 0x68, 0xbc, 0x6e, 0x4f, 0x3f, 0xc9, 0xfc, 0x11, 0xbe, 0x53, 0x11, 0xe2, 0x76, 0x09, 0xa3, 0x77, 0x82, 0x9a, 0x10, 0xbe, 0x93, 0xf0, 0x65, 0xb8, 0x92, 0xca, 0x59, 0x5d, 0x5f, 0x44, 0x14, 0xc1, 0x07, 0x36, 0x06, 0xfc, 0x5a, 0xee, 0x81, 0xb1, 0xc9, 0x11, 0x10, 0x56, 0x54, 0x5e, 0xf4, 0xbc, 0x7f, 0xbc, 0x6e, 0xab, 0x5b, 0x95, 0x2a, 0x9e, 0xb9, 0x9d, 0x7d, 0xdc, 0x81, 0xdb, 0xd7, 0x45, 0xae, 0x4d, 0x4f, 0xc2, 0x07, 0x2e, 0xbc, 0xb6, 0x61, 0xac, 0x11, 0x3d, 0xf0, 0x7b, 0xcf, 0x82, 0x5f, 0xb8, 0xe3, 0x21, 0xd3, 0xee, 0xc9, 0x44, 0x6f, 0xb9, 0x25, 0x36, 0x75, 0xdf, 0xf8, 0x92, 0x0f, 0x8d, 0x75, 0x25, 0xf3, 0xbf, 0xa0, 0x9d, 0x5f, 0x47, 0x3c, 0xcf, 0x6a, 0x38, 0x79, 0x5c, 0x44, 0x8a, 0x98, 0x02, 0x7c, 0xb4, 0x67, 0xcb, 0x5e, 0xd1, 0x19, 0xe6, 0x5c, 0x97, 0x18, 0xa7, 0xb1, 0x22, 0x1d, 0x09, 0x75, 0x3d, 0x2a, 0xd8, 0x33, 0x9d, 0xca, 0x98, 0xb8, 0xd9, 0x70, 0x28, 0x9b, 0x9e, 0xcd, 0xb6, 0x05, 0x60, 0x99, 0xa6, 0xe8, 0x34, 0x50, 0x44, 0x8b, 0x28, 0x8c, 0xb8, 0x5b, 0xaa, 0x35, 0x6f, 0xfe, 0xb2, 0xa1, 0x50, 0x55, 0x6d, 0x35, 0xdc, 0x4e, 0x90, 0x8e, 0xe3, 0x2c, 0x5c, 0xae, 0x58, 0x2f, 0xb5, 0x92, 0x52, 0x53, 0xe5, 0xee, 0x39, 0xe8, 0x3c, 0xa7, 0x89, 0x1d, 0x6b, 0x53, 0xc7, 0xd0, 0xa6, 0x8a, 0x81, 0xdb, 0x47, 0x22, 0xf9, 0xca, 0x4d, 0x38, 0xf8, 0x88, 0x77, 0x81, 0x9a, 0x5b, 0x85, 0x07, 0xbf, 0xc2, 0x1c, 0xdb, 0x7c, 0xff, 0x78, 0xbe, 0x82, 0xdb, 0xb7, 0x1f, 0x49, 0x15, 0x6d, 0x49, 0x07, 0xe7, 0x5a, 0x6a, 0xe6, 0x55, 0xdc, 0x79, 0x0f, 0x58, 0xb8, 0x13, 0xed, 0x43, 0x50, 0xb6, 0x45, 0x7c, 0x0d, 0x35, 0x99, 0x1e, 0x6e, 0xb1, 0x76, 0x80, 0x4a, 0xea, 0xd9, 0x32, 0x17, 0x4c, 0x46, 0xb2, 0x76, 0xbc, 0x6a, 0x45, 0x80, 0xcf, 0xaa, 0x65, 0xf9, 0x9b, 0xc7, 0x72, 0x88, 0x9e, 0xdd, 0x2c, 0x55, 0x45, 0x4e, 0xf6, 0x6d, 0x6e, 0xbb, 0x64, 0x90, 0xa0, 0x01, 0xbc, 0xc3, 0xcc, 0x2d, 0xd5, 0x94, 0xf7, 0x74, 0xe7, 0x58, 0xd2, 0x7f, 0x14, 0x2e, 0xf5, 0x1a, 0x7d, 0xec, 0x06, 0x4d, 0xe5, 0x6d, 0xd0, 0x8b, 0xe5, 0x89, 0x84, 0x97, 0xcc, 0x90, 0x50, 0xb0, 0x9b, 0x74, 0x9e, 0x57, 0x10, 0x47, 0xf2, 0xda, 0xc1, 0xf3, 0x4d, 0xf0, 0x1b, 0xf5, 0x8d, 0xff, 0x12, 0xca, 0x8c, 0x8e, 0x07, 0xb9, 0x7d, 0x9a, 0xfe, 0x2d, 0x96, 0xcf, 0x3d, 0x2f, 0x9a, 0xd4, 0x38, 0x77, 0x94, 0x17, 0x04, 0x8a, 0x85, 0x73, 0xe9, 0x3f, 0xe8, 0x68, 0x33, 0x93, 0x39, 0x3d, 0x47, 0x64, 0x05, 0xc1, 0x65, 0x4a, 0x0b, 0x8f, 0x40, 0x73, 0xda, 0x21, 0x14, 0x08, 0x89, 0xca, 0x45, 0xcc, 0x2d, 0xd5, 0x94, 0xc8, 0xb6, 0x44, 0x82, 0xf0, 0x04, 0x03, 0x1e, 0x7d, 0x50, 0x90, 0x5f, 0x4d, 0x2e, 0xcd, 0xd8, 0x21, 0x28, 0x69, 0xf4, 0x5b, 0x2e, 0x96, 0xbe, 0x04, 0x3d, 0x07, 0x1f, 0x4f, 0x47, 0x5a, 0x1d, 0xc5, 0xd0, 0xc9, 0xab, 0x8b, 0xa9, 0x38, 0xe6, 0xeb, 0xd9, 0x40, 0x3a, 0x6b, 0xa3, 0xfd, 0x12, 0xdb, 0x87, 0x8e, 0xa4, 0x15, 0x27, 0x41, 0xcd, 0x62, 0xf0, 0xf0, 0x34, 0x6f, 0x0c, 0x7a, 0xcc, 0x1e, 0x06, 0x85, 0x1f, 0xb2, 0x35, 0xc7, 0xbd, 0xd9, 0x1a, 0xe3, 0xd4, 0x2f, 0x37, 0x9a, 0x9d, 0xaa, 0xf9, 0x3f, 0x92, 0xe4, 0x93, 0x9b, 0x2d, 0x16, 0x35, 0x69, 0x22, 0x29, 0xa4, 0xb0, 0xd4, 0x3a, 0xf2, 0x09, 0x73, 0xa9, 0x61, 0xfe, 0x21, 0xea, 0x1d, 0xb9, 0x61, 0x7e, 0xbc, 0x20, 0x6a, 0x24, 0x67, 0x08, 0x0a, 0xfb, 0xf5, 0x85, 0x50, 0xe6, 0x22, 0x68, 0x91, 0x7b, 0x88, 0xad, 0x5c, 0xb9, 0x89, 0x25, 0x0a, 0x89, 0xef, 0x16, 0x16, 0x12, 0xb7, 0x7d, 0x40, 0x13, 0x7c, 0x4a, 0x64, 0x55, 0xab, 0x10, 0x58, 0xa0, 0xc0, 0xac, 0xf2, 0xa8, 0x3d, 0x62, 0xa5, 0x58, 0x29, 0x97, 0xc2, 0x3f, 0x23, 0x62, 0x43, 0xce, 0xf8, 0xc8, 0xb0, 0x8a, 0xaf, 0x6d, 0x9a, 0x97, 0x14, 0x04, 0xac, 0xd2, 0x0b, 0xe7, 0xee, 0xba, 0x57, 0x0a, 0xe4, 0xf7, 0x9e, 0x3a, 0x73, 0x41, 0x0e, 0x5c, 0x73, 0x57, 0x6f, 0x6f, 0x6d, 0xbd, 0xfd, 0x2a, 0x4f, 0xad, 0x0b, 0xbe, 0xfb, 0xd8, 0x53, 0x4f, 0x3d, 0xb6, 0xfd, 0xa1, 0x27, 0x9e, 0x78, 0x08, 0x7c, 0xbf, 0xeb, 0xdc, 0x6b, 0xb7, 0xde, 0xfa, 0xf5, 0xb3, 0x5d, 0xbc, 0xeb, 0xce, 0x9e, 0xad, 0xf6, 0x2d, 0xfc, 0x95, 0xf9, 0x01, 0xf4, 0x15, 0xf5, 0x44, 0x14, 0xd5, 0x91, 0x40, 0x45, 0x08, 0x65, 0x41, 0x38, 0xe2, 0x66, 0x94, 0x29, 0x81, 0x56, 0x1b, 0xbc, 0x60, 0xd8, 0x8b, 0x51, 0xf6, 0x63, 0x2f, 0x21, 0x46, 0x15, 0x1c, 0x25, 0x9b, 0x44, 0x99, 0x72, 0xe2, 0xbb, 0x53, 0x72, 0x29, 0x4a, 0x1e, 0x11, 0x1c, 0xb5, 0xd7, 0xe4, 0xb6, 0xe2, 0x5c, 0xc8, 0x0c, 0x77, 0x27, 0xd7, 0x7e, 0x14, 0x4f, 0x29, 0xf6, 0x22, 0xeb, 0x5c, 0xba, 0x30, 0x8b, 0x52, 0xd4, 0x80, 0xe8, 0x9d, 0x4b, 0x8a, 0x8b, 0xc2, 0x21, 0xbf, 0xd7, 0x56, 0x60, 0x32, 0xb2, 0x25, 0xc9, 0x15, 0x4b, 0x94, 0x24, 0xf7, 0x8b, 0x3d, 0xf1, 0x5c, 0x07, 0x1c, 0x95, 0x1b, 0xc6, 0x94, 0xa8, 0x64, 0x74, 0xd5, 0x8e, 0xee, 0x50, 0xa8, 0x7b, 0xc7, 0xaa, 0x56, 0xf6, 0x27, 0xf5, 0xdf, 0xc1, 0xfc, 0x87, 0xea, 0x3f, 0x3d, 0xbb, 0xf5, 0xe9, 0xc3, 0xab, 0x56, 0x1d, 0x7e, 0x7a, 0xeb, 0xec, 0xa7, 0xeb, 0xc9, 0xed, 0xe9, 0x46, 0x30, 0x95, 0xfe, 0xe8, 0xc5, 0x8a, 0xc9, 0x33, 0x6b, 0x07, 0xcf, 0x4c, 0xc6, 0x62, 0x93, 0x67, 0x06, 0xd7, 0x9e, 0x99, 0xac, 0x10, 0x3d, 0x7c, 0xb2, 0x25, 0xd5, 0x77, 0xf7, 0xab, 0x73, 0x7b, 0x5e, 0xbd, 0xbb, 0x2f, 0xb5, 0xea, 0xd4, 0x23, 0xe9, 0xdf, 0x5d, 0x4c, 0xff, 0x07, 0xc1, 0xe6, 0x7e, 0xbd, 0xc5, 0x3c, 0x0d, 0xe7, 0x83, 0x83, 0xad, 0x50, 0x5c, 0x8a, 0x46, 0xb8, 0x1b, 0xc5, 0x32, 0x00, 0x3a, 0x0b, 0xc5, 0xf4, 0xe8, 0x58, 0x30, 0x24, 0x3e, 0x10, 0x44, 0xeb, 0x0b, 0x4e, 0x07, 0x97, 0x33, 0x56, 0x1e, 0x0e, 0x3a, 0xeb, 0x5c, 0x75, 0xc6, 0x40, 0xc0, 0x27, 0x41, 0x26, 0x4e, 0x96, 0xc9, 0x4d, 0x10, 0xe7, 0x6c, 0x00, 0x31, 0x23, 0x55, 0x41, 0x67, 0x9d, 0xb5, 0x60, 0xc6, 0x9d, 0x06, 0x3e, 0x6a, 0xd2, 0x58, 0xdc, 0x52, 0xea, 0xeb, 0x4d, 0x16, 0xfd, 0xcf, 0xbf, 0xfe, 0xf5, 0x9d, 0xe2, 0xe6, 0x3e, 0x6f, 0x49, 0x73, 0xb1, 0xf1, 0xda, 0xc6, 0x97, 0x00, 0xf3, 0x64, 0x6f, 0xed, 0xb6, 0x07, 0xd6, 0x25, 0xd6, 0x77, 0x55, 0x1b, 0x8c, 0x35, 0x5d, 0xa3, 0xf1, 0xe1, 0x07, 0xb6, 0xd6, 0xf6, 0x7d, 0x1a, 0x2a, 0xb5, 0x4f, 0x36, 0x6e, 0xe9, 0x0a, 0x87, 0xd6, 0x1c, 0x5f, 0xf7, 0xcd, 0xaf, 0xbf, 0x76, 0x7d, 0xf8, 0xf6, 0xb5, 0x45, 0xe1, 0xae, 0x2d, 0x8d, 0xdf, 0xff, 0x3e, 0x39, 0x33, 0xf1, 0xd8, 0x77, 0xe6, 0x66, 0x3e, 0x77, 0x22, 0x65, 0x2d, 0x6b, 0x0e, 0x06, 0x9b, 0xcb, 0xac, 0x3d, 0xa7, 0x3e, 0x3f, 0xb5, 0xe7, 0xbf, 0x3d, 0x36, 0x81, 0xed, 0x38, 0xe5, 0xc2, 0x9f, 0xc8, 0xb5, 0x70, 0x7d, 0x59, 0x10, 0x17, 0x09, 0x22, 0xcf, 0x96, 0x9b, 0x45, 0x70, 0x0e, 0x2b, 0xb0, 0x35, 0x65, 0xcb, 0x5e, 0x51, 0x19, 0x7c, 0x86, 0x9e, 0xcd, 0xcc, 0x63, 0x2d, 0x3d, 0x6e, 0x29, 0xf2, 0xc4, 0xdb, 0x5c, 0xd2, 0x1e, 0xb2, 0x3f, 0x28, 0x3e, 0x27, 0x06, 0x93, 0x36, 0x3a, 0x79, 0x22, 0x37, 0x41, 0x0b, 0x61, 0x3d, 0x63, 0x96, 0x7b, 0xdb, 0x42, 0x58, 0x0a, 0x0d, 0x7e, 0x0f, 0xe7, 0xdb, 0x61, 0x28, 0x2f, 0x57, 0x5d, 0x88, 0x2b, 0xb7, 0x57, 0xa5, 0x25, 0xd7, 0xee, 0x75, 0x59, 0x18, 0x45, 0xf4, 0xee, 0x4d, 0x9e, 0x3a, 0x93, 0x52, 0x6c, 0x55, 0x55, 0xbb, 0xcb, 0x3a, 0x6b, 0x4a, 0xf4, 0x68, 0x59, 0x9b, 0x1d, 0xf7, 0x4d, 0xed, 0x53, 0xa9, 0x2f, 0xda, 0xe4, 0xb6, 0x78, 0x6f, 0x05, 0x79, 0x24, 0xf3, 0x7e, 0xd0, 0x4e, 0xfc, 0x1b, 0xdf, 0x6f, 0x66, 0xf9, 0xf7, 0x9b, 0xf9, 0xc0, 0xf7, 0x9b, 0xf9, 0x7f, 0xe1, 0xfd, 0x98, 0xeb, 0xef, 0xd5, 0xe7, 0xbe, 0x1f, 0xdd, 0x81, 0xdf, 0x0f, 0xee, 0xa3, 0x63, 0x70, 0xfc, 0x02, 0xc4, 0xd3, 0xdc, 0xfb, 0x59, 0xa5, 0xf0, 0x8d, 0xd4, 0x70, 0xff, 0xc4, 0xef, 0xc7, 0x5f, 0x51, 0x19, 0xa6, 0x2e, 0x3b, 0xc1, 0x0f, 0x1f, 0xe7, 0x5a, 0xe7, 0x6d, 0x20, 0x25, 0x78, 0xfd, 0xf3, 0xbc, 0x1b, 0x79, 0x6d, 0x09, 0xe1, 0x1e, 0x52, 0x94, 0x61, 0xe7, 0x13, 0xb6, 0x16, 0xf8, 0xec, 0x79, 0xdb, 0x08, 0xf4, 0x76, 0x0d, 0x39, 0xdb, 0x48, 0xc3, 0xe2, 0xf7, 0x17, 0x1e, 0xcf, 0x8e, 0x71, 0x9b, 0x48, 0xe4, 0x44, 0x9e, 0x30, 0xae, 0xe7, 0xef, 0x21, 0x6f, 0x4e, 0xed, 0x15, 0x8c, 0xfb, 0xfb, 0xaf, 0x73, 0xc7, 0xb0, 0x78, 0x1f, 0x7d, 0x9a, 0xfc, 0x1e, 0x2d, 0x87, 0xa2, 0x7f, 0x88, 0x1d, 0x5e, 0x19, 0xd2, 0xd4, 0x5a, 0xc0, 0x51, 0xf3, 0x96, 0x70, 0x77, 0x68, 0xee, 0x0e, 0xba, 0x20, 0xb9, 0x8b, 0x51, 0x6e, 0x42, 0x2c, 0x4d, 0xcc, 0x5b, 0xf2, 0x7f, 0x44, 0xcc, 0xeb, 0x81, 0xa2, 0x10, 0x94, 0xef, 0x36, 0x8b, 0x45, 0xd8, 0x0b, 0x15, 0x94, 0x11, 0x6d, 0x2e, 0x3f, 0x9f, 0xf7, 0xd6, 0xd7, 0xe6, 0xdc, 0x66, 0x86, 0x8a, 0x4f, 0xef, 0x53, 0x73, 0xaf, 0x5a, 0x7e, 0xa3, 0x8c, 0xe9, 0x35, 0x3b, 0x78, 0xbf, 0x1e, 0xbf, 0x67, 0x98, 0xf8, 0x3d, 0x3b, 0xcc, 0x6a, 0x39, 0x54, 0xe8, 0x26, 0x54, 0x1e, 0xce, 0x03, 0x44, 0xa8, 0x8e, 0x06, 0xfb, 0xc8, 0xfc, 0x6d, 0x86, 0xbf, 0x8d, 0xee, 0x50, 0xc2, 0x3b, 0xfc, 0x6b, 0x5b, 0xb2, 0x2b, 0x60, 0x36, 0xb7, 0xec, 0x1c, 0x3f, 0x8f, 0xd0, 0x07, 0x99, 0xb9, 0xb1, 0x3b, 0xbf, 0x0d, 0x3b, 0x8f, 0xb8, 0xfa, 0x74, 0xf9, 0x6d, 0x09, 0x61, 0x15, 0xbb, 0x30, 0x41, 0x52, 0x0c, 0xc5, 0x32, 0x8d, 0x53, 0x88, 0x69, 0x9c, 0xe3, 0xe2, 0x5d, 0xdc, 0x16, 0x1d, 0x40, 0xc8, 0xfc, 0x9e, 0x20, 0x5c, 0x45, 0x6e, 0x4c, 0xcd, 0xcb, 0x02, 0xdb, 0xf2, 0x45, 0xb8, 0xa8, 0x76, 0xdd, 0x39, 0x24, 0x3a, 0x45, 0xf4, 0xfc, 0xe6, 0x5c, 0x89, 0xc6, 0xe1, 0x34, 0x42, 0x25, 0xeb, 0xd4, 0xdc, 0x44, 0xa3, 0xde, 0x31, 0x3b, 0xee, 0x9f, 0x61, 0xe5, 0x6b, 0xaf, 0xec, 0x2d, 0x9f, 0xef, 0xc0, 0x13, 0x69, 0x35, 0x3b, 0xc9, 0xb0, 0x9c, 0xaf, 0xc1, 0x8d, 0xe1, 0xdb, 0x34, 0xdc, 0xa5, 0x88, 0x41, 0x5c, 0x5d, 0xf2, 0xaa, 0x0c, 0x73, 0xf2, 0xda, 0xe0, 0x4f, 0x8a, 0xe5, 0xf7, 0x40, 0x25, 0x27, 0xb5, 0xc2, 0x57, 0xe5, 0xdc, 0x33, 0x0b, 0x82, 0xd0, 0x50, 0x24, 0x31, 0x27, 0x78, 0x33, 0xfc, 0x11, 0x74, 0x86, 0xfd, 0x7e, 0x9c, 0x88, 0xec, 0x59, 0x14, 0xf6, 0x31, 0x82, 0xbb, 0xae, 0xe7, 0x9e, 0xb5, 0x5f, 0x26, 0x2f, 0xae, 0x39, 0x3f, 0x95, 0xd0, 0xe9, 0x87, 0xe1, 0xe5, 0xeb, 0xf8, 0x99, 0xa0, 0xb3, 0x41, 0x4a, 0xf0, 0x33, 0xb5, 0x5e, 0x45, 0x05, 0x51, 0x01, 0x57, 0x94, 0x5a, 0x9b, 0xc9, 0xb2, 0xe2, 0x83, 0x05, 0xe8, 0x29, 0x00, 0x42, 0x4f, 0x61, 0x46, 0xcd, 0x9c, 0x8f, 0x46, 0x5f, 0x84, 0xce, 0x83, 0x07, 0xbb, 0xe4, 0x95, 0xb8, 0x1c, 0x70, 0xf6, 0x34, 0x46, 0xeb, 0x05, 0xad, 0x9c, 0x2b, 0x6e, 0xe2, 0x5d, 0x73, 0xf2, 0xca, 0xeb, 0xf0, 0x7a, 0x98, 0x75, 0xcc, 0xe7, 0x77, 0xb1, 0x18, 0x2e, 0xb8, 0xc1, 0x3f, 0x0e, 0x75, 0x91, 0x0c, 0x5a, 0x4f, 0x89, 0x64, 0xcc, 0xc5, 0xd6, 0x95, 0xcc, 0x3f, 0xf7, 0x98, 0xcd, 0x56, 0x95, 0xb4, 0xe4, 0x54, 0x94, 0x14, 0x2f, 0xaa, 0x44, 0x48, 0x71, 0x39, 0xfe, 0x1e, 0xee, 0x4c, 0x08, 0x2e, 0x8b, 0x97, 0x13, 0xfd, 0xf1, 0x82, 0x2d, 0xdb, 0x0e, 0x93, 0xe3, 0x9f, 0x89, 0x9a, 0x7f, 0x22, 0x77, 0xd8, 0x07, 0x5e, 0x01, 0xf1, 0x2d, 0x0f, 0x4d, 0x96, 0xee, 0xdf, 0xe9, 0x2b, 0x20, 0x8d, 0x70, 0x8f, 0x0b, 0xf4, 0x0e, 0xa6, 0x9f, 0x9b, 0xff, 0x7d, 0x00, 0xb4, 0xea, 0x34, 0xe9, 0xfd, 0xd4, 0x3b, 0xc5, 0xbd, 0x3b, 0x9b, 0xb7, 0x3c, 0xe2, 0x31, 0x06, 0xf0, 0x33, 0x9e, 0x85, 0x4f, 0xf3, 0x89, 0xec, 0x33, 0x2a, 0xd9, 0x2c, 0x7f, 0x0e, 0xe3, 0xc3, 0x70, 0x59, 0xae, 0xb0, 0xe1, 0x38, 0xc1, 0x3d, 0xa3, 0xc7, 0x65, 0x43, 0xa5, 0x2f, 0x0d, 0xd9, 0x67, 0xf4, 0xb3, 0x87, 0x32, 0xe8, 0x19, 0x45, 0xfc, 0xc1, 0xae, 0x87, 0x2b, 0x2c, 0xa8, 0x4f, 0xd4, 0x01, 0xf0, 0xea, 0xd6, 0x2d, 0x05, 0x95, 0xfd, 0x89, 0x43, 0x60, 0xa2, 0xc0, 0xb7, 0x73, 0x7f, 0xc9, 0x86, 0x4b, 0x5b, 0xd2, 0xff, 0xf8, 0xca, 0x80, 0xc3, 0x21, 0xff, 0x89, 0xb1, 0xfc, 0x33, 0x97, 0x06, 0x7b, 0x03, 0x70, 0x13, 0x4e, 0x3f, 0x07, 0xfe, 0x14, 0x30, 0x7a, 0x1e, 0xde, 0xda, 0xbc, 0xb3, 0xb7, 0x98, 0x7a, 0x27, 0x7d, 0x40, 0xad, 0x03, 0xad, 0x01, 0x8e, 0x8f, 0x1f, 0xea, 0x74, 0x0f, 0xb4, 0x3b, 0xfa, 0x89, 0x0f, 0x27, 0xd5, 0xfd, 0x00, 0x48, 0xa2, 0x76, 0x92, 0x14, 0x07, 0x01, 0xf4, 0x94, 0xb9, 0xdc, 0x5e, 0x54, 0x88, 0x0a, 0x3e, 0xa6, 0x68, 0x0e, 0xd5, 0xfa, 0x10, 0x89, 0x77, 0xf3, 0xa7, 0x1e, 0xb8, 0xe0, 0x34, 0x57, 0xee, 0x01, 0xb9, 0x08, 0xd9, 0xc3, 0x93, 0x44, 0x7e, 0x0f, 0x9e, 0x04, 0x7b, 0x89, 0x9e, 0x02, 0x1a, 0xec, 0xd1, 0xa4, 0xbe, 0x37, 0x55, 0x5b, 0x1d, 0x8f, 0x15, 0x87, 0x7d, 0x85, 0x48, 0x14, 0x3e, 0xe8, 0x8a, 0x9b, 0x8a, 0x81, 0x28, 0x9f, 0x9e, 0x0a, 0x8e, 0x96, 0x31, 0x7f, 0xd9, 0x35, 0x80, 0x65, 0xe8, 0xad, 0xc8, 0xff, 0xae, 0xb2, 0x6a, 0x82, 0x89, 0xee, 0x58, 0x31, 0x4b, 0x49, 0xb5, 0x79, 0xa2, 0xab, 0x34, 0x36, 0xf5, 0xe0, 0xe4, 0xd8, 0x60, 0x79, 0x92, 0x5f, 0x91, 0x3d, 0x45, 0xdd, 0x5b, 0x93, 0x0e, 0x7c, 0x7e, 0x12, 0xce, 0xe5, 0xb5, 0x8a, 0x4e, 0x9e, 0x1d, 0x9d, 0xf9, 0x64, 0x2d, 0xd5, 0x65, 0xc9, 0x67, 0xa2, 0xea, 0x38, 0x3c, 0x52, 0x61, 0xef, 0xea, 0x1d, 0xce, 0xae, 0xd9, 0x47, 0x96, 0x25, 0xb2, 0x82, 0x3e, 0x68, 0x47, 0xfa, 0xc3, 0xe4, 0x05, 0xe6, 0x0a, 0xe1, 0x27, 0xf6, 0x72, 0xb5, 0x57, 0x10, 0x6f, 0xbb, 0x8d, 0xf7, 0x41, 0x25, 0x50, 0x53, 0xda, 0x78, 0x1f, 0x14, 0x7d, 0x6c, 0x16, 0x44, 0x2a, 0x73, 0xdd, 0x4a, 0x1f, 0x81, 0xa2, 0x00, 0x6c, 0x55, 0x7b, 0x12, 0x55, 0xb5, 0xcf, 0x46, 0x26, 0x05, 0x3e, 0x65, 0x52, 0xea, 0x41, 0x47, 0x50, 0xb8, 0xb0, 0xbd, 0x7e, 0xe5, 0x23, 0x27, 0xec, 0x52, 0x2e, 0x8a, 0x37, 0x62, 0x4d, 0x00, 0xaf, 0x91, 0x26, 0x08, 0xc3, 0x6b, 0xba, 0x92, 0x57, 0x05, 0x6a, 0xd5, 0xf4, 0x96, 0xf7, 0xbf, 0xbd, 0xe6, 0x9e, 0x99, 0x1a, 0xfe, 0x0a, 0xbe, 0x5f, 0x3b, 0x7e, 0xbf, 0xa7, 0x09, 0x1f, 0x31, 0x7a, 0x55, 0x8f, 0xe9, 0x74, 0x05, 0xef, 0x91, 0x53, 0x20, 0x85, 0xdf, 0xd1, 0x7d, 0xcb, 0xd4, 0x7c, 0xd8, 0x91, 0xa9, 0xf9, 0x80, 0xf9, 0x39, 0xa5, 0x9e, 0x00, 0x52, 0xc2, 0xdc, 0x51, 0x84, 0xc0, 0x21, 0xcc, 0xd1, 0x1a, 0x8b, 0xe2, 0x79, 0x3c, 0x83, 0x8e, 0x91, 0xbb, 0xe6, 0xc3, 0x7b, 0xaf, 0xb0, 0x5a, 0x84, 0x6e, 0xda, 0x3a, 0x9d, 0xad, 0x09, 0xfa, 0xfe, 0xd7, 0xd8, 0x50, 0x1f, 0x41, 0x2e, 0x0c, 0xa7, 0xef, 0x23, 0xff, 0x08, 0xdf, 0x43, 0x4e, 0xd4, 0x13, 0x8f, 0x72, 0x67, 0x8e, 0xe8, 0xa8, 0xa0, 0x16, 0x00, 0x91, 0x07, 0xd5, 0xf6, 0x29, 0xf6, 0x91, 0x0c, 0x5b, 0x07, 0xcf, 0x00, 0xe8, 0x45, 0xb7, 0x33, 0x95, 0x73, 0x38, 0x14, 0x87, 0xe0, 0xcc, 0x51, 0x9c, 0xc3, 0x92, 0x1b, 0x20, 0x28, 0x1a, 0xee, 0x80, 0x68, 0x8d, 0x70, 0x78, 0x8f, 0xcc, 0x01, 0xa3, 0xb0, 0x21, 0xc6, 0xfc, 0x55, 0x27, 0x10, 0xd9, 0x65, 0x81, 0x99, 0x27, 0x77, 0x44, 0x98, 0x3f, 0x73, 0x05, 0xa2, 0x65, 0x66, 0x83, 0xae, 0x4b, 0x8d, 0x2f, 0xa2, 0x30, 0x11, 0x8b, 0x02, 0xd0, 0xd1, 0x0b, 0xe2, 0xf5, 0x53, 0xc5, 0xb5, 0x49, 0xc4, 0x93, 0x65, 0xed, 0x46, 0x77, 0x7b, 0x38, 0x1e, 0xad, 0xfa, 0x97, 0xfc, 0x8a, 0x4f, 0xe5, 0xe6, 0x52, 0x63, 0xbd, 0xd5, 0xa3, 0x96, 0x68, 0x24, 0xe3, 0xe7, 0x9b, 0xc7, 0x6b, 0xad, 0xc0, 0x96, 0x18, 0x4c, 0xc4, 0x46, 0xc2, 0xd4, 0x5b, 0x15, 0x55, 0x6e, 0x6f, 0xa5, 0x27, 0x50, 0xce, 0x04, 0x85, 0xa1, 0xe6, 0x1b, 0xfe, 0xc7, 0xe7, 0x0a, 0x53, 0xe1, 0xb0, 0x4e, 0x6a, 0xd6, 0xb8, 0xd4, 0xeb, 0x03, 0x0d, 0xbd, 0xc1, 0x60, 0x77, 0x9d, 0xd7, 0x65, 0xf7, 0x84, 0xe0, 0x5b, 0xe9, 0x17, 0xde, 0x26, 0x9f, 0x61, 0x9a, 0x88, 0x42, 0x62, 0x00, 0x55, 0x7e, 0xa2, 0x68, 0x02, 0x5b, 0x8a, 0x3c, 0xf8, 0x98, 0x26, 0x90, 0x56, 0x80, 0x8a, 0x90, 0x3f, 0x1b, 0xe4, 0xaa, 0x34, 0x2c, 0xf9, 0xc9, 0x1e, 0x3c, 0x17, 0x50, 0xce, 0x86, 0x16, 0xcf, 0x69, 0xae, 0x42, 0x0e, 0x66, 0xe5, 0x60, 0xa3, 0x4b, 0x99, 0xf9, 0x60, 0x32, 0x22, 0xab, 0xc6, 0x48, 0x3e, 0xc3, 0xd2, 0xf2, 0xa4, 0x7f, 0x09, 0xdc, 0x42, 0xbc, 0xfe, 0xa9, 0x8e, 0xa3, 0xa7, 0x0e, 0x6a, 0xcd, 0x60, 0xdc, 0x94, 0xfe, 0xbe, 0x56, 0x0f, 0x86, 0x2f, 0x90, 0x83, 0x1c, 0x06, 0x7f, 0x43, 0x71, 0xf4, 0xfc, 0x1d, 0xf3, 0x8f, 0x9b, 0x8c, 0x38, 0xc7, 0xf9, 0x22, 0xf9, 0x1c, 0xd3, 0x00, 0x9f, 0x7d, 0x0a, 0x57, 0xff, 0x66, 0x64, 0x80, 0x25, 0xe2, 0xe0, 0xcc, 0x5a, 0x86, 0xde, 0x27, 0xa0, 0x1a, 0xca, 0x6c, 0x7e, 0x38, 0x98, 0xe6, 0xe5, 0x19, 0x89, 0x70, 0x9e, 0xb1, 0x90, 0x91, 0x28, 0xdb, 0x0a, 0x17, 0x7a, 0x0c, 0xc1, 0x6d, 0xb0, 0x90, 0x25, 0x9e, 0x5d, 0x54, 0x01, 0x37, 0x9f, 0x8a, 0xe8, 0x81, 0xca, 0x06, 0xac, 0xae, 0x44, 0x9a, 0xd2, 0x81, 0x58, 0xf7, 0x8e, 0x36, 0x4f, 0x4e, 0xe9, 0x8d, 0x06, 0xa1, 0xf1, 0x40, 0x9d, 0x15, 0x12, 0x0c, 0x91, 0x44, 0x62, 0xe1, 0x4d, 0xfa, 0x7e, 0xcc, 0x15, 0xe6, 0x27, 0x66, 0x5f, 0x54, 0x4b, 0x48, 0x96, 0xf0, 0x05, 0x45, 0x78, 0x9c, 0x32, 0x09, 0xa2, 0x21, 0x84, 0x72, 0x16, 0x11, 0x9b, 0xa4, 0x80, 0x4b, 0x9d, 0x61, 0x63, 0x3e, 0x78, 0x0f, 0x2f, 0x5c, 0xa2, 0x01, 0x57, 0xa3, 0x3e, 0x9b, 0x1e, 0xa0, 0xf1, 0xfb, 0xbc, 0x85, 0x28, 0xbe, 0x1d, 0xf0, 0xe8, 0xbd, 0x1a, 0x39, 0x57, 0x1f, 0x1c, 0x07, 0x49, 0xfd, 0x5e, 0x36, 0xf4, 0x4b, 0x2d, 0x2a, 0x10, 0x4e, 0xb5, 0xa6, 0xcb, 0xf0, 0xa9, 0x19, 0xe8, 0x39, 0x77, 0x20, 0xa4, 0x50, 0x79, 0x3a, 0x07, 0xc7, 0x71, 0xd0, 0xe6, 0xf0, 0xe1, 0xeb, 0xe0, 0x07, 0xd7, 0xce, 0x81, 0xef, 0x4e, 0xa0, 0xf3, 0xb3, 0x57, 0xd3, 0x15, 0xd9, 0x58, 0xcd, 0xc4, 0x59, 0xf0, 0x44, 0x7a, 0x12, 0xfd, 0x43, 0x7b, 0xd7, 0xd0, 0xc2, 0x5f, 0xe8, 0x75, 0xf0, 0xbd, 0xea, 0x88, 0x0d, 0x49, 0x85, 0x19, 0x6d, 0xfc, 0x71, 0x0f, 0x02, 0x94, 0x70, 0x33, 0xcc, 0x42, 0x8a, 0x48, 0x14, 0x06, 0x60, 0x77, 0xda, 0x0c, 0x5d, 0x35, 0x72, 0xa1, 0x96, 0xfe, 0x08, 0x33, 0xf9, 0xeb, 0xd1, 0x7a, 0x2b, 0x29, 0x72, 0x3b, 0x6d, 0x05, 0x2a, 0x05, 0x51, 0x07, 0xea, 0xb0, 0xa9, 0x00, 0x75, 0x65, 0x1e, 0xbf, 0x32, 0x9a, 0x7d, 0x18, 0xb6, 0x66, 0x64, 0x69, 0x98, 0xf9, 0xf8, 0x7d, 0x30, 0x8e, 0x69, 0xb3, 0xa8, 0x2f, 0xb8, 0x57, 0x79, 0x50, 0x91, 0xbb, 0xde, 0x33, 0x2f, 0x6d, 0xdb, 0xf6, 0xe2, 0x99, 0xde, 0x70, 0xf7, 0x96, 0x46, 0xcf, 0x2a, 0xf7, 0x13, 0x85, 0x76, 0x70, 0x20, 0x3a, 0xdc, 0xe4, 0xaf, 0x18, 0xda, 0x9b, 0x6c, 0xda, 0x3b, 0x54, 0xe1, 0x4f, 0x0e, 0x95, 0x83, 0x83, 0x8e, 0x42, 0x6a, 0x4c, 0xc4, 0x98, 0x57, 0xef, 0x3c, 0xd9, 0x35, 0x73, 0xe5, 0x58, 0x7b, 0xfb, 0xb1, 0x2b, 0x33, 0x5d, 0x27, 0x77, 0xae, 0x36, 0x33, 0xa2, 0xcd, 0x56, 0xcf, 0x4f, 0x25, 0x75, 0xe3, 0x07, 0x5b, 0x7a, 0xf7, 0xf5, 0x04, 0x02, 0x3d, 0xfb, 0x7a, 0x5b, 0x0e, 0x8e, 0xd7, 0x49, 0x7e, 0xea, 0xb1, 0x6e, 0xc6, 0xfb, 0xf7, 0xde, 0x85, 0xb7, 0xa9, 0x5f, 0xd3, 0x62, 0xa2, 0x8a, 0xb8, 0xc4, 0xad, 0x2b, 0x56, 0xcb, 0x92, 0xbc, 0x07, 0x91, 0x79, 0x69, 0x76, 0xea, 0x2e, 0xf9, 0xf1, 0x1e, 0xfc, 0x71, 0x14, 0x83, 0xfa, 0x51, 0xd0, 0x2b, 0xa3, 0xa8, 0x73, 0x9b, 0x24, 0x0b, 0x91, 0xd1, 0x4c, 0x33, 0xd4, 0x1c, 0x5f, 0xed, 0x75, 0x71, 0x23, 0x16, 0xd7, 0x1f, 0x2a, 0x29, 0x0a, 0xb1, 0x2a, 0x3b, 0xb7, 0xc0, 0x2b, 0x14, 0x18, 0xd2, 0x48, 0x48, 0x11, 0x61, 0x4c, 0x89, 0x50, 0x70, 0xac, 0x4a, 0x0f, 0x82, 0x85, 0x92, 0x54, 0xf9, 0xc6, 0xa3, 0xed, 0xb6, 0x35, 0xf7, 0xbe, 0xbc, 0x75, 0xf0, 0xf4, 0x86, 0x18, 0x74, 0x28, 0xa5, 0xf2, 0x8b, 0x76, 0x2b, 0x2d, 0x12, 0x6b, 0x9c, 0xf1, 0xce, 0xa2, 0x9a, 0x1d, 0x03, 0xe5, 0xfa, 0x40, 0xb5, 0xdf, 0x53, 0x2c, 0x55, 0x59, 0x69, 0xea, 0xf7, 0x6a, 0x65, 0xf3, 0xce, 0x0b, 0xbd, 0x0f, 0xbf, 0xfd, 0x99, 0xd1, 0xea, 0x3d, 0x4f, 0xed, 0xe9, 0xf9, 0xc2, 0xc6, 0xc4, 0xfe, 0x46, 0xb7, 0xb9, 0x7c, 0xba, 0x6c, 0xe2, 0xa9, 0x96, 0xbe, 0xbd, 0x9d, 0x85, 0x0d, 0x47, 0x5e, 0x3a, 0x92, 0xda, 0x54, 0x6d, 0x94, 0x29, 0xad, 0x50, 0x5e, 0x3a, 0x38, 0xec, 0xef, 0xc1, 0x39, 0xa3, 0x23, 0xe0, 0x2c, 0x90, 0x60, 0x03, 0x96, 0x10, 0xc4, 0xf9, 0xb4, 0x25, 0x6e, 0x44, 0x9e, 0xe3, 0xc1, 0xb9, 0xbc, 0xc2, 0x4a, 0x5c, 0x31, 0xea, 0xbd, 0x4f, 0x98, 0x5d, 0xcc, 0x9f, 0x55, 0x3a, 0x89, 0xc4, 0xa0, 0xfa, 0xab, 0xc8, 0x69, 0x78, 0x04, 0x3c, 0xf6, 0x79, 0xb3, 0x86, 0x9c, 0x0b, 0xa1, 0x61, 0x09, 0xcd, 0xdf, 0xaf, 0x35, 0xc1, 0xaf, 0xc5, 0x63, 0xe2, 0x58, 0x48, 0x8b, 0x54, 0xf0, 0xa2, 0x98, 0xf0, 0x24, 0x9d, 0x3e, 0x93, 0x04, 0xe9, 0x81, 0x6e, 0x36, 0x8a, 0xca, 0x9f, 0xa0, 0x84, 0x3d, 0x45, 0xf5, 0x6e, 0xe4, 0x93, 0x2d, 0xfa, 0x4b, 0xe8, 0x32, 0x10, 0x14, 0xf1, 0x7f, 0xb6, 0xc2, 0xac, 0x37, 0x61, 0xaa, 0x24, 0x68, 0xec, 0x89, 0x54, 0x8f, 0xc0, 0x47, 0x78, 0x51, 0xaa, 0x16, 0x49, 0x8d, 0xf2, 0xd7, 0x19, 0xbb, 0x61, 0xc8, 0xe4, 0x60, 0xbe, 0x69, 0xd4, 0x29, 0xf4, 0x5f, 0x16, 0xb9, 0x0c, 0x17, 0x41, 0xe7, 0xa3, 0x76, 0xd9, 0x4f, 0xd4, 0x52, 0x46, 0x21, 0xf9, 0x67, 0x59, 0xe1, 0x25, 0xf4, 0x6c, 0x81, 0x2e, 0x9f, 0xaf, 0x2b, 0x30, 0x7f, 0x8f, 0xc6, 0x68, 0xd4, 0x90, 0xfb, 0x53, 0x9d, 0x5d, 0xdd, 0xec, 0x73, 0xfa, 0xe6, 0xb7, 0x57, 0x46, 0xad, 0xe5, 0x56, 0xf2, 0x21, 0x68, 0x4d, 0x10, 0x0a, 0xc4, 0x09, 0xc5, 0xf4, 0x12, 0x26, 0xc2, 0x9e, 0x2c, 0x30, 0x21, 0x9e, 0x11, 0x24, 0x14, 0x82, 0xdc, 0xc7, 0x09, 0xc5, 0x2b, 0x10, 0x0a, 0x10, 0x0a, 0x05, 0xb3, 0xfc, 0x51, 0x37, 0x50, 0xc1, 0x96, 0x74, 0x5a, 0xaa, 0x82, 0x03, 0x24, 0x7d, 0x57, 0xe4, 0xd2, 0x3f, 0x46, 0x56, 0xe2, 0x52, 0x2e, 0x16, 0x35, 0xb9, 0xd9, 0x5c, 0x66, 0xb1, 0x44, 0xcd, 0xf3, 0x1f, 0xd7, 0x9a, 0x18, 0x97, 0xc9, 0x92, 0xde, 0x81, 0x6b, 0xd8, 0xc0, 0xbf, 0xf7, 0x39, 0xea, 0x5d, 0xe8, 0x43, 0x38, 0x92, 0x56, 0x39, 0xc5, 0xa6, 0xa4, 0x90, 0xb8, 0x52, 0x22, 0xeb, 0xa5, 0x14, 0x17, 0x6a, 0x29, 0x36, 0xce, 0xaa, 0xc5, 0xee, 0x16, 0x27, 0x02, 0xf4, 0xe7, 0xc8, 0xc6, 0xaf, 0xa4, 0xef, 0x7b, 0xb2, 0xc0, 0x21, 0xfb, 0xb2, 0x4b, 0x2f, 0xb5, 0x68, 0xbe, 0x24, 0x71, 0x5a, 0x2e, 0x83, 0x7b, 0xd2, 0xfb, 0xc1, 0x79, 0x72, 0x4f, 0x81, 0x3e, 0xfd, 0xb9, 0xfa, 0x0a, 0x6b, 0xc2, 0x09, 0x06, 0x34, 0x76, 0xcc, 0x0b, 0x7a, 0x91, 0x6e, 0xc6, 0xbc, 0xe2, 0xb3, 0x57, 0xe5, 0x1c, 0x2f, 0x28, 0x1b, 0xb2, 0x5e, 0x82, 0x69, 0x6b, 0x51, 0xc8, 0x7a, 0x11, 0x19, 0x68, 0x96, 0x94, 0x4b, 0x10, 0x6b, 0xc0, 0xa1, 0x06, 0xad, 0xd1, 0x98, 0x1b, 0x6a, 0xc8, 0x63, 0xe8, 0x32, 0x66, 0x83, 0xd5, 0xcd, 0xa5, 0x3d, 0xe1, 0xd4, 0x96, 0xa4, 0x63, 0xfe, 0x33, 0x42, 0x4a, 0x50, 0xd2, 0x1e, 0x4d, 0xe6, 0x46, 0x19, 0x22, 0x39, 0xd4, 0x5d, 0x3f, 0xc6, 0x76, 0x26, 0xf2, 0x29, 0x6a, 0x16, 0x9a, 0xe9, 0x21, 0xf8, 0x4e, 0x72, 0xa2, 0x92, 0xe8, 0x4a, 0xb6, 0x87, 0xb0, 0xdf, 0xe3, 0x05, 0x54, 0x47, 0x86, 0x42, 0x3b, 0x53, 0xb5, 0x40, 0x2a, 0x96, 0x50, 0x7c, 0xb2, 0x09, 0xd4, 0xe5, 0x0a, 0x05, 0x1c, 0xe9, 0x4a, 0x45, 0x25, 0x0a, 0x82, 0x06, 0x03, 0x59, 0xaa, 0x68, 0x59, 0x6e, 0x06, 0x81, 0x37, 0x8b, 0xcc, 0x2d, 0x06, 0xd9, 0x7c, 0x81, 0xdc, 0x37, 0x22, 0xd3, 0xd3, 0x38, 0x67, 0x20, 0xe0, 0xd5, 0x5c, 0x2a, 0x68, 0xe8, 0x1e, 0xa9, 0xc4, 0x29, 0x02, 0xc3, 0xc7, 0x06, 0x87, 0x44, 0x4a, 0x11, 0xe3, 0x71, 0xf5, 0x88, 0x14, 0xe2, 0xb4, 0x4d, 0xf8, 0x86, 0x0f, 0x37, 0x24, 0x82, 0x95, 0x86, 0x68, 0x7f, 0xdd, 0xd4, 0xc9, 0x3e, 0x97, 0xb7, 0x61, 0x20, 0x52, 0x3b, 0xd5, 0x19, 0xf2, 0x74, 0x17, 0x59, 0x35, 0x8c, 0x3c, 0xba, 0x2e, 0x94, 0x47, 0x54, 0x86, 0xea, 0x33, 0x3c, 0xfa, 0xff, 0xdd, 0xfa, 0x0c, 0x9f, 0xbc, 0xb5, 0x2d, 0x7b, 0xb8, 0x1a, 0xe8, 0xda, 0xb1, 0xa8, 0x3e, 0x03, 0x49, 0xb8, 0xd3, 0xf7, 0x53, 0x8d, 0xf0, 0xf9, 0x2b, 0x88, 0x24, 0x71, 0x67, 0x52, 0x99, 0x70, 0x49, 0x28, 0x91, 0x58, 0x2d, 0x23, 0x81, 0x88, 0xe4, 0xb8, 0xd6, 0x42, 0x99, 0x9a, 0x89, 0x59, 0x8a, 0x7a, 0xae, 0x58, 0xc2, 0x4e, 0x7c, 0xea, 0xc6, 0xbf, 0x12, 0x32, 0x16, 0xf9, 0xa6, 0x24, 0x21, 0x16, 0x91, 0x62, 0x41, 0x17, 0x61, 0xc3, 0xd1, 0xa4, 0xa9, 0x32, 0x56, 0x5b, 0x1d, 0x4b, 0x56, 0x26, 0x51, 0xa0, 0x3b, 0xe8, 0xc7, 0x27, 0xf5, 0x6c, 0xd8, 0x9f, 0x62, 0x7d, 0x5a, 0x68, 0x12, 0xf2, 0x60, 0xfb, 0x2a, 0xb6, 0x4c, 0x53, 0x62, 0xb1, 0x3c, 0xb2, 0xa6, 0xa5, 0x93, 0x04, 0x4f, 0x6c, 0x7b, 0x74, 0xb6, 0x22, 0x1e, 0x73, 0xd9, 0xeb, 0x6c, 0x1e, 0x95, 0xa5, 0xea, 0xec, 0xc0, 0xe8, 0xd1, 0xde, 0x42, 0x00, 0x5c, 0x4d, 0x13, 0x4d, 0x53, 0x4f, 0xac, 0xb2, 0x16, 0xb6, 0x6d, 0xeb, 0x78, 0xc1, 0x06, 0xba, 0xd3, 0x06, 0x28, 0x20, 0x6d, 0xfa, 0x78, 0xcd, 0x84, 0x37, 0x76, 0x5b, 0x5b, 0x4d, 0xb9, 0x2e, 0xd4, 0x80, 0xa4, 0x35, 0x7d, 0xba, 0x7f, 0xe8, 0x23, 0xf5, 0xe5, 0x09, 0x64, 0x21, 0x96, 0x7a, 0x03, 0xc5, 0x6d, 0xc3, 0x25, 0xe1, 0x9e, 0x5a, 0xef, 0x9a, 0xd6, 0x86, 0xde, 0x3a, 0x24, 0x33, 0xb7, 0x89, 0x93, 0xd9, 0xfb, 0x3f, 0x74, 0xd8, 0x2a, 0x93, 0x45, 0x6e, 0x83, 0xd7, 0xa6, 0x41, 0xe3, 0x5f, 0x08, 0x85, 0xf8, 0x31, 0xe6, 0xf2, 0x8a, 0x9c, 0xb7, 0x3b, 0xf2, 0x38, 0x6f, 0x29, 0x41, 0xd6, 0xa1, 0x36, 0x8f, 0xf3, 0xf6, 0x17, 0xde, 0x89, 0xad, 0x7b, 0x12, 0xdb, 0xaf, 0x9e, 0x4e, 0x55, 0x4e, 0xdd, 0x3f, 0x71, 0x7d, 0xe6, 0x22, 0x9c, 0xbf, 0xfd, 0x7b, 0x3b, 0x5b, 0x36, 0x76, 0x20, 0xce, 0x5b, 0x46, 0x8c, 0x20, 0xc9, 0x8d, 0xb7, 0x3e, 0xb7, 0x7f, 0xe4, 0xa3, 0x77, 0x6c, 0xb4, 0xa6, 0x7d, 0x64, 0xab, 0xb2, 0x75, 0xfa, 0xce, 0xd4, 0xc0, 0x6d, 0x83, 0x45, 0x1c, 0x84, 0x1c, 0xae, 0xae, 0x08, 0x1c, 0x85, 0x2b, 0xf8, 0x99, 0x4a, 0x93, 0x45, 0x28, 0xea, 0xe9, 0x21, 0x70, 0x55, 0xad, 0x4c, 0x00, 0x2e, 0xfb, 0x5c, 0x78, 0xc5, 0x59, 0x7d, 0xa6, 0x30, 0x0e, 0x72, 0xf0, 0xc8, 0x62, 0x27, 0x89, 0x23, 0x47, 0xd9, 0x83, 0x06, 0xb3, 0xd6, 0x0b, 0x9e, 0x9f, 0xb8, 0x7f, 0xaa, 0x32, 0x75, 0xfa, 0xea, 0xf6, 0xc4, 0x9e, 0xad, 0x13, 0xde, 0x2a, 0xa5, 0x45, 0x57, 0xde, 0xb1, 0xb1, 0xa5, 0x73, 0x6f, 0x7f, 0x51, 0x64, 0xe3, 0xc5, 0x99, 0xeb, 0xe0, 0xe7, 0xd6, 0x8d, 0x77, 0x7c, 0x74, 0x64, 0xff, 0x73, 0xb7, 0x36, 0x66, 0x70, 0xfc, 0x45, 0x83, 0xb7, 0x0d, 0xa4, 0xee, 0x9c, 0x6e, 0x55, 0xce, 0xbf, 0x82, 0xf9, 0x81, 0x17, 0xfe, 0x4a, 0x9d, 0x64, 0x1e, 0x23, 0x3c, 0xd0, 0xce, 0x96, 0xbb, 0x10, 0xa5, 0x9d, 0x1a, 0x13, 0x63, 0x72, 0x66, 0x50, 0xa6, 0x14, 0x02, 0x45, 0x4d, 0x09, 0x49, 0x31, 0x6d, 0xcb, 0x7c, 0x84, 0x4c, 0xed, 0x17, 0x2d, 0xfc, 0xb3, 0x2f, 0x01, 0xa8, 0xc5, 0x53, 0x26, 0xa8, 0x8d, 0x81, 0x05, 0xfc, 0xe8, 0xa7, 0xd8, 0x47, 0xc7, 0xa9, 0x08, 0xe5, 0xed, 0x1b, 0x5b, 0xee, 0xff, 0x24, 0x7a, 0xf0, 0x6b, 0xd4, 0xd7, 0xac, 0x1b, 0xee, 0x7c, 0x6c, 0x64, 0xff, 0xf3, 0xf0, 0xc1, 0x83, 0xf5, 0x45, 0x9d, 0x7a, 0xa7, 0xcf, 0x79, 0xf9, 0x09, 0xf8, 0xd8, 0x2d, 0x2a, 0xb2, 0x15, 0xcb, 0x73, 0x67, 0xfa, 0x13, 0xd4, 0x33, 0xf8, 0xdc, 0xb1, 0x36, 0x59, 0x55, 0x04, 0x00, 0xc3, 0x26, 0xb2, 0xe6, 0x57, 0xa2, 0x5e, 0x84, 0x61, 0xdd, 0x4a, 0xc3, 0x2d, 0x97, 0x4f, 0xff, 0x02, 0x2b, 0x14, 0x9e, 0x06, 0x64, 0x5c, 0xa3, 0xc3, 0xe1, 0xdd, 0xdd, 0x38, 0xff, 0x2b, 0xd6, 0x1e, 0x43, 0x56, 0x54, 0xb8, 0x6f, 0x5f, 0x57, 0x75, 0x57, 0x05, 0xca, 0xff, 0xaa, 0xf2, 0x0e, 0x4f, 0x6e, 0x8e, 0x6c, 0x7d, 0xf6, 0x78, 0xfb, 0xbb, 0x6f, 0x6e, 0x79, 0x32, 0x49, 0xd5, 0xe2, 0xf4, 0xa5, 0xa6, 0x93, 0xaf, 0xdf, 0xfb, 0xd0, 0xf7, 0xef, 0xac, 0xcd, 0xa4, 0x7f, 0xa5, 0x3e, 0x05, 0xe8, 0x2f, 0x82, 0x31, 0xb0, 0xb9, 0x3d, 0x09, 0x77, 0xab, 0x91, 0x85, 0xb7, 0x98, 0x1d, 0xd0, 0x47, 0x44, 0xbc, 0x55, 0x75, 0x98, 0x47, 0x10, 0xba, 0x7e, 0x14, 0x03, 0x35, 0x12, 0x17, 0x88, 0xc3, 0x13, 0x80, 0x2f, 0x5b, 0x18, 0x42, 0xff, 0xb1, 0x3e, 0x8b, 0x81, 0xc4, 0x0f, 0x0b, 0x1f, 0xab, 0x52, 0x87, 0x15, 0x12, 0xd0, 0x89, 0xf3, 0x20, 0xb7, 0xfe, 0x7c, 0x8a, 0x80, 0x1d, 0x93, 0x4f, 0xff, 0x8f, 0x07, 0x1f, 0xf8, 0x1f, 0x4f, 0x4f, 0xc2, 0x9f, 0x0f, 0x3c, 0x08, 0x7f, 0x5e, 0x4b, 0x1e, 0x7a, 0x6a, 0xeb, 0x96, 0xa7, 0x0e, 0x35, 0x37, 0x1f, 0x7a, 0x6a, 0xcb, 0xd6, 0xa7, 0x0e, 0x25, 0xd3, 0xaf, 0x18, 0x7d, 0x65, 0x36, 0x6b, 0xa4, 0x10, 0xee, 0x42, 0x11, 0xab, 0xad, 0xcc, 0x67, 0x24, 0xbf, 0xfb, 0x78, 0xfa, 0xcf, 0xd7, 0x76, 0xee, 0xbc, 0x06, 0x94, 0x8f, 0x7f, 0x02, 0xa8, 0xbe, 0xbe, 0x6b, 0xd7, 0xb5, 0xf4, 0x9f, 0x3f, 0x71, 0xe2, 0x87, 0x97, 0xfa, 0xfb, 0x2f, 0xfd, 0xf0, 0xc4, 0xc9, 0x1f, 0x5e, 0xec, 0xef, 0xbf, 0xf8, 0xc3, 0x93, 0xd6, 0x88, 0xd7, 0x68, 0xf4, 0xb2, 0x3d, 0xe0, 0x37, 0xb0, 0xe7, 0x77, 0x6b, 0x16, 0xde, 0xa6, 0xed, 0x1c, 0xde, 0x78, 0xfb, 0xd5, 0x32, 0x7c, 0x0c, 0xc5, 0xd3, 0xf0, 0x53, 0x22, 0x7c, 0x1c, 0x35, 0x85, 0xc7, 0x41, 0x0c, 0x16, 0xe5, 0xee, 0xad, 0xd8, 0x02, 0x1f, 0x4d, 0x9a, 0xdd, 0x2e, 0x40, 0x14, 0x85, 0x5c, 0xd5, 0xee, 0x6a, 0x87, 0xcd, 0xa8, 0x97, 0x4b, 0x11, 0xf8, 0x58, 0x82, 0x0d, 0xec, 0x5c, 0xd4, 0x71, 0x16, 0xae, 0x4f, 0x65, 0xd3, 0xd7, 0xb2, 0x20, 0x7d, 0xf0, 0x2f, 0x08, 0x04, 0x60, 0xab, 0x1b, 0x6b, 0x1e, 0x3f, 0x31, 0xe0, 0xf7, 0x0f, 0xde, 0x39, 0xd1, 0x34, 0x52, 0x65, 0x99, 0x1d, 0x7f, 0xf4, 0xd6, 0x53, 0xaf, 0x9f, 0x6f, 0xef, 0xb8, 0xe7, 0x07, 0xe7, 0xff, 0xe1, 0x9f, 0xa6, 0x87, 0x22, 0xc9, 0xa0, 0xb6, 0x74, 0xf3, 0xa3, 0xe0, 0x9f, 0xac, 0xa5, 0xf5, 0xde, 0x40, 0x63, 0x69, 0x41, 0xe5, 0xa6, 0x7b, 0x46, 0x46, 0xce, 0x6f, 0xa8, 0xb0, 0x04, 0x63, 0xf6, 0xa6, 0x9e, 0x93, 0x64, 0x9c, 0x3f, 0xc3, 0x7c, 0xf8, 0xfe, 0x89, 0xd3, 0x6e, 0x4d, 0xb4, 0xb6, 0xc9, 0x59, 0x39, 0xd5, 0x57, 0xc6, 0x61, 0x5e, 0xe9, 0x67, 0xe9, 0x7f, 0x23, 0x9a, 0x89, 0x5b, 0x93, 0xfa, 0x2a, 0x20, 0x16, 0x45, 0xa0, 0x70, 0x18, 0x13, 0x5c, 0xf4, 0x4e, 0x68, 0x48, 0xd0, 0x99, 0xc4, 0x34, 0x11, 0xc1, 0x56, 0x57, 0xe7, 0x98, 0x24, 0x05, 0x88, 0xa9, 0xac, 0x83, 0x64, 0xbb, 0x99, 0x76, 0x18, 0x98, 0x2d, 0x0f, 0x05, 0x43, 0x5e, 0x83, 0x3e, 0x14, 0xc2, 0x20, 0xa1, 0x5c, 0x94, 0x6b, 0x4c, 0x88, 0x86, 0xcd, 0x3d, 0x9c, 0x14, 0xa2, 0x62, 0xb1, 0xe2, 0xa6, 0xbe, 0x9b, 0x50, 0x84, 0xcd, 0xe6, 0xb2, 0x9a, 0x8e, 0xb2, 0x14, 0x8b, 0x7c, 0xed, 0x44, 0x10, 0x59, 0x9d, 0x11, 0x0c, 0xf6, 0x7e, 0xf9, 0xd8, 0x9e, 0x97, 0xcf, 0xf6, 0x76, 0x9f, 0xbf, 0x7e, 0xb8, 0x71, 0x7b, 0xc4, 0x14, 0x5f, 0xd7, 0x1c, 0x5d, 0xe5, 0xb5, 0x88, 0xdd, 0x5a, 0xb9, 0x4d, 0x15, 0x2d, 0xa6, 0x8e, 0xe7, 0x63, 0x61, 0x79, 0xc4, 0x6c, 0x75, 0x9c, 0xc7, 0xcb, 0x1a, 0xb4, 0x86, 0xd2, 0xa1, 0x96, 0xb0, 0x4a, 0xb5, 0xd9, 0x2a, 0xa3, 0x29, 0xaa, 0xa5, 0x09, 0xaa, 0xc5, 0x85, 0xe1, 0xf4, 0xfd, 0xe0, 0x9f, 0x98, 0x2f, 0xc1, 0x35, 0xe1, 0x22, 0x9a, 0x88, 0xd5, 0xc9, 0x5e, 0x68, 0x19, 0x32, 0x85, 0x66, 0x68, 0x3d, 0x95, 0xf8, 0x49, 0xb8, 0x6b, 0x77, 0xa3, 0x32, 0xd6, 0x24, 0x43, 0x61, 0x5a, 0x65, 0x9e, 0xfa, 0x90, 0xad, 0x64, 0xc1, 0xad, 0xea, 0x39, 0xba, 0xc7, 0x68, 0x00, 0x44, 0x53, 0x63, 0x4d, 0x55, 0x59, 0xa9, 0xad, 0xc0, 0xe0, 0x32, 0xba, 0xd8, 0x13, 0x5a, 0x29, 0x9e, 0x1f, 0xb9, 0xe0, 0x73, 0xc1, 0x1e, 0x65, 0x02, 0x8b, 0x83, 0x1d, 0xfc, 0xd6, 0x06, 0x75, 0x54, 0xa7, 0x2d, 0xea, 0x37, 0x1a, 0xfd, 0x51, 0x9b, 0xad, 0xc2, 0x6f, 0x32, 0xf9, 0x2b, 0xd2, 0xbf, 0x88, 0xb4, 0x1b, 0x5d, 0x38, 0xfe, 0x01, 0x3e, 0x56, 0x6e, 0x2e, 0x31, 0xa2, 0x7d, 0x4d, 0xa2, 0x91, 0x8c, 0xdd, 0xdd, 0x82, 0x02, 0x1e, 0xc0, 0x96, 0x18, 0xa8, 0x42, 0x11, 0x8f, 0xeb, 0xf4, 0x33, 0x46, 0x7f, 0xb9, 0xcd, 0x5e, 0x8e, 0x7a, 0x97, 0xdb, 0x6d, 0xf0, 0xe7, 0x7b, 0xd3, 0x15, 0x55, 0x2e, 0x5f, 0xdc, 0xe3, 0x2f, 0x07, 0x9b, 0x9f, 0x98, 0x2b, 0x4c, 0x85, 0xd8, 0xb8, 0x87, 0x6a, 0x94, 0x8d, 0x7b, 0xd4, 0xfa, 0x50, 0xdc, 0x83, 0xfa, 0x3c, 0xcb, 0xd9, 0x8e, 0xea, 0x03, 0xc0, 0xfd, 0xdf, 0x85, 0xb0, 0xa6, 0xea, 0x4c, 0xe4, 0x97, 0xa4, 0xc1, 0x14, 0x74, 0xa7, 0x33, 0x80, 0x8a, 0x0c, 0xfc, 0xc8, 0x45, 0xb8, 0x82, 0x41, 0xbf, 0x47, 0x8b, 0x53, 0xc4, 0x31, 0x84, 0x8e, 0xab, 0x9b, 0x08, 0xd0, 0x8b, 0x06, 0x39, 0xec, 0x30, 0x55, 0x07, 0xb4, 0xd4, 0xd1, 0xeb, 0x88, 0x85, 0x36, 0xfd, 0x7d, 0xc4, 0x42, 0x0b, 0xb6, 0x5a, 0x7d, 0x06, 0xbb, 0x2a, 0x7d, 0xe7, 0xe7, 0x2c, 0x3e, 0x25, 0xa8, 0x95, 0xa8, 0xb4, 0x46, 0x75, 0x25, 0xf5, 0xce, 0x7b, 0x5f, 0x20, 0x7f, 0xa3, 0xd0, 0xa5, 0x9f, 0xd3, 0xeb, 0xc1, 0xb9, 0x42, 0xb3, 0xc2, 0x6b, 0x00, 0x7f, 0xd6, 0x18, 0xd3, 0x5b, 0xe5, 0x2a, 0x31, 0x05, 0x7e, 0xc9, 0xe3, 0x51, 0xc9, 0x9f, 0xc1, 0xe7, 0x63, 0x08, 0x17, 0x86, 0x79, 0x70, 0x87, 0xac, 0x59, 0xfe, 0x53, 0x86, 0x60, 0xb4, 0x5a, 0x5a, 0x66, 0x2a, 0xf6, 0x1b, 0x91, 0xbb, 0x4e, 0xfe, 0x6c, 0xfe, 0xb3, 0xaf, 0x7c, 0xec, 0x63, 0xd4, 0x3b, 0x1c, 0xd7, 0xe5, 0x60, 0xfa, 0x69, 0x2e, 0xe7, 0x30, 0x5b, 0x23, 0x17, 0x61, 0xf3, 0x2b, 0xe1, 0xab, 0xfb, 0x72, 0x73, 0x0e, 0x85, 0xb7, 0xf8, 0x1a, 0xb9, 0xcb, 0xe5, 0x1c, 0xf2, 0x25, 0xa9, 0xd8, 0xf2, 0xc1, 0x1c, 0xcd, 0xfb, 0x8a, 0x8d, 0xf7, 0x64, 0x6a, 0x0d, 0xaf, 0x9c, 0xa4, 0x98, 0xd7, 0x1c, 0x65, 0x52, 0x85, 0xbc, 0xfa, 0xbf, 0x21, 0x4b, 0x51, 0x9b, 0x0f, 0xc9, 0x8f, 0xec, 0xe3, 0xaa, 0x4f, 0x0c, 0xa3, 0x1c, 0xc5, 0xdc, 0xe8, 0xe9, 0x35, 0x9c, 0xd8, 0x39, 0x93, 0x68, 0xd8, 0xfb, 0xc4, 0xcc, 0xc6, 0xd3, 0xa5, 0x59, 0x72, 0xf7, 0xde, 0x36, 0xca, 0x28, 0x8c, 0xa0, 0xbd, 0xb7, 0x01, 0xfc, 0xa6, 0x8b, 0x4d, 0xec, 0x7c, 0xed, 0xc2, 0xea, 0xd6, 0x5a, 0xbe, 0x2e, 0xca, 0xbf, 0x33, 0xd7, 0xe1, 0x7c, 0xd1, 0x13, 0x15, 0x88, 0xed, 0x0b, 0x11, 0x77, 0x68, 0x22, 0x80, 0x10, 0x39, 0xf5, 0x0c, 0x62, 0xae, 0x01, 0x68, 0x41, 0x71, 0x59, 0xfa, 0xf0, 0x3e, 0xc8, 0xbf, 0x1f, 0xe5, 0xee, 0x4b, 0x72, 0xee, 0x67, 0x6f, 0x51, 0xdc, 0xad, 0xd1, 0x51, 0x8e, 0x15, 0xc4, 0x89, 0x09, 0x0e, 0x91, 0xbb, 0x27, 0x1a, 0x43, 0x16, 0xe5, 0x6e, 0x3c, 0x1b, 0xe8, 0x71, 0x86, 0x23, 0xec, 0x64, 0xff, 0x54, 0x88, 0xe0, 0x08, 0x95, 0xf9, 0x46, 0xc4, 0xf2, 0xed, 0xd9, 0x1c, 0xb8, 0x4c, 0x7b, 0x82, 0x6b, 0x2e, 0x19, 0x25, 0x10, 0xb5, 0x5e, 0x7e, 0x6b, 0xf4, 0xc0, 0xd1, 0x0f, 0x68, 0x8d, 0xb3, 0xe1, 0xd1, 0xaf, 0xcc, 0x38, 0xc1, 0x15, 0x74, 0x89, 0x2c, 0xdb, 0x85, 0xa7, 0x7e, 0x99, 0xce, 0x90, 0x53, 0xe3, 0xda, 0x2e, 0x70, 0xd4, 0x15, 0x5a, 0x7d, 0xc8, 0xe7, 0x31, 0x07, 0xbc, 0x50, 0xaf, 0x38, 0x8a, 0xfd, 0xc8, 0xa9, 0x11, 0xe7, 0x26, 0xf3, 0xb2, 0x2e, 0x9b, 0x67, 0x89, 0x4a, 0x2e, 0x5a, 0x0f, 0xf5, 0x73, 0x5a, 0x02, 0x9d, 0x9a, 0x9f, 0xe7, 0x54, 0x1b, 0xa1, 0xfe, 0x11, 0xfb, 0x70, 0xce, 0x1b, 0x1f, 0x5a, 0x94, 0x99, 0x7a, 0x9d, 0x12, 0x9d, 0x91, 0x4a, 0xc9, 0x6f, 0x4d, 0x08, 0x0a, 0x8c, 0x9c, 0xc1, 0x4e, 0xce, 0xc3, 0xf9, 0x35, 0x5b, 0xd2, 0x95, 0xec, 0xba, 0xba, 0x24, 0xb6, 0xe1, 0x5c, 0xe8, 0x21, 0x70, 0x17, 0x3b, 0x38, 0xe6, 0x5e, 0x40, 0xc8, 0x10, 0x30, 0x3c, 0x06, 0x9d, 0x87, 0x06, 0xa8, 0xfb, 0x44, 0x4e, 0xa8, 0x5b, 0x19, 0x7e, 0x02, 0xf0, 0x1f, 0x8b, 0xf3, 0x3f, 0xc6, 0x9f, 0x28, 0x96, 0xf8, 0x84, 0x1f, 0xf5, 0x2a, 0x82, 0x96, 0x30, 0x12, 0x04, 0x50, 0x23, 0xa1, 0xbb, 0x4b, 0x8a, 0xa6, 0x08, 0xb9, 0x7c, 0x06, 0x87, 0x9f, 0x65, 0x63, 0x0a, 0x80, 0xf3, 0xb1, 0xd0, 0x89, 0x0d, 0x07, 0x73, 0xce, 0x1e, 0x2e, 0x70, 0x7f, 0xb8, 0x79, 0xc9, 0xee, 0x84, 0xa0, 0xb7, 0x58, 0xfc, 0x81, 0x5f, 0x84, 0xe6, 0x49, 0xd3, 0xf2, 0x5f, 0x44, 0x70, 0xdf, 0xa3, 0x18, 0x25, 0x14, 0x8a, 0xd9, 0xe5, 0xbe, 0x26, 0xb9, 0x8a, 0xa0, 0xa5, 0x8c, 0xf4, 0xa6, 0xbe, 0x01, 0xf7, 0x05, 0x00, 0xa7, 0xf0, 0xec, 0xc9, 0x85, 0xe1, 0x8d, 0x26, 0x0d, 0x03, 0xab, 0xfb, 0xfb, 0x52, 0x5d, 0xcd, 0xd0, 0x97, 0x43, 0x6e, 0xa4, 0x3e, 0x50, 0xa8, 0x5c, 0x94, 0xc0, 0xe3, 0xbf, 0x19, 0xec, 0xee, 0x92, 0x93, 0x07, 0x39, 0x57, 0xb9, 0xa9, 0xcd, 0xe4, 0x16, 0xec, 0x7a, 0x1e, 0xe5, 0x5c, 0xcf, 0x52, 0xa9, 0x45, 0x6b, 0x08, 0xd7, 0x40, 0xd7, 0x73, 0x17, 0x74, 0x3d, 0x5b, 0xb7, 0x77, 0xbd, 0xa0, 0x52, 0x80, 0xd4, 0xfc, 0x7f, 0x1e, 0x5c, 0x6a, 0x5e, 0x99, 0x35, 0xc2, 0xb4, 0xe7, 0x17, 0x57, 0xaa, 0x1c, 0xa8, 0xb5, 0xca, 0xc1, 0x47, 0x6e, 0x3c, 0xbe, 0x78, 0xc6, 0xb1, 0xde, 0x2a, 0xe2, 0x03, 0x1b, 0x5c, 0x68, 0x16, 0xff, 0x16, 0xd7, 0x25, 0xd3, 0x12, 0x6e, 0x62, 0x04, 0xbc, 0xc9, 0xcd, 0xbd, 0x7e, 0x40, 0x4a, 0x75, 0x80, 0x21, 0x9b, 0xdc, 0x24, 0x01, 0x14, 0x40, 0x44, 0xd0, 0xdd, 0xd5, 0x40, 0xd4, 0xc1, 0xcf, 0xbd, 0x7e, 0x40, 0x2d, 0xf3, 0x71, 0x21, 0xf7, 0xb1, 0x7c, 0xe9, 0x8f, 0xa3, 0xcb, 0x7e, 0x2c, 0xe9, 0xb0, 0x2d, 0xfe, 0x84, 0x66, 0x3f, 0x19, 0xcd, 0x68, 0xac, 0x22, 0x54, 0xa9, 0x88, 0x60, 0xc0, 0x14, 0x1c, 0x5f, 0x16, 0x40, 0x23, 0x1d, 0x93, 0x03, 0x94, 0x95, 0xa5, 0x52, 0xd0, 0x4a, 0x1a, 0x2a, 0xa6, 0x71, 0x89, 0x98, 0x14, 0x89, 0xa6, 0x45, 0xfc, 0x74, 0xad, 0xcc, 0xe9, 0x01, 0xc7, 0x9c, 0xef, 0x40, 0xac, 0xdc, 0x97, 0xcd, 0x21, 0x5b, 0xd4, 0x97, 0xe0, 0xba, 0xca, 0x47, 0x71, 0x8d, 0xd0, 0xa5, 0x7a, 0x46, 0xf1, 0xdc, 0xbe, 0xe9, 0x9e, 0x62, 0x8c, 0xb4, 0xe5, 0x2e, 0x25, 0xe3, 0x70, 0xa6, 0xee, 0x91, 0xc0, 0xb9, 0x5d, 0x7b, 0x13, 0xdf, 0xa0, 0xa4, 0xb2, 0x5f, 0x00, 0xb5, 0x1e, 0x3e, 0x65, 0x85, 0x9d, 0xb1, 0xc2, 0xf3, 0x7b, 0x3c, 0x80, 0xf0, 0x8c, 0x78, 0x46, 0xd6, 0x0c, 0x0e, 0xac, 0xee, 0x4d, 0xad, 0x6a, 0x6e, 0xa8, 0x2b, 0x2f, 0x0b, 0xfa, 0x9d, 0x76, 0x93, 0x41, 0xcb, 0x85, 0x80, 0xd4, 0xb9, 0x21, 0x20, 0x06, 0x25, 0xed, 0xe4, 0x69, 0x45, 0x66, 0x99, 0xb0, 0xd0, 0xb2, 0x13, 0x1d, 0x88, 0x1e, 0xc0, 0xbc, 0x12, 0xe1, 0x80, 0x57, 0x33, 0x7f, 0x88, 0x14, 0x51, 0x50, 0x6d, 0xfe, 0x6b, 0x8e, 0xda, 0x24, 0xbf, 0x94, 0x0d, 0x1d, 0x0d, 0x1d, 0x67, 0x43, 0x47, 0x6e, 0x37, 0x0a, 0x1d, 0xcd, 0x5f, 0x5d, 0x62, 0xc2, 0x33, 0x2a, 0xc5, 0xfc, 0xef, 0x32, 0x61, 0x24, 0x3d, 0x23, 0x61, 0x72, 0xd4, 0xea, 0x5d, 0x4b, 0x07, 0x96, 0x96, 0x98, 0xf3, 0x99, 0x5a, 0x7c, 0x7e, 0x68, 0xe5, 0x28, 0x98, 0x06, 0xc2, 0x4c, 0xf4, 0x13, 0xff, 0x81, 0x0d, 0x93, 0x17, 0xfb, 0x9b, 0x49, 0x4a, 0x0c, 0x38, 0x3a, 0x4d, 0x74, 0x05, 0xd0, 0x15, 0x7f, 0x9b, 0x27, 0xd1, 0x8c, 0x10, 0x22, 0x89, 0x68, 0x1f, 0x81, 0xce, 0x51, 0x58, 0x8c, 0x05, 0x77, 0xac, 0x8d, 0x49, 0xf3, 0x58, 0xf4, 0x3f, 0xaa, 0x71, 0x89, 0xcd, 0x5a, 0x76, 0x83, 0x43, 0xf4, 0x88, 0xe5, 0x39, 0xbd, 0xe0, 0xc6, 0x9c, 0x39, 0x0e, 0xc7, 0xbf, 0x2f, 0xd5, 0xf1, 0x6f, 0xfd, 0x4b, 0x5c, 0x35, 0xcd, 0x54, 0x57, 0x67, 0x47, 0x5b, 0x43, 0x7d, 0x55, 0xa5, 0xc7, 0x65, 0xb7, 0x6a, 0x35, 0x84, 0x19, 0x98, 0x64, 0x59, 0x98, 0xa3, 0x28, 0x5e, 0x59, 0xe5, 0xd5, 0xa2, 0x71, 0x05, 0x8b, 0x4a, 0x3f, 0xd1, 0xac, 0xa5, 0x9d, 0x7b, 0xfa, 0xc4, 0x62, 0x5e, 0xa8, 0xd1, 0x81, 0xa9, 0x84, 0xae, 0xb4, 0x6b, 0x72, 0xd3, 0x64, 0x57, 0xa9, 0xe3, 0x3f, 0xdc, 0xc9, 0x0d, 0x4d, 0x64, 0xef, 0x12, 0xf5, 0x9e, 0x94, 0xbe, 0xc6, 0x8d, 0xe7, 0xc6, 0xdf, 0x38, 0xf7, 0xb9, 0x69, 0x7f, 0xb6, 0x44, 0xdc, 0xc9, 0xc0, 0x48, 0x6c, 0xe6, 0x53, 0x35, 0x17, 0xcd, 0xc1, 0x0a, 0xab, 0xbf, 0xdc, 0x6b, 0xf7, 0x14, 0x35, 0x4f, 0xb4, 0x34, 0xd7, 0xcf, 0xff, 0x31, 0xd0, 0x1c, 0x75, 0x90, 0x57, 0xb9, 0x42, 0x4f, 0x15, 0x8e, 0x40, 0x85, 0xdf, 0xe1, 0x0e, 0x37, 0x0e, 0x37, 0x06, 0x7b, 0xdb, 0xea, 0x8a, 0x0b, 0x2a, 0x46, 0x0e, 0xb5, 0x93, 0xfa, 0xec, 0x69, 0x96, 0x5a, 0x9b, 0xac, 0x61, 0xfd, 0xd4, 0x50, 0xfa, 0x12, 0xfd, 0x1a, 0x73, 0x05, 0xee, 0x7c, 0xed, 0xa0, 0x8f, 0x85, 0xa0, 0x98, 0xf4, 0x40, 0x46, 0x36, 0x02, 0x20, 0x47, 0x95, 0x23, 0xa3, 0x40, 0x42, 0x04, 0x00, 0x23, 0x42, 0xfb, 0xe0, 0x52, 0x1f, 0x88, 0x19, 0xde, 0x16, 0xad, 0x24, 0x64, 0x24, 0x45, 0xca, 0x28, 0x61, 0x7e, 0x28, 0xe2, 0x24, 0xe7, 0x16, 0x17, 0xb2, 0x29, 0x90, 0xcc, 0x59, 0xae, 0x11, 0x41, 0x75, 0xd4, 0xc2, 0x9b, 0xef, 0xbb, 0x27, 0xbf, 0x2f, 0x7a, 0xdc, 0x3a, 0xd8, 0x97, 0x26, 0x65, 0x34, 0x1c, 0x55, 0x5a, 0x4e, 0x53, 0x72, 0x81, 0xd9, 0xfa, 0xc1, 0x5f, 0x91, 0x6c, 0x59, 0xd4, 0x7b, 0x51, 0xa2, 0xea, 0x92, 0xdf, 0x22, 0xcc, 0x54, 0x85, 0xb3, 0x45, 0x87, 0x8a, 0x05, 0xd7, 0xd6, 0xf8, 0x30, 0x58, 0xc0, 0x6f, 0x2c, 0x54, 0xa0, 0xed, 0xce, 0xc0, 0xe1, 0xe3, 0x3c, 0xfe, 0x0f, 0x04, 0x0e, 0x34, 0x2c, 0x7d, 0x5e, 0x09, 0xce, 0xb5, 0x56, 0xda, 0xe3, 0xd1, 0xb0, 0x26, 0xfd, 0x5d, 0x50, 0x91, 0x7f, 0xca, 0x9c, 0x87, 0x31, 0x8a, 0x1e, 0xe9, 0x6a, 0xd8, 0xd8, 0x52, 0x98, 0x9d, 0x2b, 0x15, 0xc0, 0xde, 0x9e, 0xd4, 0x58, 0x0b, 0xb5, 0xe4, 0x96, 0x7f, 0xc9, 0xc1, 0x1c, 0xbc, 0xba, 0xe6, 0xdc, 0x54, 0x42, 0x6b, 0x18, 0x2a, 0x50, 0x29, 0x5f, 0x9f, 0x2d, 0x4d, 0x04, 0x3b, 0xa7, 0x1b, 0xc8, 0xb5, 0xd9, 0xc9, 0x81, 0x6b, 0xde, 0x85, 0x16, 0x62, 0xcc, 0x05, 0xb8, 0xa6, 0x95, 0x84, 0x8d, 0x98, 0x06, 0xa7, 0x58, 0x86, 0x49, 0xe3, 0xf4, 0x3a, 0x92, 0x92, 0x97, 0x18, 0xe0, 0x5e, 0x92, 0xea, 0x24, 0xc5, 0x98, 0xa0, 0x5a, 0xdc, 0xc1, 0x15, 0x70, 0x46, 0x1f, 0x32, 0x8b, 0x3f, 0xcc, 0xde, 0x27, 0x85, 0xf7, 0x79, 0x15, 0x00, 0x47, 0x4f, 0x21, 0xdb, 0xc7, 0x93, 0x3c, 0xf0, 0x87, 0xf8, 0x52, 0xe4, 0xda, 0x61, 0x56, 0xe9, 0xdd, 0x29, 0xfc, 0x3b, 0x27, 0x7f, 0x52, 0x09, 0xad, 0x70, 0xf1, 0x38, 0xaa, 0x48, 0xb2, 0x33, 0xc3, 0x7b, 0xf9, 0x77, 0x7f, 0xc5, 0x9e, 0x4c, 0xf1, 0xe9, 0xea, 0x25, 0xbf, 0x02, 0x0e, 0x2f, 0xff, 0x05, 0x60, 0xb9, 0xfe, 0x68, 0x02, 0xe5, 0x75, 0x25, 0x97, 0xeb, 0x8a, 0xc2, 0xd2, 0x6c, 0xc7, 0x94, 0x12, 0x20, 0x8f, 0x8a, 0xfb, 0x12, 0x34, 0x81, 0xe0, 0xde, 0xb2, 0x79, 0xe3, 0x86, 0xc9, 0x89, 0xb1, 0x35, 0x83, 0xfd, 0xbd, 0xf5, 0xb5, 0x15, 0x51, 0xbf, 0x17, 0x95, 0x8b, 0xe2, 0xe9, 0x89, 0x54, 0xf9, 0xf4, 0x44, 0x7e, 0xf6, 0x58, 0x98, 0xad, 0x2b, 0xed, 0x61, 0x6e, 0x92, 0xac, 0x08, 0x9d, 0x39, 0x2d, 0x26, 0xee, 0x63, 0x4f, 0x54, 0xaa, 0xd9, 0xa3, 0x08, 0x1f, 0xa2, 0x2f, 0x9a, 0x9f, 0xa8, 0xae, 0x29, 0xea, 0x70, 0x7a, 0x03, 0x95, 0xb1, 0xf4, 0xbf, 0x53, 0x5f, 0xca, 0x92, 0x3e, 0x75, 0xfb, 0x9b, 0xeb, 0x58, 0x2e, 0xa3, 0xd1, 0x9a, 0x5c, 0x2e, 0xa3, 0x94, 0x44, 0x2d, 0x0d, 0xb4, 0x39, 0xea, 0xee, 0x4f, 0x1d, 0x3b, 0x95, 0x3d, 0x52, 0xff, 0xd7, 0xb5, 0x5b, 0x73, 0x89, 0x8d, 0x2e, 0x34, 0xb4, 0xfa, 0xdd, 0xf5, 0x97, 0x46, 0xb6, 0x7e, 0xbe, 0xcd, 0x2d, 0xa0, 0x38, 0x0a, 0xae, 0x5b, 0x92, 0xe2, 0x68, 0x7b, 0x54, 0x53, 0x64, 0x6b, 0xa9, 0xbf, 0x70, 0x27, 0x39, 0x96, 0x9d, 0x9f, 0x6e, 0xc4, 0x76, 0x84, 0xe3, 0xc8, 0xe9, 0x8b, 0x22, 0x07, 0xf3, 0x02, 0xd1, 0x09, 0xa4, 0x9c, 0x0f, 0x57, 0x5b, 0x43, 0xca, 0xa4, 0x08, 0x42, 0x42, 0xeb, 0xd9, 0x6c, 0x50, 0xce, 0xa3, 0xce, 0xdc, 0x17, 0x71, 0xf7, 0xf3, 0x6e, 0x51, 0x59, 0x08, 0x65, 0x8c, 0x67, 0x31, 0xdf, 0xc1, 0x26, 0x6b, 0xb1, 0x27, 0xb3, 0x98, 0xd0, 0x1c, 0x17, 0x57, 0x60, 0xb3, 0x7a, 0x04, 0x58, 0xe2, 0x39, 0xc0, 0x6b, 0xb1, 0xf2, 0x6c, 0x57, 0xc1, 0x99, 0xae, 0x48, 0xb4, 0x42, 0x47, 0x38, 0xf3, 0x48, 0x06, 0x31, 0x70, 0xcc, 0xdd, 0x54, 0x5f, 0xc1, 0x01, 0x0d, 0x52, 0x3a, 0x1d, 0x6d, 0xc9, 0x86, 0xea, 0x84, 0x09, 0xf1, 0xaa, 0x7a, 0xfc, 0x61, 0x1f, 0x56, 0x3a, 0x2b, 0x27, 0x0f, 0xe1, 0xf4, 0x8f, 0x15, 0x48, 0x99, 0x2b, 0xcc, 0xd4, 0x11, 0x4f, 0xe7, 0x20, 0x9b, 0xf5, 0x91, 0xaf, 0x70, 0xd8, 0xc4, 0x90, 0xf4, 0xeb, 0x1c, 0x29, 0xf3, 0xc9, 0xc1, 0xb6, 0x5a, 0x3a, 0x4b, 0xaf, 0xfa, 0xf9, 0x90, 0x9c, 0xda, 0xee, 0xe3, 0x93, 0x41, 0x4e, 0x08, 0x35, 0x0e, 0xf9, 0x10, 0x97, 0x2e, 0x92, 0x65, 0x65, 0x1e, 0xc7, 0x2c, 0xaa, 0x9b, 0x95, 0x1c, 0x8b, 0xea, 0x70, 0x10, 0x8e, 0xe7, 0x1d, 0x70, 0x03, 0xfe, 0x36, 0xb4, 0x9d, 0x9b, 0x89, 0xc6, 0x64, 0x9d, 0x03, 0x63, 0x33, 0x79, 0xfc, 0x25, 0x3a, 0x55, 0x9c, 0x49, 0xe5, 0xa0, 0x30, 0xf9, 0xbd, 0x1a, 0xfe, 0xda, 0x4c, 0x34, 0xd7, 0xd7, 0x04, 0x7c, 0x6e, 0x97, 0xad, 0x20, 0x2e, 0x46, 0xc7, 0x34, 0x68, 0x57, 0xae, 0x03, 0xf9, 0x05, 0x1a, 0x45, 0xcb, 0x6c, 0xd2, 0xa0, 0x15, 0x6d, 0xc2, 0x6f, 0x0c, 0x4c, 0x55, 0xe9, 0x72, 0xe1, 0x6a, 0x25, 0x18, 0xf2, 0x96, 0xca, 0xdd, 0x95, 0x1b, 0x36, 0x9d, 0x1d, 0xff, 0xd1, 0xb9, 0xcf, 0x4e, 0x07, 0x98, 0xeb, 0xef, 0x9f, 0x44, 0xdb, 0x6e, 0xce, 0x5e, 0x5c, 0x8f, 0x80, 0x6a, 0x35, 0x1d, 0x96, 0xe5, 0xf7, 0x61, 0xfa, 0x18, 0x9a, 0xbb, 0x27, 0xe0, 0x0b, 0xfc, 0x0c, 0xbe, 0xab, 0x99, 0x08, 0x22, 0x04, 0x17, 0x89, 0x33, 0x1e, 0xb9, 0xb3, 0x28, 0x8a, 0x0f, 0xed, 0xe1, 0x7a, 0x37, 0x5e, 0xa3, 0xc7, 0xc0, 0x46, 0xbd, 0x39, 0x7f, 0x9a, 0xcf, 0xa2, 0xab, 0x03, 0xe8, 0xa4, 0x89, 0xfe, 0x19, 0xb2, 0xe7, 0xa0, 0xd3, 0xfc, 0x7e, 0xdf, 0xad, 0x83, 0x45, 0xd7, 0x3a, 0x92, 0x26, 0x68, 0x20, 0xfe, 0x98, 0x92, 0x53, 0x97, 0x59, 0xf7, 0xd8, 0x9e, 0xdc, 0xd1, 0x4f, 0x7f, 0xf6, 0xfd, 0x35, 0xab, 0xea, 0xc8, 0x6f, 0x3e, 0xcc, 0xee, 0xf9, 0x3b, 0xa0, 0x82, 0x7f, 0x95, 0x79, 0x8c, 0x10, 0x13, 0x6a, 0x22, 0x9a, 0x2c, 0x15, 0x21, 0x66, 0x18, 0x06, 0xe1, 0x0a, 0x29, 0x82, 0x4d, 0x88, 0x61, 0x18, 0x6c, 0x13, 0xb1, 0x04, 0xe0, 0xd3, 0x38, 0xe5, 0x12, 0xb3, 0xbf, 0xa3, 0x00, 0x8f, 0x27, 0x4b, 0xc0, 0x8f, 0xff, 0x51, 0xaf, 0x9e, 0x99, 0xff, 0xd2, 0x19, 0x6a, 0x0d, 0xfb, 0x7f, 0x70, 0x28, 0x7d, 0x8e, 0x0c, 0x83, 0x6e, 0xf6, 0x27, 0x01, 0x16, 0x0e, 0xa1, 0x5c, 0x71, 0x9c, 0xaf, 0x7d, 0x30, 0xa9, 0x32, 0x01, 0x92, 0xf6, 0x43, 0x63, 0x02, 0x8e, 0x2c, 0x43, 0x71, 0xc0, 0x36, 0x0f, 0xc5, 0x07, 0x2e, 0x77, 0xa4, 0x32, 0x85, 0x8d, 0x73, 0x12, 0xc8, 0x51, 0x5d, 0x63, 0x8a, 0x14, 0x91, 0x14, 0xce, 0x1e, 0x5e, 0xb1, 0xe9, 0x68, 0x52, 0x0d, 0x7f, 0x09, 0x10, 0x81, 0xa0, 0xdf, 0x13, 0xf4, 0x6a, 0x51, 0xa1, 0x2c, 0x61, 0xc2, 0xf9, 0x12, 0x78, 0xb7, 0xfc, 0x04, 0xf4, 0xca, 0x7a, 0x77, 0x19, 0x42, 0x07, 0xa9, 0x4b, 0x58, 0x74, 0x50, 0x36, 0x1f, 0xfd, 0x0d, 0x36, 0x1f, 0x9d, 0x52, 0xe0, 0xd2, 0x81, 0xfd, 0xec, 0xf1, 0xf2, 0x3c, 0x85, 0x73, 0xd3, 0x7f, 0x87, 0xf3, 0xe3, 0x0e, 0xa5, 0x2f, 0xd2, 0xbf, 0xc0, 0x79, 0xda, 0x5f, 0x46, 0x59, 0xe8, 0x24, 0x9c, 0x5f, 0x8c, 0x48, 0xca, 0xbd, 0x2b, 0x3a, 0x30, 0x0f, 0x08, 0xde, 0x95, 0xc8, 0x7d, 0x7e, 0xb4, 0x15, 0x08, 0x00, 0xfd, 0x85, 0xb8, 0x18, 0x0c, 0xbc, 0x09, 0x24, 0xe2, 0xb9, 0xe5, 0x7b, 0x11, 0x82, 0x4e, 0xd0, 0x88, 0xe2, 0xa5, 0xc4, 0xf5, 0x23, 0x6e, 0xa2, 0x1b, 0x8a, 0xe4, 0x78, 0xbc, 0x7a, 0x24, 0xaf, 0x60, 0xa1, 0x94, 0x27, 0xf1, 0x15, 0x70, 0xb0, 0x2d, 0x2f, 0x34, 0x01, 0xec, 0x9f, 0xfe, 0x05, 0x87, 0xc6, 0x5e, 0x49, 0x8a, 0xd7, 0x78, 0xe8, 0x36, 0x7f, 0x36, 0xff, 0xec, 0x4a, 0x12, 0xcd, 0x24, 0x04, 0x90, 0x84, 0x1e, 0xce, 0xd7, 0xb7, 0x30, 0xee, 0xca, 0x44, 0x94, 0x25, 0x4b, 0x68, 0x84, 0xfb, 0xed, 0x46, 0x45, 0xbd, 0x11, 0x95, 0xd1, 0x66, 0x2e, 0xdd, 0x81, 0x2b, 0xc1, 0x67, 0x32, 0x22, 0x6a, 0x5b, 0x39, 0xae, 0xae, 0x88, 0xaa, 0x7b, 0x03, 0x1e, 0x13, 0xc2, 0xd2, 0xcc, 0x7b, 0xc4, 0x2c, 0x1c, 0x84, 0xfc, 0xe4, 0x11, 0xa9, 0x5e, 0x26, 0x35, 0x48, 0x8f, 0x90, 0xf7, 0x3f, 0x50, 0xe0, 0x94, 0xf4, 0xa4, 0xc7, 0x7a, 0x44, 0x1e, 0xcb, 0x83, 0xe0, 0xbe, 0xaf, 0x81, 0x3b, 0x40, 0x9f, 0xb9, 0xc9, 0xe7, 0x6f, 0x32, 0xa7, 0x5f, 0x48, 0xdf, 0x61, 0xd1, 0x7f, 0xf7, 0xbb, 0x5a, 0x2b, 0x42, 0xa4, 0x90, 0x0b, 0x69, 0xb8, 0x66, 0xff, 0x27, 0x8e, 0xf5, 0x5a, 0x88, 0xbe, 0x64, 0x0a, 0xba, 0x1d, 0xd0, 0x97, 0xc1, 0x58, 0x6e, 0x40, 0x6e, 0x42, 0xee, 0x03, 0x43, 0x11, 0xcc, 0x26, 0xc4, 0x8c, 0x9d, 0xa5, 0x5b, 0xa6, 0xc7, 0xb8, 0x02, 0x3c, 0x22, 0x11, 0x74, 0x2d, 0x2d, 0x22, 0x54, 0x78, 0x47, 0x8f, 0xb2, 0xbe, 0x15, 0x72, 0x99, 0x54, 0x22, 0x46, 0xe1, 0x61, 0x39, 0x7e, 0x4c, 0xfc, 0x8c, 0x7a, 0xb8, 0xbe, 0xd1, 0xbf, 0x20, 0xf7, 0x9c, 0x1e, 0x23, 0xf9, 0xa1, 0x06, 0xb9, 0x45, 0xf9, 0x79, 0x70, 0xa9, 0x10, 0xcc, 0xa6, 0x9f, 0x29, 0x04, 0x0f, 0xcc, 0xff, 0xc0, 0x95, 0x7e, 0x60, 0x5a, 0xe2, 0xb3, 0xde, 0x47, 0x32, 0xe4, 0x5b, 0xf0, 0x61, 0xff, 0x10, 0x68, 0xb2, 0xa7, 0x1f, 0x02, 0xff, 0x86, 0x73, 0xa0, 0x1e, 0x7e, 0xf7, 0xbb, 0x06, 0x33, 0xf5, 0xce, 0xc3, 0xd8, 0x86, 0xeb, 0x59, 0x78, 0x53, 0xa4, 0xa0, 0xe5, 0x58, 0x6e, 0xa3, 0xc4, 0x37, 0x38, 0x36, 0xd7, 0xee, 0x16, 0x68, 0x68, 0x87, 0xbd, 0x24, 0x81, 0x4b, 0xea, 0xe0, 0x4b, 0x29, 0x77, 0x39, 0xca, 0x7b, 0xf8, 0x32, 0x8a, 0xa4, 0x64, 0x88, 0x25, 0x0a, 0x1a, 0xfa, 0x0c, 0x31, 0x27, 0x01, 0xa4, 0x52, 0x45, 0x02, 0x86, 0x84, 0x66, 0x15, 0x32, 0x67, 0xc6, 0x14, 0x40, 0x2e, 0xdf, 0x9d, 0xa2, 0x71, 0x9c, 0x13, 0xad, 0xc4, 0x9d, 0x28, 0x25, 0xa0, 0x91, 0x90, 0x49, 0x49, 0xe9, 0xd2, 0xdd, 0x88, 0x6c, 0x2f, 0xb8, 0xc7, 0x2d, 0xf9, 0x05, 0xa3, 0xc9, 0x42, 0x82, 0x18, 0x1d, 0x59, 0xdd, 0xd7, 0xd9, 0x9e, 0x6c, 0xac, 0xab, 0x89, 0xc7, 0x4a, 0x8b, 0x83, 0x7e, 0x04, 0x1f, 0xd3, 0x69, 0xd8, 0x62, 0x99, 0x3e, 0x75, 0x96, 0xf8, 0xac, 0x09, 0x93, 0xe7, 0x06, 0xd8, 0x5d, 0x2b, 0x9f, 0x97, 0x38, 0xc8, 0x7b, 0xd2, 0x02, 0x32, 0x34, 0x96, 0xb5, 0x4b, 0x68, 0x1a, 0x89, 0xdf, 0x75, 0xd6, 0x0e, 0x25, 0x0e, 0x9e, 0x56, 0x7d, 0xe9, 0xd6, 0x27, 0xb7, 0x44, 0x26, 0xfb, 0xaa, 0x36, 0x74, 0x84, 0xa7, 0x26, 0x93, 0xeb, 0x10, 0xad, 0xae, 0x5d, 0xe6, 0x1d, 0xfa, 0xe2, 0xbe, 0x43, 0x17, 0xbd, 0x89, 0xcf, 0x1e, 0x5c, 0x73, 0xff, 0xf6, 0x7a, 0xad, 0x8d, 0xdc, 0xc3, 0x44, 0x52, 0xb3, 0x0d, 0x6d, 0xeb, 0xeb, 0x03, 0x5a, 0xbb, 0xb2, 0xd2, 0xbb, 0x7e, 0x66, 0x47, 0xe5, 0xbe, 0xaf, 0xd6, 0xd8, 0xd7, 0x6d, 0x3f, 0x54, 0xdf, 0xbe, 0x7f, 0x4d, 0x69, 0xf2, 0xe2, 0x9f, 0x9e, 0x05, 0x4f, 0x34, 0x6c, 0xeb, 0x2b, 0x79, 0xf8, 0xae, 0xc3, 0x4c, 0x61, 0xbc, 0x2d, 0x34, 0x98, 0xf2, 0xb6, 0xef, 0xe8, 0x7a, 0xeb, 0x96, 0x5b, 0x0b, 0x43, 0x85, 0x46, 0x75, 0xd3, 0xc0, 0xea, 0xcd, 0xc3, 0x43, 0x9b, 0x9a, 0x8f, 0x7c, 0x7e, 0x2e, 0xe2, 0x1a, 0xbe, 0x63, 0x6d, 0x91, 0xdd, 0x6b, 0xd7, 0xaf, 0x2a, 0xaa, 0x0f, 0xea, 0x76, 0xcd, 0xd5, 0xaf, 0x4d, 0x14, 0xf8, 0x3a, 0x76, 0x74, 0xd6, 0xef, 0x1f, 0xaf, 0xa7, 0xe1, 0xb8, 0x55, 0xc2, 0xc1, 0x43, 0xb5, 0xd5, 0x54, 0x70, 0x77, 0x28, 0x25, 0x86, 0x93, 0x6b, 0xdc, 0x39, 0xe7, 0x1e, 0x52, 0x54, 0x0b, 0x8b, 0x82, 0x3b, 0xe3, 0x26, 0x39, 0x40, 0x45, 0xbd, 0x14, 0x58, 0x15, 0xca, 0x00, 0x17, 0xa3, 0x9f, 0x16, 0xf7, 0x14, 0x58, 0xd4, 0x6a, 0x40, 0xe0, 0x14, 0xbd, 0x80, 0xa5, 0xb4, 0xa0, 0x54, 0x6d, 0x56, 0x9b, 0x74, 0x1a, 0x3c, 0xe5, 0xc4, 0x84, 0x0a, 0xa8, 0x94, 0x19, 0x27, 0x36, 0x50, 0x0c, 0x8c, 0x31, 0xa3, 0xd7, 0x58, 0xc0, 0x32, 0xa0, 0x06, 0xbc, 0x71, 0x1e, 0xb6, 0xcf, 0x42, 0xe4, 0xe3, 0x08, 0x68, 0x48, 0xd6, 0xc7, 0x3b, 0x8b, 0xf5, 0xeb, 0xa6, 0xbf, 0x7a, 0xf5, 0xea, 0x87, 0x36, 0x4d, 0x8f, 0x6e, 0x9a, 0xba, 0x30, 0x56, 0xb4, 0x76, 0xf2, 0x7b, 0xe5, 0x93, 0xe7, 0x47, 0xd3, 0x77, 0xde, 0x73, 0xcf, 0x7d, 0xb6, 0xe2, 0x6a, 0x47, 0xfb, 0x9a, 0xed, 0xeb, 0xe7, 0xc8, 0x9f, 0xa5, 0x7f, 0xd0, 0x3b, 0xd8, 0xdd, 0x7e, 0xc1, 0xdf, 0x3a, 0x51, 0xdd, 0x7f, 0xb4, 0x30, 0x7a, 0x35, 0xb9, 0xb3, 0xbf, 0x74, 0x61, 0x3d, 0xf9, 0x71, 0x6c, 0xaf, 0xf5, 0x2d, 0xfc, 0x85, 0xfa, 0x0c, 0x4d, 0x11, 0x25, 0xc4, 0x21, 0x94, 0x2d, 0x4c, 0x23, 0xde, 0x10, 0x26, 0xcb, 0x4d, 0x49, 0xd1, 0xd0, 0x8a, 0x46, 0x5b, 0x3d, 0xc1, 0xd0, 0x04, 0xb3, 0x9b, 0x77, 0xc0, 0x76, 0x67, 0x15, 0x17, 0xc0, 0x47, 0x80, 0x1f, 0xd8, 0x8e, 0xc7, 0x1f, 0x22, 0xe0, 0x86, 0xc5, 0xa4, 0x55, 0x43, 0x13, 0xba, 0x04, 0x94, 0xb0, 0x69, 0x00, 0xae, 0xbc, 0x30, 0x8b, 0x78, 0xf1, 0xb1, 0x77, 0x55, 0x82, 0x5a, 0xa5, 0x91, 0xa7, 0x1a, 0x5f, 0x3c, 0xb6, 0xf7, 0xea, 0x1d, 0xad, 0x5d, 0xa7, 0xbf, 0xb4, 0x67, 0xee, 0x4a, 0x43, 0x93, 0x08, 0xda, 0xc0, 0x85, 0xf1, 0xfe, 0xfa, 0xd6, 0x7d, 0x03, 0xa5, 0xd1, 0x35, 0x73, 0xcd, 0xc1, 0xc6, 0x78, 0x99, 0x45, 0xfd, 0xa6, 0x7c, 0xb0, 0x77, 0x00, 0x1f, 0xe8, 0xbd, 0x7a, 0xbe, 0xa7, 0xb3, 0x7e, 0x54, 0x82, 0x4c, 0xda, 0xe8, 0xd8, 0xa9, 0xb5, 0xc3, 0xa7, 0xc7, 0xa2, 0x12, 0x9d, 0xc3, 0xa8, 0x64, 0x73, 0x2c, 0x68, 0x84, 0x63, 0x53, 0x10, 0x05, 0xf8, 0x94, 0x88, 0xb3, 0xff, 0x66, 0x33, 0xa7, 0xdb, 0x70, 0x8b, 0xf5, 0x84, 0x3c, 0x18, 0x7b, 0x01, 0xd8, 0xcc, 0x04, 0x94, 0x3d, 0x21, 0x66, 0x13, 0x16, 0xd0, 0x23, 0xd1, 0xaa, 0xee, 0xbb, 0x5e, 0xdd, 0x9f, 0x3e, 0x0e, 0x4e, 0xec, 0x7f, 0xf5, 0xae, 0xee, 0xd7, 0x4a, 0xd6, 0x1e, 0x4a, 0x7d, 0xe3, 0x1b, 0xa9, 0x43, 0x6b, 0x4b, 0xc0, 0xfc, 0xdc, 0xcb, 0x77, 0xa5, 0xa8, 0x77, 0x52, 0x67, 0x5e, 0xde, 0x33, 0x78, 0x6a, 0xbc, 0x7c, 0xbe, 0xba, 0x7c, 0xec, 0x34, 0xd2, 0x99, 0x46, 0xf8, 0x37, 0x0b, 0xe0, 0xdf, 0xd4, 0x10, 0x1e, 0xa2, 0x29, 0x59, 0x2f, 0x46, 0x5e, 0x09, 0x9e, 0x33, 0xf8, 0x9c, 0x54, 0x8a, 0x33, 0xfa, 0x24, 0x3c, 0x40, 0x7a, 0x9a, 0xe9, 0xd1, 0x69, 0x01, 0x81, 0x6a, 0xa5, 0xd9, 0xad, 0x5a, 0x8f, 0xce, 0x83, 0xf5, 0x93, 0x94, 0xd0, 0x00, 0x8d, 0x20, 0xde, 0x81, 0x1f, 0xc6, 0xe8, 0xd5, 0xea, 0x79, 0xdf, 0x02, 0xee, 0xff, 0x68, 0x76, 0x98, 0xbb, 0x0f, 0xad, 0x2d, 0x2d, 0x5d, 0x7b, 0xa8, 0xfb, 0x9b, 0xe9, 0xf4, 0x37, 0x6f, 0x81, 0xcf, 0x07, 0x9f, 0xf5, 0x96, 0xf4, 0xb1, 0xdb, 0x6f, 0xff, 0x08, 0x7c, 0x96, 0xb5, 0xe8, 0xa9, 0xd2, 0x3f, 0xda, 0x99, 0xfe, 0x1e, 0xb5, 0x11, 0x3d, 0x24, 0x7a, 0x5c, 0xf0, 0xed, 0x9d, 0xe0, 0x1a, 0x97, 0x27, 0x2c, 0xc5, 0x18, 0x2c, 0xa8, 0xd5, 0x09, 0x96, 0xa8, 0x76, 0x13, 0x66, 0x1d, 0x19, 0xe5, 0x73, 0xb0, 0x30, 0x66, 0x0c, 0x33, 0x66, 0xc0, 0x56, 0x72, 0x6c, 0x85, 0xb0, 0x27, 0x7b, 0x7c, 0x1e, 0xa9, 0xc7, 0x08, 0xda, 0x51, 0x8e, 0x28, 0xb8, 0x36, 0x7f, 0x07, 0x79, 0x07, 0xb5, 0x81, 0xd5, 0x82, 0x48, 0x03, 0x02, 0xa2, 0x7c, 0xe1, 0xcf, 0x54, 0x3d, 0x2d, 0x21, 0x9a, 0x50, 0xad, 0x3c, 0x25, 0xb4, 0x72, 0xac, 0x38, 0xa7, 0x0e, 0x95, 0xfe, 0x16, 0x89, 0x51, 0x8c, 0x9b, 0xc3, 0x41, 0x31, 0x88, 0x50, 0x8d, 0x9e, 0xe2, 0x2b, 0x80, 0xe7, 0xe0, 0x30, 0x1b, 0xeb, 0x6b, 0xaa, 0xca, 0xcb, 0x10, 0x57, 0xac, 0xcb, 0x61, 0x31, 0xb1, 0xdc, 0x8b, 0xd9, 0x92, 0xad, 0x7c, 0x08, 0x3b, 0xb0, 0x44, 0x04, 0x3b, 0x87, 0x26, 0x2d, 0xf8, 0xbf, 0x99, 0x7b, 0xef, 0xf8, 0xb8, 0xaa, 0x33, 0x7f, 0xf8, 0x9e, 0x5b, 0xa6, 0xb7, 0x3b, 0x77, 0x7a, 0xbf, 0xd3, 0x35, 0x45, 0x33, 0xd2, 0x34, 0x75, 0x8d, 0x7a, 0xb5, 0xe5, 0x26, 0xd9, 0xb2, 0xdc, 0x25, 0xcb, 0x0d, 0xe3, 0x46, 0x31, 0x60, 0x63, 0x8a, 0x29, 0xb6, 0xc1, 0x06, 0x4c, 0x30, 0xb0, 0x26, 0x01, 0x42, 0x09, 0x25, 0x04, 0x08, 0x90, 0x90, 0xd0, 0x4b, 0x76, 0x93, 0x25, 0x6d, 0x13, 0x9c, 0xdf, 0xb2, 0x3f, 0x02, 0xd9, 0x6c, 0x92, 0x25, 0x09, 0x09, 0x90, 0x90, 0x90, 0x80, 0xae, 0xdf, 0x73, 0xce, 0xbd, 0x53, 0x24, 0xcb, 0x40, 0xf2, 0xbe, 0x7f, 0xbc, 0xf9, 0x10, 0x8f, 0x66, 0x6e, 0x39, 0xfd, 0xe9, 0xcf, 0xf7, 0xa9, 0x2b, 0x61, 0xdd, 0x75, 0x4e, 0x8f, 0x26, 0xdb, 0x10, 0xd6, 0xdd, 0xcd, 0x93, 0xbb, 0xef, 0x6d, 0x4c, 0xaa, 0x39, 0x9d, 0x36, 0xd4, 0xb0, 0xa8, 0x29, 0x3b, 0x92, 0x73, 0x78, 0x9b, 0x97, 0xe5, 0xf7, 0xec, 0xb9, 0xe4, 0x02, 0x6f, 0xe3, 0x92, 0xac, 0x04, 0x91, 0x47, 0x6d, 0x8b, 0x21, 0xd0, 0xbb, 0x85, 0xbb, 0xfa, 0xc7, 0x2e, 0x46, 0xa0, 0x77, 0x45, 0x6f, 0x7e, 0x72, 0x41, 0xb2, 0x3b, 0x3f, 0x5c, 0x86, 0xc8, 0xf3, 0xe6, 0xc3, 0x96, 0x2b, 0x46, 0xfb, 0x43, 0xed, 0x49, 0x04, 0x92, 0xc7, 0xa1, 0xb5, 0x9f, 0x12, 0xc6, 0xc1, 0x2e, 0x38, 0xaf, 0x94, 0x24, 0x5d, 0xa2, 0xd4, 0x2c, 0x09, 0xf4, 0x1f, 0xce, 0x28, 0x32, 0xd3, 0x6f, 0x45, 0x33, 0x6a, 0x32, 0xaa, 0x14, 0xf0, 0x1e, 0x8a, 0x65, 0xe0, 0xa0, 0x50, 0x38, 0x31, 0x35, 0x07, 0x3c, 0xf3, 0xd0, 0xcb, 0xff, 0x6b, 0x09, 0xd5, 0x3b, 0xab, 0x1d, 0xbf, 0x28, 0x65, 0x53, 0xf8, 0x85, 0xb3, 0x2e, 0x68, 0xb1, 0x04, 0xeb, 0x9c, 0xae, 0x7a, 0x74, 0xa5, 0x1e, 0x9d, 0x6d, 0xdd, 0xe9, 0x0f, 0xc1, 0x07, 0x38, 0xe7, 0xfb, 0x1c, 0x6c, 0xb0, 0x7d, 0x52, 0x0e, 0x08, 0x9c, 0x63, 0x25, 0xc7, 0xb1, 0xa1, 0x92, 0xb6, 0xcf, 0x12, 0x62, 0xf0, 0xa6, 0x94, 0x92, 0x71, 0x0e, 0x51, 0x32, 0xef, 0xce, 0xb9, 0xb0, 0x63, 0x6e, 0x54, 0x4d, 0x25, 0x21, 0x0f, 0x45, 0xd5, 0x8c, 0x3f, 0xc5, 0xf9, 0x2d, 0x95, 0xe3, 0xe2, 0x8f, 0xb4, 0x81, 0xea, 0xa4, 0x7e, 0xf0, 0x41, 0x6c, 0xd9, 0xfe, 0x25, 0x43, 0xc3, 0x50, 0xb3, 0x22, 0x39, 0x93, 0x3a, 0xe2, 0x59, 0xbc, 0xf4, 0x59, 0xb0, 0x65, 0xf4, 0xf0, 0xfa, 0x2c, 0xe7, 0x6a, 0x74, 0x19, 0x38, 0xd5, 0xfa, 0xdd, 0xe4, 0x71, 0x69, 0xff, 0xfd, 0x1a, 0xf6, 0xd9, 0x85, 0x66, 0xc9, 0x31, 0x4f, 0x56, 0x91, 0x24, 0xfe, 0x06, 0x79, 0x96, 0x0d, 0xf9, 0x90, 0x0c, 0xce, 0x57, 0x57, 0x31, 0x45, 0xd9, 0x43, 0x12, 0x02, 0x15, 0x64, 0xde, 0x16, 0x2b, 0xf5, 0xeb, 0xfb, 0x79, 0x9f, 0x46, 0x78, 0x5b, 0x17, 0xe0, 0x9b, 0x9f, 0x79, 0x26, 0x91, 0xb1, 0x02, 0x3b, 0x1b, 0x75, 0xdf, 0x2d, 0x3c, 0xa0, 0x77, 0xb3, 0x71, 0x3f, 0xc7, 0x81, 0x0b, 0x38, 0xbd, 0xd0, 0x49, 0x7d, 0x24, 0x2c, 0xf7, 0x82, 0xf5, 0x56, 0xa3, 0xb0, 0xd2, 0xe4, 0x54, 0x18, 0x52, 0xb0, 0xbd, 0x0e, 0xd8, 0x0f, 0x05, 0xec, 0x07, 0x4b, 0xc4, 0x8a, 0x11, 0x83, 0x46, 0xad, 0xa0, 0x29, 0x19, 0x4a, 0x50, 0x9b, 0xcf, 0x9d, 0xcc, 0x12, 0xac, 0xb5, 0xec, 0x4e, 0x96, 0xe7, 0xb2, 0xed, 0xf0, 0x4c, 0xb2, 0x21, 0xb3, 0x49, 0x0f, 0x58, 0x4a, 0x31, 0xf3, 0xc8, 0xf9, 0x96, 0xb5, 0xcf, 0x00, 0x99, 0xed, 0x0f, 0x5f, 0xfc, 0x22, 0x38, 0xb7, 0xf9, 0xa0, 0x4d, 0x59, 0x0f, 0xfe, 0x20, 0x6c, 0xbe, 0xf2, 0x32, 0xb7, 0x72, 0x9f, 0xf0, 0xa6, 0x94, 0x1f, 0xfd, 0x21, 0xbd, 0x18, 0xe7, 0x95, 0x47, 0x88, 0x36, 0x71, 0x51, 0xcc, 0x44, 0x25, 0xef, 0xad, 0x12, 0x6e, 0xe3, 0x9c, 0xef, 0x77, 0x1c, 0xd2, 0x84, 0xc2, 0x70, 0x2a, 0x73, 0x5f, 0x89, 0x27, 0x98, 0x1b, 0x82, 0x43, 0x2f, 0x46, 0x84, 0xa0, 0x44, 0x10, 0xd0, 0xe7, 0x0b, 0xc9, 0xa5, 0x17, 0x0e, 0x95, 0xa8, 0xc5, 0xd0, 0x85, 0x4b, 0x93, 0x90, 0x7c, 0x3d, 0x7f, 0x64, 0xe1, 0xc2, 0x23, 0xcf, 0xe3, 0x4c, 0xee, 0x85, 0x47, 0x9e, 0xc3, 0x44, 0xac, 0x6e, 0xd5, 0xc1, 0x25, 0xcb, 0x0e, 0xae, 0x4a, 0xa7, 0x57, 0x1d, 0xc4, 0x7c, 0x23, 0x07, 0xf9, 0xc6, 0xb1, 0x12, 0xdf, 0x70, 0x40, 0x1e, 0x18, 0xab, 0xc2, 0xc3, 0x0e, 0x95, 0x80, 0xae, 0x65, 0xe0, 0x53, 0x01, 0xb1, 0x3f, 0xc7, 0x7d, 0x38, 0xb4, 0x06, 0xf2, 0x8d, 0x68, 0xd8, 0xe3, 0x42, 0xc8, 0xd0, 0x0a, 0x59, 0x89, 0x6f, 0x14, 0xea, 0xad, 0x25, 0x84, 0xd0, 0x79, 0x80, 0x0f, 0x25, 0x0a, 0x90, 0xbf, 0xdd, 0xa0, 0xb7, 0xa5, 0x72, 0x6d, 0x91, 0xe2, 0xae, 0x65, 0xe9, 0xe4, 0x92, 0x5d, 0xdd, 0x2d, 0x8b, 0x72, 0x7e, 0x83, 0xde, 0x28, 0x6b, 0x6f, 0x79, 0x68, 0xe7, 0x8e, 0xa7, 0xaf, 0x1a, 0xe8, 0xbe, 0xec, 0xc9, 0x9d, 0x97, 0x3e, 0xd9, 0x36, 0xa8, 0x79, 0x4f, 0x6b, 0x76, 0x1b, 0x15, 0xe9, 0x89, 0xab, 0xc6, 0x96, 0x1d, 0x9c, 0x48, 0x43, 0x8e, 0xa1, 0x55, 0x8c, 0xb7, 0xf4, 0x0f, 0x1f, 0x7e, 0x6e, 0xf7, 0xee, 0xe7, 0x0f, 0x43, 0x7e, 0xb2, 0x44, 0x8d, 0xd7, 0x6a, 0x84, 0x30, 0xd1, 0xd3, 0xd4, 0x6f, 0xe0, 0x5a, 0x05, 0x88, 0xe6, 0xa1, 0x47, 0xeb, 0xa5, 0xb5, 0xc2, 0x2b, 0x51, 0x09, 0xd6, 0xc4, 0xf8, 0x55, 0x46, 0x9c, 0x20, 0x08, 0xd6, 0x96, 0x2f, 0x93, 0x70, 0xa1, 0xc2, 0xd2, 0x42, 0x71, 0x73, 0xce, 0x75, 0xa8, 0x04, 0x33, 0x84, 0x45, 0xa9, 0x11, 0x31, 0x78, 0xe7, 0xd0, 0xc0, 0xc0, 0x21, 0x11, 0x80, 0x80, 0xba, 0x0a, 0xc5, 0xf7, 0x7c, 0xeb, 0x85, 0xec, 0x06, 0xf8, 0xef, 0xf3, 0xe0, 0xf4, 0xce, 0x27, 0x0e, 0xf4, 0xf4, 0x1c, 0x78, 0x62, 0xe7, 0xae, 0xa7, 0xd0, 0xe7, 0x53, 0xbb, 0xfa, 0xcf, 0x1d, 0x8e, 0xdc, 0x76, 0xc3, 0xc0, 0xce, 0x05, 0x11, 0xa9, 0x9e, 0xf4, 0x52, 0xe2, 0x16, 0x7a, 0x2b, 0xf5, 0x6b, 0x22, 0x4e, 0xd8, 0x8b, 0x96, 0xa8, 0x8d, 0x24, 0xfa, 0x4a, 0xc0, 0x6e, 0xd3, 0x90, 0xb1, 0x79, 0x6b, 0x48, 0xa4, 0x92, 0x67, 0x0b, 0x95, 0xc2, 0xd7, 0xc8, 0x46, 0x05, 0x05, 0x0c, 0x99, 0x1e, 0x54, 0xd7, 0xb3, 0x95, 0xc9, 0x23, 0xb5, 0x80, 0xba, 0x40, 0x9b, 0x88, 0xd6, 0xae, 0xb9, 0x7e, 0xdd, 0xda, 0xa3, 0x6b, 0x6a, 0x6b, 0x12, 0x5a, 0x46, 0xbe, 0xb2, 0x67, 0xa0, 0xdc, 0xc3, 0x9e, 0x95, 0x0a, 0x1a, 0x50, 0x06, 0x43, 0x62, 0x63, 0xb1, 0x7f, 0xf7, 0x48, 0x3c, 0x3e, 0xb2, 0xbb, 0xbf, 0xb8, 0x31, 0x61, 0x30, 0xc8, 0x75, 0xcc, 0xd2, 0xee, 0x47, 0xf7, 0x9c, 0xf7, 0xd4, 0xfe, 0xce, 0xce, 0xfd, 0x4f, 0x9d, 0xb7, 0xe7, 0xd1, 0xee, 0xa5, 0x8c, 0x0e, 0xcb, 0xc0, 0x4b, 0x08, 0x25, 0xfd, 0x07, 0x5a, 0x4e, 0xa0, 0x4c, 0xcb, 0x1c, 0x94, 0xa5, 0xec, 0x61, 0xc8, 0x03, 0xea, 0x7c, 0x9c, 0x12, 0x1e, 0x74, 0x33, 0xae, 0xb1, 0x17, 0x02, 0x54, 0xbf, 0x09, 0x90, 0x7d, 0xa5, 0x09, 0x96, 0xd2, 0xa7, 0x2a, 0x59, 0x26, 0x18, 0x37, 0x41, 0x44, 0xbf, 0x25, 0xd7, 0x96, 0x2f, 0x43, 0x19, 0xe3, 0xa9, 0x54, 0x28, 0x1a, 0xc6, 0x27, 0x41, 0x9a, 0xcb, 0x76, 0x20, 0x91, 0x7c, 0x39, 0xa8, 0xc8, 0xa0, 0x05, 0x6c, 0x6a, 0xb1, 0x56, 0x50, 0x32, 0x51, 0xc8, 0x3e, 0x18, 0xcb, 0xa3, 0xe9, 0x3d, 0xb9, 0x7a, 0x79, 0x34, 0x03, 0x27, 0xfb, 0xf0, 0x72, 0x2a, 0xf7, 0x83, 0x9e, 0x57, 0xf7, 0xa3, 0xe9, 0x0f, 0x05, 0x16, 0x7a, 0xad, 0x3f, 0xe9, 0xfe, 0xf6, 0xf9, 0xe8, 0x8b, 0xd7, 0x3d, 0xc4, 0x3b, 0xc8, 0xc7, 0xfa, 0xb6, 0xa1, 0xe9, 0x2e, 0x8c, 0x15, 0xd7, 0xa1, 0xc9, 0x8f, 0xcd, 0xa8, 0x6f, 0xed, 0x1e, 0x40, 0x4b, 0x31, 0x72, 0x6d, 0x63, 0x93, 0x29, 0x18, 0xdc, 0x70, 0xb2, 0xa5, 0x88, 0x56, 0x68, 0xe0, 0xd2, 0x7c, 0x83, 0xa5, 0xd3, 0x87, 0xc6, 0xbd, 0xe0, 0xf4, 0x07, 0xb2, 0x83, 0x18, 0xa7, 0xd7, 0x02, 0x97, 0xe3, 0xc6, 0xa2, 0xca, 0x0b, 0x54, 0x20, 0x85, 0x73, 0xe6, 0xc5, 0x73, 0x13, 0x27, 0xa0, 0xa2, 0x02, 0x54, 0x0a, 0xa8, 0x2d, 0x6a, 0xa0, 0x64, 0xa0, 0xa0, 0xd6, 0x2b, 0xab, 0x70, 0x23, 0x48, 0x35, 0x39, 0x4b, 0xb6, 0xff, 0xdc, 0x77, 0x63, 0xdc, 0x6a, 0x5e, 0xa7, 0x03, 0x44, 0x6f, 0x77, 0x47, 0x7b, 0x73, 0x63, 0xa6, 0x2e, 0x19, 0x0f, 0xf0, 0x6e, 0xa7, 0xce, 0xa2, 0xb3, 0x98, 0x8c, 0x50, 0xfc, 0xd4, 0x02, 0x6d, 0x95, 0xf8, 0x59, 0x8a, 0x3c, 0xaa, 0x42, 0x33, 0xb6, 0x58, 0x67, 0x9f, 0xab, 0x79, 0x84, 0xf9, 0x08, 0xfd, 0xb5, 0xee, 0xa9, 0x4e, 0xfe, 0xca, 0x6b, 0xaf, 0xbe, 0xf2, 0xca, 0x4f, 0x7e, 0xec, 0x69, 0x5c, 0x96, 0xbf, 0xe0, 0xa0, 0x6e, 0x66, 0x0f, 0x16, 0xca, 0xcf, 0x7b, 0xae, 0xb1, 0xf0, 0xd2, 0xd5, 0x53, 0xf7, 0x5f, 0xd0, 0xd9, 0xb9, 0xf7, 0x81, 0x4d, 0x57, 0x3c, 0x97, 0xcf, 0x3e, 0x7d, 0xe5, 0xd2, 0x63, 0x5b, 0x5a, 0x8c, 0x4e, 0x72, 0x1b, 0x93, 0x84, 0x22, 0x7c, 0xcb, 0x58, 0xb1, 0x96, 0xb3, 0x68, 0x0f, 0xd7, 0xaf, 0x3b, 0xbc, 0xe2, 0xd1, 0x17, 0x5f, 0x78, 0xec, 0xd1, 0x57, 0x80, 0x55, 0x94, 0xdb, 0xc1, 0xbf, 0xa1, 0x18, 0xc5, 0x1d, 0x7b, 0xce, 0xb9, 0x40, 0x3c, 0x0a, 0xd7, 0x0e, 0xec, 0xd9, 0xba, 0x71, 0x6f, 0xc7, 0xc5, 0x25, 0x91, 0x5d, 0x8c, 0x60, 0x25, 0x89, 0x65, 0x90, 0x1e, 0x9d, 0x03, 0xe9, 0x35, 0x07, 0x4f, 0xe8, 0x92, 0xa2, 0xb2, 0x3b, 0xed, 0x52, 0xd1, 0x62, 0x06, 0xaf, 0x12, 0x63, 0x11, 0x88, 0x88, 0x27, 0x74, 0xc9, 0x8a, 0x83, 0xc5, 0xd6, 0x33, 0x7e, 0x15, 0x85, 0x54, 0x8c, 0xa8, 0x31, 0x62, 0x5a, 0xd8, 0xd4, 0x10, 0x09, 0xa1, 0x18, 0x2d, 0x59, 0x95, 0xb8, 0x15, 0x11, 0x13, 0x62, 0x80, 0x98, 0xae, 0x2a, 0x9f, 0x93, 0x37, 0x83, 0x02, 0xf8, 0xf2, 0x05, 0x38, 0x5d, 0xa5, 0xda, 0xc3, 0x58, 0x21, 0x8a, 0x84, 0xf3, 0x19, 0xf0, 0x76, 0x72, 0xa4, 0xc5, 0x1f, 0x6a, 0x5f, 0x56, 0x1b, 0x1b, 0x2a, 0xf0, 0xfe, 0xe6, 0x11, 0x70, 0xbe, 0x8d, 0xe3, 0x0b, 0x43, 0xb1, 0xda, 0x65, 0xed, 0xa1, 0xdc, 0xc6, 0x9b, 0xd7, 0xac, 0x39, 0x3e, 0x9d, 0x8b, 0x74, 0x8c, 0xa6, 0xe2, 0x0b, 0x1a, 0xfd, 0x26, 0xe7, 0xb5, 0x66, 0x56, 0xa5, 0xd2, 0xfa, 0x1a, 0x06, 0x62, 0xa1, 0xf6, 0x94, 0x53, 0xab, 0x54, 0x19, 0x7d, 0x87, 0x4d, 0xb1, 0xae, 0x54, 0x4d, 0x7b, 0x26, 0x66, 0xe2, 0x62, 0x99, 0x62, 0x4d, 0xaa, 0x2b, 0x66, 0xa2, 0xce, 0xe5, 0x87, 0xfd, 0xf5, 0x23, 0xed, 0xf5, 0x66, 0x6b, 0xc3, 0xd0, 0xfa, 0x62, 0xcf, 0xae, 0x45, 0x89, 0xc4, 0xa2, 0x5d, 0x3d, 0x6d, 0xab, 0xfb, 0xf2, 0x16, 0x73, 0xb6, 0x73, 0x59, 0x2e, 0xd4, 0x19, 0xf5, 0x99, 0x8d, 0x09, 0x57, 0xb8, 0x31, 0x15, 0x36, 0xb2, 0x91, 0x74, 0x73, 0xd8, 0x9a, 0x64, 0xad, 0x3e, 0x44, 0x1f, 0x26, 0xc1, 0x27, 0x74, 0x0e, 0xee, 0x45, 0x16, 0xe5, 0x71, 0xd0, 0x14, 0x72, 0xc2, 0xed, 0x29, 0xa7, 0x32, 0x97, 0xb8, 0x5a, 0xd0, 0xe8, 0xc7, 0x98, 0x30, 0xe2, 0x9e, 0x28, 0x97, 0x5a, 0xa6, 0xce, 0x39, 0x7a, 0xe3, 0x4d, 0x47, 0xbf, 0xd7, 0x73, 0xe0, 0xc9, 0x5d, 0x08, 0x44, 0x05, 0x7c, 0xf2, 0xe8, 0xcb, 0x2f, 0x3f, 0xba, 0xf7, 0xa5, 0x6b, 0x07, 0x07, 0xaf, 0x7d, 0x69, 0x2f, 0x7a, 0xf7, 0xe9, 0x0f, 0xe8, 0x3c, 0xf8, 0x04, 0xbe, 0x41, 0xe2, 0x66, 0x46, 0xd4, 0x00, 0x09, 0x76, 0x97, 0x28, 0x21, 0x66, 0x65, 0x73, 0x7f, 0xc4, 0x59, 0x70, 0x1a, 0xb1, 0x61, 0xbf, 0x29, 0x88, 0x1b, 0x2e, 0x4d, 0x6e, 0x46, 0x44, 0x69, 0xa4, 0xf3, 0x25, 0xc0, 0x96, 0xef, 0x1d, 0xbb, 0xe9, 0x86, 0xa3, 0xe0, 0x93, 0xbd, 0x2f, 0x5d, 0x33, 0x30, 0x70, 0xcd, 0x4b, 0x7b, 0x1f, 0x7d, 0xe9, 0xa5, 0x47, 0x89, 0x72, 0xee, 0xf4, 0x6a, 0x6c, 0x97, 0xb0, 0x12, 0xf5, 0xc5, 0x14, 0xb2, 0x48, 0x40, 0xdd, 0x04, 0x67, 0x4d, 0x93, 0x50, 0xed, 0x97, 0x72, 0x7b, 0xa0, 0xee, 0xaf, 0x52, 0x02, 0x6c, 0x98, 0x50, 0x5a, 0x55, 0x56, 0xa8, 0x8c, 0x28, 0x80, 0xa2, 0x0c, 0x14, 0x8b, 0xd2, 0xa6, 0xb9, 0xb2, 0x43, 0x18, 0x25, 0x6a, 0xfc, 0x64, 0xcb, 0xc6, 0x8d, 0x5b, 0x8e, 0x1e, 0x9d, 0xbe, 0x79, 0x7d, 0xaa, 0x6e, 0xf2, 0xf8, 0xe4, 0xd1, 0x67, 0xc0, 0xdf, 0xa7, 0x46, 0x17, 0xaf, 0x13, 0x6e, 0x01, 0x42, 0x7a, 0x6c, 0x6f, 0xdf, 0xe0, 0x25, 0x2b, 0xea, 0x84, 0x7f, 0x81, 0xad, 0x92, 0x90, 0x2b, 0x10, 0x54, 0xb3, 0x94, 0x17, 0xdd, 0x54, 0x2c, 0xd8, 0xb1, 0xc5, 0x14, 0x83, 0xda, 0x90, 0x80, 0x9a, 0xc4, 0xa1, 0x91, 0x72, 0x50, 0x95, 0xbc, 0xad, 0x86, 0x6c, 0x45, 0xcd, 0xab, 0x7d, 0x16, 0x93, 0x01, 0xca, 0xf7, 0xf0, 0x31, 0x25, 0xaf, 0xc0, 0xd6, 0xd2, 0x12, 0x33, 0x28, 0x05, 0xcc, 0x51, 0xb2, 0x38, 0xe0, 0x44, 0xe7, 0x02, 0x5c, 0x06, 0xf2, 0xc0, 0xc5, 0x62, 0x72, 0x34, 0xc8, 0x94, 0x33, 0xa2, 0x4f, 0x8b, 0x59, 0xd2, 0xc7, 0x37, 0x93, 0x66, 0x30, 0x22, 0x66, 0x47, 0x83, 0x5f, 0x7e, 0xa2, 0x00, 0xd7, 0x19, 0x8c, 0xc2, 0x33, 0x61, 0x70, 0x41, 0xd8, 0xcc, 0xdf, 0xbe, 0x09, 0xa5, 0x49, 0x13, 0x67, 0xf6, 0xd3, 0x8c, 0xed, 0x37, 0x50, 0xd2, 0x22, 0x00, 0x4d, 0x4c, 0xca, 0xc5, 0x28, 0x3e, 0x84, 0x41, 0x53, 0x4a, 0xe0, 0x96, 0xfa, 0xc9, 0xfb, 0x3c, 0x0e, 0x9b, 0xae, 0xd2, 0xcf, 0x10, 0x2b, 0xf1, 0x0e, 0xd8, 0x4f, 0x5e, 0xa2, 0xad, 0xe1, 0x08, 0x85, 0x52, 0x7b, 0x3c, 0xc8, 0x8f, 0x41, 0x1e, 0xb8, 0x30, 0x3f, 0x92, 0xb5, 0x6f, 0xda, 0x0c, 0xf2, 0xc2, 0xf7, 0x36, 0x1f, 0x5f, 0x9b, 0x40, 0xe9, 0xe5, 0xa7, 0x1f, 0xa8, 0x33, 0xff, 0x4c, 0xed, 0x76, 0x2f, 0xbe, 0x19, 0x8c, 0x88, 0x89, 0xe6, 0xd4, 0xd4, 0x27, 0x0a, 0x94, 0x5f, 0xbe, 0xf9, 0x36, 0xde, 0x8c, 0x7a, 0x2a, 0x3c, 0x63, 0xd4, 0x83, 0x23, 0x68, 0x9f, 0x42, 0x9d, 0x89, 0xca, 0xe3, 0xda, 0xe3, 0x28, 0xb3, 0x0d, 0xad, 0xe6, 0x78, 0x09, 0x69, 0x69, 0x6e, 0xe5, 0x71, 0x3e, 0xc7, 0x53, 0x79, 0xe1, 0x07, 0xcf, 0x08, 0xdf, 0xa7, 0x78, 0x6a, 0xe2, 0x93, 0x7b, 0xa9, 0x89, 0x3b, 0x44, 0x5e, 0xb8, 0xef, 0xf4, 0x9f, 0xa9, 0x57, 0xe1, 0x3b, 0xec, 0x84, 0x54, 0x5d, 0xd2, 0x48, 0xe1, 0x84, 0x1c, 0x1a, 0x54, 0x81, 0x95, 0x8b, 0x70, 0xd2, 0x73, 0xaf, 0xe0, 0x42, 0xe4, 0xf3, 0xdc, 0x3e, 0x3e, 0x5e, 0x84, 0xda, 0xbc, 0x89, 0xd3, 0x69, 0xe4, 0x32, 0xc2, 0x0e, 0xec, 0x4c, 0x89, 0x46, 0x20, 0x0b, 0x9c, 0xb4, 0x69, 0xd1, 0x9f, 0x1e, 0x92, 0xf4, 0x15, 0x96, 0xb7, 0x78, 0x9f, 0x69, 0xda, 0x7e, 0x72, 0xc3, 0x86, 0x93, 0xe7, 0x34, 0x7d, 0xdb, 0xd7, 0x32, 0x76, 0x7b, 0xb0, 0x73, 0x4d, 0x13, 0xb9, 0x6e, 0xe6, 0xce, 0x89, 0xdb, 0x77, 0xb6, 0xb7, 0xef, 0xbc, 0x7d, 0x02, 0xfd, 0xdd, 0xb8, 0xa6, 0x0b, 0xe5, 0x2b, 0x6d, 0x24, 0xfa, 0xe9, 0xed, 0xd4, 0x5d, 0xb0, 0xaf, 0xd1, 0x62, 0x88, 0xc1, 0x19, 0x30, 0x52, 0x82, 0xc4, 0x7a, 0x48, 0xce, 0x00, 0xb9, 0x5c, 0x12, 0x00, 0x49, 0x30, 0x62, 0xe6, 0x93, 0x78, 0xf0, 0x1c, 0x8f, 0x44, 0x4d, 0x89, 0x14, 0xc1, 0x49, 0xc0, 0x5b, 0x38, 0xc2, 0x6f, 0xa4, 0x3e, 0x8a, 0x6c, 0x0a, 0x34, 0xc5, 0xac, 0xb3, 0xc1, 0x2c, 0xa9, 0xbb, 0x2e, 0x14, 0x51, 0xa4, 0x56, 0x95, 0xf1, 0xb5, 0xae, 0x68, 0x20, 0x30, 0x5f, 0x1e, 0x21, 0xfa, 0x99, 0x5f, 0xe0, 0xb6, 0x93, 0x50, 0xfe, 0x1d, 0x2c, 0xf6, 0xc1, 0xf6, 0x69, 0xb1, 0x7d, 0x06, 0xb6, 0xaf, 0x80, 0x5b, 0x83, 0x91, 0xd1, 0xcc, 0x24, 0x41, 0x42, 0xd5, 0x75, 0xb9, 0x12, 0xc8, 0x08, 0x94, 0x02, 0x3f, 0x59, 0xe9, 0x51, 0xaa, 0xd6, 0xe9, 0x68, 0x2c, 0xd4, 0x76, 0xa4, 0x3a, 0x42, 0x01, 0x47, 0xd2, 0x99, 0x44, 0x3d, 0x54, 0xe1, 0xe5, 0x99, 0xb7, 0x87, 0xa1, 0x39, 0x10, 0x4d, 0xdc, 0x5c, 0xc8, 0xa6, 0xff, 0x3c, 0x63, 0x08, 0x17, 0xf7, 0xc3, 0x21, 0x08, 0x7f, 0xb4, 0xc5, 0x7c, 0x46, 0xa3, 0x2f, 0x66, 0xb3, 0x27, 0x78, 0x8e, 0xe3, 0x13, 0xf6, 0x39, 0xdf, 0xe7, 0x1d, 0xe3, 0x72, 0x78, 0xc1, 0x66, 0x4b, 0xf8, 0x38, 0xce, 0x87, 0x3e, 0x79, 0x4e, 0xd8, 0x6f, 0x42, 0xbf, 0xc4, 0xd0, 0x23, 0x31, 0xf4, 0x8b, 0x09, 0xce, 0xff, 0xf8, 0xe9, 0x05, 0xf4, 0x7a, 0xfa, 0x19, 0x22, 0x86, 0xa8, 0x87, 0x0c, 0xa5, 0x64, 0x0c, 0x4a, 0x11, 0xf0, 0xeb, 0xa5, 0xf8, 0x66, 0x48, 0x4a, 0xe0, 0xa6, 0x5a, 0x4e, 0x88, 0x6a, 0x11, 0x20, 0x46, 0x58, 0x33, 0x1f, 0xe6, 0x53, 0x52, 0xa5, 0x9f, 0x2a, 0x0d, 0x54, 0x1a, 0x68, 0x6d, 0xf5, 0x90, 0x51, 0x16, 0x8a, 0x9c, 0xfa, 0x5e, 0xe7, 0x68, 0x05, 0x34, 0x0f, 0x8d, 0x6b, 0xf7, 0xf9, 0xee, 0x66, 0x09, 0xde, 0x6d, 0x57, 0x67, 0xe3, 0x8c, 0x16, 0x8e, 0x9b, 0xa6, 0x1b, 0x0e, 0x4d, 0x6c, 0x3c, 0xe0, 0xb6, 0xf4, 0x8e, 0x21, 0x00, 0xb9, 0xe5, 0xb5, 0x08, 0x2e, 0x70, 0xa8, 0xd1, 0xff, 0xca, 0x1a, 0x84, 0xbe, 0xf7, 0x2a, 0x1c, 0x20, 0xde, 0xdb, 0x46, 0xc9, 0x0e, 0x6b, 0x44, 0x74, 0x5c, 0x8d, 0xf5, 0x3b, 0x0a, 0xa1, 0x9c, 0x95, 0xd2, 0xcd, 0xe0, 0xbf, 0x46, 0x82, 0xe5, 0xb1, 0x7a, 0xa2, 0x04, 0x01, 0x31, 0xe5, 0x4c, 0xc4, 0xae, 0xe5, 0x20, 0x4f, 0xa7, 0xde, 0x17, 0x36, 0x3e, 0x28, 0x4c, 0x9f, 0xb0, 0x79, 0x15, 0xef, 0x69, 0x2c, 0x2a, 0x85, 0x49, 0xf3, 0x07, 0xb9, 0xcf, 0x8a, 0x34, 0x4a, 0xea, 0x23, 0x1b, 0x37, 0x73, 0xab, 0x67, 0x30, 0x12, 0x19, 0xf4, 0x90, 0x9b, 0x59, 0x94, 0x5f, 0xe8, 0x85, 0xd4, 0xeb, 0xdf, 0x31, 0x9e, 0x22, 0xd4, 0xcd, 0xfc, 0x26, 0x19, 0x46, 0x0d, 0x46, 0x65, 0x84, 0x67, 0x83, 0x89, 0xc1, 0xcb, 0xcd, 0xbc, 0x0d, 0x23, 0xcb, 0xc2, 0x16, 0x4b, 0xb6, 0xde, 0x4a, 0xd3, 0x79, 0xd1, 0xc7, 0x59, 0xfd, 0x2d, 0xc3, 0xfc, 0xbb, 0xf0, 0xea, 0x83, 0x6f, 0x31, 0x0a, 0x9a, 0x56, 0x30, 0xbf, 0x78, 0x50, 0x78, 0xf5, 0x0e, 0x3b, 0xcf, 0x3c, 0xc2, 0x68, 0x18, 0x95, 0xe1, 0xdf, 0x18, 0xbf, 0xad, 0x00, 0xbb, 0xf7, 0xaf, 0x0a, 0xad, 0x4c, 0xa9, 0xfe, 0x2a, 0xfc, 0x86, 0x7a, 0x47, 0x3e, 0xae, 0xf3, 0x1a, 0x8d, 0x5e, 0xdd, 0xcc, 0x42, 0xea, 0x23, 0xab, 0x69, 0xe6, 0x1e, 0x6b, 0xce, 0xdd, 0x94, 0x22, 0x57, 0xb1, 0x0e, 0xd8, 0xe9, 0x7b, 0x2c, 0x19, 0x57, 0xa1, 0x16, 0x7d, 0x81, 0x7d, 0x2e, 0xc0, 0xf9, 0xe9, 0x87, 0x7d, 0xd6, 0x4a, 0x56, 0x9e, 0x12, 0xf9, 0x80, 0x73, 0x37, 0x2e, 0xd1, 0x10, 0x13, 0xcb, 0x89, 0xaa, 0x13, 0x76, 0xe5, 0x48, 0xc9, 0x42, 0x54, 0x3f, 0x37, 0xf3, 0x11, 0xa9, 0xff, 0xe4, 0x63, 0x23, 0xa9, 0x10, 0xe4, 0xe4, 0x7b, 0xd4, 0x63, 0x5e, 0xd7, 0x4c, 0xe0, 0xb6, 0x21, 0xa7, 0x83, 0x7c, 0x0b, 0xd9, 0x31, 0xb2, 0xa7, 0xff, 0x42, 0x05, 0xe1, 0x7b, 0xd3, 0xc4, 0xe6, 0x22, 0x4a, 0xe1, 0xa2, 0x63, 0x0c, 0x7c, 0xb9, 0x11, 0x7b, 0x32, 0xcb, 0xc0, 0xd1, 0x14, 0xdc, 0x2e, 0x00, 0xa9, 0xca, 0xf4, 0xb8, 0x84, 0xd7, 0x54, 0xa5, 0x64, 0x7f, 0xea, 0x0d, 0x98, 0x19, 0x22, 0xa8, 0xa3, 0x34, 0x91, 0x36, 0x47, 0x93, 0xb5, 0x18, 0xea, 0x08, 0x4b, 0xe7, 0x73, 0xcc, 0x1b, 0x96, 0x2a, 0x25, 0x08, 0x87, 0xe8, 0xb5, 0xc3, 0xde, 0x53, 0x72, 0x95, 0x51, 0x58, 0x7a, 0xc1, 0x50, 0xa0, 0x7b, 0xe7, 0xf1, 0x85, 0x1b, 0x9f, 0xe8, 0xec, 0x57, 0xb1, 0x8c, 0x8a, 0x65, 0xd5, 0xfe, 0x74, 0x7b, 0x4d, 0x7e, 0x51, 0xde, 0x69, 0xcb, 0x8e, 0xb6, 0x1e, 0xd2, 0xe8, 0x7f, 0x23, 0xac, 0xa0, 0x3e, 0xea, 0xd3, 0x07, 0x8c, 0x7f, 0x92, 0xa5, 0x87, 0x36, 0x34, 0x8e, 0x5d, 0xb5, 0x3a, 0xbd, 0x78, 0xe9, 0x32, 0x35, 0x25, 0xd7, 0xa9, 0xdd, 0x76, 0xd6, 0x97, 0xeb, 0x0d, 0xfb, 0x1b, 0x63, 0xd6, 0x25, 0x3a, 0xaf, 0xee, 0xed, 0xdb, 0xd0, 0x5e, 0xeb, 0x3a, 0xfd, 0x1e, 0xe5, 0x85, 0x32, 0x43, 0x1b, 0xd1, 0x59, 0x6c, 0xf7, 0xc0, 0x41, 0x27, 0xe2, 0x3a, 0x2d, 0xcd, 0x20, 0xb8, 0xd0, 0xf9, 0xe1, 0x0e, 0xce, 0x2d, 0xfb, 0x4d, 0xe1, 0x80, 0x9b, 0x9b, 0x0a, 0x39, 0xbf, 0xcf, 0xe5, 0xb0, 0x9a, 0xfd, 0x32, 0x11, 0x51, 0xba, 0x2c, 0x37, 0xe5, 0xca, 0x86, 0x63, 0x6c, 0x14, 0x2e, 0x8b, 0x9a, 0xf2, 0xaa, 0xfc, 0x63, 0x1c, 0xa6, 0xe3, 0x6d, 0xf9, 0xc6, 0xf9, 0xe3, 0x87, 0xd7, 0xd6, 0xe7, 0x47, 0xb7, 0x64, 0x53, 0x63, 0x1d, 0xe1, 0x8d, 0xe3, 0xf5, 0xfd, 0xf9, 0x08, 0x6b, 0xd3, 0x25, 0x9a, 0xee, 0xdf, 0x36, 0x7a, 0xd5, 0x44, 0xba, 0x6e, 0xe1, 0xfa, 0x74, 0x66, 0xcd, 0x40, 0xc2, 0x56, 0x37, 0x9c, 0x6b, 0x1a, 0x29, 0xd4, 0x98, 0x58, 0xab, 0xf2, 0x9c, 0x7c, 0x6f, 0x6c, 0x68, 0x53, 0x5b, 0xd7, 0x58, 0x83, 0x5f, 0xbf, 0x59, 0x69, 0x74, 0xc6, 0xfd, 0x8d, 0x7d, 0x16, 0x87, 0xc5, 0xd6, 0xd7, 0x50, 0x8c, 0xf6, 0x4f, 0x35, 0xb7, 0x8d, 0xd4, 0x7b, 0xb5, 0x9b, 0x55, 0x66, 0x3e, 0x13, 0x75, 0x65, 0x23, 0x36, 0x27, 0xef, 0xd2, 0xeb, 0xa4, 0x9a, 0x84, 0xa7, 0xff, 0x44, 0x07, 0xe1, 0x00, 0xea, 0x88, 0xda, 0x62, 0x3c, 0x61, 0x83, 0xeb, 0xab, 0x94, 0xc0, 0x51, 0xe1, 0xc8, 0x30, 0xb2, 0xb2, 0x08, 0xeb, 0xb3, 0x03, 0x6f, 0xfb, 0x3a, 0x22, 0x9d, 0x0c, 0x05, 0x43, 0x68, 0xa5, 0x42, 0xb3, 0xe5, 0xe4, 0x56, 0x5c, 0xff, 0xab, 0x50, 0xc9, 0x94, 0x92, 0x64, 0xc7, 0x0c, 0x1d, 0x44, 0x70, 0xd0, 0xeb, 0x7e, 0xb6, 0x69, 0xfa, 0x67, 0xeb, 0xc2, 0x51, 0x96, 0x0c, 0xfa, 0x17, 0x8d, 0xae, 0x88, 0x67, 0x57, 0xb4, 0x07, 0xea, 0x37, 0x1c, 0x5d, 0xb5, 0xea, 0xd8, 0xfa, 0xfa, 0x40, 0xfb, 0x8a, 0x6c, 0x7c, 0xc5, 0xe8, 0xa2, 0xc0, 0x55, 0x03, 0x7a, 0x27, 0xa7, 0x19, 0xee, 0xec, 0x1c, 0xd6, 0x70, 0x4e, 0xfd, 0x80, 0xc2, 0x16, 0xf7, 0xd7, 0x9d, 0xbb, 0xf7, 0xd2, 0x8e, 0xae, 0xf3, 0x46, 0xd3, 0xe9, 0xd1, 0xf3, 0xba, 0x3a, 0x0e, 0x5c, 0x78, 0x6e, 0x5d, 0x20, 0x66, 0x53, 0x54, 0xd9, 0xf0, 0x18, 0xa4, 0xfb, 0x11, 0x12, 0x74, 0x5c, 0x85, 0x65, 0x1a, 0x70, 0xfe, 0xa9, 0xe8, 0x28, 0x54, 0x0a, 0xad, 0xcf, 0x92, 0x97, 0x51, 0x1b, 0x3f, 0xf9, 0x17, 0x9c, 0xe0, 0x0b, 0xa0, 0xf0, 0x45, 0x50, 0xef, 0xfd, 0x53, 0x39, 0xc4, 0xef, 0xdd, 0x6d, 0xf7, 0x48, 0x44, 0x44, 0xfd, 0x47, 0x19, 0x6f, 0xbb, 0x1e, 0xdc, 0xfe, 0xa0, 0x8d, 0x23, 0x37, 0x7b, 0x06, 0xa2, 0xd1, 0x01, 0xcf, 0xcc, 0xad, 0xa2, 0xc7, 0x86, 0xa8, 0xc2, 0x22, 0xc4, 0x3c, 0x1d, 0xed, 0x95, 0x71, 0xa2, 0xca, 0xb2, 0x53, 0xe2, 0xe9, 0x25, 0xa7, 0x26, 0xf5, 0xf6, 0xb3, 0x42, 0xe0, 0x59, 0x6a, 0x79, 0xa9, 0x93, 0x80, 0x58, 0x44, 0x10, 0xf4, 0x66, 0xf8, 0x67, 0x88, 0x88, 0x17, 0xa3, 0x32, 0x40, 0x23, 0xe1, 0x15, 0x4a, 0x2d, 0x65, 0xd8, 0x56, 0xc9, 0xed, 0x04, 0xaf, 0x07, 0x59, 0xd6, 0x1a, 0x62, 0x59, 0xb3, 0x08, 0x7b, 0x8a, 0x77, 0x54, 0x20, 0x07, 0xd7, 0x01, 0x83, 0x28, 0xb0, 0x38, 0x49, 0x2d, 0x83, 0x72, 0xe9, 0x58, 0xa8, 0xc9, 0xd0, 0x9b, 0x0b, 0xe7, 0xdc, 0xb5, 0xf5, 0xd9, 0xad, 0x77, 0x9e, 0x53, 0x38, 0xe0, 0xf6, 0xdb, 0x1b, 0x26, 0xba, 0x9e, 0xed, 0x5a, 0xdd, 0x68, 0xe7, 0x3d, 0xd4, 0x47, 0xc2, 0xf1, 0x95, 0x37, 0x6d, 0x69, 0x63, 0xfe, 0xf4, 0x27, 0x59, 0xdb, 0x96, 0x9b, 0xc6, 0xc1, 0xf6, 0x90, 0x4b, 0xf8, 0x69, 0xf3, 0x44, 0x31, 0x44, 0x93, 0xe7, 0xcc, 0xdc, 0x4c, 0x87, 0x8a, 0x13, 0xcd, 0x20, 0xe1, 0x0a, 0xe1, 0xb1, 0x39, 0x20, 0x5d, 0x78, 0x11, 0xf6, 0xcd, 0x4a, 0xf4, 0x88, 0x56, 0x34, 0x0b, 0x32, 0xee, 0x8e, 0x97, 0x10, 0xbd, 0xc4, 0xec, 0x74, 0x11, 0x66, 0xb6, 0xea, 0x82, 0x04, 0x73, 0x56, 0x96, 0x2e, 0xbe, 0x61, 0x09, 0x84, 0x59, 0x83, 0x98, 0x43, 0xcd, 0xf2, 0x32, 0x12, 0xc3, 0xb5, 0xe2, 0x6d, 0x44, 0x19, 0x0b, 0x3c, 0xf5, 0xe2, 0x73, 0xc2, 0xef, 0xd4, 0xc1, 0xfa, 0xe2, 0xf0, 0x58, 0x3a, 0x53, 0x9b, 0xb7, 0x65, 0xd2, 0xc9, 0xa0, 0x43, 0x46, 0x5e, 0x45, 0x6d, 0x7c, 0x5e, 0x78, 0xe7, 0xe5, 0x8b, 0xbb, 0x6d, 0x46, 0x76, 0xaf, 0xa1, 0x69, 0xd7, 0x4b, 0xc0, 0xfb, 0xef, 0x58, 0x26, 0xd6, 0x53, 0xc7, 0xc9, 0xff, 0x41, 0x2e, 0x48, 0xa8, 0x77, 0x16, 0x8a, 0x59, 0xba, 0x24, 0xeb, 0xd1, 0xbb, 0xa1, 0x60, 0x43, 0x82, 0x3d, 0x0c, 0xda, 0xd5, 0xc4, 0x28, 0xf6, 0xde, 0xad, 0x44, 0x1a, 0xf5, 0xc2, 0x79, 0x7c, 0x76, 0x11, 0xbc, 0xda, 0x90, 0xd4, 0xcd, 0xf2, 0xd9, 0x81, 0x5f, 0x8f, 0xc8, 0xf5, 0x0a, 0xb9, 0x5e, 0x3e, 0x02, 0x7e, 0x76, 0x9e, 0xc9, 0x26, 0xab, 0x13, 0xfe, 0xb3, 0x8e, 0x76, 0x18, 0x2f, 0x00, 0x9e, 0xdb, 0xe9, 0x07, 0xdf, 0x30, 0xa6, 0xdd, 0xee, 0xb4, 0xf1, 0x0d, 0xe1, 0x1d, 0x93, 0xee, 0xd0, 0x21, 0xad, 0x99, 0x7c, 0x53, 0x94, 0xd1, 0x09, 0xea, 0x38, 0x75, 0x0b, 0xaa, 0x4d, 0x45, 0xd8, 0x88, 0x2b, 0x9f, 0x14, 0x5d, 0x76, 0xe2, 0x64, 0x85, 0x09, 0x39, 0xd2, 0x69, 0xe5, 0x8a, 0xf5, 0x67, 0xb8, 0xf0, 0x90, 0xcb, 0x4e, 0xdc, 0x28, 0x34, 0xd6, 0x7d, 0xcf, 0x7a, 0x23, 0x94, 0xf7, 0x69, 0x6a, 0xb4, 0xe4, 0xe6, 0xa3, 0xe8, 0x91, 0xf1, 0xa2, 0xfb, 0xb3, 0x1d, 0x7d, 0x70, 0x60, 0xd6, 0x8c, 0x39, 0x60, 0x9d, 0xe5, 0xe8, 0x03, 0x6f, 0xd6, 0x28, 0x39, 0xd5, 0x2e, 0x10, 0xb6, 0xfc, 0xf5, 0x1d, 0x2b, 0xb0, 0x09, 0x5b, 0xcc, 0xa7, 0x89, 0xa2, 0xc2, 0x65, 0xde, 0x05, 0x76, 0x90, 0x11, 0xfa, 0x41, 0x30, 0xe6, 0xae, 0xb7, 0xbc, 0x0f, 0x16, 0x3c, 0x7d, 0xfe, 0x33, 0x17, 0x3f, 0x74, 0x48, 0xcf, 0x91, 0x6f, 0x5e, 0x84, 0xe5, 0xa8, 0x29, 0x38, 0xdf, 0xd7, 0xc1, 0xf1, 0xa9, 0xa0, 0x36, 0x1a, 0x42, 0xb4, 0xd2, 0x85, 0xa6, 0x79, 0x10, 0xca, 0x4f, 0x72, 0x80, 0xc1, 0x3f, 0xaa, 0xf2, 0x99, 0x25, 0x2c, 0x56, 0x66, 0xd8, 0x6c, 0x52, 0x43, 0xf6, 0x0d, 0xd9, 0x6a, 0xc8, 0x1c, 0x52, 0x73, 0x6a, 0xa3, 0x4e, 0x23, 0x22, 0x25, 0x29, 0xab, 0xd1, 0x9c, 0xcc, 0xac, 0x79, 0x7e, 0x9f, 0x10, 0x0b, 0xf6, 0x05, 0xd3, 0x6e, 0x75, 0x43, 0xcf, 0xe5, 0x53, 0xc5, 0x9e, 0x96, 0xe2, 0xc0, 0xe6, 0x4e, 0x6f, 0x73, 0xd7, 0x8d, 0xc1, 0x9e, 0xa9, 0x0e, 0xe1, 0xd7, 0xb4, 0xca, 0xe8, 0x0a, 0x99, 0x12, 0x75, 0xaf, 0x80, 0xf7, 0x5e, 0x8a, 0xe5, 0x0b, 0xf1, 0x0b, 0x5c, 0x99, 0xfe, 0x64, 0x66, 0xa9, 0xcd, 0x7f, 0x69, 0x6a, 0x51, 0x33, 0x8f, 0x17, 0x85, 0x22, 0x7c, 0xd4, 0xcd, 0xe4, 0x03, 0xb2, 0x5d, 0x84, 0x01, 0x4a, 0x7f, 0x29, 0x62, 0x59, 0x71, 0xb1, 0x1f, 0xdb, 0x5e, 0x35, 0x40, 0xa6, 0x86, 0x14, 0x5d, 0x06, 0x85, 0xbe, 0x33, 0x3c, 0x5d, 0xd8, 0x63, 0x8d, 0x5c, 0x5c, 0xd3, 0xf2, 0x61, 0xa7, 0x83, 0x65, 0x01, 0x81, 0x21, 0x28, 0xa3, 0x8e, 0x94, 0x33, 0xc5, 0xda, 0x59, 0x9b, 0x58, 0xcf, 0x54, 0x74, 0x59, 0x68, 0xcb, 0xa3, 0x40, 0x6c, 0x0a, 0xbb, 0xb7, 0xce, 0x36, 0x14, 0xec, 0xde, 0x02, 0xb7, 0x07, 0x53, 0x6e, 0xb5, 0xd6, 0x93, 0xf2, 0x5f, 0xb1, 0x73, 0xe7, 0x54, 0x47, 0x77, 0x73, 0xc7, 0xc0, 0x96, 0x4e, 0x6f, 0x4b, 0xe7, 0x0d, 0xc1, 0xee, 0x8d, 0x45, 0xe1, 0x57, 0xeb, 0xd7, 0x53, 0xa7, 0xcd, 0xfe, 0xa8, 0x09, 0x15, 0xee, 0xed, 0x5d, 0x30, 0x48, 0x2a, 0xde, 0x28, 0xe4, 0xf3, 0x85, 0xad, 0x78, 0x60, 0xcb, 0xac, 0xe2, 0xc0, 0x9e, 0x5c, 0x00, 0xfe, 0x4b, 0xdc, 0x6f, 0x68, 0x3d, 0xfe, 0x0c, 0xd7, 0x03, 0xf9, 0x7a, 0xdc, 0xe8, 0xdc, 0x11, 0x22, 0x0c, 0x78, 0xa9, 0xa6, 0xd5, 0x67, 0x79, 0x7a, 0xc8, 0x3f, 0xe7, 0x27, 0x0f, 0x2f, 0x13, 0x7e, 0x03, 0xec, 0xcb, 0x0e, 0x4f, 0xe6, 0x6f, 0xf7, 0x36, 0x2e, 0xc9, 0x5c, 0x71, 0x45, 0x66, 0x49, 0xa3, 0x97, 0x01, 0x8b, 0xae, 0x5e, 0x5f, 0x20, 0xdf, 0x2c, 0xac, 0xbf, 0x66, 0x51, 0xe3, 0x44, 0xbb, 0x5f, 0xb0, 0xf2, 0xed, 0x13, 0x22, 0x8d, 0x43, 0x6d, 0xe6, 0x60, 0x9b, 0x6a, 0x42, 0x82, 0x02, 0x75, 0x11, 0xb4, 0x0c, 0x40, 0x49, 0x92, 0x59, 0x5f, 0xf2, 0xaa, 0xa0, 0x03, 0x8f, 0x29, 0x33, 0x8a, 0xf2, 0xe7, 0xcf, 0xbc, 0x0c, 0x75, 0x30, 0x72, 0x54, 0x12, 0x59, 0x48, 0x6a, 0xc1, 0x78, 0x51, 0x37, 0xc7, 0xe3, 0xc2, 0xe1, 0x79, 0x14, 0x3d, 0x2e, 0x1f, 0x7f, 0xf8, 0x21, 0x68, 0x12, 0x52, 0xe0, 0xc7, 0xcc, 0x5f, 0xd1, 0x76, 0x14, 0xb7, 0x22, 0xec, 0x47, 0x2b, 0xec, 0x87, 0x1e, 0xf7, 0x63, 0x49, 0x09, 0x05, 0x63, 0xb6, 0x38, 0xbb, 0xbe, 0x24, 0x0a, 0x4d, 0x23, 0xa2, 0xe4, 0x3b, 0xe3, 0x2a, 0x12, 0x76, 0x47, 0x91, 0x58, 0x32, 0x8e, 0x53, 0x07, 0xc7, 0x8b, 0x4a, 0x31, 0xee, 0x04, 0xcb, 0xbb, 0x50, 0x50, 0x42, 0xcb, 0x05, 0xff, 0xe5, 0x49, 0xbd, 0x50, 0x0b, 0x9a, 0x3e, 0xfc, 0x50, 0xf8, 0x0e, 0xf8, 0x0f, 0xea, 0xf8, 0xc5, 0xcf, 0x9c, 0xff, 0xf4, 0x45, 0xd8, 0x4e, 0x4c, 0x1d, 0x21, 0x5f, 0x94, 0xed, 0x85, 0x1a, 0x45, 0xae, 0x58, 0x6f, 0x84, 0x6f, 0x8c, 0x02, 0x28, 0x31, 0xc0, 0xbd, 0x45, 0x21, 0x18, 0x06, 0xb4, 0x8f, 0xb0, 0x52, 0x4c, 0xa3, 0x83, 0x5b, 0x02, 0x3f, 0x05, 0xc3, 0x5c, 0x34, 0x16, 0x0d, 0x88, 0x4d, 0x64, 0xd8, 0x33, 0x9c, 0x83, 0xf3, 0x24, 0x0e, 0xe6, 0xc0, 0xab, 0x7f, 0x6f, 0xb8, 0x6a, 0x62, 0xd1, 0x35, 0x93, 0x0d, 0xf9, 0xa9, 0xa3, 0x2b, 0x26, 0x0e, 0x26, 0xe3, 0x6a, 0x1f, 0x67, 0xf4, 0x47, 0xd2, 0x9e, 0xcc, 0xd2, 0x66, 0xaf, 0xbf, 0x75, 0x69, 0x7d, 0xac, 0xcd, 0xb5, 0x4c, 0x07, 0x69, 0xaa, 0xf0, 0xc3, 0x9a, 0x50, 0xc3, 0xd4, 0xa1, 0xc5, 0x4b, 0x0e, 0x4f, 0x15, 0x0a, 0xb1, 0x5e, 0x83, 0x42, 0xa3, 0x36, 0x6a, 0xe5, 0x81, 0xe2, 0x44, 0xa1, 0x65, 0x4d, 0xd1, 0xaf, 0xd3, 0x5e, 0x68, 0xd0, 0x48, 0xeb, 0x77, 0x0a, 0xcf, 0x1b, 0xe4, 0xfd, 0x28, 0xab, 0x8d, 0x26, 0x20, 0x51, 0x11, 0x3d, 0x11, 0xeb, 0x71, 0x96, 0x1b, 0x31, 0x4e, 0x22, 0x08, 0xb8, 0x05, 0x67, 0xb8, 0xc1, 0x44, 0xbe, 0x85, 0x90, 0x60, 0xc8, 0x53, 0x27, 0x84, 0x67, 0x4e, 0x9c, 0x00, 0xdd, 0xb2, 0x9d, 0x33, 0x01, 0xb8, 0x3c, 0xdf, 0x21, 0xdf, 0x14, 0xbe, 0x07, 0xf2, 0xd8, 0xfe, 0x42, 0x1d, 0x27, 0xfe, 0x2e, 0x43, 0xe1, 0x3d, 0x36, 0x64, 0xdf, 0x9c, 0x3d, 0xc9, 0xac, 0x81, 0x84, 0xe2, 0x3a, 0x07, 0x5f, 0xf2, 0xf7, 0x13, 0x27, 0x60, 0x47, 0xde, 0x14, 0xd7, 0xd2, 0x4e, 0x1d, 0x01, 0xfb, 0xe1, 0x33, 0x1c, 0x92, 0xf5, 0x45, 0x5f, 0x0e, 0x01, 0x25, 0xc8, 0xdd, 0xd8, 0xb2, 0x8b, 0xfc, 0x34, 0x70, 0x27, 0x73, 0x7c, 0xb0, 0xda, 0x09, 0x33, 0xcb, 0x05, 0xb3, 0xdf, 0xd3, 0xb6, 0xaa, 0xb5, 0xd0, 0xa0, 0x04, 0x06, 0xbd, 0xc2, 0x69, 0x2a, 0xe4, 0x4f, 0xd0, 0xff, 0xd2, 0x36, 0xd5, 0x17, 0xd1, 0xae, 0xd2, 0x18, 0x64, 0xed, 0x0b, 0xc1, 0x7f, 0x96, 0xf6, 0xed, 0x5b, 0xb0, 0x0d, 0x17, 0xca, 0x03, 0x57, 0x63, 0xfd, 0x93, 0x41, 0xf8, 0xf9, 0x50, 0xe1, 0x91, 0xf2, 0xf4, 0x51, 0x78, 0x36, 0x39, 0x41, 0xa3, 0x2c, 0xec, 0x11, 0x96, 0x0d, 0x45, 0x82, 0xd8, 0x0b, 0xc3, 0x21, 0x4b, 0x2b, 0xc6, 0xcc, 0x40, 0x58, 0x6e, 0x56, 0x4e, 0x12, 0x10, 0x44, 0xa7, 0xcc, 0xdf, 0x3d, 0x21, 0xf6, 0x5b, 0x3a, 0xb7, 0xf5, 0x8a, 0x77, 0xcc, 0x0e, 0xd6, 0xad, 0x7d, 0xef, 0x2a, 0x87, 0x43, 0xf9, 0xac, 0xca, 0x69, 0x8d, 0xdd, 0x72, 0x0b, 0xd3, 0x65, 0xfd, 0x8b, 0x51, 0x27, 0xfc, 0xd4, 0xa5, 0x93, 0x5b, 0x39, 0xe1, 0x41, 0xbd, 0x0e, 0xc8, 0x74, 0x6a, 0xe1, 0x15, 0x38, 0x64, 0xb1, 0x2f, 0x66, 0xcc, 0x27, 0x3a, 0x24, 0x20, 0x3e, 0x02, 0x1b, 0x8c, 0x90, 0x44, 0x83, 0x8b, 0x95, 0x88, 0x88, 0x4a, 0xe8, 0x37, 0x0a, 0x61, 0x8f, 0x90, 0xa3, 0x25, 0xc7, 0x0c, 0x49, 0x2c, 0x1c, 0x2f, 0xaa, 0xab, 0x33, 0xfd, 0x80, 0x98, 0xe9, 0x67, 0x16, 0x46, 0x6e, 0xa1, 0x8d, 0x07, 0xc8, 0x37, 0x67, 0xee, 0x2e, 0x9f, 0x53, 0xea, 0x30, 0x6c, 0x83, 0x47, 0xfb, 0xd3, 0xa1, 0x61, 0x28, 0x9c, 0x8c, 0x2f, 0xc1, 0x1b, 0x31, 0xc8, 0x1d, 0x4e, 0x50, 0xcb, 0xd1, 0x67, 0xf5, 0x7a, 0xf3, 0x84, 0x8f, 0xb7, 0xe4, 0xca, 0xb5, 0x33, 0xb2, 0x05, 0x64, 0x9c, 0xf1, 0x47, 0xa8, 0x0a, 0x6a, 0x72, 0xb5, 0x8a, 0x75, 0xf8, 0x2a, 0xea, 0x3f, 0x0c, 0x21, 0x53, 0x90, 0xfd, 0x29, 0x49, 0x1e, 0xdc, 0x6d, 0x32, 0xa9, 0x7e, 0xa0, 0x34, 0x28, 0x14, 0xac, 0xf2, 0x35, 0x25, 0xa4, 0x7a, 0x68, 0x63, 0x7c, 0xdb, 0x1b, 0x16, 0x76, 0xf3, 0x3c, 0x38, 0xc6, 0x05, 0xf5, 0x42, 0x0f, 0xf9, 0xa6, 0x9c, 0x15, 0xf6, 0xb3, 0x51, 0x93, 0x39, 0x6a, 0x04, 0x97, 0xb3, 0xf2, 0x0a, 0x3d, 0x41, 0xfb, 0xd1, 0x84, 0xfc, 0x51, 0x32, 0x2c, 0x97, 0x21, 0x14, 0x67, 0x38, 0xd4, 0xe5, 0x18, 0xcd, 0x99, 0x18, 0x47, 0xab, 0x84, 0x7b, 0x67, 0x22, 0x4c, 0xbc, 0x95, 0xad, 0x02, 0x73, 0xb6, 0xcc, 0xc2, 0x72, 0x26, 0x4f, 0x2d, 0x64, 0xed, 0x6a, 0xe1, 0x26, 0xea, 0xe0, 0x42, 0x93, 0x51, 0x0b, 0x76, 0x92, 0x24, 0xea, 0x84, 0x46, 0x07, 0x5e, 0x0b, 0x7b, 0x85, 0xb5, 0xe4, 0x9b, 0x0a, 0x83, 0x50, 0x6f, 0x0a, 0xb1, 0xe0, 0x24, 0x6e, 0x77, 0x11, 0x6c, 0xf7, 0xa5, 0x52, 0xbb, 0xcc, 0x3c, 0xed, 0x4e, 0xe0, 0x76, 0x47, 0x70, 0xbb, 0x1c, 0x1b, 0x2e, 0xb5, 0x0b, 0x27, 0x05, 0xa7, 0x78, 0x22, 0x23, 0x88, 0x4c, 0x8e, 0x64, 0x7a, 0xf2, 0xa5, 0xab, 0x28, 0xe1, 0x26, 0xb5, 0x8d, 0x5d, 0x78, 0x10, 0x2a, 0xa8, 0xbb, 0x34, 0x46, 0x71, 0xf0, 0x77, 0xf9, 0x82, 0x42, 0x4e, 0xa7, 0x81, 0x27, 0x63, 0x8a, 0x0d, 0x99, 0xc0, 0x0f, 0x0c, 0x0a, 0x4c, 0xb3, 0x21, 0xdd, 0xa0, 0x02, 0x90, 0x6e, 0x20, 0x3f, 0xcb, 0xfc, 0x34, 0x3b, 0xfc, 0x99, 0x2e, 0x2f, 0xb4, 0xef, 0xa9, 0x00, 0xa2, 0xdd, 0x88, 0x6e, 0x97, 0x3e, 0x31, 0xfd, 0x46, 0xd4, 0xfb, 0x82, 0x4d, 0x9b, 0x2e, 0x80, 0x14, 0xfc, 0xda, 0x8d, 0x8d, 0x8d, 0x1b, 0xaf, 0x5d, 0xb4, 0xe8, 0x10, 0xfa, 0x3c, 0x84, 0xe9, 0xb8, 0xbf, 0x7d, 0xa2, 0x71, 0xf7, 0xe5, 0x97, 0xa3, 0x7e, 0x24, 0x21, 0xfd, 0xda, 0x89, 0xcf, 0xdc, 0x30, 0x91, 0x29, 0xa6, 0x7b, 0xbb, 0xd2, 0x2e, 0x15, 0x83, 0xf5, 0x5e, 0x34, 0x0b, 0xe4, 0x6e, 0xa2, 0x0c, 0x69, 0x0a, 0x86, 0xb1, 0xe1, 0x78, 0xd8, 0x34, 0xd4, 0x58, 0x65, 0x38, 0x06, 0xff, 0xa0, 0xe1, 0x58, 0x26, 0xda, 0x8d, 0x41, 0xc9, 0x6e, 0x3c, 0xe2, 0xc9, 0x46, 0x2c, 0xb6, 0x58, 0xc1, 0xeb, 0xaa, 0x0b, 0x9a, 0x2d, 0x91, 0x2c, 0x60, 0x59, 0x8d, 0x25, 0x9c, 0x75, 0xfb, 0x1a, 0x13, 0xf6, 0xc8, 0xc0, 0x39, 0x3d, 0x3d, 0xe7, 0x0c, 0x46, 0x1c, 0xc9, 0x66, 0xde, 0x93, 0x8b, 0x5a, 0xb5, 0xdc, 0x76, 0x56, 0x2b, 0xd7, 0x5a, 0x42, 0xf5, 0xee, 0xba, 0x4e, 0x05, 0xa3, 0x40, 0x10, 0x42, 0x1a, 0x77, 0x92, 0x77, 0xc5, 0x03, 0x6e, 0x9d, 0xd6, 0x1d, 0x4c, 0x38, 0xf9, 0xa4, 0x5b, 0x43, 0x6e, 0xb7, 0x16, 0xac, 0x81, 0x86, 0x64, 0x50, 0xcf, 0xd6, 0x14, 0x7a, 0x6b, 0xeb, 0x97, 0x36, 0xfb, 0x7c, 0xcd, 0x4b, 0xeb, 0x13, 0xdd, 0x99, 0x88, 0xc1, 0x10, 0x4a, 0x37, 0x87, 0x1d, 0xb5, 0x50, 0xfb, 0xb3, 0x05, 0xec, 0x11, 0x9f, 0x43, 0x63, 0x6d, 0xcf, 0xe8, 0xdd, 0x6a, 0xa8, 0x49, 0x97, 0xf8, 0xe8, 0x25, 0x58, 0x8e, 0xb4, 0x22, 0xe4, 0x12, 0xc8, 0x28, 0xe8, 0x3d, 0x32, 0x09, 0xc7, 0x0c, 0x92, 0x05, 0x24, 0xde, 0x2e, 0x67, 0xa4, 0xd2, 0x47, 0x04, 0x35, 0xf2, 0xe9, 0x16, 0x56, 0x73, 0xc5, 0xc2, 0x6a, 0x0e, 0xb0, 0x60, 0x65, 0x7f, 0x67, 0x57, 0xdf, 0xba, 0x75, 0x43, 0xdb, 0xfb, 0xfc, 0x81, 0xfe, 0xed, 0x03, 0xeb, 0x6e, 0x61, 0x98, 0xce, 0x96, 0x7c, 0xc7, 0x87, 0x60, 0x5b, 0xb0, 0x7d, 0x79, 0x26, 0x3f, 0x51, 0x0c, 0x7e, 0x0c, 0x69, 0x82, 0xd8, 0x87, 0x07, 0x71, 0x1f, 0xbc, 0x44, 0x4b, 0xb1, 0xd1, 0x8d, 0x29, 0x14, 0xaa, 0xeb, 0x41, 0xee, 0x29, 0xe5, 0x12, 0xc9, 0xc4, 0x9e, 0xc8, 0x2a, 0x3d, 0x81, 0xfd, 0x30, 0x71, 0x46, 0x56, 0xaf, 0x56, 0xce, 0xd3, 0x0f, 0x80, 0x74, 0xce, 0x12, 0xa5, 0xaa, 0x00, 0x64, 0xf6, 0x0f, 0x76, 0xf5, 0xf4, 0x8d, 0x80, 0x47, 0xaf, 0x09, 0x71, 0xd7, 0x29, 0x2c, 0xa6, 0xcc, 0x2d, 0xbf, 0x1a, 0xde, 0xde, 0xeb, 0x1b, 0x19, 0x72, 0x9b, 0x18, 0x4b, 0x4f, 0x26, 0xd3, 0xf8, 0x3f, 0xc2, 0x7e, 0xcf, 0xcf, 0x21, 0xa9, 0xfa, 0x1d, 0xf9, 0xa6, 0xaf, 0x71, 0x24, 0x35, 0x70, 0xae, 0x83, 0xf5, 0x20, 0xf9, 0x1f, 0xf6, 0x6f, 0xb5, 0x0c, 0x95, 0x81, 0xf3, 0x21, 0xf4, 0x5a, 0x64, 0x55, 0x47, 0x40, 0x35, 0x48, 0x1e, 0x2d, 0x11, 0x90, 0xb2, 0xd1, 0x12, 0x97, 0x13, 0x81, 0x9c, 0x73, 0xf5, 0xa9, 0x13, 0xa7, 0xc8, 0x2e, 0xe6, 0x4f, 0x33, 0x77, 0x92, 0xeb, 0xf6, 0xc3, 0x67, 0x56, 0x62, 0x9e, 0xb9, 0x93, 0x30, 0x13, 0x41, 0x14, 0x8a, 0x09, 0x5f, 0x22, 0x1a, 0xc9, 0xf1, 0x36, 0x43, 0x58, 0x9a, 0x46, 0x56, 0xa3, 0x82, 0x2c, 0xd3, 0x0c, 0xcc, 0x8c, 0x34, 0x92, 0x39, 0x66, 0x47, 0x0b, 0x38, 0x3a, 0xb4, 0xe4, 0x96, 0xf8, 0xc8, 0xee, 0x3e, 0xe4, 0x92, 0xbb, 0x65, 0x29, 0x6d, 0x58, 0xb5, 0x08, 0x3c, 0x21, 0x0c, 0x77, 0xed, 0x5c, 0x9c, 0x4c, 0x2e, 0xde, 0xd9, 0x85, 0xfe, 0x5e, 0xb4, 0x0a, 0xe3, 0x39, 0x51, 0xc7, 0xe9, 0x16, 0xd8, 0x56, 0x04, 0xb5, 0xe5, 0xe3, 0xc8, 0xb2, 0x49, 0x47, 0x82, 0x48, 0xda, 0x02, 0x57, 0x3e, 0x42, 0x84, 0x9b, 0x24, 0x83, 0x0e, 0xa0, 0x2a, 0x06, 0x1d, 0xc9, 0xca, 0x5b, 0x6d, 0xcf, 0x29, 0x61, 0x19, 0x65, 0xe8, 0x96, 0xff, 0x3e, 0x74, 0x0b, 0x2a, 0xba, 0x20, 0xa3, 0x6f, 0xbd, 0xf6, 0x97, 0x97, 0xd6, 0xa8, 0x76, 0x33, 0x72, 0x46, 0xa1, 0xb9, 0x8a, 0xb1, 0x9b, 0x12, 0x26, 0xab, 0xfc, 0xa0, 0x46, 0x41, 0x2b, 0xe8, 0x3d, 0x94, 0x93, 0xc3, 0x34, 0xe0, 0x0d, 0xb5, 0x4d, 0xa7, 0xb3, 0xa9, 0x85, 0x30, 0xf9, 0xa6, 0x5f, 0xd8, 0x6a, 0x8c, 0x5a, 0x93, 0x41, 0x70, 0x8b, 0xd6, 0xcc, 0xe9, 0x85, 0xad, 0xfe, 0xb8, 0x39, 0xc2, 0x82, 0x5b, 0x74, 0x26, 0x54, 0xdb, 0x93, 0x3a, 0x44, 0xd2, 0xcc, 0x3d, 0x90, 0xdd, 0xd6, 0x11, 0xbb, 0x8b, 0xba, 0x30, 0x00, 0x0c, 0xb2, 0x78, 0xd9, 0x70, 0xbe, 0xaf, 0xc8, 0x21, 0x02, 0x67, 0x94, 0x86, 0x9f, 0x1c, 0x2a, 0x19, 0x22, 0xb0, 0x76, 0x85, 0xb5, 0x87, 0xb3, 0x17, 0x92, 0x9f, 0xac, 0xbe, 0x11, 0xb2, 0x10, 0x4b, 0x08, 0xd5, 0x86, 0x0f, 0xe3, 0x28, 0x5b, 0x4e, 0xb2, 0x52, 0xc8, 0xe4, 0xe6, 0xb3, 0x96, 0x86, 0xc7, 0x10, 0x7d, 0xe8, 0x80, 0xff, 0x28, 0xd3, 0x5d, 0x63, 0xf0, 0x7b, 0x02, 0x19, 0x47, 0x2c, 0xec, 0x8e, 0xb8, 0xed, 0x06, 0x9d, 0x59, 0x66, 0xaf, 0xdd, 0x51, 0x4c, 0x0d, 0xd4, 0x3b, 0xdb, 0x4c, 0x26, 0xb9, 0x3a, 0x3a, 0x58, 0xe8, 0x59, 0xe6, 0xe9, 0xbb, 0x60, 0x9c, 0x7e, 0x85, 0xd4, 0x72, 0x76, 0xbd, 0xdb, 0x6e, 0x73, 0x3c, 0x9b, 0x4e, 0x6a, 0x0d, 0x5a, 0x9d, 0x3a, 0x19, 0xab, 0xf1, 0xe6, 0x87, 0x12, 0xa0, 0x4f, 0xc3, 0xf2, 0x81, 0x40, 0x7f, 0xaf, 0xbb, 0x29, 0xe5, 0x25, 0x45, 0x59, 0xb6, 0x0e, 0xee, 0x0d, 0x2b, 0x73, 0x1f, 0x94, 0x65, 0x43, 0xc4, 0x01, 0x51, 0xa2, 0xd3, 0xfb, 0xa1, 0x50, 0xa2, 0x41, 0x81, 0x97, 0x46, 0x54, 0x25, 0x06, 0x41, 0x11, 0xc3, 0x5f, 0x98, 0xaa, 0x5f, 0x24, 0x11, 0xb4, 0xaa, 0x22, 0xfc, 0xb6, 0x4a, 0xc8, 0xfb, 0x34, 0x32, 0x71, 0xfb, 0x71, 0xf2, 0x71, 0x69, 0x0a, 0xa4, 0x40, 0xee, 0xd9, 0x37, 0x41, 0xf9, 0x53, 0xab, 0xd5, 0x86, 0xb4, 0xc1, 0x68, 0x30, 0x18, 0x9c, 0x55, 0xee, 0xdd, 0xf2, 0x29, 0xd5, 0xde, 0xc1, 0xf5, 0xa6, 0x5a, 0x47, 0xa6, 0x3d, 0x97, 0x08, 0xd6, 0x5b, 0x0f, 0x8f, 0x3a, 0xe5, 0x1a, 0xdf, 0xc2, 0x7c, 0xac, 0x37, 0xe3, 0xf2, 0x35, 0x2f, 0xc9, 0xa4, 0xdb, 0x1c, 0x72, 0xb9, 0x93, 0xe1, 0xac, 0xec, 0x82, 0x15, 0x63, 0xbd, 0x6e, 0x27, 0xd8, 0x3a, 0xf3, 0x57, 0x7b, 0xc2, 0x1f, 0x0f, 0x76, 0x4f, 0xb5, 0x37, 0x4d, 0x0d, 0xd4, 0xf0, 0x0e, 0xbb, 0x0d, 0xe9, 0x26, 0xf5, 0xd4, 0x75, 0x54, 0x9a, 0xb9, 0x1f, 0x9e, 0x79, 0x13, 0xb1, 0x04, 0x28, 0xc4, 0x95, 0xe6, 0xda, 0x01, 0xa9, 0xea, 0x03, 0x4a, 0xd2, 0x04, 0xe4, 0xca, 0x08, 0x2f, 0x01, 0x57, 0x4a, 0xbf, 0x2a, 0xaa, 0x7f, 0x95, 0x70, 0x2b, 0xd3, 0x84, 0x92, 0x94, 0x93, 0xa8, 0x64, 0xa3, 0x5c, 0xa3, 0x25, 0x91, 0x2e, 0x36, 0x89, 0xf0, 0x19, 0x54, 0x13, 0x6a, 0xa0, 0x52, 0xe1, 0x08, 0xff, 0x73, 0x25, 0xa0, 0x2c, 0x14, 0xeb, 0x3d, 0x5d, 0xce, 0x65, 0xca, 0x94, 0x9f, 0xa3, 0xb1, 0x9d, 0xe1, 0x2c, 0xcf, 0xcf, 0x7e, 0xb4, 0xaa, 0x40, 0xe4, 0x19, 0x4f, 0x11, 0x55, 0x8d, 0xa2, 0xea, 0x90, 0xf3, 0x3c, 0x3f, 0x3e, 0x0e, 0x25, 0x71, 0x62, 0xc9, 0xe2, 0xa1, 0x81, 0x9e, 0xae, 0x96, 0xa6, 0x42, 0x2e, 0x53, 0x17, 0xaf, 0x09, 0x05, 0x3c, 0x2e, 0x84, 0x93, 0x89, 0xc3, 0x2e, 0x75, 0xf3, 0x84, 0x5d, 0xca, 0x3e, 0x23, 0xea, 0x12, 0x54, 0x82, 0x24, 0xca, 0x2e, 0x5d, 0x71, 0xb3, 0x7e, 0x55, 0x6d, 0x0d, 0xd8, 0xeb, 0x73, 0xf2, 0x2b, 0xf1, 0xa6, 0xf5, 0xa5, 0xda, 0x63, 0x11, 0x77, 0x18, 0x32, 0x01, 0x9d, 0x09, 0xee, 0xd9, 0xa9, 0x96, 0xa6, 0x21, 0x93, 0x7b, 0xac, 0x31, 0xb9, 0xa0, 0xd1, 0xa7, 0xe1, 0x40, 0x44, 0xd1, 0x91, 0xf1, 0xc7, 0x9c, 0x9c, 0x46, 0xad, 0x61, 0x42, 0x96, 0x6c, 0x43, 0x83, 0x23, 0xbf, 0xc6, 0xe7, 0xdb, 0xd1, 0xbe, 0x70, 0x39, 0xde, 0xcc, 0x9b, 0xcc, 0x31, 0xde, 0xdc, 0xda, 0xb0, 0x8c, 0xd4, 0x9b, 0x9d, 0x7a, 0xaf, 0xbb, 0xa9, 0x6b, 0x77, 0xba, 0x4e, 0x67, 0xd4, 0xe9, 0xe1, 0x9e, 0x8e, 0xc6, 0xc2, 0xc1, 0x18, 0x94, 0xb3, 0x1b, 0x7d, 0x96, 0xce, 0x56, 0xad, 0x5e, 0x23, 0x97, 0xa5, 0x1d, 0x7e, 0x4e, 0x51, 0x9b, 0xc9, 0xa4, 0x16, 0xf4, 0xbb, 0x9b, 0xeb, 0x78, 0x52, 0xda, 0xdf, 0x1c, 0x3c, 0xe3, 0x0a, 0x22, 0x4e, 0xb4, 0x16, 0x9b, 0xce, 0x5a, 0x91, 0x77, 0xb2, 0x1c, 0xff, 0x32, 0x8d, 0xe2, 0xa6, 0x62, 0xd1, 0x50, 0xe0, 0x2c, 0xe5, 0x78, 0x43, 0xf3, 0x95, 0xe3, 0x95, 0xe6, 0x22, 0xf2, 0xb0, 0xc6, 0x11, 0x76, 0xd6, 0x37, 0x2a, 0x84, 0xaf, 0x59, 0xf2, 0x8d, 0x4d, 0xce, 0xfc, 0xda, 0xbe, 0x68, 0xa0, 0x73, 0x4d, 0x53, 0xfd, 0xb2, 0x56, 0x3f, 0x1c, 0x6b, 0x83, 0xbc, 0xa7, 0xd1, 0x1d, 0x83, 0x6c, 0xd0, 0xa0, 0xa4, 0x27, 0xcc, 0x11, 0xb7, 0xb1, 0x90, 0x7d, 0xdf, 0x09, 0xbb, 0x1c, 0xec, 0xdd, 0xdc, 0x93, 0x5b, 0xd3, 0x5b, 0xe3, 0x6b, 0x1d, 0x6f, 0xf0, 0x59, 0xfa, 0x3b, 0xb4, 0xac, 0x16, 0xe9, 0x0c, 0x19, 0x28, 0x9f, 0x7f, 0x0f, 0xf6, 0xbd, 0x40, 0xec, 0x28, 0xea, 0x0a, 0x80, 0x90, 0x25, 0xe0, 0xe9, 0xf3, 0x4a, 0x70, 0xc3, 0x41, 0xb8, 0x95, 0xfc, 0x88, 0x55, 0xd2, 0xa2, 0x18, 0x83, 0xd3, 0xfa, 0x27, 0xc5, 0x6d, 0x57, 0xca, 0xd4, 0x9e, 0x16, 0x83, 0x62, 0xca, 0x90, 0xc2, 0x67, 0xdc, 0x5c, 0xb9, 0x6f, 0xbc, 0xa8, 0xaa, 0x0d, 0x07, 0x82, 0x7c, 0x30, 0x22, 0xaf, 0x2e, 0x04, 0x2b, 0xab, 0xaa, 0x03, 0x6b, 0x99, 0xa7, 0x0c, 0x2c, 0x28, 0x1d, 0x4d, 0x70, 0x87, 0x42, 0xab, 0x54, 0x72, 0xf6, 0xa0, 0x23, 0x1a, 0xaf, 0x4b, 0x36, 0x37, 0x67, 0x93, 0xf1, 0x98, 0x2b, 0xe4, 0xb4, 0x68, 0x8c, 0xf2, 0xa8, 0x25, 0x9b, 0xcb, 0x58, 0x10, 0xd6, 0x69, 0x5d, 0x34, 0x19, 0x40, 0xf8, 0xdd, 0xad, 0xcb, 0xbd, 0x4c, 0x42, 0x2b, 0x53, 0x69, 0x54, 0x2e, 0x9b, 0xdd, 0x33, 0xe1, 0x75, 0xb9, 0x43, 0x3a, 0x56, 0xa7, 0xcd, 0x38, 0xfc, 0x26, 0x85, 0xb9, 0xa6, 0x25, 0x52, 0xdb, 0x66, 0xe0, 0xda, 0xdd, 0xae, 0x4c, 0xc4, 0x1a, 0x42, 0x3c, 0xcf, 0x47, 0x1d, 0x05, 0x2f, 0x32, 0xf7, 0xc2, 0x79, 0x78, 0xab, 0xa8, 0x42, 0x31, 0x7e, 0x36, 0x1c, 0xe3, 0x27, 0x9a, 0xc5, 0x63, 0xe5, 0x50, 0xbf, 0x39, 0x21, 0x7e, 0x18, 0xa4, 0xa9, 0x52, 0xa2, 0x7c, 0x1a, 0xa7, 0xf0, 0xa8, 0x3f, 0xed, 0x89, 0x6d, 0xf3, 0x3d, 0xa1, 0xf9, 0x07, 0xdb, 0xf8, 0x47, 0x5e, 0x8f, 0x92, 0xc2, 0xd4, 0x68, 0xd2, 0x03, 0xa1, 0xda, 0xa0, 0xc4, 0x12, 0xca, 0x61, 0x87, 0xe0, 0x8c, 0xb0, 0xc3, 0x7c, 0x55, 0x6d, 0x56, 0xc9, 0xba, 0x1d, 0xf1, 0xc1, 0xc9, 0xcd, 0x5a, 0x62, 0x0b, 0x5b, 0x82, 0xb5, 0xc1, 0x08, 0xef, 0xcc, 0x2f, 0xce, 0xb5, 0x8e, 0x79, 0xbd, 0x0a, 0xad, 0x4a, 0x69, 0x84, 0x6b, 0x51, 0x13, 0x4b, 0xd7, 0xc2, 0xb5, 0xa8, 0x8d, 0xc7, 0xdc, 0x21, 0xa7, 0x59, 0x63, 0x50, 0x31, 0x23, 0xf0, 0x88, 0xc8, 0xcd, 0xd1, 0xe6, 0x48, 0xac, 0x99, 0x35, 0xb5, 0x7a, 0x91, 0x98, 0x17, 0x72, 0xe7, 0xb5, 0x72, 0x95, 0x56, 0xe5, 0xb2, 0x3a, 0x2a, 0x6b, 0x81, 0x78, 0x43, 0x3b, 0x79, 0x17, 0xf1, 0x3b, 0xe6, 0x0e, 0x1c, 0x63, 0x08, 0xb9, 0x39, 0xd2, 0xaf, 0x49, 0x6a, 0x2d, 0x8a, 0x32, 0x44, 0x7a, 0x25, 0x31, 0x09, 0xe6, 0x06, 0x18, 0x72, 0x50, 0x56, 0x90, 0xcf, 0x09, 0x44, 0x5a, 0x74, 0xf8, 0x5f, 0x74, 0x36, 0x9f, 0x91, 0xf5, 0x58, 0x75, 0x3a, 0xab, 0x87, 0x35, 0xfa, 0x6c, 0x3a, 0xea, 0x66, 0x52, 0x39, 0xf3, 0xd7, 0x93, 0x46, 0x2f, 0xfa, 0x09, 0xb9, 0x5d, 0x10, 0xb7, 0xf6, 0x8a, 0x7a, 0xc9, 0x22, 0xf2, 0x2d, 0xd2, 0x0d, 0xd7, 0x3a, 0x42, 0x7c, 0xb5, 0xa8, 0xe2, 0xa1, 0x5a, 0xcf, 0x56, 0xe1, 0xe0, 0xe2, 0xd2, 0x98, 0x24, 0x58, 0x8b, 0x8f, 0x2a, 0x92, 0x0c, 0x27, 0x45, 0x87, 0x00, 0xfe, 0x93, 0x2e, 0xad, 0xef, 0x19, 0x77, 0x6d, 0xab, 0xdc, 0xb5, 0x8d, 0x2e, 0xad, 0xe9, 0x67, 0xbd, 0xeb, 0xb3, 0x5e, 0x23, 0x82, 0x77, 0x72, 0xc1, 0x68, 0x0d, 0x86, 0xd6, 0xe2, 0xce, 0x52, 0xa5, 0x6e, 0x56, 0x45, 0x1e, 0xeb, 0x8f, 0x7d, 0x03, 0x99, 0x78, 0x47, 0xd2, 0x6a, 0x49, 0x0f, 0x66, 0xdd, 0x71, 0x87, 0x9e, 0xd1, 0xab, 0x14, 0x06, 0x7d, 0x7f, 0x34, 0xea, 0xce, 0x41, 0x6e, 0xe6, 0x50, 0x32, 0xeb, 0xbd, 0x21, 0x77, 0x6e, 0x20, 0x2e, 0xfc, 0x28, 0x39, 0xdc, 0xe0, 0x55, 0xab, 0x5b, 0x59, 0x25, 0xb3, 0x02, 0x14, 0x63, 0x29, 0xbb, 0x45, 0x38, 0x42, 0x2a, 0x6d, 0xe2, 0x1c, 0x05, 0x21, 0x5d, 0xf8, 0x6f, 0x48, 0x17, 0xa2, 0x48, 0x6a, 0x46, 0xf9, 0x36, 0x51, 0x1c, 0x2b, 0x57, 0x0a, 0x7e, 0x2b, 0x05, 0xbd, 0x61, 0xc6, 0x5b, 0x89, 0x98, 0x86, 0x87, 0x3c, 0x0a, 0x4f, 0x78, 0xa0, 0xba, 0xbb, 0xd5, 0x5b, 0x29, 0x05, 0x66, 0xa7, 0x14, 0x2e, 0x90, 0xab, 0xd5, 0x2a, 0xce, 0xe6, 0xb7, 0xb7, 0xb7, 0x74, 0x34, 0xb8, 0x03, 0x56, 0x0e, 0x1e, 0xe5, 0x1a, 0x4b, 0x26, 0xdf, 0xe0, 0x4c, 0x8f, 0x15, 0xc3, 0x7c, 0xc7, 0xda, 0xf6, 0x96, 0x71, 0xa8, 0x30, 0x29, 0x94, 0x1a, 0xad, 0x66, 0x6c, 0x68, 0xe1, 0x32, 0x9d, 0x41, 0xa7, 0xc9, 0x60, 0x6a, 0xd6, 0x33, 0x55, 0x6c, 0xdc, 0xd0, 0x5f, 0x03, 0x95, 0x16, 0x40, 0xd4, 0xd1, 0xb7, 0x91, 0x4d, 0x70, 0x3d, 0xf5, 0xc4, 0x79, 0x55, 0xb1, 0x1b, 0x55, 0xc1, 0x21, 0x93, 0x44, 0x69, 0xdd, 0xe6, 0x5e, 0xd9, 0x46, 0x94, 0xbd, 0xe8, 0xf3, 0x3c, 0x33, 0xdf, 0xed, 0x18, 0xb3, 0x04, 0x7e, 0xd7, 0x13, 0xfa, 0x20, 0x2b, 0x96, 0x1f, 0xca, 0x06, 0xfc, 0x95, 0x40, 0x13, 0xf0, 0xf0, 0x05, 0x17, 0x1e, 0x0d, 0x77, 0xaf, 0x6b, 0x6e, 0x5a, 0xd7, 0x13, 0xa2, 0x6f, 0xbb, 0xeb, 0xae, 0xb6, 0xc9, 0xde, 0x50, 0xa8, 0x77, 0xb2, 0x4d, 0x9c, 0xd7, 0x7a, 0xea, 0x7a, 0xb2, 0x99, 0xbe, 0x0d, 0xc5, 0x53, 0x8b, 0x31, 0x2c, 0x92, 0x71, 0x00, 0x8b, 0x6f, 0xd8, 0x27, 0x63, 0x20, 0x0c, 0xfc, 0x19, 0xa1, 0x24, 0x68, 0x12, 0xc9, 0xe6, 0x50, 0xcf, 0xba, 0xa6, 0x66, 0xf8, 0xda, 0xeb, 0xf7, 0xb6, 0x34, 0xd2, 0xb7, 0xb5, 0x4d, 0xf5, 0x84, 0x42, 0x3d, 0x53, 0x6d, 0x77, 0x2d, 0x5a, 0x2c, 0xbe, 0xbb, 0x83, 0x7a, 0x84, 0x5c, 0xca, 0xec, 0x83, 0x27, 0xe9, 0x42, 0x69, 0x1e, 0xb0, 0x91, 0x86, 0x58, 0x5b, 0xf2, 0x21, 0x4e, 0x92, 0xa5, 0x5c, 0x57, 0x73, 0xf9, 0xa7, 0x72, 0xf1, 0xd4, 0x73, 0xf1, 0x54, 0x78, 0xce, 0xf2, 0x18, 0xaa, 0xe7, 0x57, 0x65, 0x5d, 0xab, 0x72, 0x25, 0xc3, 0x09, 0x79, 0x8a, 0x35, 0xf3, 0x11, 0x11, 0x9d, 0x0e, 0xcc, 0xf1, 0x97, 0x63, 0xe7, 0x31, 0xa9, 0xf3, 0x44, 0x3c, 0x68, 0xcb, 0x3d, 0x09, 0x86, 0x62, 0x69, 0x87, 0xb8, 0xcf, 0xe8, 0x8f, 0x7d, 0x03, 0x59, 0xb4, 0x51, 0x8f, 0xc0, 0xad, 0x99, 0xb5, 0x1e, 0x19, 0x73, 0xca, 0xe1, 0xeb, 0x06, 0xa1, 0x0e, 0x5d, 0xcb, 0x9c, 0x44, 0x91, 0x8a, 0xc5, 0x06, 0x3f, 0x3c, 0x9b, 0x6a, 0x86, 0x44, 0xf5, 0xef, 0x30, 0x50, 0x2b, 0x35, 0x2e, 0xfa, 0x4b, 0x26, 0x45, 0xdc, 0x56, 0x31, 0x27, 0x46, 0x9c, 0xb5, 0x00, 0x11, 0x08, 0x70, 0x7e, 0x2e, 0x38, 0x4f, 0x7d, 0x3b, 0x50, 0xaa, 0x6f, 0x97, 0x91, 0x94, 0x0e, 0x29, 0x57, 0xab, 0x16, 0x8a, 0x76, 0x4a, 0x67, 0xb1, 0xb6, 0xae, 0xcd, 0x60, 0x5f, 0x55, 0xb7, 0x62, 0xcd, 0xe1, 0x91, 0x40, 0xfa, 0x70, 0x24, 0x6c, 0x8f, 0x73, 0xcc, 0x49, 0x28, 0xd4, 0xf1, 0xee, 0x50, 0x26, 0x5e, 0x9f, 0xd8, 0xb4, 0x16, 0x0c, 0x09, 0x4f, 0x8e, 0xd7, 0xc6, 0xd1, 0x47, 0xba, 0xc6, 0x6a, 0x02, 0xd8, 0x46, 0x68, 0x83, 0xeb, 0x18, 0x86, 0x7b, 0xae, 0x86, 0xd8, 0x57, 0x54, 0x87, 0xcc, 0x70, 0xa6, 0x14, 0xd8, 0xcf, 0x56, 0x9a, 0x78, 0xd4, 0x49, 0x11, 0x85, 0x9a, 0x96, 0xac, 0x3e, 0xd2, 0x06, 0xc4, 0x88, 0xea, 0x73, 0xaf, 0x68, 0xce, 0xf2, 0xcc, 0x7c, 0xb7, 0xc3, 0xf9, 0xfe, 0x46, 0x4d, 0x4d, 0x28, 0x18, 0xc4, 0x25, 0x44, 0xce, 0x70, 0xda, 0x55, 0x18, 0x66, 0x89, 0xc6, 0x37, 0x03, 0x32, 0x0c, 0xd4, 0x0e, 0x7b, 0x66, 0x53, 0x6d, 0x6a, 0x53, 0xc6, 0x6e, 0x57, 0x03, 0xce, 0x37, 0x12, 0xa9, 0x49, 0x3b, 0xd2, 0x5d, 0xb1, 0x58, 0x17, 0xfc, 0xa8, 0x09, 0x8f, 0xf8, 0x4c, 0xd4, 0x8b, 0x6a, 0xa3, 0x5e, 0x11, 0xf4, 0xf9, 0x82, 0x0a, 0xbd, 0x51, 0x9d, 0x31, 0xbb, 0xd3, 0xdb, 0x1b, 0x82, 0xed, 0x49, 0x87, 0x23, 0xd9, 0x1e, 0x6c, 0xd8, 0x9e, 0x76, 0x9b, 0x45, 0x59, 0x7e, 0x11, 0xf9, 0x0e, 0xd9, 0x22, 0xdb, 0x4b, 0xd8, 0x89, 0x2c, 0x5a, 0xa1, 0x08, 0xc0, 0xba, 0x34, 0xa8, 0x07, 0x0c, 0x41, 0x0d, 0x42, 0xc9, 0x47, 0xb6, 0xa7, 0x84, 0x16, 0x3e, 0xcb, 0x61, 0x40, 0x60, 0x11, 0x91, 0xab, 0x8d, 0x04, 0xf9, 0x28, 0x86, 0x66, 0xe5, 0x3c, 0x60, 0x6e, 0x8c, 0x3b, 0x0e, 0xd8, 0x8a, 0xcc, 0xea, 0x7a, 0x89, 0x4e, 0x98, 0x03, 0x6f, 0xb0, 0x71, 0x37, 0x9f, 0xf2, 0xea, 0xcc, 0x81, 0xb8, 0xd9, 0xa7, 0xcf, 0xd6, 0xa7, 0xc2, 0x99, 0xf8, 0x61, 0x57, 0xd3, 0x8a, 0x96, 0xc6, 0xc5, 0x19, 0x5b, 0x3a, 0xdd, 0x3d, 0xc0, 0xb7, 0x8c, 0x66, 0xc3, 0x6d, 0xb6, 0xc3, 0x74, 0xc0, 0x86, 0x00, 0xc2, 0x9c, 0xc1, 0x84, 0x4d, 0x7e, 0x6e, 0xb8, 0x3e, 0x9b, 0x1c, 0x59, 0xc7, 0x20, 0x8b, 0x21, 0x9f, 0xed, 0xf6, 0x67, 0x16, 0x99, 0x4c, 0x93, 0x5d, 0x89, 0x25, 0x6d, 0x21, 0x8b, 0xf5, 0x87, 0xe2, 0x99, 0xd1, 0x91, 0xbf, 0x27, 0x73, 0xcc, 0x5d, 0x50, 0x33, 0xaf, 0x29, 0x86, 0xad, 0x0a, 0x06, 0x63, 0xdf, 0xa2, 0x0b, 0x7b, 0x70, 0xf4, 0x15, 0xb6, 0x17, 0x61, 0x26, 0x04, 0xef, 0xf0, 0x04, 0x42, 0x16, 0x7c, 0x2e, 0x75, 0x94, 0xe8, 0x93, 0x13, 0x11, 0xa7, 0xeb, 0x71, 0xd0, 0x1c, 0x22, 0xc6, 0x64, 0x4e, 0x93, 0x68, 0xec, 0xf2, 0x1f, 0x56, 0x79, 0x13, 0x4d, 0x21, 0xce, 0x51, 0x93, 0x59, 0x58, 0x08, 0xaa, 0x8c, 0x1e, 0xa8, 0x86, 0x32, 0x16, 0x1d, 0x73, 0x32, 0xbf, 0xf7, 0xa2, 0x0b, 0xb2, 0x6f, 0xbd, 0xd9, 0x7c, 0xc5, 0xf6, 0xbe, 0xac, 0xd9, 0x53, 0x3f, 0x71, 0xe9, 0x50, 0x60, 0x78, 0xc1, 0x20, 0x6f, 0x33, 0xc2, 0xa9, 0xaa, 0x85, 0xfb, 0x29, 0x21, 0x3b, 0x1f, 0xc5, 0x9f, 0x11, 0x0f, 0x8a, 0x9b, 0x48, 0xdd, 0x06, 0xc9, 0x6e, 0x9d, 0x02, 0x71, 0x08, 0x09, 0x00, 0x03, 0xfd, 0x04, 0xca, 0x3f, 0xcd, 0xba, 0xa1, 0x94, 0x07, 0xef, 0x42, 0x86, 0x78, 0x12, 0x17, 0x77, 0x87, 0x1f, 0x34, 0x39, 0x59, 0x66, 0x2d, 0xd3, 0x54, 0x09, 0xf8, 0xc2, 0x0c, 0x0f, 0x14, 0x96, 0x4c, 0x67, 0x5f, 0xfc, 0xf4, 0x67, 0x51, 0x2d, 0x55, 0x40, 0x34, 0x37, 0xc4, 0xa2, 0x4e, 0xbb, 0xc5, 0x44, 0xb0, 0x80, 0x95, 0x55, 0x8a, 0x94, 0x4b, 0x96, 0x22, 0xae, 0x4a, 0xa4, 0xf3, 0xd0, 0x55, 0xd2, 0xab, 0x68, 0x2c, 0xc2, 0x33, 0x05, 0x6a, 0xb3, 0x45, 0x14, 0xbb, 0x62, 0x89, 0x7a, 0x39, 0x9d, 0x2b, 0xfe, 0x33, 0xd2, 0xc0, 0x2a, 0x43, 0xc9, 0xb4, 0xc9, 0x65, 0x4f, 0xf5, 0x2e, 0xee, 0x4d, 0xf9, 0x3b, 0xd6, 0xb6, 0xb4, 0xac, 0xed, 0x0c, 0x2c, 0x5a, 0x62, 0x0a, 0x38, 0xf4, 0x6b, 0xd7, 0x9b, 0xea, 0x73, 0x59, 0x33, 0xf5, 0x97, 0xfe, 0x76, 0x7f, 0x3e, 0xe6, 0xd7, 0x29, 0xdd, 0x35, 0x85, 0x20, 0xb2, 0x2e, 0x91, 0x1c, 0xab, 0x51, 0xaa, 0x94, 0x06, 0x8d, 0xd1, 0x17, 0xae, 0xeb, 0x5e, 0x53, 0xcc, 0x8d, 0x77, 0x84, 0x82, 0xc5, 0x95, 0xb9, 0x91, 0x3d, 0x7e, 0x8d, 0x93, 0x8f, 0xd8, 0x5b, 0x87, 0x29, 0x99, 0x82, 0x81, 0xf3, 0x9a, 0x22, 0x7f, 0x4f, 0xe9, 0xb1, 0xfe, 0x5d, 0x8f, 0x72, 0x54, 0xc3, 0xd8, 0xab, 0x85, 0xad, 0x40, 0x78, 0x7d, 0xb1, 0x54, 0x8e, 0x82, 0xf4, 0x45, 0x9f, 0x16, 0x1c, 0x67, 0x7d, 0x1d, 0xcb, 0xda, 0xa0, 0xf6, 0x2c, 0x17, 0xd1, 0xb2, 0x4b, 0xa5, 0x54, 0x03, 0x62, 0xf7, 0xcd, 0x15, 0x7f, 0x2c, 0xb2, 0x81, 0x59, 0x3d, 0x22, 0xf6, 0x0a, 0x0a, 0x44, 0x06, 0x6f, 0x2e, 0x5a, 0x61, 0xf6, 0x45, 0x38, 0xbb, 0xef, 0xf2, 0x8d, 0xa3, 0x0d, 0xab, 0x3a, 0x82, 0xb5, 0x8b, 0x77, 0x75, 0x5e, 0xde, 0xb9, 0x63, 0x24, 0xe9, 0xca, 0xf4, 0xc5, 0x83, 0x0c, 0xc9, 0x28, 0x65, 0x81, 0xf8, 0xf2, 0x61, 0xea, 0x03, 0x72, 0xe5, 0xf2, 0x60, 0x63, 0xc4, 0xe4, 0x4b, 0xac, 0x5b, 0x1a, 0xe9, 0x5e, 0x93, 0xef, 0xd8, 0x3c, 0x10, 0x01, 0x5f, 0xfa, 0x12, 0x88, 0xf4, 0x4e, 0xb6, 0xd4, 0x2e, 0xe9, 0xca, 0xb1, 0x0d, 0x0a, 0xa3, 0x41, 0x43, 0x59, 0x86, 0xf3, 0x0b, 0x96, 0x23, 0xb0, 0x30, 0xa8, 0xcb, 0xfd, 0x82, 0x54, 0x30, 0x5f, 0x26, 0x52, 0xc4, 0x8a, 0xa2, 0x32, 0xee, 0xe7, 0x54, 0xc8, 0x1b, 0x31, 0x28, 0xa1, 0x25, 0x20, 0x5c, 0x64, 0xa8, 0x39, 0x13, 0xeb, 0x70, 0x4e, 0x06, 0xc2, 0xbd, 0xc0, 0xe4, 0xda, 0x85, 0x32, 0xe7, 0x18, 0x0c, 0x77, 0xcf, 0x10, 0x73, 0x2f, 0x8b, 0x6e, 0x99, 0x14, 0x51, 0x0b, 0xc9, 0x48, 0x3c, 0x80, 0xd5, 0x62, 0xa9, 0xa0, 0x3c, 0x22, 0x9a, 0x7a, 0x6c, 0x04, 0xe5, 0x22, 0xc8, 0xbe, 0x94, 0x6b, 0x11, 0xed, 0x03, 0xc8, 0x4c, 0x20, 0xd9, 0xca, 0xe1, 0x98, 0x33, 0xa4, 0xc2, 0xe2, 0xb1, 0x27, 0xa3, 0x7e, 0x5d, 0x27, 0x29, 0xa7, 0x17, 0xd2, 0x2c, 0x3b, 0x79, 0xdf, 0x02, 0x9d, 0xd7, 0xc7, 0x29, 0x2d, 0xe6, 0xa6, 0x85, 0xb6, 0x6e, 0x52, 0xc6, 0x0c, 0xd2, 0x46, 0x76, 0xf2, 0xde, 0xa5, 0x2a, 0xb7, 0x9b, 0xea, 0x2f, 0x78, 0x8c, 0x4a, 0x5b, 0x3c, 0x70, 0x3b, 0xcb, 0x6b, 0xee, 0x55, 0xeb, 0xc1, 0xb6, 0xc7, 0x3d, 0xe6, 0x8c, 0x9a, 0xd3, 0x2a, 0x12, 0x8d, 0x27, 0x59, 0x8f, 0xe6, 0x3e, 0xb5, 0x5e, 0xb8, 0xed, 0x79, 0xab, 0x4d, 0xa4, 0x2f, 0xfd, 0xc2, 0x5e, 0xb2, 0x01, 0xd4, 0x11, 0x1a, 0x82, 0x27, 0x96, 0x15, 0x55, 0x88, 0xb6, 0xa0, 0x5a, 0x32, 0x25, 0x90, 0x5e, 0x4b, 0x99, 0xaa, 0x94, 0x62, 0x24, 0x69, 0x7a, 0x2b, 0x8d, 0xab, 0x2e, 0xe1, 0x35, 0x2d, 0x5f, 0x45, 0xd6, 0x34, 0xa2, 0x6c, 0x4c, 0x1b, 0xff, 0x06, 0x17, 0x8d, 0x04, 0x71, 0xd1, 0x25, 0xab, 0x07, 0x54, 0xa8, 0x4d, 0x60, 0x96, 0x20, 0x89, 0xe8, 0xcc, 0x7f, 0x58, 0xf2, 0x8e, 0x4c, 0x5b, 0x73, 0x3a, 0xd9, 0x7e, 0xd8, 0xd7, 0xb6, 0xaa, 0x25, 0xb3, 0xb2, 0x27, 0x0a, 0xd5, 0xfe, 0xfa, 0x68, 0xb7, 0xf3, 0xf0, 0xb0, 0xd3, 0x82, 0x74, 0xfe, 0x62, 0x7b, 0x7e, 0x79, 0xab, 0x3f, 0xd4, 0x3d, 0xd9, 0xda, 0x3c, 0x39, 0x50, 0x63, 0xb7, 0xfe, 0x88, 0x20, 0x4e, 0x9f, 0x26, 0x52, 0xc2, 0x5e, 0x4a, 0x0f, 0xd2, 0x94, 0x9c, 0x1a, 0x21, 0x88, 0x8f, 0xaf, 0x23, 0x94, 0x8f, 0x11, 0xe4, 0x37, 0x3f, 0xbe, 0xee, 0xd1, 0x4c, 0x5c, 0x1c, 0xd7, 0xa5, 0xa7, 0xdf, 0x93, 0x5d, 0x46, 0x43, 0x3d, 0x8f, 0x68, 0x27, 0xbe, 0x53, 0xd4, 0xa3, 0xf2, 0xca, 0xcd, 0x50, 0xcb, 0x8a, 0xf9, 0x49, 0x9a, 0xa1, 0xca, 0x6a, 0x06, 0x43, 0x21, 0x47, 0xd6, 0x4e, 0x35, 0x8a, 0xbd, 0x64, 0xc4, 0xd8, 0x4b, 0x72, 0x5c, 0xa5, 0xc0, 0x11, 0xbf, 0x4a, 0x5c, 0x62, 0xa9, 0x14, 0x49, 0x2d, 0x72, 0xea, 0xcf, 0xfb, 0xc4, 0x0e, 0x19, 0xc6, 0xae, 0x99, 0x7b, 0xb3, 0x0a, 0x43, 0x02, 0x54, 0xaa, 0x01, 0xcd, 0x7a, 0x00, 0x1e, 0x78, 0x17, 0x81, 0xe1, 0x1f, 0xb3, 0x99, 0xda, 0x44, 0x34, 0x8c, 0x52, 0x28, 0xcd, 0x9c, 0x56, 0x8d, 0x95, 0x55, 0x0d, 0x0e, 0xc5, 0xc7, 0x47, 0x43, 0x4e, 0x95, 0x43, 0xd2, 0x03, 0xbc, 0x18, 0xd8, 0x53, 0x52, 0xe0, 0xca, 0xba, 0x7c, 0x99, 0xeb, 0x22, 0x46, 0xcc, 0xfc, 0x88, 0x2f, 0xae, 0x6a, 0xb9, 0xe4, 0x7a, 0x8e, 0x4d, 0xdf, 0x71, 0xe1, 0x92, 0x9b, 0xb6, 0x17, 0x4d, 0xce, 0x99, 0x69, 0xaa, 0x76, 0xf1, 0xce, 0x9e, 0x8e, 0xb5, 0x3d, 0x29, 0xa3, 0x4d, 0xdb, 0x10, 0x5c, 0xbd, 0x69, 0x4b, 0xea, 0xe0, 0x2b, 0xad, 0x0b, 0x5e, 0xbf, 0x68, 0xe9, 0x81, 0xd1, 0xc4, 0x2b, 0x93, 0x93, 0xe3, 0xb7, 0x37, 0xf2, 0xb7, 0x92, 0xdb, 0x9b, 0x36, 0xf4, 0x47, 0x8f, 0x5e, 0x0a, 0xe4, 0x5d, 0x8b, 0x5b, 0xcf, 0x7b, 0x70, 0x7b, 0xd2, 0x3b, 0x71, 0xcd, 0xca, 0x04, 0x8a, 0xa6, 0xc6, 0xa0, 0xb0, 0xe7, 0xae, 0xdf, 0xb0, 0xa3, 0x52, 0xe9, 0x7b, 0xed, 0xa2, 0xa9, 0x8b, 0xc0, 0x2f, 0x88, 0x12, 0xae, 0xd0, 0x79, 0x70, 0xee, 0x35, 0x44, 0x0e, 0x72, 0x6b, 0x73, 0xc4, 0xaf, 0x21, 0x68, 0x19, 0x51, 0xef, 0xb4, 0x31, 0x34, 0x25, 0xe3, 0x0c, 0x24, 0xc0, 0xca, 0x2e, 0x9a, 0x4f, 0x3f, 0x1c, 0x3b, 0xdc, 0x30, 0x32, 0x94, 0xc3, 0x8e, 0x52, 0xd8, 0xcf, 0xc5, 0x66, 0x25, 0x31, 0xcc, 0x89, 0x2c, 0xd5, 0xf8, 0x0e, 0x9f, 0x71, 0x93, 0x14, 0x0f, 0x21, 0x21, 0x88, 0x92, 0xa5, 0x32, 0xdf, 0x7a, 0xad, 0x56, 0x9b, 0xd3, 0xe6, 0xc2, 0x41, 0x93, 0xd5, 0x1a, 0xc5, 0x39, 0xe4, 0x1e, 0xca, 0x2a, 0x99, 0xe3, 0x18, 0x13, 0x96, 0xd7, 0x6b, 0xa9, 0x08, 0xce, 0x2b, 0x47, 0xf4, 0xa5, 0x50, 0x55, 0x4f, 0x13, 0x55, 0xa4, 0xce, 0x81, 0x5f, 0xb5, 0x9f, 0xbf, 0x6b, 0x67, 0x8b, 0x98, 0x3a, 0x7e, 0x6c, 0xff, 0xfe, 0x57, 0x8a, 0xe7, 0xc1, 0xaf, 0x2f, 0xb7, 0xa5, 0x8f, 0x5d, 0xbe, 0x6f, 0x56, 0x85, 0x86, 0xb6, 0x24, 0x79, 0x58, 0x97, 0xac, 0xaf, 0xd5, 0xdd, 0x87, 0xe2, 0x25, 0xb7, 0x1d, 0xba, 0x97, 0x15, 0x5e, 0x04, 0x03, 0x5a, 0xf8, 0x8b, 0x5e, 0xd0, 0x01, 0x41, 0xb3, 0x74, 0xe0, 0xf0, 0x3d, 0x2c, 0x98, 0xac, 0xd4, 0x6d, 0x78, 0x4c, 0xb3, 0x64, 0x50, 0xe4, 0x7f, 0x81, 0xd3, 0xef, 0xd1, 0x63, 0x70, 0x6e, 0x26, 0x88, 0xc9, 0xe2, 0xba, 0x09, 0x00, 0x94, 0x08, 0xf3, 0xb7, 0x1f, 0x28, 0xe4, 0x49, 0x5c, 0xdf, 0x7b, 0x2e, 0xf6, 0x2f, 0x8a, 0xa2, 0x94, 0x9f, 0x4b, 0xc8, 0x51, 0x18, 0xc3, 0xb9, 0x84, 0x82, 0x01, 0x0a, 0xe6, 0x5c, 0x82, 0x81, 0x34, 0x46, 0x76, 0x6e, 0x09, 0xc4, 0xed, 0xdc, 0x72, 0x05, 0x6e, 0x54, 0x8b, 0x62, 0x79, 0x30, 0x19, 0x08, 0xc5, 0xe1, 0x3f, 0x62, 0x61, 0xda, 0x59, 0x59, 0x92, 0x74, 0xe9, 0x2c, 0xe6, 0x9b, 0x11, 0x4b, 0xb5, 0xcc, 0x9f, 0x03, 0x47, 0x4b, 0x18, 0xaa, 0x26, 0x19, 0xdc, 0x71, 0x90, 0xe6, 0x86, 0xcb, 0x88, 0x8f, 0x1b, 0xab, 0xd1, 0x8e, 0xfc, 0x8d, 0x43, 0x4b, 0x47, 0x23, 0xac, 0x97, 0x6d, 0x69, 0xd0, 0x5b, 0xce, 0x05, 0xc6, 0x78, 0x7f, 0x83, 0x02, 0xd5, 0x15, 0xda, 0xf2, 0x78, 0x4b, 0xad, 0xc2, 0x66, 0xe2, 0xa2, 0x52, 0x21, 0x21, 0x7f, 0xf3, 0xf0, 0xb2, 0x31, 0x34, 0xe9, 0xf0, 0x3e, 0xeb, 0x76, 0x63, 0x72, 0xb8, 0x49, 0x59, 0xb7, 0xfa, 0xd0, 0x0a, 0x5c, 0x54, 0x48, 0x82, 0x3d, 0xca, 0xb8, 0xf9, 0x5a, 0xbf, 0xd3, 0xe1, 0x36, 0x31, 0x4c, 0x2e, 0xa6, 0xb3, 0xa6, 0xc6, 0x07, 0xf3, 0x1a, 0x9f, 0x1f, 0x15, 0x18, 0xea, 0xcd, 0x57, 0x6a, 0x0a, 0x05, 0xea, 0x02, 0xf0, 0x1e, 0x33, 0x23, 0xcf, 0xc5, 0xb4, 0xd6, 0xf4, 0xca, 0xa1, 0xbc, 0x9a, 0xaf, 0x91, 0xca, 0x0b, 0xa1, 0xd8, 0x04, 0xe1, 0x66, 0xea, 0x47, 0xb4, 0x12, 0xaa, 0x09, 0x01, 0x62, 0x0d, 0x71, 0x7d, 0x51, 0xdd, 0x07, 0x18, 0x72, 0xa4, 0x16, 0xb1, 0x55, 0x69, 0xaf, 0xc5, 0x11, 0x80, 0x2d, 0x54, 0xde, 0x27, 0x4b, 0xb9, 0x62, 0x90, 0x66, 0x95, 0x70, 0x13, 0x15, 0x8a, 0xcd, 0x18, 0x47, 0x0a, 0x27, 0xe2, 0xee, 0x94, 0xe3, 0xc3, 0xab, 0x40, 0x15, 0x37, 0x51, 0x32, 0xfe, 0xd9, 0x9e, 0xaa, 0x7e, 0x00, 0xe3, 0x4e, 0x4d, 0xac, 0x5c, 0x31, 0x56, 0x6c, 0xcb, 0x67, 0x93, 0x71, 0x9f, 0x07, 0x17, 0xb1, 0xd2, 0x03, 0x3d, 0xca, 0xc3, 0x64, 0x2a, 0x19, 0x85, 0x90, 0x71, 0x51, 0x68, 0x21, 0x2a, 0x39, 0x35, 0x54, 0x49, 0x7c, 0x14, 0x7f, 0x93, 0xe5, 0xce, 0x10, 0xc8, 0xaa, 0xe4, 0x32, 0x14, 0x79, 0x48, 0xba, 0x37, 0x9d, 0xe3, 0xcc, 0x8f, 0x64, 0xb2, 0x23, 0x39, 0xe7, 0x39, 0x3f, 0xaa, 0xe9, 0x99, 0x98, 0xde, 0x5a, 0x97, 0xed, 0xe4, 0x7b, 0xb6, 0x0c, 0xf4, 0xed, 0x18, 0x8a, 0x24, 0x74, 0x86, 0xa6, 0xe9, 0xeb, 0x97, 0xf5, 0x34, 0x7b, 0xbd, 0xcd, 0x6b, 0x2f, 0x39, 0x70, 0xc9, 0xda, 0xe6, 0x8e, 0x7d, 0x8f, 0xef, 0xda, 0xf9, 0x95, 0x1d, 0x85, 0x89, 0xc5, 0x89, 0x8c, 0x81, 0x4f, 0xf3, 0x43, 0x63, 0x4d, 0x9b, 0x8e, 0x8d, 0x76, 0xee, 0x49, 0x0b, 0xff, 0x9b, 0x7c, 0x7b, 0xf3, 0x78, 0xac, 0x3f, 0xe3, 0x76, 0x43, 0x86, 0x39, 0xbe, 0x99, 0x7a, 0x3c, 0x18, 0xb7, 0xab, 0x19, 0x99, 0xdc, 0x31, 0x58, 0xcf, 0x17, 0x6a, 0xac, 0xf6, 0x64, 0xdb, 0x27, 0x13, 0x9e, 0xde, 0xf0, 0xd0, 0xde, 0xb1, 0x5a, 0xbd, 0x31, 0x6e, 0x0e, 0xf8, 0x0d, 0xb4, 0xd2, 0x92, 0x1d, 0xef, 0xeb, 0xbc, 0x64, 0x5d, 0x53, 0x62, 0x70, 0x7d, 0x6e, 0x70, 0x93, 0xc7, 0xd4, 0xdd, 0x68, 0xa9, 0xab, 0x8d, 0x1b, 0x62, 0x47, 0x56, 0x0d, 0x5d, 0x34, 0x56, 0xeb, 0x76, 0xf6, 0x87, 0x94, 0x65, 0x9f, 0xe0, 0x65, 0x50, 0xaf, 0x50, 0xe3, 0x4c, 0x46, 0xc9, 0x27, 0x2e, 0xc6, 0x66, 0x63, 0xcf, 0xf8, 0x24, 0x71, 0x66, 0x6e, 0x68, 0x00, 0x2a, 0x2f, 0x50, 0xa5, 0x36, 0xf3, 0x39, 0xf2, 0xb2, 0x13, 0x82, 0x70, 0xe2, 0x04, 0x20, 0x91, 0x66, 0x70, 0xea, 0x14, 0xa9, 0x7c, 0xee, 0x39, 0xb8, 0x0e, 0x11, 0xb8, 0xc6, 0xf7, 0xe3, 0xfc, 0x8d, 0x1e, 0xe4, 0x67, 0x6b, 0x81, 0x27, 0xa7, 0x5e, 0x45, 0xe2, 0x68, 0xbb, 0x72, 0xd0, 0x66, 0x39, 0x4f, 0x63, 0x27, 0x18, 0x06, 0x44, 0x4f, 0x77, 0x47, 0x6b, 0x4d, 0x04, 0xf9, 0xd9, 0x18, 0xbc, 0x0e, 0xb3, 0xfd, 0x6c, 0x70, 0x86, 0xf1, 0x5f, 0xf4, 0x19, 0x7e, 0x36, 0x5c, 0xdf, 0x24, 0xc0, 0x63, 0xb0, 0x61, 0xf0, 0xa3, 0xce, 0x2d, 0x83, 0x51, 0x67, 0xaa, 0x2d, 0x30, 0x70, 0xf5, 0x74, 0x8b, 0xbf, 0xb8, 0x16, 0xfc, 0xd4, 0x69, 0x18, 0xdc, 0xb7, 0xb2, 0xbe, 0x73, 0xf2, 0xbc, 0xc9, 0xce, 0xb6, 0x8b, 0x1e, 0xdb, 0xb3, 0xe5, 0x8e, 0xad, 0xb9, 0xa6, 0x73, 0x6e, 0x5d, 0x13, 0x68, 0x4b, 0xb9, 0xd2, 0x2b, 0xf6, 0x0d, 0xe5, 0x36, 0xc7, 0x85, 0x8f, 0x12, 0x41, 0xca, 0xf7, 0x73, 0x7f, 0xeb, 0xb2, 0xfa, 0xd4, 0x70, 0x7b, 0xc6, 0x62, 0x69, 0x1e, 0x1c, 0xcf, 0x45, 0x7a, 0xb3, 0x1e, 0xfa, 0xa0, 0xc7, 0xa1, 0x08, 0x8e, 0x2d, 0x1b, 0xb2, 0xb9, 0x72, 0xad, 0xc3, 0x53, 0x07, 0x97, 0x2f, 0x3c, 0x7a, 0x4e, 0x31, 0xbb, 0x7c, 0x77, 0x7b, 0xc7, 0x79, 0x93, 0x0b, 0x9d, 0xc6, 0x50, 0x4d, 0xca, 0x1d, 0x59, 0xb9, 0x6e, 0x43, 0x4a, 0x26, 0xab, 0x09, 0x0c, 0xc0, 0x31, 0xd7, 0x9f, 0xfe, 0x33, 0x7d, 0x3b, 0xf5, 0x37, 0x6c, 0xcb, 0xdf, 0x27, 0xb2, 0x2d, 0xb7, 0x52, 0x4e, 0xa2, 0xc8, 0x42, 0x24, 0x71, 0xac, 0x57, 0x88, 0x78, 0x1a, 0x92, 0xfd, 0x69, 0x07, 0x55, 0xd2, 0x6e, 0x3e, 0xe5, 0xa6, 0x73, 0x70, 0xd5, 0xc4, 0x4f, 0x7d, 0x09, 0xaa, 0x71, 0x1d, 0x0a, 0xa2, 0x95, 0x31, 0x87, 0xcc, 0x21, 0x83, 0x4a, 0xaa, 0x68, 0x23, 0xd1, 0x88, 0x50, 0xa0, 0x34, 0x61, 0xb3, 0x0b, 0xda, 0x2c, 0x14, 0x52, 0xde, 0x96, 0xe5, 0x85, 0xc2, 0x58, 0x8b, 0x17, 0x0c, 0x1f, 0x13, 0xa3, 0xd9, 0xb7, 0x37, 0x1d, 0x3b, 0xf6, 0x0c, 0xf8, 0xf1, 0xb3, 0xc7, 0xc0, 0x4f, 0x5b, 0x50, 0xf8, 0x3a, 0x0a, 0x6b, 0x6f, 0x69, 0x29, 0x05, 0xb5, 0xb7, 0x1c, 0x05, 0x27, 0x84, 0x2d, 0xe8, 0xff, 0x88, 0x6f, 0x64, 0x21, 0x6d, 0xfc, 0x26, 0xce, 0x2b, 0xae, 0x45, 0x11, 0x15, 0x52, 0x0f, 0x91, 0xbf, 0xae, 0xd4, 0x43, 0xc9, 0xb3, 0x0b, 0x47, 0x50, 0x9b, 0x8c, 0xa0, 0xde, 0x05, 0x59, 0x53, 0x10, 0xf7, 0xae, 0x94, 0x03, 0x5e, 0x03, 0x02, 0x73, 0x6a, 0xed, 0xd4, 0x82, 0x48, 0xb8, 0x8d, 0x6c, 0x46, 0xbc, 0x20, 0x03, 0x2e, 0xe7, 0x9b, 0x97, 0x65, 0xe2, 0xcb, 0xfb, 0x33, 0x0a, 0xc1, 0x46, 0x15, 0x6e, 0x52, 0x2d, 0xbb, 0xf4, 0x8b, 0xa3, 0xeb, 0x6f, 0xdb, 0x52, 0x38, 0x74, 0xe8, 0x15, 0xf0, 0x8e, 0xcc, 0xab, 0x23, 0x95, 0x06, 0xb3, 0xea, 0x6a, 0xa5, 0x51, 0xa7, 0x22, 0x6f, 0x00, 0xbb, 0xf2, 0xa3, 0x2d, 0x5e, 0x7e, 0xf8, 0xc0, 0xea, 0x4b, 0xbf, 0x71, 0x61, 0x43, 0x6e, 0xea, 0xd8, 0xf8, 0xe4, 0x51, 0x70, 0x8b, 0xb0, 0x75, 0xec, 0x2b, 0x63, 0x46, 0x87, 0x5e, 0x9e, 0xdf, 0x38, 0x39, 0x95, 0xc5, 0x7b, 0xdd, 0x20, 0xec, 0xa1, 0x7a, 0x98, 0x07, 0x21, 0xb7, 0x7b, 0x41, 0xaa, 0x6d, 0x96, 0x00, 0x72, 0x59, 0xb9, 0xca, 0x65, 0x02, 0x28, 0xa4, 0x2f, 0x52, 0x55, 0x50, 0x3f, 0x02, 0x0d, 0xc1, 0x44, 0xa4, 0xaa, 0x26, 0xd8, 0xd4, 0x50, 0xb9, 0x52, 0xd8, 0xd6, 0x32, 0x32, 0x68, 0x8d, 0x24, 0x60, 0xed, 0x9c, 0xff, 0x09, 0xa2, 0xea, 0x81, 0x62, 0x06, 0x72, 0x09, 0x5a, 0x21, 0xc7, 0x75, 0xd1, 0x14, 0xb8, 0x2e, 0x9a, 0xf8, 0x28, 0xc6, 0x86, 0xa1, 0xd6, 0x42, 0x1a, 0x27, 0xbd, 0xa0, 0xa4, 0x20, 0x4e, 0x49, 0x28, 0x22, 0xa1, 0x08, 0x1f, 0x09, 0xf1, 0x41, 0x13, 0x46, 0x11, 0x11, 0x9d, 0xad, 0x67, 0x56, 0x97, 0xcb, 0xe6, 0xcb, 0x70, 0x44, 0x73, 0xaa, 0x26, 0x92, 0x54, 0x60, 0xe1, 0xbe, 0x71, 0xaa, 0x4e, 0x2a, 0xe0, 0xa1, 0xc7, 0x05, 0x3e, 0x3a, 0xdc, 0x6e, 0xbd, 0x2e, 0x1d, 0x5a, 0xb2, 0xb0, 0x8c, 0x45, 0x24, 0x82, 0x9d, 0x81, 0x9d, 0x08, 0xfc, 0x77, 0x45, 0x75, 0xd9, 0xb8, 0x97, 0xed, 0x3a, 0xdd, 0xd4, 0x66, 0xb2, 0x0a, 0xea, 0xec, 0xdf, 0x61, 0x97, 0x1b, 0xe0, 0x9c, 0xae, 0x84, 0x7b, 0xa1, 0x0d, 0x10, 0x45, 0x35, 0xaf, 0x85, 0x44, 0xd8, 0x86, 0xa7, 0x50, 0x3c, 0x02, 0x29, 0x42, 0x26, 0xa7, 0xe5, 0xd8, 0xe6, 0xcf, 0xc8, 0xd1, 0x58, 0xe5, 0xf2, 0x12, 0xf0, 0xad, 0x34, 0xb8, 0x73, 0xe6, 0x4e, 0xa7, 0xef, 0x73, 0x3d, 0xb6, 0x63, 0xbe, 0x55, 0xc8, 0xcf, 0x7d, 0xac, 0x34, 0xb3, 0xf3, 0x3c, 0x3e, 0x67, 0x49, 0xce, 0xf2, 0xa0, 0xb4, 0x24, 0x67, 0x3e, 0x2f, 0xd9, 0xbb, 0x58, 0x53, 0x38, 0x10, 0x45, 0xd8, 0x50, 0x78, 0x4d, 0x2a, 0xe1, 0xdc, 0x9f, 0x55, 0xf6, 0x2f, 0x3c, 0xab, 0x90, 0x05, 0xa9, 0x59, 0x71, 0x60, 0x71, 0xb8, 0x69, 0xc7, 0xdd, 0x9b, 0x5a, 0xd6, 0xd7, 0xa4, 0x8b, 0x12, 0xb4, 0x8b, 0x54, 0x80, 0x25, 0xa9, 0xb4, 0x71, 0x5c, 0xbc, 0x6d, 0x79, 0x47, 0xeb, 0x78, 0x93, 0x7b, 0xdb, 0x8e, 0x7b, 0x1c, 0x20, 0x28, 0xc8, 0x50, 0x0d, 0x0b, 0x72, 0x52, 0x5e, 0x3b, 0xb8, 0xb1, 0xb5, 0xef, 0xa2, 0xf1, 0x0c, 0xab, 0xdf, 0x53, 0xbd, 0x52, 0xaf, 0x61, 0x66, 0xec, 0x72, 0xd5, 0x75, 0x45, 0xfa, 0x97, 0xb5, 0xf9, 0x4d, 0x40, 0x7d, 0xdb, 0x12, 0x47, 0xad, 0x4e, 0xb4, 0x53, 0x50, 0x47, 0xc8, 0xbc, 0xe4, 0x73, 0xdc, 0x2b, 0xf9, 0x1c, 0xb5, 0x50, 0xe3, 0xd3, 0xa0, 0x31, 0x23, 0x0f, 0x3c, 0xf6, 0x39, 0xc2, 0x5f, 0x98, 0xaa, 0x5f, 0x24, 0x9f, 0xa3, 0x15, 0xab, 0x86, 0x0c, 0x40, 0xa0, 0x7d, 0x34, 0xa8, 0xb8, 0x6c, 0x9c, 0x45, 0xbe, 0x5c, 0x58, 0x71, 0x72, 0xa8, 0xa4, 0x3f, 0xce, 0xbe, 0x07, 0x19, 0x6c, 0x83, 0xd1, 0x50, 0x50, 0xb4, 0x80, 0x56, 0xe9, 0x1a, 0xf2, 0xc8, 0xec, 0x74, 0x44, 0xb9, 0x0c, 0x47, 0xea, 0x59, 0xdf, 0xb0, 0xd6, 0x85, 0x12, 0xd9, 0xb6, 0x8c, 0xa3, 0xd6, 0xa4, 0x75, 0x14, 0xd3, 0xf5, 0x4b, 0x9a, 0x7d, 0xae, 0x4c, 0x6f, 0x2c, 0x3f, 0xe2, 0x53, 0x2b, 0xed, 0xa3, 0xa3, 0x76, 0x25, 0x5d, 0xef, 0x74, 0xf7, 0x8e, 0xad, 0x58, 0xc0, 0x5a, 0x57, 0x38, 0xf8, 0x9a, 0x81, 0xa9, 0xa6, 0xf6, 0xa9, 0x6e, 0xa8, 0xb5, 0x25, 0xed, 0x92, 0xad, 0x96, 0x24, 0xd2, 0x54, 0x02, 0xfc, 0x09, 0xeb, 0xb8, 0x45, 0x62, 0x71, 0x71, 0x61, 0x11, 0x50, 0x32, 0x07, 0xaa, 0x81, 0x97, 0xc3, 0x46, 0x99, 0x0c, 0x60, 0xfa, 0x08, 0x12, 0x4a, 0xfe, 0x24, 0x5a, 0xf7, 0xd9, 0x42, 0xc3, 0xa4, 0xb8, 0xd2, 0x72, 0xd1, 0x83, 0x07, 0x88, 0xb6, 0x96, 0x78, 0xd4, 0xe7, 0x31, 0x73, 0x06, 0x5d, 0xa5, 0xb2, 0x1d, 0xa8, 0xc0, 0xc5, 0x55, 0xeb, 0xf7, 0xb3, 0xd0, 0xe1, 0x62, 0xa0, 0x84, 0x0e, 0x37, 0xcb, 0x5f, 0xd3, 0x12, 0x8b, 0x1b, 0x8d, 0x0a, 0x9d, 0x56, 0x2b, 0xbb, 0x5e, 0xa6, 0x41, 0x86, 0x5e, 0xde, 0xde, 0xde, 0x02, 0x75, 0x5b, 0x4f, 0x30, 0x6e, 0xf3, 0xd7, 0xfb, 0xf4, 0x96, 0x64, 0x57, 0x52, 0xae, 0x93, 0xa3, 0xfc, 0x7a, 0x8b, 0x35, 0x27, 0x53, 0x76, 0x2f, 0xe6, 0x8b, 0xeb, 0xda, 0x5a, 0x56, 0x06, 0xa9, 0x0f, 0x5c, 0x56, 0x1d, 0x27, 0x37, 0xf2, 0x0e, 0x90, 0x55, 0x28, 0xd5, 0x3a, 0xf5, 0xf2, 0xc5, 0xb5, 0x2d, 0x5c, 0x34, 0xeb, 0xd5, 0x70, 0xfe, 0x5a, 0xa7, 0x27, 0x1f, 0xb1, 0x02, 0x83, 0x9f, 0x63, 0x55, 0x94, 0xa2, 0x25, 0xed, 0xde, 0x3f, 0x81, 0x2c, 0xc1, 0xe1, 0x10, 0x9e, 0x8b, 0x14, 0x5c, 0xf7, 0x3a, 0xd9, 0x56, 0x42, 0x4b, 0x14, 0x88, 0xc6, 0x62, 0xde, 0x07, 0xb7, 0x3f, 0x42, 0x91, 0x21, 0x07, 0x33, 0xf5, 0xe9, 0x9a, 0x60, 0x40, 0x46, 0xf5, 0x4b, 0xe8, 0x54, 0x93, 0xe5, 0xf0, 0x84, 0x69, 0x14, 0x9e, 0xe0, 0x75, 0x9b, 0x39, 0x31, 0xab, 0x94, 0x91, 0x22, 0x60, 0x2a, 0xe5, 0xfc, 0x3c, 0x25, 0xed, 0x58, 0x12, 0x8c, 0x20, 0x05, 0x97, 0x47, 0xc2, 0xf9, 0x76, 0x54, 0x7b, 0xa9, 0x16, 0xaa, 0xc5, 0x16, 0x6b, 0xc1, 0x6a, 0x91, 0xc9, 0x41, 0x12, 0x55, 0x70, 0x20, 0x0d, 0xcd, 0x75, 0x2d, 0xed, 0x5d, 0xcf, 0x36, 0x74, 0x5c, 0x7b, 0x59, 0x6e, 0x55, 0x57, 0x98, 0x6f, 0x1d, 0xcd, 0xad, 0x6e, 0x56, 0xd7, 0xc6, 0xa6, 0xed, 0x4a, 0xb9, 0xce, 0xa5, 0x75, 0xe9, 0x8d, 0x9c, 0x5a, 0xbf, 0x65, 0x6c, 0xa5, 0x4b, 0x2e, 0xd7, 0x50, 0x7f, 0x31, 0xc6, 0xfb, 0xb2, 0xfe, 0xf4, 0x40, 0xa6, 0xb7, 0x1f, 0x9c, 0xce, 0xde, 0x3c, 0x7d, 0xe2, 0x5e, 0x7f, 0x71, 0x55, 0x73, 0xed, 0xd2, 0xf6, 0x70, 0x6b, 0xaa, 0xd7, 0xd1, 0x9e, 0xd4, 0x59, 0x15, 0x7a, 0x46, 0xc1, 0xc8, 0x7c, 0xae, 0x34, 0x9b, 0x2e, 0x24, 0xb5, 0x56, 0x39, 0xf6, 0x55, 0x5d, 0x0f, 0xbe, 0x89, 0x7c, 0x55, 0xc0, 0x5c, 0x54, 0xa9, 0xff, 0x7f, 0xee, 0xab, 0x12, 0xbd, 0x88, 0xff, 0x48, 0x1b, 0xc5, 0xe4, 0x67, 0x21, 0x6b, 0x6c, 0x9b, 0xe3, 0xe0, 0x2a, 0x79, 0xb8, 0xc2, 0xb5, 0x7e, 0xd1, 0xc3, 0x05, 0xe6, 0x9a, 0x10, 0x67, 0x57, 0x4b, 0x99, 0xeb, 0xe1, 0x0a, 0x47, 0x6a, 0xbd, 0xcb, 0xdb, 0xea, 0x86, 0xb3, 0xae, 0x40, 0x6d, 0xb4, 0x3e, 0xd8, 0x34, 0x14, 0x35, 0x67, 0x72, 0x59, 0x4b, 0x54, 0xc9, 0x6a, 0x2c, 0x8e, 0x90, 0x33, 0x11, 0xaf, 0xcd, 0x36, 0x37, 0xd7, 0xa6, 0x63, 0x35, 0x8e, 0xa0, 0xdd, 0xa8, 0x54, 0x69, 0x15, 0xcc, 0x94, 0x3b, 0x68, 0x8d, 0x64, 0x5c, 0xee, 0x76, 0xce, 0xd0, 0x9a, 0x8a, 0x34, 0xd7, 0x98, 0xe5, 0x26, 0xbf, 0x23, 0xab, 0xd1, 0xb1, 0xba, 0x90, 0xdb, 0xe5, 0x9d, 0xf0, 0x38, 0xac, 0x6e, 0x95, 0x56, 0x25, 0xd7, 0x62, 0x7e, 0xcc, 0x90, 0x6f, 0x81, 0x2c, 0x73, 0x07, 0x61, 0x47, 0xb1, 0xff, 0x6a, 0x15, 0xb2, 0xf9, 0x0f, 0xa2, 0x30, 0x6f, 0xe4, 0x2b, 0x25, 0xd6, 0x8b, 0x2e, 0xa0, 0x75, 0x88, 0xa6, 0x30, 0x28, 0x91, 0x80, 0x0b, 0x9b, 0xc5, 0x72, 0x49, 0x81, 0x59, 0xfa, 0x50, 0x1e, 0x0b, 0x10, 0xb9, 0x8c, 0x39, 0x03, 0xb2, 0x1d, 0xc6, 0x68, 0x57, 0xda, 0xe8, 0x35, 0xd0, 0x40, 0x6b, 0x0c, 0x3b, 0x8a, 0xc5, 0xc3, 0x1d, 0xf4, 0x25, 0x7f, 0x0c, 0xb4, 0xa7, 0x5c, 0x0c, 0x33, 0xac, 0x52, 0xb8, 0xa3, 0x7f, 0x9c, 0x7c, 0xfe, 0x79, 0x64, 0x4b, 0x27, 0xdf, 0x25, 0x2d, 0x50, 0xe6, 0x35, 0xa3, 0x9a, 0x07, 0x26, 0x0c, 0x82, 0x81, 0x71, 0x4b, 0x66, 0x65, 0xa6, 0x8f, 0xe3, 0xa4, 0x10, 0x51, 0xfc, 0x35, 0x13, 0xe6, 0x00, 0x94, 0x78, 0xa5, 0x18, 0xc1, 0xdc, 0x5c, 0xe5, 0x9d, 0x25, 0x2d, 0x87, 0xdd, 0xbd, 0xe9, 0x58, 0x67, 0xc2, 0x7a, 0xa8, 0x06, 0xd9, 0xec, 0xa1, 0x34, 0xbc, 0xf8, 0x3f, 0x9d, 0x3e, 0x4f, 0x76, 0x00, 0x5b, 0xc7, 0x63, 0x29, 0x87, 0x19, 0x9c, 0x2f, 0xd2, 0xa4, 0x18, 0x75, 0x33, 0xc8, 0xca, 0xae, 0x21, 0x58, 0xc2, 0x8d, 0x50, 0xf8, 0xb4, 0x62, 0xe4, 0x27, 0x50, 0x20, 0x40, 0x4c, 0x62, 0x52, 0x0e, 0x18, 0x14, 0xff, 0xc4, 0x90, 0x28, 0xa0, 0x18, 0x17, 0x8d, 0x9a, 0xa4, 0xe0, 0x21, 0x74, 0xbb, 0x9c, 0x0e, 0x54, 0x70, 0xd7, 0x6c, 0xe2, 0x8c, 0x6a, 0x25, 0xb2, 0x31, 0xa2, 0x48, 0x72, 0xae, 0x12, 0x9d, 0x81, 0x66, 0x84, 0x83, 0x5d, 0xcc, 0x49, 0x53, 0xf1, 0xd5, 0x92, 0xcb, 0x2f, 0x98, 0xd5, 0xd9, 0x7c, 0xe7, 0x17, 0x0f, 0xc3, 0xb9, 0x28, 0x32, 0x6f, 0x60, 0x8f, 0x9f, 0xcf, 0xaa, 0xcb, 0x04, 0x91, 0xff, 0x4f, 0xb8, 0xeb, 0x37, 0xbf, 0x99, 0xfc, 0xfa, 0xd7, 0x45, 0x7d, 0x77, 0x98, 0x3a, 0x06, 0x5a, 0xe1, 0x5a, 0xb0, 0x28, 0xb7, 0x4a, 0x0e, 0xca, 0xf0, 0x18, 0x88, 0x1a, 0x60, 0x87, 0x23, 0x39, 0x1c, 0x0a, 0xe0, 0x6c, 0x13, 0x4e, 0xc4, 0xc6, 0x2b, 0xcb, 0xe2, 0xc3, 0x87, 0x1c, 0xee, 0x3a, 0xa3, 0x27, 0xcc, 0xf1, 0x99, 0x00, 0xcb, 0xdc, 0x21, 0x9c, 0xf0, 0xfa, 0x62, 0x7a, 0x8b, 0x5e, 0x61, 0x8b, 0x35, 0xf1, 0xe2, 0xbb, 0x17, 0x53, 0x37, 0x13, 0xaf, 0x63, 0x5f, 0x26, 0x57, 0x34, 0x94, 0x42, 0x94, 0xe1, 0xcc, 0x56, 0x42, 0x63, 0x5f, 0x3f, 0x78, 0x10, 0x39, 0x26, 0x51, 0x2d, 0x34, 0x38, 0x3f, 0x39, 0x78, 0xaf, 0x1a, 0xf9, 0x81, 0x18, 0x80, 0xdd, 0x4c, 0x14, 0xb1, 0xbe, 0x94, 0x5f, 0x31, 0x09, 0x86, 0xab, 0x42, 0x96, 0xab, 0x46, 0x0c, 0x72, 0xcd, 0x57, 0x37, 0xc3, 0xff, 0xe8, 0xdd, 0x3f, 0xff, 0xf9, 0xc2, 0x37, 0x71, 0x1c, 0xfc, 0xe9, 0xff, 0x22, 0x7f, 0x0e, 0x5a, 0xe1, 0x5c, 0x3b, 0x88, 0x30, 0xd1, 0x80, 0x30, 0x6a, 0xb2, 0x50, 0x1f, 0xb7, 0x01, 0x1a, 0x78, 0xa0, 0x1a, 0x42, 0xab, 0x18, 0x92, 0xa0, 0xa0, 0x26, 0x2f, 0x53, 0x40, 0x0d, 0x14, 0x05, 0xc5, 0x43, 0x0e, 0x40, 0x01, 0x1a, 0xae, 0x01, 0x0a, 0xf2, 0xc2, 0x86, 0x41, 0x38, 0xe8, 0x68, 0xc4, 0xe5, 0xac, 0x4f, 0x47, 0x1a, 0xa2, 0x0d, 0x7e, 0x9f, 0x33, 0xec, 0x0a, 0x1b, 0xa3, 0x71, 0x25, 0x76, 0x41, 0x49, 0xb5, 0x3c, 0x52, 0x62, 0xd9, 0xdf, 0x1c, 0x22, 0xfa, 0x56, 0x28, 0x5b, 0x57, 0x97, 0x3b, 0x9d, 0xbd, 0x3e, 0x20, 0xea, 0xd2, 0xda, 0xd5, 0x71, 0xa3, 0x45, 0xa5, 0xae, 0xed, 0x08, 0xe6, 0xfc, 0x06, 0x60, 0xf4, 0x25, 0xdd, 0x81, 0xc0, 0xe1, 0xd7, 0x4c, 0x2e, 0x85, 0xd6, 0x65, 0xb0, 0xda, 0xec, 0x77, 0xb0, 0xa2, 0x5b, 0x96, 0x0d, 0x66, 0x74, 0x56, 0x1f, 0xf5, 0xbf, 0x2b, 0xdb, 0xd8, 0x00, 0xe7, 0xd1, 0xca, 0x75, 0xc6, 0xb0, 0xd9, 0x5f, 0x63, 0x32, 0x45, 0xbd, 0x26, 0x03, 0x78, 0x6b, 0xe6, 0x76, 0xb3, 0x57, 0xa3, 0xb7, 0xe9, 0x59, 0x2b, 0xf9, 0xb5, 0xd9, 0x2b, 0x5a, 0x5a, 0x47, 0xd9, 0x20, 0x5c, 0x47, 0x57, 0xd1, 0x2e, 0xad, 0x63, 0x19, 0x59, 0x01, 0x52, 0x85, 0xcf, 0xb3, 0x88, 0xb2, 0xc1, 0x99, 0xdf, 0xce, 0x5e, 0x44, 0x40, 0x74, 0x91, 0xdf, 0x07, 0x8d, 0xb2, 0xab, 0x08, 0x1b, 0x91, 0x28, 0xd6, 0x28, 0xa5, 0x78, 0xe5, 0x2a, 0xc7, 0x17, 0xb1, 0x16, 0x5b, 0x83, 0x30, 0x5c, 0xa4, 0x8d, 0xb0, 0xb1, 0x5c, 0x34, 0x24, 0xe5, 0xb9, 0xcd, 0xc1, 0xc9, 0x84, 0xc7, 0x15, 0x1e, 0x1c, 0xd0, 0xc8, 0x86, 0x9a, 0xe3, 0xd6, 0x1a, 0x87, 0x8a, 0x31, 0x2a, 0x14, 0x9c, 0xce, 0xce, 0xed, 0x3a, 0x44, 0x7d, 0x72, 0x39, 0xdf, 0x56, 0xe7, 0x51, 0xa9, 0x1b, 0x59, 0x25, 0xe3, 0xf3, 0x05, 0xc9, 0xf7, 0x67, 0x2e, 0x2c, 0xe9, 0xa8, 0xe0, 0x7a, 0x78, 0x5e, 0x4b, 0x35, 0x40, 0x26, 0x87, 0xaa, 0xc3, 0x86, 0xcf, 0x8c, 0x0c, 0x06, 0xd7, 0x0b, 0x2f, 0x1f, 0xa4, 0xc9, 0x1d, 0xa4, 0x52, 0x98, 0x16, 0xfd, 0xda, 0x6f, 0x53, 0x59, 0xc8, 0x17, 0x32, 0xc4, 0x2b, 0x45, 0x1d, 0x24, 0x30, 0x44, 0x12, 0x1e, 0x33, 0x33, 0x3c, 0x67, 0x25, 0x03, 0xa3, 0x1f, 0x07, 0xaf, 0x93, 0x6b, 0x45, 0x40, 0x52, 0x54, 0xe3, 0x18, 0x52, 0x6c, 0x39, 0x0e, 0xe2, 0x11, 0xbf, 0x30, 0x25, 0x9e, 0x30, 0xcf, 0x9d, 0xdb, 0xaa, 0xef, 0xdc, 0xc6, 0x94, 0x78, 0xc1, 0xe7, 0x79, 0xe7, 0xe7, 0x79, 0x1d, 0x96, 0x33, 0x79, 0x2e, 0xc0, 0x05, 0x90, 0xa5, 0x16, 0xc9, 0x99, 0x9c, 0x98, 0x2b, 0x28, 0x95, 0x90, 0x3d, 0xd3, 0xe7, 0x2d, 0x95, 0x01, 0xa9, 0xf6, 0x7d, 0xff, 0xc8, 0xd9, 0x91, 0x4c, 0xb7, 0x1b, 0x1c, 0x13, 0xf5, 0x2b, 0x56, 0x5b, 0xd2, 0x43, 0x59, 0x57, 0xc2, 0x61, 0xc0, 0xfe, 0x6f, 0x43, 0x5f, 0xd9, 0xb9, 0x87, 0x7d, 0xe0, 0x53, 0xae, 0x50, 0x26, 0x56, 0x1f, 0xdf, 0xb4, 0x16, 0x79, 0xc1, 0x0b, 0x5e, 0x95, 0xba, 0x95, 0x55, 0xc9, 0x56, 0x80, 0xe2, 0x78, 0x32, 0x81, 0x9c, 0x94, 0xe9, 0x1a, 0x8b, 0x49, 0xb8, 0x5a, 0x14, 0xb0, 0x10, 0x1d, 0xfd, 0x3d, 0xf6, 0x49, 0xf2, 0xc8, 0x17, 0xee, 0x83, 0x84, 0x5b, 0x49, 0x61, 0x3b, 0x0e, 0x89, 0xa1, 0x2b, 0x64, 0x40, 0x32, 0xff, 0x95, 0xbd, 0x93, 0x78, 0xad, 0x78, 0x82, 0xc7, 0x0e, 0xc9, 0x00, 0x2b, 0x39, 0x24, 0x73, 0x9f, 0xe5, 0x8e, 0x3c, 0xec, 0x68, 0x8e, 0xcd, 0xeb, 0x8b, 0x5c, 0xf4, 0xa6, 0x23, 0x78, 0x56, 0x57, 0x24, 0x01, 0x4e, 0x3f, 0x41, 0xbe, 0x2d, 0xc5, 0x33, 0xdc, 0x55, 0x54, 0xa1, 0x58, 0x06, 0x07, 0x2e, 0x63, 0x2d, 0xae, 0xb9, 0xb3, 0x52, 0x9e, 0x1a, 0x92, 0x01, 0x4a, 0x44, 0x76, 0xc1, 0x01, 0x08, 0x64, 0x69, 0xb1, 0xcf, 0x7a, 0xcb, 0xb6, 0x72, 0x8e, 0xf9, 0xa7, 0xbe, 0xe5, 0x53, 0x5f, 0x80, 0x63, 0x18, 0xfc, 0xd1, 0x48, 0xc0, 0x3c, 0x3b, 0x86, 0xa1, 0xbc, 0x70, 0x67, 0x2c, 0xac, 0x18, 0xc3, 0x90, 0x15, 0xd9, 0x4c, 0xc4, 0x9d, 0xb3, 0x96, 0x56, 0x92, 0x36, 0xe0, 0x95, 0x1c, 0x75, 0x28, 0xca, 0x31, 0x0c, 0xc8, 0xa1, 0x8c, 0x02, 0x17, 0xee, 0xab, 0x5e, 0x45, 0xc1, 0x56, 0x8a, 0xf3, 0x78, 0x9b, 0x7c, 0x1a, 0xcf, 0x4b, 0xb6, 0x58, 0x67, 0x01, 0xa5, 0x32, 0x6c, 0x04, 0x76, 0x88, 0xac, 0x2d, 0xd7, 0xd5, 0x9d, 0x1c, 0xaa, 0xf6, 0x8d, 0x58, 0x82, 0x3c, 0x37, 0x5f, 0x79, 0x6b, 0x71, 0x87, 0x79, 0xcf, 0xac, 0x70, 0xfd, 0xb4, 0x3d, 0xe1, 0xca, 0x0e, 0xa5, 0x2d, 0xe5, 0x00, 0x0b, 0xa5, 0x14, 0x80, 0xd1, 0x67, 0x30, 0x28, 0x54, 0x06, 0x9a, 0xfc, 0x50, 0xad, 0xf2, 0x16, 0x86, 0x93, 0x20, 0x5d, 0x0e, 0xb1, 0x08, 0x4a, 0xbd, 0x7f, 0x61, 0x85, 0x4c, 0xc5, 0x96, 0x79, 0x07, 0xe9, 0x81, 0xfc, 0x00, 0xc7, 0xcf, 0xb2, 0x32, 0x1c, 0xd3, 0x8a, 0xdd, 0xbe, 0xe3, 0xa2, 0x0b, 0xb2, 0xc4, 0x9f, 0x59, 0x5b, 0x55, 0x94, 0x3c, 0x65, 0x31, 0xb9, 0x81, 0x14, 0xbc, 0x8f, 0xc0, 0x61, 0x48, 0x4f, 0x8f, 0xc1, 0xa4, 0x7a, 0x87, 0xdc, 0xd3, 0xc3, 0xea, 0xd5, 0xbf, 0x27, 0x49, 0xe6, 0x8e, 0x99, 0xbf, 0xaa, 0x34, 0xc0, 0xc9, 0x7a, 0xb4, 0x1f, 0x92, 0x4a, 0x85, 0x56, 0xf8, 0x1f, 0x96, 0xd7, 0xbd, 0x8f, 0x73, 0xa7, 0xd2, 0x50, 0x4e, 0x6e, 0x84, 0x3a, 0x03, 0x0d, 0x39, 0x90, 0x17, 0x45, 0xad, 0x41, 0x76, 0x80, 0x21, 0x1f, 0x50, 0x12, 0x0f, 0x49, 0xc3, 0x63, 0x89, 0x83, 0x3b, 0x2a, 0xbe, 0x5b, 0xad, 0x06, 0xfe, 0x4d, 0x58, 0xcd, 0x1a, 0xaf, 0xd6, 0xcb, 0xa8, 0x19, 0x35, 0x14, 0x96, 0x69, 0x82, 0xe6, 0x51, 0x35, 0x43, 0x0e, 0x2e, 0x5b, 0x41, 0xb4, 0xd2, 0xca, 0x0b, 0x73, 0xf2, 0xe1, 0x9f, 0xd4, 0x9c, 0xb7, 0x5b, 0x03, 0x14, 0xad, 0x0d, 0x8d, 0x6d, 0x8a, 0x03, 0x28, 0x24, 0x42, 0x8c, 0x61, 0xe8, 0x0e, 0x87, 0xbb, 0xd7, 0xd1, 0x17, 0x5e, 0x77, 0xdd, 0x60, 0x47, 0x57, 0x57, 0xc7, 0x2f, 0x51, 0x7c, 0x44, 0xdb, 0x64, 0x6f, 0x38, 0x2c, 0x7e, 0x86, 0xc4, 0xdc, 0xfe, 0x14, 0xf9, 0x07, 0x4a, 0x2f, 0xdb, 0x45, 0xc8, 0xa0, 0x34, 0x1f, 0x41, 0x32, 0x0c, 0xa1, 0x24, 0x64, 0x4a, 0xd9, 0x6a, 0xf1, 0xdc, 0x89, 0x3d, 0x94, 0x57, 0x87, 0x20, 0x46, 0xc2, 0x36, 0x16, 0x4e, 0x90, 0x89, 0x35, 0x89, 0x26, 0x22, 0x31, 0x24, 0x3d, 0x20, 0xfa, 0xe7, 0x38, 0x51, 0x6d, 0xad, 0xf2, 0xde, 0xe5, 0x32, 0xe0, 0xad, 0x05, 0x2b, 0x56, 0x2c, 0xb8, 0x1c, 0xff, 0xd3, 0x29, 0xc6, 0x19, 0x77, 0xc2, 0x3f, 0x16, 0x25, 0x93, 0x8b, 0xe0, 0x1f, 0xd4, 0x9f, 0xc8, 0xd1, 0x91, 0x91, 0x65, 0x24, 0xb9, 0x6c, 0x64, 0x64, 0x94, 0x04, 0x8f, 0x83, 0x70, 0xcf, 0x86, 0xd6, 0xb6, 0x0d, 0xbd, 0x61, 0xe4, 0xad, 0x0b, 0xf7, 0x6e, 0x68, 0x6b, 0xdd, 0xd0, 0x13, 0x06, 0xaf, 0xe1, 0xf5, 0x4b, 0x42, 0x1e, 0xfc, 0x34, 0x9c, 0xcf, 0x3e, 0xa2, 0xbf, 0xd8, 0xd3, 0x07, 0x80, 0x02, 0xe5, 0xde, 0xca, 0x03, 0x38, 0x90, 0x0e, 0x5e, 0x87, 0x42, 0x8f, 0x0c, 0x81, 0xd2, 0x13, 0x0c, 0xc5, 0xac, 0x25, 0xa4, 0x42, 0x03, 0x93, 0x43, 0xa2, 0xb5, 0x57, 0xaa, 0x32, 0x09, 0xf5, 0x90, 0x24, 0xd4, 0xb2, 0xfd, 0xa6, 0x64, 0x80, 0xc7, 0x7a, 0xb6, 0xec, 0x0c, 0x67, 0xea, 0xa7, 0x2a, 0xdc, 0xc8, 0xa8, 0x4b, 0x97, 0x75, 0xaf, 0x2f, 0xa8, 0x58, 0x95, 0xd9, 0xe9, 0xb7, 0xf8, 0x43, 0xa1, 0x42, 0xb1, 0x10, 0x72, 0xe6, 0x17, 0x65, 0xc3, 0x41, 0x93, 0x43, 0xef, 0x54, 0x29, 0xcd, 0x8c, 0xca, 0x91, 0xb2, 0xc6, 0x5a, 0x42, 0x2c, 0xcf, 0x18, 0x74, 0x1a, 0xb3, 0x23, 0xe8, 0x8c, 0x05, 0x42, 0x0d, 0xc5, 0x86, 0x90, 0x3d, 0x33, 0x58, 0x9f, 0x9f, 0x70, 0x33, 0x29, 0x83, 0x4a, 0xad, 0x72, 0x58, 0xcd, 0x56, 0xce, 0xc4, 0x67, 0x63, 0x08, 0x95, 0x43, 0x69, 0xe1, 0x03, 0x72, 0x8d, 0xa9, 0x56, 0xad, 0xf2, 0xd4, 0xb5, 0xfb, 0xda, 0xf3, 0x4a, 0xb5, 0x56, 0xaf, 0xe1, 0x4d, 0x16, 0xa8, 0xb4, 0x07, 0x9a, 0x52, 0x7c, 0x53, 0xd2, 0x11, 0x72, 0x23, 0x5a, 0xf4, 0x24, 0x94, 0x75, 0xcf, 0x97, 0xed, 0x85, 0x5a, 0x37, 0xe4, 0x9f, 0x4e, 0xc8, 0x36, 0x15, 0x58, 0xfb, 0x42, 0x71, 0x5d, 0x00, 0xe5, 0x4d, 0xcd, 0x09, 0x1e, 0x89, 0x46, 0x24, 0x68, 0xa9, 0x79, 0x60, 0xa6, 0xe7, 0x62, 0xdd, 0xbf, 0x61, 0x4e, 0x0e, 0x64, 0x5d, 0x69, 0x87, 0x5e, 0x64, 0xa5, 0x01, 0xde, 0x5c, 0xdb, 0x2f, 0x7e, 0x67, 0x51, 0x0d, 0xe0, 0x00, 0x4f, 0x3d, 0x52, 0xa2, 0x02, 0x4a, 0x26, 0x1d, 0x25, 0x55, 0xc9, 0x05, 0x12, 0x4d, 0x60, 0x52, 0x51, 0xd4, 0x9a, 0x1f, 0xf2, 0xf6, 0xeb, 0x64, 0x0d, 0x44, 0x8a, 0xb8, 0xbc, 0xa8, 0xf3, 0x42, 0xc5, 0xd0, 0x04, 0x48, 0x39, 0xe2, 0xf1, 0x25, 0x0c, 0xe1, 0x88, 0x58, 0x28, 0x9c, 0x94, 0x03, 0xb9, 0x68, 0x15, 0x81, 0xff, 0xad, 0x2b, 0xd7, 0x0b, 0x9f, 0x1c, 0x52, 0x2a, 0x48, 0xb8, 0x4c, 0x72, 0x9c, 0xcb, 0xf9, 0x19, 0x77, 0x02, 0x54, 0x15, 0x82, 0xc0, 0x77, 0x8f, 0x17, 0x59, 0xd1, 0xdb, 0x1a, 0x60, 0xd9, 0x08, 0x17, 0x8c, 0x86, 0x54, 0xa8, 0xbe, 0xd5, 0x3c, 0x02, 0x83, 0xac, 0x02, 0xa9, 0xde, 0x0e, 0xf2, 0xcd, 0x38, 0xe5, 0x50, 0xc2, 0xc6, 0x05, 0xd7, 0xb1, 0xa1, 0x96, 0x98, 0x35, 0xe6, 0x50, 0xd3, 0xa8, 0xe0, 0xb1, 0xd6, 0x6a, 0xd6, 0x9b, 0x5a, 0xfa, 0xea, 0xd4, 0xfa, 0xa5, 0x2e, 0xc6, 0xde, 0x7f, 0xcd, 0x35, 0x66, 0xce, 0xcc, 0xaa, 0x68, 0xf5, 0x8d, 0x3e, 0x28, 0x58, 0x28, 0xa1, 0x60, 0xa1, 0x92, 0xb9, 0x03, 0x3c, 0xbd, 0xa8, 0x07, 0x58, 0xf4, 0xba, 0x06, 0xc3, 0x55, 0xd6, 0x3b, 0xa6, 0x84, 0x3f, 0x9b, 0xed, 0x3a, 0xa9, 0x56, 0x03, 0x3c, 0xfb, 0x9b, 0x20, 0x3f, 0x53, 0x10, 0x01, 0xa2, 0xa3, 0xd8, 0xa6, 0x80, 0xab, 0xa1, 0xc7, 0xc6, 0xac, 0xea, 0xda, 0xe2, 0x88, 0x1c, 0xca, 0xc7, 0x51, 0x85, 0xf1, 0x49, 0x6c, 0xe0, 0x10, 0xe3, 0xbb, 0xa8, 0xe1, 0x80, 0x9f, 0xf7, 0x61, 0x4b, 0x67, 0xb9, 0xb4, 0x78, 0x40, 0xdc, 0x94, 0xed, 0x48, 0x4c, 0x2d, 0x87, 0x53, 0x97, 0xb5, 0x15, 0x2c, 0xb4, 0x7e, 0xe9, 0x69, 0xac, 0x2f, 0xfc, 0x5b, 0x1b, 0x0a, 0xb9, 0x91, 0x68, 0x7b, 0x2e, 0x77, 0xe8, 0xe9, 0xc3, 0x6d, 0xf4, 0x1e, 0x3f, 0x54, 0x1b, 0x2c, 0x43, 0x6f, 0xdb, 0x83, 0xde, 0x30, 0x52, 0x24, 0xfc, 0x6b, 0xbf, 0x07, 0xff, 0x87, 0xcf, 0x54, 0x86, 0x3a, 0x4a, 0x8e, 0x42, 0x9a, 0x98, 0x44, 0x91, 0x26, 0x61, 0x8d, 0x02, 0x76, 0xd1, 0xcd, 0xe2, 0x0a, 0x9f, 0xe5, 0x7c, 0x2e, 0x72, 0x72, 0xa8, 0x94, 0x33, 0x41, 0x41, 0x19, 0x09, 0x10, 0x08, 0xee, 0xde, 0x66, 0x31, 0xa0, 0x2c, 0xe0, 0x24, 0x48, 0xca, 0xcb, 0xa5, 0xea, 0xf2, 0x05, 0x19, 0x8e, 0xd6, 0x80, 0xb3, 0xaa, 0x93, 0xec, 0x89, 0x88, 0x36, 0x40, 0x35, 0xd0, 0x02, 0x49, 0xa8, 0x3f, 0x22, 0x2a, 0x8a, 0xa0, 0xbb, 0x61, 0x5d, 0x6f, 0xa4, 0xbb, 0x29, 0xc2, 0x77, 0xda, 0x4c, 0x2f, 0x86, 0x1a, 0x06, 0x0b, 0x85, 0xa1, 0xc6, 0xd0, 0x8b, 0x26, 0x5b, 0x67, 0x20, 0xd4, 0xd8, 0x1b, 0xe9, 0x5d, 0x47, 0x1d, 0x45, 0x69, 0x79, 0x5d, 0x9b, 0x6d, 0xb2, 0x1e, 0xa3, 0xf5, 0x56, 0x59, 0x5f, 0x43, 0x7b, 0x4f, 0x4f, 0x7b, 0x43, 0x9f, 0xec, 0x56, 0xab, 0xb1, 0x47, 0x66, 0xdb, 0xdc, 0x55, 0xbf, 0xb4, 0xd5, 0x8f, 0xfb, 0xbe, 0x92, 0xba, 0x9e, 0xf4, 0x62, 0xdd, 0x4b, 0xcc, 0x87, 0x00, 0x62, 0x3d, 0xcf, 0x7f, 0x28, 0x1f, 0xa2, 0xa5, 0xb6, 0xfe, 0x4a, 0x6f, 0xf3, 0xb2, 0x5c, 0x6e, 0x69, 0xb3, 0xf7, 0xca, 0x7a, 0x9a, 0x84, 0xf4, 0xaa, 0x5e, 0xf8, 0x41, 0x7a, 0x49, 0x13, 0xcf, 0x37, 0x2d, 0x49, 0xc3, 0xbf, 0x7f, 0x98, 0xc1, 0x79, 0x79, 0x5d, 0xb0, 0x2d, 0x0f, 0xe4, 0x73, 0x75, 0x28, 0xba, 0x5e, 0x89, 0xbd, 0xe4, 0xa5, 0x9a, 0xe2, 0x93, 0xf3, 0xe8, 0xfb, 0x95, 0xab, 0xdb, 0xe6, 0xd1, 0xed, 0xcf, 0xf6, 0xec, 0xd9, 0x1e, 0x43, 0xe1, 0x48, 0xd1, 0x9a, 0x48, 0x14, 0x3b, 0xd7, 0x67, 0x43, 0xd9, 0xa1, 0xea, 0xe2, 0x61, 0x5c, 0x5c, 0xdc, 0x82, 0x6b, 0x8b, 0x5b, 0xe6, 0x96, 0x16, 0x37, 0xdb, 0xa3, 0xae, 0x5c, 0x57, 0x58, 0x97, 0x18, 0x9a, 0x6a, 0x5c, 0xb9, 0x45, 0x09, 0x80, 0x7a, 0x0b, 0x67, 0x24, 0x65, 0x8a, 0x5a, 0xde, 0x9a, 0xe0, 0x4d, 0x4a, 0xbd, 0x49, 0xad, 0x85, 0xdc, 0xce, 0x48, 0x32, 0xb7, 0x28, 0xe4, 0xc1, 0xe6, 0xc1, 0xd0, 0x82, 0x4b, 0x46, 0x93, 0xbb, 0x76, 0x84, 0x27, 0xf2, 0x85, 0x4e, 0x8b, 0x3e, 0x93, 0xaa, 0x5f, 0xe6, 0xed, 0xc8, 0xd9, 0xb3, 0x8b, 0x1b, 0x42, 0x71, 0x33, 0x43, 0x21, 0x4e, 0x0a, 0x08, 0x39, 0xe4, 0xa3, 0x6e, 0xac, 0xdf, 0x95, 0xb1, 0x45, 0x70, 0x34, 0x0f, 0x06, 0x18, 0x11, 0x03, 0x79, 0x58, 0xc2, 0x20, 0x49, 0xd1, 0x38, 0x1f, 0x04, 0x92, 0x52, 0x94, 0x10, 0xd2, 0x8e, 0xf3, 0xbd, 0xdc, 0xdf, 0xdf, 0xff, 0xda, 0x6e, 0xbd, 0x19, 0x8a, 0x72, 0x5a, 0x46, 0xcd, 0x4e, 0xd0, 0x9c, 0x0e, 0xf1, 0x50, 0x52, 0xa9, 0x53, 0x7f, 0xe2, 0xc8, 0x3a, 0xb2, 0xd9, 0x4f, 0xd4, 0x12, 0xce, 0x42, 0x9c, 0x7c, 0x8d, 0x6c, 0x86, 0xed, 0xf0, 0xc8, 0x33, 0x20, 0x16, 0xb3, 0x2e, 0xc9, 0x15, 0x95, 0xc8, 0xba, 0x52, 0xb2, 0x3c, 0x18, 0xe6, 0x4c, 0xa8, 0x26, 0x75, 0x95, 0x54, 0x21, 0xd5, 0xa4, 0xe6, 0x02, 0x22, 0xba, 0x4a, 0xa6, 0x52, 0x92, 0x9a, 0x6c, 0x76, 0xc0, 0x83, 0xde, 0x1c, 0x62, 0x85, 0x5f, 0x2b, 0x95, 0xbf, 0xff, 0x23, 0xb8, 0xde, 0xf8, 0xce, 0xfb, 0x46, 0x87, 0xce, 0xa4, 0x50, 0xb0, 0x34, 0xf9, 0x0b, 0x91, 0xfe, 0x3a, 0x9a, 0x95, 0x2e, 0x35, 0xc8, 0x6c, 0x6b, 0xf2, 0x99, 0x84, 0x1f, 0xdf, 0xe8, 0xf3, 0x31, 0x4a, 0x16, 0x9e, 0xeb, 0x38, 0x95, 0x25, 0x57, 0xc3, 0x3d, 0xa7, 0x24, 0xd2, 0xc8, 0x0e, 0x98, 0xc0, 0x08, 0xe9, 0x04, 0x08, 0x62, 0xdb, 0xa6, 0x77, 0x9e, 0x9a, 0xd4, 0x93, 0x92, 0xe9, 0x52, 0xac, 0x4c, 0x0d, 0x0f, 0xb7, 0x4a, 0x05, 0x08, 0x55, 0x5a, 0x95, 0x46, 0x88, 0xbf, 0x01, 0x3f, 0x3a, 0x47, 0x72, 0x86, 0x50, 0x02, 0xa5, 0xaa, 0xca, 0x0e, 0x28, 0x13, 0x4b, 0x52, 0x5b, 0xe6, 0x96, 0x1e, 0x0a, 0x54, 0xc6, 0x23, 0x93, 0x53, 0xe0, 0xbe, 0x58, 0x1d, 0xcb, 0x71, 0x2e, 0xcd, 0x46, 0xad, 0x2f, 0x52, 0xeb, 0xec, 0x6e, 0x47, 0xb6, 0x3e, 0x2f, 0x05, 0xd9, 0xa1, 0xc5, 0x92, 0x27, 0x65, 0xd4, 0xc7, 0x70, 0x6c, 0xef, 0x81, 0xa3, 0x2a, 0xc5, 0x3b, 0xd4, 0x69, 0xa7, 0x4d, 0xc7, 0x45, 0x92, 0xe9, 0x62, 0x48, 0xdf, 0x9f, 0xf5, 0xe6, 0xa3, 0x56, 0x03, 0x8f, 0x4d, 0x7b, 0xd6, 0x1a, 0x53, 0x69, 0x98, 0x7a, 0xb7, 0x42, 0xf8, 0x31, 0x96, 0x75, 0xff, 0x46, 0xbe, 0x0d, 0xbe, 0x05, 0xc7, 0x18, 0x22, 0xa6, 0x8a, 0x2a, 0x3f, 0xa0, 0x51, 0x36, 0x0d, 0x0e, 0xbb, 0x53, 0xe3, 0x42, 0xe2, 0x14, 0x0d, 0xc5, 0xf3, 0xcd, 0x38, 0xeb, 0x1b, 0x79, 0xab, 0x11, 0xb5, 0x5b, 0x5b, 0x02, 0xc4, 0xc7, 0x11, 0xb7, 0xbc, 0x78, 0x0b, 0xa2, 0xd9, 0x67, 0xb9, 0x07, 0x65, 0x0b, 0x9b, 0x82, 0x41, 0x53, 0xa4, 0x8c, 0x8e, 0x53, 0xc2, 0x65, 0x9c, 0x65, 0x3f, 0xca, 0x42, 0x7a, 0x57, 0xaa, 0xbe, 0xbd, 0x66, 0xa5, 0xbf, 0x73, 0xb2, 0xa3, 0x73, 0xdc, 0xe3, 0x53, 0x5b, 0xb5, 0x66, 0x47, 0xc0, 0xd1, 0xd2, 0x59, 0xe8, 0x58, 0xcb, 0x5d, 0xff, 0x36, 0xb8, 0xda, 0xa0, 0xa6, 0x8f, 0xe8, 0x36, 0x0e, 0xa7, 0x47, 0x3b, 0xc2, 0x90, 0x07, 0x73, 0x6a, 0x8d, 0xba, 0xd8, 0x9c, 0x69, 0x4b, 0x98, 0x0d, 0x5f, 0xdd, 0xd6, 0x64, 0xf4, 0xaa, 0xb1, 0x9c, 0xd3, 0x01, 0xe9, 0xf1, 0x90, 0xec, 0x22, 0x42, 0x05, 0xb7, 0x66, 0x08, 0xd1, 0x3a, 0x84, 0x5a, 0x04, 0x35, 0x77, 0x72, 0xb2, 0x4a, 0x28, 0xab, 0x8a, 0xa2, 0x33, 0x22, 0x60, 0x2b, 0xb7, 0xd3, 0x18, 0xe2, 0x42, 0x6a, 0x56, 0xcd, 0xea, 0xb5, 0xf0, 0x49, 0x55, 0x10, 0x4b, 0x62, 0x62, 0xfc, 0x45, 0x21, 0x27, 0x0a, 0x63, 0x73, 0xc1, 0x89, 0xfe, 0x8d, 0xe4, 0xa0, 0x2a, 0xee, 0x4a, 0xf0, 0x1c, 0x29, 0xef, 0xc8, 0x37, 0x14, 0xe5, 0x55, 0x12, 0x59, 0x4f, 0x38, 0xdc, 0xb3, 0x8e, 0x7e, 0xa3, 0x75, 0x2c, 0x6f, 0xb7, 0xe7, 0xc7, 0x5a, 0xd7, 0xed, 0xda, 0xb0, 0x61, 0x17, 0x68, 0x4f, 0xaf, 0xb9, 0x6e, 0xd5, 0xea, 0xeb, 0xd6, 0xa6, 0xd3, 0x6b, 0xaf, 0x5b, 0xbd, 0xea, 0xba, 0x35, 0x69, 0xbc, 0xf7, 0xb7, 0xc0, 0x33, 0x96, 0x87, 0xfd, 0x0d, 0x11, 0x77, 0x20, 0x7c, 0x7a, 0x06, 0x65, 0xd9, 0x13, 0x6c, 0x15, 0x8c, 0x8c, 0x5d, 0xc4, 0x68, 0x80, 0x02, 0x22, 0xca, 0x0e, 0x9f, 0x2c, 0xa1, 0xa5, 0x55, 0x34, 0x8e, 0xb3, 0xdc, 0x50, 0xd1, 0x37, 0x3e, 0xe5, 0x0d, 0x9f, 0xf2, 0x30, 0xd6, 0x35, 0xa2, 0x21, 0xee, 0xac, 0xba, 0x46, 0xe0, 0x0c, 0x6f, 0xd1, 0x9f, 0xb0, 0x41, 0x2b, 0x69, 0x3d, 0x12, 0xc1, 0xe2, 0xbb, 0x35, 0x3d, 0x98, 0x29, 0x07, 0x4d, 0x1b, 0xfa, 0x98, 0xad, 0x4e, 0xdf, 0x2c, 0x3d, 0xe3, 0x08, 0x69, 0x4d, 0x0e, 0xe7, 0x25, 0x19, 0x63, 0x05, 0xce, 0x9d, 0x3b, 0x04, 0x56, 0x62, 0x3a, 0xbf, 0xa2, 0xa8, 0x86, 0xf2, 0x04, 0xad, 0x96, 0x91, 0xe2, 0x86, 0x44, 0x9e, 0x04, 0x3b, 0x12, 0xe1, 0x45, 0x9c, 0x9c, 0x52, 0x68, 0x27, 0xd2, 0xe6, 0xcf, 0x15, 0x93, 0x80, 0x2b, 0x97, 0xb0, 0x2f, 0x01, 0x5f, 0x01, 0x12, 0x86, 0x0e, 0x14, 0xf5, 0xf9, 0x40, 0x88, 0x97, 0x4c, 0x71, 0x7e, 0xa9, 0x4a, 0x38, 0xde, 0x84, 0xa2, 0xbe, 0x08, 0x56, 0x2e, 0x36, 0x1b, 0x68, 0xb9, 0xbd, 0x98, 0x08, 0x5a, 0x94, 0x50, 0x28, 0xf0, 0x6a, 0xc3, 0x49, 0x03, 0x72, 0x4f, 0xeb, 0x4d, 0x99, 0x64, 0x86, 0xdd, 0x6a, 0x64, 0x62, 0x21, 0xd0, 0x22, 0xd6, 0x37, 0x81, 0x72, 0xcf, 0xbb, 0x90, 0x56, 0x05, 0x10, 0x3e, 0xb2, 0x15, 0xf6, 0x51, 0xa7, 0x24, 0x51, 0xc0, 0x3b, 0x21, 0x45, 0x86, 0xcd, 0xed, 0x01, 0xc7, 0xf1, 0x81, 0xc8, 0x6c, 0x62, 0x35, 0xa7, 0xf1, 0x2a, 0xc8, 0x7e, 0xf0, 0xae, 0x23, 0xe9, 0x8d, 0xb5, 0xd6, 0x70, 0xd6, 0xe6, 0x58, 0xd8, 0x54, 0xee, 0xc9, 0x61, 0xb3, 0x15, 0x93, 0x2c, 0x06, 0x92, 0x2c, 0xa5, 0xa7, 0xae, 0x8d, 0x3f, 0x27, 0x5e, 0x67, 0x90, 0x3a, 0x35, 0xf3, 0x1f, 0x01, 0xb7, 0xa8, 0xf8, 0x90, 0x28, 0xa7, 0x9c, 0x34, 0x63, 0x59, 0xc4, 0x47, 0xe4, 0x8b, 0x19, 0xcd, 0x19, 0xe8, 0xb9, 0x12, 0xdf, 0x94, 0x95, 0x0c, 0xf5, 0x3e, 0x0f, 0xb6, 0x10, 0x72, 0xf3, 0x64, 0x45, 0x16, 0x20, 0x31, 0x12, 0xcf, 0x2b, 0x46, 0x98, 0xc6, 0x32, 0x09, 0x64, 0xf8, 0x80, 0x4d, 0x44, 0x43, 0xb5, 0xf1, 0x57, 0xe5, 0x66, 0x6d, 0x3a, 0x63, 0xcd, 0x2c, 0x6e, 0x7a, 0xe4, 0xa0, 0x43, 0xa7, 0x65, 0x96, 0x2a, 0xf9, 0x0b, 0xa9, 0x4f, 0x02, 0x6e, 0x4f, 0xfc, 0xd0, 0x5f, 0x0d, 0x66, 0xb9, 0x79, 0x61, 0x3d, 0xdf, 0x14, 0xb7, 0x93, 0xca, 0xa7, 0xe4, 0xea, 0x23, 0x76, 0x3c, 0x6f, 0x2f, 0x53, 0xdf, 0x27, 0x8d, 0xb2, 0x41, 0x48, 0x4f, 0x6d, 0x45, 0x33, 0x32, 0xd0, 0x11, 0x28, 0x75, 0x76, 0x0b, 0x20, 0x14, 0x32, 0x44, 0x14, 0x29, 0xdc, 0xb6, 0x45, 0x04, 0x93, 0xe1, 0xc0, 0xbf, 0x46, 0x82, 0x86, 0xa7, 0x65, 0x36, 0x6e, 0xf4, 0x0a, 0xd9, 0x60, 0x3e, 0x2c, 0xd0, 0x5a, 0x1b, 0xb9, 0x5f, 0x94, 0xb7, 0xe0, 0x7b, 0xa8, 0x3b, 0xf0, 0x7b, 0x38, 0x22, 0x50, 0xf4, 0x31, 0x24, 0x8d, 0x5c, 0x10, 0x08, 0xc8, 0x04, 0x6c, 0x51, 0x43, 0x8a, 0x6b, 0x34, 0xa8, 0x38, 0x35, 0x27, 0xbe, 0x54, 0x36, 0xeb, 0xa5, 0xfc, 0x7c, 0xaf, 0x27, 0xb5, 0xf3, 0xb4, 0x54, 0x69, 0x11, 0x9d, 0xcf, 0xa7, 0xc1, 0x3a, 0x19, 0xa2, 0x26, 0xb2, 0xaf, 0xb3, 0x1a, 0x90, 0x46, 0x54, 0x0c, 0x6e, 0x78, 0xc8, 0xe5, 0x3c, 0x40, 0x0f, 0x6a, 0xc9, 0x7c, 0x01, 0xc4, 0x4d, 0x1a, 0x9d, 0xcf, 0xa4, 0x09, 0xd9, 0x95, 0x34, 0xdb, 0xd2, 0xb9, 0xc8, 0xe6, 0x49, 0x30, 0xff, 0xa5, 0x08, 0x5a, 0xf3, 0xbb, 0x36, 0x8f, 0x7b, 0x54, 0x1e, 0x75, 0xb8, 0xf1, 0x86, 0x2f, 0xde, 0x89, 0x19, 0xe7, 0x7a, 0x7a, 0x0b, 0x48, 0x33, 0xbf, 0x16, 0x71, 0x11, 0x6d, 0x52, 0x70, 0x38, 0xdc, 0x3d, 0x6b, 0x4b, 0xbe, 0xef, 0x8d, 0x84, 0x14, 0x1c, 0x2e, 0x16, 0x9a, 0x46, 0xc9, 0x77, 0xcb, 0x25, 0xd7, 0x18, 0x20, 0x47, 0xc6, 0x9f, 0x82, 0xea, 0x98, 0x98, 0x39, 0x2a, 0xa6, 0xf7, 0x98, 0x4b, 0xc6, 0x42, 0x90, 0xae, 0xcd, 0x66, 0x6b, 0x63, 0x43, 0x8d, 0x3c, 0xdf, 0x38, 0x14, 0x63, 0xae, 0xcd, 0x27, 0xe2, 0xf9, 0x55, 0xae, 0xba, 0xde, 0x58, 0xbc, 0x37, 0xe3, 0x12, 0x79, 0xec, 0x3a, 0xd8, 0x76, 0x6a, 0x76, 0xdb, 0x88, 0x5c, 0x52, 0x6b, 0x4b, 0xae, 0xb7, 0x8d, 0x00, 0xb7, 0x7d, 0xb6, 0xa0, 0xbf, 0x72, 0xdb, 0x5c, 0x35, 0x68, 0x07, 0x24, 0x87, 0xeb, 0xaa, 0x9b, 0xa6, 0x5f, 0xcd, 0xc7, 0x13, 0xf9, 0x35, 0xae, 0x4c, 0x6f, 0x3c, 0xd6, 0x5b, 0xe7, 0x42, 0x31, 0x51, 0x90, 0xbe, 0x11, 0xb2, 0x0b, 0xa1, 0x5e, 0x9c, 0x2c, 0xc6, 0x0c, 0xda, 0xd9, 0xb5, 0xee, 0x01, 0xd8, 0x45, 0xa0, 0x1d, 0xb9, 0x5c, 0x0a, 0x9b, 0x23, 0xc8, 0x91, 0x40, 0xd4, 0x26, 0x2a, 0x31, 0x95, 0xcc, 0x1a, 0xb3, 0x58, 0xee, 0xbe, 0x94, 0x41, 0x13, 0xb0, 0xe4, 0x1a, 0x1a, 0xec, 0xa8, 0x26, 0xd1, 0x9a, 0x55, 0x87, 0xb3, 0xfd, 0x09, 0x53, 0x77, 0xd1, 0x13, 0xe7, 0x1d, 0x1a, 0x56, 0x21, 0xf3, 0xa2, 0x84, 0x24, 0x14, 0x6a, 0xb8, 0xf4, 0x5a, 0xdf, 0x07, 0xa0, 0x20, 0xf7, 0xa7, 0x9a, 0x7c, 0xfd, 0xbd, 0x3a, 0x83, 0x4e, 0xf4, 0x1d, 0x34, 0xc3, 0xbe, 0xd8, 0x71, 0x5f, 0xa0, 0xfe, 0xab, 0x85, 0x74, 0xd6, 0x3d, 0xa7, 0xc6, 0x3d, 0x45, 0x9d, 0xd1, 0x1f, 0x5b, 0x20, 0x8c, 0xa3, 0xf1, 0xb9, 0x4a, 0x24, 0x78, 0xb9, 0x68, 0x8d, 0x4c, 0x2a, 0x73, 0xff, 0x97, 0x89, 0x75, 0x35, 0x7d, 0xeb, 0x61, 0x9f, 0x1a, 0x72, 0x96, 0xa8, 0x82, 0xd5, 0x38, 0xf8, 0xb8, 0xa7, 0xd8, 0x6d, 0x4a, 0xf6, 0x65, 0x0f, 0xd3, 0x57, 0xfb, 0xae, 0x5d, 0xda, 0xb2, 0xaa, 0xcd, 0xa7, 0x30, 0xf9, 0x1d, 0x19, 0x2d, 0xec, 0x4a, 0x6f, 0xbf, 0xaf, 0x29, 0xe5, 0x97, 0x0b, 0xdf, 0x15, 0xd7, 0x65, 0x05, 0x95, 0xa0, 0x56, 0xc0, 0x33, 0x5b, 0x85, 0xb7, 0x84, 0x13, 0xea, 0xf7, 0x88, 0xa6, 0x64, 0x36, 0x29, 0xa2, 0x35, 0xcd, 0x83, 0xb7, 0xb4, 0xe2, 0x0a, 0xa3, 0x43, 0x71, 0xa7, 0xce, 0x0c, 0xf5, 0x37, 0xcd, 0xad, 0x72, 0xa7, 0xf9, 0x10, 0x18, 0x9c, 0xa6, 0x12, 0x26, 0x03, 0xc8, 0xba, 0xeb, 0x1d, 0x8e, 0x7a, 0xb7, 0xf0, 0x9a, 0xde, 0x8c, 0x32, 0x5c, 0x2b, 0x6d, 0x60, 0x39, 0x4e, 0xf5, 0xe9, 0x72, 0x5c, 0x19, 0x23, 0xae, 0x9c, 0xca, 0x4b, 0xad, 0x10, 0x9e, 0x9a, 0x16, 0x1e, 0xc5, 0xad, 0x39, 0x0d, 0x52, 0x63, 0x90, 0x38, 0xde, 0x40, 0xee, 0x34, 0x19, 0x84, 0xef, 0x37, 0xa6, 0xed, 0x19, 0x37, 0xc8, 0xe8, 0xcd, 0xe2, 0x78, 0x1e, 0xa7, 0x6f, 0x05, 0xed, 0xb2, 0x85, 0x12, 0xae, 0x05, 0x52, 0x4e, 0x47, 0x25, 0xeb, 0x3d, 0x01, 0x4a, 0xb8, 0x16, 0x19, 0x36, 0xf0, 0xf8, 0xf5, 0xd7, 0xcb, 0x16, 0x0a, 0x83, 0xe2, 0x99, 0x7e, 0x06, 0xfe, 0x63, 0xc7, 0x78, 0x50, 0xfa, 0xb3, 0xa2, 0xb4, 0x71, 0x1c, 0x67, 0xc4, 0x5b, 0xbf, 0x0d, 0x27, 0x1a, 0xf3, 0x9c, 0x0e, 0xa0, 0x29, 0xe1, 0x41, 0x97, 0x02, 0x8a, 0xc4, 0xf2, 0x83, 0xc2, 0x97, 0x4f, 0x89, 0x7f, 0x80, 0xd5, 0x40, 0xad, 0x87, 0xff, 0x13, 0x3e, 0x24, 0xdf, 0x45, 0x9f, 0x40, 0x8d, 0xfb, 0xf5, 0x0c, 0xb8, 0x8c, 0xb4, 0x93, 0x0f, 0xc0, 0x36, 0x66, 0xe1, 0x6d, 0x88, 0x9a, 0x33, 0x14, 0x94, 0x91, 0x08, 0x5b, 0x7a, 0xe5, 0x4d, 0xe5, 0x37, 0x7d, 0x51, 0x7a, 0x03, 0x20, 0x8e, 0x82, 0x16, 0x72, 0x1f, 0xf9, 0x07, 0xf8, 0xfc, 0x02, 0x91, 0x2f, 0x3b, 0x30, 0x1c, 0xe3, 0x34, 0x4e, 0x6a, 0x81, 0x2f, 0x43, 0x9f, 0x00, 0x41, 0x4f, 0x00, 0x62, 0x21, 0x64, 0xab, 0xe8, 0xc0, 0xee, 0x9e, 0xef, 0x9a, 0x98, 0xf0, 0x22, 0x27, 0xe4, 0x50, 0xef, 0xc3, 0xc6, 0x27, 0x33, 0x8f, 0x43, 0x59, 0xc8, 0x7d, 0x82, 0xf1, 0x91, 0x9b, 0xc0, 0xb7, 0x0f, 0x1c, 0x00, 0x19, 0xe1, 0xfb, 0xe2, 0x5c, 0x56, 0xda, 0xec, 0x10, 0xdb, 0xb4, 0xe2, 0x8e, 0x6f, 0x3a, 0x23, 0xa3, 0x1c, 0x29, 0x14, 0xf3, 0xa6, 0x9a, 0x8f, 0x3f, 0x75, 0x46, 0xb2, 0xf9, 0xbe, 0x47, 0x6e, 0x7a, 0x04, 0xfc, 0x81, 0xdc, 0x29, 0xbc, 0x06, 0xb2, 0x97, 0xce, 0x6e, 0x47, 0x4d, 0x84, 0x8b, 0x01, 0xec, 0xd0, 0x18, 0xad, 0xf2, 0xa2, 0xd0, 0x34, 0x5a, 0x03, 0x1a, 0x2c, 0x98, 0xdf, 0x89, 0x82, 0x5f, 0x88, 0xde, 0x79, 0xea, 0xf6, 0xdb, 0x0f, 0xdc, 0x76, 0x9b, 0xf8, 0xbe, 0x53, 0xf0, 0x7d, 0xa3, 0x9f, 0xba, 0x07, 0x78, 0x33, 0x7f, 0x14, 0xfc, 0x41, 0x30, 0x92, 0xa3, 0x07, 0xa4, 0x3e, 0xdc, 0x49, 0x9e, 0xa4, 0xfc, 0x84, 0x9d, 0x58, 0x2b, 0xda, 0x0f, 0xb4, 0x7a, 0x94, 0x0d, 0x31, 0x68, 0xb7, 0x42, 0xee, 0x45, 0xf4, 0x3b, 0x67, 0x7d, 0x07, 0xfd, 0x52, 0x4e, 0xaf, 0x11, 0x60, 0xe8, 0xaa, 0xe5, 0x00, 0x94, 0x0f, 0xac, 0x13, 0x2f, 0x2f, 0x4a, 0x04, 0x9a, 0x7d, 0x90, 0xc7, 0x9f, 0x0c, 0xf1, 0x11, 0xec, 0xc6, 0x08, 0x59, 0x2d, 0x62, 0xa6, 0xec, 0x6c, 0xdb, 0x8f, 0xfc, 0x28, 0xa0, 0x59, 0x75, 0xa2, 0xe0, 0xf3, 0xab, 0xd5, 0x0e, 0x19, 0xe7, 0x5e, 0x90, 0x69, 0x5d, 0x9c, 0x36, 0x7d, 0x89, 0x7c, 0x7e, 0x32, 0x64, 0xb3, 0xb7, 0x64, 0x7a, 0x74, 0x3a, 0x7b, 0x93, 0xce, 0x1a, 0x69, 0xee, 0xf5, 0xe9, 0x5b, 0xc5, 0x7d, 0x5b, 0x4b, 0xfe, 0x82, 0xec, 0x81, 0xb2, 0x80, 0x96, 0xc8, 0x12, 0xa1, 0xa2, 0x3f, 0x1b, 0x0f, 0xb8, 0xb5, 0x18, 0x1b, 0x02, 0x8f, 0x08, 0xac, 0x45, 0x22, 0x00, 0x3c, 0xc3, 0x99, 0xba, 0x50, 0xd0, 0x6e, 0x0d, 0xd1, 0x28, 0xcc, 0xb7, 0x4a, 0x5f, 0x4e, 0x81, 0xc2, 0x6c, 0x14, 0x88, 0x76, 0x20, 0x6a, 0x50, 0xe8, 0xe4, 0x49, 0xf1, 0xe1, 0x20, 0xa1, 0x52, 0x9b, 0x0c, 0x5a, 0x93, 0x46, 0xa9, 0x5d, 0xa7, 0x65, 0x55, 0x0c, 0xeb, 0xcf, 0x07, 0x43, 0xb9, 0x80, 0x81, 0x56, 0xb3, 0x5a, 0xd7, 0xbf, 0xed, 0xd6, 0x59, 0x99, 0x8d, 0x2a, 0x86, 0x52, 0x30, 0x1b, 0x19, 0x9d, 0x6e, 0xd7, 0x77, 0xa8, 0xa7, 0xcc, 0x61, 0x9d, 0xc9, 0x65, 0x71, 0x70, 0x41, 0x93, 0x33, 0xe8, 0x08, 0xba, 0x1d, 0x7a, 0xe4, 0xde, 0xb7, 0x46, 0x73, 0x1e, 0xbd, 0xdd, 0x13, 0xb4, 0xe7, 0xc0, 0x6e, 0x9d, 0xfa, 0x87, 0x1c, 0xa7, 0x36, 0x2a, 0x7f, 0xa8, 0xd6, 0x09, 0xc7, 0x50, 0x2f, 0xef, 0x82, 0x03, 0x59, 0x87, 0xeb, 0x4e, 0x89, 0x7e, 0x12, 0xd1, 0x45, 0x32, 0x59, 0x41, 0x8f, 0x94, 0x97, 0xfd, 0x24, 0x22, 0xc4, 0x6a, 0x8e, 0x5c, 0xf7, 0x45, 0xe1, 0x2d, 0xc0, 0x33, 0x27, 0xff, 0x3e, 0xc5, 0x28, 0x8e, 0x83, 0x59, 0xef, 0x80, 0x1c, 0x59, 0xe2, 0x20, 0xeb, 0x25, 0x6b, 0x70, 0xe5, 0x35, 0xe5, 0x1d, 0x2f, 0xbe, 0x87, 0xad, 0x7a, 0x0f, 0xf5, 0x9d, 0xe3, 0xa0, 0x4b, 0xdc, 0x8b, 0xff, 0xaf, 0xdf, 0x45, 0x1e, 0x3c, 0x4e, 0x5e, 0xf5, 0xff, 0xd1, 0xbb, 0x40, 0xe7, 0x71, 0xea, 0x5f, 0x67, 0xbf, 0x0b, 0xfb, 0x93, 0xa4, 0x29, 0x12, 0x0f, 0xf5, 0xdc, 0x9a, 0xf2, 0x40, 0x7a, 0x8b, 0xd0, 0x21, 0xbe, 0xe4, 0x38, 0x83, 0x7c, 0x94, 0xd7, 0x11, 0x57, 0x90, 0x0f, 0x53, 0xe7, 0xc3, 0xdb, 0x42, 0x28, 0x0d, 0x78, 0x5e, 0xc8, 0x9a, 0x79, 0xf0, 0x6a, 0x1e, 0x9e, 0x79, 0xfb, 0x26, 0x40, 0x5e, 0x46, 0xfe, 0x69, 0xe6, 0x7a, 0xb1, 0x1f, 0xa5, 0xf7, 0xc8, 0x09, 0x7f, 0xd1, 0x8b, 0xd9, 0xd7, 0xa8, 0x24, 0x2f, 0x20, 0x5b, 0x33, 0x3a, 0x63, 0x24, 0x58, 0x30, 0x0b, 0x16, 0x37, 0xc3, 0xf2, 0xe4, 0xc3, 0xc2, 0xee, 0x9b, 0x6e, 0x02, 0x47, 0xc1, 0x33, 0xc2, 0x77, 0xc8, 0x3f, 0x09, 0xdf, 0x39, 0x7d, 0x9a, 0x48, 0x9f, 0xe6, 0x48, 0x23, 0x48, 0x40, 0x76, 0xd2, 0x71, 0xfa, 0xf4, 0xc7, 0xd7, 0x4a, 0x31, 0xf6, 0xd7, 0x8a, 0x31, 0xf6, 0xf0, 0xfa, 0x85, 0x74, 0x9a, 0x7c, 0x40, 0xf6, 0x2d, 0x32, 0x4c, 0x3c, 0x4d, 0xdc, 0x43, 0x44, 0x98, 0xc6, 0xd3, 0x13, 0xc4, 0x47, 0x40, 0xce, 0x34, 0x13, 0x2f, 0x12, 0x1f, 0x11, 0xf6, 0xc7, 0x08, 0xf0, 0xcd, 0xd3, 0xf7, 0xc0, 0xbb, 0x1f, 0x03, 0xe0, 0xd1, 0x8f, 0xd0, 0x27, 0x29, 0x7e, 0xa2, 0x3e, 0x42, 0x69, 0x9c, 0xfe, 0x05, 0x73, 0x3f, 0xe1, 0x27, 0x1e, 0x7e, 0x12, 0x21, 0x9e, 0x95, 0x50, 0xb8, 0x8d, 0xc8, 0xc8, 0x24, 0x42, 0x26, 0x66, 0x50, 0xfa, 0x76, 0x82, 0xaa, 0x24, 0x0c, 0x96, 0xaf, 0xe4, 0x2a, 0x57, 0xd8, 0xb3, 0x3e, 0x63, 0x3e, 0xeb, 0x33, 0xd6, 0xb3, 0x3c, 0x33, 0xdf, 0xed, 0x28, 0xda, 0x61, 0xfc, 0x1b, 0x11, 0xab, 0x3f, 0xca, 0x33, 0x72, 0x7b, 0x9c, 0xcb, 0x4a, 0xe9, 0x47, 0x99, 0x7a, 0x64, 0xc6, 0xd2, 0x51, 0xa2, 0xb6, 0x43, 0xa2, 0xdc, 0x24, 0xeb, 0x05, 0x23, 0x27, 0xae, 0xd8, 0xec, 0xf5, 0x6e, 0xbe, 0xe2, 0xc4, 0xc8, 0x05, 0xfd, 0xd9, 0xad, 0xeb, 0x96, 0xb5, 0x05, 0x7e, 0x1a, 0x68, 0x5b, 0xb6, 0x6e, 0x6b, 0xb6, 0x9f, 0xd9, 0x1f, 0x6b, 0x39, 0xfa, 0xee, 0x63, 0x8f, 0xbd, 0x7b, 0xb4, 0x25, 0x76, 0x4d, 0x78, 0xf4, 0xda, 0x97, 0x04, 0xe1, 0x1e, 0xe1, 0x7c, 0x70, 0xe4, 0x1e, 0x40, 0xbe, 0x74, 0xed, 0x68, 0x58, 0x5c, 0xb7, 0xc7, 0xe0, 0x9c, 0xc4, 0xe1, 0xfe, 0xb1, 0x21, 0xbf, 0x97, 0x5c, 0xcc, 0x9c, 0x29, 0x89, 0x1d, 0x62, 0xd1, 0xac, 0xc9, 0x72, 0x95, 0x95, 0xb8, 0xd4, 0x3f, 0xc9, 0x2d, 0xca, 0x06, 0xa3, 0xbc, 0x5c, 0x8e, 0x0b, 0xac, 0x94, 0x21, 0x53, 0x6a, 0xa9, 0x00, 0x06, 0x47, 0x34, 0x83, 0x6b, 0xc0, 0xf8, 0xc0, 0xfa, 0x26, 0x2b, 0x14, 0xa0, 0xd9, 0x56, 0xa0, 0xf7, 0x37, 0xc4, 0x84, 0xaf, 0x9d, 0xa2, 0x2e, 0xa6, 0xff, 0x28, 0x78, 0xec, 0xf5, 0x0b, 0x32, 0x1a, 0xab, 0x49, 0xe3, 0x0a, 0x5b, 0x95, 0x33, 0x5f, 0x62, 0x4e, 0x7e, 0x15, 0x6d, 0xb2, 0xc5, 0x70, 0x8f, 0x25, 0x99, 0xaf, 0x40, 0xe9, 0x39, 0x46, 0x7c, 0xff, 0xa9, 0xa8, 0x89, 0x44, 0xf8, 0x29, 0x25, 0x6b, 0x95, 0x24, 0x0a, 0x66, 0x70, 0xa4, 0x2d, 0xe6, 0xc1, 0x95, 0x85, 0x28, 0x5f, 0xcd, 0xcd, 0xbd, 0x6a, 0xfe, 0xd4, 0x67, 0x1d, 0x9f, 0xfa, 0xac, 0xeb, 0x53, 0x9e, 0x3d, 0xdb, 0x63, 0x78, 0xd9, 0x10, 0xfc, 0x72, 0xac, 0xc6, 0xeb, 0x56, 0x2b, 0x71, 0x04, 0xb0, 0xbc, 0x82, 0xb4, 0x53, 0x82, 0x06, 0x93, 0xcf, 0xc1, 0xdb, 0x64, 0x03, 0xe4, 0xf3, 0xab, 0xbf, 0xb0, 0xa5, 0xb1, 0x71, 0xcb, 0x17, 0x56, 0x4b, 0x9f, 0xba, 0xf3, 0x9f, 0xbb, 0x66, 0x60, 0xe5, 0x3d, 0xbf, 0x3a, 0x72, 0xe4, 0xd7, 0x5f, 0x5e, 0x39, 0x70, 0xcd, 0xf3, 0xe7, 0xbf, 0x0e, 0xde, 0xce, 0x6f, 0xbc, 0x71, 0xd5, 0xc4, 0x8d, 0x53, 0xf9, 0xfc, 0xd4, 0x8d, 0x13, 0xab, 0x6e, 0xdc, 0x98, 0x7f, 0xcc, 0xb0, 0xe1, 0x96, 0x97, 0x76, 0xec, 0xfb, 0xc9, 0xed, 0xcb, 0x97, 0xdf, 0xfe, 0x93, 0x7d, 0x3b, 0x5f, 0xfa, 0xc2, 0x06, 0x03, 0x36, 0x9f, 0xc0, 0x35, 0xe5, 0x10, 0x0e, 0x25, 0x5c, 0x53, 0x2d, 0x46, 0x28, 0x12, 0x43, 0xa1, 0x36, 0xc0, 0xee, 0xc6, 0x11, 0x79, 0x49, 0x20, 0x2c, 0x2e, 0xce, 0x47, 0xcb, 0x6d, 0x18, 0x2d, 0xb3, 0xc0, 0xf1, 0x48, 0xae, 0x92, 0x53, 0x3c, 0x45, 0x1e, 0xf6, 0xb8, 0x75, 0x1f, 0x7f, 0xf2, 0x7d, 0x30, 0x7d, 0xc8, 0x0b, 0xff, 0xa0, 0x32, 0x51, 0x3f, 0x99, 0xa2, 0x02, 0x51, 0xff, 0xcc, 0x8f, 0xcb, 0x7b, 0x85, 0xf9, 0x2b, 0x7c, 0x6f, 0x18, 0xd9, 0xc3, 0xdc, 0x7a, 0xd9, 0x19, 0x98, 0x54, 0x54, 0x1c, 0x83, 0x65, 0x26, 0x30, 0xe1, 0x09, 0x13, 0x21, 0xde, 0x5a, 0x60, 0x65, 0x72, 0x09, 0x8c, 0x2a, 0x2f, 0x02, 0x2f, 0x21, 0x9b, 0x60, 0x05, 0x8f, 0x0a, 0x88, 0x78, 0x54, 0x28, 0x23, 0x82, 0xf9, 0xeb, 0xf7, 0x29, 0x5a, 0xd8, 0xc3, 0xfa, 0x1d, 0x32, 0x7b, 0xc0, 0x20, 0xec, 0xa6, 0xe9, 0x1f, 0x3c, 0xe1, 0x72, 0x1a, 0xc1, 0x15, 0x6a, 0x16, 0x8a, 0x92, 0x6a, 0x70, 0xb9, 0x52, 0x67, 0xe0, 0x74, 0x88, 0x72, 0xd1, 0x4e, 0x57, 0x27, 0x2f, 0x98, 0x2c, 0x4e, 0xa7, 0x05, 0xbc, 0xeb, 0xef, 0x74, 0x7d, 0xfc, 0x2b, 0xe6, 0xa4, 0xce, 0x22, 0xe4, 0x74, 0x36, 0x8d, 0xd6, 0xae, 0x03, 0xdf, 0x55, 0xeb, 0x90, 0xa9, 0x0b, 0x63, 0x74, 0x33, 0xbd, 0xcc, 0x43, 0x44, 0x3d, 0x31, 0x5a, 0xd4, 0xeb, 0x10, 0x1c, 0x67, 0x7d, 0x5d, 0xaa, 0x36, 0xc9, 0x88, 0x31, 0x9d, 0x2c, 0x46, 0x1e, 0x01, 0xb8, 0x18, 0xc6, 0xce, 0x52, 0x7e, 0x4c, 0xe5, 0x5c, 0x9e, 0x71, 0xa9, 0x7c, 0x3a, 0x9f, 0x8a, 0x84, 0xb9, 0x30, 0x9a, 0x3f, 0x10, 0xc2, 0x75, 0x52, 0x91, 0xd5, 0x41, 0x26, 0xa1, 0x57, 0xd0, 0x16, 0xa3, 0xd9, 0x44, 0xca, 0x21, 0xeb, 0x04, 0xf9, 0x82, 0x88, 0xb2, 0x0f, 0x45, 0x63, 0x26, 0x02, 0xe0, 0xa8, 0x21, 0x55, 0xbb, 0xb3, 0x79, 0x4b, 0xa6, 0xe5, 0xa6, 0x15, 0x57, 0xbc, 0x35, 0xcd, 0xc7, 0x8c, 0xdd, 0xfd, 0x47, 0x6f, 0xb9, 0x7b, 0xd5, 0x43, 0x40, 0xf9, 0xe4, 0x9a, 0xab, 0xae, 0xbb, 0xc2, 0xe4, 0x50, 0x2b, 0x74, 0xe1, 0x65, 0x7d, 0x29, 0x00, 0x22, 0xab, 0x9a, 0x85, 0x2d, 0x2b, 0xc8, 0x63, 0xce, 0xd4, 0xcc, 0xb9, 0xd4, 0xf0, 0x47, 0xfe, 0x3a, 0x53, 0x38, 0xda, 0xdc, 0xc8, 0x02, 0xfb, 0xa6, 0x64, 0x4f, 0xd2, 0x3a, 0xf9, 0xb4, 0xf0, 0x97, 0xfb, 0xbe, 0x29, 0xdc, 0xf0, 0x0d, 0xce, 0x1d, 0x64, 0x13, 0x57, 0x7c, 0xf9, 0xb9, 0x2d, 0x3a, 0x97, 0x2e, 0xd0, 0x1d, 0x78, 0xef, 0x46, 0x70, 0x70, 0x49, 0xbb, 0x70, 0xb1, 0x24, 0xbf, 0x0b, 0x5f, 0xa2, 0x0f, 0x31, 0x7f, 0x83, 0x12, 0xc0, 0x43, 0x4f, 0x40, 0x31, 0x92, 0xe8, 0x13, 0xcf, 0x12, 0x8b, 0x05, 0xc0, 0x72, 0xd1, 0xc8, 0x0c, 0x51, 0x3a, 0x46, 0x73, 0x2e, 0xe4, 0x88, 0xd2, 0x09, 0x9a, 0xf7, 0x09, 0xc7, 0xd9, 0x9e, 0x70, 0xcd, 0xff, 0xc4, 0x3c, 0x37, 0x8b, 0xa7, 0x45, 0x0b, 0x88, 0x78, 0x2c, 0x1c, 0x24, 0xb2, 0x20, 0x4b, 0xa3, 0xb3, 0x22, 0x9d, 0x11, 0x2b, 0x92, 0x40, 0x70, 0x38, 0x25, 0x8e, 0x9d, 0x47, 0x27, 0xc6, 0x98, 0x0f, 0x62, 0xb3, 0x57, 0xe9, 0xdc, 0x50, 0xd6, 0x6d, 0x5f, 0xbf, 0xbc, 0x6f, 0xf1, 0x91, 0xa7, 0x37, 0xad, 0xdd, 0xd5, 0x62, 0x4c, 0x46, 0x6f, 0xe5, 0x42, 0xca, 0xd0, 0x60, 0xc7, 0x15, 0xbf, 0x7d, 0x6c, 0xf3, 0xa6, 0x6f, 0x01, 0xea, 0x64, 0xef, 0x8a, 0x8c, 0x95, 0x5e, 0xc6, 0x9a, 0x54, 0x64, 0x78, 0xf9, 0xf5, 0xf4, 0x7b, 0x3d, 0x07, 0x5f, 0xdc, 0x77, 0xe0, 0x5b, 0x07, 0xc7, 0x9c, 0xb6, 0xb6, 0xbe, 0x05, 0x91, 0x81, 0x2b, 0x8b, 0x2d, 0xbe, 0xa5, 0x35, 0xe6, 0x70, 0x90, 0x67, 0x7d, 0x7b, 0x9f, 0xfa, 0xf0, 0x8b, 0x5f, 0x04, 0xd4, 0xd3, 0x1b, 0x95, 0x9c, 0xd7, 0xa2, 0x31, 0x9a, 0x14, 0xf5, 0x0b, 0xb3, 0x4e, 0xc4, 0x6b, 0xb6, 0x9f, 0xfe, 0x80, 0x0e, 0x31, 0x5f, 0x25, 0x65, 0xc4, 0x4f, 0xf0, 0x9c, 0x8e, 0x9c, 0xfe, 0x80, 0xda, 0x42, 0x2b, 0x88, 0x34, 0xf1, 0x00, 0x4e, 0xef, 0x78, 0xd2, 0x84, 0x63, 0x52, 0x9c, 0xf0, 0x13, 0xa1, 0xe9, 0x8f, 0x8b, 0x0a, 0xac, 0x97, 0x10, 0xe1, 0xc8, 0xd7, 0x97, 0x41, 0x6a, 0xf2, 0xb8, 0x02, 0x25, 0x36, 0x60, 0xa5, 0xca, 0x09, 0xd8, 0x61, 0x42, 0xce, 0xc8, 0x18, 0xb9, 0x0c, 0xf9, 0xd3, 0x51, 0xa0, 0x6e, 0xf5, 0xed, 0x44, 0xd5, 0xdd, 0xc5, 0x64, 0x39, 0xb0, 0x73, 0xce, 0x03, 0xa5, 0xa2, 0x2e, 0xb3, 0xee, 0xc7, 0x71, 0x72, 0x7e, 0xd6, 0x14, 0xb4, 0x46, 0x33, 0x0a, 0xb9, 0x2b, 0x1e, 0x2a, 0x81, 0x6c, 0x94, 0x74, 0xc4, 0x4a, 0x76, 0x8e, 0x1c, 0xca, 0xba, 0x62, 0x86, 0x2e, 0xce, 0xb4, 0xd9, 0xe2, 0x1b, 0xe8, 0xef, 0x71, 0x5f, 0xf4, 0xc2, 0xd5, 0xfd, 0x1d, 0x57, 0xff, 0xe0, 0x3a, 0x4f, 0x21, 0xdf, 0xe0, 0x53, 0x33, 0x01, 0xbb, 0xce, 0x5b, 0x93, 0x0f, 0xf6, 0xef, 0x5a, 0x94, 0x56, 0x90, 0x9a, 0x99, 0xf7, 0x75, 0xf7, 0xdf, 0x94, 0x6a, 0x8f, 0xbb, 0xb4, 0x3e, 0x0f, 0xe3, 0xd6, 0x18, 0xd4, 0x74, 0xef, 0xd5, 0x2f, 0xef, 0xdb, 0xf5, 0xc2, 0x91, 0x11, 0x5a, 0xa1, 0x55, 0x2e, 0xe6, 0x39, 0xd8, 0xc1, 0xe8, 0xf0, 0xf6, 0x9e, 0x6b, 0x6f, 0xbd, 0x5b, 0xa6, 0x90, 0xd9, 0x7d, 0x70, 0xce, 0x8a, 0x70, 0xce, 0x26, 0x4a, 0x73, 0x66, 0xc3, 0x73, 0x86, 0x78, 0x0c, 0x9a, 0x33, 0x44, 0x3d, 0xca, 0x73, 0x26, 0x46, 0xff, 0xad, 0x2f, 0x4f, 0x54, 0xbe, 0x82, 0xf1, 0x93, 0x02, 0x95, 0x39, 0x9b, 0x1d, 0x2c, 0x58, 0x7d, 0x7b, 0x69, 0x36, 0x52, 0x9f, 0x16, 0x5d, 0xb8, 0x9e, 0x98, 0xef, 0x7e, 0x31, 0xb6, 0x90, 0x35, 0x06, 0x2d, 0x0d, 0x41, 0x34, 0x67, 0xdc, 0x6c, 0x64, 0x96, 0x2a, 0xe0, 0x8c, 0x02, 0x14, 0xa1, 0x4a, 0x85, 0x45, 0xc2, 0x91, 0x31, 0xbe, 0x21, 0x9f, 0xf7, 0x5e, 0xf7, 0x83, 0xab, 0x3b, 0xfa, 0xaf, 0x7e, 0xe1, 0x22, 0x77, 0xcf, 0x40, 0x3f, 0xaf, 0x66, 0xdc, 0x3e, 0xad, 0x3b, 0xde, 0x9e, 0xba, 0xe9, 0x3e, 0xdd, 0xcc, 0xfb, 0xa4, 0x46, 0x91, 0x5e, 0xb4, 0x73, 0x20, 0x98, 0xab, 0xf1, 0xea, 0xed, 0x01, 0x9a, 0xfa, 0xb5, 0x52, 0xab, 0xa0, 0x47, 0x8e, 0xbc, 0xb0, 0x6b, 0xdf, 0xcb, 0x57, 0xf7, 0xd2, 0x6a, 0x83, 0xa6, 0xcf, 0x67, 0x87, 0x13, 0x75, 0xf7, 0xad, 0xd7, 0xf6, 0x6c, 0x1f, 0x8e, 0xc2, 0xc9, 0x33, 0xf9, 0xd0, 0xbe, 0xab, 0x39, 0xfd, 0x1e, 0xfd, 0x01, 0xf3, 0x20, 0x21, 0x23, 0xde, 0x10, 0xb1, 0x32, 0x4f, 0xbf, 0x4f, 0x4f, 0xd2, 0x72, 0xb8, 0xbc, 0xc5, 0x62, 0x6b, 0xd4, 0x0e, 0x4f, 0x52, 0x40, 0x0d, 0x25, 0x2c, 0x03, 0x2a, 0x49, 0x43, 0x0d, 0x46, 0x00, 0xd9, 0x0f, 0x99, 0x4e, 0x5f, 0x89, 0x29, 0xe5, 0x2b, 0xb1, 0xca, 0x59, 0x4c, 0x88, 0x53, 0x44, 0x2d, 0xaa, 0x6d, 0x17, 0x60, 0xe4, 0x66, 0x84, 0x00, 0x8d, 0x8d, 0x04, 0x90, 0x70, 0x41, 0xba, 0x04, 0x22, 0x6d, 0xa4, 0x04, 0xcf, 0x8d, 0x06, 0x08, 0x7f, 0x41, 0xb4, 0xd8, 0xaa, 0x23, 0xe1, 0x60, 0xe9, 0xc9, 0xba, 0x86, 0xc6, 0xc9, 0xfe, 0xb0, 0xaa, 0xbd, 0xc3, 0xdd, 0x64, 0x9b, 0x79, 0x6d, 0x7f, 0x7b, 0x9f, 0x5b, 0x15, 0x6a, 0xf4, 0xe7, 0xc5, 0x9f, 0x95, 0xf8, 0x67, 0x32, 0xbb, 0x4f, 0xfa, 0xf9, 0xc8, 0x25, 0x3a, 0x83, 0xa5, 0x65, 0xcb, 0x03, 0x7f, 0xfc, 0x72, 0x72, 0x81, 0x63, 0xe6, 0x8e, 0x4d, 0x0f, 0xff, 0xfd, 0xd1, 0xed, 0xad, 0x66, 0xf4, 0x63, 0xf3, 0xd6, 0xaf, 0xfc, 0xf1, 0xee, 0xc4, 0xb0, 0x83, 0x9c, 0x94, 0x7e, 0xc4, 0x3a, 0x8a, 0xf7, 0xf4, 0x7b, 0xb2, 0xc7, 0xa9, 0xf7, 0x08, 0x9e, 0xe8, 0x45, 0xa3, 0xea, 0x00, 0x40, 0x91, 0xc5, 0x15, 0x67, 0x10, 0x33, 0x81, 0x2c, 0x65, 0xa7, 0x1c, 0xe5, 0x2f, 0x2b, 0x26, 0x90, 0x93, 0xb5, 0x16, 0x73, 0x15, 0x11, 0x05, 0x2a, 0xcb, 0x0c, 0xfb, 0xfd, 0xfe, 0x5e, 0x7f, 0x8f, 0x35, 0x1c, 0x09, 0x9a, 0x6b, 0xec, 0x41, 0x15, 0x5c, 0xb0, 0x52, 0x26, 0x06, 0x26, 0x16, 0x7c, 0x05, 0x9b, 0xbb, 0x15, 0x64, 0x00, 0xce, 0x29, 0xab, 0x0a, 0x11, 0xad, 0xd4, 0xda, 0x09, 0xe0, 0x43, 0x41, 0xbd, 0x6a, 0x8e, 0x77, 0x24, 0x03, 0x0b, 0x3a, 0xe2, 0x5a, 0x8b, 0x4b, 0xff, 0xc9, 0xcb, 0x3a, 0x97, 0x45, 0x1b, 0xef, 0x58, 0x18, 0x48, 0x74, 0xc4, 0xcd, 0xaf, 0x90, 0xef, 0x7a, 0xbd, 0x0f, 0xf3, 0xb1, 0xa6, 0xcd, 0x37, 0x8e, 0x15, 0x56, 0x0e, 0x36, 0x98, 0x6b, 0x8e, 0x6f, 0x1d, 0xbb, 0x61, 0x73, 0x53, 0x8c, 0x7f, 0x5b, 0xc5, 0xb9, 0x8c, 0xe0, 0xcb, 0x6d, 0x9b, 0x06, 0x6b, 0x7c, 0x4b, 0x8f, 0xed, 0x48, 0xac, 0x59, 0xb9, 0xd4, 0x1f, 0x58, 0xba, 0x72, 0x75, 0x62, 0xc7, 0xb1, 0xa5, 0xbe, 0x9a, 0xc1, 0x4d, 0x6d, 0xbf, 0x01, 0xd3, 0xab, 0x1d, 0xc1, 0xa5, 0x0f, 0xaf, 0xbc, 0xf0, 0xa5, 0x43, 0x83, 0x8e, 0x74, 0x47, 0x64, 0xe1, 0xd4, 0xe0, 0xa1, 0x97, 0x2e, 0x5c, 0xf9, 0xf0, 0xd2, 0xa5, 0x1a, 0xbb, 0x59, 0x8c, 0x5f, 0xbf, 0x58, 0xd8, 0x4a, 0xdb, 0x68, 0x25, 0x61, 0x20, 0x3a, 0x89, 0x37, 0xc4, 0x3c, 0xd8, 0x4e, 0xa0, 0xc0, 0x79, 0xb0, 0x41, 0xbc, 0xdb, 0x01, 0xc0, 0x41, 0x03, 0xf9, 0x72, 0x2d, 0xf9, 0xc4, 0x50, 0x05, 0x0f, 0x2b, 0x8d, 0x60, 0x44, 0xea, 0xe9, 0x52, 0x55, 0xb4, 0x20, 0x85, 0xca, 0xf6, 0x89, 0x69, 0x63, 0xf3, 0x3c, 0x57, 0xbe, 0x17, 0x15, 0x2e, 0x4a, 0x40, 0xce, 0xc6, 0xc8, 0xa8, 0xb3, 0xdc, 0x49, 0x92, 0x38, 0x6e, 0xbc, 0xfc, 0x7e, 0x78, 0x3b, 0x2d, 0xa3, 0xf7, 0x7c, 0xae, 0x67, 0x64, 0x62, 0x8c, 0x57, 0xb4, 0x86, 0x87, 0xc2, 0x6b, 0x4d, 0xd8, 0xaf, 0x94, 0xa3, 0x5c, 0x02, 0x50, 0x55, 0xdf, 0xce, 0x4c, 0x89, 0xf8, 0x47, 0x99, 0x8a, 0xcc, 0x08, 0xb0, 0xaf, 0x10, 0x47, 0xe1, 0xcc, 0x4e, 0xd5, 0x05, 0x6d, 0xf5, 0x97, 0xf7, 0x5c, 0x7d, 0x3c, 0xd2, 0x37, 0xd9, 0x9c, 0x5f, 0xd7, 0x5b, 0xa3, 0x71, 0xb4, 0x4c, 0x0e, 0x24, 0xa7, 0xb3, 0xc5, 0xb1, 0x8c, 0x99, 0x54, 0x72, 0x86, 0x02, 0xd9, 0xdc, 0x57, 0xb7, 0x22, 0x37, 0xfd, 0xe5, 0xdd, 0xad, 0x6d, 0x17, 0x3c, 0xb8, 0x6d, 0xfd, 0x43, 0x07, 0x06, 0x5b, 0x76, 0xdf, 0x3d, 0xbd, 0xe6, 0xb6, 0x46, 0xe0, 0xed, 0x69, 0xff, 0xd6, 0x13, 0x6b, 0xae, 0x59, 0x1e, 0x8d, 0x0c, 0xef, 0xe8, 0x13, 0x0a, 0x8b, 0xb6, 0x75, 0x38, 0x55, 0xf6, 0xfa, 0xfe, 0xb4, 0xc6, 0x6a, 0xd4, 0xe4, 0x23, 0xaa, 0xe4, 0xe6, 0x87, 0x2e, 0x05, 0xdf, 0xdd, 0xf4, 0x95, 0x0b, 0x8a, 0x3d, 0x57, 0x3e, 0x7f, 0xd1, 0x8e, 0x67, 0x0f, 0x8f, 0xf4, 0x62, 0xec, 0x0e, 0xf2, 0xf4, 0x3b, 0xc2, 0x43, 0xb4, 0x1d, 0xaf, 0x49, 0x12, 0xe3, 0x3c, 0xc1, 0x35, 0x09, 0xe0, 0xdc, 0x64, 0x8c, 0x92, 0x8c, 0xe9, 0x7a, 0xe5, 0xa0, 0x95, 0xec, 0xa3, 0x89, 0x21, 0x71, 0xec, 0xc8, 0x10, 0xc1, 0x45, 0xe7, 0x1f, 0xf6, 0x19, 0x69, 0xc8, 0x08, 0x23, 0x17, 0x5b, 0x2b, 0x40, 0x7b, 0xfd, 0xe5, 0xdd, 0x68, 0x94, 0x1b, 0xf0, 0x28, 0x5f, 0xc1, 0x63, 0x79, 0xf0, 0x32, 0x3c, 0x96, 0x15, 0xc7, 0x1b, 0x6f, 0xbb, 0xed, 0x15, 0xa8, 0x23, 0x79, 0x7a, 0xda, 0x2a, 0x03, 0xaa, 0xee, 0x78, 0x5b, 0xe3, 0x37, 0x8f, 0x1e, 0x3e, 0x7c, 0x14, 0xd1, 0x8d, 0x85, 0xc2, 0xc3, 0xcc, 0x35, 0xcc, 0x49, 0xc8, 0xaf, 0xfe, 0x17, 0xd3, 0x8d, 0x8b, 0x85, 0x87, 0x30, 0xed, 0xad, 0x21, 0x2e, 0x13, 0xe9, 0xa7, 0x0a, 0x85, 0x01, 0x38, 0x11, 0xd0, 0x2d, 0xca, 0x11, 0x42, 0x51, 0x79, 0xe2, 0x97, 0x71, 0xf1, 0x32, 0x4f, 0xd0, 0x90, 0x06, 0xd1, 0x18, 0xfd, 0x95, 0xa1, 0xe1, 0x52, 0x97, 0x6b, 0xec, 0xe5, 0xa5, 0x01, 0xa2, 0xf2, 0xca, 0x25, 0xed, 0x60, 0xce, 0xbd, 0x25, 0x17, 0x4d, 0xe9, 0xd6, 0xf1, 0xa2, 0x0a, 0x2a, 0x08, 0x21, 0x3e, 0x14, 0x94, 0xcb, 0x9d, 0x52, 0x59, 0xb1, 0x8a, 0x4f, 0x06, 0x95, 0x62, 0xad, 0xda, 0x01, 0x14, 0x12, 0x11, 0x2f, 0xce, 0xee, 0x7e, 0xe2, 0xb2, 0xce, 0x83, 0x1d, 0x1a, 0x3a, 0x60, 0x77, 0x6f, 0x28, 0x8e, 0x6d, 0x6b, 0x31, 0xcf, 0xf4, 0xac, 0xca, 0x5b, 0x28, 0xa5, 0xd1, 0x50, 0x00, 0x06, 0x3e, 0x1f, 0xf9, 0xcb, 0x2b, 0xe0, 0xa3, 0xcb, 0x5f, 0xbd, 0xaa, 0xc7, 0x60, 0x84, 0xbc, 0xc6, 0x90, 0xdd, 0x70, 0xdd, 0xf2, 0xbc, 0x39, 0xd1, 0x93, 0xd2, 0xd8, 0x8c, 0x1a, 0x67, 0xd0, 0xac, 0x9c, 0xb9, 0x52, 0xb4, 0x7f, 0x8c, 0xc0, 0x35, 0xf4, 0xc3, 0x71, 0xab, 0xa1, 0xa6, 0x97, 0x2a, 0x26, 0x7c, 0x1a, 0xac, 0x4c, 0x54, 0xd3, 0x47, 0x49, 0x68, 0x4f, 0xa3, 0x80, 0x64, 0x3f, 0x6f, 0x35, 0x8b, 0x99, 0x10, 0x48, 0x5a, 0x0f, 0x95, 0xf3, 0xc6, 0x4d, 0x16, 0xeb, 0x99, 0x92, 0x7a, 0xd3, 0x0d, 0x47, 0x8f, 0xde, 0x70, 0xd3, 0xb1, 0x63, 0xa9, 0x89, 0x1b, 0xa7, 0xf3, 0x3d, 0x97, 0x89, 0x85, 0x86, 0xf2, 0xd3, 0x37, 0x4e, 0xbc, 0x7c, 0xf8, 0xa5, 0x47, 0x51, 0xb9, 0xa1, 0x47, 0x5f, 0xba, 0x41, 0x3f, 0x7a, 0xf0, 0xd1, 0x6d, 0xa8, 0x20, 0x17, 0x2a, 0x45, 0xb6, 0xed, 0xb1, 0x83, 0xa3, 0x7a, 0xe1, 0x71, 0xbc, 0x1e, 0x66, 0xe1, 0x41, 0xea, 0xb7, 0xb8, 0xe6, 0xc3, 0x6e, 0xa9, 0xfc, 0x53, 0x95, 0x70, 0x5e, 0x3b, 0x84, 0xab, 0x2a, 0x94, 0xe4, 0xb1, 0x33, 0x2e, 0x35, 0xe0, 0x4b, 0xb6, 0xb3, 0x3d, 0x35, 0xef, 0x03, 0x08, 0xb6, 0xa5, 0x24, 0xec, 0x83, 0x59, 0xc2, 0x3e, 0x79, 0xf5, 0x95, 0x50, 0xc6, 0xff, 0x68, 0xa6, 0x1f, 0x8c, 0x1f, 0x84, 0x52, 0xff, 0xbb, 0xe4, 0x37, 0x85, 0x07, 0xa1, 0xb8, 0x6f, 0x25, 0xdf, 0x8d, 0xfa, 0x05, 0x64, 0x91, 0x38, 0x04, 0xe7, 0xf0, 0x21, 0xd8, 0xd7, 0x08, 0xaa, 0x37, 0xe2, 0xd1, 0xcd, 0x23, 0xeb, 0xe3, 0x46, 0xf0, 0xd1, 0x4f, 0x93, 0xc3, 0x22, 0x1a, 0x23, 0x6b, 0x8d, 0xcc, 0x91, 0xf7, 0x03, 0xd5, 0xe2, 0x3e, 0x14, 0x3f, 0xca, 0xf8, 0xb3, 0x54, 0x86, 0x7e, 0xe8, 0x51, 0x85, 0x42, 0xf8, 0xb9, 0x21, 0xe8, 0x74, 0x05, 0x0d, 0xc2, 0xcf, 0x15, 0x8a, 0xaf, 0xbd, 0xa0, 0x33, 0x19, 0x74, 0x8a, 0xff, 0x51, 0x1b, 0x14, 0x0a, 0x93, 0xfa, 0x97, 0x0a, 0x1d, 0x6b, 0xd2, 0xe1, 0x9a, 0x1a, 0x5f, 0x88, 0x4d, 0x27, 0x85, 0x43, 0x16, 0x87, 0xc3, 0x02, 0x2e, 0x4c, 0x4e, 0xc7, 0x3e, 0xb1, 0x30, 0x0b, 0x28, 0xb9, 0x4e, 0x23, 0xfc, 0xaf, 0xce, 0xa1, 0xd1, 0x38, 0x0c, 0xc0, 0xa2, 0xd1, 0xc9, 0xd1, 0xe0, 0xd5, 0xb0, 0xcf, 0xc3, 0x34, 0xaa, 0x89, 0x5b, 0x92, 0xf7, 0xb3, 0x99, 0xba, 0x74, 0xea, 0xd3, 0xe4, 0x7d, 0x2c, 0x07, 0xa4, 0xe7, 0x95, 0xf7, 0xf1, 0xa5, 0x06, 0x24, 0xef, 0xd7, 0x48, 0xf2, 0x3e, 0x77, 0xa6, 0xbc, 0x2f, 0xed, 0x8c, 0x79, 0xe5, 0xfd, 0xdf, 0x81, 0xae, 0xec, 0x74, 0x32, 0x7d, 0x7e, 0xcf, 0xba, 0x6f, 0x2d, 0xf6, 0xc5, 0x8c, 0x8d, 0x2d, 0x7b, 0xf7, 0x5f, 0x3d, 0x74, 0xe4, 0x67, 0x37, 0x0d, 0xf9, 0x5a, 0xc7, 0x9b, 0x76, 0x43, 0x79, 0x5f, 0xae, 0x73, 0x0c, 0x76, 0xa7, 0x01, 0xf0, 0x8f, 0xe5, 0x85, 0x5f, 0x2d, 0x25, 0x69, 0x6b, 0xcd, 0xcc, 0x87, 0xe0, 0xa1, 0xdf, 0x07, 0x9a, 0x2c, 0xd1, 0x44, 0xa1, 0x00, 0xe5, 0xfd, 0x95, 0x89, 0xce, 0x84, 0x65, 0xcd, 0x23, 0x1f, 0x9e, 0x58, 0xf7, 0xf8, 0xc3, 0x0f, 0xae, 0xe0, 0xdc, 0x01, 0xb6, 0xe6, 0xbc, 0x13, 0x4f, 0x9f, 0xab, 0x73, 0xe8, 0x22, 0x8b, 0x83, 0x3f, 0xbc, 0x08, 0x5c, 0x30, 0xd2, 0x26, 0x5c, 0x21, 0xca, 0xfb, 0x23, 0xc2, 0x37, 0xa8, 0xfd, 0x74, 0x13, 0xdc, 0x0b, 0x7c, 0xd1, 0xa3, 0xd3, 0x92, 0x64, 0x1f, 0x51, 0xc6, 0xe7, 0x84, 0x37, 0xd4, 0x92, 0xc3, 0xc9, 0x78, 0x30, 0xc0, 0x7b, 0x29, 0x24, 0x65, 0xcf, 0x2f, 0x61, 0xb7, 0x61, 0xc7, 0x77, 0x29, 0xcd, 0x1f, 0x1e, 0x51, 0x60, 0xad, 0x5b, 0x73, 0x68, 0xbc, 0xb8, 0xb4, 0xde, 0x84, 0xc5, 0x6b, 0x8d, 0xb7, 0x21, 0xb9, 0xf6, 0x2b, 0xfb, 0x07, 0x3a, 0x2e, 0x7f, 0xee, 0xd2, 0xd1, 0x31, 0x2c, 0x5a, 0x5f, 0x7b, 0xd9, 0xea, 0x83, 0xa3, 0x51, 0x32, 0xb9, 0xf6, 0xe2, 0xc5, 0x0d, 0x6e, 0x6b, 0x7d, 0x63, 0x67, 0x59, 0xb2, 0x0e, 0xf1, 0x6e, 0xad, 0xab, 0xef, 0xfc, 0x47, 0xaf, 0x9a, 0xba, 0xff, 0xa2, 0xee, 0x40, 0xad, 0xc6, 0x68, 0x56, 0x4c, 0x2d, 0x09, 0xf7, 0x6f, 0x45, 0xe7, 0x33, 0x21, 0xdc, 0x44, 0x73, 0xcc, 0xf7, 0x08, 0x0d, 0xd1, 0x4e, 0xb6, 0x15, 0x35, 0x5a, 0x0d, 0x9a, 0x78, 0xb7, 0x84, 0x00, 0xa1, 0xc6, 0x39, 0x73, 0x72, 0x19, 0x29, 0x93, 0xa3, 0xea, 0xd0, 0x22, 0x9a, 0x01, 0x24, 0x29, 0x99, 0xa1, 0x33, 0xe9, 0x6e, 0x16, 0xb1, 0xa8, 0x74, 0x19, 0x8a, 0xeb, 0x1f, 0x7a, 0xac, 0x01, 0x3f, 0xa6, 0xfb, 0xe7, 0x5a, 0xe3, 0xfe, 0xb9, 0xd6, 0xcc, 0xff, 0x5c, 0x6b, 0xd6, 0x7f, 0xae, 0x35, 0xdb, 0x3f, 0xd7, 0x9a, 0xef, 0x9f, 0x6b, 0x2d, 0x8a, 0xe5, 0x0a, 0x78, 0x32, 0x76, 0x13, 0x72, 0x52, 0xbe, 0xbb, 0xfc, 0xe0, 0xd9, 0x9f, 0x29, 0xc6, 0xe7, 0xb9, 0x9d, 0xa6, 0x77, 0xcd, 0xf3, 0x88, 0x64, 0x5c, 0x11, 0xd3, 0x80, 0x2c, 0x76, 0x8b, 0x15, 0x65, 0xdf, 0x61, 0xf5, 0xa6, 0x3a, 0xfb, 0x2e, 0x37, 0x3b, 0xfb, 0xae, 0x92, 0x30, 0x8c, 0x2b, 0x69, 0x88, 0x84, 0xdc, 0x0d, 0xa0, 0x78, 0x08, 0x86, 0xd3, 0x3b, 0x3b, 0xaf, 0x3d, 0x7e, 0xc5, 0x85, 0xcb, 0x2e, 0xc9, 0xb5, 0xdf, 0x39, 0x3d, 0x7e, 0x6c, 0x2a, 0xdf, 0xba, 0xeb, 0xee, 0x8d, 0xeb, 0xae, 0xca, 0x78, 0x0b, 0x0b, 0x6b, 0x91, 0x7c, 0x86, 0xd8, 0x09, 0xdf, 0x38, 0x52, 0xab, 0x71, 0x88, 0x7f, 0xff, 0xbd, 0xbd, 0xfe, 0xa5, 0x57, 0x9e, 0x7c, 0x60, 0xb8, 0xf7, 0xc6, 0xde, 0xb1, 0x9e, 0xcb, 0xbf, 0x79, 0xde, 0x25, 0xdf, 0xbb, 0x61, 0xa4, 0xbf, 0xfd, 0xef, 0xbd, 0xab, 0xf2, 0x66, 0xc4, 0x9e, 0x1a, 0x80, 0x1e, 0xb2, 0x27, 0x66, 0xf9, 0xec, 0xef, 0x58, 0x76, 0x17, 0x6e, 0xa6, 0xbf, 0x0b, 0x65, 0xf7, 0x22, 0x78, 0x5f, 0x74, 0x0d, 0xb8, 0x1a, 0x6a, 0x48, 0x9a, 0xc9, 0x38, 0x49, 0x19, 0xed, 0x03, 0x94, 0xcc, 0x0b, 0xd5, 0x20, 0x14, 0x3e, 0xc9, 0x0c, 0x16, 0x00, 0xd3, 0xef, 0x3c, 0xcb, 0x65, 0x52, 0xbc, 0x3c, 0x2e, 0x6e, 0x58, 0x1f, 0x8a, 0x64, 0x41, 0x09, 0x8a, 0x98, 0xd7, 0x4a, 0x12, 0x48, 0x99, 0x57, 0x57, 0x78, 0xc8, 0xa7, 0xdf, 0x27, 0x32, 0x14, 0xee, 0x73, 0xbe, 0xcf, 0xfc, 0x39, 0xdf, 0x67, 0xfd, 0x9c, 0xef, 0xb3, 0x7d, 0xce, 0xf7, 0x39, 0x3e, 0xe7, 0xfb, 0xfc, 0x9f, 0xf3, 0x7d, 0x09, 0x1c, 0x02, 0x5a, 0x2a, 0x16, 0x3c, 0xcf, 0xfd, 0x25, 0x8a, 0x89, 0x6e, 0x2f, 0x06, 0xcb, 0x77, 0x56, 0x42, 0x88, 0xe6, 0x3c, 0x50, 0xbd, 0x35, 0xb5, 0xc5, 0xb6, 0x64, 0x3c, 0x1a, 0x0e, 0x60, 0xa1, 0xc7, 0x16, 0x67, 0xe6, 0xc9, 0x17, 0xf9, 0x3c, 0xba, 0x57, 0x33, 0xa0, 0xbf, 0xeb, 0x2e, 0x6e, 0x1a, 0xaa, 0x19, 0x4e, 0xea, 0x65, 0x0e, 0x8d, 0x36, 0xe5, 0x2b, 0xa6, 0xeb, 0x72, 0x6b, 0x7a, 0x22, 0x74, 0xae, 0xd1, 0x9a, 0xe0, 0x66, 0xe9, 0x62, 0x0d, 0xeb, 0xfb, 0xa2, 0x74, 0xae, 0xc1, 0x96, 0x34, 0x55, 0x74, 0xb1, 0x93, 0xc9, 0xf6, 0x3d, 0x2b, 0x72, 0xac, 0x69, 0xc4, 0xae, 0xd3, 0x8d, 0x2d, 0x68, 0xe1, 0xf2, 0xeb, 0x4f, 0xfe, 0xfc, 0x36, 0x5f, 0xce, 0x54, 0xad, 0x96, 0x99, 0x1b, 0x36, 0xde, 0xf9, 0xcb, 0xdb, 0x7d, 0x8d, 0x26, 0xac, 0x96, 0x3d, 0xb2, 0xbd, 0x45, 0xf2, 0x93, 0xde, 0x7d, 0xfa, 0x6f, 0xe0, 0x35, 0x72, 0x87, 0x98, 0x73, 0x23, 0xd5, 0x30, 0xd9, 0x59, 0x55, 0xdd, 0xf4, 0x2c, 0x39, 0x37, 0x77, 0x3f, 0xee, 0xf2, 0xb5, 0xd8, 0xc2, 0x29, 0x6b, 0x4d, 0x5b, 0xcc, 0x4c, 0xee, 0x10, 0x46, 0x03, 0xc1, 0x3c, 0xe7, 0x36, 0xa9, 0x3c, 0x99, 0xde, 0x1a, 0x2c, 0x93, 0x3d, 0x26, 0xdc, 0x2b, 0xd3, 0xcb, 0xa1, 0xc0, 0x49, 0x34, 0x13, 0x8b, 0x8a, 0x0b, 0xea, 0x83, 0xa4, 0x52, 0x0e, 0x06, 0x49, 0x15, 0xaa, 0xb5, 0xb0, 0x1b, 0xd1, 0x7f, 0x39, 0x85, 0x70, 0x6b, 0xe5, 0x72, 0x3b, 0x46, 0x9c, 0x75, 0x0c, 0xa9, 0x81, 0x52, 0x89, 0xad, 0xce, 0x6e, 0x7a, 0xd8, 0xe5, 0x02, 0x84, 0xab, 0xd9, 0xd5, 0xdc, 0xd4, 0x50, 0x9b, 0x4c, 0xc4, 0x50, 0xe9, 0x71, 0xab, 0x99, 0xd5, 0x2b, 0xe5, 0x50, 0x7c, 0x75, 0x6a, 0x64, 0x38, 0x98, 0xa2, 0x0a, 0x88, 0xaa, 0xb9, 0xac, 0x0e, 0x02, 0x2a, 0xc0, 0x61, 0x49, 0x04, 0xcd, 0x77, 0x05, 0xf5, 0x1d, 0x1b, 0x1c, 0x51, 0x15, 0x40, 0x5a, 0x66, 0xb2, 0xd4, 0x2c, 0xdc, 0xd1, 0xdb, 0xbb, 0x63, 0x61, 0x8d, 0xc5, 0xb4, 0x47, 0xcf, 0x17, 0xa2, 0xd1, 0x02, 0xaf, 0x27, 0xf7, 0xdd, 0xf6, 0xc3, 0xdd, 0xf7, 0xd2, 0x0a, 0x2e, 0x6a, 0x9a, 0xb9, 0x54, 0x5f, 0xe7, 0xfd, 0xd2, 0x29, 0x46, 0x6e, 0x8c, 0x9a, 0xc8, 0x2b, 0xf5, 0x69, 0xef, 0x97, 0xe4, 0x64, 0xd0, 0x52, 0xbf, 0x20, 0xe7, 0x72, 0xe5, 0x16, 0xd4, 0x5b, 0x82, 0xa9, 0xf6, 0x08, 0xcb, 0x46, 0xda, 0x53, 0x7f, 0xbb, 0x77, 0x46, 0x00, 0x13, 0xd4, 0xff, 0x4d, 0x4e, 0xe6, 0xd3, 0x4b, 0x7c, 0x9f, 0x74, 0xd9, 0x63, 0xcc, 0xc9, 0x4f, 0x82, 0xc9, 0xc9, 0x42, 0x7a, 0xa9, 0x8f, 0x7a, 0xd6, 0x1e, 0xfb, 0x58, 0x0d, 0xe7, 0x20, 0x4c, 0x10, 0xf4, 0x5f, 0x98, 0x93, 0x90, 0xef, 0x05, 0x88, 0xee, 0x62, 0x07, 0x54, 0x27, 0xa0, 0x52, 0x81, 0xac, 0x12, 0xaa, 0x12, 0x58, 0x39, 0xdc, 0x55, 0x76, 0x8c, 0xf1, 0x41, 0xad, 0x82, 0x7b, 0xcb, 0x41, 0x0d, 0xeb, 0xb4, 0x48, 0x40, 0xc5, 0xc5, 0xd6, 0x51, 0xda, 0x9c, 0x45, 0x1b, 0xd0, 0x05, 0xd4, 0x4a, 0x42, 0x03, 0x34, 0x6a, 0x59, 0xa5, 0xfa, 0x27, 0xaa, 0xb3, 0x32, 0xab, 0xd0, 0x7a, 0x2e, 0x83, 0xca, 0x6f, 0x98, 0xc9, 0x8f, 0x16, 0xef, 0xee, 0xe7, 0xf9, 0xbe, 0xdd, 0x8b, 0x85, 0x3f, 0x03, 0xcd, 0x55, 0x2f, 0xec, 0x6f, 0xee, 0xbe, 0xf2, 0xb9, 0x8b, 0x04, 0xdf, 0x07, 0x1f, 0xbc, 0x0e, 0x56, 0x08, 0x5f, 0x79, 0x20, 0xd8, 0xb1, 0x32, 0xdf, 0xb8, 0xb2, 0x8d, 0x17, 0xec, 0xe4, 0x0d, 0xcd, 0x9b, 0x8f, 0x2d, 0x9b, 0xbc, 0x6b, 0x77, 0x2b, 0x1d, 0xbd, 0xeb, 0x27, 0x3f, 0xb9, 0xeb, 0x8f, 0x68, 0x2f, 0xa0, 0xfe, 0xee, 0xc7, 0x76, 0xe4, 0x64, 0x31, 0x46, 0x23, 0xe1, 0x12, 0x17, 0x00, 0x41, 0x46, 0x17, 0x1c, 0x48, 0x67, 0x47, 0x87, 0xc5, 0x81, 0x85, 0x3f, 0x2d, 0xa1, 0xc5, 0x6e, 0x58, 0x56, 0x21, 0x73, 0x48, 0x15, 0xd7, 0x03, 0xb8, 0xe2, 0x3b, 0x6f, 0x06, 0xef, 0x82, 0xe5, 0xc2, 0x03, 0xa7, 0xde, 0x7f, 0x9f, 0x9a, 0xa6, 0x3f, 0x14, 0xba, 0xee, 0x9a, 0xd9, 0x42, 0x9e, 0xb8, 0x8b, 0x9c, 0xba, 0x5f, 0xb2, 0x29, 0x0b, 0x5f, 0xa0, 0x1f, 0x87, 0x6d, 0xc4, 0x89, 0x67, 0x45, 0xeb, 0xa1, 0x0e, 0xa1, 0x12, 0x87, 0xa0, 0x22, 0xab, 0xc7, 0xc1, 0x66, 0xce, 0x59, 0x3f, 0x50, 0x38, 0x7f, 0xdb, 0x25, 0x01, 0x6a, 0xd3, 0x0c, 0x41, 0x4f, 0x42, 0x4d, 0x14, 0xc7, 0x39, 0xda, 0x51, 0x22, 0x83, 0xa3, 0x62, 0x35, 0x72, 0x63, 0xab, 0x51, 0x2d, 0xce, 0x4b, 0x25, 0x19, 0x8a, 0x21, 0xb1, 0x64, 0x37, 0xdf, 0x23, 0x25, 0x4b, 0x10, 0x7a, 0x02, 0xd2, 0x86, 0xb9, 0x37, 0x4b, 0xc5, 0x92, 0x67, 0x3d, 0x83, 0x91, 0x90, 0xd5, 0x2c, 0x6b, 0x8e, 0xfa, 0x4d, 0x51, 0xbf, 0x42, 0xe6, 0x9a, 0x1d, 0x15, 0x5b, 0x3e, 0xf1, 0x15, 0xcb, 0xbf, 0x1c, 0x7b, 0x45, 0x78, 0x70, 0x4d, 0xfb, 0x91, 0x25, 0xdb, 0x5e, 0xb8, 0x61, 0xd9, 0xc2, 0xa3, 0xaf, 0x5e, 0x30, 0xb5, 0x55, 0x43, 0x5a, 0xad, 0x6c, 0x73, 0x7a, 0xe4, 0xe0, 0xea, 0x6c, 0x7e, 0xfd, 0x35, 0x8b, 0x13, 0xfd, 0x4d, 0x69, 0xf3, 0x29, 0x6a, 0x7a, 0x66, 0x27, 0xa5, 0xf7, 0x05, 0x5b, 0xae, 0x7d, 0xf3, 0xae, 0x5b, 0x7e, 0xff, 0xe5, 0xe5, 0xae, 0x4b, 0x8d, 0x56, 0x36, 0xb7, 0xe3, 0x81, 0xf3, 0x2f, 0xb9, 0x77, 0x2a, 0xae, 0x31, 0x39, 0xb4, 0x33, 0xc7, 0x98, 0x93, 0xf7, 0xc3, 0xb9, 0xd3, 0x0a, 0xf7, 0x2a, 0x0e, 0xc3, 0xb9, 0x2b, 0xa2, 0xdd, 0xd4, 0xe2, 0x25, 0x49, 0x78, 0xa2, 0x94, 0x40, 0x4e, 0x03, 0x52, 0x2d, 0x27, 0x27, 0x51, 0xa0, 0x37, 0xec, 0xae, 0x06, 0xa8, 0xd5, 0x60, 0x15, 0x83, 0x5c, 0xfd, 0x0e, 0xc4, 0x50, 0xdd, 0xd8, 0x26, 0x54, 0x24, 0x8a, 0x0d, 0xf9, 0xba, 0x14, 0x1b, 0x82, 0x1a, 0x1a, 0x1b, 0xe2, 0x03, 0x5a, 0x99, 0x3b, 0x1e, 0x62, 0x91, 0xa9, 0x84, 0xad, 0x2e, 0x5b, 0x8e, 0xb8, 0x2a, 0x3e, 0x38, 0x00, 0x4b, 0xbe, 0x11, 0x20, 0xd9, 0xec, 0x9b, 0x01, 0xae, 0x68, 0x9e, 0xaf, 0x94, 0x34, 0x87, 0x72, 0x3d, 0x50, 0x1c, 0x0e, 0x7b, 0x0e, 0x14, 0x52, 0x5d, 0x26, 0xe1, 0x3e, 0x57, 0x77, 0xf6, 0xcb, 0xdf, 0xbd, 0x3b, 0xdb, 0xe3, 0x14, 0xee, 0x33, 0x75, 0xd5, 0x1e, 0x05, 0x77, 0xf8, 0x16, 0x64, 0xe2, 0x2d, 0x6e, 0x61, 0xeb, 0x7d, 0x7c, 0x23, 0x0b, 0x96, 0x7b, 0xbb, 0x12, 0x87, 0xbf, 0x7b, 0x24, 0xd1, 0xe5, 0x05, 0xcb, 0xd9, 0x46, 0xdf, 0xfd, 0xc2, 0x56, 0x77, 0x4b, 0xbc, 0x7e, 0xa1, 0x0f, 0x24, 0xef, 0x17, 0x5a, 0x5c, 0x7e, 0x30, 0x51, 0x13, 0x00, 0xef, 0xc5, 0x42, 0x33, 0xe7, 0x93, 0x47, 0x42, 0x31, 0xc1, 0x10, 0xa8, 0x21, 0xb7, 0x16, 0x3a, 0x0c, 0xfa, 0x86, 0x18, 0xd9, 0xc8, 0xbb, 0x05, 0x7b, 0xc2, 0x45, 0x6e, 0x9f, 0x39, 0xee, 0x4a, 0x80, 0xdf, 0xb8, 0xf9, 0x99, 0x7f, 0x8d, 0x35, 0xe8, 0xd9, 0x62, 0x41, 0x68, 0x2d, 0xed, 0xa7, 0x7b, 0x99, 0x5b, 0xe1, 0x9c, 0x44, 0x50, 0x66, 0x78, 0x00, 0x90, 0x0c, 0x8e, 0xd9, 0x25, 0xe5, 0x0c, 0xf2, 0x63, 0xd1, 0xb4, 0x7d, 0x08, 0xd1, 0x18, 0xb0, 0x0a, 0xc5, 0x65, 0x55, 0x4d, 0x47, 0x84, 0x88, 0xc0, 0x79, 0x60, 0x23, 0x7c, 0x40, 0x29, 0x73, 0xce, 0x9e, 0x08, 0xaa, 0x5c, 0xbf, 0x7d, 0xce, 0x68, 0x99, 0x5b, 0xe1, 0x68, 0xc7, 0x7c, 0x71, 0x4e, 0x38, 0xa1, 0x75, 0xfa, 0x22, 0x8e, 0x07, 0x5e, 0x7f, 0xfd, 0xc6, 0x44, 0xbb, 0x1b, 0x6c, 0x31, 0x24, 0x7d, 0x4f, 0x08, 0xe3, 0xf6, 0x62, 0x38, 0x3f, 0x14, 0x00, 0x39, 0x69, 0x44, 0x7e, 0x1f, 0x49, 0x9a, 0x9c, 0xac, 0x7c, 0xe6, 0x7c, 0xe6, 0xe4, 0xcc, 0x71, 0x67, 0x0d, 0xf8, 0x83, 0x83, 0x9f, 0x79, 0x2a, 0x98, 0xd2, 0xe9, 0x3a, 0x1a, 0x85, 0xb6, 0x8a, 0x7f, 0x65, 0x2f, 0xec, 0xbb, 0x1f, 0x55, 0xc3, 0x32, 0x01, 0x99, 0x1c, 0x72, 0x7f, 0x19, 0x39, 0x48, 0xc8, 0xd0, 0xc1, 0x93, 0x41, 0xea, 0x20, 0x87, 0x44, 0x44, 0x4e, 0xae, 0x57, 0x00, 0x74, 0xf6, 0x90, 0x16, 0xeb, 0x20, 0x86, 0x23, 0xe2, 0xb9, 0x33, 0xf9, 0x94, 0x70, 0x09, 0x79, 0xb8, 0xf7, 0x50, 0xc6, 0x3a, 0x8b, 0xa3, 0x2a, 0x50, 0xaf, 0x51, 0x1d, 0x31, 0x89, 0xdc, 0x01, 0x5c, 0x01, 0x85, 0xd9, 0xfb, 0x7c, 0x9b, 0x4f, 0x78, 0xde, 0x92, 0x4f, 0x9e, 0x38, 0xf8, 0xfa, 0xeb, 0x07, 0x4f, 0xf8, 0xd2, 0x16, 0xd0, 0xa1, 0x4b, 0xf9, 0x1e, 0x11, 0x2e, 0x49, 0x0e, 0x35, 0x0c, 0xf9, 0x3a, 0x49, 0x32, 0xec, 0x25, 0x85, 0xbf, 0x02, 0xa5, 0xd8, 0x55, 0x90, 0x16, 0xfe, 0x83, 0x86, 0x23, 0xf8, 0x83, 0xd3, 0x37, 0xf3, 0x54, 0xaf, 0x43, 0x57, 0x6c, 0x26, 0x24, 0xda, 0xc0, 0xdc, 0x0b, 0xfb, 0x1a, 0x45, 0x11, 0x7d, 0x21, 0x00, 0x14, 0x38, 0xdd, 0x59, 0x0e, 0xa5, 0x15, 0x9a, 0x5a, 0x4f, 0xa0, 0x6a, 0x6f, 0x0a, 0x54, 0x93, 0x09, 0x75, 0x94, 0x14, 0x3b, 0xca, 0x7b, 0x79, 0xb1, 0xab, 0x11, 0x9f, 0x4a, 0xec, 0x6a, 0x65, 0x7a, 0x2b, 0x24, 0x6b, 0xee, 0x34, 0x33, 0xf7, 0x3e, 0x5f, 0x3d, 0xc5, 0x88, 0x72, 0x59, 0x84, 0xdf, 0x9e, 0x39, 0xd1, 0x95, 0xf9, 0x15, 0x09, 0xd8, 0xdf, 0x66, 0x4f, 0x31, 0xee, 0xb3, 0x9b, 0x20, 0x64, 0xdd, 0x18, 0x77, 0x14, 0xd2, 0x33, 0x1f, 0x00, 0xe2, 0xde, 0xc0, 0xb5, 0x19, 0x4a, 0x65, 0xb9, 0xe8, 0x72, 0x77, 0x23, 0x3c, 0xee, 0xaa, 0x1c, 0xee, 0x87, 0x59, 0x5d, 0x45, 0x4e, 0x74, 0xec, 0x88, 0xa8, 0x9a, 0x53, 0x59, 0xb7, 0xd4, 0x49, 0x87, 0x2f, 0xea, 0x78, 0xe0, 0xa7, 0xc2, 0x46, 0x72, 0xc7, 0x8d, 0x96, 0x10, 0xef, 0xd1, 0x83, 0xcd, 0x86, 0x5a, 0x2f, 0xec, 0x63, 0x7a, 0x24, 0x3f, 0xec, 0xaf, 0xea, 0xe2, 0x1f, 0xbf, 0x3a, 0x73, 0x5c, 0xa6, 0x77, 0x9a, 0xc4, 0x2e, 0xf6, 0x3a, 0xf4, 0xb0, 0x87, 0x78, 0xef, 0xca, 0x78, 0x1c, 0x73, 0x95, 0x29, 0xa6, 0xa5, 0x5d, 0xbb, 0xbe, 0x6a, 0xd7, 0xae, 0x9c, 0xb3, 0x6b, 0x8d, 0x84, 0x51, 0x9c, 0xd1, 0x59, 0xbb, 0xb6, 0x5c, 0xe5, 0x27, 0xc3, 0xca, 0xf8, 0xb0, 0xe7, 0xd2, 0xdf, 0xcd, 0x70, 0xa7, 0x4e, 0x91, 0xef, 0xbe, 0x2e, 0x6d, 0x44, 0x84, 0x9c, 0x06, 0xd7, 0xf5, 0x6a, 0xf2, 0xe2, 0x8f, 0xd5, 0xe2, 0x9e, 0x93, 0xbf, 0x51, 0xa2, 0xf1, 0xa8, 0x4d, 0xc0, 0xe0, 0xd2, 0x4b, 0xf4, 0x38, 0xd4, 0x00, 0x50, 0xcb, 0xb8, 0x4d, 0x70, 0x26, 0x8d, 0xc7, 0x14, 0xbe, 0xd4, 0x94, 0xfc, 0x8d, 0x99, 0x1d, 0xaf, 0x8b, 0xed, 0x40, 0xd2, 0xf4, 0xb1, 0xba, 0xd2, 0x88, 0x78, 0x26, 0x6f, 0x56, 0xb4, 0xc2, 0x36, 0x52, 0xc4, 0xcb, 0xa2, 0xa1, 0x49, 0x8f, 0x48, 0x7a, 0x04, 0x92, 0x74, 0xa3, 0x9e, 0x14, 0x89, 0xfc, 0xac, 0x5f, 0x44, 0x2a, 0x8f, 0x6e, 0x0c, 0x96, 0xa9, 0xb0, 0x4c, 0x86, 0x61, 0x4b, 0xec, 0x38, 0x19, 0xec, 0x0c, 0x32, 0x9f, 0x10, 0xb1, 0xb6, 0xe6, 0x50, 0xee, 0x39, 0xcf, 0xcc, 0xa2, 0xf3, 0xd1, 0xb3, 0xd1, 0xf9, 0xd9, 0x0f, 0x61, 0x42, 0xaf, 0x61, 0x31, 0xa5, 0xb7, 0x44, 0xfd, 0xe8, 0x8c, 0xcd, 0xa6, 0xf4, 0x6d, 0xe0, 0x2c, 0xb4, 0x1e, 0xfd, 0x9f, 0x31, 0x42, 0x6a, 0x7f, 0xce, 0xf3, 0x22, 0xb5, 0x9f, 0xdc, 0x4a, 0xca, 0x49, 0x48, 0xef, 0x8d, 0x4d, 0x75, 0x23, 0x57, 0xae, 0x91, 0xe8, 0x7d, 0x73, 0xda, 0xfc, 0xfa, 0xcc, 0xe0, 0x29, 0x7a, 0x3d, 0xa5, 0xe3, 0x25, 0x82, 0x7f, 0xcf, 0x72, 0x97, 0xaf, 0xdd, 0xc7, 0x5a, 0x0d, 0xb3, 0x49, 0x3e, 0xfd, 0xe1, 0xc7, 0x6a, 0xe6, 0x24, 0x96, 0xa5, 0x56, 0x9e, 0x7e, 0x4f, 0x36, 0xc9, 0x3c, 0x04, 0x65, 0xb4, 0x71, 0xa0, 0x13, 0x27, 0xca, 0x10, 0x03, 0x34, 0xb5, 0xa8, 0xc8, 0x50, 0x32, 0xc2, 0x06, 0x37, 0x25, 0x35, 0x28, 0x89, 0xc4, 0xf0, 0x77, 0x66, 0xf6, 0xef, 0x73, 0x7e, 0x52, 0x50, 0x18, 0x56, 0x94, 0xc5, 0xe6, 0x3f, 0x39, 0x3c, 0x20, 0x72, 0x62, 0x12, 0xc5, 0x2b, 0x63, 0xe1, 0xd6, 0x87, 0xb2, 0xa1, 0x64, 0x32, 0xb1, 0x88, 0x13, 0x5f, 0x76, 0xc3, 0x7c, 0xc6, 0x8d, 0x01, 0x50, 0x12, 0xc8, 0xa3, 0x08, 0x83, 0x86, 0xa1, 0x51, 0x69, 0x03, 0xf1, 0x81, 0x12, 0x50, 0xfc, 0x19, 0xf7, 0x17, 0xeb, 0x3f, 0xe5, 0x56, 0x50, 0xbe, 0x13, 0xa1, 0xa6, 0x95, 0x1f, 0x42, 0x12, 0xb7, 0x6e, 0xf9, 0x68, 0x4d, 0x4d, 0x34, 0x68, 0x8d, 0x06, 0xc3, 0x09, 0x44, 0x55, 0x40, 0xa9, 0x4a, 0x8d, 0xb8, 0x12, 0xd5, 0xd5, 0x06, 0x00, 0x29, 0x39, 0x11, 0x67, 0x7b, 0xc1, 0x4a, 0xd5, 0xba, 0xca, 0xe8, 0xa5, 0xc8, 0x7f, 0x8b, 0xb5, 0x4a, 0xec, 0x15, 0xfb, 0xa6, 0x25, 0x92, 0xf3, 0x8e, 0xad, 0x60, 0x23, 0xad, 0x09, 0x7f, 0x53, 0xcc, 0x46, 0x3e, 0xd4, 0x78, 0x59, 0x47, 0xd7, 0x89, 0xc1, 0x81, 0x7b, 0xc6, 0x6f, 0xf9, 0xdb, 0xd7, 0x27, 0x27, 0xbf, 0x0d, 0xf4, 0x0f, 0x55, 0x5c, 0x65, 0xbe, 0x68, 0x3e, 0xb8, 0xed, 0xf1, 0xcb, 0xfb, 0xfa, 0x0e, 0x3c, 0xbe, 0x2d, 0xdc, 0xe7, 0x0b, 0xb5, 0x25, 0xec, 0x4f, 0xdc, 0xbb, 0xf2, 0xe0, 0x58, 0x6c, 0xfb, 0xda, 0x48, 0xbf, 0xab, 0x65, 0xd8, 0x61, 0x0f, 0xd3, 0xe4, 0x7d, 0x23, 0x97, 0x4d, 0x2f, 0x74, 0x77, 0xfd, 0xe4, 0xaa, 0xc6, 0x73, 0x47, 0x73, 0x81, 0xc1, 0x5d, 0x33, 0x5e, 0xd6, 0xe0, 0x8f, 0x84, 0xc2, 0xe7, 0xbc, 0x04, 0x74, 0x77, 0xdf, 0x0d, 0x74, 0x2f, 0x6d, 0xa3, 0x95, 0x1a, 0xe5, 0x62, 0x3e, 0xc3, 0xc8, 0x98, 0xa1, 0xeb, 0xbf, 0x7f, 0xf9, 0x95, 0xaf, 0x1d, 0x1d, 0x54, 0x28, 0x6a, 0xa6, 0xf7, 0x1d, 0x1f, 0x7b, 0xeb, 0x9d, 0xbe, 0xfd, 0x5f, 0x59, 0xf7, 0xa5, 0xd3, 0xc4, 0x84, 0x5a, 0xa9, 0x0f, 0xf0, 0xe8, 0x6c, 0x79, 0x84, 0x7b, 0xe9, 0x83, 0x70, 0x5f, 0xe8, 0xd0, 0xf9, 0xc5, 0x28, 0x67, 0x58, 0x68, 0xb6, 0x0f, 0x95, 0xea, 0x6b, 0x3a, 0x86, 0xc4, 0x2d, 0x0f, 0xbf, 0xeb, 0x08, 0x1d, 0x22, 0x16, 0xac, 0x5c, 0x66, 0x97, 0x44, 0xe1, 0x12, 0x45, 0x33, 0x93, 0xcf, 0xfc, 0x1f, 0xc8, 0xcd, 0xde, 0x11, 0xf2, 0xf4, 0x34, 0xfd, 0xdb, 0x8f, 0x9f, 0x15, 0x49, 0x05, 0xfd, 0xdb, 0xc7, 0x1f, 0x47, 0x6d, 0xe8, 0xe1, 0x11, 0x39, 0x0d, 0xdb, 0x70, 0x21, 0x6c, 0x0b, 0x17, 0xa7, 0xa2, 0x20, 0xc5, 0x1c, 0xac, 0xc8, 0x83, 0x32, 0x50, 0x11, 0x05, 0x13, 0x7e, 0x1c, 0x8c, 0x87, 0xa8, 0x26, 0x10, 0x85, 0x40, 0x14, 0x9a, 0x05, 0x66, 0xb9, 0xfa, 0x11, 0xd9, 0x24, 0xdf, 0x47, 0x85, 0xb7, 0x7f, 0x8a, 0xaa, 0x8e, 0xcd, 0x7c, 0xf0, 0x6d, 0x97, 0x4f, 0x0b, 0x1a, 0x4c, 0x5e, 0xb5, 0xda, 0x65, 0x02, 0x05, 0xb5, 0xdf, 0xf9, 0xcc, 0xcc, 0x07, 0xa4, 0xff, 0x2e, 0xe1, 0x00, 0xb8, 0xfc, 0xae, 0x45, 0x54, 0xcc, 0x65, 0x16, 0x34, 0xfe, 0x06, 0xa7, 0xb3, 0xc1, 0x0f, 0xfe, 0x6c, 0xf4, 0x7e, 0x72, 0x4a, 0xc4, 0xf4, 0x85, 0x7c, 0x7e, 0x08, 0xf3, 0xca, 0xfa, 0x62, 0x8a, 0xb7, 0x73, 0x6a, 0x9a, 0xa1, 0x50, 0xa7, 0x28, 0xca, 0x8e, 0x55, 0x3e, 0x87, 0x54, 0xb8, 0xa2, 0x4c, 0x2c, 0xfd, 0x84, 0x3f, 0xea, 0x8f, 0xf2, 0x01, 0x34, 0xf8, 0x6a, 0x06, 0x2f, 0x09, 0x38, 0x4a, 0xb1, 0x8f, 0x12, 0xf3, 0x81, 0x8c, 0x6b, 0x08, 0x92, 0xce, 0x25, 0x56, 0x97, 0x42, 0xb8, 0x5e, 0xa1, 0x16, 0x4e, 0xa8, 0x1c, 0xa6, 0x7f, 0x05, 0xbf, 0x13, 0x62, 0x3f, 0xb1, 0x5a, 0x14, 0x60, 0x9b, 0x42, 0x07, 0x96, 0x29, 0x2d, 0xec, 0xbf, 0x0b, 0x11, 0x70, 0x89, 0x44, 0x56, 0xed, 0x3a, 0x72, 0xc4, 0x6e, 0x9c, 0xf9, 0xbf, 0x3a, 0x3b, 0xf9, 0x4b, 0xf2, 0x69, 0x9d, 0x6e, 0xe6, 0xfb, 0x0e, 0x0b, 0x38, 0x6d, 0x30, 0xcc, 0x2c, 0x9a, 0xf9, 0x36, 0xe6, 0x3d, 0x90, 0xb6, 0xbf, 0x8f, 0x69, 0x7b, 0x5d, 0xb1, 0x16, 0x85, 0xfc, 0x50, 0xe3, 0x32, 0xdc, 0x59, 0x85, 0x9c, 0x64, 0x18, 0xb0, 0x92, 0xf8, 0x6c, 0xca, 0xce, 0xe3, 0x95, 0xe2, 0x45, 0xca, 0xfe, 0x3e, 0x5c, 0xae, 0x8f, 0x0f, 0x0b, 0x1b, 0x69, 0x35, 0xe4, 0x36, 0x4f, 0x9d, 0x92, 0x7a, 0x01, 0x19, 0xcb, 0x57, 0x3f, 0xfe, 0x26, 0x22, 0x15, 0x62, 0x0c, 0xc0, 0xbd, 0x0c, 0x8a, 0xaf, 0x75, 0x20, 0x79, 0xc2, 0xaa, 0x20, 0x71, 0xd1, 0x68, 0x92, 0x82, 0xb2, 0x20, 0xb3, 0x53, 0x9c, 0x29, 0x34, 0x47, 0x73, 0xdb, 0x76, 0x10, 0x0e, 0xab, 0x99, 0x63, 0x67, 0x4d, 0x14, 0xab, 0x03, 0xb8, 0x3a, 0x29, 0x6b, 0xf2, 0x00, 0x9c, 0x1a, 0xc0, 0xac, 0x40, 0x9c, 0x65, 0xcf, 0xff, 0x79, 0xf6, 0x8e, 0x9d, 0x6d, 0xa7, 0x06, 0x4f, 0xbe, 0x7f, 0x5f, 0x99, 0xbd, 0x6c, 0xf0, 0xf8, 0xe9, 0xd4, 0xe4, 0x1d, 0xbb, 0xc0, 0x29, 0xa1, 0x66, 0xed, 0x05, 0x75, 0x5c, 0x8c, 0xbc, 0x13, 0xf5, 0x06, 0xf7, 0x07, 0xee, 0x23, 0x84, 0xcf, 0x67, 0x47, 0x9e, 0x15, 0x9b, 0x85, 0xa4, 0x19, 0x54, 0xac, 0x8a, 0x26, 0x07, 0x4b, 0x58, 0xc4, 0x90, 0xe5, 0xa0, 0x5e, 0xa1, 0xaa, 0x37, 0x48, 0xbf, 0x70, 0x0e, 0xe1, 0xee, 0xa0, 0x4e, 0xd9, 0x09, 0x3b, 0xcb, 0x99, 0x59, 0x93, 0xa4, 0x5f, 0x04, 0x58, 0x94, 0xe8, 0x91, 0x93, 0x95, 0xd2, 0xe8, 0x50, 0xb9, 0x54, 0xd8, 0x2f, 0xfa, 0xb2, 0xd7, 0xcf, 0xdb, 0x71, 0x62, 0xf3, 0x23, 0xfb, 0x7b, 0x4f, 0xed, 0xbf, 0xfa, 0xc4, 0xf4, 0x7e, 0x1c, 0x77, 0xb7, 0xc1, 0xcb, 0x0a, 0x1f, 0x0a, 0x54, 0x64, 0xf2, 0xee, 0xbd, 0xa8, 0x4f, 0xfb, 0xf6, 0xcb, 0x40, 0x9b, 0xf0, 0x3d, 0x9d, 0x8f, 0xbc, 0xb3, 0x14, 0xff, 0xf4, 0x30, 0xec, 0x13, 0x87, 0xe2, 0xdf, 0xf4, 0x28, 0xe0, 0x09, 0x85, 0x49, 0x52, 0xa8, 0x7c, 0x13, 0x8a, 0xbb, 0x96, 0x36, 0x13, 0x18, 0x66, 0xad, 0x66, 0x23, 0x2d, 0xc3, 0x71, 0xbe, 0x1e, 0x0a, 0xc7, 0x5f, 0xd5, 0x02, 0x31, 0x5f, 0x1c, 0x5c, 0xb3, 0xfb, 0xc7, 0x5f, 0xbf, 0x71, 0xba, 0xe1, 0xf5, 0xb6, 0x23, 0xff, 0x7d, 0xef, 0xa9, 0x53, 0x54, 0xb3, 0x87, 0xc7, 0x13, 0x20, 0xc4, 0xc0, 0xcf, 0xf0, 0x04, 0xcc, 0xac, 0x93, 0xe8, 0xb6, 0xfd, 0xf4, 0xfb, 0xb2, 0xe5, 0xcc, 0x63, 0x84, 0x8a, 0xe8, 0x26, 0x16, 0x14, 0x07, 0x9b, 0x00, 0x80, 0x2c, 0x0f, 0xf9, 0x5f, 0x4a, 0xd6, 0x2f, 0x85, 0x92, 0x84, 0x8a, 0x6a, 0x19, 0xe3, 0xf5, 0xff, 0xe1, 0xed, 0x3d, 0xe0, 0xdb, 0xaa, 0xce, 0xfe, 0xf1, 0x7b, 0xee, 0xd0, 0xde, 0xdb, 0x92, 0x65, 0x6b, 0x4b, 0x96, 0x65, 0x5b, 0xb6, 0x6c, 0xc9, 0xdb, 0xb2, 0xe3, 0x3d, 0x13, 0x6f, 0xc7, 0x76, 0x96, 0x13, 0x67, 0x39, 0x89, 0xe3, 0x0c, 0xb2, 0xc8, 0x26, 0x10, 0x32, 0x58, 0x01, 0xc2, 0x4a, 0x09, 0x23, 0x40, 0xc2, 0x08, 0x90, 0xb0, 0xc2, 0x48, 0x49, 0x4a, 0x0b, 0x94, 0xf6, 0x2d, 0x2d, 0xb4, 0x6f, 0x79, 0xcb, 0x5b, 0x0a, 0x1d, 0x6f, 0x29, 0x2d, 0xd0, 0xd2, 0x02, 0x89, 0x6f, 0x7e, 0xe7, 0x9c, 0x7b, 0x25, 0xcb, 0x23, 0x8b, 0xf6, 0xff, 0xe7, 0x43, 0x12, 0xe9, 0xea, 0x9c, 0x73, 0xcf, 0x7c, 0xd6, 0xf9, 0x3e, 0xcf, 0x83, 0x54, 0x6e, 0x1b, 0xb2, 0x96, 0xe1, 0xbb, 0x35, 0xbb, 0x00, 0x4e, 0x44, 0xe5, 0xb4, 0x50, 0x76, 0x46, 0xba, 0xd3, 0x91, 0x6a, 0x4d, 0x32, 0xaa, 0x95, 0xd8, 0x27, 0x48, 0x22, 0x88, 0x85, 0x69, 0x45, 0x86, 0xe8, 0x38, 0x3c, 0x7d, 0x2c, 0x4e, 0xab, 0x81, 0xcf, 0xb4, 0x01, 0x48, 0x6f, 0xec, 0xea, 0xd3, 0x88, 0xc3, 0x1d, 0x21, 0xc8, 0x27, 0x73, 0x63, 0xfe, 0xa2, 0xdb, 0x7a, 0xe7, 0xde, 0x3d, 0x5c, 0x2d, 0xfb, 0x40, 0xb5, 0xf8, 0xe8, 0x9f, 0xf6, 0x6f, 0xff, 0xcd, 0xfd, 0xbd, 0xc9, 0xee, 0xf3, 0x4e, 0xa6, 0x66, 0xf3, 0xd3, 0x2b, 0x66, 0xef, 0x1e, 0xa8, 0x4a, 0x56, 0x9b, 0x45, 0xd1, 0xc8, 0x8e, 0x5b, 0xef, 0x69, 0xb9, 0xf9, 0x9f, 0xcf, 0x2f, 0x15, 0x88, 0x4a, 0x57, 0x75, 0xe7, 0xaa, 0x75, 0x2b, 0x92, 0xb4, 0xca, 0xe6, 0x0a, 0x6a, 0xe6, 0xac, 0x7b, 0x96, 0x97, 0x14, 0x2f, 0xbb, 0x67, 0xce, 0xb6, 0x0f, 0xee, 0xef, 0xed, 0x7c, 0xf0, 0x6f, 0x77, 0x95, 0x05, 0xae, 0x7b, 0x73, 0xd7, 0xb4, 0x40, 0x6e, 0x40, 0x21, 0x9d, 0x5e, 0xda, 0x91, 0x67, 0x9a, 0xf5, 0x02, 0x50, 0x1c, 0x37, 0x14, 0xcc, 0x41, 0xc6, 0x0c, 0x3d, 0x93, 0x22, 0x55, 0x64, 0xa6, 0x16, 0x65, 0xa2, 0xb9, 0x60, 0x8f, 0xf1, 0x73, 0x51, 0x45, 0xb4, 0x46, 0x5b, 0x8a, 0xf8, 0xb9, 0xc8, 0xf0, 0x93, 0x02, 0x94, 0x82, 0xf1, 0x72, 0x13, 0x82, 0x54, 0x31, 0x2b, 0x9a, 0x90, 0xaa, 0xca, 0xdc, 0x9c, 0xc0, 0x77, 0x98, 0x10, 0xc0, 0x47, 0x66, 0xfd, 0xce, 0x33, 0xd2, 0xf7, 0xd7, 0x1d, 0x67, 0xb7, 0xbc, 0x77, 0xa8, 0x8f, 0xb9, 0x8a, 0x09, 0x59, 0x38, 0xa8, 0x40, 0x5e, 0x46, 0x8a, 0xa5, 0xcf, 0xb1, 0x0f, 0x4c, 0x9a, 0x13, 0x24, 0x27, 0xdd, 0xcd, 0xa4, 0xc1, 0xbd, 0x12, 0x24, 0xae, 0xe5, 0xef, 0xeb, 0xa0, 0x2c, 0x24, 0x50, 0x01, 0x2e, 0x12, 0x78, 0xec, 0x0b, 0x19, 0xbf, 0xaf, 0x4b, 0x25, 0x04, 0x04, 0x45, 0x0b, 0x28, 0x14, 0x6a, 0x07, 0x51, 0x60, 0x9a, 0x36, 0x8f, 0x99, 0xbb, 0xac, 0x04, 0x4e, 0x28, 0x05, 0x8f, 0x15, 0x8d, 0x2c, 0x38, 0x93, 0x4b, 0xc6, 0x0c, 0x5d, 0x56, 0x94, 0xd2, 0x5b, 0x8a, 0xd4, 0x1f, 0xa8, 0xcc, 0x66, 0x60, 0x5d, 0x16, 0x0a, 0xb5, 0xcc, 0xd4, 0x9a, 0xac, 0x7d, 0xbc, 0x2e, 0x44, 0x8d, 0x58, 0x1c, 0x65, 0x9b, 0x56, 0xcc, 0xaa, 0xf2, 0x36, 0xee, 0x79, 0x63, 0xc3, 0xbc, 0x21, 0x19, 0x96, 0x6f, 0xa0, 0x3e, 0x3b, 0x2b, 0xb7, 0xe9, 0xda, 0x43, 0x33, 0xd8, 0x0f, 0x13, 0x75, 0x22, 0xaa, 0xd5, 0x67, 0x14, 0x24, 0xe7, 0xb5, 0xae, 0xba, 0x67, 0x60, 0xff, 0x47, 0xf7, 0xb6, 0x5b, 0xb6, 0x68, 0x0c, 0xaa, 0x9c, 0xc1, 0x43, 0x43, 0x3b, 0x1e, 0xe8, 0x49, 0x4d, 0x54, 0x8b, 0xb0, 0x0f, 0xe0, 0xb7, 0x90, 0x76, 0x7d, 0x82, 0x69, 0xd7, 0x62, 0x4e, 0x96, 0x70, 0xc1, 0xfe, 0xf2, 0x56, 0x07, 0x2c, 0x99, 0x82, 0x5e, 0x4e, 0x1a, 0xc6, 0x5f, 0x70, 0x58, 0x1f, 0x3c, 0x62, 0xdf, 0x58, 0x31, 0x22, 0xfe, 0x33, 0x16, 0x24, 0x9c, 0xe3, 0x8b, 0xf6, 0x40, 0x99, 0x13, 0x13, 0x3b, 0x83, 0xc3, 0xc3, 0xcb, 0xb4, 0x6e, 0x6c, 0x2e, 0xb2, 0x4b, 0x48, 0x34, 0x72, 0x2e, 0x39, 0xb6, 0x19, 0x68, 0xed, 0x6a, 0xe6, 0x93, 0xbb, 0x7e, 0x3a, 0xf2, 0x7d, 0xb6, 0x48, 0x91, 0x5d, 0xd9, 0xb9, 0x70, 0x6d, 0xc5, 0xda, 0x1a, 0x4f, 0x53, 0x4d, 0x79, 0xae, 0x5b, 0x42, 0xcd, 0x7f, 0x04, 0x59, 0x7d, 0xe8, 0xaf, 0xd8, 0xcf, 0xd8, 0x21, 0xf6, 0xe3, 0x13, 0x83, 0x6e, 0xff, 0xf5, 0xd6, 0xde, 0x63, 0xc0, 0x02, 0xee, 0x05, 0xa9, 0xe7, 0xa4, 0xbc, 0x3e, 0x7a, 0x80, 0xa9, 0x83, 0xe3, 0xb0, 0x12, 0x8f, 0x70, 0x98, 0x21, 0x11, 0xf6, 0x73, 0xb0, 0x9c, 0xe4, 0x7c, 0x2a, 0xf8, 0x95, 0xb4, 0xf1, 0xeb, 0x33, 0x17, 0x73, 0x83, 0xa9, 0x96, 0x32, 0x6e, 0xb9, 0x1c, 0xb7, 0x9e, 0xe3, 0xca, 0x27, 0x2c, 0x28, 0xb6, 0x5c, 0x8e, 0x2f, 0xc9, 0x5b, 0x30, 0x13, 0x2a, 0xa0, 0xdc, 0x23, 0x38, 0x86, 0x91, 0x95, 0xb0, 0x22, 0x09, 0xd7, 0xa1, 0x73, 0x23, 0x6e, 0x03, 0xd4, 0x93, 0x56, 0x3e, 0x2e, 0xe7, 0x33, 0x75, 0x45, 0xab, 0x8f, 0xae, 0x88, 0xdb, 0x2d, 0xa2, 0xf3, 0x2a, 0x9d, 0x31, 0xa9, 0xff, 0xdb, 0x5f, 0x8e, 0x3c, 0xb3, 0x31, 0x9a, 0xbc, 0x05, 0x0a, 0xae, 0xa9, 0xd5, 0x2b, 0x67, 0x90, 0x49, 0x63, 0x1a, 0x00, 0xaf, 0x97, 0x0b, 0x10, 0xce, 0x54, 0x39, 0x59, 0xb7, 0xe1, 0xd6, 0x11, 0x69, 0x38, 0xb1, 0xb5, 0xe1, 0x52, 0x6f, 0x70, 0x1c, 0x50, 0x8d, 0x39, 0x20, 0xb7, 0x32, 0x63, 0x8a, 0x4d, 0x3a, 0x5a, 0x91, 0x98, 0x62, 0x83, 0x16, 0x61, 0x9c, 0x5a, 0x83, 0x79, 0xdf, 0x31, 0xcc, 0x6b, 0x30, 0xef, 0x4b, 0x92, 0x90, 0x14, 0x72, 0x39, 0xa3, 0xe0, 0x94, 0x50, 0xe4, 0x30, 0xce, 0x34, 0x86, 0x25, 0x16, 0x8c, 0x93, 0x4e, 0x90, 0x90, 0xe0, 0x76, 0x40, 0xac, 0x8f, 0x97, 0x90, 0x9c, 0x1c, 0xf7, 0x0b, 0x4d, 0xe0, 0x7e, 0xf4, 0xd6, 0x3f, 0x43, 0xe6, 0xfb, 0xab, 0x04, 0xe6, 0x87, 0xf8, 0x0c, 0xfb, 0x67, 0xab, 0x03, 0x9c, 0x07, 0xa7, 0x26, 0xb3, 0xbf, 0xd8, 0xf8, 0x85, 0xb0, 0x83, 0x84, 0x0b, 0xd9, 0x25, 0x92, 0x95, 0x90, 0xed, 0x21, 0x5e, 0x4c, 0x0b, 0x68, 0x52, 0x30, 0xcc, 0x24, 0x4c, 0x03, 0x46, 0x34, 0x8d, 0x4d, 0x83, 0x8b, 0x70, 0xa5, 0xdb, 0xf5, 0xe1, 0xc4, 0xfd, 0x89, 0x2e, 0x56, 0x71, 0x5a, 0xcf, 0x84, 0xfc, 0x99, 0x38, 0xdb, 0x1d, 0x67, 0xd3, 0x14, 0xae, 0x87, 0x53, 0x73, 0x84, 0x22, 0x59, 0x47, 0x52, 0x6e, 0x4a, 0xae, 0x89, 0x4d, 0x12, 0x9c, 0x3a, 0x94, 0x9c, 0xaa, 0x00, 0xbf, 0x55, 0x98, 0xa4, 0xfa, 0x64, 0xf0, 0x4b, 0x79, 0x6a, 0xf2, 0x21, 0x3c, 0x63, 0x14, 0x6b, 0xcc, 0xb5, 0x9c, 0xbf, 0x2d, 0x3b, 0x9b, 0x1a, 0xce, 0xcf, 0x3e, 0x4f, 0x32, 0xf7, 0x4a, 0x93, 0xce, 0x3f, 0x64, 0x2e, 0xb4, 0x16, 0x45, 0xa8, 0xde, 0x24, 0x29, 0xc7, 0xb3, 0x49, 0xa2, 0xed, 0xc2, 0x17, 0x4c, 0x13, 0xa6, 0xd3, 0xb9, 0x97, 0xcc, 0x7b, 0x65, 0x8b, 0x5f, 0xd6, 0xd8, 0x11, 0x40, 0x38, 0x27, 0x88, 0x12, 0x83, 0x59, 0x92, 0x78, 0x92, 0x2c, 0x42, 0x24, 0x39, 0x16, 0x2e, 0x95, 0xbb, 0x38, 0x05, 0x08, 0x28, 0x99, 0x90, 0x96, 0x8d, 0xc3, 0x55, 0xd1, 0x58, 0x26, 0xb3, 0xc7, 0x29, 0xf0, 0xb9, 0xd1, 0xc8, 0x8e, 0x5b, 0xee, 0x6d, 0x39, 0x70, 0xfe, 0xf9, 0x85, 0x7d, 0x4f, 0x7e, 0x73, 0xdf, 0xf6, 0xff, 0x39, 0xdc, 0x2b, 0x50, 0x26, 0x69, 0xa9, 0x0f, 0x31, 0x31, 0x8e, 0x0e, 0xb6, 0x55, 0xe4, 0xa6, 0x6b, 0x20, 0x39, 0x26, 0x6b, 0x63, 0x74, 0x97, 0xdc, 0x5d, 0xd2, 0x99, 0x67, 0x9a, 0xff, 0x3a, 0xfb, 0xbb, 0x17, 0x6f, 0x67, 0xd9, 0x57, 0x97, 0x23, 0x1a, 0x9c, 0x9a, 0x66, 0x92, 0x40, 0x2a, 0x5c, 0xa9, 0x4f, 0x75, 0xa5, 0xea, 0x15, 0x78, 0x7f, 0x04, 0x2f, 0x7c, 0x41, 0x97, 0xc3, 0xf5, 0xc8, 0x41, 0x91, 0xcb, 0xb2, 0x20, 0xe7, 0xf1, 0x42, 0x96, 0xaf, 0xc4, 0x71, 0x94, 0x08, 0x14, 0x66, 0x9e, 0x8b, 0x87, 0x4d, 0xa3, 0x78, 0xd8, 0xbc, 0xdb, 0x30, 0xde, 0x30, 0xa0, 0x8f, 0xc0, 0x9a, 0x10, 0xd4, 0x07, 0xb3, 0x7c, 0x2a, 0x81, 0xc0, 0x3c, 0x16, 0x29, 0x33, 0x06, 0x14, 0x33, 0x18, 0x39, 0x3e, 0xc3, 0xa7, 0xbd, 0x40, 0xcb, 0x15, 0x8b, 0x8d, 0xb9, 0x60, 0xcb, 0xc9, 0x55, 0x79, 0xbd, 0x77, 0xbf, 0xbd, 0xb2, 0xee, 0xc6, 0x8d, 0x8b, 0xd3, 0x5a, 0xa5, 0x5a, 0x81, 0x4c, 0xa7, 0x55, 0xe5, 0x35, 0xcd, 0x2f, 0x6b, 0xdf, 0xda, 0x9d, 0xe1, 0x69, 0xdf, 0x3d, 0x70, 0x4c, 0xa1, 0x06, 0x1b, 0x46, 0x87, 0xa8, 0x57, 0xa4, 0x06, 0x6a, 0x1d, 0x53, 0x3e, 0xb4, 0x7f, 0xc6, 0xea, 0x97, 0xb6, 0x57, 0xa7, 0x14, 0xb4, 0xe6, 0xcd, 0x92, 0xd1, 0x22, 0x95, 0x26, 0xe0, 0x35, 0xe7, 0x74, 0x0e, 0x97, 0x45, 0xfa, 0x2b, 0xbd, 0x7d, 0x6a, 0x97, 0x1a, 0xac, 0x7c, 0xa2, 0x45, 0x93, 0xa6, 0x83, 0xba, 0x6b, 0x0f, 0x5c, 0xa7, 0x4f, 0x99, 0xc7, 0xe1, 0x6c, 0xeb, 0x89, 0xb4, 0x8b, 0xc4, 0x7a, 0xb1, 0xc5, 0x03, 0x98, 0xdb, 0x99, 0x46, 0xa3, 0x01, 0x79, 0x18, 0xdb, 0x53, 0x0d, 0x69, 0xc6, 0x34, 0xa9, 0x5e, 0xaa, 0xe7, 0x57, 0x4a, 0x8c, 0x56, 0x8a, 0xf7, 0x30, 0xe6, 0xf3, 0x19, 0x0b, 0x01, 0x0f, 0x6b, 0x15, 0xc0, 0xd5, 0x71, 0x79, 0x38, 0x35, 0x0a, 0xe4, 0x89, 0x9b, 0xb6, 0x3e, 0xb9, 0x78, 0xf1, 0x93, 0x5b, 0x9a, 0xc4, 0xa4, 0xac, 0x65, 0xf3, 0xd1, 0x85, 0x83, 0x47, 0xb7, 0x4c, 0x97, 0x81, 0x4f, 0xef, 0x07, 0x82, 0x97, 0x17, 0x2e, 0x7c, 0x99, 0xfd, 0xf6, 0xfe, 0xef, 0xb1, 0xdf, 0x9c, 0x5a, 0xb4, 0xe8, 0x14, 0x10, 0x92, 0x37, 0xae, 0x7c, 0x76, 0x53, 0x79, 0xf9, 0xa6, 0x67, 0x57, 0x1e, 0x5e, 0x7a, 0x74, 0x6d, 0x29, 0xc2, 0xb7, 0x90, 0xdb, 0x96, 0x9d, 0x61, 0xbf, 0xbc, 0xff, 0x30, 0xfb, 0xf7, 0x33, 0x43, 0x43, 0x67, 0x80, 0xfc, 0xf0, 0xfd, 0x40, 0x71, 0x66, 0x19, 0x77, 0x4e, 0x68, 0x23, 0xb6, 0x91, 0x43, 0x5d, 0x83, 0x89, 0xe5, 0x56, 0x9f, 0x52, 0x9d, 0x91, 0x11, 0x32, 0xa4, 0xcd, 0x08, 0x90, 0x54, 0x96, 0xa0, 0xcd, 0xe8, 0xc1, 0xf5, 0x48, 0x97, 0xf9, 0x14, 0x99, 0x9b, 0xcf, 0x3d, 0xc3, 0x49, 0xa5, 0x9c, 0xbd, 0xd4, 0xcd, 0x1e, 0xc0, 0xf6, 0x6c, 0x07, 0xf1, 0x63, 0x7c, 0xcd, 0x79, 0x52, 0x09, 0x10, 0x59, 0xb0, 0xa0, 0x7f, 0x19, 0x10, 0x8b, 0x94, 0x9b, 0x0e, 0x29, 0xb3, 0x90, 0xd3, 0x6c, 0x50, 0x06, 0x3c, 0x71, 0x0f, 0x21, 0x16, 0xe3, 0xb5, 0xc7, 0x42, 0x87, 0xb9, 0x21, 0xae, 0xf4, 0x5a, 0xa9, 0x18, 0x59, 0x86, 0xca, 0xab, 0x00, 0x41, 0x83, 0x86, 0x2f, 0x53, 0x35, 0xa6, 0xd3, 0xa2, 0x9a, 0xd1, 0xac, 0xc9, 0x95, 0x20, 0x5d, 0x84, 0xd2, 0xf9, 0xec, 0xa9, 0xea, 0xe2, 0x58, 0xfd, 0x3a, 0x4e, 0x8f, 0x41, 0xc1, 0x39, 0xb4, 0x76, 0xb7, 0x1b, 0x12, 0x47, 0x19, 0xd2, 0x79, 0xb1, 0xb9, 0x3d, 0x34, 0x45, 0xb0, 0x11, 0xfb, 0x98, 0x09, 0xbe, 0x93, 0x3d, 0xfa, 0x2b, 0x6b, 0xf9, 0xc2, 0x7a, 0x7f, 0x63, 0x86, 0x52, 0x68, 0x96, 0xc9, 0x33, 0x6d, 0xd1, 0x20, 0x3b, 0xfa, 0xe5, 0x97, 0xbc, 0x51, 0x9e, 0x55, 0x7d, 0x5b, 0xb6, 0xb0, 0x3e, 0x4d, 0xa3, 0x9d, 0x6e, 0x52, 0x2a, 0x3b, 0x9b, 0x8a, 0x79, 0x13, 0x3d, 0xd2, 0xb3, 0x0e, 0x30, 0xfd, 0x58, 0x26, 0x79, 0x9a, 0x9b, 0x1e, 0x79, 0x96, 0xd7, 0x9e, 0x44, 0x0b, 0x19, 0x31, 0x2f, 0x96, 0x24, 0x7c, 0x27, 0xe3, 0xd1, 0x86, 0x53, 0x38, 0x9d, 0x10, 0xd1, 0x74, 0xaa, 0x0f, 0xa9, 0x1a, 0x53, 0x72, 0x33, 0x77, 0xec, 0x76, 0x6d, 0x72, 0xe9, 0x71, 0xbc, 0xcc, 0x31, 0xe1, 0x16, 0x6e, 0x52, 0x71, 0x2a, 0xc6, 0xc9, 0x82, 0x44, 0x10, 0x2a, 0x79, 0x4e, 0x38, 0x37, 0x98, 0x6b, 0x4c, 0x15, 0xb0, 0xed, 0x52, 0x3a, 0x5f, 0x7f, 0xec, 0xd6, 0x4d, 0x68, 0x86, 0x42, 0x19, 0x9c, 0xa1, 0x4d, 0x97, 0x57, 0x00, 0x37, 0x3d, 0xfc, 0x2e, 0xba, 0x6d, 0x8b, 0xcf, 0xdc, 0x25, 0x14, 0xc1, 0x17, 0xd1, 0x7d, 0x30, 0x3c, 0x81, 0x05, 0x70, 0x3e, 0x6d, 0x08, 0x99, 0x64, 0x80, 0xfb, 0x8f, 0xc1, 0xf3, 0x06, 0x75, 0x41, 0x21, 0x82, 0x73, 0x92, 0xf3, 0x38, 0x66, 0x00, 0x38, 0x23, 0x24, 0x32, 0x49, 0x43, 0xb9, 0xcc, 0x86, 0xa4, 0x32, 0x7b, 0x62, 0xd7, 0x11, 0x49, 0x89, 0xf7, 0x1e, 0xf9, 0x3f, 0xc3, 0x2d, 0xc0, 0x14, 0xfc, 0x22, 0xb1, 0xbb, 0x6f, 0x80, 0x67, 0xd8, 0xfb, 0xc1, 0x7c, 0x76, 0xd6, 0x2f, 0x8c, 0x7a, 0xbe, 0xcb, 0x9a, 0x94, 0xb7, 0xd9, 0x4e, 0xb0, 0x88, 0x3d, 0x92, 0xd0, 0xc9, 0xfd, 0x87, 0xc9, 0x65, 0xb1, 0x5e, 0xda, 0x46, 0x6f, 0x3b, 0x0c, 0x27, 0x38, 0x82, 0x28, 0x20, 0x3e, 0x83, 0x16, 0x1c, 0x91, 0x89, 0xf7, 0xfb, 0xe5, 0xb8, 0x35, 0x0e, 0xcb, 0xd2, 0xc7, 0x69, 0xfb, 0x72, 0x39, 0x41, 0xc8, 0x2d, 0x72, 0x8b, 0xd9, 0xa4, 0x52, 0xc0, 0xd2, 0x52, 0xbb, 0x10, 0x52, 0x0f, 0xb7, 0x3a, 0x6e, 0x56, 0x81, 0x3b, 0x93, 0x27, 0xef, 0x90, 0x31, 0x51, 0x99, 0xbf, 0x9e, 0xb1, 0xb1, 0xcd, 0x9f, 0xd6, 0xba, 0xb1, 0x0d, 0x3c, 0xc5, 0xce, 0xb8, 0xf1, 0xc7, 0xbb, 0xca, 0xab, 0x6f, 0x78, 0x7b, 0xe7, 0xaf, 0x0f, 0x93, 0xf5, 0x81, 0x96, 0xe5, 0xe5, 0x15, 0x4b, 0x1a, 0xd2, 0xe8, 0x73, 0xdf, 0xce, 0x8f, 0xae, 0x3d, 0xb2, 0x60, 0xc1, 0x63, 0x1b, 0x2a, 0x49, 0x25, 0xe6, 0x3f, 0x91, 0x0b, 0x5f, 0x31, 0x5f, 0xc1, 0xbe, 0xa8, 0x88, 0x32, 0x30, 0x70, 0x32, 0x05, 0x40, 0x75, 0x80, 0x77, 0x0b, 0x71, 0x89, 0x01, 0x0a, 0x94, 0x2a, 0x84, 0x7a, 0x1a, 0x8d, 0x8e, 0x10, 0x8d, 0x6d, 0x46, 0x48, 0xa7, 0x8d, 0xb9, 0xb9, 0xdb, 0xc9, 0xd8, 0x4d, 0xf8, 0xe5, 0xcb, 0x3a, 0xe3, 0x69, 0xb3, 0xae, 0xb4, 0x5d, 0xf3, 0x55, 0xb4, 0x9b, 0x7c, 0x15, 0xed, 0x7a, 0xae, 0xb8, 0xdd, 0x68, 0xe6, 0x45, 0x8a, 0xc5, 0x1d, 0xfd, 0x9d, 0xe8, 0x9e, 0x3a, 0x5e, 0x81, 0xbf, 0xa5, 0xb6, 0xa8, 0xd5, 0x80, 0x50, 0x97, 0xa9, 0x4b, 0x43, 0xd9, 0xe8, 0x3a, 0xd5, 0x61, 0x4b, 0x36, 0x4b, 0xc5, 0x84, 0x0a, 0xa8, 0x24, 0x82, 0x31, 0x04, 0x3e, 0x27, 0xf7, 0x31, 0x63, 0x61, 0xa0, 0xc7, 0x58, 0x35, 0xbe, 0xfc, 0x19, 0x0b, 0xf4, 0x82, 0xcc, 0x66, 0x64, 0x3b, 0x5a, 0x5a, 0x6f, 0xfd, 0xa2, 0x8a, 0xd2, 0x79, 0x95, 0x2e, 0xf2, 0x71, 0x46, 0x22, 0x64, 0xb2, 0xba, 0xb7, 0xb7, 0x0e, 0xdd, 0xec, 0x28, 0x7c, 0x7b, 0xe7, 0xe0, 0x43, 0xab, 0x4b, 0xf1, 0x52, 0xbf, 0xd7, 0x50, 0x1f, 0x28, 0x71, 0xab, 0x19, 0x21, 0xe3, 0xb1, 0x93, 0x9f, 0x06, 0xa6, 0x2f, 0xaf, 0x28, 0x5f, 0xda, 0xe8, 0x77, 0x96, 0x76, 0x85, 0xc0, 0xc0, 0x66, 0x67, 0x4d, 0x45, 0x91, 0x61, 0xe6, 0xbe, 0xb9, 0xa1, 0x59, 0xbd, 0xd3, 0xe7, 0x95, 0x5f, 0xf3, 0xf0, 0xc0, 0xc2, 0x63, 0x1b, 0xe1, 0x2e, 0x38, 0xf7, 0xda, 0xd0, 0x6c, 0x6b, 0x64, 0x7a, 0x28, 0xb5, 0x28, 0xe4, 0x93, 0xa5, 0x7a, 0x70, 0xbe, 0x79, 0xc8, 0xeb, 0xde, 0x67, 0x8e, 0x11, 0xb5, 0x44, 0x43, 0xb4, 0xd6, 0x0f, 0x69, 0x77, 0x1e, 0xce, 0xed, 0x07, 0x37, 0x2d, 0x0a, 0xa8, 0x81, 0xe2, 0x96, 0x03, 0x01, 0x40, 0x40, 0x79, 0x48, 0x53, 0x85, 0x24, 0x36, 0xee, 0x63, 0xae, 0x67, 0x8b, 0x03, 0xe5, 0xed, 0x58, 0xb8, 0xaa, 0x25, 0x6a, 0x52, 0xdd, 0x4e, 0x9f, 0xdd, 0x97, 0x86, 0xe5, 0xde, 0x44, 0xaf, 0x47, 0x3e, 0x63, 0xd5, 0x98, 0x4d, 0x97, 0xcb, 0x4f, 0x3a, 0x2e, 0xe6, 0x6c, 0x0a, 0x69, 0xe4, 0xf0, 0x9d, 0x68, 0x06, 0x98, 0x53, 0x78, 0xcc, 0x3d, 0xdb, 0x5a, 0x5b, 0x6f, 0x98, 0x1b, 0x76, 0xd5, 0x0f, 0x37, 0xbc, 0x71, 0x0a, 0xa1, 0x83, 0xbd, 0x75, 0x4b, 0x2b, 0x5b, 0x56, 0x34, 0x87, 0x34, 0x6a, 0x83, 0x24, 0xb7, 0xf5, 0x83, 0xdd, 0xc3, 0x27, 0x36, 0x57, 0x2e, 0x9c, 0x7d, 0xed, 0x2a, 0x9d, 0xb7, 0xc0, 0xdd, 0x53, 0x23, 0x10, 0x08, 0x3c, 0x0e, 0x72, 0x71, 0x7c, 0xe8, 0x99, 0xb3, 0x6e, 0x9d, 0xdf, 0x3a, 0x52, 0x6b, 0x7f, 0xca, 0x1a, 0x99, 0x91, 0x9b, 0xd5, 0x1c, 0x49, 0xb5, 0xa7, 0xd9, 0x55, 0xea, 0x05, 0xb5, 0x4d, 0x91, 0x79, 0x37, 0xcc, 0x58, 0xf2, 0x60, 0x30, 0xf3, 0xad, 0x43, 0x45, 0x2d, 0x39, 0x66, 0x7a, 0xe5, 0x60, 0x7c, 0x3a, 0x50, 0x9e, 0x32, 0xc8, 0x33, 0x4f, 0x60, 0x3f, 0x97, 0xda, 0x68, 0x95, 0x13, 0xd0, 0x82, 0x4c, 0x84, 0xd1, 0xac, 0x47, 0xb8, 0x4c, 0x9a, 0x41, 0x72, 0x0c, 0x81, 0x92, 0x0a, 0x0f, 0xc4, 0x0c, 0x09, 0x36, 0xee, 0x36, 0xa1, 0x67, 0xbc, 0xd9, 0x27, 0x87, 0xc8, 0xf1, 0x68, 0x7d, 0x76, 0xf7, 0x84, 0xcb, 0x04, 0x3c, 0x13, 0xde, 0x4b, 0xce, 0x84, 0x15, 0xd0, 0x27, 0x10, 0xc3, 0x75, 0xd6, 0xad, 0x68, 0x78, 0x03, 0x24, 0x8c, 0xba, 0x29, 0xa4, 0x55, 0x1b, 0xc4, 0xb9, 0x33, 0xd0, 0xa8, 0xb7, 0x54, 0x2e, 0x99, 0x3b, 0xbc, 0x83, 0x37, 0x12, 0x81, 0x6b, 0xa6, 0xaf, 0xac, 0x99, 0x7a, 0x90, 0x4b, 0x1f, 0xce, 0x4a, 0x7f, 0xfa, 0xc6, 0xcf, 0x38, 0x1d, 0xf0, 0xc2, 0xdf, 0x04, 0xff, 0x07, 0xcf, 0x7e, 0x1e, 0xb1, 0x90, 0x07, 0xa0, 0xa0, 0x85, 0xa5, 0x00, 0xca, 0xca, 0xc2, 0x87, 0xdd, 0x49, 0xe2, 0x13, 0x3b, 0xa1, 0x4b, 0x1b, 0x3b, 0xe2, 0x15, 0xfe, 0x29, 0x8a, 0x10, 0xf1, 0x12, 0x5c, 0xb0, 0x3b, 0xbe, 0x30, 0xa7, 0x01, 0xe6, 0x11, 0xb9, 0x6e, 0xa8, 0x02, 0xea, 0xd3, 0x44, 0xe3, 0x85, 0xb9, 0xbc, 0x29, 0x14, 0x41, 0x5d, 0x2c, 0x04, 0x1b, 0x5e, 0x72, 0xc1, 0x4f, 0xf0, 0x92, 0xcf, 0xdc, 0xd1, 0xd1, 0xb4, 0xb5, 0x2f, 0x67, 0x2a, 0xf5, 0x50, 0xe3, 0xca, 0x75, 0x74, 0x56, 0x73, 0xdb, 0x7d, 0x21, 0x5c, 0xe4, 0x42, 0xc3, 0xcc, 0xfd, 0x73, 0x43, 0x19, 0xdd, 0xdb, 0x3b, 0xa9, 0xc7, 0xa6, 0xd4, 0x19, 0x8f, 0x87, 0xeb, 0x33, 0x74, 0xc3, 0x83, 0xa9, 0xc5, 0x68, 0x71, 0xdd, 0x58, 0x6f, 0xb8, 0xf0, 0xb9, 0x30, 0x08, 0xe7, 0xc0, 0x4f, 0x2c, 0xe2, 0x54, 0x45, 0xe7, 0xd8, 0xf6, 0xa6, 0x09, 0x74, 0xd9, 0x3b, 0x97, 0xc1, 0xa6, 0x3c, 0xbc, 0xb5, 0xe3, 0xd3, 0xe0, 0xbd, 0x58, 0x29, 0x2c, 0x41, 0x8d, 0x9d, 0x02, 0x8e, 0x6d, 0xfa, 0x89, 0x34, 0x37, 0x4e, 0xbc, 0x30, 0xe9, 0x20, 0x24, 0xe8, 0x5c, 0xe3, 0x07, 0x2f, 0xd4, 0x73, 0x83, 0xdf, 0xd9, 0xde, 0xbc, 0xa5, 0x2f, 0x87, 0xd7, 0xc4, 0x34, 0xae, 0x3c, 0x38, 0x5e, 0x6e, 0x53, 0x2f, 0x8a, 0x6f, 0xea, 0x40, 0xf7, 0x8e, 0x4e, 0x50, 0x18, 0x57, 0xcf, 0x9e, 0x0e, 0x37, 0x64, 0xe8, 0x12, 0x36, 0x30, 0x40, 0xf7, 0x19, 0xcc, 0x49, 0x28, 0xbb, 0xe6, 0x10, 0x79, 0xd1, 0x1c, 0x3d, 0xf6, 0xa9, 0x41, 0x19, 0xad, 0x90, 0x05, 0x08, 0xc1, 0x8a, 0xf1, 0xa5, 0x8d, 0x2d, 0x9e, 0x57, 0xd7, 0x1e, 0xdb, 0xae, 0x2e, 0x5f, 0xc0, 0xe7, 0xc6, 0x9a, 0x1a, 0xd7, 0xdd, 0x98, 0x68, 0x1a, 0xb3, 0xf0, 0xf3, 0xe2, 0x6b, 0x9c, 0x9a, 0xe1, 0xd4, 0x1a, 0xcc, 0xc9, 0xee, 0x07, 0xff, 0x78, 0xf3, 0xa3, 0xec, 0x67, 0x67, 0x96, 0x2e, 0x3c, 0x0d, 0xd4, 0x47, 0x3b, 0x6f, 0xdb, 0xbc, 0x34, 0xa3, 0x58, 0x91, 0x69, 0xb1, 0x94, 0x75, 0xae, 0x69, 0x59, 0xf3, 0xea, 0xae, 0xda, 0xa6, 0xfd, 0x6f, 0x5d, 0x3b, 0xff, 0xbe, 0xa1, 0x02, 0xf6, 0x39, 0x7d, 0x12, 0x79, 0xea, 0xfa, 0x1b, 0x7e, 0x79, 0x47, 0x0b, 0x36, 0xcd, 0x3f, 0x08, 0x94, 0xdf, 0x5f, 0x8a, 0xf2, 0xe9, 0xcd, 0x31, 0xca, 0xd4, 0x4a, 0x9f, 0xc3, 0xd4, 0x74, 0xf3, 0xdb, 0x9b, 0xaf, 0xfb, 0xd9, 0xcd, 0x8d, 0xd9, 0x73, 0xf6, 0xf7, 0x9b, 0x34, 0xeb, 0x39, 0x3d, 0x37, 0xf9, 0xc2, 0x57, 0xf4, 0x03, 0x58, 0xcf, 0xfb, 0x96, 0x63, 0x53, 0xa9, 0xb1, 0x00, 0xfc, 0xe8, 0xbe, 0x96, 0xe9, 0xe1, 0x8d, 0xe5, 0x7c, 0x6c, 0x2d, 0x7b, 0xdc, 0x8d, 0xf1, 0x92, 0xc5, 0x9c, 0x71, 0x7f, 0xc6, 0x2b, 0x68, 0xcd, 0x7c, 0x65, 0xad, 0x25, 0x5f, 0x49, 0x6b, 0x97, 0x6d, 0x88, 0xe3, 0x28, 0x0a, 0x4e, 0x53, 0xd5, 0x7b, 0xe1, 0x36, 0xe2, 0x16, 0x64, 0xc2, 0x59, 0x29, 0xc2, 0x7b, 0x8a, 0x07, 0xf9, 0xdb, 0xf3, 0xe8, 0x1d, 0x6c, 0x7e, 0xc2, 0x5e, 0x59, 0x86, 0xb6, 0xd2, 0xf3, 0x47, 0xd1, 0x46, 0x62, 0xc3, 0xcc, 0xbd, 0xcf, 0x9c, 0x5f, 0x34, 0x6e, 0x8b, 0xe0, 0x8d, 0xf4, 0xdc, 0x0f, 0xd0, 0x36, 0xa2, 0x0e, 0x3e, 0x83, 0xe2, 0x95, 0x40, 0xdd, 0x6d, 0x84, 0x79, 0x82, 0xa8, 0x46, 0x71, 0x90, 0x93, 0xe1, 0x69, 0x57, 0x22, 0xbf, 0x87, 0xfa, 0x8b, 0xf8, 0x49, 0x0d, 0xc4, 0xee, 0xfd, 0xc6, 0x91, 0xff, 0xaa, 0x69, 0x25, 0x45, 0xa1, 0xec, 0x60, 0xa6, 0xc7, 0x65, 0x4f, 0x35, 0x9b, 0xb0, 0x82, 0x9a, 0xe0, 0x2a, 0x15, 0x89, 0xe1, 0x61, 0x0d, 0x13, 0xaf, 0x8a, 0x70, 0x12, 0xe3, 0xb1, 0x4d, 0x85, 0x25, 0x4b, 0x23, 0x3d, 0x92, 0xb5, 0x66, 0xd7, 0x1d, 0x2d, 0x03, 0x2f, 0xdd, 0xd4, 0xe9, 0xad, 0x5d, 0x18, 0xad, 0x9c, 0x5d, 0x68, 0xae, 0xb9, 0xfe, 0xf5, 0x4d, 0x77, 0x7f, 0xd8, 0x12, 0x54, 0x24, 0x4b, 0x35, 0x39, 0xad, 0xeb, 0x7b, 0x1a, 0x57, 0xd4, 0x3a, 0x33, 0x5a, 0x57, 0xd7, 0x7e, 0x3a, 0xda, 0xb9, 0xb9, 0x35, 0x0d, 0x12, 0x8d, 0xf6, 0xc6, 0xe5, 0x4d, 0xd9, 0x4a, 0x91, 0x46, 0xa2, 0x91, 0xff, 0xb4, 0xb8, 0x3d, 0x2f, 0xa9, 0x60, 0xf9, 0x03, 0x8b, 0x4b, 0x56, 0xf4, 0xd7, 0x99, 0x74, 0xa5, 0x8d, 0x5d, 0x19, 0x8d, 0x7b, 0x16, 0x97, 0x76, 0xd5, 0xb5, 0x99, 0x64, 0x36, 0x8f, 0xcd, 0x59, 0xd4, 0x92, 0x1e, 0x6a, 0x09, 0x5b, 0xee, 0xce, 0xa8, 0xed, 0x0b, 0x16, 0x0e, 0xd4, 0xf9, 0x53, 0xd3, 0x03, 0x32, 0x91, 0x58, 0xc1, 0xdb, 0xc9, 0x0e, 0x30, 0x1f, 0x60, 0xfc, 0xcc, 0xb7, 0x9c, 0x4e, 0x23, 0xe6, 0xed, 0x4b, 0xe2, 0x98, 0x7d, 0xc9, 0x86, 0xd1, 0x32, 0x34, 0x2d, 0xec, 0x21, 0x10, 0xba, 0x0a, 0x5f, 0xbc, 0x51, 0xbd, 0xcc, 0x54, 0x42, 0xb9, 0x03, 0xa3, 0x65, 0xc6, 0xc0, 0x71, 0x53, 0x55, 0x49, 0x94, 0xcc, 0x39, 0x31, 0x3e, 0x8f, 0x00, 0x0c, 0xc9, 0x4c, 0x51, 0x03, 0x69, 0x6e, 0x66, 0xee, 0x33, 0xe6, 0xbf, 0xce, 0xf1, 0x62, 0x7d, 0x4e, 0xbc, 0x5e, 0x1c, 0x5c, 0x77, 0xe9, 0xea, 0x5c, 0x00, 0x61, 0x14, 0x53, 0x28, 0x9d, 0x48, 0x47, 0x66, 0x3b, 0xa4, 0x04, 0x49, 0xa0, 0x68, 0xec, 0x9e, 0x52, 0xf7, 0x99, 0xc2, 0x90, 0xf7, 0xc1, 0x64, 0x15, 0x68, 0x4a, 0xb3, 0xde, 0x2f, 0xc6, 0x89, 0xf4, 0x17, 0x37, 0xf0, 0x91, 0xc4, 0x6e, 0xc8, 0x7b, 0x4f, 0xc1, 0xf9, 0xb7, 0x10, 0x85, 0x09, 0xb8, 0xb6, 0x18, 0xa0, 0x0d, 0xe1, 0xdb, 0x08, 0x4e, 0x55, 0xb1, 0x5c, 0x0c, 0xd7, 0x56, 0x98, 0x5c, 0x58, 0x10, 0xf9, 0x8f, 0xe0, 0xda, 0x62, 0x26, 0x20, 0x6a, 0xfa, 0x94, 0xb0, 0x36, 0xf0, 0xe1, 0x81, 0x17, 0x56, 0x1d, 0xa3, 0x69, 0x89, 0x56, 0xc2, 0x66, 0x48, 0x5d, 0xc6, 0xb5, 0x67, 0x68, 0x5a, 0xa7, 0x05, 0x3f, 0x97, 0xba, 0x8d, 0x6b, 0x99, 0x7b, 0x5d, 0x86, 0x9c, 0xe9, 0x05, 0xa9, 0xa9, 0x05, 0xd3, 0x11, 0xaa, 0xad, 0xdc, 0xa7, 0xd1, 0xf8, 0xca, 0xb3, 0xbe, 0xfd, 0x9a, 0x55, 0x83, 0x5e, 0xf2, 0xa0, 0x2e, 0x3b, 0x49, 0xef, 0x56, 0x8c, 0xee, 0xd0, 0xd8, 0xa8, 0x6f, 0x46, 0x17, 0xea, 0xb2, 0x4d, 0x01, 0x0b, 0xb9, 0x59, 0x6d, 0x3b, 0x7f, 0x2f, 0x67, 0x23, 0x0a, 0x10, 0x04, 0xf5, 0x3b, 0x5a, 0x42, 0x48, 0xe0, 0xc1, 0xba, 0x28, 0xae, 0xcd, 0x32, 0x0e, 0xd7, 0x26, 0x93, 0x02, 0x22, 0x8e, 0x6a, 0x33, 0x1a, 0x74, 0x52, 0xbb, 0xcc, 0x0e, 0xc7, 0x2d, 0x01, 0x12, 0x0e, 0xd7, 0x16, 0xe6, 0x44, 0x30, 0xb5, 0x76, 0x22, 0xac, 0x0d, 0x79, 0xcd, 0x90, 0x43, 0x0b, 0x06, 0x0c, 0xc1, 0xa6, 0xfc, 0x9f, 0xfe, 0x74, 0xf1, 0x2d, 0xdd, 0xde, 0xec, 0xd9, 0x7b, 0x67, 0xb2, 0xab, 0x4f, 0x9c, 0x78, 0xe9, 0x83, 0x0f, 0x6e, 0x9a, 0xdb, 0xe0, 0x28, 0xc9, 0xb0, 0xb0, 0x4f, 0x80, 0x8f, 0x42, 0x1d, 0x2b, 0x8a, 0xab, 0xd6, 0xb4, 0x67, 0x51, 0x60, 0xdf, 0xa6, 0x4d, 0xfb, 0x6e, 0xe1, 0xe2, 0xb7, 0xc2, 0xf7, 0x6f, 0xc4, 0xfe, 0x17, 0x53, 0xe3, 0xd9, 0x2c, 0x17, 0xc5, 0xb3, 0xa1, 0x20, 0x9d, 0x71, 0x3c, 0xdb, 0x43, 0x3f, 0xff, 0xf9, 0x99, 0x13, 0x27, 0xc8, 0xbb, 0xa9, 0xf9, 0xec, 0xa7, 0xfb, 0xd8, 0x52, 0xf0, 0xfa, 0x3e, 0xf0, 0xd8, 0x01, 0x02, 0xfb, 0x4c, 0x1c, 0xa0, 0xee, 0x81, 0xed, 0x07, 0x10, 0x96, 0x0d, 0x67, 0x23, 0xa1, 0x00, 0x03, 0xd2, 0x80, 0x80, 0x31, 0x68, 0x49, 0x64, 0x59, 0xb2, 0x8c, 0x7f, 0x42, 0x63, 0x9c, 0x43, 0x00, 0x1b, 0xb7, 0x01, 0xce, 0xb0, 0x86, 0x6e, 0x39, 0x84, 0x3d, 0xdc, 0x76, 0xc1, 0x16, 0x03, 0xa4, 0xde, 0x62, 0xfb, 0x9a, 0x95, 0x8c, 0x39, 0x7a, 0xd9, 0x63, 0x65, 0x51, 0xa8, 0x59, 0x14, 0x70, 0x7b, 0x42, 0x1d, 0xce, 0x4b, 0x87, 0x12, 0xd0, 0x02, 0x0a, 0xc1, 0xaf, 0xf9, 0x76, 0x69, 0x82, 0xa4, 0xa7, 0x28, 0xca, 0xeb, 0xcd, 0x01, 0x22, 0xa0, 0xf5, 0x39, 0x8c, 0x3e, 0x27, 0xb6, 0xb6, 0x22, 0xf2, 0x3d, 0x1e, 0x4a, 0x3d, 0x19, 0xe5, 0x80, 0xef, 0xbe, 0x28, 0x27, 0x9b, 0x96, 0xb5, 0xbc, 0xaa, 0xeb, 0x86, 0xfe, 0xec, 0x92, 0xa5, 0xb7, 0x76, 0x78, 0xea, 0xd3, 0x28, 0x01, 0x09, 0x2b, 0xc9, 0x3c, 0xd6, 0x60, 0x6b, 0xb1, 0xdd, 0x5e, 0xd2, 0x91, 0x5b, 0xd4, 0x68, 0x3d, 0x43, 0x7d, 0x7d, 0x80, 0x3d, 0xec, 0xf2, 0x96, 0xae, 0x7e, 0x64, 0xf1, 0x9a, 0x53, 0x3b, 0x6a, 0xc4, 0xd2, 0x14, 0x4f, 0x8a, 0x5a, 0x27, 0xf1, 0xb4, 0x5f, 0x3f, 0xa7, 0xe7, 0x9a, 0xda, 0x14, 0x87, 0x9e, 0x45, 0xab, 0x02, 0x08, 0x23, 0xfb, 0xb0, 0x60, 0x0f, 0xf5, 0x0d, 0x51, 0x86, 0x76, 0x51, 0xd1, 0x94, 0x78, 0x36, 0xcb, 0x45, 0xf0, 0x6c, 0x65, 0x44, 0x59, 0x24, 0xef, 0xe2, 0x78, 0x36, 0x2a, 0x01, 0xcf, 0x46, 0x4d, 0xc4, 0xb3, 0x69, 0xe3, 0x78, 0x36, 0x14, 0x57, 0x28, 0x8e, 0x3c, 0xd2, 0x0a, 0xf6, 0xa4, 0x9a, 0xd7, 0x95, 0xe7, 0x45, 0x54, 0x2f, 0x19, 0xab, 0x32, 0xe6, 0xbc, 0x3a, 0x27, 0xa3, 0xca, 0xf8, 0xa2, 0x3a, 0x92, 0x7b, 0x3d, 0xa0, 0xcc, 0xe5, 0x41, 0x77, 0x9e, 0x91, 0x65, 0xef, 0x4e, 0xce, 0x94, 0xbf, 0xa3, 0x2b, 0xf6, 0xb7, 0xbe, 0x3a, 0xbd, 0x2d, 0xed, 0x1d, 0x45, 0x66, 0xf2, 0x5d, 0x2c, 0x6b, 0xca, 0x75, 0x07, 0xcb, 0xcd, 0x8f, 0x1e, 0x60, 0x9b, 0x92, 0x6d, 0xa0, 0xd7, 0xee, 0x04, 0x21, 0x8f, 0x87, 0x2d, 0x06, 0x67, 0xe1, 0xdf, 0xef, 0x38, 0xed, 0xe0, 0xa1, 0x40, 0xbe, 0x42, 0x9e, 0xe5, 0x01, 0x5b, 0xad, 0x26, 0xf6, 0x0d, 0xbb, 0x03, 0x3c, 0xcb, 0x36, 0x4d, 0x03, 0x85, 0x26, 0x2b, 0xbb, 0xd5, 0x13, 0x94, 0x2b, 0x22, 0x01, 0xf6, 0x73, 0x4e, 0x8e, 0x40, 0x74, 0x65, 0x08, 0xce, 0x8a, 0x1b, 0xd9, 0xcb, 0xb1, 0xef, 0x59, 0x3d, 0x21, 0x24, 0x68, 0x38, 0x1b, 0x03, 0x28, 0x4c, 0x9a, 0x05, 0x33, 0xe1, 0xbe, 0x38, 0x49, 0x86, 0xdb, 0xc3, 0xae, 0x46, 0x03, 0x37, 0xc5, 0xd0, 0x88, 0x63, 0xe3, 0x08, 0x4d, 0x9e, 0x07, 0xee, 0x0a, 0x87, 0xfc, 0x9b, 0x32, 0x07, 0x76, 0xf9, 0x82, 0x29, 0xd7, 0x13, 0xac, 0x30, 0x3f, 0xf2, 0x43, 0x3c, 0xdc, 0x02, 0x1d, 0x1a, 0xee, 0xe2, 0x33, 0x67, 0xfa, 0xfc, 0x45, 0x7a, 0xea, 0x8c, 0x31, 0x65, 0xac, 0x67, 0xdc, 0x90, 0xe0, 0x20, 0xf0, 0x90, 0xa8, 0x6f, 0xd8, 0x66, 0x07, 0xc6, 0x20, 0xec, 0xbe, 0x70, 0x81, 0x2e, 0x87, 0x7d, 0x75, 0x22, 0xdb, 0xb6, 0x1e, 0x30, 0x02, 0x1b, 0x20, 0x21, 0x09, 0x40, 0xc8, 0x9e, 0x38, 0xa2, 0x48, 0x40, 0xa0, 0xac, 0x10, 0xf0, 0xac, 0xd1, 0xb4, 0xa5, 0x81, 0xe2, 0xac, 0x1b, 0x1e, 0x7c, 0xc2, 0x8c, 0x63, 0xd6, 0x0d, 0x6a, 0x1c, 0x74, 0x0d, 0x9e, 0xf6, 0x30, 0xb7, 0x22, 0x14, 0x37, 0x12, 0xba, 0x7c, 0x57, 0xee, 0x34, 0xe9, 0x9d, 0xca, 0x42, 0xe7, 0x75, 0x0b, 0xce, 0x9c, 0x59, 0x70, 0x5d, 0x24, 0xe9, 0x31, 0x49, 0x7e, 0xf2, 0xc1, 0xdf, 0xe8, 0xe0, 0x94, 0x47, 0x8d, 0x29, 0x1e, 0x10, 0xb2, 0xbb, 0x18, 0x76, 0x14, 0x30, 0x5c, 0xe7, 0x3e, 0xfd, 0x83, 0x3c, 0x1b, 0x14, 0x9a, 0x2d, 0xec, 0x56, 0x6f, 0x96, 0x5c, 0x11, 0xce, 0xc0, 0xf3, 0x1a, 0x80, 0x7d, 0xed, 0x86, 0x7d, 0xf5, 0x20, 0xdc, 0x9a, 0xf3, 0x12, 0xb8, 0x35, 0x4b, 0x0c, 0x08, 0x96, 0x6a, 0xe5, 0x71, 0x6b, 0xee, 0x31, 0xdc, 0x5a, 0x7c, 0x3f, 0xc5, 0x69, 0x12, 0x9a, 0xcf, 0xc4, 0xdd, 0x43, 0x77, 0x5f, 0x9f, 0x5b, 0xa8, 0x7b, 0x11, 0x4e, 0xe5, 0x12, 0x48, 0x2c, 0xce, 0x9c, 0x3a, 0x85, 0xa6, 0xf3, 0x1d, 0x6e, 0xbe, 0x8d, 0x79, 0x68, 0x8b, 0xc4, 0xe7, 0x91, 0xdc, 0xb7, 0x64, 0xc9, 0xbe, 0x53, 0x0e, 0x3b, 0x28, 0xc4, 0x93, 0x0d, 0xfb, 0x9a, 0x1f, 0xe0, 0x65, 0xc9, 0x0b, 0xf4, 0x7b, 0xb0, 0xaf, 0x36, 0x44, 0xaf, 0xac, 0x53, 0xe3, 0xd5, 0xe2, 0xdd, 0x44, 0x17, 0x78, 0xb0, 0x8b, 0x71, 0xbc, 0x5a, 0xac, 0x8b, 0x1c, 0x5e, 0x6d, 0x42, 0xe7, 0xde, 0x1b, 0xeb, 0x1c, 0xfb, 0x1a, 0x49, 0x5f, 0xbc, 0x6f, 0x03, 0x77, 0xa0, 0x45, 0x1e, 0xd7, 0x33, 0x6e, 0x6f, 0x22, 0x3a, 0x37, 0x09, 0xa7, 0x66, 0xf9, 0x8e, 0x38, 0x35, 0xfa, 0x1e, 0x5b, 0xd2, 0xba, 0x77, 0xd8, 0xac, 0x33, 0x67, 0xc0, 0xcf, 0xce, 0xf2, 0x27, 0x07, 0xfc, 0x82, 0x0d, 0x50, 0x5f, 0xb3, 0xc3, 0xe0, 0x66, 0xc8, 0x66, 0xe0, 0x3b, 0xa1, 0xf8, 0xf9, 0x3c, 0xa6, 0xdd, 0x28, 0xb7, 0x5f, 0x22, 0x4e, 0x4d, 0xc0, 0x6f, 0x29, 0xee, 0x9a, 0x5e, 0x9d, 0x80, 0x3f, 0x49, 0x78, 0x87, 0x9d, 0x79, 0x9e, 0xf5, 0x9f, 0xe5, 0xde, 0x80, 0x28, 0xf7, 0xf9, 0x7b, 0xc7, 0xda, 0xe7, 0xcf, 0xdb, 0x01, 0x41, 0x16, 0x6c, 0x3f, 0x48, 0x3c, 0xcf, 0x91, 0x59, 0x0c, 0x3b, 0xf6, 0x41, 0xc1, 0x52, 0x95, 0x88, 0x43, 0xe6, 0x1f, 0x50, 0x09, 0x94, 0x7b, 0x02, 0xda, 0xcc, 0x32, 0x35, 0x42, 0x8d, 0xa3, 0xdc, 0x7c, 0x59, 0x14, 0x62, 0x07, 0xaa, 0x39, 0xb3, 0x27, 0xd4, 0x61, 0xae, 0x00, 0x97, 0x66, 0x99, 0x8c, 0x4b, 0x33, 0x4e, 0x81, 0x4b, 0xf3, 0x5c, 0x1a, 0x98, 0x46, 0x7d, 0x94, 0xb5, 0xbc, 0x92, 0x27, 0xdb, 0xed, 0x9e, 0x06, 0x1f, 0x29, 0xa0, 0x10, 0xd9, 0x76, 0x73, 0x64, 0xbb, 0xb8, 0x33, 0xd7, 0x1a, 0xf4, 0xda, 0x94, 0x68, 0xbe, 0xa8, 0x6e, 0x52, 0xe0, 0xbc, 0x28, 0xe9, 0x16, 0xab, 0x74, 0x12, 0x48, 0xbc, 0xe1, 0x74, 0xc2, 0xa9, 0xe3, 0x7c, 0x2f, 0xa1, 0x6c, 0x7e, 0x0c, 0xfb, 0x5e, 0x36, 0xa3, 0xb8, 0xd3, 0x28, 0xbb, 0x7b, 0x35, 0x45, 0x0a, 0x08, 0x0d, 0x46, 0x9f, 0x4d, 0x02, 0x8e, 0x39, 0x26, 0x20, 0xcc, 0x1a, 0xeb, 0x7d, 0xbe, 0x7c, 0x87, 0xde, 0xe7, 0x72, 0xf9, 0x38, 0x38, 0x97, 0x01, 0xa3, 0xb9, 0x90, 0x7b, 0x4f, 0x22, 0x96, 0x8b, 0x4c, 0x94, 0xce, 0x91, 0x47, 0x5e, 0x02, 0x8e, 0x6b, 0x2c, 0x33, 0x3a, 0xdc, 0xec, 0x38, 0x94, 0x25, 0x0a, 0xb8, 0xfb, 0x5a, 0x79, 0x61, 0x41, 0x51, 0x59, 0x51, 0x41, 0x31, 0xf8, 0x8b, 0xb5, 0x3e, 0xad, 0x6a, 0x73, 0x41, 0xc1, 0xc6, 0x8a, 0x81, 0xc7, 0xb7, 0xd5, 0x97, 0x6d, 0x7c, 0x76, 0x8d, 0xb3, 0xca, 0x29, 0x65, 0x52, 0x92, 0x64, 0x29, 0x8a, 0xda, 0xe5, 0xf5, 0x9e, 0x8c, 0xb6, 0x75, 0x0d, 0x6a, 0x9b, 0xaa, 0xba, 0x39, 0xbd, 0x79, 0x79, 0x65, 0xe5, 0xca, 0x19, 0x19, 0xa0, 0x1f, 0xca, 0xdf, 0x49, 0x36, 0x95, 0xcd, 0xca, 0x80, 0x7f, 0xf6, 0xdd, 0x11, 0x0a, 0xdd, 0xda, 0xdd, 0x31, 0xb7, 0x7f, 0xf4, 0x15, 0xb1, 0xc4, 0x6a, 0x31, 0x5b, 0x63, 0x11, 0x10, 0x24, 0xd2, 0x56, 0x9b, 0x0e, 0x50, 0xb9, 0x73, 0x6e, 0xec, 0xec, 0xd9, 0x37, 0x2f, 0x97, 0x22, 0xab, 0x4e, 0xad, 0x6a, 0xde, 0xd0, 0x9e, 0x1e, 0xec, 0xba, 0xa6, 0x66, 0xf1, 0x2b, 0xb5, 0x32, 0x01, 0x89, 0xe3, 0x21, 0x98, 0xd9, 0x87, 0xa9, 0xff, 0x81, 0x93, 0x35, 0x01, 0xaf, 0x65, 0xb9, 0x5a, 0xbc, 0x56, 0xff, 0x4b, 0xa9, 0x49, 0xeb, 0xdf, 0x61, 0xb7, 0x51, 0xaf, 0x53, 0xf3, 0x47, 0x83, 0xdc, 0x91, 0xa1, 0xe6, 0x1f, 0x38, 0xc0, 0xe5, 0x06, 0x3c, 0x46, 0x7d, 0x8d, 0x73, 0x56, 0x5a, 0xa3, 0x66, 0x29, 0x45, 0xe0, 0x7c, 0xe7, 0x68, 0x82, 0x2d, 0xfc, 0x99, 0x4c, 0x77, 0xa8, 0x29, 0x81, 0x11, 0x59, 0x6a, 0xd5, 0xe3, 0x83, 0xea, 0x15, 0x01, 0x72, 0xf0, 0x2c, 0xbb, 0xfc, 0xb0, 0xd1, 0x2a, 0xfc, 0xd2, 0xac, 0x10, 0xe9, 0x14, 0xff, 0x14, 0xa6, 0xe8, 0xee, 0xaa, 0x40, 0xe9, 0x12, 0xa9, 0xe7, 0x8c, 0xaa, 0xd1, 0x97, 0xaa, 0xeb, 0x9c, 0xb5, 0x2e, 0xb2, 0x5a, 0x6d, 0x40, 0xef, 0xd1, 0xc1, 0xf7, 0x9c, 0x87, 0xef, 0xb1, 0xa0, 0xbb, 0x1a, 0x8b, 0x66, 0x2a, 0x5c, 0x98, 0x85, 0x67, 0x4e, 0xe9, 0x63, 0xb8, 0x30, 0x26, 0xee, 0xf8, 0x9c, 0x37, 0xf1, 0xe5, 0x80, 0x5c, 0xf2, 0xe1, 0x87, 0x67, 0x3f, 0xfc, 0x70, 0x52, 0x0f, 0xd8, 0xe5, 0xb2, 0x7d, 0x2f, 0xbf, 0xbc, 0xaf, 0x79, 0x42, 0x27, 0xce, 0x73, 0x11, 0xcd, 0x34, 0x90, 0x1e, 0x85, 0x61, 0x3f, 0x5c, 0x08, 0x0b, 0xe6, 0x30, 0xeb, 0xc6, 0xb0, 0x60, 0x9c, 0xd0, 0x0d, 0xfa, 0xc8, 0xf1, 0x04, 0xc9, 0x45, 0x38, 0x7d, 0x8e, 0xb4, 0x49, 0x58, 0x30, 0x2a, 0x16, 0xc5, 0x90, 0xbb, 0x18, 0xd0, 0xa2, 0x6e, 0x85, 0xd1, 0xc5, 0x40, 0x1e, 0x1d, 0x86, 0xbc, 0x71, 0x81, 0xd1, 0x22, 0xfa, 0x8a, 0x91, 0x88, 0x3e, 0x92, 0x98, 0xf5, 0x4f, 0x82, 0x79, 0xec, 0xe3, 0x2f, 0x1b, 0xf5, 0xa2, 0x2f, 0x18, 0x99, 0xe4, 0xa7, 0x9a, 0x94, 0x67, 0xd9, 0xc7, 0x01, 0xc9, 0x53, 0xae, 0x24, 0x05, 0xd8, 0xa1, 0x4c, 0x96, 0xb1, 0x27, 0x14, 0x49, 0xa4, 0x14, 0x7c, 0xa6, 0x50, 0xb0, 0xfb, 0x94, 0x56, 0x15, 0x08, 0xd9, 0xd8, 0x64, 0x76, 0x13, 0x8a, 0x7a, 0x9a, 0xcc, 0x3e, 0xcc, 0xac, 0xc0, 0xf4, 0x73, 0x1c, 0x16, 0xcc, 0xf2, 0x1d, 0xb1, 0x60, 0xcc, 0x0a, 0xd8, 0xb9, 0xf3, 0xdf, 0x63, 0x5f, 0xa3, 0x94, 0xec, 0x57, 0x63, 0x04, 0x94, 0x9a, 0x77, 0xc7, 0x1d, 0xa3, 0x39, 0xe8, 0x6c, 0xe2, 0x00, 0x7f, 0x70, 0x8e, 0x4a, 0xe0, 0x3b, 0x4d, 0xe8, 0x3e, 0x5c, 0x3f, 0x19, 0x0b, 0x66, 0x99, 0x12, 0x0b, 0x66, 0x22, 0x4c, 0x46, 0x9d, 0x66, 0x32, 0x16, 0x0c, 0x24, 0x62, 0xc1, 0xe8, 0x12, 0xb8, 0x15, 0x7f, 0xbc, 0xe3, 0x93, 0xa3, 0x73, 0xcf, 0x86, 0x87, 0x0e, 0x2f, 0x3d, 0x13, 0x23, 0xe0, 0x8f, 0x38, 0x94, 0x91, 0xeb, 0x57, 0x83, 0xe9, 0xec, 0x4f, 0xaa, 0xdb, 0xec, 0x72, 0x13, 0xf8, 0x21, 0xa7, 0x2f, 0x00, 0xa2, 0x09, 0xca, 0xe1, 0xdb, 0xf1, 0xbe, 0xc9, 0x8f, 0xe6, 0x99, 0x8d, 0x3a, 0x01, 0x24, 0x78, 0xdc, 0x75, 0x38, 0x54, 0x1b, 0x40, 0x2c, 0x81, 0xdd, 0x5c, 0x7c, 0x3a, 0x12, 0x30, 0x60, 0x16, 0xc2, 0xa2, 0xd7, 0xab, 0xf5, 0xf1, 0xcb, 0x79, 0xb8, 0x5a, 0xfa, 0x09, 0x18, 0x30, 0xb8, 0x62, 0x6a, 0x6a, 0xfb, 0x59, 0x77, 0x8d, 0x73, 0x67, 0xf3, 0xb6, 0xbe, 0xd0, 0xd9, 0xa6, 0xa6, 0x9d, 0xb6, 0x5a, 0x2f, 0x72, 0x22, 0x06, 0x47, 0xc4, 0x4a, 0xe6, 0xed, 0x57, 0xc9, 0xd4, 0xda, 0xd5, 0x6d, 0xa8, 0x4b, 0x1d, 0xed, 0x62, 0x60, 0x65, 0xff, 0x40, 0xcb, 0xc5, 0xe0, 0x87, 0x04, 0xc7, 0x5f, 0xa8, 0x57, 0x61, 0x9f, 0xd4, 0x08, 0x07, 0x26, 0x9f, 0x84, 0x03, 0xb3, 0x8c, 0xe1, 0xc0, 0x74, 0xea, 0x18, 0x0e, 0x0c, 0x8c, 0xc7, 0x81, 0x35, 0x6e, 0xff, 0xfd, 0x63, 0xf3, 0xce, 0x46, 0x86, 0x0e, 0x2f, 0x39, 0x73, 0x86, 0x9c, 0x81, 0x07, 0xcf, 0x1e, 0x07, 0x39, 0xd5, 0xad, 0x76, 0x99, 0x89, 0x2d, 0xc0, 0x04, 0x92, 0x24, 0xb2, 0x2f, 0x7c, 0x49, 0xff, 0x82, 0xcf, 0x11, 0xbe, 0x3d, 0x2a, 0x29, 0x83, 0xfa, 0x77, 0x06, 0x94, 0x98, 0x62, 0x0e, 0xca, 0x3e, 0x42, 0x40, 0x33, 0xb4, 0x80, 0x19, 0x26, 0xc5, 0x24, 0x94, 0x4d, 0x68, 0xc8, 0xdc, 0x06, 0x62, 0x17, 0xeb, 0x0e, 0x64, 0xbd, 0xc4, 0xa1, 0x17, 0xec, 0x42, 0xcc, 0x29, 0xae, 0xa4, 0xa8, 0x13, 0xe5, 0x26, 0x30, 0x11, 0x44, 0x7e, 0x24, 0x94, 0x9d, 0x9e, 0xe6, 0x71, 0xa1, 0xc8, 0xf9, 0x1a, 0x35, 0xce, 0xef, 0x3d, 0x86, 0x93, 0x12, 0x08, 0xdd, 0x31, 0xf8, 0x93, 0x71, 0x0a, 0xbc, 0x14, 0x8f, 0x1f, 0xe3, 0xbc, 0x84, 0xc3, 0x11, 0xda, 0xc5, 0xe7, 0xf6, 0x66, 0xf7, 0x9b, 0x54, 0xca, 0xc6, 0x26, 0x79, 0xe3, 0xfa, 0x07, 0x06, 0xfa, 0xef, 0x1b, 0x29, 0x4f, 0xcc, 0xf1, 0x1d, 0xd4, 0x18, 0xe5, 0xf9, 0xce, 0x59, 0x4b, 0x57, 0x46, 0x06, 0x9f, 0xda, 0xde, 0x44, 0x17, 0x0c, 0x36, 0x05, 0xd4, 0xda, 0x58, 0x72, 0xef, 0xaf, 0x39, 0xf5, 0xbb, 0x30, 0x97, 0xec, 0xb9, 0x73, 0x59, 0xe9, 0x14, 0x69, 0xbe, 0xa7, 0x6d, 0x3f, 0x7d, 0x2d, 0x50, 0x67, 0x75, 0x56, 0x43, 0x5d, 0x9d, 0xe3, 0x2b, 0xd9, 0x70, 0xff, 0x72, 0xf3, 0x86, 0xf1, 0x62, 0x55, 0xfc, 0xbc, 0xe5, 0xe7, 0x40, 0xe1, 0x03, 0xf2, 0x95, 0x2b, 0x99, 0x0a, 0xab, 0x10, 0xee, 0xa2, 0xd2, 0x92, 0x48, 0xde, 0xbf, 0x33, 0x0f, 0x42, 0xee, 0x14, 0x64, 0x52, 0x57, 0x3d, 0x19, 0xd6, 0xda, 0xd6, 0x99, 0x19, 0xb6, 0xa4, 0xf5, 0x1d, 0xfb, 0x17, 0x95, 0x5c, 0xcd, 0x7c, 0x38, 0xf2, 0xdc, 0x26, 0x01, 0xfb, 0x67, 0x78, 0xa4, 0xda, 0x15, 0x2d, 0x5b, 0x9f, 0x5a, 0x91, 0x38, 0x2f, 0x9c, 0xce, 0xf9, 0x29, 0x96, 0x5b, 0x9e, 0x7e, 0x2e, 0xdd, 0x47, 0x0a, 0x71, 0xd4, 0xb4, 0x00, 0xbf, 0x93, 0xd0, 0x4c, 0xe0, 0xe3, 0x44, 0xf2, 0x81, 0x96, 0x39, 0x0d, 0xb7, 0x41, 0x24, 0x26, 0xa1, 0x22, 0xd8, 0xc7, 0xcd, 0x09, 0x2f, 0x9c, 0xb8, 0x27, 0x96, 0xe7, 0x0a, 0x11, 0xc2, 0x39, 0x63, 0x15, 0x49, 0x1c, 0x0c, 0x24, 0x56, 0x4e, 0x44, 0x12, 0xa4, 0x88, 0x88, 0x97, 0xc7, 0xa9, 0xa9, 0x27, 0x14, 0xef, 0xc1, 0x39, 0xaa, 0x33, 0x33, 0x3c, 0xae, 0x14, 0x6b, 0x92, 0x51, 0xa3, 0x52, 0x2a, 0xe4, 0x32, 0xa9, 0x98, 0x08, 0x82, 0x20, 0xba, 0x6b, 0x62, 0xe2, 0x28, 0x89, 0xb8, 0x4a, 0x92, 0x20, 0xa5, 0x4e, 0x14, 0x64, 0xc8, 0xda, 0x98, 0x4e, 0xf9, 0x0f, 0x4e, 0x39, 0xe1, 0x25, 0x56, 0xb5, 0xcb, 0xf8, 0xc0, 0xd3, 0x9c, 0x1c, 0xc3, 0xab, 0x9f, 0x72, 0xf7, 0x07, 0xfe, 0x9e, 0x1b, 0xfa, 0x91, 0x80, 0x12, 0x53, 0x53, 0x38, 0x09, 0xd6, 0xe2, 0x53, 0x88, 0x7e, 0xfa, 0xe6, 0xca, 0x17, 0xb6, 0xd7, 0xc6, 0x04, 0x19, 0x3c, 0x87, 0x04, 0xdc, 0x5b, 0x1f, 0xc0, 0x39, 0x9c, 0x8c, 0x35, 0xb3, 0x5c, 0x19, 0xd6, 0xcc, 0xf2, 0x9d, 0xb1, 0x66, 0x02, 0x12, 0x1d, 0x2d, 0x6c, 0xa2, 0xa2, 0x34, 0x11, 0xbb, 0x9a, 0xfe, 0xe0, 0xf6, 0xe7, 0x47, 0x4e, 0xb0, 0x3f, 0x93, 0x05, 0x2b, 0xdb, 0xfa, 0xe7, 0x65, 0xf7, 0x86, 0x1d, 0x55, 0xd3, 0x8a, 0x73, 0x5c, 0x12, 0xf2, 0xc0, 0x01, 0x64, 0x8a, 0xa1, 0x66, 0x9f, 0x66, 0xff, 0xef, 0xcc, 0xfa, 0x69, 0xc6, 0x94, 0x8d, 0xc6, 0x69, 0xeb, 0xce, 0x00, 0xd3, 0xe9, 0x18, 0x4d, 0xdd, 0xcd, 0xde, 0x46, 0x7d, 0x04, 0xc7, 0xe0, 0x20, 0xe6, 0xf1, 0x26, 0xc0, 0x86, 0xe3, 0xfe, 0x49, 0xc8, 0x32, 0xcb, 0x54, 0x20, 0x41, 0x6b, 0xac, 0x08, 0x8a, 0x06, 0x8b, 0xb2, 0x9f, 0x25, 0x14, 0xa5, 0xf9, 0x1b, 0x02, 0x07, 0xe1, 0xc0, 0x10, 0xb1, 0xa4, 0x44, 0x88, 0x18, 0x5c, 0x1b, 0x6a, 0x2a, 0x94, 0x18, 0xf5, 0x51, 0xce, 0xbc, 0xfd, 0x7d, 0xfe, 0xaa, 0x34, 0xb8, 0x13, 0xa0, 0xa8, 0x0b, 0x17, 0xc4, 0xe9, 0x4d, 0xaf, 0xcd, 0xb3, 0x72, 0xe2, 0x37, 0xa4, 0xc6, 0xe9, 0x43, 0x07, 0xe7, 0x04, 0x24, 0x52, 0xa5, 0x51, 0x6f, 0x54, 0xaa, 0x75, 0x62, 0x7d, 0xc1, 0xfc, 0x66, 0xf0, 0x54, 0x82, 0x38, 0xce, 0xeb, 0x18, 0x28, 0xfe, 0x84, 0x72, 0xb2, 0x8e, 0xf1, 0x1d, 0xf0, 0x62, 0xf4, 0xb1, 0x03, 0x2f, 0xac, 0x3a, 0x19, 0x57, 0x30, 0xd4, 0x13, 0xd4, 0x0b, 0xc4, 0x93, 0xa0, 0x5c, 0xb6, 0x3d, 0xc6, 0x1f, 0x8d, 0x53, 0xe0, 0xc5, 0x2c, 0x53, 0xe1, 0xc5, 0x20, 0x7f, 0x44, 0xec, 0x71, 0x0a, 0xbc, 0x58, 0x02, 0x87, 0xa4, 0xb6, 0xbf, 0x03, 0x19, 0xe4, 0x4b, 0x3b, 0x3e, 0x79, 0x0c, 0x31, 0xc8, 0xfb, 0x97, 0x22, 0x76, 0xc4, 0x7e, 0x04, 0xcf, 0xf3, 0xdf, 0x80, 0xc6, 0x3e, 0x81, 0x45, 0xc6, 0xc6, 0xce, 0xbc, 0x45, 0x9d, 0x23, 0x7c, 0x44, 0x61, 0x34, 0x92, 0xaa, 0x16, 0x51, 0x02, 0xcc, 0x1d, 0x05, 0x40, 0x00, 0xb9, 0x23, 0x93, 0x30, 0x07, 0x13, 0xc0, 0x62, 0x3e, 0xc2, 0x17, 0xb0, 0x9b, 0xbc, 0x13, 0xc1, 0x62, 0x97, 0x88, 0xba, 0x87, 0x0c, 0x1f, 0xcc, 0x5b, 0xb7, 0x3f, 0xbf, 0xea, 0xb0, 0x50, 0xc8, 0x3e, 0x2e, 0x4e, 0xd6, 0x1a, 0x34, 0x56, 0x31, 0xfb, 0x88, 0x40, 0x74, 0xfc, 0x90, 0xce, 0xa7, 0x00, 0x69, 0x72, 0x83, 0x44, 0xa2, 0x97, 0x03, 0xb7, 0xc2, 0xa7, 0x3b, 0x84, 0xa7, 0x8d, 0xdc, 0x95, 0xd2, 0x68, 0x1b, 0x3d, 0xa2, 0xd6, 0x6a, 0xd5, 0x64, 0x9f, 0xad, 0x29, 0x75, 0x74, 0x03, 0x75, 0x4e, 0x63, 0x66, 0x25, 0xa6, 0xb0, 0xc5, 0x12, 0x36, 0x81, 0x7f, 0x5a, 0xd4, 0xe7, 0xef, 0xc2, 0xf4, 0xba, 0xfe, 0xc2, 0x97, 0x14, 0x47, 0xaf, 0x33, 0x88, 0xe5, 0x1c, 0x6f, 0xf3, 0x5c, 0x14, 0x3c, 0xe6, 0x48, 0x04, 0x8f, 0x59, 0xae, 0xa8, 0x20, 0xba, 0x7b, 0x40, 0x78, 0x98, 0x80, 0xdf, 0xeb, 0xb6, 0xa7, 0x26, 0x19, 0x95, 0x72, 0x4c, 0xcb, 0x91, 0x1d, 0x5f, 0xcb, 0x6b, 0x02, 0x60, 0x7c, 0x18, 0x2f, 0x74, 0x6b, 0x15, 0x0f, 0xde, 0xe5, 0xfd, 0xc6, 0x1e, 0xed, 0x2f, 0xda, 0xb8, 0x5f, 0x33, 0xda, 0xe0, 0x9a, 0xb5, 0x64, 0x65, 0x64, 0xf1, 0xb3, 0x3b, 0xea, 0x2a, 0x36, 0x3e, 0x3d, 0x0c, 0x29, 0x77, 0x54, 0x6b, 0x81, 0x1a, 0x22, 0xa6, 0xdc, 0xb3, 0x20, 0xe5, 0x36, 0xc9, 0xc1, 0x3e, 0x8e, 0x4a, 0x83, 0xff, 0x41, 0xac, 0xa9, 0xe6, 0xc6, 0xb7, 0x77, 0x2e, 0x7e, 0xe1, 0xfa, 0xe6, 0x92, 0xd5, 0x8f, 0x2d, 0xcb, 0x4c, 0xed, 0xbd, 0xa1, 0x07, 0x53, 0x69, 0x14, 0x9b, 0xf4, 0xc2, 0x97, 0xec, 0x3d, 0x54, 0x21, 0xdc, 0x43, 0x02, 0xf2, 0xf7, 0x22, 0x14, 0xc4, 0xbc, 0x05, 0xf2, 0x7a, 0x14, 0x87, 0x06, 0xe5, 0xb6, 0xb2, 0xa3, 0xfb, 0x3b, 0x35, 0x77, 0x87, 0x39, 0x09, 0x98, 0xe5, 0x48, 0x04, 0x66, 0x59, 0x2e, 0x53, 0x04, 0x99, 0xc1, 0xa3, 0x66, 0xa5, 0x02, 0x6e, 0x7f, 0x22, 0xc9, 0xa8, 0xb0, 0x2b, 0xed, 0x42, 0xb9, 0x50, 0x0e, 0xa9, 0xaa, 0x80, 0x10, 0x38, 0x38, 0xf8, 0x16, 0xf2, 0x5e, 0x89, 0x25, 0xeb, 0x02, 0x13, 0xf2, 0x43, 0x00, 0xb3, 0x78, 0xff, 0xee, 0xeb, 0xf7, 0x89, 0x81, 0x74, 0xdf, 0xce, 0x5d, 0xfb, 0xa5, 0x20, 0x15, 0x45, 0xb0, 0xa9, 0xdc, 0x72, 0x72, 0x78, 0xe5, 0xc9, 0xad, 0x55, 0x55, 0x5b, 0x4f, 0x82, 0x9f, 0xdc, 0xfb, 0xe8, 0xa3, 0xf7, 0x2e, 0xb9, 0xfd, 0x81, 0x07, 0x6e, 0x07, 0xff, 0x85, 0x22, 0xd8, 0xac, 0x7b, 0xfd, 0x86, 0xba, 0xba, 0x1b, 0x5e, 0x5f, 0x87, 0x22, 0xda, 0x70, 0x67, 0xf3, 0x06, 0xf6, 0x21, 0xca, 0x01, 0xc7, 0x29, 0xc3, 0x31, 0x63, 0x91, 0x0e, 0xce, 0x69, 0x30, 0x50, 0x2f, 0xe6, 0x6c, 0xf6, 0x9c, 0xcc, 0x8f, 0x54, 0x17, 0x15, 0x83, 0x4e, 0x86, 0x3d, 0x41, 0x79, 0xa1, 0x1c, 0x6c, 0xda, 0x29, 0xa4, 0xee, 0x23, 0x35, 0x1c, 0x6a, 0x2e, 0x8d, 0xf0, 0x20, 0xf4, 0x21, 0xe1, 0x08, 0x32, 0x74, 0xc8, 0xcb, 0x36, 0x62, 0x1a, 0x76, 0xb5, 0xf8, 0x2c, 0xcb, 0x77, 0xc7, 0x67, 0x59, 0xfe, 0x0d, 0x7c, 0x96, 0xe5, 0x0a, 0xf1, 0x59, 0xc8, 0x7c, 0x1c, 0x9a, 0x32, 0x7b, 0xe4, 0x98, 0x49, 0xf9, 0xa5, 0x89, 0xd8, 0xa3, 0x9f, 0x8f, 0xd9, 0x98, 0xdf, 0xfd, 0xfe, 0xb8, 0xfb, 0x88, 0x98, 0xc5, 0x99, 0xd3, 0x7d, 0x0e, 0xd0, 0xb9, 0x70, 0xce, 0xb2, 0xaf, 0x1e, 0x9f, 0x65, 0xb9, 0x2a, 0x7c, 0x96, 0xe5, 0xea, 0xf0, 0x59, 0x96, 0x29, 0xf1, 0x59, 0xd9, 0x57, 0x84, 0xcf, 0xa2, 0x04, 0xe3, 0xd4, 0x30, 0x5e, 0x2a, 0x40, 0x6a, 0x58, 0xee, 0xc4, 0xb0, 0x08, 0x4b, 0x8c, 0x16, 0xf1, 0x57, 0x22, 0xe5, 0x6f, 0xa5, 0x66, 0x1d, 0x56, 0xc9, 0x4e, 0x19, 0x0d, 0xa2, 0x2f, 0x84, 0xda, 0xff, 0x12, 0x1b, 0xd4, 0x48, 0x25, 0x23, 0x0e, 0x7c, 0x7f, 0x02, 0x38, 0x0b, 0xec, 0x48, 0x49, 0x4a, 0xd4, 0xcc, 0xac, 0xa9, 0x20, 0xa4, 0x52, 0x41, 0xcd, 0x6c, 0x0b, 0xc0, 0xf3, 0x99, 0x0b, 0x0f, 0x2e, 0x0b, 0xe7, 0xd3, 0x7e, 0x29, 0x7c, 0x96, 0x65, 0x0c, 0x9f, 0xe5, 0x1b, 0x8f, 0xcf, 0x8a, 0x75, 0x1d, 0xd8, 0xf3, 0x42, 0xe3, 0x7a, 0x0f, 0xe0, 0x1e, 0xa0, 0xd8, 0x97, 0x13, 0xbb, 0x1b, 0x66, 0xd9, 0x6f, 0xd8, 0x8f, 0xc6, 0xf5, 0xf8, 0x83, 0x0b, 0xe7, 0xc7, 0xba, 0x08, 0xde, 0xdc, 0x0f, 0x0e, 0x27, 0xf4, 0x71, 0xf6, 0x7e, 0x14, 0x27, 0xf7, 0xc2, 0x97, 0xcc, 0x3b, 0xcc, 0x59, 0x42, 0x45, 0x4c, 0x03, 0x64, 0x54, 0x91, 0x09, 0xf0, 0xfd, 0x3e, 0x81, 0x80, 0x51, 0xb1, 0xcc, 0x2c, 0x6e, 0x51, 0x1c, 0x15, 0x84, 0x6f, 0x6b, 0xe1, 0x01, 0xed, 0x25, 0x10, 0x05, 0x99, 0x88, 0x4a, 0x52, 0x5f, 0x59, 0x61, 0x0e, 0x96, 0x64, 0xba, 0x9a, 0x96, 0xcd, 0x78, 0xf7, 0x40, 0x22, 0x3e, 0x32, 0x56, 0xe3, 0x22, 0x2d, 0x5f, 0x49, 0x39, 0x1e, 0xbb, 0x14, 0x55, 0xc1, 0x13, 0x35, 0x4d, 0x5d, 0xe1, 0xd6, 0xfa, 0x5c, 0x99, 0x1e, 0x07, 0x96, 0x44, 0xf8, 0x04, 0xb5, 0xfc, 0xc5, 0x98, 0x7b, 0x2a, 0xbc, 0x12, 0x5a, 0x86, 0x18, 0x7e, 0x61, 0x0c, 0xb2, 0x44, 0x19, 0x83, 0x2b, 0xaa, 0x76, 0xdd, 0xe6, 0xab, 0xea, 0xcf, 0xcb, 0xeb, 0xab, 0xf2, 0x91, 0xf7, 0xc4, 0x30, 0x4b, 0x0b, 0x6f, 0x72, 0xe6, 0x3e, 0xbb, 0x72, 0xf0, 0xf0, 0x8a, 0x42, 0x14, 0x80, 0xad, 0x6f, 0x5b, 0xe8, 0xac, 0xd6, 0x9d, 0xef, 0x8e, 0xe3, 0x96, 0x28, 0xb2, 0x2c, 0xe7, 0xfb, 0x2f, 0xce, 0xdd, 0xdd, 0x95, 0xe6, 0xae, 0xe8, 0x8b, 0xfc, 0x8e, 0x43, 0x75, 0xec, 0x9b, 0x1b, 0xea, 0xe8, 0xe8, 0x6e, 0xaf, 0xdc, 0xf1, 0xfa, 0xe6, 0x95, 0xaf, 0xef, 0x9f, 0x51, 0x1c, 0x21, 0x03, 0xe7, 0xf6, 0x22, 0x2c, 0x87, 0x35, 0xd2, 0x12, 0x8a, 0xa3, 0x39, 0xd0, 0xfe, 0x52, 0x5f, 0xf8, 0x1b, 0xfd, 0x47, 0x1a, 0x69, 0x31, 0x9b, 0xa3, 0x12, 0x5f, 0x1c, 0xbb, 0xc4, 0x4d, 0x99, 0x37, 0x0e, 0x61, 0x12, 0xe2, 0x0c, 0x6d, 0x09, 0xd8, 0x25, 0x47, 0xe2, 0xe5, 0x35, 0x0a, 0x70, 0x49, 0x53, 0x42, 0x8a, 0x16, 0x22, 0xb0, 0x13, 0x49, 0x09, 0x71, 0x8e, 0xa3, 0x8b, 0x97, 0xe7, 0x0e, 0x5f, 0x15, 0x51, 0x99, 0xe1, 0x76, 0x7a, 0x2e, 0x01, 0x77, 0x0a, 0x4f, 0x89, 0xf1, 0x51, 0x90, 0x42, 0x43, 0x2c, 0x3a, 0x3c, 0x37, 0x77, 0xf4, 0x8f, 0x63, 0x93, 0xd5, 0xb0, 0xad, 0x3f, 0x0f, 0xa5, 0x12, 0x5a, 0xb5, 0x6a, 0x68, 0x9d, 0x39, 0xaf, 0x25, 0xb7, 0x70, 0x7a, 0xbe, 0x5b, 0xae, 0xd0, 0x4a, 0x33, 0x0a, 0x8e, 0xac, 0xee, 0xdf, 0x3f, 0x37, 0x5b, 0xe3, 0xab, 0x08, 0xbe, 0xd2, 0xd4, 0xd0, 0x5d, 0x33, 0x11, 0x04, 0x13, 0x98, 0xb1, 0xa6, 0x3e, 0x18, 0xf5, 0xaa, 0xb6, 0xcf, 0x88, 0x26, 0x87, 0xbc, 0x06, 0x63, 0xb2, 0x51, 0xaa, 0x6e, 0x08, 0x57, 0x66, 0x34, 0xcc, 0xc9, 0xb5, 0x96, 0x15, 0x87, 0xd4, 0x79, 0xaf, 0x77, 0xd4, 0x8d, 0x01, 0x61, 0x00, 0xa1, 0x85, 0xf2, 0xd9, 0xb3, 0x38, 0x8e, 0x68, 0x6d, 0xb4, 0x2a, 0x15, 0x2a, 0x20, 0x81, 0x4b, 0x62, 0x9c, 0x1c, 0x53, 0x62, 0x9c, 0x82, 0x44, 0x10, 0x4d, 0x80, 0x73, 0x22, 0xc6, 0xc9, 0x73, 0xe9, 0xd1, 0x23, 0x88, 0x13, 0xf5, 0x2c, 0x94, 0xe1, 0xde, 0x5c, 0xb9, 0x71, 0x64, 0x04, 0x0e, 0x34, 0x97, 0x1b, 0xa8, 0x02, 0x0e, 0x34, 0x50, 0x70, 0x64, 0x55, 0xff, 0x4d, 0x73, 0x72, 0xb4, 0xfe, 0xaa, 0x50, 0xff, 0xf2, 0x98, 0xe9, 0x23, 0xb7, 0xb6, 0x73, 0x8a, 0x81, 0xa5, 0x94, 0xc3, 0x81, 0x59, 0xb7, 0xf4, 0xdf, 0x8c, 0x74, 0x8e, 0x0b, 0x5f, 0x0a, 0x48, 0x48, 0x67, 0x22, 0xc4, 0xaf, 0xb8, 0x53, 0x3b, 0x19, 0xdb, 0x64, 0x49, 0xc0, 0x36, 0x39, 0xc7, 0x82, 0xfa, 0x5c, 0xb2, 0x9c, 0x7d, 0x2c, 0xb8, 0xce, 0x15, 0xb4, 0x67, 0xbe, 0xa2, 0xf6, 0x2e, 0xdf, 0x14, 0x17, 0xf8, 0x1a, 0x4e, 0x70, 0x84, 0x08, 0x5f, 0x0c, 0x4c, 0x35, 0x51, 0xd3, 0x99, 0x70, 0x1c, 0x99, 0x3f, 0x72, 0x5b, 0x6a, 0x5b, 0x6b, 0xc3, 0xd6, 0xbe, 0xdc, 0xa9, 0x14, 0x20, 0xee, 0x14, 0x7a, 0xd4, 0x08, 0x32, 0x62, 0x27, 0x97, 0xc6, 0x0f, 0x5e, 0xa0, 0x7b, 0x7b, 0x27, 0xa9, 0x9c, 0xa8, 0x17, 0x8d, 0x76, 0x4f, 0x3c, 0x7e, 0xe8, 0x9e, 0xea, 0x1f, 0xcc, 0xcf, 0xf1, 0xfd, 0xec, 0x5f, 0xb9, 0x39, 0x4f, 0x86, 0xe7, 0x0c, 0x92, 0x4f, 0x01, 0x0f, 0x8f, 0xb2, 0x8c, 0x81, 0xa8, 0xc6, 0xe6, 0xfb, 0xe2, 0x65, 0xc6, 0xe6, 0xfa, 0x72, 0xed, 0x98, 0xaf, 0xa0, 0x1d, 0xdf, 0x65, 0xdb, 0x89, 0xda, 0x26, 0xfd, 0x9c, 0x88, 0xe6, 0x72, 0xc6, 0x62, 0x2a, 0x73, 0x17, 0xba, 0xe9, 0x53, 0x23, 0xba, 0x4a, 0xc0, 0x04, 0x44, 0x57, 0xc2, 0x12, 0xbc, 0x10, 0x3f, 0xd5, 0x68, 0x09, 0x38, 0x65, 0x69, 0x1c, 0xed, 0x9b, 0x30, 0xeb, 0x3b, 0x63, 0x2a, 0xd4, 0xe4, 0xc9, 0xe6, 0xec, 0x7c, 0x17, 0xbe, 0xa2, 0x4d, 0xb4, 0x80, 0xc8, 0x24, 0xce, 0x9d, 0xe4, 0x60, 0x5d, 0x1c, 0x24, 0x2a, 0x25, 0x86, 0xee, 0x9a, 0x1b, 0x43, 0x77, 0x39, 0x12, 0xd1, 0x5d, 0xfc, 0xc4, 0x5f, 0xaa, 0x94, 0x33, 0x1e, 0x93, 0xea, 0xf2, 0x6d, 0x99, 0xaf, 0xa8, 0xad, 0xe4, 0x2b, 0x68, 0xeb, 0x72, 0xcd, 0x8c, 0x81, 0xa1, 0x32, 0x89, 0x4c, 0x8d, 0x67, 0x3c, 0x3a, 0x6d, 0x7c, 0x0c, 0xe6, 0x38, 0x36, 0x0d, 0x93, 0x55, 0x9c, 0x0e, 0x94, 0x36, 0x95, 0x8c, 0x3c, 0x38, 0xb8, 0xee, 0xec, 0x9e, 0xc6, 0xfa, 0x1b, 0xcf, 0x6c, 0x28, 0x5d, 0xd6, 0xdf, 0x60, 0x0d, 0xca, 0xd2, 0x4c, 0x8a, 0xb4, 0xe2, 0x19, 0x91, 0x86, 0xb5, 0x33, 0xfc, 0xc7, 0x8f, 0x6c, 0xdd, 0xf1, 0x4b, 0xb5, 0x1e, 0x5c, 0x58, 0xdb, 0x7f, 0xc7, 0xe2, 0xfc, 0xa6, 0x1b, 0x5f, 0x1b, 0x19, 0x39, 0x7d, 0x63, 0x93, 0xde, 0x1d, 0xb4, 0xd4, 0xeb, 0xa4, 0x4a, 0x91, 0xc9, 0xa8, 0x0a, 0xcd, 0xd9, 0xdd, 0xf1, 0xca, 0x8b, 0x1b, 0x97, 0xe9, 0x54, 0x43, 0x78, 0xfe, 0xcd, 0x17, 0xfe, 0x4e, 0xb7, 0x60, 0xbb, 0xf8, 0x51, 0x6e, 0xbf, 0xa7, 0xc2, 0xfe, 0x03, 0x1a, 0x30, 0xf4, 0x30, 0xd2, 0x1f, 0xf1, 0x75, 0x03, 0x56, 0xf3, 0x27, 0xcc, 0xfc, 0x25, 0x8b, 0x39, 0xe3, 0xbb, 0xfe, 0xb2, 0xad, 0x5d, 0xb6, 0xa1, 0x31, 0xec, 0x98, 0xd3, 0x89, 0xf6, 0x2a, 0x9e, 0xae, 0x71, 0x54, 0x03, 0x1b, 0xb9, 0xc7, 0xf3, 0x9e, 0xdc, 0x31, 0x42, 0xd1, 0x9f, 0x8b, 0x2e, 0x40, 0xd8, 0x6d, 0x0d, 0xf5, 0xdd, 0xb5, 0xe3, 0xc8, 0xc2, 0x7e, 0x0e, 0x75, 0x48, 0xea, 0x0e, 0x1c, 0x18, 0xfd, 0xcb, 0x50, 0xe7, 0xca, 0x31, 0x44, 0x25, 0x8a, 0x93, 0xda, 0x7a, 0xe1, 0xef, 0x50, 0x30, 0x15, 0x91, 0x02, 0xe2, 0x7d, 0xac, 0xab, 0x12, 0x50, 0x86, 0x7e, 0x1b, 0xce, 0x93, 0x01, 0xea, 0xaa, 0x7f, 0xe3, 0x04, 0x64, 0xa9, 0x13, 0x21, 0xed, 0xeb, 0x8d, 0x50, 0x88, 0xae, 0xb3, 0xe0, 0x6f, 0x22, 0xfe, 0x1b, 0x2f, 0x41, 0x3b, 0xa6, 0x22, 0x85, 0x9c, 0x55, 0x02, 0x33, 0x20, 0xab, 0x20, 0x06, 0xa8, 0x4a, 0x23, 0x84, 0x50, 0x35, 0x15, 0x8a, 0x86, 0xa7, 0xae, 0x41, 0x88, 0x44, 0x09, 0x15, 0x90, 0xd8, 0x9d, 0x41, 0x08, 0x49, 0x11, 0x39, 0xae, 0x02, 0xb6, 0x3a, 0x30, 0x31, 0x2c, 0x88, 0x73, 0x5c, 0xa5, 0xa8, 0x3f, 0x5e, 0x1e, 0xaa, 0x9a, 0x22, 0xc1, 0xec, 0x8b, 0x56, 0x23, 0xf9, 0x04, 0xec, 0x6e, 0xb7, 0x16, 0x19, 0xa2, 0x54, 0x5c, 0xc4, 0x98, 0x49, 0xa4, 0x59, 0x0b, 0x95, 0x92, 0x49, 0x92, 0x79, 0x1e, 0xad, 0x3a, 0x33, 0x15, 0x55, 0x56, 0xab, 0x5e, 0x9a, 0x80, 0xa9, 0xca, 0x9e, 0xc2, 0x48, 0x25, 0x1a, 0x2f, 0x87, 0xa3, 0x5c, 0xd8, 0x50, 0x9b, 0x9d, 0xc6, 0x3c, 0x02, 0x35, 0x82, 0x67, 0xb8, 0xdd, 0xa4, 0xb0, 0x00, 0x91, 0x20, 0x08, 0xe5, 0x6a, 0x29, 0x52, 0x26, 0xd0, 0x7d, 0xeb, 0xd8, 0x03, 0x86, 0xa4, 0x62, 0x91, 0xc2, 0x73, 0xe0, 0x4c, 0x4a, 0x44, 0x42, 0xc9, 0xb0, 0x18, 0xdd, 0x87, 0x23, 0xad, 0x63, 0x20, 0x36, 0x19, 0x4b, 0xd1, 0x78, 0x25, 0x3d, 0x48, 0x3b, 0x1b, 0x44, 0xc7, 0x12, 0x4f, 0xc2, 0x7c, 0x14, 0x68, 0x30, 0x12, 0xab, 0x44, 0x20, 0xbc, 0x59, 0x07, 0xac, 0x4a, 0x30, 0x14, 0x42, 0xc5, 0x5c, 0xa6, 0x26, 0xa7, 0xd1, 0x65, 0x13, 0xd9, 0x59, 0x19, 0x4e, 0xb7, 0xcf, 0x89, 0xcd, 0x4b, 0x32, 0x94, 0x0e, 0x17, 0xdd, 0xbc, 0xf0, 0x7f, 0x26, 0xdf, 0x47, 0x8e, 0x0b, 0x2c, 0x11, 0x51, 0x33, 0xd3, 0xd8, 0x62, 0x30, 0x9f, 0xbd, 0x17, 0x9c, 0x1d, 0xdd, 0x62, 0x9b, 0xe6, 0x82, 0x6a, 0x76, 0x65, 0xdd, 0x75, 0x2f, 0xae, 0x18, 0x3e, 0x5a, 0x52, 0x26, 0x50, 0xab, 0x54, 0x8e, 0xbc, 0x96, 0xe2, 0xca, 0x91, 0x19, 0x19, 0xc1, 0xb6, 0xe1, 0xf2, 0xba, 0x10, 0xf5, 0xc5, 0x5d, 0xec, 0x67, 0xb7, 0xb1, 0x7f, 0x38, 0x08, 0x8e, 0x8b, 0x24, 0x8d, 0xbb, 0x5f, 0x5e, 0x31, 0xfc, 0xda, 0x9e, 0x96, 0xda, 0xa2, 0x1e, 0x91, 0xdc, 0x94, 0x62, 0xca, 0xe9, 0xbf, 0xae, 0xb5, 0x7d, 0x7b, 0x4f, 0x96, 0x12, 0x3c, 0x80, 0x93, 0xa4, 0xf5, 0x5f, 0x30, 0x30, 0x1a, 0xc1, 0x62, 0x42, 0x42, 0x68, 0x89, 0x4e, 0xe2, 0xc3, 0xa8, 0xb5, 0x38, 0x48, 0x0a, 0x98, 0xca, 0x72, 0x12, 0x08, 0x9a, 0x80, 0x08, 0x34, 0x6b, 0x49, 0x42, 0x9c, 0x0a, 0x28, 0xc2, 0x06, 0xe7, 0x49, 0xc0, 0x87, 0xd6, 0xcc, 0x41, 0x41, 0x8c, 0x05, 0xcc, 0x20, 0x21, 0x82, 0x0a, 0x95, 0x48, 0x3c, 0x4c, 0x88, 0x09, 0x20, 0x26, 0x96, 0xd3, 0xc8, 0xf3, 0x8d, 0x5c, 0x08, 0xb7, 0xce, 0xd8, 0x84, 0x60, 0x55, 0x78, 0x28, 0x9e, 0x0b, 0x25, 0xfb, 0xe2, 0x35, 0xc9, 0x55, 0x53, 0x57, 0x8c, 0x06, 0x51, 0x1d, 0x66, 0xe4, 0xca, 0xab, 0xc0, 0xfd, 0xa9, 0xef, 0x68, 0xab, 0xab, 0x89, 0x96, 0x16, 0xe6, 0xe5, 0xe6, 0xb8, 0x1c, 0x29, 0xd6, 0x64, 0x73, 0x85, 0x46, 0x22, 0x31, 0xa0, 0x48, 0x8f, 0x28, 0x01, 0x3a, 0x94, 0xf6, 0x95, 0x28, 0x84, 0xa3, 0xce, 0x60, 0x64, 0x14, 0x40, 0x89, 0x8d, 0x5b, 0x11, 0x8f, 0x57, 0x88, 0x13, 0xe8, 0x71, 0xa8, 0x4d, 0x2e, 0x47, 0x91, 0x30, 0x9c, 0x83, 0x16, 0x24, 0x82, 0x3f, 0xc3, 0xba, 0xfc, 0xca, 0x78, 0x29, 0xde, 0x9e, 0x6c, 0x24, 0xb7, 0xd9, 0x6b, 0x3c, 0x8b, 0x75, 0x99, 0x66, 0x79, 0x92, 0x7c, 0x53, 0xed, 0xda, 0xd6, 0x8c, 0x75, 0x0b, 0x28, 0xa6, 0xfe, 0x47, 0x37, 0xa8, 0x49, 0xab, 0xb1, 0x4e, 0x5f, 0x5f, 0xa9, 0x01, 0x29, 0xc6, 0x5a, 0xf3, 0x9c, 0xbb, 0x96, 0x14, 0x34, 0xdf, 0xf4, 0xd6, 0x26, 0x25, 0xfa, 0xa6, 0x11, 0xa9, 0xc5, 0xd6, 0xb0, 0x59, 0x01, 0x3f, 0xd7, 0x19, 0x1a, 0xae, 0xcd, 0xad, 0x16, 0xe9, 0xd4, 0xb2, 0x64, 0x77, 0x56, 0xea, 0x60, 0xc3, 0xca, 0x30, 0x86, 0x72, 0xbe, 0x22, 0x95, 0x00, 0xba, 0x9a, 0x86, 0xe4, 0xce, 0x9c, 0xd4, 0x7f, 0xdd, 0x63, 0x83, 0x8f, 0x7c, 0x51, 0xe1, 0xef, 0x1e, 0x52, 0x3d, 0xae, 0xb3, 0xfd, 0x1a, 0x28, 0x4e, 0xea, 0x6c, 0x40, 0xee, 0xdd, 0x7e, 0xfc, 0x97, 0x1b, 0xf7, 0x7d, 0x71, 0xea, 0xda, 0xa4, 0x83, 0x3a, 0xdb, 0xab, 0x50, 0xc1, 0xbc, 0x59, 0x67, 0xfb, 0x5f, 0x63, 0x63, 0xce, 0x80, 0x54, 0xa1, 0x32, 0x59, 0x15, 0xe7, 0x3e, 0x96, 0x16, 0x64, 0x0b, 0x11, 0xb8, 0x13, 0x10, 0xf3, 0x2f, 0x7c, 0xce, 0x24, 0x31, 0x8f, 0x13, 0xd3, 0x88, 0x3f, 0x72, 0x94, 0x41, 0xef, 0x82, 0xcc, 0x7f, 0x1a, 0x54, 0x93, 0x4a, 0x44, 0x50, 0x3f, 0x88, 0xe8, 0xc4, 0x70, 0xcd, 0x68, 0x78, 0x54, 0xf8, 0xe7, 0x60, 0xfc, 0xf3, 0x1e, 0x8e, 0xf7, 0x65, 0x10, 0x8c, 0x40, 0x28, 0x60, 0x84, 0x48, 0x3b, 0x47, 0x80, 0xb1, 0x61, 0x94, 0x9b, 0x4a, 0x48, 0x74, 0xc1, 0xa9, 0xc7, 0x8a, 0xd6, 0x52, 0x4e, 0x53, 0x1f, 0x5b, 0x6c, 0xf4, 0xa6, 0x4c, 0x58, 0x49, 0x24, 0x60, 0x44, 0xf1, 0x4a, 0x04, 0x0a, 0x1d, 0x4d, 0x8a, 0x88, 0x81, 0xb1, 0x6a, 0x44, 0x42, 0xad, 0x68, 0x6e, 0xbc, 0x02, 0x42, 0x36, 0x10, 0xc8, 0x16, 0x80, 0x92, 0x81, 0x2f, 0x8f, 0x41, 0x6a, 0xa6, 0xac, 0x86, 0xa8, 0x91, 0xdb, 0x0f, 0x8f, 0x96, 0xcb, 0xef, 0x50, 0xc7, 0xd2, 0xa1, 0x73, 0x59, 0x3c, 0x79, 0xcb, 0x32, 0x9f, 0x06, 0x2b, 0x1e, 0xbb, 0x65, 0xd2, 0x09, 0x33, 0xaa, 0xb1, 0x3f, 0x3f, 0xe7, 0x78, 0x49, 0x9f, 0x11, 0x9a, 0x15, 0xf9, 0xb6, 0xaa, 0x4e, 0x87, 0xaf, 0xba, 0xb4, 0xc0, 0x2a, 0xd2, 0x48, 0xd5, 0x8a, 0xf6, 0xee, 0xd1, 0x67, 0xee, 0x1d, 0x3d, 0x31, 0x17, 0x27, 0x85, 0xb8, 0xfb, 0xb3, 0xee, 0x6a, 0xa1, 0x4e, 0xa3, 0x0d, 0xb5, 0x6e, 0x9c, 0x89, 0xd2, 0x47, 0x14, 0x2d, 0xbe, 0xa5, 0x63, 0x41, 0xaa, 0x89, 0x91, 0x66, 0x8c, 0x34, 0xda, 0x8b, 0x0c, 0xe4, 0x47, 0x16, 0xe9, 0xd2, 0xb9, 0xec, 0x23, 0x8c, 0x36, 0xc5, 0x67, 0x86, 0x8b, 0xd0, 0xb7, 0x68, 0x24, 0x1e, 0x95, 0x65, 0x68, 0x7e, 0x1b, 0x5c, 0x22, 0x4f, 0x96, 0x07, 0xc5, 0x64, 0x59, 0xfd, 0xf2, 0x8d, 0x5d, 0x4a, 0xf2, 0x45, 0xa3, 0x75, 0xef, 0xbc, 0x61, 0x85, 0x12, 0xad, 0x55, 0x35, 0x54, 0x5b, 0x2b, 0x70, 0x2e, 0xbb, 0x08, 0x9c, 0x0b, 0x34, 0x09, 0xf8, 0x06, 0x1c, 0xd2, 0x1b, 0x94, 0x7f, 0x60, 0x2e, 0x0a, 0x63, 0x8f, 0xc1, 0xad, 0x03, 0x0d, 0x1c, 0x69, 0xc1, 0xb9, 0xed, 0xd4, 0xb1, 0xff, 0x70, 0xb6, 0xf0, 0x38, 0x49, 0x89, 0x9b, 0x85, 0x3e, 0x04, 0xff, 0x60, 0x65, 0x20, 0x9b, 0xfd, 0xe9, 0xfb, 0xaf, 0xbf, 0x4e, 0x15, 0xd2, 0x7f, 0x63, 0xdb, 0x9e, 0x60, 0xcf, 0xad, 0x40, 0x59, 0xa0, 0x56, 0xd0, 0xb2, 0x27, 0x30, 0x8f, 0xcf, 0x83, 0xef, 0x9d, 0xc3, 0x1c, 0x23, 0x2a, 0x88, 0xfa, 0x68, 0x4d, 0x0e, 0x10, 0x49, 0x8c, 0x2a, 0x19, 0x82, 0x41, 0xd7, 0x4b, 0x81, 0x48, 0x08, 0xf9, 0x95, 0x88, 0x80, 0xbd, 0x88, 0x53, 0x4a, 0x89, 0x44, 0xdc, 0x4b, 0x88, 0xc5, 0x4b, 0x31, 0x91, 0xeb, 0xe5, 0x7a, 0x52, 0x51, 0x5e, 0x5c, 0x98, 0x19, 0x80, 0xc4, 0xcd, 0xe8, 0x76, 0x3b, 0xd5, 0x2a, 0x4c, 0xdf, 0x50, 0x24, 0x2e, 0x74, 0xf3, 0x85, 0x1c, 0x3c, 0xc2, 0x21, 0x3b, 0x87, 0xc0, 0xcc, 0x2b, 0xa5, 0xb8, 0x8c, 0xdf, 0x4e, 0x0c, 0x46, 0x4d, 0x48, 0x3b, 0xe8, 0xe1, 0xc5, 0x19, 0x2b, 0xb0, 0x53, 0xcb, 0xd8, 0x19, 0xe0, 0x29, 0x9b, 0x89, 0xdd, 0x1d, 0x09, 0x96, 0x96, 0x90, 0xfb, 0x65, 0x06, 0x9b, 0xf6, 0x7c, 0xa9, 0xa3, 0x34, 0x98, 0x7c, 0xfa, 0xf4, 0x69, 0x9b, 0xe9, 0x86, 0xc8, 0xf2, 0x47, 0x56, 0x34, 0x6f, 0x9a, 0x5d, 0x95, 0xd4, 0x9c, 0x9c, 0x63, 0xd0, 0xfa, 0xb2, 0xcb, 0xfc, 0xc5, 0xbd, 0x25, 0x29, 0xe0, 0x29, 0xf2, 0xe3, 0xed, 0x45, 0x3a, 0xfb, 0xea, 0xda, 0x65, 0x19, 0x8a, 0x36, 0x65, 0xaa, 0x49, 0xb5, 0x59, 0x62, 0xcb, 0x9e, 0x96, 0xf1, 0xe8, 0xf0, 0xf6, 0x62, 0x9d, 0xed, 0x96, 0x59, 0x47, 0x36, 0xd6, 0x7a, 0x2b, 0xfb, 0xf2, 0x72, 0x14, 0x74, 0x52, 0x52, 0xc0, 0xae, 0xf1, 0x35, 0x2c, 0xa9, 0xb8, 0x95, 0xc0, 0x69, 0xa9, 0xa0, 0x24, 0xc1, 0xfc, 0x2f, 0x9c, 0x77, 0x0a, 0xd2, 0x16, 0x39, 0xa4, 0x83, 0x45, 0x44, 0x7f, 0x74, 0xa6, 0x46, 0xa5, 0xa0, 0x18, 0xbd, 0x56, 0x4e, 0x41, 0x69, 0x99, 0x21, 0xe6, 0xc9, 0x04, 0x3a, 0x9a, 0x06, 0x52, 0xb1, 0x10, 0x65, 0xaa, 0x27, 0xe7, 0xaa, 0x81, 0x52, 0x39, 0xd0, 0x20, 0x81, 0x2b, 0x32, 0x5f, 0xd4, 0xa8, 0xd3, 0x49, 0x24, 0xba, 0x22, 0x5d, 0x51, 0x61, 0x41, 0x7e, 0x24, 0x9c, 0x97, 0x13, 0xcc, 0xca, 0xf0, 0xa7, 0xf9, 0xbc, 0x1e, 0xb7, 0xcb, 0xe9, 0xb0, 0xa7, 0x24, 0x5b, 0x92, 0x0c, 0x7a, 0xad, 0x46, 0x22, 0x97, 0xc8, 0x35, 0x7a, 0xb5, 0xca, 0x80, 0x53, 0x57, 0x19, 0x43, 0x90, 0x0c, 0xc1, 0x89, 0x70, 0x7b, 0x9d, 0x7a, 0x0a, 0xa9, 0x86, 0x21, 0x46, 0x1f, 0xd2, 0x8f, 0x05, 0x94, 0xe1, 0xd7, 0x8f, 0x9f, 0x25, 0xce, 0xde, 0x8e, 0xa4, 0xed, 0x90, 0x9e, 0xde, 0xc1, 0x7e, 0x58, 0xf5, 0x1b, 0x21, 0x4d, 0x1d, 0x62, 0x47, 0xab, 0x7e, 0x2f, 0xa0, 0xa8, 0x87, 0xc9, 0x5f, 0x3c, 0x69, 0x33, 0xad, 0x7d, 0x34, 0xc9, 0xae, 0x66, 0x3f, 0x66, 0xef, 0x7c, 0xea, 0xc9, 0x27, 0x9f, 0xb4, 0x25, 0xad, 0x39, 0x9a, 0x9c, 0xac, 0x06, 0x6e, 0x30, 0xef, 0xc9, 0x27, 0x29, 0x66, 0x03, 0xbb, 0x32, 0xa5, 0xc8, 0xfc, 0xc4, 0x8a, 0x0d, 0x1b, 0x48, 0x93, 0xb5, 0xd0, 0xf4, 0xda, 0xf0, 0x86, 0x22, 0x9d, 0x6d, 0xf4, 0x13, 0x8d, 0x0e, 0xfc, 0x73, 0xf4, 0x13, 0x32, 0x79, 0xc5, 0x06, 0x38, 0x3d, 0xa4, 0x57, 0x6e, 0x1c, 0x1d, 0x25, 0xbd, 0xa3, 0xff, 0x8d, 0x8c, 0x27, 0x70, 0x26, 0x32, 0xe0, 0x9e, 0x58, 0x85, 0x73, 0x82, 0xc9, 0xa1, 0x94, 0x55, 0x13, 0xad, 0x94, 0x22, 0xf0, 0xbe, 0x80, 0xc0, 0x50, 0x78, 0xbc, 0x01, 0x06, 0x1a, 0x70, 0x7a, 0x05, 0xb2, 0x47, 0x02, 0x09, 0x06, 0x39, 0x88, 0x74, 0x88, 0xf9, 0x82, 0x46, 0x40, 0x38, 0x1d, 0x29, 0x3c, 0x22, 0x57, 0xaf, 0x55, 0x2a, 0xe4, 0x52, 0xa1, 0x80, 0x46, 0xa0, 0x0e, 0x46, 0xc6, 0xa5, 0x60, 0x8d, 0xd8, 0x93, 0x80, 0x1d, 0xc5, 0x41, 0x03, 0x4e, 0x45, 0x3c, 0x70, 0x1b, 0xce, 0xfe, 0x64, 0x0c, 0x81, 0x7f, 0xdc, 0x79, 0x50, 0x01, 0xd7, 0xfe, 0x69, 0x26, 0xad, 0x69, 0xf5, 0xf4, 0x11, 0xf2, 0x8b, 0xeb, 0xc4, 0xb5, 0x9b, 0x4f, 0xae, 0x1d, 0x79, 0x73, 0xef, 0x5e, 0x50, 0xb4, 0xf9, 0xa5, 0x6b, 0xcb, 0x05, 0x3b, 0xc8, 0xdb, 0xaf, 0xdb, 0x78, 0x81, 0x00, 0xc3, 0xd1, 0xd9, 0x65, 0x29, 0xec, 0x01, 0x44, 0x59, 0x59, 0x21, 0x75, 0xfb, 0x30, 0xb8, 0x33, 0x6f, 0xe1, 0x41, 0x84, 0x73, 0x7a, 0xe7, 0xc2, 0xdf, 0x85, 0x5e, 0xd8, 0x6f, 0x09, 0xec, 0x75, 0x1f, 0xf1, 0x7c, 0x54, 0xd1, 0x07, 0x84, 0x84, 0x1e, 0xea, 0xfa, 0x65, 0x80, 0x92, 0x20, 0xeb, 0x16, 0x72, 0x8d, 0x2b, 0x20, 0x04, 0x42, 0x5a, 0x88, 0x2e, 0x07, 0xc4, 0xb4, 0x84, 0x16, 0x43, 0x9e, 0x2e, 0x25, 0x84, 0x02, 0xa9, 0x70, 0x40, 0x86, 0xd2, 0xf0, 0x8a, 0x11, 0x62, 0x0a, 0x99, 0x8d, 0xe4, 0x5c, 0x2a, 0x34, 0x89, 0x84, 0xea, 0x15, 0x21, 0x1c, 0xc1, 0x7c, 0x44, 0xa4, 0x4a, 0xae, 0xa6, 0x2e, 0x92, 0x9b, 0xe6, 0xa3, 0x16, 0x70, 0x8e, 0x83, 0x15, 0x22, 0x28, 0x0d, 0x38, 0x11, 0x6c, 0x79, 0x66, 0x77, 0x5d, 0x4d, 0x65, 0x45, 0x51, 0x41, 0xc0, 0xef, 0x71, 0x3b, 0xec, 0xb6, 0x14, 0xb3, 0x49, 0xea, 0x94, 0x39, 0x39, 0xf0, 0xb2, 0x42, 0x12, 0xcb, 0xf7, 0xe4, 0x49, 0x00, 0x2f, 0x0b, 0xe1, 0x0e, 0xe0, 0x02, 0xf3, 0xe1, 0x39, 0x03, 0x09, 0x69, 0x3a, 0xe9, 0xc9, 0x49, 0x46, 0xb0, 0x42, 0x46, 0xc7, 0x04, 0x0a, 0xb2, 0xba, 0xf5, 0xda, 0x76, 0xff, 0x83, 0x0f, 0x3e, 0xfe, 0xf8, 0x9e, 0x37, 0xb7, 0x97, 0x65, 0xf6, 0x6c, 0x9d, 0x91, 0x96, 0x0c, 0x7a, 0x5f, 0xcd, 0x4f, 0x66, 0x7f, 0xa9, 0xf6, 0xa6, 0x2d, 0xfb, 0x85, 0xc0, 0x23, 0x37, 0x29, 0xec, 0xe9, 0x05, 0x9e, 0xe2, 0xea, 0xe2, 0x99, 0x8b, 0x66, 0x16, 0xfb, 0x5b, 0xd7, 0xb7, 0xf8, 0x67, 0xd4, 0x96, 0x99, 0xd2, 0x45, 0x46, 0x8d, 0xc6, 0x16, 0x28, 0xce, 0x70, 0xe7, 0x39, 0x54, 0xc5, 0xbd, 0x8b, 0x7b, 0x8b, 0xbd, 0x4d, 0x23, 0x0d, 0xed, 0xbb, 0x33, 0xc9, 0x3f, 0xf9, 0xea, 0x16, 0x94, 0x6c, 0xda, 0x3c, 0xfa, 0x77, 0xf2, 0x44, 0xc1, 0xe2, 0x03, 0x7d, 0x0d, 0x3b, 0x17, 0x37, 0xea, 0x4c, 0xe9, 0xa3, 0x3f, 0xcb, 0x00, 0x5f, 0x26, 0xe9, 0x47, 0x9f, 0x65, 0xee, 0x3d, 0x7f, 0xce, 0xa8, 0xd6, 0xaa, 0x73, 0xfd, 0x2e, 0x7f, 0xaa, 0x2d, 0xb7, 0xb5, 0xa2, 0x68, 0xa0, 0x36, 0x4d, 0x69, 0x71, 0xeb, 0xeb, 0xe4, 0x0a, 0xbd, 0x49, 0xaf, 0xb6, 0xba, 0xf5, 0xde, 0xf4, 0xd4, 0xd4, 0x70, 0x77, 0x5d, 0xce, 0xcc, 0x4a, 0x5f, 0x24, 0x93, 0xc0, 0xd9, 0xea, 0xfa, 0xe1, 0x5f, 0x5f, 0xe2, 0x73, 0x28, 0x23, 0x54, 0x44, 0x12, 0x61, 0x25, 0x3c, 0x44, 0x37, 0xf1, 0x1c, 0x12, 0x9d, 0x45, 0x1a, 0x83, 0x98, 0x94, 0xc6, 0x8d, 0x5c, 0x39, 0x4a, 0xb1, 0x20, 0x89, 0x66, 0x80, 0x42, 0x26, 0x61, 0x68, 0x52, 0x0a, 0x0f, 0xa4, 0x41, 0x47, 0x6a, 0x34, 0x03, 0x0d, 0x66, 0x93, 0x4a, 0x48, 0x31, 0x04, 0x21, 0xea, 0x41, 0xf1, 0x87, 0x68, 0x91, 0x68, 0xb0, 0x41, 0x0e, 0xa4, 0xd2, 0xf9, 0x52, 0x24, 0x48, 0x70, 0x8f, 0xf8, 0x22, 0x90, 0xaa, 0xcd, 0xbb, 0x58, 0x2b, 0x7c, 0x95, 0x9e, 0x68, 0x81, 0xd7, 0x2b, 0x97, 0x7b, 0xbb, 0xbd, 0xdd, 0x5d, 0x9d, 0x1d, 0x6d, 0xd3, 0x5b, 0x1a, 0xeb, 0xaa, 0xab, 0x2a, 0xa2, 0x65, 0x25, 0xdc, 0x41, 0xcf, 0x0d, 0xe5, 0x64, 0x07, 0xb3, 0x32, 0x03, 0x69, 0x3e, 0x0e, 0x84, 0xae, 0x51, 0xcb, 0x55, 0x72, 0x55, 0x92, 0x5a, 0xad, 0xb2, 0xf0, 0x87, 0x5c, 0xe8, 0x34, 0xf2, 0xb7, 0x64, 0x5e, 0xa7, 0x12, 0xc4, 0xbe, 0x50, 0xa1, 0x88, 0xdd, 0x1d, 0xf2, 0x72, 0x19, 0x4f, 0x19, 0xa1, 0x1d, 0x9f, 0x7b, 0xca, 0x49, 0xd9, 0xd1, 0x9f, 0x3c, 0xbb, 0x97, 0x3b, 0xf4, 0x54, 0x88, 0xcb, 0x05, 0x8a, 0x64, 0x8f, 0xb1, 0x4f, 0x54, 0x48, 0x2f, 0xa8, 0x63, 0x3f, 0xf1, 0xbe, 0x9b, 0x74, 0x9f, 0x5f, 0x94, 0x24, 0x15, 0xaa, 0xc5, 0xec, 0x69, 0xcf, 0x71, 0xef, 0x2b, 0x87, 0x3c, 0x22, 0x8d, 0x48, 0x9a, 0x24, 0xda, 0xf7, 0x73, 0x2f, 0xfb, 0x3b, 0xf0, 0xeb, 0xf5, 0x7a, 0x91, 0x41, 0x2c, 0x36, 0x88, 0x28, 0x85, 0x0f, 0x48, 0x6c, 0x49, 0x9f, 0xdd, 0xf1, 0xf1, 0x2d, 0xec, 0x3b, 0xb7, 0x7e, 0x72, 0x07, 0xfb, 0x6b, 0x20, 0xf1, 0xfd, 0xce, 0x96, 0xb4, 0x6f, 0xf5, 0x91, 0x64, 0x2f, 0xd3, 0x07, 0x5a, 0x17, 0xd0, 0x69, 0x96, 0x65, 0x56, 0x2f, 0xbd, 0x00, 0x4c, 0xef, 0x83, 0x1f, 0xef, 0x5e, 0xfd, 0x06, 0x35, 0xf7, 0xd0, 0xf0, 0xd1, 0x9c, 0x0d, 0xd1, 0xf2, 0x0d, 0xd9, 0xc0, 0x7c, 0x08, 0x7d, 0x46, 0x6e, 0xf7, 0x39, 0xf0, 0xf3, 0x70, 0xb7, 0x77, 0x61, 0x28, 0xb4, 0xd0, 0xfb, 0xa3, 0x43, 0x90, 0xca, 0x8e, 0x3e, 0x41, 0xb6, 0xa1, 0x3f, 0xc3, 0xe8, 0x0b, 0x78, 0xdb, 0x62, 0x64, 0x17, 0x82, 0xbb, 0x74, 0x29, 0xdc, 0xbf, 0x7a, 0x2b, 0x9b, 0x37, 0xcc, 0x61, 0x2e, 0x7e, 0x04, 0xa9, 0xc6, 0x87, 0xf4, 0x4e, 0xb8, 0xa2, 0x49, 0x08, 0xcb, 0x47, 0x30, 0x24, 0xe2, 0x67, 0xc3, 0xd8, 0xd0, 0x28, 0x58, 0x2e, 0x16, 0x22, 0x6b, 0x3f, 0x43, 0x90, 0x88, 0xb7, 0x63, 0x7d, 0x0a, 0x9f, 0x32, 0xec, 0x13, 0x9c, 0x24, 0x4f, 0x32, 0x19, 0x74, 0x5a, 0xec, 0x15, 0x2c, 0x73, 0x68, 0xed, 0x12, 0x89, 0x31, 0x1d, 0x11, 0x0a, 0x1c, 0xb1, 0x22, 0x17, 0x85, 0xab, 0x55, 0xdb, 0x1d, 0x1e, 0x7c, 0x45, 0x1a, 0xb2, 0xab, 0x7f, 0x44, 0x96, 0xbc, 0xf0, 0xc4, 0x33, 0xcf, 0xb2, 0xab, 0xe9, 0x05, 0xa3, 0x67, 0x5e, 0x7c, 0xfc, 0x99, 0x67, 0xc1, 0x1e, 0x7a, 0xe7, 0x91, 0x23, 0xac, 0x08, 0x7c, 0x7d, 0xe2, 0xbc, 0x8e, 0xde, 0x79, 0x6e, 0xd3, 0xc3, 0x8f, 0x80, 0xaf, 0x59, 0xd1, 0x09, 0xea, 0x2f, 0x31, 0xfc, 0x10, 0x73, 0x2b, 0xce, 0x0f, 0xef, 0x8f, 0x7a, 0x45, 0x02, 0x92, 0x16, 0xa3, 0xa4, 0xa0, 0x34, 0x98, 0x87, 0xa0, 0x91, 0x03, 0x18, 0x10, 0x3b, 0x9f, 0x68, 0x74, 0x3a, 0xb4, 0x18, 0x13, 0xab, 0x36, 0xd9, 0x24, 0x12, 0x8c, 0xdb, 0x85, 0xa7, 0x0c, 0x05, 0xc7, 0x8c, 0x41, 0xf9, 0x13, 0x63, 0x7a, 0x0a, 0x50, 0x32, 0x24, 0x28, 0x52, 0xde, 0xfa, 0xa2, 0xce, 0xaa, 0x57, 0x0b, 0xd9, 0x72, 0xb5, 0xc7, 0xbe, 0xf2, 0x17, 0xef, 0xbe, 0xfb, 0x8b, 0x6a, 0x4f, 0x96, 0x16, 0xbc, 0xa5, 0xcf, 0x72, 0x9c, 0x64, 0x95, 0xa9, 0xc9, 0xe0, 0x29, 0x76, 0x49, 0xb6, 0x4b, 0x20, 0x55, 0x49, 0xc9, 0x6c, 0x83, 0x81, 0x1a, 0x5d, 0x4d, 0xee, 0x3d, 0x4c, 0xae, 0x18, 0xbd, 0x45, 0x6b, 0x24, 0xf5, 0x16, 0xfd, 0xe8, 0x5b, 0x5e, 0xcd, 0x61, 0x7d, 0x6e, 0xac, 0x8f, 0xf4, 0x09, 0x1c, 0xf7, 0x25, 0x2f, 0x9a, 0x63, 0xd2, 0x6a, 0xd4, 0x2a, 0xa5, 0x02, 0x92, 0x53, 0x06, 0xe5, 0x5c, 0x55, 0xa2, 0xeb, 0x79, 0x9c, 0x7c, 0x15, 0xab, 0xc4, 0x09, 0x59, 0x1f, 0x51, 0x24, 0x96, 0xe4, 0x64, 0x2e, 0xeb, 0xa3, 0x5b, 0xcf, 0xe8, 0x51, 0xba, 0x1e, 0x25, 0xa2, 0x19, 0x4c, 0x9e, 0x3b, 0x8f, 0x81, 0x7b, 0xab, 0x0c, 0x70, 0xf0, 0xbf, 0xbf, 0x00, 0x2b, 0xfb, 0x31, 0xb0, 0x9a, 0xb5, 0x9f, 0xbf, 0xc7, 0xfe, 0x05, 0xe8, 0xd8, 0xbf, 0x98, 0x75, 0x7f, 0x60, 0xc3, 0xf4, 0x42, 0xfa, 0xcf, 0xec, 0xbb, 0xb7, 0x5f, 0x7f, 0xe0, 0xd6, 0xdb, 0xbb, 0x34, 0xe9, 0xe2, 0xcd, 0xa3, 0xbf, 0x03, 0x2f, 0xdf, 0xbc, 0xeb, 0xe6, 0x5b, 0x6f, 0xee, 0xd4, 0xfa, 0x25, 0xeb, 0xc0, 0x2d, 0x28, 0x9c, 0x1b, 0x3c, 0xa1, 0xa5, 0xec, 0x31, 0x71, 0x31, 0xec, 0x5b, 0x80, 0x28, 0x23, 0xda, 0x89, 0x51, 0x2e, 0x2e, 0x4d, 0x2b, 0xce, 0xc7, 0x64, 0x41, 0xff, 0x0a, 0xe2, 0x71, 0x69, 0xf2, 0x08, 0x78, 0xb8, 0x68, 0x66, 0x11, 0x94, 0xfc, 0x68, 0x52, 0xb4, 0x1c, 0xab, 0xee, 0x88, 0x99, 0x0c, 0x20, 0x29, 0x0e, 0xd3, 0xc5, 0xe5, 0x0d, 0x08, 0xba, 0x06, 0xfa, 0xa0, 0x4e, 0x05, 0x30, 0xb5, 0x5c, 0x24, 0x89, 0x05, 0x91, 0x2f, 0xbb, 0x82, 0xca, 0x44, 0x42, 0x5d, 0x04, 0x03, 0x1d, 0xd7, 0x4c, 0x34, 0xca, 0xb5, 0x80, 0x84, 0xc9, 0xef, 0xd6, 0x04, 0x14, 0x30, 0xad, 0x80, 0x68, 0x6f, 0x9b, 0xde, 0x5c, 0x5d, 0x59, 0x54, 0x90, 0x9d, 0x95, 0xe6, 0x75, 0x39, 0xcc, 0x26, 0x9d, 0x46, 0x2a, 0x26, 0x02, 0x20, 0xc0, 0x71, 0xb5, 0x14, 0xa0, 0x41, 0xc1, 0xee, 0x69, 0xa7, 0x3d, 0x12, 0xc3, 0x21, 0x22, 0x20, 0x20, 0xf6, 0xee, 0x0a, 0xc7, 0x0d, 0xc2, 0x25, 0x00, 0x70, 0xb9, 0xb2, 0x60, 0x21, 0x0e, 0x70, 0xaa, 0xe4, 0xef, 0x97, 0x85, 0x48, 0x79, 0xf6, 0x78, 0x21, 0x01, 0x08, 0x3c, 0x79, 0xfe, 0x56, 0x8f, 0x2f, 0xed, 0x91, 0x8f, 0xd8, 0x7f, 0xec, 0x63, 0x1d, 0x96, 0x39, 0xdb, 0x32, 0xf7, 0xc8, 0x0b, 0xf2, 0x57, 0x3e, 0x9c, 0xdc, 0x5e, 0xf9, 0xd6, 0xec, 0xa7, 0xef, 0x18, 0xb1, 0xdb, 0xb4, 0xab, 0x3f, 0x78, 0xac, 0xbc, 0x7f, 0x7a, 0x73, 0xb6, 0x33, 0x8b, 0x59, 0x61, 0xec, 0xcb, 0xcf, 0x1b, 0xec, 0xa9, 0x31, 0x9a, 0x80, 0xa7, 0x36, 0x18, 0xe8, 0x6e, 0x8e, 0xea, 0xde, 0x34, 0xcf, 0xdd, 0xf5, 0xc8, 0xdc, 0xce, 0x9b, 0x66, 0x9a, 0x53, 0x93, 0x87, 0x6e, 0x7e, 0xac, 0xbb, 0x7e, 0x99, 0x73, 0xf5, 0xeb, 0xcc, 0xbd, 0x8b, 0x5e, 0x61, 0xff, 0x75, 0xff, 0x61, 0xf6, 0xeb, 0x57, 0x16, 0xcd, 0x15, 0x3e, 0xff, 0x02, 0x99, 0x1e, 0x0e, 0x8d, 0x0e, 0x0d, 0x3f, 0x98, 0xd3, 0x7a, 0xc7, 0xcf, 0xb7, 0x6d, 0xfd, 0xe0, 0xfe, 0x99, 0xa6, 0x64, 0x53, 0xd0, 0xb1, 0xea, 0xdc, 0x70, 0xcd, 0xb4, 0xf0, 0xc2, 0x3b, 0xe6, 0x2c, 0xba, 0xa5, 0x37, 0x2d, 0x6d, 0xc6, 0xfa, 0x16, 0x59, 0x62, 0x92, 0x98, 0xc2, 0x3c, 0x36, 0x9f, 0x3b, 0xd3, 0x85, 0xec, 0x76, 0x7a, 0x21, 0xf3, 0x29, 0x94, 0x94, 0x1a, 0x88, 0xea, 0xa8, 0xba, 0xa1, 0x3a, 0x9a, 0x6e, 0x64, 0x04, 0x28, 0x83, 0x6d, 0x55, 0x38, 0x8f, 0x22, 0x6b, 0xb8, 0x08, 0xee, 0xc8, 0x84, 0x88, 0xed, 0x5b, 0x0b, 0x1a, 0xb8, 0xb0, 0xdf, 0x18, 0xfe, 0x8c, 0xf3, 0x41, 0x0e, 0x70, 0x0f, 0x7a, 0x9e, 0xaf, 0xaf, 0x75, 0xd8, 0x32, 0x29, 0x89, 0x9e, 0x8f, 0xb9, 0x48, 0xc5, 0xac, 0xe7, 0x48, 0xc1, 0x9a, 0x90, 0x38, 0x85, 0x33, 0xe9, 0x08, 0xbd, 0x5c, 0x21, 0xaf, 0x82, 0x12, 0x4a, 0x51, 0x08, 0x1b, 0x40, 0xee, 0x32, 0x07, 0x8a, 0xec, 0x16, 0xa7, 0x56, 0x64, 0x70, 0x66, 0x9a, 0x92, 0x32, 0x1c, 0xba, 0xf6, 0xac, 0x46, 0x5b, 0xb0, 0xbf, 0x2e, 0x23, 0xd4, 0x3e, 0x54, 0x58, 0xbc, 0xb8, 0x25, 0xd3, 0xe0, 0xcd, 0x4d, 0x4d, 0x9f, 0x5e, 0xea, 0xf6, 0x4c, 0xeb, 0x0f, 0x37, 0xda, 0x0b, 0xfd, 0x26, 0xa3, 0x2f, 0x9c, 0xea, 0x6e, 0x98, 0x56, 0xa8, 0xfb, 0xf1, 0x36, 0x55, 0xda, 0x42, 0x90, 0x44, 0x2f, 0x2f, 0x5f, 0x39, 0xd8, 0x9f, 0x11, 0x5d, 0x3e, 0x6f, 0x66, 0x46, 0x59, 0x7f, 0xb1, 0xd5, 0x5a, 0xd8, 0x7d, 0xbe, 0xad, 0xf9, 0xfa, 0xe2, 0x69, 0x7b, 0x0e, 0xdc, 0xdf, 0xdd, 0x7f, 0xcb, 0x40, 0x28, 0x38, 0xe7, 0xe6, 0x39, 0xa5, 0xd7, 0xed, 0xde, 0x53, 0x19, 0x1e, 0xd9, 0x72, 0x6b, 0x67, 0xd3, 0xb6, 0x65, 0x7d, 0xbe, 0x1a, 0x7b, 0x4b, 0xe7, 0xac, 0x60, 0xb0, 0xaf, 0x6f, 0x66, 0x66, 0xd5, 0x96, 0xa1, 0xee, 0x54, 0xb0, 0x97, 0x7d, 0xc1, 0x4f, 0x1e, 0x46, 0x37, 0x4f, 0x44, 0x2a, 0xfc, 0xeb, 0x1a, 0x9c, 0xab, 0x5e, 0x41, 0xe8, 0xa0, 0x74, 0x3d, 0x10, 0x9d, 0x63, 0x85, 0x1b, 0xaa, 0x4e, 0x29, 0x27, 0x69, 0x19, 0x9a, 0x27, 0x9a, 0x98, 0x2b, 0x55, 0x91, 0x40, 0x22, 0xc4, 0x69, 0x70, 0xa0, 0x30, 0x49, 0x12, 0x22, 0x31, 0x29, 0x42, 0x8e, 0xf7, 0x8a, 0x5e, 0x42, 0xa1, 0x80, 0x5b, 0x53, 0x2c, 0x9e, 0x2f, 0x6e, 0x94, 0x88, 0x09, 0x22, 0x2f, 0x37, 0x98, 0x99, 0x11, 0x48, 0x8f, 0x49, 0x94, 0xf6, 0xd4, 0x14, 0x83, 0x5e, 0xa7, 0x51, 0x29, 0xe5, 0x32, 0xb1, 0x42, 0xa2, 0x40, 0x80, 0x05, 0xb7, 0x06, 0x4d, 0x60, 0x29, 0xc8, 0x2b, 0x06, 0x5e, 0xbb, 0x5e, 0x8b, 0x44, 0x2c, 0xbd, 0xda, 0x8e, 0x04, 0x05, 0xe0, 0x34, 0xe8, 0x90, 0xf8, 0x1d, 0xd2, 0x3b, 0x15, 0x63, 0xce, 0x06, 0x63, 0xf2, 0x16, 0x15, 0x81, 0xdd, 0x62, 0xcb, 0xb7, 0x6c, 0xbe, 0xd5, 0xca, 0xb6, 0x83, 0xa7, 0xcc, 0xb7, 0xee, 0xdc, 0x42, 0xb2, 0x3b, 0xe0, 0xb3, 0x9d, 0xb7, 0x7b, 0xbf, 0xfa, 0xc9, 0x96, 0x1f, 0xdd, 0x0a, 0xff, 0x03, 0x35, 0xcb, 0x1f, 0x5b, 0xdf, 0xa4, 0xbe, 0x85, 0x1a, 0x56, 0xda, 0xd4, 0xf2, 0x24, 0xd9, 0x08, 0xfb, 0xdf, 0x33, 0xe7, 0xbf, 0x4a, 0x52, 0x03, 0xad, 0xec, 0x47, 0xb2, 0x24, 0xb9, 0x3a, 0x55, 0xb5, 0xf8, 0xc1, 0xe7, 0xd9, 0x36, 0x4a, 0xb8, 0x78, 0x64, 0x09, 0x98, 0x1e, 0xe8, 0xb8, 0xb6, 0x95, 0x8f, 0x2f, 0x6a, 0x60, 0xaa, 0xb0, 0x5d, 0xa1, 0x82, 0xa8, 0x8b, 0x56, 0x87, 0xfc, 0xa4, 0x80, 0x2c, 0x96, 0xc0, 0x83, 0x08, 0xc5, 0x24, 0x1a, 0xa5, 0x36, 0xa6, 0x51, 0xaa, 0x74, 0x6a, 0x04, 0xe7, 0x76, 0x16, 0x02, 0x28, 0x58, 0xd2, 0x02, 0x9c, 0x00, 0x68, 0x69, 0xdc, 0xb8, 0x37, 0x44, 0x34, 0x4a, 0xa5, 0xd2, 0x0a, 0x69, 0x85, 0xbb, 0xd9, 0x69, 0x76, 0x65, 0x8a, 0x50, 0x72, 0x66, 0x2e, 0x2f, 0x97, 0x11, 0x60, 0x93, 0x09, 0xa7, 0xb1, 0x47, 0xf8, 0xcc, 0x7e, 0xbc, 0xca, 0xce, 0xa9, 0xe9, 0x50, 0x99, 0xf0, 0x52, 0xfc, 0x61, 0x53, 0xe3, 0xa3, 0x47, 0x81, 0xa5, 0xc7, 0xd6, 0x45, 0xb3, 0xfb, 0x76, 0xb6, 0x93, 0x4f, 0x2c, 0xfb, 0xe7, 0xcd, 0xe9, 0x3e, 0x15, 0xd2, 0xc0, 0x8d, 0xdb, 0x7e, 0x7a, 0x73, 0xd3, 0xdc, 0xe7, 0x00, 0x38, 0x2c, 0x47, 0x2a, 0xbb, 0x76, 0xdf, 0xbb, 0xd3, 0x91, 0x23, 0xaf, 0x29, 0x5c, 0xd3, 0x5b, 0xb2, 0x54, 0x91, 0xa2, 0x0a, 0xb6, 0x35, 0xd6, 0x38, 0x1f, 0xd2, 0x27, 0xd1, 0x5f, 0x8a, 0xda, 0xf7, 0x9f, 0xb9, 0x66, 0xc9, 0xd3, 0x37, 0xcc, 0x36, 0x91, 0xc2, 0xd1, 0xff, 0x6a, 0x6e, 0xa2, 0x1e, 0xd3, 0xd9, 0xfe, 0x96, 0xbc, 0xf5, 0x85, 0xbf, 0xec, 0xfe, 0x1e, 0x10, 0x9d, 0x1e, 0x16, 0xdc, 0xa8, 0xb3, 0x9d, 0x96, 0xce, 0xa9, 0x43, 0x8e, 0xbd, 0x72, 0x87, 0xcd, 0xcc, 0x9c, 0x7b, 0x02, 0x00, 0xa9, 0x2e, 0x45, 0x03, 0x32, 0x78, 0xbf, 0x5e, 0x8a, 0x18, 0x84, 0x3c, 0x67, 0x98, 0x97, 0x6f, 0xfc, 0x48, 0xd7, 0x12, 0x03, 0x52, 0x06, 0xea, 0xe5, 0x50, 0xae, 0x51, 0x2a, 0x24, 0x14, 0xd2, 0xf7, 0xe6, 0x21, 0x36, 0x84, 0xd8, 0x11, 0x03, 0xe8, 0xb9, 0x84, 0x4c, 0x36, 0x80, 0xa1, 0xfa, 0xf3, 0x19, 0xc8, 0x12, 0xe5, 0x7e, 0xf9, 0x98, 0x5a, 0x61, 0x53, 0x3b, 0xb5, 0x76, 0x35, 0x92, 0x35, 0x54, 0x58, 0xd7, 0x12, 0xda, 0x41, 0x4c, 0xa2, 0x00, 0x46, 0xb8, 0xdc, 0x5a, 0x4c, 0x4e, 0xec, 0xf8, 0x1e, 0x19, 0xd9, 0xe2, 0x50, 0xd2, 0x39, 0x6d, 0x48, 0x6b, 0xd7, 0x53, 0xdb, 0xd8, 0x77, 0x0a, 0xc1, 0x11, 0xf6, 0xcc, 0x2f, 0xa6, 0x89, 0xf4, 0x12, 0xa9, 0x4e, 0x04, 0x6e, 0x2c, 0xfd, 0xd3, 0x73, 0xf7, 0x3d, 0x9c, 0xec, 0x16, 0x6e, 0x1b, 0x3d, 0xbf, 0x4d, 0xe0, 0xb5, 0x3c, 0x7c, 0xdf, 0x93, 0xbf, 0x2d, 0x85, 0x42, 0xc4, 0x8c, 0xb3, 0xbf, 0x3a, 0x0b, 0x7a, 0xc8, 0xcf, 0x1e, 0x07, 0x05, 0x6f, 0x25, 0xd7, 0xf8, 0xfd, 0xb5, 0x56, 0xf6, 0xd8, 0xe3, 0x7b, 0xd9, 0x06, 0x8b, 0x11, 0xec, 0x60, 0xaf, 0xd5, 0xa5, 0x80, 0x93, 0x7b, 0x1f, 0xdf, 0x47, 0x3a, 0x46, 0x3f, 0xdc, 0x87, 0x78, 0x55, 0xef, 0x85, 0xcf, 0xe9, 0xb9, 0xcc, 0xe3, 0x44, 0x0b, 0xd1, 0x12, 0x6d, 0xd4, 0x40, 0xe5, 0xbd, 0xa5, 0xb9, 0xa1, 0xbe, 0xae, 0x16, 0xf5, 0x16, 0xf9, 0xe8, 0x01, 0x25, 0x0d, 0x0a, 0xa0, 0x1a, 0x4b, 0xd5, 0x13, 0xf8, 0xb6, 0x19, 0xe1, 0x72, 0x28, 0xf8, 0xff, 0xf2, 0x58, 0x48, 0x3e, 0xac, 0x4e, 0xe2, 0x5b, 0xc7, 0x21, 0xa6, 0xb1, 0xac, 0x24, 0x2f, 0x37, 0x3b, 0xcb, 0xed, 0xf4, 0x09, 0x50, 0x9a, 0x00, 0x66, 0x2c, 0x16, 0xeb, 0x84, 0xb8, 0x01, 0x46, 0x21, 0x27, 0x42, 0xb9, 0xc7, 0xd2, 0xb5, 0x8d, 0x85, 0x2a, 0xe4, 0x9c, 0x70, 0xbd, 0x11, 0x58, 0x3d, 0xe2, 0x7e, 0x8c, 0xf4, 0x65, 0x44, 0x52, 0x4b, 0x7a, 0x8b, 0xa7, 0xcd, 0xae, 0xca, 0xd2, 0xa0, 0x88, 0x02, 0x0d, 0x6f, 0xad, 0x1f, 0x7a, 0x6c, 0x55, 0x71, 0xc0, 0x7d, 0xd4, 0xfc, 0xa5, 0xb3, 0xd9, 0x07, 0x46, 0xcc, 0xac, 0x24, 0xad, 0x36, 0x37, 0xc5, 0x5d, 0x35, 0x50, 0x3a, 0x63, 0xe5, 0x8c, 0x88, 0x41, 0x69, 0x16, 0x95, 0xa4, 0xcd, 0x5f, 0xbd, 0xbd, 0x6a, 0xf5, 0x89, 0x4d, 0xe5, 0x46, 0x7f, 0xa1, 0xe3, 0x4d, 0x0b, 0x08, 0xf9, 0xdb, 0x7d, 0x6c, 0x94, 0x16, 0xec, 0x72, 0xf6, 0x94, 0xfa, 0xaa, 0xf3, 0xe2, 0x51, 0x07, 0x4a, 0x16, 0x5d, 0xdf, 0xd0, 0x3c, 0x12, 0x48, 0x1b, 0x2c, 0x34, 0x7a, 0x35, 0x3d, 0x83, 0xeb, 0xf4, 0x59, 0xc5, 0x8d, 0xc1, 0x9c, 0xda, 0x2c, 0x63, 0x20, 0x2f, 0xa0, 0x92, 0x0d, 0xa6, 0x57, 0x65, 0x5b, 0x50, 0x82, 0xa7, 0x50, 0x47, 0x4d, 0x61, 0x52, 0x70, 0x61, 0xbd, 0xce, 0xad, 0x5d, 0xc6, 0x9f, 0x11, 0x25, 0x3e, 0x23, 0x25, 0x08, 0x8f, 0x6a, 0x83, 0xf3, 0xe3, 0x03, 0x04, 0x95, 0x0d, 0xf5, 0x4d, 0x09, 0x94, 0x26, 0x54, 0x80, 0x04, 0x0c, 0x8a, 0x48, 0x41, 0x8f, 0x40, 0x35, 0x4c, 0x48, 0x20, 0xf3, 0x8b, 0x50, 0x00, 0xff, 0x5f, 0x8e, 0x12, 0x61, 0xae, 0x8a, 0x59, 0xbc, 0x96, 0x72, 0x2c, 0x0b, 0x12, 0xda, 0x21, 0xd0, 0xe8, 0x81, 0x1a, 0xa5, 0xd9, 0x95, 0xac, 0x12, 0x21, 0x63, 0x00, 0x7f, 0x58, 0xdc, 0x68, 0x13, 0x5c, 0xfc, 0xa8, 0x70, 0x16, 0xad, 0x31, 0x47, 0x64, 0x8a, 0x5c, 0xfc, 0xf8, 0xc6, 0xca, 0xf0, 0x9c, 0xeb, 0xa6, 0x93, 0x6b, 0xbb, 0x47, 0x9f, 0x11, 0xa3, 0x73, 0x52, 0x6b, 0xd8, 0xfa, 0x93, 0x5b, 0x9a, 0x07, 0x5f, 0x00, 0xe0, 0x1e, 0x39, 0xfa, 0xaa, 0x5d, 0xf9, 0x5c, 0x4d, 0xb5, 0x10, 0xaa, 0xb3, 0xfe, 0x92, 0xee, 0xd2, 0xc5, 0x4c, 0xe9, 0x40, 0x5b, 0xbd, 0x0b, 0xdb, 0xae, 0xe8, 0x7f, 0xd0, 0x2d, 0xfb, 0x7e, 0xb4, 0x79, 0xf9, 0x89, 0xdd, 0x33, 0xb5, 0xba, 0x45, 0x23, 0x47, 0x74, 0xb6, 0x4f, 0xf4, 0x9b, 0x5e, 0xfe, 0xd7, 0xcd, 0x87, 0x81, 0xfc, 0xf5, 0x65, 0x60, 0xb7, 0xce, 0xf6, 0x03, 0x55, 0x6f, 0xe5, 0x80, 0x54, 0x61, 0xf5, 0x78, 0x25, 0xe7, 0x5e, 0x52, 0xa6, 0x64, 0xd9, 0xb1, 0x9d, 0x8a, 0x26, 0x74, 0x17, 0xbe, 0x14, 0x64, 0xc3, 0xb3, 0x21, 0x86, 0xf4, 0xa2, 0x81, 0xe8, 0x21, 0xbe, 0x88, 0x2a, 0x6a, 0xa0, 0x88, 0x5e, 0x01, 0x28, 0xb1, 0x1b, 0xbb, 0x7d, 0x70, 0xd7, 0x2b, 0x45, 0x52, 0x00, 0x55, 0x70, 0xa1, 0x18, 0x65, 0x63, 0x84, 0x14, 0x85, 0x11, 0xa0, 0x58, 0xbf, 0x14, 0x29, 0xa4, 0x90, 0x5d, 0x42, 0xd4, 0x23, 0x07, 0x48, 0xe2, 0xc7, 0xc0, 0x12, 0x19, 0xc7, 0xe2, 0xd1, 0xd4, 0xd0, 0x88, 0x9b, 0xc7, 0x03, 0x0a, 0x17, 0x4c, 0x6a, 0x02, 0xd6, 0x22, 0x24, 0x42, 0x91, 0x64, 0x5e, 0xbc, 0xad, 0x8b, 0x34, 0x10, 0x2d, 0xb9, 0xba, 0xba, 0xbc, 0x3c, 0x81, 0xad, 0x24, 0x2b, 0x70, 0xa8, 0xbc, 0xa4, 0x69, 0xd3, 0x7a, 0xba, 0xbb, 0x3a, 0x5a, 0x67, 0x34, 0x37, 0x4e, 0x6b, 0x98, 0xd6, 0x90, 0xe5, 0xf4, 0x66, 0xb8, 0x34, 0x70, 0x67, 0x4b, 0xc6, 0xfc, 0xed, 0x20, 0xd9, 0xbe, 0xb4, 0x72, 0x87, 0xae, 0x31, 0x04, 0x31, 0xdd, 0x0e, 0x70, 0xa7, 0x5e, 0x09, 0x00, 0x3e, 0x1b, 0x76, 0xbc, 0xce, 0x20, 0xc4, 0x34, 0x49, 0xac, 0xe6, 0x7d, 0xe7, 0x7e, 0x27, 0x4d, 0xd1, 0xdf, 0x4c, 0xff, 0x58, 0x69, 0x90, 0xa5, 0xa6, 0xe5, 0x39, 0x4a, 0xca, 0x0a, 0x3a, 0xe7, 0x77, 0x16, 0x78, 0xeb, 0x87, 0xaa, 0xbc, 0xcd, 0x50, 0xbd, 0x0b, 0x0a, 0x74, 0x32, 0xa5, 0x33, 0xab, 0xd4, 0x3f, 0xad, 0xcc, 0x1e, 0xae, 0xaa, 0xad, 0x0a, 0xdb, 0x5d, 0x35, 0x83, 0x15, 0xf5, 0x9b, 0x03, 0xe7, 0x7e, 0xd4, 0x22, 0x54, 0x8a, 0x84, 0x4a, 0x61, 0x0b, 0xf8, 0xe5, 0x6a, 0x9d, 0x49, 0x90, 0xcd, 0xfe, 0x77, 0x36, 0x6d, 0xd6, 0xac, 0x05, 0x29, 0x77, 0x33, 0x9b, 0xe4, 0xda, 0x6f, 0x77, 0x28, 0xd4, 0x64, 0x9d, 0x41, 0xa9, 0x51, 0x66, 0xb8, 0x6d, 0x2e, 0xb3, 0x39, 0xb3, 0xa1, 0x24, 0xa7, 0xbb, 0xdc, 0xa3, 0x48, 0x72, 0xe9, 0xeb, 0x45, 0x32, 0x5d, 0x92, 0x2e, 0x94, 0x6e, 0x71, 0x18, 0x55, 0x3a, 0x6b, 0x56, 0x55, 0x28, 0xad, 0xa9, 0xd8, 0xed, 0x77, 0x81, 0xca, 0x5f, 0x6b, 0x82, 0x56, 0x6b, 0x50, 0xf3, 0x6b, 0xf6, 0xff, 0x74, 0x8a, 0xdd, 0xbb, 0xe5, 0x7a, 0xf2, 0x37, 0xdc, 0xfd, 0xde, 0xe3, 0x70, 0xf1, 0x37, 0x51, 0xff, 0x05, 0x37, 0x78, 0x11, 0x67, 0x9c, 0x57, 0xc3, 0x59, 0x1b, 0x6c, 0x88, 0x65, 0x0e, 0x47, 0x32, 0x2d, 0xca, 0xfc, 0xca, 0x27, 0x15, 0x8f, 0x3f, 0xeb, 0x89, 0x4a, 0x13, 0x33, 0x8a, 0xbb, 0x71, 0x46, 0x71, 0x7a, 0xd3, 0xf9, 0xe0, 0xd6, 0x6d, 0xdb, 0xa8, 0xff, 0x3a, 0xff, 0x34, 0x6e, 0x7b, 0x19, 0x6c, 0xbb, 0x84, 0x6e, 0x47, 0x81, 0x5f, 0xa3, 0x86, 0x58, 0x26, 0x6a, 0x8a, 0x5b, 0x4b, 0x28, 0x16, 0xbb, 0x50, 0x1a, 0x71, 0x6d, 0x4c, 0xc6, 0xe0, 0xe2, 0x94, 0x60, 0x93, 0xa0, 0xda, 0xb9, 0x0c, 0x81, 0xd9, 0x10, 0xa8, 0x6d, 0x6b, 0x4e, 0xff, 0xae, 0xb6, 0xb6, 0x5d, 0xfd, 0x39, 0x5b, 0xc9, 0xcd, 0xb3, 0x8f, 0x6e, 0x6b, 0x6c, 0xdc, 0x76, 0x74, 0xf6, 0xe8, 0x76, 0x72, 0x73, 0xd3, 0xf6, 0x59, 0x79, 0x79, 0xb3, 0xb6, 0x37, 0x8d, 0x6e, 0x87, 0xd4, 0xfc, 0x36, 0xb8, 0x3f, 0xbf, 0xa2, 0x2b, 0xe0, 0x27, 0x21, 0x21, 0x25, 0x02, 0xd1, 0x34, 0xde, 0xe0, 0x89, 0x7c, 0xcb, 0x20, 0xaf, 0x07, 0x73, 0x09, 0x9a, 0x61, 0xe8, 0x0e, 0x3e, 0x08, 0x1c, 0xcd, 0x34, 0x71, 0x0e, 0x66, 0x02, 0xc8, 0xd0, 0xb4, 0xf6, 0xb1, 0x6b, 0x80, 0xdb, 0xa8, 0x79, 0xe7, 0x0f, 0xc5, 0xfe, 0x90, 0x7f, 0x3d, 0x00, 0xfe, 0x74, 0x60, 0xf4, 0x2f, 0x07, 0x60, 0x5b, 0xd7, 0xc0, 0x91, 0x6e, 0x61, 0x0e, 0xc1, 0x53, 0x22, 0x41, 0x19, 0x55, 0xc4, 0x22, 0xa8, 0x44, 0x11, 0x02, 0xa2, 0x16, 0x35, 0x8f, 0xd0, 0x09, 0x70, 0x38, 0x1a, 0x2d, 0x09, 0xa7, 0x41, 0x4c, 0xaa, 0xed, 0x5a, 0x35, 0xd4, 0x8c, 0x22, 0xf4, 0x67, 0xac, 0x82, 0x55, 0x50, 0x9f, 0xdc, 0xfa, 0xed, 0xd7, 0xe4, 0x0e, 0x40, 0xad, 0x07, 0x4b, 0xd9, 0x3b, 0x46, 0x93, 0xa8, 0xbc, 0xf3, 0xf9, 0xd4, 0x8f, 0xc0, 0x7b, 0x80, 0x9b, 0xfb, 0xbb, 0x40, 0x1f, 0x75, 0x8e, 0xfc, 0x3f, 0x38, 0x8d, 0xa5, 0x9c, 0x61, 0xdf, 0x80, 0xe4, 0x33, 0xb0, 0x10, 0x43, 0xb0, 0x3a, 0xd0, 0x46, 0xee, 0x41, 0xac, 0xbc, 0xc9, 0x12, 0xd5, 0x63, 0x36, 0x3d, 0xf1, 0x79, 0xcf, 0x49, 0x38, 0x0a, 0x94, 0xc9, 0x05, 0xd8, 0xf5, 0x08, 0x95, 0x77, 0x6e, 0x34, 0x65, 0x2b, 0x35, 0x8f, 0xec, 0xf8, 0x06, 0xa0, 0x84, 0xee, 0x00, 0x44, 0xd8, 0x28, 0x55, 0x2a, 0xe8, 0x24, 0xf4, 0x44, 0xc9, 0x49, 0x64, 0xfa, 0x45, 0x77, 0xb7, 0x99, 0xd8, 0x2d, 0x8e, 0xb3, 0x6d, 0xa2, 0xb3, 0xb2, 0x04, 0xc9, 0x85, 0x7a, 0x9e, 0xa0, 0xc1, 0x32, 0x5d, 0xbc, 0xd4, 0x48, 0x80, 0x96, 0x9e, 0x93, 0x7a, 0xaf, 0x0b, 0x37, 0x1f, 0xbf, 0x18, 0xa1, 0xf8, 0xf5, 0x01, 0x91, 0xe2, 0xc1, 0x1b, 0x9a, 0x9c, 0x4d, 0x36, 0x19, 0x25, 0xd6, 0xda, 0x92, 0x0a, 0xbb, 0x8a, 0xac, 0x2f, 0x51, 0x7f, 0x3d, 0xf5, 0xf9, 0xee, 0x5c, 0xb9, 0xba, 0x4d, 0xa6, 0x14, 0x52, 0xbe, 0x85, 0xcf, 0xde, 0xf8, 0x6d, 0x23, 0x01, 0x2e, 0x7c, 0xca, 0x2e, 0x06, 0x5f, 0x30, 0xef, 0x11, 0x46, 0xa2, 0xf0, 0xa4, 0x84, 0xc7, 0x34, 0x07, 0xb0, 0x74, 0x4a, 0x92, 0x0b, 0x70, 0x9a, 0x7b, 0x24, 0x6a, 0x2c, 0x41, 0xbb, 0x4c, 0x85, 0xf3, 0x0b, 0x27, 0x3c, 0x42, 0x1d, 0xf0, 0xa0, 0x0e, 0xb8, 0x75, 0x5c, 0x50, 0xc1, 0x48, 0x29, 0x17, 0x95, 0x4f, 0x1d, 0x02, 0x5f, 0x58, 0x72, 0x2a, 0xdb, 0x67, 0x87, 0x9c, 0x8d, 0x36, 0x29, 0xdf, 0x85, 0xc2, 0x94, 0x97, 0xde, 0xbd, 0xf6, 0x95, 0xdd, 0x33, 0xc3, 0x86, 0x58, 0x1f, 0x16, 0x3d, 0x7b, 0x23, 0x23, 0x86, 0x7d, 0xf8, 0x09, 0x69, 0x12, 0x28, 0xc8, 0x83, 0x70, 0x4f, 0x98, 0xf0, 0x4c, 0x12, 0xa0, 0x03, 0xbd, 0xa5, 0x07, 0xeb, 0x78, 0x6a, 0x15, 0x5a, 0x39, 0x14, 0x8b, 0x4b, 0xa0, 0xf8, 0xe6, 0xb4, 0x20, 0x4a, 0x9a, 0xb6, 0xa2, 0xd9, 0x79, 0x92, 0x95, 0x51, 0x99, 0x82, 0x87, 0x60, 0x1d, 0x63, 0x54, 0x87, 0x40, 0xd5, 0x60, 0x04, 0x76, 0x6a, 0x31, 0x96, 0x23, 0x28, 0x35, 0x16, 0x99, 0x91, 0xf3, 0x74, 0xe6, 0x56, 0xc1, 0x43, 0xff, 0x5a, 0x24, 0x3a, 0x48, 0xf0, 0x75, 0xf0, 0x7b, 0x18, 0x22, 0x25, 0x6a, 0x99, 0x72, 0xc5, 0xe2, 0xeb, 0x85, 0x50, 0x94, 0x02, 0xc5, 0xa8, 0x6d, 0x2b, 0x35, 0x87, 0x34, 0x9d, 0xfb, 0x07, 0xbd, 0x17, 0xc5, 0x81, 0xf8, 0x09, 0x7a, 0x67, 0xbc, 0x3e, 0x6c, 0x00, 0xd6, 0x87, 0x02, 0x36, 0xaa, 0x8f, 0xf6, 0x19, 0x09, 0xe2, 0xf5, 0xb1, 0x03, 0x19, 0x7c, 0xf9, 0xa8, 0x8d, 0x9a, 0xc3, 0xca, 0x68, 0xd9, 0x56, 0xee, 0xfd, 0x68, 0x9c, 0xb0, 0xcf, 0x0c, 0xc6, 0x74, 0xf2, 0x47, 0x16, 0x75, 0x20, 0x76, 0x72, 0x49, 0xa2, 0x39, 0xf1, 0xd4, 0x02, 0x7c, 0x6a, 0x05, 0x8a, 0xf3, 0xf7, 0x6f, 0x25, 0x0f, 0x6e, 0xa5, 0xf7, 0x9e, 0xfb, 0x07, 0xde, 0x97, 0x68, 0xbe, 0xf0, 0xd8, 0x19, 0xc2, 0x15, 0xb5, 0xc3, 0xba, 0x78, 0x55, 0x62, 0xb3, 0x36, 0x13, 0x7b, 0x22, 0x8d, 0x6b, 0x06, 0xfb, 0xa7, 0x52, 0x99, 0xe7, 0xef, 0x27, 0x7f, 0x2b, 0x78, 0xe8, 0xdc, 0xea, 0xad, 0xb4, 0x8c, 0xdb, 0xdf, 0x0f, 0xb2, 0xcb, 0x84, 0x67, 0x21, 0x4f, 0x69, 0x23, 0xee, 0x7e, 0xbe, 0x35, 0xe8, 0xa1, 0x04, 0x4c, 0x0c, 0xd1, 0xee, 0x92, 0x40, 0x32, 0xce, 0x30, 0xe9, 0x0d, 0x22, 0x80, 0xa2, 0x52, 0x20, 0x80, 0x61, 0x6e, 0x1c, 0x8f, 0x18, 0x44, 0xdb, 0x22, 0x27, 0x9e, 0x13, 0xc0, 0x81, 0x26, 0x11, 0x8e, 0x63, 0x60, 0xca, 0x3a, 0x5c, 0x49, 0x34, 0x5c, 0x7e, 0xba, 0x2f, 0x52, 0x12, 0x5f, 0x54, 0x68, 0x60, 0xeb, 0x6d, 0x44, 0x9b, 0x1b, 0x12, 0x1f, 0x5f, 0xda, 0x7a, 0xdd, 0x1a, 0x29, 0x4a, 0xf1, 0xa9, 0xe6, 0x09, 0x3c, 0x17, 0x6a, 0x38, 0xe1, 0x8e, 0x9f, 0xcb, 0x3e, 0xcb, 0xbb, 0xcd, 0xf0, 0x22, 0x4d, 0x02, 0xe2, 0x22, 0x1e, 0x4e, 0x27, 0xa2, 0x16, 0x9e, 0xcd, 0xdf, 0xf7, 0xf9, 0xf3, 0xbe, 0x6b, 0xf7, 0xee, 0x09, 0x37, 0xcf, 0xce, 0xd5, 0x80, 0x7c, 0x95, 0x46, 0x4c, 0x99, 0x9b, 0x6f, 0x5a, 0x93, 0xb9, 0xfd, 0xfa, 0x0d, 0xde, 0xf5, 0x47, 0x06, 0x33, 0x6c, 0x2d, 0xb3, 0x57, 0x56, 0x54, 0xaf, 0xea, 0x9d, 0x66, 0xb0, 0xcd, 0x5a, 0xb1, 0xa9, 0x64, 0xd5, 0xe3, 0xab, 0x0a, 0xde, 0x4f, 0x6b, 0x5c, 0x5c, 0x5e, 0x3e, 0xdc, 0x9a, 0xf9, 0xbe, 0xbd, 0x62, 0x41, 0xd5, 0xa2, 0x47, 0x43, 0xcc, 0xbd, 0xdf, 0x9e, 0x59, 0x70, 0xcf, 0xe2, 0x08, 0x94, 0xd2, 0xc4, 0x7a, 0xa7, 0x45, 0xaa, 0x31, 0xca, 0xb2, 0x6a, 0xb3, 0x93, 0xe0, 0x57, 0x7d, 0xa8, 0xa3, 0xec, 0x99, 0xa2, 0xd9, 0xd3, 0x5c, 0xee, 0xca, 0xb9, 0x25, 0x55, 0xf3, 0xa3, 0x29, 0x35, 0x37, 0xbe, 0xb9, 0x8d, 0xba, 0xf9, 0xfc, 0x70, 0xdf, 0xde, 0x59, 0xc1, 0xac, 0x59, 0x7b, 0xfb, 0xd1, 0xe7, 0xce, 0x3d, 0x73, 0x73, 0x0f, 0xdc, 0x4a, 0xfd, 0x96, 0x9b, 0xfb, 0x42, 0x48, 0x7b, 0x47, 0x98, 0x87, 0x09, 0x27, 0x50, 0x45, 0x95, 0x5a, 0x0d, 0x54, 0x74, 0x90, 0x1b, 0x27, 0x24, 0xbe, 0x74, 0x0c, 0x62, 0x68, 0xe6, 0x7d, 0xb4, 0x90, 0x41, 0x99, 0xec, 0x15, 0x30, 0x24, 0x4e, 0x8f, 0x8e, 0x33, 0xbf, 0x5b, 0x38, 0x72, 0x34, 0x45, 0x89, 0xd0, 0x58, 0x09, 0xd9, 0x65, 0xdb, 0x50, 0x5f, 0xb6, 0x0d, 0xed, 0x65, 0xdb, 0xd0, 0x5f, 0xa6, 0x8d, 0x4b, 0x55, 0xef, 0x19, 0x03, 0x73, 0x38, 0x09, 0xa7, 0xd1, 0xeb, 0xd3, 0xaa, 0x85, 0xc2, 0xa4, 0xb1, 0x58, 0xe8, 0x3c, 0x59, 0xe3, 0x38, 0x3c, 0xbe, 0x8a, 0xc2, 0xa1, 0x61, 0x47, 0xde, 0xdb, 0xf0, 0xdb, 0xd3, 0xf7, 0xad, 0x28, 0x49, 0x9b, 0xb9, 0x6f, 0xc0, 0x92, 0xae, 0x45, 0xeb, 0x10, 0xae, 0xf1, 0xa9, 0x1a, 0xbe, 0xf7, 0xaf, 0x63, 0x5c, 0x30, 0x72, 0x9b, 0x5d, 0x14, 0x5e, 0x7e, 0x74, 0xfd, 0xdc, 0xc3, 0xab, 0xa2, 0x34, 0x3d, 0x4b, 0xad, 0x97, 0xd2, 0x59, 0xcd, 0x83, 0xe1, 0xe5, 0x37, 0x16, 0xab, 0x33, 0xa0, 0x06, 0x8a, 0xe2, 0x5f, 0x2e, 0xc3, 0xf1, 0xc8, 0x83, 0xc4, 0xc1, 0xe7, 0x02, 0x04, 0x29, 0x88, 0xfb, 0xf3, 0xb8, 0x08, 0x28, 0x3d, 0x0a, 0x09, 0x24, 0x34, 0x09, 0xd3, 0x91, 0x64, 0x12, 0xc0, 0x48, 0x67, 0xce, 0xb7, 0x31, 0x88, 0x24, 0xf0, 0x1c, 0x2a, 0xe6, 0xcb, 0xe3, 0x88, 0x97, 0xa5, 0x00, 0xb5, 0x8a, 0x41, 0x5e, 0x75, 0x08, 0x49, 0x3d, 0x3f, 0x5e, 0x95, 0x8b, 0x11, 0x91, 0x58, 0x28, 0x16, 0x82, 0x1d, 0x39, 0x98, 0x53, 0xcc, 0xb8, 0xa2, 0x38, 0xd5, 0x91, 0xc5, 0x9e, 0xa6, 0x81, 0xff, 0xa3, 0x9c, 0x7b, 0x7c, 0x08, 0x74, 0x0e, 0x21, 0x80, 0x2e, 0xeb, 0x12, 0x2e, 0xc0, 0xb9, 0xdd, 0x3d, 0x29, 0x36, 0x7a, 0xde, 0xca, 0xa7, 0x37, 0x46, 0x07, 0x73, 0x1b, 0x4a, 0xc8, 0x08, 0xda, 0xde, 0x86, 0xbc, 0x9e, 0xca, 0xbc, 0xc1, 0xe2, 0xc6, 0xc1, 0xe2, 0xa4, 0xc8, 0xf5, 0x9f, 0x3c, 0x99, 0x18, 0x34, 0x7d, 0x55, 0xdf, 0x2d, 0xf3, 0xf3, 0x24, 0xfe, 0xb0, 0x54, 0x6d, 0x90, 0xa5, 0xd7, 0x86, 0xac, 0x12, 0x63, 0xb8, 0x3b, 0x0a, 0xf6, 0xf7, 0x5e, 0x93, 0xa7, 0xce, 0xe0, 0x02, 0xa9, 0x03, 0x62, 0x0e, 0xd4, 0x57, 0xe4, 0x50, 0x5f, 0xa9, 0x22, 0x32, 0xa3, 0xe9, 0x0e, 0x9c, 0x4f, 0x7c, 0xcc, 0x77, 0x90, 0xd3, 0x3f, 0x43, 0xf1, 0x20, 0x45, 0xf0, 0xf4, 0x66, 0xf9, 0xb4, 0x59, 0x5e, 0x06, 0xae, 0x9e, 0x36, 0xc1, 0x39, 0x58, 0xa7, 0xc0, 0xd1, 0xbe, 0x27, 0xa9, 0x1d, 0x02, 0xec, 0x6d, 0xcc, 0x5d, 0x2e, 0x22, 0x3d, 0xe5, 0xe3, 0x8c, 0x60, 0x70, 0xfe, 0xdd, 0x4b, 0x07, 0xee, 0x5a, 0x94, 0x17, 0xe9, 0x5d, 0xbd, 0x6e, 0x75, 0x6f, 0x64, 0xff, 0xb5, 0x50, 0xc7, 0x08, 0x23, 0x1d, 0xa3, 0x34, 0x6d, 0xfe, 0x9a, 0x6d, 0x58, 0xc7, 0xc8, 0xef, 0x5d, 0xb3, 0x71, 0x4d, 0x6f, 0xfe, 0xf2, 0x05, 0xd9, 0x8d, 0x21, 0x8b, 0x42, 0xa5, 0x50, 0xaa, 0xa9, 0xe5, 0xd9, 0xc3, 0xb5, 0xd1, 0x0d, 0x73, 0x8a, 0x72, 0xfb, 0xb7, 0x35, 0xf6, 0xef, 0x9c, 0x5b, 0x59, 0x10, 0xae, 0xe8, 0x2e, 0x5d, 0xb6, 0x6a, 0x4c, 0xab, 0x28, 0x1d, 0xbe, 0x77, 0x56, 0xfb, 0xfa, 0xee, 0x68, 0x38, 0x5c, 0xd5, 0x5b, 0xde, 0x3a, 0x27, 0x39, 0xb3, 0xc8, 0x66, 0xf6, 0x5a, 0xf5, 0x62, 0xb5, 0x41, 0xc2, 0xe5, 0x22, 0xfe, 0x04, 0x12, 0xca, 0x25, 0x70, 0x9c, 0x24, 0x61, 0x47, 0xb7, 0x8a, 0x76, 0x2d, 0x5c, 0x49, 0x14, 0xd4, 0x87, 0x41, 0x2e, 0xa3, 0x98, 0x34, 0x85, 0x1a, 0x50, 0x30, 0x08, 0x2c, 0x48, 0x04, 0x90, 0x43, 0x4a, 0x4a, 0xb2, 0xd9, 0xa4, 0x90, 0x49, 0x44, 0x50, 0x3f, 0x23, 0x09, 0x52, 0x88, 0xf2, 0xd9, 0x8a, 0x01, 0x63, 0x47, 0x82, 0x26, 0x0a, 0x45, 0x8b, 0x06, 0x9e, 0x0a, 0x30, 0x27, 0xb4, 0x72, 0x37, 0xe9, 0x74, 0x2e, 0xbb, 0x9a, 0x5d, 0x45, 0xb7, 0x7c, 0xbb, 0x81, 0xac, 0x35, 0x94, 0xdc, 0x39, 0xbd, 0x6a, 0x75, 0x68, 0xc1, 0xec, 0x2e, 0x97, 0x4e, 0xe7, 0xea, 0x9a, 0xbd, 0x20, 0xb4, 0xba, 0x6a, 0xfa, 0x1d, 0x25, 0x86, 0x63, 0xe4, 0x4f, 0x46, 0x73, 0x8e, 0xd1, 0x4a, 0xef, 0xd2, 0xde, 0xeb, 0xd3, 0x4c, 0xe1, 0xee, 0x32, 0x97, 0xab, 0xac, 0x3b, 0x6c, 0x4a, 0xbb, 0xbe, 0x77, 0xa9, 0x0f, 0xdb, 0x0b, 0x16, 0x5c, 0xf8, 0x9c, 0x69, 0x64, 0xbe, 0xc2, 0x36, 0xa5, 0x75, 0x27, 0x7d, 0x3c, 0xa7, 0x46, 0x02, 0xa1, 0x07, 0x52, 0x51, 0x46, 0x80, 0xb2, 0x90, 0x62, 0xf5, 0x30, 0x3d, 0xbe, 0x2a, 0x61, 0xa8, 0x1b, 0xf3, 0x40, 0xdf, 0x5c, 0x84, 0xd0, 0x70, 0xf1, 0x05, 0xb9, 0x1c, 0xb6, 0x03, 0x63, 0x15, 0x12, 0x8b, 0x41, 0x11, 0x5c, 0xa7, 0x03, 0x84, 0xae, 0x41, 0xd7, 0x50, 0x13, 0x0d, 0xe7, 0x06, 0xfc, 0x2e, 0xab, 0x54, 0x4c, 0x68, 0x81, 0x56, 0x2c, 0x4c, 0xc8, 0x85, 0xc6, 0xaf, 0x5d, 0xa2, 0xdf, 0x03, 0x26, 0xcf, 0x89, 0xee, 0xf1, 0x78, 0xa5, 0xe9, 0x18, 0xa1, 0x8e, 0x90, 0x1f, 0xf6, 0xdf, 0xbe, 0xb8, 0xa0, 0x60, 0xf1, 0xed, 0xfd, 0x08, 0x5a, 0x05, 0xff, 0x25, 0x07, 0xd0, 0x36, 0x35, 0x45, 0x57, 0xf6, 0x38, 0x3b, 0x3b, 0xa7, 0x27, 0xcf, 0xda, 0x3b, 0xb7, 0x40, 0x56, 0xf8, 0xeb, 0x63, 0x7b, 0x3e, 0x39, 0xdc, 0x3d, 0xf3, 0xc1, 0x3f, 0xec, 0xb9, 0xfd, 0xd3, 0x07, 0x3b, 0x35, 0x59, 0x6d, 0xdb, 0x4e, 0x6e, 0x48, 0x6d, 0xeb, 0xe9, 0xf1, 0x44, 0x6a, 0xd3, 0x54, 0xe0, 0xb1, 0xec, 0xd9, 0xbb, 0xbb, 0xba, 0x6e, 0x9c, 0x9b, 0x9b, 0xd1, 0xb1, 0x79, 0xc6, 0x9c, 0x3b, 0x17, 0x85, 0x99, 0x46, 0x99, 0xc6, 0x24, 0xcb, 0x69, 0xca, 0xb3, 0x40, 0x2a, 0xec, 0x9f, 0x75, 0x70, 0xf9, 0xfc, 0xa1, 0x96, 0x9b, 0xce, 0xae, 0xb9, 0xe6, 0xed, 0x03, 0x6d, 0xb3, 0x9e, 0xb9, 0xf0, 0xc0, 0xd2, 0x1f, 0x1c, 0xdb, 0x33, 0x3f, 0x8c, 0xe9, 0xb5, 0xdb, 0xc2, 0xd1, 0x5d, 0x2f, 0x1c, 0x6a, 0x2d, 0x8e, 0x59, 0xed, 0x8f, 0x7a, 0xf9, 0xa9, 0x18, 0x16, 0xa0, 0x7d, 0x4d, 0x50, 0x5c, 0x68, 0x47, 0x3c, 0x81, 0x01, 0x14, 0xe6, 0x20, 0x4d, 0x67, 0x57, 0xab, 0x50, 0x66, 0x65, 0xee, 0xbe, 0x31, 0x91, 0xff, 0xe0, 0x60, 0x26, 0x76, 0xba, 0x96, 0xed, 0x7c, 0xef, 0x7b, 0xfe, 0x7c, 0x87, 0x12, 0x94, 0xa8, 0x75, 0x12, 0xca, 0x58, 0x38, 0xa7, 0xfe, 0x91, 0xf7, 0xc0, 0x51, 0xea, 0xaf, 0xe7, 0x35, 0xd4, 0x5f, 0xc5, 0x46, 0x4f, 0xb2, 0x4c, 0x67, 0x94, 0x85, 0x9a, 0x72, 0x92, 0x00, 0xc9, 0xb2, 0xf8, 0xfd, 0x5b, 0xd8, 0xe5, 0xcc, 0x67, 0xd4, 0xd7, 0x44, 0x13, 0xf1, 0xe4, 0xf3, 0x8d, 0x99, 0x31, 0x9e, 0xeb, 0x1b, 0xe3, 0xb9, 0x99, 0x09, 0x5c, 0x91, 0xcb, 0x94, 0xd8, 0x37, 0x9e, 0xe7, 0xfa, 0x71, 0x12, 0x50, 0x8e, 0x93, 0xce, 0x1f, 0x5f, 0x07, 0x27, 0x57, 0x44, 0x1f, 0xb9, 0xd5, 0x8c, 0x73, 0xdf, 0x40, 0x22, 0xf7, 0xbd, 0x74, 0x1d, 0x3a, 0xc6, 0x87, 0x9b, 0x88, 0x26, 0xa7, 0xde, 0xa9, 0xf7, 0x4c, 0xe6, 0xc3, 0x9e, 0x84, 0x79, 0x30, 0xa2, 0xe5, 0x35, 0xc4, 0xf8, 0xb0, 0x27, 0x91, 0x0d, 0x1b, 0xe2, 0x5c, 0x18, 0x07, 0x08, 0x50, 0x33, 0x9f, 0x05, 0x46, 0x7e, 0x78, 0x4f, 0x4a, 0x65, 0x55, 0xa5, 0xb5, 0xb1, 0x3f, 0xa4, 0x05, 0x98, 0x4a, 0x59, 0x5b, 0xf6, 0xac, 0xb0, 0x94, 0x44, 0xcb, 0x2c, 0xfd, 0x5b, 0x5a, 0x1c, 0xc9, 0x25, 0xd5, 0x2d, 0x19, 0x0b, 0x36, 0x5b, 0x8d, 0xd5, 0x9d, 0xf3, 0xb2, 0x67, 0x6c, 0xe9, 0xca, 0x3c, 0x33, 0x7f, 0x96, 0xbf, 0xb1, 0xc0, 0x71, 0x66, 0x76, 0x6f, 0xe9, 0x50, 0x06, 0xf5, 0xf5, 0xf9, 0xc1, 0xa1, 0x7b, 0x16, 0x04, 0x21, 0x03, 0x01, 0x22, 0xc4, 0x7f, 0x21, 0x61, 0x4f, 0xaf, 0x0b, 0x25, 0x03, 0xf4, 0x40, 0x95, 0xd5, 0x59, 0xdd, 0x6a, 0x8f, 0xf8, 0x0c, 0x83, 0xbd, 0xe1, 0xe6, 0x6c, 0x53, 0x68, 0xe0, 0xa6, 0x3e, 0x14, 0xad, 0x7c, 0xe5, 0x1a, 0x6b, 0x61, 0x67, 0x01, 0xfa, 0xb4, 0x66, 0x65, 0x45, 0x01, 0xf9, 0x25, 0xb7, 0x07, 0xa6, 0xb3, 0x0f, 0x53, 0x6f, 0xd0, 0x02, 0xc2, 0x8d, 0x72, 0x76, 0x23, 0x95, 0x57, 0xa9, 0x51, 0x43, 0x06, 0xec, 0x8a, 0x33, 0x60, 0x4b, 0x54, 0xa3, 0x06, 0x0c, 0xa9, 0x81, 0x54, 0x3a, 0xf6, 0x10, 0x43, 0x3d, 0x1c, 0x53, 0x33, 0xbb, 0x70, 0x3c, 0x41, 0x74, 0x4a, 0xec, 0x47, 0xe4, 0x9e, 0x91, 0xd9, 0x40, 0xf0, 0xa8, 0xb0, 0x58, 0x01, 0x8e, 0xd7, 0xb9, 0x09, 0xb7, 0xcf, 0xeb, 0x71, 0x26, 0xf0, 0xba, 0x98, 0xeb, 0x14, 0x22, 0x92, 0x63, 0xcc, 0x8e, 0x93, 0x5d, 0xa8, 0x37, 0xce, 0xf6, 0xdd, 0x13, 0xed, 0x84, 0xfb, 0xf8, 0x7b, 0x47, 0x2d, 0x7e, 0x2d, 0x90, 0x6a, 0x8d, 0x32, 0x7f, 0xa9, 0x4f, 0xeb, 0x8c, 0x76, 0xe7, 0x85, 0xb7, 0x36, 0x53, 0x5f, 0x7f, 0xfb, 0x04, 0xe9, 0x37, 0xea, 0xb2, 0x96, 0x1c, 0xdd, 0xf4, 0xce, 0xfb, 0x0c, 0xd5, 0x84, 0xd8, 0x5d, 0xa0, 0x61, 0x41, 0x41, 0xdb, 0xaa, 0x5a, 0xbb, 0x4a, 0x4d, 0x7e, 0x0f, 0xae, 0xba, 0xfa, 0xc2, 0x17, 0xd4, 0x3f, 0x69, 0x09, 0xa1, 0x84, 0x8b, 0xfa, 0x07, 0xee, 0x82, 0x50, 0x59, 0x05, 0x80, 0x48, 0x4b, 0x41, 0x25, 0x23, 0x0f, 0x40, 0xc5, 0x1c, 0x45, 0x4d, 0x82, 0x4f, 0xc4, 0x09, 0x4f, 0x78, 0x64, 0x4b, 0x9a, 0x04, 0xb9, 0x9c, 0x32, 0x84, 0x10, 0x71, 0x2d, 0x8a, 0x64, 0x28, 0x74, 0x7d, 0x2b, 0xea, 0x95, 0x8a, 0x49, 0x91, 0x28, 0xcc, 0x45, 0x3e, 0x45, 0xde, 0xf5, 0xb9, 0x64, 0xec, 0x46, 0x22, 0x63, 0x52, 0x05, 0x14, 0x8d, 0x2b, 0xb3, 0x01, 0x57, 0x23, 0x26, 0x55, 0x8a, 0x66, 0x8f, 0x2b, 0x4f, 0x4f, 0x55, 0x9e, 0xa6, 0x03, 0x5c, 0x25, 0x02, 0xd7, 0x81, 0x1b, 0x34, 0x55, 0xa5, 0x02, 0x84, 0xaa, 0x49, 0xd5, 0x38, 0xad, 0xbc, 0x20, 0x92, 0x95, 0xe1, 0xf7, 0x79, 0xdc, 0x4e, 0x7b, 0xb2, 0x19, 0x36, 0xa3, 0x04, 0x4a, 0x99, 0x90, 0xbb, 0x48, 0x1f, 0x8b, 0x59, 0x91, 0xe8, 0xec, 0x9a, 0x49, 0x63, 0x76, 0x9a, 0x78, 0x13, 0x3c, 0xc1, 0x8f, 0xa1, 0x14, 0x53, 0x6f, 0xf0, 0xcf, 0xe6, 0xa5, 0x7e, 0xff, 0xaa, 0x86, 0x74, 0x38, 0xd7, 0xda, 0xb4, 0x28, 0x19, 0x50, 0x07, 0x6d, 0x59, 0xa1, 0xac, 0xd9, 0x37, 0xcd, 0xc9, 0x69, 0x2c, 0xcc, 0x4c, 0xf7, 0x9b, 0xfd, 0x95, 0xd2, 0xbb, 0x75, 0xbb, 0xd6, 0xe6, 0xcf, 0xa8, 0x6b, 0x09, 0x87, 0xea, 0x33, 0x0d, 0xd6, 0x92, 0x43, 0x0b, 0x67, 0x6c, 0x68, 0x4d, 0x4b, 0x6b, 0xbb, 0xb6, 0x7d, 0xf6, 0xc3, 0xd3, 0x18, 0x21, 0x23, 0x10, 0x2b, 0x4c, 0x7a, 0xf2, 0x28, 0x5c, 0xd7, 0xb0, 0x36, 0xc5, 0xa3, 0xd1, 0xb8, 0x53, 0xb4, 0xa0, 0x39, 0x34, 0x94, 0x57, 0x7b, 0x7c, 0xe1, 0xbc, 0xa3, 0x5b, 0xea, 0x74, 0x56, 0xa7, 0x55, 0x17, 0x29, 0xdb, 0x71, 0x9b, 0x3f, 0xc7, 0x9f, 0xd9, 0xbe, 0xba, 0x66, 0x79, 0x4f, 0x5e, 0x79, 0x5a, 0x65, 0x4f, 0x56, 0x56, 0x4f, 0x95, 0xbf, 0x20, 0x2c, 0xd6, 0x4a, 0x14, 0x7a, 0xb9, 0x10, 0xed, 0xd7, 0xca, 0x0b, 0x9f, 0x53, 0x73, 0x68, 0x11, 0x51, 0x81, 0x78, 0xb1, 0x1d, 0xc0, 0xed, 0x38, 0x89, 0x17, 0x87, 0xc7, 0xf1, 0x62, 0x8f, 0xf3, 0xd2, 0xbc, 0x78, 0x4a, 0x56, 0x6c, 0x40, 0x9c, 0xf8, 0x25, 0xa3, 0x35, 0x6d, 0xfa, 0xba, 0xe9, 0xf5, 0x6b, 0x5b, 0xd3, 0x33, 0xea, 0xfa, 0xe7, 0xf5, 0xd7, 0x65, 0xac, 0x1a, 0xac, 0x9c, 0x5d, 0x15, 0xd4, 0xa7, 0x6a, 0x32, 0x52, 0xda, 0x16, 0xac, 0x2a, 0x9d, 0x05, 0x89, 0x77, 0x66, 0xfd, 0xec, 0xf9, 0xb3, 0xeb, 0x33, 0xdb, 0x2b, 0x9c, 0x11, 0xaf, 0x5e, 0xad, 0x56, 0x90, 0x3b, 0x53, 0x6b, 0xfd, 0xbe, 0xae, 0xea, 0x4c, 0x7f, 0xdd, 0x40, 0x41, 0x69, 0x4f, 0x45, 0xd0, 0xe7, 0x0a, 0x44, 0x3c, 0x15, 0x33, 0x2c, 0xf6, 0x64, 0x6b, 0x97, 0x25, 0xd3, 0xae, 0x09, 0xcc, 0x18, 0xa9, 0x29, 0xea, 0x2c, 0xcb, 0xf4, 0xba, 0x33, 0x8b, 0xd3, 0x0b, 0x2b, 0x34, 0xa9, 0x3e, 0x83, 0x29, 0x55, 0xac, 0x52, 0x0b, 0x38, 0x1e, 0xfc, 0x4f, 0x48, 0x93, 0x57, 0xc3, 0x81, 0x38, 0xb9, 0x88, 0x41, 0x12, 0x28, 0x6d, 0xb8, 0x1d, 0x5a, 0x9a, 0x86, 0x3c, 0xb8, 0x9e, 0x8e, 0x3b, 0xa8, 0x84, 0xb9, 0x6c, 0x43, 0x3d, 0x9c, 0xec, 0xe8, 0x72, 0xb9, 0x3c, 0x2e, 0x8f, 0xd3, 0xef, 0xf4, 0xa8, 0xd0, 0x39, 0x4a, 0x03, 0x91, 0x50, 0x8c, 0x03, 0x87, 0x13, 0x58, 0xb0, 0x81, 0xe7, 0xc1, 0xc0, 0x29, 0x06, 0xf4, 0xea, 0x73, 0xdb, 0xe7, 0x01, 0xb0, 0x4c, 0x9c, 0xb1, 0x38, 0x1c, 0xce, 0xcc, 0x71, 0xd7, 0x57, 0x97, 0x5b, 0xc4, 0x12, 0x4b, 0x79, 0x75, 0xbd, 0x2b, 0x94, 0x91, 0x17, 0x5e, 0x92, 0x21, 0x02, 0x43, 0xdf, 0x67, 0x7f, 0xcf, 0xfe, 0xfe, 0x3e, 0x32, 0xaa, 0xa8, 0xaa, 0x1a, 0x52, 0x6b, 0x94, 0xae, 0xa2, 0x74, 0xa5, 0x32, 0xbd, 0xc8, 0x05, 0x09, 0xc4, 0x50, 0x55, 0x95, 0x7c, 0xf4, 0x35, 0xf0, 0x1a, 0x5b, 0x8e, 0xf9, 0xf1, 0xca, 0x0b, 0x5f, 0xd2, 0x65, 0xcc, 0xbb, 0x84, 0x9a, 0x98, 0x46, 0x2c, 0x3f, 0xe9, 0xe2, 0xf9, 0x31, 0x32, 0xc1, 0x39, 0x50, 0x67, 0x05, 0xbd, 0xc8, 0x6f, 0x14, 0xb3, 0x12, 0x4c, 0x57, 0x11, 0xb5, 0xe5, 0x7d, 0x0b, 0x73, 0xb1, 0xfb, 0x61, 0x8c, 0x50, 0x24, 0x16, 0x4e, 0x2c, 0xd2, 0x13, 0x35, 0x02, 0xa2, 0xac, 0xa4, 0xb8, 0x30, 0x27, 0xe8, 0xf3, 0xd8, 0x52, 0xac, 0x16, 0xc8, 0x85, 0xd5, 0x40, 0x3d, 0x99, 0x0b, 0x1b, 0xc6, 0x98, 0xb0, 0x67, 0x4a, 0x1e, 0xac, 0x89, 0x7b, 0x0f, 0x47, 0xc8, 0xae, 0xaa, 0xa5, 0xb5, 0x1e, 0x4f, 0xed, 0xd2, 0xaa, 0xba, 0xe5, 0x75, 0xee, 0xdb, 0xc0, 0x3f, 0x15, 0x2a, 0x21, 0xa9, 0x09, 0xb6, 0x55, 0x64, 0xcf, 0xf2, 0xd6, 0x0c, 0x4f, 0xcf, 0x16, 0xe6, 0x9e, 0xd8, 0xd5, 0xf7, 0xc0, 0xba, 0xaa, 0x9a, 0xad, 0x27, 0x96, 0xed, 0x7d, 0x69, 0x65, 0x56, 0xe6, 0xb2, 0xe7, 0x6e, 0xc8, 0x98, 0x13, 0x76, 0x67, 0x25, 0x4b, 0xc1, 0x8d, 0x79, 0xb3, 0xb6, 0x35, 0x36, 0x6e, 0xea, 0x0a, 0x66, 0xb5, 0x2e, 0x2b, 0x3d, 0x7d, 0x86, 0xa9, 0x95, 0x28, 0x35, 0x62, 0x5f, 0xa9, 0xdf, 0x20, 0x8c, 0xac, 0x3b, 0x7d, 0x43, 0xff, 0xb2, 0xba, 0xed, 0xcf, 0x2c, 0x59, 0xf2, 0xc2, 0xee, 0xe6, 0x45, 0xcf, 0x7c, 0xba, 0xe3, 0x29, 0xe0, 0x7d, 0x6f, 0x9d, 0x50, 0xa0, 0x30, 0xaa, 0x39, 0x8c, 0x34, 0x7b, 0x8c, 0x3a, 0x82, 0x79, 0x2e, 0xb2, 0xfd, 0xf0, 0x3c, 0x17, 0x73, 0x5a, 0x12, 0x4b, 0x1e, 0xc8, 0xe5, 0x18, 0x1f, 0xf8, 0xe0, 0x15, 0x71, 0x5d, 0xea, 0x08, 0x7b, 0xe4, 0xec, 0xd2, 0xf1, 0x5c, 0x77, 0xd5, 0x59, 0x30, 0x93, 0xea, 0x38, 0xd7, 0x49, 0x1f, 0x8d, 0x73, 0xdd, 0x2a, 0xbf, 0x1a, 0x45, 0xfe, 0xc2, 0xfe, 0x5c, 0xb9, 0x42, 0x1d, 0x83, 0x62, 0x61, 0x44, 0x08, 0x47, 0x34, 0x15, 0xd9, 0x08, 0x48, 0x02, 0x59, 0x22, 0xe1, 0xd6, 0xea, 0xe2, 0x51, 0xe6, 0x24, 0x68, 0x09, 0x04, 0x02, 0x1e, 0xde, 0xe4, 0x85, 0xc4, 0x16, 0x64, 0x59, 0xa4, 0x11, 0xbd, 0x88, 0xe8, 0x52, 0xe0, 0xf7, 0x52, 0x1a, 0x41, 0x46, 0x50, 0xfa, 0x84, 0x19, 0xa5, 0x2b, 0x0f, 0x9d, 0xfe, 0xcd, 0xba, 0x6b, 0x3e, 0x3c, 0x7d, 0x68, 0xb8, 0xb4, 0x74, 0xf8, 0xd0, 0xe9, 0x0f, 0xaf, 0x41, 0x9f, 0x57, 0x96, 0x36, 0xe5, 0x2f, 0xbc, 0xf9, 0xa9, 0xb7, 0x96, 0x2d, 0x7b, 0xeb, 0xa9, 0x9b, 0x17, 0xe6, 0x27, 0x7e, 0x26, 0x6f, 0x5f, 0xf7, 0x9b, 0xef, 0xc3, 0x02, 0xb0, 0xe2, 0xf7, 0x13, 0x2b, 0xfe, 0x66, 0x3d, 0xfc, 0xfd, 0x16, 0x54, 0xf6, 0x16, 0x54, 0xf6, 0xcd, 0xe3, 0x5c, 0xbd, 0xe3, 0x6f, 0x72, 0x7c, 0x6a, 0xe0, 0xc2, 0xe7, 0x22, 0x26, 0x16, 0x83, 0xd9, 0x01, 0x18, 0x22, 0x92, 0x4b, 0x0a, 0x84, 0x5c, 0x94, 0x1f, 0x09, 0x88, 0xe7, 0x72, 0x43, 0x81, 0x38, 0x97, 0x8b, 0x71, 0x26, 0x2e, 0x14, 0xf4, 0xcb, 0x86, 0xb9, 0x79, 0x3c, 0xda, 0x4f, 0x75, 0x65, 0x79, 0x99, 0x53, 0xeb, 0x42, 0x00, 0x40, 0x9f, 0x4b, 0x8a, 0x63, 0x18, 0x8d, 0x4b, 0x0d, 0x91, 0x98, 0xb9, 0x8d, 0x43, 0xf8, 0xc5, 0xd1, 0x7f, 0xa4, 0x5a, 0xa5, 0xc1, 0x3c, 0x28, 0xa4, 0x46, 0x57, 0x45, 0x1a, 0xb5, 0x2a, 0x76, 0xc9, 0xe0, 0xa5, 0x7f, 0x90, 0x5c, 0xd1, 0xdc, 0x97, 0xbf, 0xfb, 0xf5, 0x4d, 0x05, 0x0d, 0xfb, 0x7f, 0xba, 0x73, 0xdb, 0xcf, 0xda, 0xda, 0x7e, 0xb6, 0x6d, 0xe7, 0x4f, 0xf7, 0x37, 0x14, 0x6c, 0x3c, 0xb3, 0x7b, 0xf9, 0x5d, 0xf9, 0x01, 0xa9, 0x56, 0xa1, 0xb0, 0x65, 0x4d, 0xcb, 0xbe, 0xef, 0xc8, 0xa9, 0xa7, 0x7b, 0x6f, 0x2b, 0x78, 0xbf, 0xe0, 0xd6, 0xde, 0x67, 0x5e, 0x3a, 0x72, 0x5f, 0x70, 0x5a, 0x96, 0x1d, 0xf9, 0x2e, 0x31, 0x59, 0x96, 0x0c, 0xbb, 0x76, 0xe5, 0x19, 0xf6, 0xdb, 0x3b, 0x8e, 0xb3, 0x7f, 0x3e, 0xbd, 0xa0, 0xaf, 0xa3, 0xa3, 0x6f, 0xc1, 0x69, 0x60, 0x38, 0x7e, 0x07, 0x10, 0x9c, 0x59, 0x59, 0x1d, 0x5e, 0xa2, 0x96, 0x1a, 0x93, 0x8d, 0xec, 0xa7, 0xec, 0xdf, 0xd9, 0xbf, 0xb2, 0x7f, 0xaa, 0x29, 0x03, 0x7f, 0x60, 0xcd, 0x65, 0x35, 0xc0, 0x04, 0x34, 0x40, 0x0e, 0xf4, 0xd8, 0xe1, 0x87, 0xcf, 0x79, 0x72, 0xe1, 0x73, 0x61, 0x29, 0x94, 0xdf, 0x23, 0xc4, 0x2d, 0x27, 0x8d, 0x28, 0x1e, 0x64, 0x3d, 0x77, 0xab, 0x9e, 0x1d, 0x87, 0x21, 0x49, 0x81, 0x04, 0x48, 0x25, 0xc3, 0x22, 0x40, 0x8b, 0x01, 0x90, 0x22, 0x47, 0x67, 0x04, 0x8e, 0xe4, 0x43, 0xf5, 0x4a, 0x24, 0x09, 0x3e, 0x38, 0x05, 0x97, 0xa8, 0x83, 0x4b, 0x0b, 0x85, 0xb1, 0x18, 0xd7, 0xd8, 0x29, 0x04, 0x55, 0x8e, 0x39, 0x7f, 0x69, 0xc2, 0xb9, 0x99, 0x01, 0x9f, 0x57, 0xe7, 0x74, 0xbb, 0x50, 0xa6, 0x50, 0x07, 0x72, 0x4a, 0x75, 0xf3, 0x97, 0x07, 0xbc, 0x37, 0x08, 0xa6, 0x47, 0x89, 0xd8, 0x4a, 0x84, 0x87, 0x48, 0x49, 0xb8, 0x59, 0x10, 0x96, 0x76, 0xfc, 0xfd, 0xd8, 0x4d, 0xff, 0x77, 0x6c, 0x81, 0x84, 0xfd, 0xc9, 0xfb, 0xef, 0x83, 0x5c, 0xc5, 0xb2, 0xe3, 0x7f, 0xde, 0x7b, 0xfb, 0xc7, 0x1d, 0x08, 0x4e, 0xa9, 0xc9, 0x6e, 0x5e, 0xdd, 0xd9, 0x77, 0xef, 0x35, 0x0d, 0x32, 0xf2, 0xba, 0xd1, 0xf5, 0xe2, 0xa6, 0xf5, 0xf7, 0xf5, 0x14, 0xce, 0x6a, 0xae, 0x72, 0x62, 0xfc, 0xe5, 0x5b, 0x8b, 0x46, 0xe6, 0x1e, 0xff, 0xea, 0xc0, 0xe8, 0x5a, 0xe6, 0xde, 0xd1, 0x9b, 0x6e, 0xfa, 0xcb, 0x63, 0xfd, 0x03, 0xed, 0x1c, 0x8e, 0xb2, 0x74, 0xcd, 0xd1, 0xa1, 0xc3, 0xab, 0x9e, 0xdd, 0x54, 0x1e, 0xbf, 0x49, 0x40, 0xf4, 0xf6, 0x5b, 0x48, 0x66, 0xae, 0xc3, 0xf7, 0xaf, 0x66, 0x84, 0x9c, 0xa0, 0x51, 0xd4, 0xb8, 0x7a, 0x21, 0x22, 0x47, 0xc8, 0x02, 0x3c, 0x17, 0x67, 0xe0, 0xe5, 0x82, 0xff, 0x21, 0x7d, 0x07, 0xa5, 0x44, 0xe6, 0xb1, 0x15, 0x62, 0x74, 0x9d, 0x2a, 0x42, 0x41, 0x33, 0x29, 0xee, 0x2e, 0x8d, 0xe2, 0x7c, 0x49, 0x31, 0xda, 0x83, 0xf3, 0xc9, 0x0c, 0x51, 0xd7, 0x5c, 0x2f, 0x35, 0xcb, 0xa4, 0x49, 0xb2, 0x5d, 0xd4, 0xa6, 0xa3, 0xc9, 0x2e, 0xd1, 0xff, 0xde, 0xfa, 0xe6, 0xfe, 0xff, 0x15, 0xb9, 0x2c, 0xc7, 0xc8, 0x39, 0x6f, 0x93, 0x5f, 0x81, 0x16, 0xeb, 0xf4, 0xcc, 0xac, 0xe9, 0x29, 0xec, 0xb3, 0xa3, 0xd2, 0x64, 0x03, 0xe9, 0x1f, 0x7d, 0x88, 0xec, 0x1b, 0x7d, 0x5f, 0x9b, 0x0a, 0x7b, 0x43, 0xe2, 0x3c, 0x99, 0xff, 0x83, 0xfb, 0xe5, 0x40, 0x58, 0x18, 0x1a, 0x81, 0x5a, 0xeb, 0xc5, 0x52, 0x92, 0x10, 0xd0, 0x24, 0xee, 0x19, 0xc2, 0x13, 0x26, 0x35, 0x08, 0x19, 0x24, 0x91, 0xe1, 0xbe, 0x25, 0xa6, 0x6d, 0x4e, 0xec, 0xa3, 0xec, 0x92, 0x7d, 0xe4, 0xd7, 0x03, 0x84, 0x98, 0x9f, 0x5c, 0xa4, 0xaf, 0x80, 0x60, 0xc3, 0xef, 0xbf, 0x4f, 0x4a, 0x41, 0xea, 0x25, 0xfa, 0x0c, 0xff, 0x7e, 0x08, 0x4e, 0x78, 0x0f, 0xf9, 0x08, 0xec, 0xbb, 0x81, 0x20, 0x04, 0x83, 0x38, 0x9e, 0x7f, 0x3e, 0x8a, 0xb8, 0xc3, 0xa0, 0x64, 0xb2, 0xf5, 0xf0, 0x20, 0xc2, 0x9f, 0x04, 0xe4, 0x30, 0x37, 0xb3, 0x60, 0x39, 0x8e, 0x0d, 0x89, 0xc3, 0xaf, 0xc6, 0xe6, 0x57, 0xad, 0xd6, 0x7b, 0xcb, 0xb5, 0x5e, 0xbb, 0x05, 0x87, 0x17, 0x4b, 0xa1, 0x8c, 0xa5, 0x54, 0x04, 0x1e, 0x5b, 0x2e, 0xba, 0x1c, 0x95, 0x0e, 0x84, 0xb8, 0xdf, 0x5e, 0xbc, 0x5d, 0xb8, 0xdb, 0x4a, 0xae, 0x00, 0x2c, 0x49, 0x7e, 0xdf, 0xe4, 0x4d, 0x35, 0x88, 0x84, 0xc6, 0x54, 0xaf, 0x09, 0x28, 0xcf, 0xff, 0x99, 0x3e, 0x71, 0xd4, 0xe2, 0x16, 0xdd, 0xa6, 0xf5, 0xe5, 0xb7, 0x14, 0x96, 0x91, 0xf4, 0x5b, 0x80, 0x2c, 0x2e, 0x6c, 0xc9, 0xf7, 0x69, 0x6f, 0x13, 0xb9, 0x2d, 0x47, 0x5f, 0x4e, 0xae, 0xec, 0xdb, 0x30, 0x83, 0x05, 0xcc, 0x1b, 0x2c, 0xd1, 0xba, 0xb1, 0xbf, 0x32, 0x99, 0x7a, 0x21, 0x25, 0x27, 0x33, 0xc3, 0x6c, 0xce, 0xc8, 0x84, 0xc4, 0xa0, 0xe1, 0xdb, 0xd1, 0x54, 0x2d, 0x78, 0x28, 0x5c, 0x9d, 0xa6, 0x26, 0x47, 0xd7, 0x92, 0x37, 0x92, 0xea, 0xb4, 0xea, 0x30, 0xdb, 0xa7, 0x4d, 0x05, 0x7f, 0xae, 0x1d, 0x6e, 0xc9, 0x64, 0xc8, 0x75, 0xa3, 0x37, 0x30, 0x19, 0xd3, 0x87, 0x6b, 0xb9, 0x3c, 0xe3, 0x04, 0x21, 0x7a, 0x1d, 0x8f, 0xb5, 0x04, 0x45, 0x4f, 0x80, 0x63, 0x15, 0x83, 0x7a, 0x28, 0xb5, 0xe1, 0x65, 0x12, 0x93, 0x28, 0x2c, 0x8c, 0xa8, 0x67, 0xc2, 0x5a, 0x71, 0x88, 0x30, 0x78, 0x32, 0xb8, 0xd1, 0xca, 0xae, 0x78, 0xb4, 0x20, 0x12, 0x5b, 0x2e, 0x3c, 0x68, 0x26, 0xd9, 0xe4, 0xb5, 0xc5, 0x06, 0xad, 0x80, 0x83, 0x3e, 0x79, 0xcc, 0xe2, 0x16, 0x1e, 0xc0, 0x83, 0x2e, 0x25, 0x99, 0x37, 0x01, 0x59, 0x93, 0x59, 0x5b, 0x94, 0xa1, 0xbd, 0x03, 0x2d, 0xe3, 0x29, 0x7d, 0x47, 0xfe, 0xe8, 0xa9, 0xf7, 0xdf, 0xa7, 0x32, 0xe0, 0xc0, 0x7f, 0x78, 0x45, 0x03, 0x57, 0xf9, 0xf9, 0x81, 0xff, 0x31, 0xbf, 0xd5, 0x32, 0xba, 0x1a, 0xae, 0xef, 0x6e, 0xf2, 0x9a, 0xf8, 0xe0, 0x01, 0x91, 0xc4, 0x2e, 0xa3, 0x46, 0x21, 0x4d, 0x1e, 0x20, 0x55, 0x51, 0x49, 0x08, 0x08, 0x44, 0xa5, 0x38, 0xbb, 0x1c, 0x27, 0x2c, 0xe7, 0x23, 0x62, 0x2c, 0x10, 0x32, 0x90, 0xb9, 0x11, 0x28, 0x38, 0x2b, 0x0e, 0x58, 0x87, 0x84, 0x58, 0x1b, 0xbe, 0xb0, 0x11, 0xf7, 0x4a, 0xa0, 0x68, 0x6b, 0xc6, 0xe1, 0x35, 0x89, 0x5e, 0xe4, 0xde, 0x6c, 0x6d, 0x20, 0xe4, 0xf2, 0x54, 0x79, 0xcc, 0x39, 0xaa, 0xe2, 0xe2, 0xf5, 0x89, 0xb1, 0xea, 0x18, 0x60, 0x7a, 0x89, 0x96, 0x10, 0xf9, 0x8b, 0x4e, 0x6c, 0x09, 0x96, 0x84, 0xc5, 0xe4, 0x52, 0xe2, 0xb2, 0x6d, 0xc6, 0xdb, 0x41, 0x16, 0xb1, 0xd2, 0x2b, 0x6c, 0x47, 0xd2, 0x0b, 0xa9, 0x21, 0xdf, 0x0c, 0x31, 0xae, 0x95, 0xcc, 0x71, 0xad, 0xc8, 0x09, 0xf9, 0xaa, 0xab, 0x6c, 0x4a, 0x1c, 0xb3, 0xb8, 0x15, 0x4f, 0xd9, 0x0a, 0xdc, 0x8f, 0x12, 0x31, 0x29, 0x99, 0xba, 0xb5, 0x84, 0x36, 0xa2, 0xe5, 0x97, 0xac, 0x4e, 0xc8, 0xa4, 0x84, 0xac, 0x8b, 0xb8, 0x64, 0x1b, 0xbc, 0xbd, 0xd2, 0x30, 0x30, 0xb7, 0xa7, 0xab, 0xb3, 0xbd, 0xb9, 0xb1, 0xae, 0x06, 0x45, 0xf3, 0xf7, 0x39, 0x5d, 0x39, 0x0e, 0x05, 0xda, 0xcf, 0x9c, 0xc2, 0x01, 0x37, 0x33, 0x2f, 0x83, 0xc6, 0x1c, 0xff, 0x84, 0xce, 0x4b, 0x06, 0xf7, 0xa7, 0xc6, 0x4c, 0x28, 0xbc, 0x97, 0x75, 0x38, 0x26, 0xc1, 0x41, 0x22, 0x96, 0x4e, 0x96, 0x34, 0x25, 0xf9, 0x32, 0xf2, 0xbd, 0x7f, 0x7c, 0xf7, 0x9e, 0xfd, 0xa9, 0x85, 0xe1, 0x3c, 0x4b, 0xc5, 0xc2, 0x1a, 0x57, 0x7a, 0xcb, 0x48, 0xed, 0x1b, 0xe0, 0x65, 0x9c, 0x07, 0xa0, 0x3e, 0x96, 0x07, 0x40, 0x2f, 0x89, 0xe5, 0x01, 0x58, 0xd0, 0x9f, 0xdb, 0x55, 0xe2, 0x28, 0xd9, 0x74, 0x6a, 0xb3, 0x23, 0x9c, 0xe1, 0xd3, 0x46, 0x72, 0x75, 0xd9, 0xad, 0x25, 0x55, 0xeb, 0x43, 0x55, 0x33, 0xc3, 0x06, 0x52, 0xac, 0x55, 0x45, 0xd8, 0x65, 0x61, 0x3f, 0x45, 0x53, 0x3b, 0x76, 0x6d, 0xd8, 0x21, 0x10, 0x08, 0xdc, 0x15, 0x33, 0xf3, 0x0a, 0xfb, 0x4a, 0x6d, 0x60, 0xca, 0x74, 0x01, 0x8b, 0x1f, 0x08, 0x1a, 0xca, 0x3b, 0x16, 0x95, 0xb6, 0x1d, 0x5c, 0x53, 0xcd, 0x08, 0x18, 0xbf, 0xd7, 0xe4, 0x32, 0xc9, 0x25, 0xd6, 0x50, 0x6d, 0xba, 0xcc, 0xa0, 0x96, 0x5e, 0xb8, 0x40, 0xcc, 0x64, 0x8f, 0x49, 0x4a, 0x20, 0x0f, 0xf6, 0x10, 0x16, 0x1c, 0x9f, 0x68, 0x8f, 0xa0, 0x18, 0xd3, 0x89, 0x9e, 0x0b, 0x0f, 0x0a, 0xea, 0x98, 0x6f, 0x89, 0x54, 0xa2, 0x0c, 0x45, 0xe2, 0x53, 0x49, 0xa0, 0xd4, 0x65, 0x46, 0xd7, 0x15, 0xf5, 0xb1, 0x74, 0x27, 0x04, 0x0a, 0xe0, 0xd8, 0x85, 0x01, 0xc1, 0xe8, 0x72, 0xa9, 0x25, 0x33, 0x2d, 0x33, 0x80, 0x22, 0x7f, 0xb9, 0x63, 0x28, 0x26, 0x4e, 0x40, 0xe1, 0xee, 0xe9, 0xb1, 0x74, 0x82, 0xf4, 0x63, 0x21, 0xf7, 0x98, 0xb3, 0x10, 0xc7, 0xf3, 0x41, 0x51, 0x1f, 0xf4, 0xdf, 0xc1, 0x19, 0x98, 0x8c, 0x35, 0xe9, 0xe9, 0xd5, 0xc6, 0x59, 0x77, 0x2e, 0x2d, 0x28, 0x58, 0x7a, 0xe7, 0x6c, 0x63, 0xb5, 0x3f, 0xbd, 0x9a, 0xbc, 0xae, 0xeb, 0xfe, 0x8f, 0x6e, 0x34, 0x34, 0x07, 0x83, 0x8d, 0x86, 0x3d, 0x1f, 0x3f, 0xd0, 0xdd, 0xfd, 0xc0, 0x27, 0x7b, 0x0c, 0x0d, 0xc1, 0x60, 0x93, 0xe1, 0xc6, 0x8f, 0x0e, 0x77, 0x1d, 0x57, 0x74, 0xed, 0x3d, 0xb5, 0x6a, 0xd5, 0xcb, 0x37, 0x76, 0x29, 0x6c, 0x2e, 0xb7, 0x4d, 0xd1, 0xb5, 0xe7, 0x95, 0x55, 0xab, 0x5f, 0xde, 0xd3, 0x25, 0xb7, 0xb9, 0xdd, 0x24, 0x71, 0xdb, 0xb9, 0xd7, 0x36, 0xa8, 0x3d, 0x99, 0x19, 0x1e, 0xd5, 0xba, 0xd7, 0xce, 0xdf, 0x7e, 0x60, 0xf4, 0xb5, 0xf5, 0x2a, 0x4f, 0x46, 0xa6, 0x47, 0xbd, 0xee, 0xb5, 0x73, 0x1c, 0x6e, 0x65, 0x3e, 0x92, 0xd3, 0x84, 0x4b, 0x08, 0x2d, 0xe1, 0x27, 0x56, 0x11, 0x3f, 0x8b, 0x2a, 0x92, 0xa0, 0xac, 0xd6, 0x02, 0xe4, 0xc2, 0x12, 0x20, 0x56, 0x20, 0x73, 0x3e, 0x32, 0xa3, 0xd7, 0x11, 0x42, 0x91, 0x9c, 0x11, 0x2e, 0xd2, 0x00, 0x99, 0x5c, 0x29, 0x97, 0x29, 0x87, 0x09, 0xa5, 0x42, 0xae, 0x54, 0x2c, 0x47, 0xf2, 0x30, 0x8d, 0xcc, 0x71, 0xc8, 0x04, 0xc5, 0x20, 0x13, 0x94, 0x80, 0x60, 0x44, 0x02, 0x66, 0x40, 0x0d, 0xe4, 0x72, 0xa8, 0x68, 0x03, 0xb4, 0x09, 0x55, 0x40, 0xa1, 0xe0, 0xce, 0xa7, 0x1d, 0x6d, 0xe5, 0x7a, 0xd4, 0x96, 0x48, 0x28, 0x1f, 0xfe, 0xf7, 0x1b, 0xeb, 0x89, 0xda, 0x56, 0xae, 0x18, 0x5a, 0x32, 0x38, 0x7f, 0xde, 0xdc, 0xd9, 0xfd, 0x3d, 0x5d, 0xed, 0xad, 0x0d, 0x75, 0xe5, 0x65, 0x45, 0x05, 0x6e, 0x83, 0xcb, 0xa7, 0x77, 0x68, 0x9c, 0x2a, 0x2d, 0xda, 0xd8, 0xfc, 0xde, 0x74, 0xe2, 0x6d, 0x4d, 0x87, 0x40, 0x2e, 0x42, 0xf1, 0xe1, 0x45, 0xe0, 0xac, 0x16, 0x78, 0x03, 0x1b, 0x2f, 0x2d, 0x57, 0x26, 0x66, 0x04, 0x8e, 0x09, 0x96, 0xa1, 0x89, 0x62, 0x25, 0xf3, 0xe5, 0xec, 0x03, 0xeb, 0x17, 0xe7, 0x96, 0xd5, 0xad, 0xbf, 0x6e, 0x4d, 0xb0, 0xbd, 0xa5, 0xa5, 0x73, 0x76, 0x01, 0x2b, 0x73, 0xd8, 0x1c, 0x24, 0x23, 0x17, 0xe6, 0xa4, 0x67, 0xa7, 0x87, 0x22, 0x86, 0x8c, 0xca, 0xac, 0x1f, 0x21, 0xd9, 0x33, 0xb2, 0xfb, 0xcc, 0x46, 0x2c, 0x7b, 0x6e, 0xfd, 0x59, 0x3b, 0x92, 0x3d, 0x7f, 0xc2, 0xc9, 0x9e, 0x91, 0xbe, 0xe6, 0x72, 0x6b, 0x06, 0x2f, 0x7d, 0x06, 0xef, 0x3b, 0xf2, 0xd2, 0x33, 0xab, 0xee, 0x0b, 0x3e, 0x10, 0x3c, 0xb4, 0xea, 0xe9, 0x53, 0x48, 0xfa, 0x0c, 0xda, 0x90, 0x43, 0xb9, 0x60, 0x4d, 0x7a, 0x6e, 0x7a, 0xcd, 0xb4, 0x35, 0x5b, 0xf5, 0xa9, 0xee, 0x54, 0x3d, 0xfb, 0xa0, 0x56, 0x28, 0x72, 0xb8, 0x48, 0x32, 0xd3, 0xe6, 0xc9, 0xae, 0x2c, 0x70, 0x45, 0xb3, 0x2c, 0x97, 0x94, 0x4e, 0xb5, 0xf6, 0x0c, 0xcb, 0x78, 0xf9, 0x74, 0xe5, 0x9c, 0xb9, 0xc3, 0x53, 0xc8, 0xa6, 0xbd, 0xec, 0xc3, 0xf4, 0xb7, 0xf0, 0x5c, 0x64, 0x82, 0x45, 0x51, 0x89, 0x0a, 0xea, 0xed, 0x48, 0xe5, 0x23, 0xf9, 0xeb, 0x1d, 0x6f, 0x2c, 0x32, 0x60, 0x42, 0xe8, 0xfc, 0x98, 0x5f, 0x27, 0x0a, 0x92, 0x92, 0xe0, 0xb0, 0x28, 0xc3, 0x1e, 0x8e, 0xb1, 0xc8, 0xfb, 0xf1, 0xe2, 0xe8, 0x6a, 0x82, 0xf3, 0xf2, 0xc4, 0x1f, 0x13, 0x2a, 0xa8, 0x2f, 0x53, 0xc1, 0x3e, 0xb1, 0x82, 0xf6, 0x6a, 0xdf, 0xa0, 0xbf, 0xf4, 0x08, 0x9c, 0x13, 0x46, 0x60, 0xbc, 0x9a, 0x01, 0x5f, 0x71, 0xc3, 0x3c, 0x3d, 0x96, 0x38, 0x9d, 0x3e, 0xa8, 0xde, 0x64, 0xe3, 0xc0, 0x97, 0x97, 0x71, 0x65, 0x42, 0x74, 0x18, 0x6a, 0x91, 0x18, 0x51, 0x3e, 0x64, 0x8e, 0x68, 0xc6, 0xbc, 0x96, 0xfe, 0xda, 0x05, 0xc5, 0x6c, 0xb5, 0x36, 0xd4, 0xb6, 0xa1, 0x17, 0x79, 0x2d, 0xd5, 0x6d, 0x3f, 0xbe, 0x28, 0x54, 0x9a, 0x96, 0xa2, 0x29, 0x4e, 0x4d, 0x5a, 0x2f, 0x24, 0xc9, 0x25, 0xdf, 0x07, 0xb2, 0xc9, 0x3e, 0x4a, 0x28, 0x6f, 0xb4, 0x58, 0x22, 0x66, 0x53, 0x92, 0x51, 0xdc, 0xd1, 0x95, 0x50, 0xae, 0x3e, 0x4e, 0x7d, 0x4d, 0x94, 0xa3, 0xf8, 0xb9, 0xb9, 0x2e, 0x25, 0x85, 0x2f, 0x12, 0x68, 0x8a, 0x42, 0x2e, 0xb9, 0x7c, 0x34, 0x13, 0x38, 0x86, 0x72, 0x22, 0x1a, 0xf0, 0xa4, 0x9b, 0x70, 0x4e, 0x35, 0xce, 0x2a, 0x25, 0xc4, 0xe4, 0x0c, 0x4b, 0xab, 0x48, 0xe8, 0xc1, 0x89, 0x10, 0x11, 0x8e, 0x1b, 0x87, 0xc8, 0x8b, 0x7d, 0x8e, 0x03, 0x40, 0xb1, 0x1f, 0xf9, 0x75, 0x3a, 0x63, 0x5a, 0xd3, 0x8a, 0xea, 0x55, 0x6a, 0xad, 0xbc, 0x6c, 0xc4, 0xe3, 0x1e, 0x89, 0xca, 0x14, 0xea, 0x75, 0x65, 0x8b, 0x9b, 0xfc, 0x3b, 0x52, 0x2d, 0x0b, 0x7a, 0x7d, 0xd5, 0x21, 0xeb, 0xce, 0x14, 0x8b, 0x25, 0xbb, 0x26, 0xe0, 0xaf, 0x8f, 0xd8, 0xe4, 0x2a, 0xb9, 0x41, 0x47, 0x7d, 0xdd, 0xf6, 0xbd, 0xd6, 0xe1, 0xd7, 0x8f, 0xdd, 0x5e, 0x21, 0x57, 0x94, 0x9d, 0x5c, 0xb8, 0xf0, 0x64, 0x54, 0x21, 0x2b, 0xda, 0x7b, 0xf7, 0xa3, 0x5d, 0xe0, 0xa0, 0xcb, 0xc8, 0xbe, 0xb8, 0xff, 0xc3, 0x0e, 0xdf, 0xe0, 0x9a, 0x2d, 0x65, 0x60, 0x3b, 0xfc, 0x76, 0xba, 0xec, 0xda, 0xb5, 0x4b, 0x02, 0x69, 0x83, 0xd7, 0xde, 0x31, 0xb3, 0xac, 0xaf, 0x22, 0x43, 0xa9, 0xb6, 0x72, 0x31, 0xa8, 0xbf, 0x62, 0x8a, 0x68, 0x8a, 0x08, 0x83, 0xff, 0xe5, 0xa4, 0x03, 0x89, 0x19, 0x88, 0x90, 0x1f, 0x9e, 0x14, 0xe7, 0x4e, 0xe5, 0xbe, 0x50, 0x52, 0x1c, 0x01, 0x47, 0xc6, 0xfb, 0x91, 0x32, 0x22, 0xc8, 0x71, 0x65, 0x50, 0xf8, 0x85, 0x62, 0xbc, 0x74, 0x18, 0x6a, 0xa9, 0x28, 0x5e, 0x3d, 0x0a, 0x2b, 0x2c, 0x12, 0xc5, 0x82, 0x2b, 0x13, 0x52, 0x29, 0x17, 0xfc, 0x8d, 0xcb, 0x7e, 0xae, 0xfe, 0x0e, 0x35, 0xb9, 0x74, 0xe8, 0xfa, 0xef, 0xfc, 0x4e, 0xf3, 0x77, 0x7e, 0x67, 0xf2, 0x77, 0x7e, 0x67, 0xe0, 0x3b, 0xbe, 0x33, 0x5a, 0x78, 0xa9, 0x4a, 0xb8, 0x38, 0xef, 0x72, 0x8b, 0x1d, 0xe7, 0xcd, 0x5c, 0x6d, 0x22, 0x96, 0xce, 0x1d, 0x9d, 0x98, 0x54, 0x40, 0x64, 0x67, 0x21, 0xc8, 0xac, 0xcb, 0x91, 0x6a, 0x45, 0x31, 0x1a, 0xb9, 0xa0, 0x81, 0x42, 0x06, 0x2e, 0x6d, 0x58, 0x8e, 0x93, 0x03, 0xf0, 0x4e, 0x81, 0xa4, 0x97, 0x0f, 0x14, 0x7e, 0x29, 0xe5, 0x35, 0x4c, 0xbf, 0xaf, 0x94, 0x36, 0x14, 0x3f, 0xb9, 0x7e, 0xc1, 0xc3, 0xeb, 0xea, 0xc4, 0xaf, 0xbe, 0xf2, 0xca, 0x6b, 0xb2, 0x96, 0x6b, 0x1f, 0x9e, 0xb7, 0xf4, 0xe1, 0xc2, 0x32, 0x46, 0xa3, 0x54, 0xd9, 0x73, 0x1a, 0x0a, 0xf2, 0xe7, 0xd4, 0x67, 0x8b, 0xc1, 0x51, 0xb6, 0x43, 0x14, 0x6a, 0x9a, 0x1b, 0x76, 0x15, 0xe6, 0x04, 0x8c, 0xca, 0xcf, 0xa5, 0xad, 0x4d, 0x33, 0xa6, 0xad, 0x7b, 0x64, 0x01, 0x5b, 0x49, 0x7d, 0xcd, 0xb6, 0xce, 0x7b, 0x70, 0x75, 0xb4, 0xb6, 0xb8, 0x47, 0x24, 0x4f, 0xb2, 0x26, 0xb9, 0x6b, 0x17, 0x56, 0xec, 0xaf, 0x59, 0x56, 0xef, 0x15, 0x69, 0xac, 0x7a, 0x39, 0x92, 0x25, 0x84, 0x04, 0x41, 0xf7, 0x53, 0xdf, 0x5c, 0x42, 0x67, 0xb5, 0x5c, 0x4e, 0x67, 0x4d, 0x44, 0x82, 0x8d, 0xd7, 0x59, 0xc9, 0x67, 0xbb, 0x25, 0x7a, 0xa9, 0x58, 0x2f, 0xe9, 0x26, 0x1f, 0xbf, 0x33, 0x29, 0x45, 0x74, 0x70, 0xf0, 0x99, 0x79, 0x07, 0x05, 0x36, 0xd3, 0x41, 0xf0, 0xb3, 0x13, 0xa4, 0xf8, 0x8d, 0xa4, 0x2a, 0x5f, 0x5a, 0xa5, 0xf9, 0x07, 0xa3, 0xff, 0x32, 0x69, 0x41, 0x17, 0xbb, 0x19, 0x6c, 0x67, 0x1f, 0x53, 0x9b, 0x61, 0x5f, 0x70, 0x6e, 0x0a, 0xa8, 0xf7, 0xf9, 0x71, 0xbf, 0x2e, 0xa1, 0xb3, 0x5a, 0xae, 0x58, 0x67, 0xbd, 0x58, 0x1f, 0xb5, 0xbc, 0x12, 0xa4, 0x0d, 0xd1, 0xdb, 0x50, 0x5f, 0x25, 0x7a, 0x49, 0x17, 0x79, 0xec, 0x4e, 0x53, 0xaa, 0xe8, 0xe0, 0xfc, 0x67, 0x06, 0xee, 0x14, 0xda, 0x4d, 0x07, 0x8f, 0xb3, 0xaf, 0xbf, 0xf2, 0x0a, 0xd8, 0xf4, 0xf1, 0x73, 0x17, 0xeb, 0x31, 0xfc, 0x7b, 0x33, 0xf5, 0x0d, 0xab, 0x02, 0x9f, 0xc3, 0x7e, 0x6b, 0x51, 0xbe, 0x79, 0x1c, 0x5f, 0x2c, 0x9f, 0x08, 0x46, 0x33, 0x42, 0x41, 0x06, 0x99, 0xfe, 0xeb, 0x69, 0x3e, 0x9b, 0x04, 0xf2, 0x4e, 0xc1, 0x41, 0xb7, 0xc6, 0x66, 0xd5, 0xc8, 0x69, 0x6e, 0x42, 0x2e, 0xd6, 0x05, 0xa4, 0x45, 0x65, 0x57, 0xaa, 0xa7, 0x26, 0xeb, 0x1d, 0x16, 0x9d, 0x40, 0xa0, 0xb3, 0x38, 0xf4, 0x87, 0x47, 0xff, 0x40, 0xed, 0x3c, 0x68, 0xb4, 0x89, 0x06, 0x95, 0xf6, 0x40, 0x81, 0x2f, 0x15, 0x08, 0x9e, 0xa1, 0x40, 0xb2, 0xaf, 0x20, 0xdd, 0xae, 0x5c, 0x28, 0xb4, 0x1b, 0x0f, 0xee, 0x35, 0xe6, 0x4d, 0x6b, 0x8f, 0xb0, 0xbb, 0xe9, 0xbd, 0xec, 0x0d, 0xf9, 0x9d, 0xd3, 0xf2, 0x8c, 0xe4, 0xeb, 0x66, 0xbf, 0xd7, 0x67, 0x30, 0xf8, 0xbc, 0x7e, 0xf3, 0x53, 0xe7, 0x6f, 0x32, 0xab, 0x81, 0xca, 0x97, 0x9b, 0x2c, 0x05, 0xec, 0x34, 0xf0, 0x0a, 0x90, 0x26, 0xe7, 0xfa, 0xd8, 0xcf, 0xe1, 0xc8, 0x1a, 0xc2, 0xed, 0xa5, 0x1e, 0x11, 0x78, 0x9e, 0xad, 0x13, 0x7a, 0x4a, 0x3b, 0xc2, 0xfc, 0xba, 0xbc, 0x0b, 0xd7, 0x05, 0xe9, 0xa8, 0x50, 0x1f, 0xcf, 0x67, 0x48, 0xa4, 0xa4, 0x12, 0x93, 0x75, 0xd4, 0x84, 0xb5, 0x19, 0xa7, 0xa1, 0x4a, 0xe3, 0x1a, 0x6a, 0xd9, 0xd5, 0x69, 0xa8, 0xd4, 0xdf, 0xc6, 0x86, 0xfb, 0xbd, 0xd1, 0x3f, 0xa1, 0xe1, 0xda, 0x85, 0x0b, 0x95, 0xf6, 0xf4, 0x42, 0xaf, 0x0d, 0x30, 0x70, 0xb8, 0x2e, 0x7b, 0x38, 0xdd, 0xa5, 0x5c, 0x2c, 0xb4, 0xc1, 0xe1, 0x2a, 0x8b, 0x3d, 0xec, 0x82, 0x57, 0x5e, 0x21, 0xa7, 0xb1, 0x7b, 0xe8, 0xfd, 0xec, 0xf5, 0x91, 0xce, 0xca, 0x5c, 0x34, 0xe4, 0x74, 0xaf, 0xcf, 0x68, 0xf4, 0x79, 0xd3, 0x2f, 0x31, 0xe4, 0x46, 0x4f, 0xbe, 0x8e, 0xad, 0x80, 0xeb, 0xd9, 0x08, 0x9e, 0x8d, 0x0d, 0x1b, 0xe7, 0x29, 0x1b, 0xa0, 0x52, 0x19, 0x86, 0x58, 0x4f, 0xde, 0xcc, 0x69, 0x4d, 0xda, 0xd5, 0x40, 0x21, 0x9b, 0x3b, 0x8b, 0x94, 0x28, 0xe0, 0x68, 0xc5, 0x5e, 0x40, 0xd1, 0xc8, 0x9b, 0x79, 0xd2, 0x53, 0x06, 0x5f, 0x70, 0x21, 0xc1, 0xa4, 0x84, 0x50, 0xc8, 0x24, 0x32, 0x85, 0x64, 0x98, 0x90, 0x48, 0x65, 0x12, 0xe9, 0x72, 0x39, 0x90, 0x11, 0x52, 0xb1, 0x4c, 0x3a, 0x40, 0xc0, 0x6d, 0x2a, 0x10, 0x8b, 0x06, 0x70, 0x44, 0x44, 0xac, 0x74, 0xda, 0x1b, 0x70, 0xec, 0x3c, 0x24, 0x1b, 0x58, 0x11, 0xad, 0x49, 0x8d, 0x3b, 0xb2, 0x5f, 0x45, 0x23, 0xce, 0x29, 0x1b, 0x51, 0xff, 0x27, 0x7a, 0xa2, 0xff, 0x4f, 0xf4, 0xc4, 0xf8, 0x9f, 0xe8, 0x89, 0xff, 0x3f, 0xd1, 0x13, 0xb4, 0x9c, 0x45, 0x97, 0x6b, 0x04, 0x85, 0x5f, 0x40, 0x62, 0xdc, 0xf8, 0xd6, 0xb8, 0x26, 0xa2, 0x33, 0x08, 0xb9, 0x4c, 0x3e, 0x32, 0xa1, 0x09, 0xa4, 0x08, 0x33, 0xf4, 0x22, 0x62, 0x8a, 0xde, 0x60, 0xbe, 0xc1, 0xc7, 0xc0, 0xe4, 0xc5, 0x2f, 0x1e, 0x35, 0x10, 0xeb, 0x56, 0x0f, 0xcf, 0x52, 0xec, 0xd7, 0xac, 0x19, 0x19, 0x9e, 0x3f, 0xaf, 0xb7, 0x07, 0x29, 0xc6, 0x0d, 0x75, 0xd3, 0x20, 0x2d, 0x0c, 0xe7, 0x86, 0xb2, 0x33, 0xd2, 0x9d, 0xc9, 0x26, 0xe4, 0xe8, 0x8a, 0x88, 0x1b, 0xef, 0xa2, 0x12, 0x8f, 0x1f, 0x86, 0xf5, 0x5d, 0xfe, 0x4a, 0x2a, 0xa6, 0xef, 0x22, 0x27, 0x67, 0xc1, 0x44, 0xb0, 0xcb, 0x45, 0x82, 0x47, 0x45, 0xbc, 0xf8, 0xc6, 0x14, 0x7b, 0x0d, 0x96, 0xf1, 0xee, 0xe9, 0x7a, 0x74, 0xbb, 0x47, 0x2a, 0x4a, 0x66, 0x45, 0x6d, 0x28, 0x5c, 0x54, 0xa8, 0x31, 0x94, 0x94, 0x3b, 0x6f, 0x5f, 0x4f, 0x46, 0x4b, 0x72, 0x5a, 0x46, 0x4b, 0x95, 0x21, 0x52, 0x54, 0x94, 0x54, 0xd5, 0x1b, 0x36, 0x50, 0x62, 0x8d, 0x2a, 0x1f, 0x28, 0xed, 0xb9, 0xde, 0x9c, 0x05, 0x59, 0x95, 0x6b, 0x3b, 0xb3, 0x53, 0x8a, 0xbb, 0x8b, 0x6c, 0x75, 0xce, 0x50, 0x4b, 0x38, 0x79, 0xed, 0xa6, 0x49, 0x01, 0xa8, 0x72, 0xf6, 0xce, 0xfa, 0xe9, 0x4a, 0x83, 0x55, 0xb6, 0x40, 0x20, 0x13, 0x30, 0x32, 0x61, 0x8b, 0xa1, 0xc4, 0xa9, 0x77, 0xa8, 0x75, 0x66, 0x97, 0x82, 0xfa, 0x56, 0x91, 0x3b, 0xad, 0xc5, 0x8f, 0xc3, 0x4e, 0xa9, 0x42, 0x65, 0x75, 0xde, 0x8a, 0x91, 0xd6, 0x2c, 0x89, 0xd3, 0x95, 0x9d, 0x09, 0x85, 0x69, 0x6b, 0xa8, 0x26, 0x5d, 0x66, 0x54, 0x4b, 0x2d, 0x6e, 0x83, 0x44, 0x62, 0x8f, 0xce, 0x2e, 0xcd, 0x9a, 0x5e, 0x68, 0x17, 0x9b, 0x33, 0x8a, 0x1d, 0xcd, 0x5d, 0xe3, 0x62, 0x57, 0x05, 0xa3, 0xa2, 0xaf, 0xa4, 0xaa, 0xb5, 0x22, 0xad, 0x44, 0xa2, 0x15, 0x2d, 0xb4, 0x24, 0x89, 0x98, 0x3e, 0xb3, 0x0a, 0xc7, 0x51, 0x69, 0x61, 0x8f, 0x89, 0x96, 0xd0, 0x22, 0xa8, 0x5b, 0xaf, 0xc0, 0x3e, 0xa0, 0x7b, 0xe8, 0xc5, 0x7c, 0x0e, 0x80, 0x07, 0xe9, 0x23, 0xf4, 0x2c, 0xc2, 0x48, 0x44, 0x90, 0x6e, 0x8d, 0xdc, 0x66, 0x28, 0xcd, 0x65, 0x74, 0x6b, 0xbf, 0x8f, 0xd3, 0xad, 0x51, 0xb0, 0xff, 0x98, 0x6a, 0x0d, 0x70, 0x1c, 0xe6, 0xcb, 0x68, 0xd6, 0x9a, 0x85, 0x03, 0x0b, 0x16, 0x19, 0x5a, 0xb2, 0x73, 0x9a, 0x75, 0x0b, 0x17, 0x0c, 0x0c, 0xea, 0xa6, 0x87, 0x42, 0xad, 0xe0, 0xd1, 0x82, 0x25, 0x07, 0x67, 0xeb, 0xa1, 0x46, 0xdd, 0xa4, 0x9f, 0x7d, 0xe7, 0xe2, 0x82, 0xfc, 0xc5, 0x77, 0xcc, 0xd2, 0x35, 0x87, 0x72, 0xa6, 0xeb, 0x67, 0xdd, 0xb9, 0x38, 0xff, 0x80, 0xe5, 0x85, 0xdb, 0xef, 0x78, 0xd1, 0xec, 0x0b, 0x06, 0x7d, 0xe6, 0x97, 0x6e, 0xbf, 0xfd, 0x79, 0x8b, 0x3f, 0x2f, 0x0f, 0x94, 0x2e, 0x3b, 0xb1, 0xab, 0x4d, 0xe6, 0xc9, 0xcc, 0xf4, 0xc8, 0xdb, 0xae, 0x3b, 0xb1, 0x6c, 0xe8, 0xd9, 0xeb, 0xda, 0xe5, 0xfe, 0x9c, 0x1c, 0xbf, 0xbc, 0x6d, 0xd7, 0xb3, 0x48, 0x8f, 0xee, 0x80, 0xf2, 0xe5, 0x0f, 0x98, 0x2f, 0x21, 0x6f, 0x4a, 0x23, 0x96, 0x93, 0x23, 0x51, 0x0d, 0xd2, 0xa3, 0x75, 0x40, 0xc4, 0xd4, 0x43, 0x5d, 0xba, 0x10, 0xea, 0xd2, 0x34, 0x1f, 0xdc, 0xe8, 0xdf, 0xd0, 0xa5, 0x1d, 0x13, 0x74, 0x69, 0x8e, 0xd2, 0xfc, 0x87, 0x1a, 0x74, 0x8a, 0x63, 0xca, 0xd4, 0x7f, 0xb4, 0x87, 0x88, 0x8c, 0xfd, 0x3b, 0x2a, 0xff, 0xe4, 0x16, 0xcd, 0xff, 0xc1, 0x16, 0xb9, 0x41, 0x27, 0xff, 0x87, 0xfb, 0xf8, 0x9f, 0xec, 0x1e, 0x4f, 0xa2, 0x6c, 0x43, 0x4b, 0x16, 0x2e, 0x98, 0x3b, 0xbb, 0xbf, 0x6f, 0x66, 0x77, 0x7b, 0x6b, 0x4b, 0x53, 0x6d, 0x75, 0x69, 0x71, 0x7e, 0xd8, 0x69, 0x70, 0x99, 0x2e, 0x63, 0xe8, 0x10, 0x4c, 0xb4, 0x73, 0x8c, 0xcb, 0x01, 0x72, 0x91, 0xd8, 0x34, 0x1c, 0x65, 0xe3, 0x4d, 0x1c, 0x61, 0xfe, 0x92, 0x1d, 0xdf, 0x9c, 0xbd, 0xc9, 0x99, 0x38, 0x6a, 0x37, 0xec, 0x5a, 0x1d, 0x6c, 0x9f, 0xde, 0xd2, 0x31, 0x27, 0x9f, 0x95, 0x3b, 0x6c, 0x76, 0x52, 0xac, 0x0c, 0xf9, 0x83, 0x01, 0xce, 0xc2, 0x71, 0x8b, 0x7f, 0x65, 0xdb, 0xc0, 0xde, 0x1e, 0x5f, 0xee, 0xfc, 0x3b, 0xe6, 0xce, 0xbc, 0xad, 0xb8, 0xf8, 0xd6, 0xde, 0x79, 0xb7, 0xcf, 0xcf, 0xf5, 0xf5, 0xec, 0x9d, 0x3f, 0x63, 0xc4, 0xef, 0x15, 0x6b, 0xa4, 0x52, 0x93, 0x2d, 0x3d, 0x75, 0xce, 0xe2, 0x15, 0x6b, 0x1b, 0x17, 0xd8, 0xf6, 0xdb, 0x16, 0x34, 0xae, 0x5d, 0xb1, 0x78, 0x4e, 0x4a, 0xc0, 0x66, 0x92, 0xca, 0x54, 0x12, 0x7a, 0x34, 0x3d, 0x2f, 0xbd, 0xb6, 0x62, 0xcd, 0x16, 0xce, 0xba, 0xf1, 0x90, 0x46, 0x28, 0x72, 0xba, 0xe8, 0xcc, 0x54, 0x6c, 0xdc, 0x28, 0xcb, 0xb2, 0x44, 0x7d, 0xb9, 0xd3, 0xf7, 0xbc, 0xb4, 0x64, 0xc3, 0xd9, 0x1b, 0xea, 0x4a, 0x0b, 0x0b, 0x4b, 0xeb, 0x6e, 0x38, 0xbb, 0x61, 0xc9, 0x4b, 0x7b, 0xa6, 0xe7, 0xfa, 0x66, 0x2b, 0xc5, 0x4a, 0x8d, 0xf2, 0xf8, 0xc9, 0x27, 0x9f, 0x6b, 0xa8, 0xad, 0x6d, 0x78, 0xee, 0xc9, 0x93, 0xc7, 0xe1, 0x57, 0xb1, 0x12, 0xdb, 0x32, 0x5a, 0xd8, 0x87, 0xa8, 0xeb, 0x68, 0x14, 0x69, 0xf8, 0xfd, 0xa8, 0x44, 0x79, 0x39, 0x5b, 0x86, 0xe3, 0xa2, 0xb6, 0x8c, 0x29, 0x4d, 0x13, 0x8e, 0xab, 0x35, 0x4d, 0x38, 0x2e, 0x6a, 0x9a, 0xb8, 0x54, 0x5f, 0x26, 0x1a, 0x0f, 0x62, 0x96, 0x03, 0x3b, 0xb6, 0x1c, 0x08, 0x2e, 0x19, 0x63, 0x28, 0x6e, 0x36, 0x20, 0x67, 0xd8, 0x52, 0x67, 0x3f, 0xbc, 0xbe, 0xba, 0x6c, 0xe3, 0x89, 0xd5, 0xcb, 0x8e, 0x94, 0xe7, 0x8a, 0xd5, 0x2a, 0x95, 0x33, 0xaf, 0x21, 0x52, 0xbb, 0xbc, 0xce, 0xe3, 0xab, 0x5f, 0x52, 0x6e, 0x0f, 0xa4, 0x68, 0x04, 0xf5, 0xa9, 0xe6, 0x75, 0x52, 0x2a, 0x96, 0xb6, 0xa4, 0x3a, 0x5a, 0x2b, 0x45, 0x5a, 0x0d, 0xce, 0x5b, 0xb2, 0x67, 0x76, 0x36, 0xb2, 0x18, 0x14, 0x25, 0xdb, 0xf0, 0xbc, 0x1a, 0xc0, 0x63, 0x24, 0x43, 0x46, 0xe0, 0xa6, 0x4e, 0x8d, 0x26, 0x73, 0xc1, 0xc1, 0x3b, 0x70, 0x98, 0xd8, 0x1e, 0xde, 0xc3, 0x79, 0x0c, 0xcf, 0xae, 0xb7, 0xe7, 0x91, 0x0c, 0xab, 0x26, 0x23, 0xfb, 0xf7, 0x73, 0x76, 0xc7, 0x6e, 0xfa, 0x76, 0xf2, 0x47, 0x82, 0x5d, 0x90, 0x5e, 0x7a, 0x89, 0x74, 0xa2, 0x86, 0x83, 0x91, 0xe9, 0x51, 0x4a, 0x46, 0x40, 0x33, 0x73, 0xd1, 0xb5, 0x26, 0xa7, 0x35, 0x0e, 0xa0, 0xcb, 0x4d, 0x4b, 0xec, 0x39, 0x85, 0x5f, 0xd2, 0x45, 0xe1, 0x58, 0xb4, 0x28, 0x07, 0x03, 0xd1, 0xd2, 0x13, 0x15, 0xab, 0x75, 0x3e, 0x9f, 0xcf, 0x85, 0x5c, 0x2e, 0xc0, 0x84, 0x50, 0x6a, 0xcc, 0x84, 0xef, 0x28, 0x70, 0x09, 0xa8, 0xd6, 0xd9, 0x03, 0x26, 0x93, 0xdf, 0xa6, 0xd5, 0xda, 0xfc, 0x26, 0x53, 0xc0, 0xae, 0x03, 0xdb, 0xb5, 0x36, 0xf8, 0x24, 0x80, 0x9e, 0x70, 0xff, 0x8e, 0x1e, 0x21, 0x2b, 0x47, 0x5f, 0x66, 0x24, 0xa6, 0x74, 0x9b, 0x46, 0x63, 0x4b, 0x37, 0x25, 0x05, 0xec, 0x5a, 0xad, 0x3d, 0x90, 0x34, 0xe1, 0x3b, 0x18, 0xd8, 0x82, 0xc6, 0xd2, 0x0e, 0xc7, 0xf2, 0x63, 0xc1, 0xaf, 0xe0, 0x58, 0x5c, 0x50, 0x23, 0x59, 0x72, 0xd2, 0x8a, 0xa2, 0xa9, 0xf0, 0xc8, 0x38, 0x33, 0x0d, 0xb0, 0x2b, 0x24, 0x49, 0x90, 0x23, 0x02, 0xdc, 0x6d, 0x11, 0x23, 0x84, 0x5c, 0x6e, 0x00, 0x23, 0xcb, 0x13, 0x7e, 0xc4, 0x8f, 0x91, 0x93, 0x04, 0xf7, 0x3b, 0xe8, 0x43, 0x11, 0x9e, 0xc1, 0xf4, 0x9e, 0x68, 0x92, 0xc7, 0x0d, 0x88, 0x60, 0xa6, 0x3b, 0xdf, 0x93, 0xef, 0x72, 0x18, 0xe1, 0x39, 0xe6, 0x31, 0x8d, 0x12, 0x5d, 0xba, 0x76, 0x42, 0xec, 0x66, 0xb7, 0x93, 0xc2, 0x52, 0x85, 0xc1, 0x18, 0xe6, 0xdc, 0xc0, 0xa8, 0x09, 0x05, 0xde, 0x41, 0xc3, 0x4d, 0x18, 0x3e, 0x08, 0x3f, 0xf7, 0xd1, 0x0d, 0x32, 0xa3, 0x70, 0x83, 0x18, 0xfe, 0xb7, 0x51, 0x68, 0x94, 0xed, 0xd8, 0x98, 0x38, 0x0d, 0xf0, 0x5f, 0x66, 0xc7, 0xc4, 0x71, 0xd7, 0x03, 0xa9, 0x58, 0xb0, 0x4d, 0x2c, 0x93, 0x89, 0xb7, 0x31, 0x12, 0xf6, 0xd8, 0xc4, 0x9f, 0xe3, 0x6b, 0xfb, 0x27, 0xbc, 0xb6, 0xd9, 0x44, 0x1b, 0x37, 0x11, 0x26, 0x01, 0x5c, 0x40, 0xe4, 0xed, 0x06, 0x98, 0x91, 0x09, 0xcb, 0x9b, 0x92, 0xf0, 0x53, 0xe2, 0x0a, 0xf7, 0xe1, 0x15, 0x86, 0x53, 0xa0, 0x4c, 0xf3, 0x65, 0x06, 0x7c, 0xd9, 0x69, 0xd9, 0x68, 0x9d, 0x85, 0x28, 0x00, 0xc3, 0xe5, 0xd6, 0x19, 0xcd, 0x02, 0xd6, 0x7b, 0xae, 0x64, 0xb5, 0xd9, 0x3a, 0x78, 0x56, 0x6e, 0x5b, 0x61, 0xb4, 0x2a, 0x3f, 0xbb, 0xfc, 0x92, 0xff, 0xc0, 0x60, 0xfa, 0x52, 0x8e, 0xbc, 0x7a, 0xc1, 0x85, 0xb7, 0xd8, 0x7b, 0xc0, 0x46, 0xe2, 0x00, 0x61, 0x20, 0xfa, 0x31, 0xbe, 0xee, 0xa4, 0x0c, 0x2f, 0xbc, 0x05, 0xfd, 0x4b, 0x83, 0x18, 0xe8, 0xce, 0xc0, 0xfb, 0x83, 0x10, 0xf3, 0xc7, 0xb2, 0xf5, 0x0d, 0x93, 0x38, 0xae, 0x3e, 0x05, 0xf7, 0x35, 0x4e, 0xc2, 0x17, 0x2b, 0x00, 0x4f, 0x7b, 0xec, 0xf7, 0x9e, 0xe7, 0x51, 0x84, 0x68, 0x15, 0x23, 0x49, 0x4a, 0x0f, 0x39, 0x23, 0x53, 0x04, 0x5c, 0xfb, 0xd9, 0xbf, 0xfe, 0x72, 0x72, 0x62, 0xb8, 0xe3, 0x03, 0x2f, 0x71, 0x61, 0xd4, 0x5a, 0x92, 0x14, 0x8a, 0xce, 0x26, 0xe4, 0x6f, 0x71, 0x1a, 0xf6, 0x71, 0x25, 0x71, 0xd7, 0xff, 0x0f, 0x7d, 0xcc, 0xb3, 0x4f, 0xee, 0xe3, 0x0f, 0xaf, 0x03, 0x59, 0x13, 0xe3, 0x56, 0xdf, 0xf5, 0xda, 0x84, 0x3e, 0x12, 0x87, 0xd8, 0x7b, 0xc8, 0x16, 0xe2, 0x16, 0x42, 0x47, 0x94, 0xc3, 0x6e, 0x51, 0x18, 0x8d, 0xe4, 0xc0, 0xee, 0x62, 0xf0, 0xc7, 0x11, 0x44, 0x51, 0x30, 0x3a, 0x6c, 0x98, 0xc2, 0xee, 0x44, 0xf0, 0xb4, 0x8c, 0xc4, 0xbc, 0x79, 0x62, 0xcf, 0x7b, 0x9e, 0x73, 0xdb, 0x9d, 0x2a, 0x04, 0x80, 0x41, 0x33, 0x65, 0xc0, 0xb7, 0x50, 0x86, 0x98, 0x19, 0x28, 0xef, 0xd0, 0xd1, 0x60, 0xd4, 0x96, 0x25, 0x97, 0x99, 0x05, 0xca, 0x8c, 0xc6, 0xb4, 0x86, 0x85, 0x51, 0xeb, 0x2d, 0x6f, 0x34, 0x75, 0x2a, 0x14, 0x49, 0x2d, 0x5a, 0x4d, 0x5e, 0xf7, 0xaa, 0xb2, 0xb1, 0x3e, 0x44, 0x60, 0x1f, 0xea, 0x4e, 0x4a, 0xd1, 0xea, 0xf2, 0x7d, 0xd0, 0xa0, 0x2c, 0x8a, 0xd4, 0x08, 0xda, 0x95, 0x63, 0xb3, 0x62, 0x80, 0x0f, 0x01, 0xe7, 0xdf, 0x3c, 0x42, 0x8c, 0xfd, 0xd0, 0x13, 0x45, 0xce, 0x29, 0x3a, 0x42, 0x07, 0x3b, 0x83, 0x62, 0x34, 0xb8, 0x27, 0x75, 0xa5, 0x04, 0x80, 0xda, 0x89, 0x7d, 0x89, 0x0c, 0x25, 0xf6, 0xa5, 0x1f, 0xc9, 0xc7, 0xec, 0x41, 0xba, 0x8f, 0x96, 0xc0, 0xb3, 0x13, 0x25, 0x4e, 0x72, 0x02, 0x8f, 0x36, 0x1d, 0x48, 0x84, 0xd1, 0x1c, 0x52, 0x2a, 0x49, 0x02, 0x02, 0xa9, 0x18, 0xc1, 0xf5, 0x91, 0x26, 0x0c, 0x9f, 0x8a, 0x26, 0x3c, 0xe5, 0xb1, 0x8f, 0xc1, 0x78, 0x60, 0x2e, 0x29, 0x21, 0x60, 0xa4, 0x02, 0x94, 0xb3, 0x8e, 0x06, 0x28, 0x1e, 0xc7, 0x3c, 0x42, 0x22, 0x11, 0xf6, 0xa2, 0x98, 0x5e, 0x5c, 0x6c, 0x04, 0xc4, 0x67, 0x86, 0x01, 0x0e, 0xd1, 0x75, 0xf1, 0x2a, 0xd8, 0xf5, 0x12, 0xd5, 0x23, 0x26, 0x54, 0xeb, 0x89, 0x6a, 0x4a, 0x8b, 0xc3, 0xb9, 0xd9, 0x41, 0x97, 0xcf, 0xad, 0xd6, 0xb8, 0xf4, 0x2e, 0x1c, 0x20, 0x8a, 0x71, 0x72, 0x90, 0xda, 0x31, 0xbf, 0x46, 0xe4, 0xa4, 0x9c, 0x37, 0xe6, 0xbb, 0x8b, 0x43, 0xaf, 0xe1, 0xf0, 0x47, 0x89, 0x11, 0x9a, 0xc3, 0x11, 0xba, 0xef, 0x76, 0x4d, 0xcd, 0xf0, 0x3d, 0x0b, 0xae, 0x39, 0x51, 0xeb, 0x13, 0x25, 0x19, 0x34, 0x81, 0x8a, 0x39, 0x55, 0x8b, 0x47, 0x8c, 0xec, 0x03, 0x60, 0x95, 0x6d, 0x5a, 0x43, 0x67, 0x6e, 0xff, 0x81, 0x85, 0x91, 0x92, 0x95, 0x0f, 0x0e, 0x0e, 0x5c, 0x1f, 0x96, 0xc8, 0x7c, 0x6b, 0x76, 0xb0, 0x2f, 0x65, 0xae, 0xac, 0xdc, 0x75, 0x00, 0x05, 0xeb, 0x6f, 0xdb, 0x18, 0x6e, 0x2b, 0x1b, 0xac, 0xf5, 0x95, 0x86, 0x9b, 0x15, 0xc6, 0x64, 0x7b, 0x72, 0x5d, 0xe9, 0xa6, 0xd3, 0x29, 0x81, 0x14, 0x45, 0xe1, 0xc8, 0x63, 0x2b, 0x06, 0x8e, 0x6e, 0xae, 0x2d, 0x2b, 0x28, 0xf3, 0x9e, 0xcf, 0x26, 0x3f, 0x09, 0x67, 0x3e, 0x7a, 0x4f, 0xeb, 0xaa, 0x5a, 0x7b, 0x45, 0x29, 0xd2, 0x51, 0x6a, 0xd9, 0xdd, 0xa2, 0x79, 0x82, 0x87, 0x08, 0x2f, 0xe3, 0x21, 0x37, 0x13, 0x67, 0x81, 0x80, 0x99, 0x4f, 0x12, 0x84, 0xe8, 0x69, 0x82, 0x3c, 0x7e, 0xf6, 0x78, 0x28, 0x9d, 0x18, 0x5f, 0xc6, 0x4b, 0x7e, 0x9d, 0x58, 0x06, 0x4c, 0x55, 0xc6, 0x4f, 0x3e, 0x71, 0xd9, 0x32, 0x01, 0xf2, 0x85, 0xcb, 0x96, 0xc9, 0x24, 0x6f, 0xbe, 0x6c, 0x7f, 0xd2, 0xc9, 0xb7, 0x2e, 0xdb, 0x8e, 0x8f, 0x3c, 0x72, 0xd9, 0x76, 0xb2, 0xc8, 0x8f, 0x2e, 0xdb, 0x4e, 0x1a, 0xf9, 0x78, 0x62, 0x19, 0x8a, 0x2f, 0x03, 0xc9, 0x43, 0xac, 0x0c, 0xc2, 0xa8, 0xb5, 0xc3, 0xcd, 0x12, 0x8d, 0x96, 0x14, 0x70, 0x19, 0x29, 0x05, 0x34, 0x49, 0x0b, 0x48, 0x64, 0x90, 0xa3, 0x18, 0x84, 0x5a, 0x63, 0x08, 0x91, 0x90, 0x81, 0x7a, 0xbe, 0x90, 0xa0, 0x05, 0xc2, 0xb1, 0xe4, 0xdd, 0x70, 0x1f, 0x79, 0x9c, 0x01, 0x9f, 0x0f, 0xa1, 0xd8, 0x50, 0x98, 0x37, 0x6d, 0xae, 0xa6, 0x94, 0x74, 0x85, 0x72, 0xe8, 0x14, 0x12, 0x5f, 0xc9, 0x91, 0xb4, 0xd3, 0xe1, 0xca, 0x24, 0xc9, 0x89, 0x68, 0x36, 0x26, 0x22, 0x48, 0xa4, 0xfa, 0x58, 0x43, 0xe7, 0x0c, 0x5b, 0xba, 0x98, 0xf2, 0x0d, 0xf7, 0x56, 0x6d, 0xfd, 0xf5, 0xaf, 0xfc, 0x11, 0x88, 0x6f, 0xba, 0x09, 0x88, 0xff, 0xf8, 0xca, 0xf5, 0xf5, 0xe8, 0x1b, 0xfb, 0xaf, 0x9b, 0x6e, 0x62, 0xff, 0x85, 0xbe, 0xcd, 0xac, 0xdc, 0xf8, 0xc4, 0xbb, 0x5f, 0xee, 0xd8, 0xf1, 0xe5, 0xbb, 0x4f, 0x6c, 0xac, 0x4c, 0xfc, 0x4c, 0xfd, 0x2a, 0x25, 0x2f, 0x1a, 0xec, 0xab, 0xcf, 0xc8, 0x68, 0xbf, 0xa6, 0xbe, 0x7a, 0xa4, 0x25, 0x5d, 0x65, 0xf5, 0x9b, 0xef, 0x55, 0x17, 0x38, 0x1e, 0x9c, 0x57, 0x5b, 0x90, 0x1a, 0x72, 0x6a, 0xeb, 0x8b, 0x0a, 0x1a, 0xc8, 0x3b, 0x50, 0xa3, 0xaf, 0xa2, 0x46, 0x5f, 0x4d, 0x68, 0x94, 0x7f, 0xe1, 0xf8, 0x46, 0x7f, 0xce, 0x7d, 0xfe, 0xf9, 0x97, 0xbf, 0xf5, 0x59, 0x1d, 0x35, 0x4b, 0x6b, 0x2b, 0xae, 0xe9, 0xce, 0xcd, 0x6a, 0x5f, 0x5b, 0x13, 0xec, 0xee, 0xe8, 0xc8, 0x60, 0xbf, 0x49, 0x4d, 0x26, 0x1f, 0xad, 0x6d, 0x30, 0xa6, 0x45, 0xec, 0x55, 0x4d, 0x2d, 0xc4, 0x84, 0xb9, 0xf7, 0x50, 0x0f, 0x10, 0x67, 0xd1, 0xfe, 0x03, 0xd7, 0xc4, 0xd7, 0x40, 0x15, 0x5b, 0xcb, 0xa7, 0xc9, 0x29, 0xd7, 0xcb, 0x4b, 0xbb, 0xf8, 0x3a, 0x6f, 0x27, 0xd6, 0xe1, 0xca, 0x3e, 0x0d, 0xa6, 0xde, 0xbb, 0xd4, 0xe3, 0x53, 0xbd, 0xe7, 0x92, 0x75, 0x02, 0xd4, 0x89, 0xab, 0xae, 0x93, 0x39, 0xf5, 0x7b, 0x2e, 0x39, 0x9e, 0x74, 0xea, 0xd1, 0xab, 0x7e, 0x8f, 0x8f, 0x7a, 0xf2, 0xaa, 0xdf, 0x93, 0x45, 0xbd, 0x76, 0xd5, 0xef, 0x49, 0xa3, 0x0e, 0x4f, 0x55, 0x87, 0x3b, 0x23, 0x4f, 0x53, 0xb1, 0x3a, 0x70, 0xff, 0x4f, 0x3c, 0x2b, 0xc3, 0xc4, 0xdd, 0x51, 0xe9, 0xdc, 0x99, 0xa4, 0x48, 0x0a, 0x8f, 0x8c, 0x04, 0x29, 0x25, 0xc8, 0x52, 0x96, 0x4b, 0x88, 0xa4, 0x12, 0xa9, 0x48, 0x32, 0x76, 0x72, 0xa4, 0x22, 0x46, 0xda, 0x45, 0x48, 0xe0, 0xb9, 0x91, 0xd0, 0x03, 0x32, 0x31, 0xc9, 0x08, 0x01, 0xf2, 0x51, 0x4f, 0x3c, 0x42, 0x96, 0x68, 0xde, 0xa4, 0x6a, 0x0c, 0xae, 0x38, 0x10, 0xaf, 0x08, 0xc4, 0xa8, 0x9e, 0x78, 0x5c, 0xbd, 0x9e, 0xa8, 0xa1, 0xa3, 0x63, 0xc5, 0xb2, 0x85, 0x0b, 0x3a, 0xe6, 0x75, 0xcc, 0x83, 0x24, 0x97, 0x3b, 0x87, 0xf2, 0xff, 0xc4, 0x39, 0xc4, 0xd1, 0xd3, 0x23, 0x42, 0x94, 0xef, 0x26, 0x0b, 0x88, 0xc1, 0xbf, 0x7f, 0x2a, 0xe9, 0xdf, 0x5e, 0xec, 0x54, 0xb2, 0x1f, 0x9f, 0xa6, 0x16, 0x49, 0x53, 0xac, 0x16, 0x51, 0x2e, 0x70, 0x92, 0x29, 0xff, 0x1f, 0x9d, 0x51, 0xea, 0x97, 0xe7, 0xd3, 0xc8, 0x3d, 0x99, 0x39, 0xf3, 0x24, 0x62, 0x20, 0x95, 0x95, 0x03, 0x11, 0xfb, 0xb3, 0x84, 0x43, 0x3b, 0xe1, 0xcc, 0x7e, 0x8b, 0xf7, 0x84, 0x1f, 0x04, 0x2e, 0xb5, 0xf7, 0xa0, 0x10, 0x81, 0x82, 0x6c, 0xcc, 0x60, 0xce, 0x42, 0xe6, 0x9b, 0x14, 0x35, 0x20, 0x71, 0x67, 0xc1, 0x58, 0xc4, 0xa7, 0x44, 0x8f, 0x60, 0x30, 0xe3, 0x2c, 0x73, 0xf6, 0xdb, 0x62, 0x58, 0x92, 0x24, 0x06, 0x2f, 0x7c, 0x4e, 0x87, 0x99, 0x7b, 0xa1, 0x8c, 0x9d, 0x8d, 0x32, 0x20, 0x19, 0x79, 0x2f, 0x37, 0x3e, 0x9d, 0x36, 0x4d, 0xa3, 0xf8, 0x74, 0x63, 0x61, 0x53, 0xd1, 0x9d, 0x99, 0xdb, 0x69, 0x49, 0xe2, 0x54, 0x07, 0x81, 0x64, 0xb2, 0x3b, 0x14, 0x0a, 0x69, 0x34, 0x11, 0x7d, 0xcd, 0x07, 0xbf, 0x22, 0x2b, 0xaa, 0x86, 0xa7, 0x07, 0xd2, 0x9b, 0x86, 0x2a, 0xaa, 0x96, 0x37, 0xfb, 0x3d, 0xf5, 0x4b, 0x8d, 0x6a, 0xbb, 0x32, 0xb8, 0xa6, 0xb2, 0xe3, 0xee, 0x35, 0x95, 0xb5, 0x3b, 0x9e, 0x5b, 0xb1, 0xfc, 0xe9, 0x6b, 0x2b, 0xdb, 0x66, 0x82, 0x85, 0xca, 0xb0, 0xf3, 0xc9, 0xdb, 0x32, 0x9a, 0x87, 0xca, 0xaa, 0x96, 0x35, 0xfa, 0xb2, 0x5a, 0x96, 0x14, 0x56, 0x2c, 0x6f, 0x0e, 0x90, 0x7f, 0x37, 0xa4, 0xe9, 0x52, 0x1d, 0xf9, 0x43, 0xf7, 0x2f, 0x1a, 0x78, 0x62, 0x4b, 0x5d, 0xf5, 0xb6, 0xe7, 0x87, 0xe7, 0xbc, 0xb7, 0x1e, 0x7c, 0xa4, 0xb3, 0x70, 0x7a, 0x60, 0x1f, 0x1c, 0x47, 0x80, 0x79, 0x1c, 0xeb, 0x0a, 0x35, 0x44, 0x59, 0xb4, 0x38, 0x1f, 0xc7, 0x99, 0x8a, 0x39, 0x7a, 0x09, 0x01, 0x6f, 0xb3, 0x5d, 0x8a, 0x3d, 0xd9, 0x30, 0x1e, 0x7d, 0x88, 0x6e, 0xd4, 0xeb, 0x00, 0x51, 0x5e, 0x96, 0x9d, 0x95, 0x6a, 0xd5, 0x79, 0xf5, 0x5e, 0x6e, 0x54, 0xa2, 0x29, 0x46, 0xa5, 0x4d, 0xd0, 0x81, 0xd1, 0x73, 0x6d, 0x0a, 0x48, 0x1c, 0x5e, 0xa2, 0x8b, 0x81, 0x80, 0xac, 0xac, 0x5c, 0x52, 0xef, 0xc9, 0x6c, 0x5e, 0x54, 0x58, 0xb4, 0xa8, 0x39, 0xc3, 0x53, 0xbf, 0xa4, 0xb2, 0xb4, 0xbf, 0x34, 0xd5, 0x52, 0x34, 0xab, 0x72, 0xda, 0xec, 0x22, 0x4b, 0x6a, 0x69, 0xff, 0x5d, 0xbb, 0xf6, 0xd4, 0x6c, 0x3b, 0x31, 0x34, 0x74, 0x62, 0x5b, 0xcd, 0x9e, 0x5d, 0x2b, 0x86, 0xcb, 0x56, 0x3d, 0x30, 0x6f, 0xde, 0x03, 0xab, 0xca, 0x86, 0xf7, 0x78, 0xaa, 0xe6, 0x14, 0x54, 0x2f, 0x28, 0x4f, 0x4d, 0x2d, 0x5f, 0x50, 0x5d, 0x30, 0xa7, 0xca, 0x43, 0xef, 0x71, 0x47, 0xdb, 0x83, 0xc8, 0x15, 0xc2, 0x5f, 0xd5, 0x93, 0x15, 0x6c, 0x8f, 0xba, 0x47, 0x7f, 0xe9, 0x7b, 0xe6, 0x86, 0x59, 0x07, 0x97, 0x16, 0x16, 0x2e, 0x3d, 0x38, 0xeb, 0x86, 0x67, 0x7c, 0xfe, 0x47, 0x56, 0x4d, 0xdf, 0x33, 0x58, 0x58, 0x38, 0xb8, 0x67, 0xfa, 0xaa, 0x47, 0x30, 0x5d, 0xe8, 0xb8, 0xf0, 0x37, 0xe6, 0x5f, 0xcc, 0x31, 0x5e, 0x67, 0xaa, 0x82, 0xb2, 0xf6, 0x11, 0xce, 0x30, 0x85, 0x1c, 0xbd, 0x08, 0x21, 0xb2, 0x89, 0x0a, 0x09, 0x91, 0x58, 0x28, 0xe2, 0x4e, 0x2e, 0x72, 0x2d, 0x58, 0x8a, 0x22, 0x09, 0x31, 0xbd, 0x02, 0x2e, 0x88, 0x0c, 0xef, 0xc2, 0x92, 0x7e, 0xa9, 0xe2, 0xb0, 0x20, 0xaa, 0x83, 0xcd, 0x08, 0x43, 0x02, 0x9c, 0xc3, 0x66, 0x62, 0x69, 0xb4, 0x73, 0x62, 0xb4, 0x61, 0x72, 0x0d, 0xe4, 0xf1, 0xda, 0xd9, 0xee, 0xf3, 0xb9, 0x9d, 0x1e, 0xa4, 0x8a, 0xa1, 0xb8, 0x69, 0x13, 0x55, 0x31, 0x61, 0xee, 0x78, 0xd3, 0x43, 0x42, 0xe8, 0x8b, 0xcb, 0x94, 0x24, 0x7f, 0x95, 0xd7, 0xbd, 0x26, 0x5a, 0xb5, 0xa6, 0x33, 0x3b, 0xbb, 0x73, 0x4d, 0x55, 0xd5, 0x9a, 0xae, 0xec, 0x83, 0x96, 0xcc, 0x52, 0xa7, 0xb3, 0x14, 0x01, 0x1e, 0xd1, 0xbf, 0x99, 0x16, 0x72, 0xaf, 0x34, 0x16, 0xfa, 0xe2, 0x16, 0xa6, 0x6e, 0xca, 0xe2, 0xae, 0x12, 0x54, 0xbc, 0xc4, 0x85, 0x8a, 0xd3, 0xe5, 0xed, 0x3b, 0x7b, 0xb3, 0xb3, 0x7b, 0x77, 0xb6, 0x77, 0xee, 0xea, 0xcf, 0xce, 0xee, 0xdf, 0xd5, 0x19, 0xed, 0x89, 0x24, 0x25, 0x45, 0x7a, 0xa2, 0xa5, 0x5d, 0x61, 0x93, 0x29, 0xdc, 0x05, 0xd6, 0x73, 0x11, 0x2f, 0x40, 0x5b, 0x42, 0xc1, 0x60, 0x10, 0x15, 0xec, 0x46, 0x05, 0xbb, 0xa3, 0x65, 0x9d, 0xa8, 0x60, 0x27, 0xb6, 0x5b, 0x6f, 0x64, 0x8f, 0x81, 0xed, 0xb4, 0x08, 0x67, 0x6b, 0x42, 0x59, 0x86, 0x79, 0x45, 0x69, 0x79, 0x03, 0x0e, 0xd6, 0x80, 0xe6, 0x78, 0x11, 0xe7, 0x64, 0xa9, 0xd3, 0x20, 0x9c, 0x9e, 0x81, 0x81, 0xe7, 0x56, 0xab, 0x46, 0xd6, 0xe8, 0x10, 0x72, 0x88, 0xa5, 0x12, 0xc8, 0x23, 0x9a, 0x87, 0x87, 0x21, 0x49, 0x16, 0x5c, 0x07, 0xff, 0xd0, 0x1f, 0xeb, 0x9c, 0xc1, 0x64, 0x4b, 0xd0, 0xa5, 0xd7, 0xbb, 0x82, 0x96, 0xe4, 0xa0, 0x53, 0xc7, 0x1e, 0x23, 0x2d, 0x19, 0x9d, 0x5d, 0xfd, 0xa1, 0x50, 0x7f, 0x57, 0x67, 0xc6, 0xe8, 0xef, 0xe9, 0x5f, 0xa3, 0xa7, 0xa8, 0x54, 0x32, 0x57, 0x0a, 0xd3, 0x9a, 0x6a, 0x6a, 0x27, 0xd9, 0x24, 0x58, 0x44, 0x09, 0xc1, 0xa3, 0x50, 0x6c, 0xdf, 0x49, 0x88, 0x21, 0x97, 0x7a, 0x81, 0xda, 0xc9, 0x91, 0x15, 0xf8, 0xfb, 0x34, 0x6a, 0x13, 0x59, 0x8b, 0x7f, 0x7f, 0x6c, 0xca, 0xdf, 0xe7, 0xc1, 0xfa, 0x11, 0xc1, 0x3c, 0xf8, 0xfb, 0x23, 0x53, 0xfe, 0xde, 0x0e, 0x37, 0xe6, 0x2a, 0xe6, 0x10, 0xe1, 0x05, 0xc7, 0xc9, 0x39, 0xf0, 0x91, 0x17, 0x3c, 0x72, 0xe1, 0x1f, 0xe4, 0x69, 0x20, 0x04, 0x47, 0x29, 0x04, 0x13, 0x12, 0x43, 0x6e, 0xf8, 0x02, 0x79, 0x3a, 0x5e, 0x1e, 0xce, 0x12, 0xd3, 0xc5, 0x97, 0xcf, 0xe7, 0xcb, 0xff, 0x13, 0x97, 0x7f, 0x94, 0x92, 0x4d, 0x51, 0xbe, 0x03, 0x96, 0xdf, 0xc1, 0x3c, 0x84, 0xcb, 0xff, 0x3f, 0xea, 0xde, 0x03, 0x3e, 0xae, 0xe2, 0xea, 0x1b, 0xbe, 0x33, 0xb7, 0x6d, 0x97, 0xb6, 0xef, 0x6a, 0xb5, 0xab, 0xed, 0xbb, 0xea, 0x65, 0xb5, 0x5a, 0x75, 0xad, 0x7a, 0xb7, 0x2d, 0xb9, 0xca, 0x96, 0x71, 0x91, 0x2c, 0x37, 0xd9, 0x96, 0x85, 0x71, 0xa1, 0xd8, 0xd8, 0x18, 0x07, 0x30, 0xc5, 0x04, 0x43, 0x4c, 0x09, 0x21, 0x81, 0x90, 0xd0, 0x42, 0xc7, 0xa1, 0x85, 0x84, 0x38, 0x84, 0x04, 0x42, 0x48, 0x9e, 0x40, 0x08, 0x49, 0x48, 0x20, 0x10, 0x9e, 0x02, 0x21, 0x05, 0x02, 0x09, 0xd6, 0xd5, 0x77, 0x66, 0xee, 0xee, 0xaa, 0x78, 0x6d, 0xcb, 0x79, 0xf2, 0x7e, 0xbf, 0xf7, 0xb5, 0x2d, 0xaf, 0xf6, 0x4e, 0xb9, 0x33, 0x67, 0x66, 0xce, 0x9c, 0x33, 0x73, 0xce, 0xff, 0xec, 0xa4, 0xf9, 0xef, 0x61, 0x4c, 0x89, 0xfa, 0x3d, 0xb3, 0xf3, 0x13, 0x5f, 0x08, 0xe9, 0x76, 0xb6, 0x0c, 0xe8, 0x1f, 0x62, 0x6a, 0xc8, 0xbd, 0x2f, 0x83, 0x78, 0x96, 0x47, 0x24, 0xb6, 0x07, 0xe6, 0x59, 0xbc, 0x15, 0xe6, 0x2a, 0x65, 0x0f, 0x5b, 0x53, 0x8e, 0x87, 0x63, 0x4c, 0x4f, 0x18, 0xe4, 0xca, 0x6c, 0x7a, 0x34, 0xc4, 0xa7, 0x94, 0x0f, 0xae, 0x01, 0x45, 0xac, 0x16, 0x1d, 0x37, 0x05, 0x2b, 0x64, 0x45, 0xb3, 0x86, 0x86, 0x2d, 0x73, 0xf7, 0x2c, 0xdf, 0x58, 0xd7, 0x77, 0xe5, 0x50, 0x65, 0x61, 0xcb, 0x82, 0x8e, 0x32, 0xde, 0x74, 0xb9, 0x96, 0xcb, 0x6d, 0xe9, 0x6b, 0x2b, 0x71, 0x57, 0x75, 0x17, 0xd4, 0x2e, 0xaa, 0x09, 0x6a, 0x1d, 0x19, 0xd2, 0xc6, 0xd9, 0xc3, 0xf6, 0x73, 0x67, 0x34, 0x64, 0x2d, 0x58, 0xb8, 0xbb, 0xbb, 0x67, 0x53, 0x4f, 0x45, 0x71, 0x89, 0xe7, 0xd8, 0xdd, 0x25, 0x4d, 0xe5, 0x45, 0x35, 0x0b, 0xaa, 0x73, 0x9b, 0x8b, 0xec, 0x4e, 0xaf, 0xd3, 0xce, 0xb1, 0x53, 0xd9, 0x89, 0x13, 0x6d, 0x09, 0xbd, 0x17, 0x81, 0xb5, 0xaf, 0x78, 0x7d, 0xc6, 0xda, 0xdf, 0xc2, 0xec, 0x67, 0x9e, 0x92, 0xd7, 0x7f, 0x6a, 0x41, 0xab, 0x91, 0xa8, 0x41, 0xb3, 0x97, 0xb4, 0x16, 0x11, 0x16, 0xa0, 0x02, 0x51, 0x60, 0x1a, 0x0f, 0x28, 0x3e, 0x63, 0x11, 0xba, 0xa6, 0x49, 0x39, 0x61, 0x85, 0x0a, 0x25, 0xf8, 0x40, 0x24, 0x4d, 0x89, 0x34, 0x9c, 0x60, 0x46, 0x29, 0x62, 0x4d, 0xba, 0xf7, 0xe2, 0x1d, 0xe3, 0x9b, 0x36, 0xac, 0x3e, 0x6f, 0x1a, 0x4f, 0xd0, 0xfd, 0x5b, 0x79, 0x42, 0xe0, 0xff, 0x77, 0x9e, 0xc1, 0xee, 0x3a, 0x6b, 0x8d, 0xff, 0x3e, 0xb6, 0xd2, 0x70, 0x76, 0xb6, 0xc3, 0xa0, 0x49, 0x49, 0xba, 0x0f, 0xb4, 0x70, 0x22, 0x1f, 0x08, 0x8f, 0xc2, 0x0e, 0x5f, 0x92, 0xcf, 0x46, 0xf4, 0x3e, 0xcc, 0x71, 0xfc, 0x89, 0xcf, 0xde, 0x27, 0xfa, 0xd7, 0x7a, 0xb6, 0x08, 0x4f, 0xf0, 0xcf, 0x30, 0x0a, 0x26, 0x87, 0x89, 0xc0, 0x2e, 0xba, 0x2a, 0x3e, 0xa8, 0x27, 0xd8, 0x34, 0x5d, 0x04, 0x5c, 0x5a, 0xe0, 0x58, 0x61, 0x4c, 0x9d, 0x30, 0x8d, 0x18, 0x22, 0x0e, 0x8b, 0x04, 0x9c, 0x0b, 0x24, 0x41, 0x81, 0x51, 0x0a, 0xca, 0x55, 0x14, 0x27, 0x91, 0x40, 0x2b, 0xad, 0x13, 0x7b, 0xa2, 0xe5, 0x1e, 0x37, 0xc8, 0x35, 0x4c, 0x43, 0x5d, 0x79, 0x7b, 0xb4, 0x3d, 0x3f, 0xd7, 0x1d, 0xf1, 0x44, 0x08, 0xc0, 0xba, 0xc5, 0xa4, 0xcc, 0x51, 0xe5, 0x88, 0xa0, 0xbc, 0x21, 0x85, 0x8c, 0x60, 0x5b, 0x41, 0xaf, 0x35, 0xcd, 0xb1, 0x84, 0x19, 0x30, 0x3d, 0x6c, 0x2b, 0xa6, 0xf8, 0xb5, 0x66, 0x9f, 0xde, 0x38, 0x7b, 0x13, 0x9e, 0x1d, 0x61, 0xee, 0xfe, 0xfc, 0x40, 0x28, 0xb7, 0xb0, 0x30, 0xbf, 0x36, 0xa0, 0xcf, 0x0b, 0x19, 0xc4, 0x05, 0xbd, 0x46, 0x55, 0x3d, 0xaf, 0x55, 0x3b, 0xfb, 0x2e, 0xaf, 0x5a, 0xd3, 0x11, 0x0e, 0x77, 0xac, 0xa9, 0xaa, 0x5c, 0xd3, 0x9e, 0x9b, 0xdb, 0xbe, 0x26, 0xd6, 0xb6, 0x67, 0x20, 0x12, 0x19, 0xd8, 0xd3, 0xd6, 0xba, 0x67, 0x79, 0x24, 0xb2, 0x7c, 0x0f, 0x6c, 0xac, 0x59, 0x59, 0xee, 0x3d, 0x4f, 0xe8, 0x5d, 0xb9, 0xf6, 0x50, 0x8d, 0x4e, 0xa5, 0xbf, 0xc2, 0xb0, 0x4d, 0xc5, 0x1f, 0x41, 0x07, 0xa5, 0x50, 0x5e, 0xe7, 0xda, 0xca, 0xaa, 0xb5, 0x5d, 0x79, 0x79, 0x5d, 0x6b, 0xab, 0x2a, 0xd7, 0x76, 0xe6, 0xe1, 0x45, 0x91, 0x81, 0x5d, 0x2d, 0x2d, 0x20, 0xbf, 0x95, 0x2f, 0x23, 0x9f, 0x03, 0x11, 0x99, 0xe7, 0x5c, 0x84, 0xd7, 0xe2, 0x6d, 0xac, 0x82, 0x60, 0x58, 0xa2, 0x41, 0x59, 0xd7, 0x1d, 0x4c, 0xca, 0xf3, 0x8f, 0xe2, 0x11, 0xf6, 0x35, 0xfc, 0x31, 0x2b, 0xa0, 0x6e, 0x66, 0x76, 0xda, 0x3e, 0x3c, 0xc2, 0x7f, 0x1b, 0x7f, 0x0c, 0xe5, 0x7a, 0x4e, 0x29, 0x37, 0x8a, 0xca, 0xd1, 0x8b, 0xdc, 0x0a, 0xe0, 0x5b, 0x4d, 0x20, 0x68, 0x3f, 0x26, 0xa7, 0x3e, 0x06, 0xa9, 0x90, 0x76, 0x18, 0xd2, 0xde, 0xa7, 0x69, 0xcd, 0x8c, 0x7e, 0x56, 0x1a, 0x94, 0xc3, 0x9f, 0x01, 0xcf, 0x0a, 0xc9, 0xe5, 0x48, 0x79, 0x54, 0x0d, 0x79, 0x32, 0x93, 0x79, 0x88, 0xb6, 0x31, 0x55, 0x0f, 0x6b, 0xa5, 0x79, 0x69, 0x3d, 0xa4, 0x3e, 0xd4, 0x78, 0x9a, 0xbc, 0x3b, 0x71, 0x06, 0x7a, 0x8c, 0x9d, 0x0f, 0x6d, 0x9d, 0xaf, 0x9d, 0xdd, 0xd6, 0xef, 0xa1, 0x7c, 0xf6, 0x87, 0x5c, 0x39, 0xf4, 0x71, 0x1e, 0x73, 0x2e, 0x69, 0xbb, 0xd1, 0xdf, 0x39, 0x37, 0xbb, 0x06, 0xd2, 0x06, 0xf9, 0x19, 0x69, 0xc0, 0x97, 0x6f, 0x85, 0x49, 0xfa, 0x35, 0xfe, 0x5e, 0x90, 0x62, 0xec, 0xc4, 0xcb, 0x93, 0x9a, 0x35, 0x11, 0x11, 0x8e, 0x4d, 0xa2, 0xbf, 0x25, 0xb7, 0xc7, 0x61, 0xb2, 0x3d, 0x5a, 0x4c, 0x1a, 0x15, 0x01, 0x78, 0x31, 0x90, 0xed, 0x31, 0xa2, 0x37, 0x46, 0x8c, 0xa7, 0xec, 0x8c, 0xdf, 0xcb, 0x3a, 0xe8, 0xf8, 0xdd, 0x6c, 0xe6, 0x8a, 0x6b, 0xd1, 0x26, 0xe9, 0xa6, 0x89, 0x13, 0x7c, 0xf0, 0x54, 0x1e, 0x8a, 0x99, 0x25, 0x93, 0x7f, 0x66, 0x63, 0xd0, 0x06, 0x23, 0x13, 0x25, 0x36, 0x33, 0x51, 0x72, 0xcd, 0xd6, 0x95, 0x3c, 0xbc, 0xe4, 0x51, 0x02, 0xae, 0x92, 0x02, 0x10, 0xe2, 0x41, 0x68, 0xd5, 0x28, 0x96, 0x85, 0x62, 0x7f, 0x96, 0x6d, 0x4a, 0x28, 0x46, 0xb3, 0x94, 0x98, 0x69, 0x60, 0x73, 0x64, 0xcf, 0x98, 0x42, 0xa2, 0x8b, 0xa1, 0x8f, 0x92, 0x1b, 0x72, 0xa2, 0x31, 0xa8, 0xd0, 0xdd, 0xbb, 0x7c, 0x03, 0xdd, 0x31, 0x72, 0x3a, 0x76, 0x0d, 0x64, 0x1f, 0x32, 0x35, 0x6d, 0x5f, 0x54, 0xe2, 0xae, 0xec, 0x2e, 0x88, 0xf4, 0xc7, 0x4b, 0x0d, 0xdc, 0xd7, 0xa0, 0xa1, 0xd9, 0xd3, 0x1b, 0xfe, 0xf9, 0x5b, 0xce, 0xf2, 0xb0, 0xb5, 0xa8, 0x7f, 0x47, 0x6b, 0xd3, 0xfa, 0x35, 0x9b, 0x5a, 0x4f, 0x7c, 0x5a, 0xb6, 0x78, 0xe1, 0xf2, 0x58, 0x5e, 0x53, 0xb1, 0xdd, 0xe2, 0xf6, 0x69, 0x29, 0xcd, 0x57, 0x4e, 0x3a, 0xd1, 0x7f, 0x0a, 0x83, 0x30, 0x8e, 0x0b, 0xc4, 0x0e, 0x99, 0xe6, 0x1d, 0xc9, 0xf1, 0x68, 0x87, 0xb4, 0xff, 0xa0, 0x69, 0x7d, 0x19, 0xb3, 0xd3, 0xbe, 0x37, 0xe9, 0xc5, 0x35, 0x42, 0x27, 0xa4, 0xf5, 0x9f, 0x92, 0xd6, 0x31, 0xe9, 0x45, 0xdf, 0xa7, 0x69, 0x0b, 0xb5, 0xb3, 0xd3, 0x3a, 0xa1, 0x5c, 0x80, 0xa6, 0x2d, 0x52, 0xcc, 0x4e, 0xeb, 0x81, 0xb4, 0x10, 0x4d, 0x5b, 0xac, 0x99, 0x95, 0x36, 0xf9, 0x21, 0x08, 0xf2, 0x04, 0x63, 0x5d, 0x44, 0x0f, 0x21, 0x82, 0xe1, 0x45, 0xf2, 0xaf, 0x9b, 0x3c, 0x22, 0x44, 0xf9, 0xd7, 0x40, 0xb6, 0x20, 0x92, 0xc3, 0xa5, 0xf2, 0xc9, 0xd3, 0xa5, 0xa4, 0x04, 0x62, 0xd6, 0xc3, 0xbe, 0x7f, 0x94, 0xfa, 0xbd, 0x95, 0xc5, 0x8b, 0x2b, 0xbc, 0x59, 0x02, 0x47, 0xdd, 0x82, 0x13, 0xee, 0xa6, 0x20, 0xe9, 0x13, 0x40, 0x6e, 0xd9, 0xb7, 0x4d, 0x56, 0x82, 0x62, 0x4c, 0x2c, 0xcf, 0x98, 0x97, 0xe7, 0x13, 0x55, 0x76, 0x72, 0xa8, 0x2a, 0xf8, 0x3c, 0xfa, 0x29, 0x3c, 0x51, 0x19, 0xe5, 0xdf, 0x43, 0x70, 0x20, 0x3d, 0xd1, 0x59, 0xc0, 0xa2, 0x66, 0xfe, 0x68, 0xd8, 0x2d, 0x89, 0xf3, 0x2e, 0x5b, 0x1d, 0x6d, 0xba, 0xe0, 0xae, 0x35, 0x6b, 0xee, 0xdc, 0xd9, 0x54, 0xb1, 0xea, 0xe0, 0x3c, 0x49, 0xf4, 0x84, 0xd0, 0x6d, 0xab, 0x8f, 0x6d, 0xaa, 0x5a, 0xfb, 0xd0, 0xdf, 0x8e, 0x5c, 0xff, 0xb7, 0x87, 0xd6, 0x56, 0x6d, 0x3e, 0xb6, 0xfa, 0xd1, 0xad, 0xae, 0x10, 0x7a, 0x41, 0x57, 0x3f, 0x74, 0xd5, 0xe0, 0xfa, 0xdb, 0x37, 0x55, 0x54, 0x6c, 0xba, 0x7d, 0xfd, 0xe0, 0x55, 0x43, 0xf5, 0x3a, 0xa9, 0x3a, 0xe4, 0xda, 0x7a, 0x97, 0x75, 0xf5, 0x95, 0xf7, 0x0f, 0xed, 0x7d, 0xfd, 0xd6, 0xa5, 0x4b, 0x6f, 0x7d, 0x7d, 0xef, 0xd0, 0xfd, 0x57, 0xae, 0xb6, 0xde, 0x45, 0xfa, 0xb9, 0x1c, 0xf4, 0x97, 0x3c, 0xe8, 0xbb, 0xc0, 0x5d, 0xf0, 0x31, 0xf9, 0x3e, 0x04, 0xdf, 0x23, 0xd0, 0x37, 0x81, 0xdb, 0x29, 0xd2, 0x79, 0x09, 0xfc, 0xf6, 0x59, 0x8a, 0x0b, 0x59, 0x45, 0xb0, 0x32, 0xa7, 0x43, 0x40, 0xea, 0x10, 0xc1, 0x80, 0x54, 0x81, 0xb0, 0xc2, 0xa8, 0x07, 0x32, 0x33, 0xb4, 0xac, 0x5a, 0x3d, 0x92, 0x84, 0x84, 0xe4, 0x38, 0xae, 0x8a, 0xab, 0xaa, 0x20, 0x30, 0xd4, 0xc5, 0x05, 0x79, 0x04, 0x18, 0xd2, 0xe3, 0xb3, 0xe8, 0x8d, 0x1e, 0xbf, 0x3e, 0x53, 0x4f, 0x41, 0x21, 0x59, 0x8f, 0x6c, 0x6f, 0x4b, 0x2e, 0x7c, 0x32, 0x90, 0x8f, 0xce, 0xd3, 0x98, 0x31, 0x09, 0x0a, 0xe9, 0x0d, 0xc2, 0x7c, 0x35, 0xfa, 0x68, 0x70, 0x0b, 0x19, 0xb1, 0xd6, 0x63, 0x84, 0x8c, 0x22, 0xcb, 0x46, 0xa5, 0x37, 0xea, 0x50, 0x18, 0xfd, 0x20, 0x83, 0x0f, 0xe7, 0x5d, 0x5a, 0x7f, 0xaf, 0x2b, 0x5b, 0x54, 0xfe, 0x57, 0xff, 0x80, 0xce, 0x96, 0x29, 0x72, 0xcd, 0xd2, 0x1b, 0x2d, 0x62, 0x99, 0x7b, 0x69, 0xff, 0x95, 0x39, 0x0e, 0x51, 0xf9, 0x62, 0xfd, 0x1f, 0x74, 0x90, 0xe7, 0x86, 0x56, 0xa1, 0xd8, 0x3d, 0xd0, 0x8f, 0xc2, 0xfd, 0x4b, 0xdd, 0x7e, 0xa1, 0x95, 0xdd, 0x2c, 0xbd, 0x88, 0x5e, 0x5c, 0x98, 0xe3, 0x92, 0x5e, 0x0c, 0x14, 0x34, 0x48, 0x2b, 0xb5, 0x56, 0x97, 0x01, 0xcd, 0x97, 0xbe, 0x11, 0xf6, 0xa2, 0x3b, 0xfd, 0x85, 0x0d, 0xa8, 0xb2, 0xdf, 0xed, 0x42, 0x83, 0xee, 0x80, 0xb4, 0x0a, 0x7d, 0x15, 0x28, 0xff, 0x0d, 0xd9, 0x0e, 0x64, 0xd2, 0xc2, 0xd9, 0x84, 0x8d, 0x8c, 0x80, 0x36, 0x7d, 0x42, 0xbe, 0xaf, 0x99, 0xb4, 0xb1, 0xef, 0x72, 0xef, 0xc3, 0xf7, 0xcd, 0xbf, 0x26, 0xdf, 0x09, 0x1e, 0xfd, 0x72, 0xfe, 0x3e, 0x2c, 0x20, 0x39, 0x2e, 0x51, 0x13, 0x7c, 0x27, 0x58, 0xe1, 0x5e, 0xa6, 0x2a, 0x5e, 0x91, 0x85, 0x18, 0x25, 0xf1, 0x46, 0x4b, 0xc4, 0xd8, 0x5c, 0x3b, 0x15, 0x33, 0x81, 0xdc, 0x90, 0x27, 0xc3, 0x25, 0x78, 0xdd, 0x26, 0x43, 0x32, 0x58, 0x42, 0xa6, 0x3a, 0x19, 0xa2, 0x40, 0x0e, 0x96, 0x40, 0x4e, 0x9e, 0xe1, 0x2f, 0x9a, 0x1d, 0xd9, 0x96, 0x0d, 0xa0, 0xa0, 0xf4, 0x30, 0xb4, 0xfe, 0xe1, 0x13, 0xf2, 0x07, 0x7c, 0x8d, 0x3c, 0x98, 0xed, 0x53, 0xa1, 0x5c, 0x93, 0x43, 0xa5, 0xc8, 0x32, 0xa2, 0x5c, 0x45, 0xc0, 0xf1, 0x10, 0x3e, 0x7f, 0xdb, 0xd6, 0x31, 0xe9, 0x4d, 0x14, 0x1c, 0xdb, 0xba, 0x8d, 0xcd, 0xcd, 0xb2, 0x4e, 0xfc, 0x3e, 0xaf, 0xdd, 0xe7, 0x6f, 0xcb, 0xc3, 0x6e, 0xa3, 0x2b, 0x81, 0xc9, 0x00, 0xed, 0xbd, 0xe0, 0xdf, 0x1d, 0xdb, 0xe1, 0x3f, 0xb1, 0x73, 0xe2, 0x0f, 0xa8, 0x4b, 0x7a, 0xe2, 0xb5, 0xef, 0x7f, 0x9f, 0x5d, 0x7b, 0x6a, 0x6c, 0x07, 0xa0, 0xdb, 0x30, 0xbc, 0xf7, 0x3a, 0x90, 0x1b, 0x05, 0xce, 0x54, 0x4c, 0x74, 0xe9, 0x01, 0x58, 0x57, 0x6a, 0x8a, 0xeb, 0xaf, 0x06, 0x49, 0xe0, 0x5c, 0x70, 0xfd, 0x73, 0x5c, 0xd9, 0x32, 0xaa, 0xbf, 0xc9, 0xa8, 0x27, 0x11, 0xb8, 0x4f, 0xc1, 0xf5, 0x4f, 0x81, 0xce, 0xce, 0xc0, 0xf5, 0xe7, 0xac, 0x11, 0xbc, 0xe3, 0xd8, 0x7d, 0xfa, 0x89, 0xbf, 0x60, 0x83, 0xe6, 0x9e, 0xaf, 0x3d, 0xcd, 0xf6, 0x5f, 0xae, 0x5a, 0x70, 0xe8, 0x99, 0x9d, 0x4f, 0xbf, 0x78, 0xec, 0x18, 0xba, 0x4c, 0x42, 0x0a, 0xed, 0x41, 0x7c, 0xe3, 0xc1, 0x8b, 0x26, 0xd1, 0xb6, 0x03, 0x17, 0x49, 0x37, 0x90, 0xf5, 0x26, 0x89, 0xec, 0xd1, 0x31, 0x74, 0xec, 0xd5, 0x17, 0x09, 0xdd, 0x96, 0x4e, 0xfe, 0x85, 0x3f, 0x8f, 0xfa, 0x08, 0x0f, 0xc6, 0x07, 0x40, 0x44, 0x61, 0xcb, 0x41, 0xf4, 0xc4, 0x5d, 0xa0, 0x8b, 0x10, 0x27, 0xe7, 0x31, 0x06, 0xc4, 0x50, 0x51, 0xa9, 0x18, 0xe3, 0x10, 0xa3, 0x45, 0xb4, 0x1b, 0x20, 0xb4, 0x80, 0x80, 0xaa, 0x14, 0xc4, 0xa1, 0xe4, 0x0d, 0xc4, 0x66, 0x62, 0x30, 0x4c, 0xbd, 0xae, 0x46, 0x55, 0x3d, 0x1d, 0xed, 0x8d, 0xf1, 0x86, 0xfa, 0xea, 0xca, 0x92, 0x62, 0xaf, 0xcf, 0xef, 0x33, 0xf8, 0xad, 0xfe, 0x4c, 0x22, 0x8d, 0x1a, 0x23, 0x32, 0x10, 0xf3, 0x34, 0x03, 0x07, 0x51, 0x0e, 0x5f, 0x21, 0x98, 0xb3, 0x91, 0x55, 0xfe, 0x55, 0xc6, 0x93, 0x9d, 0x06, 0xf5, 0x13, 0x82, 0x2e, 0x06, 0xcb, 0xc9, 0x29, 0x81, 0x6f, 0xe9, 0xcb, 0xd6, 0xcd, 0x5f, 0xff, 0xf5, 0xa1, 0xe6, 0x8b, 0xb7, 0x9d, 0xe7, 0x2f, 0x33, 0xd8, 0x8c, 0x99, 0x25, 0xdd, 0x9b, 0xba, 0x1e, 0xfe, 0x63, 0x18, 0x5f, 0xe4, 0xca, 0x9a, 0xf8, 0x16, 0x8b, 0x70, 0x91, 0x2b, 0x4b, 0xb2, 0x78, 0x7f, 0x77, 0xdf, 0xd2, 0xed, 0x5d, 0x25, 0x16, 0x8d, 0x45, 0xdb, 0xe4, 0x5b, 0xbb, 0xfb, 0x70, 0xf7, 0xe5, 0xaf, 0xdd, 0x36, 0x64, 0x7c, 0xf1, 0x59, 0xd8, 0xf7, 0xbe, 0xc3, 0x1e, 0xbe, 0xe4, 0xc7, 0xd7, 0xf4, 0xd8, 0x8b, 0x5b, 0x0b, 0x62, 0x26, 0xa3, 0x37, 0xec, 0xfd, 0xce, 0xf1, 0x3a, 0xa3, 0x3b, 0x18, 0xca, 0xc8, 0xd6, 0xc1, 0xe7, 0x89, 0x1f, 0x84, 0x8a, 0x43, 0x4a, 0x75, 0x4d, 0xa4, 0xaf, 0xd2, 0x39, 0xff, 0x86, 0x9f, 0xed, 0x1f, 0xd3, 0x65, 0x67, 0x98, 0x82, 0x46, 0x59, 0xe7, 0x21, 0x78, 0xc3, 0x56, 0xee, 0x3c, 0xca, 0x3f, 0xf6, 0x24, 0x2c, 0x16, 0x80, 0x71, 0xa8, 0xd5, 0x9a, 0x11, 0xca, 0x32, 0x08, 0xfb, 0x40, 0xea, 0x99, 0xd8, 0xb2, 0x09, 0x1e, 0xe2, 0x88, 0x07, 0x49, 0x4e, 0xcd, 0xf8, 0xd9, 0x32, 0x0e, 0xc4, 0x5d, 0x67, 0xe3, 0x37, 0x3e, 0x31, 0x22, 0xf3, 0x1b, 0x71, 0x1a, 0xbf, 0x69, 0x80, 0xc7, 0xd3, 0xf8, 0x4d, 0x4c, 0xe6, 0x37, 0x31, 0x19, 0xcb, 0xde, 0x48, 0x33, 0xe2, 0x5b, 0xff, 0xa3, 0xf2, 0x17, 0xe8, 0x51, 0x0d, 0x70, 0x92, 0xdd, 0xb1, 0x5b, 0x5d, 0x0e, 0x41, 0xf5, 0x56, 0xcb, 0x32, 0xca, 0x6d, 0xa2, 0xbf, 0x8c, 0x8a, 0x91, 0x9c, 0xe5, 0x9d, 0x97, 0xc0, 0x43, 0xcd, 0x77, 0x2a, 0xff, 0x83, 0x64, 0xb9, 0x2a, 0x46, 0x98, 0x4d, 0xfb, 0x9b, 0x8d, 0x4b, 0x72, 0xfc, 0x7c, 0x14, 0xdf, 0xfd, 0x11, 0xda, 0x07, 0xfc, 0xe4, 0x23, 0x7f, 0x61, 0xbd, 0xf4, 0x06, 0xe1, 0x34, 0xbf, 0x78, 0x0b, 0xf8, 0x4c, 0xd8, 0x5f, 0xd0, 0xf0, 0x11, 0x3c, 0x7e, 0xc7, 0x13, 0x90, 0xde, 0x40, 0xe1, 0xdc, 0x9c, 0xdf, 0x93, 0xb9, 0xbf, 0x63, 0xd2, 0xc2, 0x3e, 0xc5, 0x37, 0x82, 0x0c, 0x6c, 0x64, 0xda, 0x98, 0x96, 0x78, 0x63, 0x5b, 0x1d, 0x68, 0x85, 0x46, 0x98, 0xe6, 0x7c, 0x81, 0x1f, 0xc3, 0x94, 0xea, 0x62, 0x40, 0x49, 0x64, 0x29, 0x56, 0x38, 0x99, 0x5d, 0xd4, 0x14, 0x87, 0x45, 0x2b, 0xa7, 0x01, 0x12, 0x37, 0xc5, 0x89, 0xa3, 0x67, 0x28, 0x48, 0x4c, 0x9c, 0xe9, 0x11, 0x99, 0x8c, 0x4d, 0x1b, 0x62, 0xe5, 0x7d, 0x3f, 0x27, 0x61, 0x03, 0x08, 0xbd, 0x8f, 0x15, 0xa1, 0x50, 0x8c, 0x3a, 0xba, 0x59, 0x53, 0x31, 0x85, 0x22, 0xd3, 0x21, 0x57, 0x71, 0xf3, 0xa6, 0xa1, 0xec, 0x92, 0xa6, 0x60, 0x67, 0xa4, 0xbd, 0xd0, 0x64, 0x2f, 0xa8, 0xf5, 0x6d, 0xad, 0xdb, 0xbd, 0xba, 0x26, 0xb6, 0xee, 0xba, 0xa5, 0x99, 0xdb, 0x33, 0x37, 0x3c, 0x58, 0x9b, 0x67, 0x0e, 0xea, 0x42, 0x35, 0x7d, 0x55, 0x8e, 0xba, 0x9d, 0xe7, 0x55, 0x17, 0x9f, 0x77, 0xdd, 0xaa, 0xcc, 0xb1, 0xcc, 0xa5, 0x47, 0x4a, 0x0b, 0x15, 0xa6, 0x0c, 0xad, 0xb7, 0xb8, 0xb1, 0xe0, 0x72, 0xd5, 0xea, 0x16, 0x4f, 0xa9, 0x3f, 0x4b, 0x8d, 0x35, 0xbc, 0x2b, 0xbf, 0xc2, 0xe5, 0x2a, 0xf2, 0x39, 0xd4, 0x6e, 0x7d, 0x5e, 0xeb, 0x48, 0x6f, 0xfb, 0xc5, 0xab, 0x6a, 0x85, 0xd7, 0xdf, 0x50, 0x2f, 0x6a, 0x68, 0xf2, 0x65, 0xfb, 0x7d, 0x22, 0x1a, 0xce, 0xcc, 0x6f, 0xdf, 0xbc, 0xa0, 0x7a, 0xfb, 0xf2, 0x5a, 0xfe, 0x3f, 0x7e, 0xa9, 0x6e, 0x2e, 0xed, 0xd2, 0x66, 0x9a, 0xb3, 0xb3, 0x45, 0xe8, 0xe6, 0xee, 0xc9, 0x10, 0xfb, 0x22, 0xf7, 0x65, 0x46, 0x00, 0xad, 0x72, 0x6b, 0xdc, 0x18, 0xce, 0x21, 0x01, 0xcb, 0x73, 0x33, 0x04, 0x58, 0xda, 0xb8, 0x2b, 0x1b, 0xa4, 0xb4, 0x4e, 0x79, 0x42, 0x79, 0x88, 0x15, 0x25, 0xcc, 0x08, 0xe2, 0x28, 0x89, 0x79, 0x86, 0xc6, 0x0a, 0xc5, 0x40, 0x9a, 0x44, 0xe4, 0xba, 0x51, 0x72, 0x47, 0x7f, 0x96, 0x3c, 0xdb, 0x88, 0x1f, 0xb9, 0x32, 0x37, 0xa4, 0xcf, 0xd2, 0x3b, 0xa8, 0x21, 0x86, 0x6c, 0x9c, 0x07, 0x4a, 0x37, 0x25, 0x57, 0x3a, 0x1a, 0xb1, 0xb2, 0x59, 0x3e, 0xae, 0x3d, 0x74, 0xf0, 0xfc, 0x0d, 0xe8, 0x69, 0xf7, 0xfc, 0xa5, 0x2b, 0x8b, 0x74, 0x17, 0xe8, 0x96, 0x1c, 0xd9, 0x58, 0x53, 0x77, 0xc1, 0x03, 0xdb, 0x32, 0x2e, 0xc8, 0xe8, 0xbb, 0xb8, 0xac, 0x48, 0x6d, 0xd1, 0xeb, 0x9c, 0x81, 0xf2, 0x40, 0x49, 0x65, 0x77, 0xb9, 0x3f, 0x33, 0xd3, 0xac, 0x62, 0x2b, 0x32, 0x1f, 0xbb, 0xf1, 0xda, 0xfb, 0xec, 0x38, 0x58, 0xe1, 0x37, 0xb0, 0x3f, 0xf9, 0xa9, 0xba, 0xfb, 0x92, 0x7b, 0x37, 0xad, 0xfb, 0xd6, 0x65, 0xf3, 0xc5, 0x5f, 0xbc, 0xa1, 0x6e, 0x2c, 0x6c, 0xd2, 0x69, 0xf5, 0x56, 0xab, 0x30, 0x31, 0x9f, 0x73, 0xb8, 0xac, 0x6a, 0x35, 0xe5, 0xcf, 0xc0, 0x70, 0xd9, 0xbf, 0x70, 0xdb, 0x99, 0x12, 0x72, 0xbe, 0xa8, 0x05, 0x3e, 0x93, 0x1f, 0xc0, 0xa2, 0x30, 0x8d, 0xd1, 0x20, 0x86, 0x60, 0x75, 0x0f, 0x51, 0x03, 0x78, 0x99, 0x5f, 0x0a, 0x02, 0xe5, 0xd8, 0xc3, 0x62, 0x8f, 0x59, 0xef, 0xf7, 0x19, 0x03, 0x81, 0x80, 0x57, 0xbe, 0x98, 0x4a, 0x05, 0xb6, 0x49, 0xda, 0xd9, 0x10, 0x48, 0x16, 0x27, 0x4a, 0x86, 0xc3, 0xa1, 0xd1, 0x70, 0x3c, 0xd1, 0x7a, 0x12, 0x00, 0x06, 0x36, 0xa5, 0x45, 0x05, 0xab, 0x6e, 0xda, 0x7c, 0xe0, 0x81, 0xc2, 0x4a, 0xb5, 0x55, 0x97, 0x61, 0xb5, 0x39, 0x73, 0xab, 0xc3, 0xe3, 0x5b, 0xd0, 0x8d, 0x6e, 0xbb, 0x74, 0xd7, 0xf2, 0x96, 0x16, 0x1c, 0x3c, 0x39, 0x54, 0xbf, 0xe0, 0x26, 0x6c, 0xce, 0x0e, 0x6f, 0x5f, 0x74, 0xcb, 0x8e, 0xd6, 0x4b, 0xb7, 0x15, 0x2a, 0x34, 0x66, 0xb7, 0xdf, 0x6d, 0xbe, 0xe2, 0xe2, 0xf1, 0x2e, 0x93, 0x7b, 0xfc, 0x8a, 0xc5, 0xfa, 0x2f, 0x5f, 0xa8, 0x5b, 0xb1, 0xf0, 0xeb, 0xf2, 0x3e, 0xd3, 0x40, 0x94, 0x3d, 0xe0, 0x05, 0x7a, 0xe2, 0x45, 0xc0, 0x88, 0xb0, 0xaf, 0x30, 0xa2, 0x62, 0xcd, 0xa9, 0x1b, 0xce, 0x19, 0x77, 0x19, 0xfa, 0xd7, 0x17, 0x4d, 0xec, 0x32, 0xcf, 0xa2, 0x67, 0xa5, 0x66, 0xe4, 0x93, 0x7e, 0x7b, 0xe2, 0xa9, 0xa7, 0x58, 0x91, 0x7d, 0x46, 0xfa, 0xf1, 0xd1, 0x6f, 0x8f, 0xbe, 0xf2, 0xca, 0x28, 0xeb, 0xff, 0x9a, 0xfc, 0xce, 0x3e, 0xa0, 0xdd, 0xa7, 0x40, 0xbb, 0x2a, 0x66, 0x49, 0x7c, 0x61, 0x16, 0x12, 0xc4, 0x0a, 0xa0, 0x98, 0x06, 0x68, 0x97, 0x84, 0x1b, 0xe0, 0x29, 0x88, 0xc3, 0x10, 0x85, 0x1b, 0x60, 0x31, 0x47, 0xb6, 0x65, 0xac, 0x46, 0x48, 0x03, 0xca, 0xa6, 0x16, 0x04, 0x1c, 0xd9, 0x68, 0x80, 0x4a, 0x71, 0x55, 0x4c, 0x65, 0xa4, 0x94, 0xe0, 0x9d, 0x13, 0x72, 0x92, 0x78, 0x7e, 0x7a, 0xc2, 0x9d, 0x03, 0x49, 0x72, 0xcd, 0x0a, 0x1c, 0x34, 0x9b, 0xbe, 0x53, 0x21, 0x87, 0xc8, 0x39, 0x35, 0xfb, 0x29, 0x90, 0x6e, 0xfe, 0x4d, 0x37, 0xdd, 0xe4, 0xb6, 0xaf, 0x2c, 0x58, 0x7d, 0xd3, 0xa6, 0x03, 0x0f, 0x14, 0x55, 0xa9, 0x52, 0x34, 0x3e, 0x7f, 0x14, 0x5d, 0x2c, 0x1d, 0x44, 0x17, 0xbb, 0x6d, 0xd2, 0x33, 0xcb, 0xbd, 0xcd, 0xbe, 0x2f, 0x53, 0x2a, 0x36, 0x2d, 0xdf, 0x19, 0x34, 0xb9, 0xc3, 0x33, 0x29, 0xfd, 0x85, 0x4b, 0x72, 0x97, 0xef, 0x0c, 0x98, 0x3c, 0x19, 0x57, 0x2c, 0x52, 0x28, 0x80, 0x27, 0xc4, 0x80, 0xc6, 0x39, 0x40, 0x63, 0x9e, 0x9e, 0xf2, 0x35, 0xc7, 0xe3, 0x8c, 0x12, 0x26, 0x87, 0x92, 0x1d, 0x22, 0x3b, 0x26, 0xd9, 0x3c, 0x41, 0x8b, 0x5e, 0x33, 0xb5, 0xaf, 0x26, 0xf6, 0x4f, 0x8f, 0x7b, 0x6a, 0xff, 0x4c, 0x1b, 0x17, 0x87, 0xec, 0x9f, 0x72, 0x5c, 0x1c, 0x34, 0x3b, 0x2e, 0xce, 0x9f, 0xf6, 0xec, 0xd2, 0x4a, 0x97, 0xa0, 0xcb, 0x79, 0x77, 0xdd, 0xca, 0xf8, 0x21, 0x7c, 0xfc, 0x32, 0xfd, 0x2f, 0x7e, 0x74, 0xe8, 0xe9, 0x9d, 0x3b, 0x51, 0xe3, 0xe8, 0xed, 0x1b, 0x63, 0xc2, 0x7e, 0xf4, 0xd9, 0x92, 0xd5, 0x4f, 0x20, 0x6f, 0xa4, 0x25, 0xcf, 0x20, 0xbd, 0x7d, 0xf3, 0x37, 0xa5, 0x1b, 0xf0, 0xb7, 0x46, 0x51, 0x4b, 0x6e, 0xdf, 0x85, 0x0b, 0xc8, 0xfe, 0xb0, 0x0c, 0xda, 0x7b, 0xe0, 0xff, 0xe1, 0xfd, 0xe1, 0xb3, 0x0f, 0x63, 0x1f, 0xa0, 0xe7, 0x75, 0x42, 0x38, 0xef, 0xb2, 0xd8, 0xdd, 0x39, 0xc0, 0x3b, 0xfe, 0x1b, 0xf6, 0x07, 0x3b, 0xec, 0x0f, 0xe5, 0x1f, 0x54, 0x82, 0x30, 0x3a, 0xd0, 0x79, 0x05, 0xd9, 0x34, 0x5e, 0x8a, 0xfd, 0x9e, 0x64, 0x39, 0x5a, 0x09, 0xfb, 0xc3, 0xb2, 0xd6, 0x3f, 0xb5, 0x2e, 0x06, 0x59, 0x34, 0x36, 0xf7, 0xfd, 0x81, 0x9c, 0x4d, 0xc2, 0xfe, 0x70, 0x1b, 0xec, 0x0f, 0x02, 0xf7, 0xf5, 0x77, 0x64, 0x39, 0x3d, 0xc4, 0x3e, 0x4a, 0x78, 0x23, 0x77, 0xf7, 0xff, 0x24, 0x65, 0x29, 0x25, 0xcc, 0x73, 0x81, 0xfb, 0x86, 0x8d, 0x7c, 0x27, 0xf1, 0x39, 0x4a, 0x80, 0xae, 0x02, 0xda, 0xe6, 0x4b, 0x9c, 0x85, 0x72, 0x66, 0xf2, 0x9d, 0xfb, 0xa6, 0x99, 0x7c, 0x4f, 0xac, 0x0b, 0x2c, 0x70, 0xf7, 0x50, 0x59, 0x6c, 0x04, 0xd2, 0xe7, 0xd1, 0xf4, 0x7b, 0x8b, 0x65, 0xbd, 0xe0, 0x2f, 0xdc, 0x3a, 0x6e, 0x14, 0xca, 0x6f, 0xec, 0x26, 0xeb, 0x68, 0x09, 0xb3, 0x96, 0xfd, 0x3d, 0xa7, 0x01, 0xc9, 0xac, 0xad, 0xfb, 0xa1, 0x32, 0x62, 0xeb, 0x06, 0x8b, 0x95, 0xe8, 0xc9, 0x2c, 0xb3, 0x26, 0x19, 0x90, 0x71, 0x04, 0xc9, 0x98, 0x93, 0x2c, 0xe2, 0x16, 0x4f, 0x4b, 0xe5, 0x38, 0x02, 0xe0, 0xc7, 0xa1, 0x5e, 0x62, 0xea, 0x96, 0x42, 0x17, 0x46, 0x09, 0xd1, 0x96, 0xa2, 0xca, 0xfc, 0x5e, 0xba, 0xea, 0x0e, 0xe9, 0x6a, 0xb4, 0xe3, 0x0e, 0xb4, 0x13, 0x7d, 0x28, 0x5d, 0x80, 0x0e, 0x1f, 0x45, 0x87, 0xa5, 0x0b, 0xc8, 0xfc, 0xb8, 0x0c, 0xf5, 0xb2, 0x8f, 0xb3, 0x01, 0x8a, 0x5f, 0x4c, 0xf0, 0x63, 0x52, 0xe8, 0xc5, 0x08, 0x91, 0xf3, 0x57, 0x8a, 0x0d, 0xc8, 0x10, 0xf0, 0x57, 0x3d, 0x35, 0x9f, 0x49, 0x62, 0x16, 0x5f, 0xc6, 0x96, 0x9d, 0x7c, 0x85, 0xfc, 0xa0, 0x77, 0x6e, 0x44, 0x6f, 0x1c, 0x95, 0x65, 0x91, 0x63, 0xc8, 0xc9, 0x7e, 0x85, 0x1d, 0xa0, 0x77, 0x97, 0x2b, 0xe2, 0xcb, 0x4c, 0xc0, 0x76, 0x04, 0x10, 0x26, 0x4b, 0x10, 0x27, 0x7a, 0x81, 0x6f, 0x12, 0xd4, 0x7f, 0x58, 0x1d, 0x2c, 0x1e, 0x22, 0x60, 0x40, 0x24, 0x5e, 0xf7, 0x90, 0x12, 0x71, 0x02, 0xcf, 0x2d, 0x85, 0x77, 0x13, 0xa0, 0x17, 0xf2, 0x95, 0x23, 0xb7, 0x39, 0x1c, 0x3f, 0xdf, 0x99, 0x8d, 0x98, 0xf2, 0xb2, 0xfc, 0xdc, 0xa0, 0x3f, 0x3b, 0xe6, 0x8c, 0xd9, 0x2c, 0x19, 0x5a, 0x95, 0x82, 0x71, 0x20, 0x87, 0x2a, 0x11, 0x21, 0x29, 0x66, 0x9d, 0xe6, 0xae, 0x15, 0x92, 0x6f, 0xa6, 0x8c, 0xa7, 0x79, 0x8e, 0xdf, 0x72, 0x56, 0x14, 0xb8, 0x15, 0x59, 0xde, 0x6d, 0xb5, 0x25, 0x6b, 0xcf, 0x5b, 0x91, 0xeb, 0xac, 0x28, 0x74, 0x2b, 0x5c, 0xde, 0xb1, 0x78, 0xd9, 0xda, 0xf3, 0x06, 0x72, 0xcf, 0x90, 0x86, 0x3f, 0xd3, 0x9a, 0xcc, 0xca, 0x4b, 0x0a, 0xf3, 0x5d, 0x01, 0x97, 0xd6, 0x6c, 0x53, 0xed, 0x8e, 0x14, 0xc3, 0x6f, 0xd2, 0x93, 0x3a, 0x93, 0x85, 0x3e, 0xf5, 0xbb, 0xb4, 0x26, 0xfa, 0x34, 0x27, 0x90, 0x03, 0x64, 0xba, 0x8c, 0xd9, 0x08, 0xb4, 0x9c, 0x04, 0x09, 0x63, 0x8d, 0x0c, 0x62, 0xea, 0xa1, 0x30, 0x3c, 0xeb, 0x15, 0x48, 0x50, 0x29, 0x61, 0x53, 0x14, 0xf8, 0xb5, 0x1c, 0xb9, 0xb5, 0xa2, 0xec, 0x18, 0x76, 0x8f, 0x11, 0x91, 0xa2, 0x25, 0x11, 0xfc, 0xe3, 0x33, 0x64, 0x91, 0x21, 0xeb, 0x8c, 0x8c, 0x31, 0xc5, 0xab, 0xa9, 0xfe, 0x42, 0x7c, 0xd4, 0x44, 0x5f, 0xcc, 0x17, 0xf5, 0x10, 0xd3, 0x99, 0x58, 0x44, 0x8c, 0x98, 0xf1, 0x97, 0x17, 0xa1, 0x51, 0xa9, 0x67, 0xeb, 0x35, 0x5b, 0xff, 0x82, 0xaa, 0x16, 0x4b, 0xe3, 0xe8, 0xf5, 0x8d, 0x87, 0x37, 0xfe, 0x0c, 0x5f, 0xff, 0xe0, 0x8d, 0xef, 0xbe, 0x7b, 0xe3, 0x83, 0x47, 0xdf, 0x7c, 0xf3, 0xa8, 0xcc, 0xb3, 0xaf, 0x67, 0xbe, 0xc8, 0x6e, 0x63, 0x7f, 0xcf, 0xa8, 0x18, 0xe1, 0x51, 0xa5, 0x80, 0x4a, 0xf2, 0x81, 0x6d, 0xc3, 0x5e, 0x6c, 0x46, 0x51, 0x64, 0x46, 0x2f, 0x06, 0x23, 0x91, 0x20, 0xfa, 0xc6, 0x84, 0x17, 0xbf, 0x85, 0x97, 0x9b, 0x34, 0x1a, 0xd3, 0x2f, 0x1f, 0x46, 0xfb, 0xfa, 0xd0, 0xde, 0x87, 0xe4, 0x31, 0xbe, 0x13, 0xe6, 0xf5, 0x7e, 0xce, 0x0a, 0x23, 0x99, 0xc9, 0x38, 0xe3, 0x59, 0x09, 0xe7, 0x3a, 0x71, 0x06, 0x20, 0xb5, 0x0c, 0x6b, 0x1c, 0x23, 0xb6, 0x94, 0x14, 0x9c, 0x2c, 0x51, 0xf5, 0x9d, 0xf8, 0xad, 0x09, 0x2f, 0xaa, 0x30, 0x12, 0x8f, 0x27, 0x23, 0xfa, 0x26, 0x79, 0xc3, 0xd1, 0xa3, 0xf8, 0x6b, 0x0a, 0x0d, 0xcf, 0x6b, 0x15, 0x27, 0x1e, 0x42, 0x23, 0x7d, 0x68, 0xdd, 0x83, 0xe4, 0x1d, 0xc7, 0xe1, 0x1d, 0x37, 0x4e, 0x7f, 0x07, 0x0d, 0x7c, 0x73, 0xda, 0x77, 0x40, 0xe5, 0x01, 0x12, 0x33, 0x04, 0x85, 0x02, 0xd1, 0xe3, 0xe4, 0x1d, 0xf4, 0x3d, 0xdf, 0x30, 0x12, 0x37, 0x23, 0xa3, 0x34, 0x00, 0xef, 0x78, 0xa4, 0x4f, 0xba, 0xe5, 0xa1, 0x13, 0x0a, 0x2d, 0xcf, 0x6b, 0x14, 0x2f, 0x3c, 0x48, 0x69, 0xf0, 0x23, 0xa0, 0xc1, 0x37, 0x80, 0x06, 0x6a, 0xa0, 0x81, 0x1a, 0x13, 0x1a, 0x24, 0xaa, 0x09, 0x42, 0x2d, 0xec, 0x37, 0x48, 0x05, 0xa6, 0xc2, 0xac, 0xd2, 0x3c, 0x69, 0x00, 0xaf, 0xed, 0x93, 0xf6, 0x3f, 0xf4, 0x86, 0x3a, 0x43, 0x50, 0x59, 0x5e, 0x7f, 0x28, 0xc1, 0x53, 0xd9, 0x77, 0x38, 0x72, 0x8a, 0x9e, 0xc1, 0x94, 0xc6, 0x8b, 0x14, 0x22, 0xe6, 0x49, 0x48, 0x4a, 0xe2, 0xa2, 0x28, 0x90, 0x43, 0x58, 0x19, 0xfc, 0x3b, 0xd1, 0x50, 0xe0, 0x8c, 0x19, 0x5c, 0x06, 0x19, 0x36, 0x83, 0x3e, 0x53, 0xa9, 0x72, 0x24, 0xda, 0x3c, 0xb5, 0x3a, 0x97, 0xb1, 0x57, 0x9d, 0xdc, 0xc9, 0x5e, 0x95, 0x5c, 0xa1, 0xd0, 0xdc, 0x37, 0x8e, 0x12, 0x7d, 0xf3, 0x28, 0x5a, 0x2a, 0xdd, 0x43, 0xdb, 0x7a, 0x19, 0xfa, 0x90, 0x7d, 0x1c, 0xef, 0x4f, 0x60, 0x48, 0xcf, 0x5e, 0x99, 0x94, 0x10, 0xc6, 0xe4, 0x9a, 0xc4, 0xfb, 0x8f, 0xd2, 0x32, 0x07, 0x98, 0xb7, 0x69, 0x19, 0x8a, 0xef, 0xad, 0xa4, 0x26, 0x7c, 0xa4, 0xe0, 0xfa, 0xd9, 0xa8, 0xce, 0xd4, 0x62, 0x2e, 0x0d, 0xda, 0x73, 0x0a, 0xdf, 0x3b, 0x20, 0xe3, 0x3d, 0x3f, 0x7e, 0xf7, 0xc4, 0xf9, 0x6c, 0xe4, 0x6d, 0xa4, 0x49, 0xcc, 0xa1, 0xcd, 0xcc, 0x21, 0xf6, 0x25, 0xf6, 0x37, 0x94, 0x7e, 0x02, 0xa1, 0x5f, 0x40, 0x89, 0x42, 0x4a, 0x14, 0x53, 0x22, 0x2b, 0xec, 0xf3, 0x43, 0xd2, 0x93, 0xd2, 0xf1, 0x0d, 0xa8, 0x43, 0x3a, 0xb1, 0x01, 0xb6, 0xee, 0xea, 0x8d, 0xd2, 0x0f, 0x50, 0xfb, 0x15, 0xa8, 0x5d, 0x3a, 0xbe, 0x11, 0x75, 0xa0, 0xda, 0x8d, 0xd2, 0x09, 0x54, 0xbd, 0x5e, 0xfa, 0x81, 0xf4, 0xa4, 0x7c, 0xc7, 0xbb, 0x14, 0xd6, 0xcd, 0x3b, 0x30, 0xbe, 0x2c, 0x63, 0x65, 0xf2, 0x99, 0x60, 0xdc, 0x37, 0x85, 0x9d, 0x4e, 0x78, 0xc1, 0xaa, 0xd4, 0x45, 0x1a, 0xdb, 0x13, 0x0e, 0x87, 0x03, 0x53, 0xe8, 0xe9, 0xb3, 0xaf, 0x65, 0xf4, 0xb3, 0xbe, 0x2f, 0x65, 0x0f, 0x9f, 0xbc, 0x00, 0xb5, 0x1a, 0x7d, 0xc5, 0x0e, 0x47, 0xb1, 0xcf, 0x6c, 0x96, 0x3f, 0x8d, 0xb3, 0xbf, 0x03, 0xcd, 0xd0, 0xcd, 0xf0, 0x8b, 0xc9, 0x24, 0x3f, 0x20, 0x9f, 0x13, 0x97, 0xa4, 0x1e, 0x14, 0x91, 0x12, 0x45, 0x30, 0xee, 0x11, 0x18, 0xfc, 0x0d, 0xf8, 0x37, 0x54, 0x0a, 0xd8, 0x23, 0x7b, 0x6b, 0xfc, 0x1f, 0xda, 0x4b, 0x63, 0x5c, 0xac, 0x9c, 0x04, 0x18, 0x2b, 0xcc, 0x0b, 0xd3, 0x90, 0x1f, 0x3e, 0xa3, 0x67, 0xc6, 0x5e, 0x1a, 0x9b, 0xbe, 0x97, 0x8a, 0x74, 0xfb, 0x94, 0xb7, 0xd2, 0xd8, 0xa9, 0x5b, 0x29, 0xe4, 0xa6, 0xf9, 0xd0, 0xc1, 0x55, 0xd6, 0x55, 0x9f, 0xab, 0xb9, 0x6c, 0x67, 0xbe, 0xb5, 0xd1, 0x6c, 0xe0, 0xc5, 0x4d, 0x81, 0xb8, 0xd1, 0xce, 0x67, 0x9d, 0xe7, 0xe0, 0x7d, 0x96, 0x3a, 0x7f, 0xc4, 0x02, 0x8f, 0xce, 0xb3, 0x6e, 0x24, 0x19, 0x8a, 0x9d, 0x9c, 0xdb, 0xd2, 0x10, 0x58, 0xed, 0xaf, 0xb7, 0xd8, 0x39, 0x27, 0x7a, 0x60, 0xef, 0x7b, 0xb5, 0x66, 0xcb, 0xde, 0xac, 0x9c, 0x82, 0x1f, 0x7b, 0x6d, 0x47, 0xbe, 0x94, 0x6d, 0xfd, 0xb1, 0xdd, 0x5d, 0xb0, 0xb7, 0xd6, 0x62, 0xbe, 0xd9, 0x6a, 0x7b, 0xf1, 0x45, 0x87, 0xf9, 0x18, 0xd1, 0xd5, 0x99, 0xf3, 0x70, 0x2b, 0x5e, 0x09, 0xf3, 0x81, 0x8c, 0xdf, 0x69, 0xb7, 0xa8, 0x69, 0xfb, 0x93, 0x4f, 0x96, 0x32, 0xa3, 0x11, 0x73, 0x04, 0xb7, 0x7e, 0x77, 0xdb, 0x73, 0xcf, 0x6d, 0xfb, 0x2e, 0xca, 0x7b, 0xf9, 0xe5, 0x8d, 0x3f, 0xfe, 0x31, 0x99, 0x5f, 0xa3, 0x28, 0x0f, 0x36, 0x8e, 0x1b, 0xce, 0x36, 0xe7, 0x47, 0xd1, 0x09, 0xa9, 0x16, 0xdd, 0x20, 0xfb, 0x0d, 0x8d, 0x32, 0xaf, 0x42, 0x99, 0x0f, 0xcf, 0x75, 0x2f, 0x93, 0xeb, 0x20, 0x3f, 0x95, 0x1b, 0x90, 0x73, 0xbd, 0x3c, 0xbf, 0x97, 0x4d, 0x6e, 0x65, 0x3e, 0x65, 0x88, 0xfd, 0x04, 0xbc, 0xdf, 0x08, 0xe3, 0xd5, 0x9e, 0x04, 0x87, 0x07, 0x61, 0xd5, 0x6f, 0x25, 0xef, 0xb7, 0x06, 0xa9, 0x27, 0x51, 0x11, 0x4a, 0x3a, 0x50, 0x08, 0xe2, 0x06, 0xa5, 0xde, 0x6c, 0xe6, 0xdd, 0xa1, 0x3c, 0x5f, 0x5e, 0xb8, 0xae, 0xea, 0x62, 0x85, 0x5a, 0x61, 0xca, 0xf2, 0x98, 0x70, 0xb1, 0x3b, 0x27, 0xaf, 0x22, 0x50, 0x76, 0x41, 0x81, 0x5c, 0xf7, 0x92, 0xc9, 0x7f, 0x30, 0xff, 0x80, 0xba, 0xd5, 0x8c, 0x71, 0x2a, 0x52, 0x12, 0xb0, 0x0a, 0xbf, 0x97, 0xf2, 0xb4, 0x64, 0x6d, 0x30, 0x94, 0x4b, 0x8a, 0x7d, 0xde, 0xe2, 0x62, 0xaf, 0xaf, 0xb8, 0xcb, 0x57, 0x4c, 0x7f, 0xa5, 0xe5, 0x47, 0xa1, 0xb3, 0x95, 0x58, 0x0f, 0xab, 0xc5, 0x1f, 0xf7, 0xe8, 0x13, 0x96, 0x1d, 0x20, 0x78, 0x2e, 0xe5, 0x28, 0x98, 0x6f, 0x22, 0xca, 0x81, 0xc7, 0xe8, 0x33, 0x7b, 0x89, 0xe9, 0xad, 0x31, 0x61, 0xb7, 0x41, 0x6c, 0x6d, 0xe4, 0xe0, 0xa8, 0x3e, 0x7d, 0xa4, 0xcc, 0xfa, 0x8e, 0xa7, 0xb7, 0x2a, 0xbf, 0xb1, 0xc0, 0x7a, 0x65, 0x38, 0xd7, 0x59, 0x6e, 0xbd, 0xaa, 0x33, 0x4b, 0x8d, 0x9e, 0xf7, 0x15, 0xb8, 0xca, 0x3b, 0xf3, 0xa5, 0xc7, 0x51, 0x77, 0x5e, 0x91, 0xdd, 0x22, 0x5d, 0x8d, 0x95, 0x4e, 0x86, 0x00, 0xa3, 0xaf, 0xc5, 0xf5, 0xb8, 0x15, 0xd4, 0x05, 0xaa, 0x21, 0xe5, 0xe5, 0x86, 0x7c, 0x1e, 0xb7, 0x80, 0xd8, 0x92, 0x62, 0x47, 0x96, 0xcd, 0x28, 0x80, 0xf6, 0xdc, 0x15, 0xf6, 0x62, 0xb6, 0xc3, 0x8e, 0x70, 0x7b, 0x0a, 0x81, 0x97, 0x5c, 0xb2, 0x2e, 0x4d, 0x18, 0xbf, 0x32, 0x78, 0x7e, 0x51, 0xc8, 0x22, 0xc7, 0x5c, 0x48, 0x59, 0x5b, 0x50, 0xaf, 0x11, 0x14, 0x22, 0xdd, 0x0c, 0x89, 0xa1, 0x18, 0x71, 0x43, 0x89, 0x85, 0x62, 0x56, 0xe8, 0xbc, 0x35, 0x66, 0x15, 0x29, 0x39, 0xad, 0x7f, 0xd7, 0xd9, 0x72, 0x0c, 0x06, 0xb7, 0x55, 0x17, 0xf1, 0x1b, 0x72, 0x6c, 0x3a, 0xc4, 0xf6, 0xe6, 0x97, 0x67, 0x15, 0x99, 0xe6, 0xb5, 0x74, 0x67, 0x69, 0xd5, 0xbd, 0x6d, 0x3d, 0xf9, 0xd1, 0x48, 0x5e, 0x4f, 0x5b, 0x8f, 0x1d, 0xbe, 0xa0, 0x45, 0x86, 0x1c, 0xab, 0x4e, 0x67, 0xcd, 0x31, 0xf8, 0x23, 0x3a, 0xab, 0x5b, 0x9a, 0xec, 0x49, 0x66, 0xc9, 0x2a, 0x36, 0xf5, 0x42, 0x01, 0x3b, 0x7c, 0x29, 0x88, 0x40, 0xe9, 0x5e, 0xc2, 0x73, 0x5a, 0x26, 0x6f, 0xc0, 0xf3, 0x58, 0x72, 0x1e, 0xa0, 0x67, 0xba, 0x99, 0x7b, 0xe2, 0x86, 0xee, 0x26, 0xcc, 0xb3, 0xc5, 0x7a, 0x05, 0x8b, 0xf9, 0xa2, 0x00, 0x3d, 0x10, 0x48, 0x40, 0x10, 0xc3, 0x64, 0x66, 0xb9, 0x91, 0xd9, 0xa7, 0x03, 0x72, 0xb0, 0x04, 0xd0, 0x6e, 0x07, 0x13, 0x81, 0x2a, 0x64, 0xb3, 0xda, 0xc0, 0xe9, 0x72, 0xa3, 0xf3, 0xa7, 0x67, 0x8e, 0xfb, 0x13, 0xa1, 0x5d, 0xce, 0x98, 0x6d, 0x60, 0x20, 0x9e, 0xd1, 0xde, 0x5a, 0x5f, 0x5b, 0x55, 0x99, 0x17, 0xf6, 0xe4, 0x64, 0x3b, 0x4e, 0x39, 0x74, 0xb0, 0x4c, 0x9d, 0x39, 0x10, 0xf7, 0x0d, 0x7a, 0xec, 0x90, 0xc0, 0x23, 0x49, 0xe9, 0xd4, 0x44, 0x63, 0x9a, 0xae, 0x56, 0xa3, 0xc1, 0x8a, 0xea, 0x0c, 0xbb, 0xcf, 0x94, 0xed, 0xce, 0xb3, 0xab, 0xca, 0xc3, 0x7d, 0xbe, 0x96, 0xa8, 0xc7, 0x13, 0x3f, 0xaf, 0x56, 0x30, 0x67, 0x46, 0x54, 0xb1, 0xb5, 0xde, 0x3c, 0x41, 0xa7, 0x56, 0x59, 0x9c, 0x81, 0x2c, 0xa3, 0xaf, 0xb9, 0x1c, 0x52, 0x56, 0xd6, 0xf2, 0xa6, 0xcc, 0x88, 0xb2, 0xac, 0x23, 0xcb, 0xab, 0xd0, 0xaa, 0x15, 0x99, 0x96, 0x6c, 0xe3, 0x26, 0xa1, 0xa2, 0xc0, 0xe8, 0x30, 0x67, 0x0a, 0xa8, 0x83, 0xcd, 0xb4, 0x39, 0x33, 0x03, 0xb9, 0x3a, 0xbb, 0xda, 0x91, 0xd7, 0x50, 0x54, 0xba, 0x24, 0x1e, 0x62, 0x9b, 0xb4, 0x96, 0xcd, 0xaa, 0x62, 0x4f, 0xa3, 0x42, 0xa1, 0x33, 0x9a, 0x84, 0x5f, 0xa8, 0xb2, 0x8b, 0x5a, 0x22, 0xe1, 0xde, 0xda, 0x00, 0xaa, 0xd1, 0x9a, 0xd7, 0xa9, 0xf3, 0xb2, 0x22, 0x6a, 0xa5, 0x52, 0xa3, 0xe5, 0xa1, 0xcf, 0x43, 0x30, 0x06, 0x71, 0x7c, 0x31, 0x6c, 0x9a, 0x65, 0x4c, 0x6d, 0xbc, 0x2a, 0xcf, 0xe7, 0x66, 0x59, 0x94, 0x0f, 0xf3, 0x89, 0x23, 0xba, 0x37, 0xb1, 0xe0, 0x06, 0x36, 0x82, 0x88, 0x71, 0x39, 0x89, 0x1a, 0x44, 0xe8, 0x03, 0x93, 0x6b, 0x69, 0x62, 0x1d, 0x22, 0x66, 0x7e, 0x28, 0xe4, 0xcb, 0x2a, 0x24, 0x33, 0x3c, 0x75, 0x9a, 0x90, 0x0c, 0xe8, 0x3c, 0x23, 0x90, 0x73, 0xb2, 0xf3, 0x41, 0xf9, 0x48, 0xc1, 0x8a, 0x96, 0xd6, 0xb4, 0x54, 0x14, 0x22, 0x3e, 0xb8, 0xa6, 0x5c, 0xc0, 0x46, 0x7d, 0x99, 0xaa, 0xa0, 0xbb, 0x32, 0x87, 0x78, 0x30, 0x8a, 0xd8, 0x08, 0x34, 0x08, 0x37, 0x66, 0xfb, 0x78, 0x8d, 0x3a, 0xc3, 0xe7, 0x72, 0xda, 0xbd, 0x76, 0x93, 0x3a, 0x53, 0x89, 0xbe, 0xae, 0x9c, 0xdf, 0xd9, 0xdc, 0xa5, 0xcb, 0x2a, 0x0c, 0x70, 0xcd, 0x3a, 0xf3, 0x46, 0x45, 0x5e, 0xf3, 0x8a, 0xca, 0xd8, 0xaa, 0xf6, 0x7c, 0xb6, 0x5b, 0x6b, 0x1e, 0x57, 0xf8, 0xb2, 0x4a, 0x94, 0x0a, 0xb3, 0x42, 0xfa, 0x1e, 0xab, 0xcd, 0x50, 0xab, 0x65, 0x3d, 0x1c, 0x0f, 0xe3, 0x77, 0x99, 0x42, 0xb2, 0x5a, 0x88, 0x3d, 0x7d, 0x38, 0xe7, 0xcc, 0xe7, 0x09, 0xc4, 0xc5, 0x9f, 0x9a, 0x4e, 0x93, 0xf3, 0x04, 0x9f, 0x5f, 0x6f, 0xf4, 0x85, 0x02, 0x99, 0xf4, 0x3c, 0x21, 0x32, 0x15, 0x25, 0x37, 0x75, 0x38, 0xe9, 0x44, 0x11, 0xb3, 0x2f, 0x71, 0x94, 0x40, 0x34, 0x5e, 0x59, 0x3d, 0x6e, 0xe8, 0x76, 0xd7, 0xad, 0xa8, 0x2d, 0x68, 0x89, 0x86, 0x74, 0x51, 0x55, 0x06, 0x68, 0xaf, 0x8e, 0xa0, 0xab, 0x3c, 0xf6, 0xeb, 0x5f, 0xe7, 0x14, 0x80, 0x68, 0xe3, 0x76, 0x48, 0x2e, 0xaf, 0xa7, 0x1b, 0x1d, 0x1a, 0x2f, 0x59, 0x12, 0x0f, 0x9a, 0x7c, 0xa5, 0x4e, 0x3f, 0x2f, 0x18, 0x6d, 0xc6, 0x78, 0xc5, 0xf8, 0x8a, 0x65, 0xf5, 0xb9, 0xea, 0x1a, 0x73, 0x68, 0xa9, 0x18, 0x0e, 0xed, 0x95, 0xf9, 0x53, 0x1e, 0xb4, 0x7f, 0x1c, 0xf6, 0x39, 0x0f, 0x39, 0x5f, 0xb7, 0xd3, 0xf3, 0x75, 0x15, 0x52, 0x32, 0xca, 0xf1, 0x74, 0xa7, 0xec, 0xf2, 0x61, 0x82, 0x27, 0x67, 0xf6, 0xf9, 0x3a, 0x69, 0x23, 0xf9, 0x91, 0x99, 0x3c, 0xfc, 0xb0, 0x04, 0x9b, 0x35, 0xe5, 0xcf, 0x5a, 0x83, 0xd0, 0x1d, 0x5f, 0xb8, 0xf1, 0xc6, 0x1b, 0xaf, 0xbc, 0xe1, 0x86, 0x1b, 0x0e, 0xfd, 0x7c, 0x3c, 0xa0, 0xdd, 0xc2, 0xab, 0x78, 0x4e, 0xc9, 0x8f, 0xf2, 0x76, 0xc3, 0x05, 0x28, 0xde, 0x54, 0xdf, 0xb8, 0x6f, 0x5f, 0x63, 0x7d, 0x13, 0xba, 0xcd, 0x7b, 0x32, 0xd3, 0x6f, 0xb6, 0xf8, 0x33, 0x4e, 0xea, 0x4c, 0x74, 0xd0, 0xa1, 0x6d, 0x2b, 0xa1, 0x6d, 0xff, 0xf2, 0x19, 0x87, 0x79, 0x46, 0xc3, 0xc8, 0x19, 0x47, 0xcd, 0xfb, 0xef, 0x1f, 0x39, 0xf2, 0xa5, 0xc5, 0x8b, 0xd1, 0x93, 0x78, 0xe8, 0xb1, 0xad, 0x3b, 0xe3, 0x87, 0x0f, 0xc7, 0xb1, 0x7e, 0x8f, 0x7c, 0xcf, 0x80, 0xd7, 0xc3, 0x38, 0x56, 0x30, 0x7d, 0xf1, 0x79, 0x64, 0x1c, 0x0b, 0xfd, 0x58, 0xa9, 0x48, 0x3b, 0x8e, 0x1a, 0x79, 0x1c, 0xd5, 0x20, 0xc6, 0x23, 0x51, 0x21, 0x88, 0x04, 0xe1, 0x82, 0x9e, 0xa8, 0x0f, 0x2b, 0x7b, 0xe4, 0x68, 0xa1, 0x05, 0x79, 0x01, 0x68, 0x02, 0x1d, 0x59, 0xed, 0x99, 0x47, 0x96, 0x8e, 0xae, 0x30, 0x6b, 0x78, 0x09, 0x0d, 0x9b, 0x4e, 0x19, 0xe2, 0x90, 0x2b, 0x12, 0x7b, 0x13, 0xfe, 0x64, 0x64, 0x26, 0x47, 0xd9, 0xe3, 0xed, 0xee, 0xee, 0x46, 0xc7, 0x1b, 0x67, 0x8f, 0x73, 0x23, 0x10, 0xd3, 0xe7, 0x2e, 0xb5, 0x24, 0x87, 0xba, 0xb3, 0xa9, 0x9e, 0xca, 0x5e, 0x2d, 0xd0, 0xc7, 0xfd, 0x40, 0x4f, 0x72, 0x27, 0xe0, 0x9c, 0xf3, 0x79, 0x86, 0x33, 0x3b, 0x4b, 0x3e, 0xcd, 0x30, 0xe8, 0x33, 0xd2, 0xdd, 0x07, 0xf8, 0xcc, 0xd1, 0x08, 0xb9, 0x0f, 0xe0, 0xe9, 0x79, 0x86, 0x59, 0x8e, 0x3d, 0x47, 0xce, 0x6c, 0x22, 0xa8, 0xa6, 0xa6, 0x55, 0xf1, 0xec, 0x77, 0xc4, 0xa6, 0x86, 0x0a, 0x74, 0x51, 0xa3, 0xea, 0xc0, 0x9e, 0xe8, 0x17, 0x6a, 0x6a, 0x9e, 0xbd, 0xf4, 0x12, 0x45, 0x1c, 0x2d, 0x2d, 0xaf, 0xd9, 0xf6, 0x50, 0x55, 0xc9, 0xbd, 0xc3, 0x5b, 0x3f, 0x46, 0x2f, 0x34, 0xbe, 0xb6, 0x75, 0x3d, 0x19, 0xef, 0x82, 0xc9, 0x7f, 0xc0, 0x5c, 0x7c, 0x17, 0xd6, 0xd4, 0x70, 0x7c, 0x0d, 0x48, 0x96, 0xc8, 0x8a, 0x78, 0xc6, 0x83, 0x44, 0xa1, 0x18, 0xa9, 0x14, 0x5c, 0x57, 0x03, 0xe2, 0x51, 0xe7, 0xec, 0x0b, 0x81, 0x69, 0xe3, 0xc2, 0xcc, 0xbc, 0x17, 0x18, 0x4e, 0xde, 0x06, 0x0c, 0xab, 0x7a, 0x48, 0x20, 0x57, 0xe2, 0xc4, 0xed, 0xf1, 0x99, 0x7c, 0x06, 0x5f, 0xc0, 0x9f, 0x18, 0x96, 0x59, 0x60, 0x20, 0x42, 0x22, 0x46, 0xab, 0x60, 0x22, 0x83, 0x43, 0x7e, 0x9b, 0xc2, 0x19, 0x46, 0xc9, 0x7b, 0x80, 0x68, 0x2d, 0x08, 0x5e, 0x05, 0xdd, 0xea, 0xb2, 0xee, 0x35, 0xd5, 0xed, 0xa3, 0xee, 0x52, 0x51, 0xad, 0x56, 0x1b, 0x1c, 0xe1, 0x9c, 0x86, 0xde, 0x0c, 0xc4, 0x9a, 0x0c, 0xef, 0xa2, 0xdf, 0x58, 0xf4, 0xdf, 0xb2, 0x5f, 0xec, 0xcb, 0xcf, 0x36, 0xa9, 0x35, 0xca, 0x1a, 0xdf, 0x8e, 0x8e, 0xf8, 0xda, 0xf6, 0x02, 0x65, 0x67, 0x33, 0x6a, 0x41, 0x3f, 0xaf, 0x5f, 0xd7, 0x11, 0x8a, 0x15, 0xc7, 0x14, 0x62, 0xa6, 0x31, 0x73, 0x5e, 0x77, 0x8e, 0xc6, 0xaa, 0x30, 0x28, 0x9c, 0x39, 0x6a, 0xcb, 0x1d, 0x06, 0xab, 0x41, 0x51, 0x55, 0x59, 0x1d, 0xee, 0x5a, 0xdf, 0x10, 0x77, 0xc2, 0x43, 0xba, 0x36, 0x2b, 0x27, 0x2f, 0x43, 0x36, 0xb6, 0x05, 0xe4, 0x12, 0xe1, 0x51, 0xad, 0x08, 0x72, 0xb7, 0x11, 0x18, 0x9f, 0x45, 0xc8, 0x40, 0x54, 0x0c, 0x89, 0x59, 0x5d, 0xe4, 0x98, 0xe5, 0xa1, 0x92, 0x02, 0x8d, 0x56, 0x5d, 0xd7, 0x1d, 0x6f, 0xec, 0xaa, 0x53, 0x6b, 0x35, 0x05, 0x25, 0x68, 0x78, 0xe4, 0xc5, 0x25, 0x56, 0x9b, 0x18, 0x3a, 0xff, 0xc3, 0xfd, 0x97, 0xfe, 0x69, 0x3c, 0xa0, 0xb0, 0x5a, 0x97, 0xfd, 0x50, 0x8e, 0xff, 0x0d, 0xf5, 0x59, 0x66, 0xd4, 0x57, 0x1e, 0x0c, 0x05, 0x2b, 0x1a, 0x28, 0x7a, 0x8f, 0x20, 0x86, 0x8a, 0x88, 0x10, 0x79, 0x7b, 0x49, 0xa1, 0x46, 0xab, 0xa9, 0xef, 0x8a, 0xc7, 0xbb, 0xea, 0xe1, 0x97, 0x42, 0x52, 0xdf, 0x0f, 0x97, 0x59, 0xad, 0x8a, 0xc0, 0xf9, 0x1f, 0xec, 0xdf, 0xff, 0xe1, 0xf9, 0x21, 0xd1, 0x66, 0x5d, 0xf2, 0x22, 0x3d, 0xe7, 0x88, 0xb0, 0x07, 0xf1, 0x06, 0x61, 0x35, 0x2b, 0x72, 0xdf, 0x83, 0xe9, 0x45, 0x6d, 0xc6, 0xf0, 0x74, 0x9b, 0xb1, 0xa5, 0xec, 0x3b, 0xb8, 0x95, 0x7f, 0x1b, 0xd2, 0x9f, 0x4f, 0xa6, 0xcf, 0xb0, 0x29, 0x1b, 0xe5, 0xbe, 0x85, 0x4b, 0x78, 0x2b, 0xa4, 0x7f, 0xff, 0x34, 0xe9, 0x8b, 0x20, 0x7d, 0x3b, 0xa4, 0x9f, 0x48, 0x5b, 0xff, 0x00, 0xfe, 0x2b, 0xf3, 0x19, 0xe7, 0x86, 0xf4, 0x1f, 0xc0, 0x76, 0x71, 0x6a, 0xf9, 0xc5, 0xec, 0x25, 0x89, 0xf4, 0x17, 0x26, 0x27, 0xd3, 0xa4, 0x57, 0xb3, 0x17, 0xe3, 0xb5, 0xd4, 0x26, 0xee, 0xee, 0xb4, 0xf5, 0x7b, 0xa0, 0x7f, 0x23, 0xa4, 0x7f, 0xe8, 0x5e, 0xd8, 0xc2, 0x4f, 0x4d, 0x9f, 0x07, 0xe5, 0x6b, 0x69, 0xfa, 0x7d, 0x69, 0xeb, 0x27, 0xe9, 0x8b, 0x69, 0xfa, 0xfd, 0xc9, 0xf6, 0xe1, 0x99, 0xed, 0x3b, 0x88, 0x17, 0xd2, 0xf4, 0x07, 0x40, 0x12, 0x38, 0xb5, 0x7c, 0x2f, 0x94, 0x9f, 0x4f, 0xdb, 0xf7, 0xad, 0x64, 0xfb, 0xd8, 0xe9, 0xe9, 0x0b, 0xa0, 0x7c, 0x33, 0x4d, 0x7f, 0x30, 0xf9, 0xfe, 0x19, 0xf5, 0x0f, 0xb2, 0x7f, 0xc4, 0x0d, 0xfc, 0x8f, 0xa0, 0xff, 0x2f, 0xa6, 0x7d, 0x7f, 0x33, 0x6b, 0xc5, 0xbd, 0xc2, 0x0f, 0x20, 0xfd, 0x47, 0xc9, 0xf2, 0x33, 0xea, 0x1f, 0x82, 0xf4, 0x38, 0x68, 0xb9, 0x22, 0xf7, 0xe3, 0xb4, 0xf4, 0x69, 0x80, 0xf7, 0x0f, 0x93, 0xf7, 0x73, 0x2f, 0xa5, 0x1d, 0xbf, 0x3c, 0x48, 0x1f, 0xa7, 0xf3, 0xe3, 0xe5, 0xb4, 0xe9, 0xdd, 0x90, 0x3e, 0x40, 0xd3, 0x7f, 0x32, 0x79, 0x32, 0x4d, 0x7a, 0x13, 0xa4, 0xaf, 0xa7, 0xf5, 0xbf, 0x92, 0xb6, 0x7c, 0x1c, 0xd2, 0x2f, 0xa1, 0xe5, 0x7f, 0x9a, 0x2c, 0x3f, 0xa3, 0xfd, 0xb9, 0xec, 0x25, 0x78, 0x1b, 0x2d, 0xff, 0xea, 0xe4, 0x44, 0xda, 0xf1, 0x17, 0x51, 0x96, 0xf0, 0x22, 0xa4, 0xff, 0x2c, 0xad, 0xcd, 0x63, 0x04, 0xd2, 0x4d, 0x34, 0xfd, 0xe7, 0x93, 0x52, 0xba, 0xf4, 0x49, 0x17, 0xde, 0x80, 0xfe, 0x29, 0xcf, 0xff, 0x49, 0x97, 0x4c, 0x9f, 0x49, 0xd7, 0xd4, 0xfc, 0x94, 0xae, 0x66, 0x3e, 0x9b, 0x7c, 0x4a, 0x9e, 0x9f, 0x72, 0x3a, 0x9a, 0x9e, 0xbe, 0x64, 0xd2, 0xca, 0xfc, 0x83, 0xa6, 0xbf, 0x90, 0x2c, 0x3f, 0x23, 0x7d, 0x1f, 0xd4, 0xdf, 0x8d, 0x8a, 0x91, 0xc8, 0xfd, 0x90, 0x31, 0xa6, 0x49, 0x1f, 0x9c, 0xbc, 0x14, 0xe4, 0x9e, 0x21, 0x79, 0x7c, 0xd3, 0xbc, 0xbf, 0x45, 0x7a, 0x18, 0xcf, 0xc3, 0xab, 0xc8, 0xf8, 0x26, 0xeb, 0x67, 0xa7, 0xa7, 0x0f, 0x43, 0x7a, 0x23, 0xba, 0x85, 0x8e, 0x6f, 0xba, 0xf2, 0x0d, 0xf0, 0xfe, 0x61, 0x8c, 0xe5, 0xf1, 0x4d, 0xf3, 0xfe, 0x3c, 0x48, 0x1f, 0xa7, 0xfd, 0x7f, 0x39, 0x6d, 0x7a, 0x27, 0xa4, 0x2f, 0xa5, 0xe9, 0x3f, 0x99, 0xfc, 0x67, 0x9a, 0xf4, 0x26, 0x48, 0x5f, 0x4f, 0xeb, 0x7f, 0x25, 0x6d, 0x79, 0xf2, 0xfe, 0x8b, 0x69, 0xf9, 0x9f, 0x4e, 0x7e, 0x9e, 0xa6, 0xfd, 0xe1, 0x49, 0x2b, 0xde, 0x4a, 0xcb, 0xbf, 0x3a, 0x79, 0x32, 0x4d, 0xf9, 0x28, 0xfe, 0x2e, 0xde, 0xc4, 0xdf, 0x46, 0xc7, 0x07, 0xe3, 0xef, 0xca, 0xfd, 0x9b, 0x66, 0x73, 0xba, 0x94, 0x75, 0x01, 0x7f, 0x72, 0xca, 0xfc, 0x49, 0x4e, 0x9f, 0x61, 0x93, 0x3a, 0xca, 0xfe, 0x37, 0x2e, 0xe1, 0x16, 0xca, 0xfc, 0x29, 0x6d, 0xfa, 0x11, 0x48, 0x7f, 0x40, 0xe6, 0x4f, 0x69, 0xea, 0x5f, 0x86, 0x8b, 0x98, 0x4f, 0xd9, 0x41, 0x3a, 0xfe, 0xe9, 0xca, 0x2f, 0xc6, 0x4f, 0x32, 0x9f, 0xd1, 0x74, 0xe0, 0x4f, 0x69, 0xd2, 0xb7, 0x40, 0xfb, 0xab, 0xb8, 0x9f, 0xb1, 0x64, 0xfc, 0x51, 0x9a, 0xf4, 0x6a, 0x78, 0xdb, 0x5a, 0xfe, 0x2e, 0x99, 0x7f, 0xa5, 0x79, 0xff, 0x2a, 0x28, 0x5f, 0xce, 0xdf, 0x4e, 0x6c, 0x7a, 0xd3, 0xd6, 0xdf, 0x01, 0xe9, 0xf3, 0x69, 0xf9, 0x6f, 0x82, 0x18, 0x7e, 0x6a, 0x7a, 0x1c, 0x6a, 0x6b, 0xa5, 0xe9, 0xf7, 0xa4, 0x2d, 0x6f, 0x86, 0xf2, 0xcb, 0x09, 0x7d, 0xd1, 0xbd, 0x93, 0x9f, 0xa5, 0x79, 0xff, 0x7c, 0xf8, 0x56, 0x47, 0xd3, 0xef, 0x4b, 0xdb, 0xff, 0x5e, 0x48, 0x5f, 0x44, 0xd3, 0xef, 0x4f, 0xdb, 0xfe, 0xf9, 0x50, 0x7f, 0x2f, 0x4d, 0x7f, 0x60, 0x72, 0xe2, 0x34, 0xe5, 0xe5, 0xf6, 0x7f, 0x2b, 0x59, 0x9e, 0x9d, 0x9e, 0xde, 0x0f, 0xe5, 0xe5, 0xf6, 0x3f, 0x98, 0xa4, 0xdf, 0x8c, 0xfa, 0x07, 0x59, 0x2f, 0xf0, 0x47, 0x5e, 0x5e, 0x3f, 0x69, 0xd2, 0x5b, 0xf0, 0x42, 0x3c, 0x0f, 0x74, 0x5f, 0xba, 0x7e, 0xd2, 0xd4, 0xbf, 0x16, 0xd2, 0x1b, 0xf8, 0x5a, 0xb2, 0x7e, 0x92, 0xf4, 0xc1, 0x33, 0xe9, 0xf7, 0x5d, 0xbc, 0x8e, 0xbc, 0x1f, 0xd6, 0x4f, 0xba, 0xf1, 0xcb, 0x83, 0xf4, 0x71, 0x3a, 0x3f, 0x5f, 0x4e, 0x4b, 0x9f, 0x3e, 0x48, 0x5f, 0x45, 0xd3, 0x7f, 0x92, 0x9c, 0xbf, 0x28, 0x7d, 0xfd, 0xaf, 0x4c, 0x4a, 0x69, 0xd2, 0x5b, 0x20, 0x7d, 0x3f, 0x2d, 0xff, 0xd3, 0xb4, 0xed, 0xcf, 0xc3, 0x4f, 0xe2, 0x31, 0x5a, 0xfe, 0xd5, 0xb4, 0xe5, 0x2b, 0x70, 0x33, 0xb2, 0x80, 0xb4, 0x07, 0xfc, 0x31, 0x6d, 0x7a, 0x0c, 0xd2, 0x6d, 0x34, 0xfd, 0xe7, 0x69, 0xdb, 0x77, 0x0d, 0xbc, 0xf5, 0xaf, 0xfc, 0x09, 0x26, 0xc8, 0xed, 0x20, 0x77, 0x00, 0xa8, 0x1a, 0x1d, 0xc0, 0xfb, 0x12, 0x96, 0xdc, 0xfb, 0x68, 0x2e, 0x96, 0x59, 0x30, 0xd9, 0xc5, 0xbf, 0xc9, 0xbf, 0xc2, 0x58, 0x99, 0x52, 0xa6, 0x93, 0x69, 0x8d, 0x37, 0x15, 0xe4, 0x83, 0x4e, 0x98, 0x07, 0x7a, 0x9f, 0x07, 0x81, 0x5c, 0xd7, 0xd5, 0x60, 0xc5, 0x6c, 0x47, 0xa7, 0x16, 0x93, 0xd3, 0x06, 0x8e, 0xba, 0xd9, 0x92, 0xcb, 0x66, 0xd0, 0xa2, 0xf1, 0x76, 0xe2, 0x6f, 0x48, 0xaf, 0x4a, 0x98, 0x9e, 0xd2, 0x92, 0xe2, 0xa2, 0x80, 0x2f, 0x27, 0xdb, 0x6e, 0xe3, 0x89, 0x54, 0xaa, 0xe3, 0xa9, 0x21, 0x98, 0x48, 0x54, 0xc3, 0x2c, 0x54, 0x11, 0x43, 0xbe, 0x44, 0xf0, 0x96, 0xc4, 0x85, 0x41, 0x54, 0x1f, 0x8a, 0x36, 0x20, 0x8f, 0x39, 0xc2, 0xd6, 0xf3, 0x54, 0xc3, 0x8e, 0x11, 0xd5, 0x9a, 0xc4, 0x62, 0xc5, 0x17, 0x2f, 0xbe, 0x6c, 0x75, 0x63, 0x50, 0x4f, 0x62, 0x49, 0x6b, 0x6a, 0xce, 0x6b, 0xf6, 0xb7, 0x5d, 0xf5, 0xd3, 0x2b, 0xd1, 0x77, 0xbf, 0x15, 0x89, 0x2a, 0xdd, 0x7a, 0x53, 0xd9, 0x78, 0xfd, 0xea, 0x4b, 0xe7, 0xb9, 0x87, 0x77, 0x0f, 0x23, 0xfc, 0xad, 0xab, 0x20, 0x67, 0x3c, 0x9c, 0x29, 0xe7, 0x5c, 0xd5, 0x44, 0x73, 0xf2, 0xcf, 0x57, 0x1d, 0xff, 0xcd, 0xc7, 0x1f, 0xfd, 0x66, 0xf1, 0xe2, 0x5d, 0xcb, 0x5b, 0xbc, 0x9a, 0xd8, 0x8e, 0xc3, 0xf7, 0x8f, 0xdd, 0x82, 0xa2, 0xc8, 0xf1, 0xd7, 0x25, 0x13, 0xe5, 0xd2, 0xbd, 0xc1, 0x02, 0xc4, 0xbe, 0x6e, 0xb2, 0x14, 0x2d, 0x3f, 0xb8, 0x04, 0x15, 0x14, 0xbd, 0xa9, 0xf9, 0xf8, 0xe4, 0xe7, 0xe7, 0x55, 0x3d, 0xf1, 0xeb, 0x8f, 0xff, 0xf4, 0xe6, 0x92, 0x25, 0x7b, 0x96, 0xb7, 0x7a, 0x35, 0x95, 0x3b, 0xae, 0xbe, 0x7f, 0xeb, 0x2d, 0xd2, 0x4b, 0xd2, 0x7b, 0x7f, 0xa5, 0xf6, 0xbe, 0x3a, 0xc6, 0x3e, 0xf9, 0x53, 0xfe, 0x73, 0xfe, 0x61, 0x90, 0xd9, 0xac, 0x8c, 0x83, 0xc9, 0x61, 0x7c, 0x14, 0x23, 0xa1, 0x98, 0x89, 0x30, 0x31, 0xa6, 0x06, 0x24, 0xe5, 0x66, 0x66, 0x29, 0xf3, 0xf7, 0x78, 0x59, 0xa3, 0x1d, 0xb3, 0xb8, 0xc9, 0x81, 0x19, 0xb6, 0xb9, 0x2e, 0x8b, 0x55, 0x33, 0x0d, 0xf5, 0x39, 0x2e, 0x27, 0xcf, 0xab, 0x63, 0x15, 0xd1, 0x50, 0x90, 0x57, 0x71, 0x65, 0xf9, 0x01, 0x16, 0xa9, 0x22, 0x79, 0x58, 0x44, 0xa5, 0xb9, 0x58, 0x10, 0x15, 0x09, 0xc4, 0x8b, 0x22, 0x78, 0x89, 0x1a, 0xf4, 0x81, 0x21, 0x85, 0x06, 0xf3, 0x5a, 0x25, 0xe6, 0x40, 0x31, 0x5b, 0xc3, 0xa8, 0x60, 0xb4, 0x54, 0x88, 0x38, 0x82, 0x08, 0xa2, 0x40, 0xee, 0xf5, 0x69, 0xec, 0xf9, 0x75, 0x29, 0x2c, 0xaa, 0xd2, 0x33, 0x96, 0x4a, 0x66, 0x27, 0x38, 0xf3, 0x14, 0x7b, 0x6d, 0x98, 0x98, 0x95, 0x9f, 0xe3, 0x9b, 0x06, 0x06, 0xe2, 0xae, 0xca, 0x4a, 0xb7, 0x7b, 0x51, 0x7f, 0x57, 0x47, 0x65, 0x4d, 0x65, 0x4d, 0x75, 0x55, 0x71, 0x51, 0x61, 0x81, 0xdb, 0xe7, 0xf6, 0x65, 0xe9, 0x4d, 0xc4, 0xb2, 0xdc, 0x9e, 0x3f, 0xfb, 0x44, 0x18, 0x45, 0xac, 0xbe, 0x80, 0x55, 0x0c, 0xf1, 0xf0, 0x83, 0xcc, 0x62, 0xd4, 0x28, 0x86, 0x62, 0x2c, 0xfc, 0x20, 0x5f, 0x28, 0xc2, 0x87, 0x62, 0xd6, 0x00, 0xfc, 0xa0, 0x68, 0xcc, 0xcc, 0xc6, 0xac, 0xa2, 0x11, 0x7e, 0xd8, 0x69, 0xce, 0x40, 0xf4, 0x7c, 0x4a, 0xd9, 0x77, 0xf8, 0xe9, 0xcd, 0x1b, 0x9f, 0x3a, 0xdc, 0xd7, 0x77, 0xf8, 0xa9, 0x8d, 0x9b, 0x9f, 0x3e, 0xdc, 0x87, 0x7e, 0x34, 0xa6, 0xbc, 0x14, 0x7d, 0x61, 0xf5, 0xbc, 0x31, 0xf4, 0x85, 0xd8, 0x91, 0x46, 0x94, 0x75, 0xff, 0x50, 0xe5, 0xcd, 0xf1, 0xad, 0x6b, 0x56, 0x6e, 0x95, 0xde, 0x1a, 0x53, 0x5c, 0x2a, 0xed, 0x59, 0x33, 0x7f, 0x4c, 0xda, 0x13, 0x3b, 0x12, 0x97, 0xde, 0x97, 0x53, 0xd6, 0x0e, 0x6e, 0x8d, 0xd4, 0x8f, 0x1e, 0xe9, 0xef, 0xbf, 0x6e, 0x73, 0x7d, 0xfd, 0xe6, 0xeb, 0xfa, 0xfb, 0x8f, 0x8c, 0xd6, 0xe3, 0xef, 0x6f, 0x7a, 0x9a, 0x54, 0xf9, 0xf4, 0xa6, 0x44, 0xd5, 0xec, 0x52, 0xc8, 0x7c, 0x4b, 0x23, 0xc9, 0x2c, 0xfd, 0x6e, 0xbb, 0x62, 0xbf, 0xb4, 0x9b, 0x56, 0x53, 0x79, 0x7d, 0xa3, 0xf4, 0xc7, 0x54, 0x35, 0xc8, 0x03, 0x29, 0xe8, 0x0a, 0x48, 0x41, 0x87, 0xaa, 0x20, 0xa5, 0xb8, 0xff, 0x08, 0xa9, 0x32, 0x55, 0x35, 0xc3, 0x68, 0xe7, 0x30, 0x37, 0x7e, 0x11, 0x2f, 0x68, 0xac, 0x23, 0xae, 0x06, 0xb8, 0xb9, 0xc9, 0x91, 0xc5, 0x31, 0x2c, 0x99, 0x17, 0x1c, 0xcf, 0x54, 0x84, 0x30, 0xe2, 0x62, 0x91, 0xb2, 0xfc, 0xbc, 0xa0, 0x20, 0xa2, 0x68, 0x69, 0x6e, 0x80, 0x03, 0xfa, 0x27, 0xe6, 0x44, 0x50, 0xa1, 0xc2, 0xbc, 0x1a, 0x46, 0x08, 0x91, 0x11, 0x4a, 0xdc, 0xde, 0x9d, 0x76, 0x2e, 0xe4, 0xa6, 0xcd, 0x9d, 0x7e, 0x0e, 0xcc, 0xb1, 0x66, 0x18, 0xfb, 0x2c, 0x32, 0xf6, 0x69, 0xc6, 0x5d, 0x93, 0x66, 0xdc, 0x8d, 0x11, 0xd6, 0x07, 0xaa, 0x62, 0x28, 0x60, 0xe5, 0x43, 0xbc, 0x39, 0x10, 0x05, 0x4e, 0x13, 0x33, 0x8a, 0x6c, 0x8c, 0xf5, 0x19, 0x23, 0x28, 0x84, 0xe0, 0x69, 0xc0, 0x1a, 0x88, 0xf2, 0x66, 0x14, 0x43, 0xf0, 0xd4, 0x28, 0xe2, 0x4d, 0xf6, 0x82, 0x6a, 0x8f, 0xa7, 0xba, 0xc0, 0x9e, 0xfc, 0xdc, 0x30, 0x66, 0x6e, 0x43, 0x9b, 0x57, 0x4b, 0x9f, 0xd6, 0xe1, 0x8c, 0x98, 0xd4, 0xe9, 0xc7, 0x7e, 0x29, 0x13, 0x7d, 0xa3, 0x52, 0x5a, 0xe9, 0x7b, 0x72, 0xcd, 0x1f, 0x6b, 0xd7, 0x8f, 0x59, 0xda, 0xa4, 0x1b, 0xd7, 0x20, 0x55, 0xdd, 0xc4, 0x5f, 0x62, 0xe8, 0xb8, 0x7f, 0xe2, 0x37, 0xe8, 0xcf, 0xd2, 0x40, 0x25, 0xba, 0xd3, 0xf7, 0xe4, 0xda, 0xf7, 0x6a, 0xf1, 0x09, 0x4f, 0x4d, 0x7e, 0x56, 0x56, 0x7e, 0x8d, 0xc7, 0x53, 0x43, 0x6a, 0xac, 0x61, 0x75, 0x89, 0xc2, 0xfe, 0x6f, 0xaf, 0x7d, 0xaf, 0x6e, 0xfd, 0xf6, 0xa9, 0xc2, 0x95, 0xe8, 0xdb, 0xbe, 0x89, 0xdf, 0x26, 0x0a, 0xa7, 0x52, 0xd1, 0xe6, 0x35, 0xd2, 0xdf, 0xeb, 0xb0, 0xae, 0x4a, 0xea, 0xf0, 0xc9, 0xf7, 0x6e, 0x51, 0xf6, 0x30, 0xeb, 0x13, 0x76, 0xc3, 0xb8, 0xfa, 0x08, 0x9a, 0x4a, 0xd2, 0xf1, 0x62, 0x69, 0x77, 0xd2, 0xc2, 0x66, 0x19, 0xd3, 0x13, 0x0c, 0x87, 0xfd, 0x9c, 0xd2, 0x76, 0x06, 0xf7, 0x0a, 0xc2, 0xf0, 0x58, 0x5f, 0xc5, 0xd0, 0x55, 0x8b, 0x16, 0x5d, 0x35, 0x54, 0x91, 0xfc, 0xbc, 0x25, 0xa7, 0xaa, 0x3f, 0x12, 0xe9, 0xaf, 0xca, 0xd9, 0xb9, 0x7e, 0xfd, 0x4e, 0x1e, 0x2d, 0xb8, 0x62, 0x5d, 0x55, 0xd5, 0xba, 0x2b, 0x16, 0x2c, 0xb8, 0x92, 0x7c, 0x5e, 0xb9, 0xa0, 0x6a, 0x45, 0x83, 0xd7, 0xdb, 0xb0, 0xa2, 0x6a, 0x7c, 0xff, 0x7e, 0xca, 0xc3, 0x4f, 0xaa, 0xb9, 0x7b, 0xd8, 0x4f, 0xf8, 0xf7, 0x18, 0x11, 0x17, 0x7c, 0xfe, 0x24, 0x93, 0x78, 0x06, 0x3a, 0xc5, 0x27, 0x22, 0x8f, 0xe8, 0x33, 0xe4, 0x96, 0xf9, 0x3e, 0x72, 0x27, 0x7c, 0x73, 0xd0, 0xe4, 0x3f, 0xa4, 0x1b, 0x71, 0x88, 0xfd, 0x8c, 0xb1, 0x13, 0x44, 0x2f, 0x15, 0x3d, 0xb3, 0x16, 0x88, 0x0d, 0x36, 0x83, 0x39, 0x66, 0x0d, 0xc3, 0xf3, 0x23, 0xdd, 0xd3, 0x3c, 0x4e, 0xc9, 0x41, 0x49, 0x20, 0x90, 0x29, 0xaa, 0x1c, 0x24, 0xdc, 0xe8, 0x74, 0xd4, 0x88, 0x8a, 0x28, 0x35, 0x8d, 0x80, 0xbf, 0x38, 0x74, 0xbd, 0xa7, 0x7d, 0xfb, 0x02, 0x4f, 0x2c, 0x5b, 0xc1, 0x39, 0x82, 0xbd, 0xf1, 0x5b, 0x6f, 0xfd, 0xfe, 0xf5, 0xe8, 0x1e, 0xc9, 0xda, 0xb0, 0xa5, 0xaf, 0x48, 0xa5, 0xdc, 0x64, 0xe4, 0xda, 0xfa, 0xd0, 0x7f, 0x5d, 0x8b, 0xbe, 0x27, 0x35, 0x50, 0xfa, 0x55, 0xc0, 0x7f, 0x01, 0xfe, 0x49, 0xa0, 0x5f, 0x0e, 0xc1, 0xf5, 0x17, 0x51, 0xf2, 0xca, 0x4e, 0x91, 0xb4, 0xfd, 0x5e, 0x47, 0xac, 0x1d, 0x47, 0x08, 0x3d, 0x87, 0x59, 0x6a, 0xf2, 0x28, 0xe3, 0x58, 0xce, 0x44, 0x54, 0x99, 0x6d, 0x01, 0x6e, 0xf4, 0x25, 0x8c, 0x35, 0xe0, 0x2f, 0x2a, 0x4e, 0xd9, 0x6f, 0x97, 0x06, 0x2d, 0x96, 0x60, 0xe9, 0x9b, 0x5f, 0x3c, 0x71, 0xf3, 0xcd, 0x27, 0xbe, 0xc8, 0xdd, 0x6f, 0x0e, 0x94, 0xc2, 0xb3, 0x80, 0x19, 0x3e, 0xb3, 0x1d, 0xf0, 0x79, 0x72, 0x42, 0x5a, 0x8a, 0xee, 0x49, 0x34, 0x0e, 0x4d, 0x4a, 0xd0, 0x36, 0x12, 0x5f, 0x41, 0xcd, 0x78, 0xe2, 0xae, 0x19, 0xb7, 0x26, 0x72, 0x1c, 0x69, 0x4a, 0x90, 0xd4, 0xb5, 0xc9, 0xd4, 0x0b, 0xf1, 0xc8, 0x8f, 0x5e, 0xff, 0x11, 0xfc, 0xc3, 0x5f, 0x99, 0x58, 0x83, 0xbf, 0xb2, 0x17, 0xef, 0x9b, 0xd8, 0x4f, 0x75, 0x7a, 0x02, 0x96, 0x28, 0xb1, 0xff, 0x48, 0x53, 0xdf, 0xc8, 0x19, 0xeb, 0x43, 0xd2, 0xe5, 0xdf, 0xb9, 0x1c, 0xfe, 0xa1, 0x97, 0xa5, 0x72, 0xf4, 0xf2, 0x18, 0x31, 0xfd, 0xa1, 0xf5, 0x2d, 0x96, 0x2e, 0xc0, 0x02, 0xec, 0xd9, 0x36, 0xe6, 0x31, 0x19, 0x23, 0x44, 0x49, 0x43, 0x02, 0x39, 0xc8, 0x27, 0x46, 0x49, 0x44, 0xdc, 0x6c, 0xa8, 0x7f, 0x1d, 0x8d, 0x63, 0xc4, 0x2e, 0x27, 0x44, 0xec, 0xa6, 0x50, 0x3f, 0x2b, 0x64, 0x3f, 0xab, 0x84, 0xe7, 0xd1, 0xe9, 0xf3, 0x6c, 0x62, 0x92, 0x31, 0xba, 0xed, 0x32, 0x52, 0x10, 0xb3, 0x6a, 0x56, 0x5e, 0x96, 0x5e, 0x96, 0x13, 0x40, 0x1c, 0xba, 0xd7, 0x93, 0x41, 0x3d, 0x35, 0x4b, 0x32, 0xe6, 0xa6, 0x8d, 0xb1, 0xe9, 0x8d, 0x9e, 0x40, 0x20, 0x61, 0x11, 0x3e, 0x0b, 0x77, 0xa4, 0x0e, 0x11, 0x4c, 0x4b, 0x2c, 0xcc, 0x86, 0x47, 0xf9, 0xc5, 0x89, 0xa3, 0xbf, 0x27, 0xe0, 0x23, 0x7a, 0x13, 0x05, 0x1f, 0xe9, 0x19, 0xe4, 0x4f, 0xfc, 0xf3, 0x55, 0x79, 0xfd, 0xf5, 0x4b, 0xb7, 0xf2, 0x2e, 0xfe, 0x3e, 0xd8, 0x7d, 0xdb, 0x98, 0x8f, 0xe4, 0x86, 0xda, 0xac, 0xd0, 0x98, 0x36, 0xc4, 0x28, 0x2b, 0x74, 0x0c, 0x4c, 0xe4, 0x28, 0x52, 0xa0, 0x02, 0x24, 0xf2, 0x7c, 0x97, 0x83, 0x24, 0x71, 0x69, 0x93, 0x12, 0x38, 0x94, 0xf9, 0x29, 0x14, 0x15, 0xea, 0xfd, 0x40, 0x4f, 0x0a, 0xb7, 0xa6, 0xc2, 0x82, 0x8d, 0xd2, 0x2e, 0xd1, 0x03, 0xe1, 0x31, 0x31, 0x49, 0x96, 0x33, 0x17, 0xd9, 0x36, 0xb3, 0x48, 0xbc, 0x0a, 0x24, 0x23, 0x25, 0xc7, 0x2a, 0xc7, 0x18, 0x41, 0x41, 0x00, 0xc4, 0xc6, 0x92, 0xa8, 0x2c, 0xe9, 0x0b, 0x33, 0x53, 0x65, 0x09, 0x01, 0x5b, 0x9a, 0xc2, 0x01, 0x0f, 0xfc, 0x0d, 0x05, 0xbd, 0xd4, 0x85, 0x73, 0x06, 0x04, 0x87, 0x7e, 0xba, 0xf9, 0x15, 0xc5, 0x39, 0xd4, 0x9f, 0x0a, 0x63, 0x32, 0x0b, 0x3a, 0x8c, 0xb5, 0x95, 0x6c, 0x6b, 0x3d, 0x74, 0x83, 0xa7, 0x61, 0xb0, 0x7a, 0xf1, 0xc5, 0x15, 0xb9, 0x8b, 0x0e, 0x0c, 0x94, 0x2e, 0x28, 0xb4, 0x08, 0x36, 0x9d, 0x36, 0x1c, 0xe8, 0x5f, 0x34, 0x1b, 0xf1, 0x24, 0xb3, 0xf1, 0xeb, 0xa3, 0x4b, 0xaf, 0x19, 0xae, 0xa8, 0x1d, 0xff, 0xda, 0xc8, 0xe0, 0xfe, 0x08, 0x8b, 0x1b, 0xca, 0xbe, 0xf7, 0xe4, 0x9a, 0x2b, 0x97, 0xe6, 0xf6, 0xb4, 0xb2, 0x96, 0x85, 0x57, 0xad, 0xad, 0xd0, 0x1b, 0x97, 0xd8, 0x32, 0x74, 0xc3, 0x1b, 0x3e, 0xdf, 0x37, 0x1d, 0x1a, 0x65, 0xd1, 0xd6, 0xde, 0x95, 0x2d, 0x97, 0x3d, 0xbf, 0x77, 0xfb, 0xf3, 0xd7, 0xf6, 0xd5, 0xc6, 0x28, 0x4e, 0xca, 0xad, 0xdc, 0xcf, 0x39, 0x05, 0xa3, 0x80, 0x7d, 0xef, 0x97, 0x71, 0x55, 0x14, 0x91, 0x00, 0x43, 0xac, 0x90, 0x44, 0xf6, 0x2a, 0x65, 0x44, 0x9e, 0x98, 0x9d, 0x8c, 0x01, 0x3b, 0x62, 0x78, 0xe0, 0x08, 0x04, 0x38, 0x35, 0xe9, 0x22, 0x98, 0x08, 0x1f, 0x38, 0xda, 0x4d, 0xac, 0xb3, 0x53, 0xc8, 0x2e, 0xf2, 0x50, 0xcc, 0xa5, 0xe0, 0xb6, 0x99, 0x05, 0x49, 0xf8, 0x0c, 0xb9, 0x0c, 0xa3, 0x64, 0x15, 0xac, 0x52, 0x71, 0xe6, 0xb2, 0xcc, 0x54, 0x51, 0xb2, 0xf1, 0x31, 0x4c, 0x5d, 0x4d, 0x35, 0x31, 0x8e, 0x2b, 0x20, 0xf1, 0xb2, 0x2c, 0x26, 0xad, 0x9a, 0x80, 0xea, 0xfa, 0xd5, 0xc4, 0xef, 0xbb, 0x5c, 0x3e, 0xa5, 0x4c, 0x51, 0x7e, 0x3a, 0xe1, 0xa9, 0xf7, 0x95, 0x2f, 0x01, 0x65, 0x4c, 0x8e, 0x2c, 0x53, 0x80, 0x33, 0x56, 0x36, 0xcf, 0x13, 0x1f, 0xac, 0xbd, 0xe8, 0x1a, 0x23, 0xfb, 0x4c, 0x82, 0xea, 0x95, 0xbb, 0xab, 0x37, 0x3e, 0x7a, 0x59, 0x67, 0xd3, 0x45, 0x8f, 0x6c, 0x5b, 0xf9, 0xe5, 0xf1, 0x46, 0x93, 0x63, 0x62, 0x84, 0x2d, 0xea, 0x1b, 0x6b, 0x6d, 0x5c, 0xd5, 0x5a, 0x6c, 0xb0, 0x69, 0x2b, 0x93, 0xa3, 0x84, 0xb7, 0x54, 0xaf, 0xed, 0x08, 0x5f, 0xbb, 0xf7, 0xe4, 0x42, 0x99, 0xec, 0x4d, 0x16, 0x53, 0xfb, 0x95, 0x2f, 0x1d, 0xdc, 0x74, 0xfc, 0x8a, 0x79, 0x75, 0x3b, 0xee, 0xdd, 0x52, 0x98, 0xb3, 0xe2, 0x0b, 0xcb, 0x0b, 0x5c, 0x3e, 0x97, 0xa1, 0x43, 0x06, 0xd1, 0x91, 0xef, 0x14, 0x16, 0xc2, 0x58, 0x0c, 0xc3, 0x58, 0xcc, 0x67, 0x1e, 0x8f, 0x5b, 0x6c, 0x88, 0x45, 0x6a, 0xe0, 0xc0, 0xb5, 0x30, 0x22, 0x90, 0x26, 0xb4, 0x23, 0x95, 0x82, 0x4f, 0x00, 0xea, 0x54, 0xa6, 0xf0, 0xd0, 0x92, 0x04, 0xe3, 0x80, 0x4e, 0x1c, 0x9f, 0x32, 0xe9, 0xa6, 0x74, 0x92, 0x8d, 0x4a, 0x29, 0x8d, 0xe9, 0x79, 0xee, 0x98, 0x0a, 0x68, 0xdc, 0x70, 0x4a, 0x51, 0x02, 0x3e, 0x4c, 0x0e, 0x84, 0xcf, 0x50, 0x05, 0x33, 0x55, 0xc3, 0x40, 0xdc, 0xd6, 0xdd, 0xd9, 0xd2, 0xd4, 0xd8, 0x50, 0x5d, 0x19, 0x8d, 0x14, 0xe6, 0xe7, 0x85, 0xbd, 0x6e, 0xa7, 0xc3, 0x62, 0xf2, 0x12, 0x3a, 0x07, 0x52, 0x91, 0x0c, 0x63, 0x53, 0xd1, 0x7b, 0x67, 0x20, 0xa9, 0x59, 0x6a, 0x90, 0x39, 0xb5, 0x4c, 0x66, 0x91, 0x3b, 0x05, 0x35, 0x84, 0x1f, 0x2d, 0x5b, 0x5f, 0xda, 0x7f, 0x74, 0x5b, 0xe3, 0xc8, 0xe2, 0xc2, 0x86, 0x90, 0xa1, 0x6c, 0xe8, 0xc6, 0xa1, 0xf1, 0xbb, 0xaa, 0x68, 0x44, 0xa9, 0x40, 0xac, 0xaf, 0x2a, 0x3a, 0x3f, 0x9a, 0xb5, 0x7b, 0xcb, 0xf8, 0xf8, 0x45, 0xbb, 0x72, 0xaa, 0x16, 0x46, 0x1a, 0x16, 0xd5, 0x84, 0x8d, 0xd3, 0x28, 0x9f, 0x5c, 0x1f, 0x36, 0x93, 0x3e, 0x6f, 0xfe, 0xf6, 0xf6, 0xa5, 0x7b, 0xdc, 0x86, 0xf2, 0xda, 0xc6, 0x9c, 0x8a, 0xa1, 0x9e, 0xc2, 0x96, 0x8a, 0x6e, 0x8a, 0xda, 0x6f, 0x09, 0x46, 0x9c, 0xf1, 0xbe, 0x03, 0x4b, 0xda, 0x03, 0x0d, 0x45, 0x59, 0x39, 0xfe, 0x9c, 0xe4, 0x20, 0xa4, 0x90, 0x8c, 0x28, 0x76, 0xff, 0xe4, 0x27, 0x14, 0x6f, 0xba, 0x86, 0x59, 0x15, 0xd7, 0x94, 0xa9, 0x30, 0xc2, 0x61, 0x13, 0xe1, 0xaa, 0x89, 0x75, 0x61, 0xe3, 0x69, 0x5c, 0x3a, 0x12, 0xc5, 0x70, 0x44, 0x16, 0x2f, 0x88, 0x6b, 0x3d, 0xb5, 0xf2, 0x4d, 0x9f, 0x44, 0x8d, 0x7b, 0x8d, 0x04, 0x84, 0x2c, 0x3f, 0x37, 0xc7, 0x69, 0xd4, 0xeb, 0x34, 0x4c, 0x0d, 0xaa, 0x11, 0x61, 0xbb, 0xe4, 0x13, 0x53, 0x31, 0xa6, 0x4b, 0x44, 0xaf, 0xf3, 0x98, 0xa7, 0x9c, 0xa4, 0xa8, 0x96, 0x15, 0xf5, 0xc8, 0x71, 0xec, 0x82, 0x21, 0xf9, 0x26, 0x13, 0xaf, 0xdc, 0x78, 0xfc, 0x50, 0x6f, 0x6e, 0xe7, 0x48, 0xbd, 0xbb, 0xc9, 0x7d, 0xa7, 0xd7, 0x89, 0x2e, 0x28, 0x59, 0xd2, 0x10, 0x28, 0x5b, 0xbc, 0xbd, 0xa1, 0x61, 0x7c, 0x71, 0x59, 0xa0, 0x61, 0x49, 0x09, 0xda, 0xe1, 0xf4, 0x7e, 0xcd, 0xdd, 0xec, 0x21, 0xf1, 0x41, 0x7a, 0x0f, 0x3d, 0xa1, 0x6e, 0xbb, 0xe4, 0xde, 0x75, 0x9d, 0x07, 0x46, 0x17, 0x58, 0x79, 0x7e, 0x6d, 0x96, 0xf7, 0x57, 0x8a, 0x9a, 0xc1, 0x9d, 0x4d, 0xbd, 0xe3, 0x3d, 0xc1, 0x60, 0xcf, 0x78, 0x6f, 0xd3, 0xce, 0xc1, 0x1a, 0xc5, 0xaf, 0xbc, 0x59, 0x6b, 0x05, 0xde, 0xda, 0x37, 0x7a, 0xa0, 0x73, 0xdd, 0xbd, 0x97, 0xb4, 0x31, 0x49, 0x6c, 0x6b, 0xf6, 0xab, 0x40, 0x03, 0x15, 0xe3, 0x21, 0x71, 0xf9, 0x72, 0xc8, 0xd5, 0x62, 0x17, 0xe9, 0x8c, 0x1c, 0x3e, 0x0d, 0x21, 0x8a, 0x2a, 0x30, 0xd2, 0x4d, 0xba, 0x3a, 0x4c, 0x7c, 0xc1, 0x6c, 0xb2, 0xf3, 0x83, 0x5a, 0x29, 0x32, 0x2a, 0xa4, 0x12, 0x13, 0x66, 0x68, 0xd4, 0xcf, 0x51, 0x0f, 0xed, 0x0f, 0xb1, 0x45, 0xb2, 0xe5, 0xa6, 0x27, 0xe9, 0xf3, 0x68, 0x45, 0x4f, 0x57, 0xcc, 0x8f, 0xda, 0xd7, 0x6f, 0xdc, 0x83, 0x07, 0xef, 0x29, 0xb1, 0xfe, 0x52, 0xed, 0xcc, 0xee, 0x7b, 0x06, 0x45, 0xd7, 0xdf, 0x78, 0x5e, 0xe1, 0x8e, 0x51, 0xbf, 0x1d, 0x9b, 0xb3, 0x8a, 0x1b, 0x83, 0xbd, 0xfd, 0xd2, 0xc3, 0x13, 0xff, 0x13, 0x44, 0x2d, 0x86, 0x4c, 0x69, 0x07, 0xfb, 0x8f, 0xfc, 0x79, 0xa3, 0x8d, 0xeb, 0x6f, 0xf1, 0x98, 0x83, 0xb4, 0x8d, 0x23, 0x93, 0x9f, 0xb0, 0xf3, 0xd9, 0xb7, 0xa9, 0xdf, 0xef, 0x72, 0x79, 0x6c, 0xcc, 0x49, 0x29, 0x69, 0xa4, 0x7b, 0xba, 0xf9, 0xb5, 0x8c, 0xb8, 0x9e, 0x26, 0x71, 0x1b, 0x19, 0x35, 0x23, 0xd1, 0x90, 0x61, 0x7c, 0x87, 0xa6, 0x5b, 0x63, 0x0f, 0x3c, 0x41, 0xbc, 0x71, 0x39, 0x95, 0xed, 0xac, 0xde, 0xb8, 0xec, 0xfc, 0x86, 0x0d, 0x5f, 0xe8, 0xe8, 0x38, 0xb4, 0xbe, 0xbe, 0x7e, 0xfd, 0xa1, 0x8e, 0x8e, 0x2f, 0x6c, 0x68, 0xf8, 0xa1, 0xb3, 0xa4, 0xc1, 0xe7, 0xab, 0x27, 0xde, 0xe6, 0xf5, 0x3e, 0x5f, 0x43, 0x89, 0x13, 0x7d, 0xb0, 0xff, 0xd9, 0x4b, 0x6a, 0x6b, 0x2f, 0x79, 0x76, 0xff, 0xfe, 0x67, 0x2e, 0xae, 0xad, 0xbd, 0xf8, 0x99, 0xfd, 0x23, 0x07, 0xba, 0x9d, 0xce, 0xee, 0x03, 0x23, 0xeb, 0x2f, 0xeb, 0xce, 0xce, 0xee, 0xbe, 0x2c, 0x79, 0xaf, 0x88, 0x3e, 0x60, 0x7f, 0x09, 0x5a, 0x4a, 0xe7, 0x13, 0x1a, 0x1e, 0x13, 0x19, 0x50, 0x8e, 0xe8, 0x63, 0x22, 0xb7, 0x77, 0x20, 0xa5, 0x8c, 0xb1, 0xf2, 0xc4, 0x4a, 0x5c, 0x92, 0xcf, 0x78, 0x9c, 0x34, 0x14, 0x1e, 0x88, 0x13, 0xa3, 0x6d, 0xe2, 0x3e, 0x9f, 0x08, 0x00, 0x3f, 0x0d, 0x41, 0x04, 0xa1, 0x0f, 0xa6, 0x81, 0x7c, 0x9c, 0x82, 0xc5, 0x41, 0x68, 0xba, 0x72, 0xf2, 0x13, 0xfc, 0xdf, 0xb4, 0x0d, 0xf9, 0xcc, 0x85, 0x8f, 0xcb, 0xc6, 0x13, 0x32, 0x69, 0xed, 0x09, 0x74, 0x8c, 0x61, 0x2a, 0x27, 0x4d, 0x47, 0xc7, 0x48, 0xc4, 0x1d, 0x3a, 0x4d, 0x86, 0x6d, 0x44, 0x30, 0xb1, 0x33, 0x1c, 0xe6, 0xc6, 0x93, 0x19, 0xa6, 0xa7, 0x01, 0xff, 0xd6, 0x23, 0x86, 0x2c, 0x0c, 0xb3, 0x51, 0xe4, 0x19, 0x2d, 0xd2, 0xca, 0xf0, 0x1a, 0x15, 0x53, 0xc1, 0x29, 0x8d, 0x69, 0xb1, 0x35, 0xa8, 0x5f, 0x16, 0xba, 0xbb, 0x69, 0x41, 0x5f, 0xbd, 0x37, 0x16, 0x34, 0xf5, 0x44, 0x55, 0x06, 0xa5, 0xa3, 0xd6, 0x53, 0xb8, 0xa2, 0xb3, 0xb8, 0x78, 0xc9, 0x85, 0x5d, 0x3d, 0xbb, 0x17, 0x15, 0x54, 0x94, 0x1c, 0xd7, 0x57, 0x7b, 0x1f, 0x38, 0xb6, 0xa8, 0x7b, 0xde, 0x42, 0x7b, 0x61, 0xad, 0xaf, 0x75, 0x09, 0x7a, 0x47, 0x63, 0xd3, 0xe8, 0x8d, 0xfe, 0xce, 0x6d, 0xdd, 0xad, 0x17, 0xae, 0xac, 0x88, 0xac, 0xdc, 0x3f, 0xaf, 0xf3, 0xfa, 0x5e, 0x94, 0x93, 0xe3, 0x62, 0x58, 0x32, 0x9f, 0xf0, 0x07, 0x30, 0x9f, 0xf4, 0x30, 0xe7, 0xe3, 0xcc, 0x91, 0xc7, 0xcb, 0x28, 0x94, 0x86, 0xdc, 0xfb, 0x9c, 0x53, 0x10, 0x35, 0x46, 0x66, 0x20, 0x6a, 0x24, 0xa6, 0xd8, 0x19, 0xb3, 0x6d, 0xe3, 0x28, 0x22, 0x1c, 0xec, 0x6a, 0x44, 0xb2, 0x1e, 0x4a, 0x66, 0x9d, 0x9e, 0x0e, 0xd4, 0xb0, 0x1a, 0x0d, 0x88, 0xa9, 0xa9, 0xca, 0xcf, 0x75, 0xd8, 0x0d, 0x1e, 0xa3, 0x47, 0x8e, 0xfd, 0x78, 0x2a, 0x38, 0x87, 0xc5, 0x38, 0xc5, 0x4b, 0x81, 0x54, 0x67, 0x40, 0xe6, 0x40, 0xf7, 0x06, 0x62, 0x7e, 0xbd, 0x2d, 0xb7, 0x3c, 0xdb, 0x59, 0x1e, 0xb6, 0x75, 0xb5, 0xd7, 0x35, 0x65, 0xb8, 0x4b, 0xdc, 0xee, 0x62, 0x77, 0x46, 0xd3, 0xd6, 0xbe, 0xf6, 0xe2, 0x45, 0xbb, 0x3a, 0x3a, 0x77, 0x2d, 0x2e, 0x6e, 0xef, 0xaf, 0xaf, 0x2b, 0x98, 0x37, 0x1a, 0x8f, 0x6f, 0x9e, 0x97, 0x7f, 0xbd, 0x25, 0x18, 0xcd, 0xf1, 0x47, 0x7d, 0x04, 0xdd, 0xd2, 0x5f, 0xdf, 0x8f, 0x9f, 0x6a, 0xab, 0x4c, 0x8a, 0xee, 0x95, 0x6d, 0xd2, 0xe3, 0x39, 0xbb, 0x16, 0x34, 0x8f, 0xf6, 0xe6, 0xe6, 0xf6, 0x8e, 0x36, 0x2f, 0xd8, 0x95, 0x93, 0x33, 0xda, 0x5d, 0xb5, 0x96, 0xb8, 0x1b, 0xaf, 0xad, 0x3a, 0x26, 0xf3, 0xcf, 0x55, 0x30, 0x91, 0x3e, 0x05, 0x3a, 0x16, 0x31, 0x0b, 0xe3, 0x2a, 0x07, 0x8c, 0x78, 0x3e, 0xec, 0xe5, 0x38, 0x11, 0x67, 0xdd, 0xc2, 0x90, 0xe0, 0xda, 0x30, 0xf6, 0x84, 0x77, 0x4c, 0x99, 0x01, 0x92, 0xd9, 0xcc, 0x52, 0x3b, 0x8f, 0xa1, 0xe9, 0x8f, 0x07, 0xe2, 0x19, 0x88, 0xf1, 0xe4, 0x18, 0xf5, 0x3c, 0xc7, 0x14, 0xa1, 0x42, 0x9e, 0x92, 0x01, 0x18, 0xa2, 0x47, 0x46, 0x57, 0x61, 0x13, 0x93, 0x04, 0xe6, 0x77, 0xb1, 0x1c, 0xdb, 0x10, 0xcb, 0xe6, 0x1e, 0x30, 0x5d, 0xf0, 0x63, 0x39, 0xfe, 0x89, 0x0d, 0x19, 0x26, 0xc4, 0x81, 0xb2, 0x69, 0x6f, 0xa9, 0xb2, 0xe4, 0x7a, 0x6d, 0x82, 0x22, 0x3f, 0x38, 0xa2, 0xaa, 0x5b, 0xbe, 0xbd, 0xbe, 0x79, 0x5b, 0x5f, 0x61, 0xa0, 0xa6, 0xc3, 0xff, 0x98, 0xaf, 0xd5, 0xf9, 0x54, 0xd0, 0xb9, 0xab, 0xc9, 0x54, 0x60, 0x40, 0xae, 0x40, 0x53, 0xbc, 0xd1, 0x5f, 0xdb, 0xa8, 0x73, 0xe4, 0x3a, 0xb2, 0xcc, 0xa2, 0xbd, 0x73, 0x4b, 0x87, 0x3f, 0xaf, 0x67, 0x43, 0x43, 0x74, 0x70, 0xe1, 0xbc, 0xf0, 0x8b, 0x6a, 0x2d, 0xb5, 0x4d, 0xfa, 0x04, 0xbf, 0x00, 0xeb, 0xa3, 0x84, 0xf9, 0x4a, 0x3c, 0xa3, 0x88, 0xc7, 0x3c, 0x2a, 0x44, 0x2c, 0x6f, 0x45, 0x98, 0x63, 0x13, 0x33, 0x25, 0xc0, 0xb0, 0x3c, 0xc7, 0x53, 0xc8, 0x3f, 0x86, 0x67, 0x65, 0x01, 0x86, 0xaa, 0xf3, 0x23, 0x29, 0xe8, 0xbf, 0x51, 0x9c, 0x5c, 0x33, 0x73, 0xc8, 0xbc, 0x8d, 0x88, 0x47, 0xbe, 0x54, 0x3e, 0x01, 0x88, 0x23, 0x10, 0x84, 0xe7, 0x44, 0xfe, 0xa9, 0x5c, 0x04, 0xd4, 0xd4, 0x14, 0xf6, 0x85, 0xc2, 0x3e, 0x2f, 0xd5, 0x0c, 0xc9, 0x65, 0xec, 0xac, 0x75, 0x33, 0x05, 0x28, 0x97, 0x9c, 0x4a, 0x82, 0x18, 0x32, 0x7a, 0xf0, 0xd7, 0xcb, 0xeb, 0x5e, 0x73, 0x66, 0x95, 0x58, 0xdb, 0xb7, 0x74, 0x06, 0x23, 0xcb, 0x2f, 0xe9, 0x2a, 0xee, 0x6f, 0xaf, 0xb5, 0xd5, 0xa8, 0xec, 0x3a, 0xa3, 0x27, 0x14, 0x09, 0xba, 0x63, 0xb9, 0x56, 0xbd, 0xbf, 0x3a, 0xd7, 0x59, 0xe3, 0xdc, 0x3d, 0x0f, 0xfd, 0x94, 0xdd, 0x9f, 0x57, 0xf8, 0x0c, 0xcf, 0x13, 0x5e, 0xdc, 0x7b, 0xe9, 0x60, 0xb9, 0xc9, 0x57, 0x68, 0x5f, 0xac, 0xd3, 0x58, 0xec, 0x16, 0x4b, 0x6e, 0x4d, 0xd0, 0x53, 0x99, 0x6b, 0xd1, 0x68, 0x7d, 0xe8, 0x1f, 0xd0, 0xc0, 0x41, 0xa0, 0xd5, 0x5b, 0x30, 0x0f, 0xaa, 0x99, 0xfd, 0x71, 0x95, 0x16, 0xb1, 0x5c, 0x16, 0x4c, 0xfc, 0xe4, 0x2e, 0xea, 0x24, 0xae, 0xc5, 0x40, 0x35, 0x12, 0x76, 0x9b, 0x32, 0xdd, 0x29, 0x0d, 0x77, 0x34, 0x15, 0x23, 0xe3, 0x0c, 0x99, 0xb6, 0x21, 0x0a, 0x6c, 0xca, 0x10, 0x18, 0x60, 0x42, 0x86, 0x44, 0xbe, 0xa9, 0x54, 0x20, 0x87, 0xd2, 0xaf, 0xb7, 0x14, 0x14, 0x51, 0xf8, 0x0a, 0x63, 0x72, 0x76, 0x08, 0x53, 0xd1, 0x80, 0x81, 0x1c, 0x15, 0x04, 0xe3, 0x3a, 0x69, 0x6f, 0x15, 0xa5, 0xe8, 0xd8, 0x84, 0x26, 0x3a, 0x36, 0x74, 0x81, 0xaa, 0x6a, 0xd1, 0x68, 0x7d, 0xf3, 0x86, 0x8e, 0x60, 0x5f, 0xdb, 0xa2, 0xfe, 0xfc, 0xbe, 0x9d, 0x3d, 0x9d, 0x1b, 0x7b, 0x22, 0xc6, 0x25, 0xde, 0x32, 0xb5, 0x60, 0xf6, 0x46, 0x83, 0xde, 0x88, 0xcf, 0xb0, 0x66, 0x6c, 0x68, 0xb0, 0xb1, 0xaa, 0xa8, 0x2b, 0x43, 0x50, 0x68, 0xad, 0x46, 0xfc, 0x1c, 0x99, 0x2f, 0xb9, 0x6d, 0x2b, 0x23, 0xab, 0xb7, 0xda, 0xed, 0x1b, 0x96, 0x35, 0x6c, 0xe9, 0x2f, 0x0e, 0xb4, 0xae, 0xa9, 0x0d, 0xea, 0x74, 0x36, 0x5d, 0x28, 0x98, 0x65, 0x0b, 0x95, 0x3b, 0xda, 0xe2, 0xd7, 0xa2, 0xf6, 0x96, 0xea, 0xba, 0x8a, 0x88, 0x5a, 0xaf, 0xc6, 0xac, 0xbc, 0xd7, 0x0e, 0xc2, 0x7f, 0xaf, 0x02, 0x9d, 0xb4, 0x4c, 0x01, 0xd3, 0x2c, 0x77, 0xdc, 0x46, 0x12, 0x38, 0x06, 0x0f, 0x25, 0x41, 0x1a, 0x47, 0x12, 0xbe, 0x8f, 0x0e, 0x58, 0x40, 0x29, 0x13, 0xb2, 0x44, 0x16, 0x9a, 0x30, 0x70, 0x1c, 0x84, 0xdf, 0x20, 0x35, 0x68, 0x9b, 0x81, 0x13, 0x65, 0x94, 0x51, 0x8c, 0xd3, 0xa2, 0x4b, 0x59, 0xa6, 0x50, 0x9e, 0xee, 0x50, 0xe8, 0x14, 0xa7, 0x41, 0x86, 0x9a, 0x86, 0xab, 0x26, 0x5d, 0xae, 0x36, 0xaa, 0xd2, 0x23, 0x3b, 0x41, 0x3f, 0xaa, 0x81, 0xab, 0x3b, 0x61, 0x6d, 0xf0, 0x8c, 0x85, 0x59, 0x1d, 0x57, 0xc3, 0x3a, 0xe6, 0x8c, 0x22, 0x96, 0x97, 0x3e, 0x99, 0xec, 0x56, 0x10, 0x3b, 0xd9, 0x84, 0xf1, 0xb6, 0xbc, 0x49, 0x30, 0x54, 0x78, 0x70, 0xc4, 0xad, 0xc4, 0x31, 0x67, 0x7c, 0x46, 0x32, 0x4d, 0x01, 0x6e, 0x08, 0x82, 0x28, 0x23, 0x58, 0x04, 0x90, 0x2e, 0xa8, 0x29, 0xb5, 0x92, 0xb8, 0xbe, 0x13, 0x6e, 0x88, 0xf4, 0xc0, 0x01, 0x58, 0xd9, 0xf8, 0xc0, 0xec, 0x8b, 0x7a, 0x08, 0x2c, 0x3c, 0x8a, 0xe8, 0xd1, 0xc7, 0x2c, 0x37, 0x81, 0x73, 0x4c, 0x86, 0xa1, 0x3b, 0xa4, 0xab, 0xf5, 0x2e, 0xce, 0x88, 0xee, 0x3c, 0x8e, 0x76, 0x22, 0x6d, 0x56, 0x8d, 0x5b, 0xda, 0x3b, 0x9a, 0x69, 0xfd, 0xc3, 0x1f, 0x6c, 0x86, 0x11, 0x3c, 0x3c, 0xf1, 0x15, 0x86, 0xda, 0x3c, 0xe2, 0x9f, 0x41, 0x7b, 0x0d, 0x4c, 0x95, 0xdc, 0x40, 0x3d, 0x41, 0x4c, 0x1f, 0x98, 0xb9, 0xc9, 0x66, 0xb0, 0x09, 0xec, 0x87, 0x35, 0xd3, 0xf7, 0x57, 0x03, 0x63, 0x30, 0x85, 0xe8, 0xfe, 0x4a, 0x66, 0x94, 0x0c, 0xc5, 0x41, 0x82, 0x41, 0x10, 0x2f, 0x6a, 0xfc, 0xb3, 0x0c, 0x85, 0xf4, 0x82, 0xb6, 0x24, 0x7c, 0x35, 0x6a, 0xd6, 0xb9, 0xf4, 0xd2, 0xf9, 0x06, 0x07, 0xfb, 0xcb, 0x25, 0xa1, 0x22, 0xfd, 0x44, 0xc8, 0x11, 0xc2, 0x27, 0xb2, 0x8b, 0x4c, 0xd9, 0xfa, 0x2b, 0xc8, 0xb9, 0xc6, 0xfb, 0xd2, 0x85, 0x8c, 0x5d, 0x20, 0x56, 0xff, 0x41, 0x19, 0xab, 0x44, 0x4d, 0x01, 0x3b, 0xa1, 0x11, 0x9b, 0xc8, 0xe4, 0x56, 0x26, 0xbf, 0x6d, 0x1c, 0x78, 0x4c, 0xb6, 0xe8, 0x24, 0xb8, 0x15, 0xf6, 0xb2, 0x32, 0xa1, 0xeb, 0xb3, 0x12, 0x52, 0xbe, 0x01, 0xca, 0xbf, 0x20, 0xae, 0x05, 0x9d, 0x3b, 0x2f, 0x4e, 0x89, 0xa3, 0x52, 0x2a, 0x44, 0x81, 0xc5, 0x48, 0xc3, 0x74, 0xd0, 0x0a, 0x1f, 0x4f, 0x1c, 0x1c, 0x3c, 0x0a, 0x9f, 0x1b, 0x07, 0x1e, 0x35, 0x21, 0x02, 0xa8, 0x63, 0x15, 0x43, 0x20, 0x13, 0x88, 0x21, 0x62, 0x4c, 0x18, 0xb3, 0xb6, 0xef, 0x6b, 0xdd, 0x50, 0x36, 0xdc, 0xb2, 0x6f, 0x1f, 0xfc, 0x6b, 0x15, 0x6e, 0xdf, 0xd7, 0xb2, 0xe1, 0xb3, 0x98, 0xf8, 0x03, 0xf2, 0xa0, 0x15, 0xfe, 0xc9, 0xfc, 0xfc, 0x36, 0xac, 0x62, 0x6f, 0xc3, 0xc7, 0xce, 0x66, 0x6b, 0x7a, 0x1b, 0xcb, 0x4c, 0x30, 0xf8, 0xd8, 0xb5, 0xf2, 0x99, 0x08, 0x7b, 0x1f, 0xea, 0xe1, 0x4f, 0xc2, 0x5e, 0x2a, 0x3c, 0xaa, 0xd7, 0x10, 0xfb, 0x71, 0x6a, 0x79, 0xe1, 0x42, 0x56, 0x17, 0xca, 0x40, 0x45, 0x18, 0xb8, 0xb7, 0xd5, 0xac, 0xd1, 0x79, 0x4c, 0x9a, 0x80, 0x5d, 0xc9, 0xe9, 0x6b, 0x9b, 0x16, 0xd8, 0x72, 0x0a, 0xf8, 0x9f, 0x28, 0xfc, 0xd6, 0x8a, 0xb1, 0x8d, 0x03, 0x2e, 0x65, 0x8e, 0x3a, 0x58, 0x75, 0xfd, 0x97, 0xbf, 0xda, 0x29, 0xdf, 0xf9, 0xdf, 0x87, 0x2f, 0x83, 0xba, 0x82, 0x3c, 0xf1, 0x0e, 0x16, 0xf9, 0x72, 0x14, 0xa3, 0xeb, 0x66, 0x18, 0x6d, 0xc2, 0xcb, 0xd8, 0x6e, 0x6a, 0xcf, 0xaa, 0x78, 0x5c, 0x04, 0x7e, 0x88, 0x4b, 0xf2, 0x8d, 0x01, 0x33, 0xaf, 0x87, 0x9f, 0x61, 0xf4, 0xb1, 0xa4, 0xa1, 0x3f, 0x7f, 0x1c, 0xbf, 0x78, 0x1c, 0x6d, 0x83, 0xff, 0x48, 0x5d, 0x77, 0x70, 0xbb, 0xf0, 0x26, 0xa1, 0x11, 0x0b, 0xd8, 0x42, 0xda, 0x89, 0xc2, 0xdc, 0x1a, 0x6c, 0xe4, 0xbf, 0x03, 0x33, 0xb6, 0xe6, 0x71, 0x3e, 0x11, 0xc9, 0x59, 0x4f, 0xa7, 0x02, 0x39, 0x41, 0x59, 0x9f, 0xb4, 0x3e, 0x8d, 0x90, 0xa9, 0x90, 0x49, 0x2c, 0x35, 0xcf, 0x9f, 0xf6, 0x68, 0xe0, 0x71, 0x8b, 0x3f, 0xc4, 0x8a, 0xd6, 0xfc, 0xf4, 0x51, 0xd1, 0x50, 0x4d, 0x6e, 0x30, 0x80, 0x05, 0xad, 0xa2, 0xb6, 0xac, 0xaa, 0xbc, 0xb6, 0x29, 0x2b, 0xda, 0x1f, 0xe3, 0xae, 0xb2, 0x29, 0x14, 0x81, 0x30, 0xc6, 0xb1, 0x40, 0x61, 0x75, 0x5f, 0x73, 0x5e, 0x57, 0x45, 0x0e, 0x93, 0x68, 0xc7, 0x3a, 0x68, 0xc7, 0xf3, 0xd0, 0x8e, 0xfe, 0x64, 0x03, 0x88, 0x85, 0xe8, 0xfa, 0x24, 0x43, 0x8b, 0xe0, 0x64, 0x40, 0xc5, 0x4c, 0xd2, 0xf5, 0xf3, 0xa7, 0x3f, 0x97, 0x41, 0x5d, 0xcf, 0x9f, 0x0d, 0xfa, 0x3f, 0x30, 0xf0, 0x78, 0x79, 0x30, 0x93, 0x36, 0x2f, 0x15, 0x63, 0x5e, 0x36, 0x9b, 0x0d, 0xc1, 0x30, 0x58, 0xcc, 0x98, 0xa9, 0xec, 0x8b, 0x66, 0x35, 0xd5, 0x96, 0x57, 0x95, 0xd5, 0x2a, 0xb4, 0x02, 0x17, 0xf4, 0xe5, 0x72, 0xeb, 0x7c, 0x35, 0xbd, 0x79, 0xcd, 0xad, 0xd5, 0x85, 0x81, 0x18, 0x42, 0x81, 0xa0, 0x42, 0x61, 0x83, 0xfa, 0x7a, 0xb8, 0xcd, 0xb8, 0x92, 0x7f, 0x14, 0xb6, 0x9e, 0x04, 0xbe, 0x1a, 0x65, 0xc5, 0x11, 0x02, 0x46, 0x01, 0xcf, 0x04, 0x0f, 0x2b, 0x82, 0x9e, 0x2d, 0xfb, 0xc7, 0xa3, 0x9e, 0x86, 0x8b, 0x9c, 0x66, 0xd5, 0x76, 0x6e, 0x33, 0xea, 0x08, 0xe4, 0x48, 0x27, 0xe9, 0x5c, 0xf8, 0x3d, 0x7c, 0x7b, 0xf9, 0xf4, 0xe5, 0x7d, 0xb4, 0x3c, 0x1b, 0xac, 0xa8, 0x41, 0x46, 0xb4, 0x62, 0x4c, 0x6d, 0x76, 0x5e, 0xd4, 0x00, 0x25, 0xb8, 0x9c, 0x80, 0xf4, 0x6d, 0x5a, 0x7e, 0x03, 0x77, 0x1f, 0x7e, 0x96, 0xbf, 0x8e, 0x71, 0x30, 0xf9, 0xf1, 0xb0, 0x40, 0xe3, 0xfe, 0x30, 0x78, 0x15, 0x55, 0x56, 0x89, 0xbe, 0x95, 0xcf, 0x52, 0xf5, 0x83, 0x98, 0x5a, 0xa9, 0x14, 0x1c, 0x4b, 0xbc, 0x60, 0xc4, 0x44, 0x18, 0xf8, 0xc8, 0x54, 0x74, 0x45, 0xd1, 0x97, 0xc4, 0x36, 0x96, 0xbc, 0x01, 0xb4, 0xcb, 0x51, 0xd2, 0x92, 0xeb, 0xab, 0xab, 0x28, 0xb5, 0x78, 0x02, 0x68, 0x8f, 0xa3, 0xac, 0x39, 0xd7, 0x5f, 0x57, 0x51, 0xc2, 0x57, 0x84, 0x0b, 0xca, 0xfa, 0x6b, 0xdc, 0xd9, 0xde, 0xec, 0xbc, 0xfc, 0xd2, 0x85, 0xd5, 0x9e, 0x6c, 0x0f, 0xe1, 0x6b, 0x77, 0xc3, 0x1c, 0x79, 0x1c, 0xe6, 0x88, 0x8d, 0xc9, 0x61, 0xee, 0x95, 0xd1, 0x98, 0x55, 0x02, 0x81, 0x56, 0x26, 0xed, 0xe8, 0x4a, 0x6c, 0x55, 0x89, 0x27, 0x1c, 0x43, 0xe3, 0x61, 0xc1, 0x17, 0x56, 0xfe, 0x32, 0x30, 0x90, 0x00, 0xb2, 0xa6, 0xf3, 0x86, 0x06, 0xd4, 0x89, 0x50, 0xad, 0x29, 0xf9, 0x05, 0x27, 0x37, 0x3b, 0x17, 0xb0, 0xf3, 0xc4, 0xf4, 0x4a, 0x64, 0x23, 0x76, 0xe1, 0x89, 0xdf, 0x39, 0xba, 0xdb, 0xa5, 0x32, 0x30, 0x89, 0x64, 0x39, 0x98, 0x71, 0x84, 0x0a, 0x8e, 0xba, 0x2c, 0xbb, 0xcb, 0x69, 0xcf, 0xc9, 0xca, 0xb1, 0x04, 0x6d, 0x02, 0x8c, 0xba, 0x11, 0x18, 0xe4, 0x14, 0xf3, 0x9f, 0x9a, 0x95, 0x64, 0x9c, 0x7c, 0xc6, 0x27, 0x8d, 0xd6, 0x40, 0x80, 0x55, 0x67, 0xba, 0xf2, 0xb3, 0x34, 0xd5, 0x91, 0xba, 0x46, 0x32, 0x3b, 0xd1, 0x8f, 0x9d, 0x66, 0xf5, 0xd6, 0xed, 0x0d, 0xdc, 0x55, 0x5a, 0x1d, 0x0f, 0xd3, 0x94, 0xcb, 0xb0, 0x7b, 0x0d, 0xc9, 0x79, 0x8a, 0xfa, 0x61, 0x40, 0x25, 0x94, 0xd0, 0x0f, 0xaf, 0x07, 0x9a, 0x7c, 0x15, 0x68, 0x62, 0x67, 0xdc, 0xcc, 0xed, 0x67, 0xa1, 0x09, 0x9b, 0xa2, 0x09, 0x27, 0x7f, 0x49, 0xd2, 0xc4, 0x46, 0xfb, 0x42, 0x90, 0x4d, 0x22, 0x34, 0x48, 0x6a, 0xe2, 0xf7, 0x14, 0x45, 0x80, 0xf8, 0x6c, 0xa2, 0xc3, 0x72, 0x26, 0x3a, 0xe6, 0xf4, 0x57, 0x96, 0xba, 0x7a, 0xa5, 0x92, 0x99, 0x44, 0x2a, 0x95, 0xa3, 0x23, 0x44, 0xab, 0x38, 0xee, 0xd3, 0x5b, 0x82, 0x61, 0x5e, 0xb4, 0x27, 0xe9, 0x90, 0x3a, 0xa0, 0x48, 0x12, 0x22, 0xe0, 0xa3, 0x73, 0xee, 0x2e, 0x4a, 0x08, 0x58, 0xa5, 0x32, 0x25, 0xe4, 0x75, 0x8a, 0x26, 0xb6, 0x6f, 0x85, 0x89, 0xb8, 0x27, 0x49, 0x09, 0x8c, 0xa7, 0x93, 0x42, 0x83, 0x70, 0x4e, 0x40, 0xa6, 0xc3, 0x21, 0xa0, 0xc3, 0xaf, 0x85, 0x97, 0x60, 0xdd, 0x96, 0x31, 0x5b, 0x66, 0x70, 0x10, 0xbf, 0xcc, 0x41, 0x94, 0x0a, 0x2c, 0x30, 0x1c, 0x2b, 0x70, 0x43, 0xc0, 0xb9, 0x40, 0x9f, 0x10, 0xd9, 0xa1, 0x19, 0x4c, 0xc5, 0x47, 0x99, 0xca, 0x99, 0x73, 0x81, 0x8a, 0x64, 0xb5, 0x5a, 0xcb, 0xac, 0x65, 0x3e, 0xbf, 0xc1, 0x17, 0x00, 0x9e, 0xa3, 0x12, 0xb3, 0x4f, 0xc7, 0x73, 0xd2, 0xcc, 0x78, 0x72, 0x2a, 0x99, 0x86, 0x17, 0xa1, 0x3b, 0xd2, 0x2e, 0x02, 0x0b, 0x2c, 0x8f, 0x34, 0x6c, 0x0a, 0x0f, 0x97, 0xf5, 0xc1, 0xca, 0xf0, 0xc0, 0xca, 0x80, 0x25, 0xe2, 0x81, 0x25, 0x12, 0x2e, 0x80, 0xa6, 0xdf, 0x01, 0x7c, 0xeb, 0x5e, 0xca, 0xb7, 0x9c, 0xcc, 0x66, 0xf9, 0x10, 0x3b, 0x8b, 0x99, 0x9a, 0xdc, 0xb2, 0x8c, 0x9f, 0xf8, 0xc2, 0x24, 0x59, 0x98, 0x35, 0xc5, 0xbd, 0x68, 0x7a, 0x62, 0x12, 0x33, 0x94, 0x93, 0xc9, 0x97, 0xd5, 0x43, 0x33, 0x9e, 0x93, 0xc9, 0x6d, 0xb3, 0x66, 0x3b, 0xac, 0x4e, 0x9b, 0x13, 0x58, 0x9a, 0x70, 0x5a, 0x96, 0x96, 0x98, 0xdb, 0x68, 0xf7, 0x29, 0x9c, 0x0d, 0x31, 0x84, 0x2d, 0xc1, 0xd4, 0x3e, 0x95, 0xc3, 0xa1, 0x00, 0xe1, 0x54, 0xa8, 0x23, 0x31, 0x9e, 0xeb, 0x52, 0xe3, 0xf9, 0xa5, 0xe4, 0x40, 0x52, 0x4e, 0x9c, 0x6e, 0x88, 0x4e, 0x61, 0xce, 0x3e, 0xca, 0x89, 0xcf, 0x92, 0x35, 0x1e, 0x3e, 0x6d, 0xae, 0x53, 0x58, 0xf8, 0xf4, 0x91, 0xcf, 0x87, 0xbe, 0xd3, 0x91, 0x4f, 0xdb, 0x77, 0xe3, 0x69, 0x46, 0x3e, 0x0d, 0x9b, 0xff, 0xf5, 0xe9, 0x07, 0xfe, 0x54, 0xf2, 0xe0, 0x58, 0x9a, 0x81, 0xa7, 0xb4, 0xba, 0x16, 0x68, 0x75, 0x5b, 0x62, 0xec, 0x57, 0xcb, 0x63, 0x6f, 0x4b, 0x01, 0xa5, 0x91, 0x15, 0x9d, 0x5a, 0xb8, 0xa9, 0x91, 0xb7, 0x4c, 0x8d, 0x3c, 0x9b, 0x54, 0x7b, 0x23, 0x89, 0xb3, 0x0c, 0x79, 0xe0, 0xa7, 0x3f, 0x26, 0xab, 0xd8, 0x0a, 0x9d, 0x26, 0xab, 0x38, 0x7d, 0xa7, 0x79, 0x79, 0x0d, 0xa3, 0x91, 0x53, 0x07, 0xbc, 0x62, 0x3b, 0xdd, 0x4a, 0xd2, 0xf4, 0xc8, 0x47, 0xf6, 0x16, 0xe0, 0xcf, 0x1a, 0x6e, 0x0b, 0xab, 0xa3, 0xfb, 0x52, 0x16, 0xe8, 0xaa, 0xf3, 0x12, 0x3c, 0x89, 0xf0, 0x66, 0x79, 0xe6, 0xe5, 0x77, 0x0b, 0xd3, 0x36, 0x2b, 0x90, 0xbc, 0x89, 0xed, 0x34, 0xa2, 0x1e, 0x9a, 0xd3, 0x13, 0xa8, 0x03, 0x62, 0x7e, 0xae, 0x27, 0xc7, 0x6a, 0xd6, 0xaa, 0xe9, 0x86, 0x28, 0x4e, 0x6d, 0x88, 0x81, 0xb3, 0x21, 0xed, 0x7b, 0x1b, 0xf6, 0x10, 0xa6, 0x8b, 0x2e, 0x3a, 0x25, 0x86, 0xc2, 0x07, 0xb3, 0x91, 0xf7, 0xb9, 0x2d, 0xa8, 0x8d, 0xf0, 0xe0, 0x2f, 0x9f, 0x05, 0x63, 0x9f, 0xf9, 0x3f, 0xd8, 0x37, 0x9f, 0x38, 0xb5, 0x59, 0x9f, 0xb5, 0x6f, 0x57, 0x50, 0x1e, 0xda, 0x80, 0xcc, 0x73, 0xea, 0x1b, 0xf0, 0x54, 0xe9, 0xa9, 0xb9, 0xf4, 0x4d, 0x8e, 0x8f, 0xf0, 0x12, 0xc8, 0x03, 0xc5, 0x4c, 0x33, 0xb3, 0x4d, 0xee, 0x9b, 0x1b, 0x44, 0x01, 0xd9, 0xac, 0x59, 0x45, 0x25, 0x04, 0x91, 0x21, 0x7e, 0xb4, 0xc4, 0x6f, 0x40, 0x89, 0x44, 0x31, 0x9f, 0x5e, 0xa5, 0xa7, 0xc9, 0x02, 0x89, 0x90, 0x67, 0x29, 0xc9, 0x23, 0x0c, 0x42, 0x7e, 0x51, 0x58, 0x30, 0x10, 0x77, 0x20, 0x86, 0x84, 0xb0, 0x2a, 0xc8, 0xf3, 0xba, 0x67, 0xca, 0x15, 0xea, 0x33, 0xc8, 0x15, 0xec, 0x59, 0xa8, 0x81, 0xb9, 0xd3, 0xca, 0x1d, 0xdf, 0x3c, 0x7b, 0x8c, 0x05, 0x61, 0x3b, 0x88, 0x26, 0xb3, 0xd6, 0xa1, 0x74, 0xf3, 0xd9, 0x88, 0xf5, 0x7f, 0x8d, 0x7c, 0xcb, 0x6c, 0xe5, 0x5e, 0xc2, 0x8f, 0xf0, 0x5d, 0x67, 0xd3, 0x21, 0xb6, 0xd2, 0xb8, 0x21, 0x5d, 0xfb, 0x68, 0x99, 0x5d, 0xdc, 0xd5, 0xf8, 0x1e, 0xfe, 0x36, 0xc6, 0x4c, 0xee, 0x69, 0x41, 0xf6, 0x21, 0x1c, 0x62, 0x15, 0x8d, 0x7a, 0x81, 0x40, 0xe2, 0xd3, 0x67, 0x6a, 0x54, 0x02, 0xcf, 0x98, 0x91, 0x99, 0x4f, 0x5e, 0x3c, 0x47, 0x92, 0xe7, 0x61, 0x64, 0x7c, 0xd0, 0x91, 0xf9, 0x9b, 0x9b, 0x9c, 0x57, 0x69, 0x3d, 0xb1, 0xbc, 0xbc, 0x98, 0x47, 0x7b, 0x95, 0xb3, 0x69, 0x33, 0x77, 0x75, 0x7e, 0xf7, 0xda, 0x4a, 0x77, 0x45, 0xc8, 0x6c, 0x0e, 0x55, 0xb8, 0x2b, 0xd7, 0x76, 0xcb, 0xf8, 0xc1, 0x43, 0xd2, 0x4e, 0x74, 0xde, 0xe4, 0x93, 0x4c, 0x06, 0xd3, 0xfa, 0xb8, 0x08, 0x6b, 0x20, 0x79, 0xec, 0x6a, 0x4c, 0x59, 0x4e, 0x25, 0x62, 0x14, 0x94, 0x11, 0xe6, 0x6d, 0x9b, 0x7a, 0x0a, 0xb2, 0xce, 0xe2, 0x29, 0x17, 0xae, 0x81, 0xc7, 0x03, 0x3e, 0x2a, 0x6e, 0x5b, 0x23, 0xf2, 0x6d, 0x87, 0x7c, 0xad, 0x1a, 0x1d, 0xfa, 0xa2, 0xa7, 0xd0, 0x91, 0x99, 0x1d, 0x30, 0xf5, 0x76, 0xb6, 0x5f, 0x5d, 0x12, 0x72, 0xe8, 0xb3, 0xf4, 0xca, 0xee, 0x3e, 0xd0, 0xdf, 0xfe, 0xcc, 0x35, 0xb3, 0x25, 0xfc, 0xe5, 0x20, 0x43, 0xad, 0x97, 0xbd, 0xa3, 0x80, 0x30, 0x2c, 0x0b, 0x32, 0x3f, 0xf1, 0xea, 0x01, 0x49, 0x89, 0x28, 0xc1, 0x43, 0x29, 0x61, 0xc8, 0x44, 0xa6, 0xfe, 0xf9, 0xb3, 0xd3, 0x88, 0x50, 0x48, 0x3c, 0x81, 0xe4, 0x34, 0x10, 0x8f, 0xc8, 0xee, 0xc1, 0x0e, 0xd2, 0x00, 0x0b, 0x0b, 0x06, 0xe4, 0x88, 0x0a, 0x76, 0xc6, 0xe6, 0x09, 0x7b, 0x48, 0x44, 0x05, 0xa3, 0x39, 0x35, 0x23, 0x3d, 0xd1, 0xd4, 0xc1, 0xd0, 0x9d, 0x55, 0xb6, 0x02, 0xaf, 0xc9, 0xe8, 0x2d, 0xc8, 0xc2, 0x86, 0xc4, 0xaf, 0x85, 0x76, 0xee, 0x2b, 0x83, 0x3a, 0x67, 0x9e, 0x33, 0xa7, 0x20, 0x5b, 0x0b, 0xbf, 0xe4, 0x67, 0x93, 0x5f, 0xc8, 0xa1, 0x13, 0x77, 0x00, 0xda, 0xfc, 0x45, 0xc6, 0xca, 0x84, 0xe3, 0x01, 0x32, 0x26, 0x40, 0xaf, 0x55, 0xa9, 0xf3, 0xdc, 0x21, 0x06, 0x06, 0xc6, 0x68, 0xc8, 0xd4, 0x11, 0x45, 0x94, 0xb1, 0x22, 0xab, 0x8c, 0x0a, 0xa7, 0xe3, 0x7c, 0xd1, 0xb2, 0x7a, 0x8e, 0xcc, 0x1b, 0x5f, 0x79, 0x11, 0xc6, 0x9e, 0xbc, 0xae, 0xae, 0xde, 0xc1, 0x6d, 0x6d, 0xb5, 0xc3, 0x4b, 0xfb, 0x16, 0x2e, 0xaf, 0x38, 0x74, 0xe1, 0xd5, 0x16, 0x7f, 0x31, 0x6f, 0xb3, 0x78, 0x83, 0x5e, 0xcb, 0x6e, 0x2b, 0xb4, 0xd5, 0x7a, 0xcd, 0x65, 0x79, 0x55, 0xbe, 0xcc, 0x19, 0x7a, 0x51, 0x79, 0x5a, 0xbd, 0x68, 0x1d, 0xa6, 0x13, 0x77, 0xba, 0x4a, 0x04, 0x8f, 0x64, 0xcd, 0x47, 0xf5, 0xbf, 0xd4, 0x7c, 0xce, 0x41, 0x3f, 0x5c, 0x77, 0xea, 0xfa, 0x59, 0x97, 0x5c, 0x3f, 0xaa, 0x7f, 0xc7, 0xfa, 0x29, 0x9a, 0xfc, 0x33, 0x7f, 0x80, 0x62, 0x9c, 0x0a, 0x8f, 0x66, 0xf0, 0xa0, 0x4f, 0x2b, 0x11, 0x2f, 0x22, 0x36, 0x64, 0x44, 0x31, 0x25, 0x8e, 0x21, 0xa3, 0x95, 0xe5, 0x45, 0xf6, 0x41, 0xe9, 0x73, 0x34, 0xb6, 0x5a, 0xba, 0xe6, 0x4f, 0x77, 0xfe, 0x59, 0xba, 0x62, 0x35, 0xba, 0x10, 0xf1, 0xd2, 0x95, 0xf0, 0xf1, 0xa7, 0xbb, 0xfe, 0x0c, 0x8f, 0xd9, 0x85, 0xd2, 0xe5, 0x13, 0xd7, 0x23, 0x46, 0xba, 0x7b, 0x0b, 0x1a, 0x44, 0xec, 0x11, 0x10, 0x79, 0x2e, 0x42, 0xf8, 0x7a, 0x28, 0xb2, 0x7c, 0x8b, 0x74, 0x97, 0x74, 0x84, 0xbc, 0x47, 0xc1, 0xac, 0x9c, 0xcc, 0x53, 0xac, 0x17, 0x4a, 0x41, 0x6f, 0xf7, 0x31, 0x51, 0xa6, 0x9d, 0x19, 0x60, 0x36, 0x33, 0x97, 0x30, 0xd7, 0x31, 0xaf, 0xc5, 0x7f, 0x2e, 0x22, 0x5e, 0xb0, 0x09, 0xbc, 0x6d, 0x2c, 0xc7, 0xe9, 0x60, 0xed, 0x59, 0x8a, 0x2c, 0xbb, 0x62, 0x2c, 0x43, 0xab, 0x66, 0x95, 0xaa, 0x4c, 0x95, 0x32, 0x73, 0xcc, 0x6a, 0x36, 0xb2, 0x7a, 0x03, 0x63, 0xd0, 0x93, 0xfb, 0x08, 0x98, 0x15, 0xe7, 0x7b, 0x40, 0xff, 0x11, 0x78, 0x4e, 0x18, 0x0a, 0x65, 0x07, 0x59, 0x9b, 0x0b, 0x65, 0xd9, 0x6d, 0x59, 0x43, 0x3e, 0x8d, 0x97, 0x55, 0xe8, 0x90, 0x4a, 0xa9, 0x50, 0x0d, 0x05, 0x4c, 0x7e, 0x36, 0xd3, 0x82, 0x0c, 0xfa, 0x4c, 0xc3, 0x90, 0x1b, 0x51, 0x87, 0x48, 0x37, 0x4a, 0x2d, 0xa7, 0x7d, 0x7b, 0xb7, 0x8c, 0xae, 0x58, 0xde, 0xd9, 0x11, 0xab, 0x08, 0xf8, 0xc9, 0x91, 0xf8, 0xe1, 0xab, 0xae, 0x38, 0x74, 0xf0, 0xb2, 0xbd, 0xd7, 0xed, 0xbb, 0x6e, 0xcf, 0xee, 0x9d, 0x3b, 0xc6, 0xb7, 0x8f, 0x5e, 0xb2, 0xe5, 0x92, 0xf5, 0x23, 0xc3, 0x6b, 0x57, 0xaf, 0x5a, 0xbe, 0x79, 0xc5, 0xe6, 0xc5, 0x8b, 0xfa, 0x17, 0xcc, 0xeb, 0xed, 0x18, 0xe8, 0x1c, 0x68, 0x6e, 0x8a, 0xd7, 0xd7, 0xd6, 0x54, 0xb4, 0xc7, 0xda, 0x4b, 0x4b, 0x8a, 0x0a, 0xf2, 0x72, 0xfd, 0xd1, 0x40, 0x94, 0xe0, 0x73, 0xd9, 0xac, 0x06, 0x9f, 0xd1, 0xa7, 0xd5, 0xa8, 0x14, 0xc0, 0x35, 0xf4, 0x48, 0x1f, 0xa6, 0xb1, 0x6e, 0x61, 0x09, 0xf8, 0xe8, 0x39, 0x99, 0x99, 0xf0, 0x71, 0xe3, 0xac, 0xef, 0x68, 0xd6, 0x77, 0xcf, 0xbf, 0x39, 0x9d, 0x5b, 0x5b, 0x37, 0x58, 0x9f, 0x73, 0x55, 0x71, 0x5d, 0x5d, 0xd1, 0x55, 0x39, 0x75, 0x83, 0x75, 0x33, 0xbe, 0x4d, 0x14, 0xce, 0xf8, 0x2a, 0xac, 0x84, 0x0c, 0x39, 0x57, 0xc2, 0xd7, 0xe2, 0x2b, 0xe1, 0xeb, 0x3f, 0x7f, 0x3b, 0x23, 0x95, 0xfb, 0xed, 0x8c, 0xd4, 0xcf, 0xbf, 0x7c, 0xa6, 0xd4, 0x2c, 0x5f, 0xc3, 0xb2, 0x8a, 0xea, 0x9a, 0x9a, 0xea, 0x8a, 0x65, 0x0d, 0x3e, 0xc1, 0x3a, 0xfd, 0xdb, 0xc9, 0x4f, 0x7c, 0x0d, 0x03, 0x15, 0x35, 0xf0, 0xa7, 0x62, 0xa0, 0xc1, 0x37, 0xfd, 0x77, 0xa9, 0x8e, 0x7c, 0xa3, 0xf9, 0x12, 0x29, 0xc9, 0xdf, 0xd9, 0x43, 0xa7, 0x4b, 0x91, 0xe7, 0x54, 0xbb, 0x74, 0x50, 0xd1, 0x24, 0x5c, 0xca, 0xb8, 0x98, 0x6a, 0x98, 0x4f, 0xbb, 0x98, 0x9b, 0x98, 0xc7, 0x98, 0x57, 0x99, 0x0f, 0xf0, 0x6a, 0x39, 0xc0, 0x66, 0xa9, 0xd1, 0x80, 0x45, 0xc5, 0x2e, 0xa4, 0x41, 0x2e, 0xc4, 0x68, 0x3e, 0xa8, 0xc6, 0x6c, 0xe6, 0x00, 0x52, 0xf1, 0xaf, 0x22, 0x41, 0x35, 0xaf, 0x17, 0x2b, 0xb5, 0x62, 0x57, 0x31, 0xca, 0x7c, 0x1b, 0x24, 0x93, 0xce, 0x7b, 0x90, 0xa2, 0xe3, 0x2a, 0xa4, 0x6c, 0x77, 0xc8, 0xae, 0xa7, 0xe7, 0x5a, 0x50, 0xd7, 0xee, 0x90, 0xcb, 0xa8, 0xcf, 0xa5, 0x0c, 0x51, 0x56, 0x49, 0x33, 0x17, 0x31, 0xa2, 0x02, 0x29, 0x44, 0x34, 0xc6, 0x20, 0x8d, 0x02, 0x69, 0xb6, 0x32, 0x8c, 0x06, 0x6b, 0x18, 0xd8, 0x06, 0xf8, 0x0c, 0x55, 0x06, 0xaf, 0x1a, 0x63, 0x04, 0x95, 0x56, 0x25, 0x68, 0xc7, 0xc8, 0xe9, 0x04, 0x9b, 0x81, 0x81, 0x43, 0x67, 0xea, 0x51, 0x06, 0x9f, 0x99, 0x31, 0xc4, 0xa8, 0x74, 0x06, 0xac, 0x54, 0x29, 0x57, 0x31, 0x1a, 0x8d, 0x62, 0x85, 0x51, 0x8d, 0x15, 0x0a, 0x7b, 0x37, 0xa3, 0xd5, 0x66, 0x69, 0x7b, 0x12, 0x3d, 0x59, 0x31, 0xc7, 0xba, 0x19, 0xad, 0x52, 0xa5, 0x55, 0x6e, 0x65, 0x74, 0x4a, 0xdd, 0xf9, 0xe9, 0xde, 0x63, 0x40, 0x4a, 0x9d, 0x4a, 0x39, 0x34, 0xed, 0x3d, 0x1a, 0xf2, 0x0a, 0x3f, 0xbc, 0x62, 0x15, 0x23, 0xaa, 0x91, 0x5a, 0x7e, 0x85, 0xfa, 0xdf, 0xf0, 0x0a, 0xda, 0x07, 0x8d, 0x46, 0xbd, 0xc2, 0x88, 0xd4, 0x6a, 0xf2, 0x9a, 0xf8, 0xca, 0x7f, 0xe1, 0x0d, 0x50, 0x98, 0xc4, 0x1e, 0xd5, 0x0c, 0x9d, 0xe5, 0x55, 0x0a, 0x6a, 0xb9, 0x54, 0x74, 0xec, 0x4b, 0x7b, 0x76, 0xbf, 0xf7, 0x87, 0xdf, 0xfe, 0xfa, 0xb5, 0xff, 0x78, 0xe1, 0x07, 0xdf, 0x3e, 0x7e, 0xc7, 0xed, 0x5f, 0x7a, 0xec, 0xd8, 0x63, 0xfb, 0xf7, 0xed, 0xbe, 0x69, 0xcf, 0x4d, 0x5b, 0x46, 0x57, 0x0e, 0x2e, 0x59, 0xd8, 0xdc, 0x14, 0x29, 0xcd, 0xcf, 0x0d, 0xf8, 0x1c, 0x36, 0xab, 0x49, 0x30, 0x93, 0x33, 0x47, 0x1a, 0x56, 0xdd, 0x4b, 0x5c, 0xb6, 0x62, 0xf5, 0x6c, 0x0e, 0xb2, 0x08, 0xc4, 0x26, 0x41, 0xc7, 0x8a, 0xb2, 0x41, 0xa4, 0xbc, 0x2b, 0xd0, 0x8b, 0x00, 0x39, 0xdc, 0xa5, 0xd5, 0x58, 0x44, 0xce, 0x71, 0xeb, 0x41, 0x91, 0xb4, 0x92, 0xbc, 0x6c, 0x31, 0x22, 0x25, 0x51, 0xa4, 0x9e, 0x25, 0x17, 0x8e, 0x64, 0xf7, 0x30, 0xd1, 0xcb, 0xd4, 0x50, 0x11, 0xdb, 0x80, 0x2a, 0xac, 0x2e, 0x64, 0x86, 0x42, 0xa9, 0x5d, 0x26, 0x71, 0xba, 0x4e, 0xbc, 0xc2, 0xc8, 0xa1, 0x03, 0x94, 0x81, 0x7a, 0xa6, 0xc9, 0x81, 0xd4, 0xff, 0x0c, 0x2a, 0x60, 0xc9, 0x91, 0x31, 0x54, 0x12, 0x2c, 0x97, 0x6f, 0x20, 0x32, 0x90, 0x00, 0x65, 0xa2, 0x45, 0xc0, 0xcf, 0x4d, 0x72, 0xc8, 0xc1, 0xa4, 0xef, 0xa3, 0x20, 0xc7, 0xe4, 0x0c, 0x09, 0x7b, 0x0c, 0x99, 0x08, 0x76, 0xd6, 0x70, 0x5e, 0xae, 0x21, 0x4f, 0x1b, 0x76, 0xf5, 0xaf, 0x19, 0xad, 0x44, 0x98, 0x17, 0xb9, 0x4c, 0x85, 0xae, 0x30, 0x5a, 0x69, 0xff, 0xe7, 0x2f, 0x40, 0xee, 0xcb, 0x2a, 0x6c, 0x98, 0x5f, 0x95, 0x37, 0xb8, 0x62, 0xb1, 0x3f, 0x5b, 0x93, 0x29, 0xe8, 0x04, 0x95, 0xb7, 0xb0, 0xc2, 0xf5, 0xe3, 0x64, 0xc1, 0x3c, 0x28, 0x98, 0x2b, 0x17, 0x44, 0x1c, 0x14, 0x54, 0xea, 0x0a, 0xa2, 0xb1, 0xac, 0x80, 0x21, 0xb7, 0x20, 0xdf, 0xe0, 0xf6, 0x9b, 0x8b, 0x8b, 0x8b, 0x4c, 0xae, 0x92, 0x1c, 0xad, 0x90, 0xab, 0x57, 0xf3, 0x20, 0x3f, 0xa8, 0x4c, 0x06, 0xf4, 0x67, 0x7b, 0xae, 0x2b, 0xb3, 0x21, 0xe2, 0x2a, 0x74, 0x65, 0xd8, 0x8b, 0x5a, 0xf2, 0x5d, 0x0a, 0x01, 0x29, 0x04, 0xa5, 0xcb, 0x65, 0xe7, 0x61, 0x3f, 0x47, 0x7a, 0x57, 0xbe, 0xdd, 0x55, 0xe0, 0xca, 0xb0, 0x15, 0x37, 0xd3, 0x24, 0x9d, 0xd2, 0xe9, 0xb2, 0x0b, 0xe8, 0x6f, 0x50, 0x63, 0x9e, 0xc1, 0xed, 0xcb, 0x5f, 0x9b, 0xeb, 0x2a, 0x76, 0x6b, 0xf8, 0xbc, 0x54, 0x7d, 0x1f, 0x35, 0x47, 0x8a, 0x1a, 0xcc, 0x81, 0xb2, 0xec, 0xd9, 0x6d, 0xe4, 0x0e, 0x1b, 0xf2, 0xf2, 0xf3, 0x0d, 0x99, 0x99, 0x06, 0x43, 0xaa, 0x94, 0x4a, 0x23, 0x72, 0x50, 0x4c, 0x6d, 0x32, 0x48, 0x99, 0xd0, 0x0c, 0x7d, 0x3c, 0x52, 0x53, 0x47, 0x1a, 0x91, 0xa3, 0x55, 0x0a, 0xf4, 0x45, 0x13, 0xef, 0x9c, 0xb6, 0x90, 0xca, 0x6c, 0xe0, 0x5a, 0xf4, 0xa4, 0xdf, 0xee, 0x81, 0x80, 0x59, 0xcf, 0xdb, 0x4d, 0xae, 0xfe, 0xb5, 0xa4, 0xdf, 0x6a, 0x4e, 0x0f, 0xf4, 0x2a, 0xaf, 0x4a, 0xd2, 0x2b, 0x9e, 0x68, 0xcd, 0x12, 0x7f, 0xb6, 0xa8, 0x51, 0x26, 0x9b, 0xc3, 0x4b, 0x2d, 0x11, 0x6b, 0xc8, 0xa5, 0x9f, 0x91, 0xa8, 0xd1, 0xa9, 0x3d, 0x90, 0xf6, 0xcf, 0x97, 0xf5, 0x7a, 0x4c, 0xeb, 0xa5, 0xd4, 0x9c, 0xaa, 0xb5, 0x00, 0x46, 0x01, 0x67, 0x94, 0x35, 0xc4, 0x53, 0xe4, 0x4a, 0xb5, 0x14, 0xf6, 0xb3, 0x6f, 0x4d, 0xaa, 0xf8, 0x62, 0xc1, 0xc0, 0xe4, 0x81, 0x36, 0x16, 0x8a, 0xfb, 0xf3, 0xbc, 0x1e, 0xb7, 0xcd, 0xa4, 0xd5, 0xa8, 0x59, 0x15, 0x81, 0x6d, 0x99, 0x29, 0x0e, 0x13, 0x8c, 0x2f, 0x4c, 0x04, 0x27, 0x98, 0x43, 0x30, 0x7f, 0x91, 0x8e, 0x06, 0xff, 0x10, 0x85, 0x60, 0x08, 0x11, 0x01, 0xc6, 0xac, 0x97, 0x11, 0x38, 0x73, 0xe0, 0x21, 0x99, 0x3e, 0x64, 0x56, 0x12, 0x80, 0x2f, 0x7c, 0x93, 0x42, 0xcc, 0x0c, 0xdb, 0xa5, 0xbf, 0x19, 0xf3, 0x2d, 0xad, 0xb8, 0x11, 0xd7, 0xb7, 0x5a, 0xf2, 0x4d, 0xd2, 0xdf, 0x23, 0x2e, 0x51, 0xa1, 0x73, 0x9b, 0x90, 0xc6, 0x04, 0x8f, 0x27, 0x9e, 0x9b, 0x78, 0x9e, 0x3c, 0x46, 0x2a, 0x93, 0x5b, 0x27, 0x55, 0x70, 0x2d, 0x9f, 0x3f, 0xc3, 0xe9, 0x1a, 0xf6, 0x54, 0x98, 0x0b, 0xb3, 0xa4, 0x8f, 0x4d, 0x0a, 0x73, 0x0b, 0x6e, 0x9c, 0xf8, 0x5e, 0x8b, 0x59, 0x01, 0x19, 0x22, 0xe1, 0x8a, 0xdd, 0x0d, 0x13, 0x21, 0xbd, 0xdf, 0x84, 0xd4, 0x90, 0xd2, 0x3c, 0xf1, 0x1c, 0x6e, 0x68, 0xb6, 0x28, 0x4d, 0xd2, 0xa7, 0x26, 0x9f, 0x5e, 0x5a, 0x7b, 0xad, 0x2c, 0x97, 0x7c, 0x8a, 0x3f, 0xc5, 0xab, 0xb8, 0x1b, 0x40, 0x3e, 0xf2, 0xc7, 0x3d, 0xb2, 0x80, 0x94, 0x08, 0xeb, 0x35, 0x13, 0xdf, 0xc0, 0x9a, 0x4f, 0xe5, 0xb1, 0x24, 0xb6, 0x35, 0x05, 0x2a, 0x4b, 0x5c, 0xc4, 0x7d, 0x5a, 0xd0, 0x56, 0x92, 0xa5, 0x33, 0xe8, 0x22, 0x15, 0xc3, 0x82, 0x58, 0xbc, 0xe2, 0xd0, 0x32, 0x76, 0xbe, 0xd1, 0x5b, 0xe4, 0xc8, 0xb4, 0xd9, 0x5c, 0x96, 0x50, 0xbe, 0xa9, 0x22, 0xbb, 0x75, 0xbc, 0xbf, 0x90, 0xc4, 0x78, 0x78, 0x79, 0xd2, 0xc2, 0xbd, 0x22, 0x6c, 0x64, 0x54, 0x8c, 0x8e, 0x31, 0x80, 0x54, 0x5b, 0x4d, 0x7c, 0x5e, 0xfd, 0x30, 0x99, 0xe1, 0x9d, 0x5a, 0x85, 0x76, 0x9c, 0x51, 0x29, 0x55, 0xe7, 0x0b, 0x88, 0x20, 0x74, 0x82, 0x7a, 0x37, 0x24, 0x66, 0x62, 0x9e, 0x57, 0xac, 0xd0, 0x20, 0x85, 0x62, 0xa8, 0x3b, 0x83, 0xd5, 0xb1, 0xc4, 0xb0, 0x4f, 0x8d, 0xa8, 0xcf, 0x71, 0x46, 0x86, 0x5a, 0x8d, 0x98, 0xea, 0x4a, 0xea, 0x7a, 0x5c, 0x9c, 0x9f, 0x47, 0x2c, 0x59, 0x4d, 0xc6, 0x0c, 0x43, 0x06, 0x08, 0x23, 0x6a, 0x9d, 0x5a, 0xa7, 0xd5, 0x28, 0xa8, 0x1d, 0x8b, 0x3e, 0x75, 0xef, 0x1e, 0x94, 0x71, 0xc7, 0xec, 0x38, 0xa2, 0x37, 0xca, 0x51, 0x74, 0xa3, 0x89, 0x98, 0xc6, 0x9c, 0x9e, 0x46, 0x2f, 0x36, 0x53, 0x1b, 0x1e, 0x8f, 0x9e, 0x38, 0x24, 0xd7, 0x20, 0xfc, 0xab, 0xbe, 0xf3, 0x3b, 0xbd, 0x47, 0x6f, 0x55, 0xee, 0x7d, 0xf6, 0xd9, 0xbd, 0x5f, 0xd8, 0xbf, 0xff, 0x0b, 0x17, 0x3e, 0xb8, 0x3d, 0xea, 0x6e, 0x1a, 0x6e, 0x41, 0x55, 0x2d, 0x07, 0xbe, 0xb3, 0x07, 0x21, 0x97, 0xb5, 0x43, 0xba, 0x10, 0x1d, 0x22, 0x9f, 0xf7, 0x04, 0x9b, 0x56, 0x54, 0x6c, 0xbe, 0x48, 0xf2, 0xa0, 0xdf, 0x49, 0x1e, 0xae, 0x47, 0xca, 0x43, 0xaf, 0x4b, 0x05, 0xca, 0x8b, 0x87, 0xcf, 0xdf, 0x2e, 0x7c, 0x83, 0x8f, 0xad, 0xf9, 0xc2, 0xa2, 0xf2, 0xd5, 0x0b, 0x9b, 0x2c, 0xf5, 0xd2, 0xa7, 0x83, 0x5f, 0xda, 0x5a, 0xfb, 0x84, 0xc9, 0xfd, 0xdf, 0xfc, 0x6d, 0x37, 0x9b, 0xdc, 0xb2, 0xdd, 0xf0, 0xfb, 0x50, 0xc9, 0x5f, 0xc4, 0xe5, 0x40, 0x93, 0x96, 0xb8, 0x06, 0xcb, 0xc8, 0xd1, 0x2a, 0x25, 0xc6, 0xf2, 0x05, 0x50, 0x5c, 0x47, 0xc4, 0x54, 0x60, 0xad, 0x6b, 0x92, 0x97, 0x4a, 0x66, 0x19, 0x44, 0x81, 0x08, 0xaf, 0x04, 0xcd, 0x86, 0x58, 0x8d, 0x60, 0x86, 0xa2, 0xd9, 0x18, 0xa9, 0xa0, 0x2a, 0x86, 0x68, 0x44, 0x73, 0x4f, 0x28, 0x86, 0xfe, 0xf2, 0x5c, 0xfd, 0x46, 0xe9, 0x99, 0x32, 0x74, 0xc1, 0x86, 0x7a, 0xe1, 0xd6, 0xe7, 0x1a, 0x36, 0x7c, 0xfa, 0xa9, 0xa2, 0x66, 0x7d, 0x3d, 0x83, 0xa4, 0x0c, 0x72, 0x6f, 0x45, 0xdf, 0xd9, 0x11, 0xcf, 0x24, 0x08, 0x56, 0x3c, 0x47, 0x14, 0xa2, 0x2e, 0x85, 0x38, 0xe3, 0xbd, 0xc9, 0x4b, 0xb3, 0x4d, 0xf2, 0xc1, 0x5e, 0x7a, 0x14, 0x1d, 0x8b, 0x8c, 0xa2, 0x63, 0x84, 0x57, 0x5a, 0xe1, 0xdd, 0x56, 0xd1, 0x63, 0x2f, 0x93, 0x9e, 0xd9, 0x58, 0xff, 0xdc, 0x73, 0xf5, 0x1b, 0xd0, 0x05, 0xd2, 0x85, 0x0a, 0xe5, 0x86, 0x06, 0xf8, 0x7d, 0x3d, 0x81, 0xee, 0x3b, 0x28, 0xdd, 0x88, 0x0e, 0x0b, 0x4b, 0xa8, 0xbd, 0x4f, 0x0d, 0x33, 0x9f, 0xc4, 0x6d, 0x61, 0x04, 0x46, 0xe4, 0x08, 0x9e, 0xa9, 0x8c, 0x65, 0x32, 0x34, 0x2d, 0x88, 0x41, 0x42, 0x0c, 0x1f, 0x43, 0x04, 0x14, 0x87, 0xfc, 0xf1, 0x2b, 0x54, 0xd9, 0xf9, 0xd6, 0xb3, 0x40, 0xe2, 0x9c, 0xed, 0xfb, 0xc1, 0xd9, 0xb0, 0xda, 0xe7, 0xfa, 0xfd, 0x3f, 0x13, 0x11, 0x28, 0xb2, 0x13, 0x89, 0x78, 0xf9, 0x2c, 0x38, 0xee, 0x53, 0x1f, 0x0c, 0xcc, 0x2a, 0x42, 0x86, 0xac, 0x4d, 0xba, 0x52, 0xb1, 0x3a, 0x11, 0x07, 0xc9, 0x49, 0x2f, 0x17, 0x64, 0xc3, 0xac, 0x29, 0x23, 0x71, 0x12, 0x84, 0x88, 0x22, 0x8a, 0xcc, 0x31, 0x08, 0x51, 0xdb, 0xbf, 0x14, 0x50, 0xe8, 0x5f, 0x8a, 0x0e, 0x04, 0x53, 0x61, 0xaa, 0xfd, 0x59, 0x71, 0x2b, 0x8d, 0x53, 0x39, 0x2e, 0x37, 0x1b, 0x31, 0x19, 0x5a, 0x72, 0xd4, 0xc2, 0x52, 0x0b, 0xb2, 0xd9, 0x8d, 0x8e, 0xd2, 0xce, 0x70, 0x0b, 0xd3, 0x36, 0x52, 0x7e, 0xa9, 0x70, 0x57, 0xda, 0xa6, 0x24, 0x9a, 0x49, 0x14, 0xe5, 0x64, 0x8c, 0x21, 0x32, 0x8f, 0x9a, 0x49, 0x24, 0x0c, 0xe0, 0x19, 0xf0, 0x9c, 0x84, 0x13, 0x4e, 0xd9, 0xa6, 0x24, 0x2d, 0xdc, 0xc6, 0xf8, 0x9e, 0xdc, 0x70, 0x2c, 0x1a, 0x6e, 0xce, 0x6d, 0x4e, 0x05, 0xcf, 0x9c, 0x65, 0xff, 0x15, 0x9a, 0x75, 0xd0, 0xc3, 0x9e, 0xa6, 0xd5, 0xfc, 0x4f, 0xce, 0x6a, 0x27, 0xf6, 0xf0, 0x19, 0xfa, 0x85, 0x1f, 0x4a, 0xda, 0x8e, 0x25, 0x6d, 0xc9, 0x12, 0xb6, 0x63, 0xeb, 0x13, 0xb6, 0x64, 0xec, 0xb5, 0x67, 0xec, 0x38, 0x4e, 0xf5, 0x5b, 0xcb, 0xe4, 0x12, 0x1b, 0x7f, 0x05, 0xb5, 0x1e, 0x03, 0x8a, 0x60, 0x72, 0xc3, 0x3d, 0x26, 0x20, 0x96, 0x27, 0x42, 0x18, 0x5e, 0x93, 0x44, 0x12, 0x1f, 0xe3, 0x7a, 0x32, 0x74, 0x0c, 0xe3, 0x74, 0xe8, 0x72, 0x33, 0x72, 0xa9, 0x49, 0x99, 0x49, 0x54, 0x59, 0x40, 0xf3, 0xb4, 0xba, 0x70, 0x45, 0x03, 0x12, 0xcc, 0xfa, 0x88, 0xfe, 0x74, 0x9d, 0xe5, 0x5a, 0xa5, 0xdf, 0x96, 0x2b, 0x1c, 0x4e, 0x97, 0x7a, 0x03, 0xfb, 0xdd, 0xeb, 0xce, 0xd0, 0x2d, 0xee, 0x56, 0xe9, 0xb3, 0x46, 0x90, 0x17, 0x95, 0xaa, 0xb5, 0x65, 0x45, 0x13, 0x17, 0xb0, 0xbf, 0xc4, 0xca, 0x33, 0x8f, 0xdf, 0x54, 0x3f, 0x8a, 0x98, 0x2e, 0x82, 0xc8, 0x60, 0xa4, 0xd6, 0x7b, 0xe4, 0x5e, 0x49, 0x24, 0xa8, 0x58, 0x98, 0x93, 0x4d, 0xaa, 0xe8, 0x89, 0x0d, 0xb5, 0xf4, 0x4b, 0x0e, 0x64, 0x49, 0x31, 0x62, 0xea, 0x6b, 0x8b, 0xbb, 0x4a, 0xba, 0xf2, 0x72, 0x9d, 0x0e, 0x98, 0x66, 0x45, 0xa8, 0x90, 0x9a, 0x12, 0x78, 0x05, 0x31, 0x36, 0x05, 0x88, 0x2e, 0x5a, 0x2d, 0x44, 0x76, 0x4b, 0x68, 0xe9, 0x64, 0xfb, 0xa5, 0xf6, 0x46, 0x41, 0xdf, 0x69, 0xa7, 0xe3, 0x97, 0x9d, 0xad, 0xbe, 0xc7, 0xfc, 0x1d, 0x35, 0x81, 0xc2, 0xbe, 0x6d, 0xcd, 0xf5, 0xdb, 0x97, 0xd7, 0xa9, 0x46, 0x82, 0xf9, 0x0a, 0xc1, 0xe6, 0xcd, 0xb5, 0x54, 0xb5, 0xd8, 0xf3, 0x6b, 0x3c, 0xd2, 0x49, 0x53, 0x06, 0x3e, 0xe6, 0xcf, 0x91, 0x3e, 0x3e, 0x03, 0x25, 0xd0, 0x5e, 0xad, 0xfa, 0xc5, 0xf0, 0xbc, 0x85, 0x83, 0xd1, 0x86, 0x0d, 0x3d, 0x79, 0xfe, 0x8e, 0x2d, 0x9d, 0x76, 0xd1, 0x9c, 0xe5, 0xc8, 0x75, 0xe8, 0x1a, 0x6b, 0xfd, 0x8d, 0xf1, 0xa6, 0x80, 0xf4, 0x8e, 0xa1, 0xc0, 0xd4, 0xb4, 0xcb, 0x19, 0xe4, 0x83, 0x67, 0x26, 0xd1, 0x34, 0x1a, 0x55, 0x33, 0x4b, 0x80, 0x4b, 0xb7, 0x56, 0xc3, 0x96, 0x58, 0x08, 0x1c, 0xd7, 0x4d, 0xfc, 0x03, 0xba, 0x14, 0x48, 0x60, 0x08, 0x96, 0x1e, 0x65, 0x9b, 0x1c, 0xa6, 0xf6, 0x9c, 0x49, 0xf3, 0x1a, 0x8e, 0x4b, 0x5a, 0xba, 0xd7, 0xd6, 0x74, 0x75, 0xd4, 0x2c, 0xa9, 0x5d, 0x52, 0x54, 0x10, 0xd4, 0x9b, 0x42, 0x4a, 0xe2, 0x0c, 0x61, 0x49, 0x46, 0x86, 0x27, 0x56, 0x0f, 0xe6, 0xc4, 0x31, 0x06, 0x91, 0x9f, 0x8b, 0x50, 0x06, 0x4a, 0x19, 0xd2, 0x84, 0x64, 0x0c, 0x1e, 0x4a, 0xcb, 0xd3, 0xd1, 0x8c, 0xbd, 0xc1, 0x68, 0xd5, 0x2a, 0x84, 0x8c, 0xae, 0xa2, 0xaa, 0xc6, 0xc1, 0xa1, 0xb1, 0x35, 0x06, 0x5f, 0x04, 0xc4, 0x5e, 0xaf, 0x59, 0x50, 0x97, 0x79, 0x97, 0x18, 0x23, 0x3d, 0x1b, 0x3b, 0x7b, 0x76, 0xf6, 0xe5, 0xf7, 0x2f, 0x6a, 0xeb, 0x0b, 0x76, 0x6c, 0x68, 0xae, 0x1f, 0x5d, 0x54, 0xa5, 0x42, 0xe1, 0x33, 0x10, 0x8f, 0xdd, 0xc6, 0x62, 0xb5, 0x5e, 0x1d, 0xa9, 0xa8, 0xab, 0x6e, 0x69, 0x47, 0xd7, 0xc6, 0xdb, 0x1c, 0xe5, 0x21, 0x5b, 0x56, 0x30, 0xa4, 0xb3, 0xe9, 0x74, 0xc1, 0xda, 0x35, 0xad, 0x81, 0xe2, 0xfe, 0x2d, 0x0d, 0xcb, 0x36, 0xd8, 0xed, 0x5b, 0x57, 0x47, 0x56, 0xb6, 0xe5, 0x12, 0xea, 0xb2, 0x9e, 0x33, 0x52, 0x71, 0x8a, 0x4f, 0xf0, 0xf4, 0x36, 0xe8, 0x50, 0x9c, 0x98, 0xa4, 0xc3, 0x30, 0x61, 0x6a, 0xd0, 0x42, 0xf0, 0x0b, 0x83, 0x4a, 0x5e, 0xc1, 0x92, 0x8d, 0x0c, 0xb4, 0x1c, 0xe2, 0x2e, 0xc3, 0xe2, 0x11, 0x15, 0x90, 0x16, 0x31, 0x02, 0x6c, 0x7c, 0x2c, 0x2b, 0x0e, 0x26, 0xdd, 0x0d, 0x42, 0xb3, 0x32, 0xc2, 0xbf, 0xad, 0xe9, 0x72, 0x0e, 0xc4, 0x73, 0x6c, 0xd4, 0xe2, 0x25, 0xe8, 0xb7, 0x96, 0xd9, 0xca, 0x88, 0xdd, 0x0b, 0x08, 0x24, 0x19, 0x3a, 0x22, 0x8d, 0xf0, 0x0c, 0xaf, 0x26, 0x93, 0xd5, 0xa8, 0x43, 0x64, 0xe1, 0xf9, 0x88, 0x25, 0x45, 0xc4, 0x63, 0x26, 0xb8, 0x21, 0x15, 0x75, 0xe8, 0xb4, 0x74, 0xbe, 0x76, 0x82, 0x63, 0xef, 0x38, 0x2e, 0xad, 0x34, 0x72, 0x2e, 0x3d, 0xda, 0x71, 0xc7, 0x90, 0xc1, 0x24, 0x7d, 0xf3, 0x4c, 0x5c, 0xa6, 0x1a, 0x1d, 0x70, 0xd7, 0x64, 0x49, 0x7f, 0xc3, 0x6b, 0x26, 0x6e, 0x1b, 0x31, 0xd8, 0xfe, 0xf0, 0x07, 0x6b, 0xe6, 0x28, 0xb7, 0x69, 0xce, 0xf3, 0xad, 0x84, 0xe9, 0x61, 0xfa, 0xe3, 0xf3, 0xf3, 0x61, 0x1f, 0xe2, 0x61, 0x51, 0x22, 0x3b, 0x88, 0x63, 0x2c, 0x10, 0x93, 0x5e, 0x2d, 0x8c, 0x81, 0x14, 0xcb, 0x30, 0x82, 0x48, 0xc4, 0x11, 0x72, 0x76, 0x88, 0x87, 0x92, 0x41, 0xb9, 0xe9, 0xc4, 0xa3, 0xce, 0x16, 0x63, 0x8a, 0x9e, 0xb2, 0xd2, 0x78, 0x7d, 0x69, 0x4f, 0x59, 0x8f, 0x2f, 0x1c, 0x36, 0x86, 0xfd, 0x99, 0x2a, 0x62, 0xcc, 0xe6, 0x61, 0xad, 0x33, 0x4d, 0xd9, 0x7c, 0x5e, 0x61, 0x6a, 0xca, 0xc9, 0x27, 0xf1, 0x31, 0xe2, 0xff, 0x74, 0x3a, 0x42, 0xf0, 0xdb, 0xa4, 0xd2, 0x79, 0xbb, 0x9d, 0x35, 0xce, 0xdc, 0x6a, 0xbf, 0xde, 0x9a, 0x1b, 0x73, 0x07, 0x23, 0x21, 0x8f, 0x51, 0x67, 0x57, 0xd5, 0xd8, 0x6a, 0xdb, 0xfb, 0x8b, 0xbb, 0x2e, 0x59, 0x1e, 0x09, 0x76, 0x6e, 0x69, 0xb7, 0x96, 0x64, 0x39, 0x5f, 0xab, 0x2b, 0x97, 0xfe, 0x78, 0x66, 0x96, 0x25, 0xfa, 0xb4, 0x1a, 0x4b, 0x6e, 0xa5, 0x27, 0x58, 0x93, 0x6b, 0xb1, 0xd8, 0x2d, 0x1a, 0xdd, 0x62, 0x7b, 0xa1, 0xcf, 0x54, 0x3e, 0x78, 0x69, 0x6f, 0xe3, 0xe8, 0xbc, 0x7c, 0x9e, 0x7f, 0xa6, 0x30, 0x0f, 0xaf, 0x9a, 0xeb, 0x3e, 0x44, 0x6c, 0x6d, 0xdb, 0x09, 0xde, 0x7c, 0x58, 0x96, 0x65, 0x41, 0x80, 0x05, 0xe2, 0x8c, 0x29, 0x10, 0x0f, 0x44, 0x22, 0x86, 0xa2, 0xa0, 0x03, 0x10, 0x37, 0x19, 0x62, 0x9f, 0x4c, 0xa9, 0x35, 0x26, 0xf4, 0x14, 0x16, 0x10, 0x23, 0xd1, 0x82, 0xf6, 0xc2, 0xf6, 0xa0, 0x3f, 0x3b, 0x2b, 0x53, 0xa7, 0x14, 0x89, 0xe9, 0xac, 0xec, 0x82, 0x95, 0x50, 0x1f, 0xc9, 0x22, 0x35, 0x12, 0xba, 0xb0, 0x04, 0xfe, 0x69, 0xa6, 0xd1, 0x1f, 0x68, 0x10, 0xc6, 0xd3, 0x71, 0xb3, 0x9e, 0x96, 0x1e, 0x53, 0x30, 0xe6, 0xad, 0xef, 0x5b, 0x70, 0xff, 0x03, 0xde, 0x6a, 0xfd, 0xf1, 0x92, 0x8a, 0x82, 0x45, 0xbb, 0x7b, 0xba, 0x2e, 0x5c, 0x52, 0x5c, 0xdc, 0xb9, 0xa2, 0xd0, 0x53, 0xeb, 0x50, 0x1a, 0x54, 0xb6, 0x33, 0xf1, 0xb1, 0xc1, 0x25, 0xad, 0xbe, 0xda, 0x42, 0xfb, 0xc2, 0x79, 0xdd, 0x8b, 0xd8, 0x06, 0x57, 0x8e, 0xf4, 0x76, 0xef, 0xf5, 0x9d, 0xf3, 0xf6, 0xaf, 0x8c, 0x54, 0xac, 0xbc, 0xb0, 0xb5, 0x7b, 0x5b, 0xa7, 0xdf, 0xa8, 0xd7, 0xd8, 0x34, 0xec, 0xc8, 0x9c, 0xe7, 0x94, 0x01, 0x38, 0x7d, 0x45, 0x3c, 0x82, 0xa8, 0x37, 0xd5, 0xb4, 0x6d, 0x8a, 0xf2, 0x2b, 0x20, 0xd9, 0xa0, 0xbc, 0x57, 0x99, 0x8c, 0x0c, 0xe3, 0x75, 0x1b, 0x8b, 0x4c, 0x45, 0xc4, 0x3c, 0xcb, 0xef, 0x13, 0x65, 0xf3, 0x2c, 0x4b, 0xc4, 0x43, 0x94, 0x28, 0x7a, 0x09, 0x4e, 0xa0, 0x82, 0x4e, 0xbb, 0x52, 0x2a, 0x1c, 0x06, 0x74, 0x8d, 0xde, 0xa5, 0x93, 0x9e, 0xbd, 0x3a, 0x5c, 0xa2, 0x45, 0xd5, 0x8a, 0x0c, 0xe9, 0xce, 0x33, 0xce, 0x82, 0x2b, 0xf4, 0xd9, 0xa6, 0xa2, 0xec, 0x89, 0xda, 0x90, 0x03, 0xff, 0x4a, 0x5f, 0x14, 0x5a, 0x72, 0xb6, 0x9d, 0x6b, 0x7a, 0xac, 0x57, 0xd9, 0xc6, 0x78, 0x90, 0x59, 0x10, 0xef, 0x2d, 0xa5, 0x66, 0xc6, 0x84, 0x1d, 0x70, 0x98, 0x5c, 0x06, 0x23, 0x05, 0x62, 0x78, 0x79, 0xb4, 0xf1, 0x0a, 0x60, 0x37, 0x24, 0x40, 0xda, 0x94, 0xff, 0x51, 0x53, 0x23, 0x39, 0x0c, 0x5d, 0x30, 0xaf, 0x71, 0xb0, 0x69, 0xb0, 0xba, 0x32, 0x2f, 0x2c, 0x5b, 0x09, 0xc3, 0xf0, 0xeb, 0x91, 0x5e, 0x3d, 0x7d, 0xf8, 0xe5, 0xa5, 0xc2, 0x4e, 0x31, 0x69, 0xd0, 0xb9, 0x8c, 0x15, 0xd3, 0x0c, 0x88, 0xad, 0xd3, 0xa7, 0x45, 0xf0, 0xb4, 0x1b, 0xf9, 0x92, 0xf6, 0x2e, 0x5b, 0xb8, 0xdc, 0x99, 0x5d, 0x9e, 0x6b, 0xd3, 0xfb, 0x63, 0x81, 0xda, 0xa6, 0x0c, 0x77, 0xb1, 0xdb, 0x5d, 0xe2, 0xce, 0x68, 0x7a, 0xff, 0xc2, 0xfc, 0x79, 0x9b, 0xe3, 0xf1, 0xd1, 0x79, 0x05, 0x75, 0xf5, 0xfd, 0xed, 0xc5, 0x8b, 0x77, 0x75, 0x76, 0xec, 0x5a, 0x54, 0xdc, 0x7e, 0xf2, 0x4c, 0x73, 0x63, 0x71, 0x7f, 0x7d, 0xd2, 0xb0, 0x38, 0x27, 0x1a, 0xb4, 0xcc, 0x36, 0x2d, 0xae, 0x3f, 0x96, 0xb4, 0x25, 0xee, 0x1e, 0xcd, 0x99, 0x61, 0x67, 0xcc, 0x4e, 0x9e, 0x85, 0xb8, 0xd3, 0xd7, 0x53, 0x01, 0x28, 0xd5, 0x0d, 0xf1, 0xda, 0x5c, 0x58, 0x4e, 0x64, 0x3d, 0x01, 0xf3, 0xe1, 0xf0, 0x98, 0x08, 0xeb, 0x89, 0x65, 0xf8, 0xa9, 0xdd, 0x8e, 0x46, 0x3a, 0x13, 0x06, 0xe5, 0x05, 0x55, 0x54, 0x58, 0x5b, 0x5d, 0xd8, 0x51, 0xd4, 0x11, 0x2e, 0xf0, 0x06, 0xbd, 0x0a, 0xea, 0x32, 0x9b, 0x3a, 0xa8, 0xf7, 0x06, 0x59, 0x32, 0x7d, 0x8c, 0x33, 0x83, 0xa6, 0x44, 0xca, 0x88, 0x76, 0x7e, 0x3a, 0xba, 0xb1, 0x6f, 0x76, 0x57, 0xd5, 0x74, 0x19, 0x7d, 0x91, 0x9c, 0xaa, 0x8e, 0xb5, 0x77, 0x7a, 0xab, 0xf4, 0xb7, 0x65, 0xe5, 0x39, 0x33, 0xf3, 0xe7, 0x8f, 0xb7, 0x75, 0xed, 0x5a, 0x54, 0x58, 0xd8, 0x35, 0x58, 0x12, 0x8f, 0x2a, 0x74, 0x8a, 0xc0, 0x99, 0xf6, 0xb4, 0xd1, 0x8e, 0xf9, 0xbd, 0xad, 0x9e, 0x58, 0xae, 0xb5, 0x7b, 0xe2, 0x8e, 0xec, 0x1c, 0x24, 0x16, 0x2e, 0x5e, 0xbc, 0xac, 0xa4, 0x7d, 0xe7, 0xa2, 0xe2, 0xf2, 0x65, 0xbb, 0x9a, 0x3a, 0x36, 0xb7, 0x7b, 0x9d, 0x2a, 0xa3, 0x9a, 0xdb, 0x7c, 0x66, 0xc2, 0x70, 0x33, 0xe4, 0xdd, 0x28, 0xd3, 0xc7, 0xbc, 0xfa, 0x44, 0x49, 0x2e, 0xe6, 0xa8, 0x69, 0x3b, 0xb9, 0x58, 0x2a, 0x52, 0x01, 0x57, 0x16, 0xc7, 0x95, 0x02, 0xd1, 0xa1, 0xc6, 0x19, 0x0e, 0x24, 0x04, 0xa0, 0x16, 0x0f, 0x9a, 0x9a, 0x02, 0x23, 0xc5, 0xd8, 0x34, 0x99, 0x98, 0x72, 0x67, 0x47, 0x22, 0xc4, 0x6a, 0xda, 0x52, 0x0c, 0x71, 0x5b, 0xe0, 0xc9, 0x11, 0x20, 0x29, 0xbb, 0xf5, 0x94, 0xb2, 0xf1, 0x4a, 0xb9, 0x18, 0x31, 0xfc, 0x1a, 0x57, 0x22, 0x81, 0x15, 0xe6, 0x5a, 0x94, 0x58, 0xc6, 0xc7, 0x2a, 0xc2, 0xe1, 0xd6, 0xe6, 0x8a, 0xbe, 0x58, 0x5f, 0x38, 0x1a, 0x8e, 0x5a, 0x6c, 0x44, 0x22, 0x57, 0x13, 0xb9, 0xe4, 0x2c, 0x12, 0xf9, 0x0c, 0x89, 0xf5, 0x74, 0x7c, 0x4f, 0xf8, 0xcb, 0x59, 0xc5, 0xf3, 0x93, 0xb7, 0x4c, 0x13, 0x6a, 0xff, 0xfa, 0xbf, 0x91, 0xd5, 0xd1, 0xe5, 0xe7, 0x28, 0xf5, 0xce, 0x9c, 0xdf, 0x44, 0xeb, 0xeb, 0x89, 0x77, 0x5a, 0x32, 0x14, 0xc4, 0xc6, 0x8c, 0x32, 0x0f, 0x2c, 0x8c, 0x13, 0x21, 0x03, 0x84, 0x93, 0x71, 0x98, 0xf4, 0x8c, 0x08, 0x9b, 0xc6, 0x18, 0xb1, 0xa5, 0x48, 0xf2, 0x0d, 0x67, 0xb6, 0x4e, 0x47, 0xae, 0xf2, 0x09, 0x5c, 0xb4, 0xce, 0xa1, 0x73, 0x64, 0xd9, 0x8d, 0x72, 0x94, 0x45, 0x93, 0x7a, 0xb6, 0x4c, 0xef, 0x99, 0x0b, 0xb9, 0xf8, 0xed, 0xd3, 0x68, 0x31, 0xa1, 0x98, 0xf6, 0xe5, 0xf6, 0x73, 0x91, 0xf6, 0xcf, 0x8d, 0x0c, 0xec, 0x34, 0xd9, 0xbf, 0x96, 0x59, 0xc6, 0xdc, 0x1c, 0xd7, 0xc4, 0x4a, 0x8c, 0x44, 0x84, 0xa5, 0xf2, 0xbf, 0x3c, 0x33, 0x0b, 0xd4, 0xc0, 0xee, 0xc5, 0x71, 0x95, 0x40, 0x8e, 0x43, 0x60, 0x7a, 0x61, 0x62, 0x26, 0x0d, 0xa2, 0x47, 0x4a, 0x25, 0x20, 0xf6, 0x0f, 0x49, 0x59, 0xc3, 0x11, 0x2f, 0x91, 0xb3, 0x33, 0x48, 0x40, 0xe3, 0x32, 0x1d, 0xcf, 0x52, 0x64, 0x20, 0xee, 0xa9, 0xaf, 0x2b, 0x06, 0x5d, 0xa2, 0xa7, 0xab, 0x6e, 0x59, 0xfd, 0xb2, 0xe2, 0xda, 0xe2, 0xda, 0x9a, 0xea, 0xf2, 0xc8, 0x94, 0x4e, 0xa1, 0x39, 0x07, 0x9d, 0x62, 0x4e, 0x53, 0x93, 0xbf, 0x6d, 0xae, 0x0a, 0xc6, 0xc9, 0xe1, 0x39, 0xce, 0xd0, 0xb9, 0x2b, 0x1b, 0xac, 0xf3, 0x5f, 0x1d, 0xa3, 0x6a, 0xe0, 0xc2, 0xc3, 0xcc, 0xc7, 0xf1, 0xcc, 0xe6, 0xda, 0x6a, 0x56, 0x98, 0xa6, 0x7f, 0xc8, 0x7c, 0xa7, 0x42, 0x83, 0x94, 0xbc, 0x72, 0x5c, 0xad, 0xc0, 0x20, 0xda, 0x8e, 0x33, 0xb2, 0x34, 0x0c, 0x5c, 0x9a, 0x63, 0x55, 0x2c, 0xa7, 0x1a, 0x4b, 0xa3, 0x9b, 0xc8, 0x7e, 0x8c, 0xf2, 0x28, 0xd7, 0xa6, 0x2d, 0xcd, 0x88, 0x2c, 0xc1, 0x56, 0x19, 0x63, 0x38, 0x52, 0x07, 0x89, 0xb2, 0xc1, 0x8a, 0xe9, 0xea, 0x88, 0xc7, 0xe5, 0xe2, 0x0c, 0xaf, 0xe0, 0xc7, 0xd5, 0x48, 0xc1, 0x28, 0xce, 0xb5, 0x0a, 0xe0, 0x47, 0xf6, 0xae, 0xce, 0x9a, 0x9a, 0x65, 0x4b, 0x3a, 0x87, 0xbb, 0x86, 0x6b, 0x3a, 0x6a, 0x3a, 0x2c, 0x76, 0x59, 0x53, 0xd2, 0x12, 0x18, 0xc5, 0x73, 0xd7, 0x94, 0xe6, 0x34, 0x13, 0xb8, 0xeb, 0xce, 0x59, 0x6d, 0x92, 0x6e, 0x9d, 0xe3, 0x94, 0xf8, 0x17, 0x54, 0x28, 0x64, 0x3b, 0xb7, 0xb9, 0xc1, 0xcd, 0xd0, 0xa9, 0xfc, 0x20, 0x01, 0x6d, 0x8a, 0xaf, 0xcf, 0xb1, 0x21, 0x56, 0x48, 0xe8, 0x55, 0x5a, 0xe0, 0xfd, 0x4a, 0x01, 0x36, 0x06, 0xe2, 0xe8, 0xa2, 0x14, 0x98, 0xad, 0x22, 0x4c, 0x18, 0x15, 0x66, 0x55, 0x63, 0x3a, 0xa4, 0xd4, 0xf0, 0x6a, 0x58, 0xe5, 0x4a, 0x66, 0x0d, 0x31, 0x63, 0x4a, 0x3a, 0xa5, 0x06, 0x03, 0x56, 0xaa, 0x31, 0x95, 0x97, 0x05, 0xe2, 0xc1, 0xb8, 0xd5, 0x6f, 0xf5, 0xfb, 0xbc, 0xd9, 0x8e, 0xd9, 0x9a, 0x53, 0xc6, 0xe9, 0x35, 0xa7, 0xb9, 0xd1, 0xfd, 0xde, 0x53, 0xd4, 0xa8, 0xcf, 0x3f, 0x9f, 0xeb, 0x66, 0x90, 0x4e, 0xa5, 0xc2, 0x6b, 0xce, 0x75, 0x03, 0x60, 0xa7, 0xe9, 0x58, 0xf5, 0xcc, 0x72, 0xe6, 0x57, 0x09, 0xb3, 0x90, 0xaa, 0x32, 0xcc, 0x0b, 0x44, 0xdf, 0x12, 0x93, 0xfa, 0x16, 0xd7, 0xe5, 0x38, 0xf5, 0xb1, 0x02, 0x1e, 0x0f, 0xc8, 0x45, 0x6a, 0x89, 0xbd, 0x21, 0x4f, 0x2e, 0xea, 0x12, 0x6a, 0x19, 0xec, 0x22, 0x58, 0x8d, 0x39, 0xf5, 0x98, 0x16, 0xa9, 0x34, 0x4a, 0xcc, 0x08, 0xaa, 0xb4, 0x1a, 0x9a, 0x5a, 0x3d, 0xa6, 0x86, 0xa5, 0xd3, 0x34, 0xbb, 0x38, 0x03, 0x75, 0x63, 0x05, 0x59, 0x31, 0xa4, 0x92, 0xad, 0xb3, 0x2a, 0x51, 0xa4, 0xa9, 0x64, 0x20, 0x9e, 0x15, 0x6f, 0x28, 0x2d, 0x9d, 0xd7, 0xd3, 0xb0, 0x3c, 0xbe, 0xbc, 0xb4, 0xbe, 0xb4, 0xde, 0x62, 0x4d, 0x28, 0x7b, 0x3a, 0x39, 0x3a, 0xdb, 0x1c, 0x95, 0xbd, 0xc0, 0x9c, 0x36, 0xf6, 0x79, 0xe7, 0xa2, 0xf9, 0x9d, 0xdc, 0x39, 0xc7, 0x61, 0x9d, 0x9b, 0x16, 0xb8, 0xe4, 0x9c, 0xf7, 0x79, 0x6e, 0x86, 0x5e, 0x58, 0xc5, 0x2c, 0x66, 0xbe, 0x16, 0x57, 0x97, 0x17, 0x62, 0x1e, 0x13, 0xf5, 0x30, 0xe9, 0x46, 0x57, 0xac, 0x81, 0x11, 0x55, 0x8c, 0xab, 0x45, 0x0c, 0x09, 0xe3, 0x04, 0x06, 0x1e, 0xb6, 0x2d, 0xc4, 0x8d, 0x83, 0x7a, 0x44, 0x10, 0x4a, 0x95, 0x20, 0x46, 0xa9, 0x12, 0x4a, 0xa3, 0x52, 0x39, 0xa6, 0x24, 0xc1, 0xed, 0x4f, 0x53, 0x22, 0x81, 0xc9, 0x0f, 0xa3, 0x49, 0xca, 0x6d, 0xa5, 0xe5, 0x84, 0x54, 0xb9, 0x81, 0xb8, 0xaf, 0xa6, 0xba, 0x00, 0x94, 0xcd, 0xce, 0xf6, 0xea, 0xc5, 0x35, 0x8b, 0x0b, 0xaa, 0x0a, 0xaa, 0x2a, 0x63, 0xa5, 0x25, 0xd3, 0x95, 0x4e, 0xed, 0x39, 0x28, 0x9d, 0x73, 0xdb, 0xee, 0x16, 0xcf, 0x41, 0x03, 0x9d, 0xd8, 0x3b, 0xd7, 0x9d, 0x6e, 0x4e, 0xea, 0x28, 0x5a, 0xf9, 0xaf, 0xaf, 0x47, 0x03, 0xe3, 0x06, 0x69, 0x04, 0x34, 0x39, 0xa2, 0xa1, 0x3a, 0x4c, 0x98, 0x88, 0x22, 0x09, 0x27, 0xad, 0x31, 0x90, 0x28, 0x64, 0xb1, 0x4c, 0x48, 0x28, 0xac, 0xc4, 0x1e, 0x36, 0x79, 0xdc, 0xe3, 0xf5, 0x18, 0x41, 0x67, 0x2d, 0x29, 0xf2, 0xd4, 0x7a, 0x6b, 0x8d, 0x6e, 0xa3, 0x3b, 0xc7, 0x65, 0xb3, 0xca, 0xba, 0xab, 0x3a, 0xbd, 0xee, 0x3a, 0x27, 0xc9, 0x8c, 0x33, 0x9f, 0xa2, 0xc8, 0x9e, 0x8c, 0xcc, 0x79, 0x52, 0xcf, 0x56, 0x6a, 0xcf, 0x79, 0xfe, 0xf2, 0xb3, 0x74, 0xdc, 0x79, 0xcc, 0x26, 0xc4, 0xc7, 0xd5, 0x9d, 0x4d, 0x58, 0xc0, 0x44, 0xd5, 0x4d, 0xce, 0xe0, 0x4a, 0xe0, 0xe8, 0x9c, 0x72, 0x5c, 0x4b, 0x98, 0x07, 0x99, 0x85, 0x98, 0xc5, 0x30, 0x0b, 0x89, 0x92, 0xa6, 0x62, 0x88, 0xb5, 0x00, 0x52, 0x4f, 0x53, 0x83, 0x35, 0xb2, 0x1a, 0x3c, 0x5d, 0x00, 0xa8, 0x4f, 0x5b, 0x9e, 0x21, 0xec, 0x5e, 0x24, 0x61, 0xb6, 0x48, 0x2d, 0x5b, 0x69, 0x2d, 0x62, 0xda, 0x5a, 0xe2, 0x4d, 0x72, 0x05, 0x0c, 0xa7, 0xe0, 0xc6, 0xb5, 0x74, 0x6d, 0x9c, 0x73, 0x25, 0x20, 0x04, 0xe4, 0x2e, 0x98, 0xdf, 0x48, 0x55, 0xf2, 0xf3, 0x06, 0xe7, 0x6f, 0x5a, 0xb0, 0xa9, 0x71, 0x5e, 0xe3, 0xbc, 0xde, 0x9e, 0xb6, 0xd6, 0xd9, 0xaa, 0x79, 0xc6, 0xff, 0x42, 0x35, 0x9f, 0xdb, 0xa2, 0xe9, 0x3e, 0x67, 0x3d, 0x7d, 0xce, 0x2b, 0xe8, 0x5f, 0x56, 0xda, 0xd1, 0xdf, 0xff, 0x55, 0xf9, 0x80, 0xe8, 0xf0, 0xe4, 0xec, 0xfa, 0xcb, 0x71, 0x75, 0xb4, 0x08, 0x44, 0x3c, 0xa2, 0xca, 0x27, 0x05, 0xfc, 0x08, 0x08, 0x6c, 0xac, 0x62, 0x5c, 0x25, 0x12, 0x5c, 0x63, 0xe0, 0x64, 0xc4, 0x2c, 0x11, 0x76, 0xa3, 0x84, 0x9a, 0x3f, 0xa5, 0xdd, 0x0b, 0x30, 0x52, 0x14, 0xca, 0x85, 0xf2, 0xbf, 0x98, 0x5c, 0x0a, 0xa4, 0x39, 0x76, 0x3c, 0xa1, 0x84, 0xce, 0xa9, 0xe4, 0x40, 0xdc, 0x56, 0x5b, 0x03, 0x0a, 0x3b, 0x3d, 0x0b, 0x2f, 0xac, 0x2e, 0xac, 0xb6, 0xd8, 0xe9, 0x19, 0x81, 0x86, 0x1c, 0x4b, 0xce, 0xed, 0x8c, 0x60, 0x6e, 0x22, 0xc6, 0x3b, 0x73, 0x3a, 0x30, 0x98, 0x68, 0x9a, 0xab, 0x38, 0x37, 0xa7, 0xd3, 0x03, 0xbc, 0xf6, 0x5c, 0x17, 0xf8, 0xcc, 0xf3, 0x84, 0x25, 0xa0, 0xfb, 0xdf, 0x12, 0x57, 0xd5, 0x80, 0xd4, 0x9e, 0x8f, 0xf0, 0xd4, 0xf6, 0x24, 0xcb, 0xd4, 0x3c, 0x71, 0x83, 0x02, 0x6a, 0x13, 0x47, 0x25, 0xd8, 0x99, 0x58, 0xea, 0xf1, 0x99, 0xfc, 0x55, 0x04, 0x4e, 0x38, 0x35, 0x3c, 0x15, 0xd3, 0x4b, 0x80, 0xf2, 0xaa, 0x14, 0x91, 0x72, 0xec, 0x34, 0x25, 0x81, 0x7b, 0x4e, 0x8d, 0x8e, 0x63, 0xd9, 0xd2, 0xdc, 0xf0, 0xc8, 0xf0, 0xd2, 0xf1, 0x65, 0xe3, 0xfd, 0x7d, 0xad, 0xcd, 0xb1, 0x68, 0x78, 0x49, 0xee, 0x62, 0x72, 0x32, 0xa0, 0x99, 0xc3, 0x5d, 0xdd, 0x5c, 0xd5, 0xb4, 0xd3, 0x0d, 0x9a, 0xd8, 0x75, 0xf6, 0x43, 0x83, 0x9b, 0xe7, 0xaa, 0xbc, 0x5d, 0xf0, 0xbf, 0x39, 0x51, 0x90, 0x36, 0xfc, 0x9b, 0x6e, 0x8f, 0xa8, 0x3f, 0x32, 0xb7, 0x88, 0xfd, 0x8c, 0xfa, 0x55, 0x12, 0x2c, 0x35, 0x96, 0x86, 0x46, 0x4d, 0xf8, 0x5e, 0x0f, 0xa4, 0x10, 0xb2, 0x12, 0x41, 0x42, 0xf4, 0x9e, 0x18, 0xfc, 0x0c, 0xb2, 0x9f, 0x9d, 0x54, 0xec, 0xc3, 0xff, 0x9c, 0x10, 0xc8, 0x2f, 0x97, 0xe2, 0xcf, 0x60, 0x69, 0x1d, 0x44, 0x87, 0xf1, 0xfd, 0xec, 0x63, 0x67, 0xb6, 0xf9, 0xd6, 0x7b, 0x0e, 0xe2, 0xf0, 0xc4, 0x1b, 0xec, 0x63, 0x13, 0xbf, 0x24, 0x67, 0xbe, 0x53, 0x65, 0xe4, 0x18, 0x25, 0xf2, 0xbd, 0xf6, 0x9a, 0xd9, 0x86, 0x03, 0xb3, 0x5f, 0x4f, 0xeb, 0xb8, 0x14, 0x85, 0x25, 0x5a, 0xd1, 0x7e, 0x14, 0x22, 0xe6, 0x06, 0x5b, 0x98, 0x9d, 0x5c, 0x33, 0xd7, 0x23, 0xc7, 0x28, 0xa1, 0xef, 0x5f, 0x9a, 0xb0, 0x0a, 0x60, 0xd0, 0xfc, 0xb0, 0x9f, 0xbe, 0x7f, 0xd6, 0xf5, 0xfe, 0x96, 0xde, 0xcb, 0x1f, 0x19, 0x19, 0x79, 0xf4, 0x50, 0x6f, 0xef, 0xa1, 0x47, 0x47, 0x46, 0x1e, 0xb9, 0xbc, 0x17, 0xaf, 0x4e, 0x7d, 0x7b, 0xf4, 0x60, 0x6f, 0xef, 0xc1, 0x47, 0xa1, 0x8d, 0x7b, 0xd1, 0x41, 0xfc, 0x08, 0xfb, 0x14, 0x9d, 0xff, 0x53, 0x6d, 0x1c, 0xa2, 0x6d, 0x5c, 0x9a, 0xb8, 0x81, 0xc7, 0xf0, 0x86, 0x04, 0x7e, 0xc8, 0xec, 0x77, 0xc4, 0x66, 0x61, 0x28, 0xee, 0x2d, 0xe8, 0x19, 0xa9, 0xa9, 0xde, 0xd0, 0x53, 0x58, 0xd8, 0xb3, 0xa1, 0xba, 0x66, 0xa4, 0xa7, 0xe0, 0x62, 0x5b, 0x5e, 0xa5, 0xdb, 0x5d, 0x95, 0x67, 0xb7, 0xe7, 0x55, 0xb9, 0xdd, 0x95, 0x79, 0x36, 0xfc, 0x95, 0x54, 0xf2, 0xfa, 0xde, 0x82, 0x82, 0xde, 0xf5, 0x35, 0xe4, 0xe9, 0xb4, 0x5c, 0xd4, 0x16, 0x84, 0xe0, 0xce, 0xc1, 0xf6, 0xfb, 0x1e, 0xc8, 0x7f, 0x91, 0xc7, 0x04, 0x1e, 0x33, 0xed, 0xb2, 0x55, 0xb9, 0x7a, 0x96, 0x91, 0x32, 0x71, 0xcb, 0x99, 0x16, 0x25, 0x62, 0xe0, 0x31, 0x2b, 0x1d, 0x0a, 0x3d, 0x8d, 0x08, 0x56, 0xe1, 0xf7, 0xdc, 0x30, 0xf1, 0xfe, 0x9b, 0x63, 0xe5, 0x3f, 0x45, 0x9f, 0xa0, 0x52, 0xbe, 0xdf, 0x92, 0xdf, 0x90, 0x7b, 0x60, 0xc9, 0x30, 0xb5, 0xf5, 0x61, 0x12, 0xb8, 0x76, 0xa4, 0x7e, 0x8e, 0xc5, 0x28, 0x55, 0x7f, 0x82, 0x6f, 0xae, 0x43, 0xb2, 0x2d, 0xf6, 0x4c, 0x42, 0xa7, 0xea, 0x77, 0x73, 0x04, 0x5d, 0xd4, 0x87, 0x4a, 0xd1, 0x27, 0x3f, 0x8d, 0x6c, 0xff, 0xd5, 0xfb, 0x13, 0x47, 0xf9, 0xf7, 0x86, 0x97, 0x1c, 0xc8, 0x6d, 0xc8, 0xb7, 0xc8, 0xed, 0xcf, 0xe3, 0xee, 0x84, 0xf6, 0xff, 0x11, 0xe8, 0xda, 0x12, 0x57, 0x19, 0xa9, 0x95, 0xbd, 0xec, 0x3c, 0x6e, 0xa3, 0x6e, 0xaf, 0xd4, 0x70, 0x25, 0x71, 0x22, 0xb3, 0x8e, 0x99, 0x69, 0xcb, 0x42, 0xc8, 0x4e, 0xda, 0x80, 0xa1, 0x3f, 0x8f, 0xfb, 0x0d, 0x7e, 0x62, 0x53, 0xa2, 0x87, 0x97, 0x25, 0xec, 0x6c, 0xa9, 0x2d, 0x5d, 0xed, 0x45, 0x07, 0x0e, 0xf7, 0xdc, 0x7c, 0xed, 0x57, 0x2a, 0xc7, 0x1f, 0xd8, 0xd9, 0x75, 0xc5, 0xbe, 0x8b, 0xea, 0xb8, 0xff, 0x32, 0x06, 0x5d, 0xc6, 0xc5, 0x2b, 0x7a, 0x77, 0x2f, 0xcc, 0xcf, 0x74, 0xf8, 0x8c, 0x72, 0x1f, 0xaf, 0x64, 0x5f, 0xe7, 0x5f, 0x22, 0x32, 0x5d, 0xdc, 0xa9, 0x24, 0x67, 0xa0, 0xc4, 0xa5, 0xb1, 0x8b, 0x49, 0x40, 0xae, 0xc1, 0x9b, 0x71, 0x8f, 0x21, 0xd7, 0x4b, 0x6d, 0x65, 0x12, 0xb6, 0x83, 0x51, 0xa2, 0x98, 0x54, 0x44, 0x23, 0xec, 0xeb, 0xef, 0xfc, 0x0f, 0xad, 0xb7, 0xee, 0xa2, 0xfd, 0x57, 0xf5, 0xdc, 0x7c, 0xdd, 0x57, 0xf8, 0x97, 0x6e, 0xf9, 0x66, 0x66, 0x96, 0xcf, 0x78, 0x9b, 0xfc, 0x1a, 0x7a, 0xa7, 0x41, 0x7c, 0x59, 0x3f, 0x14, 0xde, 0x66, 0x1c, 0x4c, 0x80, 0xa9, 0x8f, 0xd7, 0x10, 0xb3, 0x2e, 0xd8, 0x88, 0x04, 0x24, 0x90, 0xe0, 0x11, 0xd4, 0xb1, 0x75, 0x75, 0xca, 0xb1, 0x75, 0x88, 0xed, 0xc9, 0xce, 0x46, 0x4c, 0x76, 0x20, 0xdb, 0xef, 0xc9, 0x99, 0xe9, 0x88, 0xa2, 0x52, 0x9d, 0xc1, 0xc1, 0x95, 0x3a, 0xf7, 0x93, 0xd3, 0x87, 0x33, 0xb8, 0xba, 0x22, 0xfe, 0x43, 0x9d, 0xd3, 0x7a, 0xc1, 0x0d, 0x3a, 0xaf, 0x2f, 0x9d, 0xd7, 0x2b, 0xba, 0x4f, 0x9a, 0xd0, 0x6b, 0xff, 0x0a, 0xe2, 0x28, 0xb4, 0xb9, 0x07, 0xa4, 0xf9, 0x95, 0x8a, 0xa5, 0x8c, 0x1a, 0xf4, 0x91, 0x8e, 0x78, 0xab, 0x01, 0x61, 0xde, 0x4b, 0xa2, 0x48, 0x74, 0x09, 0x48, 0x36, 0xf5, 0xe2, 0x99, 0x71, 0x91, 0x20, 0x60, 0xc0, 0x30, 0xf0, 0x0c, 0xc7, 0xf2, 0x1c, 0x85, 0x3f, 0x21, 0xc7, 0xb7, 0x98, 0x9d, 0xaf, 0xd5, 0x30, 0x4c, 0x38, 0xe8, 0x74, 0xd8, 0x2c, 0x9a, 0x7c, 0x6d, 0x3e, 0x54, 0xa2, 0xf2, 0x29, 0x54, 0xe6, 0x7c, 0x25, 0x75, 0x9a, 0x27, 0xb0, 0x0c, 0x6c, 0xd2, 0x63, 0x23, 0x85, 0x15, 0x95, 0x72, 0xa9, 0x59, 0x29, 0x3d, 0xad, 0x77, 0x5a, 0xaf, 0x7e, 0x47, 0x99, 0x69, 0xca, 0xd2, 0x9f, 0x6f, 0xf5, 0x5a, 0x35, 0xbb, 0xec, 0x25, 0x6d, 0x45, 0x45, 0x1d, 0x75, 0x15, 0x0e, 0xab, 0xd7, 0xa2, 0xd9, 0x63, 0x2f, 0x6d, 0x2b, 0x2c, 0xee, 0xa8, 0x8b, 0x2a, 0x96, 0x4a, 0xef, 0x64, 0x88, 0x48, 0xaf, 0xd1, 0x89, 0x58, 0x2a, 0xd3, 0x67, 0xfb, 0x0c, 0x85, 0xfd, 0x0d, 0x41, 0x77, 0xc8, 0x63, 0x70, 0xfa, 0xf5, 0x85, 0x0b, 0xe3, 0x41, 0x4f, 0xc8, 0x4d, 0xe9, 0x4f, 0xfc, 0x50, 0x3e, 0x14, 0x9e, 0x87, 0x39, 0x0c, 0x12, 0x46, 0x3c, 0x97, 0x4b, 0xf4, 0x00, 0xc3, 0x9e, 0x43, 0xee, 0x6f, 0x09, 0x8b, 0x21, 0x58, 0x79, 0x98, 0x9d, 0xc7, 0x30, 0xc4, 0xa3, 0x9e, 0x40, 0x64, 0xf9, 0x78, 0x02, 0x24, 0x36, 0x45, 0x55, 0x62, 0x6b, 0xf7, 0x61, 0x8a, 0x7c, 0x52, 0x03, 0xf1, 0x59, 0x11, 0x9e, 0x97, 0x24, 0x20, 0x98, 0xd5, 0x2a, 0x5d, 0xb1, 0x8f, 0x54, 0xb7, 0x9e, 0xf8, 0xbb, 0x24, 0xde, 0x53, 0x18, 0xcf, 0xe3, 0x12, 0x16, 0x2e, 0xe4, 0x40, 0x72, 0xd6, 0x7b, 0x10, 0x7d, 0x8f, 0xc0, 0x31, 0x4a, 0xa4, 0xa4, 0x9e, 0x2c, 0x04, 0x0e, 0x8b, 0x46, 0x63, 0xa1, 0x21, 0xbc, 0x72, 0x80, 0x22, 0x37, 0x6c, 0xb3, 0x3a, 0x75, 0x1f, 0x8a, 0xf2, 0x7b, 0xac, 0xd6, 0xbf, 0x6a, 0xf5, 0x08, 0x93, 0xf7, 0x4c, 0x4e, 0x32, 0x3b, 0x84, 0x12, 0x76, 0x40, 0x91, 0x8d, 0x43, 0xcc, 0x03, 0x4c, 0x25, 0xf3, 0x16, 0x12, 0x18, 0xe2, 0x33, 0x4d, 0xe3, 0xcb, 0xbf, 0x25, 0x83, 0x4b, 0x62, 0x46, 0x2d, 0x94, 0xe0, 0xfb, 0x15, 0x4e, 0x20, 0x7d, 0xc6, 0x59, 0xda, 0x82, 0x19, 0x82, 0x23, 0xa0, 0x10, 0x18, 0x35, 0x56, 0x93, 0xb6, 0x18, 0x69, 0x88, 0x5e, 0xd2, 0x18, 0x44, 0x1a, 0xf3, 0x6b, 0xbd, 0xdd, 0x64, 0x50, 0xbe, 0x7d, 0x8d, 0xd5, 0xa1, 0x97, 0x5e, 0xd8, 0x88, 0x2b, 0x27, 0x5e, 0x54, 0x38, 0xb1, 0x42, 0xab, 0x91, 0x3e, 0x12, 0x74, 0xc8, 0x2d, 0x1d, 0x3a, 0x5f, 0x3e, 0xa7, 0x1e, 0x86, 0x39, 0xfe, 0x0b, 0xe1, 0x78, 0xc2, 0x3f, 0xab, 0x2e, 0x5e, 0x0d, 0xb3, 0x5c, 0x81, 0x04, 0x98, 0xea, 0x09, 0xc7, 0xab, 0xe9, 0xde, 0xdb, 0x43, 0xc4, 0x7b, 0xfb, 0x34, 0xde, 0x56, 0xaa, 0x7f, 0xdd, 0xdb, 0xea, 0x0c, 0x53, 0xff, 0xa6, 0xb3, 0x7b, 0x5b, 0xa5, 0x5d, 0x12, 0x0a, 0x5b, 0x7e, 0x8e, 0xd1, 0x98, 0x33, 0xdd, 0xdb, 0x6a, 0xc6, 0x77, 0x46, 0xf6, 0x4d, 0x7b, 0x09, 0xbf, 0x08, 0x7d, 0x37, 0x52, 0x5c, 0xd9, 0x60, 0xdc, 0x47, 0x7d, 0x21, 0xa0, 0xd3, 0xa0, 0x3d, 0x70, 0x3c, 0xcf, 0x2d, 0x66, 0x38, 0x8e, 0x1f, 0x20, 0xda, 0x77, 0xaf, 0xde, 0x44, 0x76, 0x08, 0x0a, 0x32, 0x79, 0xb6, 0xce, 0x90, 0x89, 0xd0, 0x76, 0xf6, 0x66, 0x4f, 0xdc, 0x4d, 0xa7, 0x48, 0xfd, 0x59, 0x1a, 0x2a, 0xfd, 0x6d, 0x9f, 0xac, 0xbf, 0xc2, 0xbc, 0x78, 0x43, 0x7c, 0x3d, 0xd1, 0xd6, 0xd6, 0x24, 0xd6, 0x14, 0x0d, 0x2a, 0xb7, 0x12, 0x53, 0xef, 0x4d, 0xe2, 0x6d, 0xb5, 0x91, 0xf0, 0xda, 0xac, 0xd3, 0xf7, 0x83, 0x84, 0xd5, 0x9c, 0x73, 0x57, 0x78, 0xd2, 0x95, 0x12, 0xa3, 0x3b, 0xdf, 0x66, 0xcd, 0x25, 0x8d, 0xca, 0xb5, 0xda, 0xf2, 0xdd, 0x46, 0xf4, 0x4d, 0xe8, 0x94, 0x15, 0x3a, 0x67, 0x30, 0x40, 0xe7, 0xac, 0xd0, 0xc9, 0x89, 0xdb, 0xc8, 0xf4, 0x12, 0xd7, 0xd8, 0x72, 0xc9, 0xb3, 0xdc, 0xa9, 0x8e, 0xcf, 0xfc, 0xfe, 0x3a, 0x61, 0xff, 0x0b, 0x79, 0x89, 0x6d, 0x16, 0xaf, 0xa1, 0x51, 0xdb, 0x17, 0xca, 0xbb, 0x85, 0x9d, 0x43, 0x49, 0x24, 0xcc, 0x84, 0x6b, 0x10, 0xbd, 0x4c, 0xda, 0x48, 0xbc, 0x85, 0x5c, 0xb3, 0xd2, 0x88, 0x35, 0xc6, 0xd2, 0x44, 0x0c, 0x3d, 0x16, 0xcf, 0xa7, 0x00, 0x99, 0xd9, 0x59, 0x86, 0x4c, 0xca, 0xaa, 0xf8, 0x19, 0xac, 0x6a, 0x76, 0x6f, 0xd8, 0xe6, 0xe9, 0x0c, 0x4a, 0xfa, 0x93, 0xd9, 0x5f, 0xec, 0xb0, 0x17, 0x91, 0xc8, 0x79, 0x45, 0x76, 0x47, 0xb1, 0xdf, 0x2c, 0x96, 0x49, 0xef, 0xe8, 0x14, 0xc8, 0xa0, 0xd1, 0x8a, 0x38, 0x9c, 0x55, 0xe4, 0x35, 0x9b, 0xbd, 0x45, 0x59, 0x24, 0x01, 0x32, 0xca, 0x3e, 0xb4, 0x8b, 0xb8, 0xeb, 0xf1, 0xcb, 0xc2, 0x09, 0x18, 0x03, 0x3f, 0xb3, 0xf4, 0x71, 0x17, 0xb1, 0x0d, 0x49, 0x00, 0xe4, 0x58, 0x49, 0xd8, 0x16, 0xbc, 0x9e, 0x41, 0xbc, 0x7c, 0x8e, 0x23, 0x70, 0xb0, 0x58, 0x87, 0x58, 0x8a, 0x00, 0x43, 0xfd, 0xde, 0x67, 0x27, 0x0c, 0xc4, 0xcd, 0x26, 0x13, 0x62, 0x4c, 0x7e, 0x93, 0x8f, 0xa0, 0xb2, 0xca, 0xa0, 0xac, 0x62, 0x1a, 0x50, 0x56, 0x9e, 0xf5, 0xb1, 0x14, 0x74, 0xcf, 0x62, 0xad, 0x20, 0xb1, 0x86, 0x22, 0xe8, 0x3a, 0x47, 0x09, 0x41, 0x5f, 0x2d, 0x71, 0x38, 0xca, 0x02, 0x16, 0x4b, 0xa0, 0x0c, 0x79, 0xdf, 0x7e, 0xe2, 0xed, 0x2b, 0x34, 0x56, 0xf1, 0x42, 0x25, 0xfc, 0xb9, 0x48, 0xb4, 0x6a, 0xf8, 0xe3, 0x24, 0x39, 0xbb, 0x8c, 0x64, 0x2b, 0xcb, 0x26, 0xd9, 0x1d, 0xd2, 0x27, 0x48, 0xad, 0x14, 0x0e, 0x28, 0x35, 0x1a, 0xe5, 0x01, 0x50, 0x94, 0x31, 0xd3, 0x26, 0xa8, 0xf0, 0x9b, 0x0a, 0x17, 0xf4, 0x25, 0xc8, 0xac, 0x95, 0x3b, 0xe1, 0x26, 0x8d, 0xa3, 0xb0, 0x4e, 0xe4, 0x3c, 0x1f, 0xb6, 0xed, 0x29, 0xe1, 0x83, 0x18, 0x0b, 0x33, 0xf3, 0x1d, 0xf1, 0x9c, 0xe9, 0x59, 0xf0, 0xf6, 0x53, 0x72, 0x24, 0xbb, 0x15, 0x34, 0x05, 0xe6, 0xd8, 0xad, 0x54, 0x10, 0xa5, 0x08, 0xba, 0x76, 0x16, 0xd1, 0x51, 0xe9, 0x47, 0x4f, 0x7c, 0x74, 0xad, 0xd6, 0xa7, 0x1b, 0x67, 0x05, 0x16, 0xfe, 0x8d, 0xeb, 0x7c, 0x5a, 0x31, 0x7b, 0x5a, 0x06, 0x07, 0x29, 0x80, 0xa4, 0x8f, 0x90, 0x5e, 0x30, 0x5e, 0xcb, 0x2b, 0x39, 0x4e, 0xc9, 0x5f, 0x63, 0x12, 0x64, 0x5b, 0xdb, 0xdf, 0x73, 0x03, 0x6c, 0x9c, 0xef, 0x62, 0x9c, 0xc4, 0x1f, 0x91, 0xc0, 0xc2, 0x80, 0xdc, 0x21, 0x43, 0xaf, 0x11, 0x39, 0x85, 0x84, 0xbc, 0x85, 0xa4, 0x6c, 0x6b, 0x16, 0x27, 0x58, 0x88, 0x70, 0x40, 0x62, 0x6d, 0x11, 0x54, 0x1a, 0x9f, 0x38, 0xf3, 0xb3, 0x08, 0x45, 0xd9, 0x38, 0x8b, 0x4b, 0x59, 0xbd, 0x41, 0xcf, 0xba, 0xde, 0xf3, 0xc0, 0x67, 0x26, 0x9b, 0xf3, 0xbe, 0x9b, 0xcb, 0x84, 0xef, 0x45, 0x98, 0xe5, 0xbb, 0x34, 0x99, 0xea, 0x47, 0x34, 0xf0, 0x67, 0xe7, 0xce, 0xa9, 0xff, 0x1f, 0x51, 0x1a, 0x34, 0x24, 0x6e, 0x25, 0xca, 0xc7, 0xd5, 0xec, 0x2a, 0x72, 0x64, 0xf7, 0x28, 0xc8, 0x47, 0xc4, 0x0f, 0x8c, 0x84, 0x9c, 0x42, 0x95, 0xfb, 0xa4, 0x1f, 0xb2, 0xab, 0x24, 0x57, 0x3f, 0x7a, 0x47, 0xc6, 0xdc, 0x90, 0xee, 0xc5, 0xcb, 0x99, 0x6c, 0x68, 0x50, 0x6e, 0x3c, 0x48, 0xe4, 0x24, 0x07, 0xbd, 0x21, 0x4b, 0xda, 0x87, 0x0e, 0x13, 0xfb, 0x58, 0xba, 0x24, 0x36, 0xb0, 0x3d, 0x61, 0xbd, 0x1c, 0x7b, 0x8e, 0x97, 0x3d, 0xc6, 0xa9, 0x5c, 0x1a, 0x4b, 0x09, 0xac, 0xc1, 0x50, 0xac, 0x02, 0x19, 0x78, 0x8d, 0x31, 0xdb, 0x52, 0x5a, 0xa1, 0x75, 0x95, 0x05, 0x33, 0xaa, 0xcf, 0x6b, 0xf6, 0x3b, 0xcb, 0xbb, 0x8a, 0xca, 0xcf, 0xcb, 0x71, 0xf1, 0x3f, 0x11, 0x45, 0xb1, 0xbe, 0xc8, 0x9a, 0xe7, 0x31, 0x65, 0x3b, 0x23, 0xcd, 0xc1, 0x70, 0x4b, 0x69, 0x76, 0xb6, 0xb5, 0x53, 0x4b, 0x63, 0x25, 0x08, 0x22, 0x7e, 0x5b, 0x61, 0x84, 0xbd, 0xe9, 0x09, 0x86, 0x34, 0x40, 0x60, 0x1e, 0x23, 0xe0, 0xaa, 0x04, 0xf1, 0x1f, 0x39, 0xc8, 0xe6, 0x44, 0xe2, 0x09, 0x70, 0x57, 0xb3, 0xbc, 0xe0, 0xc4, 0x41, 0xe6, 0xfe, 0x16, 0x82, 0x6d, 0xfd, 0xbd, 0xc9, 0x7e, 0x66, 0x25, 0xec, 0x5f, 0x08, 0x3d, 0xb4, 0x52, 0xde, 0xbf, 0x90, 0xf4, 0xc7, 0xc9, 0x1b, 0x38, 0x2f, 0xff, 0x5b, 0x12, 0x3b, 0x51, 0xf6, 0x32, 0x57, 0xcb, 0xc6, 0xc4, 0x20, 0xd1, 0xca, 0x38, 0x39, 0xf2, 0x1e, 0xce, 0x6c, 0x1c, 0x78, 0x2c, 0x40, 0x43, 0x04, 0x8a, 0x32, 0x1e, 0x0f, 0x5b, 0x83, 0xe6, 0x59, 0xe3, 0xee, 0xc5, 0xdc, 0x0e, 0xb5, 0xc1, 0x64, 0x33, 0x6c, 0x29, 0x73, 0x3b, 0x04, 0x53, 0x86, 0x59, 0x23, 0xc0, 0x58, 0x7e, 0x71, 0x32, 0xca, 0x5e, 0xc3, 0x9d, 0x80, 0xfd, 0x59, 0x4f, 0xd6, 0x7b, 0x02, 0x68, 0xc7, 0x47, 0x8b, 0xcb, 0xe1, 0xb7, 0x48, 0x1d, 0xf8, 0x1f, 0x19, 0x41, 0x47, 0xcb, 0xc9, 0x63, 0x19, 0xb9, 0xd6, 0x16, 0xee, 0x8b, 0x56, 0xc7, 0x3f, 0xef, 0xb4, 0x3a, 0xe4, 0xb9, 0x70, 0x12, 0x5f, 0x81, 0xbb, 0x59, 0x2f, 0x8c, 0x03, 0x68, 0x48, 0x32, 0x22, 0xef, 0x62, 0x0a, 0x6d, 0x32, 0x40, 0x2a, 0xeb, 0xa5, 0xfc, 0x95, 0xd5, 0xb3, 0x44, 0x70, 0x30, 0x7b, 0xa2, 0xb8, 0x7b, 0xe2, 0x61, 0xd6, 0x7b, 0xcb, 0x2d, 0x89, 0x18, 0x12, 0x42, 0x26, 0xf0, 0xdd, 0xe3, 0x40, 0x17, 0xd8, 0xab, 0x91, 0x09, 0xe8, 0xf2, 0x84, 0x4c, 0x17, 0xf4, 0x6d, 0x64, 0x4a, 0xd0, 0x05, 0xf2, 0xb0, 0x9d, 0x8a, 0x7e, 0xba, 0xaf, 0x87, 0x4f, 0x93, 0x87, 0xf8, 0x41, 0x7f, 0x20, 0xfc, 0x27, 0xe4, 0xb9, 0x9f, 0xc9, 0x47, 0xb5, 0x90, 0xe7, 0xf1, 0x54, 0x9e, 0xda, 0x44, 0x9e, 0x36, 0x78, 0xd7, 0x9b, 0x8a, 0x62, 0xc8, 0xf3, 0xf0, 0xe9, 0xde, 0x35, 0xf9, 0x3e, 0x69, 0x0f, 0x7d, 0xd7, 0x7d, 0x93, 0x2f, 0x9e, 0xe6, 0x5d, 0x3b, 0x84, 0x2c, 0x7c, 0xbf, 0xf8, 0x9b, 0x44, 0x9b, 0xab, 0x20, 0xcf, 0xf3, 0xa9, 0x3c, 0x55, 0x89, 0x3c, 0x5b, 0xb9, 0x47, 0x40, 0xe6, 0xb9, 0x1c, 0xf2, 0x1c, 0x87, 0x3c, 0x35, 0x90, 0xe7, 0x7b, 0xa9, 0x3c, 0x35, 0x74, 0x30, 0xf1, 0xe4, 0x1d, 0xc0, 0x23, 0xae, 0x10, 0x1d, 0x40, 0x77, 0x23, 0x91, 0x45, 0x78, 0x90, 0x1a, 0xa9, 0xbe, 0x40, 0x7f, 0x91, 0x37, 0x1c, 0xd0, 0x19, 0xd4, 0x20, 0x27, 0x64, 0xea, 0x54, 0x46, 0xb5, 0x51, 0x96, 0x8b, 0x84, 0x69, 0x72, 0x11, 0x2c, 0xa4, 0xd4, 0x6f, 0xe8, 0x16, 0x5f, 0xbe, 0xee, 0x8b, 0x2b, 0xad, 0xd9, 0x99, 0xaf, 0xfc, 0xd1, 0x57, 0xa0, 0x7f, 0x72, 0xa3, 0xd5, 0xa3, 0xff, 0x5c, 0x74, 0x58, 0xad, 0x7f, 0xc8, 0xd4, 0xfd, 0xcd, 0x66, 0xf9, 0x43, 0x46, 0xc6, 0xdf, 0x60, 0x34, 0x56, 0x0a, 0x4a, 0xfc, 0xbc, 0x68, 0x04, 0xe9, 0x27, 0x2f, 0x1e, 0x92, 0x7d, 0x13, 0x39, 0xd9, 0xdd, 0x90, 0x9b, 0x72, 0x37, 0x24, 0x62, 0x98, 0x82, 0x84, 0xe9, 0xca, 0x40, 0x19, 0x09, 0x31, 0x4c, 0xf6, 0xd4, 0xa3, 0xf2, 0x68, 0x05, 0x3a, 0x72, 0xe4, 0x6b, 0x37, 0xb5, 0x2e, 0x2d, 0xa8, 0x0d, 0x64, 0xde, 0x74, 0xa7, 0x68, 0x3c, 0xbc, 0xaf, 0xb5, 0xd7, 0x14, 0x88, 0x79, 0xaf, 0x38, 0x90, 0x90, 0xe9, 0x9f, 0x15, 0x2e, 0xa7, 0x78, 0xf2, 0xb1, 0x78, 0x39, 0xb1, 0x49, 0x47, 0x82, 0xb8, 0x46, 0x91, 0x5e, 0xd6, 0x21, 0xc0, 0xd9, 0x33, 0xe5, 0x1c, 0xe5, 0x99, 0x84, 0x79, 0xb2, 0x5f, 0x9e, 0x5e, 0x96, 0x91, 0x0e, 0x51, 0x87, 0xe9, 0x74, 0xe2, 0xca, 0x7c, 0x2a, 0x1f, 0xf6, 0x90, 0x35, 0x28, 0x3e, 0x9d, 0x18, 0x37, 0xdb, 0xd4, 0x1a, 0x84, 0x31, 0xb1, 0xc9, 0x63, 0xc2, 0x74, 0x71, 0x1f, 0xb3, 0xb7, 0x0b, 0xc7, 0xa8, 0xce, 0xdd, 0x20, 0xef, 0x9f, 0xc6, 0x64, 0xc4, 0xb8, 0x24, 0xc4, 0x3c, 0xdd, 0x78, 0x6c, 0xa9, 0xa7, 0x29, 0xb9, 0x71, 0x80, 0xc8, 0x8d, 0xbd, 0x03, 0x4f, 0x4c, 0x0f, 0x1a, 0x4a, 0xfe, 0x76, 0xb1, 0x8b, 0x4e, 0xde, 0x4f, 0x7e, 0x84, 0x63, 0xfb, 0xee, 0xde, 0x27, 0xfb, 0x8a, 0x7c, 0x55, 0x1a, 0xc5, 0x1b, 0x27, 0xbf, 0x4a, 0x4c, 0x4f, 0x1f, 0x85, 0x61, 0x28, 0xc9, 0xe7, 0x89, 0xc5, 0x7f, 0x0c, 0x6f, 0x7c, 0xf7, 0xdd, 0x05, 0x47, 0x8f, 0x3e, 0xf4, 0xee, 0xbb, 0x7d, 0x37, 0xdf, 0x3c, 0x3b, 0x1f, 0x47, 0x78, 0x1c, 0x22, 0x01, 0x30, 0xad, 0xd8, 0x7e, 0xf4, 0xe8, 0x82, 0x77, 0xdf, 0x95, 0x46, 0x6f, 0xbe, 0xb9, 0xef, 0xdd, 0x77, 0x21, 0xdf, 0x5b, 0x13, 0x9f, 0x62, 0xc3, 0xe4, 0x77, 0xe5, 0x7c, 0x34, 0x46, 0x31, 0x75, 0x21, 0x40, 0x17, 0x1e, 0x83, 0x6c, 0x0b, 0xa4, 0x5f, 0x43, 0xee, 0xf7, 0x16, 0xc0, 0xec, 0x52, 0x4d, 0xfc, 0x1d, 0xd7, 0x4f, 0x3e, 0x47, 0xf3, 0xf1, 0xe4, 0xbd, 0x88, 0xa2, 0x5f, 0xe1, 0xfa, 0x77, 0x17, 0x1c, 0x3b, 0x06, 0xf9, 0xde, 0xeb, 0x83, 0x8a, 0x81, 0x0e, 0xcf, 0x4b, 0x1e, 0xbc, 0x6a, 0xf2, 0x76, 0x8a, 0x81, 0x7e, 0x06, 0xbd, 0xde, 0x1f, 0x3c, 0x9d, 0x5e, 0x6f, 0x89, 0x24, 0x60, 0x2b, 0x85, 0xe7, 0x3d, 0x95, 0xdd, 0xb9, 0x79, 0xdd, 0x55, 0x5e, 0x6f, 0x55, 0x77, 0x5e, 0x6e, 0x77, 0xa5, 0x67, 0x4b, 0x79, 0x51, 0x41, 0x34, 0x5a, 0x50, 0x54, 0x2e, 0xbd, 0x95, 0xdf, 0x16, 0xc9, 0xce, 0x8e, 0xb4, 0xe5, 0xe7, 0xb5, 0x95, 0xc1, 0x7e, 0xdb, 0x96, 0x97, 0x5f, 0x51, 0x91, 0x5f, 0x50, 0x51, 0x21, 0xd3, 0xe8, 0x18, 0x77, 0x08, 0x8f, 0x0b, 0x5d, 0xd0, 0x56, 0x57, 0xdc, 0x91, 0xd6, 0x57, 0x23, 0x11, 0x77, 0x58, 0x8e, 0x5f, 0x8e, 0xc7, 0xa5, 0x87, 0x0f, 0xa3, 0x37, 0xf9, 0x97, 0xa4, 0xa7, 0xd1, 0x09, 0xb9, 0xfc, 0xe5, 0xd3, 0xca, 0xa7, 0xf3, 0xb9, 0x48, 0x96, 0xa7, 0x71, 0x8b, 0xf1, 0xf8, 0x61, 0xe9, 0x61, 0xf4, 0x26, 0x77, 0x39, 0x6a, 0x93, 0x7d, 0xee, 0x8f, 0x91, 0xd8, 0x86, 0x50, 0x15, 0x4f, 0x4f, 0x7e, 0x88, 0x8e, 0x40, 0x77, 0xb8, 0xe5, 0x32, 0xf8, 0x19, 0x3d, 0xde, 0xe7, 0xf5, 0x14, 0x59, 0x34, 0x60, 0x26, 0xd1, 0x99, 0xa1, 0x01, 0xc1, 0xc3, 0xfb, 0xf6, 0xa1, 0x13, 0xd2, 0xd3, 0xb4, 0xfc, 0x51, 0x28, 0xbf, 0x33, 0x59, 0x9e, 0xdc, 0x0f, 0x62, 0xc2, 0x58, 0xd3, 0x95, 0x47, 0x34, 0xbe, 0x33, 0xde, 0x29, 0xb9, 0xd0, 0x52, 0x12, 0x5d, 0x76, 0x1f, 0x6a, 0x85, 0xf2, 0x5d, 0xd2, 0x51, 0xee, 0xd8, 0xe4, 0x53, 0x30, 0x13, 0x57, 0x75, 0x3f, 0xa4, 0x23, 0x33, 0x91, 0x9c, 0x34, 0x40, 0xe3, 0x47, 0x98, 0x44, 0x33, 0x36, 0xa4, 0xf0, 0x5c, 0x0d, 0x34, 0x89, 0x68, 0x31, 0x53, 0x29, 0x71, 0x6b, 0xea, 0x61, 0x92, 0x6c, 0xcb, 0x09, 0xd9, 0xe6, 0x25, 0x7c, 0xe1, 0x45, 0x46, 0x84, 0xa9, 0x4a, 0x7c, 0xe1, 0x79, 0x3d, 0xb4, 0x1f, 0x78, 0x33, 0x77, 0xec, 0xf3, 0x47, 0xd6, 0xb0, 0x57, 0xb7, 0x49, 0x6f, 0xa2, 0xe0, 0xdd, 0x77, 0x33, 0xd3, 0xda, 0xc0, 0x33, 0x83, 0x72, 0x1b, 0x0c, 0x14, 0x97, 0x65, 0x24, 0x45, 0x89, 0xa9, 0x26, 0xe8, 0x67, 0x92, 0x48, 0x6e, 0x41, 0xf2, 0x19, 0x69, 0x40, 0x32, 0x49, 0x6e, 0x81, 0x7a, 0x3a, 0x01, 0x78, 0x42, 0x40, 0x33, 0x7d, 0xfd, 0x07, 0x97, 0xc2, 0xbb, 0x29, 0xfd, 0xde, 0x95, 0xcc, 0xac, 0x6b, 0x72, 0x07, 0xf0, 0xc7, 0x8a, 0xe3, 0xb0, 0xb1, 0x77, 0x80, 0xcc, 0xdd, 0x9e, 0x04, 0x87, 0x93, 0x77, 0x17, 0xd9, 0x9f, 0x46, 0x3e, 0xc6, 0x59, 0xcc, 0xa4, 0x76, 0x9c, 0xe4, 0x31, 0x0b, 0x30, 0x8c, 0x40, 0xb0, 0x3c, 0x06, 0xa3, 0xc3, 0xba, 0xa4, 0x8f, 0xa4, 0x4f, 0x5c, 0x2e, 0xa4, 0x46, 0x7a, 0xe9, 0xc2, 0x75, 0x57, 0x66, 0x46, 0x32, 0x0e, 0xae, 0x97, 0x31, 0x6e, 0x16, 0xe3, 0xdd, 0xc2, 0x4a, 0x1a, 0x03, 0xa3, 0x3d, 0x01, 0x75, 0x40, 0x50, 0x05, 0x40, 0xaa, 0x5c, 0x39, 0xe3, 0xac, 0x28, 0x8b, 0xc0, 0x08, 0x60, 0x76, 0x31, 0x79, 0x17, 0x34, 0x64, 0x88, 0x42, 0xac, 0x0f, 0x90, 0x7c, 0x20, 0xf4, 0x6b, 0x4c, 0x26, 0x93, 0xd5, 0x64, 0x35, 0x87, 0xfd, 0x3c, 0x3d, 0x0c, 0x99, 0x29, 0x19, 0xc7, 0x60, 0x76, 0xa2, 0xa3, 0x96, 0x60, 0x69, 0x76, 0x52, 0xb4, 0x24, 0xa0, 0xff, 0xef, 0xee, 0xe3, 0x97, 0x38, 0x64, 0xa0, 0xff, 0xa4, 0x2c, 0xf9, 0x3f, 0x13, 0x9f, 0x61, 0x85, 0x2c, 0x0b, 0xff, 0x80, 0x0b, 0xe3, 0x7f, 0x08, 0x7d, 0xb4, 0x5d, 0xbe, 0xb8, 0xdb, 0x4a, 0xbd, 0xc9, 0x88, 0xb7, 0x7e, 0xfb, 0xec, 0x33, 0x2b, 0xf9, 0xf0, 0xee, 0x14, 0x5d, 0x03, 0x85, 0x10, 0xfa, 0x39, 0x88, 0xaf, 0x8e, 0xe9, 0xe2, 0x2c, 0x0a, 0x49, 0x47, 0x06, 0xd0, 0x18, 0xff, 0xa3, 0x54, 0x43, 0xe4, 0x17, 0xf7, 0x4a, 0x47, 0x96, 0x23, 0xaa, 0x02, 0x5c, 0xcd, 0x5d, 0x82, 0x8f, 0x0a, 0x63, 0xf4, 0xbd, 0xf9, 0xf1, 0x30, 0x81, 0x58, 0x48, 0x98, 0xdb, 0x62, 0xd2, 0xf9, 0x54, 0x9f, 0x11, 0x33, 0x15, 0xec, 0x80, 0x4f, 0x23, 0x80, 0x5a, 0x61, 0x42, 0x4f, 0x89, 0xd2, 0x89, 0x80, 0x06, 0xff, 0x45, 0xfa, 0xc7, 0xaf, 0xb5, 0x24, 0xa4, 0xe6, 0x44, 0x03, 0x2c, 0xef, 0x5d, 0x2a, 0xaf, 0xf7, 0x7a, 0x58, 0xaf, 0xaf, 0xf2, 0x0f, 0xc2, 0xac, 0xc8, 0x8e, 0xdb, 0xe5, 0x53, 0xa6, 0xf3, 0xc8, 0x9c, 0x59, 0x87, 0x4f, 0x5d, 0x2c, 0x51, 0xfc, 0xea, 0xc4, 0x56, 0x2c, 0xf2, 0x0f, 0x4a, 0xaf, 0xef, 0xbd, 0x9f, 0xae, 0x75, 0x2f, 0xf7, 0x1b, 0x61, 0x2d, 0xcc, 0x95, 0xac, 0xb8, 0x55, 0xa9, 0xe0, 0xa1, 0xd9, 0x02, 0xf5, 0x26, 0x94, 0x85, 0x19, 0xa3, 0x81, 0x12, 0x29, 0x60, 0xf6, 0x45, 0x03, 0x30, 0x1a, 0x3c, 0xf7, 0x1b, 0xa9, 0xf0, 0x2e, 0xe9, 0x97, 0x77, 0x71, 0x2d, 0xdc, 0xcb, 0x5b, 0x9e, 0x79, 0xa7, 0x55, 0xda, 0x8b, 0xe3, 0x12, 0x5d, 0xf3, 0x93, 0x5b, 0xb9, 0xdf, 0xe0, 0x1d, 0x89, 0x7a, 0x04, 0x1e, 0x93, 0x0b, 0xe0, 0xf6, 0xa9, 0x7a, 0x8c, 0x98, 0x2e, 0xf7, 0x00, 0x2c, 0x77, 0x3e, 0x1a, 0xd1, 0x73, 0xbf, 0xf9, 0xfc, 0x99, 0xbb, 0x50, 0xee, 0xd7, 0xc5, 0x9b, 0x25, 0xac, 0x90, 0x36, 0xb7, 0xfe, 0xb7, 0xf4, 0x91, 0x2c, 0xd7, 0x6c, 0xe5, 0x76, 0xe2, 0xef, 0x0b, 0x57, 0xe3, 0x10, 0x5f, 0x39, 0x79, 0x01, 0xf3, 0x0a, 0x13, 0xe2, 0xab, 0x99, 0xc3, 0xcc, 0x2b, 0xb0, 0xe7, 0x1c, 0x67, 0xee, 0x63, 0xec, 0xe4, 0x4c, 0xe2, 0x15, 0xd8, 0x72, 0x88, 0x6c, 0x47, 0x3f, 0x31, 0xfa, 0xf6, 0xe4, 0x7d, 0x29, 0xd9, 0x61, 0x27, 0xc8, 0x0e, 0x72, 0xd9, 0x21, 0x5a, 0xb6, 0x86, 0x79, 0xee, 0x1c, 0xca, 0x7e, 0x5f, 0x38, 0x02, 0x65, 0xab, 0xfe, 0xc5, 0xf7, 0xca, 0x65, 0x57, 0x9e, 0xe3, 0x7b, 0x77, 0x71, 0x25, 0xf0, 0xde, 0x27, 0x40, 0x76, 0x7d, 0x92, 0xb9, 0x8b, 0x91, 0x7b, 0xfd, 0x19, 0x12, 0xe9, 0xdb, 0x3f, 0xa3, 0x65, 0xbf, 0x3d, 0x79, 0x57, 0xa2, 0xf0, 0x67, 0x72, 0x61, 0xfa, 0x99, 0x2c, 0x7b, 0xcf, 0x8c, 0xb2, 0xab, 0x68, 0xd9, 0x1a, 0x90, 0x99, 0xe6, 0x52, 0xf6, 0x41, 0xe1, 0xa9, 0x54, 0xd9, 0xaa, 0xc9, 0x35, 0x89, 0xf7, 0xee, 0x3a, 0x6b, 0xd9, 0xfc, 0xc9, 0x3f, 0x73, 0xbf, 0xe3, 0xef, 0x67, 0x04, 0xdc, 0x7c, 0x92, 0x8c, 0x70, 0xb1, 0x74, 0x23, 0xfb, 0x09, 0xff, 0x34, 0x53, 0xc0, 0x74, 0xc6, 0xdb, 0x32, 0x74, 0x98, 0xc8, 0xb0, 0xa2, 0xe0, 0x71, 0x63, 0x8e, 0x67, 0x53, 0xda, 0x02, 0x0d, 0xfd, 0xc1, 0x0c, 0x10, 0x73, 0xc2, 0x91, 0x6e, 0x05, 0x12, 0xc5, 0xe1, 0xee, 0xa4, 0xa3, 0xdd, 0x18, 0x0d, 0x22, 0x5b, 0xc0, 0x14, 0x18, 0x2b, 0x02, 0xc6, 0x80, 0xcf, 0xa2, 0x54, 0x39, 0xf2, 0x23, 0xfa, 0x50, 0x11, 0xaa, 0x43, 0x11, 0x19, 0xea, 0xc2, 0x4a, 0x7d, 0xac, 0x05, 0x27, 0x6c, 0x58, 0xa9, 0x38, 0x13, 0xb8, 0x8c, 0x86, 0x2a, 0xf1, 0xe8, 0x7f, 0xb2, 0xc9, 0xea, 0xbc, 0xfd, 0x96, 0xab, 0xed, 0x21, 0xa3, 0x2b, 0xab, 0xa5, 0x01, 0x6f, 0xf2, 0x74, 0x6c, 0x9f, 0xef, 0x89, 0x65, 0x8b, 0x58, 0x91, 0x61, 0x37, 0xf6, 0xc6, 0xa5, 0xd5, 0xec, 0x32, 0x2d, 0x6f, 0xb5, 0xf4, 0xed, 0xff, 0xfa, 0x03, 0xbc, 0xb0, 0x59, 0xaf, 0x5a, 0xbe, 0x7e, 0xff, 0xe7, 0x6f, 0x24, 0x42, 0x98, 0xa8, 0x94, 0x5c, 0xfb, 0x02, 0xf6, 0x96, 0x93, 0x77, 0x27, 0xfa, 0x21, 0x6c, 0x83, 0x7e, 0x34, 0x33, 0xdf, 0x93, 0x63, 0xec, 0x98, 0xfc, 0x99, 0x19, 0x2c, 0x11, 0xba, 0x54, 0xca, 0x28, 0x12, 0x15, 0x6c, 0x97, 0x0f, 0xb8, 0xa9, 0x23, 0x6e, 0x82, 0x0f, 0x6e, 0x66, 0x12, 0xb5, 0x8b, 0x21, 0x45, 0xa2, 0xd0, 0x29, 0x19, 0xb0, 0x6a, 0xa8, 0x3b, 0x85, 0x63, 0x35, 0xd4, 0x4d, 0x5c, 0x46, 0x99, 0x01, 0x01, 0x64, 0x01, 0xe8, 0xbd, 0x56, 0x83, 0x55, 0xaa, 0x61, 0x62, 0x93, 0x3a, 0x15, 0x4c, 0x22, 0x92, 0x24, 0xc6, 0xba, 0x14, 0x3a, 0xed, 0x59, 0x4b, 0x0d, 0x50, 0x61, 0xaa, 0x99, 0x69, 0x6e, 0x6a, 0x8c, 0x37, 0xd4, 0xd7, 0x05, 0x08, 0xf1, 0xcc, 0xd1, 0x80, 0x85, 0xd8, 0xbf, 0x4c, 0xd1, 0x8f, 0xf0, 0x16, 0xab, 0xa8, 0x63, 0x89, 0x14, 0x2a, 0x83, 0x1e, 0x9f, 0x86, 0xa0, 0x28, 0x41, 0x4f, 0x5f, 0x92, 0xa6, 0x65, 0x2b, 0x0f, 0xf6, 0x37, 0xd7, 0xf2, 0x48, 0x67, 0xf5, 0x18, 0x0b, 0xbb, 0xca, 0xb3, 0xd1, 0xb3, 0xb7, 0x5c, 0x6d, 0x0b, 0x1b, 0x9c, 0x59, 0xad, 0x84, 0xca, 0xd3, 0x03, 0xc3, 0x48, 0xab, 0xbe, 0x2f, 0x75, 0x26, 0xc8, 0xdc, 0xb3, 0xf4, 0x8a, 0x55, 0xa5, 0xa6, 0x21, 0x8d, 0x56, 0x40, 0x96, 0xf2, 0xc5, 0x0d, 0x6d, 0xe9, 0xc8, 0x4e, 0x22, 0xc7, 0x10, 0xaa, 0xb3, 0xcb, 0x09, 0xe5, 0x09, 0xff, 0xbc, 0x10, 0xf4, 0x90, 0xbb, 0xf9, 0xcb, 0xe8, 0x39, 0x52, 0xb9, 0x8c, 0xc6, 0xa4, 0x9d, 0x3a, 0x3e, 0x1a, 0xa2, 0x10, 0xeb, 0x2c, 0x55, 0x91, 0x96, 0xb2, 0x14, 0x5b, 0x0a, 0xcb, 0x8a, 0xfd, 0x69, 0x11, 0xd6, 0x4f, 0x39, 0xff, 0xda, 0x70, 0xca, 0xd9, 0x57, 0xfd, 0x29, 0x47, 0x76, 0xaa, 0xb3, 0x22, 0x47, 0x91, 0x9b, 0x09, 0x15, 0xe8, 0x28, 0x56, 0xfe, 0xbb, 0xd0, 0xd6, 0x69, 0xb1, 0xe5, 0x61, 0xb3, 0x4b, 0xbf, 0xa3, 0x60, 0xeb, 0xec, 0xdd, 0x84, 0xaf, 0x9c, 0xb5, 0x7f, 0x81, 0xfe, 0xf4, 0x6d, 0xe9, 0x46, 0x5c, 0xc2, 0xdf, 0xc6, 0x04, 0x99, 0xf5, 0x93, 0x44, 0xc2, 0x78, 0x68, 0x72, 0x98, 0xf0, 0xc3, 0xc9, 0xff, 0x96, 0x6e, 0x65, 0xcb, 0xc8, 0x73, 0xfc, 0x1e, 0x45, 0xba, 0x7c, 0xc8, 0x20, 0xf3, 0xfc, 0x47, 0x26, 0xff, 0xc2, 0xc7, 0xe1, 0x79, 0x11, 0x73, 0x54, 0xd6, 0x67, 0x55, 0xf9, 0x88, 0x17, 0xb5, 0xd4, 0x36, 0xc2, 0x91, 0xfc, 0x02, 0x33, 0x87, 0xcc, 0x4b, 0x92, 0x5c, 0x40, 0x40, 0x8e, 0x79, 0x86, 0x18, 0x58, 0x11, 0x57, 0xdb, 0xad, 0x0c, 0x81, 0x0f, 0x67, 0xb7, 0x12, 0xa7, 0x80, 0x01, 0x98, 0x4d, 0x43, 0x14, 0x16, 0x71, 0x90, 0xc0, 0x76, 0x11, 0xe4, 0xe4, 0x78, 0xc9, 0x59, 0xb3, 0x0b, 0xc2, 0xb0, 0x5c, 0x46, 0x06, 0x5b, 0x06, 0xb9, 0x44, 0x4f, 0x02, 0x45, 0x99, 0xc3, 0x99, 0xc4, 0x45, 0x36, 0x11, 0x33, 0x3d, 0x35, 0xb1, 0xa6, 0x6e, 0xab, 0x48, 0x8c, 0x82, 0x98, 0x2c, 0x78, 0x12, 0xb3, 0x46, 0x76, 0xe3, 0xde, 0x47, 0xb7, 0x47, 0x97, 0xdf, 0xf2, 0xd2, 0xf6, 0x8e, 0x03, 0xf5, 0x3e, 0xde, 0x6e, 0xd4, 0xe4, 0x14, 0xc4, 0xfc, 0x8b, 0x2e, 0x5d, 0x56, 0x18, 0x5c, 0x7c, 0xe5, 0xd0, 0x17, 0x73, 0xd0, 0xe0, 0xc4, 0xed, 0xaf, 0x71, 0x6d, 0x5a, 0x13, 0xbb, 0x9c, 0x6f, 0x58, 0x7f, 0xb8, 0x6f, 0xc7, 0xa3, 0x17, 0xc5, 0xb3, 0xb3, 0xf7, 0x98, 0x74, 0x4a, 0x8d, 0xb2, 0x60, 0xde, 0xa6, 0x86, 0xd8, 0xf2, 0xb8, 0x7f, 0x10, 0xb4, 0x99, 0xeb, 0x3e, 0xd7, 0xf3, 0xb7, 0xcd, 0x37, 0x84, 0x2c, 0x30, 0x97, 0x56, 0x33, 0x0c, 0xf7, 0x0d, 0xa0, 0x8d, 0x48, 0x6e, 0x1b, 0x08, 0x50, 0x9e, 0x8c, 0x07, 0xbc, 0x86, 0x1e, 0x37, 0x92, 0x18, 0x2e, 0x43, 0x09, 0xd0, 0x60, 0x85, 0x42, 0xa1, 0x56, 0xa8, 0x49, 0x50, 0xed, 0x4c, 0x12, 0xc4, 0x86, 0xe8, 0x1d, 0xa4, 0x5d, 0x44, 0xdb, 0x5e, 0xcd, 0xb5, 0x4b, 0x1d, 0xaf, 0x49, 0x6d, 0x5c, 0xfb, 0xe7, 0x4f, 0x72, 0xf3, 0x1e, 0x39, 0x39, 0xcc, 0xde, 0x86, 0x99, 0x47, 0x1e, 0x61, 0xe4, 0x1d, 0x8d, 0xe1, 0xdc, 0x50, 0xbf, 0x8f, 0x78, 0x40, 0xaa, 0x10, 0xc7, 0xa3, 0x2e, 0x10, 0xb3, 0x68, 0x5c, 0x6c, 0xbc, 0x26, 0x79, 0x7b, 0x41, 0x62, 0x1e, 0x29, 0x07, 0x18, 0xa5, 0x72, 0x28, 0x11, 0xee, 0xc8, 0xe7, 0xf5, 0xea, 0x0d, 0x1e, 0x7f, 0x50, 0x6f, 0xcc, 0xa4, 0x01, 0x5f, 0x3c, 0x2e, 0x64, 0x8d, 0x98, 0x7d, 0x3a, 0x44, 0xc2, 0x18, 0xf8, 0xbc, 0x21, 0x44, 0x82, 0x1b, 0x10, 0x73, 0xc2, 0x18, 0x6c, 0xd3, 0xf4, 0x77, 0x0f, 0xe7, 0x96, 0x6e, 0x6c, 0x68, 0x51, 0xff, 0xf6, 0x77, 0xe6, 0x5b, 0xf7, 0x95, 0x78, 0xd0, 0xd1, 0xef, 0x54, 0x66, 0x4b, 0xb7, 0xeb, 0xc3, 0xb9, 0x0f, 0x3c, 0x77, 0xe8, 0xab, 0x59, 0x13, 0x12, 0x16, 0xcd, 0x5f, 0x39, 0xfc, 0x02, 0x7b, 0x3f, 0xcf, 0xfa, 0xd7, 0x35, 0xdc, 0xbc, 0xe1, 0xb6, 0x80, 0xbd, 0x74, 0xe2, 0x67, 0x85, 0xe8, 0xaf, 0x76, 0xf3, 0x0d, 0x9b, 0x97, 0xde, 0xbc, 0x72, 0x5d, 0x02, 0x9f, 0x98, 0x5b, 0x08, 0x6d, 0xcd, 0x27, 0xde, 0x2f, 0xb9, 0x01, 0xbf, 0x28, 0xf0, 0x24, 0x22, 0x3a, 0xeb, 0xcb, 0x71, 0xa9, 0x39, 0xf9, 0x56, 0x8a, 0xe0, 0x25, 0x33, 0x63, 0x04, 0x79, 0x89, 0x1c, 0xe8, 0xe0, 0x41, 0x99, 0x36, 0xce, 0x40, 0xb0, 0x80, 0xae, 0xab, 0x80, 0xd9, 0x24, 0xf2, 0x66, 0xfa, 0xbf, 0x5e, 0x1e, 0xc4, 0x8a, 0x04, 0x2e, 0x95, 0x47, 0x2f, 0x46, 0xcb, 0x1b, 0x50, 0x94, 0xfe, 0xaf, 0x8f, 0xa0, 0x3f, 0xa0, 0xfa, 0x6c, 0xe9, 0x2d, 0xfa, 0xdf, 0xba, 0xde, 0xbd, 0x03, 0xa5, 0x6e, 0xd3, 0x81, 0xd6, 0xcb, 0xbf, 0xbf, 0x57, 0x7a, 0xe1, 0x49, 0xc7, 0xed, 0xf0, 0xef, 0x35, 0xf6, 0xbe, 0xbb, 0x8e, 0x78, 0xb5, 0x37, 0x5f, 0x7e, 0xd7, 0xf5, 0x3e, 0xf5, 0x2d, 0xd2, 0xdf, 0xf3, 0x16, 0xec, 0xe8, 0x34, 0x65, 0x95, 0x5f, 0xf0, 0xd8, 0x45, 0x71, 0x74, 0xde, 0x79, 0x37, 0x38, 0xb5, 0x0b, 0xaf, 0x38, 0xef, 0x06, 0x97, 0x7a, 0x21, 0x5e, 0xc8, 0xa0, 0xc9, 0xa7, 0xa4, 0x5b, 0xd1, 0x66, 0xd0, 0x04, 0x2c, 0xcc, 0x4a, 0x39, 0x1e, 0x92, 0x86, 0x1e, 0x6b, 0x3a, 0xc8, 0x27, 0x47, 0xe3, 0x21, 0x79, 0x29, 0x14, 0x60, 0xe2, 0xd8, 0x66, 0xb8, 0x3b, 0x15, 0xba, 0x87, 0x86, 0x82, 0x71, 0xca, 0x09, 0x2c, 0xc1, 0xf4, 0xe6, 0xc6, 0x68, 0x86, 0x24, 0xca, 0x39, 0x89, 0xf7, 0x72, 0xdc, 0x13, 0x08, 0xe8, 0x33, 0x09, 0xce, 0xf5, 0xff, 0xc7, 0xde, 0x9b, 0xc0, 0x47, 0x55, 0x9d, 0xfd, 0xe3, 0xe7, 0xdc, 0x75, 0x96, 0x64, 0x26, 0x93, 0x49, 0x32, 0x49, 0x26, 0xdb, 0x64, 0x92, 0xc9, 0x9e, 0x90, 0x3d, 0x21, 0x09, 0x19, 0x02, 0x04, 0x48, 0xd8, 0x11, 0x30, 0x82, 0x22, 0xab, 0x68, 0x58, 0x02, 0x58, 0x15, 0xf1, 0x15, 0x11, 0x54, 0x40, 0xc4, 0x05, 0xb5, 0x0a, 0xa8, 0xb8, 0xe0, 0x06, 0x28, 0x88, 0xa8, 0x88, 0x5a, 0xb6, 0x57, 0x5b, 0xb1, 0xb5, 0xb6, 0x62, 0xad, 0xb5, 0xad, 0x5a, 0x6b, 0xdf, 0xd6, 0xda, 0xaa, 0xd5, 0xba, 0x90, 0xb9, 0xf3, 0x3f, 0xcf, 0x39, 0xf7, 0xde, 0xb9, 0x77, 0x32, 0x09, 0x8b, 0xf4, 0x7d, 0x3f, 0xbf, 0xcf, 0xe7, 0x6f, 0x3f, 0x55, 0xf8, 0xce, 0xd9, 0xee, 0x39, 0xcf, 0x79, 0xce, 0xf7, 0x39, 0xcb, 0xf3, 0x54, 0xfb, 0xeb, 0xab, 0xfb, 0xc4, 0x2e, 0xaa, 0x3d, 0xdc, 0x39, 0x61, 0x47, 0x74, 0xe0, 0xa2, 0x3b, 0x5f, 0x84, 0xb0, 0x45, 0x89, 0x6e, 0x1a, 0xb6, 0x68, 0x2c, 0xc8, 0xc1, 0x15, 0xca, 0x46, 0x6e, 0x3e, 0x6d, 0xe3, 0xf8, 0xfd, 0x0e, 0x70, 0x19, 0xd0, 0xd1, 0xa7, 0x55, 0x40, 0xc3, 0xb4, 0x35, 0xce, 0xd0, 0x2a, 0xb6, 0x53, 0x49, 0x5b, 0x15, 0xf9, 0xbd, 0xeb, 0xf9, 0x7c, 0x9f, 0xdf, 0xd8, 0xaa, 0xe8, 0xc0, 0x26, 0xfb, 0x5b, 0x27, 0xdc, 0x3b, 0x28, 0x98, 0x53, 0xee, 0xb0, 0xa7, 0xcb, 0x5a, 0x9c, 0x92, 0x3b, 0xdf, 0x65, 0xb1, 0x47, 0x92, 0x5c, 0x91, 0xd8, 0x23, 0x57, 0x28, 0x5b, 0xb9, 0x95, 0xa7, 0xd3, 0x77, 0xcc, 0x16, 0xe8, 0xdb, 0x77, 0x5a, 0xb7, 0xe9, 0x09, 0x62, 0xf7, 0x5d, 0x11, 0xae, 0xf7, 0xf5, 0xed, 0x3b, 0xfc, 0x96, 0xb2, 0x63, 0x02, 0xde, 0x7f, 0xaa, 0xee, 0x23, 0x63, 0xfc, 0x3a, 0x19, 0xe3, 0xff, 0x8d, 0x76, 0x56, 0xfb, 0x6b, 0x63, 0xb4, 0xf3, 0xad, 0x79, 0xd8, 0x79, 0x8a, 0x46, 0x12, 0xae, 0x43, 0x38, 0x81, 0xf0, 0x96, 0x78, 0x90, 0x0b, 0x88, 0x3f, 0xa1, 0x96, 0xdf, 0x53, 0x9c, 0x48, 0x79, 0x6a, 0x05, 0x51, 0x90, 0xdd, 0xe2, 0xab, 0x84, 0x3f, 0x75, 0x51, 0xfc, 0x19, 0x7e, 0x2e, 0xc3, 0x95, 0x95, 0x42, 0x9a, 0xf8, 0x82, 0x8e, 0xff, 0x37, 0xe7, 0xa3, 0x78, 0xb9, 0xb2, 0x52, 0x9c, 0x2c, 0xee, 0x23, 0xf8, 0x74, 0x15, 0xdf, 0xc5, 0xf0, 0xf0, 0xe7, 0xe2, 0xab, 0xb4, 0x9c, 0xe9, 0x6a, 0x39, 0x3f, 0xa1, 0x7a, 0xff, 0x24, 0x99, 0xb7, 0xb3, 0xc5, 0xfb, 0x51, 0x01, 0xde, 0x83, 0x7f, 0x4f, 0xa3, 0x78, 0x3e, 0x16, 0xde, 0x4a, 0x63, 0x7d, 0x62, 0x53, 0xac, 0xcf, 0x69, 0xe1, 0x6f, 0xc5, 0x0b, 0x58, 0x3a, 0xae, 0x81, 0x40, 0x05, 0x24, 0xdd, 0x37, 0xdc, 0x21, 0x92, 0xfe, 0x09, 0x3e, 0x89, 0xee, 0xd2, 0xf4, 0x4d, 0xdf, 0x10, 0x33, 0xfd, 0x4e, 0xee, 0x64, 0x3f, 0xe9, 0xbb, 0x62, 0xa6, 0xdf, 0xc5, 0x8b, 0xfd, 0xa4, 0x9f, 0x10, 0x33, 0xfd, 0x53, 0x40, 0xfd, 0xfa, 0xa4, 0x9f, 0x42, 0xd2, 0xb7, 0x8b, 0x8f, 0xd0, 0xf4, 0x57, 0xd0, 0xf4, 0x4f, 0xa0, 0x24, 0xd6, 0x1e, 0xde, 0xda, 0x4f, 0xfa, 0x0b, 0x63, 0xa6, 0x7f, 0x8a, 0x77, 0xc4, 0x48, 0x3f, 0x9c, 0xa4, 0x5f, 0x4a, 0xf4, 0x1f, 0xa4, 0x5f, 0x4f, 0xd3, 0x3f, 0x89, 0x0a, 0xd4, 0xf4, 0xb9, 0x2c, 0xbd, 0x29, 0x76, 0xec, 0x54, 0x92, 0xfe, 0x7a, 0x35, 0xfd, 0x2c, 0x9a, 0x7e, 0x27, 0x92, 0xd5, 0xef, 0x2d, 0x8f, 0x51, 0x3e, 0xa4, 0x5f, 0x11, 0x33, 0xfd, 0x53, 0x7c, 0x71, 0x8c, 0xf4, 0x33, 0x48, 0x7a, 0x81, 0xa5, 0xc7, 0x9f, 0xd1, 0xf4, 0xbb, 0x91, 0x8d, 0xa5, 0xe7, 0xf6, 0x47, 0xa7, 0x27, 0x7a, 0xa2, 0x4b, 0x79, 0x4c, 0xfc, 0x94, 0x70, 0x6d, 0x37, 0x0a, 0xa2, 0x6b, 0xb5, 0x30, 0x22, 0xea, 0xdb, 0xfa, 0x6e, 0x7d, 0x03, 0x9e, 0x46, 0x17, 0xa3, 0x92, 0xbf, 0x80, 0xfa, 0xfa, 0x8b, 0x8b, 0x9d, 0x6c, 0xa1, 0x29, 0xd9, 0x29, 0x0b, 0x82, 0x60, 0x10, 0x83, 0x1b, 0x0a, 0x93, 0x03, 0x70, 0x2c, 0x07, 0x21, 0x01, 0xa2, 0xe2, 0x76, 0x6a, 0xbe, 0xe8, 0xea, 0xd9, 0x55, 0xa6, 0x3a, 0x70, 0x10, 0x91, 0x92, 0x48, 0x6f, 0x3d, 0x41, 0x04, 0xa2, 0x14, 0x16, 0xa2, 0xc9, 0x83, 0xab, 0xda, 0xaf, 0x7f, 0x7e, 0xe9, 0xd2, 0xe7, 0x57, 0xb7, 0xb7, 0xaf, 0x86, 0xff, 0x5e, 0xdf, 0xce, 0x1d, 0xcf, 0xb9, 0xb0, 0x76, 0xfa, 0xbc, 0x61, 0x75, 0xf7, 0x9e, 0x7c, 0x76, 0xce, 0x82, 0x83, 0x58, 0x7e, 0x60, 0x3b, 0x96, 0x5e, 0xba, 0xe4, 0xfc, 0xed, 0x1f, 0xdd, 0xec, 0xce, 0xcf, 0x4a, 0xac, 0x28, 0x48, 0xc9, 0x48, 0x8a, 0x13, 0xe3, 0x65, 0xee, 0x17, 0xab, 0x8f, 0x6f, 0xea, 0xec, 0xdc, 0x74, 0x7c, 0xf5, 0x75, 0x6f, 0x6c, 0x1a, 0x33, 0x66, 0xd3, 0x1b, 0x27, 0x37, 0x7a, 0x32, 0xc6, 0xb7, 0xd5, 0xce, 0x0c, 0x88, 0x3d, 0x87, 0xb1, 0xf5, 0xbe, 0xed, 0xd8, 0x71, 0x74, 0xd1, 0xa2, 0xa3, 0xca, 0x97, 0xdb, 0xef, 0xec, 0x7d, 0x65, 0x45, 0x82, 0x3d, 0x2b, 0xbf, 0xd8, 0xe3, 0x2f, 0x96, 0x65, 0x39, 0x9e, 0xce, 0xa1, 0x5d, 0xe1, 0xaf, 0xb8, 0x6c, 0xc2, 0x29, 0x03, 0xe8, 0x77, 0x74, 0x6e, 0xed, 0x0f, 0x9f, 0x47, 0xf5, 0x63, 0x0e, 0x99, 0x5b, 0x98, 0xfa, 0x84, 0x6c, 0x0e, 0x36, 0x4a, 0x58, 0x80, 0xf7, 0x01, 0x02, 0x3f, 0xcb, 0x82, 0x39, 0x2b, 0x21, 0x77, 0xe0, 0x70, 0x04, 0x28, 0x82, 0x1d, 0xdb, 0x6c, 0x86, 0x3d, 0xa7, 0x64, 0x04, 0xb1, 0x89, 0xb4, 0x7f, 0xe2, 0xc8, 0x0a, 0x0e, 0x9b, 0x57, 0xfe, 0xda, 0xea, 0x5a, 0x35, 0xb2, 0x5f, 0xb2, 0x3f, 0x99, 0xfc, 0xdf, 0x25, 0xe0, 0xde, 0xd7, 0x77, 0x92, 0x7f, 0x4e, 0xbc, 0x49, 0xfe, 0xd9, 0xb7, 0x0f, 0x1f, 0xdb, 0xb2, 0x7c, 0x0b, 0x57, 0x19, 0x7a, 0x93, 0xfc, 0x87, 0xda, 0xb8, 0x08, 0xde, 0x55, 0x21, 0x41, 0x22, 0xf5, 0x67, 0xa2, 0xa1, 0xc1, 0x21, 0x4e, 0xb2, 0x70, 0x64, 0xf0, 0x70, 0x77, 0xb0, 0x83, 0xd0, 0x07, 0x35, 0xb6, 0x96, 0x40, 0x88, 0x98, 0x2c, 0xd0, 0x78, 0x9e, 0x73, 0xa8, 0xb1, 0x64, 0x68, 0x48, 0x26, 0xca, 0xcc, 0x4b, 0xcc, 0x75, 0x25, 0x25, 0xb9, 0xc0, 0x4e, 0x82, 0x46, 0x10, 0x0a, 0x01, 0x5b, 0xbd, 0xf5, 0xa4, 0x15, 0x34, 0xda, 0x2e, 0xdb, 0x03, 0x2e, 0x70, 0x09, 0x52, 0xef, 0xeb, 0x99, 0xd9, 0x78, 0x63, 0x56, 0x6d, 0x47, 0xe9, 0x89, 0x9c, 0xb4, 0x82, 0x12, 0xbc, 0x21, 0xbb, 0x61, 0xdc, 0xa0, 0x1c, 0xdf, 0xbe, 0x7d, 0xdc, 0x3b, 0x62, 0x71, 0xa0, 0x72, 0x62, 0xb3, 0x9f, 0xe7, 0xea, 0x43, 0xd7, 0x26, 0x14, 0x95, 0x55, 0x9f, 0xd7, 0xec, 0xb3, 0x2b, 0xa5, 0x54, 0xe6, 0xc6, 0x2b, 0x8f, 0x09, 0x3e, 0xc1, 0x42, 0xd8, 0x53, 0x03, 0xda, 0xa0, 0x9e, 0xc1, 0x69, 0xa2, 0xb2, 0xd8, 0x20, 0x4c, 0x40, 0xa5, 0x0c, 0x42, 0x97, 0x1a, 0x3b, 0xdd, 0x12, 0x73, 0xba, 0x60, 0x01, 0x92, 0x78, 0x91, 0x87, 0x70, 0x79, 0x31, 0x92, 0x9a, 0xa5, 0x2f, 0xae, 0xbe, 0xb6, 0x6a, 0x50, 0x61, 0x72, 0x5e, 0x5e, 0xae, 0xac, 0x85, 0xa4, 0x50, 0xf7, 0x46, 0x0d, 0xb2, 0xc7, 0x9b, 0x82, 0x52, 0x98, 0x04, 0x2f, 0xf5, 0xd6, 0x3b, 0x6e, 0xdf, 0xb4, 0xe9, 0xf6, 0x3b, 0x6e, 0xc5, 0x5f, 0x65, 0x5f, 0x54, 0x43, 0x04, 0xae, 0xe1, 0xd2, 0xc7, 0xaf, 0x6c, 0x1d, 0xb1, 0x6a, 0xff, 0xd2, 0x9e, 0xfd, 0xab, 0x86, 0xd7, 0xcd, 0xbb, 0x7d, 0x7a, 0x52, 0x20, 0xc7, 0x4d, 0xc4, 0x2d, 0xd3, 0x1d, 0x27, 0x3a, 0x45, 0xdc, 0xbb, 0xe7, 0xc8, 0x91, 0x3d, 0x7b, 0x8e, 0x1e, 0xed, 0x2d, 0x07, 0x31, 0xab, 0x9b, 0x19, 0x90, 0xc6, 0xdd, 0x74, 0xf0, 0xf2, 0xab, 0x0e, 0xaf, 0x1f, 0x3d, 0x7a, 0xfd, 0xe1, 0xab, 0xba, 0xf7, 0xae, 0x3d, 0xcf, 0x69, 0xcf, 0xce, 0x2f, 0x49, 0xd5, 0x64, 0x0c, 0x7c, 0x92, 0x2a, 0x5f, 0x73, 0xcf, 0x84, 0x2f, 0x1f, 0xe8, 0x7e, 0x9a, 0x47, 0xf3, 0x49, 0xba, 0x51, 0xdd, 0xc3, 0xfe, 0x44, 0x79, 0x90, 0xbb, 0x21, 0x7c, 0x98, 0xed, 0x95, 0xe8, 0x7e, 0x4b, 0x16, 0x80, 0xdf, 0x12, 0x76, 0x54, 0x01, 0x7e, 0x4b, 0x44, 0xfd, 0x0a, 0x07, 0x7e, 0x40, 0xbd, 0xbe, 0xe1, 0xfc, 0xac, 0x55, 0xbd, 0xb8, 0xc1, 0xca, 0x39, 0xa6, 0x3c, 0xc4, 0x2d, 0x08, 0xc3, 0xfd, 0x10, 0xe6, 0x13, 0x9f, 0x91, 0x8e, 0x05, 0x54, 0x4a, 0xe0, 0xca, 0x09, 0x9c, 0x1c, 0x89, 0xfa, 0x95, 0x13, 0x6e, 0x81, 0x7e, 0xdd, 0x64, 0x88, 0x7a, 0xcf, 0x04, 0xec, 0x08, 0xa5, 0x03, 0xb7, 0xd2, 0x93, 0x26, 0xd5, 0x96, 0xe9, 0x62, 0x5e, 0x7e, 0x58, 0xd3, 0x9b, 0xb0, 0xcb, 0xff, 0xcc, 0x2d, 0xb7, 0x28, 0x1d, 0x54, 0x77, 0xed, 0x55, 0xe2, 0xc4, 0xbb, 0xc5, 0x6d, 0x64, 0xad, 0x82, 0xb0, 0x12, 0x32, 0x27, 0xa0, 0xdf, 0x7c, 0x77, 0x90, 0x68, 0x2c, 0x0e, 0xbf, 0xf0, 0xdd, 0x41, 0x75, 0x6f, 0x60, 0x92, 0x12, 0xc7, 0xbf, 0x2f, 0x1e, 0x23, 0x69, 0xea, 0xd5, 0x34, 0x37, 0xf5, 0x49, 0x33, 0x57, 0x79, 0x9d, 0x3b, 0x10, 0x7e, 0x8a, 0x97, 0xd1, 0xbe, 0x70, 0xef, 0xf7, 0xd9, 0xec, 0x24, 0xe1, 0xfb, 0x6c, 0x4d, 0xe7, 0x85, 0xef, 0x0e, 0x7f, 0x29, 0x24, 0x8a, 0xc7, 0x50, 0x1c, 0xb1, 0x6d, 0xbf, 0x50, 0x03, 0x56, 0xd6, 0x61, 0xab, 0x9c, 0x87, 0x25, 0x31, 0x3e, 0x0e, 0x6e, 0xc5, 0x43, 0x28, 0x31, 0xa1, 0x63, 0x18, 0xe1, 0xdc, 0xa3, 0xbd, 0x7d, 0x7f, 0x13, 0xb4, 0xdf, 0xd4, 0x48, 0x90, 0x65, 0x70, 0x55, 0x57, 0x94, 0x64, 0x71, 0x0e, 0xb2, 0xda, 0xb1, 0xc4, 0x59, 0x25, 0xf0, 0x07, 0x37, 0xaf, 0x53, 0xf5, 0x91, 0xcd, 0xa9, 0x02, 0x08, 0xec, 0x82, 0x45, 0xa2, 0xca, 0x3d, 0xb3, 0x4c, 0x34, 0x3a, 0x55, 0xe5, 0xa9, 0xd3, 0x0b, 0x42, 0xe4, 0xc8, 0x94, 0x45, 0xad, 0x4a, 0x6c, 0x0b, 0x36, 0x37, 0x35, 0xd6, 0x27, 0xd6, 0x24, 0x26, 0x05, 0x0a, 0xf3, 0xf3, 0x72, 0xa9, 0x5a, 0xc9, 0xc2, 0xba, 0x98, 0xcb, 0xb5, 0x51, 0xb1, 0x77, 0x64, 0xa9, 0x04, 0xbb, 0x68, 0xa8, 0x48, 0xf5, 0x74, 0x27, 0xc5, 0x53, 0x5b, 0x9d, 0x6c, 0x9c, 0xf5, 0xb8, 0x73, 0x50, 0x4f, 0xdb, 0xba, 0xcd, 0xd7, 0x5f, 0x79, 0xde, 0xca, 0xda, 0xd6, 0xed, 0xf3, 0xbb, 0x6e, 0x9d, 0x5b, 0xd7, 0xb2, 0xf4, 0xa1, 0x79, 0x95, 0x93, 0x46, 0x0c, 0x4e, 0xb7, 0x5a, 0x8a, 0xaf, 0x15, 0x6d, 0x62, 0xa0, 0x04, 0x6f, 0xcc, 0x69, 0x1c, 0x57, 0x61, 0x89, 0x97, 0x8f, 0x26, 0x26, 0x68, 0x6a, 0xc1, 0xed, 0xf9, 0xbe, 0xb5, 0xea, 0xc8, 0xb1, 0xfd, 0x4f, 0x8c, 0x69, 0xbf, 0xbd, 0x7d, 0xea, 0x88, 0xd5, 0x2f, 0x5c, 0xbe, 0xf2, 0xf5, 0xdb, 0xc6, 0xbb, 0xf3, 0x6a, 0x7c, 0x53, 0x8a, 0x84, 0xa7, 0x30, 0x2e, 0x29, 0x05, 0x1d, 0x81, 0xf1, 0xfa, 0xf7, 0x25, 0xa6, 0x2e, 0x44, 0x1a, 0xb3, 0x93, 0x8c, 0xd5, 0x9b, 0x64, 0xac, 0x1c, 0xa8, 0x0d, 0x6d, 0x0d, 0x26, 0xb7, 0x0c, 0xe6, 0x6c, 0x96, 0x32, 0xcc, 0x49, 0xa5, 0x18, 0x89, 0x19, 0x34, 0x30, 0xa7, 0xa0, 0xb2, 0xe2, 0xf2, 0x38, 0x3b, 0x67, 0x41, 0x12, 0x67, 0x21, 0x1d, 0x63, 0x93, 0x89, 0xb1, 0x64, 0xe3, 0xe0, 0x7d, 0xc8, 0x3c, 0xe8, 0x12, 0xea, 0x5d, 0x85, 0x06, 0xcb, 0x64, 0x3d, 0x4a, 0x03, 0x91, 0x56, 0x9d, 0x46, 0x06, 0x88, 0x6f, 0xaa, 0xe9, 0x11, 0x08, 0x7d, 0x15, 0x4c, 0x71, 0x3a, 0x9d, 0x6d, 0xce, 0xb6, 0x60, 0x6b, 0x73, 0x53, 0x5e, 0x62, 0x5e, 0x61, 0x5e, 0x6e, 0x7d, 0x20, 0x37, 0xbe, 0x4f, 0x7c, 0x4d, 0xac, 0xb2, 0x41, 0x97, 0x3f, 0xb7, 0x02, 0x9b, 0xc3, 0x68, 0xba, 0x02, 0x91, 0xce, 0xd5, 0xfb, 0xd6, 0x1c, 0x5b, 0x13, 0x3f, 0x0f, 0xfd, 0x65, 0x71, 0x58, 0xae, 0x28, 0xb2, 0xb9, 0xcc, 0x71, 0x34, 0x13, 0xe3, 0xb5, 0xce, 0x4d, 0x71, 0x1d, 0xb3, 0xc5, 0x91, 0xbe, 0x35, 0x46, 0xd6, 0x14, 0x52, 0x59, 0x17, 0x9e, 0x7c, 0xb2, 0xe0, 0x47, 0xc6, 0x38, 0x9a, 0xd8, 0x2d, 0xb2, 0xde, 0x15, 0x6e, 0xfe, 0x23, 0x5f, 0x54, 0x46, 0xe7, 0x1a, 0x91, 0x93, 0xbf, 0xd0, 0xfd, 0x80, 0x64, 0x1a, 0xaf, 0xf9, 0x30, 0xbe, 0x33, 0xf4, 0x1b, 0xc6, 0x0e, 0x42, 0xbf, 0x51, 0xe7, 0xd1, 0xb5, 0xe1, 0xcf, 0xf9, 0x77, 0xc4, 0x5b, 0xc9, 0x5c, 0xbb, 0x91, 0xae, 0x6f, 0xfb, 0xd0, 0x76, 0xaa, 0x17, 0x2a, 0x95, 0x47, 0xf9, 0x41, 0x44, 0x7f, 0x77, 0xa1, 0x47, 0x83, 0xe9, 0xf5, 0x58, 0xb2, 0x0c, 0x27, 0x36, 0xaa, 0x9b, 0xb4, 0x64, 0x0c, 0x06, 0xbf, 0xbf, 0x08, 0x4f, 0xc3, 0x56, 0x24, 0xa9, 0x4e, 0x68, 0xeb, 0x11, 0x31, 0x09, 0x25, 0x78, 0xa8, 0x40, 0xd6, 0x40, 0x81, 0xe3, 0x7b, 0xc0, 0x80, 0x25, 0x36, 0xff, 0x1c, 0x62, 0xe1, 0xd3, 0xd7, 0xaa, 0xb9, 0x9d, 0x5a, 0x48, 0x47, 0x9f, 0x1e, 0xd7, 0x34, 0x93, 0x5e, 0x86, 0x3f, 0xfd, 0x9c, 0x7e, 0x63, 0xce, 0xae, 0xa0, 0x63, 0xca, 0xe4, 0x7c, 0x7f, 0xc0, 0x17, 0xf0, 0x07, 0x46, 0xe4, 0xd9, 0xa4, 0xcc, 0xc8, 0xa5, 0x81, 0x40, 0x81, 0x94, 0xab, 0xfb, 0x52, 0xd4, 0xdf, 0x2e, 0x30, 0xb3, 0x58, 0xa3, 0x1d, 0xf4, 0xd0, 0x36, 0x32, 0x2d, 0xd4, 0x50, 0x4c, 0x7a, 0x44, 0x26, 0x7a, 0xd5, 0x60, 0xa8, 0x7a, 0xd5, 0x20, 0xab, 0x40, 0xb2, 0x8b, 0x83, 0xe6, 0xcd, 0x3e, 0xdf, 0xdf, 0x10, 0x9f, 0xea, 0x2e, 0x6a, 0x9a, 0x3c, 0xa4, 0x76, 0x72, 0x63, 0xd6, 0x15, 0x2b, 0x97, 0x2f, 0x5f, 0x78, 0x55, 0x7a, 0xed, 0xf8, 0x9a, 0xc1, 0x13, 0x1a, 0xf2, 0xe3, 0x1d, 0x6e, 0x7b, 0x59, 0xe3, 0xa3, 0x97, 0x5f, 0xb8, 0x69, 0x56, 0xa5, 0xbb, 0x78, 0x44, 0xf5, 0x45, 0x8b, 0x4b, 0xc7, 0x2e, 0x68, 0x6e, 0x18, 0xa2, 0xdf, 0x4e, 0x78, 0x8b, 0xde, 0x4e, 0xf0, 0x37, 0x3b, 0x70, 0x62, 0xa0, 0xb9, 0x64, 0x54, 0x62, 0x76, 0x5e, 0x76, 0x7a, 0xf9, 0x90, 0xfc, 0x51, 0x53, 0xaf, 0x9f, 0x18, 0xcc, 0xa8, 0x2e, 0x48, 0xf1, 0x64, 0x78, 0xec, 0xae, 0xce, 0xba, 0xe1, 0x65, 0x9d, 0x17, 0xd7, 0x64, 0x0d, 0x6d, 0xae, 0x76, 0x65, 0x5e, 0x7b, 0xd1, 0xe0, 0x59, 0x9d, 0x35, 0x71, 0x69, 0x93, 0xab, 0xf5, 0xab, 0x0c, 0xf4, 0xc8, 0x81, 0x8c, 0xcb, 0x6d, 0x64, 0x5c, 0x86, 0xa3, 0xf5, 0xc1, 0xc4, 0x62, 0x2c, 0x92, 0xce, 0xe1, 0x24, 0x08, 0x8f, 0xe7, 0x80, 0xa0, 0x1f, 0xea, 0x78, 0x94, 0x23, 0x41, 0x94, 0x45, 0x41, 0xee, 0x81, 0xde, 0x95, 0x25, 0xd8, 0x72, 0xa1, 0x0a, 0x23, 0x17, 0xb6, 0x5c, 0xa8, 0x26, 0xf1, 0x75, 0x6a, 0x97, 0x65, 0x33, 0x11, 0x0d, 0xf0, 0x7e, 0xaa, 0x0c, 0x7e, 0x63, 0x86, 0xae, 0xa0, 0xdd, 0x5f, 0xe8, 0xcf, 0x2f, 0xcc, 0x2f, 0x2a, 0xb6, 0x48, 0x19, 0x10, 0x73, 0xd3, 0xd4, 0x75, 0xd5, 0x91, 0xa1, 0x30, 0xce, 0x0a, 0x53, 0x14, 0x4e, 0x2d, 0xf8, 0xa6, 0x87, 0xbf, 0xad, 0xf6, 0x96, 0x09, 0x75, 0x83, 0xf5, 0x9e, 0x72, 0x6a, 0xdd, 0x1e, 0x88, 0x9f, 0xb9, 0x63, 0x45, 0x7b, 0xeb, 0xca, 0x67, 0x2f, 0x5f, 0xf4, 0xe8, 0xd0, 0x1a, 0xab, 0x0b, 0x9e, 0x7d, 0x74, 0xd6, 0x8f, 0x5a, 0x3c, 0x3a, 0x50, 0xd8, 0x71, 0xd9, 0xd0, 0xd6, 0x49, 0x75, 0x79, 0xee, 0xd4, 0xf8, 0xdb, 0x87, 0x8e, 0x74, 0xa4, 0x4d, 0xaa, 0xd4, 0xba, 0x88, 0xf5, 0x72, 0x93, 0xa3, 0xfd, 0xc6, 0xa3, 0xd7, 0x2c, 0x3d, 0x74, 0xf3, 0xf8, 0xf6, 0xe0, 0x28, 0x7b, 0x7c, 0x5a, 0x66, 0x5a, 0xcd, 0xc5, 0x1b, 0xa6, 0x76, 0xdd, 0x3c, 0xb3, 0x12, 0x22, 0x6d, 0x22, 0x75, 0x1d, 0x7a, 0x84, 0x7b, 0x5d, 0xc2, 0x84, 0x3b, 0x8f, 0xc4, 0x10, 0x8d, 0x99, 0x1e, 0x34, 0x10, 0x5c, 0x8d, 0x31, 0xc4, 0x49, 0x62, 0x35, 0xe3, 0xfe, 0xca, 0x30, 0xfe, 0xfc, 0xf0, 0x76, 0x14, 0xc0, 0x63, 0x71, 0x0b, 0x61, 0xd8, 0x63, 0x89, 0xcd, 0x39, 0x1b, 0xb9, 0x60, 0x3f, 0xbe, 0x85, 0x6d, 0x6d, 0xbf, 0x10, 0x9e, 0xad, 0xce, 0xa5, 0xa3, 0xfc, 0xcf, 0xc5, 0x5c, 0xa9, 0x03, 0x05, 0xf8, 0xeb, 0x61, 0x2e, 0xf1, 0x6b, 0xf0, 0x0d, 0xb4, 0x8c, 0x06, 0x49, 0x12, 0xde, 0x97, 0xb6, 0x20, 0x19, 0xf7, 0x72, 0x1b, 0x58, 0x1c, 0x6e, 0xb4, 0x21, 0xfc, 0x05, 0x9f, 0x05, 0x73, 0x13, 0xd3, 0x58, 0x42, 0xb8, 0x06, 0x6f, 0xa4, 0xe3, 0x1b, 0x50, 0x1e, 0x15, 0xfc, 0xe2, 0x4e, 0x74, 0x3e, 0x7a, 0x32, 0x98, 0x42, 0xd6, 0x00, 0xb9, 0x0a, 0x8b, 0x42, 0x32, 0xe6, 0x45, 0x3f, 0x21, 0x76, 0x39, 0xe0, 0xe3, 0x4e, 0x9f, 0x73, 0x56, 0x8b, 0x6c, 0xb1, 0xca, 0x40, 0xf4, 0x44, 0x49, 0x10, 0xe1, 0xb1, 0x90, 0x24, 0xc8, 0x74, 0xc5, 0xa0, 0xe3, 0x94, 0xd3, 0xa9, 0x8d, 0xa2, 0x4f, 0xdf, 0x0e, 0xce, 0xc4, 0x74, 0xce, 0x9d, 0x76, 0x4e, 0xbf, 0x31, 0x27, 0x51, 0x8c, 0x53, 0xcf, 0x9b, 0x38, 0x7e, 0xe4, 0x88, 0xe1, 0x6d, 0xcd, 0x83, 0xeb, 0x6b, 0x07, 0x95, 0x17, 0x15, 0x64, 0x65, 0xe4, 0xda, 0xa4, 0xe4, 0x12, 0xb7, 0x36, 0x95, 0x24, 0x59, 0xf5, 0x45, 0xd8, 0xaf, 0x38, 0x18, 0xf6, 0xf2, 0xe8, 0x6c, 0x94, 0x09, 0xf3, 0x35, 0xd8, 0x03, 0x90, 0xe0, 0xa2, 0x49, 0xbf, 0x5b, 0xdf, 0xf3, 0xec, 0xb5, 0xc3, 0x2f, 0xb9, 0x18, 0x1c, 0x01, 0x37, 0x74, 0xdf, 0x37, 0xaf, 0xa6, 0x31, 0x86, 0x78, 0x58, 0xb6, 0xf4, 0x9e, 0xdf, 0x62, 0x49, 0x77, 0xa6, 0x34, 0x5f, 0xb0, 0x6a, 0xfa, 0xbd, 0x8f, 0x97, 0x4c, 0x58, 0x36, 0xea, 0xd5, 0x83, 0x10, 0x6c, 0xb4, 0x60, 0x74, 0xf7, 0xf0, 0xf1, 0x4b, 0xc6, 0x55, 0x27, 0xba, 0x52, 0x6c, 0x62, 0xca, 0xa8, 0xb1, 0xf5, 0xb3, 0xd7, 0x4d, 0xbc, 0xec, 0xe1, 0x41, 0xa9, 0x6d, 0x93, 0x67, 0xd5, 0x9d, 0x77, 0xf7, 0x95, 0x13, 0x1c, 0x69, 0x13, 0x07, 0x45, 0x0b, 0xcc, 0xfc, 0xa9, 0xf3, 0xe3, 0x12, 0x4a, 0x6b, 0x4b, 0x6f, 0xbe, 0xae, 0xee, 0x82, 0xa0, 0x1f, 0x3f, 0x9d, 0x59, 0x3f, 0xb1, 0xa6, 0x62, 0x5c, 0x7d, 0xb6, 0xaf, 0xc8, 0x97, 0xe0, 0x82, 0x71, 0xb9, 0x40, 0x79, 0x94, 0xd4, 0xbd, 0x0b, 0xb5, 0xe3, 0x92, 0xa0, 0xb3, 0x88, 0xcc, 0x3b, 0x17, 0x1c, 0xef, 0xc2, 0xa4, 0xd3, 0xe2, 0xed, 0xf5, 0x37, 0x87, 0x72, 0x62, 0xcf, 0x21, 0xef, 0xc0, 0x53, 0x35, 0xa7, 0x9f, 0xa9, 0xca, 0x28, 0xf2, 0x99, 0xd7, 0x95, 0x7e, 0x16, 0x75, 0x9d, 0x69, 0x35, 0x5d, 0xe0, 0x1b, 0xd7, 0x1e, 0x00, 0xcd, 0x90, 0x7c, 0x4a, 0xcd, 0x10, 0xb1, 0xf7, 0x62, 0x6a, 0x06, 0x2a, 0x25, 0x1e, 0xd1, 0x39, 0xee, 0xbd, 0xbb, 0xca, 0xab, 0xfa, 0x8c, 0x7e, 0x46, 0xde, 0x5d, 0x5f, 0x3c, 0x79, 0xe1, 0x25, 0x2f, 0x62, 0xfe, 0xbe, 0x7b, 0xff, 0x71, 0x7e, 0xbb, 0x9c, 0xec, 0x72, 0x57, 0x4f, 0xbe, 0x7a, 0x7a, 0xf7, 0x33, 0xab, 0x47, 0x76, 0x5c, 0xbf, 0x67, 0xfe, 0x90, 0x39, 0x93, 0x3b, 0xf2, 0x2c, 0x89, 0xb6, 0xc4, 0xf8, 0xd7, 0x2f, 0x5c, 0x62, 0x4f, 0x1f, 0x5b, 0x66, 0x1a, 0xee, 0xdc, 0xc1, 0x4e, 0x89, 0x98, 0x7e, 0x96, 0xfb, 0x1f, 0xc2, 0x8e, 0x23, 0x0b, 0xe7, 0x4e, 0x9e, 0x63, 0x77, 0x04, 0x2a, 0x02, 0x9d, 0xb7, 0xfc, 0x7c, 0xf5, 0x9a, 0x37, 0x36, 0x75, 0x38, 0xb3, 0x2a, 0x7c, 0xb2, 0xd5, 0x01, 0x73, 0x74, 0x7c, 0xf8, 0x4b, 0xd0, 0xb5, 0x64, 0x6d, 0xac, 0x64, 0xeb, 0x27, 0xda, 0xdb, 0xfb, 0x2b, 0xb6, 0x7e, 0xf6, 0xfe, 0x4a, 0x9d, 0xf3, 0x65, 0x24, 0xcd, 0x62, 0xa2, 0x8f, 0x03, 0xdc, 0x9e, 0xef, 0xd9, 0xbd, 0xb1, 0x6f, 0xfa, 0xa4, 0xb9, 0x20, 0xfc, 0x39, 0x95, 0x9d, 0x00, 0x58, 0x6d, 0x74, 0x1d, 0xbe, 0x25, 0xb4, 0x5f, 0x5d, 0x87, 0xf7, 0x47, 0x9d, 0x87, 0x05, 0xe0, 0x3c, 0x8c, 0xd6, 0xb5, 0xab, 0x4f, 0x9a, 0xbb, 0xf0, 0x4f, 0xf0, 0x36, 0xa2, 0x1b, 0x0a, 0xf0, 0x60, 0x54, 0x8a, 0x7f, 0x41, 0xb4, 0x11, 0xf9, 0xaf, 0xf0, 0x0c, 0x4a, 0xa4, 0xb7, 0x68, 0xd4, 0xe3, 0xc1, 0x17, 0x84, 0x67, 0x28, 0x09, 0x26, 0xe9, 0x7d, 0xdc, 0x36, 0xde, 0xca, 0xef, 0xe6, 0x65, 0x71, 0x3b, 0x5a, 0xdd, 0x7b, 0x07, 0xbb, 0xf1, 0xd6, 0x7b, 0x87, 0xbe, 0x8f, 0x00, 0x9e, 0xd9, 0x2b, 0xb8, 0x6f, 0x78, 0x99, 0x27, 0x6b, 0x7f, 0xc8, 0xc5, 0x38, 0x74, 0xc8, 0xa5, 0xff, 0x3e, 0x0b, 0xaf, 0xc0, 0x83, 0xb8, 0x7f, 0x93, 0xdf, 0x89, 0x1e, 0x0b, 0x25, 0xa8, 0xbf, 0x27, 0xa8, 0x1c, 0x1b, 0xa3, 0x46, 0xe5, 0x7a, 0xfe, 0x6a, 0xf2, 0xed, 0x41, 0x74, 0x2c, 0x68, 0xcf, 0x77, 0x71, 0x58, 0xca, 0x20, 0x26, 0xa8, 0x16, 0x58, 0xb1, 0x02, 0x59, 0x64, 0x51, 0xb6, 0xc0, 0xeb, 0x45, 0xb2, 0xc0, 0x13, 0xe1, 0x21, 0x16, 0xa8, 0xea, 0xed, 0x46, 0x92, 0x58, 0x7c, 0x45, 0xf3, 0xd6, 0x26, 0xa3, 0x78, 0xa7, 0xce, 0xb6, 0x24, 0x6a, 0x47, 0xb4, 0x2e, 0x3a, 0x87, 0xf6, 0x1c, 0x3c, 0x46, 0x4e, 0xe3, 0x56, 0x29, 0xb1, 0x0d, 0xc9, 0x2a, 0x56, 0x94, 0xef, 0x23, 0x14, 0xcf, 0x4a, 0xe8, 0x9d, 0x3b, 0x12, 0xb8, 0x93, 0x70, 0xe0, 0xe4, 0x18, 0xc1, 0xd2, 0xfb, 0x44, 0xad, 0xa4, 0x29, 0x1b, 0xfd, 0xd3, 0x66, 0x77, 0xd7, 0x6b, 0x8b, 0xd5, 0x85, 0x63, 0xa2, 0xc3, 0x40, 0xe3, 0xfe, 0x16, 0x2f, 0xfe, 0xd2, 0xe2, 0x96, 0x40, 0x62, 0xfb, 0x4d, 0x64, 0xad, 0xfa, 0xc9, 0xc6, 0x71, 0xe9, 0x17, 0x18, 0x23, 0x40, 0x3f, 0x87, 0x47, 0xc6, 0xb1, 0x95, 0x6b, 0x3d, 0xac, 0x5c, 0x55, 0x6c, 0xe5, 0xc2, 0xa8, 0x96, 0x70, 0x3a, 0x51, 0xdc, 0x87, 0x9a, 0xd1, 0x8c, 0x60, 0x57, 0x76, 0x16, 0xa1, 0x4a, 0x09, 0x98, 0x93, 0xcb, 0x9d, 0x9c, 0x85, 0xe3, 0x3b, 0x9a, 0x30, 0xc6, 0xa3, 0xc1, 0x81, 0x01, 0xdc, 0x8e, 0x9a, 0x8b, 0x2c, 0x36, 0x8c, 0xed, 0x16, 0xac, 0x3b, 0x23, 0xed, 0x86, 0x07, 0x6b, 0x7c, 0x17, 0x7b, 0xbb, 0x16, 0x87, 0xed, 0xf6, 0xb9, 0x76, 0x30, 0xf3, 0x9a, 0x51, 0xf3, 0xe0, 0x86, 0x9a, 0xea, 0x5c, 0x57, 0x72, 0x6d, 0x7e, 0x5a, 0x52, 0x62, 0x25, 0x50, 0x5d, 0x9f, 0x91, 0xc2, 0x12, 0xfb, 0xa0, 0x9f, 0x83, 0xc0, 0x64, 0x9f, 0x79, 0xb7, 0x00, 0xe7, 0x46, 0x2c, 0x03, 0xc9, 0x2e, 0xb1, 0x03, 0xc0, 0x35, 0xc4, 0x6c, 0x10, 0x22, 0x07, 0x80, 0x99, 0xca, 0x5f, 0x93, 0x5d, 0x9a, 0xc1, 0xe0, 0xf1, 0xe0, 0x2f, 0x38, 0xcd, 0x2a, 0x78, 0x8f, 0x9e, 0xfd, 0xcd, 0xa0, 0x67, 0x7f, 0xb3, 0xe3, 0xd5, 0xb3, 0xbf, 0xa9, 0x9b, 0x94, 0x5f, 0x5b, 0x54, 0x63, 0x41, 0x99, 0x4b, 0xad, 0x4c, 0x3f, 0x59, 0x30, 0x6f, 0x24, 0x72, 0x17, 0x87, 0x3c, 0x68, 0x34, 0x4e, 0xec, 0xdc, 0xe3, 0x86, 0x18, 0x5d, 0x75, 0x18, 0xdb, 0x9a, 0x3d, 0x64, 0xb9, 0xaa, 0x4c, 0x00, 0x4f, 0x19, 0x23, 0x4b, 0x38, 0x24, 0xe7, 0xe7, 0x7a, 0x79, 0x11, 0x89, 0x1d, 0xde, 0x60, 0x6e, 0xe4, 0x67, 0x1c, 0xcf, 0x25, 0x20, 0x4e, 0x8e, 0x57, 0x53, 0x70, 0x56, 0xe4, 0xc5, 0xa2, 0x55, 0x86, 0xbd, 0x62, 0x28, 0xa8, 0xd0, 0x11, 0xcf, 0x91, 0x1f, 0x48, 0x8a, 0x39, 0x84, 0x6f, 0x62, 0x1a, 0xd6, 0x13, 0x8e, 0x0b, 0x6d, 0xd3, 0x89, 0x2d, 0x61, 0xb3, 0xcd, 0xef, 0xb4, 0x48, 0x1c, 0x0b, 0x81, 0xc9, 0xae, 0xac, 0xd4, 0x39, 0x48, 0x81, 0x32, 0x17, 0x2f, 0x43, 0x18, 0x73, 0x11, 0x59, 0x81, 0xcb, 0x6a, 0xd9, 0xd1, 0x00, 0xb9, 0x83, 0x83, 0x4f, 0x99, 0x11, 0x41, 0x06, 0xc8, 0x6d, 0x9f, 0x0e, 0xc3, 0xb5, 0x58, 0xcf, 0x0c, 0xc1, 0xe6, 0x53, 0x53, 0x53, 0x47, 0xa7, 0x8e, 0x6e, 0x0b, 0x92, 0xb1, 0xab, 0x2a, 0x2f, 0x73, 0xb9, 0xdd, 0x3e, 0x4f, 0x7e, 0xb2, 0x3f, 0xd1, 0x09, 0x86, 0x4a, 0x16, 0x8c, 0x15, 0xbb, 0x33, 0x48, 0x4c, 0x41, 0x8f, 0x6a, 0x08, 0x8a, 0x64, 0x7d, 0x06, 0xdf, 0xc1, 0xd4, 0x68, 0x21, 0x16, 0x20, 0xfc, 0x1b, 0xde, 0x25, 0xd5, 0xd5, 0xbb, 0x20, 0x0a, 0x3a, 0xdd, 0xd5, 0xf6, 0xb0, 0x49, 0x90, 0x89, 0xeb, 0x6a, 0x85, 0xe6, 0x8b, 0x8e, 0xf5, 0xcc, 0x99, 0x59, 0x3c, 0xa6, 0xd1, 0x17, 0xda, 0xb9, 0xec, 0xd8, 0xf4, 0x8b, 0xa6, 0xb7, 0x2e, 0x2c, 0x13, 0x1f, 0xb8, 0xf2, 0xbf, 0x8e, 0x09, 0x4b, 0x8f, 0x4c, 0x3f, 0x76, 0xd1, 0xd1, 0x9e, 0xa3, 0x0b, 0xee, 0xbd, 0x76, 0xcd, 0x8f, 0x8a, 0x6c, 0x8e, 0x86, 0xf5, 0x33, 0xe6, 0x5d, 0x9b, 0x99, 0xd2, 0x3e, 0x75, 0x56, 0xd5, 0xc4, 0x55, 0xe7, 0x97, 0x4f, 0xe7, 0xf2, 0xb2, 0x4b, 0x9f, 0x5a, 0x76, 0x79, 0xe6, 0xe0, 0xa9, 0x8d, 0x7e, 0x5c, 0x98, 0xe9, 0xbf, 0xa2, 0xa7, 0xad, 0x71, 0xee, 0x15, 0xf7, 0xe5, 0x2a, 0x8f, 0xe1, 0xb6, 0x40, 0x86, 0x72, 0x0d, 0x3e, 0x96, 0x5d, 0xc6, 0x85, 0xfe, 0x81, 0xbf, 0x2d, 0x5a, 0xb6, 0x62, 0x7b, 0x0e, 0x9e, 0x5d, 0x30, 0x6b, 0xd8, 0x94, 0x25, 0x33, 0xeb, 0xc6, 0x55, 0xa5, 0x56, 0xcf, 0xbe, 0x6d, 0xc6, 0xa6, 0x92, 0xcb, 0x60, 0x9c, 0xc3, 0x3f, 0x56, 0x76, 0x0a, 0x6e, 0xc1, 0x4e, 0xec, 0xc2, 0x54, 0xd4, 0x8e, 0x7e, 0x13, 0x4c, 0xa2, 0xde, 0x7c, 0x13, 0x89, 0xfd, 0xe1, 0x26, 0x3c, 0xa8, 0x7a, 0x10, 0x27, 0x59, 0x04, 0x6d, 0xf1, 0xb5, 0xc5, 0x71, 0x3c, 0xb2, 0x48, 0xbc, 0x65, 0x0e, 0xe2, 0xac, 0x84, 0x36, 0x71, 0xd2, 0x1c, 0x6a, 0xd3, 0xd9, 0xb1, 0xba, 0x39, 0x44, 0xad, 0xbd, 0x85, 0x30, 0x03, 0x16, 0xc8, 0xfa, 0x82, 0x78, 0x7a, 0xb9, 0x96, 0x68, 0xb9, 0x82, 0x55, 0xb1, 0x32, 0xd8, 0xed, 0xf3, 0x3b, 0x4d, 0xfb, 0x50, 0xa6, 0x4c, 0x64, 0xa8, 0x32, 0x9c, 0xce, 0x11, 0xc3, 0x86, 0x0e, 0x19, 0xdc, 0x58, 0x5f, 0x57, 0x55, 0x59, 0x51, 0x56, 0x58, 0x90, 0x9d, 0xe9, 0x4c, 0x75, 0xa6, 0x16, 0x82, 0x55, 0xe9, 0xd1, 0x6d, 0x75, 0x76, 0xdf, 0xd2, 0x4d, 0x86, 0x45, 0xb5, 0x17, 0x23, 0xea, 0xa8, 0x04, 0xc3, 0x8c, 0x2b, 0x48, 0xf6, 0xd7, 0x9a, 0xcd, 0x76, 0x17, 0x6c, 0x50, 0x8d, 0x89, 0x98, 0x95, 0xff, 0x55, 0xfb, 0x5c, 0xe3, 0xb4, 0x00, 0x35, 0xc7, 0x57, 0xe5, 0x8c, 0x3e, 0x6f, 0x66, 0x1d, 0xb1, 0x2a, 0xeb, 0x5b, 0x96, 0x3e, 0x3c, 0xdf, 0xc5, 0xe5, 0xd4, 0x8f, 0x2b, 0x77, 0x7b, 0x8e, 0x9a, 0x2d, 0x77, 0x32, 0x74, 0xdf, 0xeb, 0x96, 0x65, 0x7b, 0x28, 0x27, 0xae, 0xb6, 0xf0, 0x00, 0x31, 0xcc, 0xf9, 0x79, 0x81, 0xd6, 0xb2, 0xb4, 0xe1, 0x6b, 0x89, 0x69, 0x79, 0x78, 0xd3, 0xc4, 0xca, 0x89, 0x8d, 0x39, 0xe2, 0xd2, 0x57, 0x23, 0xc6, 0x3b, 0xff, 0x7d, 0x01, 0xbb, 0x7b, 0xfc, 0x1d, 0x19, 0xa4, 0x3a, 0x32, 0x3e, 0x35, 0xe8, 0x4b, 0xd6, 0x9f, 0x49, 0xf0, 0x10, 0xab, 0xc2, 0xc5, 0x09, 0x30, 0x48, 0x42, 0x6e, 0x36, 0xf8, 0x85, 0xd7, 0x62, 0xf0, 0xc1, 0x6f, 0xb6, 0x3e, 0xbf, 0x01, 0xcc, 0xf5, 0x81, 0x21, 0xd4, 0x01, 0x94, 0x97, 0x67, 0xc5, 0x02, 0x02, 0x02, 0x32, 0x07, 0xf1, 0x16, 0x2c, 0x4a, 0xbc, 0x38, 0x87, 0x85, 0x6d, 0xb4, 0x61, 0x55, 0xad, 0xcf, 0x95, 0xb4, 0x1b, 0x70, 0x85, 0x7d, 0xd3, 0xd2, 0xe9, 0xa3, 0xc7, 0x79, 0xd4, 0xd3, 0x93, 0x25, 0x06, 0x5e, 0x08, 0x63, 0xa1, 0xa7, 0x9f, 0x2c, 0xea, 0x1b, 0x3e, 0x3d, 0x07, 0x19, 0xc2, 0x84, 0xaa, 0x41, 0x65, 0xa5, 0x89, 0xee, 0xc4, 0xbc, 0xc4, 0x40, 0x6e, 0x52, 0xae, 0x1d, 0xe6, 0x18, 0x9b, 0x2e, 0xd4, 0x31, 0x3e, 0x7d, 0xb2, 0x6b, 0xda, 0x41, 0x21, 0x33, 0xcb, 0xb8, 0x07, 0xe0, 0x82, 0xa4, 0xf8, 0xf1, 0xca, 0x0d, 0xb3, 0x7c, 0x4d, 0x29, 0xf1, 0x72, 0xba, 0xa3, 0x21, 0xa7, 0x62, 0x54, 0x63, 0xa9, 0xdb, 0x15, 0xa7, 0x29, 0xc2, 0xe4, 0x84, 0xa3, 0x71, 0xba, 0xdd, 0x9f, 0xe0, 0xea, 0xc9, 0xf1, 0x88, 0x7c, 0xed, 0xdc, 0x65, 0x4e, 0xe7, 0x66, 0xaf, 0xdd, 0x5b, 0x3b, 0xb6, 0x72, 0xbb, 0xc8, 0x74, 0xa0, 0xa0, 0xec, 0xc3, 0x0f, 0x0a, 0x6c, 0x28, 0x78, 0xfc, 0x33, 0x4f, 0x26, 0x19, 0x87, 0x5f, 0x20, 0xc4, 0xfd, 0x4c, 0xb0, 0xa2, 0x41, 0x68, 0x4b, 0xe7, 0x9e, 0x00, 0xdc, 0xa2, 0xb3, 0xd0, 0x5b, 0x0e, 0x51, 0x5d, 0xed, 0xb6, 0xd0, 0x95, 0xc1, 0xdc, 0xd3, 0x2c, 0x83, 0xbf, 0xbf, 0x8e, 0x36, 0xf5, 0x5b, 0xf1, 0x00, 0xfd, 0x16, 0xdd, 0x65, 0x41, 0x47, 0x05, 0xeb, 0x2f, 0xbf, 0x2f, 0x3f, 0x0f, 0xce, 0xaa, 0xdd, 0x46, 0x2b, 0x90, 0xf4, 0x4d, 0x26, 0xee, 0xbf, 0xbf, 0x52, 0x3c, 0x47, 0x8b, 0xce, 0xbb, 0xbe, 0xab, 0x72, 0x42, 0x59, 0x8a, 0x94, 0xea, 0x88, 0x2f, 0xcc, 0x7f, 0xd4, 0x15, 0xaf, 0x75, 0x54, 0x4a, 0xc2, 0xb1, 0x38, 0xbb, 0x2e, 0xc3, 0x4e, 0x0b, 0xb7, 0x79, 0xf2, 0x86, 0x39, 0x75, 0x89, 0xee, 0xa9, 0x24, 0xe1, 0xf1, 0xe7, 0xf4, 0x5e, 0xda, 0x8a, 0x5f, 0xe4, 0x35, 0x81, 0xa5, 0xfb, 0x19, 0xe1, 0xcf, 0xf9, 0x5c, 0xb1, 0x05, 0x0d, 0x41, 0x53, 0x82, 0x93, 0xd2, 0xb1, 0x20, 0xe5, 0x27, 0x71, 0x16, 0x1e, 0xa2, 0x2c, 0x55, 0x61, 0x3b, 0x27, 0x74, 0x10, 0xee, 0x20, 0x2e, 0xb3, 0xc2, 0xd6, 0x06, 0x07, 0x21, 0x8d, 0xc9, 0xd2, 0xc9, 0xd9, 0x2d, 0xdc, 0x1c, 0x99, 0x28, 0x02, 0x2d, 0x0e, 0xb6, 0xdd, 0x0e, 0x2f, 0x92, 0xd1, 0x42, 0x34, 0xa6, 0xbe, 0x16, 0x7c, 0xb5, 0xe7, 0xba, 0x5c, 0x85, 0xee, 0xa4, 0xc4, 0x64, 0xb6, 0xe3, 0x46, 0x3f, 0xab, 0x05, 0x9b, 0x76, 0x7e, 0x28, 0xc2, 0x02, 0xc4, 0xbb, 0xa2, 0xd7, 0x4a, 0x7d, 0x8b, 0x39, 0xc5, 0xc3, 0xe7, 0x0e, 0x5a, 0x3b, 0x72, 0xe5, 0x0d, 0x71, 0x71, 0xda, 0x67, 0xb9, 0x9c, 0x57, 0xb8, 0x3c, 0x3b, 0x4a, 0x05, 0x4f, 0xe2, 0x1c, 0x9c, 0xa3, 0x7c, 0x60, 0xfc, 0xf8, 0xa6, 0x72, 0x6f, 0x41, 0x46, 0x92, 0xc5, 0x66, 0x97, 0xc6, 0x94, 0x0e, 0xda, 0x70, 0xdd, 0xbf, 0xd5, 0x8f, 0xe4, 0x7f, 0x9f, 0x9c, 0x3c, 0xde, 0x83, 0x2b, 0x5c, 0x6e, 0x65, 0xe7, 0x26, 0xe5, 0x97, 0x5a, 0x37, 0xdc, 0xd9, 0xdc, 0xe4, 0x70, 0x39, 0xac, 0x36, 0xc6, 0xd7, 0x88, 0x56, 0xe5, 0xd7, 0xf1, 0xdf, 0xa2, 0x6a, 0x34, 0x35, 0x38, 0x39, 0x37, 0x8d, 0x93, 0x2d, 0xd5, 0x84, 0x35, 0x40, 0xec, 0x24, 0xc4, 0x77, 0x20, 0x8b, 0x20, 0x0b, 0x16, 0xb2, 0x16, 0xf1, 0x9c, 0x95, 0x03, 0xbf, 0x2d, 0x02, 0x02, 0x77, 0x78, 0x73, 0xc8, 0x8a, 0x64, 0x45, 0xb2, 0x95, 0xba, 0xce, 0x51, 0x3b, 0x82, 0x94, 0x75, 0x01, 0x7b, 0x7d, 0x5d, 0x59, 0x91, 0x54, 0xe0, 0x72, 0xe7, 0x91, 0x7f, 0xd1, 0xfb, 0x08, 0xf9, 0xe4, 0x23, 0x69, 0x40, 0x74, 0x50, 0x53, 0xfa, 0xfb, 0xc9, 0x14, 0x0f, 0xdc, 0x46, 0xa0, 0x17, 0x35, 0x40, 0xd5, 0xe9, 0x8f, 0x2c, 0x03, 0x05, 0x78, 0x3c, 0x97, 0xd5, 0x1b, 0x72, 0xc4, 0x63, 0x6b, 0x82, 0x37, 0xce, 0x91, 0x95, 0xea, 0xba, 0x27, 0x67, 0xf0, 0xc4, 0xaa, 0xe2, 0x09, 0xf9, 0xcf, 0x87, 0xfe, 0xce, 0xfd, 0xdb, 0x19, 0xf7, 0x57, 0x87, 0x37, 0x2e, 0x3e, 0xc3, 0xe3, 0x5c, 0x9f, 0x56, 0xd5, 0x59, 0x55, 0x3c, 0x3e, 0xff, 0xde, 0x7b, 0x27, 0x39, 0x08, 0xc9, 0x98, 0x29, 0xc9, 0xf6, 0x94, 0x9c, 0x94, 0xea, 0x29, 0x2d, 0xbe, 0x14, 0xd7, 0xbf, 0xee, 0x9d, 0x94, 0x5c, 0xe8, 0x50, 0x8e, 0x8b, 0xb2, 0x2d, 0x19, 0xb0, 0x66, 0x5f, 0x4a, 0x02, 0xe1, 0xae, 0xad, 0xe1, 0x6c, 0xf4, 0x29, 0xe7, 0x22, 0x4c, 0xfa, 0x1e, 0x84, 0x4e, 0x6e, 0x64, 0xdc, 0xf7, 0xe4, 0x46, 0x9d, 0xdb, 0x8e, 0x0f, 0x67, 0xe3, 0x5b, 0xc9, 0xd8, 0xcb, 0xe8, 0xc1, 0x70, 0x98, 0xfd, 0x8e, 0x8d, 0xbf, 0x77, 0x84, 0x3d, 0x5c, 0x0a, 0x2e, 0xe4, 0x65, 0xf4, 0x88, 0x96, 0xdf, 0xf4, 0xbb, 0x25, 0x9c, 0xcd, 0x65, 0xe1, 0x42, 0x92, 0x7f, 0x07, 0xc2, 0x31, 0x7e, 0x9f, 0xa0, 0xfc, 0x88, 0x6b, 0x26, 0x5d, 0x2b, 0xf3, 0x63, 0x62, 0xd6, 0xef, 0x52, 0xae, 0xe2, 0x1a, 0x70, 0x19, 0xf9, 0x7d, 0x2c, 0xe2, 0x63, 0xe4, 0x8f, 0x53, 0x16, 0x71, 0x76, 0x5c, 0x41, 0x7e, 0x9f, 0xd0, 0xf7, 0x77, 0xb2, 0x0c, 0x96, 0x93, 0xb1, 0xfc, 0x5c, 0xbc, 0x1f, 0x95, 0xa0, 0xa9, 0x70, 0x63, 0x2e, 0xe0, 0xb1, 0xf2, 0x3c, 0x9a, 0xdc, 0x5e, 0xcf, 0x4b, 0x22, 0xd7, 0x61, 0x83, 0x97, 0x4c, 0x22, 0x21, 0x8a, 0x3d, 0x56, 0x0b, 0x27, 0x4a, 0x44, 0xb6, 0x17, 0x83, 0x6f, 0x6d, 0x34, 0xa7, 0x53, 0xc6, 0x74, 0x7a, 0x22, 0x34, 0xae, 0xb3, 0x75, 0x48, 0x4b, 0x53, 0x55, 0xe5, 0xa0, 0x72, 0x52, 0x44, 0xb1, 0xab, 0xa4, 0xc5, 0x65, 0x87, 0xab, 0xe4, 0xf0, 0x70, 0xc9, 0xe3, 0xa6, 0x0f, 0x97, 0x98, 0xdc, 0x56, 0x99, 0xfe, 0x06, 0xab, 0x10, 0x1f, 0x28, 0x20, 0x23, 0x29, 0xf9, 0xc5, 0x28, 0xa0, 0x04, 0xeb, 0x99, 0xeb, 0xa2, 0xf2, 0x7e, 0x77, 0x7c, 0xa9, 0xcb, 0xc3, 0xcf, 0x17, 0xad, 0xa2, 0x18, 0x67, 0xb9, 0x5c, 0x48, 0x72, 0xd4, 0xe7, 0x5a, 0x96, 0xca, 0x36, 0x91, 0x00, 0xdd, 0x42, 0x8a, 0x63, 0xe1, 0x2f, 0x16, 0x25, 0x5a, 0xae, 0xb1, 0x4a, 0x92, 0xf5, 0x5a, 0xab, 0x07, 0x7f, 0x17, 0x8d, 0x5c, 0xa6, 0xe7, 0xb6, 0x39, 0xcd, 0x99, 0x79, 0x9e, 0xbb, 0xd7, 0x95, 0xa0, 0xac, 0xf5, 0x56, 0xa5, 0xa5, 0x55, 0x79, 0xf1, 0x75, 0x0e, 0x57, 0x8e, 0xb2, 0x3a, 0xbd, 0x32, 0xd5, 0x53, 0x99, 0x8e, 0xaf, 0x75, 0xba, 0x42, 0x97, 0xa4, 0xe3, 0xfb, 0xf3, 0xb2, 0xfd, 0xb9, 0xca, 0xdc, 0xf4, 0x9b, 0x13, 0x4c, 0x7f, 0x25, 0xf9, 0x5c, 0x90, 0x2f, 0xb5, 0xa2, 0x0e, 0x5f, 0xe7, 0x4c, 0x80, 0x6c, 0x83, 0x20, 0x1b, 0xb3, 0x8b, 0x94, 0x11, 0xb8, 0x22, 0x7c, 0x84, 0xd9, 0x45, 0xdf, 0xbd, 0xcf, 0xfa, 0xfe, 0xbb, 0xf7, 0x23, 0x76, 0x11, 0xf9, 0x7d, 0x10, 0xfd, 0xfd, 0x86, 0x58, 0xbf, 0x2b, 0x4b, 0x38, 0x1b, 0xbe, 0x9d, 0xbb, 0x07, 0xc9, 0xb8, 0x33, 0xf4, 0x12, 0xea, 0x07, 0x83, 0x3b, 0x16, 0xc2, 0x46, 0x2e, 0x99, 0xd8, 0x7c, 0xf4, 0x0c, 0x87, 0xde, 0xb1, 0x87, 0xdb, 0x6a, 0xdd, 0xc8, 0x74, 0x86, 0x13, 0x79, 0x86, 0x8b, 0x2b, 0xf4, 0x33, 0x1c, 0x71, 0x57, 0xe4, 0x10, 0x07, 0xb3, 0xb7, 0x7b, 0xe2, 0x36, 0x94, 0x00, 0xef, 0xc1, 0x6c, 0x2c, 0xf6, 0x05, 0xdc, 0xb8, 0xe7, 0xe1, 0x16, 0xe5, 0x1c, 0x1a, 0xde, 0x38, 0x01, 0x39, 0xd5, 0x0b, 0xbc, 0xd1, 0x6f, 0xee, 0xb8, 0x37, 0xfa, 0x3c, 0xb1, 0xdb, 0x16, 0xf5, 0xa2, 0x8e, 0xd6, 0xa1, 0xdc, 0xc3, 0xbd, 0x11, 0x7e, 0x98, 0x14, 0xe4, 0x0d, 0xa6, 0xca, 0xd4, 0x03, 0x2e, 0x3c, 0x57, 0x59, 0x06, 0xda, 0xb5, 0x07, 0x8d, 0x71, 0x95, 0xa8, 0x97, 0xf9, 0x61, 0x63, 0x95, 0xc8, 0x83, 0x93, 0x3d, 0x55, 0x24, 0xa5, 0xaf, 0x89, 0xf3, 0xc4, 0xff, 0xc8, 0x62, 0xb5, 0x5a, 0xae, 0xb4, 0xa5, 0xc5, 0xad, 0xfb, 0xf0, 0x39, 0xe5, 0x1e, 0x4b, 0xfc, 0x35, 0x96, 0xb8, 0x38, 0xcb, 0x35, 0x76, 0x1b, 0xb6, 0xb3, 0xf8, 0x7a, 0xca, 0x36, 0xee, 0x89, 0xf0, 0x2e, 0x94, 0x0c, 0xb1, 0x3b, 0xe8, 0xbb, 0x18, 0x7a, 0x95, 0x7e, 0xba, 0xc0, 0xca, 0x3e, 0xab, 0x10, 0x7b, 0xca, 0xb6, 0x18, 0x21, 0xf6, 0x20, 0x86, 0xb3, 0xf2, 0x32, 0xf7, 0x72, 0xf8, 0x41, 0xe4, 0x85, 0x57, 0xd7, 0x7a, 0x1c, 0x46, 0xe6, 0xa7, 0x1d, 0xdc, 0xb9, 0x2d, 0xe0, 0xfa, 0x06, 0x71, 0xa6, 0x27, 0x69, 0x67, 0x1c, 0xc4, 0xf9, 0x85, 0x18, 0x31, 0x9c, 0x71, 0xf8, 0x46, 0x7c, 0x23, 0x57, 0xc5, 0x2d, 0x45, 0x19, 0xf0, 0x26, 0x17, 0x9e, 0xb1, 0x0a, 0xd3, 0x44, 0xcc, 0x7c, 0x34, 0xcd, 0x21, 0xfc, 0x91, 0xde, 0x49, 0x14, 0xd0, 0xf8, 0x44, 0x88, 0xb3, 0x6a, 0x7a, 0xc8, 0x1a, 0x15, 0x4e, 0xb5, 0xb6, 0x9a, 0xab, 0xca, 0xac, 0xe9, 0x2c, 0x2f, 0xed, 0x1c, 0xd6, 0x92, 0x95, 0x13, 0xc0, 0x37, 0x66, 0xd6, 0x75, 0x56, 0x94, 0x74, 0xb6, 0xb5, 0x64, 0x67, 0x17, 0xae, 0xe5, 0xba, 0x41, 0x0f, 0x66, 0xf8, 0x33, 0x8a, 0xcb, 0x88, 0x92, 0xcc, 0xcd, 0xcc, 0xcd, 0x2c, 0x2a, 0xa3, 0x63, 0x78, 0x27, 0x19, 0xc3, 0x07, 0xc8, 0x18, 0x36, 0xec, 0xb7, 0xa9, 0x31, 0xe4, 0x80, 0x3a, 0xc5, 0x83, 0xa0, 0xe8, 0x07, 0x7f, 0xde, 0xa0, 0x3d, 0xf2, 0xf7, 0x4b, 0xd9, 0x5b, 0x01, 0x5d, 0x7c, 0xc4, 0x53, 0x8b, 0xcf, 0xac, 0x28, 0xe9, 0x81, 0xf1, 0x7d, 0x82, 0x8c, 0xef, 0x8b, 0x64, 0x7c, 0x73, 0x83, 0xd9, 0x02, 0xbb, 0xc3, 0xcf, 0x51, 0xab, 0x68, 0x41, 0xdf, 0x00, 0x8a, 0x22, 0x0b, 0x4d, 0x66, 0x78, 0x81, 0x1f, 0x63, 0x74, 0xdb, 0x4b, 0x3b, 0xe6, 0xd6, 0x6b, 0x83, 0x5b, 0x3f, 0xb7, 0xa3, 0x14, 0xe6, 0x58, 0xe8, 0x29, 0x49, 0xe2, 0x26, 0xc9, 0x2b, 0xb8, 0x00, 0xee, 0xa5, 0xb7, 0x5d, 0x20, 0xea, 0xf7, 0x1f, 0xd9, 0x1b, 0xbe, 0x3f, 0x6a, 0x7b, 0x22, 0x55, 0xc2, 0x34, 0xee, 0x5d, 0x62, 0x92, 0xc9, 0x9c, 0x8b, 0x10, 0xf5, 0x9d, 0x6c, 0xee, 0x8a, 0x3b, 0x75, 0xbd, 0x8a, 0x42, 0x1f, 0x08, 0xeb, 0x88, 0x9c, 0x6b, 0x7e, 0x3f, 0x58, 0x3c, 0x8f, 0x59, 0x7d, 0x4e, 0x65, 0xd5, 0x67, 0x46, 0x74, 0x59, 0x27, 0xff, 0x17, 0xd6, 0x9d, 0xbc, 0x0a, 0xfe, 0xaf, 0x5c, 0x7e, 0x27, 0x3e, 0x7a, 0x27, 0xa9, 0xe7, 0x80, 0xc2, 0x71, 0x97, 0x84, 0x5f, 0x20, 0x0c, 0x43, 0x21, 0x3a, 0xe2, 0x32, 0xa6, 0xff, 0xbf, 0xbb, 0x4c, 0xd7, 0x21, 0xf7, 0x84, 0x3e, 0xe3, 0x96, 0x85, 0xf7, 0x92, 0x76, 0x58, 0xc8, 0xef, 0x97, 0xa8, 0x3a, 0xe4, 0x12, 0xfd, 0xf7, 0xed, 0xa1, 0x6f, 0xb9, 0xcb, 0x40, 0xc7, 0x40, 0x7c, 0xf8, 0xef, 0x56, 0xab, 0xbf, 0xaf, 0xd6, 0x7f, 0xef, 0x0e, 0x67, 0xe1, 0x9b, 0xd0, 0xbf, 0xc8, 0xef, 0xf5, 0x08, 0x7d, 0xff, 0x8a, 0x7a, 0xfe, 0xf9, 0x8a, 0xbe, 0x37, 0xb3, 0x5c, 0xd9, 0x2a, 0x8e, 0x86, 0x3b, 0x22, 0x68, 0x23, 0xdb, 0x32, 0xb4, 0xe7, 0xc0, 0x41, 0x6a, 0x22, 0xf5, 0x97, 0xe0, 0x8d, 0xfc, 0x4d, 0xa4, 0xb7, 0x3b, 0x21, 0x41, 0x2e, 0xb2, 0x20, 0xb8, 0xd6, 0x37, 0xcb, 0x66, 0xe5, 0xe0, 0x76, 0xa6, 0x44, 0xa6, 0x20, 0x9a, 0xdb, 0x29, 0x52, 0xd7, 0x37, 0x88, 0xe7, 0x7b, 0xe0, 0x3c, 0xad, 0x08, 0x5e, 0x65, 0x23, 0xd8, 0x33, 0x8e, 0x99, 0x58, 0xdb, 0x20, 0xec, 0xe1, 0x59, 0xc4, 0x56, 0x52, 0x7d, 0x81, 0xcb, 0x95, 0x92, 0x97, 0x54, 0x48, 0x3a, 0x8c, 0xb2, 0x66, 0x7a, 0x33, 0xd8, 0xdf, 0xdf, 0xa6, 0x60, 0xbd, 0x1f, 0xdc, 0xbe, 0xba, 0xc4, 0xd1, 0x27, 0x42, 0xee, 0xe6, 0xa4, 0x0b, 0x27, 0xfa, 0x9a, 0x26, 0x57, 0x16, 0x8d, 0xce, 0x49, 0x94, 0xdc, 0x52, 0x42, 0x72, 0x56, 0x52, 0x41, 0xd1, 0xc4, 0xb1, 0xe5, 0xc5, 0xef, 0x70, 0x9f, 0x89, 0xdb, 0xbe, 0x9f, 0xcb, 0xdd, 0x14, 0x5a, 0x31, 0x32, 0x69, 0x64, 0x9d, 0xaf, 0x26, 0x2f, 0xc9, 0xe9, 0x68, 0x71, 0xcb, 0xb2, 0x9c, 0x93, 0x9a, 0x97, 0x69, 0xdb, 0x20, 0x6e, 0x0b, 0xdd, 0xc8, 0x5d, 0x4d, 0xfa, 0x68, 0xb9, 0xf2, 0x35, 0xf7, 0xa6, 0x60, 0xe7, 0x02, 0xe8, 0x47, 0xb0, 0xc3, 0x2f, 0x7e, 0x24, 0xb2, 0xbe, 0x5b, 0xaf, 0x6c, 0xe3, 0x3f, 0xa4, 0xe7, 0xcb, 0x33, 0x61, 0xef, 0x5d, 0x4a, 0x41, 0x0f, 0xd0, 0x3e, 0x4b, 0x23, 0x02, 0xf0, 0x7b, 0xf1, 0x18, 0xca, 0x81, 0x3b, 0x99, 0x19, 0x18, 0xcb, 0xd9, 0x64, 0xa8, 0x1d, 0x76, 0x8e, 0x13, 0x78, 0xea, 0x62, 0x02, 0xb8, 0x2f, 0xb1, 0xcd, 0xe1, 0x8a, 0x05, 0x02, 0x62, 0x24, 0xcf, 0x53, 0xaf, 0x65, 0x12, 0x16, 0xe8, 0x27, 0xff, 0x67, 0xb7, 0x56, 0x4d, 0xa4, 0xd6, 0x68, 0xaf, 0x79, 0xe0, 0x6e, 0x05, 0xfe, 0x37, 0x21, 0x79, 0x77, 0xf9, 0x06, 0x4f, 0xac, 0x4c, 0x4c, 0x38, 0x26, 0xc7, 0x4b, 0x81, 0x42, 0x7c, 0x8f, 0xaf, 0x79, 0xe2, 0x20, 0x57, 0xe2, 0xb1, 0x38, 0x7e, 0x78, 0x21, 0xa5, 0x6d, 0x7c, 0x68, 0x14, 0x1f, 0xc7, 0xc1, 0xb6, 0x08, 0x61, 0x38, 0x3c, 0x57, 0x18, 0x5a, 0xc3, 0xd3, 0x76, 0x67, 0x87, 0x3f, 0xe7, 0x8e, 0x48, 0xcd, 0xa4, 0xdd, 0xd7, 0xd0, 0x7d, 0xc4, 0xfd, 0xe1, 0xd7, 0xd0, 0x01, 0x90, 0x6f, 0x6e, 0xcf, 0x01, 0x55, 0xbe, 0x43, 0x53, 0x85, 0x8d, 0xca, 0xd7, 0x64, 0x4d, 0x91, 0xd1, 0x93, 0xbd, 0x1f, 0xb0, 0xb5, 0xa7, 0x37, 0x2c, 0xd9, 0xc2, 0x16, 0x79, 0x08, 0xc1, 0x76, 0x6a, 0x58, 0xa8, 0x57, 0xd8, 0x18, 0x7e, 0x90, 0xa6, 0xdb, 0xa5, 0x63, 0x7f, 0x93, 0xc8, 0x6c, 0xa5, 0xe9, 0x76, 0xeb, 0x58, 0x8d, 0x70, 0x7b, 0x78, 0x17, 0xbd, 0x07, 0xfb, 0x94, 0x8e, 0x95, 0x92, 0xf2, 0x5e, 0x94, 0xbd, 0x04, 0x7b, 0x3a, 0x46, 0xba, 0x3d, 0x3a, 0x16, 0xd0, 0xd3, 0xed, 0xd5, 0xb1, 0x51, 0xc2, 0xce, 0xf0, 0x7d, 0xe2, 0xad, 0x70, 0xd2, 0x68, 0x28, 0x4f, 0x0e, 0x1f, 0x96, 0xbe, 0x20, 0xd8, 0xb3, 0x3a, 0xd6, 0x20, 0xdc, 0x15, 0x7e, 0x5c, 0x5c, 0x03, 0x37, 0x6e, 0x74, 0xac, 0x50, 0x4a, 0x08, 0xbf, 0x20, 0x7d, 0x4b, 0xb0, 0xe7, 0x74, 0x6c, 0x9c, 0x70, 0x3c, 0x7c, 0x87, 0xd8, 0x41, 0xb0, 0xe7, 0x75, 0xec, 0x3c, 0x69, 0x50, 0x78, 0xa3, 0xf4, 0x30, 0xc1, 0x5e, 0xd0, 0xb1, 0x69, 0xe4, 0x7b, 0xd7, 0xd3, 0xf6, 0x1d, 0xd0, 0xb1, 0x16, 0xc9, 0x1a, 0x7e, 0x48, 0x76, 0x13, 0xec, 0x45, 0x1d, 0x7b, 0x48, 0xb8, 0x34, 0xdc, 0x2a, 0x7e, 0x42, 0xb0, 0x83, 0x3a, 0xf6, 0x94, 0xf8, 0x65, 0xb8, 0x0a, 0xee, 0x2c, 0xa3, 0x97, 0x74, 0xec, 0x0e, 0x61, 0x23, 0xaa, 0xa0, 0xfd, 0xf7, 0x13, 0x1d, 0xbb, 0x86, 0xf4, 0xdf, 0x1c, 0xfa, 0xbd, 0x87, 0x22, 0x7d, 0x2a, 0x3c, 0xa3, 0x7c, 0x23, 0x1e, 0x22, 0xd8, 0x61, 0x1d, 0xfb, 0xa7, 0x94, 0xae, 0x7c, 0x21, 0x67, 0x12, 0xec, 0x88, 0x8e, 0xbd, 0xa9, 0x6c, 0x0d, 0x0f, 0x25, 0xab, 0x82, 0x8c, 0x8e, 0xea, 0xd8, 0x21, 0xe5, 0xc9, 0x70, 0x25, 0x8a, 0x27, 0xd8, 0x31, 0x1d, 0xab, 0x13, 0x56, 0x87, 0x15, 0xf1, 0x03, 0x82, 0x7d, 0xa6, 0x63, 0xc3, 0x89, 0xc6, 0xda, 0x06, 0xdf, 0x86, 0x2b, 0x23, 0x98, 0xf8, 0x5d, 0x78, 0x9b, 0x4c, 0xb4, 0x18, 0xae, 0xd6, 0x30, 0x65, 0x9d, 0xf2, 0x48, 0x38, 0x47, 0x02, 0x17, 0x72, 0x23, 0xc1, 0xc9, 0x11, 0x4d, 0xf7, 0xaa, 0xf2, 0x60, 0x78, 0x5e, 0xf8, 0x30, 0x31, 0xe7, 0xfe, 0xa4, 0xe7, 0x7d, 0x44, 0x79, 0x28, 0x3c, 0x2a, 0x7c, 0x84, 0x60, 0x1f, 0x33, 0x0c, 0x61, 0x65, 0x61, 0x78, 0x07, 0xbe, 0x53, 0x28, 0x23, 0x5c, 0x25, 0xa0, 0x7b, 0xf3, 0x61, 0x6f, 0x9f, 0x17, 0x47, 0xbf, 0x7d, 0xf6, 0x1b, 0xdf, 0x3e, 0x13, 0x1a, 0xf8, 0x2b, 0x77, 0x7d, 0xce, 0xf9, 0xdc, 0xa5, 0xae, 0x4a, 0xff, 0x05, 0x09, 0xfe, 0x42, 0x21, 0x98, 0x53, 0x08, 0xf2, 0xa2, 0xdc, 0x19, 0xde, 0x45, 0xd6, 0x22, 0x59, 0x4a, 0x8b, 0xc8, 0x90, 0x72, 0x0f, 0xc1, 0xc8, 0xb8, 0x49, 0xc9, 0x91, 0x71, 0x53, 0xb6, 0x85, 0xd7, 0x13, 0x6e, 0x40, 0x66, 0x69, 0x44, 0x86, 0x94, 0x97, 0xc3, 0xf7, 0x91, 0x35, 0x5c, 0x96, 0x3c, 0x11, 0x39, 0x50, 0xbe, 0x0e, 0xdf, 0x11, 0xbe, 0x9c, 0xb4, 0xf9, 0x23, 0xfd, 0x7b, 0x2d, 0xc2, 0x2c, 0xe5, 0x5f, 0xe2, 0x2b, 0x04, 0x6b, 0x30, 0x60, 0xf3, 0x08, 0x06, 0xdf, 0xd6, 0xa8, 0x63, 0xa5, 0x42, 0x77, 0x38, 0x55, 0xdc, 0x47, 0xb0, 0xc1, 0x91, 0xbe, 0x17, 0xba, 0x95, 0x5d, 0x14, 0x6b, 0xea, 0x2b, 0xbb, 0x62, 0xb3, 0x8e, 0xdd, 0x28, 0xcc, 0x0a, 0xdf, 0x45, 0xeb, 0x68, 0xd1, 0xb1, 0xa5, 0x04, 0x5b, 0x4d, 0xb1, 0x21, 0x3a, 0x36, 0x9f, 0x60, 0x07, 0xa5, 0xe3, 0x04, 0x6b, 0xd5, 0xb1, 0xeb, 0x84, 0x79, 0xe1, 0x0d, 0xb4, 0x2d, 0x41, 0x1d, 0x5b, 0x4c, 0xb0, 0x6b, 0x28, 0xd6, 0x66, 0xc8, 0x3b, 0x4f, 0xcd, 0x3b, 0x54, 0x9f, 0xd3, 0x7f, 0x17, 0x16, 0x85, 0xff, 0x41, 0xdb, 0x37, 0x2c, 0x06, 0x36, 0xdc, 0x3c, 0x8f, 0x68, 0xde, 0x11, 0xfa, 0xf7, 0x1e, 0x57, 0xae, 0x08, 0xbf, 0x1a, 0x3e, 0x40, 0xb0, 0xd1, 0xca, 0x02, 0x95, 0x8b, 0x3e, 0x48, 0xec, 0x89, 0x5f, 0x12, 0x19, 0xb2, 0x43, 0x3c, 0x5d, 0x1b, 0xcf, 0xb1, 0x00, 0x6a, 0x98, 0x90, 0x22, 0x42, 0x06, 0x38, 0x7a, 0xab, 0x9b, 0x5d, 0x1d, 0xb3, 0x23, 0xbb, 0x2f, 0x91, 0x3e, 0x5c, 0x04, 0x5d, 0x8e, 0x89, 0x65, 0x57, 0x95, 0x8d, 0x6b, 0xf9, 0x5f, 0xde, 0xaf, 0xfc, 0x44, 0xd9, 0x83, 0x7b, 0xf2, 0x53, 0x3f, 0x06, 0xe5, 0xcc, 0xff, 0x13, 0xa7, 0x6d, 0x4e, 0x7f, 0xed, 0x23, 0x53, 0xd9, 0x71, 0x50, 0xb6, 0x7d, 0x80, 0xb2, 0xe3, 0x50, 0x5c, 0x74, 0xd9, 0xc4, 0xa0, 0x70, 0x41, 0xe1, 0x4f, 0x2b, 0x87, 0x70, 0x4f, 0x5a, 0x91, 0xf5, 0x1d, 0xaa, 0xfa, 0x0f, 0x72, 0x63, 0x37, 0xa7, 0x27, 0x2b, 0xeb, 0xf1, 0x63, 0xe7, 0xaa, 0xfc, 0xc7, 0x94, 0x9f, 0xe2, 0x9e, 0xac, 0x32, 0xe1, 0x57, 0x50, 0x3e, 0xbe, 0x96, 0xdf, 0xb0, 0x39, 0x2f, 0x23, 0xf4, 0x1e, 0x04, 0xd6, 0x3d, 0xdb, 0xf2, 0x6b, 0x4d, 0xe5, 0x3f, 0xa4, 0xfc, 0x9c, 0x94, 0x5f, 0x2e, 0xbc, 0x05, 0xe5, 0x0f, 0x15, 0x5e, 0xdb, 0x9c, 0x9f, 0x19, 0xf2, 0xf3, 0x39, 0xb4, 0x7f, 0x16, 0x92, 0x75, 0xe7, 0x62, 0xaa, 0x9b, 0x08, 0xe3, 0x90, 0x44, 0x81, 0x17, 0xa0, 0x06, 0x8e, 0xbd, 0xff, 0xd1, 0xca, 0x86, 0x07, 0xa3, 0xf4, 0x19, 0x3f, 0xb4, 0xbc, 0xde, 0x53, 0xcb, 0x5d, 0x7c, 0xbf, 0xf2, 0xe4, 0xa5, 0x9f, 0xa9, 0x9d, 0x5d, 0xb2, 0xe4, 0xcd, 0x8f, 0xb4, 0xb2, 0x68, 0x5b, 0x9d, 0xc0, 0x6f, 0x1d, 0x10, 0xd8, 0x8f, 0x2c, 0x57, 0xd0, 0x5c, 0x88, 0xb1, 0x04, 0x11, 0x71, 0x4d, 0x0d, 0x76, 0x22, 0x67, 0xbe, 0xfa, 0xae, 0xcf, 0xef, 0x72, 0x72, 0x12, 0xdc, 0xfd, 0xa9, 0xc7, 0xf9, 0x30, 0x9c, 0x7f, 0xe5, 0x38, 0x4f, 0x66, 0x96, 0x47, 0xb9, 0xed, 0x52, 0xdc, 0x8c, 0x7d, 0x50, 0x8d, 0xf0, 0xd8, 0x1b, 0x98, 0x4b, 0x4c, 0xc4, 0x6d, 0x4b, 0x94, 0xbf, 0x87, 0x8a, 0x0d, 0x75, 0xd9, 0xe1, 0x3d, 0x3b, 0x04, 0x5d, 0xe3, 0xa9, 0x51, 0x00, 0xc6, 0x01, 0x7d, 0xa2, 0x69, 0x96, 0x9a, 0x24, 0x9f, 0xde, 0xf3, 0x3e, 0x5a, 0x8d, 0x8f, 0x76, 0xcc, 0x1f, 0x59, 0x1d, 0xd3, 0x31, 0x47, 0x3f, 0xe5, 0x55, 0x5a, 0xfe, 0x2f, 0xf1, 0x30, 0x53, 0xf9, 0xa4, 0x5f, 0x6c, 0xfd, 0xf4, 0x0b, 0x29, 0xd9, 0xd0, 0x2f, 0x38, 0x0b, 0x67, 0x63, 0x28, 0x37, 0x34, 0xf8, 0x52, 0xbc, 0x48, 0x10, 0x79, 0x2a, 0x8d, 0xdc, 0x6e, 0x6e, 0xd1, 0x12, 0xe5, 0x10, 0x27, 0xf2, 0xaf, 0x71, 0x79, 0xc8, 0xd4, 0x4f, 0xac, 0x6c, 0x49, 0x3c, 0x65, 0xd9, 0x56, 0x52, 0xb8, 0x36, 0x98, 0xdb, 0x95, 0x2d, 0xa4, 0xf4, 0x44, 0x0b, 0x15, 0x46, 0xbc, 0x82, 0x5b, 0x80, 0x1b, 0x49, 0xf1, 0x71, 0x6e, 0x65, 0x3d, 0xf7, 0x57, 0x43, 0xd9, 0x71, 0x50, 0xb6, 0xbd, 0x9f, 0xb2, 0x89, 0xac, 0x68, 0x65, 0xd7, 0x5a, 0x69, 0xc3, 0xd5, 0xc2, 0xb7, 0x2a, 0x0f, 0x90, 0xc2, 0xe3, 0x12, 0x38, 0x2a, 0x8a, 0xcd, 0xfc, 0x13, 0xb8, 0x96, 0x14, 0x6e, 0x49, 0x73, 0x86, 0xde, 0xe3, 0x9f, 0xa0, 0x6d, 0x9f, 0x71, 0xe6, 0xf2, 0xe2, 0x76, 0x81, 0xbc, 0x6c, 0xd9, 0xf0, 0xbe, 0x3a, 0x7d, 0x96, 0x8d, 0x56, 0x56, 0x91, 0xc9, 0x43, 0xcb, 0x3a, 0x33, 0x79, 0xc9, 0xd3, 0xe5, 0x85, 0xa7, 0x5b, 0x5e, 0x05, 0xe0, 0x4c, 0x17, 0x04, 0xe6, 0x37, 0xd6, 0xc2, 0xb4, 0xf4, 0x42, 0xab, 0x72, 0xf7, 0x06, 0xa2, 0xf8, 0x99, 0xc0, 0x14, 0x2b, 0xeb, 0x92, 0xd2, 0xd3, 0x93, 0xb9, 0xab, 0x47, 0x87, 0xbe, 0xc6, 0xdf, 0xd7, 0x23, 0x43, 0x7d, 0x0e, 0xa8, 0x0f, 0x1c, 0x4e, 0x0d, 0x58, 0x9f, 0x03, 0x39, 0xfc, 0x7a, 0x7d, 0x2c, 0x06, 0x29, 0xc8, 0x27, 0xf4, 0xd3, 0x9f, 0x3d, 0xf9, 0x6a, 0x6d, 0x41, 0x1c, 0x47, 0x45, 0x67, 0xde, 0x4f, 0xd3, 0x58, 0x5d, 0x6f, 0x7e, 0x8a, 0x17, 0x1a, 0xeb, 0x8a, 0x03, 0xf9, 0xb4, 0x5a, 0x06, 0x92, 0x4f, 0x32, 0x1a, 0xc9, 0x46, 0xf9, 0xa4, 0xf5, 0x54, 0x33, 0x01, 0x55, 0xab, 0x99, 0xfe, 0x05, 0xed, 0xbb, 0xb5, 0xac, 0x8e, 0x07, 0xb9, 0x1b, 0x90, 0xb1, 0x0e, 0x26, 0x47, 0x16, 0xf9, 0xd4, 0x32, 0x4a, 0x8b, 0xf6, 0xd0, 0xa2, 0x9f, 0x74, 0xca, 0x50, 0x72, 0xf7, 0x87, 0x54, 0x8e, 0xba, 0x71, 0x49, 0x7c, 0x02, 0x94, 0xbd, 0xf6, 0x18, 0x3f, 0xdf, 0x58, 0x76, 0x3c, 0x94, 0x1d, 0xd7, 0x4f, 0xd9, 0xf1, 0x28, 0x5e, 0x97, 0x23, 0x56, 0xb6, 0x26, 0xa5, 0xf7, 0x89, 0x02, 0x94, 0xbe, 0x48, 0xe2, 0xa9, 0x9c, 0xd6, 0x73, 0x8b, 0x24, 0x2b, 0x14, 0xbf, 0xda, 0x22, 0x2b, 0xeb, 0x05, 0xd5, 0x4e, 0x98, 0x76, 0xb6, 0xb2, 0x74, 0xd7, 0xe6, 0x77, 0x55, 0x55, 0x79, 0xa0, 0x36, 0xf4, 0x33, 0xf0, 0x0f, 0x04, 0x65, 0x9d, 0x23, 0x59, 0xfa, 0x95, 0x50, 0x96, 0x95, 0x59, 0x2a, 0x91, 0x4a, 0x70, 0x01, 0x93, 0x25, 0xfe, 0xf1, 0xd0, 0x7b, 0x19, 0x79, 0xbe, 0x74, 0xfe, 0xc5, 0xda, 0xde, 0x93, 0xdc, 0xb7, 0x2d, 0xe8, 0x9c, 0xd5, 0x07, 0x7d, 0xf5, 0x8e, 0xb5, 0x28, 0x4d, 0xad, 0xaf, 0x16, 0xbb, 0xe9, 0x38, 0xff, 0x8f, 0xb2, 0x3e, 0x39, 0x9d, 0xd5, 0x77, 0x02, 0x7f, 0x84, 0x57, 0x18, 0xea, 0xfb, 0xc1, 0xb2, 0xfb, 0x71, 0x6a, 0xbe, 0x5a, 0xdb, 0x28, 0x8c, 0x68, 0x6d, 0xfe, 0xd7, 0xd4, 0xba, 0x1e, 0xfa, 0x80, 0x7b, 0xca, 0xf8, 0x6d, 0x3f, 0x50, 0x76, 0xd5, 0x6a, 0x2e, 0xfa, 0x1b, 0x1d, 0xab, 0x36, 0x56, 0xc7, 0xa5, 0xfc, 0x6b, 0xc8, 0x58, 0x87, 0x9d, 0xee, 0x1b, 0xc9, 0x3c, 0x3f, 0xb0, 0xe8, 0xd6, 0x9a, 0x44, 0x37, 0x89, 0x16, 0x7c, 0xd9, 0x1f, 0xa0, 0xe0, 0x22, 0x5c, 0xe2, 0xa4, 0x25, 0x77, 0x1c, 0x11, 0x1e, 0x67, 0x72, 0x35, 0xe5, 0x0c, 0xe5, 0xaa, 0x56, 0x93, 0xab, 0xcd, 0xf7, 0x9c, 0x60, 0x4b, 0x64, 0xa8, 0x30, 0x94, 0x02, 0x0b, 0x24, 0x2d, 0x4b, 0x1d, 0xe7, 0xfc, 0x60, 0xae, 0x23, 0x1e, 0xfa, 0x1d, 0xf6, 0x9f, 0x58, 0x7f, 0xc3, 0x06, 0xd4, 0xa9, 0x05, 0xea, 0x2d, 0xbe, 0x3c, 0x3b, 0xa7, 0x9c, 0x53, 0xee, 0xbc, 0x07, 0xe7, 0xa9, 0x02, 0x95, 0x13, 0xf2, 0x67, 0x06, 0x02, 0x99, 0xc2, 0xc9, 0xc2, 0x5e, 0x8e, 0xff, 0x2a, 0xf8, 0xc3, 0xeb, 0x71, 0xa9, 0x82, 0xab, 0xd6, 0x53, 0x81, 0x53, 0xe8, 0xd0, 0xce, 0x07, 0xc1, 0xa5, 0xf5, 0x9c, 0x7c, 0x9f, 0xfb, 0x1d, 0x5e, 0xa5, 0xf7, 0xcf, 0x0f, 0xad, 0x0b, 0x84, 0x56, 0xad, 0xab, 0x19, 0x5b, 0xe9, 0x00, 0x3f, 0x0a, 0x42, 0xcb, 0xea, 0xda, 0x85, 0xdf, 0xe4, 0x0e, 0x9a, 0xeb, 0x72, 0x40, 0x5d, 0xf1, 0x71, 0x36, 0x6b, 0xec, 0xba, 0x8c, 0x02, 0x5b, 0xab, 0x0b, 0x6c, 0xbd, 0x2a, 0xb0, 0x6a, 0x4d, 0xe3, 0xbe, 0x81, 0x8a, 0x3e, 0x7c, 0x4d, 0xad, 0x65, 0xf1, 0xbb, 0xfc, 0xe7, 0xa6, 0x3a, 0x6c, 0xe0, 0xe7, 0x89, 0xc6, 0x62, 0xa5, 0xfe, 0x2a, 0xd5, 0x39, 0x81, 0xa9, 0x32, 0x53, 0xeb, 0xb1, 0x21, 0x5b, 0xb2, 0x4f, 0x75, 0x26, 0x10, 0x11, 0x56, 0x65, 0xa8, 0x5a, 0xc3, 0x2c, 0xba, 0x64, 0xb3, 0xe2, 0xcb, 0x44, 0x2b, 0xf5, 0x9f, 0x7a, 0x86, 0x3a, 0x4a, 0x2e, 0xc0, 0xc0, 0x8f, 0x3e, 0x5b, 0x80, 0xa7, 0x30, 0xe2, 0xf2, 0xe6, 0x12, 0x5c, 0x52, 0x8c, 0x98, 0x2f, 0x56, 0x4d, 0xe6, 0x03, 0x41, 0x3f, 0x38, 0x27, 0xd5, 0xb8, 0x5c, 0xd4, 0xec, 0xb5, 0x23, 0x8b, 0x4b, 0xef, 0x78, 0x1f, 0xd1, 0x42, 0x75, 0xd5, 0x3e, 0xca, 0x72, 0x15, 0x65, 0xc7, 0x02, 0xbc, 0xc4, 0x93, 0xc5, 0xa4, 0x48, 0xb4, 0x28, 0xbf, 0x5c, 0x82, 0xdb, 0x12, 0xb1, 0xb1, 0xec, 0x78, 0xe8, 0xe7, 0x38, 0x68, 0xa8, 0x08, 0xc5, 0xb3, 0x6d, 0x58, 0x93, 0xc6, 0x0e, 0xe8, 0x45, 0xe7, 0xd3, 0xa2, 0x35, 0x0a, 0xfd, 0x47, 0xe5, 0x18, 0x2d, 0x5c, 0x67, 0xd1, 0x21, 0xe5, 0xef, 0x50, 0x3c, 0x30, 0xe9, 0x48, 0xf9, 0x6c, 0x1c, 0xfb, 0x2d, 0x9f, 0x8c, 0x63, 0xa4, 0x7c, 0x2b, 0x2b, 0x5f, 0x5b, 0x13, 0x5e, 0x57, 0x7e, 0x4b, 0x2b, 0x30, 0x30, 0xe9, 0x6c, 0x45, 0xa1, 0x55, 0xa8, 0x74, 0xfa, 0x2c, 0xeb, 0xc1, 0xa2, 0xa9, 0x9e, 0x97, 0x95, 0x4f, 0x68, 0x3d, 0x06, 0x46, 0x3d, 0xf1, 0x1b, 0xa8, 0x46, 0x63, 0xd5, 0x67, 0x59, 0x4f, 0x6d, 0x54, 0x3d, 0xfb, 0x95, 0x7f, 0xb0, 0x7a, 0x74, 0x66, 0x7d, 0xd3, 0xe7, 0x50, 0x8d, 0xca, 0xae, 0x23, 0xdc, 0x3d, 0x1e, 0xf4, 0x68, 0x1c, 0x3f, 0x90, 0x1e, 0x85, 0x95, 0x34, 0x5f, 0xd7, 0xa3, 0xc0, 0x86, 0xc1, 0x8f, 0x86, 0xce, 0x85, 0x95, 0xdb, 0xf0, 0x44, 0x1c, 0x8c, 0x10, 0xe1, 0xcd, 0x38, 0xad, 0x38, 0xda, 0x76, 0x22, 0x75, 0x68, 0xb6, 0x93, 0x1a, 0x77, 0xb8, 0x2f, 0x0f, 0xee, 0x6b, 0x3d, 0xb9, 0xa2, 0xad, 0x27, 0x6e, 0x37, 0xb5, 0x9e, 0x34, 0xae, 0xfa, 0xa0, 0x71, 0x2d, 0xb0, 0x0f, 0x58, 0xfe, 0xe9, 0x5a, 0x50, 0x84, 0xb4, 0x32, 0x0b, 0x8a, 0x10, 0xd6, 0x1f, 0x54, 0x47, 0x6d, 0xff, 0x56, 0x54, 0xb3, 0x6a, 0x44, 0x11, 0xd6, 0x7a, 0x76, 0x36, 0x8e, 0xcb, 0x60, 0xe3, 0x90, 0x2e, 0x21, 0x36, 0x0e, 0xf4, 0x48, 0x84, 0x5f, 0x9f, 0xf6, 0x3a, 0x1c, 0xa5, 0x42, 0x4d, 0x94, 0x95, 0x99, 0x1f, 0x44, 0xa8, 0x22, 0x8c, 0x95, 0x9a, 0x20, 0x0a, 0x97, 0x53, 0x6f, 0xa8, 0x8b, 0x7a, 0x69, 0x25, 0x55, 0x59, 0x07, 0xac, 0x8b, 0x88, 0x90, 0x79, 0xcd, 0x37, 0xd2, 0x55, 0x93, 0x31, 0x05, 0x6c, 0x55, 0x35, 0xa5, 0x08, 0x59, 0xfd, 0x8f, 0xd8, 0x52, 0xdc, 0x5a, 0x66, 0x4b, 0x11, 0xa6, 0x7a, 0x0e, 0x6d, 0x29, 0xdc, 0xad, 0xdb, 0x52, 0xc0, 0x52, 0xcf, 0xc6, 0x96, 0xaa, 0xed, 0xd7, 0x96, 0xaa, 0x37, 0x98, 0x52, 0xc0, 0x50, 0x7f, 0xb8, 0xad, 0x43, 0x04, 0x1d, 0x6c, 0x1d, 0x6a, 0x97, 0xcd, 0x38, 0xa7, 0xfc, 0x34, 0xbd, 0xc8, 0x02, 0xf4, 0x3a, 0x03, 0x0f, 0x8a, 0xf0, 0x53, 0x6f, 0x32, 0xb7, 0x62, 0xb4, 0xe2, 0xe0, 0x33, 0x5b, 0xd0, 0x39, 0xab, 0x4f, 0x5b, 0xea, 0xd5, 0xfa, 0x54, 0xdb, 0x8a, 0xf1, 0x53, 0x5a, 0x1f, 0xd8, 0x56, 0x94, 0x9f, 0xce, 0x38, 0x87, 0xfc, 0x54, 0xad, 0x4d, 0xb5, 0xad, 0x80, 0x9f, 0xb2, 0xba, 0xde, 0xfc, 0x94, 0xf1, 0xd3, 0x73, 0x65, 0x5b, 0xb1, 0x6a, 0x98, 0x6d, 0x85, 0xdb, 0x58, 0x1d, 0x0f, 0xaa, 0xfc, 0xf4, 0x4c, 0x6d, 0x2b, 0x33, 0x41, 0x4d, 0x90, 0x22, 0xb6, 0x15, 0x21, 0xa8, 0xf1, 0x2e, 0x28, 0x7a, 0xed, 0x31, 0x4a, 0x50, 0xcf, 0xdc, 0xee, 0xa9, 0x8d, 0xb2, 0x7b, 0x9a, 0xa9, 0xd9, 0x03, 0x06, 0xb9, 0xd1, 0x0e, 0xa1, 0x5c, 0x0e, 0xc6, 0xf9, 0x6c, 0xf8, 0xa9, 0xca, 0xd5, 0x33, 0x70, 0x69, 0x84, 0x9f, 0x52, 0xc2, 0x1e, 0xf2, 0x08, 0xde, 0xe0, 0x0f, 0xaf, 0xc7, 0x15, 0xd3, 0xb0, 0x62, 0xfc, 0x54, 0x37, 0xac, 0x34, 0x7e, 0x7a, 0x0e, 0xea, 0xea, 0x6b, 0x54, 0x31, 0x7e, 0xaa, 0x1b, 0x55, 0x1a, 0x3f, 0x9d, 0x66, 0xe6, 0xa7, 0xfd, 0x71, 0xe1, 0xd8, 0xfc, 0x34, 0xb6, 0x41, 0xf5, 0x61, 0xc4, 0x9e, 0x02, 0x82, 0x1a, 0xa9, 0xc3, 0x06, 0xbc, 0x4f, 0xa6, 0x21, 0xf9, 0xce, 0x88, 0x9c, 0x9a, 0x0c, 0x29, 0xd5, 0x8c, 0x12, 0xad, 0xe8, 0xec, 0xf6, 0x63, 0x78, 0xc6, 0x4f, 0xdf, 0xdf, 0x80, 0xe7, 0xab, 0xab, 0xce, 0xaa, 0xd1, 0xdc, 0xf2, 0x7a, 0xf3, 0xbc, 0x1a, 0x98, 0x9f, 0xc6, 0x21, 0x6b, 0x84, 0x9f, 0xfa, 0xc9, 0x60, 0x0e, 0xc1, 0x8c, 0xa0, 0x7e, 0xa1, 0xec, 0xd8, 0x80, 0x2f, 0xb3, 0x14, 0xa5, 0x6b, 0x04, 0x35, 0xf4, 0xe0, 0x68, 0x6e, 0x45, 0xb2, 0x17, 0x23, 0x64, 0xde, 0x7f, 0x39, 0x15, 0xe7, 0x2a, 0x30, 0x71, 0x54, 0xb8, 0xed, 0xa6, 0x92, 0xd4, 0xaf, 0x94, 0x43, 0xac, 0x06, 0x9d, 0xa5, 0xfe, 0x2d, 0xf4, 0x4b, 0x5a, 0x07, 0xdd, 0xf0, 0x35, 0xea, 0x3d, 0x90, 0x9d, 0x7e, 0xeb, 0x20, 0xb2, 0x53, 0x60, 0xe2, 0xa9, 0xec, 0x46, 0x1d, 0x5b, 0x16, 0xfe, 0xa8, 0x9c, 0x60, 0x95, 0x44, 0x98, 0x2a, 0xfe, 0x2e, 0xf4, 0x35, 0xab, 0x46, 0xa3, 0xaa, 0xe7, 0xa8, 0xae, 0x5f, 0x2a, 0x1f, 0xb3, 0xba, 0x0c, 0x6c, 0x35, 0x53, 0x71, 0xd0, 0xba, 0x8c, 0x7c, 0xf5, 0x2c, 0xea, 0xaa, 0x8d, 0xae, 0xeb, 0x35, 0xe5, 0x33, 0xb5, 0x2e, 0x9d, 0xb1, 0x8e, 0x56, 0xb2, 0x69, 0x55, 0x3a, 0x67, 0x3d, 0x1b, 0x7b, 0xc7, 0x65, 0xb0, 0x77, 0xf8, 0x79, 0x60, 0xef, 0xb0, 0x3d, 0xb0, 0x45, 0xa6, 0xbd, 0xc8, 0x7e, 0x74, 0xa8, 0x61, 0x2f, 0x92, 0xf0, 0x5e, 0x0c, 0x52, 0xaf, 0xd2, 0x5e, 0xe5, 0x36, 0x42, 0xaf, 0x45, 0x3c, 0x51, 0xa7, 0xbd, 0xca, 0x2b, 0x84, 0x5e, 0x0b, 0xb8, 0xa0, 0xd8, 0x64, 0x4b, 0x91, 0x35, 0x40, 0x93, 0xd5, 0x7e, 0x79, 0x6f, 0x9e, 0x2b, 0xb2, 0x06, 0x30, 0x7b, 0xca, 0xd5, 0xd7, 0x9e, 0xe2, 0x5f, 0x55, 0xed, 0xa9, 0x61, 0x26, 0x7b, 0x0a, 0xb8, 0x97, 0xde, 0xdf, 0xfd, 0x73, 0xaf, 0x18, 0x66, 0x95, 0x2b, 0x86, 0x59, 0xc5, 0xed, 0xd6, 0xcd, 0x2a, 0x42, 0x28, 0x8d, 0x76, 0x08, 0xac, 0x9b, 0xa7, 0xaa, 0xe7, 0x4c, 0xcc, 0x2b, 0xc2, 0x3e, 0x0c, 0xe6, 0x95, 0xc6, 0xb5, 0xcf, 0xbe, 0xbe, 0xda, 0x53, 0x98, 0x59, 0xcd, 0x06, 0x2b, 0x8b, 0xad, 0x4d, 0xd1, 0xbc, 0x7e, 0xe0, 0x75, 0xda, 0x95, 0xa7, 0x8f, 0x11, 0x28, 0x73, 0xcd, 0xfe, 0x01, 0x9e, 0x4c, 0xcc, 0x9f, 0xa0, 0x2a, 0x07, 0x94, 0x24, 0x6f, 0xe6, 0xc6, 0xd5, 0xc7, 0xb0, 0x7d, 0x06, 0xe6, 0xad, 0xe0, 0xfe, 0x4f, 0x2d, 0x5f, 0x66, 0xe6, 0x95, 0xca, 0x8d, 0x23, 0xc6, 0x15, 0x10, 0x63, 0x62, 0x5a, 0x81, 0xf4, 0xfe, 0x47, 0x6c, 0x2b, 0xdc, 0x4d, 0x6d, 0x2b, 0x8d, 0xbb, 0x9e, 0x1b, 0xbb, 0xc7, 0x64, 0x5b, 0xd5, 0xab, 0xa6, 0x15, 0x21, 0xb0, 0x3f, 0xdc, 0xee, 0x81, 0x5d, 0xe1, 0x25, 0x6f, 0xb2, 0xfd, 0xe0, 0x73, 0x60, 0xf7, 0x88, 0x3a, 0x7d, 0x65, 0xa6, 0x42, 0x3a, 0x1e, 0x16, 0x61, 0xaf, 0x60, 0x2e, 0x7c, 0xc3, 0x4f, 0x6c, 0x39, 0x67, 0x36, 0x96, 0xc6, 0x02, 0x4c, 0x36, 0x16, 0x63, 0xae, 0x9a, 0x8d, 0x45, 0x88, 0xab, 0xc9, 0x86, 0xf8, 0xa1, 0x76, 0x16, 0x61, 0x02, 0x26, 0x3b, 0x0b, 0x98, 0xab, 0x6a, 0x67, 0x11, 0xe2, 0xfa, 0x1f, 0xb1, 0xb3, 0x40, 0x5d, 0x81, 0x9d, 0x45, 0x58, 0x6b, 0xa4, 0x7c, 0x2b, 0x8c, 0xb7, 0xa5, 0x9f, 0xf1, 0xb6, 0x92, 0xc5, 0x3b, 0xc2, 0x2b, 0x0d, 0x56, 0xd6, 0x32, 0xee, 0xaf, 0x94, 0xae, 0x82, 0x89, 0xf5, 0x12, 0x7e, 0x43, 0xe5, 0xaa, 0x33, 0xce, 0x96, 0xab, 0xaa, 0x36, 0x50, 0x3d, 0x35, 0x81, 0x74, 0x7b, 0xea, 0x5c, 0x70, 0x55, 0x95, 0xb7, 0x27, 0xe2, 0xc1, 0x11, 0xae, 0xca, 0x8c, 0x9f, 0x1c, 0xa1, 0x23, 0xf8, 0xc3, 0xeb, 0x71, 0xc5, 0x34, 0xb2, 0x18, 0x57, 0xd5, 0x8d, 0x2c, 0x8d, 0xab, 0xce, 0x38, 0x37, 0x5c, 0xd5, 0x6c, 0x60, 0x31, 0xae, 0xaa, 0x1b, 0x58, 0x1a, 0x57, 0x9d, 0x71, 0x2e, 0xb8, 0xaa, 0xd9, 0xb8, 0xfa, 0x30, 0x62, 0x5b, 0x31, 0xae, 0x3a, 0xe3, 0x07, 0x72, 0x55, 0x93, 0x51, 0xa5, 0x9a, 0x54, 0x1a, 0x57, 0x9d, 0x76, 0x96, 0x5c, 0xf5, 0xdd, 0xcd, 0xb8, 0x5b, 0xd5, 0x14, 0x3f, 0xab, 0xe5, 0x0f, 0xb4, 0x98, 0xcf, 0x28, 0xce, 0x92, 0xab, 0xfe, 0x4d, 0x79, 0x88, 0x14, 0x2b, 0x95, 0x66, 0x6a, 0x5c, 0xb5, 0xf7, 0xd2, 0x5a, 0xfe, 0xc5, 0x74, 0x1f, 0x8e, 0x65, 0x17, 0x9c, 0x0d, 0x57, 0x0d, 0x23, 0xe5, 0x05, 0x56, 0x83, 0xce, 0x55, 0x3f, 0xe8, 0x7d, 0x88, 0xd6, 0xa1, 0x72, 0xd5, 0x69, 0xe7, 0x80, 0x3f, 0x7e, 0xa6, 0x1c, 0x67, 0x95, 0x18, 0xb8, 0xea, 0x87, 0xbd, 0x27, 0x58, 0x35, 0x06, 0xae, 0x3a, 0xed, 0x9c, 0xf0, 0xe2, 0xdf, 0xb2, 0xba, 0x22, 0x5c, 0x95, 0xfb, 0xb6, 0xf7, 0x24, 0xad, 0xcb, 0xc8, 0x55, 0xa7, 0x9d, 0x03, 0xae, 0xfa, 0xb6, 0xf2, 0xb1, 0x5a, 0x97, 0xce, 0x55, 0xd3, 0x43, 0x29, 0xb4, 0x2a, 0x9d, 0xab, 0x9e, 0x8d, 0xed, 0xe3, 0x32, 0xd8, 0x3e, 0x64, 0x35, 0x00, 0xdb, 0xc7, 0xb4, 0x7f, 0x71, 0xda, 0x67, 0xf2, 0x32, 0xac, 0x29, 0x8c, 0xab, 0x7e, 0xa8, 0xdc, 0x45, 0x78, 0xb5, 0x9c, 0xa4, 0x51, 0xd5, 0x63, 0xa1, 0xb5, 0x84, 0x54, 0xbb, 0x9d, 0xc0, 0x54, 0xfb, 0xec, 0x57, 0x0c, 0xcc, 0x55, 0xc9, 0x3a, 0x9f, 0x1f, 0xe1, 0xaa, 0x9a, 0xbc, 0xba, 0xfa, 0xda, 0x56, 0xfc, 0xab, 0x9a, 0x6d, 0x35, 0x0c, 0xf5, 0x3d, 0xdb, 0x3e, 0x0d, 0x5e, 0x17, 0x4b, 0x6c, 0x5d, 0xb1, 0x4c, 0x2c, 0x6e, 0x77, 0xc4, 0xc4, 0x62, 0x7b, 0xa0, 0xe6, 0xbd, 0xa5, 0x53, 0xd5, 0x75, 0x66, 0xa6, 0x16, 0x5e, 0x61, 0x32, 0xb5, 0x62, 0xec, 0x9d, 0x9d, 0x49, 0x7d, 0xb5, 0xa7, 0x34, 0xb7, 0x9a, 0x4d, 0xd6, 0x16, 0xe3, 0xad, 0x3f, 0xd4, 0x06, 0xe2, 0xfc, 0x60, 0x03, 0xb1, 0xbd, 0xaa, 0x45, 0x67, 0x7a, 0x46, 0xcf, 0x4b, 0x44, 0xb2, 0xd4, 0x2d, 0x62, 0xd9, 0xa5, 0xdc, 0xb1, 0x00, 0xcf, 0xc3, 0x17, 0x6b, 0xd4, 0x37, 0xc1, 0xa1, 0x1c, 0x5a, 0x82, 0x1b, 0xb9, 0x4b, 0xeb, 0xcf, 0xca, 0xbe, 0x92, 0xa1, 0x6c, 0xa2, 0xaa, 0x29, 0xf1, 0x95, 0x04, 0x28, 0x5c, 0x33, 0xb0, 0x08, 0xf5, 0x95, 0x78, 0x28, 0x1c, 0x0c, 0x2c, 0x4a, 0x80, 0xff, 0x23, 0x36, 0x16, 0xb7, 0x96, 0xd9, 0x58, 0x74, 0x5f, 0xf8, 0x3f, 0x65, 0x63, 0xe1, 0x6e, 0xdd, 0xc6, 0x22, 0xe4, 0xf5, 0x07, 0xd9, 0x3c, 0xa7, 0xb0, 0xb1, 0xea, 0x8d, 0x26, 0x16, 0xe5, 0xdc, 0xe7, 0xc4, 0xe6, 0x01, 0xf6, 0x41, 0xac, 0x92, 0x06, 0xdc, 0x15, 0x21, 0xc8, 0x9b, 0xf9, 0x0d, 0x2d, 0xe7, 0xa2, 0x7c, 0x8d, 0x71, 0x44, 0x6c, 0x2a, 0x46, 0x8a, 0x89, 0x4d, 0xa5, 0xf1, 0xe1, 0x73, 0x61, 0x57, 0x11, 0xa6, 0x11, 0xb1, 0xab, 0x80, 0x08, 0x13, 0xbb, 0x0a, 0x66, 0x04, 0x8a, 0x71, 0x2e, 0x36, 0x80, 0x5c, 0x91, 0x41, 0x8f, 0xb6, 0x7b, 0xb2, 0xf9, 0x88, 0x5d, 0x95, 0xe9, 0xe1, 0x54, 0xd6, 0x4a, 0x0c, 0xab, 0x44, 0x8e, 0xb2, 0xd6, 0x33, 0xb7, 0x7b, 0x6a, 0xa3, 0xec, 0x9e, 0x22, 0x30, 0x7b, 0x54, 0xfe, 0xbb, 0xf0, 0xb4, 0x39, 0x57, 0x7f, 0x06, 0x0f, 0x50, 0x56, 0xc6, 0xd9, 0x13, 0x71, 0x67, 0x84, 0xb1, 0x02, 0x6f, 0xff, 0xa7, 0xb0, 0x2e, 0xf8, 0x83, 0xeb, 0x70, 0xc5, 0x32, 0xaa, 0x18, 0x5b, 0x55, 0x8d, 0x2a, 0x8d, 0xab, 0xfe, 0x90, 0x7a, 0xfa, 0x31, 0xa8, 0x18, 0x53, 0xd5, 0x0c, 0x2a, 0xee, 0x60, 0x94, 0x3d, 0x05, 0x67, 0xd1, 0xf6, 0xfe, 0xce, 0xfc, 0x8d, 0x86, 0x54, 0xed, 0x80, 0x86, 0xd4, 0x87, 0xba, 0x1d, 0x45, 0x48, 0xaa, 0xd1, 0xce, 0x61, 0xdc, 0xaf, 0x3f, 0x8e, 0x4a, 0x4c, 0x9d, 0x24, 0x9d, 0xa3, 0x6a, 0x16, 0x94, 0x32, 0xd4, 0x64, 0x40, 0x31, 0xf3, 0x09, 0x18, 0xea, 0x99, 0xdf, 0x1b, 0xd1, 0xf8, 0xe9, 0x89, 0x7b, 0xf0, 0x22, 0x75, 0x60, 0x53, 0x0a, 0x05, 0x25, 0x68, 0xbc, 0x93, 0x10, 0x07, 0x7d, 0x20, 0xe1, 0x33, 0xa6, 0xa7, 0x1f, 0x2b, 0x0f, 0xdc, 0x83, 0x17, 0x12, 0xd1, 0xd1, 0xe8, 0xe9, 0xc9, 0xf2, 0x42, 0xe1, 0x64, 0x66, 0x3e, 0x36, 0xdd, 0xa9, 0xc8, 0x0b, 0xfa, 0xe2, 0x25, 0xb5, 0x9d, 0x03, 0x52, 0xd3, 0x02, 0x13, 0x35, 0xfd, 0x46, 0x79, 0x86, 0x15, 0xae, 0x33, 0xd3, 0x77, 0x4f, 0x2e, 0xa1, 0xc5, 0xb3, 0x6b, 0xb3, 0xa6, 0x3b, 0x22, 0xa4, 0x0e, 0x47, 0x7f, 0x75, 0x0c, 0xb8, 0xb6, 0x7f, 0xa3, 0x1c, 0x63, 0xb5, 0x18, 0xa8, 0xe9, 0x9b, 0x27, 0x77, 0xb1, 0x7a, 0x18, 0x35, 0x3d, 0x27, 0xf5, 0xfc, 0x4d, 0x79, 0x8b, 0xd5, 0x63, 0xa0, 0xa5, 0xbf, 0x3b, 0xf9, 0x7b, 0x5a, 0x8f, 0x4a, 0x4b, 0xcf, 0xa6, 0x9e, 0xda, 0xbe, 0x5c, 0xe5, 0x7d, 0xb5, 0x1e, 0x8d, 0x92, 0xf2, 0xff, 0xea, 0xe5, 0x68, 0x35, 0xda, 0x85, 0xda, 0xb3, 0xb3, 0x71, 0x5c, 0x06, 0x1b, 0x87, 0x4c, 0x5c, 0xb0, 0x71, 0xc8, 0x94, 0x35, 0xda, 0x38, 0xbe, 0x60, 0x96, 0x5d, 0x8a, 0x79, 0x47, 0xca, 0xb4, 0xbc, 0xd3, 0x06, 0x33, 0x4e, 0xfa, 0x07, 0xe5, 0x6e, 0x52, 0x22, 0xef, 0xb2, 0x6b, 0xa4, 0xf4, 0x48, 0x6f, 0x07, 0x61, 0xcf, 0x0e, 0x8f, 0x85, 0xed, 0x9f, 0x4e, 0x3b, 0x17, 0x9c, 0x34, 0xca, 0x86, 0xe2, 0x5f, 0xd5, 0x6c, 0xa8, 0x61, 0x51, 0x36, 0xd4, 0x0f, 0xe4, 0xa4, 0x7d, 0x4c, 0x29, 0x6e, 0x77, 0xc4, 0x94, 0x62, 0x9c, 0x74, 0xda, 0xb9, 0xe3, 0xa4, 0x7d, 0x4c, 0x2a, 0xc2, 0x49, 0x8d, 0x26, 0x15, 0xe5, 0xa4, 0xd3, 0xce, 0x1d, 0x27, 0xed, 0x63, 0x56, 0x35, 0x9b, 0xac, 0x2a, 0xc6, 0x49, 0x7f, 0xa8, 0xad, 0x43, 0x14, 0x35, 0xd8, 0x3a, 0x44, 0x43, 0x9f, 0xf9, 0xdd, 0x4e, 0xc6, 0x49, 0x35, 0x6b, 0xe7, 0x37, 0x3c, 0x21, 0x8e, 0x84, 0x44, 0xf3, 0x32, 0x9e, 0xad, 0xf1, 0x52, 0x8b, 0x18, 0xba, 0x8e, 0x90, 0x68, 0x8b, 0x95, 0x5b, 0x54, 0x8f, 0x62, 0x9c, 0x9f, 0x9e, 0xbe, 0x3d, 0xe5, 0xea, 0x63, 0x4f, 0xf1, 0xf3, 0x22, 0xf6, 0x54, 0x8c, 0xbb, 0xb5, 0xe7, 0xc8, 0x9e, 0xe2, 0xd6, 0xaa, 0xf6, 0x54, 0xd4, 0xdd, 0xda, 0x73, 0x6f, 0x4f, 0xe1, 0xee, 0x88, 0x3d, 0x65, 0xbe, 0x6b, 0x7b, 0x4e, 0x64, 0x29, 0xca, 0x9e, 0xaa, 0x37, 0x99, 0x53, 0x6c, 0x1f, 0xee, 0x4c, 0xed, 0x9b, 0xda, 0x28, 0xfb, 0xe6, 0x43, 0x30, 0x6f, 0xd4, 0xbb, 0x77, 0x66, 0x1b, 0xc4, 0x6a, 0x39, 0xa5, 0x0d, 0x42, 0x08, 0x85, 0x6e, 0xe0, 0xfc, 0x8a, 0x4b, 0x88, 0x53, 0x36, 0x2f, 0xc0, 0x17, 0xe3, 0x79, 0x1a, 0xd1, 0x75, 0xa6, 0x59, 0xc0, 0x0a, 0xa9, 0xe5, 0x9f, 0x68, 0x41, 0x3f, 0xc4, 0x7e, 0xa2, 0x6c, 0xc5, 0x92, 0x08, 0xa5, 0xab, 0xf6, 0x13, 0xd0, 0x5c, 0x77, 0x9c, 0x6a, 0x3f, 0x51, 0x9b, 0x7f, 0x91, 0x69, 0x1e, 0xf4, 0xd3, 0x76, 0xe3, 0x3c, 0x90, 0x69, 0xdb, 0x55, 0x03, 0xea, 0x63, 0x5e, 0x14, 0xa0, 0xf8, 0x25, 0x3c, 0x9e, 0xac, 0x92, 0x5c, 0x5e, 0xe4, 0xa8, 0x09, 0xc5, 0xe1, 0x22, 0x8d, 0xeb, 0xfe, 0x07, 0x6c, 0x28, 0xdc, 0xc6, 0x6c, 0x28, 0xba, 0xe7, 0xbb, 0xe8, 0x4c, 0x65, 0x27, 0x62, 0xdb, 0x68, 0x36, 0x14, 0x6f, 0xb2, 0xa1, 0x22, 0x94, 0x5a, 0x35, 0xa2, 0x54, 0x5a, 0x1d, 0xe3, 0x2d, 0x0a, 0xd8, 0x05, 0xea, 0x37, 0xc0, 0x93, 0xe9, 0x08, 0xb3, 0xeb, 0xdf, 0xb2, 0x01, 0x32, 0x4c, 0xec, 0x82, 0x6a, 0x7c, 0x51, 0x84, 0x09, 0x6f, 0x16, 0x7e, 0x16, 0x44, 0xe7, 0xa0, 0x7c, 0x57, 0x1f, 0xcb, 0x89, 0xb1, 0x60, 0x62, 0x39, 0x01, 0x03, 0x3e, 0x27, 0xe5, 0x9b, 0x2d, 0x27, 0xc6, 0x7e, 0x89, 0xe5, 0x04, 0xc4, 0xb7, 0xef, 0x3b, 0xa6, 0x7e, 0xcb, 0x37, 0x59, 0x4d, 0xb5, 0xfd, 0x59, 0x4d, 0x1f, 0x52, 0xa3, 0x09, 0xe6, 0x19, 0xbc, 0x9f, 0x0b, 0xa3, 0x70, 0x8e, 0x74, 0x21, 0x66, 0xef, 0xe7, 0x66, 0xb0, 0xe8, 0xd6, 0x33, 0xe0, 0xe5, 0x25, 0x0e, 0xfd, 0x23, 0xbc, 0x83, 0x03, 0x7f, 0xc3, 0x56, 0xe6, 0x03, 0x59, 0x0f, 0x0b, 0xca, 0x1e, 0xc3, 0x89, 0xfa, 0x63, 0x38, 0xe5, 0xcd, 0xd4, 0xd6, 0xfc, 0x79, 0xfc, 0xb8, 0xf8, 0x9a, 0xbc, 0xa5, 0x09, 0x25, 0x39, 0xe2, 0x9f, 0x53, 0xf2, 0x68, 0xd9, 0xea, 0xfb, 0x3a, 0x4e, 0x92, 0x46, 0xd0, 0x77, 0x5b, 0x6a, 0x79, 0xe4, 0xef, 0x7f, 0xf9, 0x4f, 0xbc, 0xeb, 0xc4, 0x19, 0xc2, 0x46, 0xee, 0xb0, 0xb8, 0x8b, 0x93, 0x10, 0x8b, 0x21, 0x90, 0xcd, 0x6d, 0xe3, 0x0a, 0xf9, 0x5f, 0xf2, 0xa4, 0x2c, 0x84, 0x98, 0xaf, 0x38, 0x6c, 0xf0, 0x15, 0x87, 0x33, 0xb9, 0x6d, 0x82, 0x00, 0xbe, 0xe4, 0xd0, 0xa1, 0x58, 0xbe, 0xe4, 0x14, 0x41, 0x59, 0xa9, 0xbc, 0x19, 0x7e, 0x08, 0xa2, 0x12, 0x0c, 0xf8, 0x36, 0x2e, 0xd6, 0x1b, 0xba, 0x18, 0x6f, 0xed, 0x62, 0xbd, 0xc9, 0x8b, 0xf9, 0x76, 0xaf, 0xcf, 0x1b, 0x3f, 0x32, 0x16, 0x56, 0x6e, 0x7f, 0xf8, 0xe7, 0xfc, 0x03, 0xc8, 0x8b, 0xaa, 0x82, 0x15, 0x12, 0x3c, 0xf1, 0x47, 0xd3, 0xfa, 0x3e, 0xf5, 0xa7, 0xb1, 0x36, 0xfb, 0xbe, 0xf4, 0xef, 0x2f, 0x00, 0xa6, 0xf2, 0x89, 0xf6, 0x70, 0xb7, 0x68, 0x54, 0x6b, 0x7d, 0x9a, 0xf6, 0x6e, 0xb7, 0x78, 0x64, 0x6b, 0x9d, 0xf6, 0x6e, 0x37, 0xc3, 0x9f, 0xc1, 0xde, 0xec, 0x92, 0x3f, 0xa8, 0xfe, 0x03, 0x85, 0x5c, 0xe9, 0x06, 0x2e, 0x80, 0x96, 0x80, 0x32, 0x17, 0x1f, 0x42, 0xf7, 0xd2, 0x39, 0x56, 0x85, 0x8a, 0xb9, 0x77, 0x71, 0x1d, 0xe9, 0xab, 0x46, 0xf6, 0xf4, 0xda, 0x89, 0x98, 0x07, 0x80, 0x59, 0x1c, 0x13, 0x4f, 0x2f, 0xb8, 0xe2, 0xa6, 0x32, 0xc4, 0x63, 0x4c, 0x23, 0xbf, 0xc1, 0xb3, 0x73, 0x1e, 0x1b, 0xe2, 0x5b, 0xe6, 0x43, 0x3c, 0x0a, 0x08, 0xbc, 0xf8, 0xee, 0x0d, 0xca, 0xdc, 0x1b, 0xb8, 0x47, 0x8b, 0xb7, 0x6c, 0xc1, 0x71, 0xff, 0xc9, 0xb2, 0xff, 0x1f, 0x78, 0xd7, 0x18, 0x91, 0x85, 0xf6, 0xfe, 0xdf, 0x3a, 0x92, 0xbc, 0x4d, 0xe2, 0xd6, 0xf0, 0x7e, 0xf9, 0x65, 0x74, 0x09, 0x7a, 0xb6, 0xf7, 0x5f, 0xf8, 0xbc, 0x96, 0xe9, 0xb5, 0x5e, 0xf2, 0xe7, 0xc3, 0xa1, 0x0b, 0xb8, 0x41, 0xe5, 0x2f, 0x94, 0x6f, 0xc5, 0x23, 0xc9, 0x9f, 0x2f, 0xe4, 0x37, 0xc1, 0x9f, 0x91, 0x97, 0xfa, 0x56, 0x3c, 0x4f, 0xf5, 0xad, 0xc8, 0x0d, 0x52, 0x63, 0xaf, 0xf1, 0x9b, 0x98, 0x4f, 0xc6, 0x50, 0xa9, 0xf8, 0x52, 0xf8, 0xb0, 0xfc, 0x10, 0x2a, 0x80, 0xb7, 0xcb, 0xe1, 0xfd, 0x98, 0xbd, 0x61, 0xde, 0x4b, 0x3d, 0xc4, 0xbe, 0x10, 0xde, 0xaf, 0x46, 0x3f, 0xdb, 0xab, 0xcd, 0x93, 0x50, 0x83, 0xf2, 0x8b, 0xf0, 0xe3, 0xe1, 0x97, 0x30, 0x7d, 0xd7, 0xfc, 0x7d, 0x23, 0x9b, 0x47, 0xdf, 0x37, 0xea, 0xbf, 0xef, 0x16, 0x2e, 0x0e, 0x67, 0x13, 0x3d, 0x22, 0xe3, 0x5f, 0xeb, 0xdf, 0xfa, 0xb8, 0xf8, 0x45, 0xb8, 0x50, 0xce, 0x23, 0xd8, 0xdb, 0x3a, 0xf6, 0xbd, 0xb2, 0x59, 0xf9, 0x6b, 0x78, 0x0d, 0xc1, 0xfe, 0xac, 0xc9, 0x7d, 0xef, 0xaf, 0xc4, 0x2f, 0xd0, 0x56, 0x39, 0x9f, 0xd9, 0x57, 0x5a, 0xcc, 0x4b, 0x2d, 0x3a, 0x23, 0x0b, 0x58, 0x48, 0xd7, 0x1f, 0x87, 0x6b, 0x18, 0xac, 0x3f, 0x6e, 0xea, 0xa3, 0x03, 0x7c, 0xf3, 0xd4, 0x0f, 0xe1, 0xab, 0xb7, 0x2a, 0x2f, 0xba, 0x32, 0x3c, 0xb7, 0x60, 0x65, 0x2c, 0x97, 0x78, 0xbd, 0xd5, 0x95, 0x94, 0xee, 0x92, 0xf3, 0x69, 0xd0, 0xf0, 0xe5, 0xf7, 0x43, 0xd4, 0xf0, 0x33, 0x78, 0xcb, 0xac, 0xa4, 0x0a, 0xeb, 0x88, 0xae, 0x82, 0xf7, 0xd7, 0xaf, 0x46, 0x30, 0xf1, 0x03, 0xe5, 0x6b, 0xe9, 0x24, 0xa6, 0x18, 0x3e, 0x5f, 0x8d, 0xfc, 0x7a, 0xbe, 0xe6, 0xaf, 0xa0, 0xd7, 0x45, 0xbe, 0xf1, 0x00, 0x69, 0x7b, 0x3c, 0x8b, 0x37, 0x6a, 0x0a, 0x9c, 0x39, 0x9d, 0x06, 0x14, 0x70, 0xfb, 0x83, 0xe0, 0x62, 0x84, 0xf7, 0x79, 0xe8, 0x69, 0x1a, 0xf5, 0x42, 0xe1, 0x1b, 0xa9, 0x5c, 0x8c, 0x37, 0x7a, 0xbc, 0x2e, 0xe5, 0xd5, 0x5f, 0xe6, 0xe6, 0x25, 0x5e, 0x8f, 0x15, 0xf1, 0x4b, 0x2c, 0x39, 0x71, 0xb6, 0xdd, 0x73, 0x3f, 0x2b, 0xf7, 0x17, 0xa4, 0x4f, 0x6a, 0xf5, 0x72, 0x63, 0xc6, 0x21, 0x75, 0xe7, 0x40, 0xb9, 0x6e, 0xea, 0xba, 0x04, 0x36, 0xb7, 0xb2, 0x38, 0x4f, 0x2d, 0x94, 0xf9, 0x92, 0x92, 0xc7, 0x35, 0x38, 0x93, 0x13, 0x13, 0x2c, 0xe2, 0x17, 0x92, 0x73, 0xd7, 0x72, 0x5e, 0x8e, 0xa7, 0xfe, 0x4a, 0x7a, 0xaf, 0x23, 0x6d, 0xfd, 0x22, 0xd2, 0x56, 0x2d, 0x36, 0xea, 0xb4, 0x48, 0x6c, 0xd4, 0x7c, 0x7f, 0x02, 0x6b, 0xab, 0xac, 0x45, 0x6c, 0xf7, 0x39, 0x15, 0xff, 0x51, 0xe8, 0xe0, 0x8f, 0xac, 0x2e, 0x77, 0x9a, 0x8b, 0x6b, 0x90, 0x9e, 0xd9, 0xa9, 0x45, 0x64, 0x27, 0x65, 0x9e, 0xbc, 0x9f, 0xe8, 0xe8, 0x35, 0x44, 0x47, 0x67, 0x42, 0xfc, 0x45, 0xb7, 0xc8, 0xa9, 0x9b, 0x35, 0xaa, 0x87, 0x95, 0x4b, 0x1b, 0xfc, 0x75, 0x39, 0x34, 0xd6, 0x2b, 0x6d, 0x25, 0x2d, 0x55, 0xef, 0x84, 0x7a, 0x08, 0x30, 0x4d, 0xd8, 0xe7, 0x1a, 0x68, 0xf4, 0xbb, 0x7f, 0x24, 0x95, 0xdc, 0xaf, 0xf7, 0xc8, 0x23, 0x8f, 0xd0, 0xa1, 0xfc, 0x13, 0xf9, 0xa3, 0x64, 0x93, 0x9c, 0xff, 0x7d, 0xcc, 0x61, 0x61, 0xdd, 0xf3, 0xda, 0x6b, 0x50, 0xb3, 0xdd, 0x03, 0xdf, 0x93, 0x49, 0xea, 0x0e, 0x91, 0xba, 0xb3, 0xa1, 0xee, 0x54, 0x4b, 0x74, 0xdd, 0x85, 0xb5, 0x81, 0x2a, 0xa8, 0x9b, 0x67, 0xb5, 0xaa, 0xd5, 0xbb, 0x59, 0xad, 0x6a, 0xf5, 0xa3, 0x5d, 0x19, 0x29, 0x9b, 0x3c, 0x69, 0xac, 0x7a, 0xf8, 0x46, 0xb5, 0xd2, 0x2d, 0x5b, 0xe9, 0x1f, 0xdf, 0x97, 0x72, 0xa0, 0x62, 0xa8, 0x9e, 0x7d, 0xb2, 0xdd, 0xf3, 0xd2, 0x4b, 0xec, 0xd3, 0x49, 0xfd, 0xdf, 0xf0, 0x8d, 0xe1, 0xbf, 0x08, 0x7e, 0xa2, 0xaf, 0x6f, 0x66, 0xde, 0xfc, 0x92, 0xc1, 0xd5, 0x02, 0x12, 0xd0, 0x4c, 0xea, 0xcc, 0x9d, 0x30, 0x36, 0x9e, 0x87, 0x57, 0xe6, 0x2c, 0x3a, 0x5e, 0xaa, 0x01, 0xec, 0x04, 0xa5, 0x46, 0x1d, 0xef, 0xcd, 0xa7, 0x5e, 0x6e, 0xf3, 0xfa, 0xcf, 0x4c, 0x46, 0x8a, 0x7a, 0x5b, 0x01, 0xa0, 0x8f, 0xbb, 0x15, 0x70, 0x5c, 0x0b, 0x91, 0x5f, 0xbc, 0xc8, 0x4b, 0x1d, 0xaf, 0x40, 0xa8, 0x08, 0x3e, 0xb6, 0xe3, 0x95, 0xfa, 0x34, 0xba, 0x06, 0x4c, 0x99, 0x30, 0x22, 0xb2, 0x06, 0x4c, 0x19, 0x3f, 0x3c, 0x8d, 0xac, 0x0e, 0x42, 0x66, 0xf4, 0x2a, 0x50, 0x58, 0x46, 0xf5, 0x8d, 0xd0, 0x18, 0xde, 0x27, 0xed, 0x67, 0xfa, 0x26, 0xbc, 0xa3, 0xa5, 0xb0, 0x71, 0x2d, 0xd5, 0x37, 0x0d, 0xe8, 0xaa, 0xea, 0x11, 0xd5, 0x15, 0x54, 0xdf, 0x34, 0x70, 0x19, 0xf0, 0x67, 0x94, 0x4e, 0xf5, 0xc6, 0x0e, 0x55, 0x6f, 0x5c, 0xa5, 0xaa, 0x1b, 0x2e, 0x43, 0xf3, 0xef, 0x30, 0x4a, 0x68, 0x0a, 0xdf, 0x27, 0x55, 0x10, 0x7d, 0xb3, 0x8f, 0xe8, 0x9b, 0xdb, 0x30, 0xf3, 0xa3, 0xb0, 0x5d, 0xd5, 0x37, 0xb7, 0xa9, 0xf9, 0xb6, 0xeb, 0xf3, 0xea, 0x0d, 0x61, 0x23, 0x1a, 0x4e, 0xf8, 0x40, 0x7c, 0x24, 0xae, 0xe7, 0x4c, 0x20, 0xa4, 0xdd, 0x1c, 0x23, 0xd6, 0x71, 0xaa, 0x2e, 0xe0, 0x23, 0xba, 0x00, 0x57, 0x0f, 0xff, 0xcc, 0x99, 0xe1, 0xb9, 0x02, 0x2b, 0xa5, 0x9c, 0xaf, 0xdd, 0xe1, 0xf3, 0x8b, 0xbb, 0x20, 0xd6, 0x03, 0x5e, 0xb5, 0xcc, 0xe3, 0xa1, 0x65, 0x56, 0x12, 0x8e, 0x71, 0x3f, 0x2d, 0x33, 0x25, 0xe8, 0xa6, 0xa2, 0x4f, 0x3b, 0xbb, 0x1b, 0x8f, 0x31, 0xcf, 0x51, 0xe6, 0x29, 0xc6, 0x37, 0x5b, 0xa9, 0xc4, 0xd4, 0x6b, 0xd1, 0x01, 0x7f, 0xae, 0x63, 0x04, 0x97, 0x21, 0xdc, 0x82, 0xc1, 0x75, 0x91, 0xc7, 0x43, 0xb4, 0x53, 0xef, 0x9f, 0x49, 0xfb, 0x5c, 0xa4, 0x2c, 0x3b, 0xc4, 0x9f, 0xd0, 0x78, 0x13, 0x75, 0x2d, 0x4c, 0x8a, 0xf3, 0x45, 0x4f, 0xcd, 0x14, 0x8f, 0x0b, 0x4a, 0xba, 0x2a, 0xa4, 0x70, 0xed, 0xa4, 0x30, 0x61, 0x63, 0xbc, 0x6b, 0xcd, 0x2a, 0x88, 0x41, 0xd1, 0xfb, 0x00, 0x69, 0xd3, 0x71, 0xbd, 0x1c, 0x3a, 0x27, 0x59, 0x48, 0x6d, 0xae, 0x1b, 0x8d, 0xf1, 0x9b, 0xa6, 0x63, 0x3d, 0xdd, 0xdf, 0x53, 0x84, 0xab, 0xe0, 0x23, 0xef, 0x24, 0xdf, 0xc7, 0xb5, 0x8b, 0x65, 0x6b, 0x58, 0x30, 0x0b, 0xfa, 0x7d, 0x4d, 0xa4, 0x4d, 0xf1, 0xa4, 0x2c, 0x2f, 0xcc, 0x6f, 0x75, 0x2e, 0x92, 0x99, 0xc0, 0xa1, 0xe5, 0x6a, 0xcf, 0xd5, 0xc5, 0x9c, 0x8d, 0xbc, 0x3a, 0x1b, 0x31, 0x9d, 0x8d, 0xf1, 0xd0, 0xd0, 0xcd, 0x77, 0x90, 0x3a, 0xd6, 0x6b, 0xdf, 0x3e, 0x6f, 0x3e, 0xa9, 0xec, 0xa8, 0xda, 0xee, 0x07, 0x1e, 0x70, 0xc5, 0xb3, 0x6e, 0x78, 0xec, 0x31, 0x8f, 0x87, 0x7e, 0xc3, 0x44, 0x52, 0x6f, 0x09, 0xad, 0x97, 0xe8, 0x6f, 0x8f, 0xd5, 0x50, 0x2f, 0x7d, 0x68, 0xce, 0x86, 0xcc, 0x8b, 0xd2, 0xab, 0x03, 0xd5, 0xc0, 0x5b, 0xdd, 0xe6, 0x09, 0xa9, 0x56, 0x4c, 0x5b, 0x50, 0x5d, 0x02, 0x23, 0x78, 0xb3, 0xd6, 0x02, 0xfa, 0x95, 0x50, 0xf1, 0xfc, 0x4b, 0xf4, 0xe1, 0x64, 0x2d, 0xf8, 0x12, 0xaa, 0x7e, 0xfc, 0x71, 0x52, 0x3d, 0xf5, 0xa5, 0x71, 0x45, 0xf8, 0x61, 0x69, 0x23, 0x44, 0x01, 0xed, 0x9d, 0xc2, 0xe2, 0x97, 0x86, 0xba, 0xd1, 0x2f, 0x30, 0xf5, 0xad, 0x11, 0x1e, 0x30, 0x0a, 0x29, 0xd2, 0xf2, 0xdf, 0xa1, 0xe6, 0x1f, 0xc6, 0x62, 0x98, 0x86, 0x1e, 0x38, 0xc3, 0xfc, 0x0f, 0x4b, 0xb7, 0x41, 0x24, 0xd1, 0x1f, 0x50, 0x3f, 0xcb, 0xdf, 0x72, 0x16, 0xf5, 0x4f, 0x13, 0x06, 0x91, 0xfa, 0x9f, 0x23, 0x73, 0xeb, 0x00, 0x49, 0xff, 0x88, 0xda, 0x0f, 0x34, 0xa2, 0x29, 0x69, 0xc7, 0x80, 0x11, 0x4d, 0xf5, 0xfc, 0xeb, 0xa3, 0xf2, 0xb7, 0xb2, 0x68, 0xaa, 0xa1, 0x07, 0x4f, 0x33, 0xff, 0xad, 0xd2, 0x8b, 0x86, 0xfc, 0x8d, 0xbd, 0x43, 0xd5, 0xfa, 0xa7, 0x9d, 0x76, 0xfd, 0xe6, 0xfc, 0x4d, 0xa7, 0x5f, 0x3f, 0xe1, 0x59, 0xb3, 0xc2, 0xff, 0x90, 0xae, 0x24, 0xf9, 0x08, 0xf7, 0x0a, 0x7f, 0xc5, 0xfa, 0x0d, 0x97, 0x81, 0x7f, 0x69, 0xfe, 0x85, 0xf0, 0x57, 0x90, 0x9e, 0x27, 0x8b, 0x76, 0x99, 0xaa, 0x8b, 0xd4, 0xf4, 0x27, 0x0c, 0xe9, 0x49, 0xbd, 0xb8, 0x31, 0x3a, 0x7d, 0xa3, 0x39, 0x3d, 0x94, 0x3f, 0xfc, 0xcc, 0xca, 0x67, 0xe5, 0x62, 0x99, 0xe5, 0x53, 0xfd, 0x5d, 0x37, 0xd2, 0xef, 0x50, 0x33, 0x52, 0x1b, 0x86, 0x68, 0x22, 0x71, 0xa2, 0x3c, 0x84, 0xf0, 0x6d, 0x9e, 0x6e, 0x9e, 0xec, 0xc4, 0xf1, 0x31, 0xf1, 0xdd, 0x26, 0xdc, 0xab, 0xe3, 0x4f, 0xe3, 0xb4, 0x08, 0x2e, 0x7d, 0xa1, 0xe3, 0xcf, 0x9a, 0xf0, 0x6f, 0x75, 0xfc, 0x39, 0x13, 0xfe, 0xb0, 0x8e, 0xbf, 0x60, 0xc4, 0x65, 0xb7, 0x8e, 0xbf, 0x68, 0xc2, 0xbb, 0x74, 0xfc, 0x25, 0x9c, 0x1e, 0xc1, 0x2d, 0x41, 0x1d, 0x7f, 0x05, 0x7b, 0xa9, 0x4f, 0x2a, 0x8a, 0xd3, 0xbd, 0xa2, 0x64, 0x54, 0x11, 0x2c, 0x85, 0x78, 0x28, 0xb8, 0x43, 0x54, 0xfd, 0x68, 0xcd, 0x26, 0xcb, 0x58, 0x09, 0x75, 0x12, 0x5c, 0x0a, 0x9e, 0xde, 0x20, 0xc0, 0x94, 0x83, 0x18, 0x34, 0xb0, 0x77, 0x24, 0xc9, 0xc4, 0x9c, 0xe1, 0x03, 0x05, 0x0e, 0x2c, 0xf3, 0xcc, 0x85, 0x9c, 0x8f, 0xaa, 0x0c, 0x5c, 0xcd, 0xef, 0x3a, 0xe1, 0xf1, 0xd8, 0x3d, 0xf6, 0xb7, 0xf9, 0x27, 0x1e, 0xc9, 0xc8, 0xb3, 0xac, 0x0a, 0xdd, 0xb4, 0x4a, 0x0e, 0x78, 0x1f, 0xe1, 0x9a, 0x7f, 0xc3, 0x79, 0xb8, 0xb2, 0xc6, 0x69, 0x25, 0x1d, 0x99, 0xa1, 0x13, 0xa1, 0xbf, 0x66, 0xa4, 0xe0, 0xb1, 0xca, 0x3e, 0x77, 0x36, 0xa9, 0x5b, 0x6f, 0x1f, 0x8d, 0x3f, 0xc7, 0x53, 0xff, 0x43, 0x7f, 0x0f, 0x97, 0x86, 0x7f, 0xcb, 0xe2, 0xf2, 0x84, 0x7f, 0x4b, 0x07, 0x81, 0x23, 0xa9, 0x90, 0x38, 0x9e, 0xa4, 0xb7, 0xa0, 0x14, 0x68, 0xab, 0xd0, 0xa7, 0xad, 0x69, 0xb4, 0xad, 0xe9, 0xd0, 0xd6, 0x94, 0xe4, 0xa4, 0x44, 0xa7, 0xc3, 0x6e, 0x25, 0x89, 0x2d, 0x92, 0x04, 0x6d, 0x2d, 0xc7, 0xb1, 0x1a, 0xfb, 0xc4, 0x09, 0xd2, 0x54, 0xd6, 0xda, 0x1d, 0x19, 0x7e, 0xeb, 0xaa, 0xd0, 0xba, 0x55, 0x96, 0x7c, 0xef, 0x0e, 0xae, 0xf9, 0x5d, 0x68, 0x6d, 0xc6, 0xe8, 0xe2, 0x92, 0xd1, 0x7d, 0x9a, 0x4b, 0xdb, 0xab, 0x6c, 0x86, 0x7e, 0xd3, 0xfa, 0x13, 0xff, 0x19, 0xa7, 0x18, 0xfa, 0x3f, 0x4f, 0xc7, 0xdf, 0x36, 0xf6, 0xbf, 0xfc, 0x90, 0x8e, 0xbf, 0x63, 0x1a, 0x2f, 0x7d, 0x7c, 0xf1, 0xbb, 0x26, 0xfc, 0x73, 0x1d, 0x7f, 0xcf, 0x88, 0x5b, 0xda, 0x75, 0xfc, 0x7d, 0x0d, 0x87, 0xf6, 0xe8, 0xf2, 0x56, 0xc0, 0xe4, 0x2d, 0xba, 0x5d, 0x96, 0x80, 0x9e, 0xef, 0x0f, 0xa6, 0xf2, 0xf2, 0x75, 0xfc, 0x03, 0x13, 0x1e, 0xf9, 0x8e, 0x8f, 0xfa, 0xa9, 0xff, 0x63, 0x53, 0xfd, 0x6e, 0xbd, 0xfe, 0x17, 0x63, 0xd5, 0x6f, 0x98, 0x0f, 0x7b, 0x4d, 0xdf, 0xf9, 0xbc, 0x9e, 0x8f, 0xc8, 0x37, 0x4e, 0xc2, 0xaa, 0xfc, 0xb3, 0xf1, 0xc7, 0x49, 0xaa, 0x8f, 0x7a, 0x96, 0xf6, 0x7d, 0x53, 0x5a, 0x32, 0x73, 0xd1, 0x11, 0x43, 0xda, 0x46, 0x53, 0x5a, 0xbd, 0x3e, 0x71, 0x7b, 0xec, 0x79, 0x23, 0x3e, 0x68, 0xc2, 0x33, 0x74, 0xf9, 0x3b, 0x82, 0x53, 0x09, 0xca, 0xe4, 0x2f, 0x4c, 0xe5, 0x8f, 0x27, 0x12, 0x08, 0x63, 0xfe, 0x7b, 0x92, 0xd6, 0x8e, 0xf2, 0xd0, 0xd2, 0xa0, 0x2d, 0x2f, 0xc3, 0x22, 0x62, 0x11, 0x49, 0xe0, 0xec, 0x2e, 0x87, 0x46, 0x17, 0x13, 0x38, 0x61, 0x39, 0x26, 0x2c, 0x17, 0xf6, 0x05, 0xf8, 0xd9, 0xcc, 0x8d, 0x97, 0x24, 0xcd, 0xe9, 0x14, 0x31, 0x0d, 0x5a, 0xea, 0xed, 0x9b, 0x04, 0x7e, 0x25, 0xe9, 0xb8, 0xe9, 0x6a, 0x92, 0xae, 0x20, 0x61, 0x0a, 0x7e, 0x9f, 0x37, 0x3d, 0x2d, 0xd5, 0x93, 0x9c, 0xe8, 0x72, 0xc4, 0x09, 0x10, 0x8e, 0x93, 0xb3, 0xc2, 0xf6, 0x81, 0x1d, 0xfb, 0xab, 0x71, 0x01, 0x9d, 0x74, 0xaa, 0x18, 0x33, 0x33, 0xcb, 0x8f, 0xab, 0xbd, 0x18, 0xa8, 0x32, 0xe6, 0x77, 0xcd, 0x3f, 0xf6, 0xde, 0x60, 0x5c, 0xd6, 0x40, 0xa7, 0x20, 0x77, 0x82, 0xce, 0x40, 0xa5, 0x4c, 0x39, 0xea, 0x09, 0xe6, 0x4c, 0x99, 0x70, 0x3e, 0x99, 0x84, 0xd7, 0x7c, 0x66, 0x4f, 0x4c, 0x4a, 0x4d, 0x5c, 0xa4, 0xac, 0xe5, 0x3c, 0xd8, 0xae, 0x7c, 0x8d, 0xb7, 0xde, 0x41, 0xe7, 0xe4, 0xc9, 0x57, 0xa8, 0x88, 0x8f, 0xcf, 0xf1, 0x2e, 0x12, 0xb7, 0x29, 0x3f, 0xc5, 0xc3, 0x9c, 0xc9, 0x71, 0x52, 0xe8, 0xbb, 0xc8, 0xdc, 0x7c, 0x45, 0xef, 0xb3, 0x86, 0xb0, 0x8b, 0xc6, 0xaf, 0x1b, 0x17, 0x85, 0xb7, 0x28, 0x8f, 0x53, 0xbc, 0x31, 0x0a, 0x1f, 0xa2, 0xe2, 0xf7, 0x53, 0x5d, 0x76, 0x5c, 0xc7, 0x5b, 0xc3, 0xa9, 0x86, 0xb9, 0x7f, 0x44, 0xc7, 0x1b, 0x19, 0x1e, 0x1e, 0x15, 0x85, 0x07, 0xd5, 0x72, 0xea, 0xa2, 0xf0, 0xb6, 0x7e, 0xca, 0x1f, 0x6a, 0x2a, 0x7f, 0x9f, 0x8e, 0x0f, 0x56, 0xcb, 0xff, 0x36, 0x0a, 0x6f, 0x32, 0xa6, 0x3f, 0x13, 0x9d, 0xab, 0x5c, 0x61, 0xd4, 0x05, 0xe2, 0x68, 0xf0, 0x0e, 0xa8, 0xe3, 0x5a, 0x3f, 0x14, 0xb0, 0x7e, 0x8b, 0xfc, 0x1e, 0x1e, 0x17, 0xf5, 0x3b, 0xed, 0x3f, 0xc3, 0xef, 0x8d, 0x51, 0xbf, 0x0f, 0x89, 0xfa, 0xfd, 0x7e, 0xf8, 0x5d, 0xff, 0xde, 0x02, 0xd6, 0x9f, 0x7d, 0xea, 0x3f, 0xa2, 0xff, 0xde, 0x68, 0xfe, 0x3d, 0x3c, 0x2a, 0xea, 0xf7, 0x60, 0x54, 0xf9, 0x75, 0x51, 0xbf, 0xb7, 0x9d, 0xa2, 0xfe, 0xa1, 0x51, 0xf5, 0x83, 0xbb, 0xf2, 0xf1, 0x32, 0x8f, 0xc8, 0xef, 0x0a, 0xe8, 0x82, 0x6a, 0x55, 0x17, 0x50, 0x5c, 0xfa, 0x56, 0xc3, 0xc9, 0x5c, 0x57, 0x75, 0x84, 0x52, 0x69, 0xec, 0x47, 0xce, 0x89, 0xd9, 0x38, 0xfd, 0x1b, 0x21, 0xe9, 0x0b, 0xba, 0xc6, 0x8a, 0x6c, 0x8d, 0xe5, 0x87, 0x47, 0x70, 0x3a, 0x4e, 0x0c, 0x7f, 0x81, 0x9f, 0x40, 0xe3, 0xb3, 0x7d, 0x45, 0xf0, 0x7f, 0x10, 0x45, 0x2d, 0xa2, 0x54, 0x34, 0x2e, 0xd8, 0x89, 0xb1, 0x48, 0xd6, 0x09, 0x24, 0x41, 0x28, 0x1a, 0x79, 0x96, 0x15, 0xd6, 0x0b, 0x0b, 0x82, 0x40, 0xc8, 0xa2, 0xd0, 0x45, 0x17, 0x0d, 0x1b, 0xb6, 0x58, 0x58, 0x1c, 0x6f, 0xb2, 0x74, 0x48, 0x12, 0xc9, 0x9d, 0x2a, 0xa5, 0x7a, 0xc8, 0x0a, 0xe2, 0x4e, 0x74, 0x25, 0x38, 0xe3, 0x6d, 0x56, 0x8b, 0x4c, 0xca, 0x12, 0xed, 0x74, 0x19, 0x21, 0x73, 0xcf, 0x8a, 0x65, 0x9f, 0xb6, 0x82, 0xf8, 0x92, 0x7d, 0x10, 0x96, 0xd9, 0xcd, 0x3f, 0x38, 0xc3, 0x91, 0xe5, 0x54, 0xde, 0x16, 0x13, 0x70, 0xef, 0xbf, 0x2b, 0x42, 0xef, 0x3f, 0x69, 0x2f, 0xca, 0x7c, 0x90, 0xfb, 0xa7, 0x60, 0x0b, 0xe5, 0x36, 0x70, 0xb9, 0xa1, 0x7f, 0x0d, 0x26, 0xcb, 0xc9, 0x27, 0xa5, 0x63, 0x72, 0x43, 0xcf, 0x2a, 0x77, 0xef, 0xc6, 0xab, 0x94, 0x67, 0x52, 0xbc, 0xe2, 0xb6, 0xdd, 0x4a, 0xe3, 0x63, 0x34, 0xa6, 0x21, 0x0f, 0xf1, 0xb4, 0x84, 0x7d, 0xf4, 0x5e, 0x2e, 0x44, 0x18, 0x1f, 0x1e, 0x1c, 0x9a, 0x85, 0x31, 0x17, 0xb9, 0x96, 0x23, 0x63, 0x9e, 0x17, 0xa6, 0x5b, 0x30, 0xac, 0xc7, 0xb0, 0xcb, 0x48, 0xdd, 0xc0, 0x97, 0x8a, 0x63, 0x92, 0x93, 0xec, 0x76, 0x8c, 0x02, 0x79, 0x49, 0x05, 0xc9, 0x05, 0x76, 0xb7, 0x3d, 0xd1, 0x11, 0x67, 0x95, 0x91, 0x0d, 0xdb, 0xac, 0x64, 0x79, 0xce, 0x67, 0xd1, 0xa9, 0xfc, 0xbe, 0x34, 0xd5, 0x05, 0xa2, 0xbf, 0x56, 0x8b, 0x49, 0x25, 0xb8, 0x58, 0x7c, 0x64, 0xee, 0x99, 0x89, 0xcb, 0x47, 0xe7, 0xde, 0xb9, 0x55, 0x79, 0xfb, 0xeb, 0xfb, 0xee, 0xbd, 0x63, 0xcb, 0x4d, 0x87, 0xaf, 0x69, 0xca, 0x69, 0x9b, 0x3b, 0x1c, 0x37, 0x0e, 0xbf, 0xfe, 0x95, 0x15, 0xa1, 0x93, 0x4f, 0x04, 0xda, 0xa6, 0xd7, 0x75, 0xaf, 0x54, 0x7c, 0xc2, 0x18, 0xa5, 0x78, 0xd5, 0xc2, 0x95, 0x3d, 0x8f, 0xd5, 0xcc, 0xbe, 0xf9, 0xfc, 0x9a, 0x8b, 0x27, 0xb7, 0xa5, 0x0c, 0x51, 0xbe, 0x99, 0xf1, 0xe3, 0xc5, 0xcd, 0xa4, 0xc5, 0x67, 0xd0, 0xf6, 0xb4, 0x48, 0xdb, 0xd3, 0x07, 0x68, 0xbb, 0xf4, 0xbf, 0xd8, 0x76, 0x2b, 0x69, 0xfb, 0x31, 0x7a, 0x0e, 0xe8, 0x41, 0x15, 0x68, 0x62, 0x70, 0x5c, 0x2e, 0xc6, 0x20, 0x2d, 0x10, 0x7b, 0x5a, 0xc0, 0x73, 0xac, 0x98, 0xb3, 0x60, 0xe6, 0xa5, 0x53, 0x14, 0xa5, 0xe9, 0x76, 0x0c, 0xea, 0x1a, 0x3c, 0xc4, 0xd3, 0x18, 0xf7, 0x73, 0xe5, 0x31, 0x69, 0xa9, 0x18, 0x95, 0x97, 0x95, 0x96, 0x14, 0x17, 0xa6, 0x56, 0xa4, 0x55, 0x78, 0x92, 0x5d, 0x4e, 0xb8, 0x78, 0x43, 0x3e, 0xc4, 0x81, 0x1d, 0x71, 0x36, 0xc3, 0x87, 0x40, 0xf0, 0xcd, 0x64, 0x17, 0xfb, 0x18, 0xa2, 0xb2, 0xa3, 0xbf, 0x06, 0x82, 0x74, 0x72, 0x47, 0xb5, 0x2f, 0x3a, 0x7c, 0x98, 0x7e, 0x53, 0x38, 0x1c, 0xfd, 0x55, 0x07, 0x0e, 0xa8, 0xdf, 0x75, 0xdf, 0x96, 0xc7, 0xb9, 0x2b, 0x62, 0x7e, 0x16, 0x9e, 0xb8, 0x85, 0xdf, 0x8b, 0xd4, 0x18, 0x6d, 0xc2, 0x3e, 0xd9, 0x4f, 0xe6, 0x87, 0xa4, 0xc6, 0x98, 0x39, 0x8a, 0xe7, 0x90, 0x35, 0x0d, 0x88, 0xf0, 0x1c, 0x75, 0x9d, 0xdc, 0xab, 0x6c, 0x86, 0xb1, 0x63, 0x69, 0x60, 0xbd, 0x46, 0x5f, 0xa9, 0x31, 0xac, 0xbe, 0x36, 0xe2, 0xe2, 0x47, 0x2a, 0xde, 0x15, 0xfe, 0x5c, 0x74, 0xc8, 0x43, 0x20, 0xde, 0x4c, 0x98, 0xf1, 0xdd, 0x5b, 0x0d, 0xb8, 0x57, 0xc7, 0x9f, 0xc6, 0x5b, 0x63, 0xe2, 0x7b, 0x4d, 0x78, 0x06, 0xc3, 0xe9, 0x7a, 0xbb, 0x85, 0xae, 0xb7, 0x58, 0x5f, 0x6f, 0xb1, 0x1e, 0xdb, 0xa6, 0x0c, 0x8d, 0x08, 0xb6, 0xb9, 0xb1, 0xc0, 0xf1, 0x64, 0xb1, 0xe5, 0x3a, 0x10, 0x2f, 0x20, 0xb2, 0x28, 0xf6, 0x10, 0x59, 0x82, 0x48, 0x16, 0x8b, 0xb5, 0xa0, 0xca, 0x34, 0xb4, 0x14, 0x0b, 0x04, 0x07, 0xae, 0x5f, 0x0b, 0xf2, 0x33, 0xbd, 0xa9, 0x29, 0x2e, 0xa7, 0x2c, 0xa2, 0x32, 0x5c, 0x26, 0xc3, 0xe4, 0xad, 0x52, 0xe3, 0xad, 0xe0, 0x53, 0x85, 0x07, 0x12, 0xb6, 0x25, 0xc6, 0x9f, 0x77, 0x7e, 0xe8, 0x99, 0x6d, 0xa1, 0x7d, 0xb3, 0x4f, 0x15, 0x09, 0x08, 0xd7, 0x38, 0x66, 0x2c, 0x58, 0xb6, 0xf0, 0x08, 0x76, 0x3c, 0x34, 0x40, 0xd0, 0x1f, 0xe0, 0xda, 0x39, 0x84, 0x3f, 0xcc, 0x20, 0xfc, 0xc1, 0x8e, 0xda, 0xd0, 0xc6, 0x60, 0x72, 0x73, 0x63, 0x25, 0x4f, 0xb4, 0x0a, 0xe6, 0xe4, 0x40, 0x3a, 0x87, 0xb9, 0x1c, 0xcc, 0xd3, 0x20, 0x6b, 0x39, 0x34, 0x9a, 0x92, 0x0c, 0xf1, 0x3a, 0xb8, 0x1e, 0x4a, 0x6d, 0x31, 0xf9, 0x44, 0x5e, 0x58, 0x4e, 0xa8, 0x42, 0x24, 0xb6, 0x60, 0x37, 0x0b, 0x46, 0x88, 0x58, 0x48, 0x48, 0x92, 0x41, 0x12, 0xa5, 0x65, 0x31, 0x73, 0xc5, 0xca, 0xd0, 0x15, 0xb4, 0x17, 0x14, 0x14, 0xe6, 0xe7, 0x05, 0x8a, 0x73, 0xa9, 0x5f, 0x51, 0xd5, 0xa7, 0x7a, 0x12, 0xb1, 0xf0, 0x99, 0x1b, 0xf5, 0x7a, 0x76, 0xc0, 0xcd, 0x1e, 0x32, 0xb0, 0x3d, 0xb9, 0x40, 0xa4, 0xd3, 0xe8, 0xb6, 0x9c, 0x9c, 0x85, 0x59, 0xef, 0x15, 0xf0, 0x5f, 0x16, 0x74, 0x8f, 0x82, 0x5e, 0x29, 0x2d, 0x9e, 0xd2, 0x7a, 0xdf, 0xf4, 0x48, 0x84, 0x24, 0xe8, 0xc2, 0x6b, 0xde, 0x9a, 0xb2, 0x02, 0xc8, 0xc7, 0x27, 0xe7, 0x75, 0xd1, 0x9e, 0x9c, 0xf9, 0xf0, 0x88, 0x6a, 0xc6, 0x40, 0x36, 0xd9, 0x93, 0xed, 0xd0, 0xa9, 0x8e, 0x14, 0xe1, 0x67, 0x89, 0x69, 0xd0, 0x5d, 0xf3, 0x1e, 0x6f, 0x9b, 0xdf, 0x30, 0x58, 0x8d, 0x92, 0x44, 0x3a, 0x75, 0x78, 0xf0, 0xd1, 0x1c, 0x2f, 0x1e, 0x3c, 0x8b, 0x8c, 0x07, 0xed, 0xda, 0x42, 0x7f, 0x3b, 0xb0, 0x92, 0x23, 0x56, 0xb7, 0x8d, 0xf4, 0xb1, 0xe3, 0x2c, 0x64, 0xa4, 0x5a, 0x97, 0x91, 0x9a, 0x18, 0x32, 0x22, 0xff, 0xdf, 0xcb, 0x48, 0x97, 0xb2, 0x95, 0xce, 0x2f, 0x3b, 0x3a, 0x0f, 0x0b, 0xea, 0x29, 0x4b, 0x3d, 0xb6, 0xca, 0x45, 0x09, 0x9c, 0xc0, 0x79, 0x31, 0x2f, 0xf0, 0x1d, 0x5e, 0x33, 0x22, 0xf2, 0x1d, 0x6a, 0x1c, 0xd1, 0x46, 0xea, 0x99, 0x18, 0xc9, 0x3d, 0xc8, 0x6e, 0xb1, 0x59, 0xec, 0xb6, 0x1e, 0x70, 0xa3, 0x6b, 0xc7, 0x16, 0x88, 0x4a, 0x49, 0xf5, 0x15, 0x91, 0x01, 0x9b, 0x8d, 0x9b, 0x2e, 0x11, 0xa6, 0xb9, 0xd0, 0xec, 0x7c, 0x97, 0xd5, 0x74, 0x26, 0x05, 0x2c, 0x89, 0xf2, 0xde, 0x1b, 0xec, 0x93, 0x57, 0xe0, 0x44, 0x0e, 0xbc, 0xf9, 0x0e, 0x54, 0x86, 0xd1, 0xa9, 0x6f, 0x57, 0x30, 0x71, 0xf2, 0xc4, 0xce, 0xd1, 0x43, 0x5b, 0xf3, 0x0b, 0xc1, 0xab, 0xaf, 0x3b, 0x9f, 0x06, 0x3f, 0xc0, 0xfa, 0x86, 0xb0, 0xdb, 0x81, 0x9d, 0xb8, 0x3f, 0xe7, 0xbe, 0xa7, 0x15, 0xf2, 0x8b, 0xbf, 0x30, 0xb2, 0x83, 0xfc, 0x7e, 0xf7, 0xc4, 0x3f, 0x6d, 0x4b, 0x88, 0xe9, 0x06, 0xb8, 0xe8, 0xd6, 0xff, 0x79, 0xa4, 0xeb, 0x34, 0x02, 0x80, 0xc1, 0xc6, 0x33, 0xdb, 0x72, 0xfe, 0x6e, 0xe3, 0xcc, 0xa5, 0xd6, 0x86, 0x18, 0x0e, 0x83, 0xaf, 0xb1, 0x5d, 0x7e, 0x08, 0xf3, 0x5b, 0x07, 0x8c, 0x06, 0xa6, 0xe9, 0x6a, 0xf1, 0x6e, 0x6a, 0xaf, 0x58, 0x55, 0xfb, 0xc8, 0x6b, 0xc0, 0x33, 0xb4, 0x98, 0xb6, 0x44, 0x4f, 0x7a, 0xcd, 0x76, 0x09, 0xd5, 0xd5, 0xd7, 0x1a, 0xe3, 0xde, 0x12, 0x9b, 0x2b, 0x3b, 0xfc, 0xa9, 0x9a, 0xe6, 0x53, 0x3d, 0xcd, 0x07, 0xc6, 0x34, 0xe2, 0x47, 0x31, 0xd3, 0xac, 0x31, 0xa6, 0x91, 0x92, 0x63, 0xa5, 0x41, 0xc8, 0xb6, 0x8a, 0xc6, 0xfd, 0x64, 0xed, 0x2c, 0x95, 0x64, 0xad, 0x9d, 0xd6, 0x61, 0x06, 0x7c, 0xa3, 0xf8, 0x6f, 0x7d, 0x7f, 0xe1, 0x31, 0xe6, 0x3f, 0x1e, 0xb5, 0x06, 0x9b, 0x65, 0xcc, 0x49, 0xea, 0x7a, 0x0a, 0x51, 0x6d, 0xac, 0x44, 0x84, 0x20, 0x80, 0xb4, 0xaa, 0xa3, 0xe6, 0x8a, 0x63, 0x12, 0x5d, 0x18, 0xe5, 0x64, 0x67, 0x65, 0x66, 0xa4, 0xbb, 0x7c, 0x89, 0x3e, 0x27, 0xbc, 0xb8, 0xb2, 0xa2, 0x04, 0x9c, 0x60, 0xd3, 0xd7, 0xd2, 0x72, 0x58, 0x2e, 0xc1, 0x35, 0x36, 0x66, 0xd1, 0xbc, 0x39, 0x49, 0xd0, 0xd6, 0xcf, 0x3f, 0x68, 0x41, 0xbc, 0x95, 0x17, 0x71, 0x86, 0xf2, 0x31, 0x6e, 0xa7, 0x61, 0xbb, 0x2f, 0x79, 0x49, 0xf9, 0x7e, 0x7b, 0xe8, 0xe3, 0x43, 0x87, 0x76, 0x75, 0xdc, 0xf2, 0xfa, 0xea, 0xeb, 0x8e, 0xdf, 0xd2, 0xa1, 0xe4, 0x5e, 0xa3, 0xa4, 0x08, 0x9f, 0x2f, 0x78, 0x59, 0xf9, 0x66, 0xfb, 0x83, 0xca, 0xb7, 0x2f, 0x2f, 0xe0, 0xee, 0xbe, 0x86, 0x46, 0x4f, 0xfc, 0x7f, 0xac, 0xbd, 0xea, 0x1a, 0x5f, 0x4a, 0xb9, 0xb1, 0x9d, 0xed, 0x4b, 0xa1, 0x3f, 0xc6, 0xc4, 0x77, 0x9b, 0x70, 0xaf, 0x8e, 0x3f, 0x8d, 0x3e, 0x8d, 0x89, 0xef, 0x35, 0xe2, 0x74, 0x5f, 0x8a, 0xe1, 0xcf, 0x99, 0xf0, 0x87, 0x75, 0xfc, 0x05, 0x53, 0x39, 0x6e, 0x1d, 0x7f, 0xd1, 0x84, 0x67, 0x30, 0x1c, 0xe4, 0x18, 0x7d, 0xda, 0x67, 0xbd, 0xa7, 0x69, 0xe8, 0xfd, 0x8a, 0x8a, 0x60, 0x29, 0xe1, 0x66, 0x82, 0xc8, 0x0b, 0xb3, 0xd4, 0xc3, 0x34, 0x5c, 0x42, 0xc3, 0x74, 0xc3, 0x19, 0x18, 0x2a, 0xd5, 0x2f, 0x59, 0xc0, 0x3f, 0xb2, 0x9c, 0x56, 0x92, 0x2f, 0xfb, 0x34, 0x6f, 0xe9, 0x2e, 0xa1, 0x14, 0xf7, 0xde, 0xc8, 0x5f, 0x1d, 0x9a, 0xc1, 0xe5, 0x84, 0x3e, 0xd8, 0xbd, 0x5b, 0x63, 0xd9, 0xa4, 0x36, 0xc2, 0x64, 0x84, 0x9f, 0x8b, 0xfb, 0x08, 0x47, 0xb5, 0xc3, 0x5d, 0x0e, 0xd8, 0x65, 0x52, 0x9e, 0xa1, 0xbb, 0x4c, 0x58, 0xdf, 0x65, 0x3a, 0x65, 0x3b, 0xd2, 0x22, 0xed, 0x48, 0x37, 0xb7, 0x43, 0x3a, 0xdd, 0x76, 0x0c, 0x38, 0x66, 0xb1, 0xfa, 0x9a, 0x70, 0xb4, 0x52, 0x3a, 0x2f, 0xed, 0xea, 0x9e, 0x0a, 0x32, 0xf4, 0x69, 0x9e, 0x8e, 0xbf, 0x8d, 0x3e, 0x33, 0x94, 0xf3, 0x85, 0x5e, 0xce, 0xb3, 0x86, 0x31, 0xf8, 0x8c, 0xee, 0x41, 0xb1, 0xf4, 0xef, 0xa8, 0xb8, 0x95, 0x96, 0xa3, 0x8f, 0x25, 0x7e, 0xd7, 0x90, 0x7e, 0x26, 0xdd, 0x83, 0x62, 0xf8, 0x7b, 0xc6, 0xb1, 0xa4, 0x7b, 0x40, 0x0c, 0x7f, 0xdf, 0xd8, 0x4e, 0x5d, 0x86, 0x0a, 0x98, 0x6c, 0x19, 0xda, 0x7b, 0xb3, 0x59, 0xc6, 0xc4, 0xed, 0xb1, 0x65, 0x46, 0x7c, 0x90, 0xe6, 0x23, 0xe3, 0xa0, 0x6c, 0xa5, 0xe3, 0x50, 0x80, 0xb6, 0xa9, 0x7e, 0xe6, 0xd3, 0x21, 0xfa, 0x5e, 0x1c, 0xf5, 0xfe, 0xe6, 0x8d, 0xfc, 0x8d, 0xd3, 0xfd, 0xcc, 0x17, 0x83, 0x95, 0x67, 0x21, 0x56, 0x9e, 0x66, 0xdc, 0xcd, 0xe9, 0x44, 0x16, 0x0b, 0xdf, 0x65, 0xb3, 0x72, 0x10, 0x89, 0x59, 0x8f, 0x3b, 0xd4, 0x03, 0x77, 0x15, 0x2a, 0x11, 0xe6, 0x09, 0x5d, 0x20, 0x74, 0x69, 0xc0, 0x4c, 0x5a, 0xf8, 0xa1, 0x1e, 0x64, 0xf4, 0x3b, 0xef, 0x62, 0xae, 0xe7, 0xc1, 0xef, 0xbc, 0xaf, 0x5f, 0x8f, 0xf3, 0x11, 0x61, 0xf0, 0xc9, 0xdc, 0x6b, 0xfd, 0xf8, 0x9c, 0x57, 0x2e, 0xd0, 0x84, 0x84, 0xdb, 0x81, 0xfb, 0x71, 0x3a, 0xcf, 0x44, 0x67, 0x37, 0xe2, 0xc3, 0xaf, 0x12, 0x0e, 0xc9, 0xd1, 0x3d, 0x28, 0x09, 0x65, 0xa3, 0x2b, 0x9f, 0xcf, 0x76, 0x21, 0xb8, 0x37, 0xac, 0xf2, 0xc6, 0x22, 0xc2, 0x76, 0xc4, 0xe5, 0x16, 0x8c, 0xad, 0x70, 0x0a, 0x89, 0x66, 0xb1, 0x0d, 0x28, 0x8e, 0xa3, 0xd1, 0x75, 0xe6, 0x80, 0x47, 0x79, 0xfa, 0x81, 0x34, 0x44, 0x5c, 0x11, 0x6c, 0xe2, 0x2e, 0x8b, 0xce, 0x10, 0x2b, 0x6d, 0x57, 0xd0, 0x91, 0x95, 0xe9, 0xca, 0x4f, 0xcc, 0xa3, 0x9f, 0x0d, 0xc1, 0x78, 0x70, 0x3d, 0xd1, 0x5f, 0x18, 0x3e, 0xcb, 0x57, 0xcb, 0xd8, 0xa1, 0xcb, 0x47, 0x49, 0x21, 0xae, 0x4e, 0xf6, 0xf1, 0xf8, 0x8e, 0xae, 0xae, 0xb7, 0xf1, 0xd8, 0xd0, 0x1f, 0xc9, 0x07, 0xf5, 0xde, 0x58, 0x0c, 0xec, 0xef, 0xdf, 0xfc, 0x15, 0x16, 0x75, 0xdf, 0x49, 0xd8, 0xa5, 0xbc, 0xf7, 0x3d, 0x27, 0x5f, 0xfd, 0x18, 0x7e, 0xed, 0xe4, 0x2b, 0xbb, 0xe5, 0x46, 0xc2, 0xf0, 0x26, 0x08, 0x07, 0xec, 0x74, 0xbb, 0xa9, 0xf3, 0x31, 0x65, 0xa7, 0x41, 0xb6, 0x2d, 0x99, 0xba, 0x4c, 0x6c, 0x30, 0xc8, 0xca, 0x08, 0x4b, 0x86, 0x2e, 0x63, 0xbb, 0x71, 0x1e, 0xfa, 0x03, 0x56, 0xf5, 0x0f, 0x8b, 0xc7, 0xf0, 0x07, 0x6d, 0xcf, 0x7e, 0xaf, 0x72, 0x8f, 0x71, 0xde, 0x90, 0xf5, 0x4c, 0x8b, 0xcf, 0xfb, 0x7a, 0x04, 0x87, 0x53, 0x4e, 0xc4, 0xa2, 0xbc, 0x63, 0x3d, 0xca, 0x3b, 0xc7, 0x64, 0x4f, 0x76, 0x23, 0x17, 0x6a, 0x44, 0x6f, 0xa8, 0xd2, 0x57, 0xea, 0xe6, 0x44, 0x32, 0xd6, 0x82, 0x48, 0xa5, 0x4f, 0xfb, 0x9b, 0xa4, 0x4b, 0x5f, 0xa3, 0x03, 0xe2, 0xb6, 0xb2, 0xdb, 0x05, 0x16, 0x0e, 0xcd, 0x21, 0x06, 0xb1, 0x3d, 0xce, 0x66, 0x9f, 0x45, 0x98, 0x8f, 0xa5, 0x8b, 0x88, 0x14, 0xe9, 0xd4, 0xb8, 0x38, 0xb9, 0xcb, 0x19, 0xcf, 0x11, 0xd3, 0x91, 0xaa, 0x91, 0x48, 0xa0, 0xc9, 0xa0, 0x96, 0x57, 0x94, 0x21, 0xd0, 0x64, 0xcf, 0xe9, 0x95, 0x41, 0x46, 0x4a, 0x0f, 0x39, 0x19, 0xcc, 0x4b, 0x24, 0xd2, 0x99, 0xd8, 0x98, 0xd8, 0xd8, 0x50, 0x4f, 0xc4, 0xaf, 0xba, 0xaa, 0x72, 0x50, 0x45, 0x79, 0x59, 0x41, 0xbe, 0x2f, 0x27, 0x23, 0x9d, 0x7c, 0x86, 0x2b, 0x2f, 0x29, 0x37, 0x01, 0x6e, 0xa4, 0x25, 0x49, 0xfe, 0x48, 0x90, 0x93, 0xba, 0x5a, 0xfe, 0xb4, 0xa4, 0x97, 0xdf, 0xfd, 0xd0, 0x6d, 0xa3, 0x96, 0x8d, 0x2f, 0xbe, 0x3b, 0x21, 0xbf, 0xb9, 0x74, 0xda, 0x88, 0x09, 0xa7, 0x23, 0xcb, 0xb2, 0x7b, 0xd5, 0xcd, 0x60, 0xbb, 0xfa, 0xeb, 0xf2, 0x93, 0xc6, 0x8e, 0xf8, 0xf6, 0xb3, 0x81, 0x65, 0x3b, 0x1c, 0x0e, 0x67, 0x51, 0xbd, 0xfb, 0x8a, 0x3e, 0xe6, 0xed, 0xa1, 0x7f, 0xd3, 0xfb, 0x1d, 0xd5, 0x51, 0x78, 0x4b, 0xe8, 0x16, 0x8a, 0x47, 0xa7, 0x1f, 0xc2, 0xf0, 0x70, 0x3e, 0xc5, 0x8f, 0xe8, 0x78, 0x63, 0x28, 0x44, 0xd3, 0x97, 0x45, 0xe1, 0x41, 0xb5, 0x1c, 0x6f, 0x14, 0xde, 0xa6, 0x96, 0x33, 0x8e, 0xe0, 0x95, 0x64, 0xbd, 0x20, 0xb8, 0x48, 0xf7, 0x0d, 0x15, 0x4e, 0xdb, 0xdf, 0x24, 0xe9, 0xf7, 0xe9, 0xe9, 0x9b, 0x94, 0x2e, 0x83, 0x1e, 0xe3, 0x75, 0xbd, 0x58, 0x6d, 0x90, 0xd9, 0x12, 0x6a, 0x87, 0xc7, 0xab, 0x76, 0xf8, 0x27, 0xd4, 0x0e, 0xc7, 0x06, 0x3b, 0x1c, 0xec, 0xe1, 0x11, 0x54, 0x37, 0x3a, 0xd4, 0xf3, 0xa2, 0x5d, 0x28, 0x82, 0xbb, 0x75, 0xfc, 0x45, 0x13, 0x9e, 0xc1, 0x70, 0xca, 0x0b, 0x77, 0x9a, 0xd7, 0x53, 0x48, 0xa3, 0xac, 0x14, 0x47, 0x88, 0xbb, 0xf4, 0xbc, 0xff, 0x8d, 0xa7, 0xa2, 0x58, 0x75, 0xed, 0x35, 0x95, 0x39, 0x44, 0xc7, 0x77, 0xe3, 0xed, 0x11, 0x9c, 0xae, 0xf5, 0x0e, 0x75, 0x3f, 0x74, 0x97, 0xce, 0x4d, 0x47, 0xd3, 0x72, 0x9c, 0x6a, 0x9b, 0x97, 0x6a, 0xeb, 0x27, 0x8d, 0x0f, 0x62, 0x47, 0x05, 0xc1, 0x3c, 0xd8, 0x49, 0x41, 0x02, 0x9e, 0x45, 0x2f, 0x4b, 0x94, 0xd0, 0x4b, 0x15, 0x7d, 0xd7, 0x6f, 0x35, 0x74, 0x47, 0x24, 0x40, 0xc7, 0x3b, 0x86, 0x40, 0x1c, 0x5a, 0xcc, 0x8d, 0xf0, 0x65, 0xa4, 0xdc, 0x71, 0x74, 0x3c, 0x9c, 0x22, 0x5b, 0xbd, 0x53, 0xa2, 0x56, 0xef, 0x81, 0xea, 0x4e, 0xa3, 0x75, 0xf7, 0x5d, 0xb3, 0x4f, 0xab, 0xee, 0x3e, 0xdf, 0xba, 0x17, 0xbe, 0x55, 0xc7, 0x33, 0x18, 0x4e, 0xc7, 0x61, 0x69, 0x9f, 0x71, 0x20, 0xeb, 0x22, 0xb4, 0x49, 0xcb, 0x4b, 0xd6, 0xc3, 0xe5, 0x91, 0xbc, 0x74, 0xbd, 0x77, 0xaa, 0x67, 0x7e, 0x6a, 0x99, 0xca, 0x36, 0x63, 0x7a, 0x29, 0x05, 0xae, 0xc5, 0xb3, 0xf9, 0x41, 0xf0, 0x57, 0x34, 0xdc, 0x38, 0x3f, 0x4c, 0xb8, 0x61, 0x7e, 0x98, 0x70, 0x75, 0x7e, 0x28, 0x6b, 0x68, 0xbd, 0xc7, 0x75, 0xbc, 0x95, 0xcd, 0x0f, 0x98, 0x37, 0x24, 0xfd, 0x11, 0x1d, 0x37, 0xcc, 0x1b, 0x13, 0x6e, 0x98, 0x37, 0x26, 0xbc, 0xad, 0x9f, 0xf2, 0x87, 0xaa, 0xe5, 0x8f, 0x33, 0x8e, 0x9f, 0x61, 0x3e, 0x55, 0xd2, 0x72, 0xf6, 0xe9, 0xe9, 0x9b, 0x94, 0x89, 0x6a, 0x3f, 0x5c, 0x61, 0xec, 0x07, 0x71, 0x34, 0x77, 0x3e, 0xeb, 0x07, 0x8a, 0x6b, 0xdf, 0x55, 0xc0, 0xfa, 0x21, 0xf2, 0xbb, 0x52, 0x1d, 0xf5, 0x3b, 0xed, 0x0f, 0xc3, 0xef, 0xd1, 0xf9, 0x87, 0x44, 0xfd, 0xbe, 0x06, 0x7e, 0xd7, 0xdb, 0x5f, 0xc0, 0xfa, 0xc7, 0x50, 0x7f, 0x3e, 0xcd, 0x7f, 0x44, 0xff, 0xbd, 0xd1, 0xfc, 0xbb, 0x52, 0x16, 0xf5, 0x7b, 0x30, 0xaa, 0x7c, 0x6f, 0xd4, 0xef, 0x6d, 0xa7, 0xa8, 0x7f, 0x68, 0x54, 0xfd, 0x0f, 0x11, 0x49, 0xdb, 0x49, 0xe7, 0x29, 0x9c, 0x27, 0x48, 0x68, 0xe7, 0x1a, 0xb6, 0xae, 0x91, 0x52, 0xb8, 0x47, 0x0c, 0xf8, 0x6e, 0x86, 0x87, 0x7f, 0x47, 0xf0, 0x4b, 0xa9, 0xec, 0x32, 0xfc, 0xe9, 0x5b, 0x23, 0x78, 0x0f, 0xe5, 0x89, 0x0c, 0x7f, 0x56, 0xc5, 0x7f, 0x43, 0xf0, 0x4b, 0xa8, 0x5c, 0x32, 0xfc, 0x39, 0x86, 0xc3, 0xae, 0x3c, 0xd7, 0x44, 0xf5, 0x00, 0xc3, 0x5f, 0x50, 0xd3, 0xff, 0x9d, 0xe0, 0xec, 0x5c, 0x84, 0xe1, 0x2f, 0xaa, 0xe9, 0xef, 0x27, 0xed, 0xdc, 0x4e, 0xe7, 0x06, 0x84, 0x39, 0x97, 0xd0, 0x91, 0x5b, 0xcd, 0x8c, 0x5f, 0x9d, 0xaf, 0xf8, 0x26, 0xba, 0x13, 0x0b, 0x77, 0x9b, 0x81, 0xe6, 0x33, 0x82, 0xef, 0x4a, 0xe0, 0xe4, 0x94, 0x12, 0x1f, 0x99, 0x96, 0xf8, 0xa6, 0x77, 0xde, 0x21, 0x09, 0x48, 0x3d, 0x53, 0x68, 0xda, 0x7d, 0x6a, 0x79, 0x30, 0xff, 0xb1, 0x79, 0xfe, 0xf7, 0xd7, 0xf6, 0x18, 0xf5, 0xa4, 0x75, 0x32, 0x02, 0x4f, 0xea, 0x91, 0xa2, 0xeb, 0xe9, 0xef, 0xfb, 0x89, 0x96, 0xc7, 0xbf, 0xa5, 0x7c, 0x9b, 0xe2, 0xf8, 0xed, 0xdb, 0x19, 0xbe, 0x43, 0xd9, 0x8c, 0xef, 0xa4, 0x72, 0xca, 0xf0, 0x3f, 0xdf, 0xcc, 0xfa, 0xe5, 0x44, 0x54, 0xbf, 0xef, 0x55, 0xfb, 0xab, 0x82, 0xe0, 0x9e, 0x08, 0x2e, 0x6e, 0x8f, 0xdd, 0x8f, 0xe2, 0x83, 0x2a, 0xfe, 0xa5, 0xf2, 0x3a, 0x37, 0x52, 0x2b, 0x1f, 0xf8, 0x4a, 0x78, 0x83, 0x99, 0xaf, 0xa8, 0x6d, 0x7e, 0xd7, 0x32, 0x49, 0xcd, 0x4b, 0x78, 0x51, 0xf8, 0x79, 0x9c, 0x84, 0x59, 0xdb, 0xd9, 0x4a, 0x93, 0xa4, 0xdd, 0xc5, 0x08, 0x3b, 0x68, 0x5f, 0xbc, 0xa2, 0xa6, 0x05, 0x3d, 0xf2, 0x09, 0x95, 0x3b, 0x7f, 0x14, 0xde, 0x12, 0xfa, 0x2f, 0x8a, 0x47, 0xa7, 0x1f, 0xa2, 0xe2, 0xcb, 0x01, 0xa7, 0x72, 0xea, 0x52, 0xf5, 0x08, 0xb5, 0x3f, 0xc2, 0xc9, 0x34, 0xfd, 0x11, 0x1d, 0x6f, 0x64, 0xb8, 0x92, 0x19, 0x85, 0x07, 0xd5, 0x72, 0xec, 0x51, 0x78, 0x5b, 0x3f, 0xe5, 0x0f, 0x55, 0xcb, 0x6f, 0x34, 0xca, 0x01, 0xe8, 0x11, 0x15, 0xcf, 0x8e, 0xc2, 0x9b, 0x94, 0xd6, 0x33, 0x97, 0xdb, 0x33, 0x91, 0x95, 0x33, 0x93, 0x2b, 0xb4, 0x97, 0xac, 0xa3, 0xd9, 0x74, 0x8f, 0x84, 0xb6, 0x0f, 0xb9, 0x39, 0xa6, 0xef, 0x3d, 0xe1, 0xcf, 0xf9, 0x5b, 0xa8, 0x3c, 0xb8, 0x55, 0x9b, 0x7c, 0x1f, 0x6d, 0x07, 0xc1, 0xb9, 0xa7, 0x48, 0xfa, 0x64, 0x34, 0xa9, 0x73, 0x4f, 0xc6, 0x84, 0xf3, 0xf7, 0xcb, 0xf4, 0x9d, 0xa9, 0x77, 0x3f, 0x8b, 0x91, 0xd7, 0x45, 0xc1, 0x20, 0x18, 0x00, 0x69, 0x11, 0xd3, 0xc6, 0x07, 0xa6, 0x4d, 0xaa, 0x6e, 0xda, 0xc0, 0x6f, 0x9a, 0x05, 0x43, 0x7e, 0xea, 0x7a, 0x2e, 0x39, 0x37, 0x29, 0x4f, 0x90, 0x52, 0xd5, 0xd8, 0x6f, 0x2c, 0xec, 0x38, 0x8d, 0x16, 0x5a, 0xeb, 0xaa, 0xe6, 0x9e, 0x6a, 0x5e, 0xbe, 0x73, 0xc9, 0xdc, 0xcb, 0xe2, 0x38, 0x8f, 0xc7, 0xd5, 0x34, 0x28, 0x38, 0x67, 0xb8, 0xff, 0x04, 0xfe, 0xea, 0xaa, 0x43, 0x37, 0x8c, 0xcc, 0xb8, 0x36, 0xd1, 0xe3, 0x0a, 0x4c, 0xbc, 0xf6, 0x7c, 0x7e, 0x37, 0xd2, 0xda, 0xc6, 0xcf, 0x20, 0x6d, 0xcb, 0x44, 0xdd, 0xac, 0x6d, 0x56, 0x7a, 0xe6, 0xe3, 0x85, 0xff, 0xf2, 0x7a, 0xdb, 0xbc, 0x32, 0x9c, 0xbd, 0x21, 0x4e, 0x24, 0x96, 0x05, 0x18, 0x15, 0x70, 0xc7, 0x8d, 0x85, 0x67, 0x87, 0x5b, 0x93, 0xb9, 0x6a, 0x04, 0xb4, 0x9e, 0xa8, 0x54, 0x70, 0x6f, 0x5c, 0x4d, 0xd4, 0x15, 0x84, 0x85, 0x9a, 0xb4, 0x39, 0x9f, 0x05, 0xc0, 0xf2, 0x27, 0x47, 0x37, 0xbb, 0x05, 0xc3, 0x56, 0x0a, 0xf9, 0x1f, 0x3f, 0xa3, 0x6f, 0xeb, 0xdf, 0x78, 0xe3, 0xc4, 0x53, 0xdc, 0xba, 0x3b, 0x4d, 0x5f, 0x70, 0xef, 0x16, 0xee, 0xea, 0xd0, 0x8d, 0x6c, 0x7d, 0x59, 0x29, 0x0e, 0xa1, 0xf3, 0x2a, 0x49, 0xe5, 0x47, 0x92, 0xce, 0x5f, 0x08, 0x4e, 0xfa, 0x8b, 0x70, 0x88, 0x6c, 0xfd, 0x3c, 0x88, 0x30, 0x89, 0x39, 0x22, 0x36, 0x90, 0x98, 0x7c, 0x9f, 0xcb, 0x55, 0x94, 0x23, 0xc9, 0xe9, 0x64, 0x9c, 0x09, 0xa5, 0x2e, 0xc0, 0xe5, 0x3c, 0x65, 0x13, 0xd5, 0x70, 0xcc, 0x53, 0x4f, 0x9f, 0x64, 0x60, 0x07, 0x96, 0x53, 0x3c, 0xe2, 0x90, 0x03, 0x49, 0x99, 0xc9, 0x2e, 0x59, 0x29, 0xb0, 0x25, 0xa5, 0xe7, 0x7a, 0xa6, 0x9d, 0x38, 0xd1, 0x1e, 0xa8, 0x70, 0xe3, 0x9f, 0xa5, 0x94, 0xe7, 0x3e, 0x17, 0xfa, 0x7b, 0x52, 0x20, 0xa5, 0xbe, 0x48, 0xb2, 0x27, 0xd8, 0xb9, 0x41, 0xf1, 0x89, 0x76, 0x31, 0xf4, 0x23, 0x42, 0x3a, 0x36, 0xbb, 0x3d, 0x9c, 0xdb, 0x9b, 0x1c, 0xda, 0x96, 0xe6, 0xb3, 0xbb, 0x07, 0xab, 0x5c, 0x61, 0x08, 0x95, 0x91, 0x24, 0x95, 0x7f, 0x0c, 0xd2, 0x39, 0xc4, 0x10, 0xaa, 0xf3, 0x93, 0x54, 0xce, 0x96, 0xa7, 0xd9, 0xe2, 0xc6, 0x6f, 0x23, 0x9c, 0x43, 0xd2, 0xce, 0x75, 0x8c, 0xb8, 0xf8, 0x91, 0x8a, 0xdf, 0x83, 0x10, 0xff, 0x17, 0x5a, 0x4e, 0x32, 0x5b, 0x3b, 0x36, 0x69, 0xb6, 0xd2, 0x4a, 0xfe, 0x2f, 0x34, 0x7d, 0x32, 0xeb, 0x23, 0xb4, 0x83, 0xf6, 0x11, 0x91, 0x54, 0xfe, 0x3b, 0xfa, 0xb6, 0xaa, 0x2c, 0x58, 0x2c, 0x80, 0x4b, 0x76, 0x2c, 0x71, 0x78, 0x16, 0x82, 0xd7, 0x30, 0x10, 0x08, 0x14, 0x5d, 0x60, 0x7a, 0x61, 0x45, 0xff, 0xb1, 0xd8, 0xd2, 0x4b, 0xf2, 0x93, 0xd9, 0xde, 0x17, 0xf9, 0x5f, 0xb2, 0xdf, 0xc5, 0x7f, 0xd7, 0xfb, 0xfa, 0x9e, 0x3d, 0x27, 0x5e, 0x7f, 0x7d, 0xdf, 0x3e, 0x6e, 0x1d, 0x1d, 0x97, 0x2d, 0x8a, 0x66, 0xbb, 0x6d, 0x36, 0xd6, 0x8b, 0xff, 0x0c, 0xf5, 0x12, 0xdc, 0x01, 0x38, 0x9d, 0xcb, 0x80, 0x17, 0x30, 0x1c, 0xe6, 0xf4, 0x16, 0xad, 0xbd, 0x5f, 0x1b, 0xf3, 0x89, 0x1f, 0xa9, 0xf9, 0x88, 0x2d, 0x68, 0xc4, 0xa5, 0x64, 0x0d, 0x0f, 0x7f, 0x2e, 0xfd, 0x9e, 0xce, 0x49, 0xf6, 0x7d, 0x6e, 0xa1, 0x42, 0x4d, 0x7f, 0x97, 0x78, 0x8f, 0x78, 0x40, 0xc7, 0xe7, 0x0b, 0x59, 0x9a, 0x6c, 0x48, 0x3f, 0x26, 0xe9, 0x73, 0xe0, 0xbb, 0xd3, 0xe3, 0x39, 0x10, 0x76, 0x32, 0xd9, 0xc0, 0x78, 0xee, 0x11, 0x85, 0x28, 0x8a, 0x9b, 0x83, 0xb2, 0x7d, 0xc9, 0x75, 0x2e, 0x49, 0x4e, 0xa5, 0x72, 0x01, 0xd1, 0x6b, 0xa5, 0x12, 0x9c, 0x5b, 0x80, 0x53, 0x92, 0x41, 0x4c, 0x20, 0x6e, 0x33, 0x8d, 0x68, 0x0a, 0x4f, 0xfc, 0xa4, 0x1f, 0xbf, 0xc8, 0x73, 0x4a, 0x6e, 0x5a, 0x4d, 0x56, 0x4d, 0xaa, 0x92, 0x26, 0x1d, 0xbc, 0x3f, 0x23, 0xdb, 0x81, 0x3f, 0x70, 0xa4, 0xda, 0x93, 0x33, 0xf0, 0x6f, 0xe2, 0xb3, 0x33, 0xe8, 0x7b, 0x2a, 0xc5, 0x53, 0xe3, 0xed, 0xdd, 0x5c, 0x59, 0xc9, 0xf7, 0x34, 0x54, 0xf6, 0x72, 0xe2, 0x36, 0x7b, 0x5a, 0xef, 0x23, 0xe9, 0x83, 0x33, 0x9b, 0xea, 0xf9, 0xe9, 0x69, 0xf6, 0xd3, 0x6b, 0x9f, 0x91, 0x06, 0x6b, 0xed, 0x93, 0xfe, 0x97, 0xda, 0xc7, 0xe4, 0x55, 0xfa, 0x31, 0x95, 0xb3, 0x54, 0x26, 0xaf, 0x9c, 0x11, 0xcf, 0x60, 0x38, 0xf0, 0x68, 0xce, 0x13, 0x8b, 0x47, 0xc3, 0xb7, 0x69, 0x79, 0xf1, 0x9f, 0x39, 0xb7, 0x81, 0x83, 0xb7, 0x10, 0x3c, 0x8d, 0xe6, 0xdd, 0x8d, 0x17, 0xf7, 0xcd, 0x4b, 0xd3, 0xa4, 0xeb, 0x69, 0xf6, 0xe2, 0xab, 0x63, 0xa7, 0x91, 0x3e, 0xd7, 0xd3, 0x3c, 0x1b, 0x2b, 0x0d, 0xd1, 0x19, 0x8c, 0x93, 0xa6, 0xa9, 0x3a, 0xe3, 0x72, 0x93, 0xcd, 0x93, 0x8c, 0x32, 0x83, 0xe9, 0xf4, 0x4a, 0x6d, 0x09, 0x75, 0x85, 0xc0, 0x04, 0x21, 0x19, 0x25, 0xfb, 0x3c, 0x3e, 0x51, 0xa6, 0x0f, 0xc2, 0xe0, 0x72, 0x3d, 0xb1, 0xb9, 0xb5, 0x1e, 0x2e, 0xc7, 0xb5, 0xc4, 0xee, 0x58, 0x98, 0xe6, 0x73, 0xf5, 0x9e, 0xe0, 0xf8, 0x83, 0x8b, 0x33, 0x32, 0x5c, 0x7c, 0x19, 0xcf, 0x53, 0xe7, 0x05, 0x49, 0x42, 0x9a, 0xb7, 0xc6, 0xd3, 0x5b, 0x20, 0x6e, 0x8b, 0xf7, 0x9c, 0xfc, 0x24, 0xb3, 0x3e, 0x95, 0xff, 0x95, 0xfe, 0x2d, 0x13, 0xf4, 0x76, 0x3e, 0x49, 0x56, 0xf7, 0x47, 0xd4, 0x55, 0xfe, 0x11, 0xd3, 0xf7, 0x7a, 0xf5, 0x34, 0x47, 0xf0, 0x8a, 0xb0, 0xa2, 0x7e, 0x8b, 0xd2, 0xc7, 0x2e, 0xd1, 0xee, 0x6a, 0x2d, 0xd4, 0x75, 0x84, 0x01, 0x27, 0x3a, 0x62, 0x91, 0x36, 0x87, 0x8c, 0xb8, 0x94, 0xac, 0xe1, 0xe1, 0xcf, 0x2d, 0x3f, 0xa1, 0x73, 0x88, 0xf5, 0x89, 0x5b, 0x72, 0x69, 0x73, 0x48, 0xbe, 0x9a, 0xce, 0x21, 0x86, 0xcf, 0x97, 0x54, 0x1e, 0x15, 0xfe, 0x42, 0xfc, 0x94, 0xca, 0x80, 0x57, 0xbd, 0x4b, 0xb9, 0x2e, 0x26, 0xbe, 0xdb, 0x84, 0x7b, 0x75, 0xfc, 0x69, 0x7c, 0x67, 0x04, 0xa7, 0xfc, 0xd5, 0xab, 0xde, 0xa5, 0x34, 0xe2, 0xdf, 0xea, 0xf8, 0x73, 0x26, 0xfc, 0x61, 0x1d, 0x7f, 0xc1, 0x88, 0x53, 0x1e, 0xe0, 0x55, 0x6d, 0xec, 0x3b, 0x63, 0xd6, 0x7b, 0x08, 0x70, 0x38, 0xa3, 0x03, 0x5c, 0xdc, 0x45, 0x6f, 0x3b, 0x64, 0x07, 0x33, 0x90, 0xba, 0xa6, 0x55, 0xc3, 0xc2, 0x45, 0x17, 0xda, 0x1a, 0x34, 0xa6, 0xb0, 0xb0, 0x30, 0x4f, 0x80, 0x79, 0x4f, 0xf7, 0xfb, 0x73, 0xf3, 0xf4, 0xd3, 0x53, 0x21, 0x25, 0x51, 0xae, 0x31, 0x1f, 0x4e, 0x89, 0x9f, 0xd2, 0x2d, 0xff, 0x07, 0x94, 0xef, 0x0e, 0x2e, 0x58, 0x70, 0x10, 0xcb, 0x0f, 0xd0, 0x63, 0x80, 0xf7, 0xe0, 0x60, 0x60, 0xe9, 0xf3, 0xd7, 0xb7, 0xb7, 0x5f, 0x0f, 0xff, 0x5d, 0xdd, 0xce, 0xfd, 0xe2, 0x41, 0xe5, 0x5f, 0x47, 0x17, 0x2e, 0x3c, 0x8a, 0xe3, 0x1f, 0xdc, 0x8e, 0x1d, 0x47, 0x17, 0x2d, 0x3a, 0xaa, 0x7c, 0xb9, 0xfd, 0xba, 0x37, 0x36, 0x8d, 0x19, 0xb3, 0xe9, 0x8d, 0xeb, 0x56, 0x1f, 0xdf, 0xd4, 0xd9, 0xb9, 0xe9, 0x38, 0xf0, 0x9a, 0x9b, 0x48, 0xfb, 0xfe, 0x46, 0xed, 0x2c, 0x6f, 0x98, 0xf1, 0xe4, 0xea, 0xe8, 0xbb, 0x94, 0xfd, 0x7d, 0x43, 0x8e, 0xfe, 0x0d, 0x3e, 0xf5, 0x1b, 0xa4, 0xff, 0x93, 0x6f, 0x40, 0x5d, 0x44, 0x36, 0x3f, 0xa5, 0x7b, 0x17, 0x5e, 0x55, 0x36, 0x37, 0x1b, 0xc6, 0x24, 0x4f, 0xc7, 0xdf, 0xc6, 0x77, 0x19, 0xf0, 0x87, 0x74, 0xfc, 0x1d, 0xd3, 0x18, 0xea, 0x63, 0x8e, 0xdf, 0x35, 0xe1, 0x9f, 0xeb, 0xf8, 0x7b, 0x46, 0x9c, 0xee, 0x6d, 0x7b, 0xd5, 0xfb, 0x95, 0x2a, 0x0e, 0xed, 0xd1, 0x65, 0xa1, 0x80, 0xc9, 0x60, 0x9f, 0x76, 0x45, 0x64, 0x65, 0x2f, 0xfd, 0x9d, 0x27, 0xf9, 0x9e, 0x04, 0x99, 0x23, 0x7f, 0xf2, 0xa0, 0xe1, 0xb8, 0x9e, 0x1d, 0xd4, 0x16, 0xc2, 0x63, 0x09, 0x09, 0xb6, 0x5b, 0x79, 0x5e, 0x9a, 0x4e, 0x38, 0x4f, 0x37, 0x70, 0x34, 0x7a, 0xa4, 0xb4, 0xa4, 0xd3, 0x82, 0x65, 0x99, 0x6e, 0xea, 0x2d, 0x10, 0xe0, 0x5c, 0xd6, 0x3e, 0x70, 0xfa, 0xb9, 0xd1, 0xe9, 0xe3, 0xce, 0xb0, 0x7c, 0xc7, 0x19, 0x96, 0x9f, 0x7a, 0x46, 0xe5, 0x07, 0x2b, 0x90, 0x45, 0x94, 0x45, 0x88, 0xfd, 0x3e, 0x40, 0x16, 0x14, 0xc9, 0xd1, 0xd5, 0x45, 0xdf, 0xcb, 0x0c, 0x1b, 0x3a, 0xa4, 0xb9, 0x30, 0x39, 0x50, 0x98, 0xef, 0x4a, 0xb0, 0xda, 0xbc, 0x25, 0xf0, 0x40, 0x03, 0x6b, 0x51, 0x99, 0x55, 0xc9, 0x63, 0x97, 0x13, 0x20, 0x24, 0x7c, 0xe4, 0xec, 0x97, 0x1d, 0xb0, 0x11, 0xce, 0xe5, 0xe0, 0x93, 0x93, 0x28, 0x3d, 0xac, 0xae, 0xf2, 0x08, 0xc1, 0xd0, 0x4f, 0xb9, 0x86, 0xde, 0x2f, 0x35, 0x19, 0xd4, 0x64, 0x92, 0x7b, 0x26, 0xfb, 0xa2, 0x9a, 0xe9, 0xf3, 0x86, 0xd6, 0x6d, 0xfe, 0xd7, 0xae, 0x99, 0x11, 0x99, 0xbd, 0xf0, 0xc9, 0x7f, 0xde, 0x91, 0xe8, 0xf3, 0x26, 0x54, 0x14, 0xa4, 0x64, 0xba, 0xe3, 0x44, 0xa7, 0x28, 0x0d, 0x5a, 0xde, 0x5b, 0xa2, 0x8a, 0xe6, 0x6a, 0x55, 0x54, 0x4f, 0xde, 0xea, 0xc9, 0x18, 0xdf, 0x56, 0x3d, 0x33, 0x60, 0x5b, 0x7e, 0x18, 0xf3, 0x5b, 0x22, 0xa2, 0x7c, 0xaf, 0xa2, 0x1c, 0xfa, 0x91, 0xdd, 0x9e, 0xe9, 0x2f, 0x48, 0xf1, 0x17, 0xcb, 0xb2, 0x1c, 0xdf, 0x57, 0x36, 0xc4, 0xed, 0xb1, 0xf5, 0x8e, 0xf8, 0xa0, 0x09, 0x7f, 0x5e, 0x97, 0x35, 0xa2, 0xa7, 0xd4, 0x3b, 0xb1, 0x77, 0xf6, 0xb9, 0x13, 0xcb, 0xd2, 0x1e, 0x34, 0xa5, 0x4d, 0xa5, 0x6b, 0x17, 0x44, 0x0a, 0x4e, 0xa4, 0x69, 0x53, 0xd5, 0xdb, 0xef, 0x74, 0x11, 0xd3, 0xf3, 0x64, 0xb0, 0x3c, 0x74, 0x6d, 0xd8, 0xdc, 0xe7, 0x4c, 0x99, 0xa5, 0x79, 0xdf, 0x54, 0x2e, 0xbb, 0x6b, 0x7b, 0x67, 0x9f, 0xbb, 0xb6, 0x6c, 0xce, 0x4c, 0xd5, 0xca, 0x13, 0x6f, 0xa4, 0x75, 0x93, 0x34, 0x5c, 0x54, 0x79, 0x16, 0xb7, 0x5e, 0x1e, 0xf9, 0x16, 0xec, 0xc5, 0xaa, 0xce, 0x66, 0x69, 0xb1, 0xd7, 0x94, 0x36, 0x53, 0xef, 0x97, 0x0d, 0xa6, 0xb9, 0x99, 0xa1, 0x97, 0xb1, 0x1b, 0xff, 0x9c, 0xed, 0xfd, 0xd3, 0x32, 0x2c, 0xf0, 0xe0, 0x40, 0xdb, 0xfb, 0x0f, 0x0f, 0xa5, 0xfa, 0xed, 0x15, 0xbd, 0x8c, 0x76, 0x85, 0xde, 0xd5, 0x54, 0xa6, 0x46, 0xe1, 0x2d, 0xa1, 0x3d, 0x14, 0x8f, 0x4e, 0x3f, 0x84, 0xe1, 0xe1, 0xd1, 0x14, 0x3f, 0xa2, 0xe3, 0x8d, 0x8a, 0x8f, 0xa6, 0x9f, 0x10, 0x85, 0x07, 0xd5, 0x72, 0x86, 0x44, 0xe1, 0x6d, 0x6a, 0x39, 0x8b, 0x28, 0xbe, 0x4f, 0xc7, 0x07, 0xb3, 0x72, 0xd4, 0xf2, 0x23, 0x78, 0x93, 0x72, 0xe9, 0x29, 0xd6, 0x30, 0xd0, 0x43, 0x70, 0xff, 0x2a, 0x03, 0xe9, 0xf7, 0xaf, 0xe0, 0xbc, 0x02, 0x21, 0x61, 0x35, 0xbd, 0x83, 0x97, 0x06, 0x2f, 0x93, 0xe1, 0x56, 0x3c, 0x2f, 0x20, 0x7e, 0x0e, 0x7d, 0xf1, 0x04, 0x77, 0x2d, 0x4a, 0x3a, 0xc9, 0x04, 0x2b, 0x15, 0xc6, 0xc4, 0xd9, 0x31, 0x4a, 0xf5, 0xa4, 0x24, 0xd9, 0xd3, 0xe2, 0xd2, 0xd8, 0x7d, 0x3b, 0x76, 0xed, 0x85, 0x5e, 0x53, 0x23, 0xf4, 0xc4, 0xad, 0xde, 0x4c, 0xa3, 0x8c, 0x90, 0x9b, 0x3b, 0x71, 0xe5, 0xe4, 0xe2, 0x87, 0x1f, 0xfd, 0xc3, 0x1f, 0x36, 0xbc, 0x71, 0xe3, 0xd0, 0xf6, 0x75, 0xc7, 0xd7, 0xfe, 0xe1, 0x04, 0x77, 0xa0, 0x78, 0x6c, 0xf7, 0xd0, 0x35, 0x77, 0x87, 0x8e, 0x71, 0x7f, 0x0a, 0xfe, 0xe8, 0xd1, 0x79, 0xf3, 0x9e, 0xb8, 0x7a, 0x58, 0xe8, 0x9f, 0xf4, 0x5e, 0xfc, 0xe9, 0xb4, 0x23, 0x0d, 0xda, 0x91, 0x1e, 0xb3, 0x1d, 0xd2, 0xb9, 0x6a, 0x87, 0xba, 0xe7, 0xbe, 0x9a, 0x72, 0x89, 0x2c, 0xf5, 0xfc, 0xfb, 0x39, 0x03, 0x9e, 0xc1, 0x70, 0x7a, 0x0e, 0x7d, 0x38, 0xea, 0x9e, 0x37, 0x47, 0x52, 0x23, 0x61, 0x33, 0xbd, 0x13, 0xe8, 0x43, 0x3f, 0xea, 0xdc, 0xe3, 0x21, 0x1a, 0x2f, 0x00, 0xd7, 0x46, 0x45, 0x09, 0x89, 0x73, 0xe8, 0x3d, 0x00, 0x1b, 0xbd, 0x07, 0x00, 0x17, 0x1b, 0xb9, 0x2e, 0x0b, 0xf9, 0xa8, 0xf9, 0x70, 0x12, 0x33, 0x57, 0x22, 0xfa, 0x2e, 0x8f, 0xfc, 0x55, 0x66, 0x37, 0x06, 0x62, 0x65, 0x61, 0xc9, 0xba, 0x82, 0x99, 0x09, 0x4e, 0xf5, 0xde, 0x80, 0x37, 0x3d, 0xd5, 0xe9, 0x4b, 0xf0, 0x45, 0x6e, 0x0d, 0xda, 0x6d, 0x86, 0x6e, 0xa0, 0xb7, 0x06, 0x4d, 0x5d, 0x41, 0x2f, 0x0e, 0x5c, 0xa1, 0x76, 0xc7, 0x2b, 0xaf, 0x18, 0x3a, 0xe4, 0xa5, 0x97, 0xb4, 0x2e, 0x79, 0x6b, 0xcb, 0x5b, 0xc6, 0x3e, 0xe1, 0x37, 0x6c, 0xc1, 0x57, 0xe9, 0xdf, 0x3e, 0x5f, 0xfb, 0x76, 0xb1, 0x11, 0xbd, 0x41, 0x79, 0x23, 0xa7, 0xf1, 0x46, 0xc3, 0xf8, 0xc5, 0xa3, 0x6c, 0xd4, 0x12, 0x1c, 0x0c, 0x0e, 0x6f, 0x31, 0x4a, 0x4b, 0xe1, 0x78, 0x9c, 0xea, 0xe1, 0x04, 0x9e, 0xef, 0x80, 0xf7, 0x8d, 0x02, 0x26, 0x03, 0x0a, 0x4f, 0xe0, 0x44, 0xc3, 0x80, 0x3a, 0x1d, 0x70, 0x0b, 0xc2, 0x91, 0xed, 0xcc, 0xf6, 0xe5, 0x4b, 0x52, 0x4a, 0x09, 0x31, 0x1a, 0xd8, 0x48, 0x56, 0x57, 0xd5, 0x17, 0xd4, 0xf3, 0xf4, 0x1b, 0x24, 0xd9, 0x23, 0x17, 0x48, 0xcc, 0xeb, 0x42, 0x75, 0xf3, 0x10, 0x18, 0xd0, 0x6c, 0xef, 0xdc, 0xeb, 0xa6, 0xc2, 0x47, 0x5c, 0xb1, 0xfc, 0x82, 0x55, 0x4b, 0x3a, 0xa6, 0x92, 0x81, 0xfd, 0x2c, 0xb0, 0x72, 0x1c, 0x8c, 0x2b, 0xbf, 0x65, 0x12, 0xfe, 0x0a, 0x3e, 0xe3, 0x86, 0xe3, 0x4d, 0x37, 0x4f, 0xda, 0xe5, 0x64, 0xa3, 0x6b, 0x94, 0x31, 0x2f, 0xaa, 0x0f, 0xd6, 0xc8, 0x58, 0xbd, 0x68, 0x2a, 0xe9, 0xc2, 0x26, 0x8a, 0x46, 0x71, 0x87, 0xab, 0xf4, 0x44, 0xd0, 0xbc, 0x71, 0x5e, 0x26, 0x68, 0x96, 0xfe, 0x05, 0xbe, 0x3a, 0xa6, 0xa8, 0x9d, 0xc0, 0xcf, 0x15, 0x8f, 0xe9, 0x1e, 0xba, 0xf6, 0xae, 0xd0, 0x7f, 0x93, 0x7e, 0xbd, 0x02, 0xfa, 0x75, 0xc5, 0x70, 0xe5, 0x5f, 0xe2, 0x36, 0x25, 0xa4, 0xc9, 0xd4, 0x27, 0x54, 0xd6, 0x7c, 0xaa, 0xac, 0xbd, 0x8c, 0x22, 0xb8, 0x57, 0xc7, 0xf7, 0xa2, 0x9f, 0x69, 0x36, 0xb5, 0xf0, 0x09, 0xe5, 0xd1, 0x3e, 0xd5, 0xa6, 0x0e, 0x53, 0xfc, 0x52, 0x73, 0x7a, 0x71, 0xbb, 0x96, 0x9e, 0xe2, 0x6e, 0x1d, 0x7f, 0xd0, 0x84, 0x67, 0x30, 0x9c, 0xca, 0xf2, 0x4f, 0x63, 0xdc, 0x0d, 0xda, 0x6c, 0xac, 0x8b, 0xd8, 0xcb, 0xdf, 0xe8, 0x67, 0xf5, 0x9f, 0x50, 0x2e, 0xec, 0x53, 0xed, 0xe8, 0x6f, 0xd4, 0x7b, 0x1c, 0x3f, 0xd3, 0xef, 0x7f, 0x1a, 0xf2, 0x11, 0x3b, 0xfa, 0x5b, 0x8a, 0xb7, 0x84, 0x3f, 0xe7, 0xff, 0x46, 0xbf, 0x35, 0x57, 0xfd, 0xd6, 0x67, 0x0c, 0xb8, 0x57, 0xc7, 0x9f, 0x46, 0xaf, 0xc4, 0xc4, 0xf7, 0x6a, 0xb8, 0xb2, 0x92, 0xff, 0x1b, 0xe5, 0x77, 0xb9, 0xd4, 0x7e, 0xff, 0xef, 0x2b, 0xd9, 0xbe, 0x12, 0x4d, 0x4f, 0x78, 0x69, 0x2b, 0x1a, 0x12, 0x6c, 0x6a, 0xc5, 0x58, 0xcc, 0xc1, 0xc4, 0x9a, 0xea, 0x40, 0xe0, 0x46, 0x00, 0xf1, 0x3d, 0x54, 0xf6, 0x84, 0xc5, 0x64, 0x46, 0x51, 0xea, 0x90, 0xd3, 0xa9, 0x5d, 0xec, 0xf3, 0x71, 0x63, 0x5a, 0x9a, 0x8a, 0x0b, 0xf3, 0xfd, 0x19, 0xe9, 0xfe, 0x5c, 0x2a, 0x7e, 0xa6, 0x4b, 0x60, 0x52, 0xb2, 0xfa, 0xe2, 0x96, 0xed, 0x1c, 0xe9, 0x4c, 0x42, 0xaa, 0xa5, 0xaf, 0x6f, 0x53, 0x3c, 0xfc, 0x3d, 0xb6, 0x14, 0x57, 0x62, 0xf9, 0x88, 0x8b, 0x87, 0xb5, 0xce, 0x0c, 0xfa, 0x9a, 0x2e, 0xbe, 0xfa, 0xba, 0xab, 0x2f, 0x6e, 0x1a, 0x7a, 0xcd, 0xfe, 0xe5, 0x23, 0xae, 0xbf, 0x7c, 0x5e, 0x61, 0x8b, 0x35, 0x2d, 0x21, 0xb9, 0x7e, 0xe2, 0xd2, 0x89, 0x6d, 0x0b, 0x46, 0x06, 0x06, 0xd3, 0x1f, 0x07, 0x37, 0x2f, 0x7f, 0x7c, 0xe1, 0x8a, 0xe3, 0x9d, 0xc2, 0x33, 0xae, 0x04, 0x5f, 0x91, 0xcf, 0xd7, 0x38, 0xb6, 0x6c, 0xf0, 0x98, 0xba, 0xc2, 0x92, 0x86, 0x29, 0x2b, 0xbb, 0x66, 0x3e, 0x7a, 0xcd, 0x48, 0x6f, 0xe5, 0x88, 0x92, 0xf9, 0x71, 0x09, 0xa5, 0xb5, 0xa5, 0xfe, 0xa1, 0x33, 0x1a, 0x5a, 0x27, 0xd4, 0x05, 0x8a, 0xea, 0xa6, 0xad, 0xb9, 0x78, 0xec, 0x6d, 0x4b, 0x86, 0x8d, 0x1d, 0x85, 0x0c, 0x7d, 0x93, 0xc1, 0xfa, 0x80, 0x8e, 0xdf, 0xcb, 0x7d, 0xec, 0xcd, 0x16, 0x65, 0xb3, 0xb1, 0x9f, 0xf0, 0x9f, 0xaf, 0xd4, 0xf3, 0x0a, 0x58, 0xbe, 0x45, 0xeb, 0x57, 0x71, 0x93, 0xb1, 0xbf, 0xe9, 0xda, 0x49, 0xcb, 0x14, 0x6f, 0x43, 0xaf, 0xb0, 0x32, 0xb9, 0xa8, 0x32, 0xb5, 0x7a, 0x51, 0x00, 0xca, 0x34, 0xd4, 0x1e, 0xd1, 0x84, 0x90, 0x16, 0xce, 0x58, 0xd7, 0xd2, 0xf1, 0xcb, 0x53, 0xc7, 0xef, 0x18, 0xc3, 0x89, 0x0c, 0xaf, 0xa5, 0xf2, 0x91, 0xa7, 0xca, 0xf0, 0x31, 0x3a, 0x7e, 0x34, 0x3d, 0x7d, 0x4f, 0xe6, 0x0d, 0xa6, 0xc2, 0x4e, 0x03, 0xdf, 0x85, 0x60, 0x53, 0x04, 0xd1, 0x37, 0x64, 0xd4, 0x0b, 0x04, 0x58, 0x45, 0x70, 0xe0, 0x46, 0x38, 0x9d, 0xb0, 0x56, 0x69, 0x78, 0x5b, 0xa9, 0x13, 0xe6, 0x0b, 0x7f, 0x3b, 0x99, 0x22, 0xfc, 0xed, 0x19, 0x34, 0x40, 0x19, 0x69, 0x50, 0x46, 0xba, 0x5a, 0x86, 0x74, 0xca, 0x32, 0x94, 0xad, 0xb4, 0x0c, 0x3f, 0xba, 0xfe, 0x34, 0xef, 0x8e, 0x64, 0x49, 0x44, 0x43, 0x8b, 0x5d, 0x44, 0xaa, 0xe6, 0x74, 0x5a, 0xe4, 0x18, 0xf7, 0x45, 0xf2, 0xb5, 0x4d, 0xd5, 0xbe, 0x09, 0x4d, 0x77, 0x44, 0x12, 0xc8, 0x1f, 0xfc, 0xc8, 0xaf, 0xde, 0x10, 0x01, 0xfa, 0xda, 0xff, 0x0d, 0x11, 0xfa, 0x05, 0xc9, 0x3e, 0x17, 0x77, 0x73, 0x3f, 0x27, 0xea, 0xd5, 0xf0, 0x65, 0x4a, 0x43, 0xcc, 0xb3, 0x73, 0xf2, 0xb1, 0xcf, 0x9c, 0x4c, 0xa1, 0xeb, 0x6e, 0xf8, 0x38, 0xe1, 0x05, 0x32, 0xbd, 0x17, 0x92, 0x8e, 0x96, 0x04, 0xe3, 0xd3, 0x30, 0x12, 0xe2, 0x65, 0x81, 0x83, 0x6d, 0x55, 0xf5, 0x6e, 0x48, 0x3e, 0x51, 0x91, 0xdc, 0x72, 0x19, 0xdc, 0xcc, 0xb1, 0xdb, 0x6b, 0xf3, 0x3a, 0xd9, 0x5f, 0xb4, 0x3b, 0x2f, 0x18, 0xcf, 0x85, 0x5d, 0xd9, 0x2c, 0x9a, 0x8e, 0x5e, 0x9b, 0x9d, 0x16, 0x23, 0x41, 0x57, 0xd0, 0xc9, 0xf3, 0x7c, 0x3a, 0x9f, 0x9e, 0xef, 0x4a, 0xcc, 0x4b, 0x4a, 0x80, 0x3d, 0x3d, 0x0c, 0x3e, 0xd6, 0x20, 0xbc, 0x00, 0x7d, 0x11, 0xe1, 0x62, 0x4e, 0x1f, 0x9a, 0xb0, 0x9c, 0xcc, 0xef, 0xff, 0x07, 0xde, 0xa4, 0x2c, 0x7b, 0x5b, 0x79, 0x04, 0xee, 0x80, 0x70, 0x85, 0xe4, 0x5b, 0x1c, 0xea, 0x35, 0x90, 0xab, 0x84, 0xbf, 0x29, 0x2f, 0xe1, 0xe1, 0xa1, 0x0f, 0x43, 0xb5, 0xf8, 0x09, 0x25, 0x3f, 0xc7, 0xcb, 0x7d, 0xc1, 0xcd, 0x7f, 0x86, 0xde, 0x02, 0x79, 0x64, 0xbc, 0x2e, 0x77, 0x19, 0x4c, 0xbe, 0xa8, 0x74, 0x1e, 0xed, 0x33, 0x37, 0xb2, 0x88, 0x0e, 0x33, 0xc8, 0x20, 0xd1, 0x61, 0x9a, 0x6c, 0x7e, 0x6d, 0xc4, 0x89, 0xee, 0xd2, 0xf0, 0x7b, 0x8c, 0xb8, 0x94, 0xac, 0xe2, 0x0f, 0x84, 0xff, 0x29, 0xfa, 0xa9, 0x4e, 0x0b, 0xa8, 0xfb, 0x11, 0x23, 0x62, 0xe2, 0xbb, 0x4d, 0xb8, 0x57, 0xc7, 0x9f, 0xc6, 0x13, 0x23, 0x38, 0xdd, 0x8f, 0x08, 0xa8, 0xfb, 0x11, 0x46, 0xfc, 0x5b, 0x1d, 0x7f, 0xce, 0x84, 0x3f, 0xac, 0xe3, 0x2f, 0x18, 0x71, 0xaa, 0xef, 0x03, 0xea, 0x7e, 0x84, 0x11, 0xef, 0xd2, 0xf1, 0x97, 0xf0, 0xe4, 0x98, 0xed, 0x39, 0xa4, 0xa5, 0x27, 0x36, 0xab, 0x9f, 0x7e, 0x6f, 0x40, 0xb5, 0x55, 0x27, 0x18, 0xd2, 0xe7, 0xe9, 0xf8, 0xdb, 0xfd, 0x94, 0xb3, 0xd7, 0x54, 0xef, 0xf3, 0x2a, 0x0e, 0xb6, 0xc0, 0x44, 0xd5, 0x1e, 0x99, 0x88, 0x4c, 0xa7, 0x4b, 0x5a, 0x5a, 0x7a, 0x0e, 0x15, 0x50, 0x39, 0xfa, 0x8a, 0x53, 0xa4, 0xad, 0xd0, 0xd3, 0xee, 0x3d, 0x83, 0x72, 0x77, 0x12, 0x55, 0x30, 0x60, 0xda, 0xc8, 0x77, 0x10, 0x7b, 0x2b, 0x66, 0xbf, 0x12, 0x7b, 0x4b, 0xeb, 0xa7, 0xd7, 0x23, 0xfd, 0x04, 0xe7, 0x6b, 0x78, 0x7c, 0xf4, 0xf9, 0x1a, 0x49, 0x73, 0x8f, 0xb1, 0x2f, 0xa5, 0x64, 0x63, 0x99, 0xd4, 0xf6, 0x09, 0xa8, 0xb6, 0xcf, 0xc4, 0x3e, 0xfa, 0x96, 0xd5, 0xfb, 0x8e, 0x5e, 0xef, 0x2d, 0x6a, 0xde, 0xc8, 0x5b, 0xe0, 0x42, 0x55, 0x5e, 0x32, 0xa2, 0xde, 0x96, 0x16, 0xaa, 0xe3, 0xc6, 0xde, 0xa5, 0x11, 0x4b, 0xc0, 0x22, 0x50, 0x79, 0x2c, 0x62, 0x72, 0xca, 0x2d, 0x8b, 0x89, 0xef, 0x36, 0xe1, 0x5e, 0x1d, 0x7f, 0x9a, 0xbb, 0x36, 0x82, 0x53, 0x79, 0x64, 0xf8, 0x73, 0x46, 0x9c, 0xce, 0xb9, 0x22, 0x75, 0x0f, 0xf6, 0xda, 0x3e, 0x73, 0xce, 0xab, 0x6c, 0xb6, 0x08, 0xb4, 0x6d, 0x45, 0xea, 0x1e, 0xec, 0xb5, 0x54, 0xdf, 0x3a, 0xc9, 0xb7, 0xd8, 0x89, 0xbe, 0xf5, 0xa2, 0x9c, 0x60, 0xa6, 0xd7, 0x1d, 0x4f, 0xd4, 0x87, 0xea, 0xb5, 0x55, 0xdf, 0x07, 0x2d, 0xcc, 0x2d, 0xcc, 0x01, 0xe5, 0xef, 0xa3, 0x3b, 0xa0, 0x92, 0x4c, 0xd5, 0x85, 0x95, 0x3a, 0xb7, 0xc3, 0xda, 0x71, 0x89, 0x68, 0xff, 0xb5, 0x27, 0xc3, 0xa2, 0xdc, 0x62, 0xb1, 0x2b, 0x3f, 0xb6, 0xa5, 0x27, 0xbd, 0x86, 0x3f, 0x55, 0x8a, 0x7f, 0xed, 0x49, 0xb1, 0xe0, 0x6e, 0x8b, 0x03, 0x9f, 0x67, 0x4d, 0x71, 0x1d, 0x57, 0x0a, 0xd2, 0x1c, 0xdc, 0xf8, 0xb4, 0xc4, 0xd0, 0xfb, 0x8e, 0x34, 0xee, 0x4f, 0xdc, 0x01, 0x87, 0x23, 0xf4, 0xf3, 0xf4, 0x14, 0x1c, 0x4e, 0x48, 0x08, 0x4d, 0x18, 0xb0, 0x1d, 0x69, 0xb4, 0x1d, 0xe9, 0x6a, 0x3b, 0xa4, 0xff, 0x64, 0x3b, 0x48, 0x3f, 0xd1, 0x76, 0xd0, 0xbe, 0x2c, 0x56, 0xf5, 0x57, 0xa8, 0x4f, 0x5f, 0xd2, 0x34, 0x74, 0x1c, 0x8a, 0xd5, 0xfb, 0x9e, 0x0a, 0xb5, 0x09, 0xc9, 0xbf, 0x85, 0x7f, 0xd0, 0xf1, 0x2c, 0x51, 0xef, 0x87, 0xbe, 0x1f, 0x13, 0xdf, 0x6d, 0xc2, 0xbd, 0x3a, 0xfe, 0x34, 0xfa, 0x9f, 0x08, 0x4e, 0xcb, 0x2f, 0x51, 0xcb, 0xff, 0x1f, 0x18, 0x2b, 0x86, 0x93, 0x3e, 0x4a, 0x82, 0xbd, 0xea, 0x38, 0x1e, 0xe9, 0x7e, 0x47, 0xb5, 0xc3, 0xf7, 0xd2, 0x5c, 0x17, 0x2f, 0xab, 0xa1, 0x6e, 0xb4, 0x53, 0x00, 0x70, 0x3c, 0x0a, 0x5d, 0xc3, 0x7d, 0xf0, 0x76, 0xe8, 0xcb, 0x83, 0x19, 0xb9, 0x76, 0xdc, 0xe0, 0xce, 0x8e, 0xb3, 0x67, 0xba, 0x71, 0xbd, 0xcd, 0xef, 0x7d, 0x89, 0x6b, 0x80, 0x4d, 0x16, 0x7e, 0xae, 0x37, 0x25, 0xf4, 0x97, 0xfc, 0xb6, 0x9c, 0x9c, 0xb6, 0x7c, 0x2e, 0x2d, 0x31, 0x9b, 0xf9, 0xe7, 0xf9, 0x84, 0xd6, 0x07, 0xfb, 0x91, 0x25, 0xec, 0xdc, 0x3e, 0x74, 0x47, 0x9f, 0x73, 0xfb, 0x98, 0x6d, 0x05, 0x9c, 0xe8, 0xf8, 0x7f, 0x50, 0x79, 0x2b, 0x51, 0x75, 0xfc, 0xc7, 0x86, 0x6f, 0xfb, 0x42, 0x4f, 0xff, 0xac, 0xf1, 0x9b, 0xa9, 0x6e, 0x2b, 0x51, 0xef, 0xc4, 0xfe, 0xd5, 0x90, 0xfe, 0x61, 0x3d, 0xfd, 0x0b, 0xa6, 0xf4, 0x19, 0x9c, 0xd6, 0xb6, 0x23, 0xe8, 0x2f, 0xd1, 0xe3, 0xa4, 0x1c, 0xa3, 0xed, 0x3f, 0x82, 0xd4, 0xbc, 0x62, 0x63, 0x88, 0xbe, 0x59, 0x0f, 0xed, 0x8b, 0xc2, 0x83, 0x21, 0x7a, 0x1e, 0x11, 0x3a, 0x10, 0x85, 0xb7, 0x85, 0xe8, 0x9e, 0x84, 0x92, 0x4b, 0xdb, 0x70, 0x5c, 0xc7, 0x87, 0xb2, 0x72, 0x94, 0x5f, 0x1a, 0xfb, 0x87, 0x9e, 0x67, 0xaf, 0xa5, 0xf8, 0xdb, 0x51, 0x78, 0x53, 0xe8, 0xab, 0xd3, 0xf8, 0x16, 0xb7, 0x8e, 0xbf, 0xa8, 0x8e, 0x77, 0x2f, 0xc1, 0x3f, 0x8d, 0x35, 0xde, 0xda, 0x61, 0x35, 0x8c, 0xb7, 0xd4, 0xdf, 0x78, 0xff, 0xe1, 0x44, 0xe8, 0x5f, 0x07, 0x33, 0x7c, 0x64, 0xbc, 0x93, 0xd4, 0xf1, 0xb6, 0xc2, 0x78, 0x37, 0x86, 0x5e, 0xe3, 0x1a, 0xf9, 0x39, 0x31, 0xc6, 0x1b, 0x55, 0x91, 0xfa, 0xae, 0xa2, 0x72, 0x5a, 0xaa, 0xca, 0xe9, 0x5b, 0x06, 0xdc, 0xab, 0xe3, 0x7b, 0xd1, 0xef, 0x0d, 0x78, 0x06, 0xc3, 0xe9, 0x18, 0xbc, 0xdf, 0xe7, 0x6e, 0x38, 0x4d, 0xa3, 0xf9, 0xb6, 0x96, 0xc9, 0x4c, 0x86, 0xf7, 0x85, 0x9c, 0x46, 0x2e, 0x49, 0x8a, 0x2e, 0x95, 0x61, 0x26, 0xbb, 0xdc, 0x6c, 0xdf, 0x5d, 0x86, 0xb7, 0x99, 0xb0, 0x3d, 0x08, 0x9b, 0x87, 0xc2, 0x55, 0x29, 0xbd, 0x7f, 0xe1, 0x7d, 0x27, 0x7f, 0x6a, 0xb7, 0xf2, 0xde, 0xde, 0xef, 0x84, 0x2c, 0x71, 0x6c, 0x9e, 0xef, 0xe4, 0x8e, 0xdd, 0xe7, 0xb9, 0xb2, 0x1c, 0xc2, 0x45, 0xbb, 0x51, 0xcc, 0xf6, 0x3d, 0xad, 0xb5, 0x8f, 0xc8, 0xe0, 0x55, 0x54, 0x06, 0x4b, 0x55, 0x19, 0xfc, 0x48, 0xc5, 0xbf, 0x36, 0xe2, 0x84, 0x67, 0x7c, 0xa4, 0x73, 0xf0, 0xcb, 0x28, 0x67, 0x2f, 0xa3, 0xb6, 0xcd, 0x93, 0x33, 0x50, 0x4c, 0x7c, 0x97, 0x09, 0xdf, 0xa6, 0xe3, 0x4f, 0x99, 0xf0, 0x5b, 0x75, 0x7c, 0x9f, 0x09, 0x5f, 0xa3, 0xe3, 0xfb, 0x4d, 0x78, 0x87, 0x8e, 0x3f, 0xdf, 0x4f, 0xf9, 0x07, 0x4c, 0xf8, 0x27, 0x3a, 0x7e, 0xf0, 0x42, 0x03, 0x2e, 0xb7, 0xe9, 0xf8, 0xcb, 0x17, 0xb2, 0xbd, 0x1d, 0x8a, 0x0b, 0x76, 0xa2, 0xb7, 0x1a, 0xd1, 0xd2, 0xe7, 0xb2, 0x9c, 0xe0, 0xeb, 0x8d, 0x90, 0xcb, 0x2c, 0xd8, 0x1f, 0x21, 0x4b, 0xa2, 0x28, 0xc9, 0x62, 0x0f, 0x3c, 0xc5, 0xc5, 0x22, 0x9a, 0x83, 0x24, 0x89, 0xda, 0x65, 0xb9, 0xec, 0xd9, 0x19, 0xb8, 0x49, 0x4c, 0x87, 0x67, 0x42, 0xc5, 0x03, 0x24, 0x24, 0x49, 0x3a, 0xe9, 0x5e, 0x09, 0xa1, 0x9b, 0x3e, 0xb8, 0xd1, 0x6a, 0xf7, 0xfb, 0x53, 0x0b, 0xf2, 0x0b, 0x0a, 0x13, 0x2c, 0x52, 0x06, 0x31, 0xe8, 0x78, 0xfd, 0x35, 0x0f, 0x66, 0x46, 0x5c, 0xb2, 0x8b, 0x3a, 0xc9, 0x71, 0xf0, 0xf4, 0x46, 0x2b, 0xb1, 0xe4, 0xea, 0xe1, 0xa9, 0x65, 0x2e, 0x23, 0xda, 0xdc, 0xce, 0x2b, 0x1f, 0x5d, 0x50, 0x9e, 0x56, 0x32, 0x38, 0xb7, 0xac, 0xb3, 0xad, 0x39, 0x33, 0x74, 0x6c, 0xca, 0x6b, 0x57, 0x4e, 0xbb, 0x71, 0x46, 0xc5, 0xb4, 0xcc, 0x2c, 0x6b, 0x72, 0xc1, 0x98, 0xc9, 0x17, 0x35, 0x36, 0x5f, 0x3a, 0xae, 0x6c, 0xe8, 0xe6, 0xcf, 0x76, 0xcf, 0x9e, 0x19, 0x9c, 0xd6, 0x5a, 0x91, 0x96, 0x94, 0x69, 0xc5, 0x47, 0x85, 0xcc, 0x41, 0x43, 0x0b, 0x03, 0xf5, 0xfe, 0x84, 0x44, 0xdf, 0xa0, 0x6c, 0x6e, 0xc9, 0xc4, 0x09, 0x8d, 0xf3, 0x37, 0x4d, 0x0d, 0x5d, 0xe5, 0x4e, 0xef, 0xca, 0xab, 0x2f, 0x48, 0xf2, 0x0d, 0x9f, 0x3f, 0xbc, 0x79, 0xc9, 0xf9, 0x0d, 0x42, 0xc7, 0xf2, 0x65, 0x39, 0x05, 0x39, 0x29, 0x4e, 0xa4, 0xd9, 0x53, 0xa4, 0x5f, 0xb4, 0xfe, 0xc2, 0x7f, 0x9e, 0x66, 0xec, 0xdf, 0x63, 0x3a, 0xfe, 0xeb, 0x48, 0xff, 0x0a, 0x33, 0xa5, 0x46, 0x1d, 0x3f, 0xc1, 0xf0, 0xf0, 0x35, 0x24, 0x7d, 0xb7, 0xd4, 0x48, 0xe6, 0x7b, 0x19, 0xdd, 0x2c, 0xff, 0xcd, 0x45, 0x91, 0x72, 0xde, 0x92, 0x7a, 0xf4, 0xf4, 0xbf, 0x35, 0x8e, 0x93, 0xf4, 0x53, 0x1d, 0xff, 0x9d, 0x86, 0x43, 0x7b, 0xf4, 0xf1, 0x0e, 0xd0, 0xf6, 0x50, 0xa9, 0xd2, 0xf3, 0x3c, 0xac, 0xe7, 0xf9, 0xbd, 0x51, 0x16, 0x0c, 0xf8, 0x1f, 0x4d, 0xf8, 0x9f, 0x74, 0xfc, 0xc3, 0x19, 0xb1, 0xeb, 0xfe, 0xd3, 0x8c, 0x81, 0xea, 0x3e, 0x30, 0x23, 0x96, 0x1c, 0xee, 0x31, 0x95, 0x75, 0xa3, 0x86, 0x0b, 0xd3, 0x4c, 0xf8, 0x0d, 0x2a, 0x5e, 0x80, 0x9e, 0x47, 0x33, 0x88, 0x42, 0x92, 0xd0, 0xe1, 0x19, 0xda, 0x0e, 0x74, 0x93, 0x6e, 0xa7, 0xb2, 0xb2, 0x0f, 0xe9, 0x65, 0x43, 0x1a, 0x32, 0x9d, 0x51, 0x45, 0xb8, 0x56, 0x28, 0x16, 0x8e, 0x11, 0xe9, 0x75, 0x10, 0xfd, 0xd7, 0x81, 0xb6, 0x06, 0xd3, 0x46, 0x60, 0xc9, 0xd2, 0x8a, 0x65, 0xa9, 0x06, 0xb6, 0xfa, 0x32, 0x93, 0xe0, 0xe2, 0x75, 0x09, 0xb1, 0xec, 0x44, 0xd5, 0x4e, 0x1a, 0x84, 0xac, 0x16, 0xeb, 0x32, 0x22, 0xa6, 0x48, 0x92, 0x89, 0x41, 0x27, 0xf2, 0x22, 0xbc, 0xa1, 0x84, 0x3d, 0x26, 0x90, 0x55, 0xcb, 0x74, 0x64, 0xb1, 0x2c, 0x06, 0x83, 0x8f, 0x09, 0xf5, 0x5c, 0x10, 0xea, 0x9a, 0xa8, 0x2c, 0x42, 0xcc, 0x2c, 0xaa, 0xa1, 0x48, 0xc5, 0x7b, 0x21, 0xbd, 0x55, 0xdf, 0x31, 0x2a, 0xdf, 0x5f, 0x92, 0x57, 0x5f, 0x9e, 0xa7, 0xde, 0xaa, 0x97, 0xeb, 0x93, 0xc1, 0x45, 0x09, 0x3d, 0xb8, 0xa8, 0x2f, 0x70, 0x10, 0x9b, 0xd1, 0x43, 0x8f, 0xe2, 0x35, 0x21, 0x57, 0x9f, 0x62, 0xd6, 0x97, 0x63, 0x10, 0xf6, 0x56, 0xac, 0xc9, 0x7a, 0x85, 0xea, 0x23, 0x98, 0xba, 0x69, 0xe2, 0x76, 0x65, 0x66, 0x66, 0xd5, 0xa7, 0x1f, 0x4f, 0x4c, 0xa6, 0x72, 0xff, 0xcf, 0x4b, 0xa7, 0xfb, 0xb8, 0x40, 0x49, 0x44, 0xea, 0x5d, 0xbe, 0x84, 0x0b, 0x9c, 0x45, 0x9e, 0xe1, 0x9b, 0x06, 0x8f, 0xa8, 0xea, 0x79, 0xf6, 0xfa, 0xeb, 0x8b, 0xd7, 0x68, 0xd2, 0xef, 0x27, 0x33, 0x64, 0xd2, 0xea, 0xb2, 0xcd, 0xce, 0x80, 0x77, 0x38, 0xd7, 0x5d, 0x8e, 0x57, 0x62, 0x81, 0x53, 0x0e, 0xf1, 0x30, 0x13, 0x9e, 0xf2, 0x4c, 0xaa, 0x1f, 0x92, 0xc7, 0xed, 0xee, 0x5a, 0xae, 0xcf, 0x05, 0x9e, 0xdb, 0xe4, 0xf1, 0x7e, 0x90, 0x73, 0xf9, 0xd4, 0x9a, 0xb9, 0x93, 0x5b, 0x1c, 0x3f, 0xef, 0xa6, 0xf3, 0xa1, 0x75, 0xe2, 0x84, 0xaa, 0xf2, 0xbf, 0x78, 0x98, 0x6f, 0x99, 0x16, 0x65, 0xa3, 0x68, 0xa3, 0xfa, 0x62, 0x1c, 0x6e, 0xeb, 0xdc, 0xe3, 0x06, 0x2b, 0x7c, 0x30, 0xe9, 0x78, 0xaa, 0x39, 0xc0, 0x0a, 0x77, 0xc0, 0xdf, 0x9c, 0xd8, 0x8a, 0xb2, 0x30, 0xb6, 0xd2, 0x67, 0x8c, 0x90, 0xa6, 0x2a, 0x86, 0x8a, 0x50, 0xfb, 0xd0, 0x8a, 0x35, 0x65, 0x32, 0xb7, 0xd3, 0x8e, 0xe1, 0x19, 0x21, 0xe9, 0xd0, 0x1e, 0x0e, 0xce, 0xa5, 0xb2, 0xe8, 0xfb, 0x45, 0x2d, 0xa7, 0x95, 0x2c, 0x19, 0x56, 0x4c, 0x4b, 0xb0, 0x9a, 0x4a, 0x40, 0xfd, 0x16, 0x90, 0x4b, 0x0a, 0x68, 0x39, 0x8d, 0x02, 0xe8, 0x30, 0xaa, 0x5a, 0x6a, 0xa1, 0xb9, 0x90, 0xe0, 0xf0, 0x3e, 0xf9, 0xed, 0x02, 0xe9, 0x01, 0xdb, 0xa9, 0xcb, 0x41, 0x91, 0x62, 0xe0, 0x00, 0x2c, 0x71, 0x4c, 0xc7, 0xe8, 0x91, 0xc3, 0x87, 0xe5, 0x6b, 0x0a, 0x90, 0xbd, 0x84, 0x34, 0x28, 0x40, 0xb6, 0xbb, 0xd5, 0x82, 0xfb, 0x57, 0x80, 0x15, 0xba, 0x92, 0xac, 0xa6, 0x67, 0x61, 0x34, 0xcc, 0x49, 0x94, 0x52, 0xe4, 0x66, 0x96, 0x8d, 0x29, 0xea, 0xbc, 0x24, 0x98, 0xd9, 0x3e, 0x80, 0x52, 0xf4, 0xab, 0x8a, 0xf3, 0xde, 0x41, 0xc1, 0x9c, 0x72, 0x87, 0x3d, 0x5d, 0x8e, 0xd2, 0x92, 0xa1, 0xe7, 0x92, 0x5c, 0xb5, 0xe7, 0x2f, 0x6f, 0xad, 0xed, 0x4f, 0x49, 0xb6, 0x32, 0x2d, 0xaa, 0xc4, 0xbd, 0x3b, 0x76, 0xaa, 0xc3, 0x91, 0x16, 0x6b, 0x4d, 0xc2, 0x95, 0xa7, 0xb5, 0xb6, 0xed, 0xd4, 0x70, 0x8e, 0x57, 0xf1, 0x2a, 0xf3, 0x9a, 0x8a, 0xc3, 0xba, 0x1e, 0xaa, 0x34, 0xea, 0x64, 0xce, 0xd9, 0xc5, 0xf0, 0x60, 0xf8, 0x73, 0x09, 0xd1, 0xf4, 0x15, 0x74, 0x8d, 0xdf, 0x85, 0x6f, 0x31, 0xe0, 0x1d, 0x3a, 0xfe, 0x3c, 0xe0, 0x84, 0x29, 0x51, 0x9c, 0xc8, 0x70, 0x3c, 0x4a, 0x25, 0xda, 0x47, 0xee, 0xdc, 0x93, 0x4e, 0xc4, 0x24, 0x6e, 0x62, 0x27, 0x67, 0xe3, 0x2a, 0x0a, 0x38, 0x4c, 0x9f, 0x82, 0xe8, 0x7f, 0x85, 0x97, 0x21, 0x44, 0x88, 0x5d, 0x24, 0x49, 0x2d, 0xb2, 0x71, 0x3c, 0x67, 0xe3, 0x61, 0xe4, 0x31, 0x12, 0x71, 0x8f, 0xc5, 0xc1, 0xf1, 0xce, 0x78, 0x8e, 0xe8, 0x13, 0x04, 0x5e, 0x05, 0x38, 0xfb, 0xf4, 0x38, 0x6c, 0xb7, 0xe7, 0x76, 0x0a, 0x44, 0xa4, 0xa5, 0xe9, 0x70, 0x65, 0xca, 0x27, 0x81, 0x18, 0xa6, 0x9f, 0x5d, 0x66, 0x3f, 0x1c, 0x3e, 0xb4, 0x1a, 0xf2, 0x09, 0x44, 0x0d, 0xf5, 0x9b, 0x8f, 0x88, 0x9c, 0x9a, 0x0d, 0x44, 0x50, 0x2f, 0x81, 0x08, 0x5e, 0x5e, 0x5a, 0x1a, 0x19, 0xa2, 0x19, 0x69, 0xd3, 0xa7, 0x4c, 0x1e, 0x3f, 0x76, 0x54, 0xfb, 0xb0, 0xa1, 0x43, 0x5a, 0x6a, 0xab, 0xcb, 0x4a, 0xf2, 0x72, 0xb3, 0x33, 0x1d, 0xa9, 0x8e, 0xd4, 0xa2, 0x40, 0x42, 0x02, 0x30, 0x45, 0xfd, 0x7c, 0xb5, 0x36, 0x5f, 0x3d, 0x8e, 0x75, 0xe2, 0x2c, 0x9e, 0x1d, 0xb8, 0xa6, 0x64, 0xab, 0xca, 0x6a, 0x00, 0xb1, 0x4c, 0xf1, 0xa8, 0xc2, 0x99, 0x24, 0xf9, 0xb9, 0xdd, 0x57, 0x3e, 0x7a, 0x49, 0xf9, 0xcc, 0x71, 0xf5, 0x33, 0xdb, 0x8b, 0xb8, 0x1d, 0x59, 0x83, 0xa7, 0xd4, 0x35, 0x4d, 0x6f, 0x2b, 0x92, 0x39, 0xb1, 0xac, 0x73, 0xfe, 0x90, 0x11, 0x17, 0x34, 0x07, 0x5c, 0x4e, 0xb7, 0x54, 0xe3, 0xef, 0x9a, 0xd7, 0x5d, 0xb3, 0xfc, 0x95, 0xc6, 0x8c, 0x69, 0x97, 0x5d, 0xd9, 0x3c, 0x72, 0xf9, 0xe4, 0xb2, 0x60, 0x94, 0x90, 0xbe, 0xb0, 0xec, 0xca, 0x3b, 0xfc, 0x75, 0x4f, 0x5d, 0x39, 0xf9, 0xb6, 0xcb, 0x9a, 0x13, 0xbd, 0xf8, 0x8f, 0xa2, 0xaf, 0x76, 0x44, 0xe1, 0xe4, 0xce, 0xdc, 0x91, 0xdd, 0xa3, 0x8f, 0xb6, 0x5c, 0x3a, 0xae, 0xb4, 0xb0, 0xa3, 0x7b, 0xd8, 0x8a, 0xa9, 0xd7, 0x9d, 0x57, 0x9c, 0x91, 0x9b, 0x61, 0xb3, 0x0c, 0x2b, 0x6e, 0x09, 0x24, 0x2e, 0x5a, 0xda, 0x3c, 0xa5, 0x2e, 0x2d, 0x6f, 0x64, 0xf7, 0xc8, 0xe6, 0xe5, 0x17, 0xb6, 0x08, 0xe5, 0xcb, 0xaf, 0xca, 0x2d, 0xca, 0x25, 0xf2, 0x3a, 0x61, 0xe2, 0x9c, 0x69, 0x53, 0x66, 0x0f, 0x5d, 0xb9, 0xa7, 0xa7, 0x2c, 0x9b, 0xc8, 0xc6, 0xfa, 0xf0, 0x97, 0x82, 0x9b, 0xae, 0xe3, 0x83, 0xa8, 0x6c, 0x1c, 0x56, 0xdf, 0xd9, 0xad, 0x27, 0xbc, 0xd0, 0x80, 0x13, 0x5e, 0xf8, 0x98, 0x8a, 0x7f, 0x6d, 0xc4, 0x09, 0x2f, 0x7c, 0x8c, 0xea, 0xc3, 0xf5, 0xca, 0x56, 0xc0, 0x51, 0x1c, 0xe1, 0x4f, 0xbf, 0x62, 0x0a, 0x27, 0x31, 0x3e, 0x0e, 0xe8, 0x78, 0x75, 0x69, 0x2a, 0x2f, 0x21, 0x9f, 0xfa, 0xbc, 0x3b, 0x59, 0x05, 0x53, 0x39, 0x19, 0x95, 0x12, 0x03, 0x95, 0xe0, 0xa2, 0xa0, 0x3d, 0xf2, 0x0e, 0x10, 0xed, 0x31, 0xaf, 0x53, 0x73, 0x64, 0xb8, 0x18, 0xb8, 0x2f, 0x1b, 0xbf, 0x3e, 0x2f, 0xba, 0x73, 0x07, 0x4a, 0x1d, 0xfd, 0x7c, 0xbb, 0x12, 0x09, 0x44, 0x73, 0xc1, 0x73, 0xed, 0xa8, 0x0c, 0xf4, 0x99, 0x11, 0xe4, 0x42, 0x90, 0xc9, 0xfc, 0x5e, 0x3b, 0xce, 0x15, 0xc8, 0xf3, 0xe5, 0x97, 0xe6, 0xe7, 0xe5, 0x5a, 0x89, 0x7e, 0xca, 0x07, 0xd7, 0x01, 0xd4, 0x57, 0x47, 0xae, 0x24, 0xd7, 0xea, 0xe3, 0x2b, 0xeb, 0x3b, 0x9f, 0xea, 0x9d, 0xcd, 0xba, 0x16, 0x4c, 0x9f, 0x20, 0x15, 0xc8, 0x70, 0x12, 0x54, 0x8d, 0xc7, 0x0c, 0xea, 0x69, 0x5b, 0xb7, 0xf9, 0xfa, 0x2b, 0xcf, 0x5b, 0x59, 0xdb, 0xba, 0x7d, 0x7e, 0xd7, 0xad, 0x73, 0xeb, 0xe6, 0x4c, 0xcf, 0x0c, 0x5e, 0xd2, 0x59, 0x34, 0xa6, 0xcc, 0x29, 0xa5, 0xc7, 0xc5, 0x57, 0xe4, 0x04, 0x07, 0x05, 0x0a, 0x2a, 0x27, 0x8d, 0x18, 0x9c, 0x6e, 0xb5, 0x14, 0x5f, 0x7b, 0xf4, 0xfb, 0xd6, 0xaa, 0x23, 0xc7, 0xf6, 0x3f, 0x31, 0xa6, 0xfd, 0xf6, 0xf6, 0xa9, 0x23, 0x56, 0xbf, 0x70, 0xf9, 0x03, 0x5f, 0x8c, 0xb8, 0xa4, 0x75, 0xf9, 0xf9, 0xb5, 0x89, 0xee, 0xf1, 0x44, 0x76, 0xa7, 0x8e, 0x2d, 0x93, 0xdc, 0x79, 0x35, 0xbe, 0x29, 0x45, 0xe2, 0x31, 0xe6, 0x27, 0x99, 0xbf, 0x8d, 0xce, 0xf7, 0x4a, 0x36, 0xdf, 0xd1, 0x7d, 0x08, 0xe9, 0xf8, 0x36, 0x1d, 0x7f, 0xaa, 0x1f, 0x7c, 0x8f, 0x09, 0x3f, 0xa4, 0xe3, 0x87, 0x01, 0x07, 0xaf, 0xb7, 0x80, 0x0b, 0x16, 0x62, 0x01, 0x74, 0x07, 0x6d, 0xa5, 0x18, 0x8b, 0xe9, 0x98, 0x4c, 0xc1, 0x0e, 0xa6, 0x04, 0xf2, 0x22, 0xc7, 0x16, 0x1c, 0x61, 0x18, 0xfa, 0xb1, 0x45, 0xae, 0xce, 0x03, 0x7c, 0x70, 0x47, 0xe2, 0xd4, 0xc9, 0xfc, 0x40, 0x17, 0xac, 0x85, 0x01, 0x7f, 0x21, 0x1c, 0x6e, 0xa4, 0x47, 0x1d, 0x6e, 0x70, 0x6c, 0xde, 0x65, 0x1b, 0xd6, 0x01, 0x7a, 0xa4, 0x31, 0xd1, 0xea, 0x4a, 0x48, 0xf0, 0xd7, 0x76, 0xd6, 0x8f, 0x5a, 0x3c, 0x3a, 0x50, 0xd8, 0x71, 0xd9, 0xd0, 0xd6, 0x49, 0x75, 0x79, 0xee, 0xd4, 0xf8, 0x06, 0xff, 0xb4, 0xd9, 0xdd, 0xf5, 0x33, 0x77, 0xac, 0x68, 0x6f, 0x5d, 0xf9, 0xec, 0xe5, 0x8b, 0x1e, 0x1d, 0xca, 0x0f, 0xb6, 0xc7, 0xa7, 0x65, 0xa6, 0xd5, 0x5c, 0xbc, 0x61, 0x6a, 0xd7, 0xcd, 0x33, 0x2b, 0xb3, 0xf3, 0xb2, 0x13, 0x47, 0x95, 0x34, 0x07, 0x12, 0xdb, 0x6f, 0x3c, 0x7a, 0xcd, 0xd2, 0x43, 0x37, 0x8f, 0x6f, 0x0f, 0xd2, 0x3d, 0xe6, 0x8f, 0xc2, 0xb5, 0xfc, 0xef, 0x05, 0xf0, 0x43, 0xde, 0x84, 0x6e, 0x0b, 0xa6, 0xd5, 0x54, 0x95, 0xf2, 0x48, 0x6c, 0x22, 0x06, 0x1b, 0x51, 0x2d, 0x72, 0x6e, 0x12, 0x87, 0xb9, 0x0c, 0xf2, 0x1d, 0xc0, 0xa3, 0xac, 0xd4, 0x87, 0xc5, 0xa9, 0x5c, 0x52, 0x2c, 0x3e, 0x53, 0x1f, 0x16, 0x91, 0x0c, 0x4b, 0xa8, 0x0f, 0x8b, 0x40, 0x5e, 0x61, 0x3e, 0xe9, 0x13, 0x1f, 0xbd, 0x1a, 0x1c, 0x60, 0xbd, 0x91, 0x8d, 0x53, 0x8c, 0xdd, 0xa1, 0x79, 0xaf, 0x60, 0x94, 0xa9, 0xba, 0x8a, 0x71, 0x24, 0x5e, 0x75, 0x12, 0x40, 0xac, 0x84, 0x8d, 0xbe, 0x72, 0xe8, 0x1e, 0x6f, 0xed, 0x98, 0xd5, 0xa6, 0xee, 0x19, 0xb3, 0xae, 0xe1, 0x76, 0xa0, 0x49, 0xbf, 0xaf, 0x6d, 0x82, 0x5e, 0x9a, 0x39, 0xfe, 0x32, 0xa0, 0x45, 0x3b, 0x8a, 0xfd, 0x35, 0xa4, 0x57, 0x1d, 0x59, 0x49, 0xfc, 0x02, 0x2b, 0xf4, 0xd7, 0xa5, 0x63, 0x66, 0x5f, 0xa8, 0xf7, 0x57, 0x59, 0xe9, 0xc7, 0x1e, 0x2f, 0x9e, 0x31, 0xec, 0xb9, 0xe5, 0xd0, 0x6b, 0x19, 0x8f, 0x7a, 0xbc, 0x4a, 0xaf, 0xd7, 0x3f, 0xca, 0x1e, 0xef, 0x8e, 0x33, 0xcb, 0xca, 0xe0, 0x60, 0x7d, 0x44, 0x56, 0xfa, 0x19, 0xfd, 0x3a, 0x7d, 0xf4, 0x6b, 0x84, 0x31, 0xea, 0xd0, 0xcb, 0xff, 0x37, 0x43, 0x0f, 0xf2, 0xaf, 0x6c, 0x55, 0xe7, 0x51, 0x95, 0x69, 0x1e, 0x4d, 0x22, 0xdf, 0xf4, 0x3e, 0xd5, 0x75, 0xf5, 0x8c, 0x77, 0x5f, 0xc2, 0xd2, 0x4f, 0x22, 0x76, 0x40, 0x04, 0x07, 0xdd, 0xf8, 0x84, 0x8a, 0x7f, 0x6d, 0xc4, 0x89, 0x6e, 0xdc, 0xa9, 0xe2, 0xf7, 0x18, 0x71, 0x29, 0x19, 0x70, 0x22, 0x09, 0x93, 0x88, 0xce, 0x5c, 0x4d, 0x74, 0xa6, 0x03, 0x0d, 0x46, 0x8f, 0x32, 0x9d, 0x96, 0x50, 0x8f, 0x91, 0x5c, 0xe0, 0x80, 0x2b, 0xad, 0xa9, 0xa4, 0x03, 0x41, 0x63, 0x9a, 0x21, 0x81, 0x52, 0xc9, 0x5c, 0x7a, 0x51, 0x89, 0x6a, 0x33, 0xd5, 0x5d, 0xcf, 0xe2, 0x4e, 0x9b, 0x95, 0x83, 0xde, 0x85, 0xa7, 0x97, 0x73, 0x3b, 0x81, 0x05, 0x46, 0xa8, 0x5b, 0x35, 0xe2, 0x25, 0x41, 0xe2, 0x85, 0x9e, 0xa8, 0x2c, 0xf4, 0x0e, 0x14, 0xcd, 0xa7, 0xa9, 0x40, 0xf5, 0xc4, 0x11, 0x98, 0x5a, 0x30, 0xd1, 0xe9, 0x74, 0x0e, 0x76, 0x0e, 0x76, 0x17, 0xe6, 0xf9, 0x7d, 0xf9, 0x85, 0x81, 0x5c, 0x78, 0x0f, 0x4c, 0xbd, 0xa9, 0x48, 0x6c, 0x70, 0xea, 0xea, 0xc5, 0x18, 0xea, 0xcf, 0x9f, 0x5b, 0xa1, 0x0e, 0xa3, 0x36, 0x60, 0xae, 0x6a, 0x17, 0x9f, 0x3a, 0x68, 0xc9, 0x88, 0x1b, 0x37, 0xfb, 0x5a, 0x67, 0x0c, 0x9e, 0x72, 0x4d, 0x1d, 0xbe, 0x34, 0x5a, 0x05, 0x2e, 0x2d, 0xb2, 0xb9, 0x86, 0xee, 0x58, 0x38, 0xed, 0x96, 0xb9, 0x75, 0xcd, 0xcb, 0x1e, 0x9a, 0x3f, 0x63, 0x75, 0xf5, 0x31, 0x9e, 0x6b, 0xad, 0x3a, 0x7c, 0x60, 0xd6, 0xfa, 0x69, 0x45, 0x63, 0x46, 0x6c, 0x7c, 0xd1, 0xa8, 0x03, 0x6b, 0x0b, 0x7e, 0x34, 0xf6, 0xc2, 0xe1, 0x6b, 0x8e, 0x5c, 0xbb, 0xf4, 0xc8, 0xa6, 0x89, 0xcd, 0xf5, 0x5c, 0xe9, 0xf7, 0xbf, 0x64, 0x36, 0xbc, 0x3a, 0x5e, 0xb0, 0x8f, 0x82, 0x6e, 0x0f, 0xc6, 0xe5, 0x61, 0x24, 0x24, 0xd1, 0x7e, 0x03, 0x9d, 0x05, 0xc7, 0x60, 0x19, 0xd0, 0x39, 0x74, 0x63, 0x88, 0xca, 0x5e, 0x6e, 0xa7, 0x1e, 0xd6, 0x2a, 0x5d, 0xa7, 0xc9, 0xb9, 0x90, 0x46, 0x05, 0xfb, 0x4b, 0x19, 0xcc, 0x89, 0xfc, 0xc0, 0x92, 0x23, 0x9a, 0x5a, 0x9d, 0xc0, 0x7e, 0xba, 0x80, 0x38, 0x48, 0xf7, 0xe5, 0x3b, 0xf3, 0x0b, 0xf3, 0xf2, 0x02, 0xb9, 0xf4, 0x2d, 0x9e, 0xa9, 0xeb, 0xdc, 0xae, 0x18, 0x1d, 0x65, 0xee, 0xa6, 0x63, 0x57, 0xf4, 0xe9, 0x15, 0x63, 0xa7, 0x08, 0x8f, 0x7c, 0xdf, 0x1c, 0xdd, 0x13, 0x48, 0x93, 0x5b, 0xf9, 0x75, 0xd2, 0x0f, 0xaa, 0xbc, 0xa1, 0x8d, 0xc2, 0x7f, 0xe9, 0x7a, 0xfe, 0x3d, 0x2a, 0xe7, 0x4d, 0x14, 0x7f, 0x12, 0x3c, 0x05, 0xc4, 0xc0, 0x77, 0x99, 0xf0, 0x6d, 0x3a, 0xfe, 0x54, 0x3f, 0xf8, 0x1e, 0x13, 0xbe, 0x46, 0xc7, 0xf7, 0x9b, 0xf0, 0x0e, 0x1d, 0x7f, 0xbe, 0x9f, 0x72, 0x0e, 0x98, 0xf0, 0x43, 0x3a, 0x7e, 0x18, 0x70, 0x70, 0x65, 0x0e, 0x38, 0xd1, 0x31, 0x16, 0xc2, 0x7b, 0x17, 0xb3, 0x55, 0x28, 0x40, 0x4c, 0x6a, 0x91, 0xac, 0xee, 0x3d, 0xb0, 0x6d, 0x26, 0x48, 0x58, 0x80, 0xbb, 0x1b, 0x74, 0x10, 0x72, 0x41, 0xba, 0xb5, 0xe3, 0x73, 0xef, 0x69, 0x25, 0xf4, 0x83, 0xd4, 0x27, 0x21, 0x54, 0x5a, 0x5c, 0x90, 0xef, 0xcb, 0x4e, 0xf3, 0x38, 0xe3, 0xc1, 0xc3, 0x66, 0x9e, 0x45, 0x4a, 0x2e, 0xc1, 0x35, 0xcc, 0xa1, 0xb5, 0x49, 0x17, 0x31, 0x8a, 0xe7, 0xd3, 0xb8, 0xa1, 0x87, 0x2f, 0xf6, 0x05, 0x67, 0x34, 0xaf, 0xbc, 0xc5, 0xcd, 0xed, 0xcf, 0xbb, 0xe8, 0xb2, 0xa5, 0xf5, 0x97, 0xee, 0x5b, 0x33, 0xba, 0x6d, 0xe5, 0x33, 0x4b, 0x2e, 0xbc, 0x6f, 0xd9, 0xd0, 0x24, 0x6f, 0x68, 0x3e, 0x5f, 0x3e, 0xb1, 0x67, 0xc4, 0xd0, 0x99, 0x23, 0x2a, 0x12, 0x53, 0xe3, 0xb9, 0x45, 0x83, 0x67, 0x8f, 0x2a, 0xdc, 0x74, 0xad, 0x92, 0x07, 0x64, 0x6d, 0xe4, 0xfa, 0xe3, 0x6b, 0x2f, 0x7b, 0x7e, 0xdd, 0xb8, 0x96, 0xcb, 0x9f, 0x5c, 0x54, 0x96, 0x3d, 0xfd, 0xa6, 0x0b, 0x4a, 0xb3, 0xfc, 0x59, 0x89, 0xa7, 0x1e, 0xb3, 0x18, 0x7d, 0x4d, 0x74, 0x13, 0xe9, 0x23, 0x0d, 0x27, 0xba, 0xe9, 0x51, 0x43, 0xfa, 0x63, 0x3a, 0xfe, 0x6b, 0xf4, 0x90, 0x01, 0xbf, 0x55, 0x2f, 0x67, 0x5f, 0xa4, 0x7c, 0xe1, 0x11, 0xba, 0x8f, 0xc3, 0xd2, 0x9f, 0x60, 0xe9, 0xc3, 0x87, 0x20, 0xbd, 0x01, 0xff, 0x4d, 0xa4, 0x1c, 0x21, 0x85, 0xee, 0xe3, 0x30, 0xfc, 0xb7, 0xc6, 0xf2, 0xe9, 0x5e, 0x0a, 0xc3, 0x7f, 0xa7, 0xe1, 0xd0, 0x4e, 0x7d, 0xec, 0x0b, 0x98, 0x6c, 0x69, 0xed, 0x85, 0xb1, 0x26, 0x3a, 0x99, 0x8d, 0xf5, 0x28, 0xf4, 0x95, 0xea, 0x2b, 0xa8, 0x08, 0x0b, 0xe2, 0xa8, 0x0a, 0xce, 0x22, 0x11, 0xd3, 0xda, 0x42, 0x7d, 0x05, 0x19, 0x11, 0xab, 0xee, 0x2b, 0xa8, 0x92, 0xd8, 0x04, 0x02, 0xa1, 0x95, 0x3d, 0x36, 0x08, 0x5e, 0x21, 0x6a, 0x17, 0x7a, 0x18, 0xe7, 0x03, 0xeb, 0x00, 0x01, 0xa3, 0xb4, 0x62, 0x8b, 0x85, 0xea, 0xc1, 0x1e, 0x59, 0xbb, 0xfb, 0x79, 0x3a, 0x19, 0x97, 0x98, 0x33, 0x06, 0x1b, 0xb4, 0x3c, 0xc8, 0x22, 0x59, 0x25, 0x8b, 0x75, 0xe0, 0xbc, 0x9a, 0xa3, 0x20, 0x92, 0x95, 0xa8, 0x88, 0x74, 0x84, 0x86, 0x0d, 0x6d, 0x69, 0x6e, 0xa8, 0x83, 0xb7, 0xec, 0xb9, 0x39, 0x19, 0xe9, 0x49, 0x89, 0x54, 0xe2, 0xec, 0xb6, 0x88, 0xc4, 0xb9, 0xcb, 0xf9, 0x98, 0xd2, 0x06, 0xcb, 0x62, 0xbf, 0x2e, 0x83, 0x74, 0x49, 0xfc, 0xd3, 0x85, 0xf7, 0x2d, 0x6d, 0xed, 0x4f, 0x06, 0x1b, 0x72, 0x26, 0xce, 0x98, 0x53, 0xe9, 0x8a, 0x79, 0x38, 0xaf, 0xc9, 0x67, 0x68, 0x9d, 0x6d, 0xc2, 0xba, 0x43, 0x57, 0xf7, 0x15, 0xce, 0x51, 0x81, 0xfa, 0x82, 0x14, 0xbe, 0x29, 0xc6, 0xd9, 0x3d, 0xdd, 0x9b, 0x4a, 0x0d, 0xd7, 0x0a, 0x6e, 0xc2, 0xa7, 0x78, 0xf2, 0x41, 0x09, 0x28, 0x88, 0x9e, 0x08, 0xa6, 0x36, 0x61, 0xce, 0x0a, 0xc4, 0xc7, 0x82, 0x25, 0xec, 0x4f, 0xe3, 0x44, 0x69, 0x50, 0x31, 0x27, 0x8b, 0x1a, 0xa5, 0xaa, 0x42, 0x36, 0xab, 0x6d, 0x19, 0xb1, 0x72, 0x45, 0x2c, 0x01, 0x53, 0x97, 0x2d, 0xcb, 0x79, 0x3b, 0x87, 0x88, 0x15, 0x28, 0x21, 0x3c, 0x97, 0x74, 0xa3, 0x75, 0x3a, 0xe9, 0x3c, 0xba, 0x66, 0xd1, 0x1e, 0x5c, 0x08, 0x9d, 0x7f, 0xa6, 0x99, 0x96, 0xc8, 0x70, 0x3f, 0x0d, 0xa1, 0xe0, 0x90, 0xc6, 0xfa, 0x8a, 0xb2, 0xa2, 0x02, 0x5f, 0x76, 0x46, 0x7a, 0x6a, 0x4a, 0x82, 0x33, 0xde, 0x2e, 0x0a, 0xa4, 0xa9, 0x3c, 0x78, 0xb5, 0xc3, 0x35, 0x05, 0x4e, 0xec, 0xcf, 0xc2, 0x1e, 0xa6, 0xb0, 0x89, 0x85, 0x02, 0x9d, 0x4c, 0x7a, 0x9e, 0x37, 0x91, 0xaf, 0x28, 0xee, 0xa5, 0x6e, 0x50, 0xf1, 0xa5, 0xa5, 0x97, 0x1e, 0x3e, 0xca, 0x65, 0xa4, 0xe2, 0x25, 0x19, 0x6d, 0x05, 0x64, 0x00, 0xb0, 0x1d, 0xd7, 0xb4, 0xb6, 0x27, 0x79, 0x95, 0xfb, 0x7e, 0x32, 0x66, 0xb9, 0xd6, 0xef, 0x54, 0x3b, 0xdc, 0xb3, 0xf4, 0x06, 0xe0, 0x60, 0xc7, 0x47, 0x8c, 0x85, 0xf1, 0x69, 0x5e, 0x54, 0x71, 0x15, 0xdd, 0x9b, 0x5a, 0x34, 0x7b, 0xd8, 0x8e, 0x15, 0x23, 0x96, 0x14, 0x73, 0x6d, 0x09, 0xae, 0x4d, 0xd7, 0xe2, 0x8a, 0xa6, 0xa7, 0x96, 0x97, 0x65, 0x2b, 0x6f, 0x37, 0xd3, 0x3e, 0xa7, 0xba, 0xe2, 0x57, 0x84, 0x87, 0x0d, 0x1d, 0xf1, 0xf3, 0xf5, 0x30, 0x26, 0x69, 0xe9, 0x2f, 0x7b, 0xbc, 0x91, 0x79, 0x76, 0x44, 0x9b, 0x67, 0xe2, 0x3a, 0xa3, 0x9e, 0x30, 0xe0, 0x37, 0xe9, 0x7a, 0xe2, 0x1e, 0xa3, 0x9e, 0x20, 0x9c, 0x44, 0xd5, 0x13, 0xca, 0xeb, 0x11, 0x9c, 0xfa, 0x8f, 0x78, 0x88, 0x9e, 0x17, 0x73, 0xba, 0xff, 0x08, 0x9e, 0xcd, 0x4d, 0xfa, 0x1e, 0x24, 0x0b, 0x5d, 0x84, 0x4b, 0xd4, 0xd9, 0xd9, 0x8a, 0x2d, 0xd6, 0x8b, 0x46, 0x70, 0x71, 0xb6, 0x09, 0xd8, 0x1e, 0x47, 0x67, 0xa7, 0x11, 0x89, 0xd7, 0x67, 0x67, 0x10, 0x59, 0x65, 0x8b, 0x6c, 0xb5, 0x50, 0x37, 0x10, 0x12, 0x87, 0xe6, 0x38, 0xb1, 0x88, 0x64, 0xab, 0x28, 0xcf, 0x85, 0xdd, 0x4a, 0x08, 0x5f, 0xa5, 0xce, 0x96, 0xc5, 0xe0, 0xaa, 0xc4, 0x36, 0x1d, 0xd9, 0x6c, 0x0b, 0x3b, 0xe3, 0x71, 0x5c, 0x9c, 0x7d, 0x3a, 0xb2, 0xdb, 0x7b, 0xec, 0xda, 0x64, 0x3d, 0x8b, 0x72, 0x96, 0x98, 0xcb, 0x09, 0x8e, 0xd4, 0x8b, 0x88, 0xb3, 0xc5, 0xdb, 0xe2, 0xe2, 0xcf, 0xa8, 0x28, 0x14, 0x1f, 0xaf, 0x95, 0x04, 0x7b, 0x0b, 0x29, 0x29, 0x08, 0x75, 0x4d, 0x9b, 0x3c, 0x69, 0xdc, 0x98, 0xe1, 0xc3, 0x5a, 0x9a, 0x1a, 0xea, 0xaa, 0x06, 0x15, 0x17, 0xa6, 0x64, 0xa5, 0x64, 0x65, 0xa4, 0xc3, 0x0b, 0x99, 0xbc, 0xa4, 0x3c, 0xe6, 0xa0, 0x82, 0xdd, 0xd4, 0x52, 0x3d, 0x54, 0x0c, 0xe1, 0x6a, 0xdd, 0x3f, 0x68, 0x9a, 0x77, 0x64, 0xb6, 0x75, 0x8f, 0x1f, 0xdf, 0xdd, 0x96, 0xb9, 0x21, 0xde, 0x57, 0x5f, 0x5c, 0x5c, 0xef, 0x8b, 0xbf, 0xec, 0x87, 0x4e, 0x7c, 0x71, 0x5b, 0xc3, 0xec, 0xce, 0x92, 0x92, 0xce, 0xd9, 0x0d, 0x39, 0x75, 0x05, 0xc9, 0xc9, 0x05, 0x75, 0x39, 0xbd, 0x6f, 0xfd, 0x10, 0x55, 0xd0, 0x77, 0x9d, 0xc7, 0x95, 0xa6, 0x35, 0x6c, 0xa7, 0x86, 0x73, 0x7c, 0x6c, 0x3e, 0x82, 0xc3, 0x2a, 0x5e, 0x4e, 0x14, 0xea, 0xd3, 0x72, 0x2b, 0xc1, 0x5b, 0x54, 0x5f, 0x13, 0xdd, 0x78, 0x95, 0x7a, 0x3f, 0x62, 0x95, 0x7a, 0x96, 0x56, 0xae, 0x6c, 0xe5, 0x9e, 0x16, 0xf7, 0xa1, 0x1a, 0xf4, 0x38, 0xe3, 0x73, 0x4e, 0xd8, 0x0a, 0xb7, 0x06, 0x88, 0xf5, 0x91, 0x46, 0x16, 0x0b, 0x2a, 0x97, 0x46, 0x44, 0xd2, 0xf9, 0x74, 0x39, 0xb1, 0x51, 0x48, 0x1b, 0xe4, 0x1e, 0xb8, 0x73, 0x01, 0x6a, 0x83, 0xde, 0xe2, 0xc7, 0x5d, 0x16, 0x8c, 0xf1, 0x7c, 0xb6, 0x57, 0x60, 0x76, 0x6b, 0x52, 0xab, 0x67, 0xd0, 0xfc, 0x9a, 0xf4, 0x9b, 0xd1, 0xe4, 0xcb, 0xc4, 0x51, 0x5d, 0x99, 0xe2, 0xf6, 0xe5, 0xe7, 0xbb, 0x5c, 0xc9, 0xd5, 0x36, 0xb6, 0xf7, 0x09, 0x16, 0x4f, 0x26, 0x9c, 0xe9, 0xc1, 0x7e, 0x66, 0x5f, 0x4a, 0xed, 0x2a, 0x28, 0xc7, 0x2d, 0xb8, 0x5a, 0xb5, 0xf2, 0xb8, 0x9d, 0xd8, 0xe1, 0xf1, 0xb9, 0xcb, 0x3a, 0x6a, 0x32, 0x70, 0xa6, 0xf2, 0xd7, 0x1d, 0xd1, 0x64, 0xfa, 0x27, 0x97, 0x79, 0x32, 0xef, 0xaf, 0xba, 0x70, 0xcd, 0xa4, 0x11, 0x83, 0xc5, 0x96, 0xf8, 0x78, 0x09, 0xa7, 0xd4, 0x4c, 0x69, 0x9d, 0xba, 0x29, 0x34, 0xd8, 0xcc, 0xa2, 0xf9, 0xf3, 0xe3, 0x45, 0x4f, 0xca, 0xc4, 0x19, 0xd3, 0xd6, 0xcd, 0xac, 0x4c, 0xa2, 0xfd, 0x5b, 0xa1, 0x6c, 0x16, 0xec, 0x94, 0x23, 0x0c, 0x53, 0xef, 0x3f, 0x70, 0x2a, 0xfe, 0xba, 0x70, 0x50, 0xc3, 0xe9, 0x1d, 0x12, 0x25, 0xca, 0xa7, 0x0c, 0x86, 0x9d, 0x45, 0xe1, 0x66, 0xc2, 0x39, 0x0b, 0xd1, 0xe8, 0x60, 0x7b, 0x72, 0x12, 0xb1, 0x53, 0x88, 0x4a, 0x26, 0xc4, 0xdb, 0x6e, 0xe3, 0x24, 0x2b, 0x96, 0x31, 0xb8, 0x20, 0x52, 0xf7, 0x64, 0xa8, 0x05, 0xc2, 0x75, 0x89, 0xf4, 0x6e, 0x31, 0x74, 0xd2, 0x5c, 0x70, 0x6a, 0x4e, 0x72, 0x16, 0x16, 0x04, 0xe0, 0x41, 0x6c, 0x4d, 0x7e, 0x8a, 0xcb, 0x45, 0x37, 0x85, 0xa9, 0xef, 0x0a, 0xe3, 0xb7, 0xab, 0xa6, 0x2d, 0xed, 0x2c, 0xfa, 0xc8, 0x12, 0xee, 0x0c, 0xf3, 0x95, 0xca, 0xc5, 0xc7, 0xe0, 0x9b, 0x9b, 0xae, 0xdc, 0x7b, 0x65, 0xe0, 0xbc, 0x4c, 0x99, 0x2b, 0x2f, 0x9a, 0x7e, 0xdd, 0xa4, 0x00, 0x77, 0xd9, 0xdd, 0x77, 0x1f, 0xbb, 0x8b, 0xab, 0xc7, 0xfe, 0xde, 0x47, 0xd5, 0x8f, 0xad, 0x24, 0xb3, 0xa1, 0xd5, 0x66, 0x9b, 0x9f, 0xc4, 0xe5, 0x8d, 0x5a, 0x34, 0xaa, 0x44, 0x19, 0xbf, 0x49, 0x99, 0x86, 0x9f, 0xd8, 0x04, 0xdf, 0x48, 0xda, 0x7f, 0x54, 0x3c, 0x48, 0x38, 0x73, 0x27, 0xe3, 0xc0, 0xda, 0xb7, 0x13, 0xbc, 0x8d, 0x7e, 0x7b, 0x27, 0xe3, 0x4d, 0x44, 0x4a, 0x23, 0x7d, 0x75, 0x50, 0xc7, 0xcd, 0x7d, 0xa5, 0xe2, 0x03, 0xf6, 0xd5, 0x41, 0x94, 0x07, 0x7d, 0xe5, 0x4e, 0x3c, 0x8b, 0xbe, 0xca, 0x43, 0x79, 0xfe, 0x5c, 0xd2, 0x57, 0xee, 0x7c, 0x7f, 0xcc, 0xbe, 0xa2, 0xfb, 0xe1, 0xa0, 0x47, 0x72, 0xa5, 0x98, 0x3d, 0xb5, 0x65, 0x63, 0x5a, 0x81, 0x3b, 0x2b, 0x7d, 0x78, 0xab, 0xda, 0x47, 0x6d, 0x38, 0x55, 0xef, 0xa3, 0xd5, 0x3b, 0x76, 0x8b, 0x52, 0xb7, 0xcb, 0x76, 0xc1, 0x25, 0xab, 0x95, 0x0b, 0x36, 0x29, 0x63, 0xf0, 0xb3, 0x9b, 0xd8, 0xf7, 0x4a, 0xeb, 0x69, 0x3f, 0x8c, 0x61, 0xdf, 0x2b, 0x5c, 0xa7, 0x7d, 0xaf, 0xdc, 0xa8, 0xe1, 0xf0, 0xbd, 0xc2, 0x35, 0xb1, 0xbe, 0x57, 0x96, 0x89, 0x6c, 0x0c, 0x45, 0x0b, 0x82, 0xf3, 0xca, 0xb0, 0x68, 0x29, 0xf7, 0x70, 0x56, 0x31, 0x15, 0x4b, 0x56, 0x2b, 0xb6, 0x23, 0xa1, 0x23, 0xc1, 0xc9, 0xd9, 0x1d, 0x38, 0x8e, 0x2c, 0x1b, 0xd4, 0x15, 0xd0, 0x74, 0x66, 0xa5, 0x4a, 0x12, 0xdd, 0x2a, 0x98, 0xc3, 0x62, 0xd5, 0x74, 0xd9, 0x64, 0x4e, 0x10, 0xe6, 0x77, 0xc6, 0x83, 0x42, 0x66, 0x9d, 0x30, 0x14, 0x0d, 0x0d, 0xb6, 0x0e, 0x69, 0x69, 0x6e, 0x1a, 0xdc, 0xd8, 0x50, 0x5f, 0x55, 0x91, 0x9f, 0x5c, 0x0b, 0x82, 0x03, 0x5e, 0x9c, 0xb4, 0xee, 0x88, 0x16, 0x1f, 0x22, 0x3d, 0xda, 0x54, 0x1b, 0x40, 0x9e, 0xc4, 0x5d, 0xca, 0xcc, 0xa3, 0xca, 0xe8, 0x63, 0x6c, 0x1a, 0xad, 0x9d, 0x34, 0xac, 0x59, 0x8c, 0x4c, 0xb8, 0x97, 0x9b, 0xae, 0x78, 0xe6, 0xca, 0xc0, 0x64, 0x10, 0xb2, 0xc2, 0xe9, 0xab, 0x41, 0xc8, 0xee, 0xb9, 0xe7, 0xe8, 0x9d, 0xaa, 0x90, 0x5d, 0xa0, 0x77, 0xe2, 0x18, 0x3a, 0xab, 0xe6, 0xc4, 0xa9, 0xb3, 0xaf, 0xbd, 0x1f, 0xc1, 0x43, 0x9a, 0x8c, 0xc9, 0x43, 0xa9, 0xcc, 0x9c, 0xc7, 0x64, 0x4f, 0xeb, 0x5b, 0xb2, 0x18, 0x1d, 0xa2, 0x76, 0xdc, 0x79, 0x4c, 0xf6, 0x84, 0xd5, 0x9a, 0xec, 0x49, 0xeb, 0x22, 0xe9, 0xc9, 0x58, 0xac, 0xd2, 0xc7, 0xa2, 0x41, 0xc3, 0xe9, 0x58, 0xac, 0xec, 0x67, 0x2c, 0x0e, 0x12, 0x0d, 0x7a, 0x98, 0x85, 0x05, 0x4a, 0x28, 0x4d, 0xf5, 0xf0, 0x56, 0x11, 0x46, 0x82, 0xeb, 0x28, 0xc1, 0xe2, 0x28, 0x6f, 0x30, 0x81, 0xfc, 0xc7, 0x12, 0x81, 0xa9, 0x86, 0x84, 0xa4, 0x75, 0xe6, 0x81, 0x12, 0xa7, 0x5b, 0x30, 0x8c, 0x0f, 0x1d, 0x29, 0xf6, 0xc7, 0x98, 0x43, 0xe5, 0x0d, 0xd6, 0x9c, 0xcd, 0x08, 0x77, 0x05, 0xe1, 0x75, 0x43, 0x0b, 0x52, 0x07, 0x18, 0xdc, 0x3e, 0x0d, 0x2a, 0xf7, 0x9f, 0xe1, 0x20, 0xf7, 0x33, 0x11, 0x06, 0x1e, 0xe2, 0x2d, 0x1b, 0x53, 0x0b, 0x13, 0x33, 0xd3, 0x47, 0xb4, 0x9e, 0xe6, 0xe0, 0x1a, 0x67, 0x8c, 0x3e, 0xb0, 0x6c, 0xfc, 0xc4, 0x56, 0x7a, 0x57, 0xa4, 0x4b, 0xbd, 0xbb, 0x96, 0x45, 0xf1, 0x26, 0xe5, 0x4e, 0xfe, 0x53, 0xba, 0x1e, 0x5e, 0xc8, 0xee, 0x44, 0x5c, 0x81, 0x62, 0xe2, 0x07, 0x4c, 0xf8, 0x21, 0x1d, 0x3f, 0x6c, 0xc2, 0x1f, 0xd6, 0xf1, 0x57, 0xfb, 0x29, 0x67, 0x8f, 0x09, 0xdf, 0xa5, 0xe3, 0xbb, 0x4c, 0x78, 0x87, 0x8e, 0x3f, 0x7f, 0x05, 0x32, 0xec, 0x5b, 0x41, 0xf9, 0x17, 0xd1, 0xf6, 0xbf, 0xaa, 0xbe, 0x2b, 0x67, 0xf8, 0x36, 0x1d, 0x7f, 0xca, 0x84, 0x1f, 0xd2, 0xf1, 0xc3, 0xfd, 0xa4, 0x3f, 0x40, 0xdf, 0xb5, 0x73, 0x0c, 0x17, 0x6c, 0xc8, 0x81, 0x9a, 0xd1, 0x0e, 0x76, 0x3a, 0xe4, 0x6e, 0xc4, 0x48, 0x72, 0xc0, 0xfe, 0x68, 0x01, 0xe1, 0xf4, 0xb0, 0xdf, 0x25, 0x90, 0x15, 0x1b, 0x50, 0x39, 0x0a, 0xed, 0x62, 0x19, 0x8a, 0x88, 0xad, 0x05, 0x3e, 0x0c, 0x7b, 0xe8, 0x3e, 0xbe, 0x45, 0xe6, 0x28, 0x49, 0x53, 0xb7, 0x19, 0xe7, 0x1a, 0x37, 0xb4, 0xbc, 0xc1, 0x32, 0x2d, 0x2d, 0x3c, 0xe3, 0x9b, 0xaf, 0xef, 0xfc, 0xf7, 0x93, 0xbe, 0x2b, 0x98, 0xe0, 0x74, 0x3a, 0x9b, 0x9d, 0x4d, 0x85, 0x45, 0x7e, 0xd8, 0xfe, 0xa2, 0x6f, 0xf5, 0xa2, 0xf6, 0x70, 0x8c, 0x6b, 0x35, 0x23, 0x74, 0x2d, 0x51, 0xbb, 0x95, 0x84, 0xaf, 0xf1, 0xa9, 0xe5, 0x4b, 0x87, 0xc3, 0xae, 0xce, 0x85, 0x4d, 0x93, 0xae, 0xa9, 0x3b, 0x36, 0x74, 0xe5, 0x33, 0xcb, 0x56, 0xed, 0x6f, 0x2e, 0xb3, 0xa4, 0xb9, 0xdd, 0xc5, 0x4d, 0xe7, 0xb5, 0x8e, 0x5a, 0x38, 0x2a, 0xcf, 0x37, 0xac, 0x73, 0x4a, 0xf5, 0x85, 0x77, 0x5e, 0x52, 0x0f, 0xfb, 0x3c, 0xb3, 0xd7, 0xd5, 0xc6, 0xd9, 0x8a, 0x78, 0xa1, 0xb9, 0xfa, 0xc5, 0x3d, 0x33, 0x6e, 0x98, 0x5a, 0x34, 0x66, 0x04, 0xf7, 0x4a, 0x6f, 0xfb, 0x8c, 0xad, 0x3d, 0xad, 0xa3, 0xeb, 0x3a, 0x1d, 0xce, 0x0c, 0x5f, 0x46, 0xd1, 0xe8, 0x39, 0x8d, 0x97, 0xe5, 0xd7, 0xe7, 0x25, 0x0e, 0xbf, 0xfe, 0xe5, 0xab, 0x17, 0xbe, 0xb4, 0x61, 0xfc, 0xf0, 0x96, 0x51, 0x05, 0xe8, 0xff, 0xef, 0xd3, 0xff, 0x5c, 0x9f, 0x8a, 0x37, 0xd1, 0x3e, 0x9d, 0xc4, 0x0d, 0x61, 0xc6, 0x47, 0xd2, 0x58, 0x8c, 0x6c, 0xb4, 0xf7, 0x9a, 0x48, 0xef, 0x95, 0xa6, 0x72, 0x1c, 0x4f, 0x3a, 0x95, 0x75, 0x9f, 0xf1, 0xb7, 0x38, 0xc3, 0x6f, 0x59, 0xea, 0x6f, 0xf6, 0x18, 0xbf, 0xe5, 0xc6, 0xfc, 0xcd, 0x02, 0x03, 0xd5, 0x17, 0x86, 0x91, 0x22, 0xff, 0xb0, 0x96, 0x94, 0x13, 0xa3, 0x8e, 0x17, 0x65, 0x1e, 0x06, 0xcb, 0x36, 0x1d, 0x8e, 0xec, 0x17, 0x77, 0xc6, 0xc7, 0xd1, 0xad, 0x60, 0x2b, 0xdd, 0x0a, 0xb6, 0x80, 0x67, 0x04, 0xf6, 0x3e, 0x8d, 0x5d, 0x06, 0x80, 0x36, 0x36, 0xc4, 0xc8, 0x85, 0x22, 0x79, 0xc0, 0x4c, 0xea, 0x37, 0x3f, 0x7c, 0x47, 0xb3, 0x9e, 0xdf, 0x6e, 0xa7, 0xa3, 0x4e, 0x8d, 0xa2, 0xd3, 0x2f, 0x03, 0xbe, 0xb7, 0xfd, 0xcc, 0xca, 0x60, 0x5b, 0xda, 0xb4, 0x08, 0xb0, 0xc2, 0x8c, 0xa5, 0x15, 0x92, 0xd2, 0xda, 0xf4, 0xd2, 0x78, 0x90, 0xc7, 0xc5, 0xa7, 0xd9, 0x30, 0x0c, 0xc2, 0xa9, 0x95, 0x44, 0x6c, 0xca, 0x53, 0x15, 0x42, 0x09, 0xbe, 0x5a, 0x46, 0x17, 0xea, 0x53, 0x04, 0x7b, 0xe2, 0x9b, 0x4a, 0x84, 0x7c, 0x92, 0x73, 0xd2, 0xc4, 0xf1, 0xe0, 0xf1, 0xd9, 0x9f, 0x9f, 0xe7, 0xa6, 0xfb, 0xe7, 0x8e, 0xd3, 0xda, 0x3f, 0xf7, 0xb1, 0x93, 0x9d, 0x01, 0xc5, 0xbf, 0x09, 0xbb, 0xcc, 0x13, 0x80, 0x5b, 0x12, 0x6d, 0x04, 0x28, 0x27, 0xed, 0xa3, 0x57, 0x3c, 0xb6, 0xe0, 0x34, 0x26, 0xc5, 0x15, 0xc6, 0x59, 0xf1, 0xa8, 0xd9, 0x46, 0x98, 0xb0, 0x76, 0x66, 0xed, 0x80, 0x13, 0xa4, 0x77, 0x5b, 0x0c, 0xfd, 0xbe, 0xc7, 0xa4, 0xf7, 0x77, 0xe9, 0xf8, 0x2e, 0x13, 0xde, 0xa1, 0xe3, 0xcf, 0x33, 0x3c, 0x4c, 0xfe, 0xcd, 0x7f, 0x48, 0xef, 0x28, 0xce, 0x64, 0x77, 0x28, 0xc3, 0x33, 0xd9, 0x99, 0x33, 0xe0, 0x94, 0x63, 0xce, 0x54, 0xd7, 0x95, 0x7f, 0x68, 0x67, 0xd4, 0x46, 0x1c, 0xff, 0x19, 0xe2, 0xc4, 0x92, 0x72, 0x9e, 0x80, 0xf4, 0xf4, 0xbe, 0x2d, 0x4b, 0xff, 0x5c, 0x78, 0x16, 0x3d, 0xcf, 0x6c, 0x57, 0xb6, 0xf2, 0x1f, 0x10, 0x3e, 0x55, 0x89, 0xee, 0x66, 0x73, 0xc7, 0x0d, 0xf7, 0x98, 0x5c, 0x18, 0x73, 0x65, 0x49, 0x9c, 0x88, 0xb3, 0x89, 0x7d, 0x48, 0x35, 0x63, 0x34, 0x2a, 0x09, 0xda, 0x3b, 0x9a, 0x5c, 0x46, 0x5c, 0xd4, 0x17, 0x31, 0x8b, 0x3b, 0x65, 0xca, 0x55, 0xa2, 0xec, 0xc2, 0x22, 0x44, 0xe8, 0x3c, 0x06, 0x7b, 0x30, 0x66, 0x62, 0x93, 0x2d, 0x18, 0x47, 0xdf, 0xd1, 0xb8, 0xfd, 0xc9, 0xf4, 0x94, 0x39, 0x72, 0xf5, 0xcf, 0xc5, 0x4e, 0x9a, 0x5d, 0xfe, 0x01, 0x5e, 0xd7, 0x54, 0xe3, 0xce, 0xe0, 0xd6, 0x79, 0xe7, 0xad, 0x9a, 0x5a, 0x7a, 0x6c, 0xee, 0x25, 0x9d, 0x6b, 0x1a, 0xca, 0xfa, 0x79, 0x66, 0x13, 0x38, 0xca, 0x5f, 0x39, 0x74, 0x42, 0xc3, 0xbc, 0x4d, 0x53, 0x43, 0x2b, 0xb8, 0x9b, 0x16, 0xad, 0x18, 0xd9, 0x12, 0xf2, 0xc4, 0xf6, 0x57, 0x79, 0x0c, 0x69, 0x67, 0xfc, 0xc6, 0x7e, 0x15, 0x3f, 0x42, 0x5b, 0xe9, 0x5d, 0xdb, 0x2f, 0x48, 0xbf, 0x42, 0xff, 0x65, 0xa2, 0xf9, 0x41, 0x9b, 0xd6, 0x49, 0xda, 0xd9, 0x4a, 0x8e, 0x4c, 0x48, 0x23, 0x86, 0x9b, 0x19, 0xe0, 0xe8, 0x28, 0xe2, 0x98, 0xc9, 0xab, 0xc5, 0xad, 0xf2, 0x9a, 0x92, 0xe8, 0xbf, 0x92, 0xc4, 0xb8, 0x4b, 0x4d, 0x02, 0xfd, 0x01, 0xff, 0xd0, 0xfe, 0x90, 0x62, 0xf6, 0x07, 0xbc, 0x11, 0xad, 0xad, 0x4e, 0x26, 0xa6, 0x32, 0x1e, 0x13, 0xdc, 0xa2, 0x7e, 0xfd, 0x7c, 0xf2, 0xf5, 0xc7, 0x7e, 0xfc, 0xe3, 0x63, 0x38, 0x57, 0xf9, 0x03, 0x7f, 0x71, 0xdb, 0x38, 0xfa, 0xad, 0xb7, 0x73, 0x4b, 0x16, 0xad, 0x68, 0x1f, 0x12, 0xaa, 0x10, 0xae, 0xbb, 0xe6, 0xa7, 0x3f, 0xbd, 0x86, 0x7c, 0xd7, 0x22, 0xe5, 0x2e, 0xfe, 0x5a, 0xea, 0xb3, 0x63, 0x36, 0xf3, 0xd9, 0xc1, 0x09, 0x54, 0xbf, 0x2f, 0x22, 0x1f, 0x07, 0xbe, 0x3c, 0xc0, 0x8f, 0x01, 0x78, 0x56, 0x84, 0xfb, 0x6f, 0xc4, 0xf6, 0xca, 0xa1, 0x57, 0x50, 0xbc, 0x9d, 0x9a, 0x37, 0xe4, 0xa4, 0x44, 0x9b, 0x05, 0x36, 0x09, 0x5d, 0xa2, 0xc4, 0x42, 0xaa, 0xbb, 0xb5, 0xdd, 0x1c, 0x75, 0xe3, 0x06, 0x4f, 0x39, 0xfa, 0x3f, 0x29, 0xf9, 0x55, 0x5e, 0xef, 0xa0, 0xfc, 0xe4, 0xe4, 0xfc, 0x41, 0x5e, 0x6f, 0x55, 0x7e, 0x0a, 0xff, 0x6d, 0xaf, 0x85, 0xff, 0x16, 0x0f, 0xf6, 0x56, 0xfe, 0x7f, 0xec, 0xbd, 0x09, 0x80, 0x94, 0xc5, 0x95, 0x38, 0x5e, 0xf5, 0x9d, 0x7d, 0xf7, 0xf4, 0x7d, 0x4d, 0xcf, 0x74, 0x4f, 0x5f, 0x33, 0xdd, 0xd3, 0x73, 0xf5, 0xdc, 0x67, 0x0f, 0x37, 0x33, 0x03, 0x33, 0x20, 0xd7, 0xc8, 0xcd, 0x0c, 0x97, 0x22, 0x0c, 0xa8, 0x88, 0x17, 0xa8, 0x88, 0x0a, 0x22, 0xa8, 0x40, 0x44, 0xc5, 0x8d, 0x89, 0x46, 0x57, 0x21, 0x04, 0x45, 0x34, 0x26, 0xc6, 0x28, 0xb0, 0xbb, 0xd9, 0x98, 0xc4, 0xe4, 0x67, 0xd6, 0xb8, 0x26, 0xba, 0x9a, 0x78, 0x65, 0xa3, 0x59, 0xe3, 0x15, 0xaf, 0xf9, 0xe6, 0x5f, 0xc7, 0xf7, 0x7d, 0x7d, 0xcc, 0x0c, 0xe8, 0x26, 0xd9, 0x24, 0xbb, 0x7f, 0x50, 0xba, 0xfb, 0x7d, 0xef, 0xab, 0x7a, 0xf5, 0xaa, 0x5e, 0xd5, 0xab, 0xaa, 0x77, 0x60, 0x58, 0xb5, 0xaf, 0x90, 0x7e, 0x12, 0x3e, 0x5f, 0x20, 0xed, 0xc3, 0xf5, 0x32, 0x32, 0x3d, 0xf0, 0xf5, 0x91, 0x15, 0x64, 0xfc, 0xfe, 0xab, 0xf4, 0x43, 0xa6, 0x48, 0x81, 0x93, 0x98, 0x68, 0xcb, 0xf3, 0xce, 0xe0, 0x18, 0x30, 0x20, 0xed, 0x66, 0xde, 0x46, 0x34, 0xdb, 0x40, 0x12, 0x6c, 0xa5, 0xf3, 0x29, 0x0e, 0x90, 0x8b, 0xa7, 0xad, 0x21, 0x1c, 0xc3, 0x9b, 0x44, 0xbc, 0x58, 0x89, 0xd7, 0xde, 0x55, 0xd4, 0x1c, 0x0b, 0x1b, 0x46, 0x64, 0x96, 0xed, 0x2a, 0x05, 0x17, 0x88, 0xac, 0xc0, 0x8a, 0xc2, 0xd8, 0xef, 0x80, 0xcc, 0x2b, 0xfd, 0x69, 0x17, 0x04, 0x89, 0xb2, 0xd2, 0x68, 0x28, 0x88, 0x5d, 0x7b, 0xdd, 0x4e, 0xbd, 0x16, 0xd8, 0xa0, 0x0d, 0xa7, 0x6e, 0xb2, 0xe5, 0x5c, 0x88, 0xc8, 0xc1, 0x0b, 0x88, 0xfd, 0x96, 0x25, 0x65, 0x69, 0xc8, 0x32, 0xd0, 0x7a, 0x2b, 0x9b, 0x31, 0x88, 0x51, 0xf0, 0xb7, 0xb2, 0x85, 0xd6, 0xf1, 0x53, 0x50, 0x35, 0xc0, 0xe2, 0x8e, 0x64, 0xb1, 0xa9, 0x10, 0xb3, 0xed, 0x53, 0x5e, 0xb6, 0xc1, 0x42, 0xbc, 0xbc, 0x83, 0xc9, 0xd8, 0x59, 0x8d, 0xfc, 0x07, 0x22, 0x79, 0x2b, 0x7f, 0x04, 0xf5, 0xe7, 0x20, 0xc9, 0xbf, 0xf1, 0xe0, 0x08, 0xf5, 0x2f, 0xdf, 0x81, 0x18, 0x14, 0xa7, 0x70, 0x3a, 0xef, 0x50, 0xf8, 0xc8, 0xf7, 0x10, 0xbc, 0x8e, 0xc4, 0x72, 0xa1, 0xf8, 0x47, 0x65, 0xf8, 0xcf, 0x10, 0x3c, 0xca, 0xef, 0x55, 0xe1, 0xc7, 0x65, 0xf8, 0x6f, 0x10, 0xdc, 0xcc, 0x6f, 0x57, 0xe0, 0xfc, 0xd3, 0x32, 0x1c, 0xc7, 0xa3, 0xf3, 0xf1, 0x5d, 0x2a, 0xfe, 0x63, 0x32, 0xfc, 0x15, 0x04, 0xb7, 0x66, 0x95, 0xff, 0xb8, 0x0c, 0xff, 0x31, 0x82, 0x5b, 0x28, 0xfe, 0x47, 0x14, 0x7f, 0xe9, 0x99, 0xca, 0xc7, 0xb7, 0x76, 0xf0, 0x01, 0xb2, 0xef, 0x23, 0x70, 0xf8, 0x1c, 0x8e, 0x0e, 0x3a, 0x06, 0xfd, 0xc7, 0x14, 0x7a, 0xd0, 0x78, 0x29, 0x67, 0x71, 0xce, 0xa1, 0x41, 0x13, 0x1d, 0x2f, 0x1b, 0x46, 0xc5, 0xd0, 0x7b, 0x1a, 0x27, 0x05, 0x16, 0xde, 0x92, 0xdf, 0x8d, 0x21, 0x9e, 0xec, 0x87, 0xad, 0x38, 0x9b, 0x32, 0x2e, 0x43, 0x3e, 0x43, 0x6b, 0x95, 0xf7, 0x88, 0x3a, 0xf4, 0xcf, 0x27, 0xa8, 0x3c, 0x3d, 0x08, 0xa6, 0x8b, 0x78, 0xc8, 0x62, 0x5b, 0x68, 0x16, 0x2c, 0xa7, 0xe3, 0x82, 0xec, 0xcd, 0x88, 0x94, 0x16, 0x08, 0xd4, 0x3f, 0x2e, 0xa4, 0x04, 0xbd, 0x82, 0x9f, 0x1c, 0x38, 0x8d, 0x8f, 0x13, 0xe0, 0x03, 0x64, 0x0f, 0x04, 0x9f, 0xc6, 0x31, 0xea, 0x50, 0x79, 0x68, 0xfe, 0x86, 0x3d, 0xa8, 0x3c, 0x16, 0xe7, 0x18, 0x65, 0xe8, 0xa9, 0x98, 0x12, 0x40, 0x0b, 0xcb, 0x13, 0xab, 0xa3, 0xf2, 0x04, 0x7b, 0x4e, 0x51, 0x71, 0xc1, 0xf1, 0xb4, 0xb1, 0x6c, 0x28, 0x6d, 0x22, 0xb2, 0xb1, 0x31, 0x8b, 0xc7, 0x87, 0x15, 0x1e, 0x30, 0xac, 0xcc, 0x03, 0x54, 0x13, 0xfc, 0x55, 0x66, 0x2c, 0xc0, 0x11, 0xd2, 0x2e, 0x66, 0xe4, 0xdb, 0xd2, 0x01, 0x22, 0xdb, 0x16, 0x10, 0x01, 0xf3, 0x4f, 0x68, 0x49, 0x90, 0xb9, 0xee, 0x63, 0x15, 0x38, 0x7e, 0x1b, 0x8e, 0x02, 0x8d, 0x27, 0xe2, 0x00, 0xc9, 0x60, 0xe6, 0xeb, 0x96, 0xb3, 0x3d, 0xa3, 0xe9, 0x8d, 0x46, 0x99, 0x23, 0x39, 0x5f, 0xf3, 0x90, 0xb0, 0x20, 0x98, 0x01, 0x28, 0x09, 0xb8, 0x9d, 0x38, 0xee, 0x70, 0x84, 0x7a, 0x83, 0xe7, 0xe8, 0xac, 0xf5, 0x75, 0x63, 0x4c, 0x0c, 0xc1, 0xa9, 0x1b, 0xfb, 0x82, 0x0d, 0x85, 0x1a, 0xce, 0x17, 0x9d, 0x91, 0x7e, 0xcb, 0x19, 0x45, 0x23, 0x5b, 0x96, 0x85, 0xc2, 0xea, 0x28, 0x9a, 0x24, 0x3e, 0xfb, 0x41, 0xc7, 0x05, 0xb3, 0x2a, 0x74, 0xda, 0xf3, 0x6c, 0xdc, 0x94, 0x59, 0x5c, 0xe3, 0x18, 0xf3, 0x05, 0x6a, 0xcb, 0x8b, 0xd2, 0x01, 0x34, 0x5e, 0xa8, 0xcc, 0x4f, 0x4d, 0x4f, 0x2a, 0x82, 0x2c, 0x07, 0xbb, 0x34, 0x50, 0x40, 0x94, 0x71, 0x02, 0x43, 0x7c, 0xf2, 0x15, 0x11, 0x26, 0xba, 0x78, 0xc6, 0x06, 0x0a, 0x02, 0x9c, 0xc6, 0xa7, 0xac, 0x34, 0x86, 0xc4, 0xb7, 0xd0, 0xeb, 0xb0, 0x53, 0xe1, 0xc5, 0x71, 0xfc, 0x61, 0x9e, 0xf0, 0xda, 0x42, 0xf9, 0x8d, 0x51, 0x3a, 0xd7, 0x96, 0x4d, 0x31, 0x6a, 0xc1, 0x2b, 0x07, 0x73, 0x1a, 0xb5, 0x7f, 0xff, 0xa9, 0x83, 0xa3, 0x65, 0xf7, 0xf3, 0x61, 0xc9, 0x95, 0xd5, 0x32, 0xf8, 0x5b, 0x3a, 0x2a, 0x98, 0x91, 0xa3, 0xa8, 0x5f, 0x86, 0x48, 0x5b, 0x62, 0xb0, 0x52, 0xed, 0x17, 0x6d, 0x76, 0xbf, 0xa4, 0x08, 0xcb, 0x2b, 0xd4, 0x7e, 0x69, 0x04, 0x4a, 0xcc, 0x98, 0x71, 0x51, 0xaa, 0x80, 0x12, 0x26, 0xe6, 0x2c, 0xa5, 0x58, 0xce, 0x5e, 0x8a, 0xed, 0xec, 0xa5, 0x38, 0xce, 0x5e, 0x4a, 0xc5, 0xd9, 0x4a, 0x19, 0x35, 0xd2, 0x64, 0x24, 0x59, 0x97, 0xec, 0x27, 0xe3, 0x2d, 0x5c, 0xe2, 0x75, 0x23, 0x56, 0xd9, 0xea, 0x23, 0x82, 0x98, 0x33, 0xde, 0x98, 0x9a, 0xb1, 0x07, 0x5c, 0x4f, 0x70, 0xda, 0xc6, 0xde, 0x92, 0x7a, 0xbf, 0x86, 0xd1, 0x98, 0x3d, 0x36, 0x3c, 0xe4, 0x46, 0xad, 0x4b, 0xea, 0x90, 0xd3, 0x69, 0xb9, 0xa9, 0x7d, 0x63, 0x8e, 0x39, 0x24, 0x57, 0x48, 0x7e, 0x98, 0x2a, 0x32, 0xe7, 0xac, 0x96, 0xe7, 0xcc, 0x41, 0xa2, 0x0b, 0xfc, 0x0b, 0xea, 0xbf, 0xcf, 0x90, 0x7c, 0xfa, 0x40, 0x45, 0x3a, 0xa1, 0xd7, 0xe1, 0xf8, 0xde, 0x5d, 0x02, 0xe4, 0xb0, 0xb1, 0x05, 0x07, 0x70, 0xb4, 0xf9, 0x55, 0xdd, 0x4a, 0xdc, 0xa5, 0x21, 0x32, 0x5f, 0x20, 0xda, 0x0b, 0x44, 0xbc, 0xdd, 0x0b, 0x8d, 0xa2, 0x5e, 0x19, 0x61, 0x9f, 0xdd, 0x86, 0xa9, 0x46, 0x23, 0x4a, 0x94, 0xa9, 0xde, 0xbf, 0xff, 0xf4, 0x6d, 0x68, 0x32, 0x71, 0x65, 0x13, 0xaa, 0x0c, 0x21, 0xa2, 0xa7, 0x5c, 0xce, 0x0e, 0x13, 0x3d, 0x65, 0x0d, 0xf5, 0xb3, 0xdf, 0x0a, 0x94, 0x18, 0xb2, 0xec, 0x30, 0xd1, 0x23, 0xd7, 0xc8, 0x7a, 0xe4, 0x22, 0x55, 0x8f, 0x1c, 0x26, 0xe7, 0x31, 0x14, 0xff, 0x9b, 0xd7, 0x00, 0x55, 0x8f, 0xcc, 0x2a, 0x07, 0xbe, 0xbe, 0x39, 0xa3, 0x07, 0x65, 0xc1, 0xf9, 0x5f, 0x6f, 0xa1, 0xfe, 0xd7, 0xd8, 0xd6, 0x71, 0x06, 0xd1, 0x23, 0xf7, 0xd3, 0x95, 0x57, 0xe7, 0x81, 0x90, 0x8b, 0x41, 0x51, 0x83, 0x2d, 0x66, 0x95, 0x1f, 0x5a, 0x46, 0xb9, 0x59, 0x28, 0xc3, 0x11, 0x16, 0x05, 0x0e, 0x0e, 0xd0, 0x03, 0x5d, 0xc2, 0x1b, 0x1c, 0x45, 0x10, 0x1f, 0x7b, 0x0d, 0xe6, 0x5d, 0x27, 0x57, 0x01, 0x0d, 0xd0, 0x02, 0x8d, 0x76, 0xe8, 0x8c, 0xef, 0x64, 0x5f, 0x23, 0xa7, 0x4d, 0x95, 0xc9, 0x48, 0xc4, 0x85, 0x03, 0x36, 0x46, 0x0a, 0xf0, 0x7d, 0x42, 0x30, 0x14, 0x8d, 0xa1, 0x7d, 0xc6, 0xa8, 0x70, 0x8d, 0x02, 0xce, 0x6a, 0x04, 0x47, 0x5f, 0x30, 0xd4, 0xb1, 0xf7, 0xac, 0x0c, 0x3b, 0xa4, 0x7f, 0x36, 0x17, 0x15, 0xd5, 0x9e, 0x3a, 0x55, 0xe1, 0x28, 0x34, 0xc3, 0x2a, 0x53, 0x89, 0xf7, 0x61, 0xe9, 0x76, 0x7f, 0xd0, 0xe6, 0x37, 0x41, 0xc3, 0xc9, 0xfc, 0xcd, 0x05, 0x17, 0x84, 0x07, 0x5d, 0x46, 0xa9, 0x98, 0x3f, 0xfd, 0xf9, 0xd7, 0x0c, 0x56, 0xd8, 0x6b, 0xb3, 0x49, 0x97, 0x44, 0xec, 0xc6, 0x12, 0xbb, 0xf4, 0xb5, 0x9c, 0xfd, 0x83, 0xc2, 0x73, 0x34, 0x4e, 0x04, 0x40, 0xd7, 0x36, 0xb4, 0xf2, 0x31, 0x9b, 0x71, 0x1f, 0x70, 0x17, 0xd1, 0x33, 0x31, 0x1c, 0xf9, 0x16, 0xaf, 0xd9, 0xd2, 0xe5, 0x70, 0x16, 0xe6, 0xb5, 0x0c, 0xff, 0x27, 0x59, 0x67, 0xf7, 0x23, 0x7c, 0x03, 0xe2, 0xf5, 0xe8, 0xb5, 0x68, 0xe5, 0x19, 0xd7, 0x22, 0xc6, 0x70, 0xd7, 0xe9, 0x3d, 0x7b, 0x4e, 0xdf, 0xc5, 0xdc, 0x31, 0xbc, 0x8a, 0xb9, 0x63, 0x0f, 0x73, 0x68, 0x78, 0x90, 0xd4, 0x73, 0x39, 0x5a, 0x57, 0xce, 0xcb, 0xd4, 0x83, 0xd6, 0x95, 0xe5, 0x64, 0xcc, 0x3c, 0x27, 0xed, 0x63, 0xf4, 0xd8, 0x47, 0x84, 0xc0, 0x63, 0x14, 0x8e, 0xfd, 0xcf, 0xe4, 0xb5, 0xfa, 0x59, 0xe9, 0x43, 0xc6, 0x96, 0x79, 0x8f, 0xff, 0xb5, 0xfc, 0xde, 0x09, 0xe9, 0x20, 0x93, 0xcc, 0xc0, 0x05, 0x87, 0x4c, 0x37, 0x5a, 0x8f, 0x98, 0x0a, 0x44, 0xb7, 0x0d, 0xd4, 0xa5, 0x6b, 0xd0, 0x1c, 0xad, 0xd3, 0x32, 0x3c, 0x1a, 0x42, 0xcb, 0x39, 0x9c, 0x8b, 0x00, 0x7b, 0x4f, 0xa8, 0xf9, 0x0b, 0x06, 0x49, 0x7a, 0x6a, 0x24, 0xdc, 0x16, 0xe5, 0x0f, 0x31, 0xb4, 0x72, 0x28, 0x67, 0x95, 0xa9, 0x3a, 0x25, 0xa6, 0x24, 0xbc, 0xe5, 0x2e, 0xdc, 0x28, 0xdc, 0x2c, 0xb6, 0xe0, 0xe6, 0x3d, 0x92, 0x09, 0xbe, 0xb7, 0xe7, 0xe6, 0x3d, 0x4c, 0x74, 0xf8, 0x45, 0x7a, 0xde, 0x78, 0x13, 0xd2, 0x6f, 0x2f, 0xc5, 0x7a, 0x83, 0xcc, 0xc3, 0x55, 0x70, 0x44, 0x59, 0x7f, 0xf9, 0x3f, 0x72, 0x1a, 0x10, 0x07, 0x0b, 0xd3, 0x7a, 0x1e, 0x29, 0xe0, 0x65, 0x7a, 0x86, 0xe5, 0x18, 0xd9, 0xbd, 0xc9, 0x89, 0x5d, 0xfe, 0x71, 0x42, 0x77, 0x06, 0x80, 0x55, 0xdd, 0x1a, 0xb4, 0x01, 0x96, 0x3d, 0x3f, 0xbc, 0x00, 0x03, 0xf0, 0x53, 0xb8, 0x10, 0xaf, 0xda, 0xeb, 0x33, 0x0f, 0x69, 0x20, 0x80, 0x38, 0x88, 0x87, 0x1c, 0x21, 0x47, 0x34, 0x1c, 0xc4, 0xe7, 0x38, 0x78, 0xa8, 0x15, 0x2b, 0x06, 0xd0, 0x6c, 0xb6, 0xde, 0x6e, 0xca, 0xd6, 0xe0, 0x77, 0x5e, 0x5c, 0xa6, 0x33, 0x35, 0xee, 0x5c, 0xb4, 0x72, 0xab, 0xdf, 0x39, 0x65, 0xde, 0xf2, 0x9a, 0x59, 0xdb, 0x16, 0x54, 0x9c, 0x1e, 0x58, 0x1a, 0xef, 0x69, 0x0a, 0x9e, 0x5e, 0xb2, 0xb0, 0x63, 0x5d, 0x92, 0xfd, 0x38, 0xb6, 0x7c, 0xe2, 0xdc, 0x0d, 0x4b, 0xeb, 0x67, 0xd6, 0xb8, 0x53, 0x2b, 0x6e, 0x5e, 0x84, 0x37, 0x2d, 0x9b, 0x2e, 0xf2, 0x37, 0xcf, 0x6b, 0xc2, 0xdf, 0x36, 0x0f, 0x4d, 0x68, 0x1a, 0x96, 0xf3, 0x0a, 0x91, 0x76, 0x91, 0xfd, 0xe5, 0x3a, 0xd9, 0xde, 0xfd, 0x9f, 0xb2, 0xe0, 0x4f, 0xa9, 0xf0, 0xa7, 0x15, 0xb8, 0xb4, 0x0f, 0xf3, 0x41, 0x81, 0xc3, 0xd7, 0xe1, 0xc3, 0x8a, 0x3c, 0x0b, 0x47, 0x10, 0x7f, 0x3a, 0xc0, 0x4f, 0xe5, 0x3b, 0x43, 0x2f, 0x0e, 0xce, 0x61, 0x63, 0x34, 0xb0, 0x0e, 0x6a, 0x75, 0xe4, 0xce, 0x30, 0x1b, 0xa2, 0x27, 0x27, 0xe2, 0x98, 0x77, 0x11, 0xbc, 0x71, 0xe1, 0x17, 0x8a, 0x02, 0x43, 0x04, 0x54, 0xa3, 0x01, 0xfd, 0x1c, 0xbe, 0x08, 0xa2, 0x0e, 0x18, 0xe4, 0xac, 0x60, 0x48, 0xab, 0x9c, 0x77, 0x94, 0x11, 0x29, 0x66, 0x55, 0xb3, 0xf6, 0xf5, 0xe3, 0xbf, 0x80, 0xa6, 0x01, 0x1d, 0x1a, 0xf2, 0x3a, 0xfd, 0xd0, 0x19, 0xdf, 0x01, 0x7a, 0xbd, 0xf2, 0x0a, 0xf6, 0xab, 0x68, 0x6b, 0x69, 0x6e, 0x4c, 0x55, 0xe3, 0xfe, 0x40, 0x1b, 0xa8, 0x10, 0xf1, 0xab, 0x08, 0x8e, 0xd1, 0x03, 0xa1, 0x31, 0x3a, 0x6a, 0x8c, 0x59, 0x41, 0x60, 0x9e, 0xcd, 0xee, 0x96, 0x53, 0x63, 0xf4, 0xdb, 0x89, 0xfc, 0x89, 0x61, 0x74, 0x4f, 0xe5, 0xf7, 0xa6, 0x64, 0xcc, 0x99, 0x21, 0xc8, 0xfe, 0xeb, 0xc6, 0x91, 0x3a, 0xfe, 0x13, 0x0e, 0xcf, 0xa7, 0xf5, 0xe0, 0x59, 0xca, 0x58, 0x47, 0x31, 0x64, 0x78, 0x13, 0x5a, 0x4c, 0xea, 0x53, 0xe5, 0x65, 0xa5, 0x76, 0x11, 0xa0, 0xfd, 0x09, 0x31, 0x1f, 0x47, 0x70, 0x76, 0x14, 0x5c, 0xe9, 0x0d, 0xf4, 0x4b, 0x73, 0x21, 0x4e, 0x82, 0xd6, 0x8f, 0x7a, 0x84, 0xec, 0xb5, 0xb1, 0x09, 0x0e, 0x89, 0x58, 0xb9, 0xaa, 0x9b, 0x82, 0x20, 0xee, 0x0d, 0x9a, 0x4e, 0x24, 0x1f, 0x99, 0xa3, 0xc1, 0x8e, 0x64, 0xb3, 0xb0, 0xf5, 0x99, 0x17, 0xc6, 0xc0, 0x05, 0x2a, 0x2a, 0x3e, 0x96, 0xca, 0xe0, 0x62, 0xc3, 0xf1, 0xb0, 0xd3, 0x11, 0x72, 0xe2, 0x3e, 0x20, 0x5b, 0xfa, 0xb1, 0xb9, 0xde, 0x41, 0xa6, 0x64, 0x91, 0x86, 0xca, 0x44, 0xdb, 0x79, 0x88, 0x6f, 0xe7, 0x48, 0x88, 0x89, 0x06, 0x13, 0x74, 0x30, 0xff, 0x91, 0xcf, 0x72, 0xc7, 0xa4, 0x73, 0x96, 0x56, 0x2f, 0xc0, 0x06, 0x22, 0xdb, 0x2e, 0xda, 0x7a, 0x7a, 0xda, 0x64, 0xe9, 0xb3, 0xd3, 0x4f, 0x63, 0xeb, 0x11, 0xc6, 0xbd, 0xb7, 0x7d, 0x16, 0x9c, 0x01, 0x9b, 0x73, 0xb9, 0x5d, 0x37, 0xa3, 0xc6, 0x03, 0x77, 0xb9, 0x7c, 0x3f, 0xf0, 0x7f, 0xef, 0x4e, 0xdc, 0x17, 0xcb, 0x87, 0xcc, 0xd2, 0xa0, 0x54, 0x77, 0x9b, 0xf4, 0x53, 0x97, 0x8f, 0x9d, 0xf1, 0xaa, 0x6d, 0xfd, 0xc2, 0xcc, 0xb9, 0x0a, 0x96, 0x9f, 0x0b, 0xe4, 0xf3, 0x99, 0xfb, 0xb3, 0xce, 0x5b, 0x0e, 0xa9, 0xf0, 0x63, 0x39, 0xf0, 0xbd, 0x2a, 0xfc, 0xb8, 0xea, 0x13, 0x70, 0x39, 0xfb, 0x2a, 0x91, 0xab, 0x0b, 0xe4, 0x78, 0x29, 0xf7, 0x80, 0xb1, 0xca, 0x7f, 0x30, 0xa7, 0x9c, 0xa7, 0x54, 0xf8, 0xd3, 0x6a, 0x39, 0xfb, 0xb2, 0xcb, 0x81, 0xaf, 0x83, 0x3b, 0x33, 0xe7, 0x11, 0x19, 0x38, 0xff, 0x6b, 0xd9, 0x3e, 0x77, 0xa7, 0x74, 0x30, 0x1b, 0x2e, 0x38, 0x64, 0xbb, 0x75, 0xbc, 0x3e, 0x7f, 0x05, 0xc9, 0x73, 0x05, 0xbe, 0xe3, 0xc2, 0x92, 0xa7, 0x0f, 0x15, 0x32, 0x1a, 0x4e, 0xab, 0xc4, 0x47, 0x51, 0x7e, 0xd1, 0xf8, 0x28, 0x78, 0xe4, 0x84, 0x54, 0xa7, 0x91, 0x55, 0x64, 0x3d, 0x66, 0xfb, 0x79, 0x38, 0x2a, 0x48, 0x0a, 0x2d, 0x2b, 0x06, 0x30, 0x12, 0xc2, 0x97, 0x6d, 0x68, 0xc7, 0x45, 0x4f, 0x57, 0x64, 0x72, 0xf0, 0x8c, 0xfb, 0x46, 0x76, 0x70, 0x95, 0xfe, 0x34, 0x0e, 0x9d, 0x5d, 0x01, 0x2a, 0xa2, 0x68, 0xec, 0x60, 0x03, 0x01, 0x9d, 0xae, 0x30, 0x11, 0x19, 0xd3, 0x1e, 0x20, 0x77, 0x4c, 0xc9, 0x32, 0xcc, 0x7d, 0x25, 0x5f, 0x1c, 0x9f, 0x25, 0xe7, 0x42, 0x78, 0x18, 0x99, 0x83, 0xd3, 0x66, 0x2d, 0xae, 0xc3, 0xa7, 0x25, 0xfb, 0x73, 0x4f, 0xf8, 0x94, 0x03, 0x21, 0x34, 0x72, 0x56, 0x85, 0x9a, 0xcb, 0x5c, 0xf8, 0xe8, 0x44, 0x32, 0x2a, 0xfa, 0xcf, 0x01, 0xfe, 0xb7, 0xe4, 0xbc, 0xe4, 0x02, 0x39, 0x4e, 0xf0, 0x06, 0xd5, 0xd6, 0xaf, 0x84, 0xf4, 0xeb, 0x06, 0xb9, 0x5f, 0x0f, 0x8f, 0x09, 0x3f, 0x92, 0x03, 0x3f, 0xa4, 0xc2, 0x8f, 0xe6, 0xc0, 0xf7, 0xaa, 0xf0, 0xe3, 0x39, 0xf0, 0xed, 0x2a, 0xfc, 0x44, 0x0e, 0xbc, 0x4b, 0x85, 0x3f, 0x36, 0x4e, 0xf9, 0x8f, 0x8f, 0x43, 0xcf, 0xf7, 0x65, 0xbb, 0x6b, 0x02, 0x47, 0xe3, 0xc3, 0x06, 0x8a, 0x71, 0x24, 0x61, 0x12, 0x15, 0x14, 0xad, 0xcb, 0xa0, 0x9e, 0x68, 0xde, 0x4a, 0x06, 0x45, 0x87, 0x1d, 0x02, 0x9f, 0xc7, 0x5e, 0xec, 0x28, 0xa6, 0xbb, 0x25, 0x92, 0x15, 0x5e, 0xde, 0x25, 0x29, 0xba, 0xb6, 0x4d, 0xf1, 0x09, 0x71, 0xd8, 0x9d, 0x4c, 0xf3, 0xd0, 0x89, 0x6d, 0x93, 0x26, 0x6d, 0x3b, 0x31, 0xb4, 0xf1, 0xc4, 0x55, 0x93, 0x27, 0x5f, 0x75, 0x62, 0xe3, 0xcd, 0x7b, 0xf6, 0xdc, 0x7c, 0xeb, 0xde, 0xbd, 0x9c, 0x66, 0xfa, 0xce, 0xa7, 0xb7, 0x6c, 0x39, 0x79, 0xc3, 0xf4, 0xe9, 0x37, 0x9c, 0xdc, 0xb2, 0xe5, 0xe9, 0x9d, 0xd3, 0x3f, 0xbf, 0xe6, 0xe4, 0xb1, 0x63, 0xa7, 0x4e, 0x1d, 0x3b, 0x76, 0x32, 0x8b, 0xce, 0x17, 0x55, 0x3a, 0xdf, 0x1e, 0x83, 0xce, 0x5e, 0xba, 0x67, 0x71, 0xa9, 0xc4, 0x96, 0x64, 0x88, 0x0d, 0xe2, 0x39, 0x6a, 0xcc, 0x27, 0x21, 0x7c, 0xa6, 0x66, 0x1b, 0xd5, 0x12, 0xe1, 0x2f, 0xd0, 0x12, 0x6a, 0xd3, 0x89, 0xe9, 0x55, 0xda, 0x81, 0xe4, 0x37, 0xbb, 0x1f, 0x4e, 0xab, 0xf0, 0xe7, 0xc0, 0x11, 0x15, 0x7e, 0x94, 0xf8, 0x04, 0x6f, 0x90, 0x6d, 0x4c, 0x09, 0x7c, 0xe4, 0x87, 0x18, 0x9f, 0xd8, 0x98, 0x6e, 0x90, 0x6d, 0x4c, 0x33, 0xf8, 0x25, 0xc4, 0xc6, 0x74, 0x83, 0x6c, 0x63, 0x9a, 0x0d, 0xff, 0x81, 0x0a, 0xff, 0xa5, 0x02, 0xc7, 0xf4, 0xa8, 0xe3, 0x22, 0x46, 0xe9, 0x19, 0x35, 0xfe, 0x32, 0xe3, 0xe6, 0x18, 0x79, 0x8e, 0xed, 0xdb, 0x1e, 0xc4, 0xe3, 0x0c, 0x7d, 0x33, 0x83, 0x56, 0x39, 0xc6, 0xd2, 0x09, 0x0f, 0x8e, 0x01, 0xde, 0xe5, 0x43, 0x9f, 0x22, 0x8d, 0x4f, 0xef, 0x26, 0xe6, 0xc7, 0x0c, 0x2b, 0xb0, 0x8c, 0x40, 0x22, 0x80, 0xf2, 0x34, 0x02, 0xa8, 0xec, 0xcd, 0x81, 0x27, 0x13, 0x6e, 0x91, 0x12, 0x2a, 0xb4, 0x1c, 0x21, 0x8a, 0x2c, 0x23, 0x0e, 0xa1, 0x69, 0x8a, 0x15, 0x85, 0xf5, 0xa3, 0xf1, 0x41, 0x06, 0xbd, 0x3f, 0x6d, 0x6c, 0x69, 0xaa, 0xaf, 0xad, 0x08, 0x87, 0x43, 0x16, 0x1a, 0x84, 0x88, 0x84, 0x08, 0x75, 0xaa, 0x1e, 0x48, 0x59, 0xe1, 0x41, 0xd9, 0xec, 0xcd, 0x7b, 0x4e, 0x68, 0x50, 0x76, 0xf1, 0xf0, 0x13, 0xcc, 0xa4, 0xe1, 0x27, 0xf6, 0xde, 0x4a, 0x7b, 0x0f, 0xfe, 0x86, 0x84, 0x04, 0x9d, 0xd0, 0xb0, 0xf2, 0xeb, 0x1b, 0xdb, 0x70, 0xa7, 0xe2, 0x4e, 0x6e, 0xbe, 0xe0, 0xce, 0xe5, 0xd6, 0xb0, 0xcf, 0xa2, 0x06, 0x04, 0xe5, 0x9e, 0xd9, 0xf6, 0xf9, 0xc3, 0xc7, 0x4e, 0x92, 0x5e, 0xfd, 0xbc, 0x1a, 0x07, 0x02, 0xad, 0x5d, 0x1a, 0xd5, 0xf6, 0x5d, 0xff, 0xed, 0x8d, 0xb8, 0xbb, 0x71, 0xf7, 0x6f, 0x78, 0x74, 0xc7, 0x6c, 0xbd, 0xc1, 0x1f, 0x2a, 0x93, 0x83, 0x80, 0x82, 0xac, 0x3e, 0xb8, 0x4e, 0xe1, 0x25, 0x37, 0x3f, 0x87, 0xc7, 0x4f, 0xa9, 0x3c, 0x7e, 0x3a, 0x1b, 0x4e, 0xfc, 0x9f, 0x37, 0xc8, 0xfe, 0xcf, 0x87, 0xf1, 0x82, 0x48, 0x9f, 0x67, 0x7b, 0x40, 0xab, 0xb8, 0xaf, 0x2a, 0x65, 0xf0, 0x3b, 0x72, 0xca, 0x78, 0x4c, 0x85, 0xdf, 0x98, 0x03, 0x3f, 0xa9, 0xc2, 0x6f, 0x18, 0x07, 0x7e, 0x7d, 0x0e, 0x8d, 0x4f, 0xaa, 0xf0, 0x29, 0xb9, 0x70, 0x65, 0x7c, 0xf2, 0x6d, 0xe3, 0xe0, 0xb7, 0xe7, 0xc0, 0x4f, 0xaa, 0xf8, 0x4d, 0xe3, 0xc0, 0xd3, 0xb9, 0x70, 0xb5, 0x9c, 0x09, 0x39, 0xf0, 0xbc, 0xf9, 0x40, 0x85, 0x1f, 0x57, 0xf1, 0x5b, 0xbe, 0xd0, 0x7c, 0x79, 0x58, 0x81, 0x33, 0xec, 0xd8, 0xf3, 0x22, 0xda, 0x25, 0x50, 0xf8, 0xe4, 0x91, 0x47, 0xb8, 0x4e, 0x72, 0x86, 0x36, 0x24, 0xcf, 0xdf, 0x0f, 0xc8, 0xbe, 0x7d, 0x87, 0x39, 0x1b, 0xa7, 0x07, 0x26, 0xd4, 0x4f, 0xd7, 0xa4, 0xf1, 0x5d, 0x24, 0x40, 0x73, 0x0e, 0x0b, 0x14, 0x57, 0x30, 0x1f, 0x71, 0x8e, 0xc0, 0x93, 0x4e, 0x89, 0xaa, 0x3e, 0x05, 0xf1, 0x82, 0xe7, 0xe7, 0x94, 0xfb, 0xc6, 0xb1, 0x50, 0x42, 0x0a, 0x4a, 0xba, 0x18, 0x49, 0x80, 0x4f, 0x75, 0xcc, 0x1b, 0x8d, 0x81, 0x96, 0x46, 0x6d, 0xd0, 0x56, 0x1a, 0x8b, 0x12, 0x07, 0x31, 0xc5, 0x17, 0x8f, 0x1a, 0x4e, 0xc6, 0xe4, 0xe5, 0x4f, 0x5e, 0x26, 0xc9, 0x9a, 0xe8, 0x82, 0x3d, 0x19, 0x47, 0x8a, 0x2b, 0xeb, 0x74, 0x81, 0xe9, 0x73, 0x96, 0xd6, 0xcf, 0xbf, 0x69, 0xb0, 0xa1, 0x6d, 0xe3, 0x3d, 0xab, 0x16, 0x5e, 0x93, 0xc2, 0x2b, 0xe2, 0xa7, 0xaa, 0x27, 0xc5, 0x94, 0x1d, 0xd1, 0x8e, 0xa4, 0x67, 0xd2, 0xb5, 0x27, 0xb7, 0x6e, 0x7c, 0x7a, 0xcf, 0xac, 0xd6, 0x7a, 0x69, 0x2a, 0x3f, 0x23, 0x46, 0xd7, 0x3f, 0xd4, 0x6e, 0xc2, 0x8f, 0x4d, 0xd4, 0x5e, 0xa2, 0x4d, 0x59, 0x17, 0x31, 0xfc, 0x29, 0x19, 0x8e, 0xc7, 0xec, 0x9d, 0xe4, 0x7c, 0xee, 0x23, 0x95, 0x4f, 0x95, 0xe0, 0x1b, 0x79, 0x7c, 0xc2, 0x27, 0x5b, 0x11, 0x0d, 0xe4, 0x44, 0x88, 0x3d, 0x11, 0x97, 0x93, 0xb6, 0x62, 0xaf, 0xd5, 0xf5, 0x6a, 0x63, 0xd7, 0xe1, 0x29, 0x60, 0x8d, 0xea, 0x27, 0x7b, 0x66, 0xe4, 0x0d, 0x0a, 0x72, 0xba, 0x2c, 0x0b, 0x4f, 0xab, 0x5d, 0x95, 0xeb, 0x0f, 0x99, 0x83, 0x8b, 0x7d, 0x52, 0x2a, 0xca, 0xc9, 0xfd, 0x0a, 0x66, 0xa5, 0x2e, 0xcb, 0xad, 0x71, 0x4c, 0x56, 0x92, 0xf8, 0x9c, 0x21, 0xba, 0xfb, 0x3c, 0x2b, 0x4b, 0x6f, 0xbf, 0xfd, 0xd4, 0xbe, 0x7d, 0x67, 0x64, 0xeb, 0x91, 0x3d, 0xdb, 0xb6, 0xed, 0xe1, 0xd2, 0xf2, 0x9d, 0x3a, 0xf1, 0x17, 0x25, 0xbc, 0x6a, 0x85, 0x7e, 0xaa, 0x84, 0x15, 0x14, 0x9a, 0xb0, 0x6b, 0x68, 0x3d, 0x14, 0x40, 0x84, 0x7a, 0x8b, 0xca, 0x3e, 0x51, 0xd9, 0x70, 0x0d, 0xf1, 0x22, 0xcd, 0x05, 0xe1, 0x2c, 0xc1, 0xb2, 0xe9, 0x42, 0x28, 0x97, 0x57, 0x54, 0xa7, 0x1b, 0x94, 0xfd, 0x41, 0x35, 0x50, 0x71, 0x1f, 0x2d, 0x22, 0x8a, 0xdc, 0x28, 0x76, 0x8d, 0x87, 0x8e, 0xa9, 0x48, 0x8c, 0x8f, 0x8e, 0xb6, 0x64, 0x6b, 0xf2, 0x5c, 0x4e, 0xeb, 0x01, 0x27, 0xf2, 0x22, 0x76, 0x39, 0xcd, 0x7b, 0x8b, 0xe0, 0xcb, 0x0e, 0x58, 0x1b, 0xe8, 0x9b, 0x39, 0x7e, 0xa7, 0xa8, 0x8f, 0x5a, 0x9a, 0x6c, 0x41, 0xa4, 0xfe, 0x95, 0x9f, 0xb9, 0x8f, 0xc6, 0xf4, 0x3d, 0x0d, 0x09, 0x67, 0x97, 0x81, 0xc6, 0xfa, 0x7c, 0x35, 0xb1, 0xb2, 0xbe, 0xf9, 0x0c, 0x42, 0x71, 0xc1, 0x13, 0x53, 0x1f, 0xcf, 0x51, 0x19, 0x4d, 0x8a, 0x88, 0x64, 0xcf, 0x0d, 0x36, 0x34, 0xe6, 0x6f, 0x4a, 0x3b, 0x6d, 0x1a, 0x51, 0xe0, 0x91, 0xa8, 0xc0, 0xb2, 0x70, 0x10, 0xe9, 0x23, 0xa1, 0x12, 0x12, 0x57, 0xf5, 0x7f, 0x74, 0x9a, 0x48, 0xc4, 0x12, 0x99, 0x69, 0xa2, 0x21, 0xd6, 0xe0, 0xc2, 0xb1, 0x5a, 0x47, 0x31, 0x0f, 0xc7, 0x6a, 0xcd, 0xe3, 0x55, 0x91, 0xeb, 0xbc, 0xab, 0x36, 0x26, 0xab, 0x46, 0x31, 0x6c, 0xf5, 0x92, 0x95, 0xdb, 0x36, 0xd6, 0xd4, 0x65, 0x58, 0xc4, 0xdd, 0x3b, 0xfb, 0x5b, 0x55, 0xb7, 0xad, 0xc9, 0xe3, 0xd3, 0x81, 0xb7, 0x27, 0x1f, 0x9a, 0xfd, 0x2d, 0x43, 0xf6, 0xfc, 0x81, 0x18, 0xf4, 0x3c, 0x99, 0x67, 0x2f, 0xa6, 0xf3, 0x87, 0x83, 0xce, 0x1f, 0x6b, 0x10, 0xfc, 0x15, 0x72, 0xa6, 0x7a, 0xf1, 0x08, 0xb1, 0xcf, 0xb2, 0x53, 0xf8, 0xd5, 0xd2, 0xe5, 0xcc, 0xb3, 0xc4, 0xaf, 0xff, 0x62, 0xf9, 0x0c, 0x6c, 0x31, 0xcd, 0x47, 0x83, 0xf0, 0x7f, 0x8f, 0x78, 0x1c, 0x00, 0xdf, 0xa6, 0x6c, 0xd2, 0x09, 0x68, 0x9f, 0x61, 0x81, 0x2c, 0xde, 0xce, 0x74, 0x1f, 0x2b, 0x97, 0x21, 0x1a, 0x19, 0x82, 0x7f, 0x30, 0xf2, 0x0f, 0x45, 0x2e, 0x0a, 0x45, 0xc8, 0x0a, 0x90, 0x24, 0x7b, 0xa5, 0x9b, 0x14, 0x7c, 0xdf, 0x47, 0x1d, 0x00, 0xf9, 0x1e, 0xb9, 0x8c, 0x40, 0x06, 0x07, 0x8d, 0xcb, 0x55, 0x99, 0xdd, 0x8c, 0x8a, 0x87, 0x64, 0x06, 0x7b, 0x75, 0x42, 0x9c, 0xd5, 0x26, 0x07, 0x35, 0x73, 0xc9, 0x4d, 0x31, 0xf1, 0x36, 0x38, 0x68, 0xa1, 0xd9, 0x6d, 0x32, 0x37, 0xdb, 0x4e, 0x97, 0x88, 0x79, 0x8e, 0x8f, 0x23, 0xc4, 0xcc, 0xf1, 0x16, 0xcd, 0x14, 0xb1, 0xac, 0x7a, 0xd7, 0xf2, 0x60, 0x8b, 0xd3, 0x28, 0x7a, 0x4d, 0x8d, 0x81, 0xca, 0x69, 0x4d, 0xe5, 0xb6, 0x3b, 0xef, 0x3c, 0x75, 0xcb, 0x2d, 0x43, 0x01, 0x17, 0xcf, 0xd6, 0x0d, 0x6e, 0x32, 0x9b, 0xf7, 0xf9, 0xf4, 0xbe, 0xba, 0x19, 0xd5, 0x93, 0xf7, 0xc8, 0x77, 0x4b, 0x2e, 0xbf, 0xb2, 0x97, 0x64, 0x9e, 0x25, 0xfa, 0x05, 0xe5, 0xf1, 0xd3, 0x32, 0x8f, 0x2f, 0x91, 0xf6, 0x65, 0xf3, 0x12, 0xbe, 0x8e, 0x79, 0xa9, 0xc0, 0xc9, 0x1e, 0x02, 0xc3, 0xa3, 0xe0, 0x31, 0x87, 0xfa, 0x94, 0xf0, 0xba, 0x4f, 0xba, 0x93, 0xf9, 0x09, 0xe2, 0x75, 0x12, 0xc6, 0x29, 0xe7, 0x4c, 0x0a, 0xaf, 0x0b, 0xa9, 0xf9, 0x38, 0x9d, 0x0d, 0x4c, 0x0a, 0xbf, 0x55, 0x68, 0x79, 0x1e, 0x54, 0xc0, 0xf3, 0x94, 0x49, 0xe9, 0x09, 0x0a, 0xe8, 0x57, 0xba, 0xa3, 0x24, 0xa7, 0x13, 0x58, 0x56, 0x3c, 0x77, 0xb4, 0x7d, 0x79, 0xc6, 0xc9, 0x3d, 0xb7, 0x2b, 0xc6, 0xc3, 0xc6, 0x04, 0x24, 0x55, 0x53, 0xf4, 0x71, 0xdf, 0xca, 0x32, 0x3f, 0xc0, 0xc1, 0x5d, 0xe4, 0xfe, 0xcc, 0x79, 0x2f, 0xab, 0x37, 0xc7, 0x7a, 0xad, 0x9f, 0xf4, 0x2e, 0xde, 0xa0, 0x8e, 0xdb, 0xbb, 0xe3, 0x98, 0xb1, 0x93, 0x9e, 0x4e, 0x8f, 0xea, 0xe9, 0x47, 0x47, 0xed, 0x5b, 0x47, 0x75, 0xbb, 0xe4, 0xce, 0x33, 0x5e, 0xff, 0x58, 0xee, 0xff, 0x1b, 0x50, 0xff, 0xff, 0x84, 0xdc, 0x07, 0x12, 0x59, 0x62, 0x58, 0x2a, 0x4b, 0x23, 0xff, 0x49, 0xe0, 0x47, 0x14, 0x38, 0x1c, 0x91, 0x65, 0xac, 0x7d, 0xe4, 0x5d, 0xd6, 0x47, 0x64, 0x72, 0x33, 0x95, 0xc9, 0x9a, 0x6c, 0xf8, 0x21, 0x15, 0x7e, 0x74, 0x1c, 0xf8, 0x31, 0x05, 0x2e, 0x5d, 0xce, 0xfa, 0xc8, 0x5e, 0x87, 0xc2, 0xff, 0xc9, 0x49, 0xc7, 0x0f, 0xc1, 0x47, 0x7b, 0xb6, 0x36, 0x1c, 0xe3, 0xb7, 0x0d, 0x42, 0xbe, 0x08, 0x32, 0xdc, 0xb8, 0x31, 0x7e, 0x4b, 0xb2, 0x63, 0xfc, 0xb6, 0x34, 0x95, 0x92, 0xeb, 0xb9, 0x33, 0xc4, 0xf8, 0x8d, 0x8e, 0x17, 0xe2, 0x97, 0x79, 0x45, 0x63, 0x35, 0x15, 0xc4, 0x1a, 0xba, 0x52, 0xf1, 0xc9, 0x55, 0xde, 0xe4, 0xf4, 0xc5, 0xcb, 0x17, 0x4f, 0x4f, 0xa6, 0x06, 0x6e, 0x5d, 0xb2, 0xf6, 0xe1, 0xd6, 0x0a, 0x8d, 0xdb, 0x6e, 0x2b, 0x6d, 0xec, 0x6b, 0xad, 0xe9, 0xaa, 0x76, 0x27, 0xa7, 0x2f, 0x59, 0xb1, 0x64, 0x7a, 0xb2, 0x7a, 0xf1, 0xce, 0x05, 0x2b, 0xef, 0x69, 0x66, 0xa7, 0x1b, 0xf5, 0x4e, 0xaf, 0xd3, 0x11, 0x49, 0xf9, 0xa3, 0x35, 0x61, 0x7f, 0xa0, 0xac, 0x6d, 0x5e, 0xfb, 0xd4, 0x4b, 0x17, 0xd4, 0x4c, 0xa1, 0x36, 0x3f, 0x3e, 0x4f, 0xa2, 0xb1, 0xb8, 0xb4, 0x2e, 0x5a, 0x18, 0x88, 0xa7, 0x17, 0x4d, 0x68, 0x59, 0x37, 0xbb, 0x3a, 0xdd, 0xa8, 0xf2, 0xe4, 0x29, 0xb5, 0xed, 0x4f, 0xab, 0x3c, 0xd9, 0x97, 0xcd, 0x13, 0xf8, 0xba, 0x33, 0xc3, 0xc3, 0x06, 0xc1, 0x2f, 0xc3, 0xa3, 0xd8, 0xee, 0x14, 0x47, 0x3a, 0xba, 0x76, 0x31, 0x8d, 0x6b, 0xbf, 0x58, 0xc9, 0x69, 0x4b, 0xca, 0x15, 0x4e, 0x2b, 0xef, 0xf3, 0x7b, 0xb3, 0xcb, 0x55, 0xeb, 0x8b, 0x92, 0x72, 0x69, 0xad, 0x88, 0xdf, 0xd2, 0x9d, 0xec, 0x76, 0xc4, 0xef, 0x73, 0xe0, 0x14, 0x59, 0x32, 0x9b, 0xa1, 0x56, 0xf4, 0xa1, 0xe9, 0x09, 0x1f, 0xf7, 0xb0, 0x34, 0x10, 0x8f, 0x0a, 0x60, 0x54, 0x5b, 0xe6, 0x1a, 0xa0, 0xd1, 0xf2, 0x5a, 0x0d, 0x0e, 0xc4, 0x23, 0x68, 0x79, 0xb4, 0x21, 0x93, 0x6f, 0x54, 0xd6, 0x77, 0x63, 0x2b, 0x7c, 0x41, 0x89, 0xa3, 0x93, 0x77, 0x00, 0x54, 0x44, 0x4c, 0xe7, 0xd4, 0x37, 0x75, 0x5a, 0x5e, 0xb7, 0x5e, 0x0f, 0x75, 0x82, 0x56, 0x97, 0x5d, 0x02, 0x18, 0xf7, 0xfd, 0x12, 0x62, 0x3a, 0x77, 0xd6, 0xf7, 0x65, 0xdb, 0x0b, 0x39, 0x7c, 0x4e, 0xee, 0xa9, 0xd2, 0xc4, 0xf1, 0x5e, 0x87, 0x38, 0x22, 0x2e, 0x33, 0x34, 0x6e, 0x31, 0x39, 0x47, 0x4d, 0xfd, 0x69, 0xdc, 0x37, 0xe7, 0x80, 0x73, 0x26, 0x4d, 0xe8, 0x68, 0x6b, 0xac, 0x0f, 0x45, 0x4a, 0xb0, 0x3c, 0x1b, 0x74, 0xbe, 0xb1, 0x0f, 0x9c, 0x68, 0x3c, 0x81, 0x71, 0xc6, 0x9e, 0x98, 0x3f, 0x52, 0x45, 0x76, 0x7b, 0xbe, 0x44, 0x3f, 0x51, 0x3c, 0x31, 0x7a, 0xe6, 0xa1, 0x98, 0x18, 0x63, 0x00, 0x27, 0xf2, 0x0e, 0xab, 0x34, 0x9a, 0x33, 0x0c, 0xcc, 0xa9, 0xa3, 0x87, 0xf2, 0xa2, 0x2e, 0xc5, 0x0e, 0x9b, 0x79, 0x98, 0xf8, 0x0d, 0x5d, 0x2e, 0xfb, 0x0d, 0x5d, 0x92, 0xeb, 0x37, 0x84, 0x70, 0x62, 0x68, 0xed, 0x7d, 0x0a, 0xed, 0xf0, 0xa2, 0xe0, 0x0a, 0x89, 0xc8, 0x73, 0x15, 0x95, 0xe7, 0x38, 0x5a, 0x0f, 0x4e, 0x21, 0x78, 0x3b, 0xbe, 0xeb, 0xc3, 0x5b, 0x7e, 0x4b, 0x0c, 0xb2, 0x7c, 0x7b, 0x91, 0x9f, 0xe3, 0xa0, 0x68, 0x96, 0x87, 0x58, 0x1e, 0x8c, 0x8e, 0x32, 0x37, 0x09, 0x92, 0xc1, 0xf1, 0xdc, 0x26, 0x20, 0xb2, 0x90, 0x15, 0xe1, 0x90, 0x80, 0x86, 0x04, 0x31, 0x1a, 0x94, 0xa7, 0xe6, 0x45, 0x4a, 0x9f, 0xc6, 0x73, 0xd0, 0xf0, 0x9c, 0x0c, 0x99, 0xf5, 0x59, 0xd8, 0xb8, 0xeb, 0x16, 0x29, 0x21, 0x98, 0xf5, 0x21, 0x8b, 0xa5, 0xd4, 0x1b, 0xb6, 0x27, 0x70, 0x64, 0x88, 0x88, 0x09, 0x8e, 0xeb, 0x1d, 0x86, 0x43, 0x69, 0xe1, 0xf8, 0x5a, 0x6d, 0xb0, 0xc1, 0x86, 0x4f, 0x9b, 0x49, 0xba, 0x63, 0xe5, 0x78, 0xc0, 0x59, 0x0c, 0x99, 0x53, 0x65, 0xe7, 0xb5, 0x14, 0x8c, 0x69, 0x33, 0x16, 0x5f, 0xb8, 0x62, 0xb3, 0xc5, 0xf5, 0x8d, 0x72, 0xce, 0x65, 0x1d, 0x80, 0x01, 0xe9, 0x95, 0x96, 0x0a, 0x5f, 0xac, 0xd0, 0xae, 0xd1, 0xe9, 0x85, 0xe6, 0x9e, 0x48, 0x52, 0x6c, 0x1e, 0xc3, 0x7a, 0xec, 0x2a, 0xcb, 0x75, 0x17, 0x30, 0xcb, 0x1c, 0x8e, 0x5e, 0x17, 0xac, 0xb4, 0xd8, 0xa4, 0xc3, 0x7b, 0x86, 0xbf, 0xd9, 0xda, 0x62, 0xb2, 0x98, 0xb4, 0xba, 0x25, 0x98, 0xbf, 0x65, 0x23, 0xef, 0x32, 0xff, 0x49, 0xf6, 0x4c, 0x57, 0xc8, 0x7b, 0xa6, 0xe7, 0x48, 0xdf, 0x94, 0xa1, 0xf5, 0xf8, 0x24, 0x8f, 0xf3, 0x5b, 0x5d, 0x41, 0xe7, 0x0c, 0x59, 0xe6, 0xcb, 0xa4, 0x0f, 0x99, 0xd7, 0x32, 0x70, 0xfe, 0xd7, 0x2a, 0xfc, 0x20, 0x23, 0x65, 0xe0, 0x82, 0xa3, 0x86, 0xc6, 0x88, 0x7e, 0x70, 0xa4, 0x8e, 0xfd, 0x06, 0xea, 0xa7, 0x72, 0xd4, 0xdb, 0x93, 0xd2, 0x9d, 0x02, 0xea, 0x85, 0xa4, 0x1d, 0x47, 0xcc, 0x8f, 0x05, 0x19, 0x9e, 0xc3, 0xa1, 0xfd, 0x59, 0xb8, 0x09, 0xb0, 0x0c, 0xbb, 0x09, 0x47, 0xe2, 0xb8, 0x50, 0x84, 0xd8, 0xad, 0x86, 0x9e, 0xe2, 0x73, 0x1c, 0xbf, 0x08, 0xb1, 0x79, 0x1d, 0xdf, 0x63, 0x8d, 0xc4, 0xc3, 0x21, 0x4b, 0xa9, 0x05, 0x73, 0xd6, 0x96, 0xcb, 0x3a, 0xe2, 0xdd, 0x09, 0x1d, 0x59, 0xec, 0xa3, 0x32, 0x41, 0x0f, 0xed, 0x6d, 0xa1, 0x17, 0x33, 0xfc, 0xd2, 0xac, 0xc3, 0xe7, 0xf4, 0xc3, 0x1f, 0x67, 0x31, 0xad, 0xea, 0xda, 0xa9, 0xfd, 0x97, 0xf8, 0x1e, 0xc6, 0x27, 0xf6, 0xf0, 0xfd, 0xa3, 0x47, 0xa1, 0x9d, 0x99, 0x92, 0xe1, 0xd4, 0x83, 0x2e, 0xdf, 0xe7, 0x3f, 0xde, 0xa5, 0x30, 0xab, 0xbc, 0xaa, 0x6b, 0xb2, 0xf4, 0x96, 0xcb, 0xc7, 0x5a, 0xa1, 0x8d, 0xf2, 0x8d, 0xdb, 0x42, 0xe6, 0xbe, 0xab, 0xc9, 0x9e, 0xff, 0x24, 0xb3, 0x1c, 0xc8, 0xfc, 0xe4, 0x36, 0x93, 0x75, 0xec, 0x6a, 0x7a, 0x06, 0x2b, 0xc3, 0x63, 0x23, 0xef, 0xf2, 0x01, 0xb2, 0xee, 0x5d, 0x2d, 0x51, 0x3f, 0xa6, 0xa5, 0x0a, 0x3e, 0x9f, 0x24, 0xeb, 0x18, 0xc5, 0x3f, 0x9a, 0x29, 0x87, 0x17, 0xc9, 0xd9, 0x01, 0x2d, 0xff, 0xe9, 0x2c, 0xb8, 0x8d, 0xe8, 0x49, 0x14, 0xff, 0xb1, 0xac, 0xf2, 0x0b, 0x89, 0xad, 0x14, 0x2d, 0xff, 0x78, 0x56, 0xf9, 0xba, 0xac, 0xf2, 0x1f, 0xcf, 0x2a, 0x27, 0xbb, 0xde, 0x63, 0x4a, 0x39, 0xd2, 0x0f, 0x79, 0x17, 0x19, 0x0f, 0xb8, 0x9c, 0x18, 0x6a, 0xd7, 0x52, 0x6a, 0xeb, 0xc6, 0x9c, 0x9b, 0x6b, 0xeb, 0xa6, 0xe2, 0xb6, 0x51, 0xdc, 0x2c, 0x9c, 0xec, 0x18, 0xc6, 0x98, 0xae, 0x6e, 0x62, 0xdf, 0x44, 0xca, 0x43, 0xed, 0xde, 0x45, 0xed, 0x9b, 0x70, 0xb9, 0x39, 0xf6, 0x4d, 0x0c, 0xe1, 0xdd, 0x5b, 0xa8, 0x6e, 0x1b, 0x48, 0x83, 0xfb, 0xd3, 0x3a, 0xb4, 0x8b, 0xd4, 0xc7, 0x91, 0xe4, 0x29, 0x17, 0xa2, 0xb5, 0x40, 0x87, 0xe4, 0x5a, 0x07, 0x86, 0x0c, 0x38, 0x75, 0x23, 0x23, 0xf2, 0xcc, 0x72, 0x63, 0xc6, 0xb4, 0x46, 0x8b, 0xb6, 0x7c, 0xfa, 0x7e, 0x6a, 0x18, 0x2c, 0x8a, 0xec, 0x22, 0x8d, 0x12, 0x28, 0xaf, 0xf1, 0x0b, 0xbf, 0x46, 0xe6, 0x65, 0x51, 0xd4, 0x2c, 0x42, 0xda, 0xd6, 0x3a, 0x0d, 0x12, 0xe9, 0x20, 0x04, 0xc4, 0x93, 0xa9, 0xb9, 0xa6, 0xba, 0xa2, 0xbc, 0x24, 0x80, 0x13, 0x67, 0xe0, 0xb4, 0x0e, 0x8a, 0x9d, 0x8e, 0x69, 0x6c, 0x3b, 0x1d, 0xaa, 0x3d, 0xe7, 0x88, 0x34, 0x3d, 0x17, 0xcc, 0x1d, 0xb9, 0x96, 0x14, 0xfb, 0x6e, 0x9e, 0xe1, 0xdd, 0x4b, 0x87, 0x4e, 0xef, 0xdd, 0x2b, 0x31, 0xb9, 0x03, 0xf4, 0xf2, 0x1d, 0x68, 0x08, 0xdf, 0x97, 0xc0, 0x43, 0x98, 0x39, 0x79, 0x88, 0x7b, 0x98, 0x58, 0x82, 0xd4, 0xe0, 0xf7, 0x6a, 0xa8, 0x29, 0xcf, 0x1f, 0xa9, 0xe6, 0x1d, 0xca, 0x08, 0x76, 0x79, 0xd5, 0xae, 0xab, 0xb2, 0x65, 0x5e, 0xea, 0x25, 0x73, 0x26, 0xe5, 0x2f, 0x96, 0xc5, 0xcb, 0xd2, 0xba, 0x12, 0xc8, 0x69, 0x9d, 0x10, 0x07, 0x13, 0x53, 0xa2, 0x69, 0x6a, 0xb4, 0x9a, 0x4d, 0x3a, 0x72, 0x93, 0xcf, 0xb3, 0x60, 0xb9, 0x88, 0xf6, 0xcd, 0xda, 0x7e, 0xba, 0x4d, 0xe6, 0x79, 0xb8, 0x48, 0x80, 0xf2, 0xa5, 0x5b, 0xfc, 0x0c, 0x88, 0xe4, 0x0a, 0x8e, 0xe7, 0x85, 0x45, 0xd8, 0x93, 0x1f, 0xe7, 0x1c, 0x29, 0x20, 0xe6, 0x4d, 0xb6, 0x78, 0x88, 0x18, 0x03, 0x90, 0x0b, 0xf4, 0x2f, 0xc1, 0x20, 0xee, 0xad, 0xb3, 0x33, 0x44, 0x31, 0x6a, 0x3b, 0x5b, 0xf3, 0x55, 0x59, 0x38, 0xac, 0x8c, 0x79, 0xa4, 0x61, 0x67, 0x64, 0xf6, 0xe7, 0x19, 0x99, 0x85, 0x23, 0x19, 0xb8, 0xf0, 0x0e, 0x91, 0x85, 0x1d, 0x54, 0xc6, 0xb9, 0xe7, 0x55, 0xf8, 0xef, 0x88, 0x2c, 0xef, 0xa0, 0x32, 0x2e, 0xc3, 0xd1, 0x58, 0x17, 0x0f, 0x50, 0x38, 0x95, 0x71, 0xee, 0xe7, 0x0a, 0xbe, 0xf8, 0x75, 0xb2, 0xaf, 0xa4, 0xf8, 0x47, 0x33, 0xe5, 0x88, 0x57, 0x10, 0x3b, 0x43, 0x5a, 0xfe, 0xd3, 0x59, 0xf0, 0x1b, 0x88, 0x5d, 0x22, 0xc5, 0x7f, 0x2c, 0xab, 0xfc, 0x5b, 0x88, 0x8c, 0xd3, 0xf2, 0x8f, 0x67, 0x95, 0xbf, 0x2d, 0xab, 0xfc, 0xc7, 0xb3, 0xca, 0xc9, 0xae, 0xf7, 0x98, 0x52, 0x8e, 0xf4, 0x43, 0x71, 0x0f, 0x6d, 0x17, 0x91, 0xc9, 0xe3, 0xdc, 0xcf, 0x3e, 0x2d, 0x86, 0xb8, 0x7d, 0x3f, 0x57, 0x64, 0x32, 0x5b, 0xc6, 0x11, 0x6e, 0x1b, 0xc5, 0x25, 0xbe, 0x6b, 0x3f, 0x1b, 0x4b, 0xc6, 0xc5, 0xa7, 0x89, 0x8c, 0xd3, 0xf2, 0x8e, 0xf0, 0x80, 0xca, 0x78, 0xa6, 0xbc, 0x6c, 0x19, 0x17, 0x97, 0x12, 0x19, 0x5f, 0x04, 0x9e, 0x48, 0xeb, 0xbb, 0xa1, 0x60, 0xea, 0x88, 0x33, 0x19, 0x21, 0x6f, 0x32, 0x43, 0x24, 0x9d, 0xd0, 0x08, 0x86, 0x2c, 0xaa, 0xb8, 0x5a, 0x33, 0xe2, 0x6a, 0xd0, 0x22, 0x25, 0xd0, 0xd4, 0x5f, 0x00, 0x4d, 0xa6, 0x55, 0xdd, 0x3a, 0x88, 0x05, 0x5d, 0xaf, 0x61, 0x64, 0x49, 0x6f, 0xfb, 0x12, 0xef, 0x12, 0x61, 0xc7, 0x05, 0x68, 0x16, 0xe9, 0xa1, 0x2c, 0xee, 0x09, 0xb4, 0xf4, 0x2f, 0x3c, 0xb7, 0x7f, 0xc1, 0xbc, 0x73, 0x66, 0xcf, 0xea, 0x9d, 0x3a, 0x65, 0x62, 0x67, 0x43, 0xdd, 0xd8, 0x82, 0x6f, 0xfb, 0xb2, 0x82, 0x7f, 0xd6, 0xb1, 0xae, 0x0e, 0x79, 0x61, 0x65, 0xde, 0x9c, 0xf0, 0x1f, 0x77, 0x9c, 0xbe, 0xe5, 0x16, 0xc9, 0xd9, 0x5c, 0x49, 0x44, 0x40, 0x6b, 0x40, 0x22, 0xb0, 0x03, 0x89, 0x80, 0xd4, 0x30, 0xae, 0x50, 0xc0, 0x9f, 0x26, 0x59, 0x97, 0x75, 0x90, 0x39, 0x7a, 0xc7, 0x97, 0x98, 0x2c, 0xc6, 0x97, 0x1e, 0x58, 0xad, 0xca, 0x10, 0x9d, 0x43, 0x50, 0xff, 0xb5, 0x81, 0x69, 0x60, 0x6f, 0x5a, 0xdf, 0x00, 0x39, 0x43, 0xc2, 0xc9, 0x64, 0x26, 0x91, 0x84, 0x11, 0xea, 0x0d, 0xfa, 0x4d, 0x66, 0x75, 0x72, 0x20, 0xbe, 0x83, 0x86, 0x7e, 0x13, 0x34, 0x18, 0xb0, 0x9f, 0x2c, 0x9e, 0x48, 0xb4, 0xea, 0xf5, 0x7d, 0xe5, 0x19, 0xb1, 0xc9, 0x64, 0x82, 0x5f, 0x11, 0x16, 0xe1, 0x49, 0x9a, 0x4c, 0x27, 0xde, 0x69, 0x53, 0xa7, 0x4c, 0x46, 0xaa, 0x71, 0x7b, 0x5b, 0x4b, 0xaa, 0xa6, 0x32, 0x19, 0x52, 0x26, 0x96, 0x82, 0xb3, 0x4d, 0x2c, 0x5f, 0xbc, 0x03, 0xc4, 0xa5, 0x7f, 0x02, 0xc3, 0xc7, 0x9d, 0x8c, 0xce, 0xce, 0x5e, 0x55, 0x8e, 0x0f, 0x2b, 0xf2, 0xca, 0xb0, 0x59, 0x72, 0x3c, 0x3d, 0x33, 0xdf, 0xc0, 0x11, 0x19, 0xbe, 0x15, 0xed, 0xcb, 0xe8, 0x39, 0xd6, 0x75, 0xf2, 0x7d, 0xef, 0xdd, 0x63, 0xc2, 0x8f, 0xe4, 0xc0, 0x0f, 0xa9, 0xf0, 0xa3, 0x39, 0xf0, 0xed, 0x2a, 0xfc, 0x44, 0x0e, 0xbc, 0x4b, 0x85, 0x3f, 0x36, 0x4e, 0x39, 0x8f, 0xe7, 0xc0, 0xdf, 0x50, 0xe1, 0xdf, 0x95, 0xe3, 0x03, 0x60, 0xf8, 0x7f, 0x11, 0xfa, 0xaf, 0x93, 0xef, 0x7b, 0x73, 0xca, 0x07, 0xa3, 0xca, 0x47, 0xfb, 0xca, 0xe7, 0xd9, 0x8f, 0x95, 0x72, 0xe0, 0xeb, 0x39, 0xf8, 0xa7, 0x55, 0xf8, 0x73, 0x59, 0xe5, 0x67, 0xd3, 0x73, 0x2c, 0x1b, 0x9f, 0xdc, 0x7d, 0x5d, 0x27, 0xdf, 0x7d, 0x65, 0xc3, 0xdf, 0x92, 0xe1, 0x68, 0xbe, 0x02, 0xcf, 0xd0, 0xf9, 0x0a, 0x3f, 0xcf, 0x9e, 0xaf, 0x54, 0xdc, 0x17, 0xd4, 0x32, 0x16, 0x8c, 0x59, 0x86, 0xc8, 0xf5, 0x83, 0x7b, 0x65, 0xda, 0xbf, 0x92, 0xa1, 0x9d, 0x94, 0xf9, 0xe0, 0xa7, 0x90, 0xe6, 0x66, 0xf8, 0x14, 0x2a, 0x65, 0x4a, 0x3f, 0xcc, 0xc1, 0x39, 0x0e, 0xee, 0x1e, 0x35, 0xa7, 0x6e, 0x95, 0x0e, 0x66, 0xf3, 0x40, 0x70, 0xc8, 0xf1, 0x79, 0x68, 0xbd, 0xaf, 0x2a, 0x70, 0x7e, 0x47, 0x0e, 0x3d, 0x8f, 0xa9, 0xf0, 0xdd, 0xa4, 0x2d, 0x10, 0x95, 0xb3, 0x9b, 0xdf, 0xcc, 0x7e, 0x0c, 0x2a, 0xc0, 0xfe, 0x47, 0x03, 0x46, 0x86, 0x63, 0x95, 0xe8, 0xd9, 0xd8, 0x68, 0x8f, 0xcd, 0x36, 0x23, 0xa3, 0x59, 0x17, 0xf3, 0x36, 0xc5, 0x25, 0x14, 0x0b, 0x50, 0x6f, 0x1a, 0xf9, 0x4c, 0x64, 0x5d, 0x2e, 0x26, 0x36, 0xc3, 0x61, 0x44, 0x46, 0x23, 0x0e, 0x8d, 0x8d, 0x0c, 0x32, 0xb8, 0xe4, 0x84, 0x2a, 0x84, 0xfd, 0x48, 0xa2, 0x11, 0x7a, 0x42, 0x95, 0x89, 0x29, 0xab, 0x86, 0x94, 0xb5, 0xa4, 0x88, 0xb0, 0xd6, 0x37, 0x10, 0xe3, 0x3a, 0xd5, 0x4b, 0x81, 0xdf, 0x2c, 0x7b, 0x25, 0x0c, 0x96, 0x1a, 0x4c, 0xc1, 0x69, 0xb3, 0x17, 0xd5, 0x29, 0x2e, 0x36, 0xd7, 0x34, 0x9d, 0x52, 0xdd, 0x14, 0xa4, 0x87, 0x64, 0xa7, 0x84, 0x8c, 0xd9, 0x04, 0x73, 0xfd, 0xf0, 0xa5, 0x17, 0x5c, 0x3a, 0xb5, 0x9d, 0x79, 0xeb, 0xf3, 0x43, 0xd4, 0x4b, 0x41, 0x1d, 0x33, 0x87, 0x15, 0x7e, 0x31, 0xac, 0xcc, 0xc7, 0xcd, 0xb9, 0xb2, 0x03, 0x47, 0x64, 0x38, 0xce, 0x3d, 0xfd, 0x19, 0xb1, 0x89, 0xb8, 0x81, 0xc6, 0x45, 0xbf, 0x88, 0xea, 0x14, 0x36, 0x34, 0x56, 0x3f, 0x23, 0xfd, 0x74, 0x03, 0xdd, 0x27, 0x5d, 0x4c, 0xe7, 0x49, 0xab, 0x74, 0x27, 0xfb, 0x29, 0xe2, 0x7b, 0x0c, 0x5c, 0x21, 0xdb, 0xa2, 0x16, 0xa3, 0x39, 0x4e, 0x47, 0x1c, 0x73, 0x7c, 0xea, 0x0f, 0x56, 0xb5, 0x45, 0x2d, 0x06, 0x38, 0x84, 0x33, 0xe0, 0x86, 0xf0, 0xe9, 0x21, 0xd9, 0xfc, 0x0c, 0x66, 0x32, 0x97, 0x0f, 0x41, 0x12, 0x29, 0x4b, 0xc6, 0x00, 0xa8, 0x17, 0x39, 0x86, 0xcd, 0xc6, 0x54, 0xb2, 0x97, 0x0f, 0x61, 0xbb, 0x02, 0x5d, 0x39, 0x39, 0x04, 0x24, 0x56, 0xbd, 0x91, 0x31, 0x0f, 0xfc, 0xc8, 0xa9, 0xa0, 0x8d, 0x06, 0xdc, 0x27, 0x13, 0x1f, 0x73, 0xc7, 0xb1, 0xfc, 0x93, 0x81, 0xf7, 0xa4, 0xc1, 0xaf, 0xb9, 0x8a, 0xf8, 0xf7, 0x8d, 0x36, 0x8d, 0xc6, 0x6e, 0xfa, 0x48, 0x28, 0x72, 0xdc, 0xbe, 0x3f, 0xff, 0xac, 0xcf, 0x69, 0x19, 0xbe, 0x39, 0x36, 0x23, 0x1a, 0xed, 0x29, 0x65, 0x86, 0x2c, 0x4e, 0xc2, 0x0f, 0x3f, 0x62, 0x82, 0x89, 0xf0, 0x6f, 0x27, 0x9d, 0x93, 0xe4, 0x7c, 0xba, 0xf9, 0xf0, 0x23, 0x39, 0xf0, 0x43, 0x2a, 0xfc, 0x68, 0x0e, 0x7c, 0xbb, 0x0a, 0x3f, 0x91, 0x03, 0x7f, 0x4a, 0x85, 0x3f, 0xad, 0xc0, 0xa5, 0x7d, 0x82, 0x89, 0xfd, 0x4c, 0x81, 0xc3, 0xd7, 0xe1, 0xb5, 0x59, 0xf8, 0x6f, 0xa8, 0xf8, 0xdf, 0x85, 0x3b, 0xd5, 0x5c, 0xe2, 0x2f, 0xa1, 0xfe, 0x29, 0xc4, 0x39, 0x3b, 0x0a, 0xed, 0xa6, 0x4c, 0xce, 0x0e, 0x5f, 0x26, 0x67, 0x47, 0x59, 0x26, 0x67, 0x07, 0x8b, 0x83, 0x64, 0x67, 0x92, 0x76, 0xd8, 0x30, 0xe7, 0xea, 0xd1, 0xce, 0x93, 0x7d, 0xe9, 0x09, 0x97, 0x4f, 0xf3, 0x21, 0xaf, 0xd3, 0xbc, 0xaa, 0xf3, 0x3a, 0x8e, 0xc2, 0x15, 0xd2, 0x91, 0x27, 0x5c, 0x0e, 0xcd, 0x1f, 0x78, 0x83, 0xee, 0x59, 0x6b, 0xd1, 0x71, 0xe9, 0x88, 0xc7, 0x04, 0xb7, 0x9b, 0x0b, 0x0d, 0xd2, 0x23, 0x26, 0x0f, 0xa3, 0x87, 0xef, 0x98, 0x4c, 0xd2, 0x4d, 0x66, 0x7f, 0x01, 0x4c, 0x05, 0xa4, 0x42, 0x65, 0x5c, 0xbd, 0x44, 0xda, 0xb3, 0x2b, 0x27, 0x96, 0x21, 0x85, 0x6f, 0x57, 0xe1, 0x27, 0xd6, 0xa8, 0xe3, 0x8a, 0xab, 0x42, 0x74, 0x57, 0x83, 0xeb, 0xe8, 0xc0, 0x31, 0x56, 0x95, 0x96, 0x78, 0x38, 0x91, 0x57, 0xac, 0xa8, 0xb2, 0x7e, 0x33, 0xea, 0xe8, 0x2a, 0xa2, 0x63, 0x45, 0x43, 0x14, 0xa6, 0x31, 0x4e, 0xc0, 0xb2, 0xf3, 0x4c, 0xe5, 0x23, 0x8e, 0xca, 0x33, 0x55, 0x0d, 0xaa, 0x11, 0x6b, 0x42, 0x68, 0x88, 0x69, 0xc7, 0x3b, 0x97, 0x1a, 0x9f, 0x61, 0x5a, 0xc8, 0x55, 0xe5, 0x0f, 0xb5, 0x55, 0x67, 0xe6, 0x20, 0x04, 0x79, 0x03, 0xef, 0x0c, 0x2c, 0x95, 0xae, 0x42, 0xbc, 0xb3, 0x48, 0x87, 0x39, 0x48, 0xc6, 0xda, 0x8d, 0xf2, 0xba, 0x78, 0x0b, 0xe1, 0x69, 0x3e, 0xfc, 0x48, 0x0e, 0xfc, 0x90, 0x0a, 0x3f, 0x9a, 0x03, 0xdf, 0xae, 0xc2, 0x4f, 0x60, 0x38, 0xea, 0x03, 0x02, 0x47, 0x7d, 0xe0, 0x04, 0x25, 0xe9, 0x62, 0xb3, 0xc0, 0x60, 0x1f, 0x09, 0x16, 0x89, 0x2a, 0xf5, 0x49, 0xf3, 0x13, 0xff, 0x19, 0x27, 0x70, 0x94, 0x84, 0x38, 0xc1, 0x99, 0xd0, 0x92, 0x54, 0x2f, 0xb6, 0xec, 0x1c, 0x17, 0x88, 0x17, 0x29, 0x0e, 0x0e, 0xff, 0x48, 0x5b, 0xe4, 0xbf, 0xe5, 0x3b, 0xd2, 0xd2, 0x07, 0x3d, 0x85, 0xf8, 0x3a, 0xc2, 0xcc, 0x8b, 0x05, 0xda, 0x4f, 0xc4, 0x22, 0x1b, 0xfb, 0xf1, 0xa7, 0xaf, 0x3a, 0xdd, 0xf0, 0x00, 0xfb, 0x6d, 0xb7, 0x79, 0xf8, 0x1f, 0x5c, 0x95, 0x2e, 0x77, 0xa5, 0x8b, 0x59, 0x2e, 0xcb, 0x57, 0x1e, 0xad, 0xb0, 0x5a, 0xa5, 0x75, 0x1f, 0xa6, 0x49, 0x85, 0xbf, 0x0e, 0x1f, 0xc8, 0xc2, 0xdf, 0xab, 0xb6, 0xe1, 0x78, 0x4e, 0xdb, 0x4e, 0xab, 0xf8, 0xcf, 0x81, 0x5b, 0xb3, 0xe0, 0x5d, 0x2a, 0xfe, 0x63, 0x39, 0xf8, 0x4f, 0xa9, 0xf0, 0xa7, 0x73, 0xe0, 0x6f, 0xa8, 0xf0, 0xef, 0x7e, 0xa1, 0x72, 0xf2, 0x78, 0x3a, 0x46, 0xdf, 0x7c, 0x5f, 0x86, 0x37, 0x21, 0x39, 0x98, 0x49, 0x74, 0x87, 0xdd, 0x90, 0xdc, 0x0b, 0xcc, 0xa0, 0xf2, 0xd1, 0x88, 0xe0, 0x33, 0x08, 0x1f, 0x76, 0xd3, 0xf3, 0xff, 0x9e, 0x6c, 0xf8, 0x53, 0x2a, 0xfc, 0xe9, 0x9e, 0xb1, 0xf1, 0x8f, 0x2a, 0x70, 0x34, 0x7f, 0xcf, 0x20, 0x7c, 0xdb, 0x4d, 0xe7, 0xef, 0x7a, 0x05, 0xfe, 0x61, 0x36, 0x9c, 0xff, 0x75, 0x3d, 0x95, 0xbf, 0x46, 0x34, 0xaf, 0x3f, 0x84, 0xfa, 0xbe, 0x44, 0xf1, 0xee, 0xd3, 0x39, 0x20, 0x2b, 0x68, 0x64, 0xe1, 0x53, 0x7e, 0x64, 0x24, 0x2f, 0x00, 0xe4, 0xbc, 0x6d, 0xc4, 0x4f, 0x80, 0xe4, 0x0e, 0xc9, 0x97, 0xbd, 0x98, 0x6a, 0x8f, 0x38, 0x06, 0x6a, 0x8e, 0xf4, 0xe9, 0x1c, 0x16, 0x07, 0x9a, 0xda, 0xc3, 0x78, 0x66, 0xe7, 0xc7, 0x70, 0x51, 0x56, 0x32, 0x8f, 0xe0, 0x8b, 0x54, 0x18, 0x64, 0x1f, 0x52, 0x04, 0x4d, 0xf4, 0xea, 0x4d, 0x15, 0x48, 0xd0, 0x86, 0xab, 0x4d, 0x46, 0xc6, 0x3d, 0xfc, 0x36, 0xf3, 0x91, 0xd9, 0x30, 0xfc, 0x3b, 0x66, 0x7f, 0x34, 0x47, 0xac, 0x66, 0x9b, 0x62, 0x16, 0xe6, 0x5f, 0x6e, 0x9f, 0xed, 0x28, 0x35, 0x0d, 0xb7, 0xe3, 0xbd, 0x5a, 0x29, 0x7b, 0x23, 0xfc, 0x0d, 0x7f, 0x1f, 0xd0, 0x83, 0x1a, 0x7c, 0x6b, 0xa2, 0x47, 0x44, 0x7b, 0xc8, 0xed, 0xe5, 0x78, 0xf9, 0x34, 0xce, 0x27, 0xce, 0xe0, 0x64, 0x25, 0x5a, 0xc5, 0xf6, 0x84, 0x42, 0xce, 0x58, 0x24, 0x1c, 0x2d, 0x20, 0x91, 0x72, 0x65, 0x0b, 0x28, 0x44, 0x5f, 0x7d, 0x5e, 0x16, 0x8d, 0x68, 0x7e, 0x12, 0x0d, 0xb8, 0x28, 0x35, 0xa9, 0xd4, 0x12, 0xf2, 0x87, 0x52, 0x5e, 0xe9, 0xb5, 0xc5, 0x57, 0x57, 0x4c, 0x4f, 0xf9, 0x3a, 0xec, 0x76, 0x51, 0x6f, 0x8f, 0xc4, 0x92, 0x85, 0x93, 0xe7, 0xf8, 0xa7, 0x5e, 0xd2, 0x1f, 0x8f, 0xfa, 0x63, 0x7e, 0x4f, 0x81, 0xc9, 0x2e, 0x72, 0xe7, 0x33, 0x46, 0x9b, 0xc7, 0xec, 0xf7, 0xb8, 0xbd, 0xb0, 0x61, 0x76, 0x51, 0x7d, 0x77, 0x52, 0x7a, 0xdc, 0x60, 0x0d, 0x58, 0xbc, 0x56, 0xed, 0xb4, 0xa9, 0x85, 0xcd, 0x95, 0x01, 0xc6, 0x53, 0x95, 0x34, 0x16, 0x18, 0xcd, 0x7a, 0x6a, 0xdf, 0x50, 0x3a, 0xe2, 0x86, 0xbf, 0x81, 0x29, 0xd2, 0xa6, 0xdb, 0xd2, 0xba, 0x4c, 0x9b, 0xa8, 0x29, 0x48, 0x48, 0x6d, 0x1a, 0x03, 0x04, 0x91, 0x11, 0x06, 0x00, 0x44, 0x4d, 0x82, 0xec, 0x52, 0xd4, 0x2d, 0xa4, 0x1f, 0x36, 0xa8, 0xce, 0x4e, 0x67, 0x45, 0x5d, 0x87, 0x3b, 0xf7, 0x8b, 0x14, 0xd8, 0x4f, 0xb2, 0x8e, 0x64, 0x98, 0xc5, 0xff, 0xa5, 0x98, 0xa5, 0x19, 0xcd, 0x2b, 0xbd, 0x35, 0xa8, 0xf2, 0xaa, 0x98, 0xf1, 0x12, 0x5e, 0x99, 0x64, 0x5e, 0xf5, 0xb1, 0x37, 0x32, 0xf5, 0xc2, 0xb5, 0xc0, 0x00, 0xc2, 0x38, 0xe7, 0xa9, 0x11, 0x29, 0x2f, 0x06, 0x6c, 0x3b, 0xe3, 0x87, 0x2c, 0x60, 0xbb, 0x00, 0xcf, 0xb0, 0xfc, 0x5c, 0xe5, 0xd6, 0x7f, 0x80, 0x74, 0x3d, 0x69, 0x11, 0xea, 0x7a, 0x4b, 0xa4, 0x34, 0x12, 0x2e, 0xc1, 0xbe, 0x1f, 0xb6, 0x31, 0x03, 0x74, 0x53, 0xda, 0x49, 0xa4, 0x3e, 0x4b, 0xea, 0x45, 0x7b, 0x85, 0x37, 0xd5, 0x5e, 0x57, 0x11, 0xa8, 0x71, 0x17, 0xf5, 0xa4, 0xe2, 0x53, 0x52, 0x85, 0x81, 0x96, 0xd9, 0x35, 0x55, 0xed, 0x5e, 0x96, 0x2d, 0x30, 0x2d, 0xdc, 0xc5, 0x15, 0xb9, 0x2c, 0x33, 0x16, 0xcc, 0x9b, 0xe2, 0x2b, 0x5c, 0x5e, 0x1c, 0x0e, 0x4d, 0x5a, 0x91, 0x6e, 0x1e, 0x9c, 0x5e, 0x16, 0x29, 0x0a, 0x98, 0x0d, 0xcc, 0x1f, 0xa8, 0x4c, 0x56, 0xa1, 0x71, 0xfa, 0x0e, 0x7f, 0x2f, 0x28, 0x03, 0x6b, 0xd3, 0xba, 0x32, 0x08, 0x79, 0x17, 0xe4, 0x88, 0xff, 0x73, 0x98, 0xb8, 0x86, 0x2b, 0x57, 0x7c, 0xf2, 0xdd, 0xde, 0x40, 0x37, 0xb1, 0x22, 0x57, 0x2e, 0xf8, 0x56, 0x9d, 0x29, 0x1a, 0xf6, 0x40, 0x36, 0x1a, 0x8e, 0x86, 0x1d, 0x0b, 0x85, 0x43, 0xa4, 0x5d, 0xea, 0x35, 0xa0, 0x90, 0x73, 0xf6, 0x9c, 0x13, 0x10, 0x00, 0x9e, 0x16, 0xf5, 0x7a, 0x9d, 0xcd, 0x1d, 0xf4, 0x74, 0xb4, 0xa6, 0x1b, 0xfd, 0x21, 0xb7, 0xd5, 0xa8, 0x31, 0x72, 0xa5, 0xce, 0x54, 0x7d, 0x83, 0xaf, 0x6a, 0x5e, 0x67, 0x24, 0x98, 0x5e, 0xd6, 0xde, 0x7a, 0x6e, 0x88, 0x8f, 0x6a, 0xb4, 0x7a, 0x93, 0x7e, 0x6e, 0xcf, 0xcc, 0x39, 0x68, 0x8b, 0x28, 0x88, 0x29, 0x6f, 0x89, 0x5d, 0x13, 0x9e, 0x3c, 0x98, 0x6e, 0x5a, 0x31, 0xad, 0x2c, 0x1a, 0xa1, 0x7d, 0x51, 0x83, 0xfa, 0xc2, 0x4d, 0xfa, 0x22, 0x84, 0x73, 0xe4, 0x96, 0x40, 0xc0, 0x19, 0x70, 0xec, 0x09, 0x2b, 0xce, 0x29, 0xd9, 0x85, 0x56, 0x6d, 0x8e, 0x41, 0x9d, 0x21, 0x47, 0xbf, 0x3d, 0x3f, 0xa3, 0x3a, 0xae, 0x62, 0x7a, 0x8c, 0x46, 0x63, 0xc8, 0x18, 0x2a, 0x0d, 0x87, 0xc3, 0x25, 0xa2, 0x2e, 0x13, 0xef, 0xd6, 0x39, 0x6e, 0xb8, 0x5b, 0x78, 0x93, 0x3d, 0xe9, 0x4d, 0x75, 0xd4, 0xd7, 0x04, 0xaa, 0x1c, 0x37, 0xce, 0x2d, 0x14, 0xc5, 0x40, 0x5f, 0x23, 0xe9, 0x96, 0xe6, 0xd9, 0xa9, 0x78, 0x8b, 0x8b, 0xb7, 0x3b, 0x69, 0x87, 0x78, 0x99, 0xb9, 0xc3, 0x66, 0x4f, 0x75, 0xb8, 0x22, 0x3c, 0x69, 0xb0, 0x03, 0xf7, 0x8a, 0xd7, 0x01, 0x8d, 0x88, 0x56, 0xd4, 0x1f, 0x8c, 0x05, 0xf5, 0x87, 0x06, 0x24, 0xc0, 0x96, 0x13, 0x76, 0xd4, 0x19, 0x50, 0xee, 0x8c, 0xb0, 0x08, 0xa1, 0x06, 0x0a, 0x2c, 0x14, 0x06, 0x94, 0xf8, 0xda, 0x63, 0x75, 0x47, 0x0c, 0xb0, 0x02, 0x23, 0xb0, 0xcc, 0xd0, 0x98, 0xe8, 0xd9, 0x3d, 0xe2, 0xd6, 0x6a, 0xd1, 0x8e, 0x2d, 0xa1, 0x8d, 0x63, 0x4f, 0x3c, 0x6b, 0x81, 0x51, 0x4f, 0xe2, 0x74, 0x6a, 0x33, 0x71, 0x3a, 0xf1, 0x2d, 0xcc, 0x58, 0xf1, 0xaa, 0x69, 0x38, 0x3f, 0xf8, 0x15, 0x83, 0x27, 0xe6, 0xab, 0x69, 0x42, 0xfb, 0x97, 0xc9, 0x4d, 0xfe, 0x78, 0xc0, 0x6b, 0xb0, 0x8a, 0xa5, 0xce, 0xfa, 0xa6, 0x66, 0x5f, 0xfd, 0xd2, 0x29, 0xa5, 0xa1, 0xce, 0xc5, 0x2d, 0x35, 0x73, 0xda, 0x4a, 0x0c, 0x36, 0xee, 0xdf, 0x1c, 0x31, 0xbf, 0xb5, 0x31, 0x35, 0x67, 0x5a, 0xa7, 0xd1, 0x62, 0x34, 0xd2, 0x1e, 0x9a, 0xb2, 0x76, 0x72, 0xed, 0x92, 0x29, 0x65, 0x81, 0xb6, 0x05, 0x4d, 0x01, 0x07, 0xee, 0xa3, 0xaa, 0x11, 0x0b, 0x63, 0x81, 0x71, 0xd2, 0xee, 0xc9, 0xe9, 0x09, 0xb4, 0xe1, 0x4a, 0x53, 0xd4, 0xb9, 0x92, 0x01, 0x1c, 0xc3, 0x2d, 0x15, 0x89, 0xeb, 0x88, 0x06, 0x92, 0xdc, 0xcc, 0xe3, 0x36, 0x22, 0xf2, 0x25, 0x1a, 0xd1, 0xa8, 0x55, 0x1b, 0x61, 0xd1, 0x8c, 0xd5, 0x88, 0x85, 0x8e, 0xa8, 0xdf, 0xda, 0x90, 0x9a, 0x4b, 0xda, 0x60, 0x48, 0xf9, 0x4a, 0x6c, 0xa8, 0x0d, 0x6b, 0x26, 0xd7, 0x29, 0x6d, 0x70, 0x62, 0x59, 0x0a, 0xb3, 0xfb, 0xe0, 0x15, 0xc2, 0x95, 0xc0, 0x03, 0xaa, 0xd3, 0x15, 0x1e, 0x44, 0x2c, 0x0f, 0xbb, 0x8c, 0x68, 0x8c, 0x4d, 0xc7, 0x77, 0x34, 0x17, 0x66, 0x1a, 0xc1, 0xc0, 0x7e, 0xec, 0x85, 0x3f, 0xc0, 0xf4, 0xe0, 0x48, 0x67, 0xb6, 0x8c, 0x4c, 0x08, 0x7e, 0x48, 0x4d, 0x48, 0xb2, 0x62, 0x9e, 0xb9, 0xe0, 0xa5, 0xd0, 0xe5, 0x8a, 0x55, 0x5c, 0x73, 0xe5, 0xae, 0x32, 0xb3, 0xbd, 0xe7, 0xbc, 0x25, 0xa1, 0xa0, 0x90, 0x34, 0xeb, 0x4a, 0xeb, 0x0b, 0x07, 0xa5, 0xc5, 0xf0, 0x1e, 0x91, 0x2b, 0x30, 0x37, 0x07, 0xcf, 0x5d, 0xa3, 0x05, 0x2c, 0x88, 0x30, 0x2f, 0x33, 0x2e, 0xfe, 0x1b, 0x68, 0x7e, 0xf6, 0x80, 0x99, 0xb0, 0x28, 0x6d, 0x2e, 0x42, 0x4b, 0xeb, 0x24, 0xc8, 0x81, 0x76, 0x0b, 0xc3, 0xf0, 0xac, 0x3c, 0x4d, 0xc7, 0xb0, 0x4f, 0x2d, 0xde, 0x0c, 0x0d, 0xa0, 0x57, 0x04, 0x56, 0x58, 0x8a, 0x46, 0xba, 0xb8, 0x50, 0x87, 0xba, 0x51, 0x1c, 0xc0, 0x4b, 0x26, 0xb5, 0xdd, 0x65, 0x94, 0xa9, 0xfa, 0x0c, 0xe8, 0xe7, 0xe7, 0xa1, 0x1b, 0xbe, 0x5c, 0xe9, 0xb6, 0x2f, 0x53, 0x7a, 0x3a, 0x91, 0x8f, 0xa9, 0x83, 0x1a, 0x0d, 0x49, 0x72, 0x4a, 0xae, 0x86, 0x73, 0xb0, 0x89, 0x8b, 0xa9, 0xdf, 0xeb, 0x85, 0xc0, 0x3b, 0xd3, 0x3b, 0x73, 0x46, 0x77, 0x43, 0x5d, 0x45, 0x79, 0x24, 0x8c, 0x83, 0xa2, 0x8a, 0x3c, 0xd0, 0x43, 0x3d, 0x4d, 0xd9, 0x9d, 0x31, 0x0e, 0xc7, 0x01, 0x2b, 0x49, 0xfa, 0x1e, 0x2c, 0xd7, 0x36, 0x87, 0xdd, 0x0c, 0x9d, 0xca, 0x3c, 0x8b, 0xe3, 0x9a, 0x70, 0x19, 0xa3, 0xf2, 0x7a, 0x92, 0xcb, 0xa7, 0xbe, 0x41, 0x0d, 0x4f, 0x0f, 0x75, 0x89, 0xb2, 0xb2, 0x04, 0xfa, 0xff, 0x5b, 0xd1, 0xe6, 0xe9, 0x25, 0x16, 0x47, 0xcd, 0xc4, 0x8e, 0xb6, 0x9d, 0xfe, 0xb8, 0xa6, 0xb8, 0xb1, 0xbb, 0xdc, 0xe4, 0x35, 0x3a, 0xaa, 0xbb, 0xe7, 0x75, 0x57, 0x97, 0x74, 0x2e, 0x6d, 0x6b, 0x5d, 0xd0, 0x5c, 0x58, 0x16, 0xb1, 0x39, 0x83, 0xa1, 0xd2, 0x8a, 0x40, 0xe3, 0x8c, 0xe4, 0xc4, 0x1a, 0xee, 0x9b, 0x65, 0x25, 0xa1, 0x32, 0xb4, 0x19, 0x88, 0x4b, 0x3f, 0xb7, 0x5b, 0xd0, 0xa6, 0x5f, 0xef, 0x2d, 0xf0, 0x46, 0xcb, 0x98, 0x68, 0x9d, 0x4b, 0xd4, 0xba, 0x7d, 0xb1, 0xf6, 0x72, 0x37, 0xcb, 0x18, 0xcd, 0x36, 0x03, 0xcb, 0xbb, 0x6b, 0x6b, 0x42, 0x13, 0x6a, 0x03, 0xae, 0x68, 0xb5, 0x2f, 0x54, 0x65, 0xd2, 0x47, 0x0a, 0x5d, 0xb5, 0x16, 0xe7, 0xd4, 0xca, 0x58, 0x47, 0xb9, 0x0b, 0x7b, 0xb4, 0xfd, 0xff, 0x7d, 0xff, 0x7f, 0xb8, 0xef, 0x21, 0xe8, 0x67, 0xf7, 0x31, 0x51, 0xb4, 0xbe, 0x39, 0x40, 0x29, 0xde, 0x8c, 0x2a, 0x31, 0x4c, 0x00, 0xc7, 0x42, 0xa4, 0x30, 0x2d, 0x56, 0xd4, 0x60, 0x34, 0xdf, 0x90, 0x28, 0x24, 0x3c, 0x5a, 0xc8, 0x6c, 0x63, 0x44, 0x21, 0x49, 0xfd, 0xc6, 0x3f, 0xa5, 0x2a, 0xd1, 0x99, 0x74, 0xed, 0x2a, 0x2d, 0xf5, 0xd7, 0xb9, 0x76, 0xed, 0xe2, 0xd7, 0x7a, 0x83, 0x45, 0xb5, 0xd3, 0x13, 0xd2, 0x09, 0xd8, 0x1d, 0xaf, 0xf4, 0x38, 0xa5, 0x1b, 0xb1, 0xd6, 0xc0, 0x80, 0x4b, 0xd1, 0x3c, 0x17, 0x11, 0x76, 0xa0, 0xbe, 0xb0, 0x81, 0x59, 0x27, 0x38, 0x7c, 0xb5, 0x8f, 0xc6, 0x57, 0x1c, 0x87, 0x7b, 0x42, 0x30, 0xac, 0x04, 0x0c, 0x90, 0x0c, 0x7f, 0x64, 0x92, 0x03, 0xc4, 0xeb, 0x52, 0x06, 0x73, 0xf8, 0x78, 0x00, 0xce, 0xc5, 0x0f, 0x70, 0xfc, 0x38, 0x08, 0x66, 0xf4, 0xa7, 0x4d, 0x00, 0x58, 0x0b, 0x94, 0x28, 0x24, 0x72, 0xd4, 0x84, 0x86, 0x0c, 0xd3, 0xd1, 0xaa, 0x0a, 0xad, 0xbb, 0xee, 0x32, 0xb9, 0x03, 0x56, 0x4b, 0xb1, 0xcb, 0x58, 0x1d, 0x0a, 0xd7, 0xf2, 0xd7, 0x4a, 0x66, 0xf8, 0x87, 0xc3, 0xd6, 0x62, 0x97, 0xc9, 0xe4, 0x2a, 0xb6, 0x86, 0x6b, 0xe4, 0xfb, 0xf2, 0xcf, 0x98, 0x97, 0xc1, 0x30, 0xa2, 0x0b, 0xc7, 0x45, 0x58, 0xf4, 0xa8, 0x4e, 0xcb, 0x50, 0x07, 0x7c, 0x4c, 0x98, 0x13, 0x70, 0x68, 0x12, 0xe6, 0xd8, 0x01, 0xea, 0x98, 0xbe, 0x0c, 0x9b, 0x63, 0x0e, 0xe0, 0xa1, 0x15, 0xe4, 0x31, 0xf9, 0x48, 0x73, 0x19, 0xca, 0xc1, 0x98, 0x0f, 0xa8, 0x2d, 0x03, 0x04, 0x7d, 0x39, 0xc1, 0x11, 0xa2, 0x0e, 0x41, 0xe7, 0x4c, 0xd8, 0xf2, 0xe3, 0x09, 0x34, 0xe4, 0xf9, 0xaa, 0xff, 0xa7, 0xb5, 0x74, 0x62, 0x95, 0xb5, 0xd8, 0xc2, 0x33, 0x06, 0x5b, 0xc4, 0x8b, 0x88, 0x2f, 0xc6, 0xc4, 0x63, 0x6a, 0x2d, 0xd6, 0x62, 0xb7, 0x09, 0x35, 0x40, 0x0c, 0x75, 0x54, 0x16, 0xf2, 0x7c, 0xb7, 0x5e, 0xe3, 0x2f, 0x85, 0x9f, 0xa8, 0x6d, 0x41, 0x4f, 0x8d, 0x46, 0x77, 0xb1, 0x6c, 0xb7, 0x87, 0xfa, 0xb5, 0x1d, 0xf5, 0xab, 0x1f, 0xd4, 0xa4, 0x2b, 0x4d, 0x48, 0x71, 0xc4, 0xee, 0xe8, 0x88, 0x5e, 0xa2, 0xac, 0xb0, 0x0c, 0x60, 0x07, 0x38, 0x45, 0x55, 0x19, 0x20, 0x0e, 0xb7, 0x7e, 0xe0, 0xb7, 0x44, 0x5d, 0x51, 0x0b, 0x56, 0x55, 0x6c, 0xd8, 0x4b, 0x5a, 0x76, 0xc9, 0x33, 0x41, 0x11, 0x29, 0x58, 0x44, 0x11, 0xc6, 0x3e, 0xd4, 0x68, 0x89, 0x79, 0xd1, 0x65, 0x2a, 0x10, 0x2e, 0x12, 0xdc, 0x96, 0xad, 0x5f, 0xd1, 0x38, 0x8c, 0x2e, 0xf3, 0xd7, 0xaf, 0x2c, 0xf0, 0xf0, 0x9b, 0x05, 0x8b, 0xd9, 0xbf, 0x4b, 0xb8, 0x56, 0x9a, 0x27, 0xea, 0x0f, 0x1a, 0xf5, 0x4f, 0x98, 0xec, 0x1a, 0xbd, 0xff, 0x33, 0x9d, 0xf9, 0x7b, 0x26, 0xe1, 0x3f, 0x15, 0x7d, 0x71, 0x3e, 0xbb, 0x0f, 0xbc, 0x8a, 0x68, 0x62, 0x41, 0x71, 0xba, 0x90, 0xa1, 0xdd, 0xc9, 0x64, 0xba, 0x33, 0x2b, 0xfa, 0x05, 0x52, 0x8f, 0x2c, 0xaf, 0xa2, 0xd2, 0x86, 0xcd, 0xca, 0xbb, 0xa8, 0x3d, 0x6c, 0x2d, 0xd2, 0x6d, 0x42, 0xa0, 0x36, 0x5d, 0x2d, 0xa0, 0xf6, 0xf8, 0x49, 0x3e, 0x0d, 0x9e, 0x28, 0xbf, 0x22, 0xf6, 0xd5, 0x1d, 0xe8, 0x16, 0x90, 0xf6, 0xc9, 0x28, 0xbf, 0x98, 0x1e, 0xec, 0x35, 0x56, 0x62, 0x0b, 0x97, 0x10, 0xeb, 0x85, 0x4c, 0x22, 0x22, 0x27, 0x19, 0xb8, 0x58, 0x80, 0x73, 0x5d, 0x40, 0x5d, 0xff, 0xea, 0x9f, 0x58, 0x55, 0xd5, 0x51, 0xe0, 0x5d, 0x58, 0xb3, 0x60, 0xf1, 0xae, 0xde, 0x50, 0xd5, 0xae, 0x48, 0x29, 0x62, 0xc5, 0xae, 0xb9, 0x5e, 0x81, 0x5f, 0x18, 0x28, 0x4b, 0xc5, 0x6b, 0x12, 0xab, 0x97, 0xe2, 0xe1, 0xdc, 0x9f, 0x2c, 0xc7, 0x1f, 0x89, 0x0a, 0xa7, 0x5d, 0xba, 0x8e, 0xd1, 0x7a, 0x89, 0x9e, 0x88, 0x75, 0xaf, 0x26, 0x44, 0x9f, 0x0d, 0xf8, 0x72, 0x7c, 0xa8, 0x06, 0xba, 0x55, 0x13, 0xde, 0x55, 0xd4, 0x87, 0xca, 0xe3, 0xb2, 0xfb, 0x1c, 0x3e, 0xd9, 0xf3, 0x48, 0x37, 0x96, 0xe7, 0x51, 0x1d, 0xa2, 0x0e, 0x61, 0xd6, 0x35, 0x2f, 0x9b, 0x1c, 0x89, 0x4c, 0x5e, 0xd6, 0xdc, 0xb2, 0x6c, 0x52, 0x34, 0x3a, 0x69, 0x59, 0xcb, 0xe6, 0xcd, 0x97, 0xf0, 0xf7, 0x46, 0xa6, 0x0c, 0xb4, 0xb7, 0x0f, 0x4c, 0x89, 0x46, 0xe9, 0x67, 0x44, 0x3a, 0xe7, 0x6b, 0xe8, 0x0f, 0x50, 0xf4, 0xa0, 0x26, 0xa4, 0x07, 0x61, 0x1a, 0x90, 0x16, 0x81, 0xab, 0x27, 0x3d, 0x8d, 0x2a, 0x47, 0x34, 0xcc, 0x57, 0xbc, 0xa0, 0x18, 0xd8, 0xfb, 0x27, 0xd3, 0x01, 0xe3, 0x67, 0xa0, 0xa3, 0x8f, 0x79, 0x9b, 0xa9, 0x47, 0xfb, 0x57, 0x9c, 0x77, 0x62, 0x50, 0xb6, 0xfb, 0x2f, 0x96, 0xed, 0xfe, 0xab, 0x90, 0x40, 0x05, 0x55, 0x96, 0x9c, 0xdf, 0x0d, 0x94, 0xcd, 0x0b, 0xd5, 0x2f, 0x09, 0x90, 0xda, 0x36, 0xcb, 0x1b, 0x9c, 0x3c, 0x14, 0xaa, 0x8c, 0x92, 0x4d, 0x41, 0xd0, 0x56, 0x1a, 0x89, 0xe6, 0x6c, 0x76, 0x14, 0xab, 0xe6, 0x9c, 0xdd, 0x8e, 0x83, 0x74, 0xee, 0x8b, 0xf6, 0x4a, 0xb4, 0xd9, 0x31, 0x79, 0x42, 0xce, 0x68, 0xb5, 0xcb, 0x10, 0x98, 0xd5, 0x14, 0x9f, 0x4c, 0xf7, 0x3b, 0x89, 0x66, 0xf7, 0xae, 0xb9, 0x7e, 0x81, 0xee, 0x76, 0x12, 0xad, 0x91, 0x02, 0xbf, 0x6f, 0x4e, 0x04, 0x69, 0xd6, 0x03, 0x64, 0xbf, 0xe3, 0x71, 0xfe, 0x84, 0x39, 0xe6, 0xa6, 0xe3, 0x0f, 0xde, 0x8c, 0xda, 0xe4, 0x00, 0x2b, 0x68, 0x23, 0x74, 0x3c, 0x1a, 0xb8, 0x26, 0xc5, 0xc2, 0x18, 0xfd, 0xe0, 0xe8, 0x8f, 0x7e, 0xfa, 0xd8, 0x4d, 0xcf, 0x1a, 0x70, 0x1f, 0xc8, 0x2d, 0x20, 0x5f, 0xf1, 0x75, 0xa9, 0x53, 0x3d, 0x86, 0x20, 0x4f, 0xc8, 0xce, 0x61, 0x80, 0xed, 0xe9, 0x7f, 0x2c, 0x68, 0x0b, 0x86, 0x32, 0x33, 0xac, 0x62, 0x3e, 0x2a, 0x0f, 0x4f, 0xa7, 0xeb, 0x27, 0x9e, 0x09, 0xc9, 0x12, 0xb7, 0x96, 0xb3, 0x6a, 0x8a, 0x4c, 0xd1, 0x64, 0xc1, 0xae, 0x59, 0x0e, 0x33, 0xcf, 0x77, 0x56, 0xd6, 0x17, 0x9c, 0x67, 0xe3, 0xcb, 0x22, 0xd2, 0x29, 0x46, 0x6b, 0xb6, 0x13, 0x59, 0xa9, 0x46, 0xfb, 0xb2, 0xef, 0xa1, 0xb1, 0x98, 0x02, 0x8d, 0xe9, 0xba, 0x14, 0xda, 0x52, 0x79, 0x72, 0x6d, 0x2e, 0xf3, 0x36, 0x64, 0x72, 0xe6, 0x19, 0xb4, 0x63, 0x49, 0x86, 0xed, 0x49, 0xa4, 0x22, 0x62, 0x95, 0x52, 0x50, 0x77, 0x59, 0xca, 0x32, 0x96, 0x63, 0x4d, 0x55, 0xc1, 0xa9, 0x9b, 0xad, 0xdb, 0x75, 0x16, 0x9d, 0xc3, 0x57, 0xe2, 0x0c, 0x46, 0x23, 0x0d, 0xe9, 0x86, 0x88, 0xaf, 0xbe, 0xb7, 0xb6, 0x7e, 0xa0, 0x38, 0x68, 0x70, 0x19, 0x1c, 0xbe, 0xb0, 0xaf, 0x2c, 0x14, 0x69, 0x4c, 0x37, 0x46, 0x3c, 0xa9, 0xae, 0x9a, 0x86, 0x85, 0x7e, 0xbe, 0xb2, 0x40, 0xa7, 0xd7, 0x79, 0x5d, 0x0e, 0x97, 0xcd, 0x1e, 0xac, 0x8d, 0x47, 0x3b, 0x2b, 0xbd, 0xe1, 0xc2, 0x7a, 0xbb, 0xd1, 0x6c, 0x08, 0xda, 0x9d, 0x6e, 0x9b, 0x2d, 0xd4, 0x5c, 0x19, 0x6c, 0x4e, 0x7a, 0x23, 0x7e, 0xd2, 0x8e, 0x20, 0x6a, 0xc7, 0x2e, 0xa1, 0x01, 0x14, 0x83, 0xd6, 0x74, 0x93, 0x16, 0x42, 0xd6, 0x0f, 0xd1, 0xe6, 0xb7, 0x0b, 0x20, 0xc1, 0xdf, 0x84, 0x97, 0x01, 0x72, 0x54, 0x83, 0xc4, 0x1e, 0xca, 0x7b, 0x94, 0x55, 0xa0, 0x07, 0x02, 0x97, 0xc3, 0x6a, 0x29, 0x30, 0x9b, 0x0c, 0x3c, 0x07, 0x8a, 0x61, 0xb1, 0x48, 0xe2, 0x31, 0x29, 0x97, 0x8e, 0x1d, 0xb0, 0xbe, 0x85, 0x06, 0x12, 0x50, 0x6e, 0x37, 0xf0, 0xa5, 0xa4, 0x34, 0xa5, 0xbb, 0x46, 0x6f, 0x3e, 0xa7, 0x90, 0xf7, 0x4e, 0xbd, 0xfe, 0x7a, 0xa7, 0xcd, 0x6e, 0xd5, 0x09, 0x22, 0x5f, 0x56, 0x38, 0x9d, 0xbd, 0xb1, 0x6f, 0x32, 0x74, 0x9a, 0x4d, 0x8d, 0x05, 0x3b, 0x5c, 0x77, 0x0d, 0x4a, 0x1f, 0x38, 0x3c, 0x68, 0x93, 0x38, 0xc9, 0xe5, 0x22, 0xb4, 0x75, 0x21, 0x79, 0x77, 0xf2, 0x87, 0x80, 0x0b, 0xa4, 0xd2, 0x55, 0x4e, 0x9c, 0x52, 0x12, 0xb1, 0x96, 0xe9, 0x82, 0xc4, 0x8e, 0x8a, 0xc7, 0x13, 0xd1, 0x00, 0x39, 0xcf, 0x26, 0x06, 0xf9, 0xab, 0xc8, 0xd1, 0xa4, 0x0b, 0xb8, 0x42, 0x8e, 0x30, 0x62, 0xaf, 0x1b, 0xaf, 0x53, 0x99, 0xed, 0x5f, 0xe6, 0xde, 0x85, 0x71, 0xce, 0xf5, 0x68, 0xc5, 0x40, 0x57, 0x5d, 0x7c, 0x42, 0xb9, 0x6b, 0x67, 0x59, 0xa9, 0xbf, 0xd6, 0xc5, 0x1f, 0x1a, 0xfe, 0xa3, 0x27, 0x1a, 0x88, 0xe1, 0xb5, 0x14, 0x76, 0x4b, 0x27, 0xe2, 0x95, 0x5e, 0x27, 0xbc, 0x88, 0xce, 0x89, 0x22, 0x9a, 0xe3, 0xfd, 0x88, 0x06, 0x2b, 0x8e, 0x05, 0xa1, 0x27, 0x21, 0x63, 0x30, 0xf8, 0x42, 0x72, 0x26, 0x3a, 0x40, 0x62, 0x02, 0x5b, 0x81, 0xc5, 0x62, 0xe1, 0xd0, 0xb2, 0x03, 0xd9, 0x10, 0x4b, 0xda, 0x4b, 0x8e, 0x42, 0xf1, 0xdc, 0x9e, 0x62, 0xfc, 0x3f, 0xbe, 0xf2, 0x47, 0x9b, 0xcc, 0x4e, 0x66, 0xb1, 0xa0, 0xe7, 0x05, 0x83, 0xb8, 0x84, 0xb5, 0x9b, 0x70, 0x75, 0x8c, 0xd6, 0x64, 0x18, 0xf6, 0xd7, 0x7a, 0xbd, 0x75, 0xfe, 0xcf, 0x0d, 0x26, 0x6c, 0x6f, 0x8f, 0xe6, 0xde, 0x16, 0xfe, 0x2e, 0x50, 0x8e, 0xa3, 0x81, 0x45, 0xdc, 0x1a, 0xbc, 0x20, 0xe3, 0x96, 0x32, 0xe0, 0x42, 0x9e, 0x63, 0x59, 0xb2, 0x1a, 0x92, 0x64, 0xd2, 0x89, 0x56, 0x4b, 0x9c, 0xb6, 0x0f, 0x27, 0x04, 0x60, 0x2b, 0x60, 0x0c, 0x1f, 0x46, 0x0b, 0xe8, 0x67, 0x76, 0xd5, 0xa8, 0x0f, 0x6a, 0xb2, 0xe9, 0x60, 0x5b, 0xee, 0x5f, 0x67, 0x13, 0xe7, 0xf3, 0x68, 0xb7, 0x26, 0xf2, 0xfd, 0x82, 0xeb, 0xbc, 0x7b, 0x37, 0x9a, 0x5d, 0xcc, 0x24, 0x4e, 0xc3, 0xf3, 0x7a, 0x71, 0xa6, 0xd6, 0xdf, 0x10, 0xd0, 0xcc, 0x10, 0x74, 0x1c, 0xaf, 0xe1, 0xa7, 0xb2, 0x0e, 0x42, 0xa1, 0x07, 0x3a, 0x0a, 0x8a, 0x0b, 0x2c, 0xc5, 0x16, 0xe9, 0x1d, 0x0f, 0x1a, 0xf5, 0x96, 0x0f, 0xbc, 0x15, 0x2e, 0x57, 0x85, 0xf7, 0x8f, 0x45, 0xc5, 0x1f, 0x79, 0x92, 0x2e, 0x67, 0xd2, 0xf3, 0x51, 0x81, 0x19, 0xd1, 0x5d, 0x8c, 0xf8, 0xd3, 0xa8, 0xe6, 0x07, 0x57, 0xee, 0x1a, 0x48, 0x17, 0x91, 0xd4, 0x35, 0x48, 0xbf, 0x28, 0xb5, 0xc6, 0x0b, 0x38, 0x4c, 0x30, 0x3d, 0xe9, 0x81, 0x66, 0x7a, 0x6a, 0x1c, 0x23, 0xf4, 0xd6, 0x13, 0x5a, 0x45, 0xa6, 0x71, 0x87, 0xd9, 0xc6, 0xaf, 0x66, 0x05, 0x6e, 0x01, 0x6f, 0x33, 0x5d, 0x7e, 0xec, 0x81, 0x1d, 0x66, 0x93, 0xb0, 0x9a, 0xd7, 0xf5, 0x6a, 0x9d, 0xdb, 0x1e, 0x60, 0xf7, 0x99, 0xf5, 0x47, 0x75, 0x56, 0xcd, 0x2e, 0x5d, 0x01, 0x34, 0x7d, 0xa6, 0xd1, 0xfe, 0xa3, 0xd5, 0x79, 0xa5, 0xfd, 0x43, 0xda, 0x3f, 0xce, 0x91, 0x00, 0x53, 0x01, 0xcb, 0x50, 0xfd, 0xd1, 0x74, 0xc8, 0x97, 0x75, 0xd7, 0x81, 0x08, 0x98, 0x8f, 0x09, 0x00, 0x0b, 0x09, 0x17, 0x7b, 0x15, 0x2a, 0xf8, 0x33, 0x51, 0x51, 0xb1, 0xc3, 0x44, 0xa8, 0xe0, 0x17, 0xf0, 0x56, 0x99, 0x0a, 0x7e, 0x0d, 0xaf, 0x9b, 0xa9, 0x73, 0x6e, 0x7d, 0xe0, 0x15, 0x44, 0x84, 0xde, 0xa2, 0xd9, 0xa5, 0x37, 0x43, 0xe3, 0xe7, 0x1a, 0xdd, 0xfd, 0x0a, 0x11, 0x88, 0x06, 0x34, 0x17, 0x57, 0x93, 0xdc, 0x04, 0x41, 0x7c, 0x1b, 0x41, 0xc7, 0x08, 0x80, 0x0b, 0xe9, 0x18, 0x21, 0xe3, 0xd2, 0x01, 0xec, 0xd6, 0x10, 0x1e, 0x23, 0xf4, 0xc8, 0xbc, 0x41, 0xed, 0xac, 0x7a, 0x3a, 0x4c, 0xaa, 0xa5, 0xdf, 0x69, 0x6d, 0xf6, 0x35, 0x03, 0x3f, 0xbb, 0xac, 0xc0, 0xca, 0x0d, 0xf2, 0x5a, 0x56, 0xab, 0x5f, 0xc1, 0x39, 0x8c, 0xa8, 0x23, 0x2e, 0x76, 0x14, 0xfc, 0x3f, 0xf8, 0xb2, 0x59, 0xff, 0xbe, 0xb9, 0xc8, 0x1c, 0x28, 0x7e, 0x57, 0x6f, 0xa6, 0xed, 0x4e, 0xa0, 0xb9, 0xf2, 0x5f, 0x50, 0x9d, 0x66, 0x50, 0x96, 0x8e, 0x62, 0xeb, 0x4d, 0x0d, 0x6e, 0xa6, 0x9a, 0xc3, 0x99, 0x2c, 0x89, 0x24, 0x04, 0xf0, 0x2a, 0x92, 0xc3, 0xd9, 0x4e, 0x3a, 0xc0, 0x2e, 0x88, 0x6c, 0x8a, 0x26, 0x71, 0xb6, 0x85, 0xea, 0x82, 0xf0, 0x5f, 0x74, 0xfa, 0x37, 0x3f, 0x92, 0xce, 0xd3, 0x19, 0xde, 0x7a, 0x17, 0xee, 0xe1, 0x57, 0x98, 0x8b, 0x0c, 0xd2, 0xf3, 0xeb, 0x5a, 0xb4, 0x7e, 0x33, 0xac, 0x38, 0x1f, 0xab, 0x59, 0x48, 0xe6, 0x7b, 0xc9, 0xd8, 0xf7, 0xa5, 0xdd, 0xf8, 0xe4, 0x97, 0xe4, 0x88, 0xa6, 0x32, 0x37, 0x08, 0x7a, 0x2c, 0xe5, 0x01, 0x56, 0xe7, 0x92, 0xd3, 0xbe, 0xd3, 0x71, 0x48, 0x1a, 0x07, 0x53, 0x7c, 0xef, 0xa3, 0x85, 0x21, 0xad, 0x74, 0x85, 0xc9, 0xa7, 0xd7, 0xb9, 0x4d, 0xd2, 0x95, 0x62, 0xc4, 0xf7, 0x55, 0x66, 0xda, 0xf7, 0x0b, 0x9d, 0xec, 0x4f, 0xc3, 0xf3, 0x2a, 0x2b, 0xe7, 0x85, 0x3f, 0xaf, 0xb2, 0x15, 0xa3, 0x82, 0x51, 0x1d, 0x9d, 0x72, 0x1e, 0xea, 0x08, 0xa8, 0x4f, 0xa7, 0xf0, 0xd4, 0x59, 0x84, 0x4a, 0xb7, 0x91, 0x83, 0x1f, 0xb2, 0xb6, 0xaa, 0x8d, 0x21, 0x96, 0x93, 0x83, 0x0c, 0x9a, 0x7d, 0xc2, 0x21, 0x8f, 0xdb, 0xe5, 0x00, 0x11, 0x18, 0xe1, 0x89, 0xad, 0x4a, 0x34, 0x86, 0x63, 0xcc, 0x14, 0xb1, 0x0e, 0x27, 0xc9, 0x3d, 0x2f, 0x22, 0x7e, 0xca, 0x99, 0xaa, 0xb5, 0x10, 0xef, 0xd3, 0x83, 0x0e, 0xf8, 0x83, 0xea, 0x99, 0xb1, 0xc4, 0x24, 0x3f, 0x34, 0x5b, 0x1c, 0x06, 0x0e, 0xe9, 0x57, 0xd2, 0x0f, 0x8b, 0xe2, 0x8e, 0xe8, 0x44, 0x3f, 0xf3, 0xaf, 0x9f, 0xfd, 0xab, 0xd3, 0x02, 0x7f, 0x2a, 0x95, 0xe8, 0x4d, 0x5c, 0x03, 0xf7, 0xa1, 0xa8, 0x71, 0xd5, 0x96, 0x48, 0xdf, 0xb2, 0xfb, 0xdd, 0x76, 0xad, 0xab, 0xa6, 0x54, 0xba, 0x2e, 0x54, 0x6a, 0xe4, 0xf9, 0xfb, 0xfb, 0xdc, 0x95, 0x56, 0x26, 0xc4, 0x34, 0x18, 0x02, 0x96, 0x45, 0xf7, 0x53, 0xfe, 0x2f, 0x03, 0x00, 0xcd, 0x60, 0x98, 0xee, 0x44, 0xba, 0x54, 0xc0, 0xa7, 0x21, 0x00, 0x6d, 0xe6, 0x06, 0x68, 0xb4, 0x12, 0x0f, 0x26, 0xd8, 0x4b, 0x26, 0x87, 0x08, 0x08, 0x5b, 0x2c, 0x2e, 0x1c, 0x77, 0x5f, 0x2b, 0xf8, 0x68, 0x14, 0x25, 0xbc, 0xd6, 0x54, 0xb0, 0x75, 0x16, 0xa2, 0x26, 0xd9, 0x8b, 0x18, 0x9c, 0xac, 0xaa, 0x1d, 0xfd, 0x46, 0xba, 0xb4, 0x20, 0xf6, 0x1e, 0x7a, 0xf3, 0xe0, 0xbf, 0x1d, 0x7c, 0xe3, 0xce, 0xde, 0x3d, 0x25, 0x91, 0xe4, 0xf2, 0xdb, 0xd7, 0xfd, 0xdb, 0xba, 0x3b, 0x56, 0x24, 0x23, 0x25, 0x68, 0x20, 0x8c, 0x5c, 0xf5, 0x93, 0x03, 0x73, 0x05, 0xe9, 0x1e, 0xb8, 0x58, 0x98, 0xb7, 0xff, 0xd9, 0x6d, 0x0c, 0x8c, 0x17, 0x4b, 0xd3, 0x16, 0xdf, 0xb2, 0xaa, 0x89, 0x67, 0xf7, 0x7d, 0x7e, 0x01, 0xdf, 0xb4, 0xea, 0x96, 0xc5, 0xf0, 0xdb, 0xc5, 0xf1, 0x91, 0x91, 0x91, 0x27, 0x50, 0xbf, 0xbd, 0x43, 0xf2, 0x9c, 0xff, 0x74, 0x98, 0xe4, 0x87, 0x97, 0xd6, 0xe4, 0xe6, 0x87, 0x47, 0xf4, 0x9f, 0x8f, 0x70, 0x3e, 0xe6, 0x0f, 0x83, 0x16, 0x1c, 0x2b, 0xc6, 0x2b, 0x20, 0x1d, 0xbb, 0x31, 0xcc, 0xe0, 0xd9, 0x55, 0x49, 0xa1, 0x7a, 0x7e, 0xb7, 0xc0, 0x33, 0x78, 0x4d, 0x65, 0xa0, 0x12, 0x37, 0xad, 0x05, 0x34, 0x27, 0x42, 0xf1, 0x78, 0x08, 0xab, 0xaf, 0x11, 0x92, 0x31, 0xa2, 0x0e, 0xd1, 0x5d, 0x9b, 0x49, 0x9e, 0xe6, 0x72, 0x04, 0xf1, 0x12, 0x10, 0x44, 0x4b, 0x56, 0x9e, 0x93, 0x30, 0xea, 0x13, 0xfe, 0xe3, 0xd2, 0xc0, 0xf0, 0x07, 0x33, 0x6f, 0x18, 0x68, 0x9a, 0x79, 0xdd, 0xb1, 0xe5, 0xcb, 0x8f, 0xed, 0x98, 0xd9, 0x34, 0x70, 0xc3, 0xcc, 0xe1, 0x0f, 0x02, 0xa5, 0xb0, 0x20, 0x76, 0x4e, 0xe9, 0xc2, 0x3b, 0x36, 0x74, 0x5c, 0xf0, 0xbd, 0xe1, 0x5b, 0x6e, 0x96, 0x9e, 0xb8, 0xa0, 0x63, 0xc3, 0x1d, 0x0b, 0x4b, 0xcf, 0x89, 0x1d, 0x5f, 0x5f, 0x14, 0xfb, 0xd0, 0xb4, 0x64, 0xef, 0x63, 0xab, 0x2f, 0x7d, 0xf4, 0xb2, 0xd6, 0xd6, 0xcb, 0x1e, 0xbd, 0x74, 0xf5, 0x63, 0x7b, 0x97, 0x98, 0x3e, 0x8c, 0x15, 0xad, 0xd7, 0x08, 0xc5, 0x9b, 0xef, 0xfe, 0xe7, 0xa1, 0xfd, 0x1f, 0xe1, 0x92, 0x3e, 0xda, 0x3f, 0xf4, 0xcf, 0x77, 0x6f, 0x2e, 0x16, 0x34, 0xa8, 0xed, 0x5b, 0x50, 0xbb, 0xde, 0x25, 0x7e, 0xb2, 0x3f, 0xa5, 0x7e, 0xb5, 0x52, 0x1f, 0xc9, 0xfd, 0x7e, 0x53, 0x1e, 0xbc, 0x6d, 0xf8, 0x4d, 0x02, 0xcf, 0xc7, 0x6f, 0x97, 0xe1, 0x2f, 0x61, 0xb8, 0xf0, 0x8c, 0x0a, 0xef, 0x90, 0xe6, 0x13, 0x1f, 0x8f, 0x6d, 0x04, 0xff, 0xa4, 0x0a, 0x6f, 0xa2, 0x70, 0xe9, 0xfa, 0x3c, 0x78, 0x5a, 0x2e, 0xe7, 0xe2, 0x3c, 0xf8, 0x84, 0x71, 0xca, 0xef, 0x94, 0xcb, 0xbf, 0x87, 0xe0, 0x1f, 0x57, 0xe1, 0xcd, 0x32, 0xfc, 0x32, 0x04, 0xff, 0x2f, 0xda, 0xa7, 0x24, 0x6e, 0x5f, 0x8b, 0x74, 0x07, 0xb9, 0xcb, 0x19, 0x94, 0x36, 0x23, 0xfc, 0xc3, 0x2a, 0xfe, 0x74, 0x66, 0x3b, 0xc1, 0xdf, 0x42, 0xe0, 0x4a, 0xbb, 0x62, 0x94, 0x0f, 0x99, 0xe7, 0xd2, 0x4d, 0x79, 0xcf, 0x09, 0x3f, 0xb2, 0x9e, 0xe7, 0xbf, 0xdf, 0x9e, 0xf7, 0xfc, 0x25, 0xfc, 0x5c, 0xa5, 0x3f, 0x46, 0xf9, 0x93, 0x55, 0xff, 0x36, 0xf2, 0xfe, 0x49, 0xf5, 0x79, 0x53, 0xee, 0x73, 0xe9, 0xfa, 0xbc, 0xe7, 0xe9, 0xbc, 0xf2, 0x2f, 0xce, 0x7b, 0x3e, 0xe1, 0x2c, 0xf5, 0x77, 0xe6, 0x96, 0x0f, 0xfa, 0x46, 0xde, 0xe3, 0xee, 0x20, 0xfe, 0xcc, 0x3f, 0x93, 0xfd, 0x99, 0xb3, 0xe1, 0x4f, 0x2a, 0x70, 0xbe, 0x71, 0x1c, 0x78, 0xdb, 0x38, 0xf0, 0xf6, 0x6c, 0x38, 0xa9, 0x9f, 0xc2, 0x3b, 0x72, 0xf0, 0x4f, 0xaa, 0xf0, 0xa6, 0x71, 0xe0, 0xe9, 0x71, 0xe0, 0x13, 0xc6, 0x29, 0xbf, 0x13, 0x7c, 0x91, 0x76, 0x1d, 0x57, 0xf1, 0x5b, 0x72, 0xe0, 0x7b, 0x55, 0x78, 0x6b, 0x0e, 0xbc, 0x4b, 0x2d, 0xe7, 0xb1, 0x1c, 0xf8, 0x21, 0x15, 0xfe, 0xb8, 0x02, 0x97, 0x36, 0x73, 0x77, 0x10, 0xdf, 0x36, 0x5a, 0xce, 0x74, 0xb8, 0x39, 0x03, 0x57, 0xf9, 0x13, 0xa3, 0xf0, 0x6c, 0xbe, 0xe6, 0x3d, 0x27, 0x7c, 0x3d, 0xc3, 0xfb, 0xed, 0x63, 0x3d, 0x57, 0xf9, 0x10, 0xa3, 0x7c, 0x1e, 0xf5, 0xfe, 0x49, 0xf5, 0x79, 0xd3, 0x59, 0x9e, 0xa7, 0xcf, 0xf2, 0x7c, 0xc2, 0x59, 0xea, 0xef, 0x1c, 0xf3, 0xfd, 0xe3, 0xea, 0xf3, 0xe6, 0xb3, 0x3c, 0x6f, 0x19, 0xf3, 0xf9, 0xde, 0x51, 0xfc, 0x53, 0xfa, 0x69, 0xf6, 0xc8, 0xfb, 0xec, 0x22, 0xd2, 0xdf, 0xcf, 0x93, 0xbb, 0xdd, 0xb7, 0x17, 0x83, 0x2c, 0xf8, 0x93, 0x0a, 0x9c, 0x9f, 0x32, 0x0e, 0xbc, 0x6d, 0x1c, 0x78, 0x7b, 0x0e, 0xfc, 0xa4, 0x0a, 0x6f, 0x1a, 0x07, 0x9e, 0x1e, 0x07, 0x3e, 0x21, 0x07, 0x3e, 0x1e, 0x9d, 0xc7, 0x55, 0xfc, 0x16, 0x19, 0x6e, 0x95, 0x0e, 0x73, 0x4e, 0x82, 0xff, 0x02, 0xc5, 0xdf, 0x48, 0xd7, 0xd5, 0x52, 0x04, 0x2f, 0xe7, 0x34, 0x20, 0x80, 0x63, 0xfc, 0x15, 0xa0, 0xb5, 0xc8, 0xad, 0x41, 0x5a, 0x14, 0xd3, 0x85, 0xc3, 0x51, 0x71, 0x0b, 0xb1, 0xc3, 0x33, 0xd9, 0x4d, 0x11, 0x87, 0xe6, 0x35, 0xa0, 0x27, 0x1c, 0x09, 0xd9, 0x42, 0x4a, 0x20, 0x36, 0xf5, 0xa0, 0xa1, 0x36, 0x9a, 0x50, 0x53, 0x64, 0xc9, 0x1b, 0x55, 0x0e, 0x87, 0xec, 0xed, 0xb9, 0xa6, 0xf1, 0x74, 0xba, 0x23, 0xa9, 0xf7, 0xc4, 0x0a, 0x67, 0x5f, 0xee, 0x99, 0xf2, 0xad, 0x0b, 0xb0, 0x95, 0x95, 0x74, 0x98, 0xfd, 0x15, 0x8d, 0x47, 0xc3, 0xbc, 0xdc, 0x3d, 0xb5, 0xd7, 0xe8, 0xb6, 0xe9, 0x7b, 0x66, 0x4d, 0x3f, 0x17, 0x5b, 0x57, 0xa9, 0x74, 0x3e, 0x29, 0xd3, 0x89, 0xe3, 0x34, 0x7c, 0x35, 0x8b, 0xfe, 0x0c, 0xbc, 0x0d, 0xec, 0x19, 0x13, 0xde, 0x2e, 0xc7, 0x25, 0x22, 0x70, 0x32, 0x8e, 0x5e, 0x90, 0xe7, 0x8b, 0xdd, 0x59, 0xf8, 0x27, 0x55, 0x78, 0x53, 0x4e, 0xf9, 0x19, 0x78, 0x1a, 0x7c, 0x65, 0x4c, 0xf8, 0x04, 0xf0, 0x8f, 0x63, 0x96, 0xdf, 0x99, 0x53, 0x7e, 0x1e, 0x9f, 0x55, 0xf8, 0x71, 0x15, 0xbf, 0x05, 0xbc, 0x95, 0x05, 0xdf, 0xab, 0xc2, 0x5b, 0xc1, 0x83, 0x32, 0x7c, 0x33, 0xe7, 0x24, 0x7e, 0x76, 0xa4, 0x1c, 0x7e, 0xfa, 0x66, 0x90, 0x81, 0xab, 0xed, 0x8d, 0x51, 0xfe, 0x9c, 0xe1, 0x39, 0xe1, 0xd3, 0x19, 0x9e, 0x13, 0x7e, 0xe5, 0x3f, 0x57, 0xdb, 0x15, 0xa3, 0x7c, 0x1b, 0xf5, 0xfe, 0x49, 0xf5, 0x79, 0xd3, 0x98, 0xf5, 0x67, 0x9e, 0x13, 0x3e, 0x9e, 0xe1, 0x39, 0xe1, 0xe7, 0x19, 0xea, 0xef, 0x1c, 0xb3, 0x7e, 0x85, 0x8f, 0x51, 0xbe, 0xf9, 0x52, 0xf9, 0xe9, 0xa8, 0x67, 0x31, 0xca, 0xe3, 0x51, 0xef, 0xee, 0x55, 0x9f, 0x13, 0x5e, 0x67, 0x3d, 0xbf, 0x7a, 0xe4, 0x03, 0xa6, 0x98, 0xf4, 0xdd, 0x2f, 0xe9, 0x5c, 0x4f, 0xe3, 0x16, 0x8e, 0x3c, 0x88, 0xe0, 0x9b, 0xf8, 0x57, 0x54, 0xf8, 0x3b, 0x23, 0x73, 0x88, 0xec, 0x10, 0x7c, 0xf6, 0x63, 0x60, 0x01, 0x3d, 0xf4, 0x3a, 0xa1, 0x80, 0x9e, 0xe8, 0xc8, 0xa7, 0x3c, 0x1b, 0xd4, 0x8b, 0x80, 0x5c, 0xf8, 0x3a, 0x7c, 0xce, 0x3a, 0x0a, 0xb5, 0xbf, 0xff, 0x44, 0x24, 0x54, 0x80, 0x15, 0x7c, 0x1b, 0xde, 0x3b, 0xb7, 0x40, 0xf5, 0xdc, 0xea, 0xea, 0xd3, 0x1d, 0x53, 0xe6, 0x7a, 0xcb, 0x5b, 0x02, 0xed, 0x4b, 0xd3, 0x01, 0xf6, 0xe3, 0xe1, 0xf0, 0xf4, 0x19, 0xad, 0x8e, 0x80, 0x53, 0x1f, 0xef, 0x5e, 0x4b, 0x63, 0xef, 0x76, 0x22, 0x3a, 0x26, 0x20, 0x3a, 0x3c, 0xe0, 0x12, 0x39, 0xbc, 0x2a, 0x07, 0xd1, 0xde, 0x1d, 0x07, 0xb3, 0xc1, 0xc1, 0x67, 0x56, 0x91, 0x38, 0x35, 0x59, 0x16, 0x07, 0x86, 0x33, 0xa1, 0x10, 0x4b, 0x83, 0x33, 0x15, 0x40, 0xa3, 0x95, 0xa2, 0xca, 0xdc, 0xc4, 0xf9, 0x3b, 0x82, 0x83, 0xd7, 0x44, 0x14, 0x62, 0xdb, 0xa0, 0x12, 0xc8, 0x30, 0x84, 0x6f, 0x10, 0x98, 0x09, 0x0a, 0xdd, 0x03, 0x03, 0xa7, 0x7f, 0xf0, 0x83, 0x8e, 0x29, 0xd7, 0xa9, 0x94, 0x1f, 0x24, 0x21, 0x3e, 0xf7, 0xdc, 0x34, 0x7d, 0x06, 0xe1, 0xfd, 0x95, 0x84, 0xf7, 0x4f, 0x2a, 0x3c, 0xe6, 0xa7, 0xc8, 0xbc, 0xff, 0x94, 0xc2, 0x81, 0x02, 0x6f, 0xc3, 0xbc, 0x47, 0xf0, 0xd7, 0xf3, 0xe0, 0xed, 0x32, 0xfc, 0x97, 0x08, 0x5e, 0x45, 0xc6, 0x0f, 0x85, 0x77, 0xc8, 0xb1, 0x27, 0x69, 0xf9, 0x27, 0x55, 0x78, 0x93, 0x5c, 0xfe, 0x07, 0x14, 0xae, 0x96, 0x93, 0x96, 0xcb, 0xf9, 0x75, 0x1e, 0x7c, 0xc2, 0x38, 0xe5, 0x77, 0x9e, 0x69, 0x8c, 0x20, 0xf8, 0x32, 0x04, 0x5f, 0x41, 0xf4, 0x48, 0x8a, 0x3f, 0x49, 0x86, 0x1f, 0xc1, 0xe5, 0x0b, 0xcf, 0xa8, 0xf0, 0xc9, 0x14, 0x2e, 0x8f, 0xc1, 0xe3, 0x6a, 0xf9, 0xcd, 0x32, 0xfd, 0x75, 0x79, 0xf0, 0x16, 0xb9, 0xde, 0x27, 0x08, 0x7c, 0xaf, 0x5a, 0x4e, 0xab, 0x5c, 0xfe, 0x49, 0x02, 0xef, 0x52, 0xe0, 0xe0, 0x31, 0x19, 0xfe, 0x0c, 0x81, 0x1f, 0x52, 0xe1, 0x8f, 0xcb, 0x63, 0xf9, 0x18, 0x9a, 0x87, 0x9e, 0x23, 0xb1, 0x98, 0x8f, 0x52, 0xf7, 0x75, 0x7d, 0x40, 0xcb, 0x70, 0xac, 0x19, 0x02, 0x81, 0xc4, 0x53, 0x93, 0x7f, 0xa1, 0x21, 0xa1, 0xe4, 0xf8, 0x0b, 0xc9, 0x16, 0xba, 0x38, 0xb0, 0x24, 0xde, 0xa1, 0xae, 0x93, 0x47, 0x09, 0xfd, 0x8e, 0x06, 0xf5, 0x1a, 0x35, 0x13, 0x45, 0x3e, 0xea, 0x86, 0x2c, 0xd4, 0x0d, 0x0a, 0x2a, 0xbe, 0x7a, 0x90, 0xb1, 0xc8, 0x11, 0xfe, 0x60, 0xd6, 0xa8, 0x93, 0x71, 0xe4, 0xa1, 0x17, 0x05, 0x11, 0x5b, 0xc8, 0x16, 0x8d, 0x86, 0xe4, 0xa1, 0x47, 0xb3, 0xb1, 0x53, 0x5f, 0x3d, 0x12, 0x6d, 0x0f, 0x1f, 0x8e, 0x65, 0x19, 0xfe, 0xa2, 0x2f, 0xdc, 0x73, 0x93, 0xda, 0xe3, 0x33, 0xd2, 0x15, 0xfa, 0xe6, 0xaf, 0x2c, 0x99, 0x76, 0x5e, 0x44, 0xe4, 0x97, 0xc7, 0x93, 0xa7, 0x57, 0xae, 0xea, 0xbf, 0x26, 0x71, 0xba, 0xa7, 0xe3, 0x86, 0x99, 0xde, 0xb8, 0x3b, 0xd5, 0x53, 0xb7, 0x60, 0x45, 0xa2, 0x2c, 0x3e, 0x37, 0x22, 0xbd, 0x9f, 0x28, 0xe3, 0x3e, 0x19, 0xbe, 0x6b, 0xdd, 0x55, 0xcb, 0xe7, 0x31, 0x07, 0x87, 0x4b, 0xba, 0xa7, 0x92, 0x98, 0xa2, 0x87, 0x39, 0x0f, 0x19, 0x9f, 0xbf, 0x96, 0xf5, 0xad, 0x5b, 0xe5, 0x58, 0xa3, 0x18, 0x7e, 0x52, 0x85, 0x37, 0xc9, 0x70, 0x6c, 0x13, 0xfc, 0x9f, 0x64, 0x2e, 0x79, 0x43, 0xd6, 0x1b, 0x6f, 0xca, 0x82, 0x6f, 0x57, 0xe1, 0x27, 0x72, 0xe0, 0xaf, 0xa8, 0xf0, 0x77, 0x72, 0xe0, 0x4f, 0x2a, 0x70, 0x34, 0xdf, 0x8f, 0x0d, 0x6f, 0x1b, 0x07, 0xde, 0x9e, 0x0d, 0x27, 0xe3, 0xf6, 0x0d, 0x79, 0x3d, 0xcc, 0xc6, 0x3f, 0xa9, 0xc2, 0x9b, 0xc6, 0x81, 0xa7, 0xc7, 0x81, 0x4f, 0x18, 0xa7, 0xfc, 0xce, 0x1c, 0xfc, 0xe3, 0x2a, 0x7c, 0xe2, 0x38, 0xf0, 0x49, 0xe3, 0x94, 0x33, 0x79, 0x1c, 0xfc, 0xe6, 0x71, 0xe0, 0x2d, 0x39, 0xf0, 0xbd, 0x2a, 0xbc, 0x35, 0x07, 0xde, 0xa5, 0xf2, 0xf9, 0xb1, 0x1c, 0xf8, 0x21, 0x15, 0xfe, 0x38, 0x86, 0x03, 0x06, 0xcc, 0x90, 0x8e, 0xf0, 0xd7, 0x21, 0xfd, 0xc8, 0x08, 0x1a, 0xc1, 0x82, 0x13, 0x71, 0x08, 0x19, 0x25, 0x86, 0x75, 0x61, 0x26, 0xe2, 0x8b, 0x40, 0x0e, 0x45, 0xf1, 0xf0, 0x5d, 0xd7, 0xad, 0x44, 0x03, 0x1b, 0xf3, 0xf1, 0x86, 0x6e, 0x25, 0xfa, 0x97, 0xbe, 0xb1, 0xc1, 0x62, 0x71, 0xc7, 0x4a, 0x0b, 0x44, 0x3c, 0x86, 0x95, 0x6d, 0xbd, 0xe0, 0x08, 0x65, 0xef, 0xe2, 0xe9, 0xc1, 0x05, 0xb6, 0xd2, 0x72, 0x15, 0xe1, 0x93, 0x16, 0x7c, 0x2d, 0x8b, 0x76, 0xfd, 0x6c, 0xe7, 0x92, 0x9b, 0x57, 0xd4, 0x24, 0xa6, 0x9c, 0x5b, 0xd5, 0x31, 0xed, 0xb1, 0xc4, 0xec, 0xcd, 0xdd, 0x03, 0xdf, 0xb8, 0xb8, 0xf3, 0x9c, 0xdb, 0x7e, 0x71, 0xed, 0x63, 0xdb, 0x7f, 0x7e, 0x60, 0x56, 0x6a, 0xe9, 0x8e, 0x59, 0x35, 0x22, 0x23, 0x9a, 0x34, 0x4b, 0x06, 0xab, 0x97, 0xde, 0xb4, 0xe8, 0x5a, 0xd8, 0xbc, 0xf6, 0xc0, 0xe2, 0x99, 0xdb, 0x16, 0xd5, 0xf4, 0x2d, 0x99, 0x73, 0x69, 0x6f, 0xa4, 0x67, 0xc7, 0x89, 0x35, 0x57, 0xfe, 0xf0, 0xe6, 0x3e, 0x28, 0x7d, 0x07, 0x4e, 0x81, 0xbd, 0x37, 0x7e, 0x7f, 0xe3, 0xc2, 0xaf, 0x5e, 0xbb, 0x2a, 0xd8, 0x6b, 0xf0, 0xba, 0xac, 0x6c, 0xcd, 0x77, 0x2e, 0x9d, 0xb5, 0x73, 0xa0, 0x19, 0x92, 0x39, 0xe1, 0x52, 0xd4, 0x76, 0xb4, 0x37, 0x01, 0x49, 0x7c, 0x1e, 0x1c, 0x0f, 0xf8, 0x59, 0xc8, 0xe8, 0x88, 0x71, 0x22, 0xc0, 0x47, 0x2e, 0xe4, 0x40, 0x98, 0x84, 0x6a, 0xf1, 0x66, 0x4c, 0x55, 0x93, 0xa0, 0xbc, 0xd0, 0x66, 0xf1, 0x09, 0x82, 0x3b, 0x81, 0x4f, 0x88, 0xe4, 0x46, 0x84, 0x01, 0x3e, 0x7d, 0xc1, 0xd7, 0xab, 0x99, 0xe3, 0x17, 0x7c, 0xcd, 0x5c, 0x5b, 0x9f, 0xe2, 0x07, 0x0c, 0x0e, 0x93, 0x66, 0xe1, 0x9d, 0x3f, 0xde, 0x74, 0x4a, 0xfa, 0x9d, 0xf4, 0xf6, 0xf5, 0x06, 0x57, 0x81, 0xae, 0x7a, 0xd1, 0xb5, 0x73, 0x4e, 0xf5, 0x5e, 0x3f, 0xd0, 0x68, 0xb4, 0x5a, 0xf9, 0x90, 0x9d, 0xfd, 0xb8, 0xe3, 0xa2, 0x4d, 0x17, 0xa5, 0x0f, 0xfc, 0xe8, 0xea, 0xb4, 0x56, 0xfa, 0x36, 0x9c, 0x6a, 0x85, 0xcc, 0x2f, 0x19, 0xa7, 0xd9, 0xe9, 0x33, 0x48, 0xf5, 0xdd, 0x17, 0xcf, 0xa9, 0xd5, 0xf1, 0x86, 0x4f, 0xdf, 0x17, 0xab, 0x16, 0xdf, 0x7a, 0xde, 0xf4, 0x0d, 0x8b, 0xe7, 0x54, 0x44, 0xfc, 0xb8, 0x3f, 0xe7, 0x8c, 0xbc, 0xcb, 0x3f, 0x4c, 0xe4, 0xef, 0x77, 0x54, 0xfe, 0xe0, 0x26, 0x90, 0x81, 0x3f, 0xa9, 0xc0, 0xf9, 0x29, 0xe3, 0xc0, 0xdb, 0xc6, 0x81, 0xb7, 0x67, 0xc3, 0xc9, 0x38, 0xa5, 0xf0, 0x8e, 0x1c, 0xfc, 0x93, 0x2a, 0xbc, 0x69, 0x1c, 0x78, 0x7a, 0x1c, 0xf8, 0x84, 0x71, 0xca, 0xef, 0xcc, 0xc1, 0x3f, 0xae, 0xc2, 0x9b, 0xc7, 0x81, 0xb7, 0xc0, 0x0b, 0xb3, 0xe0, 0x7b, 0x55, 0x78, 0xab, 0x82, 0x8f, 0xf6, 0xf3, 0x0f, 0x13, 0x9b, 0xd4, 0xdf, 0xc9, 0xfb, 0xf8, 0xf9, 0x19, 0xb8, 0xda, 0xde, 0x18, 0xe5, 0xcf, 0x19, 0x9e, 0xb7, 0x9d, 0xe5, 0x79, 0xfb, 0x58, 0xcf, 0xd5, 0x76, 0xc5, 0x28, 0xdf, 0x46, 0xbd, 0x7f, 0x52, 0x7d, 0xde, 0x74, 0x96, 0xe7, 0xe9, 0xb3, 0x3c, 0x9f, 0x70, 0x96, 0xfa, 0x3b, 0xc7, 0x7c, 0xff, 0xb8, 0xfa, 0xbc, 0xf9, 0x2c, 0xcf, 0x5b, 0xc6, 0x7c, 0xbe, 0x57, 0x7d, 0xde, 0x9a, 0xfd, 0x1c, 0xcd, 0x27, 0x0f, 0x01, 0xc0, 0xed, 0x45, 0x32, 0x65, 0x40, 0x02, 0xd3, 0x94, 0xae, 0xc7, 0xf7, 0xba, 0xf8, 0xa0, 0x77, 0x00, 0xf0, 0x3c, 0xd7, 0x8f, 0x2f, 0xcd, 0xe4, 0xd9, 0x02, 0x10, 0x3f, 0x44, 0x93, 0x11, 0x02, 0xe2, 0x2f, 0xe8, 0x34, 0xfa, 0x4d, 0x7e, 0xbd, 0x16, 0x18, 0xa0, 0x41, 0xa3, 0xb3, 0x27, 0x22, 0x34, 0x3c, 0x03, 0x13, 0x0a, 0x5a, 0xd4, 0x2b, 0xce, 0x20, 0xf1, 0xfd, 0x67, 0x6e, 0x99, 0xb5, 0x69, 0x5a, 0x30, 0x38, 0x75, 0xd3, 0x2c, 0xe9, 0x03, 0x68, 0xd8, 0xf1, 0xd4, 0x95, 0x2d, 0x93, 0xb6, 0x3f, 0x79, 0xa9, 0x14, 0x60, 0x8f, 0x0c, 0x97, 0x3d, 0x10, 0xee, 0x3c, 0xb7, 0xbe, 0xe9, 0xdc, 0xf6, 0xa0, 0xe4, 0x61, 0x6e, 0x6e, 0x59, 0xb3, 0x77, 0xce, 0xc0, 0xd7, 0x36, 0xb5, 0xf1, 0x87, 0xee, 0x97, 0x26, 0x51, 0xfd, 0x17, 0xd3, 0xe6, 0x22, 0xb1, 0xd3, 0xff, 0x40, 0xe3, 0x76, 0x83, 0x67, 0x08, 0xdc, 0x04, 0x80, 0xa6, 0x48, 0x64, 0x11, 0xfc, 0x03, 0x62, 0xdf, 0x9d, 0x62, 0xae, 0xcf, 0xc0, 0x49, 0x0e, 0x9e, 0x0f, 0x68, 0x0e, 0x1e, 0x19, 0x1e, 0x1d, 0x79, 0x8f, 0x0b, 0x11, 0xf8, 0x87, 0x14, 0x0e, 0x0e, 0x2a, 0xe5, 0xf3, 0xd3, 0x45, 0x06, 0xc1, 0x3f, 0xc2, 0xbe, 0x4a, 0x30, 0x05, 0x2f, 0x42, 0x50, 0x7a, 0x4e, 0x3a, 0x22, 0xfb, 0x2a, 0x51, 0x9c, 0x36, 0x05, 0x07, 0x1c, 0x86, 0x6b, 0xc6, 0xc6, 0x11, 0xbe, 0xae, 0xe2, 0x7c, 0x7b, 0xbc, 0x72, 0x84, 0x3f, 0xaa, 0x38, 0x8f, 0x2a, 0x38, 0x4c, 0x2e, 0x4e, 0x3b, 0x69, 0xef, 0x1f, 0x69, 0x7b, 0x61, 0x92, 0xdc, 0xd7, 0xf7, 0x8f, 0xfc, 0x81, 0xc7, 0x3e, 0xb9, 0x3a, 0xe0, 0x00, 0x65, 0xd8, 0x42, 0x92, 0x44, 0x42, 0xc2, 0x41, 0xbd, 0x97, 0x93, 0x8b, 0x45, 0x9c, 0x0d, 0x2a, 0xd5, 0xad, 0x58, 0x11, 0xd6, 0xf2, 0x3d, 0x2e, 0xa7, 0x5e, 0x0f, 0x40, 0xb0, 0xd8, 0x59, 0xe6, 0x2a, 0xd3, 0x3b, 0xf4, 0x0e, 0x8b, 0x19, 0xbd, 0xab, 0x0b, 0x6b, 0x45, 0x07, 0x9e, 0x05, 0xb1, 0xb5, 0x5d, 0x43, 0xac, 0x82, 0x24, 0x2c, 0x14, 0x61, 0x0d, 0xe7, 0xb4, 0x3a, 0xec, 0x8c, 0xc0, 0x85, 0x4a, 0xc2, 0x51, 0xd4, 0x89, 0xd6, 0xfa, 0x30, 0xac, 0xd3, 0xce, 0xb8, 0xea, 0xe8, 0xda, 0xb5, 0x47, 0xb7, 0xcd, 0xd0, 0x32, 0x86, 0xde, 0xad, 0x0f, 0xae, 0x5e, 0xf5, 0xe0, 0xb6, 0x3e, 0x03, 0xfc, 0xdd, 0xdd, 0x50, 0x78, 0x62, 0xf5, 0xea, 0x27, 0xa4, 0x4f, 0xef, 0xfe, 0xaa, 0xf4, 0xc9, 0x77, 0xd7, 0xac, 0xf9, 0x2e, 0x14, 0x99, 0x5d, 0x1b, 0x8f, 0x5f, 0xd1, 0xd9, 0x79, 0xc5, 0xf1, 0x8d, 0x5f, 0x3b, 0xff, 0xc1, 0xcd, 0xed, 0xed, 0x9b, 0x1f, 0x3c, 0x9f, 0xb9, 0xfa, 0x82, 0x53, 0xd2, 0x7b, 0x77, 0x7f, 0x4d, 0x7a, 0xff, 0xd4, 0xba, 0x75, 0xa7, 0xa0, 0xf1, 0x6b, 0x77, 0x43, 0xd3, 0xa9, 0x0b, 0x50, 0xfb, 0x18, 0xd4, 0x0f, 0xef, 0x91, 0x7e, 0x1b, 0xa6, 0xfd, 0x06, 0x24, 0xd2, 0x0f, 0x04, 0x8e, 0xe3, 0xa0, 0x53, 0x38, 0xf8, 0x36, 0xd2, 0x4e, 0x32, 0xf0, 0x8f, 0x55, 0xf8, 0xa3, 0xd9, 0x70, 0x12, 0x6b, 0x9f, 0xc2, 0x9f, 0x92, 0xe1, 0x0b, 0xd0, 0x78, 0xb9, 0x0a, 0xe3, 0xe3, 0xdb, 0x1f, 0x82, 0xff, 0x96, 0xd2, 0xcf, 0xc2, 0x52, 0x01, 0xe9, 0x92, 0x88, 0xef, 0xd8, 0x27, 0xce, 0xc2, 0xbd, 0x40, 0x9f, 0xc3, 0xcf, 0xc9, 0xf3, 0xda, 0x91, 0x77, 0xb9, 0xdf, 0xe2, 0xbe, 0x81, 0x1a, 0xda, 0x37, 0xe0, 0x91, 0xfc, 0xbe, 0x41, 0xbf, 0x01, 0xbf, 0x9a, 0xd4, 0x29, 0x11, 0x9f, 0x2a, 0x00, 0x7d, 0xa4, 0x6f, 0x70, 0x5c, 0xe1, 0x03, 0x48, 0x4f, 0xd5, 0x01, 0x17, 0x28, 0xc7, 0x51, 0x20, 0x4a, 0xc8, 0x92, 0x0c, 0x64, 0x31, 0x42, 0x3d, 0xc5, 0xd2, 0x0e, 0x5a, 0x95, 0xb9, 0x43, 0x1d, 0xe4, 0x7b, 0x3c, 0x6e, 0xbd, 0x1e, 0xe7, 0xb6, 0x71, 0x97, 0x7b, 0xca, 0xf5, 0x2e, 0xbd, 0xd3, 0x64, 0xd0, 0x8a, 0x40, 0x07, 0x75, 0x5a, 0x22, 0x4c, 0xe4, 0x9a, 0x3d, 0x01, 0x3d, 0x4a, 0xd8, 0x13, 0x26, 0xd4, 0xa0, 0x2c, 0xc1, 0x72, 0xdc, 0x0d, 0x24, 0x58, 0x8e, 0x25, 0xeb, 0xed, 0x55, 0x33, 0x9b, 0x9e, 0xfb, 0x6e, 0x7c, 0x6a, 0xca, 0x6f, 0x4b, 0x76, 0x35, 0x08, 0x4b, 0x6e, 0x58, 0x50, 0x66, 0x2d, 0xef, 0x6e, 0x1e, 0xba, 0xb2, 0x6d, 0xcd, 0xee, 0x99, 0xd2, 0x85, 0x3b, 0xfb, 0x7b, 0x82, 0x4d, 0x71, 0x97, 0xf4, 0x14, 0xf3, 0x8e, 0xf4, 0xac, 0x3b, 0xd1, 0x16, 0xf6, 0xd5, 0x94, 0xba, 0xaf, 0x89, 0x4c, 0x58, 0x90, 0x0a, 0x4e, 0xeb, 0xac, 0x37, 0xdb, 0xb7, 0x0c, 0x74, 0x6f, 0xe8, 0x0a, 0x23, 0xe2, 0xe5, 0xf8, 0xc8, 0x21, 0xec, 0xa3, 0x00, 0xad, 0x34, 0x46, 0x51, 0x94, 0xca, 0xa0, 0x6b, 0x64, 0x44, 0x08, 0x60, 0x3d, 0x04, 0x3a, 0xa8, 0x4f, 0x06, 0xbc, 0x31, 0x0b, 0xbe, 0x5d, 0x81, 0x83, 0x13, 0x18, 0x8e, 0xef, 0xc5, 0x47, 0xde, 0x63, 0xab, 0x48, 0x2c, 0xa2, 0xeb, 0xd2, 0x3a, 0x23, 0xe4, 0x78, 0x2f, 0x64, 0x19, 0xc5, 0x02, 0x3d, 0x99, 0x31, 0x18, 0xa7, 0x6e, 0x5e, 0x80, 0x07, 0x1c, 0xc3, 0x73, 0xd8, 0xc8, 0x3e, 0x13, 0xfd, 0x89, 0x68, 0xdb, 0xeb, 0x20, 0x49, 0xf4, 0xf7, 0x85, 0xf1, 0x37, 0x60, 0xef, 0x30, 0x2b, 0x09, 0x61, 0x5a, 0x9e, 0x28, 0x0b, 0x05, 0x8b, 0x0a, 0x9d, 0xf6, 0x12, 0x0d, 0x36, 0x73, 0xc9, 0x58, 0x2a, 0xa8, 0x79, 0xad, 0xd0, 0x52, 0xaf, 0xdc, 0xb3, 0x63, 0xfe, 0xe2, 0xd3, 0x22, 0xd5, 0xb6, 0x81, 0x5e, 0xc3, 0x4f, 0x6f, 0xba, 0xef, 0xa2, 0xc5, 0x7b, 0x96, 0x57, 0xdb, 0xe2, 0x93, 0x53, 0x4b, 0xd6, 0x77, 0xae, 0xbf, 0x79, 0x66, 0xd5, 0xca, 0x15, 0x0b, 0x42, 0x8d, 0x46, 0xb7, 0xad, 0xac, 0xe5, 0x9c, 0xf6, 0xba, 0x73, 0x9a, 0x8a, 0x36, 0x5f, 0x7e, 0xe1, 0x85, 0xeb, 0xb6, 0x78, 0xeb, 0x7a, 0x6b, 0x9b, 0xfb, 0x1a, 0x23, 0x46, 0x93, 0x4d, 0xcf, 0xee, 0xa9, 0x9f, 0x94, 0xec, 0x5e, 0x56, 0x5b, 0xd4, 0xd9, 0x9a, 0xb2, 0xf8, 0xb7, 0x2e, 0x99, 0xba, 0xa9, 0x37, 0x6e, 0x8d, 0xb6, 0x26, 0xa6, 0x59, 0x8b, 0xc3, 0xc5, 0xde, 0x8a, 0xf6, 0xc8, 0xb4, 0x79, 0xd7, 0xcc, 0x4a, 0x17, 0xa6, 0x62, 0x4e, 0x57, 0xa1, 0x4b, 0x6f, 0x21, 0xfc, 0x6a, 0x92, 0xee, 0x64, 0x1b, 0x11, 0xbf, 0xfa, 0xc0, 0x7b, 0x69, 0xab, 0x11, 0x8a, 0x9a, 0x72, 0x28, 0xf0, 0x6e, 0xb4, 0xaf, 0x09, 0xe1, 0xd8, 0x3a, 0x32, 0xdf, 0x1a, 0x81, 0x4e, 0xab, 0xd1, 0xea, 0x34, 0x59, 0x21, 0xb3, 0xd0, 0x14, 0xae, 0x11, 0x07, 0x80, 0x56, 0xab, 0xf0, 0x41, 0x10, 0x28, 0xdf, 0x88, 0x79, 0x74, 0x5e, 0x40, 0xaf, 0x2f, 0xf1, 0xfe, 0x86, 0xdc, 0xf7, 0xd3, 0x1d, 0xa3, 0x5e, 0x65, 0xf1, 0xe5, 0x1c, 0x73, 0xc6, 0x22, 0x40, 0xa6, 0x04, 0x9c, 0x99, 0x6e, 0x66, 0xcf, 0xf4, 0xa9, 0x9d, 0x1d, 0xed, 0xad, 0xf5, 0xb5, 0xd5, 0x95, 0xb1, 0x48, 0xa0, 0xc8, 0xef, 0x43, 0x7d, 0xa3, 0x1f, 0xa7, 0x6f, 0xa2, 0x31, 0x47, 0xb6, 0xe1, 0x00, 0xdd, 0xc4, 0x9f, 0xad, 0xbb, 0x66, 0x8d, 0xee, 0xae, 0x55, 0xd5, 0x55, 0xe9, 0x40, 0xa5, 0xd1, 0xe0, 0x15, 0xcc, 0xb2, 0x6f, 0x25, 0xfc, 0xf2, 0xbd, 0x67, 0xb1, 0xf7, 0x10, 0xa7, 0xca, 0x5e, 0x9b, 0x15, 0x3b, 0x5e, 0x3e, 0x0a, 0xc7, 0xef, 0x4a, 0x24, 0x13, 0x64, 0xec, 0x13, 0x99, 0x70, 0x52, 0xbf, 0xb6, 0x42, 0x39, 0x76, 0xe7, 0xc8, 0x08, 0xfb, 0x19, 0x91, 0x21, 0x97, 0xec, 0xd7, 0xf4, 0x64, 0x16, 0xfc, 0x88, 0x02, 0x07, 0x0f, 0x62, 0x38, 0x8e, 0x25, 0x8e, 0xe1, 0x48, 0x2c, 0xbd, 0x20, 0x9c, 0x0e, 0xda, 0x05, 0x06, 0x5f, 0xbc, 0xca, 0x01, 0xe5, 0xa1, 0x7a, 0x19, 0xe7, 0x05, 0x1e, 0x4b, 0xdc, 0xc2, 0xe3, 0x04, 0xef, 0xf8, 0xc4, 0xb3, 0xbe, 0x41, 0xb9, 0x7e, 0xc5, 0x1b, 0x4c, 0x41, 0x24, 0x09, 0x38, 0x52, 0xec, 0x67, 0xa7, 0x39, 0x4e, 0x67, 0xd3, 0x49, 0x49, 0x7d, 0xd8, 0xb5, 0xf9, 0x14, 0xc7, 0xd9, 0x6d, 0xf0, 0x39, 0x7d, 0xc4, 0x85, 0x53, 0x1e, 0x31, 0x07, 0xed, 0xd5, 0x1e, 0x47, 0xc4, 0x34, 0xbc, 0xdd, 0x1a, 0x60, 0x3f, 0x19, 0x5e, 0x6d, 0xaf, 0x76, 0x97, 0xfb, 0x98, 0xad, 0x96, 0x40, 0x16, 0x6d, 0x5d, 0x2a, 0x6d, 0x8f, 0xe5, 0xd0, 0xbc, 0x5d, 0x85, 0x9f, 0xc8, 0xc0, 0xb1, 0x8f, 0x13, 0x9a, 0x63, 0xdd, 0xb2, 0xff, 0xd9, 0x6e, 0x35, 0x0f, 0x04, 0x60, 0x3f, 0x03, 0x61, 0x7c, 0x9e, 0xeb, 0x37, 0x0b, 0x2c, 0x64, 0x21, 0x0e, 0x8c, 0x83, 0x86, 0x11, 0x18, 0xe2, 0x71, 0x8a, 0xc6, 0x55, 0xdd, 0x99, 0x1b, 0xc6, 0x30, 0x08, 0x05, 0xdd, 0x31, 0x8b, 0x6c, 0xbe, 0x81, 0x5b, 0x45, 0x9a, 0x84, 0x2f, 0x98, 0x69, 0xd8, 0xfa, 0x2c, 0x4f, 0x33, 0xdc, 0x3e, 0x1e, 0x1c, 0x13, 0x45, 0xe9, 0x88, 0xb6, 0xd0, 0xe6, 0xb4, 0xfa, 0xb5, 0xd2, 0xfd, 0x82, 0xe6, 0xd8, 0x3f, 0xd8, 0x4b, 0x4d, 0xb0, 0xcc, 0xe8, 0xd4, 0xe9, 0x1c, 0x46, 0x18, 0x31, 0x95, 0xda, 0xd9, 0xcf, 0x3e, 0xe7, 0x99, 0xeb, 0x8a, 0x7a, 0x02, 0xc3, 0xf7, 0x59, 0x6c, 0x36, 0x0b, 0xb3, 0x28, 0x30, 0xa3, 0x78, 0xf8, 0x32, 0xf6, 0x33, 0xab, 0x57, 0xd2, 0xb9, 0xeb, 0x7d, 0xbe, 0x7a, 0x37, 0xfc, 0xc8, 0x67, 0x91, 0x69, 0x65, 0x5f, 0x25, 0x39, 0xa8, 0x70, 0x46, 0x36, 0x0e, 0x41, 0x38, 0xb8, 0x9c, 0xdc, 0x82, 0x66, 0xd3, 0xa8, 0x07, 0x7a, 0x92, 0x31, 0x83, 0xf8, 0x1b, 0xc8, 0xb9, 0x80, 0xb1, 0x63, 0x3e, 0xfb, 0xea, 0x69, 0xa9, 0xf2, 0xd4, 0x29, 0xf8, 0x33, 0xcc, 0x5c, 0xf8, 0x73, 0xa9, 0x9c, 0xfd, 0x58, 0x1a, 0x82, 0x37, 0x23, 0xde, 0xcc, 0x44, 0xe5, 0x6e, 0x27, 0x3c, 0x8b, 0xd0, 0x71, 0xb1, 0x22, 0x93, 0x4b, 0x22, 0x4c, 0xe0, 0x65, 0xf2, 0x1c, 0xda, 0x41, 0x73, 0x71, 0xa1, 0x71, 0xb4, 0x85, 0xc0, 0x13, 0x14, 0xbf, 0x84, 0xe2, 0x23, 0x22, 0xd1, 0xde, 0x1e, 0x8f, 0x97, 0x20, 0xf1, 0xe7, 0x1a, 0xb9, 0x80, 0xfa, 0x4f, 0x74, 0x8d, 0xfc, 0x01, 0xce, 0x42, 0x3a, 0x9c, 0x11, 0x04, 0x09, 0xe5, 0xaa, 0x6d, 0x67, 0xb7, 0x92, 0x56, 0x6e, 0x1d, 0x8b, 0x28, 0x2f, 0x2a, 0xb4, 0x5b, 0x11, 0x8e, 0xd1, 0x49, 0xad, 0x38, 0xb1, 0xd0, 0x85, 0xf0, 0x3d, 0x34, 0x9b, 0xb5, 0xc5, 0xc3, 0xe2, 0x06, 0x93, 0x9c, 0xa0, 0x13, 0xae, 0x15, 0x74, 0x22, 0x17, 0xb7, 0x87, 0xaa, 0x0a, 0x0b, 0xab, 0xc2, 0x76, 0x7b, 0x18, 0x7f, 0x86, 0xec, 0xfc, 0xa1, 0xe1, 0xd3, 0xc9, 0x79, 0xf3, 0x17, 0xa5, 0x52, 0x8b, 0xe6, 0xcf, 0x4b, 0x32, 0xad, 0x9f, 0xfe, 0xbb, 0xaf, 0x2a, 0xec, 0x70, 0x84, 0xab, 0x7c, 0x32, 0x16, 0xa6, 0x73, 0xbb, 0xf4, 0x7b, 0xb8, 0x8b, 0x7d, 0x09, 0x8d, 0x85, 0x86, 0x10, 0xd6, 0xab, 0x9a, 0x81, 0x97, 0x79, 0x8d, 0xea, 0x43, 0xcc, 0x6b, 0xf2, 0xfd, 0xf3, 0x85, 0xec, 0x31, 0x78, 0x1e, 0x5a, 0x11, 0x2d, 0x40, 0x38, 0xce, 0x03, 0x58, 0x95, 0xb0, 0x55, 0xa0, 0x0e, 0x6d, 0x70, 0x39, 0x05, 0xd1, 0xc4, 0x38, 0xf1, 0xc7, 0xf7, 0xca, 0x8b, 0xdc, 0x7d, 0x13, 0x5a, 0x2d, 0x9c, 0xd6, 0x13, 0x31, 0xd8, 0x03, 0x26, 0x83, 0x9d, 0x3d, 0x36, 0xfd, 0x6b, 0xff, 0x70, 0x73, 0x53, 0x44, 0x5f, 0xac, 0x2b, 0xea, 0x5f, 0x33, 0x54, 0xef, 0x0a, 0x6b, 0x08, 0x5f, 0x90, 0xb4, 0x71, 0x53, 0xf9, 0xcf, 0x41, 0x0c, 0xa6, 0x47, 0x4e, 0x72, 0x6f, 0x43, 0x01, 0x5c, 0xb0, 0x0a, 0xfb, 0xba, 0x03, 0xf8, 0x6d, 0xee, 0x6d, 0x59, 0xb7, 0x5a, 0xc9, 0x1e, 0x86, 0x8b, 0xf8, 0xcf, 0x11, 0x35, 0x69, 0xb4, 0xfe, 0xbf, 0x9d, 0xfd, 0x14, 0xc8, 0xcf, 0x99, 0x7d, 0xb4, 0x0c, 0xfc, 0x1c, 0xe3, 0xc1, 0x69, 0x08, 0xcf, 0xaa, 0xe2, 0x61, 0xf2, 0x95, 0x17, 0x70, 0xbc, 0xff, 0x7b, 0x61, 0x10, 0xd1, 0xcf, 0x02, 0x1b, 0x3e, 0x3b, 0x85, 0x60, 0x13, 0x9a, 0xf9, 0xd6, 0xc0, 0x1e, 0x4b, 0x01, 0x83, 0x0d, 0x50, 0x53, 0x96, 0xd0, 0xce, 0x83, 0x07, 0x05, 0xf8, 0xf1, 0x3d, 0x38, 0x5e, 0xa0, 0x74, 0x2f, 0xbb, 0x05, 0xe1, 0x46, 0xe1, 0xd4, 0x5f, 0x60, 0xf9, 0x9f, 0xca, 0x54, 0x52, 0xff, 0x75, 0x46, 0xc7, 0x3f, 0xc6, 0x1c, 0x44, 0x65, 0xb8, 0xd3, 0x68, 0x15, 0x45, 0xb2, 0x3d, 0x97, 0xf4, 0x1d, 0x7a, 0x04, 0x67, 0xc8, 0x05, 0x21, 0x1d, 0x7a, 0x2b, 0xdf, 0xf4, 0xe9, 0xbf, 0x30, 0x07, 0xf7, 0x90, 0x77, 0x76, 0xb1, 0x37, 0xf0, 0x27, 0x39, 0x27, 0x7a, 0x27, 0x49, 0x73, 0x4a, 0xe8, 0xb3, 0x0c, 0x3d, 0xc8, 0x31, 0x6e, 0x6e, 0x31, 0xfd, 0x8f, 0x64, 0x0a, 0xda, 0xc5, 0x37, 0x7e, 0xfa, 0x03, 0xce, 0xb9, 0x07, 0x97, 0x03, 0xa4, 0x57, 0x59, 0xfb, 0xc8, 0xc0, 0x17, 0xb0, 0x13, 0xe5, 0x1d, 0xc1, 0x3a, 0xd6, 0xfe, 0xf9, 0xdb, 0x77, 0x5d, 0x73, 0xcd, 0x9f, 0xf2, 0x1e, 0x58, 0xca, 0xda, 0xc1, 0xab, 0x5f, 0xe0, 0xbd, 0x88, 0xfc, 0xde, 0xab, 0x7f, 0x5a, 0x7d, 0xff, 0xc3, 0xef, 0xad, 0x85, 0xc7, 0xe1, 0xfd, 0xcc, 0xfb, 0xc0, 0xa6, 0xf6, 0xe5, 0x7c, 0x40, 0xa3, 0xb3, 0x00, 0xd8, 0x5b, 0x1a, 0x66, 0x44, 0xd4, 0x05, 0x79, 0x72, 0xb6, 0xd6, 0x93, 0x68, 0x0a, 0x04, 0x5b, 0xca, 0x3d, 0x9e, 0xf2, 0x96, 0x60, 0xa0, 0x29, 0xe1, 0x61, 0x66, 0x2b, 0xbf, 0x82, 0x4d, 0xf8, 0xb3, 0x09, 0xbd, 0x7c, 0x11, 0x5b, 0x0d, 0x1f, 0xe6, 0x36, 0x8e, 0x5b, 0xae, 0x6e, 0x74, 0xb9, 0x17, 0xa1, 0x72, 0x83, 0xc1, 0x96, 0x84, 0xc7, 0x93, 0xc0, 0x25, 0x25, 0x3c, 0xec, 0x4f, 0xf3, 0xcb, 0xe5, 0xc0, 0xa4, 0x91, 0x77, 0x85, 0x95, 0xfc, 0x43, 0x40, 0x0b, 0x1a, 0x40, 0x37, 0x5a, 0xc0, 0xff, 0x90, 0xd6, 0xf9, 0x91, 0xe0, 0x18, 0xa1, 0x56, 0x54, 0xb2, 0x6b, 0x36, 0x00, 0x5e, 0x60, 0xd0, 0xf4, 0x37, 0x64, 0x80, 0x8c, 0x28, 0x30, 0xe2, 0x7a, 0x34, 0x45, 0xb1, 0x3a, 0x91, 0x45, 0x4b, 0x3e, 0xd0, 0xe9, 0xb5, 0xba, 0x41, 0xa2, 0x85, 0xc9, 0x06, 0xb8, 0x48, 0x9d, 0x45, 0x6a, 0xae, 0x92, 0xec, 0x04, 0x07, 0x61, 0x68, 0x39, 0xeb, 0xeb, 0xe4, 0x45, 0x39, 0x56, 0xe0, 0xf9, 0xb9, 0x45, 0x20, 0xbd, 0xe3, 0x4b, 0xbe, 0x4d, 0xc2, 0x33, 0xe9, 0xf5, 0x24, 0xcf, 0x3b, 0x8e, 0x78, 0xd3, 0x9f, 0xf6, 0xeb, 0x75, 0xe7, 0x2e, 0x98, 0xdd, 0x37, 0x6d, 0x4a, 0x7b, 0x2b, 0xd6, 0x3b, 0x4a, 0x02, 0x3e, 0x8f, 0xc5, 0xac, 0x6b, 0xd0, 0x37, 0x84, 0x4a, 0x8c, 0xd8, 0x5a, 0x8f, 0x44, 0x57, 0x23, 0x06, 0x4b, 0x35, 0xc5, 0x79, 0x6e, 0x62, 0xb9, 0xe1, 0x05, 0x89, 0xde, 0xd1, 0xd0, 0x0e, 0x61, 0x9e, 0x15, 0xb9, 0xa8, 0x9a, 0xbe, 0x60, 0x85, 0x85, 0xfb, 0xa5, 0xbe, 0xd0, 0xbb, 0xe7, 0xb3, 0x5f, 0xeb, 0x8b, 0x1c, 0xb7, 0xc0, 0x7b, 0xf5, 0xda, 0xd6, 0xe4, 0xf6, 0x85, 0xb3, 0xb7, 0x2f, 0xaa, 0xae, 0x5b, 0xb6, 0x63, 0xd6, 0xac, 0x2b, 0xe3, 0x35, 0xbc, 0xd1, 0x60, 0xf0, 0x84, 0xeb, 0xe3, 0x15, 0x33, 0x1a, 0x8a, 0x83, 0x4d, 0x33, 0xca, 0xbd, 0xf1, 0x70, 0x71, 0x81, 0x60, 0xe0, 0x0c, 0xf5, 0x03, 0xbb, 0xe6, 0xcc, 0xd9, 0x35, 0x50, 0xaf, 0x7c, 0xde, 0x51, 0xdc, 0x34, 0x3b, 0x95, 0x9a, 0xdd, 0x54, 0xbc, 0x79, 0xf5, 0xea, 0xcd, 0xfc, 0x15, 0x46, 0xdb, 0xa7, 0xdb, 0x4d, 0x16, 0x76, 0xbb, 0x76, 0x42, 0x53, 0x4b, 0xe3, 0xe0, 0xce, 0x59, 0xb3, 0x77, 0x0d, 0x36, 0xd4, 0x27, 0xa6, 0x8a, 0x3a, 0x8b, 0xc3, 0x12, 0x6c, 0x5f, 0xd4, 0xdc, 0xbc, 0xb0, 0x3d, 0xc8, 0x1b, 0x6c, 0x26, 0x5e, 0x18, 0xfe, 0x4d, 0xdf, 0x0d, 0x2b, 0x9b, 0x9a, 0x56, 0xde, 0xd0, 0xd7, 0xb7, 0x13, 0x7f, 0xee, 0xec, 0x6b, 0x5a, 0xd8, 0x51, 0x52, 0xd2, 0xb1, 0xb0, 0x69, 0xd3, 0xd5, 0x57, 0xa3, 0x01, 0x73, 0x2b, 0x78, 0x8d, 0x3d, 0xc4, 0x2d, 0x01, 0x05, 0x68, 0xce, 0x35, 0x68, 0xd1, 0x9c, 0x0b, 0x05, 0xd1, 0xe9, 0xe2, 0x8b, 0xa0, 0x1f, 0xc2, 0x3a, 0x48, 0x1c, 0x6b, 0x9f, 0x29, 0xab, 0xad, 0x2d, 0x83, 0x87, 0x1d, 0x96, 0xcf, 0x3f, 0x62, 0x51, 0x05, 0xcc, 0x49, 0x87, 0xc1, 0xe0, 0x78, 0x41, 0x5f, 0x68, 0xd9, 0x0d, 0x0f, 0xf4, 0xc1, 0x03, 0xbb, 0x2d, 0x85, 0x7a, 0x7a, 0x4f, 0xfb, 0x36, 0x2a, 0xeb, 0x23, 0x54, 0x96, 0x09, 0x95, 0x65, 0x62, 0x70, 0x59, 0x0e, 0x58, 0x57, 0xdb, 0x10, 0xa9, 0x6f, 0x88, 0xc6, 0x22, 0xd1, 0x36, 0xc8, 0x7e, 0xf4, 0xf9, 0x47, 0x51, 0x78, 0xa4, 0xb4, 0xae, 0xae, 0x54, 0x9a, 0x13, 0x65, 0xfe, 0xb5, 0x4f, 0x3a, 0x7f, 0x77, 0xd8, 0xfe, 0x02, 0x29, 0xce, 0x1e, 0xa6, 0x77, 0x93, 0x68, 0x5f, 0x2b, 0x6c, 0x13, 0xdb, 0xd0, 0xbc, 0xf8, 0x06, 0xd9, 0x77, 0x7d, 0x13, 0xee, 0x24, 0xfb, 0x2e, 0x26, 0x6b, 0xdf, 0x9c, 0x8f, 0x73, 0x78, 0x5c, 0x9c, 0x90, 0x82, 0x03, 0x7f, 0x0e, 0xbf, 0x32, 0x36, 0x8e, 0xf0, 0xae, 0x5a, 0xce, 0x23, 0xf0, 0xc0, 0x68, 0x1c, 0x69, 0x1f, 0xc2, 0x89, 0x53, 0x1c, 0xe2, 0x67, 0x7e, 0x1b, 0xa1, 0xf3, 0xab, 0x23, 0xff, 0x25, 0x1c, 0xc1, 0x67, 0x00, 0xf0, 0x4d, 0x99, 0xce, 0xee, 0x51, 0xfb, 0xfb, 0x7c, 0x9c, 0xc3, 0xe3, 0xe2, 0x84, 0x14, 0x1c, 0x44, 0x67, 0xff, 0xd8, 0x38, 0xc2, 0xbb, 0x6a, 0x39, 0x8f, 0xc0, 0xf9, 0xa3, 0x71, 0x10, 0x9d, 0x47, 0x84, 0x38, 0xc5, 0x21, 0x74, 0xf6, 0xab, 0x31, 0xf6, 0xdf, 0x24, 0xfa, 0xc1, 0x5b, 0xb2, 0x0e, 0xf6, 0xe0, 0x98, 0xf0, 0x07, 0x73, 0xe0, 0xa7, 0x15, 0x38, 0x7c, 0x2e, 0x2b, 0x56, 0xff, 0x9b, 0xf8, 0xcc, 0x48, 0xc6, 0x3f, 0xae, 0xe0, 0x4b, 0xfb, 0xb8, 0x37, 0xb9, 0x7d, 0x2a, 0xfe, 0xeb, 0x32, 0x7c, 0xeb, 0xc8, 0xbb, 0xbc, 0x9f, 0xff, 0x21, 0x82, 0xff, 0x96, 0xd0, 0x7c, 0x04, 0xec, 0x05, 0x45, 0x40, 0x83, 0x68, 0x3e, 0x56, 0xa4, 0xc4, 0x59, 0xcd, 0xc7, 0x79, 0x70, 0x5c, 0x9c, 0x5f, 0x2b, 0x38, 0x88, 0x9e, 0x9b, 0xc7, 0xc1, 0xf9, 0xa6, 0x5a, 0xce, 0xf1, 0xb1, 0xca, 0x91, 0xf6, 0xf1, 0x7e, 0x42, 0xe7, 0x6f, 0xa9, 0xdf, 0xfb, 0x15, 0x64, 0xcc, 0x0e, 0x1f, 0x15, 0xaf, 0x65, 0x66, 0x6b, 0xdc, 0x67, 0x5a, 0x6f, 0x21, 0x5a, 0x26, 0x87, 0x8f, 0x72, 0xf7, 0x7e, 0xb6, 0x48, 0xe3, 0xde, 0x4f, 0xde, 0x11, 0x04, 0x66, 0xb6, 0x70, 0xc7, 0x17, 0x7b, 0x47, 0xb8, 0x63, 0xff, 0x7f, 0xb3, 0x1e, 0xd8, 0x25, 0x38, 0xe1, 0xf3, 0xe2, 0x8f, 0xe8, 0xba, 0x5e, 0x91, 0xbd, 0xae, 0xaf, 0x3d, 0xe3, 0xba, 0x0e, 0x91, 0xa6, 0x01, 0xbb, 0x76, 0xef, 0x16, 0x7f, 0x24, 0x75, 0x91, 0x72, 0xb8, 0x83, 0xf0, 0x79, 0x61, 0xe6, 0x19, 0xeb, 0x96, 0x5f, 0x11, 0x66, 0x4a, 0xc4, 0x4e, 0xf5, 0xcf, 0x54, 0x37, 0x03, 0x1e, 0x17, 0x9c, 0xcc, 0x6a, 0x52, 0x8e, 0x08, 0x6a, 0x69, 0x49, 0x46, 0x96, 0xac, 0x9d, 0x58, 0x7b, 0x26, 0x65, 0xd9, 0xb0, 0x9d, 0x24, 0x2a, 0x0c, 0x03, 0x48, 0x0a, 0x66, 0x5c, 0xdc, 0xa3, 0x38, 0xcc, 0x15, 0x36, 0x50, 0xc5, 0x6a, 0x13, 0x2e, 0xf4, 0xf1, 0x9b, 0x6e, 0x82, 0xc1, 0x9b, 0x6e, 0xc2, 0xe5, 0xc2, 0x47, 0xe5, 0xb2, 0xb9, 0x83, 0xcc, 0x6a, 0xd2, 0x2e, 0x11, 0xf8, 0xd3, 0xde, 0x31, 0x8a, 0x19, 0xa7, 0x10, 0xdc, 0x4a, 0x52, 0xc8, 0xdf, 0x3c, 0x7d, 0x23, 0xcf, 0x73, 0xbb, 0x99, 0x0b, 0xd1, 0x1e, 0x4a, 0x0b, 0x1c, 0x20, 0xa2, 0xc4, 0xeb, 0xe6, 0x59, 0x8e, 0x05, 0xe0, 0x7c, 0xd8, 0xa3, 0xd3, 0x41, 0xa0, 0x73, 0xe8, 0x1c, 0xd6, 0x02, 0x81, 0x03, 0x5a, 0xa8, 0xa5, 0xfe, 0x24, 0xd4, 0x6f, 0x28, 0xc6, 0x86, 0xd0, 0x7a, 0x24, 0x7f, 0x85, 0xb7, 0x87, 0x42, 0xc6, 0xeb, 0x67, 0xb9, 0x8a, 0x8c, 0x3f, 0x78, 0xc4, 0xe2, 0xb1, 0x9a, 0x35, 0xc7, 0x17, 0xbb, 0xfc, 0xa6, 0x0f, 0xf9, 0x23, 0x2e, 0xd7, 0x7b, 0x46, 0x0b, 0x64, 0x04, 0x83, 0x45, 0x4f, 0xbe, 0x60, 0x9f, 0xac, 0xfb, 0x04, 0x1d, 0xb3, 0x47, 0xf4, 0xa1, 0x3a, 0x6d, 0xd8, 0xa7, 0x86, 0x67, 0x50, 0x6d, 0x58, 0xc9, 0x20, 0x5f, 0xb0, 0x0d, 0x33, 0xa6, 0xbd, 0x57, 0x8f, 0xea, 0x2e, 0x30, 0xe9, 0x6c, 0x7a, 0xdb, 0xe8, 0xba, 0x2b, 0x61, 0x48, 0xfd, 0x06, 0xef, 0x0e, 0x25, 0x4c, 0xb7, 0x2e, 0x71, 0xf9, 0x0a, 0x7e, 0xf2, 0x66, 0x28, 0x69, 0x79, 0x7c, 0xad, 0x2b, 0x68, 0xf9, 0x4c, 0xf4, 0xb9, 0x5c, 0xbf, 0x29, 0x30, 0xbd, 0xef, 0x76, 0xfe, 0xc6, 0x6c, 0x7e, 0x1f, 0xf1, 0x6a, 0x2a, 0x6a, 0xe7, 0xbf, 0x0b, 0xb7, 0x90, 0xd3, 0x4c, 0xec, 0xc7, 0xa3, 0x1c, 0xea, 0x30, 0x38, 0x82, 0x2a, 0xd2, 0x13, 0x21, 0xb1, 0xac, 0x47, 0xfa, 0x72, 0xb8, 0xd0, 0x6b, 0x29, 0x30, 0xe8, 0x04, 0x1e, 0x38, 0xa0, 0x43, 0x54, 0x02, 0xd3, 0xa5, 0xe4, 0x15, 0x19, 0x7d, 0x31, 0x31, 0xa3, 0xb2, 0x90, 0xde, 0xbc, 0xf0, 0xca, 0x99, 0xa1, 0x5d, 0x8e, 0x8a, 0xe9, 0xa9, 0xd4, 0xf4, 0x0a, 0xc7, 0xae, 0xd0, 0xcc, 0x2b, 0x63, 0xf6, 0x50, 0x12, 0x69, 0x3d, 0x41, 0xbb, 0x3d, 0x88, 0xb4, 0x9f, 0x64, 0xc8, 0xce, 0xed, 0x4e, 0x74, 0xaf, 0x68, 0x0c, 0xd4, 0xc7, 0x1c, 0x8e, 0x58, 0x7d, 0xa0, 0x71, 0x45, 0x77, 0x02, 0xde, 0xe0, 0x29, 0x2f, 0xb1, 0xdb, 0x4b, 0xd0, 0xf3, 0x0a, 0xfc, 0x59, 0x41, 0xef, 0xda, 0x90, 0xfe, 0xf1, 0x9c, 0x86, 0x41, 0x9c, 0x21, 0xd6, 0xc9, 0x3c, 0x76, 0xed, 0x40, 0x3b, 0x57, 0x92, 0x78, 0x07, 0xa9, 0x3d, 0x03, 0x88, 0x6c, 0x86, 0x9b, 0x4f, 0x7c, 0x6a, 0x04, 0xec, 0xfd, 0xd1, 0xeb, 0xb0, 0x07, 0x03, 0x45, 0x85, 0x5e, 0x8f, 0x3d, 0xe2, 0x88, 0x94, 0x86, 0x45, 0x2c, 0x22, 0x79, 0xe4, 0xb1, 0xb4, 0x01, 0x24, 0xad, 0x0e, 0x26, 0x9f, 0xb1, 0xe6, 0x13, 0x77, 0xcd, 0xc2, 0xcb, 0x7b, 0x82, 0x5b, 0x97, 0xac, 0x5e, 0xbd, 0x64, 0x6b, 0xb0, 0xe7, 0x72, 0xf1, 0x3a, 0x4f, 0x12, 0x3f, 0x43, 0x38, 0x15, 0xf8, 0xb3, 0x42, 0x5a, 0x55, 0x36, 0x6d, 0x59, 0x63, 0x67, 0x4f, 0x4f, 0x67, 0xe3, 0xb2, 0x69, 0x65, 0x74, 0x2d, 0xbe, 0x44, 0xf0, 0x30, 0x0f, 0x88, 0x65, 0x88, 0x9f, 0xf1, 0x74, 0x0c, 0x67, 0x7a, 0x84, 0x17, 0x72, 0x38, 0x4e, 0x07, 0x98, 0xcf, 0x11, 0x3b, 0x7c, 0xd4, 0x14, 0xa6, 0x17, 0x6d, 0xb5, 0x54, 0x56, 0xf2, 0x8a, 0x2b, 0x52, 0x48, 0xd1, 0x60, 0xf0, 0x35, 0x1f, 0xbc, 0xb9, 0xf7, 0xfc, 0x09, 0xfe, 0x5d, 0xc6, 0x60, 0x43, 0x3c, 0xde, 0x10, 0x34, 0xee, 0xf2, 0x4f, 0x38, 0x5f, 0x2c, 0x4b, 0x74, 0x0f, 0x34, 0x28, 0x9c, 0x6a, 0x18, 0xe8, 0x26, 0x7b, 0xb7, 0x4b, 0xb8, 0x3b, 0x99, 0x07, 0xf8, 0xc3, 0xa8, 0xbe, 0x70, 0x3a, 0x48, 0xeb, 0x23, 0x3e, 0x8c, 0xc4, 0xfb, 0x06, 0x5b, 0x68, 0xff, 0x37, 0x6a, 0xe2, 0x0f, 0x8f, 0xae, 0xe9, 0x7f, 0x79, 0xdb, 0x60, 0x98, 0x7f, 0x96, 0xb1, 0x89, 0x7b, 0x80, 0x07, 0xd4, 0x1c, 0xf7, 0x40, 0x30, 0x95, 0xaa, 0xdd, 0x7a, 0x59, 0xbf, 0x87, 0x6b, 0x01, 0x99, 0x7c, 0x55, 0x77, 0xc7, 0x85, 0x98, 0xbe, 0xde, 0xfe, 0x47, 0xc2, 0x5d, 0x64, 0xf2, 0x25, 0xb9, 0x98, 0xda, 0xd9, 0x06, 0xa1, 0x84, 0xe6, 0x65, 0xc2, 0xe6, 0xe4, 0x58, 0x85, 0x9b, 0x65, 0xd0, 0xf0, 0x1a, 0xb7, 0xc7, 0x85, 0xfe, 0xe5, 0x6b, 0xd3, 0xe9, 0xfa, 0xd6, 0xb6, 0xf9, 0x7d, 0xfc, 0x6f, 0xf5, 0x26, 0xde, 0x28, 0x68, 0x7d, 0xe1, 0x84, 0x07, 0x4e, 0xaa, 0xab, 0xec, 0x9c, 0x33, 0xa1, 0xf7, 0xb6, 0x89, 0x84, 0x06, 0x6e, 0x16, 0x63, 0x13, 0x0a, 0x10, 0x0d, 0x85, 0x69, 0x0f, 0x26, 0x22, 0xbf, 0xbe, 0xff, 0x46, 0x6d, 0xdc, 0xf9, 0xda, 0x82, 0xb1, 0x6a, 0xfb, 0xab, 0xd4, 0xf7, 0x7f, 0x90, 0xc7, 0x26, 0xfe, 0x59, 0x38, 0x8c, 0xda, 0xec, 0xc3, 0xeb, 0xb9, 0x5b, 0x59, 0xcf, 0x17, 0xe6, 0xae, 0xe7, 0x59, 0xdb, 0xc8, 0xfe, 0x47, 0xa6, 0x44, 0x08, 0x01, 0xc4, 0x7f, 0x9a, 0xd4, 0x8a, 0x66, 0xad, 0x38, 0x8c, 0x15, 0xb1, 0x2e, 0x7c, 0x2c, 0x2b, 0xc2, 0x47, 0xfa, 0xe6, 0xb5, 0xb5, 0xd6, 0x75, 0x5a, 0x8a, 0x13, 0x1e, 0x5e, 0xcb, 0x8b, 0x6e, 0xaf, 0x5b, 0xc3, 0x8b, 0x7a, 0xfe, 0xd9, 0x89, 0xb7, 0xf5, 0x4e, 0x98, 0xd3, 0x59, 0xe9, 0x4d, 0x06, 0xac, 0xd0, 0x93, 0x08, 0xfb, 0xb4, 0x82, 0x91, 0xc7, 0x11, 0x56, 0x10, 0x0d, 0xdc, 0x2c, 0x38, 0x8c, 0xda, 0xec, 0xc3, 0xab, 0xa1, 0x6d, 0x8c, 0x36, 0x7b, 0xdd, 0x93, 0x18, 0x7c, 0x0e, 0x95, 0x5b, 0x95, 0x59, 0xa5, 0x01, 0x0e, 0xab, 0xb5, 0x79, 0x3c, 0xb8, 0xb6, 0x42, 0x4a, 0x03, 0x7f, 0x77, 0x4e, 0x6d, 0x66, 0x9d, 0x97, 0x12, 0xf1, 0xb7, 0xd1, 0x6e, 0x70, 0x50, 0xd2, 0x30, 0x9b, 0x46, 0x7e, 0x84, 0xb4, 0x80, 0x92, 0x74, 0x31, 0x87, 0x56, 0x0b, 0xa4, 0x11, 0x2c, 0x26, 0x21, 0x25, 0xe8, 0xf9, 0x10, 0x51, 0xa8, 0x45, 0xa4, 0x0b, 0xe0, 0x93, 0x5b, 0xde, 0x12, 0xaa, 0x0b, 0xa2, 0xbf, 0xcc, 0xa6, 0x1b, 0xa5, 0x87, 0x60, 0x6f, 0xad, 0xd4, 0x7a, 0xcf, 0xd6, 0x7b, 0xe8, 0xbc, 0x97, 0x29, 0x07, 0x8d, 0x19, 0x7c, 0x9b, 0xc4, 0xc0, 0xc5, 0xd8, 0xc5, 0xb1, 0x9f, 0xda, 0xbd, 0x2a, 0xca, 0x04, 0xd2, 0x23, 0x43, 0x96, 0x94, 0x23, 0xc8, 0x6c, 0x92, 0x1e, 0xba, 0xf1, 0x46, 0xd8, 0x2b, 0x1d, 0xbe, 0x07, 0x9e, 0xbe, 0x07, 0xbf, 0x2f, 0x70, 0xcc, 0x26, 0xf1, 0x07, 0x68, 0x6d, 0xf5, 0xa4, 0x49, 0xf0, 0x8b, 0x0b, 0x15, 0x1f, 0x9a, 0xb5, 0x38, 0x7d, 0x31, 0x31, 0xec, 0x83, 0x21, 0x0b, 0xaa, 0x9d, 0x56, 0xfe, 0xa2, 0xc0, 0xc1, 0xc9, 0xdb, 0xe4, 0xba, 0xb9, 0xeb, 0x98, 0x4d, 0x42, 0x17, 0x7a, 0xb7, 0x28, 0xed, 0xa3, 0xef, 0x62, 0xe7, 0xd6, 0xb9, 0x72, 0x33, 0x18, 0xac, 0xcb, 0x8c, 0x2e, 0x80, 0xbb, 0x8e, 0x16, 0xf0, 0xd7, 0xad, 0xfb, 0x56, 0xc4, 0xb7, 0x2d, 0x84, 0x6f, 0xd8, 0xd7, 0x9e, 0x81, 0xcc, 0x26, 0x8e, 0x65, 0xa8, 0x53, 0x3b, 0x8b, 0x8f, 0x73, 0xce, 0xc5, 0x48, 0x33, 0x73, 0x7a, 0x01, 0x3a, 0x82, 0xc4, 0x46, 0x90, 0xd9, 0x22, 0x79, 0xbf, 0xb9, 0x5b, 0xba, 0x6e, 0xeb, 0x56, 0x38, 0x45, 0xfa, 0x4e, 0x4e, 0x1f, 0x04, 0xd2, 0x7e, 0x3c, 0x62, 0x2e, 0x04, 0xb8, 0x3f, 0xe7, 0x92, 0x7e, 0x60, 0xf0, 0xd9, 0x9f, 0xaa, 0xd4, 0xf1, 0x34, 0x40, 0x2b, 0xa2, 0xe7, 0xfe, 0xdd, 0xf7, 0xc3, 0x17, 0x8f, 0x4b, 0xdf, 0x25, 0x04, 0x65, 0xca, 0xd0, 0x93, 0x32, 0x38, 0x1c, 0x21, 0x69, 0x31, 0x71, 0xa8, 0x26, 0x3a, 0x27, 0xed, 0x49, 0x35, 0x95, 0x34, 0xcd, 0x7c, 0x55, 0x97, 0x72, 0xa4, 0x48, 0x39, 0xe8, 0x3f, 0xd4, 0x9f, 0x68, 0x48, 0xa8, 0x63, 0xe2, 0x30, 0x2a, 0xab, 0x4c, 0x39, 0x73, 0x04, 0xca, 0x98, 0xa2, 0xda, 0xb8, 0x0b, 0x6d, 0x29, 0x0e, 0xc2, 0x17, 0xa5, 0xe8, 0x2f, 0xb6, 0x22, 0xdc, 0xf5, 0xd2, 0x0d, 0x4c, 0xe7, 0xc8, 0xdb, 0xc0, 0x89, 0xcf, 0xb1, 0x74, 0xf8, 0x56, 0x9f, 0xa8, 0xa9, 0x9b, 0xb2, 0x12, 0xa4, 0xc7, 0x23, 0x84, 0x91, 0x7c, 0x6a, 0x54, 0x9e, 0x8f, 0xfa, 0x3a, 0x78, 0x74, 0x5f, 0x51, 0xeb, 0xa2, 0xb6, 0xee, 0x8b, 0x82, 0x8b, 0x74, 0x26, 0x4e, 0x67, 0x76, 0x04, 0x7c, 0xe5, 0x29, 0xfb, 0xda, 0xea, 0x85, 0x93, 0xe3, 0x3d, 0x73, 0xc3, 0x1c, 0x74, 0x78, 0x1d, 0xed, 0x0d, 0x98, 0xa6, 0x67, 0x50, 0x3d, 0xff, 0x40, 0xea, 0x41, 0x1a, 0xab, 0x81, 0xfa, 0xbe, 0xb1, 0x90, 0xdd, 0x84, 0xd9, 0xb5, 0x09, 0xd0, 0x15, 0x9a, 0x19, 0x62, 0x7a, 0x1c, 0xe1, 0x54, 0x09, 0x61, 0x54, 0x5e, 0xac, 0x5a, 0x7c, 0xbb, 0x93, 0x82, 0x8b, 0x6b, 0x13, 0xbe, 0x80, 0xa3, 0x40, 0xc7, 0x19, 0x75, 0x8b, 0x83, 0x17, 0x77, 0xb7, 0x2f, 0x6c, 0xf5, 0xdf, 0x7a, 0x65, 0x43, 0x3b, 0xaa, 0x05, 0x72, 0xe1, 0xb9, 0x3d, 0xf1, 0xc9, 0x0b, 0xab, 0xd7, 0xd2, 0xb3, 0xee, 0x93, 0x52, 0x35, 0xb3, 0x64, 0xe4, 0x28, 0xc9, 0x4f, 0xd9, 0x41, 0xad, 0xf9, 0x6c, 0x8a, 0xb7, 0xbd, 0xd2, 0xb6, 0xf3, 0x20, 0x49, 0x71, 0xaf, 0x42, 0xb1, 0x63, 0x36, 0x50, 0xfd, 0xb2, 0xfb, 0x1f, 0x0d, 0x47, 0x4b, 0xc3, 0x44, 0x13, 0xcf, 0x3b, 0x38, 0x6b, 0xc8, 0x4a, 0x77, 0x78, 0x32, 0xd8, 0xd8, 0x55, 0x16, 0xef, 0x6e, 0x2a, 0x29, 0x69, 0xea, 0x8e, 0x97, 0x75, 0x35, 0x06, 0xd7, 0xd5, 0x56, 0x26, 0xea, 0xeb, 0x13, 0x95, 0xb5, 0xd2, 0xef, 0x13, 0x53, 0x6b, 0x0a, 0x0b, 0x6b, 0xa6, 0x26, 0xe2, 0x53, 0xaa, 0x0b, 0x0b, 0xab, 0xa7, 0xa0, 0x75, 0xbe, 0x21, 0x9e, 0xac, 0xab, 0xa3, 0x7b, 0x29, 0xa9, 0x0e, 0x3e, 0x3f, 0xf2, 0x3b, 0xba, 0x97, 0x2a, 0x52, 0x96, 0x9a, 0x7e, 0x85, 0xaa, 0x71, 0xf7, 0x52, 0xbc, 0xbc, 0x97, 0x72, 0xd0, 0x3d, 0x19, 0xa8, 0x91, 0x8e, 0x30, 0x2f, 0x8c, 0x7c, 0x1b, 0x8d, 0x3d, 0x2c, 0x43, 0x48, 0x27, 0xc7, 0xb7, 0xfd, 0x99, 0x21, 0x23, 0x3b, 0x54, 0xd6, 0x61, 0xf1, 0x0f, 0x32, 0x2f, 0xec, 0x90, 0x06, 0x77, 0x30, 0xf7, 0x49, 0x47, 0x6e, 0xbf, 0x1d, 0x1a, 0xc0, 0x9f, 0xe9, 0xfd, 0x67, 0xc9, 0xfb, 0x01, 0x70, 0xe1, 0x09, 0x11, 0xbf, 0x2c, 0x1f, 0x0a, 0x3a, 0x45, 0x01, 0x09, 0x12, 0xcb, 0x32, 0xab, 0xc8, 0x28, 0x5e, 0x04, 0x69, 0x91, 0xbe, 0xee, 0x63, 0x85, 0x38, 0x85, 0x32, 0x7d, 0xca, 0x6c, 0xca, 0x7d, 0x98, 0xf6, 0x65, 0xc1, 0x51, 0xb7, 0xcc, 0x57, 0x1f, 0xb3, 0xa0, 0x4f, 0x36, 0xa2, 0x0c, 0x80, 0x40, 0xc8, 0x16, 0xb2, 0x85, 0x89, 0x11, 0x25, 0x4f, 0x8c, 0x28, 0xa3, 0xd4, 0x86, 0xb2, 0x0d, 0xe7, 0x8f, 0x77, 0xa2, 0xc1, 0x49, 0xbf, 0xb8, 0x9c, 0xf5, 0x6d, 0x90, 0x79, 0x61, 0xed, 0x9c, 0xea, 0x1e, 0x8f, 0xa7, 0xa7, 0x7a, 0xce, 0xda, 0x1b, 0x83, 0x25, 0xc5, 0x81, 0xdd, 0x81, 0xe2, 0x92, 0xe0, 0x34, 0xe7, 0xba, 0xd5, 0xe1, 0x60, 0x30, 0xbc, 0x7a, 0x9d, 0xd3, 0x59, 0x96, 0x84, 0x30, 0x19, 0x73, 0x3a, 0x63, 0xf8, 0xb3, 0xcc, 0x49, 0xdb, 0x74, 0xb7, 0xcc, 0x13, 0x34, 0x72, 0x4c, 0x78, 0xe4, 0xe0, 0x39, 0x01, 0x0d, 0x8c, 0x55, 0x58, 0xc5, 0x3b, 0x17, 0xc8, 0xc4, 0x5a, 0x09, 0x14, 0x6e, 0xca, 0x02, 0xf6, 0xa7, 0x0d, 0x79, 0x13, 0x77, 0x10, 0xcf, 0xba, 0x75, 0xcc, 0x0b, 0xc3, 0xe7, 0xee, 0x80, 0x87, 0xa6, 0x49, 0x6f, 0x41, 0xf7, 0x7d, 0xf7, 0xe1, 0xf1, 0xa9, 0xcc, 0x3f, 0x2c, 0x5d, 0x01, 0xf0, 0x84, 0x41, 0x72, 0xc7, 0xe3, 0x6a, 0xe6, 0xca, 0xd3, 0x20, 0x64, 0xb2, 0xe6, 0x0d, 0x47, 0x0a, 0xed, 0x91, 0x82, 0xf0, 0x93, 0xb7, 0xde, 0xda, 0x26, 0x79, 0xe1, 0x1b, 0xdf, 0x7c, 0x6e, 0x0b, 0x3c, 0x8d, 0x68, 0xad, 0x97, 0x7e, 0xcc, 0xfc, 0x7c, 0xe4, 0x51, 0xe0, 0x06, 0xfd, 0x69, 0xa3, 0x0b, 0x71, 0xca, 0x6a, 0xc1, 0x57, 0xe0, 0x24, 0x98, 0x1b, 0xe6, 0xb4, 0x85, 0xe3, 0x19, 0x39, 0xab, 0xfb, 0x80, 0x62, 0xb0, 0xea, 0xc5, 0x45, 0x5f, 0x88, 0x1f, 0xe0, 0x75, 0x0e, 0x7f, 0xaa, 0x1b, 0xb8, 0xfe, 0xb4, 0x05, 0x02, 0xa7, 0xbd, 0xc0, 0xa4, 0xd7, 0x89, 0x3c, 0x70, 0x43, 0x37, 0xde, 0xc1, 0xf1, 0xf8, 0xa4, 0x33, 0x1a, 0x32, 0x41, 0x3f, 0x1a, 0x09, 0xb5, 0xd1, 0x58, 0x09, 0x75, 0xeb, 0x77, 0xd5, 0x33, 0xbe, 0x69, 0x03, 0xa6, 0x1b, 0x84, 0x56, 0xc2, 0xd6, 0x56, 0xe1, 0x06, 0xd3, 0xc0, 0xb4, 0x81, 0x54, 0xaa, 0xab, 0x67, 0x56, 0x78, 0x12, 0xac, 0xc0, 0x3c, 0xad, 0x80, 0x93, 0xc2, 0xb3, 0x7a, 0x26, 0x4f, 0xa6, 0x63, 0xe5, 0x6e, 0xfe, 0x43, 0xe6, 0x3c, 0xf1, 0xfb, 0x68, 0x8e, 0x13, 0x8e, 0x6b, 0xc8, 0x59, 0xa0, 0x4b, 0x8c, 0x89, 0x31, 0x9c, 0x07, 0xad, 0xc1, 0x05, 0xff, 0xed, 0x40, 0xdf, 0x01, 0xe5, 0x3f, 0xe1, 0x45, 0xfa, 0x6d, 0xff, 0xac, 0x03, 0x07, 0x66, 0xd1, 0x77, 0xb9, 0x4b, 0x98, 0xf3, 0x84, 0xce, 0x2f, 0xf0, 0x2e, 0xbf, 0x68, 0xd4, 0xbb, 0x7f, 0x9d, 0x7a, 0x61, 0x90, 0xdb, 0xc1, 0x70, 0xc2, 0x2f, 0x91, 0x9e, 0x81, 0x7b, 0x98, 0x74, 0x30, 0xea, 0x04, 0x96, 0x61, 0xd8, 0xf9, 0x68, 0x69, 0xc0, 0x33, 0x1e, 0xcb, 0xf4, 0x5a, 0x7c, 0x16, 0x0f, 0x11, 0xb2, 0x5a, 0x79, 0xf7, 0xe7, 0xa0, 0xd6, 0x92, 0x64, 0xb2, 0xa9, 0x6b, 0x87, 0xf0, 0xf2, 0xc2, 0x92, 0xc2, 0x60, 0xf3, 0xec, 0x9a, 0x44, 0xbc, 0x30, 0x88, 0xc3, 0x8f, 0x55, 0x97, 0x97, 0xf2, 0x93, 0x9d, 0x95, 0xf5, 0x6d, 0xe1, 0xd2, 0xc9, 0xd5, 0x85, 0x97, 0xc1, 0x48, 0xd0, 0x59, 0x55, 0xdf, 0x1e, 0x2a, 0x9b, 0x54, 0xe5, 0xbb, 0x04, 0x5f, 0x1c, 0xa2, 0x7a, 0xf9, 0xdf, 0x33, 0x9c, 0xa6, 0xe0, 0xcf, 0x5f, 0xaf, 0x70, 0xaf, 0x33, 0x12, 0x4f, 0x85, 0x4b, 0x27, 0x24, 0x3d, 0x97, 0x41, 0x5f, 0xd0, 0x19, 0x8d, 0xd7, 0x86, 0xca, 0x3a, 0x93, 0xae, 0x4b, 0x20, 0x59, 0x82, 0xa7, 0xf1, 0x0f, 0x31, 0x2f, 0x89, 0xdb, 0x41, 0x09, 0x98, 0x90, 0xee, 0xf0, 0x7b, 0x19, 0x8e, 0xd5, 0x6b, 0xb0, 0x46, 0xd2, 0x55, 0xe8, 0x63, 0xd8, 0x69, 0x38, 0x8e, 0xcb, 0x54, 0xbc, 0x1f, 0xe6, 0xe6, 0xe3, 0xbb, 0x52, 0x32, 0xd3, 0x0c, 0x10, 0xd3, 0x6a, 0x34, 0xdd, 0x70, 0x00, 0xc7, 0x56, 0x2f, 0x01, 0x25, 0xd1, 0xb0, 0x35, 0x16, 0x26, 0x57, 0xa6, 0x4a, 0x80, 0x3c, 0x9c, 0x2b, 0x06, 0xfb, 0xeb, 0x84, 0xe4, 0x33, 0xf7, 0x06, 0x97, 0x48, 0x0c, 0xa4, 0x99, 0x97, 0x7c, 0x55, 0x93, 0xca, 0x02, 0x13, 0x02, 0x03, 0x8b, 0x96, 0x5a, 0x83, 0xa6, 0x60, 0x04, 0x5e, 0xe8, 0xab, 0x99, 0x58, 0x16, 0x9a, 0x1c, 0x1c, 0x5c, 0xb4, 0x34, 0xe8, 0x2e, 0x89, 0x40, 0x51, 0x53, 0x33, 0xbb, 0x39, 0x60, 0x37, 0xdd, 0x99, 0xda, 0xaf, 0xd1, 0x56, 0x25, 0xaa, 0xcf, 0x69, 0x0e, 0xba, 0x4c, 0x77, 0xd6, 0x1c, 0x30, 0x55, 0x94, 0x93, 0xbe, 0x9d, 0xc6, 0xa5, 0x98, 0x97, 0xd0, 0x1e, 0xad, 0x04, 0xc7, 0x34, 0x18, 0x8f, 0x5e, 0x42, 0x1c, 0x0e, 0x69, 0xc4, 0x2e, 0x24, 0x57, 0xbc, 0xd4, 0x97, 0xff, 0xcf, 0x4c, 0x29, 0xbf, 0xab, 0x66, 0x76, 0x4b, 0xc0, 0x91, 0x47, 0x69, 0x6a, 0x3f, 0xa6, 0xf4, 0xef, 0x8f, 0xaf, 0x7e, 0xc9, 0xc1, 0x75, 0x8e, 0x5c, 0x04, 0xcc, 0xa0, 0x29, 0xad, 0x33, 0x1b, 0x18, 0x38, 0x4d, 0xe4, 0x18, 0xbc, 0xa5, 0x2a, 0xcc, 0x3e, 0x33, 0x3c, 0x2f, 0xb3, 0xa5, 0x9a, 0x0b, 0x32, 0x71, 0x84, 0x1e, 0x71, 0x39, 0x88, 0xae, 0xed, 0xb4, 0x8b, 0x3c, 0x5a, 0x76, 0x22, 0xd1, 0xfa, 0x06, 0x7c, 0xd3, 0xc7, 0x85, 0x8a, 0x8a, 0xa4, 0x0f, 0xa5, 0x0a, 0xe9, 0x23, 0xec, 0x0e, 0xaf, 0x83, 0xff, 0x4f, 0x7a, 0xbb, 0x20, 0x65, 0xde, 0xb1, 0x72, 0xe5, 0x4e, 0xf4, 0x71, 0xed, 0xea, 0xd5, 0x78, 0xdc, 0x5f, 0x35, 0xfc, 0x3a, 0xf3, 0xf2, 0xc8, 0xc3, 0x40, 0x0b, 0x26, 0xd2, 0xba, 0xcc, 0x00, 0x9f, 0x5d, 0x81, 0xc5, 0x44, 0xa5, 0x5b, 0x83, 0xd7, 0x55, 0x2f, 0x99, 0xb3, 0x71, 0xd0, 0xc0, 0x21, 0xfc, 0x0f, 0x59, 0xef, 0x71, 0xcd, 0x0c, 0x8e, 0x60, 0x84, 0xe7, 0x6e, 0x2d, 0xd0, 0x5a, 0x15, 0x75, 0x0f, 0x69, 0x59, 0xb6, 0x7a, 0xa7, 0xcb, 0xc6, 0x5c, 0x72, 0xeb, 0xca, 0x1b, 0x70, 0x35, 0xd2, 0x0d, 0xf7, 0xdc, 0x03, 0xf5, 0x88, 0x00, 0xe9, 0x43, 0x4c, 0xec, 0xc7, 0xfc, 0xc7, 0x4c, 0xb9, 0xf8, 0x34, 0xd2, 0x41, 0x85, 0xe3, 0x3c, 0xc4, 0x73, 0x03, 0x99, 0x18, 0x98, 0xf2, 0xd7, 0xfa, 0x0e, 0x1e, 0xec, 0x13, 0x7e, 0xf5, 0x5a, 0xdf, 0xfe, 0xfd, 0xb3, 0x30, 0x1e, 0x77, 0x19, 0x53, 0x2e, 0x4c, 0x1c, 0x07, 0x8f, 0x5f, 0xa2, 0xe2, 0x7d, 0xc1, 0xf2, 0xde, 0x13, 0x0a, 0xe0, 0xf3, 0xe2, 0xe3, 0xc0, 0x42, 0x74, 0x7e, 0xb2, 0x75, 0x58, 0xaa, 0xe8, 0x34, 0x6b, 0x99, 0x1e, 0xbb, 0xc5, 0x4d, 0x97, 0x6d, 0xe5, 0x3c, 0xc9, 0x41, 0x43, 0x1d, 0xc2, 0xe7, 0x53, 0x73, 0x5a, 0x83, 0xc1, 0xd6, 0x39, 0xa9, 0x44, 0x63, 0x63, 0x42, 0x28, 0x28, 0x6a, 0xec, 0xad, 0xaa, 0x9a, 0xd9, 0x58, 0xb4, 0xba, 0xb1, 0xbe, 0x9e, 0xe8, 0x68, 0xef, 0x71, 0xb7, 0xc2, 0xe7, 0x85, 0x16, 0x54, 0x2e, 0x9e, 0x37, 0x70, 0xae, 0x28, 0xc8, 0x0c, 0x90, 0xf2, 0xe7, 0xcb, 0x71, 0x5f, 0x58, 0xd0, 0xfb, 0xc5, 0x4a, 0xe7, 0x6e, 0xcd, 0x2b, 0x1d, 0xfc, 0xc5, 0xe8, 0x66, 0x40, 0x27, 0xff, 0x1a, 0xf3, 0xb2, 0x78, 0x27, 0xd2, 0xf5, 0x62, 0x60, 0x31, 0x0d, 0xb2, 0x58, 0x98, 0x73, 0x08, 0x07, 0x51, 0x3d, 0xf2, 0xe9, 0x1b, 0xb3, 0x16, 0x5b, 0x2b, 0x95, 0xa8, 0x8f, 0x11, 0x08, 0xc9, 0x0e, 0xc3, 0x0d, 0x90, 0x59, 0x32, 0x7b, 0x6f, 0x9f, 0xb6, 0x38, 0xec, 0xa1, 0x12, 0x72, 0x4c, 0x17, 0x73, 0xc4, 0xc6, 0x39, 0xa6, 0xa3, 0xc7, 0x3a, 0x51, 0xf5, 0x54, 0x87, 0xd1, 0xd8, 0x82, 0xe5, 0x1e, 0x77, 0x22, 0x68, 0xb3, 0x05, 0x13, 0x6e, 0x4f, 0x79, 0xd0, 0xb6, 0xe5, 0xdc, 0xad, 0xb3, 0x22, 0xdb, 0x56, 0x9e, 0x5f, 0xd7, 0x9b, 0xf2, 0x6c, 0x8d, 0xcc, 0xda, 0x26, 0x2c, 0x74, 0x97, 0x07, 0x6c, 0xb6, 0x40, 0xb9, 0xdb, 0x43, 0xb1, 0x60, 0x2c, 0x39, 0x6b, 0xc3, 0x84, 0x85, 0x1b, 0xfc, 0x75, 0xdd, 0xc9, 0x09, 0x1b, 0x66, 0x25, 0xa9, 0xfe, 0xda, 0xc9, 0xad, 0x64, 0x5e, 0x16, 0xfc, 0xa4, 0x4d, 0xd9, 0x67, 0x8a, 0xe3, 0xd2, 0xfb, 0x17, 0x21, 0x96, 0x7b, 0xc9, 0x5d, 0x8e, 0x9f, 0xe6, 0x13, 0xbb, 0xde, 0x5f, 0xab, 0x12, 0xfb, 0xbf, 0x91, 0xff, 0x10, 0xbc, 0x3c, 0xfc, 0x47, 0xc6, 0x3a, 0xf2, 0x14, 0x91, 0x45, 0x4e, 0x59, 0xf7, 0xd1, 0x8a, 0x7f, 0xd9, 0xc1, 0xbe, 0xd7, 0x5e, 0xeb, 0x93, 0x7e, 0xb9, 0xbf, 0xef, 0xb5, 0xd7, 0xfb, 0x10, 0xb5, 0xba, 0xe1, 0x8f, 0x98, 0xf6, 0x91, 0xef, 0x8f, 0x92, 0xd9, 0x76, 0x22, 0xb3, 0xd2, 0x2f, 0x5f, 0x9f, 0xb5, 0x7f, 0x7f, 0x1f, 0xe2, 0x91, 0x6d, 0xf8, 0x23, 0x36, 0x40, 0xf0, 0xf4, 0x40, 0x73, 0x42, 0x87, 0x8d, 0xb5, 0xaa, 0x12, 0xc5, 0x04, 0x19, 0xe2, 0xb9, 0xd8, 0x35, 0xf0, 0x1a, 0x7e, 0x83, 0xf9, 0x1e, 0x42, 0x7f, 0xed, 0xb5, 0xce, 0xd7, 0xb1, 0xac, 0x4b, 0x3f, 0xbd, 0xfd, 0xf6, 0x59, 0xaf, 0xbd, 0x26, 0xdf, 0xbd, 0x84, 0x99, 0x0a, 0xf1, 0xc7, 0x48, 0x76, 0xda, 0x69, 0x3a, 0x4d, 0x2b, 0x35, 0x31, 0x58, 0xaa, 0xb8, 0x6b, 0xac, 0xa5, 0x9b, 0x19, 0xc5, 0x2c, 0x1b, 0x0b, 0xec, 0x7c, 0x39, 0x09, 0x2d, 0x60, 0xd1, 0x66, 0xc6, 0x6d, 0xb1, 0x53, 0xe9, 0xa2, 0x3b, 0x97, 0x3a, 0x65, 0x3f, 0x03, 0xbb, 0xb0, 0x54, 0x29, 0x12, 0x26, 0xdc, 0xd8, 0x50, 0x5f, 0xdf, 0xb8, 0xba, 0xa8, 0x71, 0x66, 0x55, 0x55, 0x6f, 0x63, 0x11, 0xe5, 0xc5, 0x16, 0xee, 0x3e, 0xb8, 0x48, 0xe8, 0x95, 0xe7, 0x85, 0x71, 0x6a, 0x50, 0xca, 0xb7, 0xe5, 0x95, 0xbf, 0x25, 0xd1, 0xd4, 0xa4, 0x16, 0xcf, 0x1b, 0x73, 0x8b, 0xc7, 0xf7, 0x50, 0xf7, 0x31, 0x15, 0x5f, 0xb4, 0xec, 0x33, 0xd3, 0x9e, 0x5f, 0xf8, 0x5f, 0x9d, 0x6f, 0xe7, 0x49, 0x1b, 0xe1, 0xbf, 0x8d, 0xbc, 0x03, 0x2a, 0xc1, 0xc4, 0xb4, 0xad, 0xb2, 0x22, 0x51, 0x1a, 0x0a, 0x14, 0xbb, 0x5d, 0xb6, 0x02, 0xbd, 0x28, 0x98, 0x68, 0x48, 0xbb, 0xf0, 0x17, 0x58, 0x10, 0x4f, 0xc4, 0xa2, 0x45, 0xf8, 0x04, 0x8c, 0xa7, 0xd1, 0xdf, 0x44, 0xa4, 0x54, 0xc7, 0x9c, 0x44, 0x13, 0xc7, 0xb6, 0x7c, 0x66, 0x88, 0xb7, 0xe3, 0x0d, 0xd1, 0x4a, 0x6c, 0x13, 0xe5, 0xa2, 0x7b, 0x20, 0x41, 0xac, 0xef, 0x80, 0x2e, 0x78, 0x83, 0xd1, 0x6b, 0xf2, 0xbb, 0x67, 0x84, 0x66, 0x14, 0xb8, 0xb4, 0xa6, 0x69, 0xbc, 0xa8, 0x75, 0x9a, 0xd0, 0x0f, 0xb3, 0xcf, 0x5c, 0xe4, 0x9b, 0x1e, 0x9a, 0xee, 0xb7, 0x62, 0x98, 0xb5, 0x70, 0x46, 0xe8, 0x49, 0x8d, 0x8e, 0x61, 0x2d, 0xe5, 0x6d, 0xe5, 0x5a, 0xbb, 0xa9, 0xb0, 0xa4, 0x7f, 0x81, 0xa5, 0xd0, 0x68, 0xb0, 0x6a, 0x92, 0xed, 0xe5, 0x04, 0x9e, 0x68, 0x8b, 0x17, 0xf8, 0x0a, 0x4b, 0xce, 0xed, 0xb7, 0xf8, 0x4d, 0xee, 0x82, 0xf2, 0x56, 0xc4, 0x96, 0xaf, 0x4b, 0x7b, 0x18, 0x69, 0xe4, 0x39, 0x34, 0x92, 0x5d, 0xa0, 0x01, 0x4c, 0x49, 0x4f, 0xac, 0x83, 0x90, 0x43, 0x4a, 0x0c, 0x60, 0xba, 0x44, 0x1d, 0x4e, 0xaf, 0x81, 0x0d, 0xe2, 0x06, 0xb4, 0x8c, 0x86, 0xc5, 0x86, 0xcc, 0x38, 0x9a, 0xfe, 0x10, 0xb6, 0x64, 0x36, 0xa2, 0x6d, 0x7f, 0xaa, 0xba, 0x2c, 0x16, 0x0a, 0xba, 0x1b, 0x3c, 0x0d, 0x36, 0x8b, 0xc1, 0x65, 0x74, 0x8d, 0x1d, 0xcb, 0x31, 0x94, 0xf5, 0x3d, 0x92, 0x15, 0x3e, 0x2c, 0xd7, 0xf8, 0xa2, 0x1d, 0x32, 0x33, 0x8b, 0xc3, 0xe1, 0xe2, 0x40, 0x38, 0xfc, 0xab, 0x40, 0x28, 0x14, 0x40, 0x9f, 0x30, 0x1a, 0x6d, 0x4f, 0xb8, 0x5c, 0x89, 0xf6, 0x68, 0xd5, 0x24, 0x9b, 0x7d, 0x62, 0x55, 0xb4, 0xbd, 0xdc, 0xed, 0x2e, 0x47, 0xbf, 0x26, 0xda, 0x6d, 0x93, 0xa4, 0xdf, 0x87, 0xfd, 0xfe, 0x70, 0xf6, 0xff, 0xaf, 0x7b, 0xca, 0x5b, 0x4a, 0x4a, 0x5a, 0xcb, 0x3d, 0xb1, 0x40, 0x20, 0xe6, 0x29, 0x6f, 0x2d, 0x29, 0x69, 0x29, 0xf7, 0x44, 0x03, 0x81, 0x28, 0xed, 0xbf, 0x67, 0xf8, 0x1f, 0x31, 0x97, 0x88, 0xc3, 0xc0, 0x08, 0xe6, 0xd0, 0x38, 0x9a, 0x45, 0x80, 0xec, 0x68, 0xc9, 0xde, 0x7c, 0x35, 0x0e, 0x61, 0x0d, 0xd9, 0xb9, 0x24, 0xda, 0xf7, 0xb9, 0x38, 0x9c, 0xd7, 0x4c, 0xec, 0x3c, 0x85, 0x37, 0xaf, 0xe4, 0x38, 0x6a, 0xf4, 0x63, 0xaa, 0x60, 0x18, 0x81, 0x31, 0x68, 0x0b, 0x12, 0x05, 0x03, 0xc7, 0xb2, 0xee, 0x80, 0x21, 0x1b, 0x6a, 0x8c, 0x19, 0x32, 0x97, 0x6c, 0x59, 0xe1, 0x30, 0x69, 0x26, 0x7e, 0x7a, 0xc9, 0x0a, 0xa7, 0x51, 0x9c, 0x28, 0x06, 0xa5, 0xc3, 0x6e, 0xdb, 0x43, 0x70, 0xae, 0xc7, 0xfa, 0x10, 0xa1, 0x85, 0x9b, 0xc9, 0x5c, 0x22, 0x6c, 0x43, 0xaf, 0x4f, 0xa3, 0xb4, 0xb8, 0xb3, 0x69, 0xc1, 0xea, 0x69, 0x3f, 0xae, 0x6a, 0x00, 0x8f, 0x66, 0x67, 0x86, 0x8a, 0xec, 0x07, 0x5f, 0xaa, 0x7e, 0xfe, 0x9e, 0xbc, 0xfa, 0xff, 0x86, 0x78, 0x31, 0x5b, 0xaa, 0xd1, 0xb4, 0x8e, 0xac, 0x47, 0x32, 0xdd, 0xf1, 0xa8, 0xd1, 0x20, 0xf0, 0x1c, 0xe6, 0x09, 0xd6, 0xf7, 0x0c, 0x8c, 0x92, 0xe9, 0x8a, 0x1c, 0xa2, 0x58, 0xe9, 0xed, 0xea, 0x7c, 0x05, 0x4a, 0xf6, 0xb7, 0x7a, 0x08, 0x74, 0x1a, 0x60, 0x81, 0x16, 0x96, 0x0c, 0x3a, 0xec, 0xa3, 0x26, 0x70, 0xc1, 0x40, 0x38, 0xd6, 0x50, 0xc4, 0x72, 0x53, 0xf7, 0x0d, 0xbf, 0xf9, 0xe2, 0x50, 0xed, 0xb3, 0x92, 0x5e, 0x7a, 0x16, 0xfd, 0xd5, 0x3f, 0x9b, 0x1a, 0x7a, 0xf1, 0xcd, 0x61, 0xe9, 0xb4, 0x33, 0x91, 0x2e, 0xdb, 0x3e, 0x6f, 0x60, 0x60, 0xde, 0xf6, 0xb2, 0x74, 0xc2, 0x49, 0xe2, 0x15, 0x08, 0x26, 0x4d, 0xab, 0xd8, 0xc5, 0x8a, 0x4c, 0x0c, 0x6d, 0xf8, 0x7f, 0x45, 0xed, 0x04, 0xc5, 0x5f, 0xa9, 0x76, 0x82, 0xb3, 0xb9, 0x9b, 0x35, 0xad, 0xfc, 0x8f, 0x20, 0x79, 0x2e, 0x2c, 0xa1, 0xcf, 0x85, 0x25, 0x99, 0xe7, 0xf4, 0x7d, 0x38, 0xde, 0xfb, 0x6b, 0x84, 0xb9, 0x9a, 0xa8, 0xf0, 0x16, 0x2a, 0x3f, 0x3e, 0xf6, 0x73, 0xee, 0x17, 0x9a, 0x28, 0x7f, 0x1e, 0x24, 0xcf, 0xc7, 0x28, 0x5f, 0x7e, 0x1f, 0x8e, 0xf9, 0x3e, 0x62, 0xc5, 0x1a, 0xe9, 0x52, 0x4d, 0x74, 0xc4, 0x40, 0xcf, 0x9f, 0x0a, 0xbf, 0xf8, 0xf9, 0x13, 0x3e, 0x3d, 0x5c, 0x23, 0x3e, 0xf9, 0xf1, 0x84, 0x0f, 0xf6, 0x91, 0x72, 0x36, 0x0a, 0xa2, 0xc6, 0x2f, 0x0e, 0x82, 0x22, 0xd0, 0x4f, 0xa3, 0xc1, 0x7a, 0xf1, 0xd1, 0x0d, 0xe4, 0xc8, 0x1e, 0x04, 0x00, 0x72, 0xed, 0xa9, 0xec, 0x44, 0x7c, 0xe9, 0x52, 0xc0, 0xb3, 0x90, 0xe5, 0x21, 0x36, 0xd4, 0x47, 0x5b, 0x2b, 0xa4, 0x1d, 0xa8, 0xb8, 0x8c, 0x82, 0x0b, 0x30, 0x6a, 0x7f, 0x5a, 0x6b, 0x8d, 0x84, 0xad, 0x11, 0x1a, 0x53, 0x99, 0x78, 0xca, 0x72, 0x59, 0x6e, 0x86, 0x1c, 0xb9, 0x17, 0xd5, 0xf8, 0x7f, 0xfd, 0xbb, 0x35, 0x8f, 0x3e, 0x70, 0xf7, 0xbd, 0xdf, 0x5c, 0x7e, 0xfb, 0x9e, 0xaf, 0x36, 0x6e, 0xfa, 0xe6, 0x66, 0xf2, 0xeb, 0xc8, 0xf2, 0xdb, 0xf7, 0x7e, 0x55, 0x9c, 0x7e, 0xc7, 0x3f, 0x16, 0x25, 0x6b, 0x93, 0x45, 0x73, 0x17, 0xce, 0xd8, 0x72, 0x4e, 0xa2, 0x28, 0x59, 0x87, 0xbf, 0xd2, 0xb6, 0x6f, 0xe4, 0x76, 0x6a, 0xfc, 0xfc, 0x1b, 0x88, 0xe6, 0x21, 0x4a, 0xb3, 0x3d, 0x67, 0x9b, 0x87, 0x0f, 0x9d, 0x56, 0x12, 0xf3, 0x34, 0x37, 0x49, 0x4a, 0xa3, 0x90, 0x8a, 0x9b, 0x03, 0x70, 0xcc, 0xfa, 0x95, 0xdd, 0x04, 0x0f, 0x50, 0xb4, 0xb4, 0x7f, 0x74, 0x63, 0x54, 0x4c, 0x92, 0x6e, 0xfc, 0x4f, 0x6c, 0x0a, 0xff, 0xcc, 0x98, 0x4d, 0xf9, 0xfb, 0xe4, 0xfd, 0x6c, 0x21, 0x86, 0xc6, 0xfd, 0x00, 0x92, 0xdd, 0x68, 0x3a, 0x44, 0x85, 0x37, 0xfb, 0xca, 0x07, 0x4b, 0x2c, 0xe8, 0xcd, 0x11, 0x51, 0xec, 0x7d, 0x14, 0xab, 0x0f, 0x07, 0x03, 0x9c, 0x4b, 0xac, 0x40, 0x22, 0xba, 0x1f, 0x8b, 0x68, 0xea, 0x59, 0xf8, 0x01, 0xac, 0x81, 0xd5, 0xf0, 0xc3, 0x67, 0x6b, 0xb1, 0x88, 0x0a, 0x3f, 0x75, 0x26, 0x3a, 0xca, 0xae, 0x99, 0x37, 0x38, 0x38, 0xef, 0x9a, 0xb2, 0x8e, 0x84, 0x53, 0x1e, 0xe3, 0x0e, 0x8d, 0x1d, 0xed, 0x43, 0xb5, 0xa0, 0xfe, 0x31, 0x2d, 0x84, 0xd3, 0x38, 0xf8, 0x85, 0x37, 0xa1, 0x16, 0x79, 0x13, 0x2a, 0xe0, 0x4d, 0x28, 0xfa, 0x2b, 0x7e, 0x88, 0x77, 0x7d, 0x1f, 0x7d, 0x72, 0xa5, 0xb0, 0x5d, 0xde, 0x78, 0xee, 0x5b, 0x4d, 0x74, 0xdf, 0x05, 0xfc, 0x2f, 0xe0, 0x2b, 0xe2, 0x71, 0xb4, 0x16, 0x56, 0x82, 0x96, 0x74, 0xe3, 0x19, 0xf2, 0x43, 0x28, 0x39, 0x0f, 0xe4, 0x3c, 0x11, 0x6b, 0x85, 0x9e, 0x50, 0x89, 0x33, 0xea, 0x18, 0x95, 0x1c, 0x82, 0xcd, 0xc9, 0x77, 0x00, 0x33, 0xf9, 0x0e, 0xd4, 0x74, 0x07, 0xab, 0xcb, 0x5b, 0x42, 0x26, 0xbf, 0xcf, 0x5f, 0xee, 0xfa, 0xa4, 0xef, 0xc2, 0xb2, 0xce, 0xa4, 0xbb, 0xd1, 0x62, 0x13, 0x74, 0x25, 0xe9, 0x8a, 0xce, 0x6e, 0x6f, 0xfb, 0xda, 0x99, 0x93, 0xbd, 0x41, 0xaf, 0xd3, 0x6c, 0xb4, 0xf2, 0xc2, 0x21, 0x46, 0x6f, 0x71, 0x1a, 0xbc, 0x76, 0xbb, 0x13, 0x5a, 0xa7, 0xfb, 0xaa, 0x26, 0xc4, 0xa4, 0xff, 0xd0, 0x59, 0x7c, 0xfe, 0x40, 0xba, 0xdd, 0x93, 0x8a, 0x17, 0x32, 0xce, 0x3e, 0xbd, 0x49, 0x6f, 0xd4, 0xd2, 0x76, 0x70, 0xfd, 0xf0, 0x15, 0xa1, 0x8e, 0xb4, 0xa3, 0x33, 0xdd, 0x3e, 0x46, 0x3b, 0x94, 0xdc, 0x0d, 0x6a, 0x7b, 0xd0, 0x64, 0x3e, 0x9f, 0x08, 0x02, 0x69, 0x58, 0xef, 0x5f, 0xba, 0x35, 0x7c, 0x11, 0x6d, 0x8d, 0xc3, 0x91, 0xd7, 0x9a, 0x8e, 0x8e, 0x9c, 0xd6, 0xfc, 0xef, 0xe9, 0x93, 0x73, 0x50, 0x3b, 0x24, 0xf1, 0x61, 0x20, 0x80, 0x32, 0x1c, 0x4b, 0xdf, 0x9a, 0x13, 0x4b, 0x3f, 0x2b, 0x2d, 0x00, 0x8d, 0xa6, 0xaf, 0xf8, 0xf5, 0xad, 0x05, 0x3d, 0xa2, 0x88, 0xe6, 0xfc, 0x32, 0xb1, 0x14, 0x47, 0x3b, 0x2e, 0x30, 0xe9, 0xb5, 0xa8, 0x04, 0xa1, 0x84, 0x26, 0x04, 0x88, 0x9e, 0x3d, 0x94, 0xfe, 0xde, 0x44, 0x45, 0x79, 0xb5, 0x86, 0x11, 0xdb, 0xaa, 0x3d, 0x91, 0x42, 0x97, 0xde, 0xa4, 0x09, 0xdb, 0x2b, 0x6b, 0x6a, 0xdd, 0x55, 0x73, 0xda, 0xc3, 0xc5, 0xcd, 0xe7, 0xd4, 0x26, 0xbb, 0xea, 0x8b, 0xf4, 0x16, 0xe1, 0xd3, 0x70, 0x53, 0x55, 0xb2, 0xbb, 0xbd, 0xc9, 0x60, 0x36, 0x18, 0x2a, 0xdc, 0x45, 0x16, 0x31, 0xd0, 0xbe, 0xa8, 0xad, 0xe2, 0x9c, 0x8e, 0xb0, 0xbf, 0x7e, 0x66, 0x8d, 0xdf, 0x46, 0xe8, 0x47, 0x63, 0x4a, 0x12, 0x52, 0x84, 0xfe, 0xcb, 0x4f, 0x58, 0xe5, 0x1c, 0x08, 0x45, 0x24, 0xf7, 0xf4, 0xd8, 0x19, 0x01, 0x70, 0x2e, 0x00, 0xd8, 0x8f, 0xf3, 0x02, 0x0c, 0x08, 0x24, 0xcd, 0x6a, 0x1e, 0x5a, 0x76, 0xa3, 0xd1, 0x86, 0x72, 0xbe, 0xec, 0x40, 0xc4, 0x70, 0x68, 0xd2, 0x72, 0xff, 0x0f, 0xb4, 0x9a, 0xbf, 0x22, 0xbf, 0xd5, 0xc5, 0xed, 0x8b, 0x51, 0xab, 0xdb, 0xc3, 0x85, 0x72, 0xab, 0xff, 0x37, 0xf4, 0xdb, 0x0a, 0xfe, 0x65, 0xf0, 0xb9, 0x66, 0x0b, 0x5a, 0x1f, 0x2c, 0x59, 0x71, 0xc1, 0x0b, 0xc7, 0x8c, 0x0b, 0xbe, 0xf6, 0x8b, 0xc4, 0x05, 0x2f, 0x30, 0x65, 0xc5, 0x05, 0xb7, 0xa9, 0x71, 0xc1, 0x65, 0xdb, 0x85, 0x77, 0xae, 0xb8, 0xd1, 0xe0, 0xf0, 0x17, 0x14, 0x27, 0x93, 0xc5, 0x81, 0x0a, 0xf1, 0x56, 0xa9, 0x07, 0x3e, 0x72, 0x7b, 0x41, 0xa1, 0xc3, 0x90, 0x0c, 0x04, 0x92, 0x49, 0x42, 0x0f, 0xb7, 0x0c, 0x7c, 0x2e, 0x7c, 0x4a, 0xe8, 0x21, 0xb1, 0x94, 0x31, 0x41, 0xe3, 0x57, 0xf9, 0xa5, 0xea, 0x13, 0xec, 0x63, 0xd4, 0xf7, 0x37, 0xd6, 0x7e, 0x34, 0x9e, 0x98, 0x20, 0x1a, 0x4f, 0x36, 0xe0, 0xc7, 0xd6, 0x4d, 0x3c, 0x24, 0xd7, 0xfc, 0x4b, 0xd5, 0x48, 0xc6, 0x6b, 0x59, 0x12, 0xcc, 0xda, 0xe7, 0xb1, 0xfb, 0x1d, 0xfe, 0x33, 0x05, 0x91, 0xa6, 0xbb, 0x2a, 0x84, 0xdb, 0x52, 0x3b, 0x17, 0xef, 0x5b, 0xe7, 0xd6, 0xd6, 0xce, 0x6d, 0x2b, 0x29, 0x69, 0x9b, 0x5b, 0x5b, 0x57, 0x5d, 0x5d, 0xb7, 0x46, 0x7c, 0x38, 0xd8, 0xbe, 0xa0, 0xbe, 0x61, 0x41, 0x7b, 0x10, 0x7d, 0x36, 0xd4, 0xa3, 0x4f, 0xe9, 0xba, 0x29, 0xe8, 0xcf, 0xee, 0xdd, 0xf2, 0xb8, 0xe6, 0xfa, 0x99, 0x20, 0x92, 0x67, 0x4c, 0xc7, 0xd9, 0x63, 0x5a, 0xff, 0xc9, 0xe4, 0x08, 0xa9, 0x60, 0x5b, 0x36, 0x39, 0x6d, 0x41, 0xe9, 0x7a, 0x99, 0x9c, 0xbf, 0x15, 0x9e, 0xa0, 0xad, 0x0f, 0x92, 0xf5, 0x11, 0xf1, 0x41, 0xe0, 0xc6, 0x7e, 0x6b, 0x2e, 0x34, 0x06, 0x04, 0x12, 0x7b, 0x98, 0x9e, 0x9a, 0x66, 0x45, 0x20, 0x5e, 0x4b, 0xc2, 0xf2, 0xba, 0x81, 0x3b, 0xe4, 0x88, 0x2a, 0x7e, 0x6b, 0xca, 0x62, 0x92, 0x13, 0x77, 0x18, 0x8e, 0xcc, 0xd0, 0x9b, 0x04, 0xd1, 0xd7, 0x59, 0x11, 0x6d, 0x2a, 0xb5, 0x5f, 0x51, 0x12, 0xf2, 0x26, 0xed, 0xe2, 0x83, 0x52, 0xad, 0x41, 0x5f, 0xe4, 0x0b, 0x7a, 0x2b, 0x3a, 0xa3, 0x30, 0x2c, 0xfd, 0x2a, 0x52, 0xea, 0xb4, 0xc3, 0xa9, 0xa4, 0x7e, 0x34, 0xc7, 0x8e, 0x08, 0xe5, 0xa8, 0xe4, 0x54, 0xba, 0x2a, 0x53, 0x3f, 0xae, 0x13, 0x8d, 0xc9, 0xb9, 0x99, 0xd3, 0x5b, 0xf9, 0x16, 0xf5, 0xbf, 0x4f, 0x84, 0x50, 0x8e, 0x88, 0xd0, 0x15, 0x8f, 0x22, 0xe2, 0xaf, 0xcf, 0x03, 0x3c, 0xdf, 0xbe, 0x8c, 0xc6, 0x42, 0x14, 0x67, 0x68, 0x8a, 0x42, 0xc8, 0x3b, 0x49, 0xee, 0xa3, 0xfc, 0x7c, 0x46, 0xf2, 0x74, 0x2b, 0x47, 0xdc, 0x5e, 0x0b, 0x7b, 0x4a, 0xc3, 0x67, 0xc8, 0x64, 0x94, 0xef, 0xea, 0x01, 0x1f, 0xe7, 0xf5, 0x3a, 0xad, 0xc5, 0x51, 0xe8, 0x6c, 0xa8, 0x6d, 0xac, 0xf2, 0x14, 0x39, 0x2c, 0x7a, 0xb3, 0x26, 0x5c, 0xba, 0xa2, 0x2a, 0xde, 0xd3, 0x54, 0xe2, 0x6f, 0x9e, 0xd3, 0x58, 0xd7, 0x1b, 0x10, 0xd3, 0xa2, 0x46, 0x67, 0xd4, 0x75, 0x4f, 0x98, 0x3c, 0xdd, 0x60, 0x36, 0x1a, 0x2a, 0x2a, 0x8b, 0x71, 0x52, 0x99, 0x9a, 0x79, 0xe9, 0x48, 0x28, 0x88, 0xe9, 0x44, 0x7d, 0xf5, 0x32, 0x92, 0x9f, 0x28, 0xf6, 0xcd, 0x1f, 0x9f, 0x4e, 0x1e, 0xa1, 0xf2, 0x70, 0xd4, 0x0a, 0xf7, 0x67, 0xa7, 0x96, 0x3f, 0x39, 0x0e, 0xb5, 0xe1, 0x92, 0x20, 0xf8, 0xfb, 0xe1, 0x2b, 0x9d, 0x0b, 0x18, 0xcd, 0x25, 0x48, 0xdf, 0x0b, 0x81, 0xe6, 0x74, 0x03, 0xce, 0x0b, 0xa5, 0x1f, 0x9d, 0x17, 0x0a, 0x6b, 0x80, 0x90, 0x59, 0xaa, 0xe4, 0x87, 0x42, 0x13, 0xb7, 0xc1, 0x60, 0x08, 0x19, 0x42, 0xa5, 0x51, 0x7b, 0x38, 0x48, 0x6e, 0xc6, 0xa2, 0xb9, 0x39, 0xa1, 0xa2, 0xf9, 0x49, 0xa1, 0xea, 0xe0, 0x75, 0xc1, 0xca, 0xf2, 0x86, 0x64, 0x34, 0x50, 0x6e, 0xbf, 0xa2, 0xc7, 0x60, 0x12, 0xb4, 0x85, 0x53, 0xaa, 0xc2, 0xed, 0x49, 0x4f, 0x61, 0xed, 0xf4, 0xf2, 0x68, 0xca, 0x21, 0x56, 0x97, 0x4c, 0x9a, 0xd1, 0xdd, 0xea, 0x71, 0x31, 0x86, 0xe1, 0xd5, 0x06, 0x7d, 0xb4, 0x28, 0x1a, 0x68, 0x9d, 0xdf, 0x58, 0xbb, 0x20, 0x1d, 0x71, 0xda, 0xde, 0x91, 0xe7, 0x4f, 0x46, 0xf8, 0x84, 0xd0, 0x99, 0x4e, 0xb7, 0x8d, 0x43, 0x27, 0xda, 0xb9, 0xe1, 0x08, 0xc1, 0xc4, 0x12, 0x60, 0x3e, 0x39, 0xa5, 0x24, 0x84, 0xf7, 0xfe, 0xb9, 0xa9, 0xe5, 0x8f, 0x67, 0x53, 0xab, 0x8b, 0xe5, 0x52, 0x0b, 0xfe, 0x6e, 0xf8, 0x8a, 0xfd, 0x83, 0x5f, 0x66, 0x34, 0x88, 0xce, 0xb3, 0xe4, 0x4d, 0x59, 0x7b, 0xb6, 0xbc, 0x29, 0x6f, 0x7b, 0x5b, 0xe3, 0xd1, 0x66, 0x34, 0xe1, 0x84, 0x42, 0x5e, 0x44, 0xc9, 0x15, 0xe2, 0x56, 0x97, 0xdf, 0x5b, 0xd9, 0x19, 0x95, 0x7e, 0x05, 0xc3, 0x91, 0x52, 0x87, 0x4d, 0x7a, 0x8c, 0xb9, 0x1d, 0xcb, 0xc5, 0x10, 0xb7, 0x8c, 0xd1, 0xa0, 0x7e, 0x74, 0xe0, 0xf9, 0x2e, 0xbb, 0x3e, 0xd4, 0x71, 0xf8, 0xc8, 0x04, 0x5f, 0x2c, 0xe2, 0x1b, 0x58, 0x62, 0xd1, 0xcf, 0xc1, 0x19, 0x5f, 0xae, 0x56, 0x01, 0xb8, 0x0a, 0xbd, 0x95, 0xe9, 0xbc, 0x5a, 0xc1, 0x5f, 0xa1, 0x9d, 0x1b, 0x50, 0x7d, 0x58, 0x8f, 0x0d, 0xe3, 0x5d, 0x94, 0x41, 0x60, 0x38, 0x10, 0x82, 0x2c, 0xc7, 0x74, 0xe1, 0xbc, 0xc6, 0x78, 0xab, 0xce, 0xa0, 0xba, 0xba, 0x69, 0x24, 0x6b, 0xe5, 0x27, 0x4b, 0xbd, 0xb3, 0xc3, 0x24, 0xdb, 0x47, 0xd4, 0x82, 0x93, 0xa3, 0xe2, 0x04, 0x26, 0xce, 0x62, 0x78, 0xe6, 0xa4, 0x1f, 0x1b, 0x66, 0xe8, 0xcd, 0x82, 0xe0, 0x6a, 0x8c, 0x26, 0x1b, 0x4d, 0xae, 0x73, 0x92, 0xdd, 0xb3, 0xaf, 0x9c, 0x54, 0x14, 0xbf, 0x22, 0x58, 0xe4, 0x8a, 0x59, 0xf8, 0x97, 0xe1, 0x8f, 0x0c, 0x3a, 0x9f, 0x27, 0x90, 0x8c, 0x26, 0xa3, 0x8b, 0xe6, 0x60, 0x22, 0x67, 0xc4, 0x08, 0xad, 0xa5, 0x11, 0x9b, 0x45, 0x3a, 0x4e, 0x79, 0xb3, 0x01, 0xf5, 0x09, 0xde, 0x6b, 0x84, 0x71, 0x8e, 0x15, 0x01, 0x30, 0x4a, 0x52, 0x12, 0x1c, 0xa8, 0x86, 0x87, 0x44, 0x4d, 0x21, 0xb9, 0x48, 0x98, 0x7e, 0x9c, 0x99, 0x04, 0x30, 0xb8, 0x53, 0xbe, 0x4c, 0x46, 0x12, 0xa7, 0xeb, 0x37, 0xae, 0x86, 0x31, 0xa8, 0xbb, 0x02, 0x13, 0xce, 0x7f, 0x8a, 0xa8, 0x8b, 0x94, 0x47, 0x17, 0x8e, 0xa6, 0x0e, 0xd3, 0xfe, 0xf7, 0xc6, 0xcb, 0xf3, 0x11, 0xda, 0xf5, 0x68, 0xff, 0xec, 0x20, 0x9e, 0x1e, 0x6a, 0x6a, 0x8d, 0x4c, 0x16, 0x8d, 0xb5, 0xdd, 0xd9, 0x7a, 0xd6, 0x59, 0x52, 0x65, 0xbc, 0xe2, 0x6c, 0x8c, 0x16, 0xdb, 0x35, 0x5c, 0x81, 0xe8, 0xd3, 0x07, 0x63, 0xa6, 0x2b, 0xa7, 0xdb, 0x4d, 0xac, 0xb8, 0x34, 0x96, 0x34, 0x2f, 0x2d, 0xe0, 0x42, 0xc5, 0xd2, 0x9b, 0xf0, 0x47, 0x26, 0x8b, 0x5c, 0x2f, 0xb7, 0x0c, 0x5e, 0x2f, 0xd4, 0x51, 0x1b, 0xfe, 0x4c, 0xbd, 0xd8, 0x8e, 0x1f, 0x12, 0x1b, 0x16, 0x25, 0xd2, 0x3b, 0x60, 0x67, 0xfc, 0x37, 0x2a, 0xe5, 0x5f, 0x1d, 0x55, 0xe9, 0xff, 0xad, 0xb6, 0xa6, 0xd1, 0x7a, 0xbe, 0x55, 0x53, 0x03, 0x8a, 0x71, 0x04, 0x27, 0x9c, 0xc3, 0xa3, 0x28, 0x93, 0xc3, 0x03, 0xf0, 0x48, 0x69, 0xe3, 0xc1, 0x32, 0x35, 0x97, 0xc7, 0x5a, 0xec, 0x59, 0xe3, 0xb0, 0xe7, 0x27, 0xf0, 0x80, 0xd8, 0xce, 0x2e, 0x37, 0x81, 0x87, 0xc3, 0xae, 0x5a, 0x6a, 0xa2, 0xd1, 0xdc, 0x31, 0x21, 0xa1, 0x33, 0x4d, 0xf7, 0x08, 0xce, 0xf4, 0xe6, 0xcd, 0x11, 0xad, 0x51, 0xa7, 0x15, 0x04, 0x03, 0xeb, 0xaa, 0xe3, 0x7f, 0x31, 0xb5, 0xed, 0x03, 0xa3, 0x21, 0x65, 0xba, 0xc8, 0xb1, 0xab, 0xff, 0xad, 0x18, 0xcf, 0xf3, 0x82, 0x06, 0xed, 0xef, 0x29, 0x5d, 0x48, 0x2f, 0xda, 0x2a, 0x3c, 0x82, 0xe8, 0xaa, 0x4a, 0x27, 0xf5, 0x90, 0x44, 0x7e, 0x20, 0x34, 0xb1, 0x88, 0x1a, 0x1e, 0x47, 0x2f, 0x42, 0xdb, 0x7f, 0x7e, 0x3e, 0xce, 0x16, 0xba, 0x08, 0x87, 0x89, 0xe8, 0xb3, 0x5a, 0x2c, 0x31, 0x9b, 0x9a, 0x1b, 0x65, 0x7c, 0x82, 0x5c, 0x70, 0x6b, 0xdd, 0x64, 0x42, 0x50, 0x97, 0x3b, 0x97, 0x20, 0x6e, 0x7b, 0x7c, 0x6a, 0xdb, 0xfb, 0x26, 0x4a, 0xcf, 0xb9, 0x6f, 0x52, 0x7a, 0x72, 0x79, 0x84, 0xfa, 0x25, 0x9b, 0x16, 0x4c, 0x42, 0x2e, 0x7b, 0xfe, 0x74, 0x2a, 0x04, 0x18, 0x1f, 0xcd, 0x15, 0xca, 0x93, 0x36, 0x34, 0x5f, 0x38, 0x90, 0x4e, 0x6d, 0x19, 0x2b, 0x9f, 0xc8, 0x5a, 0x62, 0x83, 0x64, 0x01, 0x05, 0xa3, 0xf3, 0x89, 0xc8, 0x79, 0x22, 0x1c, 0x8f, 0x0c, 0x3d, 0xb2, 0xda, 0x64, 0x63, 0x7a, 0x34, 0x7a, 0x5e, 0x5b, 0x30, 0x93, 0xb1, 0x19, 0xb0, 0xfe, 0x8c, 0x84, 0x5f, 0xff, 0x62, 0xb0, 0xbd, 0xb8, 0x65, 0xe2, 0x8b, 0x3a, 0xa3, 0x5c, 0x0f, 0x9a, 0x37, 0x1d, 0x68, 0xff, 0x60, 0xc1, 0xf6, 0xbf, 0xb4, 0x1e, 0x5c, 0x05, 0x35, 0xca, 0x5d, 0xfb, 0xa5, 0x6b, 0xc1, 0x9b, 0x84, 0xbc, 0x5a, 0xfe, 0xe7, 0xda, 0xd2, 0x89, 0xea, 0x29, 0x44, 0xf5, 0xd0, 0x1c, 0x23, 0x36, 0x63, 0x7e, 0x8e, 0x11, 0xa4, 0x9a, 0x94, 0x59, 0xcb, 0xb2, 0x72, 0x8c, 0x08, 0x34, 0xb9, 0x47, 0x43, 0x4c, 0x9e, 0x55, 0xe5, 0xec, 0x1e, 0x85, 0x97, 0x98, 0x0a, 0xf8, 0x7e, 0x4e, 0xd3, 0xc3, 0x17, 0x18, 0x36, 0xdc, 0x79, 0x60, 0x0b, 0x1a, 0xff, 0xfd, 0xac, 0xc0, 0x4f, 0xd2, 0xda, 0x36, 0xee, 0xe7, 0x5f, 0x36, 0x69, 0x6f, 0xb3, 0x98, 0xb6, 0x68, 0x4d, 0xbf, 0x7f, 0x59, 0xd4, 0xde, 0xaa, 0xb5, 0x6a, 0xd7, 0x59, 0xff, 0x1d, 0xd7, 0x8d, 0xf8, 0x58, 0x88, 0xf8, 0x48, 0xf3, 0x8b, 0x28, 0x75, 0x8f, 0x91, 0x5f, 0xe4, 0x4f, 0xa6, 0x80, 0x5b, 0x36, 0x16, 0x05, 0x7f, 0xdd, 0xf6, 0xb3, 0xa0, 0x82, 0xbf, 0x9a, 0x79, 0x4f, 0xfc, 0x16, 0xfa, 0xe6, 0x02, 0x09, 0x1c, 0x5d, 0x04, 0xa9, 0x26, 0x38, 0x1d, 0x0d, 0x09, 0xbb, 0x28, 0x9b, 0x80, 0xc8, 0x41, 0x46, 0x90, 0x7e, 0x52, 0x8a, 0xfe, 0x10, 0x63, 0x81, 0x06, 0x28, 0x46, 0xf2, 0x3d, 0xf3, 0xf2, 0x7e, 0xdf, 0x33, 0x00, 0xb7, 0x0f, 0x4a, 0x7f, 0xb4, 0x07, 0xcb, 0xdd, 0xee, 0x38, 0xb6, 0xdc, 0x88, 0xbb, 0xdd, 0xe5, 0x41, 0x3b, 0x7c, 0x1e, 0xdb, 0x70, 0x28, 0xb6, 0x1c, 0xf8, 0x53, 0x0c, 0x0d, 0x4a, 0x5b, 0x57, 0xce, 0x73, 0x27, 0x02, 0x56, 0x6b, 0x80, 0x1a, 0x7f, 0xd8, 0x82, 0xe5, 0x30, 0xe6, 0x4e, 0x14, 0xdb, 0x6c, 0xc5, 0x2a, 0x80, 0xe6, 0x59, 0xac, 0xe0, 0x58, 0xe6, 0x3d, 0xa1, 0x5a, 0xa6, 0x17, 0xcd, 0xfd, 0xd9, 0xf4, 0x2a, 0x87, 0x3c, 0x79, 0xae, 0x28, 0x7f, 0x11, 0xaa, 0xf9, 0x6f, 0x0c, 0x4a, 0x57, 0x0e, 0x7e, 0x01, 0xaa, 0xff, 0x0e, 0x79, 0x9c, 0x4f, 0x2f, 0x36, 0x41, 0x25, 0x2b, 0x2b, 0x43, 0x8d, 0xd7, 0xc9, 0x79, 0x0d, 0x3d, 0x79, 0x67, 0x33, 0xf4, 0xc6, 0xa0, 0x6b, 0x54, 0xa8, 0x99, 0xbc, 0xdf, 0x9f, 0x0d, 0xc2, 0xed, 0x03, 0x97, 0xe6, 0x93, 0x2b, 0xc5, 0x47, 0x31, 0xf7, 0xee, 0x95, 0xf0, 0x9a, 0x41, 0xe9, 0x39, 0x99, 0x2c, 0x8f, 0x4c, 0x37, 0x5c, 0x90, 0x01, 0x10, 0xba, 0xc7, 0x19, 0x13, 0xd9, 0xf4, 0x32, 0x78, 0x54, 0xb0, 0x03, 0xa3, 0x42, 0x6b, 0xfc, 0x05, 0xa8, 0xe6, 0x4a, 0x06, 0x71, 0x77, 0x7c, 0x01, 0xaa, 0xff, 0xde, 0x78, 0x8c, 0xfd, 0x26, 0x2f, 0x67, 0x1e, 0xe0, 0x1f, 0x03, 0x4e, 0xd0, 0x42, 0xef, 0x57, 0xad, 0x64, 0xab, 0xc9, 0xe0, 0xf3, 0x83, 0x95, 0x58, 0xd5, 0x1a, 0xc0, 0x69, 0xd9, 0x1c, 0x40, 0xb1, 0x1e, 0xcb, 0x3c, 0x84, 0x3d, 0xfd, 0x8f, 0xda, 0xc3, 0xf6, 0x30, 0xcd, 0x50, 0x94, 0xe5, 0xda, 0x4b, 0x06, 0x72, 0x0a, 0x42, 0x5b, 0x71, 0xd4, 0x16, 0xe9, 0xac, 0x2e, 0xdc, 0x55, 0x39, 0xff, 0xf2, 0x19, 0x75, 0x73, 0x5a, 0x02, 0xbb, 0xf8, 0x0b, 0xc3, 0x49, 0xb7, 0xd6, 0x5d, 0x39, 0xb1, 0xbc, 0xf3, 0xc2, 0x79, 0x35, 0xbe, 0x96, 0x25, 0x93, 0xfe, 0x66, 0x68, 0x78, 0x99, 0xff, 0x98, 0xb1, 0xca, 0xb6, 0x9e, 0xa3, 0xed, 0xcb, 0x84, 0x5f, 0x61, 0xfb, 0x32, 0x6c, 0xeb, 0xf9, 0x32, 0x77, 0x19, 0x63, 0x95, 0x6d, 0x47, 0x47, 0xe3, 0xf1, 0x4b, 0x54, 0xbc, 0x2f, 0x54, 0x5e, 0xbe, 0x1d, 0x9a, 0x56, 0xcc, 0xb2, 0x43, 0xe3, 0xf1, 0x0b, 0x0d, 0xd4, 0x0e, 0x8d, 0x35, 0xbe, 0x86, 0x2d, 0xd0, 0xa8, 0x1d, 0x5a, 0xe7, 0xeb, 0xaf, 0x23, 0x10, 0xb5, 0x2b, 0xa8, 0x1a, 0xf9, 0x03, 0xd7, 0x89, 0xe3, 0x1b, 0x32, 0x8b, 0x88, 0xa7, 0xc3, 0x43, 0xe0, 0x7e, 0x9a, 0x7f, 0x1c, 0xc3, 0xf9, 0x43, 0xa0, 0x06, 0xb4, 0xa7, 0x5b, 0x2a, 0x21, 0xc7, 0xc7, 0x10, 0xeb, 0xcc, 0x78, 0x7e, 0x42, 0xc3, 0x90, 0x63, 0x39, 0x7a, 0x5e, 0xc4, 0xe1, 0xf3, 0x22, 0x8e, 0xe3, 0xfb, 0x69, 0xca, 0x3b, 0xb4, 0xfe, 0x2f, 0xa2, 0x31, 0xfc, 0x2c, 0x8e, 0xd2, 0xca, 0x52, 0xea, 0xdd, 0x44, 0xc3, 0xef, 0xd2, 0xe0, 0x7b, 0x74, 0xa3, 0x43, 0x43, 0x51, 0xd2, 0x58, 0x87, 0xd8, 0xea, 0x09, 0x06, 0xeb, 0x82, 0xd8, 0xca, 0x89, 0x59, 0xb9, 0xed, 0xc4, 0x85, 0x75, 0x0b, 0xef, 0x78, 0x66, 0xe3, 0xf4, 0x5d, 0x97, 0xaf, 0x2d, 0x9b, 0xad, 0xb7, 0x09, 0x06, 0xbb, 0xad, 0xa0, 0x6e, 0xc6, 0x60, 0xc7, 0x9c, 0xab, 0x16, 0x24, 0xa3, 0x73, 0x76, 0x0e, 0x1c, 0x36, 0x59, 0xe0, 0x65, 0xc3, 0xeb, 0xd8, 0xef, 0xe9, 0x9d, 0xec, 0x16, 0xbe, 0x73, 0xdd, 0x9e, 0x59, 0x17, 0x7d, 0xe7, 0x9a, 0x29, 0x45, 0x4d, 0xb3, 0xeb, 0x96, 0x18, 0x38, 0x4d, 0x81, 0xb5, 0x3c, 0xe6, 0xad, 0x99, 0x37, 0xd4, 0xd1, 0xb0, 0x78, 0x52, 0x6c, 0x91, 0x25, 0x6c, 0x81, 0x1b, 0xbf, 0xd9, 0x6b, 0x2d, 0xb3, 0x23, 0x3e, 0xcd, 0x96, 0x76, 0xf3, 0x57, 0x70, 0x7a, 0x60, 0x02, 0xad, 0xe0, 0x34, 0xf5, 0x1f, 0x32, 0x94, 0x23, 0x0d, 0xb9, 0xd0, 0x84, 0x87, 0x05, 0x0e, 0x2d, 0x8e, 0x7f, 0x0a, 0xca, 0xcf, 0x7e, 0x39, 0x60, 0x38, 0x50, 0x2c, 0x9e, 0xd6, 0x77, 0xe3, 0x5b, 0x2c, 0x72, 0x37, 0xca, 0x0e, 0x76, 0x6b, 0xa1, 0x46, 0xa3, 0x78, 0x38, 0xc9, 0xb1, 0xf6, 0x62, 0x40, 0x10, 0x56, 0xa9, 0xc1, 0x38, 0xcf, 0x80, 0x9e, 0xae, 0x01, 0x5a, 0x4e, 0xc3, 0x69, 0x35, 0x43, 0x79, 0x6f, 0xe0, 0x50, 0xab, 0xe4, 0x35, 0x72, 0xee, 0xb2, 0xa1, 0x1b, 0x64, 0x5e, 0xea, 0xef, 0x4f, 0x9b, 0x9a, 0x1b, 0x43, 0x96, 0x70, 0x69, 0x38, 0x12, 0x89, 0x96, 0xe8, 0x74, 0x7e, 0x35, 0xe5, 0x39, 0xf1, 0xdb, 0x6a, 0x50, 0x7d, 0xaa, 0x46, 0xa7, 0x3e, 0x6f, 0x40, 0x3a, 0x80, 0x25, 0xa5, 0x06, 0xd7, 0x73, 0xb1, 0xee, 0xca, 0x8d, 0x13, 0xaf, 0xdb, 0x17, 0xec, 0x58, 0xd8, 0x3c, 0xf7, 0x8a, 0x7a, 0x66, 0x50, 0x8e, 0x92, 0xb7, 0xb2, 0x4c, 0x67, 0x49, 0xdf, 0xb7, 0x6e, 0xfe, 0x4d, 0x83, 0xf5, 0xad, 0x9b, 0xbe, 0xbe, 0x6a, 0xc5, 0x0d, 0x75, 0x06, 0x5d, 0xd9, 0xe6, 0xdb, 0xab, 0xd2, 0x81, 0x0a, 0x93, 0xde, 0x2b, 0xb2, 0x6c, 0x7b, 0xcd, 0xd3, 0x8f, 0x2f, 0xdf, 0x39, 0xbf, 0xac, 0x67, 0xf2, 0xf0, 0x2d, 0x76, 0x0b, 0x0e, 0x8b, 0x57, 0x17, 0xbb, 0x78, 0xc6, 0xe2, 0x49, 0xdb, 0x4f, 0x6e, 0xdd, 0x78, 0x72, 0xcf, 0xac, 0xc9, 0x6d, 0xd3, 0x62, 0x9f, 0x1f, 0x7a, 0x81, 0x84, 0xcd, 0x43, 0xe3, 0x27, 0x35, 0xf2, 0x1e, 0x7b, 0x11, 0xa7, 0x01, 0xe5, 0xe0, 0xfc, 0xb4, 0x0e, 0xc7, 0x82, 0x2c, 0xc7, 0x19, 0x52, 0xe5, 0xb8, 0x86, 0x61, 0x7c, 0xcf, 0xc0, 0x10, 0x51, 0xc4, 0xe7, 0xfc, 0xeb, 0x95, 0xb8, 0x81, 0xeb, 0xbb, 0x95, 0x53, 0xc7, 0x75, 0x3c, 0xc9, 0x60, 0x7f, 0x36, 0xb4, 0x0d, 0x3c, 0xc9, 0x60, 0x1f, 0x0d, 0x95, 0xd2, 0xf3, 0x49, 0xd5, 0x93, 0x4d, 0x0e, 0x9f, 0x49, 0x6e, 0x73, 0x2b, 0x61, 0x3b, 0x9b, 0x9d, 0xb4, 0x72, 0x93, 0xd6, 0x52, 0x50, 0x10, 0xaa, 0xeb, 0x6e, 0x98, 0xb6, 0x7e, 0x7a, 0xb4, 0xb4, 0xeb, 0xbc, 0xce, 0x8e, 0xd9, 0xf5, 0x61, 0x9b, 0xdb, 0xd8, 0x18, 0x9a, 0xbf, 0xe2, 0xfc, 0x86, 0xa5, 0xdf, 0xb8, 0x74, 0x4a, 0xc7, 0xe5, 0x8f, 0x5c, 0x74, 0xc1, 0x7d, 0x9d, 0xdf, 0x37, 0x18, 0x3d, 0x7e, 0x4f, 0xed, 0xb2, 0x9d, 0xf3, 0xfa, 0x6f, 0x5c, 0x5a, 0x53, 0x1c, 0x2e, 0xb6, 0x4e, 0x8b, 0xb7, 0x45, 0xad, 0x53, 0xae, 0x3f, 0x75, 0xc5, 0xc6, 0xef, 0xef, 0x9e, 0x39, 0x25, 0x8d, 0xda, 0x99, 0x44, 0xed, 0x5c, 0x4f, 0xda, 0xd9, 0x9c, 0x6e, 0xc8, 0xb4, 0x73, 0x1c, 0xca, 0xeb, 0x55, 0xca, 0x6b, 0xf9, 0x1e, 0x99, 0x6c, 0xf1, 0x0b, 0x92, 0xbd, 0xee, 0xcf, 0x47, 0x36, 0x80, 0x23, 0xbb, 0xa5, 0xdb, 0x98, 0xe7, 0xf8, 0x07, 0x41, 0x12, 0xdc, 0x48, 0xc7, 0xaf, 0x0e, 0x27, 0x92, 0x2b, 0x86, 0x3c, 0x09, 0xb3, 0x8f, 0x7e, 0x30, 0xf8, 0x87, 0x48, 0x24, 0x01, 0x3f, 0x0e, 0x6a, 0x21, 0xab, 0x81, 0x9c, 0xc0, 0x72, 0xcb, 0xb3, 0x52, 0x6c, 0xe2, 0xc8, 0xf8, 0xa4, 0x39, 0x43, 0xb8, 0xbf, 0x2a, 0x71, 0x3e, 0x3a, 0xa4, 0x69, 0xa1, 0x81, 0xcd, 0x89, 0x9c, 0x20, 0x0e, 0x65, 0xde, 0x51, 0xce, 0xb2, 0xce, 0xef, 0x06, 0xa2, 0xa8, 0xbc, 0x81, 0x06, 0x75, 0xb2, 0xdc, 0x16, 0x8c, 0x44, 0x2c, 0xe8, 0x9f, 0x30, 0x1e, 0xd4, 0xb6, 0xec, 0x38, 0x4c, 0xe4, 0x98, 0xd1, 0x42, 0x32, 0x4a, 0x66, 0xf9, 0x29, 0x12, 0x57, 0x3d, 0x4b, 0xa8, 0x2e, 0xf5, 0x93, 0xb2, 0x39, 0x57, 0xf7, 0xd7, 0xf4, 0x26, 0x1d, 0xa2, 0xcb, 0x64, 0x2a, 0x8d, 0xcc, 0x9a, 0x7b, 0xe0, 0x40, 0xd9, 0x9c, 0x6b, 0xfa, 0xab, 0x11, 0x44, 0x70, 0x9b, 0x8c, 0x65, 0x08, 0x72, 0xdb, 0x6d, 0x5c, 0xed, 0x39, 0xbb, 0x06, 0xea, 0xad, 0xb6, 0x79, 0x6e, 0xb3, 0x69, 0x70, 0xf5, 0xf0, 0xdb, 0x7b, 0xa4, 0xf3, 0xcf, 0xd9, 0xbd, 0xb2, 0x49, 0x01, 0xc0, 0xe3, 0x7b, 0x10, 0x2f, 0xa6, 0x4a, 0xb7, 0xb1, 0x10, 0xf1, 0xa2, 0x05, 0xdc, 0x49, 0x1b, 0xab, 0xaf, 0x82, 0x1a, 0xd1, 0xaf, 0x25, 0x8a, 0x98, 0x0f, 0xff, 0xd2, 0x92, 0x5f, 0x8c, 0xca, 0x8d, 0x08, 0x96, 0x68, 0x39, 0xc7, 0xb5, 0x1e, 0xea, 0x74, 0x64, 0x0b, 0x39, 0x48, 0x32, 0xa7, 0x29, 0xe7, 0x0c, 0x43, 0xf8, 0x22, 0x31, 0x05, 0x34, 0x5a, 0x9d, 0x56, 0xa3, 0x1b, 0xe2, 0xd1, 0x64, 0x80, 0xcd, 0xef, 0x87, 0x80, 0x56, 0x3b, 0xc6, 0x7b, 0x50, 0x09, 0x35, 0x30, 0x84, 0x7d, 0xce, 0xec, 0x24, 0xe7, 0x5a, 0x4b, 0x63, 0x7d, 0x24, 0x86, 0xf8, 0x82, 0x19, 0x64, 0x40, 0xbc, 0x89, 0x38, 0xf2, 0xd9, 0xd0, 0x06, 0x83, 0xf8, 0xda, 0x38, 0x87, 0x31, 0xa3, 0x19, 0xc8, 0x42, 0x7f, 0x7a, 0x75, 0x77, 0x59, 0x4f, 0xd2, 0x2c, 0x78, 0x0d, 0xc6, 0xca, 0x40, 0xba, 0x4a, 0xfa, 0x90, 0x4d, 0xcc, 0xd9, 0x36, 0x3f, 0x9b, 0x53, 0xf9, 0xbc, 0xdc, 0xff, 0x9d, 0x8e, 0x0b, 0x17, 0xd4, 0x59, 0x6d, 0xbd, 0x1e, 0x93, 0x69, 0xde, 0x8c, 0xba, 0xde, 0x1d, 0xcb, 0xea, 0x14, 0x9e, 0x71, 0xed, 0xd9, 0x2c, 0xfd, 0x7c, 0x3a, 0xd1, 0x03, 0x46, 0x86, 0x91, 0xf2, 0x72, 0x29, 0x7f, 0x1f, 0x08, 0xe2, 0xbc, 0x84, 0x56, 0x33, 0x43, 0x36, 0xda, 0xd8, 0x37, 0x83, 0xc5, 0x06, 0x4a, 0x6a, 0xe7, 0x93, 0x28, 0x5f, 0xa1, 0x52, 0x2f, 0x59, 0x71, 0xd9, 0xcc, 0x00, 0xc7, 0x67, 0x1f, 0x38, 0x18, 0x7b, 0x94, 0x8e, 0xfd, 0x18, 0x1b, 0x5e, 0xbc, 0x66, 0x43, 0xfd, 0x79, 0x27, 0xae, 0xed, 0xae, 0x1d, 0xb8, 0x79, 0xf1, 0xa9, 0x95, 0xfb, 0x96, 0x55, 0xdc, 0x73, 0xf3, 0x84, 0x65, 0x53, 0xaa, 0xad, 0x6e, 0x23, 0x2f, 0xe2, 0xe1, 0xdc, 0xbe, 0xe5, 0xa1, 0x8b, 0x16, 0xdc, 0x75, 0xf5, 0x52, 0xaf, 0x14, 0x66, 0x26, 0x19, 0x27, 0x0d, 0x5e, 0xdd, 0xfd, 0xb5, 0xfb, 0x8a, 0x42, 0x45, 0x56, 0x44, 0xcb, 0x2e, 0x44, 0x8b, 0x80, 0x68, 0x49, 0x60, 0xcb, 0x17, 0x1b, 0xea, 0x90, 0x12, 0x4c, 0x06, 0xd1, 0xa9, 0x59, 0xa4, 0x01, 0x0c, 0x69, 0x44, 0x06, 0x6d, 0xd5, 0x39, 0x1e, 0x8f, 0x4b, 0xd9, 0x53, 0x15, 0x91, 0x46, 0xe2, 0xf3, 0xa2, 0xdf, 0x09, 0x90, 0xb0, 0x58, 0x2c, 0xde, 0x68, 0x28, 0x4e, 0x4f, 0xe8, 0x32, 0xa9, 0xee, 0x72, 0x65, 0x52, 0x24, 0x87, 0x02, 0xd8, 0x73, 0xf7, 0x49, 0x4c, 0x5e, 0xbc, 0x77, 0xe3, 0xb4, 0x09, 0xcb, 0xa6, 0x62, 0x02, 0x1b, 0x42, 0x94, 0xf8, 0x1d, 0x84, 0xf8, 0x7d, 0xfb, 0x4e, 0x1f, 0x84, 0x13, 0x21, 0x24, 0x34, 0xce, 0xba, 0x6c, 0x76, 0x1c, 0x53, 0x39, 0x2d, 0xd3, 0x84, 0xab, 0x96, 0x79, 0x6f, 0xdf, 0x73, 0xe2, 0xc4, 0x1e, 0xaa, 0x4f, 0x15, 0x23, 0xda, 0xef, 0x24, 0xb4, 0xb7, 0xa4, 0x1b, 0xcd, 0x68, 0x43, 0x10, 0x04, 0x32, 0xed, 0x54, 0xb4, 0x48, 0x08, 0x68, 0x4a, 0x7a, 0x86, 0xab, 0x84, 0x74, 0xec, 0x4f, 0xec, 0x0d, 0x3b, 0xcb, 0x88, 0xa5, 0x4b, 0x26, 0x0d, 0x1f, 0x39, 0x5e, 0xca, 0x4a, 0xd5, 0x67, 0x51, 0xa8, 0x4e, 0xc1, 0x7b, 0x16, 0xdf, 0x3c, 0x50, 0xdb, 0xbd, 0xe3, 0xc4, 0x79, 0xf5, 0x1b, 0xd6, 0x2c, 0x0e, 0x35, 0x18, 0xdd, 0xd6, 0xea, 0xa9, 0xcb, 0x26, 0x4c, 0xdb, 0xd8, 0x1b, 0xaf, 0x58, 0xb6, 0x6f, 0xe5, 0x9e, 0x3d, 0xa7, 0xef, 0xba, 0x0b, 0xfe, 0xca, 0xbb, 0xec, 0xaa, 0xbb, 0x16, 0x5c, 0xf4, 0xd0, 0x96, 0x76, 0x6b, 0xb4, 0x2d, 0x3e, 0xcd, 0x8a, 0x88, 0x8f, 0xcf, 0xbe, 0x6c, 0x56, 0xf7, 0xd5, 0x83, 0x93, 0x8c, 0x92, 0xb4, 0xe7, 0x91, 0x47, 0xf6, 0xd0, 0xbb, 0x64, 0x1c, 0x43, 0x73, 0x31, 0xfb, 0x31, 0xd0, 0x20, 0xbd, 0xb5, 0x26, 0x5d, 0xc9, 0xe3, 0xd3, 0x2e, 0x0e, 0x7b, 0xe1, 0xe2, 0x0b, 0x0f, 0x3c, 0xbe, 0x49, 0x42, 0x8d, 0x41, 0xae, 0x47, 0xa7, 0x85, 0xc0, 0xe9, 0xb0, 0x5b, 0xb5, 0x2e, 0x1d, 0xb6, 0x16, 0xd6, 0x20, 0x49, 0x53, 0xad, 0x85, 0x13, 0xd0, 0x62, 0x93, 0xb9, 0x4b, 0x42, 0x6c, 0xc2, 0xe7, 0xd6, 0xae, 0x5c, 0xb9, 0x76, 0xcf, 0x9e, 0x55, 0xfb, 0x97, 0x57, 0x56, 0x0f, 0xec, 0x1b, 0xd8, 0xf3, 0x04, 0xfc, 0x74, 0x70, 0xee, 0xac, 0x65, 0xd2, 0x57, 0xa0, 0x54, 0x35, 0x6f, 0xcb, 0xd4, 0xae, 0xcb, 0x17, 0x54, 0x4b, 0x77, 0xa2, 0x5a, 0x19, 0x60, 0x66, 0xf7, 0x31, 0xaf, 0x91, 0x00, 0xb7, 0x4e, 0x50, 0x99, 0x2e, 0xc7, 0xee, 0x53, 0xb0, 0x0b, 0x53, 0x81, 0x0f, 0xef, 0x57, 0xa0, 0xaa, 0x7b, 0xbb, 0x71, 0x7c, 0x99, 0x3e, 0x7c, 0x3a, 0x81, 0xab, 0x37, 0x63, 0x73, 0x0c, 0x0d, 0xd0, 0x08, 0x5a, 0x54, 0x79, 0x8c, 0x64, 0xb1, 0x44, 0xdc, 0x26, 0xbb, 0xe8, 0xa0, 0x48, 0xb3, 0x59, 0xc2, 0x37, 0x7a, 0x45, 0xb3, 0x46, 0x34, 0x8b, 0xbd, 0xf0, 0x17, 0x17, 0xd9, 0xdd, 0x42, 0xb5, 0xf4, 0xef, 0xd5, 0x9c, 0xd7, 0xba, 0x19, 0x16, 0xdd, 0xc1, 0x3d, 0xf8, 0xa2, 0xb5, 0xca, 0xef, 0xaf, 0xb2, 0xbe, 0x28, 0xfd, 0xd6, 0x6e, 0xda, 0xb9, 0xd3, 0xe8, 0x60, 0x5e, 0xa2, 0x7b, 0x8e, 0x41, 0x44, 0xc7, 0x6e, 0x44, 0x87, 0x8e, 0x44, 0x8f, 0x99, 0x94, 0xee, 0x2c, 0x1c, 0x27, 0x2e, 0x74, 0x6f, 0x26, 0x2e, 0x74, 0x1f, 0xdf, 0xe3, 0xb0, 0xe3, 0xb8, 0xd0, 0xe1, 0x12, 0x1c, 0x41, 0x46, 0x6f, 0xd3, 0x5b, 0xd5, 0xb8, 0xd0, 0x5a, 0x6a, 0xd4, 0x4a, 0x98, 0xe3, 0xb0, 0x38, 0x52, 0xd9, 0x56, 0xeb, 0x82, 0xe8, 0x94, 0x83, 0x42, 0xc3, 0x2b, 0xc2, 0x55, 0x7e, 0x7d, 0xe3, 0xe4, 0xab, 0x07, 0xd3, 0x93, 0x5b, 0xd3, 0xd3, 0xd7, 0x4c, 0x28, 0x6e, 0x99, 0x78, 0x4b, 0x78, 0xf2, 0x60, 0xa7, 0xf4, 0x06, 0xa7, 0xb3, 0x16, 0x46, 0xec, 0xe5, 0xd5, 0xa7, 0xe1, 0xbb, 0x27, 0xe3, 0xf5, 0x0d, 0x89, 0xcd, 0x85, 0xa9, 0x69, 0xc9, 0xd4, 0x39, 0xee, 0x92, 0xad, 0x95, 0x7d, 0x2d, 0x41, 0x44, 0x36, 0x43, 0x68, 0xfe, 0x00, 0xd1, 0x6c, 0x00, 0x1e, 0x7c, 0x6a, 0xa4, 0xb8, 0xf0, 0xf4, 0xaa, 0xd9, 0xa1, 0xfb, 0x40, 0x4f, 0xb0, 0x34, 0x18, 0xe6, 0xb4, 0x6e, 0xea, 0xb1, 0x11, 0xb4, 0x04, 0xe5, 0x30, 0x78, 0x38, 0xec, 0x2c, 0x22, 0x8f, 0xf9, 0x00, 0x07, 0xb1, 0x93, 0xde, 0x84, 0x1e, 0x35, 0x90, 0xdd, 0x35, 0xd7, 0xe0, 0x50, 0x76, 0x3c, 0xec, 0xbb, 0x6e, 0x79, 0x03, 0xf3, 0x52, 0xc3, 0xf2, 0xeb, 0x49, 0x50, 0x3a, 0xc9, 0x85, 0xf4, 0x0e, 0x3c, 0xce, 0x71, 0x9d, 0x75, 0xa8, 0x4e, 0x3d, 0xee, 0x2d, 0x40, 0x97, 0xc9, 0xe5, 0x58, 0xd1, 0x22, 0xd1, 0xe7, 0x7b, 0xbb, 0x95, 0x84, 0xa6, 0x7d, 0x6c, 0x4e, 0xec, 0x55, 0xad, 0x27, 0x61, 0x23, 0x72, 0x87, 0x63, 0xcc, 0x07, 0x1d, 0x9f, 0x7d, 0xf8, 0x21, 0x6c, 0x96, 0x2a, 0xe1, 0xcf, 0xf8, 0x3f, 0x3e, 0x7e, 0xf1, 0x13, 0x97, 0x31, 0x2f, 0x5d, 0x8a, 0xcb, 0xae, 0x63, 0x6f, 0x64, 0x9e, 0x16, 0xb6, 0xa0, 0xb5, 0xad, 0x21, 0x5d, 0x6b, 0x45, 0x13, 0x6f, 0x29, 0x92, 0x18, 0xb4, 0x6a, 0x71, 0x58, 0x77, 0x05, 0x3c, 0x07, 0xf8, 0xe5, 0xca, 0x1e, 0x6a, 0xbe, 0x9a, 0xe6, 0x65, 0x01, 0xec, 0xb1, 0x95, 0xc6, 0x4b, 0x43, 0x05, 0x82, 0xd6, 0x8b, 0x23, 0x5c, 0x8d, 0x0a, 0x0c, 0x28, 0x47, 0x4e, 0x57, 0x34, 0xae, 0x3a, 0x6c, 0xef, 0x01, 0xff, 0xe9, 0xd3, 0xc6, 0x1d, 0x0b, 0xfb, 0xae, 0x1f, 0x68, 0xac, 0x1f, 0xdc, 0xb3, 0x60, 0xe1, 0xb5, 0xc9, 0x84, 0x3e, 0x60, 0xb3, 0x96, 0xfc, 0x7f, 0xcc, 0x9d, 0x07, 0x7c, 0x14, 0xe5, 0xfa, 0xef, 0xdf, 0xd9, 0x94, 0xdd, 0xd9, 0x40, 0x20, 0x24, 0x54, 0x43, 0x58, 0x7a, 0x87, 0x50, 0x42, 0x09, 0x2d, 0xf4, 0xde, 0x91, 0x22, 0x45, 0x52, 0x16, 0x12, 0x48, 0x23, 0x09, 0x20, 0x08, 0x22, 0xa0, 0x18, 0x51, 0xb0, 0x71, 0x00, 0x2b, 0xa8, 0x88, 0xe2, 0x51, 0x8f, 0xbd, 0x1d, 0x3d, 0x47, 0xb1, 0x82, 0x82, 0xa2, 0x21, 0x31, 0x01, 0x12, 0x10, 0x36, 0x65, 0x03, 0xa4, 0x90, 0x00, 0xc9, 0x2e, 0x79, 0xef, 0x77, 0x66, 0x27, 0x10, 0x50, 0xcf, 0xd1, 0xfb, 0xbf, 0xf7, 0x73, 0xaf, 0x7c, 0xbe, 0xce, 0xec, 0xec, 0xcc, 0x5b, 0x7f, 0xcf, 0xf3, 0x3e, 0xcf, 0x64, 0x77, 0xb6, 0x63, 0x68, 0x48, 0x9f, 0x99, 0x83, 0x5a, 0xb5, 0x19, 0x32, 0xb3, 0x77, 0x97, 0xa1, 0xc1, 0xb3, 0xfc, 0x1b, 0xfa, 0x58, 0x6b, 0x7e, 0xe8, 0xdc, 0xbe, 0xf6, 0x29, 0x7e, 0xfd, 0xbb, 0x8c, 0x69, 0x68, 0xa9, 0xe7, 0xd7, 0xa8, 0xbe, 0xb9, 0x6d, 0xc4, 0xfc, 0xfe, 0x83, 0x17, 0x45, 0xb4, 0xf1, 0xaf, 0xbf, 0xba, 0x61, 0x3d, 0x51, 0x3b, 0x2e, 0x99, 0xfa, 0xb8, 0xdc, 0xfa, 0x2c, 0xda, 0xa9, 0xfa, 0xb3, 0x68, 0xa7, 0x89, 0x5b, 0xc7, 0xa3, 0xee, 0xb3, 0x68, 0x4d, 0x99, 0xbb, 0x6a, 0x3e, 0xd9, 0xb5, 0x4b, 0x19, 0xe5, 0x9b, 0x7c, 0xad, 0x2d, 0x23, 0xf3, 0xb5, 0xe9, 0x54, 0xcd, 0x61, 0xa5, 0x1f, 0xa5, 0x44, 0x7b, 0x3d, 0x21, 0xaa, 0xb5, 0xa7, 0x38, 0xeb, 0xcf, 0x10, 0xd0, 0x27, 0x57, 0x2f, 0x2c, 0xa0, 0xa1, 0x49, 0x6d, 0xd2, 0x35, 0x90, 0xcb, 0xab, 0x77, 0xed, 0xa2, 0xee, 0x53, 0x9c, 0xdb, 0xdc, 0xeb, 0x21, 0x65, 0x3d, 0xe7, 0x06, 0x6a, 0xf7, 0xf7, 0xcc, 0xc6, 0x77, 0xfb, 0xf5, 0xfa, 0x8d, 0x85, 0x7f, 0xae, 0x98, 0x14, 0xd8, 0xba, 0xdd, 0x0d, 0x3d, 0xb4, 0xa9, 0xbb, 0x02, 0xf5, 0x51, 0xd6, 0x87, 0x0c, 0x5d, 0x30, 0xa4, 0xff, 0x00, 0x55, 0x69, 0xd8, 0xc0, 0x72, 0x5b, 0x50, 0xff, 0x7e, 0xbb, 0xbc, 0x9f, 0x1e, 0x1a, 0x33, 0xb6, 0x63, 0xfd, 0x05, 0xf5, 0x1a, 0xfa, 0x0e, 0x9b, 0xa2, 0x64, 0xd7, 0xf6, 0x33, 0x8f, 0x3a, 0x82, 0xb5, 0x7e, 0xfa, 0x69, 0x77, 0xc8, 0x27, 0x88, 0xeb, 0x1f, 0x3f, 0xd1, 0x9a, 0xe7, 0x8d, 0xcf, 0x98, 0x66, 0xc2, 0xab, 0xb5, 0xef, 0xd8, 0xce, 0xa6, 0xcd, 0x49, 0x60, 0x9f, 0xde, 0xc3, 0xf4, 0x67, 0x37, 0x7a, 0xf9, 0x9a, 0x43, 0x94, 0xa6, 0x81, 0xfa, 0x33, 0xaf, 0x7a, 0x28, 0x7a, 0xff, 0xfb, 0x54, 0x87, 0xb4, 0x0f, 0xf8, 0xd8, 0xbf, 0x65, 0xd3, 0x4d, 0x45, 0x8d, 0x5b, 0x04, 0xb4, 0xac, 0x5f, 0x7a, 0x7f, 0x8b, 0x16, 0xea, 0xa7, 0xd6, 0xdb, 0x9a, 0x76, 0xd9, 0xb9, 0xd3, 0x67, 0x64, 0xd3, 0xcb, 0x8d, 0xfc, 0x6b, 0x32, 0x82, 0xfd, 0xcd, 0x4d, 0x03, 0x6b, 0x5e, 0x6d, 0xe0, 0xaf, 0xf8, 0xfa, 0xfb, 0xd5, 0x7c, 0xa9, 0x1b, 0xac, 0xa7, 0x1d, 0x8d, 0x7d, 0xb5, 0x0f, 0xa4, 0xb5, 0xd2, 0x7f, 0x2a, 0x65, 0xaa, 0xfe, 0x1c, 0x6c, 0xfd, 0xfb, 0xdf, 0x9e, 0xa1, 0xf6, 0x11, 0x3e, 0x01, 0x01, 0xde, 0x8c, 0x91, 0x82, 0xe2, 0xfa, 0x04, 0x98, 0x1a, 0xd7, 0x4c, 0xdd, 0xe9, 0xdd, 0x68, 0x83, 0xe9, 0xd4, 0xb5, 0x17, 0x8c, 0xeb, 0xbd, 0xb6, 0x72, 0x7d, 0x6b, 0xed, 0x6f, 0x87, 0x2d, 0xea, 0xf9, 0xfc, 0xf6, 0x39, 0xc7, 0x75, 0xe7, 0xad, 0xb5, 0xb0, 0xb5, 0x6e, 0x12, 0x16, 0xe0, 0xab, 0x1a, 0xcf, 0x39, 0xee, 0xaf, 0xfd, 0x80, 0x33, 0xab, 0xdd, 0x8d, 0x87, 0x1c, 0x07, 0x7a, 0x1e, 0x72, 0xac, 0xfd, 0x65, 0xc0, 0x6b, 0xeb, 0xfd, 0x5e, 0x3f, 0x35, 0x6c, 0x1f, 0xd4, 0x2e, 0x20, 0xc3, 0x64, 0xba, 0x2f, 0x25, 0x28, 0xc8, 0x7a, 0x54, 0x6d, 0x68, 0xb1, 0x04, 0xa8, 0xdf, 0xab, 0xd8, 0xbe, 0x36, 0xc1, 0xff, 0x6c, 0xd5, 0xa1, 0x26, 0xa5, 0x75, 0x6b, 0xe5, 0x91, 0xc0, 0x76, 0x0d, 0x6a, 0x46, 0x9b, 0x4e, 0x99, 0x03, 0x6a, 0xd6, 0x07, 0x74, 0x0a, 0x6a, 0xdc, 0xa9, 0x91, 0xb2, 0x31, 0xc0, 0x7c, 0x93, 0x9e, 0x82, 0x44, 0xdb, 0x08, 0x9b, 0xaf, 0x72, 0xfd, 0x91, 0xd2, 0x53, 0xf5, 0x47, 0x4a, 0x7b, 0x5a, 0x15, 0x24, 0x82, 0x5a, 0x37, 0x0d, 0xf0, 0x51, 0x9b, 0x7a, 0x3e, 0xc0, 0xd1, 0x51, 0x69, 0x72, 0xfd, 0x91, 0xcb, 0x3d, 0x94, 0x30, 0x44, 0x35, 0x25, 0xa0, 0xb9, 0x5f, 0xcd, 0xe3, 0x5e, 0xf7, 0x4d, 0x09, 0x6a, 0x54, 0x5f, 0x49, 0x36, 0x99, 0xb4, 0xca, 0xeb, 0xf9, 0x2b, 0xdf, 0x77, 0x68, 0x55, 0x73, 0xa7, 0xe9, 0x94, 0xa5, 0x61, 0x4d, 0xef, 0xa0, 0xf6, 0x01, 0xca, 0x33, 0xc2, 0xf0, 0x25, 0x77, 0xeb, 0x7e, 0xf8, 0x0f, 0xd6, 0x81, 0xa9, 0x9a, 0xc5, 0x4d, 0xfb, 0xdd, 0x75, 0x40, 0xbd, 0xb1, 0x0e, 0x34, 0xbe, 0xb1, 0x0e, 0x10, 0xff, 0x29, 0x77, 0x8c, 0x1b, 0x31, 0x72, 0xec, 0xe2, 0xc5, 0x13, 0x13, 0xc6, 0xb6, 0x69, 0x3b, 0x2e, 0x61, 0xfc, 0xe2, 0x9d, 0x3e, 0x3e, 0x23, 0x06, 0xf7, 0x1b, 0x5e, 0xa9, 0x2c, 0x6f, 0x37, 0x6c, 0x4e, 0x9f, 0x7e, 0xf3, 0x23, 0xda, 0xb9, 0xf4, 0xf9, 0xf4, 0xd4, 0xff, 0xaa, 0x5e, 0x7f, 0x2b, 0xed, 0x7b, 0x76, 0x2d, 0x75, 0x65, 0xf9, 0xd6, 0x7e, 0xb2, 0xa9, 0x36, 0xfa, 0xbe, 0x2e, 0x2f, 0xed, 0xcb, 0xf1, 0x81, 0x8d, 0x02, 0x1a, 0xf8, 0xa9, 0xbf, 0xd3, 0x06, 0x3d, 0x99, 0xaa, 0x55, 0x58, 0xad, 0xc7, 0x6d, 0xaa, 0x8c, 0x9b, 0x30, 0x72, 0xf4, 0xd8, 0xa9, 0xca, 0x9b, 0x0f, 0xb4, 0x0f, 0x7c, 0xd8, 0xd2, 0x24, 0xa8, 0xcf, 0x4e, 0xc7, 0xa4, 0x84, 0x31, 0xb6, 0xa9, 0x13, 0x5b, 0x06, 0xf9, 0x34, 0x19, 0xdd, 0xa7, 0xcf, 0xc0, 0x73, 0x35, 0xeb, 0x43, 0x72, 0x91, 0x58, 0xb1, 0xe9, 0x94, 0x6d, 0xe0, 0xd4, 0x9e, 0xe3, 0x13, 0x5b, 0x04, 0x84, 0xe8, 0x73, 0xd1, 0x82, 0xb6, 0x2d, 0xa4, 0x6d, 0x66, 0xed, 0x77, 0x95, 0x4d, 0xb5, 0xbf, 0x72, 0x3c, 0x55, 0x8b, 0x8a, 0xa6, 0x29, 0x9e, 0xa7, 0x33, 0x68, 0xc2, 0xa8, 0x7d, 0xa4, 0xc8, 0xc2, 0xcc, 0x5d, 0x99, 0xa6, 0x91, 0x3e, 0x97, 0xae, 0xed, 0x35, 0x2d, 0x5e, 0xaf, 0x5d, 0x7f, 0x87, 0xee, 0xd7, 0x92, 0xaf, 0x3f, 0xa3, 0x4a, 0xb7, 0x12, 0x23, 0x80, 0x99, 0xab, 0xf5, 0xa5, 0x51, 0x40, 0x3d, 0x2b, 0xee, 0x4d, 0x7b, 0x46, 0x95, 0xd1, 0x0f, 0xed, 0x33, 0x38, 0xc6, 0xdd, 0x0c, 0xfd, 0xe3, 0x38, 0xca, 0xf6, 0x89, 0x33, 0x76, 0x76, 0x9d, 0x9a, 0x32, 0x76, 0x5c, 0xca, 0xd4, 0xae, 0x3b, 0x67, 0x7a, 0x37, 0x5c, 0x30, 0x4d, 0x79, 0xb7, 0x66, 0xd2, 0xc8, 0xe4, 0xe9, 0xdd, 0xbb, 0x4f, 0x4f, 0x1e, 0xa9, 0xed, 0x4f, 0x5b, 0xa0, 0x7f, 0xe7, 0xd7, 0xeb, 0x09, 0xef, 0xc1, 0xd4, 0xd5, 0x51, 0xbb, 0xff, 0x6d, 0x0b, 0xd4, 0x9f, 0x45, 0xee, 0xf9, 0x41, 0xf2, 0xba, 0xca, 0xe1, 0xdd, 0xf0, 0xd6, 0xcd, 0x3c, 0x7a, 0xf6, 0x6a, 0x5b, 0xfb, 0xa3, 0xe4, 0xda, 0x1d, 0x7f, 0x5d, 0xc8, 0xfd, 0x34, 0x1d, 0x0f, 0x52, 0x34, 0x59, 0x37, 0xa9, 0x55, 0xb5, 0xf7, 0xe0, 0x5f, 0x1f, 0xdc, 0xa9, 0xe5, 0x04, 0xbe, 0xde, 0xbb, 0xd3, 0xcf, 0xde, 0xd3, 0xd9, 0x9a, 0xe2, 0x63, 0xf6, 0xb1, 0xd4, 0xbb, 0xdf, 0xa7, 0x79, 0x50, 0xb7, 0xa0, 0xa6, 0xe6, 0xfb, 0xea, 0x59, 0x48, 0x8e, 0x53, 0xbd, 0x6e, 0x0b, 0xd4, 0x15, 0x9e, 0xe3, 0xd7, 0xcc, 0xdf, 0xbf, 0x99, 0x5f, 0x4d, 0x07, 0xd3, 0xa9, 0x36, 0x35, 0xcb, 0x1a, 0x75, 0x6a, 0xda, 0xbd, 0x9d, 0xb2, 0xb3, 0x7e, 0xe3, 0xc0, 0x06, 0x35, 0xcb, 0xda, 0x74, 0x6d, 0xdc, 0x31, 0x40, 0xd9, 0xe9, 0x1f, 0xa4, 0x8f, 0xed, 0x17, 0x5e, 0x47, 0x4c, 0x8d, 0x7c, 0x27, 0x08, 0x55, 0x7b, 0xfe, 0xa1, 0xfe, 0xa9, 0x73, 0xed, 0xc1, 0xcc, 0x71, 0x0a, 0x2b, 0xbc, 0xf6, 0xe0, 0x3a, 0xcf, 0x97, 0x43, 0x9a, 0x98, 0xbd, 0xf4, 0x1f, 0xff, 0x56, 0xbe, 0xe9, 0xd8, 0xae, 0xe1, 0x47, 0xbe, 0xcd, 0x02, 0x6f, 0xdf, 0xe4, 0x3b, 0xa1, 0x5f, 0x87, 0x1a, 0xef, 0xfa, 0xcd, 0x4c, 0xeb, 0x3d, 0x71, 0x0c, 0xe5, 0x78, 0x3d, 0xab, 0x97, 0x13, 0xa8, 0x59, 0x8c, 0xe7, 0xb9, 0x78, 0x22, 0x45, 0x1b, 0xed, 0x38, 0xed, 0x69, 0x78, 0x8d, 0x1a, 0x6a, 0x4f, 0xc3, 0xf3, 0x14, 0xea, 0x7b, 0x53, 0xa1, 0xad, 0x7f, 0xaf, 0x78, 0x53, 0xfd, 0xdf, 0xa9, 0xe9, 0x46, 0x8d, 0x8a, 0x88, 0xf4, 0x8e, 0x53, 0x42, 0x7d, 0xf2, 0x3d, 0xdf, 0x67, 0xf3, 0xdc, 0xbb, 0xd2, 0x8c, 0x54, 0xff, 0xfc, 0x85, 0xe7, 0x2b, 0x09, 0x42, 0xff, 0x3e, 0x9b, 0xe7, 0x79, 0x12, 0x75, 0x3f, 0x45, 0xa2, 0x9d, 0x55, 0xf7, 0xfb, 0x6c, 0x9e, 0xcf, 0x64, 0x34, 0xae, 0x5d, 0xb4, 0x94, 0xd0, 0x1e, 0x7d, 0xfb, 0xf6, 0xe8, 0x32, 0x71, 0x60, 0xeb, 0xd6, 0x03, 0x27, 0x76, 0xf1, 0x49, 0xef, 0xd7, 0xad, 0x6b, 0xbf, 0x05, 0xda, 0x53, 0x4b, 0xba, 0x8e, 0xe9, 0x13, 0xec, 0x89, 0x35, 0xff, 0x5f, 0xd6, 0x3d, 0xc8, 0x6b, 0x87, 0xa9, 0xb9, 0xef, 0x6a, 0xac, 0xb4, 0x57, 0x44, 0x8f, 0xfa, 0xac, 0xfe, 0x2d, 0x6f, 0x8e, 0x73, 0xa9, 0x7d, 0x85, 0xd0, 0x9f, 0xf7, 0x26, 0xae, 0x3f, 0xee, 0xad, 0x59, 0xdb, 0x0e, 0x1d, 0xf5, 0xbb, 0x9d, 0x3d, 0x94, 0xdf, 0x44, 0xb7, 0xbe, 0x9e, 0xe0, 0xb6, 0xed, 0xe5, 0xf9, 0x8b, 0x3b, 0x8f, 0x8d, 0x1c, 0xd0, 0x7c, 0xe0, 0x80, 0xb0, 0x26, 0x9d, 0x2c, 0x01, 0xf5, 0x5a, 0xb4, 0xee, 0x1a, 0x12, 0x31, 0x2a, 0xa8, 0xfb, 0xd8, 0xbe, 0x5b, 0xbd, 0xb7, 0xd8, 0xd2, 0x67, 0x0e, 0x5e, 0x30, 0xd4, 0x66, 0x09, 0x6a, 0xd3, 0xa2, 0x4f, 0x7d, 0xff, 0x86, 0xfe, 0x63, 0xc6, 0xd9, 0xc2, 0x7b, 0xb6, 0x31, 0xd7, 0x1c, 0xd2, 0xef, 0x85, 0x49, 0xed, 0x23, 0x2f, 0x67, 0xb4, 0xdf, 0xd4, 0xf1, 0x7a, 0x56, 0x7f, 0xc6, 0xe8, 0xf9, 0x6b, 0xaf, 0xdd, 0xfc, 0xfb, 0xd9, 0xda, 0xef, 0x5f, 0x68, 0xe7, 0x68, 0xbf, 0x35, 0xa2, 0x9d, 0xa3, 0xff, 0xd6, 0xc8, 0x45, 0xed, 0xda, 0x6b, 0x9f, 0xeb, 0xd7, 0x1e, 0xac, 0x3d, 0xee, 0x13, 0x71, 0xad, 0xb5, 0x7e, 0xfc, 0xbd, 0x5b, 0x8e, 0x8f, 0xb8, 0xd6, 0x51, 0xff, 0x1d, 0xe3, 0x50, 0xbd, 0x9c, 0xef, 0xae, 0x1f, 0x1f, 0x7e, 0x4d, 0x7f, 0xa6, 0x6a, 0x8d, 0xb3, 0x6e, 0x1b, 0xb4, 0xdf, 0x55, 0xba, 0xf6, 0x9c, 0x7e, 0x3c, 0xe3, 0x96, 0xe3, 0x83, 0xae, 0x55, 0x88, 0x1b, 0xed, 0x79, 0xf1, 0x7a, 0x7b, 0x3e, 0xf4, 0xb4, 0xc7, 0x73, 0xdc, 0x1c, 0x78, 0xfd, 0xf8, 0xc7, 0x9e, 0xe3, 0x35, 0x3f, 0xdd, 0xd2, 0x9e, 0x81, 0x5a, 0xbd, 0xcc, 0xc7, 0x5b, 0x35, 0x2f, 0xf9, 0xb6, 0xd6, 0x7f, 0xf3, 0xbd, 0x4f, 0x44, 0x28, 0x8b, 0x9e, 0xc9, 0xec, 0x63, 0x8a, 0xd4, 0x3e, 0xcc, 0x10, 0x3d, 0xd1, 0xa2, 0x98, 0xcd, 0xca, 0x1d, 0x9a, 0x05, 0xd4, 0xfe, 0xfa, 0x2a, 0xff, 0x6f, 0x84, 0xb3, 0xd1, 0xff, 0x6b, 0xab, 0x65, 0x49, 0xed, 0x03, 0x82, 0x7c, 0x6f, 0x0a, 0x44, 0xfa, 0x04, 0xf8, 0xb6, 0xee, 0x10, 0x72, 0x4f, 0xf1, 0xb5, 0xc0, 0xcc, 0x4c, 0xd3, 0x85, 0xe3, 0x2f, 0xd7, 0x0c, 0x0e, 0x6e, 0xa3, 0xcc, 0x37, 0x3d, 0x70, 0x6d, 0x8d, 0xcf, 0x33, 0xd7, 0xb6, 0x98, 0xd6, 0xba, 0xfc, 0x68, 0xe3, 0x4c, 0x59, 0xe6, 0x33, 0x59, 0x1f, 0xcb, 0x9f, 0x8c, 0xdf, 0x65, 0x69, 0x5c, 0xfb, 0x7c, 0x60, 0x9f, 0x62, 0xfd, 0x78, 0x86, 0x71, 0x7c, 0x97, 0xe7, 0x79, 0xf7, 0x35, 0xff, 0xf4, 0x5e, 0xe3, 0x4d, 0xd0, 0x2e, 0x86, 0x8b, 0x13, 0x11, 0xfe, 0x5d, 0x14, 0x5f, 0x9f, 0xfe, 0x58, 0x7a, 0x23, 0x6d, 0xb5, 0x35, 0xee, 0x2c, 0xf5, 0x12, 0xbe, 0x3e, 0xde, 0x3e, 0xbe, 0xde, 0xc9, 0xc2, 0xdb, 0xaa, 0x7f, 0x6d, 0x31, 0x12, 0x6f, 0xaf, 0x27, 0xdc, 0x89, 0x13, 0x6b, 0x55, 0x1d, 0x7f, 0xe3, 0x7e, 0x5c, 0xec, 0xf5, 0xdb, 0x77, 0x7f, 0xe6, 0xc2, 0xa4, 0x9b, 0x2f, 0x8c, 0x18, 0x70, 0xfd, 0x1a, 0xd5, 0xcb, 0xe2, 0xa5, 0xdd, 0xd0, 0xfb, 0x4f, 0xd7, 0x8a, 0x1b, 0x97, 0xce, 0x9b, 0x17, 0xd1, 0x42, 0x88, 0x61, 0x43, 0x06, 0x87, 0x87, 0xf6, 0xe8, 0xd4, 0xa1, 0x6d, 0xeb, 0x96, 0xb7, 0x35, 0x0e, 0xac, 0xef, 0xa7, 0xa5, 0x23, 0xed, 0xf4, 0x9f, 0xc7, 0xb8, 0xf5, 0xa6, 0x9e, 0xaf, 0x27, 0x14, 0xed, 0xd7, 0xa7, 0xb5, 0x27, 0x06, 0xad, 0x9b, 0x88, 0xfa, 0x9a, 0x6f, 0xdc, 0xda, 0xab, 0x6a, 0x1d, 0xb1, 0x70, 0xd0, 0xdd, 0xdb, 0x1b, 0x29, 0xf1, 0xc6, 0x4d, 0xbd, 0x45, 0x69, 0x0b, 0x9f, 0x4d, 0x89, 0x08, 0xbc, 0xcd, 0xf4, 0x94, 0x57, 0x8f, 0xe9, 0xc9, 0xa3, 0x87, 0x2f, 0x1a, 0x1d, 0x4a, 0x7e, 0x3a, 0xa0, 0xdd, 0xa2, 0x65, 0x2b, 0xfa, 0xc7, 0xbd, 0xb3, 0x79, 0x7c, 0xef, 0xd0, 0xda, 0x1f, 0xcb, 0x50, 0xb6, 0x85, 0x47, 0x8d, 0xeb, 0xb4, 0xfd, 0x9e, 0x6b, 0x33, 0x3d, 0x3f, 0x79, 0xd1, 0xb6, 0xeb, 0x90, 0xb4, 0x03, 0x09, 0x3d, 0x5a, 0xcd, 0x4f, 0x9f, 0xd7, 0xed, 0x7a, 0xc2, 0x3a, 0x76, 0xeb, 0x77, 0xf7, 0x2d, 0x7c, 0x61, 0xc4, 0x27, 0x9e, 0x9b, 0x7c, 0xfa, 0x5c, 0x4d, 0x90, 0xe5, 0x5e, 0x19, 0xda, 0x6f, 0x16, 0x78, 0x0b, 0xe3, 0x77, 0x21, 0xee, 0xbf, 0xfe, 0x2c, 0xe1, 0x36, 0xfa, 0x71, 0x93, 0x71, 0xfc, 0xef, 0xfa, 0x7d, 0x82, 0x52, 0xef, 0x91, 0x5e, 0xa1, 0x3e, 0xf7, 0x93, 0x69, 0x2c, 0xf5, 0xdc, 0x62, 0x6d, 0xac, 0x7f, 0x45, 0x77, 0xa9, 0xfe, 0xd5, 0x0d, 0xdc, 0x4c, 0x0c, 0x23, 0xd4, 0x5c, 0x9f, 0x95, 0x6e, 0xfa, 0xe7, 0xb3, 0x71, 0x3e, 0xa9, 0xb7, 0xbe, 0x17, 0x71, 0x9b, 0xfe, 0x80, 0x2f, 0xe3, 0xbd, 0xda, 0xaf, 0xad, 0x2c, 0xd0, 0xbf, 0xfb, 0xa1, 0x3d, 0x56, 0xa8, 0x9e, 0xe7, 0x67, 0x41, 0x49, 0x59, 0x7c, 0x7c, 0x9b, 0x92, 0x2f, 0x5c, 0x0f, 0xe4, 0x5b, 0x87, 0x5d, 0xbf, 0xa1, 0xf6, 0xe2, 0xc0, 0x66, 0xdd, 0xda, 0x04, 0x05, 0xb6, 0xe9, 0xd6, 0xc2, 0xd4, 0xc8, 0xd8, 0xed, 0xde, 0xdc, 0x7b, 0xcf, 0x02, 0xff, 0x96, 0x5d, 0x5a, 0xb6, 0xea, 0x16, 0x5c, 0x9f, 0x9d, 0xae, 0xc1, 0xda, 0x8e, 0xf6, 0xe1, 0x4d, 0x5f, 0xe1, 0x15, 0x6a, 0x6e, 0x88, 0x4d, 0x69, 0x9f, 0x0f, 0xf3, 0xf2, 0xd1, 0x9e, 0xa4, 0x20, 0xea, 0x3e, 0x5f, 0xca, 0x5b, 0xcb, 0xf1, 0xa6, 0x2a, 0x22, 0xb0, 0x51, 0x43, 0x7f, 0xd5, 0x62, 0xf6, 0x15, 0x4d, 0x95, 0xa6, 0xbe, 0x9e, 0xef, 0x3a, 0x79, 0x93, 0xab, 0x0d, 0xf5, 0xd6, 0x3e, 0xd8, 0xa3, 0x3d, 0x2d, 0xd2, 0xd4, 0xba, 0xcb, 0x84, 0x09, 0x93, 0x17, 0x24, 0x8d, 0x19, 0x1c, 0x33, 0x67, 0xfa, 0xcc, 0x3b, 0xfa, 0x6d, 0x59, 0xfb, 0x70, 0x93, 0x76, 0x3d, 0x7d, 0x77, 0x36, 0x69, 0xd3, 0xa1, 0x4d, 0x93, 0xbb, 0x9a, 0xd2, 0xe2, 0xa6, 0xdb, 0x36, 0x77, 0x19, 0xd8, 0x56, 0x7f, 0xca, 0x17, 0x99, 0xca, 0x26, 0xc6, 0xea, 0x71, 0xea, 0xd5, 0x9f, 0x69, 0xa6, 0xd7, 0x72, 0xa7, 0x4f, 0xed, 0xcf, 0x72, 0x35, 0x17, 0x93, 0x6e, 0xa9, 0xd1, 0xf7, 0x4f, 0xd7, 0xe8, 0xd3, 0xec, 0xb7, 0x35, 0xfe, 0x3f, 0xeb, 0xa7, 0xd2, 0xc9, 0xe7, 0xac, 0x29, 0xd0, 0x1c, 0x41, 0xe6, 0xce, 0x3a, 0x14, 0xa0, 0x3d, 0x6c, 0x4a, 0xf7, 0xfc, 0x4b, 0x6f, 0x5d, 0x70, 0xb4, 0xbf, 0xe1, 0xe0, 0x06, 0x52, 0x7f, 0xb3, 0x10, 0xbd, 0xd7, 0xb7, 0x83, 0xe7, 0xd9, 0x70, 0x86, 0x99, 0xe8, 0xcf, 0x4f, 0xf4, 0xfc, 0x0a, 0x67, 0xd3, 0x26, 0x8d, 0x4d, 0x62, 0xc0, 0xf4, 0xb0, 0x16, 0x23, 0x06, 0xf7, 0x1d, 0xd8, 0x7b, 0xb0, 0xa5, 0xbe, 0xaf, 0x77, 0x87, 0xb6, 0x9d, 0x7d, 0xce, 0xb6, 0x1d, 0x34, 0xb9, 0xcb, 0xc8, 0xd1, 0xe1, 0xdd, 0xdb, 0xf7, 0x57, 0x94, 0xf6, 0x1d, 0x2c, 0x96, 0x66, 0x5a, 0x3b, 0xbc, 0xed, 0xa6, 0x40, 0x9f, 0x83, 0xb4, 0x63, 0x86, 0xa7, 0x1d, 0x01, 0x46, 0x3b, 0x8c, 0x84, 0xd8, 0xa6, 0xeb, 0xb2, 0x87, 0xe7, 0x87, 0x74, 0xb5, 0x76, 0xd4, 0x39, 0xfe, 0x47, 0x4d, 0xf3, 0xb4, 0xcd, 0xf7, 0x2f, 0xb4, 0xcd, 0xdb, 0xfe, 0xbb, 0x6d, 0xfb, 0xff, 0x65, 0x8c, 0x7c, 0xf2, 0x68, 0xc7, 0x60, 0xda, 0x31, 0xfa, 0x3d, 0x1f, 0xc5, 0xf3, 0xad, 0x77, 0x4f, 0x73, 0xb4, 0x4f, 0x46, 0x2c, 0xbd, 0xf5, 0x4f, 0xec, 0x5a, 0x73, 0x58, 0xba, 0x53, 0x7f, 0xf3, 0xa4, 0xcd, 0xf7, 0x9a, 0xb4, 0xeb, 0xa8, 0x37, 0xa7, 0xaf, 0xf6, 0x75, 0x77, 0xe3, 0x2b, 0x58, 0xd7, 0x9f, 0xc5, 0xa8, 0x0c, 0xea, 0xdc, 0xa1, 0xbd, 0xc9, 0xb7, 0xbe, 0x65, 0x70, 0xef, 0x81, 0x7d, 0x07, 0x8f, 0x68, 0x11, 0x36, 0xa3, 0xbf, 0xaf, 0xa5, 0x99, 0xc5, 0xd2, 0xa1, 0xa3, 0xc9, 0xd4, 0xbf, 0x7d, 0xf7, 0xf0, 0xe9, 0x23, 0xbb, 0x4c, 0xe8, 0xd7, 0x4a, 0xd7, 0x8e, 0x77, 0x24, 0x73, 0xf6, 0x2f, 0xed, 0x6f, 0x7f, 0x37, 0xb5, 0x27, 0xc0, 0x68, 0x8f, 0xf1, 0xfd, 0x61, 0x9b, 0xe7, 0x7b, 0x81, 0x7a, 0x53, 0x6e, 0x1c, 0xf2, 0xb4, 0xc2, 0xf7, 0x2f, 0xb4, 0xc2, 0x7b, 0x2b, 0xad, 0x68, 0xdf, 0xe9, 0xe6, 0x56, 0x88, 0xff, 0xdf, 0xc6, 0x05, 0xff, 0x1b, 0x2d, 0x4b, 0x2d, 0x3e, 0xbe, 0x17, 0xf1, 0xbf, 0xda, 0xed, 0x7e, 0xb3, 0xcf, 0x63, 0xa6, 0x17, 0x95, 0x7d, 0x9e, 0x58, 0x46, 0xd9, 0x67, 0xc4, 0x32, 0x04, 0x58, 0x3e, 0x6f, 0x6a, 0xbf, 0xf5, 0xe3, 0x3d, 0x51, 0x8f, 0x05, 0x1e, 0x53, 0xd6, 0xd6, 0xfe, 0x6e, 0x9a, 0x75, 0x83, 0xf9, 0x36, 0xd1, 0x41, 0xa8, 0x7a, 0x8c, 0xdf, 0xcd, 0xd7, 0xec, 0xf9, 0xdb, 0xa0, 0xef, 0x39, 0xe3, 0xb7, 0xdd, 0xcb, 0xcd, 0x87, 0x7d, 0xbe, 0xd4, 0x3e, 0x47, 0xae, 0xbf, 0xff, 0xb0, 0xf7, 0x7a, 0xfd, 0xfd, 0x37, 0xbd, 0x0f, 0xd5, 0x5e, 0xaf, 0x8e, 0xd4, 0x7f, 0xdb, 0xd7, 0x73, 0xfd, 0xc3, 0x3e, 0x97, 0x3d, 0xef, 0xfb, 0x8e, 0xd0, 0xfd, 0x9a, 0x4d, 0x3e, 0xe1, 0xfb, 0xbd, 0xcf, 0x29, 0x31, 0x5c, 0x59, 0xe8, 0x59, 0x03, 0x9a, 0x84, 0x33, 0xa2, 0xe6, 0xfe, 0x8d, 0x83, 0x48, 0xb4, 0x4d, 0x4d, 0x14, 0x45, 0x6d, 0x85, 0xc7, 0xf7, 0x9e, 0x70, 0xdb, 0x4d, 0x6f, 0xf8, 0xdc, 0x78, 0x63, 0xde, 0xc4, 0x37, 0x1b, 0x73, 0x59, 0xb8, 0xf0, 0xb5, 0x0a, 0xab, 0xaf, 0x60, 0xfd, 0x45, 0xe2, 0xaa, 0xc2, 0xfa, 0xab, 0x78, 0xa7, 0x6a, 0xf7, 0xd5, 0xed, 0x13, 0x2d, 0x3e, 0x26, 0xe3, 0xfe, 0x68, 0xa2, 0x67, 0xdf, 0x3c, 0x8f, 0xf5, 0x78, 0x89, 0x7e, 0x13, 0xd0, 0xf3, 0x5b, 0x15, 0x5a, 0xc5, 0x43, 0x7f, 0xb7, 0x04, 0xe1, 0xe7, 0x67, 0x9f, 0x28, 0xfe, 0x5c, 0x29, 0x36, 0xfd, 0x17, 0x2f, 0xae, 0x97, 0xe2, 0xa3, 0xf8, 0xfc, 0xb6, 0x14, 0x71, 0xa3, 0x0c, 0x85, 0xc0, 0xe0, 0xe6, 0x22, 0x22, 0x86, 0xff, 0xd1, 0xd5, 0xfa, 0xc7, 0x8d, 0xb5, 0x98, 0xeb, 0xbf, 0x95, 0x31, 0x8f, 0x35, 0xaf, 0xd1, 0xf0, 0x61, 0x03, 0x07, 0x90, 0xef, 0x05, 0x74, 0x6a, 0xdc, 0x39, 0xa8, 0x6d, 0xe3, 0xd6, 0xda, 0x5f, 0x0c, 0x94, 0x7e, 0x37, 0x7d, 0x56, 0xb8, 0x81, 0x16, 0xa0, 0x79, 0x35, 0xd5, 0x6e, 0x74, 0xea, 0x9f, 0x12, 0xd2, 0x72, 0x7b, 0xfd, 0x23, 0x6e, 0x9e, 0x2c, 0x4d, 0xff, 0xd9, 0xc6, 0x20, 0xf2, 0x5b, 0x82, 0x37, 0xa2, 0x8b, 0x41, 0x8a, 0x8f, 0x32, 0x60, 0xe8, 0xac, 0x0d, 0xb3, 0xbb, 0x7d, 0x19, 0xb3, 0x74, 0xf4, 0x9a, 0xb0, 0xc0, 0x2f, 0x8a, 0x43, 0x5b, 0xcf, 0xea, 0x69, 0x6b, 0x1a, 0x61, 0xbb, 0x7d, 0xc2, 0xdc, 0x85, 0xab, 0x02, 0x9a, 0xbe, 0xd4, 0xcd, 0xbb, 0x69, 0xa3, 0x68, 0xc5, 0x56, 0x73, 0xba, 0x5f, 0x57, 0xe5, 0xf1, 0x2f, 0x94, 0x25, 0x7e, 0x8d, 0x82, 0x9a, 0x35, 0x4a, 0xf0, 0xba, 0x12, 0x30, 0xc0, 0xbe, 0x7d, 0xf6, 0xb5, 0x35, 0xa6, 0x07, 0x12, 0xd6, 0x0c, 0xee, 0xdb, 0xe2, 0x5a, 0x4b, 0x25, 0xbb, 0xe6, 0xb3, 0xc9, 0x21, 0x5d, 0xac, 0xdd, 0x6c, 0xb7, 0xd9, 0x83, 0xee, 0x5b, 0x66, 0x5a, 0xdc, 0xb8, 0xf1, 0xd4, 0xa6, 0x4a, 0xcf, 0x80, 0xc0, 0x9a, 0xbf, 0x6f, 0xbf, 0xf6, 0xfa, 0xc0, 0xc1, 0x26, 0x53, 0x3b, 0xaf, 0x86, 0xd7, 0xaa, 0x95, 0xa2, 0x06, 0x8d, 0xeb, 0xf9, 0x0a, 0x5f, 0xd1, 0x53, 0xee, 0xf5, 0x09, 0xf0, 0xc9, 0x15, 0x56, 0xed, 0xbb, 0x40, 0x62, 0x88, 0x58, 0x28, 0x36, 0x2a, 0xde, 0xef, 0xb5, 0x52, 0x7c, 0xcd, 0xb5, 0xdf, 0x37, 0x1c, 0x68, 0xd5, 0xee, 0xe8, 0x7b, 0x99, 0x62, 0x2d, 0x8a, 0xb7, 0x9f, 0x1a, 0x40, 0x62, 0x4a, 0x9e, 0x16, 0xdd, 0x50, 0x4b, 0x4c, 0xcd, 0x8a, 0x88, 0x6e, 0x44, 0xf0, 0xee, 0x5b, 0xdf, 0xec, 0x1b, 0x2d, 0xea, 0x8b, 0x06, 0xf5, 0x1b, 0xdc, 0x29, 0xea, 0xd5, 0xf3, 0x9f, 0x2f, 0xfc, 0xfd, 0xed, 0xf5, 0xb4, 0x29, 0xeb, 0x44, 0x01, 0xc3, 0x6a, 0x0b, 0x10, 0xfe, 0xa2, 0x9e, 0xb7, 0x7f, 0xbd, 0xe8, 0xff, 0x5a, 0x50, 0x83, 0x06, 0xf5, 0xe7, 0x88, 0xfa, 0xf5, 0x1b, 0xcc, 0xd7, 0x4a, 0xc4, 0x7e, 0x23, 0x8c, 0x12, 0x98, 0xaf, 0xff, 0xbd, 0x22, 0x98, 0xb4, 0x1e, 0x21, 0x21, 0xda, 0x0f, 0x36, 0xae, 0x4a, 0x8b, 0x89, 0x9a, 0x35, 0x63, 0xd4, 0x08, 0xe2, 0xc1, 0x01, 0xda, 0x6f, 0xa5, 0x85, 0x74, 0x09, 0xe9, 0xd2, 0xbe, 0xad, 0x5f, 0x4b, 0xbf, 0x96, 0xcd, 0x9a, 0x04, 0x06, 0x34, 0xa8, 0xaf, 0xff, 0x88, 0x63, 0x60, 0xed, 0x43, 0x73, 0x35, 0xff, 0xdd, 0x51, 0x77, 0xdd, 0x61, 0xfa, 0xec, 0x69, 0xb7, 0x62, 0x5b, 0x2a, 0x81, 0x37, 0x7e, 0xf9, 0x5c, 0xff, 0x23, 0x9a, 0xf6, 0x07, 0x75, 0xcf, 0x97, 0xbd, 0x39, 0x4d, 0x31, 0xfe, 0x80, 0xd1, 0x52, 0xd1, 0x1f, 0x08, 0x57, 0xfb, 0x97, 0x46, 0x68, 0x6f, 0xe4, 0x51, 0x2d, 0xb5, 0xa2, 0x29, 0xc2, 0x5b, 0xfb, 0x72, 0xb8, 0xf6, 0xf7, 0x8e, 0x30, 0x2f, 0xdf, 0x86, 0xc1, 0xed, 0x1b, 0x37, 0x6e, 0x1f, 0xdc, 0x70, 0x70, 0xbb, 0x16, 0x2d, 0xda, 0x86, 0x76, 0xe9, 0xa4, 0x04, 0x06, 0xb6, 0x6a, 0xec, 0x77, 0x25, 0xa0, 0xeb, 0xf8, 0x01, 0x03, 0xc6, 0x75, 0x09, 0xa8, 0xf4, 0x6b, 0xdc, 0x2a, 0x50, 0x09, 0xec, 0xd4, 0x25, 0xb4, 0x6d, 0x8b, 0x16, 0xed, 0x62, 0x95, 0x0f, 0xc7, 0x6e, 0xfa, 0x70, 0xc5, 0x8a, 0x0f, 0x37, 0x8d, 0x6d, 0xde, 0xb2, 0x79, 0x44, 0xf2, 0x53, 0xf3, 0xe6, 0x3d, 0xb9, 0x22, 0x82, 0x5d, 0xd7, 0xb3, 0xb7, 0x3f, 0x5f, 0xf8, 0xc4, 0x13, 0x85, 0xcf, 0xdf, 0xde, 0xa4, 0x45, 0x93, 0x16, 0x03, 0xe6, 0xac, 0x7b, 0xde, 0x6e, 0x7f, 0x7e, 0xdd, 0x9c, 0x01, 0xbc, 0x68, 0xa2, 0x7c, 0xd7, 0xb8, 0xc3, 0x6d, 0x0d, 0x1b, 0xde, 0xd6, 0xa1, 0xf1, 0x47, 0x2d, 0xda, 0xb5, 0x6b, 0xd1, 0xab, 0xdd, 0x6d, 0xbe, 0x8d, 0x5a, 0x35, 0xb7, 0xd5, 0xfc, 0xad, 0x69, 0xc3, 0x76, 0xad, 0x82, 0x82, 0x5a, 0xb5, 0x6b, 0xd8, 0x54, 0x79, 0xa8, 0x79, 0xab, 0x46, 0xbe, 0xb7, 0xb5, 0xeb, 0xa5, 0x9d, 0xa0, 0x34, 0x49, 0xd8, 0x97, 0x34, 0x60, 0x40, 0xd2, 0xbe, 0x84, 0x97, 0x6d, 0x53, 0x63, 0xd6, 0x4e, 0x1c, 0x77, 0xf7, 0x1d, 0xbd, 0x7b, 0xdf, 0x71, 0xf7, 0xb8, 0x89, 0x6b, 0x63, 0xa6, 0xda, 0x5e, 0xde, 0x9c, 0xb9, 0x7b, 0xe6, 0xcc, 0xdd, 0x99, 0x9b, 0x5f, 0x0e, 0x9f, 0x3f, 0x67, 0xf1, 0xd0, 0xee, 0xb1, 0xf6, 0x85, 0xe3, 0xba, 0x74, 0x19, 0xb7, 0xd0, 0xbe, 0xb4, 0xc7, 0xd0, 0xc5, 0x73, 0xe6, 0x87, 0x0b, 0xfd, 0xbf, 0x36, 0xca, 0x73, 0xfa, 0xaf, 0x12, 0x69, 0xff, 0x7d, 0xa1, 0x7b, 0x23, 0x4f, 0x3e, 0xea, 0xcd, 0x2b, 0xcf, 0xbe, 0x09, 0x05, 0x7e, 0x63, 0xec, 0x7b, 0xe1, 0xcf, 0x8f, 0x18, 0xfb, 0xde, 0xc2, 0x4f, 0x9c, 0x35, 0xf6, 0x7d, 0x44, 0x5f, 0xf2, 0x29, 0xcf, 0xbe, 0xaf, 0xf0, 0x53, 0xda, 0x18, 0xfb, 0x66, 0x11, 0xaa, 0x74, 0x37, 0xf6, 0x2d, 0xa2, 0x99, 0xb2, 0xd0, 0xd8, 0x57, 0x45, 0x7f, 0x65, 0xa5, 0xb1, 0x6f, 0xf5, 0x4e, 0x55, 0x5e, 0x33, 0xf6, 0xfd, 0x44, 0x33, 0xdf, 0x56, 0xc6, 0x7e, 0x3d, 0xd1, 0xdc, 0xb7, 0xbf, 0xb1, 0x5f, 0xbf, 0x4e, 0xdb, 0xfc, 0x45, 0x0b, 0xdf, 0x69, 0xc6, 0x7e, 0xc3, 0x3a, 0xed, 0x0c, 0xd0, 0xdb, 0xa9, 0x7d, 0xe7, 0x1f, 0x2f, 0xab, 0x6c, 0xf1, 0x5d, 0x66, 0xec, 0x2b, 0xa2, 0x89, 0xb9, 0xd4, 0xd8, 0x37, 0x09, 0x7f, 0x8b, 0xaf, 0xb1, 0xef, 0x25, 0xba, 0x59, 0x82, 0x8c, 0x7d, 0xef, 0x3a, 0xe7, 0xf8, 0x88, 0x58, 0x4b, 0x84, 0xb1, 0xef, 0x2b, 0x9a, 0x58, 0x5e, 0x31, 0xf6, 0xcd, 0x62, 0xb1, 0xe5, 0x5f, 0xc6, 0xbe, 0x45, 0x84, 0xaa, 0x21, 0xc6, 0xbe, 0x2a, 0x12, 0xd5, 0x29, 0xc6, 0xbe, 0xd5, 0x92, 0xaf, 0x7e, 0x62, 0xec, 0xfb, 0x89, 0xd0, 0x46, 0x2f, 0x18, 0xfb, 0xf5, 0x44, 0xef, 0x46, 0x3f, 0x19, 0xfb, 0xf5, 0xeb, 0xb4, 0xcd, 0x5f, 0xf4, 0x0d, 0xf4, 0x36, 0xf6, 0x1b, 0x0a, 0x4b, 0xe0, 0x00, 0x63, 0x3f, 0x40, 0xd4, 0x0b, 0x8c, 0x18, 0x99, 0x94, 0xbc, 0x26, 0x25, 0x6e, 0x69, 0x6c, 0x9a, 0xad, 0x53, 0x74, 0x67, 0x5b, 0xef, 0xd0, 0x5e, 0xbd, 0xba, 0xf3, 0xbf, 0x30, 0x5b, 0xd4, 0x1a, 0x5b, 0xda, 0x9a, 0x69, 0x49, 0xf1, 0x91, 0x89, 0x31, 0xb6, 0x49, 0x2b, 0x97, 0x47, 0xa6, 0xae, 0xb5, 0x8d, 0x5a, 0x1b, 0x67, 0x8f, 0x59, 0x1b, 0x17, 0x6d, 0xeb, 0x14, 0x9b, 0x96, 0x96, 0x1c, 0xde, 0xb3, 0xe7, 0xea, 0xd5, 0xab, 0x7b, 0xa4, 0xad, 0x49, 0xd6, 0x4f, 0xea, 0x11, 0x9d, 0x94, 0xd0, 0xb3, 0xb3, 0x6d, 0x75, 0x5c, 0x5a, 0xac, 0x6d, 0x86, 0x3d, 0xd5, 0x9e, 0xb2, 0xca, 0x1e, 0x63, 0x1b, 0x93, 0x94, 0x98, 0x66, 0x9b, 0x12, 0x99, 0x60, 0xb7, 0xb5, 0x99, 0x14, 0x99, 0x96, 0xd4, 0xa6, 0x87, 0x6d, 0x52, 0x5c, 0xb4, 0x3d, 0x31, 0x95, 0xb7, 0x56, 0x26, 0xc6, 0xd8, 0x53, 0x6c, 0x69, 0xb1, 0x76, 0xdb, 0xcc, 0xf1, 0x93, 0x6c, 0x53, 0x93, 0xed, 0x89, 0x9e, 0xb3, 0x8d, 0x13, 0xba, 0xd9, 0x66, 0xdb, 0x53, 0x52, 0xe3, 0x92, 0x12, 0x6d, 0xbd, 0x7a, 0xf4, 0xba, 0x5e, 0x61, 0x6a, 0x74, 0x4a, 0x5c, 0x72, 0x5a, 0x6a, 0x8f, 0xd4, 0xb8, 0xf8, 0x1e, 0x49, 0x29, 0x4b, 0x7b, 0x4e, 0x1d, 0x33, 0xa9, 0x33, 0x65, 0x52, 0xf4, 0x64, 0x7b, 0x4c, 0xdc, 0xca, 0x84, 0xde, 0x3d, 0x68, 0xfa, 0x20, 0xda, 0x3d, 0x69, 0x90, 0x76, 0xb0, 0xbb, 0xe7, 0xa8, 0xb6, 0x6b, 0xf3, 0xec, 0xd6, 0x16, 0xea, 0x39, 0x51, 0xeb, 0x6e, 0x58, 0xf7, 0xd0, 0x01, 0xdd, 0x43, 0xfb, 0x0d, 0xb2, 0xd5, 0xe9, 0x53, 0x3c, 0x17, 0x2c, 0xa1, 0x31, 0xa9, 0x7a, 0xa7, 0x6e, 0x2d, 0x29, 0x2e, 0xd5, 0x16, 0x69, 0x4b, 0x4b, 0x89, 0x8c, 0xb1, 0x27, 0x44, 0xa6, 0x2c, 0xb7, 0x25, 0x2d, 0xf9, 0xc3, 0x81, 0xea, 0xf1, 0x47, 0x6f, 0xdc, 0x3a, 0xa0, 0xfa, 0xa8, 0x0d, 0x8f, 0x89, 0x4c, 0xb0, 0xcd, 0x5a, 0x1d, 0x99, 0x12, 0x93, 0x14, 0x1d, 0x6b, 0xd3, 0xae, 0x1a, 0x91, 0x94, 0x16, 0x97, 0x64, 0x9b, 0x12, 0xb7, 0x3c, 0x29, 0x3e, 0x2d, 0x3a, 0xd6, 0xbe, 0xea, 0x46, 0xf5, 0xa9, 0x91, 0x8c, 0x51, 0x4a, 0x9c, 0x56, 0x75, 0xb2, 0x7d, 0x49, 0x64, 0xb4, 0xdd, 0xb6, 0x24, 0x32, 0x21, 0x2e, 0x7e, 0x8d, 0x2d, 0xc6, 0x9e, 0x1a, 0xb7, 0x34, 0x91, 0xf1, 0x8d, 0x4b, 0xf4, 0x0c, 0xee, 0xca, 0x84, 0x04, 0xc6, 0x99, 0x8e, 0x86, 0xea, 0x45, 0xda, 0xef, 0x4a, 0xb3, 0x33, 0xf2, 0xbf, 0xf3, 0x7e, 0x1f, 0x6d, 0xca, 0xe7, 0x44, 0xa6, 0xa4, 0x46, 0xae, 0xee, 0x1e, 0x15, 0xa9, 0x4d, 0x91, 0x51, 0x56, 0xca, 0x6f, 0xe7, 0xdf, 0x33, 0x9b, 0xb6, 0x04, 0x3b, 0xcd, 0xb0, 0xb5, 0xf1, 0x94, 0xd1, 0x46, 0x2b, 0x92, 0xee, 0xc6, 0xa5, 0xc6, 0x32, 0x27, 0xe3, 0xd3, 0x18, 0xa2, 0x38, 0x7b, 0xaa, 0x8d, 0x16, 0x47, 0x47, 0xa6, 0xd8, 0x97, 0xac, 0x8c, 0xa7, 0x75, 0x51, 0x91, 0x8c, 0x06, 0x8d, 0x4d, 0x4d, 0x42, 0x12, 0xc9, 0x49, 0x34, 0x25, 0x2d, 0x2e, 0x52, 0x7b, 0x23, 0x3a, 0x29, 0x71, 0x49, 0x7c, 0x5c, 0x74, 0x5a, 0x5c, 0xe2, 0x52, 0x5b, 0x72, 0x4a, 0x5c, 0x52, 0x4a, 0x5c, 0x1a, 0x17, 0x87, 0xdb, 0xe2, 0xd2, 0x6c, 0xa9, 0xb1, 0x49, 0x2b, 0xe3, 0x63, 0x6c, 0xa9, 0x76, 0x7b, 0x82, 0x6d, 0xc5, 0xca, 0xb8, 0x34, 0xb4, 0xc4, 0xe0, 0x27, 0xa6, 0x26, 0x53, 0x6a, 0x62, 0x5a, 0x1b, 0xdb, 0xea, 0x58, 0xa4, 0xb3, 0x32, 0xd5, 0xd3, 0xa5, 0xa8, 0xa4, 0x18, 0x74, 0x4b, 0x27, 0x6d, 0x51, 0x2b, 0xd3, 0x6c, 0xab, 0xf5, 0x2b, 0x63, 0xe2, 0x52, 0x93, 0xe3, 0x23, 0xd7, 0x78, 0x2a, 0xa5, 0xe4, 0xa5, 0x71, 0x89, 0x91, 0xf1, 0xda, 0x04, 0xc6, 0xa5, 0xa5, 0xde, 0x7c, 0x75, 0x7c, 0x64, 0xca, 0x52, 0xba, 0x9b, 0x1a, 0xb7, 0xd6, 0x9e, 0xda, 0xc3, 0x36, 0x8b, 0xe1, 0x89, 0x8e, 0x8f, 0x4c, 0x4d, 0x8d, 0x8b, 0xe6, 0xfc, 0xe4, 0x94, 0xa4, 0xe4, 0xa4, 0x14, 0xe6, 0x23, 0x31, 0xb5, 0x9b, 0x8d, 0xba, 0xd3, 0xe2, 0xa2, 0x57, 0x72, 0x3e, 0x6d, 0x5f, 0x15, 0x97, 0x1a, 0x17, 0x15, 0x6f, 0xaf, 0x1d, 0xd1, 0x95, 0xc9, 0xc9, 0xf6, 0x94, 0xe8, 0x48, 0x4d, 0xc2, 0x4b, 0xe3, 0x56, 0xd9, 0xf5, 0x63, 0xf1, 0xf6, 0xb4, 0x34, 0x7b, 0xca, 0x92, 0xa4, 0x94, 0x84, 0x54, 0xcf, 0x44, 0xc5, 0x45, 0xa6, 0xd8, 0x62, 0x23, 0x53, 0x12, 0x92, 0x12, 0xd7, 0x78, 0xa6, 0x25, 0xde, 0xbe, 0x54, 0x1b, 0x99, 0x1e, 0xb6, 0xe1, 0x69, 0xfa, 0x15, 0xa9, 0x9a, 0xd1, 0xa4, 0xc5, 0x25, 0x50, 0x8a, 0xd6, 0xcc, 0xd4, 0x78, 0xbb, 0x7d, 0xf9, 0x8d, 0x59, 0x8f, 0x4f, 0x4a, 0x5a, 0x6e, 0x4b, 0x88, 0x5c, 0xce, 0xf8, 0xda, 0x57, 0xc5, 0xc5, 0x30, 0x0e, 0xfa, 0x45, 0x08, 0x41, 0xdb, 0x89, 0x4c, 0xd3, 0x4d, 0xc2, 0xb6, 0x3a, 0x32, 0xf5, 0x37, 0x7a, 0xd0, 0x84, 0x40, 0x07, 0xec, 0xab, 0xec, 0xda, 0xeb, 0xa4, 0x95, 0x4b, 0x63, 0xb5, 0x31, 0x8e, 0x49, 0xa2, 0xa4, 0xc4, 0xa4, 0x34, 0xdb, 0x92, 0xa4, 0xf8, 0xf8, 0xa4, 0xd5, 0x34, 0x89, 0x29, 0x59, 0x99, 0x92, 0xa2, 0x17, 0xcc, 0xff, 0x63, 0x3c, 0xa3, 0x91, 0x6a, 0x4f, 0x88, 0xeb, 0x9e, 0x92, 0xb4, 0x52, 0x17, 0x50, 0x8c, 0x3d, 0x2d, 0x32, 0x2e, 0x3e, 0x55, 0xd7, 0xff, 0xf5, 0x2e, 0xa6, 0x7a, 0xfa, 0xac, 0x57, 0x1f, 0x69, 0x5b, 0x62, 0xb7, 0xc7, 0x6b, 0x33, 0xca, 0x29, 0xc8, 0x3a, 0x21, 0x2d, 0xb6, 0x1b, 0xe3, 0x1d, 0x17, 0xef, 0x19, 0x93, 0xd4, 0xb4, 0x94, 0x24, 0xde, 0x63, 0xb3, 0x32, 0x3a, 0x6d, 0x65, 0x8a, 0x5d, 0x1b, 0x61, 0xad, 0x2f, 0x74, 0x36, 0x2d, 0x32, 0x8a, 0x21, 0x4a, 0xf3, 0x0c, 0x8d, 0xd6, 0xe3, 0xa4, 0x95, 0xa9, 0x89, 0xf6, 0x54, 0xe6, 0x64, 0x7c, 0xa2, 0x2e, 0x55, 0xcd, 0x45, 0xf5, 0xed, 0x66, 0xf4, 0x59, 0xd7, 0xbd, 0xd6, 0xd7, 0xa5, 0x29, 0xf6, 0xc8, 0x34, 0xf6, 0xaf, 0x6b, 0xbc, 0x93, 0x6e, 0x57, 0xda, 0x59, 0xb1, 0xf6, 0xf8, 0x64, 0xad, 0x19, 0xff, 0xdd, 0xc6, 0x3a, 0xeb, 0x8a, 0x4d, 0x5a, 0x85, 0x0e, 0xfa, 0x84, 0x86, 0x86, 0x76, 0xb5, 0x2d, 0x8d, 0x5f, 0x93, 0x1c, 0x4b, 0x37, 0xb5, 0x23, 0x89, 0x71, 0x89, 0x76, 0xdb, 0x6a, 0xbb, 0xe6, 0x2e, 0x53, 0x3d, 0x46, 0x1b, 0x97, 0x16, 0x89, 0x6e, 0x53, 0x75, 0xc1, 0x27, 0x32, 0x72, 0xa9, 0x4c, 0x3f, 0x32, 0x49, 0xb5, 0xf5, 0xd2, 0xae, 0x65, 0x1c, 0xe2, 0x12, 0x0d, 0x6b, 0x42, 0xf9, 0x4b, 0x57, 0x46, 0x2e, 0xb5, 0x33, 0xfa, 0x61, 0xbc, 0x35, 0x12, 0xaf, 0x1b, 0xcf, 0xa5, 0xb7, 0xbe, 0x6b, 0xa3, 0x23, 0xab, 0xed, 0xf1, 0xf1, 0xda, 0x76, 0x6c, 0x8a, 0x36, 0xeb, 0x5a, 0x2b, 0xc7, 0x4f, 0x1b, 0x6e, 0x4b, 0x8e, 0x4d, 0x4a, 0xb4, 0xa7, 0xe9, 0x95, 0x69, 0x93, 0xa1, 0x8f, 0xb1, 0xee, 0xa5, 0x6c, 0x58, 0x82, 0x2d, 0x72, 0x15, 0xb3, 0x11, 0xa9, 0xe9, 0x70, 0x09, 0x57, 0x69, 0x7d, 0x8d, 0x8e, 0xd5, 0xe4, 0xfc, 0xdf, 0x1c, 0x2d, 0xa7, 0x27, 0x25, 0xfc, 0xb1, 0xf3, 0xfb, 0x23, 0x4f, 0xff, 0xdf, 0x2e, 0xf8, 0x1d, 0x4f, 0x2d, 0x46, 0x8a, 0x24, 0xe2, 0xed, 0x35, 0x22, 0x45, 0xc4, 0x91, 0x42, 0xc7, 0x8a, 0x34, 0x61, 0x13, 0x9d, 0x44, 0xb4, 0xe8, 0xcc, 0xb6, 0xb7, 0x08, 0x15, 0xbd, 0xf8, 0xd7, 0xdd, 0xd8, 0x0b, 0xe3, 0x58, 0x14, 0xe7, 0xda, 0x38, 0x6b, 0x8d, 0x98, 0xc6, 0x95, 0xf1, 0x22, 0x52, 0x24, 0x8a, 0x18, 0x8e, 0x4c, 0x12, 0x2b, 0xc5, 0x72, 0x5e, 0xa5, 0x8a, 0xb5, 0xbc, 0x1a, 0xc5, 0xff, 0xe3, 0x84, 0x9d, 0x77, 0xb4, 0x6d, 0xb4, 0x5e, 0xa6, 0x56, 0x76, 0x1a, 0x75, 0x85, 0x8b, 0x9e, 0xfc, 0x5b, 0xad, 0xff, 0xeb, 0xa1, 0x97, 0x94, 0x5c, 0xa7, 0xa4, 0x1e, 0x9c, 0x9d, 0x24, 0x12, 0x38, 0x43, 0x6b, 0xc1, 0x6a, 0xae, 0x4e, 0xe3, 0x4a, 0x9b, 0x98, 0x41, 0x69, 0xa9, 0x90, 0x22, 0x56, 0xe9, 0xe5, 0xda, 0xc4, 0x18, 0xce, 0x4b, 0xd4, 0xdb, 0x3b, 0x85, 0x6b, 0x13, 0x38, 0x6a, 0x13, 0x6d, 0x68, 0x47, 0x24, 0xc7, 0x92, 0xd8, 0xeb, 0xa1, 0xb7, 0x4a, 0xab, 0xdd, 0xce, 0x79, 0xa9, 0xc6, 0x55, 0x2b, 0xf5, 0x5a, 0xb4, 0x72, 0x6c, 0x7a, 0xc9, 0xda, 0x55, 0x33, 0xc5, 0x78, 0xce, 0xb4, 0x89, 0xa9, 0xb4, 0x44, 0x3b, 0xb7, 0x6e, 0xd9, 0x37, 0x97, 0xd0, 0x8d, 0x23, 0xb3, 0xf5, 0xab, 0x53, 0x39, 0x9e, 0xa4, 0x9f, 0xdb, 0x8b, 0x9a, 0x7a, 0xfd, 0x4e, 0x0f, 0x53, 0xb9, 0x4e, 0x1b, 0xd5, 0x64, 0x8e, 0xa5, 0x72, 0x8e, 0x76, 0x45, 0x3c, 0xdb, 0x24, 0x8e, 0x2e, 0xe5, 0xfd, 0xa9, 0xd4, 0x32, 0x89, 0x5e, 0x7a, 0xda, 0xe9, 0x69, 0xb5, 0x4d, 0x4c, 0xd6, 0xdb, 0x19, 0x47, 0x3b, 0x13, 0xf4, 0x3e, 0x2f, 0x65, 0x4f, 0x1b, 0x9b, 0x14, 0xe6, 0xa0, 0x87, 0x31, 0x0b, 0x83, 0x8c, 0xf1, 0x9f, 0xc4, 0x5e, 0xed, 0x95, 0xdd, 0x6f, 0xba, 0xf2, 0xd6, 0x36, 0xd6, 0xbd, 0xd6, 0x76, 0x7d, 0x36, 0xbb, 0xb3, 0x1d, 0xa0, 0xff, 0xbf, 0x9f, 0x7e, 0xfc, 0xf7, 0x67, 0x28, 0xde, 0xa8, 0x61, 0x89, 0x31, 0x26, 0xa9, 0x75, 0xe6, 0xe8, 0xf7, 0x6b, 0xbf, 0xd1, 0x9b, 0x38, 0xce, 0xb6, 0xf1, 0x4a, 0x1b, 0xeb, 0x14, 0xb6, 0xda, 0xc8, 0x27, 0xe8, 0xbd, 0x59, 0xce, 0x31, 0xad, 0xcc, 0xbf, 0xae, 0xa5, 0x1e, 0x7f, 0xf9, 0x8a, 0xff, 0xa6, 0xcd, 0x1b, 0x2a, 0x1b, 0xce, 0x51, 0x4d, 0x4b, 0x36, 0x31, 0x8b, 0xa3, 0x5a, 0x3b, 0x63, 0xa8, 0x27, 0x5a, 0x7f, 0xaf, 0xb6, 0xae, 0x11, 0x1c, 0x49, 0xd3, 0x47, 0x56, 0x53, 0x5e, 0x1c, 0x25, 0x6b, 0x2d, 0x49, 0xd3, 0xcf, 0xb2, 0xa3, 0xce, 0xdf, 0xeb, 0x7d, 0xaa, 0x7e, 0xb5, 0x47, 0xbf, 0x71, 0xd7, 0x7b, 0xad, 0xa9, 0x6d, 0x09, 0xef, 0x44, 0xeb, 0x2a, 0x5c, 0xa2, 0xd7, 0xac, 0x69, 0x44, 0xb3, 0xb0, 0x18, 0x5d, 0xef, 0x9a, 0x4d, 0x26, 0x1a, 0xca, 0x8d, 0xd3, 0x67, 0xf2, 0x86, 0x66, 0xb5, 0x91, 0x4e, 0x30, 0x94, 0xec, 0x99, 0xd1, 0xd0, 0x3a, 0xad, 0xb4, 0x8b, 0xbb, 0x38, 0xd7, 0x6e, 0xa8, 0xfd, 0xcf, 0x5d, 0xdf, 0xe7, 0xba, 0x7d, 0xcf, 0xd1, 0xfb, 0xae, 0xb5, 0x7a, 0x35, 0x73, 0x1b, 0xa5, 0x8f, 0x9d, 0xa7, 0x94, 0x9b, 0xdb, 0x95, 0xf2, 0xa7, 0x2c, 0xbf, 0xae, 0x55, 0xda, 0xf4, 0x3a, 0x3d, 0xa3, 0xa1, 0xd9, 0x6b, 0xdd, 0x76, 0xb4, 0xb9, 0xde, 0x4a, 0xcf, 0xec, 0x6a, 0xe3, 0x17, 0x6b, 0x58, 0xc8, 0x78, 0xdd, 0x1a, 0xd3, 0xf4, 0xf1, 0xb3, 0xeb, 0xd7, 0x7a, 0xc6, 0x38, 0x5a, 0x6f, 0xa9, 0x36, 0x8e, 0x9a, 0x9d, 0x78, 0xc6, 0x4e, 0x6b, 0xaf, 0x47, 0x1b, 0x9e, 0x91, 0x4d, 0xd5, 0xd5, 0xaa, 0xed, 0x25, 0xeb, 0x73, 0x67, 0xd7, 0x75, 0x1c, 0xa7, 0x9f, 0xe5, 0xb9, 0x22, 0x5a, 0xd7, 0xf6, 0x12, 0xbd, 0xce, 0x68, 0xfd, 0xbd, 0x44, 0x7a, 0xa8, 0x9d, 0x9f, 0xa2, 0xcf, 0x74, 0x8a, 0xae, 0x10, 0x4f, 0xcd, 0xe1, 0x7a, 0x2b, 0xd3, 0xf4, 0x72, 0x63, 0x79, 0x4f, 0xab, 0x37, 0x46, 0x7f, 0x65, 0xd7, 0xd5, 0x6d, 0x13, 0x2b, 0x38, 0x16, 0xa7, 0xd7, 0xa3, 0xf5, 0xd0, 0xa3, 0x7c, 0xad, 0xbf, 0xc9, 0x46, 0x5b, 0xb5, 0xda, 0xdb, 0xe8, 0xba, 0x8b, 0x35, 0xfc, 0xcd, 0xca, 0xeb, 0xe3, 0xeb, 0xe9, 0x7f, 0x14, 0xe5, 0xc6, 0x18, 0x9e, 0xd6, 0x33, 0x93, 0xda, 0xb1, 0x95, 0xfa, 0x76, 0x75, 0x9d, 0x3a, 0x63, 0xf4, 0x31, 0x4a, 0xd6, 0x7b, 0xbb, 0xe6, 0xa6, 0x9e, 0x26, 0x19, 0x1e, 0x5d, 0x2b, 0x4f, 0xeb, 0x67, 0xad, 0x05, 0xc6, 0xe9, 0x16, 0xfc, 0x9f, 0xea, 0xf6, 0x78, 0x9b, 0xa5, 0xc6, 0xec, 0x6a, 0x73, 0xbd, 0x56, 0xef, 0x77, 0x0f, 0xdd, 0x2a, 0x3c, 0xea, 0x89, 0xd6, 0xcf, 0x4a, 0xd5, 0xdf, 0x8d, 0x36, 0xca, 0xd7, 0xc6, 0x2a, 0x49, 0x1f, 0xe1, 0x14, 0xc3, 0x3e, 0xb4, 0x3e, 0x77, 0xd3, 0xdf, 0x89, 0x34, 0x8e, 0x45, 0x5f, 0xf7, 0x66, 0x9e, 0x71, 0x5f, 0xa5, 0xb7, 0x3f, 0x8e, 0xbe, 0xc5, 0xeb, 0xe5, 0xde, 0xac, 0xd1, 0x95, 0x5c, 0x99, 0xac, 0xb7, 0x23, 0xda, 0x50, 0xa0, 0x56, 0x9a, 0xd6, 0xa7, 0x55, 0xfa, 0xfb, 0xb5, 0xe7, 0x69, 0xd7, 0xa6, 0xe9, 0x23, 0x95, 0xa2, 0x7b, 0xa9, 0x14, 0xc6, 0x20, 0xf5, 0x26, 0x8b, 0x8a, 0xd3, 0xeb, 0xd4, 0x3c, 0x5c, 0xa4, 0xfe, 0xae, 0xd6, 0xb6, 0x35, 0x37, 0x59, 0x4b, 0xbc, 0xee, 0x6b, 0x6b, 0x35, 0xd3, 0x43, 0xf7, 0x05, 0x69, 0x75, 0xea, 0x48, 0xbd, 0xbe, 0xc6, 0x68, 0xfd, 0x48, 0x30, 0xda, 0x52, 0x3b, 0x9a, 0xa9, 0xfa, 0xf5, 0x76, 0xdd, 0xab, 0xfd, 0xd6, 0xd6, 0xe3, 0xa9, 0x2f, 0x49, 0x7f, 0x4f, 0xf3, 0x7d, 0xcb, 0x0d, 0xfd, 0xda, 0xf5, 0xde, 0xc7, 0x18, 0x7a, 0xb8, 0x51, 0x93, 0xc7, 0x23, 0xd4, 0x1e, 0x89, 0x34, 0x56, 0xa1, 0x5a, 0x9f, 0xb2, 0x5a, 0x1f, 0x89, 0xff, 0xee, 0x1f, 0x6a, 0x3d, 0x82, 0x67, 0x06, 0xec, 0xfa, 0x88, 0xd5, 0xbe, 0xaf, 0xe9, 0x67, 0xa9, 0xee, 0xd3, 0x3c, 0x3a, 0xd6, 0x7c, 0x9c, 0xa7, 0x4d, 0x89, 0xba, 0x7d, 0xd8, 0xf4, 0x51, 0x8c, 0xd7, 0xdb, 0xbd, 0xda, 0x18, 0x25, 0x8f, 0x95, 0xac, 0xa4, 0x4f, 0x29, 0x75, 0x5a, 0x9c, 0x62, 0x78, 0x98, 0xba, 0xda, 0x48, 0xd5, 0xad, 0x20, 0x0e, 0xbf, 0x91, 0xa2, 0xd7, 0x74, 0xc3, 0x03, 0xc5, 0xe8, 0xf3, 0x14, 0xa9, 0xcf, 0x48, 0x6a, 0x1d, 0xff, 0xff, 0xdb, 0x59, 0x4c, 0xbd, 0x69, 0x9e, 0x6f, 0xf4, 0x3e, 0x52, 0x6f, 0x9b, 0x5d, 0x9f, 0xaf, 0x5a, 0x1b, 0xf5, 0x94, 0xb2, 0xda, 0x98, 0x59, 0xad, 0xb4, 0x6e, 0x86, 0xbe, 0xe3, 0x0c, 0x55, 0xdd, 0x98, 0xc3, 0x34, 0xbd, 0x4d, 0x9e, 0xeb, 0x3c, 0xaf, 0x56, 0xea, 0x63, 0xbd, 0x52, 0xef, 0x49, 0xad, 0x86, 0x6b, 0xe7, 0x25, 0xd5, 0x38, 0x2b, 0x12, 0x7d, 0x7a, 0x54, 0x94, 0x76, 0x93, 0x6a, 0x6a, 0xe7, 0x38, 0x49, 0xb7, 0xa1, 0x44, 0xfd, 0x8a, 0x54, 0xc3, 0x5b, 0x25, 0xd6, 0xf1, 0xaa, 0xb5, 0xf1, 0x53, 0x5f, 0xbd, 0x65, 0x75, 0xe7, 0xf9, 0x86, 0xbf, 0xaf, 0x9d, 0xd7, 0xa5, 0x7a, 0x4b, 0xb4, 0xfe, 0x7a, 0x8e, 0xff, 0xd6, 0x8f, 0x77, 0xaa, 0xb3, 0x5e, 0xd5, 0x96, 0x15, 0xab, 0x8f, 0x48, 0xf2, 0xf5, 0xd1, 0xf8, 0x3f, 0xb1, 0x8e, 0x75, 0xae, 0xe3, 0x63, 0x93, 0xf4, 0x99, 0xd0, 0x2c, 0xa8, 0x0f, 0x3d, 0xd1, 0xfe, 0x75, 0xd5, 0xdb, 0x1a, 0xaf, 0xaf, 0x62, 0xb1, 0xc6, 0x6c, 0xd6, 0x9e, 0x93, 0xa8, 0xcf, 0x8d, 0x5d, 0xef, 0x95, 0xfd, 0x7a, 0x5c, 0x99, 0x7a, 0xd3, 0x4a, 0x1b, 0xa7, 0x8f, 0xab, 0xc7, 0xdf, 0xa6, 0xd6, 0xf1, 0xf0, 0x89, 0x86, 0xe6, 0x52, 0x0d, 0xeb, 0xf7, 0x78, 0x93, 0x54, 0x3d, 0xce, 0xaa, 0xad, 0xd7, 0xa3, 0x07, 0xad, 0x8e, 0x9b, 0xd7, 0x26, 0x8f, 0xcf, 0xd7, 0x22, 0xa6, 0x48, 0xdd, 0x7f, 0x79, 0xb4, 0x1f, 0x66, 0x5c, 0x35, 0xd2, 0x88, 0x72, 0xe3, 0x8d, 0x5a, 0xff, 0xdb, 0xb5, 0x36, 0x63, 0x46, 0x56, 0xeb, 0x63, 0x1b, 0x7f, 0xfd, 0xf5, 0x58, 0x7d, 0x86, 0x3c, 0xb6, 0x5e, 0x3b, 0x96, 0xe3, 0x59, 0xb1, 0x86, 0xeb, 0xfa, 0x89, 0xd5, 0xf5, 0x65, 0x37, 0xbc, 0xdd, 0xcd, 0x5e, 0xf3, 0x86, 0x8e, 0x6f, 0xc4, 0x52, 0x36, 0x63, 0x4d, 0xd0, 0xb6, 0xab, 0x0c, 0xdb, 0x88, 0xbc, 0xee, 0x0f, 0x97, 0x18, 0x75, 0xd5, 0xce, 0x6b, 0xb4, 0xe1, 0xc1, 0x96, 0x1a, 0xfe, 0xf1, 0x7f, 0x12, 0xcb, 0x7a, 0x4a, 0x4f, 0xd2, 0x55, 0xf2, 0xd7, 0x23, 0xbf, 0xbf, 0x1a, 0xcd, 0xff, 0x4f, 0x6b, 0xf8, 0x73, 0xd1, 0x74, 0xed, 0x08, 0xd7, 0x8d, 0x46, 0xb5, 0x7b, 0x97, 0xfa, 0x7f, 0x35, 0xa1, 0xda, 0x97, 0xec, 0x7f, 0xe7, 0x3f, 0x45, 0x28, 0x52, 0x8a, 0x06, 0xfa, 0xdd, 0x44, 0x2b, 0xe7, 0xec, 0x15, 0xde, 0xa3, 0xc6, 0x4c, 0x9a, 0x25, 0x5a, 0x44, 0xaf, 0x49, 0x89, 0x17, 0x9d, 0x48, 0x61, 0x97, 0x8b, 0x7e, 0x64, 0x54, 0x89, 0x44, 0x37, 0xfa, 0x1f, 0xf7, 0x39, 0xdb, 0xa4, 0xdf, 0x07, 0xbd, 0xf1, 0x4a, 0x7b, 0xc4, 0xc0, 0x8d, 0x57, 0xda, 0x9d, 0xd3, 0x1b, 0xaf, 0xb4, 0x5f, 0xc2, 0xb1, 0x2c, 0xb7, 0xa7, 0x24, 0x8a, 0xde, 0xbf, 0xfd, 0xbf, 0x76, 0xbf, 0x4a, 0x0c, 0xf8, 0xed, 0xff, 0xaf, 0xdf, 0xf5, 0xd4, 0x3e, 0xf9, 0xaa, 0x95, 0xe1, 0x4d, 0x0d, 0xf5, 0xc4, 0x56, 0xf1, 0x94, 0x78, 0x55, 0x7c, 0xac, 0xff, 0x3d, 0xa9, 0x9e, 0x08, 0x11, 0x1d, 0x18, 0x83, 0x7e, 0x62, 0x28, 0xbd, 0x9f, 0x42, 0xd4, 0xbf, 0x88, 0x91, 0x4c, 0x44, 0x47, 0x8a, 0xde, 0xbe, 0xbd, 0x9e, 0xed, 0x81, 0x65, 0xfa, 0x56, 0x59, 0x92, 0xee, 0xd9, 0x9e, 0xda, 0xaf, 0x6f, 0x4d, 0x41, 0xdb, 0x3d, 0xdb, 0x39, 0xf9, 0x9e, 0xed, 0xba, 0xc7, 0x3d, 0xdb, 0x87, 0x23, 0x3c, 0xdb, 0xa7, 0x5e, 0xf3, 0x6c, 0x5f, 0xab, 0xe7, 0xd9, 0xfe, 0xcb, 0xe4, 0xd9, 0xfe, 0xf8, 0x89, 0x5e, 0xbb, 0x22, 0x2c, 0xfa, 0xdd, 0x62, 0x53, 0x6e, 0xf5, 0xcd, 0xaf, 0x2f, 0x3f, 0x75, 0xd3, 0x6b, 0xaf, 0xa0, 0xaf, 0x6f, 0x7e, 0xdd, 0xe4, 0xa0, 0x50, 0x4c, 0x0d, 0xf5, 0xa7, 0x01, 0x79, 0x99, 0xa6, 0xf9, 0x7c, 0xed, 0x3b, 0x2e, 0x70, 0x8b, 0x6f, 0xae, 0x39, 0x98, 0x7f, 0xb3, 0xcd, 0xb3, 0x03, 0xb7, 0x98, 0xbf, 0xb5, 0xb4, 0xb0, 0xec, 0x68, 0x54, 0x6e, 0xf9, 0x57, 0xa3, 0xf2, 0x86, 0x5f, 0x36, 0x74, 0x06, 0x74, 0x0a, 0xd8, 0xc4, 0xbf, 0x8a, 0x46, 0xe5, 0xda, 0xbf, 0xc0, 0x20, 0xfe, 0x6d, 0x09, 0xcc, 0x08, 0x6a, 0xd3, 0xec, 0x95, 0xe6, 0x6d, 0x9a, 0x3f, 0xcc, 0xbf, 0xf7, 0x6f, 0xeb, 0xd6, 0xb6, 0x37, 0xff, 0xee, 0x6a, 0xb7, 0xaa, 0xfd, 0xdc, 0xf6, 0x07, 0x3b, 0x7a, 0x77, 0x5c, 0xd6, 0xf1, 0x93, 0x81, 0xef, 0x87, 0x7b, 0x77, 0x6a, 0xd4, 0xa9, 0xd1, 0x80, 0xd7, 0xfa, 0x26, 0x0e, 0xdb, 0x3d, 0x6c, 0x77, 0xa7, 0x49, 0x9d, 0xd6, 0x76, 0x8d, 0xe9, 0xfa, 0xf6, 0xc0, 0xf7, 0x07, 0xbe, 0x3f, 0xec, 0xd5, 0x70, 0xef, 0x70, 0xef, 0xee, 0xb3, 0x06, 0xbc, 0x36, 0xe0, 0xb5, 0x88, 0xa1, 0xdd, 0x8f, 0x76, 0x2f, 0xed, 0xf1, 0x53, 0xcf, 0xc2, 0xd0, 0x3b, 0x42, 0x77, 0x44, 0x0c, 0xd5, 0xfe, 0x85, 0x66, 0x86, 0x5e, 0xed, 0x95, 0xd9, 0xfb, 0x7c, 0x9f, 0x45, 0x7d, 0x76, 0x7b, 0x8e, 0x44, 0x0c, 0xed, 0x9b, 0xd8, 0x37, 0xb1, 0x4f, 0x4e, 0x9f, 0x1c, 0xad, 0xac, 0xbe, 0xf5, 0xfa, 0x76, 0xd1, 0xae, 0xe5, 0xea, 0x76, 0x7d, 0xa7, 0x45, 0xb4, 0xd3, 0xfe, 0x69, 0xef, 0xf7, 0xdd, 0xdb, 0x37, 0xc7, 0x73, 0x7c, 0x40, 0xf5, 0xc0, 0x69, 0x5a, 0x0b, 0xc2, 0xbd, 0x23, 0xe6, 0x86, 0x2f, 0x0a, 0xdf, 0x11, 0xbe, 0x43, 0xbb, 0x2e, 0xfc, 0xe8, 0x90, 0xf4, 0x61, 0xbb, 0x87, 0x54, 0xb3, 0xff, 0x2a, 0xd7, 0x0c, 0xad, 0xdd, 0x46, 0xcc, 0x8d, 0xd8, 0x12, 0xf1, 0x61, 0x84, 0x73, 0x78, 0xf0, 0xf0, 0x31, 0xc3, 0xef, 0x1a, 0xfe, 0xd4, 0xe8, 0xbd, 0xe3, 0x8e, 0x8e, 0x7f, 0x77, 0x52, 0xfe, 0xe4, 0xb4, 0xc9, 0x3f, 0x4d, 0x3b, 0x3d, 0xe3, 0xe3, 0xdb, 0x5f, 0x9b, 0xdb, 0x7b, 0x6e, 0xfc, 0xdc, 0x37, 0xe6, 0x56, 0xdc, 0xb1, 0x75, 0xfe, 0xa4, 0xf9, 0xcf, 0x2d, 0x1a, 0xbc, 0x68, 0xd1, 0xa2, 0x75, 0x51, 0x8f, 0x2e, 0x0e, 0x89, 0x7a, 0x34, 0xea, 0xd7, 0x98, 0x20, 0x7b, 0x9b, 0x25, 0x19, 0xcb, 0x42, 0x63, 0xbb, 0xc4, 0x2e, 0x88, 0xab, 0x59, 0x16, 0xba, 0x6c, 0xd9, 0xb2, 0xcf, 0xe2, 0x6b, 0x12, 0xc6, 0x25, 0x6c, 0x4a, 0xfa, 0x31, 0xe9, 0xc7, 0xb4, 0xea, 0xe4, 0x55, 0xc9, 0x9f, 0xad, 0x12, 0x2b, 0xc2, 0x57, 0x89, 0x35, 0x5f, 0xaf, 0xf9, 0x7a, 0xe5, 0x5a, 0xf6, 0xef, 0x4b, 0x19, 0x9a, 0xb2, 0x38, 0xad, 0x7a, 0x95, 0xd0, 0xfe, 0xa5, 0x55, 0xaf, 0xf9, 0x5a, 0x7b, 0x6f, 0xe5, 0xda, 0x95, 0xb3, 0xb4, 0x77, 0x57, 0x85, 0xdf, 0x15, 0xba, 0xe6, 0xeb, 0xb5, 0x93, 0xd6, 0xd6, 0xac, 0xeb, 0x7d, 0xf7, 0xee, 0xbb, 0x8f, 0xae, 0x73, 0xae, 0xab, 0xb7, 0xae, 0xf7, 0x3d, 0xa6, 0x75, 0xf7, 0xdd, 0x63, 0x5a, 0x1f, 0x7a, 0x8f, 0x49, 0xfb, 0xff, 0xfa, 0x37, 0x79, 0xfd, 0xea, 0xba, 0xd3, 0xeb, 0xdf, 0x5c, 0xe7, 0xd4, 0x8e, 0xf0, 0xca, 0xa9, 0xbd, 0xa7, 0xbd, 0xb3, 0xfe, 0x1e, 0xed, 0xdd, 0x7b, 0xda, 0xdd, 0x73, 0x70, 0x83, 0xe5, 0xbe, 0x26, 0x5b, 0xea, 0x6d, 0x71, 0x3e, 0x74, 0x75, 0x7b, 0xbd, 0x6d, 0xa6, 0xed, 0xf5, 0xb6, 0xbf, 0xf0, 0x88, 0xed, 0x91, 0xe7, 0x1e, 0xf9, 0x51, 0xe3, 0xd1, 0x31, 0xcf, 0x9d, 0xdb, 0xd3, 0xe8, 0xa5, 0x69, 0xfb, 0x2f, 0xbe, 0xa2, 0x3d, 0xf5, 0xce, 0xaa, 0x7d, 0x62, 0x15, 0x95, 0x2c, 0x11, 0x0f, 0x63, 0xe9, 0xdf, 0x88, 0xb3, 0x28, 0xdd, 0x21, 0x8a, 0xc5, 0xcb, 0xa2, 0x54, 0x09, 0x15, 0x6f, 0x28, 0xfd, 0x95, 0x70, 0x51, 0xa2, 0x0c, 0x56, 0xc6, 0x8a, 0x4b, 0xca, 0x78, 0x65, 0x82, 0x90, 0xca, 0x24, 0xfe, 0x29, 0xca, 0x54, 0xfe, 0x99, 0xc4, 0x29, 0xe1, 0x25, 0x3f, 0x15, 0xde, 0xe0, 0x23, 0xdf, 0x15, 0x81, 0xf2, 0x6b, 0xd1, 0x44, 0x16, 0x8b, 0x76, 0xf2, 0x4b, 0xd1, 0x5e, 0x7e, 0x23, 0x3a, 0xc9, 0x0f, 0x44, 0x67, 0x59, 0x24, 0xba, 0xca, 0xb7, 0xc4, 0x32, 0xf9, 0xb6, 0x58, 0x2f, 0x2f, 0x89, 0x7b, 0x64, 0xb9, 0x48, 0x97, 0x3f, 0x8a, 0x07, 0x65, 0x96, 0x78, 0x48, 0x1e, 0x15, 0xdf, 0xca, 0xcf, 0xc5, 0x61, 0xae, 0xff, 0x41, 0x9e, 0x37, 0x0d, 0x97, 0x07, 0x4d, 0xa3, 0x61, 0x26, 0xdc, 0x21, 0xdf, 0x32, 0xc5, 0xca, 0x48, 0xd3, 0x16, 0xf9, 0xab, 0xe9, 0x01, 0x79, 0xca, 0xb4, 0x43, 0x6e, 0xf3, 0x7a, 0x56, 0x7e, 0xee, 0xd3, 0x57, 0x96, 0xfa, 0x84, 0xc1, 0x97, 0xf2, 0x94, 0xcf, 0x69, 0x99, 0x6f, 0xee, 0x2a, 0x3f, 0x35, 0x77, 0x83, 0xee, 0xd0, 0x03, 0x7a, 0x42, 0x28, 0xf4, 0x82, 0xde, 0xd0, 0x07, 0xfa, 0x42, 0x18, 0xf4, 0x87, 0x01, 0x30, 0x10, 0xc2, 0x61, 0x10, 0x0c, 0x86, 0x21, 0x30, 0x14, 0x86, 0x41, 0x04, 0x0c, 0x87, 0x11, 0x30, 0x12, 0x46, 0xc1, 0x68, 0x18, 0x03, 0x63, 0x61, 0x1c, 0x4c, 0x00, 0xed, 0x5b, 0x64, 0x93, 0x44, 0x23, 0xf3, 0x64, 0xf6, 0xa7, 0xc0, 0x54, 0x5e, 0x4f, 0x13, 0xcd, 0xcd, 0xd3, 0xd9, 0x9f, 0x01, 0x33, 0x61, 0x16, 0xdc, 0x0e, 0xb3, 0x61, 0x0e, 0xcc, 0xe5, 0x9c, 0x79, 0x5c, 0x73, 0x07, 0xfb, 0xf3, 0x61, 0x01, 0xaf, 0x17, 0x72, 0xcd, 0x22, 0xf6, 0x23, 0x21, 0x4a, 0xbe, 0x6b, 0x8e, 0x86, 0xfb, 0xe5, 0x9d, 0xe6, 0xa7, 0x44, 0x03, 0xf3, 0xd3, 0xe2, 0x47, 0xf3, 0x33, 0x22, 0xdf, 0xfc, 0xac, 0x08, 0x34, 0x3f, 0x27, 0xb2, 0xcc, 0x7b, 0x44, 0x91, 0x79, 0xaf, 0xbc, 0x68, 0x7e, 0x5e, 0x78, 0x99, 0xdf, 0xe5, 0xfd, 0xf7, 0x78, 0xff, 0x7d, 0xde, 0xff, 0x40, 0x4c, 0x36, 0x7f, 0xc8, 0x39, 0x1f, 0x71, 0xce, 0xc7, 0x9c, 0xf3, 0x4f, 0x5e, 0x7f, 0xc2, 0x79, 0x9f, 0x72, 0xce, 0xbf, 0xe1, 0x33, 0xce, 0xfb, 0x9c, 0xf3, 0x0e, 0x72, 0xfc, 0x0b, 0xce, 0xfb, 0x92, 0xf3, 0xbe, 0xe2, 0xbc, 0xaf, 0x79, 0x7d, 0x42, 0x34, 0x35, 0x9f, 0x14, 0xa7, 0xcd, 0xa7, 0xc4, 0x65, 0x73, 0xae, 0x98, 0x63, 0xce, 0x13, 0xad, 0xcc, 0xa7, 0x45, 0x81, 0xf9, 0x8c, 0xa8, 0x32, 0xff, 0x2a, 0xe6, 0x99, 0xcf, 0xca, 0x2a, 0xf3, 0x39, 0xd1, 0xd8, 0xec, 0x94, 0x5f, 0x9b, 0xcf, 0xcb, 0xaf, 0x2d, 0x4d, 0xe4, 0xb7, 0x96, 0xae, 0xd2, 0x69, 0xe9, 0x26, 0x5e, 0xb7, 0x74, 0x17, 0x07, 0x2d, 0x3d, 0x64, 0x81, 0xa5, 0xa7, 0x78, 0xc7, 0x12, 0x2a, 0xbe, 0xb2, 0xf4, 0x92, 0x87, 0x2c, 0xbd, 0x65, 0x89, 0x65, 0x9a, 0xfc, 0xd2, 0x32, 0x1d, 0x66, 0xc0, 0x2c, 0x98, 0x0d, 0x73, 0x60, 0x2e, 0xcc, 0x83, 0x64, 0xf9, 0x81, 0x65, 0x05, 0xa4, 0xc8, 0x22, 0x4b, 0x2a, 0xa4, 0xc1, 0x4a, 0x58, 0x05, 0xab, 0x61, 0x9d, 0x7c, 0xcb, 0xb2, 0x1e, 0xee, 0x81, 0x0d, 0x70, 0x2f, 0x6c, 0x14, 0x5d, 0x2d, 0x9b, 0xd8, 0x6e, 0x86, 0xfb, 0xe0, 0x7e, 0xd8, 0x02, 0x0f, 0x40, 0xba, 0x88, 0xb0, 0x3c, 0xa8, 0x4c, 0xb7, 0x6c, 0x55, 0x26, 0x5a, 0x1e, 0x12, 0xbb, 0x2c, 0x0f, 0x8b, 0xde, 0x96, 0x6d, 0x9c, 0xbf, 0x9d, 0xf7, 0x1e, 0x91, 0x6f, 0xa9, 0x3d, 0x65, 0x89, 0x3a, 0x18, 0x86, 0xcb, 0xcf, 0xd4, 0xfb, 0xd8, 0xee, 0x92, 0x97, 0xd4, 0x67, 0x65, 0x81, 0x9a, 0x29, 0x7f, 0x54, 0xb3, 0x20, 0x5b, 0x66, 0xa9, 0x39, 0x70, 0x02, 0x4e, 0xc2, 0x29, 0xc8, 0x85, 0x3c, 0x38, 0x27, 0x8f, 0xaa, 0x0e, 0xc8, 0x87, 0x02, 0x70, 0x42, 0x31, 0x9c, 0x87, 0x0b, 0x70, 0x11, 0x4a, 0xe4, 0x51, 0x6b, 0x4b, 0xf1, 0x88, 0x35, 0x44, 0x6c, 0xb3, 0xb6, 0x12, 0x36, 0xab, 0x4d, 0x16, 0x58, 0x5b, 0xf3, 0xba, 0x8d, 0x78, 0xde, 0xda, 0x96, 0xd7, 0x9d, 0xd8, 0xef, 0xcc, 0x7b, 0x5d, 0xd8, 0xef, 0xca, 0x7b, 0xdd, 0x78, 0xdd, 0x9d, 0xf7, 0x88, 0x51, 0xac, 0x33, 0x65, 0xb9, 0xf5, 0x17, 0xf9, 0xa4, 0xdf, 0x28, 0x99, 0xe3, 0xf7, 0x86, 0xa8, 0xe7, 0xf7, 0x0f, 0xf9, 0xb9, 0xdf, 0x9b, 0xca, 0x08, 0xbf, 0xb7, 0x94, 0x29, 0x7e, 0x6f, 0x8b, 0x74, 0xbf, 0x77, 0x44, 0x73, 0xbf, 0x77, 0x45, 0x17, 0xbf, 0xf7, 0x38, 0xfe, 0x3e, 0x7c, 0x40, 0x4c, 0x6b, 0x13, 0x26, 0x2c, 0xa6, 0x89, 0x74, 0x61, 0x65, 0x1b, 0x44, 0xb8, 0x2c, 0x14, 0x76, 0xd8, 0x26, 0x9f, 0x13, 0xdb, 0xe5, 0x63, 0xe2, 0x11, 0xf9, 0xae, 0xd2, 0x4f, 0x66, 0x2b, 0xe3, 0xe5, 0x36, 0x65, 0xa6, 0x3c, 0xa9, 0xdc, 0x2e, 0x4b, 0x95, 0xd9, 0xf2, 0x11, 0x25, 0x52, 0x9e, 0x57, 0xa2, 0x21, 0x46, 0x6e, 0x52, 0xec, 0x72, 0xaf, 0x52, 0x22, 0x9d, 0x4a, 0xb9, 0x74, 0x9b, 0xc6, 0xc9, 0x33, 0x26, 0xbb, 0x3c, 0x66, 0xda, 0x22, 0xba, 0x61, 0x69, 0xaf, 0x98, 0x3e, 0x90, 0x65, 0xde, 0x9b, 0xe4, 0x13, 0xde, 0x4f, 0xc8, 0x72, 0xac, 0x6d, 0x1d, 0xd6, 0xb6, 0x0e, 0x6b, 0x7b, 0xc5, 0x9a, 0x2b, 0x5f, 0xf7, 0xdb, 0x26, 0xab, 0xfc, 0xb6, 0xcb, 0x2a, 0xd1, 0xfe, 0x77, 0x5b, 0xb0, 0x84, 0x75, 0xf0, 0xd6, 0x56, 0x44, 0x88, 0x26, 0xca, 0x70, 0xd0, 0x5a, 0x33, 0x5d, 0xf8, 0x29, 0x33, 0x45, 0x23, 0xe5, 0x76, 0xd1, 0xf0, 0x7f, 0xaf, 0x45, 0x42, 0xf1, 0xfe, 0x9b, 0xf0, 0xfb, 0xc3, 0x56, 0x35, 0x30, 0x5a, 0x54, 0x4c, 0x8b, 0xca, 0x68, 0xd1, 0x05, 0xfa, 0x7f, 0x9e, 0xfe, 0x97, 0x50, 0x5a, 0x15, 0x25, 0x39, 0x4d, 0x6f, 0xca, 0x2f, 0x29, 0xed, 0x22, 0x7d, 0xfb, 0xc5, 0xfb, 0x6f, 0xb2, 0x8c, 0x12, 0x9c, 0x6a, 0x88, 0xf0, 0x56, 0x7b, 0x09, 0xed, 0x2f, 0x83, 0xe1, 0xf2, 0x22, 0x71, 0x4c, 0x73, 0x11, 0x23, 0x5d, 0xca, 0x6c, 0x61, 0x51, 0x62, 0x44, 0x7d, 0xae, 0x3a, 0xcb, 0x55, 0xe5, 0x9c, 0x79, 0x96, 0x33, 0x9b, 0xa9, 0xad, 0x44, 0x27, 0xd5, 0x26, 0x7a, 0xaa, 0xad, 0xc5, 0x6d, 0x6a, 0x1b, 0xe1, 0xcf, 0x95, 0x2d, 0x45, 0xb0, 0xb0, 0xca, 0x4a, 0xd1, 0x10, 0x9a, 0x50, 0x42, 0x7b, 0x79, 0x4a, 0x2f, 0xc9, 0xce, 0xb8, 0xf7, 0x97, 0x47, 0x95, 0x31, 0xb2, 0x40, 0x99, 0xce, 0x48, 0xcc, 0x94, 0x47, 0x68, 0xcb, 0x71, 0xda, 0x72, 0xc5, 0xb4, 0x54, 0x56, 0x52, 0xaa, 0x53, 0x6f, 0xcb, 0x63, 0x32, 0x8b, 0xf6, 0x1c, 0xa1, 0x3d, 0x59, 0x3e, 0x21, 0xb2, 0xd2, 0x27, 0x0f, 0x4e, 0xcb, 0x4a, 0x33, 0xe7, 0x98, 0xd7, 0xc2, 0xdd, 0x50, 0x29, 0x2b, 0x2d, 0x03, 0x65, 0x25, 0x3d, 0x3d, 0x43, 0x4f, 0xcf, 0x88, 0x20, 0x6a, 0xf8, 0x99, 0x76, 0xe6, 0x53, 0x8b, 0x8b, 0x71, 0x6f, 0x2a, 0xd6, 0xca, 0x42, 0xc6, 0x4d, 0xeb, 0xe5, 0x2f, 0x5a, 0x7b, 0x29, 0xd9, 0x41, 0x9b, 0x7f, 0xb1, 0xbc, 0x24, 0x1a, 0x5a, 0x72, 0x65, 0xae, 0x1a, 0x4c, 0xdb, 0x5b, 0xca, 0x4a, 0xfa, 0xd0, 0x8a, 0x3e, 0x34, 0xa2, 0x0f, 0x8d, 0x68, 0xbf, 0x45, 0xed, 0x20, 0x3a, 0xd0, 0x87, 0x76, 0x44, 0x34, 0x26, 0xf9, 0x13, 0xa5, 0xee, 0x12, 0x91, 0xc4, 0x4e, 0x31, 0xf2, 0x0d, 0x4a, 0xfc, 0x96, 0xf6, 0x4f, 0x51, 0x06, 0xc8, 0x12, 0x65, 0xa0, 0xbc, 0x48, 0x3f, 0x5e, 0x54, 0x4a, 0xe5, 0x69, 0xa5, 0x0c, 0xca, 0x65, 0x3e, 0xb5, 0x5d, 0xa1, 0xb6, 0x3c, 0x6a, 0xdb, 0x4b, 0x6d, 0xcf, 0x50, 0x5b, 0x9e, 0xe5, 0x25, 0xf9, 0xab, 0x51, 0x5b, 0x6b, 0x6a, 0xcb, 0xa1, 0xb6, 0x4e, 0xd4, 0xd6, 0x82, 0xda, 0x5a, 0x30, 0x62, 0xbe, 0xd4, 0x18, 0x40, 0x8d, 0x3d, 0xa8, 0xb1, 0x9b, 0x1a, 0x85, 0x75, 0x6d, 0x62, 0xd5, 0xb0, 0x52, 0x6b, 0x05, 0xfd, 0xa8, 0x66, 0x84, 0xca, 0x28, 0xb1, 0x88, 0x12, 0x0b, 0x29, 0xb1, 0x82, 0x51, 0xb9, 0x44, 0xa9, 0x45, 0x94, 0xa0, 0x50, 0x82, 0xc5, 0xa4, 0x08, 0x33, 0x63, 0x6c, 0x01, 0x7f, 0xb8, 0x0d, 0x82, 0x81, 0x5e, 0x09, 0x1b, 0x74, 0x66, 0x65, 0xe9, 0x2e, 0x4f, 0x8a, 0x1e, 0xd0, 0x13, 0x42, 0x51, 0x64, 0x2f, 0xf9, 0x8b, 0xe8, 0x0d, 0x7d, 0xa0, 0x2f, 0x84, 0x41, 0x3f, 0xe8, 0x2f, 0x73, 0xc4, 0x00, 0x18, 0x08, 0xe1, 0x32, 0x57, 0x0c, 0xe2, 0xd8, 0x60, 0x18, 0x22, 0x2f, 0x88, 0xa1, 0x30, 0x0c, 0x22, 0x60, 0x38, 0x8c, 0x80, 0x91, 0x30, 0x0a, 0x46, 0xc3, 0x18, 0x18, 0x0b, 0xe3, 0x60, 0x3c, 0x4c, 0x80, 0x89, 0x30, 0x09, 0x26, 0xc3, 0x14, 0x98, 0x0a, 0xd3, 0x60, 0x3a, 0xcc, 0x80, 0x99, 0x30, 0x0b, 0x6e, 0x87, 0xd9, 0x30, 0x07, 0xe6, 0xc2, 0x3c, 0xb8, 0x03, 0xe6, 0xc3, 0x02, 0x58, 0x28, 0x33, 0xc5, 0x22, 0x99, 0x2d, 0xee, 0xa4, 0xed, 0x8b, 0x21, 0x12, 0xa2, 0x58, 0x41, 0xa3, 0x21, 0x06, 0xec, 0xb2, 0x44, 0x2c, 0x61, 0xbb, 0x14, 0x62, 0x79, 0x2f, 0x0e, 0x96, 0xc1, 0x72, 0x88, 0x87, 0x04, 0x48, 0x84, 0x24, 0xfa, 0x92, 0x0c, 0x2b, 0x20, 0x85, 0xd7, 0xa9, 0x90, 0x46, 0x5f, 0x57, 0xb2, 0x5d, 0xc5, 0xd8, 0xac, 0x86, 0xbb, 0x60, 0x0d, 0xa0, 0x1c, 0x71, 0x37, 0x75, 0xaf, 0x63, 0x7f, 0x3d, 0xdc, 0x03, 0x1b, 0xe0, 0x5e, 0xd8, 0x08, 0x9b, 0x60, 0x33, 0xdc, 0x07, 0xf7, 0xc3, 0x16, 0xe9, 0x10, 0x0f, 0x40, 0x3a, 0x71, 0xf1, 0x83, 0x44, 0x0f, 0x5b, 0x89, 0x28, 0x1f, 0x22, 0xc6, 0x7c, 0x58, 0x9e, 0x55, 0xf2, 0x65, 0xa5, 0x52, 0x28, 0x7f, 0x51, 0x8a, 0xa4, 0xc3, 0x34, 0x0a, 0x8d, 0x4f, 0x84, 0xe9, 0x70, 0x27, 0x24, 0x33, 0xab, 0x2b, 0x20, 0x15, 0x85, 0xd2, 0x16, 0xd3, 0x5d, 0xec, 0xaf, 0x81, 0x75, 0x40, 0x9d, 0x26, 0xea, 0x34, 0x6d, 0xc4, 0xda, 0xb6, 0xc8, 0x4c, 0x54, 0x50, 0x6a, 0x4a, 0xe7, 0x9c, 0xad, 0xbc, 0x7e, 0x94, 0xe3, 0x4f, 0xb0, 0xbf, 0x03, 0x05, 0x3c, 0xc9, 0xb9, 0x4f, 0xc1, 0xd3, 0x1c, 0xdf, 0x2b, 0x2f, 0x98, 0x9e, 0x87, 0x17, 0xe0, 0x45, 0xd8, 0x07, 0x2f, 0xc1, 0x7e, 0x78, 0x19, 0x5e, 0x81, 0x03, 0xf0, 0x2a, 0xd7, 0xff, 0x1d, 0xde, 0x94, 0xc5, 0xa8, 0xea, 0x9c, 0x37, 0xe3, 0xeb, 0x9d, 0x26, 0x0b, 0x7d, 0x82, 0xe0, 0xdf, 0xf2, 0x82, 0xcf, 0x67, 0xf0, 0x85, 0x2c, 0x42, 0x69, 0xa5, 0x3e, 0x47, 0xd8, 0x3f, 0x0a, 0x3f, 0xc0, 0x8f, 0x70, 0x0c, 0x4e, 0x61, 0x8f, 0x67, 0xe4, 0x2f, 0x3e, 0x05, 0x32, 0xc7, 0xe7, 0xbc, 0x74, 0xf8, 0xb6, 0x95, 0x85, 0xbe, 0xed, 0xa0, 0x83, 0x3c, 0xe9, 0x5b, 0x25, 0x7f, 0x31, 0x2f, 0xc7, 0x3e, 0xe3, 0x21, 0x01, 0x12, 0x21, 0x05, 0xf6, 0xc1, 0x4b, 0xb0, 0x1f, 0x5e, 0x86, 0x57, 0xe0, 0x00, 0xbc, 0x8a, 0x1d, 0xd7, 0x07, 0xf4, 0x6b, 0x69, 0x00, 0xf8, 0x0c, 0x4b, 0x00, 0x34, 0x82, 0x40, 0x08, 0x82, 0x66, 0xd0, 0x1c, 0x5a, 0x00, 0x1a, 0xb7, 0xa0, 0x71, 0x0b, 0x1a, 0xb7, 0xe0, 0x1b, 0x2c, 0xad, 0x00, 0xad, 0x5b, 0x5a, 0x43, 0x1b, 0x68, 0x0b, 0xed, 0xa0, 0x3d, 0x74, 0x80, 0x8e, 0xd0, 0x09, 0x3a, 0x43, 0x17, 0xe8, 0x0b, 0x29, 0xb2, 0x9c, 0x55, 0xb6, 0x9c, 0x55, 0xb6, 0x9c, 0x55, 0xb6, 0x9c, 0x55, 0xb6, 0x9c, 0x55, 0xb6, 0xdc, 0xb2, 0x5b, 0x9e, 0xb4, 0x3c, 0x09, 0x4f, 0xc1, 0xd3, 0xf0, 0x0c, 0x3c, 0x0b, 0xcf, 0xc1, 0x1e, 0xd8, 0x0b, 0x2f, 0xc0, 0x8b, 0xb0, 0x0f, 0x5e, 0x82, 0xfd, 0xf0, 0x32, 0xbc, 0x02, 0x07, 0x80, 0xb1, 0xb5, 0x30, 0xb6, 0x96, 0xd7, 0xe0, 0x75, 0x78, 0x03, 0xfe, 0x01, 0x6f, 0xc2, 0x5b, 0xf0, 0x36, 0xbc, 0x0f, 0x1f, 0xc0, 0x87, 0xf0, 0x11, 0x7c, 0x0c, 0x9f, 0xc8, 0x42, 0xcb, 0xa7, 0xf0, 0x2f, 0xf8, 0x0c, 0x2f, 0xf5, 0x39, 0x1c, 0x84, 0x2f, 0xe0, 0x1b, 0xf8, 0x56, 0xe6, 0x58, 0x0e, 0xc1, 0x61, 0xf8, 0x0e, 0x8e, 0xc2, 0x0f, 0x1c, 0xff, 0x11, 0x8e, 0xc1, 0x4f, 0xf0, 0x33, 0x64, 0xc0, 0x71, 0xc8, 0x84, 0x1c, 0x38, 0x01, 0x27, 0xe1, 0x14, 0xe4, 0x42, 0x1e, 0x9c, 0x06, 0xe6, 0xcf, 0xf2, 0x2b, 0x9c, 0x03, 0x07, 0xe4, 0x43, 0x01, 0xa0, 0x55, 0x8b, 0x13, 0x8a, 0xe1, 0xbc, 0xbc, 0x60, 0xb9, 0x08, 0x25, 0x50, 0x0a, 0x65, 0x50, 0x0e, 0x97, 0xa0, 0x02, 0x2a, 0xe1, 0x32, 0x5c, 0x81, 0xab, 0x50, 0x05, 0xd5, 0xe0, 0x02, 0x37, 0x5c, 0x83, 0x1a, 0x90, 0x32, 0x53, 0x15, 0xa0, 0x80, 0x09, 0xbc, 0xc0, 0x1b, 0x7c, 0xc0, 0x57, 0x66, 0xab, 0x66, 0xb0, 0x80, 0x0a, 0xf5, 0xc1, 0x1f, 0x1a, 0x40, 0x43, 0x59, 0xa8, 0x06, 0x40, 0x23, 0x08, 0x04, 0xf4, 0xa9, 0x36, 0x86, 0xa6, 0x78, 0xc0, 0xe6, 0xd0, 0x02, 0x82, 0xa1, 0x25, 0x84, 0xc8, 0x2a, 0xb5, 0x15, 0x5b, 0x1b, 0xb4, 0x86, 0x36, 0xd0, 0x1e, 0x3a, 0x40, 0x27, 0xe8, 0x06, 0xdd, 0xa1, 0x27, 0xf4, 0x92, 0x35, 0x6a, 0x1f, 0xca, 0xe9, 0x0b, 0x61, 0xd0, 0x0f, 0xfa, 0x03, 0xab, 0xb6, 0x3a, 0x08, 0x06, 0xc3, 0x10, 0x18, 0x0a, 0xc3, 0x20, 0x02, 0x86, 0xc3, 0x08, 0x18, 0x05, 0xa3, 0x61, 0x0c, 0x8c, 0x87, 0x09, 0x30, 0x11, 0x26, 0xc1, 0x64, 0x98, 0x02, 0x53, 0x61, 0x1a, 0x4c, 0x87, 0x99, 0x30, 0x4b, 0xfe, 0xa2, 0xde, 0x0e, 0xb3, 0x61, 0x0e, 0xcc, 0x85, 0x79, 0x70, 0x07, 0xcc, 0x87, 0x3b, 0x61, 0x31, 0x44, 0x42, 0x14, 0x44, 0x43, 0x0c, 0xd8, 0x61, 0x09, 0x2c, 0x85, 0x58, 0x88, 0x83, 0x65, 0xb0, 0x1c, 0xe2, 0x21, 0x01, 0x12, 0xe1, 0x2e, 0x58, 0x03, 0x6b, 0x01, 0xdf, 0xa1, 0xae, 0x87, 0x8d, 0xb0, 0x09, 0x36, 0xc3, 0x7d, 0x70, 0x3f, 0x6c, 0x81, 0x07, 0xe0, 0x41, 0xd8, 0x0a, 0x0f, 0xc9, 0x93, 0xea, 0xc3, 0xb0, 0x0d, 0xb6, 0x03, 0x7e, 0x45, 0x7d, 0x0c, 0x1e, 0x87, 0x27, 0x60, 0x07, 0xec, 0x02, 0xec, 0x42, 0xc5, 0x2e, 0x54, 0xec, 0x42, 0xc5, 0x2e, 0x54, 0xec, 0x82, 0x68, 0xf1, 0xa4, 0x8a, 0x5d, 0xa8, 0xd8, 0x85, 0x8a, 0x5d, 0xa8, 0xcf, 0x03, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0xbe, 0x03, 0xef, 0xc2, 0x7b, 0x80, 0x9d, 0xa8, 0xd8, 0x89, 0x8a, 0x9d, 0xa8, 0xd8, 0x89, 0x8a, 0x9d, 0xa8, 0xff, 0x84, 0x4f, 0xe0, 0x53, 0xf8, 0x97, 0x74, 0xa8, 0xff, 0x86, 0xcf, 0xe0, 0x73, 0x38, 0x08, 0x5f, 0xc0, 0x97, 0xf0, 0x15, 0x7c, 0x03, 0xdf, 0xc2, 0x21, 0x38, 0x0c, 0xdf, 0xc1, 0xf7, 0x70, 0x04, 0x8e, 0xc2, 0x0f, 0xf0, 0x23, 0x1c, 0x83, 0x9f, 0x20, 0x03, 0x8e, 0x43, 0xa6, 0xf0, 0x51, 0xb3, 0x20, 0x5b, 0x98, 0xd4, 0x1c, 0x38, 0x01, 0x27, 0xe1, 0x14, 0xe4, 0x42, 0x1e, 0x9c, 0x21, 0x46, 0xfa, 0x15, 0xce, 0x11, 0x31, 0x38, 0x20, 0x1f, 0x0a, 0xc0, 0x09, 0xc5, 0x70, 0x1e, 0x2e, 0xc0, 0x45, 0x28, 0x81, 0x4b, 0x44, 0x4b, 0x15, 0x50, 0x09, 0x97, 0xe1, 0x0a, 0x5c, 0x95, 0x67, 0x89, 0x74, 0x0b, 0xad, 0x4e, 0x59, 0x69, 0x2d, 0x86, 0xf3, 0x70, 0x01, 0x2e, 0x42, 0x85, 0xfc, 0xc5, 0x5a, 0x09, 0x97, 0xe1, 0x0a, 0x5c, 0x85, 0x2a, 0xe9, 0xb0, 0x56, 0x83, 0x0b, 0xdc, 0x70, 0x4d, 0x3a, 0xfc, 0xf0, 0x05, 0x44, 0x6a, 0xda, 0x8a, 0xad, 0xaf, 0x84, 0x42, 0xd1, 0x57, 0x0a, 0xc3, 0xb3, 0x6b, 0x1e, 0x5c, 0xb7, 0x0c, 0x14, 0x4f, 0x2c, 0x18, 0xce, 0x3a, 0x1a, 0xa3, 0x47, 0xa6, 0x17, 0x88, 0x63, 0xce, 0x13, 0xbf, 0x5c, 0xe0, 0x6c, 0x2d, 0xee, 0xca, 0xe3, 0xec, 0x93, 0x46, 0x74, 0x51, 0x89, 0x5f, 0x3b, 0x8b, 0x4d, 0x15, 0x72, 0xa5, 0xc4, 0x7e, 0x6a, 0x88, 0x4d, 0xbc, 0x45, 0x13, 0xae, 0xfe, 0x90, 0x2b, 0xaf, 0xb2, 0x8a, 0x1e, 0x21, 0x4e, 0xcd, 0x24, 0x4e, 0xcd, 0x22, 0x1e, 0xf4, 0x27, 0xea, 0x39, 0x4b, 0x04, 0x57, 0x41, 0x6c, 0x1a, 0x4a, 0x69, 0x8f, 0x51, 0xd2, 0x09, 0x4a, 0xf9, 0x84, 0x38, 0x6b, 0x00, 0x1e, 0xc7, 0x45, 0xe4, 0x33, 0x9c, 0x58, 0xc5, 0x9f, 0x68, 0xa7, 0x3e, 0xd1, 0x4e, 0x2f, 0xa2, 0x9d, 0x29, 0xa8, 0xbc, 0x1c, 0xb5, 0x1e, 0x21, 0x56, 0xfd, 0x89, 0x58, 0xd1, 0x44, 0x76, 0x1c, 0x2e, 0x0f, 0x52, 0xfa, 0x0f, 0x44, 0xcb, 0x7d, 0x89, 0x96, 0xfb, 0x10, 0x2d, 0xf7, 0x21, 0x5a, 0xce, 0x23, 0x4a, 0x4e, 0x51, 0xa6, 0xd6, 0x9c, 0x25, 0x3a, 0xee, 0x4d, 0xad, 0xa7, 0xa9, 0xf5, 0x34, 0xd1, 0x71, 0x2f, 0xa2, 0xe3, 0xde, 0x44, 0xc7, 0x55, 0xd4, 0x5e, 0x48, 0xed, 0x5f, 0x51, 0xbb, 0x95, 0xda, 0xa7, 0xd1, 0x97, 0xab, 0xb4, 0xc0, 0x49, 0x74, 0xdc, 0xdb, 0x7b, 0xab, 0x8c, 0xf5, 0xde, 0x25, 0x2f, 0x12, 0x1d, 0xf7, 0x27, 0x3a, 0xee, 0x4b, 0xab, 0xa6, 0x51, 0x63, 0xb8, 0x18, 0xa6, 0xd7, 0xd8, 0x4e, 0x26, 0x12, 0x99, 0x26, 0x92, 0x93, 0xef, 0x26, 0x52, 0xda, 0x43, 0x4e, 0xbe, 0xfb, 0x0f, 0x5b, 0x31, 0x44, 0x4e, 0x52, 0x86, 0xc1, 0x78, 0xb9, 0xc6, 0x68, 0xcd, 0x14, 0x65, 0xa1, 0x9c, 0xa5, 0x2c, 0x82, 0x1b, 0xad, 0x5a, 0x71, 0x53, 0xab, 0xee, 0x90, 0xbb, 0xf5, 0x96, 0xc5, 0xb2, 0xfd, 0xcf, 0xad, 0x9b, 0x41, 0xeb, 0xa6, 0x6a, 0xad, 0x23, 0x4f, 0x4c, 0x24, 0x4f, 0x4c, 0x24, 0x47, 0x4c, 0x24, 0x47, 0x4c, 0x24, 0x47, 0x4c, 0x24, 0x47, 0x4c, 0x24, 0x47, 0x4c, 0x24, 0x47, 0xdc, 0x4d, 0x8e, 0xb8, 0x9b, 0xd5, 0x6b, 0x0f, 0xab, 0xd7, 0x1e, 0x56, 0xaf, 0x3d, 0xac, 0x5e, 0x7b, 0x58, 0xbd, 0xf6, 0xb0, 0x7a, 0xed, 0x21, 0x47, 0xdc, 0x4d, 0x8e, 0xb8, 0x9b, 0x1c, 0x71, 0x37, 0x39, 0xe2, 0x6e, 0xf2, 0xc2, 0xdd, 0xe4, 0x85, 0xbb, 0xc9, 0x0b, 0x77, 0x93, 0x17, 0xee, 0x26, 0x2f, 0xdc, 0xad, 0xf7, 0xde, 0x52, 0xab, 0x96, 0x3f, 0x54, 0x4a, 0xdd, 0xe8, 0xbd, 0x84, 0x31, 0xca, 0xe5, 0x8a, 0x12, 0xae, 0x28, 0xab, 0x13, 0xbd, 0xe7, 0x10, 0x9b, 0xfe, 0x40, 0xf4, 0x9e, 0x75, 0x4b, 0xf4, 0x5e, 0x42, 0xf4, 0xfe, 0x0b, 0x4a, 0x3a, 0xfa, 0x27, 0xa3, 0xf7, 0x5f, 0x89, 0xde, 0x7f, 0x15, 0x81, 0x37, 0x45, 0xef, 0xff, 0xd3, 0xc8, 0x3d, 0x90, 0xf9, 0x3d, 0xa4, 0x67, 0x3e, 0xe1, 0xf2, 0x80, 0xd0, 0x9e, 0x6c, 0x1a, 0x23, 0x2f, 0xd3, 0xf6, 0x91, 0xb4, 0x7d, 0xbf, 0x16, 0x53, 0x2b, 0x77, 0x52, 0x6a, 0xac, 0x1e, 0x57, 0x57, 0x51, 0xfa, 0x69, 0xda, 0x9b, 0x43, 0x7b, 0x2f, 0x19, 0xd1, 0x79, 0x00, 0xa5, 0x74, 0xaf, 0xcd, 0xa1, 0x94, 0xd6, 0xc2, 0x8b, 0xb6, 0x79, 0x43, 0x20, 0x39, 0x40, 0x1b, 0xe9, 0x16, 0x6d, 0xa1, 0x1d, 0x4a, 0x69, 0x0f, 0x9d, 0x64, 0x86, 0x7e, 0x47, 0xa7, 0x8b, 0xfc, 0x18, 0x05, 0x1d, 0x10, 0xdd, 0x78, 0xdd, 0x9d, 0x9a, 0x7b, 0x40, 0x4f, 0x08, 0x85, 0x85, 0x70, 0x27, 0x2c, 0x86, 0x48, 0x88, 0x82, 0x68, 0x88, 0xd1, 0x32, 0x15, 0x58, 0x0a, 0xb1, 0x10, 0x07, 0xcb, 0x60, 0x39, 0xc4, 0x43, 0x02, 0x24, 0x42, 0x0a, 0xa4, 0xc2, 0x4a, 0x7d, 0x6c, 0x5c, 0xc4, 0x9b, 0x6e, 0xe2, 0x4d, 0xb7, 0xd8, 0x2a, 0xab, 0x89, 0x31, 0xdd, 0x4a, 0x98, 0x3e, 0x33, 0x5f, 0x29, 0xe1, 0xf2, 0x53, 0x65, 0x10, 0x44, 0xc8, 0x6f, 0x94, 0xe1, 0x30, 0x86, 0x5c, 0x77, 0xba, 0x9e, 0xf3, 0xfd, 0xc2, 0x4c, 0x95, 0xa3, 0xd0, 0x4b, 0xc4, 0xa1, 0x6e, 0x94, 0x79, 0xc0, 0x34, 0x5f, 0x66, 0x98, 0x62, 0x20, 0x96, 0xfd, 0x78, 0xb6, 0xc9, 0xd2, 0x45, 0x2c, 0xea, 0x22, 0x06, 0x75, 0x11, 0x83, 0xba, 0x88, 0x41, 0x5d, 0xc4, 0x9e, 0x6e, 0x62, 0x4f, 0x17, 0x31, 0xa7, 0x9b, 0x38, 0xd3, 0x45, 0x9c, 0xe9, 0x22, 0xce, 0x74, 0x13, 0x2b, 0xba, 0x98, 0xe5, 0x62, 0x23, 0x67, 0xbc, 0x44, 0xdc, 0xe8, 0x42, 0xc5, 0xe5, 0xa8, 0xb8, 0x9c, 0xb8, 0xd1, 0x45, 0x4c, 0xe8, 0x26, 0x26, 0x74, 0x11, 0x13, 0xba, 0xcc, 0x5d, 0xe5, 0xcf, 0xe6, 0x6e, 0xd0, 0x1d, 0x7a, 0x40, 0x4f, 0x08, 0x85, 0x5e, 0xd0, 0x1b, 0xfa, 0x40, 0x7f, 0x18, 0x00, 0x03, 0x01, 0x1d, 0x98, 0x07, 0xc1, 0x60, 0x18, 0x02, 0x43, 0x61, 0x18, 0x44, 0xc0, 0x70, 0x18, 0x01, 0x23, 0x61, 0x14, 0x8c, 0x86, 0x31, 0x30, 0x16, 0xc6, 0x41, 0x24, 0x38, 0xe5, 0x1b, 0xe6, 0xf3, 0xf2, 0x0d, 0xcb, 0x18, 0xe9, 0xb6, 0x8c, 0x85, 0x71, 0x30, 0x1e, 0x26, 0xc2, 0x24, 0x98, 0x0c, 0x53, 0x60, 0x2a, 0x4c, 0x93, 0x07, 0xb1, 0xb6, 0x83, 0x58, 0xdb, 0x41, 0xac, 0xed, 0x20, 0xd6, 0x76, 0x10, 0x6b, 0x3b, 0x88, 0xb5, 0x1d, 0xc4, 0xda, 0x32, 0xb0, 0xb6, 0x8c, 0x3f, 0xbc, 0x23, 0xb3, 0x56, 0x7e, 0x6c, 0xb9, 0x1b, 0xd6, 0xc9, 0x03, 0x58, 0xdd, 0x01, 0xac, 0xee, 0x00, 0x56, 0x77, 0x00, 0xab, 0x3b, 0x80, 0xd5, 0x1d, 0xc0, 0xea, 0x0e, 0x60, 0x75, 0x07, 0xb0, 0xba, 0x03, 0x96, 0xc7, 0x28, 0xe7, 0x71, 0x78, 0x02, 0xfe, 0x06, 0x3b, 0x61, 0x17, 0xec, 0x96, 0x2e, 0xe2, 0x4d, 0x17, 0xf1, 0xa6, 0x8b, 0x78, 0xd3, 0x45, 0xbc, 0xe9, 0x22, 0xde, 0x74, 0x11, 0x6f, 0xba, 0x88, 0x37, 0x5d, 0xc4, 0x9b, 0x2e, 0xe2, 0x4d, 0x17, 0xf1, 0xa6, 0x8b, 0x78, 0xd3, 0x85, 0x5f, 0x76, 0x11, 0x6f, 0xba, 0x88, 0x37, 0x5d, 0xc4, 0x9b, 0x2e, 0xe2, 0x4d, 0x17, 0xf1, 0xa6, 0x8b, 0x78, 0xd3, 0x45, 0xbc, 0xe9, 0x22, 0xde, 0x74, 0x11, 0x6f, 0xba, 0x88, 0x37, 0x5d, 0xc4, 0x9b, 0x2e, 0xe2, 0x4d, 0x17, 0xf1, 0xa6, 0x8b, 0x78, 0xd3, 0x45, 0xbc, 0xe9, 0x22, 0xde, 0x74, 0x11, 0x6f, 0xba, 0x88, 0x37, 0x5d, 0xc4, 0x9b, 0x2e, 0xe2, 0x4d, 0x17, 0xf1, 0xa6, 0x8b, 0xf8, 0xcc, 0x45, 0x7c, 0xe6, 0x22, 0x3e, 0x73, 0x11, 0x9f, 0xb9, 0x88, 0xcf, 0x5c, 0xc4, 0x67, 0x2e, 0xe2, 0x33, 0x17, 0x31, 0x98, 0x8b, 0x18, 0xcc, 0x45, 0x0c, 0xe6, 0x22, 0x06, 0x73, 0x11, 0x83, 0xb9, 0x88, 0xc1, 0x5c, 0xc4, 0x60, 0x2e, 0x62, 0x30, 0x17, 0x31, 0x18, 0x1e, 0x1e, 0x5a, 0x42, 0x08, 0xb4, 0x02, 0x1b, 0xb4, 0x86, 0x36, 0xd0, 0x1e, 0x3a, 0x40, 0x27, 0xe8, 0x06, 0xd8, 0x06, 0x31, 0x98, 0x0b, 0x3f, 0xe3, 0x22, 0x06, 0x73, 0x11, 0x83, 0xb9, 0x88, 0xc1, 0x5c, 0xc4, 0x60, 0x2e, 0x62, 0x30, 0x17, 0x31, 0x98, 0x8b, 0x18, 0xcc, 0x45, 0x0c, 0xe6, 0x22, 0x06, 0x73, 0x11, 0x83, 0xb9, 0x88, 0xc1, 0x5c, 0xc4, 0x60, 0x2e, 0x62, 0x30, 0x17, 0x31, 0x98, 0x8b, 0x18, 0xcc, 0x45, 0x0c, 0xe6, 0x22, 0x06, 0x73, 0x11, 0x83, 0xb9, 0x88, 0xc1, 0x5c, 0xc4, 0x60, 0x2e, 0x62, 0x30, 0x17, 0x31, 0x98, 0x8b, 0x18, 0xcc, 0x45, 0x0c, 0xe6, 0x22, 0x06, 0x73, 0x11, 0x83, 0xb9, 0x88, 0xc1, 0x5c, 0xc4, 0x45, 0x2e, 0xe2, 0x22, 0x17, 0x71, 0x91, 0x8b, 0xb8, 0xc8, 0x45, 0x5c, 0xe4, 0x22, 0x2e, 0x72, 0x11, 0x17, 0xb9, 0x88, 0x8b, 0x5c, 0xc4, 0x45, 0x2e, 0xe2, 0x22, 0x17, 0x71, 0x91, 0x8b, 0xb8, 0xc2, 0x4d, 0x5c, 0xe1, 0x26, 0xae, 0x70, 0x13, 0x57, 0xb8, 0x89, 0x2b, 0xdc, 0xc4, 0x15, 0x6e, 0xe2, 0x0a, 0x37, 0x71, 0x85, 0x9b, 0xb8, 0xc2, 0x4d, 0x5c, 0xe1, 0x26, 0xae, 0x70, 0x13, 0x57, 0xb8, 0x89, 0x2b, 0xdc, 0xc4, 0x15, 0x6e, 0xe2, 0x0a, 0x37, 0x71, 0x85, 0x9b, 0xb8, 0xc2, 0x4d, 0x5c, 0xe1, 0x26, 0xae, 0x70, 0x13, 0x57, 0xb8, 0x89, 0x2b, 0xdc, 0xc4, 0x15, 0x6e, 0xf5, 0x8c, 0xac, 0x56, 0x7f, 0x85, 0x4b, 0xec, 0x57, 0x40, 0x25, 0x5c, 0x86, 0x2b, 0x70, 0x55, 0xba, 0x89, 0x05, 0x5c, 0x78, 0xe8, 0x52, 0xd6, 0x79, 0x37, 0xeb, 0xbc, 0x9b, 0x75, 0xde, 0xcd, 0x3a, 0xef, 0x66, 0x9d, 0x77, 0x8b, 0x90, 0xff, 0xe8, 0x93, 0x23, 0x64, 0x35, 0x16, 0x5f, 0x6d, 0xf8, 0xe6, 0x73, 0x58, 0x7c, 0x35, 0x16, 0xef, 0xfa, 0x03, 0xdf, 0x5c, 0x84, 0xd5, 0x9e, 0xff, 0xd3, 0xbe, 0xb9, 0x05, 0x35, 0x5f, 0xa5, 0xe6, 0xab, 0xd4, 0x7c, 0x95, 0x9a, 0x7f, 0xa1, 0xe6, 0x8f, 0xa9, 0xf9, 0x84, 0x16, 0x09, 0xe0, 0xa7, 0x4b, 0xa9, 0xe5, 0x02, 0xb5, 0x5c, 0x65, 0x95, 0xeb, 0x64, 0xf8, 0xec, 0xf3, 0xd4, 0x76, 0x86, 0x1a, 0xae, 0x6a, 0x7e, 0x9b, 0x5a, 0xae, 0x52, 0xcb, 0x55, 0x6a, 0xb9, 0x4a, 0x2d, 0x57, 0xa9, 0xe5, 0x2a, 0xb5, 0x5c, 0xa5, 0x96, 0xab, 0x68, 0xe6, 0x1a, 0xde, 0x56, 0xd1, 0xa2, 0x01, 0x61, 0xa5, 0x06, 0x37, 0xa5, 0xbb, 0x28, 0xbd, 0x8a, 0x3e, 0x94, 0x1b, 0x77, 0x53, 0x0e, 0xe9, 0xf7, 0xbb, 0x9e, 0xd0, 0xef, 0x39, 0xe5, 0xe1, 0xed, 0x7d, 0xc5, 0x42, 0x23, 0x66, 0x98, 0x2f, 0x46, 0xca, 0x8d, 0x62, 0x94, 0x7c, 0x40, 0x4c, 0x60, 0x7b, 0x3b, 0xcc, 0x83, 0x48, 0x62, 0x9d, 0x18, 0x39, 0x4a, 0x2b, 0x05, 0x7f, 0xb9, 0x87, 0x11, 0x4a, 0x53, 0x06, 0xc8, 0xa7, 0x94, 0x81, 0x72, 0x17, 0x23, 0xb4, 0x09, 0xef, 0x7f, 0x58, 0x59, 0x2c, 0x5f, 0x61, 0xe5, 0x3e, 0xc7, 0xca, 0x7d, 0x8e, 0x28, 0xc6, 0xac, 0x94, 0xca, 0xbf, 0x29, 0x65, 0x72, 0x87, 0x52, 0x2e, 0x1f, 0xa7, 0xd6, 0x71, 0xd4, 0xda, 0x91, 0x5a, 0x1b, 0xb3, 0x42, 0x17, 0x52, 0xeb, 0x4b, 0xe4, 0xc0, 0x1b, 0xc9, 0x81, 0x37, 0x62, 0xb3, 0xa3, 0xb0, 0xd7, 0xa9, 0xd8, 0xeb, 0x50, 0xec, 0x75, 0x1a, 0xf6, 0xda, 0x19, 0x7b, 0x1d, 0x88, 0x7d, 0x4e, 0xc1, 0x3e, 0x0b, 0xb0, 0xcf, 0x29, 0xd8, 0xe7, 0x14, 0xec, 0x73, 0x2a, 0xf6, 0x19, 0x87, 0x7d, 0xc6, 0x61, 0x9f, 0x71, 0xd8, 0x67, 0x1c, 0xf6, 0x39, 0x10, 0xfb, 0x9c, 0x4f, 0x6e, 0xd7, 0x83, 0xdc, 0x2e, 0x94, 0xfc, 0xad, 0x25, 0xb9, 0x5b, 0x27, 0x72, 0xb7, 0xae, 0xe4, 0x6e, 0x9d, 0xc9, 0xd3, 0x9a, 0x92, 0xa7, 0x75, 0x64, 0x8d, 0x7b, 0x85, 0x3c, 0xad, 0x23, 0x79, 0x5a, 0x47, 0xf2, 0xb4, 0x4e, 0xe4, 0x60, 0x1b, 0xc9, 0xc1, 0x36, 0x92, 0x83, 0x6d, 0x24, 0x9f, 0xda, 0x88, 0xbd, 0x46, 0x60, 0xaf, 0xed, 0xb1, 0xd3, 0x11, 0xe4, 0x4e, 0x9d, 0xc8, 0x99, 0xba, 0xb0, 0x26, 0xf6, 0xc1, 0x06, 0x3f, 0x60, 0x05, 0x1b, 0xca, 0x0a, 0xd6, 0x8d, 0x51, 0xea, 0x40, 0xc4, 0xd5, 0x8c, 0x75, 0xb1, 0xbb, 0x1e, 0x71, 0xf5, 0x12, 0x23, 0xc9, 0x43, 0x9a, 0x90, 0x83, 0xd8, 0xc8, 0x3f, 0x5a, 0x93, 0x6b, 0xb4, 0x22, 0x0a, 0xdb, 0x4b, 0xae, 0xd1, 0x8a, 0x5c, 0xa3, 0x15, 0xb9, 0x86, 0x8d, 0x5c, 0xa3, 0x2d, 0xb6, 0xf1, 0x02, 0xb6, 0x30, 0x97, 0xbc, 0x20, 0x9e, 0xbc, 0xe0, 0x7e, 0x74, 0xba, 0x1c, 0x4d, 0xfe, 0x40, 0x2c, 0xda, 0x44, 0x7b, 0x6a, 0x93, 0xfc, 0x86, 0xd5, 0x70, 0x03, 0xab, 0xe1, 0x06, 0x61, 0x96, 0x79, 0xc2, 0x02, 0xfe, 0x70, 0x1b, 0x04, 0x43, 0x4b, 0xb0, 0x41, 0x77, 0x39, 0x8e, 0x15, 0x70, 0x1c, 0x2b, 0xe0, 0x38, 0xd1, 0x4b, 0x4e, 0x16, 0xbd, 0xa1, 0x0f, 0xf4, 0x85, 0x30, 0xe8, 0x07, 0xfd, 0xe5, 0x14, 0x31, 0x00, 0x06, 0x82, 0x36, 0x93, 0x83, 0x38, 0x36, 0x18, 0x86, 0xc8, 0xe7, 0xc5, 0x50, 0x18, 0x06, 0x11, 0x30, 0x1c, 0x46, 0x80, 0x67, 0xa6, 0x9f, 0x16, 0xa3, 0xe5, 0x7e, 0xc1, 0x3a, 0x2e, 0xc6, 0xc2, 0x38, 0x18, 0x0f, 0xda, 0xec, 0x4f, 0x64, 0x3b, 0x89, 0xf3, 0x26, 0xc3, 0x14, 0x98, 0x0a, 0xd3, 0x60, 0x3a, 0xcc, 0x80, 0x99, 0x30, 0x0b, 0x34, 0x95, 0xcc, 0x66, 0x3b, 0x07, 0xe6, 0xc2, 0x3c, 0xca, 0xbc, 0x83, 0xed, 0x7c, 0x58, 0x00, 0x0b, 0xe5, 0x4c, 0xb1, 0x48, 0x4e, 0xbb, 0x49, 0x45, 0x71, 0x32, 0x8a, 0x15, 0x39, 0x8a, 0x15, 0x39, 0x8a, 0x15, 0x39, 0x4a, 0x24, 0xd1, 0xce, 0x64, 0x58, 0x01, 0x29, 0xbc, 0x4e, 0x93, 0xab, 0x58, 0x9d, 0xa3, 0xc4, 0x2a, 0xb9, 0x50, 0xac, 0x86, 0xbb, 0x60, 0x0d, 0xa0, 0x3e, 0x71, 0x37, 0x65, 0xae, 0x93, 0x4f, 0x89, 0xf5, 0x70, 0x0f, 0x6c, 0x80, 0x7b, 0x61, 0x23, 0x6c, 0x82, 0xcd, 0x70, 0x1f, 0xdc, 0x0f, 0x5b, 0x64, 0x3c, 0x2b, 0x7b, 0xbc, 0x48, 0x97, 0xeb, 0xc4, 0x83, 0xb4, 0x73, 0xab, 0x4c, 0x12, 0x0f, 0xc9, 0xb5, 0xac, 0xf2, 0x73, 0x0d, 0x15, 0x7f, 0x8c, 0x8a, 0x3f, 0x54, 0x46, 0xc8, 0xcd, 0xca, 0x48, 0xd4, 0x3a, 0x8a, 0xed, 0x68, 0xb6, 0x63, 0xe4, 0x76, 0x65, 0x82, 0xdc, 0xab, 0x4c, 0x82, 0x1b, 0xea, 0xbe, 0x84, 0xba, 0x2f, 0xe9, 0xea, 0xce, 0x27, 0x86, 0x2d, 0x94, 0x93, 0x59, 0xfd, 0xe3, 0x6f, 0x52, 0xfa, 0x28, 0xec, 0x6b, 0x22, 0x4c, 0x87, 0x3b, 0x21, 0x55, 0x4e, 0x36, 0xa5, 0xc9, 0x29, 0x44, 0x01, 0x51, 0x44, 0x00, 0x51, 0x26, 0xda, 0x6b, 0xa2, 0xbd, 0x44, 0x02, 0x73, 0x89, 0x04, 0x66, 0xea, 0x96, 0x91, 0xce, 0xfb, 0x5b, 0x79, 0xfd, 0xa8, 0x5c, 0x68, 0x7a, 0x82, 0xfd, 0x1d, 0x72, 0x2d, 0x51, 0xc1, 0x5c, 0xd3, 0x5e, 0xb9, 0xdf, 0xf4, 0x3c, 0xbc, 0x20, 0x5f, 0x32, 0xbd, 0xc8, 0x76, 0x1f, 0xbc, 0x24, 0x9f, 0x37, 0xed, 0x87, 0x97, 0xe1, 0x15, 0x38, 0x00, 0xaf, 0x52, 0xde, 0xdf, 0xa1, 0xd6, 0xc2, 0x18, 0x6b, 0xef, 0xcd, 0xf2, 0x0d, 0xef, 0xfb, 0xe5, 0x2b, 0xde, 0x5b, 0xe4, 0x47, 0xde, 0x0f, 0xc8, 0x83, 0xde, 0xe9, 0x6c, 0x1f, 0x64, 0xbb, 0x4b, 0xbf, 0x23, 0xb5, 0xdf, 0xe7, 0x33, 0xf9, 0xbc, 0x6e, 0x85, 0x47, 0xd8, 0x3f, 0x2a, 0x9f, 0xc6, 0x12, 0x9f, 0xf6, 0xf9, 0x91, 0x63, 0xc7, 0xe0, 0x94, 0xcc, 0xf3, 0x39, 0x23, 0x27, 0xfb, 0x14, 0xc8, 0x29, 0x44, 0x1e, 0xf1, 0x44, 0x1e, 0x51, 0xbe, 0x1d, 0xe4, 0x53, 0xbe, 0x55, 0x72, 0x32, 0xd1, 0xc7, 0x06, 0xa2, 0x8f, 0x0d, 0x44, 0x1f, 0x1b, 0x88, 0x3e, 0x36, 0x10, 0x7d, 0x6c, 0x20, 0xfa, 0xd8, 0x40, 0xf4, 0xb1, 0x81, 0xe8, 0x63, 0x03, 0xd1, 0xc7, 0x06, 0xa2, 0x8f, 0x0d, 0x44, 0x1f, 0x1b, 0x88, 0x3e, 0x36, 0x10, 0x7d, 0x6c, 0x20, 0xfa, 0xd8, 0x40, 0xf4, 0xb1, 0x81, 0xe8, 0x63, 0x03, 0xd1, 0xc7, 0x06, 0xa2, 0x8f, 0x0d, 0x44, 0x1f, 0x1b, 0x88, 0x3e, 0x36, 0x10, 0x7d, 0x6c, 0x20, 0xfa, 0xd8, 0x40, 0xf4, 0xb1, 0x81, 0xe8, 0x63, 0x03, 0xd1, 0xc7, 0x06, 0xa2, 0x8f, 0x0d, 0x44, 0x1f, 0x1b, 0x88, 0x3e, 0x36, 0x98, 0x97, 0xcb, 0x3c, 0x73, 0x3c, 0x24, 0x40, 0x22, 0xa4, 0xc0, 0x3e, 0x78, 0x09, 0xf6, 0xc3, 0xcb, 0xf0, 0x0a, 0x1c, 0x80, 0x57, 0x65, 0x9e, 0xa5, 0x3e, 0x60, 0x3f, 0x96, 0x06, 0xd0, 0x10, 0x02, 0xa0, 0x11, 0x04, 0x42, 0x10, 0x34, 0x83, 0xe6, 0xd0, 0x02, 0xb0, 0x31, 0x0b, 0x36, 0x66, 0xc1, 0xc6, 0x2c, 0x21, 0xd0, 0x0a, 0xb0, 0x35, 0x4b, 0x6b, 0x68, 0x03, 0x6d, 0xa1, 0x1d, 0xb4, 0x87, 0x0e, 0xd0, 0x11, 0x3a, 0x41, 0x67, 0xe8, 0x02, 0x7d, 0x41, 0xf3, 0x5e, 0x4f, 0xca, 0x71, 0xbf, 0xeb, 0xc1, 0xf6, 0x70, 0x7c, 0x2f, 0xbc, 0x00, 0x2f, 0xc2, 0x5f, 0xf1, 0x68, 0xff, 0xe0, 0xfc, 0x37, 0xe1, 0x2d, 0x78, 0x1b, 0xde, 0x87, 0x0f, 0xe0, 0x43, 0xf8, 0x08, 0x3e, 0x86, 0xcf, 0xe4, 0xe4, 0xeb, 0x9e, 0xef, 0x0b, 0xf6, 0xbf, 0x81, 0x6f, 0x29, 0xf7, 0x10, 0x1c, 0x86, 0xef, 0xe0, 0x28, 0x68, 0x5e, 0xf1, 0x47, 0xde, 0xab, 0xeb, 0x19, 0x33, 0x78, 0x7d, 0x1c, 0x32, 0x21, 0x07, 0x4e, 0xc0, 0x1f, 0x79, 0xcb, 0x5f, 0x79, 0xef, 0x1c, 0x38, 0x20, 0x1f, 0x0a, 0x00, 0xed, 0x5b, 0x9c, 0x50, 0x0c, 0xe7, 0xe5, 0xf3, 0x37, 0x79, 0xd4, 0x32, 0xb9, 0xdf, 0x52, 0x0e, 0x97, 0xa0, 0x02, 0x2a, 0xe1, 0x32, 0xe7, 0x5c, 0x81, 0xab, 0x50, 0x05, 0xd5, 0xe0, 0x02, 0x37, 0x5c, 0x83, 0x1a, 0xf9, 0xf4, 0x75, 0x2f, 0xac, 0xc8, 0x99, 0x44, 0x4d, 0x33, 0x75, 0x6f, 0xec, 0xcd, 0xd6, 0x07, 0x7c, 0xe5, 0x34, 0xdd, 0x33, 0x5b, 0xd8, 0xaa, 0xa0, 0x79, 0x68, 0x7f, 0xb6, 0x0d, 0xe0, 0xcf, 0x78, 0xea, 0x51, 0x32, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x4a, 0xf7, 0xee, 0xb7, 0xcb, 0xc9, 0xba, 0x87, 0x9f, 0xc3, 0x56, 0xf3, 0xf2, 0xf3, 0xd8, 0xde, 0x01, 0xf3, 0xe1, 0x4e, 0x58, 0x0c, 0x7f, 0xe4, 0xf9, 0x97, 0xf2, 0x5e, 0x2c, 0xc4, 0xc1, 0x32, 0x58, 0x0e, 0xf1, 0x90, 0x00, 0x89, 0x70, 0x17, 0xac, 0x81, 0xb5, 0x80, 0x6f, 0x20, 0x9a, 0x8a, 0x22, 0x9a, 0x8a, 0xd2, 0x57, 0x8c, 0xcd, 0x6c, 0xef, 0x83, 0xfb, 0x61, 0x0b, 0x68, 0x2b, 0xc8, 0x83, 0x6c, 0xb7, 0xc2, 0x43, 0x72, 0xe1, 0xf5, 0xd5, 0x64, 0x3b, 0xfb, 0xf8, 0x0d, 0xf5, 0x31, 0x78, 0x1c, 0x9e, 0x80, 0x1d, 0xb0, 0x4b, 0x3e, 0xa5, 0xee, 0x86, 0x27, 0xe1, 0x29, 0x78, 0x1a, 0x9e, 0x81, 0x67, 0xe1, 0x39, 0xd8, 0x03, 0x7b, 0xe1, 0x79, 0x78, 0x01, 0x5e, 0x84, 0x7d, 0xf0, 0x12, 0xec, 0x87, 0x97, 0xe1, 0x15, 0x38, 0x00, 0xf8, 0x17, 0x15, 0xff, 0xa2, 0xbe, 0x06, 0xaf, 0xc3, 0x1b, 0xf0, 0x0f, 0x78, 0x13, 0xde, 0x82, 0xb7, 0xe1, 0x1d, 0x78, 0x17, 0xde, 0x83, 0xf7, 0xe1, 0x03, 0xf8, 0x10, 0x3e, 0x82, 0x8f, 0xe1, 0x9f, 0xf0, 0x09, 0x7c, 0x0a, 0xff, 0xa2, 0xed, 0xff, 0x86, 0xcf, 0xe0, 0x73, 0x38, 0x08, 0x5f, 0xc0, 0x97, 0xf0, 0x15, 0x7c, 0x03, 0xdf, 0xc2, 0x21, 0x38, 0x0c, 0xdf, 0xc1, 0xf7, 0x70, 0x04, 0x8e, 0xc2, 0x0f, 0xf0, 0x23, 0x1c, 0x83, 0x9f, 0x20, 0x03, 0x8e, 0x43, 0xa6, 0x5c, 0xa7, 0x66, 0x41, 0xb6, 0xdc, 0xa8, 0xe6, 0xc0, 0x09, 0x38, 0x09, 0xa7, 0x20, 0x17, 0xf2, 0xe0, 0x8c, 0x4c, 0x22, 0x62, 0x4c, 0x52, 0xcf, 0xc9, 0xb5, 0xaa, 0x03, 0xf2, 0xa1, 0x00, 0x9c, 0x50, 0x0c, 0xe7, 0xe1, 0x02, 0x5c, 0x84, 0x12, 0xb8, 0xc4, 0xb8, 0xb3, 0x62, 0x13, 0x59, 0xce, 0x25, 0xb2, 0x9c, 0x4b, 0x64, 0x39, 0x97, 0xc8, 0x72, 0x2e, 0x91, 0x65, 0x94, 0xf5, 0x94, 0xdc, 0xcb, 0x4a, 0x9e, 0x63, 0x75, 0xca, 0x3c, 0x6b, 0x31, 0x9c, 0x87, 0x0b, 0x70, 0x11, 0x2a, 0xe4, 0x64, 0x6d, 0x85, 0xb7, 0x5e, 0x66, 0x7b, 0x05, 0xae, 0x42, 0x95, 0x8c, 0x27, 0x0a, 0x8d, 0x27, 0x0a, 0x8d, 0x27, 0x0a, 0x8d, 0x27, 0x0a, 0x8d, 0xf7, 0xc3, 0x46, 0x45, 0x80, 0x11, 0xa5, 0x69, 0xf9, 0xfa, 0x19, 0x56, 0x47, 0x27, 0xab, 0xdc, 0x19, 0x56, 0xa7, 0x0c, 0x56, 0xa1, 0x7c, 0xe3, 0x7e, 0xd2, 0x49, 0x3c, 0x7b, 0x96, 0x11, 0xb1, 0x69, 0xf7, 0x93, 0xce, 0xa0, 0xf8, 0x33, 0x37, 0xdd, 0x4f, 0xd2, 0xee, 0xc1, 0x7c, 0x48, 0x49, 0xd5, 0x94, 0x74, 0x37, 0x6b, 0x6d, 0x00, 0xa5, 0x3d, 0x41, 0x49, 0x1f, 0x50, 0xd2, 0x72, 0xd6, 0xb9, 0x33, 0xac, 0x73, 0xa7, 0x29, 0xf1, 0x1e, 0x65, 0xbc, 0x50, 0x95, 0xe9, 0xa2, 0x81, 0x32, 0x53, 0x04, 0xea, 0x7f, 0x27, 0xbd, 0x93, 0x4c, 0x77, 0xb1, 0xfc, 0x8e, 0xf5, 0xcd, 0xc9, 0xfa, 0xe6, 0x64, 0x7d, 0x6b, 0xc3, 0x9a, 0xf6, 0x6f, 0xd6, 0xb4, 0x4f, 0x59, 0xd3, 0xbe, 0x23, 0x22, 0x2d, 0xa0, 0x15, 0x3f, 0xd1, 0x8a, 0x74, 0x5a, 0x91, 0xa8, 0xdd, 0xd5, 0x62, 0x1d, 0x71, 0xea, 0x2d, 0xd9, 0x2d, 0x1f, 0xc3, 0xe7, 0x6d, 0xc0, 0xdf, 0xdd, 0x6b, 0x79, 0x49, 0xdc, 0x86, 0x3f, 0xdb, 0x87, 0x3f, 0xdb, 0x87, 0x3f, 0xdb, 0x87, 0x3f, 0xdb, 0x87, 0x3f, 0xbb, 0x17, 0x7f, 0xf6, 0x1c, 0x3e, 0x6a, 0x13, 0x7e, 0x28, 0x19, 0xff, 0xb3, 0x1e, 0xff, 0x52, 0x8d, 0x9d, 0xdf, 0x85, 0x7d, 0xef, 0xc4, 0x8e, 0xd7, 0x63, 0xbf, 0xb3, 0xe8, 0xcd, 0x31, 0xe3, 0xee, 0x83, 0x0d, 0xfb, 0xed, 0x85, 0xfd, 0xf6, 0xc2, 0x7e, 0x47, 0xaa, 0xed, 0x45, 0x03, 0x6c, 0x38, 0x8e, 0x1e, 0xb6, 0xd7, 0xef, 0x71, 0x2d, 0x91, 0x69, 0xd8, 0x47, 0x2e, 0xf6, 0xf0, 0x1a, 0x36, 0xb0, 0x11, 0x1b, 0x38, 0xc8, 0xfc, 0x7c, 0x29, 0x06, 0xd0, 0xfb, 0x22, 0xe2, 0xea, 0x72, 0xe2, 0xea, 0x72, 0x46, 0xa1, 0x9c, 0x51, 0x48, 0x67, 0x14, 0xfc, 0x18, 0x85, 0xd7, 0x8c, 0xb8, 0xf5, 0xdf, 0x8c, 0xc4, 0x0e, 0x46, 0xe2, 0x02, 0x23, 0xa1, 0xdd, 0xab, 0xd3, 0x56, 0xf6, 0xd3, 0xf4, 0x5c, 0xeb, 0x71, 0x43, 0xa5, 0x44, 0x5e, 0xa5, 0xd7, 0x99, 0xf4, 0xfa, 0x38, 0xbd, 0x3e, 0xa3, 0xdf, 0x2f, 0xb1, 0x33, 0xf6, 0x4b, 0xf5, 0xa8, 0xd9, 0x41, 0xef, 0x5f, 0xa5, 0xf7, 0x3b, 0x88, 0xc1, 0xb5, 0x39, 0xb8, 0x44, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x6e, 0x8c, 0xc4, 0x7e, 0x46, 0x62, 0x3f, 0x23, 0xb1, 0x9f, 0x91, 0xd8, 0xcf, 0x28, 0xec, 0xa1, 0xe7, 0x57, 0xe9, 0xf5, 0xdf, 0xe8, 0xf1, 0x08, 0x7a, 0x9c, 0x41, 0x8f, 0x83, 0xe9, 0x71, 0x10, 0x3d, 0x6e, 0x47, 0x8f, 0x5b, 0xd2, 0xe3, 0xde, 0xf4, 0x76, 0x2a, 0xbd, 0xb5, 0xd1, 0xdb, 0x32, 0x7a, 0x7a, 0x52, 0xef, 0xe9, 0x36, 0xf9, 0x19, 0x59, 0x43, 0xb8, 0x7c, 0x8f, 0xde, 0x14, 0xd3, 0x9b, 0x72, 0x62, 0xa5, 0x83, 0xf4, 0xe8, 0x02, 0x73, 0x96, 0xcb, 0x9c, 0xe5, 0xd2, 0x83, 0x46, 0xb4, 0x5a, 0xbb, 0x4f, 0x76, 0x41, 0x6f, 0xf5, 0x16, 0x11, 0x42, 0xab, 0x9f, 0xa6, 0xd5, 0x95, 0x9a, 0x72, 0x68, 0xf1, 0x21, 0x5a, 0x15, 0x4e, 0xcd, 0xa3, 0xa8, 0xf9, 0x02, 0x35, 0x06, 0x51, 0x63, 0x20, 0x35, 0xf6, 0xa5, 0xc6, 0xe9, 0xd4, 0x76, 0x15, 0x3f, 0x74, 0x50, 0xcb, 0x96, 0xc4, 0x34, 0xc6, 0xf2, 0x4d, 0xc6, 0xb0, 0x8a, 0x1a, 0x17, 0x13, 0xcd, 0xfd, 0x8d, 0x5a, 0x6f, 0x17, 0xcb, 0x64, 0x05, 0x35, 0x3e, 0xcb, 0x18, 0x2e, 0x62, 0x0c, 0xdf, 0x63, 0x0c, 0xdf, 0x61, 0x0c, 0x97, 0xa3, 0x26, 0xeb, 0x2d, 0x6a, 0xfa, 0x07, 0x63, 0xfa, 0x02, 0x2d, 0xfb, 0x82, 0x96, 0x7d, 0x61, 0xa8, 0x69, 0x1b, 0xe3, 0xfa, 0x30, 0x2d, 0x7c, 0x8c, 0x16, 0x9e, 0xa2, 0x65, 0x7f, 0xa3, 0x65, 0xe3, 0x69, 0xd9, 0x70, 0x4d, 0xd3, 0xda, 0x3d, 0x0f, 0x14, 0xf5, 0x15, 0xad, 0x3c, 0x4d, 0x34, 0xb2, 0x8b, 0x68, 0x64, 0x17, 0xca, 0x4a, 0x65, 0x45, 0xed, 0x8d, 0xba, 0xe6, 0xb2, 0xa2, 0x8e, 0x42, 0x61, 0x51, 0xac, 0xa2, 0x3d, 0xe8, 0x85, 0x0f, 0x63, 0xbb, 0x85, 0xb1, 0xdd, 0xc2, 0xd8, 0x6e, 0x61, 0x6c, 0xb7, 0xa0, 0xb2, 0x28, 0xc6, 0xf7, 0x5e, 0x54, 0x66, 0x47, 0x65, 0x33, 0x50, 0xd9, 0x62, 0x56, 0xb9, 0xfe, 0xac, 0x70, 0x23, 0x18, 0xf3, 0x53, 0xac, 0x2e, 0xbb, 0x50, 0x5c, 0x34, 0x63, 0x7f, 0x2f, 0xab, 0xc7, 0x08, 0x54, 0xb7, 0xd8, 0x50, 0xdd, 0x07, 0x8c, 0x7f, 0x20, 0xa3, 0xd1, 0x91, 0xd1, 0x68, 0xcf, 0x68, 0x4c, 0x34, 0x14, 0xb7, 0x9c, 0x15, 0x22, 0x81, 0x15, 0x60, 0x22, 0x5e, 0x3f, 0x14, 0x8f, 0x1f, 0xce, 0x08, 0x1d, 0x47, 0x7d, 0x73, 0x99, 0x93, 0x23, 0xcc, 0xc9, 0x4e, 0xd4, 0x37, 0x9c, 0x79, 0x79, 0x01, 0x6f, 0xb6, 0x04, 0x4f, 0x95, 0x8a, 0x0a, 0x5f, 0x67, 0xf4, 0xbe, 0xc6, 0xea, 0x27, 0x8a, 0xe6, 0x86, 0x45, 0x1f, 0x47, 0x81, 0xbe, 0x8c, 0x9e, 0x83, 0x39, 0x73, 0x6b, 0x77, 0xe1, 0x18, 0x95, 0x52, 0x46, 0x45, 0xcb, 0xf0, 0xae, 0x32, 0x0a, 0xdf, 0x33, 0x0a, 0x6e, 0x46, 0xa1, 0x80, 0x9e, 0x7f, 0x4f, 0xcf, 0x5a, 0xd3, 0xda, 0x0b, 0xb4, 0xac, 0x23, 0x2d, 0x2b, 0x47, 0x1d, 0x7e, 0x86, 0x3a, 0x5a, 0xd1, 0xba, 0x7a, 0xb4, 0xae, 0x29, 0x2d, 0xeb, 0x8b, 0x3a, 0x1a, 0xd2, 0x9a, 0x73, 0x22, 0x48, 0x6c, 0x93, 0x65, 0x62, 0x3b, 0xfa, 0x0d, 0x93, 0xc7, 0x94, 0x7e, 0xf2, 0x07, 0x65, 0xbc, 0x7c, 0x8d, 0x6c, 0xf5, 0x14, 0x99, 0xde, 0x57, 0x64, 0xab, 0x47, 0x94, 0xd9, 0xe8, 0x39, 0x46, 0xfe, 0xa8, 0x68, 0x9f, 0x13, 0x28, 0x45, 0xf3, 0x65, 0x50, 0x2e, 0x2f, 0x52, 0x73, 0xb5, 0x4f, 0x5f, 0x79, 0xd4, 0x27, 0x0c, 0xbe, 0x94, 0xd5, 0xd6, 0x99, 0xb2, 0xc4, 0x6f, 0x94, 0x7c, 0x52, 0x34, 0x16, 0x4b, 0xc4, 0xc8, 0xff, 0x50, 0xaa, 0x83, 0x52, 0x1d, 0x7f, 0xb9, 0x54, 0xed, 0x53, 0x0f, 0x45, 0x94, 0x56, 0x4c, 0x69, 0x4e, 0x4a, 0xfb, 0x82, 0xd2, 0x72, 0x29, 0xed, 0x6b, 0xa3, 0x8d, 0x15, 0x94, 0x76, 0x8a, 0x92, 0xae, 0x52, 0xd2, 0x55, 0x4a, 0x38, 0x43, 0x09, 0x67, 0xb8, 0xf2, 0x2d, 0xe1, 0xc7, 0xd1, 0x72, 0x8e, 0x96, 0x6b, 0xe5, 0xfb, 0x7c, 0x21, 0x9a, 0x31, 0x46, 0x41, 0x8c, 0x4b, 0x63, 0xc6, 0x21, 0x50, 0xed, 0x24, 0x02, 0xd4, 0xee, 0x62, 0x19, 0xe3, 0xd1, 0x42, 0xdd, 0x2a, 0x02, 0xc5, 0xc7, 0xc2, 0x84, 0x95, 0x84, 0x8a, 0xbb, 0xc5, 0x10, 0x32, 0x8d, 0xa1, 0x30, 0x0c, 0x22, 0x60, 0x38, 0x8c, 0x80, 0x91, 0x30, 0x0a, 0x46, 0x8b, 0x66, 0x62, 0x0c, 0x8c, 0x85, 0x71, 0x30, 0x1e, 0x26, 0x70, 0x7c, 0x22, 0xdb, 0x49, 0x6c, 0x27, 0xc3, 0x14, 0x98, 0x0a, 0xd3, 0x00, 0x7d, 0x8b, 0x19, 0x30, 0x13, 0x66, 0xc1, 0xed, 0x30, 0x1b, 0xe6, 0xc0, 0x5c, 0x98, 0x07, 0x77, 0xc0, 0x7c, 0x58, 0x00, 0x77, 0xd2, 0x86, 0xc5, 0x42, 0xf3, 0xc2, 0x77, 0x0b, 0x72, 0x18, 0x11, 0x0d, 0x31, 0xb0, 0x04, 0x96, 0x42, 0x2c, 0xc7, 0x97, 0xc1, 0x72, 0x48, 0x85, 0xb5, 0x70, 0x37, 0xd7, 0xa5, 0x8b, 0x8e, 0xe2, 0x41, 0xd1, 0x46, 0x6c, 0x85, 0x87, 0x44, 0x67, 0xb1, 0x4d, 0xac, 0x16, 0xdb, 0xc5, 0x52, 0xa5, 0xaf, 0x88, 0x51, 0xc2, 0xc4, 0x7a, 0xa5, 0x9f, 0x78, 0x5c, 0x19, 0x2c, 0x92, 0x94, 0x21, 0x22, 0x5d, 0x19, 0xca, 0x76, 0x18, 0xdb, 0xb1, 0x22, 0x0a, 0x1b, 0x64, 0x86, 0xc4, 0x3e, 0x6c, 0xf0, 0x45, 0x6c, 0x70, 0xaf, 0x32, 0x5b, 0xdc, 0xab, 0x2c, 0x14, 0x0f, 0x29, 0x8b, 0x20, 0x46, 0x7c, 0xa5, 0xd8, 0xc5, 0xa2, 0xba, 0x23, 0x69, 0x4a, 0x16, 0x77, 0x9b, 0x56, 0xc0, 0x1a, 0xd8, 0x21, 0x3a, 0x9b, 0x9e, 0x64, 0xfb, 0x14, 0xec, 0x15, 0xcd, 0x4c, 0xcf, 0xc3, 0x0b, 0xa2, 0x81, 0xe9, 0x45, 0xb6, 0xfb, 0xe0, 0x25, 0xf6, 0xf7, 0xc3, 0xcb, 0xf0, 0x0a, 0x1c, 0x10, 0x0d, 0xbc, 0xd3, 0xc4, 0xdd, 0x3e, 0x41, 0xd0, 0x57, 0x3c, 0xe4, 0x13, 0x06, 0xff, 0x16, 0xcd, 0x7c, 0x3e, 0x13, 0x0d, 0xb4, 0x19, 0xf2, 0x39, 0x02, 0x47, 0xd9, 0xff, 0x01, 0x7e, 0x84, 0x63, 0x50, 0x21, 0x62, 0x7c, 0xdb, 0x89, 0xbb, 0xb5, 0xd9, 0xb3, 0x7c, 0xc2, 0xf6, 0x53, 0xf8, 0x17, 0x9c, 0x17, 0x0d, 0x2c, 0x17, 0xa1, 0x04, 0x4a, 0xa1, 0x8c, 0x19, 0x2e, 0x87, 0x4b, 0x50, 0x01, 0x95, 0x70, 0x99, 0xe3, 0x57, 0xe0, 0x2a, 0x54, 0x41, 0x35, 0xb8, 0xc0, 0x0d, 0xd7, 0xa0, 0x06, 0x7b, 0x6d, 0x28, 0xee, 0x56, 0x03, 0xa0, 0x11, 0x04, 0x02, 0x6d, 0x53, 0x1b, 0x43, 0x53, 0xb1, 0x4a, 0x6d, 0x0e, 0x2d, 0x20, 0x18, 0x5a, 0x82, 0xa6, 0x9e, 0x56, 0x6c, 0x6d, 0xd0, 0x1a, 0xda, 0x40, 0x7b, 0xa8, 0x55, 0x54, 0x37, 0xf6, 0x35, 0x55, 0xf5, 0x64, 0xab, 0x29, 0xab, 0x0f, 0xe5, 0xf4, 0x85, 0x30, 0xe8, 0x07, 0xfd, 0x21, 0x1c, 0x06, 0xc1, 0x60, 0x18, 0x02, 0x43, 0x61, 0x18, 0x44, 0xc0, 0x70, 0x18, 0x01, 0x28, 0x52, 0xcd, 0xc4, 0x92, 0xb3, 0x20, 0x5b, 0xb4, 0x51, 0x73, 0xe0, 0x04, 0x9c, 0x84, 0x53, 0x90, 0x0b, 0x79, 0x70, 0x06, 0x7e, 0x85, 0x73, 0xa2, 0xb3, 0xea, 0x80, 0x7c, 0x28, 0x00, 0x27, 0x14, 0xc3, 0x79, 0xb8, 0x00, 0x17, 0xa1, 0x04, 0x2a, 0xd0, 0x4f, 0x20, 0xaa, 0xa8, 0x8f, 0x22, 0x54, 0xd4, 0x60, 0x46, 0x09, 0x66, 0x66, 0xbc, 0x19, 0x33, 0x1d, 0x7c, 0x93, 0xbd, 0xf4, 0x15, 0xf5, 0x98, 0x9d, 0x7a, 0xff, 0xd5, 0x6e, 0x62, 0xc5, 0x10, 0x7c, 0xfd, 0x50, 0x18, 0x06, 0x11, 0x30, 0x1c, 0x46, 0xc0, 0x48, 0x18, 0x05, 0xa3, 0x61, 0x0c, 0x8c, 0x05, 0xd6, 0x45, 0x31, 0x1e, 0x26, 0xc0, 0x44, 0x98, 0x04, 0x93, 0x61, 0x0a, 0x4c, 0x85, 0x69, 0x30, 0x1d, 0x66, 0xc0, 0x4c, 0x98, 0x05, 0xb7, 0xc3, 0x6c, 0x98, 0x03, 0x73, 0x61, 0x1e, 0xdc, 0x01, 0xf3, 0x61, 0x01, 0xdc, 0x0d, 0xe9, 0xf2, 0x32, 0xd9, 0xf9, 0x59, 0xb2, 0xf3, 0x4a, 0xb2, 0xf3, 0x0a, 0xfc, 0xc5, 0x25, 0xfd, 0x2f, 0x45, 0xda, 0x1d, 0xb8, 0xf1, 0x78, 0x9c, 0xe9, 0x30, 0x53, 0x1e, 0xc5, 0x67, 0x64, 0xe0, 0x2f, 0x2e, 0x93, 0x41, 0x57, 0x90, 0x35, 0x57, 0x90, 0x2d, 0x57, 0x92, 0x2d, 0x57, 0x90, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0xfd, 0x56, 0x91, 0xfd, 0x56, 0x91, 0xf9, 0x56, 0xb1, 0xd6, 0x54, 0xb1, 0xd6, 0x54, 0x91, 0xf9, 0x56, 0x91, 0xf9, 0x56, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0xb1, 0xd6, 0x54, 0x11, 0x89, 0x5e, 0x26, 0x12, 0xbd, 0x4c, 0xb4, 0x79, 0x96, 0x68, 0xf3, 0x2c, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x05, 0xeb, 0x48, 0x05, 0xd1, 0x66, 0x05, 0xd1, 0x66, 0x05, 0xd1, 0x66, 0x05, 0xd1, 0x66, 0x85, 0xfe, 0xc9, 0xa9, 0x13, 0x78, 0xd1, 0x2f, 0x59, 0x7d, 0x7f, 0x64, 0x44, 0x3e, 0xc7, 0xda, 0x2d, 0x58, 0x7b, 0x7d, 0xac, 0x5d, 0x65, 0xde, 0x2d, 0xcc, 0x7b, 0x21, 0xf3, 0x5e, 0xc8, 0xbc, 0x6b, 0x7f, 0xdd, 0x71, 0x33, 0xe7, 0x73, 0x99, 0xf3, 0x08, 0xcb, 0x07, 0xa2, 0x91, 0x25, 0x97, 0xf5, 0x22, 0x58, 0x0c, 0x61, 0xfe, 0x55, 0x94, 0x1f, 0x8a, 0xf2, 0x07, 0xa1, 0xfc, 0xce, 0x28, 0x3f, 0x0c, 0x3d, 0xcc, 0x44, 0x0f, 0x93, 0x51, 0x7d, 0x07, 0x34, 0x71, 0x08, 0x4d, 0xf8, 0xab, 0x51, 0xc2, 0x82, 0x2e, 0xa6, 0x5a, 0x7f, 0x11, 0xad, 0xf4, 0x92, 0xaf, 0x2b, 0x8a, 0x12, 0xac, 0x5c, 0xad, 0x70, 0x45, 0x2b, 0xce, 0x9e, 0xc4, 0xd9, 0x0d, 0x88, 0xbc, 0xb4, 0xef, 0x26, 0xde, 0x09, 0x8b, 0xf5, 0x6f, 0x3a, 0xac, 0xd5, 0xbf, 0x03, 0xa2, 0x7d, 0x2e, 0xdd, 0xf3, 0x8d, 0x29, 0x1b, 0x47, 0xac, 0xb7, 0x78, 0x1f, 0x1b, 0xde, 0xc7, 0x86, 0xf7, 0xb1, 0xe1, 0x79, 0x6c, 0x78, 0x1e, 0x1b, 0x1e, 0xc5, 0x86, 0x47, 0xb1, 0xe1, 0x25, 0x6c, 0x96, 0xa7, 0x44, 0x43, 0xbc, 0x84, 0x0d, 0x2f, 0x61, 0xc3, 0x4b, 0xd8, 0x2c, 0x92, 0xf6, 0x34, 0x24, 0x22, 0x0a, 0x80, 0x46, 0x10, 0x08, 0x9c, 0x8b, 0x85, 0xdb, 0xb0, 0xe8, 0x40, 0x5a, 0xf5, 0x1e, 0xfd, 0xda, 0x4a, 0xcb, 0x62, 0xe8, 0xd7, 0x32, 0xfa, 0xd5, 0xa3, 0x4e, 0x0b, 0x3f, 0xc2, 0x82, 0x6d, 0x58, 0xb0, 0x0d, 0x0b, 0xb6, 0x61, 0xc1, 0x36, 0x2c, 0xd8, 0x86, 0x05, 0xdb, 0xb0, 0x60, 0x1b, 0x16, 0x6c, 0xc3, 0x82, 0x6d, 0x58, 0xb0, 0x0d, 0x0b, 0xb6, 0x61, 0xc1, 0x36, 0x2c, 0xd8, 0x86, 0x05, 0xdb, 0xd4, 0x87, 0x45, 0x77, 0xc6, 0x20, 0x82, 0xd5, 0x22, 0x54, 0x98, 0xe9, 0xa1, 0x99, 0x1e, 0x9a, 0xe9, 0x21, 0x1e, 0x57, 0xd4, 0x17, 0xda, 0x63, 0x77, 0x62, 0xe8, 0xe5, 0x12, 0xb6, 0xda, 0x93, 0xaf, 0x62, 0x79, 0xcf, 0xd3, 0x6b, 0x33, 0xbd, 0x36, 0xd3, 0x6b, 0xe5, 0x96, 0x5e, 0x9b, 0xe9, 0xb5, 0x99, 0x5e, 0x9b, 0xe9, 0xb5, 0x99, 0x5e, 0x9b, 0xe9, 0xb5, 0x99, 0x5e, 0x9b, 0x99, 0xad, 0xfa, 0xf4, 0xdc, 0x4c, 0xaf, 0xcd, 0xf4, 0xda, 0x4c, 0xaf, 0xcd, 0xf4, 0xd8, 0x4c, 0x8f, 0xcd, 0xf4, 0xd8, 0x4c, 0x8f, 0xcd, 0xf4, 0xd8, 0x4c, 0x8f, 0xcd, 0xf8, 0xb4, 0xfa, 0xf8, 0xb4, 0xfa, 0xf8, 0xb4, 0xfa, 0xcc, 0xea, 0x0a, 0x46, 0x40, 0x61, 0x04, 0xfa, 0x32, 0x02, 0x71, 0x8c, 0x40, 0x02, 0x23, 0xb0, 0x99, 0x11, 0xd8, 0x88, 0x4f, 0xab, 0xcf, 0xec, 0xd6, 0xd7, 0x47, 0xa2, 0x1b, 0x5b, 0x6d, 0x34, 0x7a, 0xb2, 0xed, 0x25, 0x06, 0x30, 0x22, 0x66, 0x46, 0xc4, 0xcc, 0x88, 0x98, 0x19, 0x11, 0x33, 0x23, 0x62, 0x66, 0x44, 0xcc, 0x8c, 0x88, 0x99, 0x11, 0x31, 0x33, 0x22, 0x66, 0x46, 0xc4, 0xcc, 0x88, 0x98, 0x19, 0x11, 0x33, 0x23, 0x62, 0x66, 0x44, 0xcc, 0xea, 0x26, 0xd1, 0x55, 0xdd, 0x26, 0x02, 0x19, 0x95, 0x29, 0x22, 0x98, 0x91, 0x98, 0xc3, 0x48, 0x04, 0x31, 0x12, 0x41, 0xc6, 0x48, 0x04, 0x69, 0x23, 0x71, 0xcb, 0x9a, 0x1d, 0x44, 0x8b, 0x83, 0x68, 0x71, 0x10, 0x2d, 0x0e, 0xa2, 0xc5, 0xe1, 0x46, 0x8b, 0x17, 0xd3, 0xe2, 0xbb, 0x68, 0x71, 0x22, 0x2d, 0x5e, 0x47, 0x8b, 0x17, 0xd2, 0xe2, 0x20, 0x5a, 0x1c, 0x64, 0xb4, 0x38, 0xc8, 0x68, 0x71, 0x10, 0x2d, 0xb6, 0x53, 0x63, 0x18, 0x71, 0x44, 0xa4, 0x18, 0x79, 0x93, 0x1e, 0x83, 0x45, 0x3f, 0x4a, 0xb2, 0x53, 0xd2, 0x66, 0x4a, 0x4a, 0xa3, 0xa4, 0x7b, 0x29, 0x29, 0xbe, 0xce, 0xec, 0xc7, 0x71, 0x65, 0x6f, 0xd1, 0x90, 0x2b, 0x87, 0xdc, 0x72, 0xe5, 0x52, 0xae, 0xec, 0xc2, 0x95, 0x33, 0xb8, 0x72, 0x36, 0x57, 0xc6, 0x73, 0x65, 0x42, 0x9d, 0x2b, 0x7b, 0xd2, 0x5b, 0x1b, 0xbd, 0xb5, 0x50, 0xc2, 0xc8, 0xdf, 0xd8, 0x81, 0xe5, 0x16, 0x3b, 0xa8, 0x2f, 0xfa, 0xa3, 0x92, 0x10, 0x54, 0x42, 0xcf, 0x40, 0xfb, 0xce, 0x66, 0x2c, 0x5b, 0x8f, 0x22, 0x42, 0x50, 0x44, 0x08, 0x8a, 0xb0, 0xdc, 0xa2, 0x88, 0x10, 0x14, 0x11, 0x82, 0x22, 0x42, 0x50, 0x44, 0x08, 0x8a, 0x08, 0x41, 0x11, 0x21, 0x28, 0x22, 0x04, 0x35, 0x84, 0x60, 0x07, 0xfe, 0x28, 0x22, 0x04, 0x45, 0x84, 0xa0, 0x88, 0x10, 0x14, 0x11, 0x82, 0x22, 0x42, 0x50, 0x44, 0x08, 0x8a, 0x08, 0x41, 0x11, 0x21, 0x28, 0x22, 0x84, 0xf1, 0x0c, 0xa0, 0x45, 0xef, 0xd0, 0x97, 0x74, 0x5a, 0x15, 0x45, 0x5f, 0xe2, 0xe8, 0x4b, 0xb7, 0x3a, 0xad, 0xfb, 0x90, 0x19, 0x0f, 0x61, 0xc6, 0x43, 0x98, 0xf1, 0x10, 0x66, 0x3c, 0x84, 0x19, 0x0f, 0x61, 0xc6, 0x43, 0x98, 0xf1, 0x10, 0x66, 0x3c, 0x84, 0x19, 0x0f, 0x61, 0xc6, 0x43, 0x98, 0xf1, 0x10, 0x66, 0x3c, 0x84, 0x19, 0x0f, 0x61, 0xc6, 0x43, 0xb0, 0x81, 0x6e, 0xf4, 0x7f, 0xa8, 0x6e, 0x03, 0x2a, 0xbd, 0x53, 0xe9, 0x9d, 0x6a, 0xd8, 0x80, 0x3f, 0x33, 0xef, 0x6f, 0xcc, 0xbc, 0xbf, 0x61, 0x03, 0xaa, 0xd1, 0x63, 0x95, 0x1e, 0xab, 0xbf, 0x63, 0x03, 0x2a, 0x3d, 0x56, 0xe9, 0xb1, 0x4a, 0x8f, 0x55, 0x7a, 0xac, 0xd2, 0x63, 0x95, 0x1e, 0xab, 0x28, 0xc5, 0x9f, 0x5e, 0xab, 0xf4, 0x58, 0xa5, 0xc7, 0x2a, 0x3d, 0x56, 0xe9, 0xb1, 0x4a, 0x8f, 0x55, 0x7a, 0xac, 0xd2, 0x63, 0x95, 0x1e, 0xab, 0xf4, 0x58, 0x45, 0x51, 0xfe, 0x28, 0xca, 0x1f, 0x45, 0xf9, 0x33, 0x93, 0xa9, 0x86, 0xa2, 0xc2, 0x18, 0x81, 0x65, 0x86, 0x0d, 0xdc, 0xc7, 0x08, 0x6c, 0x42, 0x51, 0xfe, 0x28, 0xca, 0xdf, 0x50, 0x94, 0xbf, 0xa1, 0x28, 0xed, 0x13, 0xb5, 0xe1, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0xa8, 0xa2, 0x1b, 0xaa, 0x08, 0x62, 0x54, 0xa6, 0xfe, 0x8f, 0x6d, 0x20, 0x92, 0x16, 0xa7, 0x19, 0x36, 0x70, 0x17, 0x2d, 0x9e, 0xfd, 0xa7, 0x6c, 0x60, 0xd4, 0x2d, 0x4a, 0xee, 0x4f, 0x49, 0xb1, 0x94, 0xf4, 0x00, 0x25, 0xdd, 0x65, 0xd8, 0x7f, 0x52, 0x9d, 0xd9, 0x4f, 0xe0, 0xca, 0xbe, 0xba, 0x0d, 0x0c, 0xbd, 0xe5, 0xca, 0x58, 0xae, 0xec, 0xca, 0x95, 0xb3, 0xb9, 0x72, 0x2e, 0x57, 0x26, 0x71, 0xe5, 0x8a, 0x3a, 0x57, 0x86, 0xd2, 0xdb, 0x36, 0xf4, 0xd6, 0x4a, 0x09, 0xa3, 0x44, 0x3d, 0x65, 0xaa, 0xfc, 0xd7, 0x5f, 0x8a, 0xc8, 0xcd, 0x7a, 0xe4, 0x3f, 0x5e, 0x9e, 0xe3, 0x2a, 0x27, 0x57, 0x15, 0x1b, 0xeb, 0xd3, 0x35, 0xa2, 0xfb, 0x1f, 0x59, 0xb9, 0x6e, 0xe4, 0x04, 0xdf, 0xb2, 0xb6, 0x5f, 0x60, 0x5d, 0x3f, 0x5b, 0xb7, 0x7c, 0xce, 0xfa, 0x8e, 0x32, 0x96, 0x88, 0x0e, 0x37, 0xb5, 0xdb, 0x66, 0x78, 0x35, 0xda, 0x28, 0xea, 0x51, 0xc3, 0xe7, 0x5c, 0x9f, 0xc5, 0xf5, 0x99, 0xc4, 0x06, 0x5a, 0x19, 0x05, 0x9c, 0xed, 0xe6, 0x6c, 0x37, 0x59, 0x70, 0x1b, 0x6a, 0x3b, 0xaf, 0x3e, 0xab, 0xfb, 0xac, 0x86, 0x94, 0xf7, 0x3a, 0x23, 0x68, 0x92, 0x1f, 0x91, 0xcd, 0x56, 0xfd, 0xe5, 0xde, 0x54, 0x90, 0x5f, 0x74, 0xc4, 0x0e, 0xd2, 0xc9, 0x2f, 0x82, 0xb1, 0x89, 0x60, 0xf2, 0x8b, 0x60, 0xd6, 0x87, 0x60, 0xf2, 0x0b, 0x32, 0x7a, 0x18, 0x09, 0xa3, 0x60, 0x34, 0xe7, 0x8d, 0x81, 0xb1, 0x30, 0x0e, 0xc6, 0xc3, 0x04, 0x8e, 0x4f, 0x64, 0x3b, 0x89, 0xed, 0x64, 0xd0, 0x3c, 0xa8, 0xa6, 0xa0, 0x69, 0x30, 0x1d, 0x66, 0xc0, 0x4c, 0x98, 0x05, 0xb7, 0xc3, 0x6c, 0x98, 0x03, 0x73, 0x61, 0x1e, 0xdc, 0x01, 0xf3, 0x61, 0x01, 0xdc, 0x49, 0x1b, 0x16, 0x0b, 0xed, 0xfe, 0x46, 0x3a, 0xea, 0xdb, 0x8c, 0xfa, 0x36, 0xa3, 0xbe, 0xcd, 0x8c, 0xd4, 0x66, 0xd4, 0xb7, 0x19, 0x3b, 0x4c, 0xc7, 0x0e, 0xd3, 0xb1, 0xc3, 0x74, 0xec, 0x30, 0x1d, 0x3b, 0x4c, 0x27, 0xbf, 0x08, 0x16, 0xeb, 0xe8, 0xc5, 0x7a, 0xb8, 0x07, 0x36, 0xc0, 0xbd, 0xb0, 0x11, 0x36, 0xc1, 0x66, 0xb8, 0x0f, 0xee, 0x87, 0x74, 0x94, 0xf6, 0xa0, 0xe8, 0x49, 0x1e, 0xd2, 0x93, 0x3c, 0xa4, 0x3f, 0x79, 0xc8, 0x7d, 0x44, 0x9c, 0x69, 0xe4, 0x21, 0x2b, 0x88, 0x3a, 0xb7, 0x92, 0x87, 0xec, 0x21, 0xf2, 0x5c, 0x47, 0x1e, 0xf2, 0x37, 0xa2, 0xcf, 0x75, 0xe4, 0x21, 0x7f, 0x23, 0x0f, 0x49, 0x26, 0x0f, 0x79, 0x4f, 0x1f, 0xd5, 0xe9, 0xe4, 0x23, 0x33, 0xc5, 0x9b, 0x44, 0x27, 0xaf, 0x13, 0x99, 0x6e, 0x27, 0x17, 0xd9, 0x4d, 0x2e, 0xb2, 0x9b, 0x48, 0xe5, 0x08, 0xb9, 0xc8, 0xf2, 0x5b, 0x7c, 0x42, 0x3a, 0x3e, 0x21, 0x1d, 0x9f, 0x90, 0x6e, 0xa2, 0x5d, 0x26, 0xda, 0x45, 0x4e, 0xd2, 0x1f, 0xff, 0x90, 0x8e, 0x7f, 0x48, 0x27, 0x27, 0xe9, 0x48, 0x4e, 0xd2, 0x91, 0x9c, 0xa4, 0x2d, 0x39, 0x49, 0x47, 0x72, 0x92, 0x8e, 0xe4, 0x24, 0xc1, 0xe4, 0x24, 0xc1, 0xe4, 0x24, 0xc1, 0xe4, 0x24, 0xc1, 0xe4, 0x24, 0xc1, 0xa6, 0x57, 0xb9, 0xee, 0xef, 0x22, 0x10, 0x7f, 0x92, 0x8e, 0x3f, 0x49, 0x27, 0xfa, 0xdd, 0x4d, 0xf4, 0xbb, 0x9b, 0xdc, 0xa4, 0x23, 0xb9, 0x49, 0xb0, 0x91, 0x9b, 0x74, 0x24, 0x37, 0x09, 0x26, 0x37, 0x09, 0x26, 0x37, 0x09, 0x26, 0x37, 0x09, 0x26, 0x37, 0x59, 0x81, 0xdf, 0x49, 0xf7, 0x65, 0xc6, 0x8d, 0xfc, 0x24, 0x1d, 0xff, 0x93, 0x8e, 0xff, 0x49, 0xb7, 0x1c, 0x24, 0x8a, 0x3a, 0x2f, 0x82, 0xc9, 0x51, 0x82, 0xc9, 0x51, 0x82, 0xc9, 0x51, 0x82, 0xc9, 0x51, 0x3a, 0x92, 0xa3, 0x74, 0x24, 0x47, 0xe9, 0x48, 0x8e, 0xd2, 0x91, 0x1c, 0xa5, 0x23, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0xbe, 0x2c, 0x1d, 0x5f, 0x96, 0x8e, 0x2f, 0x4b, 0xc7, 0x97, 0xa5, 0xe3, 0xcb, 0xd2, 0xf1, 0x65, 0xe9, 0x78, 0x86, 0xcd, 0x78, 0x86, 0xcd, 0x78, 0x86, 0xcd, 0x58, 0xe4, 0x66, 0x3c, 0xc3, 0x66, 0x23, 0x47, 0xd1, 0xd6, 0xb4, 0xcd, 0x86, 0x3d, 0x6f, 0xc6, 0x33, 0x6c, 0xae, 0x93, 0xa3, 0x6c, 0x36, 0x72, 0x94, 0xcd, 0x46, 0x8e, 0x92, 0x8e, 0x2f, 0x4b, 0xc7, 0x97, 0xa5, 0xe3, 0xcb, 0xd2, 0xf1, 0x65, 0xe9, 0xf8, 0xb2, 0x74, 0x7c, 0x59, 0x3a, 0xbe, 0x2c, 0x1d, 0x5f, 0x96, 0x8e, 0x2f, 0x4b, 0xc7, 0x97, 0xa5, 0xe3, 0xcb, 0xd2, 0xf1, 0x65, 0xe9, 0xf8, 0xb2, 0x74, 0x75, 0x02, 0x56, 0xae, 0xe5, 0x29, 0xdb, 0xc4, 0x0e, 0x75, 0x17, 0xdb, 0xdd, 0xf0, 0x24, 0x3c, 0x05, 0x4f, 0xc3, 0x33, 0x80, 0xed, 0xa8, 0xcf, 0xc1, 0x1e, 0xd8, 0x0b, 0xcf, 0xc3, 0x0b, 0xf0, 0x22, 0xec, 0x83, 0x97, 0x60, 0x3f, 0xbc, 0x0c, 0xaf, 0xc0, 0x01, 0x60, 0x2e, 0x54, 0xe6, 0x42, 0x7d, 0x0d, 0x5e, 0x87, 0x37, 0xe0, 0x1f, 0xf0, 0x26, 0xbc, 0x05, 0x6f, 0xc3, 0x3b, 0xf0, 0x2e, 0xbc, 0x07, 0xef, 0xc3, 0x07, 0xf0, 0x21, 0x7c, 0x04, 0x1f, 0xc3, 0x3f, 0xe1, 0x13, 0xf8, 0x54, 0xcf, 0xa3, 0xc2, 0xc8, 0xa3, 0xc2, 0xc8, 0xa3, 0x7a, 0x92, 0x47, 0xf5, 0x24, 0x8f, 0xea, 0x49, 0x1e, 0xd5, 0x93, 0x3c, 0xaa, 0x27, 0x79, 0x54, 0x4f, 0xf2, 0xa8, 0x9e, 0xe4, 0x51, 0x3d, 0xc9, 0xa3, 0x7a, 0x92, 0x47, 0xf5, 0x27, 0x8f, 0xea, 0x4f, 0x1e, 0xd5, 0x9f, 0x3c, 0xaa, 0x3f, 0x79, 0x54, 0x7f, 0xf2, 0xa8, 0xfe, 0xe4, 0x51, 0xfd, 0xc9, 0xa3, 0xfa, 0x93, 0x47, 0xf5, 0x27, 0x8f, 0xea, 0x4f, 0x1e, 0xa5, 0x29, 0x7e, 0xbb, 0x3c, 0x8f, 0x17, 0xfa, 0xe9, 0x96, 0xfb, 0x1c, 0x5f, 0xd7, 0xb9, 0x7b, 0x72, 0x4c, 0xb1, 0xa3, 0xd4, 0x1b, 0xf7, 0x39, 0xb4, 0x4f, 0xf8, 0x55, 0xf9, 0xf4, 0x95, 0x3f, 0xf8, 0x84, 0xc1, 0x97, 0xb2, 0xca, 0xb8, 0xcf, 0xf1, 0x9c, 0x68, 0x2c, 0xb6, 0xc9, 0x0b, 0x94, 0xf8, 0x0b, 0x25, 0x7e, 0x41, 0x89, 0x5f, 0x52, 0xe2, 0x3e, 0x4a, 0xfc, 0x92, 0x12, 0xff, 0x49, 0x89, 0x5f, 0x50, 0xe2, 0x61, 0x4a, 0x3c, 0x44, 0x89, 0x67, 0x28, 0xd1, 0x41, 0x89, 0x0e, 0x4a, 0x3c, 0x4f, 0x89, 0x97, 0xb4, 0x6f, 0x24, 0x50, 0xea, 0x67, 0x94, 0xfa, 0x99, 0x76, 0x7f, 0x91, 0x52, 0x8b, 0x28, 0xf5, 0x41, 0xd1, 0xdc, 0x28, 0xb5, 0xf2, 0x7a, 0xa9, 0x83, 0x89, 0xf3, 0x87, 0xc2, 0xef, 0x95, 0xbe, 0x90, 0x92, 0x17, 0xc1, 0x7f, 0xae, 0xe5, 0x14, 0xb5, 0x9c, 0xba, 0xa9, 0x16, 0x6d, 0x24, 0xf2, 0xa8, 0xe1, 0x28, 0x35, 0x1c, 0xa5, 0xe4, 0xd7, 0x29, 0xf9, 0x27, 0x4a, 0x3e, 0x48, 0xc9, 0xdf, 0x50, 0xf2, 0x31, 0x4a, 0x3c, 0x62, 0xdc, 0xf1, 0xd1, 0x3c, 0xb9, 0x53, 0xbf, 0xdb, 0xe8, 0xb9, 0xe3, 0xf3, 0x2d, 0xa5, 0x7d, 0x6b, 0xdc, 0xf1, 0xb9, 0x44, 0x69, 0x3b, 0x44, 0x13, 0xda, 0x7c, 0x8e, 0x12, 0x73, 0x28, 0xf1, 0x20, 0x25, 0x7e, 0x41, 0xce, 0x72, 0x9e, 0x52, 0x0f, 0x50, 0xea, 0xd7, 0x94, 0xfa, 0x29, 0xa5, 0x7e, 0x65, 0x8c, 0xc6, 0xb7, 0x94, 0x7a, 0x9a, 0x52, 0x0b, 0x28, 0xb5, 0xc0, 0xb8, 0x8f, 0xe4, 0xd2, 0x3e, 0xf1, 0x40, 0xc9, 0x9f, 0x52, 0xf2, 0xa7, 0x94, 0xec, 0xa2, 0x64, 0x6d, 0x35, 0xb8, 0x8b, 0x38, 0xcb, 0x24, 0x33, 0xf1, 0xf9, 0xda, 0xbd, 0xdc, 0x7f, 0x50, 0xaa, 0xd6, 0x4e, 0xcd, 0xf7, 0x7f, 0x4f, 0x89, 0x3f, 0x53, 0x4a, 0x3e, 0xa5, 0xe4, 0x1b, 0xab, 0x4c, 0x05, 0x3e, 0xbf, 0x09, 0x57, 0x39, 0x85, 0x3f, 0xed, 0xc8, 0xa7, 0x1d, 0x0e, 0xda, 0xf0, 0x29, 0x57, 0x64, 0x70, 0xc5, 0x97, 0xc6, 0x1c, 0x6b, 0x9f, 0xe2, 0xca, 0xe1, 0xca, 0x22, 0xa3, 0x7e, 0xed, 0x1b, 0x49, 0x79, 0xd4, 0x9b, 0xc7, 0x95, 0xff, 0x10, 0x6f, 0x90, 0x1b, 0x3b, 0xc9, 0x8d, 0x9d, 0xe4, 0xc6, 0x4e, 0x72, 0x63, 0x27, 0xb9, 0xb1, 0x93, 0xdc, 0xd8, 0x49, 0x6e, 0xec, 0x14, 0x5a, 0xd9, 0xa3, 0x61, 0x0c, 0x8c, 0x85, 0x71, 0x30, 0x1e, 0x26, 0xc0, 0x44, 0x98, 0x04, 0x93, 0x61, 0x0a, 0x4c, 0x85, 0x69, 0x30, 0x1d, 0x66, 0xc0, 0x4c, 0x98, 0x05, 0xb7, 0xc3, 0x6c, 0x98, 0x03, 0x73, 0x61, 0x1e, 0xdc, 0x01, 0xf3, 0x61, 0x01, 0x2c, 0x94, 0x57, 0xc4, 0x2a, 0xf2, 0xe3, 0xd5, 0x70, 0x17, 0xac, 0xd1, 0xf3, 0x65, 0xa7, 0x58, 0x27, 0x8b, 0xc5, 0x7a, 0xb8, 0x07, 0x36, 0xc0, 0xbd, 0xb0, 0x11, 0x36, 0xc1, 0x66, 0xb8, 0x0f, 0xee, 0x87, 0x74, 0x79, 0x46, 0x3c, 0x28, 0xcf, 0x93, 0x5b, 0x97, 0x90, 0x5b, 0xe7, 0x32, 0x02, 0x4e, 0xe3, 0xf3, 0x6b, 0xa5, 0x26, 0xae, 0x35, 0x71, 0xad, 0x69, 0x8b, 0xbc, 0x62, 0x7a, 0x94, 0xf9, 0xdc, 0x21, 0x73, 0xc9, 0xa3, 0x9d, 0xe4, 0xd1, 0x4e, 0xf2, 0x68, 0x27, 0x79, 0xb4, 0x93, 0x3c, 0xda, 0x49, 0x1e, 0xed, 0x24, 0x8f, 0x76, 0x92, 0x47, 0x3b, 0xc9, 0xa3, 0x9d, 0xe4, 0xd1, 0x4e, 0xd3, 0xab, 0x5c, 0xf7, 0x77, 0x59, 0x4c, 0x3e, 0xed, 0x24, 0x9f, 0x76, 0x92, 0x4f, 0x3b, 0xc9, 0xa7, 0x9d, 0xe4, 0xd3, 0x4e, 0xf2, 0x69, 0x27, 0xf9, 0xb4, 0xd3, 0xb7, 0x83, 0x2c, 0x26, 0xa7, 0x76, 0x92, 0x53, 0x3b, 0xc9, 0xa9, 0x9d, 0xe4, 0xd4, 0x4e, 0x72, 0x6a, 0x27, 0x39, 0xb5, 0x93, 0x9c, 0xda, 0x49, 0x4e, 0xed, 0x24, 0xa7, 0x76, 0x92, 0x53, 0x3b, 0xc9, 0xa9, 0x9d, 0xe4, 0xd4, 0x4e, 0x72, 0x6a, 0x27, 0x39, 0xb5, 0x93, 0x9c, 0xda, 0x49, 0x4e, 0xed, 0x24, 0xa7, 0x76, 0x92, 0x53, 0x3b, 0x2d, 0x52, 0x5e, 0x51, 0x05, 0x28, 0x60, 0x02, 0x2f, 0xf0, 0x06, 0x1f, 0x78, 0x88, 0x9c, 0xfb, 0x61, 0xd8, 0x06, 0xdb, 0x81, 0xbe, 0xa8, 0x8f, 0xc1, 0xe3, 0xf0, 0x04, 0xec, 0x80, 0x5d, 0xb2, 0x58, 0xdd, 0x0d, 0x4f, 0xc2, 0x53, 0xf0, 0x34, 0x3c, 0x03, 0xcf, 0xc2, 0x73, 0xb0, 0x07, 0xf6, 0xc2, 0xf3, 0xf0, 0x02, 0xbc, 0x08, 0xfb, 0xe0, 0x25, 0xd8, 0x0f, 0x2f, 0xc3, 0x2b, 0x70, 0x00, 0xe8, 0xbb, 0x4a, 0xdf, 0xd5, 0xd7, 0xe0, 0x75, 0x78, 0x03, 0xfe, 0x01, 0x6f, 0xc2, 0x5b, 0xf0, 0x36, 0xbc, 0x03, 0xef, 0xc2, 0x7b, 0xf0, 0x3e, 0x7c, 0x00, 0x1f, 0xc2, 0x47, 0xf0, 0x31, 0xfc, 0x13, 0x3e, 0x81, 0x4f, 0x21, 0x53, 0x9e, 0x51, 0xb3, 0x20, 0x9b, 0x58, 0x25, 0x07, 0x4e, 0xc0, 0x49, 0x38, 0x05, 0xb9, 0x90, 0x07, 0x67, 0x64, 0x89, 0xfa, 0x2b, 0x9c, 0x93, 0xb9, 0xaa, 0x03, 0xf2, 0xa1, 0x00, 0x9c, 0x50, 0x0c, 0xe7, 0xe1, 0x02, 0x5c, 0x84, 0x12, 0x99, 0xcb, 0x6a, 0x1f, 0x05, 0xd1, 0xe0, 0x89, 0x4b, 0x83, 0x7f, 0x27, 0x2e, 0x0d, 0x66, 0xf5, 0x09, 0x66, 0xf5, 0x09, 0x66, 0xf5, 0x09, 0x66, 0xf5, 0xd9, 0x6d, 0xc4, 0xa5, 0x93, 0x59, 0x7d, 0x62, 0x8d, 0x8c, 0x2a, 0x8a, 0xd5, 0xe7, 0x23, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x19, 0xc4, 0x88, 0x61, 0x58, 0xda, 0x42, 0xca, 0x0f, 0x25, 0x6e, 0x08, 0x97, 0x97, 0x89, 0x4f, 0xc2, 0x88, 0x4f, 0xc2, 0x88, 0x4f, 0xc2, 0x68, 0x45, 0x28, 0xad, 0x08, 0xa5, 0x15, 0xa1, 0xb4, 0x22, 0x54, 0x3f, 0x2b, 0x96, 0xe3, 0xcb, 0x60, 0x39, 0xa4, 0xc2, 0x5a, 0x78, 0x88, 0x7c, 0x61, 0x1b, 0xd1, 0xd4, 0x76, 0xd1, 0x9b, 0x38, 0xa3, 0x0b, 0x71, 0xc6, 0x68, 0xe2, 0x8c, 0x79, 0xc4, 0x19, 0x03, 0x89, 0x33, 0xa6, 0x10, 0x67, 0x0c, 0x24, 0xce, 0x98, 0x42, 0x9c, 0xd1, 0x99, 0x38, 0x23, 0x92, 0x18, 0xc3, 0x4e, 0x8c, 0xb1, 0x8c, 0x18, 0x63, 0x09, 0x31, 0x46, 0x04, 0x31, 0xc6, 0x74, 0x62, 0x8c, 0xe9, 0xc4, 0x18, 0x89, 0xc4, 0x18, 0x9d, 0x89, 0x2b, 0xc2, 0x88, 0x2b, 0xc2, 0x88, 0x2b, 0xc2, 0x88, 0x03, 0x1b, 0x12, 0x53, 0x68, 0x39, 0x47, 0x18, 0x31, 0x45, 0x18, 0x31, 0x42, 0x18, 0x31, 0x42, 0x18, 0x31, 0xc2, 0x54, 0x62, 0x84, 0xa9, 0x8c, 0x44, 0x28, 0x71, 0x40, 0x17, 0xe2, 0x80, 0x30, 0xd6, 0xff, 0x30, 0xd6, 0xff, 0x30, 0xd6, 0xff, 0x30, 0xd6, 0x7f, 0x5f, 0xcb, 0x4f, 0x8c, 0x48, 0x43, 0xd6, 0x9e, 0x00, 0x68, 0x04, 0x81, 0xc0, 0xb5, 0xac, 0xdd, 0x61, 0x8c, 0x5e, 0x28, 0xa3, 0x17, 0xca, 0xe8, 0x85, 0x32, 0x7a, 0xa1, 0xc6, 0xe8, 0x85, 0x1a, 0x77, 0x59, 0x42, 0x19, 0xbd, 0x50, 0x46, 0x2f, 0x94, 0xd1, 0x0b, 0x65, 0xf4, 0x42, 0x19, 0xbd, 0x50, 0x46, 0x2f, 0x94, 0xd1, 0x0b, 0x65, 0xf4, 0x42, 0xf5, 0x08, 0xbb, 0x0f, 0xe5, 0xf4, 0x85, 0x30, 0xe8, 0x07, 0xfd, 0x21, 0x1c, 0x06, 0xc1, 0x60, 0x18, 0x02, 0x43, 0x61, 0x18, 0x44, 0xc0, 0x70, 0x18, 0x01, 0xdb, 0xc4, 0x70, 0xd6, 0x3a, 0x95, 0xb5, 0x4e, 0x65, 0xad, 0x53, 0x59, 0xeb, 0x54, 0xd6, 0x3a, 0x95, 0xb5, 0x4e, 0x65, 0xad, 0x53, 0x59, 0xeb, 0x54, 0xd6, 0x3a, 0x95, 0xb5, 0x4e, 0xc5, 0x93, 0x1d, 0x13, 0xde, 0xda, 0xcc, 0x60, 0xfd, 0xe7, 0xb5, 0xd1, 0xd0, 0x8f, 0xfc, 0x9f, 0xba, 0xab, 0x14, 0xf8, 0x27, 0xee, 0x2a, 0x35, 0xbd, 0xe9, 0xae, 0x92, 0xef, 0x1f, 0xc6, 0xf3, 0xb7, 0xbe, 0x73, 0x3d, 0xc7, 0x16, 0x7e, 0xf8, 0xf0, 0xf3, 0xd7, 0xb3, 0x85, 0xe9, 0x9c, 0x31, 0x53, 0x7e, 0x87, 0x17, 0xfb, 0xc9, 0xc8, 0x1a, 0x2e, 0x71, 0xd5, 0x25, 0xae, 0xd2, 0xd6, 0xe2, 0x23, 0xe4, 0x05, 0x4b, 0xc4, 0xe0, 0xdf, 0xb9, 0x22, 0x97, 0x2b, 0xf2, 0xfe, 0xe0, 0x0a, 0x13, 0x39, 0x49, 0x38, 0xab, 0x48, 0x7f, 0xf9, 0x11, 0xa3, 0xf1, 0x0e, 0x57, 0xfc, 0x7c, 0x3d, 0x16, 0x28, 0x65, 0x2d, 0xf5, 0x7c, 0x2b, 0xf1, 0x02, 0x23, 0x51, 0xcc, 0x15, 0x85, 0xfa, 0x15, 0x59, 0x5c, 0x91, 0xcf, 0x15, 0xc7, 0xb8, 0xe2, 0x90, 0xf1, 0x29, 0x6e, 0xed, 0x1e, 0x65, 0x96, 0xb1, 0xa2, 0x14, 0x71, 0xc5, 0x65, 0xae, 0xb8, 0xca, 0x15, 0x45, 0xfa, 0xf7, 0x10, 0xbf, 0xa7, 0x35, 0xc5, 0x46, 0xb6, 0x72, 0xb1, 0xf6, 0xaf, 0x1e, 0x46, 0xa6, 0x72, 0x51, 0x8b, 0xb6, 0x38, 0xf3, 0x9c, 0x5e, 0xf6, 0x0f, 0x46, 0x6b, 0xde, 0xa3, 0xec, 0x77, 0x8d, 0xfb, 0x9f, 0x87, 0xb8, 0xea, 0x47, 0xae, 0x3a, 0xcb, 0x55, 0x67, 0xf5, 0xd5, 0x92, 0xfc, 0x46, 0x6f, 0x8d, 0xf6, 0x29, 0x74, 0x6d, 0x7d, 0x3b, 0xcb, 0x59, 0x3f, 0x73, 0xd6, 0x2f, 0x75, 0x5a, 0xa0, 0xad, 0xca, 0xf9, 0x8a, 0x22, 0xbe, 0x95, 0x63, 0xc5, 0x21, 0xf9, 0xa9, 0x38, 0x2c, 0x47, 0x8b, 0xa3, 0x64, 0x41, 0x3f, 0xca, 0x4f, 0xc4, 0xb1, 0x9a, 0x3d, 0xe2, 0x27, 0xb9, 0x5b, 0xfc, 0x2c, 0x13, 0x44, 0x86, 0x6c, 0x2a, 0x8e, 0xcb, 0x75, 0x22, 0xb3, 0xe6, 0xa8, 0xc8, 0x92, 0xa7, 0xc4, 0x2f, 0x72, 0xb8, 0xc8, 0x96, 0xf7, 0x8b, 0x1c, 0x39, 0x44, 0x9c, 0x90, 0x1d, 0xc5, 0x49, 0x5a, 0x76, 0x4a, 0xb6, 0x16, 0xda, 0xa7, 0xde, 0xf3, 0x58, 0x87, 0x4e, 0xcb, 0x5d, 0xe2, 0x8c, 0x6c, 0x2f, 0x7e, 0xad, 0x79, 0x52, 0x9c, 0xad, 0xd9, 0x2b, 0xce, 0x81, 0x43, 0x76, 0x10, 0xf9, 0xb2, 0x8b, 0x28, 0x60, 0xbf, 0xb0, 0xe6, 0x5f, 0xa2, 0x48, 0x8e, 0x12, 0x4e, 0xd9, 0x4d, 0x14, 0xd7, 0x14, 0xe9, 0x9f, 0x18, 0x1f, 0x20, 0xd7, 0x2a, 0x03, 0xe5, 0x3d, 0x4a, 0xb8, 0x1c, 0xa4, 0x0c, 0x82, 0x08, 0x39, 0x56, 0x19, 0x0e, 0x23, 0x64, 0x77, 0x65, 0xa4, 0x9c, 0xa2, 0x8c, 0x62, 0x3b, 0x9a, 0xed, 0x18, 0x19, 0xa5, 0x4c, 0x90, 0x8b, 0x95, 0x49, 0x72, 0xb1, 0xd7, 0x8b, 0xf2, 0x53, 0xaf, 0x7d, 0x32, 0xc1, 0xeb, 0xa5, 0x9a, 0xbd, 0x5e, 0xfb, 0xa5, 0xb7, 0xd7, 0xcb, 0x72, 0x88, 0xd7, 0xab, 0x35, 0x4f, 0x7a, 0xfd, 0x1d, 0x5e, 0xe3, 0xd8, 0x1b, 0xf0, 0x76, 0xcd, 0x5e, 0xef, 0xcd, 0x72, 0xa9, 0xf7, 0xfd, 0xb0, 0x45, 0x26, 0x79, 0x3f, 0x20, 0x9f, 0xf1, 0x4e, 0x67, 0xfb, 0xa0, 0x7c, 0xc6, 0x27, 0x17, 0x6f, 0x77, 0xa5, 0x66, 0xaf, 0xcf, 0xd5, 0x9a, 0x6a, 0x9f, 0x2a, 0xfd, 0xdb, 0xfa, 0xa3, 0xcd, 0x61, 0x30, 0x01, 0x26, 0xca, 0x37, 0xcc, 0x93, 0x60, 0x32, 0xfb, 0x53, 0x60, 0x2a, 0xfb, 0xd3, 0x60, 0x3a, 0xfb, 0x33, 0x60, 0x26, 0xcc, 0x82, 0xdb, 0x61, 0x36, 0xcc, 0x81, 0xb9, 0xbc, 0x3f, 0x0f, 0xee, 0x60, 0x7f, 0x3e, 0x2c, 0x60, 0x7f, 0x21, 0x2c, 0x62, 0x7f, 0x85, 0xfc, 0xd4, 0xd2, 0x18, 0x9a, 0x40, 0x57, 0xe8, 0x26, 0x33, 0x2d, 0x3d, 0xd8, 0xf6, 0x64, 0x1b, 0x0a, 0xbd, 0xd8, 0xef, 0x0d, 0x5f, 0xd7, 0xec, 0x55, 0x07, 0xca, 0xd6, 0xea, 0x02, 0xb6, 0x0b, 0x21, 0x09, 0x92, 0x6b, 0xbe, 0x56, 0x57, 0xd4, 0x1c, 0x53, 0x53, 0xd8, 0x4f, 0xad, 0xf9, 0x46, 0x4d, 0xab, 0xf9, 0x45, 0x5d, 0xc9, 0xfe, 0xaa, 0x9a, 0xbd, 0xd6, 0x16, 0xf2, 0x13, 0x6b, 0x30, 0xb4, 0x94, 0x39, 0xd6, 0x10, 0x68, 0x05, 0x36, 0x5e, 0xb7, 0x66, 0xdb, 0x06, 0xda, 0x42, 0x3b, 0x5e, 0xb7, 0x87, 0x0e, 0xd0, 0x11, 0x3a, 0x71, 0xac, 0x33, 0x74, 0x81, 0xae, 0xbc, 0xee, 0xc6, 0xb6, 0x3b, 0xf4, 0x80, 0x9e, 0x35, 0x7b, 0xac, 0xa1, 0xd0, 0xab, 0xa6, 0xc8, 0xda, 0xbb, 0xa6, 0xc2, 0xda, 0x87, 0xfd, 0xbe, 0x10, 0xc6, 0xeb, 0x7e, 0x35, 0x2e, 0x6b, 0x7f, 0xf6, 0x07, 0xc0, 0xc0, 0x9a, 0x4f, 0xad, 0xe1, 0xd2, 0x6c, 0x1d, 0x54, 0x73, 0xc9, 0x3a, 0x58, 0x06, 0x5a, 0x87, 0x70, 0x6c, 0x28, 0x0c, 0xe3, 0xbc, 0x08, 0xae, 0x1b, 0xce, 0xfe, 0x08, 0x18, 0xc9, 0xeb, 0x51, 0x5c, 0x37, 0x9a, 0xfd, 0x31, 0x30, 0x96, 0xeb, 0xc6, 0x71, 0xdd, 0xf8, 0x9a, 0xa3, 0xd6, 0x09, 0x30, 0x51, 0xfa, 0x59, 0x27, 0xc9, 0xdb, 0xac, 0x93, 0xd9, 0x9f, 0xc2, 0xfe, 0x54, 0xd9, 0xd2, 0x3a, 0x8d, 0xfd, 0xe9, 0x35, 0xb9, 0xd6, 0x19, 0x72, 0xb8, 0x75, 0x16, 0xdc, 0x0e, 0xb3, 0x61, 0x0e, 0xcc, 0x85, 0x79, 0x70, 0x07, 0xcc, 0x87, 0x05, 0xb0, 0x10, 0x16, 0xc1, 0x9d, 0xb0, 0x18, 0x22, 0x21, 0x0a, 0xa2, 0x21, 0x06, 0xec, 0xb0, 0x04, 0x96, 0x42, 0x2c, 0xc4, 0xc1, 0x32, 0x58, 0x2e, 0x47, 0x5a, 0xe3, 0x89, 0x07, 0x93, 0xe4, 0x76, 0x6b, 0xb2, 0x68, 0x68, 0x5d, 0x21, 0xda, 0x5b, 0x53, 0xe4, 0x23, 0xd6, 0x54, 0x48, 0x13, 0x4d, 0xad, 0x2b, 0x45, 0x67, 0xeb, 0x2a, 0xf6, 0x57, 0x73, 0xce, 0x5d, 0x62, 0x91, 0x75, 0x8d, 0xbc, 0x68, 0x5d, 0x2b, 0xc7, 0x5a, 0xef, 0x96, 0x3f, 0x59, 0xd7, 0xc9, 0x93, 0xd6, 0xf5, 0xf2, 0x5b, 0xeb, 0x3d, 0x62, 0xb8, 0xf5, 0xde, 0x9a, 0x27, 0xad, 0x1b, 0x61, 0x93, 0xec, 0x62, 0xdd, 0x2c, 0xfb, 0x59, 0xef, 0x93, 0x03, 0xad, 0xf7, 0xb3, 0xbf, 0x05, 0x1e, 0x80, 0x74, 0x78, 0x10, 0xb6, 0xc2, 0x43, 0xf0, 0x30, 0x6c, 0x93, 0x29, 0xd6, 0xed, 0xf0, 0x08, 0xe7, 0x3f, 0xca, 0xeb, 0xc7, 0xe0, 0x71, 0x78, 0x02, 0x76, 0xc0, 0x2e, 0xc6, 0x6d, 0x37, 0x3c, 0x09, 0x4f, 0xc1, 0xd3, 0xf0, 0x0c, 0x3c, 0x0b, 0xcf, 0xc1, 0x1e, 0xd8, 0x0b, 0xcf, 0xc3, 0x0b, 0x35, 0xd9, 0xd6, 0x17, 0xd9, 0xee, 0x83, 0x97, 0x60, 0x3f, 0xbc, 0x0c, 0xaf, 0xc0, 0x01, 0x78, 0x15, 0xfe, 0x0e, 0xaf, 0xc1, 0xeb, 0xf0, 0x06, 0xe7, 0x7f, 0x24, 0x3f, 0xb5, 0x9e, 0x92, 0x8b, 0xad, 0xb9, 0xf2, 0xb0, 0xdf, 0x63, 0x35, 0xd5, 0xe2, 0x3b, 0x3c, 0xc0, 0x19, 0xac, 0x3f, 0x4f, 0xfc, 0x20, 0x5f, 0x14, 0xc7, 0x88, 0x0a, 0x7f, 0x92, 0x6e, 0xac, 0x3f, 0x0f, 0xeb, 0xdf, 0x89, 0xf5, 0x97, 0x8a, 0x4c, 0xe9, 0xc2, 0xfa, 0x2f, 0x60, 0xfd, 0x05, 0x58, 0x7f, 0x16, 0xd6, 0x7f, 0x10, 0xeb, 0xaf, 0xc6, 0xf2, 0xdd, 0x58, 0xfe, 0x7b, 0x58, 0xfe, 0x05, 0x2c, 0xff, 0xa2, 0x38, 0xcb, 0xf6, 0x1c, 0x38, 0xe4, 0x33, 0x58, 0x7c, 0x85, 0x28, 0x60, 0xbf, 0x90, 0x88, 0xb3, 0x88, 0xb5, 0x85, 0xa8, 0x44, 0x14, 0xcb, 0x2a, 0xe3, 0xaf, 0x38, 0x27, 0x94, 0x21, 0x72, 0x97, 0x32, 0x0c, 0xc6, 0xcb, 0xc7, 0xf1, 0x4f, 0x25, 0xc6, 0x13, 0x03, 0x76, 0x92, 0xaf, 0xec, 0x20, 0x5f, 0xd9, 0x81, 0x85, 0x9f, 0xc1, 0xc2, 0xf3, 0xbc, 0x5e, 0x92, 0x17, 0xb0, 0xee, 0x83, 0x5e, 0xaf, 0xb1, 0x7d, 0x03, 0xde, 0x96, 0x17, 0xb0, 0xe6, 0x32, 0x2c, 0xd9, 0xf3, 0x79, 0xe5, 0x5c, 0x32, 0x8e, 0x2b, 0xf2, 0x02, 0x56, 0x7c, 0x06, 0x4b, 0x3b, 0x83, 0xa5, 0x9d, 0xc1, 0xd2, 0xce, 0x60, 0x69, 0x67, 0xb0, 0xb4, 0xcb, 0x58, 0xda, 0x19, 0x2c, 0xed, 0x32, 0x96, 0x76, 0x19, 0x4b, 0x3b, 0x83, 0xa5, 0x9d, 0xb1, 0xcc, 0x90, 0xab, 0x2d, 0xf7, 0xc2, 0x46, 0x79, 0xcc, 0xb2, 0x89, 0x6d, 0x3a, 0xdb, 0x87, 0x61, 0x1b, 0x6c, 0xe7, 0xf5, 0x23, 0xf0, 0xb5, 0xbc, 0x80, 0x35, 0xba, 0xd5, 0x05, 0x6c, 0x17, 0x42, 0x12, 0x24, 0xc3, 0x0a, 0x48, 0x81, 0x54, 0x48, 0x83, 0x95, 0xb0, 0x4a, 0x5e, 0xb0, 0xf6, 0x94, 0x25, 0xd6, 0x50, 0xe8, 0x05, 0xbd, 0xa1, 0x0f, 0xf4, 0x85, 0x30, 0xe8, 0x07, 0xfd, 0x61, 0x00, 0x0c, 0x84, 0x70, 0x18, 0x04, 0x83, 0x61, 0x08, 0x0c, 0x85, 0x61, 0x10, 0x01, 0xc3, 0x61, 0x04, 0x8c, 0x84, 0x51, 0x30, 0x1a, 0xc6, 0xc0, 0x58, 0x18, 0x07, 0xe3, 0xc9, 0x7d, 0x26, 0xc0, 0x44, 0x98, 0x04, 0x93, 0x61, 0x0a, 0x4c, 0x85, 0x69, 0x30, 0x1d, 0x66, 0xc8, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x6a, 0xac, 0xa7, 0x1a, 0xeb, 0xa9, 0xb6, 0x26, 0xc3, 0x0a, 0x48, 0x81, 0x54, 0x48, 0x83, 0x95, 0xb0, 0x0a, 0x56, 0xc3, 0x5d, 0xb0, 0x06, 0xd6, 0xc2, 0xdd, 0xb0, 0x0e, 0xd6, 0xc3, 0x3d, 0xb0, 0x09, 0xeb, 0xda, 0x0c, 0xf7, 0xc1, 0xfd, 0xb0, 0x05, 0x1e, 0x80, 0x74, 0x78, 0x10, 0xb6, 0xc2, 0x43, 0xf0, 0x30, 0x6c, 0x83, 0xed, 0xf0, 0x08, 0x3c, 0x0a, 0x8f, 0xc1, 0xe3, 0xf0, 0x04, 0xec, 0x80, 0x5d, 0xe4, 0xdd, 0xbb, 0xe1, 0x49, 0x78, 0x0a, 0x9e, 0x86, 0x67, 0xe0, 0x59, 0x78, 0x0e, 0xf6, 0xc0, 0x5e, 0x78, 0x1e, 0x5e, 0x80, 0x17, 0x61, 0x1f, 0xbc, 0x04, 0xfb, 0xe1, 0x65, 0x78, 0x05, 0x0e, 0xc0, 0xab, 0xf0, 0x77, 0x78, 0x0d, 0x5e, 0x87, 0x37, 0xe0, 0x23, 0x79, 0x06, 0x8b, 0x79, 0x4b, 0x0c, 0x64, 0xcd, 0xcc, 0x63, 0xbd, 0xcc, 0x16, 0xdf, 0xc9, 0x5f, 0xc4, 0x11, 0x99, 0x83, 0xb5, 0x5c, 0xc0, 0x5a, 0xaa, 0xb0, 0x94, 0x12, 0xac, 0xa4, 0x14, 0x0b, 0xa9, 0x62, 0x5d, 0xdc, 0x83, 0x65, 0xb8, 0xb1, 0x8c, 0x12, 0x2c, 0xa3, 0x84, 0x35, 0xb1, 0x04, 0x4b, 0x28, 0xc7, 0x0a, 0x7e, 0xf7, 0x1b, 0x5e, 0x28, 0xbd, 0x8a, 0x75, 0xac, 0xa0, 0xee, 0x37, 0xbb, 0x58, 0x93, 0xb2, 0x59, 0x93, 0xb2, 0x59, 0x93, 0xb2, 0x59, 0x93, 0x2e, 0xb3, 0x26, 0x5d, 0x66, 0x4d, 0xca, 0x66, 0x4d, 0xca, 0x66, 0x4d, 0xba, 0xcc, 0x9a, 0x74, 0x99, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0xe9, 0x32, 0x6b, 0xd2, 0x65, 0xd6, 0xa4, 0x6c, 0xd6, 0xa4, 0x6c, 0xd6, 0xa4, 0xcb, 0xac, 0x49, 0x97, 0x59, 0x93, 0xb2, 0xb1, 0x80, 0x2f, 0x2c, 0x77, 0xc9, 0xc3, 0x58, 0x40, 0x11, 0xea, 0x2f, 0x42, 0xfd, 0x45, 0xa8, 0xbf, 0xc8, 0xb2, 0x43, 0x9e, 0x66, 0xc6, 0xa4, 0xf8, 0x1e, 0x9f, 0x90, 0x8d, 0x4f, 0xc8, 0xc2, 0x27, 0xec, 0xc2, 0x27, 0x14, 0x19, 0x3e, 0xe1, 0x67, 0x7c, 0xc2, 0xa3, 0xf4, 0xb4, 0x08, 0x9f, 0x50, 0x46, 0x6f, 0x1d, 0xf8, 0x04, 0x07, 0x3d, 0xfe, 0x0e, 0x9f, 0xf0, 0x01, 0x3e, 0xa1, 0xcc, 0xf0, 0x09, 0x6f, 0xd2, 0xf3, 0x7c, 0x7a, 0x5e, 0x80, 0x4f, 0x70, 0xe0, 0x13, 0x1c, 0xf8, 0x84, 0xdd, 0xf8, 0x04, 0x07, 0x3e, 0xc1, 0xc1, 0x48, 0x9c, 0xc1, 0x27, 0x1c, 0x67, 0x34, 0x7e, 0xc2, 0x27, 0x38, 0xf5, 0x67, 0x76, 0x0c, 0x91, 0x8f, 0xe2, 0x0f, 0x1e, 0xfd, 0xcd, 0x13, 0x44, 0x16, 0xf2, 0x7a, 0x91, 0xdc, 0x86, 0x3f, 0xc8, 0x66, 0x94, 0x7e, 0xc6, 0x1f, 0x38, 0xf0, 0x07, 0x1f, 0xe0, 0x0f, 0x1c, 0xf8, 0x03, 0x07, 0xfe, 0xc0, 0xc1, 0x2a, 0x5f, 0xc2, 0x2a, 0x5f, 0x82, 0x5f, 0xc8, 0xc2, 0x2f, 0x64, 0x19, 0x7e, 0xa1, 0x0c, 0xbf, 0xe0, 0xc0, 0x2f, 0x64, 0xe3, 0x17, 0xb2, 0xf1, 0x0b, 0xd9, 0xf8, 0x85, 0x6c, 0xfc, 0x42, 0xb6, 0xe1, 0x17, 0xb2, 0xeb, 0xf8, 0x85, 0x6c, 0xfc, 0x82, 0x36, 0x2a, 0xa9, 0xf8, 0x85, 0x14, 0xc3, 0x2f, 0xa4, 0xdc, 0xe2, 0x17, 0x52, 0xf0, 0x0b, 0x29, 0xf8, 0x05, 0x87, 0xe1, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xa1, 0x08, 0xbf, 0x50, 0x84, 0x5f, 0x60, 0x55, 0x85, 0x3e, 0xd0, 0x17, 0xc2, 0xa0, 0x1f, 0xf4, 0x87, 0x01, 0x30, 0x10, 0xc2, 0x61, 0x10, 0x0c, 0x86, 0x21, 0x30, 0x14, 0x86, 0x41, 0x04, 0x0c, 0x87, 0x11, 0x30, 0x12, 0x88, 0x1c, 0xf1, 0x0b, 0x45, 0xf8, 0x85, 0x22, 0xfc, 0x42, 0x11, 0x7e, 0xa1, 0x08, 0xbf, 0x50, 0x86, 0x5f, 0x28, 0xc3, 0x2f, 0x94, 0xe1, 0x17, 0xca, 0xf0, 0x0b, 0x65, 0xf8, 0x85, 0x32, 0xfc, 0x42, 0x19, 0x7e, 0xa1, 0x0c, 0xbf, 0x50, 0x86, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x28, 0xc3, 0x2f, 0x94, 0xe1, 0x17, 0xca, 0xf0, 0x0b, 0x65, 0xf8, 0x85, 0x32, 0xfc, 0x42, 0x19, 0x7e, 0xa1, 0x0c, 0xbf, 0x50, 0x86, 0x5f, 0x28, 0xc3, 0x2f, 0x94, 0xe1, 0x17, 0xca, 0x50, 0x59, 0x19, 0x7e, 0xa1, 0x0c, 0xbf, 0x50, 0x86, 0x5f, 0x28, 0xc3, 0x2f, 0x94, 0xe1, 0x17, 0xca, 0xf0, 0x0b, 0x65, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0xb2, 0xb5, 0xe7, 0xc1, 0x88, 0xb6, 0xf8, 0x83, 0x72, 0x2c, 0xe6, 0x34, 0x96, 0x52, 0x84, 0xa5, 0x7c, 0x83, 0xa5, 0x7c, 0x8a, 0xa5, 0x64, 0x60, 0x25, 0x67, 0xb1, 0x90, 0x6f, 0xb0, 0x90, 0x8f, 0xb1, 0x8c, 0xe3, 0x58, 0xc6, 0x15, 0x2c, 0xe3, 0x0c, 0x3e, 0xa1, 0x12, 0xab, 0xf8, 0x1a, 0x6b, 0x38, 0x8a, 0x35, 0x38, 0xb1, 0x86, 0xef, 0x50, 0xf8, 0x37, 0xf8, 0x81, 0x0a, 0x14, 0xfe, 0xb1, 0x4f, 0xae, 0xb0, 0xa1, 0xd4, 0x4b, 0xa8, 0xf4, 0x12, 0x2a, 0xbd, 0x64, 0x8d, 0x17, 0x6d, 0x8d, 0x51, 0x75, 0x31, 0x9a, 0xe5, 0xc4, 0x28, 0x26, 0xeb, 0x6a, 0x8e, 0xdd, 0x25, 0x16, 0x58, 0xd7, 0x08, 0x9b, 0x75, 0x9d, 0xf0, 0xf7, 0x78, 0x56, 0xa1, 0xfd, 0x96, 0xd9, 0x4f, 0xd8, 0xe2, 0xcf, 0xd8, 0x5f, 0x06, 0xf6, 0x76, 0x5c, 0x9e, 0xa4, 0x15, 0xa7, 0x69, 0xc5, 0x39, 0x5a, 0x91, 0x8b, 0x77, 0xfa, 0x85, 0x96, 0x14, 0xd1, 0x92, 0x02, 0x5a, 0x92, 0x4b, 0x4b, 0x0a, 0x68, 0x49, 0x11, 0x2d, 0xc9, 0xa5, 0x15, 0x0e, 0x5a, 0x51, 0x40, 0x2b, 0xce, 0xd3, 0x8a, 0x5c, 0x5a, 0x61, 0xa2, 0x66, 0x1f, 0x6a, 0xf2, 0xa1, 0xa6, 0xa1, 0xd4, 0x64, 0x12, 0x5f, 0xe0, 0x1d, 0x4e, 0xd0, 0xd7, 0x9f, 0xf1, 0x0c, 0x19, 0xd4, 0xe4, 0xa4, 0xa6, 0xaf, 0xa8, 0xe9, 0x63, 0x6a, 0xca, 0xc4, 0x33, 0xe4, 0x53, 0x5b, 0x3e, 0x9e, 0xa1, 0x90, 0x1a, 0x0f, 0x51, 0xe3, 0x3e, 0x3c, 0x43, 0x2e, 0x9e, 0xc1, 0x41, 0xad, 0x3f, 0x50, 0x6b, 0x35, 0xb5, 0x3a, 0xa8, 0xb5, 0x42, 0xfc, 0xca, 0x39, 0x67, 0x89, 0x0e, 0xce, 0x81, 0x43, 0x7e, 0x86, 0x87, 0xc8, 0xc0, 0x43, 0x1c, 0xa3, 0x25, 0xdf, 0x33, 0x26, 0xc5, 0xb4, 0xe6, 0x6b, 0x3c, 0xc4, 0x71, 0x72, 0x81, 0x52, 0xf2, 0x80, 0x52, 0xe2, 0xff, 0x4b, 0xc4, 0xff, 0x97, 0xf0, 0x06, 0x27, 0x68, 0xe5, 0x57, 0x78, 0x83, 0x63, 0xb4, 0xd4, 0x4d, 0x4b, 0xf7, 0x79, 0xbd, 0x2a, 0x0b, 0xbd, 0xfe, 0x0e, 0xaf, 0x71, 0xec, 0x0d, 0x78, 0x5b, 0x1e, 0xc3, 0x23, 0x5c, 0xc0, 0x23, 0x5c, 0xa0, 0x17, 0xed, 0xf1, 0x06, 0xc7, 0x7c, 0xae, 0x92, 0xb3, 0x56, 0xc9, 0x13, 0x78, 0x84, 0x13, 0x78, 0x84, 0x13, 0x78, 0x84, 0x13, 0x78, 0x84, 0x13, 0x8c, 0xf3, 0x79, 0x3c, 0xc2, 0x09, 0xc6, 0xfa, 0x3c, 0x63, 0x7d, 0x1e, 0x8f, 0x70, 0x02, 0x8f, 0x70, 0x02, 0x6b, 0x3f, 0x86, 0xb5, 0x3b, 0xb0, 0xf6, 0x63, 0x58, 0xfb, 0x31, 0xac, 0xfd, 0x18, 0xd6, 0x7e, 0x0c, 0x6b, 0x27, 0x26, 0x87, 0x54, 0x48, 0x83, 0x95, 0xb0, 0x4a, 0x1e, 0xc3, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x1f, 0x6b, 0xcf, 0xc7, 0xda, 0xf3, 0xb1, 0xf6, 0x7c, 0xac, 0x3d, 0x1f, 0x6b, 0xcf, 0xc7, 0xda, 0xf3, 0xb1, 0xf6, 0x7c, 0xac, 0x3d, 0x1f, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0xcf, 0x45, 0x19, 0x9d, 0xb0, 0xf6, 0x5c, 0x74, 0x59, 0x89, 0x2e, 0x73, 0xb1, 0xf6, 0x5c, 0xac, 0x3d, 0x17, 0x7d, 0x56, 0xa2, 0x4f, 0x33, 0xd6, 0x9e, 0x8b, 0x72, 0x3a, 0xa1, 0x9c, 0x48, 0x94, 0xd3, 0x1e, 0x6b, 0xcf, 0xc5, 0xda, 0x73, 0xd1, 0x6a, 0x20, 0x5a, 0x95, 0x68, 0x35, 0xd0, 0x7a, 0x2f, 0x65, 0x6d, 0x84, 0x4d, 0xf4, 0x6b, 0x33, 0xdc, 0x07, 0xf7, 0xc3, 0x16, 0x78, 0x00, 0xd2, 0xe1, 0x41, 0xd8, 0x0a, 0x0f, 0xc1, 0xc3, 0xb0, 0x0d, 0xb6, 0xc3, 0x23, 0xf0, 0x28, 0x3c, 0x06, 0x8f, 0xc3, 0x13, 0xb0, 0x03, 0x76, 0xc9, 0xe3, 0x58, 0xfd, 0x71, 0xac, 0xfe, 0x38, 0x56, 0x7f, 0x1c, 0xab, 0x3f, 0x8e, 0xd5, 0x1f, 0xc7, 0xea, 0x8f, 0x63, 0xf5, 0xc7, 0xb1, 0xfa, 0xe3, 0x58, 0xfd, 0x71, 0xac, 0xfe, 0x38, 0x56, 0x7f, 0x1c, 0xab, 0x3f, 0x8e, 0xd5, 0x1f, 0xc7, 0xea, 0x8f, 0x63, 0xf5, 0xc7, 0xb1, 0xfa, 0xe3, 0x58, 0xfd, 0x71, 0xac, 0xfe, 0x38, 0x56, 0x7f, 0x1c, 0xab, 0x3f, 0x8e, 0xd5, 0x1f, 0xc7, 0xea, 0x8f, 0x63, 0xf5, 0xc7, 0xb1, 0xfa, 0x13, 0xc4, 0xd0, 0x97, 0xfc, 0x1e, 0x63, 0xb5, 0x5b, 0x45, 0x44, 0x90, 0x4b, 0x44, 0x90, 0x85, 0x07, 0xc8, 0x24, 0x22, 0x38, 0x86, 0x45, 0x54, 0x60, 0x09, 0x97, 0xb0, 0x84, 0x4a, 0x2c, 0xa1, 0x12, 0xf5, 0xbb, 0xb1, 0xb9, 0x67, 0xb0, 0x80, 0x4b, 0xa8, 0xff, 0x12, 0xea, 0xbf, 0x64, 0xa8, 0xbf, 0x04, 0xc5, 0xbb, 0x51, 0xb9, 0x9b, 0xa8, 0xe0, 0x08, 0x19, 0xf0, 0x11, 0x32, 0xe0, 0x23, 0x64, 0xc0, 0x39, 0x64, 0xc0, 0x39, 0xda, 0xa7, 0xbd, 0x8d, 0xa7, 0x38, 0x69, 0x51, 0x42, 0x36, 0x4a, 0x2f, 0x46, 0xe5, 0x25, 0xa8, 0xbc, 0xf6, 0x7b, 0x86, 0x3f, 0x12, 0x29, 0xe4, 0xb0, 0xd6, 0xb9, 0x51, 0x76, 0x25, 0x11, 0x43, 0x16, 0x11, 0x43, 0x16, 0x11, 0x43, 0x56, 0x9d, 0x88, 0x21, 0x8b, 0x88, 0x21, 0xab, 0x4e, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x55, 0x27, 0x62, 0xc8, 0x22, 0x62, 0xc8, 0xaa, 0x13, 0x31, 0x64, 0xb1, 0x36, 0x7e, 0x4e, 0xc4, 0xf0, 0x19, 0xeb, 0xe3, 0x3f, 0xf4, 0xa8, 0x61, 0x13, 0xdb, 0xba, 0x91, 0xc3, 0x76, 0x5e, 0x3f, 0x02, 0x3b, 0x64, 0x0e, 0x16, 0x73, 0x09, 0x85, 0x56, 0xa2, 0xd0, 0x4a, 0x14, 0x5a, 0x89, 0x42, 0x2b, 0x51, 0x68, 0x25, 0x0a, 0xad, 0x44, 0xa1, 0x95, 0x28, 0xb4, 0x12, 0x85, 0x56, 0xa2, 0x20, 0x37, 0xeb, 0x85, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x9b, 0xf5, 0xc2, 0x8d, 0x82, 0xdc, 0xac, 0x17, 0x6e, 0xd6, 0x0b, 0x37, 0xeb, 0x85, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x9b, 0xf5, 0xc2, 0x8d, 0x82, 0xdc, 0xac, 0x17, 0x6e, 0x14, 0x54, 0x82, 0x82, 0x4a, 0x50, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x9b, 0x99, 0xad, 0x14, 0x9f, 0x30, 0xb3, 0x7b, 0x99, 0xd9, 0x9d, 0xe2, 0x47, 0x7c, 0xf6, 0x31, 0x79, 0x98, 0x99, 0x3d, 0x87, 0xaf, 0xd3, 0x3c, 0xea, 0x21, 0x66, 0x37, 0x4b, 0xf7, 0x73, 0xd9, 0xf8, 0xa9, 0x1c, 0x62, 0xbe, 0x13, 0x78, 0xd1, 0x93, 0xf8, 0xc5, 0x53, 0xf8, 0xfb, 0xd3, 0xe0, 0x99, 0xe1, 0x8f, 0xf0, 0x6f, 0x47, 0xf0, 0x6f, 0x47, 0x98, 0xe9, 0x22, 0x7c, 0xdb, 0x11, 0x7c, 0xdb, 0x21, 0x66, 0xbc, 0x18, 0xbf, 0x96, 0xc9, 0xac, 0xdf, 0xce, 0xac, 0x4f, 0x62, 0xd6, 0x27, 0x31, 0xeb, 0xf3, 0x98, 0xf5, 0x79, 0xcc, 0xfa, 0xdf, 0xf1, 0x73, 0x4e, 0xfc, 0x9c, 0x13, 0x1f, 0xe7, 0xc0, 0xc7, 0x1d, 0x61, 0xe6, 0x2b, 0xf1, 0x71, 0x55, 0xcc, 0xfe, 0x47, 0xcc, 0xfe, 0x47, 0xf8, 0xb8, 0x23, 0xf8, 0xb8, 0x23, 0xf8, 0xb8, 0x23, 0x44, 0x3f, 0x65, 0x44, 0x3f, 0x65, 0x86, 0x8f, 0x3b, 0x82, 0x0a, 0x76, 0xa2, 0x82, 0x9d, 0xa8, 0x60, 0x27, 0x2a, 0xc8, 0x46, 0x05, 0xd9, 0xa8, 0x60, 0x27, 0x2a, 0xd8, 0x89, 0x0a, 0xb2, 0x51, 0x41, 0x36, 0x2a, 0xd8, 0x89, 0x0a, 0x76, 0xa2, 0x82, 0x9d, 0xa8, 0x60, 0x27, 0x2a, 0xd8, 0x89, 0x0a, 0x76, 0xa2, 0x82, 0x9d, 0xa8, 0x20, 0x1b, 0x15, 0x64, 0xa3, 0x82, 0x9d, 0xa8, 0x60, 0x27, 0x2a, 0xc8, 0x46, 0x05, 0xd9, 0xa8, 0x60, 0x27, 0xfe, 0xf0, 0x08, 0xb3, 0x7b, 0x16, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0xc4, 0xda, 0x42, 0x5e, 0xb1, 0x06, 0x83, 0x0d, 0xda, 0x41, 0x7b, 0xe8, 0x00, 0x1d, 0xa1, 0x2b, 0xf4, 0x94, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0x51, 0x63, 0x16, 0x6a, 0xcc, 0x42, 0x8d, 0x59, 0xa8, 0x31, 0x0b, 0x35, 0x66, 0xa1, 0xc6, 0x2c, 0xd4, 0x98, 0x85, 0x1a, 0xb3, 0x50, 0x63, 0x16, 0x6a, 0x2c, 0x30, 0xfc, 0x59, 0x81, 0xe1, 0xcf, 0x0a, 0x50, 0x63, 0x01, 0x6a, 0x2c, 0xa8, 0xe3, 0xcf, 0x0a, 0x0c, 0x7f, 0x16, 0x67, 0xf8, 0xb3, 0x02, 0xd4, 0x58, 0xe0, 0xf1, 0x67, 0xbc, 0x7f, 0x8f, 0x50, 0x51, 0xe3, 0x47, 0xa8, 0xf1, 0x23, 0xd4, 0x58, 0x84, 0x1a, 0x8b, 0x50, 0x63, 0x11, 0x6a, 0x2c, 0x42, 0x8d, 0x45, 0xa8, 0xb1, 0x08, 0x35, 0x16, 0xa1, 0xc6, 0x22, 0xd4, 0x58, 0x84, 0x1a, 0x8b, 0x50, 0x63, 0x11, 0x6a, 0x2c, 0x42, 0x8d, 0x45, 0xa8, 0xb1, 0x08, 0x35, 0x16, 0xa1, 0xc6, 0x22, 0xd4, 0x58, 0x84, 0x1a, 0x8b, 0x50, 0x63, 0x11, 0xfe, 0x2c, 0x13, 0x7f, 0x96, 0x89, 0x3f, 0xcb, 0xc4, 0x9f, 0x65, 0xe2, 0xcf, 0x32, 0xf1, 0x67, 0x99, 0xf8, 0xb3, 0x4c, 0xfc, 0x59, 0x26, 0xfe, 0x2c, 0x13, 0x7f, 0x96, 0x89, 0x3f, 0xcb, 0xc4, 0x9f, 0x65, 0xe2, 0xcf, 0x32, 0xf1, 0x67, 0x99, 0xf8, 0xb3, 0x4c, 0xfc, 0x59, 0x26, 0xfe, 0x2c, 0x13, 0x7f, 0x96, 0x89, 0x3f, 0xcb, 0xc4, 0x9f, 0x65, 0xe2, 0xcf, 0x32, 0xf1, 0x67, 0x99, 0xf8, 0xb3, 0x4c, 0xfc, 0x59, 0x26, 0xbe, 0xcc, 0x49, 0x14, 0x73, 0x49, 0x3c, 0x8f, 0xe2, 0xdd, 0xf8, 0xb1, 0x2a, 0xe2, 0xfe, 0xab, 0x28, 0xbe, 0x14, 0xc5, 0x57, 0xa1, 0xf8, 0x0b, 0xac, 0xee, 0xe5, 0xa8, 0xfe, 0x3c, 0xaa, 0xaf, 0xd2, 0xef, 0x05, 0x64, 0xb3, 0x9f, 0xc3, 0x39, 0x5a, 0xcc, 0x7f, 0x92, 0xfd, 0x53, 0xec, 0x13, 0x73, 0xe3, 0xdf, 0xce, 0xa3, 0xfe, 0x0b, 0x28, 0xbf, 0x0c, 0xe5, 0x5f, 0x44, 0xf9, 0x17, 0x51, 0x7e, 0x09, 0xca, 0xbf, 0x88, 0xf2, 0x2f, 0xa0, 0xfc, 0xf3, 0x28, 0xbf, 0xd4, 0xf8, 0xab, 0x53, 0x09, 0x0a, 0xbf, 0x80, 0xc2, 0x2f, 0xa2, 0xf0, 0xab, 0x28, 0xfc, 0x2a, 0x0a, 0x2f, 0x43, 0xe1, 0x65, 0x28, 0xfc, 0x22, 0x0a, 0xbf, 0x88, 0xc2, 0x2f, 0x1a, 0xb1, 0xfc, 0x45, 0x6d, 0xf5, 0xc6, 0x0f, 0x7d, 0x86, 0x1f, 0xaa, 0x36, 0xfc, 0x90, 0xd3, 0xf0, 0x43, 0x4e, 0xfc, 0x90, 0x13, 0x3f, 0xe4, 0xbc, 0xee, 0x87, 0xbe, 0x96, 0x17, 0x55, 0xed, 0xdb, 0xcf, 0x0b, 0xd8, 0x2e, 0x84, 0x24, 0x48, 0x86, 0x15, 0x90, 0x02, 0xa9, 0x90, 0x06, 0x2b, 0x61, 0x95, 0xbc, 0x88, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0xac, 0x42, 0x89, 0x55, 0x28, 0xb1, 0x0a, 0x25, 0x56, 0xa1, 0xc4, 0x2a, 0x94, 0x58, 0x85, 0x12, 0xab, 0x50, 0x62, 0x15, 0x4a, 0xac, 0xfa, 0x8b, 0x71, 0xf4, 0xd5, 0x3f, 0x8c, 0xa3, 0xef, 0x85, 0x8d, 0x7a, 0x3c, 0x5d, 0x82, 0x12, 0x4b, 0x50, 0x62, 0x09, 0x4a, 0x2c, 0x41, 0x89, 0x25, 0x28, 0xb1, 0x04, 0x25, 0x96, 0xa0, 0xc4, 0x12, 0x94, 0x58, 0x82, 0x12, 0x4b, 0x50, 0x62, 0x09, 0x4a, 0x2c, 0x41, 0x89, 0x25, 0x28, 0xb1, 0x04, 0x25, 0x96, 0xa0, 0xc4, 0x12, 0x94, 0x58, 0x82, 0x12, 0x4b, 0x50, 0x62, 0x09, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0xa9, 0xbe, 0xa2, 0xfa, 0xa3, 0xb6, 0x32, 0x14, 0x76, 0x59, 0x57, 0x58, 0x2e, 0x9e, 0xf8, 0xb4, 0xbc, 0x46, 0x7c, 0x78, 0x19, 0xf5, 0x5c, 0x53, 0xc2, 0x58, 0x19, 0x89, 0xa5, 0xf4, 0xbf, 0x1e, 0x7b, 0xee, 0x7f, 0x7f, 0x4f, 0xa6, 0x98, 0xab, 0xa9, 0x87, 0xc8, 0x37, 0x83, 0xf5, 0x37, 0x07, 0x9d, 0x9d, 0x24, 0x4b, 0xcf, 0x95, 0xbf, 0xe2, 0x85, 0x2f, 0x72, 0xe5, 0x09, 0xa2, 0xc9, 0xc3, 0xfa, 0xe7, 0x8e, 0x3c, 0x59, 0xb7, 0xf6, 0x54, 0xc4, 0x9f, 0xd0, 0x5c, 0x29, 0x57, 0x5d, 0x40, 0x57, 0x85, 0x58, 0x74, 0x2f, 0xea, 0x2e, 0x14, 0x01, 0x94, 0xf0, 0x2d, 0x25, 0x68, 0x2b, 0x76, 0x26, 0x25, 0x9c, 0xa3, 0x84, 0x22, 0x4a, 0xc8, 0xd0, 0x4b, 0xf0, 0xfc, 0xcd, 0xe0, 0xb8, 0x76, 0xa7, 0x9e, 0xab, 0x2f, 0x72, 0xb5, 0x1b, 0xbf, 0x6b, 0x31, 0x4a, 0x18, 0x85, 0x4f, 0xb0, 0xe8, 0xa5, 0xd8, 0xb1, 0x1d, 0x27, 0xf6, 0xf2, 0xeb, 0xf5, 0x3c, 0xf9, 0xa4, 0x7c, 0x1e, 0xdb, 0x28, 0xa7, 0x27, 0x65, 0xd8, 0x46, 0x11, 0xb6, 0x51, 0x8c, 0x6d, 0x14, 0x63, 0x17, 0xc5, 0xd8, 0x45, 0xa1, 0x7e, 0x6f, 0xac, 0xbf, 0x7c, 0x9f, 0xd5, 0xe0, 0x43, 0x56, 0x83, 0x0f, 0x59, 0x0d, 0xde, 0x61, 0x35, 0xd0, 0xbe, 0x51, 0xf3, 0x21, 0xb5, 0x1e, 0xaf, 0xfd, 0xdb, 0x06, 0xb6, 0xa2, 0xc5, 0x00, 0x45, 0xd8, 0x48, 0x11, 0x36, 0x52, 0x8c, 0x8d, 0x14, 0x63, 0x23, 0xc5, 0xd8, 0x47, 0x31, 0x9a, 0x2f, 0x46, 0xef, 0xc5, 0xe8, 0xbd, 0x18, 0xbd, 0x17, 0xa3, 0xf7, 0x62, 0xf4, 0x5e, 0x8c, 0xde, 0x8b, 0xd1, 0x7b, 0x31, 0x7a, 0x2f, 0x46, 0xef, 0xc5, 0xe8, 0xbd, 0x18, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0xf3, 0x4f, 0xe5, 0xa5, 0xf7, 0xe2, 0xe1, 0x36, 0xea, 0x5e, 0xee, 0xff, 0xea, 0x3d, 0x1c, 0x11, 0xca, 0xdc, 0xbe, 0x43, 0x5e, 0x52, 0xa1, 0xaf, 0xd7, 0x27, 0x65, 0x0e, 0xf3, 0xfb, 0xb3, 0x91, 0xfd, 0x1c, 0x64, 0x7e, 0x0f, 0xa2, 0xad, 0x52, 0xb4, 0x55, 0xaa, 0x0c, 0x21, 0xf2, 0x1a, 0x06, 0xe3, 0xe5, 0x79, 0x46, 0xbf, 0x94, 0xd1, 0xcf, 0x60, 0xf4, 0xcf, 0xa3, 0xb3, 0x6f, 0x95, 0x85, 0xb2, 0x52, 0x59, 0xc4, 0x7a, 0xbc, 0x5f, 0xbf, 0x33, 0x59, 0xc5, 0xdc, 0x17, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x18, 0xea, 0x2c, 0x10, 0x41, 0xf8, 0xe2, 0x4a, 0x7a, 0xe1, 0x42, 0x5b, 0x4e, 0x74, 0x55, 0xac, 0xdf, 0x6d, 0xd2, 0xac, 0xe4, 0x24, 0xd6, 0x91, 0x0b, 0xb5, 0x77, 0x9c, 0x0a, 0x65, 0x81, 0xf1, 0x69, 0x3a, 0xed, 0xf3, 0x76, 0x9f, 0xd7, 0xf9, 0x86, 0x8d, 0x66, 0x2d, 0x15, 0xf8, 0xdd, 0x4a, 0xdd, 0x62, 0x5e, 0x46, 0xa9, 0x59, 0x94, 0xfa, 0x1d, 0xa5, 0xbe, 0x4d, 0xa9, 0xa7, 0x75, 0xc5, 0xfe, 0x42, 0x8e, 0xe6, 0xc9, 0xdd, 0xde, 0xc4, 0xc3, 0x5f, 0xa1, 0xe4, 0xcf, 0x28, 0xf5, 0x1c, 0xaa, 0x3d, 0x84, 0x6a, 0x0f, 0x31, 0x5e, 0x6f, 0xe2, 0xd5, 0xf3, 0x50, 0xef, 0x21, 0x6a, 0xfa, 0x0c, 0xbb, 0x3c, 0x8c, 0x82, 0x73, 0xc8, 0xd3, 0x9e, 0x55, 0x50, 0x02, 0xb9, 0xda, 0xb3, 0x0a, 0x4a, 0x50, 0xc6, 0x08, 0x3f, 0x62, 0x99, 0x47, 0x89, 0x65, 0x1e, 0x55, 0x66, 0x8b, 0xfa, 0xd4, 0xfa, 0x1d, 0x0a, 0x3e, 0x44, 0xcd, 0x6f, 0xa2, 0xde, 0x43, 0xa8, 0xf7, 0x10, 0xea, 0x3d, 0x84, 0x0d, 0xa5, 0xa0, 0xe0, 0x43, 0x28, 0xf8, 0x10, 0x0a, 0x3e, 0x84, 0x82, 0x0f, 0xa1, 0xe0, 0x43, 0x28, 0xf8, 0x10, 0x0a, 0x3e, 0x84, 0x82, 0x0f, 0xa1, 0xe0, 0x43, 0x28, 0xf8, 0x10, 0x0a, 0x3e, 0x84, 0x82, 0x0f, 0xa1, 0xe0, 0x2c, 0x14, 0x9c, 0x85, 0x82, 0xb3, 0x50, 0x70, 0x16, 0x0a, 0xce, 0x42, 0xc1, 0x59, 0x28, 0x38, 0x0b, 0x05, 0x67, 0xa1, 0xe0, 0x2c, 0x14, 0x9c, 0x85, 0x82, 0xb3, 0x50, 0x70, 0x16, 0x0a, 0xce, 0x42, 0xc1, 0x59, 0x28, 0x38, 0x0b, 0x05, 0x67, 0xa1, 0xe0, 0x2c, 0x14, 0x9c, 0x85, 0x82, 0xb3, 0x50, 0x70, 0x16, 0x0a, 0xce, 0x42, 0xc1, 0x59, 0x28, 0x38, 0x0b, 0x05, 0x67, 0xa1, 0xe0, 0x2c, 0x14, 0x9c, 0xf5, 0x27, 0xef, 0xac, 0x54, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0xfc, 0x57, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0xfc, 0x57, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0xfc, 0x57, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0xfc, 0x57, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0xfc, 0x57, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0x8a, 0x70, 0x85, 0xd8, 0x64, 0x09, 0x2b, 0xc2, 0x15, 0x6b, 0xb2, 0x08, 0xb7, 0xae, 0x10, 0xd3, 0x58, 0x11, 0xae, 0xb0, 0x22, 0x5c, 0xb1, 0xa6, 0x89, 0x31, 0xc4, 0x26, 0x73, 0x59, 0x11, 0xae, 0x10, 0x9b, 0x2c, 0x41, 0x2b, 0xff, 0xc2, 0x0f, 0xa5, 0xb0, 0x22, 0x5c, 0x61, 0x45, 0xb8, 0x42, 0x6c, 0x32, 0x9d, 0xd8, 0x64, 0x16, 0xb1, 0xc9, 0x52, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0x62, 0x92, 0x47, 0x85, 0x05, 0x55, 0x9e, 0x42, 0x7d, 0x4e, 0x14, 0x57, 0x88, 0x85, 0x5e, 0x34, 0x3e, 0x9b, 0xf5, 0xa3, 0xfe, 0xd9, 0xac, 0xd9, 0x78, 0x68, 0x33, 0xca, 0xff, 0x18, 0x2f, 0xf9, 0x2b, 0x47, 0x0a, 0x51, 0xf9, 0x2f, 0x46, 0xb6, 0x74, 0x9c, 0x71, 0x68, 0x2d, 0x62, 0x51, 0x77, 0x3e, 0xaa, 0xce, 0x31, 0xfc, 0xf0, 0x69, 0xce, 0x7e, 0xee, 0x7a, 0x0e, 0xf6, 0xab, 0x3c, 0x83, 0xa2, 0x0b, 0x50, 0x74, 0x01, 0x4a, 0x2e, 0x40, 0xc9, 0xe7, 0x50, 0x71, 0x25, 0x7e, 0xf8, 0x1b, 0xfc, 0xf0, 0x61, 0xfc, 0xf0, 0xe1, 0xeb, 0x4f, 0x7a, 0x1a, 0xa1, 0xab, 0x1a, 0x45, 0xcb, 0x0f, 0xf4, 0xd8, 0xc5, 0xf3, 0xd7, 0xd7, 0x5a, 0x9f, 0x5c, 0x80, 0x4f, 0x3e, 0x83, 0x4f, 0x3e, 0x83, 0xaa, 0x0b, 0x50, 0x75, 0x01, 0xaa, 0x2e, 0x40, 0xd1, 0x05, 0x28, 0xba, 0x00, 0x45, 0x17, 0xa0, 0xe8, 0x02, 0x14, 0x5d, 0x80, 0xa2, 0x0b, 0x50, 0x74, 0x01, 0x8a, 0x2e, 0x40, 0xd1, 0x05, 0x28, 0xba, 0x00, 0x45, 0x17, 0xa8, 0x5a, 0x64, 0xd9, 0x93, 0xac, 0x3d, 0x14, 0x7a, 0x41, 0x6f, 0xe8, 0x03, 0x7d, 0x21, 0x0c, 0xfa, 0x41, 0x7f, 0x18, 0x00, 0x03, 0x21, 0x1c, 0x06, 0xc1, 0x60, 0xc0, 0x5b, 0xa1, 0xe8, 0x7c, 0x14, 0x9d, 0x8f, 0xa2, 0xf3, 0x51, 0x74, 0x3e, 0x8a, 0xce, 0x47, 0xd1, 0xf9, 0x28, 0x3a, 0x1f, 0x45, 0xe7, 0xa3, 0xe8, 0x7c, 0x14, 0x9d, 0x8f, 0xa2, 0xf3, 0xff, 0xa4, 0x4f, 0x3e, 0x83, 0x4f, 0x3e, 0xc3, 0x2c, 0x57, 0x32, 0xcb, 0x95, 0xcc, 0x72, 0x25, 0xb3, 0x5c, 0xc9, 0x2c, 0x57, 0x32, 0xcb, 0x95, 0xcc, 0x72, 0x25, 0xb3, 0x5c, 0xc9, 0x2c, 0xff, 0x2f, 0xee, 0xee, 0x03, 0xbe, 0xa9, 0xba, 0xff, 0xfb, 0xff, 0x37, 0x27, 0x2d, 0x39, 0x27, 0x75, 0x31, 0xc4, 0xc5, 0x90, 0x21, 0x32, 0x04, 0x04, 0x71, 0x82, 0xca, 0x12, 0x15, 0xd9, 0x4b, 0x50, 0x04, 0xdc, 0x28, 0x8a, 0x38, 0x58, 0x0a, 0x88, 0xc8, 0x12, 0x04, 0x2a, 0x22, 0x7b, 0x8f, 0xb6, 0x4c, 0x15, 0x90, 0x55, 0xca, 0x6a, 0xe9, 0x4a, 0x9b, 0xae, 0xb4, 0xa5, 0x05, 0x52, 0x04, 0x12, 0xda, 0x92, 0x6e, 0x4a, 0x0b, 0x04, 0x3f, 0xf7, 0x2b, 0x69, 0x2f, 0xe5, 0xfa, 0x5d, 0xd7, 0x7d, 0xdd, 0xd7, 0x7d, 0xff, 0x7f, 0xf7, 0xf5, 0x7f, 0xfc, 0xff, 0x0f, 0x1f, 0x4f, 0xcf, 0x49, 0x9a, 0x9c, 0x9c, 0x7c, 0xf7, 0x3b, 0x09, 0x49, 0x39, 0xb5, 0x5c, 0x4e, 0x2d, 0x97, 0x53, 0xcb, 0xe5, 0xd4, 0x72, 0x39, 0xb5, 0x5c, 0x4e, 0x2d, 0x97, 0x53, 0xcb, 0xe5, 0xd4, 0x72, 0x39, 0xb5, 0x5c, 0x4e, 0x2d, 0x97, 0x53, 0xcb, 0xe5, 0xd4, 0x72, 0x39, 0xb5, 0x5c, 0x4e, 0x2d, 0x97, 0x53, 0xcb, 0xe5, 0x6a, 0x35, 0x35, 0xe4, 0x61, 0xfc, 0x39, 0x43, 0x4d, 0x4f, 0xa7, 0xa6, 0x0a, 0xa8, 0xa9, 0xca, 0xea, 0x57, 0x96, 0x2b, 0x18, 0x83, 0x12, 0x18, 0x83, 0x8e, 0xd2, 0x02, 0x56, 0x53, 0x6b, 0x39, 0xd4, 0xda, 0x39, 0x6a, 0x2c, 0x97, 0x1a, 0xcb, 0xa5, 0x45, 0xac, 0xad, 0x7e, 0x97, 0x29, 0xb7, 0xfa, 0x15, 0x65, 0xdf, 0xeb, 0x45, 0x19, 0xd4, 0x60, 0x51, 0xf5, 0xbf, 0x54, 0x8c, 0x64, 0x1c, 0x5f, 0xc4, 0x38, 0xbe, 0x88, 0xda, 0x4b, 0xa0, 0xf6, 0x7c, 0xef, 0xb4, 0x8f, 0x63, 0x3c, 0x3a, 0xc9, 0x78, 0x74, 0x92, 0xda, 0x4b, 0xf6, 0xbf, 0x77, 0x3e, 0x44, 0xe6, 0x33, 0xa6, 0x2f, 0x62, 0x4c, 0x5f, 0xc4, 0xf8, 0x74, 0x86, 0xda, 0xcc, 0x65, 0x7c, 0x3a, 0x4a, 0x4d, 0xe6, 0x52, 0x93, 0xb9, 0xd4, 0x64, 0x2e, 0x35, 0x99, 0x4b, 0x4d, 0xe6, 0x52, 0x93, 0xb9, 0xd4, 0x64, 0x2e, 0x35, 0x99, 0x4b, 0x4d, 0xe6, 0x52, 0x93, 0xb9, 0xd4, 0x64, 0x2e, 0x35, 0x99, 0x4b, 0x4d, 0xe6, 0x52, 0x93, 0xb9, 0xd4, 0x64, 0x2e, 0x35, 0xe9, 0xa1, 0x26, 0x3d, 0xd4, 0xa4, 0x87, 0x9a, 0xf4, 0x50, 0x93, 0x1e, 0x6a, 0xd2, 0x43, 0x4d, 0x7a, 0xa8, 0x49, 0x0f, 0x35, 0xe9, 0xa1, 0x26, 0x3d, 0xd4, 0xa4, 0x87, 0x9a, 0xf4, 0x50, 0x93, 0x1e, 0x6a, 0xd2, 0x43, 0x4d, 0x7a, 0xa8, 0x49, 0x0f, 0x35, 0xe9, 0xa1, 0x26, 0x3d, 0xd4, 0xa4, 0x87, 0x9a, 0xf4, 0x50, 0x93, 0x1e, 0x6a, 0xd2, 0x43, 0x4d, 0x7a, 0xa8, 0x49, 0x0f, 0x35, 0xe9, 0xa1, 0x26, 0x3d, 0xd4, 0x64, 0x25, 0x35, 0x59, 0x49, 0x4d, 0x56, 0x52, 0x93, 0x95, 0xd4, 0x64, 0x25, 0x35, 0x59, 0x49, 0x4d, 0x56, 0x52, 0x93, 0x95, 0xd4, 0x64, 0x25, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0xc5, 0x7f, 0xf3, 0xbb, 0x38, 0x45, 0xb4, 0xb6, 0x22, 0x5a, 0x5b, 0x11, 0xad, 0xad, 0x88, 0xd6, 0x56, 0x44, 0x6b, 0x2b, 0xa2, 0xb5, 0x15, 0xd1, 0xda, 0x8a, 0x68, 0x6d, 0x45, 0xb4, 0xb6, 0x22, 0x5a, 0x5b, 0x11, 0xad, 0xad, 0x88, 0xd6, 0x56, 0x44, 0x6b, 0x2b, 0xa2, 0xb5, 0x15, 0xd1, 0xda, 0x8a, 0x68, 0x6d, 0x45, 0xb4, 0xb6, 0x22, 0x5a, 0x5b, 0x11, 0xad, 0xad, 0x88, 0xd6, 0x56, 0x44, 0x6b, 0x2b, 0xa2, 0xb5, 0x15, 0xd1, 0xda, 0x8a, 0x18, 0x53, 0x4e, 0xfa, 0xe7, 0xd0, 0x4a, 0x5a, 0x9b, 0x93, 0xd6, 0x76, 0x9d, 0x96, 0x76, 0x95, 0x56, 0x56, 0xe1, 0x5f, 0x33, 0x3a, 0x69, 0x61, 0x55, 0x6b, 0xb4, 0x32, 0x5a, 0xd7, 0x39, 0x5a, 0x95, 0x6f, 0xc5, 0x59, 0x41, 0x6b, 0x72, 0x54, 0x7f, 0x0a, 0x72, 0x29, 0xad, 0x26, 0x9e, 0x56, 0x63, 0xa3, 0xd5, 0x24, 0xd1, 0x5a, 0x7c, 0x99, 0xa5, 0x40, 0xf5, 0xa2, 0x0d, 0x17, 0x70, 0x34, 0x17, 0x6d, 0xb7, 0x8c, 0x23, 0xe6, 0x32, 0xca, 0x6c, 0xe1, 0x48, 0x57, 0x38, 0x52, 0x29, 0xa3, 0xcc, 0xe5, 0x3f, 0x93, 0x50, 0x55, 0x0a, 0xba, 0xcc, 0x08, 0x73, 0xd8, 0xe4, 0xfb, 0xcc, 0x62, 0x47, 0x74, 0x96, 0x83, 0x8c, 0x30, 0x07, 0xab, 0x57, 0x7a, 0xbe, 0x4f, 0x98, 0xc4, 0xf2, 0x08, 0x49, 0xfe, 0x54, 0xb4, 0x8d, 0xd5, 0xdd, 0x76, 0xdc, 0x9a, 0x86, 0x48, 0x42, 0xfe, 0x74, 0xf3, 0xef, 0x27, 0x9b, 0x02, 0xda, 0x62, 0x01, 0x6d, 0xb1, 0x80, 0xb6, 0x58, 0x40, 0x5b, 0x2c, 0xa0, 0x2d, 0x16, 0xd0, 0x16, 0x0b, 0x68, 0x8b, 0x05, 0xb4, 0xc5, 0x02, 0xda, 0x62, 0x01, 0x6d, 0xb1, 0x80, 0xb6, 0x58, 0x40, 0x5b, 0x2c, 0xa0, 0x2d, 0x16, 0xd0, 0x16, 0x0b, 0x68, 0x8b, 0x05, 0xb4, 0xc5, 0x02, 0xda, 0x62, 0x01, 0x6d, 0xb1, 0x80, 0xb6, 0x58, 0x40, 0x5b, 0x2c, 0xa0, 0x2d, 0x16, 0xd0, 0x16, 0x0b, 0x68, 0x8b, 0x05, 0xb4, 0xc5, 0x02, 0xda, 0x62, 0x01, 0x6d, 0xb1, 0x8c, 0xb6, 0x58, 0x46, 0x5b, 0x2c, 0xa3, 0x2d, 0x96, 0xd1, 0x16, 0xcb, 0x68, 0x8b, 0x65, 0xb4, 0xc5, 0x32, 0xda, 0x62, 0x19, 0x6d, 0xb1, 0x8c, 0x51, 0xe5, 0xb2, 0xd5, 0xf7, 0x99, 0xc4, 0x6e, 0x94, 0x98, 0xd7, 0xbf, 0xca, 0x4f, 0xf7, 0xf7, 0xf6, 0x6b, 0x94, 0x7d, 0x85, 0xbf, 0xb4, 0x2e, 0xb0, 0xd2, 0xbf, 0xe8, 0x5f, 0xed, 0xdf, 0xa0, 0xb4, 0x6e, 0x52, 0x5a, 0x37, 0xe8, 0xbd, 0x1e, 0x7a, 0xaf, 0x87, 0x12, 0xb9, 0x49, 0x49, 0xdc, 0xa4, 0x24, 0x6e, 0x52, 0x12, 0x37, 0x59, 0x35, 0x8c, 0xa4, 0x34, 0x6e, 0x52, 0x1a, 0x37, 0x29, 0x8d, 0x9b, 0x94, 0xc6, 0x4d, 0x4a, 0xe3, 0x26, 0xa5, 0x71, 0x93, 0xd2, 0xb8, 0x49, 0x69, 0xdc, 0xa4, 0x34, 0x6e, 0x52, 0x1a, 0x37, 0x29, 0x8d, 0x9b, 0x94, 0xc6, 0x4d, 0x4a, 0xc3, 0x4b, 0x69, 0x78, 0x29, 0x0d, 0x2f, 0xa5, 0xe1, 0xa5, 0x34, 0xbc, 0x94, 0x86, 0x97, 0xd2, 0xf0, 0x52, 0x1a, 0x5e, 0x4a, 0xc3, 0x4b, 0x69, 0x78, 0x29, 0x0d, 0x2f, 0xa5, 0xe1, 0xa5, 0x34, 0xbc, 0x94, 0x86, 0x97, 0xd2, 0xf0, 0x52, 0x1a, 0x5e, 0x4a, 0xc3, 0x4b, 0x69, 0x78, 0x29, 0x0d, 0x2f, 0xa5, 0xe1, 0xa5, 0x34, 0xbc, 0x94, 0x86, 0x97, 0xd2, 0xf0, 0x52, 0x1a, 0x5e, 0x4a, 0xc3, 0x4b, 0x69, 0x78, 0x99, 0xad, 0xdf, 0x61, 0xa6, 0x6e, 0xc7, 0x4c, 0xdd, 0x91, 0x19, 0xba, 0x1d, 0x33, 0x74, 0x37, 0x66, 0xe7, 0x77, 0x98, 0x95, 0x56, 0x31, 0x3b, 0x8f, 0x64, 0x56, 0xee, 0xc2, 0xac, 0xfc, 0x0c, 0xb3, 0x72, 0x2f, 0xf5, 0x91, 0xff, 0x5d, 0xf7, 0xaa, 0x95, 0x9d, 0xd3, 0xbf, 0xb2, 0xcb, 0xf0, 0xb7, 0x4c, 0xdf, 0x7b, 0x89, 0x65, 0xb4, 0xcc, 0x72, 0x4a, 0x27, 0x97, 0xf6, 0xe4, 0xad, 0x5e, 0x83, 0x55, 0xb5, 0x25, 0xd7, 0x9f, 0xed, 0xc9, 0xf7, 0xde, 0x41, 0x05, 0xe3, 0x5e, 0x09, 0xad, 0x32, 0xdf, 0xbf, 0xb2, 0xdb, 0xe2, 0xcf, 0x26, 0xe5, 0x7f, 0x9f, 0xa2, 0xd5, 0xb0, 0xff, 0x83, 0xb6, 0xf3, 0x9f, 0x7a, 0x57, 0xfb, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x25, 0xfa, 0x06, 0x25, 0xda, 0x92, 0x12, 0x7d, 0x9c, 0x12, 0x6d, 0x49, 0x89, 0x76, 0xa2, 0x44, 0xdf, 0xa0, 0x44, 0x7f, 0xa2, 0x44, 0x87, 0x51, 0xa2, 0x1d, 0x29, 0xd1, 0x0e, 0x94, 0x68, 0x77, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0x35, 0xd8, 0xff, 0x9e, 0x68, 0x3a, 0xb3, 0x4e, 0x55, 0xb6, 0xf3, 0xad, 0xbb, 0xcb, 0xfd, 0xd9, 0xee, 0x6f, 0xb3, 0x53, 0xd5, 0xac, 0x74, 0xc1, 0x5f, 0x2b, 0x5d, 0xe5, 0x34, 0xed, 0xf7, 0x34, 0xe3, 0x87, 0x8b, 0xd9, 0xe7, 0x2a, 0xb3, 0xcf, 0xd5, 0xbf, 0xad, 0x52, 0xfc, 0xb3, 0xcd, 0xff, 0xb3, 0x99, 0xe6, 0x3f, 0xf3, 0xfe, 0xe2, 0xff, 0xe5, 0x12, 0x65, 0x64, 0xbe, 0xaa, 0x6e, 0xa3, 0x24, 0x4f, 0x2a, 0x97, 0xba, 0x93, 0xf1, 0xf1, 0x92, 0xc9, 0xf7, 0x39, 0x3a, 0x56, 0x38, 0x8c, 0x8f, 0x97, 0x18, 0x1f, 0x2f, 0x31, 0x3e, 0xa6, 0xdf, 0xf2, 0x9b, 0x06, 0x69, 0xbe, 0x3c, 0xe1, 0x5f, 0xdf, 0x39, 0xa8, 0x87, 0xa8, 0xea, 0x91, 0xd7, 0xee, 0x3f, 0x42, 0xd5, 0xfa, 0xce, 0xf7, 0x1a, 0x94, 0x6f, 0xb5, 0x90, 0x49, 0x7d, 0x64, 0x52, 0x1f, 0x99, 0xd4, 0x47, 0xb2, 0xff, 0xfd, 0xe6, 0x27, 0xa9, 0x93, 0xce, 0xd4, 0x45, 0x17, 0x74, 0xa5, 0x1e, 0xba, 0xa3, 0x87, 0x5c, 0xa4, 0x6e, 0xb2, 0xa8, 0x9b, 0x2c, 0x1e, 0xc5, 0x53, 0xfd, 0xa9, 0x3d, 0x5f, 0xfd, 0x64, 0x32, 0x0a, 0x9f, 0x63, 0x14, 0x3e, 0x47, 0x3d, 0x65, 0x52, 0x4f, 0x99, 0xd4, 0x53, 0x26, 0xf5, 0x94, 0x49, 0x3d, 0x65, 0x52, 0x4f, 0x99, 0xd4, 0x53, 0x26, 0xf5, 0x94, 0x49, 0x3d, 0x65, 0x52, 0x4f, 0x99, 0xd4, 0x53, 0x26, 0xf5, 0x94, 0x49, 0x3d, 0x65, 0x52, 0x4f, 0x99, 0xd4, 0x53, 0xe6, 0x7f, 0xf0, 0x9d, 0xa1, 0x7f, 0x67, 0x14, 0x3e, 0xc7, 0x28, 0x7c, 0xee, 0xff, 0xfa, 0x7b, 0xa3, 0x67, 0x25, 0x8b, 0x3a, 0x75, 0x30, 0x8a, 0x9f, 0x91, 0x68, 0x72, 0xb6, 0xef, 0xb5, 0x9b, 0x53, 0xfe, 0x77, 0xfb, 0x7b, 0xf8, 0x53, 0x69, 0x14, 0x25, 0x9d, 0x58, 0xfd, 0x6f, 0x0a, 0xe2, 0x19, 0xbb, 0xae, 0x92, 0xa9, 0x6f, 0xfa, 0x5f, 0xfd, 0xb1, 0x71, 0x0f, 0x47, 0xf5, 0xab, 0x2f, 0x05, 0xd5, 0xaf, 0xdf, 0xd8, 0xff, 0xd5, 0xbf, 0xa2, 0xe0, 0x9e, 0x45, 0xea, 0x6e, 0xee, 0x19, 0xc3, 0x3d, 0x93, 0xb9, 0xe7, 0x39, 0xee, 0xe9, 0x5b, 0x23, 0xa6, 0x72, 0xcf, 0x58, 0xee, 0x79, 0x98, 0x7b, 0x86, 0xb3, 0x1e, 0x3c, 0xce, 0x7a, 0xf0, 0x78, 0xf5, 0xbf, 0x21, 0x5f, 0xcf, 0x51, 0x8e, 0x73, 0x94, 0x83, 0x1c, 0xe5, 0x28, 0x47, 0x39, 0xcc, 0x3a, 0xf0, 0x24, 0xeb, 0xc0, 0x93, 0xbe, 0x77, 0x3f, 0xc9, 0xf5, 0x79, 0x64, 0xee, 0x3c, 0xb5, 0x88, 0x36, 0x96, 0xca, 0xe8, 0xec, 0x9b, 0xab, 0x62, 0x68, 0x67, 0x19, 0xb4, 0xb3, 0x58, 0x7f, 0xee, 0x3e, 0xcd, 0x1c, 0x75, 0x86, 0xd5, 0xe9, 0x59, 0x46, 0xeb, 0x1c, 0x32, 0xc6, 0x39, 0x49, 0xa7, 0xbd, 0xd9, 0x69, 0x6f, 0x76, 0x56, 0xa6, 0xbe, 0xf9, 0xcb, 0x5e, 0xfd, 0x6a, 0xbf, 0x97, 0x76, 0xe7, 0x34, 0x75, 0x56, 0xba, 0xa9, 0x0b, 0xfe, 0xd6, 0xe6, 0x5e, 0x26, 0xcf, 0xf4, 0x94, 0x2b, 0xb4, 0xb9, 0x2b, 0xb4, 0x66, 0x83, 0x91, 0xbb, 0x84, 0xf6, 0x66, 0xf7, 0xbf, 0x16, 0xba, 0x83, 0xed, 0x2e, 0xec, 0x16, 0x7b, 0xa0, 0x53, 0x6e, 0xd0, 0xde, 0xec, 0xb4, 0x37, 0xbb, 0xf1, 0xb4, 0x5c, 0xa7, 0xcd, 0xd9, 0x69, 0x73, 0x76, 0xda, 0x9c, 0x9d, 0x36, 0x67, 0xa7, 0xcd, 0xd9, 0x69, 0x73, 0x76, 0xda, 0x9c, 0x9d, 0x36, 0x67, 0xa7, 0xcd, 0xd9, 0x69, 0x73, 0x76, 0xda, 0x5c, 0x2a, 0x6d, 0x2e, 0x95, 0x36, 0x97, 0x4a, 0x9b, 0x4b, 0xa5, 0xcd, 0xa5, 0xd2, 0xe6, 0x52, 0x69, 0x73, 0xa9, 0xb4, 0xb9, 0x54, 0xda, 0x5c, 0x2a, 0x6d, 0x2e, 0x95, 0x36, 0x97, 0x4a, 0x9b, 0x4b, 0xa5, 0xcd, 0xa5, 0xd2, 0xe6, 0x52, 0x69, 0x73, 0xa9, 0xb4, 0xb9, 0x54, 0xda, 0x5c, 0x2a, 0x6d, 0x2e, 0x95, 0x36, 0x97, 0x4a, 0x9b, 0x4b, 0xa5, 0xcd, 0xa5, 0xd2, 0xe6, 0x52, 0x69, 0x73, 0xa9, 0xb4, 0xb9, 0x54, 0xda, 0x5c, 0x2a, 0x6d, 0x2e, 0x95, 0x36, 0x97, 0x41, 0x9b, 0xcb, 0xa0, 0xcd, 0x65, 0xd0, 0xe6, 0x32, 0x68, 0x73, 0x19, 0xb4, 0xb9, 0x0c, 0xda, 0x5c, 0x06, 0x6d, 0x2e, 0x83, 0x36, 0x97, 0x41, 0x82, 0xbd, 0x61, 0xfd, 0x08, 0x9f, 0x60, 0x1c, 0x7c, 0xef, 0x62, 0x7f, 0x86, 0xcf, 0xfd, 0xef, 0x66, 0xdf, 0xb0, 0x8e, 0xc7, 0x04, 0x4c, 0xc4, 0x24, 0x4c, 0xc6, 0x97, 0xf8, 0x0a, 0x53, 0x30, 0x15, 0xd3, 0x30, 0x03, 0xdf, 0x62, 0x26, 0x66, 0x61, 0x36, 0xe6, 0x60, 0x2e, 0xbe, 0xc3, 0x3c, 0xcc, 0xc7, 0xf7, 0x58, 0x80, 0x85, 0x58, 0x84, 0x60, 0xfc, 0x80, 0xc5, 0xf8, 0x11, 0x4b, 0xb0, 0x4c, 0x9c, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0x9d, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0x9d, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0x9d, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0x9d, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0x9d, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0xaf, 0xa8, 0x5a, 0xb4, 0xc9, 0xeb, 0xb4, 0x92, 0x63, 0xb4, 0xc9, 0xeb, 0xb4, 0x49, 0x6f, 0x75, 0x82, 0xf5, 0x7d, 0x8e, 0xb7, 0x8c, 0xf6, 0x78, 0x95, 0xf6, 0x78, 0xd5, 0xff, 0x4e, 0x5f, 0xd5, 0xab, 0x34, 0x71, 0xb4, 0xc7, 0x63, 0xd5, 0xff, 0xc6, 0xa6, 0x8c, 0x96, 0x50, 0xe6, 0x7b, 0xd5, 0xfb, 0xcf, 0x57, 0x45, 0xdd, 0xd5, 0xef, 0xdd, 0x97, 0xf8, 0xdf, 0x5d, 0x72, 0xb1, 0x5e, 0xca, 0x63, 0x5e, 0x79, 0x9c, 0xb6, 0x54, 0xf5, 0xef, 0x95, 0x7c, 0x9f, 0x0a, 0x8e, 0xe7, 0x08, 0x47, 0xab, 0x8f, 0xe0, 0xf4, 0x7d, 0x9a, 0x40, 0xfd, 0xa8, 0xca, 0x78, 0xec, 0x3f, 0x78, 0xbc, 0x20, 0x29, 0x36, 0xdd, 0x21, 0x05, 0xa6, 0x3b, 0xc9, 0x48, 0x77, 0x71, 0x2e, 0x35, 0xd9, 0xd6, 0x42, 0x6d, 0xd4, 0x61, 0x04, 0xbc, 0x9b, 0xa3, 0xd5, 0x95, 0x33, 0xa6, 0x7b, 0xb8, 0x7c, 0x2f, 0xee, 0xa3, 0x9f, 0xde, 0x8f, 0x07, 0xd8, 0xaf, 0x87, 0xfa, 0x68, 0xc0, 0xfd, 0x1a, 0xb2, 0x7d, 0x90, 0xf3, 0x6d, 0x24, 0x99, 0xa6, 0xc6, 0x5c, 0x6e, 0xc2, 0xe5, 0xa6, 0x78, 0x88, 0x91, 0xb3, 0x19, 0xdb, 0x87, 0xd1, 0x5c, 0xa2, 0x4d, 0x2d, 0xd8, 0xb6, 0x44, 0x2b, 0x39, 0x6b, 0x7a, 0x84, 0xdb, 0xb5, 0x66, 0xbf, 0x0d, 0x59, 0xfd, 0x51, 0xb1, 0x99, 0xa3, 0xe4, 0xba, 0xf9, 0xa4, 0xdc, 0x30, 0xc7, 0xb3, 0x4e, 0xb1, 0x21, 0x93, 0xfd, 0x53, 0x22, 0xe6, 0x0b, 0x52, 0x64, 0xbe, 0x08, 0x17, 0xdc, 0xb8, 0x24, 0xa7, 0xcd, 0xb9, 0x6c, 0xf3, 0x90, 0x8f, 0xcb, 0x72, 0xdc, 0xec, 0x61, 0x5b, 0x80, 0x42, 0x14, 0x89, 0xd7, 0x5c, 0xcc, 0x75, 0x25, 0xe2, 0x30, 0x97, 0xa2, 0x8c, 0x0c, 0x77, 0x85, 0xcb, 0xe5, 0xac, 0x18, 0xaf, 0xa2, 0x82, 0xdb, 0x54, 0xe2, 0x1a, 0xae, 0xe3, 0x86, 0xe4, 0x99, 0xbd, 0x6c, 0x6f, 0xe2, 0x0f, 0xfe, 0x2e, 0x52, 0x14, 0xa0, 0xc4, 0x1b, 0x60, 0x12, 0x67, 0x80, 0xc6, 0xd6, 0xcc, 0xe5, 0x00, 0x04, 0xca, 0x99, 0x80, 0x1a, 0xe2, 0x09, 0xb0, 0x70, 0x9d, 0x55, 0xf2, 0x02, 0x82, 0xb8, 0xae, 0xab, 0xe4, 0x07, 0xbc, 0xc2, 0x75, 0xbd, 0xd8, 0xef, 0xcd, 0xdf, 0xfb, 0xb0, 0xed, 0x2b, 0xee, 0x80, 0x7e, 0x6c, 0xfb, 0x4b, 0x7a, 0xc0, 0x00, 0x6e, 0x3b, 0x10, 0x83, 0xe4, 0x46, 0xc0, 0x60, 0xf1, 0x06, 0x16, 0xc8, 0x99, 0xc0, 0x42, 0xf1, 0x5a, 0x9e, 0x90, 0xcb, 0x96, 0x91, 0x18, 0x85, 0x77, 0xe4, 0x94, 0x4e, 0xeb, 0xd6, 0x83, 0xe5, 0xba, 0xbe, 0x81, 0xed, 0x5e, 0xfc, 0x86, 0x70, 0x44, 0x89, 0x57, 0x4f, 0x44, 0x26, 0xb2, 0x90, 0x27, 0x5e, 0xc3, 0x8a, 0x7b, 0xe9, 0xf5, 0x94, 0xb1, 0xd1, 0x94, 0x6d, 0x1b, 0xb0, 0x8e, 0x32, 0x46, 0x70, 0xfd, 0x44, 0x7c, 0xc5, 0xfe, 0x74, 0x2c, 0x92, 0x3c, 0x23, 0x5a, 0x8a, 0x8d, 0x73, 0x72, 0xc6, 0xc8, 0x95, 0x54, 0x23, 0x0f, 0xc5, 0x28, 0x41, 0x29, 0xbd, 0x74, 0x29, 0xeb, 0xac, 0xe3, 0x8c, 0xfe, 0x91, 0x6c, 0xa3, 0xe4, 0xb4, 0xf5, 0x24, 0x62, 0xe5, 0x8c, 0x35, 0x8e, 0xcb, 0xf1, 0x48, 0x40, 0x22, 0xec, 0x20, 0x93, 0x5b, 0x53, 0xd9, 0xa6, 0xc9, 0x59, 0xab, 0x43, 0x32, 0x83, 0x0e, 0xd3, 0x06, 0x23, 0x10, 0x47, 0x56, 0x6f, 0x41, 0x6b, 0xfa, 0x50, 0x89, 0xec, 0xa3, 0x35, 0x6d, 0xa3, 0x35, 0x65, 0xd0, 0x9a, 0xde, 0xa7, 0x35, 0x2d, 0x35, 0xd5, 0xf9, 0x63, 0x2a, 0xb5, 0xfe, 0x94, 0xa9, 0xcd, 0x1f, 0xfb, 0x4c, 0x6d, 0x55, 0x7d, 0x6a, 0x79, 0x18, 0xad, 0xbc, 0x87, 0xe9, 0x29, 0x99, 0x62, 0x7a, 0x5a, 0xbe, 0xa6, 0x95, 0xbf, 0x4d, 0x29, 0x3f, 0x4a, 0x29, 0xbe, 0x18, 0xd0, 0xfb, 0x8f, 0xe1, 0x94, 0xdc, 0x13, 0x94, 0xda, 0x02, 0x4a, 0xab, 0x01, 0xa5, 0xd2, 0xd8, 0xe2, 0xfb, 0xee, 0x40, 0x56, 0x81, 0x94, 0x48, 0x2f, 0x4a, 0xa4, 0x03, 0x25, 0xa0, 0x28, 0x01, 0x0b, 0xcf, 0xdc, 0x6e, 0x34, 0x55, 0x8d, 0x8d, 0x89, 0x7f, 0x54, 0x1a, 0x8b, 0xfe, 0xc8, 0xe6, 0x59, 0x0e, 0xe5, 0xd9, 0xb4, 0xe5, 0xec, 0xbf, 0xe0, 0x4c, 0xfb, 0x72, 0x96, 0x6f, 0x72, 0x96, 0x11, 0xfe, 0x4f, 0xb4, 0x1e, 0x96, 0xc7, 0x82, 0x22, 0xfe, 0xa8, 0xa4, 0xdf, 0x95, 0xa9, 0x3b, 0x39, 0xcb, 0x10, 0xce, 0x6e, 0x27, 0x67, 0x77, 0xcc, 0x54, 0x47, 0x59, 0x39, 0xbb, 0x1f, 0x38, 0xab, 0xcd, 0xe6, 0xfd, 0x72, 0xcd, 0x1c, 0x21, 0xd7, 0x38, 0x9b, 0x39, 0x3c, 0xfa, 0x61, 0x1e, 0xc1, 0xcb, 0x23, 0x98, 0x28, 0xc3, 0x52, 0x8e, 0x1e, 0xc5, 0xd1, 0xf7, 0x70, 0xc4, 0x54, 0xab, 0xef, 0xf3, 0x3a, 0x0d, 0x78, 0xbe, 0x57, 0x4d, 0x81, 0x72, 0x8d, 0xe7, 0x5b, 0xc6, 0xd1, 0xf2, 0x38, 0x5a, 0x19, 0xbd, 0x25, 0x81, 0xa3, 0x15, 0xd0, 0x9a, 0x93, 0x38, 0x62, 0x21, 0x7d, 0xae, 0xd0, 0xf7, 0x0d, 0xe0, 0x1c, 0xb9, 0x92, 0x23, 0x57, 0xd2, 0x92, 0x4b, 0x39, 0x7a, 0x31, 0x2d, 0xa8, 0x98, 0xe7, 0x5b, 0x12, 0xd0, 0x5d, 0x2a, 0x03, 0x7a, 0xc8, 0x75, 0x5a, 0x4d, 0x82, 0xbf, 0xc5, 0xf8, 0x5a, 0xcb, 0x20, 0x6e, 0x53, 0xfd, 0xbd, 0xe8, 0x3c, 0x7f, 0x37, 0xcf, 0xff, 0x1a, 0x67, 0x90, 0xc3, 0x19, 0x14, 0x70, 0x06, 0x1e, 0xff, 0x73, 0xb8, 0x9d, 0xe7, 0xb0, 0xb9, 0xfa, 0x39, 0x1c, 0xe5, 0x39, 0xe8, 0x3c, 0x6a, 0x30, 0x8f, 0xb8, 0xe9, 0x96, 0xe7, 0x30, 0xb7, 0xea, 0x39, 0x28, 0x33, 0xcf, 0x21, 0x80, 0x23, 0x94, 0x55, 0x3f, 0x87, 0xdf, 0x78, 0x0e, 0x0e, 0xff, 0x73, 0x68, 0xc3, 0x73, 0x18, 0xcf, 0x91, 0x62, 0x79, 0x0e, 0x91, 0x1c, 0x6d, 0x15, 0x47, 0xdb, 0x41, 0x7d, 0x95, 0x73, 0xb4, 0x28, 0x9e, 0xc3, 0x0b, 0x1c, 0xf1, 0xb8, 0xe9, 0x09, 0x65, 0xa1, 0xbe, 0x86, 0x50, 0x5f, 0x76, 0xea, 0xcb, 0x46, 0x7d, 0x7d, 0x62, 0x7a, 0x99, 0xd5, 0x58, 0x7f, 0x75, 0xb7, 0x69, 0xa0, 0xba, 0xcf, 0x34, 0x58, 0xdd, 0xcd, 0xa3, 0x85, 0xf3, 0x7c, 0xa6, 0xf8, 0x9f, 0x4f, 0xef, 0x3f, 0x4e, 0xf1, 0x5c, 0x36, 0xf2, 0x5c, 0xe2, 0x39, 0x83, 0x01, 0x01, 0x3f, 0x28, 0xbd, 0xea, 0xdb, 0xbb, 0x94, 0x85, 0xe7, 0xd3, 0x97, 0xe7, 0x33, 0x86, 0xfa, 0xcc, 0xa6, 0x3e, 0x67, 0x50, 0x9f, 0x93, 0xa8, 0xcf, 0x57, 0xa9, 0xcf, 0x51, 0x9c, 0xe9, 0xb3, 0x9c, 0x69, 0x7b, 0x5a, 0x6f, 0x5f, 0xce, 0x76, 0x30, 0x67, 0xbb, 0x81, 0xb3, 0x9d, 0x49, 0x7d, 0xee, 0xa6, 0x2e, 0x37, 0x58, 0x1d, 0x2a, 0x88, 0x7a, 0x1c, 0x4f, 0x8b, 0xeb, 0xab, 0x74, 0xce, 0x7c, 0x32, 0x67, 0x7e, 0x84, 0x33, 0x3f, 0xc2, 0x99, 0xcf, 0xe3, 0xac, 0x9d, 0x9c, 0xf5, 0x02, 0xce, 0xba, 0x2b, 0x47, 0xec, 0xa3, 0x42, 0xb8, 0xc5, 0x66, 0x75, 0x45, 0x42, 0x54, 0xb9, 0x24, 0xa9, 0x4a, 0xd9, 0xab, 0xae, 0xe1, 0xa6, 0x44, 0x30, 0xe2, 0x65, 0x50, 0x6f, 0x11, 0x26, 0x5d, 0x92, 0xb9, 0xb7, 0xd3, 0x74, 0x1b, 0xfb, 0xb7, 0xe3, 0x4e, 0xb9, 0xce, 0xf3, 0xf7, 0xfc, 0x97, 0xd1, 0x6f, 0x29, 0xa3, 0x9f, 0x93, 0xd1, 0x2f, 0xf7, 0x96, 0xd1, 0xef, 0x27, 0x46, 0xbf, 0x9f, 0xfe, 0xcb, 0xe8, 0x57, 0xe6, 0x1f, 0xfd, 0x1a, 0xb3, 0xfd, 0xdb, 0xa8, 0xf7, 0xb7, 0x11, 0xef, 0xaf, 0xd1, 0xee, 0x32, 0xa3, 0x5d, 0x59, 0xf5, 0x68, 0x77, 0xd1, 0xff, 0x3a, 0xfc, 0x93, 0x94, 0xf3, 0x33, 0x94, 0x7f, 0x47, 0x74, 0x66, 0xd5, 0xd3, 0x05, 0x3d, 0x28, 0xe7, 0xaa, 0x7f, 0x7b, 0xe8, 0x7b, 0x1d, 0x31, 0x8d, 0x1a, 0x2d, 0x33, 0x1f, 0x92, 0x10, 0x6a, 0xb5, 0xcc, 0x7c, 0x44, 0x22, 0xcc, 0x91, 0x88, 0x92, 0x70, 0x46, 0xc9, 0xdd, 0xe6, 0x68, 0xf6, 0x13, 0x65, 0xaf, 0x39, 0x4d, 0x52, 0x18, 0x29, 0x77, 0xd3, 0xbe, 0x32, 0xcd, 0x67, 0xb9, 0xfc, 0x8f, 0xa3, 0xa5, 0xf3, 0xef, 0x46, 0xcb, 0xbf, 0x1f, 0x29, 0xcb, 0x18, 0x1d, 0x2f, 0xfa, 0x47, 0xc4, 0xaa, 0x91, 0xf0, 0xa7, 0x3f, 0x47, 0xc2, 0xaa, 0x51, 0xb0, 0x8c, 0x11, 0xb0, 0xec, 0x96, 0x11, 0xf0, 0x22, 0xed, 0xb7, 0x80, 0x11, 0xb0, 0x8c, 0x11, 0xf0, 0xa7, 0xea, 0x11, 0x30, 0x86, 0xba, 0xb7, 0xd3, 0x96, 0xe3, 0x19, 0x09, 0xbd, 0xd5, 0x23, 0xe1, 0xf2, 0xea, 0x91, 0x30, 0xa3, 0x7a, 0x24, 0x2c, 0x64, 0x24, 0x2c, 0x63, 0x24, 0x2c, 0xa3, 0x5d, 0x5c, 0x61, 0x24, 0x2c, 0x0b, 0xf8, 0x41, 0xd2, 0x68, 0x1b, 0x36, 0xda, 0xba, 0x83, 0x51, 0x31, 0x97, 0x51, 0xb1, 0x2c, 0x90, 0xfa, 0x62, 0x64, 0x4c, 0x60, 0x64, 0x4c, 0x60, 0x64, 0x4c, 0xa0, 0xcd, 0x6c, 0xa1, 0xcd, 0xfc, 0x40, 0x9b, 0x29, 0xd3, 0x07, 0x4a, 0x04, 0xa3, 0xe4, 0x6e, 0x46, 0xc9, 0x9d, 0x8c, 0x92, 0x17, 0x69, 0x43, 0x17, 0x69, 0x43, 0x17, 0x19, 0x25, 0x2f, 0x32, 0x4a, 0x96, 0x31, 0x4a, 0x96, 0x31, 0x4a, 0x96, 0xd1, 0x02, 0xca, 0x68, 0x53, 0x65, 0x8c, 0x92, 0x65, 0x8c, 0x92, 0xc5, 0xd5, 0xa3, 0x64, 0x31, 0xa3, 0x64, 0x71, 0xf5, 0x28, 0x59, 0x46, 0x3b, 0x2b, 0xbb, 0x65, 0x94, 0xf4, 0xf5, 0x8e, 0x52, 0x46, 0xc9, 0x8b, 0xb4, 0xb9, 0x2b, 0xd6, 0x5f, 0x25, 0xc4, 0xba, 0x1b, 0x7b, 0x24, 0xc9, 0x7a, 0x88, 0x31, 0x24, 0x1c, 0x87, 0x11, 0x81, 0x63, 0x92, 0xcc, 0x88, 0x99, 0x51, 0x3d, 0x62, 0x3a, 0x19, 0x31, 0x9d, 0xb4, 0xd1, 0xdc, 0x7f, 0x3a, 0x62, 0x56, 0x8d, 0x96, 0x97, 0x69, 0xb7, 0x65, 0xb4, 0xdb, 0x32, 0x46, 0x4a, 0xaf, 0xaa, 0x4b, 0xbb, 0xb5, 0xd3, 0x02, 0x1d, 0xd5, 0xa3, 0x64, 0x38, 0xad, 0xce, 0x46, 0xdb, 0x0d, 0xa1, 0xb7, 0xfd, 0xcc, 0x6c, 0x7d, 0x89, 0x19, 0xfa, 0x14, 0xb5, 0x5e, 0x4a, 0x8d, 0x97, 0x52, 0xd3, 0xc7, 0x03, 0xba, 0xaa, 0xdb, 0x29, 0x31, 0x7b, 0x60, 0x07, 0x39, 0x15, 0xc8, 0x5a, 0xd9, 0xf2, 0x8e, 0xba, 0x8d, 0x33, 0xae, 0xe0, 0x8c, 0x93, 0xad, 0xf7, 0x49, 0x34, 0x67, 0xec, 0x1b, 0xe5, 0xd2, 0x94, 0xc1, 0x91, 0x93, 0x68, 0xd3, 0xe5, 0x1c, 0x35, 0x95, 0xa3, 0xe6, 0x73, 0x54, 0x3b, 0x6d, 0xcd, 0x43, 0xdf, 0xba, 0x83, 0xbe, 0xd5, 0x94, 0x7b, 0xb8, 0x55, 0x23, 0x7a, 0x46, 0x34, 0x8f, 0x1f, 0xcf, 0x2d, 0xf3, 0xb9, 0xe5, 0x35, 0x6e, 0x59, 0x4c, 0x4b, 0x0f, 0xe1, 0x96, 0xe7, 0xfe, 0xe9, 0xe7, 0x90, 0xf7, 0x33, 0x0f, 0x47, 0x20, 0x4a, 0x4e, 0xd0, 0xd2, 0xce, 0xd3, 0xca, 0x7c, 0x2d, 0x20, 0x9f, 0x5a, 0x3f, 0x45, 0xad, 0xe7, 0x50, 0xeb, 0x59, 0xd4, 0xf6, 0x16, 0x7f, 0x4d, 0xf7, 0xe7, 0xfa, 0x41, 0xfe, 0x9a, 0xfd, 0xeb, 0x33, 0xcb, 0xef, 0xc8, 0x11, 0x6a, 0xf0, 0x10, 0xe7, 0x5c, 0xcc, 0x19, 0x94, 0x70, 0xbe, 0xa5, 0xea, 0x0e, 0x1e, 0xf9, 0x12, 0x8f, 0x7c, 0x86, 0x47, 0x76, 0x73, 0x9e, 0x97, 0x79, 0x74, 0x37, 0x25, 0x90, 0xcf, 0x58, 0xe2, 0xe1, 0x68, 0x4e, 0x8e, 0x76, 0x95, 0xa3, 0xe5, 0x71, 0xb4, 0x8b, 0xdc, 0xf3, 0x22, 0xf7, 0x2c, 0xe4, 0x9e, 0x2e, 0x35, 0x95, 0x5e, 0x5d, 0x46, 0x4f, 0x0e, 0x33, 0x69, 0xf4, 0xa2, 0x40, 0x59, 0xec, 0x7f, 0xc6, 0xb7, 0xc9, 0x5c, 0x7a, 0xf1, 0x5c, 0x8e, 0xea, 0x5b, 0xbf, 0x78, 0xe9, 0xc1, 0x5e, 0x7a, 0xb0, 0x97, 0xa3, 0x7b, 0xe9, 0xc1, 0xe7, 0xe9, 0xc1, 0x97, 0xe9, 0xc1, 0x5e, 0x7a, 0xb0, 0x97, 0x9e, 0xeb, 0xa5, 0xe7, 0x7a, 0xe9, 0xb9, 0xbe, 0x35, 0x8b, 0x97, 0x35, 0xcb, 0x22, 0x7a, 0xad, 0x97, 0x5e, 0xeb, 0x65, 0xad, 0x72, 0x9d, 0x9e, 0xeb, 0xa5, 0xe7, 0x7a, 0x39, 0xab, 0xd5, 0xf4, 0x5e, 0x2f, 0xbd, 0xd7, 0x4b, 0xef, 0xf5, 0xd0, 0x73, 0xbd, 0x9c, 0x65, 0x71, 0xf5, 0xbf, 0x17, 0xcb, 0x36, 0x75, 0x92, 0x5f, 0x58, 0xab, 0xfd, 0x66, 0x7a, 0x8e, 0xed, 0xf3, 0x6c, 0x7d, 0xdf, 0x59, 0xe7, 0xfb, 0x7e, 0x0a, 0xdf, 0x6f, 0x3e, 0x0c, 0x61, 0x16, 0x7c, 0x83, 0xeb, 0x47, 0xc8, 0x2f, 0xf4, 0x5e, 0x5f, 0xcf, 0x9d, 0x4b, 0xcf, 0x9d, 0x4b, 0xaf, 0x2d, 0xa2, 0xd7, 0xce, 0x35, 0xc7, 0xd0, 0xe3, 0x62, 0x91, 0x8c, 0x4c, 0xae, 0x3b, 0x85, 0x0b, 0xac, 0x59, 0x2e, 0xc2, 0x05, 0x37, 0x2e, 0x51, 0xd6, 0xb9, 0x6c, 0x99, 0xef, 0xe9, 0xb5, 0x5e, 0xd6, 0x38, 0x8b, 0xe8, 0xb9, 0x5e, 0x7a, 0xae, 0x97, 0x9e, 0xeb, 0x5b, 0xdf, 0x2c, 0xa2, 0xe7, 0x66, 0xb1, 0xae, 0x59, 0xc4, 0xba, 0xe6, 0x3a, 0xeb, 0x9a, 0xeb, 0xf4, 0x62, 0x2f, 0xbd, 0xd8, 0x4b, 0x0f, 0xf6, 0xd2, 0x83, 0xbd, 0xac, 0x65, 0xae, 0xd3, 0x8b, 0x7d, 0xeb, 0x17, 0x2f, 0xbd, 0xd7, 0x4b, 0xef, 0xcd, 0xa2, 0xee, 0xb2, 0xe8, 0xb5, 0xbe, 0x5e, 0xea, 0xa5, 0x87, 0x7a, 0xe9, 0x9d, 0x5e, 0xea, 0xaa, 0x88, 0xf6, 0xf5, 0x33, 0xed, 0xeb, 0x67, 0x7a, 0xe2, 0x65, 0x7a, 0x60, 0x25, 0x3d, 0xb0, 0x92, 0x1e, 0x58, 0x49, 0xaf, 0x9b, 0x4b, 0xaf, 0x2b, 0xf2, 0xcf, 0x87, 0x6d, 0xd0, 0xcd, 0xbf, 0xbe, 0xf0, 0xd2, 0x73, 0xbc, 0xf4, 0x98, 0x2c, 0xd6, 0x15, 0xd1, 0xac, 0x2b, 0xa2, 0x59, 0x57, 0x44, 0xb3, 0xae, 0x88, 0x66, 0x5d, 0x11, 0x4d, 0x0f, 0x2a, 0xa3, 0x07, 0x95, 0xd1, 0x7b, 0xc2, 0xe8, 0x3d, 0x61, 0xf4, 0x9e, 0x30, 0x7a, 0x4f, 0x98, 0xf5, 0x08, 0x3d, 0x2c, 0x52, 0xbc, 0xf4, 0x9c, 0xf3, 0xf4, 0x9c, 0xf3, 0xf4, 0x9c, 0xcb, 0xf4, 0x1c, 0x2f, 0x3d, 0xc7, 0x4b, 0xcf, 0xf1, 0xd2, 0x73, 0xbc, 0xf4, 0x1c, 0x2f, 0x33, 0xf8, 0x75, 0x7a, 0x8f, 0x97, 0xde, 0xe3, 0xa1, 0x6d, 0xef, 0x57, 0xef, 0x52, 0xe3, 0x27, 0xa8, 0xf1, 0x5c, 0x6a, 0x7c, 0x01, 0xb5, 0x3d, 0x8d, 0xda, 0x9e, 0x46, 0x6d, 0xdf, 0xf8, 0xbb, 0xda, 0xfe, 0x9f, 0xd7, 0xf0, 0x86, 0x5b, 0x6a, 0xf8, 0xca, 0x2d, 0x35, 0xbc, 0xed, 0xcf, 0x1a, 0xf6, 0xd5, 0x6e, 0x5b, 0xff, 0x4a, 0xbc, 0xd8, 0xff, 0xaf, 0x01, 0x3b, 0xb1, 0x6e, 0x7e, 0x56, 0x0e, 0x50, 0xbb, 0x7b, 0xa9, 0xdd, 0x03, 0xd4, 0xee, 0xe1, 0xbf, 0xcd, 0xe7, 0xd4, 0xee, 0x4e, 0x6a, 0x77, 0x3f, 0xb5, 0xbb, 0x9f, 0x9a, 0x9d, 0x46, 0xcd, 0x4e, 0xa3, 0x56, 0xa7, 0x51, 0xab, 0xb9, 0xd4, 0x6a, 0x2e, 0xb5, 0x9a, 0xfb, 0x0f, 0xb5, 0xf9, 0xf7, 0x35, 0xb9, 0xe1, 0xbf, 0xd4, 0xe4, 0x06, 0x6a, 0x71, 0x03, 0xb5, 0x78, 0x85, 0x5a, 0xbc, 0xf2, 0x4f, 0x6a, 0xf1, 0xca, 0xdf, 0xd5, 0xe2, 0x3f, 0xd6, 0x5e, 0x19, 0xb5, 0xb7, 0x9d, 0xda, 0xdb, 0x4e, 0x6d, 0x4d, 0x33, 0xea, 0x52, 0x33, 0xff, 0xac, 0xb6, 0xa2, 0x91, 0x2b, 0xe1, 0xd4, 0x56, 0x38, 0xb5, 0x15, 0x4e, 0x6d, 0x85, 0x53, 0x5b, 0xe1, 0xd4, 0xd2, 0x09, 0x6a, 0xe9, 0x04, 0xb5, 0x74, 0x82, 0x5a, 0x3a, 0x41, 0x2d, 0xe5, 0xfa, 0x6b, 0xe9, 0x9f, 0xd7, 0xcc, 0x15, 0x7f, 0xcd, 0x38, 0x99, 0x4b, 0x1b, 0x51, 0x1b, 0xa3, 0xe9, 0x7f, 0x15, 0x8c, 0x65, 0x5f, 0xfb, 0xfb, 0x5e, 0x73, 0x59, 0xee, 0x1b, 0x71, 0xe8, 0xc9, 0x3b, 0xab, 0x33, 0x8d, 0xad, 0xfa, 0x5f, 0xf4, 0x97, 0x54, 0x7f, 0x46, 0xc2, 0xf7, 0x09, 0xfe, 0x49, 0x8c, 0x25, 0x6b, 0x68, 0xf3, 0xe5, 0xfe, 0xf5, 0x75, 0xd5, 0x6a, 0xa8, 0xac, 0x7a, 0x25, 0xe4, 0xa1, 0xe7, 0x5f, 0xa4, 0xe7, 0x9f, 0xe6, 0x59, 0xa5, 0x33, 0x7e, 0xf8, 0xfe, 0xa5, 0xfd, 0x48, 0x9e, 0xd9, 0x60, 0xc6, 0x91, 0x7c, 0x46, 0x02, 0xff, 0xb3, 0x60, 0xdc, 0x2b, 0x62, 0x34, 0xf0, 0x9d, 0xc5, 0x77, 0xea, 0x11, 0x7a, 0xf9, 0x59, 0x7a, 0x79, 0x3a, 0xbd, 0x3c, 0x9f, 0xf9, 0xf9, 0x22, 0xf3, 0xf3, 0x45, 0xea, 0x3c, 0x85, 0x24, 0x92, 0xc3, 0x19, 0xa5, 0xfb, 0x7b, 0x6f, 0x1b, 0xea, 0xfd, 0x51, 0x46, 0xdc, 0x1e, 0xfe, 0x5f, 0x66, 0xf1, 0xfd, 0xdb, 0xa2, 0x32, 0xce, 0x24, 0x8f, 0x5e, 0x96, 0x4e, 0x7d, 0xa4, 0x50, 0x07, 0x29, 0xa4, 0x85, 0x33, 0xa4, 0x85, 0x33, 0xf4, 0xaa, 0x64, 0xea, 0x23, 0x85, 0xb9, 0xf0, 0xa2, 0x7f, 0x3c, 0x0a, 0x94, 0x64, 0xff, 0x9c, 0x67, 0xe5, 0x72, 0x6f, 0xf4, 0x65, 0xd4, 0xeb, 0x2f, 0x39, 0xf4, 0x94, 0x7c, 0xcb, 0x25, 0xc9, 0xd1, 0x1f, 0x93, 0x12, 0x7a, 0x42, 0x32, 0x65, 0x7b, 0x8a, 0xb2, 0x3d, 0x45, 0xd9, 0x9e, 0xa2, 0x6c, 0x4f, 0x51, 0xb6, 0xa7, 0x68, 0xe9, 0xe9, 0xb4, 0xf4, 0x74, 0x5a, 0x7a, 0xbe, 0xbf, 0x35, 0x3b, 0x24, 0x47, 0x35, 0xe3, 0x8c, 0x7f, 0xe7, 0x8c, 0x93, 0x38, 0xe3, 0x4b, 0x9c, 0xb1, 0x9b, 0x33, 0x76, 0x73, 0xc6, 0xfb, 0x38, 0xdb, 0x23, 0x9c, 0x6d, 0x2e, 0x67, 0x5b, 0xc4, 0x59, 0x5e, 0xf6, 0xfd, 0x66, 0x28, 0x67, 0x99, 0xcd, 0x59, 0x26, 0x71, 0x96, 0xfb, 0x38, 0xcb, 0x7d, 0x9c, 0xa5, 0x9d, 0xb3, 0xb4, 0x73, 0x96, 0x71, 0x9c, 0xe5, 0x3e, 0x72, 0x8c, 0x97, 0x1c, 0xe3, 0xe5, 0x6c, 0xdd, 0x9c, 0xed, 0x79, 0xce, 0x36, 0x8e, 0xb3, 0x8d, 0xe3, 0x6c, 0xdd, 0x9c, 0xe1, 0x25, 0xca, 0xec, 0x3a, 0x67, 0x17, 0xc7, 0xd9, 0xc5, 0x72, 0x76, 0xb1, 0x9c, 0x5d, 0x2c, 0x67, 0x17, 0xcb, 0xd9, 0xc5, 0x72, 0x76, 0x49, 0x9c, 0x5d, 0x12, 0x67, 0x77, 0x89, 0xb3, 0xcb, 0x55, 0xb7, 0x33, 0x26, 0x1f, 0xe2, 0xec, 0x66, 0x71, 0x06, 0x36, 0x66, 0x83, 0x45, 0x94, 0xd7, 0xaa, 0xea, 0x4f, 0x3c, 0xf8, 0xd6, 0x24, 0x31, 0x9c, 0x91, 0x9d, 0x32, 0xf8, 0xca, 0x37, 0x2e, 0xf3, 0xdc, 0x2f, 0x73, 0xf4, 0x1b, 0xac, 0x06, 0xff, 0x77, 0xef, 0xd5, 0x84, 0x95, 0x7b, 0x53, 0xd5, 0xce, 0x7f, 0x6f, 0x83, 0x96, 0x53, 0xcc, 0x11, 0x32, 0xfd, 0xdf, 0xe0, 0x57, 0xfd, 0xef, 0x52, 0xb9, 0x65, 0x0e, 0xb7, 0x2c, 0xa1, 0x6c, 0xcf, 0x50, 0xd3, 0xd7, 0x54, 0x1d, 0x8e, 0x7f, 0x8d, 0xf2, 0xb9, 0xc2, 0x63, 0x5c, 0xa3, 0x26, 0xaf, 0x56, 0x7f, 0x0f, 0x45, 0x62, 0xf5, 0x37, 0x72, 0x24, 0xdd, 0x92, 0x70, 0xb3, 0x28, 0x87, 0x1b, 0x94, 0xc1, 0x35, 0x1e, 0xb3, 0x84, 0xda, 0x2a, 0xa7, 0xb6, 0x4a, 0x68, 0x47, 0xa9, 0xb4, 0xa3, 0x54, 0x8e, 0x7a, 0x81, 0xa3, 0x5e, 0xfe, 0xa7, 0x8f, 0x9b, 0xf5, 0x0f, 0x8f, 0xeb, 0x2b, 0x91, 0x28, 0x1e, 0xdb, 0xd7, 0xae, 0x7d, 0x9f, 0xcd, 0xdc, 0xca, 0x73, 0x0b, 0xab, 0x7e, 0x6e, 0xe9, 0xd5, 0xef, 0xfc, 0xa4, 0xf0, 0x38, 0xbe, 0xc4, 0xe2, 0xe6, 0x9e, 0x1e, 0x5f, 0x1b, 0x55, 0x1a, 0xf7, 0xf0, 0xad, 0x46, 0x93, 0x55, 0x4d, 0x1e, 0xc5, 0xe5, 0x4f, 0x07, 0xcd, 0xb9, 0xff, 0xa3, 0xb4, 0xd4, 0xaa, 0x77, 0x8e, 0x7c, 0xe7, 0xbd, 0x9a, 0x73, 0x4d, 0xe5, 0x5c, 0xaf, 0x06, 0xf4, 0x56, 0x16, 0xdf, 0xbc, 0xc9, 0x39, 0x26, 0x73, 0x8e, 0xc9, 0x1c, 0xe7, 0x2c, 0x67, 0x10, 0x4e, 0x5b, 0xbf, 0xc1, 0x59, 0xb8, 0x55, 0x6d, 0xee, 0xed, 0x2b, 0xdd, 0x4a, 0x8e, 0xb0, 0x91, 0x23, 0x1c, 0xe3, 0x08, 0x7b, 0xfc, 0xbf, 0x8b, 0xd5, 0x9f, 0x75, 0xec, 0x40, 0x5a, 0x4d, 0xd5, 0x33, 0x1f, 0xcd, 0xd1, 0xb2, 0x79, 0xe6, 0x15, 0x9c, 0xd1, 0xd5, 0xea, 0x39, 0xd4, 0xc5, 0x51, 0xdf, 0xe6, 0xa8, 0x6f, 0x73, 0x54, 0x27, 0x47, 0xcd, 0x51, 0xb7, 0xf9, 0x47, 0xce, 0xbb, 0xfc, 0xb3, 0xee, 0xf5, 0x5b, 0xbe, 0x5f, 0xe9, 0x04, 0x47, 0x3b, 0x73, 0xeb, 0x77, 0xae, 0x72, 0xcf, 0x73, 0xdc, 0xf3, 0x9c, 0xbf, 0xf4, 0xcf, 0x72, 0x0e, 0x67, 0xb9, 0x47, 0x39, 0xe7, 0x70, 0xfe, 0x5f, 0xbd, 0xee, 0xc6, 0x39, 0x94, 0x70, 0x0e, 0xbe, 0x35, 0xe0, 0x79, 0xce, 0xe1, 0x1c, 0xe7, 0x70, 0xe1, 0x6f, 0xdf, 0x5a, 0xc2, 0x39, 0xfc, 0xce, 0x39, 0x94, 0x92, 0x59, 0xaa, 0xe6, 0xd1, 0xcb, 0x1c, 0x21, 0xae, 0xfa, 0x33, 0x54, 0xfe, 0xef, 0xe7, 0xa8, 0x2e, 0xfd, 0x02, 0xf5, 0x30, 0x8f, 0x79, 0x9d, 0x5e, 0x92, 0x47, 0x2f, 0xa9, 0xa4, 0x77, 0x24, 0xf1, 0xf8, 0x0e, 0x7a, 0x87, 0xef, 0xdf, 0x0b, 0x46, 0x71, 0xcf, 0x13, 0x8c, 0xd1, 0xbe, 0x77, 0x73, 0x4f, 0x32, 0x46, 0x47, 0x32, 0x46, 0x9f, 0xe4, 0x48, 0x9b, 0x6e, 0x79, 0xb7, 0xdf, 0xc1, 0xb9, 0x1c, 0x61, 0x9c, 0x8e, 0x63, 0x9c, 0x8e, 0xa3, 0x07, 0xe5, 0xd1, 0x83, 0x92, 0xe8, 0x41, 0x49, 0xf4, 0x9c, 0x0b, 0xf4, 0x9c, 0x24, 0x7a, 0xca, 0x05, 0xce, 0xd3, 0x77, 0x6e, 0x07, 0x39, 0xb7, 0x83, 0xf4, 0x96, 0x4a, 0x7a, 0xca, 0x05, 0x7a, 0x4a, 0x26, 0x3d, 0x25, 0x93, 0x9e, 0x92, 0x49, 0x4f, 0xc9, 0xa4, 0xa7, 0x64, 0xd2, 0x53, 0xf2, 0xe8, 0x29, 0x79, 0xf4, 0x94, 0x4a, 0xab, 0xef, 0x5f, 0x76, 0xd5, 0xac, 0x6e, 0x17, 0x11, 0xf4, 0xdf, 0x78, 0xfa, 0x6f, 0xfc, 0xbf, 0x6a, 0x1f, 0xf4, 0xcd, 0x78, 0xfa, 0x63, 0xfc, 0x7f, 0x6d, 0x27, 0xa6, 0x6e, 0xca, 0x42, 0x3e, 0xd1, 0x71, 0x07, 0x1e, 0x40, 0x3d, 0xd4, 0x07, 0xcf, 0x57, 0x35, 0x25, 0x9f, 0x3d, 0x84, 0x66, 0xa4, 0xb5, 0x87, 0xd1, 0x5c, 0xc6, 0xa8, 0x16, 0xf2, 0x93, 0x6a, 0x25, 0xa3, 0xd4, 0x5c, 0x39, 0xa8, 0xbe, 0x93, 0x54, 0x35, 0x9f, 0x5c, 0x13, 0xc7, 0x6d, 0xed, 0x62, 0x57, 0x49, 0x32, 0x50, 0x39, 0x64, 0x85, 0xca, 0x96, 0x33, 0xca, 0xc9, 0xdf, 0x5d, 0x72, 0x48, 0xb1, 0xe6, 0x57, 0x37, 0xf8, 0xfb, 0x4d, 0xb1, 0xb1, 0x3e, 0x3c, 0xc9, 0x1c, 0xda, 0x91, 0x51, 0x7b, 0x96, 0xc9, 0xa0, 0x8c, 0x6e, 0x93, 0xf1, 0xcc, 0xa3, 0xe3, 0x29, 0xdb, 0xed, 0xcc, 0x8b, 0x9b, 0xfc, 0xa3, 0x4f, 0x27, 0x69, 0x41, 0x99, 0xb6, 0xa0, 0x4c, 0x5b, 0x50, 0xa6, 0x2d, 0x28, 0xbf, 0xf6, 0x94, 0x5f, 0x7b, 0xd3, 0x17, 0xb2, 0xcb, 0xe4, 0xfb, 0x2e, 0xe9, 0x29, 0xb4, 0x94, 0x39, 0xcc, 0x83, 0x73, 0x65, 0x8b, 0xc9, 0xf7, 0xfd, 0xbf, 0x6e, 0x8e, 0xc3, 0x18, 0xaa, 0x75, 0x91, 0x29, 0x5a, 0x77, 0x49, 0xd2, 0x5e, 0x60, 0xfb, 0x92, 0x14, 0x6a, 0x3d, 0x95, 0x55, 0x7b, 0x85, 0xcb, 0xf4, 0x4c, 0x6d, 0x20, 0xd7, 0xbd, 0x26, 0xa3, 0xb4, 0x91, 0xec, 0xbf, 0x8b, 0xd1, 0xec, 0x7f, 0xc0, 0x7d, 0x7c, 0xbf, 0x84, 0xb6, 0x84, 0xbc, 0xb2, 0x46, 0xc6, 0x98, 0xd7, 0xc9, 0x97, 0xe6, 0x4d, 0xd4, 0x4d, 0x28, 0xe3, 0xef, 0x11, 0x19, 0x6f, 0x3e, 0xc1, 0x7e, 0x24, 0xdb, 0x28, 0xf9, 0x8c, 0xb9, 0x75, 0x3c, 0x73, 0x6b, 0x47, 0xe6, 0xd6, 0x8e, 0xe6, 0x78, 0x19, 0x67, 0xb6, 0x21, 0x99, 0xfd, 0x54, 0x29, 0x34, 0x3b, 0xfc, 0x23, 0xe3, 0x76, 0xea, 0x75, 0x3b, 0x23, 0xe3, 0x3e, 0x46, 0xc6, 0x7d, 0xd4, 0xed, 0x76, 0xe6, 0xd2, 0x4d, 0xcc, 0xa5, 0x9b, 0x18, 0x1d, 0xb7, 0x33, 0x3a, 0x6e, 0x67, 0x0e, 0xdd, 0xe4, 0x1f, 0xcb, 0x5f, 0xa4, 0xde, 0x5f, 0x42, 0x4f, 0x49, 0xa2, 0xde, 0x5b, 0x50, 0xef, 0x0f, 0xf8, 0x7f, 0x71, 0xf5, 0x2c, 0x97, 0xaf, 0xe2, 0x1a, 0xf9, 0xe3, 0x15, 0xd5, 0xc8, 0xd2, 0x4b, 0x3d, 0x62, 0xe9, 0xcb, 0xb6, 0x9f, 0x7a, 0xcc, 0x32, 0x94, 0xed, 0x30, 0x2e, 0x0f, 0x67, 0xfb, 0x06, 0x97, 0xc7, 0x70, 0x9b, 0x8f, 0xf0, 0x31, 0xc6, 0xe2, 0x13, 0x7c, 0x8a, 0xcf, 0xb0, 0x92, 0xdb, 0xfc, 0xf5, 0x7b, 0xe9, 0x8d, 0xfe, 0xfc, 0xbd, 0xf4, 0x0d, 0xaa, 0xa6, 0x65, 0x33, 0x7f, 0xdf, 0x82, 0x10, 0x84, 0x22, 0x0c, 0x5b, 0xb1, 0x0d, 0x7b, 0xb9, 0xed, 0x5f, 0xbf, 0xa3, 0xfe, 0xba, 0xe5, 0x00, 0x97, 0xff, 0xfa, 0x1d, 0xf5, 0xd7, 0x2d, 0x11, 0xaa, 0xbe, 0xe5, 0x28, 0xd7, 0xfd, 0xf5, 0x1b, 0xea, 0xaf, 0x5b, 0x22, 0xb9, 0xfc, 0xd7, 0x6f, 0xa8, 0xbf, 0x6e, 0x39, 0xad, 0x5a, 0xdf, 0xf2, 0x1b, 0xea, 0xef, 0x5b, 0x72, 0xb8, 0xfc, 0xd7, 0x6f, 0xa8, 0xbf, 0x6f, 0xb9, 0xa0, 0x6e, 0xb3, 0x5c, 0x54, 0x4d, 0xf4, 0xdb, 0x25, 0x49, 0xa7, 0xbd, 0xe9, 0x77, 0xe2, 0x2e, 0xd4, 0x44, 0x2d, 0xd4, 0x46, 0x1d, 0x30, 0x33, 0xe9, 0x75, 0x71, 0x0f, 0xee, 0xc5, 0x7d, 0xb8, 0x1f, 0xb4, 0x4f, 0x9d, 0xf6, 0xa9, 0xd3, 0x3e, 0xf5, 0x06, 0x68, 0x08, 0xda, 0xa9, 0xde, 0x08, 0x8d, 0xd1, 0x04, 0x4d, 0xf1, 0x10, 0x9a, 0xe1, 0x61, 0x34, 0x47, 0x0b, 0xb4, 0x84, 0xff, 0xdf, 0xdf, 0xfe, 0xf9, 0x7b, 0xed, 0xbf, 0xeb, 0x6d, 0xd5, 0x3c, 0xfd, 0x51, 0xb5, 0x5a, 0x6f, 0xc7, 0xdf, 0xda, 0x4b, 0x3e, 0xbd, 0x3e, 0x49, 0xef, 0x80, 0x7e, 0x32, 0x5e, 0xef, 0x8f, 0x01, 0x18, 0x88, 0x41, 0x18, 0x82, 0x57, 0x31, 0x14, 0xc3, 0xf0, 0x9a, 0xa4, 0xeb, 0xaf, 0x63, 0x38, 0xde, 0xc0, 0x08, 0x8c, 0xc4, 0x28, 0xbc, 0x89, 0xb7, 0xf0, 0x36, 0xde, 0xc1, 0xbb, 0x78, 0x0f, 0xef, 0x63, 0x34, 0x68, 0x7b, 0xfa, 0x87, 0x18, 0x83, 0x8f, 0xf0, 0x31, 0xc6, 0xe2, 0x13, 0x8c, 0x93, 0x31, 0xfa, 0xa7, 0xf8, 0x4c, 0x7e, 0xd2, 0x3f, 0xc7, 0x17, 0x18, 0x8f, 0x09, 0x98, 0x88, 0x29, 0x32, 0x4a, 0x9f, 0x8a, 0x69, 0xf8, 0x1a, 0xd3, 0xf1, 0x8d, 0xaa, 0xa7, 0xcf, 0x60, 0xfb, 0x2d, 0x66, 0x62, 0x16, 0x66, 0x63, 0x0e, 0xfe, 0xfe, 0x37, 0xe1, 0x97, 0xea, 0xdf, 0xab, 0xc7, 0xf4, 0x05, 0xaa, 0xa5, 0xbe, 0x90, 0xbf, 0x2d, 0x42, 0xb0, 0xbc, 0x65, 0x64, 0xc8, 0x41, 0x23, 0x13, 0x59, 0x92, 0x6a, 0x64, 0xe3, 0x34, 0xce, 0xe0, 0x2c, 0x9c, 0xc8, 0xc1, 0x45, 0xd9, 0x6b, 0xb8, 0xe0, 0xc6, 0x25, 0xe4, 0x22, 0x0f, 0xf9, 0xb8, 0x0c, 0x0f, 0x0a, 0x50, 0x88, 0x22, 0x14, 0xa3, 0x04, 0xa5, 0xb2, 0xb7, 0xfa, 0x37, 0xe1, 0x57, 0xfa, 0x7f, 0x13, 0xfe, 0x1f, 0x7f, 0x0f, 0x7e, 0xa5, 0xff, 0xf7, 0xe0, 0x6f, 0xfd, 0x2d, 0xf8, 0x83, 0xfe, 0xdc, 0x6b, 0x63, 0x4d, 0x68, 0x63, 0x4d, 0x68, 0x63, 0x4d, 0x68, 0x63, 0x4d, 0xd8, 0x91, 0xb5, 0xdf, 0x26, 0x6b, 0x3e, 0x7f, 0x63, 0x1c, 0xb5, 0x7a, 0x50, 0x80, 0x42, 0x14, 0xb1, 0xbe, 0x29, 0x46, 0x09, 0x4a, 0x51, 0x26, 0xe9, 0x41, 0xdd, 0xe5, 0xd1, 0xa0, 0x5d, 0x4a, 0x0b, 0xfa, 0x59, 0xc6, 0xfc, 0x4f, 0x7e, 0x47, 0x7e, 0x4c, 0xd0, 0x3e, 0xec, 0x57, 0xed, 0x83, 0x0e, 0x4a, 0x61, 0xd0, 0x21, 0xc6, 0xa8, 0xa9, 0x8c, 0x78, 0x2b, 0x18, 0xf1, 0x0e, 0x30, 0xd2, 0xe5, 0x30, 0xd2, 0x79, 0x18, 0xe9, 0x7c, 0xaf, 0xda, 0xf8, 0x72, 0xde, 0x56, 0x46, 0xab, 0x15, 0x8c, 0x56, 0x2b, 0x18, 0x49, 0xf6, 0x32, 0x82, 0xf8, 0x46, 0x8d, 0x95, 0x8c, 0x14, 0x2b, 0x18, 0x25, 0x56, 0x30, 0x4a, 0xfc, 0xc6, 0x28, 0xb1, 0x82, 0x9e, 0x5b, 0x8b, 0x9e, 0x5b, 0x9f, 0x9e, 0x5b, 0x8b, 0x9e, 0xdb, 0x94, 0x9e, 0x5b, 0x8b, 0x9e, 0x5b, 0x9f, 0x9e, 0x5b, 0x8b, 0x9e, 0xdb, 0x94, 0xde, 0x59, 0x8b, 0xde, 0x19, 0x4e, 0xef, 0x4c, 0xa0, 0x77, 0xd6, 0xa2, 0x77, 0x86, 0xd3, 0x3b, 0x13, 0xe9, 0x7d, 0xb5, 0xe8, 0x7d, 0xe1, 0xf4, 0xbe, 0x04, 0x7a, 0x5f, 0x2f, 0x7a, 0x5f, 0x2d, 0x7a, 0x5f, 0x38, 0xbd, 0x2f, 0x91, 0xde, 0xd7, 0x8b, 0xde, 0x77, 0x07, 0xbd, 0xaf, 0x16, 0xbd, 0x2f, 0x9c, 0xde, 0x97, 0x40, 0xef, 0xeb, 0x45, 0xef, 0xab, 0x45, 0xef, 0x0b, 0xa7, 0xf7, 0x25, 0xd2, 0xfb, 0x7a, 0xd1, 0xfb, 0x1a, 0xd0, 0xfb, 0x62, 0xe8, 0x7d, 0x67, 0xe8, 0x7d, 0x43, 0xe9, 0x7d, 0x0d, 0xe8, 0x7d, 0x89, 0xf4, 0xbe, 0x1c, 0x7a, 0xdf, 0x50, 0x7a, 0x5e, 0x1d, 0x5a, 0xff, 0x72, 0x5a, 0x7f, 0x18, 0x2d, 0x7f, 0x39, 0x2d, 0x7f, 0x1b, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x32, 0x87, 0x16, 0x99, 0x43, 0x8b, 0xf4, 0xd0, 0x22, 0x3d, 0xb4, 0x48, 0x0f, 0x2d, 0xd2, 0x43, 0x8b, 0xf4, 0xd0, 0x22, 0x3d, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0x26, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xf6, 0xb4, 0xc8, 0x27, 0x69, 0x91, 0xed, 0x68, 0x91, 0x53, 0x69, 0x91, 0x2d, 0x68, 0x91, 0x8d, 0x69, 0x91, 0x7b, 0x69, 0x91, 0x7b, 0x69, 0x91, 0xbf, 0xd1, 0x62, 0xa6, 0xd3, 0x62, 0xe6, 0xd3, 0x62, 0x6a, 0xd2, 0x62, 0xa6, 0xd3, 0x62, 0x16, 0xd3, 0x62, 0x6a, 0xd2, 0x62, 0xa6, 0xd3, 0x62, 0xe6, 0xd3, 0x62, 0x6a, 0xd2, 0x62, 0xa6, 0xd3, 0x62, 0x16, 0xd3, 0x62, 0x6a, 0xd2, 0x5a, 0x2e, 0xd2, 0x5a, 0x2e, 0xd2, 0x5a, 0x2e, 0xd2, 0x5a, 0x2e, 0x52, 0xfb, 0x97, 0x82, 0x76, 0xc9, 0x55, 0x6a, 0x7f, 0x25, 0xb5, 0xdf, 0x9c, 0xda, 0xef, 0x40, 0xed, 0x8f, 0x0d, 0xda, 0x23, 0x42, 0xed, 0xd7, 0xa1, 0xf6, 0x57, 0x52, 0xfb, 0x2b, 0xa9, 0xfd, 0x7b, 0xd5, 0x93, 0xd4, 0xfa, 0x52, 0x6a, 0x7d, 0xff, 0x2d, 0xb5, 0xbe, 0xa7, 0xba, 0xd6, 0x43, 0xa8, 0xf5, 0xa5, 0xd4, 0xfa, 0x52, 0x6a, 0x7d, 0x0f, 0xb5, 0xbe, 0x87, 0x1a, 0x5f, 0x4a, 0x8d, 0x2f, 0xad, 0xae, 0xf1, 0xa5, 0x94, 0xe0, 0x52, 0x4a, 0x70, 0x29, 0x25, 0xb8, 0x94, 0x12, 0x5c, 0x4a, 0x09, 0x2e, 0xa5, 0x04, 0x97, 0x52, 0x82, 0x4b, 0x29, 0xc1, 0xa5, 0x94, 0xe0, 0xd2, 0x7f, 0xa3, 0x04, 0xf7, 0x50, 0x82, 0x7b, 0x28, 0xc1, 0x3d, 0x94, 0xe0, 0x1e, 0x4a, 0x70, 0x0f, 0x25, 0xd8, 0x8e, 0x12, 0xdc, 0x43, 0x09, 0xee, 0xa1, 0x04, 0xf7, 0x50, 0x82, 0x7b, 0x28, 0xc1, 0x3d, 0x94, 0xe0, 0x1e, 0x7d, 0xae, 0xe4, 0xea, 0xdf, 0x63, 0x01, 0x16, 0x72, 0x79, 0x11, 0x7c, 0xa5, 0xf6, 0x8f, 0xa5, 0xf0, 0xbb, 0x7f, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0x83, 0x19, 0xde, 0x71, 0xcb, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0x45, 0x09, 0x8c, 0xa5, 0x04, 0x96, 0x51, 0x02, 0xc3, 0x99, 0xe1, 0x0f, 0x33, 0xc3, 0xa7, 0x57, 0xcf, 0xf0, 0x0e, 0x66, 0xf8, 0x04, 0x66, 0xf8, 0x5e, 0xcc, 0xf0, 0x2b, 0x99, 0xe1, 0x7d, 0x9f, 0x64, 0x3f, 0xcc, 0x0c, 0x7f, 0x98, 0x19, 0xbe, 0x80, 0x19, 0xde, 0x41, 0x89, 0x45, 0x33, 0xc3, 0xc7, 0x30, 0xc3, 0x77, 0xa0, 0xe4, 0x66, 0x32, 0xc3, 0x3b, 0x6e, 0x99, 0xe1, 0x77, 0x30, 0xc3, 0x6f, 0x60, 0x86, 0x3f, 0xfa, 0x2f, 0x66, 0xf8, 0x5f, 0x98, 0xe1, 0x53, 0x98, 0xe1, 0xdd, 0xcc, 0xf0, 0xc5, 0xcc, 0xf0, 0x21, 0xa6, 0x05, 0x6c, 0x17, 0x92, 0x47, 0xdc, 0x1c, 0xeb, 0x92, 0x64, 0x55, 0xcf, 0xf2, 0x8e, 0xea, 0x59, 0xbe, 0x94, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0xa8, 0x9e, 0xe1, 0x87, 0x33, 0xc3, 0x3b, 0x98, 0xe1, 0xd3, 0xa8, 0xa9, 0xe1, 0xcc, 0xf0, 0x59, 0xcc, 0xf0, 0xf6, 0xea, 0x19, 0xfe, 0x43, 0x66, 0xf8, 0xa9, 0xcc, 0xf0, 0x0e, 0x66, 0xf8, 0x9c, 0xea, 0x19, 0xde, 0x51, 0x3d, 0xc3, 0x7f, 0x52, 0x3d, 0xc3, 0x77, 0x60, 0x86, 0xef, 0xc0, 0x0c, 0xff, 0x39, 0x33, 0xfc, 0xe7, 0xcc, 0xf0, 0x1d, 0x98, 0xe1, 0x4b, 0x99, 0xe1, 0x1d, 0xcc, 0xf0, 0x3b, 0x98, 0xe1, 0x77, 0x30, 0xc3, 0x1f, 0x62, 0x86, 0x3f, 0xc4, 0x0c, 0xbf, 0x83, 0x19, 0x7e, 0x03, 0x33, 0xfc, 0x06, 0x66, 0xf8, 0x9d, 0xcc, 0xf0, 0x3b, 0x99, 0xe1, 0x37, 0x30, 0xc3, 0xc7, 0x31, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x8e, 0xea, 0x19, 0xbe, 0x0e, 0x33, 0xbc, 0x9d, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0x60, 0x86, 0x77, 0xfc, 0x1b, 0x33, 0xbc, 0x83, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x8e, 0xff, 0xc5, 0x0c, 0xef, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0xe3, 0x3f, 0x3c, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0x83, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0x83, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0x83, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0x83, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0xbe, 0x65, 0x86, 0x77, 0x33, 0xce, 0x2d, 0x64, 0x9c, 0x5b, 0xcf, 0x0c, 0xef, 0x60, 0x86, 0x2f, 0x64, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0xfe, 0xcd, 0x19, 0x3e, 0x8b, 0x19, 0x3e, 0x8b, 0x19, 0x3e, 0x8b, 0x19, 0x3e, 0x4b, 0xf7, 0x7d, 0x47, 0xc1, 0x48, 0x8c, 0xc2, 0x9b, 0x78, 0x0b, 0x6f, 0xe3, 0x1d, 0xbc, 0x8b, 0xf7, 0xf0, 0x3e, 0x46, 0x83, 0xb6, 0xc7, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0xc5, 0x68, 0x30, 0x96, 0xd1, 0x60, 0x2c, 0xa3, 0xc1, 0x32, 0x46, 0x83, 0x65, 0x8c, 0x06, 0xcb, 0x18, 0x0d, 0x96, 0x31, 0x1a, 0x2c, 0x63, 0x34, 0x58, 0xc6, 0x68, 0x30, 0x9c, 0xd1, 0x60, 0x38, 0xa3, 0xc1, 0x70, 0x46, 0x83, 0xe1, 0x8c, 0x06, 0xc3, 0xab, 0x67, 0xf8, 0xe1, 0x8c, 0x06, 0xc3, 0x19, 0x0d, 0x86, 0x33, 0x1a, 0x0c, 0x67, 0x34, 0x18, 0xce, 0x68, 0x30, 0xfc, 0x5f, 0xcc, 0xf0, 0xc3, 0x19, 0x19, 0x86, 0x33, 0x32, 0x8c, 0x64, 0x86, 0x3f, 0xcc, 0x0c, 0x7f, 0x98, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0xfd, 0x3f, 0x3c, 0xc3, 0x3b, 0x18, 0xad, 0xa2, 0x19, 0xad, 0xa2, 0x19, 0xad, 0xa2, 0x19, 0xad, 0xa2, 0x99, 0xe1, 0x3b, 0x30, 0xc3, 0x6f, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0x7c, 0x16, 0x33, 0x7c, 0x16, 0x33, 0x7c, 0x16, 0x33, 0x7c, 0x16, 0x33, 0x7c, 0x16, 0xa3, 0x5b, 0xf3, 0xea, 0x19, 0xfe, 0xc3, 0xff, 0xc9, 0x0c, 0xff, 0x21, 0x63, 0xfc, 0x87, 0xd5, 0x33, 0x7c, 0x29, 0x33, 0xbc, 0x43, 0x75, 0x55, 0xcf, 0x90, 0x43, 0x46, 0xa9, 0x27, 0x54, 0xac, 0x7c, 0xa7, 0xe2, 0x65, 0x96, 0x4a, 0x67, 0x24, 0x63, 0xcd, 0xa3, 0x72, 0xe5, 0x37, 0x55, 0x46, 0x2e, 0xb9, 0x8b, 0x64, 0x5f, 0x47, 0x7e, 0xf5, 0x65, 0x67, 0xd2, 0x54, 0x0b, 0x46, 0x8b, 0x54, 0x73, 0x94, 0x32, 0x05, 0x46, 0xaa, 0x67, 0x02, 0x9d, 0xea, 0x1e, 0x4b, 0x07, 0x99, 0x65, 0x79, 0x5c, 0x96, 0x59, 0x7a, 0xb2, 0x7d, 0x85, 0x34, 0xdf, 0x0b, 0xbd, 0xd9, 0xef, 0x83, 0xbe, 0xec, 0xf7, 0x43, 0x7f, 0xf6, 0x07, 0x60, 0x20, 0x06, 0x61, 0x30, 0x86, 0xe0, 0x55, 0x0c, 0xe5, 0xef, 0xc3, 0xf0, 0x1a, 0xfb, 0xaf, 0x63, 0x38, 0xfb, 0x6f, 0x60, 0x04, 0xfb, 0xef, 0x48, 0x4f, 0xcb, 0x4e, 0x35, 0xda, 0x12, 0xa7, 0xee, 0xb3, 0xc4, 0xab, 0x87, 0x2c, 0xc9, 0x6a, 0xb2, 0xe5, 0x94, 0xaa, 0x63, 0xb9, 0x4c, 0x6b, 0xac, 0xab, 0x7a, 0xd2, 0x0a, 0x92, 0xf5, 0x2d, 0xaa, 0x9b, 0xbe, 0x5f, 0x2a, 0x75, 0xa7, 0x6a, 0xa0, 0x5f, 0x66, 0x5d, 0x56, 0x5b, 0x35, 0x34, 0xea, 0xa9, 0x0e, 0xfe, 0x6f, 0x38, 0x6b, 0xac, 0xea, 0x1b, 0xcd, 0xd4, 0xe3, 0x46, 0x73, 0x15, 0x64, 0xb4, 0x56, 0x6b, 0x8c, 0x76, 0xea, 0x5e, 0xe3, 0x39, 0xff, 0x77, 0xe5, 0xdf, 0x63, 0xcc, 0x53, 0x1d, 0x49, 0x7c, 0x67, 0xac, 0xf7, 0xab, 0x87, 0xac, 0x1f, 0xf9, 0xbf, 0x09, 0xf8, 0xbf, 0xa7, 0x24, 0x66, 0xfd, 0x7f, 0xbe, 0x24, 0x1e, 0x52, 0x9a, 0x14, 0x53, 0x1a, 0xa9, 0x94, 0x46, 0x43, 0xf5, 0xa6, 0xff, 0xdd, 0xbd, 0x59, 0x3c, 0xf3, 0xed, 0xfe, 0x57, 0x93, 0xda, 0xfa, 0x3f, 0xf9, 0xd5, 0x82, 0x54, 0x1d, 0x6c, 0xa2, 0xf7, 0x9b, 0x46, 0x49, 0x81, 0x36, 0xce, 0xff, 0x7b, 0x15, 0x8f, 0x71, 0x96, 0xdd, 0x38, 0x3b, 0x9d, 0xb3, 0x9a, 0xc0, 0xd9, 0x34, 0xe1, 0x6c, 0x9c, 0xba, 0x53, 0xc4, 0xff, 0x4d, 0xf8, 0xf5, 0x54, 0x13, 0xce, 0xe4, 0x69, 0xa3, 0xa1, 0x6a, 0x6e, 0x3c, 0xa8, 0x5a, 0x19, 0x8d, 0x78, 0x64, 0xdf, 0x6f, 0xe3, 0x34, 0x51, 0xf5, 0x8c, 0xa6, 0x5c, 0x6e, 0xe6, 0xff, 0xfe, 0xff, 0x67, 0x8d, 0x47, 0x54, 0x03, 0xce, 0x70, 0x3e, 0x67, 0xf8, 0x9c, 0xf1, 0x96, 0x54, 0x70, 0x76, 0xcd, 0x38, 0xbb, 0x34, 0xce, 0xa4, 0xae, 0x94, 0x33, 0xe7, 0x9e, 0x61, 0xce, 0x1d, 0xc5, 0x9c, 0xbb, 0x88, 0xba, 0xf1, 0xfd, 0xdb, 0xa0, 0xbd, 0xcc, 0xb9, 0xbf, 0x32, 0xe7, 0x5e, 0xf0, 0xbf, 0xdb, 0x12, 0x28, 0xdf, 0x33, 0xa7, 0xee, 0x23, 0xed, 0x3e, 0xa4, 0xf5, 0x53, 0xf7, 0x33, 0xf7, 0x9d, 0x62, 0xce, 0x3b, 0x49, 0x5d, 0x65, 0x33, 0xaf, 0x4d, 0x66, 0x3e, 0x3a, 0xce, 0x1c, 0xd4, 0x9a, 0x39, 0xa8, 0x39, 0x73, 0xd0, 0x49, 0xfd, 0x11, 0xc6, 0xc0, 0xb6, 0x78, 0x14, 0x03, 0x64, 0x9a, 0x7f, 0x5c, 0x99, 0x2b, 0x51, 0xac, 0x26, 0xa2, 0x58, 0x4d, 0x44, 0x19, 0x6b, 0xd4, 0x1d, 0xf4, 0xab, 0x76, 0xff, 0xb1, 0x47, 0xbf, 0xed, 0x9f, 0x3e, 0xfa, 0x6d, 0xac, 0x49, 0x7e, 0xe4, 0xe8, 0x53, 0x38, 0xca, 0x66, 0x8e, 0x50, 0xc2, 0x11, 0x4a, 0x28, 0xe5, 0x02, 0xee, 0x35, 0x86, 0x7b, 0x35, 0xe6, 0x5e, 0xd9, 0xdc, 0x2b, 0x9b, 0x7b, 0x65, 0x73, 0x8f, 0xcf, 0x54, 0x7d, 0xee, 0x31, 0x43, 0xd9, 0x58, 0x89, 0x24, 0x4a, 0x3c, 0xe7, 0x1b, 0xa5, 0xce, 0x48, 0x0c, 0xe7, 0xeb, 0x51, 0xbf, 0x53, 0xb7, 0x2e, 0xff, 0xf7, 0x3d, 0xa5, 0x52, 0xaf, 0x27, 0x39, 0xea, 0x27, 0xa6, 0x20, 0x49, 0xa7, 0x7e, 0x8f, 0x73, 0xee, 0xb4, 0x68, 0x09, 0xae, 0x7e, 0x47, 0xa9, 0x98, 0x47, 0xfa, 0x85, 0x47, 0xfa, 0x85, 0x7a, 0xcd, 0xe3, 0xd1, 0x7e, 0xe6, 0xd1, 0x46, 0xea, 0x93, 0x24, 0x96, 0x47, 0xac, 0xcf, 0x23, 0xc6, 0xf2, 0x88, 0xb1, 0x3c, 0x62, 0xac, 0xbe, 0x84, 0xd1, 0xa4, 0xbb, 0x0c, 0x34, 0x87, 0x2a, 0xb3, 0x2c, 0x50, 0x01, 0x08, 0x94, 0x0f, 0x59, 0x67, 0x79, 0x58, 0x67, 0x79, 0x58, 0x67, 0x79, 0x54, 0x6d, 0x4a, 0xec, 0x01, 0xb6, 0xf5, 0x50, 0x1f, 0x0f, 0xa2, 0xb9, 0x32, 0x54, 0x2b, 0x65, 0x55, 0xad, 0xe5, 0x67, 0xd5, 0x06, 0x6d, 0xd1, 0x4e, 0x36, 0xab, 0xf6, 0x78, 0x0c, 0x1d, 0xf0, 0x38, 0x9e, 0xc0, 0x93, 0x78, 0x0a, 0x4f, 0xe3, 0x19, 0x89, 0x50, 0x1d, 0xd9, 0x76, 0xc2, 0xff, 0xfb, 0xdf, 0xf3, 0xba, 0x5d, 0x8d, 0x90, 0x2d, 0xea, 0x03, 0x4a, 0xf7, 0x23, 0x7c, 0x8c, 0xb1, 0xf8, 0x84, 0x73, 0x1b, 0x87, 0x4f, 0xf1, 0x19, 0x97, 0xbf, 0x60, 0x3b, 0x9e, 0xed, 0x04, 0x5a, 0xcc, 0x44, 0x4c, 0xc2, 0x64, 0x54, 0x7d, 0x27, 0x6c, 0xae, 0x9a, 0x8a, 0x69, 0xf8, 0x1a, 0xd3, 0xf1, 0x0d, 0x66, 0xe0, 0x5b, 0xcc, 0xc4, 0x2c, 0xcc, 0x66, 0xbd, 0x39, 0x07, 0xf3, 0xa8, 0xbd, 0xef, 0x19, 0x9b, 0x62, 0xe5, 0x07, 0xe5, 0xfb, 0xac, 0x47, 0x3c, 0xe5, 0x6d, 0xe7, 0x38, 0xc9, 0x72, 0x49, 0x91, 0x08, 0xab, 0xbf, 0xeb, 0x23, 0x83, 0x5a, 0xff, 0x9d, 0x31, 0x2c, 0x49, 0x65, 0xc8, 0x2f, 0xea, 0x14, 0xfb, 0x59, 0x1c, 0x23, 0x5b, 0xe2, 0xd4, 0x69, 0x5a, 0xc4, 0x59, 0xb6, 0xe7, 0xb8, 0xfc, 0xbb, 0x9c, 0x56, 0xe7, 0xb9, 0xcf, 0x05, 0xce, 0xef, 0x22, 0x7c, 0xdf, 0x30, 0xe2, 0xe6, 0x31, 0x2e, 0xb1, 0x9f, 0x2b, 0xeb, 0xab, 0xbf, 0xf3, 0x24, 0x41, 0x31, 0xdf, 0xd1, 0x5a, 0x56, 0xa8, 0x4a, 0x59, 0xa7, 0xae, 0xe1, 0x06, 0x8f, 0xeb, 0xfb, 0x14, 0xa0, 0x48, 0x3e, 0xeb, 0x5a, 0x0f, 0x2d, 0xe8, 0xa2, 0xe9, 0x4e, 0x5a, 0xd1, 0x5d, 0x72, 0xd6, 0x54, 0x93, 0x35, 0x6f, 0x2d, 0xd4, 0x46, 0x1d, 0xf9, 0xdc, 0x74, 0xb7, 0x64, 0x98, 0xea, 0xd2, 0xba, 0xee, 0xe1, 0xf2, 0xbd, 0xb8, 0x4f, 0x3e, 0x36, 0xdd, 0x8f, 0x07, 0xd8, 0xaf, 0x87, 0xfa, 0x68, 0x20, 0x9b, 0x4d, 0x0d, 0xd9, 0xd2, 0x2e, 0x4c, 0x8d, 0xd9, 0x6f, 0xc2, 0x7e, 0x53, 0x3c, 0x24, 0x2e, 0x53, 0x33, 0xb6, 0x0f, 0xc3, 0xf7, 0xae, 0x64, 0x0b, 0xb6, 0x2d, 0xd1, 0x4a, 0x4e, 0x98, 0x1e, 0xe1, 0x76, 0xad, 0xd9, 0x6f, 0x23, 0x1b, 0x4d, 0x3d, 0xfe, 0x78, 0xd6, 0xf4, 0xb9, 0x1c, 0x62, 0xcd, 0x1c, 0x67, 0x1a, 0xcf, 0xdf, 0x26, 0xf0, 0x78, 0x13, 0xe5, 0x0b, 0xd3, 0x24, 0xd9, 0x67, 0x9a, 0x4c, 0x56, 0x9d, 0xc2, 0x98, 0x3d, 0x9f, 0xf5, 0xb4, 0x9b, 0xe3, 0xe7, 0x72, 0xbf, 0x3c, 0x49, 0x30, 0x15, 0xcb, 0x19, 0x53, 0x09, 0xe7, 0x5b, 0x2a, 0x79, 0xac, 0xa1, 0x3d, 0xac, 0x9d, 0x3d, 0xac, 0x9d, 0x3d, 0xda, 0x6b, 0xca, 0xca, 0xba, 0xd9, 0xa3, 0x8d, 0x56, 0x26, 0xed, 0x73, 0xd9, 0xac, 0x51, 0x77, 0xda, 0x24, 0x89, 0xd1, 0xa6, 0x80, 0x3a, 0xd2, 0xa8, 0x23, 0xed, 0x1b, 0xc9, 0xd1, 0x66, 0xc9, 0x41, 0x6d, 0xb6, 0x6c, 0xd7, 0xe6, 0xf2, 0xf7, 0x79, 0x5c, 0x0e, 0x26, 0x07, 0xff, 0xc8, 0xfe, 0x2a, 0xf6, 0xff, 0xfd, 0xef, 0xe3, 0xcd, 0xd5, 0xb6, 0xe3, 0x37, 0x39, 0xc8, 0x5a, 0xdc, 0x63, 0xde, 0x2c, 0x19, 0xe6, 0x2d, 0xb2, 0xd9, 0x1c, 0x22, 0x89, 0x8c, 0x1a, 0x71, 0xe6, 0x6d, 0xb2, 0xc9, 0xbc, 0x1d, 0x3b, 0xb8, 0x6e, 0x17, 0x76, 0xe3, 0x04, 0xb7, 0x8b, 0x52, 0x9a, 0x39, 0x51, 0xd6, 0xb1, 0x16, 0xf7, 0x98, 0xb3, 0xe4, 0x57, 0x73, 0x36, 0xce, 0x72, 0xf9, 0x82, 0xc4, 0x98, 0x2f, 0xc2, 0x05, 0x37, 0x2e, 0x71, 0xbc, 0x5c, 0xb6, 0x79, 0xc8, 0xc7, 0x65, 0x6e, 0xef, 0x61, 0x5b, 0x80, 0x42, 0x14, 0x71, 0xbc, 0x62, 0xae, 0x2b, 0x91, 0x02, 0xd6, 0xf1, 0x05, 0xe6, 0x32, 0xb9, 0xcc, 0x5a, 0xde, 0xc3, 0x5a, 0xde, 0xc5, 0x5a, 0xde, 0x65, 0xae, 0xe0, 0x36, 0x95, 0x72, 0x94, 0xf5, 0xfc, 0x51, 0xf3, 0x75, 0xf6, 0x6f, 0xc8, 0xc7, 0x66, 0x2f, 0xdb, 0x9b, 0xf8, 0x83, 0xbf, 0x93, 0x6f, 0x02, 0x94, 0x1c, 0x64, 0x64, 0xcb, 0x0b, 0xd0, 0x64, 0x73, 0x80, 0x99, 0xcb, 0x01, 0x08, 0x94, 0xcb, 0x8c, 0x20, 0x97, 0x03, 0x2c, 0x5c, 0x67, 0x95, 0x8f, 0x03, 0x82, 0xb8, 0xee, 0x45, 0xf1, 0x90, 0x03, 0x3c, 0xe4, 0x00, 0xdf, 0x67, 0xb3, 0x62, 0x02, 0xfa, 0xa0, 0x1f, 0x06, 0x70, 0x9b, 0x81, 0x18, 0x0c, 0xfa, 0x52, 0xc0, 0x0a, 0x46, 0x9d, 0x66, 0xb2, 0x2d, 0xf0, 0x61, 0x39, 0x10, 0xd8, 0x5c, 0xbe, 0x0f, 0x6c, 0x21, 0x3f, 0x07, 0xb6, 0xe4, 0xba, 0x56, 0xb2, 0x2b, 0xf0, 0x11, 0xb4, 0x96, 0xfd, 0x81, 0x6d, 0x64, 0x76, 0x60, 0x5b, 0xb9, 0xf0, 0xe7, 0xf7, 0x18, 0x47, 0xaa, 0xce, 0xff, 0xec, 0xbb, 0x8c, 0xc9, 0x18, 0x1e, 0xe6, 0xe6, 0xfa, 0x81, 0xbf, 0x33, 0x6e, 0xd2, 0xa6, 0x03, 0x3d, 0x92, 0x10, 0x58, 0xc0, 0x28, 0x5c, 0xc8, 0xfe, 0x55, 0xfe, 0x56, 0xc1, 0xb6, 0x52, 0x22, 0xc9, 0x20, 0x9e, 0x1a, 0xb4, 0xb9, 0x1a, 0xcd, 0xe4, 0x66, 0x8d, 0x6b, 0xb2, 0xd9, 0xd2, 0x4a, 0x16, 0x58, 0x1e, 0x41, 0x6b, 0xb4, 0x41, 0x5b, 0x3c, 0x8a, 0x76, 0x68, 0x8f, 0xc7, 0xd0, 0x01, 0x8f, 0xcb, 0x1a, 0xcb, 0x13, 0x12, 0x6c, 0x79, 0x92, 0xfd, 0xa7, 0xf0, 0x34, 0x9e, 0x41, 0x47, 0x74, 0xc2, 0xb3, 0x78, 0x0e, 0xcf, 0xa3, 0x33, 0xba, 0xa0, 0x2b, 0xba, 0xa1, 0x3b, 0x5e, 0x40, 0x0f, 0xbc, 0x88, 0x97, 0xd0, 0x53, 0xa2, 0xc9, 0x42, 0x0b, 0xc8, 0x42, 0x1b, 0x59, 0x3b, 0x44, 0xb3, 0x76, 0x38, 0x41, 0x26, 0x5a, 0x44, 0x26, 0xda, 0xc0, 0xda, 0xe1, 0x04, 0x6b, 0x87, 0xb5, 0xac, 0x1d, 0xfe, 0x60, 0xed, 0xb0, 0x80, 0xb5, 0xc3, 0x02, 0xd6, 0x0e, 0x0b, 0x58, 0x3b, 0x44, 0x93, 0x97, 0x16, 0x90, 0x97, 0x36, 0xb2, 0x76, 0x88, 0x66, 0xed, 0x70, 0x82, 0xdc, 0xb4, 0x88, 0xdc, 0xb4, 0x81, 0xb5, 0xc3, 0x09, 0xcb, 0x48, 0xce, 0x6f, 0x14, 0xde, 0xe4, 0xb6, 0x6f, 0xc9, 0x87, 0x96, 0xb7, 0xf1, 0x8e, 0x7c, 0x44, 0xa6, 0xf2, 0x90, 0xa9, 0x3c, 0x64, 0x2a, 0x0f, 0x99, 0xca, 0x43, 0xa6, 0xf2, 0x90, 0xa9, 0x3c, 0x64, 0x2a, 0x0f, 0x99, 0x2a, 0xc2, 0xb2, 0xca, 0x14, 0x66, 0x59, 0x6d, 0x8a, 0x20, 0x53, 0xfd, 0x6a, 0x59, 0x6b, 0xda, 0x66, 0x59, 0x67, 0x3a, 0x6c, 0x59, 0x4f, 0xa6, 0xd9, 0xa0, 0xb6, 0x93, 0xab, 0x3c, 0xe4, 0x2a, 0x0f, 0xb9, 0xca, 0x43, 0xae, 0xf2, 0x90, 0xab, 0x3c, 0xe4, 0x2a, 0x0f, 0xb9, 0xca, 0xc3, 0x3a, 0xe5, 0x03, 0xb2, 0x55, 0x84, 0xe5, 0x37, 0xee, 0xbf, 0x8f, 0xfb, 0xef, 0x57, 0xfb, 0xc8, 0x56, 0xbf, 0x5a, 0x0e, 0x72, 0x8c, 0x43, 0x1c, 0x23, 0x9c, 0xfd, 0xc3, 0x1c, 0x27, 0x42, 0xed, 0x21, 0x5f, 0x45, 0x58, 0x8e, 0x71, 0xbb, 0xe3, 0xdc, 0xee, 0x04, 0xb7, 0x8b, 0xe4, 0x6f, 0x51, 0xdc, 0xee, 0x24, 0xb7, 0x8b, 0x66, 0x3f, 0x59, 0x7d, 0x49, 0xc6, 0x4a, 0xb4, 0x9c, 0x31, 0x1d, 0xb0, 0x9c, 0x35, 0x25, 0x92, 0xb1, 0xa2, 0xc9, 0x58, 0xc7, 0x2d, 0xe7, 0x4c, 0x87, 0x2c, 0xbf, 0x9b, 0x12, 0xc8, 0x58, 0xc7, 0xc9, 0x58, 0x5f, 0x91, 0xb1, 0x4e, 0x5a, 0xf2, 0x65, 0x11, 0xb3, 0xd4, 0x22, 0x8b, 0x47, 0x16, 0x91, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xac, 0x9b, 0xba, 0x91, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0xe8, 0xad, 0xd4, 0x4a, 0xfd, 0x11, 0xd3, 0x4c, 0xbd, 0xb5, 0x69, 0x85, 0xde, 0x86, 0xac, 0xd5, 0xd6, 0x34, 0x5b, 0x7f, 0xd4, 0xb4, 0x5c, 0x6f, 0xa7, 0x7a, 0xeb, 0xed, 0xd5, 0x6a, 0xf2, 0x96, 0x87, 0xbc, 0xe5, 0xd1, 0xc7, 0x29, 0x43, 0xff, 0x14, 0x53, 0x94, 0x55, 0x9f, 0x8a, 0x69, 0xf8, 0x1a, 0xd3, 0xf1, 0x8d, 0x3a, 0xa5, 0xcf, 0x60, 0xfb, 0x2d, 0x66, 0x62, 0x16, 0x66, 0x63, 0x0e, 0xe6, 0x9a, 0x6a, 0xeb, 0xdf, 0x69, 0x4d, 0xf4, 0x79, 0x5a, 0x7d, 0x7d, 0x3e, 0xfb, 0xdf, 0xab, 0x4b, 0xfa, 0x02, 0x53, 0x4d, 0x7d, 0x21, 0x7f, 0x5b, 0x84, 0x60, 0x56, 0x10, 0xcb, 0x99, 0xa9, 0x57, 0x60, 0x25, 0x56, 0x61, 0x35, 0xd6, 0x60, 0x2d, 0xd6, 0x61, 0x3d, 0x36, 0x60, 0x23, 0x36, 0x61, 0x33, 0xb6, 0xa8, 0x36, 0x7a, 0x08, 0xdb, 0x50, 0x84, 0x61, 0x2b, 0xb6, 0x61, 0x3b, 0x76, 0x60, 0x27, 0x76, 0xe1, 0x67, 0xfc, 0x82, 0x5f, 0xb1, 0x1b, 0x7b, 0xf1, 0x1b, 0xf6, 0x61, 0x3f, 0xcf, 0xeb, 0x00, 0xdb, 0x83, 0x38, 0x84, 0x70, 0x1c, 0x93, 0xcd, 0xfa, 0x71, 0x9c, 0x40, 0x24, 0x58, 0xbf, 0xe8, 0xd1, 0x88, 0x41, 0x2c, 0xe2, 0x10, 0x0f, 0x1b, 0x12, 0x61, 0x47, 0x12, 0x92, 0x91, 0x82, 0x54, 0xa4, 0xc1, 0x81, 0x74, 0x64, 0x20, 0x13, 0x59, 0xc8, 0xc6, 0x69, 0x9c, 0xc1, 0x59, 0x38, 0x95, 0x45, 0xcf, 0x61, 0x7b, 0x0e, 0xf4, 0x77, 0xfd, 0x3c, 0x98, 0xd3, 0x74, 0x17, 0xdc, 0xa0, 0xff, 0xeb, 0x8c, 0xfd, 0x7a, 0x1e, 0xf2, 0x71, 0x99, 0x15, 0xd8, 0x7f, 0xdf, 0xf7, 0x9c, 0x6f, 0x37, 0x14, 0x4c, 0xd0, 0x60, 0x46, 0x00, 0x02, 0x51, 0x43, 0xb6, 0x18, 0x16, 0xe8, 0x30, 0x60, 0x95, 0xcd, 0xc6, 0xed, 0x6c, 0xef, 0xc0, 0x9d, 0xf0, 0xfd, 0x16, 0x8c, 0xef, 0xdb, 0x94, 0x1b, 0xb1, 0x4a, 0xf6, 0xad, 0xd3, 0x9b, 0xb0, 0x46, 0x6f, 0xea, 0xff, 0x1d, 0xa8, 0x8e, 0xfe, 0x6f, 0xee, 0x6e, 0xad, 0x8e, 0x1a, 0x4f, 0x4b, 0x9c, 0xd1, 0x4d, 0x62, 0x8c, 0xee, 0x78, 0x01, 0x3d, 0xf0, 0x32, 0x7a, 0xe2, 0x15, 0x30, 0x9e, 0x1a, 0xbd, 0xc1, 0x98, 0x6a, 0xf4, 0x05, 0xe3, 0xaa, 0xd1, 0x1f, 0x03, 0x31, 0x88, 0xc7, 0x63, 0x6c, 0x35, 0x86, 0xe0, 0x55, 0x0c, 0xc5, 0x30, 0xbc, 0x86, 0xd7, 0x31, 0x1c, 0x6f, 0x60, 0x04, 0x46, 0x62, 0x14, 0xde, 0xc4, 0x5b, 0xac, 0xdc, 0xdf, 0x66, 0xfb, 0x0e, 0xde, 0xc5, 0x7b, 0x78, 0x1f, 0xa3, 0xf1, 0x01, 0x3e, 0xc4, 0x18, 0x7c, 0x84, 0x8f, 0x31, 0x16, 0xac, 0x7b, 0x0c, 0xd6, 0x3d, 0x06, 0xeb, 0x1e, 0xe3, 0x33, 0x30, 0x7f, 0x1a, 0xcc, 0x9f, 0xc6, 0x78, 0x4c, 0xc0, 0x44, 0x4c, 0xc2, 0x64, 0x89, 0x30, 0xbe, 0x64, 0xfb, 0x15, 0xe7, 0xc7, 0xbc, 0x6a, 0x4c, 0xc5, 0x74, 0x7c, 0x83, 0x19, 0x92, 0x66, 0x7c, 0xcb, 0x76, 0x26, 0x66, 0x61, 0x36, 0xe6, 0xe0, 0x3b, 0xcc, 0x53, 0x2d, 0x8d, 0xf9, 0x64, 0xec, 0xef, 0xb1, 0x00, 0x0b, 0xb1, 0x08, 0xcc, 0xbd, 0xc6, 0x0f, 0x58, 0x8c, 0x1f, 0xb1, 0x04, 0xcb, 0x24, 0xd7, 0x58, 0x8e, 0x15, 0x58, 0x89, 0x55, 0x58, 0x8d, 0x35, 0x58, 0x8b, 0x75, 0x58, 0x8f, 0x0d, 0xd8, 0x88, 0x4d, 0xd8, 0x8c, 0x2d, 0x08, 0x41, 0x28, 0xc2, 0xb0, 0x15, 0xcc, 0xd3, 0x06, 0xf3, 0xb4, 0xb1, 0x03, 0x3b, 0xb1, 0x0b, 0x3f, 0xe3, 0x17, 0xfc, 0x8a, 0xdd, 0xd8, 0x83, 0xbd, 0xf8, 0x0d, 0xfb, 0xb0, 0x1f, 0x07, 0x70, 0x10, 0x87, 0x10, 0x8e, 0xc3, 0x88, 0xc0, 0x11, 0x49, 0x30, 0x8e, 0xe2, 0x18, 0x8e, 0xe3, 0x04, 0x22, 0x11, 0x85, 0x93, 0x88, 0x16, 0xb7, 0x11, 0xc3, 0x36, 0x16, 0x71, 0x88, 0x87, 0x0d, 0x09, 0x48, 0x84, 0x1d, 0x49, 0x48, 0x46, 0x0a, 0x52, 0xe1, 0x40, 0xba, 0x64, 0x18, 0xe7, 0xe4, 0xb2, 0xf1, 0x3b, 0xce, 0xa3, 0x4c, 0x72, 0x8c, 0x2b, 0x28, 0xc7, 0x55, 0x54, 0xa0, 0x52, 0x72, 0xc8, 0x7a, 0xad, 0xac, 0x0f, 0x88, 0xdb, 0x5a, 0x4f, 0xbd, 0x6d, 0xad, 0x6f, 0xea, 0x6d, 0x6d, 0x60, 0x7a, 0xdb, 0xda, 0x50, 0x8d, 0xb0, 0x3e, 0xa8, 0x86, 0x59, 0x1b, 0x99, 0xfa, 0x5a, 0x1b, 0x9b, 0xde, 0xb4, 0x36, 0x61, 0xbf, 0xa9, 0x7a, 0xdc, 0xfa, 0x90, 0xfa, 0xd4, 0xda, 0x4c, 0x2e, 0x59, 0x1f, 0xe6, 0xb6, 0xcd, 0xb9, 0x6d, 0x0b, 0x6e, 0xdb, 0x92, 0xdb, 0xb6, 0xe2, 0xef, 0x8f, 0x70, 0xdb, 0xd6, 0xdc, 0xb6, 0x0d, 0xfb, 0x6d, 0x65, 0x93, 0xf5, 0x51, 0xb4, 0x43, 0x7b, 0x3c, 0x86, 0x0e, 0x78, 0x1c, 0x4f, 0xe0, 0x49, 0x3c, 0x85, 0xa7, 0xf1, 0x0c, 0x3a, 0xa2, 0x13, 0x9e, 0xc5, 0x73, 0x78, 0x1e, 0x9d, 0xd1, 0x05, 0x5d, 0xd1, 0x0d, 0xdd, 0xf1, 0x02, 0x7a, 0xe0, 0x45, 0xbc, 0x84, 0x97, 0xe5, 0x17, 0x6b, 0x4f, 0xbc, 0x82, 0x5e, 0xe8, 0x8d, 0x3e, 0xe8, 0x8b, 0x7e, 0xe8, 0x8f, 0x01, 0xf2, 0xbb, 0x75, 0x20, 0x06, 0x61, 0x30, 0x86, 0xe0, 0x55, 0x0c, 0xc5, 0x30, 0xbc, 0x86, 0xd7, 0x31, 0x1c, 0x6f, 0x60, 0x04, 0x46, 0x62, 0x14, 0xde, 0xc4, 0x5b, 0x78, 0x1b, 0xef, 0xe0, 0x5d, 0xbc, 0x87, 0xf7, 0x31, 0x1a, 0x1f, 0xe0, 0x43, 0x8c, 0x91, 0x78, 0xf2, 0xf2, 0xfd, 0xd6, 0x4f, 0xd8, 0x8e, 0x93, 0x0a, 0xeb, 0xa7, 0x4a, 0xb3, 0x7e, 0xc6, 0xfe, 0xe7, 0xa8, 0xfa, 0xee, 0xbf, 0x00, 0xeb, 0x04, 0xf6, 0x27, 0x72, 0x9b, 0xaa, 0xef, 0x22, 0xaa, 0x6f, 0xfd, 0x92, 0xcb, 0x5f, 0x49, 0x96, 0x75, 0x0a, 0xb7, 0x9f, 0xaa, 0xee, 0xb6, 0x4e, 0x53, 0xad, 0xad, 0xd3, 0x79, 0x4e, 0xdf, 0x60, 0x86, 0x24, 0x58, 0xbf, 0xc5, 0x4c, 0xcc, 0x02, 0xeb, 0x7d, 0x2b, 0xeb, 0x7d, 0xeb, 0x5c, 0x7c, 0x87, 0x79, 0x98, 0x8f, 0xef, 0xb1, 0x00, 0x0b, 0xb1, 0x08, 0xc1, 0xf8, 0x01, 0x8b, 0xf1, 0x23, 0x96, 0x60, 0x99, 0xec, 0xb5, 0x2e, 0xc7, 0x0a, 0xac, 0xc4, 0x2a, 0xac, 0xc6, 0x1a, 0xac, 0xc5, 0x3a, 0xac, 0xc7, 0x06, 0x6c, 0xc4, 0x26, 0x6c, 0xc6, 0x16, 0x84, 0x20, 0x14, 0x61, 0xd8, 0x8a, 0x6d, 0xd8, 0x8e, 0x1d, 0xd8, 0x89, 0x5d, 0x38, 0x28, 0x1e, 0xeb, 0x21, 0x75, 0x87, 0x35, 0x1c, 0x87, 0x11, 0x81, 0x48, 0x89, 0xb1, 0x46, 0x49, 0x86, 0xf5, 0x24, 0x62, 0xe5, 0xb8, 0x35, 0x8e, 0xcb, 0xf1, 0x48, 0x40, 0x22, 0xec, 0x48, 0x11, 0x97, 0x35, 0x95, 0x6d, 0x9a, 0x9c, 0xb0, 0x9e, 0x92, 0x22, 0x6b, 0x3e, 0xc7, 0x61, 0x4d, 0x6a, 0xf5, 0xa0, 0x00, 0x85, 0xb8, 0x22, 0x9b, 0xad, 0xe5, 0xb8, 0x0a, 0xd6, 0x67, 0xd6, 0x4a, 0x5c, 0xe3, 0x39, 0x5d, 0xc7, 0x0d, 0x78, 0x71, 0x53, 0x12, 0x82, 0x7e, 0x90, 0xc8, 0xa0, 0x5d, 0x6a, 0x4f, 0xd0, 0x2f, 0xda, 0xdd, 0x41, 0xbf, 0x6a, 0x8d, 0x82, 0x76, 0x9b, 0x02, 0x83, 0xf6, 0xa8, 0xc4, 0xa0, 0xbd, 0xa6, 0x9a, 0x41, 0xfb, 0xd9, 0x3f, 0x24, 0x9e, 0xa0, 0xc3, 0x72, 0x30, 0x28, 0x42, 0x36, 0x07, 0x31, 0xbf, 0xa8, 0x20, 0x35, 0x4a, 0xdd, 0x43, 0x36, 0x6f, 0x45, 0x36, 0x6f, 0xc1, 0x8a, 0x31, 0x88, 0x59, 0xef, 0x29, 0x66, 0x8d, 0xbb, 0xfd, 0xbf, 0x0c, 0xd7, 0x5a, 0x8d, 0xf0, 0xfd, 0x22, 0x9c, 0xf1, 0x16, 0xd6, 0xa8, 0xbb, 0x4c, 0x8b, 0xc8, 0xaf, 0x2e, 0xf2, 0xab, 0x8b, 0xfc, 0xea, 0x22, 0xbb, 0xba, 0xc8, 0xae, 0x2e, 0xb2, 0xab, 0x8b, 0xec, 0xea, 0x52, 0x4d, 0x65, 0x3a, 0x09, 0xfb, 0x7b, 0xd5, 0x4c, 0xdc, 0xea, 0x61, 0x34, 0x97, 0x69, 0xaa, 0x05, 0x79, 0xa9, 0x95, 0x8c, 0x21, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x53, 0xba, 0xc9, 0x94, 0x4e, 0x32, 0xa5, 0x93, 0x4c, 0xe9, 0x24, 0x53, 0x3a, 0xc9, 0x94, 0x4e, 0x32, 0xa5, 0x93, 0x4c, 0xe9, 0x24, 0x53, 0x3a, 0xc9, 0x94, 0x4e, 0x32, 0xa5, 0x53, 0xcd, 0x95, 0x3d, 0xea, 0x3b, 0xb2, 0xe0, 0x7c, 0x39, 0x40, 0x96, 0x74, 0x91, 0x23, 0x2f, 0xaa, 0x24, 0x32, 0x6d, 0x1a, 0xc7, 0x70, 0x90, 0x6b, 0x4f, 0xf9, 0xbf, 0x3b, 0xed, 0x3c, 0xb9, 0x31, 0x5a, 0x39, 0xb9, 0xad, 0xef, 0x3b, 0x45, 0x72, 0xc9, 0x99, 0xf9, 0xdc, 0xf6, 0x06, 0x6e, 0x92, 0x2f, 0x45, 0x12, 0x4d, 0x9a, 0xac, 0x31, 0x05, 0xca, 0x22, 0xb2, 0xa0, 0xcb, 0x74, 0x9b, 0x4c, 0x37, 0xdd, 0x0e, 0xdf, 0xe7, 0x6c, 0x1e, 0x94, 0x30, 0x32, 0xdc, 0x11, 0xb2, 0x9b, 0xef, 0xd3, 0x7b, 0xeb, 0x4d, 0xcf, 0xe3, 0x0d, 0x6e, 0x3b, 0x02, 0x5f, 0xc8, 0x1e, 0xd3, 0x97, 0x52, 0x6c, 0x9a, 0x43, 0x86, 0x9c, 0x4b, 0x46, 0x5b, 0xc8, 0xbe, 0x9b, 0xfb, 0x5c, 0x12, 0xb7, 0xd6, 0x85, 0x1c, 0xd5, 0x5d, 0x5c, 0xda, 0x0b, 0x6c, 0x7b, 0xaa, 0x6e, 0x64, 0x33, 0x17, 0xd9, 0xcc, 0xa5, 0x91, 0x13, 0xb4, 0xd7, 0x64, 0x0c, 0xf9, 0xcc, 0xa5, 0xbd, 0x2b, 0x67, 0xb5, 0xd1, 0xec, 0x7f, 0xc0, 0xed, 0x79, 0x9e, 0xe4, 0x32, 0xa7, 0x36, 0x5b, 0xdd, 0xae, 0xcd, 0x21, 0xcf, 0x2d, 0x91, 0x03, 0x64, 0x30, 0x37, 0x19, 0xcc, 0x4d, 0x06, 0x73, 0x93, 0xc1, 0xdc, 0x64, 0x30, 0x37, 0x19, 0xcc, 0x4d, 0x06, 0x73, 0x93, 0xc1, 0xdc, 0x64, 0x30, 0x37, 0x19, 0xcc, 0x4d, 0x06, 0x73, 0x92, 0xc1, 0x9c, 0xe4, 0x2f, 0xdf, 0xb7, 0x6a, 0xba, 0xc9, 0x5d, 0xd1, 0xe6, 0x23, 0x32, 0x9d, 0x9c, 0xe5, 0x32, 0x47, 0xb2, 0x8d, 0x92, 0x79, 0xe6, 0x68, 0xb6, 0x31, 0xb2, 0xc6, 0x1c, 0x8b, 0x78, 0xd9, 0x6b, 0xb6, 0x21, 0x99, 0x7d, 0x07, 0xb7, 0xb9, 0x2c, 0x61, 0x64, 0xa8, 0x30, 0x32, 0xd4, 0x6f, 0x64, 0xa8, 0xdf, 0xc8, 0x4f, 0x61, 0xe4, 0xa7, 0x23, 0xe4, 0xa7, 0x23, 0x64, 0xa7, 0x08, 0xb2, 0x53, 0x04, 0x59, 0xe9, 0x88, 0x3f, 0x23, 0xbd, 0x28, 0x2e, 0x32, 0x90, 0x8b, 0x0c, 0xe4, 0x22, 0xdf, 0xe4, 0x04, 0x76, 0x90, 0xe0, 0xc0, 0xc7, 0x71, 0x54, 0xdc, 0xe4, 0x18, 0x77, 0x60, 0x94, 0xe4, 0x91, 0x63, 0xdc, 0xe4, 0x18, 0x37, 0x39, 0xc6, 0x4d, 0x8e, 0x71, 0x93, 0x63, 0xdc, 0xe4, 0x18, 0x17, 0x79, 0xc5, 0xf7, 0x4d, 0xb9, 0x2e, 0x32, 0x8a, 0x93, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x4b, 0xaf, 0x8b, 0x7b, 0x70, 0x2f, 0xee, 0xc3, 0xfd, 0xa0, 0xfd, 0xb3, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xe9, 0xad, 0xd0, 0x06, 0xed, 0xd0, 0x1e, 0x8f, 0xa1, 0x03, 0xfa, 0xc9, 0x74, 0xbd, 0x3f, 0x06, 0x60, 0x20, 0x06, 0x61, 0x08, 0x5e, 0xc5, 0x50, 0x0c, 0x03, 0x6d, 0x5b, 0xa7, 0x6d, 0xeb, 0xb4, 0x6d, 0xfd, 0x0d, 0x8c, 0xc0, 0x48, 0x8c, 0xc2, 0x9b, 0x78, 0x0b, 0x6f, 0xe3, 0x1d, 0xbc, 0x8b, 0xf7, 0xf0, 0x3e, 0x46, 0x83, 0xb6, 0xa1, 0x7f, 0x88, 0x31, 0xf8, 0x08, 0x1f, 0x63, 0x2c, 0x3e, 0xc1, 0x38, 0x99, 0xa6, 0x7f, 0x8a, 0xcf, 0x64, 0xbd, 0xfe, 0x39, 0xbe, 0xc0, 0x78, 0x4c, 0xc0, 0x44, 0x4c, 0x91, 0x31, 0xfa, 0x54, 0x4c, 0xc3, 0xd7, 0x98, 0xee, 0x7f, 0xad, 0x4d, 0xd3, 0x67, 0xb0, 0xfd, 0x16, 0x33, 0x31, 0x0b, 0xb3, 0x31, 0x07, 0x73, 0x25, 0x51, 0xff, 0x1e, 0x0b, 0xb0, 0x90, 0xcb, 0x8b, 0x10, 0x2c, 0x1f, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0x9e, 0x71, 0xb2, 0x9e, 0x71, 0xb2, 0x9e, 0x71, 0xb2, 0x9e, 0x71, 0xb2, 0x9e, 0x71, 0xb2, 0x9e, 0x71, 0x32, 0x5a, 0x35, 0x66, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x34, 0x32, 0x64, 0x8f, 0x91, 0x89, 0x2c, 0xd6, 0xa9, 0xd9, 0x38, 0x8d, 0x33, 0x38, 0x0b, 0x27, 0x72, 0x70, 0x51, 0x0e, 0x18, 0x2e, 0xb8, 0x71, 0x09, 0xb9, 0xc8, 0x43, 0x3e, 0x2e, 0xc3, 0x83, 0x02, 0x14, 0xa2, 0x08, 0xc5, 0x28, 0x41, 0xa9, 0x1c, 0x60, 0x3e, 0x77, 0x33, 0x9f, 0xbb, 0x99, 0xcf, 0xdd, 0xcc, 0xe7, 0x6e, 0xe6, 0x73, 0x37, 0xf3, 0xb9, 0x9b, 0xf9, 0xdc, 0xcd, 0x7c, 0xee, 0x66, 0x3e, 0x77, 0x33, 0x9f, 0xbb, 0x99, 0xcf, 0xdd, 0xcc, 0xe7, 0x6e, 0xe6, 0x73, 0x37, 0xf3, 0xb9, 0x9b, 0xf9, 0xdc, 0xcd, 0x7c, 0xee, 0x66, 0x3e, 0x77, 0x33, 0x9f, 0xbb, 0x99, 0xcf, 0xdd, 0xcc, 0xe7, 0x6e, 0xe6, 0x73, 0x37, 0xf3, 0xb9, 0x9b, 0xf9, 0xdc, 0xcd, 0x7c, 0xee, 0x66, 0x3e, 0x77, 0x33, 0xa7, 0xb9, 0xac, 0x87, 0x24, 0xc9, 0x1a, 0x8e, 0xc3, 0x88, 0xc0, 0x11, 0x59, 0xc3, 0x9c, 0x75, 0x84, 0x79, 0xca, 0xc5, 0x3c, 0xe5, 0x62, 0x9e, 0x72, 0x31, 0x4f, 0xb9, 0x98, 0xa7, 0x5c, 0x56, 0xea, 0xd3, 0x4a, 0x7d, 0x5a, 0xa9, 0x4f, 0x2b, 0xf5, 0x69, 0xa5, 0x3e, 0x83, 0xba, 0xcb, 0x0c, 0xe6, 0x1d, 0x97, 0x7a, 0x9f, 0xd9, 0x61, 0x5d, 0xf5, 0xeb, 0xaf, 0x29, 0x8c, 0xa8, 0xf9, 0xd5, 0xff, 0x2e, 0xd0, 0x51, 0xfd, 0x6f, 0x02, 0x5d, 0xbe, 0xef, 0xc9, 0x51, 0x65, 0x72, 0x80, 0x51, 0x73, 0x99, 0xa9, 0x8e, 0x1c, 0x66, 0x84, 0xcc, 0x33, 0x4d, 0x14, 0x0f, 0x23, 0xe2, 0xcf, 0xa6, 0x05, 0x12, 0xc5, 0x28, 0xe6, 0xfb, 0x84, 0xff, 0x72, 0xf3, 0x29, 0x29, 0x66, 0xe4, 0x28, 0x0f, 0xa8, 0x21, 0x57, 0xff, 0xfc, 0x44, 0x78, 0x94, 0x94, 0xff, 0x2f, 0xdf, 0x31, 0x65, 0xe5, 0xf2, 0x2f, 0xdf, 0x19, 0xfd, 0x0f, 0xbe, 0xfb, 0x49, 0x2a, 0x5f, 0xf2, 0xe7, 0xbb, 0x90, 0x6d, 0x55, 0xa8, 0xfe, 0xa8, 0x3a, 0x48, 0x1a, 0xb6, 0xd0, 0xeb, 0x67, 0xeb, 0x93, 0x24, 0xb9, 0xea, 0xf5, 0xe4, 0xbf, 0x7b, 0xd7, 0x6e, 0x59, 0xf5, 0xbb, 0x76, 0xad, 0xf4, 0x25, 0x52, 0xfe, 0xbf, 0xfb, 0x6e, 0x1a, 0xb5, 0xd0, 0xdb, 0xff, 0x4e, 0xd8, 0x2f, 0xa6, 0x2e, 0x41, 0xbf, 0x9a, 0x7a, 0xff, 0xdd, 0xbb, 0x60, 0xfb, 0x55, 0xfb, 0xff, 0xd6, 0xda, 0x29, 0xaa, 0xae, 0x9d, 0xf2, 0xff, 0x1f, 0xd7, 0x4e, 0x2d, 0x6a, 0xa7, 0xd1, 0x7f, 0xa9, 0x9d, 0x7b, 0xa9, 0x9d, 0x9a, 0xff, 0xe7, 0xb5, 0x23, 0xd7, 0xff, 0x49, 0xed, 0xdc, 0xe3, 0xaf, 0x9d, 0x9a, 0xea, 0x43, 0x39, 0xa2, 0xc6, 0xb0, 0x6a, 0x98, 0x28, 0x05, 0x94, 0xf8, 0xaf, 0xac, 0x0e, 0x0a, 0x28, 0xf5, 0x98, 0x80, 0x2f, 0xe4, 0x48, 0x60, 0x1d, 0x39, 0x62, 0x3c, 0x83, 0x8e, 0xe8, 0x84, 0x67, 0xf1, 0x1c, 0x9e, 0x47, 0x67, 0x74, 0x41, 0x57, 0xee, 0x6f, 0x32, 0xe5, 0xaa, 0x00, 0xa5, 0x54, 0x91, 0xb9, 0x65, 0xcd, 0x8f, 0x1a, 0x9f, 0xe8, 0x70, 0xdb, 0x33, 0x0d, 0x3a, 0x76, 0xea, 0xf4, 0xda, 0x73, 0x0f, 0x3e, 0x37, 0xa3, 0x73, 0x58, 0xe7, 0x23, 0x9d, 0x1d, 0x9d, 0xcf, 0x77, 0x2e, 0xee, 0xfc, 0x47, 0x97, 0x4e, 0x5d, 0xfa, 0x74, 0x19, 0xdd, 0x65, 0x61, 0x97, 0xe5, 0x5d, 0x8e, 0x74, 0x39, 0xd7, 0xc5, 0xdd, 0xa5, 0xb8, 0x6b, 0x83, 0xae, 0x4d, 0xbb, 0xf6, 0xe9, 0x3a, 0xae, 0xeb, 0xbc, 0xae, 0xa7, 0xba, 0xfe, 0xd1, 0xad, 0xf9, 0x2b, 0x75, 0x07, 0x8c, 0x7b, 0x6d, 0xe5, 0x9b, 0x3b, 0x46, 0x9f, 0xf8, 0xc4, 0x31, 0x79, 0xde, 0xb8, 0x5a, 0xe3, 0x0e, 0x4f, 0x9e, 0xf7, 0x69, 0x9b, 0x89, 0x17, 0x27, 0x8d, 0x98, 0x74, 0x71, 0xb2, 0x75, 0xf2, 0xe8, 0xc9, 0xf3, 0x26, 0x7b, 0xbf, 0xec, 0xf9, 0xe5, 0xd0, 0x2f, 0x3f, 0x5b, 0xfc, 0xc5, 0x57, 0x7b, 0xa7, 0x7e, 0xf6, 0x6d, 0xd8, 0xe2, 0x2f, 0xbe, 0x3d, 0xf4, 0xc3, 0x8e, 0x79, 0x67, 0x7e, 0x78, 0xef, 0x87, 0x1d, 0x8b, 0xeb, 0x2e, 0xfe, 0x62, 0x59, 0xbf, 0x65, 0xfd, 0x16, 0x67, 0x2f, 0xe9, 0xbc, 0x64, 0xec, 0x92, 0x99, 0x4b, 0x16, 0x2f, 0xab, 0xb3, 0xac, 0x07, 0xd7, 0x6c, 0x5b, 0x76, 0x6c, 0x59, 0xea, 0xb2, 0x2b, 0xcb, 0xad, 0x1b, 0x3f, 0x5c, 0x7e, 0xef, 0xf2, 0x21, 0xab, 0xc6, 0xad, 0x9a, 0xb1, 0x3a, 0x61, 0xd5, 0xdc, 0x55, 0x27, 0x56, 0x07, 0xac, 0x7e, 0x62, 0x75, 0xf7, 0xd5, 0x4b, 0x57, 0x27, 0xac, 0x2e, 0x5d, 0x37, 0x73, 0xcd, 0x6d, 0x6b, 0xf2, 0xd7, 0xde, 0xbb, 0x76, 0xc8, 0xba, 0x99, 0x6b, 0x57, 0xaf, 0x75, 0xae, 0xbb, 0x6d, 0x5d, 0xd7, 0x75, 0x33, 0xd7, 0xcd, 0x0c, 0xbd, 0xbe, 0x6e, 0xed, 0xba, 0x1d, 0xeb, 0x0e, 0xad, 0x8b, 0xdd, 0xd8, 0x27, 0x34, 0x37, 0x74, 0xf1, 0xc6, 0xe1, 0x1b, 0x3f, 0xdc, 0x78, 0x6c, 0x63, 0xf6, 0x66, 0xc7, 0xe6, 0xc2, 0xcd, 0xde, 0xd0, 0xc3, 0x5b, 0xac, 0x5b, 0x9a, 0x6e, 0x79, 0x6e, 0x4b, 0xaf, 0x90, 0x4e, 0x5b, 0x76, 0x6c, 0x79, 0x6f, 0xcb, 0x17, 0x5b, 0x37, 0x6f, 0x99, 0xbd, 0x65, 0xc9, 0x96, 0xb5, 0x5b, 0x76, 0x84, 0xe6, 0x6e, 0x39, 0xb4, 0xc5, 0xb1, 0xa5, 0x30, 0x44, 0x0f, 0xa9, 0x1b, 0xf2, 0x60, 0x48, 0x9b, 0xd0, 0x5d, 0x21, 0x9d, 0x42, 0xfa, 0x84, 0x0c, 0x0f, 0xf9, 0x30, 0x64, 0x66, 0xc8, 0xe2, 0x90, 0xf5, 0x21, 0x87, 0x42, 0xa2, 0x42, 0xec, 0x21, 0xa7, 0x42, 0x4a, 0x43, 0x03, 0x42, 0x6b, 0x85, 0x36, 0x0f, 0x7d, 0x22, 0xb4, 0x7b, 0xe8, 0x6b, 0xa1, 0x5f, 0x84, 0xce, 0x08, 0x9d, 0x1b, 0xba, 0x38, 0xf4, 0x54, 0xe8, 0xfa, 0xd0, 0xf8, 0xd0, 0x5d, 0xa1, 0x87, 0xf9, 0xff, 0xa9, 0xd0, 0xf3, 0xa1, 0xb9, 0xdb, 0x8e, 0x85, 0x0d, 0x09, 0xbd, 0x1e, 0xa6, 0x87, 0x35, 0x0e, 0xeb, 0xc4, 0x7f, 0x43, 0xc2, 0x46, 0x87, 0x4d, 0x08, 0x9b, 0x19, 0xb6, 0x36, 0xec, 0x97, 0xb0, 0x13, 0xfc, 0x77, 0x26, 0xec, 0xea, 0x56, 0x7d, 0x6b, 0xdd, 0xad, 0x4d, 0xb7, 0x3e, 0xb7, 0xf5, 0x9d, 0xad, 0xd3, 0xb6, 0x6e, 0xe6, 0xbf, 0xdd, 0x5b, 0x4f, 0x6d, 0x2d, 0xdd, 0x5a, 0xba, 0x4d, 0x6d, 0xb3, 0x6e, 0x7b, 0x74, 0xdb, 0x90, 0x6d, 0xa3, 0xb7, 0xcd, 0xd8, 0x16, 0xbc, 0x6d, 0xe5, 0xb6, 0x63, 0xdb, 0x52, 0xb7, 0x79, 0xb6, 0xd7, 0xda, 0xfe, 0xc8, 0xf6, 0x67, 0xb6, 0x0f, 0xda, 0x3e, 0x76, 0xfb, 0x92, 0xed, 0xbb, 0xb6, 0x1f, 0xde, 0x1e, 0xbf, 0xe3, 0xcc, 0xcf, 0xbb, 0x77, 0xff, 0xb2, 0x3b, 0x7b, 0xef, 0x84, 0xbd, 0x9d, 0x51, 0xf5, 0xdf, 0x2f, 0xbf, 0xb9, 0x0f, 0x0c, 0x3a, 0x90, 0x1d, 0xfe, 0xa8, 0xd2, 0xd4, 0xe3, 0xa6, 0x7e, 0xa6, 0x7e, 0x4a, 0x99, 0x06, 0x98, 0x06, 0x50, 0xeb, 0x83, 0x4c, 0x83, 0x94, 0x66, 0x1a, 0x62, 0x1a, 0xa2, 0xcc, 0xa6, 0x37, 0x4c, 0xa3, 0x54, 0x80, 0xe9, 0x5d, 0xd3, 0x7b, 0x4a, 0x37, 0x7d, 0x6e, 0x9a, 0xa2, 0xac, 0xa6, 0x39, 0xa6, 0x39, 0xaa, 0x8e, 0xe9, 0x3b, 0xd3, 0x42, 0x75, 0xb7, 0xc9, 0x6d, 0xca, 0x53, 0x0f, 0x98, 0x0a, 0x4c, 0xa5, 0xaa, 0xa1, 0xd6, 0x45, 0xeb, 0xa2, 0x1e, 0xd2, 0xba, 0x69, 0x2f, 0xa8, 0x66, 0xda, 0x4b, 0x5a, 0x2f, 0xd5, 0x42, 0xeb, 0xa3, 0xf5, 0x51, 0x6d, 0xb4, 0x7e, 0x5a, 0x7f, 0xd5, 0x56, 0x1b, 0xa8, 0x0d, 0x54, 0xed, 0xb4, 0xa1, 0xda, 0xeb, 0xaa, 0xbd, 0x36, 0x52, 0x1b, 0xa9, 0x9e, 0xd0, 0xde, 0xd4, 0xde, 0x54, 0x4f, 0x6a, 0x6f, 0x6b, 0xef, 0xaa, 0xa7, 0xb4, 0xf7, 0xb5, 0x8f, 0x54, 0x47, 0x6d, 0x9c, 0xf6, 0xa9, 0xea, 0xa2, 0x7d, 0xae, 0x8d, 0x67, 0xb5, 0x3a, 0x51, 0xfb, 0x52, 0xf5, 0xd0, 0xa6, 0x68, 0x53, 0x54, 0x4f, 0x6d, 0x9a, 0xf6, 0xb5, 0x7a, 0x45, 0xfb, 0x46, 0xfb, 0x56, 0xf5, 0xd6, 0x66, 0x69, 0xf3, 0x54, 0x3f, 0x2d, 0x58, 0x0b, 0x56, 0x43, 0xb4, 0x1f, 0xb5, 0x25, 0xea, 0x55, 0x6d, 0x99, 0xb6, 0x4c, 0x0d, 0xd3, 0x56, 0x68, 0xab, 0xd5, 0x6b, 0xda, 0x7a, 0x2d, 0x42, 0x8d, 0xd0, 0x8e, 0x6a, 0x47, 0xd5, 0x14, 0xed, 0xb8, 0x76, 0x42, 0x4d, 0xd5, 0xa2, 0xb4, 0x28, 0xf5, 0xb5, 0x16, 0xa3, 0x25, 0xaa, 0xe9, 0x5a, 0x92, 0x76, 0x51, 0xcd, 0xd1, 0x2e, 0x69, 0x05, 0x6a, 0x8d, 0x56, 0xac, 0x95, 0xaa, 0xcd, 0xda, 0x15, 0xad, 0x42, 0x85, 0x6a, 0xd7, 0xb4, 0x1b, 0x6a, 0xbb, 0x76, 0xd3, 0xdc, 0x4c, 0xed, 0x32, 0x0f, 0x34, 0x0f, 0x56, 0xa7, 0xcc, 0xc3, 0xcc, 0xc3, 0xd5, 0x69, 0xf3, 0x1a, 0xf3, 0x1a, 0x95, 0x63, 0x5e, 0x67, 0x5e, 0xa7, 0xce, 0x99, 0x37, 0x99, 0x43, 0xd5, 0xef, 0xe6, 0x6d, 0xe6, 0x1d, 0xca, 0x6d, 0xde, 0x65, 0xde, 0xa5, 0xf2, 0xcc, 0xbb, 0xcd, 0xbb, 0x55, 0xbe, 0x79, 0x9f, 0xf9, 0xa0, 0xba, 0x6c, 0x0e, 0x26, 0xe9, 0x64, 0x92, 0x74, 0x32, 0x49, 0x3a, 0x99, 0x24, 0x9d, 0x4c, 0x92, 0x4e, 0x26, 0x49, 0x27, 0x93, 0xa4, 0x93, 0xa9, 0x9a, 0xab, 0x3b, 0x55, 0x2b, 0xb4, 0x96, 0x6c, 0xd5, 0x06, 0x6d, 0xd1, 0x4e, 0xa2, 0x54, 0x7b, 0x3c, 0x86, 0x0e, 0x78, 0x1c, 0x4f, 0xe0, 0x49, 0x89, 0x55, 0x4f, 0xe1, 0x69, 0x3c, 0x23, 0x76, 0xd5, 0x91, 0xeb, 0x3a, 0xe1, 0x59, 0xc9, 0x21, 0x11, 0xe5, 0x90, 0x88, 0x72, 0x48, 0x44, 0x39, 0x24, 0xa2, 0x1c, 0x12, 0x51, 0x0e, 0x89, 0x28, 0x87, 0x44, 0x94, 0x43, 0x22, 0xca, 0x21, 0x11, 0xe5, 0x90, 0x88, 0x72, 0x48, 0x44, 0x39, 0x24, 0xa2, 0x1c, 0x12, 0x51, 0x0e, 0x89, 0x28, 0x87, 0x44, 0x94, 0x43, 0x22, 0xca, 0x21, 0x11, 0xe5, 0x90, 0x88, 0x72, 0x48, 0x44, 0x39, 0x24, 0xa2, 0x1c, 0x12, 0x51, 0x0e, 0x89, 0x28, 0x87, 0x44, 0x94, 0x43, 0x22, 0xca, 0x21, 0x11, 0xe5, 0x90, 0x88, 0x72, 0x48, 0x44, 0x39, 0x24, 0xa2, 0x1c, 0x12, 0x51, 0x0e, 0x89, 0x28, 0x87, 0x44, 0x94, 0xa3, 0x46, 0x48, 0x29, 0xd9, 0xaf, 0xb1, 0xef, 0x37, 0x89, 0x19, 0xfb, 0x9b, 0xa9, 0x0f, 0xb8, 0xfc, 0x11, 0x3e, 0xc6, 0x58, 0x7c, 0xc2, 0x39, 0x8e, 0xc3, 0xa7, 0xf8, 0x8c, 0xcb, 0x5f, 0x70, 0xfe, 0xe3, 0xd9, 0x4e, 0x90, 0xd3, 0x6a, 0x22, 0x26, 0x61, 0x32, 0xbe, 0xe2, 0x58, 0x53, 0x24, 0x8b, 0x64, 0x95, 0x45, 0xb2, 0xca, 0x22, 0x59, 0x65, 0x91, 0xac, 0xb2, 0x48, 0x56, 0x59, 0x24, 0xab, 0x2c, 0x92, 0x55, 0x16, 0xc9, 0x2a, 0x8b, 0x64, 0x95, 0xa5, 0x66, 0x33, 0x9b, 0xcc, 0xc1, 0x5c, 0x49, 0x23, 0x61, 0x65, 0x93, 0xb0, 0x0a, 0x49, 0x58, 0x99, 0x24, 0xac, 0x0c, 0x95, 0x42, 0x9a, 0x4a, 0x93, 0x74, 0x66, 0x9d, 0xe4, 0xea, 0x6f, 0xe3, 0x4f, 0x22, 0x69, 0x65, 0x90, 0xb4, 0x4e, 0x91, 0xb4, 0xe2, 0xd5, 0x69, 0x6e, 0x77, 0x96, 0xc7, 0xf7, 0xfd, 0x2b, 0xc2, 0x73, 0x72, 0x56, 0x5d, 0xe0, 0xbc, 0x2e, 0xc2, 0xf7, 0x4d, 0x19, 0x6e, 0xae, 0xbb, 0xc4, 0x7e, 0xae, 0x1c, 0x27, 0x85, 0xd9, 0xd5, 0x65, 0x6e, 0x7f, 0x83, 0xdb, 0xdf, 0x54, 0xb5, 0x49, 0x62, 0xa9, 0x24, 0xb0, 0x4c, 0x92, 0x97, 0xc3, 0x74, 0x97, 0x14, 0x99, 0x6a, 0x4a, 0xa9, 0xa9, 0x16, 0x6a, 0xa3, 0xae, 0x38, 0x4d, 0xf7, 0xb0, 0xbd, 0x17, 0x0f, 0xa0, 0x1e, 0xea, 0xa3, 0x81, 0x44, 0x99, 0x1a, 0xb2, 0x7d, 0x50, 0xd2, 0x4d, 0x8d, 0xd9, 0x6f, 0xc2, 0x7e, 0x53, 0x3c, 0x24, 0x36, 0x53, 0x33, 0xb6, 0x0f, 0xc3, 0xf7, 0x2f, 0x25, 0x5a, 0xb0, 0x6d, 0x89, 0x56, 0x72, 0xc6, 0xf4, 0x08, 0xb7, 0x6b, 0xcd, 0xfe, 0xe7, 0x12, 0x4f, 0x8a, 0x3b, 0xc6, 0x38, 0x9d, 0x66, 0x9a, 0x2c, 0x49, 0xa6, 0x29, 0x52, 0xe8, 0xff, 0xa4, 0xda, 0x7c, 0x8e, 0xe5, 0xe6, 0x3c, 0x72, 0xb9, 0x5d, 0x9e, 0x38, 0x48, 0x72, 0x99, 0xa4, 0xb8, 0xa1, 0xa4, 0xb8, 0x4c, 0xfa, 0x56, 0x43, 0xf2, 0xf7, 0x00, 0xd2, 0x5c, 0xa6, 0x36, 0x40, 0x32, 0xb4, 0xd7, 0xd4, 0x9d, 0xa4, 0xb9, 0xcc, 0xea, 0x77, 0xdb, 0xa2, 0x34, 0xca, 0x5e, 0x9b, 0x24, 0xa5, 0xda, 0x57, 0x12, 0xa9, 0x4d, 0x61, 0x4b, 0x39, 0x93, 0xec, 0xb2, 0xb4, 0x59, 0x92, 0x4c, 0xba, 0xd3, 0xb5, 0x39, 0x64, 0xf8, 0xb9, 0xdc, 0x26, 0x58, 0x4e, 0x6b, 0x3f, 0xb2, 0x5d, 0x22, 0x85, 0xda, 0x6a, 0x39, 0x4e, 0xda, 0xcb, 0x21, 0xed, 0xe5, 0x90, 0xf6, 0x72, 0x48, 0x7b, 0x39, 0xa4, 0xbd, 0x1c, 0xd2, 0x5e, 0x0e, 0x69, 0x2f, 0x87, 0xb4, 0x97, 0x43, 0xda, 0xcb, 0x21, 0xed, 0xe5, 0x90, 0xf6, 0xb2, 0x48, 0x7b, 0x59, 0xda, 0x0e, 0xce, 0x6b, 0x27, 0xf7, 0xdf, 0xc5, 0x63, 0xb0, 0xea, 0xd5, 0x7e, 0x95, 0x38, 0x6d, 0xb7, 0x44, 0x6b, 0x7b, 0xb8, 0x6e, 0xaf, 0xa4, 0x6a, 0xbf, 0xf1, 0x78, 0xfb, 0x24, 0x45, 0xdb, 0x2f, 0x49, 0xda, 0x01, 0xee, 0x77, 0x90, 0xf3, 0x39, 0xc4, 0x6d, 0xc3, 0xe5, 0x84, 0x76, 0x98, 0x6d, 0x84, 0x9c, 0xd2, 0x8e, 0x70, 0x8c, 0xe3, 0xdc, 0x36, 0x8a, 0xed, 0x49, 0x44, 0x73, 0x7d, 0x0c, 0xa3, 0x40, 0x2c, 0xdb, 0x38, 0xc4, 0xf3, 0x7c, 0x6c, 0xdc, 0x2f, 0x41, 0x4e, 0x6a, 0xa9, 0xa4, 0xd5, 0x34, 0xc9, 0xd6, 0x1c, 0x5c, 0x9f, 0x8e, 0x4c, 0xf5, 0x96, 0x76, 0x4a, 0x2d, 0xd2, 0xb2, 0xd4, 0x4f, 0xda, 0x19, 0x89, 0xd1, 0xce, 0xf2, 0x78, 0x4e, 0x8e, 0x71, 0x5e, 0xd2, 0x34, 0x97, 0x7a, 0x40, 0xbb, 0xa4, 0xa6, 0x69, 0xf9, 0x52, 0xa0, 0x5d, 0xe6, 0xf1, 0x3d, 0xfc, 0xad, 0x80, 0xc7, 0x2d, 0xe4, 0xb6, 0xc5, 0xdc, 0xb7, 0x84, 0xf3, 0x2c, 0xe5, 0x3c, 0xcb, 0xd8, 0x5e, 0xe1, 0xbe, 0xe5, 0x9c, 0xcb, 0x55, 0x54, 0xa0, 0x52, 0xce, 0x68, 0x5e, 0x8e, 0x7d, 0x53, 0xbd, 0x65, 0x56, 0x92, 0x62, 0x36, 0x89, 0xc3, 0xac, 0x49, 0xa4, 0xd9, 0x2c, 0xa5, 0xe6, 0x00, 0x04, 0xa2, 0x86, 0x9c, 0x31, 0x5b, 0xd8, 0x1a, 0xb0, 0x4a, 0x9a, 0x39, 0x48, 0x4e, 0x98, 0x6f, 0x93, 0xb3, 0xe6, 0xdb, 0xe5, 0x98, 0xf9, 0x0e, 0x39, 0x6e, 0xbe, 0x53, 0x92, 0xcd, 0x77, 0xa1, 0x26, 0xf7, 0xad, 0x25, 0xb1, 0xe6, 0xda, 0x92, 0x6a, 0xae, 0xc3, 0x6d, 0xeb, 0x4a, 0x96, 0xf9, 0x1e, 0x39, 0x6d, 0xbe, 0x97, 0xe3, 0xde, 0x27, 0x65, 0xe6, 0xfb, 0x25, 0xce, 0x5c, 0x8f, 0xdb, 0xd4, 0x97, 0x22, 0xf3, 0x83, 0x72, 0xca, 0xdc, 0x98, 0xdb, 0xd0, 0x76, 0xcc, 0x0f, 0x49, 0x82, 0x79, 0x20, 0xd7, 0x0f, 0x92, 0x78, 0xf3, 0x60, 0x6e, 0x33, 0x8c, 0xc7, 0x7f, 0x4d, 0xa2, 0x48, 0xd8, 0xbe, 0x5f, 0xd1, 0xf1, 0x7d, 0x5f, 0x4f, 0x14, 0x29, 0x3b, 0xde, 0xbc, 0x83, 0xed, 0x2e, 0xec, 0xc6, 0x09, 0xfe, 0x16, 0xa5, 0x2c, 0x24, 0xea, 0x4c, 0x73, 0x16, 0xe7, 0x97, 0x8d, 0x0b, 0x1c, 0xeb, 0x22, 0x5c, 0x70, 0x23, 0x17, 0x79, 0xc8, 0xc7, 0x65, 0x8e, 0xe3, 0x61, 0x5b, 0x80, 0x42, 0x14, 0x71, 0x8c, 0x62, 0xae, 0x2b, 0x91, 0x0c, 0x52, 0x78, 0x06, 0x29, 0x3c, 0x9d, 0x14, 0x6e, 0x23, 0x85, 0xdb, 0xcc, 0x15, 0xfc, 0xfd, 0xaf, 0x77, 0x31, 0x4b, 0xcd, 0x5e, 0xdc, 0xc4, 0x1f, 0xfc, 0x4d, 0xa4, 0x34, 0x40, 0x49, 0x72, 0x80, 0x49, 0xd5, 0x0e, 0xd0, 0x24, 0x2a, 0x80, 0x72, 0x0a, 0xa0, 0x9c, 0x02, 0x2c, 0xec, 0x07, 0xb1, 0x7d, 0x51, 0x32, 0x49, 0xec, 0x99, 0x24, 0xf6, 0xcc, 0x80, 0x5e, 0x5c, 0xee, 0x83, 0x7e, 0x18, 0xc0, 0xdf, 0x07, 0x62, 0x30, 0x18, 0x5f, 0x02, 0x56, 0x48, 0x4a, 0x20, 0x7d, 0x2a, 0xb0, 0x21, 0xdb, 0x46, 0x12, 0x1d, 0xd8, 0x58, 0x22, 0x03, 0x9b, 0x88, 0x9d, 0x64, 0x9f, 0x43, 0xb2, 0xcf, 0x09, 0x8c, 0x54, 0x2f, 0x05, 0x46, 0xa9, 0x07, 0x48, 0xf7, 0x39, 0xa4, 0xfb, 0x1c, 0xd2, 0x7d, 0x0e, 0xe9, 0x3e, 0x87, 0x74, 0x9f, 0x43, 0xba, 0xcf, 0xf4, 0x7f, 0x33, 0xfc, 0xef, 0x12, 0x15, 0x78, 0x49, 0x62, 0x03, 0xf3, 0x54, 0xb3, 0xc0, 0x7c, 0xb6, 0x1e, 0xff, 0xbf, 0xc3, 0x76, 0x06, 0x16, 0x72, 0xfd, 0x55, 0xff, 0xf7, 0x19, 0x45, 0x05, 0x5e, 0x93, 0xcc, 0x1a, 0x4a, 0x52, 0x6b, 0x98, 0x24, 0xbe, 0x86, 0xa6, 0x86, 0xd7, 0x30, 0xb3, 0x0d, 0x90, 0xdf, 0x6b, 0x04, 0x8a, 0xbd, 0x46, 0x0d, 0xc9, 0xaf, 0x41, 0xff, 0xad, 0xf1, 0x90, 0xa4, 0xd7, 0x68, 0x26, 0xe7, 0x6b, 0x5c, 0x93, 0x28, 0xcb, 0x18, 0xc9, 0xb4, 0x7c, 0x84, 0x8f, 0x31, 0x16, 0x9f, 0xe0, 0x53, 0x7c, 0x86, 0xcd, 0xd8, 0x82, 0x10, 0x84, 0x22, 0x0c, 0x5b, 0xb1, 0x0d, 0x3b, 0xd5, 0x48, 0x4b, 0x3c, 0x2b, 0xd1, 0x64, 0xf5, 0x85, 0x25, 0x8b, 0xd5, 0xe0, 0xed, 0x92, 0xa9, 0x33, 0x3f, 0xe8, 0x77, 0xe2, 0x2e, 0xd4, 0x44, 0x2d, 0xd4, 0x46, 0x1d, 0xdc, 0x8d, 0xba, 0xea, 0x09, 0xfd, 0x1e, 0xb6, 0xf7, 0xe2, 0x3e, 0xdc, 0x0f, 0xe6, 0x13, 0x9d, 0xf9, 0x44, 0x67, 0x3e, 0xd1, 0x1b, 0xa0, 0x21, 0x98, 0x57, 0xf4, 0x46, 0x68, 0x8c, 0x26, 0x68, 0x8a, 0x87, 0xd0, 0x0c, 0x0f, 0xa3, 0x39, 0x5a, 0xa0, 0x25, 0x5a, 0xc1, 0xf7, 0x39, 0x99, 0x36, 0x6c, 0xff, 0xf6, 0x59, 0x99, 0x76, 0xec, 0xb7, 0xc7, 0x63, 0xe8, 0x80, 0x71, 0xea, 0x4e, 0xfd, 0x53, 0x4c, 0xc1, 0x54, 0x4c, 0xc3, 0xd7, 0x98, 0x8e, 0x6f, 0xd4, 0x61, 0x7d, 0x06, 0xdb, 0x6f, 0x31, 0x13, 0xb3, 0x30, 0x1b, 0x73, 0xb0, 0x10, 0x8b, 0x10, 0xcc, 0x4a, 0x76, 0xb9, 0x64, 0xeb, 0x2b, 0xb0, 0x12, 0xab, 0xb0, 0x1a, 0x6b, 0xb0, 0x16, 0xeb, 0xb0, 0x1e, 0x1b, 0xb0, 0x11, 0x9b, 0xb0, 0x19, 0x5b, 0xd4, 0xa3, 0x7a, 0x08, 0xdb, 0x50, 0x84, 0x61, 0x2b, 0xb6, 0x61, 0x3b, 0x76, 0x60, 0x27, 0x76, 0xe1, 0x67, 0xfc, 0x82, 0x5f, 0xb1, 0x5b, 0xd5, 0xd0, 0xf7, 0x48, 0x96, 0xbe, 0x97, 0xfd, 0xdf, 0xb0, 0x0f, 0xfb, 0xe5, 0xa6, 0x7e, 0x80, 0xed, 0x41, 0x1c, 0x42, 0x38, 0x8e, 0x49, 0x94, 0x7e, 0x1c, 0x27, 0x10, 0x89, 0x28, 0x44, 0x23, 0x06, 0xb1, 0x12, 0xab, 0xc7, 0x21, 0x1e, 0x36, 0x24, 0x48, 0xb4, 0x9e, 0xc8, 0xf5, 0x76, 0xf6, 0x93, 0xd8, 0x26, 0x23, 0x05, 0xa9, 0x48, 0x83, 0x03, 0xe9, 0xc8, 0x40, 0x26, 0xb2, 0x90, 0x8d, 0xd3, 0x38, 0x83, 0xb3, 0x70, 0xaa, 0x40, 0x3d, 0x87, 0xed, 0x39, 0xd0, 0x0e, 0xf5, 0xf3, 0x60, 0xce, 0xd1, 0x5d, 0x70, 0x83, 0x39, 0x47, 0x67, 0x3c, 0xd7, 0xf3, 0x90, 0x8f, 0xcb, 0x24, 0x03, 0x8f, 0xe4, 0xe8, 0x85, 0x28, 0x42, 0x31, 0x4a, 0x50, 0x8a, 0x32, 0x5c, 0x41, 0x39, 0xae, 0xa2, 0x02, 0x95, 0xb8, 0x86, 0xeb, 0xb8, 0x01, 0x2f, 0x6e, 0xe2, 0x0f, 0xc9, 0x31, 0x6a, 0x48, 0xa9, 0xc1, 0x78, 0x64, 0xe8, 0x60, 0x4c, 0x32, 0xac, 0x12, 0x65, 0xd0, 0xf7, 0x8c, 0xdb, 0x70, 0x3b, 0xee, 0xc0, 0x9d, 0xa8, 0xad, 0x82, 0x8c, 0xfb, 0xd5, 0x03, 0xfe, 0x77, 0xcc, 0x1a, 0xa8, 0xdb, 0x8c, 0x86, 0xca, 0x6a, 0x3c, 0xa8, 0xea, 0x19, 0x8d, 0xd4, 0xbd, 0x46, 0x63, 0xd5, 0xcc, 0x68, 0xa2, 0xee, 0x34, 0x9a, 0xfa, 0x7f, 0xf3, 0xb6, 0x9b, 0xd1, 0x5c, 0xb5, 0x32, 0x98, 0xaf, 0x8c, 0xd6, 0xea, 0x67, 0xa3, 0x0d, 0x7f, 0x6b, 0xab, 0xea, 0x1a, 0xed, 0xd8, 0x3e, 0xcd, 0x75, 0xcf, 0xab, 0xfa, 0x46, 0x37, 0xb6, 0xdd, 0xf1, 0x02, 0x7a, 0xe0, 0x45, 0xbc, 0x8c, 0x9e, 0x78, 0x05, 0xf4, 0x75, 0xa3, 0x37, 0xe8, 0xef, 0x46, 0x5f, 0xd0, 0xe7, 0x8d, 0xfe, 0x18, 0x80, 0x81, 0x18, 0xc4, 0x39, 0xd2, 0xf7, 0x8d, 0x21, 0x78, 0x15, 0x43, 0x31, 0x0c, 0x8c, 0x73, 0xc6, 0xeb, 0x18, 0x8e, 0x37, 0x30, 0x02, 0x23, 0x31, 0x0a, 0x6f, 0xe2, 0x2d, 0xb9, 0x69, 0xbc, 0xcd, 0xf6, 0x1d, 0xbc, 0x8b, 0xf7, 0xf0, 0x3e, 0x46, 0xe3, 0x03, 0x7c, 0x88, 0x31, 0xf8, 0x08, 0x1f, 0x63, 0x2c, 0x58, 0x9f, 0x18, 0xac, 0x4f, 0x0c, 0xd6, 0x27, 0xc6, 0x67, 0x60, 0x9e, 0x34, 0xbe, 0xc0, 0x78, 0x4c, 0xc0, 0x44, 0x4c, 0xc2, 0x64, 0x7c, 0x89, 0xaf, 0x38, 0x3f, 0xe6, 0x4e, 0x63, 0x2a, 0xa6, 0xe3, 0x1b, 0xcc, 0xc0, 0xb7, 0x98, 0x89, 0x59, 0x98, 0x8d, 0x39, 0xf8, 0x0e, 0xf3, 0xd4, 0xa3, 0xc6, 0x7c, 0x39, 0x6d, 0x7c, 0x8f, 0x05, 0x58, 0x88, 0x45, 0x60, 0x8e, 0x35, 0x7e, 0xc0, 0x62, 0xfc, 0x88, 0x25, 0xf8, 0x09, 0xcb, 0x24, 0xcb, 0x58, 0x8e, 0x15, 0x58, 0x89, 0x55, 0x58, 0x8d, 0x35, 0x6a, 0x98, 0xb1, 0x96, 0xed, 0x3a, 0xac, 0xc7, 0x06, 0x6c, 0xc4, 0x26, 0x6c, 0xc6, 0x16, 0x84, 0x20, 0x14, 0x61, 0xd8, 0x0a, 0xe6, 0x63, 0x83, 0xf9, 0xd8, 0xd8, 0x81, 0x9d, 0xd8, 0x85, 0x9f, 0xf1, 0x0b, 0x7e, 0xc5, 0x6e, 0xd0, 0x5f, 0x8c, 0xbd, 0xf8, 0x0d, 0xfb, 0xb0, 0x1f, 0x07, 0x70, 0x10, 0x87, 0x10, 0x8e, 0xc3, 0x88, 0x00, 0x73, 0xb2, 0x71, 0x14, 0xc7, 0x70, 0x1c, 0x27, 0x10, 0x09, 0xe6, 0x68, 0x83, 0x39, 0xda, 0x88, 0x96, 0xb3, 0x46, 0x0c, 0xdb, 0x58, 0xc4, 0x21, 0x1e, 0x36, 0x24, 0x20, 0x11, 0x76, 0x24, 0x21, 0x19, 0x29, 0x48, 0x45, 0x1a, 0x1c, 0x48, 0x47, 0x86, 0xa4, 0x19, 0x99, 0x38, 0x25, 0xe9, 0x46, 0x96, 0x64, 0x1b, 0xd9, 0x38, 0x8d, 0x33, 0x38, 0x0b, 0x27, 0x72, 0x70, 0x51, 0x0a, 0x0d, 0x17, 0xdc, 0xb8, 0x84, 0x5c, 0xe4, 0x21, 0x1f, 0x97, 0xe1, 0x41, 0x01, 0x0a, 0x51, 0x84, 0x62, 0x94, 0xa0, 0x54, 0x0a, 0xad, 0xf7, 0xab, 0xb6, 0xd6, 0xb6, 0x12, 0x6d, 0x7d, 0x14, 0xed, 0xd0, 0x1e, 0x8f, 0xa1, 0x03, 0x1e, 0xc7, 0x13, 0x78, 0x12, 0x4f, 0xe1, 0x69, 0x3c, 0x83, 0x8e, 0xe8, 0x84, 0x67, 0xf1, 0x1c, 0x9e, 0x47, 0x67, 0x74, 0x41, 0x57, 0x74, 0x43, 0x77, 0xbc, 0x80, 0x1e, 0x78, 0x11, 0x2f, 0xe1, 0x65, 0x49, 0xb2, 0xf6, 0xc4, 0x2b, 0xe8, 0x85, 0xde, 0xe8, 0x83, 0xbe, 0xe8, 0x87, 0xfe, 0x60, 0x9d, 0x66, 0x1d, 0x88, 0x41, 0x18, 0x8c, 0x21, 0x78, 0x15, 0x43, 0x31, 0x0c, 0xaf, 0xe1, 0x75, 0x0c, 0xc7, 0x1b, 0x18, 0x81, 0x91, 0x18, 0x85, 0x37, 0xf1, 0x16, 0xde, 0xc6, 0x3b, 0x78, 0x17, 0xef, 0xe1, 0x7d, 0x8c, 0xc6, 0x07, 0xf8, 0xd0, 0xff, 0xdd, 0x63, 0x99, 0xd6, 0x8f, 0x54, 0x4b, 0x2b, 0xf3, 0x94, 0x75, 0xac, 0xd8, 0xac, 0xcc, 0x53, 0xd6, 0x71, 0x60, 0xae, 0xb2, 0x32, 0x57, 0x59, 0x3f, 0xc7, 0x17, 0x18, 0x0f, 0xdf, 0x77, 0xf5, 0x4d, 0xc4, 0x24, 0x4c, 0xc6, 0x97, 0xf8, 0x0a, 0x53, 0x30, 0x15, 0xd3, 0x30, 0x43, 0xd2, 0xac, 0xdf, 0x62, 0x26, 0x66, 0x61, 0x36, 0xe6, 0x80, 0xf5, 0xb8, 0xf5, 0x3b, 0xcc, 0xc3, 0x7c, 0x7c, 0x8f, 0x05, 0x58, 0x88, 0x45, 0x08, 0xc6, 0x0f, 0x58, 0x8c, 0x1f, 0xb1, 0x04, 0x3f, 0xc9, 0x71, 0xeb, 0x32, 0x89, 0xb7, 0x2e, 0xc7, 0x0a, 0xac, 0xc4, 0x2a, 0xac, 0xc6, 0x1a, 0xac, 0xc5, 0x3a, 0xac, 0xc7, 0x06, 0x6c, 0xc4, 0x26, 0x6c, 0xc6, 0x16, 0x84, 0x80, 0xf5, 0x8e, 0x35, 0x0c, 0x5b, 0xb1, 0x0d, 0xdb, 0xb1, 0x03, 0x3b, 0xb1, 0x0b, 0x07, 0x39, 0xe7, 0x43, 0xaa, 0xb6, 0x35, 0x1c, 0x87, 0x11, 0x81, 0x13, 0x52, 0x6a, 0x8d, 0x44, 0xb4, 0xff, 0x3b, 0x4d, 0x9c, 0x56, 0xd6, 0x94, 0xd6, 0x78, 0xd8, 0x90, 0x80, 0x44, 0xd8, 0x91, 0x84, 0x64, 0xa4, 0x50, 0x6e, 0xa9, 0x6c, 0xd3, 0xe4, 0x8c, 0xf5, 0x94, 0xd2, 0xac, 0xf9, 0x1c, 0xf3, 0x32, 0x3c, 0x28, 0x40, 0x21, 0xae, 0x48, 0x94, 0xb5, 0x1c, 0x57, 0xc1, 0x7a, 0xc2, 0x5a, 0x89, 0x6b, 0xe2, 0xb0, 0x5e, 0xc7, 0x0d, 0x78, 0x71, 0x53, 0x1c, 0x41, 0x8b, 0x24, 0x3a, 0x68, 0xb1, 0x7a, 0x26, 0xe8, 0x47, 0xf5, 0x56, 0xd0, 0x4f, 0xea, 0x89, 0xa0, 0x65, 0xfe, 0x4f, 0x8c, 0x67, 0x06, 0x85, 0x4b, 0x72, 0xd0, 0x61, 0x44, 0x48, 0x54, 0x10, 0x73, 0x4f, 0x50, 0xa2, 0x5c, 0x0e, 0x18, 0xac, 0xcc, 0x24, 0x9c, 0x00, 0x04, 0x4a, 0x22, 0x69, 0xd2, 0x4e, 0x9a, 0xb4, 0x93, 0x26, 0xed, 0xaa, 0x2e, 0xa9, 0xe7, 0x01, 0xb6, 0xf5, 0x50, 0x1f, 0x0f, 0xa2, 0xb1, 0x14, 0xa9, 0x26, 0x68, 0x4e, 0x62, 0x69, 0x85, 0xd6, 0x72, 0x92, 0x64, 0x79, 0x92, 0x64, 0x79, 0x92, 0x64, 0x19, 0x4e, 0xb2, 0x0c, 0x27, 0x59, 0x86, 0x93, 0x2c, 0xc3, 0x49, 0x96, 0xe1, 0x24, 0xcb, 0x70, 0x92, 0x65, 0x04, 0xc9, 0x32, 0x82, 0x64, 0x19, 0xe1, 0xfb, 0xed, 0x6b, 0x92, 0x65, 0x38, 0xc9, 0x32, 0x9c, 0x64, 0x99, 0x42, 0xb2, 0x4c, 0x21, 0x59, 0xa6, 0x90, 0x2c, 0x53, 0x48, 0x96, 0x29, 0x24, 0xcb, 0x14, 0x92, 0x65, 0x8a, 0xf2, 0xfd, 0x5a, 0xf6, 0x0b, 0xe8, 0x81, 0x17, 0xf1, 0x12, 0x5e, 0x46, 0x4f, 0xbc, 0x82, 0x5e, 0xe8, 0x8d, 0x3e, 0xe8, 0x8b, 0x7e, 0xe8, 0x8f, 0x01, 0x18, 0x88, 0x41, 0x18, 0x8c, 0x21, 0x78, 0x15, 0x43, 0x31, 0x0c, 0xaf, 0xe1, 0x75, 0x0c, 0xc7, 0x08, 0x29, 0x20, 0x59, 0x0e, 0x55, 0xef, 0x92, 0xd6, 0xde, 0x53, 0x6d, 0x48, 0x97, 0x6d, 0x48, 0x97, 0x36, 0xd2, 0xa5, 0x8d, 0x74, 0x69, 0x23, 0x5d, 0xda, 0x48, 0x97, 0xe1, 0xa4, 0xcb, 0x70, 0xd2, 0x65, 0x38, 0xe9, 0xd2, 0x46, 0xba, 0x8c, 0x20, 0x5d, 0xda, 0x48, 0x97, 0x51, 0xa4, 0xcb, 0x28, 0xd2, 0x65, 0x14, 0xe9, 0x32, 0x8a, 0x74, 0x99, 0x42, 0xba, 0xb4, 0x91, 0x2e, 0x6d, 0xa4, 0x4b, 0x1b, 0xe9, 0xd2, 0x46, 0xba, 0xb4, 0x91, 0x2e, 0x6d, 0xa4, 0x4b, 0x1b, 0xe9, 0xd2, 0x46, 0xba, 0xb4, 0x91, 0x2e, 0x6d, 0xa4, 0xcb, 0x18, 0xd2, 0x65, 0x0c, 0xe9, 0x32, 0x9a, 0x74, 0x69, 0x57, 0xf3, 0x48, 0x8f, 0xf3, 0x49, 0x9b, 0xb4, 0x66, 0x15, 0x4b, 0xfa, 0x8c, 0xe3, 0xba, 0x78, 0xea, 0xc3, 0x4e, 0xb2, 0x4c, 0x66, 0x9b, 0x22, 0x87, 0x49, 0x9c, 0x09, 0xfe, 0xdf, 0xcd, 0x48, 0x67, 0x9b, 0x21, 0xc7, 0x54, 0xa6, 0xe4, 0x93, 0x3a, 0x63, 0x48, 0x9d, 0x49, 0xa4, 0xce, 0x28, 0x52, 0x67, 0x2c, 0xa9, 0x33, 0x81, 0xd4, 0x19, 0xa3, 0x72, 0xa4, 0x98, 0xe4, 0x99, 0xa4, 0x7e, 0xa7, 0x3e, 0x2f, 0x70, 0xde, 0x17, 0xe1, 0xe2, 0xef, 0x6e, 0xfe, 0x76, 0x89, 0xfd, 0x5c, 0x39, 0x48, 0xfa, 0x8c, 0x22, 0x7d, 0x1e, 0x55, 0xe5, 0x3c, 0xf7, 0x1b, 0x3c, 0xde, 0x4d, 0x75, 0x0f, 0x09, 0xd4, 0x66, 0xd2, 0xa5, 0x84, 0x14, 0x6a, 0x27, 0x85, 0xc6, 0x93, 0x42, 0x53, 0x48, 0xa1, 0x36, 0x52, 0xa8, 0x8d, 0x14, 0x6a, 0x33, 0xdd, 0x4d, 0x52, 0xac, 0x2b, 0xd1, 0x24, 0x51, 0x1b, 0x49, 0xd4, 0x66, 0xba, 0x8f, 0xdb, 0xdd, 0x8f, 0x07, 0xd8, 0xaf, 0x87, 0xfa, 0x68, 0x20, 0xe1, 0x24, 0x52, 0x1b, 0x89, 0x34, 0x9e, 0x44, 0x1a, 0x4e, 0x22, 0xb5, 0x91, 0x48, 0x6d, 0x24, 0xd2, 0x93, 0x24, 0x52, 0x1b, 0x89, 0xd4, 0xf7, 0xfd, 0x3c, 0xf1, 0x24, 0x52, 0x1b, 0x89, 0xd4, 0x46, 0x22, 0x8d, 0x26, 0x91, 0x86, 0x93, 0x48, 0x6d, 0xa6, 0x36, 0x5c, 0xff, 0xb9, 0x9c, 0x20, 0x95, 0x86, 0x9b, 0xc6, 0x4b, 0x9e, 0x69, 0x82, 0x14, 0x90, 0x4e, 0x8f, 0x98, 0x26, 0x91, 0x86, 0x29, 0x63, 0xff, 0xfb, 0x8d, 0x5f, 0x91, 0x92, 0xa7, 0x88, 0x87, 0xa4, 0x9a, 0x6a, 0x9a, 0x2b, 0xd9, 0xa6, 0xef, 0xa4, 0xcc, 0x34, 0x8f, 0xe4, 0x3a, 0x9f, 0x73, 0xfd, 0x9e, 0x54, 0x5b, 0xf5, 0x3e, 0xa4, 0x9d, 0xf4, 0x1a, 0x4e, 0x7a, 0x8d, 0x31, 0x15, 0x4b, 0x96, 0xa9, 0x04, 0xa5, 0xfe, 0x7f, 0x55, 0x5d, 0x4a, 0x9a, 0xb5, 0x6b, 0xac, 0x1a, 0xb4, 0x97, 0xa4, 0x48, 0x7b, 0x99, 0xc4, 0xd5, 0x53, 0x8d, 0x21, 0xd9, 0xda, 0x49, 0xb6, 0x4d, 0x48, 0xb6, 0x1f, 0x90, 0x6c, 0xed, 0x24, 0x5b, 0xbb, 0xc6, 0x2a, 0x81, 0x74, 0x5b, 0x9b, 0x74, 0x6b, 0xd7, 0xde, 0x66, 0xbf, 0x2a, 0xe1, 0x86, 0x93, 0x70, 0x23, 0xb4, 0xf1, 0x52, 0x4c, 0xca, 0xb5, 0x91, 0x72, 0x0f, 0x91, 0x72, 0x6d, 0xa4, 0x5c, 0x1b, 0x29, 0xd7, 0xa6, 0x7d, 0x43, 0xfa, 0x9b, 0x45, 0x62, 0x9c, 0x43, 0x4a, 0x9e, 0xcb, 0xed, 0xbe, 0xe3, 0x76, 0x8c, 0x54, 0xa4, 0xdd, 0x28, 0xd2, 0x6e, 0x04, 0x69, 0xd7, 0xa1, 0x2d, 0x93, 0x04, 0x6d, 0x15, 0xd7, 0xad, 0xe6, 0xf2, 0x7a, 0x12, 0xea, 0x06, 0x6c, 0xc4, 0x26, 0x6c, 0xc6, 0x16, 0x84, 0x20, 0x14, 0x61, 0xd8, 0x8a, 0x6d, 0x1c, 0x7b, 0x3b, 0x76, 0x90, 0x16, 0x77, 0xca, 0x11, 0x52, 0xaf, 0x4d, 0xfb, 0x99, 0xf3, 0xff, 0x85, 0x04, 0xf9, 0x2b, 0xe9, 0x79, 0x37, 0xe7, 0xb5, 0x87, 0xe3, 0xed, 0x25, 0x65, 0xfe, 0xc6, 0xe3, 0xef, 0x63, 0xbb, 0x5f, 0x8e, 0x91, 0x7c, 0x63, 0x48, 0xbe, 0x87, 0x48, 0xbe, 0x05, 0x24, 0xdf, 0x43, 0x24, 0xdf, 0x42, 0x92, 0x6f, 0x22, 0xc9, 0x37, 0x46, 0x3b, 0xca, 0xfd, 0x8f, 0x71, 0xf9, 0x38, 0xc7, 0x8a, 0xe2, 0xf2, 0x49, 0x52, 0x6e, 0x34, 0xfb, 0x31, 0xa4, 0xfb, 0x58, 0xb6, 0x71, 0x9c, 0x67, 0x3c, 0xc7, 0xb5, 0x71, 0xbf, 0x04, 0x24, 0x92, 0xb0, 0xed, 0x24, 0xd9, 0x24, 0xfe, 0x96, 0xca, 0xe3, 0xd2, 0x1e, 0x49, 0xc3, 0x09, 0xa4, 0xe1, 0x04, 0xd2, 0xf0, 0x04, 0xd2, 0xf0, 0x1c, 0x7f, 0x1a, 0xce, 0xa6, 0xac, 0x4e, 0x4b, 0x2e, 0xa9, 0xf8, 0x18, 0xa9, 0x38, 0x8a, 0x54, 0x1c, 0xa3, 0xe5, 0x70, 0x4e, 0xe7, 0xb8, 0xdf, 0xef, 0xec, 0x9f, 0xe7, 0x71, 0x2e, 0x90, 0xba, 0x2f, 0xfa, 0x93, 0x72, 0x43, 0x92, 0x72, 0x3f, 0x2d, 0x97, 0xf3, 0xcb, 0xa3, 0x9c, 0xf3, 0x49, 0xf6, 0x97, 0xb9, 0x8d, 0x87, 0xe7, 0x54, 0x20, 0x07, 0xfd, 0x89, 0xb9, 0x48, 0x32, 0x49, 0xcd, 0x09, 0xa4, 0xe6, 0x70, 0x52, 0x73, 0x04, 0xa9, 0x39, 0x9c, 0xd4, 0x7c, 0x98, 0xd4, 0x9c, 0x40, 0x6a, 0xb6, 0x91, 0x9a, 0x6d, 0xa4, 0x66, 0xbb, 0x76, 0x43, 0xca, 0x48, 0xce, 0x43, 0x49, 0xce, 0x43, 0xb5, 0x3f, 0x78, 0x5c, 0x21, 0xf9, 0x2a, 0x92, 0xab, 0x09, 0x9a, 0x84, 0x93, 0xa0, 0x13, 0x48, 0xd0, 0x09, 0x24, 0xe8, 0x04, 0x12, 0x74, 0x34, 0x09, 0x3a, 0xd7, 0xac, 0x93, 0x62, 0x0d, 0x2e, 0x5b, 0x49, 0xd0, 0x41, 0x72, 0x88, 0x14, 0x1d, 0x4d, 0x8a, 0x3e, 0x48, 0x8a, 0x3e, 0x44, 0x8a, 0x3e, 0x49, 0x8a, 0x8e, 0x24, 0x45, 0x47, 0x91, 0xa2, 0xa3, 0x49, 0xd1, 0x71, 0xa4, 0xe8, 0x04, 0xf3, 0xdd, 0x5c, 0x5f, 0x97, 0xfd, 0x7b, 0x24, 0x89, 0x24, 0x7d, 0x92, 0x24, 0x1d, 0x4f, 0x92, 0x3e, 0x6a, 0x7e, 0x80, 0x44, 0x5a, 0x8f, 0xc7, 0xac, 0x4f, 0xba, 0x6e, 0x40, 0xe2, 0x6e, 0xc8, 0x6d, 0x1f, 0xe4, 0x7e, 0x8d, 0x24, 0x86, 0x64, 0x9d, 0x60, 0x6e, 0xc2, 0x71, 0x9a, 0xb2, 0x7d, 0x88, 0x6d, 0x33, 0xae, 0x1b, 0xc8, 0xdf, 0x06, 0x91, 0xd8, 0x07, 0x73, 0xdf, 0x61, 0x12, 0x41, 0xc2, 0x8e, 0x20, 0x61, 0xdb, 0x49, 0xd8, 0x09, 0x24, 0xec, 0x70, 0x12, 0x76, 0x14, 0x09, 0x3b, 0x9c, 0x84, 0x1d, 0x4e, 0xc2, 0x0e, 0x27, 0x61, 0xdb, 0x49, 0xd8, 0x41, 0xe6, 0x78, 0x92, 0xba, 0x0d, 0xa9, 0xa4, 0x76, 0x07, 0xd7, 0x65, 0x49, 0x1a, 0x69, 0x3b, 0x8d, 0xb4, 0x6d, 0x23, 0x6d, 0xdb, 0x48, 0xdb, 0x36, 0xd2, 0xb6, 0xcd, 0x7c, 0x89, 0xeb, 0x72, 0xd9, 0xe6, 0xc1, 0xf7, 0xbb, 0x97, 0x97, 0x39, 0x4f, 0x0f, 0xdb, 0x02, 0x14, 0xa2, 0x88, 0x63, 0x16, 0x73, 0x5d, 0x09, 0x4a, 0x51, 0xe6, 0x4f, 0xdd, 0xf1, 0xa4, 0xee, 0x93, 0xa4, 0xee, 0x93, 0xa4, 0x6e, 0xdb, 0x9f, 0xef, 0x7f, 0x5f, 0x67, 0x9f, 0x31, 0x82, 0xe4, 0x6d, 0x23, 0x79, 0xdb, 0x48, 0xde, 0x27, 0x49, 0xde, 0x36, 0x92, 0x77, 0x24, 0xc9, 0xfb, 0x1e, 0x92, 0x77, 0x38, 0xc9, 0xdb, 0x46, 0xf2, 0xb6, 0x05, 0x04, 0x4a, 0x46, 0x40, 0x0d, 0x58, 0xb8, 0xce, 0x2a, 0x76, 0x52, 0xb8, 0x8d, 0x14, 0x6e, 0x27, 0x85, 0xdb, 0x49, 0xe1, 0x76, 0x52, 0xb8, 0x8d, 0x14, 0x6e, 0x23, 0x85, 0xdb, 0x48, 0xe1, 0xe1, 0xa4, 0xf0, 0x70, 0x52, 0x78, 0x38, 0x29, 0xbc, 0x80, 0x14, 0x1e, 0x13, 0xb0, 0x52, 0x0a, 0x49, 0xe2, 0xc5, 0x81, 0xf5, 0xd9, 0x36, 0x24, 0x85, 0x37, 0x92, 0x70, 0xd2, 0xf8, 0x21, 0xd2, 0xf8, 0x51, 0xd2, 0x78, 0x0a, 0x69, 0x3c, 0x85, 0x34, 0xde, 0x8b, 0x34, 0xde, 0x90, 0x34, 0x9e, 0x42, 0x1a, 0x4f, 0x21, 0x8d, 0xa7, 0x90, 0xc6, 0x53, 0x48, 0xe3, 0x29, 0xa4, 0x71, 0x3b, 0x69, 0xbc, 0x01, 0x69, 0x3c, 0x9c, 0x34, 0x1e, 0x41, 0x12, 0x8f, 0x20, 0x89, 0xc7, 0x90, 0xc4, 0xa3, 0x49, 0xe2, 0xe1, 0x81, 0x8c, 0x77, 0xa4, 0x71, 0x3b, 0x69, 0x3c, 0x9c, 0x34, 0x6e, 0x27, 0x8d, 0xc7, 0x93, 0xc6, 0x8f, 0x91, 0xc6, 0xc7, 0x90, 0xc6, 0x8f, 0x92, 0xc6, 0x9d, 0xa4, 0xf1, 0x68, 0xd2, 0x78, 0x22, 0x69, 0xdc, 0x46, 0x1a, 0x4f, 0x21, 0x8d, 0x17, 0x93, 0xc6, 0xc3, 0x2d, 0xad, 0x24, 0xdb, 0xf2, 0x08, 0x5a, 0xa3, 0x0d, 0xda, 0xe2, 0x51, 0xb4, 0x43, 0x7b, 0x3c, 0x86, 0x0e, 0x78, 0x1c, 0x4f, 0x48, 0xa1, 0xe5, 0x49, 0xb6, 0x4f, 0xe1, 0x69, 0x3c, 0x83, 0x8e, 0xe8, 0x84, 0x67, 0xf1, 0x1c, 0x9e, 0x47, 0x67, 0x74, 0x41, 0x57, 0x74, 0x43, 0x77, 0xbc, 0x80, 0x1e, 0x78, 0x11, 0x2f, 0xa1, 0x27, 0x5e, 0xe1, 0x78, 0xbd, 0xd0, 0x9b, 0xfd, 0x3e, 0xe8, 0xcb, 0x7e, 0x3f, 0xf4, 0x67, 0x7f, 0x00, 0x06, 0x62, 0x10, 0x06, 0x63, 0x08, 0x5e, 0xc5, 0x50, 0xfe, 0x3e, 0x0c, 0xaf, 0xb1, 0xff, 0x3a, 0x86, 0xb3, 0xff, 0x06, 0x46, 0xb0, 0x3f, 0x92, 0xed, 0x28, 0xbc, 0xc9, 0xfe, 0x5b, 0x92, 0x68, 0x79, 0x1b, 0xef, 0x48, 0xb2, 0x65, 0x8c, 0xd8, 0x2d, 0x1f, 0xe1, 0x63, 0x8c, 0xc5, 0x27, 0xf8, 0x14, 0x9f, 0x61, 0x33, 0xb6, 0x20, 0x04, 0xa1, 0x08, 0xc3, 0x56, 0x6c, 0xc3, 0x4e, 0xf5, 0xa9, 0x25, 0x4e, 0x75, 0xb4, 0xc4, 0xab, 0x67, 0x2c, 0x36, 0xd5, 0xc4, 0x92, 0xa0, 0xee, 0xb1, 0x24, 0xab, 0x19, 0x96, 0x14, 0xf6, 0xd3, 0x70, 0x4a, 0xb5, 0xb1, 0x64, 0xa9, 0xfb, 0x2c, 0xd9, 0xec, 0x57, 0xa8, 0x20, 0xfd, 0x76, 0xb1, 0xeb, 0xac, 0x31, 0xf4, 0x3b, 0x71, 0x17, 0x6a, 0xa2, 0x16, 0x6a, 0xa3, 0x0e, 0xee, 0x46, 0xd5, 0x2b, 0x12, 0x76, 0xfd, 0x5e, 0x30, 0xd7, 0xe8, 0xcc, 0x35, 0x3a, 0xeb, 0x11, 0x9d, 0xf5, 0x88, 0xce, 0x7a, 0x44, 0x6f, 0x80, 0x86, 0x60, 0x5d, 0xa2, 0x37, 0x42, 0x63, 0x34, 0x41, 0x53, 0x3c, 0x84, 0x66, 0x78, 0x18, 0xcd, 0xd1, 0x02, 0x2d, 0xd1, 0x0a, 0x8f, 0x48, 0xbe, 0xde, 0x86, 0x6d, 0x5b, 0xb6, 0x8f, 0xa2, 0x1d, 0xfb, 0xed, 0xf1, 0x18, 0x3a, 0xa0, 0x87, 0x14, 0xe9, 0x2f, 0x82, 0x39, 0x42, 0x7f, 0x19, 0x3d, 0xf1, 0x0a, 0x7a, 0xa1, 0x37, 0xfa, 0xa0, 0x2f, 0xc6, 0xa9, 0xda, 0xfa, 0xa7, 0x98, 0x82, 0xa9, 0x98, 0x86, 0xaf, 0x31, 0x1d, 0xdf, 0xa8, 0x78, 0x7d, 0x06, 0xdb, 0x6f, 0x31, 0x13, 0xb3, 0x30, 0x1b, 0x73, 0xb0, 0x10, 0x8b, 0x10, 0xac, 0xea, 0xea, 0xcb, 0xe5, 0xa4, 0xbe, 0x02, 0x2b, 0xb1, 0x0a, 0xab, 0xb1, 0x06, 0x6b, 0xb1, 0x0e, 0xeb, 0xb1, 0x01, 0x1b, 0xb1, 0x09, 0x9b, 0xb1, 0x45, 0x8d, 0xd0, 0x43, 0xd8, 0x86, 0x22, 0x0c, 0x5b, 0xb1, 0x0d, 0xdb, 0xb1, 0x03, 0x3b, 0xb1, 0x0b, 0x3f, 0xe3, 0x17, 0xfc, 0x8a, 0xdd, 0xca, 0xa4, 0xef, 0x21, 0xf9, 0xef, 0x65, 0xff, 0x37, 0xec, 0x83, 0xef, 0xdf, 0x9b, 0x1d, 0x60, 0x7b, 0x10, 0x87, 0x10, 0x8e, 0x63, 0x12, 0xae, 0x1f, 0xc7, 0x09, 0x44, 0x22, 0x0a, 0xd1, 0x88, 0x41, 0xac, 0x44, 0xe8, 0x71, 0x88, 0x87, 0x0d, 0x09, 0x5c, 0x97, 0x08, 0x3b, 0xfb, 0x49, 0x6c, 0x93, 0x91, 0x82, 0x54, 0xa4, 0xc1, 0x81, 0x74, 0x64, 0x20, 0x13, 0x59, 0xc8, 0xc6, 0x69, 0x9c, 0xc1, 0x59, 0x38, 0xd5, 0x53, 0x7a, 0x0e, 0xdb, 0x73, 0xa0, 0xcf, 0xea, 0xe7, 0xc1, 0x7a, 0x45, 0x77, 0xc1, 0x0d, 0xd6, 0x2b, 0x3a, 0x73, 0xb9, 0x9e, 0x87, 0x7c, 0x5c, 0x96, 0x14, 0xdd, 0x03, 0xd2, 0x97, 0x5e, 0xc8, 0xb6, 0x08, 0xc5, 0x28, 0x41, 0x29, 0xca, 0x70, 0x05, 0xe5, 0xb8, 0x8a, 0x0a, 0x54, 0xe2, 0x1a, 0xae, 0xe3, 0x06, 0xbc, 0xb8, 0x89, 0x3f, 0x24, 0xc5, 0xa8, 0x21, 0x05, 0x86, 0x05, 0x3a, 0x0c, 0x58, 0x25, 0xdc, 0x08, 0x62, 0x7b, 0x1b, 0x6e, 0xc7, 0x1d, 0xb8, 0x13, 0xb5, 0xd5, 0xf3, 0xc6, 0xfd, 0xaa, 0xb5, 0x51, 0x4f, 0x75, 0x36, 0x1a, 0x2a, 0xdd, 0x78, 0x50, 0xd5, 0x37, 0x1a, 0xa9, 0x7a, 0x46, 0x63, 0xd5, 0xd2, 0x68, 0xa2, 0x02, 0x8c, 0xa6, 0xfe, 0x7f, 0xa3, 0xf7, 0x62, 0xf5, 0x2b, 0x17, 0x09, 0xd5, 0xaf, 0x5c, 0xb4, 0x31, 0xda, 0x72, 0x7d, 0x3b, 0xb6, 0x4f, 0x73, 0xdd, 0x73, 0xaa, 0xad, 0xf1, 0xbc, 0x7a, 0xc0, 0xe8, 0xac, 0x9e, 0x36, 0xba, 0x89, 0xcd, 0xe8, 0x8e, 0x17, 0xd0, 0x03, 0x2f, 0xe2, 0x65, 0xf4, 0xc4, 0x2b, 0x60, 0x9c, 0x34, 0x7a, 0x83, 0xb1, 0xd2, 0xe8, 0x0b, 0xc6, 0x4b, 0xa3, 0x3f, 0x06, 0x60, 0x20, 0x06, 0x71, 0x9e, 0x8c, 0x9b, 0xc6, 0x10, 0xbc, 0x8a, 0xa1, 0x18, 0x86, 0xd7, 0xf0, 0x3a, 0x86, 0xe3, 0x0d, 0x8c, 0xc0, 0x48, 0x8c, 0xc2, 0x9b, 0x78, 0x4b, 0xb5, 0x33, 0xde, 0x66, 0xfb, 0x0e, 0xde, 0xc5, 0x7b, 0x78, 0x1f, 0xa3, 0xf1, 0x01, 0x3e, 0xc4, 0x18, 0x7c, 0x84, 0x8f, 0x31, 0x16, 0xac, 0x81, 0x0d, 0xd6, 0xc0, 0x06, 0x6b, 0x60, 0xe3, 0x33, 0xb0, 0x0e, 0x32, 0x58, 0xa3, 0x19, 0xe3, 0x31, 0x01, 0x13, 0x31, 0x09, 0x93, 0x25, 0xce, 0xf8, 0x92, 0xed, 0x57, 0x9c, 0x1f, 0xeb, 0x22, 0x83, 0xb5, 0xb0, 0xc1, 0x1a, 0xd8, 0x60, 0x0d, 0x6c, 0xcc, 0x90, 0x4c, 0x83, 0x35, 0xb0, 0xc1, 0x1a, 0xd8, 0x60, 0x0d, 0x6c, 0xcc, 0xc6, 0x1c, 0xcc, 0xa5, 0x6c, 0xbf, 0x63, 0x3b, 0x8f, 0x32, 0x9a, 0x2f, 0x51, 0xc6, 0xf7, 0xf0, 0xfd, 0xeb, 0xb9, 0x85, 0x58, 0x04, 0xd6, 0x4e, 0xc6, 0x0f, 0x58, 0x8c, 0x1f, 0xb1, 0x04, 0x3f, 0xc9, 0x49, 0x63, 0x19, 0xf7, 0x59, 0x8e, 0x15, 0x58, 0x89, 0x55, 0x58, 0x8d, 0x35, 0xea, 0x55, 0x63, 0x2d, 0xdb, 0x75, 0x58, 0x8f, 0x0d, 0xd8, 0x88, 0x4d, 0xd8, 0x8c, 0x2d, 0x08, 0x41, 0x28, 0xc2, 0xb0, 0x15, 0xac, 0xaf, 0x0c, 0xd6, 0x57, 0xc6, 0x0e, 0xec, 0x04, 0xeb, 0x2b, 0xe3, 0x67, 0xfc, 0x82, 0x5f, 0xb1, 0x1b, 0x7b, 0xb0, 0x17, 0xbf, 0x61, 0x1f, 0xf6, 0xe3, 0x00, 0x0e, 0xe2, 0x10, 0xc2, 0x71, 0x18, 0x11, 0x60, 0x7d, 0x65, 0x1c, 0xc5, 0x31, 0x1c, 0xc7, 0x09, 0x44, 0x82, 0x75, 0x96, 0x71, 0x12, 0xd1, 0x92, 0x6c, 0xc4, 0xb0, 0x8d, 0x45, 0x1c, 0xe2, 0x61, 0x43, 0x02, 0x12, 0x61, 0x47, 0x12, 0x92, 0x91, 0x82, 0x54, 0xa4, 0x49, 0xb4, 0xe1, 0x60, 0x9b, 0x2e, 0xe9, 0x46, 0x06, 0xfb, 0x99, 0x60, 0xfd, 0x6f, 0x64, 0x89, 0xdd, 0xc8, 0xc6, 0x69, 0x9c, 0x01, 0xf3, 0x9f, 0xe1, 0x44, 0x0e, 0xce, 0x49, 0x86, 0xf1, 0x3b, 0xce, 0xe3, 0x82, 0xff, 0xdf, 0x44, 0x3b, 0x0c, 0x17, 0xdc, 0xb8, 0x84, 0x5c, 0xe4, 0x21, 0x1f, 0x97, 0xe1, 0x41, 0x01, 0x0a, 0x51, 0x84, 0x62, 0x94, 0xa0, 0x14, 0x65, 0x92, 0x66, 0x5c, 0x41, 0x39, 0xae, 0xa2, 0x02, 0x95, 0xb8, 0x26, 0xa9, 0xd6, 0xfb, 0x55, 0x63, 0x6b, 0x3d, 0xc9, 0xb6, 0xd6, 0x97, 0x22, 0x6b, 0x03, 0x34, 0xc4, 0x83, 0x5c, 0x6e, 0xc4, 0x96, 0xbc, 0x68, 0x25, 0x2f, 0x5a, 0x9b, 0x72, 0xf9, 0x21, 0x34, 0xc3, 0xc3, 0x68, 0xce, 0x75, 0x2d, 0xd0, 0x12, 0xcc, 0xab, 0xd6, 0x47, 0xd8, 0xb6, 0x46, 0x1b, 0xb4, 0x95, 0xc3, 0xd6, 0x47, 0xd1, 0x0e, 0xed, 0xf1, 0x18, 0x3a, 0xe0, 0x71, 0x3c, 0x81, 0x27, 0xf1, 0x14, 0x9e, 0xc6, 0x33, 0xe8, 0x88, 0x4e, 0x78, 0x16, 0xcf, 0xe1, 0x79, 0x74, 0x46, 0x17, 0x74, 0x45, 0x37, 0x74, 0xc7, 0x0b, 0xe8, 0x81, 0x17, 0xf1, 0x12, 0x5e, 0x96, 0x63, 0xd6, 0x9e, 0x78, 0x05, 0xbd, 0xd0, 0x1b, 0x7d, 0xd0, 0x17, 0xfd, 0xd0, 0x1f, 0x03, 0x24, 0xc6, 0x3a, 0x10, 0x83, 0x30, 0x18, 0x43, 0xf0, 0x2a, 0x86, 0x62, 0x18, 0x5e, 0xc3, 0xeb, 0x18, 0x8e, 0x37, 0x30, 0x02, 0x23, 0x31, 0x0a, 0x6f, 0xe2, 0x2d, 0xbc, 0x8d, 0x77, 0xf0, 0x2e, 0xde, 0xc3, 0xfb, 0x18, 0x8d, 0x0f, 0xf0, 0x21, 0xc6, 0x48, 0xac, 0xff, 0xb7, 0xc7, 0x3f, 0x66, 0x3b, 0x56, 0x8e, 0x5a, 0x3f, 0x61, 0x3b, 0x0e, 0x9f, 0xe2, 0x33, 0x7c, 0x8e, 0x2f, 0x30, 0x1e, 0x13, 0x30, 0x11, 0x93, 0x30, 0x19, 0x5f, 0xe2, 0x2b, 0x4c, 0xc1, 0x54, 0x4c, 0xc3, 0x0c, 0x8e, 0xf9, 0x2d, 0x66, 0x62, 0x16, 0xc8, 0x9b, 0x56, 0xf2, 0xa6, 0x75, 0x2e, 0xbe, 0xc3, 0x3c, 0xcc, 0xc7, 0xf7, 0x58, 0x80, 0x85, 0x58, 0x84, 0x60, 0xfc, 0x80, 0xc5, 0xf8, 0x11, 0x4b, 0xf0, 0x93, 0x1c, 0xb4, 0x2e, 0xe3, 0x9c, 0x96, 0x63, 0x05, 0x56, 0x62, 0x15, 0x56, 0x63, 0x0d, 0xd6, 0x62, 0x1d, 0xd6, 0x63, 0x03, 0x36, 0x62, 0x13, 0x36, 0x63, 0x0b, 0x42, 0x10, 0x8a, 0x30, 0x6c, 0xc5, 0x36, 0x6c, 0xc7, 0x0e, 0xec, 0xc4, 0x2e, 0xec, 0x91, 0x52, 0xeb, 0x41, 0xb1, 0x5b, 0x0f, 0xa9, 0x7b, 0xac, 0xe1, 0x38, 0x8c, 0x08, 0x1c, 0x93, 0x12, 0xeb, 0x09, 0x49, 0xb0, 0x46, 0x8a, 0xcd, 0x1a, 0x25, 0x69, 0xd6, 0x93, 0x88, 0x96, 0x68, 0x6b, 0x2c, 0xe2, 0xb8, 0x2e, 0x1e, 0x36, 0x24, 0x20, 0x11, 0x76, 0x24, 0x71, 0xfb, 0x64, 0xa4, 0xc8, 0x49, 0x6b, 0x2a, 0x97, 0xe9, 0x37, 0xd6, 0x53, 0x72, 0xc3, 0x9a, 0xcf, 0xf1, 0x2f, 0xc3, 0x83, 0x02, 0x14, 0xe2, 0x8a, 0x84, 0x5b, 0xcb, 0x71, 0x15, 0xac, 0x07, 0xad, 0x95, 0xb8, 0xc6, 0x73, 0xbe, 0x8e, 0x1b, 0xf0, 0xe2, 0xa6, 0xc4, 0x04, 0x2d, 0x92, 0xc3, 0x41, 0x8b, 0x55, 0xef, 0xa0, 0x1f, 0xd5, 0x84, 0xa0, 0x9f, 0x54, 0xc3, 0xa0, 0xa5, 0x52, 0x18, 0x44, 0x8e, 0x0b, 0x3a, 0x28, 0x45, 0x41, 0x87, 0xc4, 0x1e, 0x14, 0x2e, 0x91, 0x41, 0x87, 0x11, 0x21, 0xe1, 0x41, 0xcc, 0x8b, 0x41, 0x89, 0x92, 0x1a, 0x30, 0x40, 0x99, 0xa5, 0x50, 0x05, 0x20, 0x50, 0x92, 0x95, 0x45, 0xd2, 0x95, 0x8e, 0x3b, 0x50, 0x5b, 0xca, 0x54, 0x5d, 0x12, 0xfa, 0x03, 0xec, 0xd7, 0x43, 0x7d, 0x3c, 0x88, 0xbf, 0x5e, 0x59, 0xb9, 0x4b, 0xb5, 0x42, 0x6b, 0xd2, 0x7c, 0x1b, 0xb4, 0x45, 0x3b, 0x89, 0x54, 0xed, 0xf1, 0x18, 0x3a, 0xe0, 0x71, 0x3c, 0x81, 0x27, 0xe5, 0xa4, 0x7a, 0x0a, 0x4f, 0xe3, 0x19, 0x89, 0x57, 0x1d, 0xb9, 0xae, 0x13, 0x9e, 0x95, 0x4c, 0xf5, 0x1c, 0x9e, 0x47, 0x67, 0x74, 0x41, 0x57, 0x74, 0x43, 0x77, 0xbc, 0x80, 0x1e, 0x78, 0x11, 0x2f, 0xe1, 0x65, 0xf4, 0xc4, 0x2b, 0xe8, 0x85, 0xde, 0xe8, 0x83, 0xbe, 0xe8, 0x87, 0xfe, 0x18, 0x80, 0x81, 0x18, 0x84, 0xc1, 0x18, 0x82, 0x57, 0x31, 0x14, 0xc3, 0xf0, 0x1a, 0x5e, 0xc7, 0x70, 0x8c, 0xe0, 0x79, 0x8c, 0x52, 0x83, 0xd4, 0x7b, 0xaa, 0xa5, 0x7a, 0x1f, 0x1f, 0x48, 0x9a, 0xfa, 0x08, 0x1f, 0x63, 0x2c, 0x3e, 0xe1, 0x1c, 0xc7, 0xe1, 0x53, 0x7c, 0xc6, 0xe5, 0x2f, 0x38, 0xff, 0xf1, 0x6c, 0x27, 0x48, 0x9c, 0x9a, 0x88, 0x49, 0x60, 0x36, 0x51, 0x5f, 0x71, 0xac, 0x29, 0xe2, 0x50, 0x53, 0x31, 0x0d, 0x5f, 0x63, 0x3a, 0xbe, 0xc1, 0x0c, 0x7c, 0x8b, 0x99, 0x98, 0x85, 0xd9, 0x62, 0x57, 0x73, 0x30, 0x57, 0x12, 0xd5, 0x77, 0xdc, 0x6f, 0x1e, 0x65, 0x3f, 0x5f, 0xb2, 0xd5, 0xf7, 0xec, 0xc7, 0xfa, 0xdf, 0xbf, 0x4f, 0x57, 0xf1, 0x6c, 0xed, 0x6c, 0x93, 0xa5, 0x54, 0xa5, 0x48, 0x94, 0x4a, 0x93, 0x94, 0xea, 0xf7, 0xf1, 0x53, 0x54, 0x86, 0xc4, 0xaa, 0x4c, 0xfe, 0x7e, 0x4a, 0x92, 0x54, 0x16, 0xb7, 0xc9, 0x16, 0x9b, 0x3a, 0xcd, 0xfe, 0x59, 0xce, 0x8b, 0x11, 0xb5, 0xfa, 0x37, 0x4f, 0x7c, 0x9f, 0x32, 0x4b, 0x53, 0x17, 0x38, 0xef, 0x8b, 0x70, 0xf1, 0x77, 0x37, 0x2e, 0xb1, 0x9f, 0x2b, 0x47, 0x15, 0x49, 0x4b, 0x91, 0x60, 0xab, 0x5f, 0x55, 0x49, 0x57, 0x37, 0x55, 0x1d, 0xff, 0xfb, 0xfa, 0x55, 0xaf, 0xaa, 0xa4, 0x9b, 0xee, 0x94, 0x14, 0xd3, 0x5d, 0x72, 0xca, 0x54, 0x53, 0xd2, 0x4c, 0xb5, 0x50, 0x1b, 0x77, 0x73, 0xb9, 0xae, 0x24, 0x98, 0xee, 0x61, 0xff, 0x5e, 0xfc, 0xf5, 0xaa, 0x4a, 0x9a, 0xa9, 0x1e, 0xea, 0xa3, 0x81, 0x44, 0x9a, 0x1a, 0xb2, 0xf5, 0x7d, 0x0f, 0x67, 0x63, 0xf6, 0x9b, 0xb0, 0xdf, 0x14, 0x0f, 0x49, 0xa2, 0xa9, 0x19, 0xdb, 0x87, 0xd1, 0x9c, 0xbf, 0xb5, 0x60, 0xdb, 0x12, 0xad, 0xb8, 0xfe, 0x11, 0x6e, 0xd7, 0x9a, 0xfd, 0x36, 0xec, 0x57, 0xbd, 0xd7, 0x7f, 0xd2, 0x34, 0x5e, 0xf2, 0x4d, 0x13, 0xa4, 0xd0, 0x34, 0x51, 0xa2, 0x4c, 0x93, 0xa4, 0xd4, 0x34, 0x99, 0xc7, 0xfd, 0x92, 0x73, 0xfb, 0x4a, 0x4e, 0x9b, 0xa6, 0x48, 0x91, 0x69, 0x8e, 0x64, 0x99, 0xe6, 0xf2, 0xf7, 0x79, 0x5c, 0x37, 0x9f, 0xfd, 0xef, 0xa5, 0xcc, 0xe4, 0xe6, 0xbc, 0x73, 0x39, 0x96, 0xef, 0xf7, 0x41, 0x8a, 0xb9, 0xbe, 0x04, 0xa5, 0x92, 0x5d, 0xfd, 0x6a, 0x4a, 0x7a, 0xf5, 0xab, 0x29, 0x25, 0xda, 0xcb, 0x52, 0xac, 0xf5, 0x54, 0xef, 0x6b, 0xaf, 0x70, 0x5d, 0x2f, 0xf5, 0xa0, 0xd6, 0x4f, 0xbd, 0xab, 0xf5, 0x67, 0x7f, 0x00, 0x49, 0xbf, 0xea, 0xd5, 0x94, 0xbb, 0xb4, 0x91, 0x5c, 0xae, 0x7a, 0x25, 0x25, 0x52, 0xe3, 0x7c, 0xb4, 0xf1, 0x5c, 0x3f, 0x89, 0xbf, 0x7f, 0x25, 0x27, 0xb4, 0x29, 0x6c, 0xa9, 0x63, 0x8d, 0x3a, 0xd6, 0xbe, 0x21, 0xf5, 0xcf, 0x22, 0xe1, 0xcf, 0x51, 0x0f, 0x68, 0x73, 0xb9, 0xdd, 0x77, 0xa4, 0xfb, 0x79, 0x5c, 0x17, 0x2c, 0x71, 0xda, 0x8f, 0x5c, 0x5e, 0xc2, 0xe3, 0x2f, 0x93, 0x64, 0x6d, 0x15, 0xd7, 0xb1, 0x62, 0xd6, 0xd6, 0xb3, 0xdd, 0x80, 0x8d, 0xd8, 0x84, 0xcd, 0xd8, 0x82, 0x10, 0x84, 0x22, 0x0c, 0x5b, 0xb1, 0x8d, 0x63, 0x6f, 0xc7, 0x0e, 0xb1, 0x6b, 0x3b, 0x25, 0x5a, 0xdb, 0xc5, 0x63, 0xfe, 0xec, 0xff, 0x0c, 0x41, 0xa2, 0xf6, 0xab, 0xc4, 0x6a, 0xbb, 0x39, 0xaf, 0x3d, 0x1c, 0x6f, 0xaf, 0xc4, 0x68, 0xbf, 0xf1, 0xf8, 0xcc, 0xf4, 0xda, 0x7e, 0xae, 0x3f, 0xc0, 0xdf, 0xab, 0x3e, 0x43, 0x50, 0x78, 0xcb, 0x67, 0x08, 0x1c, 0xda, 0x11, 0x8e, 0x73, 0x94, 0x7d, 0x46, 0xaa, 0xea, 0xcf, 0x12, 0xd8, 0xb5, 0x93, 0xdc, 0x36, 0x9a, 0xfd, 0x18, 0xd5, 0x57, 0x8b, 0x65, 0x1b, 0xc7, 0x63, 0xc4, 0x73, 0xdf, 0xaa, 0xcf, 0x12, 0x9c, 0xd0, 0x12, 0x25, 0x5e, 0x23, 0xb5, 0x6a, 0x49, 0x6c, 0x53, 0xb9, 0x6d, 0x1a, 0xc7, 0x71, 0x70, 0x9b, 0x74, 0x64, 0xaa, 0xcf, 0xb5, 0x53, 0xea, 0x5b, 0x2d, 0x4b, 0xfd, 0xa8, 0x9d, 0x96, 0xfc, 0xea, 0xcf, 0x16, 0x24, 0x6a, 0x4e, 0x49, 0xd2, 0x72, 0x38, 0xaf, 0x73, 0x1c, 0xff, 0x77, 0xca, 0xe0, 0x3c, 0xe7, 0x76, 0x41, 0x32, 0xb4, 0x8b, 0x3c, 0x27, 0x97, 0xaa, 0xa7, 0x5d, 0x52, 0xfd, 0xb5, 0x5c, 0xca, 0x3f, 0x8f, 0xdb, 0xe5, 0x4b, 0x9e, 0xc6, 0x48, 0xa7, 0x79, 0xb8, 0x5d, 0xd5, 0xe7, 0x0e, 0x16, 0x6b, 0x45, 0x52, 0xa4, 0x15, 0x73, 0xfc, 0x12, 0xce, 0xa1, 0x94, 0xe3, 0x94, 0x71, 0xfd, 0x15, 0xf6, 0xcb, 0x79, 0xec, 0xab, 0x9c, 0x63, 0x05, 0x2a, 0xa9, 0x1b, 0xaf, 0x1a, 0xac, 0xdd, 0xc4, 0x1f, 0x94, 0x8d, 0x48, 0x9c, 0x59, 0x49, 0x82, 0xd9, 0x44, 0xe2, 0xd7, 0xe4, 0x84, 0xd9, 0x2c, 0x69, 0xe6, 0x00, 0x04, 0x82, 0x74, 0x6c, 0xb6, 0x88, 0xa7, 0xfa, 0x15, 0x94, 0x34, 0xb3, 0x95, 0xdb, 0x54, 0x7d, 0x0e, 0x21, 0xc1, 0x7c, 0xbb, 0x1c, 0x37, 0xdf, 0xc1, 0xfe, 0x9d, 0xdc, 0xe6, 0x2e, 0x2e, 0xd7, 0x44, 0x2d, 0x6e, 0x57, 0x5b, 0x92, 0xcc, 0x75, 0xb8, 0xed, 0xdd, 0x1c, 0xb7, 0xae, 0xa4, 0x98, 0xef, 0x91, 0x0c, 0x33, 0xc9, 0xce, 0x7c, 0x9f, 0x24, 0x9b, 0xef, 0x97, 0x68, 0x33, 0xa9, 0xce, 0x5c, 0x5f, 0x32, 0xcd, 0x0d, 0xe4, 0x94, 0xb9, 0x21, 0x7f, 0x7f, 0x90, 0xfb, 0x37, 0xe2, 0xb6, 0x8d, 0xb9, 0x4f, 0x13, 0xfe, 0x4e, 0x7b, 0xf7, 0x7f, 0x36, 0xa1, 0x19, 0xd7, 0x0d, 0xe4, 0xb6, 0x7f, 0x7d, 0x3e, 0x21, 0xca, 0xfc, 0x9a, 0x44, 0x9a, 0x37, 0x49, 0xba, 0x79, 0x33, 0xf7, 0xdb, 0xc2, 0x3e, 0xab, 0x3b, 0xf3, 0x0e, 0xb6, 0xbb, 0x40, 0x9d, 0x9a, 0x4f, 0xf0, 0xb7, 0x28, 0xa5, 0x9b, 0xe3, 0xa5, 0xd8, 0x6c, 0x43, 0xaa, 0x94, 0x98, 0x1d, 0x5c, 0x97, 0x25, 0xd9, 0xe6, 0x6c, 0x5c, 0xe0, 0xd8, 0x17, 0xe1, 0x82, 0x1b, 0x97, 0x38, 0x87, 0x5c, 0xb6, 0x79, 0xc8, 0x87, 0xef, 0x3b, 0x6d, 0x3d, 0x6c, 0x0b, 0x50, 0x88, 0x22, 0x8e, 0x59, 0xf5, 0x1d, 0xb7, 0x29, 0x66, 0x52, 0x87, 0xb9, 0x4c, 0x0a, 0xfd, 0xdf, 0x71, 0x5b, 0xce, 0x39, 0x5f, 0x05, 0xab, 0x23, 0x73, 0x25, 0xe7, 0x75, 0x0d, 0xd7, 0xd9, 0xaf, 0x7a, 0xf5, 0x24, 0xcd, 0x7c, 0x13, 0x7f, 0xf0, 0x77, 0x91, 0xb4, 0x00, 0xca, 0x36, 0xc0, 0xa4, 0xea, 0x04, 0x68, 0x12, 0x19, 0x40, 0xd9, 0x06, 0x50, 0xb6, 0x01, 0x81, 0x52, 0x18, 0x50, 0x03, 0x16, 0xae, 0xab, 0x7a, 0xf5, 0x24, 0x2d, 0xe0, 0x45, 0x49, 0x0f, 0x78, 0x09, 0x3d, 0xd1, 0x8b, 0xcb, 0x7d, 0xd0, 0x0f, 0x03, 0xb8, 0xcd, 0x40, 0x0c, 0x06, 0xe3, 0x6d, 0xc0, 0x0a, 0x6e, 0xbf, 0x52, 0x4a, 0xfc, 0x9f, 0x63, 0xa8, 0xcf, 0xb6, 0xa1, 0xc4, 0x05, 0x36, 0x92, 0xc8, 0xc0, 0xc6, 0x72, 0x22, 0xb0, 0x89, 0xc4, 0x04, 0x1e, 0x95, 0xcc, 0xc0, 0x63, 0x88, 0x54, 0x2f, 0x07, 0x46, 0xa9, 0x7a, 0x81, 0x89, 0xec, 0xdb, 0x91, 0x84, 0x64, 0xa4, 0xe0, 0xac, 0xa4, 0x07, 0x3a, 0x55, 0x93, 0xc0, 0xdf, 0xb9, 0xdf, 0x25, 0x39, 0x19, 0x98, 0x0f, 0x66, 0xcc, 0xc0, 0x02, 0x49, 0x08, 0x2c, 0xe4, 0xba, 0xaa, 0x57, 0x4f, 0xd2, 0x03, 0x2b, 0xfc, 0xff, 0xea, 0x3a, 0xbd, 0xfa, 0xb3, 0x0c, 0x71, 0x35, 0x34, 0xf5, 0xfe, 0x2d, 0x9f, 0x65, 0x48, 0xaa, 0x51, 0x43, 0x52, 0x6a, 0x30, 0x46, 0x55, 0x7f, 0x96, 0xc1, 0xf7, 0xea, 0x49, 0xa4, 0xa5, 0x95, 0x14, 0x5a, 0x1e, 0x41, 0x6b, 0xb4, 0x41, 0x5b, 0x3c, 0x8a, 0x76, 0x68, 0x8f, 0xc7, 0xd0, 0x01, 0x8f, 0xa3, 0xea, 0xd5, 0x93, 0x42, 0xcb, 0x53, 0x78, 0x1a, 0xcf, 0xa0, 0x23, 0x3a, 0xe1, 0x59, 0x3c, 0x87, 0xe7, 0xd1, 0x19, 0x5d, 0xd0, 0x15, 0xdd, 0xd0, 0x1d, 0x2f, 0xa0, 0x07, 0x5e, 0xc4, 0x4b, 0xe8, 0x89, 0xbf, 0x5e, 0x3d, 0x29, 0xb4, 0xf4, 0xc1, 0x5f, 0xaf, 0x9e, 0x14, 0x5a, 0x06, 0x60, 0x20, 0x06, 0x61, 0x30, 0x86, 0xe0, 0x55, 0xfc, 0xf5, 0xea, 0x49, 0xa1, 0xe5, 0x75, 0xfc, 0xf5, 0xea, 0x49, 0xe1, 0x2d, 0xaf, 0x9e, 0x14, 0x5a, 0xde, 0x92, 0x64, 0xcb, 0xdb, 0x78, 0x47, 0x52, 0x2d, 0x63, 0x24, 0xdd, 0xf2, 0x11, 0x3e, 0xc6, 0x58, 0x7c, 0x82, 0x4f, 0xf1, 0x19, 0x36, 0x63, 0x0b, 0x42, 0x10, 0x8a, 0x30, 0x6c, 0xc5, 0x36, 0xec, 0x54, 0x1f, 0x5b, 0xe2, 0xd4, 0x93, 0x96, 0x78, 0xf5, 0x84, 0xc5, 0xa6, 0x1e, 0xb4, 0x24, 0xa8, 0xda, 0x96, 0x64, 0x35, 0xcd, 0x92, 0xc2, 0x7e, 0x1a, 0x4e, 0xa9, 0x96, 0x96, 0x2c, 0x55, 0xd7, 0x92, 0xcd, 0x7e, 0xbe, 0x94, 0x59, 0x2e, 0xc3, 0x83, 0x0a, 0xa5, 0xeb, 0xb7, 0x4b, 0xba, 0xce, 0xba, 0x42, 0xbf, 0x13, 0x77, 0xa1, 0x26, 0x6a, 0xa1, 0x36, 0xea, 0xe0, 0x6e, 0xd4, 0x25, 0x65, 0xdf, 0xc3, 0xf6, 0x5e, 0xdc, 0x87, 0xfb, 0xc1, 0xfa, 0x43, 0x67, 0xfd, 0xa1, 0xb3, 0xfe, 0xd0, 0x1b, 0xa0, 0x21, 0x58, 0x87, 0xe8, 0x8d, 0xd0, 0x18, 0x4d, 0xd0, 0x14, 0xd4, 0xa1, 0xde, 0x0c, 0x0f, 0xa3, 0x39, 0x5a, 0xa0, 0x25, 0x5a, 0xa1, 0xea, 0xb3, 0x1d, 0xe9, 0xb7, 0x7c, 0xb6, 0x23, 0x5d, 0x6f, 0x8f, 0xc7, 0xd0, 0x01, 0xff, 0xfe, 0x2b, 0x29, 0x77, 0xe9, 0x9f, 0x62, 0x0a, 0xa6, 0x62, 0x1a, 0xbe, 0xc6, 0x74, 0x7c, 0xa3, 0xa2, 0xf5, 0x19, 0x6c, 0xbf, 0xc5, 0x4c, 0xcc, 0xc2, 0x6c, 0xcc, 0xc1, 0x42, 0x2c, 0x42, 0xb0, 0xaa, 0xa5, 0x2f, 0x97, 0x04, 0x7d, 0x05, 0x56, 0x62, 0x15, 0x56, 0x63, 0x0d, 0xd6, 0x62, 0x1d, 0xd6, 0x63, 0x03, 0x36, 0x62, 0x13, 0x36, 0xa3, 0xea, 0x95, 0x94, 0x04, 0x3d, 0x14, 0x61, 0xd8, 0x8a, 0x6d, 0xd8, 0x8e, 0x1d, 0xd8, 0x89, 0x5d, 0xf8, 0x19, 0xbf, 0xe0, 0x57, 0x54, 0xbd, 0x92, 0x92, 0xa0, 0xef, 0x05, 0x73, 0x84, 0xbe, 0x0f, 0xfb, 0xa5, 0x42, 0x3f, 0xc0, 0xf6, 0x20, 0x0e, 0x21, 0x1c, 0xc7, 0x24, 0x52, 0x3f, 0x8e, 0x13, 0x88, 0x44, 0x14, 0xa2, 0x11, 0x83, 0x58, 0x39, 0xa9, 0xc7, 0x21, 0x1e, 0x36, 0x24, 0x70, 0x5d, 0x22, 0xec, 0xec, 0x27, 0xb1, 0x4d, 0x46, 0x0a, 0x52, 0x91, 0x06, 0x07, 0xd2, 0x91, 0x81, 0x4c, 0x64, 0x21, 0x1b, 0xa7, 0x71, 0x06, 0x67, 0xe1, 0x54, 0xcf, 0xe8, 0x39, 0x6c, 0xcf, 0x81, 0xfe, 0xab, 0x9f, 0x07, 0x6b, 0x14, 0xdd, 0x05, 0x37, 0x58, 0xa3, 0xe8, 0xcc, 0xe3, 0x7a, 0x1e, 0xf2, 0x71, 0x59, 0xd2, 0x74, 0x8f, 0x64, 0xea, 0x05, 0x52, 0xa2, 0x17, 0xb2, 0x2d, 0x42, 0x31, 0x4a, 0x50, 0x8a, 0x32, 0x5c, 0x41, 0x39, 0xae, 0xc2, 0xf7, 0x7b, 0x6c, 0x95, 0xb8, 0x86, 0xeb, 0xb8, 0x01, 0x2f, 0x6e, 0xe2, 0x0f, 0xd2, 0x7d, 0x0d, 0x29, 0x32, 0x2c, 0xd0, 0x61, 0xc0, 0x2a, 0x91, 0x46, 0x10, 0xdb, 0xdb, 0x70, 0x3b, 0xee, 0xc0, 0x9d, 0xa8, 0xad, 0x3a, 0x1a, 0xf7, 0xab, 0x16, 0x46, 0x3d, 0xd5, 0xe9, 0x96, 0x57, 0x52, 0x1e, 0xa8, 0xfe, 0x0c, 0x88, 0x5e, 0xfd, 0x4a, 0x4a, 0xf7, 0xea, 0x57, 0x52, 0xd2, 0x8c, 0xd6, 0x6a, 0x9b, 0xd1, 0x46, 0xb5, 0x34, 0xda, 0xaa, 0xfb, 0x8d, 0x76, 0x6c, 0x9f, 0xe6, 0xba, 0xe7, 0xd4, 0x23, 0xc6, 0xf3, 0xea, 0x1e, 0xa3, 0xb3, 0x7a, 0xdc, 0xe8, 0xc6, 0xe5, 0xee, 0x78, 0x01, 0x3d, 0xf0, 0xa2, 0xa4, 0x1a, 0x2f, 0xb3, 0xed, 0x89, 0x57, 0xc0, 0x98, 0x69, 0xf4, 0x06, 0xe3, 0xa6, 0xd1, 0x17, 0x8c, 0x9d, 0x46, 0x7f, 0x0c, 0xe0, 0x76, 0x03, 0xd9, 0x0e, 0xe2, 0x3c, 0x19, 0x43, 0x8d, 0x21, 0x78, 0x15, 0x43, 0x31, 0x0c, 0xcc, 0x29, 0xc6, 0xeb, 0x18, 0x8e, 0x37, 0x30, 0x02, 0x23, 0x31, 0x0a, 0x6f, 0xe2, 0x2d, 0xd5, 0xde, 0x78, 0x9b, 0xed, 0x3b, 0x78, 0x17, 0xef, 0xe1, 0x7d, 0x8c, 0xc6, 0x07, 0xf8, 0x10, 0x63, 0xf0, 0x11, 0x3e, 0xc6, 0x58, 0xb0, 0xee, 0x35, 0x58, 0xf7, 0x1a, 0xac, 0x7b, 0x8d, 0xcf, 0xc0, 0x3a, 0xc8, 0xf8, 0x02, 0xe3, 0x31, 0x01, 0x13, 0x31, 0x09, 0x93, 0xc5, 0x66, 0x7c, 0xc9, 0xf6, 0x2b, 0xce, 0x8f, 0x75, 0x91, 0x31, 0x15, 0xd3, 0xf1, 0x0d, 0xaa, 0xfe, 0x85, 0x75, 0x9a, 0x31, 0x13, 0xb3, 0x30, 0x1b, 0x73, 0x30, 0x57, 0x8a, 0x8d, 0xef, 0xd8, 0xce, 0x53, 0x6d, 0x8d, 0xf9, 0x12, 0x67, 0x7c, 0x8f, 0x05, 0x58, 0x88, 0x45, 0x60, 0xed, 0x64, 0xfc, 0x80, 0xc5, 0xf8, 0x11, 0x4b, 0xf0, 0x13, 0x96, 0x91, 0xee, 0x97, 0x63, 0x05, 0x56, 0x62, 0x15, 0x56, 0x63, 0x8d, 0x1a, 0x60, 0xac, 0x65, 0xbb, 0x0e, 0xeb, 0xb1, 0x01, 0x1b, 0xb1, 0x09, 0x9b, 0xb1, 0x05, 0x21, 0x08, 0x45, 0x18, 0xb6, 0x82, 0xf5, 0x95, 0xc1, 0xfa, 0xca, 0xd8, 0x81, 0x9d, 0xd8, 0x85, 0x9f, 0xf1, 0x0b, 0x7e, 0xc5, 0x6e, 0xec, 0xc1, 0x5e, 0xfc, 0x86, 0x7d, 0xd8, 0x8f, 0x03, 0x38, 0x88, 0x43, 0x08, 0xc7, 0x61, 0xb0, 0xbe, 0x32, 0x58, 0x5f, 0x19, 0x47, 0x71, 0x0c, 0xc7, 0x71, 0x02, 0x91, 0x60, 0x9d, 0x65, 0x9c, 0x44, 0xb4, 0x64, 0x18, 0x31, 0x6c, 0x63, 0x11, 0x87, 0x78, 0xd8, 0x90, 0x80, 0x44, 0xd8, 0x91, 0x84, 0x64, 0xa4, 0x20, 0x15, 0x69, 0x92, 0x68, 0x38, 0xd8, 0xfa, 0xfe, 0xb5, 0x75, 0x06, 0xfb, 0x99, 0x60, 0xcd, 0x6f, 0x64, 0xd1, 0x86, 0xb3, 0x71, 0x1a, 0x67, 0x70, 0x16, 0x4e, 0xe4, 0xe0, 0x9c, 0x14, 0x1a, 0xbf, 0xe3, 0x3c, 0x2e, 0xe0, 0xa2, 0x64, 0x1b, 0x2e, 0xb8, 0x71, 0x09, 0xb9, 0xc8, 0x43, 0x3e, 0x2e, 0xc3, 0x83, 0x02, 0x14, 0xa2, 0x08, 0xc5, 0x28, 0x01, 0xeb, 0x66, 0x83, 0x7e, 0x65, 0xd0, 0xaf, 0x0c, 0xfa, 0x95, 0x41, 0xbf, 0x32, 0xe8, 0x57, 0x06, 0xfd, 0xca, 0xa0, 0x5f, 0x59, 0xef, 0x57, 0x4d, 0xac, 0xcc, 0xdf, 0xd6, 0xfa, 0x68, 0x80, 0x86, 0x78, 0x10, 0x8d, 0xd0, 0x18, 0x4d, 0xd0, 0x14, 0x0f, 0xa1, 0x19, 0x1e, 0x46, 0x73, 0xb4, 0x40, 0x4b, 0xb4, 0xc2, 0x23, 0x68, 0x8d, 0x36, 0x68, 0x2b, 0x51, 0xd6, 0x47, 0xd1, 0x0e, 0xed, 0xf1, 0x18, 0x3a, 0xe0, 0x71, 0x3c, 0x81, 0x27, 0xf1, 0x14, 0x9e, 0xc6, 0x33, 0xe8, 0x88, 0x4e, 0x78, 0x16, 0xcf, 0xe1, 0x79, 0x74, 0x46, 0x17, 0x74, 0x45, 0x37, 0x74, 0xc7, 0x0b, 0xe8, 0x81, 0x17, 0xf1, 0x12, 0x5e, 0x96, 0x58, 0x6b, 0x4f, 0xbc, 0x82, 0x5e, 0xe8, 0x8d, 0x3e, 0xe8, 0x8b, 0x7e, 0xe8, 0x8f, 0x01, 0x92, 0x64, 0x1d, 0x88, 0x41, 0x18, 0x8c, 0x21, 0x78, 0x15, 0x43, 0x31, 0x0c, 0xaf, 0xe1, 0x75, 0x0c, 0xc7, 0x1b, 0x18, 0x81, 0x91, 0x18, 0x85, 0x37, 0xf1, 0x16, 0xde, 0xc6, 0x3b, 0x78, 0x17, 0xef, 0xe1, 0x7d, 0x8c, 0xc6, 0x07, 0xf8, 0x10, 0x63, 0xf0, 0x91, 0x6a, 0x6e, 0xfd, 0x98, 0xed, 0x58, 0x92, 0xff, 0x27, 0x6c, 0xc7, 0xe1, 0x53, 0x7c, 0x86, 0xcf, 0xf1, 0x05, 0xc6, 0x63, 0x02, 0x26, 0x62, 0x12, 0x26, 0xe3, 0x4b, 0x7c, 0x85, 0x29, 0x98, 0x8a, 0x69, 0x98, 0x81, 0x6f, 0x31, 0x13, 0xb3, 0x30, 0x1b, 0x73, 0x30, 0x17, 0xdf, 0x61, 0x1e, 0xe6, 0xe3, 0x7b, 0x2c, 0xc0, 0x42, 0x2c, 0x42, 0x30, 0x7e, 0xc0, 0x62, 0xfc, 0x88, 0x25, 0xa8, 0xfa, 0x1c, 0x4a, 0x8c, 0x75, 0x39, 0x56, 0x60, 0x25, 0x56, 0x61, 0x35, 0xd6, 0x60, 0x2d, 0xd6, 0x61, 0x3d, 0x36, 0x60, 0x23, 0x36, 0x61, 0x33, 0xb6, 0x20, 0x04, 0xa1, 0x08, 0xc3, 0x56, 0x6c, 0xc3, 0x76, 0xec, 0xc0, 0x4e, 0xec, 0x42, 0xd5, 0x2b, 0x29, 0xe9, 0xd6, 0x43, 0xaa, 0x8e, 0x35, 0x1c, 0x87, 0x11, 0x81, 0xaa, 0x57, 0x52, 0xd2, 0xac, 0x91, 0x88, 0x92, 0x53, 0xd6, 0x93, 0x88, 0x96, 0x04, 0x6b, 0x2c, 0xc8, 0x27, 0xd6, 0x78, 0xd8, 0x90, 0x80, 0x44, 0xd8, 0x91, 0x84, 0x64, 0xa4, 0x48, 0xa2, 0x35, 0x95, 0x2d, 0xfd, 0xc6, 0x7a, 0x4a, 0xbc, 0xd6, 0x7c, 0x8e, 0x7f, 0x19, 0x1e, 0x14, 0xa0, 0x10, 0x64, 0x09, 0x6b, 0x39, 0xae, 0x82, 0xb5, 0xa1, 0xb5, 0x12, 0xd7, 0xc4, 0x6e, 0xbd, 0x0e, 0xd6, 0xbe, 0x56, 0x2f, 0x6e, 0x8a, 0x3d, 0x68, 0x91, 0x44, 0x06, 0x2d, 0x56, 0x3d, 0x83, 0x7e, 0x54, 0x9f, 0x07, 0xfd, 0xa4, 0x1e, 0x0c, 0x5a, 0x2a, 0x25, 0x41, 0xcb, 0x24, 0x2d, 0xe8, 0x20, 0xdb, 0x43, 0x92, 0x1e, 0x14, 0x2e, 0xb6, 0xa0, 0xc3, 0x92, 0x10, 0x14, 0xc1, 0xed, 0x98, 0x17, 0x83, 0x12, 0xc9, 0xa0, 0x33, 0x95, 0x59, 0x82, 0x55, 0x00, 0x02, 0x65, 0xb0, 0xaa, 0x2d, 0xe3, 0x55, 0x6b, 0xc9, 0x52, 0x6d, 0xd0, 0x16, 0xed, 0x24, 0x5e, 0xb5, 0xc7, 0x63, 0xe8, 0x80, 0xc7, 0xf1, 0x04, 0x9e, 0x24, 0x91, 0x3f, 0x85, 0xa7, 0xf1, 0x8c, 0xc4, 0xa9, 0x8e, 0x5c, 0xd7, 0x09, 0x6f, 0xf8, 0x5f, 0x9d, 0xb0, 0xab, 0x37, 0x49, 0xe6, 0x9f, 0x70, 0x79, 0x1c, 0x3e, 0x05, 0xa3, 0xb0, 0x9a, 0x20, 0x67, 0xd4, 0x44, 0x4c, 0xc2, 0x64, 0xc4, 0xca, 0x4f, 0x2a, 0x9e, 0xc7, 0x4d, 0xe1, 0xfe, 0xe9, 0x72, 0x4e, 0x65, 0xc8, 0x69, 0x95, 0x29, 0xe5, 0x2a, 0x5b, 0x9c, 0xea, 0x8c, 0xff, 0xd7, 0x4f, 0x13, 0xd4, 0x05, 0xee, 0x7b, 0x11, 0x97, 0x90, 0x2b, 0x11, 0xbe, 0x5f, 0xa1, 0x54, 0x65, 0xf2, 0x9d, 0xaa, 0x94, 0x85, 0xea, 0x9a, 0x2c, 0x24, 0xed, 0x5f, 0x31, 0xd5, 0x91, 0x9d, 0x24, 0xfc, 0x2b, 0x24, 0xfb, 0x4d, 0x24, 0xfb, 0x4d, 0xa4, 0x79, 0xdf, 0x67, 0x23, 0xe2, 0x49, 0xe9, 0x57, 0x48, 0xe9, 0xbe, 0xef, 0xba, 0x4f, 0x37, 0x31, 0xbb, 0x91, 0xb2, 0xe3, 0xb5, 0x71, 0xea, 0x1e, 0xd2, 0x71, 0x3c, 0xe9, 0x38, 0x92, 0x04, 0x7c, 0x5e, 0x9b, 0x4d, 0xc2, 0x9b, 0xcb, 0x7e, 0xb0, 0x9c, 0x21, 0xf9, 0x46, 0x92, 0x4a, 0xcf, 0x93, 0x70, 0xe2, 0x49, 0x38, 0x4e, 0x33, 0x2b, 0x15, 0x33, 0x2b, 0x15, 0x92, 0x4e, 0x3c, 0x49, 0x27, 0x9e, 0xa4, 0x13, 0x6f, 0x4e, 0x94, 0x85, 0xa4, 0x9a, 0xe5, 0xa4, 0x9a, 0xe5, 0xe6, 0xb3, 0xec, 0x17, 0x71, 0x5d, 0xa5, 0xe4, 0x90, 0x44, 0x72, 0x48, 0x21, 0x9b, 0x48, 0x1d, 0xe7, 0x49, 0x1c, 0xf1, 0x24, 0x0c, 0xdf, 0x77, 0xe8, 0x6f, 0x22, 0x45, 0xc4, 0x93, 0x22, 0xe2, 0x49, 0x11, 0xf1, 0xa4, 0x08, 0x3b, 0xe9, 0xc0, 0x42, 0x02, 0x78, 0x99, 0x04, 0x10, 0x4f, 0x02, 0x88, 0x64, 0xe5, 0x7f, 0x85, 0x95, 0x7f, 0x3c, 0x2b, 0xfe, 0x78, 0x56, 0xf1, 0xf1, 0xac, 0xe2, 0x83, 0x59, 0xc5, 0x07, 0xb3, 0x8a, 0x0f, 0x66, 0x15, 0x1f, 0xcc, 0x2a, 0x3e, 0x98, 0x55, 0x7c, 0x30, 0xab, 0xf8, 0x60, 0x56, 0xf1, 0xc1, 0xac, 0xe2, 0x83, 0x59, 0xc5, 0x07, 0xb3, 0x8a, 0xdf, 0xc1, 0x2a, 0xfe, 0x47, 0x56, 0xf1, 0xc1, 0xac, 0xe2, 0x83, 0x59, 0xc5, 0x07, 0xb3, 0x8a, 0x0f, 0x66, 0x15, 0x1f, 0xcc, 0x2a, 0x3e, 0x98, 0x55, 0x7c, 0x30, 0xab, 0xf8, 0xff, 0x41, 0xdd, 0x9d, 0xc0, 0xc7, 0x59, 0xd6, 0x7b, 0xff, 0xbf, 0x27, 0x4d, 0xda, 0x2c, 0xb2, 0xc9, 0x26, 0x1c, 0x76, 0x04, 0xb1, 0x22, 0x54, 0x05, 0x0a, 0x88, 0xec, 0x16, 0xd9, 0x0a, 0x88, 0xa0, 0xe0, 0x02, 0x08, 0x28, 0x22, 0xb2, 0x56, 0x40, 0x04, 0x05, 0xb1, 0x2c, 0x0a, 0x6d, 0x01, 0x15, 0xaa, 0x82, 0x58, 0x2d, 0x8a, 0x50, 0x05, 0x8f, 0xc8, 0x62, 0x2b, 0x95, 0x96, 0x92, 0x06, 0xda, 0x49, 0x53, 0x92, 0x12, 0x0a, 0x6d, 0xda, 0x69, 0x66, 0xa6, 0x99, 0xb9, 0x67, 0x32, 0xcd, 0x42, 0x33, 0x99, 0x70, 0xff, 0xdf, 0x33, 0x4c, 0x7b, 0x2a, 0x0f, 0x9c, 0xa3, 0xcf, 0x73, 0xb6, 0xff, 0x8b, 0xd7, 0x97, 0x99, 0x24, 0x33, 0x73, 0x2f, 0xd7, 0xef, 0xfa, 0x7d, 0x3e, 0xd7, 0xdd, 0x64, 0x66, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x69, 0x2c, 0x7e, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x69, 0x2c, 0x7e, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x69, 0x2c, 0xfe, 0x65, 0x16, 0xff, 0x32, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x97, 0x59, 0xfc, 0xcb, 0x2c, 0x7e, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x69, 0x2c, 0x7e, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x97, 0x59, 0xfc, 0xcb, 0x2c, 0x7e, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0x5f, 0x66, 0xf1, 0x2f, 0xb3, 0xf8, 0x69, 0x2c, 0xfe, 0x1e, 0x16, 0x7f, 0x0f, 0x8b, 0x9f, 0xc6, 0xe2, 0xcf, 0x60, 0xf1, 0xe5, 0xcf, 0x45, 0xf9, 0xec, 0x98, 0xc9, 0x41, 0x23, 0x23, 0xff, 0x16, 0x03, 0xbf, 0x83, 0x6d, 0x7f, 0xb3, 0xfc, 0x3e, 0x8d, 0x6c, 0xbb, 0xfc, 0x5e, 0x8d, 0xe7, 0xb0, 0xd1, 0xf5, 0x4c, 0xb3, 0x93, 0x69, 0x76, 0x32, 0xcd, 0x4e, 0xa6, 0xd9, 0xc9, 0x34, 0x3b, 0x99, 0x66, 0x27, 0xd3, 0xec, 0x64, 0x9a, 0x9d, 0x4c, 0xb3, 0x93, 0x69, 0x76, 0x32, 0xcd, 0x4e, 0xa6, 0xd9, 0xc9, 0x34, 0x3b, 0xeb, 0x67, 0xca, 0x43, 0xf2, 0x1b, 0xf9, 0xad, 0x3c, 0x2c, 0xbf, 0x93, 0x47, 0xe4, 0x51, 0x99, 0x25, 0xbf, 0x97, 0x3f, 0xc8, 0x63, 0xf2, 0xb8, 0xfc, 0xb1, 0xf2, 0x9b, 0xc6, 0x9d, 0x2c, 0xb3, 0x93, 0x65, 0x76, 0xb2, 0xcc, 0x4e, 0x96, 0xd9, 0xc9, 0x32, 0x3b, 0x59, 0x66, 0x27, 0xcb, 0xec, 0x64, 0x99, 0x2d, 0x2c, 0xb3, 0x85, 0x65, 0xb6, 0xb0, 0xcc, 0x16, 0x96, 0xd9, 0xc2, 0x32, 0x5b, 0x58, 0x66, 0x0b, 0xcb, 0x9c, 0xc7, 0x32, 0xe7, 0xb1, 0xcc, 0x79, 0x2c, 0xb3, 0x6c, 0x98, 0x2d, 0x0c, 0x73, 0x1e, 0xc3, 0x6c, 0x61, 0x98, 0x2d, 0x0c, 0xb3, 0x85, 0x61, 0xb6, 0x30, 0xcc, 0x16, 0x86, 0xd9, 0xc2, 0x30, 0x5b, 0x18, 0x66, 0x0b, 0xc3, 0x6c, 0x61, 0x98, 0x2d, 0x0c, 0xb3, 0x85, 0x61, 0xb6, 0x30, 0xcc, 0x16, 0x86, 0xd9, 0x52, 0xbf, 0x42, 0x56, 0x4a, 0x97, 0xa8, 0x0f, 0x86, 0xd9, 0xc2, 0x30, 0x5b, 0x18, 0x66, 0x0b, 0xc3, 0x6c, 0x61, 0x98, 0x2d, 0x0c, 0xb3, 0x85, 0x61, 0xb6, 0x30, 0xcc, 0x16, 0x86, 0xd9, 0x52, 0x1f, 0x21, 0x5b, 0x20, 0x31, 0xa9, 0x91, 0x51, 0x52, 0x2b, 0x75, 0x15, 0x63, 0x5c, 0xcc, 0x18, 0x17, 0x33, 0xc6, 0xc5, 0x8c, 0x71, 0x31, 0x63, 0x6c, 0x61, 0x8a, 0x8b, 0x99, 0xe2, 0x62, 0xa6, 0xb8, 0xb8, 0x61, 0xbb, 0x60, 0xeb, 0x86, 0x5d, 0x82, 0x2d, 0x58, 0xe2, 0x1e, 0x2c, 0x71, 0x2c, 0x4b, 0xdc, 0x8e, 0x25, 0x6e, 0xce, 0x12, 0xff, 0x85, 0x25, 0x8e, 0x65, 0x88, 0x87, 0x35, 0xec, 0xcb, 0x02, 0x3f, 0x1c, 0x7c, 0x92, 0x19, 0x6e, 0xc3, 0xe4, 0x5a, 0x98, 0x5c, 0x0b, 0x93, 0x6b, 0x61, 0x72, 0x2d, 0x4c, 0xae, 0x85, 0xc9, 0xb5, 0x30, 0xb9, 0x16, 0x26, 0xd7, 0xc2, 0xe4, 0x5a, 0x98, 0x5c, 0x0b, 0x93, 0x6b, 0x61, 0x72, 0x2d, 0x4c, 0xae, 0x85, 0xc9, 0xb5, 0x34, 0x7c, 0x59, 0xce, 0x97, 0x0b, 0xe4, 0x42, 0xf9, 0x8a, 0x7c, 0x55, 0x2e, 0x92, 0xaf, 0xc9, 0xc5, 0xf2, 0x75, 0xb9, 0x44, 0xbe, 0x21, 0x97, 0x8a, 0xde, 0xc1, 0xe4, 0x5a, 0x98, 0x5c, 0x0b, 0x93, 0x6b, 0x61, 0x72, 0x2d, 0x4c, 0xae, 0x85, 0xc9, 0xb5, 0x30, 0xb9, 0x16, 0x26, 0xd7, 0xc2, 0xe4, 0x5a, 0x98, 0x5c, 0x0b, 0x93, 0x6b, 0x61, 0x64, 0xaf, 0x31, 0xb2, 0xd7, 0x18, 0xd9, 0x6b, 0x8c, 0xec, 0xb5, 0xf2, 0x7b, 0x76, 0x32, 0xb2, 0xd7, 0x18, 0xd9, 0x6b, 0x8c, 0xec, 0x35, 0x46, 0xf6, 0x1a, 0x23, 0x2b, 0xbf, 0x8f, 0xe7, 0x71, 0xa8, 0xbd, 0x10, 0xb5, 0x17, 0xa2, 0xf6, 0x42, 0xd4, 0x5e, 0x88, 0xda, 0x0b, 0x51, 0x7b, 0x21, 0x6a, 0x2f, 0x44, 0xed, 0x85, 0xa8, 0xbd, 0x10, 0xb5, 0x17, 0xa2, 0xf6, 0x42, 0xd4, 0x5e, 0x88, 0xda, 0x0b, 0x51, 0x7b, 0x21, 0x6a, 0x2f, 0x44, 0xed, 0x85, 0xa8, 0xbd, 0x10, 0xb5, 0x17, 0xa2, 0xf6, 0x42, 0xd4, 0x5e, 0x88, 0xda, 0x0b, 0x51, 0x7b, 0x21, 0x6a, 0x2f, 0x44, 0xed, 0x85, 0xa8, 0xbd, 0x10, 0xb5, 0x17, 0xa2, 0xf6, 0x72, 0xd4, 0x5e, 0x8e, 0xda, 0xcb, 0x51, 0x7b, 0x39, 0x6a, 0x2f, 0x47, 0xed, 0xe5, 0xa8, 0xbd, 0x1c, 0xb5, 0x97, 0xa3, 0xf6, 0x72, 0x34, 0x3d, 0xa1, 0xf1, 0x46, 0x94, 0xb8, 0xa9, 0xf2, 0xbe, 0x24, 0xff, 0xb5, 0x9f, 0xe6, 0xda, 0x1c, 0xf5, 0xa3, 0x4c, 0x3f, 0x9a, 0xb4, 0xa0, 0x49, 0x0b, 0x9a, 0xb4, 0xa0, 0x49, 0x0b, 0x9a, 0x94, 0x3f, 0x75, 0x72, 0x35, 0x2a, 0xb4, 0xa0, 0xc2, 0xbc, 0xe0, 0xb8, 0xca, 0xfb, 0x9b, 0x36, 0x47, 0x77, 0xeb, 0xce, 0x53, 0x74, 0xe6, 0x94, 0x8e, 0x9c, 0xd7, 0x7d, 0x17, 0xe8, 0xbc, 0xb7, 0xea, 0xb6, 0x7f, 0xd6, 0x51, 0x53, 0xb1, 0xf1, 0xd1, 0x47, 0x63, 0x13, 0xa2, 0x07, 0x62, 0xe7, 0x44, 0xaf, 0xc5, 0xae, 0x8e, 0xd6, 0xc5, 0xae, 0x89, 0xae, 0x8d, 0x5d, 0x1b, 0x65, 0x63, 0xdf, 0x8a, 0x16, 0xc5, 0x7e, 0x18, 0xcd, 0xd1, 0x41, 0xcb, 0xd7, 0x4c, 0x96, 0xea, 0x78, 0x07, 0xe8, 0x78, 0x87, 0xea, 0x5a, 0x53, 0x74, 0xad, 0x1f, 0xeb, 0x34, 0x53, 0x74, 0x9a, 0x65, 0x3a, 0xcd, 0x32, 0x9d, 0x66, 0x8a, 0x4e, 0x33, 0x45, 0xa7, 0x59, 0xa6, 0xd3, 0x2c, 0xd3, 0x69, 0xa6, 0xe8, 0x34, 0x53, 0x74, 0x9a, 0x29, 0x3a, 0xcd, 0x14, 0x9d, 0x66, 0x8a, 0x4e, 0x33, 0x45, 0xa7, 0x99, 0xa2, 0xd3, 0x2c, 0xd3, 0x69, 0x96, 0xe9, 0x34, 0x53, 0x74, 0x9a, 0x29, 0x3a, 0xcd, 0x32, 0x9d, 0x66, 0x99, 0x4e, 0x33, 0x45, 0x57, 0x39, 0xb3, 0xf2, 0x37, 0x19, 0xad, 0xc1, 0x55, 0xba, 0xc9, 0x24, 0x9d, 0x64, 0x82, 0x4e, 0xd2, 0x6e, 0xfd, 0xb9, 0x45, 0xe5, 0x9d, 0xb7, 0x56, 0x44, 0xeb, 0xac, 0x87, 0xf6, 0x52, 0xed, 0x1f, 0x51, 0xed, 0x3b, 0x55, 0x7f, 0x2f, 0xbe, 0xfc, 0x8e, 0x52, 0xef, 0x55, 0xed, 0xef, 0xa9, 0xfe, 0x5e, 0xfc, 0xfe, 0x2a, 0x7e, 0x3f, 0x15, 0xff, 0x5e, 0x15, 0x7f, 0x83, 0x8a, 0x3f, 0x50, 0xe5, 0x66, 0xf8, 0xfe, 0xbe, 0xaa, 0x29, 0x54, 0x39, 0xfb, 0x19, 0xad, 0x4f, 0xfc, 0x97, 0x9e, 0x9d, 0x29, 0xff, 0xbf, 0x3f, 0x3b, 0x57, 0x04, 0xa3, 0x82, 0x03, 0x82, 0x5a, 0xa9, 0x0b, 0xc6, 0x07, 0x5b, 0x07, 0xdb, 0x06, 0xef, 0x8f, 0xee, 0x0f, 0xf6, 0x8a, 0x1e, 0x0d, 0xf6, 0x89, 0x1e, 0x09, 0x3e, 0x18, 0x3d, 0x17, 0x8c, 0x0d, 0xf6, 0x0d, 0x3e, 0xe4, 0xec, 0xed, 0xeb, 0xa7, 0xb7, 0x39, 0x73, 0x3f, 0x08, 0xf6, 0x2c, 0xff, 0xfd, 0x5f, 0xec, 0x13, 0xd1, 0xca, 0xd8, 0x11, 0xa2, 0x83, 0xc4, 0x74, 0x90, 0x9a, 0xa3, 0x2a, 0xef, 0x70, 0x52, 0x7e, 0x57, 0x93, 0xee, 0x9a, 0xb3, 0xa3, 0xbb, 0x6b, 0x3e, 0x1f, 0x6c, 0x5d, 0x73, 0x81, 0x5c, 0x18, 0xf5, 0xd4, 0x5c, 0xe4, 0xeb, 0x4b, 0xdc, 0xbf, 0x25, 0x18, 0x57, 0x73, 0x6b, 0x14, 0x96, 0xff, 0xce, 0xad, 0xee, 0x80, 0xe8, 0x6f, 0x75, 0x07, 0xca, 0xfc, 0x28, 0x1c, 0xf3, 0xa1, 0xe0, 0x80, 0x31, 0xfb, 0xca, 0x87, 0x65, 0x3f, 0xd9, 0x5f, 0xc6, 0xc9, 0x47, 0xe4, 0xa3, 0xf2, 0x31, 0x19, 0x2f, 0x07, 0xcb, 0x21, 0x72, 0xa8, 0x7c, 0x5c, 0x0e, 0x93, 0x4f, 0xc8, 0xe1, 0x72, 0x84, 0x1c, 0x29, 0x47, 0xc9, 0xd1, 0x72, 0x8c, 0x1c, 0x2b, 0x9f, 0x94, 0x09, 0x72, 0x9c, 0x7c, 0x4a, 0xce, 0x93, 0x2f, 0x07, 0xe3, 0xc7, 0x9c, 0x2f, 0x3d, 0xc1, 0xb6, 0x63, 0xb2, 0xc1, 0xb6, 0xf5, 0xa7, 0x46, 0xf7, 0xd7, 0x9f, 0x26, 0x9f, 0x91, 0x33, 0xe5, 0xb3, 0xf2, 0x39, 0x39, 0x4b, 0x2e, 0x8f, 0x1e, 0xa9, 0xbf, 0x42, 0xae, 0x8c, 0x9e, 0xab, 0xbf, 0x4a, 0x26, 0xc9, 0x37, 0xe5, 0x6a, 0xb9, 0x46, 0xae, 0x0b, 0xf6, 0xad, 0xff, 0xb6, 0x5c, 0x1f, 0xdd, 0x5d, 0x7f, 0x83, 0x7c, 0x47, 0xbe, 0x2b, 0x37, 0xcb, 0xf7, 0x65, 0xb2, 0xdc, 0x22, 0xb7, 0xca, 0x5d, 0xc1, 0xd6, 0xf5, 0x77, 0xcb, 0x3d, 0xf2, 0x63, 0xf9, 0x89, 0xdc, 0xab, 0xab, 0xdf, 0x1f, 0xec, 0x66, 0xed, 0x95, 0xb7, 0xf6, 0xca, 0x37, 0xac, 0x0a, 0xf6, 0x6c, 0x58, 0x2d, 0x6f, 0xff, 0x9d, 0xfb, 0x77, 0xf9, 0x5d, 0xfb, 0xa6, 0x63, 0xa3, 0x19, 0xc1, 0xfb, 0x8c, 0xda, 0x3c, 0xa3, 0x36, 0xcf, 0xa8, 0xf5, 0x18, 0xad, 0xd9, 0x46, 0x60, 0xb6, 0xb3, 0x3e, 0xdb, 0x91, 0xcd, 0x73, 0x64, 0xf3, 0x1c, 0xd9, 0x3c, 0x47, 0x36, 0xcf, 0x91, 0xcd, 0x73, 0x64, 0xf3, 0x1c, 0xd9, 0x3c, 0x47, 0xd6, 0xe3, 0xc8, 0x7a, 0xec, 0xf9, 0x6c, 0x7b, 0x3e, 0xdb, 0x9e, 0xcf, 0xb6, 0xe7, 0xb3, 0xed, 0xf9, 0x6c, 0x7b, 0x3e, 0xdb, 0x9e, 0xcf, 0xb6, 0xe7, 0xb3, 0xed, 0xf9, 0xec, 0xe0, 0x76, 0xd6, 0x39, 0x93, 0x75, 0xce, 0x64, 0x9d, 0x97, 0xb3, 0xce, 0x6b, 0x98, 0x66, 0x2f, 0xd3, 0xec, 0x65, 0x9a, 0xbd, 0x4c, 0xb3, 0x97, 0x69, 0xf6, 0x32, 0xcd, 0x5e, 0xa6, 0xb9, 0x8a, 0x69, 0xae, 0x62, 0x9a, 0xab, 0xcc, 0xbc, 0x15, 0x4c, 0xb3, 0x97, 0x69, 0xf6, 0xb2, 0xcb, 0x5e, 0x76, 0xd9, 0xcb, 0x2e, 0x7b, 0xd9, 0xe5, 0xaa, 0x58, 0xf9, 0xfa, 0xfc, 0x55, 0xe2, 0x3e, 0xa3, 0x5b, 0xc5, 0xe6, 0x56, 0x99, 0x5d, 0xbb, 0x31, 0xad, 0x5e, 0xa6, 0xb5, 0x8a, 0x5d, 0xf5, 0xb2, 0xab, 0x99, 0xec, 0x6a, 0x26, 0xbb, 0x9a, 0xc9, 0xae, 0x66, 0xb2, 0xab, 0x99, 0xec, 0x6a, 0x26, 0xbb, 0x9a, 0xc9, 0xae, 0x66, 0xb2, 0xab, 0x99, 0x8c, 0x6a, 0x26, 0xa3, 0x9a, 0xc9, 0xa8, 0x66, 0x32, 0xaa, 0x99, 0x8c, 0x6a, 0x26, 0xa3, 0x9a, 0xc9, 0xa8, 0x66, 0x32, 0xaa, 0x99, 0x8c, 0x6a, 0x26, 0xa3, 0x9a, 0xc9, 0xa8, 0x66, 0x32, 0xaa, 0x99, 0x8c, 0x6a, 0x26, 0xa3, 0x9a, 0xc9, 0xa8, 0x66, 0x32, 0xaa, 0x99, 0x8c, 0x6a, 0x26, 0xa3, 0x9a, 0xc9, 0x7a, 0x66, 0xb2, 0x9e, 0xcb, 0x59, 0xcf, 0xe5, 0x2c, 0xe7, 0x1a, 0x86, 0x73, 0x0d, 0x9b, 0xe8, 0x65, 0x13, 0xbd, 0x6c, 0xa2, 0x97, 0x4d, 0xf4, 0xb2, 0x88, 0x5e, 0x16, 0xb1, 0x8a, 0x45, 0xac, 0x62, 0x11, 0xab, 0x58, 0xc4, 0x2a, 0x06, 0xb1, 0x8a, 0x41, 0xf4, 0x32, 0x88, 0x5e, 0x06, 0xd1, 0xcb, 0x20, 0x7a, 0x19, 0x44, 0x2f, 0x83, 0xe8, 0x65, 0x10, 0xbd, 0x0c, 0xa2, 0x97, 0x39, 0xf4, 0x32, 0x87, 0x5e, 0xe6, 0xd0, 0xcb, 0x1c, 0x7a, 0xcd, 0xe0, 0x5e, 0xe6, 0xd0, 0xcb, 0x1c, 0x7a, 0x99, 0x43, 0x2f, 0x73, 0xe8, 0x65, 0x0e, 0xbd, 0xcc, 0xa1, 0x97, 0x39, 0xf4, 0x32, 0x87, 0x5e, 0xe6, 0xd0, 0xcb, 0x1a, 0x7a, 0x59, 0x43, 0xaf, 0xd9, 0xde, 0x68, 0xb6, 0xef, 0x6c, 0xb6, 0x8f, 0x31, 0xdb, 0xc7, 0x98, 0xdd, 0xff, 0x62, 0x76, 0xef, 0x60, 0x66, 0x5f, 0x64, 0x66, 0xef, 0x81, 0xf4, 0xbd, 0x48, 0xdf, 0x8b, 0xf4, 0xbd, 0x48, 0xdf, 0x8b, 0xf4, 0xbd, 0x48, 0xdf, 0x8b, 0xf4, 0xbd, 0x48, 0xdf, 0x8b, 0xee, 0xbd, 0xe8, 0xde, 0x8b, 0xee, 0xbd, 0xba, 0x40, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0x72, 0xf7, 0x22, 0x77, 0x2f, 0x72, 0xf7, 0xea, 0x20, 0x5b, 0x21, 0x52, 0x2f, 0x22, 0xf5, 0x22, 0x52, 0x2f, 0x22, 0xf5, 0x22, 0x52, 0x2f, 0x12, 0xad, 0x0a, 0xea, 0xaa, 0xbd, 0xf3, 0x3b, 0xfa, 0x66, 0x9b, 0xbe, 0x39, 0x4f, 0xcf, 0x4c, 0xfb, 0xee, 0xf1, 0xd1, 0x33, 0xd5, 0x4f, 0x1a, 0x7f, 0x2e, 0xc6, 0x48, 0x62, 0xd7, 0x45, 0x7d, 0xc1, 0x28, 0xdf, 0x2d, 0x7f, 0x1e, 0xd8, 0x2b, 0xbe, 0x93, 0xab, 0x7c, 0xf5, 0x57, 0x5f, 0xf5, 0xf8, 0x2a, 0x53, 0xf9, 0x6a, 0xc3, 0xe7, 0x5d, 0xe6, 0x83, 0x2d, 0x7c, 0xf5, 0x9b, 0xd8, 0x09, 0x51, 0x3e, 0x76, 0x92, 0x9c, 0xe2, 0xbb, 0xa7, 0x55, 0x3e, 0xc1, 0xae, 0xcd, 0x4f, 0xdb, 0xf5, 0x9f, 0x36, 0xfd, 0xa7, 0x2d, 0xf6, 0x15, 0x5d, 0x7c, 0x92, 0x8e, 0x6d, 0x5f, 0xab, 0x9f, 0x25, 0x55, 0x68, 0x7c, 0xdd, 0xb3, 0x6b, 0x3d, 0x7b, 0x6d, 0xf5, 0x53, 0xf9, 0x0a, 0x1e, 0x91, 0x0b, 0x46, 0xfb, 0x4e, 0x7e, 0xe3, 0xa7, 0x69, 0x7e, 0xd1, 0xe3, 0xbf, 0x24, 0x5f, 0xf1, 0xd8, 0x06, 0x1c, 0xf8, 0x6d, 0xec, 0xd0, 0xe8, 0xd1, 0xd8, 0xc7, 0x65, 0x42, 0xf4, 0x84, 0x2d, 0xf5, 0x55, 0x3f, 0x2b, 0xaf, 0xfc, 0xaf, 0x84, 0xcf, 0x38, 0xae, 0xbe, 0xa0, 0xa6, 0xfa, 0x79, 0x6e, 0xcb, 0x2b, 0x8f, 0x7f, 0xdd, 0xe3, 0xbb, 0x3d, 0xbe, 0xdb, 0xe3, 0xcb, 0x9f, 0xf6, 0xf6, 0x62, 0xf5, 0x48, 0xe7, 0x7b, 0xc4, 0x8b, 0x9e, 0x93, 0x0f, 0xea, 0x3d, 0x2a, 0xf4, 0xa8, 0x5e, 0x8f, 0xea, 0xf5, 0xa8, 0xb5, 0x1e, 0xd1, 0xeb, 0x11, 0x4b, 0xca, 0x9f, 0x09, 0x17, 0xbb, 0xd3, 0x2b, 0xc6, 0x9c, 0xab, 0x7c, 0xe5, 0x75, 0x2b, 0x9f, 0x01, 0x19, 0x34, 0x55, 0x3f, 0xe3, 0x6b, 0xc3, 0x5e, 0xf6, 0xd9, 0xcb, 0xac, 0xbd, 0xcc, 0xda, 0xcb, 0x6c, 0x4c, 0x17, 0x8e, 0xdd, 0xee, 0x58, 0xee, 0xf4, 0xb3, 0x29, 0x95, 0x67, 0xdf, 0xe6, 0xff, 0x8d, 0xb6, 0x72, 0x89, 0xad, 0x5c, 0x6a, 0x2b, 0x97, 0xda, 0xca, 0x0d, 0xce, 0xd8, 0x02, 0x67, 0x6c, 0x81, 0x33, 0xd6, 0xec, 0xf5, 0xff, 0x50, 0x5b, 0x7e, 0xff, 0xb4, 0xd7, 0x71, 0xaf, 0xbc, 0x3f, 0x3f, 0xf4, 0xc8, 0xa9, 0x1e, 0x59, 0xfe, 0x34, 0xbd, 0xdf, 0x6c, 0x18, 0x05, 0x8f, 0x2a, 0xff, 0xed, 0x67, 0x5f, 0x30, 0x3a, 0xa8, 0x71, 0x66, 0xc6, 0x47, 0xf7, 0xc5, 0x0e, 0x8e, 0xd2, 0xb1, 0x43, 0x64, 0x42, 0xf4, 0x3b, 0x6c, 0x4c, 0x62, 0x40, 0x8d, 0x33, 0x73, 0x50, 0xe5, 0xc8, 0x2f, 0xf4, 0xd3, 0x47, 0xfc, 0xf4, 0x21, 0x3f, 0xbd, 0x2a, 0x76, 0x7c, 0xb0, 0x4d, 0xec, 0xb4, 0x60, 0xeb, 0xd8, 0xe9, 0xc1, 0xf6, 0xb1, 0x33, 0xdc, 0x3f, 0xd3, 0x1e, 0x9d, 0x13, 0xcd, 0x89, 0x9d, 0x17, 0xbd, 0x10, 0x3b, 0x5f, 0x2e, 0x08, 0xc6, 0xd5, 0xde, 0x15, 0x6c, 0x5e, 0x7b, 0x8f, 0xfc, 0x38, 0x78, 0x4f, 0xe3, 0x8a, 0x68, 0x6e, 0xd0, 0xe0, 0xd5, 0x5e, 0x08, 0xee, 0x0c, 0x36, 0x0f, 0xa6, 0x78, 0xc6, 0x78, 0xa3, 0x39, 0xa1, 0x32, 0xee, 0xe5, 0xb3, 0x50, 0xa8, 0x3b, 0x20, 0x68, 0xac, 0x3b, 0x30, 0x68, 0xf4, 0x5f, 0x4d, 0x34, 0xdf, 0xa3, 0xde, 0xeb, 0x51, 0xef, 0xf3, 0xa8, 0x36, 0x8f, 0xfa, 0x6b, 0x75, 0x44, 0xd7, 0xd9, 0x4e, 0xbd, 0x47, 0x6e, 0xe6, 0x91, 0x9b, 0xc5, 0x86, 0x75, 0xa9, 0x01, 0x5d, 0x6a, 0xa0, 0xf2, 0x5b, 0x05, 0xef, 0x8f, 0x0a, 0x7a, 0x62, 0x41, 0x4f, 0xcc, 0x21, 0xd9, 0xba, 0x60, 0x6c, 0xb4, 0x56, 0x6f, 0x6c, 0xb7, 0x5e, 0xce, 0x58, 0x2f, 0x67, 0xac, 0x97, 0x33, 0xc1, 0x38, 0xdd, 0xe8, 0x23, 0x6e, 0x3f, 0x2a, 0xe5, 0xcf, 0xed, 0x3b, 0x40, 0x0e, 0x94, 0x83, 0x64, 0xbc, 0x1c, 0x2c, 0x87, 0xc8, 0xc7, 0xe5, 0x30, 0xf9, 0x84, 0xc7, 0x1f, 0x2e, 0x47, 0xc8, 0x91, 0x72, 0x94, 0x1c, 0x2d, 0xc7, 0xc8, 0xb1, 0xf2, 0x49, 0x99, 0x20, 0xc7, 0xc9, 0xa7, 0xe4, 0x78, 0x39, 0x41, 0x4e, 0x94, 0x93, 0xe4, 0x64, 0x99, 0x28, 0xa7, 0xc8, 0xa9, 0xa2, 0x12, 0x82, 0x4f, 0xcb, 0xe9, 0x62, 0x16, 0x07, 0x66, 0x71, 0x60, 0x16, 0x07, 0x66, 0x71, 0x60, 0x16, 0x07, 0x66, 0x71, 0x60, 0x16, 0x07, 0x66, 0x71, 0xf0, 0x05, 0xf9, 0x62, 0x94, 0xb0, 0x5e, 0xcf, 0x04, 0x66, 0x74, 0x60, 0x46, 0x07, 0x66, 0x74, 0x60, 0x46, 0x07, 0x66, 0x74, 0x60, 0x46, 0x07, 0x66, 0x73, 0x60, 0x36, 0x07, 0x66, 0x73, 0xf0, 0x35, 0x35, 0x65, 0x36, 0x07, 0x66, 0x73, 0x70, 0x89, 0xfb, 0xdf, 0x90, 0x4b, 0xe5, 0x32, 0xcf, 0xbf, 0x5c, 0xae, 0x90, 0x2b, 0x7d, 0x7d, 0x55, 0xa5, 0x23, 0x67, 0x82, 0x6f, 0xba, 0x7f, 0x35, 0x3f, 0xba, 0x46, 0xae, 0x95, 0x6f, 0x89, 0x19, 0x1f, 0x7c, 0x5b, 0xae, 0x77, 0xfe, 0x6e, 0x90, 0xef, 0xc8, 0x77, 0xe5, 0x46, 0xb9, 0x49, 0xbe, 0x27, 0x37, 0xcb, 0xf7, 0x65, 0xb2, 0xdc, 0xe2, 0x35, 0x6e, 0x95, 0xdb, 0xa2, 0xe1, 0xe0, 0x07, 0xce, 0xf7, 0x0f, 0xdd, 0xde, 0x51, 0xf9, 0x34, 0xea, 0xf2, 0x67, 0xf0, 0xbd, 0x5a, 0x99, 0x83, 0x69, 0x5f, 0xaf, 0x8d, 0xf2, 0x18, 0xd5, 0x8e, 0x51, 0xed, 0x35, 0xa8, 0x50, 0x73, 0x45, 0x85, 0x06, 0x19, 0x34, 0xc8, 0xd4, 0x5c, 0xeb, 0x67, 0xba, 0x4d, 0xcd, 0xf5, 0x6e, 0x6d, 0xaf, 0xc6, 0xf6, 0x6a, 0x6e, 0xf2, 0xfd, 0x5b, 0xa2, 0x04, 0x52, 0x64, 0x6a, 0x7e, 0x20, 0xd3, 0xa2, 0x14, 0x62, 0x64, 0x98, 0xc4, 0x70, 0xcd, 0x74, 0x8f, 0xfd, 0xa9, 0xfc, 0xcc, 0xd7, 0x0f, 0xba, 0xfd, 0xa5, 0xcc, 0x90, 0x5f, 0xc9, 0xaf, 0x65, 0xa6, 0x3c, 0x24, 0xbf, 0x91, 0xdf, 0xca, 0xc3, 0xf2, 0x3b, 0xaf, 0xf9, 0x88, 0x6a, 0x77, 0x2e, 0x6b, 0x27, 0x45, 0xbd, 0xb5, 0x77, 0x45, 0x7d, 0xb5, 0xf7, 0x44, 0xcb, 0x6a, 0x7f, 0x1c, 0xad, 0xa8, 0xdb, 0x06, 0x81, 0x9e, 0x15, 0xdd, 0xbf, 0x4e, 0xd7, 0xaf, 0x5b, 0x24, 0x8b, 0x45, 0x97, 0xaf, 0xd3, 0xe5, 0xeb, 0x74, 0x79, 0x94, 0xca, 0xa0, 0x54, 0xa6, 0x2e, 0x1b, 0xe5, 0x47, 0xef, 0x29, 0xef, 0x8f, 0x7a, 0x47, 0xef, 0x1d, 0xad, 0x43, 0xad, 0x0c, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x40, 0xe5, 0x5f, 0xc0, 0xb2, 0x51, 0x1f, 0xd6, 0x17, 0xb0, 0xbe, 0x80, 0xf5, 0x05, 0xac, 0x2f, 0x60, 0x7d, 0x01, 0xeb, 0x0b, 0x58, 0x5f, 0xc0, 0xfa, 0x1c, 0xd6, 0xe7, 0x58, 0xcc, 0x3a, 0x16, 0xb3, 0x8e, 0xc5, 0xac, 0x63, 0x31, 0xeb, 0x58, 0xcc, 0x3a, 0x16, 0xb3, 0xae, 0xfe, 0xba, 0x68, 0x6d, 0xfd, 0xb7, 0xe5, 0xfa, 0xa8, 0x9d, 0x0b, 0xb4, 0x73, 0x81, 0x76, 0x2e, 0xd0, 0xce, 0x05, 0xda, 0xb9, 0x40, 0x3b, 0x17, 0x68, 0xe7, 0x02, 0xed, 0x5c, 0xa0, 0xdd, 0x7a, 0x3f, 0x63, 0xbd, 0x9f, 0xb1, 0xde, 0xcf, 0x58, 0xef, 0x67, 0xac, 0xf7, 0x33, 0xd6, 0xfb, 0x19, 0xeb, 0xfd, 0x8c, 0xf5, 0x7e, 0xc6, 0x7a, 0x3f, 0x63, 0xad, 0x9f, 0xb1, 0xd6, 0xcf, 0x58, 0xeb, 0x67, 0xac, 0xf5, 0x33, 0xd6, 0xfa, 0x19, 0x6b, 0xfd, 0x8c, 0xb5, 0x7e, 0xc6, 0x5a, 0x3f, 0x63, 0xad, 0x9f, 0xb1, 0xd6, 0xcf, 0x58, 0xeb, 0x67, 0xac, 0xf5, 0x33, 0xd6, 0xfa, 0x19, 0x6b, 0xfd, 0x8c, 0xb5, 0x7e, 0xc6, 0x5a, 0x3f, 0x63, 0xad, 0x9f, 0xb1, 0xc6, 0xcf, 0xf0, 0xdd, 0x8c, 0x35, 0x7e, 0xc6, 0x1a, 0x3f, 0x63, 0x8d, 0x9f, 0xa9, 0x9f, 0x8d, 0x88, 0x73, 0xe4, 0xaf, 0x32, 0xd7, 0xd7, 0x7f, 0x93, 0xe7, 0x64, 0x9e, 0xbc, 0x20, 0xcd, 0xb2, 0x50, 0x5a, 0xe4, 0x45, 0x59, 0x2c, 0x71, 0x69, 0x95, 0x25, 0xd2, 0x26, 0x4b, 0xe5, 0x65, 0x69, 0x97, 0x0e, 0x79, 0x55, 0x96, 0xcb, 0x6b, 0xf2, 0xba, 0xac, 0x90, 0x95, 0xd2, 0x25, 0xc6, 0x19, 0x9d, 0x33, 0xe8, 0x9c, 0x41, 0xe7, 0x0c, 0x3a, 0x67, 0xd0, 0x39, 0x83, 0xce, 0x19, 0x74, 0xce, 0xa0, 0x73, 0xa6, 0x3e, 0x6b, 0x5f, 0x72, 0x92, 0x97, 0x5e, 0x29, 0xc8, 0x3a, 0xe9, 0x13, 0xa4, 0xac, 0x47, 0xca, 0x7a, 0xa4, 0xac, 0x47, 0xca, 0x7a, 0xa4, 0xac, 0x67, 0x36, 0xf5, 0x45, 0x19, 0x96, 0x92, 0x8c, 0xc8, 0x9b, 0x12, 0x45, 0x89, 0x86, 0x40, 0x62, 0x52, 0x23, 0xa3, 0xa4, 0x56, 0xea, 0x64, 0x34, 0x77, 0x1f, 0x23, 0xf5, 0xd2, 0x20, 0x9b, 0xc9, 0xe6, 0xb2, 0x85, 0x6c, 0x89, 0xca, 0x5b, 0xc9, 0x7b, 0x65, 0x6b, 0x51, 0xbf, 0x0d, 0xdb, 0xca, 0xf6, 0xb2, 0x83, 0xec, 0x58, 0x36, 0x08, 0xd9, 0x59, 0x76, 0x91, 0x5d, 0x65, 0x37, 0xd9, 0x5d, 0xf6, 0x90, 0xbd, 0x64, 0x6f, 0xd9, 0x47, 0xf6, 0x95, 0x0f, 0xcb, 0xfe, 0xc2, 0xf0, 0x1a, 0xd8, 0x5d, 0x03, 0xbb, 0x6b, 0x60, 0x77, 0x0d, 0xec, 0xae, 0x61, 0xbc, 0x20, 0x59, 0x03, 0x92, 0x35, 0xb0, 0xba, 0x06, 0xfd, 0xb0, 0x41, 0x3f, 0x6c, 0xd0, 0x0f, 0x1b, 0xf4, 0xc3, 0x06, 0xfd, 0xb0, 0x41, 0x3f, 0x6c, 0x38, 0x96, 0xc9, 0x7e, 0x52, 0x26, 0x08, 0xda, 0x36, 0xa0, 0x77, 0xc3, 0x89, 0x82, 0xe0, 0x0d, 0x27, 0xcb, 0x44, 0x39, 0x45, 0x4e, 0x15, 0x9c, 0x6b, 0xd0, 0xcd, 0x59, 0x4c, 0x86, 0xc5, 0x64, 0x58, 0x4c, 0x86, 0xc5, 0x64, 0x58, 0x4c, 0x86, 0xc5, 0x64, 0x58, 0x4c, 0x86, 0xc5, 0x64, 0x58, 0x4c, 0x86, 0xc5, 0x64, 0x58, 0x4c, 0xa6, 0xb2, 0x96, 0x39, 0x5f, 0x2e, 0x90, 0x0b, 0xe5, 0x2b, 0xf2, 0x55, 0xb9, 0x48, 0xbe, 0x26, 0x17, 0xcb, 0xd7, 0xe5, 0x12, 0xf9, 0x86, 0x5c, 0x2a, 0xd7, 0xca, 0xb7, 0xe4, 0x3a, 0xd1, 0x5b, 0x1a, 0x6e, 0x90, 0x9b, 0xe4, 0x7b, 0x72, 0xb3, 0x7c, 0x5f, 0x26, 0x8b, 0x1e, 0xd6, 0xa0, 0x87, 0x35, 0xdc, 0x2e, 0x3f, 0x90, 0x1f, 0x46, 0xa9, 0x86, 0x3b, 0xe4, 0x4e, 0x99, 0x22, 0x7a, 0x4f, 0xc3, 0x5d, 0x72, 0xb7, 0xdc, 0x23, 0x3f, 0x92, 0x7b, 0xad, 0xc9, 0xee, 0x93, 0xe9, 0xf2, 0x53, 0xf9, 0x99, 0xfc, 0x5c, 0xee, 0x97, 0x07, 0xe4, 0x17, 0xf2, 0xa0, 0xfc, 0x52, 0x66, 0xc8, 0xaf, 0xe4, 0xd7, 0x32, 0x53, 0x1e, 0x92, 0xdf, 0xc8, 0x6f, 0xe5, 0x61, 0xd1, 0x9b, 0x1a, 0xf4, 0xa6, 0x86, 0x47, 0x65, 0x96, 0xfc, 0x5e, 0xfe, 0x20, 0x8f, 0xc9, 0xe3, 0xf2, 0x47, 0xf9, 0x57, 0xf9, 0x93, 0x3c, 0x21, 0x7f, 0x96, 0x27, 0xe5, 0x29, 0x79, 0x5a, 0x9e, 0x91, 0xbf, 0xc8, 0x6c, 0x99, 0x23, 0x7f, 0x75, 0x0c, 0xcf, 0xca, 0x5c, 0xf9, 0x9b, 0x3c, 0x27, 0xf3, 0x64, 0xbe, 0x3c, 0x2f, 0x2f, 0x48, 0xb3, 0x2c, 0x94, 0x16, 0x79, 0x51, 0x5e, 0x92, 0x45, 0xb2, 0x58, 0xe2, 0xd2, 0x2a, 0x4b, 0xa4, 0x4d, 0x5e, 0x96, 0x76, 0xe9, 0x88, 0x86, 0xad, 0x58, 0x86, 0x1b, 0x56, 0x45, 0x6b, 0x1b, 0x56, 0xcb, 0x1a, 0xf7, 0xbb, 0x25, 0x29, 0x29, 0xe9, 0x91, 0x8c, 0x64, 0x25, 0x94, 0x9c, 0xe4, 0xa5, 0xcf, 0xf9, 0xef, 0x97, 0x01, 0x19, 0x94, 0x37, 0x64, 0x7d, 0x94, 0xa9, 0xbc, 0x87, 0xaa, 0xef, 0x33, 0xcb, 0x0c, 0xb3, 0xcc, 0x30, 0xcb, 0x4c, 0x63, 0xf9, 0xfb, 0x43, 0xbe, 0x5f, 0x94, 0x61, 0x29, 0xc9, 0x48, 0x94, 0x67, 0x9b, 0x99, 0x58, 0x07, 0xc2, 0x87, 0x08, 0x1f, 0x56, 0x56, 0x3d, 0x1f, 0x74, 0xfb, 0xa1, 0x68, 0x11, 0xba, 0x87, 0xe8, 0x1e, 0xa2, 0x7b, 0x88, 0xee, 0x45, 0x74, 0x4f, 0xa3, 0x7b, 0x1a, 0xdd, 0xd3, 0xe8, 0x9e, 0x46, 0xf7, 0x34, 0xba, 0xa7, 0xd1, 0x7d, 0x2d, 0xba, 0xaf, 0x45, 0xf7, 0xb5, 0xe8, 0x9e, 0x46, 0xf7, 0xf4, 0xff, 0x82, 0x4f, 0xe0, 0xe8, 0x46, 0xf7, 0x34, 0xba, 0x17, 0xd1, 0xbd, 0x88, 0xee, 0x45, 0x74, 0x2f, 0xa2, 0x7b, 0x11, 0xdd, 0x8b, 0xe8, 0x5e, 0x44, 0xf7, 0x22, 0xba, 0x17, 0xd1, 0xbd, 0x88, 0xee, 0x45, 0x74, 0x2f, 0xa2, 0x7b, 0x11, 0xdd, 0x8b, 0xe8, 0x5e, 0x44, 0xf7, 0x34, 0xba, 0xa7, 0xd1, 0x3d, 0x8d, 0xee, 0x45, 0x74, 0x2f, 0xa2, 0xfb, 0x5a, 0x74, 0x2f, 0xa2, 0x7b, 0x16, 0xdd, 0xb3, 0xe8, 0x9e, 0x45, 0xf7, 0x2c, 0xba, 0x17, 0xab, 0x9f, 0xda, 0x91, 0x41, 0xf7, 0x0c, 0xba, 0x67, 0xd0, 0x3d, 0x83, 0xee, 0x19, 0x74, 0xcf, 0xa0, 0x7b, 0x06, 0xdd, 0x33, 0xe8, 0x9e, 0x41, 0xf7, 0x0c, 0xba, 0x17, 0xd0, 0xbd, 0x80, 0xee, 0xbd, 0xc1, 0xed, 0x9e, 0x5b, 0xb6, 0xd6, 0x3b, 0x2a, 0x44, 0x4f, 0x23, 0x7a, 0x01, 0xd1, 0x17, 0x21, 0xfa, 0x22, 0x44, 0x2f, 0x22, 0x7a, 0x11, 0xd1, 0xd3, 0x88, 0xbe, 0x16, 0xd1, 0x8b, 0x88, 0x5e, 0x44, 0xf4, 0x22, 0xa2, 0x67, 0x10, 0x3d, 0x83, 0xe8, 0x05, 0x44, 0xef, 0x46, 0xf4, 0xb5, 0x88, 0x5e, 0x40, 0xf4, 0x2c, 0xa2, 0x97, 0xdf, 0xed, 0x34, 0x8f, 0xe8, 0x45, 0x44, 0x2f, 0x22, 0x7a, 0xe1, 0x9f, 0xf8, 0xf4, 0x89, 0x0c, 0xa2, 0x67, 0x10, 0x3d, 0x8d, 0xe8, 0x45, 0x14, 0x2f, 0xfe, 0xdb, 0x27, 0x2a, 0xb8, 0xff, 0x4e, 0x9f, 0xa8, 0xb0, 0x2a, 0x4a, 0xa3, 0xf8, 0x5a, 0x14, 0x2f, 0xa0, 0x78, 0x11, 0xc5, 0x8b, 0x28, 0x9e, 0x41, 0xf1, 0x34, 0x9a, 0x86, 0x68, 0x1a, 0xa2, 0x69, 0x88, 0xa6, 0x21, 0x9a, 0x86, 0x68, 0x1a, 0xa2, 0x69, 0xb8, 0x71, 0xe5, 0x7c, 0xa5, 0xfb, 0x57, 0xc9, 0x24, 0xf9, 0xa6, 0x5c, 0x2d, 0xd7, 0xc8, 0xf5, 0xd1, 0x22, 0x14, 0x5d, 0x84, 0xa2, 0x8b, 0x50, 0x74, 0x11, 0x8a, 0x2e, 0x42, 0xd1, 0x45, 0x28, 0xba, 0x08, 0x45, 0x17, 0xa1, 0xe8, 0x22, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd7, 0xa2, 0xe8, 0x5a, 0x14, 0x5d, 0x8b, 0xa2, 0x6b, 0x51, 0x74, 0x2d, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0xf4, 0x7f, 0xe2, 0xbb, 0xd7, 0x77, 0xa3, 0x68, 0x37, 0x8a, 0x76, 0xa3, 0x68, 0x37, 0x8a, 0x76, 0xa3, 0x68, 0x37, 0x8a, 0x76, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0xcd, 0xa2, 0x68, 0x16, 0x45, 0xb3, 0x28, 0x9a, 0x45, 0xd1, 0x2c, 0x8a, 0x66, 0x51, 0x34, 0x8b, 0xa2, 0x59, 0x14, 0xcd, 0xa2, 0x68, 0x06, 0x45, 0x33, 0x28, 0x9a, 0x41, 0xd1, 0x0c, 0x8a, 0x66, 0x50, 0x34, 0x83, 0xa2, 0x19, 0x14, 0xcd, 0xa0, 0x68, 0x06, 0x45, 0x33, 0x28, 0x9a, 0x41, 0xd1, 0x0c, 0x8a, 0x66, 0x50, 0x34, 0x83, 0xa2, 0x19, 0x14, 0xcd, 0xa0, 0x68, 0x06, 0x45, 0x33, 0x28, 0x9a, 0x41, 0xd1, 0x0c, 0x8a, 0x66, 0x50, 0x34, 0x83, 0xa2, 0x19, 0x14, 0xcd, 0xa0, 0x68, 0x06, 0x45, 0x33, 0x28, 0x9a, 0x41, 0xd1, 0x0c, 0x8a, 0x66, 0x50, 0x34, 0x83, 0xa2, 0x19, 0x14, 0xcd, 0xa0, 0x68, 0x06, 0x45, 0x33, 0x28, 0x9a, 0x41, 0xd1, 0x0c, 0x8a, 0x66, 0x50, 0x34, 0x83, 0xa2, 0x19, 0x14, 0x2d, 0xa0, 0x68, 0x01, 0x45, 0x0b, 0x28, 0x5a, 0x40, 0xd1, 0x02, 0x8a, 0x16, 0x50, 0xb4, 0x80, 0xa2, 0x05, 0x14, 0x2d, 0xa0, 0x68, 0x01, 0x45, 0x0b, 0x28, 0x5a, 0x40, 0xd1, 0x02, 0x8a, 0x16, 0x50, 0xb4, 0x80, 0xa2, 0x05, 0x14, 0x2d, 0xa0, 0x68, 0x01, 0x45, 0x0b, 0x28, 0x5a, 0x40, 0xd1, 0x02, 0x8a, 0x16, 0x50, 0xb4, 0x17, 0x45, 0x7b, 0x1b, 0x3a, 0x9d, 0xa3, 0x57, 0x65, 0xb9, 0xbc, 0x26, 0xaf, 0xcb, 0x0a, 0x59, 0x29, 0x6b, 0xd0, 0xb6, 0x5b, 0x92, 0x92, 0x92, 0x1e, 0xc9, 0x88, 0xd5, 0x07, 0xb2, 0xe6, 0x91, 0x35, 0x8f, 0xac, 0x79, 0x64, 0x2d, 0x20, 0x6b, 0x01, 0x59, 0x0b, 0xc8, 0x5a, 0x40, 0xd6, 0x02, 0xb2, 0x16, 0x90, 0xb5, 0x88, 0xac, 0x69, 0x64, 0x4d, 0x23, 0x6b, 0x1a, 0x59, 0xd3, 0xc8, 0x9a, 0x46, 0xd6, 0x02, 0xb2, 0x16, 0x90, 0xb5, 0x80, 0xac, 0x05, 0x64, 0x2d, 0x20, 0xeb, 0xda, 0x9a, 0x46, 0xeb, 0xe8, 0xa4, 0x75, 0xb4, 0xf5, 0x7e, 0xf4, 0x2a, 0xca, 0xf6, 0xa3, 0x6c, 0x3f, 0xca, 0x66, 0xab, 0xeb, 0xe8, 0x04, 0xd2, 0x2e, 0x0d, 0x54, 0x39, 0xda, 0xae, 0x45, 0xdb, 0xb5, 0x68, 0xbb, 0x16, 0x6d, 0xf3, 0x68, 0x1b, 0xa2, 0x6d, 0x18, 0x94, 0x3f, 0x63, 0xfd, 0x00, 0x39, 0x50, 0x0e, 0x92, 0xf1, 0x72, 0xb0, 0x1c, 0x22, 0x1f, 0x97, 0xc3, 0xe4, 0x13, 0xd6, 0xe8, 0x87, 0xcb, 0x11, 0x72, 0xa4, 0x1c, 0x25, 0x47, 0xcb, 0x31, 0x72, 0xac, 0x7c, 0x52, 0x26, 0xc8, 0x71, 0xf2, 0x29, 0x39, 0x5e, 0x4e, 0x90, 0x13, 0xe5, 0x24, 0x39, 0x59, 0x26, 0xca, 0x29, 0x72, 0xaa, 0x9c, 0x26, 0x9f, 0x96, 0xd3, 0xe5, 0x33, 0x72, 0x86, 0x9c, 0x29, 0x9f, 0x95, 0xcf, 0xc9, 0x59, 0x72, 0xb6, 0x7c, 0x5e, 0xbe, 0x20, 0x5f, 0x8c, 0x56, 0xa1, 0x6d, 0x0e, 0x6d, 0xf3, 0x68, 0x9b, 0x47, 0xdb, 0x3c, 0xda, 0xe6, 0xd0, 0x36, 0x87, 0xb6, 0x39, 0xb4, 0xcd, 0xa1, 0x6d, 0x0e, 0x6d, 0xf3, 0x68, 0x9b, 0x43, 0xdb, 0x3c, 0xda, 0xe6, 0xd1, 0x36, 0x87, 0xb6, 0x39, 0xb4, 0xcd, 0xa1, 0x6d, 0x88, 0xb6, 0x21, 0xda, 0x86, 0x68, 0x9b, 0x43, 0xdb, 0x3c, 0xda, 0x86, 0x68, 0x9b, 0x7b, 0x87, 0xb5, 0x74, 0x1e, 0x6d, 0xfb, 0xd0, 0x76, 0x18, 0x6d, 0x87, 0xd1, 0x76, 0x18, 0x6d, 0x87, 0xd1, 0x76, 0x18, 0x6d, 0x87, 0xd1, 0x76, 0x18, 0x6d, 0x87, 0xd1, 0x76, 0x18, 0x6d, 0x87, 0xd1, 0x36, 0x8b, 0xb6, 0x59, 0xb4, 0x2d, 0x59, 0x4b, 0xa7, 0xd1, 0xb6, 0x84, 0xb6, 0x59, 0x6b, 0xe9, 0xcc, 0xc6, 0xab, 0x49, 0xd6, 0x88, 0x31, 0x6b, 0xc4, 0xea, 0x75, 0xaa, 0x0d, 0xeb, 0xeb, 0x10, 0x8d, 0xb3, 0x68, 0xbc, 0xb4, 0xe6, 0xf3, 0x28, 0xca, 0x1c, 0x50, 0x79, 0x69, 0x0d, 0x4b, 0x40, 0xe6, 0x3c, 0x32, 0xe7, 0x91, 0x39, 0x44, 0xe6, 0x10, 0x99, 0x73, 0xc8, 0x9c, 0x47, 0xe6, 0x1c, 0x32, 0x0f, 0x23, 0xf3, 0x30, 0x32, 0x67, 0x91, 0xb9, 0x7c, 0x55, 0x36, 0x44, 0xe6, 0x6c, 0x75, 0xad, 0x5d, 0xbe, 0x6a, 0x5f, 0x42, 0xe6, 0x3c, 0x32, 0xe7, 0x91, 0x39, 0x8b, 0xcc, 0x7d, 0xc8, 0xdc, 0x87, 0xcc, 0x7d, 0xc8, 0xdc, 0x87, 0xcc, 0x7d, 0xc8, 0xdc, 0x87, 0xcc, 0x7d, 0xc8, 0xdc, 0x87, 0xcc, 0x7d, 0xc8, 0xdc, 0x87, 0xcc, 0xc3, 0xc8, 0x3c, 0x8c, 0xcc, 0x39, 0x64, 0xce, 0x5b, 0x6b, 0xaf, 0xab, 0xae, 0xb5, 0x0b, 0x28, 0x9d, 0x47, 0xe9, 0x3e, 0x94, 0xee, 0x43, 0xe9, 0x1c, 0x4a, 0xf7, 0xa1, 0x74, 0x1f, 0x4a, 0xf7, 0xa1, 0x74, 0x1f, 0x4a, 0xf7, 0xa1, 0x74, 0x88, 0xd2, 0x21, 0x4a, 0x97, 0x3f, 0xd7, 0x28, 0x87, 0xd2, 0x79, 0x94, 0x1e, 0x46, 0xe9, 0xd0, 0x5a, 0x3b, 0x69, 0xad, 0x9d, 0xb4, 0xd6, 0x4e, 0x5a, 0x6b, 0x27, 0xad, 0xb5, 0x93, 0xd6, 0xda, 0x49, 0x6b, 0xed, 0xa4, 0xb5, 0x76, 0xd2, 0x5a, 0x3b, 0x69, 0xad, 0x9d, 0xb4, 0xd6, 0x4e, 0x5a, 0x6b, 0x27, 0xad, 0xb5, 0x93, 0xd6, 0xda, 0x49, 0x6b, 0xed, 0xa4, 0xb5, 0x76, 0xd2, 0x5a, 0x3b, 0x69, 0xad, 0x9d, 0xb4, 0xd6, 0x4e, 0x5a, 0x6b, 0x27, 0xad, 0xb5, 0x93, 0xd6, 0xda, 0x49, 0x6b, 0xed, 0xa4, 0xb5, 0x76, 0xd2, 0x5a, 0x3b, 0x69, 0xad, 0x9d, 0xb4, 0xd6, 0x4e, 0x5a, 0x6b, 0x27, 0xad, 0xb5, 0x5f, 0xb5, 0xd6, 0x7e, 0x95, 0x1d, 0xf4, 0xb3, 0x83, 0x7e, 0x76, 0xd0, 0xcf, 0x0e, 0xfa, 0xd9, 0x41, 0x3f, 0x3b, 0xe8, 0x67, 0x07, 0xfd, 0xec, 0x20, 0xcb, 0x0e, 0xb2, 0xff, 0xce, 0x5a, 0x3b, 0x61, 0xad, 0x9d, 0x60, 0x09, 0x4b, 0x59, 0xc2, 0x52, 0x96, 0xb0, 0x94, 0x25, 0x2c, 0x65, 0x09, 0x4b, 0x59, 0xc2, 0x52, 0x96, 0xb0, 0x94, 0x25, 0x2c, 0x65, 0x09, 0x4b, 0xeb, 0xef, 0x42, 0xe8, 0xbb, 0xe5, 0x1e, 0xf9, 0xb1, 0xfc, 0x44, 0xee, 0x95, 0xfb, 0x10, 0x79, 0xba, 0xfc, 0x54, 0x7e, 0x26, 0x3f, 0x97, 0xfb, 0xe5, 0x01, 0xf9, 0x85, 0x3c, 0x28, 0x33, 0xe4, 0x57, 0xf2, 0x6b, 0x99, 0x29, 0x0f, 0xc9, 0x6f, 0xe4, 0xb7, 0xf2, 0xb0, 0xfc, 0x4e, 0x1e, 0x91, 0x47, 0x65, 0x96, 0xfc, 0x5e, 0xfe, 0x20, 0x8f, 0xc9, 0xe3, 0xf2, 0x47, 0xf9, 0xb3, 0x3c, 0x29, 0x4f, 0xc9, 0xd3, 0xf2, 0x8c, 0xcc, 0x8e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0xac, 0x2f, 0x7f, 0x76, 0x60, 0xf9, 0xfa, 0x47, 0x4e, 0xf2, 0xd2, 0x2b, 0x05, 0x59, 0x27, 0x7d, 0xd2, 0x2f, 0x03, 0x32, 0x28, 0x6f, 0xc8, 0x7a, 0x19, 0x92, 0xa2, 0x0c, 0x4b, 0x49, 0x46, 0xe4, 0x4d, 0x89, 0xa2, 0x55, 0xec, 0x61, 0x15, 0x7b, 0x58, 0xc5, 0x1e, 0x56, 0xb1, 0x87, 0x55, 0xec, 0x61, 0x15, 0x7b, 0x58, 0xc5, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0x8e, 0x3d, 0xe4, 0xd8, 0x43, 0x8e, 0x3d, 0xe4, 0xd8, 0x43, 0x8e, 0x3d, 0xe4, 0xd8, 0x43, 0x8e, 0x3d, 0xe4, 0xd8, 0x43, 0x8e, 0x3d, 0xe4, 0xd8, 0x43, 0xee, 0x1f, 0x5c, 0x83, 0x0f, 0xb3, 0x87, 0x61, 0xf6, 0x30, 0xcc, 0x1e, 0x86, 0xd9, 0xc3, 0x30, 0x7b, 0x18, 0x66, 0x0f, 0xc3, 0xec, 0x61, 0x98, 0x3d, 0x0c, 0xb3, 0x87, 0x61, 0xf6, 0x30, 0xcc, 0x1e, 0x86, 0xd9, 0xc3, 0x30, 0x7b, 0x18, 0x66, 0x0f, 0xc3, 0xec, 0x61, 0x98, 0x3d, 0x0c, 0xb3, 0x87, 0x61, 0xf6, 0x30, 0xcc, 0x1e, 0x86, 0xd9, 0xc3, 0x30, 0x7b, 0x18, 0x66, 0x0f, 0xc3, 0xec, 0x61, 0x98, 0x3d, 0x0c, 0xb3, 0x87, 0x61, 0xf6, 0x30, 0xcc, 0x1e, 0x86, 0xd9, 0xc3, 0x30, 0x7b, 0x18, 0x66, 0x0f, 0xc3, 0xec, 0x61, 0x98, 0x3d, 0x0c, 0xb3, 0x87, 0x61, 0xf6, 0x30, 0xcc, 0x1e, 0x86, 0xd9, 0xc3, 0x30, 0x7b, 0x18, 0x66, 0x0f, 0xc3, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0xa1, 0xc4, 0x1e, 0x4a, 0xd6, 0xe0, 0x69, 0x6b, 0xf0, 0x34, 0x53, 0x28, 0x31, 0x85, 0x12, 0x53, 0x28, 0x31, 0x85, 0x12, 0x53, 0x28, 0x31, 0x85, 0x12, 0x53, 0x28, 0x31, 0x85, 0x12, 0x53, 0x28, 0x31, 0x85, 0x12, 0x53, 0xc8, 0x32, 0x85, 0x2c, 0x53, 0xc8, 0x32, 0x85, 0x2c, 0x53, 0xc8, 0x32, 0x85, 0x2c, 0x53, 0xc8, 0x31, 0x85, 0x90, 0x29, 0x84, 0x4c, 0x21, 0x64, 0x0a, 0x21, 0x53, 0x08, 0x99, 0x42, 0x96, 0x29, 0x64, 0x99, 0x42, 0x96, 0x29, 0x64, 0x99, 0x42, 0x96, 0x29, 0x84, 0xb1, 0xf9, 0x0c, 0x20, 0xcb, 0x00, 0xb2, 0x0c, 0x20, 0xcb, 0x00, 0xc2, 0x7f, 0xe7, 0x6a, 0x7a, 0x96, 0x01, 0x64, 0x19, 0x40, 0x76, 0x93, 0xab, 0xe9, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0xaf, 0x1a, 0x40, 0x96, 0x01, 0x84, 0x0c, 0x20, 0x64, 0x00, 0x21, 0x03, 0xc8, 0x32, 0x80, 0x2c, 0x03, 0xc8, 0x32, 0x80, 0x2c, 0x03, 0xc8, 0x32, 0x80, 0x90, 0x01, 0x84, 0x0c, 0x20, 0x64, 0x00, 0x21, 0x03, 0x08, 0x19, 0x40, 0xc8, 0x00, 0xc2, 0xb7, 0x5d, 0x4d, 0x0f, 0x19, 0x40, 0xc8, 0x00, 0xb2, 0x0c, 0x20, 0x64, 0x00, 0x69, 0x06, 0x90, 0x66, 0x00, 0x69, 0x06, 0x90, 0x66, 0x00, 0x21, 0x03, 0x88, 0x33, 0x80, 0x76, 0x06, 0xd0, 0xce, 0x00, 0xda, 0x19, 0x40, 0x3b, 0x03, 0x68, 0x67, 0x00, 0xed, 0x0c, 0xa0, 0x9d, 0x01, 0xb4, 0x33, 0x80, 0x76, 0x06, 0xd0, 0xbe, 0x89, 0x01, 0xbc, 0x68, 0xbd, 0xbd, 0x9c, 0x05, 0x94, 0x3f, 0xdd, 0x64, 0x41, 0xf9, 0x6f, 0xf8, 0x62, 0x07, 0x46, 0x1d, 0xb1, 0x83, 0xac, 0xbb, 0x8f, 0x8f, 0xfe, 0x86, 0xfe, 0xaf, 0xa2, 0xff, 0x22, 0xf4, 0x7f, 0x39, 0x86, 0x46, 0xb1, 0x0b, 0xa2, 0x15, 0xd5, 0xab, 0xec, 0x59, 0xc4, 0x0f, 0x11, 0x3f, 0xac, 0x5e, 0x5d, 0xcf, 0x22, 0x7e, 0x88, 0xf8, 0x21, 0xe2, 0x87, 0x88, 0xdf, 0x8e, 0xf8, 0xed, 0x95, 0xbf, 0x2f, 0x7b, 0x8b, 0xf8, 0xd9, 0xea, 0xdf, 0x95, 0xa5, 0x11, 0x3f, 0x8b, 0xf8, 0x0b, 0x10, 0x3f, 0x44, 0xfc, 0xb0, 0xf2, 0xb7, 0x65, 0x0f, 0x46, 0x71, 0xc4, 0x8f, 0x23, 0x7e, 0x1c, 0xf1, 0xe3, 0x88, 0x1f, 0x47, 0xfc, 0x38, 0xe2, 0xc7, 0x11, 0x3f, 0x8e, 0xf8, 0x71, 0xc4, 0x8f, 0x23, 0x7e, 0x3b, 0xe2, 0xb7, 0x23, 0x7e, 0x16, 0xf1, 0x43, 0x94, 0x0f, 0xeb, 0x0e, 0x88, 0x3a, 0xea, 0xec, 0x33, 0xda, 0xc7, 0xd1, 0x3e, 0x8e, 0xf6, 0x59, 0xb4, 0x8f, 0xa3, 0x7d, 0x1c, 0xed, 0xe3, 0x68, 0x1f, 0x47, 0xfb, 0x78, 0xf5, 0xca, 0x7a, 0xb6, 0x4a, 0xfb, 0x10, 0xed, 0x43, 0xb4, 0x6f, 0x2f, 0x5f, 0x59, 0x47, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xa2, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xa2, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xa2, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x5b, 0xfd, 0x04, 0xb8, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xbe, 0xcb, 0x95, 0xe7, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xfe, 0x37, 0x5c, 0x79, 0x8e, 0xa3, 0x5e, 0x1c, 0xf5, 0xe2, 0xa8, 0x17, 0x47, 0xbd, 0x38, 0xea, 0xc5, 0x51, 0x2f, 0x8e, 0x7a, 0x71, 0xd4, 0x8b, 0xa3, 0x5e, 0x1c, 0xf5, 0xe2, 0xa8, 0x17, 0x47, 0xbd, 0x38, 0xea, 0xc5, 0x51, 0x2f, 0x8e, 0x7a, 0x71, 0xd4, 0x8b, 0xa3, 0x5e, 0xfc, 0x3f, 0xa0, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0x0b, 0x51, 0x2f, 0x44, 0xbd, 0x10, 0xf5, 0x42, 0xd4, 0x0b, 0x51, 0x2f, 0x44, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xa2, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xa2, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0xf8, 0xdf, 0x7c, 0xe5, 0x39, 0x44, 0xbd, 0x10, 0xf5, 0x42, 0xd4, 0x0b, 0x51, 0x2f, 0x44, 0xbd, 0x10, 0xf5, 0x42, 0xd4, 0x0b, 0x51, 0xaf, 0xfc, 0x0e, 0x10, 0x21, 0xea, 0x85, 0xa8, 0x97, 0x46, 0xbd, 0x34, 0xea, 0xa5, 0x51, 0x2f, 0x8d, 0x7a, 0x69, 0xd4, 0x4b, 0xa3, 0x5e, 0x1a, 0xf5, 0xd2, 0xa8, 0x97, 0x46, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0xf6, 0xff, 0x42, 0xea, 0xbd, 0x88, 0x7a, 0x2f, 0x5a, 0x33, 0x2f, 0xb7, 0x66, 0x5e, 0x6e, 0xcd, 0xbc, 0xdc, 0x9a, 0x79, 0xb9, 0x35, 0xf3, 0x72, 0x6b, 0xe6, 0xe5, 0xd6, 0xcc, 0xcb, 0x11, 0x71, 0x21, 0x22, 0x96, 0x3f, 0xa9, 0x65, 0x01, 0x22, 0x2e, 0x40, 0xc4, 0x05, 0x88, 0xb8, 0x00, 0x11, 0x17, 0x20, 0xe2, 0x02, 0x44, 0x5c, 0x80, 0x88, 0x0b, 0x10, 0x71, 0x01, 0x22, 0x2e, 0x78, 0xb7, 0xbf, 0x43, 0x40, 0xc4, 0xf0, 0x5d, 0xae, 0x4a, 0xbf, 0x13, 0x11, 0xb3, 0xb1, 0xe7, 0xdf, 0x61, 0x4d, 0x9c, 0x44, 0xc4, 0x24, 0x22, 0x26, 0x11, 0x31, 0x89, 0x88, 0x49, 0x44, 0x4c, 0x22, 0x62, 0x0a, 0x11, 0x53, 0x88, 0x98, 0x42, 0xc4, 0x24, 0x22, 0x26, 0xff, 0xc7, 0xdf, 0x03, 0xf0, 0x8b, 0xd1, 0x6a, 0x44, 0x4c, 0xbd, 0x6d, 0x4d, 0x9c, 0x47, 0xc4, 0x3c, 0x22, 0xe6, 0x83, 0xf2, 0x6f, 0x49, 0x7c, 0x55, 0xde, 0x5a, 0x13, 0xe7, 0x37, 0x59, 0x13, 0x6f, 0xfa, 0xef, 0xcb, 0x49, 0x44, 0x4c, 0x22, 0x62, 0xb2, 0xfa, 0xef, 0xcb, 0xe5, 0x35, 0x71, 0xaa, 0xfa, 0xef, 0xcb, 0x3d, 0x88, 0xd8, 0x83, 0x88, 0x3d, 0x88, 0xd8, 0x53, 0x5d, 0x13, 0x97, 0xdf, 0x2b, 0xb0, 0x0d, 0x11, 0xdb, 0x10, 0xb1, 0x0d, 0x11, 0xdb, 0x10, 0xb1, 0x0d, 0x11, 0xdb, 0x10, 0xb1, 0x0d, 0x11, 0xdb, 0x10, 0xb1, 0x0d, 0x11, 0xdb, 0x36, 0x21, 0xe2, 0x73, 0x88, 0xd8, 0x81, 0x88, 0x29, 0x44, 0x9c, 0x83, 0x88, 0x09, 0x44, 0x5c, 0x5e, 0x21, 0xe2, 0x61, 0x72, 0x78, 0x85, 0x8c, 0xcf, 0x56, 0x68, 0xf8, 0xc5, 0x68, 0x5d, 0xec, 0x4b, 0x72, 0x41, 0xd4, 0x89, 0x8a, 0xc9, 0x2a, 0x15, 0x37, 0xac, 0x83, 0x93, 0xa8, 0x98, 0xaa, 0xfe, 0x9b, 0x73, 0xbe, 0xfa, 0x6f, 0xce, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x09, 0x54, 0x5c, 0x8d, 0x8a, 0x29, 0x54, 0x4c, 0xa0, 0x62, 0x0f, 0x2a, 0xa6, 0x50, 0x71, 0xce, 0x26, 0xeb, 0xe0, 0xc4, 0x3f, 0xf1, 0xbe, 0x75, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0xa9, 0xf2, 0x3a, 0xb8, 0xb2, 0xf6, 0x3d, 0x20, 0x5a, 0x8a, 0x8a, 0x4b, 0xff, 0xed, 0xbd, 0xbc, 0x7c, 0xef, 0x9d, 0xde, 0xc7, 0x6b, 0x55, 0x94, 0x44, 0xc5, 0x54, 0x95, 0x8a, 0xf9, 0xea, 0x1a, 0xb8, 0x6d, 0x74, 0xf9, 0xf3, 0x95, 0xfe, 0xf7, 0xac, 0x05, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x29, 0x54, 0x4c, 0xa1, 0x62, 0x0a, 0x15, 0x53, 0xa8, 0x98, 0x42, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0xac, 0xbc, 0x1f, 0xd2, 0x7f, 0xc2, 0x7b, 0x21, 0xa1, 0xe2, 0x6a, 0x54, 0x5c, 0x8d, 0x8a, 0xab, 0x51, 0x71, 0x35, 0x2a, 0xae, 0x46, 0xc5, 0xd5, 0xa8, 0xb8, 0x1a, 0x15, 0x53, 0xa8, 0x98, 0x42, 0xc5, 0x14, 0x2a, 0xa6, 0x50, 0x31, 0x85, 0x8a, 0x29, 0x54, 0x4c, 0xfd, 0x3b, 0x6b, 0xc1, 0x3c, 0x2a, 0xe6, 0x51, 0x31, 0x8f, 0x8a, 0x79, 0x54, 0xcc, 0xa3, 0x62, 0x1e, 0x15, 0xf3, 0xa8, 0x98, 0x47, 0xc5, 0x3c, 0x2a, 0xe6, 0x51, 0x31, 0x8f, 0x8a, 0x79, 0x54, 0xcc, 0xa3, 0x62, 0x1e, 0x15, 0xf3, 0xa8, 0x98, 0x47, 0xc5, 0xfc, 0xff, 0xc3, 0x5a, 0xf0, 0xff, 0xe6, 0xdf, 0x63, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0xe4, 0x3f, 0xf9, 0xef, 0xb1, 0x3d, 0xa8, 0xd8, 0x83, 0x8a, 0x3d, 0xa8, 0xd8, 0x83, 0x8a, 0x3d, 0xa8, 0xd8, 0x83, 0x8a, 0x3d, 0xa8, 0xd8, 0x83, 0x8a, 0x3d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0xf6, 0x5f, 0x48, 0xc5, 0xe7, 0x50, 0xf1, 0x39, 0x54, 0xec, 0x40, 0xc5, 0x0e, 0x54, 0xec, 0x40, 0xc5, 0x0e, 0x54, 0xec, 0x40, 0xc5, 0x0e, 0x54, 0x2c, 0xbf, 0xff, 0x51, 0x0a, 0x15, 0x53, 0xa8, 0x38, 0x07, 0x15, 0xe7, 0xa0, 0xe2, 0x1c, 0x54, 0x9c, 0x83, 0x8a, 0x73, 0x50, 0x71, 0x0e, 0x2a, 0xce, 0x41, 0xc5, 0x39, 0xa8, 0x38, 0x07, 0x15, 0xe7, 0xa0, 0x62, 0x02, 0x15, 0x13, 0xa8, 0x98, 0x40, 0xc5, 0x04, 0x2a, 0x26, 0x50, 0x31, 0x51, 0xfd, 0xb7, 0xda, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xbe, 0x0b, 0x15, 0x53, 0x95, 0x75, 0x62, 0x0f, 0x2a, 0xf6, 0xa0, 0x62, 0x4f, 0x75, 0x9d, 0xf8, 0x6e, 0x57, 0x8a, 0x73, 0xa8, 0x98, 0x43, 0xc5, 0xdc, 0x26, 0x57, 0x8a, 0xff, 0xa7, 0xa9, 0xd8, 0x55, 0xbd, 0x52, 0xbc, 0xe9, 0x3a, 0x31, 0x44, 0xc5, 0x10, 0x15, 0x43, 0x54, 0x0c, 0x51, 0x31, 0xfc, 0x0f, 0xd6, 0x89, 0x9b, 0x5e, 0x29, 0xde, 0xb0, 0x4e, 0xcc, 0x55, 0xd7, 0x89, 0x6f, 0xbf, 0x52, 0x1c, 0x56, 0xa9, 0xd8, 0x81, 0x8a, 0x1d, 0xa8, 0xd8, 0x81, 0x8a, 0x1d, 0xa8, 0xd8, 0x81, 0x8a, 0x1d, 0xa8, 0xd8, 0x81, 0x8a, 0x1d, 0xa8, 0xd8, 0x81, 0x8a, 0x1d, 0xa8, 0x98, 0x41, 0xc5, 0x0c, 0x2a, 0x2e, 0x41, 0xc5, 0x55, 0xa8, 0xf8, 0x02, 0x2a, 0x2e, 0x2a, 0xbf, 0x83, 0xee, 0x26, 0xeb, 0xc4, 0x79, 0xd6, 0x89, 0xcb, 0xac, 0x13, 0x5f, 0xb4, 0x4e, 0x5c, 0x8a, 0x8c, 0x83, 0x88, 0xd8, 0x55, 0xbd, 0x5a, 0x9c, 0xd9, 0x64, 0x9d, 0x58, 0xbe, 0x32, 0x9c, 0x7b, 0xdb, 0x3a, 0xb1, 0x03, 0x11, 0x3b, 0x2a, 0xef, 0xe8, 0x7a, 0x4b, 0xd4, 0x85, 0x88, 0xb9, 0xea, 0x3b, 0xb9, 0x96, 0xaf, 0x0c, 0xe7, 0x10, 0x71, 0xd1, 0x26, 0xeb, 0xc4, 0xa5, 0xff, 0x04, 0x11, 0x3b, 0x10, 0xb1, 0xa3, 0x7a, 0x65, 0x78, 0xc3, 0x3a, 0xf1, 0x15, 0x44, 0x7c, 0x65, 0x13, 0x22, 0x86, 0xef, 0x42, 0xc4, 0xf2, 0x55, 0xe1, 0x1c, 0x22, 0x66, 0x36, 0x59, 0x27, 0x76, 0x94, 0xaf, 0x0a, 0x23, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0xcf, 0xdb, 0xd6, 0x89, 0xef, 0x74, 0x75, 0x34, 0x87, 0x88, 0x39, 0x44, 0xcc, 0x21, 0x62, 0x0e, 0x11, 0x73, 0xff, 0x0d, 0x57, 0x47, 0xff, 0xb3, 0x88, 0xd8, 0x85, 0x88, 0x5d, 0x88, 0xd8, 0x85, 0x88, 0x5d, 0x88, 0xd8, 0x85, 0x88, 0x5d, 0x88, 0xd8, 0xf5, 0x1f, 0x5c, 0x1d, 0x7d, 0xb7, 0x75, 0x62, 0x88, 0x88, 0x21, 0x22, 0x86, 0x88, 0x18, 0x22, 0x62, 0x88, 0x88, 0x21, 0x22, 0x86, 0x88, 0x18, 0x22, 0x62, 0x88, 0x88, 0x21, 0x22, 0x86, 0x88, 0x18, 0x22, 0x62, 0x88, 0x88, 0x21, 0x22, 0x86, 0x88, 0x18, 0x22, 0x62, 0xf8, 0xdf, 0xbc, 0x4e, 0xfc, 0xef, 0xbc, 0x3a, 0xfa, 0xcf, 0xac, 0x13, 0xff, 0x91, 0xab, 0xa3, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0x98, 0x41, 0xc4, 0x0c, 0x22, 0x66, 0x10, 0x31, 0x83, 0x88, 0x19, 0x44, 0xcc, 0x20, 0x62, 0x06, 0x11, 0x33, 0x88, 0x98, 0x41, 0xc4, 0x0c, 0x22, 0x66, 0x10, 0x31, 0x83, 0x88, 0x19, 0x44, 0xcc, 0x20, 0x62, 0x06, 0x11, 0x33, 0x88, 0x98, 0x41, 0xc4, 0x0c, 0x22, 0x66, 0x10, 0x31, 0x83, 0x88, 0x19, 0x44, 0xcc, 0x20, 0xe2, 0x12, 0x44, 0x5c, 0x82, 0x88, 0xab, 0x10, 0x71, 0x15, 0x22, 0xae, 0x42, 0xc4, 0x55, 0x88, 0xb8, 0x0a, 0x11, 0x57, 0x21, 0xe2, 0x2a, 0x44, 0x7c, 0x01, 0x11, 0x5f, 0x40, 0xc4, 0x45, 0x88, 0xb8, 0x08, 0x11, 0x17, 0x21, 0xe2, 0x22, 0x44, 0x5c, 0x84, 0x88, 0x8b, 0x10, 0x71, 0x11, 0x22, 0x2e, 0x42, 0xc4, 0x45, 0x88, 0xb8, 0xe8, 0xdd, 0xde, 0xf9, 0xaf, 0xba, 0x4e, 0x7c, 0xa7, 0x2b, 0xa7, 0x19, 0x44, 0xcc, 0x20, 0x62, 0x06, 0x11, 0x33, 0x88, 0x98, 0x41, 0xc4, 0x5c, 0xb0, 0x45, 0xb0, 0x5d, 0x34, 0x1c, 0xec, 0xa5, 0x6b, 0x1f, 0x1a, 0x15, 0x02, 0x15, 0xa2, 0x33, 0x67, 0xcb, 0xbf, 0xef, 0x5e, 0x73, 0x61, 0x34, 0x54, 0x73, 0xab, 0xb5, 0xc6, 0x63, 0xd1, 0xfc, 0x9a, 0x27, 0x75, 0xc6, 0x7b, 0xa2, 0x57, 0x2a, 0xff, 0x56, 0x36, 0x9f, 0xd7, 0xec, 0x12, 0xd4, 0x36, 0x7c, 0x24, 0x68, 0x08, 0xc6, 0x6e, 0x7c, 0x5f, 0xb4, 0x43, 0xbd, 0xd6, 0x79, 0xc1, 0x0e, 0x38, 0x34, 0x1c, 0x3b, 0x2c, 0x38, 0x20, 0xf6, 0x89, 0xe0, 0xe4, 0xd8, 0xe1, 0x6e, 0x8f, 0x70, 0x7b, 0x5c, 0xf0, 0xf9, 0xd8, 0x69, 0xc1, 0x05, 0xb1, 0xd3, 0x83, 0x9f, 0xc5, 0xce, 0x08, 0xee, 0x8b, 0x9d, 0x19, 0xc4, 0x62, 0x17, 0x04, 0xb5, 0x95, 0xf7, 0x9e, 0xba, 0x48, 0x6e, 0xb5, 0x7a, 0x79, 0x2c, 0x5a, 0x57, 0x7b, 0x57, 0x70, 0x47, 0xed, 0x3d, 0xc1, 0x2f, 0x6b, 0x7f, 0x1c, 0xfc, 0xa2, 0xee, 0x80, 0xe0, 0xdc, 0xba, 0x03, 0x65, 0x7e, 0x94, 0x78, 0xd7, 0xf7, 0x21, 0x79, 0x87, 0xf7, 0x1e, 0xb1, 0x67, 0xef, 0x6b, 0xd8, 0x35, 0xd8, 0xa7, 0x61, 0xb7, 0x60, 0xff, 0xea, 0xfb, 0x57, 0x6c, 0x6e, 0x4f, 0x77, 0x0e, 0x76, 0x0a, 0x1a, 0xa3, 0x81, 0x60, 0x4b, 0x29, 0xbf, 0x7f, 0xfe, 0x5e, 0xd1, 0xeb, 0x95, 0x3d, 0xbe, 0x30, 0xea, 0x8d, 0x8d, 0x8f, 0x16, 0xc7, 0x26, 0x70, 0x83, 0xd3, 0xac, 0xcb, 0xde, 0xba, 0x7a, 0xd9, 0xee, 0xd8, 0xdf, 0xa8, 0xf9, 0x6a, 0x34, 0x60, 0xaf, 0x7a, 0x2a, 0xc7, 0x7e, 0x57, 0xb4, 0xcc, 0xf1, 0x2f, 0x72, 0xfc, 0xcb, 0xea, 0x76, 0x89, 0x06, 0xea, 0x56, 0x4a, 0x57, 0x34, 0x30, 0xc6, 0x63, 0xc6, 0x5c, 0x27, 0xdf, 0x96, 0x81, 0x68, 0xa0, 0xfe, 0x90, 0x68, 0xa0, 0xe9, 0xce, 0x68, 0x55, 0xd3, 0x94, 0x68, 0x55, 0x6c, 0x16, 0xd7, 0x28, 0x71, 0x8d, 0x12, 0xd7, 0x28, 0x55, 0x7f, 0x07, 0x2c, 0xc1, 0x35, 0x12, 0x5c, 0x23, 0xc1, 0x35, 0x12, 0x5c, 0x23, 0xc1, 0x35, 0x12, 0x5c, 0x23, 0xcd, 0x35, 0xd2, 0x5c, 0x23, 0xcd, 0x35, 0x12, 0x5c, 0x23, 0xf1, 0x3f, 0xfe, 0x5e, 0x71, 0x6f, 0xad, 0xc0, 0xd7, 0xbc, 0xed, 0x77, 0xc0, 0x86, 0xb9, 0xc6, 0x70, 0x79, 0x8c, 0xb9, 0xc6, 0x30, 0xd7, 0x18, 0xae, 0xfe, 0x0e, 0x58, 0xb8, 0xc9, 0xef, 0x80, 0x6d, 0xea, 0x1a, 0x09, 0xae, 0x91, 0xe0, 0x1a, 0x89, 0xaa, 0x6b, 0x94, 0x7f, 0x07, 0x2c, 0x5d, 0x75, 0x8d, 0x1c, 0xd7, 0xc8, 0x71, 0x8d, 0x1c, 0xd7, 0xc8, 0x55, 0x7f, 0x07, 0x6c, 0xd9, 0x3f, 0x71, 0x4d, 0x3a, 0xc5, 0x35, 0x52, 0xd5, 0x6b, 0xd2, 0xcb, 0x78, 0xc6, 0x0b, 0x1c, 0x22, 0xc1, 0x21, 0x52, 0x9b, 0xfc, 0xde, 0x57, 0x82, 0x43, 0xa4, 0xab, 0x0e, 0x51, 0xfc, 0xbb, 0x6b, 0xcd, 0x6f, 0xad, 0xa8, 0xd3, 0xdc, 0x21, 0xc7, 0x1d, 0xd2, 0xdc, 0xe1, 0x85, 0x8d, 0xbf, 0xef, 0xf5, 0x8f, 0xbf, 0x6f, 0xd9, 0x86, 0xeb, 0xcb, 0x6b, 0x36, 0xf9, 0x5d, 0xaf, 0xea, 0xfb, 0x39, 0x45, 0xc3, 0xef, 0xf8, 0x5e, 0x4e, 0xab, 0xa2, 0x04, 0x5f, 0x48, 0xf3, 0x85, 0x54, 0xd5, 0x17, 0x8a, 0xd5, 0xeb, 0xca, 0x09, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0x7a, 0xdb, 0xef, 0x62, 0x25, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x9a, 0x2f, 0xa4, 0xf9, 0x42, 0x9a, 0x2f, 0xa4, 0xf9, 0x42, 0x9a, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0xa2, 0xf2, 0x3e, 0x38, 0xff, 0x09, 0xef, 0x81, 0xf3, 0x1f, 0xac, 0xa0, 0xd7, 0xf0, 0x85, 0x35, 0x7c, 0x61, 0x0d, 0x5f, 0x58, 0xc3, 0x17, 0xd6, 0xf0, 0x85, 0x35, 0x7c, 0x61, 0xcd, 0xbf, 0xf3, 0xbb, 0x58, 0xc3, 0x7c, 0x61, 0x98, 0x2f, 0x0c, 0xf3, 0x85, 0x61, 0xbe, 0x30, 0xcc, 0x17, 0x86, 0xf9, 0xc2, 0x30, 0x5f, 0x18, 0xe6, 0x0b, 0xc3, 0x7c, 0x61, 0x98, 0x2f, 0x0c, 0xf3, 0x85, 0x61, 0xbe, 0x30, 0xcc, 0x17, 0x86, 0xf9, 0xc2, 0x30, 0x5f, 0x18, 0xe6, 0x0b, 0xc3, 0xff, 0x0f, 0xbf, 0x8b, 0xf5, 0x7f, 0xe3, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x89, 0x7f, 0xd2, 0x17, 0x72, 0x7c, 0x21, 0xc7, 0x17, 0x72, 0x7c, 0x21, 0xc7, 0x17, 0x72, 0x7c, 0x21, 0xc7, 0x17, 0x72, 0x7c, 0x21, 0xc7, 0x17, 0x72, 0xff, 0xcb, 0xaf, 0x2b, 0xa7, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x6a, 0x93, 0xeb, 0xca, 0xef, 0xfe, 0xde, 0x37, 0x6b, 0xb8, 0x42, 0xb7, 0x24, 0x25, 0x25, 0x3d, 0x92, 0x91, 0xac, 0x84, 0x92, 0x93, 0x7c, 0xf4, 0x42, 0xd5, 0x07, 0x12, 0x7c, 0x20, 0xc1, 0x07, 0x12, 0x7c, 0x20, 0xd1, 0x58, 0x5e, 0x39, 0x0f, 0x45, 0x29, 0x3e, 0x90, 0xe2, 0x03, 0x29, 0x3e, 0x90, 0xe2, 0x03, 0x29, 0x3e, 0x90, 0x0e, 0x76, 0x09, 0x6a, 0xa2, 0x36, 0x6c, 0xbc, 0x57, 0xc7, 0x8f, 0x74, 0xfa, 0xdf, 0xeb, 0xcc, 0xcd, 0x18, 0x39, 0x31, 0x76, 0x70, 0x94, 0x8f, 0x59, 0x0d, 0x63, 0xe5, 0xaf, 0x62, 0xbd, 0x56, 0x6c, 0x05, 0x59, 0x17, 0x25, 0x6b, 0x3e, 0x85, 0x95, 0xb7, 0x46, 0x2b, 0xb1, 0xf2, 0x41, 0xac, 0xfc, 0x39, 0x62, 0xaf, 0xd4, 0x99, 0x56, 0x9b, 0xed, 0x2b, 0x1a, 0x76, 0x0a, 0x76, 0x37, 0x3b, 0x5e, 0x45, 0xe5, 0x7d, 0x50, 0x79, 0x47, 0x54, 0xde, 0x11, 0x95, 0x47, 0xa3, 0xf2, 0x56, 0x0d, 0x7b, 0x07, 0xfb, 0x21, 0xf3, 0xbe, 0x2a, 0x6e, 0x99, 0x4a, 0xf9, 0x53, 0xb0, 0x7b, 0xf0, 0x21, 0x6b, 0xf2, 0x43, 0xa3, 0x7e, 0x54, 0x2e, 0x72, 0x88, 0xed, 0x38, 0xc4, 0xfe, 0x1c, 0x62, 0x3b, 0x0e, 0xb1, 0x3f, 0x87, 0x38, 0x9c, 0x43, 0x1c, 0xc7, 0x21, 0x2e, 0xe7, 0x10, 0xdf, 0xae, 0x39, 0xbb, 0xf2, 0xf7, 0xbc, 0x3d, 0xb6, 0xbc, 0xd6, 0x96, 0xd3, 0xb6, 0xdc, 0xcf, 0x1f, 0x2e, 0xe3, 0x0f, 0xd7, 0xf0, 0x87, 0x9b, 0xf8, 0xc3, 0x71, 0xfc, 0xe1, 0x38, 0x7b, 0x53, 0xfe, 0x6b, 0x92, 0x9e, 0xfa, 0x1b, 0xe4, 0x3b, 0xf2, 0x5d, 0xb9, 0x59, 0xbe, 0x2f, 0x93, 0xe5, 0x16, 0x61, 0x39, 0xf6, 0x2e, 0x66, 0xef, 0xea, 0x2b, 0x7f, 0x51, 0x96, 0x8c, 0x1d, 0x1a, 0xad, 0x89, 0x7d, 0x5c, 0x4e, 0x0b, 0x76, 0xb3, 0xc5, 0xbd, 0x6c, 0x71, 0x77, 0xaf, 0xbe, 0x8d, 0x57, 0x7f, 0xaf, 0x57, 0xdf, 0x3a, 0xd8, 0xde, 0x59, 0x7a, 0xc4, 0xbe, 0x3e, 0x65, 0x5f, 0xd7, 0x63, 0xdc, 0xa2, 0xd8, 0x79, 0xd6, 0xb7, 0xe7, 0x5b, 0xd7, 0x5e, 0x10, 0x6c, 0xee, 0x8c, 0x24, 0x18, 0x44, 0x7f, 0xf5, 0xef, 0x8c, 0xef, 0xb2, 0x6f, 0xcb, 0xed, 0xc7, 0xec, 0xfa, 0x99, 0xc1, 0xc1, 0xce, 0x8a, 0x9e, 0x11, 0x1c, 0x65, 0x5b, 0x9b, 0x3b, 0x13, 0x9b, 0x39, 0x13, 0x1f, 0x71, 0x26, 0x26, 0x3a, 0x0b, 0xeb, 0xcc, 0x9f, 0x45, 0x8d, 0x2b, 0x9c, 0xfd, 0xfa, 0xca, 0x5f, 0xac, 0x5e, 0xc8, 0xc1, 0x6e, 0x8d, 0x7a, 0x1d, 0x5d, 0xc6, 0x2b, 0xac, 0xf1, 0x0a, 0xe5, 0xbf, 0x9b, 0x18, 0xd2, 0x3d, 0xde, 0xfc, 0x3b, 0x9b, 0xc9, 0xb3, 0x99, 0x15, 0x9e, 0x91, 0xf7, 0x8c, 0xc2, 0x26, 0x36, 0x53, 0xfe, 0xb7, 0xd8, 0x38, 0x9b, 0x59, 0xf6, 0x36, 0x9b, 0x29, 0xff, 0xe6, 0xd3, 0x2b, 0x6c, 0x66, 0xf1, 0x3f, 0x68, 0x33, 0xab, 0xd9, 0xcc, 0xea, 0xca, 0x5f, 0xdc, 0x2d, 0xac, 0xb8, 0xe2, 0xa1, 0xd1, 0xc3, 0x4c, 0xaf, 0x4e, 0x6d, 0x0c, 0xda, 0xda, 0x31, 0xb6, 0xf6, 0x90, 0x2d, 0x15, 0x62, 0xe7, 0x94, 0x3f, 0x8f, 0xa5, 0x32, 0x1a, 0x43, 0xb6, 0xd2, 0x65, 0x0b, 0xaf, 0xda, 0x42, 0x5f, 0x75, 0xec, 0xb7, 0x32, 0xd6, 0x1f, 0xf6, 0x6a, 0x43, 0x5e, 0x6d, 0x28, 0x18, 0xe5, 0x99, 0x4f, 0x3a, 0xcb, 0xb3, 0x9d, 0xe5, 0xd9, 0xc1, 0xe8, 0xbf, 0xfb, 0x4b, 0xc0, 0xd3, 0x2b, 0xbf, 0x97, 0xd5, 0xe9, 0xb9, 0xeb, 0x82, 0x53, 0x6d, 0xb5, 0xc1, 0x56, 0x87, 0x6c, 0xf5, 0x5c, 0x8e, 0xf2, 0x63, 0x5b, 0x3d, 0x83, 0x6b, 0xf4, 0xab, 0xca, 0xfb, 0x3d, 0xeb, 0x4b, 0xaa, 0xf2, 0x09, 0x55, 0xf9, 0xaf, 0xf6, 0xe2, 0xeb, 0xb1, 0xe3, 0x79, 0xe5, 0x69, 0x5e, 0xed, 0xf4, 0xa0, 0xd1, 0x88, 0x8d, 0xb6, 0x47, 0x7f, 0x88, 0x9d, 0x1b, 0xcd, 0x30, 0x36, 0xf3, 0x8c, 0xcd, 0x3c, 0x63, 0xf3, 0x3e, 0x95, 0x7b, 0xa7, 0xca, 0xbd, 0x43, 0xe5, 0xde, 0x65, 0x9c, 0x5e, 0x77, 0x86, 0x7f, 0x6c, 0x8f, 0x8f, 0xb7, 0xc7, 0x47, 0xd9, 0xea, 0xba, 0xf2, 0x1e, 0xd7, 0xde, 0x1b, 0x3d, 0xef, 0x6c, 0x77, 0x61, 0xf7, 0xbd, 0xd8, 0x7d, 0x2f, 0x2e, 0x5f, 0x85, 0xc7, 0x1f, 0xc5, 0xe3, 0xcf, 0xe1, 0xf1, 0xb1, 0x78, 0xfc, 0x65, 0x0c, 0xde, 0xcf, 0x78, 0xd6, 0x61, 0xee, 0x2d, 0x98, 0x7b, 0x0b, 0xe6, 0xde, 0x82, 0xb9, 0xb7, 0x60, 0xee, 0x97, 0x31, 0xf7, 0x46, 0xec, 0xbc, 0x10, 0x1f, 0x3f, 0x8d, 0x8b, 0xe7, 0xe2, 0xe2, 0x78, 0x4c, 0x3c, 0xda, 0xd8, 0xbf, 0x8e, 0x47, 0xf7, 0x62, 0xd1, 0xf9, 0x18, 0x74, 0x23, 0xde, 0x1c, 0x8d, 0x37, 0xe7, 0xaa, 0x87, 0xcf, 0x98, 0x25, 0x4f, 0x3a, 0x4b, 0x5b, 0xab, 0x8b, 0x0f, 0xa8, 0x8b, 0xbd, 0xd4, 0xc5, 0x89, 0x0d, 0x7b, 0x05, 0x5b, 0xa8, 0x8d, 0xaf, 0xeb, 0xf1, 0xdf, 0xd0, 0xc3, 0x4f, 0xd4, 0xb7, 0xc7, 0xe9, 0xd9, 0x87, 0xaa, 0x95, 0x76, 0x7d, 0xf9, 0x73, 0x66, 0xcd, 0x22, 0xfd, 0xf4, 0x27, 0x7a, 0xe8, 0x51, 0x7a, 0xe8, 0x0c, 0xfd, 0xe8, 0x2b, 0x7a, 0xcd, 0x55, 0xd6, 0x05, 0xb3, 0xd4, 0xd1, 0x02, 0x73, 0xfe, 0xc4, 0xe0, 0x32, 0x67, 0x70, 0x31, 0xf3, 0x1c, 0x61, 0x9e, 0x23, 0xcc, 0x73, 0x84, 0x79, 0x8e, 0x30, 0xcf, 0x11, 0xe6, 0x39, 0xc2, 0x3c, 0x4b, 0xcc, 0xb3, 0xc4, 0x3c, 0x4b, 0x95, 0x99, 0xf7, 0x71, 0xb5, 0x75, 0x98, 0xef, 0x7f, 0xc9, 0xd7, 0x17, 0x56, 0x7e, 0x8b, 0x7f, 0x44, 0x75, 0x8f, 0x30, 0xb8, 0x11, 0xe6, 0x56, 0x7a, 0x6b, 0x9c, 0x83, 0x7a, 0xa6, 0x35, 0xc2, 0xae, 0x46, 0xd8, 0x55, 0xa9, 0x32, 0x03, 0x6f, 0x73, 0x7b, 0x8f, 0x6c, 0x98, 0x89, 0x9e, 0xef, 0x6c, 0xf6, 0x95, 0x67, 0x1f, 0xe3, 0x19, 0x61, 0x3c, 0x25, 0x86, 0x33, 0xc2, 0x2c, 0x46, 0x98, 0xc5, 0x88, 0xb3, 0x33, 0xc2, 0x2c, 0x46, 0x98, 0xc5, 0x08, 0xb3, 0x28, 0x31, 0x8b, 0x12, 0xb3, 0x28, 0x31, 0x8b, 0x12, 0xb3, 0x28, 0x39, 0x73, 0x23, 0xcc, 0x62, 0x84, 0x59, 0x8c, 0x38, 0x83, 0x23, 0xce, 0xe0, 0x08, 0xb3, 0x18, 0x71, 0x16, 0x47, 0x98, 0xc5, 0x08, 0xb3, 0x18, 0x61, 0x16, 0x23, 0xcc, 0x62, 0x84, 0x59, 0x8c, 0x38, 0xb3, 0x23, 0xcc, 0x62, 0x84, 0x59, 0x8c, 0x30, 0x8b, 0x11, 0x66, 0x31, 0xc2, 0x2c, 0x46, 0x98, 0xc5, 0x08, 0xb3, 0x18, 0x61, 0x16, 0x23, 0xcc, 0x62, 0x84, 0x59, 0x8c, 0x30, 0x8b, 0x11, 0xd4, 0x2f, 0xa1, 0x7e, 0x09, 0xf5, 0x4b, 0x46, 0xa1, 0x64, 0x14, 0x4a, 0xa8, 0x5f, 0x42, 0xfd, 0xd2, 0x86, 0x4e, 0xe0, 0x8c, 0x8f, 0xa0, 0xe6, 0x08, 0x6a, 0x8e, 0xa0, 0xe6, 0x88, 0xb3, 0x3f, 0x82, 0x9a, 0x23, 0x46, 0x60, 0x04, 0x35, 0x47, 0x50, 0x73, 0x04, 0x35, 0x47, 0x50, 0x73, 0xa4, 0xfa, 0x09, 0x84, 0x23, 0xa8, 0x39, 0x82, 0x9a, 0x23, 0x46, 0x67, 0x04, 0x35, 0x47, 0x50, 0x73, 0x04, 0x35, 0x47, 0x50, 0x73, 0x04, 0x35, 0x47, 0x50, 0x73, 0x04, 0x35, 0x47, 0x50, 0x73, 0x04, 0x35, 0x47, 0x50, 0x73, 0xc4, 0xac, 0x1f, 0xd1, 0xa1, 0x47, 0x8c, 0xd6, 0x88, 0x0e, 0x3d, 0xa2, 0x43, 0x8f, 0xe8, 0xd0, 0x23, 0xba, 0x71, 0x29, 0x76, 0x67, 0x30, 0x2a, 0x7a, 0x20, 0xa8, 0x95, 0xad, 0xa3, 0xeb, 0x83, 0x3d, 0x8c, 0xca, 0x9e, 0xc2, 0x19, 0xcd, 0xf9, 0x62, 0xb0, 0x4f, 0xd4, 0x1d, 0x7c, 0x30, 0x1a, 0x0c, 0xc6, 0x46, 0x2f, 0xe9, 0x9d, 0x1d, 0xc1, 0xbe, 0xd1, 0x4a, 0x6b, 0x8e, 0xb8, 0x35, 0x47, 0xdc, 0x9a, 0x23, 0x6e, 0xcd, 0x91, 0x36, 0xf2, 0xad, 0x46, 0xbe, 0xd5, 0xc8, 0xb7, 0x1a, 0xf9, 0x56, 0x23, 0xdf, 0x6a, 0xe4, 0x5b, 0x8d, 0x7c, 0xdc, 0xc8, 0xc7, 0x8d, 0x7c, 0xdc, 0xa8, 0xb7, 0x1a, 0xf5, 0x56, 0xce, 0xbf, 0xd4, 0xc8, 0xb7, 0x71, 0xfe, 0x34, 0xe7, 0x4f, 0x23, 0x40, 0x9a, 0xf3, 0xa7, 0x38, 0x7f, 0xca, 0xbc, 0x4b, 0x71, 0xfe, 0x14, 0xe7, 0x4f, 0x71, 0xfe, 0x34, 0xe7, 0xef, 0x32, 0x0f, 0xd3, 0x9c, 0x3f, 0xcd, 0xf9, 0xbb, 0x38, 0x7f, 0x17, 0xe7, 0xef, 0x52, 0x31, 0xad, 0x2a, 0xa6, 0x55, 0xc5, 0xb4, 0x72, 0xfe, 0x2e, 0xce, 0x9f, 0x56, 0x39, 0x71, 0xce, 0xdf, 0xc5, 0xf9, 0x57, 0x70, 0xfe, 0x15, 0x9c, 0x7f, 0x05, 0xe7, 0x5f, 0x61, 0x0e, 0xa7, 0x37, 0x7a, 0xfc, 0x0f, 0xcc, 0xf1, 0x3b, 0x10, 0x66, 0x7c, 0x34, 0x49, 0x27, 0xb8, 0x41, 0x27, 0xb8, 0x21, 0x76, 0xa4, 0xae, 0x70, 0x94, 0x4c, 0x30, 0x8f, 0x4f, 0xab, 0xfc, 0xe5, 0xe6, 0xd2, 0xca, 0xdf, 0x2a, 0xa7, 0xa3, 0xd6, 0x8a, 0xe7, 0x9f, 0x1d, 0x75, 0xd4, 0x7c, 0x1e, 0x79, 0x2e, 0x90, 0x8b, 0xdc, 0xbf, 0xc4, 0xed, 0xe5, 0x2a, 0xf0, 0x0a, 0xb9, 0x2a, 0x6a, 0x55, 0x9d, 0x71, 0xee, 0xdf, 0xc5, 0xfd, 0xd3, 0xdc, 0xbf, 0xab, 0xe6, 0x26, 0x1d, 0xf5, 0x96, 0x68, 0xa9, 0x6a, 0x8d, 0xd7, 0xfc, 0xc0, 0xfd, 0x69, 0xd1, 0x0a, 0x55, 0x1b, 0xe7, 0xfd, 0x69, 0xde, 0x9f, 0xae, 0xf9, 0x99, 0xde, 0xe8, 0xf8, 0xb9, 0x7c, 0x5a, 0x8f, 0x0c, 0x37, 0xf6, 0xc8, 0x6d, 0xf8, 0x3a, 0xae, 0xab, 0xe4, 0x56, 0x95, 0x1c, 0xaf, 0xba, 0x7b, 0x17, 0x77, 0x4f, 0xab, 0xea, 0xd6, 0x31, 0x1f, 0x8a, 0x1e, 0x18, 0xb3, 0xaf, 0x7c, 0x58, 0xf6, 0x93, 0xfd, 0x65, 0x9c, 0x7c, 0x44, 0x3e, 0x2a, 0x1f, 0x93, 0xf1, 0x72, 0xb0, 0x1c, 0x22, 0x87, 0xca, 0xc7, 0xe5, 0x30, 0xf9, 0x84, 0x1c, 0x2e, 0x47, 0xc8, 0x91, 0x72, 0x94, 0x1c, 0x2d, 0xc7, 0xc8, 0xb1, 0xf2, 0x49, 0x99, 0x20, 0xc7, 0xc9, 0xa7, 0xe4, 0x3c, 0xe9, 0x89, 0xae, 0x1f, 0x93, 0x8d, 0xae, 0xaf, 0x9f, 0x60, 0xa6, 0x1c, 0x27, 0x9f, 0x92, 0xe3, 0xe5, 0x44, 0x39, 0x49, 0x4e, 0x96, 0x89, 0x72, 0x8a, 0x9c, 0xca, 0xe7, 0x4f, 0x93, 0xcf, 0xc8, 0x99, 0xf2, 0x59, 0xf9, 0x9c, 0x9c, 0x25, 0x97, 0x47, 0xdd, 0xf5, 0x57, 0xc8, 0x95, 0xd1, 0x60, 0xfd, 0x55, 0x32, 0x49, 0xbe, 0x29, 0x57, 0xcb, 0x35, 0x72, 0x5d, 0xf4, 0x52, 0xfd, 0xb7, 0xe5, 0xfa, 0xa8, 0x03, 0x3f, 0x3b, 0xf0, 0xb3, 0x03, 0x3f, 0x3b, 0xf0, 0xb3, 0x03, 0x3f, 0x3b, 0xf0, 0xb3, 0x03, 0x3f, 0x3b, 0xf0, 0xb3, 0xa3, 0xfe, 0x2e, 0xd4, 0xbf, 0x5b, 0xee, 0x91, 0x1f, 0xcb, 0x4f, 0xe4, 0x5e, 0xb9, 0x2f, 0x8a, 0x5b, 0xd3, 0xc4, 0xad, 0x69, 0xe2, 0x7a, 0x68, 0x5c, 0x0f, 0x8d, 0xeb, 0xa1, 0x71, 0x3d, 0x34, 0x6e, 0x4d, 0x13, 0xb7, 0xa6, 0x89, 0x5b, 0xd3, 0xc4, 0xf5, 0xd3, 0xb8, 0x35, 0x4d, 0x9c, 0x39, 0xc4, 0xad, 0x69, 0xe2, 0xd6, 0x34, 0x71, 0x6b, 0x9a, 0xb8, 0xfe, 0x1a, 0xd7, 0x5f, 0xe3, 0xfa, 0x6b, 0x5c, 0x7f, 0x8d, 0xeb, 0xaf, 0x71, 0xfd, 0x35, 0x6e, 0x4d, 0x13, 0xb7, 0xa6, 0x89, 0x5b, 0xd3, 0xc4, 0xad, 0x69, 0xe2, 0xd6, 0x34, 0x71, 0x6b, 0x9a, 0xb8, 0x35, 0x4d, 0xdc, 0x9a, 0x26, 0x6e, 0x4d, 0x13, 0xb7, 0xa6, 0x49, 0x5b, 0xd3, 0xa4, 0xad, 0x69, 0xd2, 0x3a, 0x4f, 0xab, 0xce, 0xd3, 0xaa, 0xf3, 0xb4, 0xea, 0x3c, 0xad, 0x3a, 0x4f, 0xab, 0xce, 0x13, 0xd7, 0x79, 0xe2, 0x3a, 0x4f, 0x5c, 0xe7, 0x89, 0xeb, 0x3c, 0x71, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x75, 0x9e, 0x56, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x75, 0x9e, 0x56, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x75, 0x9e, 0x56, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x75, 0x9e, 0x56, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x75, 0x9e, 0x56, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x2b, 0x9f, 0xef, 0x1a, 0x45, 0x4b, 0xf5, 0xff, 0xa5, 0xd6, 0x22, 0x4b, 0xad, 0x45, 0x96, 0xe2, 0xc0, 0x52, 0x6b, 0x91, 0xa5, 0xd6, 0x22, 0x4b, 0x75, 0xa5, 0x36, 0x5d, 0xa9, 0x4d, 0x57, 0x6a, 0xd3, 0x95, 0xda, 0x74, 0xa5, 0x36, 0x5d, 0xa9, 0x4d, 0x57, 0x6a, 0xb3, 0x16, 0x49, 0x5b, 0x8b, 0xa4, 0xad, 0x45, 0xd2, 0xd6, 0x22, 0x69, 0x6b, 0x91, 0xb4, 0xb5, 0x48, 0xda, 0x5a, 0x24, 0x65, 0x2d, 0x92, 0xb2, 0x16, 0x49, 0x59, 0x8b, 0xa4, 0x70, 0x24, 0xc5, 0x12, 0x52, 0xd6, 0x22, 0x29, 0x6b, 0x91, 0x94, 0xb5, 0x48, 0xca, 0x5a, 0x24, 0x65, 0x2d, 0x92, 0xb2, 0x16, 0x49, 0x59, 0x8b, 0xa4, 0xac, 0x45, 0x52, 0xd6, 0x22, 0x29, 0x6b, 0x91, 0x14, 0x9b, 0x48, 0x59, 0x8b, 0xa4, 0xad, 0x45, 0xd2, 0xd6, 0x22, 0x69, 0x6b, 0x91, 0xb4, 0xb5, 0x48, 0xda, 0x5a, 0x24, 0x6d, 0x2d, 0x92, 0xb6, 0x16, 0x49, 0x5b, 0x8b, 0xa4, 0xad, 0x45, 0xd2, 0xd6, 0x22, 0x69, 0x6b, 0x91, 0xb4, 0xb5, 0x48, 0xda, 0x5a, 0x24, 0x6d, 0x2d, 0xd2, 0x65, 0x2d, 0xd2, 0x65, 0x2d, 0xd2, 0x65, 0x2d, 0xd2, 0x85, 0x53, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0x5a, 0x8b, 0x74, 0x59, 0x8b, 0x74, 0x59, 0x8b, 0x74, 0x61, 0x65, 0x97, 0xb5, 0x48, 0x97, 0xb5, 0x48, 0x97, 0xb5, 0x48, 0x97, 0xb5, 0x48, 0x17, 0x76, 0x76, 0x59, 0x8b, 0x74, 0x59, 0x8b, 0x74, 0x59, 0x8b, 0xac, 0xc0, 0xd1, 0x15, 0x38, 0xba, 0xc2, 0x5a, 0x64, 0x85, 0xb5, 0xc8, 0x0a, 0x6b, 0x91, 0x15, 0xd6, 0x22, 0x2b, 0xac, 0x45, 0x56, 0x58, 0x8b, 0xac, 0xf8, 0x2f, 0xf3, 0xfd, 0x55, 0xac, 0x6e, 0xb5, 0xf4, 0x45, 0x79, 0xec, 0xce, 0x37, 0x0c, 0xc8, 0xa0, 0xbc, 0x21, 0xeb, 0xa3, 0x3c, 0x87, 0xef, 0x42, 0x88, 0x56, 0x84, 0x68, 0x45, 0x88, 0x56, 0x84, 0x68, 0x45, 0x88, 0xd6, 0x77, 0x71, 0xf8, 0x78, 0x50, 0x5b, 0x7d, 0xa7, 0x87, 0x82, 0x2e, 0x5a, 0x28, 0x5b, 0x4d, 0xac, 0x19, 0x47, 0x96, 0xe0, 0xc8, 0x92, 0x60, 0x4c, 0xf4, 0x46, 0x50, 0x2f, 0x9b, 0xcb, 0xd6, 0xd1, 0x5c, 0x6e, 0x55, 0x0a, 0xfe, 0xc5, 0xfd, 0x9d, 0x64, 0x67, 0xd9, 0x4d, 0xf6, 0xc0, 0x94, 0x3d, 0xe5, 0x83, 0xfc, 0x72, 0x6c, 0xd4, 0x87, 0x2b, 0x03, 0xb8, 0xd2, 0x8c, 0x2b, 0xcd, 0xb8, 0xd2, 0x8c, 0x2b, 0xdd, 0x6c, 0x61, 0x69, 0xf0, 0x89, 0x28, 0x17, 0x1c, 0x2e, 0x47, 0xc8, 0x91, 0x72, 0x94, 0x1c, 0x2d, 0xc7, 0xc8, 0xb1, 0xf2, 0x49, 0x99, 0x20, 0xc7, 0xc9, 0xa7, 0xe4, 0x78, 0x39, 0x41, 0x4e, 0x94, 0x93, 0xe4, 0x64, 0x99, 0x28, 0xa7, 0xc8, 0xa9, 0x72, 0x9a, 0x7c, 0x5a, 0x4e, 0x97, 0xcf, 0x48, 0xf9, 0x1d, 0x39, 0xce, 0x94, 0xcf, 0xca, 0xe7, 0xe4, 0x2c, 0x39, 0x5b, 0x3e, 0x2f, 0x5f, 0xa8, 0xfc, 0x3b, 0x58, 0x37, 0x4e, 0x75, 0xe3, 0x54, 0x37, 0x4e, 0xad, 0xc1, 0xa9, 0x35, 0x38, 0xb5, 0x8a, 0xb9, 0xf4, 0x60, 0xd5, 0x1a, 0xac, 0x5a, 0x83, 0x55, 0xdd, 0x58, 0xd5, 0x89, 0x55, 0xdd, 0x58, 0xd5, 0x8d, 0x55, 0x9d, 0x58, 0xd5, 0x89, 0x55, 0x9d, 0xf8, 0xd4, 0x89, 0x4f, 0xdd, 0xd8, 0xd4, 0x89, 0x45, 0xab, 0x82, 0x6f, 0x7b, 0xcd, 0xeb, 0x1d, 0xff, 0x0d, 0xf2, 0x1d, 0xf9, 0xae, 0xdc, 0x28, 0x37, 0xc9, 0xf7, 0xe4, 0x66, 0xf9, 0xbe, 0x4c, 0x96, 0x5b, 0x3c, 0xe7, 0x56, 0xb9, 0xcd, 0xb6, 0x6e, 0x8f, 0x7a, 0xf1, 0x6b, 0x55, 0xf0, 0x43, 0xf7, 0xef, 0xb0, 0x0e, 0x28, 0x3b, 0xb8, 0xf3, 0xe3, 0xec, 0x2f, 0xc5, 0xb0, 0x4e, 0x0c, 0xeb, 0xc4, 0xb0, 0xd7, 0x63, 0xc9, 0xe8, 0x0d, 0xdc, 0xea, 0xac, 0x39, 0x96, 0x8f, 0x9f, 0x28, 0xa7, 0xc9, 0xe7, 0x79, 0xf9, 0x39, 0x6e, 0x2f, 0x70, 0x7b, 0x89, 0xe8, 0xc9, 0xf8, 0xd5, 0x8d, 0x5b, 0x9d, 0xb8, 0xd5, 0x8d, 0x5b, 0x9d, 0x35, 0xf6, 0xa5, 0xc6, 0xbe, 0xe0, 0x57, 0x07, 0xdb, 0x1a, 0xc0, 0xae, 0x8e, 0x9a, 0x1f, 0x71, 0xf9, 0xe9, 0x7e, 0xfe, 0x53, 0xf9, 0x99, 0xaf, 0x1f, 0x8c, 0x72, 0x35, 0xbf, 0x94, 0x19, 0xf2, 0x2b, 0xf9, 0xb5, 0xcc, 0x94, 0x87, 0xe4, 0x37, 0xf2, 0x5b, 0x79, 0x58, 0x7e, 0xe7, 0x75, 0x1e, 0x91, 0xc7, 0xa2, 0xd7, 0x98, 0xda, 0x32, 0xac, 0xeb, 0x2e, 0x57, 0x08, 0xc6, 0x75, 0xd7, 0x3d, 0x1b, 0xe5, 0xea, 0xe6, 0xca, 0xbc, 0xca, 0x6a, 0x63, 0xa0, 0x6e, 0x91, 0xfb, 0x8b, 0x25, 0x2e, 0xad, 0xb2, 0x44, 0x5e, 0x8f, 0xde, 0xc0, 0xbf, 0x4e, 0xfc, 0xeb, 0xc4, 0xbf, 0xee, 0xd1, 0x7b, 0x47, 0x6f, 0xe0, 0xdf, 0x12, 0xfc, 0x5b, 0x82, 0x7f, 0x4b, 0xf0, 0x6f, 0x09, 0xfe, 0x2d, 0xc1, 0xbf, 0x25, 0xf8, 0xb7, 0x04, 0xff, 0x96, 0xe0, 0xdf, 0x12, 0xfc, 0x5b, 0x82, 0x7f, 0x4b, 0xf0, 0x6f, 0x09, 0xfe, 0x2d, 0xc1, 0xbf, 0x25, 0xf8, 0xb7, 0x04, 0xff, 0x96, 0xe0, 0xdf, 0x12, 0xfc, 0x5b, 0x82, 0x7f, 0x4b, 0xf0, 0x6f, 0x09, 0xfe, 0x2d, 0xc1, 0xbf, 0x25, 0xf8, 0xb7, 0x04, 0xff, 0x96, 0xe0, 0xdf, 0x12, 0xfc, 0x5b, 0x82, 0x7f, 0x4b, 0xf0, 0x6f, 0xc9, 0x98, 0xaf, 0xdb, 0xe6, 0x25, 0xf2, 0x0d, 0xb9, 0x54, 0xae, 0x94, 0x5f, 0xcb, 0x4c, 0x79, 0x48, 0x7e, 0x23, 0xbf, 0x95, 0x87, 0xc5, 0xf1, 0xe2, 0xe5, 0x5c, 0xbc, 0x9c, 0x5b, 0xbf, 0x59, 0xf4, 0x46, 0xbd, 0xba, 0xaf, 0xdf, 0x42, 0xb6, 0x94, 0xad, 0xe4, 0xbd, 0xb2, 0xb5, 0x6c, 0x23, 0xef, 0x93, 0x1d, 0x64, 0x47, 0x31, 0x27, 0xea, 0xcd, 0x89, 0x7a, 0x73, 0xa2, 0x7e, 0x17, 0xd9, 0x55, 0xcc, 0x8d, 0xfa, 0xdd, 0x65, 0x0f, 0xd9, 0x53, 0xde, 0x2f, 0x7b, 0x89, 0x73, 0x50, 0xff, 0x01, 0xd9, 0x47, 0x3e, 0x28, 0x63, 0xe5, 0x00, 0x99, 0x80, 0xa1, 0xc7, 0xc9, 0xa7, 0xe4, 0x78, 0x39, 0x51, 0x4e, 0x92, 0x93, 0x65, 0xa2, 0x9c, 0x22, 0x57, 0x5a, 0x67, 0x5d, 0x25, 0x93, 0xe4, 0x9b, 0x72, 0xb5, 0x5c, 0x23, 0xd7, 0x45, 0x7d, 0xd8, 0xdb, 0x87, 0xab, 0x03, 0xb8, 0x3a, 0x80, 0xab, 0x03, 0xb8, 0x3a, 0x80, 0xab, 0x03, 0xb8, 0x3a, 0x80, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xab, 0x70, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x1b, 0x57, 0xbb, 0x71, 0xb5, 0xbb, 0x3e, 0x5b, 0xf9, 0xc4, 0xaf, 0x5c, 0x7d, 0x5e, 0x7a, 0xa5, 0x20, 0xeb, 0xa4, 0x4f, 0xfa, 0x65, 0x40, 0x06, 0xe5, 0x0d, 0x59, 0x2f, 0x43, 0x52, 0x94, 0x61, 0x29, 0xc9, 0x88, 0xbc, 0x59, 0xf9, 0xb7, 0xb9, 0x6e, 0x7c, 0xeb, 0xc6, 0xb7, 0x6e, 0x7c, 0xeb, 0xc6, 0xb7, 0x6e, 0x7c, 0xeb, 0xc6, 0xb7, 0x35, 0xf8, 0xb6, 0x06, 0xdf, 0xd6, 0xe0, 0xdb, 0x1a, 0x7c, 0x5b, 0x85, 0x6f, 0x11, 0xbe, 0xad, 0xc1, 0xb7, 0x35, 0xf8, 0xb6, 0x06, 0xdf, 0xd6, 0xe0, 0xdb, 0x1a, 0x7c, 0x7b, 0x13, 0xdf, 0xd6, 0xe0, 0xdb, 0x1a, 0x7c, 0x5b, 0x83, 0x6f, 0x6b, 0xac, 0x3a, 0x6b, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0x73, 0x3a, 0x31, 0xa7, 0x13, 0x73, 0x3a, 0x31, 0xa7, 0x13, 0x73, 0x3a, 0x31, 0xa7, 0x13, 0x73, 0x3a, 0x31, 0xa7, 0x13, 0x73, 0x3a, 0x31, 0xa7, 0x13, 0x73, 0x3a, 0x1b, 0xee, 0x8d, 0xde, 0x68, 0xb8, 0x4f, 0xa6, 0xcb, 0x4f, 0xe5, 0x67, 0xf2, 0x73, 0xb9, 0x5f, 0x1e, 0x90, 0x5f, 0xc8, 0x83, 0xf2, 0x4b, 0x99, 0x21, 0xbf, 0x12, 0x75, 0xde, 0xa0, 0xce, 0x1b, 0xd4, 0x79, 0x83, 0x3a, 0x6f, 0x50, 0xe7, 0x0d, 0xea, 0xbc, 0x41, 0x9d, 0x37, 0x98, 0xd7, 0x0d, 0x8f, 0xca, 0x2c, 0xf9, 0xbd, 0xfc, 0x41, 0x1e, 0x93, 0xc7, 0xe5, 0x8f, 0xf2, 0xaf, 0xf2, 0x27, 0x79, 0x42, 0xfe, 0x2c, 0x4f, 0xca, 0x53, 0xf2, 0xb4, 0x3c, 0x23, 0x7f, 0x91, 0xd9, 0x32, 0x47, 0xfe, 0x6a, 0xff, 0x9e, 0x95, 0xb9, 0xf2, 0x37, 0x79, 0x4e, 0xe6, 0xc9, 0x7c, 0x79, 0x5e, 0x5e, 0x90, 0x66, 0x59, 0x28, 0x2d, 0xf2, 0xa2, 0xbc, 0x24, 0x8b, 0x64, 0xb1, 0xc4, 0xa5, 0x55, 0x96, 0x48, 0x9b, 0xbc, 0x2c, 0xed, 0xd2, 0x11, 0xf5, 0x34, 0x2c, 0x93, 0xce, 0xa8, 0xb7, 0xe1, 0x55, 0x59, 0x2e, 0xaf, 0xc9, 0xeb, 0xb2, 0x42, 0x56, 0xca, 0x2a, 0x63, 0xbc, 0x5a, 0xd6, 0x78, 0x5c, 0xb7, 0x24, 0x25, 0x25, 0x3d, 0x92, 0x91, 0xac, 0x84, 0x92, 0x93, 0xbc, 0xf4, 0x45, 0x1d, 0xb8, 0xd9, 0x81, 0x9b, 0x1d, 0xb8, 0xd9, 0x81, 0x9b, 0x1d, 0xb8, 0xd9, 0x81, 0x9b, 0x9d, 0x8d, 0x3d, 0xd1, 0x1b, 0x8d, 0x19, 0xc9, 0x4a, 0x28, 0x39, 0x19, 0xf2, 0xfd, 0xa2, 0x0c, 0x4b, 0x49, 0x46, 0xf4, 0xe7, 0x19, 0xd6, 0xc9, 0xe7, 0x54, 0xae, 0x34, 0x7c, 0x38, 0x3a, 0x05, 0xe9, 0x4e, 0x41, 0xba, 0x53, 0xac, 0x9e, 0xc6, 0x5a, 0x3d, 0x8d, 0xb5, 0x7a, 0x1a, 0x6b, 0xf5, 0x34, 0xd6, 0xea, 0x69, 0xac, 0xd5, 0xd3, 0x58, 0xab, 0xa7, 0xf1, 0x56, 0x4f, 0xe3, 0xad, 0x9e, 0xc6, 0x57, 0xae, 0x4c, 0x7c, 0xdc, 0xf7, 0x0e, 0x93, 0x4f, 0x44, 0x09, 0x44, 0x4c, 0x20, 0x62, 0x02, 0x11, 0x13, 0x88, 0x98, 0x40, 0xc4, 0x04, 0x22, 0x26, 0x10, 0x31, 0x81, 0x88, 0x09, 0x44, 0x4c, 0x20, 0x62, 0x02, 0x11, 0x13, 0x88, 0x98, 0x40, 0xc4, 0x04, 0x22, 0x26, 0x10, 0x31, 0x81, 0x88, 0x09, 0x44, 0x4c, 0x20, 0x62, 0x02, 0x11, 0x13, 0x88, 0x98, 0x40, 0xc4, 0x04, 0x22, 0x26, 0x10, 0x31, 0x81, 0x88, 0x09, 0x44, 0x4c, 0x20, 0x62, 0x02, 0x11, 0x13, 0x95, 0x2b, 0x22, 0x67, 0xbb, 0xfd, 0xbc, 0x7c, 0x41, 0xbe, 0x18, 0x9d, 0x6a, 0x05, 0x77, 0x48, 0xe5, 0x2a, 0xc9, 0xd7, 0xac, 0x1a, 0xcb, 0x57, 0x4a, 0x2e, 0x71, 0xfb, 0x0d, 0xb9, 0x54, 0x2e, 0xb3, 0x8f, 0x97, 0xcb, 0x15, 0x72, 0xa5, 0xaf, 0x27, 0xd9, 0xff, 0x6f, 0xba, 0xbd, 0xda, 0xe3, 0xaf, 0x91, 0x6b, 0xe5, 0x5b, 0x72, 0x5d, 0x74, 0x3f, 0x1a, 0x26, 0xd0, 0x30, 0x87, 0x86, 0x39, 0x34, 0xcc, 0xa1, 0x61, 0x0e, 0x0d, 0x73, 0x68, 0x98, 0x43, 0xc3, 0x1c, 0x1a, 0xe6, 0xd0, 0x30, 0x87, 0x86, 0x39, 0x34, 0x9c, 0x81, 0x86, 0x33, 0xd0, 0x30, 0x87, 0x86, 0x79, 0x34, 0xec, 0xad, 0xfe, 0x55, 0xfe, 0xd3, 0xef, 0x72, 0x95, 0x26, 0x16, 0x3b, 0x5d, 0xce, 0x90, 0x7f, 0xbb, 0x4a, 0xb3, 0x28, 0x76, 0x3e, 0x72, 0x5e, 0x10, 0xd4, 0x58, 0xe1, 0x8d, 0x45, 0xca, 0x19, 0xff, 0xc7, 0xd5, 0x9a, 0xab, 0xa2, 0xb1, 0x56, 0x75, 0xe3, 0xd1, 0xf1, 0x01, 0x64, 0x7c, 0x00, 0x19, 0x73, 0xc8, 0x98, 0x43, 0xc6, 0xa7, 0xad, 0xec, 0x4e, 0xad, 0x5c, 0xcd, 0xb9, 0xcd, 0xcf, 0x7f, 0xe0, 0xeb, 0x69, 0xd1, 0x19, 0x56, 0x77, 0xe3, 0x2b, 0x7f, 0xc9, 0xff, 0x33, 0x5f, 0x3f, 0x18, 0x25, 0x10, 0x32, 0x81, 0x90, 0x09, 0x84, 0x4c, 0x20, 0x64, 0x02, 0x21, 0x13, 0x08, 0x99, 0x40, 0xc8, 0x04, 0x42, 0x26, 0x10, 0x32, 0x81, 0x90, 0x39, 0x84, 0xcc, 0x6d, 0xbc, 0x2a, 0xe4, 0x7c, 0xfe, 0xdd, 0x95, 0xa1, 0x67, 0xa3, 0x04, 0x4a, 0x26, 0x2a, 0x57, 0x88, 0x16, 0xb9, 0xad, 0x5e, 0x25, 0x42, 0xc8, 0x04, 0x42, 0x26, 0xac, 0x12, 0xc7, 0x5a, 0x25, 0x8e, 0x47, 0xc9, 0x19, 0x28, 0xf9, 0x00, 0x42, 0xe6, 0xac, 0x12, 0xc7, 0x56, 0xae, 0x22, 0x4d, 0x8f, 0x4e, 0xd1, 0xb1, 0x4f, 0xf9, 0x3f, 0xae, 0x26, 0xfd, 0xc2, 0xf7, 0x1e, 0x94, 0x19, 0x52, 0xbe, 0xb2, 0xf4, 0x6b, 0xb7, 0xe5, 0xab, 0x4b, 0x0f, 0xb9, 0xfd, 0x8d, 0xfc, 0x56, 0xde, 0xed, 0x4a, 0xd3, 0x1f, 0xfc, 0xec, 0x31, 0x79, 0x5c, 0xfe, 0x28, 0x7f, 0x96, 0x27, 0xe5, 0x29, 0x79, 0x5a, 0x9e, 0x91, 0xb9, 0xb6, 0xff, 0x37, 0x29, 0x5f, 0x95, 0x9a, 0xe7, 0xf6, 0x05, 0x69, 0x8e, 0xc6, 0x5b, 0xfd, 0x8c, 0xb7, 0xfa, 0x19, 0x6f, 0xf5, 0x33, 0xde, 0xea, 0x67, 0x7c, 0xe5, 0x8a, 0x55, 0xab, 0x9f, 0x2d, 0x91, 0x0d, 0x57, 0xae, 0x5e, 0x76, 0xbf, 0x7c, 0xf5, 0xaa, 0xc3, 0xed, 0xab, 0xb2, 0x5c, 0x5e, 0x93, 0xd7, 0xa5, 0x7c, 0x45, 0x6b, 0xa5, 0xdb, 0x2e, 0x71, 0xdc, 0x56, 0x3f, 0x63, 0xad, 0x7e, 0xc6, 0x5a, 0xfd, 0x8c, 0xb5, 0xfa, 0x19, 0x6b, 0xf5, 0x33, 0xd6, 0xea, 0x67, 0xac, 0xd5, 0xcf, 0x58, 0xab, 0x9f, 0xb1, 0x68, 0x91, 0x40, 0x8b, 0x04, 0x5a, 0x24, 0xd0, 0x22, 0x81, 0x16, 0x09, 0xb4, 0x48, 0xa0, 0x45, 0x02, 0x2d, 0x12, 0x68, 0x91, 0x40, 0x8b, 0x04, 0x5a, 0x24, 0xd0, 0x22, 0x51, 0x5f, 0xfe, 0x57, 0xb1, 0xa2, 0x0c, 0x4b, 0x49, 0x46, 0xe4, 0xcd, 0xe8, 0x5e, 0xab, 0xa8, 0x53, 0x2b, 0x57, 0xd1, 0x62, 0x6e, 0x6b, 0xa4, 0x7c, 0x35, 0xad, 0xd6, 0x6d, 0x9d, 0x8c, 0x8e, 0x0e, 0xb1, 0x8a, 0x3a, 0xc4, 0x2a, 0xea, 0x90, 0x8d, 0x57, 0xd8, 0x36, 0x77, 0x7f, 0x0b, 0xf9, 0x8f, 0xae, 0xb4, 0x59, 0x71, 0xeb, 0xf8, 0x0f, 0xe8, 0xf8, 0x0f, 0xe8, 0xf8, 0x0f, 0x54, 0xae, 0xbc, 0x9d, 0xe8, 0xf6, 0x24, 0x39, 0x59, 0x26, 0xca, 0x29, 0x72, 0xaa, 0x9c, 0x26, 0xa7, 0x4b, 0xf9, 0xca, 0xdc, 0x19, 0xd1, 0x58, 0x2b, 0x9a, 0xb1, 0x56, 0x34, 0x63, 0x2b, 0x57, 0xe9, 0xce, 0x72, 0x5b, 0xbe, 0x52, 0xf7, 0x79, 0xb7, 0xe7, 0xc8, 0xb9, 0x72, 0x9e, 0x94, 0xaf, 0xdc, 0x9d, 0xef, 0xf6, 0x02, 0xb9, 0x50, 0xca, 0x57, 0xf1, 0xbe, 0xea, 0xf6, 0x22, 0xf9, 0x9a, 0x5c, 0x2c, 0x5f, 0x97, 0x4b, 0xe4, 0x1b, 0x72, 0xa9, 0x5c, 0x2b, 0xdf, 0x92, 0xeb, 0x44, 0xcd, 0xa3, 0xcb, 0x03, 0xe8, 0xf2, 0x40, 0xe5, 0xea, 0xdf, 0xcd, 0x6e, 0xbf, 0x2f, 0x93, 0xe5, 0x16, 0x29, 0x5f, 0x0d, 0xbc, 0xdd, 0xed, 0x0f, 0xe4, 0x87, 0xd1, 0x19, 0x1b, 0xaf, 0x0c, 0x4e, 0x71, 0xdf, 0x7c, 0xb0, 0xa2, 0x39, 0xc3, 0x8a, 0xe6, 0x0c, 0x2b, 0x9a, 0x33, 0xac, 0x68, 0xce, 0x40, 0xa0, 0x1c, 0x02, 0xe5, 0x10, 0x28, 0x87, 0x40, 0x39, 0x04, 0xca, 0x21, 0x50, 0x0e, 0x81, 0x72, 0x08, 0x94, 0x43, 0xa0, 0x1c, 0x02, 0xe5, 0x10, 0x28, 0x87, 0x40, 0x39, 0x04, 0xca, 0x21, 0x50, 0x0e, 0x81, 0x72, 0x08, 0x94, 0x43, 0xa0, 0x1c, 0x02, 0xe5, 0x10, 0x28, 0x87, 0x40, 0x39, 0x04, 0xca, 0x21, 0x50, 0x0e, 0x81, 0x72, 0x08, 0x94, 0x43, 0xa0, 0x1c, 0x02, 0xe5, 0x10, 0x28, 0x87, 0x40, 0x39, 0x04, 0xca, 0x21, 0x50, 0x0e, 0x81, 0x72, 0x08, 0x94, 0x43, 0xa0, 0x1c, 0x02, 0xe5, 0x10, 0x28, 0x87, 0x40, 0x39, 0x04, 0xca, 0x21, 0x50, 0x0e, 0x81, 0x72, 0x95, 0xab, 0x99, 0xcf, 0xda, 0xf7, 0xb9, 0xf2, 0x37, 0x79, 0x4e, 0xe6, 0xc9, 0x7c, 0x79, 0x5e, 0x5e, 0x90, 0x66, 0x29, 0x5f, 0xf1, 0x6c, 0x71, 0xfb, 0xa2, 0xbc, 0x24, 0x8b, 0x64, 0xb1, 0xc4, 0xa5, 0x55, 0x96, 0x48, 0x9b, 0xbc, 0x2c, 0xed, 0xd2, 0xe1, 0xb5, 0x97, 0x49, 0xa7, 0xd5, 0xd5, 0xab, 0xb2, 0x5c, 0x5e, 0x93, 0xd7, 0x65, 0x85, 0xac, 0x2c, 0xbf, 0xfb, 0x25, 0x0a, 0xad, 0x96, 0x7f, 0xfc, 0x2f, 0xde, 0x9f, 0x2e, 0x5f, 0x75, 0x45, 0xa0, 0xa7, 0x11, 0xe8, 0x69, 0x04, 0x7a, 0x1a, 0x81, 0x9e, 0x46, 0xa0, 0x07, 0x2a, 0x57, 0x62, 0xfb, 0xa3, 0xb1, 0xe5, 0xab, 0xb1, 0x56, 0x6f, 0x63, 0xad, 0xde, 0xc6, 0x5a, 0xbd, 0x8d, 0x45, 0xa1, 0x19, 0x28, 0x34, 0x03, 0x85, 0x66, 0xa0, 0xd0, 0x0c, 0x14, 0x9a, 0x61, 0xf5, 0x36, 0x3e, 0xd8, 0xaa, 0xb2, 0x26, 0x2b, 0xaf, 0xaf, 0x36, 0xac, 0x65, 0xac, 0x4d, 0x74, 0xd1, 0x97, 0x75, 0xcf, 0x64, 0xc5, 0xff, 0x37, 0xb8, 0x7b, 0xd9, 0xdb, 0x79, 0x7a, 0xd9, 0xed, 0x36, 0x7a, 0x51, 0xd9, 0x81, 0x38, 0x4f, 0xb0, 0x63, 0xd0, 0x18, 0xad, 0x0f, 0xb6, 0x94, 0xed, 0x64, 0xaf, 0xe8, 0x15, 0xaf, 0xf8, 0x8c, 0x57, 0x5b, 0x5e, 0xfe, 0xd7, 0x0a, 0xbd, 0xb4, 0xb7, 0xe6, 0xc2, 0x28, 0xac, 0xf9, 0x6a, 0xb4, 0xbe, 0xe6, 0x96, 0x60, 0x1f, 0xaf, 0xfa, 0x8a, 0x57, 0xcd, 0x7a, 0xd5, 0x55, 0x75, 0xbb, 0x44, 0xeb, 0xbd, 0xea, 0x2b, 0x75, 0x2b, 0xdd, 0x76, 0x45, 0xeb, 0xc7, 0x78, 0xcc, 0x98, 0xeb, 0xe4, 0xdb, 0x32, 0x10, 0xad, 0xaf, 0x3f, 0x24, 0x5a, 0x6f, 0x4b, 0x23, 0xb6, 0x12, 0x2b, 0xff, 0x8b, 0x45, 0xe5, 0xdf, 0x42, 0x6e, 0x7f, 0x97, 0x7f, 0x0b, 0x89, 0xfd, 0x3f, 0xff, 0x5b, 0xc8, 0x0e, 0xd5, 0xb3, 0xd1, 0x1e, 0x9c, 0x17, 0x8c, 0x76, 0x46, 0xba, 0x6d, 0xa5, 0x54, 0xbe, 0x72, 0x68, 0x2b, 0xbd, 0xb6, 0x52, 0x3e, 0x8e, 0xf5, 0x5e, 0xf9, 0x25, 0xfb, 0x5f, 0xf2, 0xea, 0x29, 0xaf, 0x5e, 0xfe, 0x04, 0x99, 0xdd, 0xcb, 0xbf, 0x9d, 0xe6, 0xd5, 0x3f, 0xe0, 0xcc, 0xac, 0x6b, 0xd8, 0x25, 0x68, 0x32, 0xe7, 0xb7, 0xb1, 0xa5, 0x5d, 0x6d, 0xe9, 0x3d, 0xb6, 0xb4, 0xbd, 0x2d, 0x1d, 0xe0, 0x18, 0xb6, 0x6c, 0x28, 0xaf, 0x1c, 0xcf, 0x0a, 0xc6, 0x05, 0x97, 0xd9, 0xca, 0xda, 0xe0, 0x1c, 0xb7, 0xe7, 0xca, 0x79, 0xf2, 0xe5, 0xe0, 0x0b, 0xc1, 0xf9, 0x72, 0x81, 0x7c, 0x45, 0xbe, 0x2a, 0x17, 0xf9, 0xfe, 0xc5, 0xfe, 0xfb, 0xba, 0x5c, 0xe5, 0xfe, 0x75, 0x72, 0xa7, 0xff, 0x4f, 0x09, 0xa6, 0xc4, 0x0e, 0x08, 0xce, 0x8b, 0x1d, 0x18, 0x3c, 0x11, 0x3b, 0x28, 0x78, 0x2c, 0x76, 0x58, 0x70, 0x77, 0xec, 0x13, 0xc1, 0x6d, 0xb1, 0xc3, 0xdd, 0x1e, 0xe1, 0xf6, 0xb8, 0xe0, 0x02, 0xe4, 0x7b, 0x04, 0xf9, 0x7e, 0x85, 0x7c, 0xb7, 0x21, 0xdf, 0xf7, 0x63, 0x67, 0x06, 0x3f, 0x8c, 0x7d, 0x31, 0xf8, 0x79, 0xec, 0x4b, 0x72, 0x41, 0xf0, 0x78, 0xec, 0xc2, 0x60, 0x12, 0xe2, 0xad, 0x47, 0xbc, 0xf5, 0x35, 0x97, 0x07, 0x97, 0xd5, 0x5c, 0x21, 0xdf, 0x92, 0xe9, 0xf2, 0xd3, 0xe0, 0xb2, 0xda, 0x49, 0xc1, 0xc5, 0x75, 0xdb, 0xc8, 0x01, 0xc1, 0xc3, 0x75, 0x07, 0xca, 0xbc, 0xe0, 0x0b, 0x75, 0xfd, 0xc1, 0x79, 0xa3, 0xdf, 0x1f, 0x5c, 0x56, 0x3f, 0x5b, 0xe6, 0xc8, 0x5f, 0x83, 0xcb, 0x1a, 0xb6, 0x94, 0xad, 0xe4, 0xbd, 0xb2, 0xb5, 0x6c, 0x23, 0xdb, 0xca, 0xf6, 0xc1, 0x17, 0x1a, 0x76, 0x90, 0x1d, 0x65, 0x27, 0xd9, 0x59, 0x76, 0x91, 0x5d, 0x65, 0x37, 0xd9, 0x5d, 0xf6, 0x90, 0xbd, 0x64, 0x6f, 0xd9, 0x47, 0xf6, 0x95, 0x0f, 0xcb, 0xfe, 0xf2, 0x11, 0xf9, 0x98, 0xd7, 0x39, 0x40, 0x0e, 0x94, 0x83, 0x64, 0xbc, 0x1c, 0x1a, 0x5c, 0xdc, 0xf0, 0x71, 0x39, 0x4c, 0x3e, 0x21, 0x87, 0xcb, 0x11, 0x72, 0xa4, 0x1c, 0x25, 0x47, 0x07, 0x17, 0x37, 0x1d, 0x1b, 0x3d, 0x1e, 0x34, 0x39, 0xba, 0x75, 0x8e, 0x6e, 0x1d, 0x9e, 0x5b, 0x65, 0x06, 0xef, 0x33, 0x4e, 0xdb, 0xd8, 0x83, 0x6d, 0x6d, 0x6d, 0x6b, 0x5b, 0xdb, 0xca, 0x96, 0x2e, 0xb6, 0x95, 0x1d, 0x1b, 0x7e, 0x10, 0x6c, 0x1d, 0xd4, 0xa9, 0xf9, 0xd5, 0xd6, 0xd1, 0x79, 0xeb, 0xe8, 0xfc, 0x86, 0x77, 0x63, 0x0c, 0x46, 0xc5, 0x4e, 0x33, 0x13, 0x4e, 0x8f, 0x16, 0xf8, 0x6a, 0x51, 0xf0, 0x20, 0x5b, 0xeb, 0x63, 0x6b, 0x7d, 0x6c, 0xad, 0x8f, 0x7d, 0x0d, 0xb2, 0xaf, 0x41, 0xf6, 0x35, 0xc8, 0xbe, 0x06, 0xd9, 0xd7, 0x20, 0xfb, 0x1a, 0x64, 0x5f, 0x83, 0xec, 0x6b, 0x90, 0x7d, 0x0d, 0xb2, 0xaf, 0x41, 0xf6, 0x35, 0xc8, 0xbe, 0x06, 0xd9, 0xd7, 0x20, 0xfb, 0x1a, 0x64, 0x5f, 0x83, 0xec, 0x6b, 0x90, 0x7d, 0x0d, 0xb2, 0xaf, 0x41, 0xf6, 0x35, 0xc8, 0xbe, 0x06, 0xd9, 0xd7, 0x20, 0xfb, 0x1a, 0x64, 0x5f, 0x83, 0xec, 0x6b, 0x90, 0x7d, 0x0d, 0xb2, 0xaf, 0x41, 0xf6, 0x35, 0xc8, 0xbe, 0x06, 0xd9, 0xd7, 0x20, 0xfb, 0x1a, 0x64, 0x5f, 0x83, 0xec, 0x6b, 0x90, 0x31, 0x0d, 0x56, 0x0d, 0x68, 0x88, 0x01, 0xbd, 0xcc, 0x80, 0x32, 0x0c, 0xa8, 0x27, 0x76, 0x60, 0xd4, 0x1c, 0x3b, 0xc8, 0xbc, 0x28, 0xbf, 0x67, 0xe4, 0xa1, 0xd1, 0xab, 0x8e, 0xe7, 0xd5, 0xd8, 0x61, 0x51, 0x32, 0xf6, 0x89, 0x68, 0x20, 0x76, 0xb8, 0xdb, 0x23, 0xdc, 0x1e, 0x69, 0xbe, 0x1c, 0x25, 0x13, 0xa2, 0x96, 0xd8, 0xf1, 0xd1, 0x93, 0x8e, 0x72, 0x8e, 0xa3, 0x7c, 0xca, 0x51, 0xce, 0x8d, 0x9d, 0x19, 0x75, 0x6d, 0x7c, 0x7f, 0xd0, 0x0b, 0xa2, 0x57, 0xd9, 0x4e, 0x0f, 0xc3, 0xe9, 0x61, 0x36, 0x19, 0x66, 0xd3, 0xc3, 0x6c, 0x06, 0x99, 0xcd, 0x20, 0xb3, 0x19, 0x64, 0x36, 0x83, 0xcc, 0x66, 0x90, 0xd9, 0x0c, 0x32, 0x9b, 0x41, 0x66, 0x33, 0xc8, 0x6c, 0x06, 0x99, 0xcd, 0xe0, 0x26, 0xbf, 0xd3, 0x3a, 0xc8, 0x60, 0x06, 0xd9, 0xcb, 0x20, 0x7b, 0x19, 0x64, 0x2f, 0x83, 0xec, 0x65, 0x90, 0xbd, 0x0c, 0xb2, 0x94, 0x3e, 0x96, 0xd2, 0xc7, 0x52, 0xfa, 0x58, 0x4a, 0x1f, 0x4b, 0xe9, 0x63, 0x29, 0x7d, 0x2c, 0xa5, 0x8f, 0xa5, 0xf4, 0xb1, 0x94, 0x3e, 0x96, 0xd2, 0xc7, 0x52, 0xfa, 0x58, 0x4a, 0x9f, 0xde, 0xd3, 0xc7, 0x52, 0xfa, 0x58, 0x4a, 0x1f, 0x4b, 0xe9, 0x63, 0x29, 0x7d, 0x2c, 0xa5, 0x8f, 0xa5, 0xf4, 0xb1, 0x94, 0x3e, 0x96, 0xd2, 0xc7, 0x52, 0xfa, 0x58, 0x4a, 0x1f, 0x4b, 0xe9, 0x63, 0x29, 0x7d, 0x2c, 0xa5, 0x8f, 0xa5, 0xf4, 0xb1, 0x94, 0x3e, 0x96, 0xd2, 0xc7, 0x52, 0xfa, 0x58, 0x4a, 0x1f, 0x3b, 0x18, 0x64, 0x07, 0x83, 0xec, 0x60, 0x90, 0x1d, 0x0c, 0xb2, 0x83, 0x41, 0x76, 0x30, 0xc8, 0x0e, 0x06, 0xd9, 0xc1, 0x20, 0x3b, 0x18, 0x64, 0x07, 0x83, 0xec, 0x60, 0x90, 0x1d, 0x0c, 0xb2, 0x83, 0x41, 0x76, 0x30, 0xc8, 0x0e, 0x06, 0xd9, 0xc1, 0x20, 0x3b, 0x18, 0x64, 0x07, 0x83, 0x9b, 0x74, 0xf8, 0x21, 0x1d, 0x7e, 0x48, 0x87, 0x1f, 0xd2, 0xe1, 0x87, 0x74, 0xf8, 0x21, 0x1d, 0x7e, 0x48, 0x87, 0x1f, 0xd2, 0xe1, 0x5f, 0xd6, 0xe1, 0x5f, 0xd6, 0xe1, 0x33, 0x3a, 0x7c, 0x46, 0x87, 0xcf, 0xe8, 0xf0, 0x19, 0x1d, 0x3e, 0xa3, 0xc3, 0x67, 0x74, 0xf8, 0x8c, 0x0e, 0x9f, 0xd1, 0xe1, 0x33, 0x3a, 0x7c, 0x46, 0x87, 0xef, 0xd1, 0xe1, 0x7b, 0x74, 0xf8, 0x1e, 0x1d, 0xbe, 0x47, 0x87, 0xef, 0xd1, 0xe1, 0x7b, 0x82, 0xf7, 0xe8, 0x1a, 0x7f, 0x33, 0x62, 0xe5, 0xdf, 0x68, 0xee, 0x30, 0x5a, 0xa1, 0x11, 0x4a, 0xa9, 0xf3, 0x92, 0x3a, 0x2f, 0xe9, 0x7a, 0x7b, 0xe8, 0x49, 0xd9, 0x86, 0xfb, 0x83, 0xad, 0x1b, 0x5f, 0x09, 0xb6, 0x34, 0x0b, 0x66, 0x55, 0xde, 0x5f, 0xb5, 0x5c, 0xbd, 0x0b, 0x82, 0xed, 0x82, 0x3b, 0xf5, 0x9e, 0x29, 0xaa, 0xe1, 0xc0, 0xe8, 0x39, 0x15, 0x32, 0xcf, 0xe8, 0x67, 0xbd, 0xd6, 0xc3, 0x46, 0x7f, 0x81, 0x47, 0xcd, 0xf1, 0xa8, 0xe7, 0x8d, 0x7e, 0x8b, 0xd7, 0x6c, 0x8e, 0x5d, 0xa8, 0x0a, 0x7a, 0xbd, 0x76, 0x41, 0xcc, 0x1f, 0x3d, 0x6f, 0xb8, 0xdc, 0xb3, 0x8d, 0xea, 0x1c, 0xa3, 0x3a, 0x47, 0xdf, 0x1b, 0x6e, 0x3c, 0xbd, 0xf2, 0x4e, 0xcc, 0xd7, 0x06, 0x75, 0x7a, 0x72, 0xf9, 0x37, 0xfa, 0x06, 0xcb, 0xef, 0xe0, 0x6a, 0x2f, 0xca, 0x5b, 0x5e, 0x82, 0x0a, 0x6f, 0xff, 0x4e, 0xf9, 0x5f, 0x9a, 0xbb, 0x7d, 0x27, 0x51, 0xfd, 0xb7, 0x95, 0x57, 0x6c, 0x61, 0xad, 0x2d, 0xac, 0xb5, 0x85, 0x3e, 0x8f, 0x48, 0x9a, 0x85, 0xd5, 0x7f, 0xa5, 0xd5, 0xb9, 0x76, 0xd0, 0xb9, 0x76, 0xd3, 0xb9, 0x76, 0xaa, 0xcc, 0xc2, 0xf2, 0x6f, 0xcb, 0xb5, 0xc4, 0xca, 0xbf, 0xb3, 0xbe, 0x85, 0xad, 0x15, 0x2a, 0xdd, 0xf3, 0x2b, 0xc1, 0xa1, 0xc1, 0x0f, 0x82, 0x7d, 0x62, 0x13, 0x82, 0xad, 0xbd, 0x6a, 0xf9, 0xdf, 0xcf, 0x37, 0xf3, 0xf8, 0x31, 0x5e, 0xb5, 0xc7, 0xab, 0x66, 0xbc, 0xea, 0x3a, 0xe7, 0x63, 0xa4, 0x61, 0x55, 0xb0, 0x4f, 0xc3, 0xea, 0x60, 0x1f, 0x5b, 0x68, 0x0d, 0x16, 0x58, 0x65, 0x15, 0xac, 0xb2, 0x0a, 0x56, 0x59, 0x05, 0xab, 0xac, 0x82, 0x55, 0x56, 0xc1, 0x2a, 0xab, 0x60, 0x95, 0xb5, 0xde, 0x2a, 0x6b, 0xbd, 0x55, 0xd6, 0x7a, 0x2b, 0xac, 0x82, 0x15, 0x56, 0xc1, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0xea, 0x6a, 0xc0, 0x2a, 0xaa, 0x80, 0x73, 0x05, 0xab, 0xa8, 0x82, 0x15, 0xd4, 0x7a, 0xf3, 0x7e, 0x28, 0x28, 0xbf, 0xf7, 0xed, 0xed, 0xd1, 0x88, 0x39, 0xbf, 0xce, 0x48, 0x66, 0x63, 0x47, 0x47, 0x2b, 0x63, 0xc7, 0x4a, 0xf9, 0xef, 0xa2, 0x4f, 0x30, 0x6f, 0x4f, 0xaa, 0xcc, 0xdd, 0xd7, 0xac, 0x6c, 0x0a, 0x56, 0x31, 0x05, 0xab, 0x98, 0xf5, 0x56, 0x2a, 0xeb, 0xad, 0x50, 0xd6, 0x9b, 0xc7, 0xeb, 0xcc, 0xe1, 0x21, 0x73, 0x78, 0xc8, 0x1c, 0x1e, 0x32, 0x87, 0x87, 0xcc, 0xe1, 0x21, 0x73, 0x78, 0xc8, 0x1c, 0x1e, 0x32, 0x87, 0x87, 0xcc, 0xe1, 0x21, 0x73, 0x78, 0xc8, 0x6a, 0x64, 0xc0, 0xfc, 0x1d, 0x32, 0x7f, 0x87, 0xcc, 0xdf, 0x21, 0xf3, 0x77, 0xc8, 0xfc, 0x1d, 0x32, 0x7f, 0x87, 0xcc, 0xdf, 0x21, 0xab, 0x8f, 0x82, 0xd5, 0xc7, 0x7a, 0x2b, 0x8e, 0x02, 0xeb, 0x2f, 0xb0, 0xfe, 0x02, 0xeb, 0x2f, 0xb0, 0xfe, 0x02, 0xeb, 0x2f, 0xb0, 0xfe, 0xf5, 0xac, 0x7f, 0x3d, 0xeb, 0x5f, 0xcf, 0xfa, 0xd7, 0xb3, 0xfe, 0xf5, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xc8, 0x5a, 0x60, 0xfd, 0x05, 0xd6, 0x5f, 0x60, 0xfd, 0x05, 0xd6, 0x5f, 0x60, 0xfd, 0x05, 0xd6, 0x5f, 0x60, 0xfd, 0x05, 0xd6, 0x5f, 0x60, 0xfd, 0x05, 0xd6, 0x5f, 0x60, 0xfd, 0x05, 0xf3, 0x7a, 0xc8, 0xbc, 0x1e, 0x32, 0xaf, 0x87, 0xcc, 0xeb, 0x21, 0xf3, 0x7a, 0xc8, 0xbc, 0x1e, 0x32, 0xaf, 0x87, 0xcc, 0xeb, 0x21, 0xf3, 0x7a, 0xc8, 0xbc, 0x1e, 0x32, 0xaf, 0x87, 0xcc, 0xeb, 0x21, 0xf3, 0x7a, 0xc8, 0xbc, 0x1e, 0x32, 0xaf, 0x87, 0xcc, 0xeb, 0x21, 0xf3, 0x7a, 0xc8, 0xbc, 0x1e, 0x62, 0xf6, 0x03, 0xcc, 0x7e, 0x80, 0xd9, 0x0f, 0x30, 0xfb, 0x01, 0x66, 0x3f, 0xc0, 0xec, 0x07, 0x98, 0xfd, 0x00, 0x0b, 0x2f, 0xb0, 0xf0, 0x02, 0x0b, 0x2f, 0xb0, 0xf0, 0x02, 0x0b, 0x2f, 0xb0, 0xf0, 0x02, 0x0b, 0x2f, 0xb0, 0xf0, 0x02, 0x0b, 0x2f, 0xb0, 0xf0, 0x02, 0x0b, 0x2f, 0x20, 0x7e, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x97, 0x94, 0xdf, 0x2b, 0xa9, 0x4f, 0x7f, 0xe9, 0xd3, 0x5f, 0x46, 0xf4, 0x97, 0x11, 0xfd, 0x65, 0x44, 0x7f, 0x19, 0xd1, 0x5f, 0x46, 0xf4, 0x97, 0x11, 0xfd, 0x65, 0x44, 0x5f, 0x59, 0xa7, 0xaf, 0xac, 0xd3, 0x57, 0xd6, 0xe9, 0x2b, 0xeb, 0xf4, 0x95, 0x75, 0xfa, 0xca, 0x3a, 0x7d, 0x65, 0x9d, 0xbe, 0xb2, 0x4e, 0x5f, 0x59, 0xa7, 0xaf, 0xac, 0x6b, 0x7c, 0x3d, 0xea, 0x65, 0x87, 0x05, 0x76, 0x58, 0x60, 0x87, 0x05, 0x76, 0x58, 0x60, 0x87, 0xe5, 0xf7, 0x3f, 0x5a, 0x5f, 0xbe, 0x8e, 0x1f, 0x6c, 0x67, 0x0e, 0x9d, 0x64, 0xde, 0x9d, 0x64, 0x1e, 0x9d, 0x50, 0x5e, 0x35, 0x5b, 0x59, 0x6f, 0xae, 0x5f, 0x24, 0x55, 0x59, 0x79, 0xe6, 0xce, 0x31, 0x13, 0x57, 0xfa, 0x6e, 0xca, 0x4c, 0x4c, 0xe9, 0x15, 0xe5, 0xbf, 0x2f, 0x7c, 0xb5, 0x3a, 0x8b, 0xcb, 0x7d, 0x62, 0x9d, 0xfe, 0xb0, 0x52, 0x7f, 0x58, 0x69, 0xae, 0xfd, 0x21, 0xd8, 0xd2, 0x33, 0xb3, 0x9e, 0xd9, 0xe3, 0x95, 0xf7, 0x8f, 0x1d, 0x1c, 0x6c, 0x1f, 0x3b, 0x32, 0x38, 0x39, 0x76, 0x54, 0xe5, 0x77, 0x74, 0xb7, 0xf2, 0x6a, 0xcd, 0xb6, 0x76, 0xa0, 0xad, 0x1d, 0x62, 0x6b, 0x87, 0x7a, 0xa5, 0x84, 0x57, 0xea, 0xf3, 0x4a, 0x7d, 0x5e, 0x29, 0xef, 0x15, 0x16, 0xe9, 0x77, 0x35, 0xd1, 0x7d, 0x65, 0x3b, 0x45, 0xb3, 0x27, 0xf4, 0xab, 0x3f, 0xd9, 0x7e, 0x87, 0xed, 0x2f, 0xb4, 0xfd, 0x56, 0x8f, 0x4e, 0x78, 0x74, 0xa2, 0xd2, 0x9f, 0xf4, 0x3d, 0xcf, 0x48, 0x57, 0x7a, 0x45, 0xf9, 0x11, 0xcd, 0xb1, 0xf2, 0xfb, 0x20, 0x6c, 0xe3, 0xf9, 0xbf, 0xd7, 0xf3, 0xb2, 0xf6, 0xa4, 0xcd, 0x9e, 0xc4, 0x6d, 0xf5, 0x51, 0x8f, 0xe8, 0xf6, 0x88, 0x15, 0x1e, 0xb1, 0xc2, 0x31, 0x74, 0xd8, 0xf2, 0x12, 0xfd, 0x6e, 0x9d, 0xd7, 0xcb, 0x7a, 0xbd, 0xac, 0xd7, 0x0b, 0xf5, 0xbb, 0x21, 0xc7, 0x12, 0x77, 0x2c, 0x71, 0xbd, 0x6e, 0x48, 0xaf, 0x2b, 0xef, 0xd1, 0x03, 0xc1, 0x66, 0x5e, 0xf1, 0xea, 0x4d, 0xba, 0x70, 0xfa, 0x1f, 0xea, 0xc2, 0xe5, 0xf7, 0x7c, 0xee, 0xfd, 0x0f, 0xbb, 0x5f, 0xf9, 0x37, 0x53, 0x7e, 0xaf, 0x03, 0xfe, 0x41, 0x07, 0x2c, 0xff, 0x1b, 0xf4, 0xd2, 0x6a, 0xef, 0x5e, 0x1c, 0x8c, 0x67, 0x8c, 0x4d, 0x6c, 0xb1, 0x89, 0x2d, 0x36, 0xb1, 0xc5, 0xdd, 0x98, 0x61, 0x13, 0x27, 0x8c, 0x31, 0xc3, 0x26, 0x66, 0xd8, 0xc4, 0x09, 0x63, 0x9b, 0xfa, 0x0e, 0xa3, 0x6b, 0x62, 0x74, 0x4d, 0x8c, 0xae, 0x89, 0xd1, 0x35, 0x31, 0xba, 0x26, 0x46, 0xd7, 0xc4, 0xe8, 0x9a, 0x18, 0x5c, 0x13, 0x83, 0x6b, 0x62, 0x70, 0x4d, 0x0c, 0xae, 0x89, 0xc1, 0x35, 0x31, 0xb8, 0x26, 0x06, 0xd7, 0xc4, 0xe0, 0x9a, 0x18, 0x5c, 0x13, 0x83, 0x6b, 0x62, 0x6d, 0xdf, 0xe4, 0x4b, 0x9b, 0x31, 0xb6, 0x71, 0x8c, 0xed, 0x93, 0x8c, 0xed, 0x18, 0xc6, 0xf6, 0x3d, 0xee, 0xb4, 0x2b, 0x77, 0x3a, 0x89, 0x3b, 0x6d, 0xc5, 0xd0, 0x9a, 0x18, 0x5a, 0x13, 0x43, 0x6b, 0x62, 0x68, 0x4d, 0x0c, 0xad, 0x89, 0xa1, 0x35, 0x31, 0xb4, 0x26, 0x86, 0xd6, 0xc4, 0xd0, 0x9a, 0x18, 0x5a, 0x13, 0x43, 0x6b, 0x62, 0x68, 0x4d, 0x0c, 0xad, 0x89, 0xa1, 0x35, 0x35, 0x7c, 0x2f, 0xd8, 0xbf, 0xe1, 0xce, 0x60, 0x7b, 0x67, 0xe8, 0xe4, 0xca, 0x11, 0x7d, 0x70, 0xd3, 0xbd, 0xb7, 0xd5, 0x33, 0x6d, 0xf5, 0xe3, 0xb6, 0xba, 0xb7, 0xad, 0xee, 0xb9, 0xc9, 0x16, 0x3f, 0xaf, 0xff, 0x9f, 0x17, 0x7c, 0xf9, 0xef, 0x1e, 0xbd, 0x53, 0xe5, 0x37, 0xb1, 0xef, 0xf2, 0x8c, 0x9b, 0x3c, 0xe3, 0x1c, 0xcf, 0xb8, 0xda, 0x7e, 0x1e, 0xb3, 0xc9, 0xb3, 0x7e, 0xd2, 0x70, 0x47, 0xb0, 0xb9, 0x6d, 0xed, 0x66, 0xbd, 0x73, 0x6e, 0xf0, 0xb8, 0x57, 0xf8, 0xc2, 0xdb, 0x5e, 0xe1, 0x28, 0xaf, 0x70, 0xab, 0x57, 0xf8, 0x96, 0x57, 0x38, 0xcf, 0x2b, 0x9c, 0xee, 0x15, 0x3e, 0xbe, 0xc9, 0x2b, 0xfc, 0xd0, 0x51, 0xed, 0xe3, 0x55, 0xc6, 0x78, 0x95, 0x5d, 0x83, 0x2d, 0xcb, 0xbf, 0x7d, 0xfe, 0xb6, 0x57, 0xb8, 0xdc, 0x2b, 0xd4, 0x7b, 0x85, 0x0f, 0x79, 0x85, 0x23, 0xbd, 0xc2, 0xe1, 0x5e, 0xe1, 0x86, 0x4d, 0x5e, 0x61, 0x33, 0xc7, 0x3c, 0xd6, 0x31, 0xbf, 0xd7, 0x2b, 0x1c, 0x5f, 0x39, 0xe6, 0x8f, 0xbc, 0xed, 0x98, 0xbf, 0xe8, 0xd9, 0xc7, 0x78, 0xf6, 0x87, 0x3d, 0x7b, 0xec, 0x26, 0xcf, 0xfc, 0xb2, 0xaa, 0x3b, 0x2f, 0xf8, 0xdc, 0xdb, 0x1e, 0x5d, 0xde, 0xdb, 0x6b, 0x3c, 0xfa, 0x0c, 0x8f, 0xbe, 0xc4, 0xb6, 0x0e, 0xd9, 0xe4, 0x19, 0x77, 0xd8, 0xc6, 0x76, 0xf6, 0xf2, 0xad, 0x63, 0xfd, 0xfc, 0xdb, 0xf6, 0xf4, 0x70, 0xcf, 0xbe, 0xc5, 0xb3, 0xaf, 0xad, 0x9e, 0xad, 0xd3, 0x3c, 0xfb, 0xe0, 0xb7, 0x1d, 0xeb, 0xde, 0x5e, 0x61, 0x67, 0x15, 0xb9, 0xe9, 0x33, 0x77, 0xb3, 0x06, 0xab, 0x3e, 0xaa, 0x32, 0xcf, 0xd6, 0x6d, 0xf8, 0xbb, 0xaa, 0xca, 0x57, 0xcd, 0x1b, 0x1c, 0x62, 0xe3, 0x57, 0xcf, 0x56, 0xbc, 0xe3, 0x97, 0xff, 0xe3, 0xef, 0x30, 0xfa, 0xd6, 0xbb, 0x7d, 0xfe, 0x63, 0xd7, 0x16, 0x6f, 0xe3, 0x48, 0xb7, 0x23, 0xed, 0x5b, 0x46, 0xbd, 0x92, 0x51, 0x97, 0xdf, 0x79, 0x7f, 0xbe, 0xa3, 0x99, 0xed, 0x68, 0xe6, 0x6f, 0x72, 0x0d, 0xb0, 0xfc, 0x3e, 0x61, 0xe5, 0xf7, 0x07, 0x5b, 0xc9, 0x8a, 0xc3, 0x7f, 0xe2, 0x1d, 0x3b, 0x2b, 0xd7, 0xfb, 0x36, 0xbe, 0x4b, 0xe7, 0x3b, 0xbc, 0x43, 0x67, 0xf9, 0x1a, 0xde, 0x7f, 0xd6, 0x3b, 0x43, 0xfe, 0xaf, 0xbe, 0xe6, 0xd2, 0x11, 0xad, 0x41, 0xb6, 0x35, 0xc8, 0x56, 0x40, 0xb6, 0x02, 0xb2, 0x15, 0x90, 0xad, 0x80, 0x6c, 0x05, 0x64, 0x2b, 0x20, 0x5b, 0x61, 0x13, 0x73, 0x5e, 0x89, 0x70, 0x2b, 0x11, 0x6e, 0x25, 0xc2, 0xad, 0x44, 0xb8, 0x95, 0x08, 0xb7, 0x12, 0xe1, 0x56, 0x22, 0xdc, 0x4a, 0x84, 0x5b, 0x89, 0x70, 0x2b, 0x99, 0x73, 0xc8, 0x9c, 0x43, 0xe6, 0x1c, 0x32, 0xe7, 0x90, 0x39, 0x87, 0xcc, 0x39, 0xdc, 0xe8, 0x90, 0x2f, 0x54, 0xfa, 0x69, 0xf9, 0xab, 0xf2, 0x7b, 0xee, 0xff, 0xc5, 0x57, 0xf3, 0x82, 0x6d, 0x99, 0x71, 0x88, 0x12, 0xaf, 0xa0, 0xc4, 0x3c, 0x94, 0x98, 0xaf, 0x4b, 0xff, 0xba, 0x3a, 0xea, 0x95, 0x47, 0x54, 0xad, 0x78, 0x21, 0x4a, 0xac, 0x32, 0x23, 0xba, 0xcd, 0x88, 0x6e, 0x33, 0x22, 0x8b, 0x12, 0x7d, 0xe5, 0xdf, 0x42, 0x43, 0x8a, 0xb9, 0x48, 0x31, 0x17, 0x29, 0xfa, 0x90, 0x62, 0xad, 0x6e, 0x7e, 0x7b, 0xa5, 0x9b, 0xff, 0x4d, 0x37, 0x9f, 0xab, 0x9b, 0xcf, 0xf5, 0x6a, 0x7f, 0xf1, 0x6a, 0xcf, 0x54, 0xb6, 0xf7, 0x9d, 0x7f, 0xe7, 0xfd, 0x82, 0xf2, 0x1c, 0x34, 0xcf, 0x41, 0xf3, 0x1b, 0xdf, 0x2f, 0xe8, 0x8b, 0x51, 0x91, 0x07, 0xfe, 0xfd, 0x7b, 0xee, 0x4c, 0xaa, 0xbc, 0xdb, 0xfc, 0xa0, 0x0a, 0x2d, 0xbf, 0x0b, 0xde, 0x8a, 0x2a, 0x27, 0x5e, 0x2a, 0xbf, 0xff, 0x4d, 0xf5, 0x3d, 0x6f, 0xca, 0xbf, 0x7b, 0x54, 0xe4, 0x7c, 0x79, 0xce, 0x97, 0x57, 0xa5, 0x03, 0x5c, 0x2e, 0xac, 0xbe, 0xcf, 0x4c, 0xbe, 0xf2, 0xbe, 0x32, 0xef, 0xfc, 0x7e, 0x2e, 0x79, 0xbe, 0x96, 0xe7, 0x6b, 0x79, 0xbe, 0x96, 0xe7, 0x6b, 0xf9, 0xff, 0xf2, 0xf7, 0x73, 0x89, 0xa2, 0x62, 0x43, 0x20, 0x31, 0xa9, 0x91, 0x51, 0x52, 0x2b, 0x75, 0x32, 0xda, 0xe8, 0x8d, 0x91, 0x7a, 0x69, 0x90, 0xcd, 0x64, 0x73, 0xd9, 0xe2, 0xbf, 0xf9, 0xfd, 0x4b, 0x3a, 0xac, 0xf3, 0x96, 0xc9, 0x1a, 0xee, 0xd7, 0x2d, 0x49, 0x49, 0x49, 0x8f, 0x64, 0x24, 0x2b, 0xa1, 0xe4, 0x24, 0x1f, 0x0d, 0xbc, 0xd3, 0x7b, 0x67, 0x70, 0xac, 0x7c, 0xa5, 0x26, 0x9a, 0xd5, 0x44, 0xb3, 0x9a, 0x60, 0x3f, 0x7a, 0xca, 0x86, 0x9a, 0x28, 0x57, 0x64, 0xab, 0xaf, 0xfe, 0xe6, 0xab, 0x85, 0xe5, 0xaf, 0xf4, 0xfb, 0xd3, 0x83, 0xd3, 0x99, 0xd1, 0x29, 0x95, 0x67, 0x2d, 0xf4, 0xac, 0xe7, 0x3d, 0xeb, 0xf9, 0x6a, 0xdf, 0x7d, 0xeb, 0x33, 0x33, 0xca, 0x3f, 0x59, 0xe0, 0x27, 0x2f, 0xf8, 0xc9, 0x0b, 0x7e, 0xf2, 0xca, 0x86, 0x77, 0x44, 0xda, 0xd8, 0x9f, 0x17, 0x56, 0x3c, 0xa8, 0xfc, 0xb8, 0x95, 0x1e, 0xb7, 0xd2, 0xe3, 0x56, 0x56, 0x3f, 0x89, 0xa3, 0xfc, 0x19, 0x1b, 0xbd, 0x95, 0xc7, 0x95, 0x3f, 0x91, 0xe4, 0xf9, 0x72, 0xfd, 0x54, 0x1c, 0xf0, 0x80, 0xea, 0x6f, 0x3e, 0x9f, 0x65, 0xdb, 0x67, 0x54, 0x7e, 0xbe, 0x71, 0x1e, 0x54, 0x56, 0x93, 0xe5, 0x4f, 0xf0, 0x58, 0x56, 0xf9, 0xfe, 0xf3, 0x7f, 0xb7, 0xf7, 0x1b, 0x68, 0xd0, 0x56, 0xd9, 0xda, 0x43, 0xb6, 0x36, 0xd3, 0xd6, 0x66, 0x56, 0x7f, 0x37, 0xf8, 0xad, 0x67, 0xbd, 0xfd, 0x27, 0xbd, 0x1b, 0xfe, 0xfe, 0xab, 0xf2, 0x93, 0x39, 0x7e, 0xf2, 0x57, 0x3f, 0xf9, 0x6b, 0xf5, 0x39, 0x2f, 0x6d, 0x3c, 0x92, 0xd7, 0x37, 0x5e, 0x83, 0xa9, 0xa9, 0xee, 0xeb, 0xc2, 0x8d, 0x33, 0x7a, 0x61, 0x65, 0x55, 0xb8, 0xc1, 0xfb, 0x5e, 0xa8, 0x1c, 0x6f, 0xf9, 0x38, 0x26, 0x3a, 0x8e, 0x2f, 0x3b, 0x8e, 0xab, 0x1d, 0xc7, 0x55, 0x95, 0x33, 0x7a, 0x97, 0xaf, 0x6e, 0xf5, 0xd5, 0xcd, 0x1b, 0xaf, 0xeb, 0x94, 0x2d, 0xb1, 0xb5, 0xb2, 0xed, 0xf9, 0xb6, 0x3d, 0xcf, 0xb6, 0xe7, 0x55, 0x3f, 0x81, 0xe4, 0xad, 0xbd, 0x2a, 0x3f, 0x6e, 0x71, 0xf5, 0x13, 0x5c, 0x5e, 0xd8, 0x38, 0x46, 0xf3, 0x2b, 0x7b, 0x52, 0x7e, 0x56, 0xaf, 0x67, 0xad, 0xf3, 0xac, 0x75, 0xd5, 0xb3, 0xf4, 0x16, 0xff, 0xca, 0xfb, 0x38, 0x6f, 0xe3, 0xbd, 0x17, 0x2b, 0x1d, 0xa7, 0xa6, 0xfa, 0xd9, 0x2d, 0x2f, 0x57, 0x9e, 0xf7, 0x80, 0xe7, 0xdd, 0xef, 0x79, 0xf7, 0x57, 0xf7, 0xe3, 0xf9, 0xca, 0x2b, 0x96, 0xf7, 0xfa, 0x28, 0xfb, 0x79, 0x86, 0xfd, 0xbc, 0xd0, 0x7e, 0x7e, 0xb9, 0xb2, 0xc5, 0x57, 0xaa, 0x5b, 0x6c, 0xa9, 0x5c, 0xa3, 0xba, 0xc7, 0x33, 0xef, 0xf6, 0xcc, 0xbb, 0x37, 0x1d, 0xc1, 0xe0, 0x3b, 0x9e, 0x71, 0xaa, 0x67, 0x9c, 0x56, 0x79, 0xc6, 0x0b, 0x7e, 0xf6, 0xd7, 0x32, 0xad, 0x2a, 0xeb, 0xe9, 0xab, 0x3c, 0xfa, 0x2a, 0x8f, 0xd9, 0xd2, 0x63, 0xb6, 0xf6, 0x98, 0x2d, 0x2b, 0x7b, 0xd0, 0xe1, 0x27, 0xaf, 0xf8, 0xc9, 0x2b, 0x1e, 0x1f, 0xaf, 0x9e, 0x89, 0x7f, 0xeb, 0x8b, 0x6f, 0x8d, 0x6a, 0xf9, 0x5f, 0xce, 0xf6, 0xad, 0xec, 0x79, 0x79, 0xe5, 0xfd, 0x62, 0x65, 0x4b, 0x53, 0xab, 0xe7, 0xf0, 0x7b, 0x1b, 0xf7, 0x6d, 0xde, 0xc6, 0x8a, 0x9d, 0xe2, 0x67, 0x93, 0xff, 0xee, 0x67, 0x6f, 0x8d, 0xd9, 0x86, 0xb1, 0x79, 0x7e, 0xe3, 0x08, 0x56, 0xfc, 0xb7, 0xb2, 0xbe, 0xe8, 0xab, 0x1c, 0xf5, 0x7e, 0x9e, 0x7d, 0x94, 0x67, 0x7f, 0xba, 0x52, 0xef, 0xa3, 0xaa, 0xee, 0xfb, 0x56, 0x4d, 0x8e, 0xae, 0xee, 0x61, 0xd9, 0x26, 0x5e, 0xa8, 0xbd, 0x2b, 0x5a, 0x52, 0x7b, 0x4f, 0xf4, 0x6c, 0xed, 0x8f, 0x9d, 0xdd, 0xd1, 0x9b, 0xf4, 0xbe, 0x45, 0xd5, 0x4f, 0xb3, 0x78, 0xc1, 0x4f, 0x16, 0x55, 0x7e, 0xf2, 0xb7, 0xea, 0x15, 0xad, 0xbf, 0xf9, 0xc9, 0x73, 0x7e, 0xf2, 0x94, 0x9f, 0x3c, 0x6b, 0x6b, 0x6f, 0x8d, 0x43, 0xf9, 0x7a, 0x76, 0xbe, 0xfc, 0x17, 0x86, 0x95, 0xad, 0x2d, 0xf2, 0xdd, 0xb9, 0x1b, 0xc7, 0x78, 0x5d, 0xd5, 0xeb, 0xb3, 0x95, 0xd7, 0x79, 0x79, 0x43, 0x55, 0x79, 0x9d, 0x97, 0x3d, 0x6b, 0x81, 0x67, 0x2d, 0xae, 0xbc, 0x4e, 0xe5, 0xf3, 0x63, 0x7c, 0x27, 0x57, 0x79, 0x9d, 0xda, 0xea, 0x79, 0x7a, 0xc9, 0x77, 0x3a, 0x7c, 0xe7, 0xad, 0x67, 0x3f, 0xe7, 0xbb, 0x4f, 0xbf, 0x6d, 0x2f, 0xe6, 0x56, 0x46, 0xa0, 0x60, 0x04, 0xfa, 0x8c, 0x40, 0x9f, 0x47, 0xcd, 0xdd, 0xb0, 0xaf, 0x95, 0xdf, 0xa6, 0xea, 0xab, 0xee, 0x7d, 0x79, 0x14, 0x9e, 0xab, 0xec, 0x51, 0xe1, 0xef, 0x6a, 0xf0, 0xf9, 0x0d, 0x36, 0xb2, 0x71, 0xff, 0xe6, 0x57, 0xb6, 0x7b, 0x97, 0xed, 0xde, 0x13, 0xcd, 0xb7, 0x85, 0x17, 0x2b, 0x7f, 0x59, 0x70, 0x93, 0x2d, 0xdc, 0x68, 0x0b, 0x37, 0x56, 0x7d, 0xac, 0x7c, 0x9e, 0x5a, 0x3c, 0x6a, 0x81, 0x47, 0x3d, 0x5f, 0x79, 0x54, 0xf9, 0xf9, 0x6d, 0x1b, 0xba, 0x84, 0x9f, 0xb4, 0xfb, 0xc9, 0x8b, 0x7e, 0xd2, 0x5a, 0x79, 0xfe, 0x2c, 0xcf, 0x7f, 0xc4, 0xf3, 0x1f, 0xa9, 0xee, 0x4f, 0xc5, 0xe7, 0x3c, 0x6a, 0x8e, 0x47, 0xcd, 0xf6, 0xa8, 0x7f, 0x9b, 0xf7, 0x2f, 0x55, 0x2c, 0xb0, 0xe1, 0x6d, 0x73, 0xf8, 0xb5, 0xea, 0xdf, 0x04, 0xb4, 0x57, 0xcf, 0x5c, 0xf9, 0x2f, 0x1a, 0xdb, 0x2b, 0xdb, 0x5c, 0x52, 0x1d, 0xdd, 0xc5, 0xd5, 0xf1, 0x9c, 0x5f, 0x19, 0xb5, 0x0d, 0x23, 0x31, 0xaf, 0x32, 0xee, 0x35, 0xd5, 0x4f, 0x46, 0x0a, 0x2b, 0xaf, 0xbb, 0xe9, 0xba, 0x68, 0xc3, 0xcc, 0x2d, 0x3f, 0xfb, 0x45, 0xcf, 0x9e, 0x57, 0x79, 0x76, 0xf9, 0x51, 0x2f, 0x7a, 0xd4, 0x8b, 0x1e, 0xf5, 0x62, 0x75, 0x16, 0x57, 0xba, 0x4e, 0xf5, 0xb8, 0x2a, 0xbf, 0x5d, 0xbb, 0x71, 0xfc, 0x3b, 0x37, 0x8e, 0xdb, 0xe8, 0x4d, 0x3b, 0x47, 0xf5, 0x1c, 0xbe, 0x35, 0xc6, 0xe5, 0xfd, 0x59, 0x5e, 0xdd, 0xd3, 0x17, 0x2b, 0xfb, 0xf3, 0x42, 0xa5, 0xd6, 0xcb, 0x5b, 0xba, 0xdb, 0x96, 0xee, 0xb1, 0xa5, 0x7b, 0xaa, 0x8f, 0x59, 0x5c, 0x3d, 0xce, 0xf2, 0xd1, 0xb4, 0x78, 0xf6, 0xd2, 0xca, 0x96, 0xe6, 0x57, 0xce, 0xd8, 0x3d, 0xd1, 0x52, 0xdf, 0x59, 0x5b, 0xd9, 0xd2, 0xc6, 0xb3, 0x55, 0x7d, 0x47, 0xd8, 0xd6, 0xca, 0x5e, 0x95, 0x5f, 0xf1, 0x69, 0xaf, 0xf8, 0xb4, 0x57, 0x7c, 0xda, 0xa3, 0x5e, 0xda, 0x50, 0xfd, 0x1e, 0xf5, 0x82, 0x47, 0x3d, 0xe7, 0x51, 0x0b, 0x37, 0x3e, 0xbf, 0xd2, 0x65, 0x37, 0x79, 0x7e, 0x47, 0x65, 0x4f, 0xdb, 0x36, 0xd6, 0x48, 0xb9, 0xba, 0x56, 0x78, 0xb5, 0x15, 0x5e, 0x6d, 0x45, 0xb5, 0x33, 0x3d, 0x5b, 0xa9, 0xa5, 0xf2, 0x4f, 0x3a, 0x37, 0x5e, 0x15, 0x7e, 0x6b, 0xae, 0x3f, 0x5b, 0xa9, 0xbb, 0xf2, 0x1e, 0x2c, 0xf3, 0x93, 0x65, 0x7e, 0xb2, 0xac, 0xfa, 0x93, 0x72, 0x37, 0x9b, 0x5f, 0xad, 0xdb, 0xbf, 0x54, 0xc6, 0x7b, 0x74, 0xf5, 0xaa, 0xf2, 0x5b, 0xbf, 0x6d, 0xf6, 0x56, 0x75, 0xbf, 0xd5, 0x69, 0xca, 0xf3, 0xff, 0x3e, 0x33, 0x78, 0xaa, 0x19, 0x7c, 0x47, 0x65, 0x7f, 0x96, 0x6d, 0xec, 0xc6, 0xc7, 0x06, 0xcd, 0xd6, 0xcc, 0x2d, 0xd1, 0x55, 0xc1, 0xe2, 0xe8, 0xc9, 0xa0, 0x35, 0x7a, 0x2a, 0x68, 0x8b, 0x7e, 0x13, 0x2c, 0xe5, 0x48, 0x2f, 0x47, 0x1f, 0x0a, 0xda, 0xa3, 0x29, 0xc1, 0xb2, 0xa8, 0x3b, 0xe8, 0x8c, 0xa6, 0x06, 0xaf, 0x46, 0xc7, 0x05, 0xaf, 0x39, 0x53, 0x2b, 0xa2, 0xb3, 0x83, 0xae, 0xe8, 0xb7, 0xc1, 0xaa, 0xe8, 0xf0, 0xa0, 0x3b, 0x3a, 0x39, 0x48, 0xbf, 0xd9, 0x1b, 0xac, 0x8d, 0x3e, 0x13, 0xf4, 0x44, 0xe3, 0x2b, 0x9f, 0xf8, 0x74, 0xb0, 0xce, 0x39, 0x21, 0xba, 0x6e, 0xcc, 0x01, 0xd1, 0x55, 0x63, 0x0e, 0x94, 0x13, 0xe4, 0xc4, 0x28, 0x3e, 0xe6, 0x24, 0x39, 0xd9, 0xfd, 0x89, 0x72, 0x8a, 0xfb, 0xa7, 0xca, 0x69, 0xee, 0x7f, 0x5a, 0x4e, 0x97, 0xcf, 0xc8, 0x19, 0x72, 0xa6, 0x7c, 0x56, 0x3e, 0xe7, 0xe7, 0x67, 0xc9, 0xd9, 0xee, 0x7f, 0x5e, 0xbe, 0xe0, 0xfe, 0x17, 0xe5, 0x4b, 0xd1, 0x55, 0xf5, 0xfb, 0x46, 0xaf, 0xd4, 0xef, 0x2f, 0xe3, 0xa2, 0x57, 0x1a, 0x77, 0x8c, 0x9e, 0x6a, 0xdc, 0x49, 0x76, 0x76, 0x7f, 0x17, 0xd9, 0x55, 0x76, 0xf3, 0xf5, 0xee, 0x6e, 0xf7, 0x90, 0x3d, 0xe5, 0xfd, 0xbe, 0xde, 0x4b, 0xf6, 0x96, 0x0f, 0xc8, 0x3e, 0xbe, 0xf7, 0x41, 0x19, 0x2b, 0x1f, 0xf2, 0xb5, 0xd7, 0x6b, 0xfc, 0xb0, 0xec, 0xa7, 0xf3, 0x3d, 0xe1, 0x8c, 0xfc, 0xd9, 0x19, 0x79, 0x34, 0x78, 0x11, 0xbb, 0x16, 0xa9, 0x96, 0x25, 0xd1, 0xeb, 0xce, 0x4a, 0xd2, 0x59, 0xc9, 0x38, 0x23, 0xad, 0x41, 0x47, 0x94, 0x70, 0x56, 0xda, 0x9d, 0x95, 0x7c, 0xb0, 0xdc, 0x19, 0x79, 0x2d, 0x9a, 0x1c, 0xbc, 0xee, 0x2c, 0xad, 0x8c, 0x56, 0x38, 0x33, 0x2b, 0x9d, 0x99, 0x75, 0xc1, 0x6a, 0x55, 0x9f, 0x70, 0x9f, 0x1b, 0x07, 0xcc, 0x2a, 0xe0, 0xc5, 0x41, 0xda, 0xeb, 0xf4, 0x98, 0x33, 0x19, 0x86, 0x3a, 0x3e, 0xba, 0xce, 0x58, 0x5d, 0x6f, 0xac, 0xae, 0x77, 0xb6, 0x9e, 0x1a, 0x35, 0x33, 0x5a, 0x39, 0xea, 0x77, 0xd1, 0xa2, 0x51, 0x8f, 0xc8, 0xa3, 0xee, 0xff, 0x5e, 0xfe, 0x18, 0xad, 0xac, 0x5b, 0x11, 0xad, 0xad, 0x7b, 0x23, 0x5a, 0xe9, 0x6c, 0x3e, 0x5a, 0xf9, 0xcc, 0xde, 0x13, 0xe4, 0xc4, 0x68, 0xb5, 0xb3, 0xb9, 0xda, 0xd9, 0x7c, 0xd4, 0xd9, 0x7c, 0xd4, 0xd9, 0x5c, 0xed, 0x6c, 0xae, 0x76, 0x36, 0x1f, 0x75, 0x36, 0x1f, 0x75, 0x36, 0x1f, 0x75, 0x36, 0x1f, 0x75, 0x36, 0x1f, 0x75, 0x36, 0x1f, 0x75, 0x36, 0x1f, 0x75, 0x36, 0x57, 0x3b, 0x9b, 0xab, 0x9d, 0xcd, 0x47, 0x9d, 0xcd, 0x47, 0x9d, 0xcd, 0xd5, 0xce, 0xe6, 0x6a, 0x67, 0xf3, 0xd1, 0xfa, 0x4f, 0x47, 0xd9, 0xfa, 0x6b, 0xa3, 0x39, 0xf5, 0x37, 0x46, 0x2d, 0xf5, 0x37, 0x45, 0x7d, 0xf5, 0xdf, 0x73, 0x7b, 0x9b, 0xdb, 0x3b, 0xe4, 0x4e, 0x99, 0xe2, 0xeb, 0xa9, 0xf2, 0xa3, 0xa8, 0xad, 0x7e, 0x01, 0x8f, 0x3f, 0x24, 0xea, 0x6e, 0xf8, 0x82, 0xdb, 0x2f, 0xca, 0x65, 0x72, 0xb9, 0x5c, 0x21, 0x57, 0xca, 0x55, 0x32, 0x49, 0xbe, 0x29, 0x57, 0x47, 0x2b, 0x1b, 0xf7, 0x8f, 0x5e, 0x6f, 0x1c, 0x27, 0x1f, 0x91, 0x8f, 0xca, 0xc7, 0xe4, 0x00, 0x39, 0x50, 0x0e, 0x92, 0xf1, 0x72, 0xb0, 0x1c, 0x22, 0x87, 0xca, 0xc7, 0xe5, 0x30, 0xf9, 0x84, 0x1c, 0x2e, 0x47, 0xc8, 0x91, 0x72, 0x94, 0x1c, 0x2d, 0xc7, 0xc8, 0xb1, 0xf2, 0x49, 0x99, 0x20, 0xc7, 0xc9, 0xa7, 0xe4, 0xf8, 0x28, 0xd1, 0x78, 0x82, 0x9c, 0x28, 0x27, 0xc9, 0xc9, 0x32, 0x51, 0x4e, 0x91, 0x53, 0xe5, 0x34, 0xf9, 0x7a, 0xb4, 0xb6, 0xf1, 0x12, 0xb9, 0x4c, 0x2e, 0x97, 0x2b, 0xe4, 0x4a, 0xb9, 0x4a, 0x26, 0xc9, 0x37, 0xe5, 0x6a, 0xb9, 0x46, 0xae, 0x95, 0x6f, 0xc9, 0x75, 0xf2, 0x6d, 0xb9, 0x5e, 0x6e, 0x90, 0xef, 0xc8, 0x8d, 0xd1, 0xa2, 0xc6, 0x9b, 0xe4, 0x7b, 0xec, 0xf1, 0x66, 0xf9, 0xbe, 0x4c, 0x96, 0x5b, 0xe4, 0x56, 0xb9, 0x4d, 0x6e, 0x97, 0x1f, 0xc8, 0x0f, 0xe5, 0x0e, 0xb9, 0x53, 0xa6, 0xc8, 0x54, 0x99, 0x26, 0x77, 0xc9, 0xdd, 0x72, 0x8f, 0xfc, 0x48, 0xee, 0x8d, 0xba, 0x1b, 0xef, 0x93, 0xe9, 0xf2, 0x53, 0xf9, 0x99, 0xfc, 0x5c, 0xee, 0x97, 0x07, 0xe4, 0x17, 0xf2, 0xa0, 0xfc, 0x52, 0x66, 0xc8, 0xaf, 0xe4, 0xd7, 0x32, 0x53, 0x1e, 0x92, 0xdf, 0xc8, 0x6f, 0xe5, 0x61, 0xf9, 0x9d, 0x3c, 0x22, 0x8f, 0xca, 0x2c, 0xf9, 0xbd, 0xda, 0x7c, 0x36, 0x58, 0xa8, 0x6b, 0x2e, 0xd6, 0x0d, 0x96, 0x98, 0xcb, 0x6d, 0x2a, 0x71, 0xa9, 0x39, 0xfc, 0x72, 0xf4, 0x23, 0x15, 0xfd, 0xac, 0x8a, 0x7e, 0x3c, 0x78, 0x25, 0x6a, 0x56, 0xd1, 0x4f, 0x98, 0xe7, 0xdf, 0x57, 0xd5, 0xb3, 0x55, 0xf4, 0x12, 0x73, 0xfd, 0x77, 0x2a, 0xfa, 0x45, 0x15, 0xbd, 0x56, 0x45, 0xb7, 0xaa, 0xe8, 0xc9, 0x2a, 0x7a, 0xb2, 0x79, 0xff, 0x53, 0x55, 0x3d, 0x43, 0x55, 0x4f, 0x56, 0xd5, 0xdf, 0x36, 0xff, 0xd7, 0xa9, 0xec, 0x69, 0x2a, 0x7b, 0xba, 0xf9, 0x3f, 0x10, 0x3b, 0x21, 0x7a, 0x30, 0x76, 0x52, 0xf4, 0xe0, 0xa8, 0x5f, 0x45, 0x8b, 0x55, 0xf6, 0x64, 0x95, 0xdd, 0xaa, 0xb2, 0x5b, 0x55, 0xf6, 0x64, 0x95, 0x3d, 0x59, 0x65, 0x4f, 0xae, 0xbd, 0x39, 0x7a, 0xa8, 0x76, 0x72, 0xf4, 0x90, 0x0a, 0x9f, 0xad, 0xc2, 0x27, 0xd7, 0x0d, 0x45, 0x8b, 0xc7, 0x5c, 0x11, 0x2d, 0xae, 0xdf, 0x56, 0xb6, 0x93, 0x0f, 0xc9, 0xbe, 0xd1, 0xda, 0xfa, 0xfd, 0xdc, 0xee, 0xef, 0x76, 0x9c, 0x7c, 0xc4, 0xfd, 0x8f, 0xca, 0x8d, 0xc1, 0x56, 0xf5, 0xdf, 0x93, 0x29, 0x32, 0x55, 0x16, 0x44, 0x93, 0x55, 0xe5, 0x12, 0x55, 0x39, 0x59, 0x55, 0x4e, 0x56, 0x95, 0x93, 0x55, 0xe5, 0x64, 0x55, 0x39, 0x59, 0x55, 0x4e, 0x56, 0x95, 0x93, 0x55, 0xe5, 0x64, 0x55, 0x39, 0x59, 0x55, 0x4e, 0x56, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0xf9, 0xb8, 0xaa, 0x7c, 0x5c, 0x55, 0x3e, 0xae, 0x2a, 0x1f, 0x57, 0x95, 0x8f, 0xab, 0xca, 0xc7, 0x55, 0xe5, 0xe3, 0xaa, 0xf2, 0x71, 0x55, 0xf9, 0x78, 0xe3, 0xa7, 0xa3, 0xe6, 0xc6, 0xcf, 0xc8, 0x19, 0x72, 0xa6, 0x7c, 0x56, 0x3e, 0x27, 0x67, 0xc9, 0xd9, 0xf2, 0x79, 0xf9, 0x82, 0x7c, 0x51, 0xbe, 0x24, 0xe7, 0xc8, 0xb9, 0x72, 0x9e, 0x7c, 0x59, 0xce, 0x97, 0x0b, 0xe4, 0x42, 0xf9, 0x8a, 0x7c, 0x55, 0x2e, 0x92, 0xaf, 0xc9, 0xc5, 0xf2, 0xf5, 0x68, 0xb6, 0xaa, 0x9f, 0xad, 0xea, 0x67, 0xab, 0xfa, 0xd9, 0xaa, 0x7e, 0xb6, 0xaa, 0x9f, 0xad, 0xea, 0x67, 0xab, 0xfa, 0xd9, 0xaa, 0x7e, 0xb6, 0xaa, 0x9f, 0xad, 0xea, 0x67, 0xab, 0xfa, 0xd9, 0xaa, 0x7e, 0xb6, 0xaa, 0x9f, 0xad, 0xea, 0x67, 0xab, 0xfa, 0xd9, 0xaa, 0x7e, 0xb6, 0xaa, 0x9f, 0xad, 0xea, 0x5b, 0x55, 0x7d, 0xab, 0xaa, 0x9f, 0xa1, 0xea, 0x67, 0xa8, 0xfa, 0x19, 0xaa, 0x7e, 0x86, 0xaa, 0x9f, 0xa1, 0xea, 0x67, 0xa8, 0xfa, 0x19, 0xaa, 0x7e, 0x86, 0xaa, 0x9f, 0xa1, 0xea, 0x67, 0xa8, 0xfa, 0x19, 0xaa, 0x7e, 0x86, 0xaa, 0x9f, 0xa1, 0xea, 0x67, 0xa8, 0xfa, 0x19, 0xaa, 0x7e, 0x86, 0xaa, 0x9f, 0xa1, 0xea, 0x67, 0xa8, 0xfa, 0x19, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0x8d, 0x4f, 0x47, 0x8b, 0x1b, 0x5f, 0x8f, 0x1e, 0x0c, 0x1a, 0x55, 0x7a, 0xb3, 0xca, 0x2e, 0xe9, 0xd5, 0x1d, 0xaa, 0x7a, 0x4d, 0xa5, 0xa2, 0xbb, 0x19, 0xff, 0x5a, 0x5e, 0xf8, 0x16, 0xb9, 0xdb, 0x2b, 0x9f, 0xfb, 0x78, 0x96, 0xf9, 0x51, 0x30, 0x37, 0xd6, 0x7b, 0xc6, 0x90, 0xb9, 0x91, 0x33, 0x37, 0xfa, 0x75, 0xfb, 0x7c, 0xe5, 0x53, 0x7a, 0x57, 0xba, 0xdf, 0xe5, 0xe7, 0x09, 0xf7, 0xd7, 0x48, 0xaa, 0xf2, 0xc9, 0xbd, 0x59, 0x75, 0xdf, 0xaf, 0xce, 0xf3, 0xea, 0x3b, 0xaf, 0xbe, 0xf3, 0xea, 0x3b, 0xaf, 0xa6, 0xf3, 0xea, 0x32, 0xaf, 0x26, 0xf3, 0x6a, 0x32, 0xaf, 0x26, 0xf3, 0x6a, 0x32, 0xaf, 0x26, 0xf3, 0x6a, 0x32, 0xaf, 0x26, 0xf3, 0x6a, 0x32, 0xaf, 0x26, 0xf3, 0x6a, 0x32, 0xaf, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0xfb, 0xd5, 0x64, 0xbf, 0x9a, 0xec, 0x57, 0x93, 0xfd, 0x6a, 0xb2, 0x5f, 0x4d, 0xf6, 0xab, 0xc9, 0x7e, 0x35, 0xd9, 0xaf, 0x26, 0xfb, 0x8d, 0x4b, 0xbf, 0x71, 0xe9, 0x37, 0x2e, 0xfd, 0xc6, 0xa5, 0xdf, 0xb8, 0xf4, 0x1b, 0x97, 0x7e, 0xe3, 0xd2, 0x6f, 0x5c, 0xfa, 0x8d, 0x4b, 0xbf, 0x71, 0xe9, 0x37, 0x2e, 0xfd, 0xc6, 0xa5, 0xdf, 0xb8, 0xf4, 0x1b, 0x97, 0x7e, 0xe3, 0xd2, 0x6f, 0x5c, 0xfa, 0x8d, 0x4b, 0xbf, 0x71, 0xe9, 0x37, 0x2e, 0xfd, 0xc6, 0xa5, 0xdf, 0xb8, 0xf4, 0x1b, 0x97, 0x7e, 0xe3, 0xd2, 0x6f, 0x5c, 0xfa, 0x83, 0xcd, 0x9c, 0xd9, 0x3e, 0x67, 0x75, 0xd0, 0x78, 0x3c, 0xe9, 0x0c, 0x0e, 0xe1, 0xe1, 0x4b, 0x78, 0xb8, 0x18, 0x0f, 0x17, 0xc7, 0x8e, 0x74, 0xff, 0x28, 0x99, 0x50, 0xfe, 0xb7, 0xc6, 0x8a, 0xc1, 0x55, 0xfe, 0x7a, 0x35, 0xd8, 0xdc, 0x98, 0xbc, 0xe8, 0x19, 0x2f, 0x57, 0x47, 0x30, 0x34, 0x82, 0xcb, 0x8d, 0xe0, 0xe2, 0x7f, 0xef, 0xda, 0xd0, 0xa8, 0x87, 0x8c, 0xd1, 0xc9, 0x46, 0x35, 0x6b, 0x9b, 0xab, 0x2a, 0x36, 0x93, 0x70, 0x7f, 0x8d, 0xa4, 0x24, 0x8d, 0xe5, 0x99, 0xa8, 0xa4, 0x63, 0xbd, 0xa2, 0x63, 0xbd, 0x62, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0xd6, 0xbc, 0x12, 0xfc, 0x2c, 0xe8, 0x8b, 0x8a, 0x41, 0xbf, 0x79, 0x30, 0xec, 0x1c, 0x8e, 0x44, 0xc3, 0x41, 0x64, 0x5d, 0x5b, 0x13, 0x0d, 0xc7, 0x1a, 0x18, 0x7c, 0x53, 0x34, 0x18, 0xdb, 0x82, 0x71, 0x6e, 0x69, 0x2d, 0xb1, 0x55, 0xd4, 0x15, 0x7b, 0xaf, 0x6c, 0x2d, 0xdb, 0x18, 0xb1, 0x6d, 0xdd, 0x6e, 0x67, 0x46, 0x6d, 0xef, 0xf6, 0x7d, 0xb2, 0x43, 0xb4, 0x3a, 0xb6, 0xa3, 0xfc, 0x8b, 0xfb, 0x3b, 0xc9, 0xce, 0xb2, 0x8b, 0xf5, 0xc4, 0xae, 0x6e, 0x77, 0xf3, 0xf8, 0x3d, 0xdc, 0xdf, 0xd3, 0xfd, 0xf7, 0xcb, 0x5e, 0x46, 0x7b, 0x6f, 0xb7, 0x1f, 0x90, 0x7d, 0x98, 0xf0, 0x07, 0xdd, 0x8e, 0x95, 0x0f, 0x59, 0x09, 0xec, 0xeb, 0x71, 0x1f, 0x76, 0x7f, 0x3f, 0xaf, 0x3d, 0xae, 0xfc, 0x6f, 0x57, 0xd6, 0x63, 0x1b, 0x3e, 0x71, 0xe2, 0xad, 0xeb, 0x2e, 0xbd, 0xa3, 0x9e, 0x89, 0x86, 0x46, 0x3d, 0x67, 0x0e, 0xbe, 0x10, 0x0d, 0x8f, 0x6a, 0x96, 0x56, 0x79, 0xd9, 0xd7, 0x89, 0xa8, 0x6b, 0xd4, 0x1a, 0xe9, 0x96, 0xa4, 0xa4, 0x24, 0x2d, 0x6b, 0xa5, 0x47, 0x32, 0xd1, 0xf2, 0x51, 0x59, 0xb7, 0xa1, 0xe4, 0x24, 0xcf, 0xc4, 0x7a, 0x7d, 0xaf, 0x2f, 0x4a, 0x8d, 0xea, 0x77, 0x3b, 0x10, 0xc5, 0x47, 0x0d, 0xca, 0x1b, 0x7e, 0x56, 0x94, 0xe1, 0x68, 0xf5, 0xa8, 0x92, 0xdb, 0x11, 0x79, 0xd3, 0xf7, 0xa3, 0xa8, 0xab, 0x36, 0x16, 0x65, 0x6b, 0x6b, 0xac, 0xa5, 0x46, 0xb9, 0x5f, 0x2b, 0x75, 0x51, 0xaa, 0x76, 0xb4, 0x8c, 0xf1, 0xbd, 0xc6, 0x68, 0x75, 0x6d, 0x93, 0xef, 0x1d, 0x67, 0x6d, 0xfb, 0x29, 0x39, 0x41, 0x4e, 0xf2, 0xf5, 0xc9, 0xd1, 0x8a, 0xda, 0x89, 0x6e, 0x4f, 0x89, 0x06, 0x6b, 0x4f, 0x75, 0x7b, 0x5a, 0xb4, 0xb6, 0xf6, 0xd3, 0x1e, 0x7f, 0xba, 0x7c, 0x26, 0x5a, 0x53, 0x5b, 0x5e, 0x3f, 0xdf, 0x13, 0x85, 0x1c, 0xbf, 0xb7, 0x2e, 0x8c, 0xda, 0xeb, 0x72, 0xd1, 0xa2, 0xba, 0xc1, 0x28, 0x3f, 0xe6, 0xa0, 0x68, 0x78, 0xcc, 0x39, 0x72, 0xae, 0xe8, 0x2d, 0xf5, 0xdb, 0xcb, 0xc7, 0xe4, 0x97, 0x2c, 0xed, 0x4f, 0xf2, 0x84, 0xfc, 0x45, 0xe6, 0x47, 0x8b, 0xea, 0x17, 0xc9, 0x32, 0xe9, 0x94, 0xb5, 0xd1, 0xa2, 0x86, 0x46, 0x71, 0xbe, 0x1b, 0x8e, 0x91, 0x2f, 0xb9, 0x7f, 0x8d, 0x7c, 0xdb, 0xfd, 0x1b, 0x65, 0x6a, 0xb4, 0xa6, 0x61, 0x41, 0x94, 0x6b, 0xe8, 0x8a, 0x52, 0x0d, 0xe9, 0x68, 0x59, 0xc3, 0x5a, 0xe9, 0x95, 0x82, 0xac, 0x8b, 0x96, 0x35, 0xfe, 0x84, 0x51, 0x3c, 0x1e, 0x0d, 0x35, 0xfe, 0x51, 0x9e, 0x89, 0x86, 0x1b, 0xff, 0x22, 0xb3, 0x65, 0x8e, 0xfc, 0x55, 0xe6, 0x45, 0x5d, 0x8d, 0xf3, 0xe5, 0x79, 0x69, 0x8e, 0xda, 0x1b, 0x17, 0xba, 0x6d, 0x91, 0x97, 0x64, 0x91, 0x2c, 0x96, 0x25, 0x51, 0xbc, 0xb1, 0xcd, 0xed, 0xd2, 0xa8, 0xa3, 0xe9, 0x99, 0x28, 0xdf, 0x34, 0x87, 0x2d, 0xef, 0x1e, 0xab, 0x8b, 0x2e, 0x52, 0x55, 0x6f, 0xc4, 0x36, 0x8f, 0xbe, 0xab, 0xb2, 0x4a, 0x46, 0xff, 0x3e, 0x23, 0x9d, 0x35, 0xd2, 0xb3, 0xaa, 0xff, 0x46, 0xf8, 0x62, 0xf5, 0x77, 0x22, 0x0a, 0xd5, 0x7e, 0xdd, 0x6b, 0x86, 0x5f, 0x3b, 0x6a, 0x7e, 0x74, 0xff, 0xa8, 0x57, 0xa2, 0x01, 0x67, 0x7f, 0x85, 0xb3, 0xdd, 0x5b, 0x7b, 0x74, 0xd4, 0x57, 0x3b, 0x21, 0x2a, 0x3a, 0xb3, 0x59, 0x67, 0x75, 0x8d, 0x33, 0xba, 0xdc, 0x19, 0x6c, 0x2f, 0xff, 0x1d, 0x79, 0xdd, 0x01, 0xd1, 0x39, 0x75, 0x07, 0x46, 0x67, 0x8c, 0xb9, 0x20, 0xea, 0x71, 0xa4, 0x25, 0x47, 0x5a, 0x6a, 0xdc, 0x41, 0xf7, 0xfd, 0x89, 0xdb, 0x15, 0x56, 0x3d, 0x3f, 0x56, 0xf3, 0x17, 0xab, 0xf3, 0x3f, 0xdb, 0x93, 0xdf, 0xd9, 0x93, 0x0e, 0x7b, 0x72, 0xa9, 0x1a, 0xff, 0x89, 0x1a, 0x1f, 0xa7, 0xc6, 0xc7, 0xa9, 0xf1, 0x71, 0xb1, 0x6d, 0xde, 0x7c, 0x40, 0x8d, 0x1f, 0xaa, 0xc6, 0x37, 0x57, 0xe3, 0xe3, 0xd4, 0xf8, 0xb8, 0xd8, 0x0e, 0x6f, 0x4e, 0x8f, 0xed, 0x28, 0xff, 0xe2, 0xfe, 0x4e, 0xb2, 0xb3, 0xec, 0xf2, 0x66, 0x4e, 0x8d, 0x8f, 0x53, 0xe3, 0x27, 0xc4, 0xf6, 0x78, 0xb3, 0xa0, 0xc6, 0xc7, 0xa9, 0xf1, 0x71, 0x6a, 0xfc, 0x08, 0x35, 0x3e, 0x4e, 0x8d, 0x8f, 0x73, 0x94, 0x27, 0xa8, 0xf1, 0x71, 0x6a, 0x7c, 0x9c, 0x1a, 0x6f, 0x8c, 0xed, 0xeb, 0x39, 0x1f, 0x76, 0x7f, 0xbf, 0x37, 0xfb, 0x62, 0xfb, 0x07, 0x3b, 0x3b, 0xfa, 0xb3, 0xd4, 0xf9, 0x04, 0x5e, 0x35, 0x35, 0x76, 0x48, 0x34, 0x2d, 0x76, 0x74, 0x74, 0x46, 0xec, 0x98, 0xe8, 0xe2, 0xd8, 0xb1, 0x6e, 0x3f, 0xe9, 0x76, 0x42, 0x74, 0xbe, 0xee, 0x75, 0xad, 0xee, 0x75, 0xf5, 0xa8, 0xce, 0x68, 0xe2, 0xa8, 0x57, 0x25, 0x11, 0x8d, 0x53, 0xeb, 0xe3, 0xd4, 0xfa, 0xb1, 0x6a, 0x7d, 0x9c, 0x5a, 0x3f, 0x54, 0xad, 0x8f, 0x53, 0xeb, 0xe3, 0xd4, 0xfa, 0x38, 0xb5, 0xfe, 0x51, 0xb5, 0x3e, 0x4e, 0xad, 0x8f, 0x53, 0xeb, 0xe3, 0x46, 0xe5, 0xdf, 0xcc, 0xa9, 0xf5, 0x13, 0x46, 0x15, 0xa2, 0x53, 0x46, 0xad, 0x93, 0xbe, 0xe8, 0x78, 0x35, 0x7f, 0x82, 0x9a, 0x3f, 0x42, 0xcd, 0x1f, 0xa1, 0xe6, 0xc7, 0xa9, 0xf9, 0x71, 0xa3, 0x86, 0xdf, 0x9c, 0xae, 0xe6, 0xc7, 0xa9, 0xf9, 0x71, 0x6a, 0xfe, 0x08, 0x35, 0x3f, 0xae, 0x36, 0x88, 0x62, 0xce, 0xfc, 0xb8, 0xda, 0x9a, 0x37, 0x73, 0xea, 0x7e, 0x9c, 0xba, 0x1f, 0xa7, 0xee, 0x8f, 0x37, 0x12, 0xc7, 0xd7, 0x8e, 0xf1, 0xbd, 0xc6, 0x37, 0xa7, 0xab, 0xfb, 0x71, 0x46, 0xe5, 0x38, 0xf5, 0x3e, 0xae, 0xf6, 0xe4, 0x37, 0xbf, 0xa0, 0xde, 0xc7, 0x19, 0x99, 0x83, 0xd4, 0xfb, 0x38, 0xa3, 0x73, 0x67, 0xed, 0xa7, 0x3d, 0xee, 0x74, 0xf9, 0x4c, 0xb4, 0x4b, 0xed, 0x19, 0x6e, 0x6f, 0x8e, 0x6e, 0xe6, 0x86, 0x37, 0xd6, 0xde, 0x12, 0xdd, 0x5e, 0x7b, 0x5b, 0x74, 0xbb, 0xba, 0xdf, 0xbc, 0x2e, 0xf7, 0x66, 0xce, 0xa8, 0xed, 0x31, 0x26, 0x13, 0x9d, 0x5c, 0x7f, 0x42, 0x54, 0xac, 0xff, 0xe5, 0x9b, 0xeb, 0xd4, 0xf9, 0x49, 0xea, 0xfc, 0x80, 0xfa, 0xbf, 0xb8, 0x3f, 0xff, 0xcd, 0x5c, 0xfd, 0x22, 0x59, 0x26, 0x9d, 0xd1, 0x89, 0xea, 0x7c, 0x4c, 0x43, 0xe3, 0x9b, 0xb9, 0x86, 0x3d, 0x83, 0x9d, 0x1a, 0xde, 0x1f, 0xec, 0xa1, 0xd6, 0xc7, 0x35, 0x7c, 0xc9, 0xd7, 0xd7, 0x44, 0x9f, 0x54, 0xeb, 0xe3, 0xd4, 0xfa, 0xb8, 0x86, 0xa9, 0x6f, 0xbe, 0xaa, 0x02, 0xce, 0x53, 0xeb, 0xc7, 0xab, 0xf5, 0x93, 0xd5, 0xfa, 0xd7, 0xd4, 0xfa, 0x99, 0x6a, 0xfd, 0x02, 0xb5, 0x7e, 0xb2, 0xaa, 0xd8, 0x5f, 0x3d, 0x8f, 0x53, 0xcf, 0x87, 0xaa, 0xe7, 0x43, 0xd5, 0xf3, 0x24, 0xf5, 0x7c, 0x8e, 0x7a, 0x3e, 0x50, 0x3d, 0x1f, 0xa8, 0x9e, 0xc7, 0xa9, 0xe7, 0x71, 0xea, 0xf9, 0x14, 0xf5, 0x3c, 0x4e, 0x3d, 0x9f, 0xd7, 0xf8, 0x72, 0x34, 0x47, 0xf7, 0xbc, 0x5a, 0x35, 0xb5, 0x34, 0xcd, 0x8e, 0x3e, 0xd6, 0x34, 0xe7, 0xcd, 0xf5, 0xc1, 0x0d, 0xba, 0xe7, 0x3a, 0x55, 0xb5, 0x5e, 0xf7, 0x1c, 0x50, 0xe7, 0xc9, 0xd8, 0x7b, 0xa2, 0x35, 0xb1, 0xcd, 0x64, 0x0b, 0xf5, 0xbe, 0xa5, 0x5a, 0xdf, 0x2a, 0x2a, 0xaa, 0xae, 0xa2, 0xea, 0x2a, 0xaa, 0xaa, 0x92, 0xaa, 0x2a, 0xaa, 0xaa, 0xa2, 0x6a, 0x2a, 0xaa, 0xa6, 0xa2, 0x6a, 0x2a, 0xea, 0x98, 0x25, 0xd5, 0x54, 0x54, 0x4d, 0x33, 0x74, 0xcc, 0x92, 0x6a, 0x2a, 0xaa, 0xa6, 0xa2, 0x6a, 0xea, 0x56, 0x4d, 0x45, 0xd5, 0x54, 0x54, 0x4d, 0xbf, 0x53, 0x4d, 0x45, 0xd5, 0x54, 0xd4, 0x2d, 0x4b, 0x2a, 0xa9, 0x18, 0x63, 0x09, 0xb1, 0x4f, 0x44, 0x99, 0x18, 0x4b, 0x88, 0x1d, 0xe1, 0xf6, 0xcc, 0x28, 0xb1, 0xe1, 0x3d, 0x88, 0x47, 0xfd, 0x35, 0x5a, 0x33, 0x6a, 0x9e, 0xcc, 0x67, 0xe7, 0x0b, 0xdc, 0xbe, 0x10, 0x0d, 0xe8, 0x9a, 0x03, 0xa3, 0x5a, 0xa2, 0xd7, 0x46, 0xbd, 0x28, 0xad, 0xee, 0x27, 0xa2, 0xa2, 0x6a, 0x2a, 0xaa, 0xa6, 0xa2, 0x6a, 0x2a, 0xaa, 0xa4, 0xa2, 0x4a, 0x2a, 0xaa, 0xa4, 0xa2, 0x4a, 0x9a, 0xa1, 0x92, 0x8a, 0x2a, 0xa9, 0xa8, 0x92, 0x8a, 0xba, 0x66, 0x49, 0x25, 0xcd, 0x50, 0x49, 0x7f, 0x51, 0x49, 0x7f, 0x51, 0x45, 0x33, 0x54, 0x51, 0xb7, 0x2a, 0xea, 0x56, 0x45, 0xc5, 0x51, 0xeb, 0xa3, 0xc4, 0xa8, 0x21, 0x29, 0xba, 0x5f, 0x92, 0x11, 0x79, 0xd3, 0xcf, 0x22, 0xf3, 0x35, 0x88, 0x4a, 0x2a, 0xa9, 0x5d, 0x07, 0x2d, 0xa9, 0xa4, 0xa2, 0x4a, 0x2a, 0xea, 0x9c, 0x25, 0xd5, 0x53, 0x54, 0x39, 0x45, 0x55, 0x53, 0x54, 0x31, 0x45, 0xdd, 0xb1, 0xa4, 0x3b, 0x96, 0x74, 0xc6, 0x92, 0xca, 0x28, 0xe9, 0x88, 0xa5, 0xfa, 0xd3, 0xa3, 0x35, 0xf5, 0xd3, 0xa2, 0x56, 0xdd, 0xae, 0xa4, 0xdb, 0x95, 0x74, 0xbb, 0x92, 0x2a, 0x28, 0xa9, 0x82, 0x92, 0x6e, 0x57, 0xd2, 0xed, 0x8a, 0x2a, 0xa0, 0xa8, 0xdb, 0x95, 0x54, 0x40, 0x49, 0x05, 0x14, 0x55, 0x40, 0xd1, 0xe8, 0x0f, 0x19, 0xf9, 0x27, 0x8d, 0xfc, 0x93, 0x46, 0xfe, 0x49, 0x23, 0xff, 0xa4, 0x91, 0x7f, 0x52, 0x67, 0x5b, 0xa7, 0xb3, 0xad, 0xd3, 0xd9, 0xd6, 0xe9, 0x6c, 0xeb, 0x74, 0xb6, 0x01, 0x95, 0x50, 0x54, 0x01, 0x25, 0x15, 0x50, 0x54, 0x01, 0x45, 0x15, 0x50, 0x54, 0x01, 0x45, 0x15, 0x50, 0x54, 0x01, 0xdd, 0x2a, 0xa0, 0x68, 0xd4, 0x07, 0x8d, 0x7a, 0x49, 0x47, 0x2b, 0x05, 0xb1, 0xd8, 0x82, 0xa0, 0x36, 0x08, 0x82, 0x15, 0x35, 0x3f, 0x19, 0x75, 0x49, 0xed, 0xde, 0xa3, 0x77, 0x1b, 0xbd, 0x5b, 0x53, 0x76, 0xab, 0xfb, 0x46, 0x5f, 0x3c, 0xe6, 0xb0, 0xfa, 0xef, 0x34, 0xbc, 0xbf, 0xe9, 0xec, 0xa6, 0x6c, 0x53, 0x76, 0xbb, 0x1f, 0xbd, 0x67, 0xc2, 0x66, 0xbb, 0xf9, 0x6f, 0xe2, 0x16, 0xfb, 0x6e, 0xf7, 0xa3, 0xb7, 0xb2, 0xc7, 0x45, 0x5b, 0xdc, 0xbd, 0xc7, 0x45, 0x5b, 0xdd, 0xf7, 0xde, 0xc3, 0xb7, 0xf9, 0xd3, 0xb6, 0x67, 0x6f, 0xf7, 0xa3, 0xed, 0x16, 0x6f, 0x5f, 0xbb, 0x7d, 0xed, 0x4e, 0xbb, 0x6d, 0x7f, 0xf8, 0xce, 0x63, 0xb7, 0xbf, 0xe3, 0x7d, 0xef, 0x7d, 0xdf, 0x61, 0xef, 0xbb, 0xf8, 0x7d, 0xd9, 0xf7, 0x65, 0x77, 0x3a, 0x7a, 0x87, 0xe4, 0x0e, 0xef, 0xdd, 0xe1, 0xe7, 0x3b, 0x24, 0x77, 0xdc, 0x69, 0xc7, 0x09, 0x3b, 0xde, 0xb7, 0xd3, 0xd1, 0xfe, 0xdb, 0x6d, 0xc7, 0x8e, 0x7f, 0xf9, 0x8c, 0xff, 0x1e, 0x28, 0xdf, 0x7f, 0x2b, 0x7b, 0xd7, 0xee, 0x74, 0xdb, 0xde, 0xb5, 0x3b, 0x8f, 0xf5, 0xdf, 0xb9, 0x3b, 0x4f, 0xd9, 0xe5, 0x7d, 0xe5, 0xff, 0xf6, 0xc8, 0xed, 0x32, 0x61, 0x97, 0x4b, 0xfc, 0x77, 0x8b, 0xff, 0xee, 0xdb, 0xed, 0xa1, 0x3d, 0x2e, 0xda, 0x23, 0xb7, 0x67, 0xfd, 0x5e, 0xbb, 0xec, 0xb5, 0xcf, 0x5e, 0x67, 0xfb, 0xef, 0xa2, 0xbd, 0x2e, 0xdd, 0xeb, 0xda, 0xbd, 0x5a, 0xf6, 0xae, 0xdd, 0xfb, 0xcc, 0xbd, 0x5f, 0xfa, 0xc0, 0x1e, 0xe7, 0x8f, 0xfe, 0xc0, 0x49, 0x1f, 0x78, 0xf5, 0x63, 0x7f, 0x3a, 0xf0, 0xe8, 0x83, 0x5a, 0xc7, 0x37, 0x96, 0x33, 0x7e, 0xfe, 0xc1, 0x87, 0x1e, 0x7a, 0xea, 0x11, 0xfb, 0x1d, 0xb3, 0xdb, 0x31, 0x57, 0x1f, 0x73, 0xdf, 0x31, 0x33, 0x26, 0xec, 0x36, 0x61, 0xbf, 0x09, 0x47, 0x9e, 0xfc, 0xc7, 0x93, 0x5f, 0x3a, 0x79, 0xc5, 0xc9, 0xbd, 0x13, 0x6b, 0x26, 0xd6, 0x4f, 0xdc, 0x7c, 0xe2, 0x36, 0x13, 0x77, 0x9c, 0xb8, 0xdb, 0xc4, 0xbd, 0x27, 0xee, 0x3b, 0xf1, 0xa0, 0x89, 0x87, 0x4d, 0x9c, 0xf8, 0xe9, 0xeb, 0x2f, 0x3f, 0xf6, 0xcc, 0xdf, 0xf9, 0x6f, 0xfe, 0x67, 0x17, 0x9f, 0x9d, 0x3e, 0x6f, 0xbf, 0x2f, 0xbf, 0xe7, 0xfc, 0xd1, 0xe7, 0x1f, 0x7c, 0xfe, 0x09, 0xe7, 0xbf, 0x74, 0xc1, 0xe1, 0x17, 0xfc, 0xfa, 0x82, 0x5f, 0x5f, 0xf9, 0xc7, 0x0b, 0xb7, 0xbb, 0xf0, 0xb6, 0x0b, 0xff, 0x7c, 0xe1, 0xfc, 0x0b, 0xdb, 0xbe, 0xf1, 0xf2, 0xa5, 0x35, 0x97, 0x7e, 0xe6, 0xd2, 0x29, 0x97, 0xd5, 0x5f, 0x36, 0xf1, 0xb2, 0x8b, 0x2e, 0x9b, 0x71, 0xf9, 0xb1, 0x57, 0x8c, 0xbd, 0xe2, 0xe0, 0x2b, 0x26, 0x5c, 0x71, 0xee, 0x15, 0x57, 0x5e, 0x71, 0xc7, 0x15, 0x7f, 0xbc, 0x62, 0xf6, 0x15, 0x2d, 0x57, 0xbc, 0x72, 0x45, 0xf1, 0xca, 0x83, 0xaf, 0xbc, 0xe5, 0xca, 0x07, 0xaf, 0xfc, 0xe3, 0x55, 0x57, 0xfe, 0x1f, 0xff, 0xf5, 0xfb, 0xaf, 0xfe, 0x1d, 0xbe, 0xff, 0xce, 0x8f, 0x7c, 0x97, 0xff, 0x82, 0x9a, 0x60, 0x8f, 0x51, 0xcf, 0x8c, 0x7a, 0x35, 0x08, 0x46, 0xbd, 0x3e, 0x6a, 0x45, 0xb0, 0xfb, 0xa8, 0x44, 0xed, 0x98, 0x60, 0xcf, 0xda, 0xc6, 0xda, 0xa6, 0xe0, 0x53, 0xb5, 0x47, 0xd6, 0x9e, 0x11, 0x9c, 0x50, 0xfb, 0xa5, 0xda, 0x2f, 0x05, 0xe7, 0xd6, 0x4e, 0xaa, 0x9d, 0x14, 0x9c, 0x57, 0x7b, 0x6d, 0xed, 0xb5, 0xc1, 0x97, 0x6b, 0xbf, 0x5b, 0xfb, 0xdd, 0xe0, 0xfc, 0xda, 0x9b, 0x6b, 0x6f, 0x0e, 0x2e, 0xa8, 0x9d, 0x5c, 0x7b, 0x7b, 0x70, 0x61, 0xed, 0xd4, 0xda, 0x69, 0xc1, 0xc5, 0xb5, 0x77, 0xd7, 0xde, 0x1d, 0x5c, 0x52, 0xfb, 0xa3, 0xda, 0x1f, 0x05, 0xdf, 0xa8, 0xbd, 0xb7, 0xf6, 0xde, 0xe0, 0xd2, 0xda, 0xe9, 0xb5, 0x3f, 0x0d, 0x2e, 0xab, 0xdb, 0xa6, 0x6e, 0x9b, 0xe0, 0x8a, 0xba, 0x9d, 0xea, 0x76, 0x0d, 0xae, 0xac, 0xdb, 0xbd, 0x6e, 0xcf, 0xe0, 0xea, 0xba, 0xbd, 0xeb, 0xf6, 0x0f, 0xbe, 0x55, 0x77, 0x40, 0xdd, 0x81, 0xc1, 0xcd, 0x75, 0x7f, 0xad, 0x9b, 0x1b, 0x4c, 0xae, 0x9b, 0x57, 0xf7, 0x7c, 0x70, 0x5b, 0xdd, 0xa2, 0xba, 0x25, 0xc1, 0x0f, 0xeb, 0x5e, 0xaf, 0x5b, 0x15, 0x4c, 0xab, 0x4b, 0xd6, 0xa5, 0x82, 0x1f, 0xd7, 0xad, 0xad, 0xcb, 0x05, 0xf7, 0xd6, 0x0d, 0xd4, 0x15, 0x83, 0xfb, 0xeb, 0xa2, 0xd1, 0xa3, 0x83, 0x5f, 0x8d, 0xde, 0x7c, 0xf4, 0xe6, 0xc1, 0x23, 0xa3, 0xf7, 0x18, 0xbd, 0x77, 0xf0, 0xe8, 0xe8, 0xa1, 0xd1, 0x43, 0xc1, 0xe3, 0x63, 0x3e, 0x34, 0xe6, 0x53, 0xc1, 0x1f, 0xc7, 0x9c, 0x30, 0xe6, 0xb3, 0x41, 0x6b, 0x70, 0x60, 0xd0, 0x17, 0x6c, 0x11, 0xf4, 0x07, 0x3b, 0x04, 0x03, 0x3c, 0x7c, 0x7d, 0xb0, 0x4b, 0x30, 0x24, 0x51, 0xf4, 0x50, 0xac, 0x29, 0xd8, 0x4d, 0x3f, 0x9a, 0xa5, 0x1f, 0xcd, 0x8d, 0x6d, 0x13, 0x34, 0xc6, 0xb6, 0x0d, 0xc6, 0xe8, 0x45, 0x51, 0x6c, 0x87, 0x60, 0xcb, 0xd8, 0x8e, 0xb2, 0x4f, 0x74, 0x57, 0x6c, 0xbf, 0x60, 0x1b, 0x44, 0xfa, 0xf5, 0xa8, 0x27, 0x59, 0xd6, 0x33, 0xc1, 0x0e, 0xa3, 0xe6, 0xb8, 0x7d, 0x3e, 0xd8, 0x71, 0xd4, 0xa2, 0x60, 0x97, 0x51, 0xcb, 0xdc, 0xbe, 0x22, 0xaf, 0xbb, 0x9f, 0x0a, 0xc6, 0x8c, 0xea, 0x0b, 0xb6, 0x1a, 0x35, 0x1c, 0x6c, 0x69, 0x4e, 0xdf, 0x5a, 0x5b, 0x17, 0x6c, 0x55, 0x3b, 0x5a, 0x1a, 0x7d, 0xfd, 0x19, 0x2b, 0xfe, 0x30, 0x8a, 0xea, 0x6c, 0x7f, 0xcc, 0x41, 0xc1, 0x9e, 0x63, 0xce, 0x91, 0x73, 0x83, 0x3d, 0xeb, 0xbf, 0x15, 0xec, 0x68, 0x9e, 0x96, 0x74, 0xeb, 0x18, 0x36, 0xaf, 0x33, 0x37, 0xe7, 0x37, 0x74, 0x05, 0x5b, 0xe9, 0xc2, 0xff, 0xda, 0xf8, 0x78, 0xb0, 0x43, 0xe3, 0x1f, 0xe5, 0x5f, 0x83, 0xcd, 0x1a, 0xe7, 0x07, 0x63, 0x1a, 0x9f, 0x97, 0xe6, 0x28, 0xd2, 0x6d, 0xdb, 0xcc, 0xb9, 0xf6, 0x60, 0x3f, 0x47, 0xd5, 0xe8, 0xa8, 0xb6, 0x71, 0x54, 0xf5, 0x8e, 0x6a, 0x27, 0x47, 0xb5, 0x93, 0xa3, 0xfa, 0xb5, 0xa3, 0xda, 0xa9, 0x7a, 0x54, 0xcf, 0x3a, 0xaa, 0x98, 0xa3, 0x69, 0x74, 0x34, 0x8d, 0x8e, 0x66, 0x9a, 0xa3, 0xd9, 0xc2, 0xd1, 0xfc, 0xaa, 0x7a, 0x34, 0xdb, 0x54, 0x8f, 0x66, 0x5b, 0x47, 0xb3, 0x93, 0xa3, 0xd9, 0xd6, 0xd1, 0x6c, 0xeb, 0x68, 0x76, 0x72, 0x24, 0x9b, 0x39, 0x92, 0x46, 0x47, 0x72, 0x9b, 0x23, 0xd9, 0xcc, 0x91, 0x6c, 0xe6, 0x48, 0x1a, 0x2b, 0x47, 0x62, 0x7b, 0x8e, 0x62, 0x57, 0x47, 0xb1, 0xab, 0xa3, 0xd8, 0xd5, 0x51, 0x6c, 0x8b, 0x39, 0xa3, 0x1c, 0x45, 0xad, 0xa3, 0xe8, 0xab, 0x1e, 0xc5, 0x66, 0x8e, 0xe2, 0x09, 0x47, 0xb1, 0x8d, 0xa3, 0xd8, 0xc6, 0x51, 0xd4, 0xdb, 0xf3, 0x97, 0x2b, 0x7b, 0xfe, 0x1d, 0x76, 0x3d, 0xd7, 0x9e, 0xce, 0x64, 0xd5, 0x73, 0xed, 0xe9, 0xc3, 0xf6, 0xf4, 0x69, 0x3c, 0x48, 0xe1, 0x41, 0x0a, 0x0f, 0x52, 0xc6, 0xa0, 0x0f, 0x0f, 0x52, 0x78, 0x90, 0xc2, 0x83, 0x14, 0x1e, 0xa4, 0xf0, 0x20, 0x85, 0x07, 0xb3, 0xf0, 0x20, 0x85, 0x07, 0xb7, 0xe0, 0xc1, 0x2c, 0x3c, 0x48, 0xe1, 0x41, 0x0a, 0x0f, 0x6e, 0xc0, 0x83, 0x14, 0x1e, 0xa4, 0x1c, 0xe5, 0xdd, 0x78, 0x90, 0xc2, 0x83, 0x14, 0xbb, 0x28, 0x60, 0xc2, 0x2c, 0x4c, 0x48, 0xb1, 0x88, 0x05, 0x0c, 0x62, 0x01, 0x73, 0x68, 0x65, 0x0e, 0xad, 0x8c, 0x79, 0xee, 0xa8, 0xb6, 0x68, 0x2d, 0x53, 0x9e, 0xcb, 0x22, 0xb2, 0x2c, 0x22, 0xab, 0xef, 0xa7, 0xf4, 0xfd, 0x94, 0xbe, 0x9f, 0xd2, 0xf7, 0x53, 0xfa, 0x7e, 0x4a, 0xdf, 0x4f, 0xe9, 0xfb, 0x29, 0x7d, 0xff, 0x16, 0x7d, 0x3f, 0xa5, 0xef, 0xa7, 0xf4, 0xfd, 0x94, 0xbe, 0x3f, 0x4b, 0xdf, 0xbf, 0x45, 0xdf, 0xbf, 0x5b, 0xdf, 0xbf, 0x5b, 0xdf, 0xbf, 0x45, 0xdf, 0xbf, 0x41, 0xdf, 0xbf, 0x41, 0xdf, 0x4f, 0xe9, 0xf7, 0x29, 0xfd, 0x3e, 0xa5, 0xdf, 0xa7, 0xf4, 0xfb, 0x1b, 0xf4, 0xfb, 0x94, 0x7e, 0xff, 0xbc, 0x5e, 0x3f, 0x4b, 0xaf, 0x4f, 0xe9, 0xf5, 0x65, 0x4b, 0x9e, 0xa5, 0xd7, 0xa7, 0x18, 0xf2, 0x5c, 0x86, 0x3c, 0x97, 0x21, 0xcf, 0xd5, 0xf7, 0x53, 0xfa, 0x7e, 0x4a, 0xdf, 0x4f, 0xe9, 0xfb, 0xb3, 0xf4, 0xfd, 0x59, 0xfa, 0xfe, 0x2c, 0x86, 0xd0, 0xca, 0x10, 0x5a, 0x19, 0xc2, 0x53, 0x0c, 0xe1, 0x29, 0xf5, 0xd4, 0x87, 0x03, 0xb3, 0x98, 0xf1, 0x5c, 0x36, 0x3c, 0x97, 0x0d, 0xcf, 0x65, 0xc3, 0x73, 0xd9, 0x70, 0x96, 0x25, 0x64, 0x59, 0x42, 0x96, 0x0d, 0x67, 0xf1, 0x61, 0x16, 0x3e, 0xcc, 0xc2, 0x87, 0x59, 0xf8, 0x30, 0x0b, 0x1f, 0x66, 0xe1, 0xc3, 0x2c, 0x75, 0xf7, 0x46, 0xc3, 0x7e, 0xd1, 0x00, 0x46, 0xa4, 0x30, 0x62, 0x16, 0x46, 0xcc, 0xc2, 0x88, 0x14, 0x46, 0xa4, 0x8c, 0xe0, 0x33, 0x18, 0x71, 0x17, 0x46, 0xdc, 0x85, 0x11, 0x77, 0x61, 0xc4, 0x5d, 0x18, 0x71, 0x17, 0x1e, 0xa4, 0xd4, 0x60, 0x1f, 0x1e, 0xa4, 0xf0, 0x20, 0x85, 0x07, 0x29, 0x3c, 0x48, 0xe1, 0x41, 0x0a, 0x0f, 0x6e, 0xc0, 0x83, 0x14, 0x23, 0x28, 0xb0, 0x81, 0x56, 0x23, 0xbd, 0xa4, 0xe9, 0xe9, 0x68, 0x2d, 0xdb, 0x9d, 0x8b, 0x0f, 0xcf, 0xe3, 0xc3, 0xac, 0xe0, 0xbd, 0x6a, 0x76, 0xb3, 0x4a, 0x8d, 0xfe, 0x5b, 0x7d, 0xd6, 0x57, 0xea, 0x72, 0x43, 0x4d, 0xaa, 0xc7, 0x4a, 0xcd, 0xa9, 0xb3, 0xb7, 0xd7, 0x54, 0xa5, 0x9e, 0x36, 0xd4, 0xd0, 0x01, 0xfc, 0xe2, 0x09, 0x6e, 0xf1, 0x3c, 0xb7, 0xb8, 0x94, 0x5b, 0x9c, 0xc5, 0x2d, 0xce, 0xf2, 0xaa, 0xc3, 0x6a, 0xe3, 0x26, 0xf5, 0xd0, 0x53, 0x99, 0xbb, 0x07, 0x5a, 0x21, 0x1d, 0x14, 0x2d, 0xe3, 0x03, 0xe7, 0xf0, 0x81, 0xaf, 0xf3, 0x81, 0x73, 0xf8, 0xc0, 0xd7, 0x39, 0xf5, 0xfd, 0x9c, 0xe0, 0x72, 0x4e, 0x70, 0x25, 0x27, 0xb8, 0x92, 0x13, 0x9c, 0xc5, 0x09, 0xce, 0xe2, 0x03, 0x67, 0xf1, 0x81, 0xe7, 0xf9, 0xc0, 0xf3, 0x7c, 0xa0, 0x85, 0x0f, 0xb4, 0xf0, 0x81, 0xe7, 0x8d, 0xfd, 0x4d, 0xc6, 0xfa, 0x26, 0x63, 0x7c, 0x93, 0x31, 0xee, 0x31, 0xc6, 0x3d, 0xb8, 0x9e, 0xc4, 0xf5, 0xa4, 0xb1, 0xed, 0xb1, 0xc7, 0x2d, 0x3c, 0x7b, 0x22, 0xcf, 0x3e, 0x11, 0x97, 0xcf, 0x2a, 0x3b, 0xb6, 0xf3, 0xf7, 0xb0, 0xf3, 0xf7, 0xb0, 0xf3, 0xf7, 0xb0, 0xf3, 0xf7, 0xb0, 0xf3, 0xf7, 0x30, 0xc6, 0x3e, 0x81, 0xb1, 0x4f, 0x60, 0xec, 0x13, 0x18, 0xfb, 0x04, 0xc6, 0x3e, 0xef, 0xbc, 0xf5, 0x38, 0xa2, 0x5b, 0x83, 0x53, 0xcd, 0xe1, 0x06, 0x73, 0xb8, 0xc1, 0xec, 0xc8, 0x39, 0xba, 0x66, 0xe7, 0x29, 0xed, 0x08, 0xe3, 0x8e, 0xf0, 0x66, 0x33, 0x25, 0xe7, 0x28, 0x2f, 0x71, 0x94, 0x97, 0x38, 0xca, 0x65, 0xce, 0x5d, 0x68, 0x36, 0xac, 0x75, 0xb4, 0x4f, 0x98, 0x09, 0x6b, 0x1d, 0xf1, 0x53, 0x8e, 0xf8, 0x19, 0xd5, 0xbe, 0xd6, 0xd1, 0x5c, 0xa2, 0xba, 0x73, 0x8e, 0xe8, 0x12, 0x47, 0x74, 0x89, 0x23, 0x8a, 0x3b, 0xa2, 0xb8, 0x23, 0xea, 0x76, 0x44, 0xdd, 0xe6, 0x7a, 0x83, 0xa3, 0x8a, 0xab, 0xfe, 0xf2, 0x3a, 0x31, 0x67, 0xbe, 0x37, 0x38, 0xc2, 0x27, 0x54, 0xf4, 0x5a, 0x47, 0xf9, 0x84, 0xa3, 0x7c, 0xc2, 0x51, 0x3e, 0xe5, 0x28, 0x9f, 0x72, 0x94, 0xf3, 0x1c, 0xe5, 0x3c, 0x47, 0xf9, 0x94, 0xea, 0xed, 0x73, 0xa4, 0xe5, 0x0a, 0x5e, 0xab, 0x72, 0xd7, 0xaa, 0xda, 0x9c, 0xaa, 0xcd, 0xa9, 0xda, 0x9c, 0x4a, 0x5d, 0xab, 0x52, 0xd7, 0xaa, 0x54, 0xdd, 0x59, 0x06, 0xa3, 0x9c, 0xaa, 0xcc, 0xa9, 0xca, 0x9c, 0xaa, 0xcc, 0x39, 0x2b, 0x97, 0xd4, 0x97, 0xdf, 0x8f, 0x6c, 0x91, 0x2c, 0x93, 0x4e, 0x59, 0x1b, 0xad, 0x55, 0x89, 0x6b, 0x55, 0xdf, 0x5a, 0xd5, 0xb7, 0xd6, 0x59, 0x5b, 0xeb, 0xac, 0x3d, 0xe6, 0xac, 0x3d, 0xe6, 0xac, 0x3d, 0xe6, 0xac, 0x3d, 0xe6, 0xac, 0x3d, 0xe6, 0xac, 0x35, 0x3b, 0x6b, 0xcd, 0xce, 0x5a, 0xb3, 0xb3, 0xd6, 0xec, 0xac, 0xc5, 0x9d, 0xb5, 0xa7, 0x54, 0xd7, 0xb0, 0xea, 0xca, 0xa9, 0xae, 0x3e, 0xd5, 0xb5, 0x36, 0xf8, 0xa0, 0x95, 0xcc, 0x0f, 0x9c, 0xb5, 0xbf, 0x5a, 0xc9, 0xcc, 0xae, 0xae, 0x64, 0xae, 0x77, 0xa6, 0x1e, 0xb2, 0x42, 0x2f, 0xf7, 0xf2, 0xb3, 0xac, 0xad, 0x76, 0xb4, 0xc2, 0xd8, 0x5e, 0xb5, 0x7d, 0xa5, 0xf2, 0x6e, 0x3b, 0x07, 0x47, 0x3f, 0xb5, 0xc2, 0xb8, 0xd7, 0x8a, 0xe2, 0x7b, 0x8e, 0xec, 0x38, 0xc6, 0x7e, 0xa5, 0x35, 0x54, 0x03, 0x53, 0x3f, 0x8a, 0xa5, 0xff, 0x54, 0x15, 0x1e, 0xc4, 0xc0, 0x4f, 0x66, 0xe0, 0xd7, 0x32, 0xf0, 0xe1, 0xaa, 0x7d, 0x9f, 0x6a, 0xef, 0x3f, 0x68, 0xef, 0x77, 0x35, 0x87, 0x16, 0xab, 0xd0, 0xbd, 0xed, 0xfd, 0xee, 0xaa, 0x74, 0x17, 0x47, 0xf0, 0x0d, 0x55, 0xfa, 0x11, 0x73, 0x64, 0xb2, 0x3d, 0x3c, 0xdb, 0x5c, 0xb8, 0xba, 0x62, 0xc7, 0x2b, 0xa2, 0xb8, 0xbd, 0x3c, 0xd6, 0x5e, 0xee, 0x1e, 0xdc, 0x59, 0xd9, 0xcb, 0xf5, 0xd1, 0xf4, 0x60, 0x48, 0x86, 0xa3, 0x79, 0xf6, 0xf8, 0x2f, 0xc6, 0x77, 0x9e, 0xbd, 0x7e, 0xd2, 0x1e, 0xff, 0xd0, 0x1e, 0xcf, 0xd2, 0x0d, 0x27, 0xe9, 0x86, 0x93, 0x74, 0xc3, 0x49, 0xf6, 0x7e, 0x4f, 0xdd, 0x70, 0x92, 0x6e, 0x38, 0x29, 0xb6, 0x43, 0xb4, 0x7b, 0x6c, 0x47, 0xf9, 0x17, 0xf7, 0x77, 0x92, 0x9d, 0x65, 0x97, 0xe8, 0x6c, 0x5d, 0x71, 0x92, 0x3a, 0x38, 0x5b, 0x47, 0x9c, 0xa4, 0x23, 0x4e, 0x52, 0x0f, 0x37, 0xea, 0x88, 0x93, 0x74, 0xc4, 0x49, 0x8e, 0xfc, 0xa7, 0x3a, 0xe2, 0x24, 0x1d, 0x71, 0x92, 0xfa, 0x38, 0x5b, 0x37, 0x9c, 0xe4, 0x4c, 0x1c, 0xed, 0x0c, 0x5c, 0xe1, 0x0c, 0x3c, 0xe9, 0x0c, 0x3c, 0x11, 0x3b, 0x34, 0xba, 0x2c, 0xf6, 0x71, 0x39, 0x32, 0xba, 0x26, 0x76, 0x94, 0x1c, 0x6d, 0x4e, 0x1e, 0x13, 0xfd, 0x4a, 0xc7, 0x9c, 0x66, 0xcd, 0xf5, 0x2b, 0x67, 0xe8, 0x87, 0xb1, 0xe3, 0x83, 0x1a, 0xdd, 0xf3, 0x3e, 0xdd, 0xf3, 0xbe, 0xd8, 0x69, 0x48, 0x79, 0x7a, 0xd0, 0x14, 0x3b, 0x23, 0xa8, 0x57, 0x6b, 0xf3, 0x46, 0x2d, 0x8a, 0xa6, 0xab, 0xa5, 0x79, 0x3a, 0xe9, 0x2f, 0x74, 0xd2, 0x5f, 0x8c, 0x7a, 0xdd, 0xd7, 0x89, 0x68, 0x92, 0x6e, 0x3a, 0x49, 0x37, 0xbd, 0x43, 0x37, 0x9d, 0xa4, 0x9b, 0x4e, 0xd2, 0x4d, 0x27, 0xe9, 0xa6, 0x93, 0x74, 0xd2, 0x49, 0x3a, 0xe9, 0x24, 0x9d, 0x74, 0x92, 0xba, 0x3b, 0x5b, 0xbd, 0xdd, 0xa8, 0xde, 0x6e, 0xd4, 0x39, 0x27, 0xe9, 0x9c, 0x93, 0x46, 0x0d, 0xff, 0x7f, 0xec, 0xbd, 0x09, 0x78, 0x53, 0xe5, 0xda, 0xb6, 0xfd, 0x24, 0x1d, 0xb2, 0x56, 0x4a, 0x99, 0x41, 0xc1, 0x32, 0x0f, 0xa2, 0xcc, 0x08, 0x45, 0x19, 0x54, 0x10, 0x51, 0x14, 0x04, 0x65, 0x10, 0x10, 0x05, 0x04, 0x15, 0x07, 0x70, 0xa0, 0x0a, 0x28, 0x4e, 0x20, 0x88, 0x38, 0x55, 0x61, 0x6f, 0x15, 0x70, 0x64, 0x10, 0x10, 0x1c, 0x50, 0x51, 0x26, 0x0b, 0x14, 0x0a, 0x04, 0x02, 0x14, 0x02, 0x1d, 0xa0, 0x2d, 0xb4, 0x49, 0x49, 0x9a, 0x0e, 0xc9, 0x6a, 0x13, 0xda, 0x06, 0x78, 0xfe, 0x73, 0xad, 0x06, 0xac, 0x88, 0xfb, 0xdd, 0xef, 0xf1, 0xbe, 0xff, 0xfe, 0xbf, 0xff, 0xf8, 0x3c, 0x38, 0x4e, 0x56, 0x92, 0x26, 0x6b, 0x78, 0x9e, 0xfb, 0xbe, 0xee, 0xeb, 0x5e, 0x19, 0x96, 0x6c, 0x81, 0x7a, 0x26, 0xa0, 0x9e, 0x09, 0xc4, 0xde, 0x6b, 0xa8, 0x67, 0x02, 0xf1, 0xf7, 0x00, 0xb1, 0x37, 0x16, 0xf5, 0x4c, 0x40, 0x3d, 0x13, 0x88, 0xc1, 0xb1, 0x91, 0x56, 0xd9, 0x02, 0x05, 0x4d, 0x20, 0x16, 0x93, 0x89, 0xc5, 0x64, 0x62, 0x31, 0x19, 0x05, 0x4d, 0x40, 0x41, 0x13, 0x50, 0xd0, 0x04, 0xe2, 0x72, 0x2c, 0x71, 0x39, 0x96, 0xb8, 0x1c, 0x8b, 0x82, 0x7e, 0x85, 0x82, 0x7e, 0x86, 0x82, 0xae, 0x89, 0x7c, 0x53, 0xa6, 0xa2, 0xa2, 0x6b, 0x22, 0xdf, 0x32, 0xde, 0x23, 0x35, 0xbe, 0xe1, 0x4a, 0xdc, 0x8e, 0x25, 0x6e, 0x93, 0x2d, 0xf1, 0xf2, 0x5d, 0xcb, 0x78, 0x98, 0x00, 0x4f, 0x73, 0xdf, 0x2b, 0x9f, 0x24, 0x8e, 0x93, 0x89, 0xe3, 0x64, 0xd4, 0x75, 0x28, 0x51, 0x30, 0x94, 0x28, 0x18, 0x8a, 0xba, 0x0e, 0x25, 0xa6, 0xc7, 0x12, 0xd3, 0x63, 0x89, 0xe9, 0xb1, 0x44, 0xc5, 0x87, 0x44, 0xc5, 0x58, 0x62, 0x7a, 0x2c, 0xda, 0xd5, 0x1c, 0x65, 0x4d, 0x20, 0xb6, 0xc7, 0x12, 0x1d, 0x63, 0x51, 0xd6, 0x04, 0x94, 0x35, 0x81, 0x08, 0xf9, 0x27, 0x2a, 0xaa, 0xf7, 0x54, 0x4b, 0x50, 0xd0, 0x04, 0x14, 0x34, 0x01, 0x05, 0x4d, 0x40, 0x41, 0x13, 0x88, 0x98, 0xd7, 0x50, 0xd0, 0x04, 0xd4, 0xf3, 0x63, 0x22, 0x66, 0x2f, 0xb1, 0x9d, 0x4c, 0xd4, 0x3c, 0x40, 0xd4, 0x8c, 0x15, 0xad, 0x51, 0x4e, 0x13, 0xd5, 0x3e, 0x06, 0x95, 0xa8, 0x87, 0x4a, 0xd4, 0x23, 0x6a, 0xbe, 0xa3, 0xd2, 0xd7, 0x23, 0x62, 0xbe, 0x27, 0x62, 0x76, 0x84, 0x2b, 0xbd, 0x85, 0x4a, 0x6f, 0x61, 0xc6, 0x97, 0x12, 0xe3, 0x6b, 0xa8, 0xf0, 0x31, 0x54, 0xf7, 0x1a, 0x64, 0x7c, 0x3d, 0xaa, 0x7b, 0x0d, 0xaa, 0x7b, 0x0d, 0xb2, 0xbd, 0x1e, 0x95, 0xdd, 0x42, 0xbc, 0x2f, 0xa6, 0xa2, 0x5b, 0x88, 0xf1, 0xcd, 0x54, 0xf3, 0x86, 0x54, 0xf3, 0x86, 0x54, 0xf3, 0x86, 0x54, 0xf3, 0x1a, 0xc4, 0x75, 0x19, 0x71, 0x5d, 0x3f, 0xec, 0x49, 0x76, 0x86, 0xfd, 0x48, 0x0c, 0x95, 0x3c, 0x86, 0x78, 0x3e, 0xc3, 0xde, 0xf9, 0x44, 0x43, 0xf6, 0xe0, 0x20, 0xda, 0xe4, 0x08, 0x67, 0xda, 0x16, 0xf6, 0x62, 0x3f, 0x5b, 0x5e, 0xc5, 0x96, 0xbf, 0x43, 0x61, 0xf5, 0x4f, 0x64, 0xa7, 0xa3, 0xe9, 0x1a, 0x9a, 0xae, 0xd1, 0x6d, 0xed, 0x88, 0xec, 0x8f, 0x87, 0x18, 0x21, 0x0f, 0x5e, 0xfc, 0x2e, 0x8e, 0x65, 0x32, 0x5b, 0x7a, 0x9f, 0x2a, 0x94, 0x42, 0xb5, 0x68, 0x24, 0x53, 0xd8, 0x8a, 0x9e, 0x29, 0x47, 0x45, 0x7c, 0x58, 0xdb, 0x73, 0x59, 0xfb, 0x73, 0xa8, 0xde, 0x38, 0x54, 0x6f, 0x9c, 0x71, 0x7e, 0xa4, 0xb9, 0x5c, 0x47, 0x64, 0xeb, 0x3d, 0xdf, 0xcf, 0x61, 0x6d, 0xf7, 0xa0, 0xed, 0x8b, 0xd0, 0xf6, 0x4f, 0xd0, 0xf6, 0x45, 0x68, 0xfb, 0x27, 0x6c, 0xf9, 0xdb, 0xf0, 0xa7, 0x51, 0xfc, 0xec, 0xc1, 0x32, 0x34, 0xfe, 0x73, 0x34, 0xfe, 0x73, 0x54, 0x71, 0x1c, 0x8a, 0x38, 0x8e, 0x3d, 0x59, 0x8a, 0x2a, 0x8e, 0x43, 0x15, 0x73, 0x51, 0xc5, 0x5c, 0x94, 0x30, 0x17, 0xf5, 0x5b, 0x87, 0xf2, 0xad, 0xa3, 0x96, 0xef, 0xa6, 0x96, 0xef, 0x46, 0x01, 0xd7, 0x11, 0x91, 0x95, 0x44, 0xa4, 0xde, 0xab, 0x55, 0x12, 0x21, 0x7e, 0xf6, 0x7a, 0x06, 0x7b, 0xfd, 0x02, 0x4a, 0x36, 0x8e, 0xbe, 0x6b, 0x72, 0x58, 0xe3, 0xb7, 0xa3, 0x56, 0xdb, 0x51, 0xab, 0xed, 0xa8, 0xd5, 0x76, 0xd4, 0x6a, 0xfb, 0x15, 0x34, 0x3e, 0x97, 0x99, 0xd5, 0xfb, 0xa4, 0x9f, 0x98, 0xbd, 0x52, 0xf6, 0x3e, 0x4a, 0x96, 0x19, 0x67, 0x7d, 0x6a, 0xc9, 0x20, 0x63, 0xe6, 0x21, 0xbf, 0x03, 0xe4, 0xa3, 0xfe, 0xeb, 0xd3, 0x25, 0xfa, 0xe7, 0xa8, 0xf4, 0xad, 0xb1, 0xf6, 0x72, 0x31, 0x03, 0x75, 0x28, 0x63, 0x94, 0xcf, 0xa0, 0x0e, 0x65, 0x3c, 0x3b, 0x9d, 0x67, 0xfb, 0x50, 0x06, 0x37, 0xca, 0xe0, 0x46, 0x19, 0xdc, 0xa6, 0x06, 0xbc, 0xa2, 0x21, 0x5c, 0xc5, 0xed, 0xab, 0xe1, 0x1a, 0x88, 0x83, 0x26, 0xd0, 0x94, 0x35, 0x36, 0x63, 0xa9, 0xd7, 0xc3, 0x96, 0xdc, 0x6e, 0xc5, 0xed, 0xd6, 0xd0, 0x86, 0x99, 0x6a, 0xcb, 0xf2, 0x5a, 0xd0, 0x6b, 0xe4, 0x75, 0x2c, 0xaf, 0x87, 0xf6, 0x3c, 0xa7, 0x03, 0x74, 0xe4, 0x76, 0x27, 0x59, 0x46, 0x56, 0x97, 0x91, 0xd1, 0x65, 0x64, 0xb1, 0x9b, 0x2c, 0x76, 0x93, 0xc5, 0x6e, 0xb2, 0xd8, 0x1d, 0x71, 0x46, 0x96, 0x90, 0xc9, 0x6e, 0x32, 0xd9, 0x4d, 0x26, 0xbb, 0x8d, 0xda, 0x58, 0xc8, 0xb2, 0x08, 0x8a, 0xa1, 0x44, 0x16, 0x19, 0xb5, 0xb2, 0x94, 0x59, 0xaf, 0xaa, 0x97, 0x5b, 0x18, 0xc7, 0x2d, 0x64, 0xb6, 0x9b, 0xcc, 0x76, 0x93, 0xd5, 0x6e, 0xb2, 0xda, 0xcd, 0xb8, 0x6e, 0x21, 0xab, 0xdd, 0x64, 0xb5, 0x8f, 0xac, 0x2e, 0x22, 0xab, 0xdd, 0x64, 0xb5, 0x3b, 0x32, 0x8a, 0x11, 0x88, 0x06, 0x0b, 0x8f, 0xc5, 0x70, 0xff, 0x0e, 0x59, 0x46, 0x66, 0x97, 0x91, 0xd9, 0x65, 0x64, 0xb6, 0x9b, 0xcc, 0x76, 0x93, 0xd9, 0x6e, 0x32, 0xbb, 0x88, 0xcc, 0x2e, 0x22, 0xb3, 0x8b, 0xf0, 0x42, 0x25, 0x64, 0x6f, 0x11, 0xd9, 0x5b, 0x46, 0xc6, 0x96, 0x91, 0xad, 0x65, 0x64, 0x6b, 0x19, 0xd9, 0x5a, 0x41, 0xb6, 0x56, 0x90, 0xad, 0x15, 0x64, 0x6b, 0x05, 0xd9, 0x5a, 0x44, 0xb6, 0x16, 0x91, 0xad, 0x45, 0x64, 0x6b, 0x11, 0xd9, 0x5a, 0x44, 0xb6, 0x16, 0x11, 0xef, 0x6e, 0xb2, 0xd5, 0x4d, 0xb6, 0x16, 0x91, 0xad, 0x45, 0x64, 0xab, 0x9b, 0x6c, 0x75, 0x33, 0x0b, 0x4e, 0xf5, 0x14, 0x39, 0xe0, 0x96, 0x4b, 0x98, 0xe7, 0x25, 0xcc, 0xf3, 0x12, 0xe6, 0x79, 0x09, 0xf3, 0xbc, 0x84, 0x2c, 0x76, 0x5b, 0x77, 0xc9, 0x12, 0xeb, 0x6e, 0xd8, 0x0b, 0xfb, 0xb8, 0x6f, 0x83, 0x03, 0x60, 0x87, 0x83, 0x90, 0x2a, 0xb7, 0x90, 0xd1, 0x6e, 0xea, 0x40, 0x91, 0xfe, 0x5d, 0x58, 0x32, 0xba, 0x8c, 0x8c, 0xf6, 0x91, 0xd1, 0x45, 0x74, 0x23, 0xff, 0xd3, 0xfc, 0x89, 0x17, 0x26, 0xb2, 0xd6, 0x44, 0xd6, 0x9a, 0xfe, 0x32, 0x97, 0x5e, 0x27, 0xe2, 0x82, 0xfa, 0x19, 0x6d, 0x22, 0xae, 0x90, 0xad, 0x14, 0xb1, 0x95, 0x00, 0x31, 0xe4, 0x21, 0x86, 0x3c, 0xc4, 0x90, 0x1e, 0x7d, 0xbb, 0x88, 0xa3, 0x2d, 0xc4, 0xd1, 0x3e, 0xe2, 0xc8, 0x43, 0x1c, 0x79, 0xa8, 0x32, 0x7b, 0xa9, 0x32, 0x7b, 0x89, 0x27, 0x0f, 0xf1, 0xe4, 0x21, 0x9e, 0x3c, 0xc4, 0x53, 0x80, 0x78, 0xd2, 0x1d, 0xc7, 0x77, 0xc4, 0x53, 0x80, 0x78, 0xf2, 0x10, 0x4f, 0xba, 0xf3, 0x28, 0x22, 0x9e, 0x3c, 0xc4, 0x93, 0x87, 0xbd, 0xff, 0x89, 0x78, 0xf2, 0x10, 0x4f, 0x1e, 0xe2, 0xc9, 0x4e, 0x3c, 0x05, 0x88, 0x27, 0x8f, 0x7e, 0xf6, 0xda, 0x38, 0x4b, 0xbd, 0x4d, 0x96, 0x47, 0xec, 0x96, 0xe7, 0x70, 0x22, 0x7e, 0x9c, 0x88, 0x3f, 0x22, 0x8d, 0xdb, 0xe9, 0x90, 0x87, 0xf3, 0x70, 0x82, 0x0b, 0xf2, 0xe1, 0x0c, 0xb1, 0xe1, 0x66, 0x89, 0x43, 0x20, 0xc6, 0x3c, 0xc4, 0xd8, 0x77, 0xc4, 0x98, 0x87, 0x18, 0xf3, 0x10, 0x63, 0x1e, 0x62, 0x2c, 0x40, 0x8c, 0x7d, 0x47, 0x8c, 0x6d, 0x24, 0xc6, 0xbe, 0x23, 0xc6, 0x8a, 0x88, 0xb1, 0x22, 0x62, 0xcc, 0x83, 0x63, 0x29, 0xc1, 0xb1, 0x94, 0x10, 0x6b, 0x1e, 0xaa, 0xc8, 0x5e, 0xe2, 0xcd, 0x43, 0xbc, 0x79, 0x88, 0xb7, 0x22, 0xe2, 0xcd, 0x13, 0x76, 0x31, 0x07, 0x89, 0xb9, 0x00, 0x31, 0xe7, 0x21, 0xe6, 0x3c, 0xc4, 0xdc, 0x46, 0x62, 0x6e, 0x23, 0x31, 0x17, 0xa0, 0xa2, 0xec, 0x25, 0xee, 0x3c, 0xc4, 0x9a, 0x87, 0x58, 0xf3, 0x10, 0x6b, 0xfa, 0xd9, 0xe9, 0x00, 0xb1, 0x16, 0x20, 0xd6, 0x02, 0xc4, 0xda, 0x3e, 0x62, 0x2d, 0x40, 0x95, 0xc8, 0xa7, 0x4a, 0xe4, 0x53, 0x25, 0xf2, 0x95, 0x59, 0xf2, 0xdc, 0x15, 0x7c, 0x77, 0x80, 0x58, 0x0b, 0x10, 0x6b, 0x01, 0x62, 0x2d, 0x40, 0xac, 0x05, 0x88, 0xb5, 0x00, 0xb1, 0xe6, 0x21, 0xd6, 0x74, 0xd7, 0x13, 0x20, 0xd6, 0x02, 0xc4, 0x9a, 0x87, 0x58, 0xd3, 0xdd, 0x4f, 0x21, 0xb1, 0xb6, 0x91, 0x58, 0xdb, 0x41, 0xac, 0xed, 0x20, 0xd6, 0x76, 0x10, 0x6b, 0x3b, 0x88, 0xb5, 0x1d, 0x97, 0xce, 0x3e, 0x27, 0x4b, 0x0f, 0x31, 0xb7, 0x85, 0x98, 0xdb, 0x42, 0xcc, 0xed, 0x23, 0xe6, 0x3c, 0xc4, 0x9c, 0x87, 0x98, 0xf3, 0x10, 0x73, 0x1e, 0x62, 0xce, 0x43, 0xcc, 0x15, 0x11, 0x73, 0x1e, 0x62, 0xce, 0x1e, 0x76, 0x46, 0x01, 0xc3, 0x19, 0x5d, 0x7e, 0x8e, 0xf7, 0x31, 0xe3, 0x1c, 0x6f, 0xfd, 0x0b, 0x2f, 0x33, 0x6b, 0x37, 0x9a, 0x3a, 0x5d, 0xd8, 0x78, 0xd9, 0xb9, 0xd7, 0xd9, 0xf8, 0x82, 0x57, 0xf5, 0x73, 0xad, 0xc6, 0xb9, 0x4e, 0xfd, 0x5c, 0xa6, 0x7e, 0x1e, 0x53, 0x3f, 0x87, 0xa9, 0x9f, 0xbf, 0x1c, 0x21, 0x9b, 0xfe, 0xe1, 0xdc, 0xe4, 0xc5, 0xf3, 0x92, 0x19, 0x52, 0x18, 0xe7, 0x21, 0xab, 0x9c, 0x51, 0x4b, 0x75, 0xc6, 0x85, 0xf2, 0xf0, 0x39, 0xc7, 0xd1, 0xc6, 0xb9, 0xc5, 0xbd, 0x46, 0x9d, 0x1b, 0x7a, 0xe9, 0xbc, 0x61, 0xf5, 0x73, 0x86, 0x9f, 0xb2, 0x97, 0x33, 0x70, 0x46, 0x0b, 0x70, 0x46, 0x0b, 0xfe, 0xe2, 0x8c, 0x74, 0x3c, 0x71, 0x1b, 0x4f, 0xdc, 0xc6, 0xb3, 0xe7, 0x87, 0x88, 0xdb, 0xbe, 0xc4, 0x6d, 0x5d, 0xe2, 0x36, 0x9e, 0xb8, 0x8d, 0x37, 0x35, 0xba, 0x70, 0xc4, 0xd4, 0x18, 0xae, 0xe1, 0x76, 0x1c, 0x34, 0x81, 0xa6, 0x17, 0x4a, 0x89, 0xdb, 0x78, 0xe2, 0x76, 0xb0, 0xa9, 0xe5, 0x85, 0x20, 0x71, 0x1b, 0x4f, 0xdc, 0xc6, 0x13, 0xb7, 0xfd, 0x88, 0xdb, 0x78, 0xe2, 0x36, 0x9e, 0x11, 0x18, 0x44, 0xdc, 0xc6, 0x13, 0xb7, 0xf1, 0xc4, 0x6d, 0x2d, 0x53, 0x07, 0x5e, 0xd3, 0x91, 0xdb, 0x9d, 0x2e, 0x84, 0xaa, 0x8d, 0xca, 0x68, 0x46, 0x65, 0x11, 0xa3, 0xa2, 0x7f, 0xea, 0x71, 0x34, 0x6e, 0x69, 0x34, 0x6e, 0x69, 0x12, 0x6e, 0x69, 0x12, 0x6e, 0x69, 0x14, 0x6e, 0xe9, 0x29, 0xdc, 0xd2, 0x28, 0xdc, 0xd2, 0x53, 0x8c, 0xda, 0x4c, 0x9c, 0xd2, 0x2c, 0x9c, 0xd2, 0x0c, 0x9c, 0xd1, 0x02, 0xdc, 0xd0, 0x02, 0x62, 0x3c, 0x9e, 0x18, 0x8f, 0x27, 0xc6, 0xe3, 0x89, 0xf1, 0x78, 0x62, 0xbc, 0x2f, 0x31, 0x1e, 0x4f, 0x8c, 0xc7, 0x13, 0xe3, 0xf1, 0xc4, 0xf8, 0x60, 0x62, 0x3c, 0x9e, 0x18, 0x8f, 0x27, 0xc6, 0xe3, 0x23, 0x4a, 0x2e, 0x94, 0x12, 0xe3, 0x83, 0x89, 0xf1, 0xbb, 0x88, 0xf1, 0xc1, 0xc4, 0x78, 0x3f, 0x62, 0xbc, 0x1f, 0x31, 0x1e, 0x4f, 0x6c, 0xc7, 0x47, 0x84, 0x2e, 0x1c, 0x21, 0xb6, 0xe3, 0x89, 0xed, 0x78, 0x62, 0xbb, 0x1f, 0xb1, 0x1d, 0x1f, 0x3e, 0x2b, 0x5d, 0x4a, 0x5c, 0xc7, 0x13, 0xd7, 0xf1, 0xc4, 0xf5, 0x5d, 0xc4, 0xf5, 0x5d, 0x91, 0x16, 0x1e, 0xb3, 0x5e, 0x38, 0x42, 0x5c, 0xc7, 0x87, 0xcf, 0x4a, 0xc7, 0x87, 0xcf, 0x4a, 0xc7, 0x87, 0xcf, 0x4a, 0xc7, 0x87, 0xcf, 0x4a, 0x97, 0x46, 0x0e, 0x87, 0xaa, 0xb3, 0xd2, 0xa5, 0xd5, 0xce, 0x4a, 0x2f, 0xc4, 0x31, 0x2d, 0xc7, 0x31, 0x2d, 0xc4, 0x31, 0x2d, 0x27, 0x07, 0xea, 0x46, 0x15, 0x5f, 0x28, 0x25, 0x07, 0xc6, 0x93, 0x03, 0xe3, 0xc9, 0x81, 0xf1, 0x7f, 0x3c, 0x53, 0x2d, 0x4d, 0xbf, 0x9f, 0xa9, 0xe6, 0xf6, 0xae, 0x0b, 0xa5, 0x8a, 0x1d, 0xd2, 0x20, 0x83, 0xe7, 0x54, 0x9d, 0xa9, 0x2e, 0xbd, 0x14, 0x25, 0xb7, 0xc9, 0x78, 0xf5, 0x21, 0xee, 0xeb, 0xd1, 0xf2, 0x12, 0xb7, 0x5f, 0x83, 0x70, 0xd4, 0x90, 0x13, 0x77, 0x85, 0xcf, 0x54, 0x0f, 0x21, 0x27, 0x86, 0x90, 0x13, 0x43, 0xaa, 0x9d, 0xa9, 0x8e, 0x27, 0x1f, 0xfa, 0x92, 0x0f, 0x7d, 0xc3, 0x67, 0xaa, 0x27, 0x92, 0x0f, 0x37, 0x91, 0x0f, 0xf1, 0xe4, 0x43, 0x3c, 0xf9, 0x10, 0x1f, 0x3e, 0x53, 0x1d, 0x5f, 0xed, 0x4c, 0xf5, 0x8c, 0x3f, 0x44, 0xdd, 0x75, 0x61, 0x67, 0x55, 0x1b, 0x67, 0x75, 0x0d, 0xce, 0xea, 0x9a, 0xb0, 0xb3, 0xba, 0xea, 0x2f, 0x9d, 0x55, 0x27, 0x7c, 0x6f, 0x95, 0xbb, 0xaa, 0x8d, 0xbb, 0xaa, 0x83, 0xbb, 0xba, 0x06, 0x77, 0x55, 0x07, 0x77, 0x55, 0x07, 0x77, 0x75, 0x4d, 0x44, 0x29, 0x9e, 0xf8, 0xa2, 0xc3, 0x8a, 0x12, 0x4a, 0x64, 0x34, 0xfc, 0xee, 0xb4, 0x1a, 0xa3, 0xd9, 0x8d, 0xd1, 0xec, 0xc6, 0x38, 0xad, 0x3a, 0xf8, 0xc4, 0x9a, 0xd5, 0x9d, 0x96, 0x7a, 0x4a, 0x28, 0x61, 0xb7, 0x55, 0x1b, 0xb7, 0x55, 0xfb, 0x92, 0xdb, 0xba, 0xc7, 0xd8, 0xcb, 0x72, 0xa8, 0x80, 0x90, 0xcc, 0x63, 0x2f, 0xd3, 0xf0, 0x05, 0x79, 0xec, 0xe5, 0xbe, 0x6a, 0x7b, 0x69, 0x62, 0x2f, 0x4d, 0x68, 0xf5, 0x01, 0xe2, 0x7d, 0x2b, 0x3a, 0x7d, 0x80, 0x38, 0xdf, 0xc8, 0x5e, 0x27, 0xa3, 0xc5, 0x07, 0xf4, 0xbd, 0x26, 0x56, 0xf3, 0x88, 0xd3, 0xbc, 0xf0, 0x37, 0x33, 0x4b, 0xa8, 0xf3, 0x79, 0x1c, 0x81, 0x89, 0x5a, 0x9f, 0xc7, 0xde, 0x9b, 0x88, 0xc3, 0xad, 0xe8, 0xeb, 0x01, 0x62, 0x6f, 0x2b, 0x71, 0xb7, 0x95, 0xb8, 0xdb, 0x48, 0xdc, 0x6d, 0xe4, 0x88, 0x4c, 0xc4, 0xd9, 0x46, 0xe3, 0xa8, 0xcc, 0xf2, 0x00, 0x5a, 0x79, 0x80, 0xa3, 0x32, 0x51, 0x9f, 0xf3, 0xa8, 0xcf, 0x79, 0xd4, 0xe7, 0x3c, 0x74, 0xf2, 0x00, 0x3a, 0x79, 0x40, 0x3f, 0xd2, 0x48, 0xfd, 0xf3, 0x82, 0x73, 0xd1, 0xda, 0x79, 0xd2, 0x4f, 0xfc, 0xb8, 0x89, 0x1d, 0x37, 0xba, 0x79, 0x80, 0x1a, 0x9d, 0x47, 0x8d, 0xce, 0xa3, 0x46, 0xe7, 0x51, 0xa3, 0xf3, 0x14, 0xfd, 0x37, 0x0b, 0xec, 0x90, 0x06, 0x19, 0xe0, 0x91, 0x07, 0xd0, 0xc9, 0x03, 0x97, 0x46, 0xe6, 0x21, 0x6e, 0xcf, 0x80, 0x8b, 0x23, 0xe4, 0x96, 0xc9, 0xc4, 0x42, 0x32, 0xb1, 0x90, 0x4c, 0x2c, 0x24, 0x13, 0x0b, 0xc9, 0xc6, 0x88, 0xa5, 0xca, 0x8d, 0xc6, 0x68, 0x65, 0x51, 0x8b, 0xb3, 0x65, 0x05, 0xb5, 0x36, 0x0f, 0xed, 0x3b, 0x20, 0x6a, 0x32, 0x42, 0x67, 0x8c, 0xf7, 0x6c, 0xeb, 0xcb, 0x7c, 0x46, 0xc2, 0x4b, 0x35, 0xca, 0xd7, 0xdf, 0x4b, 0x35, 0xde, 0xbb, 0xd4, 0xdf, 0x8b, 0xd4, 0xdf, 0x87, 0xd4, 0xdf, 0x83, 0x1c, 0x41, 0xb5, 0x0f, 0xbf, 0x17, 0xa8, 0xbf, 0xe7, 0x27, 0x62, 0x8d, 0xce, 0xbb, 0x9d, 0x7c, 0x4b, 0xf7, 0x43, 0xe1, 0x6b, 0x1b, 0x1e, 0xa5, 0x32, 0x7f, 0x87, 0x53, 0x3b, 0x81, 0x53, 0x2b, 0xa5, 0x42, 0x3f, 0x8b, 0x5b, 0xcb, 0xa6, 0x22, 0x3f, 0x48, 0x45, 0x7e, 0x90, 0xed, 0x4e, 0x66, 0x7b, 0xff, 0xcd, 0x57, 0xe9, 0xef, 0xca, 0x19, 0xaf, 0xac, 0xc7, 0xab, 0xe6, 0xf1, 0xaa, 0x72, 0xf6, 0xee, 0x2b, 0x5e, 0xb9, 0x9d, 0x57, 0xfe, 0xc8, 0x2b, 0x67, 0x85, 0x3f, 0xe9, 0xad, 0x7f, 0xb6, 0x71, 0x0f, 0xaf, 0x9e, 0xc2, 0x9e, 0x67, 0x92, 0xd7, 0x67, 0xd9, 0xfb, 0x60, 0x78, 0xef, 0x5d, 0xac, 0x6d, 0x12, 0x6b, 0x9b, 0x64, 0x39, 0x23, 0xb3, 0x19, 0xd7, 0x1c, 0xd1, 0xc2, 0xf0, 0x97, 0xcd, 0x99, 0xe9, 0xaa, 0xb3, 0x21, 0x4e, 0xd6, 0xe8, 0x61, 0x8d, 0xb9, 0x38, 0x66, 0x2f, 0x8e, 0xb9, 0x08, 0xc7, 0xec, 0xc5, 0x31, 0x17, 0xb1, 0x85, 0xef, 0x59, 0xeb, 0x49, 0x9c, 0x72, 0x01, 0x4e, 0xb9, 0x80, 0x08, 0xc8, 0x63, 0xf6, 0xf3, 0x70, 0xc3, 0x39, 0xb8, 0xe1, 0x1c, 0xa2, 0x20, 0xaf, 0xda, 0x59, 0x8f, 0xa3, 0x54, 0xd7, 0xa3, 0xe1, 0xb3, 0x1e, 0xfa, 0xd5, 0x13, 0xf3, 0xd9, 0x6a, 0x3e, 0xb3, 0x72, 0x92, 0x59, 0x39, 0xc9, 0xac, 0x9c, 0x64, 0x56, 0x4e, 0x32, 0x2b, 0x27, 0xf5, 0x33, 0x1b, 0xc2, 0xca, 0x5e, 0xf8, 0x18, 0xfd, 0x34, 0xb2, 0x46, 0x61, 0x4b, 0xfa, 0x67, 0x5b, 0x4f, 0x73, 0x1c, 0x39, 0x8c, 0x76, 0x8e, 0x45, 0xff, 0xb6, 0xec, 0x0d, 0x3c, 0xf3, 0x9f, 0xb2, 0x82, 0x9c, 0xfa, 0xfd, 0x99, 0x6a, 0xb5, 0x67, 0x66, 0xe8, 0xf9, 0xf4, 0xa7, 0x67, 0xeb, 0xe7, 0x6d, 0x0b, 0x79, 0x76, 0x26, 0xb1, 0x9e, 0x49, 0x9c, 0x67, 0x12, 0xdf, 0x99, 0xc4, 0x76, 0x2a, 0xb1, 0x9d, 0xca, 0xab, 0x53, 0x89, 0x6f, 0x8d, 0xf8, 0xd6, 0xc2, 0x9f, 0xa5, 0x38, 0x4a, 0x4c, 0x67, 0x52, 0xfb, 0x4b, 0x88, 0xdd, 0x4c, 0x62, 0x37, 0x93, 0x58, 0xcd, 0x24, 0x56, 0x33, 0x89, 0xd3, 0x4c, 0xe2, 0x32, 0x93, 0x38, 0xcc, 0x24, 0x0e, 0x33, 0x89, 0xc3, 0x4c, 0xe2, 0x30, 0x93, 0x38, 0xcc, 0x24, 0x0e, 0x33, 0x89, 0xbf, 0x4c, 0xe2, 0x4f, 0xd7, 0xa2, 0x0a, 0x62, 0x4b, 0x43, 0x37, 0xf4, 0x77, 0x6f, 0x33, 0xd9, 0xc7, 0x8b, 0xfb, 0x5b, 0xb5, 0xaf, 0xc6, 0xef, 0xf3, 0xfe, 0x69, 0x3f, 0x5b, 0xf2, 0xac, 0x52, 0x66, 0x61, 0x26, 0xb3, 0x90, 0xc4, 0x2c, 0xcc, 0x67, 0x16, 0x52, 0x98, 0x85, 0x2d, 0xcc, 0xc2, 0x14, 0x66, 0xe1, 0x71, 0x66, 0x61, 0x0a, 0xb3, 0xf0, 0x38, 0x6b, 0x79, 0x9a, 0x7d, 0x0e, 0xb0, 0xcf, 0x01, 0x66, 0x63, 0x12, 0xb3, 0xf1, 0x38, 0xb3, 0xf1, 0x38, 0xb3, 0x31, 0x93, 0xd9, 0x98, 0xc9, 0x2c, 0xcc, 0x64, 0x16, 0x92, 0x98, 0x85, 0x24, 0x66, 0xe1, 0x67, 0x66, 0xe1, 0x67, 0x66, 0x21, 0x89, 0x59, 0x48, 0x62, 0x16, 0x26, 0x32, 0x0b, 0x13, 0x99, 0x85, 0x45, 0xcc, 0xc2, 0x22, 0x66, 0x61, 0x11, 0xb3, 0xb0, 0x88, 0x59, 0x58, 0xc4, 0x2c, 0x24, 0xb1, 0xdf, 0x01, 0x51, 0x97, 0xbd, 0xf8, 0x9c, 0xbd, 0xd0, 0xd8, 0x8b, 0xd5, 0xac, 0xf5, 0x73, 0xd6, 0xfa, 0x39, 0x6b, 0xfd, 0x9c, 0xb5, 0x6a, 0xac, 0x55, 0x63, 0xad, 0x41, 0xd6, 0x1a, 0x64, 0xad, 0x1a, 0x6b, 0xdd, 0xc7, 0xda, 0x36, 0xb1, 0xb6, 0x4d, 0xac, 0x6d, 0x13, 0x6b, 0xdb, 0xc4, 0xda, 0x36, 0xb1, 0x36, 0x8d, 0xbc, 0xa8, 0x8d, 0x67, 0xac, 0x6f, 0xbc, 0x57, 0xad, 0xff, 0xe2, 0xe3, 0x1a, 0xea, 0xdd, 0x6a, 0xfd, 0xbb, 0xf5, 0xc4, 0xe9, 0xb1, 0x8b, 0xdf, 0x8c, 0x27, 0x36, 0x75, 0x97, 0x90, 0xcf, 0x68, 0x14, 0xea, 0x11, 0x4e, 0x24, 0x54, 0xe5, 0xe0, 0x97, 0xbc, 0xea, 0xb4, 0xf1, 0x1b, 0xe8, 0x03, 0xe9, 0xe2, 0x06, 0x19, 0xdf, 0x68, 0x3b, 0x7e, 0xf1, 0x55, 0x11, 0x9a, 0xa8, 0x25, 0xea, 0xff, 0x5b, 0xcf, 0xf4, 0x8b, 0x5a, 0xfa, 0xb3, 0x89, 0x0e, 0x15, 0x95, 0x55, 0x51, 0x59, 0x15, 0xf5, 0x54, 0x55, 0xb7, 0xa8, 0xad, 0x7a, 0xc0, 0x07, 0x7e, 0xd0, 0x50, 0xf6, 0x5a, 0xc6, 0x2c, 0xd4, 0xc6, 0x6b, 0xeb, 0x2a, 0x59, 0x15, 0x5f, 0xde, 0x70, 0x7c, 0x15, 0xb3, 0x16, 0x95, 0xb5, 0x58, 0x58, 0x8b, 0x85, 0xb5, 0xe8, 0x3a, 0x9d, 0xc9, 0x5e, 0x57, 0x32, 0x87, 0xa7, 0x59, 0xa3, 0x45, 0x34, 0x63, 0xe4, 0x66, 0x31, 0x72, 0xb9, 0x8c, 0xdc, 0x9b, 0xcc, 0xdf, 0x2e, 0xe6, 0x6f, 0x33, 0xf3, 0x37, 0x91, 0xf9, 0x7b, 0x94, 0xf9, 0x9b, 0xc8, 0xfc, 0x3d, 0xca, 0x1a, 0x67, 0x5e, 0xfc, 0xfe, 0x01, 0xf3, 0x37, 0x81, 0xf9, 0x9b, 0xc4, 0xfc, 0x4d, 0x62, 0xa4, 0x67, 0x31, 0xd2, 0xb3, 0xc8, 0xa6, 0x75, 0x64, 0xd3, 0x3a, 0x46, 0x7c, 0x16, 0x23, 0x9e, 0xcb, 0x88, 0xe7, 0x32, 0xd2, 0xb9, 0xcc, 0xdd, 0x18, 0xe6, 0x6e, 0x0c, 0xa3, 0xbd, 0x82, 0xd1, 0x5e, 0xc1, 0x68, 0xaf, 0x60, 0xb4, 0x57, 0x30, 0xda, 0x2b, 0x18, 0xed, 0x5c, 0xe6, 0xad, 0x3e, 0xee, 0xbd, 0x1d, 0xb9, 0xdc, 0x85, 0x9e, 0xb1, 0x07, 0x3d, 0x43, 0x3c, 0x0c, 0x92, 0xeb, 0xab, 0x7f, 0xca, 0x9a, 0x2d, 0xa6, 0x32, 0x67, 0x95, 0xa8, 0xc2, 0x05, 0x46, 0xbe, 0x54, 0x57, 0x05, 0xd6, 0xbc, 0x97, 0x35, 0xef, 0xe5, 0x58, 0x9c, 0x1c, 0x4b, 0xa5, 0x91, 0x33, 0x7a, 0xc7, 0x59, 0x61, 0x78, 0x79, 0xdd, 0xc3, 0xb7, 0x91, 0xe7, 0x0d, 0x8f, 0x3e, 0x48, 0xee, 0x37, 0x3c, 0x75, 0x40, 0x9e, 0x67, 0xbf, 0xce, 0xb3, 0x5f, 0xe7, 0x0d, 0x7f, 0xac, 0xfb, 0xe1, 0xea, 0xde, 0x17, 0xdf, 0xfb, 0x97, 0xbe, 0xf6, 0xa2, 0x9f, 0x75, 0xcb, 0x4a, 0x8e, 0xa3, 0x92, 0xe3, 0xa8, 0xe4, 0x38, 0x2a, 0x39, 0x0e, 0xfd, 0xbd, 0xc2, 0xf3, 0x86, 0x0f, 0x55, 0xc2, 0xdf, 0xc1, 0xf6, 0xb2, 0xc5, 0x7d, 0xd5, 0x3f, 0x7f, 0x1e, 0xce, 0x99, 0x22, 0x51, 0x87, 0x7d, 0x74, 0xb1, 0x8f, 0xa5, 0x1c, 0xf1, 0x71, 0x8e, 0x38, 0x8b, 0x57, 0x38, 0x8c, 0x5f, 0x10, 0x19, 0x44, 0x2f, 0x3f, 0x4a, 0x1e, 0xe1, 0x28, 0x83, 0x91, 0x43, 0x8c, 0x59, 0x2a, 0xe5, 0x08, 0x0f, 0x73, 0x84, 0x87, 0x79, 0x75, 0x16, 0xaf, 0xde, 0x42, 0x8e, 0x86, 0xc8, 0xba, 0x7c, 0xd1, 0x8e, 0xb5, 0x68, 0xac, 0x25, 0xc8, 0x91, 0xfa, 0x39, 0x52, 0x3f, 0x47, 0x5a, 0xca, 0x91, 0xfa, 0xff, 0xea, 0xf7, 0x09, 0x38, 0x7a, 0x3f, 0x47, 0x5f, 0xca, 0xd1, 0x97, 0x92, 0x07, 0x39, 0xe4, 0x41, 0x0e, 0xa3, 0x50, 0xca, 0x28, 0xf8, 0x19, 0x05, 0x3f, 0xa3, 0xe0, 0x67, 0x14, 0xfc, 0x8c, 0x82, 0x9f, 0x51, 0xf0, 0x33, 0x0a, 0x7e, 0x46, 0xc1, 0xcf, 0x28, 0xf8, 0x19, 0x05, 0x3f, 0xa3, 0xe0, 0x67, 0x14, 0xfc, 0x8c, 0x82, 0x9f, 0x51, 0xf0, 0x33, 0x0a, 0x1a, 0xa3, 0xa0, 0x31, 0x0a, 0x1a, 0xa3, 0xa0, 0x31, 0x0a, 0x1a, 0xa3, 0x50, 0xaa, 0x7f, 0xe7, 0x9d, 0x91, 0xf0, 0x1b, 0x39, 0xb4, 0x99, 0x99, 0xd5, 0xf5, 0x7e, 0x3f, 0x31, 0xfe, 0x3e, 0x31, 0xbe, 0x34, 0x9c, 0x43, 0x17, 0xbf, 0xe3, 0x71, 0x90, 0x99, 0x7c, 0x49, 0xaf, 0x4a, 0x16, 0xfd, 0xd7, 0x39, 0xde, 0x97, 0x21, 0x43, 0x23, 0xab, 0xc7, 0xb1, 0x12, 0x8e, 0xe3, 0xe3, 0x17, 0xe3, 0xb8, 0x7a, 0xec, 0x1a, 0xda, 0x23, 0x2f, 0x3d, 0xbb, 0xa9, 0x2c, 0x61, 0x24, 0x4a, 0x18, 0x85, 0x12, 0x46, 0xc0, 0xc7, 0x08, 0xf8, 0x8c, 0x57, 0xdf, 0xc5, 0x28, 0x0d, 0x36, 0x3e, 0x4d, 0x54, 0xb5, 0x96, 0x12, 0x3c, 0x81, 0xbe, 0x26, 0x33, 0x5a, 0x69, 0x81, 0xfb, 0x60, 0x38, 0x8c, 0x30, 0xb4, 0x52, 0xef, 0xb3, 0x4b, 0x2e, 0x6e, 0x81, 0x51, 0x28, 0x61, 0x14, 0x4a, 0x18, 0x85, 0x12, 0x46, 0xa1, 0x84, 0x51, 0x28, 0x61, 0x14, 0x4a, 0x18, 0x85, 0x12, 0x46, 0xa1, 0x44, 0xd7, 0x1d, 0x8e, 0xb6, 0x84, 0xfd, 0x36, 0xde, 0x49, 0xa3, 0xb2, 0xf5, 0x40, 0x05, 0xe3, 0xe5, 0x26, 0xb6, 0xfc, 0x7e, 0xf8, 0x9b, 0x17, 0x7a, 0xc6, 0xd8, 0x98, 0xd9, 0x79, 0xcc, 0xe6, 0x5c, 0x66, 0x73, 0xae, 0x50, 0xf1, 0x36, 0x87, 0xf0, 0xfe, 0x81, 0xf0, 0xa7, 0xad, 0xf4, 0xea, 0x75, 0x50, 0xff, 0x5c, 0x4c, 0xd8, 0x13, 0xb4, 0x66, 0x9e, 0xf3, 0x45, 0x34, 0x8e, 0xa8, 0x19, 0xcf, 0xdc, 0x4b, 0xbd, 0x9d, 0xc3, 0x33, 0x0f, 0x73, 0x94, 0x8d, 0x79, 0xe6, 0x47, 0x22, 0x92, 0xbf, 0xc4, 0x18, 0xeb, 0xa8, 0x85, 0x76, 0xb4, 0x93, 0x0b, 0xc5, 0x5c, 0x3a, 0x8b, 0x0a, 0xba, 0x60, 0xfd, 0x33, 0x5d, 0x45, 0x3c, 0x5f, 0xef, 0x86, 0x35, 0x53, 0x0d, 0xf6, 0x27, 0x16, 0x6a, 0x51, 0x17, 0xeb, 0xb0, 0x95, 0xba, 0x50, 0x0f, 0xea, 0xb3, 0xb5, 0x06, 0x64, 0x7b, 0x43, 0xea, 0xf5, 0x55, 0xdc, 0xbf, 0x1a, 0x1a, 0xa1, 0xef, 0x8d, 0xe1, 0x1a, 0x6e, 0xc7, 0x41, 0x13, 0x68, 0xca, 0x3a, 0x9a, 0xb1, 0x6c, 0xce, 0xac, 0xb5, 0xe4, 0x76, 0x2b, 0x6e, 0xb7, 0x86, 0xaa, 0x8e, 0xb8, 0x80, 0xce, 0x42, 0xdf, 0xf3, 0x22, 0x3a, 0x8b, 0x02, 0x3a, 0x8b, 0x02, 0x3a, 0x8b, 0x93, 0x8c, 0xbe, 0x46, 0x67, 0x51, 0xc0, 0xd1, 0x1c, 0xa4, 0x23, 0x2e, 0x89, 0xf8, 0x8d, 0x8e, 0x38, 0xd9, 0xe8, 0x8a, 0x0b, 0x23, 0x52, 0x58, 0xa6, 0xb1, 0x4c, 0x37, 0xde, 0x89, 0x2a, 0xa0, 0x5b, 0x28, 0xa0, 0x5b, 0x28, 0xa0, 0x5b, 0x28, 0xa0, 0x5b, 0xc8, 0xa5, 0x5b, 0x28, 0xa0, 0x5b, 0x28, 0xa0, 0x5b, 0xd0, 0x6b, 0x74, 0x21, 0xdd, 0x42, 0x01, 0xdd, 0x42, 0x01, 0xdd, 0x42, 0x01, 0xb3, 0xa6, 0xa1, 0x32, 0x85, 0x74, 0x0b, 0x79, 0x28, 0x4c, 0x61, 0xb5, 0x8e, 0xb8, 0x80, 0x6e, 0xa1, 0x80, 0x4e, 0xf8, 0x28, 0xdd, 0x42, 0x01, 0xdd, 0x42, 0x41, 0xb8, 0x13, 0x2e, 0x60, 0x86, 0x35, 0x3a, 0x85, 0x02, 0x3a, 0x85, 0x02, 0x3a, 0x85, 0x3c, 0xb4, 0x23, 0x8f, 0x19, 0xd7, 0xe8, 0x80, 0x8f, 0xd2, 0x29, 0x14, 0xd0, 0x25, 0x14, 0xd0, 0x21, 0x14, 0xd0, 0x1d, 0x14, 0x10, 0x05, 0x5a, 0xa4, 0x7e, 0x75, 0x8a, 0x91, 0x52, 0xc3, 0xfd, 0x9f, 0x20, 0x0a, 0x34, 0xdc, 0xbf, 0x86, 0xfb, 0xd7, 0x70, 0xff, 0x9a, 0x32, 0x5c, 0x96, 0xd3, 0x05, 0x17, 0xe2, 0xfc, 0x7d, 0x38, 0x7f, 0x1f, 0xce, 0xdf, 0x87, 0xf3, 0xf7, 0x11, 0x21, 0x1a, 0x11, 0xa2, 0x11, 0x21, 0x1a, 0x11, 0xa2, 0x29, 0x7a, 0x4e, 0x58, 0x81, 0xb1, 0xc2, 0xf5, 0x17, 0x10, 0x29, 0x1a, 0x91, 0xa2, 0xe1, 0xfa, 0x0b, 0x70, 0xfd, 0x05, 0xb8, 0xfd, 0x3c, 0x72, 0xa7, 0x44, 0xd5, 0x23, 0xc9, 0x07, 0x7e, 0xd0, 0x88, 0xa4, 0x1f, 0x60, 0x03, 0x6c, 0xa6, 0xbb, 0xdd, 0x02, 0x5b, 0x61, 0x1b, 0x24, 0xcb, 0x02, 0x3a, 0x80, 0x5c, 0x3a, 0x80, 0x5c, 0x3a, 0x80, 0x13, 0x74, 0x00, 0x05, 0x74, 0x00, 0x05, 0x74, 0x00, 0x05, 0x74, 0x00, 0x05, 0x74, 0x00, 0x05, 0xe1, 0x8e, 0xb8, 0x80, 0x0e, 0xe0, 0x24, 0xd1, 0xa8, 0x89, 0x67, 0x84, 0x4f, 0x7a, 0x85, 0x1f, 0x34, 0x28, 0x95, 0x6f, 0x11, 0x19, 0x4b, 0x45, 0x40, 0xee, 0x10, 0x41, 0xee, 0x9f, 0x85, 0x8b, 0x67, 0xe7, 0x2b, 0xb9, 0x1d, 0xa2, 0x76, 0x9d, 0x63, 0x79, 0x9e, 0x3a, 0x27, 0xc8, 0x17, 0x13, 0x44, 0x40, 0x24, 0x44, 0xc9, 0xf1, 0xa6, 0x68, 0x96, 0x16, 0x50, 0xc8, 0x1f, 0x55, 0x9e, 0x33, 0x59, 0xb9, 0x1d, 0x83, 0xaa, 0x5d, 0x7c, 0x4f, 0xaa, 0xb6, 0xcc, 0x0e, 0xd7, 0xb2, 0xdc, 0x2b, 0xfd, 0x6e, 0x44, 0xc4, 0x46, 0xe9, 0x8d, 0xf8, 0x05, 0x7e, 0x85, 0x4d, 0xb0, 0x59, 0x2e, 0x8d, 0xd8, 0xc2, 0x72, 0x2b, 0x6c, 0x03, 0xfd, 0xbd, 0xa9, 0x24, 0x96, 0xdb, 0x61, 0x07, 0xec, 0x94, 0xe7, 0x8c, 0xf7, 0xaa, 0x76, 0xcb, 0x95, 0xc6, 0xfb, 0x55, 0xfb, 0x78, 0xec, 0x00, 0xe8, 0x67, 0xe0, 0x0f, 0xb2, 0x3c, 0x04, 0xa9, 0xe0, 0xe0, 0x79, 0xc7, 0x58, 0x1e, 0x87, 0x34, 0x9e, 0x9b, 0x0e, 0xfa, 0x59, 0xf9, 0x6c, 0xee, 0x6b, 0xa2, 0x7e, 0x64, 0x3f, 0xe9, 0x8d, 0xbc, 0x0d, 0x6e, 0x87, 0x3b, 0xe4, 0x39, 0x3c, 0xfc, 0x39, 0x3c, 0xfc, 0x39, 0x34, 0xe7, 0x2b, 0x72, 0xdd, 0x13, 0xc5, 0x78, 0xe0, 0xd5, 0xcf, 0x31, 0xcb, 0xeb, 0x99, 0xe5, 0xf5, 0xcc, 0xf2, 0x7a, 0xcb, 0xa3, 0xd2, 0x8b, 0x77, 0x3f, 0x67, 0x99, 0xcf, 0x72, 0x01, 0x7c, 0x05, 0xdf, 0xc2, 0x11, 0x38, 0x0a, 0xe8, 0x94, 0xc5, 0x0d, 0xe7, 0xa5, 0x17, 0x7f, 0x7f, 0x0e, 0x8d, 0x38, 0xa7, 0xa0, 0xf6, 0x4a, 0x6f, 0x18, 0x2e, 0xc7, 0x10, 0x21, 0x2b, 0xd1, 0xb1, 0x62, 0xeb, 0xf7, 0xd2, 0xcb, 0x8c, 0x2e, 0x65, 0x46, 0x97, 0x5a, 0x7f, 0x94, 0x3b, 0xac, 0x3f, 0x71, 0xff, 0x67, 0x60, 0x2c, 0xac, 0x8c, 0x85, 0x95, 0xb1, 0x60, 0xa6, 0xd7, 0x30, 0xd3, 0x6b, 0x98, 0xe9, 0x35, 0xcc, 0xf4, 0x1a, 0x2b, 0x63, 0x60, 0xdd, 0x6e, 0x7c, 0x7a, 0xee, 0x9c, 0x68, 0xc1, 0xac, 0xa5, 0x30, 0xfa, 0x36, 0x46, 0xba, 0x80, 0xfc, 0xad, 0x30, 0xce, 0x8c, 0xd6, 0x97, 0xab, 0x18, 0xe5, 0x53, 0x57, 0xba, 0x0e, 0x0c, 0x23, 0x1c, 0x60, 0x34, 0x03, 0x11, 0xbb, 0xe4, 0xce, 0x88, 0xa3, 0xe4, 0x52, 0xba, 0x74, 0x12, 0xef, 0x05, 0x74, 0xc3, 0xe9, 0x91, 0xd4, 0x82, 0xc8, 0x81, 0x78, 0xb7, 0x21, 0x72, 0x25, 0x75, 0xf3, 0x38, 0x6e, 0xba, 0x40, 0xaf, 0x2e, 0xd5, 0xaf, 0x15, 0x43, 0x77, 0xfb, 0x1b, 0xdd, 0xed, 0x66, 0xf6, 0xde, 0x87, 0xfa, 0xf8, 0xa9, 0x32, 0x9a, 0xae, 0x40, 0x32, 0xdd, 0xf8, 0x6c, 0x53, 0x0c, 0x5b, 0xaf, 0x45, 0x75, 0xa9, 0x2f, 0x37, 0xe8, 0x9e, 0x45, 0xcc, 0xe3, 0x2f, 0x19, 0x44, 0x54, 0x39, 0x91, 0x94, 0x47, 0x24, 0xe5, 0x11, 0x45, 0x0e, 0x14, 0x69, 0x3f, 0x51, 0x52, 0x41, 0x94, 0x38, 0x78, 0x45, 0x31, 0xaf, 0x38, 0x80, 0xee, 0xd8, 0xd0, 0x1d, 0x1b, 0xba, 0x63, 0x33, 0xf6, 0xbf, 0x81, 0xf1, 0x69, 0xa8, 0x43, 0xe8, 0x8e, 0x0d, 0xdd, 0xb1, 0xa1, 0x3b, 0xeb, 0xd0, 0x9d, 0x75, 0xe8, 0x8e, 0x0d, 0xdd, 0xb1, 0xa1, 0x3b, 0x36, 0x74, 0x67, 0x31, 0xba, 0x63, 0x43, 0x77, 0x34, 0x74, 0x67, 0x31, 0xba, 0x63, 0x43, 0x77, 0x6c, 0xe8, 0x4e, 0x36, 0xba, 0x63, 0x43, 0x77, 0x6c, 0xe8, 0x8e, 0x86, 0xee, 0xd8, 0xd0, 0x1d, 0x5b, 0xf8, 0x4c, 0xdc, 0x62, 0x74, 0xc7, 0xc6, 0xde, 0x6d, 0x20, 0x82, 0x1c, 0x44, 0x4b, 0x1e, 0x11, 0xe2, 0x20, 0x22, 0xf2, 0xd0, 0x1a, 0x1b, 0x5a, 0x63, 0x43, 0x6b, 0x6c, 0x68, 0x8d, 0x0d, 0xad, 0xd1, 0x3f, 0xf1, 0x64, 0x43, 0x6b, 0x6c, 0x68, 0x8d, 0x0d, 0xad, 0xd1, 0xd0, 0x1a, 0x1b, 0x5a, 0x63, 0x43, 0x6b, 0x6c, 0x68, 0xcd, 0x62, 0xb4, 0x46, 0x43, 0x6b, 0x2a, 0xd1, 0x1a, 0x0d, 0xad, 0xc9, 0x46, 0x6b, 0xb2, 0xd1, 0x1a, 0x1b, 0x5a, 0x63, 0x43, 0x6b, 0xd6, 0xa1, 0x35, 0x36, 0xb4, 0xc6, 0x86, 0xd6, 0x64, 0xa3, 0x35, 0x36, 0xb4, 0x66, 0x31, 0x5a, 0x63, 0x43, 0x6b, 0x6c, 0x68, 0x8d, 0xee, 0x53, 0xf4, 0x4f, 0x3a, 0x2d, 0x46, 0x6b, 0xd6, 0xa1, 0x35, 0x36, 0x22, 0xd0, 0x41, 0x04, 0x3a, 0x88, 0x40, 0x07, 0xba, 0x63, 0x43, 0x77, 0x6c, 0xe8, 0x8e, 0x0d, 0xdd, 0x59, 0x8c, 0xee, 0x2c, 0x46, 0x77, 0x16, 0xa3, 0x3b, 0x87, 0xd0, 0x9d, 0xc5, 0x44, 0x66, 0x39, 0x91, 0xe9, 0x20, 0x32, 0x73, 0x88, 0xcc, 0x1c, 0x22, 0x33, 0x87, 0xa8, 0x74, 0x10, 0x71, 0x0e, 0x22, 0xce, 0x81, 0x06, 0x6d, 0x46, 0x83, 0x36, 0xa3, 0x41, 0x9b, 0xd1, 0xa0, 0xcd, 0x68, 0xd0, 0x62, 0x34, 0x68, 0x31, 0x1a, 0xb4, 0x18, 0x0d, 0x5a, 0x8c, 0x06, 0x2d, 0x46, 0x83, 0x16, 0xa3, 0x41, 0x36, 0x34, 0xc8, 0x86, 0x06, 0x2d, 0x46, 0x83, 0x16, 0xa3, 0x41, 0x36, 0x34, 0xc8, 0x86, 0x06, 0x55, 0xa2, 0x41, 0x15, 0x68, 0x50, 0x05, 0x1a, 0x54, 0x81, 0x06, 0x55, 0xa0, 0x41, 0x15, 0x44, 0x6a, 0x39, 0x11, 0xa8, 0x9f, 0x85, 0xb3, 0xa1, 0x39, 0x95, 0x68, 0x8e, 0xfe, 0xe9, 0xa8, 0x43, 0x68, 0x8e, 0x0d, 0xcd, 0xb1, 0xa1, 0x39, 0x36, 0x34, 0xc7, 0x86, 0xe6, 0xd8, 0xd0, 0x9c, 0x6c, 0x34, 0xc7, 0x66, 0x9c, 0x85, 0xdb, 0x2c, 0x1d, 0xe8, 0xce, 0x62, 0x61, 0x36, 0xce, 0xb5, 0x76, 0x92, 0x41, 0x51, 0xc3, 0x78, 0xa7, 0xba, 0xb6, 0xd1, 0x8b, 0x56, 0x52, 0x13, 0xbd, 0xc6, 0xef, 0xd2, 0x0c, 0xc2, 0xcb, 0x55, 0x7d, 0x4f, 0xd1, 0xf8, 0x0e, 0x29, 0x35, 0xb1, 0x8c, 0x9a, 0x78, 0x8a, 0x9a, 0x78, 0x4a, 0x44, 0xf0, 0x8a, 0x83, 0x54, 0x38, 0xfd, 0x7d, 0xcc, 0x65, 0x54, 0xf3, 0x5a, 0x28, 0x92, 0x59, 0xff, 0x9f, 0xc7, 0x2c, 0x38, 0xe6, 0xfa, 0xd4, 0xb1, 0x06, 0x46, 0xbf, 0x57, 0xce, 0x33, 0xca, 0x59, 0x6f, 0x05, 0xf3, 0x18, 0x64, 0xee, 0xca, 0x99, 0xab, 0x72, 0xe6, 0xaa, 0x84, 0xb9, 0x2a, 0x67, 0xec, 0x4b, 0x18, 0xfb, 0x12, 0x8e, 0xb1, 0x84, 0x63, 0xf4, 0xa8, 0xfa, 0x7b, 0xb0, 0x3e, 0xf0, 0x83, 0x66, 0x9c, 0x5d, 0x0c, 0x72, 0x5c, 0x41, 0xba, 0xd8, 0xff, 0x4a, 0xb7, 0x74, 0x0d, 0x09, 0x6b, 0x85, 0x9e, 0xf7, 0x7a, 0xae, 0xeb, 0xb9, 0x6d, 0xec, 0x4b, 0x16, 0xfb, 0x90, 0xc5, 0x2b, 0x03, 0x38, 0xb8, 0xdc, 0x7f, 0xf5, 0x59, 0x75, 0xdc, 0x9c, 0x5f, 0xdf, 0x1f, 0xd6, 0x94, 0x4b, 0xee, 0xe9, 0x9f, 0x05, 0xce, 0xbb, 0xf8, 0xfb, 0x05, 0xac, 0xf5, 0x34, 0x6b, 0xd5, 0x44, 0x13, 0xd6, 0xe8, 0xe4, 0xe8, 0x72, 0xc8, 0x0c, 0x1f, 0x19, 0x71, 0x86, 0x8c, 0xd0, 0x3f, 0x03, 0xa2, 0x77, 0xf3, 0x1e, 0xe3, 0x33, 0x1e, 0x38, 0x0b, 0x8e, 0x36, 0x87, 0xa3, 0xcd, 0xe7, 0x68, 0xf3, 0x39, 0xda, 0x4c, 0x8e, 0x36, 0x9f, 0x48, 0x3c, 0xc3, 0x11, 0xeb, 0xbd, 0x72, 0x26, 0x91, 0x76, 0x86, 0x08, 0xf2, 0x71, 0xe4, 0x99, 0x1c, 0xb9, 0x93, 0x23, 0x77, 0x72, 0xe4, 0x4e, 0x8e, 0xdc, 0xc9, 0x91, 0x3b, 0x39, 0xf2, 0x1c, 0x8e, 0x3c, 0x87, 0x19, 0xf5, 0xe9, 0x9f, 0x61, 0x10, 0x0d, 0xd8, 0xea, 0x59, 0x72, 0xcd, 0x4d, 0x8e, 0xb9, 0xc9, 0x1f, 0xfd, 0x5d, 0x91, 0x73, 0xc4, 0xbe, 0x9b, 0x58, 0x76, 0x13, 0xbb, 0xfa, 0x3b, 0x10, 0x6e, 0x62, 0xd3, 0x4d, 0x6c, 0xea, 0x67, 0x36, 0xdc, 0xc4, 0x99, 0x9b, 0x38, 0x73, 0x13, 0x67, 0x6e, 0xe2, 0xcc, 0x4d, 0x9c, 0xb9, 0x89, 0x33, 0xfd, 0x5d, 0x05, 0x37, 0xf1, 0xe5, 0x26, 0x06, 0xdc, 0xc2, 0x2a, 0xcc, 0x46, 0x27, 0x7e, 0x71, 0xc6, 0xf7, 0x1a, 0xef, 0x55, 0x55, 0xfd, 0xda, 0xd0, 0xa5, 0xdf, 0x36, 0x89, 0x19, 0x20, 0xf7, 0x8b, 0x56, 0xa2, 0xb5, 0x7c, 0x59, 0xb4, 0x91, 0x9f, 0x8a, 0x76, 0x72, 0x9f, 0xb8, 0x4e, 0x3a, 0x45, 0x7b, 0xb9, 0xd2, 0x7c, 0x97, 0x68, 0x6f, 0x1e, 0x26, 0xae, 0x33, 0x8f, 0xe5, 0xf6, 0x14, 0xb9, 0x5e, 0x19, 0x26, 0x5f, 0x56, 0xee, 0x85, 0x11, 0x30, 0x0a, 0xee, 0x87, 0xd1, 0x30, 0x06, 0x9e, 0x91, 0xfb, 0x94, 0x67, 0xe1, 0x39, 0xfc, 0xff, 0x74, 0x48, 0x80, 0xe7, 0xe1, 0x05, 0x98, 0x01, 0xb3, 0xe5, 0x4a, 0xe5, 0x65, 0x78, 0x05, 0x5e, 0x85, 0xb9, 0xf0, 0x06, 0xcc, 0x83, 0xf9, 0xf0, 0x26, 0x6a, 0xfd, 0xa9, 0x68, 0xc8, 0xde, 0x7c, 0x64, 0x0e, 0x8a, 0x08, 0x7a, 0x91, 0x48, 0x88, 0x22, 0x1a, 0xa3, 0x71, 0xea, 0x16, 0x69, 0x13, 0x0a, 0xa8, 0xdc, 0xb6, 0xa2, 0x6e, 0x31, 0x2c, 0x6b, 0x40, 0x2c, 0xd4, 0xe4, 0xf1, 0x5a, 0x2c, 0xf1, 0x9e, 0xa2, 0x0e, 0xcb, 0xba, 0x50, 0x4f, 0x9e, 0x10, 0xf5, 0x59, 0x36, 0x80, 0x86, 0x1c, 0xd1, 0x55, 0x2c, 0xaf, 0xd6, 0xdf, 0x07, 0x81, 0xc6, 0x80, 0xc2, 0x09, 0x14, 0x4e, 0xa0, 0x70, 0xa2, 0x29, 0xf7, 0x9b, 0x41, 0x73, 0x6e, 0xb7, 0x60, 0xd9, 0x92, 0x68, 0x6f, 0x05, 0xad, 0xe5, 0x48, 0x46, 0x64, 0x9a, 0x68, 0x2b, 0xf7, 0x8a, 0x6b, 0xa1, 0x9d, 0x7c, 0x8f, 0x91, 0x59, 0x21, 0xae, 0x47, 0x61, 0xdb, 0xf3, 0x78, 0x07, 0x7c, 0xe1, 0x1f, 0xaf, 0x02, 0x54, 0x20, 0xba, 0xca, 0xed, 0xa2, 0x1b, 0xdc, 0x00, 0x44, 0x96, 0x20, 0xb2, 0x44, 0x3c, 0xf4, 0x94, 0xc9, 0xe2, 0x46, 0xb8, 0x09, 0x7a, 0xc9, 0x6d, 0xa2, 0x37, 0x8f, 0xf5, 0x81, 0xff, 0xef, 0xaf, 0xec, 0x50, 0x20, 0x26, 0xc0, 0x44, 0x78, 0x18, 0x26, 0xc1, 0x64, 0x78, 0x44, 0x1e, 0x14, 0x8f, 0x8a, 0x1a, 0xe2, 0x31, 0x98, 0xc2, 0xfd, 0xc7, 0xe1, 0x09, 0x78, 0x12, 0x9e, 0x82, 0xa9, 0x30, 0x0d, 0x9e, 0xe6, 0x18, 0x9e, 0x81, 0x67, 0xe1, 0x39, 0xee, 0x4f, 0x87, 0x04, 0x8e, 0xf1, 0x79, 0x96, 0x2f, 0x48, 0xbb, 0x98, 0x01, 0x33, 0x61, 0x16, 0xbc, 0x68, 0xfc, 0x1e, 0x8d, 0x7e, 0xe5, 0x87, 0x0c, 0xf1, 0x32, 0xbc, 0x02, 0xaf, 0xc2, 0x6b, 0xf0, 0x3a, 0xcc, 0x81, 0xb9, 0xf0, 0x06, 0xcc, 0x83, 0xf9, 0xec, 0xc3, 0x9b, 0x50, 0x75, 0x95, 0xf3, 0x74, 0xb1, 0x90, 0x39, 0xa9, 0xfa, 0xad, 0x6b, 0x8f, 0x89, 0xca, 0x61, 0x3a, 0x43, 0x3c, 0xbb, 0xe5, 0x76, 0x93, 0xfe, 0x0d, 0x95, 0x22, 0xe2, 0xba, 0x98, 0x4a, 0x57, 0x62, 0xfc, 0xb6, 0x54, 0x71, 0xf8, 0xb7, 0xac, 0x8a, 0xcd, 0xfd, 0x64, 0x92, 0x79, 0x80, 0xb4, 0x99, 0x6f, 0x67, 0x79, 0xa7, 0x3c, 0x40, 0x44, 0xdf, 0x62, 0xbe, 0x9b, 0xfb, 0xf7, 0xc8, 0x22, 0x22, 0xbb, 0x87, 0xf9, 0x5e, 0x6e, 0x0f, 0xe7, 0x6f, 0x63, 0xe5, 0x34, 0xf3, 0x03, 0x32, 0xdf, 0x3c, 0x9e, 0xfb, 0x13, 0xf9, 0xdb, 0x64, 0x6e, 0x3f, 0x22, 0xb7, 0x99, 0x1f, 0x93, 0x0e, 0x22, 0x7f, 0x9a, 0xf9, 0x71, 0xb9, 0xd7, 0xfc, 0x04, 0x8f, 0x3f, 0x09, 0x4f, 0xf1, 0xb7, 0x67, 0x64, 0x81, 0xf9, 0x59, 0x98, 0x2e, 0xb7, 0x9b, 0x39, 0x5e, 0xf3, 0x4c, 0x6e, 0xcf, 0x82, 0xd9, 0xc0, 0x71, 0x99, 0x39, 0x2e, 0xf3, 0xeb, 0xd2, 0x63, 0x9e, 0xc7, 0x36, 0xdf, 0x14, 0xb5, 0xcc, 0x0b, 0x78, 0xce, 0x42, 0xee, 0x27, 0x4a, 0xbb, 0x79, 0x11, 0xb7, 0xf5, 0xdf, 0xca, 0xfe, 0x84, 0xe7, 0x2e, 0x81, 0xa5, 0x3c, 0xfe, 0xef, 0x5f, 0x55, 0x22, 0xc3, 0xfc, 0x0d, 0xfc, 0x2c, 0x0f, 0x44, 0x26, 0xc8, 0x82, 0xa8, 0xfa, 0xd0, 0x54, 0x3a, 0x7e, 0xbf, 0xa2, 0x84, 0xa8, 0x11, 0xb5, 0x4b, 0xd4, 0x8b, 0xda, 0x6d, 0x5c, 0x45, 0xfd, 0x4a, 0x57, 0x95, 0xb0, 0x45, 0xe5, 0xf0, 0xfc, 0x53, 0x70, 0x1a, 0xe5, 0xcb, 0x37, 0xae, 0xaa, 0x9e, 0x1c, 0xe5, 0x65, 0x59, 0x28, 0x0f, 0x46, 0xb7, 0x94, 0x45, 0xd1, 0xb8, 0xe5, 0xe8, 0xd6, 0xc6, 0x6f, 0xf1, 0x64, 0x44, 0x57, 0xa0, 0x8c, 0xed, 0x65, 0xae, 0xa5, 0x03, 0x74, 0x04, 0xf4, 0xd9, 0xd2, 0x19, 0x50, 0x5a, 0x4b, 0x57, 0xe8, 0x06, 0x37, 0x40, 0x4f, 0xb8, 0x11, 0x6e, 0x82, 0x5e, 0xd0, 0x1b, 0xfa, 0x40, 0x5f, 0xb8, 0x19, 0x6e, 0x81, 0x5b, 0xa1, 0x1f, 0xf4, 0x87, 0xdb, 0x60, 0x00, 0xdc, 0x0e, 0x03, 0xe1, 0x0e, 0xb8, 0x13, 0x26, 0xc2, 0xc3, 0xf2, 0x94, 0x65, 0x12, 0x30, 0xf6, 0x96, 0x29, 0xb2, 0xc8, 0xf2, 0x38, 0x30, 0xf6, 0x96, 0x27, 0xa5, 0xcd, 0xf2, 0x14, 0x4c, 0x85, 0x69, 0xf0, 0x1c, 0x4c, 0xe7, 0xf1, 0x04, 0x78, 0x1e, 0x5e, 0x80, 0x19, 0x30, 0x13, 0x5e, 0xe4, 0xb5, 0x2f, 0xc1, 0x6c, 0x6e, 0xbf, 0x0c, 0xaf, 0xc0, 0xab, 0xf0, 0x1a, 0xbc, 0x0e, 0x73, 0x60, 0x2e, 0xbc, 0x05, 0x0b, 0xe1, 0x6d, 0x78, 0x07, 0xde, 0x85, 0xf7, 0xe0, 0x7d, 0x48, 0x84, 0x0f, 0xe0, 0x43, 0x58, 0x04, 0xff, 0x80, 0x7f, 0xc2, 0x47, 0xf0, 0x31, 0x7c, 0x02, 0xcb, 0x61, 0x05, 0xfb, 0xb1, 0x12, 0x56, 0xc1, 0xd7, 0xb0, 0x1a, 0xd6, 0xc0, 0x5a, 0xf8, 0x86, 0xbf, 0x7f, 0x07, 0xdf, 0xc3, 0x0f, 0xb0, 0x01, 0x7e, 0x84, 0x3d, 0xb0, 0x17, 0xf6, 0x81, 0x0d, 0xf6, 0xc3, 0x01, 0xb0, 0xc3, 0x41, 0x70, 0xc0, 0x31, 0x38, 0x0e, 0x69, 0x90, 0x0e, 0x19, 0x90, 0x09, 0x1e, 0x28, 0x90, 0x27, 0x2c, 0x85, 0x40, 0xdc, 0x5b, 0x4a, 0xc0, 0x07, 0x7e, 0xd0, 0xa0, 0x14, 0xca, 0x20, 0xc0, 0xf1, 0xd3, 0x95, 0x59, 0xce, 0x42, 0x39, 0x54, 0x40, 0x25, 0xd0, 0x91, 0x59, 0xa4, 0x2c, 0x52, 0x04, 0x98, 0xc0, 0x0c, 0x11, 0x10, 0x09, 0x68, 0xad, 0x62, 0x01, 0x05, 0xd0, 0x5a, 0xc5, 0x0a, 0x68, 0xad, 0x12, 0x2b, 0x6d, 0x0a, 0x3a, 0xab, 0xd4, 0x82, 0xda, 0x80, 0x93, 0x54, 0x70, 0x92, 0x0a, 0x4e, 0x52, 0xa9, 0x0f, 0xb8, 0x47, 0xa5, 0x11, 0x34, 0x06, 0xb4, 0x55, 0x41, 0x5b, 0x15, 0xb4, 0x55, 0x69, 0x0a, 0xb8, 0x47, 0x05, 0x6d, 0x55, 0x5a, 0x40, 0x4b, 0xc0, 0x09, 0x29, 0x38, 0x48, 0xa5, 0x0d, 0xe0, 0x20, 0x15, 0x1c, 0xa4, 0xd2, 0x0e, 0x70, 0x90, 0x0a, 0x0e, 0x52, 0xe9, 0x0e, 0x3d, 0xd9, 0xe6, 0x8d, 0x70, 0x13, 0x0e, 0xab, 0x2f, 0xcb, 0x9b, 0xe1, 0x16, 0xb8, 0x15, 0xfa, 0x41, 0x7f, 0xb8, 0x0d, 0x06, 0xc0, 0xed, 0x30, 0x10, 0x4f, 0x70, 0x07, 0xdc, 0x09, 0x83, 0xe0, 0x6e, 0x18, 0x0c, 0x43, 0xe0, 0x1e, 0x18, 0x0a, 0xc3, 0xe4, 0x48, 0xaa, 0xd7, 0x48, 0xaa, 0xd7, 0x48, 0xaa, 0xd7, 0x48, 0xaa, 0xd7, 0x48, 0xaa, 0xd7, 0x48, 0xaa, 0xd7, 0x48, 0x65, 0xac, 0xdc, 0xab, 0x3c, 0x00, 0xe3, 0xe0, 0x41, 0x78, 0x08, 0xc6, 0xc3, 0x04, 0x98, 0x08, 0x0f, 0xc3, 0x24, 0x98, 0x0c, 0x8f, 0xc0, 0xa3, 0xf0, 0x18, 0x4c, 0x01, 0xb4, 0x41, 0x79, 0x02, 0x9e, 0x84, 0xa7, 0x60, 0x2a, 0x4c, 0x83, 0xa7, 0xe1, 0x19, 0xf9, 0x1e, 0x55, 0xf1, 0x3d, 0xaa, 0xe2, 0x0a, 0xaa, 0xe2, 0x0a, 0xaa, 0xe2, 0x0a, 0xaa, 0xe2, 0x0a, 0xaa, 0xe2, 0x0a, 0xaa, 0xe2, 0x0a, 0xe5, 0x45, 0x99, 0xa7, 0xbc, 0x04, 0xb3, 0xe5, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xe5, 0x03, 0x99, 0xaf, 0x7c, 0x08, 0x8b, 0xe0, 0x1f, 0xf0, 0x4f, 0xf8, 0x08, 0xfe, 0x33, 0x57, 0xfe, 0x2a, 0xfd, 0x8b, 0x2b, 0x7f, 0x15, 0x28, 0xdb, 0xe0, 0x37, 0xd8, 0x2e, 0xb7, 0x2b, 0x3b, 0x60, 0x27, 0x24, 0xc3, 0x1e, 0xd8, 0x2b, 0x93, 0x95, 0x7d, 0x60, 0x83, 0xfd, 0x70, 0x10, 0x0e, 0xf1, 0xf8, 0x61, 0x48, 0x85, 0x23, 0x70, 0x14, 0x1c, 0x70, 0x0c, 0x8e, 0x43, 0x26, 0x9c, 0x80, 0x93, 0x90, 0x05, 0xd9, 0x90, 0x03, 0xa7, 0x00, 0xcd, 0x52, 0x72, 0xc1, 0x09, 0x2e, 0xc8, 0x87, 0x33, 0x40, 0x1d, 0x50, 0x0a, 0xc0, 0x0b, 0xff, 0x7b, 0x57, 0x4a, 0x29, 0x50, 0xeb, 0x40, 0x5d, 0xa8, 0x07, 0x68, 0xad, 0xda, 0x00, 0xae, 0x82, 0x46, 0xd0, 0x58, 0xd4, 0x54, 0xe3, 0x58, 0x36, 0xd1, 0xaf, 0xd3, 0xae, 0x5f, 0xd9, 0x14, 0x9a, 0x43, 0x0b, 0x68, 0x09, 0x6d, 0xa0, 0x2d, 0xb4, 0x83, 0x0e, 0xd0, 0x11, 0x3a, 0xe3, 0x5b, 0xbb, 0x8a, 0x1a, 0xea, 0x0d, 0xdc, 0xee, 0x0e, 0x3d, 0x00, 0x6f, 0xa6, 0xf6, 0x84, 0x5e, 0xd0, 0x1b, 0xfa, 0x40, 0x5f, 0xb8, 0x19, 0x6e, 0x11, 0x75, 0xd4, 0x5b, 0x59, 0xf6, 0x83, 0xfe, 0x30, 0x00, 0x6e, 0x87, 0x81, 0x30, 0x08, 0xee, 0x82, 0xbb, 0x61, 0x30, 0x0c, 0x81, 0x7b, 0x60, 0x28, 0x0c, 0x03, 0x3a, 0x4b, 0x75, 0x38, 0x8c, 0x90, 0xdb, 0xd5, 0x91, 0x30, 0x0a, 0xee, 0x87, 0xd1, 0x30, 0x06, 0xc6, 0xc2, 0x03, 0x30, 0x1e, 0x26, 0xc0, 0x44, 0x78, 0x18, 0x26, 0xc1, 0x64, 0x78, 0x04, 0x1e, 0x85, 0xc7, 0x60, 0x0a, 0x3c, 0x0e, 0x4f, 0xc0, 0x93, 0xf0, 0x14, 0x4c, 0x85, 0x69, 0x30, 0x13, 0x66, 0xc1, 0x8b, 0x40, 0xfd, 0x53, 0x5f, 0x86, 0xd7, 0x61, 0x0e, 0xcc, 0x85, 0x37, 0x60, 0x1e, 0xcc, 0x07, 0xfd, 0x0a, 0xb0, 0x6f, 0xc1, 0x42, 0x78, 0x5b, 0xda, 0xd5, 0x77, 0xe0, 0x5d, 0x78, 0x0f, 0xa8, 0x8d, 0xea, 0x07, 0xf0, 0x21, 0x2c, 0x82, 0xc5, 0xf0, 0x91, 0xcc, 0x50, 0x3f, 0x86, 0x4f, 0x60, 0x09, 0x2c, 0x85, 0x65, 0xf0, 0xa9, 0xb8, 0x43, 0xfd, 0x8c, 0xe5, 0xe7, 0xf0, 0x05, 0x7c, 0x09, 0x5f, 0xc1, 0x72, 0x58, 0x01, 0x2b, 0x61, 0x15, 0x7c, 0x0d, 0xab, 0x61, 0x0d, 0x50, 0x3b, 0x55, 0x6a, 0xa7, 0xba, 0x0e, 0xd6, 0xc3, 0xb7, 0xf0, 0x1d, 0x7c, 0x0f, 0x3f, 0xc0, 0x06, 0xf8, 0x11, 0x7e, 0x82, 0x9f, 0x61, 0x23, 0xfc, 0x02, 0xbf, 0xc2, 0x26, 0xd8, 0x0c, 0x5b, 0x60, 0x2b, 0x6c, 0x83, 0xdf, 0xe4, 0x41, 0x35, 0x09, 0xb6, 0xc3, 0x0e, 0xd8, 0x09, 0xc9, 0xb0, 0x0b, 0x76, 0xc3, 0x1e, 0xd8, 0x0b, 0xfb, 0xc0, 0x06, 0xfb, 0xe1, 0x00, 0xd8, 0xe1, 0x20, 0x1c, 0x82, 0xc3, 0x90, 0x0a, 0x47, 0xc0, 0x01, 0xc7, 0xe0, 0xb8, 0x4c, 0x55, 0xd3, 0x20, 0x43, 0xa6, 0xab, 0x99, 0x70, 0x02, 0x4e, 0x42, 0x16, 0x64, 0x43, 0x0e, 0x9c, 0x96, 0x5e, 0x35, 0x17, 0xfe, 0xfd, 0xdf, 0xcd, 0xf7, 0xa8, 0x65, 0x10, 0x80, 0x20, 0x9c, 0x85, 0x72, 0x3a, 0xb0, 0xe1, 0xf2, 0x84, 0x35, 0x5d, 0x16, 0x58, 0xe9, 0xbc, 0xad, 0x5e, 0xa0, 0xf3, 0xb6, 0xd2, 0x79, 0x5b, 0xe9, 0xbc, 0xad, 0x25, 0x72, 0xaf, 0xd5, 0x07, 0x7e, 0xd0, 0xa0, 0x14, 0xca, 0xe4, 0x76, 0x6b, 0x00, 0x82, 0x70, 0x16, 0xca, 0xa1, 0x42, 0x1e, 0xb4, 0x56, 0x42, 0x08, 0xce, 0xc1, 0x79, 0x79, 0x10, 0x17, 0x3f, 0x3d, 0xe6, 0x5d, 0x99, 0x1f, 0xf3, 0x1e, 0xfc, 0x2a, 0x8b, 0x62, 0xa8, 0x71, 0x31, 0xe4, 0xbf, 0x98, 0x84, 0xa3, 0xfe, 0x56, 0xd8, 0x70, 0x71, 0xfb, 0xe9, 0x7a, 0xec, 0xf2, 0x88, 0x38, 0xc4, 0xb2, 0x54, 0xfe, 0x6a, 0x8a, 0x92, 0x9f, 0x46, 0xec, 0x92, 0x5f, 0x45, 0xa4, 0xcb, 0x52, 0x4b, 0x77, 0x59, 0x62, 0xe9, 0x01, 0x77, 0xc1, 0xdd, 0xa2, 0xae, 0x65, 0xb0, 0x68, 0x62, 0x19, 0xc2, 0xed, 0x7b, 0x60, 0x28, 0xf7, 0x87, 0x89, 0xd6, 0x96, 0x7b, 0xb9, 0x7d, 0x1f, 0x0c, 0x87, 0x11, 0x30, 0x12, 0x46, 0xc1, 0xfd, 0x30, 0x9a, 0xe7, 0x8c, 0xe1, 0x35, 0x63, 0xb9, 0xfd, 0x00, 0x8c, 0xe3, 0xfe, 0x83, 0xbc, 0xe6, 0x21, 0x6e, 0x4f, 0x96, 0x27, 0x2d, 0x4b, 0xb8, 0xbf, 0x54, 0x6c, 0xb1, 0x2c, 0x13, 0x07, 0x2c, 0x9f, 0x72, 0xfb, 0x33, 0x6e, 0x7f, 0x2e, 0xec, 0x96, 0x9f, 0xb8, 0xfd, 0x33, 0xb7, 0x37, 0xf2, 0xf8, 0x2f, 0x62, 0xb0, 0xe5, 0x57, 0xee, 0x6f, 0xe2, 0xfe, 0x66, 0xfe, 0xb6, 0x85, 0xfb, 0xdb, 0x44, 0x4d, 0x4b, 0x12, 0x8f, 0x6d, 0xe7, 0xb1, 0x1d, 0x3c, 0x67, 0x27, 0x8f, 0x25, 0x73, 0x7f, 0x17, 0xf7, 0x77, 0xf3, 0x9c, 0x14, 0xee, 0x9f, 0x10, 0x4d, 0x2d, 0x27, 0xc5, 0x1e, 0x4b, 0x96, 0x38, 0x69, 0xc9, 0x16, 0xa3, 0x2d, 0x39, 0xdc, 0x3f, 0xc5, 0xdf, 0x4e, 0x8b, 0x1c, 0x4b, 0x2e, 0xf7, 0x9d, 0xa2, 0xbe, 0xc5, 0x2b, 0xff, 0xa1, 0x74, 0x10, 0x1f, 0x2b, 0x1d, 0xc5, 0x6a, 0xa5, 0x33, 0xcb, 0x2e, 0x62, 0xad, 0x72, 0x9f, 0x7c, 0x5b, 0x99, 0x29, 0x53, 0x95, 0xd7, 0x45, 0x8c, 0xb2, 0x40, 0x5c, 0xab, 0xbc, 0x65, 0x8a, 0x57, 0x16, 0x9a, 0xba, 0x28, 0x6f, 0x8b, 0x97, 0x95, 0x77, 0x44, 0x63, 0xe5, 0x5d, 0x51, 0xc7, 0xda, 0x44, 0xbc, 0x66, 0x6d, 0x2a, 0xde, 0xb6, 0x36, 0xe3, 0x76, 0x0b, 0x6e, 0xb7, 0x14, 0x1f, 0x5a, 0x5b, 0x71, 0xbb, 0x1d, 0xb7, 0xaf, 0xe3, 0xf1, 0xeb, 0xb9, 0xdd, 0x81, 0xdb, 0x1d, 0x79, 0xbc, 0x93, 0xa8, 0xc3, 0x2c, 0x6c, 0x8f, 0xf9, 0x56, 0x6a, 0x31, 0xdf, 0x9b, 0xda, 0xc5, 0xfc, 0x60, 0xea, 0x1e, 0xb3, 0x41, 0x4c, 0x8b, 0xf9, 0x51, 0xca, 0x98, 0x9f, 0x44, 0xed, 0x98, 0x5f, 0xc4, 0xd5, 0xc6, 0x6c, 0xac, 0xfe, 0x17, 0xb3, 0xf1, 0x19, 0xb3, 0xe1, 0xff, 0x7b, 0x36, 0x18, 0xfd, 0x3f, 0xce, 0x46, 0x1d, 0x66, 0x23, 0xe6, 0x7f, 0x7d, 0x36, 0x6a, 0x32, 0x1b, 0x3b, 0x18, 0x7d, 0x1b, 0xa3, 0xbf, 0x81, 0xd1, 0x4f, 0x65, 0x74, 0x3c, 0x96, 0xf5, 0x8c, 0xd4, 0x61, 0x71, 0x23, 0x7b, 0xb9, 0x46, 0x69, 0x28, 0x6a, 0xb3, 0x67, 0x6b, 0xd9, 0xab, 0x69, 0xca, 0x02, 0x5c, 0x0f, 0x8e, 0x55, 0xd1, 0x7f, 0xbf, 0xb1, 0x13, 0xaf, 0xfc, 0x55, 0xec, 0x65, 0xee, 0x6c, 0xf2, 0x38, 0x73, 0xe9, 0x65, 0x2e, 0x7f, 0x63, 0x4d, 0xab, 0x58, 0xd3, 0x07, 0x26, 0xfc, 0x1b, 0x6b, 0x5b, 0x13, 0x71, 0x54, 0x96, 0x32, 0x9f, 0x6e, 0xe6, 0xf3, 0x38, 0xf3, 0x79, 0x9c, 0xf9, 0x3c, 0x6e, 0xb9, 0x9b, 0xf9, 0x1d, 0x0c, 0x43, 0xb8, 0x7d, 0x0f, 0x0c, 0xe5, 0xf6, 0x30, 0xb8, 0x97, 0xdb, 0xf7, 0xc1, 0x70, 0x18, 0x01, 0x23, 0x61, 0x14, 0xdc, 0x0f, 0xa3, 0xf9, 0xfb, 0x18, 0x18, 0xcb, 0xed, 0x07, 0x60, 0x1c, 0xb7, 0x1f, 0x84, 0x87, 0xb8, 0x3d, 0x59, 0x6e, 0x61, 0x4f, 0xdf, 0x64, 0x2f, 0xdf, 0x60, 0xfc, 0xb6, 0xb2, 0xa7, 0xf5, 0xd8, 0xd3, 0x4c, 0xf6, 0x34, 0x93, 0x3d, 0xcd, 0x54, 0x16, 0x57, 0xfd, 0xba, 0x31, 0xda, 0x50, 0x8a, 0x36, 0x94, 0x8a, 0x26, 0x74, 0xd9, 0x39, 0xec, 0x7d, 0x0e, 0xdd, 0xf5, 0x09, 0xba, 0x6a, 0x07, 0x1d, 0x5b, 0x06, 0xdd, 0x5a, 0x06, 0x5d, 0x59, 0x06, 0x5d, 0x99, 0xc3, 0xb8, 0x7a, 0xc6, 0x14, 0x5c, 0xe0, 0x30, 0x99, 0x83, 0x7b, 0xcb, 0xc1, 0xbd, 0xe5, 0xe0, 0xde, 0x72, 0x70, 0x6f, 0x39, 0xb8, 0xb7, 0x1c, 0xdc, 0x5b, 0x0e, 0x2e, 0xeb, 0x04, 0x2e, 0xeb, 0x04, 0x2e, 0xca, 0x81, 0x8b, 0x72, 0xe0, 0xa2, 0x1c, 0xb8, 0x28, 0x07, 0x2e, 0xca, 0x81, 0x8b, 0x72, 0xe0, 0xa2, 0x1c, 0xb8, 0x28, 0x07, 0x2e, 0xca, 0xc1, 0xd6, 0xfd, 0x62, 0x9e, 0xb0, 0x48, 0x87, 0x50, 0xa0, 0x26, 0x34, 0x64, 0x3f, 0xae, 0x61, 0x19, 0x07, 0x4d, 0xa0, 0x39, 0xec, 0x83, 0x83, 0x50, 0x75, 0x26, 0xf5, 0xb0, 0x71, 0x16, 0x35, 0x9f, 0xbd, 0x19, 0x00, 0x83, 0x64, 0x29, 0x7d, 0xe4, 0x1d, 0xf4, 0x91, 0x0e, 0xf3, 0x60, 0x51, 0x8f, 0x3e, 0xb2, 0x0f, 0x7d, 0xa4, 0xc3, 0x7c, 0x9f, 0x3c, 0x46, 0xff, 0xe8, 0x88, 0x30, 0xc9, 0xf4, 0x08, 0xb3, 0xdc, 0x15, 0x11, 0x21, 0xb5, 0x88, 0x48, 0x88, 0x02, 0x15, 0xac, 0xf2, 0x58, 0x44, 0x8c, 0x4c, 0x8e, 0xa8, 0xc1, 0x3c, 0xd4, 0xe1, 0x76, 0x5d, 0x99, 0x12, 0x51, 0x4f, 0x1e, 0x8f, 0xa8, 0xcf, 0xdf, 0xae, 0xe2, 0x75, 0x57, 0xcb, 0xd4, 0x88, 0x38, 0xb9, 0x3f, 0xa2, 0x09, 0x7f, 0x6b, 0xc9, 0x63, 0xad, 0xa1, 0x8d, 0xdc, 0x13, 0x31, 0x46, 0xee, 0x8c, 0x58, 0xce, 0xdf, 0xf5, 0x33, 0xa9, 0x36, 0xe9, 0x8b, 0xd8, 0x0f, 0xfa, 0xd9, 0xd4, 0x0c, 0xfe, 0x9e, 0x29, 0xb5, 0xea, 0x67, 0x33, 0xa3, 0x9a, 0xc9, 0x54, 0x7a, 0x3c, 0x87, 0x7e, 0xc6, 0x32, 0xaa, 0x42, 0x3a, 0xa2, 0x23, 0xa5, 0x93, 0xbe, 0xc9, 0x41, 0xdf, 0xe4, 0xa0, 0x6f, 0x72, 0xd0, 0x37, 0x39, 0xf4, 0xb3, 0x97, 0x96, 0x67, 0xe1, 0x39, 0x58, 0x01, 0x2b, 0x61, 0x15, 0x7c, 0x0d, 0xab, 0x61, 0x0d, 0xac, 0x85, 0xf5, 0x62, 0x8a, 0x65, 0x9f, 0xe8, 0x64, 0xb1, 0x89, 0xee, 0x96, 0xfd, 0x22, 0xce, 0x72, 0x40, 0x58, 0x89, 0xc4, 0x59, 0x96, 0x54, 0x6e, 0x1f, 0x85, 0x74, 0xd1, 0xd6, 0x92, 0x29, 0xe2, 0xe8, 0x0d, 0x1c, 0xf4, 0x06, 0x0e, 0x7a, 0x03, 0x07, 0xbd, 0x81, 0x83, 0xde, 0xc0, 0x41, 0x6f, 0xe0, 0xa0, 0x37, 0x70, 0xd0, 0x1b, 0x38, 0x94, 0x06, 0xd0, 0x50, 0x5c, 0x67, 0x9c, 0x31, 0xbd, 0x1a, 0x1a, 0x41, 0x63, 0x60, 0xcc, 0xe9, 0x13, 0x1c, 0xf4, 0x09, 0x0e, 0xfa, 0x04, 0x07, 0x7d, 0x82, 0x83, 0x3e, 0xc1, 0x41, 0x9f, 0xe0, 0xa0, 0x4f, 0x70, 0xd0, 0x27, 0x38, 0xe8, 0x13, 0x1c, 0xf4, 0x09, 0x0e, 0xfa, 0x04, 0x07, 0x7d, 0x82, 0x83, 0x3e, 0xc1, 0x41, 0x9f, 0xe0, 0xa0, 0x4f, 0x70, 0x28, 0x44, 0x8b, 0xd2, 0x81, 0x0c, 0xe8, 0xc4, 0xb2, 0x33, 0xcb, 0x2e, 0xd0, 0x95, 0xdb, 0xdd, 0xc2, 0x67, 0x66, 0xbb, 0xc3, 0xeb, 0x62, 0x87, 0xb5, 0xb1, 0x68, 0x6d, 0xdd, 0x24, 0x1d, 0xd6, 0x43, 0x52, 0xa3, 0xce, 0x39, 0xa8, 0x73, 0x0e, 0xea, 0x9c, 0x83, 0x3a, 0xe7, 0xa0, 0xce, 0x39, 0x62, 0x3e, 0x22, 0x27, 0x37, 0x33, 0xbf, 0x3d, 0x45, 0x84, 0xf4, 0x89, 0x48, 0x88, 0x92, 0xc5, 0x44, 0x88, 0x9d, 0x08, 0xb1, 0x13, 0x21, 0x76, 0x22, 0x44, 0x3f, 0x63, 0x64, 0x27, 0x42, 0xec, 0x44, 0x88, 0x9d, 0x08, 0xb1, 0x8b, 0x96, 0x3c, 0xaf, 0x15, 0xb4, 0x95, 0x9a, 0xb8, 0x16, 0xda, 0x71, 0xbb, 0x3d, 0xec, 0x45, 0x4d, 0xf7, 0xf1, 0x77, 0x66, 0x8a, 0x08, 0x3a, 0x24, 0x0e, 0xf3, 0xb7, 0x00, 0x84, 0x78, 0xec, 0x3c, 0x91, 0x56, 0x75, 0x5e, 0x5e, 0x23, 0xa2, 0xec, 0x44, 0x94, 0xdd, 0x74, 0x46, 0x6a, 0xc4, 0xbc, 0x9f, 0xc8, 0xb2, 0x13, 0xf7, 0x7e, 0xe3, 0x8a, 0x31, 0x83, 0xe4, 0x71, 0x22, 0x6c, 0x2c, 0x11, 0x66, 0x27, 0xc2, 0xea, 0x98, 0x87, 0x12, 0x71, 0xc3, 0xc4, 0x08, 0xa2, 0xcc, 0x4e, 0x94, 0xd9, 0xc9, 0x0d, 0xbf, 0x79, 0x34, 0x8f, 0x8d, 0xe5, 0xb9, 0xe3, 0xb9, 0x3f, 0x89, 0xfb, 0x8f, 0x18, 0x57, 0x85, 0xf7, 0x99, 0x1f, 0x67, 0x79, 0x41, 0x1e, 0x35, 0x4b, 0x99, 0x46, 0x24, 0xa6, 0x10, 0x89, 0x9b, 0x88, 0xc4, 0xc3, 0x44, 0xe2, 0x61, 0x22, 0xf1, 0x70, 0x84, 0x42, 0x74, 0xa9, 0x2c, 0xad, 0xfc, 0x2d, 0x46, 0xfe, 0x4a, 0x34, 0xee, 0x21, 0x1a, 0x93, 0x89, 0xc6, 0xdd, 0x44, 0xe3, 0x7e, 0xa2, 0xf1, 0x30, 0xd1, 0x78, 0x80, 0x68, 0xdc, 0x45, 0x34, 0x26, 0x13, 0x8d, 0xf6, 0x88, 0xa6, 0xb2, 0x24, 0xa2, 0x19, 0x8f, 0xb5, 0xe4, 0x6f, 0xad, 0xe4, 0x51, 0x22, 0xf3, 0x30, 0x91, 0xb9, 0x23, 0xa2, 0xad, 0x3c, 0x42, 0x74, 0x6e, 0x25, 0x3a, 0xed, 0x44, 0xa7, 0x1d, 0x95, 0xf1, 0x11, 0xa1, 0x99, 0x44, 0x68, 0x66, 0xc4, 0x11, 0x23, 0x4a, 0xed, 0x44, 0xe9, 0x51, 0xa2, 0xf4, 0x28, 0x51, 0x6a, 0x27, 0x4a, 0xed, 0x44, 0xa9, 0x9d, 0x28, 0xdd, 0x4d, 0x94, 0xda, 0xa3, 0x18, 0x17, 0x22, 0xd5, 0x4e, 0xa4, 0xda, 0x89, 0xd4, 0x93, 0x16, 0xc6, 0xcf, 0xd2, 0x01, 0x3a, 0x42, 0x27, 0xe8, 0x0c, 0x5d, 0xa0, 0x2b, 0x74, 0x83, 0x1b, 0xa0, 0x3b, 0xf4, 0x80, 0x78, 0xe8, 0x09, 0x37, 0xc2, 0x4d, 0xd0, 0x0b, 0x7a, 0x43, 0x1f, 0xe8, 0x0b, 0x37, 0xc3, 0x2d, 0x70, 0x2b, 0xf4, 0x83, 0xfe, 0x70, 0x1b, 0x0c, 0x80, 0xdb, 0x61, 0x20, 0xdc, 0x01, 0x8c, 0x37, 0x4a, 0xe8, 0x43, 0x09, 0x7d, 0x28, 0xa1, 0x0f, 0x25, 0xf4, 0xa1, 0x84, 0x3e, 0x94, 0xd0, 0x87, 0x12, 0xfa, 0x50, 0x42, 0x1f, 0x4a, 0xe8, 0x43, 0x09, 0x7d, 0x28, 0xa1, 0x0f, 0x25, 0xf4, 0xa1, 0x84, 0x3e, 0x94, 0xd0, 0x87, 0x12, 0xfa, 0x50, 0x42, 0x1f, 0x4a, 0xe8, 0x43, 0x09, 0x7d, 0x28, 0xa1, 0x0f, 0x25, 0xf4, 0xa1, 0x84, 0x3e, 0xcb, 0x78, 0x98, 0x00, 0x13, 0xe1, 0x61, 0x59, 0x6c, 0x99, 0x24, 0xf5, 0xef, 0xd7, 0x17, 0x93, 0x8d, 0x76, 0xb2, 0xd1, 0x4e, 0x36, 0xda, 0xc9, 0x46, 0x3b, 0xd9, 0x68, 0x27, 0x1b, 0xed, 0x64, 0xa3, 0x9d, 0x6c, 0xb4, 0x93, 0x8d, 0x76, 0xb2, 0xd1, 0x4e, 0x36, 0xda, 0xc9, 0x46, 0x3b, 0xd9, 0x68, 0x27, 0x1b, 0xed, 0xe1, 0x6c, 0xbc, 0x81, 0x6c, 0xec, 0x45, 0x36, 0xf6, 0x20, 0x1b, 0x6b, 0x85, 0xb3, 0xb1, 0x07, 0xd9, 0xd8, 0xc3, 0x92, 0x26, 0xda, 0x19, 0x19, 0x99, 0x41, 0x96, 0x66, 0x8a, 0x1e, 0x64, 0xa5, 0x9d, 0xac, 0xb4, 0x93, 0x95, 0x76, 0xb2, 0xd2, 0x4e, 0x56, 0xda, 0xc9, 0x4a, 0x3b, 0x59, 0x69, 0x27, 0x2b, 0xed, 0x64, 0xa5, 0x9d, 0xac, 0x6c, 0x47, 0x56, 0xda, 0xc9, 0x4a, 0x3b, 0x59, 0x69, 0x27, 0x2b, 0xed, 0x64, 0xa5, 0x9d, 0xac, 0xb4, 0x93, 0x95, 0x76, 0xb2, 0xd2, 0x4e, 0x56, 0xda, 0xc9, 0x4a, 0x3b, 0x59, 0x69, 0x27, 0x2b, 0xed, 0x64, 0xa5, 0x9d, 0xac, 0xb4, 0x93, 0x95, 0x76, 0xb2, 0xd2, 0x4e, 0x56, 0xda, 0xc9, 0x4a, 0x3b, 0x59, 0x69, 0x27, 0x2b, 0xed, 0x64, 0xa5, 0x9d, 0xac, 0x2c, 0x24, 0x2b, 0xed, 0x64, 0x65, 0x21, 0x59, 0x59, 0x48, 0x56, 0xda, 0xc9, 0x4a, 0x3b, 0x59, 0x69, 0x27, 0x2b, 0xed, 0x74, 0xeb, 0x3e, 0xba, 0x75, 0x1f, 0xdd, 0xba, 0x8f, 0x6e, 0xdd, 0xa7, 0x30, 0xfe, 0x74, 0xec, 0x3e, 0x3a, 0x76, 0x1f, 0x1d, 0xbb, 0x8f, 0x8e, 0xdd, 0x47, 0xc7, 0xee, 0xa3, 0x2b, 0xd7, 0xe8, 0xca, 0x35, 0xba, 0x72, 0x8d, 0xae, 0x5c, 0xa3, 0x2b, 0xd7, 0xe8, 0xca, 0x35, 0x45, 0x7f, 0x0f, 0x78, 0x22, 0x3c, 0x0c, 0x93, 0x60, 0x32, 0x10, 0xff, 0x74, 0xe5, 0x1a, 0x5d, 0xb9, 0x46, 0x57, 0xae, 0xd1, 0x95, 0x6b, 0x74, 0xe5, 0x1a, 0x5d, 0xb9, 0x46, 0x57, 0xae, 0xd1, 0x95, 0x6b, 0x74, 0xe5, 0x1a, 0x5d, 0xb9, 0x46, 0xbd, 0xf0, 0x51, 0x2f, 0x7c, 0xd4, 0x0b, 0x1f, 0xf5, 0xc2, 0x47, 0xbd, 0xf0, 0x51, 0x2f, 0x7c, 0xca, 0x6b, 0xf0, 0xba, 0xd8, 0xa0, 0xcc, 0x61, 0x39, 0x17, 0xde, 0x80, 0x79, 0x30, 0x1f, 0xde, 0x84, 0x05, 0xf0, 0x0e, 0xbc, 0x0b, 0xef, 0xc1, 0xfb, 0x90, 0x28, 0x7d, 0x28, 0x4c, 0x43, 0x6b, 0x1c, 0xea, 0xd2, 0x04, 0x9a, 0x42, 0x33, 0x68, 0x0e, 0x2d, 0x00, 0x25, 0xb7, 0xb6, 0x02, 0xd4, 0xdc, 0xda, 0x06, 0xd0, 0x0b, 0x2b, 0x7a, 0x61, 0x6d, 0x07, 0xd7, 0xc1, 0xf5, 0xd0, 0x1e, 0x3a, 0x40, 0x47, 0xe8, 0x04, 0x3f, 0xc2, 0x26, 0x69, 0xb7, 0x6e, 0x96, 0xa5, 0xd6, 0x2d, 0xb0, 0x15, 0xb6, 0xc1, 0x76, 0x1e, 0x3f, 0x24, 0x0f, 0xa3, 0x62, 0x76, 0x54, 0xcc, 0x8e, 0x8a, 0xd9, 0x51, 0x31, 0x3b, 0x2a, 0x66, 0xb7, 0xea, 0xd7, 0x38, 0xf0, 0x81, 0x1f, 0xf4, 0xcf, 0x7f, 0x94, 0xa2, 0x6a, 0x1f, 0xc9, 0xc3, 0x31, 0x9b, 0xa4, 0x0f, 0x75, 0xb3, 0x8b, 0x73, 0xa8, 0x9b, 0x86, 0xba, 0x69, 0xa8, 0x9b, 0xef, 0xb2, 0xfa, 0x57, 0xfc, 0xa7, 0xfa, 0x77, 0x51, 0xdd, 0xda, 0xa1, 0x58, 0xec, 0x1d, 0xaa, 0xa6, 0x19, 0x35, 0xd1, 0xc6, 0xf2, 0xa0, 0x3c, 0x16, 0x56, 0x34, 0xbd, 0x36, 0x1e, 0x41, 0xcd, 0xfc, 0x97, 0xea, 0x63, 0x3f, 0xd4, 0x47, 0xaf, 0x91, 0xb7, 0xb3, 0xbc, 0x13, 0x06, 0xc9, 0x93, 0x28, 0xd9, 0xfd, 0xe1, 0x5a, 0x59, 0x17, 0x15, 0xbb, 0x2f, 0x5c, 0x2b, 0x1d, 0xa8, 0x98, 0x86, 0x82, 0x69, 0x7a, 0xcd, 0x44, 0xc1, 0x74, 0xf5, 0xd2, 0x95, 0xcb, 0x83, 0x72, 0x15, 0xa0, 0x5c, 0xfb, 0x51, 0xae, 0x1d, 0x28, 0x57, 0x1a, 0xca, 0x95, 0x86, 0x72, 0xa5, 0xa1, 0x5c, 0x05, 0x28, 0x57, 0x1a, 0xca, 0x75, 0x00, 0xe5, 0xda, 0x8e, 0x72, 0x1d, 0x40, 0xb9, 0xf6, 0xa3, 0x5c, 0xfb, 0x51, 0xae, 0x54, 0x94, 0x2b, 0x2d, 0x5c, 0x47, 0x0f, 0xa0, 0x5c, 0x76, 0x94, 0xeb, 0x38, 0xca, 0xe5, 0x47, 0xb9, 0x52, 0x51, 0xae, 0x34, 0x94, 0xcb, 0x83, 0x72, 0xa5, 0xa1, 0x5c, 0xfb, 0x50, 0xae, 0x63, 0x28, 0xd7, 0xae, 0x4b, 0x75, 0x75, 0x17, 0xb5, 0xf4, 0xe2, 0xf7, 0x04, 0x8e, 0x70, 0xbb, 0xaa, 0xbe, 0x66, 0xa2, 0x5c, 0x99, 0x97, 0xd5, 0xd7, 0x83, 0x46, 0x7d, 0xad, 0x52, 0xae, 0x8b, 0x35, 0xf6, 0x14, 0xca, 0xa5, 0xa1, 0x5c, 0x1a, 0xca, 0xa5, 0xa1, 0x5c, 0x1a, 0xca, 0xa5, 0xa1, 0x5c, 0x1a, 0xca, 0xa5, 0xa1, 0x5c, 0x1a, 0xca, 0xa5, 0xa1, 0x5c, 0x1a, 0xca, 0x55, 0xf5, 0x49, 0x86, 0x9e, 0x70, 0x23, 0xdc, 0x04, 0xbd, 0xa0, 0x37, 0xf4, 0x81, 0xbe, 0x70, 0x33, 0xdc, 0x02, 0xb7, 0x02, 0xe3, 0x89, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0xd7, 0xa5, 0x4f, 0x59, 0xa0, 0x5c, 0x1a, 0xca, 0xe5, 0x43, 0xb9, 0x7c, 0x28, 0x97, 0xef, 0x7f, 0xe0, 0x23, 0x1e, 0x45, 0xb9, 0xba, 0xa0, 0x5c, 0x3d, 0x50, 0xae, 0x6e, 0x28, 0x57, 0x0d, 0x94, 0x6b, 0x06, 0xca, 0xd5, 0x0d, 0xe5, 0xea, 0x86, 0x72, 0xb5, 0x41, 0xb9, 0x5a, 0xa1, 0x5c, 0x0a, 0xca, 0xd5, 0xed, 0xdf, 0xf4, 0x13, 0xed, 0xff, 0x5f, 0xf2, 0x13, 0x25, 0x61, 0x3f, 0x51, 0x82, 0x72, 0x95, 0xfc, 0xc9, 0x4f, 0xfc, 0xbb, 0xca, 0xf5, 0x8c, 0x2c, 0x45, 0x61, 0x4a, 0x51, 0x18, 0x0d, 0x85, 0xd1, 0x50, 0x18, 0x0d, 0x85, 0xd1, 0x50, 0x18, 0x2d, 0xac, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0xad, 0xc2, 0xaa, 0xe0, 0x40, 0x05, 0xfc, 0xa8, 0x40, 0xda, 0x5f, 0x78, 0x99, 0x34, 0x32, 0xde, 0xf0, 0x33, 0xa2, 0x01, 0xde, 0x5a, 0xc3, 0x5b, 0x6b, 0x64, 0xb0, 0x8b, 0x4c, 0x73, 0x91, 0x61, 0x2e, 0x7c, 0xb4, 0x86, 0x8f, 0xd6, 0xf0, 0xd1, 0x1a, 0x3e, 0x5a, 0xc3, 0x47, 0x6b, 0xf8, 0x68, 0x0d, 0x1f, 0xad, 0xb1, 0xb7, 0x2e, 0xf6, 0xd6, 0xc5, 0xde, 0xba, 0xd8, 0x5b, 0x17, 0x7b, 0xe8, 0x62, 0x0f, 0x5d, 0xec, 0xa1, 0x8b, 0x3d, 0x74, 0xb1, 0x87, 0x2e, 0xe3, 0x53, 0x60, 0xbe, 0xf0, 0xaf, 0x41, 0x95, 0x9a, 0x1e, 0x64, 0xf9, 0x10, 0x3c, 0x2a, 0x0b, 0x4d, 0x0b, 0x64, 0x8a, 0xe9, 0x5d, 0xb4, 0xc4, 0x7c, 0xe9, 0x1a, 0x62, 0x9f, 0x0b, 0xb3, 0xdc, 0x2f, 0xfa, 0xca, 0xf3, 0xe2, 0x66, 0xb8, 0x05, 0x6e, 0x85, 0x7e, 0xd0, 0x1f, 0x6e, 0x83, 0x01, 0x70, 0x3b, 0x0c, 0x84, 0x3b, 0xe0, 0x4e, 0x18, 0x04, 0x77, 0xc1, 0xdd, 0x30, 0x18, 0x86, 0xc0, 0x3d, 0x30, 0x14, 0x86, 0xc1, 0xbd, 0x70, 0x1f, 0x0c, 0x87, 0x11, 0x30, 0x12, 0x46, 0xc1, 0xfd, 0x30, 0x1a, 0xc6, 0xc0, 0x58, 0x78, 0x00, 0xc6, 0xc1, 0x4b, 0x30, 0x5b, 0x9e, 0x13, 0x2f, 0xc3, 0x2b, 0xf0, 0x2a, 0xbc, 0x06, 0xaf, 0xc3, 0x1c, 0x98, 0x0b, 0x6f, 0xc0, 0x3c, 0x58, 0x28, 0x2b, 0xc5, 0x3b, 0xb2, 0x4c, 0xbc, 0x27, 0x83, 0xe1, 0xab, 0x89, 0xa5, 0x99, 0x7a, 0xca, 0xaf, 0x4c, 0xbd, 0xe4, 0x0a, 0x53, 0x6f, 0xa8, 0xba, 0x9a, 0x58, 0x7e, 0xf8, 0x6a, 0x62, 0xf9, 0xa6, 0x5b, 0xe5, 0xc7, 0xa6, 0x7e, 0x30, 0xd0, 0xf8, 0xec, 0xf4, 0xde, 0xf0, 0xf5, 0xa4, 0xf4, 0x4f, 0x99, 0xed, 0x37, 0x8d, 0x92, 0x1e, 0xc6, 0xa7, 0x90, 0xf1, 0x29, 0x34, 0x4d, 0x96, 0xa7, 0xcc, 0x6c, 0xdb, 0xcc, 0xb6, 0xcd, 0xaf, 0xcb, 0x32, 0xf3, 0x42, 0x58, 0x0a, 0x5f, 0xc8, 0xf3, 0xe6, 0x2f, 0xe1, 0x2b, 0x58, 0x0e, 0x2b, 0x60, 0x25, 0xac, 0x82, 0xaf, 0x61, 0x35, 0xac, 0x81, 0xb5, 0xbc, 0xee, 0x1b, 0x79, 0xee, 0xe2, 0x67, 0x10, 0xa3, 0x92, 0xe4, 0xf9, 0xa8, 0xed, 0x60, 0x87, 0x83, 0x70, 0x08, 0x0e, 0x43, 0xaa, 0x3c, 0x1f, 0xdd, 0x56, 0x9e, 0x53, 0x0a, 0xe5, 0x79, 0xa5, 0x18, 0x4a, 0xc0, 0x07, 0x7e, 0xd0, 0xa0, 0x14, 0xca, 0x20, 0x00, 0x41, 0x38, 0x0b, 0xe5, 0x50, 0x01, 0x95, 0x10, 0x82, 0x73, 0x70, 0x1e, 0x2e, 0xc8, 0xf3, 0xea, 0x47, 0xf2, 0x9c, 0xfa, 0x31, 0x7c, 0x02, 0x4b, 0x60, 0x29, 0x2c, 0x83, 0x4f, 0xe1, 0x33, 0xf8, 0x1c, 0xbe, 0x80, 0x2f, 0xe1, 0x2b, 0x58, 0x0e, 0x2b, 0x60, 0x25, 0xac, 0x82, 0xaf, 0x61, 0x35, 0xac, 0x01, 0x8e, 0x41, 0xe5, 0x18, 0xd4, 0x75, 0xb0, 0x1e, 0xbe, 0x85, 0xef, 0xe0, 0x7b, 0xf8, 0x01, 0x36, 0xc0, 0x8f, 0xf0, 0x13, 0xfc, 0x0c, 0x1b, 0xe1, 0x17, 0xf8, 0x15, 0x36, 0xc1, 0x66, 0xd8, 0x02, 0x5b, 0x61, 0x1b, 0x9c, 0x96, 0x95, 0x6a, 0x2e, 0x94, 0xca, 0x32, 0xb5, 0x0c, 0x02, 0x10, 0x84, 0xb3, 0x50, 0xce, 0x1c, 0x46, 0x84, 0x7f, 0x23, 0x5b, 0xff, 0xad, 0xea, 0x4c, 0x11, 0x79, 0xe9, 0x37, 0xa7, 0xc3, 0xbf, 0x64, 0x66, 0x3a, 0x49, 0x15, 0x0c, 0x50, 0x05, 0x03, 0x54, 0xc1, 0x00, 0x15, 0x30, 0x40, 0x05, 0x0c, 0x50, 0x01, 0x03, 0x54, 0xc0, 0x80, 0xa0, 0x36, 0x8b, 0x8e, 0xf2, 0xa4, 0xe8, 0x04, 0x9d, 0xa1, 0x8b, 0x74, 0x8b, 0xae, 0xd0, 0x0d, 0x6e, 0x80, 0xee, 0xd0, 0x03, 0xe2, 0xa1, 0x97, 0xcc, 0x16, 0xbd, 0x59, 0xf6, 0x81, 0xbe, 0x6c, 0xeb, 0x66, 0xb8, 0x05, 0x6e, 0x85, 0x7e, 0xd0, 0x1f, 0x6e, 0x83, 0x01, 0x70, 0x3b, 0x0c, 0x84, 0x3b, 0xe0, 0x4e, 0x18, 0x04, 0x77, 0xc1, 0xdd, 0x30, 0x18, 0x86, 0xc0, 0x3d, 0x30, 0x14, 0x86, 0xc1, 0xbd, 0x70, 0x1f, 0x0c, 0x87, 0x11, 0xa0, 0x1f, 0xcb, 0x28, 0xb8, 0x1f, 0x46, 0xc3, 0x18, 0x18, 0x0b, 0x0f, 0xc0, 0x38, 0x78, 0x48, 0xbf, 0xb6, 0x22, 0xfb, 0x31, 0x01, 0x26, 0xc2, 0xc3, 0xdc, 0x9f, 0x04, 0x93, 0xe1, 0x11, 0x7a, 0x93, 0x47, 0x59, 0x3e, 0x06, 0x53, 0xf8, 0xdb, 0xe3, 0xf0, 0x04, 0x3c, 0x09, 0x4f, 0xc1, 0x54, 0x98, 0x06, 0x4f, 0xc3, 0x33, 0xf0, 0x2c, 0x3c, 0x07, 0xd3, 0xe1, 0x79, 0x78, 0x11, 0x5e, 0x62, 0x1b, 0xb3, 0x65, 0x90, 0x6c, 0x0a, 0x92, 0x4d, 0x41, 0xb2, 0x29, 0x48, 0x36, 0x05, 0xc9, 0xa6, 0x20, 0xd9, 0x14, 0x24, 0x9b, 0x82, 0x64, 0x53, 0x90, 0x6c, 0x0a, 0x0a, 0x34, 0x43, 0xe8, 0x9a, 0xb1, 0x40, 0xe6, 0x8a, 0xb7, 0x78, 0x6d, 0xd5, 0xd5, 0x8a, 0x73, 0xc9, 0xb0, 0x3c, 0xdc, 0x42, 0xc0, 0xe4, 0x96, 0x6e, 0x93, 0x07, 0x5d, 0x1a, 0x20, 0x03, 0x38, 0x84, 0x00, 0xce, 0x20, 0x80, 0x1b, 0x08, 0x98, 0xd9, 0xb6, 0x99, 0x6d, 0x9b, 0xd9, 0xae, 0x79, 0x26, 0xcc, 0x82, 0xd9, 0xc0, 0xf6, 0xc8, 0xa0, 0x20, 0x19, 0x94, 0x67, 0xc6, 0x8f, 0x91, 0x45, 0x79, 0xe6, 0xc5, 0x32, 0xd7, 0xfc, 0x09, 0x7f, 0x5b, 0x02, 0x4b, 0xb9, 0xff, 0x85, 0xcc, 0x24, 0xa3, 0x32, 0xc9, 0xa8, 0x4c, 0x32, 0x2a, 0x93, 0x8c, 0xca, 0x24, 0xa3, 0x32, 0xc9, 0xa8, 0x4c, 0x32, 0x2a, 0x93, 0x8c, 0xca, 0x24, 0xa3, 0x32, 0xc9, 0xa8, 0x20, 0x19, 0x15, 0x34, 0x7f, 0x2f, 0xbd, 0xe6, 0x5f, 0xa4, 0x33, 0x92, 0x71, 0x8b, 0x4c, 0x90, 0xee, 0xa8, 0xfa, 0x90, 0x24, 0x33, 0xc9, 0xae, 0xcc, 0xa8, 0x64, 0xe9, 0x89, 0xa2, 0xa7, 0x21, 0xcb, 0x32, 0xc9, 0xb2, 0x4c, 0xb2, 0x2c, 0x93, 0x2c, 0xcb, 0x24, 0xcb, 0x32, 0x71, 0x01, 0x81, 0xa8, 0xd3, 0x3c, 0xb7, 0x50, 0xba, 0xa2, 0x5b, 0x49, 0x77, 0x74, 0x6b, 0x68, 0x2b, 0x83, 0xd1, 0x15, 0xd2, 0x4d, 0xa5, 0x0c, 0x50, 0x29, 0x03, 0x54, 0xca, 0x00, 0x95, 0x32, 0x40, 0x85, 0x0c, 0x50, 0x21, 0x03, 0x54, 0xc8, 0x00, 0x15, 0x32, 0x40, 0x85, 0x0c, 0x50, 0x21, 0x03, 0x54, 0xc8, 0x00, 0x15, 0x32, 0x40, 0xc5, 0x0b, 0x50, 0xf1, 0x02, 0x54, 0xbc, 0x00, 0x15, 0x2f, 0x40, 0xc5, 0x0b, 0x50, 0xf1, 0x02, 0x54, 0xbc, 0x00, 0x15, 0x2f, 0x40, 0x95, 0x0b, 0x50, 0xe5, 0x02, 0x54, 0xb9, 0x00, 0x55, 0x2e, 0x40, 0x95, 0x0b, 0x50, 0xe5, 0x02, 0x54, 0xb9, 0x00, 0x55, 0x2e, 0x40, 0x95, 0x0b, 0x50, 0xe5, 0x02, 0x54, 0xb9, 0x00, 0x55, 0x2e, 0x40, 0x95, 0x0b, 0x50, 0xe5, 0x02, 0x54, 0xb9, 0x00, 0x55, 0x2e, 0x40, 0x95, 0x0b, 0x50, 0xe5, 0x02, 0x54, 0xb9, 0x00, 0x55, 0x2c, 0xa0, 0x3c, 0x87, 0xd6, 0x4f, 0x87, 0x04, 0x78, 0x1e, 0x5e, 0x80, 0x19, 0xf0, 0xb1, 0x3c, 0xa9, 0x7c, 0x02, 0x4b, 0x60, 0x29, 0x2c, 0x83, 0x4f, 0xe1, 0x33, 0xf8, 0x1c, 0xbe, 0x80, 0xaf, 0x60, 0x39, 0xac, 0x80, 0x95, 0xb0, 0x0a, 0xbe, 0x86, 0xd5, 0xb0, 0x06, 0xd6, 0xc2, 0x37, 0xb0, 0x0e, 0xd6, 0xc3, 0xb7, 0xf0, 0x1d, 0x7c, 0x0f, 0x3f, 0xc0, 0x06, 0xd8, 0x08, 0xbf, 0xc0, 0xaf, 0xb0, 0x09, 0x36, 0xc3, 0x56, 0xe9, 0x56, 0xb6, 0xc1, 0x6f, 0xb0, 0x1d, 0x76, 0xc0, 0x4e, 0x48, 0x86, 0x3d, 0x70, 0x08, 0x0e, 0x43, 0x2a, 0x1c, 0x81, 0xa3, 0xe0, 0x80, 0x63, 0x70, 0x1c, 0x32, 0xe1, 0x04, 0x9c, 0x84, 0x2c, 0xc8, 0x86, 0x1c, 0x38, 0x05, 0xcc, 0x93, 0x92, 0x0b, 0x4e, 0x70, 0x41, 0x3e, 0x9c, 0x01, 0xe2, 0x4f, 0x29, 0x00, 0x2f, 0x14, 0xca, 0x4c, 0x45, 0xff, 0x66, 0x46, 0x09, 0xf8, 0xc0, 0x0f, 0x1a, 0x94, 0x42, 0x19, 0x04, 0x20, 0x08, 0x67, 0xa1, 0x1c, 0x2a, 0xa0, 0x12, 0x42, 0x70, 0x0e, 0xce, 0xc3, 0x05, 0x99, 0xa9, 0x46, 0x4b, 0x8f, 0x6a, 0x01, 0x05, 0x54, 0x88, 0x85, 0x9a, 0x50, 0x0b, 0x6a, 0x4b, 0xb7, 0x5a, 0x07, 0xea, 0x42, 0x3d, 0x20, 0xd6, 0xd4, 0x06, 0x70, 0x15, 0x7f, 0x6b, 0x04, 0x8d, 0x21, 0x0e, 0x9a, 0x40, 0x53, 0x59, 0xa1, 0x36, 0x63, 0xd9, 0x1c, 0x5a, 0x40, 0x4b, 0x68, 0x03, 0x6d, 0xa1, 0x1d, 0x74, 0x80, 0x8e, 0xd0, 0x19, 0xba, 0xca, 0x0b, 0x2a, 0x9a, 0xa4, 0xa2, 0x49, 0x2a, 0x9a, 0xa4, 0xa2, 0x49, 0x6a, 0x4f, 0xe8, 0x05, 0xe8, 0x92, 0x8a, 0x2e, 0xa9, 0x7d, 0xe1, 0x66, 0xb8, 0x05, 0x6e, 0x85, 0x7e, 0xd0, 0x1f, 0x06, 0xc0, 0xed, 0x30, 0x10, 0x06, 0xc1, 0x5d, 0x70, 0x37, 0x0c, 0x86, 0x21, 0x70, 0x0f, 0x0c, 0x85, 0x61, 0x70, 0x2f, 0x0c, 0x87, 0x11, 0x30, 0x12, 0x46, 0xc1, 0xfd, 0x30, 0x1a, 0xc6, 0xc0, 0x58, 0x78, 0x00, 0xd0, 0x1f, 0x15, 0xfd, 0x51, 0xd1, 0x1f, 0xf5, 0x61, 0x98, 0x04, 0x93, 0xe1, 0x11, 0x78, 0x14, 0x1e, 0x03, 0xf4, 0x47, 0x45, 0x7f, 0x54, 0xf4, 0x47, 0x45, 0x7f, 0x54, 0xf4, 0x47, 0x45, 0x7f, 0x54, 0xf4, 0x47, 0x25, 0xe7, 0x55, 0x72, 0x5e, 0x45, 0x6f, 0x54, 0xf2, 0x5e, 0x7d, 0x19, 0x5e, 0x87, 0x39, 0x30, 0x17, 0xde, 0x80, 0x79, 0x30, 0x1f, 0xde, 0x04, 0xb4, 0x45, 0x5d, 0x08, 0x1f, 0xc9, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0xaa, 0xbf, 0x49, 0x97, 0x9a, 0x04, 0xdb, 0x61, 0x07, 0xec, 0x84, 0x64, 0xd8, 0x05, 0xbb, 0x61, 0x0f, 0xec, 0x85, 0x7d, 0x60, 0x83, 0xfd, 0x70, 0x00, 0xec, 0x70, 0x10, 0x0e, 0xc1, 0x61, 0x48, 0x85, 0x23, 0xe0, 0x80, 0x63, 0x70, 0x5c, 0xe6, 0xaa, 0x69, 0x90, 0xc1, 0xf1, 0x93, 0x03, 0x2a, 0x39, 0xa0, 0x92, 0x03, 0x2a, 0x39, 0xa0, 0x92, 0x03, 0x2a, 0x39, 0x50, 0xed, 0x2a, 0xcb, 0xb9, 0xaa, 0x0b, 0xf2, 0xe1, 0x0c, 0x14, 0x80, 0x17, 0x0a, 0xa1, 0x08, 0x8a, 0xa1, 0x04, 0x4a, 0x65, 0x1e, 0x15, 0x35, 0x8f, 0x8a, 0x9a, 0x47, 0x45, 0xcd, 0xa3, 0xa2, 0xe6, 0x51, 0x51, 0xf3, 0xac, 0xe9, 0xd2, 0x8d, 0xeb, 0x0c, 0xe0, 0x3a, 0x03, 0xb8, 0xce, 0x00, 0xae, 0x33, 0x80, 0xeb, 0xd4, 0xaf, 0xf9, 0xe2, 0xb6, 0x06, 0x20, 0x08, 0x67, 0xa1, 0x1c, 0x2a, 0xa4, 0xcb, 0x5a, 0x09, 0x21, 0x38, 0x07, 0xe7, 0xa9, 0x03, 0xd1, 0xd5, 0x3e, 0x85, 0xbf, 0x2f, 0xf2, 0x03, 0x79, 0x24, 0x7c, 0x35, 0x86, 0x43, 0x97, 0xae, 0x84, 0xa1, 0x5f, 0x63, 0x63, 0xbe, 0x71, 0x1d, 0x0d, 0xfd, 0x91, 0x3b, 0x78, 0xe4, 0x01, 0x1e, 0x79, 0x82, 0x47, 0x1e, 0x33, 0x1e, 0x19, 0xcb, 0x23, 0xd3, 0x78, 0xe4, 0x15, 0x1e, 0xd1, 0xaf, 0xfb, 0x36, 0x48, 0xae, 0x35, 0xfe, 0xdf, 0x6b, 0xfc, 0xff, 0xa3, 0xf1, 0xff, 0x77, 0xc6, 0xff, 0x1b, 0x8c, 0xff, 0x53, 0x8c, 0xff, 0x7f, 0xc6, 0x93, 0x0e, 0x94, 0xc7, 0xb9, 0xb5, 0xc9, 0xb8, 0xbf, 0x8e, 0x75, 0x0d, 0x34, 0xae, 0x63, 0x77, 0x3a, 0xaa, 0x9d, 0xf4, 0x47, 0x75, 0xa2, 0x83, 0x8d, 0xa1, 0x3f, 0x4e, 0xa4, 0x3f, 0x4e, 0xa4, 0x3f, 0x1e, 0x2e, 0xea, 0xc9, 0x17, 0xa8, 0xfe, 0xc5, 0x54, 0xff, 0x62, 0xaa, 0x7f, 0x31, 0x95, 0xdf, 0x41, 0xe5, 0x77, 0x50, 0xf9, 0x1d, 0x54, 0x7e, 0x07, 0x95, 0xdf, 0x41, 0xe5, 0x77, 0x88, 0x9e, 0x6c, 0xf9, 0x46, 0xb8, 0x09, 0x7a, 0x71, 0x1c, 0xbd, 0x79, 0xac, 0x0f, 0xd0, 0x4b, 0x51, 0x89, 0xd3, 0xa9, 0xa4, 0x0e, 0x2a, 0xa9, 0x83, 0x4a, 0xea, 0x10, 0x09, 0x3c, 0xe7, 0x05, 0x7c, 0xc9, 0x0c, 0x98, 0x09, 0xb3, 0xa0, 0xea, 0xd7, 0x59, 0xde, 0x17, 0x15, 0xf2, 0x7d, 0xe3, 0x1b, 0x24, 0xf5, 0xe5, 0x26, 0x53, 0x43, 0x96, 0x8d, 0xe4, 0x7a, 0x53, 0x63, 0xa0, 0x8f, 0x31, 0xd1, 0xbb, 0x98, 0xda, 0xf3, 0x58, 0x07, 0x96, 0xb8, 0x11, 0xfc, 0xe8, 0x75, 0x78, 0x99, 0xeb, 0xf0, 0x32, 0xd7, 0xe1, 0x3f, 0x3b, 0xe2, 0x3f, 0x3b, 0x9a, 0xfa, 0xcb, 0xdd, 0xa6, 0x01, 0x30, 0x50, 0x7e, 0x61, 0xba, 0x0b, 0x1f, 0x3a, 0x18, 0xc6, 0xe3, 0x7c, 0xdc, 0xf4, 0xd6, 0xd3, 0x81, 0x6d, 0x9b, 0xe9, 0x33, 0xcc, 0xf4, 0x19, 0xe6, 0x05, 0xdc, 0x4e, 0xc4, 0x7b, 0x2e, 0x62, 0xf9, 0x33, 0xbd, 0xae, 0x5d, 0xbe, 0x4f, 0xaf, 0xfb, 0x11, 0xbd, 0xee, 0x47, 0x11, 0x59, 0xdc, 0x2e, 0xa1, 0xf7, 0x0d, 0xc9, 0xf5, 0x91, 0x42, 0x6a, 0x91, 0x66, 0xfa, 0x5e, 0x0b, 0x58, 0xb9, 0x4f, 0xcf, 0x1e, 0x39, 0x1c, 0x46, 0x02, 0xc7, 0x16, 0x95, 0x2c, 0xba, 0x51, 0x01, 0x1d, 0x51, 0x67, 0xe4, 0xde, 0xa8, 0x22, 0x19, 0x8c, 0xa2, 0xff, 0xa0, 0xfa, 0x39, 0xe8, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x81, 0x3f, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x5f, 0xfd, 0x90, 0x7e, 0xf5, 0x43, 0xfa, 0xd5, 0x44, 0xfa, 0xd5, 0xe1, 0xf4, 0xab, 0xc3, 0xe9, 0x57, 0x47, 0x5a, 0x0a, 0xe4, 0x0b, 0x16, 0x2f, 0x14, 0xca, 0x17, 0xa8, 0x6c, 0xc5, 0x54, 0xb6, 0x62, 0x2a, 0x5b, 0x31, 0x95, 0xad, 0x98, 0xca, 0x56, 0x4c, 0x65, 0x2b, 0xa6, 0xb2, 0x15, 0x53, 0xd9, 0x8a, 0xa9, 0x6c, 0xc5, 0xca, 0x97, 0xf0, 0x15, 0x2c, 0x87, 0x15, 0xb0, 0x52, 0xd4, 0xa3, 0xba, 0x15, 0x53, 0xdd, 0x8a, 0xa9, 0x6e, 0xc5, 0x54, 0xb7, 0x62, 0xaa, 0x5b, 0x31, 0xd5, 0xad, 0x98, 0xea, 0x56, 0x4c, 0x75, 0x2b, 0xa6, 0xba, 0x15, 0x53, 0xdd, 0x8a, 0xa9, 0x6e, 0xc5, 0x54, 0xb7, 0x62, 0xaa, 0x5b, 0xb1, 0xf2, 0x13, 0xfc, 0x0c, 0x1b, 0xe1, 0x17, 0xf8, 0x15, 0x36, 0xc1, 0x66, 0xd8, 0x02, 0xdb, 0xe9, 0x23, 0x77, 0xc0, 0x4e, 0x48, 0x86, 0x5d, 0xb0, 0x07, 0xf6, 0xca, 0xbd, 0xca, 0x3e, 0xb0, 0xc1, 0x7e, 0xb0, 0xf3, 0xd8, 0x41, 0x96, 0x87, 0x58, 0x1e, 0x86, 0x54, 0x38, 0x02, 0x47, 0xc1, 0x01, 0xc7, 0xe0, 0x38, 0xa4, 0x41, 0x06, 0x64, 0xc2, 0x09, 0x38, 0x09, 0x59, 0x90, 0xcd, 0xb6, 0x72, 0x58, 0x9e, 0x02, 0xe6, 0x89, 0x0a, 0xe8, 0xa0, 0x02, 0x3a, 0xa8, 0x80, 0x0e, 0x2a, 0xa0, 0x83, 0x0a, 0xe8, 0xa0, 0x02, 0x3a, 0x14, 0x0f, 0xd0, 0x4b, 0x52, 0x05, 0x1d, 0x8a, 0x94, 0x9a, 0xca, 0xbc, 0xab, 0x26, 0x30, 0x43, 0x04, 0x44, 0x42, 0x14, 0x44, 0xcb, 0x74, 0x2a, 0x5b, 0x3a, 0x95, 0x2d, 0x9d, 0xca, 0x96, 0xae, 0x5a, 0xa5, 0x83, 0xea, 0x96, 0x4e, 0x75, 0x4b, 0xa7, 0xba, 0xa5, 0xab, 0x71, 0xa2, 0xb9, 0xda, 0x54, 0x58, 0xd5, 0x96, 0xc6, 0x37, 0x8d, 0x15, 0xb5, 0xad, 0xe8, 0xa4, 0xb6, 0x13, 0x57, 0xa9, 0x1d, 0xc5, 0x2c, 0xb5, 0xab, 0xa8, 0x49, 0xd5, 0x70, 0x50, 0x35, 0x1c, 0x54, 0x0d, 0x07, 0x55, 0xc3, 0x41, 0xd5, 0x70, 0x50, 0x35, 0x1c, 0x54, 0x0d, 0x07, 0x55, 0xc3, 0xa1, 0x3e, 0x04, 0xe3, 0x61, 0x02, 0x4c, 0x84, 0x87, 0x65, 0x3e, 0x95, 0xc3, 0x41, 0xe5, 0x70, 0x50, 0x39, 0x1c, 0x54, 0x0e, 0x07, 0x95, 0xc3, 0x41, 0xe5, 0x70, 0x50, 0x39, 0x1c, 0x54, 0x0e, 0x07, 0x95, 0xc3, 0x41, 0xe5, 0x70, 0x50, 0x39, 0x1c, 0x54, 0x0e, 0x87, 0x3a, 0x03, 0x66, 0xc2, 0x2c, 0x78, 0x11, 0x16, 0x8a, 0x76, 0xea, 0xdb, 0xf4, 0x03, 0xf4, 0x74, 0xea, 0xbb, 0xf0, 0x1e, 0xbc, 0x0f, 0xc4, 0xbf, 0xfa, 0x01, 0x7c, 0x08, 0x8b, 0x60, 0xb1, 0x2c, 0xb3, 0xee, 0x95, 0x41, 0xeb, 0x51, 0xc8, 0x32, 0xde, 0xe7, 0x76, 0xa0, 0x68, 0x0e, 0x14, 0xcd, 0x81, 0xa2, 0x39, 0x50, 0x34, 0x47, 0xcc, 0x56, 0xfa, 0xea, 0x6d, 0x2c, 0x99, 0x9b, 0xff, 0x5f, 0xa9, 0xc5, 0x35, 0xa8, 0x45, 0x7d, 0xd4, 0xa2, 0x3e, 0x6a, 0xd1, 0x0e, 0xb5, 0x68, 0xf7, 0xb7, 0x5a, 0xfc, 0xad, 0x16, 0x7f, 0xab, 0xc5, 0x7f, 0x4e, 0x2d, 0xfe, 0xc5, 0xb5, 0x44, 0xfb, 0x8b, 0x2e, 0xa2, 0x91, 0x18, 0x0f, 0x13, 0x60, 0x22, 0x4c, 0x81, 0x27, 0xe0, 0x49, 0x98, 0x0e, 0x2f, 0xc2, 0xbb, 0xa2, 0x8e, 0xa9, 0xbb, 0x7e, 0x35, 0x62, 0x11, 0x6b, 0x8a, 0x17, 0xdd, 0x4c, 0x7d, 0x44, 0x3d, 0x53, 0x5f, 0xd1, 0xdd, 0x74, 0x33, 0xcb, 0x5b, 0x58, 0xde, 0xc1, 0xe3, 0x83, 0x8c, 0xeb, 0xa7, 0x0d, 0xc2, 0x1b, 0x0d, 0xd1, 0xaf, 0x60, 0x6c, 0x1a, 0x25, 0x54, 0xd3, 0x83, 0xa2, 0x87, 0xe9, 0x21, 0x98, 0x2c, 0x46, 0x5e, 0x76, 0x85, 0xdc, 0x46, 0xe6, 0x67, 0x61, 0x16, 0x7c, 0x02, 0x4b, 0x44, 0xa3, 0xc8, 0x04, 0xd1, 0x28, 0xaa, 0x3e, 0x74, 0x17, 0xfa, 0xaf, 0xa2, 0x76, 0x8f, 0x2a, 0x13, 0x35, 0xa3, 0x5b, 0x8b, 0x46, 0xca, 0x56, 0xd8, 0x06, 0xbf, 0x89, 0x46, 0x6a, 0x6d, 0xa8, 0x03, 0x75, 0xa1, 0x1e, 0xf0, 0x7c, 0xb5, 0x01, 0x34, 0x17, 0xb1, 0x97, 0xae, 0xa1, 0x7a, 0x03, 0xf7, 0xbb, 0x43, 0x0f, 0x88, 0x87, 0x9e, 0xd0, 0x0b, 0x7a, 0x43, 0x1f, 0xe8, 0x0b, 0x37, 0xc3, 0x2d, 0x70, 0x2b, 0xf4, 0x83, 0xfe, 0x1c, 0xe3, 0xdf, 0x57, 0x27, 0xff, 0xfb, 0xea, 0xe4, 0x7f, 0x5f, 0x9d, 0xfc, 0x0a, 0x57, 0x27, 0x37, 0x7d, 0x80, 0xd7, 0x70, 0xe1, 0x35, 0x5c, 0xf8, 0x8c, 0x4a, 0xd1, 0x4e, 0xe6, 0x8b, 0xeb, 0x64, 0x50, 0x5c, 0xcf, 0xed, 0xf6, 0xd2, 0x89, 0xef, 0xf0, 0xe1, 0x3b, 0x7c, 0xf8, 0x0e, 0x9f, 0xe8, 0x22, 0x0b, 0xc9, 0x8c, 0x14, 0x32, 0x23, 0x85, 0xcc, 0x48, 0x21, 0x33, 0x52, 0xc8, 0x8c, 0x14, 0x32, 0x23, 0x85, 0xcc, 0x48, 0x21, 0x33, 0x52, 0xc8, 0x8c, 0x14, 0x32, 0x23, 0x85, 0xcc, 0x48, 0x21, 0x33, 0x52, 0x84, 0xde, 0x23, 0xdd, 0x05, 0x77, 0xc3, 0x60, 0x18, 0x02, 0xf7, 0xc0, 0x50, 0x18, 0x06, 0xf7, 0xc2, 0x7d, 0x30, 0x1c, 0x46, 0x80, 0x7e, 0x5d, 0xe3, 0x51, 0x70, 0x3f, 0x8c, 0x86, 0x31, 0x30, 0x16, 0x1e, 0x80, 0x71, 0xf0, 0x20, 0xfb, 0x39, 0x9e, 0xfd, 0x98, 0x00, 0x13, 0xe1, 0x61, 0xf6, 0x6b, 0x12, 0x4c, 0x86, 0x47, 0xe1, 0x31, 0x98, 0xc2, 0xe3, 0x8f, 0xcb, 0x22, 0xf1, 0x04, 0xcb, 0x27, 0xe1, 0x29, 0x6e, 0x4f, 0x85, 0x69, 0xf0, 0x1c, 0x4c, 0xe7, 0xb1, 0xe7, 0x59, 0xbe, 0x20, 0xdd, 0xf8, 0x1f, 0x37, 0xfe, 0xc7, 0x8d, 0xff, 0xd1, 0xcf, 0x3f, 0x16, 0x92, 0x65, 0x29, 0x62, 0xb6, 0xb4, 0x89, 0x97, 0xe1, 0x15, 0x78, 0x15, 0x5e, 0x83, 0xd7, 0x61, 0x0e, 0xcc, 0x85, 0x37, 0x60, 0x1e, 0xcc, 0xc7, 0x8b, 0xbd, 0x69, 0x5c, 0xd9, 0x78, 0x57, 0xf8, 0x1b, 0x24, 0x9b, 0xc9, 0xcc, 0x24, 0xf1, 0x8e, 0x3c, 0x6c, 0xf2, 0xa0, 0x83, 0x63, 0xa5, 0xd3, 0x3c, 0x05, 0x9e, 0x91, 0x85, 0xe6, 0x67, 0x61, 0xa6, 0x2c, 0x32, 0xcf, 0x62, 0x39, 0x9b, 0x25, 0xeb, 0x36, 0xb3, 0x6e, 0xf3, 0xeb, 0xf2, 0x30, 0x3e, 0xc8, 0x65, 0x5e, 0xc8, 0x32, 0x51, 0xba, 0xc9, 0xd2, 0x24, 0xf3, 0x27, 0x3c, 0x67, 0x09, 0x2c, 0xe5, 0xb1, 0x2f, 0x64, 0x0a, 0x19, 0x9b, 0x42, 0xc6, 0xa6, 0x90, 0xb1, 0x29, 0x64, 0x6c, 0x0a, 0x19, 0x9b, 0x42, 0xc6, 0xa6, 0x90, 0xb1, 0x29, 0x64, 0x6c, 0x0a, 0x19, 0x9b, 0x62, 0x5e, 0xcb, 0xba, 0xbe, 0x91, 0xb6, 0xc8, 0x04, 0x59, 0x18, 0x55, 0x1f, 0x92, 0x64, 0x0a, 0xd9, 0x9b, 0x12, 0x95, 0x6c, 0x9c, 0x5f, 0x4c, 0x21, 0x83, 0x53, 0xc8, 0xe0, 0x14, 0x32, 0x38, 0x85, 0x0c, 0x4e, 0x89, 0x2a, 0x94, 0xc5, 0xd1, 0xad, 0x64, 0x51, 0x74, 0x6b, 0x59, 0x18, 0xdd, 0x56, 0xda, 0xf0, 0x49, 0x2e, 0x7c, 0x92, 0x0b, 0x9f, 0xe4, 0xc2, 0x27, 0xb9, 0xf0, 0x49, 0x2e, 0x7c, 0x92, 0x0b, 0x9f, 0xe4, 0xc2, 0x27, 0xb9, 0xf0, 0x49, 0x2e, 0x3c, 0x92, 0x0b, 0x8f, 0xe4, 0xc2, 0x23, 0xb9, 0xf0, 0x48, 0x2e, 0x3c, 0x92, 0x0b, 0x8f, 0xe4, 0xc2, 0x23, 0xb9, 0xf0, 0x48, 0x2e, 0x3c, 0x92, 0x0b, 0x8f, 0xe4, 0xc2, 0x23, 0xb9, 0xf0, 0x48, 0x2e, 0x3c, 0x92, 0x0b, 0x8f, 0xe4, 0xc2, 0x23, 0xb9, 0xf0, 0x48, 0x2e, 0x3c, 0x92, 0x0b, 0x8f, 0xe4, 0xc2, 0x1b, 0xb9, 0xf0, 0x43, 0x95, 0x78, 0xa1, 0x4a, 0xe5, 0x19, 0x99, 0xaf, 0x3c, 0x0b, 0xcf, 0xc9, 0xa0, 0x32, 0x1d, 0x12, 0xe0, 0x79, 0x78, 0x01, 0x66, 0xc0, 0x8b, 0x3c, 0xe7, 0x25, 0x98, 0x2d, 0x9d, 0xca, 0xcb, 0xf0, 0x0a, 0xbc, 0x0a, 0x73, 0xe1, 0x0d, 0x98, 0x07, 0xf3, 0xe1, 0x4d, 0xf8, 0x58, 0xfa, 0xf0, 0x55, 0x3e, 0x7c, 0x95, 0x0f, 0x5f, 0xe5, 0xc3, 0x57, 0xf9, 0xf0, 0x55, 0x3e, 0x7c, 0x95, 0x0f, 0x5f, 0xe5, 0xc3, 0x57, 0xf9, 0xf0, 0x54, 0x3e, 0x3c, 0x95, 0x0f, 0x4f, 0xe5, 0x53, 0x56, 0xc2, 0x2a, 0xf8, 0x1a, 0x56, 0xc3, 0x1a, 0x58, 0x0b, 0xdf, 0xc0, 0x3a, 0x58, 0x0f, 0xdf, 0xc2, 0x77, 0xf0, 0x3d, 0xfc, 0x00, 0x1b, 0x60, 0x23, 0xfc, 0x02, 0xbf, 0xc2, 0x26, 0xd8, 0x0c, 0x5b, 0x65, 0xa1, 0xb2, 0x0d, 0x7e, 0x83, 0x42, 0x99, 0x82, 0x12, 0xa5, 0xa0, 0x44, 0x29, 0x28, 0x51, 0x0a, 0x4a, 0x94, 0x82, 0x12, 0xa5, 0xa0, 0x44, 0x29, 0x28, 0x51, 0x0a, 0x4a, 0x94, 0x82, 0x12, 0xa5, 0xa0, 0x44, 0x29, 0x28, 0x51, 0x0a, 0x4a, 0x94, 0x82, 0x12, 0xa5, 0xa0, 0x44, 0x29, 0x28, 0x51, 0x0a, 0x4a, 0x94, 0x82, 0x12, 0xa5, 0xe0, 0x7f, 0x5c, 0xf8, 0x1f, 0x17, 0xfe, 0xc7, 0x85, 0xff, 0x71, 0xe1, 0x7f, 0x5c, 0xf8, 0x1f, 0x17, 0xfe, 0xc7, 0xa5, 0xd6, 0x96, 0x85, 0x6a, 0x1d, 0xa8, 0x0b, 0xf5, 0x80, 0x39, 0x57, 0x1b, 0xc0, 0x55, 0xd2, 0xa7, 0x36, 0x82, 0xc6, 0x10, 0x07, 0x4d, 0xa0, 0x29, 0x34, 0x83, 0xe6, 0xd0, 0x02, 0x5a, 0x42, 0x1b, 0x68, 0x0b, 0xed, 0xa0, 0x03, 0x90, 0xe7, 0x2a, 0x39, 0xae, 0x76, 0x85, 0x1b, 0x58, 0x4f, 0x77, 0xe8, 0x01, 0x54, 0x19, 0xb5, 0x27, 0xf4, 0x82, 0xde, 0xd0, 0x07, 0xfa, 0xc2, 0xcd, 0x70, 0x0b, 0xdc, 0x0a, 0xfd, 0xa0, 0x3f, 0x0c, 0x90, 0x45, 0xea, 0xed, 0x30, 0x10, 0x06, 0xc1, 0x5d, 0x70, 0x37, 0x0c, 0x86, 0x21, 0x70, 0x0f, 0x0c, 0x85, 0x61, 0x70, 0x2f, 0x0c, 0x07, 0x72, 0x41, 0x7d, 0x19, 0x5e, 0x87, 0x39, 0x30, 0x17, 0xde, 0x80, 0x79, 0x30, 0x1f, 0xde, 0x84, 0xb7, 0x60, 0x21, 0xbc, 0x2d, 0xdd, 0x78, 0x27, 0x37, 0xde, 0xc9, 0x8d, 0x77, 0x72, 0xe3, 0x9b, 0xdc, 0xf8, 0x26, 0x37, 0xbe, 0xc9, 0x8d, 0x6f, 0x72, 0xe3, 0x9b, 0xf4, 0x73, 0x74, 0x36, 0xf5, 0x63, 0xf8, 0x04, 0x96, 0xc0, 0x52, 0x58, 0x06, 0x9f, 0xc2, 0x67, 0xf0, 0x39, 0x7c, 0x01, 0x5f, 0xc2, 0x57, 0xb0, 0x1c, 0x56, 0xc0, 0x4a, 0x58, 0x05, 0x5f, 0xc3, 0x6a, 0x58, 0x03, 0xe4, 0x94, 0x4a, 0x4e, 0xa9, 0xeb, 0x60, 0x3d, 0x7c, 0x0b, 0xdf, 0xc1, 0xf7, 0xf0, 0x03, 0x6c, 0x80, 0x1f, 0xe1, 0x27, 0xf8, 0x19, 0x36, 0xc2, 0x2f, 0xf0, 0x2b, 0x6c, 0x82, 0xcd, 0xb0, 0x05, 0xb6, 0xc2, 0x36, 0xf8, 0x0d, 0x77, 0x96, 0x04, 0xdb, 0x61, 0x07, 0xec, 0x84, 0x64, 0xd8, 0x05, 0xbb, 0x61, 0x0f, 0xec, 0x85, 0x7d, 0x60, 0x83, 0xfd, 0x70, 0x00, 0xec, 0x70, 0x10, 0x0e, 0xc1, 0x61, 0x48, 0x85, 0x23, 0xe0, 0x80, 0x63, 0xc6, 0xd5, 0xbe, 0x77, 0x51, 0x29, 0x76, 0xfd, 0x17, 0x9f, 0xe8, 0xde, 0xac, 0xe6, 0x82, 0x53, 0x26, 0x51, 0x31, 0x92, 0xa8, 0x18, 0x49, 0x54, 0x8c, 0x24, 0x2a, 0x46, 0x12, 0x15, 0x23, 0x89, 0x8a, 0x91, 0x44, 0xc5, 0x48, 0xa2, 0x62, 0x24, 0x51, 0x31, 0x92, 0xd4, 0x52, 0x79, 0x58, 0x2d, 0x83, 0x00, 0x04, 0xe1, 0x2c, 0x94, 0xcb, 0xc3, 0xd6, 0x74, 0x59, 0x64, 0xad, 0x90, 0xc5, 0xd6, 0x4a, 0x08, 0xc1, 0x39, 0x38, 0x8f, 0x06, 0xf6, 0xc4, 0x69, 0xc6, 0xe1, 0x34, 0xe3, 0x70, 0x9a, 0x71, 0x38, 0xcd, 0x19, 0x38, 0xcd, 0x38, 0x9c, 0xa6, 0x09, 0xa7, 0x19, 0x87, 0xd3, 0x8c, 0xc3, 0x69, 0xc6, 0x5d, 0xe6, 0x12, 0xe3, 0x70, 0x89, 0x71, 0xb8, 0xc4, 0x38, 0x5c, 0x62, 0x1c, 0x2e, 0x31, 0x0e, 0x97, 0x18, 0x87, 0x4b, 0x8c, 0xc3, 0x19, 0xc6, 0x29, 0x4b, 0x44, 0x0d, 0xdc, 0x61, 0x1c, 0xee, 0x30, 0x0e, 0x77, 0x18, 0x87, 0x3b, 0x8c, 0xc3, 0x1d, 0xc6, 0xe1, 0x0e, 0xe3, 0x70, 0x87, 0x71, 0xb8, 0xc3, 0x38, 0xdc, 0x61, 0x9c, 0xda, 0x04, 0x37, 0xdf, 0x54, 0x6c, 0x50, 0x9b, 0x89, 0x37, 0x71, 0x8a, 0x13, 0xd4, 0x16, 0x62, 0x0a, 0xae, 0xff, 0xfa, 0x4b, 0x8e, 0xb1, 0xab, 0xf8, 0x05, 0xd7, 0x18, 0x87, 0x6b, 0x8c, 0xc3, 0x35, 0xc6, 0xe1, 0x1a, 0xe3, 0x70, 0x8d, 0x71, 0xb8, 0xc6, 0x38, 0x5c, 0x63, 0x1c, 0xae, 0x31, 0x0e, 0xd7, 0x18, 0x87, 0x6b, 0x8c, 0xc3, 0x35, 0xc6, 0xe1, 0x1a, 0xe3, 0x70, 0x8d, 0x71, 0xb8, 0xc6, 0x38, 0xf5, 0x1d, 0x71, 0x9d, 0x35, 0x5d, 0xf4, 0x11, 0x37, 0x8b, 0x87, 0xc5, 0x5b, 0x62, 0x12, 0x4c, 0xe6, 0x88, 0x1e, 0x65, 0xf9, 0x98, 0x30, 0x99, 0x86, 0xca, 0xdf, 0xaa, 0x1f, 0x11, 0x3d, 0xe8, 0xd5, 0x51, 0x85, 0xa2, 0xa1, 0xf2, 0xb1, 0x98, 0xce, 0xde, 0xf7, 0x50, 0x96, 0x8a, 0xde, 0xca, 0x32, 0x31, 0x5e, 0xf9, 0x54, 0x0c, 0x52, 0x3e, 0x63, 0xf9, 0xb9, 0x68, 0x4c, 0x1f, 0x57, 0x5f, 0xd9, 0x21, 0x1a, 0x28, 0x3b, 0xc5, 0x43, 0xca, 0x21, 0x31, 0x4c, 0x49, 0x15, 0xb5, 0x95, 0x23, 0x62, 0x8c, 0x72, 0x54, 0xf4, 0x51, 0x1c, 0xa2, 0x8d, 0x72, 0x4c, 0xf4, 0x56, 0xaf, 0x12, 0x6f, 0xa9, 0x8d, 0xa0, 0x31, 0xc4, 0x89, 0x00, 0x47, 0xd8, 0x8b, 0x23, 0x3c, 0xc2, 0x11, 0xe6, 0x72, 0x84, 0x67, 0xd4, 0x16, 0x26, 0xb3, 0xda, 0xd2, 0x54, 0x53, 0x6d, 0xc3, 0xdf, 0xdb, 0x8a, 0x7a, 0x1c, 0x69, 0x1d, 0xb5, 0x03, 0xb7, 0x3b, 0x8a, 0x27, 0xd4, 0xce, 0x2c, 0xbb, 0x8a, 0x34, 0xf5, 0x2e, 0x71, 0x9f, 0x7a, 0xb7, 0xa8, 0xa5, 0x8e, 0x10, 0x77, 0xaa, 0xa3, 0xe8, 0x7f, 0xee, 0x67, 0x94, 0x46, 0x8b, 0xee, 0xea, 0x18, 0x8e, 0x6a, 0x2c, 0xcb, 0x39, 0x8c, 0xd8, 0x42, 0x5e, 0xfb, 0x9b, 0x18, 0xa6, 0x6e, 0x17, 0xb5, 0xd5, 0x1d, 0xa2, 0x8f, 0xba, 0x53, 0xb4, 0x51, 0x93, 0xd9, 0x7e, 0xbe, 0xa8, 0xaf, 0x9e, 0x11, 0xfd, 0x38, 0xf2, 0x23, 0xa2, 0xc6, 0x95, 0x8e, 0x52, 0x3f, 0x0a, 0xf6, 0xa8, 0xc1, 0xa5, 0xad, 0xeb, 0x5b, 0xee, 0x2a, 0x1a, 0xeb, 0x6b, 0x14, 0x91, 0xa2, 0x97, 0x0c, 0xea, 0xbf, 0xfe, 0x62, 0x9e, 0x2f, 0x6a, 0xc7, 0x0c, 0x90, 0xa9, 0xa2, 0xb9, 0xfe, 0x08, 0x23, 0xd8, 0x85, 0x11, 0xec, 0x12, 0x1e, 0xc1, 0x2e, 0xc6, 0x08, 0x86, 0x9f, 0xc5, 0x3a, 0xbb, 0x30, 0x6a, 0xd7, 0x30, 0x2a, 0xd1, 0x8c, 0x86, 0x89, 0x11, 0xe8, 0xc2, 0x08, 0x74, 0x61, 0x04, 0xba, 0x30, 0x02, 0x9f, 0x31, 0x02, 0x26, 0xb6, 0xf7, 0x04, 0x23, 0xf0, 0x2a, 0x23, 0x30, 0x87, 0x39, 0xfe, 0x8c, 0x39, 0x5e, 0xcb, 0x08, 0x74, 0x61, 0x1f, 0xba, 0xb0, 0x0f, 0x5d, 0x18, 0x81, 0x2e, 0xec, 0x47, 0x17, 0x46, 0xa0, 0x0b, 0xfb, 0x32, 0x8d, 0x23, 0x1c, 0xc3, 0x11, 0xbc, 0x68, 0xec, 0x41, 0x9c, 0xb0, 0xe2, 0xa8, 0x6b, 0x43, 0x43, 0x59, 0x22, 0xda, 0xc8, 0x6c, 0xf6, 0xa8, 0x44, 0xe0, 0xca, 0xae, 0xf4, 0xfb, 0x0a, 0xe6, 0x47, 0xe4, 0x59, 0xf3, 0x63, 0x32, 0x60, 0xfe, 0x5e, 0x16, 0x98, 0x7f, 0x91, 0x25, 0xd5, 0x7f, 0x3b, 0x21, 0xaa, 0x29, 0xce, 0x38, 0x07, 0x4e, 0xc9, 0x80, 0x85, 0xe7, 0x58, 0x5e, 0x84, 0x97, 0x20, 0x20, 0x03, 0xca, 0x4d, 0x32, 0x10, 0xf3, 0xae, 0xcc, 0x8d, 0x79, 0x4f, 0xe6, 0xd2, 0x71, 0x54, 0x5d, 0x85, 0x2e, 0x5b, 0x1c, 0xc3, 0x49, 0xe9, 0xd7, 0x4a, 0xcb, 0x90, 0x67, 0x45, 0x26, 0xae, 0x23, 0x5b, 0xe6, 0x88, 0x1c, 0xa9, 0x89, 0x53, 0x78, 0x79, 0x97, 0x3c, 0x25, 0xdc, 0xfc, 0xad, 0x40, 0xff, 0x35, 0x24, 0x61, 0xc1, 0xd3, 0x57, 0x9a, 0xfa, 0xb2, 0x37, 0xb7, 0xc0, 0x20, 0xfd, 0x3a, 0xb0, 0x74, 0x65, 0xf4, 0x0c, 0xec, 0x55, 0x39, 0x1d, 0x97, 0xc5, 0xf4, 0xa0, 0x94, 0xa6, 0x87, 0xa4, 0x8c, 0x58, 0x21, 0xcb, 0x23, 0xbe, 0x66, 0x5d, 0x71, 0xc2, 0x8c, 0xf7, 0xe8, 0x25, 0x77, 0x72, 0x2c, 0x87, 0xc4, 0xbb, 0xb2, 0xbb, 0x78, 0x4f, 0xde, 0x20, 0xde, 0x97, 0x37, 0xb0, 0x26, 0xbd, 0x2b, 0x78, 0xce, 0x34, 0xf4, 0x42, 0x9e, 0x69, 0x94, 0xec, 0x66, 0x9a, 0x28, 0x4f, 0x99, 0x26, 0xc1, 0x64, 0xd9, 0xd5, 0xf4, 0x08, 0xf7, 0x71, 0xc9, 0xe6, 0x3b, 0xf1, 0x1b, 0x8f, 0xc8, 0xdd, 0xcc, 0x80, 0xd5, 0xfc, 0xa6, 0x1c, 0xc6, 0xf1, 0x96, 0x73, 0xbc, 0x05, 0x91, 0x73, 0x64, 0xb7, 0xc8, 0x85, 0x72, 0x4a, 0xe4, 0x47, 0xcc, 0x76, 0x77, 0xd9, 0x33, 0xaa, 0x87, 0xec, 0x1e, 0xb5, 0x4b, 0x0e, 0xb3, 0x66, 0xcb, 0x5e, 0x7f, 0x18, 0xc9, 0x62, 0x46, 0x32, 0x8b, 0xad, 0x17, 0xb3, 0x75, 0x5f, 0xb5, 0x91, 0xcc, 0x08, 0x5f, 0x2b, 0xfd, 0xd8, 0x65, 0x23, 0x59, 0xcc, 0x48, 0xa6, 0x85, 0xaf, 0x56, 0xfc, 0xef, 0x8c, 0xe4, 0x69, 0x46, 0xf2, 0xb4, 0x88, 0xf9, 0x6f, 0x45, 0x5f, 0xf5, 0xab, 0x74, 0xa7, 0x18, 0xd7, 0xce, 0x1d, 0x4b, 0xcf, 0x3a, 0x8e, 0x9e, 0x75, 0x9c, 0x59, 0x30, 0x5a, 0x47, 0x85, 0x85, 0x3e, 0x4a, 0x81, 0x9a, 0x70, 0x0d, 0xc4, 0x41, 0x13, 0xfd, 0x57, 0xd8, 0xa0, 0xea, 0x3b, 0xd9, 0x23, 0x45, 0x5b, 0x59, 0x26, 0xae, 0x85, 0x76, 0x72, 0x39, 0x6e, 0x38, 0x09, 0x27, 0x3c, 0x0b, 0xf7, 0x1b, 0xa2, 0x87, 0xf4, 0xd2, 0x43, 0x7a, 0xe9, 0x21, 0xbd, 0xf4, 0x90, 0x5e, 0x7a, 0x48, 0x2f, 0x3d, 0xa4, 0x97, 0x1e, 0xb2, 0x98, 0x1e, 0xb2, 0x98, 0x1e, 0xb2, 0x98, 0x1e, 0xd2, 0x4b, 0x0f, 0xe9, 0xc5, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xa4, 0x87, 0x2c, 0xc2, 0x29, 0x87, 0x70, 0xca, 0x21, 0x9c, 0x72, 0x08, 0xa7, 0x1c, 0xc2, 0x29, 0x87, 0x70, 0xca, 0x21, 0x9c, 0x72, 0x08, 0xa7, 0x1c, 0xc2, 0x29, 0x87, 0x70, 0xca, 0x21, 0x9c, 0x72, 0x08, 0xa7, 0x1c, 0xc2, 0x29, 0x87, 0x70, 0xca, 0x21, 0x9c, 0x72, 0x88, 0xfe, 0xd3, 0x4b, 0xff, 0xe9, 0xa5, 0xff, 0xf4, 0xe2, 0x9a, 0x43, 0xb8, 0xe6, 0x10, 0x7d, 0x68, 0x31, 0xce, 0x39, 0x84, 0x53, 0x0e, 0xe1, 0x94, 0x9d, 0x38, 0x65, 0x37, 0x4e, 0xd9, 0x8d, 0x53, 0x76, 0xe3, 0x94, 0xdd, 0x38, 0x65, 0x37, 0x4e, 0xd9, 0x8d, 0x53, 0x76, 0xe3, 0x94, 0xdd, 0x38, 0x65, 0x37, 0x4e, 0xd9, 0x8d, 0x53, 0xf6, 0xe2, 0x94, 0xbd, 0x38, 0x65, 0x1b, 0x4e, 0x39, 0x17, 0x97, 0xbc, 0x87, 0x68, 0x2d, 0x20, 0x5a, 0x5d, 0x44, 0xab, 0xcf, 0xd4, 0x03, 0xfa, 0xc8, 0xbd, 0xc4, 0x7e, 0xb2, 0xe9, 0x66, 0x96, 0xb7, 0xb0, 0x1c, 0x24, 0xb7, 0x11, 0xb9, 0xfb, 0x88, 0xf9, 0x2c, 0x62, 0x3e, 0x8b, 0xc8, 0xdd, 0x49, 0xe4, 0x3a, 0x4d, 0xf9, 0xe4, 0xc3, 0x19, 0x59, 0x46, 0x8f, 0xeb, 0xc5, 0x65, 0x7b, 0xcd, 0xf4, 0xe3, 0x66, 0xfa, 0x6e, 0x33, 0x3d, 0x36, 0x8e, 0x7b, 0x96, 0x79, 0x3c, 0xcb, 0x47, 0x64, 0x19, 0xce, 0x7b, 0x96, 0xf9, 0x71, 0x96, 0xcf, 0xc8, 0x10, 0xee, 0x3b, 0x44, 0x3f, 0xec, 0xa5, 0x1f, 0x2e, 0xc6, 0x85, 0x87, 0x70, 0xe1, 0x21, 0x5c, 0x78, 0x08, 0x17, 0xee, 0xc6, 0x85, 0xbb, 0xe9, 0x91, 0x8b, 0xe9, 0x91, 0x8b, 0x71, 0xdf, 0x7b, 0x70, 0xdf, 0x21, 0xdc, 0x77, 0x08, 0xe7, 0xed, 0xc4, 0x79, 0x3b, 0x71, 0xde, 0x4e, 0x9c, 0xb7, 0x13, 0xe7, 0xed, 0xc4, 0x79, 0x3b, 0x71, 0xde, 0x4e, 0x9c, 0xb7, 0x13, 0xe7, 0xed, 0xc4, 0x79, 0x3b, 0x71, 0xde, 0x6e, 0x9c, 0xb7, 0x9b, 0x9e, 0xb9, 0x08, 0xf7, 0x1d, 0x22, 0x7b, 0xb2, 0x71, 0xe0, 0x21, 0x32, 0xe7, 0x4b, 0x32, 0xe7, 0x4b, 0x9c, 0xb8, 0x13, 0x27, 0xee, 0xc4, 0x89, 0x87, 0x70, 0xe2, 0x4e, 0x9c, 0xb8, 0x13, 0x27, 0xee, 0xc4, 0x89, 0x3b, 0x71, 0xe2, 0xce, 0xa8, 0x2c, 0xa3, 0x9f, 0xf6, 0xd2, 0x4f, 0x17, 0xe3, 0xca, 0xbd, 0xb8, 0xf2, 0x10, 0xae, 0x3c, 0x84, 0x2b, 0x77, 0xd3, 0x5f, 0x7b, 0x2d, 0x4f, 0xca, 0x0a, 0xcb, 0x53, 0x30, 0x15, 0xa6, 0xc1, 0x73, 0x40, 0xdf, 0x6e, 0xa1, 0x6f, 0xb7, 0xd0, 0xb7, 0x5b, 0xe8, 0xdb, 0x2d, 0xf4, 0xed, 0x16, 0xfa, 0x76, 0xcb, 0x5a, 0x7a, 0xd8, 0x58, 0x20, 0xb6, 0x95, 0x5a, 0x50, 0x1b, 0xea, 0x40, 0x5d, 0xa8, 0x07, 0xf5, 0xe1, 0x6a, 0x68, 0x04, 0x8d, 0x81, 0xf8, 0x57, 0x88, 0x7f, 0x85, 0xf8, 0x57, 0x9a, 0x42, 0x33, 0x20, 0x0f, 0x94, 0x16, 0xd0, 0x12, 0x5a, 0x41, 0x6b, 0x68, 0x03, 0x6d, 0xe1, 0x5a, 0x68, 0x07, 0xd7, 0xc1, 0xf5, 0xd0, 0x1d, 0xfe, 0xf5, 0xf7, 0x5e, 0xcb, 0x94, 0x07, 0x60, 0x1c, 0x3c, 0x08, 0x0f, 0xc1, 0x78, 0x98, 0x00, 0x13, 0xe1, 0x61, 0x98, 0x04, 0x93, 0x81, 0x39, 0x53, 0x1e, 0x85, 0xc7, 0x60, 0x0a, 0x30, 0x77, 0xca, 0x13, 0xf0, 0x24, 0x3c, 0x05, 0x53, 0x61, 0x1a, 0xe8, 0xbf, 0x89, 0xfd, 0x8c, 0x5c, 0x4e, 0xd7, 0xb0, 0x9c, 0xae, 0x21, 0x89, 0xae, 0x21, 0x89, 0xae, 0x21, 0x89, 0xae, 0x21, 0x89, 0xae, 0x21, 0x89, 0xae, 0x21, 0x89, 0x6e, 0x61, 0x16, 0xdd, 0xc2, 0x2c, 0xba, 0x85, 0x59, 0x74, 0x0b, 0xb3, 0xe8, 0x16, 0x66, 0xd1, 0x2d, 0xcc, 0xa2, 0x5b, 0x98, 0x45, 0xb7, 0x30, 0x8b, 0x6e, 0x61, 0x16, 0x8e, 0x3d, 0x84, 0x63, 0x0f, 0xe1, 0xd8, 0x43, 0xca, 0x76, 0xe9, 0x55, 0x76, 0xc0, 0x4e, 0x48, 0x86, 0x3d, 0x80, 0x13, 0x53, 0x70, 0x62, 0x0a, 0x4e, 0x4c, 0xc1, 0x89, 0x29, 0x38, 0x30, 0xe5, 0x10, 0x8f, 0x1f, 0x86, 0x54, 0x38, 0x02, 0x47, 0xc1, 0x01, 0xc7, 0xe0, 0x38, 0x64, 0xc2, 0x09, 0x38, 0x09, 0x59, 0x90, 0x0d, 0x39, 0x70, 0x0a, 0x98, 0x63, 0x25, 0x17, 0x9c, 0xe0, 0x82, 0x7c, 0xd0, 0x7f, 0x6b, 0x84, 0x38, 0x56, 0x0a, 0xc0, 0x0b, 0x85, 0x74, 0x31, 0xc5, 0x50, 0x02, 0x3e, 0xf0, 0x83, 0x06, 0xa5, 0x50, 0x06, 0x01, 0x08, 0xc2, 0x59, 0x28, 0x87, 0x0a, 0xa8, 0x84, 0x10, 0x9c, 0x83, 0xf3, 0x70, 0x41, 0x3a, 0xd5, 0x68, 0xdc, 0xb2, 0x05, 0x14, 0x50, 0x21, 0x16, 0x6a, 0x42, 0x2d, 0xa8, 0x2d, 0x43, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x75, 0x84, 0xf4, 0xaa, 0x23, 0x61, 0x14, 0xdc, 0x0f, 0xa3, 0x61, 0x0c, 0x8c, 0x85, 0x07, 0x60, 0x3c, 0x4c, 0x80, 0x89, 0xf0, 0x30, 0x4c, 0x82, 0xc9, 0xf0, 0x08, 0x3c, 0x0a, 0x8f, 0xc1, 0x14, 0x78, 0x1c, 0x9e, 0x80, 0x27, 0xe1, 0x29, 0x98, 0x0a, 0xd3, 0x60, 0x26, 0xcc, 0x82, 0x17, 0x01, 0xad, 0xa0, 0x4b, 0x09, 0xd1, 0xa5, 0x84, 0xe8, 0x52, 0x42, 0x74, 0x29, 0x21, 0xba, 0x94, 0x10, 0x5d, 0x4a, 0x88, 0x2e, 0x25, 0x44, 0x97, 0x12, 0xa2, 0x4b, 0x09, 0xd1, 0xa5, 0x84, 0xe8, 0x40, 0xdc, 0x74, 0x20, 0x6e, 0x3a, 0x10, 0x37, 0x1d, 0x88, 0x9b, 0x0e, 0xc4, 0x4d, 0x07, 0xe2, 0xa6, 0x03, 0x71, 0xd3, 0x81, 0xb8, 0xe9, 0x40, 0xdc, 0x74, 0x20, 0x6e, 0x3a, 0x10, 0x37, 0x1d, 0x88, 0x9b, 0x0e, 0xc4, 0x4d, 0x07, 0xe2, 0xa6, 0x03, 0x71, 0xd3, 0x81, 0xb8, 0xe9, 0x40, 0xdc, 0x74, 0x20, 0x6e, 0x3a, 0x10, 0x37, 0x1d, 0x88, 0x9b, 0x0e, 0xc4, 0x4d, 0x07, 0xe2, 0xa6, 0x03, 0x71, 0xd3, 0x81, 0xb8, 0xe9, 0x40, 0xdc, 0x74, 0x20, 0x6e, 0x3a, 0x10, 0x37, 0x1d, 0x88, 0x9b, 0x0e, 0xc4, 0x4d, 0x07, 0xe2, 0xa6, 0x03, 0x71, 0xd3, 0x81, 0xb8, 0xe9, 0x40, 0xdc, 0x74, 0x20, 0x6e, 0x3a, 0x10, 0x37, 0x1d, 0x88, 0x9b, 0x0e, 0xc4, 0x4d, 0x07, 0xe2, 0xa6, 0x03, 0x71, 0xd3, 0x81, 0x78, 0xe9, 0x40, 0xbc, 0x74, 0x20, 0x5e, 0x3a, 0x10, 0x2f, 0x1d, 0x88, 0x97, 0x0e, 0xc4, 0x4b, 0x07, 0xe2, 0xa5, 0x03, 0xf1, 0xd2, 0x81, 0x78, 0xe9, 0x40, 0xbc, 0x74, 0x20, 0x5e, 0x3a, 0x10, 0x2f, 0x1d, 0x88, 0x97, 0x0e, 0xc4, 0x4b, 0x07, 0xe2, 0xa5, 0x03, 0xf1, 0xd2, 0x81, 0x78, 0xe9, 0x40, 0xbc, 0x74, 0x20, 0x5e, 0x3a, 0x10, 0x2f, 0x1d, 0x88, 0x97, 0x0e, 0xc4, 0x4b, 0x07, 0x62, 0xa3, 0x03, 0xb1, 0xd1, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x79, 0xec, 0xa1, 0xf3, 0xd8, 0x43, 0xe7, 0xb1, 0x87, 0xce, 0x63, 0x0f, 0x9d, 0xc7, 0x1e, 0x3a, 0x8f, 0x3d, 0x74, 0x1e, 0x7b, 0xe8, 0x3c, 0xf6, 0xd0, 0x79, 0xec, 0xa1, 0xf3, 0xd8, 0x43, 0x87, 0x11, 0xc2, 0x69, 0xb8, 0xac, 0x05, 0xb2, 0xc2, 0xea, 0x85, 0x42, 0x28, 0x82, 0x62, 0x28, 0x91, 0x65, 0x56, 0x1f, 0xf8, 0x41, 0x83, 0x52, 0x28, 0x93, 0x5e, 0x6b, 0x00, 0x82, 0x70, 0x16, 0xca, 0x01, 0xdd, 0xa4, 0x43, 0xf1, 0xd2, 0xa1, 0x78, 0xe9, 0x50, 0xbc, 0x74, 0x28, 0xde, 0x18, 0x72, 0x18, 0x2f, 0x51, 0xf5, 0x9b, 0x8d, 0x05, 0xe1, 0x4f, 0x53, 0xea, 0xbf, 0xd9, 0xb8, 0x1f, 0xa7, 0x70, 0x24, 0xfc, 0xab, 0x35, 0xa5, 0xf8, 0x8c, 0x52, 0x7c, 0x86, 0xf1, 0x2d, 0x32, 0xe3, 0xd7, 0x7f, 0xf4, 0x5f, 0xab, 0x2c, 0xd3, 0x7f, 0x81, 0xc8, 0xd4, 0xc5, 0xf8, 0xcc, 0xe6, 0xc1, 0xf0, 0xef, 0x37, 0xae, 0xe3, 0xd5, 0x87, 0x78, 0x75, 0x52, 0xf8, 0x17, 0x2b, 0x33, 0x22, 0x4d, 0x68, 0x7f, 0xb4, 0xac, 0x88, 0x1c, 0x22, 0xfd, 0x91, 0x43, 0x65, 0x20, 0xf2, 0x5e, 0xe3, 0x3a, 0x0b, 0x47, 0xa8, 0x01, 0x47, 0x2c, 0x67, 0x8c, 0xdf, 0x07, 0xf6, 0x1a, 0xbf, 0xdf, 0x7b, 0xce, 0x44, 0xef, 0x5e, 0xed, 0x57, 0x73, 0x92, 0xc3, 0x9f, 0x23, 0xbc, 0xf4, 0x3b, 0x49, 0x91, 0x23, 0x8c, 0xdf, 0x4a, 0x3a, 0xcd, 0x2b, 0x4f, 0xf3, 0xca, 0x20, 0xaf, 0x2c, 0x14, 0x4d, 0xf5, 0xdf, 0xbe, 0x0f, 0xff, 0xda, 0x5f, 0xa9, 0xf1, 0x2b, 0x7f, 0xfa, 0xef, 0x4a, 0xd6, 0x97, 0x07, 0x8c, 0x5f, 0xe5, 0xeb, 0xc4, 0xbe, 0x74, 0x91, 0xc5, 0xe1, 0xcf, 0x1e, 0x6a, 0x11, 0xbf, 0xe0, 0x0f, 0xf5, 0xdf, 0xa7, 0x4f, 0x37, 0x7e, 0xfd, 0xd4, 0x17, 0xbe, 0xae, 0xa6, 0x3f, 0x72, 0x80, 0x2c, 0x0f, 0x5f, 0x5b, 0xf3, 0x00, 0xfb, 0x98, 0xcf, 0x3e, 0x1e, 0x63, 0x6b, 0xfa, 0x55, 0x1a, 0x32, 0xf5, 0xab, 0x86, 0x58, 0x26, 0xcb, 0x7c, 0x0b, 0x63, 0xae, 0xbe, 0x2f, 0x73, 0xd4, 0x14, 0x3a, 0xbd, 0x7f, 0xb2, 0xe5, 0x3a, 0xc6, 0xa7, 0xf0, 0x63, 0xd8, 0x72, 0x2d, 0x79, 0x82, 0xad, 0x86, 0xd8, 0x62, 0x06, 0x5b, 0x3c, 0x6b, 0xea, 0x2c, 0x9a, 0xb3, 0xd5, 0x93, 0x38, 0x53, 0x85, 0xad, 0x64, 0xb1, 0xb6, 0xf2, 0xc8, 0x11, 0xc6, 0xb7, 0x2e, 0x4b, 0xd5, 0x56, 0xe2, 0x1a, 0xb5, 0xb5, 0xe8, 0xcc, 0x5a, 0x7c, 0xc6, 0xaf, 0x58, 0x46, 0xe3, 0x87, 0xcf, 0xe3, 0x85, 0xbd, 0x78, 0xe1, 0x42, 0xfc, 0xaf, 0x0f, 0xff, 0xeb, 0xc3, 0xfb, 0x7a, 0xfe, 0x07, 0xd7, 0x05, 0x6e, 0x27, 0x0e, 0xca, 0x4c, 0x71, 0x58, 0x9e, 0x13, 0x47, 0xf0, 0xda, 0x47, 0x65, 0x3a, 0x6b, 0xda, 0xc9, 0x36, 0x4a, 0xc2, 0x7e, 0x3b, 0x03, 0xbf, 0xfd, 0x0b, 0x6b, 0x3d, 0xc1, 0xf6, 0xce, 0xb1, 0xbd, 0x4a, 0xd6, 0x1e, 0x62, 0xed, 0x7b, 0xd8, 0xae, 0x8b, 0x2d, 0x9c, 0xc1, 0x77, 0xe7, 0x2a, 0x1d, 0xa8, 0x27, 0x9d, 0xa1, 0x0b, 0xb1, 0xd4, 0x58, 0x9e, 0xb3, 0xc6, 0x41, 0x13, 0x68, 0x0a, 0xcd, 0xa0, 0x39, 0xb4, 0x80, 0x96, 0xd0, 0x0a, 0x5a, 0x43, 0x1b, 0x68, 0x0b, 0xd7, 0x42, 0x3b, 0xb8, 0x0e, 0xae, 0x87, 0xf6, 0xd0, 0x01, 0x3a, 0x42, 0x27, 0xb6, 0x79, 0x93, 0xd8, 0x8b, 0xdb, 0xb7, 0xb1, 0x27, 0xfb, 0xd9, 0x3b, 0x3b, 0x7b, 0x7b, 0x84, 0xe3, 0x3b, 0x6a, 0x5c, 0x9b, 0x5a, 0xdf, 0x4b, 0x1f, 0x7b, 0x59, 0xc1, 0xb1, 0x7f, 0xce, 0x5e, 0x9e, 0x63, 0x2f, 0x4b, 0xd8, 0xcb, 0x12, 0xf6, 0x52, 0xbf, 0x2e, 0xb5, 0x7e, 0x4d, 0xf6, 0x2b, 0xf6, 0x27, 0xb8, 0xff, 0x0a, 0x8e, 0xff, 0xcc, 0x1f, 0x7e, 0xd3, 0xad, 0xbb, 0xcc, 0xb0, 0xf4, 0x80, 0xbb, 0xe0, 0x6e, 0x19, 0xb4, 0x0c, 0x86, 0x21, 0xdc, 0xbe, 0x07, 0x86, 0x72, 0x7b, 0x18, 0xe0, 0xcc, 0x2d, 0xf7, 0xc1, 0x70, 0x18, 0x01, 0x23, 0x81, 0xc8, 0xb5, 0xdc, 0x0f, 0xa3, 0xf9, 0xfb, 0x18, 0x18, 0xcb, 0xed, 0x07, 0x60, 0x1c, 0xb7, 0x1f, 0x84, 0x87, 0x64, 0x86, 0x72, 0x9f, 0x4c, 0x56, 0x66, 0x4a, 0x9b, 0xf2, 0xba, 0xf4, 0x28, 0x0b, 0xe0, 0x1d, 0x78, 0x17, 0x16, 0xcb, 0x53, 0xd6, 0x99, 0x52, 0x1a, 0x9e, 0x7b, 0x37, 0x7b, 0xb7, 0x55, 0x9f, 0x1d, 0xee, 0xf5, 0xc4, 0xa9, 0xf5, 0x92, 0x3b, 0x4c, 0xbd, 0xe5, 0x0e, 0x11, 0xcd, 0xbd, 0x7c, 0xee, 0xa5, 0x73, 0x2f, 0x9d, 0xe7, 0x25, 0x87, 0x73, 0x66, 0xbb, 0xfe, 0x79, 0x1b, 0x22, 0xb8, 0xea, 0x13, 0xcd, 0xdb, 0x78, 0x64, 0xb7, 0xf1, 0xdc, 0xed, 0x3c, 0xf7, 0x37, 0x9e, 0xfb, 0x5b, 0xf8, 0x17, 0x61, 0xf5, 0xdf, 0xc7, 0x3d, 0x24, 0x54, 0xfe, 0x62, 0xe3, 0x2f, 0x3e, 0xfe, 0xe2, 0x0b, 0x3b, 0xfc, 0xdd, 0xfa, 0x6f, 0x6d, 0x32, 0x06, 0x36, 0xc6, 0x60, 0x0f, 0x63, 0x60, 0x37, 0x3e, 0x19, 0x7e, 0x50, 0xcf, 0x66, 0x63, 0x8f, 0xbc, 0xe1, 0x2e, 0x24, 0xd5, 0xd8, 0x23, 0x8d, 0x57, 0x6b, 0xbc, 0x5a, 0x13, 0x8a, 0xf1, 0x79, 0xda, 0x47, 0x64, 0x89, 0xf1, 0x19, 0xcf, 0xf0, 0xe7, 0x33, 0xf5, 0xcf, 0x61, 0x1a, 0x9f, 0x89, 0xeb, 0x2a, 0x2f, 0x88, 0x5a, 0xf4, 0x09, 0xc7, 0x79, 0x56, 0x12, 0xaf, 0xfb, 0xce, 0x74, 0xab, 0xdc, 0x68, 0xea, 0x07, 0x03, 0x8d, 0x5f, 0xa9, 0x4d, 0xbb, 0xf8, 0x2b, 0xaf, 0xa8, 0x45, 0x3e, 0x6a, 0x91, 0x8f, 0x5a, 0x68, 0xac, 0xa1, 0x8c, 0x5e, 0x53, 0xff, 0x75, 0xa9, 0x02, 0x7a, 0x90, 0xf7, 0x8c, 0xfc, 0xd6, 0xaf, 0x78, 0x7b, 0x28, 0xac, 0x14, 0x59, 0xd5, 0xf2, 0xfb, 0x38, 0x5a, 0x93, 0x8a, 0x93, 0xd5, 0x58, 0x43, 0x61, 0xf8, 0x97, 0x84, 0x8a, 0xd8, 0x97, 0x0a, 0x72, 0xfe, 0x10, 0x39, 0x7f, 0x88, 0x7d, 0xa9, 0xb0, 0x0e, 0x37, 0x34, 0xe8, 0x33, 0x63, 0x4c, 0xf6, 0xb3, 0xef, 0xfb, 0xd9, 0xf7, 0xfd, 0xac, 0x29, 0x9d, 0x35, 0x1d, 0x66, 0x4d, 0xe9, 0x17, 0xfb, 0x6c, 0x61, 0xba, 0xd4, 0x67, 0x37, 0x10, 0xfa, 0xb7, 0x75, 0xdf, 0xe3, 0x39, 0xff, 0x22, 0x73, 0xd8, 0xfa, 0x3e, 0xb6, 0x7e, 0x9a, 0xad, 0xbb, 0xd8, 0xba, 0x8b, 0xad, 0x17, 0xb2, 0xf5, 0x52, 0x46, 0xc2, 0x7d, 0xf1, 0xd7, 0xca, 0xd8, 0x83, 0x52, 0xf6, 0xc0, 0xc3, 0x5a, 0xdf, 0x12, 0x8d, 0x45, 0x29, 0x48, 0x8e, 0x3a, 0x4a, 0x2e, 0x30, 0xd5, 0x94, 0x8b, 0xd1, 0x81, 0xa3, 0xc6, 0x2f, 0x99, 0xd6, 0x17, 0x0d, 0x8c, 0xeb, 0x31, 0x75, 0x91, 0xab, 0xd9, 0xda, 0x09, 0xb6, 0x96, 0xc8, 0x96, 0x7c, 0xe1, 0x2b, 0xfb, 0x7e, 0x18, 0xb1, 0x4b, 0xbe, 0x8b, 0x2e, 0xac, 0x8c, 0x1c, 0x88, 0x3e, 0xdc, 0x6b, 0xfc, 0xee, 0xb5, 0x47, 0xbf, 0x3e, 0x11, 0x5b, 0x79, 0x85, 0xad, 0xbc, 0xa2, 0x5f, 0x23, 0xcb, 0xda, 0x48, 0x7e, 0x82, 0x3e, 0x1c, 0x45, 0xf1, 0xbf, 0x15, 0xcd, 0x19, 0xf7, 0x6d, 0xf4, 0x95, 0x21, 0x7a, 0xb0, 0x57, 0x39, 0x3a, 0x37, 0xb3, 0xe4, 0xe6, 0x98, 0x3e, 0xe3, 0x98, 0x3e, 0xa0, 0x4f, 0xf8, 0x89, 0xad, 0x64, 0xb0, 0x95, 0x77, 0xd9, 0xca, 0xc9, 0xf0, 0x56, 0xde, 0xa7, 0xb3, 0x2d, 0xa4, 0xb3, 0xd5, 0x3f, 0x03, 0x3f, 0x87, 0xe3, 0xfa, 0x82, 0xce, 0xb6, 0x80, 0x63, 0x3a, 0x47, 0x77, 0x7b, 0x9a, 0x3e, 0x20, 0x95, 0xd1, 0xe9, 0xc0, 0xf1, 0xad, 0x66, 0x8e, 0xfc, 0x78, 0xf3, 0x45, 0xe1, 0x3d, 0x98, 0xcd, 0x1e, 0xcc, 0xe6, 0x38, 0x57, 0xb3, 0xe5, 0xf5, 0xf4, 0x9a, 0x15, 0xf4, 0x9a, 0x15, 0xe2, 0x80, 0xd8, 0x47, 0xae, 0x1e, 0x94, 0x69, 0xe2, 0x90, 0xfc, 0x48, 0xa4, 0xa2, 0x4e, 0x47, 0xc8, 0xcb, 0xa3, 0x74, 0x8d, 0x0e, 0x99, 0x48, 0xce, 0x7a, 0xc4, 0x71, 0xe9, 0x37, 0xae, 0x42, 0x9e, 0x0e, 0x19, 0xe8, 0x96, 0xae, 0x30, 0x27, 0x78, 0x2c, 0x8b, 0xe7, 0x65, 0xcb, 0xef, 0xc9, 0xe1, 0x7c, 0x72, 0xf8, 0x8c, 0xc8, 0xe3, 0xef, 0x4e, 0x70, 0xc9, 0x8f, 0x45, 0x3e, 0xcb, 0x33, 0xe0, 0xa6, 0x9b, 0xf5, 0xc8, 0x63, 0xe4, 0xf5, 0x11, 0xe1, 0x65, 0x2f, 0xf5, 0xa3, 0xe9, 0xcb, 0x98, 0xdd, 0x02, 0x97, 0x1f, 0xd5, 0x83, 0xdc, 0x7f, 0x48, 0xbe, 0x1b, 0xb1, 0x5c, 0x66, 0x90, 0xef, 0x47, 0x23, 0x56, 0x4a, 0x17, 0x1d, 0xff, 0x2f, 0x11, 0xeb, 0x58, 0x7e, 0x0b, 0x1b, 0xa4, 0x2b, 0x72, 0xae, 0x2c, 0x89, 0x9c, 0x07, 0xf3, 0xe9, 0xaa, 0x17, 0x18, 0x9d, 0xb5, 0x16, 0x95, 0x4d, 0xd5, 0x39, 0x2b, 0x5d, 0x51, 0x15, 0xe4, 0xf0, 0xb3, 0xe4, 0x6e, 0x03, 0x68, 0x08, 0xed, 0xa1, 0x03, 0xb5, 0xa4, 0x13, 0xcb, 0xce, 0x2c, 0xa9, 0x66, 0x4a, 0x57, 0x6e, 0x77, 0x33, 0xf2, 0x7b, 0xba, 0xf2, 0x9a, 0x7c, 0x8e, 0xfc, 0x4e, 0x55, 0xe6, 0xb0, 0x5c, 0xc0, 0xf2, 0x1d, 0x78, 0x17, 0xde, 0xe3, 0xfe, 0xfb, 0x90, 0x22, 0x5d, 0xea, 0x4d, 0xf2, 0x9c, 0x3a, 0x8e, 0xe5, 0x83, 0xf0, 0x34, 0x3c, 0x03, 0xcf, 0xc2, 0x73, 0x30, 0x1d, 0x12, 0xe0, 0x79, 0x78, 0x81, 0xea, 0xdd, 0x99, 0xb9, 0xed, 0x02, 0x5d, 0xa1, 0x1b, 0xdc, 0x00, 0xdd, 0xa1, 0x07, 0xc4, 0x43, 0x4f, 0xb8, 0x11, 0x6e, 0x82, 0x5e, 0xd0, 0x1b, 0xfa, 0x40, 0x5f, 0xb8, 0x19, 0x6e, 0x81, 0x5b, 0xa1, 0x1f, 0xf4, 0x87, 0xdb, 0x60, 0x00, 0xdc, 0x0e, 0x03, 0xe1, 0x0e, 0xb8, 0x13, 0x06, 0x49, 0xbf, 0xf5, 0x2e, 0xb8, 0x1b, 0x06, 0x03, 0xd5, 0xd7, 0x7a, 0x0f, 0x0c, 0x85, 0x61, 0x40, 0x15, 0xb6, 0xde, 0xc7, 0xfe, 0x8c, 0x80, 0x91, 0x30, 0x0a, 0xee, 0x87, 0xd1, 0x30, 0x06, 0xc6, 0xc2, 0x03, 0xc0, 0x71, 0x59, 0x39, 0x2e, 0xeb, 0x43, 0x30, 0x1e, 0x26, 0xc0, 0x44, 0x78, 0x18, 0x26, 0xc1, 0x64, 0x78, 0x04, 0x1e, 0x85, 0xc7, 0x60, 0x0a, 0x3c, 0x0e, 0x4f, 0xc0, 0x93, 0x6c, 0xe3, 0x29, 0x78, 0x1a, 0x9e, 0x81, 0x67, 0xe1, 0x39, 0x98, 0x0e, 0x09, 0xf0, 0x3c, 0xbc, 0x00, 0x33, 0x60, 0x26, 0xcc, 0x82, 0x17, 0xe1, 0x25, 0x98, 0x0d, 0x2f, 0xc3, 0x2b, 0x30, 0x87, 0x75, 0xcd, 0x85, 0x37, 0x60, 0x1e, 0xcc, 0x87, 0x37, 0x61, 0x01, 0xbc, 0x05, 0x0b, 0xe1, 0x6d, 0x78, 0x07, 0xde, 0x05, 0xba, 0x67, 0xeb, 0xfb, 0x90, 0x08, 0x1f, 0xc0, 0x87, 0xb0, 0x08, 0x16, 0xc3, 0x47, 0xb2, 0xc0, 0xfa, 0x31, 0x7c, 0x02, 0x4b, 0x60, 0x29, 0x2c, 0x83, 0x4f, 0xe1, 0x33, 0xf8, 0x1c, 0xbe, 0x80, 0x2f, 0xe1, 0x2b, 0x58, 0x0e, 0x2b, 0x60, 0x25, 0xac, 0x82, 0xaf, 0x61, 0x35, 0xac, 0x81, 0xb5, 0xf0, 0x0d, 0xac, 0x83, 0xf5, 0xf0, 0x2d, 0x6c, 0x92, 0x19, 0x7a, 0xe6, 0xa0, 0x3e, 0x33, 0xf5, 0xab, 0xe4, 0x9a, 0x7a, 0x9a, 0xbf, 0xa8, 0xfe, 0x2f, 0xea, 0x48, 0xd4, 0x91, 0xe8, 0x3e, 0x35, 0xd7, 0xd6, 0x5c, 0x5b, 0x6b, 0x7e, 0xf4, 0x6f, 0xd6, 0xab, 0x6b, 0x6c, 0xac, 0xfa, 0x17, 0x7b, 0xb2, 0xea, 0x5f, 0x8d, 0xb2, 0xd8, 0x5e, 0xb1, 0x27, 0x6b, 0xde, 0x78, 0xf1, 0x5f, 0xfc, 0xc2, 0x9a, 0x0d, 0x8d, 0x5b, 0x6b, 0x2f, 0xfb, 0x57, 0x5e, 0xb3, 0xbc, 0xd6, 0xfc, 0x5a, 0xf3, 0xff, 0xf4, 0xf8, 0x15, 0x9e, 0x57, 0xfd, 0x1f, 0xaf, 0xd9, 0x5e, 0xbb, 0x57, 0xf5, 0x7f, 0xcd, 0xc7, 0x36, 0x3b, 0xc7, 0xbf, 0x9d, 0xcd, 0xc7, 0x5e, 0xfc, 0x67, 0xdc, 0xff, 0x2f, 0xff, 0xe9, 0xcf, 0xac, 0x3d, 0xb9, 0xf6, 0xaa, 0xba, 0x93, 0x6b, 0x1f, 0xbe, 0xf8, 0xaa, 0x3a, 0xd1, 0xcd, 0x76, 0xd6, 0x99, 0x59, 0xb5, 0x96, 0x3a, 0x29, 0x75, 0x7c, 0x75, 0xdb, 0xd6, 0x9d, 0xfc, 0x97, 0x6b, 0xd8, 0xc9, 0x36, 0x67, 0xd7, 0x5d, 0x52, 0xb7, 0xbc, 0x41, 0x74, 0xd3, 0x60, 0x5c, 0x8d, 0xa6, 0xc1, 0xaa, 0x7f, 0xcd, 0x86, 0x55, 0xff, 0xd7, 0x7c, 0x76, 0xf3, 0xcd, 0x7f, 0xdc, 0xbb, 0xaa, 0x6d, 0x55, 0xbb, 0xc7, 0x33, 0x5a, 0xde, 0xfa, 0xc7, 0x7f, 0xcd, 0x1d, 0xcd, 0x1d, 0x2d, 0xfe, 0xf4, 0xe8, 0x95, 0xff, 0xe9, 0xcf, 0xad, 0xfe, 0x4f, 0x7f, 0x5d, 0x2b, 0xd1, 0x2a, 0xbb, 0xe5, 0x06, 0xfd, 0x9f, 0x7e, 0xab, 0xf5, 0x80, 0xcb, 0xff, 0xb5, 0xfd, 0xb8, 0xea, 0x5f, 0xeb, 0x6f, 0x2f, 0xde, 0x6a, 0xfb, 0x71, 0xfb, 0x27, 0xda, 0x3f, 0xd1, 0x76, 0xe7, 0xf5, 0xbb, 0xf4, 0x65, 0xd5, 0xbf, 0x0e, 0xa7, 0xfe, 0xfd, 0x7f, 0x1d, 0xdb, 0x76, 0x6c, 0xdb, 0xa9, 0xf5, 0xef, 0xff, 0x3a, 0x47, 0x77, 0x9b, 0xd2, 0x6d, 0x6d, 0xb7, 0xb5, 0x37, 0xd4, 0xaf, 0xfa, 0xd7, 0x7d, 0x94, 0xfe, 0xef, 0xe2, 0xbd, 0x8b, 0xf7, 0x7f, 0xff, 0x17, 0xdf, 0x21, 0x7e, 0xa1, 0xfe, 0x4f, 0x98, 0x45, 0xb4, 0x65, 0xb4, 0xe5, 0x69, 0x21, 0x2c, 0xcf, 0x5a, 0xe6, 0x8b, 0xab, 0x2d, 0x0b, 0x2c, 0x8b, 0x44, 0x27, 0xcb, 0x3f, 0x94, 0xde, 0xa2, 0x97, 0xd2, 0x57, 0x19, 0x21, 0x4e, 0x2b, 0xa3, 0x94, 0x45, 0xe2, 0x6f, 0x45, 0xff, 0x5b, 0xd1, 0xff, 0x56, 0xf4, 0xff, 0x9e, 0xa2, 0xff, 0xdf, 0xe4, 0xf7, 0x8e, 0xd3, 0x9f, 0xf9, 0xe8, 0xcf, 0xbc, 0xc6, 0x2f, 0x14, 0xe9, 0xd7, 0x03, 0x39, 0xc2, 0xed, 0xa3, 0xec, 0x85, 0x83, 0xae, 0xf1, 0x18, 0xf7, 0x8f, 0xcb, 0x62, 0xd4, 0x41, 0x43, 0x1d, 0xca, 0x51, 0x87, 0x7c, 0xd4, 0xe1, 0x00, 0xea, 0x50, 0x48, 0xcf, 0xb6, 0x1b, 0x85, 0xf0, 0xa2, 0x10, 0x1e, 0x14, 0xa2, 0x14, 0x85, 0x28, 0xa3, 0x6f, 0xcb, 0x16, 0xb9, 0x74, 0x00, 0x79, 0xac, 0xcb, 0x09, 0x2e, 0x99, 0x85, 0x52, 0x14, 0xa1, 0x14, 0x7e, 0x94, 0xc2, 0x87, 0x52, 0xe4, 0xa1, 0x14, 0x05, 0x28, 0x85, 0x7e, 0xde, 0xa1, 0x90, 0x23, 0x2a, 0x41, 0x09, 0xdc, 0x28, 0x81, 0x9f, 0x7e, 0x2e, 0x1d, 0x35, 0x38, 0x10, 0xc1, 0xcc, 0x44, 0x30, 0x33, 0xa8, 0x82, 0x1f, 0x55, 0xf0, 0xa3, 0x0a, 0x7e, 0x8e, 0x20, 0x9d, 0xfe, 0xc6, 0x8f, 0x12, 0x54, 0xa2, 0x04, 0xfe, 0xa8, 0x72, 0x69, 0xa3, 0xdf, 0xf3, 0xd2, 0xef, 0x79, 0xe9, 0xf7, 0xbc, 0xf4, 0x7b, 0x95, 0xf4, 0x7b, 0x95, 0xf4, 0x7b, 0x5e, 0xfa, 0x3d, 0x2f, 0xfd, 0x5e, 0x25, 0xfd, 0x5e, 0x25, 0xfd, 0x9e, 0x97, 0x7e, 0xcf, 0x4b, 0xbf, 0xe7, 0xa5, 0xdf, 0xf3, 0xd2, 0xef, 0x79, 0xe9, 0xf7, 0xbc, 0xf4, 0x7b, 0x5e, 0xfa, 0xbd, 0x4a, 0xfa, 0xbd, 0x4a, 0xfa, 0x3d, 0x2f, 0xfd, 0x9e, 0x97, 0x7e, 0xaf, 0x92, 0x7e, 0xaf, 0x92, 0x7e, 0xcf, 0x8b, 0x7a, 0xe4, 0xd3, 0xef, 0x79, 0x51, 0x90, 0x54, 0x14, 0xc4, 0x8f, 0x82, 0xa4, 0xa2, 0x20, 0x7e, 0x14, 0xc4, 0x8f, 0x82, 0xf8, 0x51, 0x90, 0x54, 0x14, 0x24, 0x15, 0x05, 0xf1, 0xa3, 0x20, 0x5e, 0x14, 0xc4, 0x8f, 0x82, 0xf8, 0x51, 0x10, 0x3f, 0x0a, 0xe2, 0x47, 0x41, 0xfc, 0x28, 0x88, 0x1f, 0x05, 0xf1, 0xa3, 0x20, 0x7e, 0x14, 0xc4, 0xaf, 0xea, 0xd9, 0xd2, 0x59, 0x6a, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x14, 0xa3, 0x20, 0xc5, 0x28, 0x48, 0x31, 0x0a, 0x52, 0x8c, 0x82, 0x14, 0xa3, 0x20, 0xc5, 0x28, 0x48, 0x31, 0x0a, 0x52, 0x8c, 0x82, 0x14, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0x85, 0x28, 0xc8, 0x59, 0x14, 0xa4, 0x10, 0x05, 0x29, 0x44, 0x41, 0x0a, 0x51, 0x90, 0x42, 0x14, 0xa4, 0x10, 0x05, 0x29, 0x44, 0x41, 0x0a, 0x51, 0x90, 0x42, 0x14, 0xe4, 0xac, 0x75, 0xa6, 0xe8, 0x89, 0x82, 0x54, 0xa2, 0x20, 0x85, 0x28, 0x48, 0x21, 0x0a, 0x52, 0x81, 0x82, 0x14, 0xa2, 0x20, 0x85, 0xd6, 0xd7, 0xc8, 0xcc, 0xd7, 0x61, 0x8e, 0x2c, 0x42, 0x49, 0x8a, 0x50, 0x92, 0x22, 0x94, 0xa4, 0x08, 0x25, 0x29, 0x42, 0x49, 0x8a, 0x50, 0x92, 0x22, 0x94, 0xa4, 0x08, 0x25, 0x29, 0x42, 0x49, 0x8a, 0x50, 0x92, 0x22, 0x94, 0xa4, 0x08, 0x25, 0x29, 0x42, 0x49, 0x8a, 0x50, 0x92, 0x22, 0x94, 0xa4, 0x08, 0x25, 0x29, 0x42, 0x49, 0x8a, 0x50, 0x92, 0x22, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0xb3, 0xea, 0xbf, 0x40, 0x45, 0x4f, 0x2e, 0xbe, 0x16, 0xa5, 0x32, 0x20, 0xca, 0x8c, 0x6b, 0x0d, 0x78, 0xc4, 0x05, 0x79, 0x06, 0x35, 0x29, 0x36, 0xce, 0x5d, 0xd5, 0xa0, 0x67, 0x8e, 0x85, 0x5a, 0xd2, 0x8d, 0xaa, 0x68, 0xa6, 0x3a, 0x74, 0xea, 0x75, 0xa1, 0x9e, 0xfe, 0xfb, 0xdf, 0xa8, 0x4e, 0x03, 0xba, 0xe5, 0x86, 0x74, 0xcf, 0x57, 0x71, 0xff, 0x6a, 0xf8, 0xfd, 0x6a, 0x15, 0x5e, 0x53, 0x1c, 0x34, 0x01, 0xfd, 0x4a, 0x28, 0xcd, 0x58, 0x36, 0xa7, 0x8f, 0x6d, 0x21, 0x3d, 0xc6, 0x55, 0x51, 0x5a, 0x71, 0xbf, 0x35, 0xb4, 0x61, 0xbd, 0x6d, 0x59, 0x5e, 0x0b, 0xed, 0x64, 0x9e, 0xe9, 0x3a, 0x96, 0xd7, 0x43, 0x7b, 0xd6, 0xa1, 0x5f, 0x35, 0xa5, 0xa3, 0xf1, 0x4b, 0xe5, 0xfb, 0x51, 0x31, 0xe7, 0xa5, 0x2c, 0xdc, 0x6c, 0x5c, 0x9b, 0x20, 0x3f, 0x22, 0x19, 0x76, 0x51, 0x9b, 0x77, 0x73, 0x3f, 0x85, 0xdb, 0x69, 0x2c, 0xd3, 0xf9, 0x7b, 0x1e, 0x4b, 0x27, 0xb8, 0x20, 0x1f, 0xce, 0xc8, 0xac, 0x08, 0x37, 0x4b, 0x0f, 0x14, 0x80, 0x57, 0x9e, 0x8e, 0x28, 0x64, 0x59, 0x04, 0xc5, 0xa0, 0x5f, 0x81, 0xc5, 0xc7, 0x63, 0x7e, 0xb2, 0x5b, 0x83, 0x52, 0x9e, 0x5f, 0xc6, 0xfd, 0x00, 0xb7, 0x83, 0x70, 0x96, 0xe7, 0x94, 0x93, 0xf1, 0x15, 0x50, 0xc9, 0xed, 0xaa, 0x2b, 0x5a, 0x78, 0x23, 0xce, 0xc3, 0x05, 0xfe, 0x2e, 0xa5, 0xd7, 0xf8, 0xc4, 0xbb, 0x49, 0xe6, 0x1b, 0x57, 0x6d, 0x89, 0xe0, 0x7e, 0x24, 0x44, 0xc9, 0xac, 0xc8, 0x68, 0x7c, 0x82, 0x7e, 0x15, 0x97, 0xaa, 0x2b, 0x5b, 0x78, 0x23, 0xfb, 0x43, 0xd5, 0x79, 0xbd, 0xe2, 0xc8, 0xbb, 0x79, 0xfe, 0x60, 0xee, 0x0f, 0x91, 0x87, 0x22, 0x51, 0x82, 0xc8, 0xa1, 0x32, 0x37, 0x72, 0x18, 0xcb, 0x7b, 0xe5, 0xee, 0x4b, 0x57, 0x7d, 0x19, 0x51, 0x75, 0xe5, 0x97, 0x4b, 0xaa, 0x52, 0x24, 0x8f, 0xe8, 0x57, 0x81, 0xb1, 0xc4, 0xcb, 0x22, 0xcb, 0x78, 0x98, 0x00, 0x93, 0xe5, 0x09, 0x8b, 0xd7, 0xb8, 0x12, 0x86, 0x7e, 0x3d, 0x48, 0xaf, 0x92, 0x28, 0x8f, 0x5e, 0xe1, 0x6a, 0x18, 0x57, 0xbe, 0x6a, 0xcc, 0xd5, 0xfa, 0xb5, 0x4d, 0x50, 0x85, 0xd6, 0x2c, 0xf5, 0xeb, 0x9c, 0xdc, 0xc6, 0xed, 0x8b, 0x57, 0x92, 0x79, 0x89, 0xdb, 0xaf, 0x19, 0xd7, 0x7f, 0xc9, 0x08, 0x5f, 0x23, 0x32, 0x4b, 0x75, 0x4b, 0xb7, 0xaa, 0xff, 0xba, 0xb8, 0x0f, 0x18, 0x2f, 0x95, 0xf1, 0xa2, 0x42, 0xe8, 0xd7, 0x54, 0xf0, 0x5a, 0x37, 0x18, 0xd7, 0x4e, 0xf0, 0x58, 0xb7, 0xc0, 0x56, 0xd8, 0x06, 0x3b, 0xe4, 0x19, 0x6b, 0x32, 0x8f, 0xef, 0x92, 0x59, 0xd6, 0xdd, 0xb0, 0x57, 0x1e, 0xb1, 0xee, 0xe3, 0x3e, 0x2a, 0x6e, 0x3d, 0x00, 0x76, 0x38, 0x08, 0xa9, 0xac, 0x07, 0x35, 0xb7, 0xe2, 0xf5, 0xac, 0x0e, 0xe9, 0x09, 0x7f, 0x62, 0xda, 0x1f, 0xb3, 0x0f, 0x15, 0xaf, 0x15, 0xae, 0x3a, 0x5e, 0xaa, 0x8e, 0x9f, 0xaa, 0x53, 0x74, 0x31, 0x12, 0xa8, 0x20, 0x15, 0x54, 0x8f, 0x02, 0xf3, 0xf7, 0x72, 0x97, 0xf1, 0x8e, 0xf5, 0xc5, 0x51, 0xda, 0xa5, 0xff, 0x0a, 0xa7, 0x88, 0x54, 0xbb, 0x0a, 0xf5, 0x7f, 0xe9, 0x8c, 0xe8, 0xff, 0xf9, 0x67, 0x2f, 0x6b, 0x1a, 0xef, 0xed, 0xeb, 0x9f, 0x1a, 0x9f, 0xcc, 0x31, 0x72, 0x4c, 0xa6, 0xc9, 0x22, 0x96, 0xd1, 0xc9, 0x63, 0x74, 0x34, 0x46, 0x24, 0x8f, 0x11, 0xb9, 0x5a, 0x6d, 0x26, 0xda, 0xa9, 0xcd, 0x39, 0xae, 0x16, 0x1c, 0x5f, 0x4b, 0x51, 0x93, 0x11, 0x6a, 0x62, 0x9c, 0x63, 0x73, 0x51, 0xb9, 0xf3, 0xc2, 0xd7, 0x12, 0xd3, 0x7f, 0x35, 0xdb, 0x63, 0xf2, 0x83, 0x26, 0x4b, 0x63, 0x06, 0x70, 0xf4, 0xff, 0xf9, 0xcf, 0x11, 0xd4, 0x63, 0x0b, 0x47, 0x39, 0x92, 0x7c, 0xb6, 0xa2, 0xbf, 0x83, 0xed, 0xc6, 0x31, 0xe8, 0x73, 0x9d, 0xae, 0x1f, 0x0d, 0x6b, 0x75, 0x71, 0x44, 0xe9, 0xca, 0x4a, 0x51, 0x5b, 0xc9, 0x96, 0xd9, 0x6a, 0x1c, 0x47, 0xd6, 0x44, 0x06, 0x38, 0xc2, 0x66, 0x1c, 0x61, 0x5d, 0x8e, 0xb0, 0x2e, 0x47, 0xa7, 0x7f, 0x8f, 0xa0, 0x2d, 0x47, 0xd8, 0x5a, 0xb4, 0xc2, 0x31, 0xe8, 0xbf, 0x55, 0x73, 0x8a, 0xd9, 0xf3, 0x30, 0x7b, 0x7b, 0x98, 0xbd, 0x6d, 0xcc, 0x9e, 0x83, 0xd9, 0xcb, 0x63, 0xf6, 0xf6, 0x30, 0x7b, 0x9b, 0x99, 0xbd, 0x63, 0xcc, 0xde, 0x59, 0x66, 0xef, 0x34, 0xb3, 0x17, 0x60, 0xf6, 0x52, 0x98, 0xbd, 0x83, 0xcc, 0x5e, 0x01, 0xb3, 0xb7, 0x9f, 0xea, 0xbf, 0x87, 0xca, 0x5f, 0x46, 0xe5, 0xdf, 0x1c, 0x95, 0x2d, 0x9a, 0x33, 0x9b, 0xa5, 0xcc, 0x66, 0x29, 0xb3, 0x59, 0x6a, 0x7d, 0x4a, 0xb4, 0xa2, 0x72, 0xf8, 0xa8, 0x1c, 0x21, 0x2a, 0x86, 0xcf, 0xfa, 0xbc, 0x30, 0x5b, 0x67, 0xf0, 0xd8, 0x4c, 0x31, 0xce, 0x3a, 0x4b, 0x34, 0xb7, 0xce, 0x16, 0x35, 0xa9, 0x12, 0x95, 0xd6, 0x57, 0x18, 0xed, 0x9a, 0x44, 0x64, 0x06, 0x5e, 0x2d, 0x48, 0x44, 0xee, 0x26, 0x22, 0x4f, 0x10, 0x91, 0x0e, 0x22, 0x31, 0x8d, 0x28, 0x4c, 0x25, 0x0a, 0xf5, 0xf7, 0x14, 0x8e, 0x91, 0x95, 0x41, 0x22, 0xb0, 0x6e, 0xf8, 0xba, 0x1f, 0xe9, 0x44, 0xa1, 0x4d, 0x34, 0xc5, 0x83, 0x1d, 0x61, 0x64, 0x3e, 0x12, 0x13, 0xa5, 0x64, 0x74, 0xbe, 0x65, 0x64, 0xf6, 0x32, 0x07, 0xf7, 0x98, 0x6e, 0x94, 0x25, 0xa6, 0x9b, 0xd0, 0xec, 0x81, 0x72, 0x39, 0xb3, 0x77, 0x8a, 0xd9, 0x3b, 0xc5, 0xec, 0xe5, 0x33, 0x6a, 0x67, 0x19, 0xb5, 0x1c, 0x46, 0xed, 0x0b, 0x46, 0x6d, 0x19, 0xa3, 0x96, 0xa3, 0xac, 0x24, 0x12, 0xab, 0x46, 0xad, 0x05, 0xa3, 0x96, 0xc9, 0xa8, 0xb5, 0x63, 0xd4, 0x1a, 0x33, 0x6a, 0x8d, 0x89, 0x8b, 0x68, 0xe3, 0x5b, 0x18, 0xfa, 0x37, 0x30, 0xba, 0x8a, 0x0e, 0xea, 0xc3, 0x32, 0x4d, 0x9d, 0x23, 0x7f, 0x12, 0x56, 0xb6, 0x5a, 0xc6, 0x5c, 0x54, 0x9a, 0xf4, 0xdf, 0x1b, 0x7b, 0x53, 0x7a, 0xf4, 0x33, 0x90, 0xfa, 0x99, 0x54, 0x66, 0xb6, 0x94, 0xb5, 0x7a, 0x58, 0x83, 0x89, 0x35, 0x28, 0xa2, 0xf6, 0xc5, 0x33, 0xb6, 0xa6, 0x7b, 0x45, 0x6d, 0xd3, 0x70, 0x31, 0xd0, 0x34, 0x52, 0xf4, 0xaf, 0x7e, 0xf6, 0x36, 0xf2, 0x03, 0x71, 0x53, 0xe4, 0x22, 0x71, 0x73, 0xe4, 0x3f, 0x44, 0xef, 0xa8, 0xee, 0x22, 0x36, 0xaa, 0x07, 0x54, 0x3f, 0xa3, 0x6b, 0xbd, 0xd2, 0x39, 0x5f, 0x5e, 0xd1, 0x8a, 0x57, 0x34, 0xff, 0xc3, 0x33, 0x63, 0x2f, 0x6d, 0x6b, 0xb8, 0x88, 0x63, 0x3b, 0x57, 0x5f, 0xb6, 0x9d, 0x5a, 0xbc, 0xaa, 0x3e, 0xaf, 0xaa, 0xf3, 0x87, 0x57, 0xc5, 0x11, 0x05, 0x7e, 0xa2, 0xe0, 0x0c, 0x51, 0xe0, 0x22, 0x0a, 0x4e, 0x12, 0x05, 0xa7, 0x88, 0x02, 0x27, 0x51, 0x90, 0x8d, 0x57, 0x4c, 0x0f, 0xfb, 0xc4, 0x33, 0x44, 0x42, 0x36, 0x91, 0x70, 0x86, 0x48, 0xf0, 0x10, 0x09, 0xd9, 0x44, 0x81, 0x8b, 0x2d, 0xb5, 0x63, 0x4b, 0x6d, 0x88, 0x86, 0x33, 0x44, 0x43, 0x21, 0xd1, 0x90, 0xcd, 0x96, 0x9a, 0xb3, 0xa5, 0x5e, 0x6c, 0xa9, 0x27, 0x91, 0x61, 0x26, 0x1a, 0xa2, 0x98, 0xfd, 0x28, 0x66, 0xff, 0x66, 0x66, 0xdf, 0x6c, 0x5c, 0x2b, 0xfd, 0x8a, 0x57, 0x82, 0xbc, 0xb8, 0xb6, 0x2b, 0x5d, 0x11, 0xb2, 0xfa, 0x5a, 0xff, 0x70, 0x75, 0x48, 0x6b, 0xb5, 0xa3, 0x8e, 0xe5, 0xd5, 0xea, 0x5f, 0x9e, 0x1b, 0xaf, 0x3e, 0x3e, 0xdd, 0x78, 0x66, 0xe7, 0xcb, 0xc6, 0xa7, 0x3d, 0xeb, 0x6f, 0xcf, 0xfa, 0xdb, 0x5d, 0x76, 0x46, 0xfd, 0xe2, 0xab, 0x06, 0xd2, 0x43, 0xdc, 0xcb, 0x36, 0x86, 0x8b, 0x5a, 0xbc, 0x3a, 0xe6, 0xb2, 0x57, 0xd7, 0xe6, 0xd5, 0x35, 0x79, 0x75, 0xec, 0x5f, 0x6e, 0xd3, 0x38, 0xb6, 0xcb, 0x5e, 0xf5, 0xfb, 0x48, 0x55, 0x7f, 0xd5, 0x55, 0xbc, 0x2a, 0x83, 0x88, 0x76, 0x1b, 0xfa, 0xde, 0x13, 0x7d, 0x1f, 0xc8, 0xf2, 0x0e, 0x11, 0xc3, 0xf6, 0x1b, 0xb2, 0xa6, 0xc1, 0xac, 0xe9, 0x4e, 0xd6, 0xa4, 0xab, 0x4a, 0x0e, 0x6b, 0x3a, 0xc9, 0x9a, 0x6e, 0x63, 0x4d, 0x83, 0x59, 0xd3, 0x9d, 0x44, 0x51, 0x5d, 0xa2, 0xa8, 0x6e, 0x94, 0x7e, 0x85, 0xb8, 0x95, 0x28, 0x5d, 0x13, 0x2a, 0x53, 0x53, 0x29, 0xd5, 0xb6, 0xf2, 0x02, 0x51, 0x1c, 0x69, 0x5c, 0x85, 0x2b, 0x57, 0x94, 0xa3, 0xd4, 0x15, 0x64, 0x5d, 0x8c, 0x71, 0x4d, 0xf3, 0x53, 0xc6, 0x35, 0x1c, 0xab, 0x5f, 0xd3, 0xbc, 0xbe, 0xdc, 0x88, 0x9f, 0xc9, 0xc3, 0xcf, 0x64, 0x5c, 0xba, 0xae, 0x79, 0x23, 0xb9, 0x09, 0x3f, 0xb3, 0xe9, 0xb2, 0xeb, 0x9b, 0x67, 0x85, 0xaf, 0x6f, 0xae, 0x5f, 0x05, 0x27, 0xab, 0xda, 0xf5, 0xcd, 0x3d, 0xd5, 0xae, 0x6f, 0xee, 0xaf, 0x76, 0x7d, 0xf3, 0x13, 0x78, 0x99, 0xac, 0xf0, 0xf5, 0xcd, 0x53, 0x8c, 0xa3, 0xa3, 0x92, 0x99, 0x7a, 0xc3, 0xad, 0xec, 0x4f, 0x3f, 0xe0, 0x68, 0x23, 0xec, 0x32, 0x18, 0x91, 0x05, 0x7f, 0xbe, 0xee, 0x79, 0xde, 0x65, 0xd7, 0x3d, 0xd7, 0x2e, 0xbb, 0xee, 0x79, 0x96, 0x71, 0x55, 0x1c, 0x3f, 0x3e, 0x46, 0x83, 0x52, 0xd6, 0x55, 0x75, 0x75, 0x1c, 0x0f, 0xfe, 0xc5, 0x73, 0xe9, 0xfa, 0xe7, 0x21, 0xb9, 0xa9, 0xda, 0x35, 0xd0, 0x3d, 0xe1, 0x6b, 0xa0, 0xbb, 0xf0, 0x2c, 0x59, 0xd5, 0xae, 0x81, 0x5e, 0x84, 0x67, 0x29, 0xc2, 0xb3, 0x64, 0xe1, 0x59, 0x36, 0x19, 0xd7, 0x41, 0xff, 0xe3, 0xb5, 0xcf, 0xb3, 0xf0, 0x25, 0x59, 0x78, 0x92, 0x2c, 0x23, 0xf3, 0x8b, 0x64, 0x06, 0x7e, 0x24, 0xeb, 0x0f, 0x7e, 0xc4, 0x8b, 0xc6, 0x7c, 0x29, 0x33, 0xf0, 0x1f, 0x19, 0xf8, 0x8f, 0x0c, 0xfc, 0x47, 0x06, 0xfe, 0x23, 0x0b, 0xff, 0x91, 0x85, 0xff, 0xc8, 0xc2, 0x7f, 0x64, 0xe1, 0x3f, 0xb2, 0xf0, 0x1f, 0x59, 0xc6, 0xf5, 0xcf, 0x3b, 0xc9, 0x92, 0xf0, 0x35, 0xd0, 0xb3, 0xf0, 0x1d, 0x59, 0x97, 0xae, 0x81, 0xfe, 0xbe, 0x3c, 0xae, 0xea, 0xe7, 0x3d, 0x4e, 0xc9, 0x22, 0x7c, 0x87, 0x5f, 0xd5, 0x3d, 0x8b, 0x0f, 0x38, 0x4e, 0x7c, 0x87, 0x3f, 0x7c, 0x1d, 0xf4, 0x3c, 0x7c, 0x45, 0x1e, 0xbe, 0x22, 0xe3, 0x2f, 0xae, 0x83, 0xee, 0x09, 0x5f, 0x07, 0xfd, 0x04, 0x9e, 0xc2, 0x85, 0xa7, 0xc8, 0x32, 0xed, 0x10, 0x11, 0x44, 0x68, 0x24, 0x58, 0x50, 0x7b, 0x05, 0x6a, 0x42, 0x3d, 0xaa, 0xc1, 0x35, 0x2c, 0xe3, 0xa0, 0x09, 0x34, 0x87, 0x96, 0xd4, 0xfa, 0x56, 0x70, 0x1d, 0xda, 0x77, 0x3d, 0x15, 0xa1, 0x03, 0x8f, 0x75, 0x94, 0xa9, 0xa2, 0x13, 0x74, 0x86, 0x2e, 0xd4, 0xa7, 0xae, 0x54, 0x90, 0x6e, 0x70, 0x03, 0x74, 0x87, 0x1e, 0x10, 0x0f, 0x7a, 0x3c, 0xf7, 0x66, 0xd9, 0x07, 0xc6, 0xf3, 0xbc, 0x09, 0x30, 0x11, 0x1e, 0x46, 0x4f, 0x26, 0x41, 0x38, 0xd6, 0xc5, 0xa3, 0xdc, 0x7e, 0x0c, 0xa6, 0xf0, 0xb7, 0xc7, 0xd1, 0xa1, 0x27, 0x58, 0x3e, 0x09, 0x4f, 0x71, 0x7b, 0x2a, 0x4c, 0x83, 0xa7, 0x59, 0xc7, 0x33, 0xf0, 0x2c, 0x3c, 0xc7, 0xfd, 0xe9, 0xfc, 0xfd, 0x79, 0x96, 0x2f, 0xb2, 0x9c, 0x4d, 0x7d, 0x7c, 0x19, 0x5e, 0x81, 0x57, 0xe1, 0x35, 0x78, 0x1d, 0xe6, 0xc0, 0x5c, 0x78, 0x03, 0xe6, 0xc1, 0x7c, 0x74, 0xee, 0x4d, 0x58, 0x80, 0x96, 0xbd, 0x45, 0x7f, 0xbb, 0x10, 0xbd, 0x7b, 0x9b, 0x88, 0xad, 0xca, 0x35, 0x97, 0x29, 0x1f, 0x47, 0xe4, 0x26, 0xfa, 0x3d, 0xf2, 0xa4, 0x79, 0x00, 0x55, 0xe3, 0x6e, 0xb8, 0x17, 0x1e, 0x80, 0xf1, 0x30, 0x19, 0xe8, 0x91, 0xcc, 0xcf, 0x50, 0x55, 0x9e, 0x85, 0xe9, 0x38, 0x8b, 0x99, 0xf2, 0x94, 0x79, 0x16, 0xb7, 0x67, 0x1b, 0xbf, 0x68, 0x14, 0x32, 0xb3, 0x7d, 0x23, 0x47, 0x17, 0x53, 0x1f, 0x3e, 0xe1, 0xf1, 0x25, 0xb0, 0x96, 0xc7, 0xbe, 0x81, 0x8b, 0x79, 0x9b, 0x80, 0xeb, 0xd5, 0x23, 0x07, 0x25, 0x8c, 0x4a, 0x96, 0x67, 0xf4, 0x9c, 0x8d, 0xca, 0x92, 0x67, 0xa3, 0x4e, 0xcb, 0xbc, 0xa8, 0x42, 0x79, 0x32, 0xba, 0x95, 0x3c, 0x15, 0xdd, 0x5a, 0xe6, 0x47, 0xb7, 0x95, 0xa1, 0xe8, 0x0a, 0x99, 0x67, 0x69, 0x2f, 0xb3, 0x2d, 0x1d, 0xa0, 0x23, 0x74, 0x82, 0xce, 0xd0, 0x05, 0xba, 0x42, 0x37, 0xb8, 0x01, 0x7a, 0xc2, 0x8d, 0x70, 0x13, 0xa0, 0x3e, 0x96, 0xde, 0xd0, 0x07, 0xfa, 0xc2, 0xcd, 0x70, 0x0b, 0xdc, 0x0a, 0xfd, 0xa0, 0x3f, 0xdc, 0x06, 0x03, 0xe0, 0x76, 0x18, 0x08, 0x77, 0xc0, 0x9d, 0x30, 0x11, 0x9e, 0x94, 0x67, 0x2d, 0x1c, 0xa7, 0x65, 0x2a, 0x4c, 0x83, 0xe7, 0x60, 0x05, 0xac, 0x84, 0x55, 0xf0, 0x35, 0xac, 0x86, 0x35, 0xb0, 0x16, 0x0a, 0xa4, 0xc3, 0x52, 0x28, 0x1d, 0x4a, 0xac, 0x3c, 0xab, 0x10, 0x47, 0x4a, 0x2d, 0xa8, 0x0d, 0x75, 0xa0, 0x2e, 0xd4, 0x83, 0xfa, 0x70, 0x35, 0x34, 0x82, 0xc6, 0x40, 0x9c, 0x29, 0xc4, 0x99, 0x42, 0x9c, 0x29, 0x4d, 0xa1, 0x19, 0x10, 0x6f, 0x4a, 0x0b, 0x68, 0x09, 0xad, 0xa0, 0x35, 0xb4, 0x81, 0xb6, 0x70, 0x2d, 0xb4, 0x83, 0xeb, 0xe0, 0x7a, 0xe8, 0x0e, 0x03, 0x65, 0xb9, 0x72, 0x07, 0xdc, 0x09, 0x83, 0xe0, 0x6e, 0x18, 0x0c, 0x43, 0xe0, 0x1e, 0x18, 0x0a, 0xcf, 0xe1, 0x35, 0xa7, 0x43, 0x02, 0x3c, 0x0f, 0x2f, 0xc0, 0x0c, 0xc0, 0x75, 0x29, 0xb8, 0x2e, 0xe5, 0x03, 0xd6, 0xf3, 0x21, 0x2c, 0x82, 0x7f, 0xc0, 0x3f, 0xe1, 0x23, 0xf8, 0x58, 0xa6, 0x2a, 0x9f, 0xc0, 0x12, 0x58, 0x0a, 0xcb, 0xe0, 0x53, 0xf8, 0x0c, 0x3e, 0x87, 0x2f, 0xe0, 0x2b, 0x58, 0x0e, 0x2b, 0x00, 0xcd, 0x55, 0x56, 0xb1, 0xfc, 0x1a, 0x56, 0xc3, 0x1a, 0x58, 0x0b, 0xdf, 0xc0, 0x3a, 0x58, 0x0f, 0xdf, 0xc2, 0x77, 0xf0, 0x3d, 0xfc, 0x00, 0x1b, 0x60, 0x23, 0xfc, 0x02, 0xbf, 0xc2, 0x26, 0xd8, 0x0c, 0x5b, 0xe9, 0x62, 0xb6, 0x01, 0x1d, 0x9e, 0xb2, 0x9d, 0xf5, 0xee, 0x80, 0x9d, 0x90, 0x0c, 0x7b, 0xe0, 0x10, 0x1c, 0x86, 0x54, 0x38, 0x02, 0x47, 0xc1, 0x01, 0xc7, 0xe0, 0x38, 0x64, 0xc2, 0x09, 0x38, 0x09, 0x59, 0x90, 0x0d, 0x39, 0x70, 0x0a, 0x88, 0x31, 0x25, 0x17, 0x9c, 0xe0, 0x82, 0x7c, 0xd0, 0x3f, 0xed, 0x41, 0xdc, 0x2b, 0x05, 0xe0, 0xa5, 0x76, 0xd4, 0x96, 0xf9, 0x6a, 0x1d, 0xa8, 0x0b, 0xf5, 0x80, 0x38, 0x55, 0x1b, 0xc0, 0x55, 0xf2, 0x8c, 0xda, 0x08, 0x1a, 0x43, 0x1c, 0x5c, 0xac, 0x31, 0xcd, 0xb8, 0xdd, 0x1c, 0x5a, 0x40, 0x4b, 0x68, 0x03, 0x7a, 0xdd, 0x69, 0xc7, 0xb2, 0x03, 0x74, 0x84, 0xce, 0x40, 0x1d, 0x52, 0x6f, 0x60, 0x3d, 0xdd, 0x41, 0xbf, 0x6e, 0x71, 0x3c, 0xf4, 0x84, 0x5e, 0xd0, 0x1b, 0xfa, 0x40, 0x5f, 0xb8, 0x19, 0x6e, 0x81, 0x5b, 0xa1, 0x1f, 0xf4, 0x87, 0x01, 0xf2, 0x94, 0x7a, 0x3b, 0x0c, 0x84, 0x41, 0x70, 0x17, 0xdc, 0x0d, 0x83, 0x61, 0x08, 0xdc, 0x03, 0x43, 0x61, 0x18, 0xdc, 0x0b, 0xc3, 0x61, 0x04, 0xc7, 0x32, 0x12, 0x46, 0xc1, 0xfd, 0x30, 0x1a, 0xc6, 0xc0, 0x58, 0x78, 0x00, 0xc6, 0xc3, 0x04, 0x98, 0x08, 0x0f, 0xc3, 0x24, 0x98, 0x0c, 0x8f, 0xc0, 0xa3, 0xf0, 0x18, 0x4c, 0x81, 0xc7, 0xe1, 0x09, 0x78, 0x12, 0x9e, 0x82, 0xa9, 0x30, 0x0d, 0x66, 0xc2, 0x2c, 0x78, 0x11, 0xc8, 0x7f, 0xf5, 0x65, 0x78, 0x1d, 0xe6, 0xc0, 0x5c, 0x78, 0x03, 0xe6, 0xc1, 0x7c, 0x78, 0x13, 0xde, 0x82, 0x85, 0xf0, 0x91, 0x0c, 0xa9, 0x1f, 0xc3, 0x27, 0xb0, 0x04, 0x96, 0xc2, 0x32, 0xf8, 0x14, 0x3e, 0x83, 0xcf, 0xe1, 0x0b, 0xf8, 0x12, 0xbe, 0x82, 0xe5, 0xb0, 0x02, 0x56, 0xc2, 0x2a, 0xf8, 0x1a, 0x56, 0xc3, 0x1a, 0x40, 0x5b, 0x54, 0xb4, 0x45, 0x5d, 0x07, 0xeb, 0xe1, 0x5b, 0xf8, 0x0e, 0xbe, 0x87, 0x1f, 0x60, 0x03, 0xfc, 0x08, 0x3f, 0xc1, 0xcf, 0xb0, 0x11, 0x7e, 0x81, 0x5f, 0x61, 0x13, 0x6c, 0x86, 0x2d, 0xb0, 0x15, 0xb6, 0xc1, 0x6f, 0xf2, 0xa4, 0x9a, 0x04, 0xdb, 0x61, 0x07, 0xec, 0x84, 0x64, 0xd8, 0x05, 0xbb, 0x61, 0x0f, 0xec, 0x85, 0x7d, 0x60, 0x83, 0xfd, 0x70, 0x00, 0xec, 0x70, 0x10, 0x0e, 0xc1, 0x61, 0x48, 0x85, 0x23, 0xe0, 0x00, 0xfc, 0xa5, 0x7a, 0x1c, 0x7f, 0x9c, 0x06, 0x19, 0x38, 0x9e, 0x4c, 0x38, 0x01, 0x27, 0x21, 0x0b, 0xb2, 0x21, 0x07, 0x4e, 0xe3, 0xf0, 0x72, 0xc1, 0xc9, 0xf3, 0xf0, 0x9b, 0x6a, 0x3e, 0x9c, 0x81, 0x02, 0xf0, 0x42, 0x21, 0x14, 0x41, 0x31, 0x94, 0x50, 0xd5, 0xd2, 0xe5, 0x29, 0x6b, 0x81, 0x3c, 0x6b, 0xf5, 0x42, 0x21, 0x14, 0x41, 0x31, 0x94, 0x51, 0x0d, 0x03, 0x10, 0x84, 0xb3, 0xa0, 0xff, 0x56, 0x4b, 0x85, 0x3c, 0x69, 0xad, 0x84, 0x10, 0x9c, 0x83, 0xf3, 0xd4, 0x81, 0xcd, 0x62, 0x9f, 0x5c, 0x4f, 0x4f, 0xf4, 0x85, 0x48, 0x95, 0xbf, 0xe1, 0x86, 0x7f, 0xc4, 0x0d, 0xcf, 0xc4, 0x0d, 0x6f, 0x11, 0xc7, 0xe9, 0x62, 0xd3, 0x64, 0x9a, 0x48, 0xa7, 0xcf, 0xcb, 0x90, 0x1b, 0x70, 0xc5, 0xef, 0x89, 0x13, 0x54, 0xcb, 0x6c, 0xb9, 0x04, 0x57, 0x5c, 0x84, 0x2b, 0xde, 0x2e, 0xf2, 0xe8, 0x09, 0x9c, 0xe0, 0x92, 0x0b, 0x44, 0xbe, 0xdc, 0x2f, 0xce, 0x70, 0xdb, 0xcd, 0xfa, 0x3c, 0x54, 0xbd, 0x02, 0x3a, 0x61, 0xaf, 0x4c, 0x32, 0xf5, 0x97, 0x7b, 0x4c, 0x03, 0x20, 0xfc, 0xab, 0x02, 0x11, 0xcb, 0xe5, 0x7a, 0x5c, 0xf3, 0x8f, 0x11, 0x2b, 0xe5, 0x4f, 0xb8, 0xe6, 0xf7, 0x22, 0xd6, 0xb1, 0xfc, 0x16, 0x36, 0xc8, 0x9f, 0x22, 0xe7, 0xca, 0x3d, 0x91, 0xf3, 0x60, 0xbe, 0xfc, 0x39, 0x72, 0x81, 0xfc, 0x19, 0x17, 0xdd, 0x22, 0xea, 0xac, 0xfc, 0x29, 0xaa, 0x42, 0xae, 0xb7, 0x3c, 0x2b, 0xd7, 0x2b, 0x0d, 0xa0, 0x21, 0xb4, 0x87, 0x0e, 0xa8, 0x6d, 0x27, 0x96, 0x9d, 0x59, 0x76, 0x81, 0xae, 0xdc, 0xee, 0x06, 0x29, 0xf2, 0x27, 0x75, 0x1c, 0x3c, 0x08, 0x4f, 0xc3, 0x33, 0xf0, 0x2c, 0x3c, 0x07, 0xd3, 0x21, 0x01, 0x9e, 0x87, 0x17, 0xe4, 0x4f, 0xd6, 0xce, 0xf2, 0x37, 0x6b, 0x17, 0xe8, 0x0a, 0xdd, 0xe0, 0x06, 0xe8, 0x0e, 0x3d, 0x20, 0x1e, 0x7a, 0xc2, 0x8d, 0x70, 0x13, 0xf4, 0x82, 0xde, 0xd0, 0x07, 0xfa, 0xc2, 0xcd, 0x70, 0x0b, 0xdc, 0x0a, 0xfd, 0xa0, 0x3f, 0xdc, 0x06, 0x03, 0xe0, 0x76, 0x18, 0x08, 0x77, 0xc0, 0x9d, 0x30, 0x48, 0x9e, 0xb0, 0xde, 0x05, 0x77, 0xc3, 0x60, 0x18, 0x02, 0xf7, 0xc0, 0x50, 0x18, 0x06, 0xf7, 0x02, 0x9e, 0xca, 0x3a, 0x02, 0xf0, 0x54, 0xd6, 0x51, 0x70, 0x3f, 0x8c, 0x86, 0x31, 0x30, 0x16, 0x1e, 0x80, 0x71, 0xf0, 0x20, 0xe0, 0x91, 0xac, 0xe3, 0x61, 0x02, 0x4c, 0x84, 0x87, 0x61, 0x12, 0x4c, 0x86, 0x47, 0xe0, 0x51, 0x78, 0x0c, 0xa6, 0xc0, 0xe3, 0xf0, 0x04, 0x3c, 0x29, 0xb3, 0xe9, 0x48, 0xda, 0x5a, 0x9f, 0x66, 0xf9, 0x8c, 0xa8, 0x67, 0x7d, 0x56, 0x34, 0xb5, 0x3e, 0xc7, 0xed, 0xe9, 0x90, 0x20, 0x6a, 0xd0, 0xab, 0x36, 0xb5, 0xbe, 0xc0, 0xed, 0x19, 0x3c, 0x67, 0xa6, 0x78, 0x90, 0x8e, 0xa5, 0x85, 0xf5, 0x45, 0xee, 0x57, 0xbd, 0x3f, 0x52, 0x69, 0x7d, 0x99, 0xde, 0xf5, 0x15, 0xd1, 0xd7, 0x3a, 0x47, 0xee, 0xb7, 0xce, 0x85, 0x37, 0x60, 0x1e, 0xcc, 0x87, 0x37, 0x61, 0x01, 0xbc, 0x05, 0x0b, 0xe1, 0x6d, 0x78, 0x07, 0xde, 0x85, 0xf7, 0xe0, 0x7d, 0x48, 0x84, 0x0f, 0xe0, 0x43, 0x58, 0x04, 0x8b, 0xe1, 0x23, 0x99, 0x64, 0xfd, 0x18, 0x3e, 0x81, 0x25, 0xb0, 0x14, 0x96, 0xc1, 0xa7, 0xf0, 0x19, 0x7c, 0x0e, 0x5f, 0xc0, 0x97, 0xf0, 0x15, 0x2c, 0x87, 0x15, 0xb0, 0x12, 0x56, 0xc1, 0xd7, 0xb0, 0x1a, 0xd6, 0xc0, 0x5a, 0xf8, 0x06, 0xd6, 0xc1, 0x7a, 0xf8, 0x16, 0x36, 0xc9, 0xf5, 0xc6, 0xb7, 0xf1, 0xb3, 0xe9, 0x97, 0x1b, 0xe2, 0xc5, 0x7e, 0xc5, 0x6b, 0x95, 0xe3, 0xa1, 0xec, 0xa6, 0x89, 0xf2, 0xb8, 0x69, 0x92, 0x4c, 0x33, 0x4d, 0x16, 0x35, 0xe9, 0x8e, 0xf3, 0x8c, 0x4f, 0x9f, 0xcf, 0x17, 0x5d, 0xf0, 0x2d, 0x1f, 0xe0, 0x4f, 0x4e, 0xe0, 0x47, 0xb6, 0x2a, 0x2b, 0xc5, 0x8d, 0xd4, 0x8d, 0x10, 0x1d, 0x72, 0x3f, 0x7a, 0xda, 0x9a, 0x74, 0xc5, 0xb1, 0x74, 0xc5, 0x5d, 0xe9, 0x8a, 0xef, 0x41, 0x2b, 0x35, 0x34, 0xcf, 0x6e, 0xac, 0xf9, 0x3f, 0xff, 0x8d, 0x8e, 0x9a, 0x7f, 0xd5, 0x49, 0x5e, 0xa9, 0x83, 0xfc, 0x43, 0xd7, 0x78, 0xd5, 0xff, 0xa4, 0xeb, 0xad, 0xde, 0xed, 0x5e, 0xb1, 0xc3, 0xfd, 0x77, 0x3e, 0xa7, 0x75, 0x85, 0x0e, 0xef, 0x0f, 0x1d, 0x5d, 0xd5, 0x99, 0x84, 0x2b, 0x77, 0x70, 0x97, 0xff, 0x6a, 0xc2, 0xa5, 0xdf, 0x18, 0xf8, 0xbf, 0xe4, 0x53, 0x7b, 0xff, 0x67, 0x5c, 0x77, 0xf6, 0x3f, 0xff, 0xcd, 0xac, 0x7a, 0x64, 0xd8, 0x3e, 0xe3, 0xfc, 0x6f, 0x2f, 0xb9, 0x46, 0x4c, 0x14, 0x51, 0x44, 0x50, 0x90, 0xad, 0xdd, 0xc6, 0xd6, 0x56, 0xe9, 0xe7, 0xa2, 0x4c, 0xe3, 0x8d, 0x5f, 0x8b, 0xd6, 0xcf, 0x47, 0x55, 0xb0, 0x95, 0x53, 0x17, 0x3f, 0x1d, 0x1b, 0x3e, 0xab, 0x55, 0x87, 0xe8, 0xe9, 0x78, 0xe9, 0x1d, 0xc4, 0x9e, 0xc4, 0xca, 0x4a, 0x62, 0x65, 0x19, 0x31, 0xe2, 0x23, 0x46, 0x02, 0xc4, 0xc8, 0x36, 0x62, 0x24, 0x35, 0x1c, 0x23, 0x3f, 0x85, 0xbf, 0xeb, 0x55, 0x40, 0x6c, 0xa4, 0x10, 0x1b, 0x95, 0xd5, 0xb7, 0x44, 0x4c, 0x04, 0x88, 0x89, 0x8a, 0x8b, 0x5b, 0x20, 0x0f, 0x1a, 0x12, 0x13, 0xcb, 0x88, 0x89, 0x65, 0xc4, 0xc4, 0x32, 0x62, 0x22, 0x83, 0x98, 0xc8, 0x20, 0x26, 0x96, 0x11, 0x13, 0xcb, 0x88, 0x89, 0x0c, 0x62, 0x22, 0x83, 0x98, 0x58, 0x46, 0x4c, 0x2c, 0x23, 0x26, 0x96, 0x11, 0x13, 0xcb, 0x88, 0x89, 0x65, 0xc4, 0xc4, 0x32, 0x62, 0x62, 0x19, 0x31, 0x91, 0x41, 0x4c, 0x64, 0x10, 0x13, 0xcb, 0x88, 0x89, 0x65, 0xc4, 0x44, 0x06, 0x31, 0x91, 0x41, 0x4c, 0x2c, 0x23, 0x1e, 0xdc, 0xc4, 0x43, 0x88, 0x78, 0x08, 0x11, 0x0f, 0x21, 0xe2, 0x21, 0x44, 0xde, 0xd5, 0x41, 0xbf, 0xa3, 0xc9, 0xbd, 0x3a, 0xe4, 0xde, 0x7d, 0xe4, 0x5e, 0x43, 0xeb, 0x6c, 0xd1, 0xd8, 0xfa, 0x32, 0x31, 0xd2, 0x88, 0x18, 0x99, 0x1e, 0xfe, 0xc4, 0x71, 0x90, 0xf8, 0x38, 0x4c, 0x7c, 0xdc, 0x47, 0x7c, 0xec, 0x35, 0x75, 0x16, 0x8d, 0xfe, 0x70, 0x24, 0x64, 0x07, 0xf1, 0x10, 0x24, 0x16, 0x16, 0x13, 0x0b, 0x0e, 0xe2, 0x60, 0x20, 0x71, 0x50, 0x49, 0x0c, 0x1c, 0xaf, 0xf6, 0xe9, 0xe2, 0x87, 0x98, 0xff, 0x07, 0xe9, 0xd0, 0xcf, 0x12, 0x03, 0x6e, 0x62, 0xa0, 0x82, 0x18, 0xa8, 0x14, 0x8d, 0x89, 0x81, 0x72, 0x62, 0xa0, 0x9c, 0x59, 0x29, 0x27, 0x06, 0xd2, 0x99, 0x99, 0xcd, 0xc4, 0xc0, 0x09, 0x5d, 0x5d, 0x8d, 0x2b, 0x16, 0xe8, 0x57, 0xe9, 0x78, 0x0c, 0x2d, 0x9b, 0x2f, 0xda, 0x85, 0xcf, 0xd8, 0x16, 0x32, 0x33, 0xa7, 0x99, 0xf3, 0x72, 0xfd, 0xac, 0x2d, 0xf3, 0x5e, 0xce, 0xbc, 0x97, 0x33, 0xef, 0xe5, 0xcc, 0x7b, 0x39, 0xf3, 0x5e, 0xce, 0xbc, 0x97, 0x33, 0xef, 0xe5, 0xe4, 0xfc, 0x79, 0x66, 0xcc, 0xa4, 0x2b, 0xac, 0x78, 0x91, 0xe3, 0xa9, 0xe4, 0x78, 0x4a, 0x8c, 0xab, 0xcd, 0xd6, 0x96, 0xe7, 0x2e, 0x7b, 0xf7, 0x29, 0xdb, 0xd4, 0x40, 0x1e, 0x33, 0x35, 0x44, 0xcb, 0x7f, 0x7f, 0xf7, 0xc9, 0x69, 0x6a, 0x0c, 0x7f, 0x7c, 0xf7, 0x29, 0x14, 0x7e, 0xf7, 0x69, 0xab, 0xa9, 0x25, 0xb7, 0xaf, 0xfc, 0xce, 0xd3, 0xce, 0x6a, 0xef, 0x3c, 0xa5, 0x99, 0x3a, 0xf0, 0xbc, 0xaa, 0x77, 0x9e, 0xf4, 0x68, 0xd6, 0x22, 0x76, 0xcb, 0xca, 0x88, 0x34, 0x48, 0x87, 0x3f, 0xbf, 0xbb, 0x74, 0xec, 0xb2, 0x77, 0x97, 0xb6, 0x5e, 0xf6, 0xee, 0x52, 0x28, 0xc2, 0xc7, 0x63, 0xa5, 0xf2, 0x70, 0x44, 0x19, 0xcb, 0xea, 0xef, 0x2a, 0x55, 0xbd, 0x9b, 0xe4, 0xfc, 0xd3, 0xbb, 0x49, 0x66, 0x19, 0xaa, 0xf6, 0x2e, 0xd2, 0x61, 0xf2, 0xf6, 0x70, 0xa4, 0x85, 0xc7, 0xac, 0xe4, 0xb0, 0xfe, 0x2e, 0x92, 0xfe, 0xae, 0x91, 0xfe, 0x8e, 0x91, 0xfe, 0x6e, 0xd1, 0x7d, 0x3c, 0x3e, 0x1c, 0x46, 0xc2, 0x22, 0xe3, 0x1a, 0xe9, 0x69, 0x51, 0xc5, 0x32, 0x64, 0x89, 0x97, 0x6e, 0xcb, 0x78, 0x98, 0x00, 0x5e, 0x79, 0x4e, 0x99, 0x25, 0x2b, 0x95, 0x2f, 0xa5, 0xa6, 0xfc, 0x04, 0x3f, 0xc3, 0x16, 0xd8, 0x45, 0x54, 0xd9, 0x21, 0x0d, 0x32, 0xc0, 0x43, 0xcd, 0xb3, 0x82, 0x7e, 0xee, 0xb9, 0x13, 0xb3, 0x51, 0xf5, 0x6e, 0x50, 0x48, 0x9d, 0x01, 0xbf, 0xbf, 0x1b, 0x94, 0x1f, 0x7e, 0x37, 0xe8, 0xb0, 0xea, 0x96, 0x07, 0x54, 0x0f, 0xf8, 0xc0, 0x0f, 0x9a, 0x3c, 0x10, 0x7e, 0xb7, 0xe7, 0x98, 0x75, 0x37, 0xec, 0x95, 0x69, 0xff, 0xc5, 0xbb, 0x3d, 0x69, 0x31, 0xf8, 0x6e, 0x61, 0x25, 0x96, 0xce, 0x11, 0x47, 0x21, 0xe2, 0xa8, 0x42, 0x1f, 0xf1, 0xf0, 0x39, 0xeb, 0x7d, 0xc6, 0xe7, 0x02, 0x16, 0x19, 0xef, 0x5f, 0xe4, 0xa0, 0xfa, 0xd1, 0x26, 0x41, 0x06, 0xbf, 0x80, 0x73, 0xdd, 0x45, 0x16, 0x27, 0xe0, 0x5e, 0x77, 0x88, 0xc3, 0xb8, 0xd1, 0x54, 0x19, 0x4b, 0x46, 0xaf, 0x22, 0xa3, 0x17, 0x50, 0xdd, 0x6e, 0x24, 0xab, 0x3f, 0xc1, 0xc5, 0x36, 0x30, 0x2a, 0x5c, 0xba, 0x9c, 0x4a, 0x76, 0xff, 0x93, 0x2a, 0x77, 0x2f, 0x2e, 0xb6, 0x2f, 0x59, 0xee, 0x16, 0x59, 0xb2, 0x33, 0xd5, 0x60, 0x38, 0x99, 0xbe, 0x8a, 0x4a, 0xd0, 0x53, 0xe4, 0xca, 0x68, 0x1c, 0x6d, 0x2c, 0x8e, 0x36, 0x96, 0xaa, 0x77, 0x3f, 0x8e, 0x36, 0x1e, 0x47, 0x1b, 0x8b, 0x0a, 0xd4, 0xc0, 0xd1, 0x4e, 0x44, 0x09, 0xba, 0xe0, 0x68, 0xaf, 0x21, 0x87, 0x12, 0x4c, 0x37, 0xca, 0x25, 0xa6, 0x9b, 0xe4, 0x47, 0xa6, 0x5e, 0xf2, 0x79, 0x53, 0x6f, 0xb8, 0x55, 0xbe, 0x62, 0xea, 0x07, 0xfd, 0xe5, 0x04, 0xd3, 0x6d, 0xf2, 0x29, 0x1c, 0xef, 0x04, 0xd3, 0xed, 0x2c, 0x07, 0xca, 0x39, 0x38, 0xdf, 0xf9, 0x38, 0xdf, 0xf9, 0x38, 0xdf, 0x5d, 0xa8, 0xc7, 0x02, 0x9c, 0x6f, 0x2c, 0x0a, 0xd2, 0x89, 0x2a, 0x7a, 0x6f, 0xc4, 0x5a, 0x19, 0x1d, 0xf1, 0x0d, 0xac, 0xe3, 0xb1, 0x6f, 0x61, 0x83, 0x8c, 0xc5, 0x05, 0xbf, 0x83, 0x0b, 0x7e, 0x07, 0x17, 0xfc, 0x46, 0xe4, 0x9b, 0x72, 0x2d, 0x4e, 0xf8, 0x8d, 0xc8, 0xb7, 0xe4, 0xda, 0xa8, 0x6c, 0x9c, 0xc0, 0x59, 0x19, 0x1b, 0x55, 0x2e, 0x3b, 0xe2, 0x88, 0x77, 0xa1, 0x3a, 0x09, 0xa8, 0x4e, 0x02, 0xaa, 0x93, 0x80, 0xea, 0xec, 0x43, 0x75, 0xf6, 0xa1, 0x3a, 0x09, 0xa8, 0x4e, 0x02, 0xaa, 0xb3, 0x0f, 0xd5, 0xd9, 0x87, 0xea, 0x24, 0xa0, 0x3a, 0x09, 0xa8, 0x4e, 0x02, 0xaa, 0x93, 0x80, 0xea, 0x24, 0xa0, 0x3a, 0x09, 0xa8, 0x4e, 0x02, 0xaa, 0xb3, 0x0f, 0xd5, 0xd9, 0x87, 0xea, 0x24, 0xa0, 0x3a, 0x09, 0xa8, 0xce, 0x3e, 0x54, 0x67, 0x1f, 0xaa, 0x93, 0x80, 0xdb, 0xde, 0x85, 0xdb, 0xde, 0x85, 0xdb, 0xde, 0x85, 0xdb, 0xde, 0x85, 0xdb, 0x4e, 0xc7, 0x6d, 0xef, 0xc2, 0x6d, 0xa7, 0xe3, 0xb6, 0xd3, 0x71, 0xdb, 0xbb, 0x70, 0xdb, 0xbb, 0x70, 0xdb, 0xb1, 0xea, 0x4d, 0xb2, 0x33, 0x8e, 0x3b, 0x16, 0xc7, 0x1d, 0x8b, 0xe3, 0x8e, 0xc5, 0x71, 0xc7, 0xe2, 0xb8, 0x63, 0x71, 0xdc, 0xb1, 0x38, 0xee, 0x58, 0x1c, 0x77, 0x2c, 0x8e, 0x3b, 0x16, 0xc7, 0x1d, 0x6b, 0x6d, 0x2c, 0xb7, 0x5b, 0xe3, 0xa0, 0x89, 0x4c, 0xb7, 0x36, 0x85, 0x66, 0xd0, 0x9c, 0xfb, 0x2d, 0x58, 0xb6, 0x84, 0x56, 0xd0, 0x9a, 0xfb, 0x6d, 0xa0, 0x2d, 0x5c, 0x0b, 0xed, 0x78, 0xec, 0x3a, 0xb8, 0x1e, 0xda, 0x73, 0x9f, 0x7d, 0xb1, 0x76, 0x84, 0x4e, 0xd0, 0x99, 0x75, 0x76, 0x81, 0xae, 0xb2, 0x21, 0x0e, 0xbe, 0x05, 0x0e, 0x3e, 0x16, 0x07, 0x1f, 0x8b, 0x83, 0x6f, 0x88, 0x83, 0x6f, 0x87, 0x83, 0x8f, 0xc5, 0xc1, 0xc7, 0xe2, 0xe0, 0x63, 0x71, 0xf0, 0x3d, 0x70, 0xf0, 0xcd, 0x71, 0xf0, 0xed, 0x71, 0xf0, 0xb1, 0x38, 0xf8, 0x58, 0x1c, 0x7c, 0x43, 0x1c, 0x7c, 0x0b, 0x1c, 0x7c, 0x2c, 0x0e, 0x3e, 0x16, 0x07, 0xdf, 0x10, 0x07, 0xdf, 0x0e, 0x07, 0x1f, 0x8b, 0x83, 0x8f, 0xc5, 0xc1, 0xc7, 0xe2, 0xe0, 0x7b, 0xe0, 0xe0, 0x1b, 0xe0, 0xe0, 0x1b, 0xe0, 0xe0, 0xdb, 0xe0, 0xe0, 0xbb, 0xe0, 0xe0, 0x1b, 0xe0, 0xe0, 0xdb, 0xe0, 0xe0, 0x7b, 0xe2, 0xe0, 0x1b, 0xe0, 0xe0, 0x1b, 0xe0, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xfe, 0x3f, 0xe4, 0xbd, 0x07, 0x78, 0x14, 0x55, 0x1b, 0xf6, 0x7f, 0x66, 0x53, 0x76, 0x66, 0x37, 0xf4, 0x84, 0x24, 0x84, 0x34, 0x08, 0x49, 0x28, 0xd2, 0x09, 0x0a, 0xa1, 0x47, 0x44, 0x8a, 0x80, 0x58, 0xb0, 0x21, 0xf6, 0x8a, 0x85, 0xa2, 0xa8, 0xa8, 0xd8, 0x10, 0x7d, 0x01, 0x1b, 0x36, 0xb0, 0x22, 0x22, 0xa2, 0x08, 0x2f, 0x16, 0x34, 0xaf, 0x20, 0x28, 0x04, 0x24, 0xb4, 0x44, 0x5c, 0x96, 0xb0, 0x10, 0x43, 0x12, 0xb2, 0x09, 0x0b, 0x84, 0xc0, 0x46, 0x08, 0xc8, 0xf9, 0xff, 0xe6, 0x64, 0x12, 0x37, 0x01, 0x6c, 0xff, 0xf7, 0xfd, 0xae, 0xef, 0xba, 0xbe, 0xcc, 0x75, 0xef, 0x4e, 0x76, 0x67, 0xcf, 0x9c, 0x73, 0x9f, 0xe7, 0x9c, 0xfb, 0x7e, 0x66, 0x67, 0x67, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0xfd, 0x1d, 0x77, 0xcb, 0x63, 0x38, 0xf8, 0x17, 0x70, 0xf0, 0xa6, 0x02, 0x24, 0xe3, 0xe0, 0x5f, 0xc0, 0xc1, 0xbf, 0x80, 0x83, 0x8f, 0xc4, 0xc1, 0xb7, 0xc5, 0xc1, 0xbf, 0xe0, 0x78, 0x90, 0x6d, 0x94, 0x83, 0xc7, 0xf3, 0x3d, 0x2c, 0x07, 0xe3, 0xe0, 0x7f, 0xc2, 0xc1, 0x7b, 0x50, 0x87, 0x8d, 0x38, 0xf8, 0x01, 0x8e, 0xe9, 0x32, 0xd4, 0xf1, 0x04, 0x78, 0x52, 0xa6, 0xe1, 0xe4, 0xd3, 0x70, 0xf2, 0x69, 0x38, 0xf9, 0x34, 0x9c, 0x7c, 0x1a, 0x4e, 0x3e, 0x0d, 0x27, 0x9f, 0x86, 0x93, 0x4f, 0xc3, 0xc9, 0xa7, 0xe1, 0xe4, 0xd3, 0x70, 0xf2, 0x69, 0x38, 0xf9, 0xbb, 0x70, 0xf2, 0x77, 0xe1, 0xe4, 0xd3, 0x70, 0xf2, 0x69, 0x38, 0xf9, 0x34, 0x9c, 0x7c, 0x1a, 0x4e, 0x3e, 0x0d, 0x27, 0x9f, 0x86, 0x93, 0x8f, 0xc1, 0xc9, 0xc7, 0xe0, 0xe4, 0x63, 0x70, 0xf2, 0x31, 0x38, 0xf9, 0x18, 0x9c, 0x7c, 0x0c, 0x4e, 0x3e, 0x06, 0x27, 0x1f, 0x83, 0x93, 0x8f, 0xc1, 0xc9, 0xc7, 0xe0, 0xe4, 0x63, 0x70, 0xf2, 0x31, 0x38, 0xf9, 0x18, 0x9c, 0x7c, 0x0c, 0x4e, 0x3e, 0x06, 0x27, 0x1f, 0x83, 0x93, 0x8f, 0xc1, 0xc9, 0xc7, 0xe0, 0xe4, 0x63, 0x70, 0xf2, 0x31, 0x38, 0xf9, 0x18, 0x9c, 0x7c, 0x0c, 0x4e, 0x3e, 0x06, 0x27, 0x1f, 0x83, 0x93, 0x5f, 0x87, 0x93, 0x9f, 0x81, 0xfb, 0xdd, 0xe6, 0x7c, 0x49, 0x9e, 0x27, 0xda, 0xaa, 0x2b, 0xdf, 0x49, 0xb9, 0x1a, 0x65, 0xf8, 0x56, 0x6b, 0x88, 0x9b, 0x6f, 0x24, 0xa7, 0xa1, 0x0e, 0x8b, 0x50, 0x04, 0xf3, 0x1e, 0xe5, 0x57, 0x31, 0x6b, 0xb7, 0x40, 0xf1, 0x22, 0x71, 0xc5, 0xb7, 0xd6, 0x19, 0xb1, 0x8c, 0x48, 0x5c, 0xf2, 0x45, 0xa8, 0xde, 0x44, 0x14, 0xcf, 0x40, 0xf1, 0x06, 0xe0, 0x7c, 0xe6, 0xa1, 0x7a, 0x69, 0xa8, 0xdd, 0x08, 0x66, 0xc9, 0xa9, 0xfa, 0x50, 0x66, 0xc1, 0x2f, 0xe4, 0x70, 0x66, 0xc8, 0x51, 0xcc, 0x88, 0x6d, 0x99, 0x11, 0xe3, 0x99, 0x0d, 0xb7, 0x1a, 0x49, 0x22, 0x99, 0x59, 0x30, 0x91, 0xd9, 0x2f, 0x8e, 0xd9, 0x6f, 0x02, 0x4a, 0xd8, 0x85, 0xd9, 0xed, 0x19, 0x66, 0xb2, 0xab, 0x99, 0xc5, 0x1e, 0x70, 0xec, 0x90, 0xab, 0x54, 0x0d, 0xbf, 0x95, 0x19, 0xcc, 0x68, 0x89, 0xe2, 0x3a, 0x2b, 0x33, 0xb8, 0x46, 0x0c, 0x92, 0x4f, 0x88, 0x0c, 0xf9, 0xac, 0x18, 0xca, 0xf3, 0xe5, 0xe0, 0x2a, 0x70, 0x03, 0x6e, 0xf8, 0x66, 0x99, 0x61, 0xce, 0x76, 0xe2, 0x61, 0xf9, 0x6e, 0xfd, 0x5a, 0xe2, 0x69, 0x36, 0x69, 0xe3, 0xe5, 0x62, 0xb2, 0x86, 0x22, 0xb2, 0x86, 0x22, 0x72, 0x15, 0x3b, 0xfe, 0xf7, 0x55, 0xfc, 0xef, 0x5c, 0xfc, 0xef, 0xcb, 0xcc, 0x8e, 0x43, 0x98, 0x1d, 0x53, 0x98, 0x1d, 0x23, 0xc8, 0x12, 0xbc, 0xcc, 0x8e, 0x1f, 0x86, 0x6c, 0x95, 0x4f, 0x84, 0x6c, 0x93, 0x4f, 0xe8, 0x6f, 0xc8, 0x0c, 0x7d, 0x9e, 0x1c, 0xa9, 0xcf, 0x97, 0x7d, 0xf5, 0xb7, 0x68, 0xc5, 0xdb, 0xb4, 0xe2, 0x1d, 0x79, 0x81, 0xbe, 0x50, 0x5e, 0x82, 0xb3, 0x2e, 0xd1, 0x17, 0xf1, 0xfc, 0x11, 0x58, 0xcc, 0x36, 0x1f, 0xcb, 0x3b, 0xf4, 0x25, 0xe0, 0x13, 0xf0, 0x29, 0x58, 0xca, 0x76, 0x9f, 0xc9, 0x6b, 0xf4, 0xb5, 0xb2, 0xa3, 0xfe, 0xbd, 0xec, 0xac, 0x6f, 0x93, 0xb1, 0x7a, 0x8e, 0x4c, 0xd5, 0x73, 0x65, 0x7b, 0xfd, 0x27, 0xca, 0xf1, 0xc8, 0x48, 0x7d, 0x8f, 0x4c, 0x21, 0x37, 0x5a, 0xac, 0xe7, 0xf3, 0xfc, 0x0b, 0x28, 0xe0, 0xfd, 0x43, 0xec, 0xf7, 0x30, 0x28, 0x07, 0xa7, 0x81, 0x94, 0xfd, 0x0d, 0x21, 0xdb, 0x18, 0x41, 0x72, 0xa0, 0x61, 0x97, 0xa9, 0x46, 0x03, 0xd9, 0x8e, 0x5c, 0xaa, 0x1b, 0xae, 0x7e, 0x25, 0xbe, 0xac, 0x2f, 0xbe, 0xac, 0x03, 0xb3, 0x79, 0x32, 0x79, 0x55, 0x14, 0x79, 0xd5, 0x79, 0x2a, 0xaf, 0xea, 0x22, 0x06, 0x19, 0x97, 0xc9, 0xe6, 0xc6, 0x15, 0x32, 0xc1, 0x18, 0x0b, 0xd3, 0x37, 0xc0, 0xfc, 0x8d, 0xf2, 0x3d, 0xe3, 0x26, 0x9e, 0x6f, 0x06, 0xb7, 0xf0, 0xfa, 0xad, 0xb2, 0xb5, 0xf1, 0xa4, 0x5c, 0x60, 0x3c, 0x2b, 0xc7, 0x1a, 0xb3, 0xe4, 0xdd, 0xc6, 0x6c, 0xf9, 0x8c, 0x71, 0x8c, 0xd8, 0x84, 0x79, 0x87, 0x5f, 0x36, 0x17, 0x4d, 0x2c, 0x1d, 0x31, 0xbf, 0x39, 0x2e, 0x80, 0xdd, 0x32, 0xd8, 0x2d, 0x80, 0xdd, 0x1d, 0xb0, 0xba, 0xdf, 0xca, 0x35, 0x3c, 0xb0, 0xb6, 0xd3, 0xd2, 0x14, 0x33, 0xd7, 0x28, 0xa0, 0x56, 0x05, 0x75, 0x72, 0x8d, 0x7e, 0xf4, 0xdd, 0xd7, 0x94, 0x54, 0x45, 0x49, 0x8f, 0xd0, 0x57, 0x4d, 0x28, 0xed, 0x15, 0x4a, 0x5a, 0x49, 0x49, 0x77, 0xd1, 0x4f, 0x05, 0xf4, 0xd3, 0x2f, 0x94, 0xf8, 0x98, 0x76, 0xb1, 0x30, 0xb4, 0xd1, 0xa2, 0x91, 0x36, 0x46, 0x34, 0xd3, 0x2e, 0x17, 0x8d, 0xe9, 0xb7, 0xf5, 0xf4, 0x5b, 0x36, 0xfd, 0x56, 0x46, 0xbf, 0x95, 0xd1, 0x6f, 0xad, 0xe8, 0xb7, 0xef, 0xe8, 0xb7, 0x55, 0xf4, 0x5b, 0x36, 0xee, 0xa8, 0x84, 0x5a, 0xe4, 0x52, 0x8b, 0x99, 0xd4, 0xe2, 0x1e, 0x33, 0xe3, 0xa1, 0xff, 0xca, 0x54, 0x4d, 0xde, 0x90, 0x2f, 0xd1, 0x5f, 0x8f, 0xd3, 0x57, 0xd3, 0xc9, 0x3f, 0x63, 0xe8, 0x9b, 0x85, 0xf4, 0xcd, 0x42, 0xfa, 0x66, 0x21, 0x7d, 0xb3, 0x90, 0xbe, 0x99, 0x4e, 0xdf, 0xbc, 0x43, 0xbf, 0x3c, 0x49, 0xbf, 0xdc, 0x47, 0x9f, 0x3c, 0x4a, 0x3f, 0x54, 0xc1, 0xf3, 0x54, 0x78, 0x7e, 0x0d, 0x8e, 0x1f, 0x85, 0xe3, 0xcb, 0x68, 0x4d, 0x8e, 0xf5, 0x3d, 0x78, 0x02, 0x1c, 0x77, 0x81, 0xe3, 0x2e, 0x70, 0x3c, 0xc8, 0x68, 0x23, 0x1a, 0xc1, 0xf3, 0x1d, 0xb4, 0xb0, 0x8d, 0xca, 0x61, 0x6f, 0x95, 0x93, 0xe1, 0x72, 0x2f, 0x5c, 0x7e, 0x0a, 0x97, 0x4f, 0xc0, 0xe5, 0xf7, 0x70, 0xb9, 0x4e, 0x9c, 0x4f, 0xeb, 0x4b, 0xf1, 0x78, 0x15, 0x78, 0xbc, 0x0a, 0x58, 0xa8, 0x80, 0x85, 0x99, 0xb0, 0xe0, 0x84, 0x85, 0x4f, 0xad, 0x88, 0x35, 0x7f, 0x6b, 0x33, 0x17, 0x26, 0x0e, 0xc2, 0x84, 0x4f, 0x5d, 0x39, 0xf2, 0x7a, 0x18, 0x19, 0xaf, 0x5a, 0xdc, 0x98, 0xdc, 0xf6, 0x38, 0xad, 0x76, 0xd1, 0xea, 0x9f, 0x69, 0x75, 0x81, 0xfa, 0xe6, 0xfe, 0x16, 0xb8, 0xbf, 0x4d, 0xe9, 0x7a, 0x31, 0xad, 0x5f, 0x42, 0xeb, 0xe7, 0xe2, 0x07, 0xcd, 0x3e, 0x38, 0x8a, 0x1f, 0xac, 0xc0, 0x0f, 0x56, 0xe0, 0x07, 0x2b, 0xf0, 0x83, 0x15, 0xf8, 0xc1, 0x0a, 0xfc, 0x60, 0x05, 0x7e, 0xb0, 0xc2, 0x62, 0x62, 0x11, 0x4c, 0x2c, 0x82, 0x89, 0x45, 0x30, 0xb1, 0x08, 0x16, 0xde, 0xa5, 0xe5, 0xc7, 0x69, 0xf5, 0xab, 0xb4, 0x78, 0x20, 0x2d, 0xde, 0x41, 0x8b, 0x5b, 0xd2, 0xe2, 0x70, 0x5a, 0x9c, 0x44, 0x8b, 0x63, 0x69, 0x71, 0x57, 0x5a, 0x3b, 0x92, 0xd6, 0x26, 0xa8, 0x6b, 0x7b, 0x3d, 0x29, 0x3d, 0xaa, 0xa5, 0xb3, 0x71, 0x0b, 0x6e, 0xfc, 0xc3, 0x06, 0xbc, 0xc3, 0x2a, 0xbc, 0x43, 0x15, 0xde, 0x21, 0xd3, 0x3a, 0x8f, 0x70, 0x05, 0xde, 0xe1, 0x59, 0xbc, 0xc3, 0x57, 0x78, 0x87, 0x9d, 0x78, 0x07, 0x0f, 0xde, 0x21, 0x17, 0xef, 0xf0, 0x29, 0xde, 0x61, 0x16, 0xde, 0x61, 0x3b, 0xbe, 0xa1, 0x0c, 0xdf, 0xb0, 0xd0, 0x3a, 0x4b, 0x60, 0x3d, 0xde, 0xe1, 0x04, 0xde, 0xa1, 0x1c, 0xef, 0xb0, 0x1c, 0xef, 0xb0, 0x1c, 0xef, 0xf0, 0x0a, 0xde, 0x61, 0x35, 0xde, 0x61, 0x39, 0xde, 0xe1, 0x23, 0xbc, 0xc3, 0x4f, 0x78, 0x87, 0xcf, 0xf1, 0x0e, 0x99, 0x01, 0x47, 0xc3, 0xb6, 0xe3, 0x09, 0xb6, 0xe3, 0x09, 0x36, 0xe0, 0x09, 0x56, 0xe0, 0x09, 0x96, 0xe3, 0x07, 0x66, 0xe1, 0x07, 0xca, 0xf1, 0x03, 0xe5, 0xf8, 0x81, 0xe5, 0xf8, 0x81, 0xe5, 0xf8, 0x81, 0xe5, 0xf8, 0x81, 0x1c, 0xfc, 0x40, 0x0e, 0x7e, 0xe0, 0x5b, 0xbc, 0xc0, 0xb7, 0x64, 0x1c, 0xad, 0xf0, 0x01, 0xcb, 0xf1, 0x00, 0x1b, 0xd0, 0xe9, 0x0d, 0xe8, 0xf4, 0x06, 0x74, 0x7a, 0x03, 0x3a, 0xbd, 0x01, 0x9d, 0x2e, 0x41, 0xa7, 0x37, 0xa0, 0xd3, 0x25, 0xe8, 0x74, 0x09, 0x3a, 0xbd, 0x01, 0x9d, 0xde, 0x80, 0x4e, 0x2f, 0x47, 0xa7, 0xcb, 0xd0, 0xe9, 0xe5, 0xe8, 0xf4, 0x72, 0x74, 0x7a, 0x39, 0x3a, 0xbd, 0x1c, 0x9d, 0x5e, 0x8e, 0x4e, 0x2f, 0x47, 0xa7, 0x97, 0xa3, 0xd3, 0xcb, 0xd1, 0xe9, 0xe5, 0xe8, 0xf4, 0x72, 0x74, 0xba, 0x0a, 0x9d, 0xae, 0x42, 0xa7, 0xab, 0xd0, 0xe9, 0x2a, 0x74, 0xba, 0x0a, 0x9d, 0xae, 0x42, 0xa7, 0xab, 0xd0, 0xe9, 0x2a, 0x74, 0xba, 0x0a, 0x9d, 0xae, 0x42, 0xa7, 0xab, 0xd0, 0xe9, 0x2a, 0x74, 0xba, 0x0a, 0x9d, 0xae, 0x42, 0xa7, 0xab, 0xd0, 0xe9, 0x2a, 0x74, 0xba, 0x0a, 0x9d, 0xae, 0x42, 0xa7, 0xab, 0xd0, 0xe9, 0x2a, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x77, 0xa2, 0xd3, 0x3b, 0xd1, 0xe9, 0x9d, 0xe8, 0xf4, 0x4e, 0x74, 0x7a, 0x27, 0x3a, 0xbd, 0x13, 0x9d, 0xde, 0x89, 0x4e, 0xef, 0x44, 0xa7, 0x77, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xdb, 0xc9, 0xd0, 0x52, 0xd0, 0xe9, 0xed, 0xe8, 0x74, 0x63, 0x74, 0x3a, 0x06, 0x9d, 0xde, 0x8e, 0x4e, 0x6f, 0x47, 0xa7, 0x0d, 0x74, 0x3a, 0x06, 0x9d, 0xde, 0x4e, 0xf6, 0x96, 0x82, 0x4e, 0xdf, 0x44, 0xf6, 0xd6, 0x0a, 0x9d, 0xde, 0x8e, 0x4e, 0x1f, 0x42, 0xa7, 0x4f, 0x39, 0x1e, 0x15, 0x8d, 0xd0, 0xe9, 0x74, 0x74, 0xba, 0x1c, 0x9d, 0x2e, 0x47, 0xa7, 0x57, 0xa3, 0xd3, 0xab, 0xd1, 0xe9, 0xd5, 0xe8, 0xf4, 0x6a, 0x74, 0x7a, 0x35, 0x3a, 0xbd, 0x1a, 0x9d, 0x5e, 0x8d, 0x4e, 0xaf, 0x46, 0xa7, 0x57, 0xa3, 0xd3, 0xab, 0xd1, 0xe9, 0xd5, 0xe8, 0xf4, 0x6a, 0x74, 0x7a, 0x35, 0x3a, 0xbd, 0x1a, 0x9d, 0x5e, 0x8d, 0x4e, 0xaf, 0x46, 0xa7, 0x57, 0xa3, 0xd3, 0xab, 0xd1, 0xe9, 0xd5, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0xf4, 0x06, 0x74, 0x7a, 0x3b, 0x73, 0x71, 0xb9, 0x68, 0x2c, 0x8e, 0xa2, 0x70, 0x52, 0x2e, 0x43, 0x9b, 0x97, 0xa3, 0xcd, 0x6b, 0xb5, 0x70, 0xe1, 0x40, 0x9b, 0xe7, 0xa3, 0xc9, 0x1f, 0xa3, 0xc1, 0x73, 0xd1, 0xdc, 0x4c, 0xa3, 0xb5, 0x68, 0x88, 0xae, 0x86, 0xa3, 0xa9, 0x15, 0x68, 0xea, 0xf7, 0x68, 0xea, 0xe7, 0xe8, 0x68, 0x89, 0x2a, 0xa1, 0x05, 0xb3, 0xce, 0x97, 0xcc, 0x38, 0x07, 0x98, 0x71, 0x2a, 0xc8, 0x2c, 0xbf, 0x67, 0xd6, 0x39, 0xc8, 0xbc, 0xba, 0x97, 0x79, 0x75, 0x2f, 0xb3, 0x4c, 0x53, 0x66, 0x16, 0xf3, 0x08, 0xda, 0x41, 0x35, 0xb3, 0xcc, 0x10, 0x71, 0xcc, 0x2c, 0xf3, 0x99, 0x59, 0xfc, 0xe6, 0xec, 0xce, 0xac, 0xf2, 0x23, 0x33, 0x47, 0x2f, 0x66, 0x87, 0x0c, 0x66, 0x87, 0x83, 0xcc, 0x0a, 0xe1, 0xcc, 0x0a, 0xcd, 0x98, 0x15, 0xba, 0x33, 0x2b, 0x8c, 0x66, 0x46, 0x38, 0x4e, 0x86, 0xf9, 0xbd, 0xda, 0xd3, 0x28, 0xe6, 0xbb, 0xe5, 0xcc, 0x73, 0x27, 0xd8, 0xe3, 0x78, 0xd4, 0xf9, 0x55, 0xf6, 0x7a, 0xb9, 0xb8, 0x53, 0x1e, 0x63, 0x8f, 0x6f, 0x33, 0xcf, 0x8d, 0x63, 0x9e, 0xfb, 0x92, 0x79, 0xee, 0x73, 0xe6, 0xb9, 0xbb, 0x98, 0xf1, 0x1d, 0xf5, 0x66, 0xfc, 0x65, 0xcc, 0x7b, 0x0b, 0xa8, 0xd9, 0x0f, 0xd4, 0xec, 0x07, 0x6b, 0xc6, 0x9f, 0xcd, 0xdc, 0x37, 0x8b, 0x1a, 0xbe, 0x44, 0x0d, 0xf7, 0x50, 0xb3, 0x57, 0xa9, 0xd9, 0xc5, 0xd4, 0x6c, 0x80, 0xa9, 0x3b, 0x66, 0x76, 0xcd, 0xac, 0xbf, 0x9e, 0x5a, 0xfe, 0x82, 0x6a, 0xbf, 0x8e, 0x6a, 0xbf, 0xce, 0xec, 0x3f, 0x09, 0xc5, 0xee, 0x8a, 0x02, 0x8c, 0x45, 0xb1, 0x33, 0x50, 0x81, 0x1b, 0xf5, 0x0f, 0x50, 0xe1, 0x0f, 0x45, 0x08, 0xf3, 0xdf, 0x0c, 0xe6, 0xbf, 0x19, 0xcc, 0x7f, 0x33, 0x98, 0xff, 0x66, 0xa0, 0x04, 0x37, 0x32, 0x07, 0x4e, 0x47, 0x09, 0x6e, 0x41, 0x09, 0x2e, 0x45, 0x09, 0xc6, 0xa3, 0xce, 0x3d, 0xf5, 0x9f, 0xe5, 0x40, 0xe6, 0xc5, 0x3d, 0x28, 0xf0, 0xeb, 0xa8, 0xc2, 0x4d, 0xcc, 0x8f, 0xd3, 0x0d, 0x03, 0x05, 0x6e, 0x20, 0xc7, 0x5b, 0xca, 0xb0, 0x92, 0x39, 0xb2, 0x19, 0x6c, 0xa4, 0xc0, 0x46, 0x1b, 0xd8, 0x18, 0x66, 0xa9, 0xc2, 0x5d, 0xc6, 0x50, 0x39, 0x01, 0xf5, 0x1d, 0x86, 0xf2, 0x76, 0x36, 0xae, 0x96, 0xbd, 0x60, 0xe8, 0x67, 0x14, 0x62, 0x2c, 0xf3, 0xe6, 0x16, 0xe6, 0xcd, 0xd7, 0x50, 0x88, 0x01, 0xcc, 0x9d, 0x0b, 0x8c, 0xd5, 0xf2, 0x56, 0xe3, 0x47, 0x39, 0x09, 0xa5, 0x58, 0x0a, 0x7b, 0x59, 0xa8, 0xee, 0x30, 0x31, 0x45, 0xe5, 0x64, 0x9b, 0xe4, 0x44, 0xe6, 0xd4, 0xcf, 0x98, 0x53, 0x97, 0x31, 0x9f, 0xbe, 0xc7, 0x7c, 0xfa, 0x38, 0xf3, 0xe9, 0x63, 0xcc, 0xa7, 0xb3, 0xd5, 0x2f, 0x36, 0x76, 0xc9, 0x79, 0xcc, 0xa3, 0xd7, 0x92, 0x7f, 0x95, 0x32, 0x87, 0xbe, 0xcc, 0xfc, 0xf9, 0x2e, 0xf3, 0xe7, 0x6d, 0xcc, 0x99, 0xcf, 0x30, 0x57, 0xb6, 0x60, 0xae, 0xfc, 0x84, 0xb9, 0x72, 0xcc, 0x9f, 0x31, 0xce, 0x9c, 0xf9, 0x38, 0x39, 0xd4, 0x38, 0xe6, 0xcc, 0x6b, 0x6b, 0xd8, 0x64, 0x5e, 0xec, 0x4f, 0x4e, 0x34, 0x91, 0x9c, 0x68, 0x22, 0x39, 0xd1, 0x44, 0x72, 0xa2, 0x1c, 0x72, 0xa2, 0x1c, 0x72, 0xa2, 0x89, 0xe4, 0x44, 0x13, 0xc9, 0x89, 0x72, 0xc8, 0x89, 0x72, 0xc8, 0x89, 0x26, 0x92, 0x13, 0x4d, 0x24, 0x27, 0x9a, 0x48, 0x4e, 0x34, 0x91, 0x9c, 0x68, 0x22, 0x39, 0xd1, 0x44, 0x72, 0xa2, 0x89, 0xe4, 0x44, 0x39, 0xe4, 0x44, 0x39, 0xe4, 0x44, 0x13, 0xc9, 0x89, 0x26, 0x92, 0x13, 0xe5, 0x90, 0x13, 0xe5, 0x90, 0x13, 0x4d, 0x64, 0x6e, 0x2d, 0x65, 0x5e, 0x2d, 0xd5, 0xcd, 0x5f, 0x54, 0xb4, 0x90, 0xcb, 0x98, 0x1f, 0x97, 0x31, 0x3f, 0x96, 0x31, 0x3f, 0x96, 0x31, 0x3f, 0x96, 0x31, 0x3f, 0x2e, 0x63, 0x7e, 0x2c, 0x63, 0x7e, 0x2c, 0x63, 0x7e, 0x2c, 0x63, 0x7e, 0x5c, 0xc6, 0xfc, 0xb8, 0x8c, 0xf9, 0x71, 0x19, 0xf3, 0xe3, 0x32, 0xe6, 0xc7, 0x32, 0xe6, 0xc7, 0x32, 0xe6, 0xc7, 0x32, 0xe6, 0xc7, 0x65, 0xcc, 0x8f, 0x65, 0xcc, 0x8f, 0x65, 0xcc, 0x8f, 0x65, 0xcc, 0x8b, 0xfd, 0x98, 0x17, 0x87, 0x32, 0x1f, 0x5e, 0xc0, 0x7c, 0x38, 0x8c, 0x39, 0xb0, 0x23, 0x73, 0x60, 0x7f, 0xe6, 0xc0, 0x71, 0xcc, 0x7d, 0xfd, 0x98, 0xfb, 0x86, 0x32, 0xe7, 0x5d, 0xc0, 0x9c, 0x37, 0x8c, 0x79, 0xae, 0x23, 0xf3, 0xdb, 0xd5, 0xcc, 0x6f, 0xb7, 0x30, 0xb7, 0x5d, 0xc6, 0xdc, 0x76, 0x2b, 0x73, 0xce, 0xbd, 0xcc, 0x39, 0xe9, 0xcc, 0x39, 0xef, 0x33, 0xe7, 0x24, 0x30, 0xe7, 0xf4, 0x60, 0xce, 0x79, 0x9f, 0x39, 0x67, 0x1e, 0x73, 0x4e, 0x02, 0x73, 0x4e, 0x57, 0xe6, 0x9c, 0x79, 0xcc, 0x39, 0xe9, 0xcc, 0x39, 0xb3, 0x98, 0x73, 0xfa, 0x33, 0xe7, 0xdc, 0xc3, 0x9c, 0x53, 0xee, 0x98, 0x26, 0x9a, 0x33, 0xe7, 0xf4, 0x64, 0xce, 0x19, 0xcf, 0xfc, 0x31, 0x87, 0xf9, 0x63, 0x0e, 0x73, 0xc5, 0x75, 0x66, 0xcf, 0x8a, 0x48, 0x7c, 0xf6, 0xfd, 0x8c, 0xe1, 0xa5, 0xf8, 0xec, 0x95, 0x8c, 0xe3, 0x87, 0x18, 0xc7, 0xef, 0x69, 0xe1, 0xa7, 0x7f, 0x53, 0xbf, 0x2a, 0xe8, 0x28, 0xbb, 0xd1, 0x53, 0xe3, 0xe9, 0xa9, 0x95, 0xf4, 0xd4, 0x97, 0xf4, 0xd4, 0x3d, 0xf4, 0x54, 0x18, 0x3d, 0xd5, 0x84, 0x9e, 0x6a, 0x4e, 0x4f, 0x35, 0xad, 0xe9, 0x1d, 0x7c, 0xf5, 0x5d, 0x78, 0xe9, 0x5e, 0x8c, 0x77, 0xf3, 0x7c, 0xca, 0xd7, 0x28, 0x7d, 0xa3, 0x88, 0xb6, 0x9c, 0xda, 0xcf, 0x38, 0x8b, 0x50, 0x46, 0x5c, 0x31, 0xe3, 0xfc, 0x94, 0x79, 0x9e, 0x1f, 0x23, 0xa9, 0x9c, 0x91, 0x64, 0x1e, 0x45, 0x3a, 0xce, 0xc8, 0xd9, 0xcc, 0xc8, 0x39, 0xc5, 0xc8, 0x31, 0xcf, 0x13, 0xd8, 0xcc, 0x68, 0x48, 0x24, 0xc2, 0x0f, 0x12, 0xcd, 0x29, 0x44, 0x73, 0x05, 0xaa, 0xef, 0xb4, 0x54, 0x3f, 0x9e, 0x88, 0x0e, 0x23, 0xa2, 0x23, 0x89, 0xe6, 0xee, 0xa8, 0x7e, 0x63, 0x22, 0xb8, 0x48, 0x68, 0x41, 0x1e, 0xf5, 0x7b, 0xb7, 0xd2, 0xe0, 0xe1, 0xf6, 0x4b, 0xcc, 0xc5, 0xc8, 0x3b, 0x63, 0x79, 0xcf, 0x7c, 0xb4, 0x7f, 0x61, 0xe4, 0xe9, 0xdf, 0x9f, 0xe5, 0x5d, 0x73, 0xe9, 0x60, 0xcc, 0x61, 0xab, 0xf7, 0xaa, 0xff, 0x6b, 0x30, 0x8e, 0x25, 0x3c, 0x31, 0xd4, 0x5c, 0x1c, 0x1d, 0x1c, 0xbd, 0x1c, 0x9f, 0x39, 0x26, 0x03, 0xaf, 0xb3, 0x95, 0x33, 0xdd, 0xf9, 0x40, 0xf5, 0x92, 0x76, 0xfc, 0x82, 0x8e, 0x6a, 0xbb, 0xc0, 0x25, 0x9c, 0x45, 0xad, 0x39, 0x17, 0x36, 0x18, 0x17, 0xf6, 0xd9, 0x59, 0xde, 0x37, 0x1f, 0x3f, 0x6d, 0x98, 0xdc, 0x23, 0xaa, 0x47, 0x54, 0x5a, 0x46, 0xc3, 0xee, 0x8d, 0x9a, 0x57, 0xaf, 0x55, 0x3f, 0x76, 0xfa, 0xa2, 0x51, 0xff, 0xea, 0xb5, 0xd8, 0xa6, 0x8d, 0x32, 0xeb, 0x2c, 0x7b, 0xab, 0x97, 0xb4, 0xe3, 0x8d, 0xdc, 0xd5, 0x6b, 0x71, 0x13, 0x7f, 0x5f, 0x1a, 0x07, 0xc7, 0xad, 0x89, 0x3e, 0x14, 0xb7, 0xa6, 0x71, 0xc7, 0x9e, 0x2d, 0x78, 0xbc, 0xb9, 0xc9, 0x03, 0x4d, 0xa7, 0x37, 0x73, 0x45, 0x74, 0x88, 0x98, 0x13, 0x51, 0xde, 0xfc, 0xd2, 0xb8, 0x35, 0xcd, 0x17, 0x46, 0x9e, 0x8e, 0x1e, 0x1f, 0x7d, 0x4f, 0x34, 0x5b, 0x45, 0x1f, 0x6f, 0xd1, 0x3c, 0xa6, 0x65, 0xed, 0x72, 0x49, 0x6c, 0xd3, 0xd8, 0xa6, 0x2d, 0x3d, 0x3c, 0x66, 0xc4, 0x3e, 0x16, 0xbb, 0x2e, 0x2e, 0x34, 0x6e, 0x4d, 0x5c, 0x7a, 0x60, 0xd9, 0x75, 0x96, 0x35, 0x09, 0x97, 0x06, 0x2e, 0x71, 0x79, 0x03, 0x07, 0x0f, 0x1c, 0x9c, 0x20, 0xcc, 0xf5, 0xf8, 0xce, 0x60, 0x41, 0xdd, 0xf7, 0x6b, 0x16, 0x73, 0xab, 0xc0, 0xa5, 0xfa, 0x13, 0x09, 0x8b, 0x13, 0xa7, 0x2b, 0x8e, 0xc7, 0x27, 0x4e, 0x64, 0x2d, 0xb7, 0x55, 0x87, 0x56, 0x2e, 0x6b, 0x79, 0xbe, 0xd5, 0x1a, 0xf3, 0xb9, 0x75, 0x58, 0x52, 0x58, 0xeb, 0x5c, 0x30, 0x38, 0xc9, 0x5c, 0x7b, 0xc3, 0x5c, 0x6f, 0xa3, 0xb7, 0xd1, 0x93, 0x06, 0x9b, 0x8f, 0x6d, 0x5a, 0xb4, 0xe9, 0x7e, 0xde, 0xa1, 0xd4, 0xc1, 0xa9, 0xb7, 0xb7, 0xbd, 0xa4, 0x5d, 0xe7, 0xf6, 0x7d, 0xdb, 0x4f, 0xed, 0xf0, 0x72, 0x87, 0x1d, 0xe7, 0x1d, 0xea, 0x11, 0xd5, 0x31, 0x2c, 0x2d, 0x03, 0x36, 0x5b, 0x75, 0x1a, 0xdf, 0xbd, 0xaa, 0xd3, 0xcb, 0x9d, 0xbe, 0xe8, 0x3c, 0xaf, 0xcb, 0xd4, 0xae, 0x33, 0xba, 0xae, 0xe9, 0xe6, 0xea, 0x1e, 0xd5, 0xbd, 0x0a, 0x86, 0xbb, 0x9a, 0xef, 0xa7, 0xdd, 0x98, 0xf6, 0x56, 0xcf, 0x0e, 0x81, 0x4b, 0xda, 0xa6, 0xb4, 0xe3, 0x75, 0x5f, 0x09, 0x58, 0x5a, 0xa8, 0xc7, 0xab, 0x59, 0x66, 0xf5, 0x5c, 0x54, 0x67, 0x71, 0x9f, 0x6f, 0x63, 0xb9, 0xd5, 0x5a, 0xde, 0x60, 0xe9, 0x50, 0xfb, 0x5f, 0xf5, 0xff, 0xb9, 0x17, 0x84, 0x5d, 0xd0, 0xb1, 0x76, 0x19, 0xd5, 0x5b, 0xef, 0xb5, 0xaf, 0xb7, 0xfe, 0x57, 0x96, 0x3e, 0x73, 0xfe, 0xc1, 0xb2, 0xb1, 0xfe, 0xd2, 0xff, 0xad, 0xbe, 0x2d, 0xfa, 0xbf, 0xf5, 0x77, 0x96, 0x01, 0xc7, 0xfa, 0xaf, 0x1e, 0xd0, 0x79, 0x40, 0xaf, 0x01, 0xc7, 0xea, 0x2d, 0x43, 0x06, 0xbc, 0x13, 0xf8, 0xbf, 0xea, 0xcf, 0xe9, 0x7f, 0x69, 0x59, 0x31, 0x70, 0xdd, 0xa0, 0xe4, 0xbf, 0xb7, 0x08, 0x9b, 0x68, 0xaa, 0xcf, 0xd5, 0xd7, 0x09, 0xa1, 0x67, 0xe9, 0xd9, 0xa2, 0x8b, 0xbe, 0x45, 0xdf, 0x29, 0x7a, 0xea, 0xbb, 0xf4, 0x7d, 0x62, 0xa0, 0x5e, 0x64, 0x38, 0xc5, 0x70, 0xa3, 0x01, 0x73, 0xc1, 0x64, 0x23, 0xd5, 0x48, 0x15, 0x73, 0x8c, 0x76, 0x46, 0x3b, 0xf1, 0x02, 0x23, 0xb9, 0x93, 0x78, 0xd1, 0xe8, 0xc2, 0xfc, 0x30, 0xd7, 0xe8, 0x66, 0xf4, 0x14, 0xaf, 0x1a, 0x17, 0x18, 0x83, 0xc5, 0x3c, 0xe3, 0x62, 0x63, 0xb4, 0x58, 0x6c, 0x8c, 0x31, 0xe6, 0x8a, 0x7f, 0x1b, 0xaf, 0x1b, 0xfb, 0xc4, 0x51, 0xa3, 0xc8, 0x38, 0xae, 0xf5, 0x73, 0x44, 0x3b, 0x1e, 0xd3, 0x2e, 0x73, 0x4c, 0x77, 0x7c, 0xa6, 0x65, 0x8a, 0x35, 0x64, 0x0d, 0xf9, 0x28, 0x9c, 0x9b, 0x8c, 0xc1, 0x65, 0x9d, 0x47, 0xfc, 0x3d, 0x0a, 0xb7, 0x14, 0x85, 0x73, 0x91, 0x31, 0x94, 0xa0, 0x72, 0x5e, 0x32, 0x86, 0x02, 0x94, 0x6e, 0x23, 0x4a, 0xf7, 0x0e, 0x19, 0xc3, 0x6e, 0x32, 0x86, 0x62, 0xd4, 0x6e, 0x3d, 0x19, 0x43, 0x15, 0x8a, 0x67, 0x5e, 0x31, 0xa3, 0x82, 0x8c, 0xc1, 0x4b, 0xc6, 0x90, 0x43, 0xc6, 0x90, 0x83, 0xfa, 0x7d, 0x41, 0xc6, 0x90, 0x43, 0xc6, 0x90, 0x83, 0x0a, 0x6e, 0x46, 0x05, 0x8b, 0x51, 0xc1, 0xef, 0xc9, 0x18, 0x5c, 0x64, 0x0c, 0x15, 0x64, 0x0c, 0x15, 0x64, 0x0c, 0x7e, 0x32, 0x06, 0x3f, 0x19, 0x43, 0x3e, 0xea, 0xf7, 0x3d, 0x19, 0x43, 0x0e, 0xea, 0xf7, 0x0e, 0x19, 0x83, 0x97, 0x8c, 0xc1, 0x4b, 0xc6, 0x90, 0x43, 0xc6, 0x90, 0x43, 0xc6, 0x60, 0x66, 0x0a, 0x87, 0xc8, 0x14, 0x0e, 0xa1, 0x88, 0x1d, 0xc9, 0x14, 0x72, 0xc8, 0x14, 0xf2, 0xc9, 0x14, 0xf2, 0xc9, 0x14, 0xf2, 0xc9, 0x14, 0xf2, 0xc9, 0x14, 0xf2, 0x51, 0xb3, 0x23, 0x64, 0x0a, 0xf9, 0x28, 0xda, 0x11, 0x14, 0xed, 0x08, 0x99, 0x42, 0x3e, 0x99, 0x42, 0x3e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x31, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0xce, 0xde, 0x85, 0xb3, 0x77, 0xa1, 0x60, 0x2e, 0x14, 0xcc, 0x85, 0xb3, 0x77, 0xe1, 0xec, 0x5d, 0x28, 0x99, 0x0b, 0x25, 0x73, 0xe1, 0xec, 0x5d, 0x38, 0x7b, 0x17, 0xce, 0xde, 0x85, 0xaa, 0xb9, 0x50, 0x35, 0x17, 0xaa, 0xe6, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0xd4, 0xcd, 0x85, 0xba, 0xb9, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x95, 0x73, 0xa1, 0x72, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x50, 0x3c, 0x17, 0xce, 0xbe, 0x04, 0x67, 0x5f, 0x82, 0xf2, 0x95, 0xa0, 0x7c, 0x25, 0x38, 0xfb, 0x12, 0xd4, 0xaf, 0x04, 0xf5, 0x2b, 0xc1, 0xd9, 0x97, 0xe0, 0xec, 0x4b, 0x70, 0xf6, 0x05, 0x38, 0xfb, 0x02, 0x9c, 0x7d, 0x01, 0xce, 0xbe, 0x00, 0x67, 0x5f, 0x80, 0xb3, 0x2f, 0xc0, 0xd9, 0x17, 0xe0, 0xec, 0x0b, 0x70, 0xf6, 0x05, 0x38, 0xfb, 0x02, 0x9c, 0x7d, 0x01, 0xce, 0xbe, 0x00, 0x67, 0x5f, 0x80, 0xb3, 0x2f, 0xc0, 0xd9, 0x17, 0xe0, 0xec, 0x0b, 0x70, 0xf6, 0x05, 0x38, 0xfb, 0x02, 0x9c, 0x7d, 0x01, 0xce, 0xbe, 0x00, 0x67, 0x5f, 0x80, 0xb3, 0x2f, 0xc0, 0xd9, 0x17, 0xe0, 0xec, 0x0b, 0x50, 0xd9, 0xdd, 0xa8, 0x6c, 0x37, 0x54, 0x76, 0xb7, 0xe3, 0x3e, 0x70, 0xbf, 0x70, 0xa2, 0xb2, 0xbb, 0x51, 0xd9, 0xdd, 0x8e, 0xc9, 0x60, 0x8a, 0x68, 0x8a, 0xca, 0xee, 0x46, 0x65, 0xbb, 0xa1, 0xb2, 0x0f, 0xa2, 0xb2, 0x1d, 0x51, 0xd9, 0xdd, 0xa8, 0xec, 0x6e, 0x54, 0xb6, 0xa9, 0xe5, 0xec, 0x13, 0x70, 0xf6, 0x5e, 0x9c, 0xbd, 0x17, 0x67, 0x9f, 0x83, 0xb3, 0xcf, 0xc1, 0xd9, 0xe7, 0xe0, 0xec, 0x73, 0x70, 0xf6, 0x39, 0x38, 0xfb, 0x1c, 0x9c, 0x7d, 0x0e, 0xce, 0x3e, 0x07, 0x67, 0x9f, 0x83, 0xb3, 0xcf, 0xc1, 0xd9, 0xe7, 0xa0, 0xcc, 0x39, 0x28, 0x73, 0x0e, 0xce, 0x3e, 0x07, 0x67, 0x9f, 0x83, 0xb3, 0xcf, 0xc1, 0xd9, 0xe7, 0xe0, 0xec, 0x73, 0x50, 0xeb, 0x1c, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0x7d, 0x3e, 0xce, 0xde, 0xff, 0x7f, 0x48, 0xb3, 0x07, 0x89, 0xce, 0xe4, 0x0f, 0xd7, 0x83, 0xf1, 0xe0, 0x06, 0x70, 0x3b, 0xb8, 0x13, 0xdc, 0x05, 0x26, 0x81, 0x87, 0xc1, 0x6c, 0x79, 0x44, 0xcc, 0x91, 0x65, 0x5a, 0x0f, 0x99, 0xa3, 0xa5, 0x89, 0x20, 0x2d, 0x5d, 0x9e, 0xd2, 0xfa, 0xe0, 0x3f, 0xfa, 0xf2, 0xdc, 0x8f, 0xe7, 0x8b, 0x44, 0x37, 0xfc, 0x48, 0x3b, 0xfc, 0xc8, 0x79, 0xf8, 0x91, 0xab, 0xf1, 0x23, 0x57, 0xab, 0xab, 0x79, 0x5c, 0x87, 0x37, 0x19, 0x07, 0x6e, 0x16, 0xd1, 0x9a, 0xf9, 0xfb, 0x85, 0xdf, 0xaf, 0xea, 0x61, 0x5e, 0xe3, 0xb4, 0xb1, 0xed, 0x7e, 0xf0, 0x10, 0x78, 0x56, 0x56, 0xd9, 0xde, 0xe4, 0x79, 0x9e, 0x68, 0x1c, 0x3c, 0x59, 0x34, 0x0e, 0x09, 0x07, 0xdd, 0x45, 0x9b, 0x90, 0x1e, 0x60, 0x9d, 0xac, 0x0a, 0x4d, 0x12, 0x8d, 0xf5, 0x6f, 0xc1, 0x2a, 0xb0, 0x9a, 0x9a, 0x37, 0x06, 0x4d, 0x40, 0x53, 0xd0, 0x0c, 0xb0, 0xbd, 0x11, 0x01, 0xba, 0x81, 0xee, 0xa0, 0x07, 0x48, 0x03, 0x3d, 0x41, 0x2f, 0xd0, 0x1b, 0xa4, 0x83, 0x3e, 0xa0, 0x2f, 0xe8, 0x07, 0xfa, 0x83, 0x01, 0x60, 0x20, 0x99, 0x63, 0xf5, 0x15, 0x45, 0xde, 0x24, 0x0f, 0xaa, 0xdb, 0xd2, 0x60, 0x5a, 0xba, 0x8f, 0x96, 0xda, 0x68, 0xe9, 0x3e, 0x5a, 0x6a, 0xa3, 0x95, 0x0d, 0x69, 0x65, 0x04, 0xad, 0x8c, 0xa0, 0x95, 0x11, 0x56, 0x2b, 0x1b, 0xd3, 0xca, 0xc6, 0xb4, 0xb2, 0xd9, 0x19, 0xad, 0xa4, 0x65, 0x21, 0xdd, 0xe5, 0xc9, 0x90, 0x1e, 0x80, 0x96, 0xd4, 0xee, 0x29, 0xaa, 0x7a, 0x4f, 0xc2, 0x60, 0x4f, 0x7b, 0xd5, 0x2f, 0x46, 0xd3, 0x65, 0x15, 0x7b, 0xa9, 0x32, 0xef, 0x94, 0x64, 0x5d, 0x15, 0x65, 0xbd, 0x75, 0x55, 0x94, 0x42, 0xed, 0x3a, 0x78, 0x1e, 0x07, 0x6e, 0x96, 0x25, 0xe7, 0xd8, 0x43, 0x15, 0x7b, 0xa8, 0xaa, 0xb3, 0x07, 0xf3, 0x9e, 0x4d, 0x37, 0x51, 0xd7, 0x07, 0xa9, 0xeb, 0x73, 0xd4, 0x75, 0x06, 0x35, 0x37, 0xcf, 0x60, 0x30, 0xaf, 0x85, 0xa4, 0x5a, 0x57, 0xe7, 0x0a, 0x2c, 0x35, 0xfb, 0x72, 0x91, 0x2f, 0x9a, 0x57, 0x58, 0xda, 0x4a, 0x79, 0x5b, 0xed, 0xe6, 0x35, 0xd8, 0xcc, 0x6b, 0x33, 0x85, 0xd7, 0xe5, 0xe5, 0xec, 0x9f, 0xa4, 0x76, 0xdb, 0xcf, 0x51, 0x3b, 0x55, 0x5a, 0x9d, 0xda, 0x99, 0x57, 0xa1, 0xd9, 0xca, 0xa7, 0x37, 0xf2, 0xe9, 0xad, 0xea, 0xd7, 0x34, 0xa5, 0x01, 0xd7, 0x7d, 0xda, 0xc7, 0xbb, 0x3e, 0xde, 0x2d, 0xe1, 0xdd, 0xfd, 0xe6, 0x75, 0x9f, 0x28, 0xfb, 0x30, 0xe5, 0x1e, 0xa7, 0xdc, 0xe3, 0x35, 0xd7, 0x7f, 0xa2, 0x9c, 0x7f, 0x9f, 0xf1, 0xc9, 0x5d, 0xd6, 0x15, 0xa3, 0xf6, 0xf2, 0xc9, 0x3d, 0xd6, 0x27, 0x8b, 0xff, 0xd2, 0x27, 0xcf, 0xb8, 0xd6, 0x14, 0x9f, 0xdc, 0x73, 0xf6, 0x4f, 0xfe, 0xe3, 0x2b, 0x55, 0xfd, 0xd1, 0x95, 0x9e, 0xea, 0xd6, 0xa6, 0xc0, 0xba, 0x4a, 0xcf, 0x7e, 0xca, 0x2b, 0xb2, 0x6a, 0x73, 0xe8, 0xac, 0xb5, 0xf9, 0x7b, 0xd7, 0x00, 0xfd, 0x6b, 0x77, 0xbd, 0x98, 0x23, 0xf3, 0xa9, 0xcb, 0x4e, 0x46, 0x80, 0x93, 0xb8, 0x3c, 0xc9, 0x08, 0x68, 0x4a, 0x6c, 0x9e, 0x64, 0x04, 0x34, 0x65, 0xac, 0xf7, 0x61, 0x14, 0x74, 0x23, 0xb2, 0xba, 0x12, 0x59, 0x37, 0x12, 0x59, 0x37, 0x52, 0xbf, 0x1c, 0x46, 0x41, 0x38, 0xa3, 0x20, 0x9c, 0x51, 0x90, 0x60, 0x45, 0x41, 0x19, 0xb5, 0x2a, 0x53, 0x47, 0x0e, 0x02, 0xef, 0x7a, 0x51, 0x3d, 0xd6, 0xeb, 0xdf, 0xf9, 0xa2, 0x2d, 0x63, 0xbd, 0xad, 0x35, 0xd6, 0xff, 0xda, 0x9d, 0x2f, 0xfe, 0xe1, 0xdd, 0x2e, 0x88, 0x40, 0xf3, 0x57, 0x56, 0x73, 0x6b, 0x5b, 0xe9, 0xa1, 0x95, 0x0d, 0x68, 0x65, 0x29, 0xad, 0x0c, 0xa6, 0x95, 0xa5, 0xb4, 0x32, 0x98, 0x16, 0x46, 0xd1, 0xc2, 0x18, 0x5a, 0x18, 0x4b, 0x0b, 0x63, 0x69, 0x61, 0x3e, 0x2d, 0x6c, 0x44, 0x0b, 0x1b, 0xd1, 0xc2, 0x98, 0x33, 0x5a, 0x58, 0x1d, 0xe7, 0x1b, 0xe9, 0x95, 0x8d, 0x56, 0x9c, 0x57, 0xef, 0xa5, 0xa9, 0x75, 0x9d, 0xb4, 0x4a, 0xc6, 0xdd, 0xaf, 0xd6, 0x35, 0xd2, 0xb6, 0xd2, 0xbf, 0x4b, 0xad, 0x6b, 0x38, 0x7d, 0x6f, 0x5d, 0x23, 0x2d, 0x87, 0x71, 0x57, 0x15, 0x1c, 0x2a, 0x4f, 0x07, 0x8f, 0x90, 0x47, 0xcd, 0xb3, 0x8a, 0x6a, 0x4a, 0x23, 0x76, 0x8a, 0x88, 0x9d, 0x2a, 0x75, 0xed, 0xa4, 0xfc, 0x3f, 0x2a, 0x81, 0x08, 0xd9, 0xf2, 0x97, 0xeb, 0x65, 0xfe, 0xb6, 0x69, 0x37, 0x31, 0xb7, 0x8e, 0xbc, 0x72, 0xbb, 0xba, 0x23, 0xdb, 0x68, 0xa1, 0xd7, 0xfc, 0xc2, 0x85, 0x16, 0xea, 0x94, 0xe2, 0xa5, 0x14, 0xaf, 0x75, 0x0d, 0xa7, 0x53, 0x44, 0xd6, 0x58, 0x22, 0xab, 0xbf, 0xbe, 0x12, 0x27, 0xb8, 0x97, 0x79, 0xb3, 0xa5, 0xe8, 0x43, 0x94, 0x35, 0x43, 0x5d, 0x3a, 0xa3, 0x2e, 0xbd, 0x51, 0x97, 0xb6, 0xa8, 0x4b, 0x0f, 0xa2, 0x6e, 0x0c, 0x51, 0x37, 0xc2, 0xe8, 0x20, 0x92, 0x89, 0xbc, 0x1f, 0x89, 0xbc, 0x48, 0xe3, 0x46, 0xa1, 0x13, 0x7d, 0x23, 0x1d, 0x6e, 0x11, 0x5f, 0xef, 0x8a, 0x53, 0xe7, 0x13, 0x8f, 0x89, 0xc4, 0x63, 0x22, 0xf1, 0x98, 0x48, 0x3c, 0x26, 0x12, 0x8f, 0x89, 0xd6, 0xb5, 0xb1, 0x13, 0x89, 0xc7, 0x44, 0x75, 0xaf, 0xb9, 0xba, 0xd7, 0xc6, 0x4e, 0x24, 0x96, 0x12, 0x89, 0xa5, 0x44, 0xe2, 0x28, 0x91, 0x38, 0x4a, 0x24, 0x8e, 0x12, 0x89, 0xa3, 0x44, 0x62, 0x27, 0x51, 0x7f, 0x43, 0xd8, 0x89, 0x9f, 0x44, 0xe2, 0x27, 0x91, 0xf8, 0x49, 0x24, 0x7e, 0x12, 0x89, 0x9f, 0x44, 0xe2, 0x27, 0x91, 0xf8, 0x49, 0x24, 0x7e, 0x12, 0x89, 0x9f, 0x44, 0xea, 0x3f, 0xd7, 0xaa, 0xfb, 0x10, 0xea, 0x9e, 0x41, 0xdd, 0xa7, 0x04, 0x5c, 0x17, 0xdb, 0x49, 0x7c, 0x25, 0x12, 0x5f, 0x89, 0xc4, 0x57, 0x22, 0xf1, 0x95, 0x48, 0x7c, 0x25, 0x12, 0x5f, 0x89, 0xc4, 0x57, 0x22, 0xf1, 0x95, 0x48, 0x7c, 0x25, 0x12, 0x5f, 0x89, 0xc4, 0x57, 0x22, 0xf1, 0x95, 0x48, 0x7c, 0x25, 0x12, 0x5f, 0x89, 0xc6, 0x93, 0x70, 0x31, 0x5b, 0xb4, 0x36, 0x8e, 0x89, 0x86, 0xb4, 0xf7, 0x1a, 0xe1, 0xa8, 0x73, 0x2e, 0x55, 0x4b, 0xf5, 0xab, 0x40, 0x07, 0x7b, 0x8d, 0xb1, 0xce, 0x73, 0x8b, 0xa8, 0x3d, 0xb7, 0xea, 0xbf, 0xd1, 0x27, 0xcd, 0xe8, 0x13, 0x4d, 0x9d, 0x89, 0x93, 0x40, 0x1d, 0x12, 0x19, 0xf5, 0xad, 0x44, 0xec, 0x59, 0xfa, 0x44, 0xab, 0xd3, 0x27, 0x76, 0x35, 0xfb, 0x5c, 0xcc, 0x4c, 0x53, 0x1d, 0x3b, 0x07, 0xac, 0x3d, 0xfc, 0x46, 0xac, 0x6c, 0x87, 0xff, 0x31, 0xf2, 0xa0, 0x52, 0xaa, 0x03, 0xb5, 0xd7, 0x07, 0xe4, 0x15, 0xeb, 0xba, 0x80, 0xb5, 0x6d, 0x63, 0xdb, 0x6c, 0x75, 0xdc, 0xb1, 0x98, 0x2d, 0xb3, 0xd9, 0xf2, 0x47, 0xb6, 0xfc, 0xb8, 0xde, 0x15, 0xbe, 0x36, 0x11, 0x8d, 0xab, 0x89, 0xc6, 0xd5, 0xb4, 0xaa, 0xa7, 0xb0, 0x6b, 0x8c, 0x4c, 0x8d, 0x91, 0xa9, 0x0d, 0x16, 0x6d, 0x69, 0x6d, 0x02, 0xad, 0x4d, 0xa0, 0xb5, 0xf1, 0xe7, 0x3c, 0xff, 0x2c, 0x83, 0x78, 0x49, 0x21, 0x5e, 0x52, 0x88, 0x97, 0x14, 0xe2, 0x25, 0x85, 0x78, 0x49, 0x21, 0x5e, 0x52, 0x88, 0x97, 0x14, 0xe2, 0x25, 0x85, 0x78, 0x49, 0x11, 0xb3, 0x99, 0xa3, 0x99, 0x1b, 0x18, 0xd9, 0xdd, 0xb5, 0x74, 0xd1, 0x82, 0x91, 0xdd, 0x43, 0xeb, 0xcb, 0x73, 0x3f, 0x9e, 0x2f, 0xe2, 0xf9, 0x62, 0x71, 0x29, 0x7b, 0x1b, 0xc3, 0xde, 0xae, 0x61, 0x6f, 0x63, 0xd5, 0xef, 0x37, 0xaf, 0x13, 0xbd, 0x18, 0xdd, 0xbd, 0xe0, 0x79, 0x44, 0xbd, 0x78, 0x4b, 0x21, 0xde, 0x52, 0x88, 0xb7, 0x14, 0xe2, 0x2d, 0x85, 0x78, 0x4b, 0x21, 0xde, 0x52, 0x88, 0xb7, 0x14, 0xe6, 0xad, 0xde, 0xcc, 0x5b, 0xbd, 0x43, 0x8e, 0x89, 0x68, 0x62, 0x2f, 0x85, 0xb8, 0x4b, 0x21, 0xee, 0x52, 0x88, 0xbb, 0x14, 0xe2, 0x2e, 0x85, 0xb8, 0x4b, 0x21, 0xee, 0x52, 0x88, 0xbb, 0x14, 0xe2, 0x2e, 0x85, 0xb8, 0x4b, 0xa9, 0x77, 0xc7, 0x9e, 0x14, 0x62, 0x2c, 0x85, 0x18, 0x4b, 0x21, 0xc6, 0x52, 0x88, 0xb1, 0x14, 0x62, 0x2c, 0x85, 0x18, 0x4b, 0x21, 0xc6, 0x52, 0x88, 0xb1, 0x14, 0x62, 0x2c, 0x85, 0x18, 0x4b, 0x21, 0xc6, 0x52, 0x88, 0xb1, 0x14, 0x62, 0x2c, 0x85, 0x18, 0x1b, 0x40, 0x8c, 0xb5, 0x25, 0xc6, 0xc2, 0xd5, 0x8c, 0x9e, 0x00, 0x23, 0x09, 0x30, 0x92, 0x00, 0x23, 0x09, 0x30, 0x92, 0x00, 0x23, 0x09, 0x30, 0x92, 0x00, 0x23, 0x09, 0x30, 0x92, 0x00, 0x23, 0x8d, 0x71, 0x1b, 0x23, 0x60, 0x25, 0x42, 0xeb, 0x81, 0x23, 0x4b, 0x63, 0x4e, 0x4b, 0x17, 0x17, 0xc1, 0x4c, 0x38, 0xcc, 0x5c, 0x04, 0x33, 0xe1, 0x30, 0xd3, 0xcc, 0xfa, 0x8e, 0x2d, 0x12, 0x46, 0xa2, 0x61, 0xa4, 0x37, 0x8c, 0xf4, 0x86, 0x91, 0x58, 0xed, 0x16, 0xd1, 0xba, 0x1e, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x01, 0x2b, 0x19, 0xb0, 0x12, 0x01, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x50, 0x8f, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x10, 0x97, 0xc3, 0x84, 0x13, 0x26, 0x9c, 0x30, 0xe1, 0x54, 0xdf, 0xa1, 0xdd, 0x48, 0xab, 0x6f, 0x02, 0x37, 0x83, 0x5b, 0xc1, 0x6d, 0xca, 0xdf, 0x3a, 0x61, 0xc7, 0x09, 0x3b, 0x4e, 0xd8, 0x71, 0xc2, 0x8e, 0x13, 0x76, 0x5a, 0xc2, 0x8e, 0xc9, 0x4c, 0xaa, 0x15, 0x33, 0x51, 0x30, 0x93, 0x0c, 0x33, 0x51, 0x30, 0x93, 0x6c, 0xe9, 0x5d, 0x3a, 0xf1, 0xd2, 0x9f, 0x78, 0x49, 0x87, 0x9d, 0xd6, 0xb0, 0x93, 0x0a, 0x3b, 0xa9, 0xb0, 0x33, 0x00, 0x76, 0x1a, 0xd4, 0x63, 0xc7, 0x09, 0x3b, 0x4e, 0xd8, 0x71, 0xc2, 0x8e, 0x13, 0x76, 0x9c, 0xb0, 0xe3, 0x84, 0x1d, 0x27, 0xec, 0x74, 0x80, 0x9d, 0x0e, 0x8c, 0xdd, 0xc6, 0xb0, 0xe3, 0x84, 0x1d, 0x27, 0xec, 0x38, 0x61, 0xc7, 0x09, 0x3b, 0x4e, 0xd8, 0x71, 0xc2, 0x8e, 0x13, 0x76, 0x9c, 0xb0, 0xe3, 0x84, 0x1d, 0xa7, 0x11, 0xc9, 0xd8, 0x8e, 0x06, 0x2d, 0xd4, 0x18, 0x6f, 0x6c, 0xc4, 0x82, 0x38, 0x10, 0x0f, 0x4c, 0xe6, 0x12, 0x79, 0x6e, 0x05, 0xda, 0x80, 0x64, 0x60, 0x32, 0xd9, 0x81, 0x67, 0x93, 0xcd, 0x4e, 0x3c, 0x77, 0x51, 0xde, 0xd8, 0x09, 0xab, 0x4e, 0x58, 0x75, 0xc2, 0xaa, 0x13, 0x56, 0x9d, 0xb0, 0xea, 0x84, 0x55, 0x27, 0xac, 0x3a, 0x61, 0xd5, 0x09, 0xab, 0x4e, 0x58, 0x75, 0xc2, 0xaa, 0x13, 0x56, 0x9d, 0xb0, 0xea, 0x54, 0xac, 0x3a, 0x60, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x75, 0xc0, 0x6a, 0x14, 0xac, 0x46, 0xc1, 0x6a, 0x14, 0xac, 0x46, 0xc1, 0x6a, 0x14, 0xac, 0x3a, 0x60, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x75, 0xc0, 0xaa, 0x03, 0x56, 0x9b, 0xc3, 0x6a, 0x07, 0x58, 0xed, 0x06, 0xab, 0xad, 0x60, 0xb5, 0x03, 0xac, 0x36, 0x81, 0xd5, 0x0e, 0xb0, 0xda, 0x04, 0x56, 0xcd, 0xdf, 0x3b, 0xa6, 0xc2, 0x6a, 0x5b, 0x58, 0x6d, 0x0b, 0xab, 0x3d, 0x60, 0x35, 0x19, 0x56, 0x93, 0x61, 0xb5, 0x17, 0xac, 0x46, 0xd7, 0x63, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x75, 0xc0, 0xaa, 0x03, 0x56, 0x1d, 0xb0, 0xea, 0x80, 0x55, 0x07, 0xac, 0xa6, 0xc1, 0x6a, 0x9a, 0xe9, 0x7f, 0x60, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x75, 0xc0, 0xaa, 0x03, 0x56, 0x1d, 0xb0, 0xea, 0x80, 0x55, 0x07, 0xac, 0x3a, 0x60, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x8d, 0x82, 0xd5, 0x28, 0x58, 0x8d, 0xaa, 0xfe, 0x4d, 0x33, 0xa8, 0xfe, 0xd5, 0x76, 0x94, 0xc5, 0xaa, 0xf9, 0x7d, 0x79, 0x14, 0xac, 0x46, 0xc1, 0x6a, 0x94, 0xc5, 0x6a, 0x94, 0xc5, 0x6a, 0x14, 0xac, 0x46, 0xc1, 0xaa, 0x03, 0x56, 0x1d, 0xb0, 0xea, 0x80, 0x55, 0x07, 0xac, 0x3a, 0x60, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x75, 0xc0, 0xaa, 0x03, 0x56, 0x1d, 0xb0, 0xea, 0x80, 0x55, 0x07, 0xac, 0x3a, 0x60, 0xd5, 0x61, 0xfa, 0x7f, 0xb8, 0x9d, 0x03, 0x2b, 0x3d, 0x44, 0x27, 0xd8, 0x89, 0x87, 0x9d, 0x58, 0xd8, 0x69, 0x08, 0x3b, 0xb1, 0xb0, 0xd3, 0x10, 0x76, 0x1a, 0xc1, 0x4e, 0x6b, 0xd8, 0x49, 0x86, 0x9d, 0x64, 0xd8, 0x49, 0x84, 0x9d, 0xa6, 0xb0, 0xd3, 0x14, 0x76, 0xda, 0xd4, 0x71, 0x7d, 0xdd, 0x45, 0x2b, 0x5a, 0xdf, 0xaa, 0xce, 0x4c, 0x69, 0xee, 0xa1, 0x09, 0x7b, 0x70, 0xb2, 0x87, 0x78, 0xf6, 0xd0, 0x96, 0x3d, 0x34, 0x61, 0x0f, 0x2d, 0xd9, 0x43, 0x13, 0xf6, 0xd0, 0x92, 0x3d, 0xa4, 0xb2, 0x87, 0x2e, 0xec, 0xa1, 0x07, 0x7b, 0xe8, 0x62, 0x8d, 0xf9, 0x58, 0xf6, 0x10, 0xcb, 0x1e, 0xce, 0xff, 0xf3, 0x3d, 0x68, 0x9d, 0x98, 0x47, 0x7b, 0x88, 0x0b, 0x28, 0x3d, 0x81, 0xd2, 0x5b, 0x51, 0x7a, 0x33, 0x4a, 0x6f, 0x45, 0xe9, 0xcd, 0xac, 0xde, 0x8d, 0xb4, 0x7e, 0xcd, 0xda, 0x8c, 0xd2, 0x93, 0x29, 0x3d, 0x92, 0xd2, 0x23, 0x29, 0x2d, 0x99, 0xd2, 0x92, 0xed, 0xe6, 0x7d, 0xaa, 0xba, 0x91, 0xad, 0x36, 0x17, 0xc3, 0x8c, 0x28, 0x61, 0x18, 0xad, 0xc5, 0x9b, 0x46, 0x92, 0x78, 0xda, 0xe8, 0x28, 0x0c, 0x87, 0x79, 0xd4, 0x3a, 0xfc, 0x9f, 0xc4, 0x4e, 0x4d, 0x2c, 0x50, 0xf2, 0x21, 0x4a, 0x8d, 0xa2, 0xd4, 0xa7, 0x29, 0xf5, 0x7e, 0x4a, 0x8d, 0x12, 0x57, 0x10, 0xcd, 0x71, 0x44, 0x73, 0x1c, 0xd1, 0x1c, 0x47, 0x34, 0xc7, 0x11, 0xcd, 0x06, 0xd1, 0x6c, 0x10, 0xcd, 0x06, 0xd1, 0x6c, 0x10, 0xcd, 0x06, 0xd1, 0x1c, 0x47, 0x34, 0xc7, 0x11, 0xcd, 0x71, 0x44, 0x73, 0x1c, 0xd1, 0x1c, 0x67, 0x46, 0x33, 0xb3, 0x67, 0x33, 0x6a, 0xd4, 0x84, 0x1a, 0xf5, 0xa4, 0x46, 0x91, 0x96, 0xae, 0x44, 0x5a, 0xba, 0x62, 0xd6, 0x6a, 0x30, 0xb5, 0xba, 0x84, 0x5a, 0x5d, 0x4a, 0xad, 0x46, 0x52, 0xab, 0x86, 0x01, 0xba, 0x72, 0x55, 0xbd, 0x68, 0x8e, 0x23, 0x9a, 0xe3, 0x88, 0xe6, 0x38, 0xa2, 0x39, 0x8e, 0x68, 0x8e, 0x23, 0x9a, 0xe3, 0x88, 0xe6, 0xb8, 0x80, 0x68, 0x36, 0x98, 0x45, 0x9b, 0x11, 0xd1, 0x71, 0x44, 0x74, 0x1c, 0x11, 0x1d, 0x47, 0x44, 0xc7, 0x11, 0xd1, 0x71, 0x44, 0x74, 0x1c, 0x11, 0x1d, 0x47, 0x44, 0xc7, 0x11, 0xd1, 0x71, 0x44, 0x74, 0x1c, 0x11, 0x6d, 0x10, 0xd1, 0x06, 0x11, 0x6d, 0x10, 0xd1, 0x06, 0x11, 0x6d, 0x10, 0xd1, 0x06, 0x11, 0x6d, 0x58, 0x11, 0x6d, 0x10, 0xd1, 0x06, 0x11, 0x6d, 0x10, 0xd1, 0x86, 0x15, 0xd1, 0x86, 0x15, 0xd1, 0x86, 0x79, 0x05, 0x07, 0x22, 0x3a, 0x8e, 0x88, 0x8e, 0x23, 0xa2, 0xe3, 0x88, 0xe8, 0x38, 0x22, 0x3a, 0x8e, 0x88, 0x8e, 0x23, 0xa2, 0xe3, 0x88, 0xe8, 0x38, 0x22, 0x3a, 0x8e, 0x88, 0x8e, 0x23, 0xa2, 0xe3, 0x88, 0xe8, 0x38, 0x22, 0x3a, 0x8e, 0x88, 0x8e, 0x13, 0x63, 0xeb, 0xdd, 0xe5, 0xa4, 0x25, 0xcc, 0xb6, 0x83, 0xd9, 0x76, 0x30, 0xdb, 0x0e, 0x66, 0xdb, 0xc1, 0x6c, 0x3b, 0xeb, 0xce, 0x27, 0x2d, 0x03, 0xef, 0x7c, 0x02, 0xb3, 0xa9, 0xc4, 0x69, 0x5f, 0xd8, 0x6d, 0x02, 0xbb, 0x17, 0x5a, 0x33, 0x70, 0x5f, 0xd8, 0x6d, 0x0d, 0xbb, 0x7d, 0x61, 0xb7, 0x35, 0xec, 0x36, 0x86, 0xdd, 0x14, 0xd8, 0x4d, 0x83, 0xdd, 0x34, 0xd8, 0xed, 0x09, 0xbb, 0x17, 0xc1, 0xee, 0xf9, 0xb0, 0x7b, 0x3e, 0xec, 0x0e, 0x67, 0xbe, 0x68, 0xff, 0x57, 0xef, 0xa2, 0x02, 0xc3, 0x43, 0x60, 0x78, 0x08, 0x0c, 0xb7, 0x83, 0xe1, 0x26, 0xea, 0xae, 0x2a, 0x7f, 0xe5, 0x8e, 0x2a, 0x91, 0xa2, 0x1d, 0x0c, 0xb7, 0x83, 0x61, 0xf3, 0x1c, 0x9b, 0x76, 0x30, 0xdc, 0x0e, 0x86, 0xdb, 0xc1, 0x70, 0x3b, 0x8b, 0xe1, 0x76, 0xea, 0x0e, 0x2b, 0x6d, 0x40, 0xb2, 0x75, 0xa7, 0x95, 0x0e, 0x3c, 0x57, 0x33, 0xdc, 0x0e, 0x86, 0xdb, 0xfd, 0xd3, 0x3b, 0xae, 0xa8, 0x9c, 0xb9, 0xa2, 0xe6, 0x1a, 0xab, 0x28, 0xff, 0x1c, 0xd1, 0x11, 0xbe, 0x62, 0xe1, 0x4b, 0x87, 0xaf, 0x54, 0xb8, 0x32, 0xc7, 0xb3, 0x03, 0x6e, 0xec, 0x70, 0xd3, 0x2a, 0x60, 0x2c, 0x27, 0xc3, 0x8d, 0xb3, 0xde, 0x78, 0x56, 0x19, 0x57, 0x9d, 0xf1, 0x6c, 0xce, 0x47, 0x8d, 0x29, 0xc9, 0x49, 0x49, 0xf5, 0xb7, 0xb6, 0xb3, 0xb5, 0xbd, 0xce, 0xd6, 0x36, 0x35, 0xbe, 0x92, 0x44, 0x03, 0xdc, 0xa3, 0x4d, 0x5d, 0x33, 0xf5, 0xec, 0x8e, 0x6d, 0x00, 0x71, 0x11, 0x43, 0x5c, 0xc4, 0x10, 0x17, 0x31, 0xc4, 0x45, 0x0c, 0x31, 0x10, 0x43, 0x0c, 0xc4, 0x10, 0x03, 0x31, 0xc4, 0x40, 0x0c, 0x31, 0x10, 0x63, 0xcd, 0x86, 0x43, 0x88, 0x81, 0xc6, 0xd6, 0x08, 0x8b, 0xa2, 0x26, 0x03, 0x89, 0x81, 0xc6, 0xd4, 0x66, 0x20, 0x31, 0xd0, 0x98, 0x18, 0x30, 0x8f, 0xbd, 0x34, 0xa5, 0x6d, 0xe1, 0xb4, 0xad, 0x27, 0x6d, 0xeb, 0x49, 0xdb, 0xa2, 0x68, 0x5b, 0x7c, 0xbd, 0x7e, 0x8f, 0xa1, 0xdf, 0x63, 0xe8, 0xf7, 0x18, 0xfa, 0x3d, 0x86, 0x7e, 0x8f, 0xa1, 0xdf, 0x63, 0xe8, 0xf7, 0x18, 0x5a, 0xd2, 0x8f, 0x96, 0xf4, 0xa3, 0xcf, 0x4d, 0xf5, 0x8d, 0xa1, 0xcf, 0x63, 0xe8, 0xf3, 0x18, 0xfa, 0x3c, 0x86, 0x3e, 0x8f, 0xa1, 0xcf, 0x63, 0xe8, 0xf3, 0x18, 0xfa, 0x3c, 0x86, 0x3e, 0x8f, 0xa1, 0xcf, 0x63, 0xea, 0x79, 0x93, 0x18, 0xfa, 0x2e, 0x86, 0xbe, 0x8b, 0xa1, 0xef, 0x62, 0xe8, 0xbb, 0x18, 0xfa, 0x2e, 0x86, 0xbe, 0x8b, 0xa1, 0xef, 0x62, 0xe8, 0xbb, 0x18, 0xfa, 0x2e, 0x86, 0xbe, 0x8b, 0xa1, 0xef, 0x62, 0xe8, 0xbb, 0x18, 0xfa, 0x2e, 0x46, 0x84, 0x89, 0x5e, 0xea, 0xae, 0xd7, 0xe6, 0x35, 0x72, 0x5d, 0xf4, 0xdf, 0x41, 0x75, 0x2c, 0xa7, 0x5c, 0x9e, 0xa2, 0xd6, 0xa7, 0x6c, 0x33, 0x44, 0x2b, 0xdc, 0xb6, 0xcf, 0x78, 0x5b, 0x34, 0xc3, 0x8f, 0x9b, 0x57, 0xac, 0x5d, 0xca, 0xc8, 0xc9, 0x91, 0x5b, 0xc4, 0x4f, 0xe4, 0x48, 0x3f, 0xcb, 0x0d, 0xc2, 0xa5, 0x8e, 0x19, 0xfc, 0x28, 0x76, 0x91, 0x09, 0xe6, 0xc9, 0x22, 0xeb, 0x97, 0x23, 0xf9, 0xa2, 0x90, 0xd7, 0x8a, 0x40, 0x09, 0xf0, 0xca, 0x1f, 0xc4, 0x01, 0xf6, 0x30, 0x10, 0x7f, 0x9e, 0x01, 0x2e, 0x26, 0x5f, 0x1c, 0x2a, 0x77, 0x68, 0xc3, 0xc1, 0x18, 0xb9, 0x8b, 0xbd, 0x1e, 0x0a, 0x5a, 0x28, 0x2b, 0x82, 0x3e, 0x94, 0x3f, 0x06, 0x7d, 0x24, 0x8b, 0x82, 0x3e, 0xe5, 0xf9, 0x33, 0xb0, 0x42, 0xfe, 0x18, 0xf2, 0xab, 0xfc, 0x51, 0xcf, 0x92, 0x3f, 0x1a, 0xd7, 0x82, 0xeb, 0xc0, 0xbd, 0xe0, 0x3e, 0x70, 0x3f, 0x98, 0x08, 0x26, 0x81, 0xc9, 0x60, 0x0a, 0x78, 0x40, 0xfe, 0xe8, 0xe8, 0x24, 0xb7, 0x38, 0x3a, 0x83, 0x2e, 0xa0, 0x2b, 0xe8, 0x06, 0xba, 0x83, 0x1e, 0x20, 0x0d, 0xf4, 0x04, 0xe7, 0x83, 0x0b, 0x40, 0x2f, 0xd0, 0x1b, 0xa4, 0x83, 0x3e, 0xa0, 0x2f, 0xe8, 0x07, 0xfa, 0x83, 0x01, 0x60, 0x20, 0x18, 0x04, 0x32, 0xc0, 0x85, 0x60, 0x30, 0xb8, 0x08, 0x0c, 0x01, 0x17, 0xcb, 0x03, 0x8e, 0xa1, 0x60, 0x18, 0x18, 0x0e, 0x46, 0x80, 0x4b, 0xc0, 0x48, 0x30, 0x0a, 0x8c, 0x06, 0xaf, 0xcb, 0x9d, 0x8e, 0x37, 0xc0, 0x9b, 0x60, 0x1e, 0x98, 0x0f, 0xde, 0x02, 0x6f, 0x83, 0x77, 0xc0, 0xbb, 0xe0, 0x3d, 0xf0, 0x3e, 0x58, 0x00, 0x3e, 0x00, 0x0b, 0xc1, 0x87, 0x60, 0x11, 0xf8, 0x08, 0x2c, 0x06, 0x1f, 0x83, 0x25, 0xe0, 0x13, 0xf0, 0x29, 0x58, 0x0a, 0x3e, 0x03, 0x7b, 0xe4, 0x0e, 0xa2, 0x9c, 0xdc, 0xec, 0x6f, 0xdf, 0xef, 0xe7, 0xef, 0xdf, 0x21, 0xa8, 0xa1, 0xfa, 0xc4, 0x18, 0x3c, 0xc1, 0xe5, 0x28, 0xdd, 0xdf, 0xf9, 0x64, 0x13, 0x72, 0xa9, 0x04, 0x72, 0xa9, 0x04, 0x55, 0xc2, 0x68, 0xc6, 0xff, 0x18, 0xc6, 0xc5, 0xe5, 0x22, 0xe6, 0x6f, 0x95, 0xd2, 0xb8, 0x4e, 0x29, 0x63, 0x44, 0x1f, 0x4a, 0xb8, 0xe0, 0x6f, 0x95, 0x60, 0x32, 0xb5, 0xf8, 0x6f, 0xb7, 0xbb, 0xc1, 0x9f, 0xe5, 0x8d, 0x41, 0x47, 0x64, 0x61, 0x50, 0x85, 0x2c, 0x34, 0xaf, 0xd9, 0x5e, 0x9b, 0x43, 0xfe, 0xb7, 0xaf, 0x27, 0x7d, 0xae, 0x39, 0xec, 0x6c, 0x57, 0x8f, 0x5e, 0x65, 0x5d, 0x3d, 0xfa, 0x47, 0xf6, 0x95, 0xfd, 0x8f, 0xae, 0x1e, 0xdd, 0xb2, 0x5e, 0xa9, 0xfb, 0xb5, 0x74, 0x6a, 0xdc, 0x87, 0xb9, 0xa1, 0x2f, 0xcf, 0xfd, 0xd4, 0x15, 0x39, 0x4c, 0x1f, 0x15, 0x42, 0x6f, 0xda, 0x7e, 0x57, 0x3d, 0xf6, 0x74, 0x9d, 0x2c, 0xd7, 0xc6, 0xa9, 0xfb, 0x99, 0xca, 0x3f, 0xd8, 0xe3, 0x6f, 0xec, 0xf1, 0xb7, 0x3a, 0x7b, 0x8c, 0xac, 0xb7, 0xc7, 0x42, 0xda, 0xe1, 0xa5, 0x1d, 0x65, 0xd6, 0xb5, 0x0a, 0x8f, 0x58, 0xa5, 0x17, 0x52, 0x7a, 0xa1, 0x3a, 0xe6, 0x7a, 0xee, 0xd2, 0x5d, 0x94, 0xee, 0xaa, 0x53, 0xba, 0x79, 0x4f, 0x80, 0x3d, 0xf4, 0xe3, 0x1e, 0xad, 0xa3, 0xf4, 0x6b, 0x9d, 0xe5, 0xbe, 0x3f, 0xba, 0xd6, 0x36, 0xfd, 0x78, 0x24, 0x38, 0x54, 0x1e, 0x0e, 0x1e, 0x21, 0xf7, 0x05, 0x8f, 0x94, 0xbf, 0x04, 0x8f, 0x96, 0x85, 0x35, 0x2c, 0xe1, 0x0b, 0x0b, 0x50, 0x97, 0x8a, 0xff, 0xd1, 0x55, 0xbb, 0x6b, 0x58, 0xf0, 0xd6, 0x96, 0x9a, 0xce, 0xd8, 0xee, 0xab, 0xae, 0xda, 0xf8, 0x77, 0x4b, 0xdf, 0x4e, 0xe9, 0xdb, 0xeb, 0x94, 0xde, 0x9c, 0xd2, 0x8b, 0x28, 0x3d, 0x8f, 0xd2, 0xbf, 0xa7, 0xf4, 0x1f, 0xb4, 0xc1, 0xc4, 0x5d, 0x75, 0x44, 0xd7, 0x5e, 0xcf, 0xdd, 0x2a, 0x79, 0x23, 0x25, 0xff, 0x42, 0xc9, 0x25, 0x94, 0x5c, 0x62, 0x1d, 0x2f, 0x37, 0xaf, 0xdd, 0xe0, 0xa3, 0xe4, 0x55, 0x94, 0xbc, 0x8a, 0x92, 0x4f, 0x52, 0xb2, 0x79, 0x6c, 0x65, 0x6a, 0x9d, 0xab, 0xc2, 0x9b, 0xd7, 0x93, 0x5f, 0xa7, 0x1c, 0x41, 0xed, 0xf5, 0xb9, 0x14, 0xff, 0x1e, 0xf8, 0xf7, 0xaa, 0xeb, 0x6d, 0x75, 0x96, 0x07, 0xa8, 0xc1, 0x06, 0x6a, 0x60, 0x2a, 0xc1, 0x92, 0xc0, 0xeb, 0xd0, 0xb3, 0xf7, 0x9f, 0xac, 0x7b, 0x32, 0x54, 0x58, 0xfc, 0x17, 0xc2, 0xbf, 0xd9, 0x9e, 0x6c, 0xf6, 0x9a, 0x6d, 0xdd, 0x93, 0xa1, 0x8a, 0xb6, 0xd4, 0x5c, 0xed, 0x9d, 0xdc, 0x46, 0x5d, 0xf1, 0x9d, 0x2c, 0x11, 0x2d, 0x76, 0xaa, 0x2b, 0xbe, 0x5f, 0x4c, 0x46, 0x52, 0x7d, 0x06, 0x4a, 0x38, 0x91, 0x19, 0x1e, 0xf0, 0x3d, 0x48, 0x72, 0xfd, 0x2b, 0xc0, 0xd7, 0x5c, 0x79, 0xa8, 0xf6, 0x4a, 0xf0, 0x91, 0x01, 0x25, 0x87, 0x59, 0x25, 0xdb, 0x29, 0xd9, 0x6e, 0x95, 0x1c, 0x4e, 0xc9, 0x61, 0x94, 0xec, 0x34, 0xaf, 0x84, 0x43, 0xc9, 0x06, 0x25, 0x1b, 0xea, 0x48, 0xc4, 0x99, 0x25, 0xeb, 0x94, 0xac, 0x1b, 0x4f, 0x8a, 0xc6, 0xb5, 0xa5, 0x37, 0xa8, 0x73, 0x95, 0xfa, 0xea, 0xab, 0xd3, 0x17, 0xd0, 0xfa, 0x5f, 0x68, 0xfd, 0xbe, 0x73, 0x5e, 0x9d, 0x3e, 0x84, 0xad, 0x77, 0x6b, 0xbd, 0x40, 0x6f, 0x50, 0x7d, 0x5d, 0xcd, 0xa3, 0x67, 0x2d, 0xeb, 0xcf, 0xaf, 0x74, 0xdf, 0x10, 0xee, 0xf7, 0xc3, 0xbd, 0x79, 0xa5, 0xaf, 0x9f, 0xf9, 0x44, 0x3e, 0x9f, 0xd8, 0xcd, 0x27, 0x3c, 0x70, 0x5f, 0x61, 0x1d, 0x39, 0x2b, 0xb5, 0x7a, 0xdd, 0xbc, 0x06, 0x67, 0x3e, 0xbc, 0xe7, 0xf3, 0xc9, 0x65, 0xaa, 0x17, 0xab, 0xe8, 0xc1, 0xca, 0xda, 0x12, 0xd2, 0x59, 0xef, 0xc3, 0x88, 0xea, 0xcb, 0x73, 0x3f, 0x9e, 0xab, 0xe7, 0x1e, 0x53, 0xd3, 0xd7, 0x59, 0xf3, 0x5c, 0x6d, 0x09, 0x86, 0x97, 0x6c, 0xaa, 0x14, 0x94, 0x83, 0x23, 0x80, 0xf9, 0xae, 0xb6, 0x2e, 0x27, 0xf8, 0xe4, 0x09, 0x78, 0x0d, 0xe2, 0x93, 0xb2, 0xe6, 0x93, 0xd6, 0xd9, 0xee, 0x81, 0x75, 0x39, 0x48, 0x49, 0x07, 0x55, 0x5d, 0x02, 0x5b, 0xb1, 0xd9, 0xda, 0x67, 0xee, 0xef, 0x9f, 0x24, 0xc6, 0xcf, 0xd5, 0x8a, 0xc0, 0x4f, 0x9e, 0x51, 0xdb, 0x3f, 0xfc, 0x64, 0x67, 0xeb, 0x8e, 0x78, 0xe9, 0x38, 0xa0, 0x74, 0x72, 0x87, 0x74, 0x72, 0x87, 0x74, 0x72, 0x87, 0x74, 0x3c, 0x62, 0x3f, 0x3c, 0xe2, 0x30, 0x3c, 0xa2, 0x99, 0x35, 0x8f, 0x25, 0x72, 0x86, 0xe2, 0x11, 0x47, 0x10, 0x39, 0x7d, 0xf0, 0x88, 0x23, 0x88, 0x9e, 0x3e, 0xcc, 0x98, 0x89, 0x44, 0x4f, 0x5f, 0x5a, 0x39, 0xc2, 0xca, 0xc2, 0x46, 0xe3, 0x17, 0xaf, 0x20, 0x82, 0x2e, 0x26, 0x82, 0x2e, 0xa6, 0xb5, 0xd7, 0xe2, 0x17, 0x7b, 0xa9, 0x3b, 0xeb, 0x75, 0x17, 0x63, 0x89, 0x9c, 0xb1, 0x68, 0x53, 0x3a, 0x7e, 0xb0, 0x15, 0xde, 0x3e, 0x1d, 0x6f, 0x9f, 0x8e, 0xb7, 0x4f, 0xc7, 0xdb, 0xdf, 0x61, 0xdd, 0x59, 0x2f, 0x1d, 0x6f, 0x9f, 0x8e, 0x1a, 0xa4, 0xe3, 0xed, 0xd3, 0xf1, 0xf6, 0xe9, 0x78, 0xfb, 0x74, 0x34, 0x2c, 0x1d, 0x75, 0x48, 0xc7, 0xdb, 0xa7, 0xa3, 0x10, 0xe9, 0x78, 0xfb, 0x74, 0xb4, 0x2c, 0x5d, 0xdd, 0x6b, 0xe0, 0xcc, 0xbb, 0xfc, 0x85, 0xd6, 0xbc, 0x42, 0x9d, 0x5a, 0x51, 0xa7, 0xb8, 0xda, 0x77, 0xd6, 0xa9, 0xd6, 0xf6, 0x61, 0x3c, 0x90, 0x47, 0xd2, 0xbe, 0x48, 0xd1, 0x1f, 0x0c, 0x00, 0x03, 0xc1, 0x20, 0x90, 0x01, 0x2e, 0x04, 0x83, 0xc1, 0x45, 0x60, 0x08, 0xb8, 0x18, 0x0c, 0x05, 0xc3, 0xc0, 0x70, 0x30, 0x02, 0x5c, 0x02, 0x46, 0x82, 0x51, 0x80, 0xbc, 0x5b, 0x5c, 0x0a, 0xc6, 0x80, 0xcb, 0xc0, 0xe5, 0xe0, 0x0a, 0x70, 0x25, 0x18, 0x0b, 0xae, 0x02, 0x57, 0x83, 0x6b, 0xc0, 0xb5, 0xe0, 0x46, 0xf6, 0x72, 0x13, 0xb8, 0x19, 0xdc, 0x0a, 0x6e, 0x03, 0x8f, 0xf0, 0xfa, 0x34, 0x14, 0xf6, 0x51, 0xf0, 0x18, 0x78, 0x1c, 0x4c, 0x07, 0x4f, 0x80, 0x27, 0xc1, 0x53, 0xe0, 0x69, 0xf0, 0x0c, 0x98, 0x29, 0x3a, 0x89, 0xe7, 0xc8, 0xee, 0x9e, 0x17, 0xed, 0xc5, 0xbf, 0x44, 0x57, 0x7a, 0xaa, 0x3d, 0x3d, 0xf5, 0x08, 0x3d, 0x75, 0x3e, 0x3d, 0x35, 0x9c, 0x9e, 0x1a, 0x41, 0x4f, 0x4d, 0xa1, 0xa7, 0x7a, 0xd2, 0x53, 0x53, 0xe8, 0xa9, 0x9e, 0xf4, 0x54, 0x4f, 0x7a, 0xea, 0x7c, 0x7a, 0x2a, 0x03, 0x56, 0x22, 0x61, 0x25, 0x9a, 0x9e, 0x1a, 0x4e, 0x4f, 0x5d, 0x4f, 0x4f, 0x5d, 0x4f, 0x4f, 0x8d, 0xa2, 0xa7, 0x46, 0xd9, 0xd8, 0xb7, 0x8d, 0x7d, 0x9b, 0x8c, 0xd9, 0xe6, 0x8a, 0xae, 0xb6, 0xf7, 0x44, 0xa4, 0xed, 0x7d, 0xb0, 0x00, 0x7c, 0x00, 0x16, 0x82, 0x0f, 0xc1, 0x22, 0xf0, 0x11, 0x58, 0x0c, 0x3e, 0x06, 0x4b, 0xf8, 0xcc, 0x27, 0xa2, 0x19, 0xbd, 0xfc, 0x20, 0xbd, 0xfc, 0x60, 0xc8, 0x77, 0x22, 0x32, 0x64, 0x0d, 0xf8, 0x41, 0x64, 0x84, 0x6c, 0xe1, 0x79, 0x2b, 0xd8, 0x06, 0xb6, 0x83, 0x1c, 0x70, 0x4c, 0x9c, 0x1f, 0x8a, 0x33, 0xd1, 0x7d, 0x22, 0x52, 0x3f, 0x04, 0x0e, 0x83, 0x72, 0x70, 0x04, 0x54, 0x80, 0xa3, 0xe0, 0x18, 0xf0, 0x83, 0x4a, 0xf0, 0x2b, 0x38, 0x0e, 0x4e, 0x80, 0x2a, 0x70, 0x12, 0x9c, 0x02, 0xbf, 0x81, 0xd3, 0x22, 0x92, 0x68, 0xca, 0x20, 0x9a, 0x32, 0x88, 0xa6, 0x8c, 0xea, 0x33, 0x63, 0x41, 0x1c, 0x88, 0x07, 0x09, 0xa0, 0xfa, 0x3b, 0x87, 0x0c, 0xa2, 0x29, 0x83, 0x68, 0xca, 0x20, 0x9a, 0x32, 0x88, 0xa6, 0x0c, 0xa2, 0x29, 0x83, 0x68, 0xca, 0x20, 0x9a, 0x32, 0x8c, 0xd7, 0x71, 0x4a, 0x6f, 0x80, 0x37, 0xc1, 0x3c, 0x30, 0x1f, 0xbc, 0x05, 0xc8, 0x13, 0x8c, 0x77, 0xc0, 0xbb, 0xe0, 0x3d, 0xf0, 0x3e, 0x58, 0x00, 0x3e, 0x00, 0x0b, 0xc1, 0x87, 0x60, 0x11, 0xf8, 0x08, 0x2c, 0x06, 0x1f, 0x03, 0xf8, 0x30, 0xe0, 0xc3, 0xf8, 0x14, 0x2c, 0x05, 0x9f, 0x81, 0x65, 0x60, 0x39, 0xf8, 0x37, 0x58, 0x01, 0x3e, 0x07, 0x5f, 0x80, 0x2f, 0xc1, 0x57, 0x60, 0x25, 0xf8, 0x1a, 0x7c, 0x03, 0x32, 0xc1, 0x7f, 0xc0, 0xb7, 0x60, 0x15, 0x70, 0x89, 0x4e, 0xc6, 0x4e, 0xb0, 0x8b, 0x8c, 0x36, 0x0f, 0xec, 0x06, 0x1e, 0xb0, 0x07, 0xec, 0x05, 0xf9, 0xa0, 0x40, 0xb4, 0x37, 0xf6, 0x81, 0x22, 0xd1, 0xd5, 0x28, 0x06, 0xfb, 0x41, 0x09, 0x28, 0x03, 0x07, 0x80, 0x0f, 0x1c, 0x04, 0x87, 0xc0, 0x61, 0xd1, 0x55, 0x8d, 0x08, 0xbd, 0x8e, 0x0b, 0x8b, 0x13, 0x71, 0xb0, 0xa5, 0x05, 0x7c, 0x33, 0xd3, 0x5a, 0x5c, 0xc0, 0x1c, 0x91, 0x4a, 0x46, 0x99, 0x4a, 0x46, 0x99, 0x4a, 0x46, 0x99, 0x4a, 0x46, 0x99, 0x6a, 0x7d, 0x67, 0x94, 0x4a, 0x46, 0x99, 0x7a, 0x96, 0xef, 0x8c, 0x52, 0xc9, 0x08, 0x53, 0xc9, 0x08, 0x53, 0xc9, 0x08, 0x53, 0xc9, 0x08, 0x53, 0xc9, 0x08, 0x53, 0xc9, 0x08, 0x53, 0xc9, 0x02, 0x53, 0xf5, 0x37, 0x44, 0x43, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0xfa, 0x71, 0x3e, 0x35, 0x6a, 0x40, 0x1f, 0xf6, 0xa4, 0x56, 0x23, 0xe9, 0xc3, 0xe1, 0xf4, 0xe1, 0xb4, 0x80, 0xda, 0x35, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x45, 0xbf, 0x06, 0x1a, 0xb3, 0xe1, 0xec, 0x98, 0x88, 0x40, 0x45, 0x6e, 0x54, 0xdf, 0x8c, 0x05, 0x1e, 0xd7, 0x7f, 0xc4, 0x3a, 0xae, 0xaf, 0x05, 0x1c, 0xd7, 0x77, 0xfe, 0xd5, 0x63, 0xf2, 0xea, 0x38, 0xfc, 0x3c, 0xd1, 0x24, 0xf0, 0x58, 0xbc, 0x2e, 0x85, 0x71, 0xce, 0xe3, 0xf1, 0xb1, 0xea, 0x9b, 0xa4, 0xaf, 0x68, 0xe9, 0xbf, 0x68, 0xe9, 0x2d, 0xb4, 0xf4, 0x6e, 0x5a, 0xda, 0x29, 0xa0, 0xa5, 0xff, 0xf9, 0xa7, 0xc7, 0xe9, 0x8d, 0x59, 0xa2, 0x23, 0x2d, 0x1c, 0xa0, 0xce, 0x3d, 0x09, 0x3c, 0x5e, 0x7f, 0x2d, 0xad, 0x8e, 0x66, 0x1e, 0x8a, 0xb6, 0xee, 0x83, 0x1a, 0x6d, 0xde, 0x07, 0xd5, 0x3a, 0x5e, 0xaf, 0x05, 0x1c, 0xaf, 0xd7, 0xfe, 0xf2, 0xb1, 0xf6, 0x1f, 0xd4, 0xf7, 0x32, 0x7f, 0xf5, 0x18, 0x7b, 0x34, 0xe3, 0x35, 0x9a, 0xf1, 0x1a, 0x4d, 0x3f, 0xf7, 0xb3, 0x66, 0xff, 0x5b, 0x60, 0x60, 0x2a, 0x0c, 0x4c, 0x82, 0x81, 0x47, 0x60, 0xe0, 0x1a, 0xc6, 0x6b, 0x34, 0xe3, 0x35, 0xda, 0x3a, 0xb2, 0x13, 0x6d, 0x1d, 0xd9, 0x89, 0x86, 0x91, 0x3b, 0xff, 0xe9, 0x31, 0x76, 0xd8, 0xe8, 0x25, 0x1a, 0xc2, 0xc0, 0x45, 0xf5, 0xbe, 0x2b, 0xec, 0x4d, 0x0d, 0xee, 0xa6, 0x06, 0xcf, 0x53, 0x83, 0x47, 0xa8, 0xc1, 0xb3, 0xd4, 0x60, 0x52, 0x40, 0x1f, 0xdc, 0xcf, 0x27, 0x7b, 0xa2, 0x0c, 0x9d, 0x45, 0x18, 0x3c, 0x86, 0xc1, 0x63, 0x18, 0xa5, 0x0c, 0x83, 0xc7, 0x26, 0xf0, 0xd8, 0xc4, 0xe2, 0xb1, 0x89, 0xc5, 0x63, 0x98, 0xc5, 0x63, 0x18, 0x3c, 0x86, 0x9d, 0x85, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x34, 0x8f, 0x90, 0x99, 0xf7, 0x1c, 0x0e, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x26, 0xf0, 0xd8, 0x04, 0x1e, 0x9b, 0xd0, 0x82, 0x07, 0x2c, 0x1e, 0xcf, 0xa7, 0x15, 0xf7, 0x59, 0x3c, 0x3e, 0x4f, 0x2b, 0x66, 0xc2, 0xa3, 0x79, 0xdd, 0xcb, 0x26, 0x16, 0x8f, 0x4d, 0x2c, 0x1e, 0xcd, 0x2b, 0x08, 0xf4, 0x81, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x63, 0x0c, 0x75, 0x66, 0x0c, 0x45, 0x31, 0x86, 0xec, 0x30, 0x33, 0x46, 0x9d, 0xd1, 0x34, 0xa0, 0x1e, 0xa7, 0x77, 0x51, 0x9b, 0x4e, 0xd4, 0xe6, 0x6a, 0x6a, 0x73, 0x0d, 0xb5, 0x99, 0x54, 0xef, 0x9b, 0x5f, 0xf3, 0xfe, 0xbf, 0x6d, 0x28, 0xc5, 0xf4, 0xaa, 0x43, 0x14, 0xb7, 0xf1, 0x70, 0x1b, 0x0f, 0xb7, 0xf1, 0x94, 0xd6, 0x0e, 0x6e, 0x1d, 0x70, 0xeb, 0xb0, 0xb8, 0x75, 0x58, 0xdc, 0xc6, 0x5b, 0xdc, 0xc6, 0xc3, 0x6d, 0xfc, 0x59, 0xb8, 0x8d, 0x87, 0xdb, 0x78, 0xb8, 0x8d, 0x87, 0xdb, 0x78, 0xb8, 0x8d, 0x87, 0xdb, 0x78, 0xb8, 0x8d, 0x87, 0x5b, 0x07, 0xdc, 0xc6, 0xc3, 0x6d, 0x3c, 0xdc, 0xc6, 0xc3, 0x6d, 0x3c, 0xdc, 0xc6, 0xc3, 0x6d, 0x3c, 0xdc, 0xc6, 0xc3, 0x6d, 0x3c, 0xdc, 0xc6, 0xc3, 0x6d, 0x3c, 0xdc, 0x3a, 0xe0, 0xd6, 0x01, 0xb7, 0x0e, 0x5a, 0xf2, 0x9c, 0xc5, 0xad, 0x83, 0xd6, 0xf4, 0xa5, 0x35, 0x97, 0xd3, 0x9a, 0x31, 0xb4, 0xe6, 0x35, 0xb8, 0x75, 0xc0, 0xad, 0xc3, 0xe2, 0xd6, 0x61, 0x71, 0xeb, 0xa0, 0x75, 0xcd, 0xe0, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0x61, 0x65, 0x10, 0xac, 0xb4, 0x87, 0xdb, 0x16, 0x30, 0x73, 0x83, 0x72, 0x8d, 0xbf, 0xf3, 0x1a, 0x4f, 0x24, 0x9b, 0xbf, 0xfb, 0xa8, 0xc9, 0x98, 0x2f, 0x84, 0xb7, 0x58, 0x78, 0x8b, 0x85, 0xb7, 0xc1, 0x6c, 0x7d, 0x0f, 0xbc, 0xb5, 0x87, 0xb7, 0xf6, 0x16, 0x6f, 0xed, 0x2d, 0xde, 0x62, 0x2d, 0xde, 0x62, 0xe1, 0x2d, 0xf6, 0x2c, 0xbc, 0xc5, 0xc2, 0x5b, 0x2c, 0xbc, 0xc5, 0xc2, 0x5b, 0x2c, 0xbc, 0xc5, 0xc2, 0x5b, 0x2c, 0xbc, 0xc5, 0xc2, 0x5b, 0x7b, 0x78, 0x8b, 0xd5, 0xdf, 0x52, 0xdf, 0xf6, 0xc4, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x7b, 0xb8, 0x6b, 0x0f, 0x77, 0xed, 0xe1, 0x6e, 0xb4, 0xc5, 0xdd, 0x83, 0xd4, 0x7c, 0x1e, 0x35, 0x7f, 0x19, 0xee, 0xde, 0x86, 0xbb, 0xc7, 0xe0, 0xae, 0x3d, 0xdc, 0xb5, 0xb7, 0xb8, 0x6b, 0x6f, 0x71, 0xd7, 0x1e, 0xee, 0xa6, 0xc1, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x9d, 0x19, 0x4d, 0xc3, 0x45, 0x23, 0x98, 0xb8, 0xa9, 0x5e, 0x3c, 0x9a, 0xc7, 0x44, 0x66, 0x51, 0x8b, 0xa7, 0xa8, 0xc5, 0x0d, 0xd4, 0xe2, 0x21, 0x6a, 0x31, 0x24, 0x20, 0x1e, 0x5f, 0x62, 0xbe, 0x34, 0xf8, 0x74, 0xa2, 0x1a, 0xdb, 0x3d, 0xea, 0xa9, 0xe3, 0x8d, 0x7c, 0xf2, 0x22, 0x3e, 0xd9, 0x95, 0x4f, 0x76, 0xe2, 0x93, 0xf6, 0x80, 0x4f, 0xde, 0x76, 0x86, 0x96, 0xb6, 0xa8, 0xa7, 0xa5, 0xf1, 0x4a, 0x4b, 0x93, 0xe8, 0xa7, 0x24, 0xfa, 0x29, 0x89, 0xf2, 0x93, 0xe8, 0x93, 0x24, 0xab, 0x4f, 0x92, 0xe8, 0x93, 0xa4, 0xb3, 0xf4, 0x49, 0x12, 0x7d, 0x92, 0x44, 0x9f, 0x24, 0xd1, 0x27, 0x49, 0xf4, 0x49, 0x12, 0x7d, 0x92, 0x44, 0x9f, 0x24, 0xd1, 0x1f, 0x49, 0x68, 0xa9, 0x39, 0xdf, 0x26, 0xd1, 0x1f, 0x49, 0xf4, 0x47, 0x12, 0xfd, 0x91, 0x44, 0x7f, 0x24, 0xd1, 0x1f, 0x49, 0xf4, 0x47, 0x12, 0xfd, 0x91, 0x44, 0x7f, 0x24, 0xd1, 0xf2, 0x37, 0xac, 0xd8, 0xed, 0x4e, 0xad, 0x86, 0x53, 0xff, 0x21, 0xd4, 0xff, 0xa1, 0x80, 0xda, 0x35, 0x84, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0xbe, 0xc4, 0x6a, 0x32, 0xb1, 0xda, 0x04, 0xe6, 0xc6, 0xa9, 0x3b, 0xb0, 0xd7, 0x44, 0x63, 0x2c, 0xad, 0x9c, 0x7a, 0x96, 0xc8, 0xb3, 0xff, 0xd5, 0xc8, 0x53, 0x51, 0x37, 0x4f, 0x34, 0xf8, 0x4b, 0x51, 0x57, 0xfd, 0x4d, 0xed, 0xe7, 0xb4, 0x72, 0xa6, 0xd5, 0xbf, 0x77, 0xd0, 0xca, 0xf6, 0x01, 0xad, 0xfc, 0xfa, 0x1f, 0x47, 0xd5, 0x2c, 0xd1, 0x9e, 0xd6, 0xf5, 0x51, 0x3a, 0x6a, 0xa7, 0x75, 0x76, 0x5a, 0x67, 0xa7, 0x75, 0x63, 0x19, 0x6b, 0xcd, 0x19, 0x6b, 0xcd, 0xad, 0xb1, 0xd6, 0xdc, 0x1a, 0x6b, 0x76, 0xab, 0xc5, 0x76, 0x5a, 0x6c, 0x3f, 0x4b, 0xbf, 0xda, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x19, 0x6b, 0xcd, 0x69, 0xb5, 0x79, 0x5e, 0x8d, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x71, 0xd6, 0x9c, 0x71, 0xd6, 0x9c, 0x71, 0xd6, 0x5c, 0x29, 0x58, 0xf5, 0x38, 0xbb, 0x01, 0x06, 0x26, 0xc3, 0xc0, 0xbd, 0x30, 0x30, 0x15, 0x06, 0xc6, 0x32, 0xce, 0x9a, 0x33, 0xce, 0x9a, 0x5b, 0xe3, 0xac, 0xb9, 0x35, 0xce, 0x9a, 0xc3, 0xc8, 0xad, 0x30, 0x62, 0x87, 0x11, 0xbb, 0x61, 0x7e, 0xe7, 0x90, 0x06, 0x7a, 0x82, 0x5e, 0xa0, 0x37, 0x48, 0x07, 0x7d, 0x40, 0x5f, 0xd0, 0x0f, 0xf4, 0x07, 0x03, 0xc0, 0x40, 0x35, 0xe7, 0xa7, 0x29, 0x1d, 0xcd, 0xa8, 0x37, 0xc6, 0x7a, 0x52, 0x83, 0x3b, 0xa8, 0xc1, 0xb3, 0xd4, 0x60, 0x2a, 0x35, 0x78, 0x9a, 0x1a, 0xdc, 0x17, 0xd0, 0x07, 0x13, 0xf8, 0x64, 0x77, 0x35, 0x67, 0xe9, 0xf0, 0xa8, 0xc3, 0xa3, 0x4e, 0x29, 0x43, 0xe0, 0xb1, 0x21, 0x3c, 0x36, 0xb4, 0x78, 0x6c, 0x68, 0xf1, 0xa8, 0x5b, 0x3c, 0xea, 0xf0, 0xa8, 0x9f, 0x85, 0x47, 0x1d, 0x1e, 0x75, 0x78, 0xd4, 0xe1, 0x51, 0x87, 0x47, 0x1d, 0x1e, 0x75, 0x78, 0xd4, 0xe1, 0xb1, 0x21, 0x3c, 0xea, 0xf0, 0xa8, 0xc3, 0xa3, 0x0e, 0x8f, 0x3a, 0x3c, 0xea, 0xf0, 0xa8, 0xc3, 0xa3, 0x0e, 0x8f, 0x3a, 0x3c, 0xea, 0xf0, 0xa8, 0xc3, 0x63, 0x43, 0x78, 0x6c, 0x08, 0x8f, 0x0d, 0x69, 0xc1, 0x24, 0x8b, 0xc7, 0x1e, 0xb4, 0x62, 0x02, 0xad, 0xb8, 0xdf, 0x72, 0x03, 0xcf, 0xc0, 0x63, 0x43, 0x78, 0x6c, 0x68, 0xf1, 0xd8, 0xd0, 0xe2, 0xd1, 0xbc, 0xd6, 0x78, 0x2f, 0x78, 0xd4, 0xe1, 0x51, 0x37, 0xcc, 0xe3, 0x23, 0x69, 0xa0, 0x27, 0xe8, 0x05, 0x7a, 0x83, 0x74, 0xd0, 0x07, 0xf4, 0x05, 0xfd, 0x40, 0x7f, 0x30, 0x00, 0x0c, 0x54, 0xc7, 0x52, 0x3a, 0x30, 0x7e, 0xc2, 0x61, 0x65, 0xa4, 0xd2, 0xd0, 0xbe, 0xf5, 0xf8, 0xbc, 0x9d, 0x9a, 0xb4, 0xa7, 0x26, 0x57, 0x50, 0x93, 0xb1, 0xd4, 0xe4, 0x3e, 0x6a, 0x72, 0x7f, 0x00, 0x9f, 0x9d, 0x29, 0xa1, 0x15, 0x25, 0x38, 0x28, 0x21, 0x43, 0x39, 0xd9, 0xc0, 0x6f, 0x80, 0x92, 0xad, 0x6f, 0x80, 0xb4, 0x80, 0x6f, 0x80, 0xb4, 0xbf, 0xfa, 0x0d, 0xce, 0x5f, 0xfe, 0xd6, 0xa6, 0x25, 0xdc, 0x54, 0x9f, 0xfd, 0xdc, 0x9b, 0x5a, 0x8e, 0xa6, 0x96, 0xa3, 0xa8, 0xe5, 0x2b, 0x01, 0xb5, 0x6c, 0xfc, 0x4f, 0xbf, 0xcd, 0xa1, 0x75, 0xfd, 0x68, 0x5d, 0x0a, 0xf3, 0x4b, 0x73, 0x35, 0xbf, 0xd4, 0xd7, 0xc2, 0xba, 0xdf, 0x94, 0xd5, 0x3f, 0xae, 0xac, 0xd5, 0x39, 0x9b, 0xa9, 0xe6, 0x6c, 0xcc, 0x41, 0x30, 0x73, 0x27, 0x11, 0x97, 0x4a, 0xc4, 0xa5, 0x5a, 0x11, 0x97, 0x6a, 0x45, 0x5c, 0xb4, 0xc5, 0x56, 0xcd, 0x19, 0x9a, 0xf5, 0xd9, 0xfa, 0xfd, 0xec, 0xca, 0xfa, 0x67, 0x56, 0xfe, 0xa0, 0x32, 0x9c, 0xbf, 0x76, 0x46, 0x65, 0x24, 0x59, 0x48, 0x34, 0x68, 0xa1, 0x32, 0x9d, 0x11, 0x56, 0xc4, 0x4d, 0xa2, 0x3d, 0xaf, 0x51, 0xeb, 0x17, 0x60, 0x70, 0x1e, 0x0c, 0x3e, 0x42, 0xc4, 0xa5, 0x12, 0x71, 0xa9, 0x56, 0xc4, 0xa5, 0x5a, 0x11, 0x97, 0x0a, 0xa3, 0x53, 0xff, 0xe9, 0x59, 0x99, 0x30, 0x6a, 0x28, 0xbf, 0xc5, 0x1c, 0x25, 0x4f, 0x9c, 0x93, 0x2f, 0x73, 0x6c, 0x8f, 0xaf, 0x17, 0x8b, 0x4d, 0xa8, 0xe3, 0x73, 0xd4, 0x71, 0x3a, 0x5b, 0xde, 0x48, 0x1d, 0x1f, 0x50, 0x59, 0xf5, 0xef, 0xbd, 0x3c, 0x87, 0x72, 0x63, 0x55, 0x7e, 0xd1, 0xb5, 0x9e, 0x1a, 0x8e, 0xb3, 0x72, 0xf1, 0x8e, 0x7c, 0x2a, 0x70, 0x46, 0xbe, 0xa9, 0xce, 0xbd, 0x70, 0x7f, 0xb2, 0xbe, 0x75, 0xdd, 0x71, 0xce, 0x7b, 0xe1, 0xea, 0xd6, 0xd6, 0x07, 0xd8, 0xfa, 0xc7, 0xc0, 0xef, 0x68, 0x6b, 0xce, 0x6f, 0x50, 0xe7, 0x62, 0x5f, 0xab, 0x8d, 0x16, 0xf7, 0x6b, 0x63, 0xc4, 0x93, 0xda, 0xe5, 0xe2, 0xf1, 0xbf, 0x79, 0xb7, 0xdd, 0x30, 0x61, 0x93, 0xdb, 0xcd, 0x2b, 0x2d, 0x68, 0x3d, 0xe5, 0x37, 0xda, 0x60, 0xf9, 0x39, 0x9f, 0xf8, 0xa9, 0xf6, 0x7b, 0x8e, 0x72, 0x59, 0x60, 0x5d, 0x81, 0xfe, 0xa0, 0x6d, 0xa5, 0x3c, 0xc0, 0x27, 0xbc, 0xea, 0xde, 0x98, 0x87, 0xb4, 0x5e, 0xf2, 0xa0, 0xd6, 0x1b, 0x8c, 0xa6, 0xac, 0xea, 0x23, 0xfc, 0x47, 0x55, 0x59, 0x3b, 0x29, 0x6b, 0x3f, 0x5b, 0xe4, 0x50, 0xd6, 0x8f, 0xd6, 0x55, 0xc4, 0xcc, 0xbb, 0x83, 0xee, 0x0c, 0xb8, 0x17, 0x41, 0x25, 0x65, 0x1d, 0xa7, 0xac, 0x52, 0xe1, 0xd0, 0x1a, 0xf1, 0xf9, 0xc6, 0x30, 0xc0, 0xdc, 0x44, 0x7d, 0x0f, 0x58, 0xdf, 0x61, 0x1e, 0x0a, 0xaa, 0x10, 0x46, 0xf0, 0x65, 0x32, 0x8f, 0x76, 0x56, 0xd1, 0xce, 0x02, 0x75, 0xad, 0xf4, 0xcd, 0x81, 0x5b, 0xd4, 0x9c, 0xb1, 0x6c, 0x7d, 0xc3, 0x79, 0xc8, 0x3c, 0x72, 0x41, 0x99, 0x45, 0xf4, 0xa4, 0x4d, 0x6e, 0xb3, 0x5a, 0xf4, 0xa5, 0xd6, 0x5f, 0x7e, 0xa7, 0x0d, 0x00, 0x83, 0xe5, 0x17, 0xd4, 0xc6, 0x65, 0x7d, 0xab, 0xb2, 0x9d, 0x4f, 0x17, 0xf2, 0xe9, 0x42, 0x15, 0xe5, 0x2b, 0xa5, 0x4f, 0xb5, 0x2c, 0xac, 0xce, 0x27, 0xff, 0xca, 0x27, 0x1a, 0x52, 0xf7, 0xbd, 0x5a, 0xb8, 0x7c, 0x5f, 0xeb, 0x28, 0xf7, 0x9d, 0xed, 0x53, 0xb4, 0x23, 0x3c, 0x78, 0x84, 0x5c, 0x60, 0xde, 0x95, 0xc9, 0x3a, 0xe7, 0xfd, 0x94, 0xf1, 0x82, 0x3c, 0x74, 0xce, 0x4f, 0xfa, 0xf9, 0x64, 0x25, 0x9f, 0xac, 0x3c, 0xe7, 0x27, 0x03, 0xef, 0xf2, 0x90, 0x77, 0xd6, 0xbb, 0x3c, 0xfc, 0xf9, 0x7d, 0x20, 0x6e, 0x16, 0x47, 0xe5, 0xd3, 0xe2, 0x98, 0x3c, 0x2e, 0x8e, 0xcb, 0xd7, 0xc4, 0x09, 0xf9, 0x9a, 0xe6, 0xe4, 0xdd, 0x46, 0x7c, 0xaa, 0x09, 0xb5, 0x69, 0x0a, 0x9a, 0x81, 0x70, 0x79, 0x9d, 0x16, 0xc1, 0x98, 0x69, 0x2e, 0x8f, 0x68, 0x91, 0xfc, 0x1f, 0x05, 0xa2, 0xe5, 0x65, 0x5a, 0x0b, 0x10, 0xc3, 0x7a, 0x4b, 0x10, 0x0b, 0xe2, 0x88, 0x94, 0x78, 0x9e, 0x5b, 0xf1, 0xdc, 0x9a, 0xe7, 0x24, 0x90, 0x0c, 0x52, 0x40, 0x5b, 0xd0, 0x0e, 0xb4, 0xa7, 0x65, 0x1d, 0x78, 0xff, 0x3c, 0xd6, 0x3b, 0xca, 0xef, 0x83, 0x32, 0xe5, 0xf1, 0xa0, 0x2d, 0xf2, 0xb5, 0xa0, 0x3d, 0xa0, 0x50, 0xee, 0x0b, 0x2a, 0x02, 0xc5, 0x60, 0x3f, 0x28, 0x91, 0x27, 0x82, 0xbc, 0x3c, 0x97, 0x82, 0x32, 0xe0, 0x03, 0x07, 0xc1, 0x21, 0x70, 0x58, 0x6e, 0x09, 0xfa, 0x95, 0xe7, 0x2a, 0x70, 0x52, 0x5e, 0x16, 0x74, 0x8a, 0xe7, 0xdf, 0x80, 0x94, 0xfb, 0x82, 0x6d, 0x72, 0x4b, 0x70, 0x10, 0xcf, 0xc1, 0xc0, 0xce, 0xba, 0x43, 0x5e, 0x16, 0xec, 0x64, 0x7d, 0x38, 0xb8, 0x44, 0xdd, 0x93, 0x65, 0x5f, 0xf0, 0xa5, 0xbc, 0x3e, 0x06, 0x10, 0xdd, 0x21, 0x07, 0xe5, 0x91, 0x90, 0x43, 0x72, 0x8b, 0x3d, 0x4d, 0xbe, 0x69, 0xbf, 0x1e, 0x8c, 0x97, 0x6f, 0xea, 0xef, 0x4b, 0x97, 0xfe, 0x05, 0xf8, 0x12, 0xfc, 0x07, 0xac, 0x93, 0x5b, 0xf4, 0x2d, 0x60, 0x27, 0xd8, 0x05, 0x4a, 0xe5, 0x16, 0xc3, 0x01, 0x68, 0xa7, 0x31, 0x08, 0x8c, 0x63, 0xfd, 0x41, 0xf0, 0x08, 0xeb, 0xd3, 0xe5, 0x3e, 0xc7, 0xbf, 0xe5, 0x71, 0xc7, 0x0a, 0xf0, 0x03, 0xeb, 0xe6, 0x37, 0x80, 0xeb, 0xc1, 0x46, 0x79, 0xc4, 0xf1, 0x23, 0xff, 0x6f, 0x02, 0x9b, 0xc1, 0x16, 0xb0, 0x15, 0xe4, 0x82, 0x9f, 0xa4, 0xdf, 0xb9, 0x8a, 0x71, 0x68, 0xe8, 0x97, 0xca, 0x1d, 0xfa, 0x74, 0xf9, 0x9d, 0xfe, 0x84, 0x3c, 0xa8, 0x3f, 0xc9, 0xf3, 0x4c, 0x9e, 0x67, 0x81, 0xd9, 0x60, 0x0e, 0xff, 0xbf, 0x20, 0xbf, 0x13, 0xef, 0x8b, 0x8d, 0xf2, 0x94, 0xc8, 0x96, 0x27, 0xc4, 0x36, 0xfa, 0x2e, 0x47, 0x96, 0x8b, 0x5c, 0xd6, 0x7f, 0x62, 0x1e, 0xd8, 0xa1, 0xbe, 0xcd, 0xf7, 0x09, 0x17, 0xff, 0xef, 0xe4, 0xff, 0x5d, 0xac, 0xe7, 0xb1, 0x8d, 0x79, 0x4f, 0x3d, 0x0f, 0xeb, 0x7b, 0x58, 0xdf, 0xcb, 0x7a, 0x3e, 0xeb, 0xbf, 0xf0, 0xfe, 0x3e, 0xd6, 0x0b, 0x89, 0xa6, 0x22, 0xb0, 0x5f, 0x1e, 0x16, 0x25, 0x3c, 0x7b, 0x79, 0xbd, 0x8c, 0xf7, 0x0f, 0x30, 0x9e, 0x2f, 0x56, 0xdf, 0xdc, 0x99, 0x77, 0xca, 0x3a, 0x18, 0xf4, 0x21, 0xe3, 0x71, 0x11, 0xfd, 0xf5, 0x11, 0x58, 0x22, 0x8f, 0x04, 0x7d, 0x02, 0x3e, 0xe5, 0xb5, 0xcf, 0xc0, 0x0a, 0xf3, 0x37, 0x6a, 0xea, 0x5e, 0x79, 0x87, 0x42, 0x8e, 0xcb, 0x53, 0xb4, 0x62, 0x8d, 0x3e, 0x95, 0x31, 0x3b, 0x5d, 0x2e, 0xa3, 0x25, 0x65, 0xb4, 0x64, 0x19, 0x2d, 0x29, 0xa3, 0x25, 0x65, 0xb4, 0xa4, 0x8c, 0x96, 0x2c, 0xa3, 0x25, 0xcb, 0xf4, 0x2c, 0x46, 0xac, 0x79, 0x9d, 0xb8, 0x6b, 0x79, 0xbe, 0x0e, 0xdc, 0x0b, 0xee, 0x03, 0xf7, 0x83, 0x89, 0x60, 0x12, 0x98, 0x0c, 0xa6, 0x80, 0x07, 0xe4, 0x21, 0x47, 0x27, 0x59, 0xee, 0xe8, 0x0c, 0xba, 0x80, 0xae, 0xa0, 0x1b, 0xe8, 0x0e, 0x7a, 0x80, 0x34, 0xd0, 0x13, 0x9c, 0x0f, 0x2e, 0x00, 0xbd, 0x40, 0x6f, 0x90, 0x0e, 0xfa, 0x80, 0xbe, 0xa0, 0x1f, 0xe8, 0x0f, 0x06, 0x80, 0x81, 0x60, 0x10, 0xc8, 0x00, 0x17, 0x82, 0xc1, 0xe0, 0x22, 0x30, 0x04, 0x5c, 0x4c, 0x7f, 0x0d, 0x05, 0xc3, 0xc0, 0x70, 0x30, 0x02, 0x5c, 0x02, 0x46, 0x82, 0x51, 0x60, 0x34, 0xf8, 0x6f, 0xdd, 0xa7, 0x6e, 0x3a, 0x78, 0x42, 0xdd, 0xaf, 0xee, 0xb0, 0xe3, 0x29, 0xf0, 0x34, 0x78, 0x06, 0xcc, 0x00, 0xcf, 0x82, 0x99, 0xe0, 0x39, 0xf0, 0x3c, 0xf8, 0x17, 0x98, 0x05, 0x66, 0x83, 0x39, 0xe0, 0x05, 0xf0, 0x22, 0x78, 0x09, 0xbc, 0x0c, 0x5e, 0x01, 0x73, 0xc1, 0xeb, 0xb4, 0xe1, 0x0d, 0xf0, 0x26, 0x98, 0x07, 0xe6, 0x83, 0xb7, 0xc0, 0xdb, 0xe0, 0x1d, 0xf0, 0x2e, 0x78, 0x0f, 0xbc, 0x0f, 0x16, 0x80, 0x0f, 0xc0, 0x42, 0xf0, 0x21, 0x58, 0x04, 0x3e, 0x02, 0x8b, 0xc1, 0xc7, 0x60, 0x09, 0xf8, 0x04, 0x7c, 0x0a, 0x96, 0x82, 0xcf, 0x64, 0xb9, 0xf3, 0x25, 0x75, 0x87, 0x9a, 0x1d, 0x44, 0xcf, 0x2e, 0x59, 0xa9, 0x22, 0x6c, 0xaf, 0xf4, 0x13, 0x51, 0xbf, 0x89, 0x62, 0xfe, 0x2f, 0x93, 0xbf, 0xa1, 0x3e, 0x79, 0xa8, 0xcf, 0x0e, 0xf5, 0xbd, 0x64, 0xf5, 0x2c, 0xb8, 0x59, 0xbb, 0x42, 0xee, 0x35, 0xa3, 0x47, 0x84, 0xab, 0x6b, 0x35, 0xee, 0x90, 0x27, 0x89, 0xd5, 0x32, 0xe2, 0xf4, 0x80, 0xba, 0x5e, 0xa3, 0x59, 0x8a, 0x87, 0x4f, 0xef, 0x05, 0x35, 0xd7, 0xf5, 0xf4, 0xca, 0x12, 0xeb, 0xac, 0x70, 0xf3, 0xec, 0xf1, 0xb5, 0x01, 0xbf, 0x81, 0x31, 0x4b, 0x3b, 0xa6, 0xae, 0xe1, 0x68, 0x96, 0xf8, 0x9e, 0xa0, 0x87, 0x05, 0x3d, 0x2c, 0xe8, 0x61, 0x41, 0x0f, 0x0b, 0x7a, 0x58, 0xd0, 0xc3, 0x82, 0x1e, 0x16, 0xf4, 0xb0, 0xa0, 0x87, 0x05, 0x3d, 0x2c, 0xe8, 0x61, 0x41, 0x0f, 0x8b, 0x8b, 0xc1, 0x50, 0x30, 0x0c, 0x0c, 0x07, 0x23, 0xc0, 0x25, 0x60, 0xa4, 0x79, 0x0d, 0x09, 0x30, 0x1a, 0x5c, 0x0a, 0x50, 0x35, 0x71, 0x19, 0xb8, 0x1c, 0x5c, 0x01, 0xae, 0x04, 0x63, 0xc1, 0x55, 0xe0, 0x6a, 0x70, 0x0d, 0xb8, 0x16, 0x3c, 0x02, 0xa6, 0x31, 0xfa, 0x1e, 0x05, 0x8f, 0x81, 0xc7, 0xc1, 0x74, 0xf0, 0x04, 0x78, 0x12, 0x3c, 0x05, 0x9e, 0x06, 0xcf, 0x00, 0x46, 0x82, 0x78, 0x4e, 0x56, 0x89, 0x7f, 0xc9, 0x92, 0x80, 0xf3, 0x0b, 0xb6, 0x6b, 0xe9, 0xd2, 0xa3, 0xf5, 0x61, 0x66, 0xec, 0xcb, 0x73, 0x3f, 0x9e, 0xcd, 0xf3, 0x0d, 0xd4, 0xef, 0xbf, 0x18, 0x85, 0xe3, 0xcc, 0xab, 0xf1, 0x54, 0x9f, 0x6b, 0x60, 0xa3, 0x7c, 0x1b, 0xe5, 0xdb, 0xe6, 0xca, 0x12, 0x1b, 0xbd, 0x68, 0xa3, 0x17, 0x6d, 0xf4, 0xa2, 0x8d, 0x5e, 0xb4, 0xd1, 0x8b, 0x36, 0x7a, 0xd1, 0x46, 0x2f, 0xda, 0xe8, 0x45, 0x1b, 0xbd, 0x68, 0xa3, 0x17, 0x6d, 0x4b, 0xd8, 0xfe, 0x13, 0xf5, 0x4d, 0x58, 0xf5, 0x79, 0x09, 0xdf, 0xc9, 0xf2, 0x90, 0x35, 0x60, 0x0b, 0xd8, 0x0a, 0xb6, 0x81, 0xed, 0x80, 0xd9, 0x24, 0x34, 0x59, 0x56, 0xe8, 0x3e, 0x59, 0xae, 0x1f, 0x02, 0x87, 0x41, 0x39, 0x38, 0x02, 0x2a, 0xc0, 0x51, 0x70, 0x0c, 0xf8, 0x41, 0x25, 0xf8, 0x15, 0x1c, 0x07, 0x27, 0x40, 0x15, 0x38, 0x09, 0x4e, 0x81, 0xdf, 0xc0, 0x69, 0x59, 0x6e, 0xbc, 0x2e, 0x2b, 0x8c, 0x37, 0xc0, 0x9b, 0x60, 0x1e, 0x98, 0x0f, 0xde, 0x02, 0x6f, 0x83, 0x77, 0xc0, 0xbb, 0xe0, 0x3d, 0xf0, 0x3e, 0x58, 0x00, 0x3e, 0x00, 0x0b, 0xc1, 0x87, 0x60, 0x11, 0xf8, 0x08, 0x2c, 0x06, 0x1f, 0x03, 0xda, 0x60, 0xd0, 0x06, 0xe3, 0x53, 0xb0, 0x14, 0x7c, 0x06, 0x96, 0x81, 0xe5, 0xe0, 0xdf, 0x60, 0x05, 0xf8, 0x1c, 0x7c, 0x01, 0xbe, 0x04, 0x5f, 0x81, 0x95, 0xe0, 0x6b, 0xf0, 0x0d, 0xc8, 0x04, 0xff, 0x01, 0xdf, 0x82, 0x55, 0xc0, 0x25, 0xcb, 0x8c, 0x9d, 0x60, 0x97, 0xac, 0x32, 0xf2, 0xc0, 0x6e, 0xe0, 0x01, 0x7b, 0x80, 0x79, 0x2d, 0xa6, 0x7c, 0x50, 0x24, 0x4b, 0x8c, 0x62, 0xb0, 0x1f, 0x94, 0x80, 0x32, 0x70, 0x00, 0xf8, 0xc0, 0x41, 0x70, 0x08, 0x1c, 0xa6, 0x1f, 0x75, 0xe2, 0x79, 0x0f, 0xb1, 0x5b, 0xc6, 0x08, 0xf0, 0x8a, 0x52, 0xf5, 0x1b, 0xb8, 0xea, 0x5e, 0xb5, 0x7a, 0x51, 0xb4, 0x24, 0xea, 0x8f, 0xb3, 0xd5, 0x5e, 0xa2, 0xbe, 0x4a, 0xec, 0x14, 0xf1, 0x44, 0xfd, 0xaf, 0x44, 0xfd, 0x41, 0xe1, 0x11, 0xad, 0xf9, 0x64, 0xbe, 0xc8, 0x17, 0x29, 0x44, 0xfd, 0x51, 0x4a, 0xf8, 0x85, 0xc8, 0x37, 0xc7, 0xd1, 0xaf, 0xea, 0x57, 0x06, 0x3d, 0xc4, 0x28, 0x2d, 0x4d, 0xb4, 0xd7, 0xfa, 0xa8, 0xb3, 0xc5, 0xa3, 0xb4, 0x8b, 0x45, 0x1c, 0x5e, 0xaf, 0x9f, 0x75, 0x6e, 0x6a, 0xaa, 0x76, 0x85, 0xe8, 0xa5, 0x5d, 0x27, 0xba, 0x69, 0xe3, 0x44, 0x37, 0x46, 0x84, 0x39, 0x43, 0x1f, 0x34, 0x7f, 0x31, 0xa0, 0xee, 0xc9, 0x6e, 0xde, 0xc3, 0x3d, 0x5b, 0x7d, 0x8f, 0xef, 0xb3, 0x7e, 0x4b, 0xf7, 0xa3, 0x88, 0xf8, 0x47, 0x35, 0xe9, 0xc1, 0x68, 0xae, 0xfe, 0x65, 0xd0, 0x6b, 0x35, 0xe5, 0xd2, 0xae, 0x6d, 0x35, 0x7b, 0x3c, 0xa3, 0xd4, 0xc4, 0x80, 0x52, 0xdb, 0x58, 0xa5, 0xb6, 0xfd, 0xdb, 0xa5, 0x86, 0xd7, 0x2d, 0x55, 0xdd, 0x9d, 0xab, 0xba, 0x54, 0x55, 0xa2, 0xba, 0xbe, 0xeb, 0xdf, 0x2b, 0xd1, 0x9c, 0xb7, 0xb2, 0x99, 0x69, 0x76, 0x50, 0x82, 0xf9, 0x0b, 0xee, 0x83, 0x7c, 0x7a, 0x37, 0x3d, 0xb6, 0xf5, 0x8f, 0xce, 0x01, 0x41, 0xf9, 0x0e, 0xe3, 0x0c, 0x77, 0x30, 0xc3, 0x79, 0x64, 0x16, 0x9f, 0x32, 0x67, 0x38, 0xb7, 0xba, 0x47, 0xe6, 0x60, 0x35, 0x37, 0xad, 0xe3, 0x13, 0x5b, 0xac, 0x5f, 0x4b, 0x6d, 0xe2, 0x13, 0x95, 0x7c, 0xe2, 0x37, 0x75, 0x46, 0xc5, 0x11, 0xde, 0x59, 0xcb, 0x3b, 0x99, 0xbc, 0xf3, 0x3d, 0xbd, 0x17, 0x4e, 0x4c, 0x44, 0x30, 0xa7, 0x99, 0x7e, 0x2a, 0x9a, 0xba, 0xb6, 0x00, 0xed, 0xe5, 0x31, 0xf3, 0x1b, 0x79, 0x3c, 0xcf, 0x81, 0xa0, 0xa3, 0xe8, 0xe9, 0x49, 0xb9, 0x23, 0x38, 0x44, 0x1e, 0x0a, 0x0e, 0x05, 0x0e, 0xb9, 0xc3, 0xf4, 0x28, 0xc6, 0x2f, 0x68, 0x20, 0x6d, 0x34, 0x4a, 0x41, 0x39, 0x38, 0x02, 0x70, 0xd1, 0xf8, 0x8b, 0x03, 0xf8, 0x8b, 0x03, 0xca, 0x5f, 0xfc, 0x24, 0x8f, 0xa9, 0xf6, 0xed, 0xb7, 0xae, 0x00, 0x7d, 0x84, 0x9a, 0x1e, 0xa3, 0x7d, 0x15, 0xb4, 0x6f, 0x37, 0x35, 0xde, 0x66, 0x9d, 0xc7, 0x64, 0x7a, 0xcc, 0x4d, 0xd4, 0xe9, 0x3b, 0xeb, 0xb7, 0x5d, 0x7b, 0xcd, 0x3b, 0x61, 0x91, 0x01, 0x7e, 0x6f, 0x5e, 0x5f, 0x83, 0x4c, 0xc2, 0xa6, 0x2d, 0x66, 0xa9, 0x08, 0xbe, 0xa2, 0x7a, 0xb1, 0xdd, 0x6d, 0xfb, 0x2a, 0xe8, 0xad, 0xea, 0xc5, 0xfc, 0xbf, 0x55, 0x5e, 0xf0, 0x8b, 0xc1, 0x2b, 0x82, 0xd7, 0x04, 0x57, 0x06, 0x57, 0x86, 0xb4, 0x0c, 0x69, 0x17, 0x72, 0x7e, 0x88, 0x9b, 0x65, 0x93, 0x7a, 0x7c, 0x3e, 0x64, 0x79, 0xf5, 0x5a, 0xeb, 0x7b, 0x42, 0xaa, 0x42, 0x83, 0x43, 0x1d, 0xa1, 0x63, 0xed, 0x95, 0x8e, 0x54, 0x6b, 0x19, 0xec, 0x18, 0x5f, 0xbb, 0x7c, 0x56, 0xbb, 0xec, 0xa8, 0x5d, 0x4e, 0x37, 0x19, 0xd2, 0x64, 0x48, 0x74, 0x8b, 0xe8, 0x0e, 0xd1, 0xb7, 0x46, 0x4f, 0x8e, 0x7e, 0x3e, 0xfa, 0x8d, 0xe8, 0xd5, 0x67, 0x2e, 0x2d, 0xb6, 0xb7, 0xca, 0x6b, 0x95, 0xd7, 0x7a, 0x7c, 0xeb, 0x7b, 0x58, 0xd6, 0xb4, 0xce, 0x6b, 0x5d, 0x94, 0x34, 0x34, 0xe9, 0xee, 0xa4, 0x25, 0xc2, 0x26, 0x1c, 0x8e, 0xe5, 0x8e, 0x9f, 0x85, 0x70, 0xb8, 0xc9, 0xca, 0x92, 0x1d, 0x7b, 0x1c, 0x7b, 0x45, 0x8a, 0xa3, 0xcc, 0xf1, 0x9b, 0x68, 0xeb, 0xcc, 0x70, 0x66, 0x88, 0x3e, 0xce, 0xd9, 0xce, 0x17, 0x44, 0x5f, 0xe7, 0x4b, 0xce, 0x97, 0xc5, 0x00, 0xe7, 0xab, 0xce, 0xd7, 0xc5, 0x20, 0xe7, 0x67, 0xce, 0x55, 0x62, 0xb0, 0x73, 0xa3, 0x73, 0x8b, 0xb8, 0x42, 0x5d, 0x8d, 0xf0, 0x4e, 0x21, 0xe5, 0x57, 0x38, 0xe1, 0x25, 0xd6, 0xd5, 0x08, 0x6f, 0xc3, 0xa3, 0xbf, 0xa6, 0x85, 0x9f, 0x7e, 0x54, 0x4b, 0x95, 0xe7, 0x6b, 0x1d, 0x4f, 0x7f, 0xa5, 0x75, 0x12, 0xb1, 0x5a, 0x67, 0x79, 0x15, 0x7d, 0x3b, 0x58, 0x3b, 0x5f, 0x4e, 0xd3, 0x2e, 0x90, 0x8f, 0x13, 0x05, 0x37, 0x05, 0x6b, 0xb2, 0x73, 0xf0, 0x40, 0x79, 0x51, 0xf0, 0x88, 0xd3, 0xd7, 0x06, 0x8f, 0x94, 0x69, 0xc1, 0xa3, 0xe5, 0x6c, 0x7c, 0x7b, 0x9c, 0xfd, 0x66, 0xd9, 0xca, 0x7e, 0x40, 0x8e, 0xd0, 0x87, 0xe2, 0x88, 0xaa, 0xaf, 0x46, 0xd8, 0x1d, 0x17, 0x29, 0x70, 0x91, 0x76, 0xeb, 0x6a, 0x84, 0xad, 0x8c, 0x07, 0x4f, 0x1f, 0x37, 0x5e, 0x38, 0x9d, 0x67, 0x64, 0xc9, 0xb1, 0x8e, 0xd7, 0x64, 0x27, 0x7a, 0x73, 0xb2, 0x23, 0x47, 0x8e, 0xa4, 0x47, 0x6f, 0xb0, 0xae, 0x46, 0xb8, 0xc9, 0xf9, 0xad, 0xec, 0xe6, 0x5c, 0x75, 0xfa, 0xb8, 0x68, 0x6a, 0x5d, 0x8f, 0x69, 0x11, 0xb5, 0x5b, 0x4a, 0xed, 0xd6, 0x58, 0xd7, 0x63, 0x7a, 0x89, 0x5a, 0x2d, 0x0c, 0x5a, 0x89, 0x6f, 0x5e, 0x25, 0x4f, 0x50, 0x9b, 0x67, 0xd9, 0xfb, 0xb7, 0xec, 0xe1, 0x14, 0x7b, 0xd0, 0xac, 0xeb, 0x32, 0xad, 0xb3, 0xae, 0xcb, 0x94, 0xeb, 0x30, 0xef, 0x8f, 0x16, 0x47, 0x7b, 0x2b, 0xb5, 0x10, 0xdc, 0x7d, 0xf5, 0x75, 0x86, 0x4b, 0x29, 0xed, 0xa8, 0xba, 0x13, 0x64, 0x2a, 0x59, 0x5d, 0x47, 0x62, 0xc5, 0xfc, 0x35, 0xef, 0x18, 0x70, 0xb9, 0xac, 0xa0, 0xe4, 0xe3, 0x94, 0x7c, 0x3c, 0xc8, 0xad, 0xee, 0x8e, 0x58, 0x4e, 0x74, 0x96, 0xd3, 0xde, 0x23, 0xd6, 0x1d, 0x10, 0xab, 0xc8, 0x53, 0x36, 0xd3, 0xee, 0xfd, 0xb4, 0xfb, 0x67, 0xf6, 0x5c, 0x11, 0x70, 0xed, 0xe1, 0xfd, 0xb4, 0xff, 0x04, 0x35, 0xc8, 0xa7, 0x06, 0x07, 0xa9, 0x81, 0x4f, 0xb5, 0xa1, 0x01, 0x6d, 0x58, 0x68, 0xb5, 0xe1, 0x3b, 0x95, 0xfb, 0xa5, 0xca, 0x17, 0xd9, 0xe3, 0x07, 0x01, 0x6d, 0x98, 0x59, 0xdd, 0x06, 0x11, 0x44, 0x1b, 0x82, 0x29, 0xe1, 0xa8, 0xd5, 0x86, 0x2f, 0x69, 0xc3, 0x0e, 0xd5, 0x86, 0xff, 0x37, 0xae, 0x18, 0xde, 0x91, 0x9e, 0x9a, 0x02, 0x5f, 0x1b, 0xe9, 0xa9, 0x1f, 0xe0, 0x6c, 0x3e, 0x9c, 0x7d, 0x4a, 0x54, 0xfa, 0xe1, 0x6c, 0x1d, 0x3d, 0x75, 0x21, 0xbc, 0xad, 0x45, 0x25, 0xec, 0xb4, 0xe6, 0x0a, 0xa2, 0x72, 0x2b, 0x51, 0x99, 0x4d, 0xab, 0xee, 0xb5, 0x7e, 0xe7, 0x61, 0xfe, 0xa2, 0x3c, 0xda, 0xfc, 0x45, 0x39, 0x9c, 0xfe, 0x87, 0x5e, 0x9b, 0xa6, 0x7a, 0x6d, 0xc4, 0x69, 0x37, 0x3d, 0xb6, 0x80, 0x1e, 0xdb, 0x04, 0xcf, 0x97, 0x06, 0xbf, 0x24, 0xf4, 0xea, 0xeb, 0xfc, 0x08, 0x3b, 0xbd, 0x36, 0x52, 0x5d, 0xeb, 0x67, 0xa8, 0xcc, 0x23, 0x6a, 0x9f, 0x24, 0x6a, 0xa7, 0x12, 0xb5, 0x57, 0x12, 0xb5, 0xe3, 0xe9, 0x8f, 0x3e, 0xf4, 0x47, 0x57, 0x72, 0x9e, 0x91, 0xf4, 0xc9, 0xe5, 0xf4, 0xc9, 0xfb, 0xf4, 0xc9, 0xd3, 0x44, 0xed, 0x0a, 0x22, 0xf6, 0x7d, 0xc7, 0x0e, 0xe1, 0x24, 0x5a, 0xa7, 0x90, 0xd3, 0x8c, 0xfc, 0xff, 0x71, 0x8d, 0x7b, 0x9d, 0x36, 0x3f, 0x64, 0x5d, 0x1b, 0x74, 0x35, 0xa5, 0x3c, 0x4f, 0x7b, 0xf7, 0x52, 0xc2, 0x6c, 0x4a, 0x18, 0x48, 0x5d, 0x2e, 0x51, 0xba, 0xb6, 0xad, 0x66, 0x1e, 0x13, 0x0d, 0xd8, 0x4b, 0x26, 0x7b, 0x79, 0x46, 0xdd, 0x63, 0xb4, 0xa7, 0x7c, 0x81, 0xf6, 0xcf, 0x67, 0xb6, 0xab, 0x64, 0xab, 0x92, 0x9a, 0x5f, 0x7b, 0xb3, 0xc7, 0x47, 0xcc, 0x3d, 0x99, 0xd9, 0x34, 0x7b, 0x3b, 0x29, 0x0c, 0x4a, 0x2e, 0xe7, 0x93, 0x3b, 0xd9, 0xd2, 0x55, 0x93, 0x2b, 0xb3, 0x45, 0xbe, 0x75, 0x74, 0xc5, 0x43, 0x5d, 0x4e, 0xd4, 0xee, 0x69, 0x0d, 0xef, 0x6e, 0xac, 0xfd, 0x6f, 0xad, 0xd2, 0xd3, 0x70, 0xeb, 0x37, 0xb8, 0xe6, 0x1c, 0x7e, 0x82, 0x96, 0x55, 0x5a, 0xbf, 0xa2, 0xdd, 0x62, 0x9d, 0x31, 0xba, 0x2d, 0x60, 0xa6, 0xdd, 0x65, 0x9d, 0x17, 0x77, 0x82, 0x7a, 0x1c, 0xa1, 0xe5, 0x7e, 0x5a, 0x7e, 0x04, 0x57, 0x96, 0x8b, 0x2b, 0xcb, 0xb5, 0xce, 0x8b, 0x3b, 0x70, 0xd6, 0x3a, 0xed, 0x3a, 0xa3, 0x4e, 0xa1, 0x81, 0xc7, 0x82, 0x88, 0x54, 0x73, 0xc4, 0x6d, 0x37, 0x23, 0xb5, 0xde, 0x99, 0xfc, 0x7f, 0x7e, 0xfc, 0xc8, 0x46, 0x0b, 0x4c, 0x56, 0xab, 0x7f, 0x8b, 0x79, 0xf4, 0xbf, 0x7c, 0xde, 0xeb, 0x6d, 0xa2, 0x0d, 0xa3, 0x2f, 0x9b, 0xfd, 0x6c, 0x91, 0x39, 0xe4, 0xac, 0x65, 0x96, 0x42, 0xed, 0xb0, 0xd4, 0xc9, 0xbc, 0xa3, 0xe6, 0x21, 0xfa, 0xfa, 0x6b, 0x66, 0xa2, 0xd7, 0xa9, 0xc9, 0xb7, 0x70, 0x59, 0xaa, 0x3d, 0x48, 0xa9, 0x0f, 0xcb, 0x65, 0xda, 0x6c, 0xb9, 0xce, 0xbc, 0xd6, 0x67, 0xd0, 0x3a, 0xf9, 0x86, 0x79, 0x3f, 0x58, 0xf8, 0xf3, 0xc3, 0x9f, 0x9f, 0x3d, 0x2d, 0x63, 0x4f, 0xcb, 0xcc, 0xab, 0x6d, 0xda, 0x87, 0x89, 0x44, 0xfb, 0x70, 0xd1, 0xc1, 0x3e, 0x92, 0xe7, 0x51, 0xa2, 0x9b, 0x7d, 0x2c, 0xcf, 0x57, 0xf1, 0xff, 0xb5, 0x3c, 0xe3, 0x8c, 0x88, 0xe5, 0x02, 0xfb, 0x3c, 0xd6, 0xe7, 0x8b, 0xed, 0xf6, 0xb7, 0xc4, 0x7e, 0xfb, 0xdb, 0xac, 0xbf, 0x23, 0x76, 0xda, 0xdf, 0x15, 0xa5, 0xf6, 0xf7, 0x45, 0x13, 0xfb, 0x17, 0xfc, 0xff, 0x25, 0xef, 0x7d, 0xc5, 0x7b, 0x2b, 0xc5, 0x35, 0xf6, 0xaf, 0xf9, 0xff, 0x1b, 0xde, 0xcf, 0xe4, 0xfd, 0xff, 0xf0, 0xff, 0x2a, 0x11, 0x6b, 0xff, 0x8e, 0xd7, 0xd6, 0xb0, 0xcd, 0x5a, 0xb6, 0xf9, 0x9e, 0xd7, 0x7e, 0xe0, 0xff, 0x75, 0x6c, 0xb3, 0x9e, 0x6d, 0xb2, 0xf8, 0x7f, 0xb7, 0x38, 0xcf, 0xee, 0x11, 0xbf, 0xd8, 0xf7, 0x88, 0x4a, 0xfb, 0x5e, 0x71, 0x9b, 0x3d, 0x9f, 0xff, 0x7f, 0x11, 0x25, 0xf6, 0x02, 0x71, 0xc2, 0xbe, 0x8f, 0xff, 0x0b, 0x45, 0x98, 0xbd, 0x48, 0xb4, 0x66, 0x4c, 0xcd, 0xd5, 0x3b, 0x88, 0xa5, 0xfa, 0x79, 0xe2, 0x7b, 0xbd, 0x93, 0xf8, 0x48, 0xef, 0x2c, 0xbe, 0xd1, 0xbb, 0x0a, 0x3b, 0x33, 0xc0, 0x0c, 0x66, 0x80, 0xed, 0xfa, 0x13, 0xa2, 0xa9, 0x3e, 0x53, 0x24, 0xea, 0xcf, 0x69, 0xa3, 0xf5, 0xe7, 0xb5, 0x61, 0xfa, 0xbf, 0xc4, 0xeb, 0xfa, 0x2c, 0x11, 0xa5, 0xcf, 0x16, 0x4d, 0x98, 0x0d, 0xfc, 0x8e, 0x58, 0xf1, 0x82, 0x23, 0x4e, 0xcc, 0x73, 0xc4, 0x8b, 0x04, 0x47, 0x22, 0xeb, 0xad, 0xc4, 0xfb, 0x8e, 0xd6, 0xac, 0xa7, 0xb2, 0xde, 0x96, 0xd7, 0xdb, 0xb1, 0xde, 0x81, 0xf5, 0xf3, 0x78, 0xbd, 0xa3, 0x48, 0xa0, 0x07, 0x46, 0x38, 0x3f, 0x93, 0x55, 0xce, 0xe5, 0xda, 0x00, 0xe7, 0xbf, 0xb5, 0x11, 0xce, 0x15, 0x62, 0xa6, 0xf3, 0x73, 0x11, 0xed, 0xfc, 0x42, 0x44, 0x3a, 0x57, 0x8a, 0xae, 0x62, 0x96, 0x08, 0x92, 0x4b, 0x45, 0x30, 0x08, 0x91, 0x2f, 0x8a, 0x66, 0xf2, 0x01, 0x91, 0x24, 0xb7, 0xd2, 0x63, 0x79, 0xa2, 0x1d, 0x73, 0x67, 0x7b, 0xdc, 0xce, 0x46, 0xb9, 0x9c, 0xf9, 0x75, 0xa9, 0xba, 0x4f, 0x37, 0xf9, 0x02, 0x3d, 0x78, 0x94, 0xde, 0x9a, 0x2d, 0x8e, 0x31, 0xfb, 0x1e, 0x97, 0x2f, 0x88, 0x13, 0xe0, 0x37, 0xa2, 0xdf, 0x06, 0x42, 0xc8, 0xd0, 0xc2, 0x88, 0xfe, 0x06, 0x72, 0xab, 0xed, 0x6a, 0x99, 0x65, 0xbb, 0x5d, 0x66, 0x05, 0x65, 0xca, 0x9f, 0x83, 0x56, 0xcb, 0xad, 0x41, 0x3f, 0x80, 0x75, 0x72, 0x63, 0xd0, 0x7a, 0x59, 0x1e, 0x94, 0xc5, 0xfa, 0x06, 0x32, 0xb8, 0x8d, 0x60, 0x8b, 0x7c, 0x21, 0x68, 0x3b, 0xcf, 0x3f, 0xe1, 0x80, 0x98, 0xaf, 0xd5, 0x75, 0xd9, 0x77, 0xc9, 0xaf, 0x82, 0xf2, 0xc0, 0x1e, 0xf9, 0x82, 0xbd, 0xbd, 0x5c, 0x6a, 0xef, 0x00, 0xce, 0x03, 0x1d, 0x41, 0x27, 0xd0, 0x19, 0x74, 0x01, 0x5d, 0x41, 0x37, 0xd0, 0x1d, 0xf4, 0x00, 0x69, 0xf2, 0x33, 0x7b, 0x4f, 0x9e, 0xcf, 0x07, 0x17, 0x80, 0x5e, 0xa0, 0x37, 0x48, 0x07, 0x7d, 0x40, 0x5f, 0xd0, 0x0f, 0xf4, 0x07, 0x03, 0xc0, 0x40, 0x30, 0x08, 0x64, 0x80, 0x0b, 0xc1, 0x60, 0x70, 0x11, 0x18, 0x02, 0x86, 0x82, 0x61, 0xb2, 0x90, 0x79, 0xbe, 0x90, 0x79, 0x7e, 0x29, 0xf3, 0xfc, 0x52, 0xe6, 0xf9, 0x42, 0xe6, 0xf9, 0x42, 0xe6, 0xf9, 0xa5, 0xcc, 0xf3, 0x4b, 0x99, 0xe7, 0x97, 0x32, 0xcf, 0x2f, 0x65, 0x9e, 0x5f, 0xca, 0x3c, 0xbf, 0x94, 0x79, 0x7e, 0x29, 0xf3, 0x7c, 0x21, 0xf3, 0x7c, 0x21, 0xf3, 0xfc, 0x52, 0xe6, 0xf9, 0xa5, 0xcc, 0xf3, 0x85, 0xcc, 0xf3, 0x85, 0xcc, 0xf3, 0x4b, 0xed, 0xd7, 0x53, 0xbf, 0xf1, 0xe0, 0x06, 0xd6, 0x6f, 0x94, 0x2f, 0xda, 0x6f, 0x02, 0x37, 0xcb, 0x97, 0xec, 0x65, 0xf2, 0x01, 0x22, 0xe3, 0x01, 0xbb, 0x4f, 0x3e, 0xa0, 0x8f, 0x92, 0x5b, 0xf5, 0xd1, 0xe0, 0x52, 0x30, 0x06, 0x5c, 0x06, 0xae, 0x00, 0x57, 0x82, 0xb1, 0xe0, 0x2a, 0x30, 0x95, 0xfc, 0xea, 0x21, 0xf0, 0x30, 0x20, 0xf7, 0xd4, 0xa7, 0xc9, 0x2c, 0xfd, 0x51, 0xf0, 0x18, 0x78, 0x1c, 0x4c, 0x07, 0x4f, 0xc8, 0x02, 0xfd, 0x49, 0x9e, 0x9f, 0x02, 0x4f, 0x83, 0x67, 0xc0, 0x0c, 0xf0, 0x2c, 0x98, 0xc9, 0x7b, 0xb3, 0xc0, 0x6c, 0x30, 0x87, 0xff, 0x5f, 0x00, 0x2f, 0xca, 0x8d, 0x8e, 0x7f, 0xcb, 0x9f, 0x1d, 0x2b, 0x40, 0xa6, 0x3c, 0xe6, 0xf8, 0x0f, 0xf8, 0x16, 0xac, 0x02, 0xab, 0xe5, 0x31, 0xe7, 0x6c, 0x59, 0xe9, 0x9c, 0x83, 0xcb, 0x36, 0x7f, 0x8f, 0x53, 0xc1, 0xfc, 0xfa, 0x06, 0x3a, 0xe3, 0x45, 0x67, 0xbc, 0xcc, 0xb3, 0x4b, 0xb4, 0xeb, 0x99, 0xd5, 0x6f, 0xe6, 0xbd, 0xd0, 0x73, 0xbd, 0xab, 0xee, 0xfe, 0xf0, 0x29, 0x1a, 0x65, 0xf0, 0xee, 0x2d, 0xbc, 0xfb, 0x09, 0xef, 0x2e, 0xe2, 0xdd, 0x49, 0xd6, 0xf9, 0xb8, 0xcd, 0xac, 0xf3, 0xf4, 0xc2, 0xc9, 0x68, 0x1c, 0x7c, 0x62, 0x95, 0x76, 0x03, 0x73, 0xe6, 0x4d, 0xe0, 0x66, 0xd1, 0x19, 0x6d, 0x6a, 0x68, 0xdd, 0xa1, 0x32, 0x0c, 0xdd, 0x5f, 0x23, 0x0c, 0x4a, 0xdb, 0x60, 0xfe, 0x12, 0x5b, 0xcc, 0xe1, 0x13, 0x3d, 0xe5, 0xcf, 0x94, 0xf4, 0x83, 0xe5, 0x55, 0x98, 0x51, 0x85, 0x23, 0xa4, 0x87, 0x70, 0xb0, 0x98, 0x57, 0x6e, 0x9e, 0x8d, 0xdf, 0x98, 0x83, 0xf7, 0xee, 0xc9, 0x8c, 0x36, 0xd8, 0xfc, 0xdd, 0x81, 0x3c, 0x6c, 0x3a, 0x1a, 0xf3, 0x77, 0xd9, 0x35, 0x67, 0x0e, 0x6b, 0x2f, 0xb3, 0xe5, 0x66, 0x71, 0x9e, 0x7c, 0x5c, 0x74, 0x04, 0x9d, 0x40, 0x17, 0x14, 0xb7, 0x2b, 0xe8, 0x06, 0xba, 0x83, 0x1e, 0x20, 0x0d, 0xf4, 0x94, 0xe3, 0xc4, 0xf9, 0xe0, 0x02, 0xd0, 0x9b, 0xff, 0xd3, 0x41, 0x1f, 0xe9, 0x11, 0xe4, 0xed, 0xa2, 0x1f, 0xe8, 0x0f, 0x06, 0x80, 0x81, 0x60, 0x10, 0xc8, 0x00, 0x17, 0x82, 0xc1, 0xe0, 0x22, 0x30, 0x04, 0x5c, 0x0c, 0x86, 0x82, 0x61, 0x60, 0x38, 0x18, 0x01, 0x2e, 0x01, 0x23, 0xc1, 0x28, 0x30, 0x1a, 0x5c, 0x0a, 0xc6, 0x80, 0xcb, 0xc0, 0xe5, 0xe0, 0x0a, 0x70, 0x25, 0x18, 0x0b, 0xae, 0x02, 0x57, 0x83, 0x6b, 0xc0, 0xb5, 0xe0, 0x3a, 0xf9, 0xb4, 0x18, 0x27, 0x1f, 0x13, 0x77, 0xc8, 0x95, 0xe2, 0x6e, 0x30, 0x01, 0xdc, 0x03, 0xee, 0xa5, 0x6e, 0xf7, 0x81, 0xfb, 0xc1, 0x44, 0xfe, 0x9f, 0x4c, 0x9d, 0xa7, 0xf0, 0xfc, 0x80, 0x7c, 0x4e, 0x3c, 0x08, 0xa6, 0x82, 0x87, 0xc0, 0x23, 0x94, 0x31, 0x8d, 0x7c, 0xe8, 0x51, 0xf0, 0x18, 0x78, 0x1c, 0x4c, 0x07, 0x4f, 0x80, 0x27, 0xc1, 0x53, 0xe0, 0x69, 0xf0, 0x0c, 0x98, 0x01, 0xfb, 0xcf, 0x82, 0x99, 0x72, 0x9f, 0x78, 0x4e, 0x16, 0x8a, 0xe7, 0x99, 0x2f, 0xfe, 0xc5, 0xfa, 0x2c, 0x75, 0x65, 0x81, 0xce, 0xf4, 0xec, 0x4e, 0x7a, 0x76, 0x8b, 0xd6, 0x4b, 0xa6, 0x6a, 0xbd, 0x41, 0x7f, 0xd9, 0x45, 0x1b, 0x00, 0x06, 0xe2, 0x9f, 0x07, 0xc9, 0xeb, 0xb4, 0x0c, 0x9e, 0x2f, 0xe4, 0x79, 0xb0, 0x7c, 0x58, 0x1b, 0x2a, 0xfb, 0x6b, 0xc3, 0xc1, 0xf5, 0xea, 0x5e, 0x3e, 0xd5, 0xd7, 0x59, 0xf6, 0xca, 0x29, 0x5a, 0xa9, 0x5c, 0x63, 0x9b, 0x24, 0xa7, 0xd8, 0xa8, 0xaf, 0x6d, 0xaa, 0x5c, 0x69, 0x9b, 0x06, 0xa8, 0x97, 0x8d, 0x7a, 0xd9, 0x9e, 0x40, 0x11, 0x66, 0xc8, 0xa7, 0x6d, 0x33, 0x79, 0xef, 0x79, 0xd6, 0x5f, 0x94, 0xcf, 0xd9, 0x5e, 0x61, 0x7d, 0xae, 0xdc, 0x67, 0x9b, 0xcf, 0xff, 0xef, 0x49, 0x8f, 0xed, 0x7d, 0xb0, 0x00, 0x7c, 0x00, 0x16, 0x82, 0x0f, 0xc1, 0x22, 0xf0, 0x11, 0x58, 0x0c, 0x3e, 0x06, 0x4b, 0x28, 0xeb, 0x13, 0x59, 0x14, 0x0c, 0x6f, 0xc1, 0x4f, 0xc9, 0x37, 0x83, 0x9f, 0x01, 0x33, 0xe4, 0x77, 0xc1, 0x33, 0xe5, 0x77, 0x21, 0xdf, 0x49, 0x4f, 0xc8, 0x1a, 0xb0, 0x05, 0x6c, 0x05, 0xdb, 0xc0, 0x76, 0x90, 0x03, 0x0a, 0xe4, 0x94, 0x90, 0x12, 0x39, 0x2e, 0xc4, 0x27, 0xd7, 0x84, 0xb6, 0x96, 0x2b, 0x43, 0x93, 0x65, 0x51, 0xe8, 0x09, 0x39, 0x45, 0x7f, 0x43, 0x3e, 0xae, 0xbf, 0x09, 0xe6, 0x81, 0xf9, 0xc0, 0xbc, 0xfe, 0xf3, 0xdb, 0xea, 0x1a, 0xd0, 0x8f, 0xeb, 0xef, 0x82, 0xf7, 0xc0, 0x02, 0xf0, 0x01, 0x58, 0x08, 0x3e, 0x04, 0x8b, 0xc0, 0x47, 0x60, 0x31, 0xf8, 0x18, 0x2c, 0x01, 0x9f, 0x80, 0x4f, 0xc1, 0x52, 0xf0, 0x19, 0x58, 0x06, 0x96, 0x83, 0x7f, 0x83, 0x15, 0xe0, 0x2b, 0xb0, 0x12, 0x7c, 0x0d, 0xbe, 0x01, 0x99, 0x60, 0x0d, 0xfb, 0x5f, 0x0b, 0xbe, 0x07, 0x3f, 0x80, 0x0d, 0x60, 0xa3, 0x1c, 0xa7, 0xff, 0x08, 0x36, 0x81, 0x6c, 0xb0, 0x15, 0x6c, 0xe3, 0xf5, 0xed, 0x20, 0x07, 0xe4, 0x82, 0x9f, 0xc0, 0x0e, 0xf0, 0x33, 0x70, 0x81, 0x3c, 0xb0, 0x1b, 0x78, 0xc0, 0x1e, 0xb0, 0x17, 0xe4, 0x83, 0x5f, 0x00, 0xed, 0xd6, 0xf7, 0x81, 0x22, 0x50, 0x0c, 0xf6, 0x83, 0x12, 0x40, 0x7f, 0xe9, 0x65, 0xe0, 0x00, 0xf0, 0x49, 0x8f, 0x7e, 0x08, 0x1c, 0x06, 0xe5, 0xe0, 0x08, 0xa8, 0x00, 0x47, 0xc1, 0x31, 0xe0, 0x07, 0x95, 0xe0, 0x57, 0x70, 0x1c, 0x9c, 0x00, 0x55, 0xe0, 0x24, 0x38, 0x05, 0x7e, 0x03, 0xa7, 0x81, 0x94, 0x4f, 0x1b, 0x02, 0x68, 0xc0, 0x06, 0x82, 0x40, 0x30, 0x08, 0x01, 0xa1, 0xf2, 0x31, 0xc3, 0x0e, 0x74, 0x60, 0x80, 0x06, 0xa0, 0x21, 0x68, 0x04, 0x32, 0xe4, 0x4a, 0xe3, 0x42, 0x30, 0x18, 0x5c, 0x0c, 0x86, 0x82, 0x61, 0x60, 0x38, 0x18, 0x01, 0x2e, 0x01, 0x23, 0xc1, 0x28, 0x30, 0x1a, 0x8c, 0x01, 0x97, 0xc9, 0x29, 0xc6, 0xe5, 0xe0, 0x0a, 0x70, 0x25, 0x18, 0x0b, 0xae, 0x02, 0x57, 0x83, 0x6b, 0xc0, 0xf5, 0x60, 0x3c, 0xb8, 0x01, 0xdc, 0x08, 0x6e, 0x02, 0x37, 0x83, 0x5b, 0xc0, 0xad, 0xe0, 0x36, 0x70, 0x3b, 0xb8, 0x03, 0xdc, 0x09, 0xee, 0x02, 0x77, 0x83, 0x09, 0xe0, 0x1e, 0x30, 0x15, 0x3c, 0x04, 0x1e, 0x06, 0xc4, 0xb1, 0xf1, 0x28, 0x78, 0x02, 0x3c, 0x09, 0x9e, 0x02, 0x4f, 0x83, 0x67, 0xc0, 0x0c, 0xf0, 0x2c, 0x78, 0x0e, 0x3c, 0x0f, 0xfe, 0x25, 0x9f, 0x33, 0x66, 0x81, 0xd9, 0x60, 0x0e, 0x20, 0xce, 0x8d, 0x97, 0xc0, 0xcb, 0xe0, 0x15, 0x30, 0x17, 0xbc, 0x8e, 0x5f, 0x7e, 0x03, 0xbc, 0x09, 0xe6, 0x81, 0xf9, 0xe0, 0x2d, 0xf0, 0x36, 0x78, 0x07, 0xbc, 0x0b, 0xde, 0x03, 0xef, 0x83, 0x05, 0xe0, 0x03, 0xb0, 0x10, 0x7c, 0x08, 0x16, 0x81, 0x8f, 0xc0, 0x62, 0xf0, 0x31, 0x60, 0x3c, 0x18, 0x8c, 0x07, 0xe3, 0x53, 0xb0, 0x14, 0x7c, 0x06, 0x96, 0x81, 0xe5, 0xe0, 0xdf, 0x60, 0x05, 0xf8, 0x1c, 0x7c, 0x01, 0xbe, 0x04, 0x5f, 0x81, 0x95, 0xe0, 0x6b, 0xf0, 0x0d, 0xc8, 0x04, 0xff, 0x01, 0xdf, 0x82, 0x55, 0x60, 0xb5, 0x5c, 0x63, 0x7c, 0x07, 0xd6, 0x80, 0xb5, 0xe0, 0x7b, 0xf0, 0x03, 0x58, 0x07, 0xd6, 0x83, 0x0d, 0x60, 0x23, 0xf8, 0x11, 0x6c, 0x02, 0xd9, 0x60, 0x33, 0xd8, 0x02, 0xb6, 0x82, 0x6d, 0x60, 0x3b, 0xc8, 0x01, 0xb9, 0x60, 0x07, 0xf8, 0x19, 0xb8, 0xe4, 0x3e, 0x63, 0x27, 0xd8, 0x25, 0x0b, 0x8d, 0x3c, 0xb0, 0x1b, 0x78, 0xc0, 0x1e, 0xb0, 0x17, 0xe4, 0x83, 0x02, 0xf2, 0xea, 0x7d, 0xa0, 0x88, 0xed, 0x8a, 0xc1, 0x7e, 0x50, 0x02, 0xca, 0xc0, 0x01, 0xe0, 0x03, 0x07, 0xc1, 0x21, 0x70, 0x18, 0x1c, 0x95, 0xeb, 0xcc, 0xeb, 0xa5, 0x1b, 0x7e, 0x50, 0x09, 0x7e, 0x05, 0xc7, 0xc9, 0x34, 0xdd, 0x72, 0xa5, 0x63, 0x8f, 0xec, 0xef, 0x38, 0x26, 0xa7, 0x38, 0xfc, 0xa0, 0x12, 0xfc, 0x0a, 0x8e, 0x83, 0x13, 0x72, 0x8d, 0xa3, 0x0a, 0x9c, 0x04, 0xa7, 0xc0, 0x6f, 0x72, 0x8d, 0x93, 0x71, 0xa6, 0x1d, 0xc3, 0x51, 0x15, 0xe1, 0xa8, 0x8a, 0x70, 0x53, 0xbf, 0x8a, 0x54, 0xfc, 0x6e, 0x5b, 0x79, 0x1c, 0x37, 0x75, 0x0a, 0x37, 0x55, 0x82, 0xc2, 0xf8, 0x50, 0x18, 0x1f, 0x0a, 0xe3, 0x13, 0x9d, 0x41, 0x17, 0xe9, 0x47, 0x65, 0xfc, 0xa8, 0x8c, 0x1f, 0x95, 0xf1, 0xa3, 0x32, 0x7e, 0x54, 0xc6, 0x8f, 0xca, 0x54, 0xa2, 0x32, 0x95, 0xa8, 0x4c, 0x25, 0x2a, 0xe3, 0x47, 0x65, 0xfc, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x83, 0xbb, 0x05, 0xd7, 0x02, 0x5c, 0x0c, 0x2a, 0x53, 0x29, 0xae, 0xa7, 0xde, 0xe3, 0xc1, 0x0d, 0xe0, 0x46, 0x5c, 0xe1, 0x4d, 0xe0, 0x66, 0x70, 0x2b, 0xb8, 0x0d, 0xdc, 0xce, 0xeb, 0x77, 0xc8, 0x52, 0x71, 0x27, 0xcf, 0x77, 0x81, 0xbb, 0x59, 0x9f, 0x00, 0xee, 0x01, 0xf7, 0xd2, 0x8e, 0xfb, 0xc0, 0xfd, 0x60, 0x22, 0xff, 0x4f, 0xe2, 0xfd, 0xc9, 0x94, 0x39, 0x85, 0xf5, 0x07, 0x70, 0x09, 0x0f, 0x82, 0xa9, 0xe0, 0x21, 0xf0, 0x30, 0xef, 0x3d, 0xc2, 0x7e, 0xa7, 0xc9, 0x6c, 0x94, 0x29, 0x1b, 0x65, 0xca, 0x46, 0x99, 0xb2, 0x51, 0xa6, 0x6c, 0x94, 0x29, 0x1b, 0x65, 0xca, 0x46, 0x99, 0xb2, 0x51, 0xa6, 0x6c, 0x94, 0x29, 0x1b, 0x65, 0xf2, 0xa1, 0x4c, 0x3e, 0x94, 0x29, 0x0b, 0x65, 0xca, 0x43, 0x99, 0x56, 0xa2, 0x4c, 0x6b, 0x51, 0xa6, 0xcd, 0xa8, 0x8a, 0x1f, 0x55, 0xf1, 0xe1, 0x4a, 0x4b, 0x70, 0xa5, 0x25, 0xb6, 0xfb, 0x58, 0xbf, 0x1f, 0x4c, 0x92, 0x7e, 0x54, 0xa6, 0x12, 0x95, 0x29, 0xb5, 0x3d, 0xc4, 0xff, 0xd3, 0x78, 0x66, 0x3f, 0x28, 0x4d, 0x36, 0x4a, 0xb3, 0x19, 0xa5, 0x29, 0x44, 0x69, 0x2a, 0x51, 0x9a, 0xcd, 0x28, 0xcd, 0x7e, 0x94, 0xa6, 0x12, 0xa5, 0x59, 0x6b, 0x7b, 0x93, 0x6d, 0xe7, 0x81, 0xf9, 0xbc, 0xfe, 0x1e, 0x4e, 0xf7, 0x7d, 0xb0, 0x00, 0x7c, 0x00, 0x16, 0x82, 0x0f, 0xc1, 0x22, 0xf0, 0x11, 0x58, 0x0c, 0x3e, 0x06, 0x4b, 0x28, 0xf3, 0x13, 0x99, 0x8d, 0xe2, 0x54, 0x06, 0x4f, 0x96, 0xbe, 0x90, 0x70, 0xf0, 0x9d, 0xcc, 0x42, 0x69, 0xb2, 0x42, 0x7e, 0xc0, 0xb5, 0x6c, 0xe1, 0x79, 0x2b, 0xd8, 0x06, 0xb6, 0x83, 0x1c, 0x50, 0x40, 0xae, 0x53, 0x22, 0x2b, 0x51, 0x1b, 0x1f, 0x6a, 0x53, 0x1a, 0x9a, 0xc4, 0x73, 0xb2, 0xcc, 0x46, 0x71, 0xfc, 0x38, 0xe3, 0x22, 0x9c, 0x71, 0x11, 0xce, 0xb8, 0x08, 0x67, 0x5c, 0x84, 0x33, 0x2e, 0xc2, 0x19, 0x17, 0xe1, 0x8c, 0x8b, 0x70, 0xc6, 0x45, 0x38, 0xe3, 0x22, 0xdc, 0x70, 0x11, 0x6e, 0xb8, 0x08, 0x37, 0x5c, 0x84, 0x1b, 0x2e, 0xc2, 0x0d, 0x17, 0xe1, 0x86, 0x8b, 0x70, 0xc3, 0x45, 0xb8, 0xe1, 0x22, 0xdc, 0x70, 0x11, 0x6e, 0xb8, 0x08, 0x37, 0x5c, 0x84, 0x1b, 0x2e, 0xc2, 0x0d, 0x17, 0xe1, 0x86, 0x8b, 0x70, 0xc3, 0x45, 0xb8, 0xe1, 0x22, 0xdc, 0x70, 0x11, 0x6e, 0xb8, 0x08, 0xc7, 0x5a, 0x84, 0x4b, 0xfd, 0x15, 0x87, 0xfa, 0xab, 0x7e, 0x9f, 0x2c, 0xd3, 0xef, 0x07, 0x13, 0xe5, 0x71, 0x7d, 0x12, 0x98, 0x0c, 0xa6, 0x80, 0x07, 0xc0, 0x83, 0xe0, 0x61, 0x79, 0x0a, 0x57, 0x7a, 0x0a, 0x57, 0x5a, 0x82, 0x2b, 0x2d, 0xc1, 0x95, 0x96, 0xe0, 0x4a, 0x4b, 0x70, 0xa2, 0x25, 0x38, 0xd1, 0x12, 0x9c, 0x68, 0x09, 0x4e, 0xb4, 0x04, 0x27, 0x5a, 0x82, 0x6a, 0xfa, 0x50, 0x4d, 0x1f, 0xaa, 0xe9, 0x43, 0x35, 0x7d, 0xa8, 0xa6, 0x0f, 0xd5, 0xf4, 0xa1, 0x9a, 0x3e, 0x54, 0xd3, 0x87, 0x6a, 0xfa, 0x50, 0x4d, 0x1f, 0xaa, 0xe9, 0x43, 0x35, 0x7d, 0xa8, 0xa6, 0x0f, 0xd5, 0xf4, 0xa1, 0x9a, 0x3e, 0x54, 0xd3, 0x87, 0x6a, 0xfa, 0x50, 0x4d, 0x1f, 0xaa, 0xe9, 0x43, 0x35, 0x7d, 0xa8, 0xa6, 0x0f, 0xd5, 0xf4, 0xa1, 0x9a, 0x3e, 0x54, 0xd3, 0x87, 0x6a, 0xfa, 0x50, 0x4d, 0x1f, 0xaa, 0xe9, 0x43, 0x35, 0x7d, 0xa8, 0xa6, 0x0f, 0xd5, 0xf4, 0xa1, 0x9a, 0x3e, 0xfd, 0x5b, 0xb0, 0x0a, 0xac, 0x06, 0x6b, 0xa4, 0x1f, 0x05, 0xf5, 0xa3, 0xa0, 0x7e, 0x14, 0xd4, 0x8f, 0x82, 0xfa, 0x51, 0xd0, 0x4a, 0x14, 0xb4, 0x12, 0x05, 0xad, 0x44, 0x41, 0x2b, 0x51, 0xd0, 0x4a, 0x14, 0xd4, 0x8f, 0x82, 0xfa, 0x51, 0x50, 0x3f, 0x0a, 0xea, 0x47, 0x41, 0xfd, 0x28, 0xa8, 0x1f, 0x05, 0xf5, 0xa3, 0xa0, 0x7e, 0x14, 0xd4, 0x8f, 0x82, 0xfa, 0x51, 0x50, 0x3f, 0x0a, 0xea, 0x47, 0x41, 0xfd, 0x28, 0xa8, 0x1f, 0x05, 0xf5, 0xa3, 0xa0, 0x7e, 0x14, 0xd4, 0x8f, 0x82, 0xfa, 0x51, 0x50, 0x3f, 0x0a, 0xea, 0x47, 0x41, 0xfd, 0x28, 0xa8, 0x1f, 0x05, 0xf5, 0xa3, 0xa0, 0x7e, 0x14, 0x34, 0x0b, 0x05, 0xcd, 0x42, 0x41, 0xb3, 0x50, 0xd0, 0x2c, 0x14, 0x34, 0x0b, 0x05, 0xcd, 0x42, 0x41, 0xb3, 0x50, 0xd0, 0x2c, 0x14, 0x34, 0x0b, 0x05, 0xcd, 0x42, 0x41, 0xb3, 0x50, 0xd0, 0x2c, 0x14, 0x34, 0x0b, 0x05, 0xcd, 0x42, 0x41, 0xb3, 0x50, 0xd0, 0x2c, 0x14, 0x34, 0x0b, 0x05, 0xcd, 0x42, 0x41, 0x0b, 0x51, 0xd0, 0x42, 0x14, 0xb4, 0x10, 0x05, 0x2d, 0x44, 0x41, 0x0b, 0x51, 0xd0, 0x42, 0x14, 0xb4, 0x10, 0x05, 0xad, 0x44, 0x41, 0x2b, 0x51, 0xd0, 0x4a, 0x14, 0xb4, 0x12, 0x05, 0xad, 0x44, 0x41, 0x2b, 0x51, 0xd0, 0x4a, 0xa3, 0xb1, 0xf4, 0x19, 0x4d, 0x40, 0x53, 0xd0, 0x0c, 0x10, 0xa3, 0x46, 0x04, 0x88, 0x94, 0x47, 0x8c, 0x68, 0xd0, 0x02, 0xb4, 0x04, 0xb1, 0x20, 0x0e, 0xc4, 0x83, 0x04, 0x90, 0x08, 0x5a, 0x81, 0x36, 0x20, 0x19, 0xa4, 0x82, 0x0e, 0xe0, 0x3c, 0xd0, 0x09, 0x74, 0x01, 0xdd, 0x28, 0xa7, 0x3b, 0xe8, 0x01, 0xd2, 0x40, 0x4f, 0xd0, 0x0b, 0xf4, 0x06, 0xe9, 0xa0, 0x0f, 0xe8, 0x0b, 0xfa, 0x81, 0xfe, 0x60, 0x00, 0x18, 0x08, 0x32, 0x64, 0x29, 0xaa, 0x5e, 0x8a, 0xaa, 0x97, 0xa2, 0xea, 0xa5, 0xa8, 0x7a, 0x29, 0xaa, 0x5e, 0x8a, 0xaa, 0x97, 0xa2, 0xea, 0xa5, 0xa8, 0x7a, 0x29, 0xaa, 0x5e, 0x8a, 0xaa, 0x97, 0xa2, 0xea, 0xa5, 0xa8, 0x7a, 0x29, 0xaa, 0xee, 0x47, 0xd5, 0xfd, 0xa8, 0xba, 0x1f, 0x55, 0xf7, 0xa3, 0xea, 0x7e, 0x54, 0xdd, 0x8f, 0xaa, 0xfb, 0x51, 0x75, 0x3f, 0xaa, 0xee, 0x47, 0xd5, 0xfd, 0xa8, 0xba, 0x1f, 0x55, 0xf7, 0xa3, 0xea, 0x7e, 0x54, 0xdd, 0x8f, 0xaa, 0xfb, 0x51, 0x75, 0x3f, 0xaa, 0xee, 0x47, 0xd5, 0xfd, 0xa8, 0xba, 0x1f, 0x55, 0xf7, 0xa3, 0xea, 0x7e, 0x54, 0xdd, 0x8f, 0xaa, 0xfb, 0x51, 0x75, 0x3f, 0xaa, 0xee, 0x47, 0xd5, 0xfd, 0xa8, 0xba, 0x1f, 0x55, 0x2f, 0x45, 0xd5, 0x4b, 0x51, 0xf5, 0x52, 0x54, 0xbd, 0x14, 0x55, 0x2f, 0x45, 0xd5, 0x4b, 0x51, 0xf5, 0x52, 0x54, 0xbd, 0x14, 0x55, 0x2f, 0x45, 0xd5, 0x4b, 0x51, 0xf5, 0x52, 0x54, 0x7d, 0x3f, 0xaa, 0xbe, 0x1f, 0x55, 0xdf, 0x8f, 0xaa, 0xef, 0x47, 0xd5, 0xf7, 0xa3, 0xea, 0xfb, 0x51, 0xf5, 0xfd, 0xa8, 0xfa, 0x7e, 0x54, 0x7d, 0x3f, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0xba, 0x0f, 0x55, 0xf7, 0xa1, 0xea, 0x3e, 0x54, 0xdd, 0x87, 0xaa, 0xfb, 0x50, 0x75, 0x1f, 0xaa, 0xee, 0x43, 0xd5, 0x7d, 0xa8, 0xba, 0x0f, 0x55, 0xf7, 0xa1, 0xea, 0x3e, 0x54, 0xdd, 0x87, 0xaa, 0xfb, 0x50, 0x75, 0x1f, 0xaa, 0xee, 0x43, 0xd5, 0x7d, 0xa8, 0xba, 0x0f, 0x55, 0xf7, 0xa1, 0xea, 0x3e, 0x54, 0xdd, 0x87, 0xaa, 0xfb, 0x50, 0x75, 0x1f, 0xaa, 0x9e, 0x85, 0xaa, 0x67, 0xa1, 0xea, 0x79, 0xa8, 0x7a, 0x1e, 0xaa, 0x9e, 0x87, 0xaa, 0xe7, 0xa1, 0xea, 0x79, 0xa8, 0x7a, 0x1e, 0xaa, 0x9e, 0x87, 0xaa, 0xaf, 0x44, 0xd5, 0x57, 0xa2, 0xea, 0x6b, 0x51, 0xf5, 0xb5, 0xa8, 0xfa, 0x5a, 0x54, 0x7d, 0x2d, 0xaa, 0xbe, 0x16, 0x55, 0x5f, 0x8b, 0xaa, 0xaf, 0x45, 0xd5, 0xd7, 0xa2, 0xea, 0x6b, 0x51, 0xf5, 0xb5, 0xa8, 0xfa, 0x66, 0x54, 0x7d, 0x33, 0xaa, 0xbe, 0x19, 0x55, 0xdf, 0x8c, 0xaa, 0x6f, 0x46, 0xd5, 0x37, 0xa3, 0xea, 0xa5, 0x28, 0xba, 0x1f, 0x45, 0xf7, 0xa3, 0xe8, 0x7e, 0x14, 0xdd, 0x8f, 0xa2, 0xfb, 0x51, 0x74, 0x1f, 0x8a, 0xee, 0x43, 0xd1, 0x7d, 0x28, 0xba, 0x0f, 0x45, 0xf7, 0xa1, 0xe8, 0x95, 0xb5, 0xe7, 0x57, 0xdd, 0x57, 0x7b, 0x7e, 0xd5, 0x3f, 0xff, 0x16, 0x29, 0x88, 0x72, 0x86, 0x52, 0xce, 0x08, 0xca, 0x19, 0xaa, 0x7e, 0x13, 0xba, 0x96, 0x4c, 0x69, 0x3d, 0x99, 0xd2, 0xfa, 0x9a, 0xec, 0x55, 0x6d, 0xd3, 0x9b, 0x6d, 0x5a, 0xb3, 0x4d, 0x92, 0xca, 0x97, 0x97, 0xb2, 0xcd, 0x27, 0x6c, 0xf3, 0x89, 0xf5, 0x6d, 0x92, 0x79, 0x3c, 0x31, 0x2b, 0xf8, 0x25, 0xb9, 0x2a, 0xf8, 0x15, 0xf9, 0x6d, 0xf0, 0xab, 0xf2, 0x07, 0x91, 0x61, 0x5d, 0xb7, 0x7f, 0x92, 0xd8, 0x8a, 0xea, 0x6d, 0x97, 0x5f, 0x8b, 0x5c, 0xf9, 0x91, 0xf8, 0x89, 0xdc, 0x6e, 0x87, 0x6c, 0x2f, 0x7e, 0x96, 0x73, 0xac, 0xeb, 0xf6, 0xbf, 0x20, 0xf2, 0xe4, 0x45, 0xd6, 0x75, 0xfb, 0xaf, 0x16, 0xbf, 0xc8, 0xc5, 0xd4, 0xbe, 0x2f, 0xb5, 0x1f, 0x21, 0xbc, 0xa7, 0xcb, 0x69, 0xc1, 0x65, 0xa2, 0x4c, 0xf6, 0x64, 0x8f, 0x77, 0x93, 0xc7, 0xbd, 0x6d, 0xe6, 0x68, 0xf6, 0xee, 0x72, 0x92, 0xbd, 0x07, 0x18, 0x0a, 0x86, 0xc9, 0x6d, 0xf6, 0xe1, 0x60, 0x04, 0xeb, 0x97, 0x80, 0x91, 0xac, 0x8f, 0x02, 0xa3, 0x59, 0xbf, 0x14, 0x8c, 0x01, 0x97, 0x81, 0xcb, 0xc1, 0x15, 0xe0, 0x4a, 0x30, 0x96, 0xf7, 0xaf, 0x02, 0x57, 0xb3, 0x7e, 0x0d, 0xb8, 0x96, 0xf5, 0xeb, 0xc0, 0x38, 0x39, 0x49, 0xdd, 0x8f, 0xcc, 0xba, 0x17, 0x99, 0xa3, 0x85, 0xfc, 0xda, 0xd1, 0x12, 0xd4, 0xbd, 0xa7, 0xd8, 0xd7, 0xf5, 0xee, 0x29, 0xf6, 0xb5, 0xa3, 0x0d, 0x48, 0x06, 0x29, 0xa0, 0xee, 0x3d, 0xc5, 0xbe, 0x0e, 0xbc, 0xa7, 0x98, 0xcd, 0x46, 0x46, 0xff, 0x8d, 0xb0, 0xe3, 0x33, 0x74, 0xd0, 0x10, 0xc4, 0x80, 0x96, 0x20, 0x16, 0x24, 0x80, 0xb6, 0xb2, 0x02, 0x3f, 0xe6, 0xc1, 0x8f, 0x79, 0xf0, 0x63, 0x1e, 0xfc, 0x98, 0x17, 0x3f, 0xe6, 0xc6, 0x8f, 0xb9, 0xf1, 0x63, 0x6e, 0xfc, 0x98, 0x1b, 0x3f, 0xe6, 0xc6, 0x8f, 0xb9, 0xf1, 0x63, 0x79, 0xf8, 0xb1, 0x3c, 0xfc, 0x58, 0x9e, 0xba, 0x3b, 0x6a, 0x6f, 0x5e, 0x4b, 0x07, 0x7d, 0xf8, 0xbf, 0x2f, 0xe8, 0x07, 0xfa, 0x83, 0x01, 0x60, 0x20, 0x18, 0x04, 0x32, 0xc0, 0x85, 0x60, 0x30, 0xb8, 0x08, 0x0c, 0x01, 0x17, 0x83, 0xa1, 0x60, 0x18, 0x18, 0x0e, 0x46, 0x80, 0x4b, 0xc0, 0x48, 0x30, 0x0a, 0x8c, 0x06, 0x97, 0x82, 0x31, 0xe0, 0x32, 0x70, 0x39, 0xb8, 0x02, 0x5c, 0x09, 0xc6, 0x82, 0xab, 0xc0, 0xd5, 0xe0, 0x1a, 0x70, 0x2d, 0xb8, 0x4e, 0xba, 0xf0, 0x64, 0xbb, 0xf0, 0x64, 0x5e, 0x3c, 0x99, 0x17, 0x4f, 0xe6, 0xc5, 0x93, 0x95, 0xe2, 0xc9, 0x4a, 0xf1, 0x64, 0xa5, 0xe6, 0x5d, 0x5c, 0xf1, 0x65, 0xa5, 0xf8, 0xb2, 0x52, 0x7c, 0x99, 0x17, 0x5f, 0xe6, 0xc5, 0x97, 0x79, 0xf1, 0x65, 0x5e, 0x7c, 0x99, 0x17, 0x5f, 0xe6, 0xc5, 0x97, 0x79, 0xf1, 0x65, 0x6e, 0x7c, 0x99, 0x1b, 0x5f, 0xe6, 0xc6, 0x97, 0x79, 0xf1, 0x65, 0x5e, 0x7c, 0x59, 0x1e, 0xbe, 0xcc, 0x8b, 0x2f, 0xf3, 0xe0, 0xcb, 0x3c, 0xf8, 0x32, 0x0f, 0xbe, 0xcc, 0x63, 0x5e, 0x25, 0x18, 0x5f, 0x96, 0x87, 0x2f, 0xf3, 0xe0, 0xcb, 0x3c, 0xf8, 0x32, 0x0f, 0xbe, 0xcc, 0x83, 0x2f, 0xf3, 0xe0, 0xcb, 0x3c, 0xf8, 0x32, 0x0f, 0xbe, 0xcc, 0x83, 0x2f, 0xf3, 0xe0, 0xcb, 0x3c, 0xf8, 0xb2, 0x62, 0x7c, 0x59, 0x31, 0xbe, 0xac, 0x00, 0x5f, 0xe6, 0xc5, 0x97, 0x95, 0x5b, 0x47, 0x0c, 0x0a, 0x35, 0x34, 0x10, 0x6f, 0xe6, 0xc6, 0x9b, 0x15, 0xdb, 0x32, 0xf0, 0x62, 0xc3, 0xc0, 0x68, 0xc0, 0xfc, 0x8c, 0x47, 0xf3, 0xe2, 0xd1, 0xbc, 0x78, 0x34, 0x37, 0x1e, 0x2d, 0x0f, 0x8f, 0xe6, 0xc5, 0xa3, 0x79, 0xf1, 0x68, 0x5e, 0x3c, 0x9a, 0x07, 0x8f, 0xe6, 0xc1, 0xa3, 0x15, 0xe2, 0xd1, 0x5c, 0xea, 0x4e, 0xb5, 0x33, 0xd9, 0xe6, 0x79, 0xfe, 0x7f, 0x91, 0xd7, 0x5f, 0x61, 0xdd, 0x3c, 0x22, 0xf0, 0x26, 0xdb, 0xce, 0x03, 0xf3, 0x79, 0xfd, 0x3d, 0x5e, 0x7b, 0x1f, 0x2c, 0x00, 0x1f, 0x80, 0x85, 0xe0, 0x43, 0xb0, 0x08, 0x7c, 0x04, 0x16, 0x83, 0x8f, 0xc1, 0x12, 0x3e, 0xff, 0x09, 0xb0, 0xee, 0x7c, 0x8b, 0x5f, 0xdb, 0x85, 0x5f, 0xf3, 0xe2, 0xd7, 0xbc, 0xf8, 0xb5, 0x3c, 0xfc, 0x5a, 0x1e, 0x7e, 0xad, 0xd4, 0xbc, 0x23, 0x2e, 0x9e, 0x2d, 0x0f, 0xcf, 0x96, 0x87, 0x67, 0xcb, 0xc3, 0xb3, 0xe5, 0xe1, 0xd9, 0xf2, 0x42, 0xd0, 0x7d, 0x7c, 0x9b, 0x1b, 0xdf, 0x96, 0x87, 0x6f, 0x2b, 0xc6, 0xb7, 0x79, 0xf1, 0x6d, 0x5e, 0x7c, 0x9b, 0x07, 0xdf, 0xe6, 0xb6, 0xa3, 0x25, 0x76, 0xb4, 0xc4, 0x8e, 0x96, 0xd8, 0xd1, 0x12, 0xfb, 0x44, 0xb0, 0x10, 0x7c, 0x08, 0x16, 0x81, 0x8f, 0xc0, 0x62, 0xf0, 0x31, 0x58, 0x82, 0x27, 0x68, 0x00, 0x88, 0x5f, 0xbd, 0x11, 0x68, 0x0c, 0x9a, 0x80, 0xa6, 0xa0, 0x19, 0x08, 0x07, 0x51, 0x20, 0x1a, 0xb4, 0x00, 0xc4, 0xb8, 0x4e, 0x8c, 0xeb, 0xc4, 0xb8, 0x1e, 0x07, 0xe2, 0x01, 0xb1, 0xae, 0x27, 0x82, 0x56, 0xa0, 0x35, 0x48, 0x02, 0x6d, 0x40, 0x32, 0x48, 0x01, 0xa9, 0xa0, 0x2d, 0x68, 0x07, 0xc8, 0x41, 0xf0, 0x75, 0x15, 0xf8, 0xba, 0x0a, 0x7c, 0x5d, 0x05, 0xbe, 0xae, 0x02, 0x5f, 0x57, 0x81, 0xaf, 0xab, 0xc0, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0x96, 0x87, 0x57, 0xcb, 0xc3, 0xab, 0xe5, 0xe1, 0xd5, 0xf2, 0xf0, 0x6a, 0x79, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0xb5, 0x3c, 0xbc, 0x5a, 0x1e, 0x5e, 0x2d, 0x0f, 0xaf, 0x96, 0x87, 0x57, 0xcb, 0xc3, 0xab, 0xe5, 0xe1, 0xd5, 0xf2, 0xf0, 0x6a, 0x79, 0x78, 0xb5, 0x3c, 0xbc, 0x5a, 0x1e, 0x5e, 0x2d, 0x0f, 0xaf, 0x96, 0x87, 0x57, 0xcb, 0xc3, 0xab, 0xe5, 0xe1, 0xd5, 0xf2, 0xf0, 0x6a, 0x79, 0x78, 0xb5, 0x3c, 0xbc, 0x5a, 0x1e, 0x5e, 0xcd, 0x85, 0x57, 0x73, 0xe1, 0xd5, 0x5c, 0x78, 0x35, 0x17, 0x5e, 0xcd, 0x85, 0x57, 0x73, 0xe1, 0xd5, 0x5c, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0x35, 0x2f, 0x5e, 0xcd, 0x8b, 0x57, 0xf3, 0xe2, 0xd5, 0xbc, 0x78, 0x35, 0x2f, 0x5e, 0xcd, 0x8b, 0x57, 0x2b, 0xc5, 0xab, 0x95, 0xe2, 0xd5, 0x4a, 0xf1, 0x6a, 0xa5, 0x78, 0xb5, 0x52, 0x75, 0xe7, 0xe6, 0x78, 0x9e, 0x13, 0x40, 0x22, 0x68, 0x05, 0xda, 0x80, 0x64, 0x90, 0x0a, 0x3a, 0x80, 0xf3, 0x40, 0x27, 0xd0, 0x45, 0x9e, 0xc6, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0xc5, 0x78, 0xb5, 0x62, 0xbc, 0x5a, 0x31, 0x5e, 0xad, 0x18, 0xaf, 0x56, 0x8c, 0x57, 0x2b, 0xc6, 0xab, 0x15, 0xe3, 0xd5, 0x8a, 0xf1, 0x6a, 0xc5, 0x78, 0xb5, 0x62, 0xbc, 0x5a, 0x31, 0x5e, 0xad, 0x18, 0xaf, 0x56, 0x8c, 0x57, 0x2b, 0xc6, 0xab, 0x15, 0xe3, 0xd5, 0x8a, 0xf1, 0x6a, 0xc5, 0x78, 0xb5, 0x62, 0xbc, 0x5a, 0x31, 0x5e, 0xad, 0x18, 0xaf, 0x56, 0x8c, 0x57, 0x2b, 0xc6, 0xab, 0x15, 0xe0, 0xd5, 0x0a, 0xf0, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x95, 0xe3, 0xd5, 0xca, 0xff, 0xc6, 0x11, 0x98, 0x42, 0xbc, 0x5a, 0x21, 0x5e, 0xad, 0x10, 0xaf, 0x56, 0x88, 0x57, 0x2b, 0xc4, 0xab, 0x15, 0xe2, 0xd5, 0xbc, 0x0e, 0x72, 0x23, 0x07, 0xb9, 0x91, 0xc3, 0x07, 0x0e, 0x82, 0x43, 0xe0, 0x18, 0x2a, 0xef, 0x07, 0x95, 0xe0, 0x57, 0x70, 0x1c, 0x9c, 0x90, 0xc5, 0xf8, 0xb7, 0x62, 0xfc, 0x5b, 0x31, 0xfe, 0xad, 0x18, 0xff, 0x56, 0x8c, 0x7f, 0xcb, 0xfb, 0xbf, 0xe4, 0x7e, 0xdb, 0xa6, 0xab, 0x6b, 0x81, 0xab, 0x4b, 0xc5, 0xd5, 0xb5, 0xf9, 0x1f, 0x5c, 0xcd, 0x26, 0x94, 0x56, 0xba, 0x69, 0xe5, 0x7e, 0x5a, 0x59, 0x4e, 0x2b, 0x8f, 0xb0, 0x87, 0x15, 0x94, 0x5e, 0xa0, 0xbe, 0xdf, 0x35, 0xcf, 0x12, 0xa8, 0xfc, 0x3f, 0x74, 0xd5, 0xff, 0x3f, 0x3f, 0xf7, 0xbc, 0xd1, 0x1f, 0x6d, 0x11, 0xf2, 0x83, 0xe8, 0xc4, 0x3e, 0xcd, 0xdf, 0xf7, 0x9a, 0x57, 0x30, 0x6e, 0xa0, 0x7e, 0x2f, 0x79, 0x9e, 0x98, 0x6a, 0x3c, 0x2f, 0x62, 0xd5, 0xa7, 0x6d, 0xd6, 0xf9, 0xff, 0x79, 0xc2, 0x66, 0x7f, 0xcf, 0x3c, 0x67, 0x49, 0xdf, 0xe4, 0x0c, 0x17, 0xcd, 0x45, 0x07, 0x21, 0xe4, 0x29, 0x79, 0x50, 0x4e, 0x94, 0x7b, 0xe5, 0x7e, 0xf9, 0xb2, 0x5c, 0x27, 0x97, 0xc8, 0xd9, 0x3c, 0xef, 0x90, 0x5e, 0x5e, 0x7f, 0x11, 0x1c, 0x96, 0x93, 0xe4, 0x62, 0x9e, 0xcb, 0xe4, 0x2e, 0xf9, 0xbc, 0xdc, 0x29, 0x97, 0xb1, 0x4d, 0xb1, 0xf8, 0x0b, 0x7f, 0xd2, 0x27, 0x2b, 0x64, 0x0e, 0xcf, 0x47, 0x64, 0x91, 0x2c, 0xc4, 0xc3, 0x98, 0xaf, 0x15, 0xf2, 0xea, 0x01, 0x9e, 0x0f, 0x82, 0xfd, 0xbc, 0x7f, 0x10, 0xef, 0x5e, 0xbd, 0xb5, 0x57, 0x56, 0xb2, 0x65, 0x85, 0x3c, 0x20, 0x77, 0x03, 0x72, 0x1a, 0xe9, 0x91, 0xe5, 0xf2, 0x97, 0x3f, 0x28, 0xff, 0x10, 0xd8, 0x87, 0xaf, 0x34, 0xd7, 0x8b, 0xd9, 0xf6, 0x64, 0x9d, 0x77, 0xcb, 0x61, 0xd5, 0x7c, 0xce, 0x97, 0xa5, 0xd2, 0xa7, 0xd6, 0x4a, 0xd5, 0x63, 0xc5, 0x39, 0xcb, 0x3b, 0x28, 0x8f, 0xcb, 0xe5, 0x3c, 0x1f, 0x95, 0x3f, 0xd3, 0xde, 0x13, 0xea, 0xb5, 0xe3, 0x94, 0xe3, 0xb3, 0xf6, 0xf5, 0x3d, 0x25, 0x1d, 0xaa, 0xf9, 0x3c, 0x31, 0x40, 0x59, 0x32, 0x53, 0x6e, 0xe0, 0x55, 0x37, 0x5b, 0x65, 0xca, 0x02, 0x5a, 0x64, 0xb2, 0xb4, 0xa3, 0xba, 0x4e, 0xb5, 0xe5, 0x9e, 0xaa, 0x6e, 0x85, 0xfc, 0x09, 0xfc, 0x2c, 0x77, 0xaa, 0x75, 0x37, 0x4c, 0xee, 0x37, 0xcb, 0xb7, 0xb6, 0x39, 0xce, 0xff, 0x05, 0x6a, 0xed, 0x67, 0x6a, 0x9c, 0x53, 0xcd, 0x95, 0xd9, 0x0a, 0xb5, 0x56, 0x59, 0x5b, 0x56, 0x45, 0x9d, 0x72, 0x5f, 0x56, 0xcf, 0x8b, 0xc1, 0x0e, 0xbc, 0xa1, 0xb9, 0x9e, 0x2b, 0xbf, 0x92, 0x9f, 0xf0, 0x7c, 0xd8, 0xfa, 0xe4, 0x61, 0xf9, 0x91, 0x3c, 0xaa, 0xd6, 0xf6, 0xc9, 0x12, 0xfa, 0x71, 0x18, 0x6b, 0xef, 0xca, 0x59, 0xf2, 0x43, 0x79, 0xa5, 0xdc, 0x24, 0x27, 0xcb, 0xcf, 0xe5, 0x48, 0x39, 0x58, 0x7d, 0x6e, 0xa7, 0xd9, 0x4b, 0x6a, 0xcb, 0x13, 0xf2, 0x98, 0xdc, 0xc3, 0xf3, 0x31, 0xda, 0x53, 0x40, 0x8e, 0x66, 0xbe, 0xf6, 0x4b, 0x75, 0x89, 0x66, 0x7d, 0xe5, 0x49, 0x6a, 0x7b, 0x5c, 0x1e, 0xb3, 0x6a, 0xb1, 0x87, 0x1e, 0x55, 0xeb, 0xf4, 0xda, 0x51, 0x96, 0x72, 0xb6, 0x3c, 0x89, 0x83, 0x3e, 0x93, 0xdf, 0x23, 0x56, 0x8f, 0xec, 0x53, 0x71, 0x50, 0x66, 0xd5, 0xea, 0x90, 0x19, 0x01, 0xb5, 0x51, 0x70, 0xa4, 0xba, 0x1d, 0x66, 0x6f, 0x51, 0x52, 0x35, 0x23, 0xf9, 0x16, 0x17, 0x55, 0x7c, 0xea, 0xc0, 0x59, 0xca, 0x3d, 0x5c, 0xd3, 0xd3, 0xea, 0x53, 0xaa, 0x24, 0xda, 0x5a, 0x64, 0x46, 0x9c, 0xd9, 0x6f, 0xd5, 0xb1, 0x62, 0xb6, 0xc8, 0xda, 0x43, 0x15, 0x75, 0xce, 0x57, 0x4c, 0xfb, 0x68, 0xb5, 0x87, 0xc7, 0x5d, 0x44, 0x7f, 0x65, 0x4d, 0x1d, 0x02, 0x4a, 0xde, 0xaa, 0x1e, 0x57, 0x82, 0x2f, 0xe5, 0x1b, 0xaa, 0x97, 0xb6, 0xca, 0x2d, 0xd6, 0x7b, 0x1b, 0xac, 0xe7, 0x1f, 0xe4, 0x52, 0xf5, 0xfe, 0x9b, 0x72, 0xb9, 0xfc, 0xa2, 0xf6, 0x93, 0x55, 0xbf, 0x3f, 0xfe, 0xbe, 0xb5, 0xdc, 0x68, 0xfd, 0xf7, 0xb3, 0x1c, 0x24, 0xb7, 0xc9, 0x35, 0x72, 0x34, 0x3d, 0x72, 0x91, 0xec, 0x25, 0xef, 0x92, 0xab, 0xc8, 0x8d, 0x85, 0x3c, 0x1f, 0xdc, 0x26, 0x07, 0xcb, 0xc7, 0x78, 0xde, 0x2c, 0xd7, 0xca, 0xf3, 0x79, 0x67, 0x28, 0xbd, 0xb4, 0xaa, 0xb6, 0x9c, 0xca, 0x7a, 0x35, 0xfc, 0x48, 0x96, 0xf0, 0x38, 0x59, 0x2e, 0x50, 0x6d, 0x3c, 0x2e, 0x57, 0xa8, 0xf8, 0x75, 0x05, 0xc4, 0xef, 0x11, 0x35, 0xd6, 0x0e, 0xa9, 0x9a, 0xd6, 0x8f, 0xdf, 0xa3, 0x44, 0xed, 0x26, 0x15, 0xb3, 0xe5, 0xf2, 0x5b, 0xfa, 0xba, 0x84, 0x3e, 0xcc, 0x93, 0x3f, 0xd5, 0x1d, 0x75, 0xf4, 0xe6, 0x11, 0xf3, 0x15, 0xb9, 0xa5, 0x86, 0x4d, 0xab, 0xe4, 0x43, 0xaa, 0x4f, 0xaa, 0xc7, 0x56, 0x95, 0xdc, 0x1c, 0xf0, 0xde, 0x16, 0x79, 0x1d, 0xe3, 0x79, 0x97, 0x7c, 0x8b, 0xc7, 0x6b, 0xcd, 0xe8, 0x01, 0x03, 0x64, 0x2f, 0x73, 0x4e, 0xa8, 0x8e, 0xfe, 0xea, 0x58, 0x66, 0x4f, 0x59, 0xf2, 0x57, 0xea, 0xb5, 0x8b, 0x1e, 0xd9, 0x63, 0xc5, 0xc3, 0x4e, 0xb3, 0x86, 0x66, 0xcc, 0xf2, 0xae, 0x97, 0x1a, 0x6e, 0xb6, 0x22, 0xd9, 0x9c, 0x9f, 0x56, 0x57, 0xef, 0xb9, 0x76, 0x5c, 0xd4, 0xf6, 0x58, 0x75, 0x89, 0xb5, 0x35, 0x3b, 0xa9, 0x62, 0x0c, 0x4d, 0xa3, 0x8c, 0xcd, 0xd6, 0xb8, 0xa8, 0x9e, 0x77, 0x0e, 0x5b, 0x11, 0x7e, 0xb8, 0x66, 0xb6, 0xa0, 0x1f, 0xf2, 0xd9, 0xf2, 0x14, 0x2d, 0xd8, 0xc7, 0xf6, 0xc5, 0x8c, 0xa4, 0xed, 0xd4, 0x2c, 0xff, 0x2c, 0x71, 0x76, 0x42, 0x26, 0xa8, 0xe7, 0x86, 0xe0, 0x29, 0xb2, 0x7e, 0x73, 0xfd, 0x45, 0xd9, 0x43, 0xb6, 0x55, 0x31, 0x38, 0xcb, 0x8a, 0xc5, 0xc6, 0x72, 0x1d, 0xcf, 0x4f, 0xcb, 0x31, 0xf2, 0x52, 0xd9, 0x40, 0x4e, 0x65, 0xbd, 0x01, 0xfd, 0x13, 0x2b, 0x87, 0xc9, 0x51, 0x32, 0x4e, 0xf6, 0x97, 0x8f, 0xc9, 0x29, 0xbc, 0x36, 0x5e, 0xde, 0xcd, 0x5e, 0x67, 0xc8, 0x25, 0x6a, 0x9e, 0xac, 0x8e, 0x73, 0x46, 0x1a, 0x7d, 0x51, 0x61, 0xc5, 0x6f, 0x81, 0xf9, 0xaa, 0xd9, 0x77, 0xd6, 0xdc, 0xb4, 0xbb, 0x66, 0x06, 0xa0, 0xa6, 0x79, 0x2a, 0x5a, 0xb6, 0xc3, 0x49, 0x8e, 0x5c, 0xcf, 0x67, 0x72, 0x88, 0xe2, 0x5f, 0x98, 0xdb, 0xeb, 0xd7, 0xf8, 0x51, 0xf5, 0x78, 0x0f, 0x58, 0x2f, 0x73, 0xd5, 0xfa, 0x8f, 0x72, 0x9e, 0x7c, 0x51, 0xf5, 0xd7, 0x2e, 0xab, 0xdf, 0xee, 0x21, 0x1b, 0x16, 0x44, 0xc1, 0x76, 0x96, 0xdb, 0xe5, 0x03, 0xac, 0xdf, 0xc1, 0xfc, 0x3f, 0x89, 0x1a, 0x66, 0x11, 0x7d, 0x1f, 0xca, 0xa9, 0xea, 0xf3, 0xf3, 0x88, 0x8f, 0x7c, 0x66, 0x40, 0x9f, 0xe2, 0xf7, 0x75, 0xf5, 0xd9, 0x97, 0x55, 0x1d, 0xac, 0xb9, 0x4d, 0xae, 0x90, 0x1f, 0xf3, 0xec, 0xb7, 0xc6, 0x9f, 0x5f, 0xbe, 0x62, 0x8e, 0x70, 0x7a, 0xf0, 0x17, 0x5a, 0x32, 0x5b, 0x3e, 0xcf, 0xfa, 0x1c, 0xa2, 0xfd, 0x15, 0x39, 0x85, 0xde, 0x9e, 0x4d, 0x69, 0xef, 0x9a, 0xa5, 0xc8, 0x95, 0xb4, 0xe1, 0x68, 0xcd, 0x7c, 0x29, 0x7f, 0x54, 0x8f, 0x66, 0x0c, 0xaf, 0x90, 0x1f, 0x58, 0xa3, 0x6b, 0xad, 0x2a, 0xa7, 0xca, 0x9a, 0x13, 0xab, 0xe4, 0x77, 0xe6, 0x58, 0xe2, 0xf3, 0x0b, 0xe5, 0x52, 0x73, 0xb6, 0x36, 0xe7, 0xa9, 0x9a, 0x79, 0xb1, 0x3a, 0x2a, 0x02, 0xc6, 0xdb, 0x76, 0xeb, 0xbf, 0x8e, 0xea, 0x31, 0x05, 0xcc, 0x57, 0xb5, 0x3e, 0x41, 0x64, 0x5e, 0x29, 0x2f, 0x57, 0xe5, 0xcd, 0xb5, 0xca, 0x4d, 0x56, 0xfb, 0x7d, 0x4d, 0xbe, 0x24, 0x5f, 0x95, 0xad, 0xe5, 0xed, 0xe6, 0x68, 0x44, 0x15, 0xdb, 0xc9, 0x3e, 0xfc, 0x9f, 0x08, 0x4b, 0xb7, 0xca, 0xeb, 0x78, 0xed, 0x66, 0xea, 0x9e, 0x2b, 0xa7, 0xcb, 0xef, 0xd5, 0xfc, 0x5b, 0x3d, 0xe7, 0xee, 0x50, 0x11, 0xbf, 0xae, 0x7a, 0x8f, 0x30, 0x51, 0xfc, 0xfb, 0xc8, 0xa7, 0xdc, 0x9d, 0x6a, 0xae, 0x39, 0x84, 0x6a, 0x6e, 0xb5, 0xda, 0x50, 0x52, 0x1b, 0xbf, 0x55, 0x75, 0xe7, 0x75, 0xe9, 0xe7, 0xc1, 0x26, 0xba, 0x88, 0x20, 0x11, 0xc7, 0x5a, 0x02, 0xea, 0xdc, 0xd2, 0x36, 0xc0, 0x36, 0x40, 0xb4, 0xb5, 0x0d, 0xb2, 0x5d, 0x28, 0xda, 0xd9, 0x86, 0xd8, 0x86, 0x88, 0xf3, 0x6c, 0x43, 0x6d, 0xc3, 0x44, 0x47, 0xdb, 0x25, 0xb6, 0x4b, 0x44, 0x67, 0xdb, 0x28, 0xdb, 0x68, 0xd1, 0xc5, 0x36, 0xc6, 0x36, 0x46, 0x74, 0xb3, 0x5d, 0x6d, 0xbb, 0x46, 0x74, 0xb7, 0x5d, 0x6f, 0xbb, 0x5e, 0xa4, 0xd9, 0x6e, 0xb6, 0xdd, 0x22, 0x7a, 0xda, 0x6e, 0xb3, 0xdd, 0x2d, 0x2e, 0x08, 0x89, 0x0b, 0x89, 0x13, 0xfd, 0x42, 0xf6, 0x84, 0xec, 0x11, 0xfd, 0x43, 0xf2, 0x43, 0x7e, 0x11, 0x03, 0x42, 0xf6, 0x87, 0xec, 0x17, 0x83, 0x42, 0x5b, 0x85, 0xb6, 0x12, 0x19, 0xf6, 0xf6, 0xf6, 0x6e, 0xe2, 0x42, 0x7b, 0x4f, 0xfb, 0x10, 0x71, 0x89, 0xfd, 0x06, 0xfb, 0x4d, 0x62, 0xbc, 0xfd, 0x36, 0xfb, 0x3d, 0xe2, 0x26, 0xfb, 0x44, 0xfb, 0x54, 0x71, 0x97, 0xfd, 0x61, 0xfb, 0x53, 0x62, 0xa2, 0xfd, 0x39, 0xfb, 0x2b, 0x62, 0x9a, 0xfd, 0x55, 0xfb, 0x9b, 0xe2, 0x59, 0xfb, 0x07, 0xf6, 0x4f, 0xc4, 0x2c, 0xfb, 0x32, 0xfb, 0xe7, 0xe2, 0x55, 0xfb, 0x06, 0xfb, 0x56, 0x31, 0xcf, 0xbe, 0xc3, 0x9e, 0x27, 0x16, 0xd8, 0x4b, 0xed, 0x65, 0xe2, 0x63, 0xbb, 0xcf, 0x7e, 0x50, 0x7c, 0x62, 0x3f, 0x6c, 0xaf, 0x12, 0x4b, 0xed, 0xa7, 0xf5, 0x60, 0xf1, 0x8d, 0x1e, 0xaa, 0x3b, 0xc5, 0x1a, 0xbd, 0x81, 0x1e, 0x2e, 0xb2, 0xf4, 0x28, 0xbd, 0x9d, 0xd8, 0xaa, 0x77, 0xd7, 0xbb, 0x8b, 0x02, 0xbd, 0xa7, 0x7e, 0x81, 0xd8, 0xa7, 0xf7, 0xd1, 0x2f, 0x16, 0xc5, 0xfa, 0x30, 0x7d, 0xb4, 0xa8, 0xd0, 0x2f, 0xd3, 0x2f, 0x13, 0x27, 0xf4, 0x2b, 0xf4, 0x07, 0x45, 0x95, 0xfe, 0xb0, 0xfe, 0xb8, 0xd6, 0x52, 0x7f, 0x4a, 0x7f, 0x56, 0x6b, 0xa5, 0xbf, 0xa4, 0xbf, 0xa2, 0xa5, 0xe8, 0xaf, 0xea, 0xaf, 0x6b, 0xed, 0x9c, 0x5f, 0x3b, 0xbf, 0xd6, 0xce, 0x73, 0x6e, 0x72, 0x6e, 0xd2, 0x3a, 0xc2, 0xd4, 0x0d, 0x22, 0x84, 0x25, 0x18, 0x5f, 0x16, 0x2a, 0x34, 0x61, 0xc7, 0x45, 0xd9, 0x84, 0x21, 0x1c, 0xf0, 0xe7, 0x14, 0x61, 0xbc, 0xde, 0x80, 0x25, 0x44, 0x34, 0x64, 0x09, 0xc5, 0x3f, 0x35, 0x65, 0x8b, 0x66, 0x2c, 0xba, 0x08, 0x67, 0x31, 0x44, 0x84, 0x88, 0x54, 0xd7, 0xfd, 0x6e, 0xc1, 0xeb, 0x31, 0xa2, 0x25, 0x9f, 0x89, 0x65, 0x09, 0x86, 0xfb, 0x38, 0x3e, 0x9d, 0xc0, 0xe2, 0xc4, 0x07, 0x27, 0x52, 0x46, 0x2b, 0xd1, 0x9a, 0x32, 0x92, 0xf0, 0xa0, 0x8d, 0x44, 0xb2, 0x48, 0x11, 0x8d, 0x45, 0x2a, 0x4b, 0x13, 0xd1, 0x96, 0xa5, 0xa9, 0x68, 0xc7, 0xd2, 0x4c, 0xb4, 0x67, 0x09, 0xa7, 0xc7, 0x3a, 0x88, 0x08, 0xd5, 0x67, 0x1a, 0x7d, 0x36, 0x48, 0x38, 0x6d, 0x19, 0xb6, 0x0c, 0x61, 0xb3, 0x5d, 0x48, 0xff, 0x05, 0xa9, 0xfe, 0x73, 0xd2, 0x7f, 0x43, 0x45, 0x88, 0x6d, 0x18, 0xbd, 0x18, 0xaa, 0x7a, 0xd1, 0xa0, 0x17, 0x47, 0x09, 0xbb, 0x6d, 0x34, 0x7d, 0xe9, 0x54, 0x7d, 0x19, 0x46, 0x5f, 0x5e, 0x2d, 0xc2, 0x6d, 0xd7, 0xd0, 0xa3, 0x11, 0xaa, 0x47, 0x43, 0xe9, 0xd1, 0x9b, 0x59, 0xbf, 0x85, 0x7e, 0x6d, 0x4c, 0xbf, 0xde, 0x26, 0x1a, 0xd8, 0x6e, 0xb7, 0xdd, 0xce, 0x36, 0x77, 0xd8, 0xee, 0xe0, 0x95, 0x3b, 0x6d, 0x77, 0xf2, 0xca, 0x5d, 0xb6, 0xbb, 0x28, 0xed, 0x6e, 0x7a, 0x3d, 0x42, 0xf5, 0xba, 0x5d, 0xf5, 0xba, 0x53, 0xf5, 0xba, 0x43, 0xf5, 0x7a, 0x90, 0xea, 0x75, 0xbb, 0xfd, 0x46, 0x7a, 0x3a, 0x98, 0x9e, 0xbe, 0x53, 0x68, 0xf6, 0xbb, 0xe8, 0x6f, 0x1b, 0xfd, 0x3d, 0x91, 0xc7, 0x49, 0xf6, 0x07, 0x45, 0x90, 0x7d, 0x2a, 0x7d, 0x1f, 0x41, 0xdf, 0x3f, 0xc2, 0xfa, 0x34, 0x22, 0x20, 0x58, 0x45, 0x40, 0xb0, 0x8a, 0x80, 0x60, 0x22, 0xe0, 0x03, 0x11, 0x62, 0x5f, 0x68, 0x5f, 0x22, 0x42, 0xed, 0x9f, 0x10, 0x0d, 0x76, 0x15, 0x0d, 0x76, 0x15, 0x0d, 0x76, 0x15, 0x0d, 0x76, 0x15, 0x0d, 0x3a, 0xd1, 0xe0, 0xe3, 0xf1, 0x20, 0x31, 0x61, 0x10, 0x13, 0x15, 0x3c, 0x1e, 0xb5, 0x9f, 0x10, 0x0e, 0x7b, 0x15, 0xf1, 0xa1, 0xab, 0xf8, 0xb0, 0x13, 0x1f, 0x0e, 0x1e, 0x9d, 0x44, 0x89, 0xae, 0xa2, 0xc4, 0xa9, 0xa2, 0xc4, 0xa9, 0xa2, 0xc4, 0xa9, 0xa2, 0x24, 0x8c, 0x28, 0xb9, 0x50, 0x34, 0xd0, 0x07, 0x13, 0x2b, 0x0d, 0x89, 0x95, 0x91, 0x3c, 0x8e, 0x22, 0x62, 0x1a, 0xa9, 0x88, 0x69, 0x44, 0xc4, 0x5c, 0xc5, 0xe3, 0xd5, 0xfa, 0xbd, 0xa2, 0xb1, 0x7e, 0x9f, 0x7e, 0xbf, 0x68, 0xa2, 0x4f, 0x24, 0x86, 0x9a, 0x12, 0x43, 0x8f, 0x88, 0x66, 0xfa, 0x34, 0xfd, 0x71, 0x11, 0x6e, 0x46, 0x12, 0x8f, 0x44, 0x92, 0x88, 0x30, 0x23, 0x49, 0x44, 0x98, 0x91, 0x24, 0xec, 0x66, 0x24, 0x11, 0x07, 0x36, 0xb1, 0x9e, 0x98, 0x31, 0x7b, 0xde, 0x8c, 0xa7, 0x04, 0x15, 0x49, 0x11, 0x2a, 0x6e, 0x22, 0x54, 0xc4, 0xc4, 0xb0, 0xc4, 0xb2, 0x6e, 0xc6, 0x44, 0x84, 0x8a, 0x86, 0xb6, 0x2a, 0x1a, 0x9a, 0xa8, 0x68, 0x08, 0x56, 0xd1, 0x10, 0x1a, 0x10, 0x0d, 0x2d, 0x55, 0x34, 0x04, 0xa9, 0x68, 0x48, 0x16, 0xe7, 0x89, 0x4e, 0x7c, 0xa6, 0x33, 0x4b, 0x33, 0xc6, 0x76, 0x1a, 0xf1, 0xd6, 0x53, 0x5c, 0x40, 0xac, 0xf4, 0x16, 0xe9, 0xac, 0xf7, 0x11, 0xd7, 0x12, 0x67, 0xd7, 0xb1, 0xc4, 0x8a, 0x71, 0x2c, 0x21, 0xe2, 0x7a, 0xe2, 0xba, 0x99, 0xb8, 0xd1, 0xfa, 0xc5, 0x79, 0xf5, 0xef, 0xcd, 0x6f, 0xe7, 0x95, 0x3b, 0x58, 0x1a, 0x88, 0x3b, 0xc5, 0x5d, 0x44, 0xe1, 0xdd, 0xe2, 0x1e, 0xd6, 0xef, 0x15, 0xf7, 0x53, 0xc2, 0x44, 0x96, 0x06, 0x62, 0x12, 0x4b, 0x33, 0x31, 0x99, 0x25, 0x5c, 0x4c, 0x61, 0x69, 0x20, 0x1e, 0x10, 0x0f, 0xa9, 0x2b, 0xdf, 0x3c, 0xcc, 0xeb, 0x8f, 0xb0, 0x38, 0xc5, 0x34, 0xf1, 0x0c, 0x6d, 0x9b, 0x21, 0x9e, 0x25, 0xbe, 0x67, 0xb2, 0x44, 0x89, 0xe7, 0x58, 0xa2, 0xc5, 0xf3, 0x2c, 0x89, 0xe2, 0x5f, 0x2c, 0xba, 0x98, 0xc5, 0xd2, 0x5a, 0xeb, 0xa5, 0xf5, 0x16, 0x6d, 0xb4, 0x74, 0x2d, 0x5d, 0x34, 0xd7, 0xfa, 0x54, 0x5f, 0x05, 0x5f, 0xeb, 0xcb, 0x7a, 0x3f, 0x75, 0x25, 0xfc, 0xfe, 0xda, 0x00, 0x91, 0xa4, 0x0d, 0xd4, 0x06, 0x8a, 0x14, 0x6d, 0x90, 0x36, 0x88, 0xfc, 0x2c, 0x43, 0xcb, 0x60, 0xfd, 0x42, 0xed, 0x42, 0xd6, 0x87, 0x6a, 0x43, 0x45, 0x3b, 0x6d, 0xb8, 0x36, 0x9c, 0xc7, 0xeb, 0xd4, 0x55, 0x22, 0xf7, 0x6b, 0xfb, 0x45, 0x84, 0x56, 0xa2, 0x95, 0x88, 0x60, 0xcd, 0xab, 0x79, 0xc9, 0xe9, 0x4a, 0xb5, 0x52, 0xa2, 0xdc, 0x1c, 0x27, 0x0e, 0x35, 0x42, 0x22, 0xd4, 0x08, 0x71, 0xa8, 0x51, 0x11, 0xa1, 0x46, 0x42, 0x84, 0x1a, 0x09, 0x0e, 0x35, 0x12, 0x82, 0xd4, 0x48, 0x48, 0x56, 0x23, 0x21, 0x42, 0x8d, 0x84, 0x64, 0x35, 0x12, 0x82, 0xd5, 0x18, 0x08, 0x52, 0x63, 0x20, 0x58, 0xc5, 0x7d, 0xb2, 0xed, 0x3e, 0xdb, 0xfd, 0xa2, 0x99, 0x6d, 0x92, 0x6d, 0x92, 0x68, 0x61, 0x9b, 0x6c, 0x83, 0x0d, 0xdb, 0x54, 0xdb, 0x54, 0xc6, 0xc6, 0x43, 0xb6, 0x87, 0x78, 0x7d, 0x9a, 0x6d, 0x1a, 0xeb, 0x8f, 0xd9, 0x1e, 0x67, 0xbc, 0x3d, 0x61, 0x7b, 0x42, 0xb4, 0xb6, 0x3d, 0x63, 0x7b, 0x86, 0xf1, 0x39, 0xc3, 0x36, 0x43, 0xc4, 0xda, 0x66, 0xda, 0x66, 0xb2, 0xfd, 0xf3, 0xb6, 0xe7, 0x79, 0xfd, 0x45, 0xdb, 0x8b, 0x22, 0xde, 0xf6, 0x8a, 0xed, 0x15, 0x5e, 0x99, 0x6b, 0x9b, 0x6b, 0x5e, 0x3b, 0xc2, 0x36, 0x8f, 0x12, 0xe6, 0xdb, 0xe6, 0xf3, 0xee, 0x7b, 0xb6, 0x8f, 0x19, 0xab, 0x4b, 0x6c, 0x44, 0xbf, 0xed, 0x4b, 0xdb, 0x97, 0xc2, 0x16, 0x3c, 0x2e, 0x98, 0xbe, 0x0b, 0x9e, 0x1c, 0x3c, 0x59, 0x34, 0x0a, 0x7e, 0x2a, 0xf8, 0x29, 0xd1, 0x38, 0xf8, 0x99, 0xe0, 0x67, 0x78, 0x9c, 0x11, 0x3c, 0x43, 0x44, 0x06, 0x3f, 0x1b, 0xfc, 0xac, 0x68, 0x1f, 0x3c, 0x33, 0x78, 0x26, 0xeb, 0xcf, 0x05, 0x3f, 0x27, 0xda, 0x87, 0x84, 0x87, 0x84, 0x8b, 0x46, 0x21, 0xdf, 0x85, 0xac, 0x31, 0xaf, 0x8a, 0x45, 0xfe, 0xa6, 0x85, 0x6c, 0x09, 0xc9, 0x61, 0xdd, 0x1c, 0xb1, 0x11, 0x21, 0x05, 0x21, 0x05, 0xa2, 0x45, 0x48, 0x49, 0x48, 0x89, 0x08, 0x0f, 0xf1, 0x85, 0xf8, 0x44, 0x58, 0x68, 0xeb, 0xd0, 0xd6, 0xa2, 0x41, 0xa8, 0x79, 0x45, 0x97, 0x66, 0xa1, 0xc9, 0xa1, 0xc9, 0xc2, 0x1e, 0x7a, 0x22, 0xf4, 0x84, 0x68, 0xa1, 0xe6, 0xef, 0x38, 0x35, 0x7f, 0xc7, 0x31, 0x7f, 0xdf, 0xc0, 0xa3, 0x39, 0xb6, 0x13, 0xd4, 0xa8, 0x8e, 0x50, 0xa3, 0x3a, 0x42, 0x8d, 0xd5, 0x08, 0x7b, 0x19, 0x63, 0x32, 0x46, 0x8d, 0xc9, 0x18, 0x35, 0xd2, 0x22, 0xd4, 0x48, 0x8b, 0x50, 0x23, 0x2d, 0x42, 0x8d, 0xae, 0xb6, 0x6a, 0x74, 0xb5, 0x55, 0xa3, 0xab, 0x89, 0x1a, 0x5d, 0x4d, 0xd4, 0xe8, 0x6a, 0xa2, 0x46, 0x57, 0xb0, 0x1a, 0x5d, 0xa1, 0x01, 0xa3, 0xab, 0xa5, 0x1a, 0x5d, 0x41, 0x6a, 0x74, 0x05, 0xa9, 0xd1, 0x95, 0xac, 0x46, 0x57, 0xb2, 0xfe, 0x86, 0xfe, 0x9e, 0x68, 0xa5, 0x2f, 0xd0, 0x57, 0xf0, 0xf8, 0x95, 0x9e, 0xc9, 0xe3, 0xb7, 0xfa, 0x6a, 0x46, 0xe3, 0x1a, 0xfd, 0x07, 0xd1, 0x42, 0xdf, 0xa0, 0x6f, 0xe0, 0x71, 0xa3, 0x9e, 0xcd, 0x98, 0xdc, 0xaa, 0x6f, 0xe5, 0x71, 0x9b, 0xee, 0xe2, 0x95, 0x3c, 0x7d, 0x1f, 0x8f, 0x45, 0x3a, 0x11, 0xa2, 0x97, 0xe9, 0x07, 0x78, 0xf4, 0xe9, 0x3e, 0x66, 0x81, 0x43, 0xfa, 0x69, 0x1e, 0xa5, 0x11, 0x22, 0x62, 0x8d, 0x50, 0xc3, 0x10, 0x21, 0x46, 0x03, 0xa3, 0x11, 0x8f, 0xe6, 0x3d, 0xe6, 0x9a, 0x19, 0xe6, 0x95, 0xfd, 0x34, 0xc3, 0xbc, 0x4e, 0x98, 0x66, 0xb4, 0x24, 0xfb, 0xd5, 0x8c, 0x36, 0xe4, 0xbf, 0x9a, 0xba, 0xf3, 0xa5, 0x66, 0x74, 0x20, 0x0f, 0xd6, 0x8c, 0x4e, 0x46, 0x27, 0x1e, 0xbb, 0xa8, 0xfb, 0xec, 0x98, 0x77, 0xbe, 0x6c, 0x66, 0xf4, 0x32, 0x06, 0x8a, 0x46, 0x46, 0x86, 0x31, 0x98, 0x6c, 0xd9, 0xbc, 0xff, 0x65, 0x03, 0x63, 0x8c, 0x31, 0x86, 0xc7, 0xcb, 0x8c, 0x6b, 0x44, 0x0b, 0xe3, 0x7a, 0xe3, 0x1e, 0x1e, 0xa7, 0x1a, 0x0f, 0xf3, 0x38, 0xcd, 0x78, 0x94, 0xd7, 0x9f, 0x30, 0x9e, 0xe5, 0xf1, 0x39, 0x32, 0xea, 0x06, 0xc6, 0xbf, 0x8c, 0x39, 0xe4, 0xf1, 0x2f, 0x1a, 0x73, 0x79, 0x7c, 0xdd, 0x58, 0x25, 0xec, 0xc6, 0x6a, 0x63, 0x3d, 0x59, 0xfd, 0x06, 0x23, 0x97, 0xc7, 0x1d, 0xc6, 0xcf, 0x3c, 0xba, 0x8c, 0x9d, 0x22, 0xca, 0xd8, 0x65, 0xe4, 0x8b, 0x68, 0xa3, 0xc0, 0xd8, 0x27, 0x12, 0x8d, 0x22, 0xa3, 0x44, 0xe8, 0x46, 0x99, 0x71, 0x98, 0xc7, 0xa3, 0xc6, 0x71, 0xd1, 0x5a, 0xfd, 0xe6, 0xa6, 0x81, 0x63, 0x8f, 0x63, 0x8f, 0x68, 0xe7, 0x28, 0x73, 0x1c, 0x12, 0x11, 0x8e, 0xc3, 0x8e, 0xa3, 0x22, 0xd8, 0x71, 0xcc, 0x71, 0x5c, 0xb4, 0x70, 0x9c, 0x70, 0xfc, 0x26, 0xc2, 0x9c, 0xd9, 0x4e, 0x58, 0x12, 0xb6, 0x90, 0x85, 0x66, 0xd6, 0x6e, 0xbf, 0xd3, 0x71, 0xa3, 0x9a, 0x81, 0xcc, 0xfc, 0xf6, 0x04, 0x6e, 0xbd, 0x00, 0x87, 0xd5, 0x01, 0x67, 0x7c, 0x40, 0x2e, 0xc0, 0x63, 0xec, 0x96, 0x8f, 0xe0, 0x06, 0x0f, 0xe3, 0x16, 0xf3, 0xa4, 0xa3, 0x3a, 0xef, 0xfb, 0xc3, 0x0c, 0xfd, 0x30, 0x39, 0x1e, 0xce, 0x8a, 0x7c, 0xa4, 0x3b, 0xce, 0xb7, 0x10, 0xdf, 0x57, 0x8c, 0x1f, 0xbe, 0x97, 0x32, 0xbf, 0xc0, 0xc9, 0x78, 0xf0, 0xb2, 0x47, 0xd9, 0xc6, 0x8d, 0x0b, 0x2b, 0xfa, 0x93, 0x4c, 0x9f, 0xbc, 0x44, 0x8e, 0x32, 0xf3, 0x13, 0xf9, 0x99, 0xf2, 0xe7, 0xb3, 0x6b, 0xb3, 0xc3, 0x0e, 0x81, 0x19, 0x3b, 0xf5, 0xaa, 0x60, 0x29, 0x3f, 0x5b, 0x5d, 0x6a, 0x1c, 0x11, 0xd9, 0x86, 0x5f, 0x95, 0x51, 0xa5, 0xfc, 0x13, 0xf9, 0x4b, 0x4d, 0x36, 0x49, 0x8b, 0x4f, 0x9d, 0xb3, 0x0e, 0x7e, 0x16, 0x33, 0xb3, 0xf3, 0xe0, 0xb5, 0x8b, 0xc9, 0xfe, 0x9e, 0xe5, 0xd1, 0x85, 0xa3, 0x2d, 0xc5, 0xb7, 0x1e, 0xe0, 0xd5, 0x16, 0xaa, 0xcc, 0x8a, 0xdf, 0x33, 0xa0, 0x3f, 0xfc, 0x4b, 0x56, 0x19, 0x52, 0xb8, 0x72, 0x68, 0xac, 0x33, 0xaf, 0xd7, 0xf1, 0xd5, 0x38, 0xd9, 0x02, 0xea, 0x65, 0x66, 0x4c, 0xfb, 0xf0, 0xf1, 0xbf, 0xe2, 0xab, 0x7f, 0x0a, 0x78, 0xbf, 0xda, 0xcd, 0x4d, 0x51, 0xcc, 0x6c, 0x32, 0x33, 0x31, 0xe5, 0x2d, 0xcd, 0x1c, 0xa3, 0x44, 0xa6, 0x07, 0xe4, 0x55, 0xe5, 0xd5, 0xdb, 0xaa, 0xe7, 0xdf, 0xce, 0x3c, 0xba, 0xc1, 0xe3, 0xe5, 0x8a, 0x8f, 0x25, 0x8a, 0xdf, 0x17, 0x55, 0x8e, 0xed, 0x63, 0x8f, 0xa9, 0xbf, 0x1f, 0x03, 0xf8, 0xc3, 0x7e, 0x39, 0x14, 0x90, 0x7b, 0x1f, 0x0f, 0x38, 0x8a, 0x70, 0x94, 0x52, 0x4e, 0xfc, 0x39, 0xa7, 0xea, 0xfd, 0xea, 0x3c, 0xe1, 0xd5, 0xea, 0xbc, 0x55, 0x3d, 0x7e, 0x57, 0x7b, 0xc4, 0x66, 0x54, 0xc0, 0x76, 0x1b, 0x59, 0xb6, 0x49, 0xf3, 0x3b, 0xde, 0x9a, 0x57, 0xca, 0xac, 0xe3, 0x30, 0x55, 0xd4, 0x7a, 0x1f, 0x71, 0xd9, 0x89, 0xd6, 0xfb, 0x54, 0x9c, 0x7a, 0x54, 0x9c, 0x96, 0x5b, 0x71, 0xfa, 0xa7, 0xfd, 0x61, 0x66, 0xf5, 0xf4, 0x9c, 0x8b, 0x38, 0x6d, 0x4d, 0x6f, 0x16, 0x90, 0x49, 0xec, 0x63, 0x5f, 0xb7, 0x13, 0xb1, 0x2b, 0x88, 0xd3, 0x3c, 0xe9, 0xa4, 0xa4, 0x83, 0x38, 0xef, 0x7a, 0x71, 0xfa, 0xbb, 0xbf, 0xae, 0xf3, 0xea, 0x7c, 0x15, 0x89, 0x5e, 0x15, 0x5f, 0x6e, 0xe5, 0xf9, 0xd7, 0xd3, 0x37, 0x63, 0xac, 0x77, 0x6f, 0x67, 0x14, 0xe4, 0x11, 0x3b, 0x1e, 0xb5, 0x94, 0x53, 0xe3, 0x85, 0xf2, 0xdf, 0x01, 0x8e, 0xfe, 0x90, 0xe9, 0xd2, 0x65, 0x3a, 0xfb, 0x2b, 0x24, 0x87, 0x39, 0x45, 0xc4, 0x3f, 0xc7, 0x56, 0xf9, 0x8c, 0x93, 0xdd, 0x32, 0xb1, 0x26, 0x4b, 0xaf, 0x3b, 0xf6, 0x6a, 0xb8, 0xae, 0x57, 0x8f, 0x39, 0x35, 0x47, 0x2d, 0xcc, 0xfc, 0xcc, 0x7a, 0xed, 0x98, 0x1c, 0xf9, 0x67, 0xf5, 0xaf, 0x13, 0x1f, 0x57, 0xa8, 0xbe, 0x5d, 0x2a, 0x7f, 0xe5, 0x79, 0xae, 0x8a, 0x37, 0x33, 0x3e, 0x3a, 0xfc, 0xd1, 0x7e, 0x03, 0x8f, 0x20, 0xa9, 0xc7, 0x97, 0x14, 0xc3, 0xbf, 0xa8, 0xf1, 0x41, 0xc6, 0x24, 0x3f, 0xa0, 0x27, 0x2b, 0x6a, 0xf9, 0xb8, 0x57, 0x2e, 0x53, 0x47, 0xed, 0xcc, 0x23, 0x30, 0x45, 0x6c, 0x57, 0x22, 0xbf, 0x94, 0xab, 0x68, 0x71, 0x9d, 0x23, 0x31, 0x66, 0x7c, 0x98, 0xc7, 0x6b, 0x14, 0xa7, 0x66, 0x19, 0x4b, 0x89, 0x84, 0x2a, 0x79, 0xa9, 0xf5, 0xee, 0x33, 0x64, 0x3f, 0xd6, 0x51, 0x1e, 0x6a, 0x7b, 0x8c, 0xf8, 0xdb, 0x4e, 0x5f, 0x9e, 0x30, 0x6b, 0xad, 0xde, 0x2f, 0xa8, 0x2d, 0x83, 0xbc, 0x54, 0x1d, 0x99, 0xc8, 0xaa, 0x89, 0xa1, 0x3a, 0x31, 0x46, 0x36, 0xcf, 0x6c, 0xf4, 0x4b, 0x75, 0x1c, 0xfe, 0x3e, 0x6b, 0x04, 0xb4, 0xe5, 0x75, 0x35, 0x46, 0x8a, 0x54, 0x5b, 0xd8, 0x8a, 0xbe, 0xcb, 0x66, 0x3f, 0x57, 0x59, 0xdb, 0x4c, 0x90, 0x9f, 0x9a, 0x47, 0x9e, 0xcc, 0x28, 0xa1, 0x87, 0x0f, 0xd0, 0x9a, 0x15, 0xf2, 0x1b, 0x73, 0x4e, 0xa8, 0x39, 0x66, 0xa8, 0x1e, 0x1f, 0x52, 0x65, 0x64, 0xa9, 0x7a, 0x7c, 0x5e, 0x53, 0x43, 0xf3, 0x28, 0x59, 0xc0, 0x91, 0xc2, 0x43, 0xb4, 0xe3, 0x68, 0xbd, 0x79, 0xcc, 0x26, 0x86, 0xe1, 0xf5, 0x2e, 0x60, 0xad, 0xb7, 0x18, 0x8b, 0xff, 0xba, 0x06, 0x07, 0xd7, 0x1e, 0x07, 0x77, 0x87, 0xe8, 0x81, 0x5f, 0x7b, 0x88, 0x77, 0xa6, 0xe1, 0xad, 0x2e, 0x52, 0x0e, 0x65, 0x2c, 0x0e, 0x65, 0xb2, 0xb8, 0x5a, 0xb9, 0x92, 0x6b, 0x95, 0x2b, 0xb9, 0x4e, 0xb9, 0x92, 0x71, 0xca, 0x95, 0x8c, 0xc7, 0x95, 0xcc, 0x10, 0x37, 0x28, 0x27, 0x72, 0x93, 0x72, 0x22, 0x37, 0xe3, 0x44, 0xe6, 0x8a, 0x5b, 0xf0, 0x20, 0xf3, 0xc5, 0x6d, 0xb8, 0x8f, 0x4f, 0xc4, 0x9d, 0xca, 0x77, 0x3c, 0xa0, 0x7c, 0xc7, 0x83, 0xca, 0x47, 0x4c, 0x55, 0x3e, 0xe2, 0x61, 0x7c, 0xc4, 0x16, 0xf1, 0x48, 0xc8, 0x76, 0xdc, 0xc4, 0x34, 0xe5, 0x23, 0x1e, 0x53, 0x3e, 0xe2, 0x71, 0xe5, 0x23, 0xa6, 0xe3, 0x23, 0x92, 0xc4, 0x13, 0xca, 0x41, 0x3c, 0xa5, 0x1c, 0xc4, 0xd3, 0xf6, 0x87, 0xec, 0x0f, 0x89, 0x67, 0xec, 0x27, 0xed, 0x27, 0xc5, 0x0c, 0x3d, 0x4c, 0x0f, 0x13, 0xcf, 0x2a, 0xf5, 0x9e, 0xa9, 0xd4, 0xfb, 0x25, 0xa5, 0xde, 0x0b, 0x94, 0x7a, 0x7f, 0xa4, 0xd4, 0x7b, 0x09, 0xea, 0x9d, 0x2d, 0x3e, 0x43, 0xb7, 0x5d, 0xe2, 0x73, 0xa5, 0xd8, 0xdf, 0x2a, 0xc5, 0x5e, 0x8f, 0x62, 0xfb, 0xc4, 0x26, 0xb4, 0xfa, 0x37, 0xb1, 0x05, 0xad, 0x36, 0xc4, 0x1e, 0x54, 0x3a, 0x42, 0x78, 0x95, 0x3e, 0x1f, 0x51, 0xfa, 0x5c, 0xa1, 0xf4, 0xf9, 0x98, 0xd2, 0xe7, 0x93, 0x4a, 0x9f, 0x7f, 0x53, 0xfa, 0x7c, 0xda, 0xd4, 0x67, 0x4d, 0x98, 0xfa, 0xac, 0x29, 0x7d, 0xd6, 0x6c, 0xc6, 0x40, 0x63, 0xa0, 0x66, 0x37, 0xf5, 0x59, 0xd3, 0x4d, 0x7d, 0xd6, 0x9c, 0xe8, 0xf3, 0x35, 0x5a, 0xb8, 0xa9, 0xcc, 0x5a, 0xac, 0xa9, 0xcc, 0xda, 0x79, 0xa6, 0x32, 0x6b, 0x9d, 0x4d, 0x65, 0xd6, 0xba, 0xa2, 0xcc, 0x73, 0xb4, 0x0b, 0x4c, 0x4d, 0xd6, 0xfa, 0xa1, 0xc9, 0xeb, 0xb5, 0x0c, 0x53, 0x8d, 0xb5, 0x49, 0xa8, 0xf1, 0x4e, 0xed, 0x71, 0x53, 0x87, 0xb5, 0xa7, 0x4c, 0x1d, 0xd6, 0x9e, 0x37, 0x75, 0x58, 0x9b, 0x65, 0xea, 0xb0, 0xf6, 0xa2, 0xa9, 0xc3, 0xda, 0x6b, 0xa6, 0x0e, 0x6b, 0x6f, 0xa1, 0xb4, 0x9b, 0xb5, 0xb7, 0xe9, 0xc5, 0xc7, 0xf0, 0xeb, 0x9d, 0x50, 0x5b, 0xd3, 0xb1, 0x6b, 0x38, 0xf6, 0xae, 0xbc, 0xd6, 0x8d, 0xc5, 0x21, 0xba, 0xb3, 0x18, 0xf4, 0xa6, 0x79, 0x4d, 0x33, 0xd3, 0xc3, 0x07, 0x29, 0x0f, 0x1f, 0x8c, 0x87, 0xef, 0x83, 0x6f, 0xef, 0x2b, 0xfa, 0xb1, 0x7d, 0x7f, 0x96, 0x10, 0x31, 0x40, 0x98, 0xf7, 0x94, 0x1a, 0x24, 0xae, 0xc0, 0x51, 0x5f, 0x49, 0x34, 0x18, 0x44, 0xc3, 0xb5, 0x7c, 0xca, 0xf4, 0xf9, 0xa1, 0xca, 0xe7, 0xdb, 0x95, 0xcf, 0x77, 0x28, 0x9f, 0xaf, 0x2b, 0x9f, 0xaf, 0xe3, 0xf2, 0xef, 0x60, 0x4b, 0xd3, 0xdb, 0x3b, 0xf0, 0xf6, 0xf7, 0xe1, 0xd8, 0xef, 0x67, 0x09, 0xc6, 0xdf, 0x4f, 0xa2, 0x64, 0xd3, 0xdb, 0x37, 0x52, 0xde, 0x3e, 0x0c, 0x6f, 0xff, 0x20, 0x11, 0x36, 0x95, 0xa8, 0x6a, 0x48, 0x54, 0x3d, 0xc6, 0xe3, 0xe3, 0x2c, 0x21, 0x62, 0xba, 0x78, 0x82, 0x72, 0x9e, 0x64, 0x69, 0x28, 0x9e, 0x62, 0x09, 0x11, 0x4f, 0xe3, 0xf9, 0x75, 0xe5, 0xf9, 0x1b, 0x29, 0xcf, 0xdf, 0x58, 0x79, 0xfe, 0x26, 0xca, 0xf3, 0x37, 0x55, 0x9e, 0xbf, 0x99, 0xf2, 0xfc, 0xe1, 0x2a, 0x2e, 0x35, 0xe5, 0x9c, 0x6d, 0xca, 0x39, 0x07, 0xa9, 0xe8, 0x34, 0x54, 0x74, 0x6a, 0x2a, 0x3a, 0x1b, 0xaa, 0xe8, 0x0c, 0x57, 0x9e, 0xd9, 0xa9, 0x3c, 0x73, 0xa8, 0x8a, 0xd1, 0x70, 0x15, 0xa3, 0x0d, 0x94, 0x5b, 0x6e, 0xa4, 0xdc, 0x72, 0x33, 0x22, 0x55, 0xdd, 0x11, 0x0c, 0xcf, 0x6c, 0x28, 0xcf, 0x1c, 0xae, 0xa2, 0x56, 0x57, 0x51, 0xeb, 0x54, 0x51, 0x6b, 0x57, 0x51, 0xab, 0xab, 0xa8, 0xd5, 0x55, 0xd4, 0xea, 0x2a, 0x6a, 0x1b, 0xaa, 0xa8, 0x75, 0xaa, 0xa8, 0x0d, 0x52, 0x51, 0xdb, 0x48, 0xb9, 0xdf, 0x30, 0xe5, 0x7e, 0x1d, 0x2a, 0x76, 0x1b, 0xaa, 0xd8, 0x75, 0xaa, 0xd8, 0x0d, 0x57, 0xb1, 0xab, 0xab, 0xd8, 0xd5, 0xf5, 0x6f, 0x88, 0xd7, 0x60, 0x15, 0xaf, 0x9a, 0x8a, 0x57, 0x9b, 0x72, 0x9b, 0x36, 0xe5, 0x36, 0x83, 0x02, 0xdc, 0x66, 0xb0, 0x8a, 0xdd, 0x60, 0x15, 0xbb, 0xc1, 0xca, 0x6d, 0x06, 0x2b, 0xb7, 0x19, 0x42, 0x04, 0x9f, 0xa0, 0x9c, 0x2a, 0xbd, 0x8a, 0xf5, 0x93, 0x44, 0xb3, 0xae, 0x9c, 0x67, 0xa8, 0x72, 0x9e, 0x76, 0xe5, 0x3c, 0xed, 0xca, 0x79, 0x3a, 0x54, 0x64, 0xeb, 0x2a, 0xb2, 0x75, 0x15, 0xd9, 0xba, 0x8a, 0x6c, 0x5d, 0x45, 0xb6, 0xae, 0x22, 0x5b, 0x57, 0xce, 0x53, 0x57, 0xce, 0x53, 0x57, 0xce, 0xd3, 0x30, 0x23, 0x9b, 0x75, 0xd3, 0x79, 0x3a, 0x94, 0xf3, 0x74, 0x28, 0xe7, 0xa9, 0x2b, 0xe7, 0xe9, 0x54, 0xce, 0xd3, 0xa9, 0x9c, 0xa7, 0x53, 0x39, 0x4f, 0x4d, 0x39, 0xcf, 0x30, 0xe5, 0x3c, 0xc3, 0x94, 0xf3, 0x6c, 0xa0, 0x9c, 0x67, 0x03, 0xa2, 0xfc, 0x6d, 0xd1, 0xd0, 0x78, 0x07, 0xff, 0xa9, 0x2b, 0xff, 0xd9, 0x48, 0xf9, 0xcf, 0x46, 0xca, 0x7f, 0x36, 0x52, 0xfe, 0xb3, 0xb1, 0xf2, 0x9f, 0x4d, 0x94, 0xff, 0x6c, 0xaa, 0xfc, 0x67, 0x33, 0xe5, 0x3f, 0x9b, 0x29, 0xff, 0x19, 0xae, 0xfc, 0xa7, 0xc3, 0x8c, 0x7b, 0xd3, 0x61, 0x8a, 0xa5, 0x2a, 0x07, 0x6e, 0xae, 0x8e, 0xa6, 0xe8, 0x2a, 0x07, 0x0e, 0x22, 0x1a, 0x1b, 0xf0, 0x6c, 0x66, 0xc2, 0x41, 0xea, 0x08, 0x8a, 0xae, 0xf2, 0xe1, 0x08, 0xb6, 0xa7, 0xcd, 0x2a, 0x2b, 0x36, 0x8f, 0x55, 0xc5, 0xb3, 0x6e, 0xe6, 0xc6, 0x41, 0xea, 0x78, 0x89, 0xae, 0x32, 0x64, 0x47, 0x40, 0x86, 0x5c, 0x7d, 0xbc, 0x24, 0x4c, 0x65, 0xc8, 0x86, 0xca, 0x90, 0x1b, 0xaa, 0x0c, 0x39, 0x54, 0x65, 0xc8, 0x8d, 0x54, 0x86, 0xdc, 0x52, 0xe5, 0xc6, 0x91, 0x6a, 0x5c, 0x45, 0xab, 0x71, 0x15, 0xa9, 0x72, 0x63, 0x2d, 0x20, 0x37, 0x8e, 0x51, 0x19, 0x6f, 0xa4, 0x1a, 0x09, 0xd1, 0x2a, 0xbf, 0x4d, 0x54, 0x99, 0xad, 0xa6, 0x32, 0xdb, 0xa6, 0x2a, 0xbe, 0x1b, 0xa8, 0xf8, 0xb6, 0xa9, 0xf8, 0x0e, 0x57, 0xf1, 0x6d, 0x57, 0xf1, 0xdd, 0x4c, 0xe5, 0xb4, 0xf1, 0x2a, 0xa7, 0x75, 0xaa, 0x9c, 0x36, 0x58, 0xe5, 0xb4, 0x4e, 0x95, 0xd3, 0x06, 0xab, 0x9c, 0x36, 0x4e, 0xe5, 0xb4, 0x51, 0x2a, 0x9b, 0x8d, 0x52, 0x79, 0x6c, 0x0b, 0x95, 0xc7, 0xb6, 0x50, 0x79, 0x6c, 0x88, 0xca, 0x63, 0x83, 0x54, 0x1e, 0xdb, 0x58, 0xe5, 0xb1, 0x91, 0x2a, 0x77, 0x0d, 0x52, 0x47, 0x74, 0x74, 0x95, 0xbb, 0x06, 0xa9, 0x23, 0x3a, 0xba, 0x3a, 0xa2, 0xa3, 0xab, 0x3c, 0x36, 0x48, 0x65, 0xb0, 0xa1, 0x2a, 0x83, 0x6d, 0xa4, 0x32, 0xd8, 0x20, 0xdb, 0x0d, 0xb6, 0x1b, 0x78, 0xd7, 0xcc, 0x63, 0x1b, 0x05, 0x1c, 0xd1, 0xd1, 0x55, 0x36, 0x1b, 0x5a, 0x7b, 0x44, 0xe7, 0x2e, 0x5e, 0x31, 0x73, 0xda, 0x46, 0x6a, 0x4c, 0x46, 0xaa, 0x31, 0x19, 0xad, 0xc6, 0x61, 0x53, 0x35, 0x0e, 0x9b, 0xa9, 0x71, 0x98, 0x10, 0x90, 0xbb, 0x46, 0xab, 0xd1, 0xd8, 0x4c, 0x8d, 0xc6, 0x44, 0x35, 0x1a, 0xa3, 0xd5, 0x68, 0xb4, 0xab, 0x11, 0xd8, 0x4c, 0x65, 0xad, 0x9a, 0xca, 0x5a, 0x9b, 0xaa, 0x71, 0x98, 0xa0, 0xc6, 0x61, 0x8c, 0x3a, 0x56, 0xa4, 0xab, 0xd1, 0xa8, 0x85, 0xac, 0x0f, 0x59, 0xaf, 0x46, 0x63, 0x0e, 0xeb, 0x66, 0x2e, 0x1a, 0xa4, 0x8e, 0x1e, 0xe9, 0x6a, 0x4c, 0x46, 0xaa, 0x63, 0x48, 0xba, 0x1a, 0x99, 0xd1, 0x21, 0x07, 0x42, 0x0e, 0x08, 0x5d, 0x1d, 0x4f, 0xd2, 0xd5, 0x98, 0x6c, 0xaa, 0xc6, 0x64, 0xa4, 0xca, 0x48, 0x9b, 0xab, 0x8c, 0xb4, 0xb9, 0xca, 0x48, 0x9b, 0xab, 0xe3, 0x4c, 0xba, 0xca, 0x48, 0x83, 0x54, 0x46, 0x1a, 0x64, 0x9f, 0x64, 0x9f, 0xca, 0x2b, 0xe6, 0xd1, 0x45, 0x5d, 0x1d, 0x5b, 0xd2, 0xd5, 0xb1, 0x25, 0x5d, 0x1d, 0x5b, 0xd2, 0x55, 0xbe, 0x1a, 0xa4, 0x8e, 0x2d, 0xe9, 0xea, 0xd8, 0x92, 0xae, 0x8e, 0x2d, 0xe9, 0xea, 0xd8, 0x92, 0x6e, 0x2f, 0xb5, 0x97, 0xf2, 0x68, 0x66, 0xb3, 0x11, 0x2a, 0x9b, 0x8d, 0x50, 0x47, 0x98, 0x74, 0x75, 0xd4, 0xb1, 0xfa, 0xa8, 0x92, 0xae, 0x8e, 0x3a, 0x56, 0x1f, 0x4f, 0x0a, 0x52, 0x59, 0x6e, 0x90, 0xca, 0x72, 0x83, 0xd4, 0xf1, 0x24, 0x5d, 0x1d, 0x4f, 0xd2, 0x55, 0xc6, 0xeb, 0x50, 0x19, 0xaf, 0xe3, 0x1c, 0x19, 0x6f, 0xf5, 0xf1, 0xa4, 0x30, 0x95, 0xf1, 0x1a, 0x2a, 0xe3, 0x6d, 0xa8, 0x32, 0xde, 0x50, 0x95, 0xf1, 0x86, 0xaa, 0x8c, 0xb7, 0x91, 0xca, 0x78, 0x1b, 0x29, 0xcd, 0x6c, 0xa9, 0x34, 0xb3, 0xa5, 0xd2, 0xcc, 0x96, 0x6a, 0xf6, 0x89, 0x54, 0xb3, 0x4f, 0xa4, 0x9a, 0x7d, 0xa2, 0xd5, 0xec, 0x13, 0xad, 0x66, 0x9f, 0x48, 0x35, 0xfb, 0x44, 0xaa, 0xd9, 0x27, 0x52, 0xcd, 0x3e, 0x91, 0x6a, 0xf6, 0xd1, 0x54, 0xae, 0xab, 0x05, 0xe4, 0xba, 0x31, 0x6a, 0xc6, 0x89, 0x51, 0xb3, 0x43, 0xa4, 0x9a, 0x1d, 0x22, 0xd5, 0xec, 0x10, 0xa9, 0xe6, 0x82, 0x44, 0x35, 0x17, 0x24, 0xaa, 0x2c, 0xb4, 0xa9, 0x1a, 0xf3, 0x0d, 0xd4, 0x98, 0xb7, 0xa9, 0x31, 0x1f, 0xae, 0xc6, 0xbc, 0x5d, 0x8d, 0x79, 0xbb, 0x1a, 0xf3, 0xcd, 0x54, 0xb6, 0xd9, 0x42, 0x65, 0x9b, 0x41, 0x2a, 0xdb, 0x6c, 0xac, 0xb2, 0xcd, 0x48, 0x75, 0x3c, 0x4c, 0x57, 0xc7, 0xc3, 0x74, 0x95, 0x73, 0x46, 0x53, 0x86, 0xfa, 0xa6, 0xd8, 0x29, 0x1a, 0x5c, 0x21, 0xa2, 0xd0, 0x3f, 0xd3, 0xe1, 0xed, 0xa9, 0xfe, 0x3e, 0xe9, 0x7f, 0xf9, 0x57, 0x93, 0xa7, 0xe0, 0xcb, 0x7c, 0x81, 0xdf, 0x38, 0xfd, 0x97, 0xf7, 0xb2, 0xef, 0xf7, 0x1c, 0xe5, 0x7f, 0xd8, 0x16, 0x37, 0x59, 0xc1, 0x7e, 0x33, 0x87, 0xc2, 0x85, 0x56, 0xfd, 0x99, 0x1f, 0xff, 0xc7, 0x7b, 0x39, 0x5a, 0xef, 0x5b, 0xe2, 0x93, 0xff, 0xa3, 0xfd, 0x74, 0x91, 0x37, 0xd7, 0xf9, 0x3f, 0x4a, 0xf6, 0x25, 0x17, 0xc8, 0x97, 0xf7, 0xcb, 0x51, 0xa7, 0x17, 0xcb, 0xb7, 0x4f, 0x1f, 0xa2, 0x95, 0x7d, 0xe5, 0x30, 0x39, 0xdc, 0x3c, 0x67, 0xe2, 0xf4, 0xa3, 0xa7, 0x77, 0x49, 0xbb, 0xec, 0x2a, 0x3b, 0xcb, 0x7e, 0xf2, 0x4e, 0x32, 0xbe, 0xae, 0xb2, 0x97, 0x4c, 0x92, 0x03, 0xa4, 0x26, 0x83, 0x65, 0xda, 0x39, 0xf7, 0xb1, 0x93, 0xcc, 0x61, 0x4f, 0x9d, 0xef, 0xf3, 0xcc, 0xef, 0x2f, 0x77, 0xe3, 0xe9, 0x37, 0x99, 0x79, 0x01, 0x51, 0xe1, 0xc6, 0x7f, 0xbf, 0xcf, 0x1b, 0x3a, 0xb1, 0xe8, 0xab, 0x66, 0x56, 0x7d, 0x03, 0xbb, 0xc0, 0xfa, 0x86, 0xff, 0x6d, 0xeb, 0x3b, 0x9e, 0xd7, 0xc9, 0x64, 0x32, 0x65, 0x2e, 0x19, 0xc5, 0x4f, 0x94, 0x98, 0x55, 0xf3, 0x8d, 0x6d, 0xcd, 0x37, 0x44, 0x72, 0xdb, 0x19, 0x67, 0x13, 0xac, 0x61, 0xeb, 0x6f, 0xac, 0x6c, 0xfd, 0x47, 0x4a, 0x98, 0x57, 0x9b, 0x65, 0xe8, 0xea, 0x78, 0x8b, 0x9f, 0xbc, 0xff, 0x4d, 0xb9, 0x15, 0xc5, 0x32, 0x73, 0x9a, 0x47, 0x55, 0x26, 0xfd, 0xa0, 0x7c, 0xd3, 0xda, 0x6a, 0xdd, 0x5f, 0x60, 0xf0, 0x40, 0xdd, 0xa3, 0x04, 0xe6, 0xf7, 0xa7, 0x2a, 0xfb, 0xaf, 0xfe, 0xbe, 0xd8, 0x45, 0x8b, 0x3c, 0xd5, 0xdf, 0x7d, 0x5b, 0x67, 0x4b, 0x98, 0x99, 0x46, 0x91, 0xdc, 0xcb, 0x9a, 0x39, 0xf6, 0x8e, 0x5b, 0x47, 0x6d, 0x0e, 0xa9, 0x1c, 0xa4, 0xfc, 0x9c, 0x7b, 0x09, 0xf8, 0x26, 0x5b, 0x7d, 0x8b, 0x58, 0x44, 0xb9, 0x65, 0x01, 0x47, 0x61, 0x8a, 0xd4, 0xb9, 0x07, 0x45, 0x56, 0x86, 0x7e, 0x44, 0xe5, 0x8c, 0x55, 0x64, 0xc4, 0xf9, 0x67, 0xfb, 0x7e, 0x3d, 0x80, 0xb1, 0xad, 0x75, 0xcf, 0xb0, 0x30, 0x8f, 0x05, 0x04, 0x30, 0x66, 0x66, 0x5a, 0x6f, 0xfc, 0x9e, 0x97, 0xa9, 0x63, 0x0e, 0x15, 0xf2, 0x63, 0xc5, 0x58, 0x98, 0x62, 0xec, 0x61, 0x95, 0x1b, 0x3f, 0x50, 0xfd, 0xfd, 0x21, 0xeb, 0x6b, 0xcf, 0xb2, 0x97, 0x19, 0x66, 0xb6, 0x1d, 0xf0, 0xff, 0x83, 0xf2, 0x6a, 0xb2, 0xbe, 0x9d, 0xf2, 0x6b, 0x72, 0xb7, 0x21, 0x8c, 0xa5, 0x1b, 0xd5, 0x99, 0x18, 0xb7, 0xca, 0x27, 0x45, 0x84, 0x2c, 0x3a, 0x7d, 0x50, 0x8e, 0x20, 0xab, 0x7b, 0x5c, 0xbe, 0xc0, 0x6b, 0x59, 0xf2, 0x69, 0xf9, 0x3e, 0xb5, 0x9c, 0x2f, 0x3f, 0x27, 0x6b, 0xbd, 0x5f, 0x66, 0x5a, 0x25, 0x14, 0xd6, 0x39, 0x4a, 0xe4, 0x3b, 0x6b, 0xdb, 0x6e, 0x0a, 0xc8, 0xc1, 0x07, 0x59, 0xcf, 0xaf, 0x81, 0xc7, 0xcd, 0xb5, 0xd3, 0xf9, 0xf2, 0x3c, 0x79, 0x49, 0xed, 0xb6, 0xff, 0x92, 0x4f, 0xc8, 0x17, 0xe5, 0xc3, 0xf2, 0x65, 0x39, 0xfa, 0x5c, 0xe3, 0x51, 0x1d, 0xaf, 0x79, 0x44, 0xe5, 0xf3, 0x53, 0xeb, 0x9c, 0xb1, 0x51, 0x51, 0x3f, 0x03, 0x97, 0x5f, 0xab, 0xed, 0x4f, 0x05, 0x44, 0xe1, 0x09, 0x33, 0xbf, 0xe6, 0xd9, 0x43, 0xab, 0xcc, 0xa3, 0x73, 0x07, 0xe1, 0x6c, 0xbd, 0xbc, 0x56, 0x5e, 0x26, 0xef, 0x22, 0x12, 0x5e, 0xe3, 0xd5, 0x5d, 0x7f, 0x74, 0xcc, 0x44, 0x8d, 0x09, 0xf3, 0x1b, 0x77, 0xb3, 0x8f, 0xf7, 0xaa, 0xfe, 0x3e, 0x55, 0xe7, 0xe8, 0xd4, 0xa9, 0xba, 0x67, 0x11, 0xd0, 0x6f, 0xdf, 0x9e, 0x79, 0x66, 0x41, 0xbd, 0x32, 0xab, 0x8f, 0x68, 0x15, 0xfd, 0x49, 0x74, 0x9f, 0xe5, 0x7d, 0xeb, 0xbc, 0x9f, 0x4a, 0x15, 0xed, 0x07, 0x02, 0xce, 0xc3, 0xc9, 0x3f, 0x6b, 0x09, 0x5b, 0xaa, 0xbf, 0xa7, 0x0e, 0x78, 0xe5, 0xa5, 0xda, 0xb5, 0x1d, 0xb5, 0x6b, 0x4f, 0x5b, 0xcf, 0x33, 0xcf, 0x55, 0xeb, 0x9a, 0x23, 0x0b, 0x67, 0x7d, 0xcf, 0x23, 0x4b, 0x64, 0x59, 0x60, 0x54, 0x9c, 0x79, 0x1c, 0xf2, 0xf7, 0xef, 0x8e, 0x03, 0xde, 0xd9, 0x5b, 0x53, 0x37, 0xe6, 0x93, 0x4d, 0x35, 0xdf, 0xf6, 0xfe, 0x7e, 0x8c, 0xec, 0xcc, 0xb3, 0x47, 0xce, 0xde, 0xc6, 0x9a, 0xf3, 0x15, 0xce, 0x5e, 0xf7, 0x33, 0x8f, 0xf9, 0x9c, 0x3d, 0x62, 0xff, 0xd2, 0x5e, 0x76, 0x9d, 0x73, 0x2f, 0x27, 0xfe, 0xe6, 0x5e, 0xb6, 0xd5, 0x9c, 0x7f, 0x50, 0xfb, 0xca, 0xa7, 0xb5, 0x6b, 0xb5, 0xe7, 0x1d, 0xc8, 0x77, 0xad, 0xe7, 0xb7, 0x6a, 0xce, 0x6b, 0x3a, 0x97, 0xb6, 0xff, 0x03, 0xed, 0xc9, 0x3d, 0xe3, 0x95, 0x97, 0x98, 0xb5, 0xce, 0x76, 0x24, 0xf9, 0xf8, 0x1f, 0x96, 0x73, 0xb7, 0x3a, 0xaa, 0x74, 0xf7, 0x9f, 0xec, 0x6d, 0xe1, 0x99, 0xfd, 0x62, 0x9e, 0x67, 0xa5, 0x22, 0xf9, 0x41, 0x6b, 0xfe, 0xcc, 0x91, 0x9f, 0xa9, 0x57, 0xee, 0xfc, 0xbb, 0xed, 0xac, 0x3d, 0xab, 0xe3, 0x1f, 0xb3, 0x61, 0x1d, 0x6f, 0xea, 0x25, 0x72, 0x84, 0x79, 0x4e, 0x81, 0x8b, 0x45, 0x13, 0x6e, 0x16, 0x9b, 0xd8, 0xcd, 0x12, 0x24, 0xf6, 0xb0, 0x04, 0x8b, 0x03, 0x2c, 0x21, 0xe2, 0x88, 0xa8, 0x22, 0xd3, 0x3a, 0x25, 0x4e, 0x93, 0xb5, 0x08, 0xcd, 0x4e, 0x26, 0x64, 0x68, 0xf8, 0x4a, 0xcd, 0xa9, 0x39, 0x45, 0x4b, 0xad, 0xa1, 0xd6, 0x50, 0xc4, 0x6a, 0x4d, 0xb4, 0xe6, 0x64, 0x3f, 0x51, 0x5a, 0x94, 0x48, 0xd2, 0x5a, 0x68, 0x2d, 0x44, 0x1b, 0x2d, 0x56, 0x8b, 0x15, 0xc9, 0x5a, 0x82, 0x96, 0x28, 0x52, 0xfe, 0x3f, 0xf6, 0xde, 0x06, 0xce, 0xa6, 0x6a, 0xfd, 0x03, 0x5f, 0x6b, 0xef, 0x7d, 0xce, 0x7e, 0x3b, 0xef, 0xef, 0xe7, 0x0c, 0x63, 0x9a, 0x19, 0x63, 0x30, 0xc6, 0x18, 0x43, 0xf2, 0x32, 0x5e, 0x43, 0xd2, 0x24, 0x4d, 0x92, 0x24, 0x57, 0x92, 0x24, 0x69, 0x92, 0x24, 0x49, 0x92, 0x24, 0xc9, 0x75, 0x25, 0x49, 0x92, 0x24, 0x57, 0x92, 0x5c, 0x57, 0x48, 0x92, 0x24, 0x49, 0xd2, 0x24, 0x49, 0x92, 0x5c, 0x49, 0xd2, 0x24, 0x49, 0x92, 0x9c, 0xff, 0xb3, 0xbe, 0xfb, 0xcc, 0x38, 0x33, 0xa8, 0xe9, 0x76, 0xbb, 0xf7, 0xff, 0xfb, 0x7f, 0xfe, 0xf3, 0x7c, 0xf6, 0xb3, 0xd7, 0x79, 0xf6, 0xda, 0x6b, 0xaf, 0xbd, 0xd6, 0xb3, 0xd6, 0x5e, 0xcf, 0x77, 0x3f, 0xcf, 0x1e, 0x9e, 0xc1, 0x33, 0x58, 0x5d, 0x5e, 0x87, 0x67, 0xb3, 0x7a, 0x9c, 0x88, 0xe5, 0x70, 0x22, 0xd6, 0x80, 0xe7, 0xf1, 0x3c, 0x96, 0x2b, 0x2f, 0x97, 0x57, 0xb0, 0x86, 0xf2, 0x2a, 0xf9, 0x15, 0x96, 0x2f, 0xbf, 0x2a, 0xaf, 0x65, 0x05, 0xf2, 0x06, 0x79, 0x23, 0x6b, 0x26, 0xbf, 0x2b, 0xbf, 0xcf, 0x5a, 0xc8, 0x1f, 0xca, 0xdb, 0x59, 0x1b, 0xf9, 0x63, 0xf9, 0x63, 0xd6, 0x4e, 0xde, 0x27, 0x7f, 0xc1, 0xda, 0xcb, 0x5f, 0xca, 0x07, 0xd8, 0x85, 0xf2, 0x41, 0xf9, 0x1b, 0xd6, 0x49, 0x3e, 0x2c, 0x1f, 0x66, 0x17, 0xcb, 0x47, 0xe5, 0x1f, 0x58, 0x57, 0xf9, 0x67, 0xf9, 0x67, 0x56, 0x24, 0xff, 0x22, 0xff, 0xc2, 0x2e, 0x95, 0xe3, 0x72, 0x9c, 0x75, 0x53, 0x64, 0x45, 0x61, 0x97, 0x29, 0x6d, 0x95, 0xb6, 0xec, 0x72, 0xa5, 0xab, 0xd2, 0x95, 0x15, 0xdb, 0x76, 0xdb, 0x76, 0xb3, 0x2b, 0x6c, 0xdf, 0xd8, 0xbe, 0x61, 0x3d, 0x6c, 0x3f, 0xd8, 0x7e, 0x60, 0x57, 0xaa, 0x8b, 0xd5, 0xc5, 0xac, 0xa7, 0x5a, 0xaa, 0x7e, 0xc0, 0xae, 0x52, 0xbf, 0x54, 0x0f, 0xb0, 0xab, 0xd5, 0x5f, 0xd4, 0x5f, 0xd8, 0x35, 0x46, 0x43, 0xa3, 0x3b, 0xeb, 0x63, 0x14, 0x1b, 0x83, 0xd9, 0x58, 0xe3, 0x16, 0x63, 0x34, 0x7b, 0xc2, 0x78, 0xcc, 0x58, 0xcc, 0x9e, 0x37, 0x96, 0x18, 0x2b, 0xd8, 0x1b, 0xc6, 0x2a, 0xe3, 0x35, 0xb6, 0xd9, 0x78, 0xdd, 0x78, 0x9d, 0x95, 0x1a, 0x6f, 0x18, 0x6f, 0xb2, 0x0f, 0x8c, 0x8d, 0xc6, 0x46, 0xb6, 0xdd, 0xd8, 0x62, 0x6c, 0x61, 0x1f, 0x89, 0xef, 0xaa, 0xa1, 0x2d, 0xaf, 0x3b, 0x47, 0xcb, 0xca, 0x68, 0x59, 0x85, 0x5a, 0xf6, 0x08, 0xb5, 0xef, 0xf7, 0x44, 0x76, 0x76, 0x94, 0x48, 0x65, 0x3f, 0x10, 0x69, 0xec, 0x18, 0x91, 0xce, 0x7e, 0x24, 0x32, 0xd8, 0x71, 0x22, 0x9d, 0xfd, 0x44, 0x7d, 0xe0, 0xa4, 0x3e, 0x38, 0x49, 0xe9, 0x5f, 0x88, 0x4c, 0xea, 0x8d, 0x53, 0xcc, 0x41, 0xfd, 0xc1, 0x98, 0x9d, 0x73, 0x4e, 0x96, 0x36, 0x57, 0xb8, 0x42, 0x69, 0x1b, 0xb7, 0x31, 0x27, 0xb7, 0x73, 0x3b, 0x49, 0x54, 0xea, 0x2d, 0x27, 0x7a, 0xcb, 0x85, 0xde, 0xd2, 0xd1, 0x5b, 0x4e, 0xea, 0x2d, 0x2f, 0x73, 0x73, 0x1f, 0x17, 0xff, 0x1d, 0x34, 0xc0, 0x03, 0xcc, 0xcb, 0x83, 0x3c, 0xc8, 0x7c, 0x3c, 0x44, 0xbd, 0xe8, 0x46, 0x2f, 0xfa, 0xd1, 0x8b, 0x21, 0xf4, 0x62, 0x88, 0x7a, 0x31, 0x8d, 0x05, 0xf8, 0x79, 0xd4, 0x97, 0x41, 0xf4, 0xa5, 0x97, 0xfa, 0xb2, 0x0e, 0xf1, 0x6c, 0xea, 0xd1, 0x10, 0x7a, 0x34, 0x84, 0x1e, 0x0d, 0xa3, 0x47, 0x03, 0xd4, 0xa3, 0xcb, 0x99, 0x2e, 0xbf, 0x2c, 0xbf, 0xcc, 0x9c, 0xf2, 0x0a, 0xea, 0x5d, 0x1b, 0xf5, 0xee, 0x2a, 0xa6, 0xca, 0xaf, 0x50, 0x1f, 0x1b, 0xd4, 0xc7, 0x6b, 0x88, 0xbf, 0x26, 0xbf, 0xc6, 0xec, 0xf2, 0x5a, 0xea, 0x6f, 0x1b, 0xf5, 0xf7, 0x5b, 0x94, 0xde, 0x48, 0xbd, 0xae, 0x53, 0xaf, 0xbf, 0x4b, 0xe9, 0x2d, 0xf2, 0x16, 0x4a, 0xbf, 0x27, 0xbf, 0x47, 0xe9, 0xad, 0xf2, 0x56, 0x4a, 0xbf, 0x4f, 0xda, 0x60, 0x87, 0x36, 0xd8, 0xa0, 0x0d, 0x3a, 0x69, 0xc3, 0x3e, 0x16, 0x92, 0xbf, 0x20, 0x9d, 0xf0, 0x90, 0x4e, 0x7c, 0xc9, 0x7c, 0xf2, 0x01, 0xd2, 0x0c, 0x3f, 0x69, 0xc6, 0xd7, 0xc4, 0x0f, 0xc9, 0x87, 0x98, 0x57, 0xfe, 0x86, 0xb4, 0x24, 0x00, 0x2d, 0x09, 0x40, 0x4b, 0xbc, 0xd0, 0x12, 0x2f, 0xb4, 0xc4, 0x0b, 0x2d, 0xf1, 0x42, 0x4b, 0x3c, 0xd0, 0x12, 0x17, 0xb4, 0x24, 0x0c, 0x2d, 0x91, 0xa1, 0x25, 0x6e, 0x68, 0x89, 0x06, 0x2d, 0xd1, 0xa1, 0x25, 0x3a, 0xb4, 0x44, 0x87, 0x96, 0xe8, 0xc6, 0x45, 0xc6, 0x65, 0x8c, 0x1b, 0xdd, 0x49, 0x57, 0x24, 0xd2, 0x95, 0x41, 0xc4, 0x6f, 0x22, 0x8d, 0x91, 0xa1, 0x31, 0x32, 0x34, 0x46, 0x21, 0x8d, 0x59, 0xc2, 0x6c, 0xc6, 0x4b, 0xc6, 0x52, 0xa6, 0x1a, 0xff, 0x30, 0xc8, 0x1a, 0x35, 0x96, 0x19, 0x2f, 0xd3, 0xb9, 0x2b, 0x48, 0x93, 0x0c, 0xd2, 0xa4, 0x57, 0x99, 0x69, 0xac, 0x21, 0x7d, 0xd2, 0xa1, 0x4f, 0x0e, 0xd2, 0xa7, 0x37, 0xc8, 0xaa, 0x5a, 0x4f, 0x5a, 0xe5, 0x83, 0x56, 0xb9, 0xa1, 0x55, 0x7e, 0x68, 0x55, 0x10, 0x38, 0x8b, 0x18, 0xb5, 0x2a, 0xfb, 0x17, 0xdb, 0x47, 0x7a, 0xb3, 0x9f, 0x89, 0x2f, 0x4f, 0x7f, 0x49, 0x64, 0x4f, 0x8c, 0x57, 0xa1, 0x49, 0x79, 0xd0, 0xa4, 0x7c, 0xd2, 0x9d, 0x9f, 0x58, 0x1a, 0xfb, 0x99, 0xc8, 0x01, 0xbd, 0xc9, 0x81, 0xde, 0x34, 0xe0, 0x12, 0x97, 0x58, 0x2e, 0xd7, 0xb8, 0xc6, 0x1a, 0x41, 0x3f, 0x1c, 0xdc, 0xc1, 0x9d, 0x34, 0x46, 0xbd, 0xa4, 0x13, 0x06, 0xb4, 0xe1, 0x3c, 0x68, 0x43, 0x0a, 0x0f, 0x93, 0x36, 0x18, 0x3c, 0x4a, 0xda, 0x10, 0xe5, 0x29, 0xa4, 0x0d, 0x06, 0x4f, 0xe5, 0xa9, 0xa4, 0x37, 0xb5, 0x78, 0x2d, 0x4a, 0x0b, 0xcd, 0x48, 0x87, 0x66, 0xd4, 0xe1, 0xe9, 0x3c, 0x9d, 0xe4, 0x19, 0x3c, 0x93, 0xe4, 0xb5, 0x79, 0x6d, 0x96, 0xc1, 0xb3, 0x48, 0x4b, 0x0c, 0x5e, 0x97, 0xf4, 0xc3, 0xe0, 0xf5, 0x79, 0x7d, 0x9a, 0x15, 0x84, 0x96, 0x78, 0x78, 0x03, 0xde, 0x80, 0x24, 0x0d, 0x79, 0x43, 0xd2, 0x18, 0x81, 0xaa, 0x98, 0x40, 0x55, 0xea, 0x01, 0x55, 0xe1, 0x40, 0x55, 0xea, 0x01, 0x55, 0xe1, 0x40, 0x55, 0x82, 0x40, 0x55, 0x24, 0x78, 0x0a, 0xb8, 0x81, 0xad, 0x48, 0xf0, 0x14, 0x70, 0x03, 0x61, 0x91, 0x81, 0xb0, 0xc8, 0x40, 0x58, 0x14, 0xf9, 0x59, 0xf9, 0x59, 0xd2, 0x92, 0x45, 0xf2, 0x22, 0xe2, 0x8b, 0xe5, 0xc5, 0xc4, 0x97, 0xca, 0x4b, 0x89, 0x0b, 0xfd, 0xcb, 0x93, 0xd7, 0x90, 0xe6, 0xd5, 0x95, 0xd7, 0xc9, 0xeb, 0x98, 0x43, 0x7e, 0x43, 0x7e, 0x83, 0xd2, 0xeb, 0xe5, 0xf5, 0x2c, 0x5b, 0x7e, 0x53, 0x7e, 0x93, 0x66, 0xa0, 0x0d, 0xf2, 0x06, 0x92, 0xbc, 0x45, 0x5a, 0x98, 0x2b, 0x6f, 0x92, 0xdf, 0x61, 0xf5, 0xa1, 0x8b, 0x69, 0xd0, 0xbf, 0x5c, 0xb9, 0x54, 0x2e, 0x65, 0x11, 0x79, 0x9b, 0xbc, 0x8d, 0xce, 0xfd, 0x48, 0xde, 0x41, 0xf9, 0x3f, 0x96, 0x77, 0xb2, 0x1a, 0xf2, 0xa7, 0xf2, 0xa7, 0x94, 0xe7, 0x5f, 0xf2, 0x7e, 0xd2, 0x69, 0xa1, 0x85, 0xe7, 0x91, 0x16, 0x1e, 0xa4, 0xf4, 0xd7, 0xa4, 0x85, 0xe9, 0xa4, 0x85, 0x65, 0x94, 0xfe, 0x56, 0xfe, 0x96, 0x74, 0x54, 0x68, 0x61, 0xba, 0xfc, 0x9d, 0x7c, 0x84, 0xa5, 0xca, 0xdf, 0xcb, 0xdf, 0xb3, 0x4c, 0xd2, 0xc8, 0xa3, 0x24, 0xf9, 0x41, 0x3e, 0xc6, 0x32, 0xe4, 0x1f, 0x65, 0x1a, 0xed, 0xf2, 0x71, 0xf9, 0x27, 0x96, 0x25, 0x9f, 0x90, 0x4f, 0x50, 0x5a, 0x68, 0x6a, 0x54, 0x3e, 0x49, 0x9a, 0x6a, 0xc8, 0xa7, 0xe4, 0x53, 0x94, 0x47, 0xe8, 0xab, 0xa1, 0x08, 0xa3, 0xda, 0xa7, 0x48, 0x8a, 0x44, 0x5a, 0x2b, 0x74, 0xd7, 0x50, 0x6c, 0x8a, 0x9d, 0x65, 0x2a, 0xaa, 0xa2, 0x92, 0xc4, 0x50, 0x0c, 0x16, 0x55, 0x4c, 0xc5, 0x24, 0x79, 0x1b, 0xa5, 0x0d, 0x73, 0x2a, 0x9d, 0x94, 0xce, 0xcc, 0xa1, 0x5c, 0xa4, 0x5c, 0x44, 0xe9, 0x2e, 0x4a, 0x17, 0x4a, 0x5f, 0xac, 0x5c, 0xcc, 0x6a, 0x42, 0xd7, 0x0d, 0xa5, 0x48, 0x29, 0x22, 0xde, 0x4d, 0xe9, 0x46, 0xbc, 0xbb, 0x72, 0x39, 0x95, 0x70, 0x85, 0x72, 0x05, 0x71, 0xe1, 0x83, 0x50, 0x0b, 0x3e, 0x08, 0xb5, 0xe0, 0x83, 0xe0, 0x82, 0x0f, 0x82, 0x17, 0x3e, 0x08, 0x2e, 0xf8, 0x20, 0x78, 0x31, 0x42, 0x52, 0x6c, 0x65, 0xb6, 0x32, 0xe6, 0xc1, 0x38, 0xc9, 0xb7, 0x1d, 0xb3, 0x1d, 0x63, 0x0e, 0xdb, 0x8f, 0xb6, 0x1f, 0x99, 0xdd, 0x76, 0xdc, 0x46, 0x33, 0x97, 0xda, 0x54, 0x6d, 0xca, 0x42, 0xea, 0xb5, 0x6a, 0x5f, 0xe2, 0xb7, 0xa8, 0xb7, 0x30, 0x87, 0x16, 0xd6, 0xc2, 0xc4, 0x1b, 0x6b, 0x8d, 0x89, 0x5f, 0xae, 0x5d, 0xce, 0xea, 0x6a, 0x77, 0x6a, 0x77, 0xb2, 0x86, 0xda, 0x14, 0x6d, 0x0a, 0xcb, 0xd6, 0x9e, 0xd6, 0x9e, 0x66, 0x31, 0x6d, 0x99, 0xf6, 0x4f, 0xe2, 0xaf, 0x68, 0xaf, 0x10, 0x5f, 0xaf, 0xad, 0x67, 0x1e, 0x6d, 0x83, 0xb6, 0x81, 0xd9, 0xb5, 0x77, 0xb5, 0x77, 0x29, 0xfd, 0x91, 0xf6, 0x11, 0xf1, 0x8f, 0xb5, 0x8f, 0x89, 0x7f, 0xa5, 0x7d, 0xc5, 0x3c, 0xba, 0xa1, 0x1b, 0xc4, 0x43, 0x7a, 0x88, 0x85, 0xf5, 0x88, 0x4e, 0xf3, 0x97, 0x9e, 0xa1, 0x67, 0x30, 0x43, 0xcf, 0xd4, 0x33, 0x29, 0x9d, 0xab, 0xe7, 0x12, 0x6f, 0xaf, 0xb7, 0x27, 0x49, 0x6f, 0xfd, 0x1a, 0x66, 0xd7, 0xfb, 0xe8, 0x7d, 0x28, 0xff, 0x2d, 0xfa, 0x70, 0x4a, 0xdf, 0xa1, 0xdf, 0x41, 0xe9, 0xbb, 0xf4, 0xbb, 0xe8, 0xe8, 0x18, 0x7d, 0x0c, 0xf1, 0x3d, 0xfa, 0x1e, 0x96, 0xa9, 0x1f, 0xd0, 0xbf, 0x62, 0x01, 0xfd, 0xb0, 0x7e, 0x84, 0x05, 0xe8, 0x69, 0xd0, 0x99, 0x46, 0xef, 0x58, 0x63, 0x1a, 0x8d, 0x5e, 0x31, 0xaa, 0xad, 0xf1, 0x9c, 0x87, 0xf1, 0x9c, 0x8f, 0x31, 0x9c, 0x43, 0x63, 0x78, 0x0d, 0xcb, 0x35, 0xd6, 0x1a, 0x6b, 0x59, 0x23, 0x8c, 0xe4, 0x06, 0x18, 0xc9, 0x06, 0x46, 0xf2, 0x79, 0x18, 0xc9, 0x29, 0xc6, 0xdb, 0xc6, 0x26, 0x92, 0x6c, 0xa6, 0xf1, 0x6c, 0x18, 0xef, 0x1b, 0xef, 0xb3, 0x0c, 0xa3, 0xd4, 0x28, 0xa5, 0xf4, 0x07, 0xc6, 0x07, 0xac, 0x36, 0x46, 0x78, 0x1d, 0x20, 0x2c, 0xb2, 0xf9, 0x57, 0xf3, 0xaf, 0x4c, 0x37, 0x57, 0x9a, 0x2b, 0x59, 0xc4, 0x5c, 0x65, 0xae, 0x62, 0x0e, 0x73, 0xb5, 0xb9, 0x9a, 0xf9, 0xcc, 0x57, 0xcd, 0x57, 0x99, 0xc7, 0xdc, 0x68, 0x6e, 0x64, 0x4e, 0xf3, 0x6d, 0xf3, 0x6d, 0x56, 0x93, 0x49, 0x7a, 0x77, 0xa1, 0x12, 0xfa, 0x3c, 0xa3, 0x90, 0x2c, 0xcb, 0x4b, 0xf1, 0x2e, 0xf5, 0x23, 0xb2, 0x32, 0x36, 0x93, 0x45, 0xf3, 0x09, 0x59, 0xbb, 0xbb, 0xc8, 0x72, 0xda, 0x40, 0xb6, 0xd9, 0x6b, 0xf1, 0x31, 0xf1, 0xce, 0x64, 0x89, 0x3e, 0x40, 0xb2, 0x0d, 0x64, 0x11, 0xd6, 0x8d, 0x7f, 0x4e, 0xfb, 0xdb, 0x69, 0x5d, 0x62, 0x9c, 0x7b, 0x0d, 0xf8, 0xa7, 0xe0, 0x00, 0xef, 0xc5, 0x27, 0x26, 0xfc, 0x1d, 0xb7, 0xd0, 0x5a, 0x78, 0x14, 0xd5, 0xec, 0x43, 0xb2, 0xb9, 0x86, 0xc5, 0xdb, 0x90, 0x64, 0x34, 0xc9, 0x5e, 0xa3, 0xfa, 0xd7, 0xc5, 0xf1, 0xe1, 0xc2, 0x52, 0xa7, 0xa3, 0x4b, 0xc8, 0x1a, 0x5c, 0x42, 0x56, 0x89, 0x16, 0xdf, 0x13, 0x6f, 0xc0, 0xfe, 0x47, 0x7f, 0xf1, 0x1b, 0x13, 0xfb, 0xf9, 0x49, 0xb2, 0x4d, 0x67, 0xe4, 0x7a, 0x8a, 0xee, 0xe1, 0x29, 0x6a, 0xf9, 0x97, 0xe2, 0xcb, 0xe2, 0x6d, 0xe2, 0x37, 0x10, 0x6d, 0xa1, 0x5f, 0xf9, 0x64, 0x03, 0x8e, 0xa6, 0x5e, 0x78, 0x9e, 0xd2, 0xf7, 0x51, 0x4f, 0x3c, 0x1f, 0x9f, 0x29, 0xde, 0x14, 0xfe, 0x1b, 0xb5, 0x38, 0x87, 0xed, 0x26, 0xde, 0xef, 0x57, 0x89, 0x47, 0x38, 0x1c, 0x7f, 0xe5, 0x2c, 0x56, 0xc8, 0x4d, 0xc9, 0xef, 0x90, 0xad, 0xb7, 0xc8, 0x48, 0x27, 0xe4, 0x62, 0x6d, 0x8a, 0xfd, 0xab, 0xff, 0x56, 0xed, 0x36, 0x9d, 0x55, 0xfa, 0x65, 0xfc, 0x86, 0x2a, 0x92, 0x17, 0xe3, 0xaf, 0xc4, 0x1f, 0x3c, 0x6d, 0x53, 0x25, 0xf6, 0x2f, 0xc7, 0xaf, 0x15, 0xb8, 0x8a, 0x88, 0xb9, 0x88, 0x97, 0xc6, 0xfb, 0x5b, 0x78, 0x11, 0x59, 0x75, 0x6f, 0xc6, 0xaf, 0x4d, 0xd8, 0x23, 0x8b, 0xe2, 0x47, 0xb0, 0x9f, 0x49, 0xd2, 0x2d, 0x64, 0xad, 0x1f, 0x81, 0xe7, 0xc0, 0xce, 0x72, 0xbf, 0xfa, 0xdf, 0xa8, 0xdd, 0x59, 0xef, 0x89, 0x74, 0x6a, 0x6c, 0x15, 0xc9, 0x34, 0xaa, 0xdf, 0xdf, 0x2b, 0x7e, 0x1d, 0xad, 0x68, 0xbb, 0xf1, 0x15, 0x7e, 0xd4, 0x6f, 0x53, 0x1f, 0x8e, 0xa9, 0xf0, 0x5d, 0x18, 0x9f, 0x48, 0x2d, 0x4f, 0xec, 0xd7, 0x51, 0x99, 0x9f, 0x91, 0x9e, 0xff, 0x00, 0xbf, 0xe8, 0x32, 0xfa, 0xf5, 0x43, 0x79, 0xee, 0x73, 0xe2, 0x56, 0x7b, 0xe3, 0x93, 0xe8, 0x3e, 0xb6, 0xc1, 0x33, 0xe0, 0x1b, 0xea, 0x99, 0xed, 0x18, 0x1f, 0x73, 0x84, 0xa7, 0x78, 0x7c, 0x0a, 0xc9, 0xc4, 0xef, 0x7c, 0xb2, 0x1f, 0xb6, 0xd3, 0x48, 0x59, 0x54, 0x39, 0xc6, 0x40, 0x1c, 0xf9, 0x93, 0x75, 0x5f, 0x20, 0x34, 0x2b, 0xcb, 0xd3, 0xd4, 0x77, 0x02, 0x77, 0x58, 0x51, 0x71, 0x74, 0xae, 0x85, 0x9c, 0x81, 0x3f, 0x5f, 0xe1, 0xd5, 0xf3, 0x5e, 0xb9, 0x37, 0x87, 0x65, 0xfb, 0x58, 0x76, 0xa3, 0xe5, 0xa5, 0xcd, 0xbc, 0x95, 0xf1, 0xb3, 0x24, 0xbf, 0x6b, 0x6f, 0x25, 0xc9, 0x4b, 0x56, 0x9f, 0xc5, 0xd7, 0x52, 0x2b, 0xbf, 0x1d, 0xdf, 0x4f, 0x9a, 0xf4, 0xce, 0x99, 0x08, 0x32, 0xe2, 0x52, 0xae, 0x48, 0xa4, 0x5b, 0xc7, 0x9f, 0x11, 0x68, 0x4b, 0xb9, 0x07, 0x02, 0xa5, 0xda, 0x9c, 0x1e, 0xbd, 0xe5, 0x78, 0x40, 0x7c, 0x46, 0xfc, 0xae, 0xf8, 0xb0, 0x44, 0x7a, 0x18, 0x4b, 0x2b, 0xd7, 0xfc, 0x04, 0xde, 0x92, 0x5f, 0xa1, 0x11, 0x49, 0xb5, 0x41, 0xfe, 0x46, 0x15, 0x3f, 0x53, 0xe1, 0x11, 0x70, 0x1f, 0x8e, 0x8c, 0xa0, 0x6b, 0x2e, 0x26, 0xad, 0x59, 0x1a, 0xbf, 0x31, 0x3e, 0xfb, 0xdf, 0x6a, 0xdf, 0x73, 0x62, 0x23, 0xa2, 0xdf, 0x2b, 0xfe, 0xea, 0x27, 0xf6, 0xb5, 0xc1, 0xd5, 0x4a, 0x19, 0xcf, 0x36, 0x2f, 0xd6, 0x4e, 0xec, 0x43, 0x49, 0x1e, 0x48, 0xae, 0xdf, 0xc6, 0xcf, 0xab, 0xda, 0xf7, 0x67, 0xb1, 0x9e, 0x3f, 0x2c, 0xd7, 0xf4, 0x24, 0x61, 0x6e, 0x62, 0x8f, 0xb9, 0x9b, 0x6c, 0x94, 0xe4, 0xbf, 0xbc, 0xb3, 0x5c, 0xaa, 0x6e, 0x62, 0x1f, 0xa5, 0xfa, 0x95, 0xff, 0x55, 0xf2, 0x94, 0x3a, 0x95, 0x40, 0x15, 0x4e, 0x7d, 0x1f, 0x97, 0x7f, 0xa5, 0xc6, 0x37, 0xc7, 0x5d, 0xa7, 0x2a, 0x66, 0x45, 0xab, 0x47, 0x7e, 0xf5, 0x0e, 0x57, 0xff, 0x9b, 0x63, 0x60, 0x12, 0x3c, 0x77, 0x9a, 0xc4, 0xcf, 0xaf, 0x8c, 0x63, 0x9f, 0x1d, 0x35, 0x00, 0x8e, 0xf6, 0x4d, 0x25, 0x04, 0xe0, 0x60, 0x45, 0xbc, 0xd6, 0xb1, 0x72, 0xeb, 0xfd, 0xdc, 0x28, 0xec, 0xd9, 0xbc, 0x6d, 0x12, 0x48, 0xfd, 0x6f, 0x9e, 0xf3, 0xeb, 0x18, 0xc6, 0x39, 0xce, 0x79, 0x2e, 0xf9, 0xec, 0x44, 0x1c, 0xc6, 0xe0, 0xf8, 0xa8, 0xca, 0xf3, 0x0c, 0xa5, 0xae, 0x8e, 0xf7, 0xb1, 0x66, 0xf0, 0x4a, 0x35, 0xdb, 0xfd, 0x5b, 0xef, 0x42, 0xe2, 0x57, 0x57, 0x07, 0xd5, 0xae, 0x3c, 0x22, 0x2a, 0x30, 0xd8, 0x67, 0x2a, 0xbc, 0x6b, 0xbe, 0x42, 0xdb, 0x21, 0x52, 0xe9, 0x4f, 0xfb, 0x93, 0xc8, 0x46, 0x6e, 0x49, 0x16, 0x06, 0x83, 0x85, 0x61, 0xc0, 0xc2, 0x30, 0x61, 0x61, 0x38, 0xf8, 0x7e, 0xfe, 0x15, 0x73, 0xc9, 0x4f, 0xca, 0x4f, 0x92, 0xbd, 0x29, 0xac, 0x04, 0xbf, 0xbc, 0x93, 0xec, 0x80, 0x20, 0xd9, 0x01, 0xbb, 0xc9, 0x1a, 0x15, 0xab, 0xfb, 0x48, 0xc5, 0x3b, 0xb2, 0x5f, 0x58, 0x8a, 0xb9, 0xd8, 0x7c, 0x99, 0x35, 0xa4, 0x12, 0xa3, 0x09, 0xeb, 0xe6, 0xb4, 0x5d, 0x23, 0xc1, 0xae, 0x91, 0x61, 0xd7, 0x28, 0xb0, 0x6b, 0x64, 0xd8, 0x35, 0x0a, 0xae, 0x6a, 0xc3, 0x55, 0x6d, 0xb8, 0xaa, 0x1d, 0x6f, 0x8e, 0x55, 0xbc, 0x39, 0xd6, 0xf0, 0xe6, 0x58, 0x87, 0x07, 0xb4, 0x81, 0x7a, 0x98, 0xa8, 0x87, 0x03, 0xf5, 0x70, 0xa2, 0x1e, 0x4e, 0xd4, 0xc3, 0x85, 0x7a, 0xd8, 0xf0, 0xae, 0x4e, 0xc5, 0xbb, 0x3a, 0x0d, 0xef, 0xea, 0x74, 0x78, 0x86, 0x1a, 0xa8, 0x99, 0x49, 0xb5, 0xe8, 0x82, 0xf7, 0xf6, 0x3a, 0x7c, 0xd7, 0x5d, 0x78, 0x6f, 0x5f, 0x1b, 0x6f, 0xec, 0x6b, 0xe3, 0x5d, 0x7d, 0x18, 0x6f, 0xe9, 0x6b, 0xe3, 0xfd, 0x7c, 0x6d, 0xbc, 0x93, 0x6f, 0x88, 0x77, 0xf2, 0x79, 0x78, 0x27, 0x7f, 0x3e, 0xde, 0xc9, 0xa7, 0xe3, 0x9d, 0x7c, 0x4b, 0xbc, 0x93, 0xaf, 0x87, 0x77, 0xf2, 0x2d, 0xf0, 0x4e, 0xde, 0x8b, 0x77, 0xf2, 0x06, 0xde, 0xc9, 0x3b, 0xf0, 0x4e, 0xde, 0xc0, 0x3b, 0xf9, 0x0e, 0x78, 0x27, 0x2f, 0xe1, 0x9d, 0xbc, 0x02, 0xbf, 0xf4, 0x4e, 0xf0, 0x5a, 0xe9, 0x84, 0xf7, 0xf3, 0x06, 0x3c, 0xd2, 0x3b, 0xe1, 0x2d, 0xbd, 0x03, 0xfe, 0x2a, 0x9d, 0xf0, 0xae, 0x5e, 0xc3, 0xbb, 0xfa, 0x0e, 0x78, 0x57, 0xdf, 0x1e, 0x1e, 0x29, 0xed, 0xf0, 0xc6, 0xbe, 0x0e, 0xde, 0xd8, 0xd7, 0xc7, 0x1b, 0xfb, 0xd6, 0x78, 0x63, 0xdf, 0x04, 0x6f, 0xec, 0x0b, 0xd9, 0xdb, 0x44, 0x39, 0xb0, 0x96, 0x6d, 0x40, 0x62, 0xec, 0x40, 0x62, 0x2e, 0x01, 0x12, 0xd3, 0x15, 0x48, 0xcc, 0xc5, 0x6c, 0x2f, 0x11, 0x87, 0x2d, 0x1d, 0x82, 0x2d, 0xdd, 0x05, 0xb6, 0x74, 0x08, 0xb6, 0x74, 0x10, 0xb6, 0x74, 0x73, 0x58, 0xd1, 0x32, 0xac, 0xe8, 0x7c, 0x58, 0xd1, 0x51, 0xd8, 0xcf, 0xd9, 0xb0, 0x9c, 0xf3, 0x61, 0x39, 0x67, 0xc0, 0x72, 0x2e, 0x82, 0xe5, 0x7c, 0x01, 0x2c, 0xe7, 0x54, 0x58, 0xce, 0x45, 0xb0, 0x9c, 0x23, 0xb0, 0x9c, 0x8b, 0x60, 0x39, 0xfb, 0x60, 0x39, 0x17, 0xc1, 0x72, 0xce, 0x84, 0xcd, 0xec, 0x83, 0xcd, 0x5c, 0x04, 0x9b, 0xb9, 0x29, 0x6c, 0xe6, 0x22, 0xd8, 0xcc, 0x45, 0xb0, 0x99, 0xd3, 0x60, 0x33, 0xfb, 0x60, 0x33, 0x17, 0xc1, 0x66, 0xae, 0x05, 0x9b, 0x39, 0x00, 0x9b, 0xf9, 0x22, 0x68, 0x55, 0x2e, 0x6c, 0xe6, 0x8b, 0xa0, 0x5b, 0xb9, 0xd0, 0xad, 0xf3, 0xa0, 0x5b, 0x35, 0xa1, 0x5b, 0x35, 0xa0, 0x5b, 0x35, 0xa1, 0x5b, 0x35, 0xa0, 0x5b, 0x29, 0xd0, 0xad, 0x14, 0xe8, 0x56, 0x03, 0xe8, 0x56, 0x6d, 0xe8, 0x56, 0x1e, 0x74, 0xcb, 0x80, 0x6e, 0xb5, 0x83, 0x77, 0x7d, 0x63, 0x78, 0x28, 0xd4, 0x86, 0x77, 0x7d, 0x63, 0x78, 0x28, 0xd4, 0x86, 0x57, 0x42, 0xed, 0x8a, 0x98, 0xa1, 0x5e, 0xac, 0x1e, 0x7c, 0x13, 0x5a, 0xc0, 0x37, 0xa1, 0x36, 0xbc, 0x12, 0x5a, 0xc0, 0x2b, 0x21, 0x0f, 0xfe, 0x08, 0xf5, 0xe0, 0x8f, 0x90, 0x07, 0x4f, 0x84, 0x16, 0xf0, 0x44, 0x30, 0xe0, 0x89, 0xe0, 0x80, 0x5f, 0x7d, 0x27, 0x78, 0x07, 0x75, 0x82, 0x57, 0x42, 0x7b, 0x78, 0x25, 0x14, 0x26, 0x79, 0x07, 0x49, 0xf0, 0x4a, 0x70, 0xc0, 0x2b, 0xa1, 0x10, 0x5e, 0x09, 0x1a, 0xbc, 0x12, 0x1c, 0xf0, 0x4a, 0x68, 0x02, 0xaf, 0x84, 0x42, 0x78, 0x25, 0x74, 0x80, 0x57, 0x42, 0x7b, 0xcb, 0x3b, 0x08, 0xa3, 0xa2, 0xad, 0xfc, 0x8c, 0xfc, 0x0c, 0xcb, 0x01, 0x22, 0x10, 0x92, 0x17, 0xca, 0xcf, 0x33, 0x0e, 0x5c, 0x20, 0x04, 0x5c, 0x20, 0x04, 0x5c, 0x20, 0x04, 0x5c, 0xa0, 0x39, 0x70, 0x81, 0x0c, 0xe0, 0x02, 0xf9, 0xc0, 0x05, 0x32, 0x80, 0x0b, 0xb8, 0x81, 0x0b, 0xb4, 0x02, 0x2e, 0x90, 0x01, 0x5c, 0x20, 0x1b, 0x23, 0xad, 0x00, 0xb8, 0x80, 0x0c, 0x5c, 0x20, 0x1b, 0x88, 0x40, 0x3e, 0x10, 0x81, 0x56, 0x40, 0x04, 0x9c, 0x40, 0x04, 0x64, 0x20, 0x02, 0x45, 0x40, 0x04, 0x2e, 0x00, 0x22, 0x50, 0x04, 0x44, 0x20, 0x13, 0x88, 0x40, 0x11, 0x10, 0x01, 0x1f, 0x10, 0x81, 0x4c, 0x8c, 0xd5, 0x2c, 0x20, 0x02, 0xcd, 0x80, 0x08, 0x64, 0x02, 0x11, 0x68, 0x0a, 0x44, 0xa0, 0x08, 0x88, 0x80, 0x1f, 0x88, 0x40, 0x11, 0x10, 0x81, 0x08, 0x10, 0x81, 0x22, 0x20, 0x02, 0x4d, 0x81, 0x08, 0x14, 0x01, 0x11, 0x88, 0x01, 0x11, 0xf0, 0x01, 0x11, 0x28, 0x02, 0x22, 0xd0, 0x0c, 0x88, 0x80, 0x0f, 0x88, 0x40, 0x04, 0x88, 0x40, 0x11, 0xb0, 0x80, 0x7c, 0xa0, 0x00, 0xf9, 0xb0, 0xff, 0x8b, 0x60, 0xff, 0x17, 0xc1, 0xfe, 0x2f, 0x82, 0xfd, 0xef, 0x83, 0xfd, 0xef, 0x83, 0x67, 0x87, 0x02, 0x14, 0xa0, 0x33, 0x50, 0x80, 0xce, 0x40, 0x01, 0x2e, 0x04, 0x0a, 0xd0, 0x11, 0x28, 0xc0, 0x85, 0x40, 0x01, 0x3a, 0xc2, 0xef, 0xa3, 0x03, 0x3c, 0x3e, 0x3a, 0xc0, 0xe3, 0xa3, 0x36, 0xf0, 0xb3, 0xae, 0xf0, 0xf8, 0x30, 0xe0, 0xeb, 0xe1, 0x80, 0x17, 0x56, 0x3b, 0xe0, 0x05, 0xa9, 0xc0, 0x0b, 0x7c, 0x40, 0x0a, 0xf2, 0x81, 0x14, 0x84, 0x80, 0x14, 0xb4, 0xb1, 0xfd, 0x64, 0xfb, 0x89, 0xe5, 0xc0, 0x53, 0xab, 0x13, 0xfc, 0x41, 0xda, 0xc3, 0x1f, 0xc4, 0x80, 0x3f, 0x88, 0x85, 0x23, 0x78, 0xe0, 0x15, 0xa2, 0x03, 0x4d, 0xf0, 0xc0, 0x37, 0x44, 0x47, 0xb4, 0x82, 0x0b, 0xbe, 0x21, 0xb5, 0x81, 0x2f, 0xe4, 0xab, 0xb7, 0xaa, 0xb7, 0xb2, 0x1c, 0xf8, 0x89, 0xd4, 0x86, 0x27, 0x48, 0x6d, 0xf8, 0x7a, 0x84, 0xe1, 0xeb, 0x11, 0x86, 0x4f, 0x47, 0x6d, 0x2d, 0xa8, 0x85, 0x58, 0x0e, 0x90, 0x88, 0x7c, 0xf8, 0x77, 0xd4, 0xd6, 0xea, 0x6b, 0xf5, 0x49, 0x92, 0xab, 0xe5, 0x12, 0x6f, 0xa4, 0xe5, 0x13, 0x17, 0x08, 0x45, 0x3e, 0xfc, 0x3e, 0x6a, 0xc3, 0xbf, 0xa3, 0x21, 0xd0, 0x8a, 0x0c, 0x78, 0x79, 0x34, 0x84, 0x97, 0x47, 0x43, 0x78, 0x79, 0xe4, 0xc1, 0xcb, 0xe3, 0x7c, 0x78, 0x79, 0xa4, 0x03, 0xcb, 0x68, 0x05, 0x5f, 0x8f, 0x96, 0xf0, 0xf5, 0xa8, 0xa7, 0x8d, 0xd1, 0xc6, 0xb0, 0xba, 0xda, 0x58, 0x6d, 0x2c, 0x71, 0xe1, 0xf7, 0x51, 0x4f, 0x9b, 0xac, 0x3d, 0x42, 0x69, 0x81, 0x77, 0xb8, 0xe1, 0x03, 0xd2, 0x02, 0x3e, 0x20, 0x2d, 0xe0, 0x03, 0xe2, 0x05, 0x02, 0xa2, 0xc2, 0x13, 0xc4, 0x0b, 0x1c, 0x44, 0x85, 0x3f, 0x88, 0x17, 0x68, 0x88, 0x0a, 0xaf, 0x10, 0x03, 0x98, 0x88, 0x0f, 0x98, 0x48, 0x08, 0x1e, 0x22, 0x06, 0x3c, 0x44, 0x1c, 0xc0, 0x47, 0x7c, 0xf0, 0x13, 0x71, 0xc0, 0x4f, 0xc4, 0x00, 0x56, 0xe2, 0x03, 0x56, 0xe2, 0x83, 0xcf, 0x88, 0x01, 0x9f, 0x11, 0x03, 0xb8, 0x89, 0x0f, 0x9e, 0x23, 0x06, 0x3c, 0x47, 0x3a, 0xc0, 0x73, 0xa4, 0x03, 0x3c, 0x47, 0x24, 0x78, 0x8e, 0x28, 0x40, 0x55, 0x7c, 0xf0, 0x1f, 0x51, 0x80, 0xad, 0xd4, 0x02, 0xaa, 0x52, 0xa4, 0x5f, 0xa0, 0x5f, 0xc0, 0x2e, 0x06, 0x9e, 0x52, 0x04, 0x3f, 0xb4, 0x4e, 0xf0, 0x43, 0xeb, 0x04, 0x3f, 0xb4, 0x4e, 0xf0, 0x34, 0xb1, 0x70, 0x96, 0x10, 0x70, 0x16, 0x1f, 0xbc, 0x4e, 0x0c, 0xa0, 0x2d, 0x21, 0xa0, 0x2d, 0x3e, 0x78, 0xa0, 0x18, 0xc0, 0x5c, 0x8a, 0xe0, 0xa5, 0xd6, 0x09, 0xc8, 0x4b, 0x11, 0x7c, 0xd5, 0x3a, 0xc1, 0x57, 0xad, 0x13, 0xfc, 0x53, 0x34, 0xf8, 0xa7, 0x68, 0xf0, 0x4f, 0x69, 0x0f, 0x2f, 0xb5, 0x76, 0xf0, 0x52, 0x6b, 0x07, 0x2f, 0xb5, 0x76, 0xf0, 0x58, 0xa9, 0x03, 0x8f, 0x95, 0xfa, 0x40, 0x6d, 0x9a, 0xc1, 0x6f, 0xa5, 0x35, 0xfc, 0x56, 0x9a, 0x00, 0xc1, 0x69, 0x04, 0xef, 0x95, 0x26, 0xc0, 0x71, 0x1a, 0xc1, 0x87, 0xa5, 0x10, 0x68, 0x8e, 0x0d, 0xd8, 0xad, 0x1d, 0xd8, 0xed, 0x25, 0xc0, 0x6e, 0x2f, 0x01, 0x76, 0xdb, 0x15, 0xd8, 0x6d, 0x57, 0x63, 0x8c, 0x71, 0x2f, 0xe3, 0x40, 0x7c, 0xba, 0x00, 0xf1, 0x09, 0x02, 0xf1, 0x69, 0x6e, 0xac, 0x34, 0x56, 0xb2, 0x1c, 0x20, 0x3e, 0x51, 0x20, 0x3e, 0xd9, 0x40, 0x79, 0x8a, 0x80, 0xf2, 0x5c, 0x00, 0x94, 0x27, 0x15, 0x28, 0x4f, 0x11, 0x50, 0x9e, 0x22, 0xa0, 0x3c, 0x4d, 0x81, 0xf2, 0x14, 0x01, 0xe5, 0x49, 0x83, 0xd7, 0x5c, 0x27, 0x3c, 0x9b, 0x53, 0xf0, 0x6c, 0xae, 0x8d, 0x67, 0x73, 0x1e, 0x9e, 0xcd, 0x06, 0x9e, 0xcd, 0xed, 0x80, 0xfe, 0xb4, 0x31, 0x5f, 0x34, 0x5f, 0x64, 0x6d, 0xcd, 0x7f, 0x9a, 0xcb, 0x89, 0x0b, 0x0c, 0x28, 0x1f, 0x18, 0x50, 0x0c, 0x18, 0x90, 0x0f, 0x5e, 0x36, 0x0e, 0xea, 0xab, 0x03, 0x62, 0xf8, 0x1b, 0x5b, 0x4c, 0xf1, 0x3f, 0x3e, 0x3a, 0xe2, 0x6d, 0xf0, 0x7d, 0xf1, 0x71, 0x64, 0x2f, 0x0a, 0xfe, 0x1a, 0xfb, 0x9f, 0xfe, 0x25, 0xbe, 0x08, 0xf0, 0x55, 0x85, 0xe7, 0xc0, 0x91, 0xf8, 0xe7, 0xf1, 0xaf, 0x11, 0x25, 0xbe, 0x82, 0x52, 0xa5, 0xb4, 0x22, 0x16, 0x5e, 0xd4, 0xfb, 0x44, 0x4c, 0xeb, 0x7f, 0xb1, 0x56, 0x93, 0xe2, 0xcf, 0xc5, 0x4f, 0xd2, 0x55, 0xcb, 0x5b, 0x27, 0x06, 0x6e, 0xd9, 0x80, 0xb7, 0xc2, 0x83, 0x7d, 0xc7, 0x69, 0x6c, 0xe2, 0x4f, 0xae, 0xcb, 0xa8, 0xf8, 0xfd, 0xf1, 0x6f, 0x4f, 0x7b, 0xa4, 0x27, 0xfe, 0x1c, 0xe5, 0x36, 0x58, 0xfc, 0x6f, 0xbf, 0xf6, 0x0d, 0x8b, 0xff, 0x48, 0x0d, 0x92, 0xbe, 0x11, 0x51, 0x39, 0x96, 0xa3, 0xdc, 0x8b, 0x3e, 0x11, 0x4b, 0xf3, 0xfd, 0x7f, 0xb5, 0x8f, 0x1e, 0x16, 0x76, 0x74, 0xf9, 0xd7, 0x1c, 0xe8, 0xcf, 0x5d, 0xe5, 0xf8, 0xac, 0xff, 0xba, 0x2e, 0x7f, 0xce, 0xfe, 0xcf, 0xfc, 0x91, 0xad, 0xf9, 0x0d, 0x6d, 0x3f, 0x90, 0x9d, 0xf4, 0x6d, 0xfc, 0xa7, 0xf8, 0x77, 0x88, 0x1d, 0xfa, 0xb6, 0x5a, 0x91, 0x4b, 0xff, 0xce, 0xd5, 0x2c, 0x84, 0xe6, 0x7d, 0x78, 0xf8, 0x6d, 0x89, 0xbf, 0x02, 0x5f, 0x8d, 0x87, 0x2a, 0x50, 0xa0, 0x8f, 0xf1, 0xae, 0xfb, 0x3b, 0x78, 0xbe, 0x3c, 0x0a, 0xff, 0x23, 0x2b, 0x66, 0xfe, 0x93, 0xf8, 0xb4, 0xf8, 0xc4, 0xf8, 0xf4, 0xea, 0x7d, 0x0d, 0xa6, 0xda, 0x75, 0xf9, 0xf8, 0x2c, 0x5e, 0x7a, 0x0b, 0xcf, 0xf4, 0x52, 0x88, 0x2f, 0xb4, 0xf0, 0x3d, 0x0b, 0xbf, 0x88, 0x1f, 0x40, 0x44, 0xfe, 0x9b, 0xff, 0xe1, 0x76, 0x59, 0x8c, 0x58, 0xfe, 0xd5, 0xf0, 0xe3, 0x5b, 0x19, 0x7f, 0x1e, 0x5f, 0x22, 0x18, 0x97, 0x8c, 0x94, 0xc6, 0x57, 0xe3, 0x2b, 0x23, 0xe3, 0xe2, 0x0f, 0xc0, 0x7b, 0x6b, 0x3a, 0x5a, 0x67, 0x15, 0xf6, 0x33, 0xfe, 0xd8, 0x78, 0x8b, 0x3f, 0x02, 0x2f, 0xb6, 0x49, 0xf1, 0xc9, 0xf1, 0x45, 0xd4, 0xfb, 0xfb, 0x2b, 0x7c, 0xa5, 0x02, 0xe0, 0x35, 0x2c, 0x74, 0x5d, 0x60, 0xd9, 0xf1, 0x5d, 0x56, 0x7f, 0xfc, 0x4a, 0x59, 0x77, 0x60, 0xee, 0xfe, 0x57, 0xfc, 0x1d, 0xea, 0xdf, 0x1f, 0x68, 0xde, 0xde, 0x41, 0x77, 0x76, 0x92, 0x68, 0x21, 0x7c, 0xe4, 0xfe, 0x51, 0xed, 0x3a, 0x7d, 0x18, 0xdf, 0x1d, 0xff, 0xf9, 0x0c, 0x4f, 0x11, 0x47, 0x02, 0x41, 0x38, 0x19, 0x7f, 0xeb, 0xb7, 0x3c, 0x84, 0x92, 0xca, 0xfa, 0xa4, 0xdc, 0x53, 0x0d, 0xef, 0x2e, 0xa2, 0x02, 0xbd, 0x10, 0xdf, 0xfe, 0xf9, 0x7f, 0xd5, 0x28, 0xfc, 0x81, 0xb4, 0x7f, 0x0f, 0x6d, 0x3f, 0xd2, 0xbd, 0xfd, 0x48, 0x3a, 0xf6, 0xd3, 0xaf, 0xf9, 0xc7, 0xfd, 0xc9, 0x75, 0x39, 0x48, 0xad, 0xff, 0x39, 0xe9, 0xd8, 0xfe, 0xf8, 0x17, 0xe2, 0x0b, 0x41, 0x54, 0x9b, 0xed, 0xf4, 0x34, 0x3e, 0x7a, 0x46, 0xbe, 0xf7, 0x91, 0xe7, 0xfd, 0x33, 0xfd, 0x4b, 0xf0, 0x4d, 0x95, 0xef, 0x49, 0x07, 0xaa, 0x35, 0x0f, 0xd2, 0x35, 0x7e, 0x12, 0xcf, 0x0f, 0xd2, 0x98, 0xaf, 0x80, 0x6e, 0x9f, 0x4c, 0x60, 0xc8, 0x07, 0xce, 0x78, 0xd6, 0xfd, 0xea, 0x5b, 0x8d, 0x3f, 0xbd, 0x5d, 0x8e, 0x03, 0xa7, 0xfb, 0x1c, 0x5f, 0x99, 0xfa, 0x96, 0xf6, 0x5f, 0xe1, 0xfb, 0x1a, 0xc7, 0xfe, 0xe3, 0xd7, 0xa9, 0x16, 0x5e, 0x05, 0x7d, 0x16, 0x5f, 0xb5, 0xfa, 0x96, 0xe8, 0x7b, 0xa1, 0x31, 0x68, 0xc1, 0x0f, 0xe2, 0x3b, 0x19, 0x67, 0xb7, 0xb2, 0xab, 0x58, 0x2f, 0xc4, 0xef, 0xbf, 0xcd, 0xde, 0x61, 0x9b, 0xc9, 0xba, 0x33, 0xc8, 0xaa, 0x6b, 0xa3, 0xb4, 0x53, 0xda, 0x2b, 0x1d, 0x94, 0x0b, 0x95, 0x8b, 0x94, 0x8b, 0x95, 0x4b, 0xc8, 0x9e, 0xbb, 0x94, 0xac, 0xb9, 0x61, 0x64, 0xb9, 0xdd, 0x4f, 0x16, 0xdb, 0x03, 0x64, 0xa9, 0x3d, 0x68, 0x5b, 0x63, 0xdb, 0x62, 0x7b, 0x8f, 0x2c, 0xae, 0x9f, 0x6c, 0x27, 0xc8, 0x8e, 0x22, 0xeb, 0x49, 0xed, 0x4f, 0xb6, 0xd2, 0xad, 0xea, 0x38, 0xf5, 0x7e, 0x75, 0xbc, 0x3a, 0x41, 0x9d, 0xa9, 0x3e, 0xa1, 0xce, 0x52, 0x9f, 0x54, 0x67, 0xab, 0x4f, 0xa9, 0x73, 0xd4, 0xa7, 0xd5, 0xb9, 0xea, 0x22, 0xf5, 0x05, 0x75, 0x99, 0xfa, 0x4f, 0x75, 0xb9, 0xfa, 0xb2, 0xba, 0x42, 0x5d, 0xa9, 0xae, 0x52, 0x5f, 0x51, 0x57, 0xab, 0xaf, 0xaa, 0x6b, 0xd4, 0xd7, 0xd4, 0xb5, 0xea, 0xeb, 0xea, 0x3a, 0xf5, 0x0d, 0x75, 0xbd, 0xfa, 0xa6, 0xba, 0x41, 0x7d, 0x4f, 0xdd, 0xaa, 0xbe, 0xaf, 0x7e, 0xa2, 0xee, 0x52, 0x3f, 0x55, 0x77, 0xab, 0x9f, 0xa9, 0x7b, 0xd4, 0xcf, 0xd5, 0xbd, 0xea, 0xbf, 0xd4, 0x7d, 0xea, 0x17, 0xea, 0x7e, 0xf5, 0xa4, 0x66, 0x13, 0xb6, 0x16, 0x59, 0x57, 0x39, 0x5a, 0x03, 0xb2, 0xae, 0x1a, 0x6a, 0x79, 0xc2, 0xba, 0xd2, 0x4e, 0xe9, 0xcd, 0xf5, 0x16, 0x7a, 0x4b, 0xbd, 0x95, 0x5e, 0xa8, 0xb7, 0xd6, 0xdb, 0xe8, 0x6d, 0x8d, 0x95, 0xe6, 0x46, 0xf3, 0x6d, 0x26, 0xb1, 0x86, 0xb8, 0x4b, 0x15, 0xf7, 0xa9, 0x02, 0xc1, 0xf1, 0xd2, 0xdd, 0xbe, 0xc3, 0x5c, 0x6c, 0x33, 0x91, 0x07, 0xef, 0xb4, 0xb9, 0xb8, 0x67, 0x26, 0x89, 0xbb, 0x66, 0x32, 0xde, 0x6c, 0x73, 0xbc, 0xd3, 0x56, 0xc4, 0xfd, 0x33, 0x9b, 0x68, 0x01, 0x66, 0x47, 0x2c, 0xbd, 0x0a, 0x0b, 0x56, 0x83, 0x05, 0xab, 0xc1, 0x82, 0xd5, 0x61, 0xc1, 0x1a, 0xb0, 0x60, 0x75, 0x58, 0xb0, 0x06, 0xb5, 0xcf, 0x1a, 0xe6, 0x15, 0x6d, 0xc4, 0xd4, 0xc4, 0x1b, 0x6c, 0x61, 0x8b, 0x3a, 0x6d, 0x27, 0x6c, 0x27, 0x98, 0x17, 0x96, 0xa7, 0x09, 0x9b, 0xd3, 0xa4, 0x76, 0xeb, 0xcf, 0xdc, 0xd6, 0x7b, 0x6c, 0xd8, 0x99, 0x4e, 0xd1, 0x86, 0x64, 0x7f, 0x8e, 0x57, 0xc7, 0x93, 0x7c, 0x82, 0x3a, 0x81, 0xf8, 0x4c, 0xf5, 0x69, 0x92, 0xcc, 0x55, 0xe7, 0x52, 0x9a, 0xda, 0x91, 0x2c, 0xd5, 0x65, 0xea, 0x06, 0xe2, 0xef, 0xa9, 0xef, 0x13, 0xff, 0x44, 0xdd, 0x4f, 0xfc, 0xa4, 0x7a, 0x92, 0x79, 0xa8, 0x6d, 0x6c, 0xc4, 0x85, 0x2d, 0xea, 0xa5, 0x16, 0xca, 0x27, 0x7e, 0x8a, 0x6c, 0x30, 0x95, 0xda, 0xa6, 0x2d, 0x53, 0x61, 0x69, 0x78, 0xf1, 0xa6, 0x97, 0xe3, 0x4d, 0xaf, 0xc2, 0x24, 0x9e, 0x01, 0xac, 0xee, 0x3c, 0x60, 0x75, 0xb5, 0x11, 0x69, 0x63, 0x7d, 0x6d, 0x22, 0x03, 0x91, 0x36, 0x69, 0xc0, 0xed, 0x32, 0x10, 0x69, 0x93, 0x06, 0xf4, 0xae, 0x16, 0x22, 0x6d, 0xd2, 0x80, 0xe1, 0x65, 0x20, 0xd2, 0x26, 0x0d, 0x94, 0x81, 0x48, 0x9b, 0x34, 0x44, 0xda, 0xd4, 0x04, 0xaa, 0x97, 0x0a, 0x54, 0x2f, 0x06, 0x54, 0x2f, 0x0a, 0x54, 0xaf, 0x06, 0x50, 0xbd, 0x14, 0xa0, 0x7a, 0xe9, 0x40, 0xf5, 0x32, 0xd1, 0x1f, 0x96, 0xd7, 0x58, 0x0b, 0x20, 0x6a, 0xad, 0x80, 0xa8, 0x35, 0x03, 0xa2, 0x56, 0x08, 0x44, 0xad, 0x25, 0x10, 0xb5, 0x36, 0x40, 0xd4, 0x5a, 0x03, 0x51, 0xbb, 0x00, 0x88, 0x5a, 0x6b, 0x20, 0x6a, 0x6d, 0x81, 0xa8, 0x79, 0xe0, 0x9d, 0xe2, 0x03, 0xae, 0xe6, 0x05, 0xae, 0x16, 0x06, 0xae, 0x66, 0xc0, 0x3b, 0xa5, 0x09, 0xd0, 0xb5, 0x08, 0xbc, 0x53, 0x4c, 0x60, 0x6c, 0x61, 0x60, 0x6c, 0x01, 0x60, 0x6c, 0x1a, 0x30, 0x36, 0x0e, 0x8c, 0x2d, 0x08, 0x8c, 0x4d, 0x03, 0xc6, 0xe6, 0x02, 0xc6, 0xa6, 0x01, 0x63, 0xd3, 0x81, 0xb1, 0x69, 0xc0, 0xd8, 0xec, 0xf0, 0x4e, 0x39, 0x1f, 0x48, 0x9b, 0x0e, 0xa4, 0x4d, 0x03, 0xd2, 0x16, 0x02, 0xd2, 0xa6, 0x01, 0x69, 0xd3, 0x80, 0xb4, 0xf9, 0x81, 0xb4, 0xe9, 0x40, 0xda, 0x34, 0x20, 0x6d, 0x8d, 0x81, 0xb4, 0x75, 0x00, 0xd2, 0x66, 0x03, 0xd2, 0x26, 0x03, 0x69, 0xb3, 0x01, 0x69, 0x93, 0x81, 0xb4, 0x5d, 0x08, 0xa4, 0xad, 0x1d, 0x30, 0xb6, 0x76, 0x40, 0xd4, 0x24, 0x20, 0x6a, 0x19, 0x40, 0xd4, 0x62, 0x89, 0xef, 0xef, 0x08, 0x14, 0x2d, 0x03, 0x28, 0x5a, 0x5d, 0x44, 0xfb, 0xa4, 0x01, 0x4b, 0xcb, 0x40, 0xb4, 0x4f, 0x1a, 0xa2, 0x7d, 0xd2, 0x80, 0xab, 0x65, 0x00, 0x57, 0xab, 0x0b, 0x5c, 0x2d, 0x1d, 0xb8, 0x5a, 0x26, 0x70, 0xb5, 0x0c, 0xc4, 0xfc, 0xa4, 0x01, 0x5d, 0xcb, 0x04, 0xba, 0x16, 0x43, 0xcc, 0x4f, 0x1a, 0x30, 0xb6, 0x74, 0x60, 0x6c, 0x31, 0xc4, 0xfc, 0xa4, 0x01, 0x69, 0xcb, 0x04, 0x06, 0x56, 0x07, 0x18, 0x58, 0x73, 0x60, 0x60, 0xad, 0x81, 0x81, 0xb5, 0x01, 0x06, 0xd6, 0x1a, 0x18, 0x58, 0x6b, 0x60, 0x60, 0xad, 0x81, 0x81, 0x79, 0x80, 0x81, 0x05, 0x80, 0x81, 0x85, 0x81, 0x81, 0x05, 0x92, 0x7c, 0x63, 0x9c, 0xc0, 0xc0, 0x02, 0xc0, 0xc0, 0x22, 0xc0, 0xc0, 0x9a, 0x02, 0x03, 0xf3, 0x02, 0x03, 0x8b, 0xc0, 0x37, 0xa6, 0x3e, 0x90, 0xb0, 0x30, 0x90, 0x30, 0x27, 0x90, 0xb0, 0x5c, 0x20, 0x61, 0x5e, 0x20, 0x61, 0x1a, 0x90, 0x30, 0x0e, 0x24, 0x4c, 0x03, 0x12, 0x66, 0x07, 0x12, 0xa6, 0x01, 0x09, 0xd3, 0x81, 0x84, 0xd9, 0x81, 0x84, 0x35, 0x02, 0x12, 0xa6, 0x00, 0x09, 0xb3, 0x03, 0x09, 0x0b, 0x01, 0x09, 0xd3, 0x80, 0x84, 0x15, 0x00, 0x09, 0xd3, 0x80, 0x84, 0xb9, 0x80, 0x84, 0x69, 0x40, 0xc2, 0x42, 0x40, 0xc2, 0x34, 0x20, 0x61, 0xf5, 0x80, 0x84, 0xe9, 0x40, 0xc2, 0x34, 0x20, 0x61, 0x0a, 0x90, 0x30, 0x1d, 0x48, 0x98, 0x0b, 0x48, 0x98, 0x86, 0x79, 0xa4, 0x21, 0xf0, 0xb0, 0x30, 0x66, 0x90, 0x86, 0x40, 0xc5, 0xc2, 0x98, 0x47, 0x72, 0x80, 0x8d, 0x69, 0xc0, 0xc6, 0x34, 0x60, 0x63, 0x1a, 0xb0, 0x31, 0x1d, 0xd8, 0x98, 0x8e, 0x39, 0xa5, 0x01, 0xe6, 0x94, 0x06, 0x98, 0x53, 0xf2, 0x30, 0x9b, 0xe4, 0x21, 0x0e, 0x2a, 0x0d, 0x11, 0x50, 0x69, 0x40, 0xc2, 0x32, 0x80, 0x84, 0x15, 0x22, 0x02, 0x2a, 0x0d, 0xb1, 0x4f, 0x69, 0x88, 0x7a, 0x4a, 0x03, 0x12, 0x16, 0x04, 0x12, 0xa6, 0xc3, 0x73, 0xc6, 0xc2, 0xc3, 0xc2, 0xc0, 0xc3, 0x5a, 0x03, 0x0f, 0x6b, 0x8f, 0x39, 0xa8, 0x39, 0xe2, 0xa3, 0xd2, 0x80, 0x81, 0x9d, 0x87, 0x99, 0xc8, 0x0d, 0x0c, 0xec, 0x3c, 0xcc, 0x47, 0x6e, 0x60, 0x60, 0xe7, 0x01, 0x03, 0xab, 0x8d, 0x28, 0x29, 0xeb, 0xbb, 0x1d, 0x19, 0x98, 0xa1, 0xc2, 0x98, 0xa1, 0x9a, 0x03, 0x09, 0xcb, 0x40, 0xc4, 0x54, 0x1a, 0x22, 0xa6, 0xd2, 0x10, 0x31, 0x95, 0x86, 0x88, 0xa9, 0x34, 0x44, 0x4c, 0xa5, 0x01, 0x27, 0xcb, 0x40, 0xc4, 0x54, 0x1a, 0x22, 0xa6, 0xd2, 0x10, 0x31, 0x95, 0x86, 0x88, 0xa9, 0x34, 0x44, 0x4c, 0xa5, 0x01, 0x45, 0xab, 0x05, 0x14, 0xad, 0x16, 0x22, 0xa6, 0xd2, 0x10, 0x31, 0x95, 0x86, 0x88, 0xa9, 0x34, 0x44, 0x4c, 0xa5, 0x01, 0x5d, 0xcb, 0xc0, 0x8c, 0xd6, 0x1c, 0xe8, 0x5a, 0x18, 0xe8, 0x5a, 0x06, 0xd0, 0xb5, 0xe6, 0x40, 0xd7, 0x9a, 0x03, 0x5d, 0x6b, 0x0e, 0x74, 0x2d, 0x0c, 0x74, 0x2d, 0x03, 0x51, 0x55, 0x69, 0x88, 0xaa, 0x4a, 0x43, 0x54, 0x55, 0x4d, 0x44, 0x55, 0xd5, 0x04, 0xea, 0x96, 0x0a, 0xd4, 0x2d, 0x00, 0xd4, 0x2d, 0x15, 0xa8, 0x5b, 0x2a, 0x50, 0xb7, 0x18, 0x50, 0xb7, 0x28, 0x50, 0xb7, 0x1a, 0x40, 0xdd, 0x9c, 0x40, 0xdd, 0x52, 0x80, 0xba, 0xa5, 0x03, 0x75, 0xcb, 0x02, 0xea, 0x96, 0x05, 0xd4, 0x2d, 0x1d, 0xa8, 0x5b, 0x56, 0xc2, 0xcb, 0x48, 0xa0, 0x6e, 0x99, 0x40, 0xdd, 0x32, 0x81, 0xb7, 0x39, 0x80, 0xb4, 0x39, 0x80, 0xb1, 0x39, 0x80, 0xae, 0xe9, 0x40, 0xd7, 0x5a, 0x03, 0x51, 0xd3, 0x81, 0xa2, 0xe9, 0x40, 0xd1, 0x74, 0x20, 0x67, 0x3a, 0xb0, 0x31, 0x1d, 0xa8, 0x58, 0x63, 0x78, 0x1c, 0xe5, 0x03, 0x1b, 0xd3, 0xe0, 0x71, 0x94, 0x0f, 0x8f, 0xa3, 0x7c, 0xe0, 0x64, 0x2d, 0x81, 0x93, 0x69, 0xc0, 0xc3, 0x5a, 0x03, 0x0f, 0xd3, 0x81, 0x84, 0xb5, 0x06, 0x12, 0xa6, 0x03, 0x03, 0xd3, 0x80, 0x7e, 0x69, 0x40, 0xb0, 0x14, 0xa0, 0x56, 0x2a, 0xf0, 0x2a, 0x15, 0x48, 0x55, 0x0b, 0x20, 0x55, 0xad, 0x80, 0x54, 0x35, 0x03, 0x52, 0xd5, 0x0c, 0x48, 0x55, 0x21, 0x90, 0xaa, 0x42, 0x20, 0x55, 0x6d, 0x80, 0x54, 0x5d, 0x00, 0xa4, 0xaa, 0x2d, 0x90, 0x2a, 0x0f, 0x7c, 0x93, 0x7c, 0x78, 0x8a, 0x34, 0x07, 0x5e, 0x65, 0x00, 0xaf, 0x8a, 0xc0, 0x43, 0xc9, 0x84, 0x87, 0x52, 0x13, 0x60, 0x57, 0x1a, 0xb0, 0x2b, 0x0e, 0xec, 0x2a, 0x08, 0xec, 0x4a, 0x03, 0x76, 0xa5, 0x01, 0xbb, 0x0a, 0x01, 0xbb, 0xd2, 0x80, 0x5d, 0x59, 0x3e, 0x88, 0xe7, 0x03, 0xb5, 0xca, 0x00, 0x6a, 0x15, 0x03, 0x52, 0xd5, 0x1e, 0x48, 0x55, 0x1d, 0x20, 0x55, 0x75, 0x10, 0x09, 0x96, 0x06, 0xcf, 0xa5, 0xfa, 0x40, 0xad, 0xc2, 0x40, 0xad, 0xea, 0x01, 0xb5, 0xd2, 0xf1, 0x3c, 0x6b, 0x88, 0xe7, 0x59, 0x0e, 0xa2, 0xc5, 0xd2, 0x98, 0x44, 0x4f, 0x4a, 0xf1, 0x45, 0xd1, 0xfe, 0xfa, 0x0a, 0x7a, 0x1a, 0x5c, 0x80, 0xc8, 0x92, 0x9f, 0x68, 0x1d, 0xb6, 0x93, 0xe8, 0xd9, 0xf8, 0x97, 0xb4, 0xfa, 0xf9, 0x84, 0x7e, 0x6f, 0x8a, 0xbf, 0x41, 0xdb, 0x32, 0xb2, 0x43, 0xbe, 0x8f, 0xbf, 0x4e, 0xbf, 0xc5, 0xd7, 0x33, 0x4e, 0x52, 0x8e, 0xcf, 0xe3, 0x1f, 0xff, 0xa7, 0x2d, 0x73, 0xf1, 0xa5, 0xc1, 0x73, 0x1e, 0x6b, 0x5b, 0xee, 0xc9, 0x40, 0x76, 0xee, 0x21, 0x5a, 0x3d, 0x6e, 0xa4, 0x95, 0xda, 0x1e, 0xb2, 0x88, 0x76, 0xd0, 0xaf, 0x77, 0xab, 0x8f, 0xd5, 0x88, 0x2f, 0xdd, 0x91, 0x2d, 0x24, 0xbe, 0x3d, 0xd7, 0x83, 0xce, 0x6f, 0x1e, 0x9f, 0x4d, 0x77, 0x53, 0xfe, 0x8d, 0xd3, 0x93, 0x54, 0xd2, 0xe7, 0xd6, 0x17, 0x19, 0x99, 0x21, 0xae, 0x28, 0xbe, 0x7f, 0x57, 0xd5, 0xcb, 0x5e, 0x5c, 0xd1, 0xb2, 0x5d, 0xab, 0x79, 0xc5, 0x7a, 0xf1, 0xb4, 0x78, 0x7d, 0xda, 0x5f, 0x12, 0x0f, 0xc7, 0x53, 0xe3, 0xb7, 0xc7, 0x87, 0xc7, 0x5b, 0x9d, 0xf6, 0xe7, 0x8f, 0x3b, 0x89, 0x17, 0xc3, 0xeb, 0xca, 0x83, 0xef, 0x47, 0xce, 0xb6, 0xbe, 0x9c, 0x60, 0x7d, 0x97, 0x30, 0x91, 0x6b, 0x7c, 0xbc, 0x2f, 0xf6, 0x7d, 0xc8, 0x86, 0x18, 0x47, 0xfd, 0x70, 0x05, 0x95, 0x38, 0x38, 0xde, 0x89, 0xec, 0xc0, 0xae, 0x54, 0xea, 0xfa, 0xb3, 0xdc, 0xe3, 0xdb, 0x89, 0x75, 0xbd, 0x96, 0x40, 0x99, 0x16, 0x26, 0x1d, 0x5d, 0x02, 0xa4, 0xed, 0xb4, 0x45, 0xe4, 0x2c, 0xb7, 0x40, 0x92, 0xf2, 0x6c, 0x4d, 0x7c, 0x89, 0x4f, 0x7c, 0xd1, 0x75, 0x75, 0xbc, 0x34, 0x3e, 0x0f, 0x5f, 0x44, 0x1c, 0x0d, 0x8f, 0x98, 0x67, 0xc8, 0x5e, 0xfc, 0xd5, 0x58, 0x42, 0xf1, 0xc5, 0xbd, 0x73, 0x1e, 0x1b, 0x90, 0xd8, 0xf7, 0x88, 0x47, 0xe2, 0xd3, 0xe8, 0xce, 0xd3, 0xf1, 0xad, 0x94, 0xd6, 0xf1, 0x31, 0xf1, 0xfe, 0xf1, 0x0c, 0xab, 0x97, 0x85, 0x1d, 0x6e, 0x61, 0x51, 0xc9, 0x7e, 0x0e, 0xf1, 0x4b, 0xe3, 0x4b, 0xad, 0x2f, 0xa7, 0xe2, 0xd7, 0x25, 0xf1, 0x46, 0xf1, 0x8b, 0x50, 0x1f, 0x99, 0xce, 0xbb, 0x56, 0xd8, 0xca, 0x49, 0x57, 0xa9, 0x2d, 0xbe, 0x4b, 0x11, 0xbf, 0x12, 0x88, 0xc8, 0x40, 0x58, 0xfa, 0x8f, 0x25, 0x97, 0x8c, 0xf4, 0x58, 0xcb, 0x37, 0x4a, 0xd4, 0x08, 0x5f, 0x09, 0x7d, 0x14, 0xa5, 0xdd, 0x16, 0x7f, 0x53, 0xf8, 0xd3, 0x90, 0xc6, 0xff, 0x6e, 0xdf, 0xbc, 0x72, 0x0f, 0xae, 0x6a, 0xe7, 0xaf, 0xd0, 0xa0, 0xf2, 0x6f, 0xdd, 0x96, 0xdb, 0xd8, 0x64, 0x81, 0xfc, 0xe6, 0xd8, 0xaa, 0x4e, 0x8c, 0x25, 0xd9, 0x34, 0x3b, 0xab, 0x5d, 0x9b, 0x3f, 0x64, 0x7b, 0x25, 0xf4, 0xec, 0x67, 0xb4, 0x78, 0xd9, 0xbf, 0x63, 0x0f, 0x9e, 0xf3, 0xc8, 0x17, 0xbf, 0xcf, 0x5e, 0x2d, 0x47, 0xbd, 0x61, 0x35, 0x1f, 0x4c, 0xf6, 0xe7, 0x89, 0xef, 0x2a, 0xff, 0x9a, 0xec, 0x6f, 0xd9, 0xd0, 0xff, 0x19, 0xac, 0x32, 0xbe, 0x85, 0x6c, 0xff, 0xf7, 0xff, 0x4b, 0xf6, 0x74, 0x59, 0x05, 0xd6, 0xb3, 0xfd, 0xb4, 0x0f, 0x0c, 0xcd, 0x55, 0xff, 0xaa, 0xce, 0x3d, 0x26, 0xc7, 0x99, 0xfd, 0xda, 0x1b, 0x05, 0xa4, 0x76, 0x26, 0xf7, 0x48, 0xfc, 0xbd, 0xc4, 0x15, 0xbf, 0x49, 0x44, 0x0f, 0xee, 0x3a, 0xe7, 0x15, 0x2b, 0x7f, 0xe3, 0x67, 0xc7, 0xef, 0x18, 0x1d, 0xfb, 0x2b, 0xf5, 0xa3, 0xf8, 0xea, 0xaf, 0xf5, 0xf5, 0xe8, 0x6f, 0x7f, 0xe5, 0xec, 0x0f, 0xaa, 0xd1, 0x66, 0x7b, 0x92, 0xbf, 0x7e, 0xfb, 0xdb, 0x73, 0x3a, 0xe2, 0x22, 0xad, 0xb7, 0x03, 0x73, 0x69, 0x66, 0x5c, 0x79, 0xae, 0x16, 0xfa, 0x35, 0xd4, 0xa1, 0xaa, 0xb7, 0xce, 0xaf, 0x8e, 0x3a, 0xce, 0xae, 0x25, 0x9b, 0xee, 0x4b, 0xf6, 0x33, 0xd9, 0x64, 0xc2, 0xff, 0x3f, 0x9d, 0xec, 0xa5, 0x06, 0xf2, 0x7c, 0x61, 0x51, 0x90, 0x3d, 0xb1, 0x98, 0xec, 0x85, 0xf5, 0x64, 0x27, 0x94, 0x92, 0x05, 0x40, 0xab, 0x7f, 0x5a, 0xbf, 0xd3, 0x4a, 0x9d, 0x56, 0xe2, 0x92, 0x58, 0x7b, 0x8b, 0x15, 0xb6, 0x58, 0x3b, 0xd3, 0x5a, 0xf7, 0x47, 0xdb, 0x71, 0x5a, 0x13, 0x36, 0xd6, 0x9a, 0x68, 0x4d, 0xb5, 0xe6, 0x5a, 0x0b, 0x5a, 0xd7, 0xdd, 0x49, 0xab, 0xb4, 0x7b, 0x69, 0x8d, 0x36, 0x41, 0x7b, 0x50, 0x9b, 0xa8, 0x3d, 0xa4, 0x4d, 0xd2, 0x1e, 0x16, 0xab, 0x34, 0x5a, 0xa3, 0x3d, 0x2d, 0xd6, 0x63, 0xb4, 0x1a, 0x5b, 0x4f, 0xab, 0xb0, 0x8f, 0x68, 0xdd, 0xf5, 0x15, 0xad, 0xb4, 0xda, 0x8b, 0x35, 0x14, 0xad, 0xa0, 0x6e, 0xd1, 0x87, 0xea, 0xb7, 0xea, 0x25, 0xfa, 0x6d, 0xfa, 0x30, 0xfd, 0x76, 0x7d, 0x38, 0xad, 0xa3, 0xee, 0xa2, 0xf5, 0xd3, 0x1e, 0xb1, 0x6e, 0xa2, 0x55, 0xd3, 0x77, 0xfa, 0x11, 0xb1, 0x22, 0xa2, 0x55, 0xce, 0x07, 0xb4, 0x36, 0x59, 0x49, 0x2b, 0x90, 0xd5, 0xb4, 0xee, 0x90, 0xc8, 0x46, 0x15, 0x76, 0xa9, 0x03, 0xb6, 0xa8, 0x03, 0x56, 0xa2, 0x13, 0x36, 0xa1, 0x13, 0xb6, 0x9f, 0x13, 0x56, 0x9f, 0x93, 0xee, 0x69, 0x3e, 0x73, 0xd0, 0x7d, 0x2d, 0x22, 0x2e, 0x6c, 0x24, 0x2b, 0x4e, 0x40, 0x86, 0x2d, 0x64, 0x87, 0x2d, 0xa4, 0xc0, 0xc2, 0x51, 0x60, 0xa5, 0xb8, 0x61, 0x93, 0x58, 0x1e, 0xf9, 0x4e, 0x58, 0x1d, 0x4e, 0xd8, 0x1b, 0x6e, 0xba, 0xeb, 0x4e, 0xcc, 0x43, 0xf7, 0xdd, 0x9d, 0xf8, 0xe5, 0x64, 0x39, 0x38, 0xb1, 0xd6, 0xd7, 0xb0, 0xbe, 0xd7, 0xb1, 0xbe, 0x77, 0x50, 0x3b, 0x34, 0x61, 0x9c, 0xda, 0xa2, 0x29, 0x93, 0xa8, 0x3d, 0x9a, 0x53, 0xba, 0x85, 0xd6, 0x82, 0xd2, 0x62, 0xbd, 0x2b, 0x63, 0x45, 0xab, 0x50, 0xfb, 0x8c, 0x65, 0x36, 0x6a, 0x9f, 0x47, 0x88, 0x8b, 0x95, 0xab, 0x3d, 0xf1, 0x76, 0xd8, 0x7a, 0x2f, 0x2c, 0x56, 0xab, 0xd6, 0xbb, 0x60, 0x0d, 0xab, 0x55, 0x6b, 0x9d, 0x6a, 0x60, 0x9d, 0x6a, 0xbd, 0xe1, 0x35, 0xb0, 0x42, 0x35, 0xb1, 0x06, 0x35, 0xb1, 0x06, 0x75, 0x60, 0x0d, 0xea, 0xc4, 0x1a, 0xd4, 0x81, 0x35, 0xa8, 0x13, 0x6b, 0x50, 0x17, 0xd6, 0xa0, 0x2e, 0xac, 0x41, 0xdd, 0x58, 0x83, 0x7a, 0xb0, 0x06, 0xf5, 0x60, 0x7d, 0xe9, 0xc0, 0xfa, 0xcf, 0x89, 0xd5, 0x9e, 0x03, 0x2d, 0xea, 0xa4, 0x36, 0x7d, 0x03, 0x78, 0x40, 0x01, 0xf0, 0x80, 0x54, 0xe0, 0x01, 0xb5, 0x81, 0x07, 0x34, 0x01, 0x1e, 0x50, 0x13, 0x78, 0x40, 0x2d, 0xb4, 0x7b, 0x0a, 0xf0, 0x80, 0x4c, 0xe0, 0x01, 0x31, 0xf4, 0x41, 0x26, 0xf0, 0x80, 0xf3, 0x80, 0x07, 0x78, 0x81, 0x07, 0x04, 0x80, 0x07, 0x44, 0x80, 0x07, 0x34, 0x02, 0x1e, 0xe0, 0x04, 0x1e, 0xa0, 0x00, 0x09, 0x08, 0x03, 0x09, 0x68, 0x04, 0x24, 0xc0, 0x01, 0x24, 0xc0, 0x0f, 0x24, 0xc0, 0x06, 0x24, 0x80, 0x03, 0x09, 0xf0, 0x03, 0x09, 0xb0, 0x03, 0x09, 0xf0, 0x27, 0xc5, 0xa9, 0xf8, 0x81, 0x04, 0x34, 0x04, 0x12, 0x90, 0x97, 0x14, 0xa7, 0xe2, 0x07, 0x12, 0xd0, 0x00, 0x48, 0x80, 0x1f, 0x48, 0x80, 0x1f, 0xda, 0x20, 0x27, 0xc5, 0xa9, 0xf8, 0x81, 0x04, 0x04, 0x81, 0x04, 0x68, 0x40, 0x02, 0x9a, 0x02, 0x09, 0x68, 0x06, 0x24, 0xa0, 0x29, 0x90, 0x80, 0x66, 0x40, 0x02, 0x42, 0x40, 0x02, 0x32, 0xe0, 0x73, 0x93, 0x03, 0x3c, 0x20, 0x03, 0x3e, 0x37, 0x39, 0xf0, 0xb9, 0x49, 0x87, 0xcf, 0x4d, 0x3a, 0x10, 0x82, 0xf3, 0x61, 0x97, 0x17, 0xc0, 0x2e, 0xcf, 0x84, 0x5d, 0x9e, 0x02, 0xbb, 0x3c, 0x13, 0x3a, 0x97, 0x09, 0xbb, 0x3c, 0x13, 0x76, 0xb9, 0x17, 0x76, 0xb9, 0x03, 0x76, 0x79, 0x23, 0xe8, 0xa2, 0x03, 0xba, 0xe8, 0x82, 0x2e, 0x9a, 0xb0, 0xcb, 0x1d, 0xb0, 0xc8, 0xeb, 0xc2, 0x22, 0xb7, 0x6c, 0xf1, 0x7c, 0xd8, 0xe2, 0x8d, 0xa0, 0xa9, 0x26, 0x6c, 0xf1, 0x2c, 0xd8, 0xe2, 0x11, 0xd8, 0xe2, 0x7e, 0xd8, 0xe2, 0x36, 0xd8, 0xe2, 0x7e, 0xd8, 0xe2, 0x0d, 0x61, 0x8b, 0xfb, 0x93, 0xe2, 0x54, 0x1a, 0xc2, 0x16, 0xaf, 0x07, 0x2d, 0x8f, 0xc2, 0x16, 0x6f, 0x08, 0x5b, 0xbc, 0x01, 0x6c, 0x71, 0x3f, 0xf4, 0xbe, 0x3e, 0x6c, 0x71, 0x3f, 0x6c, 0x71, 0x3b, 0x6c, 0x71, 0x3f, 0x6c, 0xf1, 0x06, 0xb0, 0xc5, 0xfd, 0x18, 0x15, 0x52, 0x52, 0x9c, 0x8a, 0x1f, 0x63, 0x23, 0x9a, 0x14, 0xa7, 0x62, 0x87, 0x2d, 0xee, 0x87, 0x2d, 0x5e, 0x07, 0xb6, 0x78, 0x23, 0xd8, 0xe2, 0x75, 0x60, 0x8b, 0x37, 0x82, 0x2d, 0x6e, 0xc0, 0x16, 0xf7, 0xc3, 0x16, 0xf7, 0xc3, 0x16, 0xf7, 0x9f, 0x11, 0xa7, 0x52, 0x03, 0xb6, 0x78, 0x0d, 0xd8, 0xe2, 0xd9, 0xc0, 0xf7, 0x72, 0x61, 0x91, 0x67, 0x03, 0xdf, 0xcb, 0x85, 0x15, 0x5e, 0x13, 0x36, 0x37, 0x4f, 0x8a, 0x56, 0x09, 0xc0, 0xe6, 0x6e, 0x84, 0x31, 0x99, 0x89, 0x31, 0xd9, 0x18, 0x36, 0x77, 0x01, 0xec, 0x6c, 0xcb, 0xcb, 0x44, 0x87, 0x25, 0xdd, 0x08, 0x96, 0x74, 0x01, 0xec, 0xda, 0x02, 0xd8, 0xb5, 0x8d, 0x60, 0xd1, 0x16, 0xc0, 0xa2, 0x2d, 0x80, 0x45, 0x5b, 0x00, 0x8b, 0xb6, 0x11, 0x46, 0xaf, 0x03, 0xa3, 0xd7, 0x84, 0x0d, 0x9a, 0x06, 0x1b, 0x34, 0x0d, 0xd6, 0x67, 0x1a, 0xc6, 0xb0, 0xeb, 0x8c, 0x31, 0xac, 0x26, 0xc5, 0xb8, 0x64, 0x56, 0x23, 0xc6, 0x25, 0x08, 0x8b, 0xd3, 0x0d, 0x8b, 0xd3, 0x0f, 0x8b, 0xd3, 0x0d, 0x8b, 0xd3, 0x0d, 0x8b, 0xb3, 0x16, 0x46, 0xbb, 0x1f, 0xa3, 0x3d, 0x33, 0x29, 0xd2, 0x25, 0x33, 0x29, 0xd2, 0xc5, 0x8f, 0xd1, 0xee, 0xc7, 0x68, 0x8f, 0x62, 0xb4, 0xfb, 0x30, 0xda, 0x7d, 0xb0, 0x38, 0x53, 0x61, 0x71, 0xd6, 0x86, 0xc5, 0xd9, 0x04, 0x16, 0x67, 0x13, 0x58, 0x9c, 0x35, 0x61, 0x71, 0xd6, 0xc4, 0x8c, 0x90, 0x02, 0x8b, 0x33, 0x06, 0x8b, 0xf3, 0x3c, 0x58, 0x9c, 0x5e, 0x58, 0x9c, 0x01, 0x58, 0x9c, 0x05, 0xb0, 0x38, 0x9d, 0xb0, 0x35, 0xc3, 0xb0, 0x35, 0x15, 0xd8, 0x9a, 0x7e, 0xd8, 0x9a, 0x36, 0xd8, 0x9a, 0x1c, 0xb6, 0xa6, 0x1f, 0xb6, 0xa6, 0x1f, 0xb6, 0x66, 0x03, 0xd8, 0x9a, 0x7e, 0xcc, 0x35, 0x32, 0x6c, 0xcd, 0x3c, 0xf8, 0x49, 0xa4, 0x63, 0xde, 0x69, 0x0c, 0x9b, 0x32, 0x1f, 0x36, 0x65, 0x23, 0xd8, 0x94, 0x52, 0x52, 0x34, 0x4c, 0x1d, 0xd8, 0x94, 0x06, 0x3d, 0xab, 0x1c, 0x6c, 0x13, 0xf5, 0xf3, 0x00, 0x75, 0x10, 0xcd, 0x50, 0xb5, 0xec, 0x75, 0xec, 0x2d, 0x19, 0xb3, 0xb7, 0xb6, 0xd3, 0xf8, 0xb4, 0x5f, 0x6c, 0xef, 0xc9, 0xa2, 0xf6, 0x83, 0xf6, 0x6f, 0x58, 0x1d, 0xfb, 0x11, 0xfb, 0x8f, 0xac, 0xbe, 0xfd, 0xa4, 0xaa, 0x50, 0xef, 0xda, 0x55, 0x93, 0x35, 0x57, 0x9d, 0xaa, 0x87, 0xb5, 0x51, 0xc3, 0x6a, 0x84, 0x75, 0x50, 0x6b, 0xa9, 0xf5, 0x58, 0x47, 0x53, 0x36, 0x65, 0xd6, 0xdd, 0xb4, 0x9b, 0x76, 0x76, 0xb9, 0xa9, 0x9b, 0x3a, 0x2b, 0x36, 0xe9, 0x8f, 0x5d, 0x61, 0xba, 0x4c, 0x17, 0xeb, 0x61, 0xfa, 0x4d, 0x3f, 0xbb, 0xd2, 0x0c, 0x9b, 0x61, 0xd6, 0xd3, 0xcc, 0x35, 0x73, 0xd9, 0x55, 0x66, 0x23, 0xb3, 0x11, 0xeb, 0x65, 0x16, 0x98, 0x05, 0xec, 0x6a, 0xf3, 0x02, 0xf3, 0x02, 0xd6, 0xdb, 0x6c, 0x69, 0xb6, 0x64, 0xd7, 0x98, 0xad, 0xcd, 0xd6, 0xac, 0x8f, 0xd9, 0xd1, 0xec, 0xc8, 0xae, 0x35, 0x2f, 0x36, 0x2f, 0x66, 0x7d, 0xcd, 0xfb, 0xcc, 0xfb, 0xd8, 0x5f, 0xcc, 0xf1, 0xe6, 0x78, 0xd6, 0xcf, 0x7c, 0xc8, 0x7c, 0x88, 0x5d, 0x67, 0xbe, 0x6e, 0xbe, 0xce, 0xfa, 0x9b, 0xeb, 0xcd, 0xf5, 0xec, 0x7a, 0xf3, 0x2d, 0xf3, 0x2d, 0x36, 0x80, 0x6a, 0xbf, 0x1e, 0xdf, 0x00, 0x66, 0xf8, 0x92, 0x58, 0x3d, 0x76, 0x3e, 0x6b, 0xce, 0xea, 0xe3, 0x5b, 0x61, 0x0d, 0xf1, 0x95, 0xb0, 0x3c, 0xd6, 0x9e, 0xa8, 0x11, 0xeb, 0x4a, 0x94, 0x0f, 0xd4, 0xbc, 0x31, 0x3d, 0xa7, 0xaf, 0xa3, 0xd9, 0x77, 0x00, 0x51, 0x73, 0x7c, 0xf5, 0xb7, 0x05, 0x70, 0xf4, 0x96, 0xf8, 0xba, 0x6f, 0x21, 0x1b, 0xc9, 0xee, 0x62, 0xad, 0xd9, 0xdd, 0x44, 0x6d, 0xd9, 0xc3, 0x6c, 0x32, 0x6b, 0xc7, 0xde, 0x62, 0x5b, 0x59, 0x07, 0x56, 0x4a, 0x54, 0xc4, 0x3e, 0x24, 0xba, 0x94, 0x7d, 0x44, 0xd4, 0x8d, 0x7d, 0xcc, 0x76, 0xb2, 0xcb, 0xd8, 0x2e, 0xa2, 0xcb, 0xd9, 0x67, 0x6c, 0x0f, 0x2b, 0x66, 0x07, 0x89, 0x7a, 0xb0, 0xc3, 0xdc, 0xcd, 0xae, 0xc4, 0x2c, 0x7b, 0x07, 0xe6, 0xd1, 0x3b, 0x31, 0x3b, 0x8e, 0xe4, 0xb7, 0xf2, 0x5b, 0xd9, 0x5d, 0xfc, 0x75, 0xfe, 0x3a, 0x1b, 0xc5, 0xf7, 0xf3, 0x2f, 0xd9, 0xdd, 0x52, 0x5b, 0xe9, 0x0e, 0x76, 0x8f, 0x74, 0xa7, 0x34, 0x92, 0x2d, 0x87, 0x8f, 0xdf, 0x0a, 0xe9, 0x1e, 0xe9, 0x1e, 0xb6, 0x52, 0x9a, 0x20, 0x3d, 0xc8, 0x56, 0x49, 0x0f, 0x49, 0x0f, 0xb1, 0xd5, 0xd2, 0x64, 0x69, 0x32, 0x7b, 0x55, 0x7a, 0x54, 0x7a, 0x94, 0xad, 0xc1, 0xd7, 0x71, 0x5f, 0x93, 0x66, 0x4b, 0xb3, 0xd9, 0xeb, 0xd2, 0x8b, 0xd2, 0x8b, 0x6c, 0x9d, 0xb4, 0x54, 0xfa, 0x07, 0x7b, 0x43, 0x5a, 0x25, 0xbd, 0xc2, 0xde, 0x94, 0x5e, 0x93, 0xde, 0x60, 0x6f, 0x49, 0x6f, 0x4b, 0x6f, 0xb3, 0xcd, 0xd2, 0x16, 0x69, 0x0b, 0x7b, 0x57, 0xda, 0x26, 0x7d, 0xc8, 0xb6, 0x48, 0x1f, 0x49, 0x1f, 0xb3, 0xad, 0xd2, 0x5e, 0x69, 0x2f, 0xfb, 0x40, 0x3a, 0x20, 0x7d, 0xc5, 0xb6, 0x49, 0x5f, 0x4b, 0x5f, 0xb3, 0xed, 0x52, 0x99, 0x54, 0xc6, 0x3e, 0x92, 0xbe, 0x93, 0x8e, 0xb2, 0x1d, 0xd2, 0x31, 0xe9, 0x18, 0xdb, 0x25, 0x1d, 0x97, 0xe2, 0xec, 0x53, 0x59, 0x92, 0x25, 0xf6, 0x85, 0x6c, 0x93, 0x6d, 0x6c, 0xbf, 0xac, 0xc9, 0x1a, 0xfb, 0x92, 0xba, 0xd8, 0xc5, 0x0e, 0xc8, 0x01, 0x39, 0xc2, 0x0e, 0xc9, 0x35, 0xe4, 0x54, 0xf6, 0x9d, 0x9c, 0x29, 0x67, 0xb2, 0xa3, 0x72, 0x96, 0x9c, 0xc5, 0x7e, 0x90, 0xaf, 0x92, 0xaf, 0x62, 0xc7, 0xe4, 0xab, 0xe5, 0xab, 0xd9, 0x8f, 0xf2, 0xf5, 0xf2, 0x00, 0x76, 0x5c, 0xbe, 0x45, 0xbe, 0x85, 0x9d, 0x90, 0xef, 0x90, 0xef, 0x60, 0x3f, 0xcb, 0x77, 0xcb, 0x77, 0xb3, 0x93, 0xf2, 0x93, 0xf2, 0x3c, 0xf6, 0x8b, 0xfc, 0x9c, 0xfc, 0x1c, 0xb7, 0x89, 0x59, 0x9c, 0xdb, 0xe5, 0x25, 0xf2, 0x12, 0xae, 0xca, 0xff, 0x90, 0xff, 0xc1, 0x35, 0x79, 0xb9, 0xfc, 0x2f, 0xae, 0x8b, 0x39, 0x8f, 0x9f, 0xaf, 0x38, 0x14, 0x07, 0x6f, 0xa6, 0x78, 0x15, 0x1f, 0xbf, 0x40, 0x09, 0x2a, 0x41, 0xde, 0x42, 0x09, 0x2b, 0x11, 0xde, 0x52, 0x49, 0x51, 0x52, 0x78, 0xa1, 0x52, 0x4b, 0x49, 0xe7, 0xad, 0x95, 0x46, 0xca, 0xf9, 0xbc, 0xbd, 0xd2, 0x4a, 0x29, 0xe4, 0x5d, 0x68, 0x5e, 0xec, 0xc2, 0x69, 0xf6, 0x53, 0xba, 0xf1, 0x2b, 0x95, 0x62, 0xa5, 0x98, 0xf7, 0x16, 0xef, 0x34, 0xf8, 0x35, 0xe2, 0xdb, 0xbf, 0xbc, 0x8f, 0x2d, 0x6a, 0x8b, 0xf2, 0x6b, 0x6d, 0x35, 0x6d, 0xe7, 0xf1, 0xbe, 0xb6, 0xda, 0xb6, 0xda, 0xfc, 0x7a, 0xdb, 0x55, 0xb6, 0xab, 0xf8, 0x00, 0xf1, 0x36, 0x83, 0xdf, 0x60, 0x7b, 0xc3, 0xb6, 0x81, 0x0f, 0xb4, 0x7d, 0x6a, 0xdb, 0xc3, 0x07, 0xdb, 0xf6, 0xdb, 0xbe, 0xe4, 0x43, 0x6d, 0x5f, 0xd9, 0x0e, 0xf1, 0x12, 0x9a, 0xf9, 0x8e, 0xf1, 0xe1, 0xe2, 0x4d, 0x10, 0x1f, 0x81, 0xcf, 0x9e, 0x8f, 0xb4, 0xbb, 0xec, 0x2e, 0x7e, 0x97, 0xc0, 0x19, 0xf9, 0x28, 0x7b, 0xa6, 0x3d, 0x8b, 0xdf, 0x2d, 0xfc, 0xee, 0xf8, 0x18, 0xb5, 0xbe, 0x7a, 0x05, 0xbf, 0x97, 0x66, 0xc1, 0x11, 0x7c, 0x91, 0x3a, 0x52, 0x7d, 0x95, 0x6f, 0x50, 0xdf, 0x52, 0x4f, 0x48, 0x86, 0x7a, 0x52, 0x33, 0xa5, 0x42, 0xcd, 0xa9, 0xcd, 0x92, 0x8a, 0xb4, 0xd9, 0xda, 0x1c, 0x69, 0xb7, 0xf6, 0x8c, 0xf6, 0x92, 0xf4, 0x39, 0xcd, 0x67, 0x2f, 0x4b, 0x87, 0xb5, 0x95, 0xda, 0x4a, 0xe9, 0xa8, 0xb6, 0x5a, 0x7b, 0x43, 0xfa, 0x41, 0x7b, 0x4b, 0xfb, 0x40, 0xfa, 0x45, 0xfb, 0x50, 0xdb, 0x2e, 0x1b, 0xda, 0x0e, 0xed, 0x63, 0xd9, 0xa1, 0x7d, 0xa2, 0x7d, 0x2e, 0xbb, 0xb4, 0x7d, 0xda, 0x17, 0x72, 0x50, 0x3b, 0xa0, 0x7d, 0x23, 0x87, 0xb5, 0xb8, 0xae, 0xc8, 0x35, 0x75, 0x8f, 0x9e, 0x26, 0x67, 0xe8, 0xe9, 0x7a, 0xba, 0x5c, 0x40, 0xf3, 0x59, 0x96, 0xdc, 0x44, 0x7c, 0x03, 0x4d, 0x6e, 0xa6, 0xe7, 0xe8, 0x8d, 0xe4, 0x0b, 0xf4, 0xc6, 0x7a, 0x33, 0xb9, 0xb5, 0xf8, 0xe2, 0xae, 0x7c, 0xa1, 0xde, 0x41, 0xbf, 0x50, 0x2e, 0xd2, 0x2f, 0xd2, 0x2f, 0x96, 0xbb, 0xe9, 0x97, 0xe8, 0x45, 0xf2, 0xe5, 0xfa, 0xe5, 0x7a, 0x4f, 0xf9, 0x0a, 0xbd, 0x97, 0x4e, 0xbd, 0x44, 0xeb, 0xc3, 0x6b, 0xe4, 0x6b, 0xf4, 0xbe, 0xfa, 0x00, 0xb9, 0x8f, 0x3e, 0x50, 0x1f, 0x29, 0x5f, 0x2f, 0xbc, 0xcb, 0xe4, 0x11, 0xfa, 0xbd, 0xfa, 0x58, 0x79, 0xa4, 0x7e, 0xbf, 0x7e, 0xbf, 0x3c, 0x4a, 0x7f, 0x50, 0x7f, 0x58, 0xbe, 0x5b, 0x9f, 0xa2, 0x4f, 0x91, 0xc7, 0xea, 0x53, 0xf5, 0x69, 0xf2, 0x7d, 0xfa, 0x74, 0x7d, 0xba, 0x3c, 0x5e, 0x7f, 0x5c, 0x7f, 0x5c, 0x7e, 0x40, 0x5f, 0xa3, 0xbf, 0x2e, 0x4f, 0xd0, 0xdf, 0xd0, 0xdf, 0x94, 0x27, 0xe9, 0x1b, 0xf5, 0x77, 0xe5, 0x47, 0xf4, 0xf7, 0xf5, 0x52, 0xf9, 0x51, 0x7d, 0x9b, 0xbe, 0x5d, 0x7e, 0x4c, 0xff, 0x58, 0xff, 0x54, 0x9e, 0xa9, 0x7f, 0xa6, 0x7f, 0x26, 0x3f, 0x25, 0x3c, 0xcd, 0xe4, 0x39, 0xc2, 0xd3, 0x4c, 0x9e, 0x4b, 0x2b, 0xcf, 0xaf, 0xe4, 0xf9, 0xfa, 0xd7, 0xfa, 0x21, 0xf9, 0x39, 0xbd, 0x4c, 0xff, 0x56, 0xfe, 0x3b, 0xad, 0x43, 0x8f, 0xc9, 0xcf, 0x1b, 0x61, 0xa3, 0xb6, 0xfc, 0x12, 0xcd, 0xa9, 0xcd, 0xe5, 0xb5, 0x46, 0x4b, 0xa3, 0xa5, 0xbc, 0xd9, 0x28, 0x34, 0xba, 0xcb, 0xef, 0xd2, 0x9c, 0xda, 0x47, 0xde, 0x6b, 0xf4, 0x35, 0x06, 0xcb, 0xdf, 0xd0, 0x9c, 0x3a, 0x4a, 0x3e, 0x61, 0x8c, 0x36, 0x46, 0x2b, 0x1a, 0xcd, 0xac, 0x53, 0x15, 0xdd, 0x98, 0x66, 0x4c, 0x53, 0x6a, 0x18, 0xd3, 0x8d, 0xb9, 0x4a, 0x4d, 0x63, 0x9e, 0xf1, 0xba, 0x52, 0x4f, 0xc4, 0x98, 0x2b, 0x9d, 0x69, 0xd6, 0xdc, 0xa4, 0x5c, 0x6c, 0xbc, 0x6b, 0x6c, 0x51, 0x84, 0x5f, 0xd9, 0x36, 0xa5, 0x9b, 0xf0, 0x25, 0x53, 0x8a, 0x8d, 0x83, 0xc6, 0x8f, 0xca, 0x15, 0xc6, 0x4f, 0xc6, 0x49, 0xe5, 0x3a, 0xb3, 0x83, 0xd9, 0x41, 0xb9, 0xc1, 0xbc, 0xca, 0xbc, 0x4a, 0x19, 0x68, 0xf6, 0x36, 0x7b, 0x2b, 0x37, 0x9a, 0xfd, 0xcc, 0x7e, 0xca, 0x20, 0xf3, 0x7a, 0xf3, 0x7a, 0xe5, 0x26, 0x73, 0xa0, 0x39, 0x50, 0x19, 0x6c, 0xde, 0x64, 0xde, 0xa4, 0xdc, 0x6c, 0xde, 0x6a, 0xde, 0xaa, 0x0c, 0x31, 0x87, 0x99, 0xc3, 0x94, 0x5b, 0xcc, 0x87, 0xcd, 0xc9, 0xca, 0x50, 0x73, 0xaa, 0xf9, 0x98, 0x52, 0x62, 0x2e, 0x36, 0x5f, 0x54, 0x46, 0x98, 0x2b, 0xcc, 0x55, 0xca, 0x48, 0xb1, 0xd2, 0x53, 0xee, 0x16, 0xf3, 0xab, 0x72, 0x8f, 0xb9, 0xc9, 0x7c, 0x47, 0x19, 0x63, 0xbe, 0x6b, 0xbe, 0xab, 0x8c, 0x65, 0x37, 0x90, 0x51, 0xb0, 0x8d, 0xb6, 0x85, 0x55, 0xf6, 0xf3, 0x7e, 0xc7, 0x7e, 0x36, 0x6d, 0xcb, 0x93, 0x7e, 0xaf, 0xa6, 0x6d, 0x5d, 0xe2, 0x77, 0x72, 0xde, 0x7f, 0x77, 0xab, 0x5a, 0xb7, 0x7f, 0x77, 0x5b, 0x52, 0xe5, 0x77, 0x79, 0x9d, 0x93, 0xeb, 0xbb, 0x91, 0xb6, 0x2d, 0xe7, 0x38, 0x7f, 0x67, 0x95, 0x6d, 0x4f, 0xd2, 0xb6, 0x9f, 0xb6, 0x43, 0x89, 0xed, 0x78, 0xd2, 0x76, 0x2a, 0xb1, 0x1d, 0x49, 0x92, 0x95, 0xa7, 0x0f, 0x25, 0xed, 0xcb, 0xb7, 0x23, 0x95, 0xce, 0xe5, 0xa7, 0x6e, 0x51, 0xfa, 0x12, 0x1f, 0x6a, 0x3b, 0x48, 0x7c, 0x14, 0xf8, 0x41, 0xfb, 0x14, 0x21, 0x47, 0x7a, 0x28, 0xad, 0x90, 0xf8, 0xa9, 0x5b, 0x6d, 0x5f, 0x23, 0xcf, 0x0a, 0xe2, 0x1b, 0x95, 0x1d, 0x90, 0xe3, 0x2c, 0x5a, 0xab, 0x09, 0x5e, 0x8c, 0xa3, 0x8f, 0xd3, 0xda, 0xfa, 0x11, 0x71, 0x16, 0xbf, 0x1f, 0x7c, 0x4a, 0x82, 0x7f, 0xad, 0xdc, 0xe7, 0xdc, 0xee, 0xdc, 0xae, 0x6b, 0x8e, 0x43, 0x4e, 0x45, 0x77, 0xe9, 0xab, 0x9d, 0x21, 0x8f, 0xe1, 0xcc, 0x76, 0x37, 0x33, 0x6a, 0x78, 0x3c, 0x7a, 0xc0, 0xd9, 0xd1, 0x6c, 0xe9, 0xec, 0xee, 0xec, 0xa3, 0xc7, 0x8c, 0xed, 0xce, 0x41, 0xce, 0xe1, 0xce, 0xb1, 0x7a, 0x9a, 0x73, 0xb2, 0x6b, 0x84, 0x9e, 0xe5, 0xda, 0xed, 0x5c, 0xe4, 0x9c, 0x6c, 0xac, 0x70, 0x2e, 0xd5, 0x73, 0x9c, 0x6b, 0x8c, 0x90, 0x73, 0xab, 0x73, 0x2b, 0x59, 0x72, 0xf9, 0xce, 0xbd, 0xce, 0x03, 0xce, 0xa3, 0x2e, 0x87, 0xab, 0xc4, 0xbd, 0x5a, 0x6f, 0xa6, 0x17, 0xba, 0x32, 0x69, 0xac, 0x77, 0xd1, 0xbb, 0xe9, 0x3d, 0xf4, 0xde, 0xae, 0x01, 0x44, 0x9d, 0x5d, 0x45, 0xa0, 0x5e, 0x7a, 0x3f, 0x57, 0x5f, 0x97, 0xc3, 0xdc, 0xa0, 0x0f, 0x74, 0x4d, 0x77, 0xa5, 0xea, 0x43, 0x9c, 0xd9, 0xfa, 0x30, 0x77, 0xbe, 0x6b, 0x99, 0x3e, 0xd2, 0xe3, 0x71, 0xad, 0xd2, 0xc7, 0xb8, 0xd6, 0xba, 0x46, 0x98, 0xbd, 0x9c, 0x2b, 0xf4, 0xf1, 0xfa, 0x24, 0x73, 0x00, 0x8d, 0xf1, 0x19, 0xfa, 0x6c, 0xb7, 0xe6, 0x0e, 0xb8, 0x67, 0x9b, 0x6b, 0xf5, 0x79, 0xfa, 0x42, 0xc7, 0x12, 0x7d, 0x89, 0x39, 0xd1, 0xdc, 0x61, 0x2e, 0x76, 0x67, 0xb9, 0xf3, 0xcd, 0x59, 0xfa, 0x72, 0x7d, 0xb5, 0xbe, 0xce, 0x9c, 0x65, 0xce, 0x72, 0x1e, 0xa0, 0x31, 0xbf, 0xd1, 0x7d, 0x5c, 0xdf, 0xe2, 0x6c, 0xa3, 0x6f, 0xa3, 0xfb, 0xda, 0xe9, 0x1e, 0xe9, 0x88, 0x39, 0x62, 0xee, 0x25, 0xae, 0xd1, 0xe6, 0x14, 0x5a, 0x2d, 0xed, 0x77, 0x75, 0xd6, 0x0f, 0x99, 0x83, 0xf5, 0x23, 0xfa, 0x71, 0xa3, 0x80, 0xae, 0x7d, 0x8a, 0x96, 0xb1, 0x86, 0xe1, 0x31, 0x42, 0xee, 0x1c, 0xd7, 0x5c, 0x67, 0xb6, 0x51, 0xc3, 0x9d, 0x63, 0xa4, 0x1b, 0xd9, 0x9e, 0x5c, 0xe7, 0x58, 0x77, 0xbe, 0xa3, 0x9f, 0x91, 0x6b, 0xac, 0xf0, 0x28, 0x8e, 0x23, 0xce, 0x4d, 0xae, 0x52, 0xa3, 0xc0, 0x68, 0x6e, 0xb4, 0x71, 0xef, 0x71, 0xef, 0x74, 0xa4, 0x39, 0x34, 0x8f, 0xc7, 0x19, 0x72, 0xec, 0x34, 0x3a, 0x3a, 0x8f, 0x1a, 0x5d, 0x8d, 0x3e, 0x4e, 0xc3, 0xe8, 0xee, 0xdc, 0x6a, 0xac, 0x30, 0x23, 0xf4, 0xbb, 0xa7, 0xd1, 0x87, 0xee, 0xa3, 0xb3, 0xd1, 0xdf, 0x18, 0x64, 0x0c, 0x35, 0x33, 0xdd, 0xc3, 0x8c, 0xe1, 0xae, 0x01, 0xc6, 0x28, 0x5a, 0x6f, 0xcd, 0x31, 0xb6, 0xba, 0x4a, 0x8c, 0x32, 0x63, 0x82, 0x31, 0xd9, 0x98, 0x46, 0x2d, 0xd2, 0xcb, 0xac, 0x67, 0xcc, 0x71, 0x95, 0x38, 0xc6, 0x18, 0x33, 0xa9, 0xfc, 0x39, 0x44, 0xf3, 0xa9, 0x95, 0x7a, 0x19, 0x8b, 0x8c, 0xa5, 0x54, 0x56, 0xa6, 0xfb, 0x90, 0xb1, 0xc6, 0x58, 0xef, 0x58, 0x6e, 0x6c, 0xa2, 0xf3, 0x36, 0x1b, 0xdb, 0x8d, 0x5d, 0xae, 0x12, 0x41, 0xee, 0xa9, 0x66, 0xa9, 0xb1, 0xd7, 0x38, 0x40, 0x25, 0x1d, 0x25, 0x3a, 0x61, 0x32, 0x47, 0x37, 0xd3, 0xee, 0x58, 0x62, 0x3a, 0xcc, 0x89, 0x8e, 0x6e, 0xce, 0xad, 0x1e, 0xc5, 0x99, 0xeb, 0xcc, 0x75, 0xaf, 0x33, 0x7d, 0x66, 0xc4, 0x4c, 0x35, 0x33, 0xcd, 0x7a, 0x66, 0x9e, 0xd9, 0x94, 0x7e, 0xb7, 0x34, 0xdb, 0x99, 0x9d, 0xcd, 0x22, 0xb3, 0xd8, 0x1c, 0xe0, 0xcc, 0x76, 0x6d, 0xa6, 0x36, 0x1e, 0x6e, 0xf6, 0x75, 0xed, 0xf0, 0x64, 0x9b, 0x03, 0xcc, 0xc1, 0x66, 0x89, 0x39, 0xc2, 0x9d, 0x65, 0x8e, 0x36, 0x37, 0x98, 0x6b, 0xcd, 0x71, 0x8e, 0x2e, 0xd4, 0xc2, 0x53, 0xcc, 0xe9, 0x1e, 0xfa, 0xa3, 0x76, 0x55, 0x9c, 0x9b, 0xa8, 0x15, 0xf6, 0x9a, 0x73, 0xcd, 0x65, 0xe6, 0x32, 0x67, 0x1b, 0x73, 0x81, 0xb9, 0x80, 0x72, 0x2c, 0xc6, 0xd1, 0x65, 0xce, 0xe1, 0xee, 0x1c, 0xf7, 0x1e, 0x5a, 0xd7, 0xad, 0x35, 0x37, 0x50, 0x9e, 0xcd, 0x66, 0xa9, 0x59, 0xea, 0x9c, 0x49, 0x7c, 0x87, 0xc7, 0xe3, 0x20, 0xfd, 0x72, 0xcd, 0xa5, 0xbe, 0xda, 0x6d, 0xee, 0x33, 0x0f, 0x9a, 0x87, 0xcd, 0x63, 0xe6, 0x49, 0x67, 0x99, 0x43, 0x72, 0x04, 0xe8, 0x88, 0xcb, 0x11, 0x70, 0x2a, 0x9e, 0x5c, 0xf7, 0x3c, 0x67, 0x1b, 0xea, 0xa5, 0x9d, 0xa4, 0x49, 0xb9, 0xee, 0x61, 0xd4, 0xba, 0x59, 0x8e, 0x66, 0x8e, 0x1c, 0xda, 0xf2, 0x1d, 0xcd, 0x5c, 0xc5, 0x8e, 0x42, 0xe7, 0x64, 0x47, 0x07, 0xa7, 0xe2, 0xe8, 0xed, 0xe8, 0xe2, 0xe8, 0xe6, 0xe8, 0x41, 0xfb, 0x7e, 0x8e, 0x81, 0x8e, 0x21, 0xf4, 0xd8, 0x73, 0x38, 0x86, 0x39, 0x46, 0xba, 0x3a, 0x3b, 0xc6, 0x38, 0x97, 0x3a, 0xc6, 0xbb, 0x25, 0x57, 0x91, 0x63, 0x92, 0x63, 0xaa, 0x63, 0x06, 0xe9, 0xd2, 0x31, 0xc7, 0x6c, 0xc7, 0x3c, 0xa7, 0xc7, 0xbd, 0x9a, 0x5a, 0xb9, 0xc4, 0xb1, 0xd0, 0x35, 0xcb, 0xb1, 0xc4, 0xb1, 0xdc, 0xd9, 0xdf, 0xb1, 0xda, 0x55, 0x4c, 0x7a, 0x94, 0x45, 0xbf, 0xd7, 0x39, 0x36, 0x7a, 0xb2, 0xc5, 0x08, 0x38, 0x93, 0x1c, 0x5b, 0xce, 0x2e, 0xaf, 0x92, 0x6b, 0xdb, 0x39, 0x8f, 0xec, 0xa4, 0x7b, 0xd9, 0xee, 0xd8, 0x43, 0xb4, 0xdf, 0x71, 0x88, 0xc6, 0x57, 0x99, 0xe3, 0x08, 0xe8, 0xb8, 0x45, 0x74, 0x2f, 0xa7, 0xa8, 0x45, 0x0d, 0xa7, 0xa7, 0x9c, 0x3c, 0x05, 0xce, 0x90, 0x33, 0xe4, 0x1e, 0x58, 0x99, 0x68, 0x3c, 0xd6, 0xf0, 0x18, 0x55, 0xa5, 0xe5, 0xe4, 0xcc, 0x76, 0xa6, 0x9f, 0x4e, 0x57, 0x3a, 0xd2, 0x1d, 0x3c, 0xd7, 0x59, 0x90, 0x4c, 0xee, 0x2c, 0xd2, 0xd9, 0xe6, 0x1e, 0x0f, 0x8d, 0x99, 0x73, 0x90, 0xb3, 0x39, 0x6d, 0x6d, 0xaa, 0x50, 0xc7, 0x24, 0x5e, 0x1d, 0xea, 0x48, 0xb4, 0xd5, 0xd9, 0x95, 0xe6, 0x8e, 0x9e, 0xce, 0xfe, 0xce, 0x3e, 0xb4, 0x59, 0x34, 0xc8, 0x39, 0xc8, 0x95, 0x27, 0x38, 0x68, 0x28, 0xcd, 0x28, 0xc3, 0x9d, 0xa3, 0x04, 0x55, 0x5c, 0x7d, 0x6c, 0x52, 0x4d, 0x26, 0x78, 0x6a, 0x54, 0x26, 0xe7, 0x64, 0xe7, 0x34, 0xe7, 0xe4, 0xaa, 0x52, 0x77, 0x7e, 0x55, 0xc9, 0xd9, 0xc9, 0x39, 0xcd, 0xb5, 0x9b, 0xe6, 0xad, 0x39, 0x44, 0x33, 0x69, 0x9b, 0xef, 0x5c, 0xe4, 0x6e, 0x56, 0x95, 0x9c, 0x2b, 0x12, 0xb4, 0xb4, 0x22, 0xb5, 0xc2, 0xb9, 0x89, 0x68, 0x0d, 0xd1, 0x26, 0xe7, 0x7a, 0xa4, 0x05, 0x6d, 0xfd, 0x1d, 0x24, 0xf4, 0x61, 0x57, 0x12, 0xed, 0xb5, 0xa8, 0xe2, 0x9a, 0x07, 0x9c, 0x65, 0x95, 0xea, 0x50, 0x76, 0x9a, 0x12, 0xbf, 0x4f, 0x80, 0x8e, 0x5a, 0x7b, 0xd7, 0xe0, 0xb3, 0x10, 0x3b, 0xab, 0x34, 0x99, 0xec, 0xee, 0xd5, 0x82, 0x68, 0x6e, 0xf6, 0x25, 0x28, 0x62, 0x49, 0x48, 0x46, 0xf3, 0x89, 0xa7, 0xb9, 0x27, 0xfd, 0xb7, 0xc9, 0x35, 0x2b, 0xb1, 0x4f, 0x75, 0x65, 0xba, 0xea, 0x25, 0xd1, 0xac, 0x04, 0xe5, 0xd1, 0xd6, 0xd9, 0xd5, 0x34, 0x31, 0xcb, 0xb7, 0x4c, 0xec, 0xcf, 0x46, 0xed, 0xf0, 0x2c, 0x28, 0xae, 0xa0, 0x5e, 0xf4, 0x3c, 0xe8, 0xeb, 0x3a, 0x0c, 0x2e, 0x68, 0x00, 0xd5, 0x59, 0xcc, 0x74, 0xb3, 0x2a, 0x68, 0x84, 0x27, 0xbb, 0x2a, 0xb9, 0x46, 0x9f, 0x29, 0x4b, 0x3e, 0xe6, 0x1a, 0x5d, 0x35, 0x07, 0xee, 0x77, 0x5c, 0x05, 0x4d, 0xac, 0x44, 0x53, 0xe8, 0x59, 0x74, 0xfa, 0x7a, 0xb3, 0xdc, 0xcb, 0x93, 0x7f, 0x55, 0xd0, 0x5c, 0xd7, 0x82, 0x24, 0x5a, 0xec, 0x5a, 0xec, 0x1e, 0x76, 0x36, 0x72, 0xed, 0x4b, 0xd0, 0x32, 0xd7, 0x2a, 0x7a, 0x9a, 0x6d, 0x70, 0x6d, 0x76, 0x95, 0x9e, 0x41, 0x3b, 0x84, 0x4e, 0xfe, 0x2e, 0xda, 0x77, 0x06, 0x1d, 0xfc, 0x5d, 0x74, 0x18, 0x74, 0x8c, 0xe8, 0x5c, 0xfb, 0x93, 0xae, 0x93, 0x6e, 0xa9, 0x1a, 0xa4, 0xb9, 0x5d, 0xbf, 0x8b, 0x02, 0xee, 0x98, 0x3b, 0x8d, 0x68, 0x60, 0xc5, 0x18, 0xcf, 0x71, 0xe7, 0x57, 0xe8, 0x7d, 0xa1, 0xbb, 0x83, 0xbb, 0x8b, 0xbb, 0x9b, 0xbb, 0x87, 0xbb, 0x37, 0x51, 0xbf, 0xc4, 0x3c, 0x36, 0x84, 0x5a, 0x72, 0xa4, 0x7b, 0x8c, 0x7b, 0xbc, 0x7b, 0x92, 0x7b, 0x6a, 0x05, 0xcd, 0xa8, 0xa0, 0xd9, 0x09, 0x9a, 0x97, 0xa0, 0x85, 0xee, 0x25, 0xee, 0xe5, 0xf4, 0x7c, 0x5e, 0xed, 0x5e, 0xe7, 0xde, 0xe8, 0xde, 0xe2, 0xde, 0xe6, 0xde, 0x49, 0xbf, 0xf6, 0xbb, 0x0f, 0xb9, 0x8f, 0xb8, 0x8f, 0xbb, 0x4f, 0x79, 0x14, 0x0f, 0x3d, 0xe8, 0xe9, 0x81, 0x15, 0xa2, 0x19, 0x21, 0x9d, 0xf4, 0x21, 0xd7, 0x53, 0x40, 0x9a, 0x2f, 0xbc, 0xd1, 0xf6, 0x88, 0x35, 0x91, 0x5c, 0x0b, 0xdc, 0x86, 0xf5, 0x51, 0x3d, 0xa4, 0x83, 0xb6, 0xaf, 0xe8, 0x68, 0x07, 0x48, 0x4a, 0x05, 0x57, 0x5c, 0x90, 0xdf, 0x25, 0xb8, 0x84, 0xd5, 0x99, 0x54, 0x86, 0xa3, 0x5f, 0x40, 0x9e, 0x0a, 0xbe, 0x0a, 0xfc, 0x43, 0xc8, 0x5b, 0x81, 0x2f, 0x06, 0x9f, 0x8a, 0xfc, 0x8b, 0xc0, 0x87, 0x20, 0x4f, 0x47, 0xc8, 0x3f, 0x10, 0x57, 0x91, 0x7a, 0x0a, 0x4e, 0x56, 0xa1, 0x90, 0x6c, 0x44, 0xfa, 0x27, 0xe4, 0xe9, 0x87, 0x3a, 0x84, 0x91, 0x56, 0x90, 0x13, 0xeb, 0x41, 0xe9, 0x79, 0x48, 0x02, 0xa8, 0xd5, 0xf9, 0x48, 0x4f, 0x82, 0xbc, 0x83, 0xc8, 0x23, 0x6f, 0x85, 0x44, 0x83, 0xe4, 0x2d, 0xa4, 0xd3, 0x21, 0xaf, 0x8d, 0x12, 0xbe, 0x04, 0xbf, 0x10, 0xf2, 0x06, 0xe0, 0x69, 0xc8, 0xf9, 0x1e, 0xf8, 0xb5, 0x90, 0xb4, 0x44, 0x4d, 0x82, 0x90, 0xfc, 0x13, 0x7c, 0x07, 0xf8, 0x97, 0xe0, 0x77, 0x80, 0xdf, 0x0a, 0x3e, 0x1d, 0xfc, 0x08, 0xf8, 0x3b, 0xe0, 0x9f, 0xa0, 0x84, 0xcb, 0x90, 0xe6, 0x28, 0xa7, 0x1d, 0x24, 0xe3, 0x90, 0x9e, 0x01, 0x5e, 0x02, 0x8e, 0xfb, 0x62, 0x68, 0x43, 0x79, 0x0c, 0xd2, 0xfb, 0x91, 0xfe, 0x3b, 0x8e, 0xbe, 0x0c, 0xfe, 0x2c, 0xca, 0xf9, 0x01, 0x6d, 0x62, 0x5d, 0xe5, 0x2a, 0xe4, 0xec, 0x87, 0xa3, 0xb8, 0x53, 0x3e, 0x0b, 0x7c, 0x36, 0xf8, 0x05, 0x28, 0x61, 0x3d, 0xd2, 0xa8, 0x03, 0xdb, 0x87, 0xb3, 0x70, 0x2f, 0x0c, 0xd7, 0x95, 0xd0, 0x17, 0xca, 0x72, 0xa4, 0x07, 0x20, 0xbf, 0x0f, 0xe9, 0x2d, 0xe0, 0xd3, 0x20, 0x39, 0x88, 0xf4, 0x20, 0xf0, 0x43, 0x90, 0x5c, 0x8e, 0x36, 0xac, 0x89, 0x92, 0x2d, 0x5e, 0x8c, 0xa3, 0x87, 0xd1, 0x9e, 0x3b, 0x91, 0xbe, 0x04, 0x69, 0xb4, 0xaa, 0x04, 0x1d, 0x90, 0x46, 0x83, 0xa3, 0x34, 0xf9, 0x31, 0x9c, 0xd5, 0x06, 0xfc, 0x3e, 0xd4, 0xc1, 0x6a, 0xe1, 0xb7, 0x71, 0x54, 0x07, 0xb7, 0x7a, 0x61, 0x2f, 0xd2, 0x56, 0x5f, 0x40, 0x67, 0xe4, 0xae, 0x90, 0x0f, 0x46, 0xba, 0x0e, 0x4a, 0x38, 0x06, 0x49, 0x6f, 0xf0, 0x57, 0xc0, 0xa1, 0x51, 0xb2, 0x55, 0x87, 0x1e, 0x48, 0x5b, 0xad, 0x51, 0x08, 0x9e, 0x0b, 0xde, 0x04, 0xbc, 0x1e, 0xf4, 0x0a, 0xe5, 0xc8, 0xd0, 0x52, 0x09, 0x2d, 0x2f, 0xa1, 0x8f, 0xa4, 0xf3, 0x50, 0x3e, 0xea, 0xac, 0x34, 0x86, 0x64, 0x0d, 0x2c, 0x8f, 0x99, 0xc8, 0x6f, 0x0a, 0x6e, 0x4b, 0xc7, 0x51, 0x94, 0x26, 0xcd, 0x85, 0x3c, 0x86, 0x3c, 0xe8, 0x47, 0x09, 0x75, 0x53, 0x6a, 0xa0, 0xcd, 0xbf, 0x43, 0x69, 0x23, 0xc0, 0x91, 0x9f, 0x5f, 0x8e, 0xfc, 0xeb, 0xc0, 0xd1, 0xfe, 0x7c, 0x25, 0xd2, 0x12, 0xce, 0x7d, 0x00, 0x92, 0xa6, 0xe0, 0x2d, 0x20, 0x89, 0x80, 0x67, 0x83, 0x5f, 0x0f, 0xfe, 0x24, 0x8e, 0x62, 0x2c, 0x70, 0xab, 0x8f, 0xe6, 0xa3, 0x04, 0xb4, 0xad, 0xf4, 0x1c, 0xd2, 0xcf, 0xa0, 0x0e, 0xbb, 0x21, 0xc1, 0x15, 0xa5, 0x07, 0xc1, 0x71, 0x17, 0xd2, 0xcf, 0xc8, 0x93, 0x83, 0x74, 0x7f, 0xf0, 0xe3, 0xe0, 0x2f, 0x82, 0xff, 0x15, 0x1c, 0x7d, 0xc4, 0x7f, 0x41, 0xda, 0xba, 0xd3, 0x8f, 0x71, 0x16, 0x83, 0x1c, 0x77, 0xc7, 0x31, 0x6f, 0xf0, 0x05, 0x38, 0xda, 0x3e, 0x31, 0x06, 0x05, 0x5f, 0x0b, 0x0e, 0xcd, 0x94, 0x64, 0x9c, 0x05, 0x89, 0x6c, 0xd5, 0x6d, 0x1e, 0xb8, 0x81, 0x5e, 0x78, 0x09, 0xe9, 0x4c, 0x70, 0xb4, 0xbc, 0xd4, 0x19, 0xfc, 0x1a, 0xf0, 0x2b, 0x50, 0xfe, 0x44, 0xa4, 0x8b, 0xc0, 0xad, 0xd1, 0x6a, 0xe9, 0xde, 0x04, 0x70, 0x6b, 0xac, 0x7d, 0x0e, 0x8e, 0xb1, 0xa3, 0xb8, 0x91, 0x9e, 0x0c, 0x8e, 0x96, 0x97, 0x6e, 0x07, 0xbf, 0x19, 0xfc, 0x6a, 0x94, 0x79, 0x14, 0x57, 0xcc, 0x42, 0x3a, 0x0f, 0x1c, 0x6d, 0xc5, 0x3f, 0x03, 0xef, 0x0b, 0x6e, 0xcd, 0x57, 0x06, 0xd2, 0xd0, 0x1f, 0xfe, 0x03, 0xfa, 0x14, 0x9c, 0x87, 0x90, 0x86, 0x9e, 0xb0, 0xef, 0x91, 0x13, 0x3d, 0xc8, 0xbe, 0x06, 0xff, 0x16, 0x79, 0x1a, 0x82, 0xe3, 0xa8, 0xdc, 0x1c, 0xbc, 0x3e, 0x72, 0x36, 0x83, 0x1c, 0x33, 0x21, 0x5f, 0x0a, 0x8e, 0xfb, 0xe2, 0x37, 0xe2, 0x28, 0xce, 0x92, 0xd4, 0xc4, 0x3c, 0x2c, 0xe4, 0x56, 0xef, 0x58, 0xed, 0x9c, 0x01, 0x79, 0x01, 0x38, 0xb4, 0x42, 0x6e, 0x9c, 0xd0, 0x6a, 0xc1, 0xeb, 0x22, 0x4f, 0x77, 0x9c, 0xb5, 0x01, 0xe9, 0x2e, 0x48, 0x3b, 0xc1, 0x1d, 0x90, 0xe0, 0x5c, 0xa9, 0x5e, 0x62, 0x5e, 0x15, 0x1c, 0x7d, 0x24, 0x61, 0x86, 0x97, 0xa0, 0xc9, 0x0a, 0xee, 0x4e, 0xb2, 0x7a, 0x1f, 0xb3, 0x90, 0x8c, 0xeb, 0x4a, 0xc3, 0xc1, 0x1f, 0x87, 0xc4, 0x6a, 0xff, 0xa7, 0xc1, 0x5f, 0x45, 0xf9, 0x4f, 0x41, 0x9e, 0x82, 0xf4, 0xeb, 0x90, 0xb7, 0x05, 0xc7, 0xa8, 0x91, 0xec, 0xe0, 0xdb, 0x71, 0xf4, 0x5f, 0x48, 0x63, 0x26, 0x91, 0xfa, 0x25, 0x66, 0x18, 0xc1, 0x2f, 0x85, 0xe4, 0x06, 0xf0, 0x7b, 0x90, 0xd3, 0xaa, 0x1b, 0x9e, 0x4d, 0x12, 0x7a, 0x41, 0xca, 0x07, 0xc7, 0x9d, 0x72, 0x8c, 0x2f, 0x19, 0xf3, 0x00, 0xc7, 0x28, 0x90, 0xac, 0xb9, 0x02, 0xcf, 0x05, 0xbe, 0x02, 0xfc, 0x36, 0x48, 0x70, 0xef, 0x1c, 0x35, 0xe7, 0x0f, 0x43, 0xd2, 0x09, 0x1c, 0x4f, 0x34, 0x19, 0x73, 0xb5, 0xb4, 0x2d, 0xf1, 0xbc, 0x10, 0x69, 0x2f, 0x38, 0xe6, 0x10, 0x05, 0x2d, 0x66, 0xc3, 0x13, 0x4a, 0x41, 0x8b, 0x29, 0x18, 0x6b, 0xcc, 0xc1, 0x9a, 0x0a, 0x9d, 0x94, 0x26, 0x51, 0xda, 0x2f, 0xb5, 0xb2, 0xb7, 0xb2, 0x17, 0xda, 0xdb, 0xdb, 0x3b, 0xd8, 0x3b, 0xda, 0x3b, 0xd9, 0x3b, 0xdb, 0x2f, 0xb2, 0x77, 0xb1, 0x5f, 0x65, 0xff, 0x4e, 0xb5, 0xa9, 0xa6, 0xd9, 0xd4, 0x6c, 0x63, 0xb6, 0x25, 0x1b, 0x74, 0x12, 0x93, 0xd8, 0x65, 0x4c, 0x66, 0xe2, 0x9b, 0x37, 0xe7, 0xb3, 0x56, 0x2c, 0x0f, 0x78, 0xf8, 0xf9, 0xc0, 0xc3, 0x9b, 0xb1, 0x8b, 0x59, 0x57, 0x76, 0x01, 0x2b, 0x26, 0x6a, 0x01, 0x54, 0xbc, 0x25, 0xbe, 0x36, 0x50, 0xc8, 0xae, 0x65, 0xfd, 0x59, 0x6b, 0x36, 0x80, 0xdd, 0xc1, 0x2e, 0x64, 0x23, 0xd9, 0x68, 0x76, 0x25, 0xbb, 0x97, 0xa8, 0x37, 0xbb, 0x9f, 0x4d, 0xa6, 0xe3, 0x4f, 0x12, 0xdd, 0xc0, 0x16, 0x13, 0x0d, 0x64, 0x4b, 0x88, 0x6e, 0x64, 0x6f, 0xb1, 0x52, 0x36, 0x88, 0x6d, 0x63, 0x1f, 0xb3, 0xe1, 0xec, 0x13, 0xf6, 0x19, 0x1b, 0xc5, 0x3e, 0x67, 0x5f, 0xd2, 0x19, 0x5f, 0xb1, 0xaf, 0xd9, 0x04, 0x56, 0x46, 0xf4, 0x10, 0xf0, 0xf0, 0x49, 0xdc, 0xcb, 0xf3, 0xd8, 0x22, 0xa0, 0xdf, 0x9b, 0xf1, 0x7d, 0x88, 0x77, 0xa5, 0x86, 0x52, 0x43, 0xf6, 0xbe, 0xd4, 0x48, 0x6a, 0xc4, 0x4a, 0xa5, 0xa6, 0x52, 0x33, 0xf6, 0x81, 0xd4, 0x56, 0x7a, 0x8a, 0x6d, 0x97, 0x16, 0xc9, 0xf7, 0xf2, 0x26, 0xf2, 0x38, 0x79, 0x82, 0x64, 0xca, 0x0f, 0xc9, 0x0f, 0x4b, 0x1e, 0xf9, 0x6f, 0xf2, 0xdf, 0x24, 0xbf, 0xfc, 0x84, 0xfc, 0x84, 0x14, 0x90, 0x9f, 0x94, 0x9f, 0x95, 0x82, 0xf2, 0x73, 0xf2, 0x32, 0x29, 0x55, 0x5e, 0xae, 0xb8, 0xa5, 0x1c, 0x81, 0x0c, 0x4b, 0xe3, 0x95, 0x46, 0x4a, 0xa1, 0xf4, 0x80, 0xd2, 0x46, 0xb9, 0x42, 0x7a, 0x54, 0x20, 0xc0, 0xf4, 0xec, 0x0e, 0xd8, 0x02, 0xb4, 0x12, 0xa8, 0x61, 0xcb, 0x90, 0x5e, 0xb0, 0x5d, 0x64, 0xbb, 0x88, 0x9e, 0xb0, 0x2f, 0xd9, 0x96, 0x49, 0xcb, 0x05, 0xf6, 0x2b, 0xad, 0x12, 0xd8, 0x2f, 0xcd, 0xe7, 0x9f, 0xda, 0xbe, 0x94, 0x5e, 0xb3, 0x7d, 0x65, 0x2b, 0x93, 0x36, 0xd9, 0x7e, 0xb0, 0x9d, 0x90, 0xb6, 0xda, 0xe2, 0x76, 0x97, 0xb4, 0xdd, 0xee, 0xb7, 0x07, 0xa4, 0xfd, 0xf6, 0x74, 0x7b, 0x96, 0x74, 0xc0, 0x5e, 0x66, 0x2f, 0x93, 0xbe, 0x11, 0x78, 0xaf, 0x54, 0xa6, 0xd6, 0xd7, 0xbe, 0x91, 0xbe, 0x15, 0xf1, 0xbf, 0xca, 0x52, 0xdd, 0xd4, 0x1d, 0xca, 0x4a, 0xdd, 0xa3, 0xa7, 0x2b, 0xaf, 0x08, 0x3c, 0x56, 0xd9, 0x22, 0xf0, 0x58, 0xe5, 0x7d, 0xbd, 0x9e, 0x5e, 0x4f, 0x29, 0x15, 0xa8, 0xac, 0xf2, 0x81, 0xde, 0x58, 0xef, 0xa4, 0x7c, 0xac, 0x5f, 0xa4, 0x3f, 0xa9, 0x94, 0x89, 0xc8, 0x5c, 0x5b, 0x7b, 0xfd, 0x2d, 0xe3, 0x43, 0xdb, 0xc5, 0xc6, 0x47, 0xc6, 0x2e, 0x7b, 0x2f, 0xf1, 0xf5, 0x0b, 0x7b, 0x5f, 0x81, 0x46, 0xda, 0x87, 0x09, 0x1c, 0xd2, 0x7e, 0xbb, 0xc0, 0x21, 0xed, 0xc3, 0x05, 0xea, 0x68, 0xbf, 0x43, 0xa0, 0x8e, 0xf6, 0x11, 0x26, 0x91, 0xfd, 0x4e, 0xf3, 0x61, 0xf3, 0x31, 0xfb, 0x48, 0xf3, 0xef, 0xe6, 0x42, 0xfb, 0xfd, 0xe6, 0x0b, 0xe6, 0x8b, 0xf6, 0x07, 0xcc, 0x15, 0xe6, 0xab, 0xf6, 0x89, 0xe6, 0x46, 0xf3, 0x5d, 0xfb, 0x14, 0xe6, 0xa7, 0x3e, 0x16, 0xdf, 0x4e, 0xee, 0x55, 0x8d, 0xad, 0xb8, 0xca, 0x5e, 0x78, 0xab, 0x0d, 0xa8, 0xb4, 0x71, 0x7c, 0x53, 0x84, 0xc7, 0xfd, 0x82, 0x9f, 0x1a, 0x2a, 0xb8, 0x94, 0x27, 0x38, 0xbf, 0x3f, 0xbe, 0xd2, 0x3e, 0x3d, 0x38, 0x23, 0xb4, 0xdd, 0x99, 0x1b, 0x5c, 0x1e, 0xdc, 0x16, 0xcc, 0x77, 0x16, 0x04, 0x0f, 0x05, 0x8f, 0x87, 0x8c, 0xd0, 0x1c, 0xf7, 0xc6, 0xe8, 0x09, 0x67, 0xf3, 0xd0, 0x1c, 0x67, 0x9b, 0x50, 0x6e, 0xa8, 0x20, 0x36, 0xda, 0x5b, 0x9c, 0xd2, 0x21, 0xd4, 0x27, 0x16, 0x09, 0x0d, 0x0a, 0x8d, 0x22, 0x6b, 0xb5, 0x6b, 0x68, 0x0e, 0x1d, 0xeb, 0x1e, 0x4b, 0x8d, 0x2c, 0xf4, 0x2e, 0x0e, 0x2d, 0x75, 0x15, 0x87, 0x36, 0x85, 0xd6, 0x07, 0xe7, 0x39, 0x7b, 0x86, 0x0e, 0xc4, 0x32, 0xc3, 0x2c, 0xec, 0x0b, 0x0f, 0x0e, 0x37, 0x8d, 0x1d, 0x0b, 0x77, 0x0e, 0xe6, 0x84, 0xfb, 0x86, 0x8b, 0x29, 0x47, 0xaf, 0xf0, 0x80, 0xf0, 0x88, 0xd0, 0x34, 0x8f, 0xb0, 0x6f, 0x85, 0x85, 0x3b, 0xc8, 0xdf, 0x91, 0x2c, 0xda, 0xad, 0xb1, 0xd1, 0xb1, 0xd1, 0xe1, 0x59, 0xe1, 0x05, 0xe1, 0xb5, 0xd8, 0xd6, 0xc6, 0x1c, 0x31, 0x87, 0x73, 0x78, 0x8a, 0x14, 0x3e, 0x19, 0x1b, 0xed, 0x3f, 0x10, 0x49, 0x4b, 0x09, 0x38, 0x47, 0x45, 0x9a, 0x45, 0x9a, 0x05, 0x0e, 0x3a, 0xc7, 0x46, 0x68, 0x9d, 0x1b, 0xf2, 0x84, 0x0f, 0x3b, 0x27, 0x44, 0xc6, 0xc0, 0xb6, 0x9d, 0x19, 0xee, 0x1b, 0x38, 0x18, 0x59, 0xe8, 0x3e, 0x15, 0xd9, 0x18, 0xd9, 0x16, 0x5d, 0x2a, 0xac, 0xd4, 0x68, 0xf7, 0xe0, 0xd4, 0xe0, 0x54, 0xaa, 0xdb, 0xa2, 0x94, 0x61, 0xd1, 0x51, 0xce, 0xa5, 0xb1, 0x08, 0x95, 0xb7, 0x22, 0x3a, 0xd6, 0xb9, 0x26, 0x38, 0xc3, 0xbf, 0xc6, 0xb9, 0x3e, 0x7a, 0x40, 0x58, 0xa3, 0x91, 0x53, 0xce, 0xed, 0xd1, 0xf5, 0xb1, 0xd1, 0xce, 0x5d, 0x91, 0x8d, 0xc0, 0xe0, 0xca, 0x62, 0xc7, 0x5c, 0x13, 0x43, 0xf3, 0x3d, 0xa1, 0x68, 0x99, 0xf3, 0x68, 0x94, 0x2c, 0xc7, 0xd8, 0x82, 0x98, 0x5d, 0xdc, 0xa3, 0x8b, 0xc5, 0x7c, 0x29, 0x81, 0x58, 0x44, 0x20, 0x27, 0xfe, 0xfe, 0x2e, 0x5f, 0xca, 0x30, 0x57, 0xc4, 0x95, 0x1a, 0x9c, 0x0d, 0x7b, 0x2e, 0x2f, 0xe6, 0xf3, 0x34, 0x77, 0x35, 0xf5, 0x0d, 0x8c, 0xe5, 0xc5, 0xec, 0x91, 0xb4, 0xe0, 0x36, 0x57, 0x4b, 0xef, 0xb2, 0x72, 0x4b, 0x2d, 0xd6, 0x2e, 0xbc, 0x2c, 0xbc, 0x2c, 0xb6, 0x23, 0x56, 0x14, 0x2b, 0x8e, 0x0d, 0x70, 0xf5, 0x72, 0xcf, 0x70, 0xf5, 0x8d, 0x8d, 0x88, 0x8d, 0x8e, 0xce, 0x4c, 0x19, 0x1f, 0x1b, 0xed, 0x1b, 0xe9, 0x29, 0x73, 0x0d, 0x88, 0xed, 0xf3, 0x1f, 0x48, 0x19, 0x16, 0xd9, 0x06, 0xdb, 0x6d, 0x44, 0xac, 0xd8, 0x35, 0x3a, 0xdc, 0xd9, 0x35, 0xce, 0xb3, 0xd4, 0x35, 0x31, 0xb6, 0x9b, 0xf2, 0x4f, 0xf1, 0xb4, 0x09, 0xac, 0x75, 0x4d, 0x8f, 0x1d, 0x8e, 0x1d, 0x8b, 0x9d, 0x4c, 0x91, 0x52, 0x34, 0xd7, 0xac, 0xc0, 0xdc, 0xd8, 0xe8, 0x94, 0xa9, 0xae, 0xb9, 0xd4, 0x4b, 0x6b, 0xfc, 0xd9, 0xfe, 0xa1, 0xa1, 0xec, 0x80, 0x65, 0x55, 0x91, 0xd5, 0x14, 0x1a, 0x44, 0xb5, 0x5b, 0xeb, 0x9d, 0x15, 0x59, 0x1e, 0x99, 0xe7, 0xda, 0x10, 0x5d, 0x14, 0x9d, 0xe6, 0xda, 0x1c, 0x3a, 0x9a, 0xd2, 0x23, 0x25, 0xe0, 0x2a, 0x4d, 0x09, 0x50, 0xbe, 0x1d, 0xb0, 0x87, 0x0e, 0xba, 0xb5, 0x40, 0xaa, 0x3b, 0x4d, 0x58, 0x2f, 0xa1, 0xf5, 0xb0, 0x5d, 0x34, 0xaa, 0xd1, 0x80, 0xd0, 0x1c, 0xcf, 0x9a, 0x60, 0x1a, 0xb5, 0xc1, 0x89, 0x70, 0xdf, 0x98, 0xc3, 0xb2, 0x3f, 0x62, 0x0e, 0xcf, 0x28, 0x77, 0x5a, 0xa4, 0x99, 0xb7, 0x28, 0xdc, 0xae, 0xc2, 0xfe, 0x10, 0x96, 0xc7, 0x6c, 0xff, 0x2e, 0x8f, 0x11, 0x38, 0xe8, 0xe9, 0x49, 0xb6, 0x46, 0x17, 0xff, 0x81, 0x84, 0x1d, 0xd2, 0x2f, 0xbc, 0x21, 0xd6, 0x39, 0x70, 0xd0, 0x3d, 0x30, 0x58, 0x18, 0x8e, 0x08, 0x5b, 0x24, 0xb6, 0xcf, 0xb2, 0x46, 0xc8, 0x7a, 0xd8, 0x4b, 0x16, 0xc9, 0xec, 0xd0, 0xd2, 0x94, 0x1e, 0xc1, 0xb4, 0x94, 0x34, 0xf7, 0xd4, 0xc0, 0xc1, 0x70, 0x5f, 0xd8, 0x23, 0xc2, 0x06, 0x21, 0x2b, 0xc4, 0xbf, 0xd7, 0x33, 0x93, 0x2c, 0x91, 0xd5, 0x81, 0x83, 0xa2, 0xdf, 0x53, 0xb2, 0xc2, 0x9b, 0x85, 0x45, 0x12, 0x68, 0x17, 0x3e, 0xe9, 0x3f, 0xe0, 0x3f, 0x10, 0x1b, 0x1d, 0x49, 0x13, 0xd6, 0x89, 0xb7, 0xa5, 0xb7, 0xa5, 0x7b, 0xa7, 0x67, 0xb8, 0x67, 0x78, 0x74, 0x28, 0x69, 0xc3, 0x9e, 0x14, 0xcd, 0x7f, 0xc2, 0x33, 0x9c, 0xf2, 0x92, 0xc5, 0xe2, 0xcb, 0x11, 0x36, 0x4b, 0xa0, 0x6f, 0x6c, 0x9c, 0xfb, 0x94, 0xbf, 0xab, 0x67, 0xbe, 0x47, 0x89, 0x2e, 0xf5, 0x18, 0xa1, 0x45, 0xe5, 0xd6, 0x8b, 0xaf, 0x5f, 0x4a, 0x07, 0xcf, 0x4c, 0x4f, 0xb6, 0x77, 0xb1, 0x37, 0x4f, 0xd8, 0x31, 0xa1, 0xe1, 0xc2, 0x92, 0xf1, 0x74, 0xf4, 0x74, 0xf5, 0x74, 0x0f, 0x2e, 0x0c, 0x1e, 0xf1, 0xf4, 0xf4, 0xf4, 0xf1, 0xf4, 0x09, 0xef, 0xf0, 0xf4, 0x8f, 0x4e, 0xf3, 0x0c, 0xf2, 0x16, 0x87, 0x3b, 0x87, 0xeb, 0xa5, 0xf4, 0xf3, 0x0c, 0xa5, 0xf2, 0x17, 0x78, 0x46, 0x79, 0xc6, 0x7a, 0x26, 0x78, 0x26, 0x7b, 0xa6, 0x79, 0x66, 0x86, 0x46, 0x79, 0xe6, 0x78, 0xe6, 0x7b, 0x67, 0x79, 0x16, 0xa5, 0x74, 0x49, 0x49, 0x8b, 0x2c, 0x0c, 0xba, 0xa8, 0xcd, 0x96, 0x46, 0x67, 0x7a, 0x56, 0x78, 0xd6, 0x78, 0xd6, 0x7b, 0x36, 0x79, 0xb6, 0x7a, 0xb6, 0x7b, 0x76, 0x79, 0xf6, 0x7a, 0xca, 0x62, 0xa3, 0x3d, 0x07, 0x3c, 0x65, 0x9e, 0xa3, 0x9e, 0x13, 0x5e, 0xe6, 0xb5, 0xa7, 0x34, 0xf3, 0x3a, 0xbc, 0x0e, 0xdf, 0x46, 0x6f, 0x3d, 0xef, 0x66, 0xaf, 0xcf, 0x1b, 0xf1, 0xa6, 0x7a, 0x33, 0x29, 0x9d, 0x17, 0x28, 0xf6, 0x36, 0xa5, 0xfb, 0x6a, 0xe7, 0xed, 0xec, 0x2d, 0xf2, 0x16, 0x47, 0x0e, 0x45, 0xbb, 0x07, 0x8a, 0x23, 0xa7, 0xbc, 0x1b, 0xbc, 0xbd, 0x7c, 0x52, 0xb4, 0xbb, 0xb7, 0xaf, 0x77, 0x40, 0x68, 0xbe, 0x77, 0x70, 0xa0, 0x5d, 0x4a, 0xa1, 0xb7, 0x84, 0xe4, 0x23, 0x62, 0x9d, 0xbd, 0xa3, 0xbd, 0xe3, 0xbc, 0x13, 0xbd, 0x53, 0xbc, 0xd3, 0xbd, 0xb3, 0xbc, 0x73, 0xbd, 0x0b, 0xe8, 0x8e, 0x96, 0xf9, 0x07, 0x79, 0x57, 0x05, 0x0e, 0x53, 0xfe, 0xb5, 0xd1, 0xe6, 0x74, 0xee, 0x66, 0x4a, 0x95, 0x7a, 0x0f, 0x7a, 0x77, 0x78, 0x77, 0x7b, 0xf7, 0xd1, 0xfe, 0xb0, 0xf7, 0x98, 0xf7, 0x64, 0xa0, 0x5d, 0xb4, 0x39, 0xd1, 0x8a, 0x70, 0x71, 0xac, 0x73, 0xa4, 0x9f, 0x4f, 0xf2, 0x69, 0x3e, 0x97, 0x2f, 0xe0, 0x8b, 0xf9, 0xd2, 0xa2, 0x3d, 0x7d, 0x59, 0xbe, 0x1c, 0x5f, 0xbe, 0xaf, 0x99, 0xaf, 0x30, 0xda, 0xc6, 0xd7, 0xc1, 0xd7, 0x25, 0xb6, 0x20, 0x6a, 0x44, 0x8d, 0x94, 0xde, 0x29, 0xbd, 0x7d, 0xdd, 0x7c, 0x3d, 0x82, 0x93, 0x7c, 0xbd, 0xa3, 0x83, 0xfc, 0x34, 0x1e, 0x7c, 0xfd, 0x62, 0x23, 0x7c, 0x03, 0x7d, 0x43, 0x7c, 0xc3, 0x52, 0xc6, 0xfb, 0x46, 0xfa, 0xc6, 0xf8, 0xc6, 0xfb, 0x26, 0xf9, 0xa6, 0x12, 0xcd, 0xf0, 0xcd, 0x8e, 0x64, 0xf9, 0x87, 0xfb, 0xe6, 0xf9, 0x16, 0xfa, 0x96, 0xc4, 0x8e, 0xf9, 0x96, 0xfb, 0x56, 0xfb, 0xd6, 0xf9, 0x36, 0x86, 0xb6, 0xfb, 0xb6, 0xf8, 0xb6, 0xf9, 0x76, 0x46, 0xa4, 0x94, 0x85, 0x29, 0x03, 0x63, 0x11, 0xdf, 0x1e, 0xdf, 0x7e, 0xdf, 0xa1, 0xa0, 0xe6, 0x3b, 0xe2, 0x3b, 0xee, 0x3b, 0x15, 0xf3, 0xf9, 0x15, 0xbf, 0xe1, 0xf7, 0xf8, 0x43, 0x01, 0x5f, 0x68, 0x90, 0xbf, 0x86, 0x3f, 0x3d, 0x34, 0xdc, 0x9f, 0x9d, 0x12, 0x48, 0x19, 0xe6, 0xcf, 0xf5, 0x17, 0xf8, 0x9b, 0xfb, 0xdb, 0xf8, 0x3b, 0xfa, 0xbb, 0xfa, 0xbb, 0xfb, 0x7b, 0xfa, 0xfb, 0xf8, 0xfb, 0x47, 0xb6, 0xf9, 0x07, 0xf9, 0x87, 0xfa, 0x87, 0xfb, 0x47, 0xf9, 0xc7, 0x86, 0x72, 0xfd, 0x13, 0xfc, 0x93, 0xfd, 0xd3, 0xfc, 0x33, 0xfd, 0x73, 0xfc, 0xf3, 0x63, 0xfb, 0x48, 0x6b, 0x16, 0xf9, 0x97, 0xfa, 0x57, 0x04, 0xc6, 0xf9, 0xd7, 0x44, 0x66, 0xa4, 0x0c, 0xf4, 0xaf, 0xf7, 0x6f, 0xf2, 0x6f, 0xf5, 0x6f, 0x27, 0xda, 0xe5, 0xdf, 0x4b, 0x33, 0x49, 0xc0, 0x5f, 0xe6, 0x3f, 0x1a, 0x2e, 0xf6, 0x9f, 0xa0, 0x71, 0x3a, 0x2c, 0xc0, 0x02, 0xf6, 0x98, 0x23, 0x3a, 0x21, 0xe0, 0x08, 0xf8, 0x02, 0x91, 0xe8, 0x89, 0xd0, 0xd2, 0x40, 0x6a, 0x74, 0x69, 0x20, 0x33, 0xb8, 0x3c, 0x50, 0x2f, 0x90, 0x17, 0x68, 0x1a, 0x68, 0x19, 0x5c, 0x18, 0xda, 0x14, 0x1e, 0x10, 0x68, 0x17, 0xe8, 0x1c, 0x28, 0x0a, 0x14, 0xc7, 0x46, 0x07, 0x7a, 0x05, 0xfa, 0xa6, 0x4c, 0x0a, 0x0c, 0x08, 0x0c, 0x0e, 0x94, 0x04, 0x46, 0x04, 0x46, 0x07, 0xc6, 0x05, 0x26, 0x86, 0x46, 0x05, 0xa6, 0x04, 0xa6, 0x07, 0x68, 0x4c, 0x86, 0x8b, 0x03, 0x0b, 0x82, 0xab, 0x03, 0x8b, 0x03, 0xcb, 0x02, 0xab, 0x02, 0x6b, 0x03, 0x1b, 0x02, 0x87, 0x03, 0x9b, 0x03, 0xa5, 0xd1, 0x82, 0xc0, 0x8e, 0xc0, 0xee, 0xc0, 0x3e, 0xba, 0xde, 0x41, 0x2a, 0xeb, 0x70, 0xe0, 0x98, 0x98, 0x9b, 0x82, 0x53, 0x03, 0x27, 0x83, 0xcd, 0xc2, 0xa5, 0x91, 0x91, 0x41, 0x29, 0xa8, 0x85, 0xc6, 0x46, 0xb7, 0x07, 0x5d, 0x91, 0xa9, 0x91, 0xac, 0x60, 0x00, 0x14, 0x0b, 0xee, 0x8f, 0xe4, 0x07, 0xd3, 0xc2, 0x7d, 0x83, 0xdd, 0x22, 0xf3, 0x82, 0x59, 0xc1, 0x9c, 0x94, 0x40, 0x30, 0x3f, 0xdc, 0x32, 0xdc, 0x32, 0xd4, 0x3c, 0xd8, 0x2c, 0x58, 0x18, 0xec, 0x10, 0xec, 0x12, 0x69, 0x16, 0x5d, 0x1f, 0x1d, 0x1a, 0xec, 0x16, 0xec, 0x11, 0xec, 0x1d, 0xec, 0x17, 0x1c, 0x18, 0x1c, 0x12, 0x1c, 0x16, 0x55, 0x82, 0x23, 0x83, 0x63, 0x30, 0xf7, 0x0d, 0x8a, 0x74, 0x89, 0xcc, 0x08, 0x8e, 0x0f, 0x4e, 0x0a, 0x4e, 0x4d, 0x19, 0x1f, 0xda, 0x5a, 0x95, 0x68, 0xbe, 0xdf, 0x1a, 0x9c, 0x1d, 0xda, 0x9e, 0x24, 0xdb, 0x7e, 0x96, 0x94, 0xc8, 0x39, 0xef, 0xcc, 0xb3, 0x2b, 0xf2, 0x55, 0x21, 0x1a, 0x41, 0x0b, 0x89, 0x2f, 0x09, 0x2e, 0x89, 0x6d, 0xa0, 0x67, 0xc9, 0xf2, 0x30, 0x0b, 0xae, 0x0e, 0xae, 0x23, 0xda, 0x98, 0xa0, 0x6d, 0xc1, 0x2d, 0xb4, 0xed, 0x0c, 0xee, 0x21, 0xda, 0x2f, 0x28, 0x65, 0x46, 0xca, 0x3c, 0x7a, 0xda, 0x1c, 0x8a, 0x0d, 0xae, 0x4c, 0xc1, 0xe3, 0xc1, 0x23, 0xc1, 0xe3, 0x22, 0x15, 0x52, 0x92, 0xe5, 0xd6, 0xaf, 0x90, 0x11, 0x3c, 0x65, 0xfd, 0x22, 0x32, 0x2a, 0x1d, 0xf7, 0x60, 0x3f, 0x3a, 0x54, 0x03, 0x14, 0xb2, 0xf6, 0x31, 0xd1, 0xbb, 0xe9, 0xd1, 0x13, 0xf4, 0x1c, 0xa0, 0x67, 0x97, 0xb5, 0x55, 0xa6, 0x50, 0x3a, 0x9e, 0x6a, 0x55, 0x69, 0xce, 0xaf, 0x50, 0x76, 0x95, 0xfd, 0xb9, 0x68, 0x53, 0xb4, 0x0f, 0x3d, 0x3d, 0x73, 0x43, 0x6d, 0x42, 0x05, 0xa1, 0xe6, 0x44, 0x6d, 0x42, 0x6d, 0xe8, 0x29, 0xda, 0x35, 0xd4, 0x91, 0x36, 0x8b, 0xba, 0x87, 0x7a, 0x86, 0xfa, 0x24, 0xa8, 0x7f, 0xa2, 0x3e, 0x83, 0xca, 0xf7, 0xa0, 0xa1, 0xa1, 0xe1, 0x29, 0xc3, 0x2a, 0x53, 0x68, 0x54, 0x68, 0x32, 0x6d, 0x63, 0x93, 0x24, 0x94, 0x0e, 0x4d, 0xa8, 0x9a, 0xef, 0xec, 0x44, 0xe7, 0x8a, 0xba, 0xcd, 0x24, 0x9a, 0x46, 0x1b, 0xa5, 0x63, 0x99, 0xb1, 0xcc, 0x50, 0x99, 0xe0, 0x62, 0x0f, 0x9a, 0x0f, 0x5a, 0x14, 0x59, 0x28, 0xb8, 0x95, 0x0e, 0xad, 0x09, 0xef, 0x0b, 0x2d, 0x0d, 0x2d, 0x8d, 0x2e, 0x0a, 0xad, 0x10, 0x69, 0x41, 0xf4, 0xd4, 0xaf, 0x4a, 0xeb, 0xcf, 0x22, 0xb1, 0x64, 0x42, 0x6b, 0x76, 0x55, 0xd0, 0xde, 0xd0, 0x01, 0x8b, 0xac, 0xeb, 0xe2, 0xda, 0x47, 0x4f, 0xd7, 0x43, 0xfc, 0x3a, 0x4d, 0xf8, 0x7d, 0x22, 0x6c, 0x07, 0x31, 0xda, 0x1c, 0x44, 0xf5, 0xce, 0x20, 0x5f, 0xf8, 0x18, 0xf6, 0x11, 0xf0, 0x63, 0x15, 0xf2, 0xd3, 0xa9, 0xd4, 0x70, 0x66, 0x92, 0x34, 0x8f, 0xa8, 0x69, 0xb8, 0x38, 0x92, 0x1f, 0xc9, 0x8f, 0xad, 0x12, 0x63, 0x0c, 0xd4, 0x39, 0xdc, 0x8e, 0xb6, 0x22, 0x4b, 0x1e, 0xc9, 0x0f, 0xf7, 0x0a, 0xf7, 0x4d, 0x99, 0x9d, 0x32, 0xa6, 0x32, 0x85, 0x07, 0x80, 0x97, 0x54, 0x92, 0x95, 0x84, 0x47, 0x84, 0x07, 0x27, 0x52, 0x94, 0x4e, 0x48, 0x47, 0x87, 0xc7, 0x85, 0x27, 0x56, 0xa2, 0x29, 0xe1, 0xe9, 0x91, 0x79, 0x44, 0x33, 0x22, 0xf3, 0xa2, 0x7b, 0x23, 0x33, 0x68, 0xfd, 0x33, 0x97, 0x56, 0x3f, 0xa5, 0x44, 0x8b, 0xc3, 0xab, 0xce, 0x4a, 0xcb, 0xc0, 0xd7, 0x86, 0x37, 0x84, 0x37, 0x53, 0xae, 0xdd, 0xe1, 0x1d, 0x44, 0xbb, 0x89, 0xf6, 0xa5, 0x74, 0xa0, 0xb5, 0xd2, 0xc1, 0xe8, 0xd6, 0xf0, 0xc1, 0x04, 0x1d, 0xa6, 0xfb, 0x3a, 0x19, 0x91, 0x88, 0xb4, 0x88, 0x0b, 0x57, 0xc1, 0x75, 0xc0, 0x05, 0x05, 0x22, 0xb1, 0x94, 0xf1, 0x95, 0x89, 0x56, 0x59, 0xb4, 0x45, 0xb2, 0x88, 0xe7, 0x24, 0x24, 0x59, 0xd6, 0x56, 0x25, 0x5f, 0x8e, 0xd8, 0xca, 0xf3, 0x54, 0x48, 0x9b, 0xa1, 0x9d, 0x9a, 0x45, 0x0a, 0x63, 0xab, 0x22, 0x3d, 0x88, 0xba, 0x44, 0x3a, 0x80, 0x77, 0x89, 0x74, 0xc3, 0xef, 0x7e, 0xa0, 0x81, 0x91, 0xde, 0xe0, 0x56, 0x7a, 0x48, 0xd4, 0x13, 0x19, 0x16, 0x19, 0x19, 0x19, 0x13, 0x19, 0x0f, 0x9a, 0x94, 0xa8, 0x69, 0x39, 0x4d, 0x4d, 0xd4, 0x79, 0x5e, 0xc5, 0x1d, 0xcc, 0x48, 0x48, 0x66, 0x83, 0x2f, 0x8c, 0x2c, 0xa1, 0x55, 0xce, 0xba, 0x0a, 0x5a, 0x4d, 0xdb, 0x46, 0xd0, 0x96, 0xc8, 0xce, 0x04, 0x6d, 0xab, 0x48, 0x25, 0x51, 0x74, 0xa9, 0x45, 0x91, 0x3d, 0x44, 0xfb, 0x23, 0x87, 0x22, 0x47, 0xa2, 0xdd, 0x23, 0xc7, 0x69, 0x3b, 0x15, 0x55, 0xe8, 0x89, 0xe7, 0x21, 0x0a, 0xc5, 0x66, 0x45, 0x6b, 0x44, 0xd3, 0xa3, 0xd9, 0xd1, 0x5c, 0x7a, 0x5a, 0x16, 0xd0, 0xd6, 0x26, 0xda, 0x31, 0xda, 0x35, 0xda, 0x3d, 0xda, 0x33, 0xda, 0x27, 0xda, 0x3f, 0x3a, 0x28, 0x3a, 0x34, 0x3a, 0x3c, 0x3a, 0x8a, 0x9e, 0x1f, 0x63, 0xa3, 0x13, 0xa2, 0x93, 0xa3, 0xd3, 0xce, 0xa0, 0x99, 0xd1, 0x39, 0x67, 0xd0, 0xfc, 0x5f, 0xa1, 0x45, 0xe7, 0xa0, 0xa5, 0x67, 0xd0, 0x9a, 0xe8, 0x0a, 0xda, 0x7e, 0x0f, 0x6d, 0x25, 0x5a, 0x1f, 0xdd, 0x44, 0x7c, 0x3b, 0xd1, 0xd6, 0x73, 0xec, 0xb7, 0x47, 0x77, 0x45, 0xf7, 0x12, 0x1d, 0xa8, 0x16, 0x95, 0x45, 0x8f, 0x46, 0x4f, 0x9c, 0x9d, 0x62, 0xec, 0x1c, 0x44, 0xcf, 0xdb, 0x18, 0x2d, 0x35, 0x68, 0x86, 0x2e, 0x9f, 0x79, 0x53, 0x53, 0x86, 0x55, 0x8c, 0xfd, 0x7a, 0xb4, 0x82, 0x6e, 0x1a, 0x6b, 0x19, 0x6b, 0x17, 0xeb, 0x4c, 0xeb, 0x65, 0xb1, 0x62, 0xee, 0x15, 0xeb, 0x8b, 0xf9, 0x7c, 0x00, 0x78, 0x89, 0x58, 0x39, 0xc7, 0xc6, 0xc5, 0x26, 0xc6, 0xa6, 0xc4, 0xa6, 0xc7, 0x66, 0xd1, 0xea, 0x7c, 0x2e, 0x6d, 0x8b, 0x63, 0xcb, 0x62, 0xab, 0x62, 0x6b, 0x2d, 0xa2, 0xd5, 0xc5, 0x86, 0x04, 0x6d, 0x8e, 0x95, 0x26, 0x68, 0x47, 0x6c, 0x77, 0x82, 0xf6, 0xc5, 0x0e, 0x9e, 0x5e, 0x47, 0xa7, 0xb8, 0x68, 0x8d, 0x11, 0x4b, 0x49, 0x4b, 0xc9, 0x4a, 0xc9, 0x49, 0xc9, 0x4f, 0x69, 0x96, 0x52, 0x48, 0xe3, 0xa8, 0x4b, 0x4a, 0x37, 0x5a, 0x2b, 0xf7, 0x4e, 0xe9, 0x47, 0x34, 0x30, 0x65, 0x08, 0xcd, 0x95, 0x23, 0x69, 0xfc, 0x8e, 0x4f, 0x99, 0x94, 0x32, 0x95, 0x9e, 0x55, 0xb3, 0x53, 0xe6, 0xa5, 0x2c, 0x14, 0x08, 0x2d, 0xec, 0xa7, 0x17, 0xe3, 0xcf, 0x0a, 0xdc, 0x1b, 0x56, 0x54, 0xcd, 0x53, 0x57, 0x93, 0xe4, 0x53, 0x91, 0x56, 0x26, 0x09, 0x2e, 0x77, 0x10, 0x12, 0xc5, 0x83, 0xf4, 0x54, 0xc1, 0xed, 0x48, 0xdb, 0xdd, 0x90, 0xbc, 0x88, 0xb3, 0x5a, 0x21, 0x7d, 0xb5, 0xe0, 0xec, 0x1b, 0x48, 0xd6, 0x81, 0xbf, 0x02, 0xf9, 0x61, 0xe4, 0xbf, 0x01, 0x92, 0x5b, 0xc0, 0x51, 0x8e, 0xf4, 0x3c, 0xf8, 0x10, 0xe4, 0xe9, 0x08, 0xde, 0x00, 0x92, 0x46, 0xe2, 0x8a, 0x7c, 0x31, 0x72, 0xfe, 0x88, 0xfa, 0x5c, 0x09, 0xf9, 0xfd, 0x90, 0x7f, 0x09, 0xc9, 0xf5, 0x38, 0xfa, 0x33, 0x24, 0xbb, 0x05, 0x97, 0x39, 0x4a, 0xb0, 0xe3, 0xe8, 0x07, 0xc8, 0xff, 0x28, 0xee, 0xa2, 0x21, 0xd2, 0x7b, 0x91, 0xbf, 0x14, 0x79, 0x02, 0xe0, 0xcd, 0x90, 0x73, 0x2f, 0xee, 0xee, 0x61, 0x94, 0xb0, 0x0c, 0xf2, 0xee, 0xe0, 0x33, 0xc0, 0x9b, 0xe2, 0xdc, 0xbe, 0xe0, 0xb5, 0x21, 0x81, 0xdd, 0x29, 0x6d, 0x44, 0xfe, 0x71, 0x28, 0x01, 0xf5, 0x94, 0x3e, 0x42, 0xf9, 0x9f, 0x21, 0x6d, 0xf1, 0xf3, 0x90, 0xa7, 0x0e, 0xd2, 0x0e, 0x9c, 0xfb, 0x37, 0xf0, 0x25, 0x90, 0x3c, 0x83, 0xfc, 0x7f, 0x45, 0xfa, 0x5d, 0xd4, 0x73, 0x14, 0x78, 0x4f, 0x94, 0x89, 0x16, 0x93, 0xba, 0x59, 0x77, 0x8d, 0xb3, 0x0e, 0x21, 0xff, 0x22, 0x1c, 0xdd, 0x07, 0x3e, 0x16, 0x47, 0xad, 0xd2, 0xde, 0x06, 0x7f, 0x16, 0x57, 0xb4, 0xe1, 0x68, 0x18, 0x2d, 0x13, 0x83, 0x24, 0x07, 0xe7, 0x3e, 0x82, 0xf4, 0x55, 0x90, 0xaf, 0xb0, 0xda, 0x0a, 0xf2, 0xe9, 0xe8, 0xb5, 0x05, 0x90, 0x97, 0x82, 0xa3, 0x5f, 0x58, 0x19, 0xd2, 0x0d, 0x71, 0xd6, 0x20, 0xe4, 0xbf, 0x09, 0x12, 0xeb, 0xde, 0x47, 0xe2, 0xdc, 0xf7, 0x70, 0xdd, 0x20, 0xae, 0xd8, 0x07, 0x47, 0x9f, 0x41, 0x5a, 0x81, 0xfc, 0x80, 0x65, 0x9d, 0x43, 0x92, 0x8a, 0xfc, 0xa7, 0xc0, 0x7f, 0x02, 0x7f, 0x01, 0x47, 0x07, 0x22, 0x8d, 0x6b, 0x49, 0xd0, 0x40, 0xe9, 0x56, 0x94, 0xdf, 0x09, 0x57, 0x3c, 0x1f, 0x92, 0x93, 0xc8, 0x73, 0x19, 0xca, 0xff, 0x0e, 0xe9, 0x8b, 0x90, 0xfe, 0x3b, 0x8e, 0x8e, 0x07, 0xef, 0x81, 0xfc, 0x21, 0x1c, 0xfd, 0x1b, 0x4a, 0x7b, 0x47, 0x70, 0x86, 0x3b, 0x95, 0xd0, 0xe3, 0x7c, 0x36, 0xee, 0xeb, 0x5f, 0x38, 0xb7, 0x0b, 0xf2, 0xf4, 0xc0, 0xb5, 0x9e, 0x42, 0xcb, 0x5b, 0xd7, 0xfa, 0x0a, 0xbc, 0x05, 0xe4, 0x56, 0x0f, 0xde, 0x8e, 0x9c, 0x41, 0xc8, 0x8b, 0x91, 0xbe, 0x09, 0x69, 0x4b, 0xc3, 0xd1, 0xef, 0xec, 0x6b, 0xc8, 0x31, 0x2e, 0xf8, 0x72, 0xd4, 0xc4, 0x8b, 0xf4, 0x4b, 0x48, 0x47, 0x91, 0xbf, 0x0f, 0xd2, 0x57, 0x41, 0xbe, 0x07, 0x92, 0x4b, 0x91, 0xfe, 0x18, 0xe9, 0x6b, 0xc1, 0xa1, 0xc3, 0xd2, 0x0f, 0xe0, 0xd6, 0x48, 0x99, 0x09, 0xfe, 0x10, 0x24, 0x59, 0xb8, 0x96, 0xd5, 0xaa, 0xbd, 0xc1, 0x81, 0x85, 0xf0, 0x3b, 0xc1, 0x51, 0x73, 0x7e, 0x1d, 0xf8, 0xcd, 0xe0, 0x17, 0x20, 0xbf, 0xa5, 0x39, 0xc3, 0xc0, 0x55, 0xf0, 0x63, 0xe0, 0xb3, 0xc0, 0x1f, 0x04, 0x2f, 0xb2, 0x5a, 0x18, 0xbc, 0x1f, 0x78, 0x0b, 0xf0, 0x87, 0x71, 0x15, 0xe8, 0x0f, 0xb3, 0x7a, 0x4d, 0x87, 0xa4, 0x16, 0xd2, 0x17, 0x82, 0x37, 0x01, 0xc7, 0xc8, 0xe5, 0x8a, 0x35, 0x1b, 0xa0, 0x85, 0x9f, 0x42, 0xeb, 0x75, 0x40, 0xfe, 0x2d, 0xe0, 0xb8, 0x8a, 0x94, 0x8a, 0xa3, 0x56, 0xbf, 0xff, 0x82, 0x76, 0x1b, 0x80, 0xf2, 0x4f, 0x41, 0x8e, 0x59, 0x85, 0xa1, 0x05, 0x18, 0x46, 0x16, 0xc3, 0x0c, 0xc3, 0x30, 0x76, 0x18, 0xda, 0x96, 0xad, 0x06, 0x5f, 0x08, 0xbe, 0x08, 0x1c, 0x7a, 0x28, 0x37, 0x42, 0x1a, 0xa3, 0x9e, 0x61, 0x06, 0xe3, 0xd0, 0x22, 0x8e, 0x3a, 0xf0, 0x96, 0xe0, 0x18, 0x8f, 0xec, 0x04, 0xd2, 0x1b, 0x91, 0x3e, 0x82, 0x34, 0xe6, 0x1f, 0xde, 0x06, 0x12, 0x8c, 0x5f, 0x9e, 0x81, 0xf4, 0x71, 0xa4, 0xd1, 0xe3, 0x1c, 0x38, 0x93, 0x9c, 0x0f, 0x5e, 0x0c, 0x9d, 0x31, 0x70, 0x47, 0x9f, 0xa0, 0xe6, 0x3f, 0x23, 0x3d, 0x1a, 0x1c, 0xbd, 0x2c, 0xf7, 0xc7, 0x59, 0x47, 0x70, 0x8f, 0x07, 0x91, 0xc7, 0x81, 0xf4, 0x4c, 0xf0, 0x9f, 0xc0, 0xd3, 0xad, 0xb1, 0x8c, 0xb6, 0x6a, 0x8b, 0x34, 0x66, 0x21, 0x8e, 0xd1, 0xad, 0x40, 0xdf, 0x64, 0x13, 0xf2, 0xa3, 0x90, 0xdc, 0x02, 0x89, 0x35, 0xee, 0x30, 0x66, 0xa5, 0x55, 0x98, 0xc1, 0x5a, 0x83, 0x0f, 0x86, 0x04, 0x2d, 0xa6, 0xd4, 0xb0, 0x38, 0x24, 0xfb, 0x71, 0x56, 0x57, 0xf0, 0xd7, 0x20, 0x41, 0x4b, 0xf2, 0x12, 0xd4, 0x6a, 0x07, 0x24, 0xaf, 0x83, 0x63, 0xc6, 0x96, 0x6e, 0x43, 0xfd, 0x0b, 0x50, 0x42, 0x09, 0xd2, 0x6f, 0xe1, 0xdc, 0x6b, 0x90, 0xde, 0x80, 0x3c, 0x97, 0x83, 0x63, 0x1e, 0x90, 0x2c, 0xfd, 0x1f, 0x0f, 0x8e, 0x5e, 0x90, 0x3c, 0xc8, 0x6f, 0xcd, 0x66, 0x7f, 0x01, 0xbf, 0x08, 0x72, 0xcc, 0x72, 0x12, 0x74, 0x49, 0xee, 0x05, 0xf9, 0x1a, 0x48, 0x2e, 0x44, 0x5a, 0xc3, 0x59, 0xb8, 0x0b, 0xd9, 0x9a, 0x43, 0x32, 0x91, 0x13, 0x2d, 0x2f, 0x59, 0x33, 0xf6, 0x00, 0xf0, 0x26, 0xe0, 0x1f, 0x22, 0x27, 0x5a, 0x95, 0xaf, 0x01, 0xc7, 0x4c, 0x2e, 0xe5, 0x42, 0x6e, 0xcd, 0x0c, 0x18, 0xef, 0x12, 0x46, 0x81, 0x84, 0xbe, 0x96, 0x6e, 0x44, 0x5b, 0x8d, 0x02, 0xd7, 0xac, 0x27, 0x20, 0xf2, 0x40, 0x9f, 0xe5, 0xce, 0xe0, 0x39, 0x28, 0xed, 0x13, 0x6b, 0xd6, 0x45, 0x9e, 0x9e, 0x48, 0x43, 0x33, 0xe5, 0x95, 0x90, 0xac, 0x45, 0xfb, 0x58, 0x5a, 0x51, 0x88, 0xf4, 0xb3, 0x90, 0x2b, 0xe2, 0x5c, 0x1b, 0x7a, 0x56, 0xc1, 0xe8, 0x90, 0xac, 0xb1, 0x60, 0x22, 0x0d, 0xed, 0x95, 0xdb, 0x82, 0x3f, 0x86, 0x32, 0x5f, 0x45, 0x4e, 0xcc, 0x39, 0x52, 0x73, 0xc8, 0x9d, 0xd0, 0x3d, 0x6b, 0xae, 0x1b, 0x01, 0x8e, 0x56, 0xe5, 0xd0, 0x3d, 0x1e, 0x43, 0x39, 0x57, 0xa0, 0x86, 0x5b, 0x30, 0xcb, 0xad, 0x45, 0x1a, 0x4f, 0x0a, 0xe9, 0x09, 0xf0, 0x07, 0xc0, 0xeb, 0x22, 0xff, 0x14, 0xe4, 0x39, 0x8e, 0x3c, 0x71, 0x48, 0x76, 0x22, 0xfd, 0x15, 0x6a, 0x85, 0x36, 0x97, 0x9e, 0x84, 0x7c, 0x3d, 0xe4, 0xe3, 0x50, 0x07, 0xdc, 0xbb, 0x14, 0x81, 0xfc, 0x4d, 0x6b, 0x0c, 0xa2, 0x9c, 0xf7, 0x90, 0xa7, 0x31, 0xb8, 0x0f, 0xbc, 0x00, 0xdc, 0x7a, 0xf2, 0xba, 0x90, 0x1f, 0xbd, 0xcc, 0x07, 0x59, 0x57, 0x47, 0x39, 0xf3, 0xc1, 0xe7, 0x81, 0x63, 0xe4, 0x4a, 0xcf, 0xe1, 0x2a, 0x78, 0x0e, 0x4a, 0xb8, 0xa2, 0xf4, 0xa0, 0xc5, 0xad, 0x31, 0x82, 0xf4, 0x1c, 0xe4, 0x41, 0x1f, 0xc9, 0x2a, 0xe4, 0x18, 0x8f, 0x12, 0xee, 0x45, 0x7a, 0x04, 0x69, 0xeb, 0x19, 0x6a, 0x5d, 0xc5, 0xea, 0x4d, 0x6b, 0x1c, 0xd5, 0x03, 0x6f, 0x0f, 0xc9, 0xc7, 0x48, 0x63, 0xa6, 0x95, 0xad, 0x39, 0x13, 0xe3, 0x82, 0x5b, 0x47, 0xf1, 0x04, 0x94, 0x66, 0x59, 0x1c, 0x39, 0xcf, 0x43, 0x5a, 0x46, 0xfb, 0xa0, 0x97, 0x65, 0x09, 0x69, 0xd4, 0x5f, 0x9e, 0x87, 0x3c, 0x98, 0xbb, 0x14, 0x6b, 0x86, 0xff, 0x1e, 0x47, 0x31, 0x63, 0xc8, 0xe9, 0x56, 0xda, 0x7a, 0x82, 0xe0, 0x5a, 0xb9, 0x28, 0xed, 0x1a, 0xeb, 0xba, 0x48, 0xa7, 0x59, 0x1c, 0xe5, 0x60, 0x26, 0xe1, 0x58, 0x6f, 0x48, 0xd6, 0x7a, 0xec, 0x73, 0xc8, 0x3f, 0x47, 0xda, 0x7a, 0xc6, 0xfd, 0x1d, 0xd7, 0xc2, 0xdc, 0xa8, 0xb8, 0x71, 0x74, 0x32, 0xe4, 0xc3, 0x91, 0xbe, 0x1d, 0x69, 0xcc, 0xf0, 0x12, 0x46, 0xab, 0x74, 0xb5, 0xc5, 0x71, 0x8f, 0xd6, 0xfc, 0x8f, 0xd9, 0x83, 0x7f, 0x83, 0xfc, 0x75, 0x21, 0xff, 0x1e, 0xb5, 0xc5, 0x33, 0x45, 0xda, 0x85, 0xa3, 0xbb, 0xc1, 0x31, 0x9f, 0xf0, 0x03, 0xd6, 0x7a, 0x06, 0xf9, 0xa1, 0x75, 0xd2, 0x0c, 0xa4, 0xaf, 0x40, 0x1a, 0xeb, 0x22, 0x7e, 0x08, 0x12, 0xaf, 0xf5, 0xec, 0xc6, 0x59, 0x58, 0x29, 0xf1, 0xa1, 0x90, 0x18, 0xd6, 0x3a, 0x0d, 0xed, 0x60, 0xe5, 0x29, 0xb0, 0x46, 0x10, 0xb4, 0xc8, 0xaa, 0x4f, 0x1d, 0xa4, 0xbf, 0x86, 0x76, 0xa1, 0x3e, 0xec, 0x28, 0xca, 0x09, 0x41, 0x8e, 0x39, 0x93, 0xa1, 0x6d, 0x25, 0x09, 0xd7, 0x45, 0x2f, 0x48, 0x58, 0x09, 0xb0, 0x6f, 0xad, 0xf5, 0x09, 0xca, 0xb1, 0xda, 0xbf, 0x1d, 0xee, 0xc8, 0x1a, 0x41, 0xcd, 0x21, 0x99, 0x80, 0xfc, 0x77, 0x23, 0x5d, 0x1f, 0x69, 0x3c, 0xd7, 0xa4, 0x0b, 0x90, 0x6e, 0x86, 0x74, 0xbe, 0x35, 0x2e, 0x20, 0xb1, 0xb4, 0x02, 0xb3, 0x3a, 0x1f, 0x82, 0xb4, 0x75, 0x14, 0x6b, 0x54, 0x0e, 0x1d, 0xe6, 0x4b, 0x2d, 0x8e, 0xfc, 0x58, 0x2d, 0x73, 0xcc, 0x27, 0x7c, 0xa5, 0x95, 0xb6, 0x34, 0x1f, 0xe9, 0x7b, 0xad, 0xb9, 0x08, 0x57, 0xc1, 0x3a, 0x4a, 0x9a, 0x0a, 0x6e, 0xad, 0x48, 0x55, 0x8b, 0x43, 0x82, 0x79, 0x52, 0x0e, 0x5a, 0x1c, 0x25, 0x58, 0x7a, 0xde, 0x00, 0x12, 0x3c, 0xc7, 0x25, 0x8c, 0x2f, 0xbe, 0x01, 0x7c, 0x9d, 0xd5, 0x4a, 0xc8, 0xd9, 0x1f, 0x69, 0x68, 0x3e, 0xb7, 0xf4, 0x0d, 0xba, 0xc7, 0xf0, 0x74, 0xe3, 0xd0, 0x34, 0x8e, 0x3b, 0x65, 0x58, 0xe7, 0xf0, 0x6c, 0xa4, 0xad, 0xa7, 0xff, 0x6b, 0xd6, 0x13, 0xdc, 0x1a, 0xc5, 0x90, 0x7f, 0x08, 0xfe, 0x23, 0xf8, 0x0e, 0x1c, 0x6d, 0x0c, 0xee, 0x03, 0x5f, 0x00, 0x5e, 0xdf, 0x2a, 0x19, 0x67, 0x61, 0x55, 0xcc, 0xac, 0x67, 0x1f, 0x8e, 0xca, 0xc8, 0x2f, 0xe3, 0x2a, 0x32, 0x66, 0x3f, 0xb9, 0x1e, 0x78, 0x5d, 0x2b, 0x8d, 0xfb, 0xc5, 0x1a, 0x5b, 0xea, 0x6e, 0xdd, 0x11, 0x4a, 0xb3, 0x56, 0x5f, 0x58, 0x95, 0x71, 0x1c, 0xe5, 0xd0, 0x6a, 0x8e, 0x99, 0x90, 0x63, 0xfd, 0x2c, 0x2d, 0xb6, 0xb4, 0xc8, 0x1a, 0xdd, 0x90, 0x58, 0xb6, 0x00, 0x56, 0x0e, 0xb2, 0x8c, 0xf2, 0x31, 0x66, 0xa5, 0x4b, 0x70, 0x14, 0xf3, 0xa4, 0x82, 0x56, 0x92, 0x7e, 0xb1, 0x56, 0xc8, 0xd6, 0xa8, 0x01, 0x7f, 0x1c, 0xf9, 0x27, 0x20, 0xfd, 0x34, 0xf8, 0xab, 0xb8, 0x16, 0xc6, 0x1d, 0x47, 0x5b, 0xc9, 0x29, 0xd6, 0x6c, 0x09, 0xfe, 0x06, 0xea, 0xf6, 0x3a, 0x72, 0xb6, 0xb3, 0xe6, 0x19, 0x70, 0x6b, 0x56, 0xc7, 0x0c, 0x29, 0x59, 0xb3, 0x3d, 0xd6, 0xd5, 0xd2, 0x76, 0x9c, 0xf5, 0x2f, 0xa4, 0x6b, 0xa2, 0x95, 0xb0, 0xce, 0x97, 0xb0, 0xbe, 0x62, 0x96, 0xbd, 0x70, 0x9b, 0xd5, 0xe6, 0xa8, 0x33, 0xc3, 0x5d, 0xe0, 0xa9, 0x2a, 0x5f, 0x6a, 0x71, 0xc8, 0x61, 0x37, 0x49, 0x37, 0x20, 0x7d, 0x0f, 0xd2, 0xf7, 0xa0, 0x7f, 0xad, 0x75, 0x23, 0x5a, 0x5b, 0x42, 0xeb, 0xc9, 0x78, 0xc2, 0xb2, 0x6f, 0x51, 0x32, 0x9e, 0x32, 0x52, 0x57, 0x70, 0xcb, 0x66, 0xc1, 0xec, 0xcd, 0x61, 0x19, 0xf1, 0x77, 0xad, 0xf5, 0x0c, 0x24, 0x8f, 0x83, 0x4f, 0x83, 0xa4, 0x96, 0x35, 0xef, 0xa1, 0x9c, 0x2f, 0x21, 0xe9, 0x02, 0x09, 0x66, 0x1e, 0x8e, 0x55, 0xb4, 0x74, 0x17, 0xae, 0x75, 0x39, 0xea, 0xb3, 0xcd, 0x9a, 0xf1, 0x2c, 0xfb, 0xc8, 0x9a, 0x0d, 0xac, 0xf1, 0x85, 0x96, 0x87, 0x46, 0xc9, 0x18, 0x4d, 0x36, 0xac, 0x7e, 0x15, 0x3c, 0xd3, 0x15, 0xb4, 0xa1, 0x8c, 0xfa, 0x33, 0x07, 0x6b, 0x23, 0x7c, 0x72, 0xf8, 0x1c, 0x26, 0x31, 0x0d, 0xf1, 0x5d, 0x0c, 0x91, 0x5d, 0x2e, 0x44, 0x76, 0x05, 0x54, 0x55, 0x35, 0x59, 0x4d, 0x35, 0xa2, 0x46, 0x58, 0x3a, 0x62, 0xba, 0x32, 0xcc, 0x37, 0xcc, 0xf5, 0xac, 0xa1, 0xb9, 0xc1, 0x7c, 0x8b, 0x35, 0xa2, 0x33, 0xc3, 0x4c, 0x66, 0x26, 0x73, 0x33, 0x2f, 0xbe, 0x73, 0xb5, 0x55, 0x7e, 0x52, 0x15, 0xd1, 0x7f, 0xe3, 0xd4, 0xf1, 0xea, 0x22, 0x75, 0xb1, 0xfa, 0x9e, 0x5a, 0x2a, 0xfe, 0xdb, 0x9b, 0x16, 0xd4, 0xc2, 0xda, 0x18, 0x6d, 0x8a, 0x91, 0x22, 0xfe, 0x8b, 0x35, 0x13, 0x5f, 0x32, 0xd8, 0x93, 0xb4, 0xed, 0x4f, 0xda, 0xf6, 0x54, 0x49, 0x6f, 0x49, 0x6c, 0xdb, 0xaa, 0xc8, 0xcb, 0xb7, 0x9d, 0xd5, 0x38, 0x6f, 0xe7, 0x39, 0xce, 0x15, 0xbf, 0xc5, 0xd7, 0x1a, 0xc4, 0x97, 0x08, 0xc4, 0x57, 0x0b, 0x44, 0xbc, 0x80, 0x42, 0x9b, 0x41, 0x9b, 0x87, 0xb6, 0x10, 0x6d, 0x35, 0xaa, 0x6c, 0xe9, 0x89, 0x2d, 0x1b, 0x1b, 0x8f, 0xcf, 0x10, 0x1e, 0xff, 0xf1, 0x7c, 0xf8, 0xfd, 0x3f, 0x04, 0x3e, 0x0e, 0xfc, 0x28, 0x38, 0xb3, 0xe5, 0x32, 0xfe, 0x4b, 0x19, 0xf8, 0x4f, 0xe0, 0x9f, 0x0b, 0x4e, 0xf2, 0x9e, 0x94, 0x3e, 0x08, 0xfe, 0x05, 0xf8, 0x67, 0x82, 0x53, 0x69, 0xc5, 0xd4, 0xcf, 0xf3, 0xc4, 0xb9, 0xfc, 0x79, 0xe2, 0x61, 0xd6, 0x92, 0x75, 0x26, 0x2a, 0x62, 0xc5, 0xac, 0x17, 0xcb, 0x63, 0x4d, 0xe9, 0x77, 0x3b, 0x48, 0x2c, 0xa9, 0x45, 0xc5, 0x38, 0xda, 0x57, 0xbc, 0x9d, 0x8d, 0xff, 0x8c, 0xfa, 0x14, 0x09, 0xce, 0x56, 0x83, 0xaf, 0x84, 0xe4, 0x69, 0xf0, 0xbf, 0x82, 0xbf, 0x09, 0xde, 0x06, 0xbc, 0x15, 0xf2, 0xc8, 0xe0, 0x0e, 0xf1, 0xbf, 0x16, 0xa5, 0x63, 0xca, 0x06, 0x4a, 0x5f, 0x67, 0xaf, 0x63, 0xaf, 0x6b, 0xaf, 0x6f, 0x6f, 0x60, 0x6f, 0x68, 0x6f, 0x64, 0x6f, 0x6c, 0x6f, 0x62, 0x3f, 0xdf, 0x7e, 0x81, 0xbd, 0x85, 0xbd, 0xb5, 0xbd, 0x0d, 0x69, 0x49, 0x57, 0xfb, 0x25, 0xf6, 0x22, 0xfb, 0xa5, 0xf6, 0x6e, 0xf6, 0xcb, 0xec, 0xdd, 0xed, 0x97, 0xdb, 0x8b, 0xed, 0x57, 0xd8, 0x7b, 0xd8, 0xaf, 0xb4, 0xf7, 0xb4, 0x1f, 0xb4, 0x1f, 0x22, 0xfd, 0xf9, 0xde, 0x7e, 0xd4, 0xfe, 0x83, 0xfd, 0x98, 0xfd, 0x47, 0xfb, 0x49, 0xfb, 0x2f, 0xf6, 0x53, 0xf6, 0xb8, 0xca, 0x54, 0xae, 0x4a, 0xaa, 0xac, 0x2a, 0xaa, 0x9d, 0x74, 0x4a, 0x53, 0x75, 0xd5, 0x50, 0x4d, 0xd5, 0xa9, 0xba, 0x55, 0x8f, 0x88, 0x18, 0x24, 0xdd, 0x4a, 0x53, 0xcf, 0x53, 0xd3, 0xd5, 0x0c, 0x35, 0x53, 0xad, 0xad, 0x66, 0xa9, 0x75, 0xd4, 0x6c, 0xb5, 0xae, 0x5a, 0xcf, 0x94, 0x4c, 0x9b, 0xa9, 0x9a, 0x9a, 0x69, 0x98, 0x4e, 0xd3, 0x6d, 0xfa, 0xcc, 0x80, 0x19, 0x32, 0x6b, 0x9b, 0x0d, 0xcc, 0x86, 0x66, 0x9e, 0xd9, 0xd8, 0x6c, 0x62, 0x36, 0x35, 0x9b, 0x99, 0x2d, 0xcc, 0x42, 0xf3, 0x42, 0xb3, 0x8b, 0x39, 0xd6, 0xbc, 0xdf, 0x9c, 0x60, 0x4e, 0x34, 0x9f, 0x37, 0x17, 0x99, 0x6b, 0xcc, 0xb5, 0xa4, 0xa9, 0x1b, 0x48, 0xab, 0x7b, 0xfd, 0x2e, 0x3f, 0x87, 0xfe, 0xac, 0x10, 0x1e, 0x0e, 0x1d, 0xe1, 0xe1, 0xd0, 0x13, 0x1e, 0x0e, 0xd7, 0xb0, 0xfb, 0xd9, 0x23, 0xac, 0x0f, 0xfb, 0x27, 0xd1, 0x6f, 0x79, 0x35, 0x08, 0x7f, 0x86, 0x87, 0xe0, 0xcf, 0xf0, 0x3c, 0xfc, 0x19, 0xde, 0xe1, 0xfb, 0xf9, 0x41, 0xb6, 0x59, 0xca, 0x4d, 0xf8, 0x33, 0x14, 0xb0, 0x0f, 0xa4, 0xa6, 0xd2, 0xf9, 0xec, 0x23, 0xa9, 0xad, 0xf4, 0x28, 0xfb, 0x58, 0x7a, 0x4c, 0x9a, 0xc3, 0x73, 0xa5, 0x67, 0xa5, 0xe7, 0x78, 0x33, 0x69, 0xa1, 0xb4, 0x90, 0xb7, 0x90, 0x16, 0x49, 0xfb, 0x79, 0x4b, 0xe9, 0x80, 0x9c, 0xc5, 0xef, 0x90, 0xdb, 0xca, 0xed, 0xf9, 0x72, 0xf9, 0x42, 0xb9, 0x0b, 0x5f, 0x29, 0x5f, 0x2a, 0x5f, 0xca, 0x5f, 0x93, 0x2f, 0x93, 0x7b, 0xf0, 0xb5, 0x72, 0x4f, 0xb9, 0x37, 0xdf, 0x20, 0xf7, 0x91, 0xfb, 0xf0, 0x77, 0xe4, 0xbe, 0x72, 0x5f, 0xbe, 0x59, 0xbe, 0x49, 0x1e, 0xc2, 0xdf, 0x95, 0x87, 0xca, 0x43, 0xf9, 0xfb, 0x72, 0x89, 0x5c, 0xc2, 0x4b, 0xe5, 0xdb, 0xe5, 0xdb, 0xf9, 0x07, 0xf2, 0x08, 0x79, 0x04, 0xdf, 0x26, 0x8f, 0x94, 0x47, 0xf1, 0x0f, 0xe5, 0x27, 0xe5, 0x65, 0x7c, 0x87, 0xf0, 0x8b, 0xe0, 0x87, 0xe1, 0x11, 0xd1, 0x05, 0x1e, 0x11, 0x3d, 0xe1, 0x11, 0x31, 0x44, 0x29, 0x53, 0xca, 0xa4, 0x5b, 0x94, 0x53, 0xca, 0x29, 0x69, 0x28, 0xbc, 0x23, 0x6e, 0x85, 0x77, 0x44, 0x89, 0xad, 0xbd, 0xad, 0x83, 0x34, 0xd2, 0xb6, 0xc6, 0xb6, 0x56, 0x1a, 0x65, 0x7b, 0xc3, 0xf6, 0xa6, 0x74, 0x8f, 0xed, 0x53, 0xdb, 0xe7, 0xd2, 0x58, 0x11, 0x13, 0x27, 0x3d, 0x00, 0xef, 0x88, 0x07, 0xe1, 0x1d, 0xf1, 0x08, 0xbc, 0x23, 0x1e, 0x85, 0x5f, 0xc4, 0xb3, 0xf0, 0x88, 0x58, 0x28, 0x22, 0xe0, 0xa4, 0xe7, 0x45, 0x04, 0x9c, 0xb4, 0x5d, 0x44, 0xc0, 0x49, 0x87, 0x45, 0x04, 0x9c, 0x5c, 0x20, 0x22, 0xe0, 0xe4, 0x81, 0x9a, 0x53, 0xfb, 0x46, 0x1e, 0x01, 0x7f, 0x89, 0xab, 0x74, 0x43, 0x77, 0x28, 0x7d, 0xe1, 0x2f, 0x71, 0x1d, 0xfc, 0x25, 0x86, 0xc3, 0x5f, 0xe2, 0x4e, 0xf8, 0x4b, 0x8c, 0x84, 0xbf, 0xc4, 0x5d, 0xf0, 0x97, 0xb8, 0x17, 0xfe, 0x12, 0xd3, 0x85, 0xbf, 0x84, 0xf2, 0xb5, 0xfe, 0x96, 0xfe, 0x93, 0xf2, 0xbd, 0x88, 0x11, 0xb3, 0x65, 0x19, 0x0d, 0x8d, 0x6d, 0xb6, 0x26, 0x22, 0x9e, 0xcb, 0xf6, 0x89, 0xf0, 0x9d, 0xb0, 0xed, 0x12, 0xbe, 0x13, 0xb6, 0xc3, 0xc2, 0x3b, 0xc2, 0xf6, 0x9d, 0x88, 0xc9, 0xb2, 0x9d, 0x14, 0x7e, 0x11, 0xb6, 0x53, 0xe6, 0x46, 0xf3, 0x1d, 0xbb, 0x22, 0xe2, 0xb0, 0xec, 0x1a, 0xbb, 0x8e, 0xc6, 0xf1, 0xfc, 0xdf, 0xd8, 0x26, 0x57, 0x23, 0xcf, 0x7f, 0x73, 0x9b, 0xf6, 0x1b, 0xc7, 0x67, 0xd2, 0x36, 0xe7, 0x1c, 0xc7, 0x16, 0x55, 0xd9, 0x96, 0x26, 0x6d, 0x2b, 0x68, 0x5b, 0x73, 0x7a, 0xf3, 0x4a, 0xe7, 0xde, 0x90, 0x67, 0xbd, 0xb5, 0x55, 0x92, 0xad, 0x39, 0x2d, 0x2b, 0x2f, 0xa3, 0x6a, 0x99, 0x38, 0x6f, 0x93, 0xb5, 0x55, 0x29, 0xb7, 0x3c, 0xee, 0xca, 0x8a, 0xb5, 0xaa, 0x56, 0x64, 0x15, 0xe6, 0xca, 0xfb, 0x31, 0x7f, 0x6d, 0x54, 0xfa, 0xda, 0xf5, 0x40, 0xd3, 0x40, 0x53, 0x47, 0xc0, 0xdf, 0xd5, 0xdf, 0xdf, 0x11, 0x73, 0xd6, 0xf0, 0x0f, 0x0f, 0x6f, 0xf6, 0x4f, 0x08, 0xed, 0x72, 0x2e, 0x0d, 0x97, 0x3a, 0xd2, 0xfc, 0xf3, 0x1d, 0x59, 0xfe, 0xa5, 0xfe, 0x35, 0x8e, 0x1c, 0x77, 0x37, 0xff, 0x26, 0xff, 0x76, 0xff, 0x5e, 0x47, 0xbe, 0xbf, 0x2c, 0xd8, 0xc3, 0xd1, 0x2c, 0xd4, 0x35, 0xe0, 0xf0, 0x97, 0x39, 0x4f, 0x04, 0x7c, 0x8e, 0xc2, 0x40, 0xaa, 0x73, 0x51, 0x20, 0x2f, 0x90, 0xe7, 0x58, 0xee, 0xe8, 0x80, 0x77, 0xb6, 0xc5, 0x81, 0xbe, 0x81, 0x92, 0xc0, 0x44, 0xcf, 0xae, 0xc0, 0xac, 0xc0, 0xac, 0xe0, 0x91, 0xe0, 0x10, 0x11, 0x07, 0x13, 0x58, 0x10, 0x58, 0x6c, 0xc5, 0xc2, 0xf8, 0x26, 0x39, 0x06, 0xfa, 0xa6, 0x3a, 0x86, 0x38, 0x86, 0x85, 0x8c, 0xc0, 0x8e, 0x60, 0x0e, 0x91, 0x16, 0x74, 0x05, 0x5d, 0x8e, 0x91, 0xb4, 0x8d, 0x09, 0xc6, 0x3c, 0x86, 0x63, 0x7c, 0x30, 0x2b, 0xd8, 0xcc, 0xb3, 0xd5, 0x33, 0x56, 0xbc, 0x63, 0x75, 0x4c, 0x0a, 0x0e, 0x0b, 0x4c, 0x09, 0x4f, 0x77, 0x1d, 0x0c, 0x4e, 0x0d, 0xae, 0xf3, 0x36, 0x75, 0x4c, 0x75, 0xb5, 0x0c, 0x2e, 0x77, 0xcc, 0x10, 0x11, 0x33, 0x8e, 0x85, 0xa1, 0x69, 0xc1, 0x3d, 0xee, 0xb4, 0xe0, 0x91, 0x90, 0xe2, 0x2a, 0x09, 0xac, 0x75, 0x2c, 0x71, 0x8d, 0xf3, 0x8d, 0xf7, 0x4f, 0x70, 0x2c, 0x0f, 0x6d, 0x0f, 0xd5, 0x70, 0xac, 0x0e, 0x97, 0x86, 0xd2, 0x1d, 0xeb, 0x42, 0xd9, 0xc1, 0x1e, 0xc1, 0x61, 0x9e, 0x39, 0x54, 0x87, 0x48, 0x28, 0xe4, 0xd8, 0xe8, 0xd8, 0xe2, 0xd8, 0x16, 0x1a, 0xe4, 0xd8, 0x19, 0xd8, 0xe1, 0x9d, 0x28, 0x62, 0x5e, 0x3c, 0x2b, 0x1c, 0x87, 0xdc, 0xab, 0x3d, 0x93, 0x1d, 0x47, 0x42, 0x73, 0x1c, 0xc7, 0x43, 0xf3, 0x1d, 0xa7, 0x42, 0x2b, 0xc2, 0x83, 0xbd, 0x79, 0x22, 0xe6, 0xc5, 0xb7, 0xd1, 0xe9, 0xf1, 0x1c, 0xf5, 0x16, 0x79, 0x53, 0x43, 0x9b, 0x42, 0xdb, 0xbd, 0x76, 0x67, 0xc8, 0x59, 0xc3, 0x99, 0xee, 0xb5, 0x7b, 0xed, 0x81, 0xce, 0xce, 0x6c, 0x67, 0x76, 0x78, 0x95, 0x33, 0xd7, 0x3f, 0xc7, 0x59, 0xe0, 0xef, 0xef, 0x6c, 0x1e, 0xee, 0xec, 0xdd, 0xe1, 0xdd, 0xe1, 0x6c, 0x13, 0x1c, 0x18, 0x3a, 0xe1, 0x1b, 0xe3, 0x5d, 0xeb, 0xec, 0xe8, 0xed, 0x1b, 0x76, 0x84, 0x53, 0xbd, 0x73, 0xbd, 0xa5, 0xde, 0xd2, 0x70, 0xd3, 0x70, 0x4b, 0x5f, 0x87, 0x60, 0xef, 0x70, 0x4b, 0xef, 0x66, 0x11, 0x6f, 0x22, 0xde, 0x77, 0x79, 0xa7, 0x3b, 0x7b, 0x12, 0xf5, 0x71, 0xf6, 0x0f, 0x9d, 0x08, 0xce, 0x08, 0x94, 0x38, 0x07, 0x79, 0x07, 0x04, 0x35, 0xe7, 0x50, 0xcf, 0x1a, 0x11, 0x6d, 0xe2, 0xeb, 0xe0, 0x1c, 0xeb, 0x5e, 0xe8, 0xd9, 0xee, 0x9c, 0xe0, 0xd9, 0xea, 0x4b, 0x73, 0x8d, 0xf0, 0x2e, 0x70, 0x4e, 0x0e, 0x8e, 0x74, 0x6e, 0x0d, 0xac, 0x15, 0xfe, 0x37, 0x56, 0x5c, 0x48, 0x68, 0xab, 0x77, 0x81, 0x7f, 0x82, 0x73, 0x69, 0x68, 0xab, 0x73, 0x85, 0x73, 0x4d, 0xf8, 0x98, 0x7f, 0x6f, 0x68, 0xbb, 0xaf, 0x8b, 0x73, 0xbd, 0xf3, 0x84, 0x73, 0x93, 0xbf, 0x7b, 0xa0, 0x5e, 0xa8, 0x39, 0xa2, 0x3a, 0x76, 0x85, 0xa7, 0x38, 0xf7, 0x7a, 0x0f, 0x7b, 0x3b, 0x87, 0x4b, 0xfd, 0xc3, 0xfd, 0xcd, 0x9d, 0x07, 0x02, 0xc5, 0xce, 0x32, 0x97, 0xc3, 0x3f, 0xc8, 0x79, 0x34, 0x90, 0xe7, 0x3c, 0xe1, 0x62, 0x81, 0x62, 0xe1, 0x65, 0x13, 0x2e, 0x0d, 0x6a, 0x22, 0xda, 0x82, 0x5a, 0x33, 0x35, 0xdc, 0xce, 0x95, 0x19, 0xcc, 0x11, 0x9e, 0x36, 0xae, 0xa6, 0xc1, 0xde, 0xae, 0x55, 0xae, 0x5e, 0xae, 0x96, 0xee, 0x42, 0x4f, 0x81, 0x67, 0x5a, 0xf0, 0x88, 0x67, 0xbd, 0xab, 0x1d, 0xf5, 0xee, 0x02, 0x77, 0x4e, 0xb0, 0xd0, 0x3d, 0xd2, 0x55, 0xe2, 0xea, 0xec, 0x2a, 0x75, 0xbb, 0x10, 0x23, 0xd1, 0xcb, 0x9d, 0xe5, 0x2a, 0xa1, 0xde, 0x12, 0x51, 0x11, 0x73, 0x5d, 0x83, 0xa9, 0x77, 0x63, 0xc2, 0xbf, 0xc6, 0x35, 0xda, 0x35, 0x2e, 0xd8, 0x0f, 0x71, 0x0b, 0x53, 0xac, 0xd8, 0x85, 0xc0, 0x6e, 0xef, 0x38, 0xd7, 0xaa, 0xf0, 0xac, 0x90, 0x12, 0x2c, 0xf4, 0x8d, 0x14, 0xba, 0x12, 0x9a, 0x86, 0x78, 0x85, 0xc5, 0x81, 0x1d, 0xae, 0x65, 0xbe, 0xfd, 0xde, 0x71, 0xf4, 0x7b, 0x55, 0x22, 0x1e, 0x61, 0x83, 0x6b, 0xb3, 0x77, 0x77, 0xb0, 0x99, 0x88, 0x43, 0x20, 0x9d, 0xe9, 0x2d, 0xbc, 0x69, 0xbc, 0xd3, 0x5d, 0xfb, 0x68, 0xb3, 0xe2, 0x04, 0x8e, 0x85, 0xe7, 0xba, 0x67, 0x78, 0x0f, 0x86, 0x17, 0x5b, 0x3e, 0x35, 0x96, 0x07, 0x8d, 0x77, 0x59, 0xc8, 0x70, 0xa7, 0x79, 0xe7, 0x7a, 0xa6, 0x25, 0x79, 0xf0, 0x17, 0xba, 0x3b, 0x84, 0x0a, 0xdc, 0x5d, 0xc2, 0xfb, 0xca, 0xfd, 0x66, 0x48, 0x43, 0x89, 0xc2, 0xd3, 0xc3, 0x7d, 0xdd, 0x03, 0xbd, 0xed, 0x2a, 0xbc, 0xf8, 0x13, 0x7e, 0xfc, 0xde, 0x89, 0xee, 0x85, 0xee, 0x19, 0xc1, 0x53, 0x1e, 0xc5, 0x37, 0x26, 0xe1, 0x31, 0x23, 0xbc, 0xf6, 0x57, 0xbb, 0xd7, 0x05, 0x76, 0x87, 0x4b, 0x84, 0xdf, 0x7e, 0x38, 0xd5, 0xd7, 0x23, 0xbc, 0x81, 0xae, 0xb4, 0x2d, 0x38, 0x32, 0xa4, 0x9c, 0xf6, 0xe1, 0xf7, 0xad, 0x13, 0x5e, 0xfc, 0xe1, 0x7d, 0xc2, 0x8f, 0x3f, 0xb0, 0xa3, 0xb2, 0x1f, 0xbf, 0xef, 0xb8, 0xf0, 0xe5, 0xf7, 0xee, 0xf6, 0xb4, 0xf1, 0x87, 0x2c, 0x2f, 0x18, 0x78, 0xc0, 0xf4, 0xf7, 0x0c, 0x0a, 0x4d, 0x10, 0x5e, 0x2f, 0xde, 0x65, 0xe1, 0x0d, 0x9e, 0x51, 0xde, 0xdd, 0x81, 0x65, 0x9e, 0xb1, 0xbe, 0x34, 0x5f, 0x33, 0xcf, 0x04, 0xef, 0x44, 0xcb, 0x03, 0xc6, 0xb3, 0x88, 0xc6, 0x67, 0x81, 0x67, 0x8e, 0x7f, 0xbb, 0x67, 0x7e, 0xa8, 0x4d, 0xf8, 0x70, 0xa0, 0xc4, 0x3b, 0xce, 0xb3, 0xc8, 0xb3, 0xd7, 0xb3, 0xd4, 0xb3, 0x02, 0x5e, 0x30, 0x7b, 0x83, 0xfd, 0x92, 0x3d, 0x61, 0x42, 0x9b, 0x3c, 0x07, 0xbc, 0x4d, 0xbd, 0x79, 0x9e, 0x32, 0x6f, 0xc4, 0xf2, 0x86, 0x11, 0xef, 0x3a, 0xbd, 0x76, 0x7f, 0xff, 0x40, 0x3d, 0xd2, 0x8f, 0x76, 0x5e, 0x87, 0x37, 0xd3, 0x9b, 0xe9, 0x9f, 0xe3, 0xf5, 0x59, 0x7e, 0x31, 0x38, 0x9a, 0xe9, 0xdf, 0x1e, 0xda, 0x1a, 0x9e, 0x22, 0x3c, 0x64, 0xbc, 0x4d, 0x29, 0x8f, 0xf0, 0x90, 0x69, 0xe7, 0x3f, 0x41, 0xbc, 0x88, 0x8e, 0x76, 0xf6, 0x77, 0xf5, 0x2e, 0x10, 0xfe, 0x32, 0xde, 0x5e, 0xde, 0x0d, 0xde, 0xb5, 0xc2, 0x4b, 0x86, 0x34, 0x7c, 0xb0, 0xb7, 0x84, 0xf4, 0x79, 0x04, 0x6d, 0xa3, 0xbd, 0x9b, 0x85, 0x8f, 0x4c, 0xf0, 0x14, 0xbc, 0x64, 0x16, 0xf8, 0xd6, 0x79, 0x67, 0xc1, 0x57, 0xa6, 0x14, 0x1a, 0xbf, 0x20, 0xdc, 0x34, 0x30, 0x2b, 0x3c, 0x97, 0x46, 0xf6, 0x48, 0xef, 0x62, 0xca, 0xb3, 0xcc, 0xbb, 0x8a, 0x52, 0x6b, 0xc5, 0x51, 0xe1, 0x3b, 0x13, 0xf2, 0x50, 0xea, 0x58, 0x78, 0x04, 0xd5, 0x68, 0x87, 0xbf, 0x79, 0x20, 0xcf, 0x3f, 0x99, 0x5a, 0x61, 0x87, 0xf0, 0xa3, 0x09, 0xec, 0x08, 0xb7, 0x13, 0xbe, 0x34, 0x3e, 0xcd, 0x7b, 0xd2, 0xa7, 0x09, 0x0f, 0x9a, 0x60, 0xc0, 0xe7, 0xf2, 0x97, 0xf9, 0x02, 0xfe, 0xfe, 0xe1, 0x93, 0xc2, 0x93, 0xe6, 0xb4, 0x1f, 0x8d, 0xf0, 0xa2, 0x09, 0xcc, 0x12, 0xde, 0x33, 0xbe, 0xde, 0xbe, 0x7e, 0xbe, 0x81, 0x81, 0x29, 0xc2, 0x6b, 0x26, 0xa8, 0x91, 0x9e, 0x2d, 0x80, 0xdf, 0xcc, 0xf8, 0xd0, 0x4c, 0xe1, 0x3b, 0x13, 0x74, 0x09, 0xdf, 0x19, 0x9a, 0x2b, 0xc6, 0x5a, 0xbe, 0x33, 0xfe, 0xa1, 0xc1, 0x21, 0xa4, 0xb1, 0x85, 0xbe, 0xe5, 0xc1, 0x6d, 0x96, 0x0f, 0x8d, 0x6f, 0x8b, 0x78, 0xdf, 0xed, 0x5f, 0x2f, 0xfc, 0x68, 0xe8, 0x7a, 0x7b, 0x82, 0xc3, 0x84, 0x0f, 0x8d, 0xe5, 0x41, 0x53, 0xee, 0x3f, 0xe3, 0xaf, 0x11, 0x1a, 0x1a, 0x5a, 0x11, 0xda, 0x14, 0x1c, 0x13, 0xdc, 0xe6, 0x4f, 0xf7, 0x67, 0x53, 0xaf, 0x34, 0x3d, 0x1b, 0xf9, 0x73, 0xcf, 0x2e, 0xaf, 0x92, 0xab, 0xe0, 0x9c, 0x47, 0x9a, 0x53, 0x8b, 0x34, 0xf5, 0xb7, 0x49, 0x78, 0xe6, 0x74, 0x0d, 0x14, 0xf9, 0xbb, 0x83, 0x7a, 0x26, 0xa8, 0xbf, 0xf0, 0xd4, 0x81, 0x9f, 0x4e, 0x82, 0x22, 0x12, 0x8d, 0xf6, 0xe1, 0x67, 0xbc, 0xab, 0xdf, 0xec, 0x1f, 0x15, 0xde, 0x7c, 0x96, 0x77, 0xf8, 0x20, 0xff, 0x04, 0xff, 0xd8, 0xd3, 0xe9, 0x4a, 0x47, 0x96, 0x82, 0x0b, 0xcf, 0x9f, 0x24, 0x0a, 0x6d, 0xa2, 0x39, 0x65, 0x26, 0xcd, 0xb4, 0x9b, 0xce, 0x45, 0xfe, 0x99, 0xb4, 0xcd, 0xa9, 0x42, 0xf3, 0x93, 0x78, 0x75, 0x68, 0xbe, 0x7f, 0x3e, 0x69, 0x03, 0xfc, 0x8c, 0xfc, 0xeb, 0xfd, 0x6b, 0x68, 0xb3, 0x68, 0x93, 0x7f, 0x53, 0x60, 0x9f, 0xe0, 0xa0, 0x72, 0xaf, 0x23, 0xa2, 0x8a, 0xab, 0xef, 0x4d, 0xaa, 0xc9, 0x01, 0xbc, 0xb9, 0x4f, 0x22, 0xe1, 0x99, 0xe4, 0x2f, 0xab, 0x2a, 0x0d, 0x6d, 0xaf, 0x2a, 0x39, 0x3b, 0xf9, 0x8f, 0x0a, 0x4f, 0x92, 0x00, 0x0b, 0x30, 0xff, 0x09, 0xe1, 0xd7, 0x14, 0x70, 0x24, 0x79, 0x59, 0x24, 0x28, 0x10, 0x49, 0x90, 0xaf, 0x22, 0x15, 0x09, 0xd4, 0x23, 0x4a, 0x25, 0xaa, 0x17, 0xc8, 0x44, 0xba, 0x1e, 0x7c, 0x9f, 0xaa, 0x4f, 0x42, 0x1f, 0x5a, 0x26, 0x51, 0x3b, 0x8b, 0x2a, 0xae, 0xd9, 0x39, 0x50, 0x54, 0xa9, 0x0e, 0x45, 0xa7, 0x29, 0xf1, 0xbb, 0x17, 0xa8, 0xd8, 0xda, 0x07, 0xf3, 0xcb, 0x29, 0x30, 0x22, 0xb1, 0xef, 0x6b, 0xa5, 0x02, 0x03, 0x4e, 0x1f, 0xab, 0x4c, 0xc2, 0x1b, 0xeb, 0xf4, 0x59, 0x96, 0x57, 0x16, 0x8d, 0x1e, 0x22, 0x78, 0x66, 0x59, 0x34, 0x2b, 0xd8, 0x4d, 0xf8, 0x68, 0x59, 0x72, 0xa2, 0xc2, 0xe0, 0x91, 0x88, 0x56, 0xe1, 0x2f, 0x71, 0x30, 0xb0, 0x0a, 0x7c, 0xc1, 0xe9, 0x74, 0xd2, 0xb1, 0xc5, 0x81, 0x65, 0x96, 0x3c, 0xb0, 0x2a, 0xb8, 0x2d, 0x21, 0x13, 0xfe, 0x5d, 0x9b, 0x93, 0x88, 0x9e, 0x06, 0xc2, 0xcf, 0x2b, 0xb8, 0xcd, 0xa2, 0xc0, 0x3e, 0xe2, 0x5a, 0xe0, 0xa0, 0x58, 0x4f, 0x04, 0x5d, 0x81, 0xc3, 0xd6, 0x1e, 0xe9, 0x63, 0x49, 0xe9, 0x93, 0x89, 0x94, 0x84, 0x95, 0x47, 0xa0, 0x82, 0x62, 0xc1, 0x34, 0x5a, 0x79, 0x64, 0x85, 0x86, 0x0b, 0x0e, 0xca, 0xa1, 0xbb, 0x13, 0x9e, 0x5f, 0xf0, 0xfd, 0x4a, 0x5c, 0x65, 0x4b, 0x82, 0x0b, 0xea, 0x16, 0xec, 0x11, 0x3e, 0x5c, 0x4e, 0xc1, 0x7e, 0xe0, 0xbd, 0xc5, 0x26, 0xd2, 0xc1, 0x81, 0x55, 0x8f, 0x25, 0x93, 0x38, 0x1a, 0x1c, 0x98, 0x9c, 0x07, 0xd2, 0x61, 0x68, 0xa7, 0x61, 0xa0, 0x49, 0x44, 0x63, 0x82, 0x23, 0xc1, 0xc7, 0xc0, 0xab, 0x6c, 0x52, 0x70, 0x36, 0x68, 0x06, 0xad, 0x85, 0x04, 0xb7, 0x7e, 0xad, 0x0b, 0xce, 0x0b, 0x2e, 0x0c, 0x2e, 0x09, 0x2e, 0x0f, 0xae, 0x06, 0xad, 0x2b, 0x6f, 0x8f, 0x44, 0x5d, 0x37, 0x26, 0xf6, 0xa7, 0xa5, 0x5b, 0x2c, 0x09, 0xad, 0x9e, 0xb6, 0x59, 0x9e, 0x61, 0x21, 0x43, 0xf8, 0x83, 0x25, 0x68, 0x3f, 0x6d, 0x47, 0x82, 0xc7, 0x89, 0x4e, 0x85, 0xdb, 0x59, 0x14, 0x52, 0x68, 0x33, 0x90, 0x4a, 0xe6, 0xdd, 0x13, 0x44, 0x0f, 0x37, 0x78, 0x7d, 0xa5, 0x87, 0xb2, 0x85, 0xcf, 0x32, 0x3c, 0xae, 0x2a, 0x53, 0x9b, 0x0a, 0xbf, 0xab, 0xaa, 0xd4, 0xb1, 0x92, 0x57, 0x56, 0x65, 0xea, 0x7e, 0x06, 0xf5, 0x0f, 0xf5, 0xa4, 0xed, 0x6c, 0xd4, 0xe7, 0x1c, 0xf2, 0xe1, 0x44, 0xf0, 0xe5, 0x0a, 0x8d, 0x25, 0x1a, 0x7e, 0xd6, 0xfd, 0x28, 0xda, 0x26, 0x84, 0x26, 0x13, 0xcd, 0xfc, 0x4d, 0xb2, 0x3c, 0xb7, 0xe6, 0x0b, 0x7f, 0xac, 0xb3, 0xd2, 0xa2, 0x73, 0xc8, 0x57, 0x84, 0xd6, 0x08, 0x7f, 0xac, 0x70, 0xbd, 0x8a, 0xf9, 0x28, 0xd9, 0x2b, 0x4b, 0xf8, 0x64, 0x95, 0x85, 0x8e, 0x86, 0x4e, 0xc0, 0xc7, 0x4a, 0x78, 0x59, 0xf9, 0x12, 0xbe, 0x54, 0x96, 0xdf, 0x94, 0xf0, 0x96, 0x6a, 0x59, 0xee, 0x1d, 0x25, 0xfc, 0xa2, 0x2a, 0x68, 0x40, 0x05, 0x0d, 0x4e, 0x90, 0xf0, 0x85, 0xb2, 0xc8, 0xf2, 0x7f, 0xa2, 0xd5, 0x73, 0xc2, 0xcf, 0x69, 0x31, 0xbc, 0x99, 0x2c, 0x5f, 0x26, 0xe1, 0xcd, 0x24, 0x3c, 0x99, 0xf6, 0x25, 0x79, 0x2e, 0x69, 0x11, 0xd7, 0x59, 0xe3, 0x0b, 0x6b, 0xfe, 0xbe, 0x28, 0x43, 0xfe, 0xb3, 0xed, 0xb3, 0xdf, 0x8a, 0x35, 0x64, 0x3f, 0xc1, 0x0a, 0xf9, 0x01, 0x16, 0x09, 0x22, 0xba, 0xe4, 0x3b, 0x45, 0x5a, 0x52, 0xc5, 0xb9, 0xbc, 0xaf, 0x48, 0xcb, 0xdf, 0x09, 0x6e, 0x1f, 0x00, 0x79, 0x1e, 0x72, 0xfe, 0x6a, 0x54, 0x22, 0x3f, 0x89, 0x9c, 0x67, 0xc4, 0x26, 0x4a, 0xb7, 0x42, 0x5e, 0x0a, 0x5e, 0x13, 0xe5, 0x6c, 0x46, 0xf9, 0x12, 0xf8, 0x1f, 0x88, 0x59, 0x4c, 0xc4, 0xb8, 0xbc, 0x84, 0x32, 0x7f, 0x41, 0xf9, 0x61, 0x94, 0x79, 0x01, 0x78, 0x26, 0x24, 0xef, 0x81, 0xff, 0x0b, 0xfc, 0x10, 0xe4, 0xc9, 0x71, 0x8d, 0xb5, 0x94, 0xd1, 0x02, 0x77, 0x87, 0xbc, 0x37, 0xf2, 0xfc, 0x7b, 0x91, 0x8e, 0x9d, 0xce, 0x1e, 0xef, 0xc8, 0x0f, 0xa0, 0xcc, 0x33, 0xa3, 0x1e, 0xaf, 0x3a, 0x1d, 0x3b, 0x78, 0xae, 0x08, 0x48, 0x2b, 0x9a, 0xd0, 0x8a, 0x80, 0x94, 0x32, 0x70, 0x8f, 0x49, 0x71, 0x90, 0xfc, 0x1a, 0x48, 0xa6, 0xa3, 0x1f, 0x93, 0x62, 0x22, 0xe5, 0x07, 0x21, 0x07, 0x9e, 0x26, 0xf5, 0xc3, 0x51, 0x2b, 0x56, 0xcf, 0x3a, 0x6b, 0x17, 0xe4, 0x75, 0xc1, 0x2f, 0xc7, 0x51, 0xd4, 0x50, 0x9e, 0x89, 0xbe, 0xf6, 0x28, 0x33, 0x84, 0x0f, 0x0e, 0x8e, 0xa2, 0x77, 0xf8, 0x22, 0xf0, 0x87, 0x21, 0xb9, 0x1a, 0xdc, 0x09, 0x49, 0x52, 0xb4, 0x25, 0xb3, 0xe2, 0x1d, 0x7b, 0xe1, 0x68, 0x07, 0x94, 0xd6, 0x18, 0x69, 0xe0, 0x7b, 0x56, 0x14, 0x26, 0x7f, 0x16, 0xe9, 0xad, 0xe0, 0x36, 0x5c, 0x37, 0x39, 0x22, 0xf3, 0x05, 0xc8, 0xaf, 0xc3, 0x59, 0x7f, 0x01, 0xef, 0x81, 0x72, 0x22, 0x90, 0x7f, 0x0a, 0x0e, 0xac, 0x92, 0x4f, 0x01, 0x4f, 0x8a, 0xe0, 0x94, 0x2e, 0x42, 0xfe, 0x6c, 0xc8, 0x11, 0x7b, 0xc4, 0x51, 0x7f, 0xf6, 0x25, 0x4a, 0x40, 0x64, 0xa7, 0x64, 0x59, 0xde, 0x68, 0xe1, 0x38, 0xb4, 0x3a, 0x39, 0xbe, 0x93, 0xe1, 0xee, 0x64, 0x4b, 0x07, 0x2c, 0x3d, 0xff, 0x1a, 0x25, 0x80, 0xf3, 0x69, 0xe0, 0x18, 0x2f, 0x3c, 0xf7, 0x34, 0x26, 0xc9, 0xbf, 0x40, 0x7a, 0x0b, 0xce, 0x1a, 0x0d, 0xc9, 0x1e, 0xf0, 0x1d, 0x28, 0xff, 0x46, 0xc8, 0x3d, 0x48, 0x97, 0x80, 0x73, 0x70, 0x6b, 0xc4, 0x41, 0x27, 0xf9, 0x2b, 0xd6, 0xbd, 0x40, 0xce, 0xc0, 0x2d, 0x0d, 0x1c, 0x05, 0x5e, 0x1f, 0x47, 0x9f, 0x04, 0x7f, 0x02, 0x92, 0x74, 0xf0, 0x34, 0xf0, 0x4b, 0xc0, 0x2d, 0xed, 0xda, 0x86, 0x6b, 0xb5, 0x45, 0x1a, 0x6d, 0x25, 0xa5, 0x5a, 0xf7, 0x88, 0x73, 0x3f, 0x46, 0xda, 0x1a, 0xc5, 0x56, 0x04, 0xd8, 0x47, 0xc8, 0x8f, 0x9e, 0x92, 0xac, 0x9e, 0x6a, 0x8f, 0x9c, 0xef, 0x80, 0x7f, 0x06, 0x0e, 0x2d, 0x3a, 0x33, 0x32, 0x55, 0xb6, 0xfa, 0xa8, 0x25, 0x38, 0x22, 0xf9, 0xf8, 0x1c, 0xa4, 0xa1, 0x3f, 0xd2, 0xf9, 0xe0, 0xe8, 0x5f, 0xe9, 0x6e, 0xf0, 0x8e, 0x28, 0xed, 0x77, 0x46, 0xaf, 0x26, 0x22, 0x44, 0xab, 0x1f, 0xc3, 0xda, 0xaa, 0x6a, 0x24, 0x2b, 0x07, 0x56, 0x2c, 0xa7, 0xa0, 0x0e, 0x6e, 0xf0, 0xdb, 0xc0, 0x2d, 0xdd, 0xb6, 0x74, 0x20, 0x07, 0x5c, 0x01, 0xd7, 0x50, 0xcf, 0x6d, 0x96, 0xc6, 0xe2, 0x5c, 0xb4, 0xbf, 0x54, 0x03, 0x12, 0xe4, 0xe7, 0x71, 0x48, 0x22, 0xd6, 0x0c, 0x83, 0xab, 0x3c, 0x07, 0x89, 0x0b, 0x47, 0xa1, 0xed, 0xd2, 0x3a, 0x70, 0xc4, 0x65, 0xca, 0xd6, 0x78, 0xb9, 0x0c, 0x47, 0x37, 0x82, 0x57, 0x3f, 0x46, 0x16, 0xf8, 0xbc, 0x04, 0xac, 0x5e, 0xb2, 0x46, 0xdf, 0x04, 0x5c, 0x17, 0xa3, 0x83, 0xbf, 0x2b, 0xb8, 0xd2, 0x02, 0xf2, 0xe6, 0x48, 0x27, 0xc5, 0xce, 0x4a, 0x97, 0x42, 0x8e, 0x16, 0x28, 0x8f, 0xa3, 0x45, 0xda, 0x8a, 0xa6, 0x8d, 0xa1, 0x1c, 0x44, 0xd3, 0xf2, 0xe5, 0xd6, 0x98, 0xb2, 0xcd, 0x12, 0xfe, 0x7a, 0x89, 0x19, 0xa0, 0x6f, 0x79, 0x7c, 0xad, 0x85, 0xc0, 0x57, 0x8a, 0xac, 0x6d, 0x66, 0xb5, 0x80, 0xf5, 0xf4, 0xc1, 0xd1, 0xaf, 0xac, 0xd1, 0x77, 0x46, 0xac, 0xad, 0x35, 0xfa, 0x96, 0x22, 0x4f, 0x19, 0xf8, 0x5b, 0xe0, 0xaf, 0x22, 0x7f, 0x2d, 0x8c, 0xaf, 0xbf, 0x81, 0x5b, 0x63, 0x01, 0x7a, 0xc8, 0x7d, 0xe0, 0x05, 0xe0, 0xd6, 0x8c, 0xf7, 0xff, 0xc7, 0xec, 0xfe, 0x91, 0x98, 0x5d, 0x3c, 0xef, 0xac, 0xc8, 0x5d, 0xa9, 0x33, 0x5a, 0x1e, 0x23, 0x82, 0x7f, 0x8f, 0xde, 0xc9, 0xb2, 0x22, 0x7a, 0x91, 0xb6, 0x9e, 0xcb, 0x49, 0x71, 0xbd, 0x52, 0x03, 0x6b, 0x36, 0x40, 0xba, 0x11, 0xce, 0xb5, 0x9e, 0xa7, 0xe8, 0x1d, 0xc9, 0x8b, 0xa3, 0x67, 0x44, 0xfd, 0x4a, 0x06, 0x8e, 0x42, 0xdf, 0xa4, 0x6b, 0xad, 0x39, 0xb3, 0x6a, 0x1c, 0x30, 0xc3, 0xd5, 0x19, 0x46, 0xe2, 0xaf, 0xc7, 0x04, 0x4b, 0xd6, 0xbb, 0x9e, 0x33, 0x23, 0x83, 0xaf, 0x40, 0x9d, 0xad, 0xf8, 0x60, 0xe8, 0xbf, 0x8c, 0xe7, 0xbe, 0xe4, 0xb0, 0x46, 0x2e, 0xce, 0xb5, 0x46, 0x07, 0x56, 0x02, 0x56, 0xf4, 0x70, 0x22, 0x0a, 0xd6, 0x8f, 0xeb, 0xce, 0x07, 0x1f, 0x02, 0x3e, 0xde, 0x4a, 0xa3, 0x7c, 0x8c, 0x5f, 0x6e, 0xe9, 0x6d, 0xa5, 0x38, 0x63, 0x48, 0xfa, 0x80, 0xdf, 0x68, 0xe9, 0x24, 0xd2, 0x8f, 0xa3, 0x7c, 0x2b, 0xca, 0xb6, 0x21, 0xd2, 0x49, 0xb1, 0xc8, 0x92, 0x8a, 0xfa, 0x04, 0x2d, 0x8e, 0xb3, 0x7e, 0xc4, 0x59, 0xc7, 0xad, 0xd9, 0xcc, 0x9a, 0x2b, 0xc0, 0xed, 0x90, 0x6f, 0x00, 0xff, 0x06, 0x92, 0xdf, 0x11, 0xbb, 0x8c, 0xfc, 0xc9, 0x11, 0xcc, 0x18, 0x47, 0x52, 0x17, 0x8b, 0xa3, 0x3d, 0xf1, 0xb4, 0xe2, 0xd6, 0x55, 0x12, 0x91, 0xcd, 0x48, 0x5b, 0xf1, 0xcd, 0xd7, 0x58, 0x3d, 0x6b, 0x45, 0x39, 0x23, 0x5d, 0xcf, 0xfa, 0x5e, 0x05, 0x6a, 0x9e, 0x88, 0x78, 0x46, 0xfa, 0x3f, 0x14, 0xf7, 0x2c, 0x75, 0xc5, 0xd5, 0x93, 0xa3, 0x9f, 0x77, 0x43, 0xb2, 0x13, 0xdc, 0x8a, 0x84, 0xc6, 0x9b, 0x41, 0xc9, 0x7a, 0xae, 0x25, 0x47, 0x45, 0x5b, 0xcf, 0x5f, 0xeb, 0x89, 0xf9, 0x7b, 0x23, 0xa4, 0x6f, 0xc0, 0x59, 0xf7, 0x58, 0xd1, 0xd2, 0x28, 0x47, 0xae, 0x1a, 0x03, 0x2d, 0x61, 0xf5, 0xcb, 0xd7, 0x83, 0xaf, 0x01, 0xff, 0xd5, 0xa8, 0x68, 0xfe, 0x06, 0xca, 0x3c, 0x57, 0x54, 0x74, 0x96, 0x35, 0x5e, 0xaa, 0xc6, 0x46, 0xcb, 0x18, 0x29, 0x67, 0x44, 0x48, 0x8b, 0xef, 0x6f, 0x33, 0x66, 0xf0, 0xe1, 0xb6, 0x7c, 0xa6, 0x74, 0xe8, 0xd8, 0xb5, 0x98, 0xc5, 0xae, 0xbb, 0xb3, 0x64, 0x30, 0xeb, 0x7a, 0x43, 0xc9, 0xf5, 0x37, 0xb1, 0xf9, 0x83, 0xff, 0x32, 0x6c, 0x08, 0x2b, 0x15, 0x1f, 0xdb, 0x65, 0x2c, 0x1e, 0xc7, 0xff, 0xf7, 0x53, 0x99, 0x8b, 0xf9, 0x58, 0x94, 0xa5, 0xb1, 0x4c, 0x56, 0x9f, 0xe5, 0xb3, 0x0b, 0x58, 0x2b, 0xd6, 0x81, 0x5d, 0xcc, 0xba, 0xb3, 0xab, 0x58, 0x5f, 0x76, 0x03, 0x1b, 0xc2, 0x6e, 0x67, 0xa3, 0xc4, 0xb7, 0x52, 0x2f, 0xef, 0xde, 0x2e, 0x4d, 0xbc, 0x5d, 0xa4, 0xf3, 0xbc, 0x74, 0x15, 0x8d, 0xf9, 0x59, 0x8c, 0xd5, 0x66, 0x39, 0xac, 0x31, 0x2b, 0x64, 0x17, 0xb2, 0xae, 0xec, 0x72, 0xd6, 0x8b, 0xfd, 0x85, 0x0d, 0x64, 0xb7, 0xb0, 0xe1, 0xec, 0xee, 0x44, 0x3e, 0x89, 0xe9, 0x74, 0x95, 0x14, 0x96, 0xc5, 0x1a, 0xb0, 0x02, 0xd6, 0x9a, 0x75, 0x64, 0x97, 0xb0, 0x62, 0x76, 0x35, 0xeb, 0xc7, 0x6e, 0x64, 0x43, 0xd9, 0x1d, 0x6c, 0x74, 0x45, 0x5d, 0xbc, 0x4c, 0xa6, 0xab, 0x04, 0x59, 0x0d, 0x56, 0x87, 0xe5, 0xb2, 0x26, 0xac, 0x0d, 0xeb, 0xc4, 0x8a, 0xd8, 0x15, 0xac, 0x37, 0xbb, 0x8e, 0x0d, 0x62, 0xb7, 0xb2, 0x11, 0xec, 0x1e, 0xe6, 0x61, 0x52, 0xf7, 0x4b, 0x2f, 0x49, 0x63, 0x85, 0xc5, 0xdd, 0x2f, 0x4e, 0x63, 0x83, 0x13, 0xf7, 0xa0, 0xe0, 0xad, 0x7c, 0x88, 0xd5, 0x64, 0xe7, 0xb1, 0x6c, 0xd6, 0x90, 0x35, 0x65, 0xcd, 0x59, 0x5b, 0xd6, 0x99, 0x5d, 0xca, 0x7a, 0xe0, 0x2d, 0xe7, 0x4d, 0xac, 0x84, 0xdd, 0xc9, 0xc6, 0x24, 0x72, 0xdb, 0x98, 0x83, 0xca, 0x09, 0xb3, 0x54, 0x96, 0xce, 0xea, 0xb2, 0x3c, 0x76, 0x3e, 0x6b, 0xc1, 0xda, 0xb1, 0x8b, 0x58, 0x37, 0x76, 0x25, 0xeb, 0xc3, 0xae, 0xa7, 0x52, 0x6f, 0x63, 0x23, 0xd9, 0xbd, 0x89, 0xdc, 0x76, 0xe6, 0xa4, 0x9a, 0x45, 0x58, 0x2d, 0x96, 0xc1, 0xea, 0xb1, 0x46, 0xac, 0x19, 0x6b, 0xc9, 0xda, 0xb3, 0x2e, 0xec, 0x32, 0xd6, 0x93, 0x5d, 0xcb, 0x06, 0xb0, 0x9b, 0xd9, 0x30, 0x76, 0x17, 0x1b, 0xcb, 0xee, 0xbb, 0xee, 0x2f, 0x83, 0x87, 0xc9, 0x81, 0xea, 0xf1, 0xdb, 0xae, 0x97, 0x6b, 0x54, 0x87, 0xf7, 0x1f, 0x7c, 0xe3, 0x0d, 0x72, 0xfa, 0x59, 0xf8, 0x90, 0x5b, 0x6e, 0x96, 0xb3, 0xab, 0xc3, 0x07, 0x94, 0xfc, 0xe5, 0x3a, 0x39, 0xb7, 0x3a, 0x9c, 0x0a, 0xfe, 0x8b, 0xdc, 0xf5, 0x0c, 0x5e, 0x3c, 0x78, 0xc8, 0xed, 0x37, 0xcb, 0xbd, 0xaa, 0xc3, 0x89, 0x95, 0xc8, 0x7d, 0xab, 0xc3, 0x6f, 0x11, 0xf9, 0x07, 0x54, 0x8b, 0x97, 0xf4, 0x1f, 0x22, 0x0f, 0x3e, 0x93, 0x0f, 0x15, 0x47, 0x4b, 0xaa, 0xc3, 0x6f, 0x13, 0x6d, 0x3e, 0x21, 0x89, 0x8f, 0x38, 0x43, 0x32, 0x26, 0x91, 0xbe, 0x71, 0xc8, 0x00, 0x79, 0x61, 0xb5, 0xf8, 0x6d, 0x79, 0x8d, 0xe4, 0xc9, 0xd5, 0xe4, 0xf9, 0xf2, 0xb4, 0x6a, 0xf2, 0xc6, 0xf2, 0xac, 0x6a, 0xf2, 0x02, 0x79, 0x5e, 0xb5, 0xf8, 0xed, 0xfd, 0x6e, 0xa3, 0xda, 0x56, 0x8b, 0x0f, 0xbd, 0x4d, 0x5e, 0x52, 0x1d, 0x3e, 0x4c, 0xb4, 0xea, 0xf2, 0xea, 0x70, 0x1a, 0xd6, 0x12, 0xcd, 0x10, 0x35, 0x30, 0x27, 0x49, 0xe0, 0x69, 0xe0, 0x01, 0xfc, 0xff, 0x16, 0x85, 0x46, 0xa2, 0x1d, 0xff, 0x8f, 0x4e, 0xa7, 0x71, 0x6f, 0xd2, 0xa8, 0x74, 0xd2, 0x4c, 0xe4, 0xa1, 0xd1, 0x26, 0xce, 0x13, 0xff, 0x7d, 0x4e, 0xe4, 0xb5, 0x78, 0x08, 0xdc, 0x0f, 0x1e, 0x04, 0xaf, 0x09, 0x1e, 0x46, 0x5e, 0x4e, 0xe3, 0x9e, 0xe1, 0x0a, 0xe7, 0x41, 0x6a, 0xf1, 0x74, 0xc8, 0x32, 0x68, 0x56, 0x13, 0xfb, 0xda, 0x34, 0x03, 0x09, 0x69, 0x1d, 0x70, 0x1f, 0x78, 0x14, 0x3c, 0x42, 0xb3, 0x40, 0x31, 0xcd, 0x12, 0xc3, 0xd8, 0x38, 0x36, 0x8d, 0x6d, 0x66, 0xbb, 0xd8, 0x21, 0x76, 0x92, 0x3b, 0x78, 0x0d, 0x9e, 0xc3, 0x5b, 0xf2, 0xae, 0xbc, 0x37, 0x1f, 0xcc, 0x47, 0xf1, 0x49, 0x7c, 0x16, 0x5f, 0xc4, 0x57, 0xf3, 0xcd, 0x7c, 0x17, 0x3f, 0xc4, 0x4f, 0x4a, 0x0e, 0xa9, 0x86, 0xd4, 0x54, 0xea, 0x28, 0x0d, 0x93, 0xc6, 0x49, 0xd3, 0xa4, 0x79, 0xd2, 0x32, 0x69, 0xbd, 0xb4, 0x4d, 0xda, 0x27, 0x1d, 0x95, 0x15, 0x39, 0x20, 0x67, 0xd2, 0x7d, 0x31, 0xcc, 0x91, 0x5c, 0xcc, 0xc7, 0x4a, 0x9f, 0x2a, 0xbf, 0x07, 0x56, 0xf9, 0xdd, 0xbf, 0xf2, 0x6f, 0x5b, 0x53, 0xfc, 0xb6, 0x51, 0x6b, 0x44, 0xa8, 0xcd, 0xb2, 0x69, 0x96, 0xe2, 0xd4, 0x56, 0xcc, 0x36, 0x2f, 0xb1, 0x3f, 0x6c, 0xed, 0xed, 0x2d, 0x13, 0xfb, 0xf1, 0x89, 0xfd, 0xd6, 0xa4, 0x72, 0xe8, 0xb7, 0x1a, 0xab, 0xf2, 0xbb, 0x4f, 0x95, 0xdf, 0x8b, 0x2b, 0xff, 0xd6, 0x58, 0x95, 0xdf, 0xc5, 0x55, 0x7e, 0x2f, 0xac, 0xfc, 0x5b, 0x97, 0xaa, 0xfc, 0xee, 0x5d, 0xe5, 0xf7, 0x8a, 0xca, 0xbf, 0x8d, 0x50, 0xe5, 0xfb, 0x34, 0x86, 0x57, 0x39, 0xbe, 0xaf, 0xf2, 0x6f, 0x33, 0xbf, 0x72, 0x7e, 0x73, 0x6c, 0x95, 0xdf, 0x87, 0x2a, 0xff, 0x76, 0xf4, 0xa8, 0xf2, 0x7b, 0x6d, 0xe5, 0xdf, 0xce, 0xde, 0x55, 0x7e, 0x6f, 0xa8, 0xfc, 0xdb, 0xd5, 0xaf, 0xca, 0xef, 0xcd, 0xf8, 0x2d, 0x91, 0x7e, 0xfa, 0xac, 0x1a, 0x79, 0x3a, 0x27, 0xf6, 0xd3, 0x2b, 0xe7, 0xf4, 0x1c, 0xc5, 0x6f, 0x85, 0x34, 0x38, 0x84, 0xff, 0x8e, 0x88, 0x5c, 0xbe, 0x4c, 0x6b, 0xef, 0xef, 0x67, 0xed, 0x03, 0x73, 0xac, 0x7d, 0x70, 0x7f, 0xe5, 0xb3, 0xc3, 0xc9, 0xfa, 0x42, 0xcf, 0xc3, 0xf0, 0xf8, 0x2a, 0xbf, 0x67, 0x54, 0xfe, 0x9d, 0x52, 0x58, 0xf9, 0xfc, 0x94, 0x31, 0x55, 0x7e, 0xef, 0xae, 0xf2, 0x7b, 0x67, 0x95, 0xdf, 0xc7, 0xaa, 0xfc, 0x3e, 0x52, 0xe5, 0xf7, 0xd1, 0x2a, 0xbf, 0x0f, 0x63, 0x55, 0x21, 0x7e, 0x4b, 0x6c, 0x23, 0x3d, 0xe3, 0xfb, 0xf3, 0xef, 0xd9, 0x0d, 0xb6, 0x14, 0xbd, 0xae, 0xdd, 0x67, 0x7c, 0x6e, 0xcf, 0xb6, 0xd7, 0xb3, 0xe7, 0xd8, 0x73, 0xed, 0x79, 0xf6, 0x7c, 0x7b, 0x81, 0xbd, 0xa9, 0xbd, 0x99, 0xbd, 0xb9, 0xbd, 0xa5, 0xbd, 0xad, 0x7d, 0xbf, 0xfd, 0x80, 0xfd, 0x47, 0xfb, 0x31, 0xfb, 0x0f, 0xf6, 0xa3, 0xf6, 0xef, 0xed, 0x47, 0xce, 0xf0, 0x8f, 0xb2, 0xd9, 0xbf, 0xb2, 0x7f, 0x6d, 0xff, 0x46, 0x0d, 0xa9, 0x86, 0xea, 0x52, 0x3d, 0xf0, 0x96, 0x72, 0xa8, 0x5e, 0xd5, 0xa7, 0xfa, 0xd5, 0x80, 0x1a, 0x54, 0xdf, 0xd7, 0xc6, 0xea, 0xf5, 0xf5, 0x3a, 0x7a, 0xbe, 0xc9, 0x4c, 0xd9, 0xb4, 0x9b, 0xba, 0x69, 0x9a, 0x2e, 0xd3, 0xaf, 0x2a, 0x66, 0xd8, 0xac, 0x61, 0xff, 0xd9, 0xcc, 0x35, 0x1b, 0x99, 0x05, 0xe6, 0x05, 0x66, 0x4b, 0xb3, 0xb5, 0xd9, 0xd1, 0xbc, 0xd8, 0xbc, 0xd4, 0xec, 0x6e, 0xf6, 0x30, 0xaf, 0x36, 0xfb, 0x98, 0x7f, 0x31, 0xfb, 0x9b, 0x37, 0x98, 0x83, 0xcc, 0x9b, 0xcd, 0xa1, 0xe6, 0x6d, 0xe6, 0x70, 0x73, 0xa4, 0x79, 0xb7, 0x39, 0xc6, 0xbc, 0xcf, 0x1c, 0x6f, 0x3e, 0x64, 0xbe, 0x6e, 0xae, 0x37, 0xdf, 0xa2, 0x7b, 0xf9, 0x0b, 0xad, 0x41, 0xae, 0x63, 0x03, 0x6c, 0x31, 0x5b, 0x4d, 0xbb, 0xd7, 0x9e, 0x72, 0x0e, 0x1f, 0xb0, 0x36, 0xf6, 0x76, 0xf6, 0x0b, 0x7f, 0xd5, 0x0b, 0xec, 0x2a, 0xfb, 0x97, 0xf0, 0x04, 0x3b, 0xae, 0xea, 0xf0, 0xf1, 0x12, 0x1e, 0x5e, 0x51, 0x35, 0xa6, 0xa6, 0xa8, 0x35, 0xd4, 0x9a, 0x6a, 0xaa, 0xfa, 0x9e, 0x36, 0x46, 0x8f, 0xea, 0x31, 0xbd, 0xa1, 0x11, 0x87, 0x87, 0x97, 0xe5, 0xdf, 0x65, 0x79, 0x77, 0xa5, 0x98, 0x75, 0xcc, 0x06, 0xf0, 0xee, 0x3a, 0xed, 0xd9, 0x55, 0x64, 0x5e, 0x66, 0x5e, 0x61, 0xf6, 0x32, 0xaf, 0x31, 0xfb, 0x9a, 0xd7, 0x99, 0x03, 0xcc, 0x1b, 0xcd, 0xc1, 0xe6, 0x2d, 0x66, 0x89, 0x79, 0xbb, 0x79, 0xa7, 0x39, 0xca, 0xbc, 0x07, 0x9e, 0x5f, 0x13, 0x2b, 0x3c, 0xbe, 0x5c, 0x4c, 0xd1, 0x2e, 0xd2, 0x27, 0x6b, 0x97, 0xe9, 0x8f, 0x41, 0x37, 0x32, 0xd8, 0x1d, 0x5a, 0xb1, 0x3e, 0x03, 0x7a, 0x26, 0xfa, 0x8e, 0x73, 0x37, 0xe5, 0x2a, 0xa1, 0x35, 0xd1, 0x23, 0xec, 0x79, 0xf6, 0x02, 0x7b, 0x91, 0xbd, 0xc4, 0x96, 0xb1, 0xe5, 0x6c, 0x05, 0x5b, 0xc5, 0x56, 0xb3, 0xb5, 0x6c, 0x5d, 0xf9, 0xff, 0x7d, 0x55, 0xee, 0x53, 0xc6, 0x29, 0xf7, 0x2b, 0x0f, 0x29, 0x13, 0x95, 0x29, 0xca, 0x5f, 0x95, 0xa9, 0xca, 0xdf, 0x94, 0x69, 0xca, 0xa3, 0xca, 0xc3, 0xca, 0x63, 0xfc, 0x01, 0x3e, 0x81, 0x3f, 0xc8, 0x27, 0xf2, 0x87, 0xf8, 0x24, 0xe5, 0x71, 0xfe, 0xb0, 0x32, 0x93, 0x4f, 0xe6, 0x8f, 0xf0, 0x29, 0xfc, 0xaf, 0x7c, 0x2a, 0xff, 0x1b, 0x9f, 0xa6, 0xdc, 0xce, 0x1f, 0x55, 0x86, 0xf3, 0xe9, 0xca, 0x64, 0xe5, 0x11, 0xdb, 0x61, 0x63, 0xb7, 0xb1, 0x9f, 0xae, 0xdb, 0x94, 0x3d, 0xcc, 0x16, 0xb2, 0x45, 0x6c, 0x31, 0x5b, 0xc2, 0xfe, 0xc1, 0xfe, 0xc9, 0x5e, 0x66, 0x2b, 0xd9, 0x2b, 0xec, 0x35, 0xf6, 0x3a, 0x6f, 0xc7, 0xdb, 0xf3, 0x0e, 0xfc, 0x42, 0xde, 0x85, 0x5f, 0x4c, 0xb3, 0x6b, 0x11, 0xbf, 0x94, 0x77, 0xe3, 0x97, 0xf1, 0xee, 0xfc, 0x72, 0x5e, 0xcc, 0xaf, 0xa0, 0xd9, 0xb6, 0x2f, 0xbf, 0x8d, 0x0f, 0xe3, 0xb7, 0xf3, 0xe1, 0xfc, 0x0e, 0x3e, 0x82, 0xdf, 0xc9, 0x47, 0xf2, 0xbb, 0x68, 0xfe, 0xbd, 0x9b, 0x8f, 0xe6, 0xf7, 0xf0, 0x31, 0xfc, 0x5e, 0x3e, 0x96, 0xdf, 0xc7, 0xc7, 0xf1, 0xfb, 0xc9, 0xea, 0x1a, 0xa1, 0xdc, 0x63, 0x6b, 0x6c, 0x7c, 0x6a, 0x7c, 0x41, 0x77, 0xd8, 0x81, 0xd5, 0x52, 0x4e, 0x29, 0x27, 0x95, 0x5f, 0xf8, 0x12, 0x9b, 0xcf, 0xe6, 0xb7, 0xe9, 0x36, 0xaf, 0xcd, 0xb0, 0x99, 0x36, 0x87, 0xcd, 0xa9, 0x1c, 0x57, 0xe2, 0x36, 0x66, 0xe3, 0x36, 0xc9, 0x26, 0xdb, 0x14, 0x9b, 0xdd, 0xa6, 0xf1, 0x25, 0xca, 0x4f, 0xca, 0x09, 0xe5, 0x67, 0x9b, 0xcb, 0xe6, 0xb6, 0x79, 0x6c, 0x36, 0x9b, 0xca, 0x97, 0xe0, 0x59, 0x73, 0x33, 0x3f, 0x9f, 0x37, 0xe7, 0x1d, 0x51, 0xa3, 0x1b, 0xf8, 0x40, 0x3e, 0x88, 0xdf, 0x44, 0xb3, 0xff, 0x10, 0x5e, 0x92, 0x7c, 0xbf, 0xfc, 0x51, 0x3e, 0x9d, 0x2f, 0xe1, 0xff, 0xe0, 0xcb, 0xf8, 0xcb, 0x8a, 0x5f, 0x09, 0x28, 0x21, 0xe5, 0x76, 0x65, 0xb8, 0x32, 0x9d, 0xd6, 0xaf, 0x62, 0x8c, 0x14, 0xd1, 0xde, 0x47, 0x54, 0x84, 0x5f, 0x35, 0x12, 0x65, 0xfe, 0x76, 0x89, 0x55, 0xca, 0x43, 0xdf, 0x75, 0xa4, 0xb2, 0xca, 0x4b, 0xe3, 0x28, 0xf1, 0x3f, 0x53, 0xa6, 0x28, 0xa5, 0x2b, 0xb5, 0x9a, 0x87, 0xf8, 0x7f, 0xa6, 0x44, 0xab, 0x2c, 0x8e, 0xf2, 0xfe, 0x33, 0x25, 0x72, 0x9a, 0x47, 0xc5, 0x5a, 0xc1, 0xda, 0x33, 0x48, 0x64, 0xea, 0x9b, 0x25, 0xd4, 0xd2, 0x7f, 0xa4, 0x7c, 0x39, 0xb1, 0x02, 0xe0, 0x68, 0x53, 0xab, 0xdc, 0x3f, 0x56, 0xdb, 0x25, 0x89, 0x32, 0x03, 0xd4, 0x0a, 0xdd, 0xc8, 0x06, 0xfa, 0xe3, 0xa5, 0xd2, 0x9a, 0x46, 0x39, 0x6e, 0xb3, 0xd6, 0x3b, 0xa7, 0xcb, 0x0f, 0xd1, 0xbe, 0x3b, 0x51, 0xef, 0x3f, 0xed, 0x0a, 0x11, 0x9a, 0x41, 0x8a, 0x41, 0x7d, 0xfe, 0xb4, 0x6b, 0xc4, 0x68, 0x5d, 0xd3, 0x23, 0x41, 0x7d, 0xff, 0xb4, 0xab, 0xd4, 0xa0, 0xf9, 0xaf, 0x67, 0x05, 0xf5, 0xfb, 0xd3, 0xae, 0x93, 0x4a, 0x4f, 0xca, 0x5e, 0x49, 0xd4, 0xff, 0x4f, 0xbb, 0x52, 0x1a, 0x3d, 0x7d, 0x7b, 0x57, 0xa2, 0x01, 0x7f, 0xda, 0xb5, 0xd2, 0x69, 0x4d, 0xde, 0xa7, 0x0a, 0x0d, 0xfc, 0xd3, 0xae, 0x96, 0x49, 0xeb, 0xab, 0xbe, 0x67, 0xd0, 0xa0, 0x3f, 0xe1, 0x7a, 0x12, 0x6b, 0xc3, 0x6a, 0xda, 0x82, 0xca, 0x5b, 0xca, 0x9b, 0xca, 0x06, 0xe5, 0x7d, 0xa5, 0x54, 0xd9, 0xa8, 0x6c, 0x55, 0xde, 0x56, 0x36, 0x29, 0xef, 0x28, 0x9b, 0x95, 0xd7, 0xf9, 0x7c, 0xfe, 0x1c, 0x5f, 0xc0, 0xff, 0xce, 0x17, 0xf2, 0xe7, 0xf9, 0x0b, 0xfc, 0x45, 0x65, 0x9d, 0xf2, 0x86, 0xb2, 0x5e, 0x79, 0x57, 0xd9, 0xa2, 0xbc, 0x47, 0xab, 0xff, 0xc5, 0xb0, 0x3b, 0xa4, 0x3f, 0x30, 0xf3, 0x58, 0xf3, 0x4e, 0x7a, 0x02, 0xa5, 0x11, 0x4f, 0x66, 0x89, 0xae, 0xb9, 0x84, 0x16, 0x8e, 0xaf, 0x2b, 0xa5, 0x64, 0x03, 0xfd, 0xb1, 0x79, 0xcd, 0x9a, 0x2d, 0xbb, 0xd1, 0xd6, 0x23, 0x51, 0xfe, 0x1f, 0x9c, 0xd9, 0x44, 0xfd, 0x2a, 0x5a, 0x50, 0x62, 0x85, 0xac, 0x86, 0x52, 0xa6, 0x1c, 0x52, 0xbe, 0xe1, 0xf3, 0xf8, 0xb3, 0xca, 0xb7, 0xca, 0x8f, 0xca, 0x61, 0xe5, 0x3b, 0xe5, 0x88, 0xf2, 0xbd, 0xf2, 0x35, 0x7f, 0x8c, 0xcf, 0xe0, 0x8f, 0xf3, 0x99, 0xfc, 0x09, 0xb2, 0x93, 0x66, 0xf3, 0x39, 0xfc, 0x69, 0x3e, 0x97, 0x3f, 0xa3, 0x1c, 0x55, 0x7e, 0x50, 0x8e, 0xf1, 0x27, 0xf9, 0x53, 0x7f, 0xb0, 0x17, 0xc5, 0x95, 0xf1, 0x74, 0xff, 0xed, 0x67, 0x7b, 0xd5, 0x27, 0xfb, 0x7f, 0xe0, 0xca, 0xff, 0xbe, 0xb6, 0xfc, 0x91, 0x2b, 0x0f, 0x66, 0x8d, 0xf9, 0x8d, 0xca, 0xb3, 0xca, 0x7c, 0xe5, 0x39, 0x7e, 0x8b, 0x32, 0xaf, 0xd2, 0xaa, 0x68, 0x64, 0x95, 0x15, 0xd1, 0x38, 0x5a, 0x0d, 0x2d, 0x53, 0x5e, 0x55, 0x5e, 0x53, 0xd6, 0x2a, 0xff, 0x54, 0x5e, 0x56, 0x96, 0x2b, 0x2b, 0x94, 0x57, 0x94, 0xd5, 0xca, 0x4a, 0x65, 0x95, 0xb2, 0x46, 0x59, 0xa0, 0xfc, 0x5d, 0x59, 0x48, 0xab, 0xa6, 0xfb, 0x69, 0x35, 0x75, 0x97, 0xf2, 0xbc, 0xb2, 0x48, 0x79, 0x41, 0x59, 0xac, 0xbc, 0x48, 0x57, 0x7b, 0x49, 0x59, 0xaa, 0xfc, 0x43, 0xd8, 0x37, 0x54, 0xb7, 0x81, 0xa4, 0x94, 0x83, 0xf8, 0x60, 0x81, 0x64, 0xf3, 0x21, 0x4c, 0xa1, 0x3a, 0x96, 0x50, 0xc3, 0x3e, 0xc0, 0xa7, 0x33, 0x3b, 0xd5, 0xee, 0x35, 0x16, 0x12, 0xb5, 0x63, 0x19, 0xe2, 0xf3, 0x41, 0xac, 0x36, 0xea, 0x98, 0xa5, 0x3c, 0xae, 0x3c, 0x43, 0xd6, 0xa9, 0x44, 0xcf, 0xa4, 0x2c, 0xe5, 0x19, 0x3e, 0x50, 0x99, 0xa5, 0x3c, 0xa9, 0xcc, 0x56, 0x9e, 0xe2, 0x43, 0x94, 0x27, 0xaa, 0xbd, 0x6a, 0x9c, 0xa3, 0x3c, 0xad, 0xcc, 0xa5, 0xab, 0xa1, 0x6d, 0xac, 0x16, 0xa0, 0x12, 0xed, 0x54, 0xa3, 0xc1, 0x54, 0xa3, 0x21, 0xfc, 0x16, 0xaa, 0x45, 0x09, 0x27, 0x8b, 0x08, 0xd7, 0xaf, 0x81, 0xeb, 0xa7, 0x2a, 0xf3, 0x94, 0x85, 0xb4, 0x5e, 0x43, 0x2b, 0x29, 0xcf, 0x28, 0xff, 0x28, 0xbf, 0x32, 0xdd, 0xd5, 0x13, 0x67, 0x5d, 0x3f, 0x26, 0xd5, 0x86, 0x3f, 0x4c, 0x35, 0x11, 0xed, 0xfd, 0x4f, 0xbe, 0x9c, 0xbf, 0xcc, 0x57, 0xf0, 0x95, 0x7c, 0x15, 0x7f, 0x85, 0x6c, 0xfb, 0x57, 0xf9, 0x1a, 0xfe, 0x9a, 0x55, 0x1f, 0xba, 0xb3, 0x99, 0x15, 0x2d, 0x85, 0x76, 0x4a, 0xb4, 0x52, 0x72, 0x9d, 0x4a, 0xc8, 0x90, 0xb9, 0x9b, 0x6a, 0xa6, 0xd1, 0x7d, 0x4d, 0x67, 0x3e, 0xb4, 0x52, 0x0c, 0xb5, 0xac, 0x83, 0x5a, 0xd6, 0x45, 0x2b, 0xd5, 0x43, 0x5d, 0x73, 0xac, 0x56, 0x3a, 0xdb, 0x3a, 0xb6, 0xaa, 0x2e, 0x50, 0x7f, 0xd1, 0x79, 0xe5, 0x2d, 0x42, 0x6d, 0x89, 0x36, 0xa1, 0xd6, 0x9d, 0x57, 0xa1, 0x0d, 0xa2, 0x3f, 0x6f, 0x44, 0x2b, 0xdd, 0xc6, 0x47, 0x51, 0x8d, 0x1e, 0xe0, 0x93, 0x99, 0x41, 0xf9, 0xfc, 0xcc, 0x87, 0x3e, 0xf1, 0x53, 0xdd, 0x5f, 0xa4, 0x56, 0x7a, 0x89, 0x7a, 0x37, 0xc3, 0xb2, 0xd9, 0xcc, 0x19, 0xe6, 0xe3, 0xfc, 0xa8, 0x39, 0xd3, 0x7c, 0x82, 0xff, 0x60, 0xce, 0x32, 0x9f, 0x34, 0x67, 0x9b, 0x4f, 0x99, 0x73, 0xcc, 0xa7, 0xcd, 0xb9, 0xe6, 0x33, 0xb6, 0xa8, 0x39, 0x8f, 0x1f, 0x33, 0x9f, 0xe5, 0x3f, 0x9a, 0xf3, 0xcd, 0xe7, 0xf8, 0x71, 0x73, 0x01, 0xff, 0x89, 0x9f, 0xe0, 0x3f, 0xb3, 0x29, 0xec, 0xaf, 0x6c, 0x2a, 0xfb, 0x1b, 0x9b, 0xc6, 0x1e, 0x65, 0xd3, 0xd9, 0x63, 0x6c, 0x06, 0x7b, 0x9c, 0xcd, 0x64, 0xb3, 0xd8, 0x93, 0x6c, 0x36, 0x7b, 0x8a, 0xcd, 0x61, 0x4f, 0xb3, 0xb9, 0xec, 0x19, 0x36, 0x8f, 0x3d, 0xcb, 0xe6, 0xb3, 0xe7, 0xd8, 0x02, 0xf6, 0x77, 0xe5, 0x2b, 0xe5, 0xa0, 0xf2, 0xb1, 0x72, 0x97, 0x72, 0xa7, 0x32, 0x52, 0xd9, 0xa9, 0x7c, 0xa2, 0xec, 0x52, 0x3e, 0x55, 0x76, 0x2b, 0x9f, 0xfd, 0xa6, 0xec, 0xd3, 0xb3, 0xfc, 0xde, 0xa6, 0x7c, 0xa8, 0x6c, 0x67, 0x4f, 0x50, 0xfe, 0xdd, 0x62, 0x83, 0xcd, 0x26, 0x03, 0x33, 0xd2, 0x69, 0xae, 0x14, 0x68, 0x91, 0x88, 0xc5, 0xf2, 0xd3, 0xac, 0x17, 0xa4, 0x95, 0x4b, 0x94, 0xa5, 0xd0, 0xd3, 0x38, 0x8d, 0x9d, 0x47, 0xf3, 0x6b, 0x26, 0x59, 0xe0, 0xd9, 0xac, 0x2e, 0xab, 0xc7, 0xea, 0x53, 0xcb, 0x37, 0x60, 0x79, 0xac, 0x11, 0x70, 0xde, 0x56, 0xec, 0x1a, 0x76, 0x2d, 0xad, 0x06, 0x06, 0xd0, 0xd3, 0x6c, 0x30, 0xbb, 0x99, 0xdd, 0x42, 0x96, 0xd1, 0x30, 0x76, 0x3b, 0x1b, 0xce, 0x46, 0xb1, 0xf1, 0x6c, 0x02, 0x7b, 0x90, 0x4d, 0x64, 0x0f, 0xb1, 0x49, 0x18, 0xaf, 0xd5, 0xb4, 0x42, 0xce, 0xd0, 0xac, 0xb3, 0x8f, 0xec, 0x61, 0xb0, 0x11, 0x48, 0xab, 0x2a, 0x9e, 0x02, 0xdd, 0x12, 0x4f, 0x81, 0x6c, 0xf1, 0x54, 0x51, 0x9b, 0xa8, 0x17, 0xab, 0x5d, 0xd5, 0x4b, 0xd4, 0x4b, 0xd5, 0x6e, 0xea, 0x65, 0x6a, 0x77, 0xf5, 0x72, 0xf5, 0x7e, 0xf5, 0x09, 0x75, 0x96, 0x3a, 0x5b, 0x7d, 0x4a, 0x9d, 0xa3, 0x3e, 0xad, 0x85, 0xb4, 0x1c, 0xad, 0x81, 0xd6, 0x50, 0xcb, 0x13, 0xff, 0x3f, 0x4e, 0xbb, 0xf7, 0xf4, 0xff, 0xc0, 0x14, 0xf3, 0xb4, 0xfa, 0x5e, 0x45, 0xa9, 0x03, 0x93, 0x4a, 0xad, 0xfb, 0x5b, 0xa5, 0xaa, 0x2f, 0xa8, 0xff, 0x54, 0x97, 0xab, 0x2f, 0xab, 0x2b, 0xd5, 0x55, 0xea, 0x2b, 0xea, 0x6a, 0xf5, 0xd5, 0xca, 0xd7, 0x31, 0x62, 0x46, 0x4d, 0x23, 0xd5, 0xa8, 0x65, 0x9c, 0x67, 0xa4, 0x1b, 0x19, 0x46, 0xa6, 0x51, 0x5b, 0x5c, 0x4d, 0x1b, 0x43, 0x1a, 0xb5, 0x95, 0x5d, 0xaf, 0x16, 0x9c, 0x93, 0x7a, 0x54, 0xa1, 0x71, 0x67, 0xd0, 0xa2, 0xb3, 0xd2, 0x9a, 0x2a, 0xf4, 0xde, 0xb9, 0x48, 0x0b, 0x9e, 0x41, 0x4d, 0xc4, 0x7f, 0xd0, 0xab, 0x4c, 0x54, 0xcb, 0xb3, 0x90, 0x91, 0x55, 0x99, 0xe4, 0x27, 0xe9, 0x7e, 0xfc, 0xd4, 0x56, 0x4d, 0x18, 0x53, 0xbb, 0xa8, 0x57, 0x50, 0x7b, 0x5e, 0xa9, 0xf6, 0x61, 0x4e, 0x6a, 0xab, 0xfb, 0x59, 0x50, 0x9d, 0xa9, 0x3e, 0xcd, 0x42, 0xd4, 0x52, 0x2f, 0xb0, 0x54, 0x75, 0x99, 0xfa, 0x2a, 0xab, 0xa5, 0xbe, 0xa6, 0x6e, 0xa0, 0xb6, 0xdd, 0xaa, 0x6e, 0x65, 0x8d, 0xd4, 0x4f, 0xd4, 0xfd, 0x2c, 0x9f, 0x5a, 0x2d, 0xc4, 0x0a, 0xb5, 0xfa, 0x5a, 0x3e, 0x6b, 0x8d, 0xff, 0xce, 0xd9, 0x89, 0x7a, 0xe8, 0x5e, 0xd6, 0x19, 0xff, 0x8b, 0xf3, 0x22, 0x6a, 0xc5, 0x18, 0xbb, 0xcc, 0xa8, 0x41, 0xad, 0xd7, 0xdd, 0xa8, 0x63, 0xe4, 0xb2, 0xde, 0xe6, 0x62, 0x73, 0x31, 0xad, 0x14, 0x4f, 0x5b, 0x22, 0x13, 0xd0, 0x67, 0x29, 0xff, 0xa3, 0x7a, 0x48, 0x34, 0x93, 0x8a, 0xff, 0xed, 0xc6, 0xe4, 0x27, 0xe5, 0x67, 0x98, 0x21, 0xbe, 0x0b, 0xcb, 0xbc, 0xe2, 0xbf, 0x7e, 0x31, 0x1f, 0xf5, 0x65, 0x13, 0x16, 0xa0, 0xda, 0xf4, 0xa1, 0x6b, 0x5f, 0xaf, 0x5e, 0xcf, 0xea, 0xe3, 0xbf, 0x1b, 0xe6, 0x50, 0x0f, 0xde, 0xcf, 0x1a, 0xa0, 0x4e, 0x0d, 0xa9, 0xdf, 0x5e, 0x60, 0xe7, 0x53, 0x9d, 0x36, 0xb0, 0x0b, 0xa8, 0x6f, 0xde, 0x67, 0x5d, 0x51, 0x9b, 0x4b, 0xd5, 0x32, 0xb5, 0x8c, 0x5d, 0xad, 0x9e, 0x54, 0x4f, 0xb2, 0xde, 0x9a, 0x4d, 0xb3, 0xb1, 0x6b, 0xf0, 0x3f, 0x11, 0xfb, 0xa0, 0x7e, 0x7d, 0xf1, 0x1f, 0x4d, 0x07, 0xa2, 0x96, 0x37, 0x6a, 0x2d, 0xb5, 0x96, 0x6c, 0x90, 0xd6, 0x5d, 0xeb, 0xce, 0x6e, 0xd2, 0x46, 0x68, 0x23, 0xd8, 0x60, 0xfc, 0x2f, 0xd3, 0x9b, 0x51, 0xef, 0xa1, 0xda, 0x34, 0x6d, 0x1a, 0x1b, 0x81, 0xda, 0xdf, 0x49, 0xb5, 0xcf, 0x65, 0x23, 0xf1, 0x5f, 0x03, 0x1f, 0x16, 0x71, 0x4e, 0x6c, 0x72, 0x85, 0xe6, 0x6f, 0x4d, 0x68, 0x7e, 0xca, 0xff, 0xf1, 0xfb, 0xf9, 0x5f, 0xea, 0xa2, 0xb0, 0x60, 0x27, 0xd2, 0xac, 0xff, 0xbf, 0xd4, 0xc6, 0xf2, 0x75, 0xe7, 0xff, 0x6d, 0x9d, 0x94, 0xa8, 0x2d, 0x63, 0x22, 0xae, 0xbd, 0x42, 0x2b, 0xff, 0xbf, 0x70, 0x5f, 0xff, 0xab, 0xf9, 0xa9, 0x39, 0xb5, 0xe5, 0xef, 0x5e, 0x8b, 0xb0, 0x27, 0x80, 0x36, 0xfc, 0xee, 0xa7, 0xbf, 0x32, 0x0c, 0x2b, 0x77, 0x0b, 0xed, 0xb6, 0xd3, 0x2a, 0xa3, 0x2b, 0xf5, 0x67, 0x2b, 0x3a, 0xaf, 0x3d, 0xfd, 0xee, 0x87, 0x7e, 0x6c, 0xc5, 0xb8, 0xbc, 0x58, 0xac, 0x08, 0xa5, 0x96, 0xac, 0x35, 0x1b, 0x43, 0xe7, 0x4e, 0xa1, 0xfa, 0xcc, 0xa1, 0xab, 0x2e, 0x61, 0x2b, 0xd8, 0x5a, 0x5a, 0x6f, 0x6d, 0xa5, 0x9e, 0xdf, 0xc3, 0x0e, 0xb0, 0xc3, 0xec, 0xb8, 0x78, 0x64, 0x72, 0x0f, 0x8f, 0xf0, 0x34, 0x9e, 0xcd, 0xf3, 0xb8, 0xf8, 0xff, 0xbf, 0x9d, 0x69, 0x85, 0xd1, 0x93, 0xf7, 0xe5, 0x03, 0x68, 0x7d, 0x57, 0x42, 0xeb, 0x8b, 0xd1, 0xb4, 0x96, 0x98, 0x48, 0xeb, 0x86, 0xe9, 0x64, 0x3d, 0xcd, 0x25, 0x9b, 0x62, 0x31, 0xad, 0x19, 0x56, 0xf1, 0xb5, 0x7c, 0x03, 0xdf, 0xcc, 0x4b, 0xf9, 0x0e, 0xbe, 0x9b, 0xef, 0xe3, 0x07, 0xf9, 0x61, 0x7e, 0x8c, 0x9f, 0x94, 0x24, 0x49, 0x93, 0x5c, 0x52, 0x40, 0x8a, 0x49, 0x69, 0x52, 0x96, 0x94, 0x43, 0xd7, 0x97, 0x11, 0xed, 0xdb, 0x0e, 0xfb, 0x6b, 0xd9, 0xe5, 0xd8, 0x5f, 0xc7, 0x8a, 0xb1, 0xef, 0x4f, 0xb6, 0xa0, 0xd8, 0x0f, 0xb0, 0xad, 0xc5, 0xfe, 0x06, 0xd6, 0x13, 0xfb, 0x81, 0xac, 0x37, 0xf6, 0xa3, 0xa4, 0x67, 0xc5, 0x9e, 0x7f, 0x8f, 0xd8, 0xe1, 0x56, 0xca, 0x30, 0xed, 0xb8, 0xd8, 0xdb, 0x6a, 0x6a, 0xc7, 0xc4, 0x5e, 0x8f, 0x6a, 0x3f, 0x62, 0x1f, 0x63, 0x97, 0x60, 0x5f, 0x43, 0xfb, 0x09, 0xfb, 0x9a, 0xc0, 0x39, 0x5b, 0xe9, 0xa9, 0xec, 0x52, 0xec, 0x6b, 0xd1, 0x7a, 0x46, 0xec, 0xd3, 0xf0, 0xad, 0xf5, 0x56, 0xfa, 0x79, 0xb4, 0xda, 0x15, 0xfb, 0x74, 0xed, 0x04, 0xf6, 0xb5, 0xa5, 0xf9, 0xd8, 0x67, 0x69, 0x27, 0xb1, 0xaf, 0x23, 0xfd, 0x1d, 0xfb, 0x6c, 0xed, 0x17, 0xec, 0xeb, 0x6a, 0x3f, 0x63, 0x5f, 0x5f, 0x5a, 0x80, 0x7d, 0x8e, 0xf4, 0x1c, 0xf6, 0x0d, 0xc8, 0x02, 0x93, 0x10, 0xcb, 0x2c, 0x51, 0x6d, 0xdb, 0x12, 0xbf, 0x96, 0x75, 0x21, 0x7e, 0x1d, 0xbb, 0x98, 0x78, 0x7f, 0x76, 0x05, 0xf1, 0x01, 0xb6, 0xd7, 0x88, 0xdf, 0xc0, 0xae, 0x24, 0x3e, 0xd0, 0xb6, 0x85, 0xf8, 0x4d, 0xec, 0x6a, 0xe2, 0xa3, 0x6c, 0xef, 0x13, 0xbf, 0x5b, 0x9a, 0x43, 0x56, 0xf6, 0xf7, 0xec, 0x2a, 0x26, 0xd1, 0xdd, 0x1d, 0x66, 0x12, 0xdd, 0x5b, 0x19, 0x93, 0xe8, 0xce, 0xbe, 0x25, 0x1e, 0x63, 0x1d, 0x88, 0xd7, 0xd0, 0xbe, 0x23, 0x5e, 0x93, 0x5d, 0x48, 0x3c, 0x95, 0x75, 0x24, 0x5e, 0x8b, 0x75, 0x22, 0x9e, 0xc6, 0x3a, 0xff, 0x3f, 0x75, 0x9d, 0x3d, 0x4c, 0x14, 0x51, 0x14, 0x85, 0xef, 0x9b, 0xdd, 0xc1, 0x39, 0x79, 0xa8, 0x89, 0x31, 0x11, 0x08, 0x2c, 0x2c, 0x11, 0x58, 0x14, 0x0a, 0x44, 0x29, 0x80, 0xca, 0xc2, 0xc4, 0x86, 0x8a, 0xca, 0xc0, 0x16, 0x20, 0x9d, 0x35, 0xb1, 0x20, 0x01, 0xfc, 0xab, 0xec, 0x6d, 0x04, 0x45, 0x05, 0xe5, 0x47, 0xf9, 0xdb, 0x05, 0x15, 0xec, 0x28, 0x21, 0x10, 0x7e, 0x0a, 0x0d, 0x09, 0x8d, 0x89, 0x0d, 0x95, 0x60, 0x43, 0x01, 0x9e, 0x7b, 0x91, 0x4d, 0x24, 0xda, 0x9c, 0xef, 0x65, 0x36, 0xf3, 0xde, 0xcd, 0x3d, 0x67, 0xde, 0x26, 0xf3, 0x8a, 0xa1, 0x26, 0xe5, 0x36, 0xb5, 0x3c, 0xfa, 0x49, 0xad, 0x08, 0x86, 0xa8, 0x95, 0xd1, 0x3e, 0xb5, 0x2a, 0x78, 0x43, 0x4d, 0x45, 0xbf, 0xa8, 0xd5, 0xd1, 0x1e, 0xf5, 0x6a, 0xf0, 0x9a, 0x5a, 0x13, 0xbc, 0xa2, 0xd6, 0x86, 0x2b, 0xd4, 0xba, 0xe8, 0x90, 0xda, 0x18, 0xae, 0x52, 0x9b, 0xc2, 0x35, 0xea, 0x33, 0x3b, 0x6f, 0x6c, 0x63, 0xed, 0xf7, 0x98, 0xbe, 0x1e, 0x79, 0x24, 0x4f, 0x99, 0xe9, 0xe7, 0x4c, 0xee, 0x84, 0x64, 0x64, 0x51, 0x96, 0x64, 0x59, 0x36, 0x65, 0x5b, 0xbe, 0xcb, 0xae, 0xec, 0xcb, 0x01, 0xd3, 0x93, 0xe7, 0xf2, 0xdd, 0x05, 0xe6, 0x27, 0xe1, 0x2e, 0xbb, 0x2b, 0x4c, 0x50, 0x83, 0x6b, 0x76, 0x37, 0x99, 0xa1, 0x16, 0xd7, 0xea, 0xee, 0x1c, 0xa7, 0x48, 0x1e, 0xb2, 0x5b, 0x3d, 0xec, 0xd7, 0x07, 0x63, 0x07, 0x67, 0x55, 0x76, 0xca, 0x63, 0xe3, 0xdd, 0x60, 0x5c, 0xe9, 0xf6, 0xf0, 0x59, 0x19, 0x96, 0x60, 0x5e, 0x89, 0x22, 0x4c, 0x1a, 0x8b, 0x31, 0x65, 0x2c, 0xc1, 0xb4, 0x31, 0x81, 0x8c, 0xb1, 0x14, 0x33, 0xc6, 0x32, 0xcc, 0x1a, 0x93, 0xc8, 0x1a, 0xcb, 0x31, 0x67, 0xac, 0xc4, 0x47, 0x63, 0x15, 0x3e, 0x19, 0x53, 0x58, 0x30, 0x56, 0x63, 0xd1, 0x58, 0x83, 0x2f, 0xc6, 0x5a, 0xe9, 0x55, 0xff, 0x30, 0x68, 0x4f, 0x50, 0x9f, 0xf9, 0xd7, 0x6f, 0xfe, 0x3d, 0x50, 0x9f, 0x82, 0x31, 0x75, 0x08, 0x63, 0xea, 0x0d, 0xde, 0xaa, 0x2b, 0x78, 0xa1, 0xae, 0xe0, 0xa5, 0xba, 0x02, 0xed, 0x78, 0x02, 0xda, 0xeb, 0x52, 0x68, 0x67, 0xcb, 0xa0, 0x5d, 0x4e, 0x62, 0x58, 0x5d, 0xc1, 0x88, 0xfa, 0x81, 0x77, 0xea, 0x07, 0x46, 0xd5, 0x0f, 0x8c, 0xab, 0x1f, 0x98, 0x50, 0x27, 0xf0, 0x5e, 0x9d, 0xb0, 0x9d, 0x97, 0x59, 0x27, 0x6f, 0xd9, 0x19, 0x5d, 0x4a, 0xae, 0x49, 0x33, 0x2b, 0x8b, 0x38, 0xbe, 0x28, 0xf5, 0x96, 0xaa, 0xeb, 0x9a, 0x1e, 0xb9, 0xa1, 0xf5, 0xd8, 0x49, 0x4a, 0x43, 0x6e, 0xd4, 0xa5, 0x19, 0xb3, 0xd1, 0xfd, 0xdc, 0xb5, 0x3a, 0xce, 0xd0, 0x21, 0xdd, 0xf6, 0xad, 0xf8, 0x42, 0x49, 0xcb, 0x86, 0x7c, 0xd3, 0xe7, 0x3d, 0x9e, 0x8e, 0x0d, 0xe8, 0x5e, 0xe9, 0xe7, 0xfd, 0x94, 0x9f, 0xf6, 0x33, 0x7e, 0xd6, 0x67, 0x7c, 0xd6, 0xcf, 0xe1, 0x0c, 0x80, 0x08, 0x79, 0x38, 0x8b, 0x73, 0x38, 0xcf, 0x19, 0xf5, 0xf4, 0xb9, 0x5d, 0xd6, 0xe5, 0xab, 0xec, 0xc8, 0x8f, 0xe0, 0xc9, 0x7f, 0xbe, 0x6d, 0x7f, 0x04, 0x81, 0x43, 0x80, 0x18, 0xe2, 0x08, 0x71, 0x49, 0x4f, 0x75, 0xdc, 0x86, 0xed, 0x40, 0x45, 0x5c, 0xb9, 0x58, 0xfc, 0xbf, 0x57, 0xd3, 0xff, 0x19, 0xdb, 0xd5, 0xc5, 0x76, 0xd6, 0x98, 0xdd, 0xb7, 0xf9, 0xe7, 0xbe, 0x93, 0x93, 0xa1, 0xf8, 0xc9, 0xea, 0xf6, 0xeb, 0x56, 0x6e, 0xd6, 0x02, 0x7d, 0xf7, 0x17, 0x4f, 0x9f, 0xaa, 0xd8, 0xf1, 0x6a, 0x3b, 0xeb, 0xfc, 0xab, 0xa2, 0xdf, 0x7f, 0x02, 0x0c, 0xed }; #include uint8* extractCafeDefaultFont(sint32* size) { uint8* uncompressed = (uint8*)malloc(1024 * 1024 * 1); uLongf uncompressedSize = 1024 * 1024 * 1; uncompress(uncompressed, &uncompressedSize, cafeDefaultFontZLIB, sizeof(cafeDefaultFontZLIB)); *size = uncompressedSize; return uncompressed; } ================================================ FILE: src/resource/IconsFontAwesome5.h ================================================ // Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for language C++11 // from https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/metadata/icons.yml // for use with https://github.com/FortAwesome/Font-Awesome/blob/master/webfonts/fa-solid-900.ttf, https://github.com/FortAwesome/Font-Awesome/blob/master/webfonts/fa-regular-400.ttf, #pragma once #define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf" #define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf" #define ICON_MIN_FA 0xf000 #define ICON_MAX_FA 0xf82f #define ICON_FA_NOTES_MEDICAL u8"\uf481" #define ICON_FA_CLOUD_SHOWERS_HEAVY u8"\uf740" #define ICON_FA_SMS u8"\uf7cd" #define ICON_FA_COPY u8"\uf0c5" #define ICON_FA_CHEVRON_CIRCLE_RIGHT u8"\uf138" #define ICON_FA_CROSSHAIRS u8"\uf05b" #define ICON_FA_BROADCAST_TOWER u8"\uf519" #define ICON_FA_EXTERNAL_LINK_SQUARE_ALT u8"\uf360" #define ICON_FA_SMOKING u8"\uf48d" #define ICON_FA_KISS_BEAM u8"\uf597" #define ICON_FA_CHESS_BISHOP u8"\uf43a" #define ICON_FA_TV u8"\uf26c" #define ICON_FA_CROP_ALT u8"\uf565" #define ICON_FA_TH u8"\uf00a" #define ICON_FA_RECYCLE u8"\uf1b8" #define ICON_FA_SMILE u8"\uf118" #define ICON_FA_FAX u8"\uf1ac" #define ICON_FA_DRAFTING_COMPASS u8"\uf568" #define ICON_FA_USER_INJURED u8"\uf728" #define ICON_FA_SCREWDRIVER u8"\uf54a" #define ICON_FA_DHARMACHAKRA u8"\uf655" #define ICON_FA_PRINT u8"\uf02f" #define ICON_FA_BABY_CARRIAGE u8"\uf77d" #define ICON_FA_CARET_UP u8"\uf0d8" #define ICON_FA_SCHOOL u8"\uf549" #define ICON_FA_SORT_NUMERIC_UP u8"\uf163" #define ICON_FA_TRUCK_LOADING u8"\uf4de" #define ICON_FA_LIST u8"\uf03a" #define ICON_FA_UPLOAD u8"\uf093" #define ICON_FA_LAPTOP_MEDICAL u8"\uf812" #define ICON_FA_EXPAND_ARROWS_ALT u8"\uf31e" #define ICON_FA_ADJUST u8"\uf042" #define ICON_FA_VENUS u8"\uf221" #define ICON_FA_HEADING u8"\uf1dc" #define ICON_FA_ARROW_DOWN u8"\uf063" #define ICON_FA_BICYCLE u8"\uf206" #define ICON_FA_TIRED u8"\uf5c8" #define ICON_FA_AIR_FRESHENER u8"\uf5d0" #define ICON_FA_BACON u8"\uf7e5" #define ICON_FA_SYNC u8"\uf021" #define ICON_FA_PAPER_PLANE u8"\uf1d8" #define ICON_FA_VOLLEYBALL_BALL u8"\uf45f" #define ICON_FA_RIBBON u8"\uf4d6" #define ICON_FA_HAND_LIZARD u8"\uf258" #define ICON_FA_CLOCK u8"\uf017" #define ICON_FA_SUN u8"\uf185" #define ICON_FA_FILE_POWERPOINT u8"\uf1c4" #define ICON_FA_MICROCHIP u8"\uf2db" #define ICON_FA_TRASH_RESTORE_ALT u8"\uf82a" #define ICON_FA_GRADUATION_CAP u8"\uf19d" #define ICON_FA_ANGLE_DOUBLE_DOWN u8"\uf103" #define ICON_FA_INFO_CIRCLE u8"\uf05a" #define ICON_FA_TAGS u8"\uf02c" #define ICON_FA_FILE_ALT u8"\uf15c" #define ICON_FA_EQUALS u8"\uf52c" #define ICON_FA_DIRECTIONS u8"\uf5eb" #define ICON_FA_FILE_INVOICE u8"\uf570" #define ICON_FA_SEARCH u8"\uf002" #define ICON_FA_BIBLE u8"\uf647" #define ICON_FA_FLASK u8"\uf0c3" #define ICON_FA_CALENDAR_TIMES u8"\uf273" #define ICON_FA_GREATER_THAN_EQUAL u8"\uf532" #define ICON_FA_SLIDERS_H u8"\uf1de" #define ICON_FA_EYE_SLASH u8"\uf070" #define ICON_FA_BIRTHDAY_CAKE u8"\uf1fd" #define ICON_FA_FEATHER_ALT u8"\uf56b" #define ICON_FA_DNA u8"\uf471" #define ICON_FA_BASEBALL_BALL u8"\uf433" #define ICON_FA_HOSPITAL u8"\uf0f8" #define ICON_FA_COINS u8"\uf51e" #define ICON_FA_HRYVNIA u8"\uf6f2" #define ICON_FA_TEMPERATURE_HIGH u8"\uf769" #define ICON_FA_FONT_AWESOME_LOGO_FULL u8"\uf4e6" #define ICON_FA_PASSPORT u8"\uf5ab" #define ICON_FA_TAG u8"\uf02b" #define ICON_FA_SHOPPING_CART u8"\uf07a" #define ICON_FA_AWARD u8"\uf559" #define ICON_FA_WINDOW_RESTORE u8"\uf2d2" #define ICON_FA_PHONE u8"\uf095" #define ICON_FA_FLAG u8"\uf024" #define ICON_FA_STETHOSCOPE u8"\uf0f1" #define ICON_FA_DICE_D6 u8"\uf6d1" #define ICON_FA_OUTDENT u8"\uf03b" #define ICON_FA_LONG_ARROW_ALT_RIGHT u8"\uf30b" #define ICON_FA_PIZZA_SLICE u8"\uf818" #define ICON_FA_ADDRESS_CARD u8"\uf2bb" #define ICON_FA_PARAGRAPH u8"\uf1dd" #define ICON_FA_MALE u8"\uf183" #define ICON_FA_HISTORY u8"\uf1da" #define ICON_FA_HAMBURGER u8"\uf805" #define ICON_FA_SEARCH_PLUS u8"\uf00e" #define ICON_FA_FIRE_ALT u8"\uf7e4" #define ICON_FA_LIFE_RING u8"\uf1cd" #define ICON_FA_SHARE u8"\uf064" #define ICON_FA_ALIGN_JUSTIFY u8"\uf039" #define ICON_FA_TOILET_PAPER u8"\uf71e" #define ICON_FA_BATTERY_THREE_QUARTERS u8"\uf241" #define ICON_FA_OBJECT_UNGROUP u8"\uf248" #define ICON_FA_BRIEFCASE u8"\uf0b1" #define ICON_FA_OIL_CAN u8"\uf613" #define ICON_FA_THERMOMETER_FULL u8"\uf2c7" #define ICON_FA_PLANE u8"\uf072" #define ICON_FA_HEARTBEAT u8"\uf21e" #define ICON_FA_UNLINK u8"\uf127" #define ICON_FA_WINDOW_MAXIMIZE u8"\uf2d0" #define ICON_FA_HEADPHONES u8"\uf025" #define ICON_FA_STEP_BACKWARD u8"\uf048" #define ICON_FA_DRAGON u8"\uf6d5" #define ICON_FA_MICROPHONE_SLASH u8"\uf131" #define ICON_FA_USER_PLUS u8"\uf234" #define ICON_FA_WRENCH u8"\uf0ad" #define ICON_FA_AMBULANCE u8"\uf0f9" #define ICON_FA_ETHERNET u8"\uf796" #define ICON_FA_EGG u8"\uf7fb" #define ICON_FA_WIND u8"\uf72e" #define ICON_FA_UNIVERSAL_ACCESS u8"\uf29a" #define ICON_FA_BURN u8"\uf46a" #define ICON_FA_HAND_HOLDING_HEART u8"\uf4be" #define ICON_FA_DICE_ONE u8"\uf525" #define ICON_FA_KEYBOARD u8"\uf11c" #define ICON_FA_CHECK_DOUBLE u8"\uf560" #define ICON_FA_HEADPHONES_ALT u8"\uf58f" #define ICON_FA_BATTERY_HALF u8"\uf242" #define ICON_FA_PROJECT_DIAGRAM u8"\uf542" #define ICON_FA_PRAY u8"\uf683" #define ICON_FA_GOPURAM u8"\uf664" #define ICON_FA_GRIN_TEARS u8"\uf588" #define ICON_FA_SORT_AMOUNT_UP u8"\uf161" #define ICON_FA_COFFEE u8"\uf0f4" #define ICON_FA_TABLET_ALT u8"\uf3fa" #define ICON_FA_GRIN_BEAM_SWEAT u8"\uf583" #define ICON_FA_HAND_POINT_RIGHT u8"\uf0a4" #define ICON_FA_MAGIC u8"\uf0d0" #define ICON_FA_CHARGING_STATION u8"\uf5e7" #define ICON_FA_GRIN_TONGUE u8"\uf589" #define ICON_FA_VOLUME_OFF u8"\uf026" #define ICON_FA_SAD_TEAR u8"\uf5b4" #define ICON_FA_CARET_RIGHT u8"\uf0da" #define ICON_FA_BONG u8"\uf55c" #define ICON_FA_BONE u8"\uf5d7" #define ICON_FA_ELLIPSIS_V u8"\uf142" #define ICON_FA_BALANCE_SCALE u8"\uf24e" #define ICON_FA_FISH u8"\uf578" #define ICON_FA_SPIDER u8"\uf717" #define ICON_FA_CAMPGROUND u8"\uf6bb" #define ICON_FA_CARET_SQUARE_UP u8"\uf151" #define ICON_FA_RUPEE_SIGN u8"\uf156" #define ICON_FA_ASSISTIVE_LISTENING_SYSTEMS u8"\uf2a2" #define ICON_FA_POUND_SIGN u8"\uf154" #define ICON_FA_ANKH u8"\uf644" #define ICON_FA_BATTERY_QUARTER u8"\uf243" #define ICON_FA_HAND_PEACE u8"\uf25b" #define ICON_FA_SURPRISE u8"\uf5c2" #define ICON_FA_FILE_PDF u8"\uf1c1" #define ICON_FA_VIDEO_SLASH u8"\uf4e2" #define ICON_FA_SUBWAY u8"\uf239" #define ICON_FA_HORSE u8"\uf6f0" #define ICON_FA_WINE_BOTTLE u8"\uf72f" #define ICON_FA_BOOK_READER u8"\uf5da" #define ICON_FA_COOKIE u8"\uf563" #define ICON_FA_MONEY_BILL u8"\uf0d6" #define ICON_FA_CHEVRON_DOWN u8"\uf078" #define ICON_FA_CAR_SIDE u8"\uf5e4" #define ICON_FA_FILTER u8"\uf0b0" #define ICON_FA_FOLDER_OPEN u8"\uf07c" #define ICON_FA_SIGNATURE u8"\uf5b7" #define ICON_FA_SNOWBOARDING u8"\uf7ce" #define ICON_FA_THUMBTACK u8"\uf08d" #define ICON_FA_DICE_TWO u8"\uf528" #define ICON_FA_LAUGH_WINK u8"\uf59c" #define ICON_FA_BREAD_SLICE u8"\uf7ec" #define ICON_FA_TEXT_HEIGHT u8"\uf034" #define ICON_FA_VOLUME_MUTE u8"\uf6a9" #define ICON_FA_VOTE_YEA u8"\uf772" #define ICON_FA_QRCODE u8"\uf029" #define ICON_FA_MERCURY u8"\uf223" #define ICON_FA_USER_ASTRONAUT u8"\uf4fb" #define ICON_FA_SORT_AMOUNT_DOWN u8"\uf160" #define ICON_FA_SORT_DOWN u8"\uf0dd" #define ICON_FA_COMPACT_DISC u8"\uf51f" #define ICON_FA_PERCENTAGE u8"\uf541" #define ICON_FA_COMMENT_MEDICAL u8"\uf7f5" #define ICON_FA_STORE u8"\uf54e" #define ICON_FA_COMMENT_DOTS u8"\uf4ad" #define ICON_FA_SMILE_WINK u8"\uf4da" #define ICON_FA_HOTEL u8"\uf594" #define ICON_FA_PEPPER_HOT u8"\uf816" #define ICON_FA_USER_EDIT u8"\uf4ff" #define ICON_FA_DUMPSTER_FIRE u8"\uf794" #define ICON_FA_CLOUD_SUN_RAIN u8"\uf743" #define ICON_FA_GLOBE_ASIA u8"\uf57e" #define ICON_FA_VIAL u8"\uf492" #define ICON_FA_STROOPWAFEL u8"\uf551" #define ICON_FA_DATABASE u8"\uf1c0" #define ICON_FA_TREE u8"\uf1bb" #define ICON_FA_SHOWER u8"\uf2cc" #define ICON_FA_DRUM_STEELPAN u8"\uf56a" #define ICON_FA_FILE_UPLOAD u8"\uf574" #define ICON_FA_MEDKIT u8"\uf0fa" #define ICON_FA_MINUS u8"\uf068" #define ICON_FA_SHEKEL_SIGN u8"\uf20b" #define ICON_FA_BELL_SLASH u8"\uf1f6" #define ICON_FA_MAIL_BULK u8"\uf674" #define ICON_FA_MOUNTAIN u8"\uf6fc" #define ICON_FA_COUCH u8"\uf4b8" #define ICON_FA_CHESS u8"\uf439" #define ICON_FA_FILE_EXPORT u8"\uf56e" #define ICON_FA_SIGN_LANGUAGE u8"\uf2a7" #define ICON_FA_SNOWFLAKE u8"\uf2dc" #define ICON_FA_PLAY u8"\uf04b" #define ICON_FA_HEADSET u8"\uf590" #define ICON_FA_SQUARE_ROOT_ALT u8"\uf698" #define ICON_FA_CHART_BAR u8"\uf080" #define ICON_FA_CHART_AREA u8"\uf1fe" #define ICON_FA_EURO_SIGN u8"\uf153" #define ICON_FA_CHESS_KING u8"\uf43f" #define ICON_FA_MOBILE u8"\uf10b" #define ICON_FA_BOX_OPEN u8"\uf49e" #define ICON_FA_DOG u8"\uf6d3" #define ICON_FA_FUTBOL u8"\uf1e3" #define ICON_FA_LIRA_SIGN u8"\uf195" #define ICON_FA_LIGHTBULB u8"\uf0eb" #define ICON_FA_BOMB u8"\uf1e2" #define ICON_FA_MITTEN u8"\uf7b5" #define ICON_FA_TRUCK_MONSTER u8"\uf63b" #define ICON_FA_ARROWS_ALT_H u8"\uf337" #define ICON_FA_CHESS_ROOK u8"\uf447" #define ICON_FA_FIRE_EXTINGUISHER u8"\uf134" #define ICON_FA_BOOKMARK u8"\uf02e" #define ICON_FA_ARROWS_ALT_V u8"\uf338" #define ICON_FA_ICICLES u8"\uf7ad" #define ICON_FA_FONT u8"\uf031" #define ICON_FA_CAMERA_RETRO u8"\uf083" #define ICON_FA_BLENDER u8"\uf517" #define ICON_FA_THUMBS_DOWN u8"\uf165" #define ICON_FA_GAMEPAD u8"\uf11b" #define ICON_FA_COPYRIGHT u8"\uf1f9" #define ICON_FA_JEDI u8"\uf669" #define ICON_FA_HOCKEY_PUCK u8"\uf453" #define ICON_FA_STOP_CIRCLE u8"\uf28d" #define ICON_FA_BEZIER_CURVE u8"\uf55b" #define ICON_FA_FOLDER u8"\uf07b" #define ICON_FA_RSS u8"\uf09e" #define ICON_FA_COLUMNS u8"\uf0db" #define ICON_FA_GLASS_CHEERS u8"\uf79f" #define ICON_FA_GRIN_WINK u8"\uf58c" #define ICON_FA_STOP u8"\uf04d" #define ICON_FA_MONEY_CHECK_ALT u8"\uf53d" #define ICON_FA_COMPASS u8"\uf14e" #define ICON_FA_TOOLBOX u8"\uf552" #define ICON_FA_LIST_OL u8"\uf0cb" #define ICON_FA_WINE_GLASS u8"\uf4e3" #define ICON_FA_HORSE_HEAD u8"\uf7ab" #define ICON_FA_USER_ALT_SLASH u8"\uf4fa" #define ICON_FA_USER_TAG u8"\uf507" #define ICON_FA_MICROSCOPE u8"\uf610" #define ICON_FA_BRUSH u8"\uf55d" #define ICON_FA_BAN u8"\uf05e" #define ICON_FA_BARS u8"\uf0c9" #define ICON_FA_CAR_CRASH u8"\uf5e1" #define ICON_FA_ARROW_ALT_CIRCLE_DOWN u8"\uf358" #define ICON_FA_MONEY_BILL_ALT u8"\uf3d1" #define ICON_FA_JOURNAL_WHILLS u8"\uf66a" #define ICON_FA_CHALKBOARD_TEACHER u8"\uf51c" #define ICON_FA_PORTRAIT u8"\uf3e0" #define ICON_FA_HAMMER u8"\uf6e3" #define ICON_FA_RETWEET u8"\uf079" #define ICON_FA_HOURGLASS u8"\uf254" #define ICON_FA_HAND_PAPER u8"\uf256" #define ICON_FA_SUBSCRIPT u8"\uf12c" #define ICON_FA_DONATE u8"\uf4b9" #define ICON_FA_GLASS_MARTINI_ALT u8"\uf57b" #define ICON_FA_CODE_BRANCH u8"\uf126" #define ICON_FA_NOT_EQUAL u8"\uf53e" #define ICON_FA_MEH u8"\uf11a" #define ICON_FA_LIST_ALT u8"\uf022" #define ICON_FA_USER_COG u8"\uf4fe" #define ICON_FA_PRESCRIPTION u8"\uf5b1" #define ICON_FA_TABLET u8"\uf10a" #define ICON_FA_PENCIL_RULER u8"\uf5ae" #define ICON_FA_CREDIT_CARD u8"\uf09d" #define ICON_FA_ARCHWAY u8"\uf557" #define ICON_FA_HARD_HAT u8"\uf807" #define ICON_FA_MAP_MARKER_ALT u8"\uf3c5" #define ICON_FA_COG u8"\uf013" #define ICON_FA_HANUKIAH u8"\uf6e6" #define ICON_FA_SHUTTLE_VAN u8"\uf5b6" #define ICON_FA_MONEY_CHECK u8"\uf53c" #define ICON_FA_BELL u8"\uf0f3" #define ICON_FA_CALENDAR_DAY u8"\uf783" #define ICON_FA_TINT_SLASH u8"\uf5c7" #define ICON_FA_PLANE_DEPARTURE u8"\uf5b0" #define ICON_FA_USER_CHECK u8"\uf4fc" #define ICON_FA_CHURCH u8"\uf51d" #define ICON_FA_SEARCH_MINUS u8"\uf010" #define ICON_FA_PALLET u8"\uf482" #define ICON_FA_TINT u8"\uf043" #define ICON_FA_STAMP u8"\uf5bf" #define ICON_FA_KAABA u8"\uf66b" #define ICON_FA_ALIGN_RIGHT u8"\uf038" #define ICON_FA_QUOTE_RIGHT u8"\uf10e" #define ICON_FA_BEER u8"\uf0fc" #define ICON_FA_GRIN_ALT u8"\uf581" #define ICON_FA_SORT_NUMERIC_DOWN u8"\uf162" #define ICON_FA_FIRE u8"\uf06d" #define ICON_FA_FAST_FORWARD u8"\uf050" #define ICON_FA_MAP_MARKED_ALT u8"\uf5a0" #define ICON_FA_PENCIL_ALT u8"\uf303" #define ICON_FA_USERS_COG u8"\uf509" #define ICON_FA_CARET_SQUARE_DOWN u8"\uf150" #define ICON_FA_CRUTCH u8"\uf7f7" #define ICON_FA_OBJECT_GROUP u8"\uf247" #define ICON_FA_ANCHOR u8"\uf13d" #define ICON_FA_HAND_POINT_LEFT u8"\uf0a5" #define ICON_FA_USER_TIMES u8"\uf235" #define ICON_FA_CALCULATOR u8"\uf1ec" #define ICON_FA_DIZZY u8"\uf567" #define ICON_FA_KISS_WINK_HEART u8"\uf598" #define ICON_FA_FILE_MEDICAL u8"\uf477" #define ICON_FA_SWIMMING_POOL u8"\uf5c5" #define ICON_FA_WEIGHT_HANGING u8"\uf5cd" #define ICON_FA_VR_CARDBOARD u8"\uf729" #define ICON_FA_FAST_BACKWARD u8"\uf049" #define ICON_FA_SATELLITE u8"\uf7bf" #define ICON_FA_USER u8"\uf007" #define ICON_FA_MINUS_CIRCLE u8"\uf056" #define ICON_FA_CHESS_PAWN u8"\uf443" #define ICON_FA_CALENDAR_MINUS u8"\uf272" #define ICON_FA_CHESS_BOARD u8"\uf43c" #define ICON_FA_LANDMARK u8"\uf66f" #define ICON_FA_SWATCHBOOK u8"\uf5c3" #define ICON_FA_HOTDOG u8"\uf80f" #define ICON_FA_SNOWMAN u8"\uf7d0" #define ICON_FA_LAPTOP u8"\uf109" #define ICON_FA_TORAH u8"\uf6a0" #define ICON_FA_FROWN_OPEN u8"\uf57a" #define ICON_FA_USER_LOCK u8"\uf502" #define ICON_FA_AD u8"\uf641" #define ICON_FA_USER_CIRCLE u8"\uf2bd" #define ICON_FA_DIVIDE u8"\uf529" #define ICON_FA_HANDSHAKE u8"\uf2b5" #define ICON_FA_CUT u8"\uf0c4" #define ICON_FA_HIKING u8"\uf6ec" #define ICON_FA_STREET_VIEW u8"\uf21d" #define ICON_FA_GREATER_THAN u8"\uf531" #define ICON_FA_PASTAFARIANISM u8"\uf67b" #define ICON_FA_MINUS_SQUARE u8"\uf146" #define ICON_FA_SAVE u8"\uf0c7" #define ICON_FA_COMMENT_DOLLAR u8"\uf651" #define ICON_FA_TRASH_ALT u8"\uf2ed" #define ICON_FA_PUZZLE_PIECE u8"\uf12e" #define ICON_FA_MENORAH u8"\uf676" #define ICON_FA_CLOUD_SUN u8"\uf6c4" #define ICON_FA_USER_FRIENDS u8"\uf500" #define ICON_FA_FILE_MEDICAL_ALT u8"\uf478" #define ICON_FA_ARROW_LEFT u8"\uf060" #define ICON_FA_BOXES u8"\uf468" #define ICON_FA_THERMOMETER_EMPTY u8"\uf2cb" #define ICON_FA_EXCLAMATION_TRIANGLE u8"\uf071" #define ICON_FA_GIFT u8"\uf06b" #define ICON_FA_COGS u8"\uf085" #define ICON_FA_SIGNAL u8"\uf012" #define ICON_FA_SHAPES u8"\uf61f" #define ICON_FA_CLOUD_RAIN u8"\uf73d" #define ICON_FA_ELLIPSIS_H u8"\uf141" #define ICON_FA_LESS_THAN_EQUAL u8"\uf537" #define ICON_FA_CHEVRON_CIRCLE_LEFT u8"\uf137" #define ICON_FA_MORTAR_PESTLE u8"\uf5a7" #define ICON_FA_SITEMAP u8"\uf0e8" #define ICON_FA_BUS_ALT u8"\uf55e" #define ICON_FA_ID_BADGE u8"\uf2c1" #define ICON_FA_FIST_RAISED u8"\uf6de" #define ICON_FA_BATTERY_FULL u8"\uf240" #define ICON_FA_CROWN u8"\uf521" #define ICON_FA_EXCHANGE_ALT u8"\uf362" #define ICON_FA_SOCKS u8"\uf696" #define ICON_FA_CASH_REGISTER u8"\uf788" #define ICON_FA_REDO u8"\uf01e" #define ICON_FA_EXCLAMATION_CIRCLE u8"\uf06a" #define ICON_FA_COMMENTS u8"\uf086" #define ICON_FA_BRIEFCASE_MEDICAL u8"\uf469" #define ICON_FA_CARET_SQUARE_RIGHT u8"\uf152" #define ICON_FA_PEN u8"\uf304" #define ICON_FA_BACKSPACE u8"\uf55a" #define ICON_FA_SLASH u8"\uf715" #define ICON_FA_HOT_TUB u8"\uf593" #define ICON_FA_SUITCASE_ROLLING u8"\uf5c1" #define ICON_FA_BATTERY_EMPTY u8"\uf244" #define ICON_FA_GLOBE_AFRICA u8"\uf57c" #define ICON_FA_SLEIGH u8"\uf7cc" #define ICON_FA_BOLT u8"\uf0e7" #define ICON_FA_THERMOMETER_QUARTER u8"\uf2ca" #define ICON_FA_EYE u8"\uf06e" #define ICON_FA_TROPHY u8"\uf091" #define ICON_FA_BRAILLE u8"\uf2a1" #define ICON_FA_PLUS u8"\uf067" #define ICON_FA_LIST_UL u8"\uf0ca" #define ICON_FA_SMOKING_BAN u8"\uf54d" #define ICON_FA_BATH u8"\uf2cd" #define ICON_FA_VOLUME_DOWN u8"\uf027" #define ICON_FA_QUESTION_CIRCLE u8"\uf059" #define ICON_FA_FILE_CODE u8"\uf1c9" #define ICON_FA_GAVEL u8"\uf0e3" #define ICON_FA_CANDY_CANE u8"\uf786" #define ICON_FA_NETWORK_WIRED u8"\uf6ff" #define ICON_FA_CARET_SQUARE_LEFT u8"\uf191" #define ICON_FA_PLANE_ARRIVAL u8"\uf5af" #define ICON_FA_SHARE_SQUARE u8"\uf14d" #define ICON_FA_MEDAL u8"\uf5a2" #define ICON_FA_THERMOMETER_HALF u8"\uf2c9" #define ICON_FA_QUESTION u8"\uf128" #define ICON_FA_CAR_BATTERY u8"\uf5df" #define ICON_FA_DOOR_CLOSED u8"\uf52a" #define ICON_FA_LEAF u8"\uf06c" #define ICON_FA_USER_MINUS u8"\uf503" #define ICON_FA_MUSIC u8"\uf001" #define ICON_FA_GLOBE_EUROPE u8"\uf7a2" #define ICON_FA_HOUSE_DAMAGE u8"\uf6f1" #define ICON_FA_CHEVRON_RIGHT u8"\uf054" #define ICON_FA_GRIP_HORIZONTAL u8"\uf58d" #define ICON_FA_DICE_FOUR u8"\uf524" #define ICON_FA_DEAF u8"\uf2a4" #define ICON_FA_REGISTERED u8"\uf25d" #define ICON_FA_WINDOW_CLOSE u8"\uf410" #define ICON_FA_LINK u8"\uf0c1" #define ICON_FA_YEN_SIGN u8"\uf157" #define ICON_FA_ATOM u8"\uf5d2" #define ICON_FA_LESS_THAN u8"\uf536" #define ICON_FA_OTTER u8"\uf700" #define ICON_FA_INFO u8"\uf129" #define ICON_FA_MARS_DOUBLE u8"\uf227" #define ICON_FA_CLIPBOARD_CHECK u8"\uf46c" #define ICON_FA_SKULL u8"\uf54c" #define ICON_FA_GRIP_LINES u8"\uf7a4" #define ICON_FA_HOSPITAL_SYMBOL u8"\uf47e" #define ICON_FA_X_RAY u8"\uf497" #define ICON_FA_ARROW_UP u8"\uf062" #define ICON_FA_MONEY_BILL_WAVE u8"\uf53a" #define ICON_FA_DOT_CIRCLE u8"\uf192" #define ICON_FA_PAUSE_CIRCLE u8"\uf28b" #define ICON_FA_IMAGES u8"\uf302" #define ICON_FA_STAR_HALF u8"\uf089" #define ICON_FA_SPLOTCH u8"\uf5bc" #define ICON_FA_STAR_HALF_ALT u8"\uf5c0" #define ICON_FA_SHIP u8"\uf21a" #define ICON_FA_BOOK_DEAD u8"\uf6b7" #define ICON_FA_CHECK u8"\uf00c" #define ICON_FA_RAINBOW u8"\uf75b" #define ICON_FA_POWER_OFF u8"\uf011" #define ICON_FA_LEMON u8"\uf094" #define ICON_FA_GLOBE_AMERICAS u8"\uf57d" #define ICON_FA_PEACE u8"\uf67c" #define ICON_FA_THERMOMETER_THREE_QUARTERS u8"\uf2c8" #define ICON_FA_WAREHOUSE u8"\uf494" #define ICON_FA_TRANSGENDER u8"\uf224" #define ICON_FA_PLUS_SQUARE u8"\uf0fe" #define ICON_FA_BULLSEYE u8"\uf140" #define ICON_FA_COOKIE_BITE u8"\uf564" #define ICON_FA_USERS u8"\uf0c0" #define ICON_FA_TRANSGENDER_ALT u8"\uf225" #define ICON_FA_ASTERISK u8"\uf069" #define ICON_FA_STAR_OF_DAVID u8"\uf69a" #define ICON_FA_PLUS_CIRCLE u8"\uf055" #define ICON_FA_CART_ARROW_DOWN u8"\uf218" #define ICON_FA_FLUSHED u8"\uf579" #define ICON_FA_STORE_ALT u8"\uf54f" #define ICON_FA_PEOPLE_CARRY u8"\uf4ce" #define ICON_FA_LONG_ARROW_ALT_DOWN u8"\uf309" #define ICON_FA_SAD_CRY u8"\uf5b3" #define ICON_FA_DIGITAL_TACHOGRAPH u8"\uf566" #define ICON_FA_FILE_EXCEL u8"\uf1c3" #define ICON_FA_TEETH u8"\uf62e" #define ICON_FA_HAND_SCISSORS u8"\uf257" #define ICON_FA_FILE_INVOICE_DOLLAR u8"\uf571" #define ICON_FA_STEP_FORWARD u8"\uf051" #define ICON_FA_BACKWARD u8"\uf04a" #define ICON_FA_SCROLL u8"\uf70e" #define ICON_FA_IGLOO u8"\uf7ae" #define ICON_FA_CODE u8"\uf121" #define ICON_FA_TRAM u8"\uf7da" #define ICON_FA_TORII_GATE u8"\uf6a1" #define ICON_FA_SKIING u8"\uf7c9" #define ICON_FA_CHAIR u8"\uf6c0" #define ICON_FA_DUMBBELL u8"\uf44b" #define ICON_FA_ANGLE_DOUBLE_UP u8"\uf102" #define ICON_FA_ANGLE_DOUBLE_LEFT u8"\uf100" #define ICON_FA_MOSQUE u8"\uf678" #define ICON_FA_COMMENTS_DOLLAR u8"\uf653" #define ICON_FA_FILE_PRESCRIPTION u8"\uf572" #define ICON_FA_ANGLE_LEFT u8"\uf104" #define ICON_FA_ATLAS u8"\uf558" #define ICON_FA_PIGGY_BANK u8"\uf4d3" #define ICON_FA_DOLLY_FLATBED u8"\uf474" #define ICON_FA_RANDOM u8"\uf074" #define ICON_FA_PEN_ALT u8"\uf305" #define ICON_FA_PRAYING_HANDS u8"\uf684" #define ICON_FA_VOLUME_UP u8"\uf028" #define ICON_FA_CLIPBOARD_LIST u8"\uf46d" #define ICON_FA_GRIN_STARS u8"\uf587" #define ICON_FA_FOLDER_MINUS u8"\uf65d" #define ICON_FA_DEMOCRAT u8"\uf747" #define ICON_FA_MAGNET u8"\uf076" #define ICON_FA_VIHARA u8"\uf6a7" #define ICON_FA_GRIMACE u8"\uf57f" #define ICON_FA_CHECK_CIRCLE u8"\uf058" #define ICON_FA_SEARCH_DOLLAR u8"\uf688" #define ICON_FA_LONG_ARROW_ALT_LEFT u8"\uf30a" #define ICON_FA_CROW u8"\uf520" #define ICON_FA_EYE_DROPPER u8"\uf1fb" #define ICON_FA_CROP u8"\uf125" #define ICON_FA_SIGN u8"\uf4d9" #define ICON_FA_ARROW_CIRCLE_DOWN u8"\uf0ab" #define ICON_FA_VIDEO u8"\uf03d" #define ICON_FA_DOWNLOAD u8"\uf019" #define ICON_FA_BOLD u8"\uf032" #define ICON_FA_CARET_DOWN u8"\uf0d7" #define ICON_FA_CHEVRON_LEFT u8"\uf053" #define ICON_FA_HAMSA u8"\uf665" #define ICON_FA_CART_PLUS u8"\uf217" #define ICON_FA_CLIPBOARD u8"\uf328" #define ICON_FA_SHOE_PRINTS u8"\uf54b" #define ICON_FA_PHONE_SLASH u8"\uf3dd" #define ICON_FA_REPLY u8"\uf3e5" #define ICON_FA_HOURGLASS_HALF u8"\uf252" #define ICON_FA_LONG_ARROW_ALT_UP u8"\uf30c" #define ICON_FA_CHESS_KNIGHT u8"\uf441" #define ICON_FA_BARCODE u8"\uf02a" #define ICON_FA_DRAW_POLYGON u8"\uf5ee" #define ICON_FA_WATER u8"\uf773" #define ICON_FA_PAUSE u8"\uf04c" #define ICON_FA_WINE_GLASS_ALT u8"\uf5ce" #define ICON_FA_GLASS_WHISKEY u8"\uf7a0" #define ICON_FA_BOX u8"\uf466" #define ICON_FA_DIAGNOSES u8"\uf470" #define ICON_FA_FILE_IMAGE u8"\uf1c5" #define ICON_FA_ARROW_CIRCLE_RIGHT u8"\uf0a9" #define ICON_FA_TASKS u8"\uf0ae" #define ICON_FA_VECTOR_SQUARE u8"\uf5cb" #define ICON_FA_QUOTE_LEFT u8"\uf10d" #define ICON_FA_MOBILE_ALT u8"\uf3cd" #define ICON_FA_USER_SHIELD u8"\uf505" #define ICON_FA_BLOG u8"\uf781" #define ICON_FA_MARKER u8"\uf5a1" #define ICON_FA_USER_TIE u8"\uf508" #define ICON_FA_TOOLS u8"\uf7d9" #define ICON_FA_CLOUD u8"\uf0c2" #define ICON_FA_HAND_HOLDING_USD u8"\uf4c0" #define ICON_FA_CERTIFICATE u8"\uf0a3" #define ICON_FA_CLOUD_DOWNLOAD_ALT u8"\uf381" #define ICON_FA_ANGRY u8"\uf556" #define ICON_FA_FROG u8"\uf52e" #define ICON_FA_CAMERA u8"\uf030" #define ICON_FA_DICE_THREE u8"\uf527" #define ICON_FA_MEMORY u8"\uf538" #define ICON_FA_PEN_SQUARE u8"\uf14b" #define ICON_FA_SORT u8"\uf0dc" #define ICON_FA_PLUG u8"\uf1e6" #define ICON_FA_MOUSE_POINTER u8"\uf245" #define ICON_FA_ENVELOPE u8"\uf0e0" #define ICON_FA_LAYER_GROUP u8"\uf5fd" #define ICON_FA_TRAIN u8"\uf238" #define ICON_FA_BULLHORN u8"\uf0a1" #define ICON_FA_BABY u8"\uf77c" #define ICON_FA_CONCIERGE_BELL u8"\uf562" #define ICON_FA_CIRCLE u8"\uf111" #define ICON_FA_I_CURSOR u8"\uf246" #define ICON_FA_CAR u8"\uf1b9" #define ICON_FA_CAT u8"\uf6be" #define ICON_FA_WALLET u8"\uf555" #define ICON_FA_BOOK_MEDICAL u8"\uf7e6" #define ICON_FA_H_SQUARE u8"\uf0fd" #define ICON_FA_HEART u8"\uf004" #define ICON_FA_LOCK_OPEN u8"\uf3c1" #define ICON_FA_STREAM u8"\uf550" #define ICON_FA_LOCK u8"\uf023" #define ICON_FA_CARROT u8"\uf787" #define ICON_FA_SMILE_BEAM u8"\uf5b8" #define ICON_FA_USER_NURSE u8"\uf82f" #define ICON_FA_MICROPHONE_ALT u8"\uf3c9" #define ICON_FA_SPA u8"\uf5bb" #define ICON_FA_CHEVRON_CIRCLE_DOWN u8"\uf13a" #define ICON_FA_FOLDER_PLUS u8"\uf65e" #define ICON_FA_CLOUD_MEATBALL u8"\uf73b" #define ICON_FA_BOOK_OPEN u8"\uf518" #define ICON_FA_MAP u8"\uf279" #define ICON_FA_COCKTAIL u8"\uf561" #define ICON_FA_CLONE u8"\uf24d" #define ICON_FA_ID_CARD_ALT u8"\uf47f" #define ICON_FA_CHECK_SQUARE u8"\uf14a" #define ICON_FA_CHART_LINE u8"\uf201" #define ICON_FA_FILE_ARCHIVE u8"\uf1c6" #define ICON_FA_DOVE u8"\uf4ba" #define ICON_FA_MARS_STROKE u8"\uf229" #define ICON_FA_ENVELOPE_OPEN u8"\uf2b6" #define ICON_FA_WHEELCHAIR u8"\uf193" #define ICON_FA_ROBOT u8"\uf544" #define ICON_FA_UNDO_ALT u8"\uf2ea" #define ICON_FA_TICKET_ALT u8"\uf3ff" #define ICON_FA_TRUCK u8"\uf0d1" #define ICON_FA_WON_SIGN u8"\uf159" #define ICON_FA_SUPERSCRIPT u8"\uf12b" #define ICON_FA_TTY u8"\uf1e4" #define ICON_FA_USER_MD u8"\uf0f0" #define ICON_FA_ALIGN_LEFT u8"\uf036" #define ICON_FA_TABLETS u8"\uf490" #define ICON_FA_MOTORCYCLE u8"\uf21c" #define ICON_FA_ANGLE_UP u8"\uf106" #define ICON_FA_BROOM u8"\uf51a" #define ICON_FA_DICE_D20 u8"\uf6cf" #define ICON_FA_LEVEL_DOWN_ALT u8"\uf3be" #define ICON_FA_PAPERCLIP u8"\uf0c6" #define ICON_FA_USER_CLOCK u8"\uf4fd" #define ICON_FA_SORT_ALPHA_UP u8"\uf15e" #define ICON_FA_AUDIO_DESCRIPTION u8"\uf29e" #define ICON_FA_FILE_CSV u8"\uf6dd" #define ICON_FA_FILE_DOWNLOAD u8"\uf56d" #define ICON_FA_SYNC_ALT u8"\uf2f1" #define ICON_FA_KISS u8"\uf596" #define ICON_FA_HANDS u8"\uf4c2" #define ICON_FA_REPUBLICAN u8"\uf75e" #define ICON_FA_EDIT u8"\uf044" #define ICON_FA_UNIVERSITY u8"\uf19c" #define ICON_FA_KHANDA u8"\uf66d" #define ICON_FA_GLASSES u8"\uf530" #define ICON_FA_SQUARE u8"\uf0c8" #define ICON_FA_GRIN_SQUINT u8"\uf585" #define ICON_FA_GLOBE u8"\uf0ac" #define ICON_FA_RECEIPT u8"\uf543" #define ICON_FA_STRIKETHROUGH u8"\uf0cc" #define ICON_FA_UNLOCK u8"\uf09c" #define ICON_FA_DICE_SIX u8"\uf526" #define ICON_FA_GRIP_VERTICAL u8"\uf58e" #define ICON_FA_PILLS u8"\uf484" #define ICON_FA_EXCLAMATION u8"\uf12a" #define ICON_FA_PERSON_BOOTH u8"\uf756" #define ICON_FA_CALENDAR_PLUS u8"\uf271" #define ICON_FA_SMOG u8"\uf75f" #define ICON_FA_LOCATION_ARROW u8"\uf124" #define ICON_FA_UMBRELLA u8"\uf0e9" #define ICON_FA_QURAN u8"\uf687" #define ICON_FA_UNDO u8"\uf0e2" #define ICON_FA_DUMPSTER u8"\uf793" #define ICON_FA_FUNNEL_DOLLAR u8"\uf662" #define ICON_FA_INDENT u8"\uf03c" #define ICON_FA_LANGUAGE u8"\uf1ab" #define ICON_FA_ARROW_ALT_CIRCLE_UP u8"\uf35b" #define ICON_FA_ROUTE u8"\uf4d7" #define ICON_FA_USER_ALT u8"\uf406" #define ICON_FA_TIMES u8"\uf00d" #define ICON_FA_CLINIC_MEDICAL u8"\uf7f2" #define ICON_FA_LEVEL_UP_ALT u8"\uf3bf" #define ICON_FA_BLIND u8"\uf29d" #define ICON_FA_CHEESE u8"\uf7ef" #define ICON_FA_PHONE_SQUARE u8"\uf098" #define ICON_FA_SHOPPING_BASKET u8"\uf291" #define ICON_FA_ICE_CREAM u8"\uf810" #define ICON_FA_RING u8"\uf70b" #define ICON_FA_CITY u8"\uf64f" #define ICON_FA_TEXT_WIDTH u8"\uf035" #define ICON_FA_RSS_SQUARE u8"\uf143" #define ICON_FA_PAINT_BRUSH u8"\uf1fc" #define ICON_FA_PARACHUTE_BOX u8"\uf4cd" #define ICON_FA_SIM_CARD u8"\uf7c4" #define ICON_FA_CLOUD_UPLOAD_ALT u8"\uf382" #define ICON_FA_SORT_UP u8"\uf0de" #define ICON_FA_SIGN_OUT_ALT u8"\uf2f5" #define ICON_FA_USER_NINJA u8"\uf504" #define ICON_FA_SIGN_IN_ALT u8"\uf2f6" #define ICON_FA_MUG_HOT u8"\uf7b6" #define ICON_FA_SHARE_ALT u8"\uf1e0" #define ICON_FA_CALENDAR_CHECK u8"\uf274" #define ICON_FA_PEN_FANCY u8"\uf5ac" #define ICON_FA_BIOHAZARD u8"\uf780" #define ICON_FA_BED u8"\uf236" #define ICON_FA_FILE_SIGNATURE u8"\uf573" #define ICON_FA_TOGGLE_OFF u8"\uf204" #define ICON_FA_TRAFFIC_LIGHT u8"\uf637" #define ICON_FA_TRACTOR u8"\uf722" #define ICON_FA_MEH_ROLLING_EYES u8"\uf5a5" #define ICON_FA_COMMENT_ALT u8"\uf27a" #define ICON_FA_RULER_HORIZONTAL u8"\uf547" #define ICON_FA_PAINT_ROLLER u8"\uf5aa" #define ICON_FA_HAT_WIZARD u8"\uf6e8" #define ICON_FA_CALENDAR u8"\uf133" #define ICON_FA_MICROPHONE u8"\uf130" #define ICON_FA_FOOTBALL_BALL u8"\uf44e" #define ICON_FA_ALLERGIES u8"\uf461" #define ICON_FA_ID_CARD u8"\uf2c2" #define ICON_FA_REDO_ALT u8"\uf2f9" #define ICON_FA_PLAY_CIRCLE u8"\uf144" #define ICON_FA_THERMOMETER u8"\uf491" #define ICON_FA_DOLLAR_SIGN u8"\uf155" #define ICON_FA_DUNGEON u8"\uf6d9" #define ICON_FA_COMPRESS u8"\uf066" #define ICON_FA_SEARCH_LOCATION u8"\uf689" #define ICON_FA_BLENDER_PHONE u8"\uf6b6" #define ICON_FA_ANGLE_RIGHT u8"\uf105" #define ICON_FA_CHESS_QUEEN u8"\uf445" #define ICON_FA_PAGER u8"\uf815" #define ICON_FA_MEH_BLANK u8"\uf5a4" #define ICON_FA_EJECT u8"\uf052" #define ICON_FA_HOURGLASS_END u8"\uf253" #define ICON_FA_TOOTH u8"\uf5c9" #define ICON_FA_BUSINESS_TIME u8"\uf64a" #define ICON_FA_PLACE_OF_WORSHIP u8"\uf67f" #define ICON_FA_MOON u8"\uf186" #define ICON_FA_GRIN_TONGUE_SQUINT u8"\uf58a" #define ICON_FA_WALKING u8"\uf554" #define ICON_FA_SHIPPING_FAST u8"\uf48b" #define ICON_FA_CARET_LEFT u8"\uf0d9" #define ICON_FA_DICE u8"\uf522" #define ICON_FA_RUBLE_SIGN u8"\uf158" #define ICON_FA_RULER_VERTICAL u8"\uf548" #define ICON_FA_HAND_POINTER u8"\uf25a" #define ICON_FA_TAPE u8"\uf4db" #define ICON_FA_SHOPPING_BAG u8"\uf290" #define ICON_FA_SKIING_NORDIC u8"\uf7ca" #define ICON_FA_HIPPO u8"\uf6ed" #define ICON_FA_CUBE u8"\uf1b2" #define ICON_FA_CAPSULES u8"\uf46b" #define ICON_FA_KIWI_BIRD u8"\uf535" #define ICON_FA_CHEVRON_CIRCLE_UP u8"\uf139" #define ICON_FA_MARS_STROKE_V u8"\uf22a" #define ICON_FA_POO_STORM u8"\uf75a" #define ICON_FA_JOINT u8"\uf595" #define ICON_FA_MARS_STROKE_H u8"\uf22b" #define ICON_FA_ADDRESS_BOOK u8"\uf2b9" #define ICON_FA_PROCEDURES u8"\uf487" #define ICON_FA_GEM u8"\uf3a5" #define ICON_FA_RULER_COMBINED u8"\uf546" #define ICON_FA_BRAIN u8"\uf5dc" #define ICON_FA_STAR_AND_CRESCENT u8"\uf699" #define ICON_FA_FIGHTER_JET u8"\uf0fb" #define ICON_FA_SPACE_SHUTTLE u8"\uf197" #define ICON_FA_MAP_PIN u8"\uf276" #define ICON_FA_ALIGN_CENTER u8"\uf037" #define ICON_FA_SORT_ALPHA_DOWN u8"\uf15d" #define ICON_FA_PARKING u8"\uf540" #define ICON_FA_MAP_SIGNS u8"\uf277" #define ICON_FA_PALETTE u8"\uf53f" #define ICON_FA_GLASS_MARTINI u8"\uf000" #define ICON_FA_TIMES_CIRCLE u8"\uf057" #define ICON_FA_MONUMENT u8"\uf5a6" #define ICON_FA_GUITAR u8"\uf7a6" #define ICON_FA_GRIN_BEAM u8"\uf582" #define ICON_FA_KEY u8"\uf084" #define ICON_FA_TH_LIST u8"\uf00b" #define ICON_FA_SHARE_ALT_SQUARE u8"\uf1e1" #define ICON_FA_DRUM u8"\uf569" #define ICON_FA_FILE_CONTRACT u8"\uf56c" #define ICON_FA_RESTROOM u8"\uf7bd" #define ICON_FA_UNLOCK_ALT u8"\uf13e" #define ICON_FA_MICROPHONE_ALT_SLASH u8"\uf539" #define ICON_FA_USER_SECRET u8"\uf21b" #define ICON_FA_ARROW_RIGHT u8"\uf061" #define ICON_FA_FILE_VIDEO u8"\uf1c8" #define ICON_FA_ARROW_ALT_CIRCLE_RIGHT u8"\uf35a" #define ICON_FA_COMMENT u8"\uf075" #define ICON_FA_CALENDAR_WEEK u8"\uf784" #define ICON_FA_USER_GRADUATE u8"\uf501" #define ICON_FA_HAND_MIDDLE_FINGER u8"\uf806" #define ICON_FA_POO u8"\uf2fe" #define ICON_FA_GRIP_LINES_VERTICAL u8"\uf7a5" #define ICON_FA_TABLE u8"\uf0ce" #define ICON_FA_POLL u8"\uf681" #define ICON_FA_CAR_ALT u8"\uf5de" #define ICON_FA_THUMBS_UP u8"\uf164" #define ICON_FA_TRADEMARK u8"\uf25c" #define ICON_FA_CLOUD_MOON u8"\uf6c3" #define ICON_FA_VIALS u8"\uf493" #define ICON_FA_FIRST_AID u8"\uf479" #define ICON_FA_ERASER u8"\uf12d" #define ICON_FA_MARS u8"\uf222" #define ICON_FA_STAR_OF_LIFE u8"\uf621" #define ICON_FA_FEATHER u8"\uf52d" #define ICON_FA_SQUARE_FULL u8"\uf45c" #define ICON_FA_DOLLY u8"\uf472" #define ICON_FA_HOURGLASS_START u8"\uf251" #define ICON_FA_GRIN_HEARTS u8"\uf584" #define ICON_FA_CUBES u8"\uf1b3" #define ICON_FA_HASHTAG u8"\uf292" #define ICON_FA_SEEDLING u8"\uf4d8" #define ICON_FA_HAYKAL u8"\uf666" #define ICON_FA_TSHIRT u8"\uf553" #define ICON_FA_LAUGH_SQUINT u8"\uf59b" #define ICON_FA_HDD u8"\uf0a0" #define ICON_FA_NEWSPAPER u8"\uf1ea" #define ICON_FA_HOSPITAL_ALT u8"\uf47d" #define ICON_FA_USER_SLASH u8"\uf506" #define ICON_FA_FILE_WORD u8"\uf1c2" #define ICON_FA_ENVELOPE_SQUARE u8"\uf199" #define ICON_FA_GENDERLESS u8"\uf22d" #define ICON_FA_DICE_FIVE u8"\uf523" #define ICON_FA_SYNAGOGUE u8"\uf69b" #define ICON_FA_PAW u8"\uf1b0" #define ICON_FA_RADIATION u8"\uf7b9" #define ICON_FA_CROSS u8"\uf654" #define ICON_FA_ARCHIVE u8"\uf187" #define ICON_FA_PHONE_VOLUME u8"\uf2a0" #define ICON_FA_SOLAR_PANEL u8"\uf5ba" #define ICON_FA_INFINITY u8"\uf534" #define ICON_FA_HAND_POINT_DOWN u8"\uf0a7" #define ICON_FA_MAP_MARKER u8"\uf041" #define ICON_FA_CALENDAR_ALT u8"\uf073" #define ICON_FA_AMERICAN_SIGN_LANGUAGE_INTERPRETING u8"\uf2a3" #define ICON_FA_BINOCULARS u8"\uf1e5" #define ICON_FA_STICKY_NOTE u8"\uf249" #define ICON_FA_RUNNING u8"\uf70c" #define ICON_FA_PEN_NIB u8"\uf5ad" #define ICON_FA_MAP_MARKED u8"\uf59f" #define ICON_FA_EXPAND u8"\uf065" #define ICON_FA_TRUCK_PICKUP u8"\uf63c" #define ICON_FA_HOLLY_BERRY u8"\uf7aa" #define ICON_FA_PRESCRIPTION_BOTTLE u8"\uf485" #define ICON_FA_LAPTOP_CODE u8"\uf5fc" #define ICON_FA_GOLF_BALL u8"\uf450" #define ICON_FA_SKULL_CROSSBONES u8"\uf714" #define ICON_FA_TAXI u8"\uf1ba" #define ICON_FA_ROCKET u8"\uf135" #define ICON_FA_YIN_YANG u8"\uf6ad" #define ICON_FA_FINGERPRINT u8"\uf577" #define ICON_FA_ARROWS_ALT u8"\uf0b2" #define ICON_FA_UNDERLINE u8"\uf0cd" #define ICON_FA_ARROW_CIRCLE_UP u8"\uf0aa" #define ICON_FA_BASKETBALL_BALL u8"\uf434" #define ICON_FA_DESKTOP u8"\uf108" #define ICON_FA_SPINNER u8"\uf110" #define ICON_FA_TOGGLE_ON u8"\uf205" #define ICON_FA_STOPWATCH u8"\uf2f2" #define ICON_FA_ARROW_ALT_CIRCLE_LEFT u8"\uf359" #define ICON_FA_GAS_PUMP u8"\uf52f" #define ICON_FA_EXTERNAL_LINK_ALT u8"\uf35d" #define ICON_FA_FROWN u8"\uf119" #define ICON_FA_RULER u8"\uf545" #define ICON_FA_FLAG_USA u8"\uf74d" #define ICON_FA_GRIN u8"\uf580" #define ICON_FA_THEATER_MASKS u8"\uf630" #define ICON_FA_ARROW_CIRCLE_LEFT u8"\uf0a8" #define ICON_FA_HIGHLIGHTER u8"\uf591" #define ICON_FA_POLL_H u8"\uf682" #define ICON_FA_SERVER u8"\uf233" #define ICON_FA_TRASH_RESTORE u8"\uf829" #define ICON_FA_SPRAY_CAN u8"\uf5bd" #define ICON_FA_BOWLING_BALL u8"\uf436" #define ICON_FA_LAUGH u8"\uf599" #define ICON_FA_TERMINAL u8"\uf120" #define ICON_FA_WINDOW_MINIMIZE u8"\uf2d1" #define ICON_FA_HOME u8"\uf015" #define ICON_FA_UTENSIL_SPOON u8"\uf2e5" #define ICON_FA_QUIDDITCH u8"\uf458" #define ICON_FA_APPLE_ALT u8"\uf5d1" #define ICON_FA_UMBRELLA_BEACH u8"\uf5ca" #define ICON_FA_CANNABIS u8"\uf55f" #define ICON_FA_LAUGH_BEAM u8"\uf59a" #define ICON_FA_TEETH_OPEN u8"\uf62f" #define ICON_FA_DRUMSTICK_BITE u8"\uf6d7" #define ICON_FA_CHART_PIE u8"\uf200" #define ICON_FA_SD_CARD u8"\uf7c2" #define ICON_FA_HANDS_HELPING u8"\uf4c4" #define ICON_FA_PASTE u8"\uf0ea" #define ICON_FA_OM u8"\uf679" #define ICON_FA_LUGGAGE_CART u8"\uf59d" #define ICON_FA_INDUSTRY u8"\uf275" #define ICON_FA_SWIMMER u8"\uf5c4" #define ICON_FA_RADIATION_ALT u8"\uf7ba" #define ICON_FA_COMPRESS_ARROWS_ALT u8"\uf78c" #define ICON_FA_ROAD u8"\uf018" #define ICON_FA_IMAGE u8"\uf03e" #define ICON_FA_CHILD u8"\uf1ae" #define ICON_FA_ANGLE_DOUBLE_RIGHT u8"\uf101" #define ICON_FA_CLOUD_MOON_RAIN u8"\uf73c" #define ICON_FA_DOOR_OPEN u8"\uf52b" #define ICON_FA_GRIN_TONGUE_WINK u8"\uf58b" #define ICON_FA_REPLY_ALL u8"\uf122" #define ICON_FA_TEMPERATURE_LOW u8"\uf76b" #define ICON_FA_INBOX u8"\uf01c" #define ICON_FA_FEMALE u8"\uf182" #define ICON_FA_SYRINGE u8"\uf48e" #define ICON_FA_CIRCLE_NOTCH u8"\uf1ce" #define ICON_FA_WEIGHT u8"\uf496" #define ICON_FA_SNOWPLOW u8"\uf7d2" #define ICON_FA_TABLE_TENNIS u8"\uf45d" #define ICON_FA_LOW_VISION u8"\uf2a8" #define ICON_FA_FILE_IMPORT u8"\uf56f" #define ICON_FA_ITALIC u8"\uf033" #define ICON_FA_CLOSED_CAPTIONING u8"\uf20a" #define ICON_FA_CHALKBOARD u8"\uf51b" #define ICON_FA_BUILDING u8"\uf1ad" #define ICON_FA_TACHOMETER_ALT u8"\uf3fd" #define ICON_FA_BUS u8"\uf207" #define ICON_FA_ANGLE_DOWN u8"\uf107" #define ICON_FA_HAND_ROCK u8"\uf255" #define ICON_FA_FORWARD u8"\uf04e" #define ICON_FA_HELICOPTER u8"\uf533" #define ICON_FA_PODCAST u8"\uf2ce" #define ICON_FA_TRUCK_MOVING u8"\uf4df" #define ICON_FA_BUG u8"\uf188" #define ICON_FA_SHIELD_ALT u8"\uf3ed" #define ICON_FA_FILL_DRIP u8"\uf576" #define ICON_FA_COMMENT_SLASH u8"\uf4b3" #define ICON_FA_SUITCASE u8"\uf0f2" #define ICON_FA_SKATING u8"\uf7c5" #define ICON_FA_TOILET u8"\uf7d8" #define ICON_FA_ENVELOPE_OPEN_TEXT u8"\uf658" #define ICON_FA_HAND_HOLDING u8"\uf4bd" #define ICON_FA_VENUS_MARS u8"\uf228" #define ICON_FA_HEART_BROKEN u8"\uf7a9" #define ICON_FA_UTENSILS u8"\uf2e7" #define ICON_FA_TH_LARGE u8"\uf009" #define ICON_FA_AT u8"\uf1fa" #define ICON_FA_FILE u8"\uf15b" #define ICON_FA_TENGE u8"\uf7d7" #define ICON_FA_FLAG_CHECKERED u8"\uf11e" #define ICON_FA_FILM u8"\uf008" #define ICON_FA_FILL u8"\uf575" #define ICON_FA_GRIN_SQUINT_TEARS u8"\uf586" #define ICON_FA_PERCENT u8"\uf295" #define ICON_FA_BOOK u8"\uf02d" #define ICON_FA_METEOR u8"\uf753" #define ICON_FA_TRASH u8"\uf1f8" #define ICON_FA_FILE_AUDIO u8"\uf1c7" #define ICON_FA_SATELLITE_DISH u8"\uf7c0" #define ICON_FA_POOP u8"\uf619" #define ICON_FA_STAR u8"\uf005" #define ICON_FA_GIFTS u8"\uf79c" #define ICON_FA_GHOST u8"\uf6e2" #define ICON_FA_PRESCRIPTION_BOTTLE_ALT u8"\uf486" #define ICON_FA_MONEY_BILL_WAVE_ALT u8"\uf53b" #define ICON_FA_NEUTER u8"\uf22c" #define ICON_FA_BAND_AID u8"\uf462" #define ICON_FA_WIFI u8"\uf1eb" #define ICON_FA_MASK u8"\uf6fa" #define ICON_FA_VENUS_DOUBLE u8"\uf226" #define ICON_FA_CHEVRON_UP u8"\uf077" #define ICON_FA_HAND_SPOCK u8"\uf259" #define ICON_FA_HAND_POINT_UP u8"\uf0a6" ================================================ FILE: src/resource/MacOSXBundleInfo.plist.in ================================================ CFBundleDevelopmentRegion English CFBundleExecutable ${MACOSX_BUNDLE_EXECUTABLE_NAME} CFBundleIconFile ${MACOSX_BUNDLE_ICON_FILE} CFBundleIdentifier ${MACOSX_BUNDLE_GUI_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${MACOSX_BUNDLE_BUNDLE_NAME} CFBundlePackageType APPL CFBundleShortVersionString ${MACOSX_BUNDLE_SHORT_VERSION_STRING} CFBundleSignature ???? CFBundleVersion ${MACOSX_BUNDLE_BUNDLE_VERSION} CSResourcesFileMapped NSHumanReadableCopyright ${MACOSX_BUNDLE_COPYRIGHT} LSApplicationCategoryType ${MACOSX_BUNDLE_CATEGORY} LSMinimumSystemVersion ${MACOSX_MINIMUM_SYSTEM_VERSION} CFBundleLocalizations ca de en es fr he hu it ja ko nb nl pl pt ru sv tr uk zh CFBundleDocumentTypes CFBundleTypeExtensions ${MACOSX_BUNDLE_TYPE_EXTENSION} CFBundleTypeName Wii U File CFBundleTypeRole Viewer ================================================ FILE: src/resource/cemu.rc ================================================ // Microsoft Visual C++ generated resource script. // #include "resource/resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // Neutral (Default) (unknown sub-lang: 0x8) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ZZZ) LANGUAGE LANG_NEUTRAL, 0x8 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. M_WND_ICON128 ICON "resource\\logo_icon.ico" M_WND_ICON16 ICON "resource\\logo_icon16.ico" X_INPUT_CONTROLLER ICON "resource\\input\\icons8-game-controller-24.ico" X_BOX ICON "resource\\icons8_cardboard_box_filled.ico" X_SETTINGS ICON "resource\\icons8_automatic_26_xQK_icon.ico" X_GAME_PROFILE ICON "resource\\icons8-compose-filled-50.ico" X_HOTKEY_SETTINGS ICON "resource\\icons8_hotkeys.ico" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // #define xstr(s) str(s) #define str(s) #s VS_VERSION_INFO VERSIONINFO FILEVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0 PRODUCTVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x0L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "FileDescription", "Cemu Wii U emulator" VALUE "InternalName", "Cemu" VALUE "LegalCopyright", "Team Cemu" VALUE "OriginalFilename", "Cemu.exe" VALUE "ProductName", "Cemu" VALUE "ProductVersion", xstr(EMULATOR_VERSION_MAJOR) "." xstr(EMULATOR_VERSION_MINOR) "." xstr(EMULATOR_VERSION_PATCH) EMULATOR_VERSION_SUFFIX "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END ///////////////////////////////////////////////////////////////////////////// // // RCDATA // IDR_FONTAWESOME RCDATA "resource\\fontawesome-webfont.ttf" #endif // Neutral (Default) (unknown sub-lang: 0x8) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: src/resource/embedded/DEBUGGER_BP.hpng ================================================ unsigned char DEBUGGER_BP_png[436] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0D, 0xD6, 0x00, 0x00, 0x0D, 0xD6, 0x01, 0x90, 0x6F, 0x79, 0x9C, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, 0x00, 0x01, 0x24, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x8D, 0xD3, 0xBB, 0x4A, 0x03, 0x51, 0x14, 0x46, 0xE1, 0x41, 0xBC, 0x82, 0xE9, 0xB4, 0xF1, 0x92, 0x60, 0x95, 0x28, 0x5E, 0xB0, 0x0C, 0x82, 0xF8, 0x0E, 0x1A, 0x7C, 0x82, 0xA8, 0x5D, 0x48, 0xBA, 0x84, 0x58, 0x59, 0xF8, 0x48, 0xD6, 0xDA, 0x88, 0xA5, 0x42, 0x6C, 0xD4, 0x42, 0x34, 0x28, 0x88, 0xD8, 0x88, 0x8A, 0x22, 0xB8, 0xD6, 0x90, 0x04, 0x8C, 0x19, 0x73, 0x7E, 0xF8, 0x60, 0xF6, 0xC0, 0xCC, 0x9C, 0xB3, 0xCF, 0x9E, 0x28, 0x21, 0xA3, 0xD8, 0xC0, 0x2E, 0x76, 0xB0, 0x86, 0x11, 0xF4, 0x4D, 0x0A, 0x87, 0x78, 0xC0, 0x1B, 0x9E, 0x5B, 0xDE, 0x71, 0x8B, 0x7D, 0x8C, 0xA1, 0x67, 0x66, 0x71, 0x81, 0x26, 0xFC, 0xF2, 0x14, 0x86, 0x30, 0x8C, 0x34, 0xCA, 0x78, 0xC2, 0x29, 0x26, 0xF0, 0x2B, 0xE3, 0x38, 0xC7, 0x19, 0x26, 0xBD, 0x91, 0x90, 0x19, 0xF8, 0x91, 0x63, 0xF8, 0xE2, 0x4E, 0x5C, 0xF6, 0x3D, 0xFE, 0x7B, 0xB8, 0x9D, 0x0C, 0x5E, 0x50, 0x89, 0x2B, 0xE2, 0x9E, 0x1E, 0x51, 0x8C, 0xAB, 0xB0, 0x54, 0x71, 0x83, 0x01, 0x8B, 0x75, 0xD8, 0xA4, 0x69, 0x8B, 0xC0, 0x2C, 0xE2, 0x1B, 0xCB, 0x16, 0x7B, 0xB0, 0xD3, 0x83, 0x16, 0x81, 0xF1, 0x48, 0x3F, 0xB1, 0x65, 0x61, 0xC7, 0x7D, 0x81, 0x1D, 0x0F, 0x8D, 0xDB, 0xFE, 0xC2, 0xA6, 0x45, 0x1E, 0x1F, 0xF0, 0xA8, 0x42, 0xB3, 0x0A, 0xB7, 0xB0, 0x60, 0xE1, 0x72, 0xEE, 0x50, 0xB2, 0x08, 0xCC, 0x01, 0x2E, 0x11, 0x37, 0xD1, 0xD4, 0xE1, 0x90, 0x38, 0x4C, 0xFD, 0x92, 0xC3, 0x2B, 0xEC, 0x5D, 0x27, 0xEE, 0xC9, 0x09, 0x73, 0x48, 0xE6, 0xBC, 0x91, 0x90, 0x2C, 0xAE, 0x70, 0x84, 0x3F, 0x4D, 0x77, 0x88, 0x4E, 0xE0, 0x90, 0xD4, 0xE0, 0xFE, 0x9C, 0x36, 0x7F, 0xAC, 0x15, 0xB8, 0x6C, 0xBF, 0xEC, 0xC3, 0xFE, 0x33, 0x3D, 0xE3, 0x03, 0xCE, 0xFC, 0x35, 0x6C, 0x92, 0x47, 0x65, 0xB7, 0xBD, 0x6E, 0xC0, 0x65, 0x07, 0x1D, 0xB7, 0xCD, 0x59, 0xC2, 0x36, 0x0A, 0x98, 0x6F, 0xDD, 0xEB, 0x4A, 0x14, 0xFD, 0x00, 0x3A, 0xD0, 0x36, 0x27, 0xE1, 0xFD, 0x9A, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/DEBUGGER_BP_RED.hpng ================================================ unsigned char DEBUGGER_BP_RED_png[470] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0D, 0xD5, 0x00, 0x00, 0x0D, 0xD5, 0x01, 0x3D, 0xD6, 0x58, 0xF1, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, 0x00, 0x01, 0x53, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xA5, 0x93, 0xBD, 0x2F, 0x04, 0x51, 0x14, 0xC5, 0x8F, 0x8F, 0x58, 0x12, 0x3A, 0x0A, 0x85, 0x44, 0x25, 0x21, 0xA1, 0x23, 0x11, 0x89, 0x48, 0x24, 0x12, 0xB5, 0x15, 0xAD, 0xCA, 0x6E, 0x4B, 0x4D, 0xBD, 0x7F, 0x8B, 0x52, 0xA5, 0x52, 0x90, 0xC8, 0xD6, 0x24, 0x54, 0x14, 0xA2, 0xA0, 0x43, 0x7C, 0x16, 0xCC, 0xFA, 0x9D, 0xF9, 0x48, 0xC6, 0x78, 0x63, 0x0A, 0x37, 0xF9, 0x65, 0x5F, 0xE6, 0xDD, 0xF3, 0xDE, 0xBB, 0xF7, 0xDC, 0x55, 0x49, 0xF4, 0xC3, 0x12, 0x34, 0x60, 0x0B, 0x16, 0xA0, 0x06, 0x95, 0x31, 0x04, 0xAD, 0x5E, 0xE9, 0x6E, 0x42, 0x8A, 0x56, 0xA4, 0x8E, 0x19, 0x67, 0xDD, 0x23, 0xDD, 0xB0, 0xB7, 0x07, 0x03, 0x4E, 0x0C, 0xC5, 0x58, 0x97, 0x74, 0xBE, 0x4C, 0xF2, 0xA9, 0xF4, 0xF5, 0x0E, 0x1D, 0xC4, 0xE6, 0x8D, 0xF5, 0x11, 0xF0, 0x8C, 0x88, 0xBC, 0x36, 0x0C, 0xC7, 0x8A, 0x5C, 0x0C, 0x22, 0x3E, 0xDB, 0x94, 0x3E, 0x3F, 0x48, 0xCA, 0x84, 0x45, 0x5E, 0xD9, 0x5B, 0x23, 0x87, 0xFC, 0x63, 0xE8, 0x8B, 0x95, 0x69, 0xB4, 0x7C, 0xF3, 0x5F, 0xE2, 0x8C, 0x17, 0x72, 0x66, 0x01, 0xCD, 0x4E, 0x22, 0xA5, 0x26, 0x6A, 0xBE, 0xF7, 0xB3, 0x43, 0x82, 0x10, 0x07, 0xE4, 0xF2, 0xE2, 0x6B, 0xB4, 0xDD, 0x3E, 0x60, 0xD1, 0x0D, 0xCB, 0xD7, 0x5C, 0xC5, 0x33, 0x65, 0x8C, 0xF2, 0x8B, 0x76, 0xC6, 0x07, 0x34, 0xDD, 0xE9, 0x62, 0x52, 0x15, 0x73, 0x80, 0xB6, 0xEE, 0x03, 0x1A, 0xFF, 0x38, 0x80, 0x9E, 0x4A, 0xF3, 0xF6, 0xD9, 0x56, 0x15, 0x93, 0xCA, 0x78, 0xA4, 0x04, 0x7C, 0x64, 0xA9, 0x29, 0x1F, 0x50, 0x63, 0x48, 0x6E, 0xED, 0x73, 0x31, 0xB1, 0x8C, 0xFD, 0xA4, 0x89, 0x97, 0x68, 0xE3, 0x26, 0x3A, 0x76, 0x3D, 0x24, 0xF6, 0x39, 0x24, 0xC8, 0xC3, 0xED, 0x11, 0x9D, 0xB3, 0x8D, 0xCD, 0x44, 0x9A, 0x84, 0xC7, 0xB3, 0xED, 0x21, 0xB1, 0xCF, 0x21, 0xA1, 0x79, 0x60, 0x6F, 0x35, 0x19, 0xA4, 0x43, 0xC0, 0xFD, 0x9F, 0x31, 0x02, 0x27, 0x1E, 0x12, 0xFB, 0x6C, 0xAB, 0x32, 0xE1, 0x13, 0x6B, 0x3F, 0x3B, 0xBD, 0xD9, 0x62, 0xFF, 0x67, 0x82, 0xE1, 0xF1, 0xDC, 0xA6, 0xBE, 0x2B, 0xFB, 0xEC, 0x4E, 0x1B, 0x37, 0x8C, 0x6F, 0x17, 0xEC, 0xF9, 0xD9, 0xBF, 0x6E, 0x0E, 0x85, 0x9B, 0x33, 0x0D, 0x1B, 0xB0, 0x0E, 0x93, 0xE9, 0xB7, 0x42, 0x48, 0xDF, 0x8F, 0x4C, 0x23, 0xE8, 0x8D, 0x9D, 0x99, 0xB1, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/DEBUGGER_GOTO.hpng ================================================ unsigned char DEBUGGER_GOTO_png[292] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x15, 0x9F, 0x00, 0x00, 0x15, 0x9F, 0x01, 0xDF, 0x87, 0xF6, 0x15, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, 0x00, 0x00, 0x94, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xCD, 0xD0, 0xB1, 0x0A, 0x82, 0x50, 0x18, 0x86, 0xE1, 0x63, 0x8E, 0x4E, 0x41, 0xE0, 0x20, 0xDA, 0x8D, 0xB4, 0x05, 0x8E, 0xDE, 0x83, 0x4B, 0xD7, 0xA0, 0x82, 0xE0, 0xE2, 0x50, 0x50, 0xF7, 0xD3, 0x20, 0x82, 0x93, 0x88, 0x5B, 0xD4, 0xF5, 0xF8, 0xFE, 0x83, 0x20, 0x28, 0x9D, 0xE3, 0x20, 0xF8, 0xC2, 0xB3, 0x79, 0x3E, 0xF0, 0x57, 0xBB, 0x2A, 0xC2, 0xCB, 0x50, 0x88, 0x59, 0x4F, 0xBC, 0x91, 0x69, 0x54, 0xC8, 0x31, 0x4B, 0x06, 0xE4, 0x03, 0x5D, 0x05, 0xB6, 0x1D, 0x38, 0x22, 0x86, 0x85, 0x31, 0x1B, 0x37, 0x38, 0xD0, 0x0E, 0xF8, 0xF8, 0x21, 0x81, 0x74, 0xC0, 0x03, 0x3D, 0x4E, 0x30, 0xFA, 0x85, 0x33, 0xBE, 0x48, 0x71, 0x47, 0x07, 0x17, 0x92, 0xF1, 0x0D, 0x02, 0x7C, 0xD0, 0x62, 0x7C, 0x2C, 0xAD, 0x3A, 0xA2, 0x87, 0xE9, 0x63, 0x69, 0xD5, 0xC0, 0x52, 0x7F, 0x07, 0x6A, 0x94, 0x1A, 0x0D, 0x16, 0x07, 0xAE, 0x90, 0xA3, 0x99, 0xB8, 0x60, 0x17, 0x29, 0x35, 0x00, 0xD4, 0x45, 0x31, 0x14, 0xD0, 0x14, 0xF5, 0x33, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/DEBUGGER_PAUSE.hpng ================================================ unsigned char DEBUGGER_PAUSE_png[211] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0D, 0xD6, 0x00, 0x00, 0x0D, 0xD6, 0x01, 0x90, 0x6F, 0x79, 0x9C, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, 0x00, 0x00, 0x43, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x63, 0x18, 0x74, 0x40, 0x04, 0x88, 0x6B, 0x91, 0x30, 0x23, 0x10, 0xB3, 0xA2, 0x89, 0x71, 0x01, 0x31, 0x4E, 0xA0, 0x09, 0xC4, 0xFF, 0x91, 0x30, 0x13, 0x10, 0x83, 0x34, 0x20, 0x8B, 0x89, 0x02, 0x31, 0x4E, 0x30, 0x6A, 0xC0, 0xA8, 0x01, 0x20, 0x40, 0xB1, 0x01, 0x42, 0x40, 0x5C, 0x80, 0x84, 0x61, 0x49, 0x19, 0x59, 0x8C, 0x13, 0x88, 0x07, 0x0D, 0x60, 0x60, 0x00, 0x00, 0x3F, 0x28, 0x44, 0x7C, 0x76, 0x8B, 0xE1, 0x03, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/DEBUGGER_PLAY.hpng ================================================ unsigned char DEBUGGER_PLAY_png[306] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0D, 0xD6, 0x00, 0x00, 0x0D, 0xD6, 0x01, 0x90, 0x6F, 0x79, 0x9C, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, 0x00, 0x00, 0xA2, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xBD, 0xCF, 0xB1, 0x0A, 0x82, 0x40, 0x1C, 0xC7, 0xF1, 0x0B, 0x85, 0x68, 0x15, 0x72, 0x6C, 0x70, 0xEC, 0x19, 0xA2, 0x07, 0x68, 0xF2, 0x09, 0xDA, 0x7A, 0x05, 0xC7, 0x1E, 0xA0, 0x37, 0x70, 0xF5, 0x35, 0xDA, 0xDB, 0x5A, 0x1A, 0x82, 0x68, 0x68, 0x72, 0x72, 0x6A, 0x69, 0x8A, 0xF2, 0xAB, 0xF9, 0x87, 0x88, 0xEB, 0x4E, 0x0F, 0xF1, 0x0B, 0x1F, 0xBC, 0x5B, 0x7E, 0x78, 0xAA, 0xEF, 0x22, 0x8C, 0x3E, 0x47, 0xB7, 0x2E, 0x48, 0x31, 0xAD, 0x6F, 0x0E, 0xDD, 0xF0, 0x46, 0x81, 0x0D, 0xC6, 0xE8, 0x94, 0x0C, 0x88, 0x23, 0x96, 0x68, 0xFD, 0xAC, 0xDF, 0x81, 0xCA, 0x13, 0x19, 0x66, 0xB0, 0xA6, 0x1B, 0x10, 0x77, 0x24, 0x30, 0x3E, 0xCB, 0x34, 0x20, 0xCE, 0x58, 0x41, 0x9B, 0x6D, 0xE0, 0x85, 0x03, 0x16, 0xD0, 0x66, 0x1A, 0xC8, 0xB1, 0x86, 0x8F, 0xBF, 0xE9, 0x06, 0x1E, 0xD8, 0x21, 0x80, 0xB5, 0xEF, 0x81, 0xEA, 0x77, 0xF7, 0x98, 0xA3, 0x75, 0x32, 0x70, 0x45, 0x0C, 0x0F, 0x9D, 0x3A, 0x61, 0x8B, 0x49, 0x7D, 0x73, 0x28, 0x6C, 0xBE, 0x43, 0xA5, 0x54, 0x09, 0x70, 0x17, 0x44, 0x3D, 0x8C, 0x39, 0x0C, 0x30, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/DEBUGGER_STEP_INTO.hpng ================================================ unsigned char DEBUGGER_STEP_INTO_png[297] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x15, 0x9F, 0x00, 0x00, 0x15, 0x9F, 0x01, 0xDF, 0x87, 0xF6, 0x15, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, 0x00, 0x00, 0x99, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xD5, 0xD2, 0x3B, 0x0A, 0xC2, 0x40, 0x14, 0x46, 0xE1, 0x91, 0xF8, 0xC0, 0x57, 0x2A, 0x0D, 0xB8, 0x8B, 0xAC, 0x4C, 0x6C, 0xAC, 0xAC, 0xEC, 0x44, 0xB0, 0x8A, 0xBD, 0x75, 0x5A, 0x37, 0x92, 0x2A, 0xB5, 0xBD, 0xB5, 0x60, 0x9B, 0xF3, 0x83, 0x03, 0x97, 0x44, 0x70, 0x62, 0xA3, 0x39, 0xF0, 0x35, 0x33, 0x64, 0x32, 0x24, 0xD7, 0xFD, 0x55, 0x53, 0x2C, 0xDF, 0x18, 0x23, 0xA8, 0x1D, 0x6E, 0x35, 0x77, 0x6C, 0x10, 0xD4, 0x00, 0x93, 0x9A, 0x13, 0xB6, 0xF8, 0xBA, 0x03, 0x3A, 0x7A, 0x40, 0x8A, 0x33, 0xF4, 0x0D, 0xF6, 0xB8, 0x60, 0x85, 0xE0, 0x62, 0x5C, 0xF1, 0xC0, 0x13, 0x19, 0x86, 0x68, 0x95, 0xFE, 0x7F, 0x8E, 0x23, 0xFA, 0x5A, 0xF8, 0xD4, 0x0C, 0xC9, 0xCB, 0x42, 0x0B, 0xA4, 0xB7, 0x46, 0xE8, 0x41, 0x43, 0xE5, 0xF7, 0x35, 0x74, 0x8D, 0xEC, 0x20, 0x15, 0xD0, 0x83, 0xBE, 0x39, 0x4A, 0xF8, 0xFD, 0x35, 0x1A, 0xE9, 0x9A, 0x23, 0xC3, 0xA6, 0x1B, 0xD8, 0x3D, 0x7B, 0xF8, 0x4F, 0x73, 0xAE, 0x02, 0x87, 0x0E, 0x17, 0xF0, 0xCD, 0xD1, 0x7E, 0x32, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/DEBUGGER_STEP_OUT.hpng ================================================ unsigned char DEBUGGER_STEP_OUT_png[308] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x15, 0x9F, 0x00, 0x00, 0x15, 0x9F, 0x01, 0xDF, 0x87, 0xF6, 0x15, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, 0x00, 0x00, 0xA4, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x63, 0xA0, 0x37, 0x90, 0x01, 0x62, 0x2E, 0x08, 0x93, 0x3C, 0x30, 0x01, 0x88, 0x37, 0x00, 0x31, 0x07, 0x98, 0x47, 0x04, 0xF0, 0x00, 0xE2, 0x36, 0x24, 0xDC, 0x0B, 0xC4, 0xDF, 0x80, 0x78, 0x39, 0x10, 0xB3, 0x00, 0x31, 0x41, 0xD0, 0x01, 0xC4, 0xDB, 0x81, 0xB8, 0x1A, 0x8A, 0x9B, 0x81, 0xF8, 0x23, 0x10, 0x83, 0x0C, 0x60, 0x06, 0x62, 0x82, 0x00, 0x64, 0x40, 0x29, 0x84, 0x09, 0x06, 0x30, 0x2F, 0xB0, 0x81, 0x79, 0x44, 0x00, 0x74, 0x03, 0xA4, 0x81, 0x98, 0x68, 0xFF, 0x83, 0x00, 0xBA, 0x01, 0x24, 0x83, 0x81, 0x37, 0xA0, 0x16, 0x88, 0xB3, 0x21, 0x4C, 0xC2, 0x80, 0x07, 0x88, 0xC5, 0xA0, 0x58, 0x04, 0x24, 0x00, 0x04, 0xA0, 0xD0, 0x66, 0x05, 0x62, 0x46, 0x20, 0x16, 0x05, 0x62, 0x98, 0x3C, 0x37, 0x10, 0x63, 0x00, 0x50, 0x3C, 0xDF, 0x87, 0xE2, 0xF3, 0x40, 0x8C, 0x1C, 0xCF, 0xBC, 0x40, 0x7C, 0x15, 0x88, 0x61, 0xF2, 0x79, 0x40, 0x8C, 0x01, 0x40, 0x29, 0x8B, 0x1D, 0x09, 0x23, 0x03, 0x90, 0x0B, 0x90, 0xE5, 0x88, 0x4A, 0x44, 0xF4, 0x00, 0x0C, 0x0C, 0x00, 0xA1, 0x3A, 0x17, 0xFB, 0x69, 0xBB, 0x81, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/DEBUGGER_STEP_OVER.hpng ================================================ unsigned char DEBUGGER_STEP_OVER_png[296] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x15, 0x9F, 0x00, 0x00, 0x15, 0x9F, 0x01, 0xDF, 0x87, 0xF6, 0x15, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, 0x00, 0x00, 0x98, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x63, 0x18, 0x54, 0x80, 0x1B, 0x88, 0x45, 0x89, 0xC0, 0x42, 0x40, 0xCC, 0x08, 0xC4, 0x18, 0xA0, 0x1D, 0x88, 0x5F, 0x02, 0xF1, 0x7D, 0x3C, 0xF8, 0x21, 0x10, 0xDF, 0x04, 0x62, 0x36, 0x20, 0xC6, 0x00, 0xBD, 0x40, 0x5C, 0x0F, 0xC4, 0x5C, 0x78, 0xB0, 0x12, 0x10, 0xDF, 0x01, 0x62, 0x9C, 0x06, 0x54, 0x41, 0x98, 0x38, 0x81, 0x24, 0x10, 0x0F, 0x51, 0x03, 0x98, 0x80, 0xB8, 0x07, 0x88, 0x43, 0x80, 0x18, 0x64, 0x40, 0x24, 0x10, 0x17, 0x02, 0x31, 0x0A, 0x20, 0xE4, 0x02, 0x2F, 0x20, 0x7E, 0x03, 0xC4, 0x3F, 0x81, 0x18, 0x14, 0x1B, 0x06, 0x40, 0x8C, 0x02, 0x2C, 0x80, 0xD8, 0x10, 0xC2, 0xC4, 0x09, 0x1C, 0x81, 0xF8, 0x0A, 0x10, 0x6B, 0x83, 0x79, 0x78, 0x00, 0xC8, 0xC9, 0x62, 0x48, 0x18, 0x14, 0x85, 0x30, 0xC0, 0x01, 0xA5, 0xF1, 0x02, 0x01, 0x20, 0x06, 0xF9, 0x15, 0x96, 0x80, 0x92, 0x81, 0x98, 0x24, 0x00, 0x4A, 0xAA, 0xEC, 0x48, 0x98, 0x19, 0x88, 0x07, 0x25, 0x60, 0x60, 0x00, 0x00, 0xBC, 0xCF, 0x1F, 0x7C, 0xB2, 0x98, 0xCD, 0xAE, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/INPUT_CONNECTED.hpng ================================================ unsigned char INPUT_CONNECTED_png[400] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x1A, 0x9D, 0x00, 0x00, 0x1A, 0x9D, 0x01, 0x5D, 0x6D, 0x02, 0x48, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, 0x00, 0x01, 0x00, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x9D, 0xD3, 0xBB, 0x6A, 0x02, 0x41, 0x18, 0x86, 0xE1, 0x8D, 0xA7, 0x5B, 0xF0, 0x0E, 0x24, 0x08, 0xDA, 0x59, 0x05, 0x3C, 0xB4, 0x96, 0x31, 0x45, 0x6E, 0x40, 0xAC, 0x3D, 0x91, 0x2A, 0x88, 0x58, 0xD8, 0x0A, 0x16, 0xDE, 0x91, 0x58, 0x0B, 0x29, 0x03, 0x16, 0x49, 0x13, 0x0F, 0x9D, 0x07, 0x3C, 0xBF, 0x9F, 0x38, 0xB0, 0xB8, 0xB3, 0x2B, 0xF1, 0x83, 0xA7, 0x98, 0x65, 0xFF, 0x99, 0xD9, 0xF9, 0x67, 0x9D, 0x07, 0xF3, 0x84, 0x2A, 0x26, 0x97, 0xD1, 0x3F, 0x13, 0xC2, 0x07, 0x56, 0x28, 0xE9, 0x41, 0x50, 0xB4, 0xD2, 0x0B, 0xFA, 0x18, 0xA0, 0x88, 0x26, 0x54, 0xFC, 0x06, 0x6B, 0xC2, 0xC8, 0x22, 0x8A, 0x0E, 0x76, 0x38, 0x5D, 0x1D, 0xB1, 0xC5, 0x3B, 0xAC, 0x89, 0x20, 0x89, 0x3F, 0x8C, 0xA1, 0x02, 0x53, 0x6C, 0x1C, 0x90, 0x81, 0x35, 0x5A, 0x79, 0x8A, 0x3A, 0x66, 0xB8, 0x2D, 0x36, 0xDA, 0xB0, 0x26, 0x86, 0x2F, 0xCC, 0x11, 0x34, 0xC9, 0x27, 0x3C, 0xD1, 0x81, 0x75, 0x61, 0xB6, 0xBD, 0x80, 0x5A, 0xA5, 0xC9, 0xDC, 0xC5, 0x1B, 0x3C, 0xC3, 0x93, 0x1C, 0xF6, 0x70, 0xBF, 0xAC, 0x1D, 0xA8, 0x65, 0xFA, 0x2C, 0x8D, 0x75, 0xA0, 0x15, 0x58, 0xD3, 0x83, 0xBB, 0xD8, 0xD0, 0x24, 0x0D, 0x68, 0x47, 0x23, 0xA8, 0x3B, 0xD6, 0xF8, 0x4D, 0x20, 0xFA, 0x8C, 0x1A, 0x7E, 0x91, 0x82, 0x35, 0x79, 0xA8, 0x45, 0xB7, 0xC5, 0xDA, 0xF6, 0x10, 0x3F, 0x88, 0x43, 0x67, 0xE5, 0x89, 0x1E, 0x6A, 0x9B, 0x7A, 0xD9, 0xDD, 0xFB, 0x35, 0xCA, 0xD0, 0xFD, 0x48, 0xC3, 0xB7, 0x58, 0xD7, 0x73, 0x09, 0xDD, 0xB0, 0x02, 0xD4, 0x8D, 0x16, 0x12, 0xB8, 0x1B, 0xAD, 0xAC, 0x95, 0x5E, 0x2F, 0xA3, 0x07, 0xF2, 0x0D, 0xDF, 0x1F, 0x23, 0x38, 0x8E, 0x73, 0x06, 0xFB, 0x36, 0x5D, 0xD3, 0x1C, 0xAF, 0x1A, 0x69, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/INPUT_DISCONNECTED.hpng ================================================ unsigned char INPUT_DISCONNECTED_png[410] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x1C, 0xD5, 0x00, 0x00, 0x1C, 0xD5, 0x01, 0xE1, 0x9B, 0x06, 0xCC, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, 0x00, 0x01, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xB5, 0xD3, 0x31, 0x4B, 0x42, 0x51, 0x18, 0xC6, 0xF1, 0x93, 0xA6, 0x10, 0xE2, 0xD0, 0x52, 0x5F, 0x20, 0x08, 0x0B, 0x72, 0xCC, 0xA1, 0x31, 0xDA, 0x5B, 0x1B, 0x05, 0x97, 0x08, 0x1D, 0xDB, 0xA3, 0x86, 0x86, 0xD4, 0xA5, 0x4D, 0x90, 0xC6, 0x70, 0x70, 0x14, 0xFD, 0x00, 0x8E, 0xF5, 0x0D, 0x04, 0x45, 0x9A, 0x42, 0x5C, 0x1D, 0x4C, 0xFF, 0x8F, 0xF6, 0x5E, 0xAF, 0x77, 0x38, 0x38, 0xE8, 0x03, 0x3F, 0xB8, 0xF7, 0xC0, 0x7D, 0x38, 0xE7, 0xF5, 0xE8, 0x76, 0x9D, 0x18, 0x8E, 0x50, 0xC7, 0x23, 0xE2, 0xFF, 0x6B, 0xF7, 0xD0, 0xBA, 0x37, 0x37, 0xF8, 0xC4, 0x37, 0x66, 0xF8, 0x43, 0x15, 0x4F, 0x98, 0xE0, 0x16, 0xDE, 0xE4, 0x30, 0x82, 0x3E, 0x36, 0x2A, 0x99, 0x22, 0x8F, 0x8D, 0xF2, 0x80, 0x70, 0x81, 0x95, 0x54, 0xA0, 0xA3, 0x78, 0x73, 0x85, 0x31, 0x5A, 0xF8, 0x45, 0xB4, 0xA4, 0x84, 0xB5, 0x5C, 0x23, 0xB3, 0x7C, 0x5C, 0xE4, 0x1D, 0x0D, 0x24, 0xA1, 0xB2, 0xE8, 0x71, 0x5E, 0x10, 0x64, 0x1F, 0x6D, 0x0C, 0x71, 0xAE, 0x05, 0xA2, 0x89, 0xEB, 0x63, 0xE5, 0x10, 0x5F, 0xB0, 0x8F, 0xB5, 0xAB, 0x14, 0x82, 0x1C, 0xC3, 0xA6, 0x3D, 0xC0, 0x19, 0x2C, 0x69, 0x74, 0xD1, 0xC3, 0x1D, 0x74, 0xAC, 0x1A, 0xF6, 0x10, 0xE4, 0x03, 0xD6, 0x2E, 0x7D, 0xD8, 0x71, 0x12, 0x78, 0xC5, 0xC9, 0xE2, 0xCD, 0xB9, 0x4B, 0x64, 0x97, 0x8F, 0xAB, 0xE8, 0x92, 0x68, 0x30, 0xE1, 0x12, 0xED, 0x24, 0x3C, 0x13, 0x6F, 0x74, 0xDE, 0x32, 0xA2, 0x25, 0x3F, 0xD8, 0xA8, 0x44, 0xBF, 0xA9, 0xA6, 0x1A, 0x2D, 0x10, 0x0D, 0xF6, 0x14, 0xDE, 0xE8, 0x6E, 0xEB, 0x7A, 0x16, 0xF0, 0x06, 0x2B, 0xEA, 0xA0, 0x89, 0x0B, 0x78, 0xA3, 0x3F, 0x86, 0xDD, 0x6D, 0xED, 0xA6, 0x88, 0x67, 0x1C, 0x60, 0x6D, 0xDA, 0x5B, 0x8E, 0x73, 0x73, 0x3A, 0xF1, 0x4C, 0x89, 0x76, 0x54, 0xC3, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/INPUT_LOW_BATTERY.hpng ================================================ unsigned char INPUT_LOW_BATTERY_png[340] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x1E, 0x08, 0x06, 0x00, 0x00, 0x00, 0x3B, 0x30, 0xAE, 0xA2, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, 0xC7, 0x6F, 0xA8, 0x64, 0x00, 0x00, 0x00, 0xE9, 0x49, 0x44, 0x41, 0x54, 0x48, 0x4B, 0xED, 0xD4, 0xB1, 0x0A, 0x01, 0x71, 0x1C, 0xC0, 0xF1, 0x63, 0xB0, 0x50, 0x32, 0x48, 0x19, 0x24, 0x2F, 0x22, 0x92, 0x3C, 0x83, 0x32, 0x7A, 0x01, 0x93, 0xE2, 0x01, 0x8C, 0x26, 0x1B, 0x8B, 0x78, 0x05, 0x83, 0xDD, 0x43, 0x28, 0x83, 0xC5, 0xA2, 0xA4, 0x98, 0x24, 0x7C, 0x7F, 0xFF, 0xBB, 0xAB, 0xEB, 0xEE, 0xB2, 0xDC, 0xFF, 0x6E, 0xB8, 0xFE, 0xDF, 0xFA, 0xD4, 0xFF, 0xEF, 0xAE, 0xFB, 0xF5, 0xBF, 0x2B, 0x96, 0xC9, 0x94, 0xFA, 0x72, 0x28, 0x69, 0xF6, 0xB7, 0x22, 0xB6, 0x78, 0xE1, 0xAB, 0xD9, 0x15, 0x43, 0x84, 0xB6, 0x84, 0xDC, 0xF4, 0xC4, 0x49, 0xA3, 0x33, 0x3E, 0x8E, 0x26, 0x02, 0xDD, 0xF0, 0x46, 0x55, 0xED, 0xF4, 0x36, 0x86, 0x1C, 0x6A, 0xAE, 0x76, 0xBE, 0xE4, 0xC2, 0xDD, 0x5E, 0x5A, 0x59, 0x34, 0x1C, 0x35, 0xF9, 0x21, 0x62, 0x7D, 0xC8, 0xF3, 0x37, 0x6A, 0xE7, 0xCB, 0x3B, 0x58, 0xBE, 0xB7, 0xEC, 0x85, 0xBC, 0xAA, 0xA8, 0x79, 0x07, 0x17, 0x50, 0x47, 0x06, 0xAA, 0xA4, 0x06, 0xBB, 0xEB, 0x09, 0x54, 0x49, 0x0F, 0x5E, 0x40, 0x65, 0x06, 0xC7, 0x35, 0xB8, 0x87, 0x03, 0x5A, 0x50, 0x79, 0x07, 0xE7, 0xB1, 0x77, 0xC8, 0xCD, 0x51, 0xF3, 0x0E, 0x0E, 0x24, 0xFF, 0x2C, 0x0F, 0x7B, 0xA9, 0x3D, 0x77, 0xF0, 0x5A, 0xED, 0x7C, 0x1D, 0x21, 0x17, 0x47, 0xE8, 0x68, 0xD4, 0xC5, 0x0E, 0xF2, 0xEC, 0x29, 0x02, 0x0D, 0x20, 0xA7, 0x96, 0x1B, 0xE2, 0x70, 0x41, 0x19, 0xA1, 0xB5, 0xB1, 0x82, 0xFB, 0x7D, 0x75, 0x90, 0xD3, 0xCE, 0x50, 0x81, 0xC9, 0x64, 0x4A, 0x55, 0x96, 0xF5, 0x03, 0x93, 0x37, 0xA5, 0x1A, 0x4B, 0x52, 0x7C, 0x80, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/M_WND_ICON128.xpm ================================================ /* XPM */ const char* M_WND_ICON128_xpm[] = { "128 128 2075 2", " c None", ". c #3B494D", "+ c #3C484C", "@ c #343B3C", "# c #2E3234", "$ c #2D3232", "% c #2E3232", "& c #303435", "* c #373F40", "= c #3F4D51", "- c #3C4D50", "; c #334247", "> c #2A3132", ", c #2E3336", "' c #282C2D", ") c #262727", "! c #28292A", "~ c #2C2D2D", "{ c #2E2D2D", "] c #2E2E2E", "^ c #2E2E2D", "/ c #2D2D2D", "( c #2B2D2D", "_ c #272929", ": c #262728", "< c #2C3031", "[ c #2F3639", "} c #2E383A", "| c #3A4D52", "1 c #2F3637", "2 c #272A2B", "3 c #292C2C", "4 c #2B2C2C", "5 c #2C2C2C", "6 c #2F2F2E", "7 c #313131", "8 c #323232", "9 c #333333", "0 c #303030", "a c #2B2D2C", "b c #292F30", "c c #2A3031", "d c #344245", "e c #2C3336", "f c #272A2A", "g c #292A2B", "h c #2F2F2F", "i c #282A2B", "j c #272A2C", "k c #242B2D", "l c #2A3336", "m c #282B2B", "n c #313030", "o c #333332", "p c #333231", "q c #333131", "r c #34302E", "s c #352D2B", "t c #352D29", "u c #352D2A", "v c #342E2C", "w c #343130", "x c #333232", "y c #2A2A2A", "z c #25282A", "A c #273234", "B c #2A3536", "C c #323333", "D c #342F2E", "E c #342F2C", "F c #34302F", "G c #313637", "H c #2E3F46", "I c #294B57", "J c #265261", "K c #255666", "L c #255869", "M c #245869", "N c #255768", "O c #265565", "P c #27515F", "Q c #2A4853", "R c #2F3C41", "S c #323334", "T c #342F2D", "U c #302F2F", "V c #282929", "W c #313D41", "X c #25292A", "Y c #2A2B2B", "Z c #2C434C", "` c #215F75", " . c #177D9E", ".. c #108EB9", "+. c #0A9ACF", "@. c #06A5E0", "#. c #05AAE5", "$. c #04ACE7", "%. c #04AEE8", "&. c #04ACE6", "*. c #07A2DE", "=. c #0C95C8", "-. c #1289B1", ";. c #1B7290", ">. c #265464", ",. c #303B40", "'. c #34302D", "). c #333130", "!. c #2F2E2E", "~. c #283134", "{. c #2F3C3F", "]. c #282C2E", "^. c #31383A", "/. c #255867", "(. c #167EA2", "_. c #0A9FD1", ":. c #03ADEA", "<. c #01B1F1", "[. c #00B2F1", "}. c #00B0F2", "|. c #00AEF0", "1. c #00ADEF", "2. c #00ADEE", "3. c #00AEF1", "4. c #00B1F2", "5. c #02B0EF", "6. c #05AAE3", "7. c #0E94C2", "8. c #1B718E", "9. c #294D58", "0. c #2B2B2B", "a. c #222526", "b. c #232626", "c. c #31393B", "d. c #225E72", "e. c #128DB5", "f. c #06A9E1", "g. c #01B2F0", "h. c #00B0F1", "i. c #00AEEF", "j. c #00ACEE", "k. c #00AFF0", "l. c #02B1ED", "m. c #08A4D7", "n. c #1682A3", "o. c #28505C", "p. c #272B2D", "q. c #313F42", "r. c #1A1A1A", "s. c #272727", "t. c #138BB2", "u. c #05ABE3", "v. c #01B1F0", "w. c #00AFEF", "x. c #01B1EE", "y. c #08A5D9", "z. c #197999", "A. c #2B464E", "B. c #27292A", "C. c #282F30", "D. c #1F1F21", "E. c #282828", "F. c #352E2C", "G. c #2E3F43", "H. c #197696", "I. c #07A6DB", "J. c #01B1EF", "K. c #00ACED", "L. c #00ADED", "M. c #0D99C8", "N. c #235E71", "O. c #323434", "P. c #2C3537", "Q. c #212323", "R. c #282727", "S. c #2A4A55", "T. c #128EB3", "U. c #03AFEA", "V. c #06A8DE", "W. c #1A7592", "X. c #2F3B3D", "Y. c #313F43", "Z. c #2A2F32", "`. c #323131", " + c #275361", ".+ c #0C9BC9", "++ c #01B2ED", "@+ c #00AEED", "#+ c #04AFE7", "$+ c #1682A4", "%+ c #2E3F44", "&+ c #352E2D", "*+ c #262B2D", "=+ c #09A0D4", "-+ c #00B1F0", ";+ c #00AEEE", ">+ c #02B1EB", ",+ c #128CB3", "'+ c #2C434A", ")+ c #222424", "!+ c #252828", "~+ c #265463", "{+ c #0AA0D2", "]+ c #118EB6", "^+ c #2E4046", "/+ c #352F2C", "(+ c #2B3436", "_+ c #2E393C", ":+ c #284E5B", "<+ c #0B9DCD", "[+ c #00B1EF", "}+ c #1389AE", "|+ c #303A3E", "1+ c #262828", "2+ c #2B3638", "3+ c #272828", "4+ c #2C434B", "5+ c #0F93BD", "6+ c #00ABED", "7+ c #01AAEC", "8+ c #02A9EC", "9+ c #03A9EC", "0+ c #03AAEC", "a+ c #01ABED", "b+ c #03B0EC", "c+ c #187899", "d+ c #262B2B", "e+ c #333030", "f+ c #313738", "g+ c #1581A5", "h+ c #01ABEC", "i+ c #02AAEC", "j+ c #09AAEC", "k+ c #13ACEC", "l+ c #1AAFED", "m+ c #22B3EE", "n+ c #32B7EF", "o+ c #42BBEF", "p+ c #4ABDF0", "q+ c #4EBFF0", "r+ c #4FC0F0", "s+ c #4CBEF0", "t+ c #47BDF0", "u+ c #3DBAEF", "v+ c #2DB6EF", "w+ c #1EB1ED", "x+ c #18ADED", "y+ c #10AAEC", "z+ c #05AAEC", "A+ c #00ACEC", "B+ c #215F73", "C+ c #292A2A", "D+ c #222727", "E+ c #323231", "F+ c #215F74", "G+ c #05AAE1", "H+ c #00ABEC", "I+ c #05AAEB", "J+ c #10ACEC", "K+ c #2FB4ED", "L+ c #4FC1F0", "M+ c #70CCF2", "N+ c #94D7F4", "O+ c #AAE0F6", "P+ c #BEE7F8", "Q+ c #CEECF9", "R+ c #D6F0F9", "S+ c #DAF1FA", "T+ c #DBF2FA", "U+ c #DCF2FA", "V+ c #D8F0FA", "W+ c #D3EFF9", "X+ c #C9EBF9", "Y+ c #B7E4F7", "Z+ c #A3DCF6", "`+ c #89D3F4", " @ c #63C7F1", ".@ c #44BCEF", "+@ c #24B0EC", "@@ c #0BABEC", "#@ c #03A9EB", "$@ c #01ABEB", "%@ c #00B0EE", "&@ c #0C99C7", "*@ c #2C424A", "=@ c #232525", "-@ c #332F2D", ";@ c #2D4047", ">@ c #0D94C0", ",@ c #00AFEE", "'@ c #01AAEB", ")@ c #06A9EB", "!@ c #17ACEC", "~@ c #35B7EE", "{@ c #65C7F1", "]@ c #97DAF5", "^@ c #C2E8F8", "/@ c #E3F3FA", "(@ c #F1F8FB", "_@ c #F6FAFC", ":@ c #F9FBFC", "<@ c #FBFCFC", "[@ c #FCFCFC", "}@ c #FAFBFC", "|@ c #F8FBFC", "1@ c #F5FAFB", "2@ c #EDF7FB", "3@ c #DAF0F9", "4@ c #B5E3F6", "5@ c #89D3F3", "6@ c #56C0EF", "7@ c #29B1EC", "8@ c #0DA9EB", "9@ c #02A9EB", "0@ c #02AFEB", "a@ c #1A7391", "b@ c #323030", "c@ c #292929", "d@ c #282829", "e@ c #323130", "f@ c #332F2E", "g@ c #1B6E8B", "h@ c #03AEE8", "i@ c #08A9EB", "j@ c #26B0ED", "k@ c #5BC2F0", "l@ c #99D8F4", "m@ c #CCEBF8", "n@ c #E7F4FA", "o@ c #F7FAFC", "p@ c #FBFBFC", "q@ c #F4F9FB", "r@ c #E0F2FA", "s@ c #BBE4F6", "t@ c #7ACCF1", "u@ c #39B5EC", "v@ c #0EA9EB", "w@ c #01A9EB", "x@ c #099FD2", "y@ c #294A56", "z@ c #332E2C", "A@ c #282C2C", "B@ c #2E3738", "C@ c #0B9ACA", "D@ c #00B0EF", "E@ c #00AAEC", "F@ c #05A9EB", "G@ c #24B1ED", "H@ c #67C6F1", "I@ c #B1E1F6", "J@ c #EFF7FB", "K@ c #C9E9F7", "L@ c #7FCDF0", "M@ c #30B2EB", "N@ c #09A9EA", "O@ c #02B0EC", "P@ c #17799A", "Q@ c #29292A", "R@ c #354346", "S@ c #292C2D", "T@ c #332D2B", "U@ c #1E677F", "V@ c #03ADE9", "W@ c #00ABEB", "X@ c #1CADEC", "Y@ c #64C6F0", "Z@ c #B9E4F6", "`@ c #EBF6FA", " # c #F9FBFB", ".# c #FBFBFB", "+# c #F1F7FA", "@# c #C5E6F5", "## c #6DC5ED", "$# c #1FABE9", "%# c #03A8EB", "&# c #099ED2", "*# c #294752", "=# c #332E2D", "-# c #303A3C", ";# c #39454A", "># c #332F2F", ",# c #2F383B", "'# c #0E8EB9", ")# c #00ABEA", "!# c #01A9EA", "~# c #10A9EA", "{# c #4BBBEE", "]# c #A6DCF4", "^# c #E7F4F9", "/# c #F9FAFB", "(# c #FAFBFB", "_# c #E8F3F8", ":# c #A7DAF0", "<# c #44B5E8", "[# c #09A7E9", "}# c #00AAEB", "|# c #02ADE9", "1# c #1B6C87", "2# c #26292A", "3# c #272C2D", "4# c #342D2B", "5# c #255363", "6# c #05A9E0", "7# c #03A8EA", "8# c #22AEEB", "9# c #7CCDF1", "0# c #D4EDF8", "a# c #F6F9FB", "b# c #F5F8FA", "c# c #CBE7F4", "d# c #62C0E8", "e# c #10A7E7", "f# c #0E8EBC", "g# c #2E3B3F", "h# c #394446", "i# c #2B3335", "j# c #187393", "k# c #01AFED", "l# c #07A8EA", "m# c #3CB7ED", "n# c #A5DCF5", "o# c #FAFAFB", "p# c #DFEFF6", "q# c #78C6E9", "r# c #16A7E5", "s# c #06A5DB", "t# c #264F5E", "u# c #262B2E", "v# c #2A2B2C", "w# c #303638", "x# c #0F90BA", "y# c #00AFED", "z# c #00AAEA", "A# c #09A8EA", "B# c #48BAEE", "C# c #BAE3F6", "D# c #F3F8FA", "E# c #E5F1F6", "F# c #7DC6E7", "G# c #15A6E5", "H# c #02ADEA", "I# c #1A6F8D", "J# c #322F2E", "K# c #39474C", "L# c #2A444E", "M# c #089FD3", "N# c #4CBCEE", "O# c #C0E6F6", "P# c #F4F8FA", "Q# c #FAFAFA", "R# c #E2F0F5", "S# c #70C1E4", "T# c #10A5E4", "U# c #1287AD", "V# c #313435", "W# c #33302F", "X# c #2C2E2E", "Y# c #384448", "Z# c #342D2A", "`# c #245668", " $ c #04A8E4", ".$ c #05A8E9", "+$ c #41B8EC", "@$ c #C0E5F5", "#$ c #F5F9FA", "$$ c #F9F9F9", "%$ c #F8F8F8", "&$ c #F5F5F6", "*$ c #ECEDED", "=$ c #E4E6E8", "-$ c #E1E4E6", ";$ c #DFE2E4", ">$ c #DEE2E4", ",$ c #E0E4E5", "'$ c #E4E7E8", ")$ c #ECEDEE", "!$ c #F6F6F6", "~$ c #F8F9F9", "{$ c #F9FAFA", "]$ c #D7EAF2", "^$ c #5AB6DF", "/$ c #09A4E4", "($ c #0E92BE", "_$ c #2E3B40", ":$ c #33302E", "<$ c #2E3131", "[$ c #352C29", "}$ c #205F77", "|$ c #03ABE8", "1$ c #02A8E9", "2$ c #31B2EB", "3$ c #B4E2F5", "4$ c #F6F6F7", "5$ c #EBECED", "6$ c #D0D2D5", "7$ c #ADB3B8", "8$ c #909DA5", "9$ c #707D88", "0$ c #5D6F7C", "a$ c #546C7B", "b$ c #506A7B", "c$ c #4F6A7C", "d$ c #546E7F", "e$ c #5D7382", "f$ c #71838F", "g$ c #94A4AD", "h$ c #B5BDC2", "i$ c #E0E1E2", "j$ c #F2F3F3", "k$ c #F7F8F9", "l$ c #C3E0EC", "m$ c #3EAADB", "n$ c #03A6E6", "o$ c #089DD4", "p$ c #284854", "q$ c #282625", "r$ c #342C29", "s$ c #1D6983", "t$ c #02AEE9", "u$ c #1BACEA", "v$ c #9AD8F3", "w$ c #F4F4F5", "x$ c #D9DBDD", "y$ c #A9AEB3", "z$ c #6C7B85", "A$ c #3D5668", "B$ c #1E4760", "C$ c #0E4F6E", "D$ c #06587D", "E$ c #04628B", "F$ c #046A96", "G$ c #036E9C", "H$ c #03709F", "I$ c #04709E", "J$ c #056C99", "K$ c #076690", "L$ c #0F6085", "M$ c #265C79", "N$ c #537689", "O$ c #91A0AB", "P$ c #CFD4D7", "Q$ c #F1F2F3", "R$ c #F2F6F7", "S$ c #A1D0E4", "T$ c #1FA2DA", "U$ c #00A9E9", "V$ c #05A4DF", "W$ c #245365", "X$ c #282A2A", "Y$ c #3C4A4D", "Z$ c #272525", "`$ c #332E2B", " % c #197391", ".% c #01B0EC", "+% c #0AA8E9", "@% c #71C9F0", "#% c #E8F4F9", "$% c #DEE0E2", "%% c #989EA5", "&% c #485A6A", "*% c #18475F", "=% c #065478", "-% c #00719E", ";% c #0088BC", ">% c #0098D0", ",% c #00A1DD", "'% c #00A6E4", ")% c #00A9E7", "!% c #00AAE9", "~% c #00A9E8", "{% c #00A7E5", "]% c #00A0DD", "^% c #0096CE", "/% c #0281B3", "(% c #106B94", "_% c #3B6F8B", ":% c #8EA2AE", "<% c #DDE1E3", "[% c #E9F0F3", "}% c #71B9D9", "|% c #0BA0DE", "1% c #05A6E1", "2% c #23586C", "3% c #342D29", "4% c #363A3D", "5% c #2F3537", "6% c #16799A", "7% c #04A8E8", "8% c #45BAEC", "9% c #D0EBF6", "0% c #EDEEEF", "a% c #B5BABE", "b% c #536472", "c% c #14455F", "d% c #026088", "e% c #0086B9", "f% c #009DD8", "g% c #00A4E1", "h% c #018EC6", "i% c #1373A0", "j% c #57859E", "k% c #B9C7CE", "l% c #F0F2F2", "m% c #C6DEE8", "n% c #3AA2D1", "o% c #02A4E4", "p% c #04A8E1", "q% c #225B6F", "r% c #2F3234", "s% c #157A9D", "t% c #01A8E8", "u% c #21AEE9", "v% c #A8DDF3", "w% c #F6F8F9", "x% c #E0E2E3", "y% c #8A939A", "z% c #2B485B", "A% c #055679", "B% c #00A2DE", "C% c #00A4E2", "D% c #0688BF", "E% c #367B9E", "F% c #A4B9C4", "G% c #EEF1F1", "H% c #EEF2F4", "I% c #8CC0D7", "J% c #139BD5", "K% c #00A9EA", "L% c #225B70", "M% c #313839", "N% c #157A9E", "O% c #0AA7E8", "P% c #66C6EE", "Q% c #E5F2F8", "R% c #F7F7F7", "S% c #D8DBDD", "T% c #737E88", "U% c #18455D", "V% c #016A94", "W% c #0099D2", "X% c #0495D0", "Y% c #2E83AC", "Z% c #A4BECB", "`% c #F2F4F4", " & c #D0E1E8", ".& c #43A2CC", "+& c #01A2E3", "@& c #2E2F31", "#& c #32302F", "$& c #01AFEC", "%& c #2CB0EA", "&& c #B7E2F4", "*& c #F7F8F8", "=& c #D5D9DB", "-& c #6A7882", ";& c #124862", ">& c #0079A8", ",& c #00A1DC", "'& c #039AD7", ")& c #338BB4", "!& c #BBCFD9", "~& c #EFF2F4", "{& c #86BBD1", "]& c #0C97D3", "^& c #342C28", "/& c #313739", "(& c #00A8E8", "_& c #08A7E7", ":& c #6FC8EE", "<& c #E5F2F6", "[& c #E1E4E5", "}& c #737F89", "|& c #134964", "1& c #007EAE", "2& c #0498D5", "3& c #539DBF", "4& c #D8E4E9", "5& c #F6F7F7", "6& c #C5D9E2", "7& c #2A97C5", "8& c #01A5E5", "9& c #04A7E0", "0& c #225A70", "a& c #2E2F2F", "b& c #00A7E8", "c& c #24AEE9", "d& c #B4E0F3", "e& c #F5F7F8", "f& c #EDEFEF", "g& c #8F9AA2", "h& c #1B4963", "i& c #017AA9", "j& c #1395CD", "k& c #8DBCD1", "l& c #EEF2F3", "m& c #E5ECEE", "n& c #60A4C3", "o& c #039BD8", "p& c #2C2F2F", "q& c #157A9C", "r& c #01AEEB", "s& c #04A6E7", "t& c #57C0EC", "u& c #E1F0F6", "v& c #F5F5F5", "w& c #B8BFC3", "x& c #2B4E63", "y& c #01709C", "z& c #00A8E7", "A& c #01A3E3", "B& c #369BC9", "C& c #C3DBE4", "D& c #99BFCF", "E& c #1191C8", "F& c #215A6F", "G& c #332B28", "H& c #14A9E7", "I& c #9BD6F0", "J& c #F1F5F7", "K& c #DFE2E3", "L& c #5E7280", "M& c #055C82", "N& c #009BD5", "O& c #0B9BD9", "P& c #7ABAD6", "Q& c #ECF1F3", "R& c #C9D9DF", "S& c #2F8FBC", "T& c #00A4E3", "U& c #04A7DF", "V& c #15799C", "W& c #02A5E6", "X& c #37B3E9", "Y& c #CCE8F3", "Z& c #F3F3F4", "`& c #A1ABB1", " * c #1A516D", ".* c #008BC0", "+* c #01A4E4", "@* c #37A4D4", "#* c #CBE1E9", "$* c #E4E9EC", "%* c #529CBB", "&* c #0399D7", "** c #04A6DF", "=* c #313639", "-* c #01ADEA", ";* c #07A5E6", ">* c #6CC5EB", ",* c #E8F1F5", "'* c #DCDFE0", ")* c #4D6979", "!* c #036A95", "~* c #00A3E0", "{* c #00A7E7", "]* c #129FDB", "^* c #95C8DE", "/* c #EFF2F3", "(* c #EEF0F1", "_* c #7EABBF", ":* c #0B8EC7", "<* c #15799B", "[* c #1AA8E6", "}* c #A0D8EF", "|* c #F3F5F6", "1* c #A8B4B9", "2* c #1A5471", "3* c #008FC5", "4* c #04A2E2", "5* c #56B2D9", "6* c #DFEAEF", "7* c #F4F4F4", "8* c #F0F0F0", "9* c #A4C0CC", "0* c #158ABC", "a* c #00A6E5", "b* c #15789B", "c* c #01ADE9", "d* c #00A7E6", "e* c #00A5E6", "f* c #35B2E8", "g* c #CAE6F2", "h* c #F4F5F5", "i* c #E8EAEA", "j* c #6D8390", "k* c #056791", "l* c #00A1DE", "m* c #00A5E5", "n* c #2BA4D9", "o* c #BCDBE8", "p* c #F3F4F5", "q* c #F1F1F1", "r* c #CCCED1", "s* c #B7BCC0", "t* c #A7B1B6", "u* c #8F9CA3", "v* c #5F7E8C", "w* c #0E7FAB", "x* c #04A5DE", "y* c #313638", "z* c #01ACE9", "A* c #04A3E5", "B* c #56BEEA", "C* c #DFEEF3", "D* c #CFD5D7", "E* c #37647B", "F* c #0183B7", "G* c #00A6E6", "H* c #12A2DF", "I* c #91CBE3", "J* c #E1E5E8", "K* c #D7D9DB", "L* c #CACFD2", "M* c #BDC5C8", "N* c #A0A8AD", "O* c #838E97", "P* c #6E818C", "Q* c #516F7D", "R* c #36586A", "S* c #274D63", "T* c #1C4E68", "U* c #0E516F", "V* c #065E83", "W* c #0189BB", "X* c #00A5E3", "Y* c #15789A", "Z* c #0BA5E5", "`* c #85CDEC", " = c #ECF2F4", ".= c #F1F2F2", "+= c #A9B3B9", "@= c #175E80", "#= c #0099D4", "$= c #04A5E4", "%= c #3CA3CE", "&= c #628798", "*= c #4B6070", "== c #335669", "-= c #24556B", ";= c #15516C", ">= c #0E5171", ",= c #095A7F", "'= c #036790", ")= c #0074A1", "!= c #007FB0", "~= c #0088BD", "{= c #0092CA", "]= c #00A2E0", "^= c #215A6E", "/= c #312F2E", "(= c #01ACE8", "_= c #12A8E4", ":= c #A6DAEE", "<= c #F2F3F4", "[= c #E9EAEB", "}= c #758D99", "|= c #076994", "1= c #0396D0", "2= c #05719D", "3= c #03658F", "4= c #0172A0", "5= c #007FB1", "6= c #008CC1", "7= c #0095CE", "8= c #009BD6", "9= c #04A4DD", "0= c #21596E", "a= c #00A4E4", "b= c #2AADE5", "c= c #BFE1EF", "d= c #F3F3F3", "e= c #DADCDE", "f= c #4D7285", "g= c #007EB0", "h= c #00A5E2", "i= c #00A2DF", "j= c #157799", "k= c #01ABE7", "l= c #00A3E4", "m= c #43B4E6", "n= c #D5E9F0", "o= c #F2F2F2", "p= c #BFC7CB", "q= c #326580", "r= c #008DC5", "s= c #04A4DC", "t= c #21596D", "u= c #56BDE6", "v= c #E2EDF1", "w= c #F0F1F1", "x= c #A1B3BA", "y= c #176486", "z= c #00A6E3", "A= c #312E2E", "B= c #01AAE6", "C= c #07A2E2", "D= c #76C8E8", "E= c #EBF0F1", "F= c #EEEEEE", "G= c #90A6AF", "H= c #0D6D96", "I= c #00A0DC", "J= c #04A3DB", "K= c #322A27", "L= c #11A3E1", "M= c #95D0EA", "N= c #E5E6E7", "O= c #76909E", "P= c #0974A2", "Q= c #312E2D", "R= c #157699", "S= c #18A5E1", "T= c #A7D7EB", "U= c #EFF1F1", "V= c #DADFE0", "W= c #5B7D91", "X= c #057AAC", "Y= c #21586D", "Z= c #303637", "`= c #01AAE5", " - c #00A3E2", ".- c #1BA7E1", "+- c #B2DAEB", "@- c #EFF0F1", "#- c #D4DADD", "$- c #4B7990", "%- c #0382B7", "&- c #04A2DA", "*- c #21586C", "=- c #157698", "-- c #00A3E1", ";- c #20A9E1", ">- c #BDDDEB", ",- c #F0F0F1", "'- c #D0D8DB", ")- c #427A94", "!- c #0289C0", "~- c #01A9E4", "{- c #29ACE0", "]- c #CBE3EC", "^- c #CCD6D9", "/- c #397A96", "(- c #04A2D9", "_- c #303537", ":- c #302E2D", "<- c #157597", "[- c #01A1E0", "}- c #35AFE1", "|- c #D3E6EC", "1- c #F0EFEF", "2- c #C8D0D3", "3- c #307896", "4- c #0092CB", "5- c #04A1D9", "6- c #20576C", "7- c #01A8E3", "8- c #029FDF", "9- c #3FB1E1", "0- c #D6E7ED", "a- c #EFEFEF", "b- c #EFEEEE", "c- c #BFC8CC", "d- c #297495", "e- c #0095CF", "f- c #04A1D8", "g- c #2C2C2E", "h- c #029FDE", "i- c #44B2E1", "j- c #D7E7ED", "k- c #B9C4C8", "l- c #257496", "m- c #0097D1", "n- c #04A0D8", "o- c #157596", "p- c #01A8E2", "q- c #029EDE", "r- c #46B2E1", "s- c #D8E7EC", "t- c #EDEDED", "u- c #B7C2C7", "v- c #247497", "w- c #0097D2", "x- c #04A0D7", "y- c #2A2A2B", "z- c #2B2C2E", "A- c #029EDD", "B- c #46B2E0", "C- c #B6C2C7", "D- c #247598", "E- c #20576B", "F- c #2F3030", "G- c #01A7E1", "H- c #45B1DF", "I- c #D7E6EB", "J- c #ECECEC", "K- c #B7C3C7", "L- c #257699", "M- c #00A3DF", "N- c #312A27", "O- c #2F3536", "P- c #302D2D", "Q- c #157495", "R- c #01A6E1", "S- c #029EDC", "T- c #43B0DD", "U- c #D5E4EA", "V- c #ECECEB", "W- c #BAC6CA", "X- c #27789A", "Y- c #0096D1", "Z- c #049FD6", "`- c #20566A", " ; c #302D2C", ".; c #01A6E0", "+; c #029DDC", "@; c #3EAEDD", "#; c #D4E3E9", "$; c #C2CCCF", "%; c #2D7C9D", "&; c #0094CE", "*; c #049FD5", "=; c #2F2D2C", "-; c #019DDC", ";; c #35ABDB", ">; c #D0E1E7", ",; c #EBEBEB", "'; c #C7D2D5", "); c #347FA0", "!; c #0192CC", "~; c #312A26", "{; c #2F3436", "]; c #157394", "^; c #01A6DF", "/; c #009EDB", "(; c #28A7DA", "_; c #C7DDE5", ":; c #CAD4D8", "<; c #3D82A0", "[; c #028EC7", "}; c #049ED5", "|; c #312926", "1; c #2F3435", "2; c #01A5DE", "3; c #00A0DB", "4; c #009FDB", "5; c #1FA2D8", "6; c #B8D6E3", "7; c #E9EAEA", "8; c #EAEAEA", "9; c #CDD5D8", "0; c #46829E", "a; c #038AC3", "b; c #049ED4", "c; c #205569", "d; c #1AA0D8", "e; c #ACD1E1", "f; c #E8E9EA", "g; c #D1D7DA", "h; c #51849D", "i; c #0486BC", "j; c #2F2C2C", "k; c #147293", "l; c #01A4DD", "m; c #009FDA", "n; c #179ED7", "o; c #A1CDDF", "p; c #E7E8E9", "q; c #E9E9E9", "r; c #D9DCDD", "s; c #6790A4", "t; c #0782B7", "u; c #049DD3", "v; c #009EDA", "w; c #109BD7", "x; c #8DC6DD", "y; c #E5E8E8", "z; c #E2E3E3", "A; c #82A4B3", "B; c #0B81B2", "C; c #009ED9", "D; c #147292", "E; c #01A3DD", "F; c #069BD7", "G; c #6FBBD9", "H; c #E1E5E7", "I; c #E8E8E8", "J; c #E5E6E6", "K; c #93B1BC", "L; c #107DAB", "M; c #009CD7", "N; c #019ED9", "O; c #11A2DA", "P; c #23A6DB", "Q; c #1DA1DA", "R; c #179ED9", "S; c #0C9CD9", "T; c #049CD9", "U; c #039CD9", "V; c #029CD9", "W; c #019CD9", "X; c #009DD9", "Y; c #049DD2", "Z; c #1F5568", "`; c #302926", " > c #019BD8", ".> c #51AFD5", "+> c #D8E0E4", "@> c #E6E7E7", "#> c #A7BCC5", "$> c #227BA4", "%> c #0098D4", "&> c #069BD9", "*> c #53B6DE", "=> c #ACD3E4", "-> c #A2CFE3", ";> c #91C9E1", ">> c #77C1E0", ",> c #5FBADE", "'> c #52B4DE", ")> c #43ADDC", "!> c #2EA5DA", "~> c #1EA2DA", "{> c #14A0DA", "]> c #0C9DD9", "^> c #089BD8", "/> c #049BD9", "(> c #019DD9", "_> c #049CD2", ":> c #2E3435", "<> c #2E2C2B", "[> c #147192", "}> c #01A3DC", "|> c #009CD8", "1> c #40A7D4", "2> c #CBDBE2", "3> c #E7E7E7", "4> c #C3CCD1", "5> c #3C82A4", "6> c #0093CE", "7> c #0D9BD8", "8> c #74BFDF", "9> c #DDE4E7", "0> c #E4E6E7", "a> c #E2E6E7", "b> c #DFE5E7", "c> c #DBE3E6", "d> c #D5E1E6", "e> c #C7DCE5", "f> c #B4D6E4", "g> c #A5D1E3", "h> c #9ACEE2", "i> c #84C4E0", "j> c #67B9DE", "k> c #47B0DC", "l> c #12A2DA", "m> c #049CD1", "n> c #2E3335", "o> c #147191", "p> c #01A2DB", "q> c #009BD8", "r> c #279FD2", "s> c #B5D3DE", "t> c #E6E6E6", "u> c #D5D8DA", "v> c #558EA8", "w> c #028BC3", "x> c #189ED8", "y> c #96C9DF", "z> c #E2E5E6", "A> c #E6E6E7", "B> c #E3E5E6", "C> c #DBE3E5", "D> c #BAD3DC", "E> c #2D98C2", "F> c #049BD1", "G> c #1F5468", "H> c #2D3335", "I> c #119BD2", "J> c #9BC8D9", "K> c #E4E5E5", "L> c #DFE1E2", "M> c #84A7B8", "N> c #0C83B6", "O> c #009BD7", "P> c #26A4D8", "Q> c #B3D5E2", "R> c #E5E5E5", "S> c #B4BEC3", "T> c #226D8F", "U> c #0091C8", "V> c #049BD0", "W> c #302925", "X> c #01A1DA", "Y> c #0998D3", "Z> c #77B7D3", "`> c #DBE1E3", " , c #E4E4E3", "., c #AFC1C9", "+, c #1E83AE", "@, c #0099D5", "#, c #48ADD9", "$, c #C6DBE3", "%, c #E1E2E2", "&, c #8C9DA5", "*, c #105F83", "=, c #0094CD", "-, c #049ACF", ";, c #1F5467", ">, c #292B2C", ",, c #147090", "', c #01A1D9", "), c #009CD6", "!, c #0298D3", "~, c #4AA8CF", "{, c #CDDBDF", "], c #E4E4E4", "^, c #CCD4D7", "/, c #488EAE", "(, c #0291CA", "_, c #009DD7", ":, c #089AD5", "<, c #72BBDB", "[, c #D9E0E3", "}, c #DCDDDD", "|, c #6A8591", "1, c #046993", "2, c #049ACE", "3, c #2D3334", "4, c #009AD5", "5, c #2B9CCC", "6, c #B3CED8", "7, c #E3E3E3", "8, c #DFE0E0", "9, c #7FA9BC", "0, c #0A88BD", "a, c #1B9DD5", "b, c #9BCBDE", "c, c #E1E2E4", "d, c #C9CDCE", "e, c #476B7D", "f, c #0277A6", "g, c #0499CE", "h, c #1F5367", "i, c #302825", "j, c #2D2E2E", "k, c #146F8F", "l, c #01A0D8", "m, c #1295CD", "n, c #88BCD2", "o, c #B3C6CE", "p, c #2988B3", "q, c #0196D1", "r, c #0299D4", "s, c #40AAD7", "t, c #C3D8E0", "u, c #AEB8BB", "v, c #235B75", "w, c #0084B8", "x, c #2F2825", "y, c #2D3234", "z, c #2D2C2C", "A, c #2E2B2B", "B, c #019FD7", "C, c #009AD4", "D, c #0595D1", "E, c #56A5C9", "F, c #CFD9DD", "G, c #E2E2E2", "H, c #D6DADC", "I, c #639EB8", "J, c #088DC5", "K, c #0D99D4", "L, c #7DBFDB", "M, c #DADFE2", "N, c #DEDEDE", "O, c #88969D", "P, c #0F5B7E", "Q, c #0092C8", "R, c #0498CD", "S, c #1E5366", "T, c #2D2B2B", "U, c #136F8E", "V, c #0198D3", "W, c #2798C8", "X, c #AFCAD5", "Y, c #E1E1E1", "Z, c #ABC3CC", "`, c #288DBA", " ' c #0197D2", ".' c #0197D3", "+' c #2FA3D5", "@' c #E1E1E2", "#' c #D2D5D5", "$' c #547180", "%' c #056993", "&' c #0098D1", "*' c #1E5365", "=' c #2C3233", "-' c #2D2B2A", ";' c #136E8E", ">' c #019ED6", ",' c #0099D3", "'' c #0C92CA", ")' c #7AB2CA", "!' c #D8DCDD", "~' c #E0E0E0", "{' c #D4D9DB", "]' c #6EA7C1", "^' c #0A8EC6", "/' c #0B97D2", "(' c #6FB8D8", "_' c #D6DDE0", ":' c #B5BBBD", "<' c #2A566E", "[' c #0497CC", "}' c #1E5265", "|' c #292B2B", "1' c #019ED5", "2' c #0295CF", "3' c #3B9AC3", "4' c #BECFD6", "5' c #DFDFE0", "6' c #DFDFDF", "7' c #BCCDD4", "8' c #4198BF", "9' c #0493CC", "0' c #0296D1", "a' c #3BA5D3", "b' c #B7D2DD", "c' c #DADBDB", "d' c #7C8A93", "e' c #0B5678", "f' c #0090C6", "g' c #009AD3", "h' c #0497CB", "i' c #136D8D", "j' c #019DD4", "k' c #128FC4", "l' c #84B4C7", "m' c #D8DBDC", "n' c #DCDEDE", "o' c #A3C2CF", "p' c #2D95C3", "q' c #0394CF", "r' c #0098D2", "s' c #0096D0", "t' c #1E9BD1", "u' c #96C5DA", "v' c #DADDDF", "w' c #C0C4C6", "x' c #3A5B6C", "y' c #026D99", "z' c #0496CA", "A' c #1E5165", "B' c #2F2824", "C' c #2C3133", "D' c #2D2A2A", "E' c #136D8C", "F' c #0097D0", "G' c #0292CC", "H' c #3D96BC", "I' c #B9CAD1", "J' c #DEDFDE", "K' c #D7DBDC", "L' c #90BBCE", "M' c #2695C7", "N' c #0294CF", "O' c #1196D0", "P' c #74B8D6", "Q' c #D1DADD", "R' c #D8D9D9", "S' c #88949A", "T' c #104D6A", "U' c #0088BB", "V' c #0099D1", "W' c #1E5164", "X' c #2F2724", "Y' c #2C2C2B", "Z' c #2C2A29", "`' c #136C8C", " ) c #019CD3", ".) c #0097CF", "+) c #108BC0", "@) c #78AAC0", "#) c #D4D8D9", "$) c #DDDDDE", "%) c #D3D8DB", "&) c #8DBACE", "*) c #2998C9", "=) c #0394CE", "-) c #0195CF", ";) c #1196CF", ">) c #63B1D4", ",) c #C7D6DC", "') c #DDDDDD", ")) c #BCC0C2", "!) c #425C6C", "~) c #02658D", "{) c #0095CC", "]) c #0495C9", "^) c #2E2724", "/) c #2B3132", "() c #2C2B2B", "_) c #136C8B", ":) c #019CD2", "<) c #0292CB", "[) c #2D8DB7", "}) c #A9C0C9", "|) c #DCDCDC", "1) c #D4D9DA", "2) c #9AC1D2", "3) c #399DCB", "4) c #0893CC", "5) c #0096CF", "6) c #0294CE", "7) c #1A98CF", "8) c #6CB3D4", "9) c #C4D4DB", "0) c #D2D3D4", "a) c #78848C", "b) c #114A67", "c) c #0084B6", "d) c #0495C8", "e) c #1E5163", "f) c #29292B", "g) c #2B3133", "h) c #136B8A", "i) c #019BD1", "j) c #088BC1", "k) c #5798B4", "l) c #C7CFD2", "m) c #D6DADB", "n) c #ABC7D5", "o) c #55A7CD", "p) c #1695CC", "q) c #0294CD", "r) c #0195CE", "s) c #0993CE", "t) c #2F9ED0", "u) c #80BBD5", "v) c #C8D5DB", "w) c #DBDCDC", "x) c #DADADB", "y) c #A7ADB1", "z) c #2E4D60", "A) c #026690", "B) c #0094CB", "C) c #0494C7", "D) c #1E5063", "E) c #2C3234", "F) c #2D2927", "G) c #146884", "H) c #019BD0", "I) c #0094CC", "J) c #1484B5", "K) c #81A8BA", "L) c #D4D6D7", "M) c #D9DADB", "N) c #C3D1D8", "O) c #83B9D2", "P) c #349ECD", "Q) c #0D94CC", "R) c #0293CD", "S) c #0096CD", "T) c #0493CD", "U) c #1C98CE", "V) c #56ABD2", "W) c #A4C7D7", "X) c #D2D8DB", "Y) c #DBDBDB", "Z) c #C7C9CA", "`) c #586B76", " ! c #084E6F", ".! c #0087BB", "+! c #0493C7", "@! c #1E4F61", "#! c #2D2724", "$! c #262626", "%! c #2E3437", "&! c #2D2725", "*! c #176179", "=! c #029ACD", "-! c #0095CD", ";! c #018FC7", ">! c #2A83AB", ",! c #A3B8C1", "'! c #D9DADA", ")! c #B2CCD6", "!! c #77B7D2", "~! c #3FA3CE", "{! c #1998CD", "]! c #0992CC", "^! c #0392CC", "/! c #0093CC", "(! c #0093CD", "_! c #0492CC", ":! c #0D93CC", "~ c #354347", ",~ c #232424", "'~ c #2C2826", ")~ c #22424E", "!~ c #058DBF", "~~ c #0A7DAE", "{~ c #5788A0", "]~ c #B9C1C5", "^~ c #D6D7D7", "/~ c #D8D8D9", "(~ c #B0B3B5", "_~ c #40525F", ":~ c #054C6B", "<~ c #0082B3", "[~ c #0198CC", "}~ c #0E7A9C", "|~ c #292F31", "1~ c #2C2928", "2~ c #2A2929", "3~ c #252626", "4~ c #37454A", "5~ c #283236", "6~ c #0B82A9", "7~ c #0096CC", "8~ c #0093CA", "9~ c #0D7BA9", "0~ c #518299", "a~ c #B1BBBF", "b~ c #D5D5D5", "c~ c #D8D7D7", "d~ c #D4D4D5", "e~ c #ADB0B2", "f~ c #445461", "g~ c #074865", "h~ c #007DAC", "i~ c #0093C9", "j~ c #0196CB", "k~ c #136A87", "l~ c #2B2A2A", "m~ c #2B2A29", "n~ c #2A2F30", "o~ c #262A2C", "p~ c #116E8E", "q~ c #0092C9", "r~ c #0090C7", "s~ c #0B78A7", "t~ c #4A7C95", "u~ c #A6B2B7", "v~ c #CFD0D1", "w~ c #D0D1D1", "x~ c #A2A6AA", "y~ c #40515E", "z~ c #074664", "A~ c #007AA9", "B~ c #0491C2", "C~ c #1D4E5F", "D~ c #2C2725", "E~ c #2A3436", "F~ c #282F32", "G~ c #1B5165", "H~ c #0394C5", "I~ c #077BAA", "J~ c #33718E", "K~ c #8A9CA6", "L~ c #C8C9CB", "M~ c #C9C9CA", "N~ c #8D9297", "O~ c #304856", "P~ c #054A68", "Q~ c #007BAA", "R~ c #0091C7", "S~ c #0983AD", "T~ c #25393F", "U~ c #2D3336", "V~ c #2C2726", "W~ c #0884AF", "X~ c #0094CA", "Y~ c #037EB0", "Z~ c #20698C", "`~ c #6A8594", " { c #B2B9BC", ".{ c #D1D1D1", "+{ c #B6B8BA", "@{ c #70767E", "#{ c #223E4F", "${ c #025172", "%{ c #007FAF", "&{ c #0195C9", "*{ c #126988", "={ c #2B2929", "-{ c #222425", ";{ c #3C494F", ">{ c #3A4A51", ",{ c #222525", "'{ c #2B2928", "){ c #136885", "!{ c #0185B9", "~{ c #0E6A92", "{{ c #426D83", "]{ c #95A0A7", "^{ c #C5C6C7", "/{ c #D3D3D3", "({ c #D4D4D4", "_{ c #C7C8C9", ":{ c #989C9F", "<{ c #475561", "[{ c #113A51", "}{ c #015D82", "|{ c #0084B5", "1{ c #048EBE", "2{ c #1E4B5B", "3{ c #2C2625", "4{ c #282D2E", "5{ c #2B2625", "6{ c #204552", "7{ c #058CBA", "8{ c #0474A2", "9{ c #1E6080", "0{ c #5C7887", "a{ c #9FA6AB", "b{ c #C7C8C8", "c{ c #D2D2D2", "d{ c #CACBCB", "e{ c #A6A9AB", "f{ c #646C75", "g{ c #044865", "h{ c #006F9A", "i{ c #0089BE", "j{ c #0D789D", "k{ c #2B2827", "l{ c #242323", "m{ c #313D42", "n{ c #2E3B3E", "o{ c #232323", "p{ c #2A2928", "q{ c #0F7090", "r{ c #0193C6", "s{ c #008FC6", "t{ c #0084B7", "u{ c #066993", "v{ c #215B77", "w{ c #597381", "x{ c #919BA1", "y{ c #B7B9BB", "z{ c #D0D0D0", "A{ c #CDCECE", "B{ c #C1C2C3", "C{ c #9FA2A6", "D{ c #676F77", "E{ c #284050", "F{ c #083F59", "G{ c #006187", "H{ c #0081B1", "I{ c #008EC3", "J{ c #048FBE", "K{ c #1B5163", "L{ c #2B2725", "M{ c #2A2A29", "N{ c #232627", "O{ c #374B52", "P{ c #242729", "Q{ c #2B2624", "R{ c #1F4451", "S{ c #0689B4", "T{ c #0091C6", "U{ c #008FC4", "V{ c #0081B2", "W{ c #066892", "X{ c #185877", "Y{ c #3D6174", "Z{ c #687B86", "`{ c #969DA1", " ] c #B3B6B8", ".] c #C5C6C6", "+] c #CECECE", "@] c #CBCBCB", "#] c #BDBEBF", "$] c #7D848A", "%] c #4E5D67", "&] c #233E50", "*] c #08415D", "=] c #015F84", "-] c #007CAB", ";] c #008CC0", ">] c #008EC4", ",] c #0E7394", "'] c #282E31", ")] c #2A2827", "!] c #252525", "~] c #2B3337", "{] c #2A2828", "]] c #14637C", "^] c #0291C2", "/] c #008DC2", "(] c #0083B5", "_] c #02719D", ":] c #095B80", "<] c #18526E", "[] c #33586B", "}] c #5A707C", "|] c #78888F", "1] c #939CA1", "2] c #A7ABAE", "3] c #B3B5B7", "4] c #C0C1C2", "5] c #C8C9CA", "6] c #C9CACB", "7] c #C9CBCB", "8] c #C6C7C8", "9] c #BFC0C1", "0] c #A1A6A9", "a] c #899296", "b] c #6B787F", "c] c #475864", "d] c #224253", "e] c #0F3D55", "f] c #034C6B", "g] c #00668E", "h] c #007EAD", "i] c #008BBF", "j] c #058AB7", "k] c #1F4654", "l] c #323D40", "m] c #2A2725", "n] c #24373E", "o] c #0A7CA1", "p] c #0091C4", "q] c #008EC2", "r] c #008ABD", "s] c #0080B1", "t] c #016F9B", "u] c #036186", "v] c #095475", "w] c #194F69", "x] c #275065", "y] c #305465", "z] c #41606E", "A] c #526A76", "B] c #5D707B", "C] c #63737C", "D] c #65747D", "E] c #66747D", "F] c #65737C", "G] c #63727A", "H] c #5E6E77", "I] c #526771", "J] c #3E5966", "K] c #2F4B5B", "L] c #234557", "M] c #124158", "N] c #054764", "O] c #025475", "P] c #00668D", "Q] c #0078A6", "R] c #0085B7", "S] c #0191C3", "T] c #13637E", "U] c #242425", "V] c #252A2C", "W] c #2B3437", "X] c #222323", "Y] c #292928", "Z] c #2A2625", "`] c #1C4A59", " ^ c #0588B2", ".^ c #008FC3", "+^ c #008DC1", "@^ c #008ABE", "#^ c #007CAC", "$^ c #0174A1", "%^ c #016B95", "&^ c #026189", "*^ c #05597E", "=^ c #085376", "-^ c #095070", ";^ c #094D6D", ">^ c #0A4C6A", ",^ c #094B6A", "'^ c #094C6C", ")^ c #055274", "!^ c #025A7E", "~^ c #016C96", "{^ c #0076A3", "]^ c #0080B0", "^^ c #0087BA", "/^ c #008DC3", "(^ c #0191C4", "_^ c #0C789C", ":^ c #253339", "<^ c #212425", "[^ c #303E41", "}^ c #292827", "|^ c #292828", "1^ c #17576D", "2^ c #048CBA", "3^ c #0089BD", "4^ c #0088BA", "5^ c #0086B8", "6^ c #0085B8", "7^ c #008FC2", "8^ c #0884AC", "9^ c #20414C", "0^ c #2A2523", "a^ c #2A3439", "b^ c #2F3A41", "c^ c #212324", "d^ c #2A2726", "e^ c #282B2C", "f^ c #156179", "g^ c #038EBC", "h^ c #0688B3", "i^ c #1B4E5E", "j^ c #2A2624", "k^ c #232729", "l^ c #3A4F57", "m^ c #222222", "n^ c #272C2E", "o^ c #13657F", "p^ c #028EBF", "q^ c #008EC1", "r^ c #0589B5", "s^ c #195367", "t^ c #202223", "u^ c #2B383B", "v^ c #1F2123", "w^ c #242424", "x^ c #292524", "y^ c #272D2F", "z^ c #126580", "A^ c #038DBD", "B^ c #008BBE", "C^ c #008EC0", "D^ c #0684AD", "E^ c #195164", "F^ c #292727", "G^ c #292626", "H^ c #212222", "I^ c #2B393C", "J^ c #212527", "K^ c #252524", "L^ c #292625", "M^ c #272B2B", "N^ c #165B72", "O^ c #008DBF", "P^ c #018EBE", "Q^ c #0A7B9F", "R^ c #1D4552", "S^ c #2A2524", "T^ c #292726", "U^ c #212121", "V^ c #222728", "W^ c #314348", "X^ c #1C4A57", "Y^ c #0A7A9E", "Z^ c #018DBD", "`^ c #008CBF", " / c #038AB7", "./ c #106A87", "+/ c #22393F", "@/ c #272F33", "#/ c #303E43", "$/ c #202526", "%/ c #282726", "&/ c #292423", "*/ c #22373E", "=/ c #12637E", "-/ c #0584AF", ";/ c #018CBD", ">/ c #0089BC", ",/ c #028BBC", "'/ c #097CA1", ")/ c #195265", "!/ c #262C2D", "~/ c #292523", "{/ c #282827", "]/ c #1E2223", "^/ c #324148", "// c #212729", "(/ c #272829", "_/ c #1E424D", ":/ c #0F6A88", "( c #202425", ",( c #2B353B", "'( c #304149", ")( c #252E31", "!( c #2C383D", "~( c #35474D", "{( c #2B363B", "]( c #232728", "^( c #1F2222", "/( c #252C2F", "(( c #293439", "_( c #334147", ":( c #2C393C", "<( c #232B2D", "[( c #202020", "}( c #222729", "|( c #282F33", "1( c #303E44", "2( c #374A52", "3( c #3B4F59", "4( c #2F3D44", "5( c #2C363A", "6( c #272D30", "7( c #232628", "8( c #232829", "9( c #242829", "0( c #242728", "a( c #2A3133", "b( c #2D393D", "c( c #2E3A40", "d( c #344248", "e( c #3E5861", "f( c #455D66", "g( c #465F69", "h( c #485F67", "i( c #485F69", "j( c #495F69", "k( c #465F67", "l( c #465F68", " ", " ", " ", " ", " . + @ # $ % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % & * = - ", " ; > , ' ) ! ~ { ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ^ / ( _ : < [ } | ", " 1 2 3 4 5 6 7 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 0 / 4 a b c d ", " e f g h 7 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 0 ] i j k ", " l m 5 n 9 9 9 9 o p q r s t t u t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t u t u v w p x 9 9 9 9 x h y z A ", " B ' 5 7 9 9 9 C p D E F G H I J K L M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M N O P Q R S T T F x 9 9 9 9 U V b W ", " X Y 7 9 9 9 x w v x Z ` ...+.@.#.$.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.&.#.*.=.-.;.>.,.'.E ).9 9 9 C !.) ~. ", " {.].{ 9 9 9 x F v ^./.(._.:.<.[.}.|.1.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.1.3.4.[.5.6.7.8.9.9 E q 9 9 9 8 0.a. ", " b._ 0 9 9 9 p v c.d.e.f.g.h.i.2.j.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.k.}.l.m.n.o.x D x 9 9 9 / p.q. ", " r.s.7 9 9 o r C K t.u.v.w.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.1.w.x.y.z.A.T ).9 9 9 0 B.C. ", " D.E.8 9 9 x F.G.H.I.J.i.K.L.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.L.L.w.l.M.N.O.r o 9 9 0 : P. ", " Q.R.8 9 o x v S.T.U.w.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.v.V.W.X.T 9 C 9 0 i Y. ", " Z.s.`.9 9 x E +.+++@+L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.w.#+$+%+&+9 9 9 h *+ ", " m 7 9 9 p F.>.=+-+L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.;+>+,+'+F.9 9 9 5 )+ ", " !+] 9 9 x E ~+{+-+K.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.++]+^+/+9 9 8 Y (+ ", " _+0.9 9 o F.:+<+[+L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.K.L.++}+|+F 9 9 7 1+2+ ", " 3+7 9 C T 4+5+J.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.K.K.K.6+7+8+9+0+0+0+9+9+8+a+K.K.K.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.@+b+c+C q 9 9 6 d+ ", " !+/ 9 9 e+f+g+l.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.K.h+i+j+k+l+m+n+o+p+q+r+q+s+t+u+v+w+x+y+z+h+h+K.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.A+w.u.B+T x 9 x C+D+ ", " s.`.8 E+v F+G+;+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+H+7+I+J+K+L+M+N+O+P+Q+R+S+T+U+U+S+V+W+X+Y+Z+`+ @.@+@@@#@$@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+%@&@*@T 8 8 U =@ ", " i / 8 8 -@;@>@,@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+'@)@!@~@{@]@^@/@(@_@:@<@[@[@[@[@[@[@[@[@[@[@}@|@1@2@3@4@5@6@7@8@9@H+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+0@a@b@b@8 7 c@m ", " d@7 8 e@f@g@h@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+'@i@j@k@l@m@n@o@p@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@}@q@r@s@t@u@v@w@H+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+,@x@y@z@8 8 6 A@ ", " B@0.8 8 f@;@C@D@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+E@F@G@H@I@/@1@<@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@}@J@K@L@M@N@E@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+O@P@`.b@8 8 Q@R@ ", " S@U 8 `.T@U@V@A+W@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+H+#@X@Y@Z@`@ #[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@.#+#@###$#%#W@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+W@;+&#*#=#8 8 5 -# ", " ;#g 7 8 >#,#'#,@)#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@!#~#{#]#^#/#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#(#_#:#<#[#}#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@H+|#1#-@`.8 0 2# ", " 3#/ 8 8 4#5#6#A+W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@}#7#8#9#0#a#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#b#c#d#e#}#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@,@f#g#-@8 7 C+h# ", " i#/ 8 `.f@j#k#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@}#l#m#n#`@(#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#o#p#q#r#!#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@L.s#t#T@8 8 5 u# ", " v#0 8 f@w#x#y#z#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@}#A#B#C#D#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#(#E#F#G#'@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@A+H#I#J#e@8 5 B@ ", " K#V 8 8 =#L#M#@+)#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@}#l#N#O#P#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#Q#R#S#T#}#)#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@)#k#U#V#W#8 h X# ", " Y#y 8 8 Z#`# $A+)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#.$+$@$#$Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#$$%$&$*$=$-$;$>$,$'$)$!$~$Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#{$]$^$/$)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#y#($_$:$8 7 R. ", " <$5 8 8 [$}$|$W@)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#1$2$3$b#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#4$5$6$7$8$9$0$a$b$c$d$e$f$g$h$i$j$$$Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#k$l$m$n$)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#@+o$p$=#8 7 3+ ", " q$] 8 8 r$s$t$)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#z#u$v$+#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#w$x$y$z$A$B$C$D$E$F$G$H$I$J$K$L$M$N$O$P$Q$$$Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#R$S$T$U$)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#K.V$W$Z#8 8 X$Y$ ", " Z$h 8 8 `$ %.%)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#z#+%@%#%Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#%$$%%%&%*%=%-%;%>%,%'%)%!%!%!%~%{%]%^%/%(%_%:%<%%$Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#[%}%|%z#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#A+1%2%3%8 8 y 4% ", " 5%h 8 E+f@6%.%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%7%8%9%~$$$$$$$$$$$$$$$$$$$$$$$$$$$~$0%a%b%c%d%e%f%{%)#W@)#)#)#)#)#)#)#z#W@W@!%g%h%i%j%k%l%$$$$$$$$$$$$$$$$$$$$$$$$$$%$m%n%o%)#!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%)#p%q%r$8 8 4 r% ", " ^.0 8 `.>#s%k#!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%t%u%v%w%$$$$$$$$$$$$$$$$$$$$$$$$$$%$x%y%z%A%e%B%!%)#)#!%!%~%!%!%!%!%!%!%!%U$!%!%)#W@C%D%E%F%G%$$$$$$$$$$$$$$$$$$$$$$$$$$H%I%J%K%U$!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%)#p%L%r$8 8 5 <$ ", " M%0 8 `.W#N%k#!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%U$O%P%Q%$$$$$$$$$$$$$$$$$$$$$$$$$$R%S%T%U%V%W%~%)#!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%)#!%X%Y%Z%`%$$$$$$$$$$$$$$$$$$$$$$$$%$ &.&+&z#!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%)#p%L%r$8 8 5 @& ", " M%0 8 `.#&s%$&!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%U$t%%&&&k$$$$$$$$$$$$$$$$$$$$$$$$$*&=&-&;&>&,&z#!%~%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%~%!%z#'&)&!&4$$$$$$$$$$$$$$$$$$$$$$$$$~&{&]&z#~%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%)#p%L%^&8 8 5 @& ", " /&0 8 `.#&s%$&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%(&_&:&<&%$%$%$%$%$%$%$%$%$%$%$%$*&[&}&|&1&g%!%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%z#2&3&4&*&%$%$%$%$%$%$%$%$%$%$%$5&6&7&8&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%!%9&0&^&8 8 5 a& ", " /&0 8 `.#&s%$&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%b&c&d&e&%$%$%$%$%$%$%$%$%$%$%$%$f&g&h&i&g%z#~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%(&j&k&l&%$%$%$%$%$%$%$%$%$%$%$%$m&n&o&!%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%!%9&0&^&8 8 5 p& ", " /&h 7 7 J#q&r&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%s&t&u&R%R%R%R%R%R%R%R%R%R%R%R%v&w&x&y&B%!%(&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%z&U$A&B&C&5&R%R%R%R%R%R%R%R%R%R%R%v&D&E&(&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%!%9&F&G&7 7 4 p& ", " M%h 7 n J#q&r&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%(&H&I&J&R%R%R%R%R%R%R%R%R%R%R%R%K&L&M&N&!%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%z&~%O&P&Q&R%R%R%R%R%R%R%R%R%R%R%R%R&S&T&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%!%U&F&G&7 7 4 p& ", " /&h 7 n J#V&r&(&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%W&X&Y&!$R%R%R%R%R%R%R%R%R%R%R%Z&`& *.*z&~%z&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%z&~%+*@*#*!$R%R%R%R%R%R%R%R%R%R%R%$*%*&*U$z&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%z&~%**F&G&7 7 4 p& ", " =*h 7 n J#V&-*z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&;*>*,*!$!$!$!$!$!$!$!$!$!$!$!$'*)*!*~*~%z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&{*]*^*/*!$!$!$!$!$!$!$!$!$!$!$(*_*:*~%z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&~%**F&G&7 7 4 p& ", " /&h 7 n J#<*-*z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&{*[*}*|*!$!$!$!$!$!$!$!$!$!$!$v&1*2*3*~%z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&4*5*6*!$!$!$!$!$!$!$!$v&7*Z&8*9*0*a*z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&~%**F&G&7 7 4 p& ", " f+h 7 n J#b*c*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*e*f*g*h*v&v&v&v&v&v&v&v&v&v&v&i*j*k*l*z&d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*m*n*o*p*v&v&v&q**$i*x%r*s*t*u*v*w*C%{*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*z&x*F&G&7 7 4 p& ", " y*h 7 n J#b*z*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*A*B*C*v&v&v&v&v&v&v&v&v&v&v&v&D*E*F*{*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*G*H*I*J*K*L*M*N*O*P*Q*R*S*T*U*V*W*X*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*z&x*F&G&7 7 4 p& ", " y*h 7 n J#Y*z*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*{%Z*`* =v&v&v&v&v&v&v&v&v&v&v&.=+=@=#=z&d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*a*$=%=&=*===-=;=>=,='=)=!=~={=N&]=a*a*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*z&x*^=G&7 7 4 p& ", " y*h 7 n /=Y*(=a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*m*_=:=<=7*7*7*7*7*7*7*7*7*7*7*[=}=|=~*d*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*d*1=2=3=4=5=6=7=8=,%C%a*d*z&{*G*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*d*9=0=G&7 7 4 p& ", " y*h 7 n /=Y*(=a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a=b=c=d=7*7*7*7*7*7*7*7*7*7*d=e=f=g=a*'%'%a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*'%'%h=i=~*X*{%d*d*d*d*{%{%a*a*'%'%a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*'%d*9=0=G&7 7 Y X# ", " y*h 7 n /=j=k='%'%'%'%'%'%'%'%'%'%'%'%'%'%'%l=m=n=d=d=d=d=d=d=d=d=d=d=d=o=p=q=r=d*'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%{%{%a*'%'%X*'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%{%s=t=G&7 7 Y X# ", " y*!.7 n /=j=k='%'%'%'%'%'%'%'%'%'%'%'%'%'%'%A&u=v=d=d=d=d=d=d=d=d=d=d=d=w=x=y=#=d*z='%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%{%s=t=G&7 7 Y X# ", " y*] 0 U A=j=B=X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*C=D=E=o=o=o=o=o=o=o=o=o=o=o=F=G=H=I='%X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*'%J=t=K=0 0 Y X# ", " y*] 0 U A=j=B=X*X*X*X*X*X*X*X*X*X*X*X*X*X*T&L=M=G%o=o=o=o=o=o=o=o=o=o=o=N=O=P=g%X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*'%J=t=K=0 0 Y X# ", " w#] 0 U Q=R=B=X*X*X*X*X*X*X*X*X*X*X*X*X*X*C%S=T=U=o=o=o=o=o=o=o=o=o=o=o=V=W=X='%X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*'%J=Y=K=0 0 Y X# ", " Z=] 0 U Q=R=`=C%C%C%C%C%C%C%C%C%C%C%C%C%C% -.-+-@-q*q*q*q*q*q*q*q*q*q*q*#-$-%-'%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%X*&-*-K=0 0 Y X# ", " Z=] 0 U Q==-`=C%C%C%C%C%C%C%C%C%C%C%C%C%C%--;->-,-q*q*q*q*q*q*q*q*q*q*q*'-)-!-'%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%X*&-*-K=0 0 Y X# ", " Z=] 0 U Q==-~-g%g%g%g%g%g%g%g%g%g%g%g%g%g%]={-]-8*8*8*8*8*8*8*8*8*8*8*8*^-/-h%X*g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%h=(-*-K=0 0 Y X# ", " _-] 0 U :-<-~---g%g%g%g%g%g%g%g%g%g%g%g%g%[-}-|-8*8*8*8*8*8*8*8*8*8*8*1-2-3-4-h=~*g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%~*h=5-6-K=0 0 Y X# ", " _-] 0 U :-<-7-~*~*~*~*~*~*~*~*~*~*~*~*~*~*8-9-0-a-a-a-a-a-a-a-a-a-a-a-b-c-d-e-C%~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*g%f-6-K=0 0 Y g- ", " 5%] 0 U :-<-7-]=~*~*~*~*~*~*~*~*~*~*~*~*~*h-i-j-a-a-a-a-a-a-a-a-a-a-a-F=k-l-m-g%i=~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*g%n-6-K=0 0 Y g- ", " 5%] 0 U :-o-p-i=i=i=i=i=i=i=i=i=i=i=i=i=i=q-r-s-F=F=F=F=F=F=F=F=F=F=F=t-u-v-w---i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=~*x-6-K=0 0 y-z- ", " 5%/ 0 U :-o-p-i=i=i=i=i=i=i=i=i=i=i=i=i=i=A-B-s-F=F=F=F=F=F=F=F=F=F=F=t-C-D-w---B%i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=~*x-E-K=F-h y z- ", " 5%/ h !.:-o-G-B%B%B%B%B%B%B%B%B%B%B%B%B%B%A-H-I-t-t-t-t-t-t-t-t-t-t-t-J-K-L-w-~*B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%M-x-E-N-h h y 4 ", " O-/ h !.P-Q-R-,%,%,%,%,%,%,%,%,%,%,%,%,%,%S-T-U-J-J-J-J-J-J-J-J-J-J-J-V-W-X-Y-i=,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%B%Z-`-N-h h C+4 ", " O-/ h !. ;Q-.;,%,%,%,%,%,%,%,%,%,%,%,%,%,%+;@;#;J-J-J-J-J-J-J-J-J-J-J-V-$;%;&;i=,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%B%*;`-N-h h C+4 ", " O-/ h !.=;Q-.;]%I=I=I=I=I=I=I=I=I=I=I=I=I=-;;;>;,;,;,;,;,;,;,;,;,;,;,;,;';);!;i=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=B%*;`-~;h h C+4 ", " {;/ h !.=;];^;I=I=I=I=I=I=I=I=I=I=I=I=I=I=/;(;_;,;,;,;,;,;,;,;,;,;,;,;,;:;<;[;l*I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=,%};`-|;h h Q@4 ", " 1;/ h !.=;];2;3;3;3;3;3;3;3;3;3;3;3;3;3;3;4;5;6;7;8;8;8;8;8;8;8;8;8;8;8;9;0;a;,%3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;,&b;c;|;h h Q@4 ", " 1;/ h !.=;];2;3;3;3;3;3;3;3;3;3;3;3;3;3;3;4;d;e;f;8;8;8;8;8;8;8;8;8;8;8;g;h;i;,%3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;,&b;c;|;h h Q@4 ", " 1;/ h !.j;k;l;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;n;o;p;q;q;q;q;q;q;q;q;q;q;q;r;s;t;I=m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;3;u;c;|;h h Q@4 ", " 1;/ h !.j;k;l;m;m;m;m;m;m;m;m;m;m;m;m;m;m;v;w;x;y;q;q;q;q;q;q;q;q;q;q;q;z;A;B;4;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;C;v;v;v;v;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;3;u;c;|;h h c@4 ", " 1;5 ] { j;D;E;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;F;G;H;I;I;I;I;I;I;I;I;I;I;I;J;K;L;M;v;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;N;O;P;Q;R;S;T;U;V;W;W;X;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;m;Y;Z;`;] ] c@4 ", " 1;5 ] { j;D;E;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C; >.>+>I;I;I;I;I;I;I;I;I;I;I;@>#>$>%>v;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;&>*>=>->;>>>,>'>)>!>~>{>]>^>/>(>C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;m;_>Z;`;] ] c@4 ", " :>5 ] { <>[>}>C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;|>1>2>3>3>3>3>3>3>3>3>3>3>3>3>4>5>6>m;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;X;7>8>9>0>a>b>c>d>e>f>g>h>i>j>k>l>f%C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;m;m>Z;`;] ] c@4 ", " n>5 ] { <>o>p>X;C;C;C;C;C;C;C;C;C;C;C;C;C;f%q>r>s>t>3>3>3>3>3>3>3>3>3>3>t>u>v>w>v;f%C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;f%x>y>z>3>3>3>3>3>3>3>3>A>B>C>D>E>Y-C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;f%C;F>G>`;] ] c@4 ", " H>5 ] { <>o>p>f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%|>I>J>K>t>t>t>t>t>t>t>t>t>t>t>L>M>N>C;f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%O>P>Q>t>t>t>t>t>t>t>t>t>t>t>R>S>T>U>C;f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%C;V>G>W>] ] V 4 ", " H>5 ] { <>o>X>M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;Y>Z>`>R>R>R>R>R>R>R>R>R>R>R> ,.,+,@,M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;&*#,$,R>R>R>R>R>R>R>R>R>R>R>%,&,*,=,f%M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;f%-,;,W>] ] V >, ", " H>5 ] { <>,,',),),),),),),),),),),),),),),),),!,~,{,],],],],],],],],],],],],^,/,(,_,),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),:,<,[,],],],],],],],],],],],},|,1,W%M;),),),),),),),),),),),),),),),),_,2,;,W>] ] d@>, ", " 3,0.] { <>,,',),),),),),),),),),),),),),),),N&4,5,6,7,],],],],],],],],],],],8,9,0,8=),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),4,a,b,c,],],],],],],],],],],],d,e,f,N&N&),),),),),),),),),),),),),),),),),g,h,i,j,/ E.>, ", " 3,0./ / <>k,l,N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&4,m,n,L>7,7,7,7,7,7,7,7,7,7,7,7,o,p,q,N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&r,s,t,z;7,7,7,7,7,7,7,7,7,7,7,u,v,w,),N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&),g,h,x,/ / E.>, ", " y,0./ z,A,k,B,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,4,D,E,F,G,G,G,G,G,G,G,G,G,G,G,G,H,I,J,8=C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,#=K,L,M,G,G,G,G,G,G,G,G,G,G,G,N,O,P,Q,8=C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,N&R,S,x,/ / E.>, ", " y,0./ z,T,U,B,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,V,W,X,Y,G,G,G,G,G,G,G,G,G,G,G,Y,Z,`, 'C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,.'+'s>@'G,G,G,G,G,G,G,G,G,G,G,#'$'%'&'C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,N&R,*'x,/ / E.>, ", " ='0./ z,-';'>','W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%,''')'!'~'~'~'~'~'~'~'~'~'~'~'~'{']'^',','W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%/'('_'~'~'~'~'~'~'~'~'~'~'~'~':'<'!=C,,'W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%C,['}'x,/ / E.|' ", " ='0./ z,-';'1'W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%2'3'4'5'~'~'~'~'~'~'~'~'~'~'~'6'7'8'9'W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%0'a'b'8,~'~'~'~'~'~'~'~'~'~'~'c'd'e'f','W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%g'h'}'x,/ / E.|' ", " ='0./ z,-'i'j'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'m-k'l'm'6'6'6'6'6'6'6'6'6'6'6'6'n'o'p'q'r'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'s't'u'v'6'6'6'6'6'6'6'6'6'6'6'N,w'x'y'&'r'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'W%z'A'B'/ / E.|' ", " C'y / z,D'E'j'm-&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'F'&'G'H'I'N,6'6'6'6'6'6'6'6'6'6'6'J'K'L'M'N'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'s'O'P'Q'N,6'6'6'6'6'6'6'6'6'6'6'R'S'T'U'W%&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'V'z'W'X'~ 5 3+|' ", " C'y 5 Y'Z'`' )F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'.)F'+)@)#)$)N,N,N,N,N,N,N,N,N,N,N,N,%)&)*)=).).)F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'-);)>),)')N,N,N,N,N,N,N,N,N,N,N,},))!)~){)&'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'&'])W'^)5 5 s.|' ", " /)y 5 ()Z'_):).).).).).).).).).).).).).).).).).).).).)<)[)})|)')')')')')')')')')')')')')1)2)3)4)5).).).).).).).).).).).).).).).).).).).).)5)6)7)8)9)},')')')')')')')')')')')')0)a)b)c)F'.).).).).).).).).).).).).).).).).).).).)>%d)e)^)5 5 s.f) ", " g)y 5 ()Z'h)i)^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%5)j)k)l)|)|)|)|)|)|)|)|)|)|)|)|)|)|)m)n)o)p)q)^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%r)s)t)u)v)w)|)|)|)|)|)|)|)|)|)|)|)|)x)y)z)A)B).)^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%.)C)D)^)5 5 ) |' ", " E)c@5 5 F)G)H)^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%I)J)K)L)|)|)|)|)|)|)|)|)|)|)|)|)|)|)M)N)O)P)Q)R)=,^%^%^%^%^%^%^%^%^%^%^%^%S)=,T)U)V)W)X)w)|)|)|)|)|)|)|)|)|)|)|)|)Y)Z)`) !.!.)S)^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%.)+!@!#!5 5 $!p& ", " %!c@5 5 &!*!=!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!S);!>!,!'!Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)u>)!!!~!{!]!^!/!/!(!(!(!(!(!/!/!_!:!~,~0.0.'~)~!~-!B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B){=~~{~]~^~e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!R'/~/~/~/~/~R'e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!^~(~_~:~<~B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)[~}~|~1~0.2~3~ ", " 4~7!y 0.1~5~6~7~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~U>9~0~a~b~c~O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!d~e~f~g~h~i~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~j~k~l~m~0.s.n~ ", " o~E.0.m~0.p~7~q~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~q~8~r~s~t~u~v~z!+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~z!w~x~y~z~A~U>8~q~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~B~C~D~0.0.$!E~ ", " F~$!0.0.D~G~H~i~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~f'I~J~K~L~b~z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!b~M~N~O~P~Q~R~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~B)S~T~D~0.0.3~U~ ", " e 3~y 0.V~T~W~X~U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>q~r~Y~Z~`~ {.{b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~.{+{@{#{${%{R~Q,U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>&{*{v#={0.c@-{;{ ", " >{,{2~y y '{){&{R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~U>!{~{{{]{^{/{({({({({({({({({({({({({({({({({({({({({({({({({({({({({({({({({/{_{:{<{[{}{|{U>U>R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~q~1{2{3{y y R.X ", " 4{$!y y 5{6{7{Q,f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'U>6=8{9{0{a{b{c{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{d{e{f{#{g{h{i{R~r~f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'i~j{~.k{y y l{m{ ", " n{o{y y p{S@q{r{3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*s{f't{u{v{w{x{y{d{z{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{.{A{B{C{D{E{F{G{H{I{f'3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*f'J{K{L{M{y E.N{O{ ", " P{s.y y Q{R{S{T{U{3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*U{U{3*D!I{V{W{X{Y{Z{`{ ].]+]w~.{.{c{c{c{c{c{c{c{c{c{c{c{.{.{z{@]#]e{$]%]&]*]=]-];]3*3*U{3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*U{>]r{,]'])]y y !]'] ", " ~],~c@y {]{]]]^]U{I{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{I{U{3*/](]_]:]<][]}]|]1]2]3]4]b{5]6]6]7]6]6]5]8]9](~0]a]b]c]d]e]f]g]h]i]3*U{I{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{I{D!j]k]Q{M{y c@Q.l] ", " a.s.y 2~m]n]o]p]/]I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{q]/]I{>]I{r]s]t]u]v]w]x]y]z]A]B]C]D]E]F]G]H]I]J]K]L]M]N]O]P]Q]R];]I{I{/]/]I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{/]I{S]T]Q@{]y C+U]V] ", " W]X]c@c@Y]Z]`] ^.^+^+^/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]+^+^/]I{I{/]@^t{#^$^%^&^*^=^-^;^>^,^'^ !)^!^C!~^{^]^^^.*/^I{/]+^+^/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]+^(^_^:^m]c@c@s.<^[^ ", " k !]c@c@}^|^1^2^q]6=+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^q]I{/]6=i]3^4^5^R]c)c)c)6^^^3^i]6=q]q]/]+^6=+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^7^8^9^0^c@c@E.X]a^ ", " b^c^3+c@c@d^e^f^g^+^;];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];]+^+^/]/]/]/]/]/]/]/]/]+^;];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];]7^h^i^j^|^c@c@!]k^l^ ", " a^m^c@c@c@Z]n^o^p^6=i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]q^r^s^Z]}^c@c@s.t^u^ ", " v^w^c@c@c@x^y^z^A^;]B^B^i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]B^B^C^D^E^F^G^V c@E.H^I^ ", " J^K^E.E.E.L^M^N^ ^O^r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]B^P^Q^R^S^T^E.E.E.U^V^ ", " W^J^U]E.E.E.L^R.X^Y^Z^B^3^3^r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]3^3^`^ /./+/x^R.E.E.E.m^@/ ", " #/$/U]E.E.E.%/&/*/=/-/;/r]>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/B^,/'/)/!/~/{/3+E.s.U^]/ ", " ^///o{s.E.E.R.x^(/_/:/-//U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'>/}/|/1/2/3/x^q$E.E.E.4/U^]/ ", " 5/6/7/4/E.E.E.8/&/*+9/0/a/b/c/d/>/U'^^^^4^U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'4^^^^^U'>/r]e/f/g/h/i/R.~/R.E.E.E.!]j/k/ ", " l/m/!]{/s.s.s.q$n/3+o/p/q/r/s/t/W*u/e/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/w/e/e/x/y/z/A/B/C/D/E/q$F/q$s.s.s.) o{G/H/ ", " I/J^H^$!s.s.s.s.q$n/n/*+J/K/L/M/N/O/P/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/R/S/T/C/U/V/W/X/3+Y/Z/4/s.s.s.s.w^`/ (.( ", " +(@(#(m^$(s.s.s.s.s.%(&(n/n/&(Z$s.*(2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#*(4/=(&(-(&(&(;(s.s.s.s.s.!]U^>(,( ", " '()(j/H^w^s.s.s.s.s.s.4/4/4/4/;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(4/4/4/4/4/s.s.s.s.s.$!o{7/j/!( ", " ~({(](^(m^w^!]s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.$!!]o{U^c^/((( ", " _(:(<(N{X][(7/m^w^!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]w^m^[(7/)+}(|(1(2( ", " 3(_(4(5(6(7(k^8(k^P{P{P{P{P{P{9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(P{P{P{P{P{P{k^P{a.0(a(b(c(d(e( ", " f(g(h(i(i(i(i(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(i(i(i(i(i(k(l( ", " ", " ", " "}; ================================================ FILE: src/resource/embedded/PNG_HELP.hpng ================================================ unsigned char PNG_HELP_png[492] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x08, 0x06, 0x00, 0x00, 0x00, 0xE0, 0x77, 0x3D, 0xF8, 0x00, 0x00, 0x00, 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x01, 0xA1, 0x49, 0x44, 0x41, 0x54, 0x48, 0x89, 0xDD, 0x95, 0xB1, 0x4E, 0x02, 0x41, 0x10, 0x86, 0xBF, 0x80, 0x9C, 0x85, 0xF2, 0x06, 0x68, 0x6B, 0x02, 0xBC, 0x83, 0x58, 0xD8, 0x1A, 0x24, 0x86, 0x52, 0x4B, 0x22, 0x31, 0x88, 0x2F, 0x61, 0x88, 0xD1, 0xA7, 0x10, 0x29, 0x0C, 0x89, 0x89, 0x89, 0x96, 0xB6, 0x16, 0xDA, 0x88, 0x46, 0x11, 0xF5, 0x09, 0x8C, 0x26, 0x82, 0x85, 0x34, 0x9C, 0xC5, 0xCE, 0x86, 0xCD, 0xB2, 0x77, 0x07, 0x94, 0xFC, 0xC9, 0xE4, 0x92, 0x7F, 0x66, 0xFE, 0xD9, 0x9B, 0x9B, 0xD9, 0x83, 0x59, 0x87, 0x07, 0x14, 0x81, 0x06, 0xD0, 0x06, 0x7E, 0xC5, 0xDA, 0xC0, 0x99, 0xF8, 0xBC, 0x69, 0xC5, 0x0B, 0xC0, 0x07, 0xE0, 0x47, 0xD8, 0x3B, 0xB0, 0x39, 0x89, 0x70, 0x0C, 0x38, 0x32, 0x04, 0x1E, 0x80, 0x0A, 0x90, 0x06, 0x16, 0xC4, 0x32, 0xC0, 0x3E, 0xD0, 0x32, 0xE2, 0x6A, 0x92, 0x1B, 0x09, 0x2D, 0xFE, 0x07, 0x94, 0x22, 0x92, 0x62, 0xC0, 0xAE, 0xC4, 0xEA, 0x22, 0xA1, 0x28, 0x18, 0xE2, 0xAB, 0x06, 0xBF, 0x04, 0x34, 0x81, 0xAE, 0xD8, 0x05, 0xB0, 0x62, 0xF8, 0x73, 0x46, 0x91, 0x7C, 0x90, 0xB8, 0xC7, 0xB0, 0xE7, 0x25, 0x4B, 0xFC, 0x8B, 0xD1, 0xDE, 0x7F, 0x8B, 0x4F, 0xA3, 0x2C, 0xFC, 0x1B, 0x90, 0x70, 0x15, 0x28, 0x32, 0xEC, 0xB9, 0xD9, 0x96, 0xA6, 0xF0, 0x57, 0x40, 0x4A, 0xEC, 0x5A, 0xB8, 0x73, 0x23, 0x2E, 0x0E, 0x3C, 0x0A, 0xBF, 0xE5, 0x2A, 0xD0, 0x10, 0x67, 0xC5, 0xE2, 0xBB, 0xC2, 0xA7, 0xAC, 0xB7, 0xF2, 0x81, 0x1F, 0x2B, 0xB6, 0x2A, 0x7C, 0xDD, 0x55, 0xE0, 0x55, 0x9C, 0x69, 0x97, 0xD3, 0xC2, 0xB2, 0xC4, 0x7E, 0x5A, 0x7C, 0x46, 0xF8, 0xB6, 0x2B, 0x49, 0x9F, 0x34, 0x19, 0x21, 0x3E, 0x8F, 0x6A, 0x97, 0x0F, 0x1C, 0x5B, 0xBE, 0xA4, 0xF0, 0xDD, 0x69, 0x0B, 0xCC, 0x01, 0x97, 0x12, 0x77, 0xC3, 0xE8, 0x16, 0x87, 0x16, 0x18, 0xA7, 0x45, 0x27, 0x12, 0x73, 0x1B, 0x70, 0x90, 0x2C, 0x56, 0x8B, 0xCC, 0x69, 0xB9, 0x97, 0xE7, 0x7A, 0x48, 0x81, 0x1D, 0x79, 0x6E, 0x03, 0x3D, 0x87, 0x5F, 0xE7, 0xDE, 0xB9, 0x92, 0xF5, 0x98, 0xB6, 0x18, 0x73, 0xE5, 0x2D, 0xC4, 0x81, 0x27, 0x42, 0xC6, 0xD4, 0x43, 0x5D, 0x5C, 0x3E, 0x6A, 0xFD, 0x5D, 0xD0, 0x4B, 0xE6, 0xC2, 0x9E, 0xF8, 0x3A, 0x04, 0x2C, 0x1A, 0xA8, 0x5B, 0x51, 0x5F, 0x15, 0xB9, 0x09, 0x0A, 0xAC, 0x01, 0x7D, 0x60, 0x00, 0x6C, 0x04, 0x89, 0x6B, 0xD4, 0x8C, 0x22, 0x65, 0xD4, 0xAB, 0x07, 0x21, 0x2E, 0x27, 0xEF, 0x4B, 0xCE, 0x61, 0x94, 0x38, 0xA8, 0xFE, 0xEB, 0x22, 0x3E, 0x6A, 0xFD, 0xAB, 0xA8, 0x25, 0x5A, 0x14, 0xCB, 0x02, 0x07, 0x0C, 0x7B, 0x3E, 0x10, 0xF1, 0x89, 0xBE, 0x5D, 0x1E, 0x75, 0x71, 0x45, 0xFD, 0x70, 0x3A, 0x8C, 0xD1, 0x96, 0x20, 0x24, 0x50, 0x13, 0x51, 0x07, 0x5E, 0x50, 0xA3, 0xD9, 0x03, 0x9E, 0x81, 0x53, 0xF1, 0x05, 0x7E, 0xD0, 0xD9, 0xC0, 0x3F, 0xA0, 0xD8, 0x8F, 0x1F, 0xB8, 0x80, 0x0B, 0x45, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/PNG_REFRESH.hpng ================================================ unsigned char PNG_REFRESH_png[449] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7A, 0x7A, 0xF4, 0x00, 0x00, 0x00, 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x01, 0x76, 0x49, 0x44, 0x41, 0x54, 0x58, 0x85, 0xED, 0xD6, 0x3F, 0x4B, 0x1D, 0x41, 0x1C, 0x85, 0xE1, 0x47, 0x45, 0x88, 0x36, 0x16, 0x2A, 0x01, 0x8D, 0x28, 0x9A, 0x90, 0x46, 0x53, 0xD8, 0xFB, 0x11, 0x82, 0x88, 0x22, 0xA8, 0x85, 0x56, 0x36, 0x16, 0x16, 0x16, 0xA9, 0x52, 0xD8, 0xD9, 0xA5, 0x0C, 0xC1, 0xCA, 0xC2, 0x10, 0x08, 0x5A, 0x49, 0xD2, 0x99, 0xE0, 0x57, 0x10, 0xAC, 0x03, 0x82, 0x85, 0x7F, 0x8A, 0xA4, 0x52, 0xBC, 0x57, 0x53, 0xCC, 0xAC, 0xAE, 0x8D, 0xD9, 0xD9, 0x2B, 0x44, 0x70, 0x5F, 0x18, 0x58, 0xD8, 0x39, 0x73, 0xCE, 0xCC, 0xCE, 0xCE, 0xFC, 0xA8, 0x78, 0xEA, 0x34, 0x25, 0xF6, 0xEF, 0xC3, 0x1C, 0xC6, 0xF0, 0x06, 0x9D, 0xA8, 0xE1, 0x10, 0xBF, 0xB0, 0x83, 0x2D, 0x1C, 0x3F, 0x5C, 0xC4, 0xC0, 0x0B, 0x7C, 0x8E, 0x66, 0xD7, 0xFF, 0x68, 0x35, 0xAC, 0xA3, 0x2B, 0xA7, 0xCF, 0xDE, 0x95, 0x62, 0x12, 0x7F, 0xE2, 0x00, 0xE7, 0xD8, 0xC4, 0x34, 0x06, 0xD0, 0x86, 0x0E, 0x8C, 0x08, 0x2B, 0xF3, 0x2D, 0x17, 0xF2, 0x0C, 0xE3, 0x8D, 0x06, 0x58, 0xC2, 0x55, 0x14, 0x7F, 0x45, 0x6F, 0x01, 0xCD, 0x4B, 0xFC, 0x8C, 0x9A, 0x4B, 0x2C, 0x94, 0x0D, 0x30, 0x81, 0x7A, 0x6C, 0xCB, 0x89, 0xDA, 0x66, 0xAC, 0x46, 0xD3, 0x6C, 0x02, 0x49, 0x01, 0x9E, 0xE3, 0x34, 0x8A, 0x56, 0x12, 0xCD, 0xF3, 0xAC, 0xB9, 0xBB, 0x3F, 0x0A, 0xF3, 0x31, 0x0A, 0x76, 0x4A, 0x98, 0xDE, 0xB7, 0x41, 0x0B, 0xD1, 0x8A, 0x03, 0x61, 0x33, 0xBD, 0xFA, 0x1F, 0x01, 0x08, 0xDF, 0x70, 0xB4, 0x84, 0x79, 0x45, 0x45, 0xC5, 0xA3, 0x65, 0x54, 0xF8, 0xB5, 0x0B, 0xF3, 0x20, 0x87, 0x49, 0xE4, 0xB5, 0x70, 0xA8, 0x1D, 0x08, 0x87, 0xDC, 0x1D, 0x92, 0x52, 0x95, 0xA0, 0x09, 0x1F, 0xD0, 0x82, 0x5D, 0xE1, 0x76, 0x2C, 0x44, 0x7E, 0xC6, 0xAB, 0x0D, 0x04, 0x78, 0x17, 0xC7, 0x38, 0x46, 0x77, 0x8A, 0x30, 0x33, 0xCF, 0xAE, 0xD2, 0xF7, 0xD2, 0xCB, 0xB7, 0x95, 0xA8, 0xAF, 0xE1, 0x6D, 0xA2, 0xF6, 0x26, 0xC0, 0xBC, 0xB0, 0x6C, 0xD7, 0xF8, 0x81, 0xA1, 0x02, 0xDA, 0x7E, 0x6C, 0x47, 0x4D, 0x1D, 0x8B, 0xA9, 0xE6, 0xF9, 0x00, 0x84, 0xF4, 0x59, 0x6D, 0x50, 0xC3, 0x77, 0xCC, 0x62, 0x10, 0xCF, 0xD0, 0x1E, 0x9F, 0x67, 0xF0, 0x05, 0x17, 0xB1, 0xEF, 0x6F, 0xB7, 0x25, 0x59, 0x43, 0x01, 0x08, 0xD5, 0xEF, 0x27, 0xB7, 0xAB, 0x71, 0x5F, 0xBB, 0xC4, 0x06, 0x7A, 0x8A, 0x18, 0xA5, 0x7E, 0xD7, 0x2E, 0x4C, 0x09, 0x33, 0x1B, 0x16, 0x36, 0x56, 0x1D, 0x27, 0xD8, 0xC7, 0x9E, 0x50, 0xB4, 0x1E, 0x25, 0x8E, 0x5B, 0xF1, 0x84, 0xF9, 0x0B, 0x5F, 0xA6, 0x85, 0x78, 0x5B, 0xF8, 0x93, 0x75, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/X_BOX.xpm ================================================ /* XPM */ const char * X_BOX_xpm[] = { "32 32 2 1", " c None", ". c #000000", " ", " ", " ", " ", " ......... ....... ", " ......... ....... ", " ......... ....... .. ", " .......... ....... ... ", " .......... ....... .... ", " ..... ", " ................... ..... ", " ................... ..... ", " ................... ..... ", " ....... ..... ..... ", " ................... ..... ", " ................... ..... ", " ................... ..... ", " ................... ..... ", " ................... ..... ", " ................... ..... ", " ................... ..... ", " ................... .... ", " ................... .... ", " ................... ... ", " ................... .. ", " ................... .. ", " ................... ", " ................... ", " ", " ", " ", " "}; ================================================ FILE: src/resource/embedded/X_GAME_PROFILE.xpm ================================================ /* XPM */ const char* X_GAME_PROFILE_xpm[] = { "32 32 2 1", " c None", ". c #000000", " ", " ...................... ", " ....................... ", " .. ", " .. ..................... ", " .. ....................... ", " .. ... ", " .. .. ", " .. .. ...................... ", " .. .. ...................... ", " .. .. ...................... ", " .. .. ...................... ", " .. .. ...................... ", " .. .. ...................... ", " .. .. ...................... ", " .. .. ...................... ", " .. .. ...................... ", " .. .. ...................... ", " .. .. .......... ........... ", " .. .. ............ ......... ", " .. .. ........... ........ ", " .. .. ............ ....... ", " .. .. ............. ...... ", " .. .............. ..... ", " .. ............... .... ", " . ................ ... ", " ................. .. ", " .................. .. ", " ................... ...", " ......", " .... ", " .. "}; ================================================ FILE: src/resource/embedded/X_HOTKEY_SETTINGS.xpm ================================================ /* XPM */ const char *X_HOTKEY_SETTINGS_xpm[] = { "64 64 2 1", " c None", ". c #000000", " ... ", " .... ", " .... ", " .. .... ", " .. ...... ", " ... ...... ", " ... ...... ", " ... ....... ", " .... ........ ... ", " .... ......... ... ", " .... ......... ... ", " .... .......... .... ", " .... ......... .... ", " ..... ........... ..... ", " ..... .......... ..... ", " ........ ........... ...... ", " ........ ... ....... ...... ", " ........... ... ....... ...... ", " ........... ..... ...... ....... ", " ........... ..... ...... ........ ", " ............. ...... ...... ......... ", " ........ .... ...... .... ..... .... ", " ........ .... ....... .... ...... .... ", " ...... .... ...... ..... ....... ..... ", " ...... ........... ............ .... ", " ...... .......... .......... .... ", " ..... .......... .......... .... ", " ..... .......... ......... ..... ", " ..... .......... ........ ..... ", " ..... ........ ........ ...... ", " ..... ..... ..... ..... ", " ..... ..... .... ...... ", " ..... ..... ", " ... .... ", " . ............................ .. ", " ................................ ", " ................................ ", " .................................... ", " .................................... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... .......... ", " .......... .......... ", " .......... .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .......... ........ .......... ", " .................................... ", " .................................... ", " .................................... ", " .................................... ", " ................................ ", " ................................ ", " ............................. " }; ================================================ FILE: src/resource/embedded/X_SETTINGS.xpm ================================================ /* XPM */ const char* X_SETTINGS_xpm[] = { "64 64 2 1", " c None", ". c #000000", " ..................................................... ", " ..................................................... ", " ....................................................... ", " .......................................................... ", " ........................................................... ", ".............................................................. ", ".............................................................. ", ".............................. .............................. ", "............................. ..............................", "............................. ..............................", "............................. .............................", "............................ .............................", "............................ .............................", "............... ........... .............................", ".............. ........ ........ ...............", "............. ..... .... ...............", "............. .. .. ..............", ".............. ...............", ".............. ...............", "............... ................", "............... .................", "................ ....... .................", "................. ............. .................", "................. ............... .................", "................ ................. .................", "................ ................... .................", "............... ................... .................", ".............. .................... ...............", "........... ..................... ............", "........ ..................... .........", "....... ..................... ........", "....... ..................... ........", "....... ..................... ........", "........ ..................... .........", ".......... ..................... ...........", ".............. .................... ...............", "............... ................... ................", "................ .................. .................", "................ ................. .................", "................. ............... .................", "................. ............ .................", "................ ......... .................", "............... .................", "............... ................", ".............. ...............", ".............. ...............", ".............. ...............", ".............. ...... ...... ...............", ".............. ........ ........ ...............", "............................ .............................", "............................ .............................", "............................ .............................", "............................. .............................", "............................. ..............................", "............................. ..............................", ".............................. .............................. ", ".............................................................. ", ".............................................................. ", " ........................................................... ", " .......................................................... ", " ......................................................... ", " ..................................................... ", " ................................................. ", " ............................................... "}; ================================================ FILE: src/resource/embedded/fontawesome.S ================================================ .section .rodata,"",%progbits .global g_fontawesome_data, g_fontawesome_size g_fontawesome_data: .incbin "fontawesome-webfont.ttf" g_fontawesome_size: .int g_fontawesome_size - g_fontawesome_data .section .note.GNU-stack,"",%progbits ================================================ FILE: src/resource/embedded/fontawesome_macos.S ================================================ .section __DATA, __const .global _g_fontawesome_data, _g_fontawesome_size _g_fontawesome_data: .incbin "fontawesome-webfont.ttf" _g_fontawesome_size: .int _g_fontawesome_size - _g_fontawesome_data ================================================ FILE: src/resource/embedded/icons8-checkmark-yes-32.hpng ================================================ unsigned char PNG_CHECK_YES_png[573] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7A, 0x7A, 0xF4, 0x00, 0x00, 0x00, 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x01, 0xF2, 0x49, 0x44, 0x41, 0x54, 0x58, 0x85, 0xED, 0xD6, 0xBD, 0x6E, 0x13, 0x41, 0x10, 0x07, 0xF0, 0x1F, 0x35, 0x89, 0xA0, 0xA0, 0x00, 0x4C, 0xC4, 0x97, 0xC1, 0x79, 0x14, 0xA4, 0x38, 0xA1, 0x25, 0xF0, 0x04, 0x40, 0x28, 0x10, 0x79, 0x06, 0x2A, 0x1A, 0x3A, 0x82, 0x78, 0x80, 0xC8, 0x24, 0x08, 0x10, 0xD0, 0x22, 0x51, 0x21, 0x1E, 0x00, 0x41, 0x0A, 0x28, 0x80, 0x1A, 0x08, 0x05, 0x32, 0xC6, 0x32, 0xC5, 0x8E, 0x39, 0x63, 0x11, 0xDF, 0xDE, 0x39, 0x5D, 0xF2, 0x97, 0xB6, 0xB8, 0xDD, 0x99, 0xFF, 0xCC, 0xCE, 0xED, 0x7C, 0xB0, 0x8F, 0xBD, 0x8E, 0x03, 0x15, 0xE5, 0xE7, 0x70, 0x11, 0x0B, 0x38, 0x15, 0xDF, 0xF0, 0x09, 0x1F, 0xF1, 0x1C, 0x4F, 0xF0, 0x79, 0x97, 0xFC, 0xFB, 0x8B, 0x06, 0xD6, 0xD0, 0xC3, 0xA0, 0x64, 0xF5, 0xD1, 0xC1, 0xC9, 0xDD, 0x32, 0xBE, 0x84, 0xED, 0x20, 0xEF, 0x62, 0x1D, 0x97, 0xD0, 0xC2, 0xC1, 0x58, 0x2D, 0x2C, 0xC7, 0x59, 0x37, 0x64, 0xB7, 0xB1, 0x38, 0xAD, 0xF1, 0x1B, 0xD2, 0x8D, 0x06, 0xD2, 0xAD, 0x4E, 0x67, 0xE8, 0x9C, 0xC1, 0x86, 0x22, 0x1A, 0x2B, 0x75, 0x8D, 0x2F, 0x05, 0xC1, 0x6F, 0xDC, 0xAC, 0xA1, 0x7F, 0x2B, 0x74, 0xFB, 0x6A, 0x44, 0xA2, 0xA1, 0x08, 0x7B, 0x1D, 0xE3, 0xA3, 0x4E, 0x0C, 0xF0, 0x1D, 0xC7, 0xAA, 0x28, 0x3E, 0x50, 0x84, 0x7D, 0x5A, 0x6C, 0x06, 0xD7, 0x5A, 0xAE, 0xC2, 0x9C, 0x14, 0xBA, 0xAE, 0xBC, 0x7F, 0x5E, 0x86, 0xB3, 0xC1, 0xD5, 0x93, 0x22, 0x5B, 0x8A, 0x15, 0xC9, 0xE3, 0xF5, 0x9A, 0x06, 0xEF, 0x48, 0xA1, 0x1F, 0x45, 0x27, 0x38, 0xAF, 0xE5, 0x10, 0xBC, 0x08, 0xE1, 0xE5, 0x9A, 0xC6, 0x07, 0xF8, 0xE9, 0xDF, 0x3A, 0x70, 0x25, 0xF6, 0x9F, 0xE5, 0x90, 0x6C, 0x85, 0x70, 0xB3, 0xA2, 0xF1, 0xDB, 0x8A, 0x5A, 0xD1, 0x1E, 0x3B, 0x6B, 0xC5, 0xD9, 0xBB, 0x1C, 0xA2, 0x1F, 0x21, 0x3C, 0x33, 0xB6, 0x7F, 0x0E, 0x2F, 0xFD, 0xFF, 0x3F, 0x4E, 0x32, 0x2E, 0xB8, 0x06, 0xC1, 0x5D, 0x8A, 0x61, 0xFA, 0xCD, 0x8E, 0xED, 0x3F, 0x56, 0xDC, 0xE2, 0xF8, 0xC8, 0xFE, 0x68, 0xD8, 0x2F, 0xEC, 0xC0, 0x39, 0xAB, 0xA8, 0x8E, 0xA5, 0x78, 0x1F, 0xC2, 0xE7, 0xC7, 0xF6, 0x0F, 0xE1, 0x75, 0x9C, 0x6D, 0x49, 0x91, 0x28, 0xBB, 0xF9, 0x10, 0xF3, 0x21, 0xF7, 0x36, 0xC7, 0x81, 0x49, 0x8F, 0xF0, 0x30, 0xDE, 0xC4, 0xF9, 0x57, 0xE5, 0x37, 0x1F, 0xA2, 0xD2, 0x23, 0xBC, 0x6E, 0x72, 0x1A, 0x8E, 0x3A, 0x91, 0x63, 0x9C, 0x22, 0x0D, 0xAF, 0xE6, 0x38, 0x70, 0x42, 0x2A, 0x1A, 0x5D, 0xA9, 0xB1, 0xEC, 0xE4, 0xC4, 0xAB, 0x4C, 0xE3, 0x4D, 0x15, 0x0B, 0x11, 0xDC, 0x97, 0x3C, 0xDE, 0x98, 0x20, 0x93, 0x3B, 0xCC, 0x3C, 0x0A, 0xAE, 0x7B, 0xB9, 0xC6, 0x49, 0xAF, 0x7C, 0x98, 0x0D, 0xE3, 0x55, 0xAD, 0x0A, 0x56, 0x83, 0xE3, 0x1B, 0x8E, 0x56, 0x55, 0x5E, 0x54, 0xB4, 0xE3, 0x3A, 0x4E, 0xAC, 0x2A, 0xDA, 0xF1, 0x42, 0x0D, 0x7D, 0xA4, 0xBE, 0x30, 0x1C, 0x48, 0x36, 0xA5, 0xC6, 0x52, 0x86, 0xA6, 0x22, 0xEC, 0x7D, 0xE9, 0x51, 0x4F, 0x85, 0xB6, 0xD4, 0xCF, 0x87, 0xF9, 0xDE, 0xC1, 0x65, 0x29, 0xB7, 0x67, 0x62, 0xCD, 0x4B, 0xA9, 0xF6, 0x10, 0xBF, 0x14, 0x61, 0xAF, 0x7D, 0xF3, 0x71, 0x1C, 0xC1, 0x5D, 0x79, 0x43, 0x69, 0x4F, 0xEA, 0xFD, 0x59, 0x03, 0x48, 0xD5, 0xB1, 0xBC, 0x21, 0x8D, 0x6A, 0x6D, 0x69, 0x56, 0x18, 0x1D, 0xCB, 0x3F, 0x48, 0x63, 0xF9, 0x53, 0x7C, 0xA9, 0xC8, 0xBB, 0x8F, 0x3D, 0x8C, 0x3F, 0x75, 0x73, 0xA4, 0xCD, 0x6F, 0x2F, 0x42, 0xB3, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/icons8-error-32.hpng ================================================ unsigned char PNG_ERROR_png[472] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7A, 0x7A, 0xF4, 0x00, 0x00, 0x00, 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x01, 0x8D, 0x49, 0x44, 0x41, 0x54, 0x58, 0x85, 0xED, 0x95, 0x4F, 0x28, 0x04, 0x71, 0x1C, 0xC5, 0x3F, 0xFE, 0x24, 0xB4, 0x29, 0x29, 0x25, 0xA2, 0x94, 0x83, 0xE2, 0xE0, 0xA0, 0x28, 0x0E, 0x14, 0x27, 0x4E, 0x14, 0x27, 0x7F, 0x8E, 0x9C, 0x38, 0x2B, 0x4E, 0xD6, 0x95, 0x3B, 0x17, 0xE2, 0x66, 0x1D, 0x1D, 0x94, 0x72, 0x70, 0x50, 0x1C, 0xC8, 0x65, 0x2F, 0x22, 0x91, 0x92, 0x92, 0xBF, 0x21, 0xD6, 0x61, 0xDF, 0xB4, 0x63, 0xCD, 0xCC, 0xCE, 0xAC, 0x9D, 0xD9, 0xD4, 0xBE, 0xFA, 0xD6, 0xF6, 0x7D, 0xEF, 0xF7, 0xDE, 0xDB, 0xDF, 0x4E, 0xB3, 0x90, 0xC3, 0x3F, 0x47, 0xB3, 0x26, 0x6B, 0xD8, 0xD5, 0x64, 0x05, 0x83, 0x40, 0x4C, 0x33, 0x10, 0x74, 0x78, 0x31, 0x70, 0x66, 0x2A, 0x70, 0x01, 0x94, 0x06, 0x59, 0x60, 0x46, 0xC1, 0x27, 0x9A, 0x98, 0x76, 0x81, 0xA0, 0x1A, 0x78, 0x54, 0x68, 0x2F, 0xD0, 0xAD, 0xCF, 0xCF, 0x40, 0x6D, 0x10, 0x05, 0xD6, 0x14, 0x18, 0x31, 0xED, 0xB6, 0xB4, 0x5B, 0xF5, 0x3B, 0xBC, 0x0D, 0xF8, 0x02, 0xDE, 0x80, 0x06, 0xD3, 0xBE, 0x1E, 0x78, 0x15, 0xD7, 0xE1, 0x57, 0x78, 0x1E, 0x70, 0x40, 0xFC, 0x9B, 0x86, 0x2D, 0xF8, 0x05, 0x71, 0x87, 0x40, 0xBE, 0x1F, 0x05, 0x46, 0x15, 0x70, 0x03, 0x94, 0x59, 0xF0, 0x21, 0xE0, 0x5A, 0x9A, 0x91, 0x4C, 0x87, 0x87, 0x80, 0x2B, 0x99, 0x8F, 0x39, 0xE8, 0xC6, 0x71, 0x2E, 0x99, 0x36, 0xC2, 0x32, 0x3E, 0xE2, 0xE7, 0xF5, 0x1A, 0xEF, 0x01, 0x03, 0xF9, 0x24, 0x7E, 0xA6, 0xF9, 0x4C, 0x85, 0x9B, 0x1F, 0xB0, 0xCE, 0x24, 0x2E, 0xB9, 0x00, 0x40, 0x3B, 0xD6, 0x0F, 0x6A, 0xDA, 0x88, 0x28, 0x64, 0xDD, 0x82, 0xB3, 0x2A, 0x00, 0xB0, 0xA1, 0xFD, 0xE6, 0x5F, 0xC3, 0xBB, 0x64, 0xF4, 0x02, 0xD4, 0x79, 0x28, 0x50, 0x03, 0x3C, 0x89, 0xEB, 0x49, 0x37, 0xBC, 0x00, 0x38, 0x96, 0xC9, 0xAC, 0x8D, 0xC6, 0xAE, 0x00, 0xC0, 0x9C, 0xB8, 0x53, 0xA0, 0x30, 0x9D, 0x02, 0x93, 0x32, 0xB8, 0xC4, 0xFE, 0x8F, 0xC6, 0xA9, 0x40, 0x09, 0x70, 0x2E, 0x7E, 0xC2, 0x6B, 0x78, 0x39, 0x70, 0xAB, 0xC3, 0x43, 0x5E, 0x0F, 0x9B, 0x30, 0x2C, 0x8F, 0x3B, 0xA0, 0xC2, 0xCB, 0xC1, 0x25, 0x1D, 0xDC, 0x27, 0xFE, 0x06, 0xB4, 0x83, 0xD3, 0x0D, 0x18, 0xD8, 0x93, 0x66, 0xD1, 0x6D, 0x78, 0x23, 0xF0, 0x0E, 0x7C, 0x02, 0xAD, 0x29, 0xB4, 0x6E, 0x0A, 0xB4, 0xC8, 0xEB, 0x03, 0x68, 0x72, 0x53, 0x60, 0x5B, 0xA6, 0xCB, 0x6E, 0xC4, 0x2E, 0xB1, 0x22, 0xCF, 0x9D, 0x54, 0xC2, 0x7E, 0x09, 0x1F, 0x80, 0xAA, 0x0C, 0x16, 0xA8, 0x04, 0xEE, 0xE5, 0xDD, 0x67, 0x27, 0x2A, 0x02, 0xA2, 0x24, 0xAE, 0xD5, 0xAF, 0x89, 0x2A, 0xEB, 0x17, 0xA6, 0x03, 0x08, 0x37, 0x66, 0x2A, 0xE5, 0x9D, 0xE5, 0x90, 0x43, 0x50, 0xF8, 0x06, 0xB0, 0x55, 0xA0, 0x4B, 0x18, 0x99, 0x32, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; ================================================ FILE: src/resource/embedded/resources.cpp ================================================ #include "X_GAME_PROFILE.xpm" #include "M_WND_ICON128.xpm" #include "X_BOX.xpm" #include "X_SETTINGS.xpm" #include "X_HOTKEY_SETTINGS.xpm" #include "icons8-checkmark-yes-32.hpng" #include "icons8-error-32.hpng" #include "PNG_HELP.hpng" #include "PNG_REFRESH.hpng" #include "DEBUGGER_BP.hpng" #include "DEBUGGER_BP_RED.hpng" #include "DEBUGGER_PAUSE.hpng" #include "DEBUGGER_PLAY.hpng" #include "DEBUGGER_STEP_INTO.hpng" #include "DEBUGGER_STEP_OUT.hpng" #include "DEBUGGER_STEP_OVER.hpng" #include "DEBUGGER_GOTO.hpng" #include "INPUT_LOW_BATTERY.hpng" #include "INPUT_CONNECTED.hpng" #include "INPUT_DISCONNECTED.hpng" ================================================ FILE: src/resource/embedded/resources.h ================================================ extern const char* X_GAME_PROFILE_xpm[]; extern const char* M_WND_ICON128_xpm[]; extern const char* X_BOX_xpm[]; extern const char* X_SETTINGS_xpm[]; extern const char* X_HOTKEY_SETTINGS_xpm[]; extern unsigned char PNG_CHECK_YES_png[573]; extern unsigned char PNG_ERROR_png[472]; extern unsigned char PNG_HELP_png[492]; extern unsigned char PNG_REFRESH_png[449]; extern unsigned char DEBUGGER_BP_RED_png[470]; extern unsigned char DEBUGGER_PAUSE_png[211]; extern unsigned char DEBUGGER_PLAY_png[306]; extern unsigned char DEBUGGER_STEP_INTO_png[297]; extern unsigned char DEBUGGER_STEP_OUT_png[308]; extern unsigned char DEBUGGER_STEP_OVER_png[296]; extern unsigned char DEBUGGER_BP_png[436]; extern unsigned char DEBUGGER_GOTO_png[292]; extern unsigned char INPUT_CONNECTED_png[400]; extern unsigned char INPUT_DISCONNECTED_png[410]; extern unsigned char INPUT_LOW_BATTERY_png[340]; ================================================ FILE: src/resource/installer.nsi ================================================ ; Copyright Dolphin Emulator Project / Azahar Emulator Project / Team Cemu ; Licensed under MPL 2.0 with permission from authors ; Usage: ; get the latest nsis: https://nsis.sourceforge.io/Download ; probably also want vscode extension: https://marketplace.visualstudio.com/items?itemName=idleberg.nsis ; Require /DPRODUCT_VERSION for makensis. !ifndef PRODUCT_VERSION !error "PRODUCT_VERSION must be defined" !endif ManifestDPIAware true !define PRODUCT_NAME "Cemu" !define PRODUCT_PUBLISHER "Team Cemu" !define PRODUCT_WEB_SITE "https://cemu.info/" !define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\${PRODUCT_NAME}.exe" !define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" !define BINARY_SOURCE_DIR "..\..\bin" Name "${PRODUCT_NAME}" OutFile "cemu-${PRODUCT_VERSION}-windows-x64-installer.exe" SetCompressor /SOLID lzma InstallDir "$LOCALAPPDATA\Cemu" ShowInstDetails show ShowUnInstDetails show !include "MUI2.nsh" ; Custom page plugin !include "nsDialogs.nsh" ; MUI Settings !define MUI_ICON "logo_icon.ico" !define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico" ; License page !insertmacro MUI_PAGE_LICENSE "..\..\LICENSE.txt" ; Desktop Shortcut page Page custom desktopShortcutPageCreate desktopShortcutPageLeave ; Directory page !insertmacro MUI_PAGE_DIRECTORY ; Instfiles page !insertmacro MUI_PAGE_INSTFILES ; Finish page !define MUI_FINISHPAGE_RUN "$INSTDIR\Cemu.exe" !insertmacro MUI_PAGE_FINISH ; Uninstaller pages !insertmacro MUI_UNPAGE_INSTFILES ; Variables Var DesktopShortcutPageDialog Var DesktopShortcutCheckbox Var DesktopShortcut ; Language files !insertmacro MUI_LANGUAGE "English" !insertmacro MUI_LANGUAGE "SimpChinese" !insertmacro MUI_LANGUAGE "TradChinese" !insertmacro MUI_LANGUAGE "Danish" !insertmacro MUI_LANGUAGE "Dutch" !insertmacro MUI_LANGUAGE "French" !insertmacro MUI_LANGUAGE "German" !insertmacro MUI_LANGUAGE "Hungarian" !insertmacro MUI_LANGUAGE "Italian" !insertmacro MUI_LANGUAGE "Japanese" !insertmacro MUI_LANGUAGE "Korean" !insertmacro MUI_LANGUAGE "Lithuanian" !insertmacro MUI_LANGUAGE "Norwegian" !insertmacro MUI_LANGUAGE "Polish" !insertmacro MUI_LANGUAGE "PortugueseBR" !insertmacro MUI_LANGUAGE "Romanian" !insertmacro MUI_LANGUAGE "Russian" !insertmacro MUI_LANGUAGE "Spanish" !insertmacro MUI_LANGUAGE "Swedish" !insertmacro MUI_LANGUAGE "Turkish" !insertmacro MUI_LANGUAGE "Vietnamese" ; MUI end ------ Function .onInit StrCpy $DesktopShortcut 1 !insertmacro MUI_LANGDLL_DISPLAY FunctionEnd Function desktopShortcutPageCreate !insertmacro MUI_HEADER_TEXT "Create Desktop Shortcut" "Would you like to create a desktop shortcut?" nsDialogs::Create 1018 Pop $DesktopShortcutPageDialog ${If} $DesktopShortcutPageDialog == error Abort ${EndIf} ${NSD_CreateCheckbox} 0u 0u 100% 12u "Create a desktop shortcut" Pop $DesktopShortcutCheckbox ${NSD_SetState} $DesktopShortcutCheckbox $DesktopShortcut nsDialogs::Show FunctionEnd Function desktopShortcutPageLeave ${NSD_GetState} $DesktopShortcutCheckbox $DesktopShortcut FunctionEnd Section "Base" ExecWait '"$INSTDIR\uninst.exe" /S _?=$INSTDIR' SectionIn RO SetOutPath "$INSTDIR" ; The binplaced build output will be included verbatim. File /r "${BINARY_SOURCE_DIR}\*" ; Create start menu and desktop shortcuts CreateShortCut "$SMPROGRAMS\$(^Name).lnk" "$INSTDIR\Cemu.exe" ${If} $DesktopShortcut == 1 CreateShortCut "$DESKTOP\$(^Name).lnk" "$INSTDIR\Cemu.exe" ${EndIf} SectionEnd !include "FileFunc.nsh" Section -Post WriteUninstaller "$INSTDIR\uninst.exe" WriteRegStr HKCU "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\Cemu.exe" ; Write metadata for add/remove programs applet WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)" WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe" WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\Cemu.exe" WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}" WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}" WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "InstallLocation" "$INSTDIR" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD HKCU "${PRODUCT_UNINST_KEY}" "EstimatedSize" "$0" WriteRegStr HKCU "Software\Classes\.wud" "" "$(^Name)" WriteRegStr HKCU "Software\Classes\.wux" "" "$(^Name)" WriteRegStr HKCU "Software\Classes\.wua" "" "$(^Name)" WriteRegStr HKCU "Software\Classes\$(^Name)\DefaultIcon" "" "$INSTDIR\Cemu.exe,0" WriteRegStr HKCU "Software\Classes\$(^Name)\Shell\open\command" "" '"$INSTDIR\Cemu.exe" %1' SectionEnd Section Uninstall Delete "$DESKTOP\$(^Name).lnk" Delete "$SMPROGRAMS\$(^Name).lnk" ; Be a bit careful to not delete files a user may have put into the install directory Delete "$INSTDIR\Cemu.exe" Delete "$INSTDIR\uninst.exe" RMDir /r "$INSTDIR\gameProfiles" RMDir /r "$INSTDIR\resources" RMDir "$INSTDIR" DeleteRegKey HKCU "Software\Classes\.wud" DeleteRegKey HKCU "Software\Classes\.wux" DeleteRegKey HKCU "Software\Classes\.wua" DeleteRegKey HKCU "Software\Classes\$(^Name)" DeleteRegKey HKCU "Software\Classes\discord-460807638964371468" DeleteRegKey HKCU "${PRODUCT_UNINST_KEY}" DeleteRegKey HKCU "${PRODUCT_DIR_REGKEY}" SetAutoClose true SectionEnd ================================================ FILE: src/resource/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by cemu.rc // #include "Common/version.h" #define IDI_ICON2 102 #define IDI_ICON3 103 #define IDR_FONTAWESOME 116 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 118 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: src/resource/update.sh ================================================ #!/bin/sh APP=$(cd "$(dirname "0")"/;pwd) hdiutil attach $TMPDIR/cemu_update/cemu.dmg cp -rf /Volumes/Cemu/Cemu.app "$APP" hdiutil detach /Volumes/Cemu/ open -n -a "$APP/Cemu.app" ================================================ FILE: src/tools/ShaderCacheMerger.cpp ================================================ #include "Cemu/FileCache/FileCache.h" #include "Cafe/HW/Latte/Core/LatteShaderCache.h" #include #include void MergeShaderCacheFile(std::string fileName) { // parse titleId from fileName uint64 titleId = 0; if (sscanf(fileName.c_str(), "%" SCNx64, &titleId) != 1) return; const std::string mainPath = "shaderCache/transferable/" + fileName; const std::string mergeSourcePath = "shaderCache/transferable/merge/" + fileName; if (!fs::exists(mainPath) || !fs::exists(mergeSourcePath)) return; // open both caches FileCache* mainCache = FileCache::Open(boost::nowide::widen(mainPath)); FileCache* sourceCache = FileCache::Open(boost::nowide::widen(mergeSourcePath)); if (!mainCache && !sourceCache) { printf("Failed to open cache files for %s\n", fileName.c_str()); if (mainCache) delete mainCache; if (sourceCache) delete sourceCache; return; } // begin merging printf("Merging shaders %" PRIx64 "...", titleId); uint32 numMergedEntries = 0; // number of files added to the main cache file for (sint32 i = 0; i < sourceCache->GetFileCount(); i++) { uint64 name1, name2; std::vector fileData; if (!sourceCache->GetFileByIndex(i, &name1, &name2, fileData)) continue; std::vector existingfileData; if (mainCache->HasFile({ name1, name2 })) continue; mainCache->AddFile({ name1, name2 }, fileData.data(), (sint32)fileData.size()); numMergedEntries++; } printf(" -> Added %d new shaders for a total of %d\n", numMergedEntries, mainCache->GetFileCount()); delete mainCache; delete sourceCache; fs::remove(mergeSourcePath); } void MergePipelineCacheFile(std::string fileName) { // parse titleId from fileName uint64 titleId = 0; if (sscanf(fileName.c_str(), "%" SCNx64, &titleId) != 1) return; const std::string mainPath = "shaderCache/transferable/" + fileName; const std::string mergeSourcePath = "shaderCache/transferable/merge/" + fileName; if (!fs::exists(mainPath) || !fs::exists(mergeSourcePath)) return; // open both caches const uint32 cacheFileVersion = 1; FileCache* mainCache = FileCache::Open(boost::nowide::widen(mainPath)); FileCache* sourceCache = FileCache::Open(boost::nowide::widen(mergeSourcePath)); if (!mainCache && !sourceCache) { printf("Failed to open cache files for %s\n", fileName.c_str()); if (mainCache) delete mainCache; if (sourceCache) delete sourceCache; return; } // begin merging printf("Merging pipelines %" PRIx64 "...", titleId); uint32 numMergedEntries = 0; // number of files added to the main cache file for (sint32 i = 0; i < sourceCache->GetFileCount(); i++) { uint64 name1, name2; std::vector fileData; if (!sourceCache->GetFileByIndex(i, &name1, &name2, fileData)) continue; std::vector existingfileData; if (mainCache->HasFile({ name1, name2 })) continue; mainCache->AddFile({ name1, name2 }, fileData.data(), (sint32)fileData.size()); numMergedEntries++; } printf(" -> Added %d new pipelines for a total of %d\n", numMergedEntries, mainCache->GetFileCount()); delete mainCache; delete sourceCache; fs::remove(mergeSourcePath); } void MergeShaderAndPipelineCacheFiles() { printf("Scanning for shader cache files to merge...\n"); for (const auto& it : fs::directory_iterator("shaderCache/transferable/")) { if (fs::is_directory(it)) continue; auto filename = it.path().filename().generic_string(); if (std::regex_match(filename, std::regex("^[0-9a-fA-F]{16}(?:_shaders.bin)"))) MergeShaderCacheFile(filename); if (std::regex_match(filename, std::regex("^[0-9a-fA-F]{16}(?:_mtlshaders.bin)"))) MergeShaderCacheFile(filename); } printf("\nScanning for pipeline cache files to merge...\n"); for (const auto& it : fs::directory_iterator("shaderCache/transferable/")) { if (fs::is_directory(it)) continue; auto filename = it.path().filename().generic_string(); if (std::regex_match(filename, std::regex("^[0-9a-fA-F]{16}(?:_vkpipeline.bin)"))) MergePipelineCacheFile(filename); if (std::regex_match(filename, std::regex("^[0-9a-fA-F]{16}(?:_mtlpipeline.bin)"))) MergePipelineCacheFile(filename); } } void ToolShaderCacheMerger() { printf("****************************************************\n"); printf("****************************************************\n"); printf("Shader and pipeline cache merging tool\n"); printf("This tool will merge any shader caches placed in:\n"); printf("shaderCache/transferable/merge/\n"); printf("into the files of the same name in:\n"); printf("shaderCache/transferable/\n"); printf("****************************************************\n"); printf("****************************************************\n"); printf("\n"); MergeShaderAndPipelineCacheFiles(); printf("done!\n"); while (true) Sleep(1000); exit(0); } ================================================ FILE: src/util/CMakeLists.txt ================================================ add_library(CemuUtil boost/bluetooth.h bootSound/BootSoundReader.cpp bootSound/BootSoundReader.h ChunkedHeap/ChunkedHeap.h containers/flat_hash_map.hpp containers/IntervalBucketContainer.h containers/LookupTableL3.h containers/RangeStore.h containers/robin_hood.h containers/SmallBitset.h crypto/aes128.cpp crypto/aes128.h crypto/crc32.cpp crypto/crc32.h crypto/md5.cpp crypto/md5.h DXGIWrapper/DXGIWrapper.h EventService.h Fiber/Fiber.h helpers/ClassWrapper.h helpers/ConcurrentQueue.h helpers/enum_array.hpp helpers/fixedSizeList.h helpers/fspinlock.h helpers/helpers.cpp helpers/helpers.h helpers/MapAdaptor.h helpers/MemoryPool.h helpers/ringbuffer.h helpers/Semaphore.h helpers/Serializer.cpp helpers/Serializer.h helpers/Singleton.h helpers/StringBuf.h helpers/StringHelpers.h helpers/StringParser.h helpers/SystemException.h helpers/TempState.h highresolutiontimer/HighResolutionTimer.cpp highresolutiontimer/HighResolutionTimer.h ImageWriter/bmp.h ImageWriter/tga.h IniParser/IniParser.cpp IniParser/IniParser.h libusbWrapper/libusbWrapper.cpp libusbWrapper/libusbWrapper.h math/glm.h math/quaternion.h math/vector2.h math/vector3.h MemMapper/MemMapper.h SystemInfo/SystemInfo.cpp SystemInfo/SystemInfo.h ThreadPool/ThreadPool.h tinyxml2/tinyxml2.cpp tinyxml2/tinyxml2.h VirtualHeap/VirtualHeap.cpp VirtualHeap/VirtualHeap.h Zir/Core/IR.cpp Zir/Core/IR.h Zir/Core/ZirUtility.h Zir/Core/ZpIRBuilder.h Zir/Core/ZpIRDebug.h Zir/Core/ZpIRPasses.h Zir/Core/ZpIRScheduler.h Zir/EmitterGLSL/ZpIREmitGLSL.cpp Zir/EmitterGLSL/ZpIREmitGLSL.h Zir/Passes/RegisterAllocatorForGLSL.cpp Zir/Passes/ZpIRRegisterAllocator.cpp ) if(WIN32) target_sources(CemuUtil PRIVATE Fiber/FiberWin.cpp) target_sources(CemuUtil PRIVATE MemMapper/MemMapperWin.cpp) target_sources(CemuUtil PRIVATE SystemInfo/SystemInfoWin.cpp) elseif(UNIX) target_sources(CemuUtil PRIVATE Fiber/FiberUnix.cpp) target_sources(CemuUtil PRIVATE MemMapper/MemMapperUnix.cpp) target_sources(CemuUtil PRIVATE SystemInfo/SystemInfoUnix.cpp) if(NOT APPLE) target_sources(CemuUtil PRIVATE SystemInfo/SystemInfoLinux.cpp) else() target_sources(CemuUtil PRIVATE SystemInfo/SystemInfoMac.cpp) endif() else() target_sources(CemuUtil PRIVATE SystemInfo/SystemInfoStub.cpp) endif() set_property(TARGET CemuUtil PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") target_include_directories(CemuUtil PUBLIC "../") target_link_libraries(CemuUtil PRIVATE CemuCommon ) ================================================ FILE: src/util/ChunkedHeap/ChunkedHeap.h ================================================ #pragma once #include struct CHAddr { uint32 offset; uint32 chunkIndex; void* internal; // AllocRange CHAddr(uint32 _offset, uint32 _chunkIndex, void* internal = nullptr) : offset(_offset), chunkIndex(_chunkIndex), internal(internal) {}; CHAddr() : offset(0xFFFFFFFF), chunkIndex(0xFFFFFFFF) {}; bool isValid() { return chunkIndex != 0xFFFFFFFF; }; static CHAddr getInvalid() { return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); }; }; template class ChunkedHeap { struct AllocRange { AllocRange* nextFree{}; AllocRange* prevFree{}; AllocRange* prevOrdered{}; AllocRange* nextOrdered{}; uint32 offset; uint32 chunkIndex; uint32 size; bool isFree; AllocRange(uint32 _offset, uint32 _chunkIndex, uint32 _size, bool _isFree) : offset(_offset), chunkIndex(_chunkIndex), size(_size), isFree(_isFree), nextFree(nullptr) {}; }; struct Chunk { uint32 size; }; public: ChunkedHeap() { } CHAddr alloc(uint32 size, uint32 alignment = 4) { return _alloc(size, alignment); } void free(CHAddr addr) { _free(addr); } virtual uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) = 0; private: unsigned ulog2(uint32 v) { cemu_assert_debug(v != 0); return 31 - std::countl_zero(v); } void trackFreeRange(AllocRange* range) { // get index of msb cemu_assert_debug(range->size != 0); // size of zero is not allowed uint32 bucketIndex = ulog2(range->size); range->nextFree = m_bucketFreeRange[bucketIndex]; if (m_bucketFreeRange[bucketIndex]) m_bucketFreeRange[bucketIndex]->prevFree = range; range->prevFree = nullptr; m_bucketFreeRange[bucketIndex] = range; m_bucketUseMask |= (1u << bucketIndex); } void forgetFreeRange(AllocRange* range, uint32 bucketIndex) { AllocRange* prevRange = range->prevFree; AllocRange* nextRange = range->nextFree; if (prevRange) { prevRange->nextFree = nextRange; if (nextRange) nextRange->prevFree = prevRange; } else { cemu_assert_debug(m_bucketFreeRange[bucketIndex] == range); m_bucketFreeRange[bucketIndex] = nextRange; if (nextRange) nextRange->prevFree = nullptr; else m_bucketUseMask &= ~(1u << bucketIndex); } } bool allocateChunk(uint32 minimumAllocationSize) { uint32 chunkIndex = (uint32)m_chunks.size(); m_chunks.emplace_back(); uint32 chunkSize = allocateNewChunk(chunkIndex, minimumAllocationSize); cemu_assert_debug((chunkSize%TMinimumAlignment) == 0); // chunk size should be a multiple of the minimum alignment if (chunkSize == 0) return false; cemu_assert_debug(chunkSize < 0x80000000u); // chunk size must be below 2GB AllocRange* range = m_allocEntriesPool.allocObj(0, chunkIndex, chunkSize, true); trackFreeRange(range); m_numHeapBytes += chunkSize; return true; } void _allocFrom(AllocRange* range, uint32 bucketIndex, uint32 allocOffset, uint32 allocSize) { cemu_assert_debug(allocSize > 0); // remove the range from the chain of free ranges forgetFreeRange(range, bucketIndex); // split head, allocation and tail into separate ranges uint32 headBytes = allocOffset - range->offset; if (headBytes > 0) { // alignment padding -> create free range cemu_assert_debug(headBytes >= TMinimumAlignment); AllocRange* head = m_allocEntriesPool.allocObj(range->offset, range->chunkIndex, headBytes, true); trackFreeRange(head); if (range->prevOrdered) range->prevOrdered->nextOrdered = head; head->prevOrdered = range->prevOrdered; head->nextOrdered = range; range->prevOrdered = head; } uint32 tailBytes = (range->offset + range->size) - (allocOffset + allocSize); if (tailBytes > 0) { // tail -> create free range cemu_assert_debug(tailBytes >= TMinimumAlignment); AllocRange* tail = m_allocEntriesPool.allocObj((allocOffset + allocSize), range->chunkIndex, tailBytes, true); trackFreeRange(tail); if (range->nextOrdered) range->nextOrdered->prevOrdered = tail; tail->prevOrdered = range; tail->nextOrdered = range->nextOrdered; range->nextOrdered = tail; } range->offset = allocOffset; range->size = allocSize; range->isFree = false; } CHAddr _alloc(uint32 size, uint32 alignment) { cemu_assert_debug(size <= (0x7FFFFFFFu-TMinimumAlignment)); // make sure size is not zero and align it if(size == 0) [[unlikely]] size = TMinimumAlignment; else size = (size + (TMinimumAlignment - 1)) & ~(TMinimumAlignment - 1); // find smallest bucket to scan uint32 alignmentM1 = alignment - 1; uint32 bucketIndex = ulog2(size); // check if the bucket is available if( !(m_bucketUseMask & (1u << bucketIndex)) ) { // skip to next non-empty bucket uint32 nextIndex = BSF(m_bucketUseMask>>bucketIndex); bucketIndex += nextIndex; } while (bucketIndex < 31) { AllocRange* range = m_bucketFreeRange[bucketIndex]; while (range) { if (range->size >= size) { // verify if aligned allocation fits uint32 alignedOffset = (range->offset + alignmentM1) & ~alignmentM1; uint32 endOffset = alignedOffset + size; if((range->offset+range->size) >= endOffset) { _allocFrom(range, bucketIndex, alignedOffset, size); m_numAllocatedBytes += size; return CHAddr(alignedOffset, range->chunkIndex, range); } } range = range->nextFree; } // check next non-empty bucket or skip to end bucketIndex++; uint32 emptyBuckets = BSF(m_bucketUseMask>>bucketIndex); bucketIndex += emptyBuckets; } if(m_allocationLimitReached) return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); if (!allocateChunk(size)) { m_allocationLimitReached = true; return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); } return _alloc(size, alignment); } void _free(CHAddr addr) { if(!addr.internal) { cemuLog_log(LogType::Force, "Internal heap error. {:08x} {:08x}", addr.chunkIndex, addr.offset); return; } AllocRange* range = (AllocRange*)addr.internal; m_numAllocatedBytes -= range->size; // try merge left or right AllocRange* prevRange = range->prevOrdered; AllocRange* nextRange = range->nextOrdered; if (prevRange && prevRange->isFree) { if (nextRange && nextRange->isFree) { forgetFreeRange(nextRange, ulog2(nextRange->size)); uint32 newSize = (nextRange->offset + nextRange->size) - prevRange->offset; prevRange->nextOrdered = nextRange->nextOrdered; if (nextRange->nextOrdered) nextRange->nextOrdered->prevOrdered = prevRange; forgetFreeRange(prevRange, ulog2(prevRange->size)); prevRange->size = newSize; trackFreeRange(prevRange); m_allocEntriesPool.freeObj(range); m_allocEntriesPool.freeObj(nextRange); } else { uint32 newSize = (range->offset + range->size) - prevRange->offset; prevRange->nextOrdered = nextRange; if (nextRange) nextRange->prevOrdered = prevRange; forgetFreeRange(prevRange, ulog2(prevRange->size)); prevRange->size = newSize; trackFreeRange(prevRange); m_allocEntriesPool.freeObj(range); } } else if (nextRange && nextRange->isFree) { uint32 newOffset = range->offset; uint32 newSize = (nextRange->offset + nextRange->size) - newOffset; forgetFreeRange(nextRange, ulog2(nextRange->size)); nextRange->offset = newOffset; nextRange->size = newSize; if (range->prevOrdered) range->prevOrdered->nextOrdered = nextRange; nextRange->prevOrdered = range->prevOrdered; trackFreeRange(nextRange); m_allocEntriesPool.freeObj(range); } else { range->isFree = true; trackFreeRange(range); } } void verifyHeap() { // check for collisions within bucketFreeRange struct availableRange_t { uint32 chunkIndex; uint32 offset; uint32 size; }; std::vector availRanges; for (uint32 i = 0; i < 32; i++) { AllocRange* ar = m_bucketFreeRange[i]; while (ar) { availableRange_t dbgRange; dbgRange.chunkIndex = ar->chunkIndex; dbgRange.offset = ar->offset; dbgRange.size = ar->size; for (auto& itr : availRanges) { if (itr.chunkIndex != dbgRange.chunkIndex) continue; if (itr.offset < (dbgRange.offset + dbgRange.size) && (itr.offset + itr.size) > dbgRange.offset) cemu_assert_error(); } availRanges.emplace_back(dbgRange); ar = ar->nextFree; } } } private: std::vector m_chunks; uint32 m_bucketUseMask{0x80000000}; // bitmask indicating non-empty buckets. MSB always set to provide an upper bound for BSF instruction AllocRange* m_bucketFreeRange[32]{}; // we are only using 31 entries since the MSB is reserved (thus chunks equal or larger than 2^31 are not allowed) bool m_allocationLimitReached = false; MemoryPool m_allocEntriesPool{64}; public: // statistics uint32 m_numHeapBytes{}; // total size of the heap uint32 m_numAllocatedBytes{}; }; class VGenericHeap { public: virtual void* alloc(uint32 size, uint32 alignment) = 0; virtual void free(void* addr) = 0; }; class VHeap : public VGenericHeap { struct allocRange_t { allocRange_t* nextFree{}; allocRange_t* prevFree{}; allocRange_t* prevOrdered{}; allocRange_t* nextOrdered{}; uint32 offset; uint32 size; bool isFree; allocRange_t(uint32 _offset, uint32 _size, bool _isFree) : offset(_offset), size(_size), isFree(_isFree), nextFree(nullptr) {}; }; struct chunk_t { std::unordered_map map_allocatedRange; }; public: VHeap(void* heapBase, uint32 heapSize) : m_heapBase((uint8*)heapBase), m_heapSize(heapSize) { allocRange_t* range = new allocRange_t(0, heapSize, true); trackFreeRange(range); } ~VHeap() { for (auto freeRange : bucketFreeRange) { while (freeRange) { auto temp = freeRange; freeRange = freeRange->nextFree; delete temp; } } } void setHeapBase(void* heapBase) { cemu_assert_debug(map_allocatedRange.empty()); // heap base can only be changed when there are no active allocations m_heapBase = (uint8*)heapBase; } void* alloc(uint32 size, uint32 alignment = 4) override { cemu_assert_debug(m_heapBase != nullptr); // if this is null, we cant use alloc() == nullptr to determine if an allocation failed uint32 allocOffset = 0; bool r = _alloc(size, alignment, allocOffset); if (!r) return nullptr; return m_heapBase + allocOffset; } void free(void* addr) override { _free((uint32)((uint8*)addr - (uint8*)m_heapBase)); } bool allocOffset(uint32 size, uint32 alignment, uint32& offsetOut) { uint32 allocOffset = 0; bool r = _alloc(size, alignment, allocOffset); if (!r) return false; offsetOut = allocOffset; return true; } void freeOffset(uint32 offset) { _free((uint32)offset); } uint32 getAllocationSizeFromAddr(void* addr) { uint32 addrOffset = (uint32)((uint8*)addr - m_heapBase); auto it = map_allocatedRange.find(addrOffset); if (it == map_allocatedRange.end()) assert_dbg(); return it->second->size; } bool hasAllocations() { return !map_allocatedRange.empty(); } void getStats(uint32& heapSize, uint32& allocationSize, uint32& allocNum) { heapSize = m_heapSize; allocationSize = m_statsMemAllocated; allocNum = (uint32)map_allocatedRange.size(); } private: unsigned ulog2(uint32 v) { static const unsigned MUL_DE_BRUIJN_BIT[] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 }; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; return MUL_DE_BRUIJN_BIT[(v * 0x07C4ACDDu) >> 27]; } void trackFreeRange(allocRange_t* range) { // get index of msb if (range->size == 0) assert_dbg(); // not allowed uint32 bucketIndex = ulog2(range->size); range->nextFree = bucketFreeRange[bucketIndex]; if (bucketFreeRange[bucketIndex]) bucketFreeRange[bucketIndex]->prevFree = range; range->prevFree = nullptr; bucketFreeRange[bucketIndex] = range; } void forgetFreeRange(allocRange_t* range, uint32 bucketIndex) { allocRange_t* prevRange = range->prevFree; allocRange_t* nextRange = range->nextFree; if (prevRange) { prevRange->nextFree = nextRange; if (nextRange) nextRange->prevFree = prevRange; } else { if (bucketFreeRange[bucketIndex] != range) assert_dbg(); bucketFreeRange[bucketIndex] = nextRange; if (nextRange) nextRange->prevFree = nullptr; } } void _allocFrom(allocRange_t* range, uint32 bucketIndex, uint32 allocOffset, uint32 allocSize) { // remove the range from the chain of free ranges forgetFreeRange(range, bucketIndex); // split head, allocation and tail into separate ranges if (allocOffset > range->offset) { // alignment padding -> create free range allocRange_t* head = new allocRange_t(range->offset, allocOffset - range->offset, true); trackFreeRange(head); if (range->prevOrdered) range->prevOrdered->nextOrdered = head; head->prevOrdered = range->prevOrdered; head->nextOrdered = range; range->prevOrdered = head; } if ((allocOffset + allocSize) < (range->offset + range->size)) // todo - create only if it's more than a couple of bytes? { // tail -> create free range allocRange_t* tail = new allocRange_t((allocOffset + allocSize), (range->offset + range->size) - (allocOffset + allocSize), true); trackFreeRange(tail); if (range->nextOrdered) range->nextOrdered->prevOrdered = tail; tail->prevOrdered = range; tail->nextOrdered = range->nextOrdered; range->nextOrdered = tail; } range->offset = allocOffset; range->size = allocSize; range->isFree = false; m_statsMemAllocated += allocSize; } bool _alloc(uint32 size, uint32 alignment, uint32& allocOffsetOut) { if(size == 0) { size = 1; // zero-sized allocations are not supported cemu_assert_suspicious(); } // find smallest bucket to scan uint32 alignmentM1 = alignment - 1; uint32 bucketIndex = ulog2(size); while (bucketIndex < 32) { allocRange_t* range = bucketFreeRange[bucketIndex]; while (range) { if (range->size >= size) { // verify if aligned allocation fits uint32 alignedOffset = (range->offset + alignmentM1) & ~alignmentM1; uint32 alignmentLoss = alignedOffset - range->offset; if (alignmentLoss < range->size && (range->size - alignmentLoss) >= size) { _allocFrom(range, bucketIndex, alignedOffset, size); map_allocatedRange.emplace(alignedOffset, range); allocOffsetOut = alignedOffset; return true; } } range = range->nextFree; } bucketIndex++; // try higher bucket } return false; } void _free(uint32 addrOffset) { auto it = map_allocatedRange.find(addrOffset); if (it == map_allocatedRange.end()) { cemuLog_log(LogType::Force, "VHeap internal error"); cemu_assert(false); } allocRange_t* range = it->second; map_allocatedRange.erase(it); m_statsMemAllocated -= range->size; // try merge left or right allocRange_t* prevRange = range->prevOrdered; allocRange_t* nextRange = range->nextOrdered; if (prevRange && prevRange->isFree) { if (nextRange && nextRange->isFree) { forgetFreeRange(nextRange, ulog2(nextRange->size)); uint32 newSize = (nextRange->offset + nextRange->size) - prevRange->offset; prevRange->nextOrdered = nextRange->nextOrdered; if (nextRange->nextOrdered) nextRange->nextOrdered->prevOrdered = prevRange; forgetFreeRange(prevRange, ulog2(prevRange->size)); prevRange->size = newSize; trackFreeRange(prevRange); delete range; delete nextRange; } else { uint32 newSize = (range->offset + range->size) - prevRange->offset; prevRange->nextOrdered = nextRange; if (nextRange) nextRange->prevOrdered = prevRange; forgetFreeRange(prevRange, ulog2(prevRange->size)); prevRange->size = newSize; trackFreeRange(prevRange); delete range; } } else if (nextRange && nextRange->isFree) { uint32 newOffset = range->offset; uint32 newSize = (nextRange->offset + nextRange->size) - newOffset; forgetFreeRange(nextRange, ulog2(nextRange->size)); nextRange->offset = newOffset; nextRange->size = newSize; if (range->prevOrdered) range->prevOrdered->nextOrdered = nextRange; nextRange->prevOrdered = range->prevOrdered; trackFreeRange(nextRange); delete range; } else { range->isFree = true; trackFreeRange(range); } } private: allocRange_t* bucketFreeRange[32]{}; std::unordered_map map_allocatedRange; uint8* m_heapBase; const uint32 m_heapSize; uint32 m_statsMemAllocated{ 0 }; }; template class ChunkedFlatAllocator { public: void setBaseAllocator(VGenericHeap* baseHeap) { m_currentBaseAllocator = baseHeap; } void* alloc(uint32 size, uint32 alignment = 4) { if (m_currentBlockPtr) { m_currentBlockOffset = (m_currentBlockOffset + alignment - 1) & ~(alignment - 1); if ((m_currentBlockOffset+size) <= TChunkSize) { void* allocPtr = m_currentBlockPtr + m_currentBlockOffset; m_currentBlockOffset += size; return allocPtr; } } allocateAdditionalChunk(); return alloc(size, alignment); } void releaseAll() { for (auto it : m_allocatedBlocks) m_currentBaseAllocator->free(it); m_allocatedBlocks.clear(); m_currentBlockPtr = nullptr; m_currentBlockOffset = 0; } void forEachBlock(void(*funcCb)(void* mem, uint32 size)) { for (auto it : m_allocatedBlocks) funcCb(it, TChunkSize); } uint32 getCurrentBlockOffset() const { return m_currentBlockOffset; } uint8* getCurrentBlockPtr() const { return m_currentBlockPtr; } private: void allocateAdditionalChunk() { m_currentBlockPtr = (uint8*)m_currentBaseAllocator->alloc(TChunkSize, 256); m_currentBlockOffset = 0; m_allocatedBlocks.emplace_back(m_currentBlockPtr); } VGenericHeap* m_currentBaseAllocator{}; uint8* m_currentBlockPtr{}; uint32 m_currentBlockOffset{}; std::vector m_allocatedBlocks; }; ================================================ FILE: src/util/DXGIWrapper/DXGIWrapper.h ================================================ #pragma once #undef GetHwnd #include #include class DXGIWrapper { public: DXGIWrapper() : DXGIWrapper(nullptr) {} DXGIWrapper(uint8* deviceLUID) { m_moduleHandle = LoadLibraryA("dxgi.dll"); if (!m_moduleHandle) throw std::runtime_error("can't load dxgi module"); const auto pCreateDXGIFactory1 = (decltype(&CreateDXGIFactory1))GetProcAddress(m_moduleHandle, "CreateDXGIFactory1"); if (!pCreateDXGIFactory1) { FreeLibrary(m_moduleHandle); throw std::runtime_error("can't find CreateDXGIFactory1 in dxgi module"); } Microsoft::WRL::ComPtr dxgiFactory; pCreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)); Microsoft::WRL::ComPtr dxgiAdapter; UINT adapterIndex = 0; while (dxgiFactory->EnumAdapters1(adapterIndex, &dxgiAdapter) != DXGI_ERROR_NOT_FOUND) { DXGI_ADAPTER_DESC1 desc; dxgiAdapter->GetDesc1(&desc); if (deviceLUID == nullptr || memcmp(&desc.AdapterLuid, deviceLUID, sizeof(LUID)) == 0) { if (FAILED(dxgiAdapter.As(&m_dxgiAdapter))) { Cleanup(); throw std::runtime_error("can't create dxgi adapter"); } break; } ++adapterIndex; } } ~DXGIWrapper() { Cleanup(); } bool QueryVideoMemoryInfo(DXGI_QUERY_VIDEO_MEMORY_INFO& info) const { return m_dxgiAdapter->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &info) == S_OK; } private: HMODULE m_moduleHandle = nullptr; Microsoft::WRL::ComPtr m_dxgiAdapter; void Cleanup() { m_dxgiAdapter.Reset(); if (m_moduleHandle) { FreeLibrary(m_moduleHandle); m_moduleHandle = nullptr; } } }; ================================================ FILE: src/util/EventService.h ================================================ #pragma once #include "util/helpers/Singleton.h" #include #include enum class Events : int32_t { ControllerChanged, }; using ControllerChangedFunc = void(void); class EventService : public Singleton { friend class Singleton; EventService() = default; public: template boost::signals2::connection connect(TFunc function, TClass thisptr) { using namespace boost::placeholders; if constexpr (event == Events::ControllerChanged) return m_controller_changed_signal.connect(boost::bind(function, thisptr)); else { cemu_assert_suspicious(); } } template void disconnect(const boost::signals2::connection& slot) { using namespace boost::placeholders; if constexpr (event == Events::ControllerChanged) m_controller_changed_signal.disconnect(slot); else { cemu_assert_suspicious(); } } template void signal(TArgs&&... args) { try { if constexpr (event == Events::ControllerChanged) m_controller_changed_signal(std::forward(args)...); else { cemu_assert_suspicious(); } } catch (const std::exception& ex) { cemuLog_log(LogType::Force, "error when signaling {}: {}", event, ex.what()); } } private: boost::signals2::signal m_controller_changed_signal; }; ================================================ FILE: src/util/Fiber/Fiber.h ================================================ #pragma once #if BOOST_OS_WINDOWS #endif class Fiber { public: Fiber(void(*FiberEntryPoint)(void* userParam), void* userParam, void* privateData); ~Fiber(); static Fiber* PrepareCurrentThread(void* privateData = nullptr); static void Switch(Fiber& targetFiber); static void* GetFiberPrivateData(); private: Fiber(void* privateData); // fiber from current thread void* m_implData{nullptr}; void* m_privateData; void* m_stackPtr{ nullptr }; }; ================================================ FILE: src/util/Fiber/FiberUnix.cpp ================================================ #include "Fiber.h" #include #include thread_local Fiber* sCurrentFiber{}; Fiber::Fiber(void(*FiberEntryPoint)(void* userParam), void* userParam, void* privateData) : m_privateData(privateData) { ucontext_t* ctx = (ucontext_t*)malloc(sizeof(ucontext_t)); const size_t stackSize = 2 * 1024 * 1024; m_stackPtr = malloc(stackSize); getcontext(ctx); ctx->uc_stack.ss_sp = m_stackPtr; ctx->uc_stack.ss_size = stackSize; ctx->uc_link = &ctx[0]; #ifdef __arm64__ // https://www.man7.org/linux/man-pages/man3/makecontext.3.html#NOTES makecontext(ctx, (void(*)())FiberEntryPoint, 2, (uint64) userParam >> 32, userParam); #else makecontext(ctx, (void(*)())FiberEntryPoint, 1, userParam); #endif this->m_implData = (void*)ctx; } Fiber::Fiber(void* privateData) : m_privateData(privateData) { ucontext_t* ctx = (ucontext_t*)malloc(sizeof(ucontext_t)); getcontext(ctx); this->m_implData = (void*)ctx; m_stackPtr = nullptr; } Fiber::~Fiber() { if(m_stackPtr) free(m_stackPtr); free(m_implData); } Fiber* Fiber::PrepareCurrentThread(void* privateData) { cemu_assert_debug(sCurrentFiber == nullptr); sCurrentFiber = new Fiber(privateData); return sCurrentFiber; } void Fiber::Switch(Fiber& targetFiber) { Fiber* leavingFiber = sCurrentFiber; sCurrentFiber = &targetFiber; std::atomic_thread_fence(std::memory_order_seq_cst); swapcontext((ucontext_t*)(leavingFiber->m_implData), (ucontext_t*)(targetFiber.m_implData)); std::atomic_thread_fence(std::memory_order_seq_cst); } void* Fiber::GetFiberPrivateData() { return sCurrentFiber->m_privateData; } ================================================ FILE: src/util/Fiber/FiberWin.cpp ================================================ #include "Fiber.h" #include thread_local Fiber* sCurrentFiber{}; Fiber::Fiber(void(*FiberEntryPoint)(void* userParam), void* userParam, void* privateData) : m_privateData(privateData) { HANDLE fiberHandle = CreateFiber(2 * 1024 * 1024, (LPFIBER_START_ROUTINE)FiberEntryPoint, userParam); this->m_implData = (void*)fiberHandle; } Fiber::Fiber(void* privateData) : m_privateData(privateData) { this->m_implData = (void*)ConvertThreadToFiber(nullptr); this->m_stackPtr = nullptr; } Fiber::~Fiber() { DeleteFiber((HANDLE)m_implData); } Fiber* Fiber::PrepareCurrentThread(void* privateData) { cemu_assert_debug(sCurrentFiber == nullptr); // thread already prepared Fiber* currentFiber = new Fiber(privateData); sCurrentFiber = currentFiber; return currentFiber; } void Fiber::Switch(Fiber& targetFiber) { sCurrentFiber = &targetFiber; SwitchToFiber((HANDLE)targetFiber.m_implData); } void* Fiber::GetFiberPrivateData() { return sCurrentFiber->m_privateData; } ================================================ FILE: src/util/ImageWriter/bmp.h ================================================ #include "Common/FileStream.h" static void _bmp_write(FileStream* fs, sint32 width, sint32 height, uint32 bits, void* pixelData) { BITMAPFILEHEADER bmp_fh; BITMAPINFOHEADER bmp_ih; bmp_fh.bfType = 0x4d42; bmp_fh.bfSize = 0; bmp_fh.bfReserved1 = 0; bmp_fh.bfReserved2 = 0; bmp_fh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); bmp_ih.biSize = sizeof(bmp_ih); bmp_ih.biWidth = width; bmp_ih.biHeight = height; bmp_ih.biPlanes = 1; bmp_ih.biBitCount = bits; bmp_ih.biCompression = 0; bmp_ih.biSizeImage = 0; bmp_ih.biXPelsPerMeter = 0; bmp_ih.biYPelsPerMeter = 0; bmp_ih.biClrUsed = 0; bmp_ih.biClrImportant = 0; sint32 rowPitch = (width * bits / 8); rowPitch = (rowPitch + 3)&~3; uint8 padding[4] = { 0 }; sint32 paddingLength = rowPitch - (width * bits / 8); bmp_ih.biSize = sizeof(BITMAPINFOHEADER); bmp_fh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + rowPitch * height; fs->writeData(&bmp_fh, sizeof(BITMAPFILEHEADER)); fs->writeData(&bmp_ih, sizeof(BITMAPINFOHEADER)); if (bits == 24 || bits == 32) { for (sint32 y = 0; y < height; y++) { void* rowInput = ((uint8*)pixelData) + rowPitch * (height - y - 1); fs->writeData(rowInput, width*bits/8); // write padding if(paddingLength > 0) fs->writeData(padding, paddingLength); } } } static bool bmp_store8BitAs24(wchar_t* path, sint32 width, sint32 height, sint32 bytesPerRow, void* pixelData) { FileStream* fs = FileStream::createFile(path); if (fs == nullptr) return false; uint8* pixelI = (uint8*)pixelData; uint8* pixelRGB = (uint8*)malloc(width * height * 3); for (sint32 y = 0; y < height; y++) { sint32 srcIdx = y * bytesPerRow; for (sint32 x = 0; x < width; x++) { sint32 dstIdx = x + y * width; pixelRGB[dstIdx * 3 + 0] = pixelI[srcIdx]; pixelRGB[dstIdx * 3 + 1] = pixelI[srcIdx]; pixelRGB[dstIdx * 3 + 2] = pixelI[srcIdx]; srcIdx++; } } _bmp_write(fs, width, height, 24, pixelRGB); free(pixelRGB); delete fs; return true; } static bool bmp_store16BitAs24(wchar_t* path, sint32 width, sint32 height, sint32 bytesPerRow, void* pixelData) { FileStream* fs = FileStream::createFile(path); if (fs == nullptr) return false; uint8* pixelI = (uint8*)pixelData; uint8* pixelRGB = (uint8*)malloc(width * height * 3); for (sint32 y = 0; y < height; y++) { sint32 srcIdx = y * bytesPerRow; for (sint32 x = 0; x < width; x++) { sint32 dstIdx = x + y * width; pixelRGB[dstIdx * 3 + 0] = pixelI[srcIdx + 0]; pixelRGB[dstIdx * 3 + 1] = pixelI[srcIdx + 1]; pixelRGB[dstIdx * 3 + 2] = 0; srcIdx += 2; } } _bmp_write(fs, width, height, 24, pixelRGB); free(pixelRGB); delete fs; return true; } static bool bmp_store24BitAs24(wchar_t* path, sint32 width, sint32 height, sint32 bytesPerRow, void* pixelData) { FileStream* fs = FileStream::createFile(path); if (fs == nullptr) return false; uint8* pixelI = (uint8*)pixelData; uint8* pixelRGB = (uint8*)malloc(width * height * 3); for (sint32 y = 0; y < height; y++) { sint32 srcIdx = y * bytesPerRow; for (sint32 x = 0; x < width; x++) { sint32 dstIdx = x + y * width; pixelRGB[dstIdx * 3 + 0] = pixelI[srcIdx + 0]; pixelRGB[dstIdx * 3 + 1] = pixelI[srcIdx + 1]; pixelRGB[dstIdx * 3 + 2] = pixelI[srcIdx + 2]; srcIdx += 3; } } _bmp_write(fs, width, height, 24, pixelRGB); free(pixelRGB); delete fs; return true; } ================================================ FILE: src/util/ImageWriter/tga.h ================================================ #include "Common/FileStream.h" #include static bool tga_write_rgba(const fs::path& path, sint32 width, sint32 height, uint8* pixelData) { FileStream* fs = FileStream::createFile2(path); if (fs == nullptr) return false; uint8_t header[18] = {0,0,2,0,0,0,0,0,0,0,0,0, (uint8)(width % 256), (uint8)(width / 256), (uint8)(height % 256), (uint8)(height / 256), 32, 0x20}; fs->writeData(&header, sizeof(header)); std::vector tempPixelData; tempPixelData.resize(width * height * 4); // write one row at a time uint8* pOut = tempPixelData.data(); for (sint32 y = 0; y < height; y++) { const uint8* rowIn = pixelData + y * width*4; for (sint32 x = 0; x < width; x++) { pOut[0] = rowIn[2]; pOut[1] = rowIn[1]; pOut[2] = rowIn[0]; pOut[3] = rowIn[3]; pOut += 4; rowIn += 4; } } fs->writeData(tempPixelData.data(), width * height * 4); delete fs; return true; } ================================================ FILE: src/util/IniParser/IniParser.cpp ================================================ #include "util/IniParser/IniParser.h" IniParser::IniParser(std::span iniContents, std::string_view name) : m_name(name) { // we dont support utf8 but still skip the byte order mark in case the user saved the document with the wrong encoding if (iniContents.size() >= 3 && (uint8)iniContents[0] == 0xEF && (uint8)iniContents[1] == 0xBB && (uint8)iniContents[2] == 0xBF) iniContents = iniContents.subspan(3); m_iniFileData.assign(iniContents.begin(), iniContents.end()); m_isValid = parse(); } bool IniParser::ReadNextLine(std::string_view& lineString) { if (m_parseOffset >= m_iniFileData.size()) return false; // skip \r and \n for (; m_parseOffset < m_iniFileData.size(); m_parseOffset++) { char c = m_iniFileData[m_parseOffset]; if (c == '\r' || c == '\n') continue; break; } if (m_parseOffset >= m_iniFileData.size()) return false; size_t lineStart = m_parseOffset; // parse until end of line/file for (; m_parseOffset < m_iniFileData.size(); m_parseOffset++) { char c = m_iniFileData[m_parseOffset]; if (c == '\r' || c == '\n') break; } size_t lineEnd = m_parseOffset; lineString = { m_iniFileData.data() + lineStart, lineEnd - lineStart }; return true; } void IniParser::TrimWhitespaces(std::string_view& str) { while (!str.empty()) { char c = str[0]; if (c != ' ' && c != '\t') break; str.remove_prefix(1); } while (!str.empty()) { char c = str.back(); if (c != ' ' && c != '\t') break; str.remove_suffix(1); } } bool IniParser::parse() { sint32 lineNumber = 0; std::string_view lineView; while (ReadNextLine(lineView)) { lineNumber++; // skip whitespaces while (!lineView.empty()) { char c = lineView[0]; if (c != ' ' && c != '\t') break; lineView.remove_prefix(1); } if (lineView.empty()) continue; // cut off comments (starting with # or ;) bool isInQuote = false; for (size_t i = 0; i < lineView.size(); i++) { if (lineView[i] == '\"') isInQuote = !isInQuote; if ((lineView[i] == '#' || lineView[i] == ';') && !isInQuote) { lineView.remove_suffix(lineView.size() - i); break; } } if(lineView.empty()) continue; // handle section headers if (lineView[0] == '[') { isInQuote = false; bool endsWithBracket = false; for (size_t i = 1; i < lineView.size(); i++) { if (lineView[i] == '\"') isInQuote = !isInQuote; if (lineView[i] == ']') { lineView.remove_suffix(lineView.size() - i); lineView.remove_prefix(1); endsWithBracket = true; break; } } if (!endsWithBracket) PrintWarning(lineNumber, "Section doesn't end with a ]", lineView); StartSection(lineView, lineNumber); continue; } // otherwise try to parse it as an option in the form name = value // find and split at = character std::string_view option_name; std::string_view option_value; bool invalidName = true; for (size_t i = 0; i < lineView.size(); i++) { if (lineView[i] == '=') { option_name = lineView.substr(0, i); option_value = lineView.substr(i+1); invalidName = false; break; } } if (invalidName) { TrimWhitespaces(lineView); if (!lineView.empty()) PrintWarning(lineNumber, "Not a valid section header or name-value pair", lineView); continue; } // validate TrimWhitespaces(option_name); TrimWhitespaces(option_value); if (option_name.empty()) { PrintWarning(lineNumber, "Empty option name is not allowed", lineView); continue; } bool invalidCharacter = false; for (auto& _c : option_name) { uint8 c = (uint8)_c; if (c == ']' || c == '[') { PrintWarning(lineNumber, "Option name may not contain [ or ]", lineView); invalidCharacter = true; break; } else if (c < 32 || c > 128 || c == ' ') { PrintWarning(lineNumber, "Option name may only contain ANSI characters and no spaces", lineView); invalidCharacter = true; break; } } if(invalidCharacter) continue; // remove quotes from value if (!option_value.empty() && option_value.front() == '\"') { option_value.remove_prefix(1); if (option_value.size() >= 2 && option_value.back() == '\"') { option_value.remove_suffix(1); } else { PrintWarning(lineNumber, "Option value starts with a quote character \" but does not end with one", lineView); continue; } } if (m_sectionList.empty()) { // no current section PrintWarning(lineNumber, "Option defined without first defining a section", lineView); continue; } // convert name to lower case m_sectionList.back().m_optionPairs.emplace_back(option_name, option_value); } return true; } void IniParser::StartSection(std::string_view sectionName, size_t lineNumber) { m_sectionList.emplace_back(sectionName, lineNumber); } bool IniParser::NextSection() { if (m_currentSectionIndex == std::numeric_limits::max()) { m_currentSectionIndex = 0; return m_currentSectionIndex < m_sectionList.size(); } if (m_currentSectionIndex >= m_sectionList.size()) return false; m_currentSectionIndex++; return m_currentSectionIndex < m_sectionList.size(); } std::string_view IniParser::GetCurrentSectionName() { if (m_currentSectionIndex == std::numeric_limits::max() || m_currentSectionIndex >= m_sectionList.size()) return ""; return m_sectionList[m_currentSectionIndex].m_sectionName; } size_t IniParser::GetCurrentSectionLineNumber() { if (m_currentSectionIndex == std::numeric_limits::max() || m_currentSectionIndex >= m_sectionList.size()) return 0; return m_sectionList[m_currentSectionIndex].m_lineNumber; } std::optional IniParser::FindOption(std::string_view optionName) { if (m_currentSectionIndex == std::numeric_limits::max() || m_currentSectionIndex >= m_sectionList.size()) return std::nullopt; auto& optionPairsList = m_sectionList[m_currentSectionIndex].m_optionPairs; for (auto& itr : optionPairsList) { auto& itrOptionName = itr.first; // case insensitive ANSI string comparison if(itrOptionName.size() != optionName.size()) continue; bool isMatch = true; for (size_t i = 0; i < itrOptionName.size(); i++) { char c0 = itrOptionName[i]; char c1 = optionName[i]; if (c0 >= 'A' && c0 <= 'Z') c0 -= ('A' - 'a'); if (c1 >= 'A' && c1 <= 'Z') c1 -= ('A' - 'a'); if (c0 != c1) { isMatch = false; break; } } if (!isMatch) continue; return itr.second; } return std::nullopt; } std::span> IniParser::GetAllOptions() { if (m_currentSectionIndex == std::numeric_limits::max() || m_currentSectionIndex >= m_sectionList.size()) return {}; return m_sectionList[m_currentSectionIndex].m_optionPairs; } void IniParser::PrintWarning(int lineNumber, std::string_view msg, std::string_view lineView) { // INI logging is silenced // cemuLog_log(LogType::Force, "File: {} Line {}: {}", m_name, lineNumber, msg); } ================================================ FILE: src/util/IniParser/IniParser.h ================================================ #pragma once #include #include #include #include class IniParser { private: class IniSection { public: IniSection(std::string_view sectionName, size_t lineNumber) : m_sectionName(sectionName), m_lineNumber(lineNumber) {} std::string_view m_sectionName; size_t m_lineNumber; std::vector> m_optionPairs; }; public: IniParser(std::span iniContents, std::string_view name = {}); IniParser(std::span iniContents, std::string_view name = {}) : IniParser(std::span((char*)iniContents.data(), iniContents.size()), name) {}; // section and option iterating bool NextSection(); std::string_view GetCurrentSectionName(); size_t GetCurrentSectionLineNumber(); std::optional FindOption(std::string_view optionName); std::span> GetAllOptions(); private: // parsing bool parse(); bool ReadNextLine(std::string_view& lineString); void TrimWhitespaces(std::string_view& str); void StartSection(std::string_view sectionName, size_t lineNumber); void PrintWarning(int lineNumber, std::string_view msg, std::string_view lineView); std::vector m_iniFileData; std::string m_name; bool m_isValid{ false }; size_t m_parseOffset{ 0 }; std::vector m_sectionList; size_t m_currentSectionIndex{std::numeric_limits::max()}; }; ================================================ FILE: src/util/MemMapper/MemMapper.h ================================================ #pragma once namespace MemMapper { enum class PAGE_PERMISSION : uint32 { P_READ = (0x01), P_WRITE = (0x02), P_EXECUTE = (0x04), // combined P_NONE = 0, P_RW = (0x03), P_RWX = (0x07) }; DEFINE_ENUM_FLAG_OPERATORS(PAGE_PERMISSION); size_t GetPageSize(); void* ReserveMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags); void FreeReservation(void* baseAddr, size_t size); void* AllocateMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags, bool fromReservation = false); void FreeMemory(void* baseAddr, size_t size, bool fromReservation = false); }; ================================================ FILE: src/util/MemMapper/MemMapperUnix.cpp ================================================ #include "util/MemMapper/MemMapper.h" #include #include namespace MemMapper { const size_t sPageSize{ []() { return (size_t)getpagesize(); }() }; size_t GetPageSize() { return sPageSize; } int GetProt(PAGE_PERMISSION permissionFlags) { int p = 0; if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) p = PROT_READ | PROT_WRITE | PROT_EXEC; else if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) p = PROT_READ | PROT_WRITE; else if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) p = PROT_READ; else cemu_assert_unimplemented(); return p; } void* ReserveMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags) { return mmap(baseAddr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); } void FreeReservation(void* baseAddr, size_t size) { munmap(baseAddr, size); } void* AllocateMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags, bool fromReservation) { void* r; if(fromReservation) { uint64 page_size = sysconf(_SC_PAGESIZE); void* page = baseAddr; if ( (uint64) baseAddr % page_size != 0 ) page = (void*) ((uint64)baseAddr & ~(page_size - 1)); if( mprotect(page, size, GetProt(permissionFlags)) == 0 ) r = baseAddr; else r = nullptr; } else r = mmap(baseAddr, size, GetProt(permissionFlags), MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); return r; } void FreeMemory(void* baseAddr, size_t size, bool fromReservation) { if (fromReservation) mprotect(baseAddr, size, PROT_NONE); else munmap(baseAddr, size); } }; ================================================ FILE: src/util/MemMapper/MemMapperWin.cpp ================================================ #include "util/MemMapper/MemMapper.h" #include namespace MemMapper { const size_t sPageSize{ []() { SYSTEM_INFO si; GetSystemInfo(&si); return (size_t)si.dwPageSize; }() }; size_t GetPageSize() { return sPageSize; } DWORD GetPageProtection(PAGE_PERMISSION permissionFlags) { DWORD p = 0; if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) p = PAGE_EXECUTE_READWRITE; else if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) p = PAGE_READWRITE; else if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) p = PAGE_READONLY; else cemu_assert_unimplemented(); return p; } void* ReserveMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags) { void* r = VirtualAlloc(baseAddr, size, MEM_RESERVE, GetPageProtection(permissionFlags)); return r; } void FreeReservation(void* baseAddr, size_t size) { VirtualFree(baseAddr, size, MEM_RELEASE); } void* AllocateMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags, bool fromReservation) { void* r; if(fromReservation) r = VirtualAlloc(baseAddr, size, MEM_COMMIT, GetPageProtection(permissionFlags)); else r = VirtualAlloc(baseAddr, size, MEM_RESERVE | MEM_COMMIT, GetPageProtection(permissionFlags)); return r; } void FreeMemory(void* baseAddr, size_t size, bool fromReservation) { if(fromReservation) VirtualFree(baseAddr, size, MEM_DECOMMIT); else VirtualFree(baseAddr, size, MEM_RELEASE); } }; ================================================ FILE: src/util/ScreenSaver/ScreenSaver.h ================================================ #include "Cemu/Logging/CemuLogging.h" #include class ScreenSaver { public: static void SetInhibit(bool inhibit) { // temporary workaround because feature crashes on macOS #if BOOST_OS_MACOS return; #endif // Initialize video subsystem if necessary if (SDL_WasInit(SDL_INIT_VIDEO) == 0) { int initErr = SDL_InitSubSystem(SDL_INIT_VIDEO); if (initErr) { cemuLog_log(LogType::Force, "Could not disable screen saver (SDL video subsystem initialization error)"); } } // Toggle SDL's screen saver inhibition if (inhibit) { SDL_DisableScreenSaver(); if (SDL_IsScreenSaverEnabled() == SDL_TRUE) { cemuLog_log(LogType::Force, "Could not verify if screen saver was disabled (`SDL_IsScreenSaverEnabled()` returned SDL_TRUE)"); } } else { SDL_EnableScreenSaver(); if (SDL_IsScreenSaverEnabled() == SDL_FALSE) { cemuLog_log(LogType::Force, "Could not verify if screen saver was re-enabled (`SDL_IsScreenSaverEnabled()` returned SDL_FALSE)"); } } }; }; ================================================ FILE: src/util/SystemInfo/SystemInfo.cpp ================================================ #include "util/SystemInfo/SystemInfo.h" uint64 ProcessorTime::work() { return user + kernel; } uint64 ProcessorTime::total() { return idle + user + kernel; } double ProcessorTime::Compare(ProcessorTime &last, ProcessorTime &now) { auto dwork = now.work() - last.work(); auto dtotal = now.total() - last.total(); return (double)dwork / dtotal; } uint32 GetProcessorCount() { return std::thread::hardware_concurrency(); } void QueryProcTime(ProcessorTime &out) { uint64 now, user, kernel; QueryProcTime(now, user, kernel); out.idle = now - (user + kernel); out.kernel = kernel; out.user = user; } ================================================ FILE: src/util/SystemInfo/SystemInfo.h ================================================ #pragma once struct ProcessorTime { uint64 idle{}, kernel{}, user{}; uint64 work(); uint64 total(); static double Compare(ProcessorTime &last, ProcessorTime &now); }; uint32 GetProcessorCount(); uint64 QueryRamUsage(); void QueryProcTime(uint64 &out_now, uint64 &out_user, uint64 &out_kernel); void QueryProcTime(ProcessorTime &out); void QueryCoreTimes(uint32 count, std::vector& out); ================================================ FILE: src/util/SystemInfo/SystemInfoLinux.cpp ================================================ #include "util/SystemInfo/SystemInfo.h" #include uint64 QueryRamUsage() { static long page_size = sysconf(_SC_PAGESIZE); if (page_size == -1) { return 0; } std::ifstream file("/proc/self/statm"); if (file) { file.ignore(std::numeric_limits::max(), ' '); uint64 pages; file >> pages; return pages * page_size; } return 0; } void QueryCoreTimes(uint32 count, std::vector& out) { std::ifstream file("/proc/stat"); if (file) { file.ignore(std::numeric_limits::max(), '\n'); for (auto i = 0; i < out.size(); ++i) { uint64 user, nice, kernel, idle; file.ignore(std::numeric_limits::max(), ' '); file >> user >> nice >> kernel >> idle; file.ignore(std::numeric_limits::max(), '\n'); out[i].idle = idle; out[i].kernel = kernel; out[i].user = user + nice; } } else { for (auto i = 0; i < count; ++i) out[i] = { }; } } ================================================ FILE: src/util/SystemInfo/SystemInfoMac.cpp ================================================ #include "util/SystemInfo/SystemInfo.h" #include #include #include #include #include #include #include #include // borrowed from https://en.wikichip.org/wiki/resident_set_size#OS_X uint64 QueryRamUsage() { mach_task_basic_info info; mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT; if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &count) == KERN_SUCCESS) return info.resident_size; return 0; } // apple official documentation is non-existsent. // based on https://github.com/giampaolo/psutil/blob/master/psutil/_psutil_osx.c#L623 void QueryCoreTimes(uint32 count, std::vector& out) { // initialize default for (auto i = 0; i < out.size(); ++i) { out[i] = {}; } natural_t cpu_count; processor_info_array_t info_array; mach_msg_type_number_t info_count; kern_return_t error; mach_port_t host_port = mach_host_self(); error = host_processor_info(host_port, PROCESSOR_CPU_LOAD_INFO, &cpu_count, &info_array, &info_count); mach_port_deallocate(mach_task_self(), host_port); if (error != KERN_SUCCESS) return; processor_cpu_load_info_data_t* cpuLoad = (processor_cpu_load_info_data_t*) info_array; for (auto i = 0; i < cpu_count; ++i) { uint64 system = cpuLoad[i].cpu_ticks[CPU_STATE_SYSTEM]; uint64 user = cpuLoad[i].cpu_ticks[CPU_STATE_USER] + cpuLoad[i].cpu_ticks[CPU_STATE_NICE]; uint64 idle = cpuLoad[i].cpu_ticks[CPU_STATE_IDLE]; out[i].idle = idle; out[i].kernel = system; out[i].user = user; } int ret = vm_deallocate(mach_task_self(), (vm_address_t) info_array, info_count * sizeof(int)); if (ret != KERN_SUCCESS) cemuLog_log(LogType::Force, "vm_deallocate() failed"); } ================================================ FILE: src/util/SystemInfo/SystemInfoStub.cpp ================================================ #include "util/SystemInfo/SystemInfo.h" uint64 QueryRamUsage() { return 0; } void QueryProcTime(uint64 &out_now, uint64 &out_user, uint64 &out_kernel) { out_now = 0; out_user = 0; out_kernel = 0; } void QueryCoreTimes(uint32 count, std::vector& out) { for (auto i = 0; i < out.size(); ++i) { out[i] = { }; } } ================================================ FILE: src/util/SystemInfo/SystemInfoUnix.cpp ================================================ #include "util/SystemInfo/SystemInfo.h" #include void QueryProcTime(uint64 &out_now, uint64 &out_user, uint64 &out_kernel) { struct tms time_info; clock_t clock_now = times(&time_info); clock_t clock_user = time_info.tms_utime; clock_t clock_kernel = time_info.tms_stime; out_now = static_cast(clock_now); out_user = static_cast(clock_user); out_kernel = static_cast(clock_kernel); } ================================================ FILE: src/util/SystemInfo/SystemInfoWin.cpp ================================================ #include "util/SystemInfo/SystemInfo.h" #include #include #pragma comment(lib, "ntdll.lib") uint64 QueryRamUsage() { PROCESS_MEMORY_COUNTERS pmc{}; pmc.cb = sizeof(pmc); if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { return pmc.WorkingSetSize; } else { return 0; } } void QueryProcTime(uint64 &out_now, uint64 &out_user, uint64 &out_kernel) { FILETIME ftime, fkernel, fuser; LARGE_INTEGER now, kernel, user; GetSystemTimeAsFileTime(&ftime); now.LowPart = ftime.dwLowDateTime; now.HighPart = ftime.dwHighDateTime; if (GetProcessTimes(GetCurrentProcess(), &ftime, &ftime, &fkernel, &fuser)) { kernel.LowPart = fkernel.dwLowDateTime; kernel.HighPart = fkernel.dwHighDateTime; user.LowPart = fuser.dwLowDateTime; user.HighPart = fuser.dwHighDateTime; out_now = now.QuadPart; out_user = user.QuadPart; out_kernel = kernel.QuadPart; } else { out_now = 0; out_user = 0; out_kernel = 0; } } void QueryCoreTimes(uint32 count, std::vector& out) { std::vector sppi(count); if (NT_SUCCESS(NtQuerySystemInformation(SystemProcessorPerformanceInformation, sppi.data(), sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * count, nullptr))) { for (auto i = 0; i < out.size(); ++i) { out[i].idle = sppi[i].IdleTime.QuadPart; out[i].kernel = sppi[i].KernelTime.QuadPart; out[i].kernel -= out[i].idle; out[i].user = sppi[i].UserTime.QuadPart; } } else { for (auto i = 0; i < count; ++i) { out[i] = { }; } } } ================================================ FILE: src/util/ThreadPool/ThreadPool.h ================================================ #include class ThreadPool { public: template static void FireAndForget(TFunction&& f, TArgs&&... args) { // todo - find a way to use std::async here so we can utilize thread pooling? std::thread t(std::forward(f), std::forward(args)...); t.detach(); } }; ================================================ FILE: src/util/VirtualHeap/VirtualHeap.cpp ================================================ #include "VirtualHeap.h" VirtualBufferHeap_t* virtualBufferHeap_create(uint32 virtualHeapSize, void* baseAddr) { VirtualBufferHeap_t* bufferHeap = (VirtualBufferHeap_t*)malloc(sizeof(VirtualBufferHeap_t)); memset(bufferHeap, 0, sizeof(VirtualBufferHeap_t)); bufferHeap->firstEntry = nullptr; virtualHeapSize = (virtualHeapSize + 31)&~31; bufferHeap->virtualSize = virtualHeapSize; bufferHeap->baseAddress = baseAddr; bufferHeap->updateTrackIndex = 0; // create pool of unused entries sint32 unusedEntryPoolSize = 1024 * 16; VirtualBufferHeapEntry_t* unusedEntryPool = (VirtualBufferHeapEntry_t*)malloc(sizeof(VirtualBufferHeapEntry_t)*unusedEntryPoolSize); for (sint32 i = 0; i < unusedEntryPoolSize - 1; i++) { unusedEntryPool[i].next = unusedEntryPool + i + 1; } unusedEntryPool[unusedEntryPoolSize - 1].next = nullptr; bufferHeap->firstUnusedEntry = unusedEntryPool + 0; return bufferHeap; } VirtualBufferHeapEntry_t* virtualBufferHeap_createEntry(VirtualBufferHeap_t* bufferHeap) { VirtualBufferHeapEntry_t* newEntry = bufferHeap->firstUnusedEntry; if (newEntry == nullptr) { cemuLog_log(LogType::Force, "virtualBufferHeap_createEntry: Pool empty"); cemu_assert_suspicious(); } bufferHeap->firstUnusedEntry = newEntry->next; newEntry->previous = NULL; newEntry->next = NULL; return newEntry; } void virtualBufferHeap_releaseEntry(VirtualBufferHeap_t* bufferHeap, VirtualBufferHeapEntry_t* entry) { bufferHeap->stats.allocatedMemory -= (entry->endOffset - entry->startOffset); bufferHeap->stats.numActiveAllocs--; entry->next = bufferHeap->firstUnusedEntry; bufferHeap->firstUnusedEntry = entry; } // Allocate memory region from virtual heap. Always allocates memory at the lowest possible address VirtualBufferHeapEntry_t* virtualBufferHeap_allocate(VirtualBufferHeap_t* bufferHeap, uint32 size) { // align size size = (size + 255)&~255; // iterate already allocated entries and try to find free space between them VirtualBufferHeapEntry_t* entryItr = bufferHeap->firstEntry; if (entryItr == NULL) { // entire heap is unallocated VirtualBufferHeapEntry_t* newEntry = virtualBufferHeap_createEntry(bufferHeap); newEntry->startOffset = 0; newEntry->endOffset = size; newEntry->previous = NULL; newEntry->next = NULL; bufferHeap->firstEntry = newEntry; bufferHeap->stats.allocatedMemory += size; bufferHeap->stats.numActiveAllocs++; return newEntry; } else { uint32 currentAllocationOffset = 0; VirtualBufferHeapEntry_t* entryPrev = nullptr; while (entryItr) { if ((currentAllocationOffset + size) > entryItr->startOffset) { // space occupied currentAllocationOffset = entryItr->endOffset; currentAllocationOffset = (currentAllocationOffset + 255)&~255; // next entryPrev = entryItr; entryItr = entryItr->next; continue; } else { if ((currentAllocationOffset + size) > bufferHeap->virtualSize) return nullptr; // out of heap memory // free space found VirtualBufferHeapEntry_t* newEntry = virtualBufferHeap_createEntry(bufferHeap); newEntry->startOffset = currentAllocationOffset; newEntry->endOffset = currentAllocationOffset + size; // insert between previous entry and entryItr newEntry->previous = entryItr->previous; newEntry->next = entryItr; if (entryItr->previous) entryItr->previous->next = newEntry; else bufferHeap->firstEntry = newEntry; entryItr->previous = newEntry; bufferHeap->stats.allocatedMemory += size; bufferHeap->stats.numActiveAllocs++; return newEntry; } } // add after entryPrev if ((currentAllocationOffset + size) > bufferHeap->virtualSize) return NULL; // out of heap memory VirtualBufferHeapEntry_t* newEntry = virtualBufferHeap_createEntry(bufferHeap); newEntry->startOffset = currentAllocationOffset; newEntry->endOffset = currentAllocationOffset + size; // insert after previous entry cemu_assert_debug(entryPrev); cemu_assert_debug(entryPrev->next == nullptr); newEntry->previous = entryPrev; newEntry->next = entryPrev->next; entryPrev->next = newEntry; bufferHeap->stats.allocatedMemory += size; bufferHeap->stats.numActiveAllocs++; return newEntry; } return NULL; } void virtualBufferHeap_free(VirtualBufferHeap_t* bufferHeap, VirtualBufferHeapEntry_t* entry) { if (entry->previous == NULL) { // make the next entry the first one if (entry->next) entry->next->previous = NULL; bufferHeap->firstEntry = entry->next; } else entry->previous->next = entry->next; if (entry->next) entry->next->previous = entry->previous; // release entry virtualBufferHeap_releaseEntry(bufferHeap, entry); } void* virtualBufferHeap_allocateAddr(VirtualBufferHeap_t* bufferHeap, uint32 size) { VirtualBufferHeapEntry_t* heapEntry = virtualBufferHeap_allocate(bufferHeap, size); return ((uint8*)bufferHeap->baseAddress + heapEntry->startOffset); } void virtualBufferHeap_freeAddr(VirtualBufferHeap_t* bufferHeap, void* addr) { auto entry = bufferHeap->firstEntry; while(entry) { const auto entry_address = (uint8*)bufferHeap->baseAddress + entry->startOffset; if(entry_address == (uint8*)addr) { virtualBufferHeap_free(bufferHeap, entry); return; } entry = entry->next; } cemu_assert_suspicious(); } ================================================ FILE: src/util/VirtualHeap/VirtualHeap.h ================================================ #pragma once // virtual heap struct VirtualBufferHeapEntry_t { uint32 startOffset; uint32 endOffset; VirtualBufferHeapEntry_t* next; VirtualBufferHeapEntry_t* previous; }; struct VirtualBufferHeap_t { uint32 virtualSize; void* baseAddress; // base address for _allocateAddr and _freeAddr VirtualBufferHeapEntry_t* firstEntry; // unused entries VirtualBufferHeapEntry_t* firstUnusedEntry; // update tracking uint32 updateTrackIndex; // stats struct { uint32 numActiveAllocs; uint32 allocatedMemory; }stats; }; VirtualBufferHeap_t* virtualBufferHeap_create(uint32 virtualHeapSize, void* baseAddr = nullptr); VirtualBufferHeapEntry_t* virtualBufferHeap_allocate(VirtualBufferHeap_t* bufferHeap, uint32 size); void virtualBufferHeap_free(VirtualBufferHeap_t* bufferHeap, VirtualBufferHeapEntry_t* entry); void* virtualBufferHeap_allocateAddr(VirtualBufferHeap_t* bufferHeap, uint32 size); void virtualBufferHeap_freeAddr(VirtualBufferHeap_t* bufferHeap, void* addr); ================================================ FILE: src/util/Zir/Core/IR.cpp ================================================ #include "util/Zir/Core/IR.h" #include "util/Zir/Core/ZpIRDebug.h" #include namespace ZpIR { const char* getOpcodeName(IR::OpCode opcode) { switch (opcode) { case IR::OpCode::ADD: return "ADD"; case IR::OpCode::MOV: return "MOV"; case IR::OpCode::MUL: return "MUL"; case IR::OpCode::DIV: return "DIV"; case IR::OpCode::BITCAST: return "BITCAST"; case IR::OpCode::SWAP_ENDIAN: return "SWAP_ENDIAN"; case IR::OpCode::CONVERT_INT_TO_FLOAT: return "CONV_I2F"; case IR::OpCode::CONVERT_FLOAT_TO_INT: return "CONV_F2I"; case IR::OpCode::IMPORT_SINGLE: return "IMPORT_S"; case IR::OpCode::EXPORT: return "EXPORT"; case IR::OpCode::IMPORT: return "IMPORT"; default: cemu_assert_debug(false); return "UKN"; } return ""; } const char* getTypeName(DataType t) { switch (t) { case DataType::S64: return "s64"; case DataType::U64: return "u64"; case DataType::S32: return "s32"; case DataType::U32: return "u32"; case DataType::S16: return "s16"; case DataType::U16: return "u16"; case DataType::S8: return "s8"; case DataType::U8: return "u8"; case DataType::BOOL: return "bool"; case DataType::POINTER: return "ptr"; } return ""; } std::string DebugPrinter::getRegisterName(ZpIRBasicBlock* block, IRReg r) { std::string s; if ((uint16)r < 0x8000 && m_showPhysicalRegisters) { auto& reg = block->m_regs[(uint16)r]; if (!reg.hasAssignedPhysicalRegister()) return "UNASSIGNED"; s = m_getPhysicalRegisterNameCustom(block, reg.physicalRegister); return s; } if ((uint16)r < 0x8000 && m_getRegisterNameCustom) { return m_getRegisterNameCustom(block, r); } if ((uint16)r >= 0x8000) { auto& reg = block->m_consts[(uint16)r & 0x7FFF]; switch (reg.type) { case DataType::POINTER: return fmt::format("ptr:{}", reg.value_ptr); case DataType::U64: { if(reg.value_u64 >= 0x1000) return fmt::format("u64:0x{0:x}", reg.value_u64); return fmt::format("u64:{}", reg.value_u64); } case DataType::U32: return fmt::format("u32:{}", reg.value_u32); case DataType::S32: return fmt::format("s32:{}", reg.value_u32); case DataType::F32: return fmt::format("f32:{}", reg.value_f32); default: break; } return "ukn_const_type"; } else { auto& reg = block->m_regs[(uint16)r]; const char* regLetter = "r"; switch (reg.type) { case DataType::U64: regLetter = "uq"; // quad-word break; case DataType::U32: regLetter = "ud"; // double-word break; case DataType::U16: regLetter = "uw"; // word break; case DataType::U8: regLetter = "uc"; // char break; case DataType::S64: regLetter = "sq"; // signed quad-word break; case DataType::S32: regLetter = "sd"; // signed double-word break; case DataType::S16: regLetter = "sw"; // signed word break; case DataType::S8: regLetter = "sc"; // signed char break; case DataType::F32: regLetter = "fv"; // 32bit float break; case DataType::POINTER: regLetter = "ptr"; break; default: assert_dbg(); } if (reg.elementCount != 1) assert_dbg(); s = fmt::format("{}{}", regLetter, (uint16)r); } return s; } std::string DebugPrinter::getInstructionHRF(ZpIRBasicBlock* block, IR::__InsBase* cmd) { if (auto ins = IR::InsRR::getIfForm(cmd)) { return fmt::format("{:<10} {}, {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->rA), getRegisterName(block, ins->rB)); } else if (auto ins = IR::InsRRR::getIfForm(cmd)) { return fmt::format("{:<10} {}, {}, {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->rA), getRegisterName(block, ins->rB), getRegisterName(block, ins->rC)); } else if (auto ins = IR::InsEXPORT::getIfForm(cmd)) { if (ins->count == 4) return fmt::format("{:<10} {}, {}, {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), getRegisterName(block, ins->regArray[2]), getRegisterName(block, ins->regArray[3]), ins->exportSymbol); else if (ins->count == 3) return fmt::format("{:<10} {}, {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), getRegisterName(block, ins->regArray[2]), ins->exportSymbol); else if (ins->count == 2) return fmt::format("{:<10} {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), ins->exportSymbol); else if (ins->count == 1) return fmt::format("{:<10} {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), ins->exportSymbol); assert_dbg(); } else if (auto ins = IR::InsIMPORT::getIfForm(cmd)) { ShaderSubset::ShaderImportLocation importLocation = ins->importSymbol; std::string locDebugName = importLocation.GetDebugName(); if (ins->count == 4) return fmt::format("{:<10} {}, {}, {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), getRegisterName(block, ins->regArray[2]), getRegisterName(block, ins->regArray[3]), locDebugName); else if (ins->count == 3) return fmt::format("{:<10} {}, {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), getRegisterName(block, ins->regArray[2]), locDebugName); else if (ins->count == 2) return fmt::format("{:<10} {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), locDebugName); else if (ins->count == 1) return fmt::format("{:<10} {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), locDebugName); assert_dbg(); } else assert_dbg(); return ""; } void DebugPrinter::debugPrintBlock(ZpIRBasicBlock* block) { // print name printf("IRBasicBlock %" PRIxPTR "\n", (uintptr_t)block); // print imports printf("Imports:\n"); for(auto itr : block->m_imports) printf(" reg: %s sym:0x%llx\n", getRegisterName(block, itr.reg).c_str(), itr.name); // print exports printf("Exports:\n"); for (auto itr : block->m_exports) printf(" reg: %s sym:0x%llx\n", getRegisterName(block, itr.reg).c_str(), itr.name); // print instructions printf("Assembly:\n"); IR::__InsBase* instruction = block->m_instructionFirst; size_t i = 0; while(instruction) { std::string s = getInstructionHRF(block, instruction); printf("%04x %s\n", (unsigned int)i, s.c_str()); i++; instruction = instruction->next; } } void DebugPrinter::debugPrint(ZpIRFunction* irFunction) { printf("--- Print IR function assembly ---\n"); for (auto& itr : irFunction->m_basicBlocks) { debugPrintBlock(itr); printf("\n"); } } } ================================================ FILE: src/util/Zir/Core/IR.h ================================================ #pragma once #include using f32 = float; using f64 = double; inline void zpir_debug_assert(bool _cond) { if(!_cond) assert_dbg(); } namespace ZpIR { //enum class ZpIRCmdForm : uint8 //{ // FORM_VOID, // no-op // FORM_ZERO, // opcode without operands // FORM_1OP, // op0 // FORM_2OP, // op0, op1 // FORM_3OP, // op0, op1, op2 // FORM_4OP, // op0, op1, op2, op3 // // todo - memory read + memory store // FORM_MEM, // op0, opEA, offset, type // // todo - function calls //}; //enum class ZpIROpcodeDepr : uint8 //{ // OP_VOID, // // FORM_1OP // OP_CALL, // // FORM_2OP // OP_ASSIGN, // copy/assignment // OP_CAST_ZEROEXT, // cast type to another. If broadening then zero-extend (unsigned cast) // OP_CAST_SIGNEXT, // cast type to another. If broadening then sign-extend (signed cast) // // FORM_3OP // OP_ADD, // op0 = op1 + op2 // OP_SUB, // op0 = op1 - op2 // OP_MUL, // op0 = op1 * op2 // OP_DIV, // op0 = op1 / op2 // // memory // OP_MEM_READ, // OP_MEM_WRITE, //}; enum class DataType : uint8 { NONE = 0x00, // integer U8 = 1, S8 = 2, U16 = 3, S16 = 4, U32 = 5, S32 = 6, U64 = 7, S64 = 8, // floating-point F32 = 0x10 + 0, F64 = 0x10 + 1, // special POINTER = 0x20, // dynamic width based on pointer width of target architecture // boolean BOOL = 0x30, // can hold false/true. Size depends on target architecture }; typedef uint16 IRReg; typedef uint64 LocationSymbolName; typedef uint32 ZpIRPhysicalReg; inline bool isRegVar(IRReg r) { return r < 0x8000; }; inline bool isConstVar(IRReg r) { return r >= 0x8000; }; inline uint16 getRegIndex(IRReg r) { return (uint16)r & 0x7FFF; }; namespace IR { enum class OpCode : uint8 { UNDEF = 0, // undefined // basic opcodes MOV, // basic arithmetic opcodes ADD, // addition SUB, // subtraction MUL, // multiplication DIV, // division // conversion BITCAST, // like MOV, but allows registers of different types. No value conversion happens, raw bit copy SWAP_ENDIAN, // swap endianness CONVERT_INT_TO_FLOAT, CONVERT_FLOAT_TO_INT, // misc IMPORT_SINGLE, // import into a single IRReg. Depr: Make this like EXPORT where there is a 1-4 regs variant and one for more IMPORT, // import from external/custom resource into 1-4 IRReg EXPORT, // export 1-4 registers to external/custom resource // EXPORT_MANY // for when more than 4 registers are needed // vector EXTRACT_ELEMENT, // extract a scalar type from a vector type // some notes: We need this for texture read instructions. Where the result is a vec4 (f32x4) and this is how we can extract individual registers from that // update -> We may also instead just let the texture sample instruction specify 4 output registers }; enum class OpForm : uint8 { NONE = 0, RR = 1, RRR = 2, IMPORT_SINGLE = 3, // deprecated IMPORT = 4, EXPORT = 5, }; // instruction base class class __InsBase { public: OpCode opcode; OpForm opform; __InsBase* next; protected: __InsBase(OpCode opcode, OpForm opform) : opcode(opcode), opform(opform) { }; }; // adapted base class, instruction forms inherit from this template class __InsBaseWithForm : public __InsBase { public: //OpCode opcode; //OpForm opform; //__InsBase* next; static const OpForm getForm() { return TOpForm; } static TInstr* getIfForm(__InsBase* instructionBase) { if (instructionBase->opform != TOpForm) return nullptr; return (TInstr*)instructionBase; } protected: __InsBaseWithForm(OpCode opcode) : __InsBase(opcode, TOpForm) { }; }; class InsRR : public __InsBaseWithForm { public: InsRR(OpCode opcode, IRReg rA, IRReg rB) : __InsBaseWithForm(opcode), rA(rA), rB(rB) {}; IRReg rA; IRReg rB; }; class InsRRR : public __InsBaseWithForm { public: InsRRR(OpCode opcode, IRReg rA, IRReg rB, IRReg rC) : __InsBaseWithForm(opcode), rA(rA), rB(rB), rC(rC) {}; IRReg rA; IRReg rB; IRReg rC; }; // should we support RRI format with 32bit signed integer as a way to avoid having to generate dozens of IR const regs for stuff like shift and other logical instructions with constant rhs? // and if we do, should it be a 32bit signed integer or should the type match the instruction type? class InsEXPORT : public __InsBaseWithForm { public: InsEXPORT(LocationSymbolName exportSymbol, IRReg r) : __InsBaseWithForm(OpCode::EXPORT), exportSymbol(exportSymbol) { regArray[0] = r; count = 1; }; InsEXPORT(LocationSymbolName exportSymbol, IRReg r0, IRReg r1) : __InsBaseWithForm(OpCode::EXPORT), exportSymbol(exportSymbol) { regArray[0] = r0; regArray[1] = r1; count = 2; }; InsEXPORT(LocationSymbolName exportSymbol, IRReg r0, IRReg r1, IRReg r2) : __InsBaseWithForm(OpCode::EXPORT), exportSymbol(exportSymbol) { regArray[0] = r0; regArray[1] = r1; regArray[2] = r2; count = 3; }; InsEXPORT(LocationSymbolName exportSymbol, IRReg r0, IRReg r1, IRReg r2, IRReg r3) : __InsBaseWithForm(OpCode::EXPORT), exportSymbol(exportSymbol) { regArray[0] = r0; regArray[1] = r1; regArray[2] = r2; regArray[3] = r3; count = 4; }; InsEXPORT(LocationSymbolName exportSymbol, std::span regs) : __InsBaseWithForm(OpCode::EXPORT), exportSymbol(exportSymbol) { zpir_debug_assert(regs.size() <= 4); for(size_t i=0; i { public: InsIMPORT(LocationSymbolName importSymbol, IRReg r) : __InsBaseWithForm(OpCode::IMPORT), importSymbol(importSymbol) { regArray[0] = r; count = 1; }; InsIMPORT(LocationSymbolName importSymbol, IRReg r0, IRReg r1) : __InsBaseWithForm(OpCode::IMPORT), importSymbol(importSymbol) { regArray[0] = r0; regArray[1] = r1; count = 2; }; InsIMPORT(LocationSymbolName importSymbol, IRReg r0, IRReg r1, IRReg r2) : __InsBaseWithForm(OpCode::IMPORT), importSymbol(importSymbol) { regArray[0] = r0; regArray[1] = r1; regArray[2] = r2; count = 3; }; InsIMPORT(LocationSymbolName importSymbol, IRReg r0, IRReg r1, IRReg r2, IRReg r3) : __InsBaseWithForm(OpCode::IMPORT), importSymbol(importSymbol) { regArray[0] = r0; regArray[1] = r1; regArray[2] = r2; regArray[3] = r3; count = 4; }; InsIMPORT(LocationSymbolName importSymbol, std::span regs) : __InsBaseWithForm(OpCode::IMPORT), importSymbol(importSymbol) { zpir_debug_assert(regs.size() <= 4); for (size_t i = 0; i < regs.size(); i++) regArray[i] = regs[i]; count = (uint16)regs.size(); }; uint16 count; IRReg regArray[4]; // up to 4 registers LocationSymbolName importSymbol; }; }; // IR register definition stored in basic block struct IRRegDef { IRRegDef(DataType type, uint8 elementCount) : type(type), elementCount(elementCount) {}; DataType type; uint8 elementCount; // 1 = scalar ZpIRPhysicalReg physicalRegister{ std::numeric_limits::max()}; // todo - information about spilling location? (it depends on the architecture so we should keep this out of the core IR) bool hasAssignedPhysicalRegister() const { return physicalRegister != std::numeric_limits::max(); } void assignPhysicalRegister(ZpIRPhysicalReg physReg) { physicalRegister = physReg; } }; // IR register constant definition stored in basic block struct IRRegConstDef { IRRegConstDef() = default; // todo - support for constants with more than one element? IRRegConstDef& setU32(uint32 v) { value_u32 = v; type = DataType::U32; return *this; }; IRRegConstDef& setS32(sint32 v) { value_s32 = v; type = DataType::S32; return *this; }; IRRegConstDef& setF32(f32 v) { value_f32 = v; type = DataType::F32; return *this; }; IRRegConstDef& setPtr(void* v) { value_ptr = v; type = DataType::POINTER; return *this; }; IRRegConstDef& setRaw(uint32 v, DataType regType) { value_u32 = v; type = regType; return *this; }; DataType type{ DataType::NONE }; union { uint32 value_u32; sint32 value_s32; sint64 value_s64; uint64 value_u64; void* value_ptr; f32 value_f32; f64 value_f64; }; }; struct ZpIRBasicBlock { friend class ZpIRBuilder; struct IRBBImport { IRBBImport(IRReg reg, LocationSymbolName name) : reg(reg), name(name) {}; IRReg reg; LocationSymbolName name; }; struct IRBBExport { IRBBExport(IRReg reg, LocationSymbolName name) : reg(reg), name(name) {}; IRReg reg; LocationSymbolName name; }; IR::__InsBase* m_instructionFirst{}; IR::__InsBase* m_instructionLast{}; std::vector m_regs; std::vector m_consts; std::vector m_imports; std::vector m_exports; ZpIRBasicBlock* m_branchNotTaken{ nullptr }; // next block if branch not taken or no branch present ZpIRBasicBlock* m_branchTaken{ nullptr }; // next block if branch is taken void* m_workbuffer{}; // can be used as temporary storage for information void appendInstruction(IR::__InsBase* ins) { if (m_instructionFirst == nullptr) { m_instructionFirst = ins; m_instructionLast = ins; ins->next = nullptr; return; } m_instructionLast->next = ins; m_instructionLast = ins; ins->next = nullptr; } IRReg createRegister(DataType type, uint8 elementCount = 1) { uint32 index = (uint32)m_regs.size(); cemu_assert_debug(index < 0x8000); m_regs.emplace_back(type, elementCount); return (IRReg)index; } IRReg createConstantU32(uint32 value) { uint32 index = (uint32)m_consts.size(); cemu_assert_debug(index < 0x8000); m_consts.emplace_back().setU32(value); return (IRReg)((uint16)index + 0x8000); } IRReg createTypedConstant(uint32 value, DataType type) { uint32 index = (uint32)m_consts.size(); cemu_assert_debug(index < 0x8000); m_consts.emplace_back().setRaw(value, type); return (IRReg)((uint16)index + 0x8000); } IRReg createConstantS32(uint32 value) { uint32 index = (uint32)m_consts.size(); cemu_assert_debug(index < 0x8000); m_consts.emplace_back().setS32(value); return (IRReg)((uint16)index + 0x8000); } IRReg createConstantF32(f32 value) { uint32 index = (uint32)m_consts.size(); cemu_assert_debug(index < 0x8000); m_consts.emplace_back().setF32(value); return (IRReg)((uint16)index + 0x8000); } IRReg createConstantPointer(void* value) { uint32 index = (uint32)m_consts.size(); cemu_assert_debug(index < 0x8000); m_consts.emplace_back().setPtr(value); return (IRReg)((uint16)index + 0x8000); } void addImport(IRReg reg, LocationSymbolName importName) { m_imports.emplace_back(reg, importName); } void addExport(IRReg reg, LocationSymbolName importName) { m_exports.emplace_back(reg, importName); } void setWorkbuffer(void* buffer) { if (buffer != nullptr) { if (m_workbuffer) assert_dbg(); } m_workbuffer = buffer; } void* getWorkbuffer() { return m_workbuffer; } DataType getRegType(IRReg reg) { uint32 index = (uint32)reg; if (index >= 0x8000) { index -= 0x8000; cemu_assert_debug(index < m_consts.size()); return m_consts[index].type; } return m_regs[index].type; } IRRegConstDef* getConstant(IRReg reg) { uint32 index = (uint32)reg; if (index < 0x8000) return nullptr; index -= 0x8000; if (index >= m_consts.size()) return nullptr; return m_consts.data() + index; } std::optional getConstantS32(IRReg reg) { uint32 index = (uint32)reg; if (index < 0x8000) return std::nullopt; index -= 0x8000; if (index >= m_consts.size()) return std::nullopt; if (m_consts[index].type == DataType::U32) return (sint32)m_consts[index].value_u32; else if (m_consts[index].type == DataType::POINTER) assert_dbg(); else if (m_consts[index].type == DataType::U64) { if (m_consts[index].value_u64 >= 0x80000000ull) assert_dbg(); return (sint32)m_consts[index].value_u64; } else assert_dbg(); return std::nullopt; } std::optional getConstantU64(IRReg reg) { auto constReg = getConstant(reg); if (!constReg) return std::nullopt; if (constReg->type == DataType::U64) return constReg->value_u64; else assert_dbg(); return std::nullopt; } }; struct ZpIRFunction { std::vector m_basicBlocks; std::vector m_entryBlocks; std::vector m_exitBlocks; struct { bool registersAllocated{false}; }state; }; // helpers for shader code namespace ShaderSubset { class ShaderImportLocation { enum LOC_TYPE : uint8 { LOC_TYPE_UNIFORM_REGISTER = 1, LOC_TYPE_UNIFORM_BUFFER = 2, LOC_TYPE_ATTRIBUTE = 3, }; public: ShaderImportLocation() = default; ShaderImportLocation(LocationSymbolName loc) { uint64 v = (uint64)loc; m_locType = (LOC_TYPE)(v >> 56); m_indexA = (uint16)(v >> 0); m_indexB = (uint16)(v >> 16); } ShaderImportLocation& SetUniformRegister(uint16 index) { m_locType = LOC_TYPE_UNIFORM_REGISTER; m_indexA = index; m_indexB = 0; return *this; } ShaderImportLocation& SetVertexAttribute(uint16 attributeIndex, uint16 channelIndex) { m_locType = LOC_TYPE_ATTRIBUTE; m_indexA = attributeIndex; m_indexB = channelIndex; return *this; } bool IsUniformRegister() const { return m_locType == LOC_TYPE_UNIFORM_REGISTER; } bool IsVertexAttribute() const { return m_locType == LOC_TYPE_ATTRIBUTE; } void GetUniformRegister(uint16& index) { index = m_indexA; } void GetVertexAttribute(uint16& attributeIndex, uint16& channelIndex) const { attributeIndex = m_indexA; channelIndex = m_indexB; } operator LocationSymbolName() const { uint64 v = 0; v |= ((uint64)m_locType << 56); v |= ((uint64)m_indexA << 0); v |= ((uint64)m_indexB << 16); return (LocationSymbolName)v; } std::string GetDebugName() { const char elementTable[] = { 'x' , 'y', 'z', 'w' }; if (m_locType == LOC_TYPE_UNIFORM_REGISTER) return fmt::format("UniformReg[{0}].{1}", m_indexA >> 2, elementTable[m_indexA & 3]); if (m_locType == LOC_TYPE_ATTRIBUTE) return fmt::format("VertexAttribute[{0}].{1}", m_indexA, elementTable[m_indexB]); return "Unknown"; } private: LOC_TYPE m_locType{}; uint16 m_indexA{}; uint16 m_indexB{}; //LocationSymbolName m_symbolName{}; static_assert(sizeof(LocationSymbolName) == 8); }; class ShaderExportLocation { enum LOC_TYPE : uint8 { LOC_TYPE_POSITION = 1, LOC_TYPE_OUTPUT = 2, }; public: ShaderExportLocation() = default; ShaderExportLocation(LocationSymbolName loc) { uint64 v = (uint64)loc; m_locType = (LOC_TYPE)(v >> 56); m_indexA = (uint16)(v >> 0); m_indexB = (uint16)(v >> 16); } ShaderExportLocation& SetPosition() { m_locType = LOC_TYPE_POSITION; m_indexA = 0; m_indexB = 0; return *this; } ShaderExportLocation& SetOutputAttribute(uint16 attributeIndex) // todo - channel mask? { m_locType = LOC_TYPE_OUTPUT; m_indexA = attributeIndex; m_indexB = 0; return *this; } bool IsPosition() const { return m_locType == LOC_TYPE_POSITION; } bool IsOutputAttribute() const { return m_locType == LOC_TYPE_OUTPUT; } void GetOutputAttribute(uint16& attributeIndex) const { attributeIndex = m_indexA; } operator LocationSymbolName() const { uint64 v = 0; v |= ((uint64)m_locType << 56); v |= ((uint64)m_indexA << 0); v |= ((uint64)m_indexB << 16); return (LocationSymbolName)v; } std::string GetDebugName() { const char elementTable[] = { 'x' , 'y', 'z', 'w' }; //if (m_locType == LOC_TYPE_UNIFORM_REGISTER) // return fmt::format("UniformReg[{0}].{1}", m_indexA >> 2, elementTable[m_indexA & 3]); //if (m_locType == LOC_TYPE_ATTRIBUTE) // return fmt::format("VertexAttribute[{0}].{1}", m_indexA, elementTable[m_indexB]); return "Unknown"; } private: LOC_TYPE m_locType{}; uint16 m_indexA{}; uint16 m_indexB{}; static_assert(sizeof(LocationSymbolName) == 8); }; }; } ================================================ FILE: src/util/Zir/Core/ZirUtility.h ================================================ #pragma once #include "util/Zir/Core/IR.h" namespace ZpIR { struct ZpIRCmdUtil { template static void forEachAccessedReg(ZpIRBasicBlock& block, IR::__InsBase* instruction, TFuncRegRead funcRegRead, TFuncRegWrite funcRegWrite) { if (auto ins = IR::InsRR::getIfForm(instruction)) { switch (ins->opcode) { case ZpIR::IR::OpCode::MOV: case ZpIR::IR::OpCode::BITCAST: case ZpIR::IR::OpCode::SWAP_ENDIAN: case ZpIR::IR::OpCode::CONVERT_FLOAT_TO_INT: case ZpIR::IR::OpCode::CONVERT_INT_TO_FLOAT: if (isRegVar(ins->rB)) funcRegRead(ins->rB); cemu_assert_debug(isRegVar(ins->rA)); funcRegWrite(ins->rA); break; default: cemu_assert_unimplemented(); } } else if (auto ins = IR::InsRRR::getIfForm(instruction)) { switch (ins->opcode) { case ZpIR::IR::OpCode::ADD: case ZpIR::IR::OpCode::SUB: case ZpIR::IR::OpCode::MUL: case ZpIR::IR::OpCode::DIV: if (isRegVar(ins->rB)) funcRegRead(ins->rB); if (isRegVar(ins->rC)) funcRegRead(ins->rC); cemu_assert_debug(isRegVar(ins->rA)); funcRegWrite(ins->rA); break; default: cemu_assert_unimplemented(); } } else if (auto ins = IR::InsIMPORT::getIfForm(instruction)) { for (uint16 i = 0; i < ins->count; i++) { cemu_assert_debug(isRegVar(ins->regArray[i])); funcRegWrite(ins->regArray[i]); } } else if (auto ins = IR::InsEXPORT::getIfForm(instruction)) { for (uint16 i = 0; i < ins->count; i++) { if (isRegVar(ins->regArray[i])) funcRegRead(ins->regArray[i]); } } else { cemu_assert_unimplemented(); } } static void replaceRegisters(IR::__InsBase& ins, std::unordered_map& translationTable) { cemu_assert_unimplemented(); } }; } ================================================ FILE: src/util/Zir/Core/ZpIRBuilder.h ================================================ #pragma once #include "util/Zir/Core/IR.h" namespace ZpIR { // helper class for building a single basic block class BasicBlockBuilder { public: BasicBlockBuilder(ZpIRBasicBlock* basicBlock) : m_basicBlock(basicBlock) {}; IRReg createReg(DataType type, uint8 elementCount = 1) { return m_basicBlock->createRegister(type, elementCount); } IRReg createReg(IRReg& r, DataType type, uint8 elementCount = 1) { r = m_basicBlock->createRegister(type, elementCount); return r; } // append a single instruction at the end void append(IR::__InsBase* ins) { assert_dbg(); } void emit_EXPORT(LocationSymbolName exportSymbolName, IRReg r0) { m_basicBlock->appendInstruction(new IR::InsEXPORT(exportSymbolName, r0)); } void emit_EXPORT(LocationSymbolName exportSymbolName, std::span regs) { m_basicBlock->appendInstruction(new IR::InsEXPORT(exportSymbolName, regs)); } void emit_IMPORT(LocationSymbolName importSymbolName, IRReg r0) { m_basicBlock->appendInstruction(new IR::InsIMPORT(importSymbolName, r0)); } // result is rA, operand is rB // for some opcodes both can be operands void emit_RR(IR::OpCode opcode, IRReg rA, IRReg rB) { m_basicBlock->appendInstruction(new IR::InsRR(opcode, rA, rB)); } IRReg emit_RR(IR::OpCode opcode, DataType resultType, IRReg rB) { IRReg resultReg = m_basicBlock->createRegister(resultType); emit_RR(opcode, resultReg, rB); return resultReg; } // result is rA, operands are rB and rC // for some opcodes all three can be operands void emit_RRR(IR::OpCode opcode, IRReg rA, IRReg rB, IRReg rC) { m_basicBlock->appendInstruction(new IR::InsRRR(opcode, rA, rB, rC)); } IRReg emit_RRR(IR::OpCode opcode, DataType resultType, IRReg rB, IRReg rC) { IRReg resultReg = m_basicBlock->createRegister(resultType); m_basicBlock->appendInstruction(new IR::InsRRR(opcode, resultReg, rB, rC)); return resultReg; } void emit(IR::__InsBase* ins) { m_basicBlock->appendInstruction(ins); } // constant var creation IRReg createConstU32(uint32 v) { return m_basicBlock->createConstantU32(v); } IRReg createTypedConst(uint32 v, DataType type) { return m_basicBlock->createTypedConstant(v, type); } IRReg createConstS32(uint32 v) { return m_basicBlock->createConstantS32(v); } IRReg createConstF32(f32 v) { return m_basicBlock->createConstantF32(v); } IRReg createConstPointer(void* v) { return m_basicBlock->createConstantPointer(v); } // use templates to compact other types? DataType getRegType(IRReg reg) { return m_basicBlock->getRegType(reg); } void addImport(IRReg reg, LocationSymbolName importSymbolName) { m_basicBlock->addImport(reg, importSymbolName); } private: ZpIRBasicBlock* m_basicBlock; }; // helper class for constructing multiple basic blocks with control flow class ZpIRBuilder { public: typedef uint64 BlockBranchTarget; static const inline BlockBranchTarget INVALID_BLOCK_NAME = 0xFFFFFFFFFFFFFFFFull; struct BasicBlockWorkbuffer { BlockBranchTarget name{ INVALID_BLOCK_NAME }; BlockBranchTarget targetBranchNotTaken{ INVALID_BLOCK_NAME }; BlockBranchTarget targetBranchTaken{ INVALID_BLOCK_NAME }; }; void beginBlock(BlockBranchTarget name) { m_currentBasicBlock = new ZpIRBasicBlock(); BasicBlockWorkbuffer* wb = new BasicBlockWorkbuffer(); m_currentBasicBlock->setWorkbuffer(wb); wb->name = name; m_blocks.emplace_back(m_currentBasicBlock); m_blocksByName.emplace(name, m_currentBasicBlock); } ZpIRBasicBlock* endBlock() { ZpIRBasicBlock* block = m_currentBasicBlock; m_currentBasicBlock = nullptr; BasicBlockWorkbuffer* wb = (BasicBlockWorkbuffer*)block->getWorkbuffer(); wb->targetBranchNotTaken = m_targetBranchNotTaken; wb->targetBranchTaken = m_targetBranchTaken; m_targetBranchNotTaken = INVALID_BLOCK_NAME; m_targetBranchTaken = INVALID_BLOCK_NAME; return block; } ZpIRFunction* finish() { if (m_currentBasicBlock) assert_dbg(); // create function ZpIRFunction* func = new ZpIRFunction(); // link all blocks // and also collect a list of entry and exit nodes for (auto& itr : m_blocks) { BasicBlockWorkbuffer* wb = (BasicBlockWorkbuffer*)itr->getWorkbuffer(); if (wb->targetBranchNotTaken != INVALID_BLOCK_NAME) { auto target = m_blocksByName.find(wb->targetBranchNotTaken); if (target == m_blocksByName.end()) { assert_dbg(); } itr->m_branchNotTaken = target->second; } if (wb->targetBranchTaken != INVALID_BLOCK_NAME) { auto target = m_blocksByName.find(wb->targetBranchTaken); if (target == m_blocksByName.end()) { assert_dbg(); } itr->m_branchTaken = target->second; } delete wb; itr->setWorkbuffer(nullptr); func->m_basicBlocks.emplace_back(itr); // todo - track entry and exit blocks (set block flags for entry/exit during block gen) } return func; } IRReg createBlockRegister(DataType type, uint8 elementCount = 1) { return m_currentBasicBlock->createRegister(type, elementCount); } IRReg createConstU32(uint32 v) { return m_currentBasicBlock->createConstantU32(v); } IRReg createConstPointer(void* v) { return m_currentBasicBlock->createConstantPointer(v); } IRReg createConstPointerV(size_t v) { return m_currentBasicBlock->createConstantPointer((void*)v); } void addImport(IRReg reg, LocationSymbolName importName) { m_currentBasicBlock->addImport(reg, importName); } void addExport(IRReg reg, LocationSymbolName importName) { m_currentBasicBlock->addExport(reg, importName); } void setBlockTargetBranchTaken(BlockBranchTarget target) { if (m_targetBranchTaken != INVALID_BLOCK_NAME) assert_dbg(); m_targetBranchTaken = target; } void setBlockTargetBranchNotTaken(BlockBranchTarget target) { if (m_targetBranchNotTaken != INVALID_BLOCK_NAME) assert_dbg(); m_targetBranchNotTaken = target; } private: ZpIRBasicBlock* m_currentBasicBlock{}; std::vector m_blocks; std::unordered_map m_blocksByName; BlockBranchTarget m_targetBranchNotTaken{ INVALID_BLOCK_NAME }; BlockBranchTarget m_targetBranchTaken{ INVALID_BLOCK_NAME }; }; } ================================================ FILE: src/util/Zir/Core/ZpIRDebug.h ================================================ #pragma once #include "util/Zir/Core/IR.h" namespace ZpIR { class DebugPrinter { public: void debugPrint(ZpIRFunction* irFunction); void setShowPhysicalRegisters(bool showPhys) { m_showPhysicalRegisters = showPhys; } void setVirtualRegisterNameSource(std::string(*getRegisterNameCustom)(ZpIRBasicBlock* block, IRReg r)) { m_getRegisterNameCustom = getRegisterNameCustom; } void setPhysicalRegisterNameSource(std::string(*getRegisterNameCustom)(ZpIRBasicBlock* block, ZpIRPhysicalReg r)) { m_getPhysicalRegisterNameCustom = getRegisterNameCustom; } private: std::string getRegisterName(ZpIRBasicBlock* block, IRReg r); std::string getInstructionHRF(ZpIRBasicBlock* block, IR::__InsBase* cmd); void debugPrintBlock(ZpIRBasicBlock* block); std::string(*m_getRegisterNameCustom)(ZpIRBasicBlock* block, IRReg r) { nullptr }; std::string(*m_getPhysicalRegisterNameCustom)(ZpIRBasicBlock* block, ZpIRPhysicalReg r) { nullptr }; bool m_showPhysicalRegisters{}; // show global/physical register mapping instead of local IRReg indices }; } ================================================ FILE: src/util/Zir/Core/ZpIRPasses.h ================================================ #pragma once #include "util/Zir/Core/IR.h" namespace ZirPass { class ZpIRPass { public: ZpIRPass(ZpIR::ZpIRFunction* irFunction) : m_irFunction(irFunction) { }; virtual void applyPass() = 0; protected: ZpIR::ZpIRFunction* m_irFunction; }; struct RALivenessRange_t { RALivenessRange_t(struct RABlock_t* block, ZpIR::IRReg irReg, sint32 start, sint32 end, ZpIR::DataType irDataType); ~RALivenessRange_t(); enum class SOURCE { NONE, INSTRUCTION, // instruction initializes value PREVIOUS_BLOCK, // imported from previous block(s) PREVIOUS_RANGE, // from previous range within same block }; enum class LOCATION { UNASSIGNED, PHYSICAL_REGISTER, SPILLED, }; void setStart(sint32 startIndex); void setEnd(sint32 endIndex); void addSourceFromPreviousBlock(RALivenessRange_t* source) { if (m_source != SOURCE::NONE && m_source != SOURCE::PREVIOUS_BLOCK) assert_dbg(); m_source = SOURCE::PREVIOUS_BLOCK; m_sourceRanges.emplace_back(source); source->m_destinationRanges.emplace_back(this); } void addSourceFromSameBlock(RALivenessRange_t* source) { if (m_source != SOURCE::NONE) assert_dbg(); m_source = SOURCE::PREVIOUS_RANGE; m_sourceRanges.emplace_back(source); source->m_destinationRanges.emplace_back(this); } bool isOverlapping(sint32 start, sint32 end) const { return m_startIndex < end && m_endIndex >= start; } bool isOverlapping(RALivenessRange_t* range) const { return isOverlapping(range->m_startIndex, range->m_endIndex); } void assignPhysicalRegister(ZpIR::ZpIRPhysicalReg physReg); RABlock_t* m_block; ZpIR::IRReg m_irReg; ZpIR::DataType m_irDataType; sint32 m_startIndex{ -1 }; sint32 m_endIndex{ -1 }; // inclusive //std::vector m_reservedPhysRegisters; // unavailable physical registers std::vector m_overlappingRanges; // state / assigned location LOCATION m_location{ LOCATION::UNASSIGNED }; sint32 m_physicalRegister; // source SOURCE m_source{ SOURCE::NONE }; std::vector m_sourceRanges; // destination //RALivenessRange_t* m_destinationRange{ nullptr }; std::vector m_destinationRanges; }; struct RABlock_t { std::unordered_map livenessRanges; struct Compare { bool operator()(const RALivenessRange_t* lhs, const RALivenessRange_t* rhs) const /* noexcept */ { // order for unassignedRanges // aka order in which ranges should be assigned physical registers return lhs->m_startIndex < rhs->m_startIndex; } }; public: std::multiset unassignedRanges; }; class RARegular : public ZpIRPass { public: RARegular(ZpIR::ZpIRFunction* irFunction) : ZpIRPass(irFunction) {}; void applyPass() { prepareRABlocks(); generateLivenessRanges(); assignPhysicalRegisters(); assert_dbg(); // todo -> rewrite doesnt need to be separate any longer since we store a separate physical register index now (in IRReg) rewrite(); m_irFunction->state.registersAllocated = true; } private: void prepareRABlocks(); void generateLivenessRanges(); void assignPhysicalRegisters(); void rewrite(); void assignPhysicalRegistersForBlock(RABlock_t& raBlock); void rewriteBlock(ZpIR::ZpIRBasicBlock& basicBlock, RABlock_t& raBlock); std::span extGetSuitablePhysicalRegisters(ZpIR::DataType dataType) { const sint32 OFFSET_U64 = 0; const sint32 OFFSET_U32 = 16; static sint32 _regCandidatesU64[] = { OFFSET_U64 + 0, OFFSET_U64 + 1, OFFSET_U64 + 2, OFFSET_U64 + 3, OFFSET_U64 + 4, OFFSET_U64 + 5, OFFSET_U64 + 6, OFFSET_U64 + 7, OFFSET_U64 + 8, OFFSET_U64 + 9, OFFSET_U64 + 10, OFFSET_U64 + 11, OFFSET_U64 + 12, OFFSET_U64 + 13, OFFSET_U64 + 14, OFFSET_U64 + 15 }; static sint32 _regCandidatesU32[] = { OFFSET_U32 + 0, OFFSET_U32 + 1, OFFSET_U32 + 2, OFFSET_U32 + 3, OFFSET_U32 + 4, OFFSET_U32 + 5, OFFSET_U32 + 6, OFFSET_U32 + 7, OFFSET_U32 + 8, OFFSET_U32 + 9, OFFSET_U32 + 10, OFFSET_U32 + 11, OFFSET_U32 + 12, OFFSET_U32 + 13, OFFSET_U32 + 14, OFFSET_U32 + 15 }; if (dataType == ZpIR::DataType::POINTER || dataType == ZpIR::DataType::U64) return _regCandidatesU64; if (dataType == ZpIR::DataType::U32) return _regCandidatesU32; //if (dataType != ZpIRDataType::POINTER) //{ //} assert_dbg(); return _regCandidatesU32; } void extFilterPhysicalRegisters(std::vector& physRegCandidates, ZpIR::ZpIRPhysicalReg registerToFilter) { // todo - this is quite complex on x86 where registers overlap (e.g. RAX and EAX/AL/AH/AX) // so registerToFilter can translate to multiple filtered values // but for now we use a simplified placeholder implementation if (registerToFilter >= 0 && registerToFilter < 16) { physRegCandidates.erase(std::remove(physRegCandidates.begin(), physRegCandidates.end(), (sint32)registerToFilter), physRegCandidates.end()); physRegCandidates.erase(std::remove(physRegCandidates.begin(), physRegCandidates.end(), (sint32)registerToFilter + 16), physRegCandidates.end()); } else if (registerToFilter >= 16 && registerToFilter < 32) { physRegCandidates.erase(std::remove(physRegCandidates.begin(), physRegCandidates.end(), (sint32)registerToFilter), physRegCandidates.end()); physRegCandidates.erase(std::remove(physRegCandidates.begin(), physRegCandidates.end(), (sint32)registerToFilter - 16), physRegCandidates.end()); } else assert_dbg(); } ZpIR::ZpIRPhysicalReg extPickPreferredRegister(std::vector& physRegCandidates) { if (physRegCandidates.empty()) assert_dbg(); return physRegCandidates[0]; } void debugPrint(RABlock_t& raBlock) { std::multiset sortedRanges; for (auto& itr : raBlock.livenessRanges) sortedRanges.emplace(itr.second); for (auto& itr : sortedRanges) { printf("%04x - %04x reg %04d: ", (uint32)(uint16)itr->m_startIndex, (uint32)(uint16)itr->m_endIndex, (uint32)itr->m_irReg); if (itr->m_location == RALivenessRange_t::LOCATION::PHYSICAL_REGISTER) printf("PHYS_REG %d", (int)itr->m_physicalRegister); else if (itr->m_location == RALivenessRange_t::LOCATION::UNASSIGNED) printf("UNASSIGNED"); else assert_dbg(); printf("\n"); } } // remove all physical registers from physRegCandidates which are already reserved by any of the overlapping ranges void filterCandidates(std::vector& physRegCandidates, RALivenessRange_t* range) { for (auto& itr : range->m_overlappingRanges) { if (itr->m_location != RALivenessRange_t::LOCATION::PHYSICAL_REGISTER) continue; extFilterPhysicalRegisters(physRegCandidates, itr->m_physicalRegister); } } std::vector m_raBlockArray; }; class RegisterAllocatorForGLSL : public ZpIRPass { enum class PHYS_REG_TYPE : uint8 { U32 = 0, S32 = 1, F32 = 2 }; public: RegisterAllocatorForGLSL(ZpIR::ZpIRFunction* irFunction) : ZpIRPass(irFunction) {}; void applyPass() { assignPhysicalRegisters(); m_irFunction->state.registersAllocated = true; } static bool IsPhysRegTypeU32(ZpIR::ZpIRPhysicalReg physReg) { return ((uint32)physReg >> 30) == (uint32)PHYS_REG_TYPE::U32; } static bool IsPhysRegTypeS32(ZpIR::ZpIRPhysicalReg physReg) { return ((uint32)physReg >> 30) == (uint32)PHYS_REG_TYPE::S32; } static bool IsPhysRegTypeF32(ZpIR::ZpIRPhysicalReg physReg) { return ((uint32)physReg >> 30) == (uint32)PHYS_REG_TYPE::F32; } static uint32 GetPhysRegIndex(ZpIR::ZpIRPhysicalReg physReg) { return (uint32)physReg & 0x3FFFFFFF; } static std::string DebugPrintHelper_getPhysRegisterName(ZpIR::ZpIRBasicBlock* block, ZpIR::ZpIRPhysicalReg r); private: void assignPhysicalRegisters(); void assignPhysicalRegistersForBlock(ZpIR::ZpIRBasicBlock* basicBlock); uint32 m_physicalRegisterCounterU32{}; uint32 m_physicalRegisterCounterS32{}; uint32 m_physicalRegisterCounterF32{}; ZpIR::ZpIRPhysicalReg MakePhysReg(PHYS_REG_TYPE regType, uint32 index) { uint32 v = (uint32)regType << 30; v |= index; return (ZpIR::ZpIRPhysicalReg)v; } }; }; ================================================ FILE: src/util/Zir/Core/ZpIRScheduler.h ================================================ #pragma once #include "util/Zir/Core/IR.h" #include "util/Zir/Core/ZirUtility.h" ================================================ FILE: src/util/Zir/EmitterGLSL/ZpIREmitGLSL.cpp ================================================ #include "util/Zir/Core/IR.h" #include "util/Zir/Core/ZirUtility.h" #include "util/Zir/Core/ZpIRPasses.h" #include "util/Zir/EmitterGLSL/ZpIREmitGLSL.h" #include "util/Zir/Core/ZpIRScheduler.h" // string buffer helper class which keeps buffer space at the front and end, allow fast prepend and append class DualStringBuffer { static constexpr size_t N = 1024; public: DualStringBuffer() : m_offsetBegin(N / 2), m_offsetEnd(N / 2) { } ~DualStringBuffer() = default; static_assert(sizeof(char) == sizeof(uint8)); void reset() { m_offsetBegin = N / 2; m_offsetEnd = m_offsetBegin; } void append(std::string_view strView) { cemu_assert_debug((m_offsetEnd + strView.size()) <= N); std::memcpy(m_strBuffer + m_offsetEnd, strView.data(), strView.size()); m_offsetEnd += (uint32)strView.size(); } template void appendFmt(const char* format_str, Args... args) { char* buf = (char*)(m_strBuffer + m_offsetEnd); char* r = fmt::format_to(buf, fmt::runtime(format_str), std::forward(args)...); cemu_assert_debug(r <= (char*)(m_strBuffer + N)); m_offsetEnd += (uint32)(r - buf); } void prepend(std::string_view strView) { assert_dbg(); } size_t size() const { return m_offsetEnd - m_offsetBegin; } operator std::string_view() { return std::basic_string_view((char*)(m_strBuffer + m_offsetBegin), m_offsetEnd - m_offsetBegin); } private: //void resizeBuffer(uint32 spaceRequiredFront, uint32 spaceRequiredBack) //{ // uint32 newTotalSize = spaceRequiredFront + size() + spaceRequiredBack; // // round to next multiple of 32 and add extra buffer // newTotalSize = (newTotalSize + 31) & ~31; // newTotalSize += (newTotalSize / 4); // // //} //uint8* m_bufferPtr{ nullptr }; //size_t m_bufferSize{ 0 }; //std::vector m_buffer; uint32 m_offsetBegin; uint32 m_offsetEnd; uint8 m_strBuffer[N]; }; namespace ZirEmitter { static const char g_idx_to_element[] = { 'x' , 'y', 'z', 'w'}; void GLSL::Emit(ZpIR::ZpIRFunction* irFunction, StringBuf* output) { m_irFunction = irFunction; m_glslSource = output; cemu_assert_debug(m_irFunction->m_entryBlocks.size() == 1); cemu_assert_debug(m_irFunction->m_basicBlocks.size() == 1); // other sizes are todo m_glslSource->add("void main()\r\n{\r\n"); GenerateBasicBlockCode(*m_irFunction->m_entryBlocks[0]); m_glslSource->add("}\r\n"); } void GLSL::GenerateBasicBlockCode(ZpIR::ZpIRBasicBlock& basicBlock) { // init context #ifdef CEMU_DEBUG_ASSERT for (auto& itr : m_blockContext.regInlinedExpression) { cemu_assert_debug(itr == nullptr); // leaked buffer } #endif m_blockContext.regReadTracking.clear(); m_blockContext.regReadTracking.resize(basicBlock.m_regs.size()); m_blockContext.regInlinedExpression.resize(basicBlock.m_regs.size()); m_blockContext.currentBasicBlock = &basicBlock; // we first do an analysis pass in which we determine the read count for each register // every register which is only consumed once can be directly inlined instead of storing and referencing it via a variable ZpIR::IR::__InsBase* instruction = basicBlock.m_instructionFirst; while (instruction) { ZpIR::ZpIRCmdUtil::forEachAccessedReg(basicBlock, instruction, [this](ZpIR::IRReg readReg) { if (readReg >= 0x8000) assert_dbg(); // read access auto& entry = m_blockContext.regReadTracking.at(readReg); if (entry < 255) entry++; }, [](ZpIR::IRReg writtenReg) { }); instruction = instruction->next; } // emit GLSL for this block instruction = basicBlock.m_instructionFirst; while (instruction) { if (auto ins = ZpIR::IR::InsRR::getIfForm(instruction)) HandleInstruction(ins); else if (auto ins = ZpIR::IR::InsRRR::getIfForm(instruction)) HandleInstruction(ins); else if (auto ins = ZpIR::IR::InsIMPORT::getIfForm(instruction)) HandleInstruction(ins); else if (auto ins = ZpIR::IR::InsEXPORT::getIfForm(instruction)) HandleInstruction(ins); else { assert_dbg(); } instruction = instruction->next; } } void GLSL::HandleInstruction(ZpIR::IR::InsRR* ins) { DualStringBuffer* expressionBuf = GetStringBuffer(); bool forceNoInline = false; switch (ins->opcode) { case ZpIR::IR::OpCode::BITCAST: { auto srcType = m_blockContext.currentBasicBlock->getRegType(ins->rB); auto dstType = m_blockContext.currentBasicBlock->getRegType(ins->rA); if (srcType == ZpIR::DataType::U32 && dstType == ZpIR::DataType::F32) expressionBuf->append("uintBitsToFloat("); else if (srcType == ZpIR::DataType::S32 && dstType == ZpIR::DataType::F32) expressionBuf->append("intBitsToFloat("); else if (srcType == ZpIR::DataType::F32 && dstType == ZpIR::DataType::U32) expressionBuf->append("floatBitsToUint("); else if (srcType == ZpIR::DataType::F32 && dstType == ZpIR::DataType::S32) expressionBuf->append("floatBitsToInt("); else assert_dbg(); appendSourceString(expressionBuf, ins->rB); expressionBuf->append(")"); break; } case ZpIR::IR::OpCode::SWAP_ENDIAN: { auto srcType = m_blockContext.currentBasicBlock->getRegType(ins->rB); auto dstType = m_blockContext.currentBasicBlock->getRegType(ins->rA); cemu_assert_debug(srcType == dstType); // todo - should we store expressionBuf in a temporary variable? We reference it multiple times and reducing complexity would be good if (srcType == ZpIR::DataType::U32) { expressionBuf->append("((("); appendSourceString(expressionBuf, ins->rB); expressionBuf->append(")>>24)"); expressionBuf->append("|"); expressionBuf->append("((("); appendSourceString(expressionBuf, ins->rB); expressionBuf->append(")>>8)&0xFF00)"); expressionBuf->append("|"); expressionBuf->append("((("); appendSourceString(expressionBuf, ins->rB); expressionBuf->append(")<<8)&0xFF0000)"); expressionBuf->append("|"); expressionBuf->append("(("); appendSourceString(expressionBuf, ins->rB); expressionBuf->append(")<<24))"); // (v>>24)|((v>>8)&0xFF00)|((v<<8)&0xFF0000)|((v<<24)) } else assert_dbg(); forceNoInline = true; // avoid inlining endian-swapping, since it would add too much complexity to expressions break; } case ZpIR::IR::OpCode::MOV: appendSourceString(expressionBuf, ins->rB); break; case ZpIR::IR::OpCode::CONVERT_FLOAT_TO_INT: { auto srcType = m_blockContext.currentBasicBlock->getRegType(ins->rB); auto dstType = m_blockContext.currentBasicBlock->getRegType(ins->rA); cemu_assert_debug(srcType == ZpIR::DataType::F32); cemu_assert_debug(dstType == ZpIR::DataType::S32 || dstType == ZpIR::DataType::U32); if(dstType == ZpIR::DataType::U32) expressionBuf->append("uint("); else expressionBuf->append("int("); appendSourceString(expressionBuf, ins->rB); expressionBuf->append(")"); break; } case ZpIR::IR::OpCode::CONVERT_INT_TO_FLOAT: { auto srcType = m_blockContext.currentBasicBlock->getRegType(ins->rB); auto dstType = m_blockContext.currentBasicBlock->getRegType(ins->rA); cemu_assert_debug(srcType == ZpIR::DataType::S32 || srcType == ZpIR::DataType::U32); cemu_assert_debug(dstType == ZpIR::DataType::F32); expressionBuf->append("float("); appendSourceString(expressionBuf, ins->rB); expressionBuf->append(")"); break; } default: assert_dbg(); } AssignResult(ins->rA, expressionBuf, forceNoInline); } void GLSL::HandleInstruction(ZpIR::IR::InsRRR* ins) { DualStringBuffer* expressionBuf = GetStringBuffer(); switch (ins->opcode) { case ZpIR::IR::OpCode::ADD: { appendSourceString(expressionBuf, ins->rB); expressionBuf->append(" + "); appendSourceString(expressionBuf, ins->rC); break; } case ZpIR::IR::OpCode::MUL: { appendSourceString(expressionBuf, ins->rB); expressionBuf->append(" * "); appendSourceString(expressionBuf, ins->rC); break; } case ZpIR::IR::OpCode::DIV: { appendSourceString(expressionBuf, ins->rB); expressionBuf->append(" / "); appendSourceString(expressionBuf, ins->rC); break; } default: assert_dbg(); } AssignResult(ins->rA, expressionBuf); } void GLSL::HandleInstruction(ZpIR::IR::InsIMPORT* ins) { ZpIR::ShaderSubset::ShaderImportLocation loc(ins->importSymbol); DualStringBuffer* buf = GetStringBuffer(); if (loc.IsUniformRegister()) { uint16 index; loc.GetUniformRegister(index); // todo - this is complex. Solve via callback buf->appendFmt("uf_remappedVS[{}].{}", index/4, g_idx_to_element[index&3]); AssignResult(ins->regArray[0], buf); } else if (loc.IsVertexAttribute()) { uint16 attributeIndex; uint16 channelIndex; loc.GetVertexAttribute(attributeIndex, channelIndex); cemu_assert_debug(ins->count == 1); cemu_assert_debug(ZpIR::isRegVar(ins->regArray[0])); cemu_assert_debug(channelIndex < 4); cemu_assert_debug(m_blockContext.currentBasicBlock->getRegType(ins->regArray[0]) == ZpIR::DataType::U32); buf->appendFmt("attrDataSem{}.{}", attributeIndex, g_idx_to_element[channelIndex]); AssignResult(ins->regArray[0], buf); } else { cemu_assert_debug(false); } } void GLSL::HandleInstruction(ZpIR::IR::InsEXPORT* ins) { ZpIR::ShaderSubset::ShaderExportLocation loc(ins->exportSymbol); DualStringBuffer* buf = GetStringBuffer(); if (loc.IsPosition()) { // todo - support for output mask (e.g. xyzw, x_zw) ? buf->append("SET_POSITION(vec4("); cemu_assert_debug(ins->count == 4); for (uint32 i = 0; i < ins->count; i++) { if(i > 0) buf->append(", "); appendSourceString(buf, ins->regArray[i]); } m_glslSource->add(*buf); m_glslSource->add("));\r\n"); } else if (loc.IsOutputAttribute()) { uint16 attributeIndex; loc.GetOutputAttribute(attributeIndex); buf->appendFmt("passParameterSem{} = vec4(", attributeIndex); cemu_assert_debug(ins->count == 4); for (uint32 i = 0; i < ins->count; i++) { if (i > 0) buf->append(", "); appendSourceString(buf, ins->regArray[i]); } m_glslSource->add(*buf); m_glslSource->add(");\r\n"); } else { assert_dbg(); } ReleaseStringBuffer(buf); } void GLSL::AssignResult(ZpIR::IRReg irReg, DualStringBuffer* buf, bool forceNoInline) { if (buf->size() > 100) forceNoInline = true; // expression too long if (m_blockContext.CanInlineRegister(irReg) && !forceNoInline) { SetRegInlinedExpression(irReg, buf); } else { ZpIR::DataType regType = m_blockContext.currentBasicBlock->getRegType(irReg); if (regType == ZpIR::DataType::F32) m_glslSource->add("float "); else if (regType == ZpIR::DataType::S32) m_glslSource->add("int "); else if (regType == ZpIR::DataType::U32) m_glslSource->add("uint "); else { cemu_assert_debug(false); } char regName[16]; getRegisterName(regName, irReg); m_glslSource->add(regName); m_glslSource->add(" = "); m_glslSource->add(*buf); m_glslSource->add(";\r\n"); ReleaseStringBuffer(buf); } } void GLSL::appendSourceString(DualStringBuffer* buf, ZpIR::IRReg irReg) { if (ZpIR::isConstVar(irReg)) { ZpIR::IRRegConstDef* constDef = m_blockContext.currentBasicBlock->getConstant(irReg); if (constDef->type == ZpIR::DataType::U32) { buf->appendFmt("{}", constDef->value_u32); return; } else if (constDef->type == ZpIR::DataType::S32) { buf->appendFmt("{}", constDef->value_s32); return; } else if (constDef->type == ZpIR::DataType::F32) { buf->appendFmt("{}", constDef->value_f32); return; } assert_dbg(); } else { cemu_assert_debug(ZpIR::isRegVar(irReg)); uint16 regIndex = ZpIR::getRegIndex(irReg); DualStringBuffer* expressionBuf = m_blockContext.regInlinedExpression[regIndex]; if (expressionBuf) { buf->append(*expressionBuf); return; } char regName[16]; getRegisterName(regName, irReg); buf->append(regName); } } void GLSL::getRegisterName(char buf[16], ZpIR::IRReg irReg) { auto& regData = m_blockContext.currentBasicBlock->m_regs[(uint16)irReg & 0x7FFF]; cemu_assert_debug(regData.hasAssignedPhysicalRegister()); ZpIR::ZpIRPhysicalReg physReg = regData.physicalRegister; char typeChar; if (ZirPass::RegisterAllocatorForGLSL::IsPhysRegTypeF32(physReg)) typeChar = 'f'; else if (ZirPass::RegisterAllocatorForGLSL::IsPhysRegTypeS32(physReg)) typeChar = 'i'; else if (ZirPass::RegisterAllocatorForGLSL::IsPhysRegTypeU32(physReg)) typeChar = 'u'; else { typeChar = 'x'; cemu_assert_debug(false); } auto r = fmt::format_to(buf, "r{}{}", ZirPass::RegisterAllocatorForGLSL::GetPhysRegIndex(physReg), typeChar); *r = '\0'; } void GLSL::SetRegInlinedExpression(ZpIR::IRReg irReg, DualStringBuffer* buf) { cemu_assert_debug(ZpIR::isRegVar(irReg)); uint16 dstIndex = (uint16)irReg; if (m_blockContext.regInlinedExpression[dstIndex]) ReleaseStringBuffer(m_blockContext.regInlinedExpression[dstIndex]); m_blockContext.regInlinedExpression[dstIndex] = buf; } void GLSL::ResetRegInlinedExpression(ZpIR::IRReg irReg) { cemu_assert_debug(ZpIR::isRegVar(irReg)); uint16 dstIndex = (uint16)irReg; if (m_blockContext.regInlinedExpression[dstIndex]) { ReleaseStringBuffer(m_blockContext.regInlinedExpression[dstIndex]); m_blockContext.regInlinedExpression[dstIndex] = nullptr; } } DualStringBuffer* GLSL::GetRegInlinedExpression(ZpIR::IRReg irReg) { cemu_assert_debug(ZpIR::isRegVar(irReg)); uint16 dstIndex = (uint16)irReg; return m_blockContext.regInlinedExpression[dstIndex]; } DualStringBuffer* GLSL::GetStringBuffer() { if (m_stringBufferCache.empty()) return new DualStringBuffer(); DualStringBuffer* buf = m_stringBufferCache.back(); m_stringBufferCache.pop_back(); buf->reset(); return buf; } void GLSL::ReleaseStringBuffer(DualStringBuffer* buf) { m_stringBufferCache.emplace_back(buf); } }; ================================================ FILE: src/util/Zir/EmitterGLSL/ZpIREmitGLSL.h ================================================ #pragma once #include "util/Zir/Core/IR.h" #include "util/Zir/Core/ZpIRPasses.h" #include "util/helpers/StringBuf.h" class DualStringBuffer; namespace ZirEmitter { class GLSL { public: GLSL() {}; // emit function code and append to output string buffer void Emit(ZpIR::ZpIRFunction* irFunction, StringBuf* output); private: void GenerateBasicBlockCode(ZpIR::ZpIRBasicBlock& basicBlock); void HandleInstruction(ZpIR::IR::InsRR* ins); void HandleInstruction(ZpIR::IR::InsRRR* ins); void HandleInstruction(ZpIR::IR::InsIMPORT* ins); void HandleInstruction(ZpIR::IR::InsEXPORT* ins); void appendSourceString(DualStringBuffer* buf, ZpIR::IRReg irReg); void getRegisterName(char buf[16], ZpIR::IRReg irReg); private: ZpIR::ZpIRFunction* m_irFunction{}; StringBuf* m_glslSource{}; struct { ZpIR::ZpIRBasicBlock* currentBasicBlock{ nullptr }; std::vector regReadTracking; std::vector regInlinedExpression; bool CanInlineRegister(ZpIR::IRReg reg) const { cemu_assert_debug(ZpIR::isRegVar(reg)); return regReadTracking[ZpIR::getRegIndex(reg)] <= 1; }; }m_blockContext; void AssignResult(ZpIR::IRReg irReg, DualStringBuffer* buf, bool forceNoInline = false); // inlined expression cache void SetRegInlinedExpression(ZpIR::IRReg irReg, DualStringBuffer* buf); void ResetRegInlinedExpression(ZpIR::IRReg irReg); DualStringBuffer* GetRegInlinedExpression(ZpIR::IRReg irReg); // memory pool for StringBuffer DualStringBuffer* GetStringBuffer(); void ReleaseStringBuffer(DualStringBuffer* buf); std::vector m_stringBufferCache; }; } ================================================ FILE: src/util/Zir/Passes/RegisterAllocatorForGLSL.cpp ================================================ #include "util/Zir/Core/IR.h" #include "util/Zir/Core/ZirUtility.h" #include "util/Zir/Core/ZpIRPasses.h" #include "util/Zir/Core/ZpIRDebug.h" namespace ZirPass { void RegisterAllocatorForGLSL::assignPhysicalRegisters() { if (m_irFunction->m_basicBlocks.size() != 1) cemu_assert_unimplemented(); for (auto& itr : m_irFunction->m_basicBlocks) assignPhysicalRegistersForBlock(itr); } void RegisterAllocatorForGLSL::assignPhysicalRegistersForBlock(ZpIR::ZpIRBasicBlock* basicBlock) { // resolve imports for (auto& itr : basicBlock->m_imports) { assert_dbg(); // todo - If imported reg not assigned physical register yet -> create a shared physical register (MSB set in reg index?) And assign it to this basic block but also all the shared IRRegs in the other linked basic blocks // how to handle import: // - match physical register of every input/output // - every import must have a matching export in all the previous basic blocks. If not all match this is an error. // In our shader emitter this could happen if the original R600 code references an uninitialized register // note - we also have to make sure the register type matches. If a linked block has a shared register with a different type then we need to create a new register and insert a bitcast instruction in that block } // assign a register index to every virtual register for (auto& itr : basicBlock->m_regs) { if (itr.type != ZpIR::DataType::NONE && !itr.hasAssignedPhysicalRegister()) { if (itr.type == ZpIR::DataType::F32) itr.assignPhysicalRegister(MakePhysReg(PHYS_REG_TYPE::F32, m_physicalRegisterCounterF32++)); else if (itr.type == ZpIR::DataType::S32) itr.assignPhysicalRegister(MakePhysReg(PHYS_REG_TYPE::S32, m_physicalRegisterCounterS32++)); else if (itr.type == ZpIR::DataType::U32) itr.assignPhysicalRegister(MakePhysReg(PHYS_REG_TYPE::U32, m_physicalRegisterCounterU32++)); else { cemu_assert_debug(false); } } } } std::string RegisterAllocatorForGLSL::DebugPrintHelper_getPhysRegisterName(ZpIR::ZpIRBasicBlock* block, ZpIR::ZpIRPhysicalReg r) { std::string s; uint32 regIndex = GetPhysRegIndex(r); if (IsPhysRegTypeF32(r)) s = fmt::format("r{}f", regIndex); else if (IsPhysRegTypeU32(r)) s = fmt::format("r{}u", regIndex); else if (IsPhysRegTypeS32(r)) s = fmt::format("r{}i", regIndex); return s; } } ================================================ FILE: src/util/Zir/Passes/ZpIRRegisterAllocator.cpp ================================================ #include "util/Zir/Core/IR.h" #include "util/Zir/Core/ZirUtility.h" #include "util/Zir/Core/ZpIRPasses.h" #include "util/Zir/Core/ZpIRDebug.h" namespace ZirPass { using namespace ZpIR; /* Algorithm description: Prepare phase: Assign every basic block an index Create internal arrays to match index count First phase: Create liveness ranges for each basic block Link liveness ranges by import/export Constrained instructions split affected ranges into their own single instruction liveness range Second phase: Assign registers. Start with constrained ranges first, then process from beginning to end Whenever we assign a register to a range, we also try to propagate it to all the connected/coalesced ranges A liveness range is described by: - Source (Can be any of: List of previous basic blocks, liveness range in same basic block) - Destination (list of liveness ranges) - Index of basic block - First instruction (where register is assigned, -1 if passed from previous block) - Last instruction (where register is last accessed) - IR-Register (within the same basic block) During algorithm: - Spillcost (probably can calculate this dynamically) - Physical location (-1 if not assigned. Otherwise register index or spill memory offset) */ RALivenessRange_t::RALivenessRange_t(RABlock_t* block, IRReg irReg, sint32 start, sint32 end, DataType irDataType) : m_block(block), m_irReg(irReg), m_irDataType(irDataType) { block->livenessRanges.emplace(irReg, this); m_startIndex = start; m_endIndex = end; // register for (auto& itr : block->livenessRanges) { RALivenessRange_t* itrRange = itr.second; if (start < itrRange->m_endIndex && end >= itrRange->m_startIndex) { m_overlappingRanges.emplace_back(itrRange); itrRange->m_overlappingRanges.emplace_back(this); // todo - also immediately flag physical registers as unavailable } } block->unassignedRanges.emplace(this); } RALivenessRange_t::~RALivenessRange_t() { for (auto& itr : m_overlappingRanges) { RALivenessRange_t* overlappedRange = itr; // todo - unflag physical register (if this has one set) overlappedRange->m_overlappingRanges.erase(std::remove(overlappedRange->m_overlappingRanges.begin(), overlappedRange->m_overlappingRanges.end(), overlappedRange), overlappedRange->m_overlappingRanges.end()); } m_overlappingRanges.clear(); assert_dbg(); } void RALivenessRange_t::setStart(sint32 startIndex) { m_startIndex = startIndex; assert_dbg(); // re-register in sorted range list (if no reg assigned) } void RALivenessRange_t::setEnd(sint32 endIndex) { if (endIndex > m_endIndex) { // add ranges that are now overlapping for (auto& itr : m_block->livenessRanges) { RALivenessRange_t* itrRange = itr.second; if(itrRange->isOverlapping(this)) continue; // was overlapping before if(itrRange == this) continue; if (itrRange->isOverlapping(m_startIndex, endIndex)) { m_overlappingRanges.emplace_back(itrRange); itrRange->m_overlappingRanges.emplace_back(this); // todo - also immediately flag physical registers as unavailable } } } else if (endIndex < m_endIndex) { // remove ranges that are no longer overlapping cemu_assert_suspicious(); } m_endIndex = endIndex; } void RALivenessRange_t::assignPhysicalRegister(ZpIRPhysicalReg physReg) { if (m_location != LOCATION::UNASSIGNED) cemu_assert_suspicious(); m_location = LOCATION::PHYSICAL_REGISTER; m_physicalRegister = physReg; // remove this from unassignedRanges auto itr = m_block->unassignedRanges.find(this); if (itr == m_block->unassignedRanges.end()) cemu_assert_suspicious(); if (*itr != this) cemu_assert_suspicious(); m_block->unassignedRanges.erase(itr); } void RARegular::prepareRABlocks() { auto& irBasicBlocks = m_irFunction->m_basicBlocks; m_raBlockArray.resize(m_irFunction->m_basicBlocks.size()); } void RARegular::generateLivenessRanges() { auto& irBasicBlocks = m_irFunction->m_basicBlocks; //for (auto& itr : irBasicBlocks) for (uint32 basicBlockIndex = 0; basicBlockIndex < (uint32)irBasicBlocks.size(); basicBlockIndex++) { auto& blockItr = irBasicBlocks[basicBlockIndex]; RABlock_t* raBlock = m_raBlockArray.data() + basicBlockIndex; std::unordered_map& blockRanges = raBlock->livenessRanges; // init ranges for imports first for (auto& regImport : blockItr->m_imports) { new RALivenessRange_t(raBlock, regImport.reg, -1, -1, blockItr->m_regs[(uint16)regImport.reg].type); // imports start before the current basic block } // parse instructions and create/update ranges IR::__InsBase* ins = blockItr->m_instructionFirst; size_t i = 0; while(ins) { ZpIRCmdUtil::forEachAccessedReg(*blockItr, ins, [&blockRanges, i, raBlock](IRReg readReg) { if (readReg >= 0x8000) cemu_assert_suspicious(); // read access auto livenessRange = blockRanges.find(readReg); if (livenessRange == blockRanges.end()) cemu_assert_suspicious(); livenessRange->second->setEnd((sint32)i); }, [&blockRanges, i, raBlock, blockItr](IRReg writtenReg) { if (writtenReg >= 0x8000) cemu_assert_suspicious(); // write access auto livenessRange = blockRanges.find(writtenReg); if (livenessRange != blockRanges.end()) cemu_assert_suspicious(); new RALivenessRange_t(raBlock, writtenReg, (sint32)i, (sint32)i, blockItr->m_regs[(uint16)writtenReg].type); }); i++; ins = ins->next; } // exports extend ranges to one instruction past the end of the block for (auto& regExport : blockItr->m_exports) { auto livenessRange = blockRanges.find(regExport.reg); if (livenessRange == blockRanges.end()) cemu_assert_suspicious(); cemu_assert_unimplemented(); //livenessRange->second->setEnd((sint32)blockItr->m_cmdsDepr.size()); } } // connect liveness ranges across basic blocks based on their import/export names std::unordered_map listExportedRanges; for (uint32 basicBlockIndex = 0; basicBlockIndex < (uint32)irBasicBlocks.size(); basicBlockIndex++) { // for each block take all exported ranges and connect them to the imports of the successor blocks auto& blockItr = irBasicBlocks[basicBlockIndex]; // collect all exported liveness ranges std::unordered_map& localRanges = m_raBlockArray[basicBlockIndex].livenessRanges; listExportedRanges.clear(); for (auto& regExport : blockItr->m_exports) { auto livenessRange = localRanges.find(regExport.reg); if (livenessRange == localRanges.end()) assert_dbg(); listExportedRanges.emplace(regExport.name, livenessRange->second); } // handle imports in the connected blocks if (blockItr->m_branchTaken) { ZpIRBasicBlock* successorBlock = blockItr->m_branchTaken; std::unordered_map& successorRanges = localRanges = m_raBlockArray[basicBlockIndex].livenessRanges; for (auto& regImport : successorBlock->m_exports) { auto livenessRange = successorRanges.find(regImport.reg); if (livenessRange == successorRanges.end()) assert_dbg(); auto connectedSourceRange = listExportedRanges.find(regImport.name); if (connectedSourceRange == listExportedRanges.end()) assert_dbg(); livenessRange->second->addSourceFromPreviousBlock(connectedSourceRange->second); } } // handle imports for entry blocks // todo // handle export for exit blocks // todo } } void RARegular::assignPhysicalRegistersForBlock(RABlock_t& raBlock) { debugPrint(raBlock); std::vector physRegCandidates; physRegCandidates.reserve(32); // process livenessRanges ascending by start address while (!raBlock.unassignedRanges.empty()) { RALivenessRange_t* range = *raBlock.unassignedRanges.begin(); // get a list of potential physical registers std::span physReg = extGetSuitablePhysicalRegisters(range->m_irDataType); physRegCandidates.clear(); for (auto& r : physReg) physRegCandidates.emplace_back(r); // try to find a physical register that we can assign to the entire liveness span (current range and all connected ranges) // todo // handle special cases like copy coalescing // todo // try to find a register for only the current range filterCandidates(physRegCandidates, range); if (!physRegCandidates.empty()) { // pick preferred register ZpIRPhysicalReg physRegister = extPickPreferredRegister(physRegCandidates); range->assignPhysicalRegister(physRegister); continue; } // spill is necessary assert_dbg(); assert_dbg(); } printf("Assigned:\n"); debugPrint(raBlock); } void RARegular::assignPhysicalRegisters() { // todo - first we should assign all the fixed registers. E.g. imports/exports, constrained instructions for (auto& raBlockInfo : m_raBlockArray) assignPhysicalRegistersForBlock(raBlockInfo); } void RARegular::rewrite() { for (size_t i = 0; i < m_raBlockArray.size(); i++) rewriteBlock(*m_irFunction->m_basicBlocks[i], m_raBlockArray[i]); } void RARegular::rewriteBlock(ZpIRBasicBlock& basicBlock, RABlock_t& raBlock) { assert_dbg(); //std::vector cmdOut; //std::unordered_map translationTable; //for (auto& itr : raBlock.livenessRanges) // translationTable.emplace(itr.second->m_irReg, itr.second->m_physicalRegister); //// todo - since ir var registers are created in incremental order we could instead use a std::vector for fast look-up instead of a map? //for (uint32 i = 0; i < (uint32)basicBlock.m_cmdsDepr.size(); i++) //{ // // todo - insert spill and load instructions // // todo - insert register moves for range-to-range copies // // ZpIRCmd* currentCmd = basicBlock.m_cmdsDepr.data() + i; // // replace registers and then insert into output command list // ZpIRCmdUtil::replaceRegisters(*currentCmd, translationTable); // cmdOut.emplace_back(*currentCmd); //} //basicBlock.m_cmdsDepr = std::move(cmdOut); // todo - should we keep imports/exports but update them to use physical register indices? // the code emitter needs to know which physical registers are exported in order to determine which optimizations are allowed basicBlock.m_imports.clear(); basicBlock.m_imports.shrink_to_fit(); basicBlock.m_exports.clear(); basicBlock.m_exports.shrink_to_fit(); basicBlock.m_regs.clear(); basicBlock.m_regs.shrink_to_fit(); } } ================================================ FILE: src/util/boost/bluetooth.h ================================================ #pragma once #include "platform/platform.h" #include namespace boost { namespace asio { template class device_endpoint { public: typedef Protocol protocol_type; typedef detail::socket_addr_type data_type; struct device_t { device_t(long long device_addr) : addr(device_addr) { } long long addr; }; device_endpoint() { memset(&addr, 0x00, sizeof(addr)); } device_endpoint(device_t device_address) { memset(&addr, 0x00, sizeof(addr)); addr.addressFamily = AF_BLUETOOTH; addr.btAddr = id.addr; addr.serviceClassId = RFCOMM_PROTOCOL_UUID; addr.port = BT_PORT_ANY; } device_endpoint(const device_endpoint& other) : addr(other.addr) { } device_endpoint& operator=(const device_endpoint& other) { addr = other.addr; return *this; } protocol_type protocol() const { return protocol_type(); } data_type* data() { return reinterpret_cast(&addr); } const data_type* data() const { return reinterpret_cast(&addr); } size_t size() const { return sizeof(SOCKADDR_BTH); } size_t capacity() const { return size(); } private: SOCKADDR_BTH addr; }; class bluetooth { public: using endpoint = device_endpoint; using socket = basic_stream_socket; using acceptor = basic_socket_acceptor; using iostream = basic_socket_iostream; bluetooth() = default; int type() const { return SOCK_STREAM; } int protocol() const { return BTPROTO_RFCOMM; } int family() const { return AF_BLUETOOTH; } }; } } ================================================ FILE: src/util/bootSound/BootSoundReader.cpp ================================================ #include "BootSoundReader.h" #include "Cafe/CafeSystem.h" BootSoundReader::BootSoundReader(FSCVirtualFile* bootsndFile, sint32 blockSize) : bootsndFile(bootsndFile), blockSize(blockSize) { // crash if this constructor is invoked with a blockSize that has a different number of samples per channel cemu_assert(blockSize % (sizeof(sint16be) * 2) == 0); fsc_setFileSeek(bootsndFile, 0); fsc_readFile(bootsndFile, &muteBits, 4); fsc_readFile(bootsndFile, &loopPoint, 4); buffer.resize(blockSize / sizeof(sint16)); bufferBE.resize(blockSize / sizeof(sint16be)); // workaround: SM3DW has incorrect loop point const auto titleId = CafeSystem::GetForegroundTitleId(); if(titleId == 0x0005000010145D00 || titleId == 0x0005000010145C00 || titleId == 0x0005000010106100) loopPoint = 113074; } sint16* BootSoundReader::getSamples() { size_t totalRead = 0; const size_t loopPointOffset = 8 + loopPoint * 4; while (totalRead < blockSize) { auto read = fsc_readFile(bootsndFile, bufferBE.data(), blockSize - totalRead); if (read == 0) { cemuLog_log(LogType::Force, "failed to read PCM samples from bootSound.btsnd"); return nullptr; } if (read % (sizeof(sint16be) * 2) != 0) { cemuLog_log(LogType::Force, "failed to play bootSound.btsnd: reading PCM data stopped at an odd number of samples (is the file corrupt?)"); return nullptr; } std::copy_n(bufferBE.begin(), read / sizeof(sint16be), buffer.begin() + (totalRead / sizeof(sint16))); totalRead += read; if (totalRead < blockSize) fsc_setFileSeek(bootsndFile, loopPointOffset); } // handle case where the end of a block of samples lines up with the end of the file if(fsc_getFileSeek(bootsndFile) == fsc_getFileSize(bootsndFile)) fsc_setFileSeek(bootsndFile, loopPointOffset); return buffer.data(); } ================================================ FILE: src/util/bootSound/BootSoundReader.h ================================================ #pragma once #include "Cafe/Filesystem/fsc.h" class BootSoundReader { public: BootSoundReader() = delete; BootSoundReader(FSCVirtualFile* bootsndFile, sint32 blockSize); sint16* getSamples(); private: FSCVirtualFile* bootsndFile{}; sint32 blockSize{}; uint32be muteBits{}; uint32be loopPoint{}; std::vector buffer{}; std::vector bufferBE{}; }; ================================================ FILE: src/util/containers/IntervalBucketContainer.h ================================================ #pragma once template class IntervalBucketContainer { struct bucketEntry_t { TAddr rangeStart; TAddr rangeEnd; TData* data; int bucketStartIndex; bucketEntry_t(TAddr rangeStart, TAddr rangeEnd, TData* data, int bucketStartIndex) : rangeStart(rangeStart), rangeEnd(rangeEnd), data(data), bucketStartIndex(bucketStartIndex) {}; }; std::vector list_bucket[TBucketCount]; public: IntervalBucketContainer() = default; // range is defined as inclusive rangeStart and exclusive rangeEnd void addRange(TAddr rangeStart, TAddr rangeEnd, TData* data) { assert(rangeStart < rangeEnd); int bucketStartIndex = (rangeStart / TAddressGranularity); int bucketEndIndex = ((rangeEnd + TAddressGranularity - 1) / TAddressGranularity); int bucketItrCount = bucketEndIndex - bucketStartIndex; bucketStartIndex %= TBucketCount; int bucketFirstIndex = bucketStartIndex; bucketItrCount = std::min(bucketItrCount, TBucketCount); assert(bucketItrCount != 0); while (bucketItrCount--) { list_bucket[bucketStartIndex].emplace_back(rangeStart, rangeEnd, data, bucketFirstIndex); bucketStartIndex = (bucketStartIndex + 1) % TBucketCount; } } void removeRange(TAddr rangeStart, TAddr rangeEnd, TData* data) { assert(rangeStart < rangeEnd); int bucketStartIndex = (rangeStart / TAddressGranularity); int bucketEndIndex = ((rangeEnd + TAddressGranularity - 1) / TAddressGranularity); int bucketItrCount = bucketEndIndex - bucketStartIndex; bucketStartIndex %= TBucketCount; bucketItrCount = std::min(bucketItrCount, TBucketCount); assert(bucketItrCount != 0); int eraseCountVerifier = bucketItrCount; while (bucketItrCount--) { for (auto it = list_bucket[bucketStartIndex].begin(); it != list_bucket[bucketStartIndex].end(); it++) { if (it->data == data) { assert(it->rangeStart == rangeStart && it->rangeEnd == rangeEnd); // erase list_bucket[bucketStartIndex].erase(it); eraseCountVerifier--; break; } } bucketStartIndex = (bucketStartIndex + 1) % TBucketCount; } assert(eraseCountVerifier == 0); // triggers if rangeStart/End doesn't match up with any registered range } template void lookupRanges(TAddr rangeStart, TAddr rangeEnd, TRangeCallback cb) { assert(rangeStart < rangeEnd); int bucketStartIndex = (rangeStart / TAddressGranularity); int bucketEndIndex = ((rangeEnd + TAddressGranularity - 1) / TAddressGranularity); int bucketItrCount = bucketEndIndex - bucketStartIndex; bucketStartIndex %= TBucketCount; bucketItrCount = std::min(bucketItrCount, TBucketCount); assert(bucketItrCount != 0); // in first round we dont need to check if bucket was already visited for (auto& itr : list_bucket[bucketStartIndex]) { if (itr.rangeStart < rangeEnd && itr.rangeEnd > rangeStart) { cb(itr.data); } } bucketItrCount--; bucketStartIndex = (bucketStartIndex + 1) % TBucketCount; // for remaining buckets check if the range starts in the current bucket while (bucketItrCount--) { for (auto& itr : list_bucket[bucketStartIndex]) { if (itr.rangeStart < rangeEnd && itr.rangeEnd > rangeStart && itr.bucketStartIndex == bucketStartIndex) { cb(itr.data); } } bucketStartIndex = (bucketStartIndex + 1) % TBucketCount; } } }; ================================================ FILE: src/util/containers/LookupTableL3.h ================================================ #pragma once // staged lookup table suited for cases where the lookup index range can be very large (e.g. memory addresses) // performs 3 consecutive table lookups, where each table's width is defined by TBitsN // empty subtables consume no memory beyond the initial two default tables for TBitsY and TBitsZ template class LookupTableL3 { struct TableZ // z lookup { T arr[1 << TBitsZ]{}; }; struct TableY // y lookup { TableZ* arr[1 << TBitsY]; }; // by generating placeholder tables we can avoid conditionals in the lookup code since no null-pointer checking is necessary TableY* m_placeholderTableY{}; TableZ* m_placeholderTableZ{}; public: LookupTableL3() { // init placeholder table Z m_placeholderTableZ = GenerateNewTableZ(); // init placeholder table Y (all entries point to placeholder table Z) m_placeholderTableY = GenerateNewTableY(); // init x table for (auto& itr : m_tableXArr) itr = m_placeholderTableY; } ~LookupTableL3() { delete m_placeholderTableY; delete m_placeholderTableZ; } // lookup // only the bottom most N bits bits are used of the offset // N = TBitsX + TBitsY + TBitsZ // if no match is found a default-constructed object is returned T lookup(uint32 offset) { uint32 indexZ = offset & ((1u << TBitsZ) - 1); offset >>= TBitsZ; uint32 indexY = offset & ((1u << TBitsY) - 1); offset >>= TBitsY; uint32 indexX = offset & ((1u << TBitsX) - 1); //offset >>= TBitsX; return m_tableXArr[indexX]->arr[indexY]->arr[indexZ]; } void store(uint32 offset, T& t) { uint32 indexZ = offset & ((1u << TBitsZ) - 1); offset >>= TBitsZ; uint32 indexY = offset & ((1u << TBitsY) - 1); offset >>= TBitsY; uint32 indexX = offset & ((1u << TBitsX) - 1); if (m_tableXArr[indexX] == m_placeholderTableY) m_tableXArr[indexX] = GenerateNewTableY(); TableY* lookupY = m_tableXArr[indexX]; if (lookupY->arr[indexY] == m_placeholderTableZ) lookupY->arr[indexY] = GenerateNewTableZ(); TableZ* lookupZ = lookupY->arr[indexY]; lookupZ->arr[indexZ] = t; } private: // generate a new Y lookup table which will initially contain only pointers to m_placeholderTableZ TableY* GenerateNewTableY() { TableY* tableY = new TableY(); for (auto& itr : tableY->arr) itr = m_placeholderTableZ; return tableY; } // generate a new Z lookup table which will initially contain only default constructed T TableZ* GenerateNewTableZ() { TableZ* tableZ = new TableZ(); return tableZ; } TableY* m_tableXArr[1 << TBitsX]; // x lookup }; ================================================ FILE: src/util/containers/RangeStore.h ================================================ #pragma once template class RangeStore { public: typedef struct { _ADDR start; _ADDR end; _OBJ data; size_t lastIterationIndex; }rangeEntry_t; RangeStore() = default; size_t getBucket(_ADDR addr) { size_t index = addr / granularity; index %= count; return index; } void getBucketRange(_ADDR addrStart, _ADDR addrEnd, size_t& bucketFirst, size_t& bucketCount) { bucketFirst = getBucket(addrStart); size_t indexStart = addrStart / granularity; size_t indexEnd = std::max(addrStart, addrEnd - 1) / granularity; bucketCount = indexEnd - indexStart + 1; } // end address should be supplied as start+size void* storeRange(_OBJ data, _ADDR start, _ADDR end) { size_t bucketFirst; size_t bucketCount; getBucketRange(start, end, bucketFirst, bucketCount); bucketCount = std::min(bucketCount, count); // create range rangeEntry_t* rangeEntry = new rangeEntry_t(); rangeEntry->data = data; rangeEntry->start = start; rangeEntry->end = end; rangeEntry->lastIterationIndex = currentIterationIndex; // register range in every bucket it touches size_t idx = bucketFirst; for (size_t i = 0; i < bucketCount; i++) { rangeBuckets[idx].list_ranges.push_back(rangeEntry); idx = (idx + 1) % count; } return rangeEntry; } void deleteRange(void* rangePtr) { rangeEntry_t* rangeEntry = (rangeEntry_t*)rangePtr; // get bucket range size_t bucketFirst; size_t bucketCount; getBucketRange(rangeEntry->start, rangeEntry->end, bucketFirst, bucketCount); bucketCount = std::min(bucketCount, count); // remove from buckets size_t idx = bucketFirst; for (size_t i = 0; i < bucketCount; i++) { rangeBuckets[idx].list_ranges.erase(std::remove(rangeBuckets[idx].list_ranges.begin(), rangeBuckets[idx].list_ranges.end(), rangeEntry), rangeBuckets[idx].list_ranges.end()); idx = (idx + 1) % count; } delete rangeEntry; } void findRanges(_ADDR start, _ADDR end, std::function f) { currentIterationIndex++; size_t bucketFirst; size_t bucketCount; getBucketRange(start, end, bucketFirst, bucketCount); bucketCount = std::min(bucketCount, count); size_t idx = bucketFirst; for (size_t i = 0; i < bucketCount; i++) { for (auto r : rangeBuckets[idx].list_ranges) { if (start < r->end && end > r->start && r->lastIterationIndex != currentIterationIndex) { r->lastIterationIndex = currentIterationIndex; f(r->start, r->end, r->data); } } idx = (idx + 1) % count; } } bool findFirstRange(_ADDR start, _ADDR end, _ADDR& rStart, _ADDR& rEnd, _OBJ& rData) { currentIterationIndex++; size_t bucketFirst; size_t bucketCount; getBucketRange(start, end, bucketFirst, bucketCount); bucketCount = std::min(bucketCount, count); size_t idx = bucketFirst; for (size_t i = 0; i < bucketCount; i++) { for (auto r : rangeBuckets[idx].list_ranges) { if (start < r->end && end > r->start && r->lastIterationIndex != currentIterationIndex) { r->lastIterationIndex = currentIterationIndex; rStart = r->start; rEnd = r->end; rData = r->data; return true; } } idx = (idx + 1) % count; } return false; } void clear() { for(auto& bucket : rangeBuckets) { while(!bucket.list_ranges.empty()) deleteRange(bucket.list_ranges[0]); } } private: typedef struct { std::vector list_ranges; }rangeBucket_t; std::array rangeBuckets; size_t currentIterationIndex; }; ================================================ FILE: src/util/containers/SmallBitset.h ================================================ // optimized and compact version of std::bitset with no error checking in release mode // uses a single uint32 to store the bitmask, thus allowing up to 32 bool values template class SmallBitset { public: SmallBitset() = default; static_assert(N <= 32); bool test(size_t index) const { cemu_assert_debug(index < N); return ((m_bits >> index) & 1) != 0; } void set(size_t index, bool val) { cemu_assert_debug(index < N); m_bits &= ~(1u << index); if (val) m_bits |= (1u << index); } void set(size_t index) { cemu_assert_debug(index < N); m_bits |= (1u << index); } private: uint32 m_bits{}; }; ================================================ FILE: src/util/containers/flat_hash_map.hpp ================================================ // Copyright Malte Skarupke 2017. // Distributed under the Boost Software License, Version 1.0. // (See http://www.boost.org/LICENSE_1_0.txt) #pragma once #include #include #include #include #include #include #include #include #ifdef _MSC_VER #define SKA_NOINLINE(...) __declspec(noinline) __VA_ARGS__ #else #define SKA_NOINLINE(...) __VA_ARGS__ __attribute__((noinline)) #endif namespace ska { struct prime_number_hash_policy; struct power_of_two_hash_policy; struct fibonacci_hash_policy; namespace detailv3 { template struct functor_storage : Functor { functor_storage() = default; functor_storage(const Functor& functor) : Functor(functor) { } template Result operator()(Args &&... args) { return static_cast(*this)(std::forward(args)...); } template Result operator()(Args &&... args) const { return static_cast(*this)(std::forward(args)...); } }; template struct functor_storage { typedef Result(*function_ptr)(Args...); function_ptr function; functor_storage(function_ptr function) : function(function) { } Result operator()(Args... args) const { return function(std::forward(args)...); } operator function_ptr& () { return function; } operator const function_ptr& () { return function; } }; template struct KeyOrValueHasher : functor_storage { typedef functor_storage hasher_storage; KeyOrValueHasher() = default; KeyOrValueHasher(const hasher& hash) : hasher_storage(hash) { } size_t operator()(const key_type& key) { return static_cast(*this)(key); } size_t operator()(const key_type& key) const { return static_cast(*this)(key); } size_t operator()(const value_type& value) { return static_cast(*this)(value.first); } size_t operator()(const value_type& value) const { return static_cast(*this)(value.first); } template size_t operator()(const std::pair& value) { return static_cast(*this)(value.first); } template size_t operator()(const std::pair& value) const { return static_cast(*this)(value.first); } }; template struct KeyOrValueEquality : functor_storage { typedef functor_storage equality_storage; KeyOrValueEquality() = default; KeyOrValueEquality(const key_equal& equality) : equality_storage(equality) { } bool operator()(const key_type& lhs, const key_type& rhs) { return static_cast(*this)(lhs, rhs); } bool operator()(const key_type& lhs, const value_type& rhs) { return static_cast(*this)(lhs, rhs.first); } bool operator()(const value_type& lhs, const key_type& rhs) { return static_cast(*this)(lhs.first, rhs); } bool operator()(const value_type& lhs, const value_type& rhs) { return static_cast(*this)(lhs.first, rhs.first); } template bool operator()(const key_type& lhs, const std::pair& rhs) { return static_cast(*this)(lhs, rhs.first); } template bool operator()(const std::pair& lhs, const key_type& rhs) { return static_cast(*this)(lhs.first, rhs); } template bool operator()(const value_type& lhs, const std::pair& rhs) { return static_cast(*this)(lhs.first, rhs.first); } template bool operator()(const std::pair& lhs, const value_type& rhs) { return static_cast(*this)(lhs.first, rhs.first); } template bool operator()(const std::pair& lhs, const std::pair& rhs) { return static_cast(*this)(lhs.first, rhs.first); } }; static constexpr int8_t min_lookups = 4; template struct sherwood_v3_entry { sherwood_v3_entry() { } sherwood_v3_entry(int8_t distance_from_desired) : distance_from_desired(distance_from_desired) { } ~sherwood_v3_entry() = default; static sherwood_v3_entry* empty_default_table() { static sherwood_v3_entry result[min_lookups] = { {}, {}, {}, {special_end_value} }; return result; } bool has_value() const { return distance_from_desired >= 0; } bool is_empty() const { return distance_from_desired < 0; } bool is_at_desired_position() const { return distance_from_desired <= 0; } template void emplace(int8_t distance, Args &&... args) { new (std::addressof(value)) T(std::forward(args)...); distance_from_desired = distance; } void destroy_value() { value.~T(); distance_from_desired = -1; } int8_t distance_from_desired = -1; static constexpr int8_t special_end_value = 0; union { T value; }; }; inline int8_t log2(size_t value) { static constexpr int8_t table[64] = { 63, 0, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, 33, 42, 3, 61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56, 45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5 }; value |= value >> 1; value |= value >> 2; value |= value >> 4; value |= value >> 8; value |= value >> 16; value |= value >> 32; return table[((value - (value >> 1)) * 0x07EDD5E59A4E28C2) >> 58]; } template struct AssignIfTrue { void operator()(T& lhs, const T& rhs) { lhs = rhs; } void operator()(T& lhs, T&& rhs) { lhs = std::move(rhs); } }; template struct AssignIfTrue { void operator()(T&, const T&) { } void operator()(T&, T&&) { } }; inline size_t next_power_of_two(size_t i) { --i; i |= i >> 1; i |= i >> 2; i |= i >> 4; i |= i >> 8; i |= i >> 16; i |= i >> 32; ++i; return i; } template using void_t = void; template struct HashPolicySelector { typedef fibonacci_hash_policy type; }; template struct HashPolicySelector> { typedef typename T::hash_policy type; }; template class sherwood_v3_table : private EntryAlloc, private Hasher, private Equal { using Entry = detailv3::sherwood_v3_entry; using AllocatorTraits = std::allocator_traits; using EntryPointer = typename AllocatorTraits::pointer; struct convertible_to_iterator; public: using value_type = T; using size_type = size_t; using difference_type = std::ptrdiff_t; using hasher = ArgumentHash; using key_equal = ArgumentEqual; using allocator_type = EntryAlloc; using reference = value_type&; using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; sherwood_v3_table() = default; explicit sherwood_v3_table(size_type bucket_count, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) : EntryAlloc(alloc), Hasher(hash), Equal(equal) { rehash(bucket_count); } sherwood_v3_table(size_type bucket_count, const ArgumentAlloc& alloc) : sherwood_v3_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc) { } sherwood_v3_table(size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) : sherwood_v3_table(bucket_count, hash, ArgumentEqual(), alloc) { } explicit sherwood_v3_table(const ArgumentAlloc& alloc) : EntryAlloc(alloc) { } template sherwood_v3_table(It first, It last, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) : sherwood_v3_table(bucket_count, hash, equal, alloc) { insert(first, last); } template sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentAlloc& alloc) : sherwood_v3_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) { } template sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) : sherwood_v3_table(first, last, bucket_count, hash, ArgumentEqual(), alloc) { } sherwood_v3_table(std::initializer_list il, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) : sherwood_v3_table(bucket_count, hash, equal, alloc) { if (bucket_count == 0) rehash(il.size()); insert(il.begin(), il.end()); } sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentAlloc& alloc) : sherwood_v3_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) { } sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) : sherwood_v3_table(il, bucket_count, hash, ArgumentEqual(), alloc) { } sherwood_v3_table(const sherwood_v3_table& other) : sherwood_v3_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator())) { } sherwood_v3_table(const sherwood_v3_table& other, const ArgumentAlloc& alloc) : EntryAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor) { rehash_for_other_container(other); try { insert(other.begin(), other.end()); } catch (...) { clear(); deallocate_data(entries, num_slots_minus_one, max_lookups); throw; } } sherwood_v3_table(sherwood_v3_table&& other) noexcept : EntryAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)) { swap_pointers(other); } sherwood_v3_table(sherwood_v3_table&& other, const ArgumentAlloc& alloc) noexcept : EntryAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)) { swap_pointers(other); } sherwood_v3_table& operator=(const sherwood_v3_table& other) { if (this == std::addressof(other)) return *this; clear(); if (AllocatorTraits::propagate_on_container_copy_assignment::value) { if (static_cast(*this) != static_cast(other)) { reset_to_empty_state(); } AssignIfTrue()(*this, other); } _max_load_factor = other._max_load_factor; static_cast(*this) = other; static_cast(*this) = other; rehash_for_other_container(other); insert(other.begin(), other.end()); return *this; } sherwood_v3_table& operator=(sherwood_v3_table&& other) noexcept { if (this == std::addressof(other)) return *this; else if (AllocatorTraits::propagate_on_container_move_assignment::value) { clear(); reset_to_empty_state(); AssignIfTrue()(*this, std::move(other)); swap_pointers(other); } else if (static_cast(*this) == static_cast(other)) { swap_pointers(other); } else { clear(); _max_load_factor = other._max_load_factor; rehash_for_other_container(other); for (T& elem : other) emplace(std::move(elem)); other.clear(); } static_cast(*this) = std::move(other); static_cast(*this) = std::move(other); return *this; } ~sherwood_v3_table() { clear(); deallocate_data(entries, num_slots_minus_one, max_lookups); } const allocator_type& get_allocator() const { return static_cast(*this); } const ArgumentEqual& key_eq() const { return static_cast(*this); } const ArgumentHash& hash_function() const { return static_cast(*this); } template struct templated_iterator { templated_iterator() = default; templated_iterator(EntryPointer current) : current(current) { } EntryPointer current = EntryPointer(); using iterator_category = std::forward_iterator_tag; using value_type = ValueType; using difference_type = ptrdiff_t; using pointer = ValueType*; using reference = ValueType&; friend bool operator==(const templated_iterator& lhs, const templated_iterator& rhs) { return lhs.current == rhs.current; } friend bool operator!=(const templated_iterator& lhs, const templated_iterator& rhs) { return !(lhs == rhs); } templated_iterator& operator++() { do { ++current; } while (current->is_empty()); return *this; } templated_iterator operator++(int) { templated_iterator copy(*this); ++* this; return copy; } ValueType& operator*() const { return current->value; } ValueType* operator->() const { return std::addressof(current->value); } operator templated_iterator() const { return { current }; } }; using iterator = templated_iterator; using const_iterator = templated_iterator; iterator begin() { for (EntryPointer it = entries;; ++it) { if (it->has_value()) return { it }; } } const_iterator begin() const { for (EntryPointer it = entries;; ++it) { if (it->has_value()) return { it }; } } const_iterator cbegin() const { return begin(); } iterator end() { return { entries + static_cast(num_slots_minus_one + max_lookups) }; } const_iterator end() const { return { entries + static_cast(num_slots_minus_one + max_lookups) }; } const_iterator cend() const { return end(); } iterator find(const FindKey& key) { size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); EntryPointer it = entries + ptrdiff_t(index); for (int8_t distance = 0; it->distance_from_desired >= distance; ++distance, ++it) { if (compares_equal(key, it->value)) return { it }; } return end(); } const_iterator find(const FindKey& key) const { return const_cast(this)->find(key); } size_t count(const FindKey& key) const { return find(key) == end() ? 0 : 1; } std::pair equal_range(const FindKey& key) { iterator found = find(key); if (found == end()) return { found, found }; else return { found, std::next(found) }; } std::pair equal_range(const FindKey& key) const { const_iterator found = find(key); if (found == end()) return { found, found }; else return { found, std::next(found) }; } template std::pair emplace(Key&& key, Args &&... args) { size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); EntryPointer current_entry = entries + ptrdiff_t(index); int8_t distance_from_desired = 0; for (; current_entry->distance_from_desired >= distance_from_desired; ++current_entry, ++distance_from_desired) { if (compares_equal(key, current_entry->value)) return { { current_entry }, false }; } return emplace_new_key(distance_from_desired, current_entry, std::forward(key), std::forward(args)...); } std::pair insert(const value_type& value) { return emplace(value); } std::pair insert(value_type&& value) { return emplace(std::move(value)); } template iterator emplace_hint(const_iterator, Args &&... args) { return emplace(std::forward(args)...).first; } iterator insert(const_iterator, const value_type& value) { return emplace(value).first; } iterator insert(const_iterator, value_type&& value) { return emplace(std::move(value)).first; } template void insert(It begin, It end) { for (; begin != end; ++begin) { emplace(*begin); } } void insert(std::initializer_list il) { insert(il.begin(), il.end()); } void rehash(size_t num_buckets) { num_buckets = std::max(num_buckets, static_cast(std::ceil(num_elements / static_cast(_max_load_factor)))); if (num_buckets == 0) { reset_to_empty_state(); return; } auto new_prime_index = hash_policy.next_size_over(num_buckets); if (num_buckets == bucket_count()) return; int8_t new_max_lookups = compute_max_lookups(num_buckets); EntryPointer new_buckets(AllocatorTraits::allocate(*this, num_buckets + new_max_lookups)); EntryPointer special_end_item = new_buckets + static_cast(num_buckets + new_max_lookups - 1); for (EntryPointer it = new_buckets; it != special_end_item; ++it) it->distance_from_desired = -1; special_end_item->distance_from_desired = Entry::special_end_value; std::swap(entries, new_buckets); std::swap(num_slots_minus_one, num_buckets); --num_slots_minus_one; hash_policy.commit(new_prime_index); int8_t old_max_lookups = max_lookups; max_lookups = new_max_lookups; num_elements = 0; for (EntryPointer it = new_buckets, end = it + static_cast(num_buckets + old_max_lookups); it != end; ++it) { if (it->has_value()) { emplace(std::move(it->value)); it->destroy_value(); } } deallocate_data(new_buckets, num_buckets, old_max_lookups); } void reserve(size_t num_elements) { size_t required_buckets = num_buckets_for_reserve(num_elements); if (required_buckets > bucket_count()) rehash(required_buckets); } // the return value is a type that can be converted to an iterator // the reason for doing this is that it's not free to find the // iterator pointing at the next element. if you care about the // next iterator, turn the return value into an iterator convertible_to_iterator erase(const_iterator to_erase) { EntryPointer current = to_erase.current; current->destroy_value(); --num_elements; for (EntryPointer next = current + ptrdiff_t(1); !next->is_at_desired_position(); ++current, ++next) { current->emplace(next->distance_from_desired - 1, std::move(next->value)); next->destroy_value(); } return { to_erase.current }; } iterator erase(const_iterator begin_it, const_iterator end_it) { if (begin_it == end_it) return { begin_it.current }; for (EntryPointer it = begin_it.current, end = end_it.current; it != end; ++it) { if (it->has_value()) { it->destroy_value(); --num_elements; } } if (end_it == this->end()) return this->end(); ptrdiff_t num_to_move = std::min(static_cast(end_it.current->distance_from_desired), end_it.current - begin_it.current); EntryPointer to_return = end_it.current - num_to_move; for (EntryPointer it = end_it.current; !it->is_at_desired_position();) { EntryPointer target = it - num_to_move; target->emplace(it->distance_from_desired - num_to_move, std::move(it->value)); it->destroy_value(); ++it; num_to_move = std::min(static_cast(it->distance_from_desired), num_to_move); } return { to_return }; } size_t erase(const FindKey& key) { auto found = find(key); if (found == end()) return 0; else { erase(found); return 1; } } void clear() { for (EntryPointer it = entries, end = it + static_cast(num_slots_minus_one + max_lookups); it != end; ++it) { if (it->has_value()) it->destroy_value(); } num_elements = 0; } void shrink_to_fit() { rehash_for_other_container(*this); } void swap(sherwood_v3_table& other) { using std::swap; swap_pointers(other); swap(static_cast(*this), static_cast(other)); swap(static_cast(*this), static_cast(other)); if (AllocatorTraits::propagate_on_container_swap::value) swap(static_cast(*this), static_cast(other)); } size_t size() const { return num_elements; } size_t max_size() const { return (AllocatorTraits::max_size(*this)) / sizeof(Entry); } size_t bucket_count() const { return num_slots_minus_one ? num_slots_minus_one + 1 : 0; } size_type max_bucket_count() const { return (AllocatorTraits::max_size(*this) - min_lookups) / sizeof(Entry); } size_t bucket(const FindKey& key) const { return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); } float load_factor() const { size_t buckets = bucket_count(); if (buckets) return static_cast(num_elements) / bucket_count(); else return 0; } void max_load_factor(float value) { _max_load_factor = value; } float max_load_factor() const { return _max_load_factor; } bool empty() const { return num_elements == 0; } private: EntryPointer entries = Entry::empty_default_table(); size_t num_slots_minus_one = 0; typename HashPolicySelector::type hash_policy; int8_t max_lookups = detailv3::min_lookups - 1; float _max_load_factor = 0.5f; size_t num_elements = 0; static int8_t compute_max_lookups(size_t num_buckets) { int8_t desired = detailv3::log2(num_buckets); return std::max(detailv3::min_lookups, desired); } size_t num_buckets_for_reserve(size_t num_elements) const { return static_cast(std::ceil(num_elements / std::min(0.5, static_cast(_max_load_factor)))); } void rehash_for_other_container(const sherwood_v3_table& other) { rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count())); } void swap_pointers(sherwood_v3_table& other) { using std::swap; swap(hash_policy, other.hash_policy); swap(entries, other.entries); swap(num_slots_minus_one, other.num_slots_minus_one); swap(num_elements, other.num_elements); swap(max_lookups, other.max_lookups); swap(_max_load_factor, other._max_load_factor); } template SKA_NOINLINE(std::pair) emplace_new_key(int8_t distance_from_desired, EntryPointer current_entry, Key&& key, Args &&... args) { using std::swap; if (num_slots_minus_one == 0 || distance_from_desired == max_lookups || num_elements + 1 > (num_slots_minus_one + 1) * static_cast(_max_load_factor)) { grow(); return emplace(std::forward(key), std::forward(args)...); } else if (current_entry->is_empty()) { current_entry->emplace(distance_from_desired, std::forward(key), std::forward(args)...); ++num_elements; return { { current_entry }, true }; } value_type to_insert(std::forward(key), std::forward(args)...); swap(distance_from_desired, current_entry->distance_from_desired); swap(to_insert, current_entry->value); iterator result = { current_entry }; for (++distance_from_desired, ++current_entry;; ++current_entry) { if (current_entry->is_empty()) { current_entry->emplace(distance_from_desired, std::move(to_insert)); ++num_elements; return { result, true }; } else if (current_entry->distance_from_desired < distance_from_desired) { swap(distance_from_desired, current_entry->distance_from_desired); swap(to_insert, current_entry->value); ++distance_from_desired; } else { ++distance_from_desired; if (distance_from_desired == max_lookups) { swap(to_insert, result.current->value); grow(); return emplace(std::move(to_insert)); } } } } void grow() { rehash(std::max(size_t(4), 2 * bucket_count())); } void deallocate_data(EntryPointer begin, size_t num_slots_minus_one, int8_t max_lookups) { if (begin != Entry::empty_default_table()) { AllocatorTraits::deallocate(*this, begin, num_slots_minus_one + max_lookups + 1); } } void reset_to_empty_state() { deallocate_data(entries, num_slots_minus_one, max_lookups); entries = Entry::empty_default_table(); num_slots_minus_one = 0; hash_policy.reset(); max_lookups = detailv3::min_lookups - 1; } template size_t hash_object(const U& key) { return static_cast(*this)(key); } template size_t hash_object(const U& key) const { return static_cast(*this)(key); } template bool compares_equal(const L& lhs, const R& rhs) { return static_cast(*this)(lhs, rhs); } struct convertible_to_iterator { EntryPointer it; operator iterator() { if (it->has_value()) return { it }; else return ++iterator{ it }; } operator const_iterator() { if (it->has_value()) return { it }; else return ++const_iterator{ it }; } }; }; } struct prime_number_hash_policy { static size_t mod0(size_t) { return 0llu; } static size_t mod2(size_t hash) { return hash % 2llu; } static size_t mod3(size_t hash) { return hash % 3llu; } static size_t mod5(size_t hash) { return hash % 5llu; } static size_t mod7(size_t hash) { return hash % 7llu; } static size_t mod11(size_t hash) { return hash % 11llu; } static size_t mod13(size_t hash) { return hash % 13llu; } static size_t mod17(size_t hash) { return hash % 17llu; } static size_t mod23(size_t hash) { return hash % 23llu; } static size_t mod29(size_t hash) { return hash % 29llu; } static size_t mod37(size_t hash) { return hash % 37llu; } static size_t mod47(size_t hash) { return hash % 47llu; } static size_t mod59(size_t hash) { return hash % 59llu; } static size_t mod73(size_t hash) { return hash % 73llu; } static size_t mod97(size_t hash) { return hash % 97llu; } static size_t mod127(size_t hash) { return hash % 127llu; } static size_t mod151(size_t hash) { return hash % 151llu; } static size_t mod197(size_t hash) { return hash % 197llu; } static size_t mod251(size_t hash) { return hash % 251llu; } static size_t mod313(size_t hash) { return hash % 313llu; } static size_t mod397(size_t hash) { return hash % 397llu; } static size_t mod499(size_t hash) { return hash % 499llu; } static size_t mod631(size_t hash) { return hash % 631llu; } static size_t mod797(size_t hash) { return hash % 797llu; } static size_t mod1009(size_t hash) { return hash % 1009llu; } static size_t mod1259(size_t hash) { return hash % 1259llu; } static size_t mod1597(size_t hash) { return hash % 1597llu; } static size_t mod2011(size_t hash) { return hash % 2011llu; } static size_t mod2539(size_t hash) { return hash % 2539llu; } static size_t mod3203(size_t hash) { return hash % 3203llu; } static size_t mod4027(size_t hash) { return hash % 4027llu; } static size_t mod5087(size_t hash) { return hash % 5087llu; } static size_t mod6421(size_t hash) { return hash % 6421llu; } static size_t mod8089(size_t hash) { return hash % 8089llu; } static size_t mod10193(size_t hash) { return hash % 10193llu; } static size_t mod12853(size_t hash) { return hash % 12853llu; } static size_t mod16193(size_t hash) { return hash % 16193llu; } static size_t mod20399(size_t hash) { return hash % 20399llu; } static size_t mod25717(size_t hash) { return hash % 25717llu; } static size_t mod32401(size_t hash) { return hash % 32401llu; } static size_t mod40823(size_t hash) { return hash % 40823llu; } static size_t mod51437(size_t hash) { return hash % 51437llu; } static size_t mod64811(size_t hash) { return hash % 64811llu; } static size_t mod81649(size_t hash) { return hash % 81649llu; } static size_t mod102877(size_t hash) { return hash % 102877llu; } static size_t mod129607(size_t hash) { return hash % 129607llu; } static size_t mod163307(size_t hash) { return hash % 163307llu; } static size_t mod205759(size_t hash) { return hash % 205759llu; } static size_t mod259229(size_t hash) { return hash % 259229llu; } static size_t mod326617(size_t hash) { return hash % 326617llu; } static size_t mod411527(size_t hash) { return hash % 411527llu; } static size_t mod518509(size_t hash) { return hash % 518509llu; } static size_t mod653267(size_t hash) { return hash % 653267llu; } static size_t mod823117(size_t hash) { return hash % 823117llu; } static size_t mod1037059(size_t hash) { return hash % 1037059llu; } static size_t mod1306601(size_t hash) { return hash % 1306601llu; } static size_t mod1646237(size_t hash) { return hash % 1646237llu; } static size_t mod2074129(size_t hash) { return hash % 2074129llu; } static size_t mod2613229(size_t hash) { return hash % 2613229llu; } static size_t mod3292489(size_t hash) { return hash % 3292489llu; } static size_t mod4148279(size_t hash) { return hash % 4148279llu; } static size_t mod5226491(size_t hash) { return hash % 5226491llu; } static size_t mod6584983(size_t hash) { return hash % 6584983llu; } static size_t mod8296553(size_t hash) { return hash % 8296553llu; } static size_t mod10453007(size_t hash) { return hash % 10453007llu; } static size_t mod13169977(size_t hash) { return hash % 13169977llu; } static size_t mod16593127(size_t hash) { return hash % 16593127llu; } static size_t mod20906033(size_t hash) { return hash % 20906033llu; } static size_t mod26339969(size_t hash) { return hash % 26339969llu; } static size_t mod33186281(size_t hash) { return hash % 33186281llu; } static size_t mod41812097(size_t hash) { return hash % 41812097llu; } static size_t mod52679969(size_t hash) { return hash % 52679969llu; } static size_t mod66372617(size_t hash) { return hash % 66372617llu; } static size_t mod83624237(size_t hash) { return hash % 83624237llu; } static size_t mod105359939(size_t hash) { return hash % 105359939llu; } static size_t mod132745199(size_t hash) { return hash % 132745199llu; } static size_t mod167248483(size_t hash) { return hash % 167248483llu; } static size_t mod210719881(size_t hash) { return hash % 210719881llu; } static size_t mod265490441(size_t hash) { return hash % 265490441llu; } static size_t mod334496971(size_t hash) { return hash % 334496971llu; } static size_t mod421439783(size_t hash) { return hash % 421439783llu; } static size_t mod530980861(size_t hash) { return hash % 530980861llu; } static size_t mod668993977(size_t hash) { return hash % 668993977llu; } static size_t mod842879579(size_t hash) { return hash % 842879579llu; } static size_t mod1061961721(size_t hash) { return hash % 1061961721llu; } static size_t mod1337987929(size_t hash) { return hash % 1337987929llu; } static size_t mod1685759167(size_t hash) { return hash % 1685759167llu; } static size_t mod2123923447(size_t hash) { return hash % 2123923447llu; } static size_t mod2675975881(size_t hash) { return hash % 2675975881llu; } static size_t mod3371518343(size_t hash) { return hash % 3371518343llu; } static size_t mod4247846927(size_t hash) { return hash % 4247846927llu; } static size_t mod5351951779(size_t hash) { return hash % 5351951779llu; } static size_t mod6743036717(size_t hash) { return hash % 6743036717llu; } static size_t mod8495693897(size_t hash) { return hash % 8495693897llu; } static size_t mod10703903591(size_t hash) { return hash % 10703903591llu; } static size_t mod13486073473(size_t hash) { return hash % 13486073473llu; } static size_t mod16991387857(size_t hash) { return hash % 16991387857llu; } static size_t mod21407807219(size_t hash) { return hash % 21407807219llu; } static size_t mod26972146961(size_t hash) { return hash % 26972146961llu; } static size_t mod33982775741(size_t hash) { return hash % 33982775741llu; } static size_t mod42815614441(size_t hash) { return hash % 42815614441llu; } static size_t mod53944293929(size_t hash) { return hash % 53944293929llu; } static size_t mod67965551447(size_t hash) { return hash % 67965551447llu; } static size_t mod85631228929(size_t hash) { return hash % 85631228929llu; } static size_t mod107888587883(size_t hash) { return hash % 107888587883llu; } static size_t mod135931102921(size_t hash) { return hash % 135931102921llu; } static size_t mod171262457903(size_t hash) { return hash % 171262457903llu; } static size_t mod215777175787(size_t hash) { return hash % 215777175787llu; } static size_t mod271862205833(size_t hash) { return hash % 271862205833llu; } static size_t mod342524915839(size_t hash) { return hash % 342524915839llu; } static size_t mod431554351609(size_t hash) { return hash % 431554351609llu; } static size_t mod543724411781(size_t hash) { return hash % 543724411781llu; } static size_t mod685049831731(size_t hash) { return hash % 685049831731llu; } static size_t mod863108703229(size_t hash) { return hash % 863108703229llu; } static size_t mod1087448823553(size_t hash) { return hash % 1087448823553llu; } static size_t mod1370099663459(size_t hash) { return hash % 1370099663459llu; } static size_t mod1726217406467(size_t hash) { return hash % 1726217406467llu; } static size_t mod2174897647073(size_t hash) { return hash % 2174897647073llu; } static size_t mod2740199326961(size_t hash) { return hash % 2740199326961llu; } static size_t mod3452434812973(size_t hash) { return hash % 3452434812973llu; } static size_t mod4349795294267(size_t hash) { return hash % 4349795294267llu; } static size_t mod5480398654009(size_t hash) { return hash % 5480398654009llu; } static size_t mod6904869625999(size_t hash) { return hash % 6904869625999llu; } static size_t mod8699590588571(size_t hash) { return hash % 8699590588571llu; } static size_t mod10960797308051(size_t hash) { return hash % 10960797308051llu; } static size_t mod13809739252051(size_t hash) { return hash % 13809739252051llu; } static size_t mod17399181177241(size_t hash) { return hash % 17399181177241llu; } static size_t mod21921594616111(size_t hash) { return hash % 21921594616111llu; } static size_t mod27619478504183(size_t hash) { return hash % 27619478504183llu; } static size_t mod34798362354533(size_t hash) { return hash % 34798362354533llu; } static size_t mod43843189232363(size_t hash) { return hash % 43843189232363llu; } static size_t mod55238957008387(size_t hash) { return hash % 55238957008387llu; } static size_t mod69596724709081(size_t hash) { return hash % 69596724709081llu; } static size_t mod87686378464759(size_t hash) { return hash % 87686378464759llu; } static size_t mod110477914016779(size_t hash) { return hash % 110477914016779llu; } static size_t mod139193449418173(size_t hash) { return hash % 139193449418173llu; } static size_t mod175372756929481(size_t hash) { return hash % 175372756929481llu; } static size_t mod220955828033581(size_t hash) { return hash % 220955828033581llu; } static size_t mod278386898836457(size_t hash) { return hash % 278386898836457llu; } static size_t mod350745513859007(size_t hash) { return hash % 350745513859007llu; } static size_t mod441911656067171(size_t hash) { return hash % 441911656067171llu; } static size_t mod556773797672909(size_t hash) { return hash % 556773797672909llu; } static size_t mod701491027718027(size_t hash) { return hash % 701491027718027llu; } static size_t mod883823312134381(size_t hash) { return hash % 883823312134381llu; } static size_t mod1113547595345903(size_t hash) { return hash % 1113547595345903llu; } static size_t mod1402982055436147(size_t hash) { return hash % 1402982055436147llu; } static size_t mod1767646624268779(size_t hash) { return hash % 1767646624268779llu; } static size_t mod2227095190691797(size_t hash) { return hash % 2227095190691797llu; } static size_t mod2805964110872297(size_t hash) { return hash % 2805964110872297llu; } static size_t mod3535293248537579(size_t hash) { return hash % 3535293248537579llu; } static size_t mod4454190381383713(size_t hash) { return hash % 4454190381383713llu; } static size_t mod5611928221744609(size_t hash) { return hash % 5611928221744609llu; } static size_t mod7070586497075177(size_t hash) { return hash % 7070586497075177llu; } static size_t mod8908380762767489(size_t hash) { return hash % 8908380762767489llu; } static size_t mod11223856443489329(size_t hash) { return hash % 11223856443489329llu; } static size_t mod14141172994150357(size_t hash) { return hash % 14141172994150357llu; } static size_t mod17816761525534927(size_t hash) { return hash % 17816761525534927llu; } static size_t mod22447712886978529(size_t hash) { return hash % 22447712886978529llu; } static size_t mod28282345988300791(size_t hash) { return hash % 28282345988300791llu; } static size_t mod35633523051069991(size_t hash) { return hash % 35633523051069991llu; } static size_t mod44895425773957261(size_t hash) { return hash % 44895425773957261llu; } static size_t mod56564691976601587(size_t hash) { return hash % 56564691976601587llu; } static size_t mod71267046102139967(size_t hash) { return hash % 71267046102139967llu; } static size_t mod89790851547914507(size_t hash) { return hash % 89790851547914507llu; } static size_t mod113129383953203213(size_t hash) { return hash % 113129383953203213llu; } static size_t mod142534092204280003(size_t hash) { return hash % 142534092204280003llu; } static size_t mod179581703095829107(size_t hash) { return hash % 179581703095829107llu; } static size_t mod226258767906406483(size_t hash) { return hash % 226258767906406483llu; } static size_t mod285068184408560057(size_t hash) { return hash % 285068184408560057llu; } static size_t mod359163406191658253(size_t hash) { return hash % 359163406191658253llu; } static size_t mod452517535812813007(size_t hash) { return hash % 452517535812813007llu; } static size_t mod570136368817120201(size_t hash) { return hash % 570136368817120201llu; } static size_t mod718326812383316683(size_t hash) { return hash % 718326812383316683llu; } static size_t mod905035071625626043(size_t hash) { return hash % 905035071625626043llu; } static size_t mod1140272737634240411(size_t hash) { return hash % 1140272737634240411llu; } static size_t mod1436653624766633509(size_t hash) { return hash % 1436653624766633509llu; } static size_t mod1810070143251252131(size_t hash) { return hash % 1810070143251252131llu; } static size_t mod2280545475268481167(size_t hash) { return hash % 2280545475268481167llu; } static size_t mod2873307249533267101(size_t hash) { return hash % 2873307249533267101llu; } static size_t mod3620140286502504283(size_t hash) { return hash % 3620140286502504283llu; } static size_t mod4561090950536962147(size_t hash) { return hash % 4561090950536962147llu; } static size_t mod5746614499066534157(size_t hash) { return hash % 5746614499066534157llu; } static size_t mod7240280573005008577(size_t hash) { return hash % 7240280573005008577llu; } static size_t mod9122181901073924329(size_t hash) { return hash % 9122181901073924329llu; } static size_t mod11493228998133068689(size_t hash) { return hash % 11493228998133068689llu; } static size_t mod14480561146010017169(size_t hash) { return hash % 14480561146010017169llu; } static size_t mod18446744073709551557(size_t hash) { return hash % 18446744073709551557llu; } using mod_function = size_t(*)(size_t); mod_function next_size_over(size_t& size) const { // prime numbers generated by the following method: // 1. start with a prime p = 2 // 2. go to wolfram alpha and get p = NextPrime(2 * p) // 3. repeat 2. until you overflow 64 bits // you now have large gaps which you would hit if somebody called reserve() with an unlucky number. // 4. to fill the gaps for every prime p go to wolfram alpha and get ClosestPrime(p * 2^(1/3)) and ClosestPrime(p * 2^(2/3)) and put those in the gaps // 5. get PrevPrime(2^64) and put it at the end static constexpr const size_t prime_list[] = { 2llu, 3llu, 5llu, 7llu, 11llu, 13llu, 17llu, 23llu, 29llu, 37llu, 47llu, 59llu, 73llu, 97llu, 127llu, 151llu, 197llu, 251llu, 313llu, 397llu, 499llu, 631llu, 797llu, 1009llu, 1259llu, 1597llu, 2011llu, 2539llu, 3203llu, 4027llu, 5087llu, 6421llu, 8089llu, 10193llu, 12853llu, 16193llu, 20399llu, 25717llu, 32401llu, 40823llu, 51437llu, 64811llu, 81649llu, 102877llu, 129607llu, 163307llu, 205759llu, 259229llu, 326617llu, 411527llu, 518509llu, 653267llu, 823117llu, 1037059llu, 1306601llu, 1646237llu, 2074129llu, 2613229llu, 3292489llu, 4148279llu, 5226491llu, 6584983llu, 8296553llu, 10453007llu, 13169977llu, 16593127llu, 20906033llu, 26339969llu, 33186281llu, 41812097llu, 52679969llu, 66372617llu, 83624237llu, 105359939llu, 132745199llu, 167248483llu, 210719881llu, 265490441llu, 334496971llu, 421439783llu, 530980861llu, 668993977llu, 842879579llu, 1061961721llu, 1337987929llu, 1685759167llu, 2123923447llu, 2675975881llu, 3371518343llu, 4247846927llu, 5351951779llu, 6743036717llu, 8495693897llu, 10703903591llu, 13486073473llu, 16991387857llu, 21407807219llu, 26972146961llu, 33982775741llu, 42815614441llu, 53944293929llu, 67965551447llu, 85631228929llu, 107888587883llu, 135931102921llu, 171262457903llu, 215777175787llu, 271862205833llu, 342524915839llu, 431554351609llu, 543724411781llu, 685049831731llu, 863108703229llu, 1087448823553llu, 1370099663459llu, 1726217406467llu, 2174897647073llu, 2740199326961llu, 3452434812973llu, 4349795294267llu, 5480398654009llu, 6904869625999llu, 8699590588571llu, 10960797308051llu, 13809739252051llu, 17399181177241llu, 21921594616111llu, 27619478504183llu, 34798362354533llu, 43843189232363llu, 55238957008387llu, 69596724709081llu, 87686378464759llu, 110477914016779llu, 139193449418173llu, 175372756929481llu, 220955828033581llu, 278386898836457llu, 350745513859007llu, 441911656067171llu, 556773797672909llu, 701491027718027llu, 883823312134381llu, 1113547595345903llu, 1402982055436147llu, 1767646624268779llu, 2227095190691797llu, 2805964110872297llu, 3535293248537579llu, 4454190381383713llu, 5611928221744609llu, 7070586497075177llu, 8908380762767489llu, 11223856443489329llu, 14141172994150357llu, 17816761525534927llu, 22447712886978529llu, 28282345988300791llu, 35633523051069991llu, 44895425773957261llu, 56564691976601587llu, 71267046102139967llu, 89790851547914507llu, 113129383953203213llu, 142534092204280003llu, 179581703095829107llu, 226258767906406483llu, 285068184408560057llu, 359163406191658253llu, 452517535812813007llu, 570136368817120201llu, 718326812383316683llu, 905035071625626043llu, 1140272737634240411llu, 1436653624766633509llu, 1810070143251252131llu, 2280545475268481167llu, 2873307249533267101llu, 3620140286502504283llu, 4561090950536962147llu, 5746614499066534157llu, 7240280573005008577llu, 9122181901073924329llu, 11493228998133068689llu, 14480561146010017169llu, 18446744073709551557llu }; static constexpr size_t(* const mod_functions[])(size_t) = { &mod0, &mod2, &mod3, &mod5, &mod7, &mod11, &mod13, &mod17, &mod23, &mod29, &mod37, &mod47, &mod59, &mod73, &mod97, &mod127, &mod151, &mod197, &mod251, &mod313, &mod397, &mod499, &mod631, &mod797, &mod1009, &mod1259, &mod1597, &mod2011, &mod2539, &mod3203, &mod4027, &mod5087, &mod6421, &mod8089, &mod10193, &mod12853, &mod16193, &mod20399, &mod25717, &mod32401, &mod40823, &mod51437, &mod64811, &mod81649, &mod102877, &mod129607, &mod163307, &mod205759, &mod259229, &mod326617, &mod411527, &mod518509, &mod653267, &mod823117, &mod1037059, &mod1306601, &mod1646237, &mod2074129, &mod2613229, &mod3292489, &mod4148279, &mod5226491, &mod6584983, &mod8296553, &mod10453007, &mod13169977, &mod16593127, &mod20906033, &mod26339969, &mod33186281, &mod41812097, &mod52679969, &mod66372617, &mod83624237, &mod105359939, &mod132745199, &mod167248483, &mod210719881, &mod265490441, &mod334496971, &mod421439783, &mod530980861, &mod668993977, &mod842879579, &mod1061961721, &mod1337987929, &mod1685759167, &mod2123923447, &mod2675975881, &mod3371518343, &mod4247846927, &mod5351951779, &mod6743036717, &mod8495693897, &mod10703903591, &mod13486073473, &mod16991387857, &mod21407807219, &mod26972146961, &mod33982775741, &mod42815614441, &mod53944293929, &mod67965551447, &mod85631228929, &mod107888587883, &mod135931102921, &mod171262457903, &mod215777175787, &mod271862205833, &mod342524915839, &mod431554351609, &mod543724411781, &mod685049831731, &mod863108703229, &mod1087448823553, &mod1370099663459, &mod1726217406467, &mod2174897647073, &mod2740199326961, &mod3452434812973, &mod4349795294267, &mod5480398654009, &mod6904869625999, &mod8699590588571, &mod10960797308051, &mod13809739252051, &mod17399181177241, &mod21921594616111, &mod27619478504183, &mod34798362354533, &mod43843189232363, &mod55238957008387, &mod69596724709081, &mod87686378464759, &mod110477914016779, &mod139193449418173, &mod175372756929481, &mod220955828033581, &mod278386898836457, &mod350745513859007, &mod441911656067171, &mod556773797672909, &mod701491027718027, &mod883823312134381, &mod1113547595345903, &mod1402982055436147, &mod1767646624268779, &mod2227095190691797, &mod2805964110872297, &mod3535293248537579, &mod4454190381383713, &mod5611928221744609, &mod7070586497075177, &mod8908380762767489, &mod11223856443489329, &mod14141172994150357, &mod17816761525534927, &mod22447712886978529, &mod28282345988300791, &mod35633523051069991, &mod44895425773957261, &mod56564691976601587, &mod71267046102139967, &mod89790851547914507, &mod113129383953203213, &mod142534092204280003, &mod179581703095829107, &mod226258767906406483, &mod285068184408560057, &mod359163406191658253, &mod452517535812813007, &mod570136368817120201, &mod718326812383316683, &mod905035071625626043, &mod1140272737634240411, &mod1436653624766633509, &mod1810070143251252131, &mod2280545475268481167, &mod2873307249533267101, &mod3620140286502504283, &mod4561090950536962147, &mod5746614499066534157, &mod7240280573005008577, &mod9122181901073924329, &mod11493228998133068689, &mod14480561146010017169, &mod18446744073709551557 }; const size_t* found = std::lower_bound(std::begin(prime_list), std::end(prime_list) - 1, size); size = *found; return mod_functions[1 + found - prime_list]; } void commit(mod_function new_mod_function) { current_mod_function = new_mod_function; } void reset() { current_mod_function = &mod0; } size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const { return current_mod_function(hash); } size_t keep_in_range(size_t index, size_t num_slots_minus_one) const { return index > num_slots_minus_one ? current_mod_function(index) : index; } private: mod_function current_mod_function = &mod0; }; struct power_of_two_hash_policy { size_t index_for_hash(size_t hash, size_t num_slots_minus_one) const { return hash & num_slots_minus_one; } size_t keep_in_range(size_t index, size_t num_slots_minus_one) const { return index_for_hash(index, num_slots_minus_one); } int8_t next_size_over(size_t& size) const { size = detailv3::next_power_of_two(size); return 0; } void commit(int8_t) { } void reset() { } }; struct fibonacci_hash_policy { size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const { return (11400714819323198485ull * hash) >> shift; } size_t keep_in_range(size_t index, size_t num_slots_minus_one) const { return index & num_slots_minus_one; } int8_t next_size_over(size_t& size) const { size = std::max(size_t(2), detailv3::next_power_of_two(size)); return 64 - detailv3::log2(size); } void commit(int8_t shift) { this->shift = shift; } void reset() { shift = 63; } private: int8_t shift = 63; }; template, typename E = std::equal_to, typename A = std::allocator > > class flat_hash_map : public detailv3::sherwood_v3_table < std::pair, K, H, detailv3::KeyOrValueHasher, H>, E, detailv3::KeyOrValueEquality, E>, A, typename std::allocator_traits::template rebind_alloc>> > { using Table = detailv3::sherwood_v3_table < std::pair, K, H, detailv3::KeyOrValueHasher, H>, E, detailv3::KeyOrValueEquality, E>, A, typename std::allocator_traits::template rebind_alloc>> >; public: using key_type = K; using mapped_type = V; using Table::Table; flat_hash_map() = default; inline V& operator[](const K& key) { return emplace(key, convertible_to_value()).first->second; } inline V& operator[](K&& key) { return emplace(std::move(key), convertible_to_value()).first->second; } V& at(const K& key) { auto found = this->find(key); if (found == this->end()) throw std::out_of_range("Argument passed to at() was not in the map."); return found->second; } const V& at(const K& key) const { auto found = this->find(key); if (found == this->end()) throw std::out_of_range("Argument passed to at() was not in the map."); return found->second; } using Table::emplace; std::pair emplace() { return emplace(key_type(), convertible_to_value()); } template std::pair insert_or_assign(const key_type& key, M&& m) { auto emplace_result = emplace(key, std::forward(m)); if (!emplace_result.second) emplace_result.first->second = std::forward(m); return emplace_result; } template std::pair insert_or_assign(key_type&& key, M&& m) { auto emplace_result = emplace(std::move(key), std::forward(m)); if (!emplace_result.second) emplace_result.first->second = std::forward(m); return emplace_result; } template typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type& key, M&& m) { return insert_or_assign(key, std::forward(m)).first; } template typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type&& key, M&& m) { return insert_or_assign(std::move(key), std::forward(m)).first; } friend bool operator==(const flat_hash_map& lhs, const flat_hash_map& rhs) { if (lhs.size() != rhs.size()) return false; for (const typename Table::value_type& value : lhs) { auto found = rhs.find(value.first); if (found == rhs.end()) return false; else if (value.second != found->second) return false; } return true; } friend bool operator!=(const flat_hash_map& lhs, const flat_hash_map& rhs) { return !(lhs == rhs); } private: struct convertible_to_value { operator V() const { return V(); } }; }; template, typename E = std::equal_to, typename A = std::allocator > class flat_hash_set : public detailv3::sherwood_v3_table < T, T, H, detailv3::functor_storage, E, detailv3::functor_storage, A, typename std::allocator_traits::template rebind_alloc> > { using Table = detailv3::sherwood_v3_table < T, T, H, detailv3::functor_storage, E, detailv3::functor_storage, A, typename std::allocator_traits::template rebind_alloc> >; public: using key_type = T; using Table::Table; flat_hash_set() = default; template std::pair emplace(Args &&... args) { return Table::emplace(T(std::forward(args)...)); } std::pair emplace(const key_type& arg) { return Table::emplace(arg); } std::pair emplace(key_type& arg) { return Table::emplace(arg); } std::pair emplace(const key_type&& arg) { return Table::emplace(std::move(arg)); } std::pair emplace(key_type&& arg) { return Table::emplace(std::move(arg)); } friend bool operator==(const flat_hash_set& lhs, const flat_hash_set& rhs) { if (lhs.size() != rhs.size()) return false; for (const T& value : lhs) { if (rhs.find(value) == rhs.end()) return false; } return true; } friend bool operator!=(const flat_hash_set& lhs, const flat_hash_set& rhs) { return !(lhs == rhs); } }; template struct power_of_two_std_hash : std::hash { typedef ska::power_of_two_hash_policy hash_policy; }; } // end namespace ska ================================================ FILE: src/util/containers/robin_hood.h ================================================ // ______ _____ ______ _________ // ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ / // __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ / // _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ / // /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ // _/_____/ // // Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20 // https://github.com/martinus/robin-hood-hashing // // Licensed under the MIT License . // SPDX-License-Identifier: MIT // Copyright (c) 2018-2020 Martin Ankerl // // 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. #ifndef ROBIN_HOOD_H_INCLUDED #define ROBIN_HOOD_H_INCLUDED // see https://semver.org/ #define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes #define ROBIN_HOOD_VERSION_MINOR 10 // for adding functionality in a backwards-compatible manner #define ROBIN_HOOD_VERSION_PATCH 0 // for backwards-compatible bug fixes #include #include #include #include #include // only to support hash of smart pointers #include #include #include #include #if __cplusplus >= 201703L # include #endif // #define ROBIN_HOOD_LOG_ENABLED #ifdef ROBIN_HOOD_LOG_ENABLED # include # define ROBIN_HOOD_LOG(...) \ std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; #else # define ROBIN_HOOD_LOG(x) #endif // #define ROBIN_HOOD_TRACE_ENABLED #ifdef ROBIN_HOOD_TRACE_ENABLED # include # define ROBIN_HOOD_TRACE(...) \ std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; #else # define ROBIN_HOOD_TRACE(x) #endif // #define ROBIN_HOOD_COUNT_ENABLED #ifdef ROBIN_HOOD_COUNT_ENABLED # include # define ROBIN_HOOD_COUNT(x) ++counts().x; namespace robin_hood { struct Counts { uint64_t shiftUp{}; uint64_t shiftDown{}; }; inline std::ostream& operator<<(std::ostream& os, Counts const& c) { return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl; } static Counts& counts() { static Counts counts{}; return counts; } } // namespace robin_hood #else # define ROBIN_HOOD_COUNT(x) #endif // all non-argument macros should use this facility. See // https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ #define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() // mark unused members with this macro #define ROBIN_HOOD_UNUSED(identifier) // bitness #if SIZE_MAX == UINT32_MAX # define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32 #elif SIZE_MAX == UINT64_MAX # define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64 #else # error Unsupported bitness #endif // endianess #ifdef _MSC_VER # define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 # define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 #else # define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \ (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) # define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #endif // inline #ifdef _MSC_VER # define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) #else # define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) #endif // exceptions #if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) # define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0 #else # define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1 #endif // count leading/trailing bits #if !defined(ROBIN_HOOD_DISABLE_INTRINSICS) # ifdef _MSC_VER # if ROBIN_HOOD(BITNESS) == 32 # define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward # else # define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 # endif # include # pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) # define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ [](size_t mask) noexcept -> int { \ unsigned long index; \ return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ : ROBIN_HOOD(BITNESS); \ }(x) # else # if ROBIN_HOOD(BITNESS) == 32 # define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl # define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl # else # define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll # define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll # endif # define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) # define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) # endif #endif // fallthrough #ifndef __has_cpp_attribute // For backwards compatibility # define __has_cpp_attribute(x) 0 #endif #if __has_cpp_attribute(clang::fallthrough) # define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]] #elif __has_cpp_attribute(gnu::fallthrough) # define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]] #else # define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() #endif // likely/unlikely #ifdef _MSC_VER # define ROBIN_HOOD_LIKELY(condition) condition # define ROBIN_HOOD_UNLIKELY(condition) condition #else # define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1) # define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) #endif // detect if native wchar_t type is availiable in MSVC #ifdef _MSC_VER # ifdef _NATIVE_WCHAR_T_DEFINED # define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 # else # define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0 # endif #else # define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 #endif // workaround missing "is_trivially_copyable" in g++ < 5.0 // See https://stackoverflow.com/a/31798726/48181 #if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__) # define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) #else # define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value #endif // helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html #define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus #define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L #define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L #define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L #define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L #if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) # define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]] #else # define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() #endif namespace robin_hood { #if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) # define ROBIN_HOOD_STD std #else // c++11 compatibility layer namespace ROBIN_HOOD_STD { template struct alignment_of : std::integral_constant::type)> {}; template class integer_sequence { public: using value_type = T; static_assert(std::is_integral::value, "not integral type"); static constexpr std::size_t size() noexcept { return sizeof...(Ints); } }; template using index_sequence = integer_sequence; namespace detail_ { template struct IntSeqImpl { using TValue = T; static_assert(std::is_integral::value, "not integral type"); static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); template struct IntSeqCombiner; template struct IntSeqCombiner, integer_sequence> { using TResult = integer_sequence; }; using TResult = typename IntSeqCombiner::TResult, typename IntSeqImpl::TResult>::TResult; }; template struct IntSeqImpl { using TValue = T; static_assert(std::is_integral::value, "not integral type"); static_assert(Begin >= 0, "unexpected argument (Begin<0)"); using TResult = integer_sequence; }; template struct IntSeqImpl { using TValue = T; static_assert(std::is_integral::value, "not integral type"); static_assert(Begin >= 0, "unexpected argument (Begin<0)"); using TResult = integer_sequence; }; } // namespace detail_ template using make_integer_sequence = typename detail_::IntSeqImpl::TResult; template using make_index_sequence = make_integer_sequence; template using index_sequence_for = make_index_sequence; } // namespace ROBIN_HOOD_STD #endif namespace detail { // make sure we static_cast to the correct type for hash_int #if ROBIN_HOOD(BITNESS) == 64 using SizeT = uint64_t; #else using SizeT = uint32_t; #endif template T rotr(T x, unsigned k) { return (x >> k) | (x << (8U * sizeof(T) - k)); } // This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to // 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with // care! template inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept { return reinterpret_cast(ptr); } template inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { return reinterpret_cast(ptr); } // make sure this is not inlined as it is slow and dramatically enlarges code, thus making other // inlinings more difficult. Throws are also generally the slow path. template [[noreturn]] ROBIN_HOOD(NOINLINE) #if ROBIN_HOOD(HAS_EXCEPTIONS) void doThrow(Args&&... args) { // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) throw E(std::forward(args)...); } #else void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) { abort(); } #endif template T* assertNotNull(T* t, Args&&... args) { if (ROBIN_HOOD_UNLIKELY(nullptr == t)) { doThrow(std::forward(args)...); } return t; } template inline T unaligned_load(void const* ptr) noexcept { // using memcpy so we don't get into unaligned load problems. // compiler should optimize this very well anyways. T t; std::memcpy(&t, ptr, sizeof(T)); return t; } // Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor, // and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a // pointer. template class BulkPoolAllocator { public: BulkPoolAllocator() noexcept = default; // does not copy anything, just creates a new allocator. BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept : mHead(nullptr) , mListForFree(nullptr) {} BulkPoolAllocator(BulkPoolAllocator&& o) noexcept : mHead(o.mHead) , mListForFree(o.mListForFree) { o.mListForFree = nullptr; o.mHead = nullptr; } BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept { reset(); mHead = o.mHead; mListForFree = o.mListForFree; o.mListForFree = nullptr; o.mHead = nullptr; return *this; } BulkPoolAllocator& // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { // does not do anything return *this; } ~BulkPoolAllocator() noexcept { reset(); } // Deallocates all allocated memory. void reset() noexcept { while (mListForFree) { T* tmp = *mListForFree; ROBIN_HOOD_LOG("std::free") std::free(mListForFree); mListForFree = reinterpret_cast_no_cast_align_warning(tmp); } mHead = nullptr; } // allocates, but does NOT initialize. Use in-place new constructor, e.g. // T* obj = pool.allocate(); // ::new (static_cast(obj)) T(); T* allocate() { T* tmp = mHead; if (!tmp) { tmp = performAllocation(); } mHead = *reinterpret_cast_no_cast_align_warning(tmp); return tmp; } // does not actually deallocate but puts it in store. // make sure you have already called the destructor! e.g. with // obj->~T(); // pool.deallocate(obj); void deallocate(T* obj) noexcept { *reinterpret_cast_no_cast_align_warning(obj) = mHead; mHead = obj; } // Adds an already allocated block of memory to the allocator. This allocator is from now on // responsible for freeing the data (with free()). If the provided data is not large enough to // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor. void addOrFree(void* ptr, const size_t numBytes) noexcept { // calculate number of available elements in ptr if (numBytes < ALIGNMENT + ALIGNED_SIZE) { // not enough data for at least one element. Free and return. ROBIN_HOOD_LOG("std::free") std::free(ptr); } else { ROBIN_HOOD_LOG("add to buffer") add(ptr, numBytes); } } void swap(BulkPoolAllocator& other) noexcept { using std::swap; swap(mHead, other.mHead); swap(mListForFree, other.mListForFree); } private: // iterates the list of allocated memory to calculate how many to alloc next. // Recalculating this each time saves us a size_t member. // This ignores the fact that memory blocks might have been added manually with addOrFree. In // practice, this should not matter much. ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept { auto tmp = mListForFree; size_t numAllocs = MinNumAllocs; while (numAllocs * 2 <= MaxNumAllocs && tmp) { auto x = reinterpret_cast(tmp); tmp = *x; numAllocs *= 2; } return numAllocs; } // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree(). void add(void* ptr, const size_t numBytes) noexcept { const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE; auto data = reinterpret_cast(ptr); // link free list auto x = reinterpret_cast(data); *x = mListForFree; mListForFree = data; // create linked list for newly allocated data auto* const headT = reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); auto* const head = reinterpret_cast(headT); // Visual Studio compiler automatically unrolls this loop, which is pretty cool for (size_t i = 0; i < numElements; ++i) { *reinterpret_cast_no_cast_align_warning(head + i * ALIGNED_SIZE) = head + (i + 1) * ALIGNED_SIZE; } // last one points to 0 *reinterpret_cast_no_cast_align_warning(head + (numElements - 1) * ALIGNED_SIZE) = mHead; mHead = headT; } // Called when no memory is available (mHead == 0). // Don't inline this slow path. ROBIN_HOOD(NOINLINE) T* performAllocation() { size_t const numElementsToAlloc = calcNumElementsToAlloc(); // alloc new memory: [prev |T, T, ... T] size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE << " * " << numElementsToAlloc) add(assertNotNull(std::malloc(bytes)), bytes); return mHead; } // enforce byte alignment of the T's #if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) static constexpr size_t ALIGNMENT = (std::max)(std::alignment_of::value, std::alignment_of::value); #else static const size_t ALIGNMENT = (ROBIN_HOOD_STD::alignment_of::value > ROBIN_HOOD_STD::alignment_of::value) ? ROBIN_HOOD_STD::alignment_of::value : +ROBIN_HOOD_STD::alignment_of::value; // the + is for walkarround #endif static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT; static_assert(MinNumAllocs >= 1, "MinNumAllocs"); static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs"); static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE"); static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod"); static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT"); T* mHead{ nullptr }; T** mListForFree{ nullptr }; }; template struct NodeAllocator; // dummy allocator that does nothing template struct NodeAllocator { // we are not using the data, so just free it. void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { ROBIN_HOOD_LOG("std::free") std::free(ptr); } }; template struct NodeAllocator : public BulkPoolAllocator {}; // dummy hash, unsed as mixer when robin_hood::hash is already used template struct identity_hash { constexpr size_t operator()(T const& obj) const noexcept { return static_cast(obj); } }; // c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making // my own here. namespace swappable { #if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17) using std::swap; template struct nothrow { static const bool value = noexcept(swap(std::declval(), std::declval())); }; #else template struct nothrow { static const bool value = std::is_nothrow_swappable::value; }; #endif } // namespace swappable } // namespace detail struct is_transparent_tag {}; // A custom pair implementation is used in the map because std::pair is not is_trivially_copyable, // which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is // also tested. template struct pair { using first_type = T1; using second_type = T2; template ::value&& std::is_default_constructible::value>::type> constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())) : first() , second() {} // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. explicit constexpr pair(std::pair const& o) noexcept( noexcept(T1(std::declval())) && noexcept(T2(std::declval()))) : first(o.first) , second(o.second) {} // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. explicit constexpr pair(std::pair&& o) noexcept(noexcept( T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) : first(std::move(o.first)) , second(std::move(o.second)) {} constexpr pair(T1&& a, T2&& b) noexcept(noexcept( T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) : first(std::move(a)) , second(std::move(b)) {} template constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward( std::declval()))) && noexcept(T2(std::forward(std::declval())))) : first(std::forward(a)) , second(std::forward(b)) {} template constexpr pair( std::piecewise_construct_t /*unused*/, std::tuple a, std::tuple b) noexcept(noexcept(pair(std::declval&>(), std::declval&>(), ROBIN_HOOD_STD::index_sequence_for(), ROBIN_HOOD_STD::index_sequence_for()))) : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), ROBIN_HOOD_STD::index_sequence_for()) {} // constructor called from the std::piecewise_construct_t ctor template pair(std::tuple& a, std::tuple& b, ROBIN_HOOD_STD::index_sequence /*unused*/, ROBIN_HOOD_STD::index_sequence /*unused*/) noexcept( noexcept(T1(std::forward(std::get( std::declval&>()))...)) && noexcept(T2(std:: forward(std::get( std::declval&>()))...))) : first(std::forward(std::get(a))...) , second(std::forward(std::get(b))...) { // make visual studio compiler happy about warning about unused a & b. // Visual studio's pair implementation disables warning 4100. (void)a; (void)b; } void swap(pair& o) noexcept((detail::swappable::nothrow::value) && (detail::swappable::nothrow::value)) { using std::swap; swap(first, o.first); swap(second, o.second); } T1 first; // NOLINT(misc-non-private-member-variables-in-classes) T2 second; // NOLINT(misc-non-private-member-variables-in-classes) }; template inline void swap(pair& a, pair& b) noexcept( noexcept(std::declval&>().swap(std::declval&>()))) { a.swap(b); } template inline constexpr bool operator==(pair const& x, pair const& y) { return (x.first == y.first) && (x.second == y.second); } template inline constexpr bool operator!=(pair const& x, pair const& y) { return !(x == y); } template inline constexpr bool operator<(pair const& x, pair const& y) noexcept(noexcept( std::declval() < std::declval()) && noexcept(std::declval() < std::declval())) { return x.first < y.first || (!(y.first < x.first) && x.second < y.second); } template inline constexpr bool operator>(pair const& x, pair const& y) { return y < x; } template inline constexpr bool operator<=(pair const& x, pair const& y) { return !(x > y); } template inline constexpr bool operator>=(pair const& x, pair const& y) { return !(x < y); } inline size_t hash_bytes(void const* ptr, size_t len) noexcept { static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); static constexpr uint64_t seed = UINT64_C(0xe17a1465); static constexpr unsigned int r = 47; auto const* const data64 = static_cast(ptr); uint64_t h = seed ^ (len * m); size_t const n_blocks = len / 8; for (size_t i = 0; i < n_blocks; ++i) { auto k = detail::unaligned_load(data64 + i); k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } auto const* const data8 = reinterpret_cast(data64 + n_blocks); switch (len & 7U) { case 7: h ^= static_cast(data8[6]) << 48U; ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH case 6: h ^= static_cast(data8[5]) << 40U; ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH case 5: h ^= static_cast(data8[4]) << 32U; ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH case 4: h ^= static_cast(data8[3]) << 24U; ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH case 3: h ^= static_cast(data8[2]) << 16U; ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH case 2: h ^= static_cast(data8[1]) << 8U; ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH case 1: h ^= static_cast(data8[0]); h *= m; ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH default: break; } h ^= h >> r; h *= m; h ^= h >> r; return static_cast(h); } inline size_t hash_int(uint64_t x) noexcept { // inspired by lemire's strongly universal hashing // https://lemire.me/blog/2018/08/15/fast-strongly-universal-64-bit-hashing-everywhere/ // // Instead of shifts, we use rotations so we don't lose any bits. // // Added a final multiplcation with a constant for more mixing. It is most important that // the lower bits are well mixed. auto h1 = x * UINT64_C(0xA24BAED4963EE407); auto h2 = detail::rotr(x, 32U) * UINT64_C(0x9FB21C651E98DF25); auto h = detail::rotr(h1 + h2, 32U); return static_cast(h); } // A thin wrapper around std::hash, performing an additional simple mixing step of the result. template struct hash : public std::hash { size_t operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) { // call base hash auto result = std::hash::operator()(obj); // return mixed of that, to be save against identity has return hash_int(static_cast(result)); } }; template struct hash> { size_t operator()(std::basic_string const& str) const noexcept { return hash_bytes(str.data(), sizeof(CharT) * str.size()); } }; #if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) template struct hash> { size_t operator()(std::basic_string_view const& sv) const noexcept { return hash_bytes(sv.data(), sizeof(CharT) * sv.size()); } }; #endif template struct hash { size_t operator()(T* ptr) const noexcept { return hash_int(reinterpret_cast(ptr)); } }; template struct hash> { size_t operator()(std::unique_ptr const& ptr) const noexcept { return hash_int(reinterpret_cast(ptr.get())); } }; template struct hash> { size_t operator()(std::shared_ptr const& ptr) const noexcept { return hash_int(reinterpret_cast(ptr.get())); } }; template struct hash::value>::type> { size_t operator()(Enum e) const noexcept { using Underlying = typename std::underlying_type::type; return hash{}(static_cast(e)); } }; #define ROBIN_HOOD_HASH_INT(T) \ template <> \ struct hash { \ size_t operator()(T const& obj) const noexcept { \ return hash_int(static_cast(obj)); \ } \ } #if defined(__GNUC__) && !defined(__clang__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wuseless-cast" #endif // see https://en.cppreference.com/w/cpp/utility/hash ROBIN_HOOD_HASH_INT(bool); ROBIN_HOOD_HASH_INT(char); ROBIN_HOOD_HASH_INT(signed char); ROBIN_HOOD_HASH_INT(unsigned char); ROBIN_HOOD_HASH_INT(char16_t); ROBIN_HOOD_HASH_INT(char32_t); #if ROBIN_HOOD(HAS_NATIVE_WCHART) ROBIN_HOOD_HASH_INT(wchar_t); #endif ROBIN_HOOD_HASH_INT(short); ROBIN_HOOD_HASH_INT(unsigned short); ROBIN_HOOD_HASH_INT(int); ROBIN_HOOD_HASH_INT(unsigned int); ROBIN_HOOD_HASH_INT(long); ROBIN_HOOD_HASH_INT(long long); ROBIN_HOOD_HASH_INT(unsigned long); ROBIN_HOOD_HASH_INT(unsigned long long); #if defined(__GNUC__) && !defined(__clang__) # pragma GCC diagnostic pop #endif namespace detail { template struct void_type { using type = void; }; template struct has_is_transparent : public std::false_type {}; template struct has_is_transparent::type> : public std::true_type {}; // using wrapper classes for hash and key_equal prevents the diamond problem when the same type // is used. see https://stackoverflow.com/a/28771920/48181 template struct WrapHash : public T { WrapHash() = default; explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval()))) : T(o) {} }; template struct WrapKeyEqual : public T { WrapKeyEqual() = default; explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval()))) : T(o) {} }; // A highly optimized hashmap implementation, using the Robin Hood algorithm. // // In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but // be about 2x faster in most cases and require much less allocations. // // This implementation uses the following memory layout: // // [Node, Node, ... Node | info, info, ... infoSentinel ] // // * Node: either a DataNode that directly has the std::pair as member, // or a DataNode with a pointer to std::pair. Which DataNode representation to use // depends on how fast the swap() operation is. Heuristically, this is automatically choosen // based on sizeof(). there are always 2^n Nodes. // // * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. // Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the // corresponding node contains data. Set to 2 means the corresponding Node is filled, but it // actually belongs to the previous position and was pushed out because that place is already // taken. // // * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the // need for a idx variable. // // According to STL, order of templates has effect on throughput. That's why I've moved the // boolean to the front. // https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ template class Table : public WrapHash, public WrapKeyEqual, detail::NodeAllocator< typename std::conditional< std::is_void::value, Key, robin_hood::pair::type, T>>::type, 4, 16384, IsFlat> { public: static constexpr bool is_flat = IsFlat; static constexpr bool is_map = !std::is_void::value; static constexpr bool is_set = !is_map; static constexpr bool is_transparent = has_is_transparent::value && has_is_transparent::value; using key_type = Key; using mapped_type = T; using value_type = typename std::conditional< is_set, Key, robin_hood::pair::type, T>>::type; using size_type = size_t; using hasher = Hash; using key_equal = KeyEqual; using Self = Table; private: static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, "MaxLoadFactor100 needs to be >10 && < 100"); using WHash = WrapHash; using WKeyEqual = WrapKeyEqual; // configuration defaults // make sure we have 8 elements, needed to quickly rehash mInfo static constexpr size_t InitialNumElements = sizeof(uint64_t); static constexpr uint32_t InitialInfoNumBits = 5; static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; static constexpr size_t InfoMask = InitialInfoInc - 1U; static constexpr uint8_t InitialInfoHashShift = 0; using DataPool = detail::NodeAllocator; // type needs to be wider than uint8_t. using InfoType = uint32_t; // DataNode //////////////////////////////////////////////////////// // Primary template for the data node. We have special implementations for small and big // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these // on the heap so swap merely swaps a pointer. template class DataNode {}; // Small: just allocate on the stack. template class DataNode final { public: template explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept( noexcept(value_type(std::forward(args)...))) : mData(std::forward(args)...) {} DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept( std::is_nothrow_move_constructible::value) : mData(std::move(n.mData)) {} // doesn't do anything void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {} void destroyDoNotDeallocate() noexcept {} value_type const* operator->() const noexcept { return &mData; } value_type* operator->() noexcept { return &mData; } const value_type& operator*() const noexcept { return mData; } value_type& operator*() noexcept { return mData; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getFirst() noexcept { return mData.first; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getFirst() noexcept { return mData; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getFirst() const noexcept { return mData.first; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getFirst() const noexcept { return mData; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getSecond() noexcept { return mData.second; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getSecond() const noexcept { return mData.second; } void swap(DataNode& o) noexcept( noexcept(std::declval().swap(std::declval()))) { mData.swap(o.mData); } private: value_type mData; }; // big object: allocate on heap. template class DataNode { public: template explicit DataNode(M& map, Args&&... args) : mData(map.allocate()) { ::new (static_cast(mData)) value_type(std::forward(args)...); } DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept : mData(std::move(n.mData)) {} void destroy(M& map) noexcept { // don't deallocate, just put it into list of datapool. mData->~value_type(); map.deallocate(mData); } void destroyDoNotDeallocate() noexcept { mData->~value_type(); } value_type const* operator->() const noexcept { return mData; } value_type* operator->() noexcept { return mData; } const value_type& operator*() const { return *mData; } value_type& operator*() { return *mData; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getFirst() noexcept { return mData->first; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getFirst() noexcept { return *mData; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getFirst() const noexcept { return mData->first; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getFirst() const noexcept { return *mData; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getSecond() noexcept { return mData->second; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::type getSecond() const noexcept { return mData->second; } void swap(DataNode& o) noexcept { using std::swap; swap(mData, o.mData); } private: value_type* mData; }; using Node = DataNode; // helpers for doInsert: extract first entry (only const required) ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept { return n.getFirst(); } // in case we have void mapped_type, we are not using a pair, thus we just route k through. // No need to disable this because it's just not used if not applicable. ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept { return k; } // in case we have non-void mapped_type, we have a standard robin_hood::pair template ROBIN_HOOD(NODISCARD) typename std::enable_if::value, key_type const&>::type getFirstConst(value_type const& vt) const noexcept { return vt.first; } // Cloner ////////////////////////////////////////////////////////// template struct Cloner; // fast path: Just copy data, without allocating anything. template struct Cloner { void operator()(M const& source, M& target) const { auto const* const src = reinterpret_cast(source.mKeyVals); auto* tgt = reinterpret_cast(target.mKeyVals); auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1); std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt); } }; template struct Cloner { void operator()(M const& s, M& t) const { auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1); std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo); for (size_t i = 0; i < numElementsWithBuffer; ++i) { if (t.mInfo[i]) { ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); } } } }; // Destroyer /////////////////////////////////////////////////////// template struct Destroyer {}; template struct Destroyer { void nodes(M& m) const noexcept { m.mNumElements = 0; } void nodesDoNotDeallocate(M& m) const noexcept { m.mNumElements = 0; } }; template struct Destroyer { void nodes(M& m) const noexcept { m.mNumElements = 0; // clear also resets mInfo to 0, that's sometimes not necessary. auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { if (0 != m.mInfo[idx]) { Node& n = m.mKeyVals[idx]; n.destroy(m); n.~Node(); } } } void nodesDoNotDeallocate(M& m) const noexcept { m.mNumElements = 0; // clear also resets mInfo to 0, that's sometimes not necessary. auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { if (0 != m.mInfo[idx]) { Node& n = m.mKeyVals[idx]; n.destroyDoNotDeallocate(); n.~Node(); } } } }; // Iter //////////////////////////////////////////////////////////// struct fast_forward_tag {}; // generic iterator for both const_iterator and iterator. template // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions) class Iter { private: using NodePtr = typename std::conditional::type; public: using difference_type = std::ptrdiff_t; using value_type = typename Self::value_type; using reference = typename std::conditional::type; using pointer = typename std::conditional::type; using iterator_category = std::forward_iterator_tag; // default constructed iterator can be compared to itself, but WON'T return true when // compared to end(). Iter() = default; // Rule of zero: nothing specified. The conversion constructor is only enabled for // iterator to const_iterator, so it doesn't accidentally work as a copy ctor. // Conversion constructor from iterator to const_iterator. template ::type> // NOLINTNEXTLINE(hicpp-explicit-conversions) Iter(Iter const& other) noexcept : mKeyVals(other.mKeyVals) , mInfo(other.mInfo) {} Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept : mKeyVals(valPtr) , mInfo(infoPtr) {} Iter(NodePtr valPtr, uint8_t const* infoPtr, fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept : mKeyVals(valPtr) , mInfo(infoPtr) { fastForward(); } template ::type> Iter& operator=(Iter const& other) noexcept { mKeyVals = other.mKeyVals; mInfo = other.mInfo; return *this; } // prefix increment. Undefined behavior if we are at end()! Iter& operator++() noexcept { mInfo++; mKeyVals++; fastForward(); return *this; } Iter operator++(int) noexcept { Iter tmp = *this; ++(*this); return tmp; } reference operator*() const { return **mKeyVals; } pointer operator->() const { return &**mKeyVals; } template bool operator==(Iter const& o) const noexcept { return mKeyVals == o.mKeyVals; } template bool operator!=(Iter const& o) const noexcept { return mKeyVals != o.mKeyVals; } private: // fast forward to the next non-free info byte // I've tried a few variants that don't depend on intrinsics, but unfortunately they are // quite a bit slower than this one. So I've reverted that change again. See map_benchmark. void fastForward() noexcept { size_t n = 0; while (0U == (n = detail::unaligned_load(mInfo))) { mInfo += sizeof(size_t); mKeyVals += sizeof(size_t); } #if defined(ROBIN_HOOD_DISABLE_INTRINSICS) // we know for certain that within the next 8 bytes we'll find a non-zero one. if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { mInfo += 4; mKeyVals += 4; } if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { mInfo += 2; mKeyVals += 2; } if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) { mInfo += 1; mKeyVals += 1; } #else # if ROBIN_HOOD(LITTLE_ENDIAN) auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; # else auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; # endif mInfo += inc; mKeyVals += inc; #endif } friend class Table; NodePtr mKeyVals{ nullptr }; uint8_t const* mInfo{ nullptr }; }; //////////////////////////////////////////////////////////////////// // highly performance relevant code. // Lower bits are used for indexing into the array (2^n size) // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. template void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { // for a user-specified hash that is *not* robin_hood::hash, apply robin_hood::hash as // an additional mixing step. This serves as a bad hash prevention, if the given data is // badly mixed. using Mix = typename std::conditional, hasher>::value, ::robin_hood::detail::identity_hash, ::robin_hood::hash>::type; // the lower InitialInfoNumBits are reserved for info. auto h = Mix{}(WHash::operator()(key)); *info = mInfoInc + static_cast((h & InfoMask) >> mInfoHashShift); *idx = (h >> InitialInfoNumBits) & mMask; } // forwards the index by one, wrapping around at the end void next(InfoType* info, size_t* idx) const noexcept { *idx = *idx + 1; *info += mInfoInc; } void nextWhileLess(InfoType* info, size_t* idx) const noexcept { // unrolling this by hand did not bring any speedups. while (*info < mInfo[*idx]) { next(info, idx); } } // Shift everything up by one element. Tries to move stuff around. void shiftUp(size_t startIdx, size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { auto idx = startIdx; ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1])); while (--idx != insertion_idx) { mKeyVals[idx] = std::move(mKeyVals[idx - 1]); } idx = startIdx; while (idx != insertion_idx) { ROBIN_HOOD_COUNT(shiftUp) mInfo[idx] = static_cast(mInfo[idx - 1] + mInfoInc); if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { mMaxNumElementsAllowed = 0; } --idx; } } void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { // until we find one that is either empty or has zero offset. // TODO(martinus) we don't need to move everything, just the last one for the same // bucket. mKeyVals[idx].destroy(*this); // until we find one that is either empty or has zero offset. while (mInfo[idx + 1] >= 2 * mInfoInc) { ROBIN_HOOD_COUNT(shiftDown) mInfo[idx] = static_cast(mInfo[idx + 1] - mInfoInc); mKeyVals[idx] = std::move(mKeyVals[idx + 1]); ++idx; } mInfo[idx] = 0; // don't destroy, we've moved it // mKeyVals[idx].destroy(*this); mKeyVals[idx].~Node(); } // copy of find(), except that it returns iterator instead of const_iterator. template ROBIN_HOOD(NODISCARD) size_t findIdx(Other const& key) const { size_t idx{}; InfoType info{}; keyToIdx(key, &idx, &info); do { // unrolling this twice gives a bit of a speedup. More unrolling did not help. if (info == mInfo[idx] && ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { return idx; } next(&info, &idx); if (info == mInfo[idx] && ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { return idx; } next(&info, &idx); } while (info <= mInfo[idx]); // nothing found! return mMask == 0 ? 0 : static_cast(std::distance( mKeyVals, reinterpret_cast_no_cast_align_warning(mInfo))); } void cloneData(const Table& o) { Cloner()(o, *this); } // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. // @return index where the element was created size_t insert_move(Node&& keyval) { // we don't retry, fail if overflowing // don't need to check max num elements if (0 == mMaxNumElementsAllowed && !try_increase_info()) { throwOverflowError(); // impossible to reach LCOV_EXCL_LINE } size_t idx{}; InfoType info{}; keyToIdx(keyval.getFirst(), &idx, &info); // skip forward. Use <= because we are certain that the element is not there. while (info <= mInfo[idx]) { idx = idx + 1; info += mInfoInc; } // key not found, so we are now exactly where we want to insert it. auto const insertion_idx = idx; auto const insertion_info = static_cast(info); if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { mMaxNumElementsAllowed = 0; } // find an empty spot while (0 != mInfo[idx]) { next(&info, &idx); } auto& l = mKeyVals[insertion_idx]; if (idx == insertion_idx) { ::new (static_cast(&l)) Node(std::move(keyval)); } else { shiftUp(idx, insertion_idx); l = std::move(keyval); } // put at empty spot mInfo[insertion_idx] = insertion_info; ++mNumElements; return insertion_idx; } public: using iterator = Iter; using const_iterator = Iter; Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())) : WHash() , WKeyEqual() { ROBIN_HOOD_TRACE(this) } // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. // This tremendously speeds up ctor & dtor of a map that never receives an element. The // penalty is payed at the first insert, and not before. Lookup of this empty map works // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the // standard, but we can ignore it. explicit Table( size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))) : WHash(h) , WKeyEqual(equal) { ROBIN_HOOD_TRACE(this) } template Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) : WHash(h) , WKeyEqual(equal) { ROBIN_HOOD_TRACE(this) insert(first, last); } Table(std::initializer_list initlist, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) : WHash(h) , WKeyEqual(equal) { ROBIN_HOOD_TRACE(this) insert(initlist.begin(), initlist.end()); } Table(Table&& o) noexcept : WHash(std::move(static_cast(o))) , WKeyEqual(std::move(static_cast(o))) , DataPool(std::move(static_cast(o))) { ROBIN_HOOD_TRACE(this) if (o.mMask) { mKeyVals = std::move(o.mKeyVals); mInfo = std::move(o.mInfo); mNumElements = std::move(o.mNumElements); mMask = std::move(o.mMask); mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); mInfoInc = std::move(o.mInfoInc); mInfoHashShift = std::move(o.mInfoHashShift); // set other's mask to 0 so its destructor won't do anything o.init(); } } Table& operator=(Table&& o) noexcept { ROBIN_HOOD_TRACE(this) if (&o != this) { if (o.mMask) { // only move stuff if the other map actually has some data destroy(); mKeyVals = std::move(o.mKeyVals); mInfo = std::move(o.mInfo); mNumElements = std::move(o.mNumElements); mMask = std::move(o.mMask); mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); mInfoInc = std::move(o.mInfoInc); mInfoHashShift = std::move(o.mInfoHashShift); WHash::operator=(std::move(static_cast(o))); WKeyEqual::operator=(std::move(static_cast(o))); DataPool::operator=(std::move(static_cast(o))); o.init(); } else { // nothing in the other map => just clear us. clear(); } } return *this; } Table(const Table& o) : WHash(static_cast(o)) , WKeyEqual(static_cast(o)) , DataPool(static_cast(o)) { ROBIN_HOOD_TRACE(this) if (!o.empty()) { // not empty: create an exact copy. it is also possible to just iterate through all // elements and insert them, but copying is probably faster. auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" << numElementsWithBuffer << ")") mKeyVals = static_cast( detail::assertNotNull(std::malloc(numBytesTotal))); // no need for calloc because clonData does memcpy mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); mNumElements = o.mNumElements; mMask = o.mMask; mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; mInfoInc = o.mInfoInc; mInfoHashShift = o.mInfoHashShift; cloneData(o); } } // Creates a copy of the given map. Copy constructor of each entry is used. // Not sure why clang-tidy thinks this doesn't handle self assignment, it does // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) Table& operator=(Table const& o) { ROBIN_HOOD_TRACE(this) if (&o == this) { // prevent assigning of itself return *this; } // we keep using the old allocator and not assign the new one, because we want to keep // the memory available. when it is the same size. if (o.empty()) { if (0 == mMask) { // nothing to do, we are empty too return *this; } // not empty: destroy what we have there // clear also resets mInfo to 0, that's sometimes not necessary. destroy(); init(); WHash::operator=(static_cast(o)); WKeyEqual::operator=(static_cast(o)); DataPool::operator=(static_cast(o)); return *this; } // clean up old stuff Destroyer::value>{}.nodes(*this); if (mMask != o.mMask) { // no luck: we don't have the same array size allocated, so we need to realloc. if (0 != mMask) { // only deallocate if we actually have data! ROBIN_HOOD_LOG("std::free") std::free(mKeyVals); } auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" << numElementsWithBuffer << ")") mKeyVals = static_cast( detail::assertNotNull(std::malloc(numBytesTotal))); // no need for calloc here because cloneData performs a memcpy. mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); // sentinel is set in cloneData } WHash::operator=(static_cast(o)); WKeyEqual::operator=(static_cast(o)); DataPool::operator=(static_cast(o)); mNumElements = o.mNumElements; mMask = o.mMask; mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; mInfoInc = o.mInfoInc; mInfoHashShift = o.mInfoHashShift; cloneData(o); return *this; } // Swaps everything between the two maps. void swap(Table& o) { ROBIN_HOOD_TRACE(this) using std::swap; swap(o, *this); } // Clears all data, without resizing. void clear() { ROBIN_HOOD_TRACE(this) if (empty()) { // don't do anything! also important because we don't want to write to // DummyInfoByte::b, even though we would just write 0 to it. return; } Destroyer::value>{}.nodes(*this); auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); // clear everything, then set the sentinel again uint8_t const z = 0; std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z); mInfo[numElementsWithBuffer] = 1; mInfoInc = InitialInfoInc; mInfoHashShift = InitialInfoHashShift; } // Destroys the map and all it's contents. ~Table() { ROBIN_HOOD_TRACE(this) destroy(); } // Checks if both tables contain the same entries. Order is irrelevant. bool operator==(const Table& other) const { ROBIN_HOOD_TRACE(this) if (other.size() != size()) { return false; } for (auto const& otherEntry : other) { if (!has(otherEntry)) { return false; } } return true; } bool operator!=(const Table& other) const { ROBIN_HOOD_TRACE(this) return !operator==(other); } template typename std::enable_if::value, Q&>::type operator[](const key_type& key) { ROBIN_HOOD_TRACE(this) return doCreateByKey(key); } template typename std::enable_if::value, Q&>::type operator[](key_type&& key) { ROBIN_HOOD_TRACE(this) return doCreateByKey(std::move(key)); } template void insert(Iter first, Iter last) { for (; first != last; ++first) { // value_type ctor needed because this might be called with std::pair's insert(value_type(*first)); } } template std::pair emplace(Args&&... args) { ROBIN_HOOD_TRACE(this) Node n { *this, std::forward(args)... }; auto r = doInsert(std::move(n)); if (!r.second) { // insertion not possible: destroy node // NOLINTNEXTLINE(bugprone-use-after-move) n.destroy(*this); } return r; } template std::pair try_emplace(const key_type& key, Args&&... args) { return try_emplace_impl(key, std::forward(args)...); } template std::pair try_emplace(key_type&& key, Args&&... args) { return try_emplace_impl(std::move(key), std::forward(args)...); } template std::pair try_emplace(const_iterator hint, const key_type& key, Args&&... args) { (void)hint; return try_emplace_impl(key, std::forward(args)...); } template std::pair try_emplace(const_iterator hint, key_type&& key, Args&&... args) { (void)hint; return try_emplace_impl(std::move(key), std::forward(args)...); } template std::pair insert_or_assign(const key_type& key, Mapped&& obj) { return insert_or_assign_impl(key, std::forward(obj)); } template std::pair insert_or_assign(key_type&& key, Mapped&& obj) { return insert_or_assign_impl(std::move(key), std::forward(obj)); } template std::pair insert_or_assign(const_iterator hint, const key_type& key, Mapped&& obj) { (void)hint; return insert_or_assign_impl(key, std::forward(obj)); } template std::pair insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { (void)hint; return insert_or_assign_impl(std::move(key), std::forward(obj)); } std::pair insert(const value_type& keyval) { ROBIN_HOOD_TRACE(this) return doInsert(keyval); } std::pair insert(value_type&& keyval) { return doInsert(std::move(keyval)); } // Returns 1 if key is found, 0 otherwise. size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) auto kv = mKeyVals + findIdx(key); if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { return 1; } return 0; } template // NOLINTNEXTLINE(modernize-use-nodiscard) typename std::enable_if::type count(const OtherKey& key) const { ROBIN_HOOD_TRACE(this) auto kv = mKeyVals + findIdx(key); if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { return 1; } return 0; } bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard) return 1U == count(key); } template // NOLINTNEXTLINE(modernize-use-nodiscard) typename std::enable_if::type contains(const OtherKey& key) const { return 1U == count(key); } // Returns a reference to the value found for key. // Throws std::out_of_range if element cannot be found template // NOLINTNEXTLINE(modernize-use-nodiscard) typename std::enable_if::value, Q&>::type at(key_type const& key) { ROBIN_HOOD_TRACE(this) auto kv = mKeyVals + findIdx(key); if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { doThrow("key not found"); } return kv->getSecond(); } // Returns a reference to the value found for key. // Throws std::out_of_range if element cannot be found template // NOLINTNEXTLINE(modernize-use-nodiscard) typename std::enable_if::value, Q const&>::type at(key_type const& key) const { ROBIN_HOOD_TRACE(this) auto kv = mKeyVals + findIdx(key); if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { doThrow("key not found"); } return kv->getSecond(); } const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) const size_t idx = findIdx(key); return const_iterator{ mKeyVals + idx, mInfo + idx }; } template const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { ROBIN_HOOD_TRACE(this) const size_t idx = findIdx(key); return const_iterator{ mKeyVals + idx, mInfo + idx }; } template typename std::enable_if::type // NOLINT(modernize-use-nodiscard) find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) const size_t idx = findIdx(key); return const_iterator{ mKeyVals + idx, mInfo + idx }; } iterator find(const key_type& key) { ROBIN_HOOD_TRACE(this) const size_t idx = findIdx(key); return iterator{ mKeyVals + idx, mInfo + idx }; } template iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { ROBIN_HOOD_TRACE(this) const size_t idx = findIdx(key); return iterator{ mKeyVals + idx, mInfo + idx }; } template typename std::enable_if::type find(const OtherKey& key) { ROBIN_HOOD_TRACE(this) const size_t idx = findIdx(key); return iterator{ mKeyVals + idx, mInfo + idx }; } iterator begin() { ROBIN_HOOD_TRACE(this) if (empty()) { return end(); } return iterator(mKeyVals, mInfo, fast_forward_tag{}); } const_iterator begin() const { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) return cbegin(); } const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) if (empty()) { return cend(); } return const_iterator(mKeyVals, mInfo, fast_forward_tag{}); } iterator end() { ROBIN_HOOD_TRACE(this) // no need to supply valid info pointer: end() must not be dereferenced, and only node // pointer is compared. return iterator{ reinterpret_cast_no_cast_align_warning(mInfo), nullptr }; } const_iterator end() const { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) return cend(); } const_iterator cend() const { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) return const_iterator{ reinterpret_cast_no_cast_align_warning(mInfo), nullptr }; } iterator erase(const_iterator pos) { ROBIN_HOOD_TRACE(this) // its safe to perform const cast here // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) return erase(iterator{ const_cast(pos.mKeyVals), const_cast(pos.mInfo) }); } // Erases element at pos, returns iterator to the next element. iterator erase(iterator pos) { ROBIN_HOOD_TRACE(this) // we assume that pos always points to a valid entry, and not end(). auto const idx = static_cast(pos.mKeyVals - mKeyVals); shiftDown(idx); --mNumElements; if (*pos.mInfo) { // we've backward shifted, return this again return pos; } // no backward shift, return next element return ++pos; } size_t erase(const key_type& key) { ROBIN_HOOD_TRACE(this) size_t idx {}; InfoType info{}; keyToIdx(key, &idx, &info); // check while info matches with the source idx do { if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { shiftDown(idx); --mNumElements; return 1; } next(&info, &idx); } while (info <= mInfo[idx]); // nothing found to delete return 0; } // reserves space for the specified number of elements. Makes sure the old data fits. // exactly the same as reserve(c). void rehash(size_t c) { // forces a reserve reserve(c, true); } // reserves space for the specified number of elements. Makes sure the old data fits. // Exactly the same as rehash(c). Use rehash(0) to shrink to fit. void reserve(size_t c) { // reserve, but don't force rehash reserve(c, false); } // If possible reallocates the map to a smaller one. This frees the underlying table. // Does not do anything if load_factor is too large for decreasing the table's size. void compact() { ROBIN_HOOD_TRACE(this) auto newSize = InitialNumElements; while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) { newSize *= 2; } if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { throwOverflowError(); } ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") // only actually do anything when the new size is bigger than the old one. This prevents to // continuously allocate for each reserve() call. if (newSize < mMask + 1) { rehashPowerOfTwo(newSize, true); } } size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) return mNumElements; } size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) return static_cast(-1); } ROBIN_HOOD(NODISCARD) bool empty() const noexcept { ROBIN_HOOD_TRACE(this) return 0 == mNumElements; } float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) return MaxLoadFactor100 / 100.0F; } // Average number of elements per bucket. Since we allow only 1 per bucket float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) return static_cast(size()) / static_cast(mMask + 1); } ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { ROBIN_HOOD_TRACE(this) return mMask; } ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept { if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits::max)() / 100)) { return maxElements * MaxLoadFactor100 / 100; } // we might be a bit inprecise, but since maxElements is quite large that doesn't matter return (maxElements / 100) * MaxLoadFactor100; } ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept { // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load // 64bit types. return numElements + sizeof(uint64_t); } ROBIN_HOOD(NODISCARD) size_t calcNumElementsWithBuffer(size_t numElements) const noexcept { auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements); return numElements + (std::min)(maxNumElementsAllowed, (static_cast(0xFF))); } // calculation only allowed for 2^n values ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { #if ROBIN_HOOD(BITNESS) == 64 return numElements * sizeof(Node) + calcNumBytesInfo(numElements); #else // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows. auto const ne = static_cast(numElements); auto const s = static_cast(sizeof(Node)); auto const infos = static_cast(calcNumBytesInfo(numElements)); auto const total64 = ne * s + infos; auto const total = static_cast(total64); if (ROBIN_HOOD_UNLIKELY(static_cast(total) != total64)) { throwOverflowError(); } return total; #endif } private: template ROBIN_HOOD(NODISCARD) typename std::enable_if::value, bool>::type has(const value_type& e) const { ROBIN_HOOD_TRACE(this) auto it = find(e.first); return it != end() && it->second == e.second; } template ROBIN_HOOD(NODISCARD) typename std::enable_if::value, bool>::type has(const value_type& e) const { ROBIN_HOOD_TRACE(this) return find(e) != end(); } void reserve(size_t c, bool forceRehash) { ROBIN_HOOD_TRACE(this) auto const minElementsAllowed = (std::max)(c, mNumElements); auto newSize = InitialNumElements; while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { newSize *= 2; } if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { throwOverflowError(); } ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") // only actually do anything when the new size is bigger than the old one. This prevents to // continuously allocate for each reserve() call. if (forceRehash || newSize > mMask + 1) { rehashPowerOfTwo(newSize, false); } } // reserves space for at least the specified number of elements. // only works if numBuckets if power of two void rehashPowerOfTwo(size_t numBuckets, bool forceFree) { ROBIN_HOOD_TRACE(this) Node* const oldKeyVals = mKeyVals; uint8_t const* const oldInfo = mInfo; const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); // resize operation: move stuff init_data(numBuckets); if (oldMaxElementsWithBuffer > 1) { for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) { if (oldInfo[i] != 0) { insert_move(std::move(oldKeyVals[i])); // destroy the node but DON'T destroy the data. oldKeyVals[i].~Node(); } } // this check is not necessary as it's guarded by the previous if, but it helps silence // g++'s overeager "attempt to free a non-heap object 'map' // [-Werror=free-nonheap-object]" warning. if (oldKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { // don't destroy old data: put it into the pool instead if (forceFree) { std::free(oldKeyVals); } else { DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer)); } } } } ROBIN_HOOD(NOINLINE) void throwOverflowError() const { #if ROBIN_HOOD(HAS_EXCEPTIONS) throw std::overflow_error("robin_hood::map overflow"); #else abort(); #endif } template std::pair try_emplace_impl(OtherKey&& key, Args&&... args) { ROBIN_HOOD_TRACE(this) auto it = find(key); if (it == end()) { return emplace(std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple(std::forward(args)...)); } return { it, false }; } template std::pair insert_or_assign_impl(OtherKey&& key, Mapped&& obj) { ROBIN_HOOD_TRACE(this) auto it = find(key); if (it == end()) { return emplace(std::forward(key), std::forward(obj)); } it->second = std::forward(obj); return { it, false }; } void init_data(size_t max_elements) { mNumElements = 0; mMask = max_elements - 1; mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); // calloc also zeroes everything auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal(" << numElementsWithBuffer << ")") mKeyVals = reinterpret_cast( detail::assertNotNull(std::calloc(1, numBytesTotal))); mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); // set sentinel mInfo[numElementsWithBuffer] = 1; mInfoInc = InitialInfoInc; mInfoHashShift = InitialInfoHashShift; } template typename std::enable_if::value, Q&>::type doCreateByKey(Arg&& key) { while (true) { size_t idx{}; InfoType info{}; keyToIdx(key, &idx, &info); nextWhileLess(&info, &idx); // while we potentially have a match. Can't do a do-while here because when mInfo is // 0 we don't want to skip forward while (info == mInfo[idx]) { if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { // key already exists, do not insert. return mKeyVals[idx].getSecond(); } next(&info, &idx); } // unlikely that this evaluates to true if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { increase_size(); continue; } // key not found, so we are now exactly where we want to insert it. auto const insertion_idx = idx; auto const insertion_info = info; if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { mMaxNumElementsAllowed = 0; } // find an empty spot while (0 != mInfo[idx]) { next(&info, &idx); } auto& l = mKeyVals[insertion_idx]; if (idx == insertion_idx) { // put at empty spot. This forwards all arguments into the node where the object // is constructed exactly where it is needed. ::new (static_cast(&l)) Node(*this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()); } else { shiftUp(idx, insertion_idx); l = Node(*this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()); } // mKeyVals[idx].getFirst() = std::move(key); mInfo[insertion_idx] = static_cast(insertion_info); ++mNumElements; return mKeyVals[insertion_idx].getSecond(); } } // This is exactly the same code as operator[], except for the return values template std::pair doInsert(Arg&& keyval) { while (true) { size_t idx{}; InfoType info{}; keyToIdx(getFirstConst(keyval), &idx, &info); nextWhileLess(&info, &idx); // while we potentially have a match while (info == mInfo[idx]) { if (WKeyEqual::operator()(getFirstConst(keyval), mKeyVals[idx].getFirst())) { // key already exists, do NOT insert. // see http://en.cppreference.com/w/cpp/container/unordered_map/insert return std::make_pair(iterator(mKeyVals + idx, mInfo + idx), false); } next(&info, &idx); } // unlikely that this evaluates to true if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { increase_size(); continue; } // key not found, so we are now exactly where we want to insert it. auto const insertion_idx = idx; auto const insertion_info = info; if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { mMaxNumElementsAllowed = 0; } // find an empty spot while (0 != mInfo[idx]) { next(&info, &idx); } auto& l = mKeyVals[insertion_idx]; if (idx == insertion_idx) { ::new (static_cast(&l)) Node(*this, std::forward(keyval)); } else { shiftUp(idx, insertion_idx); l = Node(*this, std::forward(keyval)); } // put at empty spot mInfo[insertion_idx] = static_cast(insertion_info); ++mNumElements; return std::make_pair(iterator(mKeyVals + insertion_idx, mInfo + insertion_idx), true); } } bool try_increase_info() { ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements << ", maxNumElementsAllowed=" << calcMaxNumElementsAllowed(mMask + 1)) if (mInfoInc <= 2) { // need to be > 2 so that shift works (otherwise undefined behavior!) return false; } // we got space left, try to make info smaller mInfoInc = static_cast(mInfoInc >> 1U); // remove one bit of the hash, leaving more space for the distance info. // This is extremely fast because we can operate on 8 bytes at once. ++mInfoHashShift; auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); for (size_t i = 0; i < numElementsWithBuffer; i += 8) { auto val = unaligned_load(mInfo + i); val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); std::memcpy(mInfo + i, &val, sizeof(val)); } // update sentinel, which might have been cleared out! mInfo[numElementsWithBuffer] = 1; mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); return true; } void increase_size() { // nothing allocated yet? just allocate InitialNumElements if (0 == mMask) { init_data(InitialNumElements); return; } auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); if (mNumElements < maxNumElementsAllowed && try_increase_info()) { return; } ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" << maxNumElementsAllowed << ", load=" << (static_cast(mNumElements) * 100.0 / (static_cast(mMask) + 1))) // it seems we have a really bad hash function! don't try to resize again if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { throwOverflowError(); } rehashPowerOfTwo((mMask + 1) * 2, false); } void destroy() { if (0 == mMask) { // don't deallocate! return; } Destroyer::value>{} .nodesDoNotDeallocate(*this); // This protection against not deleting mMask shouldn't be needed as it's sufficiently // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise // reports a compile error: attempt to free a non-heap object 'fm' // [-Werror=free-nonheap-object] if (mKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { ROBIN_HOOD_LOG("std::free") std::free(mKeyVals); } } void init() noexcept { mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); mInfo = reinterpret_cast(&mMask); mNumElements = 0; mMask = 0; mMaxNumElementsAllowed = 0; mInfoInc = InitialInfoInc; mInfoHashShift = InitialInfoHashShift; } // members are sorted so no padding occurs Node* mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); // 8 byte 8 uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 16 size_t mNumElements = 0; // 8 byte 24 size_t mMask = 0; // 8 byte 32 size_t mMaxNumElementsAllowed = 0; // 8 byte 40 InfoType mInfoInc = InitialInfoInc; // 4 byte 44 InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 48 // 16 byte 56 if NodeAllocator }; } // namespace detail // map template , typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> using unordered_flat_map = detail::Table; template , typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> using unordered_node_map = detail::Table; template , typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> using unordered_map = detail::Table) <= sizeof(size_t) * 6 && std::is_nothrow_move_constructible>::value && std::is_nothrow_move_assignable>::value, MaxLoadFactor100, Key, T, Hash, KeyEqual>; // set template , typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> using unordered_flat_set = detail::Table; template , typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> using unordered_node_set = detail::Table; template , typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> using unordered_set = detail::Table::value && std::is_nothrow_move_assignable::value, MaxLoadFactor100, Key, void, Hash, KeyEqual>; } // namespace robin_hood #endif ================================================ FILE: src/util/crypto/aes128.cpp ================================================ /* Original implementation based on Tiny-AES-c (2015) https://github.com/kokke/tiny-AES-c Modified by Exzap */ /*****************************************************************************/ /* Includes: */ /*****************************************************************************/ #include "aes128.h" #include "Common/cpu_features.h" /*****************************************************************************/ /* Defines: */ /*****************************************************************************/ // The number of columns comprising a state in AES. This is a constant in AES. Value=4 #define Nb 4 // The number of 32 bit words in a key. #define Nk 4 // Key length in bytes [128 bit] #define KEYLEN 16 // The number of rounds in AES Cipher. #define Nr 10 typedef uint8 state_t[4][4]; typedef struct { state_t* state; uint8 RoundKey[176]; }aes128Ctx_t; #define stateVal(__x, __y) ((*aesCtx->state)[__x][__y]) #define stateValU32(__x) (*(uint32*)((*aesCtx->state)[__x])) // The lookup-tables are marked const so they can be placed in read-only storage instead of RAM // The numbers below can be computed dynamically trading ROM for RAM - // This can be useful in (embedded) bootloader applications, where ROM is often limited. static const uint8 sbox[256] = { //0 1 2 3 4 5 6 7 8 9 A B C D E F 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; static const uint8 rsbox[256] = { 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; // The round constant word array, Rcon[i], contains the values given by // x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) // Note that i starts at 1, not 0). static const uint8 Rcon[255] = { 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb }; /*****************************************************************************/ /* Private functions: */ /*****************************************************************************/ uint8 getSBoxValue(uint8 num) { return sbox[num]; } uint8 getSBoxInvert(uint8 num) { return rsbox[num]; } // This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. void KeyExpansion(aes128Ctx_t* aesCtx, const uint8* key) { uint32 i, j, k; uint8 tempa[4]; // Used for the column/row operations // The first round key is the key itself. for (i = 0; i < Nk; ++i) { aesCtx->RoundKey[(i * 4) + 0] = key[(i * 4) + 0]; aesCtx->RoundKey[(i * 4) + 1] = key[(i * 4) + 1]; aesCtx->RoundKey[(i * 4) + 2] = key[(i * 4) + 2]; aesCtx->RoundKey[(i * 4) + 3] = key[(i * 4) + 3]; } // All other round keys are found from the previous round keys. for (; (i < (Nb * (Nr + 1))); ++i) { for (j = 0; j < 4; ++j) { tempa[j] = aesCtx->RoundKey[(i - 1) * 4 + j]; } if (i % Nk == 0) { // This function rotates the 4 bytes in a word to the left once. // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] // Function RotWord() { k = tempa[0]; tempa[0] = tempa[1]; tempa[1] = tempa[2]; tempa[2] = tempa[3]; tempa[3] = k; } // SubWord() is a function that takes a four-byte input word and // applies the S-box to each of the four bytes to produce an output word. // Function Subword() { tempa[0] = getSBoxValue(tempa[0]); tempa[1] = getSBoxValue(tempa[1]); tempa[2] = getSBoxValue(tempa[2]); tempa[3] = getSBoxValue(tempa[3]); } tempa[0] = tempa[0] ^ Rcon[i / Nk]; } else if (Nk > 6 && i % Nk == 4) { // Function Subword() { tempa[0] = getSBoxValue(tempa[0]); tempa[1] = getSBoxValue(tempa[1]); tempa[2] = getSBoxValue(tempa[2]); tempa[3] = getSBoxValue(tempa[3]); } } aesCtx->RoundKey[i * 4 + 0] = aesCtx->RoundKey[(i - Nk) * 4 + 0] ^ tempa[0]; aesCtx->RoundKey[i * 4 + 1] = aesCtx->RoundKey[(i - Nk) * 4 + 1] ^ tempa[1]; aesCtx->RoundKey[i * 4 + 2] = aesCtx->RoundKey[(i - Nk) * 4 + 2] ^ tempa[2]; aesCtx->RoundKey[i * 4 + 3] = aesCtx->RoundKey[(i - Nk) * 4 + 3] ^ tempa[3]; } } // This function adds the round key to state. // The round key is added to the state by an XOR function. void AddRoundKey(aes128Ctx_t* aesCtx, uint8 round) { // note: replacing this with two 64bit xor operations decreased performance in benchmarks, probably because the state bytes need to be stored back in memory stateVal(0, 0) ^= aesCtx->RoundKey[round * Nb * 4 + 0 * Nb + 0]; stateVal(0, 1) ^= aesCtx->RoundKey[round * Nb * 4 + 0 * Nb + 1]; stateVal(0, 2) ^= aesCtx->RoundKey[round * Nb * 4 + 0 * Nb + 2]; stateVal(0, 3) ^= aesCtx->RoundKey[round * Nb * 4 + 0 * Nb + 3]; stateVal(1, 0) ^= aesCtx->RoundKey[round * Nb * 4 + 1 * Nb + 0]; stateVal(1, 1) ^= aesCtx->RoundKey[round * Nb * 4 + 1 * Nb + 1]; stateVal(1, 2) ^= aesCtx->RoundKey[round * Nb * 4 + 1 * Nb + 2]; stateVal(1, 3) ^= aesCtx->RoundKey[round * Nb * 4 + 1 * Nb + 3]; stateVal(2, 0) ^= aesCtx->RoundKey[round * Nb * 4 + 2 * Nb + 0]; stateVal(2, 1) ^= aesCtx->RoundKey[round * Nb * 4 + 2 * Nb + 1]; stateVal(2, 2) ^= aesCtx->RoundKey[round * Nb * 4 + 2 * Nb + 2]; stateVal(2, 3) ^= aesCtx->RoundKey[round * Nb * 4 + 2 * Nb + 3]; stateVal(3, 0) ^= aesCtx->RoundKey[round * Nb * 4 + 3 * Nb + 0]; stateVal(3, 1) ^= aesCtx->RoundKey[round * Nb * 4 + 3 * Nb + 1]; stateVal(3, 2) ^= aesCtx->RoundKey[round * Nb * 4 + 3 * Nb + 2]; stateVal(3, 3) ^= aesCtx->RoundKey[round * Nb * 4 + 3 * Nb + 3]; } // The SubBytes Function Substitutes the values in the // state matrix with values in an S-box. void SubBytes(aes128Ctx_t* aesCtx) { uint8 i, j; for (i = 0; i < 4; ++i) { for (j = 0; j < 4; ++j) { stateVal(j, i) = getSBoxValue(stateVal(j, i)); } } } // The ShiftRows() function shifts the rows in the state to the left. // Each row is shifted with different offset. // Offset = Row number. So the first row is not shifted. void ShiftRows(aes128Ctx_t* aesCtx) { uint8 temp; // Rotate first row 1 columns to left temp = stateVal(0, 1); stateVal(0, 1) = stateVal(1, 1); stateVal(1, 1) = stateVal(2, 1); stateVal(2, 1) = stateVal(3, 1); stateVal(3, 1) = temp; // Rotate second row 2 columns to left temp = stateVal(0, 2); stateVal(0, 2) = stateVal(2, 2); stateVal(2, 2) = temp; temp = stateVal(1, 2); stateVal(1, 2) = stateVal(3, 2); stateVal(3, 2) = temp; // Rotate third row 3 columns to left temp = stateVal(0, 3); stateVal(0, 3) = stateVal(3, 3); stateVal(3, 3) = stateVal(2, 3); stateVal(2, 3) = stateVal(1, 3); stateVal(1, 3) = temp; } uint8 aes_xtime(uint8 x) { return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); } // MixColumns function mixes the columns of the state matrix void MixColumns(aes128Ctx_t* aesCtx) { uint8 i; uint8 Tmp, Tm, t; for (i = 0; i < 4; ++i) { t = stateVal(i, 0); Tmp = stateVal(i, 0) ^ stateVal(i, 1) ^ stateVal(i, 2) ^ stateVal(i, 3); Tm = stateVal(i, 0) ^ stateVal(i, 1); Tm = aes_xtime(Tm); stateVal(i, 0) ^= Tm ^ Tmp; Tm = stateVal(i, 1) ^ stateVal(i, 2); Tm = aes_xtime(Tm); stateVal(i, 1) ^= Tm ^ Tmp; Tm = stateVal(i, 2) ^ stateVal(i, 3); Tm = aes_xtime(Tm); stateVal(i, 2) ^= Tm ^ Tmp; Tm = stateVal(i, 3) ^ t; Tm = aes_xtime(Tm); stateVal(i, 3) ^= Tm ^ Tmp; } } // Multiply is used to multiply numbers in the field GF(2^8) #define Multiply(x, y) \ ( ((y & 1) * x) ^ \ ((y>>1 & 1) * aes_xtime(x)) ^ \ ((y>>2 & 1) * aes_xtime(aes_xtime(x))) ^ \ ((y>>3 & 1) * aes_xtime(aes_xtime(aes_xtime(x)))) ^ \ ((y>>4 & 1) * aes_xtime(aes_xtime(aes_xtime(aes_xtime(x)))))) uint32 lookupTable_multiply[256]; //// MixColumns function mixes the columns of the state matrix. //// The method used to multiply may be difficult to understand for the inexperienced. //// Please use the references to gain more information. //void InvMixColumns(aes128Ctx_t* aesCtx) //{ // int i; // uint8 a, b, c, d; // for (i = 0; i < 4; ++i) // { // a = stateVal(i, 0); // b = stateVal(i, 1); // c = stateVal(i, 2); // d = stateVal(i, 3); // // uint32 _a = lookupTable_multiply[a]; // uint32 _b = lookupTable_multiply[b]; // uint32 _c = lookupTable_multiply[c]; // uint32 _d = lookupTable_multiply[d]; // // // //stateVal(i, 0) = entryA->vE ^ entryB->vB ^ entryC->vD ^ entryD->v9; // //stateVal(i, 1) = entryA->v9 ^ entryB->vE ^ entryC->vB ^ entryD->vD; // //stateVal(i, 2) = entryA->vD ^ entryB->v9 ^ entryC->vE ^ entryD->vB; // //stateVal(i, 3) = entryA->vB ^ entryB->vD ^ entryC->v9 ^ entryD->vE; // // //stateVal(i, 0) = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); // //stateVal(i, 1) = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); // //stateVal(i, 2) = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); // //stateVal(i, 3) = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); // // //stateVal(i, 0) = Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); // //stateVal(i, 1) = Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); // //stateVal(i, 2) = Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); // //stateVal(i, 3) = Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); // // stateValU32(i) = _a ^ _rotl(_b, 8) ^ _rotl(_c, 16) ^ _rotl(_d, 24); // // } //} // MixColumns function mixes the columns of the state matrix. // The method used to multiply may be difficult to understand for the inexperienced. // Please use the references to gain more information. void InvMixColumns(aes128Ctx_t* aesCtx) { uint8 a, b, c, d; // i0 a = stateVal(0, 0); b = stateVal(0, 1); c = stateVal(0, 2); d = stateVal(0, 3); stateValU32(0) = lookupTable_multiply[a] ^ std::rotl(lookupTable_multiply[b], 8) ^ std::rotl(lookupTable_multiply[c], 16) ^ std::rotl(lookupTable_multiply[d], 24); // i1 a = stateVal(1, 0); b = stateVal(1, 1); c = stateVal(1, 2); d = stateVal(1, 3); stateValU32(1) = lookupTable_multiply[a] ^ std::rotl(lookupTable_multiply[b], 8) ^ std::rotl(lookupTable_multiply[c], 16) ^ std::rotl(lookupTable_multiply[d], 24); // i2 a = stateVal(2, 0); b = stateVal(2, 1); c = stateVal(2, 2); d = stateVal(2, 3); stateValU32(2) = lookupTable_multiply[a] ^ std::rotl(lookupTable_multiply[b], 8) ^ std::rotl(lookupTable_multiply[c], 16) ^ std::rotl(lookupTable_multiply[d], 24); // i3 a = stateVal(3, 0); b = stateVal(3, 1); c = stateVal(3, 2); d = stateVal(3, 3); stateValU32(3) = lookupTable_multiply[a] ^ std::rotl(lookupTable_multiply[b], 8) ^ std::rotl(lookupTable_multiply[c], 16) ^ std::rotl(lookupTable_multiply[d], 24); } // The SubBytes Function Substitutes the values in the // state matrix with values in an S-box. void InvSubBytes(aes128Ctx_t* aesCtx) { stateVal(0, 0) = rsbox[stateVal(0, 0)]; stateVal(1, 0) = rsbox[stateVal(1, 0)]; stateVal(2, 0) = rsbox[stateVal(2, 0)]; stateVal(3, 0) = rsbox[stateVal(3, 0)]; stateVal(0, 1) = rsbox[stateVal(0, 1)]; stateVal(1, 1) = rsbox[stateVal(1, 1)]; stateVal(2, 1) = rsbox[stateVal(2, 1)]; stateVal(3, 1) = rsbox[stateVal(3, 1)]; stateVal(0, 2) = rsbox[stateVal(0, 2)]; stateVal(1, 2) = rsbox[stateVal(1, 2)]; stateVal(2, 2) = rsbox[stateVal(2, 2)]; stateVal(3, 2) = rsbox[stateVal(3, 2)]; stateVal(0, 3) = rsbox[stateVal(0, 3)]; stateVal(1, 3) = rsbox[stateVal(1, 3)]; stateVal(2, 3) = rsbox[stateVal(2, 3)]; stateVal(3, 3) = rsbox[stateVal(3, 3)]; } void InvShiftRows(aes128Ctx_t* aesCtx) { uint8 temp; // Rotate first row 1 columns to right temp = stateVal(3, 1); stateVal(3, 1) = stateVal(2, 1); stateVal(2, 1) = stateVal(1, 1); stateVal(1, 1) = stateVal(0, 1); stateVal(0, 1) = temp; // Rotate second row 2 columns to right temp = stateVal(0, 2); stateVal(0, 2) = stateVal(2, 2); stateVal(2, 2) = temp; temp = stateVal(1, 2); stateVal(1, 2) = stateVal(3, 2); stateVal(3, 2) = temp; // Rotate third row 3 columns to right temp = stateVal(0, 3); stateVal(0, 3) = stateVal(1, 3); stateVal(1, 3) = stateVal(2, 3); stateVal(2, 3) = stateVal(3, 3); stateVal(3, 3) = temp; } // Cipher is the main function that encrypts the PlainText. void Cipher(aes128Ctx_t* aesCtx) { uint8 round = 0; // Add the First round key to the state before starting the rounds. AddRoundKey(aesCtx, 0); // There will be Nr rounds. // The first Nr-1 rounds are identical. // These Nr-1 rounds are executed in the loop below. for (round = 1; round < Nr; ++round) { SubBytes(aesCtx); ShiftRows(aesCtx); MixColumns(aesCtx); AddRoundKey(aesCtx, round); } // The last round is given below. // The MixColumns function is not here in the last round. SubBytes(aesCtx); ShiftRows(aesCtx); AddRoundKey(aesCtx, Nr); } void InvCipher(aes128Ctx_t* aesCtx) { uint8 round = 0; // Add the First round key to the state before starting the rounds. AddRoundKey(aesCtx, Nr); // There will be Nr rounds. // The first Nr-1 rounds are identical. // These Nr-1 rounds are executed in the loop below. for (round = Nr - 1; round > 0; round--) { InvShiftRows(aesCtx); InvSubBytes(aesCtx); AddRoundKey(aesCtx, round); InvMixColumns(aesCtx); } // The last round is given below. // The MixColumns function is not here in the last round. InvShiftRows(aesCtx); InvSubBytes(aesCtx); AddRoundKey(aesCtx, 0); } static void BlockCopy(uint8* output, uint8* input) { uint8 i; for (i = 0; i < KEYLEN; ++i) { output[i] = input[i]; } } /*****************************************************************************/ /* Public functions: */ /*****************************************************************************/ void __soft__AES128_ECB_encrypt(uint8* input, const uint8* key, uint8* output) { aes128Ctx_t aesCtx; // Copy input to output, and work in-memory on output BlockCopy(output, input); aesCtx.state = (state_t*)output; KeyExpansion(&aesCtx, key); // The next function call encrypts the PlainText with the Key using AES algorithm. Cipher(&aesCtx); } void AES128_ECB_decrypt(uint8* input, const uint8* key, uint8 *output) { aes128Ctx_t aesCtx; // Copy input to output, and work in-memory on output BlockCopy(output, input); aesCtx.state = (state_t*)output; // The KeyExpansion routine must be called before encryption. KeyExpansion(&aesCtx, key); InvCipher(&aesCtx); } void XorWithIv(uint8* buf, const uint8* iv) { uint8 i; for (i = 0; i < KEYLEN; ++i) { buf[i] ^= iv[i]; } } void AES128_CBC_encrypt(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv) { aes128Ctx_t aesCtx; intptr_t i; uint8 remainders = length % KEYLEN; /* Remaining bytes in the last non-full block */ BlockCopy(output, input); aesCtx.state = (state_t*)output; KeyExpansion(&aesCtx, key); const uint8* currentIv = iv; for (i = 0; i < length; i += KEYLEN) { XorWithIv(input, currentIv); BlockCopy(output, input); aesCtx.state = (state_t*)output; Cipher(&aesCtx); currentIv = output; input += KEYLEN; output += KEYLEN; } cemu_assert_debug(remainders == 0); } void __soft__AES128_CBC_decrypt(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv) { aes128Ctx_t aesCtx; intptr_t i; uint8 remainders = length % KEYLEN; KeyExpansion(&aesCtx, key); uint8 currentIv[KEYLEN]; uint8 nextIv[KEYLEN]; if (iv) BlockCopy(currentIv, (uint8*)iv); else memset(currentIv, 0, sizeof(currentIv)); for (i = 0; i < length; i += KEYLEN) { aesCtx.state = (state_t*)output; BlockCopy(output, input); BlockCopy(nextIv, input); InvCipher(&aesCtx); XorWithIv(output, currentIv); BlockCopy(currentIv, nextIv); output += KEYLEN; input += KEYLEN; } cemu_assert_debug(remainders == 0); } void AES128_CBC_decrypt_buffer_depr(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv) { aes128Ctx_t aesCtx; intptr_t i; uint8 remainders = length % KEYLEN; /* Remaining bytes in the last non-full block */ BlockCopy(output, input); KeyExpansion(&aesCtx, key); const uint8* currentIv = iv; for (i = 0; i < length; i += KEYLEN) { BlockCopy(output, input); aesCtx.state = (state_t*)output; InvCipher(&aesCtx); XorWithIv(output, currentIv); currentIv = input; input += KEYLEN; output += KEYLEN; } cemu_assert_debug(remainders == 0); } void AES128_CBC_decrypt_updateIV(uint8* output, uint8* input, uint32 length, const uint8* key, uint8* iv) { length &= ~0xF; uint8 newIv[16]; if (length == 0) return; cemu_assert_debug((length&0xF) == 0); memcpy(newIv, input + (length - 16), KEYLEN); AES128_CBC_decrypt(output, input, length, key, iv); memcpy(iv, newIv, KEYLEN); } #if defined(ARCH_X86_64) ATTRIBUTE_AESNI inline __m128i AESNI128_ASSIST( __m128i temp1, __m128i temp2) { __m128i temp3; temp2 = _mm_shuffle_epi32(temp2, 0xff); temp3 = _mm_slli_si128(temp1, 0x4); temp1 = _mm_xor_si128(temp1, temp3); temp3 = _mm_slli_si128(temp3, 0x4); temp1 = _mm_xor_si128(temp1, temp3); temp3 = _mm_slli_si128(temp3, 0x4); temp1 = _mm_xor_si128(temp1, temp3); temp1 = _mm_xor_si128(temp1, temp2); return temp1; } ATTRIBUTE_AESNI void AESNI128_KeyExpansionEncrypt(const unsigned char *userkey, unsigned char *key) { __m128i temp1, temp2; __m128i *Key_Schedule = (__m128i*)key; temp1 = _mm_loadu_si128((__m128i*)userkey); Key_Schedule[0] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x1); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[1] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x2); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[2] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x4); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[3] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x8); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[4] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x10); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[5] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x20); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[6] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x40); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[7] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x80); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[8] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x1b); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[9] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x36); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[10] = temp1; } ATTRIBUTE_AESNI void AESNI128_KeyExpansionDecrypt(const unsigned char *userkey, unsigned char *key) { __m128i temp1, temp2; __m128i *Key_Schedule = (__m128i*)key; temp1 = _mm_loadu_si128((__m128i*)userkey); Key_Schedule[0] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x1); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[1] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x2); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[2] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x4); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[3] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x8); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[4] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x10); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[5] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x20); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[6] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x40); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[7] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x80); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[8] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x1b); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[9] = temp1; temp2 = _mm_aeskeygenassist_si128(temp1, 0x36); temp1 = AESNI128_ASSIST(temp1, temp2); Key_Schedule[10] = temp1; // inverse for (sint32 i = 1; i < 10; i++) { Key_Schedule[i] = _mm_aesimc_si128(Key_Schedule[i]); } } ATTRIBUTE_AESNI void AESNI128_CBC_encrypt(const unsigned char *in, unsigned char *out, unsigned char ivec[16], unsigned long length, unsigned char *key, int number_of_rounds) { __m128i feedback, data; int j; if (length % 16) length = length / 16 + 1; else length /= 16; feedback = _mm_loadu_si128((__m128i*)ivec); for (unsigned long i = 0; i < length; i++) { data = _mm_loadu_si128(&((__m128i*)in)[i]); feedback = _mm_xor_si128(data, feedback); feedback = _mm_xor_si128(feedback, ((__m128i*)key)[0]); for (j = 1; j < number_of_rounds; j++) feedback = _mm_aesenc_si128(feedback, ((__m128i*)key)[j]); feedback = _mm_aesenclast_si128(feedback, ((__m128i*)key)[j]); _mm_storeu_si128(&((__m128i*)out)[i], feedback); } } ATTRIBUTE_AESNI void AESNI128_CBC_decryptWithExpandedKey(const unsigned char *in, unsigned char *out, const unsigned char ivec[16], unsigned long length, unsigned char *key) { __m128i data, feedback, lastin; int j; if (length % 16) length = length / 16 + 1; else length /= 16; feedback = _mm_loadu_si128((__m128i*)ivec); for (unsigned long i = 0; i < length; i++) { lastin = _mm_loadu_si128(&((__m128i*)in)[i]); data = _mm_xor_si128(lastin, ((__m128i*)key)[10]); for (j = 9; j > 0; j--) { data = _mm_aesdec_si128(data, ((__m128i*)key)[j]); } data = _mm_aesdeclast_si128(data, ((__m128i*)key)[0]); data = _mm_xor_si128(data, feedback); _mm_storeu_si128(&((__m128i*)out)[i], data); feedback = lastin; } } ATTRIBUTE_AESNI void __aesni__AES128_CBC_decrypt(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv) { alignas(16) uint8 expandedKey[11 * 16]; AESNI128_KeyExpansionDecrypt(key, expandedKey); if (iv) { AESNI128_CBC_decryptWithExpandedKey(input, output, iv, length, expandedKey); } else { uint8 zeroIv[16] = { 0 }; AESNI128_CBC_decryptWithExpandedKey(input, output, zeroIv, length, expandedKey); } } ATTRIBUTE_AESNI void __aesni__AES128_ECB_encrypt(uint8* input, const uint8* key, uint8* output) { alignas(16) uint8 expandedKey[11 * 16]; AESNI128_KeyExpansionEncrypt(key, expandedKey); // encrypt single ECB block __m128i feedback; feedback = _mm_loadu_si128(&((__m128i*)input)[0]); feedback = _mm_xor_si128(feedback, ((__m128i*)expandedKey)[0]); feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[1]); feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[2]); feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[3]); feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[4]); feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[5]); feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[6]); feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[7]); feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[8]); feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[9]); feedback = _mm_aesenclast_si128(feedback, ((__m128i*)expandedKey)[10]); _mm_storeu_si128(&((__m128i*)output)[0], feedback); } #endif void(*AES128_ECB_encrypt)(uint8* input, const uint8* key, uint8* output); void (*AES128_CBC_decrypt)(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv) = nullptr; // AES128-CTR encrypt/decrypt void AES128CTR_transform(uint8* data, sint32 length, uint8* key, uint8* nonceIv) { for (sint32 i = 0; i < length; i += 16) { uint8* d = data + i; uint8 tempArray[16]; AES128_ECB_encrypt(nonceIv, key, tempArray); for (sint32 f = 0; f < 16; f++) { d[f] ^= tempArray[f]; } // increase nonce *(uint32*)(nonceIv + 0xC) = _swapEndianU32(_swapEndianU32(*(uint32*)(nonceIv + 0xC)) + 1); if (*(uint32*)(nonceIv + 0xC) == 0) { *(uint32*)(nonceIv + 0x8) = _swapEndianU32(_swapEndianU32(*(uint32*)(nonceIv + 0x8)) + 1); if (*(uint32*)(nonceIv + 0x8) == 0) { *(uint32*)(nonceIv + 0x4) = _swapEndianU32(_swapEndianU32(*(uint32*)(nonceIv + 0x4)) + 1); if (*(uint32*)(nonceIv + 0x4) == 0) { *(uint32*)(nonceIv + 0) = _swapEndianU32(_swapEndianU32(*(uint32*)(nonceIv + 0)) + 1); } } } } } void AES128_init() { for (uint32 i = 0; i <= 0xFF; i++) { uint32 vE = Multiply((uint8)(i & 0xFF), 0x0E) & 0xFF; uint32 v9 = Multiply((uint8)(i & 0xFF), 0x09) & 0xFF; uint32 vD = Multiply((uint8)(i & 0xFF), 0x0D) & 0xFF; uint32 vB = Multiply((uint8)(i & 0xFF), 0x0B) & 0xFF; lookupTable_multiply[i] = (vE << 0) | (v9 << 8) | (vD << 16) | (vB << 24); } // check if AES-NI is available #if defined(ARCH_X86_64) if (g_CPUFeatures.x86.aesni) { // AES-NI implementation AES128_CBC_decrypt = __aesni__AES128_CBC_decrypt; AES128_ECB_encrypt = __aesni__AES128_ECB_encrypt; } else { // basic software implementation AES128_CBC_decrypt = __soft__AES128_CBC_decrypt; AES128_ECB_encrypt = __soft__AES128_ECB_encrypt; } #else AES128_CBC_decrypt = __soft__AES128_CBC_decrypt; AES128_ECB_encrypt = __soft__AES128_ECB_encrypt; #endif } ================================================ FILE: src/util/crypto/aes128.h ================================================ #ifndef _AES_H_ #define _AES_H_ void AES128_init(); extern void(*AES128_ECB_encrypt)(uint8* input, const uint8* key, uint8* output); void AES128_ECB_decrypt(uint8* input, const uint8* key, uint8 *output); void AES128_CBC_encrypt(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv); extern void(*AES128_CBC_decrypt)(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv); void AES128_CBC_decrypt_updateIV(uint8* output, uint8* input, uint32 length, const uint8* key, uint8* iv); void AES128CTR_transform(uint8* data, sint32 length, uint8* key, uint8* nonceIv); #endif //_AES_H_ ================================================ FILE: src/util/crypto/crc32.cpp ================================================ #include "crc32.h" constexpr uint32 Crc32Lookup[8][256] = { { 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3, 0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91, 0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7, 0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5, 0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B, 0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59, 0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D, 0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433, 0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01, 0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457, 0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65, 0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB, 0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F, 0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD, 0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683, 0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1, 0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7, 0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5, 0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79, 0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F, 0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D, 0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713, 0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21, 0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777, 0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB, 0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9, 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF, 0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D, } , { 0x00000000,0x191B3141,0x32366282,0x2B2D53C3,0x646CC504,0x7D77F445,0x565AA786,0x4F4196C7, 0xC8D98A08,0xD1C2BB49,0xFAEFE88A,0xE3F4D9CB,0xACB54F0C,0xB5AE7E4D,0x9E832D8E,0x87981CCF, 0x4AC21251,0x53D92310,0x78F470D3,0x61EF4192,0x2EAED755,0x37B5E614,0x1C98B5D7,0x05838496, 0x821B9859,0x9B00A918,0xB02DFADB,0xA936CB9A,0xE6775D5D,0xFF6C6C1C,0xD4413FDF,0xCD5A0E9E, 0x958424A2,0x8C9F15E3,0xA7B24620,0xBEA97761,0xF1E8E1A6,0xE8F3D0E7,0xC3DE8324,0xDAC5B265, 0x5D5DAEAA,0x44469FEB,0x6F6BCC28,0x7670FD69,0x39316BAE,0x202A5AEF,0x0B07092C,0x121C386D, 0xDF4636F3,0xC65D07B2,0xED705471,0xF46B6530,0xBB2AF3F7,0xA231C2B6,0x891C9175,0x9007A034, 0x179FBCFB,0x0E848DBA,0x25A9DE79,0x3CB2EF38,0x73F379FF,0x6AE848BE,0x41C51B7D,0x58DE2A3C, 0xF0794F05,0xE9627E44,0xC24F2D87,0xDB541CC6,0x94158A01,0x8D0EBB40,0xA623E883,0xBF38D9C2, 0x38A0C50D,0x21BBF44C,0x0A96A78F,0x138D96CE,0x5CCC0009,0x45D73148,0x6EFA628B,0x77E153CA, 0xBABB5D54,0xA3A06C15,0x888D3FD6,0x91960E97,0xDED79850,0xC7CCA911,0xECE1FAD2,0xF5FACB93, 0x7262D75C,0x6B79E61D,0x4054B5DE,0x594F849F,0x160E1258,0x0F152319,0x243870DA,0x3D23419B, 0x65FD6BA7,0x7CE65AE6,0x57CB0925,0x4ED03864,0x0191AEA3,0x188A9FE2,0x33A7CC21,0x2ABCFD60, 0xAD24E1AF,0xB43FD0EE,0x9F12832D,0x8609B26C,0xC94824AB,0xD05315EA,0xFB7E4629,0xE2657768, 0x2F3F79F6,0x362448B7,0x1D091B74,0x04122A35,0x4B53BCF2,0x52488DB3,0x7965DE70,0x607EEF31, 0xE7E6F3FE,0xFEFDC2BF,0xD5D0917C,0xCCCBA03D,0x838A36FA,0x9A9107BB,0xB1BC5478,0xA8A76539, 0x3B83984B,0x2298A90A,0x09B5FAC9,0x10AECB88,0x5FEF5D4F,0x46F46C0E,0x6DD93FCD,0x74C20E8C, 0xF35A1243,0xEA412302,0xC16C70C1,0xD8774180,0x9736D747,0x8E2DE606,0xA500B5C5,0xBC1B8484, 0x71418A1A,0x685ABB5B,0x4377E898,0x5A6CD9D9,0x152D4F1E,0x0C367E5F,0x271B2D9C,0x3E001CDD, 0xB9980012,0xA0833153,0x8BAE6290,0x92B553D1,0xDDF4C516,0xC4EFF457,0xEFC2A794,0xF6D996D5, 0xAE07BCE9,0xB71C8DA8,0x9C31DE6B,0x852AEF2A,0xCA6B79ED,0xD37048AC,0xF85D1B6F,0xE1462A2E, 0x66DE36E1,0x7FC507A0,0x54E85463,0x4DF36522,0x02B2F3E5,0x1BA9C2A4,0x30849167,0x299FA026, 0xE4C5AEB8,0xFDDE9FF9,0xD6F3CC3A,0xCFE8FD7B,0x80A96BBC,0x99B25AFD,0xB29F093E,0xAB84387F, 0x2C1C24B0,0x350715F1,0x1E2A4632,0x07317773,0x4870E1B4,0x516BD0F5,0x7A468336,0x635DB277, 0xCBFAD74E,0xD2E1E60F,0xF9CCB5CC,0xE0D7848D,0xAF96124A,0xB68D230B,0x9DA070C8,0x84BB4189, 0x03235D46,0x1A386C07,0x31153FC4,0x280E0E85,0x674F9842,0x7E54A903,0x5579FAC0,0x4C62CB81, 0x8138C51F,0x9823F45E,0xB30EA79D,0xAA1596DC,0xE554001B,0xFC4F315A,0xD7626299,0xCE7953D8, 0x49E14F17,0x50FA7E56,0x7BD72D95,0x62CC1CD4,0x2D8D8A13,0x3496BB52,0x1FBBE891,0x06A0D9D0, 0x5E7EF3EC,0x4765C2AD,0x6C48916E,0x7553A02F,0x3A1236E8,0x230907A9,0x0824546A,0x113F652B, 0x96A779E4,0x8FBC48A5,0xA4911B66,0xBD8A2A27,0xF2CBBCE0,0xEBD08DA1,0xC0FDDE62,0xD9E6EF23, 0x14BCE1BD,0x0DA7D0FC,0x268A833F,0x3F91B27E,0x70D024B9,0x69CB15F8,0x42E6463B,0x5BFD777A, 0xDC656BB5,0xC57E5AF4,0xEE530937,0xF7483876,0xB809AEB1,0xA1129FF0,0x8A3FCC33,0x9324FD72, }, { 0x00000000,0x01C26A37,0x0384D46E,0x0246BE59,0x0709A8DC,0x06CBC2EB,0x048D7CB2,0x054F1685, 0x0E1351B8,0x0FD13B8F,0x0D9785D6,0x0C55EFE1,0x091AF964,0x08D89353,0x0A9E2D0A,0x0B5C473D, 0x1C26A370,0x1DE4C947,0x1FA2771E,0x1E601D29,0x1B2F0BAC,0x1AED619B,0x18ABDFC2,0x1969B5F5, 0x1235F2C8,0x13F798FF,0x11B126A6,0x10734C91,0x153C5A14,0x14FE3023,0x16B88E7A,0x177AE44D, 0x384D46E0,0x398F2CD7,0x3BC9928E,0x3A0BF8B9,0x3F44EE3C,0x3E86840B,0x3CC03A52,0x3D025065, 0x365E1758,0x379C7D6F,0x35DAC336,0x3418A901,0x3157BF84,0x3095D5B3,0x32D36BEA,0x331101DD, 0x246BE590,0x25A98FA7,0x27EF31FE,0x262D5BC9,0x23624D4C,0x22A0277B,0x20E69922,0x2124F315, 0x2A78B428,0x2BBADE1F,0x29FC6046,0x283E0A71,0x2D711CF4,0x2CB376C3,0x2EF5C89A,0x2F37A2AD, 0x709A8DC0,0x7158E7F7,0x731E59AE,0x72DC3399,0x7793251C,0x76514F2B,0x7417F172,0x75D59B45, 0x7E89DC78,0x7F4BB64F,0x7D0D0816,0x7CCF6221,0x798074A4,0x78421E93,0x7A04A0CA,0x7BC6CAFD, 0x6CBC2EB0,0x6D7E4487,0x6F38FADE,0x6EFA90E9,0x6BB5866C,0x6A77EC5B,0x68315202,0x69F33835, 0x62AF7F08,0x636D153F,0x612BAB66,0x60E9C151,0x65A6D7D4,0x6464BDE3,0x662203BA,0x67E0698D, 0x48D7CB20,0x4915A117,0x4B531F4E,0x4A917579,0x4FDE63FC,0x4E1C09CB,0x4C5AB792,0x4D98DDA5, 0x46C49A98,0x4706F0AF,0x45404EF6,0x448224C1,0x41CD3244,0x400F5873,0x4249E62A,0x438B8C1D, 0x54F16850,0x55330267,0x5775BC3E,0x56B7D609,0x53F8C08C,0x523AAABB,0x507C14E2,0x51BE7ED5, 0x5AE239E8,0x5B2053DF,0x5966ED86,0x58A487B1,0x5DEB9134,0x5C29FB03,0x5E6F455A,0x5FAD2F6D, 0xE1351B80,0xE0F771B7,0xE2B1CFEE,0xE373A5D9,0xE63CB35C,0xE7FED96B,0xE5B86732,0xE47A0D05, 0xEF264A38,0xEEE4200F,0xECA29E56,0xED60F461,0xE82FE2E4,0xE9ED88D3,0xEBAB368A,0xEA695CBD, 0xFD13B8F0,0xFCD1D2C7,0xFE976C9E,0xFF5506A9,0xFA1A102C,0xFBD87A1B,0xF99EC442,0xF85CAE75, 0xF300E948,0xF2C2837F,0xF0843D26,0xF1465711,0xF4094194,0xF5CB2BA3,0xF78D95FA,0xF64FFFCD, 0xD9785D60,0xD8BA3757,0xDAFC890E,0xDB3EE339,0xDE71F5BC,0xDFB39F8B,0xDDF521D2,0xDC374BE5, 0xD76B0CD8,0xD6A966EF,0xD4EFD8B6,0xD52DB281,0xD062A404,0xD1A0CE33,0xD3E6706A,0xD2241A5D, 0xC55EFE10,0xC49C9427,0xC6DA2A7E,0xC7184049,0xC25756CC,0xC3953CFB,0xC1D382A2,0xC011E895, 0xCB4DAFA8,0xCA8FC59F,0xC8C97BC6,0xC90B11F1,0xCC440774,0xCD866D43,0xCFC0D31A,0xCE02B92D, 0x91AF9640,0x906DFC77,0x922B422E,0x93E92819,0x96A63E9C,0x976454AB,0x9522EAF2,0x94E080C5, 0x9FBCC7F8,0x9E7EADCF,0x9C381396,0x9DFA79A1,0x98B56F24,0x99770513,0x9B31BB4A,0x9AF3D17D, 0x8D893530,0x8C4B5F07,0x8E0DE15E,0x8FCF8B69,0x8A809DEC,0x8B42F7DB,0x89044982,0x88C623B5, 0x839A6488,0x82580EBF,0x801EB0E6,0x81DCDAD1,0x8493CC54,0x8551A663,0x8717183A,0x86D5720D, 0xA9E2D0A0,0xA820BA97,0xAA6604CE,0xABA46EF9,0xAEEB787C,0xAF29124B,0xAD6FAC12,0xACADC625, 0xA7F18118,0xA633EB2F,0xA4755576,0xA5B73F41,0xA0F829C4,0xA13A43F3,0xA37CFDAA,0xA2BE979D, 0xB5C473D0,0xB40619E7,0xB640A7BE,0xB782CD89,0xB2CDDB0C,0xB30FB13B,0xB1490F62,0xB08B6555, 0xBBD72268,0xBA15485F,0xB853F606,0xB9919C31,0xBCDE8AB4,0xBD1CE083,0xBF5A5EDA,0xBE9834ED, }, { 0x00000000,0xB8BC6765,0xAA09C88B,0x12B5AFEE,0x8F629757,0x37DEF032,0x256B5FDC,0x9DD738B9, 0xC5B428EF,0x7D084F8A,0x6FBDE064,0xD7018701,0x4AD6BFB8,0xF26AD8DD,0xE0DF7733,0x58631056, 0x5019579F,0xE8A530FA,0xFA109F14,0x42ACF871,0xDF7BC0C8,0x67C7A7AD,0x75720843,0xCDCE6F26, 0x95AD7F70,0x2D111815,0x3FA4B7FB,0x8718D09E,0x1ACFE827,0xA2738F42,0xB0C620AC,0x087A47C9, 0xA032AF3E,0x188EC85B,0x0A3B67B5,0xB28700D0,0x2F503869,0x97EC5F0C,0x8559F0E2,0x3DE59787, 0x658687D1,0xDD3AE0B4,0xCF8F4F5A,0x7733283F,0xEAE41086,0x525877E3,0x40EDD80D,0xF851BF68, 0xF02BF8A1,0x48979FC4,0x5A22302A,0xE29E574F,0x7F496FF6,0xC7F50893,0xD540A77D,0x6DFCC018, 0x359FD04E,0x8D23B72B,0x9F9618C5,0x272A7FA0,0xBAFD4719,0x0241207C,0x10F48F92,0xA848E8F7, 0x9B14583D,0x23A83F58,0x311D90B6,0x89A1F7D3,0x1476CF6A,0xACCAA80F,0xBE7F07E1,0x06C36084, 0x5EA070D2,0xE61C17B7,0xF4A9B859,0x4C15DF3C,0xD1C2E785,0x697E80E0,0x7BCB2F0E,0xC377486B, 0xCB0D0FA2,0x73B168C7,0x6104C729,0xD9B8A04C,0x446F98F5,0xFCD3FF90,0xEE66507E,0x56DA371B, 0x0EB9274D,0xB6054028,0xA4B0EFC6,0x1C0C88A3,0x81DBB01A,0x3967D77F,0x2BD27891,0x936E1FF4, 0x3B26F703,0x839A9066,0x912F3F88,0x299358ED,0xB4446054,0x0CF80731,0x1E4DA8DF,0xA6F1CFBA, 0xFE92DFEC,0x462EB889,0x549B1767,0xEC277002,0x71F048BB,0xC94C2FDE,0xDBF98030,0x6345E755, 0x6B3FA09C,0xD383C7F9,0xC1366817,0x798A0F72,0xE45D37CB,0x5CE150AE,0x4E54FF40,0xF6E89825, 0xAE8B8873,0x1637EF16,0x048240F8,0xBC3E279D,0x21E91F24,0x99557841,0x8BE0D7AF,0x335CB0CA, 0xED59B63B,0x55E5D15E,0x47507EB0,0xFFEC19D5,0x623B216C,0xDA874609,0xC832E9E7,0x708E8E82, 0x28ED9ED4,0x9051F9B1,0x82E4565F,0x3A58313A,0xA78F0983,0x1F336EE6,0x0D86C108,0xB53AA66D, 0xBD40E1A4,0x05FC86C1,0x1749292F,0xAFF54E4A,0x322276F3,0x8A9E1196,0x982BBE78,0x2097D91D, 0x78F4C94B,0xC048AE2E,0xD2FD01C0,0x6A4166A5,0xF7965E1C,0x4F2A3979,0x5D9F9697,0xE523F1F2, 0x4D6B1905,0xF5D77E60,0xE762D18E,0x5FDEB6EB,0xC2098E52,0x7AB5E937,0x680046D9,0xD0BC21BC, 0x88DF31EA,0x3063568F,0x22D6F961,0x9A6A9E04,0x07BDA6BD,0xBF01C1D8,0xADB46E36,0x15080953, 0x1D724E9A,0xA5CE29FF,0xB77B8611,0x0FC7E174,0x9210D9CD,0x2AACBEA8,0x38191146,0x80A57623, 0xD8C66675,0x607A0110,0x72CFAEFE,0xCA73C99B,0x57A4F122,0xEF189647,0xFDAD39A9,0x45115ECC, 0x764DEE06,0xCEF18963,0xDC44268D,0x64F841E8,0xF92F7951,0x41931E34,0x5326B1DA,0xEB9AD6BF, 0xB3F9C6E9,0x0B45A18C,0x19F00E62,0xA14C6907,0x3C9B51BE,0x842736DB,0x96929935,0x2E2EFE50, 0x2654B999,0x9EE8DEFC,0x8C5D7112,0x34E11677,0xA9362ECE,0x118A49AB,0x033FE645,0xBB838120, 0xE3E09176,0x5B5CF613,0x49E959FD,0xF1553E98,0x6C820621,0xD43E6144,0xC68BCEAA,0x7E37A9CF, 0xD67F4138,0x6EC3265D,0x7C7689B3,0xC4CAEED6,0x591DD66F,0xE1A1B10A,0xF3141EE4,0x4BA87981, 0x13CB69D7,0xAB770EB2,0xB9C2A15C,0x017EC639,0x9CA9FE80,0x241599E5,0x36A0360B,0x8E1C516E, 0x866616A7,0x3EDA71C2,0x2C6FDE2C,0x94D3B949,0x090481F0,0xB1B8E695,0xA30D497B,0x1BB12E1E, 0x43D23E48,0xFB6E592D,0xE9DBF6C3,0x516791A6,0xCCB0A91F,0x740CCE7A,0x66B96194,0xDE0506F1, } , { 0x00000000,0x3D6029B0,0x7AC05360,0x47A07AD0,0xF580A6C0,0xC8E08F70,0x8F40F5A0,0xB220DC10, 0x30704BC1,0x0D106271,0x4AB018A1,0x77D03111,0xC5F0ED01,0xF890C4B1,0xBF30BE61,0x825097D1, 0x60E09782,0x5D80BE32,0x1A20C4E2,0x2740ED52,0x95603142,0xA80018F2,0xEFA06222,0xD2C04B92, 0x5090DC43,0x6DF0F5F3,0x2A508F23,0x1730A693,0xA5107A83,0x98705333,0xDFD029E3,0xE2B00053, 0xC1C12F04,0xFCA106B4,0xBB017C64,0x866155D4,0x344189C4,0x0921A074,0x4E81DAA4,0x73E1F314, 0xF1B164C5,0xCCD14D75,0x8B7137A5,0xB6111E15,0x0431C205,0x3951EBB5,0x7EF19165,0x4391B8D5, 0xA121B886,0x9C419136,0xDBE1EBE6,0xE681C256,0x54A11E46,0x69C137F6,0x2E614D26,0x13016496, 0x9151F347,0xAC31DAF7,0xEB91A027,0xD6F18997,0x64D15587,0x59B17C37,0x1E1106E7,0x23712F57, 0x58F35849,0x659371F9,0x22330B29,0x1F532299,0xAD73FE89,0x9013D739,0xD7B3ADE9,0xEAD38459, 0x68831388,0x55E33A38,0x124340E8,0x2F236958,0x9D03B548,0xA0639CF8,0xE7C3E628,0xDAA3CF98, 0x3813CFCB,0x0573E67B,0x42D39CAB,0x7FB3B51B,0xCD93690B,0xF0F340BB,0xB7533A6B,0x8A3313DB, 0x0863840A,0x3503ADBA,0x72A3D76A,0x4FC3FEDA,0xFDE322CA,0xC0830B7A,0x872371AA,0xBA43581A, 0x9932774D,0xA4525EFD,0xE3F2242D,0xDE920D9D,0x6CB2D18D,0x51D2F83D,0x167282ED,0x2B12AB5D, 0xA9423C8C,0x9422153C,0xD3826FEC,0xEEE2465C,0x5CC29A4C,0x61A2B3FC,0x2602C92C,0x1B62E09C, 0xF9D2E0CF,0xC4B2C97F,0x8312B3AF,0xBE729A1F,0x0C52460F,0x31326FBF,0x7692156F,0x4BF23CDF, 0xC9A2AB0E,0xF4C282BE,0xB362F86E,0x8E02D1DE,0x3C220DCE,0x0142247E,0x46E25EAE,0x7B82771E, 0xB1E6B092,0x8C869922,0xCB26E3F2,0xF646CA42,0x44661652,0x79063FE2,0x3EA64532,0x03C66C82, 0x8196FB53,0xBCF6D2E3,0xFB56A833,0xC6368183,0x74165D93,0x49767423,0x0ED60EF3,0x33B62743, 0xD1062710,0xEC660EA0,0xABC67470,0x96A65DC0,0x248681D0,0x19E6A860,0x5E46D2B0,0x6326FB00, 0xE1766CD1,0xDC164561,0x9BB63FB1,0xA6D61601,0x14F6CA11,0x2996E3A1,0x6E369971,0x5356B0C1, 0x70279F96,0x4D47B626,0x0AE7CCF6,0x3787E546,0x85A73956,0xB8C710E6,0xFF676A36,0xC2074386, 0x4057D457,0x7D37FDE7,0x3A978737,0x07F7AE87,0xB5D77297,0x88B75B27,0xCF1721F7,0xF2770847, 0x10C70814,0x2DA721A4,0x6A075B74,0x576772C4,0xE547AED4,0xD8278764,0x9F87FDB4,0xA2E7D404, 0x20B743D5,0x1DD76A65,0x5A7710B5,0x67173905,0xD537E515,0xE857CCA5,0xAFF7B675,0x92979FC5, 0xE915E8DB,0xD475C16B,0x93D5BBBB,0xAEB5920B,0x1C954E1B,0x21F567AB,0x66551D7B,0x5B3534CB, 0xD965A31A,0xE4058AAA,0xA3A5F07A,0x9EC5D9CA,0x2CE505DA,0x11852C6A,0x562556BA,0x6B457F0A, 0x89F57F59,0xB49556E9,0xF3352C39,0xCE550589,0x7C75D999,0x4115F029,0x06B58AF9,0x3BD5A349, 0xB9853498,0x84E51D28,0xC34567F8,0xFE254E48,0x4C059258,0x7165BBE8,0x36C5C138,0x0BA5E888, 0x28D4C7DF,0x15B4EE6F,0x521494BF,0x6F74BD0F,0xDD54611F,0xE03448AF,0xA794327F,0x9AF41BCF, 0x18A48C1E,0x25C4A5AE,0x6264DF7E,0x5F04F6CE,0xED242ADE,0xD044036E,0x97E479BE,0xAA84500E, 0x4834505D,0x755479ED,0x32F4033D,0x0F942A8D,0xBDB4F69D,0x80D4DF2D,0xC774A5FD,0xFA148C4D, 0x78441B9C,0x4524322C,0x028448FC,0x3FE4614C,0x8DC4BD5C,0xB0A494EC,0xF704EE3C,0xCA64C78C, }, { 0x00000000,0xCB5CD3A5,0x4DC8A10B,0x869472AE,0x9B914216,0x50CD91B3,0xD659E31D,0x1D0530B8, 0xEC53826D,0x270F51C8,0xA19B2366,0x6AC7F0C3,0x77C2C07B,0xBC9E13DE,0x3A0A6170,0xF156B2D5, 0x03D6029B,0xC88AD13E,0x4E1EA390,0x85427035,0x9847408D,0x531B9328,0xD58FE186,0x1ED33223, 0xEF8580F6,0x24D95353,0xA24D21FD,0x6911F258,0x7414C2E0,0xBF481145,0x39DC63EB,0xF280B04E, 0x07AC0536,0xCCF0D693,0x4A64A43D,0x81387798,0x9C3D4720,0x57619485,0xD1F5E62B,0x1AA9358E, 0xEBFF875B,0x20A354FE,0xA6372650,0x6D6BF5F5,0x706EC54D,0xBB3216E8,0x3DA66446,0xF6FAB7E3, 0x047A07AD,0xCF26D408,0x49B2A6A6,0x82EE7503,0x9FEB45BB,0x54B7961E,0xD223E4B0,0x197F3715, 0xE82985C0,0x23755665,0xA5E124CB,0x6EBDF76E,0x73B8C7D6,0xB8E41473,0x3E7066DD,0xF52CB578, 0x0F580A6C,0xC404D9C9,0x4290AB67,0x89CC78C2,0x94C9487A,0x5F959BDF,0xD901E971,0x125D3AD4, 0xE30B8801,0x28575BA4,0xAEC3290A,0x659FFAAF,0x789ACA17,0xB3C619B2,0x35526B1C,0xFE0EB8B9, 0x0C8E08F7,0xC7D2DB52,0x4146A9FC,0x8A1A7A59,0x971F4AE1,0x5C439944,0xDAD7EBEA,0x118B384F, 0xE0DD8A9A,0x2B81593F,0xAD152B91,0x6649F834,0x7B4CC88C,0xB0101B29,0x36846987,0xFDD8BA22, 0x08F40F5A,0xC3A8DCFF,0x453CAE51,0x8E607DF4,0x93654D4C,0x58399EE9,0xDEADEC47,0x15F13FE2, 0xE4A78D37,0x2FFB5E92,0xA96F2C3C,0x6233FF99,0x7F36CF21,0xB46A1C84,0x32FE6E2A,0xF9A2BD8F, 0x0B220DC1,0xC07EDE64,0x46EAACCA,0x8DB67F6F,0x90B34FD7,0x5BEF9C72,0xDD7BEEDC,0x16273D79, 0xE7718FAC,0x2C2D5C09,0xAAB92EA7,0x61E5FD02,0x7CE0CDBA,0xB7BC1E1F,0x31286CB1,0xFA74BF14, 0x1EB014D8,0xD5ECC77D,0x5378B5D3,0x98246676,0x852156CE,0x4E7D856B,0xC8E9F7C5,0x03B52460, 0xF2E396B5,0x39BF4510,0xBF2B37BE,0x7477E41B,0x6972D4A3,0xA22E0706,0x24BA75A8,0xEFE6A60D, 0x1D661643,0xD63AC5E6,0x50AEB748,0x9BF264ED,0x86F75455,0x4DAB87F0,0xCB3FF55E,0x006326FB, 0xF135942E,0x3A69478B,0xBCFD3525,0x77A1E680,0x6AA4D638,0xA1F8059D,0x276C7733,0xEC30A496, 0x191C11EE,0xD240C24B,0x54D4B0E5,0x9F886340,0x828D53F8,0x49D1805D,0xCF45F2F3,0x04192156, 0xF54F9383,0x3E134026,0xB8873288,0x73DBE12D,0x6EDED195,0xA5820230,0x2316709E,0xE84AA33B, 0x1ACA1375,0xD196C0D0,0x5702B27E,0x9C5E61DB,0x815B5163,0x4A0782C6,0xCC93F068,0x07CF23CD, 0xF6999118,0x3DC542BD,0xBB513013,0x700DE3B6,0x6D08D30E,0xA65400AB,0x20C07205,0xEB9CA1A0, 0x11E81EB4,0xDAB4CD11,0x5C20BFBF,0x977C6C1A,0x8A795CA2,0x41258F07,0xC7B1FDA9,0x0CED2E0C, 0xFDBB9CD9,0x36E74F7C,0xB0733DD2,0x7B2FEE77,0x662ADECF,0xAD760D6A,0x2BE27FC4,0xE0BEAC61, 0x123E1C2F,0xD962CF8A,0x5FF6BD24,0x94AA6E81,0x89AF5E39,0x42F38D9C,0xC467FF32,0x0F3B2C97, 0xFE6D9E42,0x35314DE7,0xB3A53F49,0x78F9ECEC,0x65FCDC54,0xAEA00FF1,0x28347D5F,0xE368AEFA, 0x16441B82,0xDD18C827,0x5B8CBA89,0x90D0692C,0x8DD55994,0x46898A31,0xC01DF89F,0x0B412B3A, 0xFA1799EF,0x314B4A4A,0xB7DF38E4,0x7C83EB41,0x6186DBF9,0xAADA085C,0x2C4E7AF2,0xE712A957, 0x15921919,0xDECECABC,0x585AB812,0x93066BB7,0x8E035B0F,0x455F88AA,0xC3CBFA04,0x089729A1, 0xF9C19B74,0x329D48D1,0xB4093A7F,0x7F55E9DA,0x6250D962,0xA90C0AC7,0x2F987869,0xE4C4ABCC, }, { 0x00000000,0xA6770BB4,0x979F1129,0x31E81A9D,0xF44F2413,0x52382FA7,0x63D0353A,0xC5A73E8E, 0x33EF4E67,0x959845D3,0xA4705F4E,0x020754FA,0xC7A06A74,0x61D761C0,0x503F7B5D,0xF64870E9, 0x67DE9CCE,0xC1A9977A,0xF0418DE7,0x56368653,0x9391B8DD,0x35E6B369,0x040EA9F4,0xA279A240, 0x5431D2A9,0xF246D91D,0xC3AEC380,0x65D9C834,0xA07EF6BA,0x0609FD0E,0x37E1E793,0x9196EC27, 0xCFBD399C,0x69CA3228,0x582228B5,0xFE552301,0x3BF21D8F,0x9D85163B,0xAC6D0CA6,0x0A1A0712, 0xFC5277FB,0x5A257C4F,0x6BCD66D2,0xCDBA6D66,0x081D53E8,0xAE6A585C,0x9F8242C1,0x39F54975, 0xA863A552,0x0E14AEE6,0x3FFCB47B,0x998BBFCF,0x5C2C8141,0xFA5B8AF5,0xCBB39068,0x6DC49BDC, 0x9B8CEB35,0x3DFBE081,0x0C13FA1C,0xAA64F1A8,0x6FC3CF26,0xC9B4C492,0xF85CDE0F,0x5E2BD5BB, 0x440B7579,0xE27C7ECD,0xD3946450,0x75E36FE4,0xB044516A,0x16335ADE,0x27DB4043,0x81AC4BF7, 0x77E43B1E,0xD19330AA,0xE07B2A37,0x460C2183,0x83AB1F0D,0x25DC14B9,0x14340E24,0xB2430590, 0x23D5E9B7,0x85A2E203,0xB44AF89E,0x123DF32A,0xD79ACDA4,0x71EDC610,0x4005DC8D,0xE672D739, 0x103AA7D0,0xB64DAC64,0x87A5B6F9,0x21D2BD4D,0xE47583C3,0x42028877,0x73EA92EA,0xD59D995E, 0x8BB64CE5,0x2DC14751,0x1C295DCC,0xBA5E5678,0x7FF968F6,0xD98E6342,0xE86679DF,0x4E11726B, 0xB8590282,0x1E2E0936,0x2FC613AB,0x89B1181F,0x4C162691,0xEA612D25,0xDB8937B8,0x7DFE3C0C, 0xEC68D02B,0x4A1FDB9F,0x7BF7C102,0xDD80CAB6,0x1827F438,0xBE50FF8C,0x8FB8E511,0x29CFEEA5, 0xDF879E4C,0x79F095F8,0x48188F65,0xEE6F84D1,0x2BC8BA5F,0x8DBFB1EB,0xBC57AB76,0x1A20A0C2, 0x8816EAF2,0x2E61E146,0x1F89FBDB,0xB9FEF06F,0x7C59CEE1,0xDA2EC555,0xEBC6DFC8,0x4DB1D47C, 0xBBF9A495,0x1D8EAF21,0x2C66B5BC,0x8A11BE08,0x4FB68086,0xE9C18B32,0xD82991AF,0x7E5E9A1B, 0xEFC8763C,0x49BF7D88,0x78576715,0xDE206CA1,0x1B87522F,0xBDF0599B,0x8C184306,0x2A6F48B2, 0xDC27385B,0x7A5033EF,0x4BB82972,0xEDCF22C6,0x28681C48,0x8E1F17FC,0xBFF70D61,0x198006D5, 0x47ABD36E,0xE1DCD8DA,0xD034C247,0x7643C9F3,0xB3E4F77D,0x1593FCC9,0x247BE654,0x820CEDE0, 0x74449D09,0xD23396BD,0xE3DB8C20,0x45AC8794,0x800BB91A,0x267CB2AE,0x1794A833,0xB1E3A387, 0x20754FA0,0x86024414,0xB7EA5E89,0x119D553D,0xD43A6BB3,0x724D6007,0x43A57A9A,0xE5D2712E, 0x139A01C7,0xB5ED0A73,0x840510EE,0x22721B5A,0xE7D525D4,0x41A22E60,0x704A34FD,0xD63D3F49, 0xCC1D9F8B,0x6A6A943F,0x5B828EA2,0xFDF58516,0x3852BB98,0x9E25B02C,0xAFCDAAB1,0x09BAA105, 0xFFF2D1EC,0x5985DA58,0x686DC0C5,0xCE1ACB71,0x0BBDF5FF,0xADCAFE4B,0x9C22E4D6,0x3A55EF62, 0xABC30345,0x0DB408F1,0x3C5C126C,0x9A2B19D8,0x5F8C2756,0xF9FB2CE2,0xC813367F,0x6E643DCB, 0x982C4D22,0x3E5B4696,0x0FB35C0B,0xA9C457BF,0x6C636931,0xCA146285,0xFBFC7818,0x5D8B73AC, 0x03A0A617,0xA5D7ADA3,0x943FB73E,0x3248BC8A,0xF7EF8204,0x519889B0,0x6070932D,0xC6079899, 0x304FE870,0x9638E3C4,0xA7D0F959,0x01A7F2ED,0xC400CC63,0x6277C7D7,0x539FDD4A,0xF5E8D6FE, 0x647E3AD9,0xC209316D,0xF3E12BF0,0x55962044,0x90311ECA,0x3646157E,0x07AE0FE3,0xA1D90457, 0x579174BE,0xF1E67F0A,0xC00E6597,0x66796E23,0xA3DE50AD,0x05A95B19,0x34414184,0x92364A30, }, { 0x00000000,0xCCAA009E,0x4225077D,0x8E8F07E3,0x844A0EFA,0x48E00E64,0xC66F0987,0x0AC50919, 0xD3E51BB5,0x1F4F1B2B,0x91C01CC8,0x5D6A1C56,0x57AF154F,0x9B0515D1,0x158A1232,0xD92012AC, 0x7CBB312B,0xB01131B5,0x3E9E3656,0xF23436C8,0xF8F13FD1,0x345B3F4F,0xBAD438AC,0x767E3832, 0xAF5E2A9E,0x63F42A00,0xED7B2DE3,0x21D12D7D,0x2B142464,0xE7BE24FA,0x69312319,0xA59B2387, 0xF9766256,0x35DC62C8,0xBB53652B,0x77F965B5,0x7D3C6CAC,0xB1966C32,0x3F196BD1,0xF3B36B4F, 0x2A9379E3,0xE639797D,0x68B67E9E,0xA41C7E00,0xAED97719,0x62737787,0xECFC7064,0x205670FA, 0x85CD537D,0x496753E3,0xC7E85400,0x0B42549E,0x01875D87,0xCD2D5D19,0x43A25AFA,0x8F085A64, 0x562848C8,0x9A824856,0x140D4FB5,0xD8A74F2B,0xD2624632,0x1EC846AC,0x9047414F,0x5CED41D1, 0x299DC2ED,0xE537C273,0x6BB8C590,0xA712C50E,0xADD7CC17,0x617DCC89,0xEFF2CB6A,0x2358CBF4, 0xFA78D958,0x36D2D9C6,0xB85DDE25,0x74F7DEBB,0x7E32D7A2,0xB298D73C,0x3C17D0DF,0xF0BDD041, 0x5526F3C6,0x998CF358,0x1703F4BB,0xDBA9F425,0xD16CFD3C,0x1DC6FDA2,0x9349FA41,0x5FE3FADF, 0x86C3E873,0x4A69E8ED,0xC4E6EF0E,0x084CEF90,0x0289E689,0xCE23E617,0x40ACE1F4,0x8C06E16A, 0xD0EBA0BB,0x1C41A025,0x92CEA7C6,0x5E64A758,0x54A1AE41,0x980BAEDF,0x1684A93C,0xDA2EA9A2, 0x030EBB0E,0xCFA4BB90,0x412BBC73,0x8D81BCED,0x8744B5F4,0x4BEEB56A,0xC561B289,0x09CBB217, 0xAC509190,0x60FA910E,0xEE7596ED,0x22DF9673,0x281A9F6A,0xE4B09FF4,0x6A3F9817,0xA6959889, 0x7FB58A25,0xB31F8ABB,0x3D908D58,0xF13A8DC6,0xFBFF84DF,0x37558441,0xB9DA83A2,0x7570833C, 0x533B85DA,0x9F918544,0x111E82A7,0xDDB48239,0xD7718B20,0x1BDB8BBE,0x95548C5D,0x59FE8CC3, 0x80DE9E6F,0x4C749EF1,0xC2FB9912,0x0E51998C,0x04949095,0xC83E900B,0x46B197E8,0x8A1B9776, 0x2F80B4F1,0xE32AB46F,0x6DA5B38C,0xA10FB312,0xABCABA0B,0x6760BA95,0xE9EFBD76,0x2545BDE8, 0xFC65AF44,0x30CFAFDA,0xBE40A839,0x72EAA8A7,0x782FA1BE,0xB485A120,0x3A0AA6C3,0xF6A0A65D, 0xAA4DE78C,0x66E7E712,0xE868E0F1,0x24C2E06F,0x2E07E976,0xE2ADE9E8,0x6C22EE0B,0xA088EE95, 0x79A8FC39,0xB502FCA7,0x3B8DFB44,0xF727FBDA,0xFDE2F2C3,0x3148F25D,0xBFC7F5BE,0x736DF520, 0xD6F6D6A7,0x1A5CD639,0x94D3D1DA,0x5879D144,0x52BCD85D,0x9E16D8C3,0x1099DF20,0xDC33DFBE, 0x0513CD12,0xC9B9CD8C,0x4736CA6F,0x8B9CCAF1,0x8159C3E8,0x4DF3C376,0xC37CC495,0x0FD6C40B, 0x7AA64737,0xB60C47A9,0x3883404A,0xF42940D4,0xFEEC49CD,0x32464953,0xBCC94EB0,0x70634E2E, 0xA9435C82,0x65E95C1C,0xEB665BFF,0x27CC5B61,0x2D095278,0xE1A352E6,0x6F2C5505,0xA386559B, 0x061D761C,0xCAB77682,0x44387161,0x889271FF,0x825778E6,0x4EFD7878,0xC0727F9B,0x0CD87F05, 0xD5F86DA9,0x19526D37,0x97DD6AD4,0x5B776A4A,0x51B26353,0x9D1863CD,0x1397642E,0xDF3D64B0, 0x83D02561,0x4F7A25FF,0xC1F5221C,0x0D5F2282,0x079A2B9B,0xCB302B05,0x45BF2CE6,0x89152C78, 0x50353ED4,0x9C9F3E4A,0x121039A9,0xDEBA3937,0xD47F302E,0x18D530B0,0x965A3753,0x5AF037CD, 0xFF6B144A,0x33C114D4,0xBD4E1337,0x71E413A9,0x7B211AB0,0xB78B1A2E,0x39041DCD,0xF5AE1D53, 0x2C8E0FFF,0xE0240F61,0x6EAB0882,0xA201081C,0xA8C40105,0x646E019B,0xEAE10678,0x264B06E6, } }; uint32 crc32_calc_slice_by_8(uint32 previousCrc32, const void* data, size_t length) { uint32_t crc = ~previousCrc32; // same as previousCrc32 ^ 0xFFFFFFFF const uint32_t* current = (const uint32_t*)data; // process eight bytes at once (Slicing-by-8) while (length >= 8) { if constexpr (std::endian::native == std::endian::big){ uint32_t one = *current++ ^ _swapEndianU32(crc); uint32_t two = *current++; crc = Crc32Lookup[0][two & 0xFF] ^ Crc32Lookup[1][(two >> 8) & 0xFF] ^ Crc32Lookup[2][(two >> 16) & 0xFF] ^ Crc32Lookup[3][(two >> 24) & 0xFF] ^ Crc32Lookup[4][one & 0xFF] ^ Crc32Lookup[5][(one >> 8) & 0xFF] ^ Crc32Lookup[6][(one >> 16) & 0xFF] ^ Crc32Lookup[7][(one >> 24) & 0xFF]; } else if constexpr (std::endian::native == std::endian::little) { uint32_t one = *current++ ^ crc; uint32_t two = *current++; crc = Crc32Lookup[0][(two >> 24) & 0xFF] ^ Crc32Lookup[1][(two >> 16) & 0xFF] ^ Crc32Lookup[2][(two >> 8) & 0xFF] ^ Crc32Lookup[3][two & 0xFF] ^ Crc32Lookup[4][(one >> 24) & 0xFF] ^ Crc32Lookup[5][(one >> 16) & 0xFF] ^ Crc32Lookup[6][(one >> 8) & 0xFF] ^ Crc32Lookup[7][one & 0xFF]; } else { static_assert(std::endian::native == std::endian::big || std::endian::native == std::endian::little, "Platform byte-order is unsupported"); } length -= 8; } const uint8* currentChar = (const uint8*)current; // remaining 1 to 7 bytes (standard algorithm) while (length-- != 0) crc = (crc >> 8) ^ Crc32Lookup[0][(crc & 0xFF) ^ *currentChar++]; return ~crc; // same as crc ^ 0xFFFFFFFF } uint32 crc32_calc(uint32 c, const void* data, size_t length) { if (length >= 16) { return crc32_calc_slice_by_8(c, data, length); } const uint8* p = (const uint8*)data; if (length == 0) return c; c ^= 0xFFFFFFFF; while (length) { uint8 temp = *p; temp ^= (uint8)c; c = (c >> 8) ^ Crc32Lookup[0][temp]; // next length--; p++; } return ~c; } void CRCTest() { std::vector testData; for (uint8 i = 0; i < 89; i++) testData.emplace_back(i); uint32 r = crc32_calc(0, testData.data(), testData.size()); cemu_assert(r == 0x3fc61683); } ================================================ FILE: src/util/crypto/crc32.h ================================================ #pragma once uint32 crc32_calc(uint32 c, const void* data, size_t length); inline uint32 crc32_calc(const void* data, size_t length) { return crc32_calc(0, data, length); } ================================================ FILE: src/util/crypto/md5.cpp ================================================ /* * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. * MD5 Message-Digest Algorithm (RFC 1321). * * Homepage: * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 * * Author: * Alexander Peslyak, better known as Solar Designer * * This software was written by Alexander Peslyak in 2001. No copyright is * claimed, and the software is hereby placed in the public domain. * In case this attempt to disclaim copyright and place the software in the * public domain is deemed null and void, then the software is * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the * general public under the following terms: * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * There's ABSOLUTELY NO WARRANTY, express or implied. * * (This is a heavily cut-down "BSD license".) * * This differs from Colin Plumb's older public domain implementation in that * no exactly 32-bit integer data type is required (any 32-bit or wider * unsigned integer data type will do), there's no compile-time endianness * configuration, and the function prototypes match OpenSSL's. No code from * Colin Plumb's implementation has been reused; this comment merely compares * the properties of the two independent implementations. * * The primary goals of this implementation are portability and ease of use. * It is meant to be fast, but not as fast as possible. Some known * optimizations are not included to reduce source code size and avoid * compile-time configuration. */ #ifndef HAVE_OPENSSL #include #include "md5.h" /* * The basic MD5 functions. * * F and G are optimized compared to their RFC 1321 definitions for * architectures that lack an AND-NOT instruction, just like in Colin Plumb's * implementation. */ #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) #define H(x, y, z) (((x) ^ (y)) ^ (z)) #define H2(x, y, z) ((x) ^ ((y) ^ (z))) #define I(x, y, z) ((y) ^ ((x) | ~(z))) /* * The MD5 transformation for all four rounds. */ #define STEP(f, a, b, c, d, x, t, s) \ (a) += f((b), (c), (d)) + (x) + (t); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ (a) += (b); /* * SET reads 4 input bytes in little-endian byte order and stores them in a * properly aligned word in host byte order. * * The check for little-endian architectures that tolerate unaligned memory * accesses is just an optimization. Nothing will break if it fails to detect * a suitable architecture. * * Unfortunately, this optimization may be a C strict aliasing rules violation * if the caller's data buffer has effective type that cannot be aliased by * MD5_u32plus. In practice, this problem may occur if these MD5 routines are * inlined into a calling function, or with future and dangerously advanced * link-time optimizations. For the time being, keeping these MD5 routines in * their own translation unit avoids the problem. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) #define SET(n) \ (*(MD5_u32plus *)&ptr[(n) * 4]) #define GET(n) \ SET(n) #else #define SET(n) \ (ctx->block[(n)] = \ (MD5_u32plus)ptr[(n) * 4] | \ ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) #define GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update the bit * counters. There are no alignment requirements. */ static const void *body(MD5_CTX *ctx, const void *data, unsigned long size) { const unsigned char *ptr; MD5_u32plus a, b, c, d; MD5_u32plus saved_a, saved_b, saved_c, saved_d; ptr = (const unsigned char *)data; a = ctx->a; b = ctx->b; c = ctx->c; d = ctx->d; do { saved_a = a; saved_b = b; saved_c = c; saved_d = d; /* Round 1 */ STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) STEP(F, c, d, a, b, SET(2), 0x242070db, 17) STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) /* Round 2 */ STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) STEP(G, d, a, b, c, GET(10), 0x02441453, 9) STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) /* Round 3 */ STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11) STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23) STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11) STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23) STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11) STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23) STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11) STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23) /* Round 4 */ STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) a += saved_a; b += saved_b; c += saved_c; d += saved_d; ptr += 64; } while (size -= 64); ctx->a = a; ctx->b = b; ctx->c = c; ctx->d = d; return ptr; } void MD5_Init(MD5_CTX *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; ctx->c = 0x98badcfe; ctx->d = 0x10325476; ctx->lo = 0; ctx->hi = 0; } void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size) { MD5_u32plus saved_lo; unsigned long used, available; saved_lo = ctx->lo; if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) ctx->hi++; ctx->hi += size >> 29; used = saved_lo & 0x3f; if (used) { available = 64 - used; if (size < available) { memcpy(&ctx->buffer[used], data, size); return; } memcpy(&ctx->buffer[used], data, available); data = (const unsigned char *)data + available; size -= available; body(ctx, ctx->buffer, 64); } if (size >= 64) { data = body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } memcpy(ctx->buffer, data, size); } #define OUT_MD5(dst, src) \ (dst)[0] = (unsigned char)(src); \ (dst)[1] = (unsigned char)((src) >> 8); \ (dst)[2] = (unsigned char)((src) >> 16); \ (dst)[3] = (unsigned char)((src) >> 24); void MD5_Final(unsigned char *result, MD5_CTX *ctx) { unsigned long used, available; used = ctx->lo & 0x3f; ctx->buffer[used++] = 0x80; available = 64 - used; if (available < 8) { memset(&ctx->buffer[used], 0, available); body(ctx, ctx->buffer, 64); used = 0; available = 64; } memset(&ctx->buffer[used], 0, available - 8); ctx->lo <<= 3; OUT_MD5(&ctx->buffer[56], ctx->lo) OUT_MD5(&ctx->buffer[60], ctx->hi) body(ctx, ctx->buffer, 64); OUT_MD5(&result[0], ctx->a) OUT_MD5(&result[4], ctx->b) OUT_MD5(&result[8], ctx->c) OUT_MD5(&result[12], ctx->d) memset(ctx, 0, sizeof(*ctx)); } #endif // HMAC-MD5 void hmacMD5_init_rfc2104(const unsigned char* key, int key_len, HMACMD5Ctx *ctx) { int i; memset(ctx, 0, sizeof(HMACMD5Ctx)); /* if key is longer than 64 bytes reset it to key=MD5(key) */ if (key_len > 64) { unsigned char tk[16]; MD5_CTX tctx; MD5_Init(&tctx); MD5_Update(&tctx, key, key_len); MD5_Final(tk, &tctx); key = tk; key_len = 16; } /* start out by storing key in pads */ memcpy(ctx->k_ipad, key, key_len); memcpy(ctx->k_opad, key, key_len); /* XOR key with ipad and opad values */ for (i = 0; i < 64; i++) { ctx->k_ipad[i] ^= 0x36; ctx->k_opad[i] ^= 0x5c; } MD5_Init(&ctx->ctx); MD5_Update(&ctx->ctx, ctx->k_ipad, 64); } void hmacMD5_init_limK_to_64(const unsigned char* key, int key_len, HMACMD5Ctx *ctx) { if (key_len > 64) { key_len = 64; } hmacMD5_init_rfc2104(key, key_len, ctx); } void hmacMD5_update(const unsigned char* text, int text_len, HMACMD5Ctx *ctx) { MD5_Update(&ctx->ctx, text, text_len); } void hmacMD5_final(unsigned char* digest, HMACMD5Ctx *ctx) { MD5_CTX ctx_o; MD5_Final(digest, &ctx->ctx); MD5_Init(&ctx_o); MD5_Update(&ctx_o, ctx->k_opad, 64); MD5_Update(&ctx_o, digest, 16); MD5_Final(digest, &ctx_o); } void hmacMD5(const unsigned char* key, int keyLen, const unsigned char* text, int textLen, unsigned char* digest) { HMACMD5Ctx ctx; hmacMD5_init_limK_to_64(key, keyLen, &ctx); hmacMD5_update(text, textLen, &ctx); hmacMD5_final(digest, &ctx); } ================================================ FILE: src/util/crypto/md5.h ================================================ /* * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. * MD5 Message-Digest Algorithm (RFC 1321). * * Homepage: * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 * * Author: * Alexander Peslyak, better known as Solar Designer * * This software was written by Alexander Peslyak in 2001. No copyright is * claimed, and the software is hereby placed in the public domain. * In case this attempt to disclaim copyright and place the software in the * public domain is deemed null and void, then the software is * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the * general public under the following terms: * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * There's ABSOLUTELY NO WARRANTY, express or implied. * * See md5.c for more information. */ #ifdef HAVE_OPENSSL #include #elif !defined(_MD5_H) #define _MD5_H /* Any 32-bit or wider unsigned integer data type will do */ typedef unsigned int MD5_u32plus; typedef struct { MD5_u32plus lo, hi; MD5_u32plus a, b, c, d; unsigned char buffer[64]; MD5_u32plus block[16]; }MD5_CTX; extern void MD5_Init(MD5_CTX *ctx); extern void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size); extern void MD5_Final(unsigned char *result, MD5_CTX *ctx); #endif // HMAC-MD5 typedef struct { MD5_CTX ctx; unsigned char k_ipad[64]; unsigned char k_opad[64]; }HMACMD5Ctx; void hmacMD5_init_limK_to_64(const unsigned char* key, int key_len, HMACMD5Ctx *ctx); void hmacMD5_update(const unsigned char* text, int text_len, HMACMD5Ctx *ctx); void hmacMD5_final(unsigned char* digest, HMACMD5Ctx *ctx); void hmacMD5(const unsigned char* key, int keyLen, const unsigned char* text, int textLen, unsigned char* digest); ================================================ FILE: src/util/helpers/ClassWrapper.h ================================================ #pragma once #include #include template class SingletonClass { public: static T* getInstance() { static T instance; return &instance; } protected: SingletonClass() = default; }; template class SingletonRef { public: /*static std::shared_ptr getInstance() C++20 only { static std::atomic> s_instance; std::shared_ptr result; s_instance.compare_exchange_weak(result, std::make_shared()); return result; }*/ static std::shared_ptr getInstance() { std::scoped_lock lock(s_mutex); auto result = s_instance.lock(); if(!result) { result = std::make_shared(); s_instance = result; } return result; } protected: SingletonRef() = default; private: static inline std::weak_ptr s_instance; static inline std::mutex s_mutex; }; ================================================ FILE: src/util/helpers/ConcurrentQueue.h ================================================ #pragma once #include #include #include template class ConcurrentQueue { public: ConcurrentQueue() = default; ConcurrentQueue(const ConcurrentQueue&) = delete; ConcurrentQueue& operator=(const ConcurrentQueue&) = delete; size_t push(const T& item) { std::unique_lock mlock(m_mutex); m_queue.push(item); const size_t result = m_queue.size(); mlock.unlock(); m_condVar.notify_one(); return result; } template size_t push(Args&& ... args) { std::unique_lock mlock(m_mutex); m_queue.emplace(std::forward(args)...); const size_t result = m_queue.size(); mlock.unlock(); m_condVar.notify_one(); return result; } T peek() { std::unique_lock mlock(m_mutex); while (m_queue.empty()) { m_condVar.wait(mlock); } return m_queue.front(); } bool peek2(T& item) { std::unique_lock mlock(m_mutex); if (m_queue.empty()) return false; item = m_queue.front(); m_queue.pop(); return true; } T pop() { std::unique_lock mlock(m_mutex); while (m_queue.empty()) { m_condVar.wait(mlock); } auto val = m_queue.front(); m_queue.pop(); return val; } void pop(T& item) { std::unique_lock mlock(m_mutex); while (m_queue.empty()) { m_condVar.wait(mlock); } item = m_queue.front(); m_queue.pop(); } void clear() { std::unique_lock mlock(m_mutex); while (!m_queue.empty()) m_queue.pop(); } size_t size() { std::unique_lock mlock(m_mutex); return m_queue.size(); } bool empty() { std::unique_lock mlock(m_mutex); return m_queue.empty(); } private: std::mutex m_mutex; std::condition_variable m_condVar; std::queue m_queue; }; ================================================ FILE: src/util/helpers/MapAdaptor.h ================================================ #pragma once // https://ideone.com/k0H8Ei #include #include #include template struct map_adaptor { map_adaptor(const T& t, const F& f) : _t(t), _f(f) {} map_adaptor(map_adaptor& a) = delete; map_adaptor(map_adaptor&& a) = default; [[nodiscard]] auto begin() { return boost::make_transform_iterator(_t.begin(), _f); } [[nodiscard]] auto end() { return boost::make_transform_iterator(_t.end(), _f); } [[nodiscard]] auto cbegin() const { return boost::make_transform_iterator(_t.cbegin(), _f); } [[nodiscard]] auto cend() const { return boost::make_transform_iterator(_t.cend(), _f); } protected: const T& _t; F _f; }; template auto get_map_adaptor(const T& t, const F& f) { return map_adaptor(t, f); } template auto get_keys(const T& t) { return get_map_adaptor(t, [](const auto& p) { return p.first; }); } template auto get_values(const T& t) { return get_map_adaptor(t, [](const auto& p) { return p.second; }); } ================================================ FILE: src/util/helpers/MemoryPool.h ================================================ #pragma once template class MemoryPool { static_assert(sizeof(T) >= sizeof(void*)); // object must be large enough to store a single pointer public: MemoryPool(int allocationGranularity) : m_numObjectsAllocated(0) { m_allocationGranularity = allocationGranularity; m_nextFreeObject = nullptr; } template T* allocObj(Ts&&... args) { if (m_nextFreeObject) { T* allocatedObj = m_nextFreeObject; m_nextFreeObject = *(T**)allocatedObj; new (allocatedObj) T(std::forward(args)...); return allocatedObj; } // enlarge pool increasePoolSize(); T* allocatedObj = m_nextFreeObject; m_nextFreeObject = *(T**)allocatedObj; new (allocatedObj) T(std::forward(args)...); return allocatedObj; } void freeObj(T* obj) { obj->~T(); pushElementOnFreeStack(obj); } private: void pushElementOnFreeStack(T* obj) { *(T**)obj = m_nextFreeObject; m_nextFreeObject = obj; } void increasePoolSize() { T* newElements = static_cast(::operator new(m_allocationGranularity * sizeof(T), std::nothrow)); m_numObjectsAllocated += m_allocationGranularity; for (int i = 0; i < m_allocationGranularity; i++) { pushElementOnFreeStack(newElements); newElements++; } } private: T* m_nextFreeObject; int m_allocationGranularity; int m_numObjectsAllocated; }; // this memory pool calls the default constructor when the internal pool is allocated // no constructor/destructor will be called on acquire/release template class MemoryPoolPermanentObjects { struct internalObject_t { T v; internalObject_t* next; }; public: MemoryPoolPermanentObjects(int allocationGranularity) : m_numObjectsAllocated(0) { m_allocationGranularity = allocationGranularity; m_nextFreeObject = nullptr; } template T* acquireObj(Ts&&... args) { if (m_nextFreeObject) { internalObject_t* allocatedObject = m_nextFreeObject; m_nextFreeObject = allocatedObject->next; return &allocatedObject->v; } // enlarge pool increasePoolSize(); internalObject_t* allocatedObject = m_nextFreeObject; m_nextFreeObject = allocatedObject->next; return &allocatedObject->v; } void releaseObj(T* obj) { internalObject_t* internalObj = (internalObject_t*)((uint8*)obj - offsetof(internalObject_t, v)); pushElementOnFreeStack(internalObj); } private: void pushElementOnFreeStack(internalObject_t* obj) { obj->next = m_nextFreeObject; m_nextFreeObject = obj; } void increasePoolSize() { internalObject_t* newElements = static_cast(::operator new(m_allocationGranularity * sizeof(internalObject_t), std::nothrow)); m_numObjectsAllocated += m_allocationGranularity; for (int i = 0; i < m_allocationGranularity; i++) { new (&newElements->v) T(); pushElementOnFreeStack(newElements); newElements++; } } private: internalObject_t* m_nextFreeObject; int m_allocationGranularity; int m_numObjectsAllocated; }; ================================================ FILE: src/util/helpers/Semaphore.h ================================================ #pragma once #include #include class Semaphore { public: void notify() { std::lock_guard lock(m_mutex); ++m_count; m_condition.notify_one(); } void wait() { std::unique_lock lock(m_mutex); while (m_count == 0) { m_condition.wait(lock); } --m_count; } bool try_wait() { std::lock_guard lock(m_mutex); if (m_count == 0) return false; --m_count; return true; } void reset() { std::lock_guard lock(m_mutex); m_count = 0; } private: std::mutex m_mutex; std::condition_variable m_condition; uint64 m_count = 0; }; class CounterSemaphore { public: void reset() { std::lock_guard lock(m_mutex); m_count = 0; } void increment() { std::lock_guard lock(m_mutex); ++m_count; if (m_count == 1) m_condition.notify_all(); } void decrement() { std::lock_guard lock(m_mutex); --m_count; cemu_assert_debug(m_count >= 0); if (m_count == 0) m_condition.notify_all(); } // decrement only if non-zero // otherwise wait void decrementWithWait() { std::unique_lock lock(m_mutex); while (m_count == 0) m_condition.wait(lock); m_count--; } // decrement only if non-zero // otherwise wait // may wake up spuriously bool decrementWithWaitAndTimeout(uint32 ms) { std::unique_lock lock(m_mutex); if (m_count == 0) { m_condition.wait_for(lock, std::chrono::milliseconds(ms)); } if (m_count == 0) return false; m_count--; return true; } void waitUntilZero() { std::unique_lock lock(m_mutex); while (m_count != 0) m_condition.wait(lock); } void waitUntilNonZero() { std::unique_lock lock(m_mutex); while (m_count == 0) m_condition.wait(lock); } bool isZero() const { return m_count == 0; } private: std::mutex m_mutex; std::condition_variable m_condition; sint64 m_count = 0; }; template class StateSemaphore { public: StateSemaphore(T initialState) : m_state(initialState) {}; T getValue() { std::unique_lock lock(m_mutex); return m_state; } bool hasState(T state) { std::unique_lock lock(m_mutex); return m_state == state; } void setValue(T newState) { std::unique_lock lock(m_mutex); m_state = newState; m_condition.notify_all(); } void setValue(T newState, T expectedValue) { std::unique_lock lock(m_mutex); while (m_state != expectedValue) m_condition.wait(lock); m_state = newState; m_condition.notify_all(); } void waitUntilValue(T state) { std::unique_lock lock(m_mutex); while (m_state != state) m_condition.wait(lock); } private: std::mutex m_mutex; std::condition_variable m_condition; T m_state; }; ================================================ FILE: src/util/helpers/Serializer.cpp ================================================ #include "Serializer.h" template<> uint8 MemStreamReader::readBE() { if (!reserveReadLength(sizeof(uint8))) return 0; uint8 v = m_data[m_cursorPos]; m_cursorPos += sizeof(uint8); return v; } template<> uint16 MemStreamReader::readBE() { if (!reserveReadLength(sizeof(uint16))) return 0; const uint8* p = m_data + m_cursorPos; uint16 v; std::memcpy(&v, p, sizeof(v)); v = _BE(v); m_cursorPos += sizeof(uint16); return v; } template<> uint32 MemStreamReader::readBE() { if (!reserveReadLength(sizeof(uint32))) return 0; const uint8* p = m_data + m_cursorPos; uint32 v; std::memcpy(&v, p, sizeof(v)); v = _BE(v); m_cursorPos += sizeof(uint32); return v; } template<> uint64 MemStreamReader::readBE() { if (!reserveReadLength(sizeof(uint64))) return 0; const uint8* p = m_data + m_cursorPos; uint64 v; std::memcpy(&v, p, sizeof(v)); v = _BE(v); m_cursorPos += sizeof(uint64); return v; } template<> std::string MemStreamReader::readBE() { std::string s; uint32 stringSize = readBE(); if (hasError()) return s; if (stringSize >= (32 * 1024 * 1024)) { // out of bounds read or suspiciously large string m_hasError = true; return std::string(); } s.resize(stringSize); readData(s.data(), stringSize); return s; } template<> uint8 MemStreamReader::readLE() { return readBE(); } template<> uint32 MemStreamReader::readLE() { if (!reserveReadLength(sizeof(uint32))) return 0; const uint8* p = m_data + m_cursorPos; uint32 v; std::memcpy(&v, p, sizeof(v)); v = _LE(v); m_cursorPos += sizeof(uint32); return v; } template<> uint64 MemStreamReader::readLE() { if (!reserveReadLength(sizeof(uint64))) return 0; const uint8* p = m_data + m_cursorPos; uint64 v; std::memcpy(&v, p, sizeof(v)); v = _LE(v); m_cursorPos += sizeof(uint64); return v; } template<> void MemStreamWriter::writeBE(const uint64& v) { m_buffer.resize(m_buffer.size() + 8); uint8* p = m_buffer.data() + m_buffer.size() - 8; uint64 tmp = _BE(v); std::memcpy(p, &tmp, sizeof(tmp)); } template<> void MemStreamWriter::writeBE(const uint32& v) { m_buffer.resize(m_buffer.size() + 4); uint8* p = m_buffer.data() + m_buffer.size() - 4; uint32 tmp = _BE(v); std::memcpy(p, &tmp, sizeof(tmp)); } template<> void MemStreamWriter::writeBE(const uint16& v) { m_buffer.resize(m_buffer.size() + 2); uint8* p = m_buffer.data() + m_buffer.size() - 2; uint16 tmp = _BE(v); std::memcpy(p, &tmp, sizeof(tmp)); } template<> void MemStreamWriter::writeBE(const uint8& v) { m_buffer.emplace_back(v); } template<> void MemStreamWriter::writeBE(const std::string& v) { writeBE((uint32)v.size()); writeData(v.data(), v.size()); } template<> void MemStreamWriter::writeLE(const uint64& v) { m_buffer.resize(m_buffer.size() + 8); uint8* p = m_buffer.data() + m_buffer.size() - 8; uint64 tmp = _LE(v); std::memcpy(p, &tmp, sizeof(tmp)); } template<> void MemStreamWriter::writeLE(const uint32& v) { m_buffer.resize(m_buffer.size() + 4); uint8* p = m_buffer.data() + m_buffer.size() - 4; uint32 tmp = _LE(v); std::memcpy(p, &tmp, sizeof(tmp)); } ================================================ FILE: src/util/helpers/Serializer.h ================================================ #pragma once class MemStreamReader { public: MemStreamReader(const uint8* data, sint32 size) : m_data(data), m_size(size) { m_cursorPos = 0; } template T readBE(); template T readLE(); template std::vector readPODVector() { uint32 numElements = readBE(); if (hasError()) { return std::vector(); } std::vector v; v.reserve(numElements); v.resize(numElements); readData(v.data(), v.size() * sizeof(T)); return v; } // read string terminated by newline character (or end of stream) // will also trim off any carriage return std::string_view readLine() { size_t length = 0; if (m_cursorPos >= m_size) { m_hasError = true; return std::basic_string_view((const char*)nullptr, 0); } // end of line is determined by '\n' const char* lineStrBegin = (const char*)(m_data + m_cursorPos); const char* lineStrEnd = nullptr; while (m_cursorPos < m_size) { if (m_data[m_cursorPos] == '\n') { lineStrEnd = (const char*)(m_data + m_cursorPos); m_cursorPos++; // skip the newline character break; } m_cursorPos++; } if(lineStrEnd == nullptr) lineStrEnd = (const char*)(m_data + m_cursorPos); // truncate any '\r' at the beginning and end while (lineStrBegin < lineStrEnd) { if (lineStrBegin[0] != '\r') break; lineStrBegin++; } while (lineStrEnd > lineStrBegin) { if (lineStrEnd[-1] != '\r') break; lineStrEnd--; } length = (lineStrEnd - lineStrBegin); return std::basic_string_view((const char*)lineStrBegin, length); } bool readData(void* ptr, size_t size) { if (m_cursorPos + size > m_size) { m_cursorPos = m_size; m_hasError = true; return false; } memcpy(ptr, m_data + m_cursorPos, size); m_cursorPos += (sint32)size; return true; } std::span readDataNoCopy(size_t size) { if (m_cursorPos + size > m_size) { m_cursorPos = m_size; m_hasError = true; return std::span(); } auto r = std::span((uint8*)m_data + m_cursorPos, size); m_cursorPos += (sint32)size; return r; } // returns true if any of the reads was out of bounds bool hasError() const { return m_hasError; } bool isEndOfStream() const { return m_cursorPos == m_size; } private: bool reserveReadLength(size_t length) { if (m_cursorPos + length > m_size) { m_cursorPos = m_size; m_hasError = true; return false; } return true; } void skipCRLF() { while (m_cursorPos < m_size) { if (m_data[m_cursorPos] != '\r' && m_data[m_cursorPos] != '\n') break; m_cursorPos++; } } const uint8* m_data; sint32 m_size; sint32 m_cursorPos; bool m_hasError{ false }; }; class MemStreamWriter { public: MemStreamWriter(size_t reservedSize) { if (reservedSize > 0) m_buffer.reserve(reservedSize); else m_buffer.reserve(128); } void writeData(const void* ptr, size_t size) { m_buffer.resize(m_buffer.size() + size); uint8* p = m_buffer.data() + m_buffer.size() - size; memcpy(p, ptr, size); } template void writeBE(const T& v); template void writeLE(const T& v); template void writePODVector(const std::vector& v) { writeBE(v.size()); writeData(v.data(), v.size() * sizeof(T)); } // get result buffer without copy // resets internal state void getResultAndReset(std::vector& data) { std::swap(m_buffer, data); m_buffer.clear(); } std::span getResult() { return std::span(m_buffer.data(), m_buffer.size()); } private: std::vector m_buffer; }; class SerializerHelper { public: bool serialize(std::vector& data) { MemStreamWriter streamWriter(0); bool r = serializeImpl(streamWriter); if (!r) return false; streamWriter.getResultAndReset(data); return true; } bool deserialize(std::vector& data) { MemStreamReader memStreamReader(data.data(), (sint32)data.size()); return deserializeImpl(memStreamReader); } protected: virtual bool serializeImpl(MemStreamWriter& streamWriter) = 0; virtual bool deserializeImpl(MemStreamReader& streamReader) = 0; }; ================================================ FILE: src/util/helpers/Singleton.h ================================================ #pragma once template class Singleton { protected: Singleton() = default; Singleton(const Singleton&) = delete; Singleton(Singleton&&) noexcept = delete; public: static TType& instance() noexcept { static TType s_instance; return s_instance; } }; ================================================ FILE: src/util/helpers/StringBuf.h ================================================ #pragma once class StringBuf { public: StringBuf(uint32 bufferSize) { this->str = (uint8*)malloc(bufferSize + 4); this->allocated = true; this->length = 0; this->limit = bufferSize; } ~StringBuf() { if (this->allocated) free(this->str); } template void addFmt(const TFmt& format, TArgs&&... args) { auto r = fmt::vformat_to_n((char*)(this->str + this->length), (size_t)(this->limit - this->length), fmt::detail::to_string_view(format), fmt::make_format_args(args...)); this->length += (uint32)r.size; } void add(const char* appendedStr) { const char* outputStart = (char*)(this->str + this->length); char* output = (char*)outputStart; const char* outputEnd = (char*)(this->str + this->limit - 1); while (output < outputEnd) { char c = *appendedStr; if (c == '\0') break; *output = c; appendedStr++; output++; } this->length += (uint32)(output - outputStart); *output = '\0'; } void add(std::string_view appendedStr) { size_t copyLen = appendedStr.size(); if (this->length + copyLen + 1 >= this->limit) _reserve(std::max(this->length + copyLen + 64, this->limit + this->limit / 2)); char* outputStart = (char*)(this->str + this->length); std::copy(appendedStr.data(), appendedStr.data() + copyLen, outputStart); length += copyLen; outputStart[copyLen] = '\0'; } void reset() { length = 0; } uint32 getLen() const { return length; } const char* c_str() const { str[length] = '\0'; return (const char*)str; } void shrink_to_fit() { if (!this->allocated) return; uint32 newLimit = this->length; this->str = (uint8*)realloc(this->str, newLimit + 4); this->limit = newLimit; } private: void _reserve(uint32 newLimit) { cemu_assert_debug(newLimit > length); this->str = (uint8*)realloc(this->str, newLimit + 4); this->limit = newLimit; } uint8* str; uint32 length; /* in bytes */ uint32 limit; /* in bytes */ bool allocated; }; ================================================ FILE: src/util/helpers/StringHelpers.h ================================================ #pragma once #include "boost/nowide/convert.hpp" #include // Definition for removed templates in Apple Clang 17 #if defined(__apple_build_version__) && (__apple_build_version__ >= 17000000) namespace std { template<> struct char_traits { using char_type = uint16be; using int_type = int; using off_type = streamoff; using pos_type = streampos; using state_type = mbstate_t; static inline void constexpr assign(char_type& c1, const char_type& c2) noexcept { c1 = c2; } static inline constexpr bool eq(char_type c1, char_type c2) noexcept { return c1 == c2; } static inline constexpr bool lt(char_type c1, char_type c2) noexcept { return c1 < c2; } static constexpr int compare(const char_type* s1, const char_type* s2, size_t n) { for (; n; --n, ++s1, ++s2) { if (lt(*s1, *s2)) return -1; if (lt(*s2, *s1)) return 1; } return 0; } static constexpr size_t length(const char_type* s) { size_t len = 0; for (; !eq(*s, char_type(0)); ++s) ++len; return len; } static constexpr const char_type* find(const char_type* s, size_t n, const char_type& a) { for (; n; --n) { if (eq(*s, a)) return s; ++s; } return nullptr; } static constexpr char_type* move(char_type* s1, const char_type* s2, size_t n) { if (n == 0) return s1; return static_cast(memmove(s1, s2, n * sizeof(char_type))); } static constexpr char_type* copy(char_type* s1, const char_type* s2, size_t n) { if (n == 0) return s1; return static_cast(memcpy(s1, s2, n * sizeof(char_type))); } static constexpr char_type* assign(char_type* s, size_t n, char_type a) { char_type* r = s; for (; n; --n, ++s) assign(*s, a); return r; } static inline constexpr char_type to_char_type(int_type c) noexcept { return char_type(c); } static inline constexpr int_type to_int_type(char_type c) noexcept { return int_type(c); } static inline constexpr bool eq_int_type(int_type c1, int_type c2) noexcept { return c1 == c2; } static inline constexpr int_type eof() noexcept { return static_cast(EOF); } static inline constexpr int_type not_eof(int_type c) noexcept { return eq_int_type(c, eof()) ? ~eof() : c; } }; } #endif // todo - move the Cafe/PPC specific parts to CafeString.h eventually namespace StringHelpers { // convert Wii U big-endian wchar_t string to utf8 string static std::string ToUtf8(const uint16be* ptr, size_t maxLength) { std::wstringstream result; while (*ptr != 0 && maxLength > 0) { auto c = (uint16)*ptr; result << static_cast(c); ptr++; maxLength--; } return boost::nowide::narrow(result.str()); } static std::string ToUtf8(std::span input) { return ToUtf8(input.data(), input.size()); } // convert utf8 string to Wii U big-endian wchar_t string static std::vector FromUtf8(std::string_view str) { std::vector tmpStr; std::wstring w = boost::nowide::widen(str.data(), str.size()); for (auto& c : w) tmpStr.push_back((uint16)c); return tmpStr; } static sint32 ToInt(const std::string_view& input, sint32 defaultValue = 0) { sint32 value = defaultValue; if (input.size() >= 2 && (input[0] == '0' && (input[1] == 'x' || input[1] == 'X'))) { // hex number const std::from_chars_result result = std::from_chars(input.data() + 2, input.data() + input.size(), value, 16); if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) return defaultValue; } else { // decimal value const std::from_chars_result result = std::from_chars(input.data(), input.data() + input.size(), value); if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) return defaultValue; } return value; } static sint64 ToInt64(const std::string_view& input, sint64 defaultValue = 0) { sint64 value = defaultValue; if (input.size() >= 2 && (input[0] == '0' && (input[1] == 'x' || input[1] == 'X'))) { // hex number const std::from_chars_result result = std::from_chars(input.data() + 2, input.data() + input.size(), value, 16); if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) return defaultValue; } else { // decimal value const std::from_chars_result result = std::from_chars(input.data(), input.data() + input.size(), value); if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) return defaultValue; } return value; } static size_t ParseHexString(std::string_view input, uint8* output, size_t maxOutputLength) { size_t parsedLen = 0; for (size_t i = 0; i < input.size() - 1; i += 2) { if (maxOutputLength <= 0) break; uint8 b = 0; uint8 c = input[i + 0]; // high nibble if (c >= '0' && c <= '9') b |= ((c - '0') << 4); else if (c >= 'a' && c <= 'f') b |= ((c - 'a' + 10) << 4); else if (c >= 'A' && c <= 'F') b |= ((c - 'A' + 10) << 4); else break; // low nibble c = input[i + 1]; if (c >= '0' && c <= '9') b |= (c - '0'); else if (c >= 'a' && c <= 'f') b |= (c - 'a' + 10); else if (c >= 'A' && c <= 'F') b |= (c - 'A' + 10); else break; *output = b; output++; maxOutputLength--; parsedLen++; } return parsedLen; } class StringLineIterator { public: class Iterator { public: using iterator_category = std::input_iterator_tag; using value_type = std::string_view; using difference_type = std::ptrdiff_t; using pointer = const std::string_view*; using reference = const std::string_view&; Iterator(std::string_view str, sint32 pos) : m_str(str), m_pos(pos) { update_line(); } reference operator*() const { return m_line; } pointer operator->() const { return &m_line; } Iterator& operator++() { m_pos = m_nextPos; update_line(); return *this; } friend bool operator==(const Iterator& lhs, const Iterator& rhs) { return lhs.m_str.data() == rhs.m_str.data() && lhs.m_pos == rhs.m_pos; } friend bool operator!=(const Iterator& lhs, const Iterator& rhs) { return !(lhs == rhs); } private: void update_line() { if (m_pos >= m_str.size()) { m_pos = -1; m_line = {}; return; } auto pos = m_str.find('\n', m_pos); m_nextPos = pos != std::string_view::npos ? pos : -1; if(m_nextPos < 0) m_line = m_str.substr(m_pos, std::string::npos); else { m_line = m_str.substr(m_pos, m_nextPos - m_pos); ++m_nextPos; // skip \n } while (!m_line.empty() && m_line.back() == '\r') m_line.remove_suffix(1); } std::string_view m_str; sint32 m_pos; sint32 m_nextPos; std::string_view m_line; }; StringLineIterator(std::string_view str) : m_str(str) {} StringLineIterator(std::span str) : m_str((const char*)str.data(), str.size()) {} Iterator begin() const { return Iterator{m_str, 0 }; } Iterator end() const { return Iterator{m_str, -1 }; } private: std::string_view m_str; }; }; ================================================ FILE: src/util/helpers/StringParser.h ================================================ #pragma once class StringTokenParser { public: StringTokenParser() : m_str(nullptr), m_len(0) {}; StringTokenParser(const char* input, sint32 inputLen) : m_str(input), m_len(inputLen) {}; StringTokenParser(std::string_view str) : m_str(str.data()), m_len((sint32)str.size()) {}; // skip whitespaces at current ptr position void skipWhitespaces() { m_str = _skipWhitespaces(m_str, m_len); } // decrease string length as long as there is a whitespace at the end void trimWhitespaces() { while (m_len > 0) { const char c = m_str[m_len - 1]; if (c != ' ' && c != '\t') break; m_len--; } } bool isEndOfString() { return m_len <= 0; } sint32 skipToCharacter(const char c) { auto str = m_str; auto len = m_len; sint32 idx = 0; while (len > 0) { if (*str == c) { m_str = str; m_len = len; return idx; } len--; str++; idx++; } return -1; } bool matchWordI(const char* word) { auto str = m_str; auto length = m_len; str = _skipWhitespaces(str, length); for (sint32 i = 0; i <= length; i++) { if (word[i] == '\0') { m_str = str + i; m_len = length - i; return true; } if (i == length) return false; char c1 = str[i]; char c2 = word[i]; c1 = _toUpperCase(c1); c2 = _toUpperCase(c2); if (c1 != c2) return false; } return false; } bool compareCharacter(sint32 relativeIndex, const char c) { if (relativeIndex >= m_len) return false; return m_str[relativeIndex] == c; } bool compareCharacterI(sint32 relativeIndex, const char c) { if (relativeIndex >= m_len) return false; return _toUpperCase(m_str[relativeIndex]) == _toUpperCase(c); } void skipCharacters(sint32 count) { if (count > m_len) count = m_len; m_str += count; m_len -= count; } bool parseU32(uint32& val) { auto str = m_str; auto length = m_len; str = _skipWhitespaces(str, length); uint32 value = 0; sint32 index = 0; bool isHex = false; if (length <= 0) return false; if (length >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { isHex = true; index += 2; } else if (str[index] == '0') { isHex = true; index++; } if (length <= index) return false; if (isHex) { sint32 firstDigitIndex = index; for (; index < length; index++) { const char c = str[index]; if (c >= '0' && c <= '9') { value *= 0x10; value += (c - '0'); } else if (c >= 'a' && c <= 'f') { value *= 0x10; value += (c - 'a' + 10); } else if (c >= 'A' && c <= 'F') { value *= 0x10; value += (c - 'A' + 10); } else break; } if (index == firstDigitIndex) return false; m_str = str + index; m_len = length - index; } else { sint32 firstDigitIndex = index; for (; index < length; index++) { const char c = str[index]; if (c >= '0' && c <= '9') { value *= 10; value += (c - '0'); } else break; } if (index == firstDigitIndex) return false; m_str = str + index; m_len = length - index; } val = value; return true; } bool parseSymbolName(const char*& symbolStr, sint32& symbolLength) { auto str = m_str; auto length = m_len; str = _skipWhitespaces(str, length); // symbols must start with a letter or _ if (length <= 0) return false; if (!(str[0] >= 'a' && str[0] <= 'z') && !(str[0] >= 'A' && str[0] <= 'Z') && !(str[0] == '_')) return false; sint32 idx = 1; while (idx < length) { const char c = str[idx]; if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9') && !(c == '_') && !(c == '.')) break; idx++; } symbolStr = str; symbolLength = idx; m_str = str + idx; m_len = length - idx; return true; } const char* getCurrentPtr() { return m_str; } sint32 getCurrentLen() { return m_len; } void storeParserState(StringTokenParser* bak) { bak->m_str = m_str; bak->m_len = m_len; } void restoreParserState(const StringTokenParser* bak) { m_str = bak->m_str; m_len = bak->m_len; } private: const char* _skipWhitespaces(const char* str, sint32& length) { while (length > 0) { if (*str != ' ' && *str != '\t') break; str++; length--; } return str; } char _toUpperCase(const char c) { if (c >= 'a' && c <= 'z') return c + ('A' - 'a'); return c; } private: const char* m_str; sint32 m_len; }; ================================================ FILE: src/util/helpers/SystemException.h ================================================ #pragma once #include "util/helpers/helpers.h" class SystemException : public std::runtime_error { public: SystemException() : std::runtime_error(GetSystemErrorMessage().c_str()), m_error_code(GetExceptionError()) {} SystemException(const std::exception& ex) : std::runtime_error(GetSystemErrorMessage(ex).c_str()), m_error_code(GetExceptionError()) {} SystemException(const std::error_code& ec) : std::runtime_error(GetSystemErrorMessage(ec).c_str()), m_error_code(GetExceptionError()) {} [[nodiscard]] DWORD GetErrorCode() const { return m_error_code; } private: DWORD m_error_code; }; ================================================ FILE: src/util/helpers/TempState.h ================================================ #pragma once template class TempState { public: TempState(TCtor ctor, TDtor dtor) : m_dtor(std::move(dtor)) { ctor(); } ~TempState() { m_dtor(); } private: TDtor m_dtor; }; ================================================ FILE: src/util/helpers/enum_array.hpp ================================================ #pragma once // expects the enum class (T) to have a value called ENUM_COUNT which is the maximum value + 1 template class enum_array : public std::array(E::ENUM_COUNT)> { public: T& operator[] (E e) { return std::array(E::ENUM_COUNT)>::operator[]((std::size_t)e); } const T& operator[] (E e) const { return std::array(E::ENUM_COUNT)>::operator[]((std::size_t)e); } }; ================================================ FILE: src/util/helpers/fixedSizeList.h ================================================ #pragma once #include template class FixedSizeList { public: std::array m_elementArray; int count = 0; void add(T n) { if (checkMaxSize && count >= maxElements) return; m_elementArray[count] = n; count++; } void addUnique(T n) { if (checkMaxSize && count >= maxElements) return; for (int i = 0; i < count; i++) { if (m_elementArray[i] == n) return; } m_elementArray[count] = n; count++; } void remove(T n) { for (int i = 0; i < count; i++) { if (m_elementArray[i] == n) { m_elementArray[i] = m_elementArray[count - 1]; count--; return; } } } bool containsAndRemove(T n) { for (int i = 0; i < count; i++) { if (m_elementArray[i] == n) { m_elementArray[i] = m_elementArray[count - 1]; count--; return true; } } return false; } sint32 find(T n) { for (int i = 0; i < count; i++) { if (m_elementArray[i] == n) { return i; } } return -1; } private: }; ================================================ FILE: src/util/helpers/fspinlock.h ================================================ #pragma once // minimal but efficient non-recursive spinlock implementation #include class FSpinlock { public: bool is_locked() const { return m_lockBool.load(std::memory_order_relaxed); } // implement BasicLockable and Lockable void lock() const { while (true) { if (!m_lockBool.exchange(true, std::memory_order_acquire)) break; while (m_lockBool.load(std::memory_order_relaxed)) _mm_pause(); } } bool try_lock() const { return !m_lockBool.exchange(true, std::memory_order_acquire); } void unlock() const { m_lockBool.store(false, std::memory_order_release); } private: mutable std::atomic m_lockBool = false; }; ================================================ FILE: src/util/helpers/helpers.cpp ================================================ #include "helpers.h" #include #include #include #include #include "config/ActiveSettings.h" #include #include #if BOOST_OS_WINDOWS #include #endif std::string& ltrim(std::string& str, const std::string& chars) { str.erase(0, str.find_first_not_of(chars)); return str; } std::string& rtrim(std::string& str, const std::string& chars) { str.erase(str.find_last_not_of(chars) + 1); return str; } std::string& trim(std::string& str, const std::string& chars) { return ltrim(rtrim(str, chars), chars); } std::string_view& ltrim(std::string_view& str, const std::string& chars) { str.remove_prefix(std::min(str.find_first_not_of(chars), str.size())); return str; } std::string_view& rtrim(std::string_view& str, const std::string& chars) { str.remove_suffix(std::max(str.size() - str.find_last_not_of(chars) - 1, (size_t)0)); return str; } std::string_view& trim(std::string_view& str, const std::string& chars) { return ltrim(rtrim(str, chars), chars); } #if BOOST_OS_WINDOWS std::string GetSystemErrorMessage(DWORD error_code) { if(error_code == ERROR_SUCCESS) return {}; LPSTR lpMsgBuf = nullptr; FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code, 0, (LPSTR)&lpMsgBuf, 0, nullptr); if (lpMsgBuf) { std::string str = fmt::format("{}: {}", _tr("Error"), lpMsgBuf); // TRANSLATE LocalFree(lpMsgBuf); return str; } return fmt::format("{}: {:#x}", _tr("Error code"), error_code); } std::string GetSystemErrorMessage() { return GetSystemErrorMessage(GetLastError()); } #else std::string GetSystemErrorMessage() { return ""; } #endif std::string GetSystemErrorMessage(const std::exception& ex) { const std::string msg = GetSystemErrorMessage(); if(msg.empty()) return ex.what(); return fmt::format("{}\n{}",msg, ex.what()); } std::string GetSystemErrorMessage(const std::error_code& ec) { const std::string msg = GetSystemErrorMessage(); if(msg.empty()) return ec.message(); return fmt::format("{}\n{}",msg, ec.message()); } #if BOOST_OS_WINDOWS const DWORD MS_VC_EXCEPTION = 0x406D1388; #pragma pack(push,8) typedef struct tagTHREADNAME_INFO { DWORD dwType; // Must be 0x1000. LPCSTR szName; // Pointer to name (in user addr space). DWORD dwThreadID; // Thread ID (-1=caller thread). DWORD dwFlags; // Reserved for future use, must be zero. } THREADNAME_INFO; #pragma pack(pop) #endif void SetThreadName(const char* name) { #if BOOST_OS_WINDOWS using SetThreadDescription_t = HRESULT (*)(HANDLE hThread, PCWSTR lpThreadDescription); static SetThreadDescription_t pSetThreadDescription = nullptr; if (!pSetThreadDescription) pSetThreadDescription = (SetThreadDescription_t)GetProcAddress(LoadLibraryW(L"Kernel32.dll"), "SetThreadDescription"); if (pSetThreadDescription) { size_t len = strlen(name) * 2 + 1; auto threadDescription = new wchar_t[len]; if (boost::nowide::widen(threadDescription, len, name)) pSetThreadDescription(GetCurrentThread(), threadDescription); delete[] threadDescription; } #ifdef _MSC_VER THREADNAME_INFO info; info.dwType = 0x1000; info.szName = name; info.dwThreadID = GetCurrentThreadId(); info.dwFlags = 0; #pragma warning(push) #pragma warning(disable: 6320 6322) __try { RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); } __except (EXCEPTION_EXECUTE_HANDLER) { } #pragma warning(pop) #endif #elif BOOST_OS_MACOS pthread_setname_np(name); #else if(std::strlen(name) > 15) cemuLog_log(LogType::Force, "Truncating thread name {} because it was longer than 15 characters", name); pthread_setname_np(pthread_self(), std::string{name}.substr(0,15).c_str()); #endif } #if BOOST_OS_WINDOWS std::pair GetWindowsVersion() { using RtlGetVersion_t = LONG(*)(POSVERSIONINFOEXW); static RtlGetVersion_t pRtlGetVersion = nullptr; if(!pRtlGetVersion) pRtlGetVersion = (RtlGetVersion_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion"); cemu_assert(pRtlGetVersion); OSVERSIONINFOEXW version_info{}; pRtlGetVersion(&version_info); return { version_info.dwMajorVersion, version_info.dwMinorVersion }; } bool IsWindows81OrGreater() { const auto [major, minor] = GetWindowsVersion(); return major > 6 || (major == 6 && minor >= 3); } bool IsWindows10OrGreater() { const auto [major, minor] = GetWindowsVersion(); return major >= 10; } #endif fs::path GetParentProcess() { fs::path result; #if BOOST_OS_WINDOWS HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(hSnapshot != INVALID_HANDLE_VALUE) { DWORD pid = GetCurrentProcessId(); PROCESSENTRY32 pe{}; pe.dwSize = sizeof(pe); for(BOOL ret = Process32First(hSnapshot, &pe); ret; ret = Process32Next(hSnapshot, &pe)) { if(pe.th32ProcessID == pid) { HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ParentProcessID); if(hProcess) { wchar_t tmp[MAX_PATH]; DWORD size = std::size(tmp); if (QueryFullProcessImageNameW(hProcess, 0, tmp, &size) && size > 0) result = tmp; CloseHandle(hProcess); } break; } } CloseHandle(hSnapshot); } #else assert_dbg(); #endif return result; } std::string ltrim_copy(const std::string& s) { std::string result = s; ltrim(result); return result; } std::string rtrim_copy(const std::string& s) { std::string result = s; rtrim(result); return result; } uint32_t GetPhysicalCoreCount() { static uint32_t s_core_count = 0; if (s_core_count != 0) return s_core_count; #if BOOST_OS_WINDOWS auto core_count = std::thread::hardware_concurrency(); // Get physical cores PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = nullptr; DWORD returnLength = 0; GetLogicalProcessorInformation(buffer, &returnLength); if (returnLength > 0) { buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(returnLength); if (GetLogicalProcessorInformation(buffer, &returnLength)) { uint32_t counter = 0; for (DWORD i = 0; i < returnLength / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) { if (buffer[i].Relationship == RelationProcessorCore) ++counter; } if (counter > 0 && counter < core_count) core_count = counter; } free(buffer); } s_core_count = core_count; return core_count; #else return std::thread::hardware_concurrency(); #endif } bool TestWriteAccess(const fs::path& p) { std::error_code ec; // must be path and must exist if (!fs::exists(p, ec) || !fs::is_directory(p, ec)) return false; // retry 3 times for (int i = 0; i < 3; ++i) { const auto filename = p / fmt::format("_{}.tmp", GenerateRandomString(8)); if (fs::exists(filename, ec)) continue; std::ofstream file(filename); if (!file.is_open()) // file couldn't be created break; file.close(); fs::remove(filename, ec); return true; } return false; } // make path relative to Cemu directory fs::path MakeRelativePath(const fs::path& base, const fs::path& path) { try { return fs::relative(path, base); } catch (const std::exception&) { return path; } } #ifdef HAS_DIRECTINPUT bool GUIDFromString(const char* string, GUID& guid) { unsigned long p0; int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10; const sint32 count = sscanf_s(string, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", &p0, &p1, &p2, &p3, &p4, &p5, &p6, &p7, &p8, &p9, &p10); if (count != 11) return false; guid.Data1 = p0; guid.Data2 = p1; guid.Data3 = p2; guid.Data4[0] = p3; guid.Data4[1] = p4; guid.Data4[2] = p5; guid.Data4[3] = p6; guid.Data4[4] = p7; guid.Data4[5] = p8; guid.Data4[6] = p9; guid.Data4[7] = p10; return count == 11; } std::string StringFromGUID(const GUID& guid) { char temp[256]; sprintf(temp, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); return std::string(temp); } std::wstring WStringFromGUID(const GUID& guid) { wchar_t temp[256]; swprintf_s(temp, L"%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); return std::wstring(temp); } #endif std::vector TokenizeView(std::string_view str, char delimiter) { std::vector result; size_t last_token_index = 0; for (auto index = str.find(delimiter); index != std::string_view::npos; index = str.find(delimiter, index + 1)) { const auto token = str.substr(last_token_index, index - last_token_index); result.emplace_back(token); last_token_index = index + 1; } try { const auto token = str.substr(last_token_index); result.emplace_back(token); } catch (const std::invalid_argument&) {} return result; } std::vector Tokenize(std::string_view str, char delimiter) { std::vector result; size_t last_token_index = 0; for (auto index = str.find(delimiter); index != std::string_view::npos; index = str.find(delimiter, index + 1)) { const auto token = str.substr(last_token_index, index - last_token_index); result.emplace_back(token); last_token_index = index + 1; } try { const auto token = str.substr(last_token_index); result.emplace_back(token); } catch (const std::invalid_argument&) {} return result; } std::string GenerateRandomString(size_t length) { const std::string kCharacters{ "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "1234567890" }; return GenerateRandomString(length, kCharacters); } std::string GenerateRandomString(const size_t length, const std::string_view characters) { assert(!characters.empty()); std::string result; result.resize(length); std::random_device rd; std::mt19937 gen(rd()); // workaround for static asserts using boost boost::random::uniform_int_distribution index_dist(0, characters.size() - 1); std::generate_n( result.begin(), length, [&] { return characters[index_dist(gen)]; } ); return result; } std::optional> zlibDecompress(const std::vector& compressed, size_t sizeHint) { int err; std::vector decompressed; size_t outWritten = 0; size_t bytesPerIteration = sizeHint; z_stream stream; stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; stream.avail_in = compressed.size(); stream.next_in = (Bytef*)compressed.data(); err = inflateInit2(&stream, 32); // 32 is a zlib magic value to enable header detection if (err != Z_OK) return {}; do { decompressed.resize(decompressed.size() + bytesPerIteration); const auto availBefore = decompressed.size() - outWritten; stream.avail_out = availBefore; stream.next_out = decompressed.data() + outWritten; err = inflate(&stream, Z_NO_FLUSH); if (!(err == Z_OK || err == Z_STREAM_END)) { inflateEnd(&stream); return {}; } outWritten += availBefore - stream.avail_out; bytesPerIteration *= 2; } while (err != Z_STREAM_END); inflateEnd(&stream); decompressed.resize(stream.total_out); return decompressed; } ================================================ FILE: src/util/helpers/helpers.h ================================================ #pragma once #include #include #include #include #include "util/math/vector2.h" #include "util/math/vector3.h" #ifdef __clang__ #include "Common/unix/fast_float.h" #endif template constexpr auto to_underlying(TType v) noexcept { return static_cast>(v); } // wrapper to allow reverse iteration with range-based loops before C++20 template class reverse_itr { private: T& iterable_; public: explicit reverse_itr(T& iterable) : iterable_{ iterable } {} auto begin() const { return std::rbegin(iterable_); } auto end() const { return std::rend(iterable_); } }; #ifndef M_PI #define M_PI 3.14159265358979323846 #endif template T deg2rad(T v) { return v * static_cast(M_PI) / static_cast(180); } template T rad2deg(T v) { return v * static_cast(180) / static_cast(M_PI); } template Vector3 deg2rad(const Vector3& v) { return { deg2rad(v.x), deg2rad(v.y), deg2rad(v.z) }; } template Vector3 rad2deg(const Vector3& v) { return { rad2deg(v.x), rad2deg(v.y), rad2deg(v.z) }; } template Vector2 deg2rad(const Vector2& v) { return { deg2rad(v.x), deg2rad(v.y) }; } template Vector2 rad2deg(const Vector2& v) { return { rad2deg(v.x), rad2deg(v.y) }; } uint32_t GetPhysicalCoreCount(); // Creates a temporary file to test for write access bool TestWriteAccess(const fs::path& p); fs::path MakeRelativePath(const fs::path& base, const fs::path& path); #ifdef HAS_DIRECTINPUT bool GUIDFromString(const char* string, GUID& guid); std::string StringFromGUID(const GUID& guid); std::wstring WStringFromGUID(const GUID& guid); #endif std::vector TokenizeView(std::string_view string, char delimiter); std::vector Tokenize(std::string_view string, char delimiter); std::string ltrim_copy(const std::string& s); std::string rtrim_copy(const std::string& s); std::string& ltrim(std::string& str, const std::string& chars = "\t\n\v\f\r "); std::string& rtrim(std::string& str, const std::string& chars = "\t\n\v\f\r "); std::string& trim(std::string& str, const std::string& chars = "\t\n\v\f\r "); std::string_view& ltrim(std::string_view& str, const std::string& chars = "\t\n\v\f\r "); std::string_view& rtrim(std::string_view& str, const std::string& chars = "\t\n\v\f\r "); std::string_view& trim(std::string_view& str, const std::string& chars = "\t\n\v\f\r "); std::string GenerateRandomString(size_t length); std::string GenerateRandomString(size_t length, std::string_view characters); std::string GetSystemErrorMessage(); std::string GetSystemErrorMessage(DWORD error_code); std::string GetSystemErrorMessage(const std::exception& ex); std::string GetSystemErrorMessage(const std::error_code& ec); template struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...)->overloaded; template bool equals(T v1, T v2) { /* return std::fabs(x-y) <= std::numeric_limits::epsilon() * std::fabs(x+y) * ulp // unless the result is subnormal || std::fabs(x-y) < std::numeric_limits::min(); */ if constexpr (std::is_floating_point_v) return std::abs(v1 - v2) < (T)0.000001; else if constexpr (std::is_same_v) return strcmp(v1, v2) == 0; else return v1 == v2; } template T ConvertString(std::string_view str, sint32 base) { if (str.empty()) return {}; static_assert(std::is_integral_v); T result; ltrim(str); // from_chars cant deal with hex numbers starting with "0x" if (base == 16) { const sint32 index = str[0] == '-' ? 1 : 0; if (str.size() >= 2 && str[index+0] == '0' && tolower(str[index+1]) == 'x') str = str.substr(index + 2); if (std::from_chars(str.data(), str.data() + str.size(), result, base).ec == std::errc()) { if (index == 1) { if constexpr(std::is_unsigned_v) result = static_cast(-static_cast>(result)); else result = -result; } return result; } return {}; } if(std::from_chars(str.data(), str.data() + str.size(), result, base).ec == std::errc()) return result; return {}; } template T ConvertString(std::string_view str) { if (str.empty()) return {}; T result; ltrim(str); if constexpr (std::is_same_v) { return str == "1" || boost::iequals(str, "true"); } else if constexpr(std::is_floating_point_v) { // from_chars can't deal with float conversation starting with "+" ltrim(str, "+"); #ifdef __clang__ if (fast_float::from_chars(str.data(), str.data() + str.size(), result).ec == std::errc()) return result; #else if (std::from_chars(str.data(), str.data() + str.size(), result).ec == std::errc()) return result; #endif return {}; } else if constexpr(std::is_enum_v) { return (T)ConvertString>(str); } else { const sint32 index = str[0] == '-' ? 1 : 0; if (str.size() >= 2 && str[index + 0] == '0' && tolower(str[index + 1]) == 'x') result = ConvertString(str, 16); else result = ConvertString(str, 10); } return result; } template constexpr T DegToRad(T deg) { return (T)((double)deg * M_PI / 180); } template constexpr T RadToDeg(T rad) { return (T)((double)rad * 180 / M_PI); } template std::string ToString(T value) { std::ostringstream str; str.imbue(std::locale("C")); str << value; return str.str(); } template T FromString(std::string value) { std::istringstream str(value); str.imbue(std::locale("C")); T tmp; str >> tmp; return tmp; } template size_t RemoveDuplicatesKeepOrder(std::vector& vec) { std::set tmp; auto new_end = std::remove_if(vec.begin(), vec.end(), [&tmp](const T& value) { if (tmp.find(value) != std::end(tmp)) return true; tmp.insert(value); return false; }); vec.erase(new_end, vec.end()); return vec.size(); } void SetThreadName(const char* name); inline uint64 MakeU64(uint32 high, uint32 low) { return ((uint64)high << 32) | ((uint64)low); } static bool IsValidFilename(std::string_view sv) { for (auto& it : sv) { uint8 c = (uint8)it; if (c < 0x20) return false; if (c == '.' || c == '#' || c == '/' || c == '\\' || c == '<' || c == '>' || c == '|' || c == ':' || c == '\"') return false; } if (!sv.empty()) { if (sv.back() == ' ' || sv.back() == '.') return false; } return true; } // MAJOR; MINOR std::pair GetWindowsVersion(); bool IsWindows81OrGreater(); bool IsWindows10OrGreater(); fs::path GetParentProcess(); std::optional> zlibDecompress(const std::vector& compressed, size_t sizeHint = 32*1024); ================================================ FILE: src/util/helpers/ringbuffer.h ================================================ #pragma once #include template class RingBuffer { public: RingBuffer(); bool Push(const T& v); T Pop() requires (!std::is_array_v) { std::unique_lock lock(m_mutex); if (m_readPointer == m_writePointer) { return T(); } const T& tmp = m_data[m_readPointer]; m_readPointer = (m_readPointer + 1) % elements; return tmp; } T& GetSlot(); T& GetSlotAndAdvance(); void Advance(); void Clear(); P GetReadPointer(); P GetWritePointer(); bool HasData(); private: T m_data[elements]; P m_readPointer; P m_writePointer; std::mutex m_mutex; }; template RingBuffer::RingBuffer() : m_readPointer(0), m_writePointer(0) { } template bool RingBuffer::Push(const T& v) { std::unique_lock lock(m_mutex); if (m_readPointer == ((m_writePointer + 1) % elements)) { debugBreakpoint(); // buffer is full return false; } m_data[m_writePointer] = v; m_writePointer = (m_writePointer + 1) % elements; return true; } template T& RingBuffer::GetSlot() { std::unique_lock lock(m_mutex); T& result = m_data[m_writePointer]; m_writePointer = (m_writePointer + 1) % elements; return result; } template T& RingBuffer::GetSlotAndAdvance() { std::unique_lock lock(m_mutex); T& result = m_data[m_writePointer]; m_writePointer = (m_writePointer + 1) % elements; m_readPointer = (m_readPointer + 1) % elements; return result; } template void RingBuffer::Advance() { std::unique_lock lock(m_mutex); if (m_readPointer != m_writePointer) { m_readPointer = (m_readPointer + 1) % elements; } } template void RingBuffer::Clear() { std::unique_lock lock(m_mutex); m_readPointer = 0; m_writePointer = 0; } template P RingBuffer::GetReadPointer() { std::unique_lock lock(m_mutex); P tmp = m_readPointer; return tmp; } template P RingBuffer::GetWritePointer() { std::unique_lock lock(m_mutex); P tmp = m_writePointer; return tmp; } template bool RingBuffer::HasData() { std::unique_lock lock(m_mutex); return m_readPointer != m_writePointer; } ================================================ FILE: src/util/highresolutiontimer/HighResolutionTimer.cpp ================================================ #include "util/highresolutiontimer/HighResolutionTimer.h" #include "Common/precompiled.h" HighResolutionTimer HighResolutionTimer::now() { #if BOOST_OS_WINDOWS LARGE_INTEGER pc; QueryPerformanceCounter(&pc); return HighResolutionTimer(pc.QuadPart); #elif BOOST_OS_LINUX timespec pc; clock_gettime(CLOCK_MONOTONIC_RAW, &pc); uint64 nsec = (uint64)pc.tv_sec * (uint64)1000000000 + (uint64)pc.tv_nsec; return HighResolutionTimer(nsec); #elif BOOST_OS_MACOS return HighResolutionTimer(clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW)); #elif BOOST_OS_BSD timespec pc; clock_gettime(CLOCK_MONOTONIC, &pc); uint64 nsec = (uint64)pc.tv_sec * (uint64)1000000000 + (uint64)pc.tv_nsec; return HighResolutionTimer(nsec); #endif } HRTick HighResolutionTimer::getFrequency() { return m_freq; } uint64 HighResolutionTimer::m_freq = []() -> uint64 { #if BOOST_OS_WINDOWS LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); return (uint64)(freq.QuadPart); #elif BOOST_OS_MACOS return 1000000000; #elif BOOST_OS_BSD timespec pc; clock_getres(CLOCK_MONOTONIC, &pc); return (uint64)1000000000 / (uint64)pc.tv_nsec; #else timespec pc; clock_getres(CLOCK_MONOTONIC_RAW, &pc); return (uint64)1000000000 / (uint64)pc.tv_nsec; #endif }(); ================================================ FILE: src/util/highresolutiontimer/HighResolutionTimer.h ================================================ #pragma once using HRTick = uint64; class HighResolutionTimer { public: HighResolutionTimer() { m_timePoint = 0; } HRTick getTick() const { return m_timePoint; } uint64 getTickInSeconds() const { return m_timePoint / m_freq; } // return time difference in seconds, this is an utility function mainly intended for debugging/benchmarking purposes. Avoid using doubles for precise timing static double getTimeDiff(HRTick startTime, HRTick endTime) { return (double)(endTime - startTime) / (double)m_freq; } // returns tick difference and frequency static uint64 getTimeDiffEx(HRTick startTime, HRTick endTime, uint64& freq) { freq = m_freq; return endTime - startTime; } static HighResolutionTimer now(); static HRTick getFrequency(); static HRTick microsecondsToTicks(uint64 microseconds) { return microseconds * m_freq / 1000000; } static uint64 ticksToMicroseconds(HRTick ticks) { return ticks * 1000000 / m_freq; } private: HighResolutionTimer(uint64 timePoint) : m_timePoint(timePoint) {}; uint64 m_timePoint; static uint64 m_freq; }; // benchmark helper utility // measures time between Start() and Stop() call class BenchmarkTimer { public: void Start() { m_startTime = HighResolutionTimer::now().getTick(); } void Stop() { m_stopTime = HighResolutionTimer::now().getTick(); } double GetElapsedMilliseconds() const { cemu_assert_debug(m_startTime != 0 && m_stopTime != 0); cemu_assert_debug(m_startTime <= m_stopTime); uint64 tickDif = m_stopTime - m_startTime; double freq = (double)HighResolutionTimer::now().getFrequency(); double elapsedMS = (double)tickDif * 1000.0 / freq; return elapsedMS; } private: HRTick m_startTime{}; HRTick m_stopTime{}; }; ================================================ FILE: src/util/libusbWrapper/libusbWrapper.cpp ================================================ #include "libusbWrapper.h" /* #include "config/ActiveSettings.h" libusbWrapper::libusbWrapper() { } void libusbWrapper::init() { #if BOOST_OS_WINDOWS if (m_isInitialized) return; m_isInitialized = true; // load module m_module = LoadLibraryW(L"libusb-1.0.dll"); if (!m_module) { const auto path = ActiveSettings::GetDataPath("resources/libusb-1.0.dll"); m_module = LoadLibraryW(path.generic_wstring().c_str()); if (!m_module) { cemuLog_log(LogType::Force, "libusbWrapper: can't load libusb-1.0.dll"); return; } } // grab imports #define FETCH_IMPORT(__NAME__) p_##__NAME__ = (decltype(&__NAME__))GetProcAddress(m_module, #__NAME__) FETCH_IMPORT(libusb_init); FETCH_IMPORT(libusb_exit); FETCH_IMPORT(libusb_interrupt_transfer); FETCH_IMPORT(libusb_get_device_list); FETCH_IMPORT(libusb_get_device_descriptor); FETCH_IMPORT(libusb_open); FETCH_IMPORT(libusb_close); FETCH_IMPORT(libusb_kernel_driver_active); FETCH_IMPORT(libusb_detach_kernel_driver); FETCH_IMPORT(libusb_claim_interface); FETCH_IMPORT(libusb_free_device_list); FETCH_IMPORT(libusb_get_config_descriptor); FETCH_IMPORT(libusb_hotplug_register_callback); FETCH_IMPORT(libusb_hotplug_deregister_callback); FETCH_IMPORT(libusb_has_capability); FETCH_IMPORT(libusb_error_name); FETCH_IMPORT(libusb_get_string_descriptor); FETCH_IMPORT(libusb_get_string_descriptor_ascii); FETCH_IMPORT(libusb_free_config_descriptor); #undef FETCH_IMPORT // create default context if (p_libusb_init) p_libusb_init(nullptr); #else cemuLog_log(LogType::Force, "libusbWrapper: Not supported on this OS"); #endif } libusbWrapper::~libusbWrapper() { #if BOOST_OS_WINDOWS // destroy default context if(p_libusb_exit) p_libusb_exit(nullptr); // unload module if(m_module) FreeLibrary(m_module); #endif } */ ================================================ FILE: src/util/libusbWrapper/libusbWrapper.h ================================================ #pragma once // todo - port to cmake build /* #include "util/helpers/ClassWrapper.h" #pragma warning(disable:4200) #include "libusb-1.0/libusb.h" #pragma warning(default:4200) class libusbWrapper : public SingletonRef { public: libusbWrapper(); ~libusbWrapper(); void init(); bool isAvailable() const { return p_libusb_init != nullptr; }; decltype(&libusb_init) p_libusb_init = nullptr; decltype(&libusb_exit) p_libusb_exit = nullptr; decltype(&libusb_interrupt_transfer) p_libusb_interrupt_transfer; decltype(&libusb_get_device_list) p_libusb_get_device_list; decltype(&libusb_get_device_descriptor) p_libusb_get_device_descriptor; decltype(&libusb_open) p_libusb_open; decltype(&libusb_kernel_driver_active) p_libusb_kernel_driver_active; decltype(&libusb_detach_kernel_driver) p_libusb_detach_kernel_driver; decltype(&libusb_claim_interface) p_libusb_claim_interface; decltype(&libusb_free_device_list) p_libusb_free_device_list; decltype(&libusb_get_config_descriptor) p_libusb_get_config_descriptor; decltype(&libusb_free_config_descriptor) p_libusb_free_config_descriptor; decltype(&libusb_close) p_libusb_close; decltype(&libusb_hotplug_register_callback) p_libusb_hotplug_register_callback; decltype(&libusb_hotplug_deregister_callback) p_libusb_hotplug_deregister_callback; decltype(&libusb_has_capability) p_libusb_has_capability; decltype(&libusb_error_name) p_libusb_error_name; decltype(&libusb_get_string_descriptor) p_libusb_get_string_descriptor; decltype(&libusb_get_string_descriptor_ascii) p_libusb_get_string_descriptor_ascii; private: #if BOOST_OS_WINDOWS HMODULE m_module = nullptr; bool m_isInitialized = false; #endif }; */ ================================================ FILE: src/util/math/glm.h ================================================ #pragma once namespace glm { inline quat normalize_xyz(const quat& q) { const auto xyzTargetLength = std::sqrt(1.0f - q.w * q.w); const auto lengthScaler = xyzTargetLength / sqrtf(q.x * q.x + q.y * q.y + q.z * q.z); return quat(q.w, q.x * lengthScaler, q.y * lengthScaler, q.z * lengthScaler); } inline vec3 GetVectorX(const quat& q) { return vec3( 2.0f * (q.w * q.w + q.x * q.x) - 1.0f, 2.0f * (q.x * q.y - q.w * q.z), 2.0f * (q.x * q.z + q.w * q.y)); } inline vec3 GetVectorY(const quat& q) { return vec3( 2.0f * (q.x * q.y + q.w * q.z), 2.0f * (q.w * q.w + q.y * q.y) - 1.0f, 2.0f * (q.y * q.z - q.w * q.x) ); } inline vec3 GetVectorZ(const quat& q) { return vec3 ( 2.0f * (q.x * q.z - q.w * q.y), 2.0f * (q.y * q.z + q.w * q.x), 2.0f * (q.w * q.w + q.z * q.z) - 1.0f); } } ================================================ FILE: src/util/math/quaternion.h ================================================ #pragma once #include #include "util/helpers/helpers.h" #include "util/math/vector3.h" #define DEG2RAD(__d__) ((__d__ * M_PI) / 180.0f ) #define RAD2DEG(__r__) ((__r__ * 180.0f) / M_PI) template class Quaternion { public: T w; T x; T y; T z; Quaternion(); Quaternion(const T& w, const T& x, const T& y, const T& z); Quaternion(float x, float y, float z); void Assign(float w, float x, float y, float z) { this->w = w; this->x = x; this->y = y; this->z = z; } static Quaternion FromAngleAxis(float inAngle, float inX, float inY, float inZ); std::tuple, Vector3, Vector3> GetRotationMatrix(); std::tuple, Vector3, Vector3> GetTransposedRotationMatrix(); // normalize but keep W void NormalizeXYZ() { const T xyzTargetLength = sqrtf((T)1.0 - w * w); const T lengthScaler = xyzTargetLength / sqrtf(x * x + y * y + z * z); x *= lengthScaler; y *= lengthScaler; z *= lengthScaler; } void NormalizeXYZW() { const T lengthScaler = 1.0f / sqrtf(w * w + x * x + y * y + z * z); w *= lengthScaler; x *= lengthScaler; y *= lengthScaler; z *= lengthScaler; } Vector3 GetVectorX() const { return Vector3( 2.0f * (w * w + x * x) - 1.0f, 2.0f * (x * y - w * z), 2.0f * (x * z + w * y)); } Vector3 GetVectorY() const { return Vector3( 2.0f * (x * y + w * z), 2.0f * (w * w + y * y) - 1.0f, 2.0f * (y * z - w * x) ); } Vector3 GetVectorZ() const { return Vector3( 2.0f * (x * z - w * y), 2.0f * (y * z + w * x), 2.0f * (w * w + z * z) - 1.0f); } Quaternion& operator*=(const Quaternion& rhs) { Assign(w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z, w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y, w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x, w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w); return *this; } Quaternion& operator+=(const Quaternion& rhs) { w += rhs.w; x += rhs.x; y += rhs.y; z += rhs.z; return *this; } friend Quaternion operator*(Quaternion lhs, const Quaternion& rhs) { lhs *= rhs; return lhs; } }; template Quaternion::Quaternion() : w(), x(), y(), z() {} template Quaternion::Quaternion(const T& w, const T& x, const T& y, const T& z) : w(w), x(x), y(y), z(z) {} template Quaternion::Quaternion(float x, float y, float z) { float pitch = DEG2RAD(x); float yaw = DEG2RAD(y); float roll = DEG2RAD(z); float cyaw = cos(0.5f * yaw); float cpitch = cos(0.5f * pitch); float croll = cos(0.5f * roll); float syaw = sin(0.5f * yaw); float spitch = sin(0.5f * pitch); float sroll = sin(0.5f * roll); float cyawcpitch = cyaw * cpitch; float syawspitch = syaw * spitch; float cyawspitch = cyaw * spitch; float syawcpitch = syaw * cpitch; this->w = cyawcpitch * croll + syawspitch * sroll; this->x = cyawspitch * croll + syawcpitch * sroll; this->y = syawcpitch * croll - cyawspitch * sroll; this->z = cyawcpitch * sroll - syawspitch * croll; } template Quaternion Quaternion::FromAngleAxis(float inAngle, float inX, float inY, float inZ) { Quaternion result = Quaternion(cosf(inAngle * 0.5f), inX, inY, inZ); result.NormalizeXYZ(); return result; } template std::tuple, Vector3, Vector3> Quaternion::GetRotationMatrix() { float sqw = w*w; float sqx = x*x; float sqy = y*y; float sqz = z*z; // invs (inverse square length) is only required if quaternion is not already normalised float invs = 1.0f / (sqx + sqy + sqz + sqw); Vector3 v1, v2, v3; v1.x = (sqx - sqy - sqz + sqw) * invs; // since sqw + sqx + sqy + sqz =1/invs*invs v2.y = (-sqx + sqy - sqz + sqw) * invs; v3.z = (-sqx - sqy + sqz + sqw) * invs; float tmp1 = x*y; float tmp2 = z*w; v2.x = 2.0 * (tmp1 + tmp2)*invs; v1.y = 2.0 * (tmp1 - tmp2)*invs; tmp1 = x*z; tmp2 = y*w; v3.x = 2.0 * (tmp1 - tmp2)*invs; v1.z = 2.0 * (tmp1 + tmp2)*invs; tmp1 = y*z; tmp2 = x*w; v3.y = 2.0 * (tmp1 + tmp2)*invs; v2.z = 2.0 * (tmp1 - tmp2)*invs; return std::make_tuple(v1, v2, v3); } template std::tuple, Vector3, Vector3> Quaternion::GetTransposedRotationMatrix() { float sqw = w*w; float sqx = x*x; float sqy = y*y; float sqz = z*z; // invs (inverse square length) is only required if quaternion is not already normalised float invs = 1.0f / (sqx + sqy + sqz + sqw); Vector3 v1, v2, v3; v1.x = (sqx - sqy - sqz + sqw) * invs; // since sqw + sqx + sqy + sqz =1/invs*invs v2.y = (-sqx + sqy - sqz + sqw) * invs; v3.z = (-sqx - sqy + sqz + sqw) * invs; float tmp1 = x*y; float tmp2 = z*w; v1.y = 2.0 * (tmp1 + tmp2)*invs; v2.x = 2.0 * (tmp1 - tmp2)*invs; tmp1 = x*z; tmp2 = y*w; v1.z = 2.0 * (tmp1 - tmp2)*invs; v3.x = 2.0 * (tmp1 + tmp2)*invs; tmp1 = y*z; tmp2 = x*w; v2.z = 2.0 * (tmp1 + tmp2)*invs; v3.y = 2.0 * (tmp1 - tmp2)*invs; return std::make_tuple(v1, v2, v3); } using Quaternionf = Quaternion; ================================================ FILE: src/util/math/vector2.h ================================================ #pragma once #include template class Vector2 { public: T x; T y; Vector2() : x{}, y{} {} Vector2(T x, T y) : x(x), y(y) {} template Vector2(const Vector2& v) : x((T)v.x), y((T)v.y) {} float Length() const { return (float)std::sqrt((x * x) + (y * y)); } float Cross(const Vector2& v) const { return x * v.y - y * v.x; } float Dot(const Vector2& v) const { return x * v.x + y * v.y; } Vector2 Ortho() const { return Vector2(y, -x); } Vector2 Normalized() const { const auto len = Length(); if (len == 0) return Vector2(); return Vector2((T)((float)x / len), (T)((float)y / len)); } Vector2& Normalize() { const auto len = Length(); if (len != 0) { x = (T)((float)x / len); y = (T)((float)y / len); } return *this; } static Vector2 Max(const Vector2& v1, const Vector2& v2) { return Vector2(std::max(v1.x, v2.x), std::max(v1.y, v2.y)); } static Vector2 Min(const Vector2& v1, const Vector2& v2) { return Vector2(std::min(v1.x, v2.x), std::min(v1.y, v2.y)); } bool operator==(const Vector2& v) const { return x == v.x && y == v.y; } bool operator!=(const Vector2& v) const { return !(*this == v); } Vector2& operator+=(const Vector2& v) { x += v.x; y += v.y; return *this; } Vector2& operator-=(const Vector2& v) { x -= v.x; y -= v.y; return *this; } Vector2 operator+(const Vector2& v) const { return Vector2(x + v.x, y + v.y); } Vector2 operator-(const Vector2& v) const { return Vector2(x - v.x, y - v.y); } Vector2& operator+=(const T& v) { x += v; y += v; return *this; } Vector2& operator-=(const T& v) { x -= v; y -= v; return *this; } Vector2& operator*=(const T& v) { x *= v; y *= v; return *this; } Vector2& operator/=(const T& v) { assert(v != 0); x /= v; y /= v; return *this; } Vector2 operator+(const T& v) { return Vector2(x + v, y + v); } Vector2 operator-(const T& v) { return Vector2(x - v, y - v); } Vector2 operator*(const T& v) { return Vector2(x * v, y * v); } Vector2 operator/(const T& v) { assert(v != 0); return Vector2(x / v, y / v); } }; using Vector2f = Vector2; using Vector2i = Vector2; ================================================ FILE: src/util/math/vector3.h ================================================ #pragma once #include template class Vector3 { public: T x; T y; T z; Vector3() : x{}, y{}, z{} {} Vector3(T x, T y, T z) : x(x), y(y), z(z) {} template Vector3(const Vector3& v) : x((T)v.x), y((T)v.y), z((T)v.z) {} float Length() const { return std::sqrt((x * x) + (y * y) + (z * z)); } float Dot(const Vector3& v) const { return x * v.x + y * v.y + z * v.z; } Vector3 Cross(const Vector3& v) const { return Vector3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x); } Vector3 Normalized() const { const auto len = Length(); if (len == 0) return {}; return *this / len; } Vector3& Normalize() { const auto len = Length(); if (len != 0) *this /= len; return *this; } //Vector3& Scale(const Vector3& v) //{ // *this *= v; // return *this; //} void Scale(const float s) { this->x *= s; this->y *= s; this->z *= s; } Vector3& RotateX(float theta); Vector3& RotateY(float theta); Vector3& RotateZ(float theta); bool operator==(const Vector3& v) { return x == v.x && y == v.y && z == v.z; } bool operator!=(const Vector3& v) { return !(*this == v); } template Vector3& operator+=(const Vector3& v) { x += (T)v.x; y += (T)v.y; z += (T)v.z; return *this; } template Vector3& operator-=(const Vector3& v) { x -= (T)v.x; y -= (T)v.y; z -= (T)v.z; return *this; } template Vector3& operator*=(const Vector3& v) { x *= (T)v.x; y *= (T)v.y; z *= (T)v.z; return *this; } template Vector3& operator/=(const Vector3& v) { assert(v.x != 0 && v.y != 0 && v.z != 0); x /= (T)v.x; y /= (T)v.y; z /= (T)v.z; return *this; } template Vector3 operator+(const Vector3& v) const { return Vector3(x + (T)v.x, y + (T)v.y, z + (T)v.z); } template Vector3 operator-(const Vector3& v) const { return Vector3(x - (T)v.x, y - (T)v.y, z - (T)v.z); } template Vector3 operator*(const Vector3& v) const { return Vector3(x * (T)v.x, y * (T)v.y, z * (T)v.z); } template Vector3 operator/(const Vector3& v) const { assert(v.x != 0 && v.y != 0 && v.z != 0); return Vector3(x / (T)v.x, y / (T)v.y, z / (T)v.z); } Vector3& operator+=(T v) { x += v; y += v; z += v; return *this; } Vector3& operator-=(T v) { x -= v; y -= v; z -= v; return *this; } Vector3& operator*=(T v) { x *= v; y *= v; z *= v; return *this; } Vector3& operator/=(T v) { assert(v != 0); x /= v; y /= v; z /= v; return *this; } Vector3 operator+(T v) const { return Vector3(x + v, y + v, z + v); } Vector3 operator-(T v) const { return Vector3(x - v, y - v, z - v); } Vector3 operator*(T v) const { return Vector3(x * v, y * v, z * v); } Vector3 operator/(T v) const { assert(v != 0); return Vector3(x / (T)v, y / (T)v, z / (T)v); } bool IsZero() const { return x == 0 && y == 0 && z == 0; } bool HasZero() const { return x == 0 || y == 0 || z == 0; } }; template Vector3& Vector3::RotateX(float theta) { const float sin = std::sin(theta); const float cos = std::cos(theta); y = y * cos - z * sin; z = y * sin + z * cos; return *this; } template Vector3& Vector3::RotateY(float theta) { const float sin = std::sin(theta); const float cos = std::cos(theta); x = x * cos + z * sin; z = -x * sin + z * cos; return *this; } template Vector3& Vector3::RotateZ(float theta) { const float sin = std::sin(theta); const float cos = std::cos(theta); x = x * cos - y * sin; y = x * sin + y * cos; return *this; } using Vector3f = Vector3; using Vector3i = Vector3; ================================================ FILE: src/util/tinyxml2/tinyxml2.cpp ================================================ /* Original code by Lee Thomason (www.grinninglizard.com) 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 acknowledgment 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. */ #include "tinyxml2.h" #include // yes, this one new style header, is in the Android SDK. #if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) # include # include #else # include # include #endif #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) // Microsoft Visual Studio, version 2005 and higher. Not WinCE. /*int _snprintf_s( char *buffer, size_t sizeOfBuffer, size_t count, const char *format [, argument] ... );*/ static inline int TIXML_SNPRINTF(char* buffer, size_t size, const char* format, ...) { va_list va; va_start(va, format); int result = vsnprintf_s(buffer, size, _TRUNCATE, format, va); va_end(va); return result; } static inline int TIXML_VSNPRINTF(char* buffer, size_t size, const char* format, va_list va) { int result = vsnprintf_s(buffer, size, _TRUNCATE, format, va); return result; } #define TIXML_VSCPRINTF _vscprintf #define TIXML_SSCANF sscanf_s #elif defined _MSC_VER // Microsoft Visual Studio 2003 and earlier or WinCE #define TIXML_SNPRINTF _snprintf #define TIXML_VSNPRINTF _vsnprintf #define TIXML_SSCANF sscanf #if (_MSC_VER < 1400 ) && (!defined WINCE) // Microsoft Visual Studio 2003 and not WinCE. #define TIXML_VSCPRINTF _vscprintf // VS2003's C runtime has this, but VC6 C runtime or WinCE SDK doesn't have. #else // Microsoft Visual Studio 2003 and earlier or WinCE. static inline int TIXML_VSCPRINTF(const char* format, va_list va) { int len = 512; for (;;) { len = len * 2; char* str = new char[len](); const int required = _vsnprintf(str, len, format, va); delete[] str; if (required != -1) { TIXMLASSERT(required >= 0); len = required; break; } } TIXMLASSERT(len >= 0); return len; } #endif #else // GCC version 3 and higher //#warning( "Using sn* functions." ) #define TIXML_SNPRINTF snprintf #define TIXML_VSNPRINTF vsnprintf static inline int TIXML_VSCPRINTF(const char* format, va_list va) { int len = vsnprintf(0, 0, format, va); TIXMLASSERT(len >= 0); return len; } #define TIXML_SSCANF sscanf #endif static const char LINE_FEED = (char)0x0a; // all line endings are normalized to LF static const char LF = LINE_FEED; static const char CARRIAGE_RETURN = (char)0x0d; // CR gets filtered out static const char CR = CARRIAGE_RETURN; static const char SINGLE_QUOTE = '\''; static const char DOUBLE_QUOTE = '\"'; // Bunch of unicode info at: // http://www.unicode.org/faq/utf_bom.html // ef bb bf (Microsoft "lead bytes") - designates UTF-8 static const unsigned char TIXML_UTF_LEAD_0 = 0xefU; static const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; static const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; namespace tinyxml2 { struct Entity { const char* pattern; int length; char value; }; static const int NUM_ENTITIES = 5; static const Entity entities[NUM_ENTITIES] = { { "quot", 4, DOUBLE_QUOTE }, { "amp", 3, '&' }, { "apos", 4, SINGLE_QUOTE }, { "lt", 2, '<' }, { "gt", 2, '>' } }; StrPair::~StrPair() { Reset(); } void StrPair::TransferTo(StrPair* other) { if (this == other) { return; } // This in effect implements the assignment operator by "moving" // ownership (as in auto_ptr). TIXMLASSERT(other != 0); TIXMLASSERT(other->_flags == 0); TIXMLASSERT(other->_start == 0); TIXMLASSERT(other->_end == 0); other->Reset(); other->_flags = _flags; other->_start = _start; other->_end = _end; _flags = 0; _start = 0; _end = 0; } void StrPair::Reset() { if (_flags & NEEDS_DELETE) { delete[] _start; } _flags = 0; _start = 0; _end = 0; } void StrPair::SetStr(const char* str, int flags) { TIXMLASSERT(str); Reset(); size_t len = strlen(str); TIXMLASSERT(_start == 0); _start = new char[len + 1]; memcpy(_start, str, len + 1); _end = _start + len; _flags = flags | NEEDS_DELETE; } char* StrPair::ParseText(char* p, const char* endTag, int strFlags, int* curLineNumPtr) { TIXMLASSERT(p); TIXMLASSERT(endTag && *endTag); TIXMLASSERT(curLineNumPtr); char* start = p; char endChar = *endTag; size_t length = strlen(endTag); // Inner loop of text parsing. while (*p) { if (*p == endChar && strncmp(p, endTag, length) == 0) { Set(start, p, strFlags); return p + length; } else if (*p == '\n') { ++(*curLineNumPtr); } ++p; TIXMLASSERT(p); } return 0; } char* StrPair::ParseName(char* p) { if (!p || !(*p)) { return 0; } if (!XMLUtil::IsNameStartChar(*p)) { return 0; } char* const start = p; ++p; while (*p && XMLUtil::IsNameChar(*p)) { ++p; } Set(start, p, 0); return p; } void StrPair::CollapseWhitespace() { // Adjusting _start would cause undefined behavior on delete[] TIXMLASSERT((_flags & NEEDS_DELETE) == 0); // Trim leading space. _start = XMLUtil::SkipWhiteSpace(_start, 0); if (*_start) { const char* p = _start; // the read pointer char* q = _start; // the write pointer while (*p) { if (XMLUtil::IsWhiteSpace(*p)) { p = XMLUtil::SkipWhiteSpace(p, 0); if (*p == 0) { break; // don't write to q; this trims the trailing space. } *q = ' '; ++q; } *q = *p; ++q; ++p; } *q = 0; } } const char* StrPair::GetStr() { TIXMLASSERT(_start); TIXMLASSERT(_end); if (_flags & NEEDS_FLUSH) { *_end = 0; _flags ^= NEEDS_FLUSH; if (_flags) { const char* p = _start; // the read pointer char* q = _start; // the write pointer while (p < _end) { if ((_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == CR) { // CR-LF pair becomes LF // CR alone becomes LF // LF-CR becomes LF if (*(p + 1) == LF) { p += 2; } else { ++p; } *q = LF; ++q; } else if ((_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == LF) { if (*(p + 1) == CR) { p += 2; } else { ++p; } *q = LF; ++q; } else if ((_flags & NEEDS_ENTITY_PROCESSING) && *p == '&') { // Entities handled by tinyXML2: // - special entities in the entity table [in/out] // - numeric character reference [in] // 中 or 中 if (*(p + 1) == '#') { const int buflen = 10; char buf[buflen] = { 0 }; int len = 0; char* adjusted = const_cast(XMLUtil::GetCharacterRef(p, buf, &len)); if (adjusted == 0) { *q = *p; ++p; ++q; } else { TIXMLASSERT(0 <= len && len <= buflen); TIXMLASSERT(q + len <= adjusted); p = adjusted; memcpy(q, buf, len); q += len; } } else { bool entityFound = false; for (int i = 0; i < NUM_ENTITIES; ++i) { const Entity& entity = entities[i]; if (strncmp(p + 1, entity.pattern, entity.length) == 0 && *(p + entity.length + 1) == ';') { // Found an entity - convert. *q = entity.value; ++q; p += entity.length + 2; entityFound = true; break; } } if (!entityFound) { // fixme: treat as error? ++p; ++q; } } } else { *q = *p; ++p; ++q; } } *q = 0; } // The loop below has plenty going on, and this // is a less useful mode. Break it out. if (_flags & NEEDS_WHITESPACE_COLLAPSING) { CollapseWhitespace(); } _flags = (_flags & NEEDS_DELETE); } TIXMLASSERT(_start); return _start; } // --------- XMLUtil ----------- // const char* XMLUtil::writeBoolTrue = "true"; const char* XMLUtil::writeBoolFalse = "false"; void XMLUtil::SetBoolSerialization(const char* writeTrue, const char* writeFalse) { static const char* defTrue = "true"; static const char* defFalse = "false"; writeBoolTrue = (writeTrue) ? writeTrue : defTrue; writeBoolFalse = (writeFalse) ? writeFalse : defFalse; } const char* XMLUtil::ReadBOM(const char* p, bool* bom) { TIXMLASSERT(p); TIXMLASSERT(bom); *bom = false; const unsigned char* pu = reinterpret_cast(p); // Check for BOM: if (*(pu + 0) == TIXML_UTF_LEAD_0 && *(pu + 1) == TIXML_UTF_LEAD_1 && *(pu + 2) == TIXML_UTF_LEAD_2) { *bom = true; p += 3; } TIXMLASSERT(p); return p; } void XMLUtil::ConvertUTF32ToUTF8(unsigned long input, char* output, int* length) { const unsigned long BYTE_MASK = 0xBF; const unsigned long BYTE_MARK = 0x80; const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; if (input < 0x80) { *length = 1; } else if (input < 0x800) { *length = 2; } else if (input < 0x10000) { *length = 3; } else if (input < 0x200000) { *length = 4; } else { *length = 0; // This code won't convert this correctly anyway. return; } output += *length; // Scary scary fall throughs are annotated with carefully designed comments // to suppress compiler warnings such as -Wimplicit-fallthrough in gcc switch (*length) { case 4: --output; *output = (char)((input | BYTE_MARK) & BYTE_MASK); input >>= 6; //fall through case 3: --output; *output = (char)((input | BYTE_MARK) & BYTE_MASK); input >>= 6; //fall through case 2: --output; *output = (char)((input | BYTE_MARK) & BYTE_MASK); input >>= 6; //fall through case 1: --output; *output = (char)(input | FIRST_BYTE_MARK[*length]); break; default: TIXMLASSERT(false); } } const char* XMLUtil::GetCharacterRef(const char* p, char* value, int* length) { // Presume an entity, and pull it out. *length = 0; if (*(p + 1) == '#' && *(p + 2)) { unsigned long ucs = 0; TIXMLASSERT(sizeof(ucs) >= 4); ptrdiff_t delta = 0; unsigned mult = 1; static const char SEMICOLON = ';'; if (*(p + 2) == 'x') { // Hexadecimal. const char* q = p + 3; if (!(*q)) { return 0; } q = strchr(q, SEMICOLON); if (!q) { return 0; } TIXMLASSERT(*q == SEMICOLON); delta = q - p; --q; while (*q != 'x') { unsigned int digit = 0; if (*q >= '0' && *q <= '9') { digit = *q - '0'; } else if (*q >= 'a' && *q <= 'f') { digit = *q - 'a' + 10; } else if (*q >= 'A' && *q <= 'F') { digit = *q - 'A' + 10; } else { return 0; } TIXMLASSERT(digit < 16); TIXMLASSERT(digit == 0 || mult <= UINT_MAX / digit); const unsigned int digitScaled = mult * digit; TIXMLASSERT(ucs <= ULONG_MAX - digitScaled); ucs += digitScaled; TIXMLASSERT(mult <= UINT_MAX / 16); mult *= 16; --q; } } else { // Decimal. const char* q = p + 2; if (!(*q)) { return 0; } q = strchr(q, SEMICOLON); if (!q) { return 0; } TIXMLASSERT(*q == SEMICOLON); delta = q - p; --q; while (*q != '#') { if (*q >= '0' && *q <= '9') { const unsigned int digit = *q - '0'; TIXMLASSERT(digit < 10); TIXMLASSERT(digit == 0 || mult <= UINT_MAX / digit); const unsigned int digitScaled = mult * digit; TIXMLASSERT(ucs <= ULONG_MAX - digitScaled); ucs += digitScaled; } else { return 0; } TIXMLASSERT(mult <= UINT_MAX / 10); mult *= 10; --q; } } // convert the UCS to UTF-8 ConvertUTF32ToUTF8(ucs, value, length); return p + delta + 1; } return p + 1; } void XMLUtil::ToStr(int v, char* buffer, int bufferSize) { TIXML_SNPRINTF(buffer, bufferSize, "%d", v); } void XMLUtil::ToStr(unsigned v, char* buffer, int bufferSize) { TIXML_SNPRINTF(buffer, bufferSize, "%u", v); } void XMLUtil::ToStr(bool v, char* buffer, int bufferSize) { TIXML_SNPRINTF(buffer, bufferSize, "%s", v ? writeBoolTrue : writeBoolFalse); } /* ToStr() of a number is a very tricky topic. https://github.com/leethomason/tinyxml2/issues/106 */ void XMLUtil::ToStr(float v, char* buffer, int bufferSize) { TIXML_SNPRINTF(buffer, bufferSize, "%.8g", v); } void XMLUtil::ToStr(double v, char* buffer, int bufferSize) { TIXML_SNPRINTF(buffer, bufferSize, "%.17g", v); } void XMLUtil::ToStr(int64_t v, char* buffer, int bufferSize) { // horrible syntax trick to make the compiler happy about %lld TIXML_SNPRINTF(buffer, bufferSize, "%lld", (long long)v); } bool XMLUtil::ToInt(const char* str, int* value) { if (TIXML_SSCANF(str, "%d", value) == 1) { return true; } return false; } bool XMLUtil::ToUnsigned(const char* str, unsigned *value) { if (TIXML_SSCANF(str, "%u", value) == 1) { return true; } return false; } bool XMLUtil::ToBool(const char* str, bool* value) { int ival = 0; if (ToInt(str, &ival)) { *value = (ival == 0) ? false : true; return true; } if (StringEqual(str, "true")) { *value = true; return true; } else if (StringEqual(str, "false")) { *value = false; return true; } return false; } bool XMLUtil::ToFloat(const char* str, float* value) { if (TIXML_SSCANF(str, "%f", value) == 1) { return true; } return false; } bool XMLUtil::ToDouble(const char* str, double* value) { if (TIXML_SSCANF(str, "%lf", value) == 1) { return true; } return false; } bool XMLUtil::ToInt64(const char* str, int64_t* value) { long long v = 0; // horrible syntax trick to make the compiler happy about %lld if (TIXML_SSCANF(str, "%lld", &v) == 1) { *value = (int64_t)v; return true; } return false; } char* XMLDocument::Identify(char* p, XMLNode** node) { TIXMLASSERT(node); TIXMLASSERT(p); char* const start = p; int const startLine = _parseCurLineNum; p = XMLUtil::SkipWhiteSpace(p, &_parseCurLineNum); if (!*p) { *node = 0; TIXMLASSERT(p); return p; } // These strings define the matching patterns: static const char* xmlHeader = { "(_commentPool); returnNode->_parseLineNum = _parseCurLineNum; p += xmlHeaderLen; } else if (XMLUtil::StringEqual(p, commentHeader, commentHeaderLen)) { returnNode = CreateUnlinkedNode(_commentPool); returnNode->_parseLineNum = _parseCurLineNum; p += commentHeaderLen; } else if (XMLUtil::StringEqual(p, cdataHeader, cdataHeaderLen)) { XMLText* text = CreateUnlinkedNode(_textPool); returnNode = text; returnNode->_parseLineNum = _parseCurLineNum; p += cdataHeaderLen; text->SetCData(true); } else if (XMLUtil::StringEqual(p, dtdHeader, dtdHeaderLen)) { returnNode = CreateUnlinkedNode(_commentPool); returnNode->_parseLineNum = _parseCurLineNum; p += dtdHeaderLen; } else if (XMLUtil::StringEqual(p, elementHeader, elementHeaderLen)) { returnNode = CreateUnlinkedNode(_elementPool); returnNode->_parseLineNum = _parseCurLineNum; p += elementHeaderLen; } else { returnNode = CreateUnlinkedNode(_textPool); returnNode->_parseLineNum = _parseCurLineNum; // Report line of first non-whitespace character p = start; // Back it up, all the text counts. _parseCurLineNum = startLine; } TIXMLASSERT(returnNode); TIXMLASSERT(p); *node = returnNode; return p; } bool XMLDocument::Accept(XMLVisitor* visitor) const { TIXMLASSERT(visitor); if (visitor->VisitEnter(*this)) { for (const XMLNode* node = FirstChild(); node; node = node->NextSibling()) { if (!node->Accept(visitor)) { break; } } } return visitor->VisitExit(*this); } // --------- XMLNode ----------- // XMLNode::XMLNode(XMLDocument* doc) : _document(doc), _parent(0), _value(), _parseLineNum(0), _firstChild(0), _lastChild(0), _prev(0), _next(0), _userData(0), _memPool(0) { } XMLNode::~XMLNode() { DeleteChildren(); if (_parent) { _parent->Unlink(this); } } const char* XMLNode::Value() const { // Edge case: XMLDocuments don't have a Value. Return null. if (this->ToDocument()) return 0; return _value.GetStr(); } void XMLNode::SetValue(const char* str, bool staticMem) { if (staticMem) { _value.SetInternedStr(str); } else { _value.SetStr(str); } } XMLNode* XMLNode::DeepClone(XMLDocument* target) const { XMLNode* clone = this->ShallowClone(target); if (!clone) return 0; for (const XMLNode* child = this->FirstChild(); child; child = child->NextSibling()) { XMLNode* childClone = child->DeepClone(target); TIXMLASSERT(childClone); clone->InsertEndChild(childClone); } return clone; } void XMLNode::DeleteChildren() { while (_firstChild) { TIXMLASSERT(_lastChild); DeleteChild(_firstChild); } _firstChild = _lastChild = 0; } void XMLNode::Unlink(XMLNode* child) { TIXMLASSERT(child); TIXMLASSERT(child->_document == _document); TIXMLASSERT(child->_parent == this); if (child == _firstChild) { _firstChild = _firstChild->_next; } if (child == _lastChild) { _lastChild = _lastChild->_prev; } if (child->_prev) { child->_prev->_next = child->_next; } if (child->_next) { child->_next->_prev = child->_prev; } child->_next = 0; child->_prev = 0; child->_parent = 0; } void XMLNode::DeleteChild(XMLNode* node) { TIXMLASSERT(node); TIXMLASSERT(node->_document == _document); TIXMLASSERT(node->_parent == this); Unlink(node); TIXMLASSERT(node->_prev == 0); TIXMLASSERT(node->_next == 0); TIXMLASSERT(node->_parent == 0); DeleteNode(node); } XMLNode* XMLNode::InsertEndChild(XMLNode* addThis) { TIXMLASSERT(addThis); if (addThis->_document != _document) { TIXMLASSERT(false); return 0; } InsertChildPreamble(addThis); if (_lastChild) { TIXMLASSERT(_firstChild); TIXMLASSERT(_lastChild->_next == 0); _lastChild->_next = addThis; addThis->_prev = _lastChild; _lastChild = addThis; addThis->_next = 0; } else { TIXMLASSERT(_firstChild == 0); _firstChild = _lastChild = addThis; addThis->_prev = 0; addThis->_next = 0; } addThis->_parent = this; return addThis; } XMLNode* XMLNode::InsertFirstChild(XMLNode* addThis) { TIXMLASSERT(addThis); if (addThis->_document != _document) { TIXMLASSERT(false); return 0; } InsertChildPreamble(addThis); if (_firstChild) { TIXMLASSERT(_lastChild); TIXMLASSERT(_firstChild->_prev == 0); _firstChild->_prev = addThis; addThis->_next = _firstChild; _firstChild = addThis; addThis->_prev = 0; } else { TIXMLASSERT(_lastChild == 0); _firstChild = _lastChild = addThis; addThis->_prev = 0; addThis->_next = 0; } addThis->_parent = this; return addThis; } XMLNode* XMLNode::InsertAfterChild(XMLNode* afterThis, XMLNode* addThis) { TIXMLASSERT(addThis); if (addThis->_document != _document) { TIXMLASSERT(false); return 0; } TIXMLASSERT(afterThis); if (afterThis->_parent != this) { TIXMLASSERT(false); return 0; } if (afterThis == addThis) { // Current state: BeforeThis -> AddThis -> OneAfterAddThis // Now AddThis must disappear from it's location and then // reappear between BeforeThis and OneAfterAddThis. // So just leave it where it is. return addThis; } if (afterThis->_next == 0) { // The last node or the only node. return InsertEndChild(addThis); } InsertChildPreamble(addThis); addThis->_prev = afterThis; addThis->_next = afterThis->_next; afterThis->_next->_prev = addThis; afterThis->_next = addThis; addThis->_parent = this; return addThis; } const XMLElement* XMLNode::FirstChildElement(const char* name) const { for (const XMLNode* node = _firstChild; node; node = node->_next) { const XMLElement* element = node->ToElementWithName(name); if (element) { return element; } } return 0; } const XMLElement* XMLNode::LastChildElement(const char* name) const { for (const XMLNode* node = _lastChild; node; node = node->_prev) { const XMLElement* element = node->ToElementWithName(name); if (element) { return element; } } return 0; } const XMLElement* XMLNode::NextSiblingElement(const char* name) const { for (const XMLNode* node = _next; node; node = node->_next) { const XMLElement* element = node->ToElementWithName(name); if (element) { return element; } } return 0; } const XMLElement* XMLNode::PreviousSiblingElement(const char* name) const { for (const XMLNode* node = _prev; node; node = node->_prev) { const XMLElement* element = node->ToElementWithName(name); if (element) { return element; } } return 0; } char* XMLNode::ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr) { // This is a recursive method, but thinking about it "at the current level" // it is a pretty simple flat list: // // // // With a special case: // // // // // Where the closing element (/foo) *must* be the next thing after the opening // element, and the names must match. BUT the tricky bit is that the closing // element will be read by the child. // // 'endTag' is the end tag for this node, it is returned by a call to a child. // 'parentEnd' is the end tag for the parent, which is filled in and returned. while (p && *p) { XMLNode* node = 0; p = _document->Identify(p, &node); TIXMLASSERT(p); if (node == 0) { break; } int initialLineNum = node->_parseLineNum; StrPair endTag; p = node->ParseDeep(p, &endTag, curLineNumPtr); if (!p) { DeleteNode(node); if (!_document->Error()) { _document->SetError(XML_ERROR_PARSING, 0, 0, initialLineNum); } break; } XMLDeclaration* decl = node->ToDeclaration(); if (decl) { // Declarations are only allowed at document level bool wellLocated = (ToDocument() != 0); if (wellLocated) { // Multiple declarations are allowed but all declarations // must occur before anything else for (const XMLNode* existingNode = _document->FirstChild(); existingNode; existingNode = existingNode->NextSibling()) { if (!existingNode->ToDeclaration()) { wellLocated = false; break; } } } if (!wellLocated) { _document->SetError(XML_ERROR_PARSING_DECLARATION, decl->Value(), 0, initialLineNum); DeleteNode(node); break; } } XMLElement* ele = node->ToElement(); if (ele) { // We read the end tag. Return it to the parent. if (ele->ClosingType() == XMLElement::CLOSING) { if (parentEndTag) { ele->_value.TransferTo(parentEndTag); } node->_memPool->SetTracked(); // created and then immediately deleted. DeleteNode(node); return p; } // Handle an end tag returned to this level. // And handle a bunch of annoying errors. bool mismatch = false; if (endTag.Empty()) { if (ele->ClosingType() == XMLElement::OPEN) { mismatch = true; } } else { if (ele->ClosingType() != XMLElement::OPEN) { mismatch = true; } else if (!XMLUtil::StringEqual(endTag.GetStr(), ele->Name())) { mismatch = true; } } if (mismatch) { _document->SetError(XML_ERROR_MISMATCHED_ELEMENT, ele->Name(), 0, initialLineNum); DeleteNode(node); break; } } InsertEndChild(node); } return 0; } /*static*/ void XMLNode::DeleteNode(XMLNode* node) { if (node == 0) { return; } TIXMLASSERT(node->_document); if (!node->ToDocument()) { node->_document->MarkInUse(node); } MemPool* pool = node->_memPool; node->~XMLNode(); pool->Free(node); } void XMLNode::InsertChildPreamble(XMLNode* insertThis) const { TIXMLASSERT(insertThis); TIXMLASSERT(insertThis->_document == _document); if (insertThis->_parent) { insertThis->_parent->Unlink(insertThis); } else { insertThis->_document->MarkInUse(insertThis); insertThis->_memPool->SetTracked(); } } const XMLElement* XMLNode::ToElementWithName(const char* name) const { const XMLElement* element = this->ToElement(); if (element == 0) { return 0; } if (name == 0) { return element; } if (XMLUtil::StringEqual(element->Name(), name)) { return element; } return 0; } // --------- XMLText ---------- // char* XMLText::ParseDeep(char* p, StrPair*, int* curLineNumPtr) { const char* start = p; if (this->CData()) { p = _value.ParseText(p, "]]>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr); if (!p) { _document->SetError(XML_ERROR_PARSING_CDATA, start, 0, _parseLineNum); } return p; } else { int flags = _document->ProcessEntities() ? StrPair::TEXT_ELEMENT : StrPair::TEXT_ELEMENT_LEAVE_ENTITIES; if (_document->WhitespaceMode() == COLLAPSE_WHITESPACE) { flags |= StrPair::NEEDS_WHITESPACE_COLLAPSING; } p = _value.ParseText(p, "<", flags, curLineNumPtr); if (p && *p) { return p - 1; } if (!p) { _document->SetError(XML_ERROR_PARSING_TEXT, start, 0, _parseLineNum); } } return 0; } XMLNode* XMLText::ShallowClone(XMLDocument* doc) const { if (!doc) { doc = _document; } XMLText* text = doc->NewText(Value()); // fixme: this will always allocate memory. Intern? text->SetCData(this->CData()); return text; } bool XMLText::ShallowEqual(const XMLNode* compare) const { TIXMLASSERT(compare); const XMLText* text = compare->ToText(); return (text && XMLUtil::StringEqual(text->Value(), Value())); } bool XMLText::Accept(XMLVisitor* visitor) const { TIXMLASSERT(visitor); return visitor->Visit(*this); } // --------- XMLComment ---------- // XMLComment::XMLComment(XMLDocument* doc) : XMLNode(doc) { } XMLComment::~XMLComment() { } char* XMLComment::ParseDeep(char* p, StrPair*, int* curLineNumPtr) { // Comment parses as text. const char* start = p; p = _value.ParseText(p, "-->", StrPair::COMMENT, curLineNumPtr); if (p == 0) { _document->SetError(XML_ERROR_PARSING_COMMENT, start, 0, _parseLineNum); } return p; } XMLNode* XMLComment::ShallowClone(XMLDocument* doc) const { if (!doc) { doc = _document; } XMLComment* comment = doc->NewComment(Value()); // fixme: this will always allocate memory. Intern? return comment; } bool XMLComment::ShallowEqual(const XMLNode* compare) const { TIXMLASSERT(compare); const XMLComment* comment = compare->ToComment(); return (comment && XMLUtil::StringEqual(comment->Value(), Value())); } bool XMLComment::Accept(XMLVisitor* visitor) const { TIXMLASSERT(visitor); return visitor->Visit(*this); } // --------- XMLDeclaration ---------- // XMLDeclaration::XMLDeclaration(XMLDocument* doc) : XMLNode(doc) { } XMLDeclaration::~XMLDeclaration() { //printf( "~XMLDeclaration\n" ); } char* XMLDeclaration::ParseDeep(char* p, StrPair*, int* curLineNumPtr) { // Declaration parses as text. const char* start = p; p = _value.ParseText(p, "?>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr); if (p == 0) { _document->SetError(XML_ERROR_PARSING_DECLARATION, start, 0, _parseLineNum); } return p; } XMLNode* XMLDeclaration::ShallowClone(XMLDocument* doc) const { if (!doc) { doc = _document; } XMLDeclaration* dec = doc->NewDeclaration(Value()); // fixme: this will always allocate memory. Intern? return dec; } bool XMLDeclaration::ShallowEqual(const XMLNode* compare) const { TIXMLASSERT(compare); const XMLDeclaration* declaration = compare->ToDeclaration(); return (declaration && XMLUtil::StringEqual(declaration->Value(), Value())); } bool XMLDeclaration::Accept(XMLVisitor* visitor) const { TIXMLASSERT(visitor); return visitor->Visit(*this); } // --------- XMLUnknown ---------- // XMLUnknown::XMLUnknown(XMLDocument* doc) : XMLNode(doc) { } XMLUnknown::~XMLUnknown() { } char* XMLUnknown::ParseDeep(char* p, StrPair*, int* curLineNumPtr) { // Unknown parses as text. const char* start = p; p = _value.ParseText(p, ">", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr); if (!p) { _document->SetError(XML_ERROR_PARSING_UNKNOWN, start, 0, _parseLineNum); } return p; } XMLNode* XMLUnknown::ShallowClone(XMLDocument* doc) const { if (!doc) { doc = _document; } XMLUnknown* text = doc->NewUnknown(Value()); // fixme: this will always allocate memory. Intern? return text; } bool XMLUnknown::ShallowEqual(const XMLNode* compare) const { TIXMLASSERT(compare); const XMLUnknown* unknown = compare->ToUnknown(); return (unknown && XMLUtil::StringEqual(unknown->Value(), Value())); } bool XMLUnknown::Accept(XMLVisitor* visitor) const { TIXMLASSERT(visitor); return visitor->Visit(*this); } // --------- XMLAttribute ---------- // const char* XMLAttribute::Name() const { return _name.GetStr(); } const char* XMLAttribute::Value() const { return _value.GetStr(); } char* XMLAttribute::ParseDeep(char* p, bool processEntities, int* curLineNumPtr) { // Parse using the name rules: bug fix, was using ParseText before p = _name.ParseName(p); if (!p || !*p) { return 0; } // Skip white space before = p = XMLUtil::SkipWhiteSpace(p, curLineNumPtr); if (*p != '=') { return 0; } ++p; // move up to opening quote p = XMLUtil::SkipWhiteSpace(p, curLineNumPtr); if (*p != '\"' && *p != '\'') { return 0; } char endTag[2] = { *p, 0 }; ++p; // move past opening quote p = _value.ParseText(p, endTag, processEntities ? StrPair::ATTRIBUTE_VALUE : StrPair::ATTRIBUTE_VALUE_LEAVE_ENTITIES, curLineNumPtr); return p; } void XMLAttribute::SetName(const char* n) { _name.SetStr(n); } XMLError XMLAttribute::QueryIntValue(int* value) const { if (XMLUtil::ToInt(Value(), value)) { return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } XMLError XMLAttribute::QueryUnsignedValue(unsigned int* value) const { if (XMLUtil::ToUnsigned(Value(), value)) { return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } XMLError XMLAttribute::QueryInt64Value(int64_t* value) const { if (XMLUtil::ToInt64(Value(), value)) { return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } XMLError XMLAttribute::QueryBoolValue(bool* value) const { if (XMLUtil::ToBool(Value(), value)) { return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } XMLError XMLAttribute::QueryFloatValue(float* value) const { if (XMLUtil::ToFloat(Value(), value)) { return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } XMLError XMLAttribute::QueryDoubleValue(double* value) const { if (XMLUtil::ToDouble(Value(), value)) { return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } void XMLAttribute::SetAttribute(const char* v) { _value.SetStr(v); } void XMLAttribute::SetAttribute(int v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); _value.SetStr(buf); } void XMLAttribute::SetAttribute(unsigned v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); _value.SetStr(buf); } void XMLAttribute::SetAttribute(int64_t v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); _value.SetStr(buf); } void XMLAttribute::SetAttribute(bool v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); _value.SetStr(buf); } void XMLAttribute::SetAttribute(double v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); _value.SetStr(buf); } void XMLAttribute::SetAttribute(float v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); _value.SetStr(buf); } // --------- XMLElement ---------- // XMLElement::XMLElement(XMLDocument* doc) : XMLNode(doc), _closingType(OPEN), _rootAttribute(0) { } XMLElement::~XMLElement() { while (_rootAttribute) { XMLAttribute* next = _rootAttribute->_next; DeleteAttribute(_rootAttribute); _rootAttribute = next; } } const XMLAttribute* XMLElement::FindAttribute(const char* name) const { for (XMLAttribute* a = _rootAttribute; a; a = a->_next) { if (XMLUtil::StringEqual(a->Name(), name)) { return a; } } return 0; } const char* XMLElement::Attribute(const char* name, const char* value) const { const XMLAttribute* a = FindAttribute(name); if (!a) { return 0; } if (!value || XMLUtil::StringEqual(a->Value(), value)) { return a->Value(); } return 0; } int XMLElement::IntAttribute(const char* name, int defaultValue) const { int i = defaultValue; QueryIntAttribute(name, &i); return i; } unsigned XMLElement::UnsignedAttribute(const char* name, unsigned defaultValue) const { unsigned i = defaultValue; QueryUnsignedAttribute(name, &i); return i; } int64_t XMLElement::Int64Attribute(const char* name, int64_t defaultValue) const { int64_t i = defaultValue; QueryInt64Attribute(name, &i); return i; } bool XMLElement::BoolAttribute(const char* name, bool defaultValue) const { bool b = defaultValue; QueryBoolAttribute(name, &b); return b; } double XMLElement::DoubleAttribute(const char* name, double defaultValue) const { double d = defaultValue; QueryDoubleAttribute(name, &d); return d; } float XMLElement::FloatAttribute(const char* name, float defaultValue) const { float f = defaultValue; QueryFloatAttribute(name, &f); return f; } const char* XMLElement::GetText() const { if (FirstChild() && FirstChild()->ToText()) { return FirstChild()->Value(); } return 0; } void XMLElement::SetText(const char* inText) { if (FirstChild() && FirstChild()->ToText()) FirstChild()->SetValue(inText); else { XMLText* theText = GetDocument()->NewText(inText); InsertFirstChild(theText); } } void XMLElement::SetText(int v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); SetText(buf); } void XMLElement::SetText(unsigned v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); SetText(buf); } void XMLElement::SetText(int64_t v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); SetText(buf); } void XMLElement::SetText(bool v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); SetText(buf); } void XMLElement::SetText(float v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); SetText(buf); } void XMLElement::SetText(double v) { char buf[BUF_SIZE]; XMLUtil::ToStr(v, buf, BUF_SIZE); SetText(buf); } XMLError XMLElement::QueryIntText(int* ival) const { if (FirstChild() && FirstChild()->ToText()) { const char* t = FirstChild()->Value(); if (XMLUtil::ToInt(t, ival)) { return XML_SUCCESS; } return XML_CAN_NOT_CONVERT_TEXT; } return XML_NO_TEXT_NODE; } XMLError XMLElement::QueryUnsignedText(unsigned* uval) const { if (FirstChild() && FirstChild()->ToText()) { const char* t = FirstChild()->Value(); if (XMLUtil::ToUnsigned(t, uval)) { return XML_SUCCESS; } return XML_CAN_NOT_CONVERT_TEXT; } return XML_NO_TEXT_NODE; } XMLError XMLElement::QueryInt64Text(int64_t* ival) const { if (FirstChild() && FirstChild()->ToText()) { const char* t = FirstChild()->Value(); if (XMLUtil::ToInt64(t, ival)) { return XML_SUCCESS; } return XML_CAN_NOT_CONVERT_TEXT; } return XML_NO_TEXT_NODE; } XMLError XMLElement::QueryBoolText(bool* bval) const { if (FirstChild() && FirstChild()->ToText()) { const char* t = FirstChild()->Value(); if (XMLUtil::ToBool(t, bval)) { return XML_SUCCESS; } return XML_CAN_NOT_CONVERT_TEXT; } return XML_NO_TEXT_NODE; } XMLError XMLElement::QueryDoubleText(double* dval) const { if (FirstChild() && FirstChild()->ToText()) { const char* t = FirstChild()->Value(); if (XMLUtil::ToDouble(t, dval)) { return XML_SUCCESS; } return XML_CAN_NOT_CONVERT_TEXT; } return XML_NO_TEXT_NODE; } XMLError XMLElement::QueryFloatText(float* fval) const { if (FirstChild() && FirstChild()->ToText()) { const char* t = FirstChild()->Value(); if (XMLUtil::ToFloat(t, fval)) { return XML_SUCCESS; } return XML_CAN_NOT_CONVERT_TEXT; } return XML_NO_TEXT_NODE; } int XMLElement::IntText(int defaultValue) const { int i = defaultValue; QueryIntText(&i); return i; } unsigned XMLElement::UnsignedText(unsigned defaultValue) const { unsigned i = defaultValue; QueryUnsignedText(&i); return i; } int64_t XMLElement::Int64Text(int64_t defaultValue) const { int64_t i = defaultValue; QueryInt64Text(&i); return i; } bool XMLElement::BoolText(bool defaultValue) const { bool b = defaultValue; QueryBoolText(&b); return b; } double XMLElement::DoubleText(double defaultValue) const { double d = defaultValue; QueryDoubleText(&d); return d; } float XMLElement::FloatText(float defaultValue) const { float f = defaultValue; QueryFloatText(&f); return f; } XMLAttribute* XMLElement::FindOrCreateAttribute(const char* name) { XMLAttribute* last = 0; XMLAttribute* attrib = 0; for (attrib = _rootAttribute; attrib; last = attrib, attrib = attrib->_next) { if (XMLUtil::StringEqual(attrib->Name(), name)) { break; } } if (!attrib) { attrib = CreateAttribute(); TIXMLASSERT(attrib); if (last) { TIXMLASSERT(last->_next == 0); last->_next = attrib; } else { TIXMLASSERT(_rootAttribute == 0); _rootAttribute = attrib; } attrib->SetName(name); } return attrib; } void XMLElement::DeleteAttribute(const char* name) { XMLAttribute* prev = 0; for (XMLAttribute* a = _rootAttribute; a; a = a->_next) { if (XMLUtil::StringEqual(name, a->Name())) { if (prev) { prev->_next = a->_next; } else { _rootAttribute = a->_next; } DeleteAttribute(a); break; } prev = a; } } char* XMLElement::ParseAttributes(char* p, int* curLineNumPtr) { const char* start = p; XMLAttribute* prevAttribute = 0; // Read the attributes. while (p) { p = XMLUtil::SkipWhiteSpace(p, curLineNumPtr); if (!(*p)) { _document->SetError(XML_ERROR_PARSING_ELEMENT, start, Name(), _parseLineNum); return 0; } // attribute. if (XMLUtil::IsNameStartChar(*p)) { XMLAttribute* attrib = CreateAttribute(); TIXMLASSERT(attrib); attrib->_parseLineNum = _document->_parseCurLineNum; int attrLineNum = attrib->_parseLineNum; p = attrib->ParseDeep(p, _document->ProcessEntities(), curLineNumPtr); if (!p || Attribute(attrib->Name())) { DeleteAttribute(attrib); _document->SetError(XML_ERROR_PARSING_ATTRIBUTE, start, p, attrLineNum); return 0; } // There is a minor bug here: if the attribute in the source xml // document is duplicated, it will not be detected and the // attribute will be doubly added. However, tracking the 'prevAttribute' // avoids re-scanning the attribute list. Preferring performance for // now, may reconsider in the future. if (prevAttribute) { TIXMLASSERT(prevAttribute->_next == 0); prevAttribute->_next = attrib; } else { TIXMLASSERT(_rootAttribute == 0); _rootAttribute = attrib; } prevAttribute = attrib; } // end of the tag else if (*p == '>') { ++p; break; } // end of the tag else if (*p == '/' && *(p + 1) == '>') { _closingType = CLOSED; return p + 2; // done; sealed element. } else { _document->SetError(XML_ERROR_PARSING_ELEMENT, start, p, _parseLineNum); return 0; } } return p; } void XMLElement::DeleteAttribute(XMLAttribute* attribute) { if (attribute == 0) { return; } MemPool* pool = attribute->_memPool; attribute->~XMLAttribute(); pool->Free(attribute); } XMLAttribute* XMLElement::CreateAttribute() { TIXMLASSERT(sizeof(XMLAttribute) == _document->_attributePool.ItemSize()); XMLAttribute* attrib = new (_document->_attributePool.Alloc()) XMLAttribute(); TIXMLASSERT(attrib); attrib->_memPool = &_document->_attributePool; attrib->_memPool->SetTracked(); return attrib; } // // // foobar // char* XMLElement::ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr) { // Read the element name. p = XMLUtil::SkipWhiteSpace(p, curLineNumPtr); // The closing element is the form. It is // parsed just like a regular element then deleted from // the DOM. if (*p == '/') { _closingType = CLOSING; ++p; } p = _value.ParseName(p); if (_value.Empty()) { return 0; } p = ParseAttributes(p, curLineNumPtr); if (!p || !*p || _closingType != OPEN) { return p; } p = XMLNode::ParseDeep(p, parentEndTag, curLineNumPtr); return p; } XMLNode* XMLElement::ShallowClone(XMLDocument* doc) const { if (!doc) { doc = _document; } XMLElement* element = doc->NewElement(Value()); // fixme: this will always allocate memory. Intern? for (const XMLAttribute* a = FirstAttribute(); a; a = a->Next()) { element->SetAttribute(a->Name(), a->Value()); // fixme: this will always allocate memory. Intern? } return element; } bool XMLElement::ShallowEqual(const XMLNode* compare) const { TIXMLASSERT(compare); const XMLElement* other = compare->ToElement(); if (other && XMLUtil::StringEqual(other->Name(), Name())) { const XMLAttribute* a = FirstAttribute(); const XMLAttribute* b = other->FirstAttribute(); while (a && b) { if (!XMLUtil::StringEqual(a->Value(), b->Value())) { return false; } a = a->Next(); b = b->Next(); } if (a || b) { // different count return false; } return true; } return false; } bool XMLElement::Accept(XMLVisitor* visitor) const { TIXMLASSERT(visitor); if (visitor->VisitEnter(*this, _rootAttribute)) { for (const XMLNode* node = FirstChild(); node; node = node->NextSibling()) { if (!node->Accept(visitor)) { break; } } } return visitor->VisitExit(*this); } // --------- XMLDocument ----------- // // Warning: List must match 'enum XMLError' const char* XMLDocument::_errorNames[XML_ERROR_COUNT] = { "XML_SUCCESS", "XML_NO_ATTRIBUTE", "XML_WRONG_ATTRIBUTE_TYPE", "XML_ERROR_FILE_NOT_FOUND", "XML_ERROR_FILE_COULD_NOT_BE_OPENED", "XML_ERROR_FILE_READ_ERROR", "UNUSED_XML_ERROR_ELEMENT_MISMATCH", "XML_ERROR_PARSING_ELEMENT", "XML_ERROR_PARSING_ATTRIBUTE", "UNUSED_XML_ERROR_IDENTIFYING_TAG", "XML_ERROR_PARSING_TEXT", "XML_ERROR_PARSING_CDATA", "XML_ERROR_PARSING_COMMENT", "XML_ERROR_PARSING_DECLARATION", "XML_ERROR_PARSING_UNKNOWN", "XML_ERROR_EMPTY_DOCUMENT", "XML_ERROR_MISMATCHED_ELEMENT", "XML_ERROR_PARSING", "XML_CAN_NOT_CONVERT_TEXT", "XML_NO_TEXT_NODE" }; XMLDocument::XMLDocument(bool processEntities, Whitespace whitespaceMode) : XMLNode(0), _writeBOM(false), _processEntities(processEntities), _errorID(XML_SUCCESS), _whitespaceMode(whitespaceMode), _errorStr1(), _errorStr2(), _errorLineNum(0), _charBuffer(0), _parseCurLineNum(0), _unlinked(), _elementPool(), _attributePool(), _textPool(), _commentPool() { // avoid VC++ C4355 warning about 'this' in initializer list (C4355 is off by default in VS2012+) _document = this; } XMLDocument::~XMLDocument() { Clear(); } void XMLDocument::MarkInUse(XMLNode* node) { TIXMLASSERT(node); TIXMLASSERT(node->_parent == 0); for (int i = 0; i < _unlinked.Size(); ++i) { if (node == _unlinked[i]) { _unlinked.SwapRemove(i); break; } } } void XMLDocument::Clear() { DeleteChildren(); while (_unlinked.Size()) { DeleteNode(_unlinked[0]); // Will remove from _unlinked as part of delete. } #ifdef DEBUG const bool hadError = Error(); #endif ClearError(); delete[] _charBuffer; _charBuffer = 0; #if 0 _textPool.Trace("text"); _elementPool.Trace("element"); _commentPool.Trace("comment"); _attributePool.Trace("attribute"); #endif #ifdef DEBUG if (!hadError) { TIXMLASSERT(_elementPool.CurrentAllocs() == _elementPool.Untracked()); TIXMLASSERT(_attributePool.CurrentAllocs() == _attributePool.Untracked()); TIXMLASSERT(_textPool.CurrentAllocs() == _textPool.Untracked()); TIXMLASSERT(_commentPool.CurrentAllocs() == _commentPool.Untracked()); } #endif } void XMLDocument::DeepCopy(XMLDocument* target) const { TIXMLASSERT(target); if (target == this) { return; // technically success - a no-op. } target->Clear(); for (const XMLNode* node = this->FirstChild(); node; node = node->NextSibling()) { target->InsertEndChild(node->DeepClone(target)); } } XMLElement* XMLDocument::NewElement(const char* name) { XMLElement* ele = CreateUnlinkedNode(_elementPool); ele->SetName(name); return ele; } XMLComment* XMLDocument::NewComment(const char* str) { XMLComment* comment = CreateUnlinkedNode(_commentPool); comment->SetValue(str); return comment; } XMLText* XMLDocument::NewText(const char* str) { XMLText* text = CreateUnlinkedNode(_textPool); text->SetValue(str); return text; } XMLDeclaration* XMLDocument::NewDeclaration(const char* str) { XMLDeclaration* dec = CreateUnlinkedNode(_commentPool); dec->SetValue(str ? str : "xml version=\"1.0\" encoding=\"UTF-8\""); return dec; } XMLUnknown* XMLDocument::NewUnknown(const char* str) { XMLUnknown* unk = CreateUnlinkedNode(_commentPool); unk->SetValue(str); return unk; } static FILE* callfopen(const char* filepath, const char* mode) { TIXMLASSERT(filepath); TIXMLASSERT(mode); #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) FILE* fp = 0; errno_t err = fopen_s(&fp, filepath, mode); if (err) { return 0; } #else FILE* fp = fopen(filepath, mode); #endif return fp; } void XMLDocument::DeleteNode(XMLNode* node) { TIXMLASSERT(node); TIXMLASSERT(node->_document == this); if (node->_parent) { node->_parent->DeleteChild(node); } else { // Isn't in the tree. // Use the parent delete. // Also, we need to mark it tracked: we 'know' // it was never used. node->_memPool->SetTracked(); // Call the static XMLNode version: XMLNode::DeleteNode(node); } } XMLError XMLDocument::LoadFile(const char* filename) { Clear(); FILE* fp = callfopen(filename, "rb"); if (!fp) { SetError(XML_ERROR_FILE_NOT_FOUND, filename, 0, 0); return _errorID; } LoadFile(fp); fclose(fp); return _errorID; } // This is likely overengineered template art to have a check that unsigned long value incremented // by one still fits into size_t. If size_t type is larger than unsigned long type // (x86_64-w64-mingw32 target) then the check is redundant and gcc and clang emit // -Wtype-limits warning. This piece makes the compiler select code with a check when a check // is useful and code with no check when a check is redundant depending on how size_t and unsigned long // types sizes relate to each other. template = sizeof(size_t))> struct LongFitsIntoSizeTMinusOne { static bool Fits(unsigned long value) { return value < (size_t)-1; } }; template <> struct LongFitsIntoSizeTMinusOne { static bool Fits(unsigned long) { return true; } }; XMLError XMLDocument::LoadFile(FILE* fp) { Clear(); fseek(fp, 0, SEEK_SET); if (fgetc(fp) == EOF && ferror(fp) != 0) { SetError(XML_ERROR_FILE_READ_ERROR, 0, 0, 0); return _errorID; } fseek(fp, 0, SEEK_END); const long filelength = ftell(fp); fseek(fp, 0, SEEK_SET); if (filelength == -1L) { SetError(XML_ERROR_FILE_READ_ERROR, 0, 0, 0); return _errorID; } TIXMLASSERT(filelength >= 0); if (!LongFitsIntoSizeTMinusOne<>::Fits(filelength)) { // Cannot handle files which won't fit in buffer together with null terminator SetError(XML_ERROR_FILE_READ_ERROR, 0, 0, 0); return _errorID; } if (filelength == 0) { SetError(XML_ERROR_EMPTY_DOCUMENT, 0, 0, 0); return _errorID; } const size_t size = filelength; TIXMLASSERT(_charBuffer == 0); _charBuffer = new char[size + 1]; size_t read = fread(_charBuffer, 1, size, fp); if (read != size) { SetError(XML_ERROR_FILE_READ_ERROR, 0, 0, 0); return _errorID; } _charBuffer[size] = 0; Parse(); return _errorID; } XMLError XMLDocument::SaveFile(const char* filename, bool compact) { FILE* fp = callfopen(filename, "w"); if (!fp) { SetError(XML_ERROR_FILE_COULD_NOT_BE_OPENED, filename, 0, 0); return _errorID; } SaveFile(fp, compact); fclose(fp); return _errorID; } XMLError XMLDocument::SaveFile(FILE* fp, bool compact) { // Clear any error from the last save, otherwise it will get reported // for *this* call. ClearError(); XMLPrinter stream(fp, compact); Print(&stream); return _errorID; } XMLError XMLDocument::Parse(const char* p, size_t len) { Clear(); if (len == 0 || !p || !*p) { SetError(XML_ERROR_EMPTY_DOCUMENT, 0, 0, 0); return _errorID; } if (len == (size_t)(-1)) { len = strlen(p); } TIXMLASSERT(_charBuffer == 0); _charBuffer = new char[len + 1]; memcpy(_charBuffer, p, len); _charBuffer[len] = 0; Parse(); if (Error()) { // clean up now essentially dangling memory. // and the parse fail can put objects in the // pools that are dead and inaccessible. DeleteChildren(); _elementPool.Clear(); _attributePool.Clear(); _textPool.Clear(); _commentPool.Clear(); } return _errorID; } void XMLDocument::Print(XMLPrinter* streamer) const { if (streamer) { Accept(streamer); } else { XMLPrinter stdoutStreamer(stdout); Accept(&stdoutStreamer); } } void XMLDocument::SetError(XMLError error, const char* str1, const char* str2, int lineNum) { TIXMLASSERT(error >= 0 && error < XML_ERROR_COUNT); _errorID = error; _errorStr1.Reset(); _errorStr2.Reset(); _errorLineNum = lineNum; if (str1) _errorStr1.SetStr(str1); if (str2) _errorStr2.SetStr(str2); } /*static*/ const char* XMLDocument::ErrorIDToName(XMLError errorID) { TIXMLASSERT(errorID >= 0 && errorID < XML_ERROR_COUNT); const char* errorName = _errorNames[errorID]; TIXMLASSERT(errorName && errorName[0]); return errorName; } const char* XMLDocument::GetErrorStr1() const { return _errorStr1.GetStr(); } const char* XMLDocument::GetErrorStr2() const { return _errorStr2.GetStr(); } const char* XMLDocument::ErrorName() const { return ErrorIDToName(_errorID); } void XMLDocument::PrintError() const { if (Error()) { static const int LEN = 20; char buf1[LEN] = { 0 }; char buf2[LEN] = { 0 }; if (!_errorStr1.Empty()) { TIXML_SNPRINTF(buf1, LEN, "%s", _errorStr1.GetStr()); } if (!_errorStr2.Empty()) { TIXML_SNPRINTF(buf2, LEN, "%s", _errorStr2.GetStr()); } // Should check INT_MIN <= _errorID && _errorId <= INT_MAX, but that // causes a clang "always true" -Wtautological-constant-out-of-range-compare warning TIXMLASSERT(0 <= _errorID && XML_ERROR_COUNT - 1 <= INT_MAX); printf("XMLDocument error id=%d '%s' str1=%s str2=%s line=%d\n", static_cast(_errorID), ErrorName(), buf1, buf2, _errorLineNum); } } void XMLDocument::Parse() { TIXMLASSERT(NoChildren()); // Clear() must have been called previously TIXMLASSERT(_charBuffer); _parseCurLineNum = 1; _parseLineNum = 1; char* p = _charBuffer; p = XMLUtil::SkipWhiteSpace(p, &_parseCurLineNum); p = const_cast(XMLUtil::ReadBOM(p, &_writeBOM)); if (!*p) { SetError(XML_ERROR_EMPTY_DOCUMENT, 0, 0, 0); return; } ParseDeep(p, 0, &_parseCurLineNum); } XMLPrinter::XMLPrinter(FILE* file, bool compact, int depth) : _elementJustOpened(false), _stack(), _firstElement(true), _fp(file), _depth(depth), _textDepth(-1), _processEntities(true), _compactMode(compact), _buffer() { for (int i = 0; i'] = true; // not required, but consistency is nice _buffer.Push(0); } void XMLPrinter::Print(const char* format, ...) { va_list va; va_start(va, format); if (_fp) { vfprintf(_fp, format, va); } else { const int len = TIXML_VSCPRINTF(format, va); // Close out and re-start the va-args va_end(va); TIXMLASSERT(len >= 0); va_start(va, format); TIXMLASSERT(_buffer.Size() > 0 && _buffer[_buffer.Size() - 1] == 0); char* p = _buffer.PushArr(len) - 1; // back up over the null terminator. TIXML_VSNPRINTF(p, len + 1, format, va); } va_end(va); } void XMLPrinter::PrintSpace(int depth) { for (int i = 0; i 0 && *q < ENTITY_RANGE) { // Check for entities. If one is found, flush // the stream up until the entity, write the // entity, and keep looking. if (flag[(unsigned char)(*q)]) { while (p < q) { const size_t delta = q - p; // %.*s accepts type int as "precision" const int toPrint = (INT_MAX < delta) ? INT_MAX : (int)delta; Print("%.*s", toPrint, p); p += toPrint; } bool entityPatternPrinted = false; for (int i = 0; i"); } else { if (_textDepth < 0 && !compactMode) { Print("\n"); PrintSpace(_depth); } Print("", name); } if (_textDepth == _depth) { _textDepth = -1; } if (_depth == 0 && !compactMode) { Print("\n"); } _elementJustOpened = false; } void XMLPrinter::SealElementIfJustOpened() { if (!_elementJustOpened) { return; } _elementJustOpened = false; Print(">"); } void XMLPrinter::PushText(const char* text, bool cdata) { _textDepth = _depth - 1; SealElementIfJustOpened(); if (cdata) { Print("", text); } else { PrintString(text, true); } } void XMLPrinter::PushText(int64_t value) { char buf[BUF_SIZE]; XMLUtil::ToStr(value, buf, BUF_SIZE); PushText(buf, false); } void XMLPrinter::PushText(int value) { char buf[BUF_SIZE]; XMLUtil::ToStr(value, buf, BUF_SIZE); PushText(buf, false); } void XMLPrinter::PushText(unsigned value) { char buf[BUF_SIZE]; XMLUtil::ToStr(value, buf, BUF_SIZE); PushText(buf, false); } void XMLPrinter::PushText(bool value) { char buf[BUF_SIZE]; XMLUtil::ToStr(value, buf, BUF_SIZE); PushText(buf, false); } void XMLPrinter::PushText(float value) { char buf[BUF_SIZE]; XMLUtil::ToStr(value, buf, BUF_SIZE); PushText(buf, false); } void XMLPrinter::PushText(double value) { char buf[BUF_SIZE]; XMLUtil::ToStr(value, buf, BUF_SIZE); PushText(buf, false); } void XMLPrinter::PushComment(const char* comment) { SealElementIfJustOpened(); if (_textDepth < 0 && !_firstElement && !_compactMode) { Print("\n"); PrintSpace(_depth); } _firstElement = false; Print("", comment); } void XMLPrinter::PushDeclaration(const char* value) { SealElementIfJustOpened(); if (_textDepth < 0 && !_firstElement && !_compactMode) { Print("\n"); PrintSpace(_depth); } _firstElement = false; Print("", value); } void XMLPrinter::PushUnknown(const char* value) { SealElementIfJustOpened(); if (_textDepth < 0 && !_firstElement && !_compactMode) { Print("\n"); PrintSpace(_depth); } _firstElement = false; Print("", value); } bool XMLPrinter::VisitEnter(const XMLDocument& doc) { _processEntities = doc.ProcessEntities(); if (doc.HasBOM()) { PushHeader(true, false); } return true; } bool XMLPrinter::VisitEnter(const XMLElement& element, const XMLAttribute* attribute) { const XMLElement* parentElem = 0; if (element.Parent()) { parentElem = element.Parent()->ToElement(); } const bool compactMode = parentElem ? CompactMode(*parentElem) : _compactMode; OpenElement(element.Name(), compactMode); while (attribute) { PushAttribute(attribute->Name(), attribute->Value()); attribute = attribute->Next(); } return true; } bool XMLPrinter::VisitExit(const XMLElement& element) { CloseElement(CompactMode(element)); return true; } bool XMLPrinter::Visit(const XMLText& text) { PushText(text.Value(), text.CData()); return true; } bool XMLPrinter::Visit(const XMLComment& comment) { PushComment(comment.Value()); return true; } bool XMLPrinter::Visit(const XMLDeclaration& declaration) { PushDeclaration(declaration.Value()); return true; } bool XMLPrinter::Visit(const XMLUnknown& unknown) { PushUnknown(unknown.Value()); return true; } } // namespace tinyxml2 ================================================ FILE: src/util/tinyxml2/tinyxml2.h ================================================ /* Original code by Lee Thomason (www.grinninglizard.com) 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 acknowledgment 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. */ #ifndef TINYXML2_INCLUDED #define TINYXML2_INCLUDED #if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) # include # include # include # include # include # if defined(__PS3__) # include # endif #else # include # include # include # include # include #endif #include /* TODO: intern strings instead of allocation. */ /* gcc: g++ -Wall -DDEBUG tinyxml2.cpp xmltest.cpp -o gccxmltest.exe Formatting, Artistic Style: AStyle.exe --style=1tbs --indent-switches --break-closing-brackets --indent-preprocessor tinyxml2.cpp tinyxml2.h */ #if defined( _DEBUG ) || defined (__DEBUG__) # ifndef DEBUG # define DEBUG # endif #endif #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable: 4251) #endif #ifdef _WIN32 # ifdef TINYXML2_EXPORT # define TINYXML2_LIB __declspec(dllexport) # elif defined(TINYXML2_IMPORT) # define TINYXML2_LIB __declspec(dllimport) # else # define TINYXML2_LIB # endif #elif __GNUC__ >= 4 # define TINYXML2_LIB __attribute__((visibility("default"))) #else # define TINYXML2_LIB #endif #if defined(DEBUG) # if defined(_MSC_VER) # // "(void)0," is for suppressing C4127 warning in "assert(false)", "assert(true)" and the like # define TIXMLASSERT( x ) if ( !((void)0,(x))) { __debugbreak(); } # elif defined (ANDROID_NDK) # include # define TIXMLASSERT( x ) if ( !(x)) { __android_log_assert( "assert", "grinliz", "ASSERT in '%s' at %d.", __FILE__, __LINE__ ); } # else # include # define TIXMLASSERT assert # endif #else # define TIXMLASSERT( x ) {} #endif /* Versioning, past 1.0.14: http://semver.org/ */ static const int TIXML2_MAJOR_VERSION = 5; static const int TIXML2_MINOR_VERSION = 0; static const int TIXML2_PATCH_VERSION = 1; namespace tinyxml2 { class XMLDocument; class XMLElement; class XMLAttribute; class XMLComment; class XMLText; class XMLDeclaration; class XMLUnknown; class XMLPrinter; /* A class that wraps strings. Normally stores the start and end pointers into the XML file itself, and will apply normalization and entity translation if actually read. Can also store (and memory manage) a traditional char[] */ class StrPair { public: enum { NEEDS_ENTITY_PROCESSING = 0x01, NEEDS_NEWLINE_NORMALIZATION = 0x02, NEEDS_WHITESPACE_COLLAPSING = 0x04, TEXT_ELEMENT = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, TEXT_ELEMENT_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, ATTRIBUTE_NAME = 0, ATTRIBUTE_VALUE = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, ATTRIBUTE_VALUE_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, COMMENT = NEEDS_NEWLINE_NORMALIZATION }; StrPair() : _flags(0), _start(0), _end(0) {} ~StrPair(); void Set(char* start, char* end, int flags) { TIXMLASSERT(start); TIXMLASSERT(end); Reset(); _start = start; _end = end; _flags = flags | NEEDS_FLUSH; } const char* GetStr(); bool Empty() const { return _start == _end; } void SetInternedStr(const char* str) { Reset(); _start = const_cast(str); } void SetStr(const char* str, int flags = 0); char* ParseText(char* in, const char* endTag, int strFlags, int* curLineNumPtr); char* ParseName(char* in); void TransferTo(StrPair* other); void Reset(); private: void CollapseWhitespace(); enum { NEEDS_FLUSH = 0x100, NEEDS_DELETE = 0x200 }; int _flags; char* _start; char* _end; StrPair(const StrPair& other); // not supported void operator=(StrPair& other); // not supported, use TransferTo() }; /* A dynamic array of Plain Old Data. Doesn't support constructors, etc. Has a small initial memory pool, so that low or no usage will not cause a call to new/delete */ template class DynArray { public: DynArray() : _mem(_pool), _allocated(INITIAL_SIZE), _size(0) { } ~DynArray() { if (_mem != _pool) { delete[] _mem; } } void Clear() { _size = 0; } void Push(T t) { TIXMLASSERT(_size < INT_MAX); EnsureCapacity(_size + 1); _mem[_size] = t; ++_size; } T* PushArr(int count) { TIXMLASSERT(count >= 0); TIXMLASSERT(_size <= INT_MAX - count); EnsureCapacity(_size + count); T* ret = &_mem[_size]; _size += count; return ret; } T Pop() { TIXMLASSERT(_size > 0); --_size; return _mem[_size]; } void PopArr(int count) { TIXMLASSERT(_size >= count); _size -= count; } bool Empty() const { return _size == 0; } T& operator[](int i) { TIXMLASSERT(i >= 0 && i < _size); return _mem[i]; } const T& operator[](int i) const { TIXMLASSERT(i >= 0 && i < _size); return _mem[i]; } const T& PeekTop() const { TIXMLASSERT(_size > 0); return _mem[_size - 1]; } int Size() const { TIXMLASSERT(_size >= 0); return _size; } int Capacity() const { TIXMLASSERT(_allocated >= INITIAL_SIZE); return _allocated; } void SwapRemove(int i) { TIXMLASSERT(i >= 0 && i < _size); TIXMLASSERT(_size > 0); _mem[i] = _mem[_size - 1]; --_size; } const T* Mem() const { TIXMLASSERT(_mem); return _mem; } T* Mem() { TIXMLASSERT(_mem); return _mem; } private: DynArray(const DynArray&); // not supported void operator=(const DynArray&); // not supported void EnsureCapacity(int cap) { TIXMLASSERT(cap > 0); if (cap > _allocated) { TIXMLASSERT(cap <= INT_MAX / 2); int newAllocated = cap * 2; T* newMem = new T[newAllocated]; TIXMLASSERT(newAllocated >= _size); memcpy(newMem, _mem, sizeof(T)*_size); // warning: not using constructors, only works for PODs if (_mem != _pool) { delete[] _mem; } _mem = newMem; _allocated = newAllocated; } } T* _mem; T _pool[INITIAL_SIZE]; int _allocated; // objects allocated int _size; // number objects in use }; /* Parent virtual class of a pool for fast allocation and deallocation of objects. */ class MemPool { public: MemPool() {} virtual ~MemPool() {} virtual int ItemSize() const = 0; virtual void* Alloc() = 0; virtual void Free(void*) = 0; virtual void SetTracked() = 0; virtual void Clear() = 0; }; /* Template child class to create pools of the correct type. */ template< int ITEM_SIZE > class MemPoolT : public MemPool { public: MemPoolT() : _blockPtrs(), _root(0), _currentAllocs(0), _nAllocs(0), _maxAllocs(0), _nUntracked(0) {} ~MemPoolT() { Clear(); } void Clear() { // Delete the blocks. while (!_blockPtrs.Empty()) { Block* lastBlock = _blockPtrs.Pop(); delete lastBlock; } _root = 0; _currentAllocs = 0; _nAllocs = 0; _maxAllocs = 0; _nUntracked = 0; } virtual int ItemSize() const { return ITEM_SIZE; } int CurrentAllocs() const { return _currentAllocs; } virtual void* Alloc() { if (!_root) { // Need a new block. Block* block = new Block(); _blockPtrs.Push(block); Item* blockItems = block->items; for (int i = 0; i < ITEMS_PER_BLOCK - 1; ++i) { blockItems[i].next = &(blockItems[i + 1]); } blockItems[ITEMS_PER_BLOCK - 1].next = 0; _root = blockItems; } Item* const result = _root; TIXMLASSERT(result != 0); _root = _root->next; ++_currentAllocs; if (_currentAllocs > _maxAllocs) { _maxAllocs = _currentAllocs; } ++_nAllocs; ++_nUntracked; return result; } virtual void Free(void* mem) { if (!mem) { return; } --_currentAllocs; Item* item = static_cast(mem); #ifdef DEBUG memset(item, 0xfe, sizeof(*item)); #endif item->next = _root; _root = item; } void Trace(const char* name) { printf("Mempool %s watermark=%d [%dk] current=%d size=%d nAlloc=%d blocks=%d\n", name, _maxAllocs, _maxAllocs * ITEM_SIZE / 1024, _currentAllocs, ITEM_SIZE, _nAllocs, _blockPtrs.Size()); } void SetTracked() { --_nUntracked; } int Untracked() const { return _nUntracked; } // This number is perf sensitive. 4k seems like a good tradeoff on my machine. // The test file is large, 170k. // Release: VS2010 gcc(no opt) // 1k: 4000 // 2k: 4000 // 4k: 3900 21000 // 16k: 5200 // 32k: 4300 // 64k: 4000 21000 // Declared public because some compilers do not accept to use ITEMS_PER_BLOCK // in private part if ITEMS_PER_BLOCK is private enum { ITEMS_PER_BLOCK = (4 * 1024) / ITEM_SIZE }; private: MemPoolT(const MemPoolT&); // not supported void operator=(const MemPoolT&); // not supported union Item { Item* next; char itemData[ITEM_SIZE]; }; struct Block { Item items[ITEMS_PER_BLOCK]; }; DynArray< Block*, 10 > _blockPtrs; Item* _root; int _currentAllocs; int _nAllocs; int _maxAllocs; int _nUntracked; }; /** Implements the interface to the "Visitor pattern" (see the Accept() method.) If you call the Accept() method, it requires being passed a XMLVisitor class to handle callbacks. For nodes that contain other nodes (Document, Element) you will get called with a VisitEnter/VisitExit pair. Nodes that are always leafs are simply called with Visit(). If you return 'true' from a Visit method, recursive parsing will continue. If you return false, no children of this node or its siblings will be visited. All flavors of Visit methods have a default implementation that returns 'true' (continue visiting). You need to only override methods that are interesting to you. Generally Accept() is called on the XMLDocument, although all nodes support visiting. You should never change the document from a callback. @sa XMLNode::Accept() */ class TINYXML2_LIB XMLVisitor { public: virtual ~XMLVisitor() {} /// Visit a document. virtual bool VisitEnter(const XMLDocument& /*doc*/) { return true; } /// Visit a document. virtual bool VisitExit(const XMLDocument& /*doc*/) { return true; } /// Visit an element. virtual bool VisitEnter(const XMLElement& /*element*/, const XMLAttribute* /*firstAttribute*/) { return true; } /// Visit an element. virtual bool VisitExit(const XMLElement& /*element*/) { return true; } /// Visit a declaration. virtual bool Visit(const XMLDeclaration& /*declaration*/) { return true; } /// Visit a text node. virtual bool Visit(const XMLText& /*text*/) { return true; } /// Visit a comment node. virtual bool Visit(const XMLComment& /*comment*/) { return true; } /// Visit an unknown node. virtual bool Visit(const XMLUnknown& /*unknown*/) { return true; } }; // WARNING: must match XMLDocument::_errorNames[] enum XMLError { XML_SUCCESS = 0, XML_NO_ATTRIBUTE, XML_WRONG_ATTRIBUTE_TYPE, XML_ERROR_FILE_NOT_FOUND, XML_ERROR_FILE_COULD_NOT_BE_OPENED, XML_ERROR_FILE_READ_ERROR, UNUSED_XML_ERROR_ELEMENT_MISMATCH, // remove at next major version XML_ERROR_PARSING_ELEMENT, XML_ERROR_PARSING_ATTRIBUTE, UNUSED_XML_ERROR_IDENTIFYING_TAG, // remove at next major version XML_ERROR_PARSING_TEXT, XML_ERROR_PARSING_CDATA, XML_ERROR_PARSING_COMMENT, XML_ERROR_PARSING_DECLARATION, XML_ERROR_PARSING_UNKNOWN, XML_ERROR_EMPTY_DOCUMENT, XML_ERROR_MISMATCHED_ELEMENT, XML_ERROR_PARSING, XML_CAN_NOT_CONVERT_TEXT, XML_NO_TEXT_NODE, XML_ERROR_COUNT }; /* Utility functionality. */ class TINYXML2_LIB XMLUtil { public: static const char* SkipWhiteSpace(const char* p, int* curLineNumPtr) { TIXMLASSERT(p); while (IsWhiteSpace(*p)) { if (curLineNumPtr && *p == '\n') { ++(*curLineNumPtr); } ++p; } TIXMLASSERT(p); return p; } static char* SkipWhiteSpace(char* p, int* curLineNumPtr) { return const_cast(SkipWhiteSpace(const_cast(p), curLineNumPtr)); } // Anything in the high order range of UTF-8 is assumed to not be whitespace. This isn't // correct, but simple, and usually works. static bool IsWhiteSpace(char p) { return !IsUTF8Continuation(p) && isspace(static_cast(p)); } inline static bool IsNameStartChar(unsigned char ch) { if (ch >= 128) { // This is a heuristic guess in attempt to not implement Unicode-aware isalpha() return true; } if (isalpha(ch)) { return true; } return ch == ':' || ch == '_'; } inline static bool IsNameChar(unsigned char ch) { return IsNameStartChar(ch) || isdigit(ch) || ch == '.' || ch == '-'; } inline static bool StringEqual(const char* p, const char* q, int nChar = INT_MAX) { if (p == q) { return true; } TIXMLASSERT(p); TIXMLASSERT(q); TIXMLASSERT(nChar >= 0); return strncmp(p, q, nChar) == 0; } inline static bool IsUTF8Continuation(char p) { return (p & 0x80) != 0; } static const char* ReadBOM(const char* p, bool* hasBOM); // p is the starting location, // the UTF-8 value of the entity will be placed in value, and length filled in. static const char* GetCharacterRef(const char* p, char* value, int* length); static void ConvertUTF32ToUTF8(unsigned long input, char* output, int* length); // converts primitive types to strings static void ToStr(int v, char* buffer, int bufferSize); static void ToStr(unsigned v, char* buffer, int bufferSize); static void ToStr(bool v, char* buffer, int bufferSize); static void ToStr(float v, char* buffer, int bufferSize); static void ToStr(double v, char* buffer, int bufferSize); static void ToStr(int64_t v, char* buffer, int bufferSize); // converts strings to primitive types static bool ToInt(const char* str, int* value); static bool ToUnsigned(const char* str, unsigned* value); static bool ToBool(const char* str, bool* value); static bool ToFloat(const char* str, float* value); static bool ToDouble(const char* str, double* value); static bool ToInt64(const char* str, int64_t* value); // Changes what is serialized for a boolean value. // Default to "true" and "false". Shouldn't be changed // unless you have a special testing or compatibility need. // Be careful: static, global, & not thread safe. // Be sure to set static const memory as parameters. static void SetBoolSerialization(const char* writeTrue, const char* writeFalse); private: static const char* writeBoolTrue; static const char* writeBoolFalse; }; /** XMLNode is a base class for every object that is in the XML Document Object Model (DOM), except XMLAttributes. Nodes have siblings, a parent, and children which can be navigated. A node is always in a XMLDocument. The type of a XMLNode can be queried, and it can be cast to its more defined type. A XMLDocument allocates memory for all its Nodes. When the XMLDocument gets deleted, all its Nodes will also be deleted. @verbatim A Document can contain: Element (container or leaf) Comment (leaf) Unknown (leaf) Declaration( leaf ) An Element can contain: Element (container or leaf) Text (leaf) Attributes (not on tree) Comment (leaf) Unknown (leaf) @endverbatim */ class TINYXML2_LIB XMLNode { friend class XMLDocument; friend class XMLElement; public: /// Get the XMLDocument that owns this XMLNode. const XMLDocument* GetDocument() const { TIXMLASSERT(_document); return _document; } /// Get the XMLDocument that owns this XMLNode. XMLDocument* GetDocument() { TIXMLASSERT(_document); return _document; } /// Safely cast to an Element, or null. virtual XMLElement* ToElement() { return 0; } /// Safely cast to Text, or null. virtual XMLText* ToText() { return 0; } /// Safely cast to a Comment, or null. virtual XMLComment* ToComment() { return 0; } /// Safely cast to a Document, or null. virtual XMLDocument* ToDocument() { return 0; } /// Safely cast to a Declaration, or null. virtual XMLDeclaration* ToDeclaration() { return 0; } /// Safely cast to an Unknown, or null. virtual XMLUnknown* ToUnknown() { return 0; } virtual const XMLElement* ToElement() const { return 0; } virtual const XMLText* ToText() const { return 0; } virtual const XMLComment* ToComment() const { return 0; } virtual const XMLDocument* ToDocument() const { return 0; } virtual const XMLDeclaration* ToDeclaration() const { return 0; } virtual const XMLUnknown* ToUnknown() const { return 0; } /** The meaning of 'value' changes for the specific type. @verbatim Document: empty (NULL is returned, not an empty string) Element: name of the element Comment: the comment text Unknown: the tag contents Text: the text string @endverbatim */ const char* Value() const; /** Set the Value of an XML node. @sa Value() */ void SetValue(const char* val, bool staticMem = false); /// Gets the line number the node is in, if the document was parsed from a file. int GetLineNum() const { return _parseLineNum; } /// Get the parent of this node on the DOM. const XMLNode* Parent() const { return _parent; } XMLNode* Parent() { return _parent; } /// Returns true if this node has no children. bool NoChildren() const { return !_firstChild; } /// Get the first child node, or null if none exists. const XMLNode* FirstChild() const { return _firstChild; } XMLNode* FirstChild() { return _firstChild; } /** Get the first child element, or optionally the first child element with the specified name. */ const XMLElement* FirstChildElement(const char* name = 0) const; XMLElement* FirstChildElement(const char* name = 0) { return const_cast(const_cast(this)->FirstChildElement(name)); } /// Get the last child node, or null if none exists. const XMLNode* LastChild() const { return _lastChild; } XMLNode* LastChild() { return _lastChild; } /** Get the last child element or optionally the last child element with the specified name. */ const XMLElement* LastChildElement(const char* name = 0) const; XMLElement* LastChildElement(const char* name = 0) { return const_cast(const_cast(this)->LastChildElement(name)); } /// Get the previous (left) sibling node of this node. const XMLNode* PreviousSibling() const { return _prev; } XMLNode* PreviousSibling() { return _prev; } /// Get the previous (left) sibling element of this node, with an optionally supplied name. const XMLElement* PreviousSiblingElement(const char* name = 0) const; XMLElement* PreviousSiblingElement(const char* name = 0) { return const_cast(const_cast(this)->PreviousSiblingElement(name)); } /// Get the next (right) sibling node of this node. const XMLNode* NextSibling() const { return _next; } XMLNode* NextSibling() { return _next; } /// Get the next (right) sibling element of this node, with an optionally supplied name. const XMLElement* NextSiblingElement(const char* name = 0) const; XMLElement* NextSiblingElement(const char* name = 0) { return const_cast(const_cast(this)->NextSiblingElement(name)); } /** Add a child node as the last (right) child. If the child node is already part of the document, it is moved from its old location to the new location. Returns the addThis argument or 0 if the node does not belong to the same document. */ XMLNode* InsertEndChild(XMLNode* addThis); XMLNode* LinkEndChild(XMLNode* addThis) { return InsertEndChild(addThis); } /** Add a child node as the first (left) child. If the child node is already part of the document, it is moved from its old location to the new location. Returns the addThis argument or 0 if the node does not belong to the same document. */ XMLNode* InsertFirstChild(XMLNode* addThis); /** Add a node after the specified child node. If the child node is already part of the document, it is moved from its old location to the new location. Returns the addThis argument or 0 if the afterThis node is not a child of this node, or if the node does not belong to the same document. */ XMLNode* InsertAfterChild(XMLNode* afterThis, XMLNode* addThis); /** Delete all the children of this node. */ void DeleteChildren(); /** Delete a child of this node. */ void DeleteChild(XMLNode* node); /** Make a copy of this node, but not its children. You may pass in a Document pointer that will be the owner of the new Node. If the 'document' is null, then the node returned will be allocated from the current Document. (this->GetDocument()) Note: if called on a XMLDocument, this will return null. */ virtual XMLNode* ShallowClone(XMLDocument* document) const = 0; /** Make a copy of this node and all its children. If the 'target' is null, then the nodes will be allocated in the current document. If 'target' is specified, the memory will be allocated is the specified XMLDocument. NOTE: This is probably not the correct tool to copy a document, since XMLDocuments can have multiple top level XMLNodes. You probably want to use XMLDocument::DeepCopy() */ XMLNode* DeepClone(XMLDocument* target) const; /** Test if 2 nodes are the same, but don't test children. The 2 nodes do not need to be in the same Document. Note: if called on a XMLDocument, this will return false. */ virtual bool ShallowEqual(const XMLNode* compare) const = 0; /** Accept a hierarchical visit of the nodes in the TinyXML-2 DOM. Every node in the XML tree will be conditionally visited and the host will be called back via the XMLVisitor interface. This is essentially a SAX interface for TinyXML-2. (Note however it doesn't re-parse the XML for the callbacks, so the performance of TinyXML-2 is unchanged by using this interface versus any other.) The interface has been based on ideas from: - http://www.saxproject.org/ - http://c2.com/cgi/wiki?HierarchicalVisitorPattern Which are both good references for "visiting". An example of using Accept(): @verbatim XMLPrinter printer; tinyxmlDoc.Accept( &printer ); const char* xmlcstr = printer.CStr(); @endverbatim */ virtual bool Accept(XMLVisitor* visitor) const = 0; /** Set user data into the XMLNode. TinyXML-2 in no way processes or interprets user data. It is initially 0. */ void SetUserData(void* userData) { _userData = userData; } /** Get user data set into the XMLNode. TinyXML-2 in no way processes or interprets user data. It is initially 0. */ void* GetUserData() const { return _userData; } protected: XMLNode(XMLDocument*); virtual ~XMLNode(); virtual char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); XMLDocument* _document; XMLNode* _parent; mutable StrPair _value; int _parseLineNum; XMLNode* _firstChild; XMLNode* _lastChild; XMLNode* _prev; XMLNode* _next; void* _userData; private: MemPool* _memPool; void Unlink(XMLNode* child); static void DeleteNode(XMLNode* node); void InsertChildPreamble(XMLNode* insertThis) const; const XMLElement* ToElementWithName(const char* name) const; XMLNode(const XMLNode&); // not supported XMLNode& operator=(const XMLNode&); // not supported }; /** XML text. Note that a text node can have child element nodes, for example: @verbatim This is bold @endverbatim A text node can have 2 ways to output the next. "normal" output and CDATA. It will default to the mode it was parsed from the XML file and you generally want to leave it alone, but you can change the output mode with SetCData() and query it with CData(). */ class TINYXML2_LIB XMLText : public XMLNode { friend class XMLDocument; public: virtual bool Accept(XMLVisitor* visitor) const; virtual XMLText* ToText() { return this; } virtual const XMLText* ToText() const { return this; } /// Declare whether this should be CDATA or standard text. void SetCData(bool isCData) { _isCData = isCData; } /// Returns true if this is a CDATA text element. bool CData() const { return _isCData; } virtual XMLNode* ShallowClone(XMLDocument* document) const; virtual bool ShallowEqual(const XMLNode* compare) const; protected: XMLText(XMLDocument* doc) : XMLNode(doc), _isCData(false) {} virtual ~XMLText() {} char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); private: bool _isCData; XMLText(const XMLText&); // not supported XMLText& operator=(const XMLText&); // not supported }; /** An XML Comment. */ class TINYXML2_LIB XMLComment : public XMLNode { friend class XMLDocument; public: virtual XMLComment* ToComment() { return this; } virtual const XMLComment* ToComment() const { return this; } virtual bool Accept(XMLVisitor* visitor) const; virtual XMLNode* ShallowClone(XMLDocument* document) const; virtual bool ShallowEqual(const XMLNode* compare) const; protected: XMLComment(XMLDocument* doc); virtual ~XMLComment(); char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); private: XMLComment(const XMLComment&); // not supported XMLComment& operator=(const XMLComment&); // not supported }; /** In correct XML the declaration is the first entry in the file. @verbatim @endverbatim TinyXML-2 will happily read or write files without a declaration, however. The text of the declaration isn't interpreted. It is parsed and written as a string. */ class TINYXML2_LIB XMLDeclaration : public XMLNode { friend class XMLDocument; public: virtual XMLDeclaration* ToDeclaration() { return this; } virtual const XMLDeclaration* ToDeclaration() const { return this; } virtual bool Accept(XMLVisitor* visitor) const; virtual XMLNode* ShallowClone(XMLDocument* document) const; virtual bool ShallowEqual(const XMLNode* compare) const; protected: XMLDeclaration(XMLDocument* doc); virtual ~XMLDeclaration(); char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); private: XMLDeclaration(const XMLDeclaration&); // not supported XMLDeclaration& operator=(const XMLDeclaration&); // not supported }; /** Any tag that TinyXML-2 doesn't recognize is saved as an unknown. It is a tag of text, but should not be modified. It will be written back to the XML, unchanged, when the file is saved. DTD tags get thrown into XMLUnknowns. */ class TINYXML2_LIB XMLUnknown : public XMLNode { friend class XMLDocument; public: virtual XMLUnknown* ToUnknown() { return this; } virtual const XMLUnknown* ToUnknown() const { return this; } virtual bool Accept(XMLVisitor* visitor) const; virtual XMLNode* ShallowClone(XMLDocument* document) const; virtual bool ShallowEqual(const XMLNode* compare) const; protected: XMLUnknown(XMLDocument* doc); virtual ~XMLUnknown(); char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); private: XMLUnknown(const XMLUnknown&); // not supported XMLUnknown& operator=(const XMLUnknown&); // not supported }; /** An attribute is a name-value pair. Elements have an arbitrary number of attributes, each with a unique name. @note The attributes are not XMLNodes. You may only query the Next() attribute in a list. */ class TINYXML2_LIB XMLAttribute { friend class XMLElement; public: /// The name of the attribute. const char* Name() const; /// The value of the attribute. const char* Value() const; /// Gets the line number the attribute is in, if the document was parsed from a file. int GetLineNum() const { return _parseLineNum; } /// The next attribute in the list. const XMLAttribute* Next() const { return _next; } /** IntValue interprets the attribute as an integer, and returns the value. If the value isn't an integer, 0 will be returned. There is no error checking; use QueryIntValue() if you need error checking. */ int IntValue() const { int i = 0; QueryIntValue(&i); return i; } int64_t Int64Value() const { int64_t i = 0; QueryInt64Value(&i); return i; } /// Query as an unsigned integer. See IntValue() unsigned UnsignedValue() const { unsigned i = 0; QueryUnsignedValue(&i); return i; } /// Query as a boolean. See IntValue() bool BoolValue() const { bool b = false; QueryBoolValue(&b); return b; } /// Query as a double. See IntValue() double DoubleValue() const { double d = 0; QueryDoubleValue(&d); return d; } /// Query as a float. See IntValue() float FloatValue() const { float f = 0; QueryFloatValue(&f); return f; } /** QueryIntValue interprets the attribute as an integer, and returns the value in the provided parameter. The function will return XML_SUCCESS on success, and XML_WRONG_ATTRIBUTE_TYPE if the conversion is not successful. */ XMLError QueryIntValue(int* value) const; /// See QueryIntValue XMLError QueryUnsignedValue(unsigned int* value) const; /// See QueryIntValue XMLError QueryInt64Value(int64_t* value) const; /// See QueryIntValue XMLError QueryBoolValue(bool* value) const; /// See QueryIntValue XMLError QueryDoubleValue(double* value) const; /// See QueryIntValue XMLError QueryFloatValue(float* value) const; /// Set the attribute to a string value. void SetAttribute(const char* value); /// Set the attribute to value. void SetAttribute(int value); /// Set the attribute to value. void SetAttribute(unsigned value); /// Set the attribute to value. void SetAttribute(int64_t value); /// Set the attribute to value. void SetAttribute(bool value); /// Set the attribute to value. void SetAttribute(double value); /// Set the attribute to value. void SetAttribute(float value); private: enum { BUF_SIZE = 200 }; XMLAttribute() : _name(), _value(), _parseLineNum(0), _next(0), _memPool(0) {} virtual ~XMLAttribute() {} XMLAttribute(const XMLAttribute&); // not supported void operator=(const XMLAttribute&); // not supported void SetName(const char* name); char* ParseDeep(char* p, bool processEntities, int* curLineNumPtr); mutable StrPair _name; mutable StrPair _value; int _parseLineNum; XMLAttribute* _next; MemPool* _memPool; }; /** The element is a container class. It has a value, the element name, and can contain other elements, text, comments, and unknowns. Elements also contain an arbitrary number of attributes. */ class TINYXML2_LIB XMLElement : public XMLNode { friend class XMLDocument; public: /// Get the name of an element (which is the Value() of the node.) const char* Name() const { return Value(); } /// Set the name of the element. void SetName(const char* str, bool staticMem = false) { SetValue(str, staticMem); } virtual XMLElement* ToElement() { return this; } virtual const XMLElement* ToElement() const { return this; } virtual bool Accept(XMLVisitor* visitor) const; /** Given an attribute name, Attribute() returns the value for the attribute of that name, or null if none exists. For example: @verbatim const char* value = ele->Attribute( "foo" ); @endverbatim The 'value' parameter is normally null. However, if specified, the attribute will only be returned if the 'name' and 'value' match. This allow you to write code: @verbatim if ( ele->Attribute( "foo", "bar" ) ) callFooIsBar(); @endverbatim rather than: @verbatim if ( ele->Attribute( "foo" ) ) { if ( strcmp( ele->Attribute( "foo" ), "bar" ) == 0 ) callFooIsBar(); } @endverbatim */ const char* Attribute(const char* name, const char* value = 0) const; /** Given an attribute name, IntAttribute() returns the value of the attribute interpreted as an integer. The default value will be returned if the attribute isn't present, or if there is an error. (For a method with error checking, see QueryIntAttribute()). */ int IntAttribute(const char* name, int defaultValue = 0) const; /// See IntAttribute() unsigned UnsignedAttribute(const char* name, unsigned defaultValue = 0) const; /// See IntAttribute() int64_t Int64Attribute(const char* name, int64_t defaultValue = 0) const; /// See IntAttribute() bool BoolAttribute(const char* name, bool defaultValue = false) const; /// See IntAttribute() double DoubleAttribute(const char* name, double defaultValue = 0) const; /// See IntAttribute() float FloatAttribute(const char* name, float defaultValue = 0) const; /** Given an attribute name, QueryIntAttribute() returns XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion can't be performed, or XML_NO_ATTRIBUTE if the attribute doesn't exist. If successful, the result of the conversion will be written to 'value'. If not successful, nothing will be written to 'value'. This allows you to provide default value: @verbatim int value = 10; QueryIntAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 @endverbatim */ XMLError QueryIntAttribute(const char* name, int* value) const { const XMLAttribute* a = FindAttribute(name); if (!a) { return XML_NO_ATTRIBUTE; } return a->QueryIntValue(value); } /// See QueryIntAttribute() XMLError QueryUnsignedAttribute(const char* name, unsigned int* value) const { const XMLAttribute* a = FindAttribute(name); if (!a) { return XML_NO_ATTRIBUTE; } return a->QueryUnsignedValue(value); } /// See QueryIntAttribute() XMLError QueryInt64Attribute(const char* name, int64_t* value) const { const XMLAttribute* a = FindAttribute(name); if (!a) { return XML_NO_ATTRIBUTE; } return a->QueryInt64Value(value); } /// See QueryIntAttribute() XMLError QueryBoolAttribute(const char* name, bool* value) const { const XMLAttribute* a = FindAttribute(name); if (!a) { return XML_NO_ATTRIBUTE; } return a->QueryBoolValue(value); } /// See QueryIntAttribute() XMLError QueryDoubleAttribute(const char* name, double* value) const { const XMLAttribute* a = FindAttribute(name); if (!a) { return XML_NO_ATTRIBUTE; } return a->QueryDoubleValue(value); } /// See QueryIntAttribute() XMLError QueryFloatAttribute(const char* name, float* value) const { const XMLAttribute* a = FindAttribute(name); if (!a) { return XML_NO_ATTRIBUTE; } return a->QueryFloatValue(value); } /** Given an attribute name, QueryAttribute() returns XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion can't be performed, or XML_NO_ATTRIBUTE if the attribute doesn't exist. It is overloaded for the primitive types, and is a generally more convenient replacement of QueryIntAttribute() and related functions. If successful, the result of the conversion will be written to 'value'. If not successful, nothing will be written to 'value'. This allows you to provide default value: @verbatim int value = 10; QueryAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 @endverbatim */ int QueryAttribute(const char* name, int* value) const { return QueryIntAttribute(name, value); } int QueryAttribute(const char* name, unsigned int* value) const { return QueryUnsignedAttribute(name, value); } int QueryAttribute(const char* name, int64_t* value) const { return QueryInt64Attribute(name, value); } int QueryAttribute(const char* name, bool* value) const { return QueryBoolAttribute(name, value); } int QueryAttribute(const char* name, double* value) const { return QueryDoubleAttribute(name, value); } int QueryAttribute(const char* name, float* value) const { return QueryFloatAttribute(name, value); } /// Sets the named attribute to value. void SetAttribute(const char* name, const char* value) { XMLAttribute* a = FindOrCreateAttribute(name); a->SetAttribute(value); } /// Sets the named attribute to value. void SetAttribute(const char* name, int value) { XMLAttribute* a = FindOrCreateAttribute(name); a->SetAttribute(value); } /// Sets the named attribute to value. void SetAttribute(const char* name, unsigned value) { XMLAttribute* a = FindOrCreateAttribute(name); a->SetAttribute(value); } /// Sets the named attribute to value. void SetAttribute(const char* name, int64_t value) { XMLAttribute* a = FindOrCreateAttribute(name); a->SetAttribute(value); } /// Sets the named attribute to value. void SetAttribute(const char* name, bool value) { XMLAttribute* a = FindOrCreateAttribute(name); a->SetAttribute(value); } /// Sets the named attribute to value. void SetAttribute(const char* name, double value) { XMLAttribute* a = FindOrCreateAttribute(name); a->SetAttribute(value); } /// Sets the named attribute to value. void SetAttribute(const char* name, float value) { XMLAttribute* a = FindOrCreateAttribute(name); a->SetAttribute(value); } /** Delete an attribute. */ void DeleteAttribute(const char* name); /// Return the first attribute in the list. const XMLAttribute* FirstAttribute() const { return _rootAttribute; } /// Query a specific attribute in the list. const XMLAttribute* FindAttribute(const char* name) const; /** Convenience function for easy access to the text inside an element. Although easy and concise, GetText() is limited compared to getting the XMLText child and accessing it directly. If the first child of 'this' is a XMLText, the GetText() returns the character string of the Text node, else null is returned. This is a convenient method for getting the text of simple contained text: @verbatim This is text const char* str = fooElement->GetText(); @endverbatim 'str' will be a pointer to "This is text". Note that this function can be misleading. If the element foo was created from this XML: @verbatim This is text @endverbatim then the value of str would be null. The first child node isn't a text node, it is another element. From this XML: @verbatim This is text @endverbatim GetText() will return "This is ". */ const char* GetText() const; /** Convenience function for easy access to the text inside an element. Although easy and concise, SetText() is limited compared to creating an XMLText child and mutating it directly. If the first child of 'this' is a XMLText, SetText() sets its value to the given string, otherwise it will create a first child that is an XMLText. This is a convenient method for setting the text of simple contained text: @verbatim This is text fooElement->SetText( "Hullaballoo!" ); Hullaballoo! @endverbatim Note that this function can be misleading. If the element foo was created from this XML: @verbatim This is text @endverbatim then it will not change "This is text", but rather prefix it with a text element: @verbatim Hullaballoo!This is text @endverbatim For this XML: @verbatim @endverbatim SetText() will generate @verbatim Hullaballoo! @endverbatim */ void SetText(const char* inText); /// Convenience method for setting text inside an element. See SetText() for important limitations. void SetText(int value); /// Convenience method for setting text inside an element. See SetText() for important limitations. void SetText(unsigned value); /// Convenience method for setting text inside an element. See SetText() for important limitations. void SetText(int64_t value); /// Convenience method for setting text inside an element. See SetText() for important limitations. void SetText(bool value); /// Convenience method for setting text inside an element. See SetText() for important limitations. void SetText(double value); /// Convenience method for setting text inside an element. See SetText() for important limitations. void SetText(float value); /** Convenience method to query the value of a child text node. This is probably best shown by example. Given you have a document is this form: @verbatim 1 1.4 @endverbatim The QueryIntText() and similar functions provide a safe and easier way to get to the "value" of x and y. @verbatim int x = 0; float y = 0; // types of x and y are contrived for example const XMLElement* xElement = pointElement->FirstChildElement( "x" ); const XMLElement* yElement = pointElement->FirstChildElement( "y" ); xElement->QueryIntText( &x ); yElement->QueryFloatText( &y ); @endverbatim @returns XML_SUCCESS (0) on success, XML_CAN_NOT_CONVERT_TEXT if the text cannot be converted to the requested type, and XML_NO_TEXT_NODE if there is no child text to query. */ XMLError QueryIntText(int* ival) const; /// See QueryIntText() XMLError QueryUnsignedText(unsigned* uval) const; /// See QueryIntText() XMLError QueryInt64Text(int64_t* uval) const; /// See QueryIntText() XMLError QueryBoolText(bool* bval) const; /// See QueryIntText() XMLError QueryDoubleText(double* dval) const; /// See QueryIntText() XMLError QueryFloatText(float* fval) const; int IntText(int defaultValue = 0) const; /// See QueryIntText() unsigned UnsignedText(unsigned defaultValue = 0) const; /// See QueryIntText() int64_t Int64Text(int64_t defaultValue = 0) const; /// See QueryIntText() bool BoolText(bool defaultValue = false) const; /// See QueryIntText() double DoubleText(double defaultValue = 0) const; /// See QueryIntText() float FloatText(float defaultValue = 0) const; // internal: enum ElementClosingType { OPEN, // CLOSED, // CLOSING // }; ElementClosingType ClosingType() const { return _closingType; } virtual XMLNode* ShallowClone(XMLDocument* document) const; virtual bool ShallowEqual(const XMLNode* compare) const; protected: char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); private: XMLElement(XMLDocument* doc); virtual ~XMLElement(); XMLElement(const XMLElement&); // not supported void operator=(const XMLElement&); // not supported XMLAttribute* FindAttribute(const char* name) { return const_cast(const_cast(this)->FindAttribute(name)); } XMLAttribute* FindOrCreateAttribute(const char* name); //void LinkAttribute( XMLAttribute* attrib ); char* ParseAttributes(char* p, int* curLineNumPtr); static void DeleteAttribute(XMLAttribute* attribute); XMLAttribute* CreateAttribute(); enum { BUF_SIZE = 200 }; ElementClosingType _closingType; // The attribute list is ordered; there is no 'lastAttribute' // because the list needs to be scanned for dupes before adding // a new attribute. XMLAttribute* _rootAttribute; }; enum Whitespace { PRESERVE_WHITESPACE, COLLAPSE_WHITESPACE }; /** A Document binds together all the functionality. It can be saved, loaded, and printed to the screen. All Nodes are connected and allocated to a Document. If the Document is deleted, all its Nodes are also deleted. */ class TINYXML2_LIB XMLDocument : public XMLNode { friend class XMLElement; public: /// constructor XMLDocument(bool processEntities = true, Whitespace whitespaceMode = PRESERVE_WHITESPACE); ~XMLDocument(); virtual XMLDocument* ToDocument() { TIXMLASSERT(this == _document); return this; } virtual const XMLDocument* ToDocument() const { TIXMLASSERT(this == _document); return this; } /** Parse an XML file from a character string. Returns XML_SUCCESS (0) on success, or an errorID. You may optionally pass in the 'nBytes', which is the number of bytes which will be parsed. If not specified, TinyXML-2 will assume 'xml' points to a null terminated string. */ XMLError Parse(const char* xml, size_t nBytes = (size_t)(-1)); /** Load an XML file from disk. Returns XML_SUCCESS (0) on success, or an errorID. */ XMLError LoadFile(const char* filename); /** Load an XML file from disk. You are responsible for providing and closing the FILE*. NOTE: The file should be opened as binary ("rb") not text in order for TinyXML-2 to correctly do newline normalization. Returns XML_SUCCESS (0) on success, or an errorID. */ XMLError LoadFile(FILE*); /** Save the XML file to disk. Returns XML_SUCCESS (0) on success, or an errorID. */ XMLError SaveFile(const char* filename, bool compact = false); /** Save the XML file to disk. You are responsible for providing and closing the FILE*. Returns XML_SUCCESS (0) on success, or an errorID. */ XMLError SaveFile(FILE* fp, bool compact = false); bool ProcessEntities() const { return _processEntities; } Whitespace WhitespaceMode() const { return _whitespaceMode; } /** Returns true if this document has a leading Byte Order Mark of UTF8. */ bool HasBOM() const { return _writeBOM; } /** Sets whether to write the BOM when writing the file. */ void SetBOM(bool useBOM) { _writeBOM = useBOM; } /** Return the root element of DOM. Equivalent to FirstChildElement(). To get the first node, use FirstChild(). */ XMLElement* RootElement() { return FirstChildElement(); } const XMLElement* RootElement() const { return FirstChildElement(); } /** Print the Document. If the Printer is not provided, it will print to stdout. If you provide Printer, this can print to a file: @verbatim XMLPrinter printer( fp ); doc.Print( &printer ); @endverbatim Or you can use a printer to print to memory: @verbatim XMLPrinter printer; doc.Print( &printer ); // printer.CStr() has a const char* to the XML @endverbatim */ void Print(XMLPrinter* streamer = 0) const; virtual bool Accept(XMLVisitor* visitor) const; /** Create a new Element associated with this Document. The memory for the Element is managed by the Document. */ XMLElement* NewElement(const char* name); /** Create a new Comment associated with this Document. The memory for the Comment is managed by the Document. */ XMLComment* NewComment(const char* comment); /** Create a new Text associated with this Document. The memory for the Text is managed by the Document. */ XMLText* NewText(const char* text); /** Create a new Declaration associated with this Document. The memory for the object is managed by the Document. If the 'text' param is null, the standard declaration is used.: @verbatim @endverbatim */ XMLDeclaration* NewDeclaration(const char* text = 0); /** Create a new Unknown associated with this Document. The memory for the object is managed by the Document. */ XMLUnknown* NewUnknown(const char* text); /** Delete a node associated with this document. It will be unlinked from the DOM. */ void DeleteNode(XMLNode* node); void SetError(XMLError error, const char* str1, const char* str2, int lineNum); void ClearError() { SetError(XML_SUCCESS, 0, 0, 0); } /// Return true if there was an error parsing the document. bool Error() const { return _errorID != XML_SUCCESS; } /// Return the errorID. XMLError ErrorID() const { return _errorID; } const char* ErrorName() const; static const char* ErrorIDToName(XMLError errorID); /// Return a possibly helpful diagnostic location or string. const char* GetErrorStr1() const; /// Return a possibly helpful secondary diagnostic location or string. const char* GetErrorStr2() const; /// Return the line where the error occured, or zero if unknown. int GetErrorLineNum() const { return _errorLineNum; } /// If there is an error, print it to stdout. void PrintError() const; /// Clear the document, resetting it to the initial state. void Clear(); /** Copies this document to a target document. The target will be completely cleared before the copy. If you want to copy a sub-tree, see XMLNode::DeepClone(). NOTE: that the 'target' must be non-null. */ void DeepCopy(XMLDocument* target) const; // internal char* Identify(char* p, XMLNode** node); // internal void MarkInUse(XMLNode*); virtual XMLNode* ShallowClone(XMLDocument* /*document*/) const { return 0; } virtual bool ShallowEqual(const XMLNode* /*compare*/) const { return false; } private: XMLDocument(const XMLDocument&); // not supported void operator=(const XMLDocument&); // not supported bool _writeBOM; bool _processEntities; XMLError _errorID; Whitespace _whitespaceMode; mutable StrPair _errorStr1; mutable StrPair _errorStr2; int _errorLineNum; char* _charBuffer; int _parseCurLineNum; // Memory tracking does add some overhead. // However, the code assumes that you don't // have a bunch of unlinked nodes around. // Therefore it takes less memory to track // in the document vs. a linked list in the XMLNode, // and the performance is the same. DynArray _unlinked; MemPoolT< sizeof(XMLElement) > _elementPool; MemPoolT< sizeof(XMLAttribute) > _attributePool; MemPoolT< sizeof(XMLText) > _textPool; MemPoolT< sizeof(XMLComment) > _commentPool; static const char* _errorNames[XML_ERROR_COUNT]; void Parse(); template NodeType* CreateUnlinkedNode(MemPoolT& pool); }; template inline NodeType* XMLDocument::CreateUnlinkedNode(MemPoolT& pool) { TIXMLASSERT(sizeof(NodeType) == PoolElementSize); TIXMLASSERT(sizeof(NodeType) == pool.ItemSize()); NodeType* returnNode = new (pool.Alloc()) NodeType(this); TIXMLASSERT(returnNode); returnNode->_memPool = &pool; _unlinked.Push(returnNode); return returnNode; } /** A XMLHandle is a class that wraps a node pointer with null checks; this is an incredibly useful thing. Note that XMLHandle is not part of the TinyXML-2 DOM structure. It is a separate utility class. Take an example: @verbatim @endverbatim Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very easy to write a *lot* of code that looks like: @verbatim XMLElement* root = document.FirstChildElement( "Document" ); if ( root ) { XMLElement* element = root->FirstChildElement( "Element" ); if ( element ) { XMLElement* child = element->FirstChildElement( "Child" ); if ( child ) { XMLElement* child2 = child->NextSiblingElement( "Child" ); if ( child2 ) { // Finally do something useful. @endverbatim And that doesn't even cover "else" cases. XMLHandle addresses the verbosity of such code. A XMLHandle checks for null pointers so it is perfectly safe and correct to use: @verbatim XMLHandle docHandle( &document ); XMLElement* child2 = docHandle.FirstChildElement( "Document" ).FirstChildElement( "Element" ).FirstChildElement().NextSiblingElement(); if ( child2 ) { // do something useful @endverbatim Which is MUCH more concise and useful. It is also safe to copy handles - internally they are nothing more than node pointers. @verbatim XMLHandle handleCopy = handle; @endverbatim See also XMLConstHandle, which is the same as XMLHandle, but operates on const objects. */ class TINYXML2_LIB XMLHandle { public: /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. XMLHandle(XMLNode* node) : _node(node) { } /// Create a handle from a node. XMLHandle(XMLNode& node) : _node(&node) { } /// Copy constructor XMLHandle(const XMLHandle& ref) : _node(ref._node) { } /// Assignment XMLHandle& operator=(const XMLHandle& ref) { _node = ref._node; return *this; } /// Get the first child of this handle. XMLHandle FirstChild() { return XMLHandle(_node ? _node->FirstChild() : 0); } /// Get the first child element of this handle. XMLHandle FirstChildElement(const char* name = 0) { return XMLHandle(_node ? _node->FirstChildElement(name) : 0); } /// Get the last child of this handle. XMLHandle LastChild() { return XMLHandle(_node ? _node->LastChild() : 0); } /// Get the last child element of this handle. XMLHandle LastChildElement(const char* name = 0) { return XMLHandle(_node ? _node->LastChildElement(name) : 0); } /// Get the previous sibling of this handle. XMLHandle PreviousSibling() { return XMLHandle(_node ? _node->PreviousSibling() : 0); } /// Get the previous sibling element of this handle. XMLHandle PreviousSiblingElement(const char* name = 0) { return XMLHandle(_node ? _node->PreviousSiblingElement(name) : 0); } /// Get the next sibling of this handle. XMLHandle NextSibling() { return XMLHandle(_node ? _node->NextSibling() : 0); } /// Get the next sibling element of this handle. XMLHandle NextSiblingElement(const char* name = 0) { return XMLHandle(_node ? _node->NextSiblingElement(name) : 0); } /// Safe cast to XMLNode. This can return null. XMLNode* ToNode() { return _node; } /// Safe cast to XMLElement. This can return null. XMLElement* ToElement() { return (_node ? _node->ToElement() : 0); } /// Safe cast to XMLText. This can return null. XMLText* ToText() { return (_node ? _node->ToText() : 0); } /// Safe cast to XMLUnknown. This can return null. XMLUnknown* ToUnknown() { return (_node ? _node->ToUnknown() : 0); } /// Safe cast to XMLDeclaration. This can return null. XMLDeclaration* ToDeclaration() { return (_node ? _node->ToDeclaration() : 0); } private: XMLNode* _node; }; /** A variant of the XMLHandle class for working with const XMLNodes and Documents. It is the same in all regards, except for the 'const' qualifiers. See XMLHandle for API. */ class TINYXML2_LIB XMLConstHandle { public: XMLConstHandle(const XMLNode* node) : _node(node) { } XMLConstHandle(const XMLNode& node) : _node(&node) { } XMLConstHandle(const XMLConstHandle& ref) : _node(ref._node) { } XMLConstHandle& operator=(const XMLConstHandle& ref) { _node = ref._node; return *this; } const XMLConstHandle FirstChild() const { return XMLConstHandle(_node ? _node->FirstChild() : 0); } const XMLConstHandle FirstChildElement(const char* name = 0) const { return XMLConstHandle(_node ? _node->FirstChildElement(name) : 0); } const XMLConstHandle LastChild() const { return XMLConstHandle(_node ? _node->LastChild() : 0); } const XMLConstHandle LastChildElement(const char* name = 0) const { return XMLConstHandle(_node ? _node->LastChildElement(name) : 0); } const XMLConstHandle PreviousSibling() const { return XMLConstHandle(_node ? _node->PreviousSibling() : 0); } const XMLConstHandle PreviousSiblingElement(const char* name = 0) const { return XMLConstHandle(_node ? _node->PreviousSiblingElement(name) : 0); } const XMLConstHandle NextSibling() const { return XMLConstHandle(_node ? _node->NextSibling() : 0); } const XMLConstHandle NextSiblingElement(const char* name = 0) const { return XMLConstHandle(_node ? _node->NextSiblingElement(name) : 0); } const XMLNode* ToNode() const { return _node; } const XMLElement* ToElement() const { return (_node ? _node->ToElement() : 0); } const XMLText* ToText() const { return (_node ? _node->ToText() : 0); } const XMLUnknown* ToUnknown() const { return (_node ? _node->ToUnknown() : 0); } const XMLDeclaration* ToDeclaration() const { return (_node ? _node->ToDeclaration() : 0); } private: const XMLNode* _node; }; /** Printing functionality. The XMLPrinter gives you more options than the XMLDocument::Print() method. It can: -# Print to memory. -# Print to a file you provide. -# Print XML without a XMLDocument. Print to Memory @verbatim XMLPrinter printer; doc.Print( &printer ); SomeFunction( printer.CStr() ); @endverbatim Print to a File You provide the file pointer. @verbatim XMLPrinter printer( fp ); doc.Print( &printer ); @endverbatim Print without a XMLDocument When loading, an XML parser is very useful. However, sometimes when saving, it just gets in the way. The code is often set up for streaming, and constructing the DOM is just overhead. The Printer supports the streaming case. The following code prints out a trivially simple XML file without ever creating an XML document. @verbatim XMLPrinter printer( fp ); printer.OpenElement( "foo" ); printer.PushAttribute( "foo", "bar" ); printer.CloseElement(); @endverbatim */ class TINYXML2_LIB XMLPrinter : public XMLVisitor { public: /** Construct the printer. If the FILE* is specified, this will print to the FILE. Else it will print to memory, and the result is available in CStr(). If 'compact' is set to true, then output is created with only required whitespace and newlines. */ XMLPrinter(FILE* file = 0, bool compact = false, int depth = 0); virtual ~XMLPrinter() {} /** If streaming, write the BOM and declaration. */ void PushHeader(bool writeBOM, bool writeDeclaration); /** If streaming, start writing an element. The element must be closed with CloseElement() */ void OpenElement(const char* name, bool compactMode = false); /// If streaming, add an attribute to an open element. void PushAttribute(const char* name, const char* value); void PushAttribute(const char* name, int value); void PushAttribute(const char* name, unsigned value); void PushAttribute(const char* name, int64_t value); void PushAttribute(const char* name, bool value); void PushAttribute(const char* name, double value); /// If streaming, close the Element. virtual void CloseElement(bool compactMode = false); /// Add a text node. void PushText(const char* text, bool cdata = false); /// Add a text node from an integer. void PushText(int value); /// Add a text node from an unsigned. void PushText(unsigned value); /// Add a text node from an unsigned. void PushText(int64_t value); /// Add a text node from a bool. void PushText(bool value); /// Add a text node from a float. void PushText(float value); /// Add a text node from a double. void PushText(double value); /// Add a comment void PushComment(const char* comment); void PushDeclaration(const char* value); void PushUnknown(const char* value); virtual bool VisitEnter(const XMLDocument& /*doc*/); virtual bool VisitExit(const XMLDocument& /*doc*/) { return true; } virtual bool VisitEnter(const XMLElement& element, const XMLAttribute* attribute); virtual bool VisitExit(const XMLElement& element); virtual bool Visit(const XMLText& text); virtual bool Visit(const XMLComment& comment); virtual bool Visit(const XMLDeclaration& declaration); virtual bool Visit(const XMLUnknown& unknown); /** If in print to memory mode, return a pointer to the XML file in memory. */ const char* CStr() const { return _buffer.Mem(); } /** If in print to memory mode, return the size of the XML file in memory. (Note the size returned includes the terminating null.) */ int CStrSize() const { return _buffer.Size(); } /** If in print to memory mode, reset the buffer to the beginning. */ void ClearBuffer() { _buffer.Clear(); _buffer.Push(0); _firstElement = true; } protected: virtual bool CompactMode(const XMLElement&) { return _compactMode; } /** Prints out the space before an element. You may override to change the space and tabs used. A PrintSpace() override should call Print(). */ virtual void PrintSpace(int depth); void Print(const char* format, ...); void SealElementIfJustOpened(); bool _elementJustOpened; DynArray< const char*, 10 > _stack; private: void PrintString(const char*, bool restrictedEntitySet); // prints out, after detecting entities. bool _firstElement; FILE* _fp; int _depth; int _textDepth; bool _processEntities; bool _compactMode; enum { ENTITY_RANGE = 64, BUF_SIZE = 200 }; bool _entityFlag[ENTITY_RANGE]; bool _restrictedEntityFlag[ENTITY_RANGE]; DynArray< char, 20 > _buffer; // Prohibit cloning, intentionally not implemented XMLPrinter(const XMLPrinter&); XMLPrinter& operator=(const XMLPrinter&); }; } // tinyxml2 #if defined(_MSC_VER) # pragma warning(pop) #endif #endif // TINYXML2_INCLUDED ================================================ FILE: vcpkg.json ================================================ { "name": "cemu", "version-string": "1.0", "builtin-baseline": "f0fb3ddba5135b80982668de39dbaa139c00d281", "dependencies": [ "pugixml", "zlib", "zstd", { "name": "libzip", "default-features": false }, "rapidjson", "sdl2", "boost-tokenizer", "boost-container", "boost-program-options", "boost-nowide", "boost-algorithm", "boost-functional", "boost-optional", "boost-signals2", "boost-asio", "boost-ptr-container", "boost-property-tree", "boost-static-string", "boost-random", { "name": "fmt", "version>=": "12.1.0" }, "hidapi", "libpng", "glm", { "name": "glslang", "default-features": false }, "zstd", "wxwidgets", "openssl", { "name": "curl", "default-features": false, "features": [ "openssl" ] }, { "name": "dbus", "default-features": false, "platform": "linux" }, { "name": "tiff", "default-features": false, "features": [ "jpeg", "zip" ] }, "libusb" ], "overrides": [ { "name": "glslang", "version": "15.1.0" }, { "name": "sdl2", "version": "2.32.10" }, { "name": "wxwidgets", "version": "3.3.1" } ] }